1 module gfm.math.quaternion; 2 3 import std.math, 4 std.string; 5 6 import gfm.math.vector, 7 gfm.math.matrix, 8 funcs = gfm.math.funcs; 9 10 static if( __VERSION__ < 2066 ) private enum nogc = 1; 11 12 /// Quaternion implementation. 13 /// Holds a rotation + angle in a proper but wild space. 14 align(1) struct Quaternion(T) 15 { 16 align(1): 17 public 18 { 19 union 20 { 21 Vector!(T, 4u) v; 22 struct 23 { 24 T x, y, z, w; 25 } 26 } 27 28 /// Construct a Quaternion from a value. 29 @nogc this(U)(U x) pure nothrow if (isAssignable!U) 30 { 31 opAssign!U(x); 32 } 33 34 /// Constructs a Quaternion from coordinates. 35 /// Warning: order of coordinates is different from storage. 36 @nogc this(T qw, T qx, T qy, T qz) pure nothrow 37 { 38 x = qx; 39 y = qy; 40 z = qz; 41 w = qw; 42 } 43 44 /// Constructs a Quaternion from axis + angle. 45 @nogc static Quaternion fromAxis(Vector!(T, 3) axis, T angle) pure nothrow 46 { 47 Quaternion q = void; 48 axis.normalize(); 49 T cos_a = cos(angle / 2); 50 T sin_a = sin(angle / 2); 51 q.x = sin_a * axis.x; 52 q.y = sin_a * axis.y; 53 q.z = sin_a * axis.z; 54 q.w = cos_a; 55 return q; // should be normalized 56 } 57 58 /// Constructs a Quaternion from Euler angles. 59 /// All paramers given in radians. 60 /// Roll->X axis, Pitch->Y axis, Yaw->Z axis 61 /// See_also: $(LINK https://www.cs.princeton.edu/~gewang/projects/darth/stuff/quat_faq.html) 62 @nogc static Quaternion fromEulerAngles(T roll, T pitch, T yaw) pure nothrow 63 { 64 Quaternion q = void; 65 T sinPitch = sin(pitch / 2); 66 T cosPitch = cos(pitch / 2); 67 T sinYaw = sin(yaw / 2); 68 T cosYaw = cos(yaw / 2); 69 T sinRoll = sin(roll / 2); 70 T cosRoll = cos(roll / 2); 71 T cosPitchCosYaw = cosPitch * cosYaw; 72 T sinPitchSinYaw = sinPitch * sinYaw; 73 q.x = sinRoll * cosPitchCosYaw - cosRoll * sinPitchSinYaw; 74 q.y = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw; 75 q.z = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw; 76 q.w = cosRoll * cosPitchCosYaw + sinRoll * sinPitchSinYaw; 77 return q; 78 } 79 80 /// Converts a quaternion to Euler angles. 81 /// TODO: adds a EulerAngles type. 82 /// Returns: A vector which contains roll-pitch-yaw as x-y-z. 83 @nogc vec3!T toEulerAngles() pure const nothrow 84 { 85 mat3!T m = cast(mat3!T)(this); 86 87 T pitch, yaw, roll; 88 T s = sqrt(m.c[0][0] * m.c[0][0] + m.c[1][0] * m.c[1][0]); 89 if (s > T.epsilon) 90 { 91 pitch = - atan2(m.c[2][0], s); 92 yaw = atan2(m.c[1][0], m.c[0][0]); 93 roll = atan2(m.c[2][1], m.c[2][2]); 94 } 95 else 96 { 97 pitch = m.c[2][0] < 0.0f ? PI /2 : -PI / 2; 98 yaw = -atan2(m.c[0][1], m.c[1][1]); 99 roll = 0.0f; 100 } 101 return vec3!T(roll, pitch, yaw); 102 } 103 104 /// Assign from another Quaternion. 105 @nogc ref Quaternion opAssign(U)(U u) pure nothrow if (is(typeof(U._isQuaternion)) && is(U._T : T)) 106 { 107 v = u.v; 108 return this; 109 } 110 111 /// Assign from a vector of 4 elements. 112 @nogc ref Quaternion opAssign(U)(U u) pure nothrow if (is(U : Vector!(T, 4u))) 113 { 114 v = u; 115 return this; 116 } 117 118 /// Converts to a pretty string. 119 string toString() const nothrow 120 { 121 try 122 return format("%s", v); 123 catch (Exception e) 124 assert(false); // should not happen since format is right 125 } 126 127 /// Normalizes a quaternion. 128 @nogc void normalize() pure nothrow 129 { 130 v.normalize(); 131 } 132 133 /// Returns: Normalized quaternion. 134 @nogc Quaternion normalized() pure const nothrow 135 { 136 Quaternion res = void; 137 res.v = v.normalized(); 138 return res; 139 } 140 141 /// Inverses a quaternion in-place. 142 @nogc void inverse() pure nothrow 143 { 144 x = -x; 145 y = -y; 146 z = -z; 147 } 148 149 /// Returns: Inverse of quaternion. 150 @nogc Quaternion inversed() pure const nothrow 151 { 152 Quaternion res = void; 153 res.v = v; 154 res.inverse(); 155 return res; 156 } 157 158 @nogc ref Quaternion opOpAssign(string op, U)(U q) pure nothrow 159 if (is(U : Quaternion) && (op == "*")) 160 { 161 T nx = w * q.x + x * q.w + y * q.z - z * q.y, 162 ny = w * q.y + y * q.w + z * q.x - x * q.z, 163 nz = w * q.z + z * q.w + x * q.y - y * q.x, 164 nw = w * q.w - x * q.x - y * q.y - z * q.z; 165 x = nx; 166 y = ny; 167 z = nz; 168 w = nw; 169 return this; 170 } 171 172 @nogc ref Quaternion opOpAssign(string op, U)(U operand) pure nothrow if (isConvertible!U) 173 { 174 Quaternion conv = operand; 175 return opOpAssign!op(conv); 176 } 177 178 @nogc Quaternion opBinary(string op, U)(U operand) pure const nothrow 179 if (is(U: Quaternion) || (isConvertible!U)) 180 { 181 Quaternion temp = this; 182 return temp.opOpAssign!op(operand); 183 } 184 185 /// Compare two Quaternions. 186 bool opEquals(U)(U other) pure const if (is(U : Quaternion)) 187 { 188 return v == other.v; 189 } 190 191 /// Compare Quaternion and other types. 192 bool opEquals(U)(U other) pure const nothrow if (isConvertible!U) 193 { 194 Quaternion conv = other; 195 return opEquals(conv); 196 } 197 198 /// Convert to a 3x3 rotation matrix. 199 /// TODO: check out why we can't do is(Unqual!U == mat3!T) 200 @nogc U opCast(U)() pure const nothrow if (is(typeof(U._isMatrix)) 201 && is(U._T : _T) 202 && (U._R == 3) && (U._C == 3)) 203 { 204 // do not assume that quaternion is normalized 205 T norm = x*x + y*y + z*z + w*w; 206 T s = (norm > 0) ? 2 / norm : 0; 207 208 T xx = x * x * s, xy = x * y * s, xz = x * z * s, xw = x * w * s, 209 yy = y * y * s, yz = y * z * s, yw = y * w * s, 210 zz = z * z * s, zw = z * w * s; 211 return mat3!(U._T) 212 ( 213 1 - (yy + zz) , (xy - zw) , (xz + yw) , 214 (xy + zw) , 1 - (xx + zz) , (yz - xw) , 215 (xz - yw) , (yz + xw) , 1 - (xx + yy) 216 ); 217 } 218 219 /// Converts a to a 4x4 rotation matrix. 220 /// Bugs: check why we can't do is(Unqual!U == mat4!T) 221 @nogc U opCast(U)() pure const nothrow if (is(typeof(U._isMatrix)) 222 && is(U._T : _T) 223 && (U._R == 4) && (U._C == 4)) 224 { 225 auto m3 = cast(mat3!(U._T))(this); 226 return cast(U)(m3); 227 } 228 229 // Workaround Vector not being constructable through CTFE 230 @nogc static Quaternion identity() pure nothrow @property 231 { 232 Quaternion q; 233 q.x = q.y = q.z = 0; 234 q.w = 1; 235 return q; 236 } 237 } 238 239 private 240 { 241 alias T _T; 242 enum _isQuaternion = true; 243 244 template isAssignable(T) 245 { 246 enum bool isAssignable = 247 is(typeof( 248 { 249 T x; 250 Quaternion v = x; 251 }())); 252 } 253 254 // define types that can be converted to Quaternion, but are not Quaternion 255 template isConvertible(T) 256 { 257 enum bool isConvertible = (!is(T : Quaternion)) && isAssignable!T; 258 } 259 } 260 } 261 262 alias Quaternion!float quatf; 263 alias Quaternion!double quatd; 264 265 /// Linear interpolation, for quaternions. 266 @nogc Quaternion!T lerp(T)(Quaternion!T a, Quaternion!T b, float t) pure nothrow 267 { 268 Quaternion!T res = void; 269 res.v = funcs.lerp(a.v, b.v, t); 270 return res; 271 } 272 273 274 /// Returns: Nlerp of quaternions. 275 /// See_also: $(WEB keithmaggio.wordpress.com/2011/02/15/math-magician-lerp-slerp-and-nlerp/, Math Magician – Lerp, Slerp, and Nlerp) 276 @nogc Quaternion!T Nlerp(T)(Quaternion!T a, Quaternion!T b, float t) pure nothrow 277 { 278 assert(t >= 0 && t <= 1); // else probably doesn't make sense 279 Quaternion!T res = void; 280 res.v = funcs.lerp(a.v, b.v, t); 281 res.v.normalize(); 282 return res; 283 } 284 285 /// Returns: Slerp of quaternions. Slerp is more expensive than Nlerp. 286 /// See_also: "Understanding Slerp, Then Not Using It" 287 @nogc Quaternion!T slerp(T)(Quaternion!T a, Quaternion!T b, T t) pure nothrow 288 { 289 assert(t >= 0 && t <= 1); // else probably doesn't make sense 290 291 Quaternion!T res = void; 292 293 T dotProduct = dot(a.v, b.v); 294 295 // spherical lerp always has 2 potential paths 296 // here we always take the shortest 297 if (dotProduct < 0) 298 { 299 b.v *= -1; 300 dotProduct = dot(a.v, b.v); 301 } 302 303 immutable T threshold = 10 * T.epsilon; // idMath uses 1e-6f for 32-bits float precision 304 if ((1 - dotProduct) > threshold) // if small difference, use lerp 305 return lerp(a, b, t); 306 307 T theta_0 = funcs.safeAcos(dotProduct); // angle between this and other 308 T theta = theta_0 * t; // angle between this and result 309 310 vec3!T v2 = dot(b.v, a.v * dotProduct); 311 v2.normalize(); 312 313 res.v = dot(b.v, a.v * dotProduct); 314 res.v.normalize(); 315 316 res.v = a.v * cos(theta) + res.v * sin(theta); 317 return res; 318 } 319 320 unittest 321 { 322 quatf a = quatf.fromAxis(vec3f(1, 0, 0), 1); 323 quatf b = quatf.fromAxis(vec3f(0, 1, 0), 0); 324 a = a * b; 325 326 quatf c = lerp(a, b, 0.5f); 327 quatf d = Nlerp(a, b, 0.1f); 328 quatf e = slerp(a, b, 0.0f); 329 quatd f = quatd(1.0, 4, 5.0, 6.0); 330 quatf g = quatf.fromEulerAngles(-0.1f, 1.2f, -0.3f); 331 vec3f ga = g.toEulerAngles(); 332 } 333