1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.numbers.quaternion;
19
20 import java.util.Arrays;
21 import java.util.function.ToDoubleFunction;
22 import java.util.function.BiPredicate;
23 import java.io.Serializable;
24 import org.apache.commons.numbers.core.Precision;
25
26
27
28
29
30
31
32
33
34
35
36 public final class Quaternion implements Serializable {
37
38 public static final Quaternion ZERO = of(0, 0, 0, 0);
39
40 public static final Quaternion ONE = new Quaternion(Type.POSITIVE_POLAR_FORM, 1, 0, 0, 0);
41
42 public static final Quaternion I = new Quaternion(Type.POSITIVE_POLAR_FORM, 0, 1, 0, 0);
43
44 public static final Quaternion J = new Quaternion(Type.POSITIVE_POLAR_FORM, 0, 0, 1, 0);
45
46 public static final Quaternion K = new Quaternion(Type.POSITIVE_POLAR_FORM, 0, 0, 0, 1);
47
48
49 private static final long serialVersionUID = 20170118L;
50
51 private static final String ILLEGAL_NORM_MSG = "Illegal norm: ";
52
53
54 private static final String FORMAT_START = "[";
55
56 private static final String FORMAT_END = "]";
57
58 private static final String FORMAT_SEP = " ";
59
60
61 private static final int VECTOR_DIMENSIONS = 3;
62
63 private static final int NUMBER_OF_PARTS = 4;
64
65
66 private final Type type;
67
68 private final double w;
69
70 private final double x;
71
72 private final double y;
73
74 private final double z;
75
76
77
78
79 private enum Type {
80
81 DEFAULT(Default.NORMSQ,
82 Default.NORM,
83 Default.IS_UNIT),
84
85 NORMALIZED(Normalized.NORM,
86 Normalized.NORM,
87 Normalized.IS_UNIT),
88
89 POSITIVE_POLAR_FORM(Normalized.NORM,
90 Normalized.NORM,
91 Normalized.IS_UNIT);
92
93
94 private final ToDoubleFunction<Quaternion> normSq;
95
96 private final ToDoubleFunction<Quaternion> norm;
97
98 private final BiPredicate<Quaternion, Double> testIsUnit;
99
100
101 private static final class Default {
102
103 static final ToDoubleFunction<Quaternion> NORMSQ = q ->
104 q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z;
105
106
107 private static final ToDoubleFunction<Quaternion> NORM = q ->
108 Math.sqrt(NORMSQ.applyAsDouble(q));
109
110
111 private static final BiPredicate<Quaternion, Double> IS_UNIT = (q, eps) ->
112 Precision.equals(NORM.applyAsDouble(q), 1d, eps);
113 }
114
115
116 private static final class Normalized {
117
118 static final ToDoubleFunction<Quaternion> NORM = q -> 1;
119
120 static final BiPredicate<Quaternion, Double> IS_UNIT = (q, eps) -> true;
121 }
122
123
124
125
126
127
128 Type(ToDoubleFunction<Quaternion> normSq,
129 ToDoubleFunction<Quaternion> norm,
130 BiPredicate<Quaternion, Double> isUnit) {
131 this.normSq = normSq;
132 this.norm = norm;
133 this.testIsUnit = isUnit;
134 }
135
136
137
138
139
140 double normSq(Quaternion q) {
141 return normSq.applyAsDouble(q);
142 }
143
144
145
146
147 double norm(Quaternion q) {
148 return norm.applyAsDouble(q);
149 }
150
151
152
153
154
155 boolean isUnit(Quaternion q,
156 double eps) {
157 return testIsUnit.test(q, eps);
158 }
159 }
160
161
162
163
164
165
166
167
168
169
170 private Quaternion(Type type,
171 final double w,
172 final double x,
173 final double y,
174 final double z) {
175 this.type = type;
176 this.w = w;
177 this.x = x;
178 this.y = y;
179 this.z = z;
180 }
181
182
183
184
185
186
187
188 private Quaternion(Type type,
189 Quaternion q) {
190 this.type = type;
191 w = q.w;
192 x = q.x;
193 y = q.y;
194 z = q.z;
195 }
196
197
198
199
200
201
202
203
204
205
206 public static Quaternion of(final double w,
207 final double x,
208 final double y,
209 final double z) {
210 return new Quaternion(Type.DEFAULT,
211 w, x, y, z);
212 }
213
214
215
216
217
218
219
220
221
222
223 public static Quaternion of(final double scalar,
224 final double[] v) {
225 if (v.length != VECTOR_DIMENSIONS) {
226 throw new IllegalArgumentException("Size of array must be 3");
227 }
228
229 return of(scalar, v[0], v[1], v[2]);
230 }
231
232
233
234
235
236
237
238
239 public static Quaternion of(final double[] v) {
240 return of(0, v);
241 }
242
243
244
245
246
247
248
249 public Quaternion conjugate() {
250 return of(w, -x, -y, -z);
251 }
252
253
254
255
256
257
258
259
260 public static Quaternion multiply(final Quaternion q1,
261 final Quaternion q2) {
262
263 final double q1a = q1.w;
264 final double q1b = q1.x;
265 final double q1c = q1.y;
266 final double q1d = q1.z;
267
268
269 final double q2a = q2.w;
270 final double q2b = q2.x;
271 final double q2c = q2.y;
272 final double q2d = q2.z;
273
274
275 final double w = q1a * q2a - q1b * q2b - q1c * q2c - q1d * q2d;
276 final double x = q1a * q2b + q1b * q2a + q1c * q2d - q1d * q2c;
277 final double y = q1a * q2c - q1b * q2d + q1c * q2a + q1d * q2b;
278 final double z = q1a * q2d + q1b * q2c - q1c * q2b + q1d * q2a;
279
280 return of(w, x, y, z);
281 }
282
283
284
285
286
287
288
289 public Quaternion multiply(final Quaternion q) {
290 return multiply(this, q);
291 }
292
293
294
295
296
297
298
299
300 public static Quaternion add(final Quaternion q1,
301 final Quaternion q2) {
302 return of(q1.w + q2.w,
303 q1.x + q2.x,
304 q1.y + q2.y,
305 q1.z + q2.z);
306 }
307
308
309
310
311
312
313
314 public Quaternion add(final Quaternion q) {
315 return add(this, q);
316 }
317
318
319
320
321
322
323
324
325 public static Quaternion subtract(final Quaternion q1,
326 final Quaternion q2) {
327 return of(q1.w - q2.w,
328 q1.x - q2.x,
329 q1.y - q2.y,
330 q1.z - q2.z);
331 }
332
333
334
335
336
337
338
339 public Quaternion subtract(final Quaternion q) {
340 return subtract(this, q);
341 }
342
343
344
345
346
347
348
349
350 public static double dot(final Quaternion q1,
351 final Quaternion q2) {
352 return q1.w * q2.w +
353 q1.x * q2.x +
354 q1.y * q2.y +
355 q1.z * q2.z;
356 }
357
358
359
360
361
362
363
364 public double dot(final Quaternion q) {
365 return dot(this, q);
366 }
367
368
369
370
371
372
373 public double norm() {
374 return type.norm(this);
375 }
376
377
378
379
380
381
382 public double normSq() {
383 return type.normSq(this);
384 }
385
386
387
388
389
390
391
392
393
394 public Quaternion normalize() {
395 switch (type) {
396 case NORMALIZED:
397 case POSITIVE_POLAR_FORM:
398 return this;
399 case DEFAULT:
400 final double norm = norm();
401
402 if (norm < Precision.SAFE_MIN ||
403 !Double.isFinite(norm)) {
404 throw new IllegalStateException(ILLEGAL_NORM_MSG + norm);
405 }
406
407 final Quaternion unit = divide(norm);
408
409 return w >= 0 ?
410 new Quaternion(Type.POSITIVE_POLAR_FORM, unit) :
411 new Quaternion(Type.NORMALIZED, unit);
412 default:
413 throw new IllegalStateException();
414 }
415 }
416
417
418
419
420 @Override
421 public boolean equals(Object other) {
422 if (this == other) {
423 return true;
424 }
425 if (other instanceof Quaternion) {
426 final Quaternion q = (Quaternion) other;
427 return ((Double) w).equals(q.w) &&
428 ((Double) x).equals(q.x) &&
429 ((Double) y).equals(q.y) &&
430 ((Double) z).equals(q.z);
431 }
432
433 return false;
434 }
435
436
437
438
439 @Override
440 public int hashCode() {
441 return Arrays.hashCode(new double[] {w, x, y, z});
442 }
443
444
445
446
447
448
449
450
451
452
453 public boolean equals(final Quaternion q,
454 final double eps) {
455 return Precision.equals(w, q.w, eps) &&
456 Precision.equals(x, q.x, eps) &&
457 Precision.equals(y, q.y, eps) &&
458 Precision.equals(z, q.z, eps);
459 }
460
461
462
463
464
465
466
467
468
469 public boolean isUnit(double eps) {
470 return type.isUnit(this, eps);
471 }
472
473
474
475
476
477
478
479
480 public boolean isPure(double eps) {
481 return Math.abs(w) <= eps;
482 }
483
484
485
486
487
488
489 public Quaternion positivePolarForm() {
490 switch (type) {
491 case POSITIVE_POLAR_FORM:
492 return this;
493 case NORMALIZED:
494 return w >= 0 ?
495 new Quaternion(Type.POSITIVE_POLAR_FORM, this) :
496 new Quaternion(Type.POSITIVE_POLAR_FORM, negate());
497 case DEFAULT:
498 return w >= 0 ?
499 normalize() :
500
501
502 negate().normalize();
503 default:
504 throw new IllegalStateException();
505 }
506 }
507
508
509
510
511
512
513
514 public Quaternion negate() {
515 switch (type) {
516 case POSITIVE_POLAR_FORM:
517 case NORMALIZED:
518 return new Quaternion(Type.NORMALIZED, -w, -x, -y, -z);
519 case DEFAULT:
520 return new Quaternion(Type.DEFAULT, -w, -x, -y, -z);
521 default:
522 throw new IllegalStateException();
523 }
524 }
525
526
527
528
529
530
531
532
533
534 public Quaternion inverse() {
535 switch (type) {
536 case POSITIVE_POLAR_FORM:
537 case NORMALIZED:
538 return new Quaternion(type, w, -x, -y, -z);
539 case DEFAULT:
540 final double squareNorm = normSq();
541 if (squareNorm < Precision.SAFE_MIN ||
542 !Double.isFinite(squareNorm)) {
543 throw new IllegalStateException(ILLEGAL_NORM_MSG + Math.sqrt(squareNorm));
544 }
545
546 return of(w / squareNorm,
547 -x / squareNorm,
548 -y / squareNorm,
549 -z / squareNorm);
550 default:
551 throw new IllegalStateException();
552 }
553 }
554
555
556
557
558
559
560 public double getW() {
561 return w;
562 }
563
564
565
566
567
568
569
570 public double getX() {
571 return x;
572 }
573
574
575
576
577
578
579
580 public double getY() {
581 return y;
582 }
583
584
585
586
587
588
589
590 public double getZ() {
591 return z;
592 }
593
594
595
596
597
598
599
600 public double getScalarPart() {
601 return getW();
602 }
603
604
605
606
607
608
609
610
611
612 public double[] getVectorPart() {
613 return new double[] {x, y, z};
614 }
615
616
617
618
619
620
621
622 public Quaternion multiply(final double alpha) {
623 return of(alpha * w,
624 alpha * x,
625 alpha * y,
626 alpha * z);
627 }
628
629
630
631
632
633
634
635 public Quaternion divide(final double alpha) {
636 return of(w / alpha,
637 x / alpha,
638 y / alpha,
639 z / alpha);
640 }
641
642
643
644
645
646
647
648
649
650
651 public static Quaternion parse(String s) {
652 final int startBracket = s.indexOf(FORMAT_START);
653 if (startBracket != 0) {
654 throw new QuaternionParsingException("Expected start string: " + FORMAT_START);
655 }
656 final int len = s.length();
657 final int endBracket = s.indexOf(FORMAT_END);
658 if (endBracket != len - 1) {
659 throw new QuaternionParsingException("Expected end string: " + FORMAT_END);
660 }
661 final String[] elements = s.substring(1, s.length() - 1).split(FORMAT_SEP);
662 if (elements.length != NUMBER_OF_PARTS) {
663 throw new QuaternionParsingException("Incorrect number of parts: Expected 4 but was " +
664 elements.length +
665 " (separator is '" + FORMAT_SEP + "')");
666 }
667
668 final double a;
669 try {
670 a = Double.parseDouble(elements[0]);
671 } catch (NumberFormatException ex) {
672 throw new QuaternionParsingException("Could not parse scalar part" + elements[0], ex);
673 }
674 final double b;
675 try {
676 b = Double.parseDouble(elements[1]);
677 } catch (NumberFormatException ex) {
678 throw new QuaternionParsingException("Could not parse i part" + elements[1], ex);
679 }
680 final double c;
681 try {
682 c = Double.parseDouble(elements[2]);
683 } catch (NumberFormatException ex) {
684 throw new QuaternionParsingException("Could not parse j part" + elements[2], ex);
685 }
686 final double d;
687 try {
688 d = Double.parseDouble(elements[3]);
689 } catch (NumberFormatException ex) {
690 throw new QuaternionParsingException("Could not parse k part" + elements[3], ex);
691 }
692
693 return of(a, b, c, d);
694 }
695
696
697
698
699 @Override
700 public String toString() {
701 final StringBuilder s = new StringBuilder();
702 s.append(FORMAT_START)
703 .append(w).append(FORMAT_SEP)
704 .append(x).append(FORMAT_SEP)
705 .append(y).append(FORMAT_SEP)
706 .append(z)
707 .append(FORMAT_END);
708
709 return s.toString();
710 }
711
712
713 private static class QuaternionParsingException extends NumberFormatException {
714
715 private static final long serialVersionUID = 20181128L;
716
717
718
719
720 QuaternionParsingException(String msg) {
721 super(msg);
722 }
723
724
725
726
727
728 QuaternionParsingException(String msg, Throwable cause) {
729 super(msg);
730 initCause(cause);
731 }
732 }
733 }