traffic
authorhgn <hgodden00@gmail.com>
Sat, 22 Apr 2023 14:53:39 +0000 (15:53 +0100)
committerhgn <hgodden00@gmail.com>
Sat, 22 Apr 2023 14:53:39 +0000 (15:53 +0100)
18 files changed:
blender_export.py
entity.h
maps_src/mp_gridmap.mdl
maps_src/mp_mtzero.mdl
maps_src/mp_spawn.mdl
model.h
models_src/board_fish.mdl
models_src/ch_jordan.mdl
models_src/ch_new.mdl
models_src/ch_outlaw.mdl
models_src/rs_font.mdl
models_src/rs_gate.mdl
models_src/rs_menu.mdl
models_src/rs_scoretext.mdl
models_src/rs_skydome.mdl
world.h
world_gen.h
world_render.h

index a7698e9bf2fa6f33334d1d2d31de574286b6c873..62112041a8683776a10905957f505cfc65aba0c5 100644 (file)
@@ -68,7 +68,8 @@ class mdl_submesh(Structure):
                ("vertex_start",c_uint32),
                ("vertex_count",c_uint32),
                ("bbx",(c_float*3)*2),
-               ("material_id",c_uint32)]        # index into the material array
+               ("material_id",c_uint16), # index into the material array
+               ("flags",c_uint16)]
 #}
 
 class mdl_material(Structure):
@@ -329,6 +330,18 @@ class ent_font(Structure):
                ("glyph_utf32_base",c_uint32)]
 #}
 
+class ent_traffic(Structure):
+#{
+   _fields_ = [("transform",mdl_transform),
+               ("submesh_start",c_uint32),
+               ("submesh_count",c_uint32),
+               ("start_node",c_uint32),
+               ("node_count",c_uint32),
+               ("speed",c_float),
+               ("t",c_float),
+               ("index",c_uint32)]
+#}
+
 def obj_ent_type( obj ):
 #{
    if obj.type == 'ARMATURE': return 'mdl_armature'
@@ -664,25 +677,16 @@ def sr_armature_bones( armature ):
          yield from _recurse_bone( b )
 #}
 
-def sr_compile_mesh( obj ):
+# Returns submesh_start,count and armature_id
+def sr_compile_mesh_internal( obj ):
 #{
-   node=mdl_mesh()
-   compile_obj_transform(obj, node.transform)
-   node.pstr_name = sr_compile_string(obj.name)
-   ent_type = obj_ent_type( obj )
-
-   node.entity_id = 0
-
-   if ent_type != 'none':#{
-      ent_id_lwr = sr_compile.entity_ids[obj.name]
-      ent_id_upr = get_entity_enum_id( obj_ent_type(obj) )
-      node.entity_id = (ent_id_upr << 16) | ent_id_lwr
-   #}
-   print( node.entity_id )
-
    can_use_cache = True
    armature = None
 
+   submesh_start = 0
+   submesh_count = 0
+   armature_id = 0
+
    for mod in obj.modifiers:#{
       if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
          mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
@@ -695,7 +699,7 @@ def sr_compile_mesh( obj ):
          armature = mod.object
          rig_weight_groups = \
                ['0 [ROOT]']+[_.name for _ in sr_armature_bones(mod.object)]
-         node.armature_id = sr_compile.entity_ids[armature.name]
+         armature_id = sr_compile.entity_ids[armature.name]
 
          POSE_OR_REST_CACHE = armature.data.pose_position
          armature.data.pose_position = 'REST'
@@ -706,16 +710,15 @@ def sr_compile_mesh( obj ):
    #
    if can_use_cache and (obj.data.name in sr_compile.mesh_cache):#{
       ref = sr_compile.mesh_cache[obj.data.name]
-      node.submesh_start = ref[0]
-      node.submesh_count = ref[1]
-      sr_compile.mesh_data.extend(bytearray(node))
-      return
+      submesh_start = ref[0]
+      submesh_count = ref[1]
+      return (submesh_start,submesh_count,armature_id)
    #}
 
    # Compile a whole new mesh
    #
-   node.submesh_start = len(sr_compile.submesh_data)//sizeof(mdl_submesh)
-   node.submesh_count = 0
+   submesh_start = len(sr_compile.submesh_data)//sizeof(mdl_submesh)
+   submesh_count = 0
 
    dgraph = bpy.context.evaluated_depsgraph_get()
    data = obj.evaluated_get(dgraph).data
@@ -899,16 +902,37 @@ def sr_compile_mesh( obj ):
       # Add submesh to encoder
       #
       sr_compile.submesh_data.extend( bytearray(sm) )
-      node.submesh_count += 1
+      submesh_count += 1
    #}
 
    if armature:#{
       armature.data.pose_position = POSE_OR_REST_CACHE
    #}
 
-   # Save a reference to this node since we want to reuse the submesh indices
+   # Save a reference to this mesh since we want to reuse the submesh indices
    # later.
-   sr_compile.mesh_cache[obj.data.name]=(node.submesh_start,node.submesh_count)
+   sr_compile.mesh_cache[obj.data.name]=(submesh_start,submesh_count)
+   return (submesh_start,submesh_count,armature_id)
+#}
+
+def sr_compile_mesh( obj ):
+#{
+   node=mdl_mesh()
+   compile_obj_transform(obj, node.transform)
+   node.pstr_name = sr_compile_string(obj.name)
+   ent_type = obj_ent_type( obj )
+
+   node.entity_id = 0
+
+   if ent_type != 'none':#{
+      ent_id_lwr = sr_compile.entity_ids[obj.name]
+      ent_id_upr = get_entity_enum_id( obj_ent_type(obj) )
+      node.entity_id = (ent_id_upr << 16) | ent_id_lwr
+   #}
+   
+   node.submesh_start, node.submesh_count, node.armature_id = \
+         sr_compile_mesh_internal( obj )
+
    sr_compile.mesh_data.extend(bytearray(node))
 #}
 
@@ -1274,7 +1298,9 @@ def sr_compile( collection ):
 
    mesh_count = 0
    for obj in collection.all_objects: #{
-      if obj.type == 'MESH': mesh_count += 1
+      if obj.type == 'MESH':#{
+         mesh_count += 1
+      #}
 
       ent_type = obj_ent_type( obj )
       if ent_type == 'none': continue
@@ -1289,6 +1315,14 @@ def sr_compile( collection ):
    for obj in collection.all_objects:#{
       if obj.type == 'MESH':#{
          i+=1
+
+         ent_type = obj_ent_type( obj )
+
+         # entity ignore mesh list
+         #
+         if ent_type == 'ent_traffic': continue
+         #--------------------------
+
          print( F'[SR] {i: 3}/{mesh_count} {obj.name:<40}', end='\r' )
          sr_compile_mesh( obj )
       #}
@@ -1468,6 +1502,7 @@ def sr_compile( collection ):
       route_gates = []
       route_curves = []
       routes = []
+      traffics = []
 
       for obj in col.objects:#{
          if obj.type == 'ARMATURE': pass
@@ -1483,6 +1518,8 @@ def sr_compile( collection ):
             #}
             elif ent_type == 'ent_route':
                routes += [obj]
+            elif ent_type == 'ent_traffic':
+               traffics += [obj]
          #}
       #}
 
@@ -1541,6 +1578,52 @@ def sr_compile( collection ):
          sr_ent_push( route )
       #}
 
+      for obj in traffics:#{
+         traffic = ent_traffic()
+         compile_obj_transform( obj, traffic.transform )
+         traffic.submesh_start, traffic.submesh_count, _ = \
+               sr_compile_mesh_internal( obj )
+
+         # find best subsection
+         
+         graph_keys = list(dij.graph)
+         min_dist = 100.0
+         best_point = 0
+
+         for j in range(len(dij.points)):#{
+            point = dij.points[j]
+            dist = (point-obj.location).magnitude
+
+            if dist < min_dist:#{
+               min_dist = dist
+               best_point = j
+            #}
+         #}
+
+         # scan to each edge
+         best_begin = best_point
+         best_end = best_point
+
+         while True:#{
+            map0 = dij.subsections[best_begin]
+            if map0[1] == -1: break
+            best_begin = map0[1]
+         #}
+         while True:#{
+            map1 = dij.subsections[best_end]
+            if map1[2] == -1: break
+            best_end = map1[2]
+         #}
+
+         traffic.start_node = routenode_count + best_begin
+         traffic.node_count = best_end - best_begin
+         traffic.index = best_point - best_begin
+         traffic.speed = obj.SR_data.ent_traffic[0].speed
+         traffic.t = 0.0
+
+         sr_ent_push(traffic)
+      #}
+
       for point in dij.points:#{
          rn = ent_route_node()
          rn.co[0] =  point[0]
@@ -1552,7 +1635,6 @@ def sr_compile( collection ):
       routenode_count += len(dij.points)
    #}
 
-
    print( F"[SR] Writing file" )
 
    file_array_instructions = {}
@@ -1595,7 +1677,7 @@ def sr_compile( collection ):
 
    fp = open( path, "wb" )
    header = mdl_header()
-   header.version = 40
+   header.version = 100
    sr_array_title( header.arrays, \
                    'index', len(file_array_instructions), \
                    sizeof(mdl_array), header_size )
@@ -2492,9 +2574,7 @@ class SR_OBJECT_ENT_FONT(bpy.types.PropertyGroup):
 
 class SR_OBJECT_ENT_TRAFFIC(bpy.types.PropertyGroup):
 #{
-   track: bpy.props.PointerProperty(\
-               type=bpy.types.Object, name='track', \
-               poll=lambda self,obj: sr_filter_ent_type(obj,['ent_route_node']))
+   speed: bpy.props.FloatProperty(default=1.0)
 #}
 
 class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
index 25fd63ff55a85134ea295c5ed4c2b95b92a32b52..7d6fabfbdeaf909e71a12d7abf53a205c4e2a7c8 100644 (file)
--- a/entity.h
+++ b/entity.h
@@ -18,6 +18,7 @@ typedef struct ent_volume ent_volume;
 typedef struct ent_audio ent_audio;
 typedef struct ent_index ent_index;
 typedef struct ent_marker ent_marker;
+typedef struct ent_traffic ent_traffic;
 typedef struct ent_font ent_font;
 typedef struct ent_font_variant ent_font_variant;
 typedef struct ent_glyph ent_glyph;
@@ -208,6 +209,17 @@ struct ent_marker{
    u32 pstr_alias;
 };
 
+struct ent_traffic{
+   mdl_transform transform;
+   u32 submesh_start,
+       submesh_count,
+       start_node,
+       node_count;
+   float speed,
+         t;
+   u32 index;     /* into the path */
+};
+
 VG_STATIC ent_marker *ent_find_marker( mdl_context *mdl,
                                        mdl_array_ptr *arr, const char *alias )
 {
index ffe38161df72503a273b3f3c9b7d2f69e84dd9b3..557de7d79567e0d79e47df990d847786c5ae5ddb 100644 (file)
Binary files a/maps_src/mp_gridmap.mdl and b/maps_src/mp_gridmap.mdl differ
index 9eb41d7e29c9581fa5545ebe62d11248bccf4364..f5de1f762a27ca73bc31b7880c92786b092c7cbc 100644 (file)
Binary files a/maps_src/mp_mtzero.mdl and b/maps_src/mp_mtzero.mdl differ
index 8df5081721f76e4eea8cb7bdeed79cdf25dc5dc5..eb65fa960b085bdaca973320c93e27c4f950946e 100644 (file)
Binary files a/maps_src/mp_spawn.mdl and b/maps_src/mp_spawn.mdl differ
diff --git a/model.h b/model.h
index fffd26d111228c8661989f7291208afec53d5b48..81241987727e80b1acee1cad7482cb4948fa992d 100644 (file)
--- a/model.h
+++ b/model.h
@@ -7,6 +7,8 @@
 
 #include "common.h"
 
+#define MDL_VERSION_NR 100
+
 enum mdl_shader
 {
    k_shader_standard                = 0,
@@ -29,12 +31,12 @@ enum mdl_surface_prop
 
 enum material_flag
 {
-   k_material_flag_skate_target     = 0x1,
-   k_material_flag_collision        = 0x2,
-   k_material_flag_grow_grass       = 0x4,
-   k_material_flag_grindable        = 0x8,
-   k_material_flag_invisible        = 0x10,
-   k_material_flag_boundary         = 0x20
+   k_material_flag_skate_target     = 0x00000001,
+   k_material_flag_collision        = 0x00000002,
+   k_material_flag_grow_grass       = 0x00000004,
+   k_material_flag_grindable        = 0x00000008,
+   k_material_flag_invisible        = 0x00000010,
+   k_material_flag_boundary         = 0x00000020
 };
 
 #pragma pack(push,1)
@@ -75,17 +77,6 @@ struct mdl_transform
    v4f q;
 };
 
-struct mdl_submesh
-{
-   u32 indice_start,
-       indice_count,
-       vertex_start,
-       vertex_count;
-
-   boxf bbx;
-   u32 material_id;
-};
-
 struct mdl_material
 {
    u32 pstr_name,
@@ -118,9 +109,9 @@ struct mdl_bone
 
 enum bone_flag
 {
-   k_bone_flag_deform               = 0x1,
-   k_bone_flag_ik                   = 0x2,
-   k_bone_flag_cone_constraint      = 0x4
+   k_bone_flag_deform               = 0x00000001,
+   k_bone_flag_ik                   = 0x00000002,
+   k_bone_flag_cone_constraint      = 0x00000004
 };
 
 enum bone_collider
@@ -147,6 +138,23 @@ struct mdl_animation
    u32 offset;
 };
 
+struct mdl_submesh
+{
+   u32 indice_start,
+       indice_count,
+       vertex_start,
+       vertex_count;
+
+   boxf bbx;
+   u16 material_id, flags;
+};
+
+enum esubmesh_flags
+{
+   k_submesh_flag_none     = 0x0000,
+   k_submesh_flag_consumed = 0x0001
+};
+
 struct mdl_mesh
 {
    mdl_transform transform;
@@ -366,6 +374,14 @@ VG_STATIC void mdl_open( mdl_context *mdl, const char *path, void *lin_alloc )
    if( l != 1 )
       mdl_load_fatal_corrupt( mdl );
 
+   if( mdl->info.version != MDL_VERSION_NR ){
+      vg_warn( "For model: %s\n", path );
+      vg_warn( "  version: %u (current: %u)\n", mdl->info.version, 
+               MDL_VERSION_NR );
+
+      vg_fatal_exit_loop( "Legacy model version incompatable" );
+   }
+
    mdl_load_array_file( mdl, &mdl->index, &mdl->info.index, lin_alloc );
 
    mdl_array *pack = mdl_find_array( mdl, "pack" );
index b72141d6e566940e27b324e20bc4bbaafaf4944e..33af54ed0d6e5e4bec8cdf6005bae4800794186b 100644 (file)
Binary files a/models_src/board_fish.mdl and b/models_src/board_fish.mdl differ
index 00fbf4cdc377ac471dc62d1eff2b886e5cd3ac59..c42eba6164df51ed9ea0fc6da201ef2fd51a14ba 100644 (file)
Binary files a/models_src/ch_jordan.mdl and b/models_src/ch_jordan.mdl differ
index 042c4e3f75157f1318b8cfc96ca584ca6da4dc5c..85845bb089a4318964777c2728837f215ace1cf1 100644 (file)
Binary files a/models_src/ch_new.mdl and b/models_src/ch_new.mdl differ
index a761b0a7c3dfeef929d7761381b5716f21649bcf..211e447201a13ed42ebeac031f518fa7be3760ad 100644 (file)
Binary files a/models_src/ch_outlaw.mdl and b/models_src/ch_outlaw.mdl differ
index 1c841179b9e39793ee398c0655d4494ea7f1b177..cd559713985391303eb7ca28ff9afab16aa0c69d 100644 (file)
Binary files a/models_src/rs_font.mdl and b/models_src/rs_font.mdl differ
index 2f0e62ba272fa33422983ac06da64dd60ed0067e..fda4b5e533891f8e8c2b3b0b65d17b2273b6db7d 100644 (file)
Binary files a/models_src/rs_gate.mdl and b/models_src/rs_gate.mdl differ
index 18c6db322b00ab8a34f40df8b7971e97c87e9f74..9c49da5d6a1732ee7e9f7b54d7ac0034ec89c22c 100644 (file)
Binary files a/models_src/rs_menu.mdl and b/models_src/rs_menu.mdl differ
index a85faa689dbebf9f0d5c2691b48d5c67daf2a9ff..bf85fc4a99dfa77d2108fa35dbaf28f6fb69d47d 100644 (file)
Binary files a/models_src/rs_scoretext.mdl and b/models_src/rs_scoretext.mdl differ
index 894246f259c1fdd651e81c7a3f02f189715f8e3c..beb78ea7b68088eeb5eb4eb31c17f220f4c5a5c1 100644 (file)
Binary files a/models_src/rs_skydome.mdl and b/models_src/rs_skydome.mdl differ
diff --git a/world.h b/world.h
index 809397151e854c64b4508ceaf139a8e0e1c40c88..5318f8e326408c6d75624b2ed4ab8f7b38453328 100644 (file)
--- a/world.h
+++ b/world.h
@@ -164,7 +164,8 @@ struct world_instance {
 
                  ent_audio_clip,
                  ent_audio,
-                 ent_volume;
+                 ent_volume,
+                 ent_traffic;
 
    ent_gate *rendering_gate;
 
@@ -547,6 +548,67 @@ VG_STATIC void world_update( world_instance *world, v3f pos )
    world_routes_update_timer_texts( world );
    world_routes_update( world );
    //world_routes_debug( world );
+   
+   /* ---- traffic -------- */
+
+   for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
+      ent_traffic *traffic = mdl_arritm( &world->ent_traffic, i );
+      
+      u32 i1 = traffic->index,
+          i0,
+          i2 = i1+1;
+
+      if( i1 == 0 ) i0 = traffic->node_count-1;
+      else i0 = i1-1;
+
+      if( i2 >= traffic->node_count ) i2 = 0;
+
+      i0 += traffic->start_node;
+      i1 += traffic->start_node;
+      i2 += traffic->start_node;
+      
+      v3f h[3];
+
+      ent_route_node *rn0 = mdl_arritm( &world->ent_route_node, i0 ),
+                     *rn1 = mdl_arritm( &world->ent_route_node, i1 ),
+                     *rn2 = mdl_arritm( &world->ent_route_node, i2 );
+
+      v3_copy( rn1->co, h[1] );
+      v3_lerp( rn0->co, rn1->co, 0.5f, h[0] );
+      v3_lerp( rn1->co, rn2->co, 0.5f, h[2] );
+
+      float const k_sample_dist = 0.0025f;
+      v3f pc, pd;
+      eval_bezier3( h[0], h[1], h[2], traffic->t, pc );
+      eval_bezier3( h[0], h[1], h[2], traffic->t+k_sample_dist, pd );
+
+      v3f v0;
+      v3_sub( pd, pc, v0 );
+      float length = vg_maxf( 0.0001f, v3_length( v0 ) );
+      v3_muls( v0, 1.0f/length, v0 );
+
+      float mod = k_sample_dist / length;
+
+      traffic->t += traffic->speed * vg.time_delta * mod;
+
+      if( traffic->t > 1.0f ){
+         traffic->t -= 1.0f;
+
+         if( traffic->t > 1.0f ) traffic->t = 0.0f;
+
+         traffic->index ++;
+
+         if( traffic->index >= traffic->node_count ) 
+            traffic->index = 0;
+      }
+
+      v3_copy( pc, traffic->transform.co );
+
+      float a = atan2f( -v0[0], v0[2] );
+      q_axis_angle( traffic->transform.q, (v3f){0.0f,1.0f,0.0f}, -a );
+
+      vg_line_pt3( traffic->transform.co, 0.3f, VG__BLUE );
+   }
 
    /* ---- SFD ------------ */
    
index 3f574987d3335f24af95443ca459ec414ed1d331..e8e9293ddcd6401addfbdf28a2177dddeede21b1 100644 (file)
@@ -196,6 +196,43 @@ VG_STATIC void world_generate( world_instance *world )
       scene_copy_slice( world->scene_no_collide, &mat->sm_no_collide );
    }
 
+   /* this FIXME TODO IMPORTANT is going here because need to write down.
+    *
+    * acuire_thread_sync; replace this with a buffer that you fill up with 
+    * opengl loader commands in a seperate memory area. the operation blocks
+    * if the buffer is full, then those instructions get ran on the sync line.
+    * (start of the frame)
+    *
+    * also blocks if the other thread is executing the instructions, obviously.
+    *
+    * this prevents rapid context swaps between threads.
+    *
+    * guessing a 50mb loader buffer approx.
+    *
+    * TODO also: fadeout loading screen!
+    */
+
+   for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
+      ent_traffic *vehc = mdl_arritm( &world->ent_traffic, i );
+
+      for( u32 j=0; j<vehc->submesh_count; j++ ){
+         mdl_submesh *sm = mdl_arritm( &world->meta.submeshs, 
+                                       vehc->submesh_start+j );
+
+         if( sm->flags & k_submesh_flag_consumed ){
+            continue;
+         }
+
+         m4x3f identity;
+         m4x3_identity( identity );
+         scene_add_mdl_submesh( world->scene_no_collide, &world->meta, 
+                                sm, identity );
+
+         scene_copy_slice( world->scene_no_collide, sm );
+         sm->flags |= k_submesh_flag_consumed;
+      }
+   }
+
    /* upload and free that */
    vg_acquire_thread_sync();
    {
@@ -530,6 +567,13 @@ VG_STATIC void world_process_resources( world_instance *world )
    world->textures = vg_linear_alloc( world->heap,
                               vg_align8(sizeof(GLuint)*world->texture_count) );
 
+   /* TODO FIXME IMPORTANT 
+    *
+    *  this is another area that would benefit from our load thread buffer idea.
+    *  could get a stall if lots of textures, since its freading, we're locking
+    *  the frame up from drawing based on that disk read!!! terrible!
+    */
+
    vg_acquire_thread_sync();
    {
       /* error texture */
@@ -813,6 +857,7 @@ VG_STATIC void world_load( u32 index, const char *path )
    mdl_load_array( meta, &world->ent_audio_clip,"ent_audio_clip", heap );
    mdl_load_array( meta, &world->ent_audio,     "ent_audio",      heap );
    mdl_load_array( meta, &world->ent_volume,    "ent_volume",     heap );
+   mdl_load_array( meta, &world->ent_traffic,   "ent_traffic",    heap );
 
    /* process resources from pack */
    world_process_resources( world );
index b1972ba546cf7cafd4774294559c7a39eaabbfc2..f61974e37e791f0b7384b17ef384416086c791b6 100644 (file)
@@ -109,24 +109,26 @@ VG_STATIC void bind_terrain_noise(void)
    vg_tex2d_bind( &tex_terrain_noise, 0 );
 }
 
-typedef void (*func_bind_point)( world_instance *world, 
-                                 struct world_surface *mat );
-
-VG_STATIC void world_render_if( world_instance *world,
-                                enum mdl_shader shader, 
-                                enum geo_type geo_type,
-                                func_bind_point bind_point )
+struct world_pass{
+   camera *cam;
+   enum mdl_shader shader;
+   enum geo_type geo_type;
+
+   void (*fn_bind_textures)( world_instance *world, 
+                             struct world_surface *mat );
+   void (*fn_set_mdl)( m4x3f mdl );
+   void (*fn_set_uPvmPrev)( m4x4f pvm );
+};
+
+VG_STATIC void world_render_if( world_instance *world, struct world_pass *pass )
 {
-   
-   for( int i=0; i<world->surface_count; i++ )
-   {
+   for( int i=0; i<world->surface_count; i++ ){
       struct world_surface *mat = &world->surfaces[i];
 
-      if( mat->info.shader == shader )
-      {
+      if( mat->info.shader == pass->shader ){
          mdl_submesh *sm;
 
-         if( geo_type == k_geo_type_solid )
+         if( pass->geo_type == k_geo_type_solid )
             sm = &mat->sm_geo;
          else
             sm = &mat->sm_no_collide;
@@ -134,28 +136,55 @@ VG_STATIC void world_render_if( world_instance *world,
          if( !sm->indice_count )
             continue;
 
-         bind_point( world, mat );
+         m4x3f mmdl;
+         m4x3_identity( mmdl );
+         pass->fn_set_mdl( mmdl );
+         pass->fn_set_uPvmPrev( pass->cam->mtx_prev.pv );
+
+         pass->fn_bind_textures( world, mat );
          mdl_draw_submesh( sm );
+
+         for( u32 j=0; j<mdl_arrcount( &world->ent_traffic ); j++ ){
+            ent_traffic *traffic = mdl_arritm( &world->ent_traffic, j );
+            
+            for( u32 k=0; k<traffic->submesh_count; k++ ){
+               sm = mdl_arritm( &world->meta.submeshs, 
+                                 traffic->submesh_start+k );
+
+               q_m3x3( traffic->transform.q, mmdl );
+               v3_copy( traffic->transform.co, mmdl[3] );
+
+               m4x4f m4mdl;
+               m4x3_expand( mmdl, m4mdl );
+               m4x4_mul( pass->cam->mtx_prev.pv, m4mdl, m4mdl );
+
+               pass->fn_set_mdl( mmdl );
+               pass->fn_set_uPvmPrev( m4mdl );
+
+               mdl_draw_submesh( sm );
+            }
+         }
       }
    }
 }
 
 VG_STATIC 
-void world_render_both_stages(  world_instance *world,
-                                enum mdl_shader shader,
-                                func_bind_point bind_point )
+void world_render_both_stages( world_instance *world, struct world_pass *pass )
 {
    mesh_bind( &world->mesh_geo );
-   world_render_if( world, shader, k_geo_type_solid, bind_point );
+   pass->geo_type = k_geo_type_solid;
+   world_render_if( world, pass );
 
    glDisable( GL_CULL_FACE );
    mesh_bind( &world->mesh_no_collide );
-   world_render_if( world, shader, k_geo_type_nonsolid, bind_point );
+   pass->geo_type = k_geo_type_nonsolid;
+   world_render_if( world, pass );
    glEnable( GL_CULL_FACE );
 }
 
 VG_STATIC void bindpoint_diffuse_texture1( world_instance *world,
                                            struct world_surface *mat )
+                                         
 {
    glActiveTexture( GL_TEXTURE1 );
    glBindTexture( GL_TEXTURE_2D, world->textures[ mat->info.tex_diffuse ] );
@@ -163,9 +192,6 @@ VG_STATIC void bindpoint_diffuse_texture1( world_instance *world,
 
 VG_STATIC void render_world_vb( world_instance *world, camera *cam )
 {
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-
    shader_scene_vertex_blend_use();
    shader_scene_vertex_blend_uTexGarbage(0);
    shader_scene_vertex_blend_uTexGradients(1);
@@ -180,24 +206,25 @@ VG_STATIC void render_world_vb( world_instance *world, camera *cam )
    vg_tex2d_bind( &tex_terrain_noise, 0 );
 
    shader_scene_vertex_blend_uPv( cam->mtx.pv );
-   shader_scene_vertex_blend_uPvmPrev( cam->mtx_prev.pv );
-   shader_scene_vertex_blend_uMdl( identity_matrix );
    shader_scene_vertex_blend_uCamera( cam->transform[3] );
 
-   world_render_both_stages( world, k_shader_standard_vertex_blend,
-                             bindpoint_diffuse_texture1 );
+   struct world_pass pass = {
+      .shader = k_shader_standard_vertex_blend,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_diffuse_texture1,
+      .fn_set_mdl = shader_scene_vertex_blend_uMdl,
+      .fn_set_uPvmPrev = shader_scene_vertex_blend_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
 }
 
 VG_STATIC void render_world_standard( world_instance *world, camera *cam )
 {
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-
    shader_scene_standard_use();
    shader_scene_standard_uTexGarbage(0);
    shader_scene_standard_uTexMain(1);
    shader_scene_standard_uPv( cam->mtx.pv );
-   shader_scene_standard_uPvmPrev( cam->mtx_prev.pv );
 
    world_link_lighting_ub( world, _shader_scene_standard.id );
    world_bind_position_texture( world, _shader_scene_standard.id, 
@@ -208,24 +235,25 @@ VG_STATIC void render_world_standard( world_instance *world, camera *cam )
                                 _uniform_scene_standard_uLightsIndex, 4 );
 
    bind_terrain_noise();
-
-   shader_scene_standard_uMdl( identity_matrix );
    shader_scene_standard_uCamera( cam->transform[3] );
    
-   world_render_both_stages( world, k_shader_standard,
-                             bindpoint_diffuse_texture1 );
+   struct world_pass pass = {
+      .shader = k_shader_standard,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_diffuse_texture1,
+      .fn_set_mdl = shader_scene_standard_uMdl,
+      .fn_set_uPvmPrev = shader_scene_standard_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
 }
 
 VG_STATIC void render_world_alphatest( world_instance *world, camera *cam )
 {
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-
    shader_scene_standard_alphatest_use();
    shader_scene_standard_alphatest_uTexGarbage(0);
    shader_scene_standard_alphatest_uTexMain(1);
    shader_scene_standard_alphatest_uPv( cam->mtx.pv );
-   shader_scene_standard_alphatest_uPvmPrev( cam->mtx_prev.pv );
 
    world_link_lighting_ub( world, _shader_scene_standard_alphatest.id );
    world_bind_position_texture( world, _shader_scene_standard_alphatest.id, 
@@ -238,13 +266,20 @@ VG_STATIC void render_world_alphatest( world_instance *world, camera *cam )
 
    bind_terrain_noise();
 
-   shader_scene_standard_alphatest_uMdl( identity_matrix );
+   
    shader_scene_standard_alphatest_uCamera( cam->transform[3] );
 
    glDisable(GL_CULL_FACE);
-   
-   world_render_both_stages( world, k_shader_standard_cutout,
-                             bindpoint_diffuse_texture1 );
+
+   struct world_pass pass = {
+      .shader = k_shader_standard_cutout,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_diffuse_texture1,
+      .fn_set_mdl = shader_scene_standard_alphatest_uMdl,
+      .fn_set_uPvmPrev = shader_scene_standard_alphatest_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
 
    glEnable(GL_CULL_FACE);
 }
@@ -261,9 +296,6 @@ VG_STATIC void bindpoint_terrain( world_instance *world,
 
 VG_STATIC void render_terrain( world_instance *world, camera *cam )
 {
-   m4x3f identity_matrix;
-   m4x3_identity( identity_matrix );
-
    shader_scene_terrain_use();
    shader_scene_terrain_uTexGarbage(0);
    shader_scene_terrain_uTexGradients(1);
@@ -279,12 +311,17 @@ VG_STATIC void render_terrain( world_instance *world, camera *cam )
    vg_tex2d_bind( &tex_terrain_noise, 0 );
 
    shader_scene_terrain_uPv( cam->mtx.pv );
-   shader_scene_terrain_uPvmPrev( cam->mtx_prev.pv );
-
-   shader_scene_terrain_uMdl( identity_matrix );
    shader_scene_terrain_uCamera( cam->transform[3] );
 
-   world_render_both_stages( world, k_shader_terrain_blend, bindpoint_terrain );
+   struct world_pass pass = {
+      .shader = k_shader_terrain_blend,
+      .cam = cam,
+      .fn_bind_textures = bindpoint_terrain,
+      .fn_set_mdl = shader_scene_terrain_uMdl,
+      .fn_set_uPvmPrev = shader_scene_terrain_uPvmPrev,
+   };
+
+   world_render_both_stages( world, &pass );
 }
 
 VG_STATIC void render_sky( world_instance *world, camera *cam )
@@ -387,8 +424,7 @@ VG_STATIC void world_prerender( world_instance *world )
    state->g_debug_indices = k_debug_light_indices;
    state->g_light_preview = k_light_preview;
    state->g_debug_complexity = k_debug_light_complexity;
-
-   state->g_time_of_day = vg_fractf( g_time );
+state->g_time_of_day = vg_fractf( g_time );
    state->g_day_phase   = cosf( state->g_time_of_day * VG_PIf * 2.0f );
    state->g_sunset_phase= cosf( state->g_time_of_day * VG_PIf * 4.0f + VG_PIf );