1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.threed;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.List;
23
24 import org.apache.commons.geometry.core.GeometryTestUtils;
25 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
26 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
27 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
28 import org.apache.commons.geometry.euclidean.threed.line.Line3D;
29 import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
30 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
31 import org.apache.commons.numbers.angle.PlaneAngleRadians;
32 import org.junit.Assert;
33 import org.junit.Test;
34
35 public class PlaneTest {
36
37 private static final double TEST_EPS = 1e-10;
38
39 private static final DoublePrecisionContext TEST_PRECISION =
40 new EpsilonDoublePrecisionContext(TEST_EPS);
41
42 @Test
43 public void testFromNormal() {
44
45 checkPlane(Planes.fromNormal(Vector3D.Unit.PLUS_X, TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_X);
46 checkPlane(Planes.fromNormal(Vector3D.of(7, 0, 0), TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_X);
47
48 checkPlane(Planes.fromNormal(Vector3D.Unit.PLUS_Y, TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Y);
49 checkPlane(Planes.fromNormal(Vector3D.of(0, 5, 0), TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Y);
50
51 checkPlane(Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Z);
52 checkPlane(Planes.fromNormal(Vector3D.of(0, 0, 0.01), TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Z);
53 }
54
55 @Test
56 public void testFromNormal_illegalArguments() {
57
58 GeometryTestUtils.assertThrows(() -> {
59 Planes.fromNormal(Vector3D.ZERO, TEST_PRECISION);
60 }, IllegalArgumentException.class);
61 }
62
63 @Test
64 public void testFromPointAndNormal() {
65
66 final Vector3D pt = Vector3D.of(1, 2, 3);
67
68
69 checkPlane(Planes.fromPointAndNormal(pt, Vector3D.of(0.1, 0, 0), TEST_PRECISION),
70 Vector3D.of(1, 0, 0), Vector3D.Unit.PLUS_X);
71 checkPlane(Planes.fromPointAndNormal(pt, Vector3D.of(0, 2, 0), TEST_PRECISION),
72 Vector3D.of(0, 2, 0), Vector3D.Unit.PLUS_Y);
73 checkPlane(Planes.fromPointAndNormal(pt, Vector3D.of(0, 0, 5), TEST_PRECISION),
74 Vector3D.of(0, 0, 3), Vector3D.Unit.PLUS_Z);
75 }
76
77 @Test
78 public void testFromPointAndNormal_illegalArguments() {
79
80 final Vector3D pt = Vector3D.of(1, 2, 3);
81
82
83 GeometryTestUtils.assertThrows(() -> {
84 Planes.fromPointAndNormal(pt, Vector3D.ZERO, TEST_PRECISION);
85 }, IllegalArgumentException.class);
86 }
87
88 @Test
89 public void testFromPoints() {
90
91 final Vector3D a = Vector3D.of(1, 1, 1);
92 final Vector3D b = Vector3D.of(1, 1, 4.3);
93 final Vector3D c = Vector3D.of(2.5, 1, 1);
94
95
96 checkPlane(Planes.fromPoints(a, b, c, TEST_PRECISION),
97 Vector3D.of(0, 1, 0), Vector3D.Unit.PLUS_Y);
98
99 checkPlane(Planes.fromPoints(a, c, b, TEST_PRECISION),
100 Vector3D.of(0, 1, 0), Vector3D.Unit.MINUS_Y);
101 }
102
103 @Test
104 public void testFromPoints_planeContainsSourcePoints() {
105
106 final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
107 final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
108 final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
109
110
111 final Plane plane = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
112
113
114 Assert.assertTrue(plane.contains(p1));
115 Assert.assertTrue(plane.contains(p2));
116 Assert.assertTrue(plane.contains(p3));
117 }
118
119 @Test
120 public void testFromPoints_illegalArguments() {
121
122 final Vector3D a = Vector3D.of(1, 0, 0);
123 final Vector3D b = Vector3D.of(0, 1, 0);
124
125
126 GeometryTestUtils.assertThrows(() -> {
127 Planes.fromPoints(a, a, a, TEST_PRECISION);
128 }, IllegalArgumentException.class);
129
130 GeometryTestUtils.assertThrows(() -> {
131 Planes.fromPoints(a, a, b, TEST_PRECISION);
132 }, IllegalArgumentException.class);
133
134 GeometryTestUtils.assertThrows(() -> {
135 Planes.fromPoints(a, b, a, TEST_PRECISION);
136 }, IllegalArgumentException.class);
137
138 GeometryTestUtils.assertThrows(() -> {
139 Planes.fromPoints(b, a, a, TEST_PRECISION);
140 }, IllegalArgumentException.class);
141 }
142
143 @Test
144 public void testFromPoints_collection_threePoints() {
145
146 final List<Vector3D> pts = Arrays.asList(
147 Vector3D.of(1, 1, 0),
148 Vector3D.of(1, 1, -1),
149 Vector3D.of(0, 1, 0)
150 );
151
152
153 final Plane plane = Planes.fromPoints(pts, TEST_PRECISION);
154
155
156 checkPlane(plane, Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_Y);
157
158 Assert.assertTrue(plane.contains(pts.get(0)));
159 Assert.assertTrue(plane.contains(pts.get(1)));
160 Assert.assertTrue(plane.contains(pts.get(2)));
161 }
162
163 @Test
164 public void testFromPoints_collection_someCollinearPoints() {
165
166 final List<Vector3D> pts = Arrays.asList(
167 Vector3D.of(1, 0, 2),
168 Vector3D.of(2, 0, 2),
169 Vector3D.of(3, 0, 2),
170 Vector3D.of(0, 1, 2)
171 );
172
173
174 final Plane plane = Planes.fromPoints(pts, TEST_PRECISION);
175
176
177 checkPlane(plane, Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
178
179 Assert.assertTrue(plane.contains(pts.get(0)));
180 Assert.assertTrue(plane.contains(pts.get(1)));
181 Assert.assertTrue(plane.contains(pts.get(2)));
182 Assert.assertTrue(plane.contains(pts.get(3)));
183 }
184
185 @Test
186 public void testFromPoints_collection_concaveWithCollinearAndDuplicatePoints() {
187
188 final List<Vector3D> pts = Arrays.asList(
189 Vector3D.of(1, 0, 1),
190 Vector3D.of(1, 0, 0.5),
191
192 Vector3D.of(1, 0, 0),
193 Vector3D.of(1, 1, -1),
194 Vector3D.of(1, 2, 0),
195 Vector3D.of(1, 2, 1e-15),
196 Vector3D.of(1, 1, -0.5),
197 Vector3D.of(1, 1 + 1e-15, -0.5),
198 Vector3D.of(1 - 1e-15, 1, -0.5),
199 Vector3D.of(1, 0, 0),
200
201 Vector3D.of(1, 0, 0.5),
202 Vector3D.of(1, 0, 1)
203 );
204
205 final Vector3D origin = Vector3D.of(1, 0, 0);
206
207
208 checkPlane(Planes.fromPoints(pts, TEST_PRECISION), origin, Vector3D.Unit.PLUS_X);
209
210 for (int i = 1; i < 12; ++i) {
211 checkPlane(Planes.fromPoints(rotate(pts, i), TEST_PRECISION), origin, Vector3D.Unit.PLUS_X);
212 }
213 }
214
215 @Test
216 public void testFromPoints_collection_choosesBestOrientation() {
217
218 checkPlane(Planes.fromPoints(Arrays.asList(
219 Vector3D.of(1, 0, 2),
220 Vector3D.of(2, 0, 2),
221 Vector3D.of(3, 0, 2),
222 Vector3D.of(3.5, 1, 2)
223 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
224
225 checkPlane(Planes.fromPoints(Arrays.asList(
226 Vector3D.of(1, 0, 2),
227 Vector3D.of(2, 0, 2),
228 Vector3D.of(3, 0, 2),
229 Vector3D.of(3.5, -1, 2)
230 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
231
232 checkPlane(Planes.fromPoints(Arrays.asList(
233 Vector3D.of(1, 0, 2),
234 Vector3D.of(2, 0, 2),
235 Vector3D.of(3, 0, 2),
236 Vector3D.of(3.5, -1, 2),
237 Vector3D.of(4, 0, 2)
238 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
239
240 checkPlane(Planes.fromPoints(Arrays.asList(
241 Vector3D.of(1, 0, 2),
242 Vector3D.of(2, 0, 2),
243 Vector3D.of(3, 0, 2),
244 Vector3D.of(3.5, 1, 2),
245 Vector3D.of(4, -1, 2)
246 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
247
248 checkPlane(Planes.fromPoints(Arrays.asList(
249 Vector3D.of(0, 0, 2),
250 Vector3D.of(1, 0, 2),
251 Vector3D.of(1, 1, 2),
252 Vector3D.of(0, 1, 2),
253 Vector3D.of(0, 0, 2)
254 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
255
256 checkPlane(Planes.fromPoints(Arrays.asList(
257 Vector3D.of(0, 0, 2),
258 Vector3D.of(0, 1, 2),
259 Vector3D.of(1, 1, 2),
260 Vector3D.of(1, 0, 2),
261 Vector3D.of(0, 0, 2)
262 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
263
264 checkPlane(Planes.fromPoints(Arrays.asList(
265 Vector3D.of(0, 0, 2),
266 Vector3D.of(1, 0, 2),
267 Vector3D.of(2, 1, 2),
268 Vector3D.of(3, 0, 2),
269 Vector3D.of(2, 4, 2),
270 Vector3D.of(0, 0, 2)
271 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
272
273 checkPlane(Planes.fromPoints(Arrays.asList(
274 Vector3D.of(0, 0, 2),
275 Vector3D.of(0, 1, 2),
276 Vector3D.of(2, 4, 2),
277 Vector3D.of(3, 0, 2),
278 Vector3D.of(2, 1, 2),
279 Vector3D.of(0, 0, 2)
280 ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
281 }
282
283 @Test
284 public void testFromPoints_collection_illegalArguments() {
285
286 final Vector3D a = Vector3D.ZERO;
287 final Vector3D b = Vector3D.Unit.PLUS_X;
288
289
290 GeometryTestUtils.assertThrows(() -> {
291 Planes.fromPoints(Collections.emptyList(), TEST_PRECISION);
292 }, IllegalArgumentException.class);
293
294 GeometryTestUtils.assertThrows(() -> {
295 Planes.fromPoints(Collections.singletonList(a), TEST_PRECISION);
296 }, IllegalArgumentException.class);
297
298 GeometryTestUtils.assertThrows(() -> {
299 Planes.fromPoints(Arrays.asList(a, b), TEST_PRECISION);
300 }, IllegalArgumentException.class);
301 }
302
303 @Test
304 public void testFromPoints_collection_allPointsCollinear() {
305
306 GeometryTestUtils.assertThrows(() -> {
307 Planes.fromPoints(Arrays.asList(
308 Vector3D.ZERO,
309 Vector3D.Unit.PLUS_X,
310 Vector3D.of(2, 0, 0)
311 ), TEST_PRECISION);
312 }, IllegalArgumentException.class);
313
314 GeometryTestUtils.assertThrows(() -> {
315 Planes.fromPoints(Arrays.asList(
316 Vector3D.ZERO,
317 Vector3D.Unit.PLUS_X,
318 Vector3D.of(2, 0, 0),
319 Vector3D.of(3, 0, 0)
320 ), TEST_PRECISION);
321 }, IllegalArgumentException.class);
322 }
323
324 @Test
325 public void testFromPoints_collection_notEnoughUniquePoints() {
326
327 GeometryTestUtils.assertThrows(() -> {
328 Planes.fromPoints(Arrays.asList(
329 Vector3D.ZERO,
330 Vector3D.ZERO,
331 Vector3D.of(1e-12, 1e-12, 0),
332 Vector3D.Unit.PLUS_X
333 ), TEST_PRECISION);
334 }, IllegalArgumentException.class);
335
336 GeometryTestUtils.assertThrows(() -> {
337 Planes.fromPoints(Arrays.asList(
338 Vector3D.ZERO,
339 Vector3D.of(1e-12, 0, 0),
340 Vector3D.ZERO
341 ), TEST_PRECISION);
342 }, IllegalArgumentException.class);
343 }
344
345 @Test
346 public void testFromPoints_collection_pointsNotOnSamePlane() {
347
348 GeometryTestUtils.assertThrows(() -> {
349 Planes.fromPoints(Arrays.asList(
350 Vector3D.ZERO,
351 Vector3D.Unit.PLUS_X,
352 Vector3D.Unit.PLUS_Y,
353 Vector3D.Unit.PLUS_Z
354 ), TEST_PRECISION);
355 }, IllegalArgumentException.class);
356 }
357
358 @Test
359 public void testGetEmbedding() {
360
361 final Vector3D pt = Vector3D.of(1, 2, 3);
362 EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (x, y, z) -> {
363
364 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.of(x, y, z), TEST_PRECISION);
365
366
367 final EmbeddingPlane embeddingPlane = plane.getEmbedding();
368 final EmbeddingPlane nextEmbeddingPlane = plane.getEmbedding();
369
370
371 Assert.assertSame(plane.getNormal(), embeddingPlane.getNormal());
372 Assert.assertSame(plane.getNormal(), embeddingPlane.getW());
373 Assert.assertEquals(plane.getOriginOffset(), embeddingPlane.getOriginOffset(), TEST_EPS);
374 Assert.assertSame(plane.getPrecision(), embeddingPlane.getPrecision());
375
376 final Vector3D.Unit u = embeddingPlane.getU();
377 final Vector3D.Unit v = embeddingPlane.getV();
378 final Vector3D.Unit w = embeddingPlane.getW();
379
380 Assert.assertEquals(0, u.dot(v), TEST_EPS);
381 Assert.assertEquals(0, u.dot(w), TEST_EPS);
382 Assert.assertEquals(0, v.dot(w), TEST_EPS);
383
384 Assert.assertNotSame(embeddingPlane, nextEmbeddingPlane);
385 Assert.assertEquals(embeddingPlane, nextEmbeddingPlane);
386 });
387 }
388
389 @Test
390 public void testContains_point() {
391
392 final Plane plane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
393 final double halfEps = 0.5 * TEST_EPS;
394
395
396 EuclideanTestUtils.permute(-100, 100, 5, (x, y) -> {
397
398 Assert.assertTrue(plane.contains(Vector3D.of(x, y, 1)));
399 Assert.assertTrue(plane.contains(Vector3D.of(x, y, 1 + halfEps)));
400 Assert.assertTrue(plane.contains(Vector3D.of(x, y, 1 - halfEps)));
401
402 Assert.assertFalse(plane.contains(Vector3D.of(x, y, 0.5)));
403 Assert.assertFalse(plane.contains(Vector3D.of(x, y, 1.5)));
404 });
405 }
406
407 @Test
408 public void testContains_line() {
409
410 final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
411
412
413 Assert.assertTrue(plane.contains(
414 Lines3D.fromPoints(Vector3D.of(1, 0, 0), Vector3D.of(2, 0, 0), TEST_PRECISION)));
415 Assert.assertTrue(plane.contains(
416 Lines3D.fromPoints(Vector3D.of(-1, 0, 0), Vector3D.of(-2, 0, 0), TEST_PRECISION)));
417
418 Assert.assertFalse(plane.contains(
419 Lines3D.fromPoints(Vector3D.of(1, 0, 2), Vector3D.of(2, 0, 2), TEST_PRECISION)));
420 Assert.assertFalse(plane.contains(
421 Lines3D.fromPoints(Vector3D.ZERO, Vector3D.of(2, 0, 2), TEST_PRECISION)));
422 }
423
424 @Test
425 public void testContains_plane() {
426
427 final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
428 final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
429 final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
430 final Plane planeA = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
431
432
433 Assert.assertTrue(planeA.contains(planeA));
434 Assert.assertTrue(planeA.contains(Planes.fromPoints(p1, p3, p2, TEST_PRECISION)));
435 Assert.assertTrue(planeA.contains(Planes.fromPoints(p3, p1, p2, TEST_PRECISION)));
436 Assert.assertTrue(planeA.contains(Planes.fromPoints(p3, p2, p1, TEST_PRECISION)));
437
438 Assert.assertFalse(planeA.contains(Planes.fromPoints(p1, Vector3D.of(11.4, -3.8, 5.1), p2, TEST_PRECISION)));
439
440 final Vector3D offset = planeA.getNormal().multiply(1e-8);
441 Assert.assertFalse(planeA.contains(Planes.fromPoints(p1.add(offset), p2, p3, TEST_PRECISION)));
442 Assert.assertFalse(planeA.contains(Planes.fromPoints(p1, p2.add(offset), p3, TEST_PRECISION)));
443 Assert.assertFalse(planeA.contains(Planes.fromPoints(p1, p2, p3.add(offset), TEST_PRECISION)));
444
445 Assert.assertFalse(planeA.contains(Planes.fromPoints(p1.add(offset),
446 p2.add(offset),
447 p3.add(offset), TEST_PRECISION)));
448 }
449
450 @Test
451 public void testReverse() {
452
453 final Vector3D pt = Vector3D.of(0, 0, 1);
454 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
455
456
457 final Plane reversed = plane.reverse();
458
459
460 checkPlane(reversed, pt, Vector3D.Unit.MINUS_Z);
461
462 Assert.assertTrue(reversed.contains(Vector3D.of(1, 1, 1)));
463 Assert.assertTrue(reversed.contains(Vector3D.of(-1, -1, 1)));
464 Assert.assertFalse(reversed.contains(Vector3D.ZERO));
465
466 Assert.assertEquals(1.0, reversed.offset(Vector3D.ZERO), TEST_EPS);
467 }
468
469 @Test
470 public void testIsParallelAndOffset_line() {
471
472 final Plane plane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
473
474 final Line3D parallelLine = Lines3D.fromPoints(Vector3D.of(1, 0, 2), Vector3D.of(2, 0, 2), TEST_PRECISION);
475 final Line3D nonParallelLine = Lines3D.fromPoints(Vector3D.of(1, 0, 2), Vector3D.of(2, 0, 1), TEST_PRECISION);
476 final Line3D containedLine = Lines3D.fromPoints(Vector3D.of(2, 0, 1), Vector3D.of(1, 0, 1), TEST_PRECISION);
477
478
479 Assert.assertTrue(plane.isParallel(parallelLine));
480 Assert.assertEquals(1.0, plane.offset(parallelLine), TEST_EPS);
481
482 Assert.assertFalse(plane.isParallel(nonParallelLine));
483 Assert.assertEquals(0.0, plane.offset(nonParallelLine), TEST_EPS);
484
485 Assert.assertTrue(plane.isParallel(containedLine));
486 Assert.assertEquals(0.0, plane.offset(containedLine), TEST_EPS);
487 }
488
489 @Test
490 public void testIsParallelAndOffset_plane() {
491
492 final Plane plane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
493 final Plane parallelPlane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
494 final Plane parallelPlane2 = Planes.fromPointAndNormal(Vector3D.of(0, 0, 2), Vector3D.of(0, 0, 1), TEST_PRECISION);
495 final Plane parallelPlane3 = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(0, 0, 1), TEST_PRECISION).reverse();
496 final Plane nonParallelPlane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1),
497 Vector3D.of(1, 1.5, 1).cross(Vector3D.of(0, 1, 1)), TEST_PRECISION);
498 final Plane reversedPlane = plane.reverse();
499
500
501 Assert.assertTrue(plane.isParallel(parallelPlane));
502 Assert.assertEquals(0.0, plane.offset(parallelPlane), TEST_EPS);
503
504 Assert.assertTrue(plane.isParallel(parallelPlane2));
505 Assert.assertEquals(1.0, plane.offset(parallelPlane2), TEST_EPS);
506
507 Assert.assertTrue(plane.isParallel(parallelPlane3));
508 Assert.assertEquals(-1.0, plane.offset(parallelPlane3), TEST_EPS);
509
510 Assert.assertFalse(plane.isParallel(nonParallelPlane));
511 Assert.assertEquals(0.0, plane.offset(nonParallelPlane), TEST_EPS);
512
513 Assert.assertTrue(plane.isParallel(reversedPlane));
514 Assert.assertEquals(0.0, plane.offset(nonParallelPlane), TEST_EPS);
515 }
516
517 @Test
518 public void testOffset_point() {
519
520 final Vector3D p1 = Vector3D.of(1, 1, 1);
521 final Plane plane = Planes.fromPointAndNormal(p1, Vector3D.of(0.2, 0, 0), TEST_PRECISION);
522
523
524 Assert.assertEquals(-5.0, plane.offset(Vector3D.of(-4, 0, 0)), TEST_EPS);
525 Assert.assertEquals(+5.0, plane.offset(Vector3D.of(6, 10, -12)), TEST_EPS);
526 Assert.assertEquals(0.3,
527 plane.offset(Vector3D.linearCombination(1.0, p1, 0.3, plane.getNormal())),
528 TEST_EPS);
529 Assert.assertEquals(-0.3,
530 plane.offset(Vector3D.linearCombination(1.0, p1, -0.3, plane.getNormal())),
531 TEST_EPS);
532 }
533
534 @Test
535 public void testProject_point() {
536
537 final Vector3D pt = Vector3D.of(-3, -2, -1);
538 EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (nx, ny, nz) -> {
539
540 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.of(nx, ny, nz), TEST_PRECISION);
541 EuclideanTestUtils.permute(-4, 4, 1, (x, y, z) -> {
542
543 final Vector3D p = Vector3D.of(x, y, z);
544
545
546 final Vector3D proj = plane.project(p);
547
548
549 Assert.assertTrue(plane.contains(proj));
550 Assert.assertEquals(0, plane.getOrigin().vectorTo(proj).dot(plane.getNormal()), TEST_EPS);
551 });
552 });
553 }
554
555 @Test
556 public void testProject_line() {
557
558 final Plane plane = Planes.fromPointAndNormal(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
559 final Line3D line = Lines3D.fromPoints(Vector3D.of(1, 0, 1), Vector3D.of(2, 0, 2), TEST_PRECISION);
560
561
562 final Line3D projected = plane.project(line);
563
564
565 final Line3D expectedProjection = Lines3D.fromPoints(Vector3D.of(1, 0, 1), Vector3D.of(2, 0, 1), TEST_PRECISION);
566 Assert.assertEquals(expectedProjection, projected);
567
568 Assert.assertTrue(plane.contains(projected));
569
570 Assert.assertTrue(projected.contains(Vector3D.of(1, 0, 1)));
571 Assert.assertTrue(projected.contains(Vector3D.of(2, 0, 1)));
572 }
573
574 @Test
575 public void testTransform_rotationAroundPoint() {
576
577 final Vector3D pt = Vector3D.of(0, 0, 1);
578 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
579
580 final AffineTransformMatrix3D mat = AffineTransformMatrix3D.createRotation(pt,
581 QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, PlaneAngleRadians.PI_OVER_TWO));
582
583
584 final Plane result = plane.transform(mat);
585
586
587 checkPlane(result, Vector3D.ZERO, Vector3D.Unit.PLUS_X);
588 }
589
590 @Test
591 public void testTransform_asymmetricScaling() {
592
593 final Vector3D pt = Vector3D.of(0, 1, 0);
594 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.of(1, 1, 0), TEST_PRECISION);
595
596 final AffineTransformMatrix3D t = AffineTransformMatrix3D.createScale(2, 1, 1);
597
598
599 final Plane result = plane.transform(t);
600
601
602 final Vector3D expectedNormal = Vector3D.Unit.from(1, 2, 0);
603 final Vector3D expectedOrigin = result.project(Vector3D.ZERO);
604
605 checkPlane(result, expectedOrigin, expectedNormal);
606
607 final Vector3D transformedPt = t.apply(Vector3D.of(0.5, 0.5, 1));
608 Assert.assertTrue(result.contains(transformedPt));
609 Assert.assertFalse(plane.contains(transformedPt));
610 }
611
612 @Test
613 public void testTransform_negateOneComponent() {
614
615 final Vector3D pt = Vector3D.of(0, 0, 1);
616 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
617
618 final AffineTransformMatrix3D transform = AffineTransformMatrix3D.createScale(-1, 1, 1);
619
620
621 final Plane result = plane.transform(transform);
622
623
624 checkPlane(result, Vector3D.of(0, 0, 1), Vector3D.Unit.MINUS_Z);
625
626 Assert.assertFalse(transform.preservesOrientation());
627 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Z,
628 transform.normalTransform().apply(plane.getNormal()), TEST_EPS);
629 }
630
631 @Test
632 public void testTransform_negateTwoComponents() {
633
634 final Vector3D pt = Vector3D.of(0, 0, 1);
635 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
636
637 final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(-v.getX(), -v.getY(), v.getZ()));
638
639
640 final Plane result = plane.transform(transform);
641
642
643 checkPlane(result, Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z);
644 }
645
646 @Test
647 public void testTransform_negateAllComponents() {
648
649 final Vector3D pt = Vector3D.of(0, 0, 1);
650 final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
651
652 final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(Vector3D::negate);
653
654
655 final Plane result = plane.transform(transform);
656
657
658 checkPlane(result, Vector3D.of(0, 0, -1), Vector3D.Unit.PLUS_Z);
659 }
660
661 @Test
662 public void testTransform_consistency() {
663
664 final Vector3D pt = Vector3D.of(1, 2, 3);
665 final Vector3D normal = Vector3D.Unit.of(1, 1, 1);
666
667 final Plane plane = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
668
669 final Vector3D p1 = plane.project(Vector3D.of(4, 5, 6));
670 final Vector3D p2 = plane.project(Vector3D.of(-7, -8, -9));
671 final Vector3D p3 = plane.project(Vector3D.of(10, -11, 12));
672
673 final Vector3D notOnPlane1 = plane.getOrigin().add(plane.getNormal());
674 final Vector3D notOnPlane2 = plane.getOrigin().subtract(plane.getNormal());
675
676 EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (a, b, c) -> {
677 final AffineTransformMatrix3D t = AffineTransformMatrix3D.identity()
678 .rotate(Vector3D.of(-1, 2, 3),
679 QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_X, 0.3 * a))
680 .scale(Math.max(a, 1), Math.max(b, 1), Math.max(c, 1))
681 .translate(c, b, a);
682
683
684 final Plane result = plane.transform(t);
685
686
687 Vector3D expectedNormal = t.normalTransform().apply(plane.getNormal()).normalize();
688 if (!t.preservesOrientation()) {
689 expectedNormal = expectedNormal.negate();
690 }
691
692 EuclideanTestUtils.assertCoordinatesEqual(expectedNormal, result.getNormal(), TEST_EPS);
693
694 Assert.assertTrue(result.contains(t.apply(p1)));
695 Assert.assertTrue(result.contains(t.apply(p2)));
696 Assert.assertTrue(result.contains(t.apply(p3)));
697
698 Assert.assertFalse(result.contains(t.apply(notOnPlane1)));
699 Assert.assertFalse(result.contains(t.apply(notOnPlane2)));
700 });
701 }
702
703 @Test
704 public void testRotate() {
705
706 final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
707 final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
708 final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
709 Plane plane = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
710 final Vector3D oldNormal = plane.getNormal();
711
712
713 plane = plane.rotate(p2, QuaternionRotation.fromAxisAngle(p2.subtract(p1), 1.7));
714 Assert.assertTrue(plane.contains(p1));
715 Assert.assertTrue(plane.contains(p2));
716 Assert.assertFalse(plane.contains(p3));
717
718 plane = plane.rotate(p2, QuaternionRotation.fromAxisAngle(oldNormal, 0.1));
719 Assert.assertFalse(plane.contains(p1));
720 Assert.assertTrue(plane.contains(p2));
721 Assert.assertFalse(plane.contains(p3));
722
723 plane = plane.rotate(p1, QuaternionRotation.fromAxisAngle(oldNormal, 0.1));
724 Assert.assertFalse(plane.contains(p1));
725 Assert.assertFalse(plane.contains(p2));
726 Assert.assertFalse(plane.contains(p3));
727 }
728
729 @Test
730 public void testTranslate() {
731
732 final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
733 final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
734 final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
735 Plane plane = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
736
737
738 plane = plane.translate(Vector3D.linearCombination(2.0, plane.getNormal().orthogonal()));
739 Assert.assertTrue(plane.contains(p1));
740 Assert.assertTrue(plane.contains(p2));
741 Assert.assertTrue(plane.contains(p3));
742
743 plane = plane.translate(Vector3D.linearCombination(-1.2, plane.getNormal()));
744 Assert.assertFalse(plane.contains(p1));
745 Assert.assertFalse(plane.contains(p2));
746 Assert.assertFalse(plane.contains(p3));
747
748 plane = plane.translate(Vector3D.linearCombination(+1.2, plane.getNormal()));
749 Assert.assertTrue(plane.contains(p1));
750 Assert.assertTrue(plane.contains(p2));
751 Assert.assertTrue(plane.contains(p3));
752 }
753
754 @Test
755 public void testIntersection_withLine() {
756
757 final Plane plane = Planes.fromPointAndNormal(Vector3D.of(1, 2, 3), Vector3D.of(-4, 1, -5), TEST_PRECISION);
758 final Line3D line = Lines3D.fromPoints(Vector3D.of(0.2, -3.5, 0.7), Vector3D.of(1.2, -2.5, -0.3), TEST_PRECISION);
759
760
761 final Vector3D point = plane.intersection(line);
762
763
764 Assert.assertTrue(plane.contains(point));
765 Assert.assertTrue(line.contains(point));
766 Assert.assertNull(plane.intersection(Lines3D.fromPoints(Vector3D.of(10, 10, 10),
767 Vector3D.of(10, 10, 10).add(plane.getNormal().orthogonal()),
768 TEST_PRECISION)));
769 }
770
771 @Test
772 public void testIntersection_withLine_noIntersection() {
773
774 final Vector3D pt = Vector3D.of(1, 2, 3);
775 final Vector3D normal = Vector3D.of(-4, 1, -5);
776
777 final Plane plane = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
778 final Vector3D u = plane.getNormal().orthogonal();
779 final Vector3D v = plane.getNormal().cross(u);
780
781
782 Assert.assertNull(plane.intersection(Lines3D.fromPoints(pt, pt.add(u), TEST_PRECISION)));
783
784 final Vector3D offsetPt = pt.add(plane.getNormal());
785 Assert.assertNull(plane.intersection(Lines3D.fromPoints(offsetPt, offsetPt.add(v), TEST_PRECISION)));
786 }
787
788 @Test
789 public void testIntersection_withPlane() {
790
791 final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
792 final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
793 final Plane planeA = Planes.fromPoints(p1, p2, Vector3D.of(-2.0, 4.3, 0.7), TEST_PRECISION);
794 final Plane planeB = Planes.fromPoints(p1, Vector3D.of(11.4, -3.8, 5.1), p2, TEST_PRECISION);
795
796
797 final Line3D line = planeA.intersection(planeB);
798
799
800 Assert.assertTrue(line.contains(p1));
801 Assert.assertTrue(line.contains(p2));
802 EuclideanTestUtils.assertCoordinatesEqual(planeA.getNormal().cross(planeB.getNormal()).normalize(),
803 line.getDirection(), TEST_EPS);
804
805 Assert.assertNull(planeA.intersection(planeA));
806 }
807
808 @Test
809 public void testIntersection_withPlane_noIntersection() {
810
811 final Plane plane = Planes.fromPointAndNormal(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
812
813
814 Assert.assertNull(plane.intersection(plane));
815 Assert.assertNull(plane.intersection(plane.reverse()));
816
817 Assert.assertNull(plane.intersection(Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION)));
818 Assert.assertNull(plane.intersection(Planes.fromPointAndNormal(Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z, TEST_PRECISION)));
819 }
820
821 @Test
822 public void testIntersection_threePlanes() {
823
824 final Vector3D pt = Vector3D.of(1.2, 3.4, -5.8);
825 final Plane a = Planes.fromPointAndNormal(pt, Vector3D.of(1, 3, 3), TEST_PRECISION);
826 final Plane b = Planes.fromPointAndNormal(pt, Vector3D.of(-2, 4, 0), TEST_PRECISION);
827 final Plane c = Planes.fromPointAndNormal(pt, Vector3D.of(7, 0, -4), TEST_PRECISION);
828
829
830 final Vector3D result = Plane.intersection(a, b, c);
831
832
833 EuclideanTestUtils.assertCoordinatesEqual(pt, result, TEST_EPS);
834 }
835
836 @Test
837 public void testIntersection_threePlanes_intersectInLine() {
838
839 final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(1, 0, 0), TEST_PRECISION);
840 final Plane b = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(1, 0.5, 0), TEST_PRECISION);
841 final Plane c = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(1, 1, 0), TEST_PRECISION);
842
843
844 final Vector3D result = Plane.intersection(a, b, c);
845
846
847 Assert.assertNull(result);
848 }
849
850 @Test
851 public void testIntersection_threePlanes_twoParallel() {
852
853 final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
854 final Plane b = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
855 final Plane c = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
856
857
858 final Vector3D result = Plane.intersection(a, b, c);
859
860
861 Assert.assertNull(result);
862 }
863
864 @Test
865 public void testIntersection_threePlanes_allParallel() {
866
867 final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
868 final Plane b = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
869 final Plane c = Planes.fromPointAndNormal(Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
870
871
872 final Vector3D result = Plane.intersection(a, b, c);
873
874
875 Assert.assertNull(result);
876 }
877
878 @Test
879 public void testIntersection_threePlanes_coincidentPlanes() {
880
881 final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
882 final Plane b = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
883 final Plane c = b.reverse();
884
885
886 final Vector3D result = Plane.intersection(a, b, c);
887
888
889 Assert.assertNull(result);
890 }
891
892 @Test
893 public void testSpan() {
894
895 final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
896
897
898 final PlaneConvexSubset sub = plane.span();
899
900
901 Assert.assertNotSame(plane, sub.getPlane());
902 EuclideanTestUtils.assertCoordinatesEqual(plane.getOrigin(), sub.getPlane().getOrigin(), TEST_EPS);
903 EuclideanTestUtils.assertCoordinatesEqual(plane.getNormal(), sub.getPlane().getNormal(), TEST_EPS);
904
905 Assert.assertTrue(sub.isFull());
906
907 Assert.assertTrue(sub.contains(Vector3D.ZERO));
908 Assert.assertTrue(sub.contains(Vector3D.of(1, 1, 0)));
909
910 Assert.assertFalse(sub.contains(Vector3D.of(0, 0, 1)));
911 }
912
913 @Test
914 public void testSimilarOrientation() {
915
916 final Plane plane = Planes.fromNormal(Vector3D.of(1, 0, 0), TEST_PRECISION);
917
918
919 Assert.assertTrue(plane.similarOrientation(plane));
920 Assert.assertTrue(plane.similarOrientation(Planes.fromNormal(Vector3D.of(1, 1, 0), TEST_PRECISION)));
921 Assert.assertTrue(plane.similarOrientation(Planes.fromNormal(Vector3D.of(1, -1, 0), TEST_PRECISION)));
922
923 Assert.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(0, 1, 0), TEST_PRECISION)));
924 Assert.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(-1, 1, 0), TEST_PRECISION)));
925 Assert.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(-1, 1, 0), TEST_PRECISION)));
926 Assert.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(0, -1, 0), TEST_PRECISION)));
927 }
928
929 @Test
930 public void testEq() {
931
932 final double eps = 1e-3;
933 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
934
935 final Vector3D pt = Vector3D.of(1, 2, 3);
936 final Vector3D normal = Vector3D.Unit.PLUS_X;
937
938 final Vector3D ptPrime = Vector3D.of(1.0001, 2.0001, 3.0001);
939 final Vector3D normalPrime = Vector3D.Unit.of(1, 1e-4, 0);
940
941 final Plane a = Planes.fromPointAndNormal(pt, normal, precision);
942
943 final Plane b = Planes.fromPointAndNormal(Vector3D.of(2, 2, 3), normal, precision);
944 final Plane c = Planes.fromPointAndNormal(pt, Vector3D.Unit.MINUS_X, precision);
945 final Plane d = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
946
947 final Plane e = Planes.fromPointAndNormal(ptPrime, normalPrime, new EpsilonDoublePrecisionContext(eps));
948
949
950 Assert.assertTrue(a.eq(a, precision));
951
952 Assert.assertFalse(a.eq(b, precision));
953 Assert.assertFalse(a.eq(c, precision));
954
955 Assert.assertTrue(a.eq(d, precision));
956 Assert.assertTrue(a.eq(e, precision));
957 Assert.assertTrue(e.eq(a, precision));
958 }
959
960 @Test
961 public void testHashCode() {
962
963 final Vector3D pt = Vector3D.of(1, 2, 3);
964 final Vector3D normal = Vector3D.Unit.PLUS_X;
965
966 final Plane a = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
967 final Plane b = Planes.fromPointAndNormal(Vector3D.of(2, 2, 3), normal, TEST_PRECISION);
968 final Plane c = Planes.fromPointAndNormal(pt, Vector3D.of(1, 1, 0), TEST_PRECISION);
969 final Plane d = Planes.fromPointAndNormal(pt, normal, new EpsilonDoublePrecisionContext(1e-8));
970 final Plane e = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
971
972
973 final int hash = a.hashCode();
974
975 Assert.assertEquals(hash, a.hashCode());
976
977 Assert.assertNotEquals(hash, b.hashCode());
978 Assert.assertNotEquals(hash, c.hashCode());
979 Assert.assertNotEquals(hash, d.hashCode());
980
981 Assert.assertEquals(hash, e.hashCode());
982 }
983
984 @Test
985 public void testEquals() {
986
987 final Vector3D pt = Vector3D.of(1, 2, 3);
988 final Vector3D normal = Vector3D.Unit.PLUS_X;
989
990 final Plane a = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
991 final Plane b = Planes.fromPointAndNormal(Vector3D.of(2, 2, 3), normal, TEST_PRECISION);
992 final Plane c = Planes.fromPointAndNormal(pt, Vector3D.Unit.MINUS_X, TEST_PRECISION);
993 final Plane d = Planes.fromPointAndNormal(pt, normal, new EpsilonDoublePrecisionContext(1e-8));
994 final Plane e = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
995
996
997 Assert.assertEquals(a, a);
998
999 Assert.assertFalse(a.equals(null));
1000 Assert.assertFalse(a.equals(new Object()));
1001
1002 Assert.assertNotEquals(a, b);
1003 Assert.assertNotEquals(a, c);
1004 Assert.assertNotEquals(a, d);
1005
1006 Assert.assertEquals(a, e);
1007 Assert.assertEquals(e, a);
1008 }
1009
1010 @Test
1011 public void testToString() {
1012
1013 final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
1014
1015
1016 final String str = plane.toString();
1017
1018
1019 Assert.assertTrue(str.startsWith("Plane["));
1020 Assert.assertTrue(str.matches(".*origin= \\(0(\\.0)?, 0(\\.0)?\\, 0(\\.0)?\\).*"));
1021 Assert.assertTrue(str.matches(".*normal= \\(0(\\.0)?, 0(\\.0)?\\, 1(\\.0)?\\).*"));
1022 }
1023
1024 private static void checkPlane(final Plane plane, final Vector3D origin, final Vector3D normal) {
1025 EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getOrigin(), TEST_EPS);
1026 Assert.assertTrue(plane.contains(origin));
1027
1028 EuclideanTestUtils.assertCoordinatesEqual(normal, plane.getNormal(), TEST_EPS);
1029 Assert.assertEquals(1.0, plane.getNormal().norm(), TEST_EPS);
1030
1031 final double offset = plane.getOriginOffset();
1032 Assert.assertEquals(Vector3D.ZERO.distance(plane.getOrigin()), Math.abs(offset), TEST_EPS);
1033 EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getNormal().multiply(-offset), TEST_EPS);
1034 }
1035
1036 private static <T> List<T> rotate(final List<T> list, final int shift) {
1037 final int size = list.size();
1038
1039 final List<T> result = new ArrayList<>(size);
1040
1041 for (int i = 0; i < size; ++i) {
1042 result.add(list.get((i + shift) % size));
1043 }
1044
1045 return result;
1046 }
1047 }