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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.euclidean.twod;
23  
24  import java.text.NumberFormat;
25  
26  import org.hipparchus.exception.LocalizedCoreFormats;
27  import org.hipparchus.exception.MathIllegalArgumentException;
28  import org.hipparchus.exception.MathRuntimeException;
29  import org.hipparchus.geometry.Space;
30  import org.hipparchus.geometry.Vector;
31  import org.hipparchus.util.FastMath;
32  import org.hipparchus.util.MathArrays;
33  import org.hipparchus.util.MathUtils;
34  
35  /** This class represents a 2D vector.
36   * <p>Instances of this class are guaranteed to be immutable.</p>
37   */
38  public class Vector2D implements Vector<Euclidean2D, Vector2D> {
39  
40      /** Origin (coordinates: 0, 0). */
41      public static final Vector2D ZERO   = new Vector2D(0, 0);
42  
43      /** First canonical vector (coordinates: 1, 0).
44       * @since 1.6
45       */
46      public static final Vector2D PLUS_I = new Vector2D(1, 0);
47  
48      /** Opposite of the first canonical vector (coordinates: -1, 0).
49       * @since 1.6
50       */
51      public static final Vector2D MINUS_I = new Vector2D(-1, 0);
52  
53      /** Second canonical vector (coordinates: 0, 1).
54       * @since 1.6
55       */
56      public static final Vector2D PLUS_J = new Vector2D(0, 1);
57  
58      /** Opposite of the second canonical vector (coordinates: 0, -1).
59       * @since 1.6
60       */
61      public static final Vector2D MINUS_J = new Vector2D(0, -1);
62  
63      // CHECKSTYLE: stop ConstantName
64      /** A vector with all coordinates set to NaN. */
65      public static final Vector2D NaN = new Vector2D(Double.NaN, Double.NaN);
66      // CHECKSTYLE: resume ConstantName
67  
68      /** A vector with all coordinates set to positive infinity. */
69      public static final Vector2D POSITIVE_INFINITY =
70          new Vector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
71  
72      /** A vector with all coordinates set to negative infinity. */
73      public static final Vector2D NEGATIVE_INFINITY =
74          new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
75  
76      /** Serializable UID. */
77      private static final long serialVersionUID = 266938651998679754L;
78  
79      /** Abscissa. */
80      private final double x;
81  
82      /** Ordinate. */
83      private final double y;
84  
85      /** Simple constructor.
86       * Build a vector from its coordinates
87       * @param x abscissa
88       * @param y ordinate
89       * @see #getX()
90       * @see #getY()
91       */
92      public Vector2D(double x, double y) {
93          this.x = x;
94          this.y = y;
95      }
96  
97      /** Simple constructor.
98       * Build a vector from its coordinates
99       * @param v coordinates array
100      * @exception MathIllegalArgumentException if array does not have 2 elements
101      * @see #toArray()
102      */
103     public Vector2D(double[] v) throws MathIllegalArgumentException {
104         if (v.length != 2) {
105             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
106                                                    v.length, 2);
107         }
108         this.x = v[0];
109         this.y = v[1];
110     }
111 
112     /** Multiplicative constructor
113      * Build a vector from another one and a scale factor.
114      * The vector built will be a * u
115      * @param a scale factor
116      * @param u base (unscaled) vector
117      */
118     public Vector2D(double a, Vector2D u) {
119         this.x = a * u.x;
120         this.y = a * u.y;
121     }
122 
123     /** Linear constructor
124      * Build a vector from two other ones and corresponding scale factors.
125      * The vector built will be a1 * u1 + a2 * u2
126      * @param a1 first scale factor
127      * @param u1 first base (unscaled) vector
128      * @param a2 second scale factor
129      * @param u2 second base (unscaled) vector
130      */
131     public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2) {
132         this.x = a1 * u1.x + a2 * u2.x;
133         this.y = a1 * u1.y + a2 * u2.y;
134     }
135 
136     /** Linear constructor
137      * Build a vector from three other ones and corresponding scale factors.
138      * The vector built will be a1 * u1 + a2 * u2 + a3 * u3
139      * @param a1 first scale factor
140      * @param u1 first base (unscaled) vector
141      * @param a2 second scale factor
142      * @param u2 second base (unscaled) vector
143      * @param a3 third scale factor
144      * @param u3 third base (unscaled) vector
145      */
146     public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2,
147                    double a3, Vector2D u3) {
148         this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x;
149         this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y;
150     }
151 
152     /** Linear constructor
153      * Build a vector from four other ones and corresponding scale factors.
154      * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4
155      * @param a1 first scale factor
156      * @param u1 first base (unscaled) vector
157      * @param a2 second scale factor
158      * @param u2 second base (unscaled) vector
159      * @param a3 third scale factor
160      * @param u3 third base (unscaled) vector
161      * @param a4 fourth scale factor
162      * @param u4 fourth base (unscaled) vector
163      */
164     public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2,
165                    double a3, Vector2D u3, double a4, Vector2D u4) {
166         this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x + a4 * u4.x;
167         this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y + a4 * u4.y;
168     }
169 
170     /** Get the abscissa of the vector.
171      * @return abscissa of the vector
172      * @see #Vector2D(double, double)
173      */
174     public double getX() {
175         return x;
176     }
177 
178     /** Get the ordinate of the vector.
179      * @return ordinate of the vector
180      * @see #Vector2D(double, double)
181      */
182     public double getY() {
183         return y;
184     }
185 
186     /** Get the vector coordinates as a dimension 2 array.
187      * @return vector coordinates
188      * @see #Vector2D(double[])
189      */
190     public double[] toArray() {
191         return new double[] { x, y };
192     }
193 
194     /** {@inheritDoc} */
195     @Override
196     public Space getSpace() {
197         return Euclidean2D.getInstance();
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public Vector2D getZero() {
203         return ZERO;
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public double getNorm1() {
209         return FastMath.abs(x) + FastMath.abs(y);
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     public double getNorm() {
215         return FastMath.sqrt (x * x + y * y);
216     }
217 
218     /** {@inheritDoc} */
219     @Override
220     public double getNormSq() {
221         return x * x + y * y;
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     public double getNormInf() {
227         return FastMath.max(FastMath.abs(x), FastMath.abs(y));
228     }
229 
230     /** {@inheritDoc} */
231     @Override
232     public Vector2D add(Vector2D v) {
233         return new Vector2D(x + v.getX(), y + v.getY());
234     }
235 
236     /** {@inheritDoc} */
237     @Override
238     public Vector2D add(double factor, Vector2D v) {
239         return new Vector2D(x + factor * v.getX(), y + factor * v.getY());
240     }
241 
242     /** {@inheritDoc} */
243     @Override
244     public Vector2D subtract(Vector2D p) {
245         return new Vector2D(x - p.x, y - p.y);
246     }
247 
248     /** {@inheritDoc} */
249     @Override
250     public Vector2D subtract(double factor, Vector2D v) {
251         return new Vector2D(x - factor * v.getX(), y - factor * v.getY());
252     }
253 
254     /** Compute the angular separation between two vectors.
255      * <p>This method computes the angular separation between two
256      * vectors using the dot product for well separated vectors and the
257      * cross product for almost aligned vectors. This allows to have a
258      * good accuracy in all cases, even for vectors very close to each
259      * other.</p>
260      * @param v1 first vector
261      * @param v2 second vector
262      * @return angular separation between v1 and v2
263      * @exception MathRuntimeException if either vector has a null norm
264      */
265     public static double angle(Vector2D v1, Vector2D v2) throws MathRuntimeException {
266 
267         double normProduct = v1.getNorm() * v2.getNorm();
268         if (normProduct == 0) {
269             throw new MathRuntimeException(LocalizedCoreFormats.ZERO_NORM);
270         }
271 
272         double dot = v1.dotProduct(v2);
273         double threshold = normProduct * 0.9999;
274         if (FastMath.abs(dot) > threshold) {
275             // the vectors are almost aligned, compute using the sine
276             final double n = FastMath.abs(MathArrays.linearCombination(v1.x, v2.y, -v1.y, v2.x));
277             if (dot >= 0) {
278                 return FastMath.asin(n / normProduct);
279             }
280             return FastMath.PI - FastMath.asin(n / normProduct);
281         }
282 
283         // the vectors are sufficiently separated to use the cosine
284         return FastMath.acos(dot / normProduct);
285 
286     }
287 
288     /** {@inheritDoc} */
289     @Override
290     public Vector2D negate() {
291         return new Vector2D(-x, -y);
292     }
293 
294     /** {@inheritDoc} */
295     @Override
296     public Vector2D scalarMultiply(double a) {
297         return new Vector2D(a * x, a * y);
298     }
299 
300     /** {@inheritDoc} */
301     @Override
302     public boolean isNaN() {
303         return Double.isNaN(x) || Double.isNaN(y);
304     }
305 
306     /** {@inheritDoc} */
307     @Override
308     public boolean isInfinite() {
309         return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y));
310     }
311 
312     /** {@inheritDoc} */
313     @Override
314     public double distance1(Vector2D p) {
315         final double dx = FastMath.abs(p.x - x);
316         final double dy = FastMath.abs(p.y - y);
317         return dx + dy;
318     }
319 
320     /** {@inheritDoc} */
321     @Override
322     public double distance(Vector2D p) {
323         final double dx = p.x - x;
324         final double dy = p.y - y;
325         return FastMath.sqrt(dx * dx + dy * dy);
326     }
327 
328     /** {@inheritDoc} */
329     @Override
330     public double distanceInf(Vector2D p) {
331         final double dx = FastMath.abs(p.x - x);
332         final double dy = FastMath.abs(p.y - y);
333         return FastMath.max(dx, dy);
334     }
335 
336     /** {@inheritDoc} */
337     @Override
338     public double distanceSq(Vector2D p) {
339         final double dx = p.x - x;
340         final double dy = p.y - y;
341         return dx * dx + dy * dy;
342     }
343 
344     /** {@inheritDoc} */
345     @Override
346     public double dotProduct(final Vector2D v) {
347         return MathArrays.linearCombination(x, v.x, y, v.y);
348     }
349 
350     /**
351      * Compute the cross-product of the instance and the given points.
352      * <p>
353      * The cross product can be used to determine the location of a point
354      * with regard to the line formed by (p1, p2) and is calculated as:
355      * \[
356      *    P = (x_2 - x_1)(y_3 - y_1) - (y_2 - y_1)(x_3 - x_1)
357      * \]
358      * with \(p3 = (x_3, y_3)\) being this instance.
359      * <p>
360      * If the result is 0, the points are collinear, i.e. lie on a single straight line L;
361      * if it is positive, this point lies to the left, otherwise to the right of the line
362      * formed by (p1, p2).
363      *
364      * @param p1 first point of the line
365      * @param p2 second point of the line
366      * @return the cross-product
367      *
368      * @see <a href="http://en.wikipedia.org/wiki/Cross_product">Cross product (Wikipedia)</a>
369      */
370     public double crossProduct(final Vector2D p1, final Vector2D p2) {
371         final double x1 = p2.getX() - p1.getX();
372         final double y1 = getY() - p1.getY();
373         final double x2 = getX() - p1.getX();
374         final double y2 = p2.getY() - p1.getY();
375         return MathArrays.linearCombination(x1, y1, -x2, y2);
376     }
377 
378     /** Compute the distance between two vectors according to the L<sub>1</sub> norm.
379      * <p>Calling this method is equivalent to calling:
380      * <code>p1.subtract(p2).getNorm1()</code> except that no intermediate
381      * vector is built</p>
382      * @param p1 first vector
383      * @param p2 second vector
384      * @return the distance between p1 and p2 according to the L<sub>1</sub> norm
385      * @since 1.6
386      */
387     public static double distance1(Vector2D p1, Vector2D p2) {
388         return p1.distance1(p2);
389     }
390 
391     /** Compute the distance between two vectors according to the L<sub>2</sub> norm.
392      * <p>Calling this method is equivalent to calling:
393      * <code>p1.subtract(p2).getNorm()</code> except that no intermediate
394      * vector is built</p>
395      * @param p1 first vector
396      * @param p2 second vector
397      * @return the distance between p1 and p2 according to the L<sub>2</sub> norm
398      */
399     public static double distance(Vector2D p1, Vector2D p2) {
400         return p1.distance(p2);
401     }
402 
403     /** Compute the distance between two vectors according to the L<sub>&infin;</sub> norm.
404      * <p>Calling this method is equivalent to calling:
405      * <code>p1.subtract(p2).getNormInf()</code> except that no intermediate
406      * vector is built</p>
407      * @param p1 first vector
408      * @param p2 second vector
409      * @return the distance between p1 and p2 according to the L<sub>&infin;</sub> norm
410      */
411     public static double distanceInf(Vector2D p1, Vector2D p2) {
412         return p1.distanceInf(p2);
413     }
414 
415     /** Compute the square of the distance between two vectors.
416      * <p>Calling this method is equivalent to calling:
417      * <code>p1.subtract(p2).getNormSq()</code> except that no intermediate
418      * vector is built</p>
419      * @param p1 first vector
420      * @param p2 second vector
421      * @return the square of the distance between p1 and p2
422      */
423     public static double distanceSq(Vector2D p1, Vector2D p2) {
424         return p1.distanceSq(p2);
425     }
426 
427     /** {@inheritDoc} */
428     @Override
429     public Vector2D moveTowards(final Vector2D other, final double ratio) {
430         return new Vector2D(x + ratio * (other.x - x),
431                             y + ratio * (other.y - y));
432     }
433 
434      /** Compute the orientation of a triplet of points.
435      * @param p first vector of the triplet
436      * @param q second vector of the triplet
437      * @param r third vector of the triplet
438      * @return a positive value if (p, q, r) defines a counterclockwise oriented
439      * triangle, a negative value if (p, q, r) defines a clockwise oriented
440      * triangle, and 0 if (p, q, r) are collinear or some points are equal
441      * @since 1.2
442      */
443     public static double orientation(final Vector2D p, final Vector2D q, final Vector2D r) {
444         return MathArrays.linearCombination(new double[] {
445             p.getX(), -p.getX(), q.getX(), -q.getX(), r.getX(), -r.getX()
446         }, new double[] {
447             q.getY(),  r.getY(), r.getY(),  p.getY(), p.getY(),  q.getY()
448         });
449     }
450 
451     /**
452      * Test for the equality of two 2D vectors.
453      * <p>
454      * If all coordinates of two 2D vectors are exactly the same, and none are
455      * {@code Double.NaN}, the two 2D vectors are considered to be equal.
456      * </p>
457      * <p>
458      * {@code NaN} coordinates are considered to affect globally the vector
459      * and be equals to each other - i.e, if either (or all) coordinates of the
460      * 2D vector are equal to {@code Double.NaN}, the 2D vector is equal to
461      * {@link #NaN}.
462      * </p>
463      *
464      * @param other Object to test for equality to this
465      * @return true if two 2D vector objects are equal, false if
466      *         object is null, not an instance of Vector2D, or
467      *         not equal to this Vector2D instance
468      */
469     @Override
470     public boolean equals(Object other) {
471 
472         if (this == other) {
473             return true;
474         }
475 
476         if (other instanceof Vector2D) {
477             final Vector2D rhs = (Vector2D)other;
478             return x == rhs.x && y == rhs.y || isNaN() && rhs.isNaN();
479         }
480 
481         return false;
482 
483     }
484 
485     /**
486      * Test for the equality of two 2D vectors.
487      * <p>
488      * If all coordinates of two 2D vectors are exactly the same, and none are
489      * {@code NaN}, the two 2D vectors are considered to be equal.
490      * </p>
491      * <p>
492      * In compliance with IEEE754 handling, if any coordinates of any of the
493      * two vectors are {@code NaN}, then the vectors are considered different.
494      * This implies that {@link #NaN Vector2D.NaN}.equals({@link #NaN Vector2D.NaN})
495      * returns {@code false} despite the instance is checked against itself.
496      * </p>
497      *
498      * @param other Object to test for equality to this
499      * @return true if two 2D vector objects are equal, false if
500      *         object is null, not an instance of Vector2D, or
501      *         not equal to this Vector2D instance
502      * @since 2.1
503      */
504     public boolean equalsIeee754(Object other) {
505 
506         if (this == other && !isNaN()) {
507             return true;
508         }
509 
510         if (other instanceof Vector2D) {
511             final Vector2D rhs = (Vector2D) other;
512             return x == rhs.x && y == rhs.y;
513         }
514         return false;
515     }
516 
517     /**
518      * Get a hashCode for the 2D vector.
519      * <p>
520      * All NaN values have the same hash code.</p>
521      *
522      * @return a hash code value for this object
523      */
524     @Override
525     public int hashCode() {
526         if (isNaN()) {
527             return 542;
528         }
529         return 122 * (76 * MathUtils.hash(x) +  MathUtils.hash(y));
530     }
531 
532     /** Get a string representation of this vector.
533      * @return a string representation of this vector
534      */
535     @Override
536     public String toString() {
537         return Vector2DFormat.getVector2DFormat().format(this);
538     }
539 
540     /** {@inheritDoc} */
541     @Override
542     public String toString(final NumberFormat format) {
543         return new Vector2DFormat(format).format(this);
544     }
545 
546 }