From 23231e3e64177e7104f9b08998a5667235babbc2 Mon Sep 17 00:00:00 2001 From: wootguy Date: Wed, 3 Jul 2024 03:56:59 -0700 Subject: [PATCH] leaf nav mesh pathfinding and octree speedup roughly 100x faster generation using the octree. Pathfinding needs to work to avoid sending the player into areas that are impossible to reach normally. Either that or the walkable nav mesh should be used instead, which has the opposite problem of not reaching enough areas. --- CMakeLists.txt | 13 +- src/bsp/Entity.cpp | 16 +++ src/bsp/Entity.h | 4 + src/editor/BspRenderer.cpp | 4 +- src/editor/Renderer.cpp | 92 ++++++++---- src/editor/Renderer.h | 1 + src/gl/primitives.h | 1 + src/nav/LeafNavMesh.cpp | 239 +++++++++++++++++++++++++++++++ src/nav/LeafNavMesh.h | 16 +++ src/nav/LeafNavMeshGenerator.cpp | 83 ++++++++--- src/nav/LeafNavMeshGenerator.h | 8 +- src/nav/LeafOctree.cpp | 101 +++++++++++++ src/nav/LeafOctree.h | 42 ++++++ src/{util => nav}/PolyOctree.cpp | 0 src/{util => nav}/PolyOctree.h | 0 src/util/Polygon3D.cpp | 6 +- src/util/Polygon3D.h | 5 +- 17 files changed, 573 insertions(+), 58 deletions(-) create mode 100644 src/nav/LeafOctree.cpp create mode 100644 src/nav/LeafOctree.h rename src/{util => nav}/PolyOctree.cpp (100%) rename src/{util => nav}/PolyOctree.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6ec645f..4e006afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,6 @@ set(SOURCE_FILES src/util/mat4x4.h src/util/mat4x4.cpp src/util/Polygon3D.h src/util/Polygon3D.cpp src/util/Line2D.h src/util/Line2D.cpp - src/util/PolyOctree.h src/util/PolyOctree.cpp src/globals.h src/globals.cpp # Navigation meshes @@ -40,6 +39,8 @@ set(SOURCE_FILES src/nav/NavMeshGenerator.h src/nav/NavMeshGenerator.cpp src/nav/LeafNavMeshGenerator.h src/nav/LeafNavMeshGenerator.cpp src/nav/LeafNavMesh.h src/nav/LeafNavMesh.cpp + src/nav/PolyOctree.h src/nav/PolyOctree.cpp + src/nav/LeafOctree.h src/nav/LeafOctree.cpp # OpenGL rendering src/gl/shaders.h src/gl/shaders.cpp @@ -182,25 +183,27 @@ if(MSVC) src/util/vectors.h src/util/Polygon3D.h src/util/Line2D.h - src/util/PolyOctree.h src/util/mat4x4.h) source_group("Source Files\\util" FILES src/util/util.cpp src/util/vectors.cpp src/util/Polygon3D.cpp src/util/Line2D.cpp - src/util/PolyOctree.cpp src/util/mat4x4.cpp) source_group("Header Files\\nav" FILES src/nav/NavMesh.h src/nav/NavMeshGenerator.h src/nav/LeafNavMeshGenerator.h - src/nav/LeafNavMesh.h) + src/nav/LeafNavMesh.h + src/nav/PolyOctree.h + src/nav/LeafOctree.h) source_group("Source Files\\nav" FILES src/nav/NavMesh.cpp src/nav/NavMeshGenerator.cpp src/nav/LeafNavMeshGenerator.cpp - src/nav/LeafNavMesh.cpp) + src/nav/LeafNavMesh.cpp + src/nav/PolyOctree.cpp + src/nav/LeafOctree.cpp) source_group("Header Files\\util\\lib" FILES src/util/lodepng.h) diff --git a/src/bsp/Entity.cpp b/src/bsp/Entity.cpp index 9247b866..8b398cab 100644 --- a/src/bsp/Entity.cpp +++ b/src/bsp/Entity.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "Bsp.h" using namespace std; @@ -149,6 +150,21 @@ vec3 Entity::getOrigin() { return hasKey("origin") ? parseVector(keyvalues["origin"]) : vec3(0, 0, 0); } +vec3 Entity::getHullOrigin(Bsp* map) { + vec3 ori = getOrigin(); + int modelIdx = getBspModelIdx(); + + if (modelIdx != -1) { + BSPMODEL& model = map->models[modelIdx]; + + vec3 mins, maxs; + map->get_model_vertex_bounds(modelIdx, mins, maxs); + ori += (maxs + mins) * 0.5f; + } + + return ori; +} + // TODO: maybe store this in a text file or something #define TOTAL_TARGETNAME_KEYS 134 const char* potential_tergetname_keys[TOTAL_TARGETNAME_KEYS] = { diff --git a/src/bsp/Entity.h b/src/bsp/Entity.h index 8a39b9a4..0e344204 100644 --- a/src/bsp/Entity.h +++ b/src/bsp/Entity.h @@ -4,6 +4,8 @@ #include #include +class Bsp; + class Entity { public: @@ -34,6 +36,8 @@ class Entity vec3 getOrigin(); + vec3 getHullOrigin(Bsp* map); + bool hasKey(const std::string& key); vector getTargets(); diff --git a/src/editor/BspRenderer.cpp b/src/editor/BspRenderer.cpp index 7887567b..2f20e384 100644 --- a/src/editor/BspRenderer.cpp +++ b/src/editor/BspRenderer.cpp @@ -1940,8 +1940,7 @@ bool BspRenderer::pickModelPoly(vec3 start, vec3 dir, vec3 offset, int modelIdx, pickInfo.faceIdx = -1; // Nav mesh WIP code - /* - if (modelIdx == 0 && hullIdx == 3) { + if (g_app->debugNavMesh && modelIdx == 0 && hullIdx == 3) { static int lastPick = 0; g_app->debugPoly = debugFaces[i]; @@ -1956,7 +1955,6 @@ bool BspRenderer::pickModelPoly(vec3 start, vec3 dir, vec3 offset, int modelIdx, lastPick = i; logf("Picked hull %d, face %d, verts %d, area %.1f\nNav links %d\n", hullIdx, i, debugFaces[i].verts.size(), debugFaces[i].area, node.numLinks()); } - */ } } } diff --git a/src/editor/Renderer.cpp b/src/editor/Renderer.cpp index 46724a53..c58b8db5 100644 --- a/src/editor/Renderer.cpp +++ b/src/editor/Renderer.cpp @@ -457,7 +457,8 @@ void Renderer::renderLoop() { if (debugLeafNavMesh) { glLineWidth(1); - int leafIdx = mapRenderers[0]->map->get_leaf(cameraOrigin, 3); + Bsp* map = mapRenderers[0]->map; + int leafIdx = map->get_leaf(cameraOrigin, 3); int leafNavIdx = MAX_NAV_LEAVES; if (leafIdx >= 0 && leafIdx < MAX_MAP_CLIPNODE_LEAVES) { @@ -465,37 +466,77 @@ void Renderer::renderLoop() { } if (leafNavIdx < MAX_NAV_LEAVES) { - LeafNavNode& node = debugLeafNavMesh->nodes[leafNavIdx]; - LeafMesh& leaf = debugLeafNavMesh->leaves[leafNavIdx]; - drawBox(leaf.center, 2, COLOR4(0, 255, 0, 255)); + if (pickInfo.valid && pickInfo.ent && pickInfo.entIdx != 0) { + glDisable(GL_DEPTH_TEST); + + int endNode = debugLeafNavMesh->getNodeIdx(map, pickInfo.ent); + vector route = debugLeafNavMesh->AStarRoute(map, leafNavIdx, endNode); + + if (route.size()) { + LeafMesh& firstNode = debugLeafNavMesh->leaves[route[route.size() - 1]]; + + vec3 lastPos = firstNode.center; + drawBox(firstNode.center, 2, COLOR4(0, 255, 255, 255)); + + for (int i = route.size() - 2; i >= 0; i--) { + LeafNavNode& node = debugLeafNavMesh->nodes[route[i]]; + LeafMesh& mesh = debugLeafNavMesh->leaves[route[i]]; + + vec3 nodeCenter = mesh.center; + + for (int k = 0; k < MAX_NAV_LEAF_LINKS; k++) { + LeafNavLink& link = node.links[k]; - std::string linkStr; + if (link.node == route[i + 1]) { + vec3 linkPoint = link.linkArea.center; - for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { - LeafNavLink& link = node.links[i]; - if (link.node == -1) { - break; + drawLine(lastPos, linkPoint, COLOR4(0, 255, 255, 255)); + drawLine(linkPoint, mesh.center, COLOR4(0, 255, 255, 255)); + drawBox(nodeCenter, 2, COLOR4(0, 255, 255, 255)); + lastPos = nodeCenter; + break; + } + } + } + + drawLine(lastPos, pickInfo.ent->getHullOrigin(map), COLOR4(0, 255, 255, 255)); } - LeafMesh& linkLeaf = debugLeafNavMesh->leaves[link.node]; - Polygon3D& linkArea = link.linkArea; + } + else { + LeafNavNode& node = debugLeafNavMesh->nodes[leafNavIdx]; + LeafMesh& leaf = debugLeafNavMesh->leaves[leafNavIdx]; - drawLine(leaf.center, linkArea.center, COLOR4(0, 255, 255, 255)); - drawLine(linkArea.center, linkLeaf.center, COLOR4(0, 255, 255, 255)); + drawBox(leaf.center, 2, COLOR4(0, 255, 0, 255)); - for (int k = 0; k < linkArea.verts.size(); k++) { - drawBox(linkArea.verts[k], 1, COLOR4(255, 255, 0, 255)); + std::string linkStr; + + for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { + LeafNavLink& link = node.links[i]; + if (link.node == -1) { + break; + } + LeafMesh& linkLeaf = debugLeafNavMesh->leaves[link.node]; + Polygon3D& linkArea = link.linkArea; + + drawLine(leaf.center, linkArea.center, COLOR4(0, 255, 255, 255)); + drawLine(linkArea.center, linkLeaf.center, COLOR4(0, 255, 255, 255)); + + for (int k = 0; k < linkArea.verts.size(); k++) { + drawBox(linkArea.verts[k], 1, COLOR4(255, 255, 0, 255)); + } + drawBox(linkArea.center, 1, COLOR4(0, 255, 0, 255)); + drawBox(linkLeaf.center, 2, COLOR4(0, 255, 255, 255)); + linkStr += to_string(link.node) + " (" + to_string(linkArea.verts.size()) + "v), "; } - drawBox(linkArea.center, 1, COLOR4(0, 255, 0, 255)); - drawBox(linkLeaf.center, 2, COLOR4(0, 255, 255, 255)); - linkStr += to_string(link.node) + " (" + to_string(linkArea.verts.size()) + "v), "; + + //logf("Leaf node idx: %d, links: %s\n", leafNavIdx, linkStr.c_str()); } - //logf("Leaf node idx: %d, links: %s\n", leafNavIdx, linkStr.c_str()); } glDisable(GL_DEPTH_TEST); - + /* colorShader->pushMatrix(MAT_PROJECTION); colorShader->pushMatrix(MAT_VIEW); projection.ortho(0, windowWidth, windowHeight, 0, -1.0f, 1.0f); @@ -505,18 +546,9 @@ void Renderer::renderLoop() { Line2D edge(vec2(1000, 400), vec2(1400, 630)); drawLine2D(edge.start, edge.end, COLOR4(255, 0, 0, 255)); - /* - double xpos, ypos; - glfwGetCursorPos(window, &xpos, &ypos); - vec2 mousepos = vec2(xpos, ypos); - drawBox2D(mousepos, 8, COLOR4(255, 0, 0, 255)); - drawBox2D(edge.project(mousepos), 8, COLOR4(255, 0, 0, 255)); - float dist = edge.distance(mousepos); - logf("dist: %f\n", edge.distance(mousepos)); - */ - colorShader->popMatrix(MAT_PROJECTION); colorShader->popMatrix(MAT_VIEW); + */ } if (debugPoly.isValid) { diff --git a/src/editor/Renderer.h b/src/editor/Renderer.h index 072aff1c..2bb1ba36 100644 --- a/src/editor/Renderer.h +++ b/src/editor/Renderer.h @@ -62,6 +62,7 @@ class Renderer { friend class FixSurfaceExtentsCommand; friend class DeduplicateModelsCommand; friend class MoveMapCommand; + friend class LeafNavMesh; public: vector mapRenderers; diff --git a/src/gl/primitives.h b/src/gl/primitives.h index b3279c73..fc525032 100644 --- a/src/gl/primitives.h +++ b/src/gl/primitives.h @@ -39,6 +39,7 @@ struct cVert cVert() {} cVert(float x, float y, float z, COLOR4 c) : c(c), x(x), y(y), z(z) {} cVert(vec3 p, COLOR4 c) : c(c), x(p.x), y(p.y), z(p.z) {} + vec3 pos() { return vec3(x, y, z); } }; struct tTri diff --git a/src/nav/LeafNavMesh.cpp b/src/nav/LeafNavMesh.cpp index 7cc4dd11..4c04f2fa 100644 --- a/src/nav/LeafNavMesh.cpp +++ b/src/nav/LeafNavMesh.cpp @@ -1,9 +1,25 @@ +#include "Renderer.h" #include "LeafNavMesh.h" #include "GLFW/glfw3.h" #include "PolyOctree.h" #include "Clipper.h" #include "util.h" #include +#include "Bsp.h" +#include +#include "Entity.h" +#include "Fgd.h" +#include "globals.h" + +bool LeafMesh::isInside(vec3 p) { + for (int i = 0; i < leafFaces.size(); i++) { + if (leafFaces[i].distance(p) > 0) { + return false; + } + } + + return true; +} bool LeafNavNode::addLink(int node, Polygon3D linkArea) { for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { @@ -79,3 +95,226 @@ bool LeafNavMesh::addLink(int from, int to, Polygon3D linkArea) { return true; } + +int LeafNavMesh::getNodeIdx(Bsp* map, Entity* ent) { + vec3 ori = ent->getOrigin(); + vec3 mins, maxs; + int modelIdx = ent->getBspModelIdx(); + + if (modelIdx != -1) { + BSPMODEL& model = map->models[modelIdx]; + + map->get_model_vertex_bounds(modelIdx, mins, maxs); + ori += (maxs + mins) * 0.5f; + } + else { + FgdClass* fclass = g_app->fgd->getFgdClass(ent->keyvalues["classname"]); + if (fclass->sizeSet) { + mins = fclass->mins; + maxs = fclass->maxs; + } + } + + // first try testing a few points on the entity box for an early exit + + mins += ori; + maxs += ori; + + vec3 testPoints[10] = { + ori, + (mins + maxs) * 0.5f, + vec3(mins.x, mins.y, mins.z), + vec3(mins.x, mins.y, maxs.z), + vec3(mins.x, maxs.y, mins.z), + vec3(mins.x, maxs.y, maxs.z), + vec3(maxs.x, mins.y, mins.z), + vec3(maxs.x, mins.y, maxs.z), + vec3(maxs.x, maxs.y, mins.z), + vec3(maxs.x, maxs.y, maxs.z), + }; + + for (int i = 0; i < 10; i++) { + int targetLeaf = map->get_leaf(testPoints[i], 3); + int targetLeafNavIdx = MAX_NAV_LEAVES; + + if (targetLeaf >= 0 && targetLeaf < MAX_MAP_CLIPNODE_LEAVES) { + int navIdx = leafMap[targetLeaf]; + + if (navIdx < 65535) { + return navIdx; + } + } + } + + if ((maxs - mins).length() < 1) { + return -1; // point sized, so can't intersect any leaf + } + + // no points are inside, so test for plane intersections + + cCube entCube(mins, maxs, COLOR4(0, 0, 0, 0)); + cQuad* faces[6] = { + &entCube.top, + &entCube.bottom, + &entCube.left, + &entCube.right, + &entCube.front, + &entCube.back, + }; + + Polygon3D boxPolys[6]; + for (int i = 0; i < 6; i++) { + cQuad& face = *faces[i]; + boxPolys[i] = vector{ face.v1.pos(), face.v2.pos(), face.v3.pos(), face.v6.pos() }; + } + + for (int i = 0; i < numLeaves; i++) { + LeafMesh& mesh = leaves[i]; + + for (int k = 0; k < mesh.leafFaces.size(); k++) { + Polygon3D& leafFace = mesh.leafFaces[k]; + + for (int k = 0; k < 6; k++) { + if (leafFace.intersects(boxPolys[k])) { + return i; + } + } + } + } + + return -1; +} + +float LeafNavMesh::path_cost(int a, int b) { + + LeafNavNode& nodea = nodes[a]; + LeafNavNode& nodeb = nodes[b]; + LeafMesh& mesha = leaves[a]; + LeafMesh& meshb = leaves[b]; + vec3 delta = mesha.center - meshb.center; + + for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { + LeafNavLink& link = nodea.links[i]; + if (link.node == -1) { + break; + } + } + + return delta.length(); +} + +vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) +{ + set closedSet; + set openSet; + + unordered_map gScore; + unordered_map fScore; + unordered_map cameFrom; + + vector emptyRoute; + + if (startNodeIdx < 0 || endNodeIdx < 0 || startNodeIdx > MAX_NAV_LEAVES || endNodeIdx > MAX_NAV_LEAVES) { + logf("AStarRoute: invalid start/end nodes\n"); + return emptyRoute; + } + + if (startNodeIdx == endNodeIdx) { + emptyRoute.push_back(startNodeIdx); + return emptyRoute; + } + + LeafNavNode& start = nodes[startNodeIdx]; + LeafNavNode& goal = nodes[endNodeIdx]; + + openSet.insert(startNodeIdx); + gScore[startNodeIdx] = 0; + fScore[startNodeIdx] = path_cost(start.id, goal.id); + + const int maxIter = 8192; + int curIter = 0; + while (!openSet.empty()) { + if (++curIter > maxIter) { + logf("AStarRoute exceeded max iterations searching path (%d)", maxIter); + break; + } + + // get node in openset with lowest cost + int current = -1; + float bestScore = 9e99; + for (int nodeId : openSet) + { + float score = fScore[nodeId]; + if (score < bestScore) { + bestScore = score; + current = nodeId; + } + } + + //println("Current is " + current); + + if (current == goal.id) { + //println("MAde it to the goal"); + // goal reached, build the route + vector path; + path.push_back(current); + + int maxPathLen = 1000; + int i = 0; + while (cameFrom.count(current)) { + current = cameFrom[current]; + path.push_back(current); + if (++i > maxPathLen) { + logf("AStarRoute exceeded max path length (%d)", maxPathLen); + break; + } + } + + return path; + } + + openSet.erase(current); + closedSet.insert(current); + + LeafNavNode& currentNode = nodes[current]; + + for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { + LeafNavLink& link = currentNode.links[i]; + if (link.node == -1) { + break; + } + + int neighbor = link.node; + if (neighbor < 0 || neighbor >= MAX_NAV_LEAVES) { + continue; + } + if (closedSet.count(neighbor)) + continue; + //if (currentNode.blockers.size() > i and currentNode.blockers[i] & blockers != 0) + // continue; // blocked by something (monsterclip, normal clip, etc.). Don't route through this path. + + // discover a new node + openSet.insert(neighbor); + + // The distance from start to a neighbor + LeafNavNode& neighborNode = nodes[neighbor]; + + float tentative_gScore = gScore[current]; + tentative_gScore += path_cost(currentNode.id, neighborNode.id); + + float neighbor_gScore = 9e99; + if (gScore.count(neighbor)) + neighbor_gScore = gScore[neighbor]; + + if (tentative_gScore >= neighbor_gScore) + continue; // not a better path + + // This path is the best until now. Record it! + cameFrom[neighbor] = current; + gScore[neighbor] = tentative_gScore; + fScore[neighbor] = tentative_gScore + path_cost(neighborNode.id, goal.id); + } + } + + return emptyRoute; +} \ No newline at end of file diff --git a/src/nav/LeafNavMesh.h b/src/nav/LeafNavMesh.h index e687648d..64f81ef4 100644 --- a/src/nav/LeafNavMesh.h +++ b/src/nav/LeafNavMesh.h @@ -6,14 +6,24 @@ #define MAX_NAV_LEAF_LINKS 128 #define MAX_MAP_CLIPNODE_LEAVES 65536 // doubled to account for each clipnode's child contents having its own ID +class Bsp; +class Entity; + struct LeafMesh { vec3 center; + vec3 mins; + vec3 maxs; + int idx; vector leafFaces; + + // returns true if point is inside leaf volume + bool isInside(vec3 p); }; struct LeafNavLink { int16_t node; // which poly is linked to. -1 = end of links Polygon3D linkArea; // region in which leaves are making contact + bool walkable; }; struct LeafNavNode { @@ -43,5 +53,11 @@ class LeafNavMesh { void clear(); + vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx); + + float path_cost(int a, int b); + + int getNodeIdx(Bsp* map, Entity* ent); + private: }; \ No newline at end of file diff --git a/src/nav/LeafNavMeshGenerator.cpp b/src/nav/LeafNavMeshGenerator.cpp index 1b84302a..e74b6cf2 100644 --- a/src/nav/LeafNavMeshGenerator.cpp +++ b/src/nav/LeafNavMeshGenerator.cpp @@ -6,7 +6,7 @@ #include "LeafNavMesh.h" #include #include "util.h" -#include "PolyOctree.h" +#include "LeafOctree.h" #include LeafNavMesh* LeafNavMeshGenerator::generate(Bsp* map, int hull) { @@ -21,6 +21,7 @@ LeafNavMesh* LeafNavMeshGenerator::generate(Bsp* map, int hull) { LeafNavMesh* navmesh = new LeafNavMesh(emptyLeaves); linkNavLeaves(map, navmesh); + markWalkableLinks(map, navmesh); return navmesh; } @@ -42,6 +43,8 @@ vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { CMesh& mesh = emptyMeshes[m]; LeafMesh leaf = LeafMesh(); + leaf.mins = vec3(FLT_MAX, FLT_MAX, FLT_MAX); + leaf.maxs = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX); for (int f = 0; f < mesh.faces.size(); f++) { CFace& face = mesh.faces[f]; @@ -86,12 +89,18 @@ vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { leaf.leafFaces.push_back(poly); } - if (leaf.leafFaces.size()) { + if (leaf.leafFaces.size() > 2) { leaf.center = vec3(); for (int i = 0; i < leaf.leafFaces.size(); i++) { - leaf.center += leaf.leafFaces[i].center; + Polygon3D& face = leaf.leafFaces[i]; + leaf.center += face.center; + + for (int k = 0; k < face.verts.size(); k++) { + expandBoundingBox(face.verts[k], leaf.mins, leaf.maxs); + } } leaf.center /= leaf.leafFaces.size(); + leaf.idx = emptyLeaves.size(); emptyLeaves.push_back(leaf); } @@ -114,17 +123,21 @@ void LeafNavMeshGenerator::getOctreeBox(Bsp* map, vec3& min, vec3& max) { } } -PolygonOctree* LeafNavMeshGenerator::createPolyOctree(Bsp* map, const vector& leaves, int treeDepth) { +LeafOctree* LeafNavMeshGenerator::createLeafOctree(Bsp* map, LeafNavMesh* mesh, int treeDepth) { + float treeStart = glfwGetTime(); + vec3 treeMin, treeMax; getOctreeBox(map, treeMin, treeMax); - logf("Create octree depth %d, size %f -> %f\n", treeDepth, treeMax.x, treeMax.x / pow(2, treeDepth)); - PolygonOctree* octree = new PolygonOctree(treeMin, treeMax, treeDepth); + LeafOctree* octree = new LeafOctree(treeMin, treeMax, treeDepth); - for (int i = 0; i < leaves.size(); i++) { - //octree->insertPolygon(leaves[i]); + for (int i = 0; i < mesh->numLeaves; i++) { + octree->insertLeaf(&mesh->leaves[i]); } + logf("Create octree depth %d, size %f -> %f in %.2fs\n", treeDepth, + treeMax.x, treeMax.x / pow(2, treeDepth), (float)glfwGetTime() - treeStart); + return octree; } @@ -137,9 +150,16 @@ void LeafNavMeshGenerator::cullTinyLeaves(vector& leaves) { } void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { + + + LeafOctree* octree = createLeafOctree(map, mesh, octreeDepth); + int numLinks = 0; float linkStart = glfwGetTime(); + vector regionLeaves; + regionLeaves.resize(mesh->numLeaves); + for (int i = 0; i < mesh->numLeaves; i++) { LeafMesh& leaf = mesh->leaves[i]; int leafIdx = map->get_leaf(leaf.center, 3); @@ -148,11 +168,19 @@ void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { mesh->leafMap[leafIdx] = i; } + octree->getLeavesInRegion(&leaf, regionLeaves); + for (int k = i + 1; k < mesh->numLeaves; k++) { + if (!regionLeaves[k]) { + continue; + } + numLinks += tryFaceLinkLeaves(map, mesh, i, k); } } + delete octree; + logf("Added %d nav leaf links in %.2fs\n", numLinks, (float)glfwGetTime() - linkStart); } @@ -166,14 +194,7 @@ int LeafNavMeshGenerator::tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int src for (int k = 0; k < dstLeaf.leafFaces.size(); k++) { Polygon3D& dstFace = dstLeaf.leafFaces[k]; - Polygon3D intersectFace = srcFace.intersect(dstFace); - - if (srcLeafIdx == 83 && dstLeafIdx == 84) { - if (fabs(-srcFace.fdist - dstFace.fdist) < 0.1f && dotProduct(srcFace.plane_z, dstFace.plane_z) < -0.99f) { - logf("zomg\n"); - intersectFace = srcFace.intersect(dstFace); - } - } + Polygon3D intersectFace = srcFace.coplanerIntersectArea(dstFace); if (intersectFace.isValid) { mesh->addLink(srcLeafIdx, dstLeafIdx, intersectFace); @@ -184,4 +205,34 @@ int LeafNavMeshGenerator::tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int src } return 0; +} + +void LeafNavMeshGenerator::markWalkableLinks(Bsp* bsp, LeafNavMesh* mesh) { + float markStart = glfwGetTime(); + + for (int i = 0; i < mesh->numLeaves; i++) { + LeafMesh& leaf = mesh->leaves[i]; + LeafNavNode& node = mesh->nodes[i]; + + for (int k = 0; k < MAX_NAV_LEAF_LINKS; k++) { + LeafNavLink& link = node.links[k]; + + if (link.node == -1) { + break; + } + + LeafMesh& otherMesh = mesh->leaves[link.node]; + + vec3 start = leaf.center; + vec3 end = link.linkArea.center; + + + } + } + + logf("Marked link walkability in %.2fs\n", (float)glfwGetTime() - markStart); +} + +bool LeafNavMeshGenerator::isWalkable(Bsp* bsp, vec3 start, vec3 end) { + return true; } \ No newline at end of file diff --git a/src/nav/LeafNavMeshGenerator.h b/src/nav/LeafNavMeshGenerator.h index 66fac51b..efd94cc2 100644 --- a/src/nav/LeafNavMeshGenerator.h +++ b/src/nav/LeafNavMeshGenerator.h @@ -3,7 +3,7 @@ #include "LeafNavMesh.h" class Bsp; -class PolygonOctree; +class LeafOctree; // generates a navigation mesh for a BSP class LeafNavMeshGenerator { @@ -24,7 +24,7 @@ class LeafNavMeshGenerator { void getOctreeBox(Bsp* map, vec3& min, vec3& max); // group polys that are close together for fewer collision checks later - PolygonOctree* createPolyOctree(Bsp* map, const vector& leaves, int treeDepth); + LeafOctree* createLeafOctree(Bsp* map, LeafNavMesh* mesh, int treeDepth); // merged polys adjacent to each other to reduce node count void mergeLeaves(Bsp* map, vector& leaves); @@ -37,4 +37,8 @@ class LeafNavMeshGenerator { void linkNavLeaves(Bsp* map, LeafNavMesh* mesh); int tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int srcLeafIdx, int dstLeafIdx); + + void markWalkableLinks(Bsp* bsp, LeafNavMesh* mesh); + + bool isWalkable(Bsp* bsp, vec3 start, vec3 end); }; \ No newline at end of file diff --git a/src/nav/LeafOctree.cpp b/src/nav/LeafOctree.cpp new file mode 100644 index 00000000..24620f42 --- /dev/null +++ b/src/nav/LeafOctree.cpp @@ -0,0 +1,101 @@ +#include "LeafOctree.h" +#include "util.h" +#include +#include + +LeafOctant::LeafOctant(vec3 min, vec3 max) { + this->min = min; + this->max = max; + memset(children, NULL, sizeof(LeafOctant*) * 8); +} + +LeafOctant::~LeafOctant() { + for (LeafOctant* child : children) { + delete child; + } +} + +void LeafOctant::removeLeaf(LeafMesh* leaf) { + leaves.erase(std::remove(leaves.begin(), leaves.end(), leaf), leaves.end()); + for (int i = 0; i < 8; i++) { + if (children[i]) + children[i]->removeLeaf(leaf); + } +} + +LeafOctree::~LeafOctree() { + delete root; +} + +LeafOctree::LeafOctree(const vec3& min, const vec3& max, int depth) { + root = new LeafOctant(min, max); + maxDepth = depth; + buildOctree(root, 0); +} + +void LeafOctree::buildOctree(LeafOctant* node, int currentDepth) { + if (currentDepth >= maxDepth) { + return; + } + const vec3& min = node->min; + const vec3& max = node->max; + vec3 mid((min.x + max.x) / 2, (min.y + max.y) / 2, (min.z + max.z) / 2); + + // Define eight child octants using the min and max values + node->children[0] = new LeafOctant(min, mid); + node->children[1] = new LeafOctant(vec3(mid.x, min.y, min.z), vec3(max.x, mid.y, mid.z)); + node->children[2] = new LeafOctant(vec3(min.x, mid.y, min.z), vec3(mid.x, max.y, mid.z)); + node->children[3] = new LeafOctant(vec3(mid.x, mid.y, min.z), vec3(max.x, max.y, mid.z)); + node->children[4] = new LeafOctant(vec3(min.x, min.y, mid.z), vec3(mid.x, mid.y, max.z)); + node->children[5] = new LeafOctant(vec3(mid.x, min.y, mid.z), vec3(max.x, mid.y, max.z)); + node->children[6] = new LeafOctant(vec3(min.x, mid.y, mid.z), vec3(mid.x, max.y, max.z)); + node->children[7] = new LeafOctant(mid, max); + + for (LeafOctant* child : node->children) { + buildOctree(child, currentDepth + 1); + } +} + +void LeafOctree::insertLeaf(LeafMesh* leaf) { + insertLeaf(root, leaf, 0); +} + +void LeafOctree::insertLeaf(LeafOctant* node, LeafMesh* leaf, int currentDepth) { + if (currentDepth >= maxDepth) { + node->leaves.push_back(leaf); + return; + } + for (int i = 0; i < 8; ++i) { + if (isLeafInOctant(leaf, node->children[i])) { + insertLeaf(node->children[i], leaf, currentDepth + 1); + } + } +} + +void LeafOctree::removeLeaf(LeafMesh* leaf) { + root->removeLeaf(leaf); +} + +bool LeafOctree::isLeafInOctant(LeafMesh* leaf, LeafOctant* node) { + return boxesIntersect(leaf->mins, leaf->maxs, node->min, node->max); +} + +void LeafOctree::getLeavesInRegion(LeafMesh* leaf, vector& regionLeaves) { + fill(regionLeaves.begin(), regionLeaves.end(), false); + getLeavesInRegion(root, leaf, 0, regionLeaves); +} + +void LeafOctree::getLeavesInRegion(LeafOctant* node, LeafMesh* leaf, int currentDepth, vector& regionLeaves) { + if (currentDepth >= maxDepth) { + for (auto p : node->leaves) { + if (p->idx != -1) + regionLeaves[p->idx] = true; + } + return; + } + for (int i = 0; i < 8; ++i) { + if (isLeafInOctant(leaf, node->children[i])) { + getLeavesInRegion(node->children[i], leaf, currentDepth + 1, regionLeaves); + } + } +} diff --git a/src/nav/LeafOctree.h b/src/nav/LeafOctree.h new file mode 100644 index 00000000..35812ac0 --- /dev/null +++ b/src/nav/LeafOctree.h @@ -0,0 +1,42 @@ +#pragma once +#include "Polygon3D.h" +#include +#include "LeafNavMesh.h" + +struct LeafOctant { + vec3 min; + vec3 max; + vector leaves; + LeafOctant* children[8]; // Eight children octants + + LeafOctant(vec3 min, vec3 max); + + ~LeafOctant(); + + void removeLeaf(LeafMesh* polygon); +}; + +class LeafOctree { +public: + LeafOctant* root; + int maxDepth; + + LeafOctree(const vec3& min, const vec3& max, int depth); + + ~LeafOctree(); + + void insertLeaf(LeafMesh* leaf); + + void removeLeaf(LeafMesh* leaf); + + bool isLeafInOctant(LeafMesh* leaf, LeafOctant* node); + + void getLeavesInRegion(LeafMesh* leaf, vector& regionLeaves); + +private: + void buildOctree(LeafOctant* node, int currentDepth); + + void getLeavesInRegion(LeafOctant* node, LeafMesh* leaf, int currentDepth, vector& regionLeaves); + + void insertLeaf(LeafOctant* node, LeafMesh* leaf, int currentDepth); +}; \ No newline at end of file diff --git a/src/util/PolyOctree.cpp b/src/nav/PolyOctree.cpp similarity index 100% rename from src/util/PolyOctree.cpp rename to src/nav/PolyOctree.cpp diff --git a/src/util/PolyOctree.h b/src/nav/PolyOctree.h similarity index 100% rename from src/util/PolyOctree.h rename to src/nav/PolyOctree.h diff --git a/src/util/Polygon3D.cpp b/src/util/Polygon3D.cpp index 24c4f42f..2f0b9572 100644 --- a/src/util/Polygon3D.cpp +++ b/src/util/Polygon3D.cpp @@ -404,7 +404,7 @@ Polygon3D Polygon3D::merge(const Polygon3D& mergePoly) { return newPoly; } -Polygon3D Polygon3D::intersect(Polygon3D otherPoly) { +Polygon3D Polygon3D::coplanerIntersectArea(Polygon3D otherPoly) { vector outVerts; float epsilon = 1.0f; @@ -473,4 +473,8 @@ Polygon3D Polygon3D::intersect(Polygon3D otherPoly) { } return outVerts; +} + +bool Polygon3D::intersects(Polygon3D& otherPoly) { + return false; } \ No newline at end of file diff --git a/src/util/Polygon3D.h b/src/util/Polygon3D.h index 831e71a4..b0de0943 100644 --- a/src/util/Polygon3D.h +++ b/src/util/Polygon3D.h @@ -73,7 +73,10 @@ class Polygon3D { // returns the area of intersection if polys are coplaner and overlap // otherwise returns an empty polygon - Polygon3D intersect(Polygon3D otherPoly); + Polygon3D coplanerIntersectArea(Polygon3D otherPoly); + + // returns true if the polygons intersect + bool intersects(Polygon3D& otherPoly); // is point inside this polygon? Coordinates are in world space. // Points within EPSILON of an edge are not inside.