From cef251bf9dd25a660babd1fe92d1316d9d16c712 Mon Sep 17 00:00:00 2001 From: wootguy Date: Sat, 6 Jul 2024 01:37:54 -0700 Subject: [PATCH] nav mesh path finding WIP Using dijkstra instead of AStar because some paths are extremely more expensive in order to prevent routes that require flying (not possible for players without gauss, stacking, etc.). So far it's working pretty well. It prefers to walk around the entire map and up some stairs instead of getting a bunch of players to stack in order to reach a vent in the ceiling or something. Next major problem is having entities connect leaves together which otherwise wouldn't be, or would be too expensive due to vertical movement (teleports, ladders, water, elevators, trigger_push). Paths also sometimes take odd turns because of going to the center of a leaf before the face of the next leaf. --- src/editor/BspRenderer.cpp | 4 +- src/editor/Gui.cpp | 12 +- src/editor/Renderer.cpp | 130 +++++++++++++------- src/editor/Renderer.h | 1 + src/nav/LeafNavMesh.cpp | 197 ++++++++++++++++++++++--------- src/nav/LeafNavMesh.h | 56 +++++---- src/nav/LeafNavMeshGenerator.cpp | 130 ++++++++++++++------ src/nav/LeafNavMeshGenerator.h | 11 +- src/nav/LeafOctree.cpp | 21 ++-- src/nav/LeafOctree.h | 16 +-- src/util/Polygon3D.cpp | 164 +++++++++++++++++++++---- src/util/Polygon3D.h | 7 ++ 12 files changed, 548 insertions(+), 201 deletions(-) diff --git a/src/editor/BspRenderer.cpp b/src/editor/BspRenderer.cpp index 2f20e384..b2fb88a5 100644 --- a/src/editor/BspRenderer.cpp +++ b/src/editor/BspRenderer.cpp @@ -978,8 +978,8 @@ void BspRenderer::generateLeafNavMeshBuffer() { vector wireframeVerts; vector faceMaths; - for (int lf = 0; lf < navMesh->numLeaves; lf++) { - LeafMesh& mesh = navMesh->leaves[lf]; + for (int lf = 0; lf < navMesh->nodes.size(); lf++) { + LeafNode& mesh = navMesh->nodes[lf]; color = hullColors[hull]; static int r = 0; diff --git a/src/editor/Gui.cpp b/src/editor/Gui.cpp index a32a0067..3a2749ed 100644 --- a/src/editor/Gui.cpp +++ b/src/editor/Gui.cpp @@ -15,6 +15,7 @@ #include "tinyfiledialogs.h" #include #include "BspMerger.h" +#include "LeafNavMesh.h" // embedded binary data #include "fonts/robotomono.h" @@ -1951,8 +1952,15 @@ void Gui::drawDebugWidget() { if (i == 0) { ImGui::Text("Leaf: %d", leafIdx); } - else { - ImGui::Text("Pseudo ID: %d", map->get_leaf(localCamera, i)); + else if (i == 3 && g_app->debugLeafNavMesh) { + int leafIdx = map->get_leaf(localCamera, 3); + int leafNavIdx = -1; + + if (leafIdx >= 0 && leafIdx < MAX_MAP_CLIPNODE_LEAVES) { + leafNavIdx = g_app->debugLeafNavMesh->leafMap[leafIdx]; + } + + ImGui::Text("Nav ID: %d", leafNavIdx); } ImGui::Text("Parent Node: %d (child %d)", nodeBranch.size() ? nodeBranch[nodeBranch.size() - 1] : headNode, diff --git a/src/editor/Renderer.cpp b/src/editor/Renderer.cpp index c58b8db5..88453766 100644 --- a/src/editor/Renderer.cpp +++ b/src/editor/Renderer.cpp @@ -456,77 +456,99 @@ void Renderer::renderLoop() { if (debugLeafNavMesh) { glLineWidth(1); + glDisable(GL_DEPTH_TEST); Bsp* map = mapRenderers[0]->map; int leafIdx = map->get_leaf(cameraOrigin, 3); - int leafNavIdx = MAX_NAV_LEAVES; + int leafNavIdx = -1; if (leafIdx >= 0 && leafIdx < MAX_MAP_CLIPNODE_LEAVES) { leafNavIdx = debugLeafNavMesh->leafMap[leafIdx]; } - if (leafNavIdx < MAX_NAV_LEAVES) { + if (leafNavIdx >= 0 && leafNavIdx < debugLeafNavMesh->nodes.size()) { 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); + //vector route = debugLeafNavMesh->AStarRoute(leafNavIdx, endNode); + vector route = debugLeafNavMesh->dijkstraRoute(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]; - - if (link.node == route[i + 1]) { - vec3 linkPoint = link.linkArea.center; - - drawLine(lastPos, linkPoint, COLOR4(0, 255, 255, 255)); - drawLine(linkPoint, mesh.center, COLOR4(0, 255, 255, 255)); + LeafNode* lastNode = &debugLeafNavMesh->nodes[route[0]]; + + vec3 lastPos = lastNode->bottom; + drawBox(lastNode->bottom, 2, COLOR4(0, 255, 255, 255)); + + for (int i = 1; i < route.size(); i++) { + LeafNode& node = debugLeafNavMesh->nodes[route[i]]; + + vec3 nodeCenter = node.bottom; + + for (int k = 0; k < lastNode->links.size(); k++) { + LeafLink& link = lastNode->links[k]; + + if (link.node == route[i]) { + vec3 linkPoint = link.bottom; + + if (link.baseCost > 16000) { + drawLine(lastPos, linkPoint, COLOR4(255, 0, 0, 255)); + drawLine(linkPoint, node.bottom, COLOR4(255, 0, 0, 255)); + } + else if (link.baseCost > 0) { + drawLine(lastPos, linkPoint, COLOR4(255, 255, 0, 255)); + drawLine(linkPoint, node.bottom, COLOR4(255, 255, 0, 255)); + } + else { + drawLine(lastPos, linkPoint, COLOR4(0, 255, 255, 255)); + drawLine(linkPoint, node.bottom, COLOR4(0, 255, 255, 255)); + } drawBox(nodeCenter, 2, COLOR4(0, 255, 255, 255)); lastPos = nodeCenter; break; } } + + lastNode = &node; } drawLine(lastPos, pickInfo.ent->getHullOrigin(map), COLOR4(0, 255, 255, 255)); } } else { - LeafNavNode& node = debugLeafNavMesh->nodes[leafNavIdx]; - LeafMesh& leaf = debugLeafNavMesh->leaves[leafNavIdx]; + LeafNode& node = debugLeafNavMesh->nodes[leafNavIdx]; - drawBox(leaf.center, 2, COLOR4(0, 255, 0, 255)); + drawBox(node.bottom, 2, COLOR4(0, 255, 0, 255)); std::string linkStr; - for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { - LeafNavLink& link = node.links[i]; + for (int i = 0; i < node.links.size(); i++) { + LeafLink& link = node.links[i]; if (link.node == -1) { break; } - LeafMesh& linkLeaf = debugLeafNavMesh->leaves[link.node]; + LeafNode& linkLeaf = debugLeafNavMesh->nodes[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)); + if (link.baseCost > 16000) { + drawLine(node.bottom, link.bottom, COLOR4(255, 0, 0, 255)); + drawLine(link.bottom, linkLeaf.bottom, 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)); + } + else { + drawLine(node.bottom, link.bottom, COLOR4(0, 255, 255, 255)); + drawLine(link.bottom, linkLeaf.bottom, 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.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)); + drawBox(link.bottom, 1, COLOR4(0, 255, 0, 255)); + drawBox(linkLeaf.bottom, 2, COLOR4(0, 255, 255, 255)); linkStr += to_string(link.node) + " (" + to_string(linkArea.verts.size()) + "v), "; } @@ -534,17 +556,27 @@ void Renderer::renderLoop() { } } - - glDisable(GL_DEPTH_TEST); /* + 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(); - Line2D edge(vec2(1000, 400), vec2(1400, 630)); - drawLine2D(edge.start, edge.end, COLOR4(255, 0, 0, 255)); + drawPolygon2D(linkPoly, vec2(800, 100), vec2(500, 500), COLOR4(255, 0, 0, 255)); colorShader->popMatrix(MAT_PROJECTION); colorShader->popMatrix(MAT_VIEW); @@ -1799,20 +1831,38 @@ void Renderer::drawBox(vec3 mins, vec3 maxs, COLOR4 color) { buffer.draw(GL_TRIANGLES); } +void Renderer::drawPolygon3D(Polygon3D& poly, COLOR4 color) { + static cVert verts[64]; + + for (int i = 0; i < poly.verts.size() && i < 64; i++) { + vec3 pos = poly.verts[i]; + verts[i].x = pos.x; + verts[i].y = pos.z; + verts[i].z = -pos.y; + verts[i].c = color; + } + + VertexBuffer buffer(colorShader, COLOR_4B | POS_3F, verts, poly.verts.size()); + buffer.draw(GL_TRIANGLE_FAN); +} + float Renderer::drawPolygon2D(Polygon3D poly, vec2 pos, vec2 maxSz, COLOR4 color) { vec2 sz = poly.localMaxs - poly.localMins; float scale = min(maxSz.y / sz.y, maxSz.x / sz.x); - vec2 offset = poly.localMins * -scale; + vec2 offset = poly.localMins * -scale + pos; for (int i = 0; i < poly.verts.size(); i++) { vec2 v1 = poly.localVerts[i]; - vec2 v2 = poly.localVerts[(i + 1) % debugPoly.verts.size()]; + vec2 v2 = poly.localVerts[(i + 1) % poly.verts.size()]; drawLine2D(offset + v1*scale, offset + v2 * scale, color); + if (i == 0) { + drawLine2D(offset + v1 * scale, offset + (v1 + (v2-v1)*0.5f) * scale, COLOR4(0,255,0,255)); + } } { - vec2 cam = debugPoly.project(cameraOrigin); + vec2 cam = poly.project(cameraOrigin); drawBox2D(offset + cam*scale, 16, poly.isInside(cam) ? COLOR4(0, 255, 0, 255) : COLOR4(255, 32, 0, 255)); } diff --git a/src/editor/Renderer.h b/src/editor/Renderer.h index 2bb1ba36..96d550b2 100644 --- a/src/editor/Renderer.h +++ b/src/editor/Renderer.h @@ -250,6 +250,7 @@ class Renderer { void drawLine2D(vec2 start, vec2 end, COLOR4 color); void drawBox(vec3 center, float width, COLOR4 color); void drawBox(vec3 mins, vec3 maxs, COLOR4 color); + void drawPolygon3D(Polygon3D& poly, COLOR4 color); float drawPolygon2D(Polygon3D poly, vec2 pos, vec2 maxSz, COLOR4 color); // returns render scale void drawBox2D(vec2 center, float width, COLOR4 color); void drawPlane(BSPPLANE& plane, COLOR4 color); diff --git a/src/nav/LeafNavMesh.cpp b/src/nav/LeafNavMesh.cpp index 4c04f2fa..4544238f 100644 --- a/src/nav/LeafNavMesh.cpp +++ b/src/nav/LeafNavMesh.cpp @@ -10,8 +10,16 @@ #include "Entity.h" #include "Fgd.h" #include "globals.h" +#include -bool LeafMesh::isInside(vec3 p) { +LeafNode::LeafNode() { + links.clear(); + leafFaces.clear(); + id = -1; + center = bottom = top = mins = maxs = vec3(); +} + +bool LeafNode::isInside(vec3 p) { for (int i = 0; i < leafFaces.size(); i++) { if (leafFaces[i].distance(p) > 0) { return false; @@ -21,26 +29,32 @@ bool LeafMesh::isInside(vec3 p) { return true; } -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; +bool LeafNode::addLink(int node, Polygon3D linkArea) { + for (int i = 0; i < links.size(); i++) { + if (links[i].node == node) { return true; } } - logf("Error: Max links reached on node %d\n", id); - return false; + LeafLink link; + link.linkArea = linkArea; + link.node = node; + + link.bottom = 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; + } + + links.push_back(link); + + return true; } -int LeafNavNode::numLinks() { +int LeafNode::numLinks() { int numLinks = 0; - for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { - if (links[i].node == -1) { - break; - } + for (int i = 0; i < links.size(); i++) { numLinks++; } @@ -52,43 +66,35 @@ LeafNavMesh::LeafNavMesh() { } 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; - } - } + nodes.clear(); } -LeafNavMesh::LeafNavMesh(vector inleaves) { +LeafNavMesh::LeafNavMesh(vector inleaves) { clear(); - for (int i = 0; i < inleaves.size(); i++) { - leaves[i] = inleaves[i]; + nodes = inleaves; + + int totalSz = 0; + for (int i = 0; i < nodes.size(); i++) { + totalSz += sizeof(LeafNode) + (sizeof(LeafLink) * nodes[i].links.size()); } - numLeaves = inleaves.size(); - logf("Created leaf nav mesh with %d leaves (x%d = %d KB)\n", - numLeaves, sizeof(LeafNavNode), (sizeof(LeafNavNode)* numLeaves) / 1024); + logf("Created leaf nav mesh with %d leaves (%d KB)\n", + nodes.size(), totalSz / 1024); - logf("LeafNavNode = %d bytes, LeafNavLink = %d bytes\n", - sizeof(LeafNavNode), sizeof(LeafNavLink)); + logf("LeafNode = %d bytes, LeafLink = %d bytes\n", + sizeof(LeafNode), sizeof(LeafLink)); } bool LeafNavMesh::addLink(int from, int to, Polygon3D linkArea) { - if (from < 0 || to < 0 || from >= MAX_NAV_LEAVES || to >= MAX_NAV_LEAVES) { + if (from < 0 || to < 0 || from >= nodes.size() || to >= nodes.size()) { 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; + vec3& pos = nodes[from].center; logf("Failed to add link at %d %d %d\n", (int)pos.x, (int)pos.y, (int)pos.z); return false; } @@ -135,7 +141,6 @@ int LeafNavMesh::getNodeIdx(Bsp* map, Entity* ent) { 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]; @@ -168,8 +173,8 @@ int LeafNavMesh::getNodeIdx(Bsp* map, Entity* ent) { 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 i = 0; i < nodes.size(); i++) { + LeafNode& mesh = nodes[i]; for (int k = 0; k < mesh.leafFaces.size(); k++) { Polygon3D& leafFace = mesh.leafFaces[k]; @@ -187,23 +192,21 @@ int LeafNavMesh::getNodeIdx(Bsp* map, Entity* ent) { 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; + LeafNode& nodea = nodes[a]; + LeafNode& nodeb = nodes[b]; + vec3 delta = nodea.bottom - nodeb.bottom; - for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { - LeafNavLink& link = nodea.links[i]; - if (link.node == -1) { - break; + for (int i = 0; i < nodea.links.size(); i++) { + LeafLink& link = nodea.links[i]; + if (link.node == b) { + return link.baseCost + delta.length() * link.costMultiplier; } } return delta.length(); } -vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) +vector LeafNavMesh::AStarRoute(int startNodeIdx, int endNodeIdx) { set closedSet; set openSet; @@ -214,7 +217,7 @@ vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) vector emptyRoute; - if (startNodeIdx < 0 || endNodeIdx < 0 || startNodeIdx > MAX_NAV_LEAVES || endNodeIdx > MAX_NAV_LEAVES) { + if (startNodeIdx < 0 || endNodeIdx < 0 || startNodeIdx > nodes.size() || endNodeIdx > nodes.size()) { logf("AStarRoute: invalid start/end nodes\n"); return emptyRoute; } @@ -224,8 +227,8 @@ vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) return emptyRoute; } - LeafNavNode& start = nodes[startNodeIdx]; - LeafNavNode& goal = nodes[endNodeIdx]; + LeafNode& start = nodes[startNodeIdx]; + LeafNode& goal = nodes[endNodeIdx]; openSet.insert(startNodeIdx); gScore[startNodeIdx] = 0; @@ -269,6 +272,7 @@ vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) break; } } + reverse(path.begin(), path.end()); return path; } @@ -276,16 +280,16 @@ vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) openSet.erase(current); closedSet.insert(current); - LeafNavNode& currentNode = nodes[current]; + LeafNode& currentNode = nodes[current]; - for (int i = 0; i < MAX_NAV_LEAF_LINKS; i++) { - LeafNavLink& link = currentNode.links[i]; + for (int i = 0; i < currentNode.links.size(); i++) { + LeafLink& link = currentNode.links[i]; if (link.node == -1) { break; } int neighbor = link.node; - if (neighbor < 0 || neighbor >= MAX_NAV_LEAVES) { + if (neighbor < 0 || neighbor >= nodes.size()) { continue; } if (closedSet.count(neighbor)) @@ -297,7 +301,7 @@ vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) openSet.insert(neighbor); // The distance from start to a neighbor - LeafNavNode& neighborNode = nodes[neighbor]; + LeafNode& neighborNode = nodes[neighbor]; float tentative_gScore = gScore[current]; tentative_gScore += path_cost(currentNode.id, neighborNode.id); @@ -314,7 +318,88 @@ vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx) gScore[neighbor] = tentative_gScore; fScore[neighbor] = tentative_gScore + path_cost(neighborNode.id, goal.id); } - } + } return emptyRoute; +} + +// Dijkstra's algorithm to find shortest path from start to end vertex (chat-gpt code) +vector LeafNavMesh::dijkstraRoute(int start, int end) { + vector emptyRoute; + + if (start < 0 || end < 0 || start > nodes.size() || end > nodes.size()) { + logf("dijkstraRoute: invalid start/end nodes\n"); + return emptyRoute; + } + + if (start == end) { + emptyRoute.push_back(start); + return emptyRoute; + } + + int n = nodes.size(); + vector dist(n, INT_MAX); // Initialize distances with infinity + vector previous(n, -1); // Array to store previous node in the shortest path + dist[start] = 0.0f; // Distance from start node to itself is 0 + + priority_queue, vector>, greater>> pq; + pq.push({ 0.0f, start }); // Push start node with distance 0 + + while (!pq.empty()) { + int u = pq.top().second; // Get node with smallest distance + float d = pq.top().first; // Get the distance + pq.pop(); + + // If the extracted node is already processed, skip + if (d > dist[u]) + continue; + + // Stop early if we reached the end node + if (u == end) + break; + + // Traverse all links of node u + for (int i = 0; i < nodes[u].links.size(); i++) { + LeafLink& link = nodes[u].links[i]; + + if (link.node == -1) { + break; + } + + int v = link.node; + float weight = path_cost(u, link.node); + + // Relaxation step + if (dist[u] + weight < dist[v]) { + dist[v] = dist[u] + weight; + previous[v] = u; // Set previous node for path reconstruction + pq.push({ dist[v], v }); + } + } + } + + // Reconstruct the shortest path from start to end node + vector path; + for (int at = end; at != -1; at = previous[at]) { + path.push_back(at); + if (at == start) + break; + } + reverse(path.begin(), path.end()); + + // If end node is unreachable, return an empty path + if (path.empty() || path[0] != start) + return {}; + + float len = 0; + float cost = 0; + 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(); + cost += path_cost(path[i - 1], path[i]); + } + logf("Path length: %d, cost: %d\n", (int)len, (int)cost); + + return path; } \ No newline at end of file diff --git a/src/nav/LeafNavMesh.h b/src/nav/LeafNavMesh.h index 64f81ef4..48c81e8f 100644 --- a/src/nav/LeafNavMesh.h +++ b/src/nav/LeafNavMesh.h @@ -2,58 +2,66 @@ #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 +#define NAV_STEP_HEIGHT 18 +#define NAV_JUMP_HEIGHT 44 +#define NAV_CROUCHJUMP_HEIGHT 63 // 208 gravity 50% +#define NAV_CROUCHJUMP_STACK_HEIGHT 135 +#define NAV_AUTOCLIMB_HEIGHT 117 + +#define NAV_BOTTOM_EPSILON 1.0f // move waypoints this far from the bottom of the node + class Bsp; class Entity; -struct LeafMesh { +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 + float baseCost; // flat cost for using this path + float costMultiplier; // cost applied to length of path + bool useMiddleLink; +}; + +struct LeafNode { + vector links; + uint16_t id; + vec3 center; + vec3 bottom; + vec3 top; 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 { - LeafNavLink links[MAX_NAV_LEAF_LINKS]; - uint32_t flags; - uint16_t id; + LeafNode(); // adds a link to node "node" on edge "edge" with height difference "zDist" bool addLink(int node, Polygon3D linkArea); int numLinks(); + + // returns true if point is inside leaf volume + bool isInside(vec3 p); }; class LeafNavMesh { public: - LeafNavNode nodes[MAX_NAV_LEAVES]; - LeafMesh leaves[MAX_NAV_LEAVES]; + vector nodes; uint16_t leafMap[MAX_MAP_CLIPNODE_LEAVES]; // maps a BSP leaf index to nav mesh node index - int numLeaves; - LeafNavMesh(); - LeafNavMesh(vector polys); + LeafNavMesh(vector polys); bool addLink(int from, int to, Polygon3D linkArea); void clear(); - vector LeafNavMesh::AStarRoute(Bsp* map, int startNodeIdx, int endNodeIdx); + vector AStarRoute(int startNodeIdx, int endNodeIdx); + + vector dijkstraRoute(int start, int end); float path_cost(int a, int b); diff --git a/src/nav/LeafNavMeshGenerator.cpp b/src/nav/LeafNavMeshGenerator.cpp index e74b6cf2..78244f91 100644 --- a/src/nav/LeafNavMeshGenerator.cpp +++ b/src/nav/LeafNavMeshGenerator.cpp @@ -13,21 +13,21 @@ LeafNavMesh* LeafNavMeshGenerator::generate(Bsp* map, int hull) { float NavMeshGeneratorGenStart = glfwGetTime(); BSPMODEL& model = map->models[0]; - vector emptyLeaves = getHullLeaves(map, hull); + 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); markWalkableLinks(map, navmesh); + logf("Generated nav mesh in %.2fs\n", glfwGetTime() - NavMeshGeneratorGenStart); + return navmesh; } -vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { - vector emptyLeaves; +vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { + vector emptyLeaves; Clipper clipper; @@ -42,7 +42,7 @@ vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { for (int m = 0; m < emptyMeshes.size(); m++) { CMesh& mesh = emptyMeshes[m]; - LeafMesh leaf = LeafMesh(); + LeafNode leaf = LeafNode(); leaf.mins = vec3(FLT_MAX, FLT_MAX, FLT_MAX); leaf.maxs = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -100,7 +100,17 @@ vector LeafNavMeshGenerator::getHullLeaves(Bsp* map, int hull) { } } leaf.center /= leaf.leafFaces.size(); - leaf.idx = emptyLeaves.size(); + leaf.id = emptyLeaves.size(); + + vec3 testBottom = leaf.center - vec3(0,0,4096); + leaf.bottom = leaf.center; + for (int i = 0; i < leaf.leafFaces.size(); i++) { + Polygon3D& face = leaf.leafFaces[i]; + if (face.intersect(leaf.center, testBottom, leaf.bottom)) { + break; + } + } + leaf.bottom.z += NAV_BOTTOM_EPSILON; emptyLeaves.push_back(leaf); } @@ -131,8 +141,8 @@ LeafOctree* LeafNavMeshGenerator::createLeafOctree(Bsp* map, LeafNavMesh* mesh, LeafOctree* octree = new LeafOctree(treeMin, treeMax, treeDepth); - for (int i = 0; i < mesh->numLeaves; i++) { - octree->insertLeaf(&mesh->leaves[i]); + for (int i = 0; i < mesh->nodes.size(); i++) { + octree->insertLeaf(&mesh->nodes[i]); } logf("Create octree depth %d, size %f -> %f in %.2fs\n", treeDepth, @@ -141,27 +151,25 @@ LeafOctree* LeafNavMeshGenerator::createLeafOctree(Bsp* map, LeafNavMesh* mesh, return octree; } -void LeafNavMeshGenerator::mergeLeaves(Bsp* map, vector& leaves) { +void LeafNavMeshGenerator::mergeLeaves(Bsp* map, vector& leaves) { } -void LeafNavMeshGenerator::cullTinyLeaves(vector& leaves) { +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); + regionLeaves.resize(mesh->nodes.size()); - for (int i = 0; i < mesh->numLeaves; i++) { - LeafMesh& leaf = mesh->leaves[i]; + for (int i = 0; i < mesh->nodes.size(); i++) { + LeafNode& leaf = mesh->nodes[i]; int leafIdx = map->get_leaf(leaf.center, 3); if (leafIdx >= 0 && leafIdx < MAX_MAP_CLIPNODE_LEAVES) { @@ -170,7 +178,7 @@ void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { octree->getLeavesInRegion(&leaf, regionLeaves); - for (int k = i + 1; k < mesh->numLeaves; k++) { + for (int k = i + 1; k < mesh->nodes.size(); k++) { if (!regionLeaves[k]) { continue; } @@ -185,8 +193,8 @@ void LeafNavMeshGenerator::linkNavLeaves(Bsp* map, LeafNavMesh* mesh) { } int LeafNavMeshGenerator::tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int srcLeafIdx, int dstLeafIdx) { - LeafMesh& srcLeaf = mesh->leaves[srcLeafIdx]; - LeafMesh& dstLeaf = mesh->leaves[dstLeafIdx]; + LeafNode& srcLeaf = mesh->nodes[srcLeafIdx]; + LeafNode& dstLeaf = mesh->nodes[dstLeafIdx]; for (int i = 0; i < srcLeaf.leafFaces.size(); i++) { Polygon3D& srcFace = srcLeaf.leafFaces[i]; @@ -210,29 +218,85 @@ int LeafNavMeshGenerator::tryFaceLinkLeaves(Bsp* map, LeafNavMesh* mesh, int src 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 i = 0; i < mesh->nodes.size(); i++) { + LeafNode& node = mesh->nodes[i]; - for (int k = 0; k < MAX_NAV_LEAF_LINKS; k++) { - LeafNavLink& link = node.links[k]; + for (int k = 0; k < node.links.size(); k++) { + LeafLink& link = node.links[k]; - if (link.node == -1) { - break; - } + LeafNode& otherMesh = mesh->nodes[link.node]; - LeafMesh& otherMesh = mesh->leaves[link.node]; + vec3 start = node.bottom; + vec3 mid = link.bottom; + vec3 end = otherMesh.bottom; - vec3 start = leaf.center; - vec3 end = link.linkArea.center; + link.baseCost = 0; + link.costMultiplier = 1.0f; + 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 + if (!link.useMiddleLink) { + calcPathCost(link, bsp, start, end); + } + else { + calcPathCost(link, bsp, start, mid); + calcPathCost(link, bsp, mid, end); + } } } - logf("Marked link walkability in %.2fs\n", (float)glfwGetTime() - markStart); + logf("Calculated path costs in %.2fs\n", (float)glfwGetTime() - markStart); } -bool LeafNavMeshGenerator::isWalkable(Bsp* bsp, vec3 start, vec3 end) { - return true; +void LeafNavMeshGenerator::calcPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec3 end) { + TraceResult tr; + + int steps = (end - start).length() / 8.0f; + vec3 delta = end - start; + vec3 dir = delta.normalize(); + + 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); + + vec3 top = start + delta * t; + vec3 bottom = top + vec3(0, 0, -4096); + + bsp->traceHull(top, bottom, 3, &tr); + + if (tr.flFraction >= 1.0f) { + 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; + } + } + } + } + if (dir.z <= 0 && (flyingNeeded || stackingNeeded)) { + // probably falling. not much cost but prefer hitting the ground + link.costMultiplier = max(link.costMultiplier, 10.0f); + } + else if (flyingNeeded) { + link.baseCost = max(link.baseCost, 32000.0f); + link.costMultiplier = max(link.costMultiplier, 10.0f); + } + else if (stackingNeeded) { + link.baseCost = max(link.baseCost, 8000.0f); + } + else if (dir.z > 0.7) { // TODO: staircases trigger this, not just slopes + 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 efd94cc2..5b9c27df 100644 --- a/src/nav/LeafNavMeshGenerator.h +++ b/src/nav/LeafNavMeshGenerator.h @@ -18,7 +18,7 @@ class LeafNavMeshGenerator { int octreeDepth = 6; // get empty leaves of the bsp tree - vector getHullLeaves(Bsp* map, int hull); + vector getHullLeaves(Bsp* map, int hull); // get smallest octree box that can contain the entire map void getOctreeBox(Bsp* map, vec3& min, vec3& max); @@ -27,18 +27,17 @@ class LeafNavMeshGenerator { LeafOctree* createLeafOctree(Bsp* map, LeafNavMesh* mesh, int treeDepth); // merged polys adjacent to each other to reduce node count - void mergeLeaves(Bsp* map, vector& leaves); + void mergeLeaves(Bsp* map, vector& leaves); // removes tiny faces - void cullTinyLeaves(vector& leaves); + 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.) + // links nav leaves which have faces touching each other 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); + void calcPathCost(LeafLink& link, Bsp* bsp, vec3 start, vec3 end); }; \ No newline at end of file diff --git a/src/nav/LeafOctree.cpp b/src/nav/LeafOctree.cpp index 24620f42..46c6c65d 100644 --- a/src/nav/LeafOctree.cpp +++ b/src/nav/LeafOctree.cpp @@ -15,7 +15,7 @@ LeafOctant::~LeafOctant() { } } -void LeafOctant::removeLeaf(LeafMesh* leaf) { +void LeafOctant::removeLeaf(LeafNode* leaf) { leaves.erase(std::remove(leaves.begin(), leaves.end(), leaf), leaves.end()); for (int i = 0; i < 8; i++) { if (children[i]) @@ -56,11 +56,11 @@ void LeafOctree::buildOctree(LeafOctant* node, int currentDepth) { } } -void LeafOctree::insertLeaf(LeafMesh* leaf) { +void LeafOctree::insertLeaf(LeafNode* leaf) { insertLeaf(root, leaf, 0); } -void LeafOctree::insertLeaf(LeafOctant* node, LeafMesh* leaf, int currentDepth) { +void LeafOctree::insertLeaf(LeafOctant* node, LeafNode* leaf, int currentDepth) { if (currentDepth >= maxDepth) { node->leaves.push_back(leaf); return; @@ -72,24 +72,25 @@ void LeafOctree::insertLeaf(LeafOctant* node, LeafMesh* leaf, int currentDepth) } } -void LeafOctree::removeLeaf(LeafMesh* leaf) { +void LeafOctree::removeLeaf(LeafNode* leaf) { root->removeLeaf(leaf); } -bool LeafOctree::isLeafInOctant(LeafMesh* leaf, LeafOctant* node) { - return boxesIntersect(leaf->mins, leaf->maxs, node->min, node->max); +bool LeafOctree::isLeafInOctant(LeafNode* leaf, LeafOctant* node) { + vec3 epsilon = vec3(1, 1, 1); // in case leaves are touching right on the border of an octree leaf + return boxesIntersect(leaf->mins - epsilon, leaf->maxs + epsilon, node->min, node->max); } -void LeafOctree::getLeavesInRegion(LeafMesh* leaf, vector& regionLeaves) { +void LeafOctree::getLeavesInRegion(LeafNode* 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) { +void LeafOctree::getLeavesInRegion(LeafOctant* node, LeafNode* leaf, int currentDepth, vector& regionLeaves) { if (currentDepth >= maxDepth) { for (auto p : node->leaves) { - if (p->idx != -1) - regionLeaves[p->idx] = true; + if (p->id != -1) + regionLeaves[p->id] = true; } return; } diff --git a/src/nav/LeafOctree.h b/src/nav/LeafOctree.h index 35812ac0..c8f146f2 100644 --- a/src/nav/LeafOctree.h +++ b/src/nav/LeafOctree.h @@ -6,14 +6,14 @@ struct LeafOctant { vec3 min; vec3 max; - vector leaves; + vector leaves; LeafOctant* children[8]; // Eight children octants LeafOctant(vec3 min, vec3 max); ~LeafOctant(); - void removeLeaf(LeafMesh* polygon); + void removeLeaf(LeafNode* polygon); }; class LeafOctree { @@ -25,18 +25,18 @@ class LeafOctree { ~LeafOctree(); - void insertLeaf(LeafMesh* leaf); + void insertLeaf(LeafNode* leaf); - void removeLeaf(LeafMesh* leaf); + void removeLeaf(LeafNode* leaf); - bool isLeafInOctant(LeafMesh* leaf, LeafOctant* node); + bool isLeafInOctant(LeafNode* leaf, LeafOctant* node); - void getLeavesInRegion(LeafMesh* leaf, vector& regionLeaves); + void getLeavesInRegion(LeafNode* leaf, vector& regionLeaves); private: void buildOctree(LeafOctant* node, int currentDepth); - void getLeavesInRegion(LeafOctant* node, LeafMesh* leaf, int currentDepth, vector& regionLeaves); + void getLeavesInRegion(LeafOctant* node, LeafNode* leaf, int currentDepth, vector& regionLeaves); - void insertLeaf(LeafOctant* node, LeafMesh* leaf, int currentDepth); + void insertLeaf(LeafOctant* node, LeafNode* leaf, int currentDepth); }; \ No newline at end of file diff --git a/src/util/Polygon3D.cpp b/src/util/Polygon3D.cpp index 2f0b9572..625851b1 100644 --- a/src/util/Polygon3D.cpp +++ b/src/util/Polygon3D.cpp @@ -3,6 +3,7 @@ #include "Renderer.h" #include "globals.h" #include +#include #define COLINEAR_EPSILON 0.125f #define SAME_VERT_EPSILON 0.125f @@ -404,6 +405,98 @@ Polygon3D Polygon3D::merge(const Polygon3D& mergePoly) { return newPoly; } +void push_unique_vert(vector& verts, vec2 vert) { + for (int k = 0; k < verts.size(); k++) { + if ((verts[k] - vert).length() < 0.125f) { + return; + } + } + + verts.push_back(vert); +} + + +namespace GrahamScan { + // https://www.tutorialspoint.com/cplusplus-program-to-implement-graham-scan-algorithm-to-find-the-convex-hull + vec2 p0; + + vec2 secondTop(stack& stk) { + vec2 tempvec2 = stk.top(); + stk.pop(); + vec2 res = stk.top(); //get the second top element + stk.push(tempvec2); //push previous top again + return res; + } + + int squaredDist(vec2 p1, vec2 p2) { + return ((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); + } + + int direction(vec2 a, vec2 b, vec2 c) { + int val = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y); + if (val == 0) + return 0; //colinear + else if (val < 0) + return 2; //anti-clockwise direction + return 1; //clockwise direction + } + + int comp(const void* point1, const void* point2) { + vec2* p1 = (vec2*)point1; + vec2* p2 = (vec2*)point2; + int dir = direction(p0, *p1, *p2); + if (dir == 0) + return (squaredDist(p0, *p2) >= squaredDist(p0, *p1)) ? -1 : 1; + return (dir == 2) ? -1 : 1; + } + + vector findConvexHull(vec2 points[], int n) { + vector convexHullPoints; + int minY = points[0].y, min = 0; + + for (int i = 1; i < n; i++) { + int y = points[i].y; + //find bottom most or left most point + if ((y < minY) || (minY == y) && points[i].x < points[min].x) { + minY = points[i].y; + min = i; + } + } + + swap(points[0], points[min]); //swap min point to 0th location + p0 = points[0]; + qsort(&points[1], n - 1, sizeof(vec2), comp); //sort points from 1 place to end + + int arrSize = 1; //used to locate items in modified array + for (int i = 1; i < n; i++) { + //when the angle of ith and (i+1)th elements are same, remove points + while (i < n - 1 && direction(p0, points[i], points[i + 1]) == 0) + i++; + points[arrSize] = points[i]; + arrSize++; + } + + if (arrSize < 3) + return convexHullPoints; //there must be at least 3 points, return empty list. + //create a stack and add first three points in the stack + + stack stk; + stk.push(points[0]); stk.push(points[1]); stk.push(points[2]); + for (int i = 3; i < arrSize; i++) { //for remaining vertices + while (direction(secondTop(stk), stk.top(), points[i]) != 2) + stk.pop(); //when top, second top and ith point are not making left turn, remove point + stk.push(points[i]); + } + + while (!stk.empty()) { + convexHullPoints.push_back(stk.top()); //add points from stack + stk.pop(); + } + + return convexHullPoints; + } +}; + Polygon3D Polygon3D::coplanerIntersectArea(Polygon3D otherPoly) { vector outVerts; @@ -429,7 +522,7 @@ Polygon3D Polygon3D::coplanerIntersectArea(Polygon3D otherPoly) { if (otherPoly.isInside(va1, true)) { otherPoly.isInside(va1, true); - localOutVerts.push_back(va1); + push_unique_vert(localOutVerts, va1); } for (int k = 0; k < otherLocalVerts.size(); k++) { @@ -438,36 +531,21 @@ Polygon3D Polygon3D::coplanerIntersectArea(Polygon3D otherPoly) { Line2D edgeB(vb1, vb2); if (!edgeA.isAlignedWith(edgeB) && edgeA.doesIntersect(edgeB)) { - localOutVerts.push_back(edgeA.intersect(edgeB)); + push_unique_vert(localOutVerts, 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; + push_unique_vert(localOutVerts, vb1); } } - - if (isUnique) { - newLocalOutVerts.push_back(localOutVerts[i]); - } } - localOutVerts = newLocalOutVerts; if (localOutVerts.size() < 3) { return outVerts; } + localOutVerts = GrahamScan::findConvexHull(&localOutVerts[0], localOutVerts.size()); + for (int i = 0; i < localOutVerts.size(); i++) { outVerts.push_back(unproject(localOutVerts[i])); } @@ -477,4 +555,50 @@ Polygon3D Polygon3D::coplanerIntersectArea(Polygon3D otherPoly) { bool Polygon3D::intersects(Polygon3D& otherPoly) { return false; +} + +bool Polygon3D::intersect(vec3 p1, vec3 p2, vec3& ipos) { + float t1 = dotProduct(plane_z, p1) - fdist; + float t2 = dotProduct(plane_z, p2) - fdist; + + if ((t1 >= 0.0f && t2 >= 0.0f) || (t1 < 0.0f && t2 < 0.0f)) { + return false; + } + + float frac = t1 / (t1 - t2); + frac = clamp(frac, 0.0f, 1.0f); + + if (frac != frac) { + return false; // NaN + } + + ipos = p1 + (p2 - p1) * frac; + + return isInside(project(ipos)); +} + +bool Polygon3D::intersect2D(vec3 p1, vec3 p2, vec3& ipos) { + vec2 p1_2d = project(p1); + vec2 p2_2d = project(p2); + + Line2D line(p1_2d, p2_2d); + + if (isInside(p1_2d, false) == isInside(p2_2d, false)) { + ipos = p1; + return false; + } + + for (int i = 0; i < localVerts.size(); i++) { + vec2 e1 = localVerts[i]; + vec2 e2 = localVerts[(i + 1) % localVerts.size()]; + Line2D edge(e1, e2); + + if (edge.doesIntersect(line)) { + ipos = unproject(edge.intersect(line)); + return true; + } + } + + ipos = p1; + return false; } \ No newline at end of file diff --git a/src/util/Polygon3D.h b/src/util/Polygon3D.h index b0de0943..5b4ae23c 100644 --- a/src/util/Polygon3D.h +++ b/src/util/Polygon3D.h @@ -78,6 +78,13 @@ class Polygon3D { // returns true if the polygons intersect bool intersects(Polygon3D& otherPoly); + // if true, ipos is set to the intersection point with the given line segment + bool intersect(vec3 p1, vec3 p2, vec3& ipos); + + // if true, ipos is set to the projected intersection point of the given line segment + // the line segment is first projected onto the plane for a 2D intersect test + bool intersect2D(vec3 p1, vec3 p2, vec3& ipos); + // is point inside this polygon? Coordinates are in world space. // Points within EPSILON of an edge are not inside. bool isInside(vec3 p);