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.List;
21  
22  import org.apache.commons.geometry.euclidean.threed.line.Line3D;
23  import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
24  import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
25  import org.apache.commons.geometry.euclidean.threed.line.Linecastable3D;
26  import org.apache.commons.numbers.core.Precision;
27  import org.junit.jupiter.api.Assertions;
28  
29  /** Helper class designed to assist with linecast test assertions in 3D.
30   */
31  class LinecastChecker3D {
32  
33      private static final double TEST_EPS = 1e-10;
34  
35      private static final Precision.DoubleEquivalence TEST_PRECISION =
36              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
37  
38      /** The linecastable target. */
39      private final Linecastable3D target;
40  
41      /** List of expected results from the line cast operation. */
42      private final List<ExpectedResult> expectedResults = new ArrayList<>();
43  
44      /** Construct a new instance that performs linecast assertions against the
45       * given target.
46       * @param target
47       */
48      LinecastChecker3D(final Linecastable3D target) {
49          this.target = target;
50      }
51  
52      /** Configure the instance to expect no results (an empty list from linecast() and null from
53       * linecastFirst()) from the next linecast operation performed by {@link #whenGiven(Line3D)}
54       * or {@link #whenGiven(LineConvexSubset3D)}.
55       * @return
56       */
57      public LinecastChecker3D expectNothing() {
58          expectedResults.clear();
59  
60          return this;
61      }
62  
63      /** Configure the instance to expect a linecast point with the given parameters on the next
64       * linecast operation. Multiple calls to this method and/or {@link #and(Vector3D, Vector3D)}
65       * create an internal ordered list of results.
66       * @param point
67       * @param normal
68       * @return
69       */
70      public LinecastChecker3D expect(final Vector3D point, final Vector3D normal) {
71          expectedResults.add(new ExpectedResult(point, normal));
72  
73          return this;
74      }
75  
76      /** Fluent API alias for {@link #expect(Vector3D, Vector3D)}.
77       * @param point
78       * @param normal
79       * @return
80       */
81      public LinecastChecker3D and(final Vector3D point, final Vector3D normal) {
82          return expect(point, normal);
83      }
84  
85      /** Perform {@link Linecastable3D#linecast(Line3D)} and {@link Linecastable3D#linecastFirst(Line3D)}
86       * operations using the given line and assert that the results match the configured expected
87       * values.
88       * @param line
89       */
90      public void whenGiven(final Line3D line) {
91          checkLinecastResults(target.linecast(line), line);
92          checkLinecastFirstResult(target.linecastFirst(line), line);
93      }
94  
95      /** Perform {@link Linecastable3D#linecast(LineConvexSubset3D)} and {@link Linecastable3D#linecastFirst(LineConvexSubset3D)}
96       * operations using the given line segment and assert that the results match the configured
97       * expected results.
98       * @param segment
99       */
100     public void whenGiven(final LineConvexSubset3D segment) {
101         final Line3D line = segment.getLine();
102 
103         checkLinecastResults(target.linecast(segment), line);
104         checkLinecastFirstResult(target.linecastFirst(segment), line);
105     }
106 
107     /** Check that the given set of linecast result points matches those expected.
108      * @param results
109      * @param line
110      */
111     private void checkLinecastResults(final List<? extends LinecastPoint3D> results, final Line3D line) {
112         Assertions.assertNotNull(results, "Linecast result list cannot be null");
113         Assertions.assertEquals(expectedResults.size(), results.size(), "Unexpected result size for linecast");
114 
115         for (int i = 0; i < expectedResults.size(); ++i) {
116             final LinecastPoint3D expected = toLinecastPoint(expectedResults.get(i), line);
117             final LinecastPoint3D actual = results.get(i);
118 
119             if (!eq(expected, actual)) {
120                 Assertions.fail("Unexpected linecast point at index " + i + " expected " + expected +
121                         " but was " + actual);
122             }
123         }
124     }
125 
126     /** Check that the given linecastFirst result matches that expected.
127      * @param result
128      * @param line
129      */
130     private void checkLinecastFirstResult(final LinecastPoint3D result, final Line3D line) {
131         if (expectedResults.isEmpty()) {
132             Assertions.assertNull(result, "Expected linecastFirst result to be null");
133         } else {
134             final LinecastPoint3D expected = toLinecastPoint(expectedResults.get(0), line);
135 
136             Assertions.assertNotNull(result, "Expected linecastFirst result to not be null");
137 
138             if (!eq(expected, result)) {
139                 Assertions.fail("Unexpected result from linecastFirst: expected " + expected +
140                         " but was " + result);
141             }
142         }
143     }
144 
145     /** Fluent API method for creating new instances.
146      * @param src
147      * @return
148      */
149     public static LinecastChecker3D with(final Linecastable3D src) {
150         return new LinecastChecker3D(src);
151     }
152 
153     /** Return true if the given linecast points are equivalent according to the test precision.
154      * @param a
155      * @param b
156      * @return
157      */
158     private static boolean eq(final LinecastPoint3D a, final LinecastPoint3D b) {
159         return a.getPoint().eq(b.getPoint(), TEST_PRECISION) &&
160                 a.getNormal().eq(b.getNormal(), TEST_PRECISION) &&
161                 a.getLine().equals(b.getLine()) &&
162                 TEST_PRECISION.eq(a.getAbscissa(), b.getAbscissa());
163     }
164 
165     /** Convert an {@link ExpectedResult} struct to a {@link org.apache.commons.geometry.euclidean.twod.LinecastPoint2D} instance
166      * using the given line.
167      * @param expected
168      * @param line
169      * @return
170      */
171     private static LinecastPoint3D toLinecastPoint(final ExpectedResult expected, final Line3D line) {
172         return new LinecastPoint3D(expected.getPoint(), expected.getNormal(), line);
173     }
174 
175     /** Class containing intermediate expected results for a linecast operation.
176      */
177     private static final class ExpectedResult {
178         private final Vector3D point;
179         private final Vector3D normal;
180 
181         ExpectedResult(final Vector3D point, final Vector3D normal) {
182             this.point = point;
183             this.normal = normal;
184         }
185 
186         public Vector3D getPoint() {
187             return point;
188         }
189 
190         public Vector3D getNormal() {
191             return normal;
192         }
193     }
194 }