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