1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.threed.mesh;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.NoSuchElementException;
25 import java.util.regex.Pattern;
26 import java.util.stream.Collectors;
27
28 import org.apache.commons.geometry.core.GeometryTestUtils;
29 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
30 import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
31 import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
32 import org.apache.commons.geometry.euclidean.threed.Bounds3D;
33 import org.apache.commons.geometry.euclidean.threed.Planes;
34 import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
35 import org.apache.commons.geometry.euclidean.threed.Triangle3D;
36 import org.apache.commons.geometry.euclidean.threed.Vector3D;
37 import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
38 import org.apache.commons.numbers.core.Precision;
39 import org.junit.jupiter.api.Assertions;
40 import org.junit.jupiter.api.Test;
41
42 class SimpleTriangleMeshTest {
43
44 private static final double TEST_EPS = 1e-10;
45
46 private static final Precision.DoubleEquivalence TEST_PRECISION =
47 Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
48
49 @Test
50 void testFrom_verticesAndFaces() {
51
52 final Vector3D[] vertices = {
53 Vector3D.ZERO,
54 Vector3D.of(1, 1, 0),
55 Vector3D.of(1, 1, 1),
56 Vector3D.of(0, 0, 1)
57 };
58
59 final int[][] faceIndices = {{0, 1, 2}, {0, 2, 3}};
60
61
62 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(vertices, faceIndices, TEST_PRECISION);
63
64
65 Assertions.assertEquals(4, mesh.getVertexCount());
66 Assertions.assertEquals(Arrays.asList(vertices), mesh.getVertices());
67
68 Assertions.assertEquals(2, mesh.getFaceCount());
69
70 final List<TriangleMesh.Face> faces = mesh.getFaces();
71 Assertions.assertEquals(2, faces.size());
72
73 final TriangleMesh.Face f1 = faces.get(0);
74 Assertions.assertEquals(0, f1.getIndex());
75 Assertions.assertArrayEquals(new int[] {0, 1, 2}, f1.getVertexIndices());
76 Assertions.assertSame(vertices[0], f1.getPoint1());
77 Assertions.assertSame(vertices[1], f1.getPoint2());
78 Assertions.assertSame(vertices[2], f1.getPoint3());
79 Assertions.assertEquals(Arrays.asList(vertices[0], vertices[1], vertices[2]), f1.getVertices());
80 Assertions.assertTrue(f1.definesPolygon());
81
82 final Triangle3D t1 = f1.getPolygon();
83 Assertions.assertEquals(Arrays.asList(vertices[0], vertices[1], vertices[2]), t1.getVertices());
84
85 final TriangleMesh.Face f2 = faces.get(1);
86 Assertions.assertEquals(1, f2.getIndex());
87 Assertions.assertArrayEquals(new int[] {0, 2, 3}, f2.getVertexIndices());
88 Assertions.assertSame(vertices[0], f2.getPoint1());
89 Assertions.assertSame(vertices[2], f2.getPoint2());
90 Assertions.assertSame(vertices[3], f2.getPoint3());
91 Assertions.assertEquals(Arrays.asList(vertices[0], vertices[2], vertices[3]), f2.getVertices());
92 Assertions.assertTrue(f2.definesPolygon());
93
94 final Triangle3D t2 = f2.getPolygon();
95 Assertions.assertEquals(Arrays.asList(vertices[0], vertices[2], vertices[3]), t2.getVertices());
96
97 final Bounds3D bounds = mesh.getBounds();
98 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, bounds.getMin(), TEST_EPS);
99 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 1), bounds.getMax(), TEST_EPS);
100
101 Assertions.assertSame(TEST_PRECISION, mesh.getPrecision());
102 }
103
104 @Test
105 void testFrom_verticesAndFaces_empty() {
106
107 final Vector3D[] vertices = {};
108
109 final int[][] faceIndices = {};
110
111
112 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(vertices, faceIndices, TEST_PRECISION);
113
114
115 Assertions.assertEquals(0, mesh.getVertexCount());
116 Assertions.assertEquals(0, mesh.getVertices().size());
117
118 Assertions.assertEquals(0, mesh.getFaceCount());
119 Assertions.assertEquals(0, mesh.getFaces().size());
120
121 Assertions.assertNull(mesh.getBounds());
122
123 Assertions.assertTrue(mesh.toTree().isEmpty());
124 }
125
126 @Test
127 void testFrom_boundarySource() {
128
129 final BoundarySource3D src = Parallelepiped.axisAligned(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION);
130
131
132 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(src, TEST_PRECISION);
133
134
135 Assertions.assertEquals(8, mesh.getVertexCount());
136
137 final Vector3D p1 = Vector3D.of(0, 0, 0);
138 final Vector3D p2 = Vector3D.of(0, 0, 1);
139 final Vector3D p3 = Vector3D.of(0, 1, 0);
140 final Vector3D p4 = Vector3D.of(0, 1, 1);
141
142 final Vector3D p5 = Vector3D.of(1, 0, 0);
143 final Vector3D p6 = Vector3D.of(1, 0, 1);
144 final Vector3D p7 = Vector3D.of(1, 1, 0);
145 final Vector3D p8 = Vector3D.of(1, 1, 1);
146
147 final List<Vector3D> vertices = mesh.getVertices();
148 Assertions.assertEquals(8, vertices.size());
149
150 Assertions.assertTrue(vertices.contains(p1));
151 Assertions.assertTrue(vertices.contains(p2));
152 Assertions.assertTrue(vertices.contains(p3));
153 Assertions.assertTrue(vertices.contains(p4));
154 Assertions.assertTrue(vertices.contains(p5));
155 Assertions.assertTrue(vertices.contains(p6));
156 Assertions.assertTrue(vertices.contains(p7));
157 Assertions.assertTrue(vertices.contains(p8));
158
159 Assertions.assertEquals(12, mesh.getFaceCount());
160
161 final RegionBSPTree3D tree = mesh.toTree();
162
163 Assertions.assertEquals(1, tree.getSize(), TEST_EPS);
164 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0.5), tree.getCentroid(), TEST_EPS);
165
166 Assertions.assertSame(TEST_PRECISION, mesh.getPrecision());
167 }
168
169 @Test
170 void testFrom_boundarySource_empty() {
171
172 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(BoundarySource3D.of(Collections.emptyList()),
173 TEST_PRECISION);
174
175
176 Assertions.assertEquals(0, mesh.getVertexCount());
177 Assertions.assertEquals(0, mesh.getVertices().size());
178
179 Assertions.assertEquals(0, mesh.getFaceCount());
180 Assertions.assertEquals(0, mesh.getFaces().size());
181
182 Assertions.assertNull(mesh.getBounds());
183
184 Assertions.assertTrue(mesh.toTree().isEmpty());
185 }
186
187 @Test
188 void testVertices_iterable() {
189
190 final List<Vector3D> vertices = Arrays.asList(
191 Vector3D.ZERO,
192 Vector3D.of(1, 0, 0),
193 Vector3D.of(0, 1, 0)
194 );
195
196 final List<int[]> faceIndices = Collections.singletonList(new int[]{0, 1, 2});
197
198 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(vertices, faceIndices, TEST_PRECISION);
199
200
201 final List<Vector3D> result = new ArrayList<>();
202 mesh.vertices().forEach(result::add);
203
204
205 Assertions.assertEquals(vertices, result);
206 }
207
208 @Test
209 void testFaces_iterable() {
210
211 final List<Vector3D> vertices = Arrays.asList(
212 Vector3D.ZERO,
213 Vector3D.of(1, 0, 0),
214 Vector3D.of(0, 1, 0),
215 Vector3D.of(0, 0, 1)
216 );
217
218 final List<int[]> faceIndices = Arrays.asList(
219 new int[] {0, 1, 2},
220 new int[] {0, 2, 3}
221 );
222
223 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(vertices, faceIndices, TEST_PRECISION);
224
225
226 final List<TriangleMesh.Face> result = new ArrayList<>();
227 mesh.faces().forEach(result::add);
228
229
230 Assertions.assertEquals(2, result.size());
231
232 final TriangleMesh.Face f1 = result.get(0);
233 Assertions.assertEquals(0, f1.getIndex());
234 Assertions.assertArrayEquals(new int[] {0, 1, 2}, f1.getVertexIndices());
235 Assertions.assertSame(vertices.get(0), f1.getPoint1());
236 Assertions.assertSame(vertices.get(1), f1.getPoint2());
237 Assertions.assertSame(vertices.get(2), f1.getPoint3());
238 Assertions.assertEquals(Arrays.asList(vertices.get(0), vertices.get(1), vertices.get(2)), f1.getVertices());
239 Assertions.assertTrue(f1.definesPolygon());
240
241 final TriangleMesh.Face f2 = result.get(1);
242 Assertions.assertEquals(1, f2.getIndex());
243 Assertions.assertArrayEquals(new int[] {0, 2, 3}, f2.getVertexIndices());
244 Assertions.assertSame(vertices.get(0), f2.getPoint1());
245 Assertions.assertSame(vertices.get(2), f2.getPoint2());
246 Assertions.assertSame(vertices.get(3), f2.getPoint3());
247 Assertions.assertEquals(Arrays.asList(vertices.get(0), vertices.get(2), vertices.get(3)), f2.getVertices());
248 Assertions.assertTrue(f2.definesPolygon());
249 }
250
251 @Test
252 void testFaces_iterator() {
253
254 final List<Vector3D> vertices = Arrays.asList(
255 Vector3D.ZERO,
256 Vector3D.of(1, 0, 0),
257 Vector3D.of(0, 1, 0)
258 );
259
260 final List<int[]> faceIndices = Collections.singletonList(new int[]{0, 1, 2}
261 );
262
263 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(vertices, faceIndices, TEST_PRECISION);
264
265
266 final Iterator<TriangleMesh.Face> it = mesh.faces().iterator();
267
268 Assertions.assertTrue(it.hasNext());
269 Assertions.assertEquals(0, it.next().getIndex());
270 Assertions.assertFalse(it.hasNext());
271
272 Assertions.assertThrows(NoSuchElementException.class, it::next);
273 }
274
275 @Test
276 void testTriangleStream() {
277
278 final List<Vector3D> vertices = Arrays.asList(
279 Vector3D.ZERO,
280 Vector3D.of(1, 0, 0),
281 Vector3D.of(0, 1, 0),
282 Vector3D.of(0, 0, 1)
283 );
284
285 final List<int[]> faceIndices = Arrays.asList(
286 new int[] {0, 1, 2},
287 new int[] {0, 2, 3}
288 );
289
290 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(vertices, faceIndices, TEST_PRECISION);
291
292
293 final List<Triangle3D> tris = mesh.triangleStream().collect(Collectors.toList());
294
295
296 Assertions.assertEquals(2, tris.size());
297
298 final Triangle3D t1 = tris.get(0);
299 Assertions.assertSame(vertices.get(0), t1.getPoint1());
300 Assertions.assertSame(vertices.get(1), t1.getPoint2());
301 Assertions.assertSame(vertices.get(2), t1.getPoint3());
302
303 final Triangle3D t2 = tris.get(1);
304 Assertions.assertSame(vertices.get(0), t2.getPoint1());
305 Assertions.assertSame(vertices.get(2), t2.getPoint2());
306 Assertions.assertSame(vertices.get(3), t2.getPoint3());
307 }
308
309 @Test
310 void testToTriangleMesh() {
311
312 final Precision.DoubleEquivalence precision1 = Precision.doubleEquivalenceOfEpsilon(1e-1);
313 final Precision.DoubleEquivalence precision2 = Precision.doubleEquivalenceOfEpsilon(1e-2);
314
315 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(Parallelepiped.unitCube(TEST_PRECISION), precision1);
316
317
318 Assertions.assertSame(mesh, mesh.toTriangleMesh(precision1));
319
320 final SimpleTriangleMesh other = mesh.toTriangleMesh(precision2);
321 Assertions.assertSame(precision2, other.getPrecision());
322 Assertions.assertEquals(mesh.getVertices(), other.getVertices());
323 Assertions.assertEquals(12, other.getFaceCount());
324 for (int i = 0; i < 12; ++i) {
325 Assertions.assertArrayEquals(mesh.getFace(i).getVertexIndices(), other.getFace(i).getVertexIndices());
326 }
327
328 Assertions.assertSame(mesh, mesh.toTriangleMesh(precision1));
329 }
330
331 @Test
332 void testFace_doesNotDefineTriangle() {
333
334 final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-1);
335 final Vector3D[] vertices = {
336 Vector3D.ZERO,
337 Vector3D.of(0.01, -0.01, 0.01),
338 Vector3D.of(0.01, 0.01, 0.01),
339 Vector3D.of(1, 0, 0),
340 Vector3D.of(2, 0.01, 0)
341 };
342 final int[][] faces = {{0, 1, 2}, {0, 3, 4}};
343 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(vertices, faces, precision);
344
345
346 final Pattern msgPattern = Pattern.compile("^Points do not define a plane: .*");
347
348 Assertions.assertFalse(mesh.getFace(0).definesPolygon());
349 GeometryTestUtils.assertThrowsWithMessage(() -> {
350 mesh.getFace(0).getPolygon();
351 }, IllegalArgumentException.class, msgPattern);
352
353 Assertions.assertFalse(mesh.getFace(1).definesPolygon());
354 GeometryTestUtils.assertThrowsWithMessage(() -> {
355 mesh.getFace(1).getPolygon();
356 }, IllegalArgumentException.class, msgPattern);
357 }
358
359 @Test
360 void testToTree_smallNumberOfFaces() {
361
362 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(Parallelepiped.unitCube(TEST_PRECISION), TEST_PRECISION);
363
364
365 final RegionBSPTree3D tree = mesh.toTree();
366
367
368 Assertions.assertFalse(tree.isFull());
369 Assertions.assertFalse(tree.isEmpty());
370 Assertions.assertFalse(tree.isInfinite());
371 Assertions.assertTrue(tree.isFinite());
372
373 Assertions.assertEquals(1, tree.getSize(), 1);
374 Assertions.assertEquals(6, tree.getBoundarySize(), 1);
375
376 Assertions.assertEquals(6, tree.getRoot().height());
377 }
378
379 @Test
380 void testTransform() {
381
382 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(Parallelepiped.unitCube(TEST_PRECISION), TEST_PRECISION);
383
384 final AffineTransformMatrix3D t = AffineTransformMatrix3D.createScale(1, 2, 3)
385 .translate(0.5, 1, 1.5);
386
387
388 final SimpleTriangleMesh result = mesh.transform(t);
389
390
391 Assertions.assertNotSame(mesh, result);
392
393 Assertions.assertEquals(8, result.getVertexCount());
394 Assertions.assertEquals(12, result.getFaceCount());
395
396 final Bounds3D resultBounds = result.getBounds();
397 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, resultBounds.getMin(), TEST_EPS);
398 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3), resultBounds.getMax(), TEST_EPS);
399
400 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 1, 1.5), result.toTree().getCentroid(), TEST_EPS);
401 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, mesh.toTree().getCentroid(), TEST_EPS);
402 }
403
404 @Test
405 void testTransform_empty() {
406
407 final SimpleTriangleMesh mesh = SimpleTriangleMesh.builder(TEST_PRECISION).build();
408
409 final AffineTransformMatrix3D t = AffineTransformMatrix3D.createScale(1, 2, 3);
410
411
412 final SimpleTriangleMesh result = mesh.transform(t);
413
414
415 Assertions.assertEquals(0, result.getVertexCount());
416 Assertions.assertEquals(0, result.getFaceCount());
417
418 Assertions.assertNull(result.getBounds());
419 }
420
421 @Test
422 void testToString() {
423
424 final Triangle3D tri = Planes.triangleFromVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0),
425 TEST_PRECISION);
426 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(BoundarySource3D.of(tri), TEST_PRECISION);
427
428
429 final String str = mesh.toString();
430
431
432 GeometryTestUtils.assertContains("SimpleTriangleMesh[vertexCount= 3, faceCount= 1, bounds= Bounds3D[", str);
433 }
434
435 @Test
436 void testFaceToString() {
437
438 final Triangle3D tri = Planes.triangleFromVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0),
439 TEST_PRECISION);
440 final SimpleTriangleMesh mesh = SimpleTriangleMesh.from(BoundarySource3D.of(tri), TEST_PRECISION);
441
442
443 final String str = mesh.getFace(0).toString();
444
445
446 GeometryTestUtils.assertContains("SimpleTriangleFace[index= 0, vertexIndices= [0, 1, 2], vertices= [(0", str);
447 }
448
449 @Test
450 void testBuilder_mixedBuildMethods() {
451
452 final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-1);
453 final SimpleTriangleMesh.Builder builder = SimpleTriangleMesh.builder(precision);
454
455
456 builder.addVertices(Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 0, 0)));
457 builder.useVertex(Vector3D.of(0, 0, 1));
458 builder.addVertex(Vector3D.of(0, 1, 0));
459 builder.useVertex(Vector3D.of(1, 1, 1));
460
461 builder.addFace(0, 2, 1);
462 builder.addFace(new int[] {1, 2, 3});
463 builder.addFaceUsingVertices(Vector3D.of(0.5, 0, 0), Vector3D.of(1.01, 0, 0), Vector3D.of(1, 1, 0.95));
464
465 final SimpleTriangleMesh mesh = builder.build();
466
467
468 Assertions.assertEquals(6, mesh.getVertexCount());
469 Assertions.assertEquals(3, mesh.getFaceCount());
470
471 final List<TriangleMesh.Face> faces = mesh.getFaces();
472 Assertions.assertEquals(3, faces.size());
473
474 Assertions.assertArrayEquals(new int[] {0, 2, 1}, faces.get(0).getVertexIndices());
475 Assertions.assertArrayEquals(new int[] {1, 2, 3}, faces.get(1).getVertexIndices());
476 Assertions.assertArrayEquals(new int[] {5, 1, 4}, faces.get(2).getVertexIndices());
477 }
478
479 @Test
480 void testBuilder_addVerticesAndFaces() {
481
482 final SimpleTriangleMesh mesh = SimpleTriangleMesh.builder(TEST_PRECISION)
483 .addVertices(new Vector3D[] {
484 Vector3D.ZERO,
485 Vector3D.of(1, 1, 0),
486 Vector3D.of(1, 1, 1),
487 Vector3D.of(0, 0, 1)
488 })
489 .addFaces(new int[][] {
490 {0, 1, 2},
491 {0, 2, 3}
492 })
493 .build();
494
495
496 Assertions.assertEquals(4, mesh.getVertexCount());
497 Assertions.assertEquals(2, mesh.getFaceCount());
498 }
499
500 @Test
501 void testBuilder_invalidFaceIndices() {
502
503 final SimpleTriangleMesh.Builder builder = SimpleTriangleMesh.builder(TEST_PRECISION);
504 builder.useVertex(Vector3D.ZERO);
505 builder.useVertex(Vector3D.of(1, 0, 0));
506 builder.useVertex(Vector3D.of(0, 1, 0));
507
508 final String msgBase = "Invalid vertex index: ";
509
510
511 GeometryTestUtils.assertThrowsWithMessage(() -> {
512 builder.addFace(-1, 1, 2);
513 }, IllegalArgumentException.class, msgBase + "-1");
514
515 GeometryTestUtils.assertThrowsWithMessage(() -> {
516 builder.addFace(0, 3, 2);
517 }, IllegalArgumentException.class, msgBase + "3");
518
519 GeometryTestUtils.assertThrowsWithMessage(() -> {
520 builder.addFace(0, 1, 4);
521 }, IllegalArgumentException.class, msgBase + "4");
522
523 GeometryTestUtils.assertThrowsWithMessage(() -> {
524 builder.addFace(new int[] {-1, 1, 2});
525 }, IllegalArgumentException.class, msgBase + "-1");
526
527 GeometryTestUtils.assertThrowsWithMessage(() -> {
528 builder.addFace(new int[] {0, 3, 2});
529 }, IllegalArgumentException.class, msgBase + "3");
530
531 GeometryTestUtils.assertThrowsWithMessage(() -> {
532 builder.addFace(new int[] {0, 1, 4});
533 }, IllegalArgumentException.class, msgBase + "4");
534
535 GeometryTestUtils.assertThrowsWithMessage(() -> {
536 builder.addFaces(new int[][] {{-1, 1, 2}});
537 }, IllegalArgumentException.class, msgBase + "-1");
538
539 GeometryTestUtils.assertThrowsWithMessage(() -> {
540 builder.addFaces(new int[][] {{0, 3, 2}});
541 }, IllegalArgumentException.class, msgBase + "3");
542
543 GeometryTestUtils.assertThrowsWithMessage(() -> {
544 builder.addFaces(new int[][] {{0, 1, 4}});
545 }, IllegalArgumentException.class, msgBase + "4");
546 }
547
548 @Test
549 void testBuilder_invalidFaceIndexCount() {
550
551 final SimpleTriangleMesh.Builder builder = SimpleTriangleMesh.builder(TEST_PRECISION);
552 builder.useVertex(Vector3D.ZERO);
553 builder.useVertex(Vector3D.of(1, 0, 0));
554 builder.useVertex(Vector3D.of(0, 1, 0));
555 builder.useVertex(Vector3D.of(0, 0, 1));
556
557 final String msgBase = "Face must contain 3 vertex indices; found ";
558
559
560 GeometryTestUtils.assertThrowsWithMessage(() -> {
561 builder.addFace(new int[] {});
562 }, IllegalArgumentException.class, msgBase + "0");
563
564 GeometryTestUtils.assertThrowsWithMessage(() -> {
565 builder.addFace(new int[] {0});
566 }, IllegalArgumentException.class, msgBase + "1");
567
568 GeometryTestUtils.assertThrowsWithMessage(() -> {
569 builder.addFace(new int[] {0, 1});
570 }, IllegalArgumentException.class, msgBase + "2");
571
572 GeometryTestUtils.assertThrowsWithMessage(() -> {
573 builder.addFace(new int[] {0, 1, 3, 4});
574 }, IllegalArgumentException.class, msgBase + "4");
575
576 GeometryTestUtils.assertThrowsWithMessage(() -> {
577 builder.addFaces(new int[][] {{}});
578 }, IllegalArgumentException.class, msgBase + "0");
579
580 GeometryTestUtils.assertThrowsWithMessage(() -> {
581 builder.addFaces(new int[][] {{0}});
582 }, IllegalArgumentException.class, msgBase + "1");
583
584 GeometryTestUtils.assertThrowsWithMessage(() -> {
585 builder.addFaces(new int[][] {{0, 1}});
586 }, IllegalArgumentException.class, msgBase + "2");
587
588 GeometryTestUtils.assertThrowsWithMessage(() -> {
589 builder.addFaces(new int[][] {{0, 1, 2, 3}});
590 }, IllegalArgumentException.class, msgBase + "4");
591 }
592
593 @Test
594 void testBuilder_cannotModifyOnceBuilt() {
595
596 final SimpleTriangleMesh.Builder builder = SimpleTriangleMesh.builder(TEST_PRECISION)
597 .addVertices(new Vector3D[] {
598 Vector3D.ZERO,
599 Vector3D.of(1, 1, 0),
600 Vector3D.of(1, 1, 1),
601 })
602 .addFaces(new int[][] {
603 {0, 1, 2}
604 });
605 builder.build();
606
607 final String msg = "Builder instance cannot be modified: mesh construction is complete";
608
609
610 GeometryTestUtils.assertThrowsWithMessage(() -> {
611 builder.useVertex(Vector3D.ZERO);
612 }, IllegalStateException.class, msg);
613
614 GeometryTestUtils.assertThrowsWithMessage(() -> {
615 builder.addVertex(Vector3D.ZERO);
616 }, IllegalStateException.class, msg);
617
618 GeometryTestUtils.assertThrowsWithMessage(() -> {
619 builder.addVertices(Collections.singletonList(Vector3D.ZERO));
620 }, IllegalStateException.class, msg);
621
622 GeometryTestUtils.assertThrowsWithMessage(() -> {
623 builder.addVertices(new Vector3D[] {Vector3D.ZERO});
624 }, IllegalStateException.class, msg);
625
626 GeometryTestUtils.assertThrowsWithMessage(() -> {
627 builder.addFaceUsingVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0));
628 }, IllegalStateException.class, msg);
629
630 GeometryTestUtils.assertThrowsWithMessage(() -> {
631 builder.addFace(0, 1, 2);
632 }, IllegalStateException.class, msg);
633
634 GeometryTestUtils.assertThrowsWithMessage(() -> {
635 builder.addFaces(Collections.singletonList(new int[]{0, 1, 2}));
636 }, IllegalStateException.class, msg);
637
638 GeometryTestUtils.assertThrowsWithMessage(() -> {
639 builder.addFaces(new int[][] {{0, 1, 2}});
640 }, IllegalStateException.class, msg);
641 }
642
643 @Test
644 void testBuilder_addFaceAndVertices_vs_addFaceUsingVertices() {
645
646 final SimpleTriangleMesh.Builder builder = SimpleTriangleMesh.builder(TEST_PRECISION);
647 final Vector3D p1 = Vector3D.ZERO;
648 final Vector3D p2 = Vector3D.of(1, 0, 0);
649 final Vector3D p3 = Vector3D.of(0, 1, 0);
650
651
652 builder.addFaceUsingVertices(p1, p2, p3);
653 builder.addFaceAndVertices(p1, p2, p3);
654 builder.addFaceUsingVertices(p1, p2, p3);
655
656
657 Assertions.assertEquals(6, builder.getVertexCount());
658 Assertions.assertEquals(3, builder.getFaceCount());
659 Assertions.assertEquals(p1, builder.getVertex(0));
660 Assertions.assertEquals(p1, builder.getVertex(3));
661
662 final SimpleTriangleMesh mesh = builder.build();
663
664 Assertions.assertEquals(6, mesh.getVertexCount());
665 Assertions.assertEquals(3, mesh.getFaceCount());
666
667 final TriangleMesh.Face f1 = mesh.getFace(0);
668 Assertions.assertArrayEquals(new int[] {0, 1, 2}, f1.getVertexIndices());
669
670 final TriangleMesh.Face f2 = mesh.getFace(1);
671 Assertions.assertArrayEquals(new int[] {3, 4, 5}, f2.getVertexIndices());
672
673 final TriangleMesh.Face f3 = mesh.getFace(2);
674 Assertions.assertArrayEquals(new int[] {0, 1, 2}, f3.getVertexIndices());
675 }
676 }