#include "csg.h" plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; int g_nummapplanes; #define DIST_EPSILON 0.01 #if !defined HLCSG_FASTFIND /* * ============= * FindIntPlane * * Returns which plane number to use for a given integer defined plane. * * ============= */ int FindIntPlane(const vec_t* const normal, const vec_t* const origin) { int i, j; plane_t* p; plane_t temp; vec_t t; bool locked; p = g_mapplanes; locked = false; i = 0; while (1) { if (i == g_nummapplanes) { if (!locked) { locked = true; ThreadLock(); // make sure we don't race } if (i == g_nummapplanes) { break; // we didn't race } } t = 0; // Unrolled loop t += (origin[0] - p->origin[0]) * normal[0]; t += (origin[1] - p->origin[1]) * normal[1]; t += (origin[2] - p->origin[2]) * normal[2]; if (fabs(t) < DIST_EPSILON) { // on plane // see if the normal is forward, backwards, or off for (j = 0; j < 3; j++) { if (fabs(normal[j] - p->normal[j]) > NORMAL_EPSILON) { break; } } if (j == 3) { if (locked) { ThreadUnlock(); } return i; } } i++; p++; } hlassert(locked); // create a new plane p->origin[0] = origin[0]; p->origin[1] = origin[1]; p->origin[2] = origin[2]; (p + 1)->origin[0] = origin[0]; (p + 1)->origin[1] = origin[1]; (p + 1)->origin[2] = origin[2]; p->normal[0] = normal[0]; p->normal[1] = normal[1]; p->normal[2] = normal[2]; (p + 1)->normal[0] = -normal[0]; (p + 1)->normal[1] = -normal[1]; (p + 1)->normal[2] = -normal[2]; hlassume(g_nummapplanes < MAX_INTERNAL_MAP_PLANES, assume_MAX_INTERNAL_MAP_PLANES); VectorNormalize(p->normal); p->type = (p + 1)->type = PlaneTypeForNormal(p->normal); p->dist = DotProduct(origin, p->normal); VectorSubtract(vec3_origin, p->normal, (p + 1)->normal); (p + 1)->dist = -p->dist; // always put axial planes facing positive first if (p->type <= last_axial) { if (normal[0] < 0 || normal[1] < 0 || normal[2] < 0) { // flip order temp = *p; *p = *(p + 1); *(p + 1) = temp; g_nummapplanes += 2; ThreadUnlock(); return i + 1; } } g_nummapplanes += 2; ThreadUnlock(); return i; } #else //ifdef HLCSG_FASTFIND // ===================================================================================== // FindIntPlane, fast version (replacement by KGP) // This process could be optimized by placing the planes in a (non hash-) set and using // half of the inner loop check below as the comparator; I'd expect the speed gain to be // very large given the change from O(N^2) to O(NlogN) to build the set of planes. // ===================================================================================== int FindIntPlane(const vec_t* const normal, const vec_t* const origin) { int returnval; plane_t* p; plane_t temp; vec_t t; returnval = 0; find_plane: for( ; returnval < g_nummapplanes; returnval++) { if( -NORMAL_EPSILON < (t = normal[0] - g_mapplanes[returnval].normal[0]) && t < NORMAL_EPSILON && -NORMAL_EPSILON < (t = normal[1] - g_mapplanes[returnval].normal[1]) && t < NORMAL_EPSILON && -NORMAL_EPSILON < (t = normal[2] - g_mapplanes[returnval].normal[2]) && t < NORMAL_EPSILON ) { //t = (origin - plane_origin) dot (normal), unrolled t = (origin[0] - g_mapplanes[returnval].origin[0]) * normal[0] + (origin[1] - g_mapplanes[returnval].origin[1]) * normal[1] + (origin[2] - g_mapplanes[returnval].origin[2]) * normal[2]; if (-DIST_EPSILON < t && t < DIST_EPSILON) // on plane { return returnval; } } } ThreadLock(); if(returnval != g_nummapplanes) // make sure we don't race { ThreadUnlock(); goto find_plane; //check to see if other thread added plane we need } // create new planes - double check that we have room for 2 planes hlassume(g_nummapplanes+1 < MAX_INTERNAL_MAP_PLANES, assume_MAX_INTERNAL_MAP_PLANES); p = &g_mapplanes[g_nummapplanes]; VectorCopy(origin,p->origin); VectorCopy(normal,p->normal); VectorNormalize(p->normal); p->type = PlaneTypeForNormal(p->normal); p->dist = DotProduct(origin, p->normal); VectorCopy(origin,(p+1)->origin); VectorSubtract(vec3_origin,p->normal,(p+1)->normal); (p+1)->type = p->type; (p+1)->dist = -p->dist; // always put axial planes facing positive first if (p->type <= last_axial && (normal[0] < 0 || normal[1] < 0 || normal[2] < 0)) // flip order { temp = *p; *p = *(p+1); *(p+1) = temp; returnval = g_nummapplanes+1; } else { returnval = g_nummapplanes; } g_nummapplanes += 2; ThreadUnlock(); return returnval; } #endif //HLCSG_FASTFIND int PlaneFromPoints(const vec_t* const p0, const vec_t* const p1, const vec_t* const p2) { vec3_t v1, v2; vec3_t normal; VectorSubtract(p0, p1, v1); VectorSubtract(p2, p1, v2); CrossProduct(v1, v2, normal); if (VectorNormalize(normal)) { return FindIntPlane(normal, p0); } return -1; } #ifdef HLCSG_PRECISIONCLIP const char ClipTypeStrings[5][11] = {{"smallest"},{"normalized"},{"simple"},{"precise"},{"legacy"}}; const char* GetClipTypeString(cliptype ct) { return ClipTypeStrings[ct]; } // ===================================================================================== // 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 vec_t* const normal, const vec_t* const origin, const bool check_planenum) { int planenum = FindIntPlane(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) { if(g_mapplanes[planenum].type <= last_axial) //we know axial planes are added in last step { return; } bface_t* current_face; for(current_face = hull->faces; current_face; current_face = current_face->next) { if(current_face->planenum == planenum) { return; } //don't add a plane twice } } bface_t* new_face = (bface_t*)Alloc(sizeof(bface_t)); // TODO: This leaks new_face->planenum = planenum; new_face->plane = &g_mapplanes[new_face->planenum]; new_face->next = hull->faces; new_face->contents = CONTENTS_EMPTY; hull->faces = new_face; new_face->texinfo = 0; } // ===================================================================================== // ExpandBrush (replacement by KGP) // Since the six bounding box planes were always added anyway, they've been moved to // an explicit separate step eliminating the need to check for duplicate planes (which // should be using plane numbers instead of the full definition anyway). // // The core of the new function adds additional bevels to brushes containing faces that // have 3 nonzero normal components -- this is necessary to finish the beveling process, // but is turned off by default for backward compatability and because the number of // clipnodes and faces will go up with the extra beveling. The advantage of the extra // precision comes from the absense of "sticky" outside corners on ackward geometry. // // Another source of "sticky" walls has been the inconsistant offset along each axis // (variant with plane normal in the old code). The normal component of the offset has // been scrapped (it made a ~29% difference in the worst case of 45 degrees, or about 10 // height units for a standard half-life player hull). The new offsets generate fewer // clipping nodes and won't cause players to stick when moving across 2 brushes that flip // sign along an axis (this wasn't noticible on floors because the engine took care of the // invisible 0-3 unit steps it generated, but was noticible with walls). // // To prevent players from floating 10 units above the floor, the "precise" hull generation // option still uses the plane normal when the Z component is high enough for the plane to // be considered a floor. The "simple" hull generation option always uses the full hull // distance, resulting in lower clipnode counts. // // Bevel planes might be added twice (once from each side of the edge), so a planenum // based check is used to see if each has been added before. // ===================================================================================== void ExpandBrush(brush_t* brush, const int hullnum) { //for looping through the faces and constructing the hull bface_t* current_face; plane_t* current_plane; brushhull_t* hull; vec3_t origin, normal; //for non-axial bevel testing Winding* winding; bface_t* other_face; plane_t* other_plane; Winding* other_winding; vec3_t edge_start, edge_end, edge, bevel_edge; unsigned int counter, counter2, dir; bool start_found,end_found; bool axialbevel[last_axial+1][2] = { {false,false}, {false,false}, {false,false} }; bool warned = false; hull = &brush->hulls[hullnum]; for(current_face = brush->hulls[0].faces; current_face; current_face = current_face->next) { current_plane = current_face->plane; //don't bother adding axial planes, //they're defined by adding the bounding box anyway if(current_plane->type <= last_axial) { //flag case where bounding box shouldn't expand if((g_texinfo[current_face->texinfo].flags & TEX_BEVEL)) { switch(current_plane->type) { case plane_x: axialbevel[plane_x][(current_plane->normal[0] > 0 ? 1 : 0)] = true; break; case plane_y: axialbevel[plane_y][(current_plane->normal[1] > 0 ? 1 : 0)] = true; break; case plane_z: axialbevel[plane_z][(current_plane->normal[2] > 0 ? 1 : 0)] = true; break; } } continue; } //add the offset non-axial plane to the expanded hull VectorCopy(current_plane->origin, origin); VectorCopy(current_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. if((g_texinfo[current_face->texinfo].flags & TEX_BEVEL)) { //don't adjust origin - we'll correct g_texinfo's flags in a later step } else if(g_cliptype == clip_legacy || (g_cliptype == clip_precise && (normal[2] > FLOOR_Z)) || g_cliptype == clip_normalized) { if(normal[0]) { origin[0] += normal[0] * (normal[0] > 0 ? g_hull_size[hullnum][1][0] : -g_hull_size[hullnum][0][0]); } if(normal[1]) { origin[1] += normal[1] * (normal[1] > 0 ? g_hull_size[hullnum][1][1] : -g_hull_size[hullnum][0][1]); } if(normal[2]) { origin[2] += normal[2] * (normal[2] > 0 ? g_hull_size[hullnum][1][2] : -g_hull_size[hullnum][0][2]); } } else { 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); } //end for loop over all faces //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. //only executes if cliptype is simple, normalized or precise if(g_cliptype == clip_simple || g_cliptype == clip_precise || g_cliptype == clip_normalized) { for(current_face = brush->hulls[0].faces; current_face; current_face = current_face->next) { current_plane = current_face->plane; if(current_plane->type <= last_axial || !current_plane->normal[0] || !current_plane->normal[1] || !current_plane->normal[2]) { continue; } //only add bevels to completely non-axial planes //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 winding and find plane on other side of each edge. If normals change sign, //add a new plane by offsetting the points of the winding 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. winding = current_face->w; for(counter = 0; counter < (winding->m_NumPoints); counter++) //for each edge { VectorCopy(winding->m_Points[counter],edge_start); VectorCopy(winding->m_Points[(counter+1)%winding->m_NumPoints],edge_end); //grab the edge (find relative length) VectorSubtract(edge_end,edge_start,edge); //brute force - need to check every other winding for common points -- if the points match, the //other face is the one we need to look at. for(other_face = brush->hulls[0].faces; other_face; other_face = other_face->next) { if(other_face == current_face) { continue; } start_found = false; end_found = false; other_winding = other_face->w; for(counter2 = 0; counter2 < other_winding->m_NumPoints; counter2++) { if(!start_found && VectorCompare(other_winding->m_Points[counter2],edge_start)) { start_found = true; } if(!end_found && VectorCompare(other_winding->m_Points[counter2],edge_end)) { end_found = true; } if(start_found && end_found) { break; } //we've found the face we want, move on to planar comparison } // for each point in other winding if(start_found && end_found) { break; } //we've found the face we want, move on to planar comparison } // for each face if(!other_face) { if(hullnum == 1 && !warned) { Warning("Illegal Brush (edge without opposite face): Entity %i, Brush %i\n",brush->entitynum, brush->brushnum); warned = true; } continue; } other_plane = other_face->plane; //check each direction for sign change in normal -- zero can be safely ignored for(dir = 0; dir < 3; dir++) { if(current_plane->normal[dir]*other_plane->normal[dir] < 0) //sign changed, add bevel { //pick direction of bevel edge by looking at normal of existing planes VectorClear(bevel_edge); bevel_edge[dir] = (current_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); //normalize to length 1 VectorNormalize(normal); //get the origin VectorCopy(edge_start,origin); //unrolled loop - legacy never hits this point, so don't test for it if((g_cliptype == clip_precise && (normal[2] > FLOOR_Z)) || g_cliptype == clip_normalized) { if(normal[0]) { origin[0] += normal[0] * (normal[0] > 0 ? g_hull_size[hullnum][1][0] : -g_hull_size[hullnum][0][0]); } if(normal[1]) { origin[1] += normal[1] * (normal[1] > 0 ? g_hull_size[hullnum][1][1] : -g_hull_size[hullnum][0][1]); } if(normal[2]) { origin[2] += normal[2] * (normal[2] > 0 ? g_hull_size[hullnum][1][2] : -g_hull_size[hullnum][0][2]); } } else //simple or precise for non-floors { //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); //double check that this edge hasn't been added yet } } //end for loop (check for each direction) } //end for loop (over all edges in face) } //end for loop (over all faces in hull 0) } //end if completely non-axial //add the bounding box to the expanded hull -- for a //completely axial brush, this is the only necessary step //add mins VectorAdd(brush->hulls[0].bounds.m_Mins, g_hull_size[hullnum][0], origin); normal[0] = -1; normal[1] = 0; normal[2] = 0; AddHullPlane(hull,normal,(axialbevel[plane_x][0] ? brush->hulls[0].bounds.m_Mins : origin),false); normal[0] = 0; normal[1] = -1; AddHullPlane(hull,normal,(axialbevel[plane_y][0] ? brush->hulls[0].bounds.m_Mins : origin),false); normal[1] = 0; normal[2] = -1; AddHullPlane(hull,normal,(axialbevel[plane_z][0] ? brush->hulls[0].bounds.m_Mins : origin),false); normal[2] = 0; //add maxes VectorAdd(brush->hulls[0].bounds.m_Maxs, g_hull_size[hullnum][1], origin); normal[0] = 1; AddHullPlane(hull,normal,(axialbevel[plane_x][1] ? brush->hulls[0].bounds.m_Maxs : origin),false); normal[0] = 0; normal[1] = 1; AddHullPlane(hull,normal,(axialbevel[plane_y][1] ? brush->hulls[0].bounds.m_Maxs : origin),false); normal[1] = 0; normal[2] = 1; AddHullPlane(hull,normal,(axialbevel[plane_z][1] ? brush->hulls[0].bounds.m_Maxs : origin),false); /* bface_t* hull_face; //sanity check for(hull_face = hull->faces; hull_face; hull_face = hull_face->next) { for(current_face = brush->hulls[0].faces; current_face; current_face = current_face->next) { if(current_face->w->m_NumPoints < 3) { continue; } for(counter = 0; counter < current_face->w->m_NumPoints; counter++) { if(DotProduct(hull_face->plane->normal,hull_face->plane->origin) < DotProduct(hull_face->plane->normal,current_face->w->m_Points[counter])) { Warning("Illegal Brush (clip hull [%i] has backward face): Entity %i, Brush %i\n",hullnum,brush->entitynum, brush->brushnum); break; } } } } */ } #else //!HLCSG_PRECISIONCLIP #define MAX_HULL_POINTS 32 #define MAX_HULL_EDGES 64 typedef struct { brush_t* b; int hullnum; int num_hull_points; vec3_t hull_points[MAX_HULL_POINTS]; vec3_t hull_corners[MAX_HULL_POINTS * 8]; int num_hull_edges; int hull_edges[MAX_HULL_EDGES][2]; } expand_t; /* * ============= * IPlaneEquiv * * ============= */ bool IPlaneEquiv(const plane_t* const p1, const plane_t* const p2) { vec_t t; int j; // see if origin is on plane t = 0; for (j = 0; j < 3; j++) { t += (p2->origin[j] - p1->origin[j]) * p2->normal[j]; } if (fabs(t) > DIST_EPSILON) { return false; } // see if the normal is forward, backwards, or off for (j = 0; j < 3; j++) { if (fabs(p2->normal[j] - p1->normal[j]) > NORMAL_EPSILON) { break; } } if (j == 3) { return true; } for (j = 0; j < 3; j++) { if (fabs(p2->normal[j] - p1->normal[j]) > NORMAL_EPSILON) { break; } } if (j == 3) { return true; } return false; } /* * ============ * AddBrushPlane * ============= */ void AddBrushPlane(const expand_t* const ex, const plane_t* const plane) { plane_t* pl; bface_t* f; bface_t* nf; brushhull_t* h; h = &ex->b->hulls[ex->hullnum]; // see if the plane has allready been added for (f = h->faces; f; f = f->next) { pl = f->plane; if (IPlaneEquiv(plane, pl)) { return; } } nf = (bface_t*)Alloc(sizeof(*nf)); // TODO: This leaks nf->planenum = FindIntPlane(plane->normal, plane->origin); nf->plane = &g_mapplanes[nf->planenum]; nf->next = h->faces; nf->contents = CONTENTS_EMPTY; h->faces = nf; nf->texinfo = 0; // all clip hulls have same texture } // ===================================================================================== // ExpandBrush // ===================================================================================== void ExpandBrush(brush_t* b, const int hullnum) { int x; int s; int corner; bface_t* brush_faces; bface_t* f; bface_t* nf; plane_t* p; plane_t plane; vec3_t origin; vec3_t normal; expand_t ex; brushhull_t* h; bool axial; brush_faces = b->hulls[0].faces; h = &b->hulls[hullnum]; ex.b = b; ex.hullnum = hullnum; ex.num_hull_points = 0; ex.num_hull_edges = 0; // expand all of the planes axial = true; // for each of this brushes faces for (f = brush_faces; f; f = f->next) { p = f->plane; if (p->type > last_axial) // ajm: last_axial == (planetypes enum)plane_z == (2) { 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) { corner = g_hull_size[hullnum][1][x]; } else if (p->normal[x] < 0) { corner = -g_hull_size[hullnum][0][x]; } else { corner = 0; } origin[x] += p->normal[x] * corner; } nf = (bface_t*)Alloc(sizeof(*nf)); // TODO: This leaks nf->planenum = FindIntPlane(normal, origin); nf->plane = &g_mapplanes[nf->planenum]; nf->next = h->faces; nf->contents = CONTENTS_EMPTY; h->faces = nf; nf->texinfo = 0; // all clip hulls have same texture // nf->texinfo = f->texinfo; // Hack to view clipping hull with textures (might crash halflife) } // 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 VectorCopy(vec3_origin, plane.normal); plane.normal[x] = s; if (s == -1) { VectorAdd(b->hulls[0].bounds.m_Mins, g_hull_size[hullnum][0], plane.origin); } else { VectorAdd(b->hulls[0].bounds.m_Maxs, g_hull_size[hullnum][1], plane.origin); } AddBrushPlane(&ex, &plane); } } } #endif //HLCSG_PRECISECLIP // ===================================================================================== // MakeHullFaces // ===================================================================================== void MakeHullFaces(const brush_t* const b, brushhull_t *h) { bface_t* f; bface_t* f2; #ifdef HLCSG_PRECISECLIP bool warned = false; #endif restart: h->bounds.reset(); // for each face in this brushes hull for (f = h->faces; f; f = f->next) { Winding* w = new Winding(f->plane->normal, f->plane->dist); for (f2 = h->faces; f2; f2 = f2->next) { if (f == f2) { continue; } const plane_t* p = &g_mapplanes[f2->planenum ^ 1]; if (!w->Chop(p->normal, p->dist)) // Nothing left to chop (getArea will return 0 for us in this case for below) { break; } } if (w->getArea() < 0.1) { #ifdef HLCSG_PRECISECLIP if(w->getArea() == 0 && !warned) //warn user when there's a bad brush (face not contributing) { Warning("Illegal Brush (plane doesn't contribute to final shape): Entity %i, Brush %i\n",b->entitynum, b->brushnum); warned = true; } #endif delete w; if (h->faces == f) { h->faces = f->next; } else { for (f2 = h->faces; f2->next != f; f2 = f2->next) { ; } f2->next = f->next; } goto restart; } else { f->w = w; f->contents = CONTENTS_EMPTY; unsigned int i; for (i = 0; i < w->m_NumPoints; i++) { h->bounds.add(w->m_Points[i]); } } } unsigned int i; for (i = 0; i < 3; i++) { if (h->bounds.m_Mins[i] < -BOGUS_RANGE / 2 || h->bounds.m_Maxs[i] > BOGUS_RANGE / 2) { Fatal(assume_BRUSH_OUTSIDE_WORLD, "Entity %i, Brush %i: outside world(+/-%d): (%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)", b->entitynum, b->brushnum, BOGUS_RANGE / 2, h->bounds.m_Mins[0], h->bounds.m_Mins[1], h->bounds.m_Mins[2], h->bounds.m_Maxs[0], h->bounds.m_Maxs[1], h->bounds.m_Maxs[2]); } } } // ===================================================================================== // MakeBrushPlanes // ===================================================================================== bool MakeBrushPlanes(brush_t* b) { int i; int j; int planenum; side_t* s; bface_t* f; vec3_t origin; // // if the origin key is set (by an origin brush), offset all of the values // GetVectorForKey(&g_entities[b->entitynum], "origin", origin); // // convert to mapplanes // // for each side in this brush for (i = 0; i < b->numsides; i++) { s = &g_brushsides[b->firstside + i]; for (j = 0; j < 3; j++) { VectorSubtract(s->planepts[j], origin, s->planepts[j]); } planenum = PlaneFromPoints(s->planepts[0], s->planepts[1], s->planepts[2]); if (planenum == -1) { Fatal(assume_PLANE_WITH_NO_NORMAL, "Entity %i, Brush %i, Side %i: plane with no normal", b->entitynum, b->brushnum, i); } // // see if the plane has been used already // for (f = b->hulls[0].faces; f; f = f->next) { if (f->planenum == planenum || f->planenum == (planenum ^ 1)) { Fatal(assume_BRUSH_WITH_COPLANAR_FACES, "Entity %i, Brush %i, Side %i: has a coplanar plane at (%.0f, %.0f, %.0f), texture %s", b->entitynum, b->brushnum, i, s->planepts[0][0] + origin[0], s->planepts[0][1] + origin[1], s->planepts[0][2] + origin[2], s->td.name); } } f = (bface_t*)Alloc(sizeof(*f)); // TODO: This leaks f->planenum = planenum; f->plane = &g_mapplanes[planenum]; f->next = b->hulls[0].faces; b->hulls[0].faces = f; f->texinfo = g_onlyents ? 0 : TexinfoForBrushTexture(f->plane, &s->td, origin); } return true; } // ===================================================================================== // TextureContents // ===================================================================================== static contents_t TextureContents(const char* const name) { if (!strncasecmp(name, "sky", 3)) return CONTENTS_SKY; // ===================================================================================== //Cpt_Andrew - Env_Sky Check // ===================================================================================== if (!strncasecmp(name, "env_sky", 3)) return CONTENTS_SKY; // ===================================================================================== if (!strncasecmp(name + 1, "!lava", 5)) return CONTENTS_LAVA; if (!strncasecmp(name + 1, "!slime", 6)) return CONTENTS_SLIME; if (name[0] == '!') //optimized -- don't check for current unless it's liquid (KGP) { if (!strncasecmp(name, "!cur_90", 7)) return CONTENTS_CURRENT_90; if (!strncasecmp(name, "!cur_0", 6)) return CONTENTS_CURRENT_0; if (!strncasecmp(name, "!cur_270", 8)) return CONTENTS_CURRENT_270; if (!strncasecmp(name, "!cur_180", 8)) return CONTENTS_CURRENT_180; if (!strncasecmp(name, "!cur_up", 7)) return CONTENTS_CURRENT_UP; if (!strncasecmp(name, "!cur_dwn", 8)) return CONTENTS_CURRENT_DOWN; return CONTENTS_WATER; //default for liquids } if (!strncasecmp(name, "origin", 6)) return CONTENTS_ORIGIN; if (!strncasecmp(name, "clip", 4)) return CONTENTS_CLIP; if (!strncasecmp(name, "hint", 4)) return CONTENTS_HINT; if (!strncasecmp(name, "skip", 4)) return CONTENTS_HINT; if (!strncasecmp(name, "translucent", 11)) return CONTENTS_TRANSLUCENT; if (name[0] == '@') return CONTENTS_TRANSLUCENT; #ifdef ZHLT_NULLTEX // AJM: if (!strncasecmp(name, "null", 4)) return CONTENTS_NULL; #ifdef HLCSG_PRECISIONCLIP // KGP if(!strncasecmp(name,"bevel",5)) return CONTENTS_NULL; #endif //precisionclip #endif //nulltex return CONTENTS_SOLID; } // ===================================================================================== // ContentsToString // ===================================================================================== const char* ContentsToString(const contents_t type) { switch (type) { case CONTENTS_EMPTY: return "EMPTY"; case CONTENTS_SOLID: return "SOLID"; case CONTENTS_WATER: return "WATER"; case CONTENTS_SLIME: return "SLIME"; case CONTENTS_LAVA: return "LAVA"; case CONTENTS_SKY: return "SKY"; case CONTENTS_ORIGIN: return "ORIGIN"; case CONTENTS_CLIP: return "CLIP"; case CONTENTS_CURRENT_0: return "CURRENT_0"; case CONTENTS_CURRENT_90: return "CURRENT_90"; case CONTENTS_CURRENT_180: return "CURRENT_180"; case CONTENTS_CURRENT_270: return "CURRENT_270"; case CONTENTS_CURRENT_UP: return "CURRENT_UP"; case CONTENTS_CURRENT_DOWN: return "CURRENT_DOWN"; case CONTENTS_TRANSLUCENT: return "TRANSLUCENT"; case CONTENTS_HINT: return "HINT"; #ifdef ZHLT_NULLTEX // AJM case CONTENTS_NULL: return "NULL"; #endif #ifdef ZHLT_DETAIL // AJM case CONTENTS_DETAIL: return "DETAIL"; #endif default: return "UNKNOWN"; } } // ===================================================================================== // CheckBrushContents // Perfoms abitrary checking on brush surfaces and states to try and catch errors // ===================================================================================== contents_t CheckBrushContents(const brush_t* const b) { contents_t best_contents; contents_t contents; side_t* s; int i; s = &g_brushsides[b->firstside]; // cycle though the sides of the brush and attempt to get our best side contents for // determining overall brush contents best_contents = TextureContents(s->td.name); s++; for (i = 1; i < b->numsides; i++, s++) { contents_t contents_consider = TextureContents(s->td.name); if (contents_consider > best_contents) { // if our current surface contents is better (larger) than our best, make it our best. best_contents = contents_consider; } } contents = best_contents; // attempt to pick up on mixed_face_contents errors s = &g_brushsides[b->firstside]; s++; for (i = 1; i < b->numsides; i++, s++) { contents_t contents2 = TextureContents(s->td.name); // AJM: sky and null types are not to cause mixed face contents if (contents2 == CONTENTS_SKY) continue; #ifdef ZHLT_NULLTEX if (contents2 == CONTENTS_NULL) continue; #endif if (contents2 != best_contents) { Fatal(assume_MIXED_FACE_CONTENTS, "Entity %i, Brush %i: mixed face contents\n Texture %s and %s", b->entitynum, b->brushnum, g_brushsides[b->firstside].td.name, s->td.name); } } // check to make sure we dont have an origin brush as part of worldspawn if ((b->entitynum == 0) || (strcmp("func_group", ValueForKey(&g_entities[b->entitynum], "classname"))==0)) { if (contents == CONTENTS_ORIGIN) { Fatal(assume_BRUSH_NOT_ALLOWED_IN_WORLD, "Entity %i, Brush %i: %s brushes not allowed in world\n(did you forget to tie this origin brush to a rotating entity?)", b->entitynum, b->brushnum, ContentsToString(contents)); } } 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_CLIP: #ifdef ZHLT_NULLTEX // AJM case CONTENTS_NULL: break; #endif default: Fatal(assume_BRUSH_NOT_ALLOWED_IN_ENTITY, "Entity %i, Brush %i: %s brushes not allowed in entity", b->entitynum, b->brushnum, ContentsToString(contents)); break; } } return contents; } // ===================================================================================== // CreateBrush // makes a brush! // ===================================================================================== void CreateBrush(const int brushnum) { brush_t* b; int contents; int h; b = &g_mapbrushes[brushnum]; contents = b->contents; if (contents == CONTENTS_ORIGIN) return; // HULL 0 MakeBrushPlanes(b); MakeHullFaces(b, &b->hulls[0]); // these brush types do not need to be represented in the clipping hull switch (contents) { case CONTENTS_LAVA: case CONTENTS_SLIME: case CONTENTS_WATER: case CONTENTS_TRANSLUCENT: case CONTENTS_HINT: return; } #ifdef HLCSG_CLIPECONOMY // AJM if (b->noclip) return; #endif // HULLS 1-3 if (!g_noclip) { for (h = 1; h < NUM_HULLS; h++) { ExpandBrush(b, h); MakeHullFaces(b, &b->hulls[h]); } } // clip brushes don't stay in the drawing hull if (contents == CONTENTS_CLIP) { b->hulls[0].faces = NULL; b->contents = CONTENTS_SOLID; } }