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