1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean;
18
19 import java.io.File;
20 import java.util.Arrays;
21 import java.util.List;
22 import java.util.stream.Collectors;
23
24 import org.apache.commons.geometry.core.RegionLocation;
25 import org.apache.commons.geometry.core.partitioning.Split;
26 import org.apache.commons.geometry.core.partitioning.bsp.RegionCutRule;
27 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
28 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
29 import org.apache.commons.geometry.euclidean.oned.Interval;
30 import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
31 import org.apache.commons.geometry.euclidean.oned.Vector1D;
32 import org.apache.commons.geometry.euclidean.testio.TestOBJWriter;
33 import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
34 import org.apache.commons.geometry.euclidean.threed.ConvexPolygon3D;
35 import org.apache.commons.geometry.euclidean.threed.Plane;
36 import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
37 import org.apache.commons.geometry.euclidean.threed.Planes;
38 import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
39 import org.apache.commons.geometry.euclidean.threed.Vector3D;
40 import org.apache.commons.geometry.euclidean.threed.line.Line3D;
41 import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
42 import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
43 import org.apache.commons.geometry.euclidean.threed.line.Ray3D;
44 import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
45 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
46 import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
47 import org.apache.commons.geometry.euclidean.threed.shape.Sphere;
48 import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
49 import org.apache.commons.geometry.euclidean.twod.Line;
50 import org.apache.commons.geometry.euclidean.twod.LinecastPoint2D;
51 import org.apache.commons.geometry.euclidean.twod.Lines;
52 import org.apache.commons.geometry.euclidean.twod.Ray;
53 import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
54 import org.apache.commons.geometry.euclidean.twod.Segment;
55 import org.apache.commons.geometry.euclidean.twod.Vector2D;
56 import org.apache.commons.geometry.euclidean.twod.path.LinePath;
57 import org.apache.commons.geometry.euclidean.twod.shape.Parallelogram;
58 import org.apache.commons.numbers.angle.PlaneAngleRadians;
59 import org.junit.Assert;
60 import org.junit.Test;
61
62
63
64
65 public class DocumentationExamplesTest {
66
67 private static final double TEST_EPS = 1e-12;
68
69 @Test
70 public void testIndexPageExample() {
71
72 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
73
74
75 final RegionBSPTree3D tree = Parallelepiped.unitCube(precision).toTree();
76
77
78 final Sphere sphere = Sphere.from(Vector3D.ZERO, 0.65, precision);
79
80
81
82 tree.difference(sphere.toTree(3));
83
84
85 final double size = tree.getSize();
86 final Vector3D centroid = tree.getCentroid();
87
88
89 final TriangleMesh mesh = tree.toTriangleMesh(precision);
90
91
92 Assert.assertEquals(0.11509505362599505, size, TEST_EPS);
93 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, centroid, TEST_EPS);
94
95 TestOBJWriter.write(mesh, new File("target/index-page-example.obj"));
96 }
97
98 @Test
99 public void testPrecisionContextExample() {
100
101 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-3);
102
103
104 precision.eq(1.0009, 1.0);
105 precision.eq(1.002, 1.0);
106
107
108 precision.compare(1.0009, 1.0);
109 precision.compare(1.002, 1.0);
110
111
112 Assert.assertTrue(precision.eq(1.0009, 1.0));
113 Assert.assertFalse(precision.eq(1.002, 1.0));
114
115 Assert.assertEquals(0, precision.compare(1.0009, 1.0));
116 Assert.assertEquals(1, precision.compare(1.002, 1.0));
117 }
118
119 @Test
120 public void testEqualsVsEqExample() {
121 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
122
123 final Vector2D v1 = Vector2D.of(1, 1);
124 final Vector2D v2 = Vector2D.parse("(1, 1)");
125
126 final Vector2D v3 = Vector2D.of(Math.sqrt(2), 0).transform(
127 AffineTransformMatrix2D.createRotation(0.25 * Math.PI));
128
129 v1.equals(v2);
130 v1.equals(v3);
131
132 v1.eq(v3, precision);
133
134
135 Assert.assertEquals(v1, v2);
136 Assert.assertNotEquals(v1, v3);
137 Assert.assertTrue(v1.eq(v3, precision));
138 }
139
140 @Test
141 public void testManualBSPTreeExample() {
142 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
143
144
145 final RegionBSPTree2D tree = RegionBSPTree2D.empty();
146
147
148
149 tree.getRoot().insertCut(Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.of(1, 1), precision),
150 RegionCutRule.INHERIT);
151
152 RegionBSPTree2D.RegionNode2D currentNode;
153
154
155 currentNode = tree.getRoot().getPlus();
156
157 currentNode.insertCut(Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, precision));
158 currentNode = currentNode.getMinus();
159
160 currentNode.insertCut(Lines.fromPointAndDirection(Vector2D.of(1, 0), Vector2D.Unit.PLUS_Y, precision));
161
162
163 currentNode = tree.getRoot().getMinus();
164
165 currentNode.insertCut(Lines.fromPointAndDirection(Vector2D.of(1, 1), Vector2D.Unit.MINUS_X, precision));
166 currentNode = currentNode.getMinus();
167
168 currentNode.insertCut(Lines.fromPointAndDirection(Vector2D.of(0, 1), Vector2D.Unit.MINUS_Y, precision));
169
170
171 final int count = tree.count();
172 final int height = tree.height();
173 final double size = tree.getSize();
174 final Vector2D centroid = tree.getCentroid();
175
176
177 Assert.assertEquals(1, size, TEST_EPS);
178 Assert.assertEquals(11, count);
179 Assert.assertEquals(3, height);
180 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), centroid, TEST_EPS);
181 }
182
183 @Test
184 public void testHyperplaneSubsetBSPTreeExample() {
185 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
186
187
188 final RegionBSPTree2D tree = RegionBSPTree2D.empty();
189
190
191 tree.insert(Arrays.asList(
192 Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), precision),
193 Lines.segmentFromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), precision),
194 Lines.segmentFromPoints(Vector2D.of(1, 1), Vector2D.of(0, 1), precision),
195 Lines.segmentFromPoints(Vector2D.of(0, 1), Vector2D.ZERO, precision)
196 ));
197
198
199 final int count = tree.count();
200 final int height = tree.height();
201 final double size = tree.getSize();
202 final Vector2D centroid = tree.getCentroid();
203
204
205 Assert.assertEquals(1, size, TEST_EPS);
206 Assert.assertEquals(9, count);
207 Assert.assertEquals(4, height);
208 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), centroid, TEST_EPS);
209 }
210
211 @Test
212 public void testIntervalExample() {
213 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
214
215
216 final Interval closed = Interval.of(1, 2, precision);
217 final Interval halfOpen = Interval.min(1, precision);
218
219
220 closed.contains(0.0);
221 halfOpen.contains(Vector1D.ZERO);
222
223 final RegionLocation closedOneLoc = closed.classify(Vector1D.of(1));
224 final RegionLocation halfOpenOneLoc = halfOpen.classify(Vector1D.of(1));
225
226 final RegionLocation closedThreeLoc = closed.classify(3.0);
227 final RegionLocation halfOpenThreeLoc = halfOpen.classify(3.0);
228
229
230 Assert.assertFalse(closed.contains(0));
231 Assert.assertFalse(halfOpen.contains(0));
232
233 Assert.assertEquals(RegionLocation.BOUNDARY, closedOneLoc);
234 Assert.assertEquals(RegionLocation.BOUNDARY, halfOpenOneLoc);
235
236 Assert.assertEquals(RegionLocation.OUTSIDE, closedThreeLoc);
237 Assert.assertEquals(RegionLocation.INSIDE, halfOpenThreeLoc);
238 }
239
240 @Test
241 public void testRegionBSPTree1DExample() {
242 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
243
244
245 final RegionBSPTree1D tree = RegionBSPTree1D.empty();
246
247 tree.add(Interval.of(1, 2, precision));
248 tree.add(Interval.of(1.5, 3, precision));
249 tree.add(Interval.of(-1, -2, precision));
250
251
252 final double size = tree.getSize();
253
254
255 final List<Interval> intervals = tree.toIntervals();
256
257
258 Assert.assertEquals(3, size, TEST_EPS);
259 Assert.assertEquals(2, intervals.size());
260 }
261
262 @Test
263 public void testLineIntersectionExample() {
264 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
265
266
267 final Line a = Lines.fromPoints(Vector2D.ZERO, Vector2D.of(2, 2), precision);
268 final Line b = Lines.fromPointAndDirection(Vector2D.of(1, -1), Vector2D.Unit.PLUS_Y, precision);
269
270
271 final Vector2D intersection = a.intersection(b);
272 final double angleAtoB = a.angle(b);
273 final double angleBtoA = b.angle(a);
274
275
276 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), intersection, TEST_EPS);
277 Assert.assertEquals(0.25 * Math.PI, angleAtoB, TEST_EPS);
278 Assert.assertEquals(-0.25 * Math.PI, angleBtoA, TEST_EPS);
279 }
280
281 @Test
282 public void testLineSegmentIntersectionExample() {
283 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
284
285
286 final Segment segmentA = Lines.segmentFromPoints(Vector2D.of(3, -1), Vector2D.of(3, 1), precision);
287 final Segment segmentB = Lines.segmentFromPoints(Vector2D.of(-3, -1), Vector2D.of(-3, 1), precision);
288
289
290 final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(2, 0), Vector2D.Unit.PLUS_X, precision);
291
292
293 final Vector2D aIntersection = segmentA.intersection(ray);
294 final Vector2D bIntersection = segmentB.intersection(ray);
295
296
297 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 0), aIntersection, TEST_EPS);
298 Assert.assertNull(bIntersection);
299 }
300
301 @Test
302 public void testRegionBSPTree2DExample() {
303 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
304
305
306 final LinePath path = LinePath.builder(precision)
307 .append(Vector2D.ZERO)
308 .append(Vector2D.Unit.PLUS_X)
309 .append(Vector2D.of(1, 1))
310 .append(Vector2D.Unit.PLUS_Y)
311 .build(true);
312
313
314 final RegionBSPTree2D tree = path.toTree();
315
316
317 final RegionBSPTree2D copy = tree.copy();
318
319
320 copy.transform(AffineTransformMatrix2D.createTranslation(Vector2D.of(0.5, 0.5)));
321
322
323
324 tree.union(copy);
325
326
327 final double size = tree.getSize();
328 final Vector2D centroid = tree.getCentroid();
329
330
331
332 final List<LinePath> boundaries = tree.getBoundaryPaths();
333
334
335 Assert.assertEquals(1.75, size, TEST_EPS);
336 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.75, 0.75), centroid, TEST_EPS);
337 Assert.assertEquals(1, boundaries.size());
338 }
339
340 @Test
341 public void testLinecast2DExample() {
342 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
343
344 final Parallelogram box = Parallelogram.axisAligned(Vector2D.ZERO, Vector2D.of(2, 1), precision);
345
346 final LinecastPoint2D pt = box.linecastFirst(
347 Lines.segmentFromPoints(Vector2D.of(1, 0.5), Vector2D.of(4, 0.5), precision));
348
349 final Vector2D intersection = pt.getPoint();
350 final Vector2D normal = pt.getNormal();
351
352
353 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 0.5), intersection, TEST_EPS);
354 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 0), normal, TEST_EPS);
355 }
356
357 @Test
358 public void testPlaneIntersectionExample() {
359 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
360
361
362 final Plane a = Planes.fromPointAndNormal(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Z, precision);
363 final Plane b = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 1, 1),
364 Vector3D.Unit.PLUS_Z, Vector3D.Unit.MINUS_Y, precision);
365
366
367 final Line3D line = a.intersection(b);
368
369 final Vector3D dir = line.getDirection();
370
371
372 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Y, dir, TEST_EPS);
373 }
374
375 @Test
376 public void testTransform3DExample() {
377 final List<Vector3D> inputPts = Arrays.asList(
378 Vector3D.ZERO,
379 Vector3D.Unit.PLUS_X,
380 Vector3D.Unit.PLUS_Y,
381 Vector3D.Unit.PLUS_Z);
382
383
384 final AffineTransformMatrix3D mat = AffineTransformMatrix3D.createScale(2)
385 .translate(Vector3D.of(1, 2, 3));
386
387 final QuaternionRotation rot = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z,
388 PlaneAngleRadians.PI_OVER_TWO);
389
390
391 final List<Vector3D> matOutput = inputPts.stream()
392 .map(mat)
393 .collect(Collectors.toList());
394
395 final List<Vector3D> rotOutput = inputPts.stream()
396 .map(rot)
397 .collect(Collectors.toList());
398
399
400 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3), matOutput.get(0), TEST_EPS);
401 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 2, 3), matOutput.get(1), TEST_EPS);
402 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 4, 3), matOutput.get(2), TEST_EPS);
403 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 5), matOutput.get(3), TEST_EPS);
404
405 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), rotOutput.get(0), TEST_EPS);
406 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), rotOutput.get(1), TEST_EPS);
407 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 0), rotOutput.get(2), TEST_EPS);
408 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), rotOutput.get(3), TEST_EPS);
409 }
410
411 @Test
412 public void testRegionBSPTree3DExample() {
413 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
414
415
416
417 final Vector3D[] vertices = {
418 Vector3D.Unit.PLUS_Z,
419 Vector3D.of(0.5, 0.5, 0.0),
420 Vector3D.of(0.5, -0.5, 0.0),
421 Vector3D.of(-0.5, -0.5, 0.0),
422 Vector3D.of(-0.5, 0.5, 0.0)
423 };
424
425 final int[][] faceIndices = {
426 {1, 0, 2},
427 {2, 0, 3},
428 {3, 0, 4},
429 {4, 0, 1},
430 {1, 2, 3, 4}
431 };
432
433
434 final List<ConvexPolygon3D> faces = Planes.indexedConvexPolygons(vertices, faceIndices, precision);
435 final RegionBSPTree3D tree = RegionBSPTree3D.from(faces);
436
437
438 final Plane cutter = Planes.fromPointAndNormal(tree.getCentroid(), Vector3D.Unit.from(1, 1, 0), precision);
439 final Split<RegionBSPTree3D> split = tree.split(cutter);
440
441
442
443 final RegionBSPTree3D minus = split.getMinus();
444
445 final double minusSize = minus.getSize();
446 final List<PlaneConvexSubset> minusBoundaries = minus.getBoundaries();
447
448
449 Assert.assertEquals(1.0 / 6.0, minusSize, TEST_EPS);
450 Assert.assertEquals(4, minusBoundaries.size());
451 }
452
453 @Test
454 public void testLinecast3DExample() {
455 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);
456
457
458 final RegionBSPTree3D tree = Parallelepiped.axisAligned(Vector3D.ZERO, Vector3D.of(1, 1, 1), precision)
459 .toTree();
460
461
462 final Ray3D ray = Lines3D.rayFromPointAndDirection(Vector3D.of(0.5, 0.5, -1), Vector3D.Unit.PLUS_Z, precision);
463
464
465 final List<LinecastPoint3D> pts = tree.linecast(ray);
466
467
468 final int intersectionCount = pts.size();
469 final Vector3D intersection = pts.get(0).getPoint();
470 final Vector3D normal = pts.get(0).getNormal();
471
472
473 Assert.assertEquals(2, intersectionCount);
474
475 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0), intersection, TEST_EPS);
476 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, -1), normal, TEST_EPS);
477 }
478 }