1 /** 2 * Copyright: Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved 3 * Authors: $(Gabriel Gheorghe) 4 * License: $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007) 5 * Source: $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/math/quaternion.d) 6 * Documentation: 7 * Coverage: 8 **/ 9 module liberty.math.quaternion; 10 11 import liberty.math.vector; 12 import liberty.math.matrix; 13 import liberty.math.traits; 14 /// 15 struct Quaternion(T) { 16 /// 17 alias type = T; 18 union { 19 Vector!(T, 4u) v; 20 struct { 21 T x, y, z, w; 22 } 23 } 24 /// Constructs a Quaternion from a value. 25 this(U)(U x) if (isAssignable!U) { 26 opAssign!U(x); 27 } 28 /// Constructs a Quaternion from coordinates. 29 this(T qw, T qx, T qy, T qz) { 30 x = qx; 31 y = qy; 32 z = qz; 33 w = qw; 34 } 35 /// Constructs a Quaternion from axis + angle. 36 static Quaternion fromAxis(Vector!(T, 3) axis, T angle) { 37 import liberty.math.functions : sin, cos; 38 Quaternion q = void; 39 axis.normalize(); 40 T cos_a = cos(angle / 2); 41 T sin_a = sin(angle / 2); 42 q.x = sin_a * axis.x; 43 q.y = sin_a * axis.y; 44 q.z = sin_a * axis.z; 45 q.w = cos_a; 46 return q; 47 } 48 /// Constructs a Quaternion from Euler angles. 49 static Quaternion fromEulerAngles(T roll, T pitch, T yaw) { 50 import liberty.math.functions : sin, cos; 51 Quaternion q = void; 52 const T sinPitch = sin(pitch / 2); 53 const T cosPitch = cos(pitch / 2); 54 const T sinYaw = sin(yaw / 2); 55 const T cosYaw = cos(yaw / 2); 56 const T sinRoll = sin(roll / 2); 57 const T cosRoll = cos(roll / 2); 58 const T cosPitchCosYaw = cosPitch * cosYaw; 59 const T sinPitchSinYaw = sinPitch * sinYaw; 60 q.x = sinRoll * cosPitchCosYaw - cosRoll * sinPitchSinYaw; 61 q.y = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw; 62 q.z = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw; 63 q.w = cosRoll * cosPitchCosYaw + sinRoll * sinPitchSinYaw; 64 return q; 65 } 66 /// Converts a quaternion to Euler angles. 67 Vector!(T, 3) toEulerAngles() const { 68 import liberty.math.functions : atan2, sqrt, PI; 69 Matrix!(T, 3) m = cast(Matrix!(T, 3))(this); 70 T pitch, yaw, roll; 71 T s = sqrt(m.c[0][0] * m.c[0][0] + m.c[1][0] * m.c[1][0]); 72 if (s > T.epsilon) { 73 pitch = - atan2(m.c[2][0], s); 74 yaw = atan2(m.c[1][0], m.c[0][0]); 75 roll = atan2(m.c[2][1], m.c[2][2]); 76 } else { 77 pitch = m.c[2][0] < 0.0f ? PI /2 : -PI / 2; 78 yaw = -atan2(m.c[0][1], m.c[1][1]); 79 roll = 0.0f; 80 } 81 return Vector!(T, 3)(roll, pitch, yaw); 82 } 83 /// Assign from another Quaternion. 84 ref Quaternion opAssign(U)(U u) if (isQuaternionInstance!U && is(U._T : T)) { 85 v = u.v; 86 return this; 87 } 88 /// Assign from a vector of 4 elements. 89 ref Quaternion opAssign(U)(U u) if (is(U : Vector!(T, 4u))) { 90 v = u; 91 return this; 92 } 93 /// Converts to a string. 94 string toString() const { 95 import std..string : format; 96 try { 97 return format("%s", v); 98 } catch (Exception e) { 99 assert(0); 100 } 101 } 102 /// Normalizes a quaternion. 103 void normalize() { 104 v.normalize(); 105 } 106 /// Returns normalized quaternion. 107 Quaternion normalized() const { 108 Quaternion ret = void; 109 ret.v = v.normalized(); 110 return ret; 111 } 112 /// Inverses a quaternion in-place. 113 void inverse() { 114 x = -x; 115 y = -y; 116 z = -z; 117 } 118 /// Returns inverse of quaternion. 119 Quaternion inversed() const { 120 Quaternion ret = void; 121 ret.v = v; 122 ret.inverse(); 123 return ret; 124 } 125 /// 126 ref Quaternion opOpAssign(string op, U)(U q) if (is(U : Quaternion) && (op == "*")) { 127 const T nx = w * q.x + x * q.w + y * q.z - z * q.y; 128 const T ny = w * q.y + y * q.w + z * q.x - x * q.z; 129 const T nz = w * q.z + z * q.w + x * q.y - y * q.x; 130 const T nw = w * q.w - x * q.x - y * q.y - z * q.z; 131 x = nx; 132 y = ny; 133 z = nz; 134 w = nw; 135 return this; 136 } 137 /// 138 ref Quaternion opOpAssign(string op, U)(U operand) if (isConvertible!U) { 139 Quaternion conv = operand; 140 return opOpAssign!op(conv); 141 } 142 /// 143 Quaternion opBinary(string op, U)(U operand) const if (is(U: Quaternion) || (isConvertible!U)) { 144 Quaternion temp = this; 145 return temp.opOpAssign!op(operand); 146 } 147 /// Compare two Quaternions. 148 bool opEquals(U)(U other) const if (is(U : Quaternion)) { 149 return v == other.v; 150 } 151 /// Compare Quaternion and other types. 152 bool opEquals(U)(U other) const if (isConvertible!U) { 153 Quaternion conv = other; 154 return opEquals(conv); 155 } 156 /// Convert to a 3x3 rotation matrix. 157 U opCast(U)() const 158 if (isMatrixInstance!U && is(U.type : type) && (U.rowCount == 3) && (U.columnCount == 3)) 159 do { 160 const T norm = x*x + y*y + z*z + w*w; 161 const T s = (norm > 0) ? 2 / norm : 0; 162 T xx = x * x * s, xy = x * y * s, xz = x * z * s, xw = x * w * s, 163 yy = y * y * s, yz = y * z * s, yw = y * w * s, 164 zz = z * z * s, zw = z * w * s; 165 return Matrix!(U.type, 3)( 166 1 - (yy + zz), (xy - zw), (xz + yw), 167 (xy + zw), 1 - (xx + zz), (yz - xw), 168 (xz - yw), (yz + xw), 1 - (xx + yy) 169 ); 170 } 171 /// Converts a to a 4x4 rotation matrix. 172 U opCast(U)() const 173 if (isMatrixInstance!U && is(U.type : type) && (U.rowCount == 4) && (U.columnCount == 4)) 174 do { 175 auto m3 = cast(Matrix!(U.type, 3))(this); 176 return cast(U)(m3); 177 } 178 /// 179 static Quaternion identity() { 180 Quaternion q = void; 181 q.x = q.y = q.z = 0; 182 q.w = 1; 183 return q; 184 } 185 186 static if (is(T == float)) { 187 /** 188 * 189 **/ 190 Quaternion mul(Vector3F r) { 191 const float w_ = -x * r.x - y * r.y - z * r.z; 192 const float x_ = w * r.x + y * r.z - z * r.y; 193 const float y_ = w * r.y + z * r.x - x * r.z; 194 const float z_ = w * r.z + x * r.y - y * r.x; 195 196 return Quaternion(x_, y_, z_, w_); 197 } 198 } 199 200 private enum bool isAssignable(T) = is(typeof({ T x; Quaternion v = x; }())); 201 private enum bool isConvertible(T) = (!is(T : Quaternion)) && isAssignable!T; 202 } 203 /// 204 alias QuaternionF = Quaternion!float; 205 /// 206 alias QuaternionD = Quaternion!double; 207 /// Returns linear interpolation for quaternions. 208 Quaternion!T lerp(T)(Quaternion!T a, Quaternion!T b, float t) { 209 Quaternion!T ret; 210 ret.v = funcs.lerp(a.v, b.v, t); 211 return ret; 212 } 213 /// Returns nlerp of quaternions. 214 Quaternion!T nlerp(T)(Quaternion!T a, Quaternion!T b, float t) 215 in { 216 assert(t >= 0 && t <= 1); 217 } do { 218 Quaternion!T ret; 219 ret.v = funcs.lerp(a.v, b.v, t); 220 ret.v.normalize(); 221 return ret; 222 } 223 /// Returns slerp of quaternions. 224 Quaternion!T slerp(T)(Quaternion!T a, Quaternion!T b, T t) 225 in { 226 assert(t >= 0 && t <= 1); 227 } do { 228 Quaternion!T ret; 229 T dotProduct = dot(a.v, b.v); 230 if (dotProduct < 0) { 231 b.v *= -1; 232 dotProduct = dot(a.v, b.v); 233 } 234 const T threshold = 10 * T.epsilon; 235 if ((1 - dotProduct) > threshold) { 236 return lerp(a, b, t); 237 } 238 const T theta_0 = funcs.safeAcos(dotProduct); 239 const T theta = theta_0 * t; 240 vec3!T v2 = dot(b.v, a.v * dotProduct); 241 v2.normalize(); 242 ret.v = dot(b.v, a.v * dotProduct); 243 ret.v.normalize(); 244 ret.v = a.v * cos(theta) + ret.v * sin(theta); 245 return ret; 246 }