LibGame  v0.4.0
The LG Game Engine - Copyright (C) 2024-2025 ETMSoftware
math_3d.h
Go to the documentation of this file.
1 
35 /*
36 Math 3D v1.0
37 By Stephan Soller <[email protected]> and Tobias Malmsheimer
38 Licensed under the MIT license
39 
40 Math 3D is a compact C99 library meant to be used with OpenGL. It provides basic
41 3D vector and 4x4 matrix operations as well as functions to create transformation
42 and projection matrices. The OpenGL binary layout is used so you can just upload
43 vectors and matrices into shaders and work with them without any conversions.
44 
45 It's an stb style single header file library. Define MATH_3D_IMPLEMENTATION
46 before you include this file in *one* C file to create the implementation.
47 
48 
49 QUICK NOTES
50 
51 - If not explicitly stated by a parameter name all angles are in radians.
52 - The matrices use column-major indices. This is the same as in OpenGL and GLSL.
53  See the matrix documentation below for details.
54 - Matrices are passed by value. This is probably a bit inefficient but
55  simplifies code quite a bit. Most operations will be inlined by the compiler
56  anyway so the difference shouldn't matter that much. A matrix fits into 4 of
57  the 16 SSE2 registers anyway. If profiling shows significant slowdowns the
58  matrix type might change but ease of use is more important than every last
59  percent of performance.
60 - When combining matrices with multiplication the effects apply right to left.
61  This is the convention used in mathematics and OpenGL. Source:
62  https://en.wikipedia.org/wiki/Transformation_matrix#Composing_and_inverting_transformations
63  Direct3D does it differently.
64 - The `m4_mul_pos()` and `m4_mul_dir()` functions do a correct perspective
65  divide (division by w) when necessary. This is a bit slower but ensures that
66  the functions will properly work with projection matrices. If profiling shows
67  this is a bottleneck special functions without perspective division can be
68  added. But the normal multiplications should avoid any surprises.
69 - The library consistently uses a right-handed coordinate system. The old
70  `glOrtho()` broke that rule and `m4_ortho()` has be slightly modified so you
71  can always think of right-handed cubes that are projected into OpenGLs
72  normalized device coordinates.
73 - Special care has been taken to document all complex operations and important
74  sources. Most code is covered by test cases that have been manually calculated
75  and checked on the whiteboard. Since indices and math code is prone to be
76  confusing we used pair programming to avoid mistakes.
77 
78 
79 FURTHER IDEAS
80 
81 These are ideas for future work on the library. They're implemented as soon as
82 there is a proper use case and we can find good names for them.
83 
84 - bool v3_is_null(vec3_t v, float epsilon)
85  To check if the length of a vector is smaller than `epsilon`.
86 - vec3_t v3_length_default(vec3_t v, float default_length, float epsilon)
87  Returns `default_length` if the length of `v` is smaller than `epsilon`.
88  Otherwise same as `v3_length()`.
89 - vec3_t v3_norm_default(vec3_t v, vec3_t default_vector, float epsilon)
90  Returns `default_vector` if the length of `v` is smaller than `epsilon`.
91  Otherwise the same as `v3_norm()`.
92 - mat4_t m4_invert(mat4_t matrix)
93  Matrix inversion that works with arbitrary matrices. `m4_invert_affine()` can
94  already invert translation, rotation, scaling, mirroring, reflection and
95  shearing matrices. So a general inversion might only be useful to invert
96  projection matrices for picking. But with orthographic and perspective
97  projection it's probably simpler to calculate the ray into the scene directly
98  based on the screen coordinates.
99 
100 
101 VERSION HISTORY
102 
103 v1.0 2016-02-15 Initial release
104 
105 */
106 
107 #ifndef MATH_3D_HEADER
108 #define MATH_3D_HEADER
109 
110 // Define PI directly because we would need to define the _BSD_SOURCE or
111 // _XOPEN_SOURCE feature test macros to get it from math.h. That would be a
112 // rather harsh dependency. So we define it directly if necessary.
113 #ifndef M_PI
114 #define M_PI 3.14159265358979323846
115 #endif
116 
117 
118 //
119 // 3D vectors
120 //
121 // Use the `vec3()` function to create vectors. All other vector functions start
122 // with the `v3_` prefix.
123 //
124 // The binary layout is the same as in GLSL and everything else (just 3 floats).
125 // So you can just upload the vectors into shaders as they are.
126 //
127 
128 typedef struct {float x, y, z; } vec3_t;
129 static inline vec3_t vec3(float x, float y, float z) {return (vec3_t){x, y, z };}
130 
131 static inline vec3_t v3_add (vec3_t a, vec3_t b) {return (vec3_t){a.x + b.x, a.y + b.y, a.z + b.z };}
132 static inline vec3_t v3_adds (vec3_t a, float s) {return (vec3_t){a.x + s, a.y + s, a.z + s };}
133 static inline vec3_t v3_sub (vec3_t a, vec3_t b) {return (vec3_t){a.x - b.x, a.y - b.y, a.z - b.z };}
134 static inline vec3_t v3_subs (vec3_t a, float s) {return (vec3_t){a.x - s, a.y - s, a.z - s };}
135 static inline vec3_t v3_mul (vec3_t a, vec3_t b) {return (vec3_t){a.x * b.x, a.y * b.y, a.z * b.z };}
136 static inline vec3_t v3_muls (vec3_t a, float s) {return (vec3_t){a.x * s, a.y * s, a.z * s };}
137 static inline vec3_t v3_div (vec3_t a, vec3_t b) {return (vec3_t){a.x / b.x, a.y / b.y, a.z / b.z };}
138 static inline vec3_t v3_divs (vec3_t a, float s) {return (vec3_t){a.x / s, a.y / s, a.z / s };}
139 static inline float v3_length(vec3_t v) {return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);}
140 static inline vec3_t v3_norm (vec3_t v);
141 static inline float v3_dot (vec3_t a, vec3_t b) {return a.x * b.x + a.y * b.y + a.z * b.z;}
142 static inline vec3_t v3_proj (vec3_t v, vec3_t onto);
143 static inline vec3_t v3_cross (vec3_t a, vec3_t b);
144 static inline float v3_angle_between(vec3_t a, vec3_t b);
145 
146 
147 //
148 // 4x4 matrices
149 //
150 // Use the `mat4()` function to create a matrix. You can write the matrix
151 // members in the same way as you would write them on paper or on a whiteboard:
152 //
153 // mat4_t m = mat4(
154 // 1, 0, 0, 7,
155 // 0, 1, 0, 5,
156 // 0, 0, 1, 3,
157 // 0, 0, 0, 1
158 // )
159 //
160 // This creates a matrix that translates points by vec3(7, 5, 3). All other
161 // matrix functions start with the `m4_` prefix. Among them functions to create
162 // identity, translation, rotation, scaling and projection matrices.
163 //
164 // The matrix is stored in column-major order, just as OpenGL expects. Members
165 // can be accessed by indices or member names. When you write a matrix on paper
166 // or on the whiteboard the indices and named members correspond to these
167 // positions:
168 //
169 // | m[0][0] m[1][0] m[2][0] m[3][0] |
170 // | m[0][1] m[1][1] m[2][1] m[3][1] |
171 // | m[0][2] m[1][2] m[2][2] m[3][2] |
172 // | m[0][3] m[1][3] m[2][3] m[3][3] |
173 //
174 // | m00 m10 m20 m30 |
175 // | m01 m11 m21 m31 |
176 // | m02 m12 m22 m32 |
177 // | m03 m13 m23 m33 |
178 //
179 // The first index or number in a name denotes the column, the second the row.
180 // So m[i][j] denotes the member in the ith column and the jth row. This is the
181 // same as in GLSL (source: GLSL v1.3 specification, 5.6 Matrix Components).
182 //
183 
184 typedef union {
185  // The first index is the column index, the second the row index. The memory
186  // layout of nested arrays in C matches the memory layout expected by OpenGL.
187  float m[4][4];
188  // OpenGL expects the first 4 floats to be the first column of the matrix.
189  // So we need to define the named members column by column for the names to
190  // match the memory locations of the array elements.
191  struct {
192  float m00, m01, m02, m03;
193  float m10, m11, m12, m13;
194  float m20, m21, m22, m23;
195  float m30, m31, m32, m33;
196  };
197 } mat4_t;
198 
199 static inline mat4_t mat4(
200  float m00, float m10, float m20, float m30,
201  float m01, float m11, float m21, float m31,
202  float m02, float m12, float m22, float m32,
203  float m03, float m13, float m23, float m33
204 );
205 
206 static inline mat4_t m4_identity ();
207 static inline mat4_t m4_translation (vec3_t offset);
208 static inline mat4_t m4_scaling (vec3_t scale);
209 static inline mat4_t m4_rotation_x (float angle_in_rad);
210 static inline mat4_t m4_rotation_y (float angle_in_rad);
211 static inline mat4_t m4_rotation_z (float angle_in_rad);
212  mat4_t m4_rotation (float angle_in_rad, vec3_t axis);
213 
214 /* Renamed stuff by Emmanuel Thomas-Maurin */
215  mat4_t m4_ortho_RH (float left, float right, float bottom, float top, float back, float front);
216  mat4_t m4_perspective_RH (float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance);
217  mat4_t m4_look_at_RH (vec3_t from, vec3_t to, vec3_t up);
218 /* Not implemented yet:
219  mat4_t m4_ortho_LH (float left, float right, float bottom, float top, float back, float front);
220  mat4_t m4_perspective_LH (float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance);
221  mat4_t m4_look_at_LH (vec3_t from, vec3_t to, vec3_t up);
222 */
223 /* (until here) */
224  mat4_t m4_frustum (float left, float right, float bottom, float top, float near, float far);
225 
226 static inline mat4_t m4_transpose (mat4_t matrix);
227 static inline mat4_t m4_mul (mat4_t a, mat4_t b);
228  mat4_t m4_invert_affine(mat4_t matrix);
229  vec3_t m4_mul_pos (mat4_t matrix, vec3_t position);
230  vec3_t m4_mul_dir (mat4_t matrix, vec3_t direction);
231 
232  void m4_print (mat4_t matrix);
233  void m4_printp (mat4_t matrix, int width, int precision);
234  void m4_fprint (FILE* stream, mat4_t matrix);
235  void m4_fprintp (FILE* stream, mat4_t matrix, int width, int precision);
236 
237  /* New stuff here */
238  void m4_print2 (mat4_t matrix, const char *line_start);
239  void m4_printp2 (mat4_t matrix, int width, int precision, const char *line_start);
240  void m4_fprint2 (FILE* stream, mat4_t matrix, const char *line_start);
241  void m4_fprintp2 (FILE* stream, mat4_t matrix, int width, int precision, const char *line_start);
242 
243 
244 //
245 // 3D vector functions header implementation
246 //
247 
248 static inline vec3_t v3_norm(vec3_t v)
249 {
250  float len = v3_length(v);
251  if (len > 0) {
252  return (vec3_t){v.x / len, v.y / len, v.z / len};
253  } else {
254  return (vec3_t){0, 0, 0};
255  }
256 }
257 
258 static inline vec3_t v3_proj(vec3_t v, vec3_t onto)
259 {
260  return v3_muls(onto, v3_dot(v, onto) / v3_dot(onto, onto));
261 }
262 
263 static inline vec3_t v3_cross(vec3_t a, vec3_t b)
264 {
265  return (vec3_t){
266  a.y * b.z - a.z * b.y,
267  a.z * b.x - a.x * b.z,
268  a.x * b.y - a.y * b.x
269  };
270 }
271 
272 static inline float v3_angle_between(vec3_t a, vec3_t b)
273 {
274  return acosf(v3_dot(a, b) / (v3_length(a) * v3_length(b)));
275 }
276 
277 //
278 // Matrix functions header implementation
279 //
280 
281 static inline mat4_t mat4(
282  float m00, float m10, float m20, float m30,
283  float m01, float m11, float m21, float m31,
284  float m02, float m12, float m22, float m32,
285  float m03, float m13, float m23, float m33
286 )
287 {
288  return (mat4_t){
289  .m[0][0] = m00, .m[1][0] = m10, .m[2][0] = m20, .m[3][0] = m30,
290  .m[0][1] = m01, .m[1][1] = m11, .m[2][1] = m21, .m[3][1] = m31,
291  .m[0][2] = m02, .m[1][2] = m12, .m[2][2] = m22, .m[3][2] = m32,
292  .m[0][3] = m03, .m[1][3] = m13, .m[2][3] = m23, .m[3][3] = m33
293  };
294 }
295 
296 static inline mat4_t m4_identity()
297 {
298  return mat4(
299  1, 0, 0, 0,
300  0, 1, 0, 0,
301  0, 0, 1, 0,
302  0, 0, 0, 1
303  );
304 }
305 
306 static inline mat4_t m4_translation(vec3_t offset)
307 {
308  return mat4(
309  1, 0, 0, offset.x,
310  0, 1, 0, offset.y,
311  0, 0, 1, offset.z,
312  0, 0, 0, 1
313  );
314 }
315 
316 static inline mat4_t m4_scaling(vec3_t scale)
317 {
318  float x = scale.x, y = scale.y, z = scale.z;
319  return mat4(
320  x, 0, 0, 0,
321  0, y, 0, 0,
322  0, 0, z, 0,
323  0, 0, 0, 1
324  );
325 }
326 
327 static inline mat4_t m4_rotation_x(float angle_in_rad)
328 {
329  float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
330  return mat4(
331  1, 0, 0, 0,
332  0, c, -s, 0,
333  0, s, c, 0,
334  0, 0, 0, 1
335  );
336 }
337 
338 static inline mat4_t m4_rotation_y(float angle_in_rad)
339 {
340  float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
341  return mat4(
342  c, 0, s, 0,
343  0, 1, 0, 0,
344  -s, 0, c, 0,
345  0, 0, 0, 1
346  );
347 }
348 
349 static inline mat4_t m4_rotation_z(float angle_in_rad)
350 {
351  float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
352  return mat4(
353  c, -s, 0, 0,
354  s, c, 0, 0,
355  0, 0, 1, 0,
356  0, 0, 0, 1
357  );
358 }
359 
360 static inline mat4_t m4_transpose(mat4_t matrix)
361 {
362  return mat4(
363  matrix.m00, matrix.m01, matrix.m02, matrix.m03,
364  matrix.m10, matrix.m11, matrix.m12, matrix.m13,
365  matrix.m20, matrix.m21, matrix.m22, matrix.m23,
366  matrix.m30, matrix.m31, matrix.m32, matrix.m33
367  );
368 }
369 
370 /*
371  * Multiplication of two 4x4 matrices.
372  *
373  * Implemented by following the row times column rule and illustrating it on a
374  * whiteboard with the proper indices in mind.
375  *
376  * Further reading: https://en.wikipedia.org/wiki/Matrix_multiplication
377  * But note that the article use the first index for rows and the second for
378  * columns.
379  */
380 static inline mat4_t m4_mul(mat4_t a, mat4_t b)
381 {
382  mat4_t result;
383  int i, j, k;
384 
385  for (i = 0; i < 4; i++) {
386  for (j = 0; j < 4; j++) {
387  float sum = 0;
388  for (k = 0; k < 4; k++) {
389  sum += a.m[k][j] * b.m[i][k];
390  }
391  result.m[i][j] = sum;
392  }
393  }
394 
395  return result;
396 }
397 
398 #endif // MATH_3D_HEADER
399 
400 
401 #ifdef MATH_3D_IMPLEMENTATION
402 
403 /*
404  * Creates a matrix to rotate around an axis by a given angle. The axis doesn't
405  * need to be normalized.
406  *
407  * Sources:
408  *
409  * https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
410  */
411 mat4_t m4_rotation(float angle_in_rad, vec3_t axis)
412 {
413  vec3_t normalized_axis = v3_norm(axis);
414  float x = normalized_axis.x, y = normalized_axis.y, z = normalized_axis.z;
415  float c = cosf(angle_in_rad), s = sinf(angle_in_rad);
416 
417  return mat4(
418  c + x * x * (1 - c), x * y * (1 - c) - z * s, x * z * (1 - c) + y * s, 0,
419  y * x * (1 - c) + z * s, c + y * y * (1 - c), y * z * (1 - c) - x * s, 0,
420  z * x * (1 - c) - y * s, z * y * (1 - c) + x * s, c + z * z * (1 - c), 0,
421  0, 0, 0, 1
422  );
423 }
424 
425 /*
426  * Creates an orthographic projection matrix. It maps the right handed cube
427  * defined by left, right, bottom, top, back and front onto the screen and
428  * z-buffer. You can think of it as a cube you move through world or camera
429  * space and everything inside is visible.
430  *
431  * This is slightly different from the traditional glOrtho() and from the linked
432  * sources. These functions require the user to negate the last two arguments
433  * (creating a left-handed coordinate system). We avoid that here so you can
434  * think of this function as moving a right-handed cube through world space.
435  *
436  * The arguments are ordered in a way that for each axis you specify the minimum
437  * followed by the maximum. Thats why it's bottom to top and back to front.
438  *
439  * Implementation details:
440  *
441  * To be more exact the right-handed cube is mapped into normalized device
442  * coordinates, a left-handed cube where (-1 -1) is the lower left corner,
443  * (1, 1) the upper right corner and a z-value of -1 is the nearest point and
444  * 1 the furthest point. OpenGL takes it from there and puts it on the screen
445  * and into the z-buffer.
446  *
447  * Sources:
448  *
449  * https://msdn.microsoft.com/en-us/library/windows/desktop/dd373965(v=vs.85).aspx
450  * https://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/
451  */
452 mat4_t m4_ortho_RH(float left, float right, float bottom, float top, float back, float front)
453 {
454  float l = left, r = right, b = bottom, t = top, n = front, f = back;
455  float tx = -(r + l) / (r - l);
456  float ty = -(t + b) / (t - b);
457  float tz = -(f + n) / (f - n);
458 
459  return mat4(
460  2 / (r - l), 0, 0, tx,
461  0, 2 / (t - b), 0, ty,
462  0, 0, 2 / (f - n), tz,
463  0, 0, 0, 1
464  );
465 }
466 
467 /*
468  * Creates a perspective projection matrix for a camera.
469  *
470  * The camera is at the origin and looks in the direction of the negative Z axis.
471  * `near_view_distance` and `far_view_distance` have to be positive and > 0.
472  * They are distances from the camera eye, not values on an axis.
473  *
474  * `near_view_distance` can be small but not 0. 0 breaks the projection and
475  * everything ends up at the max value (far end) of the z-buffer. Making the
476  * z-buffer useless.
477  *
478  * The matrix is the same as `gluPerspective()` builds. The view distance is
479  * mapped to the z-buffer with a reciprocal function (1/x). Therefore the z-buffer
480  * resolution for near objects is very good while resolution for far objects is
481  * limited.
482  *
483  * Sources:
484  *
485  * https://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/
486  */
487 mat4_t m4_perspective_RH(float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance)
488 {
489  float fovy_in_rad = vertical_field_of_view_in_deg / 180 * M_PI;
490  float f = 1.0f / tanf(fovy_in_rad / 2.0f);
491  float ar = aspect_ratio;
492  float nd = near_view_distance, fd = far_view_distance;
493 
494  return mat4(
495  f / ar, 0, 0, 0,
496  0, f, 0, 0,
497  0, 0, (fd + nd) / (nd - fd), (2 * fd * nd) / (nd - fd),
498  0, 0, -1, 0
499  );
500 }
501 
502 /*
503  * Builds a transformation matrix for a camera that looks from `from` towards
504  * `to`. `up` defines the direction that's upwards for the camera. All three
505  * vectors are given in world space and `up` doesn't need to be normalized.
506  *
507  * Sources: Derived on whiteboard.
508  *
509  * Implementation details:
510  *
511  * x, y and z are the right-handed base vectors of the cameras subspace.
512  * x has to be normalized because the cross product only produces a normalized
513  * output vector if both input vectors are orthogonal to each other. And up
514  * probably isn't orthogonal to z.
515  *
516  * These vectors are then used to build a 3x3 rotation matrix. This matrix
517  * rotates a vector by the same amount the camera is rotated. But instead we
518  * need to rotate all incoming vertices backwards by that amount. That's what a
519  * camera matrix is for: To move the world so that the camera is in the origin.
520  * So we take the inverse of that rotation matrix and in case of a rotation
521  * matrix this is just the transposed matrix. That's why the 3x3 part of the
522  * matrix are the x, y and z vectors but written horizontally instead of
523  * vertically.
524  *
525  * The translation is derived by creating a translation matrix to move the world
526  * into the origin (thats translate by minus `from`). The complete lookat matrix
527  * is then this translation followed by the rotation. Written as matrix
528  * multiplication:
529  *
530  * lookat = rotation * translation
531  *
532  * Since we're right-handed this equals to first doing the translation and after
533  * that doing the rotation. During that multiplication the rotation 3x3 part
534  * doesn't change but the translation vector is multiplied with each rotation
535  * axis. The dot product is just a more compact way to write the actual
536  * multiplications.
537  */
538 mat4_t m4_look_at_RH(vec3_t from, vec3_t to, vec3_t up)
539 {
540  vec3_t z = v3_muls(v3_norm(v3_sub(to, from)), -1);
541  vec3_t x = v3_norm(v3_cross(up, z));
542  vec3_t y = v3_cross(z, x);
543 
544  return mat4(
545  x.x, x.y, x.z, -v3_dot(from, x),
546  y.x, y.y, y.z, -v3_dot(from, y),
547  z.x, z.y, z.z, -v3_dot(from, z),
548  0, 0, 0, 1
549  );
550 }
551 
552 /*
553  * Some extra stuff by Emmanuel Thomas-Maurin
554  *
555  * Frustum matrix
556  * Similar to Android OpenGL ES 2.0 frustum matrix:
557  * https://developer.android.com/reference/android/opengl/Matrix#frustumM(float[],%20int,%20float,%20float,%20float,%20float,%20float,%20float)
558  *
559  * NOT USED SO FAR
560  */
561 mat4_t m4_frustum(float left, float right, float bottom, float top, float near, float far)
562 {
563  /* Too much checking here ? */
564  if (fabs(left - right) < LG_FLOAT_EPSILON) {
565  INFO_ERR("left == right\n")
566  } else if (fabs(bottom - top) < LG_FLOAT_EPSILON) {
567  INFO_ERR("bottom == top\n")
568  } else if (far <= near) {
569  INFO_ERR("far <= near\n")
570  } else if (near <= 0.0f) {
571  INFO_ERR("near <= 0.0f\n")
572  } else if (far <= 0.0f) {
573  INFO_ERR("far <= 0.0f\n")
574  }
575 
576  float width = right - left;
577  float height = top - bottom;
578  float depth = far - near;
579  float x = (2.0 * near) / width;
580  float y = (2.0 * near) / height;
581  float A = (right + left) / width;
582  float B = (top + bottom) / height;
583  float C = - (far + near) / depth;
584  float D = - (2.0 * far * near) / depth;
585 
586  return mat4(
587  x, 0.0, A, 0.0,
588  0.0, y, B, 0.0,
589  0.0, 0.0, C, D,
590  0.0, 0.0, -1.0, 0.0
591  );
592 }
593 /* (until here) */
594 
595 /*
596  * Inverts an affine transformation matrix. That are translation, scaling,
597  * mirroring, reflection, rotation and shearing matrices or any combination of
598  * them.
599  *
600  * Implementation details:
601  *
602  * - Invert the 3x3 part of the 4x4 matrix to handle rotation, scaling, etc.
603  * correctly (see source).
604  * - Invert the translation part of the 4x4 matrix by multiplying it with the
605  * inverted rotation matrix and negating it.
606  *
607  * When a 3D point is multiplied with a transformation matrix it is first
608  * rotated and then translated. The inverted transformation matrix is the
609  * inverse translation followed by the inverse rotation. Written as a matrix
610  * multiplication (remember, the effect applies right to left):
611  *
612  * inv(matrix) = inv(rotation) * inv(translation)
613  *
614  * The inverse translation is a translation into the opposite direction, just
615  * the negative translation. The rotation part isn't changed by that
616  * multiplication but the translation part is multiplied by the inverse rotation
617  * matrix. It's the same situation as with `m4_look_at()`. But since we don't
618  * store the rotation matrix as 3D vectors we can't use the dot product and have
619  * to write the matrix multiplication operations by hand.
620  *
621  * Sources for 3x3 matrix inversion:
622  *
623  * https://www.khanacademy.org/math/precalculus/precalc-matrices/determinants-and-inverses-of-large-matrices/v/inverting-3x3-part-2-determinant-and-adjugate-of-a-matrix
624  */
625 mat4_t m4_invert_affine(mat4_t matrix)
626 {
627  // Create shorthands to access matrix members
628  float m00 = matrix.m00, m10 = matrix.m10, m20 = matrix.m20, m30 = matrix.m30;
629  float m01 = matrix.m01, m11 = matrix.m11, m21 = matrix.m21, m31 = matrix.m31;
630  float m02 = matrix.m02, m12 = matrix.m12, m22 = matrix.m22, m32 = matrix.m32;
631 
632  // Invert 3x3 part of the 4x4 matrix that contains the rotation, etc.
633  // That part is called R from here on.
634 
635  // Calculate cofactor matrix of R
636  float c00 = m11 * m22 - m12 * m21, c10 = -(m01 * m22 - m02 * m21), c20 = m01 * m12 - m02 * m11;
637  float c01 = -(m10 * m22 - m12 * m20), c11 = m00 * m22 - m02 * m20, c21 = -(m00 * m12 - m02 * m10);
638  float c02 = m10 * m21 - m11 * m20, c12 = -(m00 * m21 - m01 * m20), c22 = m00 * m11 - m01 * m10;
639 
640  // Caclculate the determinant by using the already calculated determinants
641  // in the cofactor matrix.
642  // Second sign is already minus from the cofactor matrix.
643  float det = m00 * c00 + m10 * c10 + m20 * c20;
644  if (fabsf(det) < 0.00001) {
645  return m4_identity();
646  }
647 
648  // Calcuate inverse of R by dividing the transposed cofactor matrix by the
649  // determinant.
650  float i00 = c00 / det, i10 = c01 / det, i20 = c02 / det;
651  float i01 = c10 / det, i11 = c11 / det, i21 = c12 / det;
652  float i02 = c20 / det, i12 = c21 / det, i22 = c22 / det;
653 
654  // Combine the inverted R with the inverted translation
655  return mat4(
656  i00, i10, i20, -(i00 * m30 + i10 * m31 + i20 * m32),
657  i01, i11, i21, -(i01 * m30 + i11 * m31 + i21 * m32),
658  i02, i12, i22, -(i02 * m30 + i12 * m31 + i22 * m32),
659  0, 0, 0, 1
660  );
661 }
662 
663 /*
664  * Multiplies a 4x4 matrix with a 3D vector representing a point in 3D space.
665  *
666  * Before the matrix multiplication the vector is first expanded to a 4D vector
667  * (x, y, z, 1). After the multiplication the vector is reduced to 3D again by
668  * dividing through the 4th component (if it's not 0 or 1).
669  */
670 vec3_t m4_mul_pos(mat4_t matrix, vec3_t position)
671 {
672  vec3_t result = vec3(
673  matrix.m00 * position.x + matrix.m10 * position.y + matrix.m20 * position.z + matrix.m30,
674  matrix.m01 * position.x + matrix.m11 * position.y + matrix.m21 * position.z + matrix.m31,
675  matrix.m02 * position.x + matrix.m12 * position.y + matrix.m22 * position.z + matrix.m32
676  );
677 
678  float w = matrix.m03 * position.x + matrix.m13 * position.y + matrix.m23 * position.z + matrix.m33;
679  if (w != 0 && w != 1) {
680  return vec3(result.x / w, result.y / w, result.z / w);
681  }
682 
683  return result;
684 }
685 
686 /*
687  * Multiplies a 4x4 matrix with a 3D vector representing a direction in 3D space.
688  *
689  * Before the matrix multiplication the vector is first expanded to a 4D vector
690  * (x, y, z, 0). For directions the 4th component is set to 0 because directions
691  * are only rotated, not translated. After the multiplication the vector is
692  * reduced to 3D again by dividing through the 4th component (if it's not 0 or
693  * 1). This is necessary because the matrix might contains something other than
694  * (0, 0, 0, 1) in the bottom row which might set w to something other than 0
695  * or 1.
696  */
697 vec3_t m4_mul_dir(mat4_t matrix, vec3_t direction)
698 {
699  vec3_t result = vec3(
700  matrix.m00 * direction.x + matrix.m10 * direction.y + matrix.m20 * direction.z,
701  matrix.m01 * direction.x + matrix.m11 * direction.y + matrix.m21 * direction.z,
702  matrix.m02 * direction.x + matrix.m12 * direction.y + matrix.m22 * direction.z
703  );
704 
705  float w = matrix.m03 * direction.x + matrix.m13 * direction.y + matrix.m23 * direction.z;
706  if (w != 0 && w != 1) {
707  return vec3(result.x / w, result.y / w, result.z / w);
708  }
709 
710  return result;
711 }
712 
713 void m4_print(mat4_t matrix)
714 {
715  m4_fprintp(STD_OUT, matrix, 6, 2);
716 }
717 
718 void m4_printp(mat4_t matrix, int width, int precision)
719 {
720  m4_fprintp(STD_OUT, matrix, width, precision);
721 }
722 
723 void m4_fprint(FILE* stream, mat4_t matrix)
724 {
725  m4_fprintp(stream, matrix, 6, 2);
726 }
727 
728 void m4_fprintp(FILE* stream, mat4_t matrix, int width, int precision)
729 {
730  mat4_t m = matrix;
731  int w = width, p = precision, r;
732 
733  for (r = 0; r < 4; r++) {
734  fprintf(stream, "| %*.*f %*.*f %*.*f %*.*f |\n",
735  w, p, m.m[0][r], w, p, m.m[1][r], w, p, m.m[2][r], w, p, m.m[3][r]
736  );
737  }
738 }
739 
740 /* New stuff here */
741 void m4_print2(mat4_t matrix, const char *line_start)
742 {
743  m4_fprintp2(STD_OUT, matrix, 6, 2, line_start);
744 }
745 
746 void m4_printp2(mat4_t matrix, int width, int precision, const char *line_start)
747 {
748  m4_fprintp2(STD_OUT, matrix, width, precision, line_start);
749 }
750 
751 void m4_fprint2(FILE* stream, mat4_t matrix, const char *line_start)
752 {
753  m4_fprintp2(stream, matrix, 6, 2, line_start);
754 }
755 void m4_fprintp2(FILE* stream, mat4_t matrix, int width, int precision, const char *line_start)
756 {
757  mat4_t m = matrix;
758  int w = width, p = precision, r;
759 
760  for (r = 0; r < 4; r++) {
761  fprintf(stream, "%s| %*.*f %*.*f %*.*f %*.*f |\n",
762  line_start, w, p, m.m[0][r], w, p, m.m[1][r], w, p, m.m[2][r], w, p, m.m[3][r]
763  );
764  }
765 }
766 
767 #endif // MATH_3D_IMPLEMENTATION
vec3_t
Definition: math_3d.h:128
mat4_t
Definition: math_3d.h:184