/*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * ****/ // brush.c #include "csg.h" plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; int g_planehash[PLANE_HASHES]; int g_nummapplanes; /* ============================================================================= PLANE FINDING ============================================================================= */ /* ================ PlaneEqual ================ */ bool PlaneEqual( const plane_t *p, const vec3_t normal, const vec3_t origin, vec_t dist ) { vec_t t; if( -DIR_EPSILON < ( t = normal[0] - p->normal[0] ) && t < DIR_EPSILON && -DIR_EPSILON < ( t = normal[1] - p->normal[1] ) && t < DIR_EPSILON && -DIR_EPSILON < ( t = normal[2] - p->normal[2] ) && t < DIR_EPSILON ) { t = PlaneDiff2( origin, p ); if( -DIST_EPSILON < t && t < DIST_EPSILON ) return true; } return false; } /* ================ AddPlaneToHash ================ */ void AddPlaneToHash( plane_t *p ) { int hash; hash = (PLANE_HASHES - 1) & (int)fabs( p->dist ); p->hash_chain = g_planehash[hash]; g_planehash[hash] = p - g_mapplanes + 1; } /* ================ CreateNewFloatPlane ================ */ int CreateNewFloatPlane( const vec3_t srcnormal, const vec3_t origin ) { plane_t *p0, *p1, temp; vec3_t normal; vec_t dist; int type; if( VectorLength( srcnormal ) < 0.5 ) return -1; // create a new plane if(( g_nummapplanes + 2 ) > MAX_INTERNAL_MAP_PLANES ) COM_FatalError( "MAX_INTERNAL_MAP_PLANES limit exceeded\n" ); p0 = &g_mapplanes[g_nummapplanes+0]; p1 = &g_mapplanes[g_nummapplanes+1]; // snap plane normal VectorCopy( srcnormal, normal ); type = SnapNormal( normal ); // snap plane distance dist = DotProduct( origin, normal ); // only snap distance if the normal is an axis. Otherwise there // is nothing "natural" about snapping the distance to an integer. if( VectorIsOnAxis( normal ) && fabs( dist - Q_rint( dist )) < DIST_EPSILON ) dist = Q_rint( dist ); // catch -0.0 VectorCopy( origin, p0->origin ); VectorCopy( normal, p0->normal ); VectorCopy( origin, p1->origin ); VectorNegate( normal, p1->normal ); p0->dist = dist; p1->dist = -dist; p0->type = type; p1->type = type; g_nummapplanes += 2; // always put axial planes facing positive first if( normal[type % 3] < 0 ) { // flip order temp = *p0; *p0 = *p1; *p1 = temp; AddPlaneToHash( p0 ); AddPlaneToHash( p1 ); return g_nummapplanes - 1; } AddPlaneToHash( p0 ); AddPlaneToHash( p1 ); return g_nummapplanes - 2; } /* ============= FindFloatPlane ============= */ int FindFloatPlane( const vec3_t normal, const vec3_t origin ) { int i, hash, h; vec_t dist; plane_t *p; dist = DotProduct( origin, normal ); hash = (PLANE_HASHES - 1) & (int)fabs( dist ); ThreadLock(); // search the border bins as well for( i = -1; i <= 1; i++ ) { h = (hash + i) & (PLANE_HASHES - 1); for( int pidx = g_planehash[h] - 1; pidx != -1; pidx = g_mapplanes[pidx].hash_chain - 1 ) { p = &g_mapplanes[pidx]; if( PlaneEqual( p, normal, origin, dist )) { ThreadUnlock(); return p - g_mapplanes; } } } // allocate a new two opposite planes int returnval = CreateNewFloatPlane( normal, origin ); ThreadUnlock(); return returnval; } int FindFloatPlane2( const vec3_t normal, const vec3_t origin ) { int returnval; plane_t *p, temp; vec_t t; returnval = 0; find_plane: for( ; returnval < g_nummapplanes; returnval++ ) { // BUG: there might be some multithread issue --vluzacn if( -DIR_EPSILON < ( t = normal[0] - g_mapplanes[returnval].normal[0] ) && t < DIR_EPSILON && -DIR_EPSILON < ( t = normal[1] - g_mapplanes[returnval].normal[1] ) && t < DIR_EPSILON && -DIR_EPSILON < ( t = normal[2] - g_mapplanes[returnval].normal[2] ) && t < DIR_EPSILON ) { t = DotProduct (origin, g_mapplanes[returnval].normal) - g_mapplanes[returnval].dist; if( -DIST_EPSILON < t && t < DIST_EPSILON ) return returnval; } } ThreadLock(); if( returnval != g_nummapplanes ) // make sure we don't race { ThreadUnlock(); // check to see if other thread added plane we need goto find_plane; } // create new planes - double check that we have room for 2 planes if(( g_nummapplanes + 2 ) > MAX_INTERNAL_MAP_PLANES ) COM_FatalError( "MAX_INTERNAL_MAP_PLANES limit exceeded\n" ); p = &g_mapplanes[g_nummapplanes]; VectorCopy( origin, p->origin ); VectorCopy( normal, p->normal ); VectorNormalize( p->normal ); p->type = MapPlaneTypeForNormal( p->normal ); // snap normal to nearest axial if possible if( p->type <= PLANE_LAST_AXIAL ) { for( int i = 0; i < 3; i++ ) { if( i == p->type ) p->normal[i] = p->normal[i] > 0 ? 1 : -1; else p->normal[i] = 0; } } p->dist = DotProduct( origin, p->normal ); VectorCopy( origin, (p+1)->origin ); VectorNegate( p->normal, (p+1)->normal ); (p+1)->type = p->type; (p+1)->dist = -p->dist; // always put axial planes facing positive first if( normal[(p->type) % 3] < 0 ) { temp = *p; *p = *(p+1); *(p+1) = temp; returnval = g_nummapplanes + 1; } else { returnval = g_nummapplanes; } g_nummapplanes += 2; ThreadUnlock(); return returnval; } /* ================ PlaneFromPoints ================ */ int PlaneFromPoints( const vec_t *p0, const vec_t *p1, const vec_t *p2 ) { vec3_t t1, t2, normal; VectorSubtract( p0, p1, t1 ); VectorSubtract( p2, p1, t2 ); CrossProduct( t1, t2, normal ); if( !VectorNormalize( normal )) return -1; return FindFloatPlane( normal, p0 ); } /* ============================================================================= CONTENTS DETERMINING ============================================================================= */ /* =========== BrushCheckSides some compilers may use SKIP instead of NULL texture replace it for consistency =========== */ void BrushCheckSides( mapent_t *mapent, brush_t *b ) { int i, nodraw_faces = 0; int skip_faces = 0; int hint_faces = 0; side_t *s; // never apply this for a patches! if( FBitSet( b->flags, FBRUSH_PATCH )) return; // explicit non-solid brushes also ignore it if( FBitSet( b->flags, FBRUSH_NOCLIP )) return; for( i = 0; i < b->sides.Count(); i++ ) { s = &b->sides[i]; if( FBitSet( s->flags, FSIDE_NODRAW )) nodraw_faces++; // count only non-shaders skip if( FBitSet( s->flags, FSIDE_SKIP ) && FBitSet( s->shader->flags, FSHADER_DEFAULTED )) skip_faces++; if( FBitSet( s->flags, FSIDE_HINT|FSIDE_SOLIDHINT )) hint_faces++; } // brush have "skip" faces and remaining faces not a hint if( skip_faces && !hint_faces ) { for( i = 0; i < b->sides.Count(); i++ ) { s = &b->sides[i]; if( FBitSet( s->flags, FSIDE_SKIP )) { Q_strncpy( s->name, "NULL", sizeof( s->name )); SetBits( s->flags, FSIDE_NODRAW ); ClearBits( s->flags, FSIDE_SKIP ); s->contents = CONTENTS_SOLID; } } } } /* =========== BrushContents =========== */ int BrushContents( mapent_t *mapent, brush_t *b ) { const char *name = ValueForKey( (entity_t *)mapent, "classname" ); int i, best_i, contents; bool assigned = false; int nodraw_fases = 0; int best_contents; side_t *s; // cycle though the sides of the brush and attempt to get our best side contents for // determining overall brush contents if( b->sides.Count() == 0 ) COM_FatalError( "Entity %i, Brush %i: Brush with no sides.\n", b->originalentitynum, b->originalbrushnum ); // for each face of each brush of this entity if( BoolForKey( (entity_t *)mapent, "zhlt_precisionclip" )) SetBits( b->flags, FBRUSH_PRECISIONCLIP ); // non-solid detail entity if( !Q_stricmp( name, "func_detail_illusionary" )) { SetBits( b->flags, FBRUSH_NOCLIP ); b->detaillevel = 2; return CONTENTS_EMPTY; } // solid detail entity if( !Q_stricmp( name, "func_detail_fence" ) || !Q_stricmp( name, "func_detail_wall" )) { b->detaillevel = 1; } BrushCheckSides( mapent, b ); s = &b->sides[0]; // NULL can't be used to select contents if( FBitSet( s->flags, FSIDE_NODRAW )) best_contents = CONTENTS_NULL; else best_contents = s->contents; best_i = 0; // SKIP doesn't split space in bsp process, ContentEmpty splits space normally. if( FBitSet( s->flags, FSIDE_SKIP )) assigned = true; for( i = 1; i < b->sides.Count(); i++ ) { s = &b->sides[i]; if( assigned ) break; if( FBitSet( s->flags, FSIDE_SKIP )) { best_contents = s->contents; assigned = true; best_i = i; } // g-cont. NULL has lower priority than other contents if( s->contents > best_contents && !FBitSet( s->flags, FSIDE_NODRAW )) { // if our current surface contents is better (larger) // than our best, make it our best. best_contents = s->contents; best_i = i; } } contents = best_contents; for( i = 0; i < b->sides.Count(); i++ ) { s = &b->sides[i]; if( FBitSet( s->flags, FSIDE_NODRAW )) nodraw_fases++; if( assigned && !FBitSet( s->flags, FSIDE_SKIP|FSIDE_HINT ) && s->contents != CONTENTS_ORIGIN ) continue; // overwrite content for this texture // sky are not to cause mixed face contents if( s->contents == CONTENTS_SKY || FBitSet( s->flags, FSIDE_NODRAW )) continue; if( s->contents != best_contents ) { MsgDev( D_ERROR, "Entity %i, Brush %i: mixed face contents\n Texture %s and %s\n", b->originalentitynum, b->originalbrushnum, b->sides[best_i].name, s->name ); break; } } if( contents != CONTENTS_ORIGIN ) { if( FBitSet( b->flags, FBRUSH_NOCLIP )) contents = CONTENTS_EMPTY; if( FBitSet( b->flags, FBRUSH_CLIPONLY )) contents = CONTENTS_SOLID; } if( contents == CONTENTS_NULL ) contents = CONTENTS_SOLID; // check to make sure we dont have an origin brush as part of worldspawn if(( b->entitynum == 0 ) || ( !Q_strcmp( "func_group", name ) || !Q_strcmp( "func_landscape", name ) || !Q_strcmp( "func_detail", name ))) { if( contents == CONTENTS_ORIGIN ) MsgDev( D_ERROR, "Entity %i, Brush %i: origin brush not allowed in world\n", b->originalentitynum, b->originalbrushnum ); } else { // otherwise its not worldspawn, therefore its an entity. check to make sure this brush is allowed // to be an entity. switch( contents ) { case CONTENTS_SOLID: case CONTENTS_WATER: case CONTENTS_SLIME: case CONTENTS_LAVA: case CONTENTS_ORIGIN: case CONTENTS_EMPTY: break; default: COM_FatalError( "Entity %i, Brush %i: %s brushes not allowed in entity", b->originalentitynum, b->originalbrushnum, ContentsToString( contents )); break; } } // turn detail null-brushes into clipbrushes if( nodraw_fases == b->sides.Count() && b->detaillevel ) SetBits( b->flags, FBRUSH_CLIPONLY ); // TyrTools style details: chop faces but ignore visportals if( !Q_stricmp( "func_detail", name ) && ( contents == CONTENTS_SOLID || contents == CONTENTS_EMPTY )) b->detaillevel++; return contents; } /* ============================================================================== BEVELED CLIPPING HULL GENERATION This is done by brute force, and could easily get a lot faster if anyone cares. ============================================================================== */ #define MAX_HULL_POINTS 512 #define MAX_HULL_EDGES 1024 typedef struct expand_s { // input brush_t *b; int hullnum; // output vec3_t points[MAX_HULL_POINTS]; vec3_t corners[MAX_HULL_POINTS * 8]; int edges[MAX_HULL_EDGES][2]; int numpoints; int numedges; } expand_t; /* ============ AddHullPlane ============= */ void AddHullPlane( expand_t *ex, const vec3_t normal, const vec3_t origin ) { int planenum = FindFloatPlane( normal, origin ); brushhull_t *hull = &ex->b->hull[ex->hullnum]; for( bface_t *f = hull->faces; f != NULL; f = f->next ) { if( f->planenum == planenum ) return; // don't add a plane twice } bface_t *face = AllocFace(); face->planenum = planenum; face->plane = &g_mapplanes[face->planenum]; face->contents[0] = CONTENTS_EMPTY; face->next = hull->faces; hull->faces = face; face->texinfo = -1; } /* ============ TestAddPlane Adds the given plane to the brush description if all of the original brush vertexes can be put on the front side ============= */ void TestAddPlane( expand_t *ex, const vec3_t normal, const vec3_t origin ) { int planenum = FindFloatPlane( normal, origin ); brushhull_t *hull = &ex->b->hull[ex->hullnum]; int points_front, points_back; vec_t *corner; vec_t d; // see if the plane has allready been added for( bface_t *f = hull->faces; f != NULL; f = f->next ) { if( f->planenum == planenum ) return; // don't add a plane twice } // check all the corner points points_front = points_back = 0; corner = ex->corners[0]; for( int i = 0; i < ex->numpoints * 8; i++, corner += 3 ) { d = (corner[0] - origin[0]) * normal[0]; d += (corner[1] - origin[1]) * normal[1]; d += (corner[2] - origin[2]) * normal[2]; if( d < -ON_EPSILON ) { if( points_front ) return; points_back = 1; } else if( d > ON_EPSILON ) { if( points_back ) return; points_front = 1; } } bface_t *face = AllocFace(); face->planenum = ( points_front ) ? planenum ^ 1 : planenum; face->plane = &g_mapplanes[face->planenum]; face->contents[0] = CONTENTS_EMPTY; face->next = hull->faces; hull->faces = face; face->texinfo = -1; } /* ============ AddHullPoint Doesn't add if duplicated ============= */ int AddHullPoint( expand_t *ex, const vec3_t p ) { vec_t *c; int i; for( i = 0; i < ex->numpoints; i++ ) { if( VectorCompareEpsilon( p, ex->points[i], EQUAL_EPSILON )) return i; } if( ex->numpoints == MAX_HULL_POINTS ) COM_FatalError( "MAX_HULL_POINTS limit exceeded\n" ); VectorCopy( p, ex->points[ex->numpoints] ); c = ex->corners[i*8]; ex->numpoints++; // also add eight hull corners for( int x = 0; x < 2; x++ ) { for( int y = 0; y < 2; y++ ) { for( int z = 0; z < 2; z++ ) { c[0] = p[0] + g_hull_size[ex->hullnum][x][0]; c[1] = p[1] + g_hull_size[ex->hullnum][y][1]; c[2] = p[2] + g_hull_size[ex->hullnum][z][2]; c += 3; } } } return i; } /* ============ AddHullEdge Creates all of the hull planes around the given edge, if not done already ============= */ void AddHullEdge( expand_t *ex, const vec3_t p1, const vec3_t p2 ) { vec3_t edgevec, planevec; vec3_t normal, origin; int a, b, c, d, e; int pt1, pt2; vec_t length; pt1 = AddHullPoint( ex, p1 ); pt2 = AddHullPoint( ex, p2 ); for( int i = 0; i < ex->numedges; i++ ) { if(( ex->edges[i][0] == pt1 && ex->edges[i][1] == pt2 ) || ( ex->edges[i][0] == pt2 && ex->edges[i][1] == pt1 )) return; // already added } if( ex->numedges == MAX_HULL_EDGES ) COM_FatalError( "MAX_HULL_EDGES limit exceeded\n" ); ex->edges[i][0] = pt1; ex->edges[i][1] = pt2; ex->numedges++; VectorSubtract( p1, p2, edgevec ); VectorNormalize( edgevec ); for( a = 0; a < 3; a++ ) { b = (a + 1) % 3; c = (a + 2) % 3; planevec[a] = 1.0; planevec[b] = 0.0; planevec[c] = 0.0; CrossProduct( planevec, edgevec, normal ); length = VectorNormalize( normal ); // if this edge is almost parallel to the hull edge, skip it. if( length < NORMAL_EPSILON ) continue; for( d = 0; d <= 1; d++ ) { for( e = 0; e <= 1; e++ ) { VectorCopy( p1, origin ); origin[b] += g_hull_size[ex->hullnum][d][b]; origin[c] += g_hull_size[ex->hullnum][e][c]; TestAddPlane( ex, normal, origin ); } } } } /* ============ ExpandBrush ============= */ void ExpandBrush2( brush_t *b, int hullnum ) { bface_t *brush_faces, *f; vec3_t origin, normal; bool axial = true; int i, x, s; vec_t corner; brushhull_t *hull; expand_t ex; plane_t *p; brush_faces = b->hull[0].faces; hull = &b->hull[hullnum]; ex.hullnum = hullnum; ex.numpoints = 0; ex.numedges = 0; ex.b = b; // expand all of the planes for( f = brush_faces; f != NULL; f = f->next ) { p = f->plane; if( p->type > PLANE_LAST_AXIAL ) axial = false; // not an xyz axial plane VectorCopy( p->origin, origin ); VectorCopy( p->normal, normal ); for ( x = 0; x < 3; x++ ) { if( p->normal[x] > 0.0 ) corner = g_hull_size[hullnum][1][x]; else if( p->normal[x] < 0.0 ) corner = -g_hull_size[hullnum][0][x]; else corner = 0.0; origin[x] += p->normal[x] * corner; } bface_t *face = AllocFace(); face->planenum = FindFloatPlane( normal, origin ); face->plane = &g_mapplanes[face->planenum]; face->contents[0] = CONTENTS_EMPTY; face->next = hull->faces; hull->faces = face; face->texinfo = -1; } // if this was an axial brush, we are done if( axial ) return; // add any axis planes not contained in the brush to bevel off corners for( x = 0; x < 3; x++ ) { for( s = -1; s <= 1; s += 2 ) { // add the plane VectorClear( normal ); normal[x] = s; if( s == -1 ) VectorAdd( b->hull[0].mins, g_hull_size[hullnum][0], origin ); else VectorAdd( b->hull[0].maxs, g_hull_size[hullnum][1], origin ); AddHullPlane( &ex, normal, origin ); } } // create all the hull points for( f = brush_faces; f != NULL; f = f->next ) { for( i = 0; i < f->w->numpoints; i++ ) AddHullPoint( &ex, f->w->p[i] ); } // add all of the edge bevels for( f = brush_faces; f != NULL; f = f->next ) { for( i = 0; i < f->w->numpoints; i++ ) AddHullEdge( &ex, f->w->p[i], f->w->p[(i + 1) % f->w->numpoints] ); } } /* ============================================================================== BEVELED CLIPPING HULL GENERATION This is done by brute force, and could easily get a lot faster if anyone cares. ============================================================================== */ // ===================================================================================== // AddHullPlane (subroutine for replacement of ExpandBrush, KGP) // Called to add any and all clip hull planes by the new ExpandBrush. // ===================================================================================== void AddHullPlane( brushhull_t *hull, const vec3_t normal, const vec3_t origin, bool check_planenum ) { int planenum = FindFloatPlane( normal, origin ); // check to see if this plane is already in the brush (optional to speed // up cases where we know the plane hasn't been added yet, like axial case) if( check_planenum ) { // we know axial planes are added in last step if( g_mapplanes[planenum].type <= PLANE_LAST_AXIAL ) return; for( bface_t *f = hull->faces; f != NULL; f = f->next ) { if( f->planenum == planenum ) return; // don't add a plane twice } } bface_t *face = AllocFace(); face->planenum = planenum; face->plane = &g_mapplanes[face->planenum]; face->contents[0] = CONTENTS_EMPTY; face->next = hull->faces; hull->faces = face; face->texinfo = -1; } /* ============ ExpandBrush ============= */ void ExpandBrush( brush_t *b, int hullnum ) { vec3_t edge_start, edge_end; plane_t *plane, *plane2; vec3_t edge, bevel_edge; bool start_found, end_found; bool warned = false; vec3_t origin, normal; bface_t *f, *f2; winding_t *w, *w2; brushhull_t *hull; hull = &b->hull[hullnum]; // step 1: for collision between player vertex and brush face. --vluzacn for( f = b->hull[0].faces; f != NULL; f = f->next ) { plane = f->plane; // don't bother adding axial planes, // they're defined by adding the bounding box anyway if( plane->type <= PLANE_LAST_AXIAL ) continue; // add the offset non-axial plane to the expanded hull VectorCopy( plane->origin, origin ); VectorCopy( plane->normal, normal ); // old code multiplied offset by normal -- this led to post-csg "sticky" walls where a // slope met an axial plane from the next brush since the offset from the slope would be less // than the full offset for the axial plane -- the discontinuity also contributes to increased // clipnodes. If the normal is zero along an axis, shifting the origin in that direction won't // change the plane number, so I don't explicitly test that case. The old method is still used if // preciseclip is turned off to allow backward compatability -- some of the improperly beveled edges // grow using the new origins, and might cause additional problems. origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0]; origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1]; origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2]; AddHullPlane( hull, normal, origin, false ); } // step 2: for collision between player edge and brush edge. --vluzacn // split bevel check into a second pass so we don't have to check for duplicate planes when adding offset planes // in step above -- otherwise a bevel plane might duplicate an offset plane, causing problems later on. for( f = b->hull[0].faces; f != NULL; f = f->next ) { plane = f->plane; // test to see if the plane is completely non-axial (if it is, need to add bevels to any // existing "inflection edges" where there's a sign change with a neighboring plane's normal for // a given axis) // move along w and find plane on other side of each edge. If normals change sign, // add a new plane by offsetting the points of the w to bevel the edge in that direction. // It's possible to have inflection in multiple directions -- in this case, a new plane // must be added for each sign change in the edge. w = f->w; // do it for each edge for( int i = 0; i < w->numpoints; i++ ) { VectorCopy( w->p[i], edge_start ); VectorCopy( w->p[(i + 1) % w->numpoints], edge_end ); // grab the edge (find relative length) VectorSubtract( edge_end, edge_start, edge ); // brute force - need to check every other w for common points // if the points match, the other face is the one we need to look at. for( f2 = b->hull[0].faces; f2 != NULL; f2 = f2->next ) { if( f == f2 ) continue; start_found = false; end_found = false; w2 = f2->w; for( int j = 0; j < w2->numpoints; j++ ) { if( !start_found && VectorCompareEpsilon( w2->p[j], edge_start, EQUAL_EPSILON )) start_found = true; if( !end_found && VectorCompareEpsilon( w2->p[j], edge_end, EQUAL_EPSILON )) end_found = true; // we've found the face we want, move on to planar comparison if( start_found && end_found ) break; } if( start_found && end_found ) break; } if( !f2 ) { if( hullnum == 1 && !warned ) { MsgDev( D_WARN, "Illegal Brush (edge without opposite face): Entity %i, Brush %i\n", b->originalentitynum, b->originalbrushnum ); warned = true; } continue; } plane2 = f2->plane; // check each direction for sign change in normal -- zero can be safely ignored for( int dir = 0; dir < 3; dir++ ) { // if sign changed, add bevel if( plane->normal[dir] * plane2->normal[dir] < -NORMAL_EPSILON ) { // pick direction of bevel edge by looking at normal of existing planes VectorClear( bevel_edge ); bevel_edge[dir] = (plane->normal[dir] > 0) ? -1 : 1; // find normal by taking normalized cross of the edge vector and the bevel edge CrossProduct( edge, bevel_edge, normal ); VectorNormalize( normal ); if( fabs( normal[(dir+1)%3]) <= NORMAL_EPSILON || fabs( normal[(dir+2)%3] ) <= NORMAL_EPSILON ) { // coincide with axial plane continue; } // get the origin VectorCopy( edge_start, origin ); // note: if normal == 0 in direction indicated, shifting origin doesn't change plane # origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0]; origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1]; origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2]; // add the bevel plane to the expanded hull AddHullPlane( hull, normal, origin, true ); } } } } // step 3: for collision between player face and brush vertex. --vluzacn // add the bounding box to the expanded hull -- for a // completely axial brush, this is the only necessary step // add mins VectorAdd( b->hull[0].mins, g_hull_size[hullnum][0], origin ); VectorSet( normal, -1.0, 0.0, 0.0 ); AddHullPlane( hull, normal, origin, false ); VectorSet( normal, 0.0, -1.0, 0.0 ); AddHullPlane( hull, normal, origin, false ); VectorSet( normal, 0.0, 0.0, -1.0 ); AddHullPlane( hull, normal, origin, false ); // add maxs VectorAdd( b->hull[0].maxs, g_hull_size[hullnum][1], origin ); VectorSet( normal, 1.0, 0.0, 0.0 ); AddHullPlane( hull, normal, origin, false ); VectorSet( normal, 0.0, 1.0, 0.0 ); AddHullPlane( hull, normal, origin, false ); VectorSet( normal, 0.0, 0.0, 1.0 ); AddHullPlane( hull, normal, origin, false ); #if _DEBUG // sanity check for( f = hull->faces; f != NULL; f = f->next ) { for( f2 = b->hull[0].faces; f2 != NULL; f2 = f2->next ) { if( f2->w->numpoints < 3 ) continue; for( int i = 0; i < f2->w->numpoints; i++ ) { if( DotProduct( f->plane->normal, f->plane->origin ) < DotProduct( f->plane->normal, f2->w->p[i] )) { MsgDev( D_WARN, "Illegal Brush (clip hull [%i] has backward face): Entity %i, Brush %i\n", hullnum, b->originalentitynum, b->originalbrushnum ); break; } } } } #endif } //============================================================================ void FreeHullFaces( void ) { for( int i = 0; i < g_nummapbrushes; i++ ) { brush_t *b = &g_mapbrushes[i]; for( int j = 0; j < MAX_MAP_HULLS; j++ ) { UnlinkFaces( &b->hull[j].faces ); } // throw sides too b->sides.Purge(); } } void SortHullFaces( brushhull_t *h ) { int numfaces; bface_t **faces; vec3_t *normals; bool *isused; int i, j; int *sorted; bface_t *f; for( numfaces = 0, f = h->faces; f != NULL; f = f->next ) numfaces++; faces = (bface_t **)Mem_Alloc( numfaces * sizeof( bface_t* ), C_TEMPORARY ); normals = (vec3_t *)Mem_Alloc( numfaces * sizeof( vec3_t ), C_TEMPORARY ); isused = (bool *)Mem_Alloc( numfaces * sizeof( bool ), C_TEMPORARY ); sorted = (int *)Mem_Alloc( numfaces * sizeof( int ), C_TEMPORARY ); for( i = 0, f = h->faces; f != NULL; i++, f = f->next ) { const plane_t *p = &g_mapplanes[f->planenum]; VectorCopy( p->normal, normals[i] ); faces[i] = f; } for( i = 0; i < numfaces; i++ ) { int bestaxial = -1; int bestside; for( j = 0; j < numfaces; j++) { if( isused[j] ) continue; int axial = AxisFromNormal( normals[j] ); if( axial > bestaxial ) { bestaxial = axial; bestside = j; } } sorted[i] = bestside; isused[bestside] = true; } for( i = -1; i < numfaces; i++ ) { *(i >= 0 ? &faces[sorted[i]]->next: &h->faces) = (i + 1 < numfaces ? faces[sorted[i + 1]] : NULL); } Mem_Free( faces, C_TEMPORARY ); Mem_Free( normals, C_TEMPORARY ); Mem_Free( isused, C_TEMPORARY ); Mem_Free( sorted, C_TEMPORARY ); } /* =========== MakeHullFaces =========== */ void MakeHullFaces( brush_t *b, brushhull_t *h, int hullnum ) { bface_t *f, *f2; mapent_t *mapent; bool warned = false; vec_t v, area; int i, j; vec3_t point; winding_t *w; plane_t *p; mapent = &g_mapentities[b->entitynum]; // sorted faces make BSP-splits is more axial than unsorted SortHullFaces( h ); restart: ClearBounds( h->mins, h->maxs ); for( f = h->faces; f != NULL; f = f->next ) { w = BaseWindingForPlane( f->plane->normal, f->plane->dist ); ASSERT( w != NULL ); for( f2 = h->faces; f2 != NULL; f2 = f2->next ) { if( f == f2 ) continue; p = &g_mapplanes[f2->planenum ^ 1]; if( !ChopWindingInPlace( &w, p->normal, p->dist, NORMAL_EPSILON, false )) break; // nothing to chop? } RemoveColinearPointsEpsilon( w, ON_EPSILON ); area = WindingArea( w ); if( area < MICROVOLUME ) { if( w ) FreeWinding( w ); if( !w && !warned && hullnum == 0 ) { MsgDev( D_WARN, "Illegal Brush (plane doesn't contribute to final shape): Entity %i, Brush %i\n", b->originalentitynum, b->originalbrushnum ); warned = true; } // remove the face and regenerate the hull UnlinkFaces( &h->faces, f ); goto restart; } f->contents[0] = CONTENTS_EMPTY; f->w = w; // at this point winging is always valid for( i = 0; i < w->numpoints; i++ ) { for( j = 0; j < 3; j++ ) { point[j] = w->p[i][j]; v = Q_rint( point[j] ); if( fabs( point[j] - v ) < DIR_EPSILON ) w->p[i][j] = v; else w->p[i][j] = point[j]; // check for incomplete brushes if( w->p[i][j] >= WORLD_MAXS || w->p[i][j] <= WORLD_MINS ) break; } // remove this brush if( j < 3 ) { UnlinkFaces( &h->faces ); MsgDev( D_REPORT, "Entity %i, Brush %i: degenerate brush was removed\n", b->originalentitynum, b->originalbrushnum ); return; } if( !FBitSet( f->flags, FSIDE_SKIP )) AddPointToBounds( w->p[i], h->mins, h->maxs ); } // make sure what all faces has valid ws CheckWindingEpsilon( w, ON_EPSILON, ( hullnum == 0 )); } for( i = 0; i < 3; i++ ) { if( h->mins[i] < WORLD_MINS || h->maxs[i] > WORLD_MAXS ) { MsgDev( D_ERROR, "Entity %i, Brush %i: outside world(+/-%d): (%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)\n", b->originalentitynum, b->originalbrushnum, BOGUS_RANGE / 2, h->mins[0], h->mins[1], h->mins[2], h->maxs[0], h->maxs[1], h->maxs[2] ); break; } } if( i == 3 && hullnum == 0 ) { // compute total entity bounds AddPointToBounds( h->mins, mapent->absmin, mapent->absmax ); AddPointToBounds( h->maxs, mapent->absmin, mapent->absmax ); } } /* =========== MakeBrushPlanes =========== */ int MakeBrushPlanes( brush_t *b ) { bool skipface; int badsides = 0; vec3_t planepts[3]; vec3_t origin; bface_t *f; side_t *s; // // if the origin key is set (by an origin brush), offset all of the values // GetVectorForKey( (entity_t *)&g_mapentities[b->entitynum], "origin", origin ); // convert to mapplanes for( int i = 0; i < b->sides.Count(); i++ ) { s = &b->sides[i]; skipface = false; if( b->entitynum ) { for( int k = 0; k < 3; k++ ) VectorSubtract( s->planepts[k], origin, planepts[k] ); s->planenum = PlaneFromPoints( planepts[0], planepts[1], planepts[2] ); } else { // world doesn't required offset by origin s->planenum = PlaneFromPoints( s->planepts[0], s->planepts[1], s->planepts[2] ); } if( s->planenum == -1 ) { MsgDev( D_REPORT, "Entity %i, Brush %i: plane with no normal\n", b->originalentitynum, b->originalbrushnum ); badsides++; continue; } // see if the plane has been used already for( f = b->hull[0].faces; f != NULL; f = f->next ) { // g-cont. there is non fatal for us. just reject this face and trying again if( f->planenum == s->planenum || f->planenum == ( s->planenum ^ 1 )) { MsgDev( D_REPORT, "Entity %i, Brush %i, Side %i: has a coplanar plane\n", b->originalentitynum, b->originalbrushnum, i ); skipface = true; badsides++; break; } } if( skipface ) continue; f = AllocFace(); f->planenum = s->planenum; f->plane = &g_mapplanes[s->planenum]; f->next = b->hull[0].faces; b->hull[0].faces = f; f->texinfo = g_onlyents ? -1 : TexinfoForSide( f->plane, s, origin ); f->flags = s->flags; } return badsides; } /* =========== CreateBrushFaces =========== */ void CreateBrushFaces( brush_t *b ) { // convert brush sides to planes int badsides = MakeBrushPlanes( b ); if( badsides > 0 ) { MsgDev( D_WARN, "Entity %i, Brush %i: has %d invalid sides (total %d)\n", b->originalentitynum, b->originalbrushnum, badsides, b->sides.Count() ); } MakeHullFaces( b, &b->hull[0], 0 ); if( b->contents == CONTENTS_EMPTY || b->contents == CONTENTS_ORIGIN ) return; if( !g_noclip && ( FBitSet( b->flags, FBRUSH_CLIPONLY ) || !FBitSet( b->flags, FBRUSH_NOCLIP ))) { for( int h = 1; h < MAX_MAP_HULLS; h++ ) { if( VectorIsNull( g_hull_size[h][0] ) && VectorIsNull( g_hull_size[h][1] )) continue; if( FBitSet( b->flags, FBRUSH_PRECISIONCLIP )) ExpandBrush2( b, h ); else ExpandBrush( b, h ); MakeHullFaces( b, &b->hull[h], h ); } } // invisible detail brush it's a clipbrush if( FBitSet( b->flags, FBRUSH_CLIPONLY )) { UnlinkFaces( &b->hull[0].faces ); b->hull[0].faces = NULL; } } /* =========== CreateBrush multi-thread version =========== */ void CreateBrush( int brushnum, int threadnum ) { CreateBrushFaces( &g_mapbrushes[brushnum] ); } /* ================== DeleteBrushFaces release specified face or purge all chain ================== */ void DeleteBrushFaces( brush_t *b ) { if( !b ) return; for( int i = 0; i < MAX_MAP_HULLS; i++ ) UnlinkFaces( &b->hull[i].faces ); } /* =========== DumpBrushPlanes NOTE: never alloc planes during threads work because planes order will be different for each compile and we gets various BSP'ing result every time when we compile map again! =========== */ void DumpBrushPlanes( void ) { #if 0 for( int i = 0; i < g_nummapbrushes; i++ ) { brush_t *b = &g_mapbrushes[i]; if( b->contents == CONTENTS_ORIGIN ) continue; Msg( "brush #%i\n", i ); // convert to mapplanes for( int j = 0; j < b->sides.Count(); j++ ) { side_t *s = &b->sides[j]; plane_t *mp = &g_mapplanes[s->planenum]; Msg( "#%i, (%g %g %g) - (%g), %d\n", j, mp->normal[0], mp->normal[1], mp->normal[2], mp->dist, mp->type ); } } #endif } void ProcessAutoOrigins( void ) { char string[32]; const char *classname; const char *pclassname; const char *ptarget; mapent_t *parent; mapent_t *mapent; vec3_t origin; int c_origins_processed = 0; bool origin_from_parent; MsgDev( D_REPORT, "ProcessAutoOrigins:\n" ); // skip the world for( int i = 1; i < g_mapentities.Count(); i++ ) { origin_from_parent = false; mapent = &g_mapentities[i]; parent = NULL; if( !mapent->numbrushes ) continue; // for some reasons entity doesn't have a valid size if( BoundsIsCleared( mapent->absmin, mapent->absmax )) continue; GetVectorForKey( (entity_t *)mapent, "origin", origin ); // origin was set by level-designer if( !VectorIsNull( origin )) continue; classname = ValueForKey( (entity_t *)mapent, "classname" ); // g-cont. old-good hack for my entity :-) if( !Q_strcmp( classname, "func_traindoor" )) { if( CheckKey( (entity_t *)mapent, "train" )) parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "train" )); } else if( !Q_strncmp( classname, "rotate_", 7 )) { // Quake1 rotational objects support parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "target" )); origin_from_parent = true; } else if( !Q_strncmp( classname, "func_portal", 11 )) { // portal always required origin-brush parent = mapent; } if( !parent && CheckKey( (entity_t *)mapent, "parent" )) parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "parent" )); if( !parent ) continue; pclassname = ValueForKey( (entity_t *)parent, "classname" ); ptarget = ValueForKey( (entity_t *)parent, "targetname" ); if( origin_from_parent || !Q_strcmp( pclassname, "func_door_rotating" )) { GetVectorForKey( (entity_t *)parent, "origin", origin ); Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", origin[0], origin[1], origin[2] ); SetKeyValue( (entity_t *)mapent, "origin", string ); } else { // now we have: // 1. entity with valid absmin\absmax // 2. it have valid parent // 3. it doesn't have custom origin VectorAverage( mapent->absmin, mapent->absmax, origin ); Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", origin[0], origin[1], origin[2] ); SetKeyValue( (entity_t *)mapent, "origin", string ); } MsgDev( D_REPORT, "%s, parent %s (%s) auto origin %g %g %g\n", classname, pclassname, ptarget, origin[0], origin[1], origin[2] ); for( int j = 0; j < mapent->numbrushes; j++ ) { brush_t *b = &g_mapbrushes[mapent->firstbrush + j]; // remove old brush faces DeleteBrushFaces( b ); // new brush with origin-adjusted sides CreateBrushFaces( b ); } c_origins_processed++; } if( c_origins_processed > 0 ) MsgDev( D_INFO, "total %i entities was adjusted with auto-origin\n", c_origins_processed ); } void RestoreModelOrigins( void ) { const char *pclassname; const char *ptarget; char string[32]; for( int i = 1; i < g_nummodels; i++ ) { mapent_t *ent = MapEntityForModel( i ); dmodel_t *bm = &g_dmodels[i]; if( VectorIsNull( bm->origin )) continue; // for some reasons origin already set if( CheckKey( (entity_t *)ent, "origin" )) continue; pclassname = ValueForKey( (entity_t *)ent, "classname" ); ptarget = ValueForKey( (entity_t *)ent, "targetname" ); MsgDev( D_REPORT, "%s, (%s) auto origin %g %g %g\n", pclassname, ptarget, bm->origin[0], bm->origin[1], bm->origin[2] ); Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", bm->origin[0], bm->origin[1], bm->origin[2] ); SetKeyValue( (entity_t *)ent, "origin", string ); } }