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 }