#ifdef SYSTEM_WIN32 #pragma warning(disable:4267 4996) // 'size_t' to 'unsigned int', possible loss of data #endif #include "bsp5.h" // PointInLeaf // PlaceOccupant // MarkLeakTrail // RecursiveFillOutside // ClearOutFaces_r // isClassnameAllowableOutside // FreeAllowableOutsideList // LoadAllowableOutsideList // FillOutside static int outleafs; static int valid; static int c_falsenodes; static int c_free_faces; static int c_keep_faces; // ===================================================================================== // PointInLeaf // ===================================================================================== static node_t* PointInLeaf(node_t* node, const vec3_t point) { vec_t d; if (node->contents) { //Log("PointInLeaf::node->contents == %i\n", node->contents); return node; } d = DotProduct(g_dplanes[node->planenum].normal, point) - g_dplanes[node->planenum].dist; if (d > 0) return PointInLeaf(node->children[0], point); return PointInLeaf(node->children[1], point); } // ===================================================================================== // PlaceOccupant // ===================================================================================== static bool PlaceOccupant(const int num, const vec3_t point, node_t* headnode) { node_t* n; n = PointInLeaf(headnode, point); if (n->contents == CONTENTS_SOLID) { return false; } //Log("PlaceOccupant::n->contents == %i\n", n->contents); n->occupied = num; return true; } // ===================================================================================== // MarkLeakTrail // ===================================================================================== static portal_t* prevleaknode; static FILE* pointfile; static FILE* linefile; static void MarkLeakTrail(portal_t* n2) { int i; vec3_t p1, p2, dir; float len; portal_t* n1; n1 = prevleaknode; prevleaknode = n2; if (!n1) { return; } n2->winding->getCenter(p1); n1->winding->getCenter(p2); // Linefile fprintf(linefile, "%f %f %f - %f %f %f\n", p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); // Pointfile fprintf(pointfile, "%f %f %f\n", p1[0], p1[1], p1[2]); VectorSubtract(p2, p1, dir); len = VectorLength(dir); VectorNormalize(dir); while (len > 2) { fprintf(pointfile, "%f %f %f\n", p1[0], p1[1], p1[2]); for (i = 0; i < 3; i++) p1[i] += dir[i] * 2; len -= 2; } } // ===================================================================================== // RecursiveFillOutside // Returns true if an occupied leaf is reached // If fill is false, just check, don't fill // ===================================================================================== static int hit_occupied; static int backdraw; static bool RecursiveFillOutside(node_t* l, const bool fill) { portal_t* p; int s; if ((l->contents == CONTENTS_SOLID) || (l->contents == CONTENTS_SKY) #ifdef ZHLT_DETAIL || (l->contents == CONTENTS_DETAIL) #endif ) { /*if (l->contents != CONTENTS_SOLID) Log("RecursiveFillOutside::l->contents == %i \n", l->contents);*/ return false; } if (l->valid == valid) { return false; } if (l->occupied) { hit_occupied = l->occupied; backdraw = 1000; return true; } l->valid = valid; // fill it and it's neighbors if (fill) { l->contents = CONTENTS_SOLID; l->planenum = -1; } outleafs++; for (p = l->portals; p;) { s = (p->nodes[0] == l); if (RecursiveFillOutside(p->nodes[s], fill)) { // leaked, so stop filling if (backdraw-- > 0) { MarkLeakTrail(p); } return true; } p = p->next[!s]; } return false; } // ===================================================================================== // ClearOutFaces_r // Removes unused nodes // ===================================================================================== static node_t* ClearOutFaces_r(node_t* node) { face_t* f; face_t* fnext; face_t** fp; portal_t* p; // mark the node and all it's faces, so they // can be removed if no children use them node->valid = 0; // will be set if any children touch it for (f = node->faces; f; f = f->next) { f->outputnumber = -1; } // go down the children if (node->planenum != -1) { // // decision node // node->children[0] = ClearOutFaces_r(node->children[0]); node->children[1] = ClearOutFaces_r(node->children[1]); // free any faces not in open child leafs f = node->faces; node->faces = NULL; for (; f; f = fnext) { fnext = f->next; if (f->outputnumber == -1) { // never referenced, so free it c_free_faces++; FreeFace(f); } else { c_keep_faces++; f->next = node->faces; node->faces = f; } } if (!node->valid) { // this node does not touch any interior leafs // if both children are solid, just make this node solid if (node->children[0]->contents == CONTENTS_SOLID && node->children[1]->contents == CONTENTS_SOLID) { node->contents = CONTENTS_SOLID; node->planenum = -1; return node; } // if one child is solid, shortcut down the other side if (node->children[0]->contents == CONTENTS_SOLID) { return node->children[1]; } if (node->children[1]->contents == CONTENTS_SOLID) { return node->children[0]; } c_falsenodes++; } return node; } // // leaf node // if (node->contents != CONTENTS_SOLID) { // this node is still inside // mark all the nodes used as portals for (p = node->portals; p;) { if (p->onnode) { p->onnode->valid = 1; } if (p->nodes[0] == node) // only write out from first leaf { p = p->next[0]; } else { p = p->next[1]; } } // mark all of the faces to be drawn for (fp = node->markfaces; *fp; fp++) { (*fp)->outputnumber = 0; } return node; } // this was a filled in node, so free the markfaces if (node->planenum != -1) { free(node->markfaces); } return node; } // ===================================================================================== // isClassnameAllowableOutside // ===================================================================================== #define MAX_ALLOWABLE_OUTSIDE_GROWTH_SIZE 64 unsigned g_nAllowableOutside = 0; unsigned g_maxAllowableOutside = 0; char** g_strAllowableOutsideList; bool isClassnameAllowableOutside(const char* const classname) { if (g_strAllowableOutsideList) { unsigned x; char** list = g_strAllowableOutsideList; for (x = 0; x < g_nAllowableOutside; x++, list++) { if (list) { if (!strcasecmp(classname, *list)) { return true; } } } } return false; } // ===================================================================================== // FreeAllowableOutsideList // ===================================================================================== void FreeAllowableOutsideList() { if (g_strAllowableOutsideList) { free(g_strAllowableOutsideList); g_strAllowableOutsideList = NULL; } } // ===================================================================================== // LoadAllowableOutsideList // ===================================================================================== void LoadAllowableOutsideList(const char* const filename) { char* fname; int i, x, y; char* pData; char* pszData; if (!filename) { return; } else { unsigned len = strlen(filename) + 5; fname = (char*)Alloc(len); safe_snprintf(fname, len, "%s", filename); } if (q_exists(fname)) { if ((i = LoadFile(fname, &pData))) { Log("Reading allowable void entities from file '%s'\n", fname); g_nAllowableOutside = 0; for (pszData = pData, y = 0, x = 0; x < i; x++) { if ((pData[x] == '\n') || (pData[x] == '\r')) { pData[x] = 0; if (strlen(pszData)) { if (g_nAllowableOutside == g_maxAllowableOutside) { g_maxAllowableOutside += MAX_ALLOWABLE_OUTSIDE_GROWTH_SIZE; g_strAllowableOutsideList = (char**)realloc(g_strAllowableOutsideList, sizeof(char*) * g_maxAllowableOutside); } g_strAllowableOutsideList[y++] = pszData; g_nAllowableOutside++; Verbose("Adding entity '%s' to the allowable void list\n", pszData); } pszData = pData + x + 1; } } } } } // ===================================================================================== // FillOutside // ===================================================================================== node_t* FillOutside(node_t* node, const bool leakfile, const unsigned hullnum) { int s; int i; bool inside; bool ret; vec3_t origin; const char* cl; Verbose("----- FillOutside ----\n"); if (g_nofill) { Log("skipped\n"); return node; } // // place markers for all entities so // we know if we leak inside // inside = false; for (i = 1; i < g_numentities; i++) { GetVectorForKey(&g_entities[i], "origin", origin); cl = ValueForKey(&g_entities[i], "classname"); if (!isClassnameAllowableOutside(cl)) { if (!VectorCompare(origin, vec3_origin)) { origin[2] += 1; // so objects on floor are ok // nudge playerstart around if needed so clipping hulls allways // have a vlaid point if (!strcmp(cl, "info_player_start")) { int x, y; for (x = -16; x <= 16; x += 16) { for (y = -16; y <= 16; y += 16) { origin[0] += x; origin[1] += y; if (PlaceOccupant(i, origin, node)) { inside = true; goto gotit; } origin[0] -= x; origin[1] -= y; } } gotit:; } else { if (PlaceOccupant(i, origin, node)) inside = true; } } } } if (!inside) { Warning("No entities exist in hull %i, no filling performed for this hull", hullnum); return node; } if(!g_outside_node.portals) { Warning("No outside node portal found in hull %i, no filling performed for this hull",hullnum); return node; } s = !(g_outside_node.portals->nodes[1] == &g_outside_node); // first check to see if an occupied leaf is hit outleafs = 0; valid++; prevleaknode = NULL; if (leakfile) { pointfile = fopen(g_pointfilename, "w"); if (!pointfile) { Error("Couldn't open pointfile %s\n", g_pointfilename); } linefile = fopen(g_linefilename, "w"); if (!linefile) { Error("Couldn't open linefile %s\n", g_linefilename); } } ret = RecursiveFillOutside(g_outside_node.portals->nodes[s], false); if (leakfile) { fclose(pointfile); fclose(linefile); } if (ret) { GetVectorForKey(&g_entities[hit_occupied], "origin", origin); { Warning("=== LEAK in hull %i ===\nEntity %s @ (%4.0f,%4.0f,%4.0f)", hullnum, ValueForKey(&g_entities[hit_occupied], "classname"), origin[0], origin[1], origin[2]); PrintOnce( "\n A LEAK is a hole in the map, where the inside of it is exposed to the\n" "(unwanted) outside region. The entity listed in the error is just a helpful\n" "indication of where the beginning of the leak pointfile starts, so the\n" "beginning of the line can be quickly found and traced to until reaching the\n" "outside. Unless this entity is accidentally on the outside of the map, it\n" "probably should not be deleted. Some complex rotating objects entities need\n" "their origins outside the map. To deal with these, just enclose the origin\n" "brush with a solid world brush\n"); } if (!g_bLeaked) { // First leak spits this out Log("Leak pointfile generated\n\n"); } if (g_bLeakOnly) { Error("Stopped by leak."); } g_bLeaked = true; return node; } // now go back and fill things in valid++; RecursiveFillOutside(g_outside_node.portals->nodes[s], true); // remove faces and nodes from filled in leafs c_falsenodes = 0; c_free_faces = 0; c_keep_faces = 0; node = ClearOutFaces_r(node); Verbose("%5i outleafs\n", outleafs); Verbose("%5i freed faces\n", c_free_faces); Verbose("%5i keep faces\n", c_keep_faces); Verbose("%5i falsenodes\n", c_falsenodes); // save portal file for vis tracing if ((hullnum == 0) && leakfile) { WritePortalfile(node); } return node; }