# Batches
cxr_view_lines = None
cxr_view_mesh = None
+cxr_mdl_mesh = None
cxr_jobs_batch = None
cxr_jobs_inf = []
cxr_error_inf = None
# Shaders
cxr_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
+
cxr_ui_shader = gpu.types.GPUShader("""
uniform mat4 ModelViewProjectionMatrix;
uniform float scale;
}
""")
+cxr_mdl_shader = gpu.types.GPUShader("""
+uniform mat4 modelMatrix;
+uniform mat4 viewProjectionMatrix;
+
+in vec3 aPos;
+in vec3 aNormal;
+
+out vec3 lPos;
+out vec3 lNormal;
+
+void main()
+{
+ vec4 pWorldPos = modelMatrix * vec4(aPos, 1.0);
+ vec3 worldPos = pWorldPos.xyz;
+
+ gl_Position = viewProjectionMatrix * pWorldPos;
+ lNormal = aNormal; //mat3(transpose(inverse(modelMatrix))) * aNormal;
+ lPos = worldPos;
+}
+""","""
+
+uniform vec4 colour;
+uniform vec3 testLightDir;
+
+in vec3 lNormal;
+in vec3 lPos;
+
+out vec4 FragColor;
+
+float SoftenCosineTerm( float flDot )
+{
+ return ( flDot + ( flDot * flDot ) ) * 0.5;
+}
+
+vec3 DiffuseTerm( vec3 worldNormal, vec3 lightDir )
+{
+ float fResult = 0.0;
+ float NDotL = dot( worldNormal, lightDir );
+
+ fResult = clamp( NDotL, 0.0, 1.0 );
+ fResult = SoftenCosineTerm( fResult );
+
+ vec3 fOut = vec3( fResult, fResult, fResult );
+ return fOut;
+}
+
+vec3 PixelShaderDoLightingLinear( vec3 worldPos, vec3 worldNormal )
+{
+ vec3 linearColor = vec3(0.0,0.0,0.0);
+ linearColor += DiffuseTerm( worldNormal, testLightDir );
+
+ return linearColor;
+}
+
+vec3 LinearToGamma( vec3 f3linear )
+{
+ return pow( f3linear, vec3(1.0 / 2.2) );
+}
+
+void main()
+{
+ vec3 tangentSpaceNormal = vec3( 0.0, 0.0, 1.0 );
+ vec4 normalTexel = vec4(1.0,1.0,1.0,1.0);
+ vec4 baseColor = colour;
+
+ //normalTexel = tex2D( BumpmapSampler, i.detailOrBumpTexCoord );
+ //tangentSpaceNormal = 2.0 * normalTexel - 1.0;
+
+ vec3 diffuseLighting = vec3( 1.0, 1.0, 1.0 );
+
+ vec3 staticLightingColor = vec3( 0.0, 0.0, 0.0 );
+ diffuseLighting = PixelShaderDoLightingLinear( lPos, lNormal );
+
+ // multiply by .5 since we want a 50% (in gamma space) reflective surface)
+ diffuseLighting *= pow( 0.5, 2.2 );
+
+ vec3 result = diffuseLighting * baseColor.xyz;
+
+ FragColor = vec4( LinearToGamma(result), 1.0 );
+}
+""")
+
# Render functions
#
def cxr_ui(_,context):
CXR_COMPILER_CHAIN.WAIT_REDRAW = False
def cxr_draw():
- global cxr_view_shader, cxr_view_mesh, cxr_view_lines
+ global cxr_view_shader, cxr_view_mesh, cxr_view_lines, cxr_mdl_shader,\
+ cxr_mdl_mesh
cxr_view_shader.bind()
if cxr_view_lines != None:
cxr_view_lines.draw( cxr_view_shader )
- gpu.state.depth_test_set('LESS_EQUAL')
- gpu.state.blend_set('ADDITIVE')
if cxr_view_mesh != None:
+ gpu.state.depth_test_set('LESS_EQUAL')
+ gpu.state.blend_set('ADDITIVE')
+
cxr_view_mesh.draw( cxr_view_shader )
+ if cxr_mdl_mesh != None:
+ gpu.state.depth_mask_set(True)
+ gpu.state.depth_test_set('LESS_EQUAL')
+ gpu.state.face_culling_set('FRONT')
+ gpu.state.blend_set('NONE')
+ cxr_mdl_shader.bind()
+ cxr_mdl_shader.uniform_float('colour',(0.5,0.5,0.5,1.0))
+ cxr_mdl_shader.uniform_float("viewProjectionMatrix", \
+ bpy.context.region_data.perspective_matrix)
+
+ testmdl = bpy.context.scene.objects['target']
+ light = bpy.context.scene.objects['point']
+ relative = light.location - testmdl.location
+ relative.normalize()
+
+ cxr_mdl_shader.uniform_float("modelMatrix", testmdl.matrix_world)
+ cxr_mdl_shader.uniform_float("testLightDir", relative)
+
+
+ cxr_mdl_mesh.draw( cxr_mdl_shader )
+
def cxr_jobs_update_graph(jobs):
global cxr_jobs_batch, cxr_ui_shader, cxr_jobs_inf
class cxr_tri_mesh(Structure):
_fields_ = [("vertices",POINTER(c_double *3)),
+ ("normals",POINTER(c_double *3)),
+ ("uvs",POINTER(c_double *2)),
("colours",POINTER(c_double *4)),
("indices",POINTER(c_int32)),
("indices_count",c_int32),
# Other
libcxr_lightpatch_bsp = extern( "cxr_lightpatch_bsp", [c_char_p], None )
+# Binary file formats and FS
+libcxr_fs_set_gameinfo = extern( "cxr_fs_set_gameinfo", [c_char_p], c_int32 )
+libcxr_fs_exit = extern( "cxr_fs_exit", [], None )
+libcxr_fs_get = extern( "cxr_fs_get", [c_char_p], c_char_p )
+libcxr_load_mdl = extern( "cxr_load_mdl", [c_char_p], POINTER(cxr_tri_mesh) )
+
libcxr_funcs = [ libcxr_decompose, libcxr_free_world, libcxr_begin_vmf, \
libcxr_vmf_begin_entities, libcxr_push_world_vmf, \
libcxr_end_vmf, libcxr_vdf_open, libcxr_vdf_close, \
libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon,
libcxr_vdf_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\
- libcxr_world_preview, libcxr_free_tri_mesh ]
+ libcxr_world_preview, libcxr_free_tri_mesh, \
+ libcxr_fs_set_gameinfo, libcxr_fs_exit, libcxr_fs_get, \
+ libcxr_load_mdl ]
# Callbacks
-
def libcxr_log_callback(logStr):
print( F"{logStr.decode('utf-8')}",end='' )
err = c_int32(0)
world = libcxr_decompose.call( mesh_src, pointer(err) )
- if world == None:
+ if not world:
cxr_view_mesh = None
cxr_batch_lines()
libcxr_write_test_data.call( pointer(mesh_src) )
return {'FINISHED'}
+class CXR_INIT_FS_OPERATOR(bpy.types.Operator):
+ bl_idname="convexer.fs_init"
+ bl_label="Initialize filesystem"
+
+ def execute(_,context):
+ gameinfo = F'{bpy.context.scene.cxr_data.subdir}/gameinfo.txt'
+
+ if libcxr_fs_set_gameinfo.call( gameinfo.encode('utf-8') ) == 1:
+ print( "File system ready" )
+ else:
+ print( "File system failed to initialize" )
+
+ return {'FINISHED'}
+
+class CXR_LOAD_MODEL_OPERATOR(bpy.types.Operator):
+ bl_idname="convexer.model_load"
+ bl_label="Load model"
+
+ def execute(_,context):
+ global cxr_mdl_mesh, cxr_mdl_shader
+
+ mdlpath = bpy.context.scene.cxr_data.dev_mdl.encode('utf-8')
+ pmesh = libcxr_load_mdl.call( mdlpath )
+
+ if not pmesh:
+ print( "Failed to load model" )
+ return {'FINISHED'}
+
+ mesh = pmesh[0]
+
+ #TODO: remove code dupe
+ vertices = mesh.vertices[:mesh.vertex_count]
+ vertices = [(_[0],_[1],_[2]) for _ in vertices]
+
+ normals = mesh.normals[:mesh.vertex_count]
+ normals = [(_[0],_[1],_[2]) for _ in normals]
+
+ indices = mesh.indices[:mesh.indices_count]
+ indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \
+ for i in range(int(mesh.indices_count/3)) ]
+
+ cxr_mdl_mesh = batch_for_shader(
+ cxr_mdl_shader, 'TRIS',
+ { "aPos": vertices, "aNormal": normals },
+ indices = indices,
+ )
+
+ libcxr_free_tri_mesh.call( pmesh )
+
+ scene_redraw()
+ return {'FINISHED'}
+
# UI: Preview how the brushes will looks in 3D view
#
class CXR_PREVIEW_OPERATOR(bpy.types.Operator):
row.scale_y = 2
row.operator("convexer.reset")
+ layout.prop( bpy.context.scene.cxr_data, "dev_mdl" )
+ layout.operator( "convexer.model_load" )
+
# Main scene properties interface, where all the settings go
#
class CXR_INTERFACE(bpy.types.Panel):
if CXR_GNU_LINUX==1:
_.layout.operator("convexer.reload")
_.layout.operator("convexer.dev_test")
+ _.layout.operator("convexer.fs_init")
_.layout.operator("convexer.hash_reset")
settings = context.scene.cxr_data
comp_compile: bpy.props.BoolProperty(name="Compile",default=True)
comp_pack: bpy.props.BoolProperty(name="Pack",default=False)
+ dev_mdl: bpy.props.StringProperty(name="Model",default="")
+
classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \
CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
CXR_ENTITY_PANEL, CXR_LIGHT_PANEL, CXR_PREVIEW_OPERATOR,\
CXR_VIEW3D, CXR_COMPILER_CHAIN, CXR_RESET_HASHES,\
- CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL, CXR_RESET ]
+ CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL, CXR_RESET, \
+ CXR_INIT_FS_OPERATOR, CXR_LOAD_MODEL_OPERATOR ]
vmt_param_dynamic_class = None
IMPLEMENTATION
*/
-#define CXR_API
#define CXR_EPSILON 0.001
#define CXR_PLANE_SIMILARITY_MAX 0.998
#define CXR_BIG_NUMBER 1e300
#include <stdio.h>
#include <math.h>
-#include <stdint.h>
-#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
-typedef uint8_t u8;
-typedef uint16_t u16;
-typedef uint32_t u32;
-typedef uint64_t u64;
-typedef int8_t i8;
-typedef int16_t i16;
-typedef int32_t i32;
-typedef int64_t i64;
-
-typedef unsigned int uint;
-
-typedef double v2f[2];
-typedef double v3f[3];
-typedef double v4f[4];
-typedef v3f m3x3f[3];
-typedef v3f m4x3f[4];
-typedef v3f boxf[2];
-
+#include "cxr_types.h"
#include "cxr_math.h"
#include "cxr_mem.h"
+#include "cxr_log.h"
+
+#ifdef CXR_VALVE_BIN
+ #include "cxr_valve_bin.h"
+#endif
typedef struct cxr_world cxr_world;
typedef struct cxr_solid cxr_solid;
/* Simple mesh type mainly for debugging */
struct cxr_tri_mesh
{
- v3f *vertices;
+ v3f *vertices,
+ *normals;
+ v2f *uvs;
v4f *colours;
+
i32 *indices,
indices_count,
vertex_count;
#endif
#endif
-static void (*cxr_log_func)(const char *str);
-static void (*cxr_line_func)( v3f p0, v3f p1, v4f colour );
-
static int cxr_range(int x, int bound)
{
if( x < 0 )
#ifdef CXR_DEBUG
-static void cxr_log( const char *fmt, ... )
-{
- char buf[512];
-
- va_list args;
- va_start( args, fmt );
- vsnprintf( buf, sizeof(buf)-1, fmt, args );
- va_end(args);
-
- if( cxr_log_func )
- cxr_log_func( buf );
-
- fputs(buf,stdout);
-}
-
static void cxr_debug_line( v3f p0, v3f p1, v4f colour )
{
if( cxr_line_func )
out->colours = malloc( sizeof(v4f)*out->vertex_count );
out->vertices = malloc( sizeof(v3f)*out->vertex_count );
out->indices = malloc( sizeof(i32)*out->indices_count );
+ out->uvs = NULL;
+ out->normals = NULL;
v3f *overts = out->vertices;
v4f *colours = out->colours;
free( mesh->colours );
free( mesh->indices );
free( mesh->vertices );
+ free( mesh->normals );
+ free( mesh->uvs );
free( mesh );
}
return 1;
}
+
+#ifdef CXR_VALVE_BIN
+
+CXR_API cxr_tri_mesh *cxr_load_mdl( const char *mdlname )
+{
+ char path[1024];
+ strcpy( path, mdlname );
+ cxr_stripext( path );
+ strcat( path, ".dx90.vtx" );
+ VTXFileHeader_t *pVtxHdr = (VTXFileHeader_t *)cxr_fs_get( path );
+
+ if( !pVtxHdr )
+ return NULL;
+
+ /* .VVD */
+ strcpy( path, mdlname );
+ cxr_stripext( path );
+ strcat( path, ".vvd" );
+ vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *)cxr_fs_get( path );
+
+ if( !pVvdHdr )
+ {
+ free( pVtxHdr );
+ return 0;
+ }
+
+ /* .MDL */
+ strcpy( path, mdlname );
+ cxr_stripext( path );
+ strcat( path, ".mdl" );
+ studiohdr_t *pMdl = (studiohdr_t *)cxr_fs_get( path );
+
+ if( !pMdl )
+ {
+ free( pVtxHdr );
+ free( pVvdHdr );
+ return 0;
+ }
+
+ cxr_tri_mesh *mesh = malloc( sizeof(cxr_tri_mesh) );
+ mesh->colours = NULL;
+
+ mesh->indices_count = vtx_count_indices( pVtxHdr, pMdl );
+ mesh->indices = malloc( mesh->indices_count * sizeof( u32 ) );
+
+ mesh->vertex_count = pVvdHdr->numLodVertexes[0];
+ mesh->vertices = malloc( mesh->vertex_count * sizeof(v3f) );
+ mesh->uvs = malloc( mesh->vertex_count * sizeof(v2f) );
+ mesh->normals = malloc( mesh->vertex_count * sizeof(v3f) );
+
+#if 0
+ mesh->bounds[0][0] = pMdl->hull_min[0];
+ mesh->bounds[0][1] = pMdl->hull_min[1];
+ mesh->bounds[0][2] = pMdl->hull_min[2];
+ mesh->bounds[0][0] = pMdl->hull_max[0];
+ mesh->bounds[0][1] = pMdl->hull_max[1];
+ mesh->bounds[0][2] = pMdl->hull_max[2];
+#endif
+
+ mesh->indices_count = 0;
+
+ for( int bodyID = 0; bodyID < pVtxHdr->numBodyParts; ++bodyID )
+ {
+ /* Body parts */
+ VTXBodyPartHeader_t* pVtxBodyPart = pBodyPartVTX( pVtxHdr, bodyID );
+ mstudiobodyparts_t *pBodyPart = studiohdr_pBodypart( pMdl, bodyID );
+
+ for( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID )
+ {
+ /* models */
+ VTXModelHeader_t* pVtxModel = pModelVTX( pVtxBodyPart, modelID );
+ mstudiomodel_t *pStudioModel =
+ mstudiobodyparts_pModel( pBodyPart, modelID );
+
+ int nLod = 0;
+ VTXModelLODHeader_t *pVtxLOD = pLODVTX( pVtxModel, nLod );
+
+ for( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh )
+ {
+ /* meshes */
+ VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh );
+ mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh );
+
+ for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup )
+ {
+ /* groups */
+ VTXStripGroupHeader_t* pStripGroup =
+ pStripGroupVTX( pVtxMesh, nGroup );
+
+ for ( int nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ )
+ {
+ /* strips */
+ VTXStripHeader_t *pStrip = pStripVTX( pStripGroup, nStrip );
+
+ if ( pStrip->flags & STRIP_IS_TRILIST )
+ {
+ /* indices */
+ for ( int i = 0; i < pStrip->numIndices; i ++ )
+ {
+ u16 i1 = *pIndexVTX( pStripGroup,
+ pStrip->indexOffset + i );
+
+ mesh->indices[ mesh->indices_count ++ ] =
+ pVertexVTX( pStripGroup, i1 )->origMeshVertID +
+ pMesh->vertexoffset;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mstudiovertex_t *vertexData = GetVertexData( pVvdHdr );
+
+ for( int i = 0; i < mesh->vertex_count; i ++ )
+ {
+ mstudiovertex_t *vert = vertexData + i;
+
+ mesh->vertices[i][0] = vert->pos[0];
+ mesh->vertices[i][1] = vert->pos[1];
+ mesh->vertices[i][2] = vert->pos[2];
+
+ mesh->normals[i][0] = vert->norm[0];
+ mesh->normals[i][1] = vert->norm[1];
+ mesh->normals[i][2] = vert->norm[2];
+
+ mesh->uvs[i][0] = vert->uv[0];
+ mesh->uvs[i][1] = vert->uv[1];
+ }
+
+ free( pVtxHdr );
+ free( pVvdHdr );
+ free( pMdl );
+
+ return mesh;
+}
+#endif /* CXR_VALVE_BIN */
+
#endif /* CXR_VALVE_MAP_FILE */
#endif /* CXR_IMPLEMENTATION */
--- /dev/null
+/*
+ * The following is used to read the existing content from CS:GO compiled assets
+ *
+ * Supported formats:
+ * vdf
+ * vpk
+ * mdl
+ */
+
+#ifndef CXR_VALVE_BIN_H
+#define CXR_VALVE_BIN_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "cxr_types.h"
+#include "cxr_math.h"
+#include "cxr_io.h"
+#include "cxr_mem.h"
+#include "cxr_log.h"
+
+typedef struct VPKHeader VPKHeader;
+typedef struct VPKDirectoryEntry VPKDirectoryEntry;
+typedef struct vdf_kv vdf_kv;
+typedef struct vdf_node vdf_node;
+typedef struct vdf_ctx vdf_ctx;
+typedef struct valve_file_system valve_file_system;
+
+/*
+ * File system
+ */
+
+static struct valve_file_system
+{
+ char *gamedir,
+ *exedir;
+
+ /* Runtime */
+ VPKHeader *vpk;
+
+ cxr_abuffer searchpaths;
+
+ FILE *current_archive;
+ u16 current_idx;
+
+ int initialized;
+}
+fs_global = { .initialized = 0 };
+
+CXR_API int cxr_fs_set_gameinfo( const char *path ); /* Setup system */
+CXR_API void cxr_fs_exit(void); /* Clean up */
+CXR_API char *cxr_fs_get( const char *path ); /* Get a file */
+
+/*
+ * VPK reading
+ */
+
+static VPKDirectoryEntry *vpk_find( VPKHeader *self, const char *asset );
+static void vpk_free( VPKHeader *self );
+
+/*
+ * VDF reading
+ */
+
+static vdf_node *vdf_open_file( const char *fn );
+static void vdf_free_r( vdf_node *p );
+
+/* Starting from *it, get next child with matching name from node. */
+static vdf_node *vdf_next( vdf_node *node, const char *name, int *it );
+
+/* Create new empty node attached to parent. name can be NULL */
+static vdf_node *vdf_create_node( vdf_node *parent, const char *name );
+
+/* KV access */
+static const char *kv_get( vdf_node *node, const char *key,
+ const char *value_defalt );
+
+/* Iterate each keyvalue starting from *it until key is matched */
+static char *kv_next( vdf_node *node, const char *key, int *it );
+
+static int kv_get_int( vdf_node *node, const char *key, const int fallback );
+static float kv_get_float( vdf_node *node, const char *key, float fallback );
+
+static void kv_int_array( vdf_node *node, const char *key, u32 count,
+ int *arr );
+static void kv_float_array( vdf_node *node, const char *key, u32 count,
+ float *arr );
+static void kv_double_array( vdf_node *node, const char *key, u32 count,
+ double *arr );
+
+#define vdf_foreach( NODE, STR, AS ) \
+int __vdf_it_##AS = 0; \
+vdf_node * AS;\
+while( (AS = vdf_next( NODE, STR, &__vdf_it_##AS )) )
+
+#define kv_foreach( NODE, STR, AS ) \
+int __kv_it_##AS = 0; \
+const char * AS;\
+while( (AS = kv_next( NODE, STR, &__kv_it_##AS )) )
+#pragma pack(push, 1)
+
+struct VPKHeader
+{
+ u32 Signature;
+ u32 Version;
+ u32 TreeSize;
+ u32 FileDataSectionSize;
+ u32 ArchiveMD5SectionSize;
+ u32 OtherMD5SectionSize;
+ u32 SignatureSectionSize;
+};
+
+struct VPKDirectoryEntry
+{
+ u32 CRC;
+ u16 PreloadBytes;
+ u16 ArchiveIndex;
+ u32 EntryOffset;
+ u32 EntryLength;
+ u16 Terminator;
+};
+#pragma pack(pop)
+
+static void vpk_free( VPKHeader *self )
+{
+ free( self );
+}
+
+static VPKDirectoryEntry *vpk_find( VPKHeader *self, const char *asset )
+{
+ if( !self )
+ return NULL;
+
+ char wbuf[ 512 ];
+ strcpy( wbuf, asset );
+
+ /*
+ * This currently fails if the filename doesn't have a file extension, or
+ * if it is located at the root path. I'm not sure if this is defined
+ * behaviour or not. TODO: allow it to work anyway
+ */
+
+ char *ext = cxr_findext( wbuf, '.' );
+
+ if( !ext ) return NULL;
+ *(ext-1) = 0x00;
+
+ char *fn = cxr_findext( wbuf, '/' );
+
+ if( !fn ) return NULL;
+ *(fn-1) = 0x00;
+
+ char *dir = wbuf;
+ char *pCur = ((char *)self) + sizeof( VPKHeader );
+
+ while( 1 )
+ {
+ if( !*pCur ) break;
+
+ int bExt = !strcmp( ext, pCur );
+
+ while( *( pCur ++ ) ) {};
+ while( 1 )
+ {
+ if( !*pCur ) { pCur ++; break; }
+
+ int bDir = !strcmp( dir, pCur );
+
+ while( *( pCur ++ ) ) {};
+ while( 1 )
+ {
+ if( !*pCur ) { pCur ++; break; }
+
+ const char *vpk_fn = pCur;
+
+ while( *( pCur ++ ) ) {};
+ VPKDirectoryEntry *entry = (VPKDirectoryEntry *)pCur;
+
+ if( !strcmp( vpk_fn, fn ) && bExt && bDir )
+ {
+ return entry;
+ }
+
+ pCur += entry->PreloadBytes + sizeof( VPKDirectoryEntry );
+ }
+
+ if( bDir && bExt ) return NULL;
+ }
+
+ if( bExt ) return NULL;
+ }
+
+ return NULL;
+}
+
+/*
+ * VDF
+ */
+
+struct vdf_kv
+{
+ char *key;
+ char *value;
+};
+
+struct vdf_node
+{
+ char *name;
+
+ vdf_node *parent;
+
+ cxr_abuffer abnodes,
+ abpairs;
+
+ u32 user;
+ u32 user1;
+};
+
+static vdf_node *vdf_next( vdf_node *node, const char *name, int *it )
+{
+ if( !node )
+ return NULL;
+
+ for( int i = it? *it: 0; i < node->abnodes.count; i ++ )
+ {
+ vdf_node **ptr_child = cxr_ab_ptr( &node->abnodes, i ),
+ *child = *ptr_child;
+
+ if( !name || !strcmp( name, child->name ))
+ {
+ if( it ) *it = i+1;
+ return child;
+ }
+ }
+
+ return NULL;
+}
+
+static char *kv_next( vdf_node *node, const char *key, int *it )
+{
+ char *val;
+
+ if( node )
+ {
+ while( *it < node->abpairs.count )
+ {
+ vdf_kv *kv = cxr_ab_ptr( &node->abpairs, *it );
+
+ if( !strcmp( kv->key, key ) )
+ {
+ val = kv->value;
+ *it = *it + 1;
+ return val;
+ }
+
+ *it = *it + 1;
+ }
+ }
+
+ return NULL;
+}
+
+static const char *kv_get( vdf_node *node, const char *key,
+ const char *value_defalt )
+{
+ int it = 0;
+ char *val = kv_next( node, key, &it );
+
+ if( val )
+ return val;
+
+ return value_defalt;
+}
+
+static void vdf_str_to_int( const char *src, void *dest )
+{
+ *((int *)dest) = atoi( src );
+}
+
+static void vdf_str_to_float( const char *src, void *dest )
+{
+ *((float *)dest) = atof( src );
+}
+
+static void vdf_str_to_double( const char *src, void *dest )
+{
+ *((double *)dest) = atof( src );
+}
+
+static void kv_parse_array( const char *source, u32 esize, u32 count,
+ void(*interp_func)(const char *src, void *dest), void *arr )
+{
+ if( !source )
+ return;
+
+ char value_buf[ 64 ];
+ int token = 0;
+
+ u32 i = 0;
+ u32 k = 0;
+
+ char const *c = source;
+
+ while( *c )
+ {
+ if( *c==' ' || *c=='\t' || *c=='[' || *c==']' || *c=='(' || *c==')' )
+ {
+ if( token )
+ {
+ value_buf[ k ] = 0x00;
+ token = 0;
+
+ interp_func( value_buf, ((u8 *)arr) + i*esize );
+ i ++;
+
+ if( i >= count )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ if( !token )
+ {
+ token = 1;
+ k = 0;
+ }
+
+ if( token )
+ {
+ if( k < sizeof( value_buf ) - 1 )
+ {
+ value_buf[ k ++ ] = *c;
+ }
+ }
+ }
+
+ c ++;
+ }
+
+ /* Add remaining case if we hit null */
+ if( token && (i < count) )
+ {
+ value_buf[ k ] = 0x00;
+ interp_func( value_buf, ((u8 *)arr) + i*esize );
+ }
+}
+
+static void kv_int_array( vdf_node *node, const char *key, u32 count, int *arr )
+{
+ kv_parse_array( kv_get( node, key, NULL ), sizeof(int), count,
+ vdf_str_to_int, arr );
+}
+
+static void kv_float_array( vdf_node *node, const char *key, u32 count,
+ float *arr )
+{
+ kv_parse_array( kv_get( node, key, NULL ), sizeof(float), count,
+ vdf_str_to_float, arr );
+}
+
+static void kv_double_array( vdf_node *node, const char *key, u32 count,
+ double *arr )
+{
+ kv_parse_array( kv_get( node, key, NULL ), sizeof(double), count,
+ vdf_str_to_double, arr );
+}
+
+static int kv_get_int( vdf_node *node, const char *key,
+ const int default_value )
+{
+ const char *v = kv_get( node, key, NULL );
+ return v? atoi(v): default_value;
+}
+
+static float kv_get_float( vdf_node *node, const char *key,
+ float default_value )
+{
+ const char *v = kv_get( node, key, NULL );
+ return v? atof( v ): default_value;
+}
+
+static vdf_node *vdf_create_node( vdf_node *parent, const char *name )
+{
+ vdf_node *node = calloc( 1, sizeof( vdf_node ) );
+
+ /* init buffers */
+ cxr_ab_init( &node->abnodes, sizeof( vdf_node* ), 0 );
+ cxr_ab_init( &node->abpairs, sizeof( vdf_kv ), 0 );
+
+ if( name )
+ {
+ node->name = cxr_str_clone(name, 0);
+ }
+
+ if( parent )
+ {
+ node->parent = parent;
+
+ vdf_node **child = cxr_ab_empty( &parent->abnodes );
+ *child = node;
+ }
+
+ return node;
+}
+
+static void vdf_kv_append( vdf_node *p, const char *k, const char *v )
+{
+ vdf_kv *kv = cxr_ab_empty( &p->abpairs );
+
+ u32 sv = strlen(v)+1;
+ u32 sk = strlen(k)+1;
+
+ kv->key = malloc( sv+sk );
+ kv->value = kv->key+sk;
+
+ memcpy( kv->key, k, sk );
+ memcpy( kv->value, v, sv );
+}
+
+static void vdf_free_r( vdf_node *p )
+{
+ for( int i = 0; i < p->abpairs.count; i ++ )
+ {
+ vdf_kv *kv = cxr_ab_ptr( &p->abpairs, i );
+ free( kv->key ); /* key and value are allocated in the same buffer */
+ }
+
+ for( int i = 0; i < p->abnodes.count; i ++ )
+ {
+ vdf_node **ptr_node = cxr_ab_ptr( &p->abnodes, i );
+ vdf_free_r( *ptr_node );
+ }
+
+ cxr_ab_free( &p->abpairs );
+ cxr_ab_free( &p->abnodes );
+
+ free( p->name );
+ free( p );
+}
+
+/*
+ * Parsing
+ */
+
+struct vdf_ctx
+{
+ char name[1024];
+
+ vdf_node *root;
+
+ u32 lines;
+ u32 errors;
+
+ struct
+ {
+ int wait;
+ int expect_decl;
+
+ char *tokens[2];
+ int i;
+
+ char *ptr_read;
+
+ vdf_node *pnode;
+ }
+ st;
+};
+
+static void vdf_newln( vdf_ctx *ctx )
+{
+ ctx->lines ++;
+
+ ctx->st.tokens[0] = NULL;
+ ctx->st.tokens[1] = NULL;
+ ctx->st.i = 0;
+}
+
+static void vdf_endl( vdf_ctx *ctx )
+{
+ if( ctx->st.tokens[0] )
+ {
+ /* Keypair */
+ if( ctx->st.tokens[1] )
+ {
+ vdf_kv_append( ctx->st.pnode, ctx->st.tokens[0], ctx->st.tokens[1] );
+ }
+ else
+ {
+ /* decl */
+ strcpy( ctx->name, ctx->st.tokens[0] );
+ ctx->st.expect_decl = 1;
+ }
+ }
+
+ vdf_newln( ctx );
+}
+
+static int vdf_line_control( vdf_ctx *ctx )
+{
+ if( *ctx->st.ptr_read == '\r' )
+ {
+ *ctx->st.ptr_read = 0x00;
+ return 1;
+ }
+ if( *ctx->st.ptr_read == '\n' )
+ {
+ *ctx->st.ptr_read = 0x00;
+ vdf_endl( ctx );
+ return 2;
+ }
+
+ return 0;
+}
+
+static void vdf_wait_endl( vdf_ctx *ctx )
+{
+ while( (*ctx->st.ptr_read) && (*ctx->st.ptr_read != '\n') )
+ {
+ if( vdf_line_control( ctx ) == 2 )
+ {
+ return;
+ }
+
+ ctx->st.ptr_read ++;
+ }
+}
+
+static void vdf_parse_string( vdf_ctx *ctx )
+{
+ while( *ctx->st.ptr_read )
+ {
+ if( *ctx->st.ptr_read == '"' )
+ {
+ *ctx->st.ptr_read = 0x00;
+ return;
+ }
+
+ if( vdf_line_control( ctx ) )
+ {
+ /* Unexpected end of line */
+ return;
+ }
+
+ ctx->st.ptr_read ++;
+ }
+}
+
+static int vdf_parse_structure( vdf_ctx *ctx )
+{
+ if( *ctx->st.ptr_read == '{' )
+ {
+ if( ctx->st.tokens[0] || !ctx->st.expect_decl )
+ {
+ /* Unexpected token '{' */
+ ctx->errors ++;
+ }
+
+ ctx->st.expect_decl = 0;
+ ctx->st.pnode = vdf_create_node( ctx->st.pnode, ctx->name );
+
+ vdf_wait_endl( ctx );
+ return 1;
+ }
+
+ if( *ctx->st.ptr_read == '}' )
+ {
+ if( !ctx->st.pnode->parent )
+ {
+ /* Unexpected token '}' */
+ ctx->errors ++;
+ }
+ else
+ {
+ ctx->st.pnode = ctx->st.pnode->parent;
+ }
+
+ vdf_wait_endl( ctx );
+ return 1;
+ }
+
+ return 0;
+}
+
+static void vdf_parse_begin_token( vdf_ctx *ctx, char *ptr )
+{
+ ctx->st.tokens[ ctx->st.i ] = ptr;
+
+ if( ctx->st.expect_decl )
+ {
+ /* Unexpected token 'name' */
+ ctx->errors ++;
+ }
+}
+
+static void vdf_parse_feedbuffer( vdf_ctx *ctx, char *buf )
+{
+ ctx->st.ptr_read = buf;
+
+ while( *ctx->st.ptr_read )
+ {
+ if( !vdf_line_control( ctx ) )
+ {
+ if( (*ctx->st.ptr_read == '/') && (ctx->st.ptr_read[1] == '/') )
+ {
+ *ctx->st.ptr_read = 0x00;
+ ctx->st.ptr_read += 2;
+
+ vdf_endl( ctx );
+ vdf_wait_endl( ctx );
+ }
+ else
+ {
+ if( !vdf_parse_structure( ctx ) )
+ {
+ if( *ctx->st.ptr_read == ' ' || *ctx->st.ptr_read == '\t' )
+ {
+ *ctx->st.ptr_read = 0x00;
+
+ if( ctx->st.tokens[ ctx->st.i ] )
+ {
+ ctx->st.i ++;
+
+ if( ctx->st.i == 2 )
+ {
+ vdf_wait_endl( ctx );
+ }
+ }
+ }
+
+ /* New entry */
+ else if( !ctx->st.tokens[ ctx->st.i ] )
+ {
+ if( *ctx->st.ptr_read == '"' )
+ {
+ *ctx->st.ptr_read = 0x00;
+ ctx->st.ptr_read ++;
+
+ vdf_parse_begin_token( ctx, ctx->st.ptr_read );
+ vdf_parse_string( ctx );
+ }
+ else
+ {
+ if( !( *ctx->st.ptr_read == '/' &&
+ *(ctx->st.ptr_read + 1) == *ctx->st.ptr_read ) )
+ {
+ vdf_parse_begin_token( ctx, ctx->st.ptr_read );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ctx->st.ptr_read ++;
+ }
+}
+
+static int vdf_load_into( const char *fn, vdf_node *node )
+{
+ char *text_src = cxr_textasset_read( fn );
+
+ if( !text_src )
+ {
+ return 0;
+ }
+
+ vdf_ctx ctx = {0};
+ ctx.root = ctx.st.pnode = node;
+
+ vdf_newln( &ctx );
+ vdf_parse_feedbuffer( &ctx, text_src );
+ free( text_src );
+
+ return 1;
+}
+
+static vdf_node *vdf_open_file( const char *fn )
+{
+ vdf_node *root = vdf_create_node( NULL, NULL );
+ if( vdf_load_into( fn, root ) )
+ {
+ return root;
+ }
+ else
+ {
+ vdf_free_r( root );
+ return NULL;
+ }
+}
+
+/*
+ * File system
+ */
+
+CXR_API i32 cxr_fs_set_gameinfo( const char *path )
+{
+ valve_file_system *fs = &fs_global;
+
+ if( fs->initialized )
+ cxr_fs_exit();
+
+ vdf_node *info = vdf_open_file( path );
+ if( !info )
+ return 0;
+
+ fs->gamedir = cxr_str_clone( path, 0 );
+ cxr_downlvl( fs->gamedir );
+
+ fs->exedir = cxr_str_clone( fs->gamedir, 0 );
+ cxr_downlvl( fs->exedir );
+
+ cxr_log( "Setting up file system:\n"
+ "gameinfo: %s\n"
+ "gamedir: %s\n"
+ "exedir: %s\n",
+
+ path, fs->gamedir, fs->exedir );
+
+ /* get search paths */
+ vdf_node *search_paths =
+ vdf_next
+ (
+ vdf_next
+ (
+ vdf_next( info, "GameInfo", NULL ),
+ "FileSystem",
+ NULL
+ ),
+ "SearchPaths",
+ NULL
+ );
+
+ cxr_ab_init( &fs->searchpaths, sizeof( char *), 0 );
+
+ kv_foreach( search_paths, "Game", kv_game )
+ {
+ cxr_log( "Game %s\n", kv_game );
+
+ if( kv_game[0] == '|' ) continue;
+
+ char *buf;
+ if( cxr_path_is_abs( kv_game ) )
+ {
+ buf = cxr_str_clone( kv_game, 1 );
+ strcat( buf, "/" );
+ }
+ else
+ {
+ buf = cxr_str_clone( fs->exedir, strlen(kv_game)+1 );
+ strcat( buf, kv_game );
+ strcat( buf, "/" );
+ }
+
+ char **sp = cxr_ab_empty( &fs->searchpaths );
+ *sp = buf;
+ }
+
+ vdf_free_r( info );
+
+ /* Find pack diretory */
+ char pack_path[512];
+ for( int i = 0; i < fs->searchpaths.count; i ++ )
+ {
+ char **sp = cxr_ab_ptr( &fs->searchpaths, i );
+
+ strcpy( pack_path, *sp );
+ strcat( pack_path, "pak01_dir.vpk" );
+
+ if( (fs->vpk = (VPKHeader *)cxr_asset_read( pack_path )) )
+ break;
+ }
+
+ if( !fs->vpk )
+ {
+ cxr_log( "Could not locate pak01_dir.vpk in %i searchpaths. "
+ "Stock models will not load!\n", fs->searchpaths.count );
+ }
+
+ fs->initialized = 1;
+ return 1;
+}
+
+CXR_API void cxr_fs_exit(void)
+{
+ valve_file_system *fs = &fs_global;
+
+ for( int i = 0; i < fs->searchpaths.count; i ++ )
+ {
+ char **sp = cxr_ab_ptr( &fs->searchpaths, i );
+ free( *sp );
+ }
+
+ cxr_ab_free( &fs->searchpaths );
+
+ if( fs->vpk )
+ {
+ vpk_free( fs->vpk );
+ fs->vpk = NULL;
+ }
+
+ if( fs->current_archive )
+ {
+ fclose( fs->current_archive );
+ fs->current_archive = NULL;
+ }
+
+ free( fs->gamedir );
+ free( fs->exedir );
+
+ memset( fs, 0, sizeof( valve_file_system ) );
+}
+
+CXR_API char *cxr_fs_get( const char *path )
+{
+ valve_file_system *fs = &fs_global;
+
+ if( !fs->initialized )
+ return NULL;
+
+ VPKDirectoryEntry *entry;
+ char pak[ 533 ];
+
+ if( fs->vpk )
+ {
+ if( (entry = vpk_find( fs->vpk, path )) )
+ {
+ if( entry->ArchiveIndex != fs->current_idx )
+ {
+ if( fs->current_archive )
+ {
+ fclose( fs->current_archive );
+ fs->current_archive = NULL;
+ }
+
+ fs->current_idx = entry->ArchiveIndex;
+ }
+
+ if( !fs->current_archive )
+ {
+ sprintf( pak, "%scsgo/pak01_%03hu.vpk", fs->exedir,
+ fs->current_idx );
+ fs->current_archive = fopen( pak, "rb" );
+
+ if( !fs->current_archive )
+ {
+ cxr_log( "Could not locate %s\n", pak );
+ return NULL;
+ }
+ }
+
+ char *filebuf = malloc( entry->EntryLength );
+
+ fseek( fs->current_archive, entry->EntryOffset, SEEK_SET );
+ if( fread( filebuf, 1, entry->EntryLength, fs->current_archive )
+ == entry->EntryLength )
+ {
+ return filebuf;
+ }
+ else
+ {
+ free( filebuf );
+ return NULL;
+ }
+ }
+ }
+
+ /* Use physical search paths */
+ char path_buf[ 512 ];
+
+ for( int i = 0; i < fs->searchpaths.count; i ++ )
+ {
+ char **sp = cxr_ab_ptr( &fs->searchpaths, i );
+
+ strcpy( path_buf, *sp );
+ strcat( path_buf, path );
+
+ char *filebuf;
+ if( (filebuf = cxr_asset_read( path_buf )) )
+ return filebuf;
+ }
+
+ /* File not found */
+ return NULL;
+}
+
+/*
+ * VMDL interface
+ *
+ * This software is not affiliated with Valve Corporation
+ * We are not affiliated, associated, authorized, endorsed by, or in any way
+ * officially connected with Valve Corporation, or any of its subsidiaries or
+ * its affiliates.
+ *
+ * All trademarks are property of their respective owners
+ */
+
+#define MAX_NUM_LODS 8
+#define MAX_NUM_BONES_PER_VERT 3
+
+#pragma pack(push, 1)
+typedef struct
+{
+ float weight[MAX_NUM_BONES_PER_VERT];
+ char bone[MAX_NUM_BONES_PER_VERT];
+ char numbones;
+}
+boneWeight_t;
+
+typedef struct
+{
+ boneWeight_t boneweights;
+ float pos[3];
+ float norm[3];
+ float uv[2];
+}
+mstudiovertex_t;
+
+typedef struct
+{
+ int id;
+ int version;
+ int checksum;
+ int numLods;
+ int numLodVertexes[MAX_NUM_LODS];
+ int numFixups;
+ int fixupTableStart;
+ int vertexDataStart;
+ int tangentDataStart;
+}
+vertexFileHeader_t;
+
+#pragma pack(pop)
+
+static mstudiovertex_t *GetVertexData( vertexFileHeader_t *t )
+{
+ return (mstudiovertex_t *) ( (char *)t + t->vertexDataStart );
+}
+
+/*
+ * VTX file format
+ */
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ unsigned char boneWeightIndex[3];
+ unsigned char numBones;
+
+ unsigned short origMeshVertID;
+ char boneID[3];
+}
+VTXVertex_t;
+
+enum StripGroupFlags
+{
+ STRIPGROUP_IS_FLEXED = 0x01,
+ STRIPGROUP_IS_HWSKINNED = 0x02,
+ STRIPGROUP_IS_DELTA_FLEXED = 0x04,
+ STRIPGROUP_SUPPRESS_HW_MORPH = 0x08
+};
+
+enum StripHeaderFlags_t {
+ STRIP_IS_TRILIST = 0x01,
+ STRIP_IS_TRISTRIP = 0x02 /* Unused by studiomdl 2015? */
+};
+
+typedef struct
+{
+ int numIndices;
+ int indexOffset;
+
+ int numVerts;
+ int vertOffset;
+
+ short numBones;
+
+ unsigned char flags;
+
+ int numBoneStateChanges;
+ int boneStateChangeOffset;
+}
+VTXStripHeader_t;
+
+typedef struct
+{
+ int numVerts;
+ int vertOffset;
+
+ int numIndices;
+ int indexOffset;
+
+ int numStrips;
+ int stripOffset;
+
+ unsigned char flags;
+}
+VTXStripGroupHeader_t;
+
+static VTXVertex_t *pVertexVTX( VTXStripGroupHeader_t *t, int i )
+{
+ return (VTXVertex_t *)(((char *)t) + t->vertOffset) + i;
+}
+
+static unsigned short *pIndexVTX( VTXStripGroupHeader_t *t, int i )
+{
+ return (unsigned short *)(((char *)t) + t->indexOffset) + i;
+}
+
+static VTXStripHeader_t *pStripVTX( VTXStripGroupHeader_t *t, int i )
+{
+ return (VTXStripHeader_t *)(((char *)t) + t->stripOffset) + i;
+}
+
+typedef struct
+{
+ int numStripGroups;
+ int stripGroupHeaderOffset;
+
+ unsigned char flags;
+}
+VTXMeshHeader_t;
+static VTXStripGroupHeader_t *pStripGroupVTX( VTXMeshHeader_t *t, int i )
+{
+ return (VTXStripGroupHeader_t *)(((char *)t) + t->stripGroupHeaderOffset) +i;
+}
+
+typedef struct
+{
+ int numMeshes;
+ int meshOffset;
+
+ float switchPoint;
+}
+VTXModelLODHeader_t;
+static VTXMeshHeader_t *pMeshVTX( VTXModelLODHeader_t *t, int i )
+{
+ return (VTXMeshHeader_t *)(((char *)t) + t->meshOffset) + i;
+}
+
+typedef struct
+{
+ int numLODs;
+ int lodOffset;
+}
+VTXModelHeader_t;
+static VTXModelLODHeader_t *pLODVTX( VTXModelHeader_t *t, int i )
+{
+ return (VTXModelLODHeader_t *)(((char *)t) + t->lodOffset) + i;
+}
+
+typedef struct
+{
+ int numModels;
+ int modelOffset;
+}
+VTXBodyPartHeader_t;
+static VTXModelHeader_t *pModelVTX( VTXBodyPartHeader_t *t, int i )
+{
+ return (VTXModelHeader_t *)(((char *)t) + t->modelOffset) + i;
+}
+
+typedef struct
+{
+ int version; /* 7 */
+
+ /* hardware params that affect how the model is to be optimized. */
+ int vertCacheSize;
+ unsigned short maxBonesPerStrip;
+ unsigned short maxBonesPerTri;
+ int maxBonesPerVert;
+
+ int checkSum;
+ int numLODs;
+ int materialReplacementListOffset;
+ int numBodyParts;
+ int bodyPartOffset;
+}
+VTXFileHeader_t;
+static VTXBodyPartHeader_t *pBodyPartVTX( VTXFileHeader_t *t, int i )
+{
+ return (VTXBodyPartHeader_t *)(((char *)t) + t->bodyPartOffset) + i;
+}
+
+/*
+ .VTX file structure
+ =============================================
+
+ FileHeader
+ L BodyParts::
+ L Models::
+ L LODS::
+ L Meshes::
+ L StripGroups::
+ L VerticesTable[StudioMDL.Vertex]
+ L IndicesTable[UINT16]
+ |
+ L Strips::
+ L Vertices[UINT16]
+ L Indices[UINT16]
+*/
+
+#pragma pack(pop)
+
+/*
+ * mdl format
+ */
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ void *pVertexData;
+ void *pTangentData;
+}
+mstudio_modelvertexdata_t;
+
+typedef struct
+{
+ int unused_modelvertexdata;
+ int numLODVertexes[MAX_NUM_LODS];
+
+ mstudio_modelvertexdata_t *_the_death_ptr;
+}
+mstudio_meshvertexdata_t;
+
+typedef struct mstudiomodel_t mstudiomodel_t;
+typedef struct
+{
+ int material;
+ int modelindex;
+ int numvertices;
+ int vertexoffset;
+ int numflexes;
+ int flexindex;
+ int materialtype;
+ int materialparam;
+ int meshid;
+ float center[3];
+ mstudio_meshvertexdata_t vertexdata;
+ int unused[6];
+}
+mstudiomesh_t;
+
+struct mstudiomodel_t
+{
+ char name[64];
+ int type;
+ float boundingradius;
+
+ int nummeshes;
+ int meshindex;
+
+ int numvertices;
+ int vertexindex;
+ int tangentsindex;
+
+ int numattachments;
+ int attachmentindex;
+
+ int numeyeballs;
+ int eyeballindex;
+
+ mstudio_modelvertexdata_t vertexdata;
+
+ int unused[8];
+};
+static mstudiomesh_t *studiomodel_pMesh( mstudiomodel_t *t, int i )
+{
+ return (mstudiomesh_t *)(((char *)t) + t->meshindex) + i;
+}
+
+typedef struct
+{
+ int sznameindex;
+ int nummodels;
+ int base;
+ int modelindex;
+} mstudiobodyparts_t;
+
+static mstudiomodel_t *mstudiobodyparts_pModel( mstudiobodyparts_t *t, int i )
+{
+ return (mstudiomodel_t *)(((char *)t) + t->modelindex) + i;
+}
+
+typedef struct
+{
+ int id;
+ int version;
+ int checksum;
+ char name[64];
+ int length;
+ float eyeposition[3];
+ float illumposition[3];
+ float hull_min[3];
+ float hull_max[3];
+ float view_bbmin[3];
+ float view_bbmax[3];
+ int flags;
+ int numbones;
+ int boneindex;
+ int numbonecontrollers;
+ int bonecontrollerindex;
+ int numhitboxsets;
+ int hitboxsetindex;
+ int numlocalanim;
+ int localanimindex;
+ int numlocalseq;
+ int localseqindex;
+ int activitylistversion;
+ int eventsindexed;
+ int numtextures;
+ int textureindex;
+ int numcdtextures;
+ int cdtextureindex;
+ int numskinref;
+ int numskinfamilies;
+ int skinindex;
+ int numbodyparts;
+ int bodypartindex;
+ int numlocalattachments;
+ int localattachmentindex;
+ int numlocalnodes;
+ int localnodeindex;
+ int localnodenameindex;
+ int numflexdesc;
+ int flexdescindex;
+ int numflexcontrollers;
+ int flexcontrollerindex;
+ int numflexrules;
+ int flexruleindex;
+ int numikchains;
+ int ikchainindex;
+ int nummouths;
+ int mouthindex;
+ int numlocalposeparameters;
+ int localposeparamindex;
+ int surfacepropindex;
+ int keyvalueindex;
+ int keyvaluesize;
+ int numlocalikautoplaylocks;
+ int localikautoplaylockindex;
+ float mass;
+ int contents;
+ int numincludemodels;
+ int includemodelindex;
+ int szanimblocknameindex;
+ int numanimblocks;
+ int animblockindex;
+ int bonetablebynameindex;
+ char constdirectionallightdot;
+ char rootLOD;
+ char numAllowedRootLODs;
+ char unused[1];
+ int unused4;
+ int numflexcontrollerui;
+ int flexcontrolleruiindex;
+ float flVertAnimFixedPointScale;
+ int unused3[1];
+ int studiohdr2index;
+ int unused2[1];
+}
+studiohdr_t;
+
+static mstudiobodyparts_t *studiohdr_pBodypart( studiohdr_t *t, int i )
+{
+ return (mstudiobodyparts_t *)(((char *)t) + t->bodypartindex) + i;
+}
+
+#pragma pack(pop)
+
+static u32 vtx_count_indices( VTXFileHeader_t *pVtxHdr, studiohdr_t *pMdl )
+{
+ int indice_count = 0;
+
+ for( int bodyID = 0; bodyID < pVtxHdr->numBodyParts; ++bodyID )
+ {
+ /* Body parts */
+ VTXBodyPartHeader_t* pVtxBodyPart = pBodyPartVTX( pVtxHdr, bodyID );
+ mstudiobodyparts_t *pBodyPart = studiohdr_pBodypart( pMdl, bodyID );
+
+ for( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID )
+ {
+ /* models */
+ VTXModelHeader_t* pVtxModel = pModelVTX( pVtxBodyPart, modelID );
+ mstudiomodel_t *pStudioModel =
+ mstudiobodyparts_pModel( pBodyPart, modelID );
+
+ int nLod = 0;
+ VTXModelLODHeader_t *pVtxLOD = pLODVTX( pVtxModel, nLod );
+
+ for( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh )
+ {
+ /* meshes */
+ VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh );
+ mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh );
+
+ for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup )
+ {
+ /* groups */
+ VTXStripGroupHeader_t* pStripGroup =
+ pStripGroupVTX( pVtxMesh, nGroup );
+
+ for ( int nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ )
+ {
+ /* strips */
+ VTXStripHeader_t *pStrip = pStripVTX( pStripGroup, nStrip );
+
+ if ( pStrip->flags & STRIP_IS_TRILIST )
+ {
+ indice_count += pStrip->numIndices;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return indice_count;
+}
+
+#endif /* CXR_VALVE_BIN_H */