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