1 /// Quaternion 2 module sily.quat; 3 4 import std.math; 5 import std.numeric; 6 import std.conv; 7 import std.traits; 8 import std.typetuple; 9 import std.algorithm; 10 import std.stdio; 11 import std.string; 12 import std.format; 13 14 import sily.meta.swizzle; 15 import sily.math; 16 import sily.array; 17 import sily.color; 18 import sily.matrix; 19 import sily.vector; 20 21 alias quat = Quaternion; 22 23 /++ 24 Struct repesentation of quaterion 25 +/ 26 struct Quaternion { 27 /// Quat data 28 public float[4] data = 0; 29 30 /// Alias to allow easy `data` access 31 alias data this; 32 33 /** 34 Constructs quaternion from single value or array of values 35 */ 36 this(in float val) { 37 foreach (i; 0 .. 4) { data[i] = val; } 38 } 39 /// Ditto 40 this(in float[4] vals...) { 41 data = vals; 42 } 43 44 /// Construct quaternion from euler angles 45 this(in float[3] vals...) { 46 float x0 = cos(vals[0] * 0.5f); 47 float x1 = sin(vals[0] * 0.5f); 48 float y0 = cos(vals[1] * 0.5f); 49 float y1 = sin(vals[1] * 0.5f); 50 float z0 = cos(vals[2] * 0.5f); 51 float z1 = sin(vals[2] * 0.5f); 52 53 data = [ 54 x1 * y0 * z0 - x0 * y1 * z1, 55 x0 * y1 * z0 + x1 * y0 * z1, 56 x0 * y0 * z1 - x1 * y1 * z0, 57 x0 * y0 * z0 + x1 * y1 * z1 58 ]; 59 } 60 61 /// Constructs quaterion from rotation matrix 62 this(in mat4 m) { 63 float fW = m[0][0] + m[1][1] + m[2][2]; 64 float fX = m[0][0] - m[1][1] - m[2][2]; 65 float fY = m[1][1] - m[0][0] - m[2][2]; 66 float fZ = m[2][2] - m[0][0] - m[1][1]; 67 68 int big = 0; 69 float fB = fW; 70 71 if (fX > fB) { 72 fB = fX; 73 big = 1; 74 } 75 if (fY > fB) { 76 fB = fY; 77 big = 2; 78 } 79 if (fZ > fB) { 80 fB = fZ; 81 big = 3; 82 } 83 84 float bV = sqrt(fB + 1.0) * 0.5f; 85 float mult = 0.25f / bV; 86 switch (big) { 87 case 0: 88 data[0] = (m[2][1] - m[1][2]) * mult; 89 data[1] = (m[0][2] - m[2][0]) * mult; 90 data[2] = (m[1][0] - m[0][1]) * mult; 91 data[3] = bV; 92 break; 93 case 1: 94 data[0] = bV; 95 data[1] = (m[1][0] + m[0][1]) * mult; 96 data[2] = (m[0][2] - m[2][0]) * mult; 97 data[3] = (m[2][1] - m[1][2]) * mult; 98 break; 99 case 2: 100 data[0] = (m[1][0] + m[0][1]) * mult; 101 data[1] = bV; 102 data[2] = (m[2][1] + m[1][2]) * mult; 103 data[3] = (m[0][2] - m[2][0]) * mult; 104 break; 105 case 3: 106 data[0] = (m[0][2] + m[2][0]) * mult; 107 data[1] = (m[2][1] + m[1][2]) * mult; 108 data[2] = bV; 109 data[3] = (m[1][0] - m[0][1]) * mult; 110 break; 111 default: break; 112 } 113 } 114 115 /// Constructs quaternion from axis angle 116 this(vec3 axis, float angle) { 117 data = identity().data; 118 float axisLen = axis.length; 119 if (axisLen != 0.0) { 120 angle *= 0.5f; 121 if (!axis.isNormalized) axis.normalize(); 122 float sina = sin(angle); 123 float cosa = cos(angle); 124 125 data = [ 126 axis.x * sina, 127 axis.y * sina, 128 axis.z * sina, 129 cosa 130 ]; 131 132 normalize(); 133 } 134 } 135 136 /// private size alias 137 private enum size = 4; 138 139 /* -------------------------------------------------------------------------- */ 140 /* UNARY OPERATIONS OVERRIDES */ 141 /* -------------------------------------------------------------------------- */ 142 143 /// opBinary x [+, -, *, /, %] y 144 quat opBinary(string op, R)(in Vector!(R, 4) b) const if ( isNumeric!R ) { 145 quat ret; 146 foreach (i; 0 .. size) { mixin( "ret[i] = data[i] " ~ op ~ " b.data[i];" ); } 147 return ret; 148 } 149 150 /// Ditto 151 quat opBinaryRight(string op, R)(in Vector!(R, 4) b) const if ( isNumeric!R ) { 152 quat ret; 153 foreach (i; 0 .. size) { mixin( "ret[i] = b.data[i] " ~ op ~ " data[i];" ); } 154 return ret; 155 } 156 157 /// opBinary x [+, -, *, /, %] y 158 quat opBinary(string op)(in quat b) const if (op == "*") { 159 quat q1 = this; 160 quat q2 = b; 161 return quat( 162 q1.x * q2.w + q1.w * q2.x + q1.y * q2.z - q1.z * q2.y, 163 q1.y * q2.w + q1.w * q2.y + q1.z * q2.x - q1.x * q2.z, 164 q1.z * q2.w + q1.w * q2.z + q1.x * q2.y - q1.y * q2.x, 165 q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z 166 ); 167 } 168 169 // /// Ditto 170 // quat opBinaryRight(string op)(in quat b) const if (op == "*") { 171 // quat q2 = this; 172 // quat q1 = b; 173 // return quat( 174 // q1.x * q2.w + q1.w * q2.x + q1.y * q2.z - q1.z * q2.y, 175 // q1.y * q2.w + q1.w * q2.y + q1.x * q2.x - q1.x * q2.z, 176 // q1.z * q2.w + q1.w * q2.z + q1.z * q2.y - q1.y * q2.x, 177 // q1.w * q2.w + q1.w * q2.x + q1.y * q2.y - q1.z * q2.z 178 // ); 179 // } 180 181 /// Ditto 182 quat opBinary(string op)(in quat b) const if ( op != "*" ) { 183 quat ret; 184 foreach (i; 0 .. size) { mixin( "ret[i] = data[i] " ~ op ~ " b.data[i];" ); } 185 return ret; 186 } 187 188 // /// Ditto 189 // quat opBinaryRight(string op)(in quat b) const if ( op != "*" ) { 190 // quat ret; 191 // foreach (i; 0 .. size) { mixin( "ret[i] = b.data[i] " ~ op ~ " data[i];" ); } 192 // return ret; 193 // } 194 195 /// Ditto 196 quat opBinary(string op, R)(in R b) const if ( isNumeric!R ) { 197 quat ret; 198 foreach (i; 0 .. size) { mixin( "ret[i] = data[i] " ~ op ~ " b;" ); } 199 return ret; 200 } 201 202 /// Ditto 203 quat opBinaryRight(string op, R)(in R b) const if ( isNumeric!R ) { 204 quat ret; 205 foreach (i; 0 .. size) { mixin( "ret[i] = b " ~ op ~ " data[i];" ); } 206 return ret; 207 } 208 209 /// opEquals x == y 210 bool opEquals(R)(in Vector!(R, size) b) const if ( isNumeric!R ) { 211 bool eq = true; 212 foreach (i; 0 .. size) { 213 eq = eq && data[i] == b.data[i]; 214 if (!eq) break; 215 } 216 return eq; 217 } 218 219 /// Ditto 220 bool opEquals()(in quat b) const { 221 bool eq = true; 222 foreach (i; 0 .. size) { 223 eq = eq && data[i] == b.data[i]; 224 if (!eq) break; 225 } 226 return eq; 227 } 228 229 /// opCmp x [< > <= >=] y 230 int opCmp(R)(in Vector!(R, size) b) const if ( isNumeric!R ) { 231 double al = cast(double) length(); 232 double bl = cast(double) b.length(); 233 if (al == bl) return 0; 234 if (al < bl) return -1; 235 return 1; 236 } 237 238 int opCmp()(in quat b) const { 239 double al = cast(double) length(); 240 double bl = cast(double) b.length(); 241 if (al == bl) return 0; 242 if (al < bl) return -1; 243 return 1; 244 } 245 246 /// opUnary [-, +, --, ++] x 247 quat opUnary(string op)() if (op == "-" || op == "+") { 248 quat ret; 249 if (op == "-") 250 foreach (i; 0 .. size) { ret[i] = -data[i]; } 251 if (op == "+") 252 foreach (i; 0 .. size) { ret[i] = data[i]; } 253 return ret; 254 } 255 256 /// Invert quaternion 257 quat opUnary(string op)() if (op == "~") { 258 quat ret = data; 259 double len = lengthSquared; 260 if (len != 0) { 261 len = 1.0 / len; 262 ret[0] *= -len; 263 ret[1] *= -len; 264 ret[2] *= -len; 265 ret[3] *= len; 266 } 267 return ret; 268 } 269 270 /// opOpAssign x [+, -, *, /, %]= y 271 quat opOpAssign(string op, R)( in Vector!(R, 4) b ) if ( isNumeric!R ) { 272 foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b.data[i];" ); } 273 return this; 274 } 275 276 quat opOpAssign(string op)( in quat b ) { 277 foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b.data[i];" ); } 278 return this; 279 } 280 281 /// Ditto 282 VecType opOpAssign(string op, R)( in R b ) if ( isNumeric!R ) { 283 foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b;" ); } 284 return this; 285 } 286 287 /// opCast cast(x) y 288 R opCast(R)() const if (isVector!(R, 4) && isFloatingPoint!(R.dataType)) { 289 R ret; 290 foreach (i; 0 .. size) { 291 ret[i] = cast(R.dataType) data[i]; 292 } 293 return ret; 294 } 295 296 // /// Cast to matrix (column/row matrix) 297 // R opCast(R)() const if (isMatrix!(R, N, 1)) { 298 // R ret; 299 // foreach (i; 0 .. N) { 300 // ret[i][0] = cast(R.dataType) data[i]; 301 // } 302 // return ret; 303 // } 304 // 305 // /// Ditto 306 // R opCast(R)() const if (isMatrix!(R, 1, N)) { 307 // R ret; 308 // foreach (i; 0 .. N) { 309 // ret[0][i] = cast(R.dataType) data[i]; 310 // } 311 // return ret; 312 // } 313 314 /// Cast to bool 315 bool opCast(T)() const if (is(T == bool)) { 316 return !lengthSquared.isClose(0, float.epsilon); 317 } 318 319 /// Returns hash 320 size_t toHash() const @safe nothrow { 321 return typeid(data).getHash(&data); 322 } 323 324 private enum AS = "x y z w"; 325 /// Mixes in swizzle 326 mixin accessByString!(float, 4, "data", AS); 327 328 /// Returns copy of quaterion 329 public quat copyof() { 330 return quat(data); 331 } 332 333 /// Returns string representation of quaternion: `[1.00, 1.00,... , 1.00]` 334 public string toString() const { 335 import std.conv : to; 336 string s; 337 s ~= "["; 338 foreach (i; 0 .. size) { 339 s ~= format("%.2f", data[i]); 340 if (i != size - 1) s ~= ", "; 341 } 342 s ~= "]"; 343 return s; 344 } 345 346 /// Returns pointer to data 347 public float* ptr() return { 348 return data.ptr; 349 } 350 351 /* -------------------------------------------------------------------------- */ 352 /* STATIC GETTERS AND SETTERS */ 353 /* -------------------------------------------------------------------------- */ 354 355 /// Constructs quaternion identity 356 static alias identity = () => quat(0, 0, 0, 1); 357 358 /* -------------------------------------------------------------------------- */ 359 /* MATH */ 360 /* -------------------------------------------------------------------------- */ 361 362 /// Returns quaternion length 363 public double length() const { 364 return sqrt(cast(double) lengthSquared); 365 } 366 367 /// Returns squared quaternion length 368 public float lengthSquared() const { 369 float l = 0; 370 foreach (i; 0 .. size) { l += data[i] * data[i]; } 371 return l; 372 } 373 374 /** 375 Is quaternion approximately close to `v` 376 Params: 377 v = Quaternion to compare 378 Returns: 379 */ 380 public bool isClose(quat v) { 381 bool eq = true; 382 foreach (i; 0 .. size) { eq = eq && data[i].isClose(v[i], float.epsilon); } 383 return eq; 384 } 385 386 /// Normalises quaternion 387 public quat normalized() { 388 double len = length(); 389 if (len == 0.0) len = 1.0; 390 double ilen = 1.0 / len; 391 quat ret; 392 foreach (i; 0..size) { 393 ret[i] = data[i] * ilen; 394 } 395 return ret; 396 } 397 398 /// Ditto 399 alias normalised = normalized; 400 401 /// Normalises quaternion in place 402 public quat normalize() { 403 double len = length(); 404 if (len == 0.0) len = 1.0; 405 double ilen = 1.0 / len; 406 foreach (i; 0..size) { 407 data[i] = data[i] * ilen; 408 } 409 return this; 410 } 411 /// Ditto 412 alias normalise = normalize; 413 414 /** 415 Linearly interpolates quaternion 416 Params: 417 to = Quaternion to interpolate to 418 weight = Interpolation weight in range [0.0, 1.0] 419 */ 420 public quat lerp(quat to, double weight) { 421 quat ret; 422 foreach (i; 0 .. size) { ret[i] = data[i] + (weight * (to.data[i] - data[i])); } 423 return ret; 424 } 425 426 /// Normalized lerp 427 public quat nlerp(quat to, double weight) { 428 quat ret = lerp(to, weight); 429 ret.normalize(); 430 return ret; 431 } 432 433 /// Spherically interpolates quaternion 434 public quat slerp(quat to, double weight) { 435 double d = dot(to); 436 437 if (d < 0.0) { 438 d = -d; 439 to = -to; 440 } 441 442 if (abs(d) >= 1.0) { 443 return this; 444 } else 445 if (d > 0.95) { 446 return nlerp(to, weight); 447 } else { 448 float hth = acos(d); 449 float sht = sqrt(1.0 - d * d); 450 if (abs(sht) < 0.001) { 451 return quat( 452 data[0] * 0.5 + to.data[0] * 0.5, 453 data[1] * 0.5 + to.data[1] * 0.5, 454 data[2] * 0.5 + to.data[2] * 0.5, 455 data[3] * 0.5 + to.data[3] * 0.5 456 ); 457 } else { 458 float ra = sin( (1.0 - weight) * hth ) / sht; 459 float rb = sin( weight * hth ) / sht; 460 return quat( 461 data[0] * ra + to.data[0] * rb, 462 data[1] * ra + to.data[1] * rb, 463 data[2] * ra + to.data[2] * rb, 464 data[3] * ra + to.data[3] * rb 465 ); 466 467 } 468 } 469 } 470 471 /// Calculates quaternion based on rotation between from and to 472 public static quat rotation(vec3 from, vec3 to) { 473 quat ret = 0; 474 double d = from.dot(to); 475 vec3 cr = from.cross(to); 476 477 ret = [cr.x, cr.y, cr.z, 1.0 + d]; 478 ret.normalize(); 479 return ret; 480 } 481 482 /// Returns rotation angle and axis 483 public void axisAngle(out vec3 axis, out float angle) { 484 if (abs(data[3]) > 1.0f) { 485 normalize(); 486 } 487 488 vec3 rx = 0; 489 float ra = 2.0f * cos(data[3]); 490 float den = sqrt(1.0f - data[3] * data[3]); 491 492 if (den > 0.0001f) { 493 rx = [data[0] / den, data[1] / den, data[2] / den]; 494 } else { 495 rx = [1.0, 0, 0]; 496 } 497 axis = rx; 498 angle = ra; 499 } 500 501 /** 502 Performs dot product 503 Params: 504 b = Quaternion 505 Returns: dot product 506 */ 507 public double dot(quat b) { 508 double d = 0; 509 foreach (i; 0 .. size) { d += cast(double) data[i] * cast(double) b.data[i]; } 510 return d; 511 } 512 513 /// Calculates angle between two quaternions 514 public double angle(quat b) { 515 return acos(dot(b)); 516 } 517 518 } 519 520 521