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 */
110enum {
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
144struct 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 */
157static 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 */
192static 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 */
201static inline void
202euler_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
284static inline void
285matrix_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 */
381graphene_euler_t *
382graphene_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 */
395void
396graphene_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 */
410static graphene_euler_order_t
411graphene_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 */
456static graphene_euler_t *
457graphene_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 */
484graphene_euler_t *
485graphene_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 */
511graphene_euler_t *
512graphene_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 */
540graphene_euler_t *
541graphene_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 */
572graphene_euler_t *
573graphene_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 */
604graphene_euler_t *
605graphene_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 */
634graphene_euler_t *
635graphene_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 */
661graphene_euler_t *
662graphene_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
671static bool
672euler_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 */
692bool
693graphene_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 */
709float
710graphene_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 */
725float
726graphene_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 */
741float
742graphene_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 */
763graphene_euler_order_t
764graphene_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 */
782void
783graphene_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 */
803float
804graphene_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 */
860float
861graphene_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 */
917float
918graphene_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 */
985void
986graphene_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 */
1010void
1011graphene_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 */
1067void
1068graphene_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

source code of gtk/subprojects/graphene/src/graphene-euler.c