Skip to content

Commit

Permalink
huge speedup generating nav mesh
Browse files Browse the repository at this point in the history
using an octree to reduce intersection tests for cutting, and only testing the center point of each poly for interior checks. Cut polys will either be wholly inside or outside of the level, so no need to check everywhere on the face. Also not using new split polygons to cut. A mesh for yabma generates in 3s, down from ~5 minutes.
  • Loading branch information
wootguy committed Oct 20, 2023
1 parent 3dd3556 commit 0e6d5da
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 148 deletions.
6 changes: 6 additions & 0 deletions src/bsp/Bsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,12 @@ bool Bsp::is_node_hull_convex(int iNode) {
return true;
}

bool Bsp::isInteriorFace(const Polygon3D& poly, int hull) {
int headnode = models[0].iHeadnodes[hull];
vec3 testPos = poly.center + poly.plane_z * 0.5f;
return pointContents(headnode, testPos, hull) == CONTENTS_EMPTY;
}

int Bsp::addTextureInfo(BSPTEXTUREINFO& copy) {
BSPTEXTUREINFO* newInfos = new BSPTEXTUREINFO[texinfoCount + 1];
memcpy(newInfos, texinfos, texinfoCount * sizeof(BSPTEXTUREINFO));
Expand Down
6 changes: 4 additions & 2 deletions src/bsp/Bsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "remap.h"
#include <set>
#include "bsptypes.h"
#include "Polygon3D.h"

// largest coordinate allowed in map
//#define MAX_COORD 131072
Expand Down Expand Up @@ -110,6 +111,9 @@ class Bsp
bool is_convex(int modelIdx);
bool is_node_hull_convex(int iNode);

// true if the center of this face is touching an empty leaf
bool isInteriorFace(const Polygon3D& poly, int hull);

// get cuts required to create bounding volumes for each solid leaf in the model
vector<NodeVolumeCuts> get_model_leaf_volume_cuts(int modelIdx, int hullIdx, int16_t contents);
void get_clipnode_leaf_cuts(int iNode, vector<BSPPLANE>& clipOrder, vector<NodeVolumeCuts>& output, int16_t contents);
Expand Down Expand Up @@ -214,8 +218,6 @@ class Bsp

void update_lump_pointers();

void write_obj_file();

private:
int remove_unused_lightmaps(bool* usedFaces);
int remove_unused_visdata(bool* usedLeaves, BSPLEAF* oldLeaves, int oldLeafCount); // called after removing unused leaves
Expand Down
183 changes: 79 additions & 104 deletions src/editor/BspRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Clipper.h"
#include <iomanip>
#include "Polygon3D.h"
#include "PolyOctree.h"

#include "icons/missing.h"

Expand Down Expand Up @@ -808,6 +809,7 @@ void BspRenderer::loadClipnodes() {
}

void BspRenderer::generateNavMeshBuffer() {
float navMeshGenStart = glfwGetTime();
BSPMODEL& model = map->models[0];
RenderClipnodes* renderClip = &renderClipnodes[0];
int hull = 3;
Expand All @@ -828,7 +830,23 @@ void BspRenderer::generateNavMeshBuffer() {
solidMeshes.push_back(clipper.clip(solidNodes[k].cuts));
}

vector<Polygon3D> solidFaces;
vector<Polygon3D*> solidFaces;

vec3 mapMins;
vec3 mapMaxs;
map->get_bounding_box(mapMins, mapMaxs);

vec3 treeMin = vec3(-MAX_COORD, -MAX_COORD, -MAX_COORD);
vec3 treeMax = vec3(MAX_COORD, MAX_COORD, MAX_COORD);

while (isBoxContained(mapMins, mapMaxs, treeMin * 0.5f, treeMax * 0.5f)) {
treeMax *= 0.5f;
treeMin *= 0.5f;
}

int treedepth = 6;
logf("Create octree depth %d, size %f -> %f\n", treedepth, treeMax.x, treeMax.x / pow(2, treedepth));
PolygonOctree octree(treeMin, treeMax, treedepth);

// GET FACES FROM MESHES
for (int m = 0; m < solidMeshes.size(); m++) {
Expand Down Expand Up @@ -871,133 +889,88 @@ void BspRenderer::generateNavMeshBuffer() {
normal = normal.invert();
}

solidFaces.push_back(faceVerts);
solidFaces.push_back(new Polygon3D(faceVerts, solidFaces.size()));
octree.insertPolygon(solidFaces[solidFaces.size()-1]);
}
}

int debugPoly = 0;
//debugPoly = 1560;
bool doCull = true;

if (doCull) {
int presplit = solidFaces.size();
int numSplits = 0;
for (int i = 0; i < solidFaces.size(); i++) {
Polygon3D& poly = solidFaces[i];
if (debugPoly && i != debugPoly) {
continue;
}
if (!poly.isValid) {
solidFaces.erase(solidFaces.begin() + i);
i--;
continue;
}

for (int k = 0; k < solidFaces.size(); k++) {
if (i == k) {
continue;
}

//if (k != 1547) {
// continue;
//}

vector<vector<vec3>> splitPolys = poly.split(solidFaces[k]);

if (splitPolys.size()) {
solidFaces.push_back(splitPolys[0]);
solidFaces.push_back(splitPolys[1]);
solidFaces.erase(solidFaces.begin() + i);
//logf("Split poly %d by %d\n", i, k);
i--;
break;
}
}
}
logf("Split %d faces into %d (%d splits)\n", presplit, solidFaces.size(), numSplits);
}
debugPoly = 0;
int avgInRegion = 0;
int regionChecks = 0;

vector<Polygon3D*> cuttingPolys = solidFaces;
vector<Polygon3D> interiorFaces;

vec3 mapMins;
vec3 mapMaxs;
map->get_bounding_box(mapMins, mapMaxs);
int presplit = solidFaces.size();
int numSplits = 0;
float startTime = glfwGetTime();
bool doCull = true;
bool walkableSurfacesOnly = false;

// CULL FACES THAT FACE INTO THE VOID
vector<Polygon3D> interiorFaces;
for (int m = 0; m < solidFaces.size(); m++) {
Polygon3D& poly = solidFaces[m];
vector<bool> regionPolys;
regionPolys.resize(cuttingPolys.size());

if (!boxesIntersect(poly.worldMins, poly.worldMaxs, mapMins, mapMaxs)) {
for (int i = 0; i < solidFaces.size(); i++) {
Polygon3D* poly = solidFaces[i];
if (debugPoly && i != debugPoly) {
continue;
}

vec3 v0 = poly.verts[0];
vec3 v1;
bool found = false;
for (int z = 1; z < poly.verts.size(); z++) {
if (poly.verts[z] != v0) {
v1 = poly.verts[z];
found = true;
break;
}
}
if (!found) {
logf("Failed to find non-duplicate vert for clipnode face\n");
if (!poly->isValid || poly->idx == -1) {
continue;
}

if (!poly.isValid) {
logf("NOT VALID\n");
if (walkableSurfacesOnly && poly->plane_z.z < 0.7) {
continue;
}

float error = getDistAlongAxis(poly.plane_z, poly.verts[1]) - poly.fdist;
if (error > 0.1f) {
logf("oh noes %f\n", error);
}
octree.getPolysInRegion(poly, regionPolys);
if (poly->idx < cuttingPolys.size())
regionPolys[poly->idx] = false;
regionChecks++;

vec2 localMins = vec2(FLT_MAX, FLT_MAX);
vec2 localMaxs = vec2(-FLT_MAX, -FLT_MAX);
for (int e = 0; e < poly.verts.size(); e++) {
vec3 p = poly.verts[e];
if (p.x < mapMins.x || p.y < mapMins.y || p.z < mapMins.z
|| p.x > mapMaxs.x || p.y > mapMaxs.y || p.z > mapMaxs.z) {
bool anySplits = false;
int sz = cuttingPolys.size();
for (int k = 0; k < sz; k++) {
if (!regionPolys[k]) {
continue;
}

vec2 localPoint = poly.project(p);
expandBoundingBox(localPoint, localMins, localMaxs);
}

int inPoly = 0;
int totalPoint = 0;
float step = 4.0f; // decrease if small polys are missing
bool touchingEmptyLeaf = false;
for (float x = localMins.x + 0.5f; x < localMaxs.x && !touchingEmptyLeaf; x += step) {
for (float y = localMins.y + 0.5f; y < localMaxs.y; y += step) {
totalPoint++;
vec2 testPos = vec2(x, y);
if (poly.isInside(testPos)) {
vec3 worldPos = poly.unproject(testPos);
if (map->pointContents(headnode, worldPos + poly.plane_z, hull) == CONTENTS_EMPTY) {
touchingEmptyLeaf = true;
break;
}
inPoly++;
Polygon3D* cutPoly = cuttingPolys[k];
avgInRegion++;
//if (k != 1547) {
// continue;
//}

vector<vector<vec3>> splitPolys = poly->split(*cutPoly);

if (splitPolys.size()) {
anySplits = true;
numSplits++;

for (int j = 0; j < 2; j++) {
int idx = solidFaces.size();
Polygon3D* newpoly = new Polygon3D(splitPolys[j], idx);
solidFaces.push_back(newpoly);
}

//logf("Split poly %d by %d\n", i, k);
break;
}
}

float sz = (localMaxs.y - localMins.y) * (localMaxs.x - localMins.x);
//logf("%d / %d points inside size %.1f\n", inPoly, totalPoint, sz);
if (touchingEmptyLeaf || !doCull) {
interiorFaces.push_back(poly);

if (!isBoxContained(poly.worldMins, poly.worldMaxs, mapMins, mapMaxs)) {
logf("Nav poly %d out of world\n", interiorFaces.size()-1);
}
if (!anySplits && (map->isInteriorFace(*poly, hull) || !doCull)) {
interiorFaces.push_back(*poly);
}
}
logf("Got %d solidfaces, %d interior faces, %d skipped\n", solidFaces.size(), interiorFaces.size());
logf("Finished cutting in %.2fs\n", (float)(glfwGetTime() - startTime));
logf("Split %d faces into %d (%d splits)\n", presplit, solidFaces.size(), numSplits);
logf("Average of %d in poly regions\n", regionChecks ? (avgInRegion / regionChecks) : 0);
logf("Got %d interior faces\n", interiorFaces.size());

for (int i = 0; i < solidFaces.size(); i++) {
if (solidFaces[i])
delete solidFaces[i];
}

//g_app->debugPoly = interiorFaces[4180];

Expand Down Expand Up @@ -1113,6 +1086,8 @@ void BspRenderer::generateNavMeshBuffer() {

renderClip->faceMaths[hull] = faceMaths;

logf("Generated nav mesh in %.2fs\n", glfwGetTime() - navMeshGenStart);

ofstream file(map->name + "_hull" + to_string(hull) + ".obj", ios::out | ios::trunc);
for (int i = 0; i < allVerts.size(); i++) {
vec3 v = vec3(allVerts[i].x, allVerts[i].y, allVerts[i].z);
Expand Down
40 changes: 0 additions & 40 deletions src/editor/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1706,46 +1706,6 @@ float Renderer::drawPolygon2D(Polygon3D poly, vec2 pos, vec2 maxSz, COLOR4 color
{
vec2 cam = debugPoly.project(cameraOrigin);
drawBox2D(offset + cam*scale, 16, poly.isInside(cam) ? COLOR4(0, 255, 0, 255) : COLOR4(255, 32, 0, 255));

Bsp* map = mapRenderers[0]->map;
vec3 mapMins;
vec3 mapMaxs;
map->get_bounding_box(mapMins, mapMaxs);

vec2 localMins = vec2(FLT_MAX, FLT_MAX);
vec2 localMaxs = vec2(-FLT_MAX, -FLT_MAX);
for (int e = 0; e < poly.verts.size(); e++) {
vec3 p = poly.verts[e];
if (p.x < mapMins.x || p.y < mapMins.y || p.z < mapMins.z
|| p.x > mapMaxs.x || p.y > mapMaxs.y || p.z > mapMaxs.z) {
continue;
}

vec2 localPoint = poly.project(p);
expandBoundingBox(localPoint, localMins, localMaxs);
}

int hull = 3;
int headnode = map->models[0].iHeadnodes[hull];
int inPoly = 0;
int totalPoint = 0;
float step = 4.0f; // decrease if small polys are missing
bool touchingEmptyLeaf = false;
for (float x = localMins.x + 0.5f; x < localMaxs.x && !touchingEmptyLeaf; x += step) {
for (float y = localMins.y + 0.5f; y < localMaxs.y; y += step) {
totalPoint++;
vec2 testPos = vec2(x, y);
if (poly.isInside(testPos)) {
vec3 worldPos = poly.unproject(testPos);
if (map->pointContents(headnode, worldPos + poly.plane_z, hull) == CONTENTS_EMPTY) {
drawBox2D(offset + testPos * scale, 2, COLOR4(0, 255, 255, 255));
}
else {
drawBox2D(offset + testPos * scale, 2, COLOR4(255, 128, 0, 255));
}
}
}
}
}


Expand Down
Loading

0 comments on commit 0e6d5da

Please sign in to comment.