rigidbody view
authorhgn <hgodden00@gmail.com>
Sat, 10 Feb 2024 20:10:03 +0000 (20:10 +0000)
committerhgn <hgodden00@gmail.com>
Sat, 10 Feb 2024 20:10:03 +0000 (20:10 +0000)
12 files changed:
labs/build.c [new file with mode: 0644]
labs/build.sh [new file with mode: 0755]
labs/physics.c [new file with mode: 0644]
labs/vg [new symlink]
projects/encode.c [deleted file]
projects/struct [deleted file]
vg.h
vg_build.h
vg_camera.h [new file with mode: 0644]
vg_lines.h
vg_m.h
vg_rigidbody_view.h [new file with mode: 0644]

diff --git a/labs/build.c b/labs/build.c
new file mode 100644 (file)
index 0000000..bb0b343
--- /dev/null
@@ -0,0 +1,34 @@
+#include "vg/vg_build.h"
+#include "vg/vg_build_utils_shader.h"
+
+void s_lab_physics(void){
+   vg_info( "running script: s_lab_physics(void)\n" );
+   vg_build.fresh = 0;
+   vg_build.platform = k_platform_linux;
+   vg_build.arch = k_architecture_x86_64;
+   vg_build.compiler = k_compiler_clang;
+   vg_build.libc = k_libc_version_native;
+
+   vg_build_new( "physics" );
+   vg_add_source( "physics.c" );
+   vg_add_graphics();
+   vg_add_game_stuff();
+   vg_compile( "physics" );
+   vg_success( "Completed 1/1\n" );
+}
+
+int main( int argc, char *argv[] ){
+   char *arg;
+   while( vg_argp( argc, argv ) ){
+      if( vg_long_opt( "physics" ) )
+         s_lab_physics();
+
+      if( vg_opt('r') )
+         vg_build.optimization = 3;
+   }
+
+   if( vg_build.warnings )
+      vg_warn( "Finished with %u warnings\n", vg_build.warnings );
+   else
+      vg_success( "All scripts ran successfully\n" );
+}
diff --git a/labs/build.sh b/labs/build.sh
new file mode 100755 (executable)
index 0000000..d807648
--- /dev/null
@@ -0,0 +1 @@
+clang -fsanitize=address -O0 -I./vg build.c -o /tmp/tmpsr && /tmp/tmpsr $@
diff --git a/labs/physics.c b/labs/physics.c
new file mode 100644 (file)
index 0000000..e741f12
--- /dev/null
@@ -0,0 +1,105 @@
+#define VG_GAME
+#define VG_AUDIO_FORCE_COMPRESSED
+#define VG_3D
+#define VG_LOG_SOURCE_INFO
+#define VG_TIMESTEP_FIXED (1.0/60.0)
+
+#ifndef VG_RELEASE
+ #define VG_DEVWINDOW
+#endif
+
+#define SDL_MAIN_HANDLED
+
+#include "vg/vg.h"
+#include "vg/vg_camera.h"
+
+int main( int argc, char *argv[] ){
+   vg_mem.use_libc_malloc = 0;
+   vg_set_mem_quota( 80*1024*1024 );
+   vg_enter( argc, argv, "Voyager Game Engine" ); 
+   return 0;
+}
+
+static void vg_launch_opt(void){
+   const char *arg;
+}
+
+static void vg_preload(void){
+   vg_audio.dsp_enabled = 0;
+}
+
+static void vg_load(void){
+   vg_bake_shaders();
+}
+
+static void vg_pre_update(void){
+}
+
+static void vg_fixed_update(void){
+}
+
+static void vg_post_update(void){
+}
+
+static void vg_framebuffer_resize( int w, int h ){
+}
+
+static void draw_origin_axis(void){
+   vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 1.0f, 0.0f, 0.0f }, 0xffff0000 );
+   vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 1.0f, 0.0f }, 0xff00ff00 );
+   vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 0.0f, 1.0f }, 0xff0000ff );
+}
+
+static void vg_render(void){
+   glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+   glViewport( 0,0, vg.window_x, vg.window_y );
+   glEnable( GL_DEPTH_TEST );
+   glDisable( GL_BLEND );
+
+   glClearColor( 0.05f, 0.05f, 0.05f, 1.0f );
+   glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+
+   f32 x = -((f32)vg.mouse_pos[0] / (f32)vg.window_x) * VG_TAUf,
+       y = (((f32)vg.mouse_pos[1] / (f32)vg.window_y) - 0.5f)*VG_PIf;
+
+   f32 t = vg.time * 0.1f * VG_TAUf;
+
+   vg_camera cam = {
+      .angles = { -x, y, 0 },
+      .nearz = 0.01f,
+      .farz = 500.0f,
+      .fov = 90.0f,
+      .pos = { sinf(x)*10.0f*cosf(y), 
+               sinf(y)*10.0f, 
+               cosf(x)*10.0f*cosf(y) },
+   };
+
+   vg_camera_update_transform( &cam );
+   vg_camera_update_view( &cam );
+   vg_camera_update_projection( &cam );
+   vg_camera_finalize( &cam );
+   m4x4_copy( cam.mtx.pv, vg.pv );
+
+   m4x3f mdl;
+   m4x3_identity( mdl );
+
+   vg_rb_view_bind();
+   vg_rb_view_capsule( mdl, (sinf(t*0.2f)*0.5f+0.55f)*2.0f, 
+                            (sinf(t*0.33f)*0.5f+0.55f)*1.0f,
+                            (v4f){1,0,0,1} );
+   vg_rb_view_box( mdl, (boxf){{-4.999,-2.001,-4.999},{4.999,-0.999,4.999}}, 
+                  (v4f){0.8f,0.8f,0.8f,1} );
+
+   mdl[3][0] = sinf(t)*2.0f;
+   mdl[3][2] = cosf(t)*2.0f;
+   vg_rb_view_sphere( mdl, 1, (v4f){0,1,0,1} );
+
+   draw_origin_axis();
+   vg_lines_drawall();
+
+   glDisable(GL_DEPTH_TEST);
+}
+
+static void vg_gui(void){
+   vg_ui.wants_mouse = 1;
+}
diff --git a/labs/vg b/labs/vg
new file mode 120000 (symlink)
index 0000000..a96aa0e
--- /dev/null
+++ b/labs/vg
@@ -0,0 +1 @@
+..
\ No newline at end of file
diff --git a/projects/encode.c b/projects/encode.c
deleted file mode 100644 (file)
index 8298bc8..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "../vg_platform.h"
-
-
-struct yo
-{
-   float a, b, c, d;
-};
-
-int main( int argc, const char *argv[] )
-{
-   struct yo yo = { 300.324, 2023.334, -324.1232, 30.494 };
-   printf( "{%f %f %f %f}\n", yo.a, yo.b, yo.c, yo.d );
-   
-   char text[ sizeof(yo)*2 ];
-   vg_bin_str( &yo, text, sizeof(yo) );
-
-   printf( "encoded: %.*s\n", (int)(sizeof(yo)*2), text );
-
-   vg_str_bin( text, &yo, sizeof(yo)*2 );
-
-   printf( "{%f %f %f %f}\n", yo.a, yo.b, yo.c, yo.d );
-}
diff --git a/projects/struct b/projects/struct
deleted file mode 100755 (executable)
index 34d1298..0000000
Binary files a/projects/struct and /dev/null differ
diff --git a/vg.h b/vg.h
index 987ed73f7d3504c9785bfe9a67fe6447bde44e95..523779260f1aa02d702415282574692a7979b00a 100644 (file)
--- a/vg.h
+++ b/vg.h
@@ -258,6 +258,7 @@ static void vg_checkgl( const char *src_info );
 #include "vg_input.h"
 #include "vg_imgui.h"
 #include "vg_lines.h"
+#include "vg_rigidbody_view.h"
 #include "vg_loader.h"
 #include "vg_opt.h"
 #include "vg_settings_menu.h"
@@ -316,6 +317,7 @@ static void _vg_load_full( void *data ){
    /* internal */
    vg_loader_step( vg_input_init, vg_input_free );
    vg_loader_step( vg_lines_init, NULL );
+   vg_loader_step( vg_rb_view_init, NULL );
 #ifndef VG_NO_AUDIO
    vg_loader_step( vg_audio_init, vg_audio_free );
 #endif
@@ -427,12 +429,12 @@ static void _vg_gameloop_update(void)
    vg.engine_stage = k_engine_stage_update_fixed;
 
    vg.fixed_iterations = 0;
-   vg_lines.allow_input = 1;
+   vg_lines.enabled = vg_lines.render;
    vg.time_fixed_accumulator += vg.time_delta;
 
    while( vg.time_fixed_accumulator >= VG_TIMESTEP_FIXED ){
       vg_fixed_update();
-      vg_lines.allow_input = 0;
+      vg_lines.enabled = 0;
       vg.time_fixed_accumulator -= VG_TIMESTEP_FIXED;
 
       vg.fixed_iterations ++;
@@ -440,7 +442,7 @@ static void _vg_gameloop_update(void)
          break;
       }
    }
-   vg_lines.allow_input = 1;
+   vg_lines.enabled = vg_lines.render;
    vg.time_fixed_extrapolate = vg.time_fixed_accumulator / VG_TIMESTEP_FIXED;
 
    vg.engine_stage = k_engine_stage_update;
index 965f14cac3941c73e687985360410d581b5008a3..a30fe17f9b73e57852db3c29bd7478a7cf80c6f9 100644 (file)
@@ -5,6 +5,8 @@
 #include <unistd.h>
 #include <time.h>
 
+#include "vg.h"
+#include "vg_opt.h"
 #include "vg_log.h"
 
 /* we dont free dynamic vg_strs in this program. so, we dont care.. */
@@ -21,7 +23,8 @@ struct {
    u32 optimization,
        warnings;
    bool fresh,
-        debug_asan;
+        debug_asan,
+        build_times;
 
    enum platform {
       k_platform_anyplatform,
diff --git a/vg_camera.h b/vg_camera.h
new file mode 100644 (file)
index 0000000..9fd660b
--- /dev/null
@@ -0,0 +1,91 @@
+#pragma once
+#include "vg_m.h"
+
+typedef struct vg_camera vg_camera;
+struct vg_camera{
+   /* Input */
+   v3f angles;
+   v3f pos;
+   f32 fov, nearz, farz;
+
+   /* Output */
+   m4x3f transform,
+         transform_inverse;
+
+   struct vg_camera_mtx{
+      m4x4f p,
+            v,
+            pv;
+   }
+   mtx,
+   mtx_prev;
+};
+
+static void vg_camera_lerp_angles( v3f a, v3f b, f32 t, v3f d ){
+   d[0] = vg_alerpf( a[0], b[0], t );
+   d[1] = vg_lerpf(  a[1], b[1], t );
+   d[2] = vg_lerpf(  a[2], b[2], t );
+}
+
+/* lerp position, fov, and angles */
+static void vg_camera_lerp( vg_camera *a, vg_camera *b, f32 t, vg_camera *d ){
+   v3_lerp( a->pos, b->pos, t, d->pos );
+   vg_camera_lerp_angles( a->angles, b->angles, t, d->angles );
+   d->fov = vg_lerpf( a->fov, b->fov, t );
+}
+
+static void vg_camera_copy( vg_camera *a, vg_camera *d ){
+   v3_copy( a->pos, d->pos );
+   v3_copy( a->angles, d->angles );
+   d->fov = a->fov;
+}
+
+static void vg_m4x3_transform_camera( m4x3f m, vg_camera *cam ){
+   m4x3_mulv( m, cam->pos, cam->pos );
+
+   v3f v0;
+   v3_angles_vector( cam->angles, v0 );
+   m3x3_mulv( m, v0, v0 );
+   v3_normalize( v0 );
+   v3_angles( v0, cam->angles );
+}
+
+/*
+ * 1) [angles, pos] -> transform 
+ */
+static void vg_camera_update_transform( vg_camera *cam ){
+   v4f qyaw, qpitch, qcam;
+   q_axis_angle( qyaw,   (v3f){ 0.0f, 1.0f, 0.0f }, -cam->angles[0] );
+   q_axis_angle( qpitch, (v3f){ 1.0f, 0.0f, 0.0f }, -cam->angles[1] );
+
+   q_mul( qyaw, qpitch, qcam );
+   q_m3x3( qcam, cam->transform );
+   v3_copy( cam->pos, cam->transform[3] );
+}
+
+/*
+ * 2) [transform] -> transform_inverse, view matrix
+ */
+static void vg_camera_update_view( vg_camera *cam ){
+   m4x4_copy( cam->mtx.v,  cam->mtx_prev.v );
+   m4x3_invert_affine( cam->transform, cam->transform_inverse );
+   m4x3_expand( cam->transform_inverse, cam->mtx.v );
+}
+
+/*
+ * 3) [fov,nearz,farz] -> projection matrix
+ */
+static void vg_camera_update_projection( vg_camera *cam ){
+   m4x4_copy( cam->mtx.p,  cam->mtx_prev.p );
+   m4x4_projection( cam->mtx.p, cam->fov,
+                    (float)vg.window_x / (float)vg.window_y, 
+                    cam->nearz, cam->farz );
+}
+
+/*
+ * 4) [projection matrix, view matrix] -> previous pv, new pv 
+ */
+static void vg_camera_finalize( vg_camera *cam ){
+   m4x4_copy( cam->mtx.pv, cam->mtx_prev.pv );
+   m4x4_mul( cam->mtx.p, cam->mtx.v, cam->mtx.pv );
+}
index 5511875b9f058747c30afce32d7f40d13cb09049..8fff226106e7bc2a007611636226a339910ac294 100644 (file)
@@ -59,8 +59,8 @@ static struct vg_shader _shader_lines = {
 };
 
 struct{
-   u32 draw,
-       allow_input;
+   u32 enabled, 
+       render;
        
    struct vg_lines_vert{
       v3f co;
@@ -106,7 +106,6 @@ static void async_vg_lines_init( void *payload, u32 payload_size ){
    glEnableVertexAttribArray( 1 );
    
    VG_CHECK_GL_ERR();
-   vg_lines.allow_input = 1;
 }
 
 static void vg_lines_init(void){
@@ -116,7 +115,7 @@ static void vg_lines_init(void){
 
    vg_async_call( async_vg_lines_init, NULL, 0 );
 
-   vg_console_reg_var( "vg_lines", &vg_lines.draw, k_var_dtype_i32, 
+   vg_console_reg_var( "vg_lines", &vg_lines.render, k_var_dtype_i32, 
                        VG_VAR_CHEAT );
    vg_shader_register( &_shader_lines );
 }
@@ -131,14 +130,13 @@ static void vg_lines_drawall( void ){
        glBindBuffer( GL_ARRAY_BUFFER, vg_lines.vbo );
 
    u32 bufusage = vg_linear_get_cur(vg_lines.vertex_buffer);
-       
        glBufferSubData( GL_ARRAY_BUFFER, 0, bufusage, vg_lines.vertex_buffer );
 
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        glBlendEquation( GL_FUNC_ADD );
 
-   if( vg_lines.draw )
+   if( vg_lines.render )
       glDrawArrays( GL_LINES, 0, bufusage / sizeof(struct vg_lines_vert) );
        
        glDisable( GL_BLEND );
@@ -146,8 +144,7 @@ static void vg_lines_drawall( void ){
 }
 
 static void vg_line2( line_co from, line_co to, u32 fc, u32 tc ){
-   if( !vg_lines.allow_input ) return;
-   if( !vg_lines.draw ) return;
+   if( !vg_lines.enabled ) return;
 
    u32 size = 2 * sizeof(struct vg_lines_vert);
        struct vg_lines_vert *v = vg_linear_alloc( vg_lines.vertex_buffer, size );
@@ -160,10 +157,14 @@ static void vg_line2( line_co from, line_co to, u32 fc, u32 tc ){
 }
 
 static void vg_line( line_co from, line_co to, u32 colour ){
+   if( !vg_lines.enabled ) return;
+
        vg_line2( from, to, colour, colour );
 }
 
 static void vg_line_arrow( line_co co, line_co dir, float size, u32 colour ){
+   if( !vg_lines.enabled ) return;
+
    v3f p1, tx, ty, p2, p3;
    v3_muladds( co, dir, size, p1 );
    v3_tangent_basis( dir, tx, ty );
@@ -178,6 +179,8 @@ static void vg_line_arrow( line_co co, line_co dir, float size, u32 colour ){
 }
 
 static void vg_line_box_verts( boxf box, v3f verts[8] ){
+   if( !vg_lines.enabled ) return;
+
    for( u32 i=0; i<8; i++ ){
       for( u32 j=0; j<3; j++ ){
          verts[i][j] = i&(0x1<<j)? box[1][j]: box[0][j];
@@ -187,12 +190,16 @@ static void vg_line_box_verts( boxf box, v3f verts[8] ){
 
 static void vg_line_mesh( v3f verts[], u32 indices[][2], u32 indice_count,
                              u32 colour ){
+   if( !vg_lines.enabled ) return;
+
    for( u32 i=0; i<indice_count; i++ ){
       vg_line( verts[indices[i][0]], verts[indices[i][1]], colour );
    }
 }
 
 static void vg_line_boxf( boxf box, u32 colour ){
+   if( !vg_lines.enabled ) return;
+
    v3f verts[8];
    vg_line_box_verts( box, verts );
    u32 indices[][2] = {{0,1},{1,3},{3,2},{2,0},
@@ -203,6 +210,8 @@ static void vg_line_boxf( boxf box, u32 colour ){
 }
 
 static void vg_line_boxf_transformed( m4x3f m, boxf box, u32 colour ){
+   if( !vg_lines.enabled ) return;
+
    v3f verts[8];
    vg_line_box_verts( box, verts );
 
@@ -218,6 +227,8 @@ static void vg_line_boxf_transformed( m4x3f m, boxf box, u32 colour ){
 }
 
 static void vg_line_cross(v3f pos,u32 colour, float scale){
+   if( !vg_lines.enabled ) return;
+
    v3f p0, p1;
    v3_add( (v3f){ scale,0.0f,0.0f}, pos, p0 );
    v3_add( (v3f){-scale,0.0f,0.0f}, pos, p1 );
@@ -231,6 +242,8 @@ static void vg_line_cross(v3f pos,u32 colour, float scale){
 }
 
 static void vg_line_point( v3f pt, float size, u32 colour ){
+   if( !vg_lines.enabled ) return;
+
    boxf box =
    {
       { pt[0]-size, pt[1]-size, pt[2]-size },
@@ -242,6 +255,8 @@ static void vg_line_point( v3f pt, float size, u32 colour ){
 
 
 static void vg_line_sphere( m4x3f m, float radius, u32 colour ){
+   if( !vg_lines.enabled ) return;
+
    v3f ly = { 0.0f, 0.0f, radius },
        lx = { 0.0f, radius, 0.0f },
        lz = { 0.0f, 0.0f, radius };
@@ -274,6 +289,8 @@ static void vg_line_sphere( m4x3f m, float radius, u32 colour ){
 }
 
 static void vg_line_capsule( m4x3f m, float radius, float h, u32 colour ){
+   if( !vg_lines.enabled ) return;
+   
    v3f ly = { 0.0f, 0.0f, radius },
        lx = { 0.0f, radius, 0.0f },
        lz = { 0.0f, 0.0f, radius };
diff --git a/vg_m.h b/vg_m.h
index 436ca63bbd5bf553cc9e39118b7e26cbcad7a1de..3d4f53ffd53eca654a003663be5a014ea43238d3 100644 (file)
--- a/vg_m.h
+++ b/vg_m.h
@@ -1505,6 +1505,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
diff --git a/vg_rigidbody_view.h b/vg_rigidbody_view.h
new file mode 100644 (file)
index 0000000..578060e
--- /dev/null
@@ -0,0 +1,319 @@
+#pragma once
+#include "vg_rigidbody.h"
+
+static struct vg_shader _shader_rigidbody = {
+   .name = "[vg] rigidbody",
+   .link = NULL,
+   .vs = {
+      .orig_file = NULL,
+      .static_src = 
+
+       "uniform mat4 uPv;"
+   "uniform mat4x3 uMdl;"
+   "uniform mat4x3 uMdl1;"
+       "layout (location=0) in vec4 a_co;"
+       "layout (location=1) in vec3 a_norm;"
+   "out vec3 aNorm;"
+   "out vec3 aCo;"
+       ""
+       "void main()"
+       "{"
+      "vec3 world_pos0 = uMdl  * vec4( a_co.xyz, 1.0 );"
+      "vec3 world_pos1 = uMdl1 * vec4( a_co.xyz, 1.0 );"
+      "vec3 co = mix( world_pos0, world_pos1, a_co.w );"
+          "vec4 vert_pos = uPv * vec4( co, 1.0 );"
+
+          "gl_Position = vert_pos;"
+      "vec3 l = vec3(length(uMdl[0]),length(uMdl[1]),length(uMdl[2]));"
+      "aNorm = (mat3(uMdl) * a_norm)/l;"
+      "aCo = a_co.xyz*l;"
+       "}"
+   },
+   .fs = {
+      .orig_file = NULL,
+      .static_src = 
+
+       "out vec4 FragColor;"
+   "uniform vec4 uColour;"
+       ""
+   "in vec3 aNorm;"
+   "in vec3 aCo;"
+   // The MIT License
+   // Copyright © 2017 Inigo Quilez
+   // Permission is hereby granted, free of charge, to any person obtaining a 
+   // copy of this software and associated documentation files (the "Software"),
+   // to deal in the Software without restriction, including without limitation
+   // the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+   // and/or sell copies of the Software, and to permit persons to whom the 
+   // Software is furnished to do so, subject to the following conditions: 
+   // The above copyright notice and this permission notice shall be included in
+   // all copies or substantial portions of the Software. THE SOFTWARE IS 
+   // PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
+   // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
+   // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+   // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+   // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+   // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+   // DEALINGS IN THE SOFTWARE.
+
+   // Info: https://iquilezles.org/articles/filterableprocedurals
+   //  
+   // More filtered patterns:  https://www.shadertoy.com/playlist/l3KXR1
+
+   "vec3 tri( in vec3 x ){"
+      "return 1.0-abs(2.0*fract(x/2.0)-1.0);"
+   "}"
+
+   "float checkersTextureGrad( in vec3 p, in vec3 ddx, in vec3 ddy ){"
+     "vec3 w = max(abs(ddx), abs(ddy)) + 0.0001;" // filter kernel
+     "vec3 i = (tri(p+0.5*w)-tri(p-0.5*w))/w;"    // analytical integral 
+                                                  //    (box filter)
+     "return 0.5 - 0.5*i.x*i.y*i.z;"              // xor pattern
+   "}"
+       ""
+       "void main()"
+       "{"
+      "vec3 uvw = aCo;"
+      "vec3 ddx_uvw = dFdx( uvw );"
+      "vec3 ddy_uvw = dFdy( uvw );"
+      "float diffuse = checkersTextureGrad( uvw, ddx_uvw, ddy_uvw )*0.5+0.4;"
+      "float light = dot( vec3(0.8017,0.5345,-0.2672), aNorm )*0.5 + 0.5;"
+          "FragColor = light * diffuse * uColour;"
+       "}"
+   }
+};
+
+#pragma pack(push,1)
+struct rb_view_vert {
+   v4f co;
+   v3f n;
+};
+#pragma pack(pop)
+
+typedef struct rb_view_vert rb_view_vert;
+
+struct {
+   GLuint vao, vbo, ebo;
+
+   u32 sphere_start, sphere_count,
+       box_start, box_count;
+}
+static vg_rb_view;
+
+struct vg_rb_mesh_init {
+   u32 verts_size, tris_size;
+   rb_view_vert *verts;
+   u16 *tris;
+};
+
+static void async_vg_rb_view_init( void *payload, u32 payload_size ){
+   struct vg_rb_mesh_init *inf = payload;
+
+   glGenVertexArrays( 1, &vg_rb_view.vao );
+   glGenBuffers( 1, &vg_rb_view.vbo );
+   glGenBuffers( 1, &vg_rb_view.ebo );
+   glBindVertexArray( vg_rb_view.vao );
+
+
+   glBindBuffer( GL_ARRAY_BUFFER, vg_rb_view.vbo );
+   glBufferData( GL_ARRAY_BUFFER, inf->verts_size, inf->verts, GL_STATIC_DRAW );
+   glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_rb_view.ebo );
+   glBufferData( GL_ELEMENT_ARRAY_BUFFER, 
+                  inf->tris_size, inf->tris, GL_STATIC_DRAW );
+
+   /* 0: coordinates */
+   size_t stride = sizeof(rb_view_vert);
+   glVertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, stride, (void*)0 );
+   glEnableVertexAttribArray( 0 );
+
+   /* 1: normal */
+   glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 
+                          stride, (void *)offsetof(rb_view_vert, n) );
+   glEnableVertexAttribArray( 1 );
+
+   VG_CHECK_GL_ERR();
+}
+
+static void vg_rb_view_init(void){
+   vg_shader_register( &_shader_rigidbody );
+
+   u32 H = 20,
+       V = 16,
+       verts_count = 0,
+       tris_count = 0;
+
+   /* box */
+   verts_count += 4*6;
+   tris_count += 2*6;
+   vg_rb_view.box_count = 2*6;
+   vg_rb_view.box_start = 0;
+
+   /* sphere */
+   verts_count += H*(V-2) + 2;
+   tris_count  += H*2 + (V-2)*(H*2);
+   vg_rb_view.sphere_count = H*2 + (V-2)*(H*2);
+
+   u32 hdr_size  = vg_align8( sizeof(struct vg_rb_mesh_init) ),
+       vert_size = vg_align8( verts_count * sizeof(rb_view_vert) ),
+       tris_size = vg_align8( tris_count * 3 * sizeof(u16) );
+
+   vg_async_item *call = vg_async_alloc( hdr_size + vert_size + tris_size );
+
+   struct vg_rb_mesh_init *inf = call->payload;
+   rb_view_vert *verts = ((void *)inf) + hdr_size;
+   u16 *tris = ((void *)inf) + hdr_size + vert_size;
+
+   inf->verts = verts;
+   inf->tris = tris;
+   inf->verts_size = vert_size;
+   inf->tris_size = tris_size;
+
+   u32 tri_index = 0,
+       vert_index = 0;
+
+   /* box 
+    * ----------------------------------------------------------- */
+   for( u32 i=0; i<6; i ++ ){
+      v3f n = {i%3==0,i%3==1,i%3==2};
+      if( i >= 3 ) v3_negate( n, n );
+      v3f v0, v1;
+      v3_tangent_basis( n, v0, v1 );
+
+      rb_view_vert *vs = &verts[vert_index];
+      vert_index += 4;
+
+      for( u32 j=0; j<4; j ++ ){
+         v3_copy( n, vs[j].n );
+         v3_muladds( n, v0, j&0x1?1.0f:-1.0f, vs[j].co );
+         v3_muladds( vs[j].co, v1, j&0x2?1.0f:-1.0f, vs[j].co );
+         vs[j].co[3] = 0.0f;
+      }
+
+      tris[tri_index*3+0] = i*4+0;
+      tris[tri_index*3+1] = i*4+1;
+      tris[tri_index*3+2] = i*4+3;
+      tris[tri_index*3+3] = i*4+0;
+      tris[tri_index*3+4] = i*4+3;
+      tris[tri_index*3+5] = i*4+2;
+      tri_index += 2;
+   }
+
+   /* sphere / capsule 
+    * ----------------------------------------------------------- */
+   u32 base = vert_index;
+   vg_rb_view.sphere_start = tri_index;
+   v4_copy( (v4f){0,-1,0,0}, verts[vert_index].co );
+   v3_copy( (v3f){0,-1,0}, verts[vert_index ++].n );
+
+   for( u32 x=0; x<H; x ++ ){
+      tris[tri_index*3+0] = base+0;
+      tris[tri_index*3+1] = base+1+x;
+      tris[tri_index*3+2] = base+1+((x+1)%H);
+      tri_index += 1;
+   }
+
+   for( u32 y=1; y<V-1; y ++ ){
+      f32 ty = ((f32)y/(f32)(V-1)) * VG_PIf;
+      u32 ybase = y-1;
+      for( u32 x=0; x<H; x ++ ){
+         f32 tx = ((f32)x/(f32)H) * VG_TAUf;
+
+         v4f co = { cosf(tx)*sinf(ty), -cosf(ty), sinf(tx)*sinf(ty), y>=(V/2) };
+         v4_copy( co, verts[vert_index].co );
+         v4_copy( co, verts[vert_index ++].n );
+
+         if( y < V-2 ){
+            tris[tri_index*3+0] = base+1 + ybase*H + x;
+            tris[tri_index*3+1] = base+1 + (ybase+1)*H + ((x+1)%H);
+            tris[tri_index*3+2] = base+1 + ybase*H + ((x+1)%H);
+            tris[tri_index*3+3] = base+1 + ybase*H + x;
+            tris[tri_index*3+4] = base+1 + (ybase+1)*H + x;
+            tris[tri_index*3+5] = base+1 + (ybase+1)*H + ((x+1)%H);
+            tri_index += 2;
+         }
+      }
+   }
+
+   v4_copy( (v4f){0, 1,0,1}, verts[vert_index].co );
+   v3_copy( (v3f){0, 1,0}, verts[vert_index ++].n );
+
+   for( u32 x=0; x<H; x ++ ){
+      tris[tri_index*3+0] = base + (H*(V-2) + 2)-1;
+      tris[tri_index*3+1] = base+1 + (V-3)*H+((x+1)%H);
+      tris[tri_index*3+2] = base+1 + (V-3)*H+x;
+      tri_index += 1;
+   }
+
+   vg_async_dispatch( call, async_vg_rb_view_init );
+}
+
+static void vg_rb_view_bind(void){
+   glEnable( GL_CULL_FACE );
+   glEnable( GL_DEPTH_TEST );
+
+       glUseProgram( _shader_rigidbody.id );
+   glUniformMatrix4fv( glGetUniformLocation( _shader_rigidbody.id, "uPv" ), 
+     1, GL_FALSE, (float *)vg.pv );
+
+       glBindVertexArray( vg_rb_view.vao );
+}
+
+static void vg_rb_view_box( m4x3f mdl, boxf bbx, v4f colour ){
+
+   v3f e;
+   v3_sub( bbx[1], bbx[0], e );
+   v3_muls( e, 0.5f, e );
+
+   m4x3f mmdl;
+   m4x3_identity( mmdl );
+   m3x3_scale( mmdl, e );
+   v3_add( bbx[0], e, mmdl[3] );
+   m4x3_mul( mdl, mmdl, mmdl );
+
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
+                 colour );
+       glDrawElements( GL_TRIANGLES, 
+                   vg_rb_view.box_count*3, GL_UNSIGNED_SHORT, 
+                   (void *)(vg_rb_view.box_start*3*sizeof(u16)) );
+}
+
+static void vg_rb_view_sphere( m4x3f mdl, f32 r, v4f colour ){
+   m4x3f mmdl;
+   m4x3_copy( mdl, mmdl );
+   m3x3_scalef( mmdl, r );
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
+                         1,GL_FALSE,(float*)mmdl);
+   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
+                 colour );
+       glDrawElements( GL_TRIANGLES, 
+                   vg_rb_view.sphere_count*3, GL_UNSIGNED_SHORT, 
+                   (void *)(vg_rb_view.sphere_start*3*sizeof(u16)) );
+}
+
+static void vg_rb_view_capsule( m4x3f mdl, f32 r, f32 h, v4f colour ){
+   m4x3f mmdl0, mmdl1;
+   m4x3_identity( mmdl0 );
+   m4x3_identity( mmdl1 );
+   m3x3_scalef( mmdl0, r );
+   m3x3_scalef( mmdl1, r );
+   mmdl0[3][1] = -h-r;
+   mmdl1[3][1] =  h+r;
+   m4x3_mul( mdl, mmdl0, mmdl0 );
+   m4x3_mul( mdl, mmdl1, mmdl1 );
+
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl" ), 
+                         1,GL_FALSE,(float*)mmdl0);
+   glUniformMatrix4x3fv( glGetUniformLocation( _shader_rigidbody.id, "uMdl1" ), 
+                         1,GL_FALSE,(float*)mmdl1);
+   glUniform4fv( glGetUniformLocation( _shader_rigidbody.id, "uColour" ), 1,
+                 colour );
+       glDrawElements( GL_TRIANGLES, 
+                   vg_rb_view.sphere_count*3, GL_UNSIGNED_SHORT, 
+                   (void *)(vg_rb_view.sphere_start*3*sizeof(u16)) );
+}