1 /**
2 Quaternion.
3 
4 Copyright:
5 Copyright (c) 2007 Juan Linietsky, Ariel Manzur.
6 Copyright (c) 2014 Godot Engine contributors (cf. AUTHORS.md)
7 Copyright (c) 2017 Godot-D contributors
8 Copyright (c) 2022 Godot-DLang contributors
9 
10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License)
11 
12 
13 */
14 module godot.quat;
15 
16 import godot.api.types;
17 import godot.vector3;
18 import godot.basis;
19 
20 import std.math;
21 
22 /**
23 A 4-dimensional vector representing a rotation.
24 
25 The vector represents a 4 dimensional complex number where multiplication of the basis elements is not commutative (multiplying i with j gives a different result than multiplying j with i).
26 
27 Multiplying quaternions reproduces rotation sequences. However quaternions need to be often renormalized, or else they suffer from precision issues.
28 
29 It can be used to perform SLERP (spherical-linear interpolation) between two rotations.
30 */
31 struct Quaternion {
32 @nogc nothrow:
33 
34     real_t x = 0;
35     real_t y = 0;
36     real_t z = 0;
37     real_t w = 1;
38 
39     void set(real_t p_x, real_t p_y, real_t p_z, real_t p_w) {
40         x = p_x;
41         y = p_y;
42         z = p_z;
43         w = p_w;
44     }
45 
46     this(real_t p_x, real_t p_y, real_t p_z, real_t p_w) {
47         x = p_x;
48         y = p_y;
49         z = p_z;
50         w = p_w;
51     }
52 
53     real_t length() const {
54         return sqrt(lengthSquared());
55     }
56 
57     void normalize() {
58         this /= length();
59     }
60 
61     Quaternion normalized() const {
62         return this / length();
63     }
64 
65     Quaternion inverse() const {
66         return Quaternion(-x, -y, -z, w);
67     }
68 
69     void setEuler(in Vector3 p_euler) {
70         real_t half_a1 = p_euler.x * 0.5;
71         real_t half_a2 = p_euler.y * 0.5;
72         real_t half_a3 = p_euler.z * 0.5;
73 
74         // R = X(a1).Y(a2).Z(a3) convention for Euler angles.
75         // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-2)
76         // a3 is the angle of the first rotation, following the notation in this reference.
77 
78         real_t cos_a1 = cos(half_a1);
79         real_t sin_a1 = sin(half_a1);
80         real_t cos_a2 = cos(half_a2);
81         real_t sin_a2 = sin(half_a2);
82         real_t cos_a3 = cos(half_a3);
83         real_t sin_a3 = sin(half_a3);
84 
85         set(sin_a1 * cos_a2 * cos_a3 + sin_a2 * sin_a3 * cos_a1,
86             -sin_a1 * sin_a3 * cos_a2 + sin_a2 * cos_a1 * cos_a3,
87             sin_a1 * sin_a2 * cos_a3 + sin_a3 * cos_a1 * cos_a2,
88             -sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3);
89     }
90 
91     Quaternion slerp(in Quaternion q, in real_t t) const {
92         Quaternion to1;
93         real_t omega, cosom, sinom, scale0, scale1;
94         // calc cosine
95         cosom = dot(q);
96 
97         // adjust signs (if necessary)
98         if (cosom < 0.0) {
99             cosom = -cosom;
100             to1.x = -q.x;
101             to1.y = -q.y;
102             to1.z = -q.z;
103             to1.w = -q.w;
104         } else {
105             to1.x = q.x;
106             to1.y = q.y;
107             to1.z = q.z;
108             to1.w = q.w;
109         }
110         // calculate coefficients
111         if ((1.0 - cosom) > CMP_EPSILON) {
112             // standard case (slerp)
113             omega = acos(cosom);
114             sinom = sin(omega);
115             scale0 = sin((1.0 - t) * omega) / sinom;
116             scale1 = sin(t * omega) / sinom;
117         } else {
118             // "from" and "to" quaternions are very close
119             //  ... so we can do a linear interpolation
120             scale0 = 1.0 - t;
121             scale1 = t;
122         }
123         // calculate final values
124         return Quaternion(
125             scale0 * x + scale1 * to1.x,
126             scale0 * y + scale1 * to1.y,
127             scale0 * z + scale1 * to1.z,
128             scale0 * w + scale1 * to1.w
129         );
130     }
131 
132     Quaternion slerpni(in Quaternion q, in real_t t) const {
133         Quaternion from = this;
134 
135         real_t dot = from.dot(q);
136 
137         if (fabs(dot) > 0.9999)
138             return from;
139 
140         real_t theta = acos(dot),
141         sinT = 1.0 / sin(theta),
142         newFactor = sin(t * theta) * sinT,
143         invFactor = sin((1.0 - t) * theta) * sinT;
144 
145         return Quaternion(invFactor * from.x + newFactor * q.x,
146             invFactor * from.y + newFactor * q.y,
147             invFactor * from.z + newFactor * q.z,
148             invFactor * from.w + newFactor * q.w);
149     }
150 
151     Quaternion cubicSlerp(in Quaternion q, in Quaternion prep, in Quaternion postq, in real_t t) const {
152         //the only way to do slerp :|
153         real_t t2 = (1.0 - t) * t * 2;
154         Quaternion sp = this.slerp(q, t);
155         Quaternion sq = prep.slerpni(postq, t);
156         return sp.slerpni(sq, t2);
157     }
158 
159     void getAxisAndAngle(out Vector3 r_axis, out real_t r_angle) const {
160         r_angle = 2 * acos(w);
161         r_axis.x = x / sqrt(1 - w * w);
162         r_axis.y = y / sqrt(1 - w * w);
163         r_axis.z = z / sqrt(1 - w * w);
164     }
165 
166     Quaternion opBinary(string op : "*")(in Vector3 v) const {
167         return Quaternion(w * v.x + y * v.z - z * v.y,
168             w * v.y + z * v.x - x * v.z,
169             w * v.z + x * v.y - y * v.x,
170             -x * v.x - y * v.y - z * v.z);
171     }
172 
173     Vector3 xform(in Vector3 v) const {
174         Quaternion q = this * v;
175         q *= this.inverse();
176         return Vector3(q.x, q.y, q.z);
177     }
178 
179     this(in Vector3 axis, in real_t angle) {
180         real_t d = axis.length();
181         if (d == 0)
182             set(0, 0, 0, 0);
183         else {
184             real_t sin_angle = sin(angle * 0.5);
185             real_t cos_angle = cos(angle * 0.5);
186             real_t s = sin_angle / d;
187             set(axis.x * s, axis.y * s, axis.z * s,
188                 cos_angle);
189         }
190     }
191 
192     this(in Vector3 v0, in Vector3 v1) // shortest arc
193     {
194         Vector3 c = v0.cross(v1);
195         real_t d = v0.dot(v1);
196 
197         if (d < -1.0 + CMP_EPSILON) {
198             x = 0;
199             y = 1;
200             z = 0;
201             w = 0;
202         } else {
203             real_t s = sqrt((1.0 + d) * 2.0);
204             real_t rs = 1.0 / s;
205 
206             x = c.x * rs;
207             y = c.y * rs;
208             z = c.z * rs;
209             w = s * 0.5;
210         }
211     }
212 
213     real_t dot(in Quaternion q) const {
214         return x * q.x + y * q.y + z * q.z + w * q.w;
215     }
216 
217     real_t lengthSquared() const {
218         return dot(this);
219     }
220 
221     void opOpAssign(string op : "+")(in Quaternion q) {
222         x += q.x;
223         y += q.y;
224         z += q.z;
225         w += q.w;
226     }
227 
228     void opOpAssign(string op : "-")(in Quaternion q) {
229         x -= q.x;
230         y -= q.y;
231         z -= q.z;
232         w -= q.w;
233     }
234 
235     void opOpAssign(string op : "*")(in Quaternion q) {
236         x *= q.x;
237         y *= q.y;
238         z *= q.z;
239         w *= q.w;
240     }
241 
242     void opOpAssign(string op : "*")(in real_t s) {
243         x *= s;
244         y *= s;
245         z *= s;
246         w *= s;
247     }
248 
249     void opOpAssign(string op : "/")(in real_t s) {
250         this *= 1.0 / s;
251     }
252 
253     Quaternion opBinary(string op : "+")(in Quaternion q2) const {
254         Quaternion q1 = this;
255         return Quaternion(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w);
256     }
257 
258     Quaternion opBinary(string op : "-")(in Quaternion q2) const {
259         Quaternion q1 = this;
260         return Quaternion(q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w);
261     }
262 
263     Quaternion opBinary(string op : "*")(in Quaternion q2) const {
264         Quaternion q1 = this;
265         q1 *= q2;
266         return q1;
267     }
268 
269     Quaternion opUnary(string op : "-")() const {
270         return Quaternion(-x, -y, -z, -w);
271     }
272 
273     Quaternion opBinary(string op : "*")(in real_t s) const {
274         return Quaternion(x * s, y * s, z * s, w * s);
275     }
276 
277     Quaternion opBinary(string op : "/")(in real_t s) const {
278         return this * (1.0 / s);
279     }
280 
281     Vector3 getEuler() const {
282         Basis m = Basis(this);
283         return m.getEuler();
284     }
285 }