From 508cb90b4705279060a1d26ad8bd5a70769a13c8 Mon Sep 17 00:00:00 2001 From: wootguy Date: Sat, 6 Jul 2024 22:37:53 -0700 Subject: [PATCH] move nav leaf origins to closest floor position improves path costs and fixes routing problems with leaves that are both over ground and a deep gap --- src/editor/Renderer.cpp | 121 ++++++++++++---------------- src/nav/LeafNavMeshGenerator.cpp | 134 ++++++++++++++++++++++++------- src/nav/LeafNavMeshGenerator.h | 8 +- src/util/vectors.cpp | 5 ++ src/util/vectors.h | 1 + 5 files changed, 167 insertions(+), 102 deletions(-) diff --git a/src/editor/Renderer.cpp b/src/editor/Renderer.cpp index 88453766..cdd12eb3 100644 --- a/src/editor/Renderer.cpp +++ b/src/editor/Renderer.cpp @@ -478,31 +478,43 @@ void Renderer::renderLoop() { if (route.size()) { LeafNode* lastNode = &debugLeafNavMesh->nodes[route[0]]; - vec3 lastPos = lastNode->bottom; - drawBox(lastNode->bottom, 2, COLOR4(0, 255, 255, 255)); + vec3 lastPos = lastNode->origin; + drawBox(lastNode->origin, 2, COLOR4(0, 255, 255, 255)); for (int i = 1; i < route.size(); i++) { LeafNode& node = debugLeafNavMesh->nodes[route[i]]; - vec3 nodeCenter = node.bottom; + vec3 nodeCenter = node.origin; for (int k = 0; k < lastNode->links.size(); k++) { LeafLink& link = lastNode->links[k]; if (link.node == route[i]) { - vec3 linkPoint = link.bottom; + vec3 linkPoint = link.pos; if (link.baseCost > 16000) { drawLine(lastPos, linkPoint, COLOR4(255, 0, 0, 255)); - drawLine(linkPoint, node.bottom, COLOR4(255, 0, 0, 255)); + drawLine(linkPoint, node.origin, COLOR4(255, 0, 0, 255)); } else if (link.baseCost > 0) { + drawLine(lastPos, linkPoint, COLOR4(255, 64, 0, 255)); + drawLine(linkPoint, node.origin, COLOR4(255, 64, 0, 255)); + } + else if (link.costMultiplier > 99.0f) { drawLine(lastPos, linkPoint, COLOR4(255, 255, 0, 255)); - drawLine(linkPoint, node.bottom, COLOR4(255, 255, 0, 255)); + drawLine(linkPoint, node.origin, COLOR4(255, 255, 0, 255)); + } + else if (link.costMultiplier > 9.0f) { + drawLine(lastPos, linkPoint, COLOR4(255, 0, 255, 255)); + drawLine(linkPoint, node.origin, COLOR4(255, 0, 255, 255)); + } + else if (link.costMultiplier > 1.9f) { + drawLine(lastPos, linkPoint, COLOR4(64, 255, 0, 255)); + drawLine(linkPoint, node.origin, COLOR4(64, 255, 0, 255)); } else { drawLine(lastPos, linkPoint, COLOR4(0, 255, 255, 255)); - drawLine(linkPoint, node.bottom, COLOR4(0, 255, 255, 255)); + drawLine(linkPoint, node.origin, COLOR4(0, 255, 255, 255)); } drawBox(nodeCenter, 2, COLOR4(0, 255, 255, 255)); lastPos = nodeCenter; @@ -519,7 +531,7 @@ void Renderer::renderLoop() { else { LeafNode& node = debugLeafNavMesh->nodes[leafNavIdx]; - drawBox(node.bottom, 2, COLOR4(0, 255, 0, 255)); + drawBox(node.origin, 2, COLOR4(0, 255, 0, 255)); std::string linkStr; @@ -532,92 +544,65 @@ void Renderer::renderLoop() { Polygon3D& linkArea = link.linkArea; if (link.baseCost > 16000) { - drawLine(node.bottom, link.bottom, COLOR4(255, 0, 0, 255)); - drawLine(link.bottom, linkLeaf.bottom, COLOR4(255, 0, 0, 255)); + drawLine(node.origin, link.pos, COLOR4(255, 0, 0, 255)); + drawLine(link.pos, linkLeaf.origin, COLOR4(255, 0, 0, 255)); } else if (link.baseCost > 0) { - drawLine(node.bottom, link.bottom, COLOR4(255, 255, 0, 255)); - drawLine(link.bottom, linkLeaf.bottom, COLOR4(255, 255, 0, 255)); + drawLine(node.origin, link.pos, COLOR4(255, 128, 0, 255)); + drawLine(link.pos, linkLeaf.origin, COLOR4(255, 128, 0, 255)); + } + else if (link.costMultiplier > 99.0f) { + drawLine(node.origin, link.pos, COLOR4(255, 255, 0, 255)); + drawLine(link.pos, linkLeaf.origin, COLOR4(255, 255, 0, 255)); + } + else if (link.costMultiplier > 9.0f) { + drawLine(node.origin, link.pos, COLOR4(255, 0, 255, 255)); + drawLine(link.pos, linkLeaf.origin, COLOR4(255, 0, 255, 255)); + } + else if (link.costMultiplier > 1.9f) { + drawLine(node.origin, link.pos, COLOR4(64, 255, 0, 255)); + drawLine(link.pos, linkLeaf.origin, COLOR4(64, 255, 0, 255)); } else { - drawLine(node.bottom, link.bottom, COLOR4(0, 255, 255, 255)); - drawLine(link.bottom, linkLeaf.bottom, COLOR4(0, 255, 255, 255)); + drawLine(node.origin, link.pos, COLOR4(0, 255, 255, 255)); + drawLine(link.pos, linkLeaf.origin, 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(link.bottom, 1, COLOR4(0, 255, 0, 255)); - drawBox(linkLeaf.bottom, 2, COLOR4(0, 255, 255, 255)); + drawBox(link.pos, 1, COLOR4(0, 255, 0, 255)); + drawBox(linkLeaf.origin, 2, COLOR4(0, 255, 255, 255)); linkStr += to_string(link.node) + " (" + to_string(linkArea.verts.size()) + "v), "; + + /* + for (int k = 0; k < node.links.size(); k++) { + if (i == k) + continue; + drawLine(link.pos, node.links[k].pos, COLOR4(64, 0, 255, 255)); + } + */ } //logf("Leaf node idx: %d, links: %s\n", leafNavIdx, linkStr.c_str()); } } - /* - Polygon3D linkPoly; - LeafNode& node = debugLeafNavMesh->nodes[1441]; - for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { - LeafLink& link = node.links[i]; - - if (link.node == 1442) { - linkPoly = link.linkArea; - break; - } - } - - drawPolygon3D(linkPoly, COLOR4(255, 255, 255, 255)); + /* colorShader->pushMatrix(MAT_PROJECTION); colorShader->pushMatrix(MAT_VIEW); projection.ortho(0, windowWidth, windowHeight, 0, -1.0f, 1.0f); view.loadIdentity(); colorShader->updateMatrixes(); - drawPolygon2D(linkPoly, vec2(800, 100), vec2(500, 500), COLOR4(255, 0, 0, 255)); + drawPolygon2D(debugPoly, vec2(800, 100), vec2(500, 500), COLOR4(255, 0, 0, 255)); colorShader->popMatrix(MAT_PROJECTION); colorShader->popMatrix(MAT_VIEW); */ } - if (debugPoly.isValid) { - if (debugPoly.verts.size() > 1) { - vec3 v1 = debugPoly.verts[0]; - vec3 v2 = debugPoly.verts[1]; - drawLine(v1, v1 + (v2 - v1) * 0.5f, COLOR4(0, 255, 0, 255)); - } - for (int i = 0; i < debugPoly.verts.size(); i++) { - drawBox(debugPoly.verts[i], 4, COLOR4(128, 128, 0, 255)); - } - - glLineWidth(1); - vec3 xaxis = debugPoly.plane_x * 16; - vec3 yaxis = debugPoly.plane_y * 16; - vec3 zaxis = debugPoly.plane_z * 16; - vec3 center = getCenter(debugPoly.verts) + debugPoly.plane_z*8; - drawLine(center, center + xaxis, COLOR4(255, 0, 0, 255)); - drawLine(center, center + yaxis, COLOR4(255, 255, 0, 255)); - drawLine(center, center + zaxis, COLOR4(0, 255, 0, 255)); - - glDisable(GL_DEPTH_TEST); - - colorShader->pushMatrix(MAT_PROJECTION); - colorShader->pushMatrix(MAT_VIEW); - projection.ortho(0, windowWidth, windowHeight, 0, -1.0f, 1.0f); - view.loadIdentity(); - float sz = min(windowWidth*0.8f, windowHeight*0.8f); - model.translate((windowWidth- sz)*0.5f, (windowHeight- sz)*0.5f, 0); - colorShader->updateMatrixes(); - - float scale = drawPolygon2D(debugPoly, vec2(0, 0), vec2(sz, sz), COLOR4(255, 255, 0, 255)); - - colorShader->popMatrix(MAT_PROJECTION); - colorShader->popMatrix(MAT_VIEW); - } - glLineWidth(1); } @@ -1861,12 +1846,12 @@ float Renderer::drawPolygon2D(Polygon3D poly, vec2 pos, vec2 maxSz, COLOR4 color } } + // draw camera origin in the same coordinate space { vec2 cam = poly.project(cameraOrigin); - drawBox2D(offset + cam*scale, 16, poly.isInside(cam) ? COLOR4(0, 255, 0, 255) : COLOR4(255, 32, 0, 255)); + drawBox2D(offset + cam * scale, 16, poly.isInside(cam) ? COLOR4(0, 255, 0, 255) : COLOR4(255, 32, 0, 255)); } - - drawLine2D(offset + debugCut.start * scale, offset + debugCut.end * scale, color); + return scale; } diff --git a/src/nav/LeafNavMeshGenerator.cpp b/src/nav/LeafNavMeshGenerator.cpp index 343c7b6c..4b9c9fdb 100644 --- a/src/nav/LeafNavMeshGenerator.cpp +++ b/src/nav/LeafNavMeshGenerator.cpp @@ -1,3 +1,5 @@ +#include "Renderer.h" +#include "globals.h" #include "LeafNavMeshGenerator.h" #include "GLFW/glfw3.h" #include "PolyOctree.h" @@ -11,20 +13,20 @@ #include #include "Entity.h" + LeafNavMesh* LeafNavMeshGenerator::generate(Bsp* map) { float NavMeshGeneratorGenStart = glfwGetTime(); BSPMODEL& model = map->models[0]; 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); linkNavLeaves(map, navmesh); + setLeafOrigins(map, navmesh); linkLadderLeaves(map, navmesh); calcPathCosts(map, navmesh); @@ -125,16 +127,7 @@ vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int modelIdx, int } leaf.center /= leaf.leafFaces.size(); leaf.id = emptyLeaves.size(); - - vec3 testBottom = leaf.center - vec3(0,0,4096); 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.origin)) { - break; - } - } - leaf.origin.z += NAV_BOTTOM_EPSILON; emptyLeaves.push_back(leaf); } @@ -175,12 +168,85 @@ LeafOctree* LeafNavMeshGenerator::createLeafOctree(Bsp* map, vector& n return octree; } -void LeafNavMeshGenerator::mergeLeaves(Bsp* map, vector& leaves) { - +void LeafNavMeshGenerator::setLeafOrigins(Bsp* map, LeafNavMesh* mesh) { + float timeStart = glfwGetTime(); + + for (int i = 0; i < mesh->nodes.size(); i++) { + LeafNode& node = mesh->nodes[i]; + + vec3 testBottom = node.center - vec3(0, 0, 4096); + node.origin = node.center; + int bottomFaceIdx = -1; + for (int i = 0; i < node.leafFaces.size(); i++) { + Polygon3D& face = node.leafFaces[i]; + if (face.intersect(node.center, testBottom, node.origin)) { + bottomFaceIdx = i; + break; + } + } + node.origin.z += NAV_BOTTOM_EPSILON; + + if (bottomFaceIdx != -1) { + node.origin = getBestPolyOrigin(map, node.leafFaces[bottomFaceIdx], node.origin); + } + + for (int k = 0; k < node.links.size(); k++) { + LeafLink& link = node.links[k]; + + link.pos = getBestPolyOrigin(map, link.linkArea, link.pos); + } + } + + logf("Set leaf origins in %.2fs\n", (float)glfwGetTime() - timeStart); } -void LeafNavMeshGenerator::cullTinyLeaves(vector& leaves) { - +vec3 LeafNavMeshGenerator::getBestPolyOrigin(Bsp* map, Polygon3D& poly, vec3 bias) { + TraceResult tr; + map->traceHull(bias, bias + vec3(0, 0, -4096), NAV_HULL, &tr); + float height = bias.z - tr.vecEndPos.z; + + if (height < NAV_STEP_HEIGHT) { + return bias; + } + + float step = 8.0f; + + float bestHeight = FLT_MAX; + float bestCenterDist = FLT_MAX; + vec3 bestPos = bias; + float pad = 1.0f + EPSILON; // don't choose a point right against a face of the volume + + for (int y = poly.localMins.y + pad; y < poly.localMaxs.y - pad; y += step) { + for (int x = poly.localMins.x + pad; x < poly.localMaxs.x - pad; x += step) { + vec3 testPos = poly.unproject(vec2(x, y)); + testPos.z += NAV_BOTTOM_EPSILON; + + map->traceHull(testPos, testPos + vec3(0, 0, -4096), NAV_HULL, &tr); + float height = testPos.z - tr.vecEndPos.z; + float heightDelta = height - bestHeight; + float centerDist = (testPos - bias).lengthSquared(); + + if (bestHeight <= NAV_STEP_HEIGHT) { + if (height <= NAV_STEP_HEIGHT && centerDist < bestCenterDist) { + bestHeight = height; + bestCenterDist = centerDist; + bestPos = testPos; + } + } + else if (heightDelta < -EPSILON) { + bestHeight = height; + bestCenterDist = centerDist; + bestPos = testPos; + } + else if (fabs(heightDelta) < EPSILON && centerDist < bestCenterDist) { + bestHeight = height; + bestCenterDist = centerDist; + bestPos = testPos; + } + } + } + + return bestPos; } void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { @@ -232,6 +298,7 @@ void LeafNavMeshGenerator::linkLadderLeaves(Bsp* map, LeafNavMesh* mesh) { ladderNode.leafFaces.push_back(node.leafFaces[i]); } } + ladderNode.maxs.z += NAV_CROUCHJUMP_HEIGHT; // players can stand on top of the ladder for more height ladderNode.origin = (ladderNode.mins + ladderNode.maxs) * 0.5f; ladderNode.id = mesh->nodes.size(); ladderNode.entidx = i; @@ -309,7 +376,7 @@ void LeafNavMeshGenerator::calcPathCosts(Bsp* bsp, LeafNavMesh* mesh) { bool isDrop = end.z + EPSILON < start.z; TraceResult tr; - bsp->traceHull(node.origin, link.pos, 3, &tr); + bsp->traceHull(node.origin, link.pos, NAV_HULL, &tr); addPathCost(link, bsp, start, mid, isDrop); addPathCost(link, bsp, mid, end, isDrop); @@ -328,6 +395,8 @@ void LeafNavMeshGenerator::addPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec bool flyingNeeded = false; bool stackingNeeded = false; + bool isSteepSlope = false; + float maxHeight = 0; for (int i = 0; i < steps; i++) { float t = i * (1.0f / (float)steps); @@ -335,20 +404,22 @@ void LeafNavMeshGenerator::addPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec vec3 top = start + delta * t; vec3 bottom = top + vec3(0, 0, -4096); - bsp->traceHull(top, bottom, 3, &tr); + bsp->traceHull(top, bottom, NAV_HULL, &tr); + float height = (tr.vecEndPos - top).length(); - if (tr.flFraction >= 1.0f) { - flyingNeeded = true; + if (tr.vecPlaneNormal.z < 0.7f) { + isSteepSlope = true; } - else { - float height = (tr.vecEndPos - top).length(); - if (height > NAV_CROUCHJUMP_STACK_HEIGHT) { - flyingNeeded = true; - } - else if (height > NAV_CROUCHJUMP_HEIGHT) { - stackingNeeded = true; - } + if (height > maxHeight) { + maxHeight = height; + } + + if (height > NAV_CROUCHJUMP_STACK_HEIGHT) { + flyingNeeded = true; + } + else if (height > NAV_CROUCHJUMP_HEIGHT) { + stackingNeeded = true; } } @@ -361,7 +432,7 @@ void LeafNavMeshGenerator::addPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec // 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.baseCost = max(link.baseCost, 64000.0f); link.costMultiplier = max(link.costMultiplier, 100.0f); } else if (stackingNeeded) { @@ -370,9 +441,12 @@ void LeafNavMeshGenerator::addPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec link.baseCost = max(link.baseCost, 8000.0f); link.costMultiplier = max(link.costMultiplier, 100.0f); } - else if (dir.z > 0.7) { + else if (isSteepSlope) { // 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); } + else if (maxHeight > NAV_STEP_HEIGHT) { + // prefer paths which don't require jumping + link.costMultiplier = max(link.costMultiplier, 2.0f); + } } \ No newline at end of file diff --git a/src/nav/LeafNavMeshGenerator.h b/src/nav/LeafNavMeshGenerator.h index 3ea93f13..39db1081 100644 --- a/src/nav/LeafNavMeshGenerator.h +++ b/src/nav/LeafNavMeshGenerator.h @@ -26,11 +26,11 @@ class LeafNavMeshGenerator { // group polys that are close together for fewer collision checks later LeafOctree* createLeafOctree(Bsp* map, vector& mesh, int treeDepth); - // merged polys adjacent to each other to reduce node count - void mergeLeaves(Bsp* map, vector& leaves); + // finds best origin for a leaf + void setLeafOrigins(Bsp* map, LeafNavMesh* mesh); - // removes tiny faces - void cullTinyLeaves(vector& leaves); + // find point on poly which is closest to a floor, using distance to the bias point as a tie breaker + vec3 getBestPolyOrigin(Bsp* map, Polygon3D& poly, vec3 bias); // links nav leaves which have faces touching each other void linkNavLeaves(Bsp* map, LeafNavMesh* mesh); diff --git a/src/util/vectors.cpp b/src/util/vectors.cpp index d287dd19..500e338d 100644 --- a/src/util/vectors.cpp +++ b/src/util/vectors.cpp @@ -202,6 +202,11 @@ float vec3::length() return sqrt( (x*x) + (y*y) + (z*z) ); } +float vec3::lengthSquared() +{ + return (x * x) + (y * y) + (z * z); +} + string vec3::toKeyvalueString(bool truncate, string suffix_x, string suffix_y, string suffix_z) { string parts[3] = { to_string(x) , to_string(y), to_string(z) }; diff --git a/src/util/vectors.h b/src/util/vectors.h index 83acdeab..1ebc9c1d 100644 --- a/src/util/vectors.h +++ b/src/util/vectors.h @@ -44,6 +44,7 @@ struct vec3 vec3( float x, float y, float z ) : x( x ), y( y ), z( z ) {} vec3 normalize(float length=1.0f); float length(); + float lengthSquared(); // faster than length vec3 invert(); std::string toKeyvalueString(bool truncate=false, std::string suffix_x=" ", std::string suffix_y=" ", std::string suffix_z=""); vec3 flip(); // flip from opengl to Half-life coordinate system and vice versa