/* 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. */ // cmodel.c -- model loading #include "qcommon.h" #include "cm_local.h" int checkcount; mempool_t *cmap_mempool = NULL; static bspFormatDesc_t *cmap_bspFormat; char map_name[MAX_QPATH]; qboolean map_clientload; int numbrushsides; cbrushside_t *map_brushsides; int numshaderrefs; cshaderref_t *map_shaderrefs; int numplanes; cplane_t *map_planes; int numnodes; cnode_t *map_nodes; int numleafs = 1; static cleaf_t map_leaf_empty; // allow leaf funcs to be called without a map cleaf_t *map_leafs = &map_leaf_empty; int nummarkbrushes; cbrush_t **map_markbrushes; int numcmodels; static cmodel_t map_cmodel_empty; cmodel_t *map_cmodels = &map_cmodel_empty; vec3_t world_mins, world_maxs; int numbrushes; cbrush_t *map_brushes; int numfaces; cface_t *map_faces; int nummarkfaces; cface_t **map_markfaces; vec3_t *map_verts; // this will be freed int numvertexes; // each area has a list of portals that lead into other areas // when portals are closed, other areas may not be visible or // hearable even if the vis info says that it should be int numareaportals = 1; careaportal_t map_areaportals[MAX_CM_AREAPORTALS]; int numareas = 1; static carea_t map_area_empty; carea_t *map_areas = &map_area_empty; dvis_t *map_pvs, *map_phs; int map_visdatasize; qbyte nullrow[MAX_CM_LEAFS/8]; int numentitychars; static char map_entitystring_empty; char *map_entitystring = &map_entitystring_empty; int floodvalid; cvar_t *cm_noAreas; cvar_t *cm_noCurves; int c_pointcontents; int c_traces, c_brush_traces; qbyte *cmod_base; /* =============================================================================== PATCH LOADING =============================================================================== */ /* ================= CM_CreateFacetFromPoints ================= */ static int CM_CreateFacetFromPoints( cfacet_t *facet, vec3_t *verts, int numverts, cshaderref_t *shaderref ) { int i, j, k; int axis, dir; cbrushside_t *s; cplane_t *planes; vec3_t normal; float d, dist; cplane_t mainplane; vec3_t vec, vec2; int numbrushplanes; cplane_t brushplanes[32]; // set default values for brush facet->brush.numsides = 0; facet->brush.brushsides = NULL; facet->brush.contents = shaderref->contents; // calculate plane for this triangle PlaneFromPoints( verts, &mainplane ); if( ComparePlanes( mainplane.normal, mainplane.dist, vec3_origin, 0 ) ) return 0; // test a quad case if( numverts > 3 ) { vec3_t v[3]; cplane_t plane; d = DotProduct( verts[3], mainplane.normal ) - mainplane.dist; if( d < -0.1 || d > 0.1 ) return 0; // try different combinations of planes for( i = 1; i < 4; i++ ) { VectorCopy( verts[i], v[0] ); VectorCopy( verts[(i+1)%4], v[1] ); VectorCopy( verts[(i+2)%4], v[2] ); PlaneFromPoints ( verts, &plane ); if( DotProduct( mainplane.normal, plane.normal ) < 0.9 ) return 0; } } numbrushplanes = 0; // add front plane SnapPlane ( mainplane.normal, &mainplane.dist ); VectorCopy( mainplane.normal, brushplanes[numbrushplanes].normal ); brushplanes[numbrushplanes].dist = mainplane.dist; numbrushplanes++; // calculate mins & maxs ClearBounds( facet->mins, facet->maxs ); for( i = 0; i < numverts; i++ ) AddPointToBounds( verts[i], facet->mins, facet->maxs ); // add the axial planes for( axis = 0; axis < 3; axis++ ) { for( dir = -1; dir <= 1; dir += 2 ) { for( i = 0; i < numbrushplanes; i++ ) { if( brushplanes[i].normal[axis] == dir ) break; } if( i == numbrushplanes ) { VectorClear( normal ); normal[axis] = dir; if (dir == 1) dist = facet->maxs[axis]; else dist = -facet->mins[axis]; VectorCopy( normal, brushplanes[numbrushplanes].normal ); brushplanes[numbrushplanes].dist = dist; numbrushplanes++; } } } // add the edge bevels for( i = 0; i < numverts; i++ ) { j = (i + 1) % numverts; k = (i + 2) % numverts; VectorSubtract( verts[i], verts[j], vec ); if( VectorNormalize( vec ) < 0.5 ) continue; SnapVector( vec ); for( j = 0; j < 3; j++ ) { if( vec[j] == 1 || vec[j] == -1 ) break; // axial } if( j != 3 ) continue; // only test non-axial edges // try the six possible slanted axials from this edge for( axis = 0; axis < 3; axis++ ) { for( dir = -1; dir <= 1; dir += 2 ) { // construct a plane VectorClear( vec2 ); vec2[axis] = dir; CrossProduct( vec, vec2, normal ); if( VectorNormalize( normal ) < 0.5 ) continue; dist = DotProduct( verts[i], normal ); for( j = 0; j < numbrushplanes; j++ ) { // if this plane has already been used, skip it if( ComparePlanes( brushplanes[j].normal, brushplanes[j].dist, normal, dist ) ) break; } if( j != numbrushplanes ) continue; // if all other points are behind this plane, it is a proper edge bevel for( j = 0; j < numverts; j++ ) { if( j != i ) { d = DotProduct( verts[j], normal ) - dist; if( d > 0.1 ) break; // point in front: this plane isn't part of the outer hull } } if( j != numverts ) continue; // add this plane VectorCopy( normal, brushplanes[numbrushplanes].normal ); brushplanes[numbrushplanes].dist = dist; numbrushplanes++; } } } if( !numbrushplanes ) return 0; // add brushsides s = facet->brush.brushsides = Mem_Alloc( cmap_mempool, numbrushplanes * sizeof (*s) ); planes = Mem_Alloc( cmap_mempool, numbrushplanes * sizeof(cplane_t) ); for( i = 0; i < numbrushplanes; i++, s++ ) { planes[i] = brushplanes[i]; SnapPlane( planes[i].normal, &planes[i].dist ); CategorizePlane( &planes[i] ); s->plane = &planes[i]; s->surfFlags = shaderref->flags; } return( facet->brush.numsides = numbrushplanes ); } /* ================= CM_CreatePatch ================= */ static void CM_CreatePatch( cface_t *patch, cshaderref_t *shaderref, vec3_t *verts, int *patch_cp ) { int step[2], size[2], flat[2], i, u, v; cfacet_t *facets; vec3_t *points; vec3_t tverts[4]; // find the degree of subdivision in the u and v directions Patch_GetFlatness( CM_SUBDIV_LEVEL, verts, patch_cp, flat ); step[0] = 1 << flat[0]; step[1] = 1 << flat[1]; size[0] = ( patch_cp[0] >> 1 ) * step[0] + 1; size[1] = ( patch_cp[1] >> 1 ) * step[1] + 1; if( size[0] <= 0 || size[1] <= 0 ) return; points = Mem_Alloc( cmap_mempool, size[0] * size[1] * sizeof (vec3_t) ); facets = Mem_Alloc( cmap_mempool, (size[0]-1) * (size[1]-1) * 2 * sizeof (cfacet_t) ); // fill in Patch_Evaluate( verts[0], patch_cp, step, points[0], 3 ); patch->numfacets = 0; patch->facets = NULL; ClearBounds ( patch->mins, patch->maxs ); // create a set of facets for( v = 0; v < size[1]-1; v++ ) { for( u = 0; u < size[0]-1; u++ ) { i = v * size[0] + u; VectorCopy( points[i], tverts[0] ); VectorCopy( points[i + size[0]], tverts[1] ); VectorCopy( points[i + size[0] + 1], tverts[2] ); VectorCopy( points[i + 1], tverts[3] ); for( i = 0; i < 4; i++ ) AddPointToBounds( tverts[i], patch->mins, patch->maxs ); // try to create one facet from a quad if( CM_CreateFacetFromPoints( &facets[patch->numfacets], tverts, 4, shaderref ) ) { patch->numfacets++; continue; } VectorCopy( tverts[3], tverts[2] ); // create two facets from triangles if( CM_CreateFacetFromPoints( &facets[patch->numfacets], tverts, 3, shaderref ) ) patch->numfacets++; VectorCopy( tverts[2], tverts[0] ); VectorCopy( points[v * size[0] + u + size[0] + 1], tverts[2] ); if( CM_CreateFacetFromPoints( &facets[patch->numfacets], tverts, 3, shaderref ) ) patch->numfacets++; } } if( !patch->numfacets ) { ClearBounds( patch->mins, patch->maxs ); Mem_Free( points ); Mem_Free( facets ); return; } patch->contents = shaderref->contents; patch->facets = Mem_Alloc( cmap_mempool, patch->numfacets * sizeof(cfacet_t) ); memcpy( patch->facets, facets, patch->numfacets * sizeof(cfacet_t) ); Mem_Free( points ); Mem_Free( facets ); for( i = 0; i < 3; i++ ) { // spread the mins / maxs by a pixel patch->mins[i] -= 1; patch->maxs[i] += 1; } } /* =============================================================================== MAP LOADING =============================================================================== */ /* ================= CMod_LoadSurfaces ================= */ static void CMod_LoadSurfaces( lump_t *l ) { int i; int count; dshaderref_t *in; cshaderref_t *out; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadSurfaces: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "CMod_LoadSurfaces: map with no shaders" ); out = map_shaderrefs = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numshaderrefs = count; for( i = 0; i < count; i++, in++, out++ ) { out->flags = LittleLong( in->flags ); out->contents = LittleLong( in->contents ); } } /* ================= CMod_LoadVertexes ================= */ static void CMod_LoadVertexes( lump_t *l ) { int i; int count; dvertex_t *in; vec3_t *out; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMOD_LoadVertexes: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no vertexes" ); out = map_verts = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numvertexes = count; for( i = 0; i < count; i++, in++ ) { out[i][0] = LittleFloat( in->point[0] ); out[i][1] = LittleFloat( in->point[1] ); out[i][2] = LittleFloat( in->point[2] ); } } /* ================= CMod_LoadVertexes_RBSP ================= */ static void CMod_LoadVertexes_RBSP( lump_t *l ) { int i; int count; rdvertex_t *in; vec3_t *out; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadVertexes_RBSP: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no vertexes" ); out = map_verts = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numvertexes = count; for( i = 0; i < count; i++, in++ ) { out[i][0] = LittleFloat( in->point[0] ); out[i][1] = LittleFloat( in->point[1] ); out[i][2] = LittleFloat( in->point[2] ); } } /* ================= CMod_LoadFace ================= */ static inline void CMod_LoadFace( cface_t *out, int shadernum, int firstvert, int numverts, int *patch_cp ) { cshaderref_t *shaderref; shadernum = LittleLong( shadernum ); if( shadernum < 0 || shadernum >= numshaderrefs ) return; shaderref = &map_shaderrefs[shadernum]; if( !shaderref->contents || (shaderref->flags & SURF_NONSOLID) ) return; patch_cp[0] = LittleLong( patch_cp[0] ); patch_cp[1] = LittleLong( patch_cp[1] ); if( patch_cp[0] <= 0 || patch_cp[1] <= 0 ) return; firstvert = LittleLong( firstvert ); if( numverts <= 0 || firstvert < 0 || firstvert >= numvertexes ) return; CM_CreatePatch( out, shaderref, map_verts + firstvert, patch_cp ); } /* ================= CMod_LoadFaces ================= */ static void CMod_LoadFaces( lump_t *l ) { int i, count; dface_t *in; cface_t *out; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadFaces: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no faces" ); out = map_faces = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numfaces = count; for( i = 0; i < count; i++, in++, out++ ) { out->contents = 0; out->numfacets = 0; out->facets = NULL; if( LittleLong( in->facetype ) != FACETYPE_PATCH ) continue; CMod_LoadFace( out, in->shadernum, in->firstvert, in->numverts, in->patch_cp ); } } /* ================= CMod_LoadFaces_RBSP ================= */ static void CMod_LoadFaces_RBSP( lump_t *l ) { int i, count; rdface_t *in; cface_t *out; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadFaces_RBSP: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no faces" ); out = map_faces = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numfaces = count; for( i = 0; i < count; i++, in++, out++ ) { out->contents = 0; out->numfacets = 0; out->facets = NULL; if( LittleLong( in->facetype ) != FACETYPE_PATCH ) continue; CMod_LoadFace( out, in->shadernum, in->firstvert, in->numverts, in->patch_cp ); } } /* ================= CMod_LoadSubmodels ================= */ static void CMod_LoadSubmodels( lump_t *l ) { int i, j; int count; dmodel_t *in; cmodel_t *out; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadSubmodels: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no models" ); out = map_cmodels = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numcmodels = count; for( i = 0; i < count; i++, in++, out++ ) { out->nummarkfaces = LittleLong( in->numfaces ); out->markfaces = Mem_Alloc( cmap_mempool, out->nummarkfaces * sizeof( cface_t * ) ); out->nummarkbrushes = LittleLong( in->numbrushes ); out->markbrushes = Mem_Alloc( cmap_mempool, out->nummarkbrushes * sizeof( cbrush_t * ) ); for( j = 0; j < out->nummarkfaces; j++ ) out->markfaces[j] = map_faces + LittleLong( in->firstface ) + j; for( j = 0; j < out->nummarkbrushes; j++ ) out->markbrushes[j] = map_brushes + LittleLong( in->firstbrush ) + j; 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; } } } /* ================= CMod_LoadNodes ================= */ static void CMod_LoadNodes( lump_t *l ) { int i; int count; dnode_t *in; cnode_t *out; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadNodes: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map has no nodes" ); out = map_nodes = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numnodes = count; for( i = 0; i < 3; i++ ) { world_mins[i] = LittleFloat( in->mins[i] ); world_maxs[i] = LittleFloat( in->maxs[i] ); } for( i = 0; i < count; i++, out++, in++ ) { out->plane = map_planes + LittleLong( in->planenum ); out->children[0] = LittleLong( in->children[0] ); out->children[1] = LittleLong( in->children[1] ); } } /* ================= CMod_LoadMarkFaces ================= */ static void CMod_LoadMarkFaces( lump_t *l ) { int i, j; int count; cface_t **out; int *in; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadMarkFaces: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no leaffaces" ); map_markfaces = out = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); nummarkfaces = count; for( i = 0; i < count; i++ ) { j = LittleLong( in[i] ); if( j < 0 || j >= numfaces ) Com_Error( ERR_DROP, "CMod_LoadMarkFaces: bad surface number" ); out[i] = map_faces + j; } } /* ================= CMod_LoadLeafs ================= */ static void CMod_LoadLeafs( lump_t *l ) { int i, j; int count; cleaf_t *out; dleaf_t *in; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadLeafs: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no leafs" ); out = map_leafs = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numleafs = count; for( i = 0; i < count; i++, in++, out++ ) { out->contents = 0; out->cluster = LittleLong( in->cluster ); out->area = LittleLong( in->area ) + 1; out->markbrushes = map_markbrushes + LittleLong( in->firstleafbrush ); out->nummarkbrushes = LittleLong( in->numleafbrushes ); out->markfaces = map_markfaces + LittleLong( in->firstleafface ); out->nummarkfaces = LittleLong( in->numleaffaces ); // OR brushes' contents for( j = 0; j < out->nummarkbrushes; j++ ) out->contents |= out->markbrushes[j]->contents; // OR patches' contents for( j = 0; j < out->nummarkfaces; j++ ) out->contents |= out->markfaces[j]->contents; if( out->area >= numareas ) numareas = out->area + 1; } map_areas = Mem_Alloc( cmap_mempool, numareas * sizeof( *map_areas ) ); } /* ================= CMod_LoadPlanes ================= */ static void CMod_LoadPlanes( lump_t *l ) { int i, j; int count; cplane_t *out; dplane_t *in; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadPlanes: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no planes" ); out = map_planes = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numplanes = count; for( i = 0; i < count; i++, in++, out++ ) { out->signbits = 0; out->type = PLANE_NONAXIAL; for( j = 0; j < 3; j++ ) { out->normal[j] = LittleFloat( in->normal[j] ); if( out->normal[j] < 0 ) out->signbits |= (1 << j); if( out->normal[j] == 1.0f ) out->type = j; } out->dist = LittleFloat( in->dist ); } } /* ================= CMod_LoadMarkBrushes ================= */ static void CMod_LoadMarkBrushes( lump_t *l ) { int i; int count; cbrush_t **out; int *in; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadMarkBrushes: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no leafbrushes" ); out = map_markbrushes = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); nummarkbrushes = count; for( i = 0; i < count; i++, in++ ) out[i] = map_brushes + LittleLong( *in ); } /* ================= CMod_LoadBrushSides ================= */ static void CMod_LoadBrushSides( lump_t *l ) { int i, j; int count; cbrushside_t *out; dbrushside_t *in; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadBrushSides: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no brushsides" ); out = map_brushsides = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numbrushsides = count; for( i = 0; i < count; i++, in++, out++ ) { out->plane = map_planes + LittleLong( in->planenum ); j = LittleLong( in->shadernum ); if( j >= numshaderrefs ) Com_Error( ERR_DROP, "Bad brushside texinfo" ); out->surfFlags = map_shaderrefs[j].flags; } } /* ================= CMod_LoadBrushSides_RBSP ================= */ static void CMod_LoadBrushSides_RBSP( lump_t *l ) { int i, j; int count; cbrushside_t *out; rdbrushside_t *in; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadBrushSides_RBSP: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no brushsides" ); out = map_brushsides = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numbrushsides = count; for( i = 0; i < count; i++, in++, out++ ) { out->plane = map_planes + LittleLong( in->planenum ); j = LittleLong( in->shadernum ); if( j >= numshaderrefs ) Com_Error( ERR_DROP, "Bad brushside texinfo" ); out->surfFlags = map_shaderrefs[j].flags; } } /* ================= CMod_LoadBrushes ================= */ static void CMod_LoadBrushes( lump_t *l ) { int i; int count; dbrush_t *in; cbrush_t *out; int shaderref; in = ( void * )(cmod_base + l->fileofs); if( l->filelen % sizeof( *in ) ) Com_Error( ERR_DROP, "CMod_LoadBrushes: funny lump size" ); count = l->filelen / sizeof( *in ); if( count < 1 ) Com_Error( ERR_DROP, "Map with no brushes" ); out = map_brushes = Mem_Alloc( cmap_mempool, count * sizeof( *out ) ); numbrushes = count; for( i = 0; i < count; i++, out++, in++ ) { shaderref = LittleLong( in->shadernum ); out->contents = map_shaderrefs[shaderref].contents; out->numsides = LittleLong( in->numsides ); out->brushsides = map_brushsides + LittleLong( in->firstside ); } } /* ================= CMod_LoadVisibility ================= */ static void CMod_LoadVisibility( lump_t *l ) { map_visdatasize = l->filelen; if( !map_visdatasize ) { map_pvs = NULL; return; } map_pvs = Mem_Alloc( cmap_mempool, map_visdatasize ); memcpy( map_pvs, cmod_base + l->fileofs, map_visdatasize ); map_pvs->numclusters = LittleLong( map_pvs->numclusters ); map_pvs->rowsize = LittleLong( map_pvs->rowsize ); } /* ================= CMod_LoadEntityString ================= */ static void CMod_LoadEntityString( lump_t *l ) { numentitychars = l->filelen; if( !l->filelen ) return; map_entitystring = Mem_Alloc( cmap_mempool, numentitychars ); memcpy( map_entitystring, cmod_base + l->fileofs, l->filelen ); } /* ================== CM_LoadMap Loads in the map and all submodels ================== */ cmodel_t *CM_LoadMap( char *name, qboolean clientload, unsigned *checksum ) { unsigned *buf; int i; dheader_t header; int length, version; static unsigned last_checksum; map_clientload = clientload; if( !strcmp( map_name, name ) && ( clientload || !Cvar_VariableValue ("flushmap")) ) { *checksum = last_checksum; if( !clientload ) { memset( map_areaportals, 0, sizeof( map_areaportals ) ); CM_FloodAreaConnections (); } return map_cmodels; // still have the right version } // free old stuff if( !cmap_mempool ) cmap_mempool = Mem_AllocPool( NULL, "Collision Map" ); else Mem_EmptyPool( cmap_mempool ); numplanes = 0; numnodes = 0; numleafs = 0; numbrushes = 0; numbrushsides = 0; numcmodels = 0; numentitychars = 0; numvertexes = 0; numfaces = 0; nummarkfaces = 0; nummarkbrushes = 0; numareas = 1; numareaportals = 1; map_name[0] = 0; map_leafs = &map_leaf_empty; map_cmodels = &map_cmodel_empty; map_areas = &map_area_empty; map_entitystring = &map_entitystring_empty; ClearBounds( world_mins, world_maxs ); if( !name || !name[0] ) { numleafs = 1; numcmodels = 2; *checksum = 0; return map_cmodels; // cinematic servers won't have anything at all } // // load the file // length = FS_LoadFile( name, ( void ** )&buf, NULL, 0 ); if( !buf ) Com_Error( ERR_DROP, "Couldn't load %s", name ); last_checksum = LittleLong( Com_BlockChecksum( buf, length ) ); *checksum = last_checksum; header = *(dheader_t *)buf; version = LittleLong( header.version ); for( i = 0, cmap_bspFormat = bspFormats; i < numBspFormats; i++, cmap_bspFormat++ ) { if( !strncmp( (char *)buf, cmap_bspFormat->header, 4 ) && (version == cmap_bspFormat->version) ) break; } if( i == numBspFormats ) Com_Error( ERR_DROP, "CM_LoadMap: %s: unknown bsp format, version %i", name, version ); for( i = 0; i < sizeof(dheader_t) / 4; i++ ) ((int *)&header)[i] = LittleLong( ((int *)&header)[i]); cmod_base = ( qbyte * )buf; // load into heap CMod_LoadSurfaces( &header.lumps[LUMP_SHADERREFS] ); CMod_LoadPlanes( &header.lumps[LUMP_PLANES] ); if( cmap_bspFormat->flags & BSP_RAVEN ) CMod_LoadBrushSides_RBSP( &header.lumps[LUMP_BRUSHSIDES] ); else CMod_LoadBrushSides( &header.lumps[LUMP_BRUSHSIDES] ); CMod_LoadBrushes( &header.lumps[LUMP_BRUSHES] ); CMod_LoadMarkBrushes( &header.lumps[LUMP_LEAFBRUSHES] ); if( cmap_bspFormat->flags & BSP_RAVEN ) { CMod_LoadVertexes_RBSP( &header.lumps[LUMP_VERTEXES] ); CMod_LoadFaces_RBSP( &header.lumps[LUMP_FACES] ); } else { CMod_LoadVertexes( &header.lumps[LUMP_VERTEXES] ); CMod_LoadFaces( &header.lumps[LUMP_FACES] ); } CMod_LoadMarkFaces( &header.lumps[LUMP_LEAFFACES] ); CMod_LoadLeafs( &header.lumps[LUMP_LEAFS] ); CMod_LoadNodes( &header.lumps[LUMP_NODES] ); CMod_LoadSubmodels( &header.lumps[LUMP_MODELS] ); CMod_LoadVisibility( &header.lumps[LUMP_VISIBILITY] ); CMod_LoadEntityString( &header.lumps[LUMP_ENTITIES] ); FS_FreeFile( buf ); CM_InitBoxHull (); memset( map_areaportals, 0, sizeof( map_areaportals ) ); CM_FloodAreaConnections (); CM_CalcPHS (); if( numvertexes ) Mem_Free( map_verts ); memset( nullrow, 255, MAX_CM_LEAFS / 8 ); Q_strncpyz( map_name, name, sizeof(map_name) ); return map_cmodels; } /* ================== CM_ClientLoad FIXME! ================== */ qboolean CM_ClientLoad( void ) { return map_clientload; } /* ================== CM_InlineModel ================== */ cmodel_t *CM_InlineModel( int num ) { if( num < 0 || num >= numcmodels ) Com_Error( ERR_DROP, "CM_InlineModel: bad number %i (%i)", num, numcmodels ); return &map_cmodels[num]; } /* ================= CM_NumInlineModels ================= */ int CM_NumInlineModels( void ) { return numcmodels; } /* ================= CM_InlineModelBounds ================= */ void CM_InlineModelBounds( cmodel_t *cmodel, vec3_t mins, vec3_t maxs ) { if( cmodel == map_cmodels ) { VectorCopy( world_mins, mins ); VectorCopy( world_maxs, maxs ); } else { VectorCopy( cmodel->mins, mins ); VectorCopy( cmodel->maxs, maxs ); } } /* ================= CM_EntityStringLen ================= */ int CM_EntityStringLen( void ) { return numentitychars; } /* ================= CM_EntityString ================= */ char *CM_EntityString( void ) { return map_entitystring; } /* ================= CM_LeafCluster ================= */ int CM_LeafCluster( int leafnum ) { if( leafnum < 0 || leafnum >= numleafs ) Com_Error( ERR_DROP, "CM_LeafCluster: bad number" ); return map_leafs[leafnum].cluster; } /* ================= CM_LeafArea ================= */ int CM_LeafArea( int leafnum ) { if( leafnum < 0 || leafnum >= numleafs ) Com_Error( ERR_DROP, "CM_LeafArea: bad number" ); return map_leafs[leafnum].area; } /* =============================================================================== PVS / PHS =============================================================================== */ /* ================= CM_CalcPHS ================= */ void CM_CalcPHS( void ) { int i, j, k, l, index; int rowbytes, rowwords; int bitbyte; unsigned *dest, *src; qbyte *scan; int count, vcount; if( !map_pvs ) { map_phs = NULL; return; } Com_DPrintf( "Building PHS...\n" ); map_phs = Mem_Alloc( cmap_mempool, map_visdatasize ); map_phs->rowsize = map_pvs->rowsize; map_phs->numclusters = map_pvs->numclusters; rowbytes = map_pvs->rowsize; rowwords = rowbytes / sizeof( long ); vcount = 0; for( i = 0; i < map_pvs->numclusters; i++ ) { scan = CM_ClusterPVS( i ); for( j = 0; j < map_pvs->numclusters; j++ ) { if( scan[j>>3] & (1<<(j&7)) ) vcount++; } } count = 0; scan = ( qbyte * )map_pvs->data; dest = ( unsigned * )(( qbyte * )map_phs->data); for( i = 0; i < map_phs->numclusters; i++, dest += rowwords, scan += rowbytes ) { memcpy( dest, scan, rowbytes ); for( j = 0; j < rowbytes; j++ ) { bitbyte = scan[j]; if( !bitbyte ) continue; for( k = 0; k < 8; k++ ) { if( !( bitbyte & (1<= map_phs->numclusters ) Com_Error( ERR_DROP, "CM_CalcPHS: Bad bit in PVS" ); // pad bits should be 0 src = ( unsigned * )(( qbyte * )map_pvs->data) + index * rowwords; for( l = 0; l < rowwords; l++ ) dest[l] |= src[l]; } } for( j = 0; j < map_phs->numclusters; j++ ) if( (( qbyte * )dest)[j>>3] & (1<<(j&7)) ) count++; } Com_DPrintf( "Average clusters visible / hearable / total: %i / %i / %i\n" , vcount/map_phs->numclusters, count/map_phs->numclusters, map_phs->numclusters ); } /* ================= CM_ClusterSize ================= */ int CM_ClusterSize( void ) { return map_pvs ? map_pvs->rowsize : MAX_CM_LEAFS / 8; } /* ================= CM_NumClusters ================= */ int CM_NumClusters( void ) { return map_pvs->numclusters; } /* ================= CM_ClusterPVS ================= */ qbyte *CM_ClusterPVS( int cluster ) { if( cluster == -1 || !map_pvs ) return nullrow; return ( qbyte * )map_pvs->data + cluster * map_pvs->rowsize; } /* ================= CM_ClusterPHS ================= */ qbyte *CM_ClusterPHS( int cluster ) { if( cluster == -1 || !map_phs ) return nullrow; return ( qbyte * )map_phs->data + cluster * map_phs->rowsize; } /* =============================================================================== AREAPORTALS =============================================================================== */ /* ================= CM_AddAreaPortal ================= */ qboolean CM_AddAreaPortal( int portalnum, int area, int otherarea ) { carea_t *a; careaportal_t *ap; if( portalnum >= MAX_CM_AREAPORTALS ) return qfalse; if( !area || area > numareas || !otherarea || otherarea > numareas ) return qfalse; ap = &map_areaportals[portalnum]; ap->area = area; ap->otherarea = otherarea; a = &map_areas[area]; a->areaportals[a->numareaportals++] = portalnum; a = &map_areas[otherarea]; a->areaportals[a->numareaportals++] = portalnum; numareaportals++; return qtrue; } /* ================= CM_FloodArea_r ================= */ static void CM_FloodArea_r( int areanum, int floodnum ) { int i; carea_t *area; careaportal_t *p; area = &map_areas[areanum]; if( area->floodvalid == floodvalid ) { if( area->floodnum == floodnum ) return; Com_Error( ERR_DROP, "FloodArea_r: reflooded" ); } area->floodnum = floodnum; area->floodvalid = floodvalid; for( i = 0; i < area->numareaportals; i++ ) { p = &map_areaportals[area->areaportals[i]]; if( !p->open ) continue; if( p->area == areanum ) CM_FloodArea_r( p->otherarea, floodnum ); else if( p->otherarea == areanum ) CM_FloodArea_r( p->area, floodnum ); } } /* ==================== CM_FloodAreaConnections ==================== */ void CM_FloodAreaConnections( void ) { int i; int floodnum; // all current floods are now invalid floodvalid++; floodnum = 0; // area 0 is not used for( i = 1; i < numareas ; i++ ) { if( map_areas[i].floodvalid == floodvalid ) continue; // already flooded into floodnum++; CM_FloodArea_r( i, floodnum ); } } /* ================= CM_SetAreaPortalState ================= */ void CM_SetAreaPortalState( int portalnum, int area, int otherarea, qboolean open ) { if( portalnum >= MAX_CM_AREAPORTALS ) Com_Error( ERR_DROP, "areaportal >= MAX_CM_AREAPORTALS" ); if( !map_areaportals[portalnum].area ) { // add new areaportal if it doesn't exist if( !CM_AddAreaPortal( portalnum, area, otherarea ) ) return; } map_areaportals[portalnum].open = open; CM_FloodAreaConnections (); } /* ================= CM_AreasConnected ================= */ qboolean CM_AreasConnected( int area1, int area2 ) { if( cm_noAreas->integer ) return qtrue; if( area1 > numareas || area2 > numareas ) Com_Error( ERR_DROP, "CM_AreasConnected: area > numareas" ); if( map_areas[area1].floodnum == map_areas[area2].floodnum ) return qtrue; return qfalse; } /* ================= CM_WriteAreaBits Writes a length byte followed by a bit vector of all the areas that area in the same flood as the area parameter This is used by the client refreshes to cull visibility ================= */ int CM_WriteAreaBits( qbyte *buffer, int area ) { int i; int bytes; bytes = (numareas + 7) >> 3; if( cm_noAreas->integer ) { // for debugging, send everything memset( buffer, 255, bytes ); } else { memset( buffer, 0, bytes ); for( i = 1; i < numareas; i++ ) { if( !area || i == area || CM_AreasConnected( i, area ) ) buffer[i>>3] |= 1<<(i&7); } } return bytes; } /* ================= CM_MergeAreaBits ================= */ void CM_MergeAreaBits( qbyte *buffer, int area ) { int i; for( i = 1; i < numareas; i++ ) { if ( CM_AreasConnected( i, area ) || i == area ) buffer[i>>3] |= 1 << (i&7); } } /* =================== CM_WritePortalState Writes the portal state to a savegame file =================== */ void CM_WritePortalState( int file ) { int i, j; FS_Write( &numareaportals, sizeof(int), file ); for( i = 1; i < MAX_CM_AREAPORTALS; i++ ) { if( map_areaportals[i].area ) { FS_Write( &i, sizeof(int), file ); FS_Write( &map_areaportals[i], sizeof(map_areaportals[0]), file ); } } FS_Write( &numareas, sizeof(int), file ); for( i = 1; i < numareas; i++ ) { FS_Write( &map_areas[i].numareaportals, sizeof(int), file ); for( j = 0; j < map_areas[i].numareaportals; j++ ) FS_Write( &map_areas[i].areaportals[j], sizeof(int), file ); } } /* =================== CM_ReadPortalState Reads the portal state from a savegame file and recalculates the area connections =================== */ void CM_ReadPortalState( int file ) { int i, j; FS_Read( &numareaportals, sizeof(int), file ); for( i = 1; i < numareaportals; i++ ) { FS_Read( &j, sizeof(int), file ); FS_Read( &map_areaportals[j], sizeof(map_areaportals[0]), file ); } FS_Read( &numareas, sizeof(int), file ); for( i = 1; i < numareas; i++ ) { FS_Read( &map_areas[i].numareaportals, sizeof(int), file ); for( j = 0; j < map_areas[i].numareaportals; j++ ) FS_Read( &map_areas[i].areaportals[j], sizeof(int), file ); } CM_FloodAreaConnections(); } /* ============= CM_HeadnodeVisible Returns true if any leaf under headnode has a cluster that is potentially visible ============= */ qboolean CM_HeadnodeVisible( int nodenum, qbyte *visbits ) { int cluster; cnode_t *node; while( nodenum >= 0 ) { node = &map_nodes[nodenum]; if( CM_HeadnodeVisible( node->children[0], visbits ) ) return qtrue; nodenum = node->children[1]; } cluster = map_leafs[-1 - nodenum].cluster; if( cluster == -1 ) return qfalse; if( visbits[cluster>>3] & (1<<(cluster&7)) ) return qtrue; return qfalse; }