acme
Foundational cross-platform layer of the ca2 framework
Loading...
Searching...
No Matches
quaternion.h
1// From sequence.h by camilo on 2025-11-14 07:30 <3ThomasBorregaardSørensen!!
2#pragma once
3
4
5#include "acme/prototype/geometry2d/angle.h"
6
7
8template<prototype_floating FLOATING_TYPE>
9struct quaternion_type
10{
11
12
13 using FLOATING = FLOATING_TYPE;
14 static constexpr ::i32 DIMENSION = 4;
15
16
17 union
18 {
19 struct
20 {
21 FLOATING w, x, y, z;
22 };
23 FLOATING m_coordinatea[4];
24 };
25
26 quaternion_type() : w(1), x(0), y(0), z(0) {}
27 quaternion_type(FLOATING _w, FLOATING _x, FLOATING _y, FLOATING _z) : w(_w), x(_x), y(_y), z(_z) {}
28
29 template<prototype_number FLOATING1>
30 quaternion_type(const FLOATING1 *p) : // expects p to point to an array of at least 4 elements
31 w((FLOATING)p[0]), x((FLOATING)p[1]), y((FLOATING)p[2]), z((FLOATING)p[3])
32 {
33 }
34
35 // Construct quaternion from Euler XYZ rotation
36 // rotation = (pitch, yaw, roll) in radians
37 quaternion_type(const ::sequence_type<FLOATING, 3> &euler)
38 {
39 FLOATING cx = std::cos(euler.x * 0.5f);
40 FLOATING sx = std::sin(euler.x * 0.5f);
41
42 FLOATING cy = std::cos(euler.y * 0.5f);
43 FLOATING sy = std::sin(euler.y * 0.5f);
44
45 FLOATING cz = std::cos(euler.z * 0.5f);
46 FLOATING sz = std::sin(euler.z * 0.5f);
47
48 // GLM uses Z * Y * X intrinsic rotation order
49 w = cx * cy * cz + sx * sy * sz;
50 x = sx * cy * cz - cx * sy * sz;
51 y = cx * sy * cz + sx * cy * sz;
52 z = cx * cy * sz - sx * sy * cz;
53 }
54
55
56 sequence_type< FLOATING, 3 > euler_angles() const
57 {
58 auto q = this->normalized();
59 return {q._pitch(), q._yaw(), q._roll()};
60 }
61
62
63 quaternion_type & set_euler_angles(const sequence_type <FLOATING, 3 > eulerAngles)
64 {
65
66 auto c = (eulerAngles * (FLOATING)(0.5)).cos();
67
68 auto s = (eulerAngles * (FLOATING)(0.5)).sin();
69
70 this->w = c.x * c.y * c.z + s.x * s.y * s.z;
71 this->x = s.x * c.y * c.z - c.x * s.y * s.z;
72 this->y = c.x * s.y * c.z + s.x * c.y * s.z;
73 this->z = c.x * c.y * s.z - s.x * s.y * c.z;
74
75 return *this;
76
77 }
78
79
80
81 constexpr FLOATING dotted(const quaternion_type &q) const
82 {
83
84 return this->w * q.w + this->x * q.x + this->y * q.y + this->z * q.z;
85
86 }
87
88
89 constexpr FLOATING squared_modulus() const
90 {
91
92 return this->dotted(*this);
93
94 }
95
96 FLOATING modulus() const
97 {
98
99 return ::std::sqrt(this->squared_modulus());
100
101 }
102
103
104 quaternion_type normalized() const
105 {
106
107 auto modulus = this->modulus();
108
109 if (modulus <= (FLOATING)0) // Problem
110 return {(FLOATING)1, (FLOATING)0, (FLOATING)0, (FLOATING)0};
111
112 auto inversedModulus = (FLOATING)1 / modulus;
113
114 return {this->w * inversedModulus, this->x * inversedModulus, this->y * inversedModulus,
115 this->z * inversedModulus};
116
117 }
118
119
120 quaternion_type crossed(const quaternion_type &q2) const
121 {
122 const auto &q1 = *this;
123 return {
124 q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z, q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
125 q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z, q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x};
126 }
127
128 inline quaternion_type slerp(const quaternion_type &q2, FLOATING t) const
129 {
130 const auto &q1 = *this;
131 // Compute the cosine of the angle
132 FLOATING dot = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z;
133
134 quaternion_type q2c = q2;
135
136 // If dot < 0, q2 is on the opposite hemisphere -> take the shorter path
137 if (dot < (FLOATING)0)
138 {
139 dot = -dot;
140 q2c.w = -q2c.w;
141 q2c.x = -q2c.x;
142 q2c.y = -q2c.y;
143 q2c.z = -q2c.z;
144 }
145
146 // If close, fall back to lerp
147 const FLOATING EPS = 1e-6f;
148 if (dot > (FLOATING)1 - EPS)
149 {
150 // Linear interpolation + renormalize
151 quaternion_type result;
152 result.w = q1.w + t * (q2c.w - q1.w);
153 result.x = q1.x + t * (q2c.x - q1.x);
154 result.y = q1.y + t * (q2c.y - q1.y);
155 result.z = q1.z + t * (q2c.z - q1.z);
156
157 // Normalize
158 FLOATING invLen =
159 (FLOATING)1 / sqrtf(result.w * result.w + result.x * result.x + result.y * result.y + result.z * result.z);
160 result.w *= invLen;
161 result.x *= invLen;
162 result.y *= invLen;
163 result.z *= invLen;
164
165 return result;
166 }
167
168 // Compute the angle θ
169 FLOATING theta = acosf(dot);
170 FLOATING sinTheta = sinf(theta);
171
172 FLOATING w1 = sinf(((FLOATING)1 - t) * theta) / sinTheta;
173 FLOATING w2 = sinf(t * theta) / sinTheta;
174
175 quaternion_type result;
176 result.w = w1 * q1.w + w2 * q2c.w;
177 result.x = w1 * q1.x + w2 * q2c.x;
178 result.y = w1 * q1.y + w2 * q2c.y;
179 result.z = w1 * q1.z + w2 * q2c.z;
180
181 return result;
182 }
183
184
185 //inline quaternion_type normalized() const
186 //{
187
188 // FLOATING len = std::sqrt(this->w * this->w + this->x * this->x + this->y * this->y + this->z * this->z);
189
190 // // Avoid division by zero
191 // if (len == (FLOATING)0)
192 // {
193 //
194 // return {(FLOATING)1, (FLOATING)0, (FLOATING)0, (FLOATING)0}; // identity rotation
195
196 // }
197
198 // FLOATING inv = (FLOATING)1 / len;
199
200 // return {this->w * inv, this->x * inv, this->y * inv, this->z * inv};
201
202 //}
203
204
205 inline quaternion_type & normalize()
206 {
207
208 return *this = this->normalized();
209
210 }
211
212
215 //EulerAngles ToEulerAngles(Quaternion q)
216 //{
217 // EulerAngles angles;
218
219 // // roll (x-axis rotation)
220 // ::f64 sinr_cosp = 2 * (q.w * q.x + q.y * q.z);
221 // ::f64 cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y);
222 // angles.roll = std::atan2(sinr_cosp, cosr_cosp);
223
224 // // pitch (y-axis rotation)
225 // ::f64 sinp = std::sqrt(1 + 2 * (q.w * q.y - q.x * q.z));
226 // ::f64 cosp = std::sqrt(1 - 2 * (q.w * q.y - q.x * q.z));
227 // angles.pitch = 2 * std::atan2(sinp, cosp) - M_PI / 2;
228
229 // // yaw (z-axis rotation)
230 // ::f64 siny_cosp = 2 * (q.w * q.z + q.x * q.y);
231 // ::f64 cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z);
232 // angles.yaw = std::atan2(siny_cosp, cosy_cosp);
233
234 // return angles;
235 //}
236
237
238
239 quaternion_type &set_yaw_and_pitch(const angle_type<FLOATING> &yaw, const angle_type<FLOATING> &pitch)
240 {
241
242 auto cosp = (pitch * (FLOATING)0.5).cos();
243 auto sinp = (pitch * (FLOATING)0.5).sin();
244 auto cosy = (yaw * (FLOATING)0.5).cos();
245 auto siny = (yaw * (FLOATING)0.5).sin();
246
247 this->w = cosp * cosy;
248 this->x = sinp * cosy;
249 this->y = cosp * siny;
250 this->z = - sinp * siny;
251
252 return *this;
253
254 }
255
256
257 // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
258 quaternion_type &set_yaw__pitch_and_roll(const angle_type<FLOATING> &yaw, const angle_type<FLOATING> &pitch,
259 const angle_type<FLOATING> &roll)
260 {
261
262 // Abbreviations for the various angular functions
263
264 FLOATING half_yaw = yaw.m_fAngle * FLOATING(0.5);
265 FLOATING half_pitch = pitch.m_fAngle * FLOATING(0.5);
266 FLOATING half_roll = roll.m_fAngle * FLOATING(0.5);
267
268
269 ::f64 cr = cos(half_roll);
270 ::f64 sr = sin(half_roll);
271 ::f64 cp = cos(half_pitch);
272 ::f64 sp = sin(half_pitch);
273 ::f64 cy = cos(half_yaw);
274 ::f64 sy = sin(half_yaw);
275
276 auto & q = *this;
277 q.w = cr * cp * cy + sr * sp * sy;
278 q.x = sr * cp * cy - cr * sp * sy;
279 q.y = cr * sp * cy + sr * cp * sy;
280 q.z = cr * cp * sy - sr * sp * cy;
281
282 return q;
283
284}
285
286
287angle_type < FLOATING > _roll() const
288{
289 auto const y = (FLOATING)(2) * (this->x * this->y + this->w * this->z);
290 auto const x = this->w * this->w + this->x * this->x - this->y * this->y - this->z * this->z;
291
292 //if (all(equal(vec<2, T, Q>(x, y), vec<2, T, Q>(0), epsilon<T>()))) // avoid atan2(0,0) - handle singularity - Matiis
293 if (::std::abs(x) < std::numeric_limits<FLOATING>::epsilon() &&
294 ::std::abs(y) < std::numeric_limits<FLOATING>::epsilon())
295 return ::radians((FLOATING) 0);
296
297 return ::geometry::atan2(y, x);
298}
299
300 constexpr angle_type<FLOATING> roll() const
301 { return this->normalized()._roll();
302 }
303//
304//template<typename T, qualifier Q>
305//GLM_FUNC_QUALIFIER T pitch(qua<T, Q> const &q)
306//{
307// // return T(atan(T(2) * (q.y * q.z + q.w * q.x), q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z));
308// T const y = static_cast<T>(2) * (q.y * q.z + q.w * q.x);
309// T const x = q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z;
310//
311// if (all(equal(vec<2, T, Q>(x, y), vec<2, T, Q>(0), epsilon<T>()))) // avoid atan2(0,0) - handle singularity - Matiis
312// return static_cast<T>(static_cast<T>(2) * atan(q.x, q.w));
313//
314// return static_cast<T>(atan(y, x));
315//}
316 constexpr angle_type < FLOATING > _pitch() const
317 {
318
319 auto const y = (FLOATING)(2) * (this->y * this->z + this->w * this->x);
320 auto const x = this->w * this->w - this->x * this->x
321 - this->y * this->y + this->z * this->z;
322
323 if (::std::abs(x) < std::numeric_limits<FLOATING>::epsilon() &&
324 ::std::abs(y) < std::numeric_limits<FLOATING>::epsilon())
325 //all(equal(vec<2, T, Q>(x, y), vec<2, T, Q>(0),
326 // ()))) // avoid atan2(0,0) - handle singularity - Matiis
327 return (FLOATING)(2) * ::geometry::atan2(this->x, this->w);
328
329 return ::geometry::atan2(y, x);
330
331
332 //auto &q = *this;
333 //FLOATING a = (FLOATING)2 * (q.w * q.y - q.x * q.z);
337
338
339 //return ::geometry::asin(a);
340
341 //FLOATING sinp = (FLOATING)2 * (this->w * this->x - this->y * this->z);
342 //
343 //sinp = ::geometry::clamp(sinp, (FLOATING)-1, (FLOATING)1);
344
345 //return ::geometry::asin(sinp);
346
347 }
348
349
350 constexpr angle_type<FLOATING> pitch() const
351 {
352
353 return this->normalized()._pitch();
354
355 }
356
357
358 constexpr angle_type<FLOATING> _yaw() const
359 {
360
362 //auto q = *this;
363
365 //auto siny_cosp = (FLOATING)2 * (q.w * q.z + q.x * q.y);
366 //auto cosy_cosp = (FLOATING)1 - (FLOATING)2 * (q.y * q.y + q.z * q.z);
368
374 //auto yaw = ::geometry::atan2(siny_cosp, cosy_cosp);
375
380
381 return ::geometry::asin(
382 (FLOATING)(-2) * (this->x * this->z - this->w * this->y));
383
384 }
385
386
387 constexpr angle_type<FLOATING> yaw() const
388 {
389
390 return this->normalized()._yaw();
391
392 }
393
394
395 quaternion_type & offset_yaw_and_pitch_with_constraints(
396 const ::angle_type<FLOATING> & angleΔYaw,
397 const ::angle_type<FLOATING> & angleΔPitch,
398 const ::angle_type<FLOATING> & angleMinimumPitch,
399 const ::angle_type<FLOATING> & angleMaximumPitch
400 )
401 {
402
403 this->normalize();
404
405 auto eulerAngles = this->euler_angles();
406
407 eulerAngles.x += angleΔPitch.m_fAngle;
408
409 eulerAngles.y += angleΔYaw.m_fAngle;
410
411 eulerAngles.x = ::geometry::clamp(eulerAngles.x, angleMinimumPitch.m_fAngle, angleMaximumPitch.m_fAngle);
412
413 this->set_euler_angles(eulerAngles);
414
415 return *this;
416
417 }
418
419
420 constexpr sequence_type < FLOATING, 3 > front() const
421 {
422
424
425 front.x = (FLOATING)2 * (this->x * this->z + this->w * this->y);
426 front.y = (FLOATING)2 * (this->y * this->z - this->w * this->x);
427 front.z = (FLOATING)1 - (FLOATING)2 * (this->x * this->x + this->y * this->y);
428
429 front.normalize();
430
431 return front;
432
433 }
434
435};
436
437
438using f32_quaternion = quaternion_type<::f32>;
439using f64_quaternion = quaternion_type<::f64>;
440
441
442
443
Definition angle.h:18
Definition quaternion.h:10
Definition sequence.h:236