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.Arrays;
    20  import java.util.Comparator;
    21  import java.util.Iterator;
    22  import java.util.function.UnaryOperator;
    23
    24  import org.apache.commons.geometry.core.internal.DoubleFunction3N;
    25  import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
    26  import org.apache.commons.geometry.euclidean.EuclideanVectorSum;
    27  import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector;
    28  import org.apache.commons.geometry.euclidean.internal.Vectors;
    29  import org.apache.commons.numbers.core.Precision;
    30
    31  /** This class represents vectors and points in three-dimensional Euclidean space.
    32   * Instances of this class are guaranteed to be immutable.
    33   */
    34  public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
    35
    36      /** Zero (null) vector (coordinates: 0, 0, 0). */
    37      public static final Vector3D ZERO = new Vector3D(0, 0, 0);
    38
    39      /** A vector with all coordinates set to NaN. */
    40      public static final Vector3D NaN = new Vector3D(Double.NaN, Double.NaN, Double.NaN);
    41
    42      /** A vector with all coordinates set to positive infinity. */
    43      public static final Vector3D POSITIVE_INFINITY =
    44          new Vector3D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
    45
    46      /** A vector with all coordinates set to negative infinity. */
    47      public static final Vector3D NEGATIVE_INFINITY =
    48          new Vector3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
    49
    50      /** Comparator that sorts vectors in component-wise ascending order.
    51       * Vectors are only considered equal if their coordinates match exactly.
    52       * Null arguments are evaluated as being greater than non-null arguments.
    53       */
    54      public static final Comparator<Vector3D> COORDINATE_ASCENDING_ORDER = (a, b) -> {
    55          int cmp = 0;
    56
    57          if (a != null && b != null) {
    58              cmp = Double.compare(a.getX(), b.getX());
    59              if (cmp == 0) {
    60                  cmp = Double.compare(a.getY(), b.getY());
    61                  if (cmp == 0) {
    62                      cmp = Double.compare(a.getZ(), b.getZ());
    63                  }
    64              }
    65          } else if (a != null) {
    66              cmp = -1;
    67          } else if (b != null) {
    68              cmp = 1;
    69          }
    70
    71          return cmp;
    72      };
    73
    74      /** X coordinate value (abscissa). */
    75      private final double x;
    76
    77      /** Y coordinate value (ordinate). */
    78      private final double y;
    79
    80      /** Z coordinate value (height). */
    81      private final double z;
    82
    83      /** Simple constructor.
    84       * Build a vector from its coordinates
    85       * @param x x coordinate value
    86       * @param y y coordinate value
    87       * @param z z coordinate value
    88       */
    89      private Vector3D(final double x, final double y, final double z) {
    90          this.x = x;
    91          this.y = y;
    92          this.z = z;
    93      }
    94
    95      /** Return the x coordinate value (abscissa) of the instance.
    96       * @return the x coordinate value
    97       */
    98      public double getX() {
    99          return x;
    100     }
    101
    102     /** Return the y coordinate value (ordinate) of the instance.
    103      * @return the y coordinate value
    104      */
    105     public double getY() {
    106         return y;
    107     }
    108
    109     /** Returns the z coordinate value (height) of the instance.
    110      * @return the z coordinate value
    111      */
    112     public double getZ() {
    113         return z;
    114     }
    115
    116     /** Get the coordinates for this instance as a dimension 3 array.
    117      * @return the coordinates for this instance
    118      */
    119     public double[] toArray() {
    120         return new double[]{x, y, z};
    121     }
    122
    123     /** {@inheritDoc} */
    124     @Override
    125     public int getDimension() {
    126         return 3;
    127     }
    128
    129     /** {@inheritDoc} */
    130     @Override
    131     public boolean isNaN() {
    132         return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z);
    133     }
    134
    135     /** {@inheritDoc} */
    136     @Override
    137     public boolean isInfinite() {
    138         return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z));
    139     }
    140
    141     /** {@inheritDoc} */
    142     @Override
    143     public boolean isFinite() {
    144         return Double.isFinite(x) && Double.isFinite(y) && Double.isFinite(z);
    145     }
    146
    147     /** {@inheritDoc} */
    148     @Override
    149     public Vector3D getZero() {
    150         return ZERO;
    151     }
    152
    153     /** {@inheritDoc} */
    154     @Override
    155     public Vector3D vectorTo(final Vector3D v) {
    156         return v.subtract(this);
    157     }
    158
    159     /** {@inheritDoc} */
    160     @Override
    161     public Unit directionTo(final Vector3D v) {
    162         return vectorTo(v).normalize();
    163     }
    164
    165     /** {@inheritDoc} */
    166     @Override
    167     public Vector3D lerp(final Vector3D p, final double t) {
    168         return Sum.create()
    169                 .addScaled(1.0 - t, this)
    170                 .addScaled(t, p).get();
    171     }
    172
    173     /** {@inheritDoc} */
    174     @Override
    175     public double norm() {
    176         return Vectors.norm(x, y, z);
    177     }
    178
    179     /** {@inheritDoc} */
    180     @Override
    181     public double normSq() {
    182         return Vectors.normSq(x, y, z);
    183     }
    184
    185     /** {@inheritDoc} */
    186     @Override
    187     public Vector3D withNorm(final double magnitude) {
    188         final double m = magnitude / getCheckedNorm();
    189
    190         return new Vector3D(
    191                     m * x,
    192                     m * y,
    193                     m * z
    194                 );
    195     }
    196
    197     /** {@inheritDoc} */
    198     @Override
    199     public Vector3D add(final Vector3D v) {
    200         return new Vector3D(
    201                     x + v.x,
    202                     y + v.y,
    203                     z + v.z
    204                 );
    205     }
    206
    207     /** {@inheritDoc} */
    208     @Override
    209     public Vector3D add(final double factor, final Vector3D v) {
    210         return new Vector3D(
    211                     x + (factor * v.x),
    212                     y + (factor * v.y),
    213                     z + (factor * v.z)
    214                 );
    215     }
    216
    217     /** {@inheritDoc} */
    218     @Override
    219     public Vector3D subtract(final Vector3D v) {
    220         return new Vector3D(
    221                     x - v.x,
    222                     y - v.y,
    223                     z - v.z
    224                 );
    225     }
    226
    227     /** {@inheritDoc} */
    228     @Override
    229     public Vector3D subtract(final double factor, final Vector3D v) {
    230         return new Vector3D(
    231                     x - (factor * v.x),
    232                     y - (factor * v.y),
    233                     z - (factor * v.z)
    234                 );
    235     }
    236
    237     /** {@inheritDoc} */
    238     @Override
    239     public Vector3D negate() {
    240         return new Vector3D(-x, -y, -z);
    241     }
    242
    243     /** {@inheritDoc} */
    244     @Override
    245     public Unit normalize() {
    246         return Unit.from(x, y, z);
    247     }
    248
    249     /** {@inheritDoc} */
    250     @Override
    251     public Unit normalizeOrNull() {
    252         return Unit.tryCreateNormalized(x, y, z, false);
    253     }
    254
    255     /** {@inheritDoc} */
    256     @Override
    257     public Vector3D multiply(final double a) {
    258         return new Vector3D(a * x, a * y, a * z);
    259     }
    260
    261     /** {@inheritDoc} */
    262     @Override
    263     public double distance(final Vector3D v) {
    264         return Vectors.norm(
    265                 x - v.x,
    266                 y - v.y,
    267                 z - v.z
    268             );
    269     }
    270
    271     /** {@inheritDoc} */
    272     @Override
    273     public double distanceSq(final Vector3D v) {
    274         return Vectors.normSq(
    275                 x - v.x,
    276                 y - v.y,
    277                 z - v.z
    278             );
    279     }
    280
    281     /** {@inheritDoc}
    282      * <p>
    283      * The implementation uses specific multiplication and addition
    284      * algorithms to preserve accuracy and reduce cancellation effects.
    285      * It should be very accurate even for nearly orthogonal vectors.
    286      * </p>
    287      * @see org.apache.commons.numbers.core.Sum
    288      */
    289     @Override
    290     public double dot(final Vector3D v) {
    291         return Vectors.linearCombination(
    292                 x, v.x,
    293                 y, v.y,
    294                 z, v.z);
    295     }
    296
    297     /** {@inheritDoc}
    298      * <p>This method computes the angular separation between two
    299      * vectors using the dot product for well separated vectors and the
    300      * cross product for almost aligned vectors. This allows to have a
    301      * good accuracy in all cases, even for vectors very close to each
    302      * other.</p>
    303      */
    304     @Override
    305     public double angle(final Vector3D v) {
    306         final double normProduct = getCheckedNorm() * v.getCheckedNorm();
    307
    308         final double dot = dot(v);
    309         final double threshold = normProduct * 0.99;
    310         if ((dot < -threshold) || (dot > threshold)) {
    311             // the vectors are almost aligned, compute using the sine
    312             final Vector3D cross = cross(v);
    313             if (dot >= 0) {
    314                 return Math.asin(cross.norm() / normProduct);
    315             }
    316             return Math.PI - Math.asin(cross.norm() / normProduct);
    317         }
    318
    319         // the vectors are sufficiently separated to use the cosine
    320         return Math.acos(dot / normProduct);
    321     }
    322
    323     /** {@inheritDoc} */
    324     @Override
    325     public Vector3D project(final Vector3D base) {
    326         return getComponent(base, false, Vector3D::new);
    327     }
    328
    329     /** {@inheritDoc} */
    330     @Override
    331     public Vector3D reject(final Vector3D base) {
    332         return getComponent(base, true, Vector3D::new);
    333     }
    334
    335     /** {@inheritDoc}
    336      * <p>There are an infinite number of normalized vectors orthogonal
    337      * to the instance. This method picks up one of them almost
    338      * arbitrarily. It is useful when one needs to compute a reference
    339      * frame with one of the axes in a predefined direction. The
    340      * following example shows how to build a frame having the k axis
    341      * aligned with the known vector u :
    342      * <pre><code>
    343      *   Vector3D k = u.normalize();
    344      *   Vector3D i = k.orthogonal();
    345      *   Vector3D j = k.cross(i);
    346      * </code></pre>
    347      * @return a unit vector orthogonal to the instance
    348      * @throws IllegalArgumentException if the norm of the instance
    349      *      is zero, NaN, or infinite
    350      */
    351     @Override
    352     public Vector3D.Unit orthogonal() {
    353         final double threshold = 0.6 * getCheckedNorm();
    354
    355         final double inverse;
    356         if (Math.abs(x) <= threshold) {
    357             inverse  = 1 / Vectors.norm(y, z);
    358             return new Unit(0, inverse * z, -inverse * y);
    359         } else if (Math.abs(y) <= threshold) {
    360             inverse  = 1 / Vectors.norm(x, z);
    361             return new Unit(-inverse * z, 0, inverse * x);
    362         }
    363         inverse  = 1 / Vectors.norm(x, y);
    364         return new Unit(inverse * y, -inverse * x, 0);
    365     }
    366
    367     /** {@inheritDoc} */
    368     @Override
    369     public Vector3D.Unit orthogonal(final Vector3D dir) {
    370         return dir.getComponent(this, true, Vector3D.Unit::from);
    371     }
    372
    373     /** Compute the cross-product of the instance with another vector.
    374      * @param v other vector
    375      * @return the cross product this ^ v as a new Vector3D
    376      */
    377     public Vector3D cross(final Vector3D v) {
    378         return new Vector3D(Vectors.linearCombination(y, v.z, -z, v.y),
    379                             Vectors.linearCombination(z, v.x, -x, v.z),
    380                             Vectors.linearCombination(x, v.y, -y, v.x));
    381     }
    382
    383     /** Convenience method to apply a function to this vector. This
    384      * can be used to transform the vector inline with other methods.
    385      * @param fn the function to apply
    386      * @return the transformed vector
    387      */
    388     public Vector3D transform(final UnaryOperator<Vector3D> fn) {
    389         return fn.apply(this);
    390     }
    391
    392     /** {@inheritDoc} */
    393     @Override
    394     public boolean eq(final Vector3D vec, final Precision.DoubleEquivalence precision) {
    395         return precision.eq(x, vec.x) &&
    396                 precision.eq(y, vec.y) &&
    397                 precision.eq(z, vec.z);
    398     }
    399
    400     /**
    401      * Get a hashCode for the vector.
    402      * <p>All NaN values have the same hash code.</p>
    403      *
    404      * @return a hash code value for this object
    405      */
    406     @Override
    407     public int hashCode() {
    408         if (isNaN()) {
    409             return 642;
    410         }
    411         return 643 * (164 * Double.hashCode(x) + 3 * Double.hashCode(y) + Double.hashCode(z));
    412     }
    413
    414     /**d
    415      * Test for the equality of two vector instances.
    416      * <p>
    417      * If all coordinates of two vectors are exactly the same, and none are
    418      * <code>Double.NaN</code>, the two instances are considered to be equal.
    419      * </p>
    420      * <p>
    421      * <code>NaN</code> coordinates are considered to globally affect the vector
    422      * and be equal to each other - i.e, if either (or all) coordinates of the
    423      * vector are equal to <code>Double.NaN</code>, the vector is equal to
    424      * {@link #NaN}.
    425      * </p>
    426      *
    427      * @param other Object to test for equality to this
    428      * @return true if two Vector3D objects are equal, false if
    429      *         object is null, not an instance of Vector3D, or
    430      *         not equal to this Vector3D instance
    431      *
    432      */
    433     @Override
    434     public boolean equals(final Object other) {
    435         if (this == other) {
    436             return true;
    437         }
    438         if (other instanceof Vector3D) {
    439             final Vector3D rhs = (Vector3D) other;
    440             if (rhs.isNaN()) {
    441                 return this.isNaN();
    442             }
    443
    444             return Double.compare(x, rhs.x) == 0 &&
    445                     Double.compare(y, rhs.y) == 0 &&
    446                     Double.compare(z, rhs.z) == 0;
    447         }
    448         return false;
    449     }
    450
    451     /** {@inheritDoc} */
    452     @Override
    453     public String toString() {
    454         return SimpleTupleFormat.getDefault().format(x, y, z);
    455     }
    456
    457     /** Returns a component of the current instance relative to the given base
    458      * vector. If {@code reject} is true, the vector rejection is returned; otherwise,
    459      * the projection is returned.
    460      * @param base The base vector
    461      * @param reject If true, the rejection of this instance from {@code base} is
    462      *      returned. If false, the projection of this instance onto {@code base}
    463      *      is returned.
    464      * @param factory factory function used to build the final vector
    465      * @param <V> Vector implementation type
    466      * @return The projection or rejection of this instance relative to {@code base},
    467      *      depending on the value of {@code reject}.
    468      * @throws IllegalArgumentException if {@code base} has a zero, NaN,
    469      *      or infinite norm
    470      */
    471     private <V extends Vector3D> V getComponent(final Vector3D base, final boolean reject,
    472                                                 final DoubleFunction3N<V> factory) {
    473         final double aDotB = dot(base);
    474
    475         // We need to check the norm value here to ensure that it's legal. However, we don't
    476         // want to incur the cost or floating point error of getting the actual norm and then
    477         // multiplying it again to get the square norm. So, we'll just check the squared norm
    478         // directly. This will produce the same error result as checking the actual norm since
    479         // Math.sqrt(0.0) == 0.0, Math.sqrt(Double.NaN) == Double.NaN and
    480         // Math.sqrt(Double.POSITIVE_INFINITY) == Double.POSITIVE_INFINITY.
    481         final double baseMagSq = Vectors.checkedNorm(base.normSq());
    482
    483         final double scale = aDotB / baseMagSq;
    484
    485         final double projX = scale * base.x;
    486         final double projY = scale * base.y;
    487         final double projZ = scale * base.z;
    488
    489         if (reject) {
    490             return factory.apply(x - projX, y - projY, z - projZ);
    491         }
    492
    493         return factory.apply(projX, projY, projZ);
    494     }
    495
    496     /** Returns a vector with the given coordinate values.
    497      * @param x x coordinate value
    498      * @param y y coordinate value
    499      * @param z z coordinate value
    500      * @return vector instance
    501      */
    502     public static Vector3D of(final double x, final double y, final double z) {
    503         return new Vector3D(x, y, z);
    504     }
    505
    506     /** Creates a vector from the coordinates in the given 3-element array.
    507      * @param v coordinates array
    508      * @return new vector
    509      * @exception IllegalArgumentException if the array does not have 3 elements
    510      */
    511     public static Vector3D of(final double[] v) {
    512         if (v.length != 3) {
    513             throw new IllegalArgumentException("Dimension mismatch: " + v.length + " != 3");
    514         }
    515         return new Vector3D(v[0], v[1], v[2]);
    516     }
    517
    518     /** Parses the given string and returns a new vector instance. The expected string
    519      * format is the same as that returned by {@link #toString()}.
    520      * @param str the string to parse
    521      * @return vector instance represented by the string
    522      * @throws IllegalArgumentException if the given string has an invalid format
    523      */
    524     public static Vector3D parse(final String str) {
    525         return SimpleTupleFormat.getDefault().parse(str, Vector3D::new);
    526     }
    527
    528     /** Return a vector containing the maximum component values from all input vectors.
    529      * @param first first vector
    530      * @param more additional vectors
    531      * @return a vector containing the maximum component values from all input vectors
    532      */
    533     public static Vector3D max(final Vector3D first, final Vector3D... more) {
    534         return computeMax(first, Arrays.asList(more).iterator());
    535     }
    536
    537     /** Return a vector containing the maximum component values from all input vectors.
    538      * @param vecs input vectors
    539      * @return a vector containing the maximum component values from all input vectors
    540      * @throws IllegalArgumentException if the argument does not contain any vectors
    541      */
    542     public static Vector3D max(final Iterable<Vector3D> vecs) {
    543         final Iterator<Vector3D> it = vecs.iterator();
    544         if (!it.hasNext()) {
    545             throw new IllegalArgumentException("Cannot compute vector max: no vectors given");
    546         }
    547
    548         return computeMax(it.next(), it);
    549     }
    550
    551     /** Internal method for computing a max vector.
    552      * @param first first vector
    553      * @param more iterator with additional vectors
    554      * @return vector containing the maximum component values of all input vectors
    555      */
    556     private static Vector3D computeMax(final Vector3D first, final Iterator<? extends Vector3D> more) {
    557         double x = first.getX();
    558         double y = first.getY();
    559         double z = first.getZ();
    560
    561         Vector3D vec;
    562         while (more.hasNext()) {
    563             vec = more.next();
    564
    565             x = Math.max(x, vec.getX());
    566             y = Math.max(y, vec.getY());
    567             z = Math.max(z, vec.getZ());
    568         }
    569
    570         return Vector3D.of(x, y, z);
    571     }
    572
    573     /** Return a vector containing the minimum component values from all input vectors.
    574      * @param first first vector
    575      * @param more additional vectors
    576      * @return a vector containing the minimum component values from all input vectors
    577      */
    578     public static Vector3D min(final Vector3D first, final Vector3D... more) {
    579         return computeMin(first, Arrays.asList(more).iterator());
    580     }
    581
    582     /** Return a vector containing the minimum component values from all input vectors.
    583      * @param vecs input vectors
    584      * @return a vector containing the minimum component values from all input vectors
    585      * @throws IllegalArgumentException if the argument does not contain any vectors
    586      */
    587     public static Vector3D min(final Iterable<Vector3D> vecs) {
    588         final Iterator<Vector3D> it = vecs.iterator();
    589         if (!it.hasNext()) {
    590             throw new IllegalArgumentException("Cannot compute vector min: no vectors given");
    591         }
    592
    593         return computeMin(it.next(), it);
    594     }
    595
    596     /** Internal method for computing a min vector.
    597      * @param first first vector
    598      * @param more iterator with additional vectors
    599      * @return vector containing the minimum component values of all input vectors
    600      */
    601     private static Vector3D computeMin(final Vector3D first, final Iterator<? extends Vector3D> more) {
    602         double x = first.getX();
    603         double y = first.getY();
    604         double z = first.getZ();
    605
    606         Vector3D vec;
    607         while (more.hasNext()) {
    608             vec = more.next();
    609
    610             x = Math.min(x, vec.getX());
    611             y = Math.min(y, vec.getY());
    612             z = Math.min(z, vec.getZ());
    613         }
    614
    615         return Vector3D.of(x, y, z);
    616     }
    617
    618     /** Compute the centroid of the given points. The centroid is the arithmetic mean position of a set
    619      * of points.
    620      * @param first first point
    621      * @param more additional points
    622      * @return the centroid of the given points
    623      */
    624     public static Vector3D centroid(final Vector3D first, final Vector3D... more) {
    625         return computeCentroid(first, Arrays.asList(more).iterator());
    626     }
    627
    628     /** Compute the centroid of the given points. The centroid is the arithmetic mean position of a set
    629      * of points.
    630      * @param pts the points to compute the centroid of
    631      * @return the centroid of the given points
    632      * @throws IllegalArgumentException if the argument contains no points
    633      */
    634     public static Vector3D centroid(final Iterable<Vector3D> pts) {
    635         final Iterator<Vector3D> it = pts.iterator();
    636         if (!it.hasNext()) {
    637             throw new IllegalArgumentException("Cannot compute centroid: no points given");
    638         }
    639
    640         return computeCentroid(it.next(), it);
    641     }
    642
    643     /** Internal method for computing the centroid of a set of points.
    644      * @param first first point
    645      * @param more iterator with additional points
    646      * @return the centroid of the point set
    647      */
    648     private static Vector3D computeCentroid(final Vector3D first, final Iterator<? extends Vector3D> more) {
    649         final Sum sum = Sum.of(first);
    650         int count = 1;
    651
    652         while (more.hasNext()) {
    653             sum.add(more.next());
    654             ++count;
    655         }
    656
    657         return sum.get().multiply(1.0 / count);
    658     }
    659
    660     /**
    661      * Represents unit vectors.
    662      * This allows optimizations for certain operations.
    663      */
    664     public static final class Unit extends Vector3D {
    665         /** Unit vector (coordinates: 1, 0, 0). */
    666         public static final Unit PLUS_X  = new Unit(1d, 0d, 0d);
    667         /** Negation of unit vector (coordinates: -1, 0, 0). */
    668         public static final Unit MINUS_X = new Unit(-1d, 0d, 0d);
    669         /** Unit vector (coordinates: 0, 1, 0). */
    670         public static final Unit PLUS_Y  = new Unit(0d, 1d, 0d);
    671         /** Negation of unit vector (coordinates: 0, -1, 0). */
    672         public static final Unit MINUS_Y = new Unit(0d, -1d, 0d);
    673         /** Unit vector (coordinates: 0, 0, 1). */
    674         public static final Unit PLUS_Z  = new Unit(0d, 0d, 1d);
    675         /** Negation of unit vector (coordinates: 0, 0, -1). */
    676         public static final Unit MINUS_Z = new Unit(0d, 0d, -1d);
    677
    678         /** Maximum coordinate value for computing normalized vectors
    679          * with raw, unscaled values.
    680          */
    681         private static final double UNSCALED_MAX = 0x1.0p+500;
    682
    683         /** Factor used to scale up coordinate values in order to produce
    684          * normalized coordinates without overflow or underflow.
    685          */
    686         private static final double SCALE_UP_FACTOR = 0x1.0p+600;
    687
    688         /** Factor used to scale down coordinate values in order to produce
    689          * normalized coordinates without overflow or underflow.
    690          */
    691         private static final double SCALE_DOWN_FACTOR = 0x1.0p-600;
    692
    693         /** Simple constructor. Callers are responsible for ensuring that the given
    694          * values represent a normalized vector.
    695          * @param x x coordinate value
    696          * @param y x coordinate value
    697          * @param z x coordinate value
    698          */
    699         private Unit(final double x, final double y, final double z) {
    700             super(x, y, z);
    701         }
    702
    703         /** {@inheritDoc} */
    704         @Override
    705         public double norm() {
    706             return 1;
    707         }
    708
    709         /** {@inheritDoc} */
    710         @Override
    711         public double normSq() {
    712             return 1;
    713         }
    714
    715         /** {@inheritDoc} */
    716         @Override
    717         public Unit normalize() {
    718             return this;
    719         }
    720
    721         /** {@inheritDoc} */
    722         @Override
    723         public Unit normalizeOrNull() {
    724             return this;
    725         }
    726
    727         /** {@inheritDoc} */
    728         @Override
    729         public Vector3D withNorm(final double mag) {
    730             return multiply(mag);
    731         }
    732
    733         /** {@inheritDoc} */
    734         @Override
    735         public Unit negate() {
    736             return new Unit(-getX(), -getY(), -getZ());
    737         }
    738
    739         /** Create a normalized vector.
    740          * @param x Vector coordinate.
    741          * @param y Vector coordinate.
    742          * @param z Vector coordinate.
    743          * @return a vector whose norm is 1.
    744          * @throws IllegalArgumentException if the norm of the given value is zero, NaN,
    745          *      or infinite
    746          */
    747         public static Unit from(final double x, final double y, final double z) {
    748             return tryCreateNormalized(x, y, z, true);
    749         }
    750
    751         /** Create a normalized vector.
    752          * @param v Vector.
    753          * @return a vector whose norm is 1.
    754          * @throws IllegalArgumentException if the norm of the given value is zero, NaN,
    755          *      or infinite
    756          */
    757         public static Unit from(final Vector3D v) {
    758             return v instanceof Unit ?
    759                 (Unit) v :
    760                 from(v.getX(), v.getY(), v.getZ());
    761         }
    762
    763         /** Attempt to create a normalized vector from the given coordinate values. If {@code throwOnFailure}
    764          * is true, an exception is thrown if a normalized vector cannot be created. Otherwise, null
    765          * is returned.
    766          * @param x x coordinate
    767          * @param y y coordinate
    768          * @param z z coordinate
    769          * @param throwOnFailure if true, an exception will be thrown if a normalized vector cannot be created
    770          * @return normalized vector or null if one cannot be created and {@code throwOnFailure}
    771          *      is false
    772          * @throws IllegalArgumentException if the computed norm is zero, NaN, or infinite
    773          */
    774         private static Unit tryCreateNormalized(final double x, final double y, final double z,
    775                 final boolean throwOnFailure) {
    776
    777             // Compute the inverse norm directly. If the result is a non-zero real number,
    778             // then we can go ahead and construct the unit vector immediately. If not,
    779             // we'll do some extra work for edge cases.
    780             final double norm = Math.sqrt((x * x) + (y * y) + (z * z));
    781             final double normInv = 1.0 / norm;
    782             if (Vectors.isRealNonZero(normInv)) {
    783                 return new Unit(
    784                         x * normInv,
    785                         y * normInv,
    786                         z * normInv);
    787             }
    788
    789             // Direct computation did not work. Try scaled versions of the coordinates
    790             // to handle overflow and underflow.
    791             final double scaledX;
    792             final double scaledY;
    793             final double scaledZ;
    794
    795             final double maxCoord = Math.max(Math.max(Math.abs(x), Math.abs(y)), Math.abs(z));
    796             if (maxCoord > UNSCALED_MAX) {
    797                 scaledX = x * SCALE_DOWN_FACTOR;
    798                 scaledY = y * SCALE_DOWN_FACTOR;
    799                 scaledZ = z * SCALE_DOWN_FACTOR;
    800             } else {
    801                 scaledX = x * SCALE_UP_FACTOR;
    802                 scaledY = y * SCALE_UP_FACTOR;
    803                 scaledZ = z * SCALE_UP_FACTOR;
    804             }
    805
    806             final double scaledNormInv = 1.0 / Math.sqrt(
    807                     (scaledX * scaledX) +
    808                     (scaledY * scaledY) +
    809                     (scaledZ * scaledZ));
    810
    811             if (Vectors.isRealNonZero(scaledNormInv)) {
    812                 return new Unit(
    813                         scaledX * scaledNormInv,
    814                         scaledY * scaledNormInv,
    815                         scaledZ * scaledNormInv);
    816             } else if (throwOnFailure) {
    817                 throw Vectors.illegalNorm(norm);
    818             }
    819             return null;
    820         }
    821     }
    822
    823     /** Class used to create high-accuracy sums of vectors. Each vector component is
    824      * summed using an instance of {@link org.apache.commons.numbers.core.Sum}.
    825      *
    826      * <p>This class is mutable and not thread-safe.
    827      * @see org.apache.commons.numbers.core.Sum
    828      */
    829     public static final class Sum extends EuclideanVectorSum<Vector3D> {
    830         /** X component sum. */
    831         private final org.apache.commons.numbers.core.Sum xsum;
    832         /** Y component sum. */
    833         private final org.apache.commons.numbers.core.Sum ysum;
    834         /** Z component sum. */
    835         private final org.apache.commons.numbers.core.Sum zsum;
    836
    837         /** Construct a new instance with the given initial value.
    838          * @param initial initial value
    839          */
    840         Sum(final Vector3D initial) {
    841             this.xsum = org.apache.commons.numbers.core.Sum.of(initial.x);
    842             this.ysum = org.apache.commons.numbers.core.Sum.of(initial.y);
    843             this.zsum = org.apache.commons.numbers.core.Sum.of(initial.z);
    844         }
    845
    846         /** {@inheritDoc} */
    847         @Override
    848         public Sum add(final Vector3D vec) {
    849             xsum.add(vec.x);
    850             ysum.add(vec.y);
    851             zsum.add(vec.z);
    852             return this;
    853         }
    854
    855         /** {@inheritDoc} */
    856         @Override
    857         public Sum addScaled(final double scale, final Vector3D vec) {
    858             xsum.addProduct(scale, vec.x);
    859             ysum.addProduct(scale, vec.y);
    860             zsum.addProduct(scale, vec.z);
    861             return this;
    862         }
    863
    864         /** {@inheritDoc} */
    865         @Override
    866         public Vector3D get() {
    867             return Vector3D.of(
    868                     xsum.getAsDouble(),
    869                     ysum.getAsDouble(),
    870                     zsum.getAsDouble());
    871         }
    872
    873         /** Create a new instance with an initial value set to the {@link Vector3D#ZERO zero vector}.
    874          * @return new instance set to zero
    875          */
    876         public static Sum create() {
    877             return new Sum(Vector3D.ZERO);
    878         }
    879
    880         /** Construct a new instance with an initial value set to the argument.
    881          * @param initial initial sum value
    882          * @return new instance
    883          */
    884         public static Sum of(final Vector3D initial) {
    885             return new Sum(initial);
    886         }
    887
    888         /** Construct a new instance from multiple values.
    889          * @param first first vector
    890          * @param more additional vectors
    891          * @return new instance
    892          */
    893         public static Sum of(final Vector3D first, final Vector3D... more) {
    894             final Sum s = new Sum(first);
    895             for (final Vector3D v : more) {
    896                 s.add(v);
    897             }
    898             return s;
    899         }
    900     }
    901 }