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 }