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 org.apache.commons.geometry.core.GeometryTestUtils;
20  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
21  import org.apache.commons.geometry.euclidean.threed.EmbeddingPlane.SubspaceTransform;
22  import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
23  import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
24  import org.apache.commons.geometry.euclidean.twod.Vector2D;
25  import org.apache.commons.numbers.angle.Angle;
26  import org.apache.commons.numbers.core.Precision;
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.Test;
29  
30  class EmbeddingPlaneTest {
31  
32      private static final double TEST_EPS = 1e-10;
33  
34      private static final Precision.DoubleEquivalence TEST_PRECISION =
35              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
36  
37      @Test
38      void testFromPointAndPlaneVectors() {
39          // arrange
40          final Vector3D pt = Vector3D.of(1, 2, 3);
41  
42          // act/assert
43          checkPlane(Planes.fromPointAndPlaneVectors(pt, Vector3D.of(2, 0, 0), Vector3D.of(3, 0.1, 0),  TEST_PRECISION),
44                  Vector3D.of(0, 0, 3), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y);
45  
46          checkPlane(Planes.fromPointAndPlaneVectors(pt, Vector3D.of(2, 0, 0), Vector3D.of(3, -0.1, 0),  TEST_PRECISION),
47                  Vector3D.of(0, 0, 3), Vector3D.Unit.PLUS_X, Vector3D.Unit.MINUS_Y);
48  
49          checkPlane(Planes.fromPointAndPlaneVectors(pt, Vector3D.of(0, 0.1, 0), Vector3D.of(0, -3, 1),  TEST_PRECISION),
50                  Vector3D.of(1, 0, 0), Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_Z);
51      }
52  
53      @Test
54      void testFromPointAndPlaneVectors_illegalArguments() {
55          // arrange
56          final Vector3D pt = Vector3D.of(1, 2, 3);
57  
58          // act/assert
59  
60          // identical vectors
61          Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPointAndPlaneVectors(pt, Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION));
62          // zero vector
63          Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPointAndPlaneVectors(pt, Vector3D.of(0, 0, 1), Vector3D.ZERO, TEST_PRECISION));
64          // collinear vectors
65          Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPointAndPlaneVectors(pt, Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 2), TEST_PRECISION));
66          // collinear vectors - reversed
67          Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPointAndPlaneVectors(pt, Vector3D.of(0, 0, 1), Vector3D.of(0, 0, -2), TEST_PRECISION));
68      }
69  
70      @Test
71      void testGetEmbedding() {
72          // arrange
73          final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.ZERO,
74                  Vector3D.Unit.PLUS_X, Vector3D.Unit.MINUS_Y, TEST_PRECISION);
75  
76          // act/assert
77          Assertions.assertSame(plane, plane.getEmbedding());
78      }
79  
80      @Test
81      void testPointAt() {
82          // arrange
83          final Vector3D pt = Vector3D.of(0, 0, 1);
84          final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt,
85                  Vector3D.Unit.PLUS_Y, Vector3D.Unit.MINUS_X, TEST_PRECISION);
86  
87          // act/assert
88          EuclideanTestUtils.assertCoordinatesEqual(pt, plane.pointAt(Vector2D.ZERO, 0), TEST_EPS);
89          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, plane.pointAt(Vector2D.ZERO, -1), TEST_EPS);
90          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, -1), plane.pointAt(Vector2D.ZERO, -2), TEST_EPS);
91          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 2), plane.pointAt(Vector2D.ZERO, 1), TEST_EPS);
92  
93          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 2, 1), plane.pointAt(Vector2D.of(2, 1), 0), TEST_EPS);
94          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(4, -3, 6), plane.pointAt(Vector2D.of(-3, -4), 5), TEST_EPS);
95      }
96  
97      @Test
98      void testReverse() {
99          // arrange
100         final Vector3D pt = Vector3D.of(0, 0, 1);
101         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt,
102                 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
103 
104         // act
105         final EmbeddingPlane reversed = plane.reverse();
106 
107         // assert
108         checkPlane(reversed, pt, Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_X);
109 
110         Assertions.assertTrue(reversed.contains(Vector3D.of(1, 1, 1)));
111         Assertions.assertTrue(reversed.contains(Vector3D.of(-1, -1, 1)));
112         Assertions.assertFalse(reversed.contains(Vector3D.ZERO));
113 
114         Assertions.assertEquals(1.0, reversed.offset(Vector3D.ZERO), TEST_EPS);
115     }
116 
117     @Test
118     void testTransform_rotationAroundPoint() {
119         // arrange
120         final Vector3D pt = Vector3D.of(0, 0, 1);
121         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt, Vector3D.Unit.PLUS_Y, Vector3D.Unit.MINUS_X, TEST_PRECISION);
122 
123         final AffineTransformMatrix3D mat = AffineTransformMatrix3D.createRotation(pt,
124                 QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, Angle.PI_OVER_TWO));
125 
126         // act
127         final EmbeddingPlane result = plane.transform(mat);
128 
129         // assert
130         checkPlane(result, Vector3D.ZERO, Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_Z);
131     }
132 
133     @Test
134     void testTransform_asymmetricScaling() {
135         // arrange
136         final Vector3D pt = Vector3D.of(0, 1, 0);
137         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt, Vector3D.Unit.MINUS_Z, Vector3D.of(-1, 1, 0), TEST_PRECISION);
138 
139         final AffineTransformMatrix3D mat = AffineTransformMatrix3D.createScale(2, 1, 1);
140 
141         // act
142         final EmbeddingPlane result = plane.transform(mat);
143 
144         // assert
145         final Vector3D expectedU = Vector3D.Unit.MINUS_Z;
146         final Vector3D expectedV = Vector3D.Unit.of(-2, 1, 0);
147         final Vector3D expectedNormal = Vector3D.Unit.of(1, 2, 0);
148 
149         final Vector3D transformedPt = mat.apply(plane.getOrigin());
150         final Vector3D expectedOrigin = transformedPt.project(expectedNormal);
151 
152         checkPlane(result, expectedOrigin, expectedU, expectedV);
153 
154         Assertions.assertTrue(result.contains(transformedPt));
155         Assertions.assertFalse(plane.contains(transformedPt));
156     }
157 
158     @Test
159     void testTransform_negateOneComponent() {
160         // arrange
161         final Vector3D pt = Vector3D.of(0, 0, 1);
162         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
163 
164         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(-v.getX(), v.getY(), v.getZ()));
165 
166         // act
167         final EmbeddingPlane result = plane.transform(transform);
168 
169         // assert
170         checkPlane(result, Vector3D.of(0, 0, 1), Vector3D.Unit.MINUS_X, Vector3D.Unit.PLUS_Y);
171     }
172 
173     @Test
174     void testTransform_negateTwoComponents() {
175         // arrange
176         final Vector3D pt = Vector3D.of(0, 0, 1);
177         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
178 
179         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(-v.getX(), -v.getY(), v.getZ()));
180 
181         // act
182         final EmbeddingPlane result = plane.transform(transform);
183 
184         // assert
185         checkPlane(result, Vector3D.of(0, 0, 1), Vector3D.Unit.MINUS_X, Vector3D.Unit.MINUS_Y);
186     }
187 
188     @Test
189     void testTransform_negateAllComponents() {
190         // arrange
191         final Vector3D pt = Vector3D.of(0, 0, 1);
192         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt,
193                 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
194 
195         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(Vector3D::negate);
196 
197         // act
198         final EmbeddingPlane result = plane.transform(transform);
199 
200         // assert
201         checkPlane(result, Vector3D.of(0, 0, -1), Vector3D.Unit.MINUS_X, Vector3D.Unit.MINUS_Y);
202     }
203 
204     @Test
205     void testTransform_consistency() {
206         // arrange
207         final Vector3D pt = Vector3D.of(1, 2, 3);
208         final Vector3D normal = Vector3D.Unit.from(1, 1, 1);
209         final Vector3D u = normal.orthogonal(Vector3D.Unit.PLUS_X);
210         final Vector3D v = normal.cross(u).normalize();
211 
212         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(pt, u, v, TEST_PRECISION);
213 
214         final Vector3D p1 = plane.project(Vector3D.of(4, 5, 6));
215         final Vector3D p2 = plane.project(Vector3D.of(-7, -8, -9));
216         final Vector3D p3 = plane.project(Vector3D.of(10, -11, 12));
217 
218         final Vector3D notOnPlane1 = plane.getOrigin().add(plane.getNormal());
219         final Vector3D notOnPlane2 = plane.getOrigin().subtract(plane.getNormal());
220 
221         EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (a, b, c) -> {
222             final AffineTransformMatrix3D t = AffineTransformMatrix3D.identity()
223                     .rotate(Vector3D.of(-1, 2, 3),
224                             QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_X, 0.3 * a))
225                     .scale(Math.max(a, 1), Math.max(b, 1), Math.max(c, 1))
226                     .translate(c, b, a);
227 
228             // act
229             final EmbeddingPlane result = plane.transform(t);
230 
231             // assert
232             Vector3D expectedNormal = t.normalTransform().apply(plane.getNormal()).normalize();
233             if (!t.preservesOrientation()) {
234                 expectedNormal = expectedNormal.negate();
235             }
236 
237             EuclideanTestUtils.assertCoordinatesEqual(expectedNormal, result.getNormal(), TEST_EPS);
238 
239             Assertions.assertTrue(result.contains(t.apply(p1)));
240             Assertions.assertTrue(result.contains(t.apply(p2)));
241             Assertions.assertTrue(result.contains(t.apply(p3)));
242 
243             Assertions.assertFalse(result.contains(t.apply(notOnPlane1)));
244             Assertions.assertFalse(result.contains(t.apply(notOnPlane2)));
245         });
246     }
247 
248     @Test
249     void testRotate() {
250         // arrange
251         final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
252         final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
253         final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
254         EmbeddingPlane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION).getEmbedding();
255         final Vector3D oldNormal = plane.getNormal();
256 
257         // act/assert
258         plane = plane.rotate(p2, QuaternionRotation.fromAxisAngle(p2.subtract(p1), 1.7));
259         Assertions.assertTrue(plane.contains(p1));
260         Assertions.assertTrue(plane.contains(p2));
261         Assertions.assertFalse(plane.contains(p3));
262 
263         plane = plane.rotate(p2, QuaternionRotation.fromAxisAngle(oldNormal, 0.1));
264         Assertions.assertFalse(plane.contains(p1));
265         Assertions.assertTrue(plane.contains(p2));
266         Assertions.assertFalse(plane.contains(p3));
267 
268         plane = plane.rotate(p1, QuaternionRotation.fromAxisAngle(oldNormal, 0.1));
269         Assertions.assertFalse(plane.contains(p1));
270         Assertions.assertFalse(plane.contains(p2));
271         Assertions.assertFalse(plane.contains(p3));
272     }
273 
274     @Test
275     void testTranslate() {
276         // arrange
277         final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
278         final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
279         final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
280         EmbeddingPlane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION).getEmbedding();
281 
282         // act/assert
283         plane = plane.translate(Vector3D.Sum.create()
284                 .addScaled(2.0, plane.getU())
285                 .addScaled(-1.5, plane.getV()).get());
286         Assertions.assertTrue(plane.contains(p1));
287         Assertions.assertTrue(plane.contains(p2));
288         Assertions.assertTrue(plane.contains(p3));
289 
290         plane = plane.translate(plane.getNormal().multiply(-1.2));
291         Assertions.assertFalse(plane.contains(p1));
292         Assertions.assertFalse(plane.contains(p2));
293         Assertions.assertFalse(plane.contains(p3));
294 
295         plane = plane.translate(plane.getNormal().multiply(+1.2));
296         Assertions.assertTrue(plane.contains(p1));
297         Assertions.assertTrue(plane.contains(p2));
298         Assertions.assertTrue(plane.contains(p3));
299     }
300 
301     @Test
302     void testSubspaceTransform() {
303         // arrange
304         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1),
305                 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
306 
307         // act/assert
308         checkSubspaceTransform(plane.subspaceTransform(AffineTransformMatrix3D.createScale(2, 3, 4)),
309                 Vector3D.of(0, 0, 4), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y,
310                 Vector3D.of(0, 0, 4), Vector3D.of(2, 0, 4), Vector3D.of(0, 3, 4));
311 
312         checkSubspaceTransform(plane.subspaceTransform(AffineTransformMatrix3D.createTranslation(2, 3, 4)),
313                 Vector3D.of(0, 0, 5), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y,
314                 Vector3D.of(2, 3, 5), Vector3D.of(3, 3, 5), Vector3D.of(2, 4, 5));
315 
316         checkSubspaceTransform(plane.subspaceTransform(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, Angle.PI_OVER_TWO)),
317                 Vector3D.of(1, 0, 0), Vector3D.Unit.MINUS_Z, Vector3D.Unit.PLUS_Y,
318                 Vector3D.of(1, 0, 0), Vector3D.of(1, 0, -1), Vector3D.of(1, 1, 0));
319     }
320 
321     private void checkSubspaceTransform(final SubspaceTransform st,
322                                         final Vector3D origin, final Vector3D u, final Vector3D v,
323                                         final Vector3D tOrigin, final Vector3D tU, final Vector3D tV) {
324 
325         final EmbeddingPlane plane = st.getPlane();
326         final AffineTransformMatrix2D transform = st.getTransform();
327 
328         checkPlane(plane, origin, u, v);
329 
330         EuclideanTestUtils.assertCoordinatesEqual(tOrigin, plane.toSpace(transform.apply(Vector2D.ZERO)), TEST_EPS);
331         EuclideanTestUtils.assertCoordinatesEqual(tU, plane.toSpace(transform.apply(Vector2D.Unit.PLUS_X)), TEST_EPS);
332         EuclideanTestUtils.assertCoordinatesEqual(tV, plane.toSpace(transform.apply(Vector2D.Unit.PLUS_Y)), TEST_EPS);
333     }
334 
335     @Test
336     void testSubspaceTransform_transformsPointsCorrectly() {
337         // arrange
338         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 2, 3),
339                 Vector3D.of(-1, -1, 1), Vector3D.of(-1, 1, 1), TEST_PRECISION);
340 
341         EuclideanTestUtils.permuteSkipZero(-2, 2, 0.5, (a, b, c) -> {
342             // create a somewhat complicate transform to try to hit all of the edge cases
343             final AffineTransformMatrix3D transform = AffineTransformMatrix3D.createTranslation(Vector3D.of(a, b, c))
344                     .rotate(QuaternionRotation.fromAxisAngle(Vector3D.of(b, c, a), Math.PI * c))
345                     .scale(0.1, 4, 8);
346 
347             // act
348             final SubspaceTransform st = plane.subspaceTransform(transform);
349 
350             // assert
351             EuclideanTestUtils.permute(-5, 5, 1, (x, y) -> {
352                 final Vector2D subPt = Vector2D.of(x, y);
353                 final Vector3D expected = transform.apply(plane.toSpace(subPt));
354                 final Vector3D actual = st.getPlane().toSpace(
355                         st.getTransform().apply(subPt));
356 
357                 EuclideanTestUtils.assertCoordinatesEqual(expected, actual, TEST_EPS);
358             });
359         });
360     }
361 
362     @Test
363     void testEq_stdAndEmbedding() {
364         // arrange
365         final Plane stdPlane = Planes.fromPointAndNormal(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
366         final EmbeddingPlane embeddingPlane = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 1, 1),
367                 Vector3D.of(1, 1, 0), Vector3D.of(-1, 1, 0), TEST_PRECISION);
368 
369         final EmbeddingPlane nonEqEmbeddingPlane = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 1, 1),
370                 Vector3D.of(1, 1, 1), Vector3D.of(-1, 1, 1), TEST_PRECISION);
371 
372         // act/assert
373         Assertions.assertTrue(stdPlane.eq(embeddingPlane, TEST_PRECISION));
374         Assertions.assertTrue(embeddingPlane.eq(stdPlane, TEST_PRECISION));
375 
376         Assertions.assertFalse(stdPlane.eq(nonEqEmbeddingPlane, TEST_PRECISION));
377         Assertions.assertFalse(nonEqEmbeddingPlane.eq(stdPlane, TEST_PRECISION));
378     }
379 
380     @Test
381     void testSimilarOrientation_stdAndEmbedding() {
382         // arrange
383         final Plane stdPlane = Planes.fromPointAndNormal(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
384         final EmbeddingPlane embeddingPlane = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 1, 1),
385                 Vector3D.of(1, 1, 1), Vector3D.of(-1, 1, 1), TEST_PRECISION);
386 
387         final EmbeddingPlane nonSimilarEmbeddingPlane = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 1, 1),
388                 Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_X, TEST_PRECISION);
389 
390         // act/assert
391         Assertions.assertTrue(stdPlane.similarOrientation(embeddingPlane));
392         Assertions.assertTrue(embeddingPlane.similarOrientation(stdPlane));
393 
394         Assertions.assertFalse(stdPlane.similarOrientation(nonSimilarEmbeddingPlane));
395         Assertions.assertFalse(nonSimilarEmbeddingPlane.similarOrientation(stdPlane));
396     }
397 
398     @Test
399     void testHashCode() {
400         // arrange
401         final Vector3D pt = Vector3D.of(1, 2, 3);
402         final Vector3D u = Vector3D.Unit.PLUS_X;
403         final Vector3D v = Vector3D.Unit.PLUS_Y;
404 
405         final EmbeddingPlane a = Planes.fromPointAndPlaneVectors(pt, u, v, TEST_PRECISION);
406         final EmbeddingPlane b = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 2, 4), u, v, TEST_PRECISION);
407         final EmbeddingPlane c = Planes.fromPointAndPlaneVectors(pt, Vector3D.of(1, 1, 0), v, TEST_PRECISION);
408         final EmbeddingPlane d = Planes.fromPointAndPlaneVectors(pt, u, Vector3D.Unit.MINUS_Y, TEST_PRECISION);
409         final EmbeddingPlane e = Planes.fromPointAndPlaneVectors(pt, u, v, Precision.doubleEquivalenceOfEpsilon(1e-8));
410         final EmbeddingPlane f = Planes.fromPointAndPlaneVectors(pt, u, v, TEST_PRECISION);
411 
412         // act/assert
413         final int hash = a.hashCode();
414 
415         Assertions.assertEquals(hash, a.hashCode());
416 
417         Assertions.assertNotEquals(hash, b.hashCode());
418         Assertions.assertNotEquals(hash, c.hashCode());
419         Assertions.assertNotEquals(hash, d.hashCode());
420         Assertions.assertNotEquals(hash, e.hashCode());
421 
422         Assertions.assertEquals(hash, f.hashCode());
423     }
424 
425     @Test
426     void testEquals() {
427         // arrange
428         final Vector3D pt = Vector3D.of(1, 2, 3);
429         final Vector3D u = Vector3D.Unit.PLUS_X;
430         final Vector3D v = Vector3D.Unit.PLUS_Y;
431 
432         final EmbeddingPlane a = Planes.fromPointAndPlaneVectors(pt, u, v, TEST_PRECISION);
433         final EmbeddingPlane b = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 2, 4), u, v, TEST_PRECISION);
434         final EmbeddingPlane c = Planes.fromPointAndPlaneVectors(pt, Vector3D.Unit.MINUS_X, v, TEST_PRECISION);
435         final EmbeddingPlane d = Planes.fromPointAndPlaneVectors(pt, u, Vector3D.Unit.MINUS_Y, TEST_PRECISION);
436         final EmbeddingPlane e = Planes.fromPointAndPlaneVectors(pt, u, v, Precision.doubleEquivalenceOfEpsilon(1e-8));
437         final EmbeddingPlane f = Planes.fromPointAndPlaneVectors(pt, u, v, TEST_PRECISION);
438 
439         final Plane stdPlane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
440 
441         // act/assert
442         GeometryTestUtils.assertSimpleEqualsCases(a);
443 
444         Assertions.assertNotEquals(a, b);
445         Assertions.assertNotEquals(a, c);
446         Assertions.assertNotEquals(a, d);
447         Assertions.assertNotEquals(a, e);
448 
449         Assertions.assertEquals(a, f);
450         Assertions.assertEquals(f, a);
451 
452         Assertions.assertNotEquals(a, stdPlane);
453     }
454 
455     @Test
456     void testToString() {
457         // arrange
458         final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.ZERO,
459                 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
460 
461         // act
462         final String str = plane.toString();
463 
464         // assert
465         Assertions.assertTrue(str.startsWith("EmbeddingPlane["));
466         Assertions.assertTrue(str.matches(".*origin= \\(0(\\.0)?, 0(\\.0)?\\, 0(\\.0)?\\).*"));
467         Assertions.assertTrue(str.matches(".*u= \\(1(\\.0)?, 0(\\.0)?\\, 0(\\.0)?\\).*"));
468         Assertions.assertTrue(str.matches(".*v= \\(0(\\.0)?, 1(\\.0)?\\, 0(\\.0)?\\).*"));
469         Assertions.assertTrue(str.matches(".*w= \\(0(\\.0)?, 0(\\.0)?\\, 1(\\.0)?\\).*"));
470     }
471 
472     private static void checkPlane(final EmbeddingPlane plane, final Vector3D origin, Vector3D u, Vector3D v) {
473         u = u.normalize();
474         v = v.normalize();
475         final Vector3D w = u.cross(v);
476 
477         EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getOrigin(), TEST_EPS);
478         Assertions.assertTrue(plane.contains(origin));
479 
480         EuclideanTestUtils.assertCoordinatesEqual(u, plane.getU(), TEST_EPS);
481         Assertions.assertEquals(1.0, plane.getU().norm(), TEST_EPS);
482 
483         EuclideanTestUtils.assertCoordinatesEqual(v, plane.getV(), TEST_EPS);
484         Assertions.assertEquals(1.0, plane.getV().norm(), TEST_EPS);
485 
486         EuclideanTestUtils.assertCoordinatesEqual(w, plane.getW(), TEST_EPS);
487         Assertions.assertEquals(1.0, plane.getW().norm(), TEST_EPS);
488 
489         EuclideanTestUtils.assertCoordinatesEqual(w, plane.getNormal(), TEST_EPS);
490         Assertions.assertEquals(1.0, plane.getNormal().norm(), TEST_EPS);
491 
492         final double offset = plane.getOriginOffset();
493         Assertions.assertEquals(Vector3D.ZERO.distance(plane.getOrigin()), Math.abs(offset), TEST_EPS);
494         EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getNormal().multiply(-offset), TEST_EPS);
495     }
496 }