1 module prova.math.quaternion; 2 3 import prova.math; 4 import std.math; 5 import std.algorithm : clamp; 6 7 /// 8 struct Quaternion 9 { 10 /// 11 float x = 0; 12 /// 13 float y = 0; 14 /// 15 float z = 0; 16 /// 17 float w = 1; 18 19 /// 20 this(float x, float y, float z, float w) 21 { 22 set(x, y, z, w); 23 } 24 25 /// Sets the values of x, y, z, and w in a single statement 26 void set(float x, float y, float z, float w) 27 { 28 this.x = x; 29 this.y = y; 30 this.z = z; 31 this.w = w; 32 } 33 34 /// angle is in degrees 35 static Quaternion fromAxisAngle(Vector3 axis, float angle) 36 { 37 return fromAxisAngle(axis.x, axis.y, axis.z, angle); 38 } 39 40 /// angle is in degrees 41 static Quaternion fromAxisAngle(float x, float y, float z, float angle) 42 { 43 angle *= PI / 180 / 2; 44 45 float sinAngle = sin(angle); 46 float cosAngle = cos(angle); 47 48 Quaternion result; 49 result.x = x * sinAngle; 50 result.y = y * sinAngle; 51 result.z = z * sinAngle; 52 result.w = cosAngle; 53 result.normalize(); 54 55 return result; 56 } 57 58 /// Create a quaternion from euler angles in degrees 59 static Quaternion fromEuler(Vector3 euler) 60 { 61 return fromEuler(euler.x, euler.y, euler.z); 62 } 63 64 /// Create a quaternion from euler angles in degrees 65 static Quaternion fromEuler(float x, float y, float z) 66 { 67 x *= PI / 180 / 2; 68 y *= PI / 180 / 2; 69 z *= PI / 180 / 2; 70 71 float cosX = cos(x); 72 float sinX = sin(x); 73 float cosY = cos(y); 74 float sinY = sin(y); 75 float cosZ = cos(z); 76 float sinZ = sin(z); 77 78 Quaternion result; 79 result.x = cosZ * sinX * cosY - sinZ * cosX * sinY; 80 result.y = cosZ * cosX * sinY + sinZ * sinX * cosY; 81 result.z = sinZ * cosX * cosY - cosZ * sinX * sinY; 82 result.w = cosZ * cosX * cosY + sinZ * sinX * sinY; 83 84 return result; 85 } 86 87 /// Creates a normalized quaternion with a random rotation and axis 88 static Quaternion random() 89 { 90 Quaternion result = Quaternion( 91 randomF(1), 92 randomF(1), 93 randomF(1), 94 randomF(1) 95 ); 96 97 result.normalize(); 98 99 return result; 100 } 101 102 /// 103 @property Vector3 xyz() const 104 { 105 return Vector3(x, y, z); 106 } 107 108 /// Returns a normalized copy of this quaternion 109 Quaternion getNormalized() const 110 { 111 const float magnitude = getMagnitude(); 112 113 Quaternion result; 114 115 if(magnitude != 0) { 116 result.x = x / magnitude; 117 result.y = y / magnitude; 118 result.z = z / magnitude; 119 result.w = w / magnitude; 120 } 121 122 return result; 123 } 124 125 /// Normalizes the quaternion 126 void normalize() 127 { 128 const float magnitude = getMagnitude(); 129 130 if(magnitude == 0) 131 return; 132 133 x = x / magnitude; 134 y = y / magnitude; 135 z = z / magnitude; 136 w = w / magnitude; 137 } 138 139 /// Returns the magnitude of the quaternion 140 float getMagnitude() const 141 { 142 return sqrt(x * x + y * y + z * z + w * w); 143 } 144 145 /** 146 * Sets the magnitude of this quaternion 147 * 148 * If the previous magnitude is zero, the x value 149 * of the quaternion will be set to the magnitude 150 */ 151 void setMagnitude(float magnitude) 152 { 153 if(getMagnitude() == 0) { 154 x = magnitude; 155 return; 156 } 157 158 normalize(); 159 160 x *= magnitude; 161 y *= magnitude; 162 z *= magnitude; 163 w *= magnitude; 164 } 165 166 /// 167 Vector3 toEuler() 168 { 169 Vector3 squared = xyz * xyz; 170 171 172 Vector3 result = Vector3( 173 atan2( 174 2 * (w * x + y * z), 175 1 - 2 * (squared.x + squared.y) 176 ), 177 asin( 178 clamp(2 * (w * y - z * x), -1, 1) 179 ), 180 atan2( 181 2 * (w * z + x * y), 182 1 - 2 * (squared.y + squared.z) 183 ), 184 ); 185 186 return result / PI * 180; 187 } 188 189 /// 190 Quaternion getConjugate() const 191 { 192 Quaternion result; 193 result.x = -x; 194 result.y = -y; 195 result.z = -z; 196 result.w = w; 197 198 return result; 199 } 200 201 202 // assignment overloading 203 /// 204 Quaternion opAddAssign(Quaternion quaternion) 205 { 206 x += quaternion.x; 207 y += quaternion.y; 208 z += quaternion.z; 209 w += quaternion.w; 210 211 return this; 212 } 213 214 /// 215 Quaternion opSubAssign(Quaternion quaternion) 216 { 217 x -= quaternion.x; 218 y -= quaternion.y; 219 z -= quaternion.z; 220 w -= quaternion.w; 221 222 return this; 223 } 224 225 /// 226 Quaternion opMulAssign(float a) 227 { 228 x *= a; 229 y *= a; 230 z *= a; 231 w *= a; 232 233 return this; 234 } 235 236 /// 237 Quaternion opMulAssign(Quaternion quaternion) 238 { 239 Vector3 crossProduct = xyz.cross(quaternion.xyz); 240 float dotProduct = xyz.dot(quaternion.xyz); 241 242 Vector3 axis = xyz * quaternion.w + quaternion.xyz * w + crossProduct; 243 244 Quaternion result; 245 this.x = axis.x; 246 this.y = axis.y; 247 this.z = axis.z; 248 this.w = (w * quaternion.w) - dotProduct; 249 250 return this; 251 } 252 253 /// 254 Quaternion opDivAssign(float a) 255 { 256 x /= a; 257 y /= a; 258 z /= a; 259 w /= a; 260 261 return this; 262 } 263 264 265 // arithmetic overloading 266 /// 267 Quaternion opAdd(Quaternion quaternion) const 268 { 269 Quaternion result; 270 result.x = x + quaternion.x; 271 result.y = y + quaternion.y; 272 result.z = z + quaternion.z; 273 result.w = w + quaternion.w; 274 275 return result; 276 } 277 278 /// 279 Quaternion opSub(Quaternion quaternion) const 280 { 281 Quaternion result; 282 result.x = x - quaternion.x; 283 result.y = y - quaternion.y; 284 result.z = z - quaternion.z; 285 result.w = w - quaternion.w; 286 287 return result; 288 } 289 290 /// 291 Quaternion opUnary(string s)() const if (s == "-") 292 { 293 Quaternion result; 294 result.x = -x; 295 result.y = -y; 296 result.z = -z; 297 result.w = -w; 298 299 return result; 300 } 301 302 /// 303 Quaternion opMul(float a) const 304 { 305 Quaternion result; 306 result.x = x * a; 307 result.y = y * a; 308 result.z = z * a; 309 result.w = w * a; 310 311 return result; 312 } 313 314 /// 315 Vector3 opMul(Vector3 vector) const 316 { 317 // Solution from Laurent Couvidou https://gamedev.stackexchange.com/users/14808/laurent-couvidou 318 // https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion 319 320 Vector3 xyz = this.xyz; 321 322 return 2 * xyz.dot(vector) * xyz + 323 (w * w - xyz.dot(xyz)) * vector + 324 2 * w * xyz.cross(vector); 325 } 326 327 /// 328 Quaternion opMul(Quaternion quaternion) const 329 { 330 Vector3 crossProduct = xyz.cross(quaternion.xyz); 331 float dotProduct = xyz.dot(quaternion.xyz); 332 333 Vector3 axis = xyz * quaternion.w + quaternion.xyz * w + crossProduct; 334 335 Quaternion result; 336 result.x = axis.x; 337 result.y = axis.y; 338 result.z = axis.z; 339 result.w = (w * quaternion.w) - dotProduct; 340 341 return result; 342 } 343 344 /// 345 Quaternion opDiv(float a) const 346 { 347 Quaternion result; 348 result.x = x / a; 349 result.y = y / a; 350 result.z = z / a; 351 result.w = w / a; 352 353 return result; 354 } 355 }