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