1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.io.euclidean.threed.stl;
18
19 import java.io.ByteArrayOutputStream;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.List;
23
24 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
25 import org.apache.commons.geometry.euclidean.threed.Vector3D;
26 import org.junit.jupiter.api.Assertions;
27 import org.junit.jupiter.api.Test;
28
29 class BinaryStlWriterTest {
30
31 private static final double TEST_EPS = 1e-7;
32
33 private static final int VECTOR_SIZE = 3 * Float.BYTES;
34
35 private final ByteArrayOutputStream out = new ByteArrayOutputStream();
36
37 @Test
38 void testWriteHeader_nullHeaderContent() {
39
40 try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
41 writer.writeHeader(null, Short.MAX_VALUE);
42 }
43
44
45 final byte[] bytes = out.toByteArray();
46 Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4, bytes.length);
47
48 assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
49 Assertions.assertEquals(Short.MAX_VALUE, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, 4));
50 }
51
52 @Test
53 void testWriteHeader_givenHeaderContent() {
54
55 final byte[] headerContent = new byte[StlConstants.BINARY_HEADER_BYTES];
56 Arrays.fill(headerContent, (byte) 1);
57
58
59 try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
60 writer.writeHeader(headerContent, 1);
61 }
62
63
64 final byte[] bytes = out.toByteArray();
65 Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4, bytes.length);
66
67 assertBytes(1, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
68 Assertions.assertEquals(1, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, 4));
69 }
70
71 @Test
72 void testWriteHeader_givenHeaderContentExceedsMaxLength() {
73
74 final byte[] headerContent = new byte[2 * StlConstants.BINARY_HEADER_BYTES];
75 Arrays.fill(headerContent, (byte) 1);
76
77
78 try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
79 writer.writeHeader(headerContent, 0);
80 }
81
82
83 final byte[] bytes = out.toByteArray();
84 Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4, bytes.length);
85
86 assertBytes(1, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
87 Assertions.assertEquals(0, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, 4));
88 }
89
90 @Test
91 void testWriteFacet() {
92
93 try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
94 writer.writeHeader(null, 2);
95
96
97 writer.writeTriangle(
98 Vector3D.of(1, 2, 3),
99 Vector3D.of(4, 5, 6),
100 Vector3D.of(7, 8, 9),
101 Vector3D.of(10, 11, 12));
102
103 writer.writeTriangle(
104 Vector3D.of(-1, -2, -3),
105 Vector3D.of(-4, -5, -6),
106 Vector3D.of(-7, -8, -9),
107 Vector3D.of(-10, -11, -12),
108 512);
109 }
110
111
112 final byte[] bytes = out.toByteArray();
113
114 Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4 + (2 * StlConstants.BINARY_TRIANGLE_BYTES),
115 bytes.length);
116
117 assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
118 Assertions.assertEquals(2, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, Integer.BYTES));
119
120 int offset = StlConstants.BINARY_HEADER_BYTES + 4;
121
122 final List<Vector3D> tri1 = readVectors(bytes, offset, 4);
123
124 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(10, 11, 12).normalize(), tri1.get(0), TEST_EPS);
125 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3), tri1.get(1), TEST_EPS);
126 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(4, 5, 6), tri1.get(2), TEST_EPS);
127 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(7, 8, 9), tri1.get(3), TEST_EPS);
128 offset += 4 * VECTOR_SIZE;
129
130 Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
131 offset += 2;
132
133 final List<Vector3D> tri2 = readVectors(bytes, offset, 4);
134
135 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-10, -11, -12).normalize(), tri2.get(0), TEST_EPS);
136 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, -2, -3), tri2.get(1), TEST_EPS);
137 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-4, -5, -6), tri2.get(2), TEST_EPS);
138 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-7, -8, -9), tri2.get(3), TEST_EPS);
139 offset += 4 * VECTOR_SIZE;
140
141 Assertions.assertEquals(512, readAsInt(bytes, offset, 2));
142 }
143
144 @Test
145 void testWriteFacet_ordersFacetCounterClockwise() {
146
147 try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
148 writer.writeHeader(null, 2);
149
150
151 writer.writeTriangle(
152 Vector3D.ZERO,
153 Vector3D.of(1, 0, 0),
154 Vector3D.of(0, 1, 0),
155 Vector3D.of(0, 0, 1));
156
157 writer.writeTriangle(
158 Vector3D.ZERO,
159 Vector3D.of(0, 1, 0),
160 Vector3D.of(1, 0, 0),
161 Vector3D.of(0, 0, 1));
162 }
163
164
165 final byte[] bytes = out.toByteArray();
166
167 Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4 + (2 * StlConstants.BINARY_TRIANGLE_BYTES),
168 bytes.length);
169
170 assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
171 Assertions.assertEquals(2, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, Integer.BYTES));
172
173 int offset = StlConstants.BINARY_HEADER_BYTES + 4;
174
175 final List<Vector3D> tri1 = readVectors(bytes, offset, 4);
176
177 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), tri1.get(0), TEST_EPS);
178 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri1.get(1), TEST_EPS);
179 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri1.get(2), TEST_EPS);
180 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri1.get(3), TEST_EPS);
181 offset += 4 * VECTOR_SIZE;
182
183 Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
184 offset += 2;
185
186 final List<Vector3D> tri2 = readVectors(bytes, offset, 4);
187
188 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), tri2.get(0), TEST_EPS);
189 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri2.get(1), TEST_EPS);
190 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri2.get(2), TEST_EPS);
191 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri2.get(3), TEST_EPS);
192 offset += 4 * VECTOR_SIZE;
193
194 Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
195 }
196
197 @Test
198 void testWriteFacet_invalidNormalGiven() {
199
200 try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
201 writer.writeHeader(null, 3);
202
203
204 writer.writeTriangle(
205 Vector3D.ZERO,
206 Vector3D.of(1, 0, 0),
207 Vector3D.of(0, 1, 0),
208 Vector3D.ZERO);
209
210 writer.writeTriangle(
211 Vector3D.ZERO,
212 Vector3D.of(0, 1, 0),
213 Vector3D.of(1, 0, 0),
214 null,
215 512);
216
217 writer.writeTriangle(
218 Vector3D.ZERO,
219 Vector3D.ZERO,
220 Vector3D.of(1, 1, 1),
221 null);
222 }
223
224
225 final byte[] bytes = out.toByteArray();
226
227 Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4 + (3 * StlConstants.BINARY_TRIANGLE_BYTES),
228 bytes.length);
229
230 assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
231 Assertions.assertEquals(3, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, Integer.BYTES));
232
233 int offset = StlConstants.BINARY_HEADER_BYTES + 4;
234
235 final List<Vector3D> tri1 = readVectors(bytes, offset, 4);
236
237 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), tri1.get(0), TEST_EPS);
238 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri1.get(1), TEST_EPS);
239 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri1.get(2), TEST_EPS);
240 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri1.get(3), TEST_EPS);
241 offset += 4 * VECTOR_SIZE;
242
243 Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
244 offset += 2;
245
246 final List<Vector3D> tri2 = readVectors(bytes, offset, 4);
247
248 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, -1), tri2.get(0), TEST_EPS);
249 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri2.get(1), TEST_EPS);
250 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri2.get(2), TEST_EPS);
251 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri2.get(3), TEST_EPS);
252 offset += 4 * VECTOR_SIZE;
253
254 Assertions.assertEquals(512, readAsInt(bytes, offset, 2));
255 offset += 2;
256
257 final List<Vector3D> tri3 = readVectors(bytes, offset, 4);
258
259 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri3.get(0), TEST_EPS);
260 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri3.get(1), TEST_EPS);
261 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri3.get(2), TEST_EPS);
262 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 1), tri3.get(3), TEST_EPS);
263 offset += 4 * VECTOR_SIZE;
264
265 Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
266 }
267
268 private static void assertBytes(final int expected, final byte[] actual, final int offset, final int len) {
269 for (int i = 0; i < len; ++i) {
270 Assertions.assertEquals(expected, actual[i + offset]);
271 }
272 }
273
274 private static int readAsInt(final byte[] bytes, final int offset, final int count) {
275 int result = 0;
276
277 for (int i = 0; i < count; ++i) {
278 result |= Byte.toUnsignedInt(bytes[i + offset]) << (i * Byte.SIZE);
279 }
280
281 return result;
282 }
283
284 private static float readFloat(final byte[] bytes, final int offset) {
285 final int value = readAsInt(bytes, offset, Float.BYTES);
286 return Float.intBitsToFloat(value);
287 }
288
289 private static Vector3D readVector(final byte[] bytes, final int offset) {
290 final double x = readFloat(bytes, offset);
291 final double y = readFloat(bytes, offset + Float.BYTES);
292 final double z = readFloat(bytes, offset + (2 * Float.BYTES));
293
294 return Vector3D.of(x, y, z);
295 }
296
297 private static List<Vector3D> readVectors(final byte[] bytes, final int offset, final int vectorCount) {
298 final List<Vector3D> vectors = new ArrayList<>(vectorCount);
299
300 for (int i = 0; i < vectorCount; ++i) {
301 vectors.add(readVector(bytes, (i * VECTOR_SIZE) + offset));
302 }
303
304 return vectors;
305 }
306 }