1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.irurueta.geometry;
17
18 import com.irurueta.algebra.ArrayUtils;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.algebra.WrongSizeException;
21
22 import java.io.Serializable;
23 import java.util.Arrays;
24
25
26
27
28
29
30
31
32
33 @SuppressWarnings("DuplicatedCode")
34 public class Quaternion extends Rotation3D implements Serializable, Cloneable {
35
36
37
38
39 public static final int N_PARAMS = 4;
40
41
42
43
44 public static final int N_ANGLES = 3;
45
46
47
48
49 public static final double AXIS_NORM_THRESHOLD = 1e-7;
50
51
52
53
54
55 public static final double LARGE_AXIS_NORM_THRESHOLD = 1e-6;
56
57
58
59
60 public static final double TRACE_THRESHOLD = 1e-8;
61
62
63
64
65 private double a;
66
67
68
69
70 private double b;
71
72
73
74
75 private double c;
76
77
78
79
80 private double d;
81
82
83
84
85 private boolean normalized;
86
87
88
89
90
91 public Quaternion() {
92 a = 1.0;
93 }
94
95
96
97
98
99
100
101
102
103 public Quaternion(final double a, final double b, final double c, final double d) {
104 this.a = a;
105 this.b = b;
106 this.c = c;
107 this.d = d;
108 }
109
110
111
112
113
114
115 public Quaternion(final Quaternion quaternion) {
116 fromQuaternion(quaternion);
117 }
118
119
120
121
122
123
124
125
126
127 public Quaternion(final double[] values) {
128 setValues(values);
129 }
130
131
132
133
134
135
136
137
138
139 public Quaternion(final double[] axis, final double theta) {
140 setFromAxisAndRotation(axis, theta);
141 }
142
143
144
145
146
147
148 public Quaternion(final AxisRotation3D axisRotation) {
149 setFromAxisAndRotation(axisRotation);
150 }
151
152
153
154
155
156
157
158
159 public Quaternion(final double roll, final double pitch, final double yaw) {
160 setFromEulerAngles(roll, pitch, yaw);
161 }
162
163
164
165
166
167
168 public Quaternion(final MatrixRotation3D matrixRotation) {
169 setFromMatrixRotation(matrixRotation);
170 }
171
172
173
174
175
176
177 public double getA() {
178 return a;
179 }
180
181
182
183
184
185
186 public void setA(final double a) {
187 this.a = a;
188 normalized = false;
189 }
190
191
192
193
194
195
196 public double getB() {
197 return b;
198 }
199
200
201
202
203
204
205 public void setB(final double b) {
206 this.b = b;
207 normalized = false;
208 }
209
210
211
212
213
214
215 public double getC() {
216 return c;
217 }
218
219
220
221
222
223
224 public void setC(final double c) {
225 this.c = c;
226 normalized = false;
227 }
228
229
230
231
232
233
234 public double getD() {
235 return d;
236 }
237
238
239
240
241
242
243 public void setD(final double d) {
244 this.d = d;
245 normalized = false;
246 }
247
248
249
250
251
252
253 public double[] getValues() {
254 final var result = new double[N_PARAMS];
255 values(result);
256 return result;
257 }
258
259
260
261
262
263
264
265 public void values(final double[] result) {
266 if (result.length != N_PARAMS) {
267 throw new IllegalArgumentException("result length must be 4");
268 }
269
270 result[0] = a;
271 result[1] = b;
272 result[2] = c;
273 result[3] = d;
274 }
275
276
277
278
279
280
281
282
283 public final void setValues(final double[] values) {
284 if (values.length != N_PARAMS) {
285 throw new IllegalArgumentException("values length must be 4");
286 }
287
288 a = values[0];
289 b = values[1];
290 c = values[2];
291 d = values[3];
292 normalized = false;
293 }
294
295
296
297
298
299
300 public final void fromQuaternion(final Quaternion quaternion) {
301 a = quaternion.a;
302 b = quaternion.b;
303 c = quaternion.c;
304 d = quaternion.d;
305 normalized = quaternion.normalized;
306 }
307
308
309
310
311
312
313
314
315 @Override
316 public Quaternion clone() throws CloneNotSupportedException {
317 final var result = (Quaternion) super.clone();
318 copyTo(result);
319 return result;
320 }
321
322
323
324
325
326
327 public void copyTo(final Quaternion output) {
328 output.a = a;
329 output.b = b;
330 output.c = c;
331 output.d = d;
332 output.normalized = normalized;
333 }
334
335
336
337
338
339
340
341
342
343 public final void setFromAxisAndRotation(
344 final double axisX, final double axisY, final double axisZ, final double theta) {
345 setFromAxisAndRotation(axisX, axisY, axisZ, theta, null, null);
346 }
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363 public void setFromAxisAndRotation(
364 final double axisX, final double axisY, final double axisZ, final double theta,
365 final Matrix jacobianOfTheta, final Matrix jacobianOfAxis) {
366
367
368 if (jacobianOfTheta != null && (jacobianOfTheta.getRows() != N_PARAMS || jacobianOfTheta.getColumns() != 1)) {
369 throw new IllegalArgumentException("jacobian of theta must be 4x1");
370 }
371
372 if (jacobianOfAxis != null && (jacobianOfAxis.getRows() != N_PARAMS
373 || jacobianOfAxis.getColumns() != N_ANGLES)) {
374 throw new IllegalArgumentException("jacobian of axis must be 4x3");
375 }
376
377
378 final var halfTheta = theta / 2.0;
379 final var cosine = Math.cos(halfTheta);
380 final var sine = Math.sin(halfTheta);
381
382 a = cosine;
383
384 b = axisX * sine;
385 this.c = axisY * sine;
386 d = axisZ * sine;
387 normalized = false;
388
389 if (jacobianOfTheta != null) {
390 final var halfC = cosine / 2.0;
391 final var halfS = sine / 2.0;
392
393 jacobianOfTheta.getBuffer()[0] = -halfS;
394 jacobianOfTheta.getBuffer()[1] = axisX * halfC;
395 jacobianOfTheta.getBuffer()[2] = axisY * halfC;
396 jacobianOfTheta.getBuffer()[3] = axisZ * halfC;
397 }
398
399 if (jacobianOfAxis != null) {
400 jacobianOfAxis.initialize(0.0);
401 jacobianOfAxis.setElementAt(1, 0, sine);
402 jacobianOfAxis.setElementAt(2, 1, sine);
403 jacobianOfAxis.setElementAt(3, 2, sine);
404 }
405 }
406
407
408
409
410
411
412
413
414
415 public final void setFromAxisAndRotation(final double[] axis, final double theta) {
416 setFromAxisAndRotation(axis, theta, null, null);
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432 public void setFromAxisAndRotation(
433 final double[] axis, final double theta, final Matrix jacobianOfTheta, final Matrix jacobianOfAxis) {
434 if (axis.length != AxisRotation3D.AXIS_PARAMS) {
435 throw new IllegalArgumentException("axis length must be 3");
436 }
437
438 setFromAxisAndRotation(axis[0], axis[1], axis[2], theta, jacobianOfTheta, jacobianOfAxis);
439 }
440
441
442
443
444
445
446 public final void setFromAxisAndRotation(final AxisRotation3D axisRotation) {
447 setFromAxisAndRotation(axisRotation, null, null);
448 }
449
450
451
452
453
454
455
456
457
458
459
460
461 public void setFromAxisAndRotation(
462 final AxisRotation3D axisRotation, final Matrix jacobianOfTheta, final Matrix jacobianOfAxis) {
463
464 final var theta = axisRotation.getRotationAngle();
465
466 setFromAxisAndRotation(axisRotation.getAxisX(), axisRotation.getAxisY(), axisRotation.getAxisZ(), theta,
467 jacobianOfTheta, jacobianOfAxis);
468 }
469
470
471
472
473
474
475
476 public void multiply(final Quaternion q) {
477 multiply(q, this);
478 }
479
480
481
482
483
484
485
486
487 public Quaternion multiplyAndReturnNew(final Quaternion q) {
488 final var result = new Quaternion(0.0, 0.0, 0.0, 0.0);
489 multiply(q, result);
490 return result;
491 }
492
493
494
495
496
497
498
499
500 public void multiply(final Quaternion q, final Quaternion result) {
501 product(this, q, result);
502 }
503
504
505
506
507
508
509
510
511
512 public static void product(final Quaternion q1, final Quaternion q2, final Quaternion result) {
513 product(q1, q2, result, null, null);
514 }
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530 public static void product(
531 final Quaternion q1, final Quaternion q2, final Quaternion result, final Matrix jacobianQ1,
532 final Matrix jacobianQ2) {
533
534 if (jacobianQ1 != null && (jacobianQ1.getRows() != Quaternion.N_PARAMS
535 || jacobianQ1.getColumns() != Quaternion.N_PARAMS)) {
536 throw new IllegalArgumentException("jacobian of q1 must be 4x4");
537 }
538 if (jacobianQ2 != null && (jacobianQ2.getRows() != Quaternion.N_PARAMS
539 || jacobianQ2.getColumns() != Quaternion.N_PARAMS)) {
540 throw new IllegalArgumentException("jacobian of q2 must be 4x4");
541 }
542
543 final var q1A = q1.a;
544 final var q1B = q1.b;
545 final var q1C = q1.c;
546 final var q1D = q1.d;
547 final var q2A = q2.a;
548 final var q2B = q2.b;
549 final var q2C = q2.c;
550 final var q2D = q2.d;
551
552 result.a = q1A * q2A - q1B * q2B - q1C * q2C - q1D * q2D;
553 result.b = q1A * q2B + q1B * q2A + q1C * q2D - q1D * q2C;
554 result.c = q1A * q2C - q1B * q2D + q1C * q2A + q1D * q2B;
555 result.d = q1A * q2D + q1B * q2C - q1C * q2B + q1D * q2A;
556 result.normalized = false;
557
558 if (jacobianQ1 != null) {
559 jacobianQ1.setElementAt(0, 0, q2A);
560 jacobianQ1.setElementAt(1, 0, q2B);
561 jacobianQ1.setElementAt(2, 0, q2C);
562 jacobianQ1.setElementAt(3, 0, q2D);
563
564 jacobianQ1.setElementAt(0, 1, -q2B);
565 jacobianQ1.setElementAt(1, 1, q2A);
566 jacobianQ1.setElementAt(2, 1, -q2D);
567 jacobianQ1.setElementAt(3, 1, q2C);
568
569 jacobianQ1.setElementAt(0, 2, -q2C);
570 jacobianQ1.setElementAt(1, 2, q2D);
571 jacobianQ1.setElementAt(2, 2, q2A);
572 jacobianQ1.setElementAt(3, 2, -q2B);
573
574 jacobianQ1.setElementAt(0, 3, -q2D);
575 jacobianQ1.setElementAt(1, 3, -q2C);
576 jacobianQ1.setElementAt(2, 3, q2B);
577 jacobianQ1.setElementAt(3, 3, q2A);
578 }
579
580 if (jacobianQ2 != null) {
581 jacobianQ2.setElementAt(0, 0, q1A);
582 jacobianQ2.setElementAt(1, 0, q1B);
583 jacobianQ2.setElementAt(2, 0, q1C);
584 jacobianQ2.setElementAt(3, 0, q1D);
585
586 jacobianQ2.setElementAt(0, 1, -q1B);
587 jacobianQ2.setElementAt(1, 1, q1A);
588 jacobianQ2.setElementAt(2, 1, q1D);
589 jacobianQ2.setElementAt(3, 1, -q1C);
590
591 jacobianQ2.setElementAt(0, 2, -q1C);
592 jacobianQ2.setElementAt(1, 2, -q1D);
593 jacobianQ2.setElementAt(2, 2, q1A);
594 jacobianQ2.setElementAt(3, 2, q1B);
595
596 jacobianQ2.setElementAt(0, 3, -q1D);
597 jacobianQ2.setElementAt(1, 3, q1C);
598 jacobianQ2.setElementAt(2, 3, -q1B);
599 jacobianQ2.setElementAt(3, 3, q1A);
600 }
601 }
602
603
604
605
606
607
608
609
610
611
612
613
614 public void setFromEulerAngles(
615 final double roll, final double pitch, final double yaw, final Matrix jacobian) {
616
617 if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_ANGLES)) {
618 throw new IllegalArgumentException("jacobian must be 4x3");
619 }
620
621
622 final var qx = new Quaternion(new double[]{1.0, 0.0, 0.0}, roll);
623
624 final var qy = new Quaternion(new double[]{0.0, 1.0, 0.0}, pitch);
625
626 final var qz = new Quaternion(new double[]{0.0, 0.0, 1.0}, yaw);
627
628 product(qz, qy, this);
629 product(this, qx, this);
630 normalize();
631
632 if (jacobian != null) {
633 final var halfRoll = roll / 2.0;
634 final var halfPitch = pitch / 2.0;
635 final var halfYaw = yaw / 2.0;
636
637 final var sr = Math.sin(halfRoll);
638 final var sp = Math.sin(halfPitch);
639 final var sy = Math.sin(halfYaw);
640
641 final var cr = Math.cos(halfRoll);
642 final var cp = Math.cos(halfPitch);
643 final var cy = Math.cos(halfYaw);
644
645 jacobian.setElementAt(0, 0, 0.5 * (-cy * cp * sr + sy * sp * cr));
646 jacobian.setElementAt(1, 0, 0.5 * (cy * cp * cr + sy * sp * sr));
647 jacobian.setElementAt(2, 0, 0.5 * (-cy * sp * sr + sy * cp * cr));
648 jacobian.setElementAt(3, 0, 0.5 * (-sy * cp * sr - cy * sp * cr));
649
650 jacobian.setElementAt(0, 1, 0.5 * (-cy * sp * cr + sy * cp * sr));
651 jacobian.setElementAt(1, 1, 0.5 * (-cy * sp * sr - sy * cp * cr));
652 jacobian.setElementAt(2, 1, 0.5 * (cy * cp * cr - sy * sp * sr));
653 jacobian.setElementAt(3, 1, 0.5 * (-cy * cp * sr - sy * sp * cr));
654
655 jacobian.setElementAt(0, 2, 0.5 * (-sy * cp * cr + cy * sp * sr));
656 jacobian.setElementAt(1, 2, jacobian.getElementAt(3, 0));
657 jacobian.setElementAt(2, 2, 0.5 * (-sy * sp * cr + cy * cp * sr));
658 jacobian.setElementAt(3, 2, jacobian.getElementAt(1, 0));
659 }
660 }
661
662
663
664
665
666
667
668
669
670 public final void setFromEulerAngles(final double roll, final double pitch, final double yaw) {
671 setFromEulerAngles(roll, pitch, yaw, null);
672 }
673
674
675
676
677
678
679
680
681
682
683 public void setFromEulerAngles(final double[] angles, final Matrix jacobian) {
684 if (angles.length != N_ANGLES) {
685 throw new IllegalArgumentException("angles length must be 3");
686 }
687
688 setFromEulerAngles(angles[0], angles[1], angles[2], jacobian);
689 }
690
691
692
693
694
695
696
697
698
699 public void setFromEulerAngles(final double[] angles) {
700 setFromEulerAngles(angles, null);
701 }
702
703
704
705
706
707
708
709
710
711
712
713
714
715 public static void eulerToMatrixRotation(
716 final double roll, final double pitch, final double yaw, final MatrixRotation3D result,
717 final Matrix jacobian) {
718
719 if (jacobian != null && (jacobian.getRows() != 3 * N_ANGLES || jacobian.getColumns() != N_ANGLES)) {
720 throw new IllegalArgumentException("jacobian must be 9x3");
721 }
722
723 result.setRollPitchYaw(roll, pitch, yaw);
724
725 if (jacobian != null) {
726 final var sr = Math.sin(roll);
727 final var cr = Math.cos(roll);
728 final var sp = Math.sin(pitch);
729 final var cp = Math.cos(pitch);
730 final var sy = Math.sin(yaw);
731 final var cy = Math.cos(yaw);
732
733 final var tmp1 = sr * sy + cr * sp * cy;
734 final var tmp2 = -cr * cy - sr * sp * sy;
735 jacobian.setElementAt(0, 0, 0.0);
736 jacobian.setElementAt(1, 0, 0.0);
737 jacobian.setElementAt(2, 0, 0.0);
738 jacobian.setElementAt(3, 0, tmp1);
739 jacobian.setElementAt(4, 0, -sr * cy + cr * sp * sy);
740 jacobian.setElementAt(5, 0, cr * cp);
741 jacobian.setElementAt(6, 0, cr * sy - sr * sp * cy);
742 jacobian.setElementAt(7, 0, tmp2);
743 jacobian.setElementAt(8, 0, -sr * cp);
744
745 jacobian.setElementAt(0, 1, -sp * cy);
746 jacobian.setElementAt(1, 1, -sp * sy);
747 jacobian.setElementAt(2, 1, -cp);
748 jacobian.setElementAt(3, 1, sr * cp * cy);
749 jacobian.setElementAt(4, 1, sr * cp * sy);
750 jacobian.setElementAt(5, 1, -sr * sp);
751 jacobian.setElementAt(6, 1, cr * cp * cy);
752 jacobian.setElementAt(7, 1, cr * cp * sy);
753 jacobian.setElementAt(8, 1, -cr * sp);
754
755 jacobian.setElementAt(0, 2, -cp * sy);
756 jacobian.setElementAt(1, 2, cp * cy);
757 jacobian.setElementAt(2, 2, 0.0);
758 jacobian.setElementAt(3, 2, tmp2);
759 jacobian.setElementAt(4, 2, -cr * sy + sr * sp * cy);
760 jacobian.setElementAt(5, 2, 0.0);
761 jacobian.setElementAt(6, 2, sr * cy - cr * sp * sy);
762 jacobian.setElementAt(7, 2, tmp1);
763 jacobian.setElementAt(8, 2, 0.0);
764 }
765 }
766
767
768
769
770
771
772
773
774
775
776
777 public static void eulerToMatrixRotation(
778 final double roll, final double pitch, final double yaw, final MatrixRotation3D result) {
779 eulerToMatrixRotation(roll, pitch, yaw, result, null);
780 }
781
782
783
784
785
786
787
788
789
790
791
792
793 public static void eulerToMatrixRotation(
794 final double[] angles, final MatrixRotation3D result, final Matrix jacobian) {
795 if (angles.length != N_ANGLES) {
796 throw new IllegalArgumentException("angles must have length 3");
797 }
798
799 eulerToMatrixRotation(angles[0], angles[1], angles[2], result, jacobian);
800 }
801
802
803
804
805
806
807
808
809
810
811 public static void eulerToMatrixRotation(final double[] angles, final MatrixRotation3D result) {
812 eulerToMatrixRotation(angles, result, null);
813 }
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828 public double toAxisAndRotationAngle(
829 final double[] axis, final Matrix jacobianAngle, final Matrix jacobianAxis) {
830 if (axis.length != AxisRotation3D.AXIS_PARAMS) {
831 throw new IllegalArgumentException("axis length must be 3");
832 }
833 if (jacobianAngle != null && (jacobianAngle.getRows() != 1 || jacobianAngle.getColumns() != N_PARAMS)) {
834 throw new IllegalArgumentException("jacobian of angle must be 1x4");
835 }
836 if (jacobianAxis != null && (jacobianAxis.getRows() != AxisRotation3D.AXIS_PARAMS
837 || jacobianAxis.getColumns() != N_PARAMS)) {
838 throw new IllegalArgumentException("jacobian of axis must be 3x4");
839 }
840
841
842 final var v = new double[]{b, c, d};
843
844
845 final var n = com.irurueta.algebra.Utils.normF(v);
846
847
848 if (n > 0.0) {
849 ArrayUtils.multiplyByScalar(v, 1.0 / n, axis);
850 } else {
851 axis[0] = axis[1] = 0.0;
852 axis[2] = 1.0;
853 }
854
855
856 final var s = a;
857 final var aValue = 2.0 * Math.atan2(n, s);
858
859 if (jacobianAngle != null) {
860 if (n > AXIS_NORM_THRESHOLD) {
861 final var denom = n * n + s * s;
862 final var aN = 2.0 * s / denom;
863 final var aS = -2.0 * n / denom;
864 final var aV = ArrayUtils.multiplyByScalarAndReturnNew(axis, aN);
865
866 jacobianAngle.setElementAtIndex(0, aS);
867 jacobianAngle.setElementAtIndex(1, aV[0]);
868 jacobianAngle.setElementAtIndex(2, aV[1]);
869 jacobianAngle.setElementAtIndex(3, aV[2]);
870 } else {
871 jacobianAngle.initialize(0.0);
872 }
873 }
874
875 if (jacobianAxis != null) {
876 jacobianAxis.initialize(0.0);
877
878 try {
879 if (n > AXIS_NORM_THRESHOLD) {
880
881 final var uV = Matrix.identity(AxisRotation3D.AXIS_PARAMS, AxisRotation3D.AXIS_PARAMS);
882 uV.multiplyByScalar(n);
883 uV.subtract(Matrix.newFromArray(v, true).multiplyAndReturnNew(
884 Matrix.newFromArray(axis, false)));
885 uV.multiplyByScalar(1.0 / (n * n));
886
887 jacobianAxis.setSubmatrix(0, 1, 2, 3, uV);
888 } else {
889
890 final var m = Matrix.identity(AxisRotation3D.AXIS_PARAMS, AxisRotation3D.AXIS_PARAMS);
891 m.multiplyByScalar(2.0);
892
893 jacobianAxis.setSubmatrix(0, 1, 2, 3, m);
894 }
895 } catch (final WrongSizeException ignore) {
896
897 }
898 }
899
900 return aValue;
901 }
902
903
904
905
906
907
908
909
910 public double toAxisAndRotationAngle(final double[] axis) {
911 return toAxisAndRotationAngle(axis, null, null);
912 }
913
914
915
916
917
918
919
920 @Override
921 public void toAxisRotation(final AxisRotation3D result) {
922 final var axis = new double[AxisRotation3D.AXIS_PARAMS];
923 final var theta = toAxisAndRotationAngle(axis, null, null);
924 result.setAxisAndRotation(axis, theta);
925 }
926
927
928
929
930
931
932 @Override
933 public AxisRotation3D toAxisRotation() {
934 final var result = new AxisRotation3D();
935 toAxisRotation(result);
936 return result;
937 }
938
939
940
941
942
943
944
945
946
947
948
949
950 public void toRotationVector(final double[] result, final Matrix jacobian) {
951 if (result.length != AxisRotation3D.AXIS_PARAMS) {
952 throw new IllegalArgumentException("result length must be 3");
953 }
954 if (jacobian != null && (jacobian.getRows() != AxisRotation3D.AXIS_PARAMS
955 || jacobian.getColumns() != N_PARAMS)) {
956 throw new IllegalArgumentException("jacobian must be 3x4");
957 }
958
959 if (jacobian == null) {
960 final var theta = toAxisAndRotationAngle(result, null, null);
961 ArrayUtils.multiplyByScalar(result, theta, result);
962 } else {
963 try {
964 final var jacobianAngle = new Matrix(1, N_PARAMS);
965 final var jacobianAxis = new Matrix(AxisRotation3D.AXIS_PARAMS, N_PARAMS);
966 final var axis = new double[AxisRotation3D.AXIS_PARAMS];
967 final var theta = toAxisAndRotationAngle(axis, jacobianAngle, jacobianAxis);
968 ArrayUtils.multiplyByScalar(axis, theta, result);
969
970 final var vA = Matrix.newFromArray(axis, true);
971 final var vU = Matrix.diagonal(new double[]{theta, theta, theta});
972
973 if (theta > AXIS_NORM_THRESHOLD) {
974
975
976
977 vA.multiply(jacobianAngle);
978
979 vU.multiply(jacobianAxis);
980
981 vA.add(vU);
982
983 jacobian.copyFrom(vA);
984 } else {
985
986 final var m = Matrix.identity(AxisRotation3D.AXIS_PARAMS,
987 AxisRotation3D.AXIS_PARAMS);
988 m.multiplyByScalar(2.0);
989
990 jacobian.setSubmatrix(0, 1, 2, 3, m);
991 }
992 } catch (final WrongSizeException ignore) {
993
994 }
995 }
996 }
997
998
999
1000
1001
1002
1003
1004
1005 public void toRotationVector(final double[] result) {
1006 toRotationVector(result, null);
1007 }
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020 public void toEulerAngles(final double[] angles, final Matrix jacobian) {
1021 if (angles.length != N_ANGLES) {
1022 throw new IllegalArgumentException("angles length must be 3");
1023 }
1024 if (jacobian != null && (jacobian.getRows() != N_ANGLES || jacobian.getColumns() != N_PARAMS)) {
1025 throw new IllegalArgumentException("jacobian must be 3x4");
1026 }
1027
1028 final var y1 = 2.0 * c * d + 2.0 * a * b;
1029 final var x1 = a * a - b * b - c * c + d * d;
1030 final var z2 = -2.0 * b * d + 2.0 * a * c;
1031 final var y3 = 2.0 * b * c + 2.0 * a * d;
1032 final var x3 = a * a + b * b - c * c - d * d;
1033
1034
1035 angles[0] = Math.atan2(y1, x1);
1036
1037
1038 angles[1] = Math.asin(z2);
1039
1040
1041 angles[2] = Math.atan2(y3, x3);
1042
1043 if (jacobian != null) {
1044 final var dx1dq = new double[]{2 * a, -2 * b, -2 * c, 2 * d};
1045 final var dy1dq = new double[]{2 * b, 2 * a, 2 * d, 2 * c};
1046 final var dz2dq = new double[]{2 * c, -2 * d, 2 * a, -2 * b};
1047 final var dx3dq = new double[]{2 * a, 2 * b, -2 * c, -2 * d};
1048 final var dy3dq = new double[]{2 * d, 2 * c, 2 * b, 2 * a};
1049
1050 final var de1dx1 = -y1 / (x1 * x1 + y1 * y1);
1051 final var de1dy1 = x1 / (x1 * x1 + y1 * y1);
1052 final var de2dz2 = 1 / Math.sqrt(1 - z2 * z2);
1053 final var de3dx3 = -y3 / (x3 * x3 + y3 * y3);
1054 final var de3dy3 = x3 / (x3 * x3 + y3 * y3);
1055
1056
1057 ArrayUtils.multiplyByScalar(dx1dq, de1dx1, dx1dq);
1058 ArrayUtils.multiplyByScalar(dy1dq, de1dy1, dy1dq);
1059 final var de1dq = ArrayUtils.sumAndReturnNew(dx1dq, dy1dq);
1060
1061
1062 final var de2dq = ArrayUtils.multiplyByScalarAndReturnNew(dz2dq, de2dz2);
1063
1064
1065 ArrayUtils.multiplyByScalar(dx3dq, de3dx3, dx3dq);
1066 ArrayUtils.multiplyByScalar(dy3dq, de3dy3, dy3dq);
1067 final var de3dq = ArrayUtils.sumAndReturnNew(dx3dq, dy3dq);
1068
1069 jacobian.setSubmatrix(0, 0, 0, N_PARAMS - 1, de1dq);
1070 jacobian.setSubmatrix(1, 0, 1, N_PARAMS - 1, de2dq);
1071 jacobian.setSubmatrix(2, 0, 2, N_PARAMS - 1, de3dq);
1072 }
1073 }
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083 public void toEulerAngles(final double[] angles) {
1084 toEulerAngles(angles, null);
1085 }
1086
1087
1088
1089
1090
1091
1092
1093 public double[] toEulerAngles() {
1094 final var result = new double[N_ANGLES];
1095 toEulerAngles(result, null);
1096 return result;
1097 }
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108 public void quaternionMatrix(final Matrix result) {
1109 if (result.getRows() != N_PARAMS || result.getColumns() != N_PARAMS) {
1110 throw new IllegalArgumentException("matrix must be 4x4");
1111 }
1112
1113 result.setElementAt(0, 0, a);
1114 result.setElementAt(1, 0, b);
1115 result.setElementAt(2, 0, c);
1116 result.setElementAt(3, 0, d);
1117
1118 result.setElementAt(0, 1, -b);
1119 result.setElementAt(1, 1, a);
1120 result.setElementAt(2, 1, d);
1121 result.setElementAt(3, 1, -c);
1122
1123 result.setElementAt(0, 2, -c);
1124 result.setElementAt(1, 2, -d);
1125 result.setElementAt(2, 2, a);
1126 result.setElementAt(3, 2, b);
1127
1128 result.setElementAt(0, 3, -d);
1129 result.setElementAt(1, 3, c);
1130 result.setElementAt(2, 3, -b);
1131 result.setElementAt(3, 3, a);
1132 }
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142 public Matrix toQuaternionMatrix() {
1143 Matrix result = null;
1144 try {
1145 result = new Matrix(N_PARAMS, N_PARAMS);
1146 quaternionMatrix(result);
1147 } catch (final WrongSizeException ignore) {
1148
1149 }
1150 return result;
1151 }
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162 public void conjugate(final Quaternion result, final Matrix jacobian) {
1163 if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_PARAMS)) {
1164 throw new IllegalArgumentException("jacobian must be 4x4");
1165 }
1166
1167 result.a = a;
1168 result.b = -b;
1169 result.c = -c;
1170 result.d = -d;
1171 result.normalized = normalized;
1172
1173 if (jacobian != null) {
1174 jacobian.initialize(0.0);
1175 jacobian.setElementAt(0, 0, 1.0);
1176 for (int i = 1; i < N_PARAMS; i++) {
1177 jacobian.setElementAt(i, i, -1.0);
1178 }
1179 }
1180 }
1181
1182
1183
1184
1185
1186
1187
1188
1189 public void conjugate(final Quaternion result) {
1190 conjugate(result, null);
1191 }
1192
1193
1194
1195
1196
1197
1198
1199 public Quaternion conjugateAndReturnNew() {
1200 final var q = new Quaternion();
1201 conjugate(q);
1202 return q;
1203 }
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216 public void quaternionMatrixN(final Matrix result) {
1217 if (result.getRows() != N_PARAMS || result.getColumns() != N_PARAMS) {
1218 throw new IllegalArgumentException("matrix must be 4x4");
1219 }
1220
1221 result.setElementAt(0, 0, a);
1222 result.setElementAt(1, 0, b);
1223 result.setElementAt(2, 0, c);
1224 result.setElementAt(3, 0, d);
1225
1226 result.setElementAt(0, 1, -b);
1227 result.setElementAt(1, 1, a);
1228 result.setElementAt(2, 1, -d);
1229 result.setElementAt(3, 1, c);
1230
1231 result.setElementAt(0, 2, -c);
1232 result.setElementAt(1, 2, d);
1233 result.setElementAt(2, 2, a);
1234 result.setElementAt(3, 2, -b);
1235
1236 result.setElementAt(0, 3, -d);
1237 result.setElementAt(1, 3, -c);
1238 result.setElementAt(2, 3, b);
1239 result.setElementAt(3, 3, a);
1240 }
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252 public Matrix toQuaternionMatrixN() {
1253 Matrix result = null;
1254 try {
1255 result = new Matrix(N_PARAMS, N_PARAMS);
1256 quaternionMatrixN(result);
1257 } catch (final WrongSizeException ignore) {
1258
1259 }
1260 return result;
1261 }
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272 public void toMatrixRotation(final Matrix result, final Matrix jacobian) {
1273 if (result.getRows() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS
1274 || result.getColumns() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS) {
1275 throw new IllegalArgumentException("result matrix is not 3x3");
1276 }
1277 if (jacobian != null && (jacobian.getRows() != 9 || jacobian.getColumns() != 4)) {
1278 throw new IllegalArgumentException("jacobian matrix is not 9x4");
1279 }
1280
1281 final var aa = a * a;
1282 final var ab = 2.0 * a * b;
1283 final var ac = 2.0 * a * c;
1284 final var ad = 2.0 * a * d;
1285 final var bb = b * b;
1286 final var bc = 2.0 * b * c;
1287 final var bd = 2.0 * b * d;
1288 final var cc = c * c;
1289 final var cd = 2.0 * c * d;
1290 final var dd = d * d;
1291
1292 result.setElementAt(0, 0, aa + bb - cc - dd);
1293 result.setElementAt(1, 0, bc + ad);
1294 result.setElementAt(2, 0, bd - ac);
1295
1296 result.setElementAt(0, 1, bc - ad);
1297 result.setElementAt(1, 1, aa - bb + cc - dd);
1298 result.setElementAt(2, 1, cd + ab);
1299
1300 result.setElementAt(0, 2, bd + ac);
1301 result.setElementAt(1, 2, cd - ab);
1302 result.setElementAt(2, 2, aa - bb - cc + dd);
1303
1304 if (jacobian != null) {
1305 final var a2 = 2.0 * a;
1306 final var b2 = 2.0 * b;
1307 final var c2 = 2.0 * c;
1308 final var d2 = 2.0 * d;
1309
1310 jacobian.setElementAt(0, 0, a2);
1311 jacobian.setElementAt(1, 0, d2);
1312 jacobian.setElementAt(2, 0, -c2);
1313 jacobian.setElementAt(3, 0, -d2);
1314 jacobian.setElementAt(4, 0, a2);
1315 jacobian.setElementAt(5, 0, b2);
1316 jacobian.setElementAt(6, 0, c2);
1317 jacobian.setElementAt(7, 0, -b2);
1318 jacobian.setElementAt(8, 0, a2);
1319
1320 jacobian.setElementAt(0, 1, b2);
1321 jacobian.setElementAt(1, 1, c2);
1322 jacobian.setElementAt(2, 1, d2);
1323 jacobian.setElementAt(3, 1, c2);
1324 jacobian.setElementAt(4, 1, -b2);
1325 jacobian.setElementAt(5, 1, a2);
1326 jacobian.setElementAt(6, 1, d2);
1327 jacobian.setElementAt(7, 1, -a2);
1328 jacobian.setElementAt(8, 1, -b2);
1329
1330 jacobian.setElementAt(0, 2, -c2);
1331 jacobian.setElementAt(1, 2, b2);
1332 jacobian.setElementAt(2, 2, -a2);
1333 jacobian.setElementAt(3, 2, b2);
1334 jacobian.setElementAt(4, 2, c2);
1335 jacobian.setElementAt(5, 2, d2);
1336 jacobian.setElementAt(6, 2, a2);
1337 jacobian.setElementAt(7, 2, d2);
1338 jacobian.setElementAt(8, 2, -c2);
1339
1340 jacobian.setElementAt(0, 3, -d2);
1341 jacobian.setElementAt(1, 3, a2);
1342 jacobian.setElementAt(2, 3, b2);
1343 jacobian.setElementAt(3, 3, -a2);
1344 jacobian.setElementAt(4, 3, -d2);
1345 jacobian.setElementAt(5, 3, c2);
1346 jacobian.setElementAt(6, 3, b2);
1347 jacobian.setElementAt(7, 3, c2);
1348 jacobian.setElementAt(8, 3, d2);
1349 }
1350 }
1351
1352
1353
1354
1355
1356
1357
1358
1359 public void toMatrixRotation(final Matrix result) {
1360 toMatrixRotation(result, null);
1361 }
1362
1363
1364
1365
1366
1367
1368
1369 @Override
1370 public void toMatrixRotation(final MatrixRotation3D result) {
1371 toMatrixRotation(result.internalMatrix);
1372 }
1373
1374
1375
1376
1377
1378
1379
1380 @Override
1381 public MatrixRotation3D toMatrixRotation() {
1382 final var rotation = new MatrixRotation3D();
1383 toMatrixRotation(rotation);
1384 return rotation;
1385 }
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402 public static void rotate(final Quaternion q, final Point3D inputPoint, final Point3D resultPoint,
1403 final Matrix jacobianPoint, final Matrix jacobianQuaternion) {
1404 if (jacobianPoint != null && (jacobianPoint.getRows() != N_ANGLES || jacobianPoint.getColumns() != N_ANGLES)) {
1405 throw new IllegalArgumentException("jacobian of point must be 3x3");
1406 }
1407 if (jacobianQuaternion != null && (jacobianQuaternion.getRows() != N_ANGLES
1408 || jacobianQuaternion.getColumns() != N_PARAMS)) {
1409 throw new IllegalArgumentException("jacobian of quaternion must be 3x4");
1410 }
1411
1412 final var v0 = new Quaternion(0.0, inputPoint.getInhomX(), inputPoint.getInhomY(), inputPoint.getInhomZ());
1413
1414 final var tmp = q.multiplyAndReturnNew(v0).multiplyAndReturnNew(q.conjugateAndReturnNew());
1415
1416 resultPoint.setInhomogeneousCoordinates(tmp.getB(), tmp.getC(), tmp.getD());
1417
1418 if (jacobianPoint != null) {
1419 q.toMatrixRotation(jacobianPoint);
1420 }
1421
1422 if (jacobianQuaternion != null) {
1423 final var a = q.a;
1424 final var b = q.b;
1425 final var c = q.c;
1426 final var d = q.d;
1427
1428 final var x = inputPoint.getInhomX();
1429 final var y = inputPoint.getInhomY();
1430 final var z = inputPoint.getInhomZ();
1431
1432 final var axdycz = 2.0 * (a * x - d * y + c * z);
1433 final var bxcydz = 2.0 * (b * x + c * y + d * z);
1434 final var cxbyaz = 2.0 * (c * x - b * y - a * z);
1435 final var dxaybz = 2.0 * (d * x + a * y - b * z);
1436
1437 jacobianQuaternion.setElementAt(0, 0, axdycz);
1438 jacobianQuaternion.setElementAt(1, 0, dxaybz);
1439 jacobianQuaternion.setElementAt(2, 0, -cxbyaz);
1440
1441 jacobianQuaternion.setElementAt(0, 1, bxcydz);
1442 jacobianQuaternion.setElementAt(1, 1, cxbyaz);
1443 jacobianQuaternion.setElementAt(2, 1, dxaybz);
1444
1445 jacobianQuaternion.setElementAt(0, 2, -cxbyaz);
1446 jacobianQuaternion.setElementAt(1, 2, bxcydz);
1447 jacobianQuaternion.setElementAt(2, 2, -axdycz);
1448
1449 jacobianQuaternion.setElementAt(0, 3, -dxaybz);
1450 jacobianQuaternion.setElementAt(1, 3, axdycz);
1451 jacobianQuaternion.setElementAt(2, 3, bxcydz);
1452 }
1453 }
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469 public void rotate(final Point3D inputPoint, final Point3D resultPoint, final Matrix jacobianPoint,
1470 final Matrix jacobianQuaternion) {
1471
1472 rotate(this, inputPoint, resultPoint, jacobianPoint, jacobianQuaternion);
1473 }
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486 @Override
1487 public void rotate(final Point3D inputPoint, final Point3D resultPoint) {
1488 rotate(inputPoint, resultPoint, null, null);
1489 }
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502 @Override
1503 public Point3D rotate(final Point3D point) {
1504 final var result = new HomogeneousPoint3D();
1505 rotate(point, result);
1506 return result;
1507 }
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517 public static void matrixRotationToQuaternion(final Matrix r, final Quaternion result) {
1518 if (r.getRows() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS
1519 || r.getColumns() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS) {
1520 throw new IllegalArgumentException("rotation matrix must be 3x3");
1521 }
1522
1523 final var trace = com.irurueta.algebra.Utils.trace(r) + 1.0;
1524 double s;
1525 double a;
1526 double b;
1527 double c;
1528 double d;
1529
1530 if (trace > TRACE_THRESHOLD) {
1531
1532 s = 2.0 * Math.sqrt(trace);
1533 a = 0.25 * s;
1534 b = (r.getElementAt(1, 2) - r.getElementAt(2, 1)) / s;
1535 c = (r.getElementAt(2, 0) - r.getElementAt(0, 2)) / s;
1536 d = (r.getElementAt(0, 1) - r.getElementAt(1, 0)) / s;
1537 } else {
1538 if (r.getElementAt(0, 0) > r.getElementAt(1, 1)
1539 && r.getElementAt(0, 0) > r.getElementAt(2, 2)) {
1540
1541
1542
1543 s = 2.0 * Math.sqrt(1.0 + r.getElementAt(0, 0) - r.getElementAt(1, 1)
1544 - r.getElementAt(2, 2));
1545 a = (r.getElementAt(1, 2) - r.getElementAt(2, 1)) / s;
1546 b = 0.25 * s;
1547 c = (r.getElementAt(0, 1) + r.getElementAt(1, 0)) / s;
1548 d = (r.getElementAt(2, 0) + r.getElementAt(0, 2)) / s;
1549 } else if (r.getElementAt(1, 1) > r.getElementAt(2, 2)) {
1550
1551
1552
1553 s = 2.0 * Math.sqrt(1.0 + r.getElementAt(1, 1) - r.getElementAt(0, 0)
1554 - r.getElementAt(2, 2));
1555 a = (r.getElementAt(2, 0) - r.getElementAt(0, 2)) / s;
1556 b = (r.getElementAt(0, 1) + r.getElementAt(1, 0)) / s;
1557 c = 0.25 * s;
1558 d = (r.getElementAt(1, 2) + r.getElementAt(2, 1)) / s;
1559 } else {
1560
1561
1562
1563 s = 2.0 * Math.sqrt(1.0 + r.getElementAt(2, 2)
1564 - r.getElementAt(0, 0) - r.getElementAt(1, 1));
1565 a = (r.getElementAt(0, 1) - r.getElementAt(1, 0)) / s;
1566 b = (r.getElementAt(2, 0) + r.getElementAt(0, 2)) / s;
1567 c = (r.getElementAt(1, 2) + r.getElementAt(2, 1)) / s;
1568 d = 0.25 * s;
1569 }
1570 }
1571
1572 result.a = a;
1573 result.b = -b;
1574 result.c = -c;
1575 result.d = -d;
1576 result.normalized = false;
1577 }
1578
1579
1580
1581
1582
1583
1584
1585
1586 public static void matrixRotationToQuaternion(final MatrixRotation3D rotation, final Quaternion result) {
1587 matrixRotationToQuaternion(rotation.internalMatrix, result);
1588 }
1589
1590
1591
1592
1593
1594
1595
1596
1597 public void setFromMatrixRotation(final Matrix matrix) {
1598 matrixRotationToQuaternion(matrix, this);
1599 }
1600
1601
1602
1603
1604
1605
1606
1607 public final void setFromMatrixRotation(final MatrixRotation3D rotation) {
1608 matrixRotationToQuaternion(rotation, this);
1609 }
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626 public static double rotationVectorToRotationAxisAndAngle(
1627 final double[] rotationVector, final double[] axis, final Matrix jacobianAlpha,
1628 final Matrix jacobianRotationVector) {
1629 if (rotationVector.length != AxisRotation3D.AXIS_PARAMS) {
1630 throw new IllegalArgumentException("rotation vector length must be 3");
1631 }
1632
1633 if (jacobianAlpha != null && (jacobianAlpha.getRows() != 1 || jacobianAlpha.getColumns() != N_ANGLES)) {
1634 throw new IllegalArgumentException("jacobian alpha must be 1x3");
1635 }
1636
1637 if (jacobianRotationVector != null && (jacobianRotationVector.getRows() != N_ANGLES
1638 || jacobianRotationVector.getColumns() != N_ANGLES)) {
1639 throw new IllegalArgumentException("jacobian rotation vector must be 3x3");
1640 }
1641
1642 var alpha = com.irurueta.algebra.Utils.normF(rotationVector);
1643
1644 if (alpha > AXIS_NORM_THRESHOLD) {
1645 ArrayUtils.multiplyByScalar(rotationVector, 1.0 / alpha, axis);
1646
1647 if (jacobianAlpha != null) {
1648 jacobianAlpha.setSubmatrix(0, 0, 0,
1649 axis.length - 1, axis);
1650 }
1651
1652 if (jacobianRotationVector != null) {
1653 jacobianRotationVector.setElementAt(0, 0, 1.0 / alpha - axis[0] * axis[0] / alpha);
1654 jacobianRotationVector.setElementAt(1, 0, -axis[0] / alpha * axis[1]);
1655 jacobianRotationVector.setElementAt(2, 0, -axis[0] / alpha * axis[2]);
1656
1657 jacobianRotationVector.setElementAt(0, 1, -axis[0] / alpha * axis[1]);
1658 jacobianRotationVector.setElementAt(1, 1, 1.0 / alpha - axis[1] * axis[1] / alpha);
1659 jacobianRotationVector.setElementAt(2, 1, -axis[1] / alpha * axis[2]);
1660
1661 jacobianRotationVector.setElementAt(0, 2, -axis[0] / alpha * axis[2]);
1662 jacobianRotationVector.setElementAt(1, 2, -axis[1] / alpha * axis[2]);
1663 jacobianRotationVector.setElementAt(2, 2, 1.0 / alpha - axis[2] * axis[2] / alpha);
1664 }
1665
1666 } else {
1667 alpha = 0.0;
1668 Arrays.fill(axis, 0.0);
1669
1670 if (jacobianAlpha != null) {
1671 jacobianAlpha.initialize(0.0);
1672 }
1673
1674 if (jacobianRotationVector != null) {
1675 jacobianRotationVector.initialize(0.0);
1676 }
1677 }
1678
1679 return alpha;
1680 }
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694 public static double rotationVectorToRotationAxisAndAngle(final double[] rotationVector, final double[] axis) {
1695 return rotationVectorToRotationAxisAndAngle(rotationVector, axis, null, null);
1696 }
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711 public static void rotationVectorToQuaternion(
1712 final double[] rotationVector, final Quaternion result, final Matrix jacobian) {
1713 if (rotationVector.length != AxisRotation3D.AXIS_PARAMS) {
1714 throw new IllegalArgumentException("rotation vector length must be 3");
1715 }
1716 if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_ANGLES)) {
1717 throw new IllegalArgumentException("jacobian must be 4x3");
1718 }
1719
1720 final var axis = new double[AxisRotation3D.AXIS_PARAMS];
1721 if (jacobian == null) {
1722 final var alpha = rotationVectorToRotationAxisAndAngle(rotationVector, axis);
1723 result.setFromAxisAndRotation(axis, alpha);
1724 } else {
1725 var alpha = com.irurueta.algebra.Utils.normF(rotationVector);
1726
1727 if (alpha < LARGE_AXIS_NORM_THRESHOLD) {
1728
1729 result.a = 1 - alpha * alpha / 8.0;
1730 result.b = rotationVector[0] / 2.0;
1731 result.c = rotationVector[1] / 2.0;
1732 result.d = rotationVector[2] / 2.0;
1733 result.normalized = false;
1734
1735 jacobian.setElementAt(0, 0, -0.25 * rotationVector[0]);
1736 jacobian.setElementAt(0, 1, -0.25 * rotationVector[1]);
1737 jacobian.setElementAt(0, 2, -0.25 * rotationVector[2]);
1738
1739 jacobian.setElementAt(1, 0, 0.5);
1740 jacobian.setElementAt(1, 1, 0.0);
1741 jacobian.setElementAt(1, 2, 0.0);
1742
1743 jacobian.setElementAt(2, 0, 0.0);
1744 jacobian.setElementAt(2, 1, 0.5);
1745 jacobian.setElementAt(2, 2, 0.0);
1746
1747 jacobian.setElementAt(3, 0, 0.0);
1748 jacobian.setElementAt(3, 1, 0.0);
1749 jacobian.setElementAt(3, 2, 0.5);
1750 } else {
1751 try {
1752
1753 final var jacobianAlpha = new Matrix(1, N_ANGLES);
1754
1755 final var jacobianRotationVector = new Matrix(N_ANGLES, N_ANGLES);
1756 alpha = rotationVectorToRotationAxisAndAngle(rotationVector, axis, jacobianAlpha,
1757 jacobianRotationVector);
1758
1759
1760 final var jacobianOfTheta = new Matrix(N_PARAMS, 1);
1761
1762 final var jacobianOfAxis = new Matrix(N_PARAMS, N_ANGLES);
1763 result.setFromAxisAndRotation(axis, alpha, jacobianOfTheta, jacobianOfAxis);
1764
1765
1766
1767 jacobianOfTheta.multiply(jacobianAlpha);
1768
1769 jacobianOfAxis.multiply(jacobianRotationVector);
1770
1771 jacobian.copyFrom(jacobianOfTheta);
1772 jacobian.add(jacobianOfAxis);
1773
1774 } catch (final WrongSizeException e) {
1775 throw new IllegalArgumentException(e);
1776 }
1777 }
1778 }
1779 }
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791 public static void rotationVectorToQuaternion(final double[] rotationVector, final Quaternion result) {
1792 rotationVectorToQuaternion(rotationVector, result, null);
1793 }
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806 public void setFromRotationVector(final double[] rotationVector) {
1807 rotationVectorToQuaternion(rotationVector, this);
1808 }
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822 public static void rotationVectorToMatrixRotation(final double[] rotationVector, final Matrix result) {
1823 final var axis = new double[AxisRotation3D.AXIS_PARAMS];
1824 final var alpha = rotationVectorToRotationAxisAndAngle(rotationVector, axis);
1825 final var r = new AxisRotation3D(axis, alpha);
1826 r.asInhomogeneousMatrix(result);
1827 }
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841 public static void rotationVectorToMatrixRotation(final double[] rotationVector, final MatrixRotation3D result) {
1842 rotationVectorToMatrixRotation(rotationVector, result.internalMatrix);
1843 }
1844
1845
1846
1847
1848
1849
1850 @Override
1851 public Rotation3DType getType() {
1852 return Rotation3DType.QUATERNION;
1853 }
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867 @Override
1868 public void setAxisAndRotation(
1869 final double axisX, final double axisY, final double axisZ, final double theta) {
1870 setFromAxisAndRotation(axisX, axisY, axisZ, theta);
1871 }
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881 @Override
1882 public void rotationAxis(final double[] axis) {
1883 toAxisAndRotationAngle(axis);
1884 }
1885
1886
1887
1888
1889
1890
1891
1892 @Override
1893 public double getRotationAngle() {
1894
1895 final var n = Math.sqrt(b * b + c * c + d * d);
1896 return 2.0 * Math.atan2(n, a);
1897 }
1898
1899
1900
1901
1902
1903
1904
1905
1906 @Override
1907 public Matrix asInhomogeneousMatrix() {
1908 Matrix m = null;
1909 try {
1910 m = new Matrix(MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS,
1911 MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS);
1912 toMatrixRotation(m);
1913 } catch (final WrongSizeException ignore) {
1914
1915 }
1916 return m;
1917 }
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927 @Override
1928 public void asInhomogeneousMatrix(final Matrix result) {
1929 toMatrixRotation(result);
1930 }
1931
1932
1933
1934
1935
1936
1937 @Override
1938 public Matrix asHomogeneousMatrix() {
1939 Matrix m = null;
1940 try {
1941 m = new Matrix(HOM_COORDS, HOM_COORDS);
1942 asHomogeneousMatrix(m);
1943 } catch (final WrongSizeException ignore) {
1944
1945 }
1946 return m;
1947 }
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957 @Override
1958 public void asHomogeneousMatrix(final Matrix result) {
1959 result.initialize(0.0);
1960 result.setElementAt(HOM_COORDS - 1, HOM_COORDS - 1, 1.0);
1961 result.setSubmatrix(0, 0, INHOM_COORDS - 1,
1962 INHOM_COORDS - 1, asInhomogeneousMatrix());
1963 }
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977 @Override
1978 public void fromInhomogeneousMatrix(final Matrix m, final double threshold) {
1979 setFromMatrixRotation(m);
1980 }
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995 @Override
1996 public void fromHomogeneousMatrix(final Matrix m, final double threshold) {
1997 setFromMatrixRotation(m.getSubmatrix(0, 0, INHOM_COORDS - 1,
1998 INHOM_COORDS - 1));
1999 }
2000
2001
2002
2003
2004
2005
2006
2007 public static void inverse(final Quaternion q, final Quaternion result) {
2008
2009 final var sqrNorm = q.a * q.a + q.b * q.b + q.c * q.c + q.d * q.d;
2010 q.conjugate(result);
2011 result.a /= sqrNorm;
2012 result.b /= sqrNorm;
2013 result.c /= sqrNorm;
2014 result.d /= sqrNorm;
2015 result.normalized = false;
2016 }
2017
2018
2019
2020
2021
2022
2023
2024 public static Quaternion inverseAndReturnNew(final Quaternion q) {
2025 final var result = new Quaternion();
2026 inverse(q, result);
2027 return result;
2028 }
2029
2030
2031
2032
2033
2034
2035 public void inverse(final Quaternion result) {
2036 inverse(this, result);
2037 }
2038
2039
2040
2041
2042
2043
2044 public Quaternion inverseAndReturnNew() {
2045 final var result = new Quaternion();
2046 inverse(result);
2047 return result;
2048 }
2049
2050
2051
2052
2053 public void inverse() {
2054 inverse(this);
2055 }
2056
2057
2058
2059
2060
2061
2062
2063
2064 @Override
2065 public Rotation3D inverseRotationAndReturnNew() {
2066 final var q = new Quaternion();
2067 inverseRotation(q);
2068 return q;
2069 }
2070
2071
2072
2073
2074
2075
2076 public void inverseRotation(final Quaternion result) {
2077 inverse(result);
2078 }
2079
2080
2081
2082
2083
2084
2085
2086
2087 @Override
2088 public void inverseRotation(final Rotation3D result) {
2089 final var inverse = inverseAndReturnNew();
2090 result.fromRotation(inverse);
2091 }
2092
2093
2094
2095
2096 @Override
2097 public void inverseRotation() {
2098 inverse();
2099 }
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109 public static void combine(final Quaternion q1, final Quaternion q2, final Quaternion result) {
2110 product(q1, q2, result);
2111 }
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121 public Quaternion combineAndReturnNew(final Quaternion q) {
2122 final var result = new Quaternion();
2123 combine(this, q, result);
2124 return result;
2125 }
2126
2127
2128
2129
2130
2131
2132
2133 public void combine(final Quaternion q) {
2134 combine(this, q, this);
2135 }
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145 @Override
2146 public Rotation3D combineAndReturnNew(final Rotation3D rotation) {
2147 return combineAndReturnNew(rotation.toQuaternion());
2148 }
2149
2150
2151
2152
2153
2154
2155
2156 @Override
2157 public void combine(final Rotation3D rotation) {
2158 combine(rotation.toQuaternion());
2159 }
2160
2161
2162
2163
2164
2165
2166 @Override
2167 public void fromRotation(final MatrixRotation3D rot) {
2168 setFromMatrixRotation(rot);
2169 }
2170
2171
2172
2173
2174
2175
2176 @Override
2177 public void fromRotation(final AxisRotation3D rot) {
2178 setFromAxisAndRotation(rot);
2179 }
2180
2181
2182
2183
2184
2185
2186 @Override
2187 public void fromRotation(final Quaternion q) {
2188 a = q.a;
2189 b = q.b;
2190 c = q.c;
2191 d = q.d;
2192 normalized = q.normalized;
2193 }
2194
2195
2196
2197
2198
2199
2200
2201 @Override
2202 public void toQuaternion(final Quaternion result) {
2203 result.fromQuaternion(this);
2204 }
2205
2206
2207
2208
2209
2210
2211 public boolean isNormalized() {
2212 return normalized;
2213 }
2214
2215
2216
2217
2218 public void normalize() {
2219 if (!normalized) {
2220 final var norm = Math.sqrt(a * a + b * b + c * c + d * d);
2221 internalNormalize(norm);
2222 }
2223 }
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233 public void normalize(final Matrix jacobian) {
2234 if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_PARAMS)) {
2235 throw new IllegalArgumentException("jacobian must be 4x4");
2236 }
2237
2238 final var aValue = this.a;
2239 final var bValue = this.b;
2240 final var cValue = this.c;
2241 final var dValue = this.d;
2242 final var norm = Math.sqrt(aValue * aValue + bValue * bValue + cValue * cValue + dValue * dValue);
2243
2244 internalNormalize(norm);
2245
2246 if (jacobian != null) {
2247 final var norm3 = norm * norm * norm;
2248
2249 jacobian.setElementAt(0, 0, (bValue * bValue + cValue * cValue + dValue * dValue)
2250 / norm3);
2251 jacobian.setElementAt(1, 0, -aValue / norm3 * bValue);
2252 jacobian.setElementAt(2, 0, -aValue / norm3 * cValue);
2253 jacobian.setElementAt(3, 0, -aValue / norm3 * dValue);
2254
2255 jacobian.setElementAt(0, 1, -aValue / norm3 * bValue);
2256 jacobian.setElementAt(1, 1, (aValue * aValue + cValue * cValue + dValue * dValue)
2257 / norm3);
2258 jacobian.setElementAt(2, 1, -bValue / norm3 * cValue);
2259 jacobian.setElementAt(3, 1, -bValue / norm3 * dValue);
2260
2261 jacobian.setElementAt(0, 2, -aValue / norm3 * cValue);
2262 jacobian.setElementAt(1, 2, -bValue / norm3 * cValue);
2263 jacobian.setElementAt(2, 2, (aValue * aValue + bValue * bValue + dValue * dValue)
2264 / norm3);
2265 jacobian.setElementAt(3, 2, -cValue / norm3 * dValue);
2266
2267 jacobian.setElementAt(0, 3, -aValue / norm3 * dValue);
2268 jacobian.setElementAt(1, 3, -bValue / norm3 * dValue);
2269 jacobian.setElementAt(2, 3, -cValue / norm3 * dValue);
2270 jacobian.setElementAt(3, 3, (aValue * aValue + bValue * bValue + cValue * cValue)
2271 / norm3);
2272 }
2273 }
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288 public Quaternion slerpAndReturnNew(final Quaternion q, final double t) {
2289 final var result = new Quaternion();
2290 slerp(q, t, result);
2291 return result;
2292 }
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308 public void slerp(final Quaternion q, final double t, final Quaternion result) {
2309 slerp(this, q, t, result);
2310 }
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326 public static Quaternion slerpAndReturnNew(final Quaternion q1, final Quaternion q2, final double t) {
2327 final var result = new Quaternion();
2328 slerp(q1, q2, t, result);
2329 return result;
2330 }
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346 public static void slerp(final Quaternion q1, final Quaternion q2, final double t, final Quaternion result) {
2347 if (t < 0.0 || t > 1.0) {
2348 throw new IllegalArgumentException();
2349 }
2350
2351
2352
2353 q1.normalize();
2354 q2.normalize();
2355
2356
2357 var dot = q1.a * q2.a + q1.b * q2.b + q1.c * q2.c + q1.d * q2.d;
2358
2359
2360
2361
2362 if (Math.abs(dot) >= 1.0) {
2363 result.a = q1.a;
2364 result.b = q1.b;
2365 result.c = q1.c;
2366 result.d = q1.d;
2367 return;
2368 }
2369
2370
2371
2372
2373 Quaternion q2b;
2374 if (dot < 0.0) {
2375 q2b = new Quaternion(-q2.a, -q2.b, -q2.c, -q2.d);
2376 dot = -dot;
2377 } else {
2378 q2b = q2;
2379 }
2380
2381 final var theta0 = Math.acos(dot);
2382 final var theta = theta0 * t;
2383 final var sinTheta = Math.sin(theta);
2384 final var sinTheta0 = Math.sin(theta0);
2385
2386 final var s2 = sinTheta / sinTheta0;
2387
2388 final var s1 = Math.cos(theta) - dot * s2;
2389
2390 result.a = s1 * q1.a + s2 * q2b.a;
2391 result.b = s1 * q1.b + s2 * q2b.b;
2392 result.c = s1 * q1.c + s2 * q2b.c;
2393 result.d = s1 * q1.d + s2 * q2b.d;
2394 }
2395
2396
2397
2398
2399
2400
2401 private void internalNormalize(final double norm) {
2402 if (!normalized) {
2403 a /= norm;
2404 b /= norm;
2405 c /= norm;
2406 d /= norm;
2407 normalized = true;
2408 }
2409 }
2410 }