1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #include "Math/BsQuaternion.h" |
4 | |
5 | #include "Math/BsMath.h" |
6 | #include "Math/BsMatrix3.h" |
7 | #include "Math/BsVector3.h" |
8 | |
9 | namespace bs |
10 | { |
11 | const Quaternion Quaternion::ZERO{BS_ZERO()}; |
12 | const Quaternion Quaternion::IDENTITY{BS_IDENTITY()}; |
13 | |
14 | void Quaternion::fromRotationMatrix(const Matrix3& mat) |
15 | { |
16 | // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes |
17 | // article "Quaternion Calculus and Fast Animation". |
18 | |
19 | float trace = mat[0][0]+mat[1][1]+mat[2][2]; |
20 | float root; |
21 | |
22 | if (trace > 0.0f) |
23 | { |
24 | // |w| > 1/2, may as well choose w > 1/2 |
25 | root = Math::sqrt(trace + 1.0f); // 2w |
26 | w = 0.5f*root; |
27 | root = 0.5f/root; // 1/(4w) |
28 | x = (mat[2][1]-mat[1][2])*root; |
29 | y = (mat[0][2]-mat[2][0])*root; |
30 | z = (mat[1][0]-mat[0][1])*root; |
31 | } |
32 | else |
33 | { |
34 | // |w| <= 1/2 |
35 | static UINT32 nextLookup[3] = { 1, 2, 0 }; |
36 | UINT32 i = 0; |
37 | |
38 | if (mat[1][1] > mat[0][0]) |
39 | i = 1; |
40 | |
41 | if (mat[2][2] > mat[i][i]) |
42 | i = 2; |
43 | |
44 | UINT32 j = nextLookup[i]; |
45 | UINT32 k = nextLookup[j]; |
46 | |
47 | root = Math::sqrt(mat[i][i]-mat[j][j]-mat[k][k] + 1.0f); |
48 | |
49 | float* cmpntLookup[3] = { &x, &y, &z }; |
50 | *cmpntLookup[i] = 0.5f*root; |
51 | root = 0.5f/root; |
52 | |
53 | w = (mat[k][j]-mat[j][k])*root; |
54 | *cmpntLookup[j] = (mat[j][i]+mat[i][j])*root; |
55 | *cmpntLookup[k] = (mat[k][i]+mat[i][k])*root; |
56 | } |
57 | |
58 | normalize(); |
59 | } |
60 | |
61 | void Quaternion::fromAxisAngle(const Vector3& axis, const Radian& angle) |
62 | { |
63 | Radian halfAngle (0.5f*angle); |
64 | float sin = Math::sin(halfAngle); |
65 | |
66 | w = Math::cos(halfAngle); |
67 | x = sin*axis.x; |
68 | y = sin*axis.y; |
69 | z = sin*axis.z; |
70 | } |
71 | |
72 | void Quaternion::fromAxes(const Vector3& xaxis, const Vector3& yaxis, const Vector3& zaxis) |
73 | { |
74 | Matrix3 kRot; |
75 | |
76 | kRot[0][0] = xaxis.x; |
77 | kRot[1][0] = xaxis.y; |
78 | kRot[2][0] = xaxis.z; |
79 | |
80 | kRot[0][1] = yaxis.x; |
81 | kRot[1][1] = yaxis.y; |
82 | kRot[2][1] = yaxis.z; |
83 | |
84 | kRot[0][2] = zaxis.x; |
85 | kRot[1][2] = zaxis.y; |
86 | kRot[2][2] = zaxis.z; |
87 | |
88 | fromRotationMatrix(kRot); |
89 | } |
90 | |
91 | void Quaternion::fromEulerAngles(const Radian& xAngle, const Radian& yAngle, const Radian& zAngle) |
92 | { |
93 | Radian halfXAngle = xAngle * 0.5f; |
94 | Radian halfYAngle = yAngle * 0.5f; |
95 | Radian halfZAngle = zAngle * 0.5f; |
96 | |
97 | float cx = Math::cos(halfXAngle); |
98 | float sx = Math::sin(halfXAngle); |
99 | |
100 | float cy = Math::cos(halfYAngle); |
101 | float sy = Math::sin(halfYAngle); |
102 | |
103 | float cz = Math::cos(halfZAngle); |
104 | float sz = Math::sin(halfZAngle); |
105 | |
106 | Quaternion quatX(cx, sx, 0.0f, 0.0f); |
107 | Quaternion quatY(cy, 0.0f, sy, 0.0f); |
108 | Quaternion quatZ(cz, 0.0f, 0.0f, sz); |
109 | |
110 | *this = quatZ * (quatX * quatY); |
111 | } |
112 | |
113 | void Quaternion::fromEulerAngles(const Radian& xAngle, const Radian& yAngle, const Radian& zAngle, EulerAngleOrder order) |
114 | { |
115 | static constexpr const EulerAngleOrderData EA_LOOKUP[6] = { { 0, 1, 2}, { 0, 2, 1}, { 1, 0, 2}, |
116 | { 1, 2, 0}, { 2, 0, 1}, { 2, 1, 0} }; |
117 | const EulerAngleOrderData& l = EA_LOOKUP[(int)order]; |
118 | |
119 | Radian halfXAngle = xAngle * 0.5f; |
120 | Radian halfYAngle = yAngle * 0.5f; |
121 | Radian halfZAngle = zAngle * 0.5f; |
122 | |
123 | float cx = Math::cos(halfXAngle); |
124 | float sx = Math::sin(halfXAngle); |
125 | |
126 | float cy = Math::cos(halfYAngle); |
127 | float sy = Math::sin(halfYAngle); |
128 | |
129 | float cz = Math::cos(halfZAngle); |
130 | float sz = Math::sin(halfZAngle); |
131 | |
132 | Quaternion quats[3]; |
133 | quats[0] = Quaternion(cx, sx, 0.0f, 0.0f); |
134 | quats[1] = Quaternion(cy, 0.0f, sy, 0.0f); |
135 | quats[2] = Quaternion(cz, 0.0f, 0.0f, sz); |
136 | |
137 | *this = quats[l.c] * (quats[l.b] * quats[l.a]); |
138 | } |
139 | |
140 | void Quaternion::toRotationMatrix(Matrix3& mat) const |
141 | { |
142 | float tx = x+x; |
143 | float ty = y+y; |
144 | float tz = z+z; |
145 | float twx = tx*w; |
146 | float twy = ty*w; |
147 | float twz = tz*w; |
148 | float txx = tx*x; |
149 | float txy = ty*x; |
150 | float txz = tz*x; |
151 | float tyy = ty*y; |
152 | float tyz = tz*y; |
153 | float tzz = tz*z; |
154 | |
155 | mat[0][0] = 1.0f-(tyy+tzz); |
156 | mat[0][1] = txy-twz; |
157 | mat[0][2] = txz+twy; |
158 | mat[1][0] = txy+twz; |
159 | mat[1][1] = 1.0f-(txx+tzz); |
160 | mat[1][2] = tyz-twx; |
161 | mat[2][0] = txz-twy; |
162 | mat[2][1] = tyz+twx; |
163 | mat[2][2] = 1.0f-(txx+tyy); |
164 | } |
165 | |
166 | void Quaternion::toAxisAngle(Vector3& axis, Radian& angle) const |
167 | { |
168 | float sqrLength = x*x+y*y+z*z; |
169 | if ( sqrLength > 0.0 ) |
170 | { |
171 | angle = 2.0*Math::acos(w); |
172 | float invLength = Math::invSqrt(sqrLength); |
173 | axis.x = x*invLength; |
174 | axis.y = y*invLength; |
175 | axis.z = z*invLength; |
176 | } |
177 | else |
178 | { |
179 | // Angle is 0 (mod 2*pi), so any axis will do |
180 | angle = Radian(0.0); |
181 | axis.x = 1.0; |
182 | axis.y = 0.0; |
183 | axis.z = 0.0; |
184 | } |
185 | } |
186 | |
187 | void Quaternion::toAxes(Vector3& xaxis, Vector3& yaxis, Vector3& zaxis) const |
188 | { |
189 | Matrix3 matRot; |
190 | toRotationMatrix(matRot); |
191 | |
192 | xaxis.x = matRot[0][0]; |
193 | xaxis.y = matRot[1][0]; |
194 | xaxis.z = matRot[2][0]; |
195 | |
196 | yaxis.x = matRot[0][1]; |
197 | yaxis.y = matRot[1][1]; |
198 | yaxis.z = matRot[2][1]; |
199 | |
200 | zaxis.x = matRot[0][2]; |
201 | zaxis.y = matRot[1][2]; |
202 | zaxis.z = matRot[2][2]; |
203 | } |
204 | |
205 | bool Quaternion::toEulerAngles(Radian& xAngle, Radian& yAngle, Radian& zAngle) const |
206 | { |
207 | Matrix3 matRot; |
208 | toRotationMatrix(matRot); |
209 | return matRot.toEulerAngles(xAngle, yAngle, zAngle); |
210 | } |
211 | |
212 | Vector3 Quaternion::xAxis() const |
213 | { |
214 | float fTy = 2.0f*y; |
215 | float fTz = 2.0f*z; |
216 | float fTwy = fTy*w; |
217 | float fTwz = fTz*w; |
218 | float fTxy = fTy*x; |
219 | float fTxz = fTz*x; |
220 | float fTyy = fTy*y; |
221 | float fTzz = fTz*z; |
222 | |
223 | return Vector3(1.0f-(fTyy+fTzz), fTxy+fTwz, fTxz-fTwy); |
224 | } |
225 | |
226 | Vector3 Quaternion::yAxis() const |
227 | { |
228 | float fTx = 2.0f*x; |
229 | float fTy = 2.0f*y; |
230 | float fTz = 2.0f*z; |
231 | float fTwx = fTx*w; |
232 | float fTwz = fTz*w; |
233 | float fTxx = fTx*x; |
234 | float fTxy = fTy*x; |
235 | float fTyz = fTz*y; |
236 | float fTzz = fTz*z; |
237 | |
238 | return Vector3(fTxy-fTwz, 1.0f-(fTxx+fTzz), fTyz+fTwx); |
239 | } |
240 | |
241 | Vector3 Quaternion::zAxis() const |
242 | { |
243 | float fTx = 2.0f*x; |
244 | float fTy = 2.0f*y; |
245 | float fTz = 2.0f*z; |
246 | float fTwx = fTx*w; |
247 | float fTwy = fTy*w; |
248 | float fTxx = fTx*x; |
249 | float fTxz = fTz*x; |
250 | float fTyy = fTy*y; |
251 | float fTyz = fTz*y; |
252 | |
253 | return Vector3(fTxz+fTwy, fTyz-fTwx, 1.0f-(fTxx+fTyy)); |
254 | } |
255 | |
256 | Quaternion Quaternion::inverse() const |
257 | { |
258 | float fNorm = w*w+x*x+y*y+z*z; |
259 | if (fNorm > 0.0f) |
260 | { |
261 | float fInvNorm = 1.0f/fNorm; |
262 | return Quaternion(w*fInvNorm,-x*fInvNorm,-y*fInvNorm,-z*fInvNorm); |
263 | } |
264 | else |
265 | { |
266 | // Return an invalid result to flag the error |
267 | return ZERO; |
268 | } |
269 | } |
270 | |
271 | Vector3 Quaternion::rotate(const Vector3& v) const |
272 | { |
273 | // Note: Does compiler generate fast code here? Perhaps its better to pull all code locally without constructing |
274 | // an intermediate matrix. |
275 | Matrix3 rot; |
276 | toRotationMatrix(rot); |
277 | return rot.multiply(v); |
278 | } |
279 | |
280 | void Quaternion::lookRotation(const Vector3& forwardDir) |
281 | { |
282 | if (forwardDir == Vector3::ZERO) |
283 | return; |
284 | |
285 | Vector3 nrmForwardDir = Vector3::normalize(forwardDir); |
286 | Vector3 currentForwardDir = -zAxis(); |
287 | |
288 | if ((nrmForwardDir + currentForwardDir).squaredLength() < 0.00005f) |
289 | { |
290 | // Oops, a 180 degree turn (infinite possible rotation axes) |
291 | // Default to yaw i.e. use current UP |
292 | *this = Quaternion(-y, -z, w, x); |
293 | } |
294 | else |
295 | { |
296 | // Derive shortest arc to new direction |
297 | Quaternion rotQuat = getRotationFromTo(currentForwardDir, nrmForwardDir); |
298 | *this = rotQuat * *this; |
299 | } |
300 | } |
301 | |
302 | void Quaternion::lookRotation(const Vector3& forwardDir, const Vector3& upDir) |
303 | { |
304 | Vector3 forward = Vector3::normalize(forwardDir); |
305 | Vector3 up = Vector3::normalize(upDir); |
306 | |
307 | if (Math::approxEquals(Vector3::dot(forward, up), 1.0f)) |
308 | { |
309 | lookRotation(forward); |
310 | return; |
311 | } |
312 | |
313 | Vector3 x = Vector3::cross(forward, up); |
314 | Vector3 y = Vector3::cross(x, forward); |
315 | |
316 | x.normalize(); |
317 | y.normalize(); |
318 | |
319 | *this = Quaternion(x, y, -forward); |
320 | } |
321 | |
322 | Quaternion Quaternion::slerp(float t, const Quaternion& p, const Quaternion& q, bool shortestPath) |
323 | { |
324 | float cos = p.dot(q); |
325 | Quaternion quat; |
326 | |
327 | if (cos < 0.0f && shortestPath) |
328 | { |
329 | cos = -cos; |
330 | quat = -q; |
331 | } |
332 | else |
333 | { |
334 | quat = q; |
335 | } |
336 | |
337 | if (Math::abs(cos) < 1 - EPSILON) |
338 | { |
339 | // Standard case (slerp) |
340 | float sin = Math::sqrt(1 - Math::sqr(cos)); |
341 | Radian angle = Math::atan2(sin, cos); |
342 | float invSin = 1.0f / sin; |
343 | float coeff0 = Math::sin((1.0f - t) * angle) * invSin; |
344 | float coeff1 = Math::sin(t * angle) * invSin; |
345 | return coeff0 * p + coeff1 * quat; |
346 | } |
347 | else |
348 | { |
349 | // There are two situations: |
350 | // 1. "p" and "q" are very close (fCos ~= +1), so we can do a linear |
351 | // interpolation safely. |
352 | // 2. "p" and "q" are almost inverse of each other (fCos ~= -1), there |
353 | // are an infinite number of possibilities interpolation. but we haven't |
354 | // have method to fix this case, so just use linear interpolation here. |
355 | Quaternion ret = (1.0f - t) * p + t * quat; |
356 | |
357 | // Taking the complement requires renormalization |
358 | ret.normalize(); |
359 | return ret; |
360 | } |
361 | } |
362 | |
363 | Quaternion Quaternion::getRotationFromTo(const Vector3& from, const Vector3& dest, const Vector3& fallbackAxis) |
364 | { |
365 | // Based on Stan Melax's article in Game Programming Gems |
366 | Quaternion q; |
367 | |
368 | Vector3 v0 = from; |
369 | Vector3 v1 = dest; |
370 | v0.normalize(); |
371 | v1.normalize(); |
372 | |
373 | float d = v0.dot(v1); |
374 | |
375 | // If dot == 1, vectors are the same |
376 | if (d >= 1.0f) |
377 | return Quaternion::IDENTITY; |
378 | |
379 | if (d < (1e-6f - 1.0f)) |
380 | { |
381 | if (fallbackAxis != Vector3::ZERO) |
382 | { |
383 | // Rotate 180 degrees about the fallback axis |
384 | q.fromAxisAngle(fallbackAxis, Radian(Math::PI)); |
385 | } |
386 | else |
387 | { |
388 | // Generate an axis |
389 | Vector3 axis = Vector3::UNIT_X.cross(from); |
390 | if (axis.isZeroLength()) // Pick another if colinear |
391 | axis = Vector3::UNIT_Y.cross(from); |
392 | axis.normalize(); |
393 | q.fromAxisAngle(axis, Radian(Math::PI)); |
394 | } |
395 | } |
396 | else |
397 | { |
398 | float s = Math::sqrt( (1+d)*2 ); |
399 | float invs = 1 / s; |
400 | |
401 | Vector3 c = v0.cross(v1); |
402 | |
403 | q.x = c.x * invs; |
404 | q.y = c.y * invs; |
405 | q.z = c.z * invs; |
406 | q.w = s * 0.5f; |
407 | q.normalize(); |
408 | } |
409 | |
410 | return q; |
411 | } |
412 | } |
413 | |