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