bad char
[vg.git] / vg_m.h
diff --git a/vg_m.h b/vg_m.h
index 46cf38c173d09111ed17fc86176707ece7249845..4af60c8c84adc1022d424071ef4438130ab8ee88 100644 (file)
--- a/vg_m.h
+++ b/vg_m.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved 
+/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved 
  *
  *  0. Misc
  *  1. Scalar operations
  *    5.d Raycast & Spherecasts
  *    5.e Curves
  *    5.f Volumes
+ *    5.g Inertia tensors
  *  6. Statistics
  *    6.a Random numbers
- **/
+ */
 
-#ifndef VG_M_H
-#define VG_M_H
+#pragma once
 
 #include "vg_platform.h"
 #include <math.h>
@@ -97,6 +97,10 @@ static inline f32 vg_fractf( f32 a )
    return a - floorf( a );
 }
 
+static inline f64 vg_fractf64( f64 a ){
+   return a - floor( a );
+}
+
 static f32 vg_cfrictf( f32 velocity, f32 F )
 {
    return -vg_signf(velocity) * vg_minf( F, fabsf(velocity) );
@@ -132,6 +136,16 @@ static f32 vg_dequantf( u32 q, u32 bits, f32 min, f32 max ){
    return min + (f32)q * ((max-min) / (f32)mask);
 }
 
+/* https://iquilezles.org/articles/functions/ 
+ *
+ * Use k to control the stretching of the function. Its maximum, which is 1, 
+ * happens at exactly x = 1/k. 
+ */
+static f32 vg_exp_impulse( f32 x, f32 k ){
+    f32 h = k*x;
+    return h*expf(1.0f-h);
+}
+
 /*
  * -----------------------------------------------------------------------------
  * Section 2.a                   2D Vectors
@@ -870,6 +884,20 @@ static void m3x3_skew_symetric( m3x3f a, v3f v )
    a[2][2] =  0.0f;
 }
 
+/* aka kronecker product */
+static void m3x3_outer_product( m3x3f out_m, v3f a, v3f b )
+{
+   out_m[0][0] = a[0]*b[0];
+   out_m[0][1] = a[0]*b[1];
+   out_m[0][2] = a[0]*b[2];
+   out_m[1][0] = a[1]*b[0];
+   out_m[1][1] = a[1]*b[1];
+   out_m[1][2] = a[1]*b[2];
+   out_m[2][0] = a[2]*b[0];
+   out_m[2][1] = a[2]*b[1];
+   out_m[2][2] = a[2]*b[2];
+}
+
 static void m3x3_add( m3x3f a, m3x3f b, m3x3f d )
 {
    v3_add( a[0], b[0], d[0] );
@@ -877,6 +905,13 @@ static void m3x3_add( m3x3f a, m3x3f b, m3x3f d )
    v3_add( a[2], b[2], d[2] );
 }
 
+static void m3x3_sub( m3x3f a, m3x3f b, m3x3f d )
+{
+   v3_sub( a[0], b[0], d[0] );
+   v3_sub( a[1], b[1], d[1] );
+   v3_sub( a[2], b[2], d[2] );
+}
+
 static inline void m3x3_copy( m3x3f a, m3x3f b )
 {
    v3_copy( a[0], b[0] );
@@ -890,12 +925,12 @@ static inline void m3x3_identity( m3x3f a )
    m3x3_copy( id, a );
 }
 
-static void m3x3_diagonal( m3x3f a, f32 v )
+static void m3x3_diagonal( m3x3f out_a, f32 v )
 {
-   m3x3_identity( a );
-   a[0][0] = v;
-   a[1][1] = v;
-   a[2][2] = v;
+   m3x3_identity( out_a );
+   out_a[0][0] = v;
+   out_a[1][1] = v;
+   out_a[2][2] = v;
 }
 
 static void m3x3_setdiagonalv3( m3x3f a, v3f v )
@@ -1469,6 +1504,36 @@ static inline void m4x4_inv( m4x4f a, m4x4f d )
    v4_muls( d[3], det, d[3] );
 }
 
+/* 
+ * http://www.terathon.com/lengyel/Lengyel-Oblique.pdf 
+ */
+static void m4x4_clip_projection( m4x4f mat, v4f plane ){
+   v4f c = 
+   {
+      (vg_signf(plane[0]) + mat[2][0]) / mat[0][0],
+      (vg_signf(plane[1]) + mat[2][1]) / mat[1][1],
+      -1.0f,
+      (1.0f + mat[2][2]) / mat[3][2]
+   };
+
+   v4_muls( plane, 2.0f / v4_dot(plane,c), c );
+
+   mat[0][2] = c[0];
+   mat[1][2] = c[1];
+   mat[2][2] = c[2] + 1.0f;
+   mat[3][2] = c[3];
+}
+
+/*
+ * Undoes the above operation
+ */
+static void m4x4_reset_clipping( m4x4f mat, float ffar, float fnear ){
+   mat[0][2] = 0.0f; 
+   mat[1][2] = 0.0f; 
+   mat[2][2] = -(ffar + fnear) / (ffar - fnear); 
+   mat[3][2] = -2.0f * ffar * fnear / (ffar - fnear); 
+}
+
 /*
  * -----------------------------------------------------------------------------
  * Section 5.a                       Boxes
@@ -1578,7 +1643,7 @@ static int plane_intersect3( v4f a, v4f b, v4f c, v3f p )
    return 1;
 }
 
-int plane_intersect2( v4f a, v4f b, v3f p, v3f n )
+static int plane_intersect2( v4f a, v4f b, v3f p, v3f n )
 {
    f32 const epsilon = 1e-6f;
 
@@ -1984,7 +2049,7 @@ static void closest_point_elipse( v2f p, v2f e, v2f o )
  * -----------------------------------------------------------------------------
  */
 
-int ray_aabb1( boxf box, v3f co, v3f dir_inv, f32 dist )
+static int ray_aabb1( boxf box, v3f co, v3f dir_inv, f32 dist )
 {
    v3f v0, v1;
    f32 tmin, tmax;
@@ -2007,7 +2072,7 @@ int ray_aabb1( boxf box, v3f co, v3f dir_inv, f32 dist )
 
 /* Time of intersection with ray vs triangle */
 static int ray_tri( v3f tri[3], v3f co, 
-                    v3f dir, f32 *dist )
+                    v3f dir, f32 *dist, int backfaces )
 {
    f32 const kEpsilon = 0.00001f;
 
@@ -2023,7 +2088,7 @@ static int ray_tri( v3f tri[3], v3f co,
    v3_cross( dir, v1, h );
    v3_cross( v0, v1, n );
 
-   if( v3_dot( n, dir ) > 0.0f ) /* Backface culling */
+   if( (v3_dot( n, dir ) > 0.0f) && !backfaces ) /* Backface culling */
       return 0;
    
    /* Parralel */
@@ -2161,7 +2226,7 @@ static int spherecast_triangle( v3f tri[3],
    f32 t_min = INFINITY,
          t1;
 
-   if( ray_tri( sum, co, dir, &t1 ) ){
+   if( ray_tri( sum, co, dir, &t1, 0 ) ){
       t_min = vg_minf( t_min, t1 );
       hit = 1;
    }
@@ -2235,9 +2300,135 @@ static void eval_bezier3( v3f p0, v3f p1, v3f p2, f32 t, v3f p )
  * -----------------------------------------------------------------------------
  */
 
-static float vg_sphere_volume( float radius ){
-   float r3 = radius*radius*radius;
-   return (4.0f/3.0f) * VG_PIf * r3;
+static f32 vg_sphere_volume( f32 r ){
+   return (4.0f/3.0f) * VG_PIf * r*r*r;
+}
+
+static f32 vg_box_volume( boxf box ){
+   v3f e;
+   v3_sub( box[1], box[0], e );
+   return e[0]*e[1]*e[2];
+}
+
+static f32 vg_cylinder_volume( f32 r, f32 h ){
+   return VG_PIf * r*r * h;
+}
+
+static f32 vg_capsule_volume( f32 r, f32 h ){
+   return vg_sphere_volume( r ) + vg_cylinder_volume( r, h-r*2.0f );
+}
+
+static void vg_sphere_bound( f32 r, boxf out_box ){
+   v3_fill( out_box[0], -r );
+   v3_fill( out_box[1],  r );
+}
+
+static void vg_capsule_bound( f32 r, f32 h, boxf out_box ){
+   v3_copy( (v3f){-r,-h*0.5f,r}, out_box[0] );
+   v3_copy( (v3f){-r, h*0.5f,r}, out_box[1] );
+}
+
+
+/*
+ * -----------------------------------------------------------------------------
+ * Section 5.g                  Inertia Tensors
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * Translate existing inertia tensor 
+ */
+static void vg_translate_inertia( m3x3f inout_inertia, f32 mass, v3f d ){
+   /* 
+    * I = I_0 + m*[(d.d)E_3 - d(X)d]
+    *
+    * I:   updated tensor
+    * I_0: original tensor
+    * m:   scalar mass
+    * d:   translation vector 
+    * (X): outer product
+    * E_3: identity matrix
+    */
+   m3x3f t, outer, scale;
+   m3x3_diagonal( t, v3_dot(d,d) );
+   m3x3_outer_product( outer, d, d );
+   m3x3_sub( t, outer, t );
+   m3x3_diagonal( scale, mass );
+   m3x3_mul( scale, t, t );
+   m3x3_add( inout_inertia, t, inout_inertia );
+}
+
+/*
+ * Rotate existing inertia tensor 
+ */
+static void vg_rotate_inertia( m3x3f inout_inertia, m3x3f rotation ){
+   /*
+    *  I = R I_0 R^T
+    *
+    *  I:   updated tensor
+    *  I_0: original tensor
+    *  R:   rotation matrix
+    *  R^T: tranposed rotation matrix
+    */
+
+   m3x3f Rt;
+   m3x3_transpose( rotation, Rt );
+   m3x3_mul( rotation, inout_inertia, inout_inertia );
+   m3x3_mul( inout_inertia, Rt, inout_inertia );
+}
+/*
+ * Create inertia tensor for box
+ */
+static void vg_box_inertia( boxf box, f32 mass, m3x3f out_inertia ){
+   v3f e, com;
+   v3_sub( box[1], box[0], e );
+   v3_muladds( box[0], e, 0.5f, com );
+
+   f32 ex2 = e[0]*e[0],
+       ey2 = e[1]*e[1],
+       ez2 = e[2]*e[2],
+       ix  = (ey2+ez2) * mass * (1.0f/12.0f),
+       iy  = (ex2+ez2) * mass * (1.0f/12.0f),
+       iz  = (ex2+ey2) * mass * (1.0f/12.0f);
+
+   m3x3_identity( out_inertia );
+   m3x3_setdiagonalv3( out_inertia, (v3f){ ix, iy, iz } );
+   vg_translate_inertia( out_inertia, mass, com );
+}
+
+/*
+ * Create inertia tensor for sphere
+ */
+static void vg_sphere_inertia( f32 r, f32 mass, m3x3f out_inertia ){
+   f32 ixyz = r*r * mass * (2.0f/5.0f);
+   
+   m3x3_identity( out_inertia );
+   m3x3_setdiagonalv3( out_inertia, (v3f){ ixyz, ixyz, ixyz } );
+}
+
+/*
+ * Create inertia tensor for capsule
+ */
+static void vg_capsule_inertia( f32 r, f32 h, f32 mass, m3x3f out_inertia ){
+   f32 density = mass / vg_capsule_volume( r, h ),
+       ch  = h-r*2.0f, /* cylinder height */
+       cm  = VG_PIf * ch*r*r * density, /* cylinder mass */
+       hm  = VG_TAUf * (1.0f/3.0f) * r*r*r * density, /* hemisphere mass */
+       
+       iy  = r*r*cm * 0.5f,
+       ixz = iy * 0.5f + cm*ch*ch*(1.0f/12.0f),
+
+       aux0= (hm*2.0f*r*r)/5.0f;
+
+   iy += aux0 * 2.0f;
+
+   f32 aux1= ch*0.5f,
+       aux2= aux0 + hm*(aux1*aux1 + 3.0f*(1.0f/8.0f)*ch*r);
+
+   ixz += aux2*2.0f;
+
+   m3x3_identity( out_inertia );
+   m3x3_setdiagonalv3( out_inertia, (v3f){ ixz, iy, ixz } );
 }
 
 /*
@@ -2267,59 +2458,55 @@ static float vg_sphere_volume( float radius ){
 /* changes to STATE_VECTOR_LENGTH also require changes to this */
 #define MT_STATE_VECTOR_M      397 
 
-struct {
+typedef struct vg_rand vg_rand;
+struct vg_rand {
   u32 mt[MT_STATE_VECTOR_LENGTH];
   i32 index;
-} 
-static vg_rand;
+};
 
-static void vg_rand_seed( unsigned long seed ) 
-{
+static void vg_rand_seed( vg_rand *rand, unsigned long seed ) {
    /* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator
     * from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer
     * Programming," Vol. 2 (2nd Ed.) pp.102.
     */
-   vg_rand.mt[0] = seed & 0xffffffff;
-   for( vg_rand.index=1; vg_rand.index<MT_STATE_VECTOR_LENGTH; vg_rand.index++){
-      vg_rand.mt[vg_rand.index] = 
-         (6069 * vg_rand.mt[vg_rand.index-1]) & 0xffffffff;
+   rand->mt[0] = seed & 0xffffffff;
+   for( rand->index=1; rand->index<MT_STATE_VECTOR_LENGTH; rand->index++){
+      rand->mt[rand->index] = (6069 * rand->mt[rand->index-1]) & 0xffffffff;
    }
 }
 
 /*
  * Generates a pseudo-randomly generated long.
  */
-static u32 vg_randu32(void) 
-{
+static u32 vg_randu32( vg_rand *rand ) {
    u32 y;
    /* mag[x] = x * 0x9908b0df for x = 0,1 */
    static u32 mag[2] = {0x0, 0x9908b0df}; 
-   if( vg_rand.index >= MT_STATE_VECTOR_LENGTH || vg_rand.index < 0 ){
+   if( rand->index >= MT_STATE_VECTOR_LENGTH || rand->index < 0 ){
       /* generate STATE_VECTOR_LENGTH words at a time */
       int kk;
-      if( vg_rand.index >= MT_STATE_VECTOR_LENGTH+1 || vg_rand.index < 0 ){
-         vg_rand_seed( 4357 );
+      if( rand->index >= MT_STATE_VECTOR_LENGTH+1 || rand->index < 0 ){
+         vg_rand_seed( rand, 4357 );
       }
       for( kk=0; kk<MT_STATE_VECTOR_LENGTH-MT_STATE_VECTOR_M; kk++ ){
-         y = (vg_rand.mt[kk] & MT_UPPER_MASK) | 
-             (vg_rand.mt[kk+1] & MT_LOWER_MASK);
-         vg_rand.mt[kk] = vg_rand.mt[kk+MT_STATE_VECTOR_M] ^ 
-                           (y >> 1) ^ mag[y & 0x1];
+         y = (rand->mt[kk] & MT_UPPER_MASK) | 
+             (rand->mt[kk+1] & MT_LOWER_MASK);
+         rand->mt[kk] = rand->mt[kk+MT_STATE_VECTOR_M] ^ (y>>1) ^ mag[y & 0x1];
       }
       for( ; kk<MT_STATE_VECTOR_LENGTH-1; kk++ ){
-         y = (vg_rand.mt[kk] & MT_UPPER_MASK) | 
-             (vg_rand.mt[kk+1] & MT_LOWER_MASK);
-         vg_rand.mt[kk] = 
-            vg_rand.mt[ kk+(MT_STATE_VECTOR_M-MT_STATE_VECTOR_LENGTH)] ^ 
+         y = (rand->mt[kk] & MT_UPPER_MASK) | 
+             (rand->mt[kk+1] & MT_LOWER_MASK);
+         rand->mt[kk] = 
+            rand->mt[ kk+(MT_STATE_VECTOR_M-MT_STATE_VECTOR_LENGTH)] ^ 
                         (y >> 1) ^ mag[y & 0x1];
       }
-      y = (vg_rand.mt[MT_STATE_VECTOR_LENGTH-1] & MT_UPPER_MASK) | 
-          (vg_rand.mt[0] & MT_LOWER_MASK);
-      vg_rand.mt[MT_STATE_VECTOR_LENGTH-1] = 
-         vg_rand.mt[MT_STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1];
-      vg_rand.index = 0;
+      y = (rand->mt[MT_STATE_VECTOR_LENGTH-1] & MT_UPPER_MASK) | 
+          (rand->mt[0] & MT_LOWER_MASK);
+      rand->mt[MT_STATE_VECTOR_LENGTH-1] = 
+         rand->mt[MT_STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1];
+      rand->index = 0;
    }
-   y = vg_rand.mt[vg_rand.index++];
+   y = rand->mt[rand->index++];
    y ^= (y >> 11);
    y ^= (y << 7) & MT_TEMPERING_MASK_B;
    y ^= (y << 15) & MT_TEMPERING_MASK_C;
@@ -2330,21 +2517,21 @@ static u32 vg_randu32(void)
 /*
  * Generates a pseudo-randomly generated f64 in the range [0..1].
  */
-static inline f64 vg_randf64(void)
-{
-   return (f64)vg_randu32()/(f64)0xffffffff;
+static inline f64 vg_randf64( vg_rand *rand ){
+   return (f64)vg_randu32(rand)/(f64)0xffffffff;
 }
 
-static inline f64 vg_randf64_range( f64 min, f64 max )
-{
-   return vg_lerp( min, max, (f64)vg_randf64() );
+static inline f64 vg_randf64_range( vg_rand *rand, f64 min, f64 max ){
+   return vg_lerp( min, max, (f64)vg_randf64(rand) );
 }
 
-static inline void vg_rand_dir( v3f dir )
-{
-   dir[0] = vg_randf64();
-   dir[1] = vg_randf64();
-   dir[2] = vg_randf64();
+static inline void vg_rand_dir( vg_rand *rand, v3f dir ){
+   dir[0] = vg_randf64(rand);
+   dir[1] = vg_randf64(rand);
+   dir[2] = vg_randf64(rand);
+
+   /* warning: *could* be 0 length.
+    * very unlikely.. 1 in (2^32)^3. but its mathematically wrong. */
 
    v3_muls( dir, 2.0f, dir );
    v3_sub( dir, (v3f){1.0f,1.0f,1.0f}, dir );
@@ -2352,10 +2539,73 @@ static inline void vg_rand_dir( v3f dir )
    v3_normalize( dir );
 }
 
-static inline void vg_rand_sphere( v3f co )
-{
-   vg_rand_dir(co);
-   v3_muls( co, cbrtf( vg_randf64() ), co );
+static inline void vg_rand_sphere( vg_rand *rand, v3f co ){
+   vg_rand_dir(rand,co);
+   v3_muls( co, cbrtf( vg_randf64(rand) ), co );
+}
+
+static void vg_rand_disc( vg_rand *rand, v2f co ){
+   f32 a = vg_randf64(rand) * VG_TAUf;
+   co[0] = sinf(a);
+   co[1] = cosf(a); 
+   v2_muls( co, sqrtf( vg_randf64(rand) ), co );
+}
+
+static void vg_rand_cone( vg_rand *rand, v3f out_dir, f32 angle ){
+   f32 r = sqrtf(vg_randf64(rand)) * angle * 0.5f,
+       a = vg_randf64(rand) * VG_TAUf;
+
+   out_dir[0] = sinf(a) * sinf(r);
+   out_dir[1] = cosf(a) * sinf(r);
+   out_dir[2] = cosf(r);
+}
+
+static void vg_hsv_rgb( v3f hsv, v3f rgb ){
+   i32 i = floorf( hsv[0]*6.0f );
+   f32 v = hsv[2],
+       f = hsv[0] * 6.0f - (f32)i,
+       p = v * (1.0f-hsv[1]),
+       q = v * (1.0f-f*hsv[1]),
+       t = v * (1.0f-(1.0f-f)*hsv[1]);
+
+   switch( i % 6 ){
+      case 0: rgb[0] = v; rgb[1] = t; rgb[2] = p; break;
+      case 1: rgb[0] = q; rgb[1] = v; rgb[2] = p; break;
+      case 2: rgb[0] = p; rgb[1] = v; rgb[2] = t; break;
+      case 3: rgb[0] = p; rgb[1] = q; rgb[2] = v; break;
+      case 4: rgb[0] = t; rgb[1] = p; rgb[2] = v; break;
+      case 5: rgb[0] = v; rgb[1] = p; rgb[2] = q; break;
+   }
 }
 
-#endif /* VG_M_H */
+static void vg_rgb_hsv( v3f rgb, v3f hsv ){
+   f32 min = v3_minf( rgb ),
+       max = v3_maxf( rgb ),
+       range = max-min,
+       k_epsilon = 0.00001f;
+
+   hsv[2] = max;
+   if( range < k_epsilon ){
+      hsv[0] = 0.0f;
+      hsv[1] = 0.0f;
+      return;
+   }
+
+   if( max > k_epsilon ){
+      hsv[1] = range/max;
+   }
+   else {
+      hsv[0] = 0.0f;
+      hsv[1] = 0.0f;
+      return;
+   }
+
+   if( rgb[0] >= max )
+      hsv[0] = (rgb[1]-rgb[2])/range;
+   else if( max == rgb[1] )
+      hsv[0] = 2.0f+(rgb[2]-rgb[0])/range;
+   else
+      hsv[0] = 4.0f+(rgb[0]-rgb[1])/range;
+
+   hsv[0] = vg_fractf( hsv[0] * (60.0f/360.0f) );
+}