X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=model.h;h=c92960f04569b11ea2b8e5a6b62776c07e8f59dd;hb=a1056ed8198f0f5be0e0f341da8bd49aa6c47198;hp=ad9dfeb53ce6e782125243df58b27a6f7ba49619;hpb=1030b1e134d422a3cbc1e06102053447da59ceba;p=carveJwlIkooP6JGAAIwe30JlM.git diff --git a/model.h b/model.h index ad9dfeb..c92960f 100644 --- a/model.h +++ b/model.h @@ -1,32 +1,74 @@ +/* + * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved + */ + #ifndef MODEL_H #define MODEL_H #include "common.h" -typedef struct glmesh glmesh; -typedef struct mdl_vert mdl_vert; -typedef struct mdl_submesh mdl_submesh; -typedef struct mdl_material mdl_material; -typedef struct mdl_node mdl_node; -typedef struct mdl_header mdl_header; +enum mdl_shader +{ + k_shader_standard = 0, + k_shader_standard_cutout = 1, + k_shader_terrain_blend = 2, + k_shader_standard_vertex_blend = 3, + k_shader_water = 4 +}; + +enum mdl_surface_prop +{ + k_surface_prop_concrete = 0, + k_surface_prop_wood = 1, + k_surface_prop_grass = 2, + k_surface_prop_tiles = 3 +}; -#define MDL_SIZE_MAX 0x1000000 -#define MDL_VERT_MAX 1000000 -#define MDL_INDICE_MAX 1000000 -#define MDL_MATERIAL_MAX 32 -#define MDL_NODE_MAX 4000 -#define MDL_SUBMESH_MAX 8000 -#define MDL_STRING_LENGTH_MAX 64 +enum material_flag +{ + k_material_flag_skate_surface = 0x1, + k_material_flag_collision = 0x2, + k_material_flag_grow_grass = 0x4, + k_material_flag_grind_surface = 0x8 +}; #pragma pack(push,1) +/* 48 byte */ struct mdl_vert { - v3f co, - norm; - v4f colour; - v2f uv; + v3f co, /* 3*32 */ + norm; /* 3*32 */ + v2f uv; /* 2*32 */ + + u8 colour[4]; /* 4*8 */ + u16 weights[4];/* 4*16 */ + u8 groups[4]; /* 4*8 */ +}; + +#pragma pack(pop) + +typedef struct mdl_context mdl_context; +typedef struct mdl_array_ptr mdl_array_ptr; +typedef struct mdl_vert mdl_vert; +typedef struct mdl_transform mdl_transform; +typedef struct mdl_submesh mdl_submesh; +typedef struct mdl_material mdl_material; +typedef struct mdl_bone mdl_bone; +typedef struct mdl_armature mdl_armature; +typedef struct mdl_animation mdl_animation; +typedef struct mdl_transform mdl_keyframe; +typedef struct mdl_mesh mdl_mesh; +typedef struct mdl_file mdl_file; +typedef struct mdl_texture mdl_texture; +typedef struct mdl_array mdl_array; +typedef struct mdl_header mdl_header; + +struct mdl_transform +{ + v3f co, s; + v4f q; }; struct mdl_submesh @@ -35,381 +77,396 @@ struct mdl_submesh indice_count, vertex_start, vertex_count; - + boxf bbx; u32 material_id; }; struct mdl_material { - u32 pstr_name; + u32 pstr_name, + shader, + flags, + surface_prop; + + v4f colour, + colour1; + + u32 tex_diffuse, + tex_none0, + tex_none1; }; -struct mdl_node +struct mdl_bone { - v3f co; - v4f q; - v3f s; - - union{ u32 submesh_start, sub_uid; }; - u32 - submesh_count, - classtype, - offset, + v3f co, end; + u32 parent, + collider, + ik_target, + ik_pole, + flags, pstr_name; + + boxf hitbox; + v3f conevx, conevy, coneva; + float conet; }; -struct mdl_header +enum bone_flag { - u32 identifier, version, file_length; - - u32 vertex_count, vertex_offset, - indice_count, indice_offset, - submesh_count, submesh_offset, - material_count, material_offset, - node_count, node_offset, - strings_offset, entdata_offset; + k_bone_flag_deform = 0x1, + k_bone_flag_ik = 0x2, + k_bone_flag_cone_constraint = 0x4 }; -/* - * Entity data structures - */ - -struct classtype_block +enum bone_collider { - boxf bbx; + k_bone_collider_none = 0, + k_bone_collider_box = 1, + k_bone_collider_capsule = 2 }; - -struct classtype_gate + +struct mdl_armature { - u32 target; - v3f dims; + mdl_transform transform; + u32 bone_start, + bone_count, + anim_start, + anim_count; }; -struct classtype_spawn +struct mdl_animation { - u32 target; + u32 pstr_name, + length; + float rate; + u32 offset; }; -struct classtype_water +struct mdl_mesh { - u32 temp; + mdl_transform transform; + u32 submesh_start, + submesh_count, + pstr_name, + flags, + armature_id; }; -struct classtype_car_path +struct mdl_file { - u32 target, target1; + u32 pstr_path, + pack_offset, + pack_size; }; -struct classtype_instance +struct mdl_texture { - u32 pstr_file; + mdl_file file; + u32 type; }; -struct classtype_capsule +struct mdl_array { - float height, radius; + u32 file_offset, + item_count, + item_size; + + char name[16]; }; -struct classtype_route_node +struct mdl_header { - u32 target, target1; + u32 version; + mdl_array index; }; -struct classtype_route +struct mdl_context { - u32 pstr_name; - u32 id_start; - v3f colour; + FILE *file; + mdl_header info; + + struct mdl_array_ptr + { + void *data; + u32 count, stride; + } + index, + + /* metadata */ + strings, + meshs, + submeshs, + materials, + textures, + armatures, + bones, + animations, + + /* animation buffers */ + keyframes, + + /* mesh buffers */ + verts, + indices, + + /* pack data */ + pack; }; -#pragma pack(pop) + +VG_STATIC void mdl_load_fatal_corrupt( mdl_context *mdl ) +{ + fclose( mdl->file ); + vg_file_print_invalid( mdl->file ); + vg_fatal_exit_loop( "Corrupt model" ); +} /* - * Simple mesh interface for OpenGL + * Model implementation */ -struct glmesh +VG_STATIC void mdl_load_array_file( mdl_context *mdl, mdl_array_ptr *ptr, + mdl_array *arr, void *lin_alloc ) { - GLuint vao, vbo, ebo; - u32 indice_count; -}; + if( arr->item_count ){ + u32 size = arr->item_size*arr->item_count; + ptr->data = vg_linear_alloc( lin_alloc, vg_align8(size) ); -static void mesh_upload( glmesh *mesh, - mdl_vert *verts, u32 vert_count, - u32 *indices, u32 indice_count ) -{ - glGenVertexArrays( 1, &mesh->vao ); - glGenBuffers( 1, &mesh->vbo ); - glGenBuffers( 1, &mesh->ebo ); - glBindVertexArray( mesh->vao ); + fseek( mdl->file, arr->file_offset, SEEK_SET ); + u64 l = fread( ptr->data, arr->item_size*arr->item_count, 1, mdl->file ); - size_t stride = sizeof(mdl_vert); + if( l != 1 ) + mdl_load_fatal_corrupt( mdl ); + } + else + ptr->data = NULL; - glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo ); - glBufferData( GL_ARRAY_BUFFER, vert_count*stride, verts, GL_STATIC_DRAW ); + ptr->count = arr->item_count; + ptr->stride = arr->item_size; +} - glBindVertexArray( mesh->vao ); - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo ); - glBufferData( GL_ELEMENT_ARRAY_BUFFER, indice_count*sizeof(u32), - indices, GL_STATIC_DRAW ); - - glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 ); - glEnableVertexAttribArray( 0 ); +VG_STATIC void *mdl_arritm( mdl_array_ptr *arr, u32 index ) +{ + return ((u8 *)arr->data) + index*arr->stride; +} - glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, - stride, (void *)offsetof(mdl_vert, norm) ); - glEnableVertexAttribArray( 1 ); +VG_STATIC u32 mdl_arrcount( mdl_array_ptr *arr ) +{ + return arr->count; +} - glVertexAttribPointer( 2, 4, GL_FLOAT, GL_FALSE, - stride, (void *)offsetof(mdl_vert, colour) ); - glEnableVertexAttribArray( 2 ); +VG_STATIC int mdl_load_array( mdl_context *mdl, mdl_array_ptr *ptr, + const char *name, void *lin_alloc ) +{ + for( u32 i=0; iindex); i++ ){ + mdl_array *arr = mdl_arritm( &mdl->index, i ); + + if( !strncmp(arr->name,name,16) ){ + mdl_load_array_file( mdl, ptr, arr, lin_alloc ); + return 1; + } + } - glVertexAttribPointer( 3, 2, GL_FLOAT, GL_FALSE, - stride, (void *)offsetof(mdl_vert, uv) ); - glEnableVertexAttribArray( 3 ); - - VG_CHECK_GL(); - mesh->indice_count = indice_count; + ptr->data = NULL; + ptr->count = 0; + ptr->stride = 0; + return 0; } -static void mesh_bind( glmesh *mesh ) +VG_STATIC int mdl_load_mesh_block( mdl_context *mdl, void *lin_alloc ) { - glBindVertexArray( mesh->vao ); + int success = 1; + + success &= mdl_load_array( mdl, &mdl->verts, "mdl_vert", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->indices, "mdl_indice", lin_alloc ); + + return success; } -static void mesh_drawn( u32 start, u32 count ) +VG_STATIC int mdl_load_metadata_block( mdl_context *mdl, void *lin_alloc ) { - glDrawElements( GL_TRIANGLES, count, GL_UNSIGNED_INT, - (void *)(start*sizeof(u32)) ); + int success = 1; + + success &= mdl_load_array( mdl, &mdl->strings, "strings", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->meshs, "mdl_mesh", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->submeshs, "mdl_submesh", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->materials, "mdl_material", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->textures, "mdl_texture", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->armatures, "mdl_armature", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->bones, "mdl_bone", lin_alloc ); + success &= mdl_load_array( mdl, &mdl->animations,"mdl_animation",lin_alloc ); + + return success; } -static void mesh_draw( glmesh *mesh ) +VG_STATIC int mdl_load_animation_block( mdl_context *mdl, void *lin_alloc ) { - mesh_drawn( 0, mesh->indice_count ); + return mdl_load_array( mdl, &mdl->keyframes, "mdl_keyframe", lin_alloc ); } -static void mesh_free( glmesh *mesh ) +VG_STATIC int mdl_load_pack_block( mdl_context *mdl, void *lin_alloc ) { - glDeleteVertexArrays( 1, &mesh->vao ); - glDeleteBuffers( 1, &mesh->ebo ); - glDeleteBuffers( 1, &mesh->vbo ); + return mdl_load_array( mdl, &mdl->pack, "pack", lin_alloc ); } - /* - * Model implementation + * if calling mdl_open, and the file does not exist, the game will fatal quit */ - -static mdl_header *mdl_load( const char *path ) +VG_STATIC void mdl_open( mdl_context *mdl, const char *path, void *lin_alloc ) { - i64 size; - mdl_header *header = vg_asset_read_s( path, &size ); + memset( mdl, 0, sizeof( mdl_context ) ); + mdl->file = fopen( path, "rb" ); - /* - * Check file is valid - */ - if( !header ) - { - vg_error( "Could not open '%s'\n", path ); - return NULL; + if( !mdl->file ){ + vg_error( "mdl_open('%s'): %s\n", path, strerror(errno) ); + vg_fatal_exit_loop( "see above for details" ); } - if( size < sizeof(mdl_header) ) - { - free( header ); - vg_error( "Invalid file '%s' (too small for header)\n", path ); - return NULL; - } - - if( header->file_length != size ) - { - vg_error( "Invalid file '%s'" - "(wrong .file_length, %ub != real file size %ub)\n", - path, header->file_length, size ); - free( header ); - return NULL; - } + u64 l = fread( &mdl->info, sizeof(mdl_header), 1, mdl->file ); + if( l != 1 ) + mdl_load_fatal_corrupt( mdl ); - /* - * Validate offsets and memory sections, to ensure all arrays are in-bounds, - * and that they do not overlap. - */ + mdl_load_array_file( mdl, &mdl->index, &mdl->info.index, lin_alloc ); +} - struct memregion - { - const char *desc; - u32 count, max_count, size, offset; - } - regions[] = { - { - "Vertices", - header->vertex_count, MDL_VERT_MAX, - sizeof(mdl_vert), header->vertex_offset - }, - { - "Indices", - header->indice_count, MDL_INDICE_MAX, - sizeof(u32), header->indice_offset - }, - { - "Submesh", - header->submesh_count, MDL_SUBMESH_MAX, - sizeof(mdl_submesh), header->submesh_offset - }, - { - "Materials", - header->material_count, MDL_MATERIAL_MAX, - sizeof(mdl_material), header->material_offset - }, - { - "Nodes", - header->node_count, MDL_NODE_MAX, - sizeof(mdl_node), header->node_count - } - }; +/* + * close file handle + */ +VG_STATIC void mdl_close( mdl_context *mdl ) +{ + fclose( mdl->file ); + mdl->file = NULL; +} - for( int i=0; icount == 0 ) - continue; +VG_STATIC void mdl_transform_m4x3( mdl_transform *transform, m4x3f mtx ) +{ + q_m3x3( transform->q, mtx ); + v3_muls( mtx[0], transform->s[0], mtx[0] ); + v3_muls( mtx[1], transform->s[1], mtx[1] ); + v3_muls( mtx[2], transform->s[2], mtx[2] ); + v3_copy( transform->co, mtx[3] ); +} - if( ri->count > ri->max_count ) - { - free( header ); - vg_error( "'%s': '%s' buffer exceeds the maximum (%u/%u)\n", - path, ri->desc, ri->count, ri->max_count ); - return NULL; - } +VG_STATIC const char *mdl_pstr( mdl_context *mdl, u32 pstr ) +{ + return mdl_arritm( &mdl->strings, pstr ); +} - if( ri->offset >= header->file_length ) - { - free( header ); - vg_error( "'%s': '%s' buffer offset is out of range\n", - path, ri->desc ); - return NULL; - } +/* + * Simple mesh interface for OpenGL + * ---------------------------------------------------------------------------- + */ - if( ri->offset + ri->size*ri->count > header->file_length ) - { - free( header ); - vg_error( "'%s': '%s' buffer size is out of range\n", - path, ri->desc ); - return NULL; - } +typedef struct glmesh glmesh; +struct glmesh +{ + GLuint vao, vbo, ebo; + u32 indice_count; + u32 loaded; +}; - for( int j=0; jcount == 0 ) - continue; - - if( ri->offset >= rj->offset && - (ri->offset+ri->size*ri->count < rj->offset+rj->size*rj->count)) - { - free( header ); - vg_error( "'%s': '%s' buffer overlaps '%s'\n", - path, ri->desc, rj->desc ); - return NULL; - } - } - } +VG_STATIC void mesh_upload( glmesh *mesh, + mdl_vert *verts, u32 vert_count, + u32 *indices, u32 indice_count ) +{ + //assert( mesh->loaded == 0 ); - /* - * Pointer validation TODO(workshop) - */ + glGenVertexArrays( 1, &mesh->vao ); + glGenBuffers( 1, &mesh->vbo ); + glGenBuffers( 1, &mesh->ebo ); + glBindVertexArray( mesh->vao ); - /* - * strings TODO(workshop) - */ + size_t stride = sizeof(mdl_vert); - return header; -} + glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo ); + glBufferData( GL_ARRAY_BUFFER, vert_count*stride, verts, GL_STATIC_DRAW ); -static void *mdl_baseptr( mdl_header *mdl, u32 offset ) -{ - return (void *)mdl + offset; -} + glBindVertexArray( mesh->vao ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo ); + glBufferData( GL_ELEMENT_ARRAY_BUFFER, indice_count*sizeof(u32), + indices, GL_STATIC_DRAW ); + + /* 0: coordinates */ + glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 ); + glEnableVertexAttribArray( 0 ); -static const char *mdl_pstr( mdl_header *mdl, u32 pstr ) -{ - return (const char *)(mdl_baseptr( mdl, mdl->strings_offset )) + pstr; -} + /* 1: normal */ + glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, + stride, (void *)offsetof(mdl_vert, norm) ); + glEnableVertexAttribArray( 1 ); -static mdl_node *mdl_node_from_id( mdl_header *mdl, u32 id ) -{ - return ((mdl_node *)mdl_baseptr( mdl, mdl->node_offset )) + id; -} + /* 2: uv */ + glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, + stride, (void *)offsetof(mdl_vert, uv) ); + glEnableVertexAttribArray( 2 ); -static mdl_node *mdl_node_from_name( mdl_header *mdl, const char *name ) -{ - for( int i=0; inode_count; i++ ) - { - mdl_node *pnode = mdl_node_from_id( mdl, i ); - - if( !strcmp( name, mdl_pstr( mdl, pnode->pstr_name )) ) - return pnode; - } - - return NULL; -} + /* 3: colour */ + glVertexAttribPointer( 3, 4, GL_UNSIGNED_BYTE, GL_TRUE, + stride, (void *)offsetof(mdl_vert, colour) ); + glEnableVertexAttribArray( 3 ); -static mdl_submesh *mdl_submesh_from_id( mdl_header *mdl, u32 id ) -{ - if( id >= mdl->submesh_count ) - return NULL; + /* 4: weights */ + glVertexAttribPointer( 4, 4, GL_UNSIGNED_SHORT, GL_TRUE, + stride, (void *)offsetof(mdl_vert, weights) ); + glEnableVertexAttribArray( 4 ); - return ((mdl_submesh *)mdl_baseptr( mdl, mdl->submesh_offset )) + id; -} + /* 5: groups */ + glVertexAttribIPointer( 5, 4, GL_UNSIGNED_BYTE, + stride, (void *)offsetof(mdl_vert, groups) ); + glEnableVertexAttribArray( 5 ); -static mdl_submesh *mdl_node_submesh( mdl_header *mdl, mdl_node *node, u32 i ) -{ - if( i >= node->submesh_count ) - return NULL; + VG_CHECK_GL_ERR(); - return mdl_submesh_from_id( mdl, node->submesh_start+i ); + mesh->indice_count = indice_count; + mesh->loaded = 1; } -static u32 *mdl_submesh_indices( mdl_header *mdl, mdl_submesh *sm ) +VG_STATIC void mesh_bind( glmesh *mesh ) { - return ((u32 *)mdl_baseptr( mdl, mdl->indice_offset )) + sm->indice_start; + glBindVertexArray( mesh->vao ); } -static mdl_vert *mdl_submesh_vertices( mdl_header *mdl, mdl_submesh *sm ) +VG_STATIC void mesh_drawn( u32 start, u32 count ) { - return ((mdl_vert *)mdl_baseptr(mdl,mdl->vertex_offset)) + sm->vertex_start; + glDrawElements( GL_TRIANGLES, count, GL_UNSIGNED_INT, + (void *)(start*sizeof(u32)) ); } -static mdl_material *mdl_material_from_id( mdl_header *mdl, u32 id ) +VG_STATIC void mesh_draw( glmesh *mesh ) { - return ((mdl_material *)mdl_baseptr(mdl,mdl->material_offset)) + id; + mesh_drawn( 0, mesh->indice_count ); } -static void mdl_node_transform( mdl_node *pnode, m4x3f transform ) +VG_STATIC void mesh_free( glmesh *mesh ) { - q_m3x3( pnode->q, transform ); - v3_muls( transform[0], pnode->s[0], transform[0] ); - v3_muls( transform[1], pnode->s[1], transform[1] ); - v3_muls( transform[2], pnode->s[2], transform[2] ); - v3_copy( pnode->co, transform[3] ); + if( mesh->loaded ){ + glDeleteVertexArrays( 1, &mesh->vao ); + glDeleteBuffers( 1, &mesh->ebo ); + glDeleteBuffers( 1, &mesh->vbo ); + mesh->loaded = 0; + } } -static void mdl_unpack_submesh( mdl_header *mdl, glmesh *mesh, mdl_submesh *sm ) +VG_STATIC void mdl_draw_submesh( mdl_submesh *sm ) { - mesh_upload( mesh, mdl_submesh_vertices( mdl, sm ), sm->vertex_count, - mdl_submesh_indices( mdl, sm ), sm->indice_count ); + mesh_drawn( sm->indice_start, sm->indice_count ); } -static void mdl_unpack_glmesh( mdl_header *mdl, glmesh *mesh ) +/* WARNING: Destructive! Only use this once and then discard the context. */ +VG_STATIC void mdl_unpack_glmesh( mdl_context *mdl, glmesh *mesh ) { - u32 offset = mdl_submesh_from_id( mdl, 0 )->vertex_count; + if( !mdl->submeshs.count ) + vg_fatal_exit_loop( "Tried to unpack empty model file" ); - for( int i=1; i< mdl->submesh_count; i++ ) - { - mdl_submesh *sm = mdl_submesh_from_id( mdl, i ); - u32 *indices = mdl_submesh_indices( mdl, sm ); + mdl_submesh *sm = mdl_arritm( &mdl->submeshs, 0 ); + u32 offset = sm->vertex_count; + + for( u32 i=1; isubmeshs ); i++ ){ + mdl_submesh *sm = mdl_arritm( &mdl->submeshs, i ); + u32 *indices = mdl_arritm( &mdl->indices, sm->indice_start ); for( u32 j=0; jindice_count; j++ ) indices[j] += offset; @@ -417,59 +474,19 @@ static void mdl_unpack_glmesh( mdl_header *mdl, glmesh *mesh ) offset += sm->vertex_count; } - mdl_vert *vertex_base = mdl_baseptr( mdl, mdl->vertex_offset ); - u32 *indice_base = mdl_baseptr( mdl, mdl->indice_offset ); - - mesh_upload( mesh, vertex_base, mdl->vertex_count, - indice_base, mdl->indice_count ); + mesh_upload( mesh, mdl->verts.data, mdl->verts.count, + mdl->indices.data, mdl->indices.count ); } -static void mdl_draw_submesh( mdl_submesh *sm ) +VG_STATIC mdl_mesh *mdl_find_mesh( mdl_context *mdl, const char *name ) { - mesh_drawn( sm->indice_start, sm->indice_count ); -} - -static void *mdl_get_entdata( mdl_header *mdl, mdl_node *pnode ) -{ - return mdl_baseptr( mdl, mdl->entdata_offset ) + pnode->offset; -} - -static void mdl_link_materials( mdl_header *root, mdl_header *child ) -{ - u32 lookup[MDL_MATERIAL_MAX]; - - for( int i=0; imaterial_count; i++ ) - { - mdl_material *mi = mdl_material_from_id( child, i ); - const char *si = mdl_pstr( child, mi->pstr_name ); - - lookup[i] = 0; - - for( int j=0; jmaterial_count; j++ ) - { - mdl_material *mj = mdl_material_from_id( root, j ); - const char *sj = mdl_pstr( root, mj->pstr_name ); - - if( !strcmp( si, sj ) ) - { - lookup[i] = j; - break; - } - } - - if( lookup[i] == 0 && i != 0 ) - { - vg_warn( "Could not link material '%s' (not present in root model)\n", - si ); + for( u32 i=0; imeshs ); i++ ){ + mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i ); + if( !strcmp( name, mdl_pstr( mdl, mesh->pstr_name ))){ + return mesh; } } - - for( int i=0; isubmesh_count; i++ ) - { - mdl_submesh *sm = mdl_submesh_from_id( child, i ); - sm->material_id = lookup[sm->material_id]; - } + return NULL; } - - + #endif