/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * ======================================================================= * * Model loading and caching. Includes the .bsp file format * * ======================================================================= */ #include "header/local.h" #define MAX_MOD_KNOWN 512 model_t *loadmodel; int modfilelen; byte mod_novis [ MAX_MAP_LEAFS / 8 ]; model_t mod_known [ MAX_MOD_KNOWN ]; int mod_numknown; int registration_sequence; byte *mod_base; void LoadSP2 ( model_t *mod, void *buffer ); void Mod_LoadBrushModel ( model_t *mod, void *buffer ); void LoadMD2 ( model_t *mod, void *buffer ); model_t *Mod_LoadModel ( model_t *mod, qboolean crash ); void GL_BuildPolygonFromSurface ( msurface_t *fa ); void GL_CreateSurfaceLightmap ( msurface_t *surf ); void GL_EndBuildingLightmaps ( void ); void GL_BeginBuildingLightmaps ( model_t *m ); /* the inline * models from the current map are kept seperate */ model_t mod_inline [ MAX_MOD_KNOWN ]; mleaf_t * Mod_PointInLeaf ( vec3_t p, model_t *model ) { mnode_t *node; float d; cplane_t *plane; if ( !model || !model->nodes ) { ri.Sys_Error( ERR_DROP, "Mod_PointInLeaf: bad model" ); } node = model->nodes; while ( 1 ) { if ( node->contents != -1 ) { return ( (mleaf_t *) node ); } plane = node->plane; d = DotProduct( p, plane->normal ) - plane->dist; if ( d > 0 ) { node = node->children [ 0 ]; } else { node = node->children [ 1 ]; } } return ( NULL ); /* never reached */ } byte * Mod_DecompressVis ( byte *in, model_t *model ) { static byte decompressed [ MAX_MAP_LEAFS / 8 ]; int c; byte *out; int row; row = ( model->vis->numclusters + 7 ) >> 3; out = decompressed; if ( !in ) { /* no vis info, so make all visible */ while ( row ) { *out++ = 0xff; row--; } return ( decompressed ); } do { if ( *in ) { *out++ = *in++; continue; } c = in [ 1 ]; in += 2; while ( c ) { *out++ = 0; c--; } } while ( out - decompressed < row ); return ( decompressed ); } byte * Mod_ClusterPVS ( int cluster, model_t *model ) { if ( ( cluster == -1 ) || !model->vis ) { return ( mod_novis ); } return ( Mod_DecompressVis( (byte *) model->vis + model->vis->bitofs [ cluster ] [ DVIS_PVS ], model ) ); } void Mod_Modellist_f ( void ) { int i; model_t *mod; int total; total = 0; ri.Con_Printf( PRINT_ALL, "Loaded models:\n" ); for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) { if ( !mod->name [ 0 ] ) { continue; } ri.Con_Printf( PRINT_ALL, "%8i : %s\n", mod->extradatasize, mod->name ); total += mod->extradatasize; } ri.Con_Printf( PRINT_ALL, "Total resident: %i\n", total ); } void Mod_Init ( void ) { memset( mod_novis, 0xff, sizeof ( mod_novis ) ); } /* * Loads in a model for the given name */ model_t * Mod_ForName ( char *name, qboolean crash ) { model_t *mod; unsigned *buf; int i; if ( !name [ 0 ] ) { ri.Sys_Error( ERR_DROP, "Mod_ForName: NULL name" ); } /* inline models are grabbed only from worldmodel */ if ( name [ 0 ] == '*' ) { i = atoi( name + 1 ); if ( ( i < 1 ) || !r_worldmodel || ( i >= r_worldmodel->numsubmodels ) ) { ri.Sys_Error( ERR_DROP, "bad inline model number" ); } return ( &mod_inline [ i ] ); } /* search the currently loaded models */ for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) { if ( !mod->name [ 0 ] ) { continue; } if ( !strcmp( mod->name, name ) ) { return ( mod ); } } /* find a free model slot spot */ for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) { if ( !mod->name [ 0 ] ) { break; /* free spot */ } } if ( i == mod_numknown ) { if ( mod_numknown == MAX_MOD_KNOWN ) { ri.Sys_Error( ERR_DROP, "mod_numknown == MAX_MOD_KNOWN" ); } mod_numknown++; } strcpy( mod->name, name ); /* load the file */ modfilelen = ri.FS_LoadFile( mod->name, (void **) &buf ); if ( !buf ) { if ( crash ) { ri.Sys_Error( ERR_DROP, "Mod_NumForName: %s not found", mod->name ); } memset( mod->name, 0, sizeof ( mod->name ) ); return ( NULL ); } loadmodel = mod; /* call the apropriate loader */ switch ( LittleLong( *(unsigned *) buf ) ) { case IDALIASHEADER: loadmodel->extradata = Hunk_Begin( 0x200000 ); LoadMD2( mod, buf ); break; case IDSPRITEHEADER: loadmodel->extradata = Hunk_Begin( 0x10000 ); LoadSP2( mod, buf ); break; case IDBSPHEADER: loadmodel->extradata = Hunk_Begin( 0x1000000 ); Mod_LoadBrushModel( mod, buf ); break; default: ri.Sys_Error( ERR_DROP, "Mod_NumForName: unknown fileid for %s", mod->name ); break; } loadmodel->extradatasize = Hunk_End(); ri.FS_FreeFile( buf ); return ( mod ); } void Mod_LoadLighting ( lump_t *l ) { if ( !l->filelen ) { loadmodel->lightdata = NULL; return; } loadmodel->lightdata = Hunk_Alloc( l->filelen ); memcpy( loadmodel->lightdata, mod_base + l->fileofs, l->filelen ); } void Mod_LoadVisibility ( lump_t *l ) { int i; if ( !l->filelen ) { loadmodel->vis = NULL; return; } loadmodel->vis = Hunk_Alloc( l->filelen ); memcpy( loadmodel->vis, mod_base + l->fileofs, l->filelen ); loadmodel->vis->numclusters = LittleLong( loadmodel->vis->numclusters ); for ( i = 0; i < loadmodel->vis->numclusters; i++ ) { loadmodel->vis->bitofs [ i ] [ 0 ] = LittleLong( loadmodel->vis->bitofs [ i ] [ 0 ] ); loadmodel->vis->bitofs [ i ] [ 1 ] = LittleLong( loadmodel->vis->bitofs [ i ] [ 1 ] ); } } void Mod_LoadVertexes ( lump_t *l ) { dvertex_t *in; mvertex_t *out; int i, count; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->vertexes = out; loadmodel->numvertexes = count; for ( i = 0; i < count; i++, in++, out++ ) { out->position [ 0 ] = LittleFloat( in->point [ 0 ] ); out->position [ 1 ] = LittleFloat( in->point [ 1 ] ); out->position [ 2 ] = LittleFloat( in->point [ 2 ] ); } } float Mod_RadiusFromBounds ( vec3_t mins, vec3_t maxs ) { int i; vec3_t corner; for ( i = 0; i < 3; i++ ) { corner [ i ] = fabs( mins [ i ] ) > fabs( maxs [ i ] ) ? fabs( mins [ i ] ) : fabs( maxs [ i ] ); } return ( VectorLength( corner ) ); } void Mod_LoadSubmodels ( lump_t *l ) { dmodel_t *in; mmodel_t *out; int i, j, count; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->submodels = out; loadmodel->numsubmodels = count; for ( i = 0; i < count; i++, in++, out++ ) { for ( j = 0; j < 3; j++ ) { /* spread the mins / maxs by a pixel */ out->mins [ j ] = LittleFloat( in->mins [ j ] ) - 1; out->maxs [ j ] = LittleFloat( in->maxs [ j ] ) + 1; out->origin [ j ] = LittleFloat( in->origin [ j ] ); } out->radius = Mod_RadiusFromBounds( out->mins, out->maxs ); out->headnode = LittleLong( in->headnode ); out->firstface = LittleLong( in->firstface ); out->numfaces = LittleLong( in->numfaces ); } } void Mod_LoadEdges ( lump_t *l ) { dedge_t *in; medge_t *out; int i, count; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( ( count + 1 ) * sizeof ( *out ) ); loadmodel->edges = out; loadmodel->numedges = count; for ( i = 0; i < count; i++, in++, out++ ) { out->v [ 0 ] = (unsigned short) LittleShort( in->v [ 0 ] ); out->v [ 1 ] = (unsigned short) LittleShort( in->v [ 1 ] ); } } void Mod_LoadTexinfo ( lump_t *l ) { texinfo_t *in; mtexinfo_t *out, *step; int i, j, count; char name [ MAX_QPATH ]; int next; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->texinfo = out; loadmodel->numtexinfo = count; for ( i = 0; i < count; i++, in++, out++ ) { for ( j = 0; j < 8; j++ ) { out->vecs [ 0 ] [ j ] = LittleFloat( in->vecs [ 0 ] [ j ] ); } out->flags = LittleLong( in->flags ); next = LittleLong( in->nexttexinfo ); if ( next > 0 ) { out->next = loadmodel->texinfo + next; } else { out->next = NULL; } Com_sprintf( name, sizeof ( name ), "textures/%s.wal", in->texture ); out->image = R_FindImage( name, it_wall ); if ( !out->image ) { ri.Con_Printf( PRINT_ALL, "Couldn't load %s\n", name ); out->image = r_notexture; } } /* count animation frames */ for ( i = 0; i < count; i++ ) { out = &loadmodel->texinfo [ i ]; out->numframes = 1; for ( step = out->next; step && step != out; step = step->next ) { out->numframes++; } } } /* * Fills in s->texturemins[] and s->extents[] */ void Mod_CalcSurfaceExtents ( msurface_t *s ) { float mins [ 2 ], maxs [ 2 ], val; int i, j, e; mvertex_t *v; mtexinfo_t *tex; int bmins [ 2 ], bmaxs [ 2 ]; mins [ 0 ] = mins [ 1 ] = 999999; maxs [ 0 ] = maxs [ 1 ] = -99999; tex = s->texinfo; for ( i = 0; i < s->numedges; i++ ) { e = loadmodel->surfedges [ s->firstedge + i ]; if ( e >= 0 ) { v = &loadmodel->vertexes [ loadmodel->edges [ e ].v [ 0 ] ]; } else { v = &loadmodel->vertexes [ loadmodel->edges [ -e ].v [ 1 ] ]; } for ( j = 0; j < 2; j++ ) { val = v->position [ 0 ] * tex->vecs [ j ] [ 0 ] + v->position [ 1 ] * tex->vecs [ j ] [ 1 ] + v->position [ 2 ] * tex->vecs [ j ] [ 2 ] + tex->vecs [ j ] [ 3 ]; if ( val < mins [ j ] ) { mins [ j ] = val; } if ( val > maxs [ j ] ) { maxs [ j ] = val; } } } for ( i = 0; i < 2; i++ ) { bmins [ i ] = floor( mins [ i ] / 16 ); bmaxs [ i ] = ceil( maxs [ i ] / 16 ); s->texturemins [ i ] = bmins [ i ] * 16; s->extents [ i ] = ( bmaxs [ i ] - bmins [ i ] ) * 16; } } void Mod_LoadFaces ( lump_t *l ) { dface_t *in; msurface_t *out; int i, count, surfnum; int planenum, side; int ti; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->surfaces = out; loadmodel->numsurfaces = count; currentmodel = loadmodel; GL_BeginBuildingLightmaps( loadmodel ); for ( surfnum = 0; surfnum < count; surfnum++, in++, out++ ) { out->firstedge = LittleLong( in->firstedge ); out->numedges = LittleShort( in->numedges ); out->flags = 0; out->polys = NULL; planenum = LittleShort( in->planenum ); side = LittleShort( in->side ); if ( side ) { out->flags |= SURF_PLANEBACK; } out->plane = loadmodel->planes + planenum; ti = LittleShort( in->texinfo ); if ( ( ti < 0 ) || ( ti >= loadmodel->numtexinfo ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: bad texinfo number" ); } out->texinfo = loadmodel->texinfo + ti; Mod_CalcSurfaceExtents( out ); /* lighting info */ for ( i = 0; i < MAXLIGHTMAPS; i++ ) { out->styles [ i ] = in->styles [ i ]; } i = LittleLong( in->lightofs ); if ( i == -1 ) { out->samples = NULL; } else { out->samples = loadmodel->lightdata + i; } /* set the drawing flags */ if ( out->texinfo->flags & SURF_WARP ) { out->flags |= SURF_DRAWTURB; for ( i = 0; i < 2; i++ ) { out->extents [ i ] = 16384; out->texturemins [ i ] = -8192; } GL_SubdivideSurface( out ); /* cut up polygon for warps */ } /* create lightmaps and polygons */ if ( !( out->texinfo->flags & ( SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP ) ) ) { GL_CreateSurfaceLightmap( out ); } if ( !( out->texinfo->flags & SURF_WARP ) ) { GL_BuildPolygonFromSurface( out ); } } GL_EndBuildingLightmaps(); } void Mod_SetParent ( mnode_t *node, mnode_t *parent ) { node->parent = parent; if ( node->contents != -1 ) { return; } Mod_SetParent( node->children [ 0 ], node ); Mod_SetParent( node->children [ 1 ], node ); } void Mod_LoadNodes ( lump_t *l ) { int i, j, count, p; dnode_t *in; mnode_t *out; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->nodes = out; loadmodel->numnodes = count; for ( i = 0; i < count; i++, in++, out++ ) { for ( j = 0; j < 3; j++ ) { out->minmaxs [ j ] = LittleShort( in->mins [ j ] ); out->minmaxs [ 3 + j ] = LittleShort( in->maxs [ j ] ); } p = LittleLong( in->planenum ); out->plane = loadmodel->planes + p; out->firstsurface = LittleShort( in->firstface ); out->numsurfaces = LittleShort( in->numfaces ); out->contents = -1; /* differentiate from leafs */ for ( j = 0; j < 2; j++ ) { p = LittleLong( in->children [ j ] ); if ( p >= 0 ) { out->children [ j ] = loadmodel->nodes + p; } else { out->children [ j ] = (mnode_t *) ( loadmodel->leafs + ( -1 - p ) ); } } } Mod_SetParent( loadmodel->nodes, NULL ); /* sets nodes and leafs */ } void Mod_LoadLeafs ( lump_t *l ) { dleaf_t *in; mleaf_t *out; int i, j, count, p; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->leafs = out; loadmodel->numleafs = count; for ( i = 0; i < count; i++, in++, out++ ) { for ( j = 0; j < 3; j++ ) { out->minmaxs [ j ] = LittleShort( in->mins [ j ] ); out->minmaxs [ 3 + j ] = LittleShort( in->maxs [ j ] ); } p = LittleLong( in->contents ); out->contents = p; out->cluster = LittleShort( in->cluster ); out->area = LittleShort( in->area ); out->firstmarksurface = loadmodel->marksurfaces + LittleShort( in->firstleafface ); out->nummarksurfaces = LittleShort( in->numleaffaces ); } } void Mod_LoadMarksurfaces ( lump_t *l ) { int i, j, count; short *in; msurface_t **out; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->marksurfaces = out; loadmodel->nummarksurfaces = count; for ( i = 0; i < count; i++ ) { j = LittleShort( in [ i ] ); if ( ( j < 0 ) || ( j >= loadmodel->numsurfaces ) ) { ri.Sys_Error( ERR_DROP, "Mod_ParseMarksurfaces: bad surface number" ); } out [ i ] = loadmodel->surfaces + j; } } void Mod_LoadSurfedges ( lump_t *l ) { int i, count; int *in, *out; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); if ( ( count < 1 ) || ( count >= MAX_MAP_SURFEDGES ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: bad surfedges count in %s: %i", loadmodel->name, count ); } out = Hunk_Alloc( count * sizeof ( *out ) ); loadmodel->surfedges = out; loadmodel->numsurfedges = count; for ( i = 0; i < count; i++ ) { out [ i ] = LittleLong( in [ i ] ); } } void Mod_LoadPlanes ( lump_t *l ) { int i, j; cplane_t *out; dplane_t *in; int count; int bits; in = (void *) ( mod_base + l->fileofs ); if ( l->filelen % sizeof ( *in ) ) { ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name ); } count = l->filelen / sizeof ( *in ); out = Hunk_Alloc( count * 2 * sizeof ( *out ) ); loadmodel->planes = out; loadmodel->numplanes = count; for ( i = 0; i < count; i++, in++, out++ ) { bits = 0; for ( j = 0; j < 3; j++ ) { out->normal [ j ] = LittleFloat( in->normal [ j ] ); if ( out->normal [ j ] < 0 ) { bits |= 1 << j; } } out->dist = LittleFloat( in->dist ); out->type = LittleLong( in->type ); out->signbits = bits; } } void Mod_LoadBrushModel ( model_t *mod, void *buffer ) { int i; dheader_t *header; mmodel_t *bm; loadmodel->type = mod_brush; if ( loadmodel != mod_known ) { ri.Sys_Error( ERR_DROP, "Loaded a brush model after the world" ); } header = (dheader_t *) buffer; i = LittleLong( header->version ); if ( i != BSPVERSION ) { ri.Sys_Error( ERR_DROP, "Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, i, BSPVERSION ); } /* swap all the lumps */ mod_base = (byte *) header; for ( i = 0; i < sizeof ( dheader_t ) / 4; i++ ) { ( (int *) header ) [ i ] = LittleLong( ( (int *) header ) [ i ] ); } /* load into heap */ Mod_LoadVertexes( &header->lumps [ LUMP_VERTEXES ] ); Mod_LoadEdges( &header->lumps [ LUMP_EDGES ] ); Mod_LoadSurfedges( &header->lumps [ LUMP_SURFEDGES ] ); Mod_LoadLighting( &header->lumps [ LUMP_LIGHTING ] ); Mod_LoadPlanes( &header->lumps [ LUMP_PLANES ] ); Mod_LoadTexinfo( &header->lumps [ LUMP_TEXINFO ] ); Mod_LoadFaces( &header->lumps [ LUMP_FACES ] ); Mod_LoadMarksurfaces( &header->lumps [ LUMP_LEAFFACES ] ); Mod_LoadVisibility( &header->lumps [ LUMP_VISIBILITY ] ); Mod_LoadLeafs( &header->lumps [ LUMP_LEAFS ] ); Mod_LoadNodes( &header->lumps [ LUMP_NODES ] ); Mod_LoadSubmodels( &header->lumps [ LUMP_MODELS ] ); mod->numframes = 2; /* regular and alternate animation */ /* set up the submodels */ for ( i = 0; i < mod->numsubmodels; i++ ) { model_t *starmod; bm = &mod->submodels [ i ]; starmod = &mod_inline [ i ]; *starmod = *loadmodel; starmod->firstmodelsurface = bm->firstface; starmod->nummodelsurfaces = bm->numfaces; starmod->firstnode = bm->headnode; if ( starmod->firstnode >= loadmodel->numnodes ) { ri.Sys_Error( ERR_DROP, "Inline model %i has bad firstnode", i ); } VectorCopy( bm->maxs, starmod->maxs ); VectorCopy( bm->mins, starmod->mins ); starmod->radius = bm->radius; if ( i == 0 ) { *loadmodel = *starmod; } starmod->numleafs = bm->visleafs; } } void Mod_Free ( model_t *mod ) { Hunk_Free( mod->extradata ); memset( mod, 0, sizeof ( *mod ) ); } void Mod_FreeAll ( void ) { int i; for ( i = 0; i < mod_numknown; i++ ) { if ( mod_known [ i ].extradatasize ) { Mod_Free( &mod_known [ i ] ); } } } /* * Specifies the model that will be used as the world */ void R_BeginRegistration ( char *model ) { char fullname [ MAX_QPATH ]; cvar_t *flushmap; registration_sequence++; r_oldviewcluster = -1; /* force markleafs */ Com_sprintf( fullname, sizeof ( fullname ), "maps/%s.bsp", model ); /* explicitly free the old map if different this guarantees that mod_known[0] is the world map */ flushmap = ri.Cvar_Get( "flushmap", "0", 0 ); if ( strcmp( mod_known [ 0 ].name, fullname ) || flushmap->value ) { Mod_Free( &mod_known [ 0 ] ); } r_worldmodel = Mod_ForName( fullname, true ); r_viewcluster = -1; } struct model_s * R_RegisterModel ( char *name ) { model_t *mod; int i; dsprite_t *sprout; dmdl_t *pheader; mod = Mod_ForName( name, false ); if ( mod ) { mod->registration_sequence = registration_sequence; /* register any images used by the models */ if ( mod->type == mod_sprite ) { sprout = (dsprite_t *) mod->extradata; for ( i = 0; i < sprout->numframes; i++ ) { mod->skins [ i ] = R_FindImage( sprout->frames [ i ].name, it_sprite ); } } else if ( mod->type == mod_alias ) { pheader = (dmdl_t *) mod->extradata; for ( i = 0; i < pheader->num_skins; i++ ) { mod->skins [ i ] = R_FindImage( (char *) pheader + pheader->ofs_skins + i * MAX_SKINNAME, it_skin ); } mod->numframes = pheader->num_frames; } else if ( mod->type == mod_brush ) { for ( i = 0; i < mod->numtexinfo; i++ ) { mod->texinfo [ i ].image->registration_sequence = registration_sequence; } } } return ( mod ); } void R_EndRegistration ( void ) { int i; model_t *mod; for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) { if ( !mod->name [ 0 ] ) { continue; } if ( mod->registration_sequence != registration_sequence ) { /* don't need this model */ Mod_Free( mod ); } } R_FreeUnusedImages(); }