AbstractNSphere.java

  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. import java.util.Arrays;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.Objects;
  22. import java.util.function.ToDoubleBiFunction;

  23. import org.apache.commons.geometry.core.Embedding;
  24. import org.apache.commons.geometry.core.Point;
  25. import org.apache.commons.geometry.core.Region;
  26. import org.apache.commons.geometry.core.RegionLocation;
  27. import org.apache.commons.geometry.euclidean.oned.Vector1D;
  28. import org.apache.commons.numbers.core.Precision;

  29. /** Abstract base class representing an n-sphere, which is a generalization of the ordinary 3 dimensional
  30.  * sphere to arbitrary dimensions.
  31.  * @param <V> Vector implementation type
  32.  * @see <a href="https://wikipedia.org/wiki/N-sphere">N-sphere</a>
  33.  */
  34. public abstract class AbstractNSphere<V extends EuclideanVector<V>> implements Region<V> {

  35.     /** The center point of the n-sphere. */
  36.     private final V center;

  37.     /** The radius of the n-sphere. */
  38.     private final double radius;

  39.     /** Precision object used to perform floating point comparisons. */
  40.     private final Precision.DoubleEquivalence precision;

  41.     /** Construct a new instance from its component parts.
  42.      * @param center the center point of the n-sphere
  43.      * @param radius the radius of the n-sphere
  44.      * @param precision precision context used to perform floating point comparisons
  45.      * @throws IllegalArgumentException if center is not finite or radius is not finite or is
  46.      *      less than or equal to zero as evaluated by the given precision context
  47.      */
  48.     protected AbstractNSphere(final V center, final double radius, final Precision.DoubleEquivalence precision) {
  49.         if (!center.isFinite()) {
  50.             throw new IllegalArgumentException("Illegal center point: " + center);
  51.         }
  52.         if (!Double.isFinite(radius) || precision.lte(radius, 0.0)) {
  53.             throw new IllegalArgumentException("Illegal radius: " + radius);
  54.         }

  55.         this.center = center;
  56.         this.radius = radius;
  57.         this.precision = precision;
  58.     }

  59.     /** Get the center point of the n-sphere.
  60.      * @return the center of the n-sphere
  61.      */
  62.     public V getCenter() {
  63.         return center;
  64.     }

  65.     /** Get the radius of the n-sphere.
  66.      * @return the radius of the n-sphere.
  67.      */
  68.     public double getRadius() {
  69.         return radius;
  70.     }

  71.     /** Get the precision object used to perform floating point
  72.      * comparisons for this instance.
  73.      * @return the precision object for this instance
  74.      */
  75.     public Precision.DoubleEquivalence getPrecision() {
  76.         return precision;
  77.     }

  78.     /** {@inheritDoc}
  79.     *
  80.     * <p>This method always returns {@code false}.</p>
  81.     */
  82.     @Override
  83.     public boolean isFull() {
  84.         return false;
  85.     }

  86.     /** {@inheritDoc}
  87.     *
  88.     * <p>This method always returns {@code false}.</p>
  89.     */
  90.     @Override
  91.     public boolean isEmpty() {
  92.         return false;
  93.     }

  94.     /** {@inheritDoc}
  95.      *
  96.      * <p>This method is an alias for {@link #getCenter()}.</p>
  97.      */
  98.     @Override
  99.     public V getCentroid() {
  100.         return getCenter();
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     public RegionLocation classify(final V pt) {
  105.         final double dist = ((Point<V>) center).distance(pt);
  106.         final int cmp = precision.compare(dist, radius);
  107.         if (cmp < 0) {
  108.             return RegionLocation.INSIDE;
  109.         } else if (cmp > 0) {
  110.             return RegionLocation.OUTSIDE;
  111.         }
  112.         return RegionLocation.BOUNDARY;
  113.     }

  114.     /** {@inheritDoc} */
  115.     @Override
  116.     public int hashCode() {
  117.         return Objects.hash(center, radius, precision);
  118.     }

  119.     /** {@inheritDoc} */
  120.     @Override
  121.     public boolean equals(final Object obj) {
  122.         if (this == obj) {
  123.             return true;
  124.         } else if (obj == null || !obj.getClass().equals(this.getClass())) {
  125.             return false;
  126.         }

  127.         final AbstractNSphere<?> other = (AbstractNSphere<?>) obj;

  128.         return Objects.equals(this.center, other.center) &&
  129.                 Double.compare(this.radius, other.radius) == 0 &&
  130.                 Objects.equals(this.getPrecision(), other.getPrecision());
  131.     }

  132.     /** {@inheritDoc} */
  133.     @Override
  134.     public String toString() {
  135.         final StringBuilder sb = new StringBuilder(30);
  136.         sb.append(this.getClass().getSimpleName())
  137.             .append("[center= ")
  138.             .append(center)
  139.             .append(", radius= ")
  140.             .append(radius)
  141.             .append(']');

  142.         return sb.toString();
  143.     }

  144.     /** Project the given point to the boundary of the n-sphere. If
  145.      * the given point is exactly equal to the n-sphere center, it is
  146.      * projected to the boundary in the direction of {@code defaultVector}.
  147.      * @param pt the point to project
  148.      * @param defaultVector the direction to project the point if it lies
  149.      *      exactly at the center of the n-sphere
  150.      * @return the projected point
  151.      */
  152.     protected V project(final V pt, final V defaultVector) {
  153.         V vec = center.vectorTo(pt);
  154.         if (vec.equals(vec.getZero())) {
  155.             // use the default project vector if the given point lies
  156.             // exactly_ on the center point
  157.             vec = defaultVector;
  158.         }

  159.         return vec.withNorm(radius).add(center);
  160.     }

  161.     /** Internal method to compute the intersections between a line and this instance. The returned list will
  162.      * contain either 0, 1, or 2 points.
  163.      * <ul>
  164.      *      <li><strong>2 points</strong> - The line is a secant line and intersects the n-sphere at two
  165.      *      distinct points. The points are ordered such that the first point in the list is the first point
  166.      *      encountered when traveling in the direction of the line. (In other words, the points are ordered
  167.      *      by increasing abscissa value.)
  168.      *      </li>
  169.      *      <li><strong>1 point</strong> - The line is a tangent line and only intersects the n-sphere at a
  170.      *      single point (as evaluated by the n-sphere's precision context).
  171.      *      </li>
  172.      *      <li><strong>0 points</strong> - The line does not intersect the n-sphere.</li>
  173.      * </ul>
  174.      * @param <L> Line implementation type
  175.      * @param line line to intersect with the n-sphere
  176.      * @param abscissaFn function used to compute the abscissa value of a point on a line
  177.      * @param distanceFn function used to compute the smallest distance between a point
  178.      *      and a line
  179.      * @return a list of intersection points between the given line and this n-sphere
  180.      */
  181.     protected <L extends Embedding<V, Vector1D>> List<V> intersections(final L line,
  182.             final ToDoubleBiFunction<L, V> abscissaFn, final ToDoubleBiFunction<L, V> distanceFn) {

  183.         final double dist = distanceFn.applyAsDouble(line, center);

  184.         final int cmp = precision.compare(dist, radius);
  185.         if (cmp <= 0) {
  186.             // on the boundary or inside the n-sphere
  187.             final double abscissa = abscissaFn.applyAsDouble(line, center);
  188.             final double abscissaDelta = Math.sqrt((radius * radius) - (dist * dist));

  189.             final V p0 = line.toSpace(Vector1D.of(abscissa - abscissaDelta));
  190.             if (cmp < 0) {
  191.                 // secant line => two intersections
  192.                 final V p1 = line.toSpace(Vector1D.of(abscissa + abscissaDelta));

  193.                 return Arrays.asList(p0, p1);
  194.             }

  195.             // tangent line => one intersection
  196.             return Collections.singletonList(p0);
  197.         }

  198.         // no intersections
  199.         return Collections.emptyList();
  200.     }

  201.     /** Internal method to compute the first intersection between a line and this instance.
  202.      * @param <L> Line implementation type
  203.      * @param line line to intersect with the n-sphere
  204.      * @param abscissaFn function used to compute the abscissa value of a point on a line
  205.      * @param distanceFn function used to compute the smallest distance between a point
  206.      *      and a line
  207.      * @return the first intersection between the given line and this instance or null if
  208.      *      no such intersection exists
  209.      */
  210.     protected <L extends Embedding<V, Vector1D>> V firstIntersection(final L line,
  211.             final ToDoubleBiFunction<L, V> abscissaFn, final ToDoubleBiFunction<L, V> distanceFn) {

  212.         final double dist = distanceFn.applyAsDouble(line, center);

  213.         final int cmp = precision.compare(dist, radius);
  214.         if (cmp <= 0) {
  215.             // on the boundary or inside the n-sphere
  216.             final double abscissa = abscissaFn.applyAsDouble(line, center);
  217.             final double abscissaDelta = Math.sqrt((radius * radius) - (dist * dist));

  218.             return line.toSpace(Vector1D.of(abscissa - abscissaDelta));
  219.         }

  220.         return null;
  221.     }
  222. }