+
+ m4x3_mulv( transform, triangle[0].co, new_tri[0].co );
+ m4x3_mulv( transform, triangle[1].co, new_tri[1].co );
+ m4x3_mulv( transform, triangle[2].co, new_tri[2].co );
+ m3x3_mulv( normal, triangle[0].nrm, new_tri[0].nrm );
+ m3x3_mulv( normal, triangle[1].nrm, new_tri[1].nrm );
+ m3x3_mulv( normal, triangle[2].nrm, new_tri[2].nrm );
+
+ simple_raster( rt, new_tri );
+ }
+}
+
+void draw_vmf_group( csr_target *rt, vmf_map *map, vdf_node *root, csr_filter *filter, m4x3f prev, m4x3f inst )
+{
+ m4x3f transform = M4X3_IDENTITY;
+ vmf_solid solid;
+ vmf_vert tri[3];
+ vdf_node *ent_solid;
+ boxf trf_bounds;
+
+ u32 group_id = 0;
+ int filter_visgroups = 0, filter_classname = 0, compute_bounds_only = 0;
+
+ if( filter )
+ {
+ if( filter->visgroup )
+ {
+ filter_visgroups = 1;
+ group_id = vmf_visgroup_id( root, filter->visgroup );
+ }
+
+ if( filter->classname )
+ {
+ filter_classname = 1;
+ }
+
+ compute_bounds_only = filter->compute_bounds_only;
+ }
+
+ // Multiply previous transform with instance transform to create basis
+ if( prev )
+ {
+ m4x3_mul( prev, inst, transform );
+ }
+
+ // Gather world brushes
+ solidgen_ctx_init( &solid );
+
+ if( !filter_classname )
+ {
+ vdf_node *world = vdf_next( root, "world", NULL );
+
+ vdf_foreach( world, "solid", brush )
+ {
+ if( filter_visgroups && !vmf_visgroup_match( brush, group_id ) )
+ continue;
+
+ solidgen_push( &solid, brush );
+ }
+ }
+
+ // Actual entity loop
+ m4x3f model;
+
+ vdf_foreach( root, "entity", ent )
+ {
+ if( filter_visgroups && !vmf_visgroup_match( ent, group_id ) )
+ continue;
+
+ if( filter_classname )
+ if( strcmp( kv_get( ent, "classname", "" ), filter->classname ) )
+ continue;
+
+ if( ent->user & VMF_FLAG_IS_PROP )
+ {
+ // Create model transform
+ m4x3_identity( model );
+
+ vmf_entity_transform( ent, model );
+ m4x3_mul( transform, model, model );
+
+ // Draw model
+ mdl_mesh_t *mdl = &map->models[ ent->user1 ].mdl;
+
+ if( compute_bounds_only )
+ {
+ box_copy( mdl->bounds, trf_bounds );
+ m4x3_transform_aabb( model, trf_bounds );
+
+ // Join
+ box_concat( rt->bounds, trf_bounds );
+ }
+ else
+ {
+ for( int i = 0; i < mdl->num_indices/3; i ++ )
+ {
+ for( int j = 0; j < 3; j ++ )
+ {
+ v3_copy( &mdl->vertices[ mdl->indices[ i*3+j ] *8 ], tri[j].co );
+ v3_copy( &mdl->vertices[ mdl->indices[ i*3+j ] *8+3 ], tri[j].nrm );
+ tri[j].xy[0] = 0.f;
+ tri[j].xy[1] = 0.f;
+ }
+
+ csr_draw( rt, tri, 1, model );
+ }
+ }
+ }
+ else if( ent->user & VMF_FLAG_IS_INSTANCE )
+ {
+ m4x3_identity( model );
+ vmf_entity_transform( ent, model );
+
+ draw_vmf_group( rt, map, map->cache[ ent->user1 ].root, filter, transform, model );
+ }
+ else
+ {
+ // Brush entity
+ if( (ent_solid = vdf_next( ent, "solid", NULL )) )
+ {
+ solidgen_push( &solid, ent_solid );
+ }
+ }
+ }
+
+ if( compute_bounds_only )
+ {
+ solidgen_bounds( &solid, trf_bounds );
+ m4x3_transform_aabb( transform, trf_bounds );
+ box_concat( rt->bounds, trf_bounds );
+ }
+ else
+ {
+ // Draw brushes
+ for( int i = 0; i < csr_sb_count( solid.indices )/3; i ++ )
+ {
+ u32 * base = solid.indices + i*3;
+
+ tri[0] = solid.verts[ base[0] ];
+ tri[1] = solid.verts[ base[1] ];
+ tri[2] = solid.verts[ base[2] ];
+
+ csr_draw( rt, tri, 1, transform );
+ }
+ }
+
+ solidgen_ctx_reset( &solid );
+ solidgen_ctx_free( &solid );
+}
+
+void csr_rt_save_buffers( csr_target *rt, const char *basename, const char *subname )
+{
+ char output[ 512 ];
+
+ float *image = (float *)csr_malloc( 1024*1024*sizeof(float)*3 );
+
+ for( int l = 0; l < rt->x; l ++ )
+ {
+ for( int x = 0; x < rt->y; x ++ )
+ {
+ float *dst = &image[ (l*1024+x)*3 ];
+ csr_frag *src = &rt->fragments[ ((1023-l)*1024+x)*rt->num_samples ];
+
+ v3_zero( dst );
+ v3_muls( src[0].co, 1.f/(float)rt->num_samples, dst );
+ v3_muladds( dst, src[1].co, 1.f/(float)rt->num_samples, dst );
+ v3_muladds( dst, src[2].co, 1.f/(float)rt->num_samples, dst );
+ v3_muladds( dst, src[3].co, 1.f/(float)rt->num_samples, dst );
+ }