-
flags="-ggdb3 -Wall -fsanitize=address"
while (( "$#" )); do
+// 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 =
int output_set = 0;
char *extension = NULL;
+ char *gameinfo = NULL;
char *arg;
while( csr_argp( argc, argv ) )
goto IL_CSR_EXIT;
}
- api.strings[ api.num_strings ++ ] = arg;
+ api.strings[ api.num_strings ++ ].str = arg;
}
if( (arg = csr_opt_arg( 'o' )) )
if( (arg = csr_opt_arg( 'g' )) )
{
- fs_set_gameinfo( arg );
+ gameinfo = arg;
}
if( (arg = csr_opt_arg( 'r' )) )
" -r 1024 Output resolution\n"
" -o <output> Specify output name/path\n"
" -e <classname> Same as default arg, but instead filters for entity class\n"
+ //" -s <height> 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"
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 );
}
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";
// Drawing
#ifdef CSR_EXECUTABLE
#include "csr32f.h"
-//#include "csrTga.h"
+ #include "csrTga.h"
#endif
#include "csrDraw.h" // Y
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;
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;
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 )
{
// 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 );
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 ];
// 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 ++ )
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 );
}
}
{
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 );
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
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
// ==================================================================================================================
--- /dev/null
+#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;
+ }
+}
#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 );
{
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 );
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 );
}
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
// 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 );
}
{
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;
}
// 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
{
v3f co;
v3f nrm;
- v2f xy;
+ v3f origin;
};
struct vmf_face
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
// 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 );
}
}
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 );
}
}
- return 0;
+ return -1;
}
int vmf_visgroup_match( vdf_node *ent, u32 target )