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.core.precision.DoublePrecisionContext;
    27  import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector;
    28  import org.apache.commons.geometry.euclidean.internal.Vectors;
    29  import org.apache.commons.numbers.arrays.LinearCombination;
    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      // CHECKSTYLE: stop ConstantName
    40      /** A vector with all coordinates set to NaN. */
    41      public static final Vector2D NaN = new Vector2D(Double.NaN, Double.NaN);
    42      // CHECKSTYLE: resume ConstantName
    43
    44      /** A vector with all coordinates set to positive infinity. */
    45      public static final Vector2D POSITIVE_INFINITY =
    46          new Vector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
    47
    48      /** A vector with all coordinates set to negative infinity. */
    49      public static final Vector2D NEGATIVE_INFINITY =
    50          new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
    51
    52      /** Comparator that sorts vectors in component-wise ascending order.
    53       * Vectors are only considered equal if their coordinates match exactly.
    54       * Null arguments are evaluated as being greater than non-null arguments.
    55       */
    56      public static final Comparator<Vector2D> COORDINATE_ASCENDING_ORDER = (a, b) -> {
    57          int cmp = 0;
    58
    59          if (a != null && b != null) {
    60              cmp = Double.compare(a.getX(), b.getX());
    61              if (cmp == 0) {
    62                  cmp = Double.compare(a.getY(), b.getY());
    63              }
    64          } else if (a != null) {
    65              cmp = -1;
    66          } else if (b != null) {
    67              cmp = 1;
    68          }
    69
    70          return cmp;
    71      };
    72
    73      /** Abscissa (first coordinate). */
    74      private final double x;
    75
    76      /** Ordinate (second coordinate). */
    77      private final double y;
    78
    79      /** Simple constructor.
    80       * @param x abscissa (first coordinate)
    81       * @param y ordinate (second coordinate)
    82       */
    83      private Vector2D(final double x, final double y) {
    84          this.x = x;
    85          this.y = y;
    86      }
    87
    88      /** Returns the abscissa (first coordinate value) of the instance.
    89       * @return the abscissa
    90       */
    91      public double getX() {
    92          return x;
    93      }
    94
    95      /** Returns the ordinate (second coordinate value) of the instance.
    96       * @return the ordinate
    97       */
    98      public double getY() {
    99          return y;
    100     }
    101
    102     /** Get the coordinates for this instance as a dimension 2 array.
    103      * @return coordinates for this instance
    104      */
    105     public double[] toArray() {
    106         return new double[]{x, y};
    107     }
    108
    109     /** {@inheritDoc} */
    110     @Override
    111     public int getDimension() {
    112         return 2;
    113     }
    114
    115     /** {@inheritDoc} */
    116     @Override
    117     public boolean isNaN() {
    118         return Double.isNaN(x) || Double.isNaN(y);
    119     }
    120
    121     /** {@inheritDoc} */
    122     @Override
    123     public boolean isInfinite() {
    124         return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y));
    125     }
    126
    127     /** {@inheritDoc} */
    128     @Override
    129     public boolean isFinite() {
    130         return Double.isFinite(x) && Double.isFinite(y);
    131     }
    132
    133     /** {@inheritDoc} */
    134     @Override
    135     public Vector2D vectorTo(final Vector2D v) {
    136         return v.subtract(this);
    137     }
    138
    139     /** {@inheritDoc} */
    140     @Override
    141     public Unit directionTo(final Vector2D v) {
    142         return vectorTo(v).normalize();
    143     }
    144
    145     /** {@inheritDoc} */
    146     @Override
    147     public Vector2D lerp(final Vector2D p, final double t) {
    148         return linearCombination(1.0 - t, this, t, p);
    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 Vector2D multiply(final double a) {
    219         return new Vector2D(a * x, a * y);
    220     }
    221
    222     /** {@inheritDoc} */
    223     @Override
    224     public double distance(final Vector2D v) {
    225         return Vectors.norm(x - v.x, y - v.y);
    226     }
    227
    228     /** {@inheritDoc} */
    229     @Override
    230     public double distanceSq(final Vector2D v) {
    231         return Vectors.normSq(x - v.x, y - v.y);
    232     }
    233
    234     /** {@inheritDoc} */
    235     @Override
    236     public double dot(final Vector2D v) {
    237         return LinearCombination.value(x, v.x, y, v.y);
    238     }
    239
    240     /** {@inheritDoc}
    241      * <p>This method computes the angular separation between the two
    242      * vectors using the dot product for well separated vectors and the
    243      * cross product for almost aligned vectors. This allows to have a
    244      * good accuracy in all cases, even for vectors very close to each
    245      * other.</p>
    246      */
    247     @Override
    248     public double angle(final Vector2D v) {
    249         final double normProduct = getCheckedNorm() * v.getCheckedNorm();
    250
    251         final double dot = dot(v);
    252         final double threshold = normProduct * 0.9999;
    253         if ((dot < -threshold) || (dot > threshold)) {
    254             // the vectors are almost aligned, compute using the sine
    255             final double n = Math.abs(LinearCombination.value(x, v.y, -y, v.x));
    256             if (dot >= 0) {
    257                 return Math.asin(n / normProduct);
    258             }
    259             return Math.PI - Math.asin(n / normProduct);
    260         }
    261
    262         // the vectors are sufficiently separated to use the cosine
    263         return Math.acos(dot / normProduct);
    264     }
    265
    266     /** {@inheritDoc} */
    267     @Override
    268     public Vector2D project(final Vector2D base) {
    269         return getComponent(base, false, Vector2D::new);
    270     }
    271
    272     /** {@inheritDoc} */
    273     @Override
    274     public Vector2D reject(final Vector2D base) {
    275         return getComponent(base, true, Vector2D::new);
    276     }
    277
    278     /** {@inheritDoc}
    279      * The returned vector is computed by rotating the current instance {@code pi/2} radians
    280      * counterclockwise around the origin and normalizing. For example, if this method is
    281      * called on a vector pointing along the positive x-axis, then a unit vector representing
    282      * the positive y-axis is returned.
    283      * @return a unit vector orthogonal to the current instance
    284      * @throws IllegalArgumentException if the norm of the current instance is zero, NaN, or infinite
    285      */
    286     @Override
    287     public Vector2D.Unit orthogonal() {
    288         return Unit.from(-y, x);
    289     }
    290
    291     /** {@inheritDoc} */
    292     @Override
    293     public Vector2D.Unit orthogonal(final Vector2D dir) {
    294         return dir.getComponent(this, true, Vector2D.Unit::from);
    295     }
    296
    297     /** Compute the signed area of the parallelogram with sides formed by this instance
    298      * and the given vector.
    299      *
    300      * <p>The parallelogram in question can be visualized by taking the current instance as the
    301      * first side and placing {@code v} at the end of it to create the second. The other sides
    302      * are formed by lines parallel to these two vectors. If {@code v} points to the <em>left</em> of
    303      * the current instance (ie, the parallelogram is wound counter-clockwise), then the
    304      * returned area is positive. If {@code v} points to the <em>right</em> of the current instance,
    305      * (ie, the parallelogram is wound clockwise), then the returned area is negative. If
    306      * the vectors are collinear (ie, they lie on the same line), then 0 is returned. The area of
    307      * the triangle formed by the two vectors is exactly half of the returned value.
    308      * @param v vector representing the second side of the constructed parallelogram
    309      * @return the signed area of the parallelogram formed by this instance and the given vector
    310      */
    311     public double signedArea(final Vector2D v) {
    312         return LinearCombination.value(
    313                 x, v.y,
    314                 -y, v.x);
    315     }
    316
    317     /** Convenience method to apply a function to this vector. This
    318      * can be used to transform the vector inline with other methods.
    319      * @param fn the function to apply
    320      * @return the transformed vector
    321      */
    322     public Vector2D transform(final UnaryOperator<Vector2D> fn) {
    323         return fn.apply(this);
    324     }
    325
    326     /** {@inheritDoc} */
    327     @Override
    328     public boolean eq(final Vector2D vec, final DoublePrecisionContext precision) {
    329         return precision.eq(x, vec.x) &&
    330                 precision.eq(y, vec.y);
    331     }
    332
    333     /**
    334      * Get a hashCode for the 2D coordinates.
    335      * <p>
    336      * All NaN values have the same hash code.</p>
    337      *
    338      * @return a hash code value for this object
    339      */
    340     @Override
    341     public int hashCode() {
    342         if (isNaN()) {
    343             return 542;
    344         }
    345         return 122 * (76 * Double.hashCode(x) + Double.hashCode(y));
    346     }
    347
    348     /**
    349      * Test for the equality of two vector instances.
    350      * <p>
    351      * If all coordinates of two vectors are exactly the same, and none are
    352      * <code>Double.NaN</code>, the two instances are considered to be equal.
    353      * </p>
    354      * <p>
    355      * <code>NaN</code> coordinates are considered to globally affect the vector
    356      * and be equal to each other - i.e, if either (or all) coordinates of the
    357      * vector are equal to <code>Double.NaN</code>, the vector is equal to
    358      * {@link #NaN}.
    359      * </p>
    360      *
    361      * @param other Object to test for equality to this
    362      * @return true if two Vector2D objects are equal, false if
    363      *         object is null, not an instance of Vector2D, or
    364      *         not equal to this Vector2D instance
    365      *
    366      */
    367     @Override
    368     public boolean equals(final Object other) {
    369         if (this == other) {
    370             return true;
    371         }
    372         if (other instanceof Vector2D) {
    373             final Vector2D rhs = (Vector2D) other;
    374             if (rhs.isNaN()) {
    375                 return this.isNaN();
    376             }
    377
    378             return Double.compare(x, rhs.x) == 0 &&
    379                     Double.compare(y, rhs.y) == 0;
    380         }
    381         return false;
    382     }
    383
    384     /** {@inheritDoc} */
    385     @Override
    386     public String toString() {
    387         return SimpleTupleFormat.getDefault().format(x, y);
    388     }
    389
    390     /** Returns a component of the current instance relative to the given base
    391      * vector. If {@code reject} is true, the vector rejection is returned; otherwise,
    392      * the projection is returned.
    393      * @param base The base vector
    394      * @param reject If true, the rejection of this instance from {@code base} is
    395      *      returned. If false, the projection of this instance onto {@code base}
    396      *      is returned.
    397      * @param factory factory function used to build the final vector
    398      * @param <T> Vector implementation type
    399      * @return The projection or rejection of this instance relative to {@code base},
    400      *      depending on the value of {@code reject}.
    401      * @throws IllegalArgumentException if {@code base} has a zero, NaN, or infinite norm
    402      */
    403     private <T extends Vector2D> T getComponent(final Vector2D base, final boolean reject,
    404             final DoubleFunction2N<T> factory) {
    405         final double aDotB = dot(base);
    406
    407         // We need to check the norm value here to ensure that it's legal. However, we don't
    408         // want to incur the cost or floating point error of getting the actual norm and then
    409         // multiplying it again to get the square norm. So, we'll just check the squared norm
    410         // directly. This will produce the same error result as checking the actual norm since
    411         // Math.sqrt(0.0) == 0.0, Math.sqrt(Double.NaN) == Double.NaN and
    412         // Math.sqrt(Double.POSITIVE_INFINITY) == Double.POSITIVE_INFINITY.
    413         final double baseMagSq = Vectors.checkedNorm(base.normSq());
    414
    415         final double scale = aDotB / baseMagSq;
    416
    417         final double projX = scale * base.x;
    418         final double projY = scale * base.y;
    419
    420         if (reject) {
    421             return factory.apply(x - projX, y - projY);
    422         }
    423
    424         return factory.apply(projX, projY);
    425     }
    426
    427     /** Returns a vector with the given coordinate values.
    428      * @param x abscissa (first coordinate value)
    429      * @param y abscissa (second coordinate value)
    430      * @return vector instance
    431      */
    432     public static Vector2D of(final double x, final double y) {
    433         return new Vector2D(x, y);
    434     }
    435
    436     /** Creates a vector from the coordinates in the given 2-element array.
    437      * @param v coordinates array
    438      * @return new vector
    439      * @exception IllegalArgumentException if the array does not have 2 elements
    440      */
    441     public static Vector2D of(final double[] v) {
    442         if (v.length != 2) {
    443             throw new IllegalArgumentException("Dimension mismatch: " + v.length + " != 2");
    444         }
    445         return new Vector2D(v[0], v[1]);
    446     }
    447
    448     /** Parses the given string and returns a new vector instance. The expected string
    449      * format is the same as that returned by {@link #toString()}.
    450      * @param str the string to parse
    451      * @return vector instance represented by the string
    452      * @throws IllegalArgumentException if the given string has an invalid format
    453      */
    454     public static Vector2D parse(final String str) {
    455         return SimpleTupleFormat.getDefault().parse(str, Vector2D::new);
    456     }
    457
    458     /** Return a vector containing the maximum component values from all input vectors.
    459      * @param first first vector
    460      * @param more additional vectors
    461      * @return a vector containing the maximum component values from all input vectors
    462      */
    463     public static Vector2D max(final Vector2D first, final Vector2D... more) {
    464         return computeMax(first, Arrays.asList(more).iterator());
    465     }
    466
    467     /** Return a vector containing the maximum component values from all input vectors.
    468      * @param vecs input vectors
    469      * @return a vector containing the maximum component values from all input vectors
    470      * @throws IllegalArgumentException if the argument does not contain any vectors
    471      */
    472     public static Vector2D max(final Iterable<Vector2D> vecs) {
    473         final Iterator<Vector2D> it = vecs.iterator();
    474         if (!it.hasNext()) {
    475             throw new IllegalArgumentException("Cannot compute vector max: no vectors given");
    476         }
    477
    478         return computeMax(it.next(), it);
    479     }
    480
    481     /** Internal method for computing a max vector.
    482      * @param first first vector
    483      * @param more iterator with additional vectors
    484      * @return vector containing the maximum component values of all input vectors
    485      */
    486     private static Vector2D computeMax(final Vector2D first, final Iterator<Vector2D> more) {
    487         double x = first.getX();
    488         double y = first.getY();
    489
    490         Vector2D vec;
    491         while (more.hasNext()) {
    492             vec = more.next();
    493
    494             x = Math.max(x, vec.getX());
    495             y = Math.max(y, vec.getY());
    496         }
    497
    498         return Vector2D.of(x, y);
    499     }
    500
    501     /** Return a vector containing the minimum component values from all input vectors.
    502      * @param first first vector
    503      * @param more more vectors
    504      * @return a vector containing the minimum component values from all input vectors
    505      */
    506     public static Vector2D min(final Vector2D first, final Vector2D... more) {
    507         return computeMin(first, Arrays.asList(more).iterator());
    508     }
    509
    510     /** Return a vector containing the minimum component values from all input vectors.
    511      * @param vecs input vectors
    512      * @return a vector containing the minimum component values from all input vectors
    513      * @throws IllegalArgumentException if the argument does not contain any vectors
    514      */
    515     public static Vector2D min(final Iterable<Vector2D> vecs) {
    516         final Iterator<Vector2D> it = vecs.iterator();
    517         if (!it.hasNext()) {
    518             throw new IllegalArgumentException("Cannot compute vector min: no vectors given");
    519         }
    520
    521         return computeMin(it.next(), it);
    522     }
    523
    524     /** Internal method for computing a min vector.
    525      * @param first first vector
    526      * @param more iterator with additional vectors
    527      * @return vector containing the minimum component values of all input vectors
    528      */
    529     private static Vector2D computeMin(final Vector2D first, final Iterator<Vector2D> more) {
    530         double x = first.getX();
    531         double y = first.getY();
    532
    533         Vector2D vec;
    534         while (more.hasNext()) {
    535             vec = more.next();
    536
    537             x = Math.min(x, vec.getX());
    538             y = Math.min(y, vec.getY());
    539         }
    540
    541         return Vector2D.of(x, y);
    542     }
    543
    544     /** Compute the centroid of the given points. The centroid is the arithmetic mean position of a set
    545      * of points.
    546      * @param first first point
    547      * @param more additional points
    548      * @return the centroid of the given points
    549      */
    550     public static Vector2D centroid(final Vector2D first, final Vector2D... more) {
    551         return computeCentroid(first, Arrays.asList(more).iterator());
    552     }
    553
    554     /** Compute the centroid of the given points. The centroid is the arithmetic mean position of a set
    555      * of points.
    556      * @param pts the points to compute the centroid of
    557      * @return the centroid of the given points
    558      * @throws IllegalArgumentException if the argument contains no points
    559      */
    560     public static Vector2D centroid(final Iterable<Vector2D> pts) {
    561         final Iterator<Vector2D> it = pts.iterator();
    562         if (!it.hasNext()) {
    563             throw new IllegalArgumentException("Cannot compute centroid: no points given");
    564         }
    565
    566         return computeCentroid(it.next(), it);
    567     }
    568
    569     /** Internal method for computing the centroid of a set of points.
    570      * @param first first point
    571      * @param more iterator with additional points
    572      * @return the centroid of the point set
    573      */
    574     private static Vector2D computeCentroid(final Vector2D first, final Iterator<Vector2D> more) {
    575         double x = first.getX();
    576         double y = first.getY();
    577
    578         int count = 1;
    579
    580         Vector2D pt;
    581         while (more.hasNext()) {
    582             pt = more.next();
    583
    584             x += pt.getX();
    585             y += pt.getY();
    586
    587             ++count;
    588         }
    589
    590         final double invCount = 1.0 / count;
    591
    592         return new Vector2D(invCount * x, invCount * y);
    593     }
    594
    595     /** Returns a vector consisting of the linear combination of the inputs.
    596      * <p>
    597      * A linear combination is the sum of all of the inputs multiplied by their
    598      * corresponding scale factors.
    599      * </p>
    600      *
    601      * @param a scale factor for first vector
    602      * @param c first vector
    603      * @return vector calculated by {@code a * c}
    604      */
    605     public static Vector2D linearCombination(final double a, final Vector2D c) {
    606         return new Vector2D(a * c.x, a * c.y);
    607     }
    608
    609     /** Returns a vector consisting of the linear combination of the inputs.
    610      * <p>
    611      * A linear combination is the sum of all of the inputs multiplied by their
    612      * corresponding scale factors.
    613      * </p>
    614      *
    615      * @param a1 scale factor for first vector
    616      * @param v1 first vector
    617      * @param a2 scale factor for second vector
    618      * @param v2 second vector
    619      * @return vector calculated by {@code (a1 * v1) + (a2 * v2)}
    620      */
    621     public static Vector2D linearCombination(final double a1, final Vector2D v1,
    622             final double a2, final Vector2D v2) {
    623         return new Vector2D(
    624                 LinearCombination.value(a1, v1.x, a2, v2.x),
    625                 LinearCombination.value(a1, v1.y, a2, v2.y));
    626     }
    627
    628     /** Returns a vector consisting of the linear combination of the inputs.
    629      * <p>
    630      * A linear combination is the sum of all of the inputs multiplied by their
    631      * corresponding scale factors.
    632      * </p>
    633      *
    634      * @param a1 scale factor for first vector
    635      * @param v1 first vector
    636      * @param a2 scale factor for second vector
    637      * @param v2 second vector
    638      * @param a3 scale factor for third vector
    639      * @param v3 third vector
    640      * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
    641      */
    642     public static Vector2D linearCombination(final double a1, final Vector2D v1,
    643             final double a2, final Vector2D v2,
    644             final double a3, final Vector2D v3) {
    645         return new Vector2D(
    646                 LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x),
    647                 LinearCombination.value(a1, v1.y, a2, v2.y, a3, v3.y));
    648     }
    649
    650     /** Returns a vector consisting of the linear combination of the inputs.
    651      * <p>
    652      * A linear combination is the sum of all of the inputs multiplied by their
    653      * corresponding scale factors.
    654      * </p>
    655      *
    656      * @param a1 scale factor for first vector
    657      * @param v1 first vector
    658      * @param a2 scale factor for second vector
    659      * @param v2 second vector
    660      * @param a3 scale factor for third vector
    661      * @param v3 third vector
    662      * @param a4 scale factor for fourth vector
    663      * @param v4 fourth vector
    664      * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
    665      */
    666     public static Vector2D linearCombination(final double a1, final Vector2D v1,
    667                                              final double a2, final Vector2D v2,
    668                                              final double a3, final Vector2D v3,
    669                                              final double a4, final Vector2D v4) {
    670         return new Vector2D(
    671                 LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x, a4, v4.x),
    672                 LinearCombination.value(a1, v1.y, a2, v2.y, a3, v3.y, a4, v4.y));
    673     }
    674
    675     /**
    676      * Represents unit vectors.
    677      * This allows optimizations for certain operations.
    678      */
    679     public static final class Unit extends Vector2D {
    680         /** Unit vector (coordinates: 1, 0). */
    681         public static final Unit PLUS_X  = new Unit(1d, 0d);
    682         /** Negation of unit vector (coordinates: -1, 0). */
    683         public static final Unit MINUS_X = new Unit(-1d, 0d);
    684         /** Unit vector (coordinates: 0, 1). */
    685         public static final Unit PLUS_Y  = new Unit(0d, 1d);
    686         /** Negation of unit vector (coordinates: 0, -1). */
    687         public static final Unit MINUS_Y = new Unit(0d, -1d);
    688
    689         /** Simple constructor. Callers are responsible for ensuring that the given
    690          * values represent a normalized vector.
    691          * @param x abscissa (first coordinate value)
    692          * @param y abscissa (second coordinate value)
    693          */
    694         private Unit(final double x, final double y) {
    695             super(x, y);
    696         }
    697
    698         /**
    699          * Creates a normalized vector.
    700          *
    701          * @param x Vector coordinate.
    702          * @param y Vector coordinate.
    703          * @return a vector whose norm is 1.
    704          * @throws IllegalArgumentException if the norm of the given value is zero, NaN, or infinite
    705          */
    706         public static Unit from(final double x, final double y) {
    707             final double invNorm = 1 / Vectors.checkedNorm(Vectors.norm(x, y));
    708             return new Unit(x * invNorm, y * invNorm);
    709         }
    710
    711         /**
    712          * Creates a normalized vector.
    713          *
    714          * @param v Vector.
    715          * @return a vector whose norm is 1.
    716          * @throws IllegalArgumentException if the norm of the given value is zero, NaN, or infinite
    717          */
    718         public static Unit from(final Vector2D v) {
    719             return v instanceof Unit ?
    720                 (Unit) v :
    721                 from(v.getX(), v.getY());
    722         }
    723
    724         /** {@inheritDoc} */
    725         @Override
    726         public double norm() {
    727             return 1;
    728         }
    729
    730         /** {@inheritDoc} */
    731         @Override
    732         public double normSq() {
    733             return 1;
    734         }
    735
    736         /** {@inheritDoc} */
    737         @Override
    738         public Unit normalize() {
    739             return this;
    740         }
    741
    742         /** {@inheritDoc} */
    743         @Override
    744         public Vector2D.Unit orthogonal() {
    745             return new Unit(-getY(), getX());
    746         }
    747
    748         /** {@inheritDoc} */
    749         @Override
    750         public Vector2D withNorm(final double mag) {
    751             return multiply(mag);
    752         }
    753
    754         /** {@inheritDoc} */
    755         @Override
    756         public Unit negate() {
    757             return new Unit(-getX(), -getY());
    758         }
    759     }
    760 }