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