/* Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 1997 Greg Lewis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #include #include /* * Beveled clipping hull can generate many extra faces */ #define MAX_FACES 128 #define MAX_HULL_POINTS 512 #define MAX_HULL_EDGES 1024 typedef struct hullbrush_s { const mapbrush_t *srcbrush; int numfaces; vec3_t mins; vec3_t maxs; mapface_t faces[MAX_FACES]; int numpoints; int numedges; vec3_t points[MAX_HULL_POINTS]; vec3_t corners[MAX_HULL_POINTS * 8]; int edges[MAX_HULL_EDGES][2]; } hullbrush_t; /* ================= Face_Plane ================= */ plane_t Face_Plane(const face_t *face) { const qbsp_plane_t *plane = &map.planes.at(face->planenum); plane_t result; result.dist = plane->dist; VectorCopy(plane->normal, result.normal); if (face->planeside) { VectorScale(result.normal, -1.0, result.normal); result.dist = -result.dist; } return result; } /* ================= CheckFace Note: this will not catch 0 area polygons ================= */ void CheckFace(face_t *face) { const qbsp_plane_t *plane = &map.planes[face->planenum]; const vec_t *p1, *p2; vec_t length, dist, edgedist; vec3_t edgevec, edgenormal, facenormal; int i, j; if (face->w.numpoints < 3) { if (face->w.numpoints == 2) { Error("%s: too few points (2): (%f %f %f) (%f %f %f)\n", __func__, face->w.points[0][0], face->w.points[0][1], face->w.points[0][2], face->w.points[1][0], face->w.points[1][1], face->w.points[1][2]); } else if (face->w.numpoints == 1) { Error("%s: too few points (1): (%f %f %f)\n", __func__, face->w.points[0][0], face->w.points[0][1], face->w.points[0][2]); } else { Error("%s: too few points (%d)", __func__, face->w.numpoints); } } VectorCopy(plane->normal, facenormal); if (face->planeside) VectorSubtract(vec3_origin, facenormal, facenormal); for (i = 0; i < face->w.numpoints; i++) { p1 = face->w.points[i]; p2 = face->w.points[(i + 1) % face->w.numpoints]; for (j = 0; j < 3; j++) if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) Error("%s: coordinate out of range (%f)", __func__, p1[j]); /* check the point is on the face plane */ dist = DotProduct(p1, plane->normal) - plane->dist; if (dist < -ON_EPSILON || dist > ON_EPSILON) Message(msgWarning, warnPointOffPlane, p1[0], p1[1], p1[2], dist); /* check the edge isn't degenerate */ VectorSubtract(p2, p1, edgevec); length = VectorLength(edgevec); if (length < ON_EPSILON) { Message(msgWarning, warnDegenerateEdge, length, p1[0], p1[1], p1[2]); for (j = i + 1; j < face->w.numpoints; j++) VectorCopy(face->w.points[j], face->w.points[j - 1]); face->w.numpoints--; CheckFace(face); break; } CrossProduct(facenormal, edgevec, edgenormal); VectorNormalize(edgenormal); edgedist = DotProduct(p1, edgenormal); edgedist += ON_EPSILON; /* all other points must be on front side */ for (j = 0; j < face->w.numpoints; j++) { if (j == i) continue; dist = DotProduct(face->w.points[j], edgenormal); if (dist > edgedist) Error("%s: Found a non-convex face (error size %f, point: %f %f %f)\n", __func__, dist - edgedist, face->w.points[j][0], face->w.points[j][1], face->w.points[j][2]); } } } //=========================================================================== /* ================= AddToBounds ================= */ static void AddToBounds(mapentity_t *entity, const vec3_t point) { int i; for (i = 0; i < 3; i++) { if (point[i] < entity->mins[i]) entity->mins[i] = point[i]; if (point[i] > entity->maxs[i]) entity->maxs[i] = point[i]; } } //=========================================================================== static int NormalizePlane(qbsp_plane_t *p) { int i; vec_t ax, ay, az; p->outputplanenum = -1; for (i = 0; i < 3; i++) { if (p->normal[i] == 1.0) { p->normal[(i + 1) % 3] = 0; p->normal[(i + 2) % 3] = 0; p->type = PLANE_X + i; return 0; /* no flip */ } if (p->normal[i] == -1.0) { p->normal[i] = 1.0; p->normal[(i + 1) % 3] = 0; p->normal[(i + 2) % 3] = 0; p->dist = -p->dist; p->type = PLANE_X + i; return 1; /* plane flipped */ } } ax = fabs(p->normal[0]); ay = fabs(p->normal[1]); az = fabs(p->normal[2]); if (ax >= ay && ax >= az) p->type = PLANE_ANYX; else if (ay >= ax && ay >= az) p->type = PLANE_ANYY; else p->type = PLANE_ANYZ; if (p->normal[p->type - PLANE_ANYX] < 0) { VectorSubtract(vec3_origin, p->normal, p->normal); p->dist = -p->dist; return 1; /* plane flipped */ } return 0; /* no flip */ } bool PlaneEqual(const qbsp_plane_t *p1, const qbsp_plane_t *p2) { return (fabs(p1->normal[0] - p2->normal[0]) < NORMAL_EPSILON && fabs(p1->normal[1] - p2->normal[1]) < NORMAL_EPSILON && fabs(p1->normal[2] - p2->normal[2]) < NORMAL_EPSILON && fabs(p1->dist - p2->dist) < DIST_EPSILON); } bool PlaneInvEqual(const qbsp_plane_t *p1, const qbsp_plane_t *p2) { qbsp_plane_t temp = {0}; VectorScale(p1->normal, -1.0, temp.normal); temp.dist = -p1->dist; return PlaneEqual(&temp, p2); } /* Plane Hashing */ static inline int plane_hash_fn(const qbsp_plane_t *p) { return Q_rint(fabs(p->dist)); } static void PlaneHash_Add(const qbsp_plane_t *p, int index) { const int hash = plane_hash_fn(p); map.planehash[hash].push_back(index); } /* * NewPlane * - Returns a global plane number and the side that will be the front */ static int NewPlane(const vec3_t normal, const vec_t dist, int *side) { vec_t len; len = VectorLength(normal); if (len < 1 - ON_EPSILON || len > 1 + ON_EPSILON) Error("%s: invalid normal (vector length %.4f)", __func__, len); qbsp_plane_t plane; VectorCopy(normal, plane.normal); plane.dist = dist; *side = NormalizePlane(&plane) ? SIDE_BACK : SIDE_FRONT; int index = map.planes.size(); map.planes.push_back(plane); PlaneHash_Add(&plane, index); return index; } /* * FindPlane * - Returns a global plane number and the side that will be the front */ int FindPlane(const vec3_t normal, const vec_t dist, int *side) { qbsp_plane_t plane = {0}; VectorCopy(normal, plane.normal); plane.dist = dist; for (int i : map.planehash[plane_hash_fn(&plane)]) { const qbsp_plane_t &p = map.planes.at(i); if (PlaneEqual(&p, &plane)) { *side = SIDE_FRONT; return i; } else if (PlaneInvEqual(&p, &plane)) { *side = SIDE_BACK; return i; } } return NewPlane(plane.normal, plane.dist, side); } /* ============================================================================= TURN BRUSHES INTO GROUPS OF FACES ============================================================================= */ /* ================= FindTargetEntity ================= */ static const mapentity_t * FindTargetEntity(const char *target) { int i; const char *name; const mapentity_t *entity; for (i = 0; i < map.numentities(); i++) { entity = &map.entities.at(i); name = ValueForKey(entity, "targetname"); if (!Q_strcasecmp(target, name)) return entity; } return NULL; } /* ================= FixRotateOrigin ================= */ void FixRotateOrigin(mapentity_t *entity) { const mapentity_t *target = NULL; const char *search; vec3_t offset; char value[20]; search = ValueForKey(entity, "target"); if (search[0]) target = FindTargetEntity(search); if (target) { GetVectorForKey(target, "origin", offset); } else { search = ValueForKey(entity, "classname"); Message(msgWarning, warnNoRotateTarget, search); VectorCopy(vec3_origin, offset); } q_snprintf(value, sizeof(value), "%d %d %d", (int)offset[0], (int)offset[1], (int)offset[2]); SetKeyValue(entity, "origin", value); } /* ================= CreateBrushFaces ================= */ static face_t * CreateBrushFaces(hullbrush_t *hullbrush, const vec3_t rotate_offset, const int hullnum) { int i, j, k; vec_t r; face_t *f; winding_t *w; qbsp_plane_t plane; face_t *facelist = NULL; mapface_t *mapface, *mapface2; vec3_t point; vec_t max, min; min = VECT_MAX; max = -VECT_MAX; for (i = 0; i < 3; i++) { hullbrush->mins[i] = VECT_MAX; hullbrush->maxs[i] = -VECT_MAX; } mapface = hullbrush->faces; for (i = 0; i < hullbrush->numfaces; i++, mapface++) { if (!hullnum) { /* Don't generate hintskip faces */ const mtexinfo_t &texinfo = map.mtexinfos.at(mapface->texinfo); const char *texname = map.miptex.at(texinfo.miptex).c_str(); if (!Q_strcasecmp(texname, "hintskip")) continue; } w = BaseWindingForPlane(&mapface->plane); mapface2 = hullbrush->faces; for (j = 0; j < hullbrush->numfaces && w; j++, mapface2++) { if (j == i) continue; // flip the plane, because we want to keep the back side VectorSubtract(vec3_origin, mapface2->plane.normal, plane.normal); plane.dist = -mapface2->plane.dist; w = ClipWinding(w, &plane, false); } if (!w) continue; // overconstrained plane // this face is a keeper f = (face_t *)AllocMem(FACE, 1, true); f->w.numpoints = w->numpoints; if (f->w.numpoints > MAXEDGES) Error("face->numpoints > MAXEDGES (%d), source face on line %d", MAXEDGES, mapface->linenum); for (j = 0; j < w->numpoints; j++) { for (k = 0; k < 3; k++) { point[k] = w->points[j][k] - rotate_offset[k]; r = Q_rint(point[k]); if (fabs(point[k] - r) < ZERO_EPSILON) f->w.points[j][k] = r; else f->w.points[j][k] = point[k]; if (f->w.points[j][k] < hullbrush->mins[k]) hullbrush->mins[k] = f->w.points[j][k]; if (f->w.points[j][k] > hullbrush->maxs[k]) hullbrush->maxs[k] = f->w.points[j][k]; if (f->w.points[j][k] < min) min = f->w.points[j][k]; if (f->w.points[j][k] > max) max = f->w.points[j][k]; } } // account for texture offset, from txqbsp-xt if (options.fixRotateObjTexture) { const mtexinfo_t &texinfo = map.mtexinfos.at(mapface->texinfo); mtexinfo_t texInfoNew; vec3_t vecs[2]; int k, l; memcpy(&texInfoNew, &texinfo, sizeof(texInfoNew)); for (k=0; k<2; k++) { for (l=0; l<3; l++) { vecs[k][l] = texinfo.vecs[k][l]; } } texInfoNew.vecs[0][3] += DotProduct( rotate_offset, vecs[0] ); texInfoNew.vecs[1][3] += DotProduct( rotate_offset, vecs[1] ); mapface->texinfo = FindTexinfo( &texInfoNew, texInfoNew.flags ); } VectorCopy(mapface->plane.normal, plane.normal); VectorScale(mapface->plane.normal, mapface->plane.dist, point); VectorSubtract(point, rotate_offset, point); plane.dist = DotProduct(plane.normal, point); FreeMem(w, WINDING, 1); f->texinfo = hullnum ? 0 : mapface->texinfo; f->planenum = FindPlane(plane.normal, plane.dist, &f->planeside); f->next = facelist; facelist = f; CheckFace(f); UpdateFaceSphere(f); } // Rotatable objects must have a bounding box big enough to // account for all its rotations if (rotate_offset[0] || rotate_offset[1] || rotate_offset[2]) { vec_t delta; delta = fabs(max); if (fabs(min) > delta) delta = fabs(min); for (k = 0; k < 3; k++) { hullbrush->mins[k] = -delta; hullbrush->maxs[k] = delta; } } return facelist; } /* ================= FreeBrushFaces ================= */ static void FreeBrushFaces(face_t *facelist) { face_t *face, *next; for (face = facelist; face; face = next) { next = face->next; FreeMem(face, FACE, 1); } } /* ===================== FreeBrushes ===================== */ void FreeBrushes(brush_t *brushlist) { brush_t *brush, *next; for (brush = brushlist; brush; brush = next) { next = brush->next; FreeBrushFaces(brush->faces); FreeMem(brush, BRUSH, 1); } } /* ============================================================================== BEVELED CLIPPING HULL GENERATION This is done by brute force, and could easily get a lot faster if anyone cares. ============================================================================== */ /* ============ AddBrushPlane ============= */ static void AddBrushPlane(hullbrush_t *hullbrush, qbsp_plane_t *plane) { int i; mapface_t *mapface; vec_t len; len = VectorLength(plane->normal); if (len < 1.0 - NORMAL_EPSILON || len > 1.0 + NORMAL_EPSILON) Error("%s: invalid normal (vector length %.4f)", __func__, len); mapface = hullbrush->faces; for (i = 0; i < hullbrush->numfaces; i++, mapface++) { if (VectorCompare(mapface->plane.normal, plane->normal, EQUAL_EPSILON) && fabs(mapface->plane.dist - plane->dist) < ON_EPSILON) return; } if (hullbrush->numfaces == MAX_FACES) Error("brush->faces >= MAX_FACES (%d), source brush on line %d", MAX_FACES, hullbrush->srcbrush->face(0).linenum); mapface->plane = *plane; mapface->texinfo = 0; hullbrush->numfaces++; } /* ============ TestAddPlane Adds the given plane to the brush description if all of the original brush vertexes can be put on the front side ============= */ static void TestAddPlane(hullbrush_t *hullbrush, qbsp_plane_t *plane) { int i, c; vec_t d; mapface_t *mapface; vec_t *corner; qbsp_plane_t flip; int points_front, points_back; /* see if the plane has already been added */ mapface = hullbrush->faces; for (i = 0; i < hullbrush->numfaces; i++, mapface++) { if (PlaneEqual(plane, &mapface->plane)) return; if (PlaneInvEqual(plane, &mapface->plane)) return; } /* check all the corner points */ points_front = 0; points_back = 0; corner = hullbrush->corners[0]; c = hullbrush->numpoints * 8; for (i = 0; i < c; i++, corner += 3) { d = DotProduct(corner, plane->normal) - plane->dist; if (d < -ON_EPSILON) { if (points_front) return; points_back = 1; } else if (d > ON_EPSILON) { if (points_back) return; points_front = 1; } } // the plane is a seperator if (points_front) { VectorSubtract(vec3_origin, plane->normal, flip.normal); flip.dist = -plane->dist; plane = &flip; } AddBrushPlane(hullbrush, plane); } /* ============ AddHullPoint Doesn't add if duplicated ============= */ static int AddHullPoint(hullbrush_t *hullbrush, vec3_t p, vec3_t hull_size[2]) { int i; vec_t *c; int x, y, z; for (i = 0; i < hullbrush->numpoints; i++) if (VectorCompare(p, hullbrush->points[i], EQUAL_EPSILON)) return i; if (hullbrush->numpoints == MAX_HULL_POINTS) Error("hullbrush->numpoints == MAX_HULL_POINTS (%d), " "source brush on line %d", MAX_HULL_POINTS, hullbrush->srcbrush->face(0).linenum); VectorCopy(p, hullbrush->points[hullbrush->numpoints]); c = hullbrush->corners[i * 8]; for (x = 0; x < 2; x++) for (y = 0; y < 2; y++) for (z = 0; z < 2; z++) { c[0] = p[0] + hull_size[x][0]; c[1] = p[1] + hull_size[y][1]; c[2] = p[2] + hull_size[z][2]; c += 3; } hullbrush->numpoints++; return i; } /* ============ AddHullEdge Creates all of the hull planes around the given edge, if not done allready ============= */ static void AddHullEdge(hullbrush_t *hullbrush, vec3_t p1, vec3_t p2, vec3_t hull_size[2]) { int pt1, pt2; int i; int a, b, c, d, e; vec3_t edgevec, planeorg, planevec; qbsp_plane_t plane; vec_t length; pt1 = AddHullPoint(hullbrush, p1, hull_size); pt2 = AddHullPoint(hullbrush, p2, hull_size); for (i = 0; i < hullbrush->numedges; i++) if ((hullbrush->edges[i][0] == pt1 && hullbrush->edges[i][1] == pt2) || (hullbrush->edges[i][0] == pt2 && hullbrush->edges[i][1] == pt1)) return; if (hullbrush->numedges == MAX_HULL_EDGES) Error("hullbrush->numedges == MAX_HULL_EDGES (%d), " "source brush on line %d", MAX_HULL_EDGES, hullbrush->srcbrush->face(0).linenum); hullbrush->edges[i][0] = pt1; hullbrush->edges[i][1] = pt2; hullbrush->numedges++; VectorSubtract(p1, p2, edgevec); VectorNormalize(edgevec); for (a = 0; a < 3; a++) { b = (a + 1) % 3; c = (a + 2) % 3; planevec[a] = 1; planevec[b] = 0; planevec[c] = 0; CrossProduct(planevec, edgevec, plane.normal); length = VectorLength(plane.normal); /* If this edge is almost parallel to the hull edge, skip it. */ if (length < ANGLEEPSILON) continue; VectorScale(plane.normal, 1.0 / length, plane.normal); for (d = 0; d <= 1; d++) { for (e = 0; e <= 1; e++) { VectorCopy(p1, planeorg); planeorg[b] += hull_size[d][b]; planeorg[c] += hull_size[e][c]; plane.dist = DotProduct(planeorg, plane.normal); TestAddPlane(hullbrush, &plane); } } } } /* ============ ExpandBrush ============= */ static void ExpandBrush(hullbrush_t *hullbrush, vec3_t hull_size[2], face_t *facelist) { int i, x, s; vec3_t corner; face_t *f; qbsp_plane_t plane; mapface_t *mapface; int cBevEdge = 0; hullbrush->numpoints = 0; hullbrush->numedges = 0; // create all the hull points for (f = facelist; f; f = f->next) for (i = 0; i < f->w.numpoints; i++) { AddHullPoint(hullbrush, f->w.points[i], hull_size); cBevEdge++; } // expand all of the planes mapface = hullbrush->faces; for (i = 0; i < hullbrush->numfaces; i++, mapface++) { VectorCopy(vec3_origin, corner); for (x = 0; x < 3; x++) { if (mapface->plane.normal[x] > 0) corner[x] = hull_size[1][x]; else if (mapface->plane.normal[x] < 0) corner[x] = hull_size[0][x]; } mapface->plane.dist += DotProduct(corner, mapface->plane.normal); } // 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] = (vec_t)s; if (s == -1) plane.dist = -hullbrush->mins[x] + -hull_size[0][x]; else plane.dist = hullbrush->maxs[x] + hull_size[1][x]; AddBrushPlane(hullbrush, &plane); } // add all of the edge bevels for (f = facelist; f; f = f->next) for (i = 0; i < f->w.numpoints; i++) AddHullEdge(hullbrush, f->w.points[i], f->w.points[(i + 1) % f->w.numpoints], hull_size); } //============================================================================ static int Brush_GetContents(const mapbrush_t *mapbrush) { const char *texname; const mapface_t &mapface = mapbrush->face(0); const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); texname = map.miptex.at(texinfo.miptex).c_str(); if (!Q_strcasecmp(texname, "origin")) return CONTENTS_ORIGIN; if (!Q_strcasecmp(texname, "hint") || !Q_strcasecmp(texname, "hintskip")) return CONTENTS_HINT; if (!Q_strcasecmp(texname, "clip")) return CONTENTS_CLIP; if (texname[0] == '*') { if (!Q_strncasecmp(texname + 1, "lava", 4)) return CONTENTS_LAVA; if (!Q_strncasecmp(texname + 1, "slime", 5)) return CONTENTS_SLIME; return CONTENTS_WATER; } if (!Q_strncasecmp(texname, "sky", 3)) return CONTENTS_SKY; return CONTENTS_SOLID; } /* =============== LoadBrush Converts a mapbrush to a bsp brush =============== */ brush_t *LoadBrush(const mapbrush_t *mapbrush, const vec3_t rotate_offset, const int hullnum) { hullbrush_t hullbrush; brush_t *brush; face_t *facelist; // create the faces if (mapbrush->numfaces > MAX_FACES) Error("brush->faces >= MAX_FACES (%d), source brush on line %d", MAX_FACES, mapbrush->face(0).linenum); hullbrush.srcbrush = mapbrush; hullbrush.numfaces = mapbrush->numfaces; for (int i=0; inumfaces; i++) hullbrush.faces[i] = mapbrush->face(i); if (hullnum == 0) { facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } else { // for clipping hulls, don't apply rotation offset yet.. // it will be applied below facelist = CreateBrushFaces(&hullbrush, vec3_origin, hullnum); } if (!facelist) { Message(msgWarning, warnNoBrushFaces); logprint("^ brush at line %d of .map file\n", mapbrush->face(0).linenum); return NULL; } if (options.hexen2) { if (hullnum == 1) { vec3_t size[2] = { {-16, -16, -32}, {16, 16, 24} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } else if (hullnum == 2) { vec3_t size[2] = { {-24, -24, -20}, {24, 24, 20} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } else if (hullnum == 3) { vec3_t size[2] = { {-16, -16, -12}, {16, 16, 16} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } else if (hullnum == 4) { #if 0 if (options.hexen2 == 1) { /*original game*/ vec3_t size[2] = { {-40, -40, -42}, {40, 40, 42} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } else #endif { /*mission pack*/ vec3_t size[2] = { {-8, -8, -8}, {8, 8, 8} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } } else if (hullnum == 5) { vec3_t size[2] = { {-48, -48, -50}, {48, 48, 50} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } } else { if (hullnum == 1) { vec3_t size[2] = { {-16, -16, -32}, {16, 16, 24} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } else if (hullnum == 2) { vec3_t size[2] = { {-32, -32, -64}, {32, 32, 24} }; ExpandBrush(&hullbrush, size, facelist); FreeBrushFaces(facelist); facelist = CreateBrushFaces(&hullbrush, rotate_offset, hullnum); } } // create the brush brush = (brush_t *)AllocMem(BRUSH, 1, true); brush->faces = facelist; VectorCopy(hullbrush.mins, brush->mins); VectorCopy(hullbrush.maxs, brush->maxs); return brush; } //============================================================================= static brush_t * Brush_ListTail(brush_t *brush) { if (brush == nullptr) { return nullptr; } while (brush->next != nullptr) { brush = brush->next; } Q_assert(brush->next == nullptr); return brush; } int Brush_ListCountWithCFlags(const brush_t *brush, int cflags) { int cnt = 0; for (const brush_t *b = brush; b; b = b->next) { if (cflags == (b->cflags & cflags)) cnt++; } return cnt; } int Brush_ListCount(const brush_t *brush) { return Brush_ListCountWithCFlags(brush, 0); } static int FaceListCount(const face_t *facelist) { if (facelist) return 1 + FaceListCount(facelist->next); else return 0; } int Brush_NumFaces(const brush_t *brush) { return FaceListCount(brush->faces); } void Entity_SortBrushes(mapentity_t *dst) { Q_assert(dst->brushes == nullptr); brush_t **nextLink = &dst->brushes; if (dst->detail_illusionary) { brush_t *last = Brush_ListTail(dst->detail_illusionary); *nextLink = dst->detail_illusionary; nextLink = &last->next; } if (dst->liquid) { brush_t *last = Brush_ListTail(dst->liquid); *nextLink = dst->liquid; nextLink = &last->next; } if (dst->detail_fence) { brush_t *last = Brush_ListTail(dst->detail_fence); *nextLink = dst->detail_fence; nextLink = &last->next; } if (dst->detail) { brush_t *last = Brush_ListTail(dst->detail); *nextLink = dst->detail; nextLink = &last->next; } if (dst->sky) { brush_t *last = Brush_ListTail(dst->sky); *nextLink = dst->sky; nextLink = &last->next; } if (dst->solid) { brush_t *last = Brush_ListTail(dst->solid); *nextLink = dst->solid; nextLink = &last->next; } } /* ============ Brush_LoadEntity hullnum -1 should contain ALL brushes. hullnum 0 does not contain clip brushes. ============ */ void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) { const char *classname; const mapbrush_t *mapbrush; vec3_t rotate_offset; int i, contents, cflags = 0; int lmshift; bool detail, detail_fence, detail_illusionary; /* * The brush list needs to be ordered (lowest to highest priority): * - detail_illusionary (which is saved as empty) * - liquid * - detail_fence * - detail (which is solid) * - sky * - solid */ classname = ValueForKey(src, "classname"); /* Origin brush support */ bool usesOriginBrush = false; VectorCopy(vec3_origin, rotate_offset); for (int i = 0; i < src->nummapbrushes; i++) { const mapbrush_t *mapbrush = &src->mapbrush(i); const int contents = Brush_GetContents(mapbrush); if (contents == CONTENTS_ORIGIN) { if (dst == pWorldEnt()) { Message(msgWarning, warnOriginBrushInWorld); continue; } brush_t *brush = LoadBrush(mapbrush, vec3_origin, 0); if (brush) { vec3_t origin; VectorAdd(brush->mins, brush->maxs, origin); VectorScale(origin, 0.5, origin); char value[1024]; q_snprintf(value, sizeof(value), "%.2f %.2f %.2f", origin[0], origin[1], origin[2]); SetKeyValue(dst, "origin", value); VectorCopy(origin, rotate_offset); usesOriginBrush = true; FreeMem(brush, BRUSH, 1); } } } /* Hipnotic rotation */ if (!usesOriginBrush) { if (!strncmp(classname, "rotate_", 7)) { FixRotateOrigin(dst); GetVectorForKey(dst, "origin", rotate_offset); } } /* If the source entity is func_detail, set the content flag */ detail = false; if (!Q_strcasecmp(classname, "func_detail") && !options.fNodetail) { detail = true; } if (!Q_strcasecmp(classname, "func_detail_wall") && !options.fNodetail) { detail = true; cflags |= CFLAGS_DETAIL_WALL; } detail_fence = false; if (!Q_strcasecmp(classname, "func_detail_fence") && !options.fNodetail) { detail_fence = true; } detail_illusionary = false; if (!Q_strcasecmp(classname, "func_detail_illusionary") && !options.fNodetail) { detail_illusionary = true; } /* entities with custom lmscales are important for the qbsp to know about */ i = 16 * atof(ValueForKey(src, "_lmscale")); if (!i) i = 16; //if 0, pick a suitable default lmshift = 0; while (i > 1) { lmshift++; //only allow power-of-two scales i /= 2; } for (i = 0; i < src->nummapbrushes; i++, mapbrush++) { mapbrush = &src->mapbrush(i); contents = Brush_GetContents(mapbrush); /* "origin" brushes always discarded */ if (contents == CONTENTS_ORIGIN) continue; /* -omitdetail option */ if (options.fOmitDetail && detail && !(cflags & CFLAGS_DETAIL_WALL)) continue; if (options.fOmitDetailWall && detail && (cflags & CFLAGS_DETAIL_WALL)) continue; if (options.fOmitDetailIllusionary && detail_illusionary) continue; if (options.fOmitDetailFence && detail_fence) continue; /* turn solid brushes into detail, if we're in hull0 */ if (hullnum == 0 && contents == CONTENTS_SOLID) { if (detail) { contents = CONTENTS_DETAIL; } else if (detail_illusionary) { contents = CONTENTS_DETAIL_ILLUSIONARY; } else if (detail_fence) { contents = CONTENTS_DETAIL_FENCE; } } /* func_detail_illusionary don't exist in the collision hull */ if (hullnum && detail_illusionary) { continue; } /* * "clip" brushes don't show up in the draw hull, but we still want to * include them in the model bounds so collision detection works * correctly. */ if (contents == CONTENTS_CLIP) { if (hullnum <= 0) { brush_t *brush = LoadBrush(mapbrush, rotate_offset, hullnum); if (brush) { AddToBounds(dst, brush->mins); AddToBounds(dst, brush->maxs); FreeBrushFaces(brush->faces); FreeMem(brush, BRUSH, 1); } continue; } contents = CONTENTS_SOLID; } /* "hint" brushes don't affect the collision hulls */ if (contents == CONTENTS_HINT) { if (hullnum) continue; contents = CONTENTS_EMPTY; } /* entities never use water merging */ if (dst != pWorldEnt()) contents = CONTENTS_SOLID; /* nonsolid brushes don't show up in clipping hulls */ if (hullnum && contents != CONTENTS_SOLID && contents != CONTENTS_SKY) continue; /* sky brushes are solid in the collision hulls */ if (hullnum && contents == CONTENTS_SKY) contents = CONTENTS_SOLID; brush_t *brush = LoadBrush(mapbrush, rotate_offset, hullnum); if (!brush) continue; dst->numbrushes++; brush->contents = contents; brush->lmshift = lmshift; brush->cflags = cflags; if (brush->contents == CONTENTS_SOLID) { brush->next = dst->solid; dst->solid = brush; } else if (brush->contents == CONTENTS_SKY) { brush->next = dst->sky; dst->sky = brush; } else if (brush->contents == CONTENTS_DETAIL) { brush->next = dst->detail; dst->detail = brush; } else if (brush->contents == CONTENTS_DETAIL_ILLUSIONARY) { brush->next = dst->detail_illusionary; dst->detail_illusionary = brush; } else if (brush->contents == CONTENTS_DETAIL_FENCE) { brush->next = dst->detail_fence; dst->detail_fence = brush; } else { brush->next = dst->liquid; dst->liquid = brush; } AddToBounds(dst, brush->mins); AddToBounds(dst, brush->maxs); Message(msgPercent, i + 1, src->nummapbrushes); } } //============================================================ /* ================== BoundBrush Sets the mins/maxs based on the windings returns false if the brush doesn't enclose a valid volume from q3map ================== */ bool BoundBrush (brush_t *brush) { ClearBounds (brush->mins, brush->maxs); for (face_t *face = brush->faces; face; face = face->next) { const winding_t *w = &face->w; for (int j=0 ; jnumpoints ; j++) AddPointToBounds (w->points[j], brush->mins, brush->maxs); } for (int i=0 ; i<3 ; i++) { if (brush->mins[i] < MIN_WORLD_COORD || brush->maxs[i] > MAX_WORLD_COORD || brush->mins[i] >= brush->maxs[i] ) { return false; } } return true; } /* ================== BrushVolume from q3map modified to follow https://en.wikipedia.org/wiki/Polyhedron#Volume ================== */ vec_t BrushVolume (const brush_t *brush) { if (!brush) return 0; vec_t volume = 0; for (const face_t *face = brush->faces; face; face = face->next) { if (!face->w.numpoints) continue; const vec_t area = WindingArea(&face->w); const plane_t faceplane = Face_Plane(face); volume += DotProduct(faceplane.normal, face->w.points[0]) * area; } volume /= 3.0; return volume; } /* ================== BrushMostlyOnSide from q3map ================== */ int BrushMostlyOnSide (const brush_t *brush, const vec3_t planenormal, vec_t planedist) { vec_t max; int side; max = 0; side = SIDE_FRONT; for (const face_t *face = brush->faces; face; face = face->next) { const winding_t *w = &face->w; if (!w->numpoints) continue; for (int j=0 ; jnumpoints ; j++) { const vec_t d = DotProduct (w->points[j], planenormal) - planedist; if (d > max) { max = d; side = SIDE_FRONT; } if (-d > max) { max = -d; side = SIDE_BACK; } } } return side; } /* ================== CopyBrush from q3map Duplicates the brush, the sides, and the windings ================== */ brush_t *CopyBrush (const brush_t *brush) { brush_t *newbrush = (brush_t *)AllocMem(BRUSH, 1, true); memcpy(newbrush, brush, sizeof(brush_t)); newbrush->next = nullptr; newbrush->faces = nullptr; for (const face_t *face = brush->faces; face; face = face->next) { face_t *newface = (face_t *)AllocMem(FACE, 1, true); memcpy(newface, face, sizeof(face_t)); // clear stuff that shouldn't be copied. newface->original = nullptr; newface->outputnumber = -1; newface->edges = nullptr; // link into newbrush newface->next = newbrush->faces; newbrush->faces = newface; } return newbrush; }