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.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          // act/assert
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          // act/assert
58          GeometryTestUtils.assertThrows(() -> {
59              Planes.fromNormal(Vector3D.ZERO, TEST_PRECISION);
60          }, IllegalArgumentException.class);
61      }
62  
63      @Test
64      public void testFromPointAndNormal() {
65          // arrange
66          final Vector3D pt = Vector3D.of(1, 2, 3);
67  
68          // act/assert
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          // arrange
80          final Vector3D pt = Vector3D.of(1, 2, 3);
81  
82          // act/assert
83          GeometryTestUtils.assertThrows(() -> {
84              Planes.fromPointAndNormal(pt, Vector3D.ZERO, TEST_PRECISION);
85          }, IllegalArgumentException.class);
86      }
87  
88      @Test
89      public void testFromPoints() {
90          // arrange
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          // act/assert
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         // arrange
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         // act
111         final Plane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
112 
113         // assert
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         // arrange
122         final Vector3D a = Vector3D.of(1, 0, 0);
123         final Vector3D b = Vector3D.of(0, 1, 0);
124 
125         // act/assert
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         // arrange
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         // act
153         final Plane plane = Planes.fromPoints(pts, TEST_PRECISION);
154 
155         // assert
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         // arrange
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         // act
174         final Plane plane = Planes.fromPoints(pts, TEST_PRECISION);
175 
176         // assert
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         // arrange
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         // act
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         // act/assert
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         // arrange
286         final Vector3D a = Vector3D.ZERO;
287         final Vector3D b = Vector3D.Unit.PLUS_X;
288 
289         // act/assert
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         // act/assert
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         // act/assert
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         // act/assert
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         // arrange
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             // act
367             final EmbeddingPlane embeddingPlane = plane.getEmbedding();
368             final EmbeddingPlane nextEmbeddingPlane = plane.getEmbedding();
369 
370             // assert
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         // arrange
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         // act/assert
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         // arrange
410         final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
411 
412         // act/assert
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         // arrange
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         // act/assert
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         // arrange
453         final Vector3D pt = Vector3D.of(0, 0, 1);
454         final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
455 
456         // act
457         final Plane reversed = plane.reverse();
458 
459         // assert
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         // arrange
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         // act
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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                 // act
546                 final Vector3D proj = plane.project(p);
547 
548                 // assert
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         // arrange
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         // act
562         final Line3D projected = plane.project(line);
563 
564         // assert
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         // arrange
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         // act
584         final Plane result = plane.transform(mat);
585 
586         // assert
587         checkPlane(result, Vector3D.ZERO, Vector3D.Unit.PLUS_X);
588     }
589 
590     @Test
591     public void testTransform_asymmetricScaling() {
592         // arrange
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         // act
599         final Plane result = plane.transform(t);
600 
601         // assert
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         // 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.createScale(-1, 1,  1);
619 
620         // act
621         final Plane result = plane.transform(transform);
622 
623         // assert
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         // arrange
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         // act
640         final Plane result = plane.transform(transform);
641 
642         // assert
643         checkPlane(result, Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z);
644     }
645 
646     @Test
647     public void testTransform_negateAllComponents() {
648         // arrange
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         // act
655         final Plane result = plane.transform(transform);
656 
657         // assert
658         checkPlane(result, Vector3D.of(0, 0, -1), Vector3D.Unit.PLUS_Z);
659     }
660 
661     @Test
662     public void testTransform_consistency() {
663         // arrange
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             // act
684             final Plane result = plane.transform(t);
685 
686             // assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act
761         final Vector3D point = plane.intersection(line);
762 
763         // assert
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         // arrange
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         // act/assert
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         // arrange
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         // act
797         final Line3D line = planeA.intersection(planeB);
798 
799         // assert
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         // arrange
811         final Plane plane = Planes.fromPointAndNormal(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
812 
813         // act/assert
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         // arrange
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         // act
830         final Vector3D result = Plane.intersection(a, b, c);
831 
832         // assert
833         EuclideanTestUtils.assertCoordinatesEqual(pt, result, TEST_EPS);
834     }
835 
836     @Test
837     public void testIntersection_threePlanes_intersectInLine() {
838         // arrange
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         // act
844         final Vector3D result = Plane.intersection(a, b, c);
845 
846         // assert
847         Assert.assertNull(result);
848     }
849 
850     @Test
851     public void testIntersection_threePlanes_twoParallel() {
852         // arrange
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         // act
858         final Vector3D result = Plane.intersection(a, b, c);
859 
860         // assert
861         Assert.assertNull(result);
862     }
863 
864     @Test
865     public void testIntersection_threePlanes_allParallel() {
866         // arrange
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         // act
872         final Vector3D result = Plane.intersection(a, b, c);
873 
874         // assert
875         Assert.assertNull(result);
876     }
877 
878     @Test
879     public void testIntersection_threePlanes_coincidentPlanes() {
880         // arrange
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         // act
886         final Vector3D result = Plane.intersection(a, b, c);
887 
888         // assert
889         Assert.assertNull(result);
890     }
891 
892     @Test
893     public void testSpan() {
894         // arrange
895         final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
896 
897         // act
898         final PlaneConvexSubset sub = plane.span();
899 
900         // assert
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         // arrange
916         final Plane plane = Planes.fromNormal(Vector3D.of(1, 0, 0), TEST_PRECISION);
917 
918         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
1013         final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
1014 
1015         // act
1016         final String str = plane.toString();
1017 
1018         // assert
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 }