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 }