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