1/* graphene-matrix.h: 4x4 matrix
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-matrix
28 * @title: Matrix
29 * @short_description: 4x4 matrices
30 *
31 * #graphene_matrix_t is a type that provides a 4x4 square matrix, useful for
32 * representing 3D transformations.
33 *
34 * The matrix is treated as row-major, i.e. it has four vectors (x, y, z, and
35 * w) representing rows, and elements of each vector are a column:
36 *
37 * |[<!-- language="plain" -->
38 * ⎡ m.x ⎤ ⎛ x.x x.y x.z x.w ⎞
39 * ⎜ m.y ⎟ -\ ⎜ y.x y.y y.z y.w ⎟
40 * ⎜ m.z ⎟ -/ ⎜ z.x z.y z.z z.w ⎟
41 * ⎣ m.w ⎦ ⎝ w.x w.y w.z w.w ⎠
42 * ]|
43 *
44 * It is possible to easily convert a #graphene_matrix_t to and from an array
45 * of floating point values that can be used with other libraries.
46 *
47 * The contents of a #graphene_matrix_t are private, and direct access is not
48 * possible. You can modify and read the contents of a #graphene_matrix_t
49 * only through the provided API.
50 *
51 * # Conventions # {#conventions}
52 *
53 * Graphene uses left-multiplication for all its operations on vectors and
54 * matrices; in other words, given a matrix `A` and a vector `b`, the result
55 * of a multiplication is going to be:
56 *
57 * |[
58 * res = b × A
59 * ]|
60 *
61 * Multiplying two matrices, on the other hand, will use right-multiplication;
62 * given two matrices `A` and `B`, the result of the multiplication is going
63 * to be
64 *
65 * |[
66 * res = A × B
67 * ]|
68 *
69 * as the implementation will multiply each row vector of matrix `A` with the
70 * matrix `B` to obtain the new row vectors of the result matrix:
71 *
72 * |[
73 * res = ⎡ A.x × B ⎤
74 * ⎜ A.y × B ⎟
75 * ⎜ A.z × B ⎟
76 * ⎣ A.w × B ⎦
77 * ]|
78 *
79 * For more information, see the documentation for #graphene_simd4x4f_t,
80 * especially the following functions:
81 *
82 * - graphene_simd4x4f_vec4_mul()
83 * - graphene_simd4x4f_vec3_mul()
84 * - graphene_simd4x4f_point3_mul()
85 * - graphene_simd4x4f_matrix_mul()
86 */
87
88#include "graphene-private.h"
89
90#include "graphene-matrix.h"
91
92#include "graphene-alloc-private.h"
93#include "graphene-box.h"
94#include "graphene-euler.h"
95#include "graphene-point.h"
96#include "graphene-point3d.h"
97#include "graphene-quad.h"
98#include "graphene-quaternion.h"
99#include "graphene-ray.h"
100#include "graphene-rect.h"
101#include "graphene-simd4x4f.h"
102#include "graphene-sphere.h"
103#include "graphene-vectors-private.h"
104
105#include <stdio.h>
106
107/**
108 * graphene_matrix_alloc: (constructor)
109 *
110 * Allocates a new #graphene_matrix_t.
111 *
112 * Returns: (transfer full): the newly allocated matrix
113 *
114 * Since: 1.0
115 */
116graphene_matrix_t *
117graphene_matrix_alloc (void)
118{
119 return graphene_aligned_alloc (size: sizeof (graphene_matrix_t), number: 1, alignment: 16);
120}
121
122/**
123 * graphene_matrix_free:
124 * @m: a #graphene_matrix_t
125 *
126 * Frees the resources allocated by graphene_matrix_alloc().
127 *
128 * Since: 1.0
129 */
130void
131graphene_matrix_free (graphene_matrix_t *m)
132{
133 graphene_aligned_free (mem: m);
134}
135
136/**
137 * graphene_matrix_to_float:
138 * @m: a #graphene_matrix_t
139 * @v: (array fixed-size=16) (out caller-allocates): return location
140 * for an array of floating point values. The array must be capable
141 * of holding at least 16 values.
142 *
143 * Converts a #graphene_matrix_t to an array of floating point
144 * values.
145 *
146 * Since: 1.0
147 */
148void
149graphene_matrix_to_float (const graphene_matrix_t *m,
150 float *v)
151{
152 graphene_simd4x4f_to_float (m: &m->value, v);
153}
154
155static const float graphene_identity_matrix_floats[16] = {
156 1.f, 0.f, 0.f, 0.f,
157 0.f, 1.f, 0.f, 0.f,
158 0.f, 0.f, 1.f, 0.f,
159 0.f, 0.f, 0.f, 1.f,
160};
161
162/**
163 * graphene_matrix_init_identity:
164 * @m: a #graphene_matrix_t
165 *
166 * Initializes a #graphene_matrix_t with the identity matrix.
167 *
168 * Returns: (transfer none): the initialized matrix
169 *
170 * Since: 1.0
171 */
172graphene_matrix_t *
173graphene_matrix_init_identity (graphene_matrix_t *m)
174{
175 graphene_simd4x4f_init_from_float (m: &m->value, f: graphene_identity_matrix_floats);
176
177 return m;
178}
179
180/**
181 * graphene_matrix_init_from_float:
182 * @m: a #graphene_matrix_t
183 * @v: (array fixed-size=16): an array of at least 16 floating
184 * point values
185 *
186 * Initializes a #graphene_matrix_t with the given array of floating
187 * point values.
188 *
189 * Returns: (transfer none): the initialized matrix
190 *
191 * Since: 1.0
192 */
193graphene_matrix_t *
194graphene_matrix_init_from_float (graphene_matrix_t *m,
195 const float *v)
196{
197 graphene_simd4x4f_init_from_float (m: &m->value, f: v);
198
199 return m;
200}
201
202/**
203 * graphene_matrix_init_from_vec4:
204 * @m: a #graphene_matrix_t
205 * @v0: the first row vector
206 * @v1: the second row vector
207 * @v2: the third row vector
208 * @v3: the fourth row vector
209 *
210 * Initializes a #graphene_matrix_t with the given four row
211 * vectors.
212 *
213 * Returns: (transfer none): the initialized matrix
214 *
215 * Since: 1.0
216 */
217graphene_matrix_t *
218graphene_matrix_init_from_vec4 (graphene_matrix_t *m,
219 const graphene_vec4_t *v0,
220 const graphene_vec4_t *v1,
221 const graphene_vec4_t *v2,
222 const graphene_vec4_t *v3)
223{
224 m->value = graphene_simd4x4f_init (x: v0->value,
225 y: v1->value,
226 z: v2->value,
227 w: v3->value);
228
229 return m;
230}
231
232/**
233 * graphene_matrix_init_from_matrix:
234 * @m: a #graphene_matrix_t
235 * @src: a #graphene_matrix_t
236 *
237 * Initializes a #graphene_matrix_t using the values of the
238 * given matrix.
239 *
240 * Returns: (transfer none): the initialized matrix
241 *
242 * Since: 1.0
243 */
244graphene_matrix_t *
245graphene_matrix_init_from_matrix (graphene_matrix_t *m,
246 const graphene_matrix_t *src)
247{
248 m->value = src->value;
249
250 return m;
251}
252
253/**
254 * graphene_matrix_init_perspective:
255 * @m: a #graphene_matrix_t
256 * @fovy: the field of view angle, in degrees
257 * @aspect: the aspect value
258 * @z_near: the near Z plane
259 * @z_far: the far Z plane
260 *
261 * Initializes a #graphene_matrix_t with a perspective projection.
262 *
263 * Returns: (transfer none): the initialized matrix
264 *
265 * Since: 1.0
266 */
267graphene_matrix_t *
268graphene_matrix_init_perspective (graphene_matrix_t *m,
269 float fovy,
270 float aspect,
271 float z_near,
272 float z_far)
273{
274 float fovy_rad = GRAPHENE_DEG_TO_RAD (fovy);
275
276 graphene_simd4x4f_init_perspective (m: &m->value, fovy_rad, aspect, z_near, z_far);
277
278 return m;
279}
280
281/**
282 * graphene_matrix_init_ortho:
283 * @m: a #graphene_matrix_t
284 * @left: the left edge of the clipping plane
285 * @right: the right edge of the clipping plane
286 * @top: the top edge of the clipping plane
287 * @bottom: the bottom edge of the clipping plane
288 * @z_near: the distance of the near clipping plane
289 * @z_far: the distance of the far clipping plane
290 *
291 * Initializes a #graphene_matrix_t with an orthographic projection.
292 *
293 * Returns: (transfer none): the initialized matrix
294 *
295 * Since: 1.0
296 */
297graphene_matrix_t *
298graphene_matrix_init_ortho (graphene_matrix_t *m,
299 float left,
300 float right,
301 float top,
302 float bottom,
303 float z_near,
304 float z_far)
305{
306 graphene_simd4x4f_init_ortho (m: &m->value, left, right, bottom: top, top: bottom, z_near, z_far);
307
308 return m;
309}
310
311/**
312 * graphene_matrix_init_look_at:
313 * @m: a #graphene_matrix_t
314 * @eye: the vector describing the position to look from
315 * @center: the vector describing the position to look at
316 * @up: the vector describing the world's upward direction; usually,
317 * this is the graphene_vec3_y_axis() vector
318 *
319 * Initializes a #graphene_matrix_t so that it positions the "camera"
320 * at the given @eye coordinates towards an object at the @center
321 * coordinates. The top of the camera is aligned to the direction
322 * of the @up vector.
323 *
324 * Before the transform, the camera is assumed to be placed at the
325 * origin, looking towards the negative Z axis, with the top side of
326 * the camera facing in the direction of the Y axis and the right
327 * side in the direction of the X axis.
328 *
329 * In theory, one could use @m to transform a model of such a camera
330 * into world-space. However, it is more common to use the inverse of
331 * @m to transform another object from world coordinates to the view
332 * coordinates of the camera. Typically you would then apply the
333 * camera projection transform to get from view to screen
334 * coordinates.
335 *
336 * Returns: (transfer none): the initialized matrix
337 *
338 * Since: 1.0
339 */
340graphene_matrix_t *
341graphene_matrix_init_look_at (graphene_matrix_t *m,
342 const graphene_vec3_t *eye,
343 const graphene_vec3_t *center,
344 const graphene_vec3_t *up)
345{
346 graphene_simd4x4f_init_look_at (m: &m->value, eye: eye->value, center: center->value, up: up->value);
347
348 return m;
349}
350
351/**
352 * graphene_matrix_init_frustum:
353 * @m: a #graphene_matrix_t
354 * @left: distance of the left clipping plane
355 * @right: distance of the right clipping plane
356 * @bottom: distance of the bottom clipping plane
357 * @top: distance of the top clipping plane
358 * @z_near: distance of the near clipping plane
359 * @z_far: distance of the far clipping plane
360 *
361 * Initializes a #graphene_matrix_t compatible with #graphene_frustum_t.
362 *
363 * See also: graphene_frustum_init_from_matrix()
364 *
365 * Returns: (transfer none): the initialized matrix
366 *
367 * Since: 1.2
368 */
369graphene_matrix_t *
370graphene_matrix_init_frustum (graphene_matrix_t *m,
371 float left,
372 float right,
373 float bottom,
374 float top,
375 float z_near,
376 float z_far)
377{
378 graphene_simd4x4f_init_frustum (m: &m->value, left, right, bottom, top, z_near, z_far);
379
380 return m;
381}
382
383/**
384 * graphene_matrix_init_scale:
385 * @m: a #graphene_matrix_t
386 * @x: the scale factor on the X axis
387 * @y: the scale factor on the Y axis
388 * @z: the scale factor on the Z axis
389 *
390 * Initializes a #graphene_matrix_t with the given scaling factors.
391 *
392 * Returns: (transfer none): the initialized matrix
393 *
394 * Since: 1.0
395 */
396graphene_matrix_t *
397graphene_matrix_init_scale (graphene_matrix_t *m,
398 float x,
399 float y,
400 float z)
401{
402 m->value =
403 graphene_simd4x4f_init (graphene_simd4f_init ( x, 0.0f, 0.0f, 0.0f),
404 graphene_simd4f_init (0.0f, y, 0.0f, 0.0f),
405 graphene_simd4f_init (0.0f, 0.0f, z, 0.0f),
406 graphene_simd4f_init (0.0f, 0.0f, 0.0f, 1.0f));
407
408 return m;
409}
410
411/**
412 * graphene_matrix_init_translate:
413 * @m: a #graphene_matrix_t
414 * @p: the translation coordinates
415 *
416 * Initializes a #graphene_matrix_t with a translation to the
417 * given coordinates.
418 *
419 * Returns: (transfer none): the initialized matrix
420 *
421 * Since: 1.0
422 */
423graphene_matrix_t *
424graphene_matrix_init_translate (graphene_matrix_t *m,
425 const graphene_point3d_t *p)
426{
427 m->value =
428 graphene_simd4x4f_init (graphene_simd4f_init (1.0f, 0.0f, 0.0f, 0.0f),
429 graphene_simd4f_init (0.0f, 1.0f, 0.0f, 0.0f),
430 graphene_simd4f_init (0.0f, 0.0f, 1.0f, 0.0f),
431 graphene_simd4f_init (p->x, p->y, p->z, 1.0f));
432
433 return m;
434}
435
436/**
437 * graphene_matrix_init_skew:
438 * @m: a #graphene_matrix_t
439 * @x_skew: skew factor, in radians, on the X axis
440 * @y_skew: skew factor, in radians, on the Y axis
441 *
442 * Initializes a #graphene_matrix_t with a skew transformation
443 * with the given factors.
444 *
445 * Returns: (transfer none): the initialized matrix
446 *
447 * Since: 1.0
448 */
449graphene_matrix_t *
450graphene_matrix_init_skew (graphene_matrix_t *m,
451 float x_skew,
452 float y_skew)
453{
454 float t_x, t_y;
455
456 t_x = tanf (x: x_skew);
457 t_y = tanf (x: y_skew);
458
459 m->value =
460 graphene_simd4x4f_init (graphene_simd4f_init (1.0f, t_y, 0.0f, 0.0f),
461 graphene_simd4f_init ( t_x, 1.0f, 0.0f, 0.0f),
462 graphene_simd4f_init (0.0f, 0.0f, 1.0f, 0.0f),
463 graphene_simd4f_init (0.0f, 0.0f, 0.0f, 1.0f));
464
465 return m;
466}
467
468/**
469 * graphene_matrix_init_rotate:
470 * @m: a #graphene_matrix_t
471 * @angle: the rotation angle, in degrees
472 * @axis: the axis vector as a #graphene_vec3_t
473 *
474 * Initializes @m to represent a rotation of @angle degrees on
475 * the axis represented by the @axis vector.
476 *
477 * Returns: (transfer none): the initialized matrix
478 *
479 * Since: 1.0
480 */
481graphene_matrix_t *
482graphene_matrix_init_rotate (graphene_matrix_t *m,
483 float angle,
484 const graphene_vec3_t *axis)
485{
486 float rad = GRAPHENE_DEG_TO_RAD (angle);
487
488 graphene_simd4x4f_rotation (m: &m->value, rad, axis: axis->value);
489
490 return m;
491}
492
493/**
494 * graphene_matrix_is_identity:
495 * @m: a #graphene_matrix_t
496 *
497 * Checks whether the given #graphene_matrix_t is the identity matrix.
498 *
499 * Returns: `true` if the matrix is the identity matrix
500 *
501 * Since: 1.0
502 */
503bool
504graphene_matrix_is_identity (const graphene_matrix_t *m)
505{
506 return graphene_simd4x4f_is_identity (m: &m->value);
507}
508
509/**
510 * graphene_matrix_is_2d:
511 * @m: a #graphene_matrix_t
512 *
513 * Checks whether the given #graphene_matrix_t is compatible with an
514 * a 2D affine transformation matrix.
515 *
516 * Returns: `true` if the matrix is compatible with an affine
517 * transformation matrix
518 *
519 * Since: 1.0
520 */
521bool
522graphene_matrix_is_2d (const graphene_matrix_t *m)
523{
524#if 0
525 float res[4];
526
527 graphene_simd4f_dup_4f (m->value.x, res);
528 if (!(graphene_fuzzy_equals (res[2], 0.f, 0.000001) &&
529 graphene_fuzzy_equals (res[3], 0.f, 0.000001)))
530 return false;
531
532 graphene_simd4f_dup_4f (m->value.y, res);
533 if (!(graphene_fuzzy_equals (res[2], 0.f, 0.000001) &&
534 graphene_fuzzy_equals (res[3], 0.f, 0.000001)))
535 return false;
536
537 graphene_simd4f_dup_4f (m->value.z, res);
538 if (!(graphene_fuzzy_equals (res[0], 0.f, 0.000001) &&
539 graphene_fuzzy_equals (res[1], 0.f, 0.000001) &&
540 graphene_fuzzy_equals (res[2], 1.f, 0.000001) &&
541 graphene_fuzzy_equals (res[3], 0.f, 0.000001)))
542 return false;
543
544 graphene_simd4f_dup_4f (m->value.w, res);
545 if (!(graphene_fuzzy_equals (res[2], 0.f, 0.000001) &&
546 graphene_fuzzy_equals (res[3], 1.f, 0.000001)))
547 return false;
548
549 return true;
550#else
551 return graphene_simd4x4f_is_2d (m: &m->value);
552#endif
553}
554
555/**
556 * graphene_matrix_is_backface_visible:
557 * @m: a #graphene_matrix_t
558 *
559 * Checks whether a #graphene_matrix_t has a visible back face.
560 *
561 * Returns: `true` if the back face of the matrix is visible
562 *
563 * Since: 1.0
564 */
565bool
566graphene_matrix_is_backface_visible (const graphene_matrix_t *m)
567{
568 graphene_simd4x4f_t tmp;
569
570 if (!graphene_simd4x4f_inverse (m: &m->value, res: &tmp))
571 return false;
572
573 /* inverse.zz < 0 */
574 return graphene_simd4f_get_z (tmp.z) < 0.f;
575}
576
577/**
578 * graphene_matrix_is_singular:
579 * @m: a #graphene_matrix_t
580 *
581 * Checks whether a matrix is singular.
582 *
583 * Returns: `true` if the matrix is singular
584 *
585 * Since: 1.0
586 */
587bool
588graphene_matrix_is_singular (const graphene_matrix_t *m)
589{
590 graphene_simd4f_t det;
591
592 graphene_simd4x4f_determinant (m: &m->value, det_r: &det, NULL);
593
594 return fabsf (graphene_simd4f_get_x (det)) <= GRAPHENE_FLOAT_EPSILON;
595}
596
597/**
598 * graphene_matrix_init_from_2d:
599 * @m: a #graphene_matrix_t
600 * @xx: the xx member
601 * @yx: the yx member
602 * @xy: the xy member
603 * @yy: the yy member
604 * @x_0: the x0 member
605 * @y_0: the y0 member
606 *
607 * Initializes a #graphene_matrix_t from the values of an affine
608 * transformation matrix.
609 *
610 * The arguments map to the following matrix layout:
611 *
612 * |[<!-- language="plain" -->
613 * ⎛ xx yx ⎞ ⎛ a b 0 ⎞
614 * ⎜ xy yy ⎟ = ⎜ c d 0 ⎟
615 * ⎝ x0 y0 ⎠ ⎝ tx ty 1 ⎠
616 * ]|
617 *
618 * This function can be used to convert between an affine matrix type
619 * from other libraries and a #graphene_matrix_t.
620 *
621 * Returns: (transfer none): the initialized matrix
622 *
623 * Since: 1.0
624 */
625graphene_matrix_t *
626graphene_matrix_init_from_2d (graphene_matrix_t *m,
627 double xx,
628 double yx,
629 double xy,
630 double yy,
631 double x_0,
632 double y_0)
633{
634 m->value = graphene_simd4x4f_init (graphene_simd4f_init ((float) xx, (float) yx, 0.f, 0.f),
635 graphene_simd4f_init ((float) xy, (float) yy, 0.f, 0.f),
636 graphene_simd4f_init (0.f, 0.f, 1.f, 0.f),
637 graphene_simd4f_init ((float) x_0, (float) y_0, 0.f, 1.f));
638
639 return m;
640}
641
642/**
643 * graphene_matrix_to_2d:
644 * @m: a #graphene_matrix_t
645 * @xx: (out): return location for the xx member
646 * @yx: (out): return location for the yx member
647 * @xy: (out): return location for the xy member
648 * @yy: (out): return location for the yy member
649 * @x_0: (out): return location for the x0 member
650 * @y_0: (out): return location for the y0 member
651 *
652 * Converts a #graphene_matrix_t to an affine transformation
653 * matrix, if the given matrix is compatible.
654 *
655 * The returned values have the following layout:
656 *
657 * |[<!-- language="plain" -->
658 * ⎛ xx yx ⎞ ⎛ a b 0 ⎞
659 * ⎜ xy yy ⎟ = ⎜ c d 0 ⎟
660 * ⎝ x0 y0 ⎠ ⎝ tx ty 1 ⎠
661 * ]|
662 *
663 * This function can be used to convert between a #graphene_matrix_t
664 * and an affine matrix type from other libraries.
665 *
666 * Returns: `true` if the matrix is compatible with an affine
667 * transformation matrix
668 *
669 * Since: 1.0
670 */
671bool
672graphene_matrix_to_2d (const graphene_matrix_t *m,
673 double *xx,
674 double *yx,
675 double *xy,
676 double *yy,
677 double *x_0,
678 double *y_0)
679{
680 float res[4];
681
682 if (!graphene_simd4x4f_is_2d (m: &m->value))
683 return false;
684
685 graphene_simd4f_dup_4f (m->value.x, res);
686 if (xx != NULL)
687 *xx = res[0];
688 if (yx != NULL)
689 *yx = res[1];
690
691 graphene_simd4f_dup_4f (m->value.y, res);
692 if (xy != NULL)
693 *xy = res[0];
694 if (yy != NULL)
695 *yy = res[1];
696
697 graphene_simd4f_dup_4f (m->value.w, res);
698 if (x_0 != NULL)
699 *x_0 = res[0];
700 if (y_0 != NULL)
701 *y_0 = res[1];
702
703 return true;
704}
705
706/**
707 * graphene_matrix_get_row:
708 * @m: a #graphene_matrix_t
709 * @index_: the index of the row vector, between 0 and 3
710 * @res: (out caller-allocates): return location for the #graphene_vec4_t
711 * that is used to store the row vector
712 *
713 * Retrieves the given row vector at @index_ inside a matrix.
714 *
715 * Since: 1.0
716 */
717void
718graphene_matrix_get_row (const graphene_matrix_t *m,
719 unsigned int index_,
720 graphene_vec4_t *res)
721{
722 switch (index_)
723 {
724 case 0:
725 res->value = m->value.x;
726 break;
727
728 case 1:
729 res->value = m->value.y;
730 break;
731
732 case 2:
733 res->value = m->value.z;
734 break;
735
736 case 3:
737 res->value = m->value.w;
738 break;
739
740 default:
741 res->value = graphene_simd4f_init_zero ();
742 break;
743 }
744}
745
746/**
747 * graphene_matrix_get_value:
748 * @m: a #graphene_matrix_t
749 * @row: the row index
750 * @col: the column index
751 *
752 * Retrieves the value at the given @row and @col index.
753 *
754 * Returns: the value at the given indices
755 *
756 * Since: 1.0
757 */
758float
759graphene_matrix_get_value (const graphene_matrix_t *m,
760 unsigned int row,
761 unsigned int col)
762{
763 graphene_simd4f_t r;
764
765 if (row > 3 || col > 3)
766 return 0.f;
767
768 switch (row)
769 {
770 case 0:
771 r = m->value.x;
772 break;
773
774 case 1:
775 r = m->value.y;
776 break;
777
778 case 2:
779 r = m->value.z;
780 break;
781
782 case 3:
783 r = m->value.w;
784 break;
785
786 default:
787 return 0.f;
788 }
789
790 switch (col)
791 {
792 case 0:
793 return graphene_simd4f_get (r, 0);
794
795 case 1:
796 return graphene_simd4f_get (r, 1);
797
798 case 2:
799 return graphene_simd4f_get (r, 2);
800
801 case 3:
802 return graphene_simd4f_get (r, 3);
803
804 default:
805 return 0.f;
806 }
807
808 return 0.f;
809}
810
811/**
812 * graphene_matrix_multiply:
813 * @a: a #graphene_matrix_t
814 * @b: a #graphene_matrix_t
815 * @res: (out caller-allocates): return location for the matrix
816 * result
817 *
818 * Multiplies two #graphene_matrix_t.
819 *
820 * Matrix multiplication is not commutative in general; the order of the factors matters.
821 * The product of this multiplication is (@a × @b)
822 *
823 * Since: 1.0
824 */
825void
826graphene_matrix_multiply (const graphene_matrix_t *a,
827 const graphene_matrix_t *b,
828 graphene_matrix_t *res)
829{
830 graphene_simd4x4f_matrix_mul (a: &a->value, b: &b->value, res: &res->value);
831}
832
833/**
834 * graphene_matrix_determinant:
835 * @m: a #graphene_matrix_t
836 *
837 * Computes the determinant of the given matrix.
838 *
839 * Returns: the value of the determinant
840 *
841 * Since: 1.0
842 */
843float
844graphene_matrix_determinant (const graphene_matrix_t *m)
845{
846 graphene_simd4f_t det;
847
848 graphene_simd4x4f_determinant (m: &m->value, det_r: &det, NULL);
849
850 return graphene_simd4f_get_x (det);
851}
852
853/**
854 * graphene_matrix_transform_vec3:
855 * @m: a #graphene_matrix_t
856 * @v: a #graphene_vec3_t
857 * @res: (out caller-allocates): return location for a #graphene_vec3_t
858 *
859 * Transforms the given #graphene_vec3_t using the matrix @m.
860 *
861 * This function will multiply the X, Y, and Z row vectors of the matrix @m
862 * with the corresponding components of the vector @v. The W row vector will
863 * be ignored.
864 *
865 * See also: graphene_simd4x4f_vec3_mul()
866 *
867 * Since: 1.0
868 */
869void
870graphene_matrix_transform_vec3 (const graphene_matrix_t *m,
871 const graphene_vec3_t *v,
872 graphene_vec3_t *res)
873{
874 graphene_simd4x4f_vec3_mul (m: &m->value, v: &v->value, res: &res->value);
875}
876
877/**
878 * graphene_matrix_transform_vec4:
879 * @m: a #graphene_matrix_t
880 * @v: a #graphene_vec4_t
881 * @res: (out caller-allocates): return location for a #graphene_vec4_t
882 *
883 * Transforms the given #graphene_vec4_t using the matrix @m.
884 *
885 * See also: graphene_simd4x4f_vec4_mul()
886 *
887 * Since: 1.0
888 */
889void
890graphene_matrix_transform_vec4 (const graphene_matrix_t *m,
891 const graphene_vec4_t *v,
892 graphene_vec4_t *res)
893{
894 graphene_simd4x4f_vec4_mul (a: &m->value, b: &v->value, res: &res->value);
895}
896
897/**
898 * graphene_matrix_transform_point:
899 * @m: a #graphene_matrix_t
900 * @p: a #graphene_point_t
901 * @res: (out caller-allocates): return location for the
902 * transformed #graphene_point_t
903 *
904 * Transforms the given #graphene_point_t using the matrix @m.
905 *
906 * Unlike graphene_matrix_transform_vec3(), this function will take into
907 * account the fourth row vector of the #graphene_matrix_t when computing
908 * the dot product of each row vector of the matrix.
909 *
910 * See also: graphene_simd4x4f_point3_mul()
911 *
912 * Since: 1.0
913 */
914void
915graphene_matrix_transform_point (const graphene_matrix_t *m,
916 const graphene_point_t *p,
917 graphene_point_t *res)
918{
919 graphene_simd4f_t vec3;
920
921 vec3 = graphene_simd4f_init (p->x, p->y, 0.0f, 1.0f);
922 graphene_simd4x4f_point3_mul (m: &m->value, p: &vec3, res: &vec3);
923
924 res->x = graphene_simd4f_get_x (vec3);
925 res->y = graphene_simd4f_get_y (vec3);
926}
927
928/**
929 * graphene_matrix_transform_point3d:
930 * @m: a #graphene_matrix_t
931 * @p: a #graphene_point3d_t
932 * @res: (out caller-allocates): return location for the result
933 *
934 * Transforms the given #graphene_point3d_t using the matrix @m.
935 *
936 * Unlike graphene_matrix_transform_vec3(), this function will take into
937 * account the fourth row vector of the #graphene_matrix_t when computing
938 * the dot product of each row vector of the matrix.
939 *
940 * See also: graphene_simd4x4f_point3_mul()
941 *
942 * Since: 1.2
943 */
944void
945graphene_matrix_transform_point3d (const graphene_matrix_t *m,
946 const graphene_point3d_t *p,
947 graphene_point3d_t *res)
948{
949 graphene_simd4f_t vec3;
950
951 vec3 = graphene_simd4f_init (p->x, p->y, p->z, 1.f);
952 graphene_simd4x4f_point3_mul (m: &m->value, p: &vec3, res: &vec3);
953
954 res->x = graphene_simd4f_get_x (vec3);
955 res->y = graphene_simd4f_get_y (vec3);
956 res->z = graphene_simd4f_get_z (vec3);
957}
958
959/**
960 * graphene_matrix_transform_rect:
961 * @m: a #graphene_matrix_t
962 * @r: a #graphene_rect_t
963 * @res: (out caller-allocates): return location for the
964 * transformed quad
965 *
966 * Transforms each corner of a #graphene_rect_t using the given matrix @m.
967 *
968 * The result is a coplanar quadrilateral.
969 *
970 * See also: graphene_matrix_transform_point()
971 *
972 * Since: 1.0
973 */
974void
975graphene_matrix_transform_rect (const graphene_matrix_t *m,
976 const graphene_rect_t *r,
977 graphene_quad_t *res)
978{
979 graphene_point_t ret[4];
980 graphene_rect_t rr;
981
982 graphene_rect_normalize_r (r, res: &rr);
983
984#define TRANSFORM_POINT(matrix, rect, corner, out_p) do {\
985 graphene_simd4f_t __s; \
986 graphene_point_t __p; \
987 graphene_rect_get_ ## corner (rect, &__p); \
988 __s = graphene_simd4f_init (__p.x, __p.y, 0.f, 1.f); \
989 graphene_simd4x4f_vec4_mul (&matrix->value, &__s, &__s); \
990 out_p.x = graphene_simd4f_get_x (__s); \
991 out_p.y = graphene_simd4f_get_y (__s); } while (0)
992
993 TRANSFORM_POINT (m, &rr, top_left, ret[0]);
994 TRANSFORM_POINT (m, &rr, top_right, ret[1]);
995 TRANSFORM_POINT (m, &rr, bottom_right, ret[2]);
996 TRANSFORM_POINT (m, &rr, bottom_left, ret[3]);
997
998#undef TRANSFORM_POINT
999
1000 graphene_quad_init (q: res, p1: &ret[0], p2: &ret[1], p3: &ret[2], p4: &ret[3]);
1001}
1002
1003/**
1004 * graphene_matrix_transform_bounds:
1005 * @m: a #graphene_matrix_t
1006 * @r: a #graphene_rect_t
1007 * @res: (out caller-allocates): return location for the bounds
1008 * of the transformed rectangle
1009 *
1010 * Transforms each corner of a #graphene_rect_t using the given matrix @m.
1011 *
1012 * The result is the axis aligned bounding rectangle containing the coplanar
1013 * quadrilateral.
1014 *
1015 * See also: graphene_matrix_transform_point()
1016 *
1017 * Since: 1.0
1018 */
1019void
1020graphene_matrix_transform_bounds (const graphene_matrix_t *m,
1021 const graphene_rect_t *r,
1022 graphene_rect_t *res)
1023{
1024 graphene_point_t ret[4];
1025 float min_x, min_y;
1026 float max_x, max_y;
1027
1028 graphene_rect_t rr;
1029
1030 graphene_rect_normalize_r (r, res: &rr);
1031
1032#define TRANSFORM_POINT(matrix, rect, corner, out_p) do {\
1033 graphene_simd4f_t __s; \
1034 graphene_point_t __p; \
1035 graphene_rect_get_ ## corner (rect, &__p); \
1036 __s = graphene_simd4f_init (__p.x, __p.y, 0.f, 1.f); \
1037 graphene_simd4x4f_vec4_mul (&matrix->value, &__s, &__s); \
1038 out_p.x = graphene_simd4f_get_x (__s); \
1039 out_p.y = graphene_simd4f_get_y (__s); } while (0)
1040
1041 TRANSFORM_POINT (m, &rr, top_left, ret[0]);
1042 TRANSFORM_POINT (m, &rr, top_right, ret[1]);
1043 TRANSFORM_POINT (m, &rr, bottom_right, ret[2]);
1044 TRANSFORM_POINT (m, &rr, bottom_left, ret[3]);
1045
1046#undef TRANSFORM_POINT
1047
1048#if 0
1049 {
1050 int i;
1051
1052 min_x = max_x = ret[0].x;
1053 min_y = max_y = ret[0].y;
1054
1055 for (i = 1; i < 4; i += 1)
1056 {
1057 min_x = MIN (ret[i].x, min_x);
1058 min_y = MIN (ret[i].y, min_y);
1059
1060 max_x = MAX (ret[i].x, max_x);
1061 max_y = MAX (ret[i].y, max_y);
1062 }
1063 }
1064#else
1065 {
1066 const graphene_simd4f_t vx = graphene_simd4f_init (ret[0].x, ret[1].x, ret[2].x, ret[3].x);
1067 const graphene_simd4f_t vy = graphene_simd4f_init (ret[0].y, ret[1].y, ret[2].y, ret[3].y);
1068
1069 min_x = graphene_simd4f_get_x (graphene_simd4f_min_val (vx));
1070 min_y = graphene_simd4f_get_x (graphene_simd4f_min_val (vy));
1071
1072 max_x = graphene_simd4f_get_x (graphene_simd4f_max_val (vx));
1073 max_y = graphene_simd4f_get_x (graphene_simd4f_max_val (vy));
1074 }
1075#endif
1076
1077 graphene_rect_init (r: res, x: min_x, y: min_y, width: max_x - min_x, height: max_y - min_y);
1078}
1079
1080/**
1081 * graphene_matrix_transform_sphere:
1082 * @m: a #graphene_matrix_t
1083 * @s: a #graphene_sphere_t
1084 * @res: (out caller-allocates): return location for the bounds
1085 * of the transformed sphere
1086 *
1087 * Transforms a #graphene_sphere_t using the given matrix @m. The
1088 * result is the bounding sphere containing the transformed sphere.
1089 *
1090 * Since: 1.2
1091 */
1092void
1093graphene_matrix_transform_sphere (const graphene_matrix_t *m,
1094 const graphene_sphere_t *s,
1095 graphene_sphere_t *res)
1096{
1097 float max_scale;
1098
1099 graphene_simd4x4f_point3_mul (m: &m->value, p: &s->center.value, res: &res->center.value);
1100
1101 max_scale = graphene_simd4f_dot3_scalar (m->value.x, m->value.x);
1102 max_scale = fmaxf (x: max_scale, graphene_simd4f_dot3_scalar (m->value.y, m->value.y));
1103 max_scale = fmaxf (x: max_scale, graphene_simd4f_dot3_scalar (m->value.z, m->value.z));
1104
1105 res->radius = s->radius * sqrtf (x: max_scale);
1106}
1107
1108/**
1109 * graphene_matrix_transform_box:
1110 * @m: a #graphene_matrix_t
1111 * @b: a #graphene_box_t
1112 * @res: (out caller-allocates): return location for the bounds
1113 * of the transformed box
1114 *
1115 * Transforms the vertices of a #graphene_box_t using the given matrix @m.
1116 *
1117 * The result is the axis aligned bounding box containing the transformed
1118 * vertices.
1119 *
1120 * Since: 1.2
1121 */
1122void
1123graphene_matrix_transform_box (const graphene_matrix_t *m,
1124 const graphene_box_t *b,
1125 graphene_box_t *res)
1126{
1127 graphene_vec3_t points[8];
1128
1129 graphene_box_get_vertices (box: b, vertices: points);
1130
1131 for (int i = 0; i < 8; i++)
1132 graphene_simd4x4f_point3_mul (m: &m->value, p: &(points[i].value), res: &(points[i].value));
1133
1134 graphene_box_init_from_vectors (box: res, n_vectors: 8, vectors: points);
1135}
1136
1137/**
1138 * graphene_matrix_transform_ray:
1139 * @m: a #graphene_matrix_t
1140 * @r: a #graphene_ray_t
1141 * @res: (out caller-allocates): return location for the
1142 * transformed ray
1143 *
1144 * Transform a #graphene_ray_t using the given matrix @m.
1145 *
1146 * Since: 1.4
1147 */
1148void
1149graphene_matrix_transform_ray (const graphene_matrix_t *m,
1150 const graphene_ray_t *r,
1151 graphene_ray_t *res)
1152{
1153 graphene_vec3_t origin, direction;
1154 graphene_vec4_t origin4;
1155
1156 graphene_vec4_init_from_vec3 (v: &origin4, src: &r->origin, w: 1);
1157 graphene_matrix_transform_vec4 (m, v: &origin4, res: &origin4);
1158 graphene_vec4_get_xyz (v: &origin4, res: &origin);
1159
1160 graphene_matrix_transform_vec3 (m, v: &r->direction, res: &direction);
1161
1162 graphene_ray_init_from_vec3 (r: res, origin: &origin, direction: &direction);
1163}
1164
1165/**
1166 * graphene_matrix_project_point:
1167 * @m: a #graphene_matrix_t
1168 * @p: a #graphene_point_t
1169 * @res: (out caller-allocates): return location for the projected
1170 * point
1171 *
1172 * Projects a #graphene_point_t using the matrix @m.
1173 *
1174 * Since: 1.0
1175 */
1176void
1177graphene_matrix_project_point (const graphene_matrix_t *m,
1178 const graphene_point_t *p,
1179 graphene_point_t *res)
1180{
1181 graphene_simd4f_t pa, pb, pc;
1182 float a[3], b[3];
1183 float t;
1184
1185 pa = graphene_simd4f_init (p->x, p->y, 0.f, 0.f);
1186 pb = graphene_simd4f_init (p->x, p->y, 1.f, 0.f);
1187
1188 graphene_simd4x4f_vec3_mul (m: &m->value, v: &pa, res: &pa);
1189 graphene_simd4x4f_vec3_mul (m: &m->value, v: &pb, res: &pb);
1190 pc = graphene_simd4f_sub (pa, pb);
1191
1192 graphene_simd4f_dup_3f (pa, a);
1193 graphene_simd4f_dup_3f (pc, b);
1194 t = -a[2] / b[2];
1195
1196 graphene_point_init (p: res, x: a[0] + t * b[0], y: a[1] + t * b[1]);
1197}
1198
1199/**
1200 * graphene_matrix_project_rect_bounds:
1201 * @m: a #graphene_matrix_t
1202 * @r: a #graphene_rect_t
1203 * @res: (out caller-allocates): return location for the projected
1204 * rectangle
1205 *
1206 * Projects a #graphene_rect_t using the given matrix.
1207 *
1208 * The resulting rectangle is the axis aligned bounding rectangle capable
1209 * of fully containing the projected rectangle.
1210 *
1211 * Since: 1.0
1212 */
1213void
1214graphene_matrix_project_rect_bounds (const graphene_matrix_t *m,
1215 const graphene_rect_t *r,
1216 graphene_rect_t *res)
1217{
1218 graphene_point_t points[4];
1219 graphene_point_t ret[4];
1220 graphene_rect_t rr;
1221
1222 graphene_rect_normalize_r (r, res: &rr);
1223
1224 graphene_rect_get_top_left (r: &rr, p: &points[0]);
1225 graphene_rect_get_top_right (r: &rr, p: &points[1]);
1226 graphene_rect_get_bottom_left (r: &rr, p: &points[2]);
1227 graphene_rect_get_bottom_right (r: &rr, p: &points[3]);
1228
1229 graphene_matrix_project_point (m, p: &points[0], res: &ret[0]);
1230 graphene_matrix_project_point (m, p: &points[1], res: &ret[1]);
1231 graphene_matrix_project_point (m, p: &points[2], res: &ret[2]);
1232 graphene_matrix_project_point (m, p: &points[3], res: &ret[3]);
1233
1234 graphene_simd4f_t v_x = graphene_simd4f_init (ret[0].x, ret[1].x, ret[2].x, ret[3].x);
1235 graphene_simd4f_t v_y = graphene_simd4f_init (ret[0].y, ret[1].y, ret[2].y, ret[3].y);
1236
1237 float min_x = graphene_simd4f_get_x (graphene_simd4f_min_val (v_x));
1238 float max_x = graphene_simd4f_get_x (graphene_simd4f_max_val (v_x));
1239 float min_y = graphene_simd4f_get_x (graphene_simd4f_min_val (v_y));
1240 float max_y = graphene_simd4f_get_x (graphene_simd4f_max_val (v_y));
1241
1242 graphene_rect_init (r: res, x: min_x, y: min_y, width: max_x - min_x, height: max_y - min_y);
1243}
1244
1245/**
1246 * graphene_matrix_project_rect:
1247 * @m: a #graphene_matrix_t
1248 * @r: a #graphene_rect_t
1249 * @res: (out caller-allocates): return location for the projected
1250 * rectangle
1251 *
1252 * Projects all corners of a #graphene_rect_t using the given matrix.
1253 *
1254 * See also: graphene_matrix_project_point()
1255 *
1256 * Since: 1.2
1257 */
1258void
1259graphene_matrix_project_rect (const graphene_matrix_t *m,
1260 const graphene_rect_t *r,
1261 graphene_quad_t *res)
1262{
1263 graphene_point_t p[4];
1264 graphene_rect_t rr;
1265
1266 graphene_rect_normalize_r (r, res: &rr);
1267
1268 graphene_rect_get_top_left (r: &rr, p: &p[0]);
1269 graphene_matrix_project_point (m, p: &p[0], res: &p[0]);
1270
1271 graphene_rect_get_top_right (r: &rr, p: &p[1]);
1272 graphene_matrix_project_point (m, p: &p[1], res: &p[1]);
1273
1274 graphene_rect_get_bottom_left (r: &rr, p: &p[2]);
1275 graphene_matrix_project_point (m, p: &p[2], res: &p[2]);
1276
1277 graphene_rect_get_bottom_right (r: &rr, p: &p[3]);
1278 graphene_matrix_project_point (m, p: &p[3], res: &p[3]);
1279
1280 graphene_quad_init_from_points (q: res, points: p);
1281}
1282
1283/**
1284 * graphene_matrix_untransform_point:
1285 * @m: a #graphene_matrix_t
1286 * @p: a #graphene_point_t
1287 * @bounds: the bounds of the transformation
1288 * @res: (out caller-allocates): return location for the
1289 * untransformed point
1290 *
1291 * Undoes the transformation of a #graphene_point_t using the
1292 * given matrix, within the given axis aligned rectangular @bounds.
1293 *
1294 * Returns: `true` if the point was successfully untransformed
1295 *
1296 * Since: 1.0
1297 */
1298bool
1299graphene_matrix_untransform_point (const graphene_matrix_t *m,
1300 const graphene_point_t *p,
1301 const graphene_rect_t *bounds,
1302 graphene_point_t *res)
1303{
1304 graphene_matrix_t inverse;
1305 graphene_rect_t bounds_t;
1306
1307 if (graphene_matrix_is_2d (m))
1308 {
1309 if (!graphene_matrix_inverse (m, res: &inverse))
1310 return false;
1311
1312 graphene_matrix_transform_point (m: &inverse, p, res);
1313 return true;
1314 }
1315
1316 graphene_matrix_transform_bounds (m, r: bounds, res: &bounds_t);
1317 if (!graphene_rect_contains_point (r: &bounds_t, p))
1318 return false;
1319
1320 if (!graphene_matrix_inverse (m, res: &inverse))
1321 return false;
1322
1323 graphene_matrix_project_point (m: &inverse, p, res);
1324
1325 return true;
1326}
1327
1328/**
1329 * graphene_matrix_untransform_bounds:
1330 * @m: a #graphene_matrix_t
1331 * @r: a #graphene_rect_t
1332 * @bounds: the bounds of the transformation
1333 * @res: (out caller-allocates): return location for the
1334 * untransformed rectangle
1335 *
1336 * Undoes the transformation on the corners of a #graphene_rect_t using the
1337 * given matrix, within the given axis aligned rectangular @bounds.
1338 *
1339 * Since: 1.0
1340 */
1341void
1342graphene_matrix_untransform_bounds (const graphene_matrix_t *m,
1343 const graphene_rect_t *r,
1344 const graphene_rect_t *bounds,
1345 graphene_rect_t *res)
1346{
1347 graphene_matrix_t inverse;
1348 graphene_rect_t bounds_t;
1349 graphene_rect_t rect;
1350
1351 if (graphene_matrix_is_2d (m))
1352 {
1353 if (!graphene_matrix_inverse (m, res: &inverse))
1354 return;
1355
1356 graphene_matrix_transform_bounds (m: &inverse, r, res);
1357 return;
1358 }
1359
1360 graphene_matrix_transform_bounds (m, r: bounds, res: &bounds_t);
1361 if (!graphene_rect_intersection (a: r, b: &bounds_t, res: &rect))
1362 {
1363 graphene_rect_init (r: res, x: 0.f, y: 0.f, width: 0.f, height: 0.f);
1364 return;
1365 }
1366
1367 if (!graphene_matrix_inverse (m, res: &inverse))
1368 return;
1369
1370 graphene_matrix_project_rect_bounds (m: &inverse, r: &rect, res);
1371}
1372
1373/**
1374 * graphene_matrix_unproject_point3d:
1375 * @projection: a #graphene_matrix_t for the projection matrix
1376 * @modelview: a #graphene_matrix_t for the modelview matrix; this is
1377 * the inverse of the modelview used when projecting the point
1378 * @point: a #graphene_point3d_t with the coordinates of the point
1379 * @res: (out caller-allocates): return location for the unprojected
1380 * point
1381 *
1382 * Unprojects the given @point using the @projection matrix and
1383 * a @modelview matrix.
1384 *
1385 * Since: 1.2
1386 */
1387void
1388graphene_matrix_unproject_point3d (const graphene_matrix_t *projection,
1389 const graphene_matrix_t *modelview,
1390 const graphene_point3d_t *point,
1391 graphene_point3d_t *res)
1392{
1393 graphene_simd4x4f_t tmp;
1394 graphene_simd4f_t v;
1395 float values[4];
1396 float inv_w;
1397
1398 if (!graphene_simd4x4f_inverse (m: &projection->value, res: &tmp))
1399 return;
1400
1401 graphene_simd4x4f_matrix_mul (a: &tmp, b: &modelview->value, res: &tmp);
1402
1403 v = graphene_simd4f_init (point->x, point->y, point->z, 1.f);
1404 graphene_simd4x4f_vec4_mul (a: &tmp, b: &v, res: &v);
1405
1406 inv_w = 1.f / graphene_simd4f_get_w (v);
1407 v = graphene_simd4f_mul (v, graphene_simd4f_splat (inv_w));
1408
1409 graphene_simd4f_dup_4f (v, values);
1410 graphene_point3d_init (p: res, x: values[0], y: values[1], z: values[2]);
1411}
1412
1413/**
1414 * graphene_matrix_translate:
1415 * @m: a #graphene_matrix_t
1416 * @pos: a #graphene_point3d_t
1417 *
1418 * Adds a translation transformation to @m using the coordinates
1419 * of the given #graphene_point3d_t.
1420 *
1421 * This is the equivalent of calling graphene_matrix_init_translate() and
1422 * then multiplying @m with the translation matrix.
1423 *
1424 * Since: 1.0
1425 */
1426void
1427graphene_matrix_translate (graphene_matrix_t *m,
1428 const graphene_point3d_t *pos)
1429{
1430 graphene_simd4x4f_t trans_m;
1431
1432 graphene_simd4x4f_translation (m: &trans_m, x: pos->x, y: pos->y, z: pos->z);
1433 graphene_simd4x4f_matrix_mul (a: &m->value, b: &trans_m, res: &m->value);
1434}
1435
1436/**
1437 * graphene_matrix_rotate_quaternion:
1438 * @m: a #graphene_matrix_t
1439 * @q: a rotation described by a #graphene_quaternion_t
1440 *
1441 * Adds a rotation transformation to @m, using the given
1442 * #graphene_quaternion_t.
1443 *
1444 * This is the equivalent of calling graphene_quaternion_to_matrix() and
1445 * then multiplying @m with the rotation matrix.
1446 *
1447 * Since: 1.2
1448 */
1449void
1450graphene_matrix_rotate_quaternion (graphene_matrix_t *m,
1451 const graphene_quaternion_t *q)
1452{
1453 graphene_matrix_t rot;
1454
1455 graphene_quaternion_to_matrix (q, m: &rot);
1456 graphene_matrix_multiply (a: m, b: &rot, res: m);
1457}
1458
1459/**
1460 * graphene_matrix_rotate_euler:
1461 * @m: a #graphene_matrix_t
1462 * @e: a rotation described by a #graphene_euler_t
1463 *
1464 * Adds a rotation transformation to @m, using the given
1465 * #graphene_euler_t.
1466 *
1467 * Since: 1.2
1468 */
1469void
1470graphene_matrix_rotate_euler (graphene_matrix_t *m,
1471 const graphene_euler_t *e)
1472{
1473 graphene_quaternion_t q;
1474
1475 graphene_quaternion_init_from_euler (q: &q, e);
1476 graphene_matrix_rotate_quaternion (m, q: &q);
1477}
1478
1479static inline void
1480graphene_matrix_rotate_internal (graphene_simd4x4f_t *m,
1481 float rad,
1482 const graphene_simd4f_t axis)
1483{
1484 graphene_simd4x4f_t rot_m;
1485
1486 graphene_simd4x4f_rotation (m: &rot_m, rad, axis);
1487 graphene_simd4x4f_matrix_mul (a: m, b: &rot_m, res: m);
1488}
1489
1490/**
1491 * graphene_matrix_rotate:
1492 * @m: a #graphene_matrix_t
1493 * @angle: the rotation angle, in degrees
1494 * @axis: the rotation axis, as a #graphene_vec3_t
1495 *
1496 * Adds a rotation transformation to @m, using the given @angle
1497 * and @axis vector.
1498 *
1499 * This is the equivalent of calling graphene_matrix_init_rotate() and
1500 * then multiplying the matrix @m with the rotation matrix.
1501 *
1502 * Since: 1.0
1503 */
1504void
1505graphene_matrix_rotate (graphene_matrix_t *m,
1506 float angle,
1507 const graphene_vec3_t *axis)
1508{
1509 graphene_matrix_rotate_internal (m: &m->value, GRAPHENE_DEG_TO_RAD (angle), axis: axis->value);
1510}
1511
1512/**
1513 * graphene_matrix_rotate_x:
1514 * @m: a #graphene_matrix_t
1515 * @angle: the rotation angle, in degrees
1516 *
1517 * Adds a rotation transformation around the X axis to @m, using
1518 * the given @angle.
1519 *
1520 * See also: graphene_matrix_rotate()
1521 *
1522 * Since: 1.0
1523 */
1524void
1525graphene_matrix_rotate_x (graphene_matrix_t *m,
1526 float angle)
1527{
1528 graphene_matrix_rotate_internal (m: &m->value, GRAPHENE_DEG_TO_RAD (angle),
1529 graphene_simd4f_init (1.f, 0.f, 0.f, 0.f));
1530}
1531
1532/**
1533 * graphene_matrix_rotate_y:
1534 * @m: a #graphene_matrix_t
1535 * @angle: the rotation angle, in degrees
1536 *
1537 * Adds a rotation transformation around the Y axis to @m, using
1538 * the given @angle.
1539 *
1540 * See also: graphene_matrix_rotate()
1541 *
1542 * Since: 1.0
1543 */
1544void
1545graphene_matrix_rotate_y (graphene_matrix_t *m,
1546 float angle)
1547{
1548 graphene_matrix_rotate_internal (m: &m->value, GRAPHENE_DEG_TO_RAD (angle),
1549 graphene_simd4f_init (0.f, 1.f, 0.f, 0.f));
1550}
1551
1552/**
1553 * graphene_matrix_rotate_z:
1554 * @m: a #graphene_matrix_t
1555 * @angle: the rotation angle, in degrees
1556 *
1557 * Adds a rotation transformation around the Z axis to @m, using
1558 * the given @angle.
1559 *
1560 * See also: graphene_matrix_rotate()
1561 *
1562 * Since: 1.0
1563 */
1564void
1565graphene_matrix_rotate_z (graphene_matrix_t *m,
1566 float angle)
1567{
1568 graphene_matrix_rotate_internal (m: &m->value, GRAPHENE_DEG_TO_RAD (angle),
1569 graphene_simd4f_init (0.f, 0.f, 1.f, 0.f));
1570}
1571
1572/**
1573 * graphene_matrix_scale:
1574 * @m: a #graphene_matrix_t
1575 * @factor_x: scaling factor on the X axis
1576 * @factor_y: scaling factor on the Y axis
1577 * @factor_z: scaling factor on the Z axis
1578 *
1579 * Adds a scaling transformation to @m, using the three
1580 * given factors.
1581 *
1582 * This is the equivalent of calling graphene_matrix_init_scale() and then
1583 * multiplying the matrix @m with the scale matrix.
1584 *
1585 * Since: 1.0
1586 */
1587void
1588graphene_matrix_scale (graphene_matrix_t *m,
1589 float factor_x,
1590 float factor_y,
1591 float factor_z)
1592{
1593 graphene_simd4x4f_t scale_m;
1594
1595 graphene_simd4x4f_scale (m: &scale_m, x: factor_x, y: factor_y, z: factor_z);
1596 graphene_simd4x4f_matrix_mul (a: &m->value, b: &scale_m, res: &m->value);
1597}
1598
1599/**
1600 * graphene_matrix_skew_xy:
1601 * @m: a #graphene_matrix_t
1602 * @factor: skew factor
1603 *
1604 * Adds a skew of @factor on the X and Y axis to the given matrix.
1605 *
1606 * Since: 1.0
1607 */
1608void
1609graphene_matrix_skew_xy (graphene_matrix_t *m,
1610 float factor)
1611{
1612 graphene_simd4f_t m_x, m_y;
1613
1614 m_x = m->value.x;
1615 m_y = m->value.y;
1616
1617 m->value.y = graphene_simd4f_madd (m1: m_x, graphene_simd4f_splat (factor), a: m_y);
1618}
1619
1620/**
1621 * graphene_matrix_skew_xz:
1622 * @m: a #graphene_matrix_t
1623 * @factor: skew factor
1624 *
1625 * Adds a skew of @factor on the X and Z axis to the given matrix.
1626 *
1627 * Since: 1.0
1628 */
1629void
1630graphene_matrix_skew_xz (graphene_matrix_t *m,
1631 float factor)
1632{
1633 graphene_simd4f_t m_x, m_z;
1634
1635 m_x = m->value.x;
1636 m_z = m->value.z;
1637
1638 m->value.z = graphene_simd4f_madd (m1: m_x, graphene_simd4f_splat (factor), a: m_z);
1639}
1640
1641/**
1642 * graphene_matrix_skew_yz:
1643 * @m: a #graphene_matrix_t
1644 * @factor: skew factor
1645 *
1646 * Adds a skew of @factor on the Y and Z axis to the given matrix.
1647 *
1648 * Since: 1.0
1649 */
1650void
1651graphene_matrix_skew_yz (graphene_matrix_t *m,
1652 float factor)
1653{
1654 graphene_simd4f_t m_y, m_z;
1655
1656 m_y = m->value.y;
1657 m_z = m->value.z;
1658
1659 m->value.z = graphene_simd4f_madd (m1: m_y, graphene_simd4f_splat (factor), a: m_z);
1660}
1661
1662/**
1663 * graphene_matrix_transpose:
1664 * @m: a #graphene_matrix_t
1665 * @res: (out caller-allocates): return location for the
1666 * transposed matrix
1667 *
1668 * Transposes the given matrix.
1669 *
1670 * Since: 1.0
1671 */
1672void
1673graphene_matrix_transpose (const graphene_matrix_t *m,
1674 graphene_matrix_t *res)
1675{
1676 graphene_simd4x4f_transpose (s: &m->value, res: &res->value);
1677}
1678
1679/**
1680 * graphene_matrix_inverse:
1681 * @m: a #graphene_matrix_t
1682 * @res: (out caller-allocates): return location for the
1683 * inverse matrix
1684 *
1685 * Inverts the given matrix.
1686 *
1687 * Returns: `true` if the matrix is invertible
1688 *
1689 * Since: 1.0
1690 */
1691bool
1692graphene_matrix_inverse (const graphene_matrix_t *m,
1693 graphene_matrix_t *res)
1694{
1695 return graphene_simd4x4f_inverse (m: &m->value, res: &res->value);
1696}
1697
1698/**
1699 * graphene_matrix_perspective:
1700 * @m: a #graphene_matrix_t
1701 * @depth: the depth of the perspective
1702 * @res: (out caller-allocates): return location for the
1703 * perspective matrix
1704 *
1705 * Applies a perspective of @depth to the matrix.
1706 *
1707 * Since: 1.0
1708 */
1709void
1710graphene_matrix_perspective (const graphene_matrix_t *m,
1711 float depth,
1712 graphene_matrix_t *res)
1713{
1714
1715 res->value = m->value;
1716
1717 graphene_simd4x4f_perspective (m: &res->value, depth);
1718}
1719
1720/**
1721 * graphene_matrix_normalize:
1722 * @m: a #graphene_matrix_t
1723 * @res: (out caller-allocates): return location for the normalized matrix
1724 *
1725 * Normalizes the given #graphene_matrix_t.
1726 *
1727 * Since: 1.0
1728 */
1729void
1730graphene_matrix_normalize (const graphene_matrix_t *m,
1731 graphene_matrix_t *res)
1732{
1733
1734 float ww = graphene_simd4f_get_w (m->value.w);
1735
1736 if (graphene_approx_val (a: ww, b: 0.f))
1737 return;
1738
1739 graphene_simd4f_t n = graphene_simd4f_splat (1.f / ww);
1740
1741 res->value.x = graphene_simd4f_mul (m->value.x, n);
1742 res->value.y = graphene_simd4f_mul (m->value.y, n);
1743 res->value.z = graphene_simd4f_mul (m->value.z, n);
1744 res->value.w = graphene_simd4f_mul (m->value.w, n);
1745}
1746
1747/**
1748 * graphene_matrix_get_x_translation:
1749 * @m: a #graphene_matrix_t
1750 *
1751 * Retrieves the translation component on the X axis from @m.
1752 *
1753 * Returns: the translation component
1754 *
1755 * Since: 1.10
1756 */
1757float
1758graphene_matrix_get_x_translation (const graphene_matrix_t *m)
1759{
1760 return graphene_simd4f_get_x (m->value.w);
1761}
1762
1763/**
1764 * graphene_matrix_get_y_translation:
1765 * @m: a #graphene_matrix_t
1766 *
1767 * Retrieves the translation component on the Y axis from @m.
1768 *
1769 * Returns: the translation component
1770 *
1771 * Since: 1.10
1772 */
1773float
1774graphene_matrix_get_y_translation (const graphene_matrix_t *m)
1775{
1776 return graphene_simd4f_get_y (m->value.w);
1777}
1778
1779/**
1780 * graphene_matrix_get_z_translation:
1781 * @m: a #graphene_matrix_t
1782 *
1783 * Retrieves the translation component on the Z axis from @m.
1784 *
1785 * Returns: the translation component
1786 *
1787 * Since: 1.10
1788 */
1789float
1790graphene_matrix_get_z_translation (const graphene_matrix_t *m)
1791{
1792 return graphene_simd4f_get_z (m->value.w);
1793}
1794
1795/**
1796 * graphene_matrix_get_x_scale:
1797 * @m: a #graphene_matrix_t
1798 *
1799 * Retrieves the scaling factor on the X axis in @m.
1800 *
1801 * Returns: the value of the scaling factor
1802 *
1803 * Since: 1.0
1804 */
1805float
1806graphene_matrix_get_x_scale (const graphene_matrix_t *m)
1807{
1808 return graphene_simd4f_get_x (m->value.x);
1809}
1810
1811/**
1812 * graphene_matrix_get_y_scale:
1813 * @m: a #graphene_matrix_t
1814 *
1815 * Retrieves the scaling factor on the Y axis in @m.
1816 *
1817 * Returns: the value of the scaling factor
1818 *
1819 * Since: 1.0
1820 */
1821float
1822graphene_matrix_get_y_scale (const graphene_matrix_t *m)
1823{
1824 return graphene_simd4f_get_y (m->value.y);
1825}
1826
1827/**
1828 * graphene_matrix_get_z_scale:
1829 * @m: a #graphene_matrix_t
1830 *
1831 * Retrieves the scaling factor on the Z axis in @m.
1832 *
1833 * Returns: the value of the scaling factor
1834 *
1835 * Since: 1.0
1836 */
1837float
1838graphene_matrix_get_z_scale (const graphene_matrix_t *m)
1839{
1840 return graphene_simd4f_get_z (m->value.z);
1841}
1842
1843#define XY_SHEAR 0
1844#define XZ_SHEAR 1
1845#define YZ_SHEAR 2
1846
1847#define M_11 0
1848#define M_12 1
1849#define M_21 2
1850#define M_22 3
1851
1852static bool
1853matrix_decompose_2d (const graphene_matrix_t *m,
1854 graphene_vec2_t *translate_r,
1855 graphene_vec2_t *scale_r,
1856 double *angle_r,
1857 float m_r[4])
1858{
1859 float row0x = graphene_matrix_get_value (m, row: 0, col: 0);
1860 float row0y = graphene_matrix_get_value (m, row: 1, col: 0);
1861 float row1x = graphene_matrix_get_value (m, row: 0, col: 1);
1862 float row1y = graphene_matrix_get_value (m, row: 1, col: 1);
1863 float scale_x, scale_y;
1864 float angle;
1865 float det;
1866
1867 if (fabsf (x: row0x * row1y - row0y * row1x) < FLT_EPSILON)
1868 return false;
1869
1870 graphene_vec2_init (v: translate_r,
1871 x: graphene_matrix_get_value (m, row: 3, col: 0),
1872 y: graphene_matrix_get_value (m, row: 3, col: 1));
1873
1874 scale_x = sqrtf (x: row0x * row0x + row0y * row0y);
1875 scale_y = sqrtf (x: row1x * row1x + row1y * row1y);
1876
1877 det = row0x * row1y - row0y * row1x;
1878 if (det < 0)
1879 {
1880 if (row0x < row1y)
1881 scale_x = -scale_x;
1882 else
1883 scale_y = -scale_y;
1884 }
1885
1886 if (!graphene_approx_val (a: scale_x, b: 0.f))
1887 {
1888 row0x = row0x * (1.f / scale_x);
1889 row0y = row0y * (1.f / scale_y);
1890 }
1891
1892 if (!graphene_approx_val (a: scale_y, b: 0.f))
1893 {
1894 row1x = row1x * (1.f / scale_x);
1895 row1y = row1y * (1.f / scale_y);
1896 }
1897
1898 graphene_vec2_init (v: scale_r, x: scale_x, y: scale_y);
1899
1900 angle = atan2f (y: row0y, x: row0x);
1901
1902 if (fabsf (x: angle) > FLT_EPSILON)
1903 {
1904 double sn = -row0y, cs = row0x;
1905 double m11 = row0x, m12 = row0y;
1906 double m21 = row1x, m22 = row1y;
1907
1908 row0x = (float) (cs * m11 + sn * m21);
1909 row0y = (float) (cs * m12 + sn * m22);
1910 row1x = (float) (-sn * m11 + cs * m21);
1911 row1y = (float) (-sn * m12 + cs * m22);
1912 }
1913
1914 m_r[M_11] = row0x;
1915 m_r[M_12] = row0y;
1916 m_r[M_21] = row1x;
1917 m_r[M_22] = row1y;
1918
1919 *angle_r = GRAPHENE_RAD_TO_DEG (angle);
1920
1921 return true;
1922}
1923
1924static bool
1925matrix_decompose_3d (const graphene_matrix_t *m,
1926 graphene_vec3_t *scale_r,
1927 graphene_vec3_t *shear_r,
1928 graphene_quaternion_t *rotate_r,
1929 graphene_vec3_t *translate_r,
1930 graphene_vec4_t *perspective_r)
1931{
1932 graphene_matrix_t local;
1933 float shear_xy, shear_xz, shear_yz;
1934 float scale_x, scale_y, scale_z;
1935 graphene_simd4f_t perspective_v;
1936 graphene_simd4f_t cross;
1937
1938 if (graphene_approx_val (graphene_simd4f_get_w (m->value.w), b: 0.f))
1939 return false;
1940
1941 local = *m;
1942
1943 /* normalize the matrix */
1944 graphene_matrix_normalize (m: &local, res: &local);
1945
1946 /* perspective is used to solve for the perspective component,
1947 * but it also provides an easy way to test for singularity of
1948 * the upper 3x3 component
1949 */
1950 perspective_v = graphene_simd4f_init (graphene_simd4f_get_w (local.value.x),
1951 graphene_simd4f_get_w (local.value.y),
1952 graphene_simd4f_get_w (local.value.z),
1953 graphene_simd4f_get_w (local.value.w));
1954
1955 /* Clear the perspective component */
1956 local.value.x = graphene_simd4f_merge_w (local.value.x, 0.f);
1957 local.value.y = graphene_simd4f_merge_w (local.value.y, 0.f);
1958 local.value.z = graphene_simd4f_merge_w (local.value.z, 0.f);
1959 local.value.w = graphene_simd4f_merge_w (local.value.w, 1.f);
1960
1961 if (graphene_approx_val (a: graphene_matrix_determinant (m: &local), b: 0.f))
1962 return false;
1963
1964 /* isolate the perspective */
1965 if (!graphene_simd4f_is_zero3 (v: perspective_v))
1966 {
1967 graphene_matrix_t tmp;
1968
1969 /* perspective_r is the right hand side of the equation */
1970 perspective_r->value = perspective_v;
1971
1972 /* solve the equation by inverting perspective and multiplying
1973 * the inverse with the perspective vector; we don't need to
1974 * check if the matrix is invertible here because we just checked
1975 * whether the determinant is not zero.
1976 */
1977 graphene_matrix_inverse (m: &local, res: &tmp);
1978 graphene_matrix_transform_vec4 (m: &tmp, v: perspective_r, res: perspective_r);
1979 }
1980 else
1981 graphene_vec4_init (v: perspective_r, x: 0.f, y: 0.f, z: 0.f, w: 1.f);
1982
1983 /* next, take care of the translation partition */
1984 translate_r->value = graphene_simd4f_merge_w (local.value.w, 0.f);
1985 local.value.w = graphene_simd4f_init (0.f, 0.f, 0.f, graphene_simd4f_get_w (local.value.w));
1986
1987 /* now get scale and shear */
1988
1989 /* compute the X scale factor and normalize the first row */
1990 scale_x = graphene_simd4f_get_x (graphene_simd4f_length4 (local.value.x));
1991 local.value.x = graphene_simd4f_normalize4 (v: local.value.x);
1992
1993 /* compute XY shear factor and the second row orthogonal to the first */
1994 shear_xy = graphene_simd4f_get_x (graphene_simd4f_dot4 (local.value.x, local.value.y));
1995 local.value.y = graphene_simd4f_sub (local.value.y, graphene_simd4f_mul (local.value.x, graphene_simd4f_splat (shear_xy)));
1996
1997 /* now, compute the Y scale factor and normalize the second row */
1998 scale_y = graphene_simd4f_get_x (graphene_simd4f_length4 (local.value.y));
1999 local.value.y = graphene_simd4f_normalize4 (v: local.value.y);
2000 shear_xy /= scale_y;
2001
2002 /* compute XZ and YZ shears, make the third row orthogonal */
2003 shear_xz = graphene_simd4f_get_x (graphene_simd4f_dot4 (local.value.x, local.value.z));
2004 local.value.z = graphene_simd4f_sub (local.value.z, graphene_simd4f_mul (local.value.x, graphene_simd4f_splat (shear_xz)));
2005 shear_yz = graphene_simd4f_get_x (graphene_simd4f_dot4 (local.value.y, local.value.z));
2006 local.value.z = graphene_simd4f_sub (local.value.z, graphene_simd4f_mul (local.value.y, graphene_simd4f_splat (shear_yz)));
2007
2008 /* next, get the Z scale and normalize the third row */
2009 scale_z = graphene_simd4f_get_x (graphene_simd4f_length4 (local.value.z));
2010 local.value.z = graphene_simd4f_normalize4 (v: local.value.z);
2011
2012 shear_xz /= scale_z;
2013 shear_yz /= scale_z;
2014
2015 graphene_vec3_init (v: shear_r, x: shear_xy, y: shear_xz, z: shear_yz);
2016
2017 /* at this point, the matrix is orthonormal. we check for a
2018 * coordinate system flip. if the determinant is -1, then
2019 * negate the matrix and the scaling factors
2020 */
2021 cross = graphene_simd4f_dot3 (local.value.x, graphene_simd4f_cross3 (local.value.y, local.value.z));
2022 if (graphene_simd4f_get_x (cross) < 0.f)
2023 {
2024 scale_x *= -1.f;
2025 scale_y *= -1.f;
2026 scale_z *= -1.f;
2027
2028 local.value.x = graphene_simd4f_neg (local.value.x);
2029 local.value.y = graphene_simd4f_neg (local.value.y);
2030 local.value.z = graphene_simd4f_neg (local.value.z);
2031 }
2032
2033 graphene_vec3_init (v: scale_r, x: scale_x, y: scale_y, z: scale_z);
2034
2035 /* get the rotations out */
2036 graphene_quaternion_init_from_matrix (q: rotate_r, m: &local);
2037
2038 return true;
2039}
2040
2041/**
2042 * graphene_matrix_decompose:
2043 * @m: a #graphene_matrix_t
2044 * @translate: (out caller-allocates): the translation vector
2045 * @scale: (out caller-allocates): the scale vector
2046 * @rotate: (out caller-allocates): the rotation quaternion
2047 * @shear: (out caller-allocates): the shear vector
2048 * @perspective: (out caller-allocates): the perspective vector
2049 *
2050 * Decomposes a transformation matrix into its component transformations.
2051 *
2052 * The algorithm for decomposing a matrix is taken from the
2053 * [CSS3 Transforms specification](http://dev.w3.org/csswg/css-transforms/);
2054 * specifically, the decomposition code is based on the equivalent code
2055 * published in "Graphics Gems II", edited by Jim Arvo, and
2056 * [available online](http://tog.acm.org/resources/GraphicsGems/gemsii/unmatrix.c).
2057 *
2058 * Returns: `true` if the matrix could be decomposed
2059 */
2060bool
2061graphene_matrix_decompose (const graphene_matrix_t *m,
2062 graphene_vec3_t *translate,
2063 graphene_vec3_t *scale,
2064 graphene_quaternion_t *rotate,
2065 graphene_vec3_t *shear,
2066 graphene_vec4_t *perspective)
2067{
2068 if (graphene_matrix_is_2d (m))
2069 {
2070 graphene_vec2_t translate_res;
2071 graphene_vec2_t scale_res;
2072 double rotate_res;
2073 float m_res[4];
2074
2075 if (!matrix_decompose_2d (m, translate_r: &translate_res, scale_r: &scale_res, angle_r: &rotate_res, m_r: m_res))
2076 return false;
2077
2078 translate->value = translate_res.value;
2079 scale->value = scale_res.value;
2080 graphene_quaternion_init_from_angles (q: rotate, deg_x: 0.f, deg_y: 0.f, deg_z: (float) rotate_res);
2081 graphene_vec3_init_from_vec3 (v: shear, src: graphene_vec3_zero ());
2082 graphene_vec4_init_from_vec4 (v: perspective, src: graphene_vec4_zero ());
2083 }
2084 else if (!matrix_decompose_3d (m, scale_r: scale, shear_r: shear, rotate_r: rotate, translate_r: translate, perspective_r: perspective))
2085 return false;
2086
2087 return true;
2088}
2089
2090/**
2091 * graphene_matrix_interpolate:
2092 * @a: a #graphene_matrix_t
2093 * @b: a #graphene_matrix_t
2094 * @factor: the linear interpolation factor
2095 * @res: (out caller-allocates): return location for the
2096 * interpolated matrix
2097 *
2098 * Linearly interpolates the two given #graphene_matrix_t by
2099 * interpolating the decomposed transformations separately.
2100 *
2101 * If either matrix cannot be reduced to their transformations
2102 * then the interpolation cannot be performed, and this function
2103 * will return an identity matrix.
2104 *
2105 * Since: 1.0
2106 */
2107void
2108graphene_matrix_interpolate (const graphene_matrix_t *a,
2109 const graphene_matrix_t *b,
2110 double factor,
2111 graphene_matrix_t *res)
2112{
2113 bool success = false;
2114
2115 /* Always provide a valid fallback in case we can't decompose either
2116 * or both matrices
2117 */
2118 graphene_matrix_init_identity (m: res);
2119
2120 /* Special case the decomposition if we're interpolating between two
2121 * affine transformations.
2122 */
2123 if (graphene_matrix_is_2d (m: a) &&
2124 graphene_matrix_is_2d (m: b))
2125 {
2126 graphene_vec2_t translate_a, translate_b, translate_res;
2127 graphene_vec2_t scale_a, scale_b, scale_res;
2128 double rotate_a, rotate_b, rotate_res;
2129 float m_a[4], m_b[4], m_res[4];
2130
2131 success |= matrix_decompose_2d (m: a, translate_r: &translate_a, scale_r: &scale_a, angle_r: &rotate_a, m_r: m_a);
2132 success |= matrix_decompose_2d (m: b, translate_r: &translate_b, scale_r: &scale_b, angle_r: &rotate_b, m_r: m_b);
2133
2134 /* If we cannot decompose either matrix we bail out with an identity */
2135 if (!success)
2136 return;
2137
2138 /* Flip the scaling factor and angle so they are consistent */
2139 float scale_ax = graphene_vec2_get_x (v: &scale_a);
2140 float scale_ay = graphene_vec2_get_y (v: &scale_a);
2141 float scale_bx = graphene_vec2_get_x (v: &scale_b);
2142 float scale_by = graphene_vec2_get_y (v: &scale_b);
2143 if ((scale_ax < 0 && scale_by < 0) || (scale_ay < 0 && scale_bx < 0))
2144 {
2145 graphene_vec2_negate (v: &scale_a, res: &scale_a);
2146
2147 rotate_a += (rotate_a < 0) ? 180 : -180;
2148 }
2149
2150 /* Do not rotate "the long way around" */
2151 if (fabs (x: rotate_a) <= DBL_EPSILON)
2152 rotate_a = 360;
2153 if (fabs (x: rotate_b) <= DBL_EPSILON)
2154 rotate_b = 360;
2155
2156 if (fabs (x: rotate_a - rotate_b) > 180)
2157 {
2158 if (rotate_a > rotate_b)
2159 rotate_a -= 360;
2160 else
2161 rotate_b -= 360;
2162 }
2163
2164 graphene_vec2_interpolate (v1: &translate_a, v2: &translate_b, factor, res: &translate_res);
2165 graphene_vec2_interpolate (v1: &scale_a, v2: &scale_b, factor, res: &scale_res);
2166 rotate_res = graphene_flerp (a: rotate_a, b: rotate_b, factor);
2167
2168 /* Interpolate each component of the (2,2) matrices */
2169 graphene_simd4f_t tmp_va = graphene_simd4f_init_4f (m_a);
2170 graphene_simd4f_t tmp_vb = graphene_simd4f_init_4f (m_b);
2171 graphene_simd4f_t tmp_vres = graphene_simd4f_interpolate (a: tmp_va, b: tmp_vb, f: (float) factor);
2172
2173 graphene_simd4f_dup_4f (tmp_vres, m_res);
2174
2175 /* Initialize using the transposed (2,2) matrix */
2176 res->value.x = graphene_simd4f_init (m_res[M_11], m_res[M_21], 0.f, 0.f);
2177 res->value.y = graphene_simd4f_init (m_res[M_12], m_res[M_22], 0.f, 0.f);
2178 res->value.z = graphene_simd4f_init ( 0.f, 0.f, 1.f, 0.f);
2179
2180 /* Translate */
2181 float translate_x = graphene_vec2_get_x (v: &translate_res);
2182 float translate_y = graphene_vec2_get_y (v: &translate_res);
2183 res->value.w = graphene_simd4f_init (translate_x * m_res[M_11] + translate_y * m_res[M_21],
2184 translate_x * m_res[M_12] + translate_y * m_res[M_22],
2185 0.f,
2186 1.f);
2187
2188 /* Rotate using a (2,2) rotation matrix */
2189 float rot_sin, rot_cos;
2190 graphene_sincos (GRAPHENE_DEG_TO_RAD ((float) rotate_res), sin_out: &rot_sin, cos_out: &rot_cos);
2191
2192 graphene_simd4x4f_t tmp_m;
2193 tmp_m = graphene_simd4x4f_init (graphene_simd4f_init (rot_cos, -rot_sin, 0.f, 0.f),
2194 graphene_simd4f_init (rot_sin, rot_cos, 0.f, 0.f),
2195 graphene_simd4f_init ( 0.f, 0.f, 1.f, 0.f),
2196 graphene_simd4f_init ( 0.f, 0.f, 0.f, 1.f));
2197 graphene_simd4x4f_matrix_mul (a: &res->value, b: &tmp_m, res: &res->value);
2198
2199 /* Scale */
2200 float scale_x = graphene_vec2_get_x (v: &scale_res);
2201 float scale_y = graphene_vec2_get_y (v: &scale_res);
2202 graphene_simd4x4f_scale (m: &tmp_m, x: scale_x, y: scale_y, z: 1.f);
2203 graphene_simd4x4f_matrix_mul (a: &res->value, b: &tmp_m, res: &res->value);
2204 }
2205 else
2206 {
2207 graphene_vec3_t scale_a, translate_a;
2208 graphene_quaternion_t rotate_a;
2209 graphene_vec3_t shear_a;
2210 graphene_vec4_t perspective_a;
2211
2212 graphene_vec3_t scale_b, translate_b;
2213 graphene_quaternion_t rotate_b;
2214 graphene_vec3_t shear_b;
2215 graphene_vec4_t perspective_b;
2216
2217 graphene_vec3_t scale_r, translate_r;
2218 graphene_quaternion_t rotate_r;
2219 graphene_vec3_t shear_r;
2220
2221 graphene_simd4f_t tmp;
2222
2223 success |= matrix_decompose_3d (m: a, scale_r: &scale_a, shear_r: &shear_a, rotate_r: &rotate_a, translate_r: &translate_a, perspective_r: &perspective_a);
2224 success |= matrix_decompose_3d (m: b, scale_r: &scale_b, shear_r: &shear_b, rotate_r: &rotate_b, translate_r: &translate_b, perspective_r: &perspective_b);
2225
2226 /* If we cannot decompose either matrix we bail out with an identity */
2227 if (!success)
2228 return;
2229
2230 /* Interpolate the perspective row */
2231 tmp = graphene_simd4f_interpolate (a: perspective_a.value, b: perspective_b.value, f: (float) factor);
2232 res->value.x = graphene_simd4f_init (1.f, 0.f, 0.f, graphene_simd4f_get_x (tmp));
2233 res->value.y = graphene_simd4f_init (0.f, 1.f, 0.f, graphene_simd4f_get_y (tmp));
2234 res->value.z = graphene_simd4f_init (0.f, 0.f, 1.f, graphene_simd4f_get_z (tmp));
2235 res->value.w = graphene_simd4f_init (0.f, 0.f, 0.f, graphene_simd4f_get_w (tmp));
2236
2237 /* Translate */
2238 graphene_point3d_t t;
2239 graphene_vec3_interpolate (v1: &translate_a, v2: &translate_b, factor, res: &translate_r);
2240 graphene_point3d_init_from_vec3 (p: &t, v: &translate_r);
2241 graphene_matrix_translate (m: res, pos: &t);
2242
2243 /* Rotate */
2244 graphene_quaternion_slerp (a: &rotate_a, b: &rotate_b, factor: (float) factor, res: &rotate_r);
2245 graphene_matrix_rotate_quaternion (m: res, q: &rotate_r);
2246
2247 /* Skew */
2248 float shear;
2249 graphene_vec3_interpolate (v1: &shear_a, v2: &shear_b, factor, res: &shear_r);
2250 shear = graphene_simd4f_get (shear_r.value, YZ_SHEAR);
2251 if (!graphene_approx_val (a: shear, b: 0.f))
2252 graphene_matrix_skew_yz (m: res, factor: shear);
2253
2254 shear = graphene_simd4f_get (shear_r.value, XZ_SHEAR);
2255 if (!graphene_approx_val (a: shear, b: 0.f))
2256 graphene_matrix_skew_xz (m: res, factor: shear);
2257
2258 shear = graphene_simd4f_get (shear_r.value, XY_SHEAR);
2259 if (!graphene_approx_val (a: shear, b: 0.f))
2260 graphene_matrix_skew_xy (m: res, factor: shear);
2261
2262 /* Scale */
2263 graphene_point3d_t s;
2264 graphene_vec3_interpolate (v1: &scale_a, v2: &scale_b, factor, res: &scale_r);
2265 graphene_point3d_init_from_vec3 (p: &s, v: &scale_r);
2266 if (!graphene_approx_val (a: s.x, b: 1.f) ||
2267 !graphene_approx_val (a: s.y, b: 1.f) ||
2268 !graphene_approx_val (a: s.z, b: 1.f))
2269 graphene_matrix_scale (m: res, factor_x: s.x, factor_y: s.y, factor_z: s.z);
2270 }
2271}
2272
2273#undef M_11
2274#undef M_12
2275#undef M_21
2276#undef M_22
2277#undef XY_SHEAR
2278#undef XZ_SHEAR
2279#undef YZ_SHEAR
2280
2281/**
2282 * graphene_matrix_print:
2283 * @m: The matrix to print
2284 *
2285 * Prints the contents of a matrix to the standard error stream.
2286 *
2287 * This function is only useful for debugging; there are no guarantees
2288 * made on the format of the output.
2289 *
2290 * Since: 1.0
2291 */
2292void
2293graphene_matrix_print (const graphene_matrix_t *m)
2294{
2295 for (int i = 0; i < 4; i++)
2296 {
2297 fprintf (stderr,
2298 format: "| %+.6f %+.6f %+.6f %+.6f |\n",
2299 graphene_matrix_get_value (m, row: i, col: 0),
2300 graphene_matrix_get_value (m, row: i, col: 1),
2301 graphene_matrix_get_value (m, row: i, col: 2),
2302 graphene_matrix_get_value (m, row: i, col: 3));
2303 }
2304}
2305
2306/**
2307 * graphene_matrix_near:
2308 * @a: a #graphene_matrix_t
2309 * @b: a #graphene_matrix_t
2310 * @epsilon: the threshold between the two matrices
2311 *
2312 * Compares the two given #graphene_matrix_t matrices and checks
2313 * whether their values are within the given @epsilon of each
2314 * other.
2315 *
2316 * Returns: `true` if the two matrices are near each other, and
2317 * `false` otherwise
2318 *
2319 * Since: 1.10
2320 */
2321bool
2322graphene_matrix_near (const graphene_matrix_t *a,
2323 const graphene_matrix_t *b,
2324 float epsilon)
2325{
2326 if (a == b)
2327 return true;
2328
2329 if (a == NULL || b == NULL)
2330 return false;
2331
2332 for (unsigned i = 0; i < 4; i++)
2333 {
2334 graphene_vec4_t row_a, row_b;
2335
2336 graphene_matrix_get_row (m: a, index_: i, res: &row_a);
2337 graphene_matrix_get_row (m: b, index_: i, res: &row_b);
2338
2339 if (!graphene_vec4_near (v1: &row_a, v2: &row_b, epsilon))
2340 return false;
2341 }
2342
2343 return true;
2344}
2345
2346/**
2347 * graphene_matrix_equal:
2348 * @a: a #graphene_matrix_t
2349 * @b: a #graphene_matrix_t
2350 *
2351 * Checks whether the two given #graphene_matrix_t matrices are equal.
2352 *
2353 * Returns: `true` if the two matrices are equal, and `false` otherwise
2354 *
2355 * Since: 1.10
2356 */
2357bool
2358graphene_matrix_equal (const graphene_matrix_t *a,
2359 const graphene_matrix_t *b)
2360{
2361 return graphene_matrix_near (a, b, FLT_EPSILON);
2362}
2363
2364/**
2365 * graphene_matrix_equal_fast:
2366 * @a: a #graphene_matrix_t
2367 * @b: a #graphene_matrix_t
2368 *
2369 * Checks whether the two given #graphene_matrix_t matrices are
2370 * byte-by-byte equal.
2371 *
2372 * While this function is faster than graphene_matrix_equal(), it
2373 * can also return false negatives, so it should be used in
2374 * conjuction with either graphene_matrix_equal() or
2375 * graphene_matrix_near(). For instance:
2376 *
2377 * |[<!-- language="C" -->
2378 * if (graphene_matrix_equal_fast (a, b))
2379 * {
2380 * // matrices are definitely the same
2381 * }
2382 * else
2383 * {
2384 * if (graphene_matrix_equal (a, b))
2385 * // matrices contain the same values within an epsilon of FLT_EPSILON
2386 * else if (graphene_matrix_near (a, b, 0.0001))
2387 * // matrices contain the same values within an epsilon of 0.0001
2388 * else
2389 * // matrices are not equal
2390 * }
2391 * ]|
2392 *
2393 * Returns: `true` if the matrices are equal. and `false` otherwise
2394 *
2395 * Since: 1.10
2396 */
2397bool
2398graphene_matrix_equal_fast (const graphene_matrix_t *a,
2399 const graphene_matrix_t *b)
2400{
2401 return memcmp (s1: a, s2: b, n: sizeof (graphene_matrix_t)) == 0;
2402}
2403

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