1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.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
32
33
34 public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
35
36
37 public static final Vector3D ZERO = new Vector3D(0, 0, 0);
38
39
40 public static final Vector3D NaN = new Vector3D(Double.NaN, Double.NaN, Double.NaN);
41
42
43 public static final Vector3D POSITIVE_INFINITY =
44 new Vector3D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
45
46
47 public static final Vector3D NEGATIVE_INFINITY =
48 new Vector3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
49
50
51
52
53
54 public static final Comparator<Vector3D> 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 if (cmp == 0) {
62 cmp = Double.compare(a.getZ(), b.getZ());
63 }
64 }
65 } else if (a != null) {
66 cmp = -1;
67 } else if (b != null) {
68 cmp = 1;
69 }
70
71 return cmp;
72 };
73
74
75 private final double x;
76
77
78 private final double y;
79
80
81 private final double z;
82
83
84
85
86
87
88
89 private Vector3D(final double x, final double y, final double z) {
90 this.x = x;
91 this.y = y;
92 this.z = z;
93 }
94
95
96
97
98 public double getX() {
99 return x;
100 }
101
102
103
104
105 public double getY() {
106 return y;
107 }
108
109
110
111
112 public double getZ() {
113 return z;
114 }
115
116
117
118
119 public double[] toArray() {
120 return new double[]{x, y, z};
121 }
122
123
124 @Override
125 public int getDimension() {
126 return 3;
127 }
128
129
130 @Override
131 public boolean isNaN() {
132 return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z);
133 }
134
135
136 @Override
137 public boolean isInfinite() {
138 return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z));
139 }
140
141
142 @Override
143 public boolean isFinite() {
144 return Double.isFinite(x) && Double.isFinite(y) && Double.isFinite(z);
145 }
146
147
148 @Override
149 public Vector3D getZero() {
150 return ZERO;
151 }
152
153
154 @Override
155 public Vector3D vectorTo(final Vector3D v) {
156 return v.subtract(this);
157 }
158
159
160 @Override
161 public Unit directionTo(final Vector3D v) {
162 return vectorTo(v).normalize();
163 }
164
165
166 @Override
167 public Vector3D lerp(final Vector3D p, final double t) {
168 return Sum.create()
169 .addScaled(1.0 - t, this)
170 .addScaled(t, p).get();
171 }
172
173
174 @Override
175 public double norm() {
176 return Vectors.norm(x, y, z);
177 }
178
179
180 @Override
181 public double normSq() {
182 return Vectors.normSq(x, y, z);
183 }
184
185
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
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
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
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
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
238 @Override
239 public Vector3D negate() {
240 return new Vector3D(-x, -y, -z);
241 }
242
243
244 @Override
245 public Unit normalize() {
246 return Unit.from(x, y, z);
247 }
248
249
250 @Override
251 public Unit normalizeOrNull() {
252 return Unit.tryCreateNormalized(x, y, z, false);
253 }
254
255
256 @Override
257 public Vector3D multiply(final double a) {
258 return new Vector3D(a * x, a * y, a * z);
259 }
260
261
262 @Override
263 public double distance(final Vector3D v) {
264 return Vectors.norm(
265 x - v.x,
266 y - v.y,
267 z - v.z
268 );
269 }
270
271
272 @Override
273 public double distanceSq(final Vector3D v) {
274 return Vectors.normSq(
275 x - v.x,
276 y - v.y,
277 z - v.z
278 );
279 }
280
281
282
283
284
285
286
287
288
289 @Override
290 public double dot(final Vector3D v) {
291 return Vectors.linearCombination(
292 x, v.x,
293 y, v.y,
294 z, v.z);
295 }
296
297
298
299
300
301
302
303
304 @Override
305 public double angle(final Vector3D v) {
306 final double normProduct = getCheckedNorm() * v.getCheckedNorm();
307
308 final double dot = dot(v);
309 final double threshold = normProduct * 0.99;
310 if ((dot < -threshold) || (dot > threshold)) {
311
312 final Vector3D cross = cross(v);
313 if (dot >= 0) {
314 return Math.asin(cross.norm() / normProduct);
315 }
316 return Math.PI - Math.asin(cross.norm() / normProduct);
317 }
318
319
320 return Math.acos(dot / normProduct);
321 }
322
323
324 @Override
325 public Vector3D project(final Vector3D base) {
326 return getComponent(base, false, Vector3D::new);
327 }
328
329
330 @Override
331 public Vector3D reject(final Vector3D base) {
332 return getComponent(base, true, Vector3D::new);
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351 @Override
352 public Vector3D.Unit orthogonal() {
353 final double threshold = 0.6 * getCheckedNorm();
354
355 final double inverse;
356 if (Math.abs(x) <= threshold) {
357 inverse = 1 / Vectors.norm(y, z);
358 return new Unit(0, inverse * z, -inverse * y);
359 } else if (Math.abs(y) <= threshold) {
360 inverse = 1 / Vectors.norm(x, z);
361 return new Unit(-inverse * z, 0, inverse * x);
362 }
363 inverse = 1 / Vectors.norm(x, y);
364 return new Unit(inverse * y, -inverse * x, 0);
365 }
366
367
368 @Override
369 public Vector3D.Unit orthogonal(final Vector3D dir) {
370 return dir.getComponent(this, true, Vector3D.Unit::from);
371 }
372
373
374
375
376
377 public Vector3D cross(final Vector3D v) {
378 return new Vector3D(Vectors.linearCombination(y, v.z, -z, v.y),
379 Vectors.linearCombination(z, v.x, -x, v.z),
380 Vectors.linearCombination(x, v.y, -y, v.x));
381 }
382
383
384
385
386
387
388 public Vector3D transform(final UnaryOperator<Vector3D> fn) {
389 return fn.apply(this);
390 }
391
392
393 @Override
394 public boolean eq(final Vector3D vec, final Precision.DoubleEquivalence precision) {
395 return precision.eq(x, vec.x) &&
396 precision.eq(y, vec.y) &&
397 precision.eq(z, vec.z);
398 }
399
400
401
402
403
404
405
406 @Override
407 public int hashCode() {
408 if (isNaN()) {
409 return 642;
410 }
411 return 643 * (164 * Double.hashCode(x) + 3 * Double.hashCode(y) + Double.hashCode(z));
412 }
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433 @Override
434 public boolean equals(final Object other) {
435 if (this == other) {
436 return true;
437 }
438 if (other instanceof Vector3D) {
439 final Vector3D rhs = (Vector3D) other;
440 if (rhs.isNaN()) {
441 return this.isNaN();
442 }
443
444 return Double.compare(x, rhs.x) == 0 &&
445 Double.compare(y, rhs.y) == 0 &&
446 Double.compare(z, rhs.z) == 0;
447 }
448 return false;
449 }
450
451
452 @Override
453 public String toString() {
454 return SimpleTupleFormat.getDefault().format(x, y, z);
455 }
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471 private <V extends Vector3D> V getComponent(final Vector3D base, final boolean reject,
472 final DoubleFunction3N<V> factory) {
473 final double aDotB = dot(base);
474
475
476
477
478
479
480
481 final double baseMagSq = Vectors.checkedNorm(base.normSq());
482
483 final double scale = aDotB / baseMagSq;
484
485 final double projX = scale * base.x;
486 final double projY = scale * base.y;
487 final double projZ = scale * base.z;
488
489 if (reject) {
490 return factory.apply(x - projX, y - projY, z - projZ);
491 }
492
493 return factory.apply(projX, projY, projZ);
494 }
495
496
497
498
499
500
501
502 public static Vector3D of(final double x, final double y, final double z) {
503 return new Vector3D(x, y, z);
504 }
505
506
507
508
509
510
511 public static Vector3D of(final double[] v) {
512 if (v.length != 3) {
513 throw new IllegalArgumentException("Dimension mismatch: " + v.length + " != 3");
514 }
515 return new Vector3D(v[0], v[1], v[2]);
516 }
517
518
519
520
521
522
523
524 public static Vector3D parse(final String str) {
525 return SimpleTupleFormat.getDefault().parse(str, Vector3D::new);
526 }
527
528
529
530
531
532
533 public static Vector3D max(final Vector3D first, final Vector3D... more) {
534 return computeMax(first, Arrays.asList(more).iterator());
535 }
536
537
538
539
540
541
542 public static Vector3D max(final Iterable<Vector3D> vecs) {
543 final Iterator<Vector3D> it = vecs.iterator();
544 if (!it.hasNext()) {
545 throw new IllegalArgumentException("Cannot compute vector max: no vectors given");
546 }
547
548 return computeMax(it.next(), it);
549 }
550
551
552
553
554
555
556 private static Vector3D computeMax(final Vector3D first, final Iterator<? extends Vector3D> more) {
557 double x = first.getX();
558 double y = first.getY();
559 double z = first.getZ();
560
561 Vector3D vec;
562 while (more.hasNext()) {
563 vec = more.next();
564
565 x = Math.max(x, vec.getX());
566 y = Math.max(y, vec.getY());
567 z = Math.max(z, vec.getZ());
568 }
569
570 return Vector3D.of(x, y, z);
571 }
572
573
574
575
576
577
578 public static Vector3D min(final Vector3D first, final Vector3D... more) {
579 return computeMin(first, Arrays.asList(more).iterator());
580 }
581
582
583
584
585
586
587 public static Vector3D min(final Iterable<Vector3D> vecs) {
588 final Iterator<Vector3D> it = vecs.iterator();
589 if (!it.hasNext()) {
590 throw new IllegalArgumentException("Cannot compute vector min: no vectors given");
591 }
592
593 return computeMin(it.next(), it);
594 }
595
596
597
598
599
600
601 private static Vector3D computeMin(final Vector3D first, final Iterator<? extends Vector3D> more) {
602 double x = first.getX();
603 double y = first.getY();
604 double z = first.getZ();
605
606 Vector3D vec;
607 while (more.hasNext()) {
608 vec = more.next();
609
610 x = Math.min(x, vec.getX());
611 y = Math.min(y, vec.getY());
612 z = Math.min(z, vec.getZ());
613 }
614
615 return Vector3D.of(x, y, z);
616 }
617
618
619
620
621
622
623
624 public static Vector3D centroid(final Vector3D first, final Vector3D... more) {
625 return computeCentroid(first, Arrays.asList(more).iterator());
626 }
627
628
629
630
631
632
633
634 public static Vector3D centroid(final Iterable<Vector3D> pts) {
635 final Iterator<Vector3D> it = pts.iterator();
636 if (!it.hasNext()) {
637 throw new IllegalArgumentException("Cannot compute centroid: no points given");
638 }
639
640 return computeCentroid(it.next(), it);
641 }
642
643
644
645
646
647
648 private static Vector3D computeCentroid(final Vector3D first, final Iterator<? extends Vector3D> more) {
649 final Sum sum = Sum.of(first);
650 int count = 1;
651
652 while (more.hasNext()) {
653 sum.add(more.next());
654 ++count;
655 }
656
657 return sum.get().multiply(1.0 / count);
658 }
659
660
661
662
663
664 public static final class Unit extends Vector3D {
665
666 public static final Unit PLUS_X = new Unit(1d, 0d, 0d);
667
668 public static final Unit MINUS_X = new Unit(-1d, 0d, 0d);
669
670 public static final Unit PLUS_Y = new Unit(0d, 1d, 0d);
671
672 public static final Unit MINUS_Y = new Unit(0d, -1d, 0d);
673
674 public static final Unit PLUS_Z = new Unit(0d, 0d, 1d);
675
676 public static final Unit MINUS_Z = new Unit(0d, 0d, -1d);
677
678
679
680
681 private static final double UNSCALED_MAX = 0x1.0p+500;
682
683
684
685
686 private static final double SCALE_UP_FACTOR = 0x1.0p+600;
687
688
689
690
691 private static final double SCALE_DOWN_FACTOR = 0x1.0p-600;
692
693
694
695
696
697
698
699 private Unit(final double x, final double y, final double z) {
700 super(x, y, z);
701 }
702
703
704 @Override
705 public double norm() {
706 return 1;
707 }
708
709
710 @Override
711 public double normSq() {
712 return 1;
713 }
714
715
716 @Override
717 public Unit normalize() {
718 return this;
719 }
720
721
722 @Override
723 public Unit normalizeOrNull() {
724 return this;
725 }
726
727
728 @Override
729 public Vector3D withNorm(final double mag) {
730 return multiply(mag);
731 }
732
733
734 @Override
735 public Unit negate() {
736 return new Unit(-getX(), -getY(), -getZ());
737 }
738
739
740
741
742
743
744
745
746
747 public static Unit from(final double x, final double y, final double z) {
748 return tryCreateNormalized(x, y, z, true);
749 }
750
751
752
753
754
755
756
757 public static Unit from(final Vector3D v) {
758 return v instanceof Unit ?
759 (Unit) v :
760 from(v.getX(), v.getY(), v.getZ());
761 }
762
763
764
765
766
767
768
769
770
771
772
773
774 private static Unit tryCreateNormalized(final double x, final double y, final double z,
775 final boolean throwOnFailure) {
776
777
778
779
780 final double norm = Math.sqrt((x * x) + (y * y) + (z * z));
781 final double normInv = 1.0 / norm;
782 if (Vectors.isRealNonZero(normInv)) {
783 return new Unit(
784 x * normInv,
785 y * normInv,
786 z * normInv);
787 }
788
789
790
791 final double scaledX;
792 final double scaledY;
793 final double scaledZ;
794
795 final double maxCoord = Math.max(Math.max(Math.abs(x), Math.abs(y)), Math.abs(z));
796 if (maxCoord > UNSCALED_MAX) {
797 scaledX = x * SCALE_DOWN_FACTOR;
798 scaledY = y * SCALE_DOWN_FACTOR;
799 scaledZ = z * SCALE_DOWN_FACTOR;
800 } else {
801 scaledX = x * SCALE_UP_FACTOR;
802 scaledY = y * SCALE_UP_FACTOR;
803 scaledZ = z * SCALE_UP_FACTOR;
804 }
805
806 final double scaledNormInv = 1.0 / Math.sqrt(
807 (scaledX * scaledX) +
808 (scaledY * scaledY) +
809 (scaledZ * scaledZ));
810
811 if (Vectors.isRealNonZero(scaledNormInv)) {
812 return new Unit(
813 scaledX * scaledNormInv,
814 scaledY * scaledNormInv,
815 scaledZ * scaledNormInv);
816 } else if (throwOnFailure) {
817 throw Vectors.illegalNorm(norm);
818 }
819 return null;
820 }
821 }
822
823
824
825
826
827
828
829 public static final class Sum extends EuclideanVectorSum<Vector3D> {
830
831 private final org.apache.commons.numbers.core.Sum xsum;
832
833 private final org.apache.commons.numbers.core.Sum ysum;
834
835 private final org.apache.commons.numbers.core.Sum zsum;
836
837
838
839
840 Sum(final Vector3D initial) {
841 this.xsum = org.apache.commons.numbers.core.Sum.of(initial.x);
842 this.ysum = org.apache.commons.numbers.core.Sum.of(initial.y);
843 this.zsum = org.apache.commons.numbers.core.Sum.of(initial.z);
844 }
845
846
847 @Override
848 public Sum add(final Vector3D vec) {
849 xsum.add(vec.x);
850 ysum.add(vec.y);
851 zsum.add(vec.z);
852 return this;
853 }
854
855
856 @Override
857 public Sum addScaled(final double scale, final Vector3D vec) {
858 xsum.addProduct(scale, vec.x);
859 ysum.addProduct(scale, vec.y);
860 zsum.addProduct(scale, vec.z);
861 return this;
862 }
863
864
865 @Override
866 public Vector3D get() {
867 return Vector3D.of(
868 xsum.getAsDouble(),
869 ysum.getAsDouble(),
870 zsum.getAsDouble());
871 }
872
873
874
875
876 public static Sum create() {
877 return new Sum(Vector3D.ZERO);
878 }
879
880
881
882
883
884 public static Sum of(final Vector3D initial) {
885 return new Sum(initial);
886 }
887
888
889
890
891
892
893 public static Sum of(final Vector3D first, final Vector3D... more) {
894 final Sum s = new Sum(first);
895 for (final Vector3D v : more) {
896 s.add(v);
897 }
898 return s;
899 }
900 }
901 }