/* =========================================================================== Return to Castle Wolfenstein single player GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”). RTCW SP Source Code 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 3 of the License, or (at your option) any later version. RTCW SP Source Code 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 RTCW SP Source Code. If not, see . In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ //=========================================================================== // // Name: map_q3.c // Function: map loading // Programmer: Mr Elusive (MrElusive@demigod.demon.nl) // Last update: 1999-07-02 // Tab Size: 3 //=========================================================================== #include "qbsp.h" #include "l_mem.h" #include "../botlib/aasfile.h" //aas_bbox_t #include "aas_store.h" //AAS_MAX_BBOXES #include "aas_cfg.h" #include "aas_map.h" //AAS_CreateMapBrushes #include "l_bsp_q3.h" #include "../qcommon/cm_patch.h" #include "../game/surfaceflags.h" #define NODESTACKSIZE 1024 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintContents( int contents ); int Q3_BrushContents( mapbrush_t *b ) { int contents, i, mixed, hint; side_t *s; s = &b->original_sides[0]; contents = s->contents; // mixed = false; hint = false; for ( i = 1; i < b->numsides; i++ ) { s = &b->original_sides[i]; if ( s->contents != contents ) { mixed = true; } if ( s->surf & ( SURF_HINT | SURF_SKIP ) ) { hint = true; } contents |= s->contents; } //end for // if ( hint ) { if ( contents ) { Log_Write( "WARNING: hint brush with contents: " ); PrintContents( contents ); Log_Write( "\r\n" ); // Log_Write( "brush contents is: " ); PrintContents( b->contents ); Log_Write( "\r\n" ); } //end if return 0; } //end if //Log_Write("brush %d contents ", nummapbrushes); //PrintContents(contents); //Log_Write("\r\n"); //remove ladder and fog contents contents &= ~( CONTENTS_LADDER | CONTENTS_FOG ); // if ( mixed ) { Log_Write( "Entity %i, Brush %i: mixed face contents " , b->entitynum, b->brushnum ); PrintContents( contents ); Log_Write( "\r\n" ); // Log_Write( "brush contents is: " ); PrintContents( b->contents ); Log_Write( "\r\n" ); // if ( contents & CONTENTS_DONOTENTER ) { return CONTENTS_DONOTENTER; //Log_Print("mixed contents with donotenter\n"); } /* Log_Print("contents:"); PrintContents(contents); Log_Print("\ncontents:"); PrintContents(s->contents); Log_Print("\n"); Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); */ //if liquid brush if ( contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { return ( contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ); } //end if if ( contents & CONTENTS_PLAYERCLIP ) { return ( contents & CONTENTS_PLAYERCLIP ); } return ( contents & CONTENTS_SOLID ); } //end if /* if (contents & CONTENTS_AREAPORTAL) { static int num; Log_Write("Entity %i, Brush %i: area portal %d\r\n", b->entitynum, b->brushnum, num++); } //end if*/ if ( contents == ( contents & CONTENTS_STRUCTURAL ) ) { //Log_Print("brush %i is only structural\n", b->brushnum); contents = 0; } //end if if ( contents & CONTENTS_DONOTENTER ) { Log_Print( "brush %i is a donotenter brush, c = %X\n", b->brushnum, contents ); } //end if return contents; } //end of the function Q3_BrushContents //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_DPlanes2MapPlanes( void ) { int i; for ( i = 0; i < q3_numplanes; i++ ) { dplanes2mapplanes[i] = FindFloatPlane( q3_dplanes[i].normal, q3_dplanes[i].dist ); } //end for } //end of the function Q3_DPlanes2MapPlanes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_BSPBrushToMapBrush( q3_dbrush_t *bspbrush, entity_t *mapent ) { mapbrush_t *b; int i, k, n; side_t *side, *s2; int planenum; q3_dbrushside_t *bspbrushside; q3_dplane_t *bspplane; int contentFlags = 0; if ( nummapbrushes >= MAX_MAPFILE_BRUSHES ) { Error( "nummapbrushes >= MAX_MAPFILE_BRUSHES" ); } b = &mapbrushes[nummapbrushes]; b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = mapent - entities; b->brushnum = nummapbrushes - mapent->firstbrush; b->leafnum = dbrushleafnums[bspbrush - q3_dbrushes]; for ( n = 0; n < bspbrush->numSides; n++ ) { //pointer to the bsp brush side bspbrushside = &q3_dbrushsides[bspbrush->firstSide + n]; if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { Error( "MAX_MAPFILE_BRUSHSIDES" ); } //end if //pointer to the map brush side side = &brushsides[nummapbrushsides]; //if the BSP brush side is textured if ( q3_dbrushsidetextured[bspbrush->firstSide + n] ) { side->flags |= SFL_TEXTURED | SFL_VISIBLE; } else { side->flags &= ~SFL_TEXTURED;} //NOTE: all Quake3 sides are assumed textured //side->flags |= SFL_TEXTURED|SFL_VISIBLE; // if ( bspbrushside->shaderNum < 0 ) { side->contents = 0; side->surf = 0; } //end if else { side->contents = q3_dshaders[bspbrushside->shaderNum].contentFlags; side->surf = q3_dshaders[bspbrushside->shaderNum].surfaceFlags; if ( strstr( q3_dshaders[bspbrushside->shaderNum].shader, "common/hint" ) ) { //Log_Print("found hint side\n"); side->surf |= SURF_HINT; } //end if // Ridah, mark ladder brushes if ( cfg.rs_allowladders && strstr( q3_dshaders[bspbrushside->shaderNum].shader, "common/ladder" ) ) { //Log_Print("found ladder side\n"); side->contents |= CONTENTS_LADDER; contentFlags |= CONTENTS_LADDER; } //end if // done. } //end else // if ( !( strstr( q3_dshaders[bspbrushside->shaderNum].shader, "common/slip" ) ) ) { side->flags |= SFL_VISIBLE; } else if ( side->surf & SURF_NODRAW ) { side->flags |= SFL_TEXTURED | SFL_VISIBLE; } //end if /* if (side->contents & (CONTENTS_TRANSLUCENT|CONTENTS_STRUCTURAL)) { side->flags |= SFL_TEXTURED|SFL_VISIBLE; } //end if*/ // hints and skips are never detail, and have no content if ( side->surf & ( SURF_HINT | SURF_SKIP ) ) { side->contents = 0; //Log_Print("found hint brush side\n"); } /* if ((side->surf & SURF_NODRAW) && (side->surf & SURF_NOIMPACT)) { side->contents = 0; side->surf &= ~CONTENTS_DETAIL; Log_Print("probably found hint brush in a BSP without hints being used\n"); } //end if*/ /* // RF, ignore slick brushes (causes ladder issues) if (strstr(q3_dshaders[bspbrushside->shaderNum].shader, "common/slick")) { //Log_Print("found hint side\n"); b->numsides = 0; b->contents = 0; return; // get out of here } //end if */ //ME: get a plane for this side bspplane = &q3_dplanes[bspbrushside->planeNum]; planenum = FindFloatPlane( bspplane->normal, bspplane->dist ); // // see if the plane has been used already // //ME: this really shouldn't happen!!! //ME: otherwise the bsp file is corrupted?? //ME: still it seems to happen, maybe Johny Boy's //ME: brush bevel adding is crappy ? for ( k = 0; k < b->numsides; k++ ) { s2 = b->original_sides + k; // if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 // && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) if ( s2->planenum == planenum ) { Log_Print( "Entity %i, Brush %i: duplicate plane\n" , b->entitynum, b->brushnum ); break; } if ( s2->planenum == ( planenum ^ 1 ) ) { Log_Print( "Entity %i, Brush %i: mirrored plane\n" , b->entitynum, b->brushnum ); break; } } if ( k != b->numsides ) { continue; // duplicated } // // keep this side // //ME: reset pointer to side, why? hell I dunno (pointer is set above already) side = b->original_sides + b->numsides; //ME: store the plane number side->planenum = planenum; //ME: texinfo is already stored when bsp is loaded //NOTE: check for TEXINFO_NODE, otherwise crash in Q3_BrushContents //if (bspbrushside->texinfo < 0) side->texinfo = 0; //else side->texinfo = bspbrushside->texinfo; // save the td off in case there is an origin brush and we // have to recalculate the texinfo // ME: don't need to recalculate because it's already done // (for non-world entities) in the BSP file // side_brushtextures[nummapbrushsides] = td; nummapbrushsides++; b->numsides++; } //end for // get the content for the entire brush //Quake3 bsp brushes don't have a contents b->contents = q3_dshaders[bspbrush->shaderNum].contentFlags | contentFlags; // Ridah, Wolf has ladders (if we call Q3_BrushContents(), we'll get the solid area bug b->contents &= ~( /*CONTENTS_LADDER|*/ CONTENTS_FOG | CONTENTS_STRUCTURAL ); //b->contents = Q3_BrushContents(b); // // Ridah, CONTENTS_MOSTERCLIP should prevent AAS from being created, but not clip players/AI in the game if ( b->contents & CONTENTS_MONSTERCLIP ) { b->contents |= CONTENTS_PLAYERCLIP; } // func_explosive's not solid if ( !strcmp( "func_explosive", ValueForKey( &entities[b->entitynum], "classname" ) ) || !strcmp( "func_invisible_user", ValueForKey( &entities[b->entitynum], "classname" ) ) || !strcmp( "script_mover", ValueForKey( &entities[b->entitynum], "classname" ) ) || !strcmp( "func_static", ValueForKey( &entities[b->entitynum], "classname" ) ) ) { Log_Print( "Ignoring %s brush..\n", ValueForKey( &entities[b->entitynum], "classname" ) ); b->numsides = 0; b->contents = 0; return; } if ( BrushExists( b ) ) { c_squattbrushes++; b->numsides = 0; return; } //end if //if we're creating AAS if ( create_aas ) { //create the AAS brushes from this brush, don't add brush bevels AAS_CreateMapBrushes( b, mapent, false ); return; } //end if // allow detail brushes to be removed if ( nodetail && ( b->contents & CONTENTS_DETAIL ) ) { b->numsides = 0; return; } //end if // allow water brushes to be removed if ( nowater && ( b->contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { b->numsides = 0; return; } //end if // create windings for sides and bounds for brush MakeBrushWindings( b ); //mark brushes without winding or with a tiny window as bevels MarkBrushBevels( b ); // brushes that will not be visible at all will never be // used as bsp splitters if ( b->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { c_clipbrushes++; for ( i = 0; i < b->numsides; i++ ) b->original_sides[i].texinfo = TEXINFO_NODE; } //end for // // origin brushes are removed, but they set // the rotation origin for the rest of the brushes // in the entity. After the entire entity is parsed, // the planenums and texinfos will be adjusted for // the origin brush // //ME: not needed because the entities in the BSP file already // have an origin set // if (b->contents & CONTENTS_ORIGIN) // { // char string[32]; // vec3_t origin; // // if (num_entities == 1) // { // Error ("Entity %i, Brush %i: origin brushes not allowed in world" // , b->entitynum, b->brushnum); // return; // } // // VectorAdd (b->mins, b->maxs, origin); // VectorScale (origin, 0.5, origin); // // sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); // SetKeyValue (&entities[b->entitynum], "origin", string); // // VectorCopy (origin, entities[b->entitynum].origin); // // // don't keep this brush // b->numsides = 0; // // return; // } //ME: the bsp brushes already have bevels, so we won't try to // add them again (especially since Johny Boy's bevel adding might // be crappy) // AddBrushBevels(b); nummapbrushes++; mapent->numbrushes++; } //end of the function Q3_BSPBrushToMapBrush //=========================================================================== //=========================================================================== void Q3_ParseBSPBrushes( entity_t *mapent ) { int i; /* //give all the brushes that belong to this entity the number of the //BSP model used by this entity Q3_SetBrushModelNumbers(mapent); //now parse all the brushes with the correct mapent->modelnum for (i = 0; i < q3_numbrushes; i++) { if (brushmodelnumbers[i] == mapent->modelnum) { Q3_BSPBrushToMapBrush(&q3_dbrushes[i], mapent); } //end if } //end for */ for ( i = 0; i < q3_dmodels[mapent->modelnum].numBrushes; i++ ) { Q3_BSPBrushToMapBrush( &q3_dbrushes[q3_dmodels[mapent->modelnum].firstBrush + i], mapent ); } //end for } //end of the function Q3_ParseBSPBrushes //=========================================================================== //=========================================================================== qboolean Q3_ParseBSPEntity( int entnum ) { entity_t *mapent; char *model; int startbrush, startsides; startbrush = nummapbrushes; startsides = nummapbrushsides; mapent = &entities[entnum]; //num_entities]; mapent->firstbrush = nummapbrushes; mapent->numbrushes = 0; mapent->modelnum = -1; //-1 = no BSP model model = ValueForKey( mapent, "model" ); if ( model && strlen( model ) ) { if ( *model == '*' ) { //get the model number of this entity (skip the leading *) mapent->modelnum = atoi( &model[1] ); } //end if } //end if GetVectorForKey( mapent, "origin", mapent->origin ); //if this is the world entity it has model number zero //the world entity has no model key if ( !strcmp( "worldspawn", ValueForKey( mapent, "classname" ) ) ) { mapent->modelnum = 0; } //end if //if the map entity has a BSP model (a modelnum of -1 is used for //entities that aren't using a BSP model) if ( mapent->modelnum >= 0 ) { //parse the bsp brushes Q3_ParseBSPBrushes( mapent ); } //end if // //the origin of the entity is already taken into account // //func_group entities can't be in the bsp file // //check out the func_areaportal entities if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) { c_areaportals++; mapent->areaportalnum = c_areaportals; return true; } //end if return true; } //end of the function Q3_ParseBSPEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define MAX_PATCH_VERTS 1024 void AAS_CreateCurveBrushes( void ) { int i, j, n, planenum, numcurvebrushes = 0; q3_dsurface_t *surface; q3_drawVert_t *dv_p; vec3_t points[MAX_PATCH_VERTS]; int width, height, c; patchCollide_t *pc; facet_t *facet; mapbrush_t *brush; side_t *side; entity_t *mapent; winding_t *winding; qprintf( "nummapbrushsides = %d\n", nummapbrushsides ); mapent = &entities[0]; for ( i = 0; i < q3_numDrawSurfaces; i++ ) { surface = &q3_drawSurfaces[i]; if ( !surface->patchWidth ) { continue; } //if the curve is not solid if ( !( q3_dshaders[surface->shaderNum].contentFlags & ( CONTENTS_SOLID | CONTENTS_PLAYERCLIP ) ) ) { //Log_Print("skipped non-solid curve\n"); continue; } //end if // width = surface->patchWidth; height = surface->patchHeight; c = width * height; if ( c > MAX_PATCH_VERTS ) { Error( "ParseMesh: MAX_PATCH_VERTS" ); } //end if dv_p = q3_drawVerts + surface->firstVert; for ( j = 0 ; j < c ; j++, dv_p++ ) { points[j][0] = dv_p->xyz[0]; points[j][1] = dv_p->xyz[1]; points[j][2] = dv_p->xyz[2]; } //end for // create the internal facet structure pc = CM_GeneratePatchCollide( width, height, points ); // for ( j = 0; j < pc->numFacets; j++ ) { facet = &pc->facets[j]; // brush = &mapbrushes[nummapbrushes]; brush->original_sides = &brushsides[nummapbrushsides]; brush->entitynum = 0; brush->brushnum = nummapbrushes - mapent->firstbrush; // brush->numsides = facet->numBorders + 2; nummapbrushsides += brush->numsides; brush->contents = CONTENTS_SOLID; // //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); qprintf( "\r%6d curve brushes", ++numcurvebrushes ); // planenum = FindFloatPlane( pc->planes[facet->surfacePlane].plane, pc->planes[facet->surfacePlane].plane[3] ); // side = &brush->original_sides[0]; side->planenum = planenum; side->contents = CONTENTS_SOLID; side->flags |= SFL_TEXTURED | SFL_VISIBLE | SFL_CURVE; side->surf = 0; // side = &brush->original_sides[1]; if ( create_aas ) { //the plane is expanded later so it's not a problem that //these first two opposite sides are coplanar side->planenum = planenum ^ 1; } //end if else { side->planenum = FindFloatPlane( mapplanes[planenum ^ 1].normal, mapplanes[planenum ^ 1].dist + 1 ); side->flags |= SFL_TEXTURED | SFL_VISIBLE; } //end else side->contents = CONTENTS_SOLID; side->flags |= SFL_CURVE; side->surf = 0; // winding = BaseWindingForPlane( mapplanes[side->planenum].normal, mapplanes[side->planenum].dist ); for ( n = 0; n < facet->numBorders; n++ ) { //never use the surface plane as a border if ( facet->borderPlanes[n] == facet->surfacePlane ) { continue; } // side = &brush->original_sides[2 + n]; side->planenum = FindFloatPlane( pc->planes[facet->borderPlanes[n]].plane, pc->planes[facet->borderPlanes[n]].plane[3] ); if ( facet->borderInward[n] ) { side->planenum ^= 1; } side->contents = CONTENTS_SOLID; side->flags |= SFL_TEXTURED | SFL_CURVE; side->surf = 0; //chop the winding in place if ( winding ) { ChopWindingInPlace( &winding, mapplanes[side->planenum ^ 1].normal, mapplanes[side->planenum ^ 1].dist, 0.1 ); //CLIP_EPSILON); } } //end for //VectorCopy(pc->bounds[0], brush->mins); //VectorCopy(pc->bounds[1], brush->maxs); if ( !winding ) { Log_Print( "WARNING: AAS_CreateCurveBrushes: no winding\n" ); brush->numsides = 0; continue; } //end if brush->original_sides[0].winding = winding; WindingBounds( winding, brush->mins, brush->maxs ); for ( n = 0; n < 3; n++ ) { //IDBUG: all the indexes into the mins and maxs were zero (not using i) if ( brush->mins[n] < -MAX_MAP_BOUNDS || brush->maxs[n] > MAX_MAP_BOUNDS ) { Log_Print( "entity %i, brush %i: bounds out of range\n", brush->entitynum, brush->brushnum ); Log_Print( "brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n] ); brush->numsides = 0; //remove the brush break; } //end if if ( brush->mins[n] > MAX_MAP_BOUNDS || brush->maxs[n] < -MAX_MAP_BOUNDS ) { Log_Print( "entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum ); Log_Print( "brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n] ); brush->numsides = 0; //remove the brush break; } //end if } //end for if ( create_aas ) { //NOTE: brush bevels now already added //AddBrushBevels(brush); AAS_CreateMapBrushes( brush, mapent, false ); } //end if else { // create windings for sides and bounds for brush MakeBrushWindings( brush ); AddBrushBevels( brush ); nummapbrushes++; mapent->numbrushes++; } //end else } //end for } //end for //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); qprintf( "\r%6d curve brushes\n", numcurvebrushes ); } //end of the function AAS_CreateCurveBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ExpandMapBrush( mapbrush_t *brush, vec3_t mins, vec3_t maxs ); void Q3_LoadMapFromBSP( struct quakefile_s *qf ) { int i; //vec3_t mins = {-1,-1,-1}, maxs = {1, 1, 1}; // TTimo: unused Log_Print( "-- Q3_LoadMapFromBSP --\n" ); //loaded map type loadedmaptype = MAPTYPE_QUAKE3; Log_Print( "Loading map from %s...\n", qf->filename ); //load the bsp file Q3_LoadBSPFile( qf ); //create an index from bsp planes to map planes //DPlanes2MapPlanes(); //clear brush model numbers for ( i = 0; i < MAX_MAPFILE_BRUSHES; i++ ) brushmodelnumbers[i] = -1; nummapbrushsides = 0; num_entities = 0; Q3_ParseEntities(); // for ( i = 0; i < num_entities; i++ ) { Q3_ParseBSPEntity( i ); } //end for AAS_CreateCurveBrushes(); //get the map mins and maxs from the world model ClearBounds( map_mins, map_maxs ); for ( i = 0; i < entities[0].numbrushes; i++ ) { if ( mapbrushes[i].numsides <= 0 ) { continue; } //if (mapbrushes[i].mins[0] > 4096) // continue; //no valid points AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs ); AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs ); } //end for /*/ for (i = 0; i < nummapbrushes; i++) { //if (!mapbrushes[i].original_sides) continue; //AddBrushBevels(&mapbrushes[i]); //AAS_ExpandMapBrush(&mapbrushes[i], mins, maxs); } //end for*/ /* for (i = 0; i < nummapbrushsides; i++) { Log_Write("side %d flags = %d", i, brushsides[i].flags); } //end for for (i = 0; i < nummapbrushes; i++) { Log_Write("brush contents: "); PrintContents(mapbrushes[i].contents); Log_Print("\n"); } //end for*/ if ( writeaasmap ) { char name[MAX_QPATH]; strncpy( name, qf->filename, sizeof( name ) ); StripExtension( name ); strcat( name, "_aas.map" ); WriteMapFile( name ); } } //end of the function Q3_LoadMapFromBSP //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_ResetMapLoading( void ) { //reset for map loading from bsp memset( nodestack, 0, NODESTACKSIZE * sizeof( int ) ); nodestackptr = NULL; nodestacksize = 0; memset( brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof( int ) ); } //end of the function Q3_ResetMapLoading