1 | /* graphene-euler.c: Euler angles |
2 | * |
3 | * SPDX-License-Identifier: MIT |
4 | * |
5 | * Copyright 2014 Emmanuele Bassi |
6 | * |
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | * of this software and associated documentation files (the "Software"), to deal |
9 | * in the Software without restriction, including without limitation the rights |
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | * copies of the Software, and to permit persons to whom the Software is |
12 | * furnished to do so, subject to the following conditions: |
13 | * |
14 | * The above copyright notice and this permission notice shall be included in |
15 | * all copies or substantial portions of the Software. |
16 | * |
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23 | * THE SOFTWARE. |
24 | */ |
25 | |
26 | /** |
27 | * SECTION:graphene-euler |
28 | * @Title: Euler |
29 | * @Short_description: Euler angles |
30 | * |
31 | * The #graphene_euler_t structure defines a rotation along three axes using |
32 | * three angles. It also optionally can describe the order of the rotations. |
33 | * |
34 | * [Euler's rotation theorem](https://en.wikipedia.org/wiki/Euler%27s_rotation_theorem) |
35 | * states that, in three-dimensional space, any displacement of a rigid body |
36 | * such that a point on the rigid body remains fixed, is equivalent to a single |
37 | * rotation about some axis that runs through the fixed point. The angles on |
38 | * each axis can be placed in a vector of three components—α, β, and γ—called |
39 | * the *Euler angle vector*. Each rotation described by these components results |
40 | * in a rotation matrix: |
41 | * |
42 | * |[ |
43 | * rot(α) = A |
44 | * rot(β) = B |
45 | * rot(γ) = G |
46 | * ]| |
47 | * |
48 | * The resulting rotation matrix expressed by the Euler angle vector is |
49 | * given by the product of each rotation matrix: |
50 | * |
51 | * |[ |
52 | * G × B × A = R |
53 | * ]| |
54 | * |
55 | * In order to specify the meaning of an Euler angle vector, we need to |
56 | * assign each axis of rotation to the corresponding α, β, and γ components, |
57 | * for instance X, Y, and Z. |
58 | * |
59 | * Additionally, we need to specify whether the rotations move the axes |
60 | * as they are applied, also known as intrinsic, or relative rotations; |
61 | * or if the axes stay fixed and the vectors move within the axis frame, |
62 | * also known as extrinsic, or static rotations. For instance, a static |
63 | * rotation alongside the ZYX axes will be interpreted as relative to |
64 | * extrinsic coordinate axes, and be performed, in order, about the Z, |
65 | * Y, and finally X axis. A relative rotation alongside the ZXZ axes will |
66 | * be interpreted as relative to intrinsic coordinate axes, and be |
67 | * performed, in order, about the Z axis, about the rotated X axis, and |
68 | * finally about the rotated Z axis. |
69 | * |
70 | * Finally, we need to define the direction of the rotation, or the handedness |
71 | * of the coordinate system. In the case of Graphene, the direction is given |
72 | * by the right-hand rule, which means all rotations are counterclockwise. |
73 | * |
74 | * Rotations described Euler angles are typically immediately understandable, |
75 | * compared to rotations expressed using quaternions, but they are susceptible |
76 | * of ["Gimbal lock"](http://en.wikipedia.org/wiki/Gimbal_lock) — the loss of |
77 | * one degree of freedom caused by two axis on the same plane. You typically |
78 | * should use #graphene_euler_t to expose rotation angles in your API, or to |
79 | * store them, but use #graphene_quaternion_t to apply rotations to modelview |
80 | * matrices, or interpolate between initial and final rotation transformations. |
81 | * |
82 | * For more information, see: |
83 | * |
84 | * - http://en.wikipedia.org/wiki/Rotation_matrix |
85 | * - http://en.wikipedia.org/wiki/Euler_angles |
86 | * - http://mathworld.wolfram.com/EulerAngles.html |
87 | * - "Representing Attitude with Euler Angles and Quaternions: A |
88 | * Reference" by James Diebel, 2006 |
89 | * - "Graphics Gems IV", edited by Paul Heckbert, Academic Press, 1994. |
90 | * |
91 | * See also: #graphene_quaternion_t. |
92 | */ |
93 | |
94 | #include "graphene-private.h" |
95 | |
96 | #include "graphene-euler.h" |
97 | |
98 | #include "graphene-alloc-private.h" |
99 | #include "graphene-matrix.h" |
100 | #include "graphene-quaternion.h" |
101 | #include "graphene-vectors-private.h" |
102 | |
103 | #define EULER_DEFAULT_ORDER GRAPHENE_EULER_ORDER_SXYZ |
104 | |
105 | #define LAST_DEPRECATED GRAPHENE_EULER_ORDER_ZYX |
106 | |
107 | #define ORDER_OFFSET(o) ((o) - (LAST_DEPRECATED + 1)) |
108 | |
109 | /* The orders of rotation we support, minus the deprecated aliases */ |
110 | enum { |
111 | SXYZ, |
112 | SXYX, |
113 | SXZY, |
114 | |
115 | SXZX, |
116 | SYZX, |
117 | SYZY, |
118 | |
119 | SYXZ, |
120 | SYXY, |
121 | SZXY, |
122 | |
123 | SZXZ, |
124 | SZYX, |
125 | SZYZ, |
126 | |
127 | RZYX, |
128 | RXYX, |
129 | RYZX, |
130 | |
131 | RXZX, |
132 | RXZY, |
133 | RYZY, |
134 | |
135 | RZXY, |
136 | RYXY, |
137 | RYXZ, |
138 | |
139 | RZXZ, |
140 | RXYZ, |
141 | RZYZ |
142 | }; |
143 | |
144 | struct axis_param |
145 | { |
146 | /* The initial axis in the permutation */ |
147 | int first_axis; |
148 | /* Parity of the axis permutation (false for even, true for odd) */ |
149 | bool parity; |
150 | /* Repetition of first_axis as the last */ |
151 | bool repetition; |
152 | /* Frame from which the axes are taken (false for static, true for relative) */ |
153 | bool frame; |
154 | }; |
155 | |
156 | /* Map each order to the corresponding parameters of the rotation */ |
157 | static const struct axis_param order_parameters[] = { |
158 | [SXYZ] = { .first_axis: 0, false, false, false }, |
159 | [SXYX] = { .first_axis: 0, false, true, false }, |
160 | [SXZY] = { .first_axis: 0, true, false, false }, |
161 | |
162 | [SXZX] = { .first_axis: 0, true, true, false }, |
163 | [SYZX] = { .first_axis: 1, false, false, false }, |
164 | [SYZY] = { .first_axis: 1, false, true, false }, |
165 | |
166 | [SYXZ] = { .first_axis: 1, true, false, false }, |
167 | [SYXY] = { .first_axis: 1, true, true, false }, |
168 | [SZXY] = { .first_axis: 2, false, false, false }, |
169 | |
170 | [SZXZ] = { .first_axis: 2, false, true, false }, |
171 | [SZYX] = { .first_axis: 2, true, false, false }, |
172 | [SZYZ] = { .first_axis: 2, true, true, false }, |
173 | |
174 | [RZYX] = { .first_axis: 0, false, false, true }, |
175 | [RXYX] = { .first_axis: 0, false, true, true }, |
176 | [RYZX] = { .first_axis: 0, true, false, true }, |
177 | |
178 | [RXZX] = { .first_axis: 0, true, true, true }, |
179 | [RXZY] = { .first_axis: 1, false, false, true }, |
180 | [RYZY] = { .first_axis: 1, false, true, true }, |
181 | |
182 | [RZXY] = { .first_axis: 1, true, false, true }, |
183 | [RYXY] = { .first_axis: 1, true, true, true }, |
184 | [RYXZ] = { .first_axis: 2, false, false, true }, |
185 | |
186 | [RZXZ] = { .first_axis: 2, false, true, true }, |
187 | [RXYZ] = { .first_axis: 2, true, false, true }, |
188 | [RZYZ] = { .first_axis: 2, true, true, true }, |
189 | }; |
190 | |
191 | /* Axis sequences for Euler angles */ |
192 | static const int next_axis[4] = { 1, 2, 0, 1 }; |
193 | |
194 | /* Original code to convert Euler angles to and from a 4x4 matrix is taken |
195 | * from "Graphics Gems IV", edited by Paul Heckbert, Academic Press, 1994. |
196 | * |
197 | * Original author: Ken Shoemake, 1993 |
198 | * |
199 | * https://github.com/erich666/GraphicsGems/blob/master/gemsiv/euler_angle/EulerAngles.c |
200 | */ |
201 | static inline void |
202 | euler_to_matrix (float ai, |
203 | float aj, |
204 | float ak, |
205 | const struct axis_param *params, |
206 | graphene_matrix_t *matrix) |
207 | { |
208 | int i = params->first_axis; |
209 | int j = next_axis[i + (params->parity ? 1 : 0)]; |
210 | int k = next_axis[i - (params->parity ? 1 : 0) + 1]; |
211 | |
212 | if (params->frame) |
213 | { |
214 | float tmp = ai; |
215 | ai = ak; |
216 | ak = tmp; |
217 | } |
218 | |
219 | if (params->parity) |
220 | { |
221 | ai *= -1.f; |
222 | aj *= -1.f; |
223 | ak *= -1.f; |
224 | } |
225 | |
226 | float si, sj, sk; |
227 | float ci, cj, ck; |
228 | |
229 | graphene_sincos (angle: ai, sin_out: &si, cos_out: &ci); |
230 | graphene_sincos (angle: aj, sin_out: &sj, cos_out: &cj); |
231 | graphene_sincos (angle: ak, sin_out: &sk, cos_out: &ck); |
232 | |
233 | float cc = ci * ck; |
234 | float cs = ci * sk; |
235 | float sc = si * ck; |
236 | float ss = si * sk; |
237 | |
238 | float m[16]; |
239 | |
240 | /* Our matrices are row major, however the code below is based on code |
241 | that assumes matrixes apply from the left, and we apply on the |
242 | right, so need to flip row/column. */ |
243 | #define M(m, r, c) (m)[((c) << 2) + (r)] |
244 | |
245 | /* We need to construct the matrix from float values instead |
246 | * of SIMD vectors because the access is parametrised on the |
247 | * axes of the transformation, and it would lead to a |
248 | * combinatorial explosion of branches |
249 | */ |
250 | if (params->repetition) |
251 | { |
252 | M (m, i, i) = cj; |
253 | M (m, i, j) = sj * si; |
254 | M (m, i, k) = sj * ci; |
255 | M (m, j, i) = sj * sk; |
256 | M (m, j, j) = -cj * ss + cc; |
257 | M (m, j, k) = -cj * cs - sc; |
258 | M (m, k, i) = -sj * ck; |
259 | M (m, k, j) = cj * sc + cs; |
260 | M (m, k, k) = cj * cc - ss; |
261 | } |
262 | else |
263 | { |
264 | M (m, i, i) = cj * ck; |
265 | M (m, i, j) = sj * sc - cs; |
266 | M (m, i, k) = sj * cc + ss; |
267 | M (m, j, i) = cj * sk; |
268 | M (m, j, j) = sj * ss + cc; |
269 | M (m, j, k) = sj * cs - sc; |
270 | M (m, k, i) = -sj; |
271 | M (m, k, j) = cj * si; |
272 | M (m, k, k) = cj * ci; |
273 | } |
274 | |
275 | M (m, 3, 0) = M (m, 3, 1) = M (m, 3, 2) = 0.f; |
276 | M (m, 0, 3) = M (m, 1, 3) = M (m, 2, 3) = 0.f; |
277 | M (m, 3, 3) = 1.f; |
278 | |
279 | #undef M |
280 | |
281 | graphene_matrix_init_from_float (m: matrix, v: m); |
282 | } |
283 | |
284 | static inline void |
285 | matrix_to_euler (const graphene_matrix_t *matrix, |
286 | const struct axis_param *params, |
287 | float *ai, |
288 | float *aj, |
289 | float *ak) |
290 | { |
291 | int i = params->first_axis; |
292 | int j = next_axis[i + (params->parity ? 1 : 0)]; |
293 | int k = next_axis[i - (params->parity ? 1 : 0) + 1]; |
294 | |
295 | /* The cell access to the matrix is parametrised on the axes |
296 | * of the transformation, so we cannot use SIMD vectors to |
297 | * avoid a combinatorial explosion of branches, or slow single |
298 | * lane access. |
299 | */ |
300 | float m[16]; |
301 | |
302 | graphene_matrix_to_float (m: matrix, v: m); |
303 | |
304 | /* Our matrices are row major, however the code below is based on code |
305 | that assumes matrixes apply from the left, and we apply on the |
306 | right, so need to flip row/column. */ |
307 | #define M(m, r, c) (m)[((c) << 2) + (r)] |
308 | |
309 | float ax, ay, az; |
310 | if (params->repetition) |
311 | { |
312 | float sy = sqrtf (M (m, i, j) * M (m, i, j) + M (m, i, k) * M (m, i, j)); |
313 | |
314 | if (sy >= 16 * FLT_EPSILON) |
315 | { |
316 | ax = atan2f (M (m, i, j), M (m, i, k)); |
317 | ay = atan2f (y: sy, M (m, i, i)); |
318 | az = atan2f (M (m, j, i), M (m, k, i) * -1.f); |
319 | } |
320 | else |
321 | { |
322 | ax = atan2f (M (m, j, k) * -1.f, M (m, j, j)); |
323 | ay = atan2f (y: sy, M (m, i, i)); |
324 | az = 0.f; |
325 | } |
326 | } |
327 | else |
328 | { |
329 | float cy = sqrtf (M (m, i, i) * M (m, i, i) + M (m, j, i) * M (m, j, i)); |
330 | |
331 | if (cy >= 16 * FLT_EPSILON) |
332 | { |
333 | ax = atan2f (M (m, k, j), M (m, k, k)); |
334 | ay = atan2f (M (m, k, i) * -1.f, x: cy); |
335 | az = atan2f (M (m, j, i), M (m, i, i)); |
336 | } |
337 | else |
338 | { |
339 | ax = atan2f (M (m, j, k) * -1.f, M (m, j, j)); |
340 | ay = atan2f (M (m, k, i) * -1.f, x: cy); |
341 | az = 0.f; |
342 | } |
343 | } |
344 | |
345 | #undef M |
346 | |
347 | if (params->parity) |
348 | { |
349 | ax *= -1.f; |
350 | ay *= -1.f; |
351 | az *= -1.f; |
352 | } |
353 | |
354 | if (params->frame) |
355 | { |
356 | float tmp = ax; |
357 | |
358 | ax = az; |
359 | az = tmp; |
360 | } |
361 | |
362 | if (ai != NULL) |
363 | *ai = ax; |
364 | if (aj != NULL) |
365 | *aj = ay; |
366 | if (ak != NULL) |
367 | *ak = az; |
368 | } |
369 | |
370 | /** |
371 | * graphene_euler_alloc: (constructor) |
372 | * |
373 | * Allocates a new #graphene_euler_t. |
374 | * |
375 | * The contents of the returned structure are undefined. |
376 | * |
377 | * Returns: (transfer full): the newly allocated #graphene_euler_t |
378 | * |
379 | * Since: 1.2 |
380 | */ |
381 | graphene_euler_t * |
382 | graphene_euler_alloc (void) |
383 | { |
384 | return graphene_aligned_alloc0 (size: sizeof (graphene_euler_t), number: 1, alignment: 16); |
385 | } |
386 | |
387 | /** |
388 | * graphene_euler_free: |
389 | * @e: a #graphene_euler_t |
390 | * |
391 | * Frees the resources allocated by graphene_euler_alloc(). |
392 | * |
393 | * Since: 1.2 |
394 | */ |
395 | void |
396 | graphene_euler_free (graphene_euler_t *e) |
397 | { |
398 | graphene_aligned_free (mem: e); |
399 | } |
400 | |
401 | /*< private > |
402 | * graphene_euler_get_real_order: |
403 | * @order: a #graphene_euler_order_t |
404 | * |
405 | * Normalizes the given enumeration value to remove aliases and |
406 | * deprecated values. |
407 | * |
408 | * Returns: the real order |
409 | */ |
410 | static graphene_euler_order_t |
411 | graphene_euler_get_real_order (graphene_euler_order_t order) |
412 | { |
413 | switch (order) |
414 | { |
415 | case GRAPHENE_EULER_ORDER_XYZ: |
416 | return GRAPHENE_EULER_ORDER_SXYZ; |
417 | |
418 | case GRAPHENE_EULER_ORDER_YXZ: |
419 | return GRAPHENE_EULER_ORDER_SYXZ; |
420 | |
421 | case GRAPHENE_EULER_ORDER_ZXY: |
422 | return GRAPHENE_EULER_ORDER_SZXY; |
423 | |
424 | case GRAPHENE_EULER_ORDER_ZYX: |
425 | return GRAPHENE_EULER_ORDER_SZYX; |
426 | |
427 | case GRAPHENE_EULER_ORDER_YZX: |
428 | return GRAPHENE_EULER_ORDER_SYZX; |
429 | |
430 | case GRAPHENE_EULER_ORDER_XZY: |
431 | return GRAPHENE_EULER_ORDER_SXZY; |
432 | |
433 | case GRAPHENE_EULER_ORDER_DEFAULT: |
434 | return GRAPHENE_EULER_ORDER_SXYZ; |
435 | |
436 | default: |
437 | break; |
438 | } |
439 | |
440 | return order; |
441 | } |
442 | |
443 | /*< private > |
444 | * graphene_euler_init_internal: |
445 | * @e: the #graphene_euler_t to initialize |
446 | * @x: rotation angle on the X axis, in radians |
447 | * @y: rotation angle on the Y axis, in radians |
448 | * @z: rotation angle on the Z axis, in radians |
449 | * @order: order of rotations |
450 | * |
451 | * Initializes a #graphene_euler_t using the given angles |
452 | * and order of rotation. |
453 | * |
454 | * Returns: (transfer none): the initialized #graphene_euler_t |
455 | */ |
456 | static graphene_euler_t * |
457 | graphene_euler_init_internal (graphene_euler_t *e, |
458 | float rad_x, |
459 | float rad_y, |
460 | float rad_z, |
461 | graphene_euler_order_t order) |
462 | { |
463 | graphene_vec3_init (v: &e->angles, x: rad_x, y: rad_y, z: rad_z); |
464 | e->order = graphene_euler_get_real_order (order); |
465 | |
466 | return e; |
467 | } |
468 | |
469 | /** |
470 | * graphene_euler_init: |
471 | * @e: the #graphene_euler_t to initialize |
472 | * @x: rotation angle on the X axis, in degrees |
473 | * @y: rotation angle on the Y axis, in degrees |
474 | * @z: rotation angle on the Z axis, in degrees |
475 | * |
476 | * Initializes a #graphene_euler_t using the given angles. |
477 | * |
478 | * The order of the rotations is %GRAPHENE_EULER_ORDER_DEFAULT. |
479 | * |
480 | * Returns: (transfer none): the initialized #graphene_euler_t |
481 | * |
482 | * Since: 1.2 |
483 | */ |
484 | graphene_euler_t * |
485 | graphene_euler_init (graphene_euler_t *e, |
486 | float x, |
487 | float y, |
488 | float z) |
489 | { |
490 | return graphene_euler_init_internal (e, |
491 | GRAPHENE_DEG_TO_RAD (x), |
492 | GRAPHENE_DEG_TO_RAD (y), |
493 | GRAPHENE_DEG_TO_RAD (z), |
494 | order: GRAPHENE_EULER_ORDER_DEFAULT); |
495 | } |
496 | |
497 | /** |
498 | * graphene_euler_init_with_order: |
499 | * @e: the #graphene_euler_t to initialize |
500 | * @x: rotation angle on the X axis, in degrees |
501 | * @y: rotation angle on the Y axis, in degrees |
502 | * @z: rotation angle on the Z axis, in degrees |
503 | * @order: the order used to apply the rotations |
504 | * |
505 | * Initializes a #graphene_euler_t with the given angles and @order. |
506 | * |
507 | * Returns: (transfer none): the initialized #graphene_euler_t |
508 | * |
509 | * Since: 1.2 |
510 | */ |
511 | graphene_euler_t * |
512 | graphene_euler_init_with_order (graphene_euler_t *e, |
513 | float x, |
514 | float y, |
515 | float z, |
516 | graphene_euler_order_t order) |
517 | { |
518 | return graphene_euler_init_internal (e, |
519 | GRAPHENE_DEG_TO_RAD (x), |
520 | GRAPHENE_DEG_TO_RAD (y), |
521 | GRAPHENE_DEG_TO_RAD (z), |
522 | order); |
523 | } |
524 | |
525 | /** |
526 | * graphene_euler_init_from_matrix: |
527 | * @e: the #graphene_euler_t to initialize |
528 | * @m: (nullable): a rotation matrix |
529 | * @order: the order used to apply the rotations |
530 | * |
531 | * Initializes a #graphene_euler_t using the given rotation matrix. |
532 | * |
533 | * If the #graphene_matrix_t @m is %NULL, the #graphene_euler_t will |
534 | * be initialized with all angles set to 0. |
535 | * |
536 | * Returns: (transfer none): the initialized #graphene_euler_t |
537 | * |
538 | * Since: 1.2 |
539 | */ |
540 | graphene_euler_t * |
541 | graphene_euler_init_from_matrix (graphene_euler_t *e, |
542 | const graphene_matrix_t *m, |
543 | graphene_euler_order_t order) |
544 | { |
545 | float x, y, z; |
546 | |
547 | if (m == NULL || graphene_matrix_is_identity (m)) |
548 | return graphene_euler_init_with_order (e, x: 0.f, y: 0.f, z: 0.f, order); |
549 | |
550 | order = graphene_euler_get_real_order (order); |
551 | matrix_to_euler (matrix: m, params: &order_parameters[ORDER_OFFSET (order)], ai: &x, aj: &y, ak: &z); |
552 | graphene_euler_init_internal (e, rad_x: x, rad_y: y, rad_z: z, order); |
553 | |
554 | return e; |
555 | } |
556 | |
557 | /** |
558 | * graphene_euler_init_from_quaternion: |
559 | * @e: a #graphene_euler_t |
560 | * @q: (nullable): a normalized #graphene_quaternion_t |
561 | * @order: the order used to apply the rotations |
562 | * |
563 | * Initializes a #graphene_euler_t using the given normalized quaternion. |
564 | * |
565 | * If the #graphene_quaternion_t @q is %NULL, the #graphene_euler_t will |
566 | * be initialized with all angles set to 0. |
567 | * |
568 | * Returns: (transfer none): the initialized #graphene_euler_t |
569 | * |
570 | * Since: 1.2 |
571 | */ |
572 | graphene_euler_t * |
573 | graphene_euler_init_from_quaternion (graphene_euler_t *e, |
574 | const graphene_quaternion_t *q, |
575 | graphene_euler_order_t order) |
576 | { |
577 | if (q == NULL) |
578 | return graphene_euler_init_with_order (e, x: 0.f, y: 0.f, z: 0.f, order); |
579 | |
580 | graphene_matrix_t m; |
581 | |
582 | graphene_quaternion_to_matrix (q, m: &m); |
583 | |
584 | return graphene_euler_init_from_matrix (e, m: &m, order: graphene_euler_get_real_order (order)); |
585 | } |
586 | |
587 | /** |
588 | * graphene_euler_init_from_vec3: |
589 | * @e: the #graphene_euler_t to initialize |
590 | * @v: (nullable): a #graphene_vec3_t containing the rotation |
591 | * angles in degrees |
592 | * @order: the order used to apply the rotations |
593 | * |
594 | * Initializes a #graphene_euler_t using the angles contained in a |
595 | * #graphene_vec3_t. |
596 | * |
597 | * If the #graphene_vec3_t @v is %NULL, the #graphene_euler_t will be |
598 | * initialized with all angles set to 0. |
599 | * |
600 | * Returns: (transfer none): the initialized #graphene_euler_t |
601 | * |
602 | * Since: 1.2 |
603 | */ |
604 | graphene_euler_t * |
605 | graphene_euler_init_from_vec3 (graphene_euler_t *e, |
606 | const graphene_vec3_t *v, |
607 | graphene_euler_order_t order) |
608 | { |
609 | if (v != NULL) |
610 | graphene_vec3_scale (v, factor: (GRAPHENE_PI / 180.f), res: &e->angles); |
611 | else |
612 | graphene_vec3_init_from_vec3 (v: &e->angles, src: graphene_vec3_zero ()); |
613 | |
614 | e->order = graphene_euler_get_real_order (order); |
615 | |
616 | return e; |
617 | } |
618 | |
619 | /** |
620 | * graphene_euler_init_from_euler: |
621 | * @e: the #graphene_euler_t to initialize |
622 | * @src: (nullable): a #graphene_euler_t |
623 | * |
624 | * Initializes a #graphene_euler_t using the angles and order of |
625 | * another #graphene_euler_t. |
626 | * |
627 | * If the #graphene_euler_t @src is %NULL, this function is equivalent |
628 | * to calling graphene_euler_init() with all angles set to 0. |
629 | * |
630 | * Returns: (transfer none): the initialized #graphene_euler_t |
631 | * |
632 | * Since: 1.2 |
633 | */ |
634 | graphene_euler_t * |
635 | graphene_euler_init_from_euler (graphene_euler_t *e, |
636 | const graphene_euler_t *src) |
637 | { |
638 | if (src == NULL) |
639 | return graphene_euler_init (e, x: 0.f, y: 0.f, z: 0.f); |
640 | |
641 | *e = *src; |
642 | |
643 | return e; |
644 | } |
645 | |
646 | /** |
647 | * graphene_euler_init_from_radians: |
648 | * @e: the #graphene_euler_t to initialize |
649 | * @x: rotation angle on the X axis, in radians |
650 | * @y: rotation angle on the Y axis, in radians |
651 | * @z: rotation angle on the Z axis, in radians |
652 | * @order: order of rotations |
653 | * |
654 | * Initializes a #graphene_euler_t using the given angles |
655 | * and order of rotation. |
656 | * |
657 | * Returns: (transfer none): the initialized #graphene_euler_t |
658 | * |
659 | * Since: 1.10 |
660 | */ |
661 | graphene_euler_t * |
662 | graphene_euler_init_from_radians (graphene_euler_t *e, |
663 | float x, |
664 | float y, |
665 | float z, |
666 | graphene_euler_order_t order) |
667 | { |
668 | return graphene_euler_init_internal (e, rad_x: x, rad_y: y, rad_z: z, order); |
669 | } |
670 | |
671 | static bool |
672 | euler_equal (const void *p1, |
673 | const void *p2) |
674 | { |
675 | const graphene_euler_t *a = p1; |
676 | const graphene_euler_t *b = p2; |
677 | |
678 | return graphene_vec3_equal (v1: &a->angles, v2: &b->angles) && a->order == b->order; |
679 | } |
680 | |
681 | /** |
682 | * graphene_euler_equal: |
683 | * @a: a #graphene_euler_t |
684 | * @b: a #graphene_euler_t |
685 | * |
686 | * Checks if two #graphene_euler_t are equal. |
687 | * |
688 | * Returns: `true` if the two #graphene_euler_t are equal |
689 | * |
690 | * Since: 1.2 |
691 | */ |
692 | bool |
693 | graphene_euler_equal (const graphene_euler_t *a, |
694 | const graphene_euler_t *b) |
695 | { |
696 | return graphene_pointer_equal (p1: a, p2: b, func: euler_equal); |
697 | } |
698 | |
699 | /** |
700 | * graphene_euler_get_x: |
701 | * @e: a #graphene_euler_t |
702 | * |
703 | * Retrieves the rotation angle on the X axis, in degrees. |
704 | * |
705 | * Returns: the rotation angle |
706 | * |
707 | * Since: 1.2 |
708 | */ |
709 | float |
710 | graphene_euler_get_x (const graphene_euler_t *e) |
711 | { |
712 | return GRAPHENE_RAD_TO_DEG (graphene_vec3_get_x (&e->angles)); |
713 | } |
714 | |
715 | /** |
716 | * graphene_euler_get_y: |
717 | * @e: a #graphene_euler_t |
718 | * |
719 | * Retrieves the rotation angle on the Y axis, in degrees. |
720 | * |
721 | * Returns: the rotation angle |
722 | * |
723 | * Since: 1.2 |
724 | */ |
725 | float |
726 | graphene_euler_get_y (const graphene_euler_t *e) |
727 | { |
728 | return GRAPHENE_RAD_TO_DEG (graphene_vec3_get_y (&e->angles)); |
729 | } |
730 | |
731 | /** |
732 | * graphene_euler_get_z: |
733 | * @e: a #graphene_euler_t |
734 | * |
735 | * Retrieves the rotation angle on the Z axis, in degrees. |
736 | * |
737 | * Returns: the rotation angle |
738 | * |
739 | * Since: 1.2 |
740 | */ |
741 | float |
742 | graphene_euler_get_z (const graphene_euler_t *e) |
743 | { |
744 | return GRAPHENE_RAD_TO_DEG (graphene_vec3_get_z (&e->angles)); |
745 | } |
746 | |
747 | /** |
748 | * graphene_euler_get_order: |
749 | * @e: a #graphene_euler_t |
750 | * |
751 | * Retrieves the order used to apply the rotations described in the |
752 | * #graphene_euler_t structure, when converting to and from other |
753 | * structures, like #graphene_quaternion_t and #graphene_matrix_t. |
754 | * |
755 | * This function does not return the %GRAPHENE_EULER_ORDER_DEFAULT |
756 | * enumeration value; it will return the effective order of rotation |
757 | * instead. |
758 | * |
759 | * Returns: the order used to apply the rotations |
760 | * |
761 | * Since: 1.2 |
762 | */ |
763 | graphene_euler_order_t |
764 | graphene_euler_get_order (const graphene_euler_t *e) |
765 | { |
766 | if (e->order == GRAPHENE_EULER_ORDER_DEFAULT) |
767 | return EULER_DEFAULT_ORDER; |
768 | |
769 | return e->order; |
770 | } |
771 | |
772 | /** |
773 | * graphene_euler_to_vec3: |
774 | * @e: a #graphene_euler_t |
775 | * @res: (out caller-allocates): return location for a #graphene_vec3_t |
776 | * |
777 | * Retrieves the angles of a #graphene_euler_t and initializes a |
778 | * #graphene_vec3_t with them. |
779 | * |
780 | * Since: 1.2 |
781 | */ |
782 | void |
783 | graphene_euler_to_vec3 (const graphene_euler_t *e, |
784 | graphene_vec3_t *res) |
785 | { |
786 | graphene_vec3_init_from_vec3 (v: res, src: &e->angles); |
787 | graphene_vec3_scale (v: res, factor: (180.f / GRAPHENE_PI), res); |
788 | } |
789 | |
790 | /** |
791 | * graphene_euler_get_alpha: |
792 | * @e: a #graphene_euler_t |
793 | * |
794 | * Retrieves the first component of the Euler angle vector, |
795 | * depending on the order of rotation. |
796 | * |
797 | * See also: graphene_euler_get_x() |
798 | * |
799 | * Returns: the first component of the Euler angle vector, in radians |
800 | * |
801 | * Since: 1.10 |
802 | */ |
803 | float |
804 | graphene_euler_get_alpha (const graphene_euler_t *e) |
805 | { |
806 | graphene_euler_order_t order = graphene_euler_get_real_order (order: e->order); |
807 | |
808 | switch (order) |
809 | { |
810 | case GRAPHENE_EULER_ORDER_SXYZ: |
811 | case GRAPHENE_EULER_ORDER_SXYX: |
812 | case GRAPHENE_EULER_ORDER_SXZY: |
813 | case GRAPHENE_EULER_ORDER_SXZX: |
814 | case GRAPHENE_EULER_ORDER_RXYX: |
815 | case GRAPHENE_EULER_ORDER_RXZX: |
816 | case GRAPHENE_EULER_ORDER_RXZY: |
817 | case GRAPHENE_EULER_ORDER_RXYZ: |
818 | return graphene_vec3_get_x (v: &e->angles); |
819 | |
820 | case GRAPHENE_EULER_ORDER_SYZX: |
821 | case GRAPHENE_EULER_ORDER_SYZY: |
822 | case GRAPHENE_EULER_ORDER_SYXZ: |
823 | case GRAPHENE_EULER_ORDER_SYXY: |
824 | case GRAPHENE_EULER_ORDER_RYZX: |
825 | case GRAPHENE_EULER_ORDER_RYZY: |
826 | case GRAPHENE_EULER_ORDER_RYXY: |
827 | case GRAPHENE_EULER_ORDER_RYXZ: |
828 | return graphene_vec3_get_y (v: &e->angles); |
829 | |
830 | case GRAPHENE_EULER_ORDER_SZXY: |
831 | case GRAPHENE_EULER_ORDER_SZXZ: |
832 | case GRAPHENE_EULER_ORDER_SZYX: |
833 | case GRAPHENE_EULER_ORDER_SZYZ: |
834 | case GRAPHENE_EULER_ORDER_RZYX: |
835 | case GRAPHENE_EULER_ORDER_RZXY: |
836 | case GRAPHENE_EULER_ORDER_RZXZ: |
837 | case GRAPHENE_EULER_ORDER_RZYZ: |
838 | return graphene_vec3_get_z (v: &e->angles); |
839 | |
840 | default: |
841 | break; |
842 | } |
843 | |
844 | return 0.f; |
845 | } |
846 | |
847 | /** |
848 | * graphene_euler_get_beta: |
849 | * @e: a #graphene_euler_t |
850 | * |
851 | * Retrieves the second component of the Euler angle vector, |
852 | * depending on the order of rotation. |
853 | * |
854 | * See also: graphene_euler_get_y() |
855 | * |
856 | * Returns: the second component of the Euler angle vector, in radians |
857 | * |
858 | * Since: 1.10 |
859 | */ |
860 | float |
861 | graphene_euler_get_beta (const graphene_euler_t *e) |
862 | { |
863 | graphene_euler_order_t order = graphene_euler_get_real_order (order: e->order); |
864 | |
865 | switch (order) |
866 | { |
867 | case GRAPHENE_EULER_ORDER_SYXZ: |
868 | case GRAPHENE_EULER_ORDER_SYXY: |
869 | case GRAPHENE_EULER_ORDER_SZXY: |
870 | case GRAPHENE_EULER_ORDER_SZXZ: |
871 | case GRAPHENE_EULER_ORDER_RZXY: |
872 | case GRAPHENE_EULER_ORDER_RYXY: |
873 | case GRAPHENE_EULER_ORDER_RYXZ: |
874 | case GRAPHENE_EULER_ORDER_RZXZ: |
875 | return graphene_vec3_get_x (v: &e->angles); |
876 | |
877 | case GRAPHENE_EULER_ORDER_SXYZ: |
878 | case GRAPHENE_EULER_ORDER_SXYX: |
879 | case GRAPHENE_EULER_ORDER_SZYX: |
880 | case GRAPHENE_EULER_ORDER_SZYZ: |
881 | case GRAPHENE_EULER_ORDER_RZYX: |
882 | case GRAPHENE_EULER_ORDER_RXYX: |
883 | case GRAPHENE_EULER_ORDER_RXYZ: |
884 | case GRAPHENE_EULER_ORDER_RZYZ: |
885 | return graphene_vec3_get_y (v: &e->angles); |
886 | |
887 | case GRAPHENE_EULER_ORDER_SYZX: |
888 | case GRAPHENE_EULER_ORDER_SYZY: |
889 | case GRAPHENE_EULER_ORDER_SXZY: |
890 | case GRAPHENE_EULER_ORDER_SXZX: |
891 | case GRAPHENE_EULER_ORDER_RYZX: |
892 | case GRAPHENE_EULER_ORDER_RXZX: |
893 | case GRAPHENE_EULER_ORDER_RXZY: |
894 | case GRAPHENE_EULER_ORDER_RYZY: |
895 | return graphene_vec3_get_z (v: &e->angles); |
896 | |
897 | default: |
898 | break; |
899 | } |
900 | |
901 | return 0.f; |
902 | } |
903 | |
904 | /** |
905 | * graphene_euler_get_gamma: |
906 | * @e: a #graphene_euler_t |
907 | * |
908 | * Retrieves the third component of the Euler angle vector, |
909 | * depending on the order of rotation. |
910 | * |
911 | * See also: graphene_euler_get_z() |
912 | * |
913 | * Returns: the third component of the Euler angle vector, in radians |
914 | * |
915 | * Since: 1.10 |
916 | */ |
917 | float |
918 | graphene_euler_get_gamma (const graphene_euler_t *e) |
919 | { |
920 | graphene_euler_order_t order = graphene_euler_get_real_order (order: e->order); |
921 | |
922 | switch (order) |
923 | { |
924 | case GRAPHENE_EULER_ORDER_SXYX: |
925 | case GRAPHENE_EULER_ORDER_SZYX: |
926 | case GRAPHENE_EULER_ORDER_SYZX: |
927 | case GRAPHENE_EULER_ORDER_SXZX: |
928 | case GRAPHENE_EULER_ORDER_RZYX: |
929 | case GRAPHENE_EULER_ORDER_RXYX: |
930 | case GRAPHENE_EULER_ORDER_RYZX: |
931 | case GRAPHENE_EULER_ORDER_RXZX: |
932 | return graphene_vec3_get_x (v: &e->angles); |
933 | |
934 | case GRAPHENE_EULER_ORDER_SYZY: |
935 | case GRAPHENE_EULER_ORDER_SXZY: |
936 | case GRAPHENE_EULER_ORDER_SYXY: |
937 | case GRAPHENE_EULER_ORDER_SZXY: |
938 | case GRAPHENE_EULER_ORDER_RXZY: |
939 | case GRAPHENE_EULER_ORDER_RYZY: |
940 | case GRAPHENE_EULER_ORDER_RZXY: |
941 | case GRAPHENE_EULER_ORDER_RYXY: |
942 | return graphene_vec3_get_y (v: &e->angles); |
943 | |
944 | case GRAPHENE_EULER_ORDER_SZYZ: |
945 | case GRAPHENE_EULER_ORDER_SYXZ: |
946 | case GRAPHENE_EULER_ORDER_SXYZ: |
947 | case GRAPHENE_EULER_ORDER_SZXZ: |
948 | case GRAPHENE_EULER_ORDER_RYXZ: |
949 | case GRAPHENE_EULER_ORDER_RZXZ: |
950 | case GRAPHENE_EULER_ORDER_RXYZ: |
951 | case GRAPHENE_EULER_ORDER_RZYZ: |
952 | return graphene_vec3_get_z (v: &e->angles); |
953 | |
954 | default: |
955 | break; |
956 | } |
957 | |
958 | return 0.f; |
959 | } |
960 | |
961 | /** |
962 | * graphene_euler_to_matrix: |
963 | * @e: a #graphene_euler_t |
964 | * @res: (out caller-allocates): return location for a #graphene_matrix_t |
965 | * |
966 | * Converts a #graphene_euler_t into a transformation matrix expressing |
967 | * the extrinsic composition of rotations described by the Euler angles. |
968 | * |
969 | * The rotations are applied over the reference frame axes in the order |
970 | * associated with the #graphene_euler_t; for instance, if the order |
971 | * used to initialize @e is %GRAPHENE_EULER_ORDER_XYZ: |
972 | * |
973 | * * the first rotation moves the body around the X axis with |
974 | * an angle φ |
975 | * * the second rotation moves the body around the Y axis with |
976 | * an angle of ϑ |
977 | * * the third rotation moves the body around the Z axis with |
978 | * an angle of ψ |
979 | * |
980 | * The rotation sign convention is right-handed, to preserve compatibility |
981 | * between Euler-based, quaternion-based, and angle-axis-based rotations. |
982 | * |
983 | * Since: 1.2 |
984 | */ |
985 | void |
986 | graphene_euler_to_matrix (const graphene_euler_t *e, |
987 | graphene_matrix_t *res) |
988 | { |
989 | graphene_euler_order_t order = graphene_euler_get_real_order (order: e->order); |
990 | |
991 | /* We need to use the alpha/beta/gamma accessor to account for |
992 | * rotations that replicate the first axis on the last |
993 | */ |
994 | float ai = graphene_euler_get_alpha (e); |
995 | float aj = graphene_euler_get_beta (e); |
996 | float ak = graphene_euler_get_gamma (e); |
997 | |
998 | euler_to_matrix (ai, aj, ak, params: &order_parameters[ORDER_OFFSET (order)], matrix: res); |
999 | } |
1000 | |
1001 | /** |
1002 | * graphene_euler_to_quaternion: |
1003 | * @e: a #graphene_euler_t |
1004 | * @res: (out caller-allocates): return location for a #graphene_quaternion_t |
1005 | * |
1006 | * Converts a #graphene_euler_t into a #graphene_quaternion_t. |
1007 | * |
1008 | * Since: 1.10 |
1009 | */ |
1010 | void |
1011 | graphene_euler_to_quaternion (const graphene_euler_t *e, |
1012 | graphene_quaternion_t *res) |
1013 | { |
1014 | float ti = graphene_vec3_get_x (v: &e->angles) * 0.5f; |
1015 | float tj = graphene_vec3_get_y (v: &e->angles) * 0.5f; |
1016 | float tk = graphene_vec3_get_z (v: &e->angles) * 0.5f; |
1017 | |
1018 | float ci, cj, ck; |
1019 | float si, sj, sk; |
1020 | |
1021 | graphene_sincos (angle: ti, sin_out: &si, cos_out: &ci); |
1022 | graphene_sincos (angle: tj, sin_out: &sj, cos_out: &cj); |
1023 | graphene_sincos (angle: tk, sin_out: &sk, cos_out: &ck); |
1024 | |
1025 | float cc = ci * ck; |
1026 | float cs = ci * sk; |
1027 | float sc = si * ck; |
1028 | float ss = si * sk; |
1029 | |
1030 | graphene_euler_order_t order = graphene_euler_get_real_order (order: e->order); |
1031 | const struct axis_param *params = &order_parameters[ORDER_OFFSET (order)]; |
1032 | |
1033 | if (params->repetition) |
1034 | { |
1035 | res->x = cj * (cs + cc); |
1036 | res->y = sj * (cc + ss); |
1037 | res->z = sj * (cs - sc); |
1038 | res->w = cj * (cc - ss); |
1039 | } |
1040 | else |
1041 | { |
1042 | res->x = cj * sc - sj * cs; |
1043 | res->y = cj * ss + sj * cc; |
1044 | res->z = cj * cs - sj * sc; |
1045 | res->w = cj * cc + sj * ss; |
1046 | } |
1047 | |
1048 | if (params->parity) |
1049 | res->y *= -1.f; |
1050 | } |
1051 | |
1052 | /** |
1053 | * graphene_euler_reorder: |
1054 | * @e: a #graphene_euler_t |
1055 | * @order: the new order |
1056 | * @res: (out caller-allocates): return location for the reordered |
1057 | * #graphene_euler_t |
1058 | * |
1059 | * Reorders a #graphene_euler_t using @order. |
1060 | * |
1061 | * This function is equivalent to creating a #graphene_quaternion_t from the |
1062 | * given #graphene_euler_t, and then converting the quaternion into another |
1063 | * #graphene_euler_t. |
1064 | * |
1065 | * Since: 1.2 |
1066 | */ |
1067 | void |
1068 | graphene_euler_reorder (const graphene_euler_t *e, |
1069 | graphene_euler_order_t order, |
1070 | graphene_euler_t *res) |
1071 | { |
1072 | graphene_quaternion_t q; |
1073 | |
1074 | graphene_quaternion_init_from_euler (q: &q, e); |
1075 | graphene_euler_init_from_quaternion (e: res, q: &q, order: graphene_euler_get_real_order (order)); |
1076 | } |
1077 | |