+ int in_trigger = 0;
+ for( int i=0; i<world.trigger_count; i++ )
+ {
+ struct trigger_zone *zone = &world.triggers[i];
+
+ v3f local;
+ m4x3_mulv( zone->inv_transform, pos, local );
+
+ if( (fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f) )
+ {
+ in_trigger = 1;
+
+ if( !world.in_trigger )
+ {
+ world_run_relay( &zone->target );
+ }
+ }
+
+ vg_line_boxf_transformed( zone->transform, (boxf){{-1.0f,-1.0f,-1.0f},
+ { 1.0f, 1.0f, 1.0f}},
+ 0xff00ff00 );
+ }
+
+ world.in_trigger = in_trigger;
+ sfd_update();
+}
+
+/*
+ * -----------------------------------------------------------------------------
+ * API implementation
+ * -----------------------------------------------------------------------------
+ */
+
+VG_STATIC void ray_world_get_tri( ray_hit *hit, v3f tri[3] )
+{
+ for( int i=0; i<3; i++ )
+ v3_copy( world.scene_geo->arrvertices[ hit->tri[i] ].co, tri[i] );
+}
+
+VG_STATIC int ray_world( v3f pos, v3f dir, ray_hit *hit )
+{
+ return scene_raycast( world.scene_geo, world.geo_bh, pos, dir, hit );
+}
+
+/*
+ * Cast a sphere from a to b and see what time it hits
+ */
+VG_STATIC int spherecast_world( v3f pa, v3f pb, float r, float *t, v3f n )
+{
+ bh_iter it;
+ bh_iter_init( 0, &it );
+
+ boxf region;
+ box_init_inf( region );
+ box_addpt( region, pa );
+ box_addpt( region, pb );
+
+ v3_add( (v3f){ r, r, r}, region[1], region[1] );
+ v3_add( (v3f){-r,-r,-r}, region[0], region[0] );
+
+ v3f dir;
+ v3_sub( pb, pa, dir );
+
+ v3f dir_inv;
+ dir_inv[0] = 1.0f/dir[0];
+ dir_inv[1] = 1.0f/dir[1];
+ dir_inv[2] = 1.0f/dir[2];
+
+ int hit = -1;
+ float min_t = 1.0f;
+
+ int idx;
+ while( bh_next( world.geo_bh, &it, region, &idx ) )
+ {
+ u32 *ptri = &world.scene_geo->arrindices[ idx*3 ];
+ v3f tri[3];
+
+ boxf box;
+ box_init_inf( box );
+
+ for( int j=0; j<3; j++ )
+ {
+ v3_copy( world.scene_geo->arrvertices[ptri[j]].co, tri[j] );
+ box_addpt( box, tri[j] );
+ }
+
+ v3_add( (v3f){ r, r, r}, box[1], box[1] );
+ v3_add( (v3f){-r,-r,-r}, box[0], box[0] );
+
+ if( !ray_aabb1( box, pa, dir_inv, 1.0f ) )
+ continue;
+
+ float t;
+ v3f n1;
+ if( spherecast_triangle( tri, pa, dir, r, &t, n1 ) )
+ {
+ if( t < min_t )
+ {
+ min_t = t;
+ hit = idx;
+ v3_copy( n1, n );
+ }
+ }
+ }
+
+ *t = min_t;
+ return hit;
+}
+
+VG_STATIC struct world_material *world_tri_index_material( u32 index )
+{
+ for( int i=1; i<world.material_count; i++ )
+ {
+ struct world_material *mat = &world.materials[i];
+
+ if( (index >= mat->sm_geo.vertex_start) &&
+ (index < mat->sm_geo.vertex_start+mat->sm_geo.vertex_count ) )
+ {
+ return mat;
+ }
+ }
+
+ /* error material */
+ return &world.materials[0];
+}
+
+VG_STATIC struct world_material *world_contact_material( rb_ct *ct )
+{
+ return world_tri_index_material( ct->element_id );
+}
+
+VG_STATIC struct world_material *ray_hit_material( ray_hit *hit )
+{
+ return world_tri_index_material( hit->tri[0] );