1 module armos.math.quaternion;
2 import armos.math;
3 import std.math;
4 /++
5 クオータニオンを表すstructです.
6 +/
7 struct Quaternion(T)if(__traits(isArithmetic, T)){
8     alias Quaternion!(T) Q;
9     alias V4 = armos.math.Vector!(T, 4);
10     alias V3 = armos.math.Vector!(T, 3);
11 
12     /// vec[0];x
13     /// vec[1];y
14     /// vec[2];z
15     /// vec[3];w
16     V4 vec = V4();
17 
18     /++
19     +/
20     this(in T x, in T y, in T z, in T w){
21         vec[0] = x;
22         vec[1] = y;
23         vec[2] = z;
24         vec[3] = w;
25     }
26     unittest{
27         alias N = double;
28         alias Q = Quaternion!N;
29         assert(__traits(compiles, { Q q = Q(N(1), N(2), N(3), N(4)); }));
30     }
31     unittest{
32         assert(__traits(compiles, { auto q = Quaternion!(int)(0, 0, 0, 1); }));
33     }
34 
35 
36     /++
37     +/
38     this(in T s, in V3 v){
39         vec[0] = v[0];
40         vec[1] = v[1];
41         vec[2] = v[2];
42         vec[3] = s;
43     }
44     unittest{
45         alias T = double;
46         alias Q = Quaternion!T;
47         alias V3 = Vector!(T, 3);
48         assert(__traits(compiles, {
49                     Q q = Q(T(1), V3(2, 3, 4));
50                     }));
51     }
52 
53     /++
54     +/
55     this(in V3 v){
56         this(T(1), v);
57     }
58     unittest{
59         alias T = double;
60         alias Q = Quaternion!T;
61         alias V3 = Vector!(T, 3);
62         assert(__traits(compiles, {
63                     Q q = Q(V3(2, 3, 4));
64                     }));
65     }
66 
67     /++
68     +/
69     this(in V4 v){
70         vec[0] = v[0];
71         vec[1] = v[1];
72         vec[2] = v[2];
73         vec[3] = v[3];
74     }
75     unittest{
76         alias T = double;
77         alias Q = Quaternion!T;
78         alias V4 = Vector!(T, 4);
79         assert(__traits(compiles, {
80                     Q q = Q(V4(1, 2, 3, 4));
81                     }));
82     }
83 
84     /++
85     +/
86     T opIndex(in int index)const{
87         return vec[index];
88     }
89 
90     /++
91     +/
92     ref  T opIndex(in int index){
93         return vec[index];
94     }
95     unittest{
96         Quaternion!(double) q = Quaternion!(double)(1, 2, 3, 4);
97         assert(q[0]==1);
98         assert(q[1]==2);
99         assert(q[2]==3);
100         assert(q[3]==4);
101     }
102 
103     /++
104     +/
105     static Q zero(){
106         return Q(T( 0 ), T( 0 ), T( 0 ), T( 0 ));
107     }
108     unittest{
109         auto e = Quaternion!(double).zero;
110         assert(e[0]==0);
111         assert(e[1]==0);
112         assert(e[2]==0);
113         assert(e[3]==0);
114     }
115 
116     /++
117     +/
118     static Q unit(){
119         return Q(T( 0 ), T( 0 ), T( 0 ), T( 1 ));
120     }
121     unittest{
122         auto e = Quaternion!(double).unit;
123         assert(e[0]==0);
124         assert(e[1]==0);
125         assert(e[2]==0);
126         assert(e[3]==1);
127     }
128 
129     /++
130     +/
131     Q opMul(in Q r_quat)const{
132         immutable T s_l  = this[3];
133         immutable v_l = V3(this[0], this[1], this[2]);
134 
135         immutable T s_r  = r_quat[3];
136         immutable v_r = V3(r_quat[0], r_quat[1], r_quat[2],);
137 
138         immutable return_v = s_l*v_r + s_r*v_l + v_l.vectorProduct(v_r) ;
139         immutable return_s = ( s_l*s_r ) - ( v_l.dotProduct(v_r) );
140         return Q(return_v[0], return_v[1], return_v[2], return_s);
141     }
142     unittest{
143         Quaternion!(double) q1      = Quaternion!(double)(0, 0, 0, 1);
144         Quaternion!(double) q2      = Quaternion!(double)(0, 0, 0, 1);
145         Quaternion!(double) qAnswer = Quaternion!(double)(0, 0, 0, 1);
146         assert(q1*q2 == qAnswer);
147     }
148     unittest{
149         auto va = Vector!(double, 3)(0.5, 0.1, 0.4);
150         auto v = va.normalized;
151 
152         Quaternion!(double) q1      = Quaternion!(double)(v[0]*sin(0.1), v[1]*sin(0.1), v[2]*sin(0.1), cos(0.1));
153         Quaternion!(double) q2      = Quaternion!(double)(v[0]*sin(0.2), v[1]*sin(0.2), v[2]*sin(0.2), cos(0.2));
154         Quaternion!(double) qResult = q1 * q2;
155         Quaternion!(double) qAnswer = Quaternion!(double)(v[0]*sin(0.3), v[1]*sin(0.3), v[2]*sin(0.3), cos(0.3));
156         foreach (int i, value; qResult.vec.elements) {
157             assert( approxEqual(value, qAnswer[i]) );
158         }
159     }
160 
161     /++
162     +/
163     Q opNeg()const{
164         return Q(-this[0], -this[1], -this[2], -this[3]);
165     }
166     unittest{
167         Quaternion!(double) q = Quaternion!(double)(1, 1, 1, 1);
168         Quaternion!(double) a = Quaternion!(double)(-1, -1, -1, -1);
169         assert(-q == a);
170     }
171 
172     /++
173     +/
174     Q opAdd(in Q q)const{
175         auto return_quat = Q();
176         return_quat.vec = vec + q.vec;
177         return return_quat;
178     }
179     unittest{
180         auto q1 = Quaternion!(double)(1, 2, 3, 4);
181         auto q2 = Quaternion!(double)(2, 3, 4, 5);
182         auto qA = Quaternion!(double)(3, 5, 7, 9);
183         assert(q1+q2 == qA);
184     }
185 
186     /++
187     +/
188     Q opMul(in T r)const{
189         auto return_quat = Q();
190         return_quat.vec = vec * r;
191         return return_quat;
192     }
193     unittest{
194         auto q = Quaternion!(double)(1, 2, 3, 4);
195         auto qA = Quaternion!(double)(3, 6, 9, 12);
196         assert(q*3 == qA);
197     }
198 
199     /++
200     +/
201     Q opDiv(in T r)const {
202         auto return_quat = Q();
203         return_quat.vec = vec / r;
204         return return_quat;
205     }
206     unittest{
207         auto q = Quaternion!(double)(3, 6, 9, 12);
208         auto qA = Quaternion!(double)(1, 2, 3, 4);
209         assert(q/3 == qA);
210     }
211 
212     CastedType opCast(CastedType)()const{
213         import std.conv:to;
214         return CastedType(this.vec.to!(typeof(CastedType.vec)));
215     }
216 
217     unittest{ 
218         import std.conv:to;
219         Quaternion!int iq = Quaternion!double.zero.to!(Quaternion!int); 
220         import std.conv:to;
221         Quaternion!double dq = Quaternion!int.zero.to!(Quaternion!double); 
222     }
223 
224     /++
225         Quaternionのノルムを返します.
226     +/
227     T norm()()const if(__traits(isFloating, T)){
228         return sqrt(this[0]^^2.0 + this[1]^^2.0 + this[2]^^2.0 + this[3]^^2.0);
229     }
230 
231     unittest{
232         auto q = Quaternion!(double)(1, 2, 3, 4);
233         assert(q.norm == sqrt(30.0));
234     }
235 
236     /++
237     +/
238     Q normalized()const {
239         return Q(vec.normalized);
240     }
241     unittest{
242         // auto q = Quaternion!(double)(1, 2, 3, 4);
243         // assert(q.norm == sqrt(30.0));
244     }
245 
246     /++
247         Quaternionの共役Quaternionを返します.
248     +/
249     Q conjugate()const{
250         return Q(-this[0], -this[1], -this[2], this[3]);
251     }
252     unittest{
253         auto q  = Quaternion!(double)(1, 2, 3, 4);
254         auto qA = Quaternion!(double)(-1, -2, -3, 4);
255         assert(q.conjugate == qA);
256     }
257 
258     /++
259 
260     +/
261     Q inverse()()const if(__traits(isFloating, T)){
262         return conjugate/(this[0]^^T(2.0) + this[1]^^T(2.0) + this[2]^^T(2.0) + this[3]^^T(2.0));
263     }
264     unittest{
265         auto q  = Quaternion!(double)(0, 1, 0, 1);
266         auto qR = q.inverse;
267         auto qA = Quaternion!(double)(0, -0.5, 0, 0.5);
268 
269         foreach (int i, value; qR.vec.elements) {
270             assert( approxEqual(value, qA[i]) );
271         }
272     }
273 
274     /++
275         自身の回転行列(4x4)を返します.
276     +/
277     armos.math.Matrix!(T, 4, 4) matrix44()()const if(__traits(isFloating, T)){
278         return armos.math.Matrix!(T, 4, 4)(
279                 [this[3]^^2.0+this[0]^^2.0-this[1]^^2.0-this[2]^^2.0, 2.0*(this[0]*this[1]-this[3]*this[2]),               2.0*(this[0]*this[2]+this[3]*this[1]),               0],
280                 [2.0*(this[0]*this[1]+this[3]*this[2]),               this[3]^^2.0-this[0]^^2.0+this[1]^^2.0-this[2]^^2.0, 2.0*(this[1]*this[2]-this[3]*this[0]),               0],
281                 [2.0*(this[0]*this[2]-this[3]*this[1]),               2.0*(this[1]*this[2]+this[3]*this[0]),               this[3]^^2.0-this[0]^^2.0-this[1]^^2.0+this[2]^^2.0, 0],
282                 [0,                                                   0,                                                   0,                                                   1]
283                 );
284     }
285     unittest{
286     }
287 
288     /++
289         自身の回転行列(3x3)を返します.
290     +/
291     armos.math.Matrix!(T, 3, 3) matrix33()()const if(__traits(isFloating, T)){
292         return armos.math.Matrix!(T, 3, 3)(
293                 [this[3]^^2.0+this[0]^^2.0-this[1]^^2.0-this[2]^^2.0, 2.0*(this[0]*this[1]-this[3]*this[2]),               2.0*(this[0]*this[2]+this[3]*this[1])              ],
294                 [2.0*(this[0]*this[1]+this[3]*this[2]),               this[3]^^2.0-this[0]^^2.0+this[1]^^2.0-this[2]^^2.0, 2.0*(this[1]*this[2]-this[3]*this[0])              ],
295                 [2.0*(this[0]*this[2]-this[3]*this[1]),               2.0*(this[1]*this[2]+this[3]*this[0]),               this[3]^^2.0-this[0]^^2.0-this[1]^^2.0+this[2]^^2.0]
296                 );
297     }
298 
299     /++
300         指定したベクトルを自身で回転させたベクトルを返します.
301     +/
302     V3 rotatedVector()(in V3 vec)const if(__traits(isFloating, T)){
303         if( norm^^2.0 < T.epsilon){
304             return vec;
305         }else{
306             auto temp_quat = Q(vec);
307             auto return_quat= this*temp_quat*this.inverse;
308             auto return_vector = V3(return_quat[0], return_quat[1], return_quat[2]);
309             return return_vector;
310         }
311     }
312     unittest{
313         auto v = armos.math.Vector3d(1, 0, 0);
314         double ang = PI*0.5*0.5;
315         auto q  = Quaternion!(double)(0*sin(ang), 0*sin(ang), 1*sin(ang), cos(ang));
316         auto vR = q.rotatedVector(v);
317         auto vA = armos.math.Vector3d(0, 1, 0);
318 
319         foreach (int i, value; vR.elements) {
320             assert( approxEqual(value, vA[i]) );
321         }
322     }
323 
324     /++
325         指定したベクトルを自身の逆方向に回転させたベクトルを返します.
326     +/
327     V3 rotatedVectorInversely()(in V3 vec)const if(__traits(isFloating, T)){
328         if( norm^^2.0 < T.epsilon){
329             return vec;
330         }else{
331             auto temp_quat = Q(vec);
332             auto return_quat= this.inverse*temp_quat*this;
333             auto return_vector = V3(return_quat[0], return_quat[1], return_quat[2]);
334             return return_vector;
335         }
336     }
337     unittest{
338         auto v = armos.math.Vector3d(1, 0, 0);
339         double ang = PI*0.5*0.5;
340         auto q  = Quaternion!(double)(0*sin(ang), 0*sin(ang), 1*sin(ang), cos(ang));
341         auto vR = q.rotatedVectorInversely(v);
342         auto vA = armos.math.Vector3d(0, -1, 0);
343 
344         foreach (int i, value; vR.elements) {
345             assert( approxEqual(value, vA[i]) );
346         }
347     }
348 
349     /++
350         指定した軸で自身を回転させます.
351         Params:
352         ang = 回転角
353         axis = 回転軸
354     +/
355     static Q angleAxis()(T ang, V3 axis) if(__traits(isFloating, T)){
356         immutable halfAngle = ang*T(0.5);
357         return Q(axis[0]*sin(halfAngle), axis[1]*sin(halfAngle), axis[2]*sin(halfAngle), cos(halfAngle));
358     }
359     unittest{
360         auto v = armos.math.Vector3d(1, 0, 0);
361         auto q = Quaternion!(double).angleAxis(PI*0.5, armos.math.Vector3d(0, 0, 1));
362         auto vR = q.rotatedVector(v);
363         auto vA = armos.math.Vector3d(0, 1, 0);
364 
365         foreach (int i, value; vR.elements) {
366             assert( approxEqual(value, vA[i]) );
367         }
368     }
369 
370     Q productAngle()(T gain) if(__traits(isFloating, T)){
371         return slerp(Q.zero, this, gain);
372     }
373 
374 }
375 
376 /// 回転の差分のQuaternionを返します.
377 Q rotationDifference(Q)(in Q from,  in Q to){
378     return from.inverse * to;
379 }
380 unittest{
381     auto qBegin= Quaternion!(double).angleAxis(PI*0.2, armos.math.Vector3d(0, 0, 1));
382     auto qEnd = Quaternion!(double).angleAxis(PI*0.5, armos.math.Vector3d(0, 0, 1));
383     
384     auto qAns = Quaternion!(double).angleAxis(PI*0.3, armos.math.Vector3d(0, 0, 1));
385     auto qResult = rotationDifference(qBegin, qEnd);
386     
387     assert(approxEqual(qResult[0], qAns[0]));
388     assert(approxEqual(qResult[1], qAns[1]));
389     assert(approxEqual(qResult[2], qAns[2]));
390     assert(approxEqual(qResult[3], qAns[3]));
391 }
392 
393 /++
394 球面線形補間(Sphercal Linear Interpolation)を行います.
395 Params:
396 from = tが0の時のQuaternion
397 to = tが1の時のQuaternion
398 t = 
399 +/
400 Q slerp(Q, T)(in Q from, in Q to,  T t)if(__traits(isFloating, typeof(Q.vec[0]))){
401     T omega, cos_omega, sin_omega, scale_from, scale_to;
402 
403     Q quatTo = to;
404     cos_omega = from.vec.dotProduct(to.vec);
405 
406     if (cos_omega < T( 0.0 )) {
407         cos_omega = -cos_omega;
408         quatTo = -to;
409     }
410 
411     if( (T( 1.0 ) - cos_omega) > T.epsilon ){
412         omega = acos(cos_omega);
413         sin_omega = sin(omega);
414         scale_from = sin(( T( 1.0 ) - t ) * omega) / sin_omega;
415         scale_to = sin(t * omega) / sin_omega;
416     }else{
417         scale_from = T( 1.0 ) - t;
418         scale_to = t;
419     }
420 
421     return ( from * scale_from ) + ( quatTo * scale_to  );
422 }
423 unittest{
424     auto v = armos.math.Vector3d(1, 0, 0);
425     auto qBegin= Quaternion!(double).angleAxis(0, armos.math.Vector3d(0, 0, 1));
426     auto qEnd = Quaternion!(double).angleAxis(PI*0.5, armos.math.Vector3d(0, 0, 1));
427     auto qSlerped = qBegin.slerp(qEnd, 2.0);
428     auto vR = qSlerped.rotatedVector(v);
429     auto vA = armos.math.Vector3d(-1, 0, 0);
430 
431     foreach (int i, value; vR.elements) {
432         assert( approxEqual(value, vA[i]) );
433     }
434 
435 }
436 
437 alias Quaternion!(float) Quaternionf;
438 alias Quaternion!(double) Quaterniond;