From 4e26714190f45e6b6d651ef86609fd25b75d7194 Mon Sep 17 00:00:00 2001 From: wootguy Date: Sat, 6 Jul 2024 17:11:53 -0700 Subject: [PATCH] use ladders to for cheap nav mesh links also refactor and penalize flying to leaves that aren't lower than the current leaf --- src/editor/BspRenderer.cpp | 4 +- src/nav/LeafNavMesh.cpp | 47 ++++---- src/nav/LeafNavMesh.h | 26 +++-- src/nav/LeafNavMeshGenerator.cpp | 185 ++++++++++++++++++++++--------- src/nav/LeafNavMeshGenerator.h | 15 ++- src/util/Polygon3D.cpp | 7 ++ src/util/Polygon3D.h | 2 + 7 files changed, 186 insertions(+), 100 deletions(-) diff --git a/src/editor/BspRenderer.cpp b/src/editor/BspRenderer.cpp index b2fb88a5..52aebf27 100644 --- a/src/editor/BspRenderer.cpp +++ b/src/editor/BspRenderer.cpp @@ -958,12 +958,12 @@ void BspRenderer::generateNavMeshBuffer() { } void BspRenderer::generateLeafNavMeshBuffer() { - int hull = 3; + int hull = NAV_HULL; RenderClipnodes* renderClip = &renderClipnodes[0]; renderClip->clipnodeBuffer[hull] = NULL; renderClip->wireframeClipnodeBuffer[hull] = NULL; - LeafNavMesh* navMesh = LeafNavMeshGenerator().generate(map, hull); + LeafNavMesh* navMesh = LeafNavMeshGenerator().generate(map); g_app->debugLeafNavMesh = navMesh; static COLOR4 hullColors[] = { diff --git a/src/nav/LeafNavMesh.cpp b/src/nav/LeafNavMesh.cpp index 47c0843a..110ae3ff 100644 --- a/src/nav/LeafNavMesh.cpp +++ b/src/nav/LeafNavMesh.cpp @@ -15,10 +15,9 @@ #include LeafNode::LeafNode() { - links.clear(); - leafFaces.clear(); id = -1; - center = bottom = top = mins = maxs = vec3(); + entidx = 0; + center = origin = mins = maxs = vec3(); } bool LeafNode::isInside(vec3 p) { @@ -42,10 +41,11 @@ bool LeafNode::addLink(int node, Polygon3D linkArea) { link.linkArea = linkArea; link.node = node; - link.bottom = linkArea.center; + link.pos = linkArea.center; if (fabs(linkArea.plane_z.z) < 0.7f) { - linkArea.intersect2D(linkArea.center, linkArea.center - vec3(0, 0, 4096), link.bottom); - link.bottom.z += NAV_BOTTOM_EPSILON; + // wall links should be positioned at the bottom of the intersection to keep paths near the floor + linkArea.intersect2D(linkArea.center, linkArea.center - vec3(0, 0, 4096), link.pos); + link.pos.z += NAV_BOTTOM_EPSILON; } links.push_back(link); @@ -53,14 +53,19 @@ bool LeafNode::addLink(int node, Polygon3D linkArea) { return true; } -int LeafNode::numLinks() { - int numLinks = 0; - +bool LeafNode::addLink(int node, vec3 linkPos) { for (int i = 0; i < links.size(); i++) { - numLinks++; + if (links[i].node == node) { + return true; + } } - return numLinks; + LeafLink link; + link.node = node; + link.pos = linkPos; + links.push_back(link); + + return true; } LeafNavMesh::LeafNavMesh() { @@ -72,21 +77,11 @@ void LeafNavMesh::clear() { nodes.clear(); } -LeafNavMesh::LeafNavMesh(vector inleaves) { +LeafNavMesh::LeafNavMesh(vector inleaves, LeafOctree* octree) { clear(); - nodes = inleaves; - - int totalSz = 0; - for (int i = 0; i < nodes.size(); i++) { - totalSz += sizeof(LeafNode) + (sizeof(LeafLink) * nodes[i].links.size()); - } - - logf("Created leaf nav mesh with %d leaves (%d KB)\n", - nodes.size(), totalSz / 1024); - - logf("LeafNode = %d bytes, LeafLink = %d bytes\n", - sizeof(LeafNode), sizeof(LeafLink)); + this->nodes = inleaves; + this->octree = octree; } bool LeafNavMesh::addLink(int from, int to, Polygon3D linkArea) { @@ -196,7 +191,7 @@ float LeafNavMesh::path_cost(int a, int b) { LeafNode& nodea = nodes[a]; LeafNode& nodeb = nodes[b]; - vec3 delta = nodea.bottom - nodeb.bottom; + vec3 delta = nodea.origin - nodeb.origin; for (int i = 0; i < nodea.links.size(); i++) { LeafLink& link = nodea.links[i]; @@ -398,7 +393,7 @@ vector LeafNavMesh::dijkstraRoute(int start, int end) { for (int i = 1; i < path.size(); i++) { LeafNode& mesha = nodes[path[i-1]]; LeafNode& meshb = nodes[path[i]]; - len += (mesha.bottom - meshb.bottom).length(); + len += (mesha.origin - meshb.origin).length(); cost += path_cost(path[i - 1], path[i]); } logf("Path length: %d, cost: %d\n", (int)len, (int)cost); diff --git a/src/nav/LeafNavMesh.h b/src/nav/LeafNavMesh.h index 48c81e8f..132723bf 100644 --- a/src/nav/LeafNavMesh.h +++ b/src/nav/LeafNavMesh.h @@ -9,37 +9,40 @@ #define NAV_CROUCHJUMP_HEIGHT 63 // 208 gravity 50% #define NAV_CROUCHJUMP_STACK_HEIGHT 135 #define NAV_AUTOCLIMB_HEIGHT 117 +#define NAV_HULL 3 #define NAV_BOTTOM_EPSILON 1.0f // move waypoints this far from the bottom of the node class Bsp; class Entity; +class LeafOctree; struct LeafLink { - int16_t node; // which leaf is linked to. -1 = end of links - Polygon3D linkArea; // region in which leaves are making contact - vec3 bottom; // centered at the bottom of the polygon intersection + uint16_t node; // which leaf is linked to + vec3 pos; // link position float baseCost; // flat cost for using this path float costMultiplier; // cost applied to length of path - bool useMiddleLink; + + // for debugging + Polygon3D linkArea; // region in which leaves are making contact }; struct LeafNode { vector links; uint16_t id; + vec3 origin; // the best position for pathing (not necessarily the center) + int16_t entidx; // 0 for world leaves, else an entity leaf which may be relocated, enabled, or disabled + // for debugging vec3 center; - vec3 bottom; - vec3 top; - vec3 mins; - vec3 maxs; + vec3 mins, maxs; // for octree insertion, not needed after generation vector leafFaces; LeafNode(); - // adds a link to node "node" on edge "edge" with height difference "zDist" bool addLink(int node, Polygon3D linkArea); - int numLinks(); + + bool addLink(int node, vec3 linkPos); // returns true if point is inside leaf volume bool isInside(vec3 p); @@ -49,11 +52,12 @@ struct LeafNode { class LeafNavMesh { public: vector nodes; + LeafOctree* octree; // finds nearby leaves from any point in space, even outside of the BSP tree uint16_t leafMap[MAX_MAP_CLIPNODE_LEAVES]; // maps a BSP leaf index to nav mesh node index LeafNavMesh(); - LeafNavMesh(vector polys); + LeafNavMesh(vector polys, LeafOctree* octree); bool addLink(int from, int to, Polygon3D linkArea); diff --git a/src/nav/LeafNavMeshGenerator.cpp b/src/nav/LeafNavMeshGenerator.cpp index a96c1025..343c7b6c 100644 --- a/src/nav/LeafNavMeshGenerator.cpp +++ b/src/nav/LeafNavMeshGenerator.cpp @@ -9,30 +9,53 @@ #include "LeafOctree.h" #include #include +#include "Entity.h" -LeafNavMesh* LeafNavMeshGenerator::generate(Bsp* map, int hull) { +LeafNavMesh* LeafNavMeshGenerator::generate(Bsp* map) { float NavMeshGeneratorGenStart = glfwGetTime(); BSPMODEL& model = map->models[0]; - vector emptyLeaves = getHullLeaves(map, hull); + float createLeavesStart = glfwGetTime(); + vector leaves = getHullLeaves(map, 0, CONTENTS_EMPTY); //mergeLeaves(map, emptyLeaves); //cullTinyLeaves(emptyLeaves); + logf("Created %d leaf nodes in %.2fs\n", leaves.size(), glfwGetTime() - createLeavesStart); + + LeafOctree* octree = createLeafOctree(map, leaves, octreeDepth); + LeafNavMesh* navmesh = new LeafNavMesh(leaves, octree); - LeafNavMesh* navmesh = new LeafNavMesh(emptyLeaves); linkNavLeaves(map, navmesh); - markWalkableLinks(map, navmesh); + linkLadderLeaves(map, navmesh); + calcPathCosts(map, navmesh); + + int totalSz = 0; + for (int i = 0; i < navmesh->nodes.size(); i++) { + totalSz += sizeof(LeafNode) + (sizeof(LeafLink) * navmesh->nodes[i].links.size()); + + for (int k = 0; k < navmesh->nodes[i].links.size(); k++) { + totalSz += navmesh->nodes[i].links[k].linkArea.sizeBytes() - sizeof(Polygon3D); + } + for (int k = 0; k < navmesh->nodes[i].leafFaces.size(); k++) { + totalSz += navmesh->nodes[i].leafFaces[k].sizeBytes(); + } + } - logf("Generated nav mesh in %.2fs\n", glfwGetTime() - NavMeshGeneratorGenStart); + logf("Generated %d node nav mesh in %.2fs (%d KB)\n", navmesh->nodes.size(), + glfwGetTime() - NavMeshGeneratorGenStart, totalSz / 1024); return navmesh; } -vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { +vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int modelIdx, int contents) { vector emptyLeaves; + if (modelIdx < 0 || modelIdx >= map->modelCount) { + return emptyLeaves; + } + Clipper clipper; - vector emptyNodes = map->get_model_leaf_volume_cuts(0, hull, CONTENTS_EMPTY); + vector emptyNodes = map->get_model_leaf_volume_cuts(modelIdx, NAV_HULL, contents); vector emptyMeshes; for (int k = 0; k < emptyNodes.size(); k++) { @@ -104,14 +127,14 @@ vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { leaf.id = emptyLeaves.size(); vec3 testBottom = leaf.center - vec3(0,0,4096); - leaf.bottom = leaf.center; + leaf.origin = leaf.center; for (int i = 0; i < leaf.leafFaces.size(); i++) { Polygon3D& face = leaf.leafFaces[i]; - if (face.intersect(leaf.center, testBottom, leaf.bottom)) { + if (face.intersect(leaf.center, testBottom, leaf.origin)) { break; } } - leaf.bottom.z += NAV_BOTTOM_EPSILON; + leaf.origin.z += NAV_BOTTOM_EPSILON; emptyLeaves.push_back(leaf); } @@ -134,7 +157,7 @@ void LeafNavMeshGenerator::getOctreeBox(Bsp* map, vec3& min, vec3& max) { } } -LeafOctree* LeafNavMeshGenerator::createLeafOctree(Bsp* map, LeafNavMesh* mesh, int treeDepth) { +LeafOctree* LeafNavMeshGenerator::createLeafOctree(Bsp* map, vector& nodes, int treeDepth) { float treeStart = glfwGetTime(); vec3 treeMin, treeMax; @@ -142,8 +165,8 @@ LeafOctree* LeafNavMeshGenerator::createLeafOctree(Bsp* map, LeafNavMesh* mesh, LeafOctree* octree = new LeafOctree(treeMin, treeMax, treeDepth); - for (int i = 0; i < mesh->nodes.size(); i++) { - octree->insertLeaf(&mesh->nodes[i]); + for (int i = 0; i < nodes.size(); i++) { + octree->insertLeaf(&nodes[i]); } logf("Create octree depth %d, size %f -> %f in %.2fs\n", treeDepth, @@ -161,8 +184,6 @@ void LeafNavMeshGenerator::cullTinyLeaves(vector& leaves) { } void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { - LeafOctree* octree = createLeafOctree(map, mesh, octreeDepth); - int numLinks = 0; float linkStart = glfwGetTime(); @@ -177,7 +198,7 @@ void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { mesh->leafMap[leafIdx] = i; } - octree->getLeavesInRegion(&leaf, regionLeaves); + mesh->octree->getLeavesInRegion(&leaf, regionLeaves); for (int k = i + 1; k < mesh->nodes.size(); k++) { if (!regionLeaves[k]) { @@ -188,11 +209,58 @@ void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { } } - delete octree; - logf("Added %d nav leaf links in %.2fs\n", numLinks, (float)glfwGetTime() - linkStart); } +void LeafNavMeshGenerator::linkLadderLeaves(Bsp* map, LeafNavMesh* mesh) { + for (int i = 0; i < map->ents.size(); i++) { + Entity* ent = map->ents[i]; + + if (ent->keyvalues["classname"] == "func_ladder") { + vector leaves = getHullLeaves(map, ent->getBspModelIdx(), CONTENTS_SOLID); + + // create a special ladder node which is a combination of all its leaves + LeafNode ladderNode = LeafNode(); + ladderNode.mins = vec3(FLT_MAX, FLT_MAX, FLT_MAX); + ladderNode.maxs = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX); + + for (LeafNode& node : leaves) { + expandBoundingBox(node.mins, ladderNode.mins, ladderNode.maxs); + expandBoundingBox(node.maxs, ladderNode.mins, ladderNode.maxs); + + for (int i = 0; i < node.leafFaces.size(); i++) { + ladderNode.leafFaces.push_back(node.leafFaces[i]); + } + } + ladderNode.origin = (ladderNode.mins + ladderNode.maxs) * 0.5f; + ladderNode.id = mesh->nodes.size(); + ladderNode.entidx = i; + + vector regionLeaves; + regionLeaves.resize(mesh->nodes.size()); + mesh->octree->getLeavesInRegion(&ladderNode, regionLeaves); + + for (int i = 0; i < mesh->nodes.size(); i++) { + if (!regionLeaves[i]) { + continue; + } + + LeafNode& node = mesh->nodes[i]; + if (boxesIntersect(node.mins, node.maxs, ladderNode.mins, ladderNode.maxs)) { + // ladder can connect these leaves + vec3 linkPos = ladderNode.origin; + linkPos.z = node.origin.z; + + ladderNode.addLink(i, linkPos); + node.addLink(ladderNode.id, linkPos); + } + } + + mesh->nodes.push_back(ladderNode); + } + } +} + int LeafNavMeshGenerator::tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int srcLeafIdx, int dstLeafIdx) { LeafNode& srcLeaf = mesh->nodes[srcLeafIdx]; LeafNode& dstLeaf = mesh->nodes[dstLeafIdx]; @@ -216,7 +284,7 @@ int LeafNavMeshGenerator::tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int src return 0; } -void LeafNavMeshGenerator::markWalkableLinks(Bsp* bsp, LeafNavMesh* mesh) { +void LeafNavMeshGenerator::calcPathCosts(Bsp* bsp, LeafNavMesh* mesh) { float markStart = glfwGetTime(); for (int i = 0; i < mesh->nodes.size(); i++) { @@ -224,35 +292,34 @@ void LeafNavMeshGenerator::markWalkableLinks(Bsp* bsp, LeafNavMesh* mesh) { for (int k = 0; k < node.links.size(); k++) { LeafLink& link = node.links[k]; - - LeafNode& otherMesh = mesh->nodes[link.node]; - - vec3 start = node.bottom; - vec3 mid = link.bottom; - vec3 end = otherMesh.bottom; + LeafNode& otherNode = mesh->nodes[link.node]; link.baseCost = 0; link.costMultiplier = 1.0f; + if (node.entidx != 0 || otherNode.entidx != 0) { + // entity links are things like ladders and elevators and cost nothing to use + // so that the path finder prefers them to flying or jumping off ledges + continue; + } + + vec3 start = node.origin; + vec3 mid = link.pos; + vec3 end = otherNode.origin; + bool isDrop = end.z + EPSILON < start.z; + TraceResult tr; - bsp->traceHull(node.bottom, link.bottom, 3, &tr); - link.useMiddleLink = tr.flFraction < 1.0f; - link.useMiddleLink = true; // TODO: downward paths are already working well without skipping middle links + bsp->traceHull(node.origin, link.pos, 3, &tr); - if (!link.useMiddleLink) { - calcPathCost(link, bsp, start, end); - } - else { - calcPathCost(link, bsp, start, mid); - calcPathCost(link, bsp, mid, end); - } + addPathCost(link, bsp, start, mid, isDrop); + addPathCost(link, bsp, mid, end, isDrop); } } logf("Calculated path costs in %.2fs\n", (float)glfwGetTime() - markStart); } -void LeafNavMeshGenerator::calcPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec3 end) { +void LeafNavMeshGenerator::addPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec3 end, bool isDrop) { TraceResult tr; int steps = (end - start).length() / 8.0f; @@ -262,42 +329,50 @@ void LeafNavMeshGenerator::calcPathCost(LeafLink& link, Bsp* bsp, vec3 start, ve bool flyingNeeded = false; bool stackingNeeded = false; - if (dir.z > -0.5f) { - for (int i = 0; i < steps; i++) { - float t = i * (1.0f / (float)steps); + for (int i = 0; i < steps; i++) { + float t = i * (1.0f / (float)steps); + + vec3 top = start + delta * t; + vec3 bottom = top + vec3(0, 0, -4096); - vec3 top = start + delta * t; - vec3 bottom = top + vec3(0, 0, -4096); + bsp->traceHull(top, bottom, 3, &tr); - bsp->traceHull(top, bottom, 3, &tr); + if (tr.flFraction >= 1.0f) { + flyingNeeded = true; + } + else { + float height = (tr.vecEndPos - top).length(); - if (tr.flFraction >= 1.0f) { + if (height > NAV_CROUCHJUMP_STACK_HEIGHT) { flyingNeeded = true; } - else { - float height = (tr.vecEndPos - top).length(); - - if (height > NAV_CROUCHJUMP_STACK_HEIGHT) { - flyingNeeded = true; - } - else if (height > NAV_CROUCHJUMP_HEIGHT) { - stackingNeeded = true; - } + else if (height > NAV_CROUCHJUMP_HEIGHT) { + stackingNeeded = true; } } } - if (dir.z <= 0 && (flyingNeeded || stackingNeeded)) { + + if (isDrop && (flyingNeeded || stackingNeeded)) { // probably falling. not much cost but prefer hitting the ground + // TODO: deadly fall distances should be avoided link.costMultiplier = max(link.costMultiplier, 10.0f); } else if (flyingNeeded) { + // players can't fly normally so any valid ground path will be better, no matter how long it is. + // As a last resort, "flying" is possible by getting a bunch of players to stack or by using the + // gauss gun. link.baseCost = max(link.baseCost, 32000.0f); - link.costMultiplier = max(link.costMultiplier, 10.0f); + link.costMultiplier = max(link.costMultiplier, 100.0f); } else if (stackingNeeded) { + // a player can't reach this high on their own, stacking is needed. + // prefer walking an additional X units instead of waiting for a player or box to stack on link.baseCost = max(link.baseCost, 8000.0f); + link.costMultiplier = max(link.costMultiplier, 100.0f); } - else if (dir.z > 0.7) { // TODO: staircases trigger this, not just slopes + else if (dir.z > 0.7) { + // players can slide up slopes but its excruciatingly slow. Try to find stairs or something. + // TODO: staircases are triggering this, not just slopes. Need to bypass the center of nodes link.costMultiplier = max(link.costMultiplier, 10.0f); } } \ No newline at end of file diff --git a/src/nav/LeafNavMeshGenerator.h b/src/nav/LeafNavMeshGenerator.h index 5b9c27df..3ea93f13 100644 --- a/src/nav/LeafNavMeshGenerator.h +++ b/src/nav/LeafNavMeshGenerator.h @@ -12,19 +12,19 @@ class LeafNavMeshGenerator { // generate a nav mesh from the bsp // returns polygons used to construct the mesh - LeafNavMesh* generate(Bsp* map, int hull); + LeafNavMesh* generate(Bsp* map); private: int octreeDepth = 6; - // get empty leaves of the bsp tree - vector getHullLeaves(Bsp* map, int hull); + // get leaves of the bsp tree with the given contents + vector getHullLeaves(Bsp* map, int modelIdx, int contents); // get smallest octree box that can contain the entire map void getOctreeBox(Bsp* map, vec3& min, vec3& max); // group polys that are close together for fewer collision checks later - LeafOctree* createLeafOctree(Bsp* map, LeafNavMesh* mesh, int treeDepth); + LeafOctree* createLeafOctree(Bsp* map, vector& mesh, int treeDepth); // merged polys adjacent to each other to reduce node count void mergeLeaves(Bsp* map, vector& leaves); @@ -35,9 +35,12 @@ class LeafNavMeshGenerator { // links nav leaves which have faces touching each other void linkNavLeaves(Bsp* map, LeafNavMesh* mesh); + // use ladder entities to create cheaper paths between leaves + void linkLadderLeaves(Bsp* map, LeafNavMesh* mesh); + int tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int srcLeafIdx, int dstLeafIdx); - void markWalkableLinks(Bsp* bsp, LeafNavMesh* mesh); + void calcPathCosts(Bsp* bsp, LeafNavMesh* mesh); - void calcPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec3 end); + void addPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec3 end, bool isDrop); }; \ No newline at end of file diff --git a/src/util/Polygon3D.cpp b/src/util/Polygon3D.cpp index 625851b1..d5b94080 100644 --- a/src/util/Polygon3D.cpp +++ b/src/util/Polygon3D.cpp @@ -32,6 +32,13 @@ Polygon3D::Polygon3D(const vector& verts, int idx) { init(); } +int Polygon3D::sizeBytes() { + return sizeof(Polygon3D) + + sizeof(vec3) * verts.size() + + sizeof(vec2) * localVerts.size() + + sizeof(vec2) * topdownVerts.size(); +} + void Polygon3D::init() { vector triangularVerts = getTriangularVerts(this->verts); localVerts.clear(); diff --git a/src/util/Polygon3D.h b/src/util/Polygon3D.h index 5b4ae23c..32a7bd29 100644 --- a/src/util/Polygon3D.h +++ b/src/util/Polygon3D.h @@ -47,6 +47,8 @@ class Polygon3D { void init(); + int sizeBytes(); + float distance(const vec3& p); bool isConvex();