Skip to content

Commit

Permalink
leaf-based navigation mesh
Browse files Browse the repository at this point in the history
got an idea to use navigation volumes for a plugin that tells players where to go. 3D volumes are simpler to link together than flat floor polygons with differing heights, and allow more pathing possibilities. The mesh is the set of empty leaves in the clipnode tree for a crouching player. Leaves are linked together by their overlapping face regions.
  • Loading branch information
wootguy committed Jul 3, 2024
1 parent 9ca8822 commit a4fd61a
Show file tree
Hide file tree
Showing 20 changed files with 717 additions and 23 deletions.
19 changes: 17 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ set(SOURCE_FILES
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/bsp/NavMesh.h src/bsp/NavMesh.cpp
src/bsp/NavMeshGenerator.h src/bsp/NavMeshGenerator.cpp
src/globals.h src/globals.cpp

# Navigation meshes
src/nav/NavMesh.h src/nav/NavMesh.cpp
src/nav/NavMeshGenerator.h src/nav/NavMeshGenerator.cpp
src/nav/LeafNavMeshGenerator.h src/nav/LeafNavMeshGenerator.cpp
src/nav/LeafNavMesh.h src/nav/LeafNavMesh.cpp

# OpenGL rendering
src/gl/shaders.h src/gl/shaders.cpp
src/gl/primitives.h src/gl/primitives.cpp
Expand Down Expand Up @@ -83,6 +87,7 @@ include_directories(src/editor)
include_directories(src/gl)
include_directories(src/qtools)
include_directories(src/util)
include_directories(src/nav)
include_directories(imgui)
include_directories(imgui/examples)
include_directories(imgui/backends)
Expand Down Expand Up @@ -186,6 +191,16 @@ if(MSVC)
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)

source_group("Source Files\\nav" FILES src/nav/NavMesh.cpp
src/nav/NavMeshGenerator.cpp
src/nav/LeafNavMeshGenerator.cpp
src/nav/LeafNavMesh.cpp)

source_group("Header Files\\util\\lib" FILES src/util/lodepng.h)

Expand Down
39 changes: 33 additions & 6 deletions src/bsp/Bsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4418,28 +4418,55 @@ const char* Bsp::getLeafContentsName(int32_t contents) {
}
}

int Bsp::get_leaf(vec3 pos) {
int iNode = models->iHeadnodes[0];
int Bsp::get_leaf(vec3 pos, int hull) {
int iNode = models->iHeadnodes[hull];

if (hull == 0) {
while (iNode >= 0)
{
BSPNODE& node = nodes[iNode];
BSPPLANE& plane = planes[node.iPlane];

float d = dotProduct(plane.vNormal, pos) - plane.fDist;
if (d < 0) {
iNode = node.iChildren[1];
}
else {
iNode = node.iChildren[0];
}
}

return ~iNode;
}

int lastNode = -1;
int lastSide = 0;

while (iNode >= 0)
{
BSPNODE& node = nodes[iNode];
BSPCLIPNODE& node = clipnodes[iNode];
BSPPLANE& plane = planes[node.iPlane];

float d = dotProduct(plane.vNormal, pos) - plane.fDist;
if (d < 0) {
lastNode = iNode;
iNode = node.iChildren[1];
lastSide = 1;
}
else {
lastNode = iNode;
iNode = node.iChildren[0];
lastSide = 0;
}
}

return ~iNode;
// clipnodes don't have leaf structs, so generate an id based on the last clipnode index and
// the side of the plane that would be recursed to reach the leaf contents, if there were a leaf
return lastNode * 2 + lastSide;
}

bool Bsp::is_leaf_visible(int ileaf, vec3 pos) {
int ipvsLeaf = get_leaf(pos);
int ipvsLeaf = get_leaf(pos, 0);
BSPLEAF& pvsLeaf = leaves[ipvsLeaf];

int p = pvsLeaf.nVisOffset; // pvs offset
Expand Down Expand Up @@ -4491,7 +4518,7 @@ bool Bsp::is_face_visible(int faceIdx, vec3 pos, vec3 angles) {
}

int Bsp::count_visible_polys(vec3 pos, vec3 angles) {
int ipvsLeaf = get_leaf(pos);
int ipvsLeaf = get_leaf(pos, 0);
BSPLEAF& pvsLeaf = leaves[ipvsLeaf];

int p = pvsLeaf.nVisOffset; // pvs offset
Expand Down
2 changes: 1 addition & 1 deletion src/bsp/Bsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class Bsp
int count_visible_polys(vec3 pos, vec3 angles);

// get leaf index from world position
int get_leaf(vec3 pos);
int get_leaf(vec3 pos, int hull);

// strips a collision hull from the given model index
// and redirects to the given hull, if redirect>0
Expand Down
125 changes: 125 additions & 0 deletions src/editor/BspRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "Clipper.h"
#include "Polygon3D.h"
#include "NavMeshGenerator.h"
#include "LeafNavMeshGenerator.h"
#include "PointEntRenderer.h"
#include "Texture.h"
#include "LightmapNode.h"
Expand Down Expand Up @@ -956,6 +957,129 @@ void BspRenderer::generateNavMeshBuffer() {
file.close();
}

void BspRenderer::generateLeafNavMeshBuffer() {
int hull = 3;
RenderClipnodes* renderClip = &renderClipnodes[0];
renderClip->clipnodeBuffer[hull] = NULL;
renderClip->wireframeClipnodeBuffer[hull] = NULL;

LeafNavMesh* navMesh = LeafNavMeshGenerator().generate(map, hull);
g_app->debugLeafNavMesh = navMesh;

static COLOR4 hullColors[] = {
COLOR4(255, 255, 255, 128),
COLOR4(96, 255, 255, 128),
COLOR4(255, 96, 255, 128),
COLOR4(255, 255, 96, 128),
};
COLOR4 color = hullColors[hull];

vector<cVert> allVerts;
vector<cVert> wireframeVerts;
vector<FaceMath> faceMaths;

for (int lf = 0; lf < navMesh->numLeaves; lf++) {
LeafMesh& mesh = navMesh->leaves[lf];

color = hullColors[hull];
static int r = 0;
r = (r + 1) % 8;
if (r == 0) {
color = COLOR4(255, 32, 32, 128);
}
else if (r == 1) {
color = COLOR4(255, 255, 32, 128);
}
else if (r == 2) {
color = COLOR4(255, 32, 255, 128);
}
else if (r == 3) {
color = COLOR4(255, 128, 255, 128);
}
else if (r == 4) {
color = COLOR4(32, 32, 255, 128);
}
else if (r == 5) {
color = COLOR4(32, 255, 255, 128);
}
else if (r == 6) {
color = COLOR4(32, 128, 255, 128);
}
else if (r == 7) {
color = COLOR4(32, 255, 128, 128);
}

for (int m = 0; m < mesh.leafFaces.size(); m++) {
Polygon3D& poly = mesh.leafFaces[m];

vec3 normal = poly.plane_z;

// calculations for face picking
{
FaceMath faceMath;
faceMath.normal = normal;
faceMath.fdist = poly.fdist;
faceMath.worldToLocal = poly.worldToLocal;
faceMath.localVerts = poly.localVerts;
faceMaths.push_back(faceMath);
}

// create the verts for rendering
{
vector<vec3> renderVerts;
renderVerts.resize(poly.verts.size());
for (int i = 0; i < poly.verts.size(); i++) {
renderVerts[i] = poly.verts[i].flip();
}

COLOR4 wireframeColor = { 0, 0, 0, 255 };
for (int k = 0; k < renderVerts.size(); k++) {
wireframeVerts.push_back(cVert(renderVerts[k], wireframeColor));
wireframeVerts.push_back(cVert(renderVerts[(k + 1) % renderVerts.size()], wireframeColor));
}

vec3 lightDir = vec3(1, 1, -1).normalize();
float dot = (dotProduct(normal, lightDir) + 1) / 2.0f;
if (dot > 0.5f) {
dot = dot * dot;
}
COLOR4 faceColor = color * (dot);

// convert from TRIANGLE_FAN style verts to TRIANGLES
for (int k = 2; k < renderVerts.size(); k++) {
allVerts.push_back(cVert(renderVerts[0], faceColor));
allVerts.push_back(cVert(renderVerts[k - 1], faceColor));
allVerts.push_back(cVert(renderVerts[k], faceColor));
}
}
}
}

cVert* output = new cVert[allVerts.size()];
for (int i = 0; i < allVerts.size(); i++) {
output[i] = allVerts[i];
}

cVert* wireOutput = new cVert[wireframeVerts.size()];
for (int i = 0; i < wireframeVerts.size(); i++) {
wireOutput[i] = wireframeVerts[i];
}

if (allVerts.size() == 0 || wireframeVerts.size() == 0) {
renderClip->clipnodeBuffer[hull] = NULL;
renderClip->wireframeClipnodeBuffer[hull] = NULL;
return;
}

renderClip->clipnodeBuffer[hull] = new VertexBuffer(colorShader, COLOR_4B | POS_3F, output, allVerts.size());
renderClip->clipnodeBuffer[hull]->ownData = true;

renderClip->wireframeClipnodeBuffer[hull] = new VertexBuffer(colorShader, COLOR_4B | POS_3F, wireOutput, wireframeVerts.size());
renderClip->wireframeClipnodeBuffer[hull]->ownData = true;

renderClip->faceMaths[hull] = faceMaths;
}

void BspRenderer::generateClipnodeBuffer(int modelIdx) {
BSPMODEL& model = map->models[modelIdx];
RenderClipnodes* renderClip = &renderClipnodes[modelIdx];
Expand Down Expand Up @@ -1120,6 +1244,7 @@ void BspRenderer::generateClipnodeBuffer(int modelIdx) {

if (modelIdx == 0) {
//generateNavMeshBuffer();
//generateLeafNavMeshBuffer();
}
}

Expand Down
1 change: 1 addition & 0 deletions src/editor/BspRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ class BspRenderer {
void loadClipnodes();
void generateClipnodeBuffer(int modelIdx);
void generateNavMeshBuffer();
void generateLeafNavMeshBuffer();
void deleteRenderModel(RenderModel* renderModel);
void deleteRenderModelClipnodes(RenderClipnodes* renderModel);
void deleteRenderClipnodes();
Expand Down
3 changes: 3 additions & 0 deletions src/editor/Gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,9 @@ void Gui::drawDebugWidget() {
if (i == 0) {
ImGui::Text("Leaf: %d", leafIdx);
}
else {
ImGui::Text("Pseudo ID: %d", map->get_leaf(localCamera, i));
}
ImGui::Text("Parent Node: %d (child %d)",
nodeBranch.size() ? nodeBranch[nodeBranch.size() - 1] : headNode,
childIdx);
Expand Down
66 changes: 66 additions & 0 deletions src/editor/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <fstream>
#include "globals.h"
#include "NavMesh.h"
#include "LeafNavMesh.h"
#include <algorithm>
#include "BspMerger.h"

Expand Down Expand Up @@ -453,6 +454,71 @@ void Renderer::renderLoop() {
}
}

if (debugLeafNavMesh) {
glLineWidth(1);

int leafIdx = mapRenderers[0]->map->get_leaf(cameraOrigin, 3);
int leafNavIdx = MAX_NAV_LEAVES;

if (leafIdx >= 0 && leafIdx < MAX_MAP_CLIPNODE_LEAVES) {
leafNavIdx = debugLeafNavMesh->leafMap[leafIdx];
}

if (leafNavIdx < MAX_NAV_LEAVES) {
LeafNavNode& node = debugLeafNavMesh->nodes[leafNavIdx];
LeafMesh& leaf = debugLeafNavMesh->leaves[leafNavIdx];

drawBox(leaf.center, 2, COLOR4(0, 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), ";
}

//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);
view.loadIdentity();
colorShader->updateMatrixes();

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) {
if (debugPoly.verts.size() > 1) {
vec3 v1 = debugPoly.verts[0];
Expand Down
2 changes: 2 additions & 0 deletions src/editor/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ShaderProgram;
class PointEntRenderer;
class Entity;
class Bsp;
class LeafNavMesh;

enum transform_modes {
TRANSFORM_NONE = -1,
Expand Down Expand Up @@ -77,6 +78,7 @@ class Renderer {
Polygon3D debugPoly;
Polygon3D debugPoly2;
NavMesh* debugNavMesh = NULL;
LeafNavMesh* debugLeafNavMesh = NULL;
int debugNavPoly = -1;
vec3 debugTraceStart;
TraceResult debugTrace;
Expand Down
Loading

0 comments on commit a4fd61a

Please sign in to comment.