@@ -24,6 +24,10 @@ enum class MatrixColumn {
24
24
X , Y , Z , W
25
25
}
26
26
27
+ enum class RotationsOrder {
28
+ XYZ , XZY , YXZ , YZX , ZXY , ZYX
29
+ }
30
+
27
31
data class Mat2 (
28
32
var x : Float2 = Float2 (x = 1.0f),
29
33
var y : Float2 = Float2 (y = 1.0f)) {
@@ -41,14 +45,14 @@ data class Mat2(
41
45
fun identity () = Mat2 ()
42
46
}
43
47
44
- operator fun get (column : Int ) = when (column) {
48
+ operator fun get (column : Int ) = when (column) {
45
49
0 -> x
46
50
1 -> y
47
51
else -> throw IllegalArgumentException (" column must be in 0..1" )
48
52
}
49
53
operator fun get (column : Int , row : Int ) = get(column)[row]
50
54
51
- operator fun get (column : MatrixColumn ) = when (column) {
55
+ operator fun get (column : MatrixColumn ) = when (column) {
52
56
MatrixColumn .X -> x
53
57
MatrixColumn .Y -> y
54
58
else -> throw IllegalArgumentException (" column must be X or Y" )
@@ -124,15 +128,15 @@ data class Mat3(
124
128
fun identity () = Mat3 ()
125
129
}
126
130
127
- operator fun get (column : Int ) = when (column) {
131
+ operator fun get (column : Int ) = when (column) {
128
132
0 -> x
129
133
1 -> y
130
134
2 -> z
131
135
else -> throw IllegalArgumentException (" column must be in 0..2" )
132
136
}
133
137
operator fun get (column : Int , row : Int ) = get(column)[row]
134
138
135
- operator fun get (column : MatrixColumn ) = when (column) {
139
+ operator fun get (column : MatrixColumn ) = when (column) {
136
140
MatrixColumn .X -> x
137
141
MatrixColumn .Y -> y
138
142
MatrixColumn .Z -> z
@@ -263,7 +267,7 @@ data class Mat4(
263
267
inline val upperLeft: Mat3
264
268
get() = Mat3 (x.xyz, y.xyz, z.xyz)
265
269
266
- operator fun get (column : Int ) = when (column) {
270
+ operator fun get (column : Int ) = when (column) {
267
271
0 -> x
268
272
1 -> y
269
273
2 -> z
@@ -272,7 +276,7 @@ data class Mat4(
272
276
}
273
277
operator fun get (column : Int , row : Int ) = get(column)[row]
274
278
275
- operator fun get (column : MatrixColumn ) = when (column) {
279
+ operator fun get (column : MatrixColumn ) = when (column) {
276
280
MatrixColumn .X -> x
277
281
MatrixColumn .Y -> y
278
282
MatrixColumn .Z -> z
@@ -333,6 +337,8 @@ data class Mat4(
333
337
x.w * v.x + y.w * v.y + z.w * v.z+ w.w * v.w
334
338
)
335
339
340
+ fun toQuaternion () = quaternion(this )
341
+
336
342
fun toFloatArray () = floatArrayOf(
337
343
x.x, y.x, z.x, w.x,
338
344
x.y, y.y, z.y, w.y,
@@ -468,18 +474,90 @@ fun translation(t: Float3) = Mat4(w = Float4(t, 1.0f))
468
474
fun translation (m : Mat4 ) = translation(m.translation)
469
475
470
476
fun rotation (m : Mat4 ) = Mat4 (normalize(m.right), normalize(m.up), normalize(m.forward))
471
- fun rotation (d : Float3 ): Mat4 {
477
+
478
+ /* *
479
+ * Construct a rotation matrix from Euler angles using YPR around a specified order
480
+ *
481
+ * Uses intrinsic Tait-Bryan angles. This means that rotations are performed with respect to the
482
+ * local coordinate system.
483
+ * That is, for order 'XYZ', the rotation is first around the X axis (which is the same as the
484
+ * world-X axis), then around local-Y (which may now be different from the world Y-axis),
485
+ * then local-Z (which may be different from the world Z-axis)
486
+ *
487
+ * @param d Per axis Euler angles in degrees
488
+ * Yaw, pitch, roll (YPR) are taken accordingly to the rotations order input.
489
+ * @param order The order in which to apply rotations.
490
+ * Default is [RotationsOrder.ZYX] which means that the object will first be rotated around its Z
491
+ * axis, then its Y axis and finally its X axis.
492
+ *
493
+ * @return The rotation matrix
494
+ */
495
+ fun rotation (d : Float3 , order : RotationsOrder = RotationsOrder .ZYX ): Mat4 {
472
496
val r = transform(d, ::radians)
473
- val c = transform(r) { x -> cos(x) }
474
- val s = transform(r) { x -> sin(x) }
497
+ return when (order) {
498
+ RotationsOrder .XZY -> rotation(r.x, r.z, r.y)
499
+ RotationsOrder .XYZ -> rotation(r.x, r.y, r.z)
500
+ RotationsOrder .YXZ -> rotation(r.y, r.x, r.z)
501
+ RotationsOrder .YZX -> rotation(r.y, r.z, r.x)
502
+ RotationsOrder .ZYX -> rotation(r.z, r.y, r.x)
503
+ RotationsOrder .ZXY -> rotation(r.z, r.x, r.y)
504
+ }
505
+ }
475
506
476
- return Mat4 .of(
477
- c.y * c.z, - c.x * s.z + s.x * s.y * c.z, s.x * s.z + c.x * s.y * c.z, 0.0f ,
478
- c.y * s.z, c.x * c.z + s.x * s.y * s.z, - s.x * c.z + c.x * s.y * s.z, 0.0f ,
479
- - s.y, s.x * c.y, c.x * c.y, 0.0f ,
480
- 0.0f , 0.0f , 0.0f , 1.0f
481
- )
507
+ /* *
508
+ * Construct a rotation matrix from Euler yaw, pitch, roll around a specified order.
509
+ *
510
+ * @param roll about 1st rotation axis in radians. Z in case of ZYX order
511
+ * @param pitch about 2nd rotation axis in radians. Y in case of ZYX order
512
+ * @param yaw about 3rd rotation axis in radians. X in case of ZYX order
513
+ * @param order The order in which to apply rotations.
514
+ * Default is [RotationsOrder.ZYX] which means that the object will first be rotated around its Z
515
+ * axis, then its Y axis and finally its X axis.
516
+ *
517
+ * @return The rotation matrix
518
+ */
519
+ fun rotation (yaw : Float = 0.0f, pitch : Float = 0.0f, roll : Float = 0.0f, order : RotationsOrder = RotationsOrder .ZYX ): Mat4 {
520
+ val c1 = cos(yaw)
521
+ val s1 = sin(yaw)
522
+ val c2 = cos(pitch)
523
+ val s2 = sin(pitch)
524
+ val c3 = cos(roll)
525
+ val s3 = sin(roll)
526
+
527
+ return when (order) {
528
+ RotationsOrder .XZY -> Mat4 .of(
529
+ c2 * c3, - s2, c2 * s3, 0.0f ,
530
+ s1 * s3 + c1 * c3 * s2, c1 * c2, c1 * s2 * s3 - c3 * s1, 0.0f ,
531
+ c3 * s1 * s2 - c1 * s3, c2 * s1, c1 * c3 + s1 * s2 * s3, 0.0f ,
532
+ 0.0f , 0.0f , 0.0f , 1.0f )
533
+ RotationsOrder .XYZ -> Mat4 .of(
534
+ c2 * c3, - c2 * s3, s2, 0.0f ,
535
+ c1 * s3 + c3 * s1 * s2, c1 * c3 - s1 * s2 * s3, - c2 * s1, 0.0f ,
536
+ s1 * s3 - c1 * c3 * s2, c3 * s1 + c1 * s2 * s3, c1 * c2, 0.0f ,
537
+ 0.0f , 0.0f , 0.0f , 1.0f )
538
+ RotationsOrder .YXZ -> Mat4 .of(
539
+ c1 * c3 + s1 * s2 * s3, c3 * s1 * s2 - c1 * s3, c2 * s1, 0.0f ,
540
+ c2 * s3, c2 * c3, - s2, 0.0f ,
541
+ c1 * s2 * s3 - c3 * s1, c1 * c3 * s2 + s1 * s3, c1 * c2, 0.0f ,
542
+ 0.0f , 0.0f , 0.0f , 1.0f )
543
+ RotationsOrder .YZX -> Mat4 .of(
544
+ c1 * c2, s1 * s3 - c1 * c3 * s2, c3 * s1 + c1 * s2 * s3, 0.0f ,
545
+ s2, c2 * c3, - c2 * s3, 0.0f ,
546
+ - c2 * s1, c1 * s3 + c3 * s1 * s2, c1 * c3 - s1 * s2 * s3, 0.0f ,
547
+ 0.0f , 0.0f , 0.0f , 1.0f )
548
+ RotationsOrder .ZYX -> Mat4 .of(
549
+ c1 * c2, c1 * s2 * s3 - c3 * s1, s1 * s3 + c1 * c3 * s2, 0.0f ,
550
+ c2 * s1, c1 * c3 + s1 * s2 * s3, c3 * s1 * s2 - c1 * s3, 0.0f ,
551
+ - s2, c2 * s3, c2 * c3, 0.0f ,
552
+ 0.0f , 0.0f , 0.0f , 1.0f )
553
+ RotationsOrder .ZXY -> Mat4 .of(
554
+ c1 * c3 - s1 * s2 * s3, - c2 * s1, c1 * s3 + c3 * s1 * s2, 0.0f ,
555
+ c3 * s1 + c1 * s2 * s3, c1 * c2, s1 * s3 - c1 * c3 * s2, 0.0f ,
556
+ - c2 * s3, s2, c2 * c3, 0.0f ,
557
+ 0.0f , 0.0f , 0.0f , 1.0f )
558
+ }
482
559
}
560
+
483
561
fun rotation (axis : Float3 , angle : Float ): Mat4 {
484
562
val x = axis.x
485
563
val y = axis.y
@@ -498,6 +576,81 @@ fun rotation(axis: Float3, angle: Float): Mat4 {
498
576
)
499
577
}
500
578
579
+ /* *
580
+ * Construct a Quaternion Rotation Matrix following the Hamilton convention
581
+ *
582
+ * Assume the destination and local coordinate spaces are initially aligned, and the local
583
+ * coordinate space is then rotated counter-clockwise about a unit-length axis, k, by an angle,
584
+ * theta.
585
+ */
586
+ fun rotation (quaternion : Quaternion ): Mat4 {
587
+ val n = normalize(quaternion)
588
+ return Mat4 (
589
+ Float4 (
590
+ 1.0f - 2.0f * (n.y * n.y + n.z * n.z),
591
+ 2.0f * (n.x * n.y - n.z * n.w),
592
+ 2.0f * (n.x * n.z + n.y * n.w)
593
+ ),
594
+ Float4 (
595
+ 2.0f * (n.x * n.y + n.z * n.w),
596
+ 1.0f - 2.0f * (n.x * n.x + n.z * n.z),
597
+ 2.0f * (n.y * n.z - n.x * n.w)
598
+ ),
599
+ Float4 (
600
+ 2.0f * (n.x * n.z - n.y * n.w),
601
+ 2.0f * (n.y * n.z + n.x * n.w),
602
+ 1.0f - 2.0f * (n.x * n.x + n.y * n.y)
603
+ )
604
+ )
605
+ }
606
+
607
+ /* *
608
+ * Extract Quaternion rotation from a Matrix
609
+ */
610
+ fun quaternion (m : Mat4 ): Quaternion {
611
+ val trace = m.x.x + m.y.y + m.z.z
612
+ return normalize(
613
+ when {
614
+ trace > 0 -> {
615
+ val s = sqrt(trace + 1.0f ) * 2.0f
616
+ Quaternion (
617
+ (m.z.y - m.y.z) / s,
618
+ (m.x.z - m.z.x) / s,
619
+ (m.y.x - m.x.y) / s,
620
+ 0.25f * s
621
+ )
622
+ }
623
+ m.x.x > m.y.y && m.x.x > m.z.z -> {
624
+ val s = sqrt(1.0f + m.x.x - m.y.y - m.z.z) * 2.0f
625
+ Quaternion (
626
+ 0.25f * s,
627
+ (m.x.y + m.y.x) / s,
628
+ (m.x.z + m.z.x) / s,
629
+ (m.z.y - m.y.z) / s
630
+ )
631
+ }
632
+ m.y.y > m.z.z -> {
633
+ val s = sqrt(1.0f + m.y.y - m.x.x - m.z.z) * 2.0f
634
+ Quaternion (
635
+ (m.x.y + m.y.x) / s,
636
+ 0.25f * s,
637
+ (m.y.z + m.z.y) / s,
638
+ (m.x.z - m.z.x) / s
639
+ )
640
+ }
641
+ else -> {
642
+ val s = sqrt(1.0f + m.z.z - m.x.x - m.y.y) * 2.0f
643
+ Quaternion (
644
+ (m.y.x - m.x.y) / s,
645
+ (m.x.z + m.z.x) / s,
646
+ (m.y.z + m.z.y) / s,
647
+ 0.25f * s
648
+ )
649
+ }
650
+ }
651
+ )
652
+ }
653
+
501
654
fun normal (m : Mat4 ) = scale(1.0f / Float3 (length2(m.right), length2(m.up), length2(m.forward))) * m
502
655
503
656
fun lookAt (eye : Float3 , target : Float3 , up : Float3 = Float3 (z = 1.0f)): Mat4 {
0 commit comments