1 module polyplex.math.quaternion; 2 import polyplex.math.vector; 3 static import Mathf = polyplex.math.mathf; 4 5 import std.traits; 6 7 alias Quaternion = QuaternionT!float; 8 /** 9 A quaternion is a way of representing a rotation in three dimensions by 10 storing a rotation and an angle. It properly forms a topological 3-sphere, 11 which allows it to avoid the gimbal lock that plagues the more 12 human-readable euler angle. 13 Read more here: 14 https://euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/geometric/orthogonal 15 **/ 16 struct QuaternionT(T) if (__traits(isFloating, T)) { 17 /// Holds rotation (xyz) and angle (w) 18 public Vector4T!T data; 19 20 public immutable alias Type = T; 21 22 // convert to string Q<x, y, z, w> 23 public alias ToString = toString; 24 public string toString() { 25 return "Q" ~ data.toString(); 26 } 27 28 // -- Accessors, just call data vector 29 public @property auto opDispatch(string swizzle_list, U=void)() pure const nothrow { 30 return data.opDispatch!(swizzle_list); 31 } 32 public @property auto opDispatch(string swizzle_list, U)(U x) pure nothrow { 33 return data.opDispatch!(swizzle_list, U)(x); 34 } 35 unittest { 36 assert(__traits(compiles, QuaternionT!T(float3(0.2f), 0.2f).xy)); 37 assert(QuaternionT!T(Vector4T!T(4)).xwz == Vector3T!T(4)); 38 } 39 40 /// Constructs a quaternion from a vector 41 public this(Vector4T!T vec) pure nothrow { data = vec; } 42 43 /// Constructs a quaternion from another quaternion 44 public this(QuaternionT!T quat) pure nothrow { data = quat.data; } 45 46 /// Constructs a quaternion from an axis and an angle theta 47 public this(Vector3T!T axis, T theta) pure nothrow { 48 // adjust axis and theta 49 axis = axis.Normalize(); 50 theta = theta/cast(T)2; 51 T sin_theta = Mathf.Sin(theta); 52 // Set data 53 data.w = Mathf.Cos(theta); 54 data.x = sin_theta*axis.x; 55 data.y = sin_theta*axis.x; 56 data.z = sin_theta*axis.x; 57 } 58 unittest { 59 assert(__traits(compiles, QuaternionT!T(Vector3T!T(0.3f), 0.5f))); 60 } 61 62 /// Constructs identity of a quaternion 63 public static QuaternionT!T Identity() pure nothrow { 64 QuaternionT!T q; 65 q.x = q.y = q.z = 0; 66 q.w = 1; 67 return q; 68 } 69 70 /// Constructs a quaternion from euler angle 71 public static QuaternionT!T ToQuaternion(T pitch, T roll, T yaw) pure nothrow { 72 QuaternionT!T q; 73 // adjust angles 74 pitch *= cast(T)0.5f; 75 roll *= cast(T)0.5f; 76 yaw *= cast(T)0.5f; 77 78 T cy = Mathf.Cos(yaw), sy = Mathf.Sin(yaw); 79 T cr = Mathf.Cos(roll), sr = Mathf.Sin(roll); 80 T cp = Mathf.Cos(pitch), sp = Mathf.Sin(pitch); 81 82 q.w = cy*cr*cp + sy*sr*sp; 83 q.x = cy*sr*cp - sy*cr*sp; 84 q.y = cy*cr*sp + sy*sr*cp; 85 q.z = sy*cr*cp - cy*sr*sp; 86 87 return q; 88 } 89 90 /// Constructs a quaternion from euler angle vector 91 public static QuaternionT!T ToQuaternion(Vector3T!T vec) pure nothrow { 92 return ToQuaternion(vec.x, vec.y, vec.z); 93 } 94 95 unittest { 96 assert(__traits(compiles, ToQuaternion(Vector3T!T(0.2f)))); 97 assert(__traits(compiles, ToQuaternion(0.2f, 0.5f, 0.6f))); 98 } 99 100 /// Returns the euler angle equivalent of this quaternion, in the form of 101 /// <roll, pitch, yaw> 102 public Vector3T!T ToEulerAngle() pure const nothrow { 103 Vector3T!T euler; 104 QuaternionT!T q = this; 105 106 // calculate roll 107 T sinr = 2 * (q.w*q.x + q.y*q.z); 108 T cosr = 1 - 2*(q.x*q.x + q.y*q.y); 109 euler.X = Mathf.ATan2(sinr, cosr); 110 111 // calculate pitch 112 T sinp = Mathf.Clamp(2*(q.w*q.y - q.z*q.x), cast(T)-1, cast(T)1); 113 euler.Y = Mathf.ASin(sinp); 114 115 // calculate yaw 116 T siny = 2 * (q.w*q.z + q.x*q.y); 117 T cosy = 1 - 2*(q.y*q.y + q.z*q.z); 118 euler.Z = Mathf.ATan2(siny, cosy); 119 120 return euler; 121 } 122 123 /// Sets euler angle as member parameters, equivalent of this quaternion 124 public void ToEulerAngle(ref T roll, ref T pitch, ref T yaw) pure const nothrow { 125 Vector3T!T euler = ToEulerAngle(); 126 roll = euler.x; 127 pitch = euler.y; 128 yaw = euler.z; 129 } 130 unittest { 131 auto q = QuaternionT!T(Vector3T!T(0.3f), 0.5f); 132 T r, p, y; 133 assert(__traits(compiles, q.ToEulerAngle)); 134 assert(__traits(compiles, q.ToEulerAngle(r, p, y))); 135 } 136 137 /// Returns a normalized quaternion 138 public QuaternionT!T Normalize ( ) pure const nothrow { 139 return QuaternionT!T(data.Normalize); 140 } 141 unittest { 142 assert(__traits(compiles, QuaternionT!T.ToQuaternion(Vector3T!T(0.3)).Normalize)); 143 } 144 145 /// Returns a conjugated quaternion 146 public QuaternionT!T Conjugate ( ) pure const nothrow { 147 return QuaternionT!T(float4(-data.x, -data.y, -data.z, data.w)); 148 } 149 unittest { 150 assert(__traits(compiles, QuaternionT!T(Vector3T!T(0.2f), 0.4f).Normalize.Conjugate)); 151 } 152 153 // Returns a scaled quaternion 154 public QuaternionT!T Scale(T scale) pure const nothrow { 155 return QuaternionT!T(data*scale); 156 } 157 unittest { 158 assert(__traits(compiles, QuaternionT!T(Vector3T!T(0.2f), 0.4f).Scale(0.5))); 159 } 160 161 /// quaternion * quaternion 162 public QuaternionT!T opBinary(string op, U)(U rhs) pure const nothrow 163 if (is(U : QuaternionT!T) && op == "*") { 164 QuaternionT!T v; 165 QuaternionT!T lhs = this; 166 v.x = lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y + lhs.w * rhs.x; 167 v.y = -lhs.x * rhs.z + lhs.y * rhs.w + lhs.z * rhs.x + lhs.w * rhs.y; 168 v.z = lhs.x * rhs.y - lhs.y * rhs.x + lhs.z * rhs.w + lhs.w * rhs.z; 169 v.w = -lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z + lhs.w * rhs.w; 170 return v; 171 } 172 /// quaternion *= quaternion 173 public void opOpAssign(string op, U)(U rhs) pure nothrow 174 if (is(U : QuaternionT!T) && op == "*") { 175 this = this * rhs; 176 } 177 178 /// quaternion + quaternion 179 public QuaternionT!T opBinary(string op, U)(U rhs) pure const nothrow 180 if(is(U : QuaternionT!T) && op == "+") { 181 return QuaternionT!T(this.data + rhs.data); 182 } 183 /// quaternion += quaternion 184 public void opOpAssign(string op, U)(U rhs) pure nothrow 185 if (is(U : QuaternionT!T) && op == "+") { 186 this = this + rhs; 187 } 188 189 unittest { 190 QuaternionT!T q = QuaternionT!T(float4(0.4f)); 191 assert(__traits(compiles, q*q+q)); 192 q *= q; 193 q += q; 194 } 195 }