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;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.Objects;
23  import java.util.function.ToDoubleBiFunction;
24  
25  import org.apache.commons.geometry.core.Embedding;
26  import org.apache.commons.geometry.core.Point;
27  import org.apache.commons.geometry.core.Region;
28  import org.apache.commons.geometry.core.RegionLocation;
29  import org.apache.commons.geometry.euclidean.oned.Vector1D;
30  import org.apache.commons.numbers.core.Precision;
31  
32  /** Abstract base class representing an n-sphere, which is a generalization of the ordinary 3 dimensional
33   * sphere to arbitrary dimensions.
34   * @param <V> Vector implementation type
35   * @see <a href="https://wikipedia.org/wiki/N-sphere">N-sphere</a>
36   */
37  public abstract class AbstractNSphere<V extends EuclideanVector<V>> implements Region<V> {
38  
39      /** The center point of the n-sphere. */
40      private final V center;
41  
42      /** The radius of the n-sphere. */
43      private final double radius;
44  
45      /** Precision object used to perform floating point comparisons. */
46      private final Precision.DoubleEquivalence precision;
47  
48      /** Construct a new instance from its component parts.
49       * @param center the center point of the n-sphere
50       * @param radius the radius of the n-sphere
51       * @param precision precision context used to perform floating point comparisons
52       * @throws IllegalArgumentException if center is not finite or radius is not finite or is
53       *      less than or equal to zero as evaluated by the given precision context
54       */
55      protected AbstractNSphere(final V center, final double radius, final Precision.DoubleEquivalence precision) {
56          if (!center.isFinite()) {
57              throw new IllegalArgumentException("Illegal center point: " + center);
58          }
59          if (!Double.isFinite(radius) || precision.lte(radius, 0.0)) {
60              throw new IllegalArgumentException("Illegal radius: " + radius);
61          }
62  
63          this.center = center;
64          this.radius = radius;
65          this.precision = precision;
66      }
67  
68      /** Get the center point of the n-sphere.
69       * @return the center of the n-sphere
70       */
71      public V getCenter() {
72          return center;
73      }
74  
75      /** Get the radius of the n-sphere.
76       * @return the radius of the n-sphere.
77       */
78      public double getRadius() {
79          return radius;
80      }
81  
82      /** Get the precision object used to perform floating point
83       * comparisons for this instance.
84       * @return the precision object for this instance
85       */
86      public Precision.DoubleEquivalence getPrecision() {
87          return precision;
88      }
89  
90      /** {@inheritDoc}
91      *
92      * <p>This method always returns {@code false}.</p>
93      */
94      @Override
95      public boolean isFull() {
96          return false;
97      }
98  
99      /** {@inheritDoc}
100     *
101     * <p>This method always returns {@code false}.</p>
102     */
103     @Override
104     public boolean isEmpty() {
105         return false;
106     }
107 
108     /** {@inheritDoc}
109      *
110      * <p>This method is an alias for {@link #getCenter()}.</p>
111      */
112     @Override
113     public V getCentroid() {
114         return getCenter();
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public RegionLocation classify(final V pt) {
120         final double dist = ((Point<V>) center).distance(pt);
121         final int cmp = precision.compare(dist, radius);
122         if (cmp < 0) {
123             return RegionLocation.INSIDE;
124         } else if (cmp > 0) {
125             return RegionLocation.OUTSIDE;
126         }
127         return RegionLocation.BOUNDARY;
128     }
129 
130     /** {@inheritDoc} */
131     @Override
132     public int hashCode() {
133         return Objects.hash(center, radius, precision);
134     }
135 
136     /** {@inheritDoc} */
137     @Override
138     public boolean equals(final Object obj) {
139         if (this == obj) {
140             return true;
141         } else if (obj == null || !obj.getClass().equals(this.getClass())) {
142             return false;
143         }
144 
145         final AbstractNSphere<?> other = (AbstractNSphere<?>) obj;
146 
147         return Objects.equals(this.center, other.center) &&
148                 Double.compare(this.radius, other.radius) == 0 &&
149                 Objects.equals(this.getPrecision(), other.getPrecision());
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public String toString() {
155         final StringBuilder sb = new StringBuilder(30);
156         sb.append(this.getClass().getSimpleName())
157             .append("[center= ")
158             .append(center)
159             .append(", radius= ")
160             .append(radius)
161             .append(']');
162 
163         return sb.toString();
164     }
165 
166     /** Project the given point to the boundary of the n-sphere. If
167      * the given point is exactly equal to the n-sphere center, it is
168      * projected to the boundary in the direction of {@code defaultVector}.
169      * @param pt the point to project
170      * @param defaultVector the direction to project the point if it lies
171      *      exactly at the center of the n-sphere
172      * @return the projected point
173      */
174     protected V project(final V pt, final V defaultVector) {
175         V vec = center.vectorTo(pt);
176         if (vec.equals(vec.getZero())) {
177             // use the default project vector if the given point lies
178             // exactly_ on the center point
179             vec = defaultVector;
180         }
181 
182         return vec.withNorm(radius).add(center);
183     }
184 
185     /** Internal method to compute the intersections between a line and this instance. The returned list will
186      * contain either 0, 1, or 2 points.
187      * <ul>
188      *      <li><strong>2 points</strong> - The line is a secant line and intersects the n-sphere at two
189      *      distinct points. The points are ordered such that the first point in the list is the first point
190      *      encountered when traveling in the direction of the line. (In other words, the points are ordered
191      *      by increasing abscissa value.)
192      *      </li>
193      *      <li><strong>1 point</strong> - The line is a tangent line and only intersects the n-sphere at a
194      *      single point (as evaluated by the n-sphere's precision context).
195      *      </li>
196      *      <li><strong>0 points</strong> - The line does not intersect the n-sphere.</li>
197      * </ul>
198      * @param <L> Line implementation type
199      * @param line line to intersect with the n-sphere
200      * @param abscissaFn function used to compute the abscissa value of a point on a line
201      * @param distanceFn function used to compute the smallest distance between a point
202      *      and a line
203      * @return a list of intersection points between the given line and this n-sphere
204      */
205     protected <L extends Embedding<V, Vector1D>> List<V> intersections(final L line,
206             final ToDoubleBiFunction<L, V> abscissaFn, final ToDoubleBiFunction<L, V> distanceFn) {
207 
208         final double dist = distanceFn.applyAsDouble(line, center);
209 
210         final int cmp = precision.compare(dist, radius);
211         if (cmp <= 0) {
212             // on the boundary or inside the n-sphere
213             final double abscissa = abscissaFn.applyAsDouble(line, center);
214             final double abscissaDelta = Math.sqrt((radius * radius) - (dist * dist));
215 
216             final V p0 = line.toSpace(Vector1D.of(abscissa - abscissaDelta));
217             if (cmp < 0) {
218                 // secant line => two intersections
219                 final V p1 = line.toSpace(Vector1D.of(abscissa + abscissaDelta));
220 
221                 return Arrays.asList(p0, p1);
222             }
223 
224             // tangent line => one intersection
225             return Collections.singletonList(p0);
226         }
227 
228         // no intersections
229         return Collections.emptyList();
230     }
231 
232     /** Internal method to compute the first intersection between a line and this instance.
233      * @param <L> Line implementation type
234      * @param line line to intersect with the n-sphere
235      * @param abscissaFn function used to compute the abscissa value of a point on a line
236      * @param distanceFn function used to compute the smallest distance between a point
237      *      and a line
238      * @return the first intersection between the given line and this instance or null if
239      *      no such intersection exists
240      */
241     protected <L extends Embedding<V, Vector1D>> V firstIntersection(final L line,
242             final ToDoubleBiFunction<L, V> abscissaFn, final ToDoubleBiFunction<L, V> distanceFn) {
243 
244         final double dist = distanceFn.applyAsDouble(line, center);
245 
246         final int cmp = precision.compare(dist, radius);
247         if (cmp <= 0) {
248             // on the boundary or inside the n-sphere
249             final double abscissa = abscissaFn.applyAsDouble(line, center);
250             final double abscissaDelta = Math.sqrt((radius * radius) - (dist * dist));
251 
252             return line.toSpace(Vector1D.of(abscissa - abscissaDelta));
253         }
254 
255         return null;
256     }
257 }