From dee56773cecd3a165331732008b7c0acf6f13393 Mon Sep 17 00:00:00 2001 From: hgn Date: Fri, 16 Jul 2021 21:10:28 +0100 Subject: [PATCH] tga & normals --- build.sh | 1 - csRadar.c | 37 +++++++++++++++++++++++------- csRadar.h | 40 ++++++++++++++++++++++++++++---- csrDraw.h | 58 ++++++++++++++++++++++++++++++++++++++++++---- csrMath.h | 5 ++++ csrTga.h | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ ext_csr_free.c | 54 ++++++++++++++++++++++++++++--------------- vmf.h | 13 +++++++---- 8 files changed, 228 insertions(+), 42 deletions(-) create mode 100644 csrTga.h diff --git a/build.sh b/build.sh index 5159499..a898a68 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,3 @@ - flags="-ggdb3 -Wall -fsanitize=address" while (( "$#" )); do diff --git a/csRadar.c b/csRadar.c index 1125373..9f05c2b 100644 --- a/csRadar.c +++ b/csRadar.c @@ -1,9 +1,15 @@ +// Copyright (C) 2021 Harry Godden (hgn) + +// Building: +// gcc -rdynamic csRadar.c -o csRadar -lm -ldl +// +// Plugins: +// gcc -fpic -shared -o ext/my_plugin.so my_plugin.c -lm + #define VALVE_IMPLEMENTATION #define CSR_EXECUTABLE #include "csRadar.h" -// gcc -Wall -fsanitize=address csRadar.c -o csRadar -lm - int main( int argc, char *argv[] ) { csr_api api = @@ -17,6 +23,7 @@ int main( int argc, char *argv[] ) int output_set = 0; char *extension = NULL; + char *gameinfo = NULL; char *arg; while( csr_argp( argc, argv ) ) @@ -29,7 +36,7 @@ int main( int argc, char *argv[] ) goto IL_CSR_EXIT; } - api.strings[ api.num_strings ++ ] = arg; + api.strings[ api.num_strings ++ ].str = arg; } if( (arg = csr_opt_arg( 'o' )) ) @@ -42,7 +49,7 @@ int main( int argc, char *argv[] ) if( (arg = csr_opt_arg( 'g' )) ) { - fs_set_gameinfo( arg ); + gameinfo = arg; } if( (arg = csr_opt_arg( 'r' )) ) @@ -108,10 +115,9 @@ int main( int argc, char *argv[] ) " -r 1024 Output resolution\n" " -o Specify output name/path\n" " -e Same as default arg, but instead filters for entity class\n" + //" -s Add a vertical split at this height\n" " --padding=128 When cropping radar, add padding units to border\n" //" --standard-layers Use standard TAR layers/groups\n" - " --write-normals Enable normals as an output stream\n" - " --write-origins Enable entity origins as an output stream\n" " --no-txt Don't create matching radar txt\n" " --multi-sample=RGSS [ none, 2x, rgss, 8r ]\n" " --extension=TAR Use an extension binary instead\n" @@ -126,10 +132,15 @@ int main( int argc, char *argv[] ) if( api.num_strings ) { + if( gameinfo ) + { + fs_set_gameinfo( gameinfo ); + } + // Path handling if( !output_set ) { - strcpy( api.output_path, api.strings[0] ); + strcpy( api.output_path, api.strings[0].str ); csr_stripext( api.output_path ); } @@ -144,9 +155,19 @@ int main( int argc, char *argv[] ) log_info( "output_path: '%s'\n", api.output_path ); log_info( "vmf_name: '%s'\n", api.vmf_name ); - api.map = vmf_init( api.strings[0] ); + api.map = vmf_init( api.strings[0].str ); if( api.map ) { + // Update arg inferred types + api.strings[0].type = k_iftype_vmf; + for( int i = 1; i < api.num_strings; i ++ ) + { + if( vmf_visgroup_id( api.map->root, api.strings[i].str ) != -1 ) + api.strings[i].type = k_iftype_visgroup; + else + api.strings[i].type = k_iftype_classname; + } + if( !extension ) extension = "csRadarFree"; diff --git a/csRadar.h b/csRadar.h index 36c00f8..4192e55 100644 --- a/csRadar.h +++ b/csRadar.h @@ -27,7 +27,7 @@ // Drawing #ifdef CSR_EXECUTABLE #include "csr32f.h" -//#include "csrTga.h" + #include "csrTga.h" #endif #include "csrDraw.h" // Y @@ -36,10 +36,25 @@ static const u32 csr_build = 3; static const u32 csr_api_version = 1; typedef struct csr_api csr_api; +typedef struct csr_opt_str csr_opt_str; + struct csr_api { // Floating arguments from main's argv - char *strings[ 20 ]; + struct csr_opt_str + { + char *str; + + enum inferred_type + { + k_iftype_vmf, + k_iftype_visgroup, + k_iftype_classname + } + type; + } + strings[ 20 ]; + int num_strings; float padding; @@ -49,9 +64,6 @@ struct csr_api char vmf_name[ 128 ]; // Just the base name eg. my_map EMSAA sampling_mode; - int write_normals; - int write_origins; - // Main API interface vmf_map *map; csr_target target; @@ -59,6 +71,24 @@ struct csr_api u32 api_version; }; +void csr_filter_update_from_strings( csr_filter *filter, csr_opt_str *opt ); + +#ifdef CSR_EXECUTABLE +void csr_filter_update_from_strings( csr_filter *filter, csr_opt_str *opt ) +{ + if( opt->type == k_iftype_visgroup ) + { + filter->visgroup = opt->str; + filter->classname = NULL; + } + else + { + filter->classname = opt->str; + filter->visgroup = NULL; + } +} +#endif + #ifndef CSR_EXECUTABLE static int csr_init( csr_api *api ) { diff --git a/csrDraw.h b/csrDraw.h index 97fd8a5..b744c04 100644 --- a/csrDraw.h +++ b/csrDraw.h @@ -44,6 +44,9 @@ void csr_draw( csr_target *rt, vmf_vert *triangles, u32 triangle_count, m4x3f tr // Filter is optional, it can be st to NULL to just render everything. void csr_vmf_render( csr_target *rt, vmf_map *map, vdf_node *root, csr_filter *filter, m4x3f prev, m4x3f inst ); + +void csr_rt_save_tga( csr_target *rt, const char *path, u32 offset, u32 nc ); + // Obsolete void csr_rt_save_buffers( csr_target *rt, const char *basename, const char *subname ); @@ -284,7 +287,7 @@ void simple_raster( csr_target *rt, vmf_vert tri[3] ) for( u32 px = start_x; px <= end_x; px ++ ) { - u32 sample_index = (py * rt->y + px) * rt->num_samples; + u32 sample_index = ((rt->y-py-1)*rt->x+px) * rt->num_samples; void *frag = rt->colour + sample_index*rt->shader->stride; float *depth = &rt->depth[ sample_index ]; @@ -333,8 +336,6 @@ void csr_draw( csr_target *rt, vmf_vert *triangles, u32 triangle_count, m4x3f tr // Derive normal matrix m4x3_to_3x3( transform, normal ); - - // NOTE: This isn't strictly necessary since CS:GO only uses uniform scaling. m3x3_inv_transpose( normal, normal ); for( u32 i = 0; i < triangle_count; i ++ ) @@ -344,10 +345,17 @@ void csr_draw( csr_target *rt, vmf_vert *triangles, u32 triangle_count, m4x3f tr 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 ); + v3_normalize( new_tri[0].nrm ); + v3_normalize( new_tri[1].nrm ); + v3_normalize( new_tri[2].nrm ); + + m4x3_mulv( transform, triangles[0].origin, new_tri[0].origin ); + simple_raster( rt, new_tri ); } } @@ -437,8 +445,7 @@ void csr_vmf_render( csr_target *rt, vmf_map *map, vdf_node *root, csr_filter *f { 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; + v3_zero( tri[j].origin ); } csr_draw( rt, tri, 1, model ); @@ -522,4 +529,45 @@ void csr_rt_save_buffers( csr_target *rt, const char *basename, const char *subn free( image ); } +// Save floating point buffer to tga. Must be in range (0-1) +// Offset and stride are in bytes +void csr_rt_save_tga( csr_target *rt, const char *path, u32 offset, u32 nc ) +{ + u8 *image = (u8 *)csr_malloc( rt->x*rt->y * 4 ); + + float contrib = 255.f/(float)rt->num_samples; + + for( int y = 0; y < rt->y; y ++ ) + { + for( int x = 0; x < rt->x; x ++ ) + { + u32 pixel_index = (y*rt->x + x); + + void *src = rt->colour + offset + pixel_index * rt->num_samples * rt->shader->stride; + u8 *dst = image + pixel_index*4; + + v4f accum = { 0.f, 0.f, 0.f, 0.f }; + + for( int k = 0; k < rt->num_samples; k ++ ) + { + float *src_sample = (float *)(src + k*rt->shader->stride); + + for( int j = 0; j < nc; j ++ ) + { + accum[ j ] += src_sample[ j ] * contrib; + } + } + + // TODO: Clamp this value + dst[0] = accum[0]; + dst[1] = accum[1]; + dst[2] = accum[2]; + dst[3] = accum[3]; + } + } + + csr_tga_write( path, rt->x, rt->y, nc, image ); + free( image ); +} + #endif diff --git a/csrMath.h b/csrMath.h index 1555361..36ad62f 100644 --- a/csrMath.h +++ b/csrMath.h @@ -86,6 +86,11 @@ CSR_INLINE void v2_mul( v2f a, v2f b, v2f d ) d[0] = a[0]*b[0]; d[1] = a[1]*b[1]; } +CSR_INLINE void v2_div( v2f a, v2f b, v2f d ) +{ + d[0] = a[0]/b[0]; d[1] = a[1]/b[1]; +} + // Vector 3 // ================================================================================================================== diff --git a/csrTga.h b/csrTga.h new file mode 100644 index 0000000..c3fbd5b --- /dev/null +++ b/csrTga.h @@ -0,0 +1,62 @@ +#pragma pack(push, 1) +struct tga_header +{ + char idlength; + char colourmaptype; + char datatypecode; + short int colourmaporigin; + short int colourmaplength; + char colourmapdepth; + short int x_origin; + short int y_origin; + short width; + short height; + char bitsperpixel; + char imagedescriptor; +}; +#pragma pack(pop) + +// Requires RGBA data. Can write grayscale (comp=1), RGB (comp=3) or RGBA (comp=4) +int csr_tga_write( const char *path, u32 width, u32 height, int comp, u8 *rgba ) +{ + FILE *fp = fopen( path, "wb" ); + + if( fp ) + { + struct tga_header header = + { + .datatypecode = 2, + .width = width, + .height = height, + .bitsperpixel = comp * 8, + .imagedescriptor = 0x20 + }; + + fwrite( &header, sizeof( struct tga_header ), 1, fp ); + + for( u32 i = 0; i < width * height; i ++ ) + { + u8 *colour = rgba + i*4; + u8 flipped[4]; + + if( comp >= 3 ) + { + flipped[0] = colour[2]; + flipped[1] = colour[1]; + flipped[2] = colour[0]; + flipped[3] = colour[3]; + + colour = flipped; + } + + fwrite( colour, 1, comp, fp ); + } + + fclose( fp ); + return 1; + } + else + { + return 0; + } +} diff --git a/ext_csr_free.c b/ext_csr_free.c index f6b0b85..ffec370 100644 --- a/ext_csr_free.c +++ b/ext_csr_free.c @@ -5,6 +5,9 @@ #include "csRadar.h" +float min_height = 0.f; +v4f remapping_range; + // GBuffer shader void frag_gbuffer( void *dest, vmf_vert tri[3], float bca, float bcb, float bcc ); void frag_gbuffer_clear( void *dest ); @@ -27,17 +30,6 @@ void csr_ext_main( csr_api *api ) { if( !csr_init( api ) ) return; - - // Setup shader variants - if( api->write_origins ) - { - // Use origin fragment variant - } - - if( api->write_normals ) - { - // Increase stride - } csr_create_target( &api->target, api->resolution, api->resolution, api->sampling_mode, &shader_gbuffer ); csr_rt_clear( &api->target ); @@ -47,6 +39,10 @@ void csr_ext_main( csr_api *api ) csr_auto_fit( &api->target, api->padding ); vmf_load_models( api->map ); + min_height = api->target.bounds[0][2]; + v2_copy( api->target.bounds[0], remapping_range ); + v2_sub( api->target.bounds[1], api->target.bounds[0], &remapping_range[2] ); + // Draw everything draw_buffers( api, 0 ); } @@ -69,6 +65,7 @@ void draw_buffers( csr_api *api, int bounds_only ) if( !bounds_only ) { csr_rt_save_buffers( &api->target, api->output_path, "all" ); + csr_rt_save_tga( &api->target, "test_test_test.tga", 3*sizeof(float), 4 ); } } else @@ -76,16 +73,18 @@ void draw_buffers( csr_api *api, int bounds_only ) // Draw groups for( int i = 1; i < api->num_strings; i ++ ) { - filter.visgroup = api->strings[ i ]; + csr_filter_update_from_strings( &filter, &api->strings[i] ); + csr_vmf_render( &api->target, map, map->root, &filter, NULL, NULL ); if( !bounds_only ) { - csr_rt_save_buffers( &api->target, api->output_path, api->strings[i] ); + csr_rt_save_buffers( &api->target, api->output_path, api->strings[i].str ); //csr_rt_save_c32f( ... ); //csr_rt_save_tga( ... ); // tar_write_dds( ... ); + csr_rt_save_tga( &api->target, "test_test_test.tga", 3*sizeof(float), 4 ); csr_rt_clear( &api->target ); } @@ -97,15 +96,34 @@ void frag_gbuffer( void *dest, vmf_vert tri[3], float bca, float bcb, float bcc { float *dest_colour = (float *)dest; - v3_muls( tri[0].co, bca, dest_colour ); - v3_muladds( dest_colour, tri[1].co, bcb, dest_colour ); - v3_muladds( dest_colour, tri[2].co, bcc, dest_colour ); + // Position + v2_sub( tri[0].origin, remapping_range, dest_colour ); + v2_div( dest_colour, &remapping_range[2], dest_colour ); + + dest_colour[2] = tri[0].co[2]*bca + tri[1].co[2]*bcb + tri[2].co[2]*bcc; + + // Normals + v3_muls( tri[0].nrm, bca, dest_colour+3 ); + v3_muladds( dest_colour+3, tri[1].nrm, bcb, dest_colour+3 ); + v3_muladds( dest_colour+3, tri[2].nrm, bcc, dest_colour+3 ); - // TODO: Normal map + v3_muls( dest_colour+3, 0.5f, dest_colour+3 ); + v3_add( (v3f){0.5f,0.5f,0.5f}, dest_colour+3, dest_colour+3 ); + + // Mask + dest_colour[6] = 1.f; } void frag_gbuffer_clear( void *dest ) { float *dest_colour = (float *)dest; - v3_zero( dest_colour ); + dest_colour[0] = 0.f; + dest_colour[1] = 0.f; + dest_colour[2] = min_height; + + dest_colour[3] = 0.5f; + dest_colour[4] = 0.5f; + dest_colour[5] = 1.0f; + + dest_colour[6] = 0.f; } diff --git a/vmf.h b/vmf.h index c5f736b..4fbe023 100644 --- a/vmf.h +++ b/vmf.h @@ -43,7 +43,7 @@ void vmf_load_models( vmf_map *map ); // Create matrix describing this entities transform void vmf_entity_transform( vdf_node *ent, m4x3f mat ); -u32 vmf_visgroup_id( vdf_node *root, const char *name ); +int vmf_visgroup_id( vdf_node *root, const char *name ); int vmf_visgroup_match( vdf_node *ent, u32 target ); // Currently unused @@ -74,7 +74,7 @@ struct vmf_vert { v3f co; v3f nrm; - v2f xy; + v3f origin; }; struct vmf_face @@ -422,7 +422,7 @@ ESolidResult solidgen_push( vmf_solid *ctx, vdf_node *node ) v3_divs( center, (float)numpoints, center ); for( ; vert_start < csr_sb_count( ctx->verts ); vert_start ++ ) { - v2_copy( center, ctx->verts[ vert_start ].xy ); + v3_copy( center, ctx->verts[ vert_start ].origin ); } // Sort each faces and trianglulalate them @@ -522,6 +522,9 @@ ESolidResult solidgen_push( vmf_solid *ctx, vdf_node *node ) // Todo, put correct normal v3_copy( (v3f){ 0.f, 0.f, 1.f }, vert->nrm ); + + // Todo: use real bounds of displaced vertices + v3_copy( center, vert->origin ); } } @@ -817,7 +820,7 @@ void vmf_entity_transform( vdf_node *ent, m4x3f mat ) m4x3_scale( mat, scale ); } -u32 vmf_visgroup_id( vdf_node *root, const char *name ) +int vmf_visgroup_id( vdf_node *root, const char *name ) { vdf_node *dict = vdf_next( root, "visgroups", NULL ); @@ -832,7 +835,7 @@ u32 vmf_visgroup_id( vdf_node *root, const char *name ) } } - return 0; + return -1; } int vmf_visgroup_match( vdf_node *ent, u32 target ) -- 2.25.1