LibGame  v0.4.0
The LG Game Engine - Copyright (C) 2024-2025 ETMSoftware
math_3d.h
Go to the documentation of this file.
1 
101 #ifndef MATH_3D_HEADER
102 #define MATH_3D_HEADER
103 
105 // Define PI directly because we would need to define the _BSD_SOURCE or
106 // _XOPEN_SOURCE feature test macros to get it from math.h. That would be a
107 // rather harsh dependency. So we define it directly if necessary.
108 #ifndef M_PI
109 #define M_PI 3.14159265358979323846
110 #endif
111 
114 /*
115  * 3D vectors
116  * Use the `vec3()` function to create vectors. All other vector functions start
117  * with the `v3_` prefix.
118  *
119  * The binary layout is the same as in GLSL and everything else (just 3 floats).
120  * So you can just upload the vectors into shaders as they are.
121 */
122 
123 typedef struct {float x, y, z; } vec3_t;
124 static inline vec3_t vec3(float x, float y, float z) {return (vec3_t){x, y, z };}
125 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 };}
126 static inline vec3_t v3_adds (vec3_t a, float s) {return (vec3_t){a.x + s, a.y + s, a.z + s };}
127 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 };}
128 static inline vec3_t v3_subs (vec3_t a, float s) {return (vec3_t){a.x - s, a.y - s, a.z - s };}
129 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 };}
130 static inline vec3_t v3_muls (vec3_t a, float s) {return (vec3_t){a.x * s, a.y * s, a.z * s };}
131 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 };}
132 static inline vec3_t v3_divs (vec3_t a, float s) {return (vec3_t){a.x / s, a.y / s, a.z / s };}
133 static inline float v3_length(vec3_t v) {return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);}
134 static inline vec3_t v3_norm (vec3_t v);
135 static inline float v3_dot (vec3_t a, vec3_t b) {return a.x * b.x + a.y * b.y + a.z * b.z;}
136 static inline vec3_t v3_proj (vec3_t v, vec3_t onto);
137 static inline vec3_t v3_cross (vec3_t a, vec3_t b);
138 static inline float v3_angle_between(vec3_t a, vec3_t b);
139 
140 
141 /*
142  * 4x4 matrices
143  *
144  * Use the `mat4()` function to create a matrix. You can write the matrix
145  * members in the same way as you would write them on paper or on a whiteboard:
146  *
147  * mat4_t m = mat4(
148  * 1, 0, 0, 7,
149  * 0, 1, 0, 5,
150  * 0, 0, 1, 3,
151  * 0, 0, 0, 1
152  * )
153  *
154  * This creates a matrix that translates points by vec3(7, 5, 3). All other
155  * matrix functions start with the `m4_` prefix. Among them functions to create
156  * identity, translation, rotation, scaling and projection matrices.
157  *
158  * The matrix is stored in column-major order, just as OpenGL expects. Members
159  * can be accessed by indices or member names. When you write a matrix on paper
160  * or on the whiteboard the indices and named members correspond to these
161  * positions:
162  *
163  * | m[0][0] m[1][0] m[2][0] m[3][0] |
164  * | m[0][1] m[1][1] m[2][1] m[3][1] |
165  * | m[0][2] m[1][2] m[2][2] m[3][2] |
166  * | m[0][3] m[1][3] m[2][3] m[3][3] |
167  *
168  * | m00 m10 m20 m30 |
169  * | m01 m11 m21 m31 |
170  * | m02 m12 m22 m32 |
171  * | m03 m13 m23 m33 |
172  *
173  * The first index or number in a name denotes the column, the second the row.
174  * So m[i][j] denotes the member in the ith column and the jth row. This is the
175  * same as in GLSL (source: GLSL v1.3 specification, 5.6 Matrix Components).
176  */
177 
178 
179 typedef union {
180  // The first index is the column index, the second the row index. The memory
181  // layout of nested arrays in C matches the memory layout expected by OpenGL.
182  float m[4][4];
183  // OpenGL expects the first 4 floats to be the first column of the matrix.
184  // So we need to define the named members column by column for the names to
185  // match the memory locations of the array elements.
186  struct {
187  float m00, m01, m02, m03;
188  float m10, m11, m12, m13;
189  float m20, m21, m22, m23;
190  float m30, m31, m32, m33;
191  };
192 } mat4_t;
193 
194 static inline mat4_t mat4(
195  float m00, float m10, float m20, float m30,
196  float m01, float m11, float m21, float m31,
197  float m02, float m12, float m22, float m32,
198  float m03, float m13, float m23, float m33
199 );
200 
201 static inline mat4_t m4_identity ();
202 static inline mat4_t m4_translation (vec3_t offset);
203 static inline mat4_t m4_scaling (vec3_t scale);
204 static inline mat4_t m4_rotation_x (float angle_in_rad);
205 static inline mat4_t m4_rotation_y (float angle_in_rad);
206 static inline mat4_t m4_rotation_z (float angle_in_rad);
207  mat4_t m4_rotation (float angle_in_rad, vec3_t axis);
208 
209 /* Renamed stuff by Emmanuel Thomas-Maurin */
210  mat4_t m4_ortho_RH (float left, float right, float bottom, float top, float back, float front);
211  mat4_t m4_perspective_RH (float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance);
212  mat4_t m4_look_at_RH (vec3_t from, vec3_t to, vec3_t up);
213 /* Not implemented yet:
214  mat4_t m4_ortho_LH (float left, float right, float bottom, float top, float back, float front);
215  mat4_t m4_perspective_LH (float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance);
216  mat4_t m4_look_at_LH (vec3_t from, vec3_t to, vec3_t up);
217 */
218 /* (until here) */
219  mat4_t m4_frustum (float left, float right, float bottom, float top, float near, float far);
220 
221 static inline mat4_t m4_transpose (mat4_t matrix);
222 static inline mat4_t m4_mul (mat4_t a, mat4_t b);
223  mat4_t m4_invert_affine(mat4_t matrix);
224  vec3_t m4_mul_pos (mat4_t matrix, vec3_t position);
225  vec3_t m4_mul_dir (mat4_t matrix, vec3_t direction);
226 
227  void m4_print (mat4_t matrix);
228  void m4_printp (mat4_t matrix, int width, int precision);
229  void m4_fprint (FILE* stream, mat4_t matrix);
230  void m4_fprintp (FILE* stream, mat4_t matrix, int width, int precision);
231 
232  /* New stuff here */
233  void m4_print2 (mat4_t matrix, const char *line_start);
234  void m4_printp2 (mat4_t matrix, int width, int precision, const char *line_start);
235  void m4_fprint2 (FILE* stream, mat4_t matrix, const char *line_start);
236  void m4_fprintp2 (FILE* stream, mat4_t matrix, int width, int precision, const char *line_start);
237 
238 
239 /*
240  * 3D vector functions header implementation
241  */
242 
243 static inline vec3_t v3_norm(vec3_t v)
244 {
245  float len = v3_length(v);
246  if (len > 0) {
247  return (vec3_t){v.x / len, v.y / len, v.z / len};
248  } else {
249  return (vec3_t){0, 0, 0};
250  }
251 }
252 
253 static inline vec3_t v3_proj(vec3_t v, vec3_t onto)
254 {
255  return v3_muls(onto, v3_dot(v, onto) / v3_dot(onto, onto));
256 }
257 
258 static inline vec3_t v3_cross(vec3_t a, vec3_t b)
259 {
260  return (vec3_t){
261  a.y * b.z - a.z * b.y,
262  a.z * b.x - a.x * b.z,
263  a.x * b.y - a.y * b.x
264  };
265 }
266 
267 static inline float v3_angle_between(vec3_t a, vec3_t b)
268 {
269  return acosf(v3_dot(a, b) / (v3_length(a) * v3_length(b)));
270 }
271 
272 /*
273  * Matrix functions header implementation
274  */
275 
276 static inline mat4_t mat4(
277  float m00, float m10, float m20, float m30,
278  float m01, float m11, float m21, float m31,
279  float m02, float m12, float m22, float m32,
280  float m03, float m13, float m23, float m33
281  )
282 {
283  return (mat4_t){
284  .m[0][0] = m00, .m[1][0] = m10, .m[2][0] = m20, .m[3][0] = m30,
285  .m[0][1] = m01, .m[1][1] = m11, .m[2][1] = m21, .m[3][1] = m31,
286  .m[0][2] = m02, .m[1][2] = m12, .m[2][2] = m22, .m[3][2] = m32,
287  .m[0][3] = m03, .m[1][3] = m13, .m[2][3] = m23, .m[3][3] = m33
288  };
289 }
290 
291 static inline mat4_t m4_identity()
292 {
293  return mat4(
294  1, 0, 0, 0,
295  0, 1, 0, 0,
296  0, 0, 1, 0,
297  0, 0, 0, 1
298  );
299 }
300 
301 static inline mat4_t m4_translation(vec3_t offset)
302 {
303  return mat4(
304  1, 0, 0, offset.x,
305  0, 1, 0, offset.y,
306  0, 0, 1, offset.z,
307  0, 0, 0, 1
308  );
309 }
310 
311 static inline mat4_t m4_scaling(vec3_t scale)
312 {
313  float x = scale.x, y = scale.y, z = scale.z;
314  return mat4(
315  x, 0, 0, 0,
316  0, y, 0, 0,
317  0, 0, z, 0,
318  0, 0, 0, 1
319  );
320 }
321 
322 static inline mat4_t m4_rotation_x(float angle_in_rad)
323 {
324  float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
325  return mat4(
326  1, 0, 0, 0,
327  0, c, -s, 0,
328  0, s, c, 0,
329  0, 0, 0, 1
330  );
331 }
332 
333 static inline mat4_t m4_rotation_y(float angle_in_rad)
334 {
335  float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
336  return mat4(
337  c, 0, s, 0,
338  0, 1, 0, 0,
339  -s, 0, c, 0,
340  0, 0, 0, 1
341  );
342 }
343 
344 static inline mat4_t m4_rotation_z(float angle_in_rad)
345 {
346  float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
347  return mat4(
348  c, -s, 0, 0,
349  s, c, 0, 0,
350  0, 0, 1, 0,
351  0, 0, 0, 1
352  );
353 }
354 
355 static inline mat4_t m4_transpose(mat4_t matrix)
356 {
357  return mat4(
358  matrix.m00, matrix.m01, matrix.m02, matrix.m03,
359  matrix.m10, matrix.m11, matrix.m12, matrix.m13,
360  matrix.m20, matrix.m21, matrix.m22, matrix.m23,
361  matrix.m30, matrix.m31, matrix.m32, matrix.m33
362  );
363 }
364 
365 /*
366  * Multiplication of two 4x4 matrices.
367  *
368  * Implemented by following the row times column rule and illustrating it on a
369  * whiteboard with the proper indices in mind.
370  *
371  * Further reading: https://en.wikipedia.org/wiki/Matrix_multiplication
372  * But note that the article use the first index for rows and the second for
373  * columns.
374  */
375 static inline mat4_t m4_mul(mat4_t a, mat4_t b)
376 {
377  mat4_t result;
378  int i, j, k;
379 
380  for (i = 0; i < 4; i++) {
381  for (j = 0; j < 4; j++) {
382  float sum = 0;
383  for (k = 0; k < 4; k++) {
384  sum += a.m[k][j] * b.m[i][k];
385  }
386  result.m[i][j] = sum;
387  }
388  }
389 
390  return result;
391 }
392 
393 #endif // MATH_3D_HEADER
394 
395 
396 #ifdef MATH_3D_IMPLEMENTATION
397 
398 // Can't get that to show in doc
399 
412 mat4_t m4_rotation(float angle_in_rad, vec3_t axis)
413 {
414  vec3_t normalized_axis = v3_norm(axis);
415  float x = normalized_axis.x, y = normalized_axis.y, z = normalized_axis.z;
416  float c = cosf(angle_in_rad), s = sinf(angle_in_rad);
417 
418  return mat4(
419  c + x * x * (1 - c), x * y * (1 - c) - z * s, x * z * (1 - c) + y * s, 0,
420  y * x * (1 - c) + z * s, c + y * y * (1 - c), y * z * (1 - c) - x * s, 0,
421  z * x * (1 - c) - y * s, z * y * (1 - c) + x * s, c + z * z * (1 - c), 0,
422  0, 0, 0, 1
423  );
424 }
425 
453 mat4_t m4_ortho_RH(float left, float right, float bottom, float top, float back, float front)
454 {
455  float l = left, r = right, b = bottom, t = top, n = front, f = back;
456  float tx = -(r + l) / (r - l);
457  float ty = -(t + b) / (t - b);
458  float tz = -(f + n) / (f - n);
459 
460  return mat4(
461  2 / (r - l), 0, 0, tx,
462  0, 2 / (t - b), 0, ty,
463  0, 0, 2 / (f - n), tz,
464  0, 0, 0, 1
465  );
466 }
467 
488 mat4_t m4_perspective_RH(float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance)
489 {
490  float fovy_in_rad = vertical_field_of_view_in_deg / 180 * M_PI;
491  float f = 1.0f / tanf(fovy_in_rad / 2.0f);
492  float ar = aspect_ratio;
493  float nd = near_view_distance, fd = far_view_distance;
494 
495  return mat4(
496  f / ar, 0, 0, 0,
497  0, f, 0, 0,
498  0, 0, (fd + nd) / (nd - fd), (2 * fd * nd) / (nd - fd),
499  0, 0, -1, 0
500  );
501 }
502 
539 mat4_t m4_look_at_RH(vec3_t from, vec3_t to, vec3_t up)
540 {
541  vec3_t z = v3_muls(v3_norm(v3_sub(to, from)), -1);
542  vec3_t x = v3_norm(v3_cross(up, z));
543  vec3_t y = v3_cross(z, x);
544 
545  return mat4(
546  x.x, x.y, x.z, -v3_dot(from, x),
547  y.x, y.y, y.z, -v3_dot(from, y),
548  z.x, z.y, z.z, -v3_dot(from, z),
549  0, 0, 0, 1
550  );
551 }
552 
562 mat4_t m4_frustum(float left, float right, float bottom, float top, float near, float far)
563 {
564  /* Too much checking here ? */
565  if (fabs(left - right) < LG_FLOAT_EPSILON) {
566  INFO_ERR("left == right\n")
567  } else if (fabs(bottom - top) < LG_FLOAT_EPSILON) {
568  INFO_ERR("bottom == top\n")
569  } else if (far <= near) {
570  INFO_ERR("far <= near\n")
571  } else if (near <= 0.0f) {
572  INFO_ERR("near <= 0.0f\n")
573  } else if (far <= 0.0f) {
574  INFO_ERR("far <= 0.0f\n")
575  }
576 
577  float width = right - left;
578  float height = top - bottom;
579  float depth = far - near;
580  float x = (2.0 * near) / width;
581  float y = (2.0 * near) / height;
582  float A = (right + left) / width;
583  float B = (top + bottom) / height;
584  float C = - (far + near) / depth;
585  float D = - (2.0 * far * near) / depth;
586 
587  return mat4(
588  x, 0.0, A, 0.0,
589  0.0, y, B, 0.0,
590  0.0, 0.0, C, D,
591  0.0, 0.0, -1.0, 0.0
592  );
593 }
594 /* (until here) */
595 
626 mat4_t m4_invert_affine(mat4_t matrix)
627 {
628  // Create shorthands to access matrix members
629  float m00 = matrix.m00, m10 = matrix.m10, m20 = matrix.m20, m30 = matrix.m30;
630  float m01 = matrix.m01, m11 = matrix.m11, m21 = matrix.m21, m31 = matrix.m31;
631  float m02 = matrix.m02, m12 = matrix.m12, m22 = matrix.m22, m32 = matrix.m32;
632 
633  // Invert 3x3 part of the 4x4 matrix that contains the rotation, etc.
634  // That part is called R from here on.
635 
636  // Calculate cofactor matrix of R
637  float c00 = m11 * m22 - m12 * m21, c10 = -(m01 * m22 - m02 * m21), c20 = m01 * m12 - m02 * m11;
638  float c01 = -(m10 * m22 - m12 * m20), c11 = m00 * m22 - m02 * m20, c21 = -(m00 * m12 - m02 * m10);
639  float c02 = m10 * m21 - m11 * m20, c12 = -(m00 * m21 - m01 * m20), c22 = m00 * m11 - m01 * m10;
640 
641  // Caclculate the determinant by using the already calculated determinants
642  // in the cofactor matrix.
643  // Second sign is already minus from the cofactor matrix.
644  float det = m00 * c00 + m10 * c10 + m20 * c20;
645  if (fabsf(det) < 0.00001) {
646  return m4_identity();
647  }
648 
649  // Calcuate inverse of R by dividing the transposed cofactor matrix by the
650  // determinant.
651  float i00 = c00 / det, i10 = c01 / det, i20 = c02 / det;
652  float i01 = c10 / det, i11 = c11 / det, i21 = c12 / det;
653  float i02 = c20 / det, i12 = c21 / det, i22 = c22 / det;
654 
655  // Combine the inverted R with the inverted translation
656  return mat4(
657  i00, i10, i20, -(i00 * m30 + i10 * m31 + i20 * m32),
658  i01, i11, i21, -(i01 * m30 + i11 * m31 + i21 * m32),
659  i02, i12, i22, -(i02 * m30 + i12 * m31 + i22 * m32),
660  0, 0, 0, 1
661  );
662 }
663 
671 vec3_t m4_mul_pos(mat4_t matrix, vec3_t position)
672 {
673  vec3_t result = vec3(
674  matrix.m00 * position.x + matrix.m10 * position.y + matrix.m20 * position.z + matrix.m30,
675  matrix.m01 * position.x + matrix.m11 * position.y + matrix.m21 * position.z + matrix.m31,
676  matrix.m02 * position.x + matrix.m12 * position.y + matrix.m22 * position.z + matrix.m32
677  );
678 
679  float w = matrix.m03 * position.x + matrix.m13 * position.y + matrix.m23 * position.z + matrix.m33;
680  if (w != 0 && w != 1) {
681  return vec3(result.x / w, result.y / w, result.z / w);
682  }
683 
684  return result;
685 }
686 
698 vec3_t m4_mul_dir(mat4_t matrix, vec3_t direction)
699 {
700  vec3_t result = vec3(
701  matrix.m00 * direction.x + matrix.m10 * direction.y + matrix.m20 * direction.z,
702  matrix.m01 * direction.x + matrix.m11 * direction.y + matrix.m21 * direction.z,
703  matrix.m02 * direction.x + matrix.m12 * direction.y + matrix.m22 * direction.z
704  );
705 
706  float w = matrix.m03 * direction.x + matrix.m13 * direction.y + matrix.m23 * direction.z;
707  if (w != 0 && w != 1) {
708  return vec3(result.x / w, result.y / w, result.z / w);
709  }
710 
711  return result;
712 }
713 
714 void m4_print(mat4_t matrix)
715 {
716  m4_fprintp(STD_OUT, matrix, 6, 2);
717 }
718 
719 void m4_printp(mat4_t matrix, int width, int precision)
720 {
721  m4_fprintp(STD_OUT, matrix, width, precision);
722 }
723 
724 void m4_fprint(FILE* stream, mat4_t matrix)
725 {
726  m4_fprintp(stream, matrix, 6, 2);
727 }
728 
729 void m4_fprintp(FILE* stream, mat4_t matrix, int width, int precision)
730 {
731  mat4_t m = matrix;
732  int w = width, p = precision, r;
733 
734  for (r = 0; r < 4; r++) {
735  fprintf(stream, "| %*.*f %*.*f %*.*f %*.*f |\n",
736  w, p, m.m[0][r], w, p, m.m[1][r], w, p, m.m[2][r], w, p, m.m[3][r]
737  );
738  }
739 }
740 
742 void m4_print2(mat4_t matrix, const char *line_start)
743 {
744  m4_fprintp2(STD_OUT, matrix, 6, 2, line_start);
745 }
746 
747 void m4_printp2(mat4_t matrix, int width, int precision, const char *line_start)
748 {
749  m4_fprintp2(STD_OUT, matrix, width, precision, line_start);
750 }
751 
752 void m4_fprint2(FILE* stream, mat4_t matrix, const char *line_start)
753 {
754  m4_fprintp2(stream, matrix, 6, 2, line_start);
755 }
756 void m4_fprintp2(FILE* stream, mat4_t matrix, int width, int precision, const char *line_start)
757 {
758  mat4_t m = matrix;
759  int w = width, p = precision, r;
760 
761  for (r = 0; r < 4; r++) {
762  fprintf(stream, "%s| %*.*f %*.*f %*.*f %*.*f |\n",
763  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]
764  );
765  }
766 }
767 
768 #endif // MATH_3D_IMPLEMENTATION
vec3_t
Definition: math_3d.h:123
mat4_t
Definition: math_3d.h:179