/* =========================================================================== 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.c // Function: map loading and writing // Programmer: Mr Elusive (MrElusive@demigod.demon.nl) // Last update: 1997-12-03 // Tab Size: 3 //=========================================================================== #include "qbsp.h" #include "l_bsp_hl.h" #include "l_bsp_q1.h" #include "l_bsp_q2.h" #include "l_bsp_q3.h" #include "l_bsp_sin.h" #include "l_mem.h" #include "../botlib/aasfile.h" //aas_bbox_t #include "aas_store.h" //AAS_MAX_BBOXES #include "aas_cfg.h" // TTimo: messy... #define stricmp strcasecmp #define Sign( x ) ( x < 0 ? 1 : 0 ) int nummapbrushes; mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; int nummapbrushsides; side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; int nummapplanes; plane_t mapplanes[MAX_MAPFILE_PLANES]; int mapplaneusers[MAX_MAPFILE_PLANES]; #define PLANE_HASHES 1024 plane_t *planehash[PLANE_HASHES]; vec3_t map_mins, map_maxs; #ifdef SIN textureref_t side_newrefs[MAX_MAPFILE_BRUSHSIDES]; #endif map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; int map_numtexinfo; int loadedmaptype; //loaded map type // undefine to make plane finding use linear sort #define USE_HASHING int c_boxbevels; int c_edgebevels; int c_areaportals; int c_clipbrushes; int c_squattbrushes; int c_writtenbrushes; /* ============================================================================= PLANE FINDING ============================================================================= */ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int PlaneSignBits( vec3_t normal ) { int i, signbits; signbits = 0; for ( i = 2; i >= 0; i-- ) { signbits = ( signbits << 1 ) + Sign( normal[i] ); } //end for return signbits; } //end of the function PlaneSignBits //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int PlaneTypeForNormal( vec3_t normal ) { vec_t ax, ay, az; // NOTE: should these have an epsilon around 1.0? if ( normal[0] == 1.0 || normal[0] == -1.0 ) { return PLANE_X; } if ( normal[1] == 1.0 || normal[1] == -1.0 ) { return PLANE_Y; } if ( normal[2] == 1.0 || normal[2] == -1.0 ) { return PLANE_Z; } ax = fabs( normal[0] ); ay = fabs( normal[1] ); az = fabs( normal[2] ); if ( ax >= ay && ax >= az ) { return PLANE_ANYX; } if ( ay >= ax && ay >= az ) { return PLANE_ANYY; } return PLANE_ANYZ; } //end of the function PlaneTypeForNormal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== // MrE: use the same epsilons as q3map! //ME NOTE: changed from 0.00001 #define NORMAL_EPSILON 0.00001 //ME NOTE: changed from 0.01 #define DIST_EPSILON 0.01 qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ) { #if 1 if ( fabs( p->normal[0] - normal[0] ) < NORMAL_EPSILON && fabs( p->normal[1] - normal[1] ) < NORMAL_EPSILON && fabs( p->normal[2] - normal[2] ) < NORMAL_EPSILON && fabs( p->dist - dist ) < DIST_EPSILON ) { return true; } #else if ( p->normal[0] == normal[0] && p->normal[1] == normal[1] && p->normal[2] == normal[2] && p->dist == dist ) { return true; } #endif return false; } //end of the function PlaneEqual //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddPlaneToHash( plane_t *p ) { int hash; hash = (int)fabs( p->dist ) / 8; hash &= ( PLANE_HASHES - 1 ); p->hash_chain = planehash[hash]; planehash[hash] = p; } //end of the function AddPlaneToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int CreateNewFloatPlane( vec3_t normal, vec_t dist ) { plane_t *p, temp; if ( VectorLength( normal ) < 0.5 ) { Error( "FloatPlane: bad normal" ); } // create a new plane if ( nummapplanes + 2 > MAX_MAPFILE_PLANES ) { Error( "MAX_MAPFILE_PLANES" ); } p = &mapplanes[nummapplanes]; VectorCopy( normal, p->normal ); p->dist = dist; p->type = ( p + 1 )->type = PlaneTypeForNormal( p->normal ); p->signbits = PlaneSignBits( p->normal ); VectorSubtract( vec3_origin, normal, ( p + 1 )->normal ); ( p + 1 )->dist = -dist; ( p + 1 )->signbits = PlaneSignBits( ( p + 1 )->normal ); nummapplanes += 2; // allways put axial planes facing positive first if ( p->type < 3 ) { if ( p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0 ) { // flip order temp = *p; *p = *( p + 1 ); *( p + 1 ) = temp; AddPlaneToHash( p ); AddPlaneToHash( p + 1 ); return nummapplanes - 1; } } AddPlaneToHash( p ); AddPlaneToHash( p + 1 ); return nummapplanes - 2; } //end of the function CreateNewFloatPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SnapVector( vec3_t normal ) { int i; for ( i = 0 ; i < 3 ; i++ ) { if ( fabs( normal[i] - 1 ) < NORMAL_EPSILON ) { VectorClear( normal ); normal[i] = 1; break; } if ( fabs( normal[i] - -1 ) < NORMAL_EPSILON ) { VectorClear( normal ); normal[i] = -1; break; } } } //end of the function SnapVector //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SnapPlane( vec3_t normal, vec_t *dist ) { SnapVector( normal ); if ( fabs( *dist - Q_rint( *dist ) ) < DIST_EPSILON ) { *dist = Q_rint( *dist ); } } //end of the function SnapPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifndef USE_HASHING int FindFloatPlane( vec3_t normal, vec_t dist ) { int i; plane_t *p; SnapPlane( normal, &dist ); for ( i = 0, p = mapplanes; i < nummapplanes; i++, p++ ) { if ( PlaneEqual( p, normal, dist ) ) { mapplaneusers[i]++; return i; } //end if } //end for i = CreateNewFloatPlane( normal, dist ); mapplaneusers[i]++; return i; } //end of the function FindFloatPlane #else int FindFloatPlane( vec3_t normal, vec_t dist ) { int i; plane_t *p; int hash, h; SnapPlane( normal, &dist ); hash = (int)fabs( dist ) / 8; hash &= ( PLANE_HASHES - 1 ); // search the border bins as well for ( i = -1; i <= 1; i++ ) { h = ( hash + i ) & ( PLANE_HASHES - 1 ); for ( p = planehash[h]; p; p = p->hash_chain ) { if ( PlaneEqual( p, normal, dist ) ) { mapplaneusers[p - mapplanes]++; return p - mapplanes; } //end if } //end for } //end for i = CreateNewFloatPlane( normal, dist ); mapplaneusers[i]++; return i; } //end of the function FindFloatPlane #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int PlaneFromPoints( int *p0, int *p1, int *p2 ) { vec3_t t1, t2, normal; vec_t dist; VectorSubtract( p0, p1, t1 ); VectorSubtract( p2, p1, t2 ); CrossProduct( t1, t2, normal ); VectorNormalize( normal ); dist = DotProduct( p0, normal ); return FindFloatPlane( normal, dist ); } //end of the function PlaneFromPoints //=========================================================================== // Adds any additional planes necessary to allow the brush to be expanded // against axial bounding boxes // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddBrushBevels( mapbrush_t *b ) { int axis, dir; int i, j, k, l, order; side_t sidetemp; brush_texture_t tdtemp; #ifdef SIN textureref_t trtemp; #endif side_t *s, *s2; vec3_t normal; float dist; winding_t *w, *w2; vec3_t vec, vec2; float d; // // add the axial planes // order = 0; for ( axis = 0 ; axis < 3 ; axis++ ) { for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) { // see if the plane is allready present for ( i = 0, s = b->original_sides ; i < b->numsides ; i++,s++ ) { if ( mapplanes[s->planenum].normal[axis] == dir ) { break; } } if ( i == b->numsides ) { // add a new side if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) { Error( "MAX_MAP_BRUSHSIDES" ); } nummapbrushsides++; b->numsides++; VectorClear( normal ); normal[axis] = dir; if ( dir == 1 ) { dist = b->maxs[axis]; } else { dist = -b->mins[axis]; } s->planenum = FindFloatPlane( normal, dist ); s->texinfo = b->original_sides[0].texinfo; #ifdef SIN s->lightinfo = b->original_sides[0].lightinfo; #endif s->contents = b->original_sides[0].contents; s->flags |= SFL_BEVEL; c_boxbevels++; } // if the plane is not in it canonical order, swap it if ( i != order ) { sidetemp = b->original_sides[order]; b->original_sides[order] = b->original_sides[i]; b->original_sides[i] = sidetemp; j = b->original_sides - brushsides; tdtemp = side_brushtextures[j + order]; side_brushtextures[j + order] = side_brushtextures[j + i]; side_brushtextures[j + i] = tdtemp; #ifdef SIN trtemp = side_newrefs[j + order]; side_newrefs[j + order] = side_newrefs[j + i]; side_newrefs[j + i] = trtemp; #endif } } } // // add the edge bevels // if ( b->numsides == 6 ) { return; // pure axial } // test the non-axial plane edges for ( i = 6 ; i < b->numsides ; i++ ) { s = b->original_sides + i; w = s->winding; if ( !w ) { continue; } for ( j = 0 ; j < w->numpoints ; j++ ) { k = ( j + 1 ) % w->numpoints; VectorSubtract( w->p[j], w->p[k], vec ); if ( VectorNormalize( vec ) < 0.5 ) { continue; } SnapVector( vec ); for ( k = 0 ; k < 3 ; k++ ) if ( vec[k] == -1 || vec[k] == 1 ) { break; } // axial if ( k != 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( w->p[j], normal ); // if all the points on all the sides are // behind this plane, it is a proper edge bevel for ( k = 0 ; k < b->numsides ; k++ ) { // if this plane has allready been used, skip it if ( PlaneEqual( &mapplanes[b->original_sides[k].planenum] , normal, dist ) ) { break; } w2 = b->original_sides[k].winding; if ( !w2 ) { continue; } for ( l = 0 ; l < w2->numpoints ; l++ ) { d = DotProduct( w2->p[l], normal ) - dist; if ( d > 0.1 ) { break; // point in front } } if ( l != w2->numpoints ) { break; } } if ( k != b->numsides ) { continue; // wasn't part of the outer hull } // add this plane if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) { Error( "MAX_MAP_BRUSHSIDES" ); } nummapbrushsides++; s2 = &b->original_sides[b->numsides]; s2->planenum = FindFloatPlane( normal, dist ); s2->texinfo = b->original_sides[0].texinfo; #ifdef SIN s2->lightinfo = b->original_sides[0].lightinfo; #endif s2->contents = b->original_sides[0].contents; s2->flags |= SFL_BEVEL; c_edgebevels++; b->numsides++; } } } } } //end of the function AddBrushBevels //=========================================================================== // creates windigs for sides and mins / maxs for the brush // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean MakeBrushWindings( mapbrush_t *ob ) { int i, j; winding_t *w; side_t *side; plane_t *plane; ClearBounds( ob->mins, ob->maxs ); for ( i = 0; i < ob->numsides; i++ ) { plane = &mapplanes[ob->original_sides[i].planenum]; w = BaseWindingForPlane( plane->normal, plane->dist ); for ( j = 0; j < ob->numsides && w; j++ ) { if ( i == j ) { continue; } if ( ob->original_sides[j].flags & SFL_BEVEL ) { continue; } plane = &mapplanes[ob->original_sides[j].planenum ^ 1]; ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON); } side = &ob->original_sides[i]; side->winding = w; if ( w ) { side->flags |= SFL_VISIBLE; for ( j = 0; j < w->numpoints; j++ ) AddPointToBounds( w->p[j], ob->mins, ob->maxs ); } } for ( i = 0; i < 3; i++ ) { //IDBUG: all the indexes into the mins and maxs were zero (not using i) if ( ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS ) { Log_Print( "entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum ); ob->numsides = 0; //remove the brush break; } //end if if ( ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS ) { Log_Print( "entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum ); ob->numsides = 0; //remove the brush break; } //end if } //end for return true; } //end of the function MakeBrushWindings //=========================================================================== // FIXME: currently doesn't mark all bevels // NOTE: when one brush bevel is found the remaining sides of the brush // are bevels as well (when the brush isn't expanded for AAS :)) // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MarkBrushBevels( mapbrush_t *brush ) { int i; int we; side_t *s; //check all the sides of the brush for ( i = 0; i < brush->numsides; i++ ) { s = brush->original_sides + i; //if the side has no winding if ( !s->winding ) { Log_Write( "MarkBrushBevels: brush %d no winding", brush->brushnum ); s->flags |= SFL_BEVEL; } //end if //if the winding is tiny else if ( WindingIsTiny( s->winding ) ) { s->flags |= SFL_BEVEL; Log_Write( "MarkBrushBevels: brush %d tiny winding", brush->brushnum ); } //end else if //if the winding has errors else { we = WindingError( s->winding ); if ( we == WE_NOTENOUGHPOINTS || we == WE_SMALLAREA || we == WE_POINTBOGUSRANGE // || we == WE_NONCONVEX ) { Log_Write( "MarkBrushBevels: brush %d %s", brush->brushnum, WindingErrorString() ); s->flags |= SFL_BEVEL; } //end else if } //end else if ( s->flags & SFL_BEVEL ) { s->flags &= ~SFL_VISIBLE; //if the side has a valid plane if ( s->planenum > 0 && s->planenum < nummapplanes ) { //if it is an axial plane if ( mapplanes[s->planenum].type < 3 ) { c_boxbevels++; } else { c_edgebevels++;} } //end if } //end if } //end for } //end of the function MarkBrushBevels //=========================================================================== // returns true if the map brush already exists // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BrushExists( mapbrush_t *brush ) { int i, s1, s2; side_t *side1, *side2; mapbrush_t *brush1, *brush2; for ( i = 0; i < nummapbrushes; i++ ) { brush1 = brush; brush2 = &mapbrushes[i]; //compare the brushes if ( brush1->entitynum != brush2->entitynum ) { continue; } //if (brush1->contents != brush2->contents) continue; if ( brush1->numsides != brush2->numsides ) { continue; } for ( s1 = 0; s1 < brush1->numsides; s1++ ) { side1 = brush1->original_sides + s1; // for ( s2 = 0; s2 < brush2->numsides; s2++ ) { side2 = brush2->original_sides + s2; // if ( ( side1->planenum & ~1 ) == ( side2->planenum & ~1 ) // && side1->texinfo == side2->texinfo // && side1->contents == side2->contents // && side1->surf == side2->surf ) { break; } } //end if if ( s2 >= brush2->numsides ) { break; } } //end for if ( s1 >= brush1->numsides ) { return true; } } //end for return false; } //end of the function BrushExists //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteMapBrush( FILE *fp, mapbrush_t *brush, vec3_t origin ) { int sn, rotate, shift[2], sv, tv, planenum, p1, i, j; float scale[2], originshift[2], ang1, ang2, newdist; vec3_t vecs[2], axis[2]; map_texinfo_t *ti; winding_t *w; side_t *s; plane_t *plane; if ( noliquids ) { if ( brush->contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { return true; } //end if } //end if //if the brush has no contents if ( !brush->contents ) { return true; } //print the leading { if ( fprintf( fp, " { //brush %d\n", brush->brushnum ) < 0 ) { return false; } //write brush sides for ( sn = 0; sn < brush->numsides; sn++ ) { s = brush->original_sides + sn; //don't write out bevels if ( !( s->flags & SFL_BEVEL ) ) { //if the entity has an origin set if ( origin[0] || origin[1] || origin[2] ) { newdist = mapplanes[s->planenum].dist + DotProduct( mapplanes[s->planenum].normal, origin ); planenum = FindFloatPlane( mapplanes[s->planenum].normal, newdist ); } //end if else { planenum = s->planenum; } //end else //always take the first plane, then flip the points if necesary plane = &mapplanes[planenum & ~1]; w = BaseWindingForPlane( plane->normal, plane->dist ); // for ( i = 0; i < 3; i++ ) { for ( j = 0; j < 3; j++ ) { if ( fabs( w->p[i][j] ) < 0.2 ) { w->p[i][j] = 0; } else if ( fabs( (int)w->p[i][j] - w->p[i][j] ) < 0.3 ) { w->p[i][j] = (int) w->p[i][j]; } //w->p[i][j] = (int) (w->p[i][j] + 0.2); } //end for } //end for //three non-colinear points to define the plane if ( planenum & 1 ) { p1 = 1; } else { p1 = 0;} if ( fprintf( fp," ( %5i %5i %5i ) ", (int)w->p[p1][0], (int)w->p[p1][1], (int)w->p[p1][2] ) < 0 ) { return false; } if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[!p1][0], (int)w->p[!p1][1], (int)w->p[!p1][2] ) < 0 ) { return false; } if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] ) < 0 ) { return false; } //free the winding FreeWinding( w ); // if ( s->texinfo == TEXINFO_NODE ) { if ( brush->contents & CONTENTS_PLAYERCLIP ) { //player clip if ( loadedmaptype == MAPTYPE_SIN ) { if ( fprintf( fp, "generic/misc/clip 0 0 0 1 1" ) < 0 ) { return false; } } //end if else if ( loadedmaptype == MAPTYPE_QUAKE2 ) { //FIXME: don't always use e1u1 if ( fprintf( fp, "e1u1/clip 0 0 0 1 1" ) < 0 ) { return false; } } //end else else if ( loadedmaptype == MAPTYPE_QUAKE3 ) { if ( fprintf( fp, "e1u1/clip 0 0 0 1 1" ) < 0 ) { return false; } } //end else if else { if ( fprintf( fp, "clip 0 0 0 1 1" ) < 0 ) { return false; } } //end else } //end if else if ( brush->contents == CONTENTS_MONSTERCLIP ) { //monster clip if ( loadedmaptype == MAPTYPE_SIN ) { if ( fprintf( fp, "generic/misc/monster 0 0 0 1 1" ) < 0 ) { return false; } } //end if else if ( loadedmaptype == MAPTYPE_QUAKE2 ) { if ( fprintf( fp, "e1u1/clip_mon 0 0 0 1 1" ) < 0 ) { return false; } } //end else else { if ( fprintf( fp, "clip 0 0 0 1 1" ) < 0 ) { return false; } } //end else } //end else else { if ( fprintf( fp, "clip 0 0 0 1 1" ) < 0 ) { return false; } Log_Write( "brush->contents = %d\n", brush->contents ); } //end else } //end if else if ( loadedmaptype == MAPTYPE_SIN && s->texinfo == 0 ) { if ( brush->contents & CONTENTS_DUMMYFENCE ) { if ( fprintf( fp, "generic/misc/fence 0 0 0 1 1" ) < 0 ) { return false; } } //end if else if ( brush->contents & CONTENTS_MIST ) { if ( fprintf( fp, "generic/misc/volumetric_base 0 0 0 1 1" ) < 0 ) { return false; } } //end if else //unknown so far { if ( fprintf( fp, "generic/misc/red 0 0 0 1 1" ) < 0 ) { return false; } } //end else } //end if else if ( loadedmaptype == MAPTYPE_QUAKE3 ) { //always use the same texture if ( fprintf( fp, "e2u3/floor1_2 0 0 0 1 1 1 0 0" ) < 0 ) { return false; } } //end else if else { //* ti = &map_texinfo[s->texinfo]; //the scaling of the texture scale[0] = 1 / VectorNormalize2( ti->vecs[0], vecs[0] ); scale[1] = 1 / VectorNormalize2( ti->vecs[1], vecs[1] ); // TextureAxisFromPlane( plane, axis[0], axis[1] ); //calculate texture shift done by entity origin originshift[0] = DotProduct( origin, axis[0] ); originshift[1] = DotProduct( origin, axis[1] ); //the texture shift without origin shift shift[0] = ti->vecs[0][3] - originshift[0]; shift[1] = ti->vecs[1][3] - originshift[1]; // if ( axis[0][0] ) { sv = 0; } else if ( axis[0][1] ) { sv = 1; } else { sv = 2;} if ( axis[1][0] ) { tv = 0; } else if ( axis[1][1] ) { tv = 1; } else { tv = 2;} //calculate rotation of texture if ( vecs[0][tv] == 0 ) { ang1 = vecs[0][sv] > 0 ? 90.0 : -90.0; } else { ang1 = atan2( vecs[0][sv], vecs[0][tv] ) * 180 / Q_PI;} if ( ang1 < 0 ) { ang1 += 360; } if ( ang1 >= 360 ) { ang1 -= 360; } if ( axis[0][tv] == 0 ) { ang2 = axis[0][sv] > 0 ? 90.0 : -90.0; } else { ang2 = atan2( axis[0][sv], axis[0][tv] ) * 180 / Q_PI;} if ( ang2 < 0 ) { ang2 += 360; } if ( ang2 >= 360 ) { ang2 -= 360; } rotate = ang2 - ang1; if ( rotate < 0 ) { rotate += 360; } if ( rotate >= 360 ) { rotate -= 360; } //write the texture info if ( fprintf( fp, "%s %d %d %d", ti->texture, shift[0], shift[1], rotate ) < 0 ) { return false; } if ( fabs( scale[0] - ( (int) scale[0] ) ) < 0.001 ) { if ( fprintf( fp, " %d", (int) scale[0] ) < 0 ) { return false; } } //end if else { if ( fprintf( fp, " %4f", scale[0] ) < 0 ) { return false; } } //end if if ( fabs( scale[1] - ( (int) scale[1] ) ) < 0.001 ) { if ( fprintf( fp, " %d", (int) scale[1] ) < 0 ) { return false; } } //end if else { if ( fprintf( fp, " %4f", scale[1] ) < 0 ) { return false; } } //end else //write the extra brush side info if ( loadedmaptype == MAPTYPE_QUAKE2 ) { if ( fprintf( fp, " %d %d %d", s->contents, ti->flags, ti->value ) < 0 ) { return false; } } //end if //*/ } //end else if ( fprintf( fp, "\n" ) < 0 ) { return false; } } //end if } //end if if ( fprintf( fp, " }\n" ) < 0 ) { return false; } c_writtenbrushes++; return true; } //end of the function WriteMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteOriginBrush( FILE *fp, vec3_t origin ) { vec3_t normal; float dist; int i, s; winding_t *w; if ( fprintf( fp, " {\n" ) < 0 ) { return false; } // for ( i = 0; i < 3; i++ ) { for ( s = -1; s <= 1; s += 2 ) { // VectorClear( normal ); normal[i] = s; dist = origin[i] * s + 16; // w = BaseWindingForPlane( normal, dist ); //three non-colinear points to define the plane if ( fprintf( fp," ( %5i %5i %5i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2] ) < 0 ) { return false; } if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2] ) < 0 ) { return false; } if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] ) < 0 ) { return false; } //free the winding FreeWinding( w ); //write origin texture: // CONTENTS_ORIGIN = 16777216 // SURF_NODRAW = 128 if ( loadedmaptype == MAPTYPE_SIN ) { if ( fprintf( fp, "generic/misc/origin 0 0 0 1 1" ) < 0 ) { return false; } } //end if else if ( loadedmaptype == MAPTYPE_HALFLIFE ) { if ( fprintf( fp, "origin 0 0 0 1 1" ) < 0 ) { return false; } } //end if else { if ( fprintf( fp, "e1u1/origin 0 0 0 1 1" ) < 0 ) { return false; } } //end else //Quake2 extra brush side info if ( loadedmaptype == MAPTYPE_QUAKE2 ) { //if (fprintf(fp, " 16777216 128 0") < 0) return false; } //end if if ( fprintf( fp, "\n" ) < 0 ) { return false; } } //end for } //end for if ( fprintf( fp, " }\n" ) < 0 ) { return false; } c_writtenbrushes++; return true; } //end of the function WriteOriginBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== mapbrush_t *GetAreaPortalBrush( entity_t *mapent ) { int portalnum, bn; mapbrush_t *brush = NULL; // TTimo: init //the area portal number portalnum = mapent->areaportalnum; //find the area portal brush in the world brushes for ( bn = 0; bn < nummapbrushes && portalnum; bn++ ) { brush = &mapbrushes[bn]; //must be in world entity if ( brush->entitynum == 0 ) { if ( brush->contents & CONTENTS_AREAPORTAL ) { portalnum--; } //end if } //end if } //end for if ( bn < nummapbrushes ) { return brush; } //end if else { Log_Print( "area portal %d brush not found\n", mapent->areaportalnum ); return NULL; } //end else } //end of the function GetAreaPortalBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteMapFileSafe( FILE *fp ) { char key[1024], value[1024]; int i, bn, entitybrushes; epair_t *ep; mapbrush_t *brush; entity_t *mapent; //vec3_t vec_origin = {0, 0, 0}; // if ( fprintf( fp,"//=====================================================\n" "//\n" "// map file created with BSPC v1.6\n" "//\n" "// BSPC is created by Mr Elusive\n" "//\n" ) < 0 ) { return false; } if ( loadedmaptype == MAPTYPE_SIN ) { if ( fprintf( fp, "// generic/misc/red is used for unknown textures\n" ) < 0 ) { return false; } } //end if if ( fprintf( fp,"//\n" "//=====================================================\n" ) < 0 ) { return false; } //write out all the entities for ( i = 0; i < num_entities; i++ ) { mapent = &entities[i]; if ( !mapent->epairs ) { continue; } //end if if ( fprintf( fp, "{\n" ) < 0 ) { return false; } // if ( loadedmaptype == MAPTYPE_QUAKE3 ) { if ( !stricmp( ValueForKey( mapent, "classname" ), "light" ) ) { SetKeyValue( mapent, "light", "10000" ); } //end if } //end if //write epairs for ( ep = mapent->epairs; ep; ep = ep->next ) { strcpy( key, ep->key ); StripTrailing( key ); strcpy( value, ep->value ); StripTrailing( value ); // if ( loadedmaptype == MAPTYPE_QUAKE2 || loadedmaptype == MAPTYPE_SIN ) { //don't write an origin for BSP models if ( mapent->modelnum >= 0 && !strcmp( key, "origin" ) ) { continue; } } //end if //don't write BSP model numbers if ( mapent->modelnum >= 0 && !strcmp( key, "model" ) && value[0] == '*' ) { continue; } // if ( fprintf( fp, " \"%s\" \"%s\"\n", key, value ) < 0 ) { return false; } } //end for // if ( ValueForKey( mapent, "origin" ) ) { GetVectorForKey( mapent, "origin", mapent->origin ); } else { mapent->origin[0] = mapent->origin[1] = mapent->origin[2] = 0;} //if this is an area portal entity if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) { brush = GetAreaPortalBrush( mapent ); if ( !brush ) { return false; } if ( !WriteMapBrush( fp, brush, mapent->origin ) ) { return false; } } //end if else { entitybrushes = false; //write brushes for ( bn = 0; bn < nummapbrushes; bn++ ) { brush = &mapbrushes[bn]; //if the brush is part of this entity if ( brush->entitynum == i ) { //don't write out area portal brushes in the world if ( !( ( brush->contents & CONTENTS_AREAPORTAL ) && brush->entitynum == 0 ) ) { /* if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) { AAS_PositionFuncRotatingBrush(mapent, brush); if (!WriteMapBrush(fp, brush, vec_origin)) return false; } //end if else // */ { if ( !WriteMapBrush( fp, brush, mapent->origin ) ) { return false; } } //end else entitybrushes = true; } //end if } //end if } //end for //if the entity had brushes if ( entitybrushes ) { //if the entity has an origin set if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) { if ( !WriteOriginBrush( fp, mapent->origin ) ) { return false; } } //end if } //end if } //end else if ( fprintf( fp, "}\n" ) < 0 ) { return false; } } //end for if ( fprintf( fp, "//total of %d brushes\n", c_writtenbrushes ) < 0 ) { return false; } return true; } //end of the function WriteMapFileSafe //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WriteMapFile( char *filename ) { FILE *fp; double start_time; c_writtenbrushes = 0; //the time started start_time = I_FloatTime(); // Log_Print( "writing %s\n", filename ); fp = fopen( filename, "wb" ); if ( !fp ) { Log_Print( "can't open %s\n", filename ); return; } //end if if ( !WriteMapFileSafe( fp ) ) { fclose( fp ); Log_Print( "error writing map file %s\n", filename ); return; } //end if fclose( fp ); //display creation time Log_Print( "written %d brushes\n", c_writtenbrushes ); Log_Print( "map file written in %5.0f seconds\n", I_FloatTime() - start_time ); } //end of the function WriteMapFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintMapInfo( void ) { Log_Print( "\n" ); Log_Print( "%6i brushes\n", nummapbrushes ); Log_Print( "%6i brush sides\n", nummapbrushsides ); // Log_Print("%6i clipbrushes\n", c_clipbrushes); // Log_Print("%6i total sides\n", nummapbrushsides); // Log_Print("%6i boxbevels\n", c_boxbevels); // Log_Print("%6i edgebevels\n", c_edgebevels); // Log_Print("%6i entities\n", num_entities); // Log_Print("%6i planes\n", nummapplanes); // Log_Print("%6i areaportals\n", c_areaportals); // Log_Print("%6i squatt brushes\n", c_squattbrushes); // Log_Print("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], // map_maxs[0],map_maxs[1],map_maxs[2]); } //end of the function PrintMapInfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ResetMapLoading( void ) { int i; epair_t *ep, *nextep; Q2_ResetMapLoading(); Sin_ResetMapLoading(); //free all map brush side windings for ( i = 0; i < nummapbrushsides; i++ ) { if ( brushsides[i].winding ) { FreeWinding( brushsides[i].winding ); } //end for } //end for //reset regular stuff nummapbrushes = 0; memset( mapbrushes, 0, MAX_MAPFILE_BRUSHES * sizeof( mapbrush_t ) ); // nummapbrushsides = 0; memset( brushsides, 0, MAX_MAPFILE_BRUSHSIDES * sizeof( side_t ) ); memset( side_brushtextures, 0, MAX_MAPFILE_BRUSHSIDES * sizeof( brush_texture_t ) ); // nummapplanes = 0; memset( mapplanes, 0, MAX_MAPFILE_PLANES * sizeof( plane_t ) ); // memset( planehash, 0, PLANE_HASHES * sizeof( plane_t * ) ); // memset( map_texinfo, 0, MAX_MAPFILE_TEXINFO * sizeof( map_texinfo_t ) ); map_numtexinfo = 0; // VectorClear( map_mins ); VectorClear( map_maxs ); // c_boxbevels = 0; c_edgebevels = 0; c_areaportals = 0; c_clipbrushes = 0; c_writtenbrushes = 0; //clear the entities for ( i = 0; i < num_entities; i++ ) { for ( ep = entities[i].epairs; ep; ep = nextep ) { nextep = ep->next; FreeMemory( ep->key ); FreeMemory( ep->value ); FreeMemory( ep ); } //end for } //end for num_entities = 0; memset( entities, 0, MAX_MAP_ENTITIES * sizeof( entity_t ) ); } //end of the function ResetMapLoading //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifndef Q1_BSPVERSION #define Q1_BSPVERSION 29 #endif #ifndef HL_BSPVERSION #define HL_BSPVERSION 30 #endif #define Q2_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) //IBSP #define Q2_BSPVERSION 38 #define SINGAME_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'R' ) //RBSP #define SINGAME_BSPVERSION 1 #define SIN_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) //IBSP #define SIN_BSPVERSION 41 typedef struct { int ident; int version; } idheader_t; int LoadMapFromBSP( struct quakefile_s *qf ) { idheader_t idheader; if ( ReadQuakeFile( qf, &idheader, 0, sizeof( idheader_t ) ) != sizeof( idheader_t ) ) { return false; } //end if idheader.ident = LittleLong( idheader.ident ); idheader.version = LittleLong( idheader.version ); //Quake3 BSP file if ( idheader.ident == Q3_BSP_IDENT && idheader.version == Q3_BSP_VERSION ) { ResetMapLoading(); Q3_LoadMapFromBSP( qf ); Q3_FreeMaxBSP(); } //end if //Quake2 BSP file else if ( idheader.ident == Q2_BSPHEADER && idheader.version == Q2_BSPVERSION ) { ResetMapLoading(); Q2_AllocMaxBSP(); Q2_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); Q2_FreeMaxBSP(); } //endif //Sin BSP file else if ( ( idheader.ident == SIN_BSPHEADER && idheader.version == SIN_BSPVERSION ) || //the dorks gave the same format another ident and verions ( idheader.ident == SINGAME_BSPHEADER && idheader.version == SINGAME_BSPVERSION ) ) { ResetMapLoading(); Sin_AllocMaxBSP(); Sin_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); Sin_FreeMaxBSP(); } //end if //the Quake1 bsp files don't have a ident only a version else if ( idheader.ident == Q1_BSPVERSION ) { ResetMapLoading(); Q1_AllocMaxBSP(); Q1_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); Q1_FreeMaxBSP(); } //end if //Half-Life also only uses a version number else if ( idheader.ident == HL_BSPVERSION ) { ResetMapLoading(); HL_AllocMaxBSP(); HL_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); HL_FreeMaxBSP(); } //end if else { Error( "unknown BSP format %c%c%c%c, version %d\n", ( idheader.ident & 0xFF ), ( ( idheader.ident >> 8 ) & 0xFF ), ( ( idheader.ident >> 16 ) & 0xFF ), ( ( idheader.ident >> 24 ) & 0xFF ), idheader.version ); return false; } //end if // return true; } //end of the function LoadMapFromBSP