diff --git a/CMakeLists.txt b/CMakeLists.txt index 62415ac8..d6ec645f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -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) @@ -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) diff --git a/src/bsp/Bsp.cpp b/src/bsp/Bsp.cpp index 1065fc08..ea62d939 100644 --- a/src/bsp/Bsp.cpp +++ b/src/bsp/Bsp.cpp @@ -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 @@ -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 diff --git a/src/bsp/Bsp.h b/src/bsp/Bsp.h index 5f9e96b7..fc92f168 100644 --- a/src/bsp/Bsp.h +++ b/src/bsp/Bsp.h @@ -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 diff --git a/src/editor/BspRenderer.cpp b/src/editor/BspRenderer.cpp index 0db612a1..7887567b 100644 --- a/src/editor/BspRenderer.cpp +++ b/src/editor/BspRenderer.cpp @@ -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" @@ -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 allVerts; + vector wireframeVerts; + vector 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 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]; @@ -1120,6 +1244,7 @@ void BspRenderer::generateClipnodeBuffer(int modelIdx) { if (modelIdx == 0) { //generateNavMeshBuffer(); + //generateLeafNavMeshBuffer(); } } diff --git a/src/editor/BspRenderer.h b/src/editor/BspRenderer.h index 49985ecf..77c431c1 100644 --- a/src/editor/BspRenderer.h +++ b/src/editor/BspRenderer.h @@ -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(); diff --git a/src/editor/Gui.cpp b/src/editor/Gui.cpp index 532d078d..a32a0067 100644 --- a/src/editor/Gui.cpp +++ b/src/editor/Gui.cpp @@ -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); diff --git a/src/editor/Renderer.cpp b/src/editor/Renderer.cpp index bc777226..46724a53 100644 --- a/src/editor/Renderer.cpp +++ b/src/editor/Renderer.cpp @@ -14,6 +14,7 @@ #include #include "globals.h" #include "NavMesh.h" +#include "LeafNavMesh.h" #include #include "BspMerger.h" @@ -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]; diff --git a/src/editor/Renderer.h b/src/editor/Renderer.h index dd86b95b..072aff1c 100644 --- a/src/editor/Renderer.h +++ b/src/editor/Renderer.h @@ -14,6 +14,7 @@ class ShaderProgram; class PointEntRenderer; class Entity; class Bsp; +class LeafNavMesh; enum transform_modes { TRANSFORM_NONE = -1, @@ -77,6 +78,7 @@ class Renderer { Polygon3D debugPoly; Polygon3D debugPoly2; NavMesh* debugNavMesh = NULL; + LeafNavMesh* debugLeafNavMesh = NULL; int debugNavPoly = -1; vec3 debugTraceStart; TraceResult debugTrace; diff --git a/src/nav/LeafNavMesh.cpp b/src/nav/LeafNavMesh.cpp new file mode 100644 index 00000000..7cc4dd11 --- /dev/null +++ b/src/nav/LeafNavMesh.cpp @@ -0,0 +1,81 @@ +#include "LeafNavMesh.h" +#include "GLFW/glfw3.h" +#include "PolyOctree.h" +#include "Clipper.h" +#include "util.h" +#include + +bool LeafNavNode::addLink(int node, Polygon3D linkArea) { + for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { + if (links[i].node == node || links[i].node == -1) { + links[i].linkArea = linkArea; + links[i].node = node; + return true; + } + } + + logf("Error: Max links reached on node %d\n", id); + return false; +} + +int LeafNavNode::numLinks() { + int numLinks = 0; + + for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { + if (links[i].node == -1) { + break; + } + numLinks++; + } + + return numLinks; +} + +LeafNavMesh::LeafNavMesh() { + clear(); +} + +void LeafNavMesh::clear() { + memset(nodes, 0, sizeof(LeafNavNode) * MAX_NAV_LEAVES); + memset(leafMap, 65535, sizeof(uint16_t) * MAX_MAP_CLIPNODE_LEAVES); + + for (int i = 0; i < MAX_NAV_LEAVES; i++) { + leaves[i] = LeafMesh(); + nodes[i].id = i; + + for (int k = 0; k < MAX_NAV_LEAF_LINKS; k++) { + nodes[i].links[k].linkArea = Polygon3D(); + nodes[i].links[k].node = -1; + } + } +} + +LeafNavMesh::LeafNavMesh(vector inleaves) { + clear(); + + for (int i = 0; i < inleaves.size(); i++) { + leaves[i] = inleaves[i]; + } + numLeaves = inleaves.size(); + + logf("Created leaf nav mesh with %d leaves (x%d = %d KB)\n", + numLeaves, sizeof(LeafNavNode), (sizeof(LeafNavNode)* numLeaves) / 1024); + + logf("LeafNavNode = %d bytes, LeafNavLink = %d bytes\n", + sizeof(LeafNavNode), sizeof(LeafNavLink)); +} + +bool LeafNavMesh::addLink(int from, int to, Polygon3D linkArea) { + if (from < 0 || to < 0 || from >= MAX_NAV_LEAVES || to >= MAX_NAV_LEAVES) { + logf("Error: add link from/to invalid node %d %d\n", from, to); + return false; + } + + if (!nodes[from].addLink(to, linkArea)) { + vec3& pos = leaves[from].center; + logf("Failed to add link at %d %d %d\n", (int)pos.x, (int)pos.y, (int)pos.z); + return false; + } + + return true; +} diff --git a/src/nav/LeafNavMesh.h b/src/nav/LeafNavMesh.h new file mode 100644 index 00000000..e687648d --- /dev/null +++ b/src/nav/LeafNavMesh.h @@ -0,0 +1,47 @@ +#pragma once +#include "Polygon3D.h" +#include + +#define MAX_NAV_LEAVES 4096 +#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 + +struct LeafMesh { + vec3 center; + vector leafFaces; +}; + +struct LeafNavLink { + int16_t node; // which poly is linked to. -1 = end of links + Polygon3D linkArea; // region in which leaves are making contact +}; + +struct LeafNavNode { + LeafNavLink links[MAX_NAV_LEAF_LINKS]; + uint32_t flags; + uint16_t id; + + // adds a link to node "node" on edge "edge" with height difference "zDist" + bool addLink(int node, Polygon3D linkArea); + int numLinks(); +}; + + +class LeafNavMesh { +public: + LeafNavNode nodes[MAX_NAV_LEAVES]; + LeafMesh leaves[MAX_NAV_LEAVES]; + uint16_t leafMap[MAX_MAP_CLIPNODE_LEAVES]; // maps a BSP leaf index to nav mesh node index + + int numLeaves; + + LeafNavMesh(); + + LeafNavMesh(vector polys); + + bool addLink(int from, int to, Polygon3D linkArea); + + void clear(); + +private: +}; \ No newline at end of file diff --git a/src/nav/LeafNavMeshGenerator.cpp b/src/nav/LeafNavMeshGenerator.cpp new file mode 100644 index 00000000..1b84302a --- /dev/null +++ b/src/nav/LeafNavMeshGenerator.cpp @@ -0,0 +1,187 @@ +#include "LeafNavMeshGenerator.h" +#include "GLFW/glfw3.h" +#include "PolyOctree.h" +#include "Clipper.h" +#include "Bsp.h" +#include "LeafNavMesh.h" +#include +#include "util.h" +#include "PolyOctree.h" +#include + +LeafNavMesh* LeafNavMeshGenerator::generate(Bsp* map, int hull) { + float NavMeshGeneratorGenStart = glfwGetTime(); + BSPMODEL& model = map->models[0]; + + vector emptyLeaves = getHullLeaves(map, hull); + //mergeLeaves(map, emptyLeaves); + //cullTinyLeaves(emptyLeaves); + + logf("Generated nav mesh in %.2fs\n", glfwGetTime() - NavMeshGeneratorGenStart); + + LeafNavMesh* navmesh = new LeafNavMesh(emptyLeaves); + linkNavLeaves(map, navmesh); + + return navmesh; +} + +vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { + vector emptyLeaves; + + Clipper clipper; + + vector emptyNodes = map->get_model_leaf_volume_cuts(0, hull, CONTENTS_EMPTY); + + vector emptyMeshes; + for (int k = 0; k < emptyNodes.size(); k++) { + emptyMeshes.push_back(clipper.clip(emptyNodes[k].cuts)); + } + + // GET FACES FROM MESHES + for (int m = 0; m < emptyMeshes.size(); m++) { + CMesh& mesh = emptyMeshes[m]; + + LeafMesh leaf = LeafMesh(); + + for (int f = 0; f < mesh.faces.size(); f++) { + CFace& face = mesh.faces[f]; + if (!face.visible) { + continue; + } + + set uniqueFaceVerts; + + for (int k = 0; k < face.edges.size(); k++) { + for (int v = 0; v < 2; v++) { + int vertIdx = mesh.edges[face.edges[k]].verts[v]; + if (!mesh.verts[vertIdx].visible) { + continue; + } + uniqueFaceVerts.insert(vertIdx); + } + } + + vector faceVerts; + for (auto vertIdx : uniqueFaceVerts) { + faceVerts.push_back(mesh.verts[vertIdx].pos); + } + + faceVerts = getSortedPlanarVerts(faceVerts); + + if (faceVerts.size() < 3) { + //logf("Degenerate clipnode face discarded %d\n", faceVerts.size()); + continue; + } + + vec3 normal = getNormalFromVerts(faceVerts); + + if (dotProduct(face.normal, normal) < 0) { + reverse(faceVerts.begin(), faceVerts.end()); + normal = normal.invert(); + } + + Polygon3D poly = Polygon3D(faceVerts); + poly.removeDuplicateVerts(); + + leaf.leafFaces.push_back(poly); + } + + if (leaf.leafFaces.size()) { + leaf.center = vec3(); + for (int i = 0; i < leaf.leafFaces.size(); i++) { + leaf.center += leaf.leafFaces[i].center; + } + leaf.center /= leaf.leafFaces.size(); + + emptyLeaves.push_back(leaf); + } + } + + return emptyLeaves; +} + +void LeafNavMeshGenerator::getOctreeBox(Bsp* map, vec3& min, vec3& max) { + vec3 mapMins; + vec3 mapMaxs; + map->get_bounding_box(mapMins, mapMaxs); + + min = vec3(-MAX_MAP_COORD, -MAX_MAP_COORD, -MAX_MAP_COORD); + max = vec3(MAX_MAP_COORD, MAX_MAP_COORD, MAX_MAP_COORD); + + while (isBoxContained(mapMins, mapMaxs, min * 0.5f, max * 0.5f)) { + max *= 0.5f; + min *= 0.5f; + } +} + +PolygonOctree* LeafNavMeshGenerator::createPolyOctree(Bsp* map, const vector& leaves, int treeDepth) { + 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); + + for (int i = 0; i < leaves.size(); i++) { + //octree->insertPolygon(leaves[i]); + } + + return octree; +} + +void LeafNavMeshGenerator::mergeLeaves(Bsp* map, vector& leaves) { + +} + +void LeafNavMeshGenerator::cullTinyLeaves(vector& leaves) { + +} + +void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { + int numLinks = 0; + float linkStart = glfwGetTime(); + + for (int i = 0; i < mesh->numLeaves; i++) { + LeafMesh& leaf = mesh->leaves[i]; + int leafIdx = map->get_leaf(leaf.center, 3); + + if (leafIdx >= 0 && leafIdx < MAX_MAP_CLIPNODE_LEAVES) { + mesh->leafMap[leafIdx] = i; + } + + for (int k = i + 1; k < mesh->numLeaves; k++) { + numLinks += tryFaceLinkLeaves(map, mesh, i, k); + } + } + + logf("Added %d nav leaf links in %.2fs\n", numLinks, (float)glfwGetTime() - linkStart); +} + +int LeafNavMeshGenerator::tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int srcLeafIdx, int dstLeafIdx) { + LeafMesh& srcLeaf = mesh->leaves[srcLeafIdx]; + LeafMesh& dstLeaf = mesh->leaves[dstLeafIdx]; + + for (int i = 0; i < srcLeaf.leafFaces.size(); i++) { + Polygon3D& srcFace = srcLeaf.leafFaces[i]; + + 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); + } + } + + if (intersectFace.isValid) { + mesh->addLink(srcLeafIdx, dstLeafIdx, intersectFace); + mesh->addLink(dstLeafIdx, srcLeafIdx, intersectFace); + return 2; + } + } + } + + return 0; +} \ No newline at end of file diff --git a/src/nav/LeafNavMeshGenerator.h b/src/nav/LeafNavMeshGenerator.h new file mode 100644 index 00000000..66fac51b --- /dev/null +++ b/src/nav/LeafNavMeshGenerator.h @@ -0,0 +1,40 @@ +#pragma once +#include "Polygon3D.h" +#include "LeafNavMesh.h" + +class Bsp; +class PolygonOctree; + +// generates a navigation mesh for a BSP +class LeafNavMeshGenerator { +public: + LeafNavMeshGenerator() {} + + // generate a nav mesh from the bsp + // returns polygons used to construct the mesh + LeafNavMesh* generate(Bsp* map, int hull); + +private: + int octreeDepth = 6; + + // get empty leaves of the bsp tree + vector getHullLeaves(Bsp* map, int hull); + + // 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 + PolygonOctree* createPolyOctree(Bsp* map, const vector& leaves, int treeDepth); + + // merged polys adjacent to each other to reduce node count + void mergeLeaves(Bsp* map, vector& leaves); + + // removes tiny faces + void cullTinyLeaves(vector& leaves); + + // links nav polys that share an edge from a top-down view + // climbability depends on game settings (gravity, stepsize, autoclimb, grapple/gauss weapon, etc.) + void linkNavLeaves(Bsp* map, LeafNavMesh* mesh); + + int tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int srcLeafIdx, int dstLeafIdx); +}; \ No newline at end of file diff --git a/src/bsp/NavMesh.cpp b/src/nav/NavMesh.cpp similarity index 100% rename from src/bsp/NavMesh.cpp rename to src/nav/NavMesh.cpp diff --git a/src/bsp/NavMesh.h b/src/nav/NavMesh.h similarity index 100% rename from src/bsp/NavMesh.h rename to src/nav/NavMesh.h diff --git a/src/bsp/NavMeshGenerator.cpp b/src/nav/NavMeshGenerator.cpp similarity index 100% rename from src/bsp/NavMeshGenerator.cpp rename to src/nav/NavMeshGenerator.cpp diff --git a/src/bsp/NavMeshGenerator.h b/src/nav/NavMeshGenerator.h similarity index 100% rename from src/bsp/NavMeshGenerator.h rename to src/nav/NavMeshGenerator.h diff --git a/src/util/Line2D.cpp b/src/util/Line2D.cpp index 137e0eab..15fe2898 100644 --- a/src/util/Line2D.cpp +++ b/src/util/Line2D.cpp @@ -8,10 +8,28 @@ Line2D::Line2D(vec2 start, vec2 end) { dir = (end - start).normalize(); } -float Line2D::distance(vec2 p) { +float Line2D::distanceAxis(vec2 p) { return crossProduct(dir, start - p); } +float Line2D::distance(vec2 p) { + float len = (end - start).length(); + float t = dotProduct(p - start, dir) / len; + + if (t < 0) { + return (p - start).length(); + } else if (t > 1) { + return (p - end).length(); + } + + return distanceAxis(p); +} + +vec2 Line2D::project(vec2 p) { + float dot = dotProduct(p - start, dir); + return start + dir*dot; +} + bool Line2D::isAlignedWith(const Line2D& other) { if (fabs(dotProduct(dir, other.dir)) < 0.999f) { return false; // lines not colinear diff --git a/src/util/Line2D.h b/src/util/Line2D.h index bdcfad09..55a6fc64 100644 --- a/src/util/Line2D.h +++ b/src/util/Line2D.h @@ -11,8 +11,14 @@ struct Line2D { Line2D(vec2 start, vec2 end); // distance between this point and the axis of this line + float distanceAxis(vec2 p); + + // distance between this point and line segment, accounting for points beyond the start/end float distance(vec2 p); + // projects a point onto the line segment + vec2 project(vec2 p); + bool doesIntersect(const Line2D& l2); // call doesIntersect for line segments first, this returns the intersection point for infinite lines diff --git a/src/util/Polygon3D.cpp b/src/util/Polygon3D.cpp index 94ce5d7b..24c4f42f 100644 --- a/src/util/Polygon3D.cpp +++ b/src/util/Polygon3D.cpp @@ -112,13 +112,12 @@ float isLeft(const vec2& p1, const vec2& p2, const vec2& point) { } // winding method -bool Polygon3D::isInside(vec2 p) { +bool Polygon3D::isInside(vec2 p, bool includeEdge) { int windingNumber = 0; for (int i = 0; i < localVerts.size(); i++) { const vec2& p1 = localVerts[i]; const vec2& p2 = localVerts[(i + 1) % localVerts.size()]; - vec2 dir = (p2 - p1).normalize(); if (p1.y <= p.y) { if (p2.y > p.y && isLeft(p1, p2, p) > 0) { @@ -129,9 +128,11 @@ bool Polygon3D::isInside(vec2 p) { windingNumber -= 1; } - float dist = crossProduct(dir, p1 - p); + Line2D edge(p1, p2); + float dist = edge.distance(p); + if (fabs(dist) < INPOLY_EPSILON) { - return false; // point is too close to an edge + return includeEdge; // point is too close to an edge } } @@ -172,8 +173,8 @@ vector> Polygon3D::cut(Line2D cutLine) { vec2 e1 = localVerts[i]; vec2 e2 = localVerts[(i + 1) % localVerts.size()]; - float dist1 = fabs(cutLine.distance(e1)); - float dist2 = fabs(cutLine.distance(e2)); + float dist1 = fabs(cutLine.distanceAxis(e1)); + float dist2 = fabs(cutLine.distanceAxis(e2)); if (dist1 < COLINEAR_CUT_EPSILON && dist2 < COLINEAR_CUT_EPSILON) { //logf("cut is colinear with an edge\n"); @@ -212,7 +213,7 @@ vector> Polygon3D::cut(Line2D cutLine) { // define new polys (separate by left/right of line for (int i = 0; i < newLocalVerts.size(); i++) { - float dist = cutLine.distance(newLocalVerts[i]); + float dist = cutLine.distanceAxis(newLocalVerts[i]); if (dist < -SAME_VERT_EPSILON) { splitPolys[0].push_back(newVerts[i]); @@ -292,14 +293,14 @@ bool Polygon3D::isConvex() { return true; } -void Polygon3D::removeDuplicateVerts() { +void Polygon3D::removeDuplicateVerts(float epsilon) { vector newVerts; - int sz = localVerts.size(); + int sz = verts.size(); for (int i = 0; i < sz; i++) { int last = (i + (sz - 1)) % sz; - if (!vec3Equal(verts[i], verts[last], SAME_VERT_EPSILON)) + if (!vec3Equal(verts[i], verts[last], epsilon)) newVerts.push_back(verts[i]); } @@ -402,3 +403,74 @@ Polygon3D Polygon3D::merge(const Polygon3D& mergePoly) { return newPoly; } + +Polygon3D Polygon3D::intersect(Polygon3D otherPoly) { + vector outVerts; + + float epsilon = 1.0f; + + if (fabs(-fdist - otherPoly.fdist) > epsilon || dotProduct(plane_z, otherPoly.plane_z) > -0.99f) + return outVerts; // faces are not coplaner with opposite normals + + // project other polys verts onto the same coordinate system as this face + vector otherLocalVerts; + for (int i = 0; i < otherPoly.verts.size(); i++) { + otherLocalVerts.push_back(project(otherPoly.verts[i])); + } + otherPoly.localVerts = otherLocalVerts; + + vector localOutVerts; + + // find intersection points + for (int i = 0; i < localVerts.size(); i++) { + vec2& va1 = localVerts[i]; + vec2& va2 = localVerts[(i + 1) % localVerts.size()]; + Line2D edgeA(va1, va2); + + if (otherPoly.isInside(va1, true)) { + otherPoly.isInside(va1, true); + localOutVerts.push_back(va1); + } + + for (int k = 0; k < otherLocalVerts.size(); k++) { + vec2& vb1 = otherLocalVerts[k]; + vec2& vb2 = otherLocalVerts[(k + 1) % otherLocalVerts.size()]; + Line2D edgeB(vb1, vb2); + + if (!edgeA.isAlignedWith(edgeB) && edgeA.doesIntersect(edgeB)) { + localOutVerts.push_back(edgeA.intersect(edgeB)); + } + + if (isInside(vb1, true)) { + localOutVerts.push_back(vb1); + } + } + } + + vector newLocalOutVerts; + for (int i = 0; i < localOutVerts.size(); i++) { + + bool isUnique = true; + for (int k = 0; k < newLocalOutVerts.size(); k++) { + if ((newLocalOutVerts[k] - localOutVerts[i]).length() < 0.125f) { + isUnique = false; + break; + } + } + + if (isUnique) { + newLocalOutVerts.push_back(localOutVerts[i]); + } + } + localOutVerts = newLocalOutVerts; + + if (localOutVerts.size() < 3) { + return outVerts; + } + + for (int i = 0; i < localOutVerts.size(); i++) { + outVerts.push_back(unproject(localOutVerts[i])); + } + + return outVerts; +} \ No newline at end of file diff --git a/src/util/Polygon3D.h b/src/util/Polygon3D.h index f3dd438b..831e71a4 100644 --- a/src/util/Polygon3D.h +++ b/src/util/Polygon3D.h @@ -52,7 +52,7 @@ class Polygon3D { bool isConvex(); void removeColinearVerts(); - void removeDuplicateVerts(); + void removeDuplicateVerts(float epsilon=0.125f); void extendAlongAxis(float amt); @@ -68,16 +68,20 @@ class Polygon3D { vector> cut(Line2D cutLine); // returns merged polygon vertices if polys are coplaner and share an edge - // otherwise returns an empty vector + // otherwise returns an empty polygon Polygon3D merge(const Polygon3D& mergePoly); + // returns the area of intersection if polys are coplaner and overlap + // otherwise returns an empty polygon + Polygon3D intersect(Polygon3D otherPoly); + // is point inside this polygon? Coordinates are in world space. // Points within EPSILON of an edge are not inside. bool isInside(vec3 p); // is point inside this polygon? coordinates are in polygon's local space. // Points within EPSILON of an edge are not inside. - bool isInside(vec2 p); + bool isInside(vec2 p, bool includeEdge=false); // project a 3d point onto this polygon's local coordinate system vec2 project(vec3 p);