1/* graphene-quaternion.c: Quaternion
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-quaternion
28 * @title: Quaternion
29 * @short_description: Quaternion operations
30 *
31 * Quaternions are a mathematical entity that can be used to represent
32 * rotation transformations in 3D space; unlike the usual Euler representation
33 * with roll, pitch, and yaw, quaternions do not suffer from the so-called
34 * ["Gimbal Lock"](http://en.wikipedia.org/wiki/Gimbal_lock) problem.
35 *
36 * See also: #graphene_euler_t
37 */
38
39#include "graphene-private.h"
40
41#include "graphene-quaternion.h"
42
43#include "graphene-euler.h"
44#include "graphene-matrix.h"
45#include "graphene-point3d.h"
46#include "graphene-simd4f.h"
47#include "graphene-simd4x4f.h"
48#include "graphene-vec3.h"
49#include "graphene-vec4.h"
50
51#include <math.h>
52
53/**
54 * graphene_quaternion_alloc: (constructor)
55 *
56 * Allocates a new #graphene_quaternion_t.
57 *
58 * The contents of the returned value are undefined.
59 *
60 * Returns: (transfer full): the newly allocated #graphene_quaternion_t
61 *
62 * Since: 1.0
63 */
64graphene_quaternion_t *
65graphene_quaternion_alloc (void)
66{
67 return calloc (nmemb: 1, size: sizeof (graphene_quaternion_t));
68}
69
70/**
71 * graphene_quaternion_free:
72 * @q: a #graphene_quaternion_t
73 *
74 * Releases the resources allocated by graphene_quaternion_alloc().
75 *
76 * Since: 1.0
77 */
78void
79graphene_quaternion_free (graphene_quaternion_t *q)
80{
81 free (ptr: q);
82}
83
84/**
85 * graphene_quaternion_init:
86 * @q: a #graphene_quaternion_t
87 * @x: the first component of the quaternion
88 * @y: the second component of the quaternion
89 * @z: the third component of the quaternion
90 * @w: the fourth component of the quaternion
91 *
92 * Initializes a #graphene_quaternion_t using the given four values.
93 *
94 * Returns: (transfer none): the initialized quaternion
95 *
96 * Since: 1.0
97 */
98graphene_quaternion_t *
99graphene_quaternion_init (graphene_quaternion_t *q,
100 float x,
101 float y,
102 float z,
103 float w)
104{
105 q->x = x;
106 q->y = y;
107 q->z = z;
108 q->w = w;
109
110 return q;
111}
112
113/**
114 * graphene_quaternion_init_identity:
115 * @q: a #graphene_quaternion_t
116 *
117 * Initializes a #graphene_quaternion_t using the identity
118 * transformation.
119 *
120 * Returns: (transfer none): the initialized quaternion
121 *
122 * Since: 1.0
123 */
124graphene_quaternion_t *
125graphene_quaternion_init_identity (graphene_quaternion_t *q)
126{
127 q->x = q->y = q->z = 0.f;
128 q->w = 1.f;
129
130 return q;
131}
132
133/**
134 * graphene_quaternion_init_from_quaternion:
135 * @q: a #graphene_quaternion_t
136 * @src: a #graphene_quaternion_t
137 *
138 * Initializes a #graphene_quaternion_t with the values from @src.
139 *
140 * Returns: (transfer none): the initialized quaternion
141 *
142 * Since: 1.0
143 */
144graphene_quaternion_t *
145graphene_quaternion_init_from_quaternion (graphene_quaternion_t *q,
146 const graphene_quaternion_t *src)
147{
148 *q = *src;
149
150 return q;
151}
152
153static graphene_quaternion_t *
154graphene_quaternion_init_from_simd4f (graphene_quaternion_t *q,
155 graphene_simd4f_t v)
156{
157 graphene_simd4f_dup_4f (v, (float *) q);
158
159 return q;
160}
161
162/**
163 * graphene_quaternion_init_from_vec4:
164 * @q: a #graphene_quaternion_t
165 * @src: a #graphene_vec4_t
166 *
167 * Initializes a #graphene_quaternion_t with the values from @src.
168 *
169 * Returns: (transfer none): the initialized quaternion
170 *
171 * Since: 1.0
172 */
173graphene_quaternion_t *
174graphene_quaternion_init_from_vec4 (graphene_quaternion_t *q,
175 const graphene_vec4_t *src)
176{
177 return graphene_quaternion_init_from_simd4f (q, v: src->value);
178}
179
180/**
181 * graphene_quaternion_to_vec4:
182 * @q: a #graphene_quaternion_t
183 * @res: (out caller-allocates): return location for a
184 * #graphene_vec4_t
185 *
186 * Copies the components of a #graphene_quaternion_t into a
187 * #graphene_vec4_t.
188 *
189 * Since: 1.0
190 */
191void
192graphene_quaternion_to_vec4 (const graphene_quaternion_t *q,
193 graphene_vec4_t *res)
194{
195 res->value = graphene_simd4f_init (q->x, q->y, q->z, q->w);
196}
197
198/**
199 * graphene_quaternion_init_from_matrix:
200 * @q: a #graphene_quaternion_t
201 * @m: a #graphene_matrix_t
202 *
203 * Initializes a #graphene_quaternion_t using the rotation components
204 * of a transformation matrix.
205 *
206 * Returns: (transfer none): the initialized quaternion
207 *
208 * Since: 1.0
209 */
210graphene_quaternion_t *
211graphene_quaternion_init_from_matrix (graphene_quaternion_t *q,
212 const graphene_matrix_t *m)
213{
214 float xx = graphene_matrix_get_value (m, row: 0, col: 0);
215 float yy = graphene_matrix_get_value (m, row: 1, col: 1);
216 float zz = graphene_matrix_get_value (m, row: 2, col: 2);
217
218 q->w = 0.5f * sqrtf (MAX (1 + xx + yy + zz, 0.f));
219 q->x = 0.5f * sqrtf (MAX (1 + xx - yy - zz, 0.f));
220 q->y = 0.5f * sqrtf (MAX (1 - xx + yy - zz, 0.f));
221 q->z = 0.5f * sqrtf (MAX (1 - xx - yy + zz, 0.f));
222
223 if (graphene_matrix_get_value (m, row: 2, col: 1) > graphene_matrix_get_value (m, row: 1, col: 2))
224 q->x = -q->x;
225
226 if (graphene_matrix_get_value (m, row: 0, col: 2) > graphene_matrix_get_value (m, row: 2, col: 0))
227 q->y = -q->y;
228
229 if (graphene_matrix_get_value (m, row: 1, col: 0) > graphene_matrix_get_value (m, row: 0, col: 1))
230 q->z = -q->z;
231
232 return q;
233}
234
235/**
236 * graphene_quaternion_to_matrix:
237 * @q: a #graphene_quaternion_t
238 * @m: (out caller-allocates): a #graphene_matrix_t
239 *
240 * Converts a quaternion into a transformation matrix expressing
241 * the rotation defined by the #graphene_quaternion_t.
242 *
243 * Since: 1.0
244 */
245void
246graphene_quaternion_to_matrix (const graphene_quaternion_t *q,
247 graphene_matrix_t *m)
248{
249 m->value.x = graphene_simd4f_init (1.f - 2.f * (q->y * q->y + q->z * q->z),
250 2.f * (q->x * q->y + q->w * q->z),
251 2.f * (q->x * q->z - q->w * q->y),
252 0.f);
253 m->value.y = graphene_simd4f_init (2.f * (q->x * q->y - q->w * q->z),
254 1.f - 2.f * (q->x * q->x + q->z * q->z),
255 2.f * (q->y * q->z + q->w * q->x),
256 0.f);
257 m->value.z = graphene_simd4f_init (2.f * (q->x * q->z + q->w * q->y),
258 2.f * (q->y * q->z - q->w * q->x),
259 1.f - 2.f * (q->x * q->x + q->y * q->y),
260 0.f);
261 m->value.w = graphene_simd4f_init (0.f, 0.f, 0.f, 1.f);
262}
263
264/**
265 * graphene_quaternion_slerp:
266 * @a: a #graphene_quaternion_t
267 * @b: a #graphene_quaternion_t
268 * @factor: the linear interpolation factor
269 * @res: (out caller-allocates): return location for the interpolated
270 * quaternion
271 *
272 * Interpolates between the two given quaternions using a spherical
273 * linear interpolation, or [SLERP](http://en.wikipedia.org/wiki/Slerp),
274 * using the given interpolation @factor.
275 *
276 * Since: 1.0
277 */
278void
279graphene_quaternion_slerp (const graphene_quaternion_t *a,
280 const graphene_quaternion_t *b,
281 float factor,
282 graphene_quaternion_t *res)
283{
284 graphene_simd4f_t v_a = graphene_simd4f_init (a->x, a->y, a->z, a->w);
285 graphene_simd4f_t v_b = graphene_simd4f_init (b->x, b->y, b->z, b->w);
286 float left_sign = 1;
287
288 float dot = CLAMP (graphene_simd4f_get_x (graphene_simd4f_dot4 (v_a, v_b)), -1.f, 1.f);
289
290 /* Ensure we use the shortest path to the new angle */
291 if (dot < 0)
292 {
293 left_sign = -1;
294 dot = -dot;
295 }
296
297 if (graphene_approx_val (a: dot, b: 1.f))
298 {
299 *res = *a;
300 return;
301 }
302
303 float theta = acosf (x: dot);
304 float r_sin_theta = 1.f / sqrtf (x: 1.f - dot * dot);
305 float right_v = sinf (x: factor * theta) * r_sin_theta;
306 float left_v = cosf (x: factor * theta) - dot * right_v;
307
308 graphene_simd4f_t left = graphene_simd4f_init (a->x, a->y, a->z, a->w);
309 graphene_simd4f_t right = graphene_simd4f_init (b->x, b->y, b->z, b->w);
310 graphene_simd4f_t sum;
311
312 left = graphene_simd4f_mul (left, graphene_simd4f_splat (left_v * left_sign));
313 right = graphene_simd4f_mul (right, graphene_simd4f_splat (right_v));
314 sum = graphene_simd4f_add (left, right);
315
316 graphene_quaternion_init_from_simd4f (q: res, v: sum);
317}
318
319/**
320 * graphene_quaternion_init_from_angles:
321 * @q: a #graphene_quaternion_t
322 * @deg_x: rotation angle on the X axis (yaw), in degrees
323 * @deg_y: rotation angle on the Y axis (pitch), in degrees
324 * @deg_z: rotation angle on the Z axis (roll), in degrees
325 *
326 * Initializes a #graphene_quaternion_t using the values of
327 * the [Euler angles](http://en.wikipedia.org/wiki/Euler_angles)
328 * on each axis.
329 *
330 * See also: graphene_quaternion_init_from_euler()
331 *
332 * Returns: (transfer none): the initialized quaternion
333 *
334 * Since: 1.0
335 */
336graphene_quaternion_t *
337graphene_quaternion_init_from_angles (graphene_quaternion_t *q,
338 float deg_x,
339 float deg_y,
340 float deg_z)
341{
342 return graphene_quaternion_init_from_radians (q,
343 GRAPHENE_DEG_TO_RAD (deg_x),
344 GRAPHENE_DEG_TO_RAD (deg_y),
345 GRAPHENE_DEG_TO_RAD (deg_z));
346}
347
348/**
349 * graphene_quaternion_init_from_radians:
350 * @q: a #graphene_quaternion_t
351 * @rad_x: rotation angle on the X axis (yaw), in radians
352 * @rad_y: rotation angle on the Y axis (pitch), in radians
353 * @rad_z: rotation angle on the Z axis (roll), in radians
354 *
355 * Initializes a #graphene_quaternion_t using the values of
356 * the [Euler angles](http://en.wikipedia.org/wiki/Euler_angles)
357 * on each axis.
358 *
359 * See also: graphene_quaternion_init_from_euler()
360 *
361 * Returns: (transfer none): the initialized quaternion
362 *
363 * Since: 1.0
364 */
365graphene_quaternion_t *
366graphene_quaternion_init_from_radians (graphene_quaternion_t *q,
367 float rad_x,
368 float rad_y,
369 float rad_z)
370{
371 float sin_x, sin_y, sin_z;
372 float cos_x, cos_y, cos_z;
373
374 graphene_sincos (angle: rad_x * .5f, sin_out: &sin_x, cos_out: &cos_x);
375 graphene_sincos (angle: rad_y * .5f, sin_out: &sin_y, cos_out: &cos_y);
376 graphene_sincos (angle: rad_z * .5f, sin_out: &sin_z, cos_out: &cos_z);
377
378 q->x = sin_x * cos_y * cos_z + cos_x * sin_y * sin_z;
379 q->y = cos_x * sin_y * cos_z - sin_x * cos_y * sin_z;
380 q->z = cos_x * cos_y * sin_z + sin_x * sin_y * cos_z;
381 q->w = cos_x * cos_y * cos_z - sin_x * sin_y * sin_z;
382
383 return q;
384}
385
386/**
387 * graphene_quaternion_to_angles:
388 * @q: a #graphene_quaternion_t
389 * @deg_x: (out) (optional): return location for the rotation angle on
390 * the X axis (yaw), in degrees
391 * @deg_y: (out) (optional): return location for the rotation angle on
392 * the Y axis (pitch), in degrees
393 * @deg_z: (out) (optional): return location for the rotation angle on
394 * the Z axis (roll), in degrees
395 *
396 * Converts a #graphene_quaternion_t to its corresponding rotations
397 * on the [Euler angles](http://en.wikipedia.org/wiki/Euler_angles)
398 * on each axis.
399 *
400 * Since: 1.2
401 */
402void
403graphene_quaternion_to_angles (const graphene_quaternion_t *q,
404 float *deg_x,
405 float *deg_y,
406 float *deg_z)
407{
408 float rad_x, rad_y, rad_z;
409
410 graphene_quaternion_to_radians (q, rad_x: &rad_x, rad_y: &rad_y, rad_z: &rad_z);
411
412 if (deg_x != NULL)
413 *deg_x = GRAPHENE_RAD_TO_DEG (rad_x);
414 if (deg_y != NULL)
415 *deg_y = GRAPHENE_RAD_TO_DEG (rad_y);
416 if (deg_z != NULL)
417 *deg_z = GRAPHENE_RAD_TO_DEG (rad_z);
418}
419
420/**
421 * graphene_quaternion_to_radians:
422 * @q: a #graphene_quaternion_t
423 * @rad_x: (out) (optional): return location for the rotation angle on
424 * the X axis (yaw), in radians
425 * @rad_y: (out) (optional): return location for the rotation angle on
426 * the Y axis (pitch), in radians
427 * @rad_z: (out) (optional): return location for the rotation angle on
428 * the Z axis (roll), in radians
429 *
430 * Converts a #graphene_quaternion_t to its corresponding rotations
431 * on the [Euler angles](http://en.wikipedia.org/wiki/Euler_angles)
432 * on each axis.
433 *
434 * Since: 1.2
435 */
436void
437graphene_quaternion_to_radians (const graphene_quaternion_t *q,
438 float *rad_x,
439 float *rad_y,
440 float *rad_z)
441{
442 graphene_vec4_t v;
443 graphene_vec4_t sqv;
444
445 graphene_quaternion_to_vec4 (q, res: &v);
446 graphene_vec4_multiply (a: &v, b: &v, res: &sqv);
447
448 float qa[4];
449 graphene_vec4_to_float (v: &v, dest: qa);
450
451 float sqa[4];
452 graphene_vec4_to_float (v: &sqv, dest: sqa);
453
454 if (rad_x != NULL)
455 *rad_x = atan2f (y: 2 * (qa[0] * qa[3] - qa[1] * qa[2]), x: (sqa[3] - sqa[0] - sqa[1] + sqa[2]));
456
457 if (rad_y != NULL)
458 *rad_y = asinf (CLAMP (2 * (qa[0] * qa[2] + qa[1] * qa[3]), -1, 1));
459
460 if (rad_z != NULL)
461 *rad_z = atan2f (y: 2 * (qa[2] * qa[3] - qa[0] * qa[1]), x: (sqa[3] + sqa[0] - sqa[1] - sqa[2]));
462}
463
464/**
465 * graphene_quaternion_init_from_angle_vec3:
466 * @q: a #graphene_quaternion_t
467 * @angle: the rotation on a given axis, in degrees
468 * @axis: the axis of rotation, expressed as a vector
469 *
470 * Initializes a #graphene_quaternion_t using an @angle on a
471 * specific @axis.
472 *
473 * Returns: (transfer none): the initialized quaternion
474 *
475 * Since: 1.0
476 */
477graphene_quaternion_t *
478graphene_quaternion_init_from_angle_vec3 (graphene_quaternion_t *q,
479 float angle,
480 const graphene_vec3_t *axis)
481{
482 float rad, sin_a, cos_a;
483 graphene_simd4f_t axis_n;
484
485 rad = GRAPHENE_DEG_TO_RAD (angle) / 2.f;
486 graphene_sincos (angle: rad, sin_out: &sin_a, cos_out: &cos_a);
487
488 axis_n = graphene_simd4f_mul (graphene_simd4f_normalize3 (axis->value),
489 graphene_simd4f_splat (sin_a));
490
491 q->x = graphene_simd4f_get_x (axis_n);
492 q->y = graphene_simd4f_get_y (axis_n);
493 q->z = graphene_simd4f_get_z (axis_n);
494 q->w = cos_a;
495
496 return q;
497}
498
499/**
500 * graphene_quaternion_to_angle_vec3:
501 * @q: a #graphene_quaternion_t
502 * @angle: (out): return location for the angle, in degrees
503 * @axis: (out caller-allocates): return location for the rotation axis
504 *
505 * Converts a quaternion into an @angle, @axis pair.
506 *
507 * Since: 1.0
508 */
509void
510graphene_quaternion_to_angle_vec3 (const graphene_quaternion_t *q,
511 float *angle,
512 graphene_vec3_t *axis)
513{
514 graphene_quaternion_t q_n;
515 float cos_a;
516
517 graphene_quaternion_normalize (q, res: &q_n);
518
519 cos_a = q_n.w;
520
521 if (angle != NULL)
522 *angle = GRAPHENE_RAD_TO_DEG (acosf (cos_a) * 2.f);
523
524 if (axis != NULL)
525 {
526 float sin_a = sqrtf (x: 1.f - cos_a * cos_a);
527
528 if (fabsf (x: sin_a) < 0.00005)
529 sin_a = 1.f;
530
531 graphene_vec3_init (v: axis, x: q_n.x / sin_a, y: q_n.y / sin_a, z: q_n.z / sin_a);
532 }
533}
534
535/**
536 * graphene_quaternion_init_from_euler:
537 * @q: the #graphene_quaternion_t to initialize
538 * @e: a #graphene_euler_t
539 *
540 * Initializes a #graphene_quaternion_t using the given #graphene_euler_t.
541 *
542 * Returns: (transfer none): the initialized #graphene_quaternion_t
543 *
544 * Since: 1.2
545 */
546graphene_quaternion_t *
547graphene_quaternion_init_from_euler (graphene_quaternion_t *q,
548 const graphene_euler_t *e)
549{
550 graphene_euler_to_quaternion (e, res: q);
551
552 return q;
553}
554
555static bool
556quaternion_equal (const void *p1,
557 const void *p2)
558{
559 const graphene_quaternion_t *a = p1;
560 const graphene_quaternion_t *b = p2;
561
562 if (graphene_fuzzy_equals (a->x, b->x, 0.00001) &&
563 graphene_fuzzy_equals (a->y, b->y, 0.00001) &&
564 graphene_fuzzy_equals (a->z, b->z, 0.00001) &&
565 graphene_fuzzy_equals (a->w, b->w, 0.00001))
566 return true;
567
568 graphene_quaternion_t i;
569 graphene_quaternion_invert (q: a, res: &i);
570 if (graphene_fuzzy_equals (i.x, b->x, 0.00001) &&
571 graphene_fuzzy_equals (i.y, b->y, 0.00001) &&
572 graphene_fuzzy_equals (i.z, b->z, 0.00001) &&
573 graphene_fuzzy_equals (i.w, b->w, 0.00001))
574 return true;
575
576 return false;
577}
578
579/**
580 * graphene_quaternion_equal:
581 * @a: a #graphene_quaternion_t
582 * @b: a #graphene_quaternion_t
583 *
584 * Checks whether the given quaternions are equal.
585 *
586 * Returns: `true` if the quaternions are equal
587 *
588 * Since: 1.0
589 */
590bool
591graphene_quaternion_equal (const graphene_quaternion_t *a,
592 const graphene_quaternion_t *b)
593{
594 return graphene_pointer_equal (p1: a, p2: b, func: quaternion_equal);
595}
596
597/**
598 * graphene_quaternion_dot:
599 * @a: a #graphene_quaternion_t
600 * @b: a #graphene_quaternion_t
601 *
602 * Computes the dot product of two #graphene_quaternion_t.
603 *
604 * Returns: the value of the dot products
605 *
606 * Since: 1.0
607 */
608float
609graphene_quaternion_dot (const graphene_quaternion_t *a,
610 const graphene_quaternion_t *b)
611{
612 graphene_simd4f_t v_a = graphene_simd4f_init (a->x, a->y, a->z, a->w);
613 graphene_simd4f_t v_b = graphene_simd4f_init (b->x, b->y, b->z, b->w);
614
615 return graphene_simd4f_get_x (graphene_simd4f_dot4 (v_a, v_b));
616}
617
618/**
619 * graphene_quaternion_invert:
620 * @q: a #graphene_quaternion_t
621 * @res: (out caller-allocates): return location for the inverted
622 * quaternion
623 *
624 * Inverts a #graphene_quaternion_t, and returns the conjugate
625 * quaternion of @q.
626 *
627 * Since: 1.0
628 */
629void
630graphene_quaternion_invert (const graphene_quaternion_t *q,
631 graphene_quaternion_t *res)
632{
633 res->x = -q->x;
634 res->y = -q->y;
635 res->z = -q->z;
636 res->w = q->w;
637}
638
639/**
640 * graphene_quaternion_normalize:
641 * @q: a #graphene_quaternion_t
642 * @res: (out caller-allocates): return location for the normalized
643 * quaternion
644 *
645 * Normalizes a #graphene_quaternion_t.
646 *
647 * Since: 1.0
648 */
649void
650graphene_quaternion_normalize (const graphene_quaternion_t *q,
651 graphene_quaternion_t *res)
652{
653 graphene_simd4f_t v_q;
654
655 v_q = graphene_simd4f_init (q->x, q->y, q->z, q->w);
656 v_q = graphene_simd4f_normalize4 (v: v_q);
657
658 graphene_quaternion_init_from_simd4f (q: res, v: v_q);
659}
660
661/**
662 * graphene_quaternion_multiply:
663 * @a: a #graphene_quaternion_t
664 * @b: a #graphene_quaternion_t
665 * @res: (out caller-allocates): the result of the operation
666 *
667 * Multiplies two #graphene_quaternion_t @a and @b.
668 *
669 * Since: 1.10
670 */
671void
672graphene_quaternion_multiply (const graphene_quaternion_t *a,
673 const graphene_quaternion_t *b,
674 graphene_quaternion_t *res)
675{
676 /* See:
677 *
678 * http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
679 */
680 float x = a->x * b->w + a->w * b->x + a->y * b->z - a->z * b->y;
681 float y = a->y * b->w + a->w * b->y + a->z * b->x - a->x * b->z;
682 float z = a->z * b->w + a->w * b->z + a->x * b->y - a->y * b->x;
683 float w = a->w * b->w - a->x * b->x - a->y * b->y - a->z * b->z;
684
685 graphene_quaternion_init (q: res, x, y, z, w);
686}
687
688/**
689 * graphene_quaternion_scale:
690 * @q: a #graphene_quaternion_t
691 * @factor: a scaling factor
692 * @res: (out caller-allocates): the result of the operation
693 *
694 * Scales all the elements of a #graphene_quaternion_t @q using
695 * the given scalar factor.
696 *
697 * Since: 1.10
698 */
699void
700graphene_quaternion_scale (const graphene_quaternion_t *q,
701 float factor,
702 graphene_quaternion_t *res)
703{
704 graphene_simd4f_t s =
705 graphene_simd4f_mul (graphene_simd4f_init (q->x, q->y, q->z, q->w),
706 graphene_simd4f_splat (factor));
707
708 graphene_quaternion_init_from_simd4f (q: res, v: s);
709}
710
711/**
712 * graphene_quaternion_add:
713 * @a: a #graphene_quaternion_t
714 * @b: a #graphene_quaternion_t
715 * @res: (out caller-allocates): the result of the operation
716 *
717 * Adds two #graphene_quaternion_t @a and @b.
718 *
719 * Since: 1.10
720 */
721void
722graphene_quaternion_add (const graphene_quaternion_t *a,
723 const graphene_quaternion_t *b,
724 graphene_quaternion_t *res)
725{
726 graphene_simd4f_t sa = graphene_simd4f_init (a->x, a->y, a->z, a->w);
727 graphene_simd4f_t sb = graphene_simd4f_init (b->x, b->y, b->z, b->w);
728 graphene_simd4f_t sr = graphene_simd4f_add (sa, sb);
729
730 graphene_quaternion_init_from_simd4f (q: res, v: sr);
731}
732

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