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