From d85f5afcd98bd6d20145280337b3ea83563576cb Mon Sep 17 00:00:00 2001 From: wootguy Date: Fri, 27 Oct 2023 12:17:53 -0700 Subject: [PATCH] nav mesh WIP Improved splitting code and fixed some missing polys. Polygons are also merged and tiny polys stripped to reduce the node count. for yabma it makes a few hundred polys above what the recast lib makes (1700 -> 2000). Outdoor areas with slopes end up looking very messy and have gaps. Using HULL 0 in those cases should help but it might be hard to know when that's possible, and it might not connect to HULL 3 mesh nicely. --- src/editor/BspRenderer.cpp | 158 ++++++++++++++++++---- src/editor/BspRenderer.h | 2 + src/editor/Renderer.cpp | 1 + src/editor/Renderer.h | 3 +- src/util/PolyOctree.h | 9 +- src/util/Polygon3D.cpp | 268 ++++++++++++++++++++++++++++++------- src/util/Polygon3D.h | 13 +- src/util/util.cpp | 2 +- 8 files changed, 375 insertions(+), 81 deletions(-) diff --git a/src/editor/BspRenderer.cpp b/src/editor/BspRenderer.cpp index f1469db9..9fd80df6 100644 --- a/src/editor/BspRenderer.cpp +++ b/src/editor/BspRenderer.cpp @@ -872,7 +872,7 @@ void BspRenderer::generateNavMeshBuffer() { vector faceVerts; for (auto vertIdx : uniqueFaceVerts) { - faceVerts.push_back(mesh.verts[vertIdx].pos.vec3f()); + faceVerts.push_back(mesh.verts[vertIdx].pos); } faceVerts = getSortedPlanarVerts(faceVerts); @@ -884,18 +884,21 @@ void BspRenderer::generateNavMeshBuffer() { vec3 normal = getNormalFromVerts(faceVerts); - if (dotProduct(face.normal.vec3f(), normal) < 0) { + if (dotProduct(face.normal, normal) < 0) { reverse(faceVerts.begin(), faceVerts.end()); normal = normal.invert(); } - solidFaces.push_back(new Polygon3D(faceVerts, solidFaces.size())); + Polygon3D* poly = new Polygon3D(faceVerts, solidFaces.size()); + poly->removeDuplicateVerts(); + + solidFaces.push_back(poly); octree.insertPolygon(solidFaces[solidFaces.size()-1]); } } int debugPoly = 0; - //debugPoly = 2233; + //debugPoly = 601; int avgInRegion = 0; int regionChecks = 0; @@ -908,20 +911,21 @@ void BspRenderer::generateNavMeshBuffer() { float startTime = glfwGetTime(); bool doSplit = true; bool doCull = true; + bool doMerge = true; bool walkableSurfacesOnly = true; + const int TINY_POLY = 64; // cull faces smaller than this vector regionPolys; regionPolys.resize(cuttingPolys.size()); vector debugSplits; - int didx = 0; for (int i = 0; i < solidFaces.size(); i++) { Polygon3D* poly = solidFaces[i]; //if (debugPoly && i != debugPoly && i < cuttingPolys.size()) { // continue; //} - if (!poly->isValid || poly->area == 0) { + if (!poly->isValid || poly->area < 4.0f) { continue; } if (walkableSurfacesOnly && poly->plane_z.z < 0.7) { @@ -957,21 +961,27 @@ void BspRenderer::generateNavMeshBuffer() { vector> splitPolys = poly->split(*cutPoly); if (splitPolys.size()) { - anySplits = true; - numSplits++; - Polygon3D* newpoly0 = new Polygon3D(splitPolys[0], solidFaces.size()); Polygon3D* newpoly1 = new Polygon3D(splitPolys[1], solidFaces.size()); - + + if (newpoly0->area < EPSILON || newpoly1->area < EPSILON) { + delete newpoly0; + delete newpoly1; + continue; + } + solidFaces.push_back(newpoly0); solidFaces.push_back(newpoly1); - + + anySplits = true; + numSplits++; + float newArea = newpoly0->area + newpoly1->area; if (newArea < poly->area * 0.9f) { logf("Poly %d area shrunk by %.1f (%.1f -> %1.f)\n", i, (poly->area - newArea), poly->area, newArea); } - //logf("Split poly %d by %d\n", i, k); + //logf("Split poly %d by %d into areas %.1f %.1f\n", i, k, newpoly0->area, newpoly1->area); break; } } @@ -988,14 +998,106 @@ void BspRenderer::generateNavMeshBuffer() { logf("Split %d faces into %d (%d splits)\n", presplit, solidFaces.size(), numSplits); logf("Average of %d in poly regions\n", regionChecks ? (avgInRegion / regionChecks) : 0); logf("Got %d interior faces\n", interiorFaces.size()); + + float mergeStart = glfwGetTime(); + + int preMergePolys = interiorFaces.size(); + vector mergedFaces = interiorFaces; + int pass = 0; + int maxPass = 10; + for (pass = 0; pass <= maxPass; pass++) { + + PolygonOctree mergeOctree(treeMin, treeMax, treedepth); + for (int i = 0; i < mergedFaces.size(); i++) { + mergedFaces[i].idx = i; + //interiorFaces[i].removeColinearVerts(); + mergeOctree.insertPolygon(&mergedFaces[i]); + } + regionPolys.resize(mergedFaces.size()); + + vector newMergedFaces; + + for (int i = 0; i < mergedFaces.size(); i++) { + Polygon3D& poly = mergedFaces[i]; + if (poly.idx == -1) + continue; + //if (pass == 4 && i != 149) + // continue; + + mergeOctree.getPolysInRegion(&poly, regionPolys); + regionPolys[poly.idx] = false; + + int sz = doMerge ? regionPolys.size() : 0; + bool anyMerges = false; + + for (int k = i+1; k < sz; k++) { + if (!regionPolys[k]) { + continue; + } + Polygon3D& mergePoly = mergedFaces[k]; + /* + if (pass == 4 && k != 242) { + continue; + } + if (pass == 4) { + logf("debug time\n"); + } + */ + + //poly.removeColinearVerts(); + //mergePoly.removeColinearVerts(); + Polygon3D mergedPoly = poly.merge(mergePoly); + + if (!mergedPoly.isValid) { + continue; + } + + anyMerges = true; + + // prevent any further merges on the original polys + mergePoly.idx = -1; + poly.idx = -1; + + newMergedFaces.push_back(mergedPoly); + break; + } + + if (!anyMerges) + newMergedFaces.push_back(poly); + } + + logf("Removed %d polys in pass %d\n", mergedFaces.size() - newMergedFaces.size(), pass+1); + + if (mergedFaces.size() == newMergedFaces.size() || pass == maxPass) { + break; + } + else { + mergedFaces = newMergedFaces; + } + } + + vector finalPolys; + for (int i = 0; i < mergedFaces.size(); i++) { + if (mergedFaces[i].area < TINY_POLY) { + // TODO: only remove if there is at least one unconnected edge, + // otherwise there will be holes + continue; + } + finalPolys.push_back(mergedFaces[i]); + } + + logf("Finished merging in %.2fs\n", (float)(glfwGetTime() - mergeStart)); + logf("Merged %d polys down to %d in %d passes\n", preMergePolys, finalPolys.size(), pass); + logf("Removed %d tiny polys\n", mergedFaces.size() - finalPolys.size()); + + debugFaces = finalPolys; + //debugFaces = interiorFaces; for (int i = 0; i < solidFaces.size(); i++) { if (solidFaces[i]) delete solidFaces[i]; } - //g_app->debugPoly = interiorFaces[4180]; - static COLOR4 hullColors[] = { COLOR4(255, 255, 255, 128), COLOR4(96, 255, 255, 128), @@ -1008,8 +1110,8 @@ void BspRenderer::generateNavMeshBuffer() { vector wireframeVerts; vector faceMaths; - for (int m = 0; m < interiorFaces.size(); m++) { - Polygon3D& poly = interiorFaces[m]; + for (int m = 0; m < finalPolys.size(); m++) { + Polygon3D& poly = finalPolys[m]; vec3 normal = poly.plane_z; @@ -1116,7 +1218,7 @@ void BspRenderer::generateNavMeshBuffer() { file << "v " << fixed << std::setprecision(2) << v.x << " " << v.y << " " << v.z << endl; } for (int i = 0; i < allVerts.size(); i += 3) { - file << "f " << (i + 3) << " " << (i + 2) << " " << (i + 1) << endl; + file << "f " << (i + 1) << " " << (i + 2) << " " << (i + 3) << endl; } logf("Wrote %d verts\n", allVerts.size()); file.close(); @@ -1180,7 +1282,7 @@ void BspRenderer::generateClipnodeBuffer(int modelIdx) { vector faceVerts; for (auto vertIdx : uniqueFaceVerts) { - faceVerts.push_back(mesh.verts[vertIdx].pos.vec3f()); + faceVerts.push_back(mesh.verts[vertIdx].pos); } faceVerts = getSortedPlanarVerts(faceVerts); @@ -1192,7 +1294,7 @@ void BspRenderer::generateClipnodeBuffer(int modelIdx) { vec3 normal = getNormalFromVerts(faceVerts); - if (dotProduct(mesh.faces[i].normal.vec3f(), normal) < 0) { + if (dotProduct(mesh.faces[i].normal, normal) < 0) { reverse(faceVerts.begin(), faceVerts.end()); normal = normal.invert(); } @@ -1200,8 +1302,8 @@ void BspRenderer::generateClipnodeBuffer(int modelIdx) { // calculations for face picking { FaceMath faceMath; - faceMath.normal = mesh.faces[i].normal.vec3f(); - faceMath.fdist = getDistAlongAxis(mesh.faces[i].normal.vec3f(), faceVerts[0]); + faceMath.normal = mesh.faces[i].normal; + faceMath.fdist = getDistAlongAxis(mesh.faces[i].normal, faceVerts[0]); vec3 v0 = faceVerts[0]; vec3 v1; @@ -1217,7 +1319,7 @@ void BspRenderer::generateClipnodeBuffer(int modelIdx) { logf("Failed to find non-duplicate vert for clipnode face\n"); } - vec3 plane_z = mesh.faces[i].normal.vec3f(); + vec3 plane_z = mesh.faces[i].normal; vec3 plane_x = (v1 - v0).normalize(); vec3 plane_y = crossProduct(plane_z, plane_x).normalize(); faceMath.worldToLocal = worldToLocalTransform(plane_x, plane_y, plane_z); @@ -1979,8 +2081,16 @@ bool BspRenderer::pickModelPoly(vec3 start, vec3 dir, vec3 offset, int modelIdx, pickInfo.valid = true; pickInfo.bestDist = t; pickInfo.faceIdx = -1; - if (modelIdx == 0) { - logf("Picked hull %d face %d\n", hullIdx, i); + if (modelIdx == 0 && hullIdx == 3) { + static int lastPick = 0; + logf("Picked hull %d, face %d, verts %d, area %.1f\n", hullIdx, i, debugFaces[i].verts.size(), debugFaces[i].area); + g_app->debugPoly = debugFaces[i]; + + Polygon3D merged = debugFaces[lastPick].merge(debugFaces[i]); + vector> split = debugFaces[i].split(debugFaces[lastPick]); + logf("split %d by %d == %d\n", i, lastPick, split.size()); + + lastPick = i; } } } diff --git a/src/editor/BspRenderer.h b/src/editor/BspRenderer.h index 851479c5..088bd207 100644 --- a/src/editor/BspRenderer.h +++ b/src/editor/BspRenderer.h @@ -167,6 +167,8 @@ class BspRenderer { int numPointEnts; int numLoadedTextures = 0; + vector debugFaces; + Texture** glTextures = NULL; Texture** glLightmapTextures = NULL; diff --git a/src/editor/Renderer.cpp b/src/editor/Renderer.cpp index e9334357..a19b91ac 100644 --- a/src/editor/Renderer.cpp +++ b/src/editor/Renderer.cpp @@ -1708,6 +1708,7 @@ float Renderer::drawPolygon2D(Polygon3D poly, vec2 pos, vec2 maxSz, COLOR4 color 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/editor/Renderer.h b/src/editor/Renderer.h index dd04cb5b..d06bb3f6 100644 --- a/src/editor/Renderer.h +++ b/src/editor/Renderer.h @@ -108,8 +108,7 @@ class Renderer { vec3 debugLine0; vec3 debugLine1; - vec3 debugLine2; - vec3 debugLine3; + Line2D debugCut; Polygon3D debugPoly; Polygon3D debugPoly2; diff --git a/src/util/PolyOctree.h b/src/util/PolyOctree.h index e2956c04..af9a10bc 100644 --- a/src/util/PolyOctree.h +++ b/src/util/PolyOctree.h @@ -25,17 +25,18 @@ class PolygonOctree { ~PolygonOctree(); - void buildOctree(PolyOctant* node, int currentDepth); - void insertPolygon(Polygon3D* polygon); - void insertPolygon(PolyOctant* node, Polygon3D* polygon, int currentDepth); - void removePolygon(Polygon3D* polygon); bool isPolygonInOctant(Polygon3D* polygon, PolyOctant* node); void getPolysInRegion(Polygon3D* poly, vector& regionPolys); +private: + void buildOctree(PolyOctant* node, int currentDepth); + void getPolysInRegion(PolyOctant* node, Polygon3D* poly, int currentDepth, vector& regionPolys); + + void insertPolygon(PolyOctant* node, Polygon3D* polygon, int currentDepth); }; \ No newline at end of file diff --git a/src/util/Polygon3D.cpp b/src/util/Polygon3D.cpp index 5ac9d19f..28cba908 100644 --- a/src/util/Polygon3D.cpp +++ b/src/util/Polygon3D.cpp @@ -3,6 +3,21 @@ #include "BspRenderer.h" #include "Renderer.h" +#define COLINEAR_EPSILON 0.125f +#define SAME_VERT_EPSILON 0.125f + +bool vec3Equal(vec3 v1, vec3 v2, float epsilon) +{ + vec3 v = v1 - v2; + if (fabs(v.x) >= epsilon) + return false; + if (fabs(v.y) >= epsilon) + return false; + if (fabs(v.z) >= epsilon) + return false; + return true; +} + Line2D::Line2D(vec2 start, vec2 end) { this->start = start; this->end = end; @@ -87,6 +102,16 @@ Polygon3D::Polygon3D(const vector& verts, int idx) { void Polygon3D::init() { vector triangularVerts = getTriangularVerts(this->verts); + localVerts.clear(); + isValid = false; + center = vec3(); + area = 0; + + localMins = vec2(FLT_MAX, FLT_MAX); + localMaxs = vec2(-FLT_MAX, -FLT_MAX); + + worldMins = vec3(FLT_MAX, FLT_MAX, FLT_MAX); + worldMaxs = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX); if (triangularVerts.empty()) return; @@ -104,18 +129,9 @@ void Polygon3D::init() { if (localToWorld.m[15] == 0) { // failed matrix inversion - isValid = false; return; } - localMins = vec2(FLT_MAX, FLT_MAX); - localMaxs = vec2(-FLT_MAX, -FLT_MAX); - - worldMins = vec3(FLT_MAX, FLT_MAX, FLT_MAX); - worldMaxs = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX); - - center = vec3(); - for (int e = 0; e < verts.size(); e++) { vec2 localPoint = project(verts[e]); localVerts.push_back(localPoint); @@ -124,7 +140,6 @@ void Polygon3D::init() { center += verts[e]; } - area = 0; for (int i = 0; i < localVerts.size(); i++) { area += crossProduct(localVerts[i], localVerts[(i+1) % localVerts.size()]); } @@ -193,78 +208,95 @@ bool Polygon3D::isInside(vec2 p) { vector> Polygon3D::cut(Line2D cutLine) { vector> splitPolys; - int vertIntersections = 0; - for (int i = 0; i < localVerts.size(); i++) { - float dist = fabs(cutLine.distance(localVerts[i])); - if (dist < EPSILON) { - vertIntersections++; - if (vertIntersections > 1) { - //logf("cut is colinear with an edge\n"); - return splitPolys; // line is colinear with an edge, no intersections possible - } - } + bool intersectsAnyEdge = false; + if (isInside(cutLine.start) || isInside(cutLine.end)) { + intersectsAnyEdge = true; } - bool intersectsAnyEdge = 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 (!intersectsAnyEdge) { + 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(cutLine)) { - intersectsAnyEdge = true; - break; + if (edge.doesIntersect(cutLine)) { + intersectsAnyEdge = true; + break; + } } } - if (!intersectsAnyEdge && !(isInside(cutLine.start) && isInside(cutLine.end))) { + if (!intersectsAnyEdge) { //logf("No edge intersections\n"); return splitPolys; } - // extend to "infinity" now that we know the cutting edge is touching the poly somewhere + // extend to "infinity" if we know the cutting edge is touching the poly somewhere // a split should happen along that edge across the entire polygon cutLine.start = cutLine.start - cutLine.dir * MAX_COORD; cutLine.end = cutLine.end + cutLine.dir * MAX_COORD; + for (int i = 0; i < localVerts.size(); i++) { + vec2 e1 = localVerts[i]; + vec2 e2 = localVerts[(i + 1) % localVerts.size()]; + + float dist1 = fabs(cutLine.distance(e1)); + float dist2 = fabs(cutLine.distance(e2)); + + if (dist1 < COLINEAR_EPSILON) { + if (dist2 < COLINEAR_EPSILON) { + //logf("cut is colinear with an edge\n"); + return splitPolys; // line is colinear with an edge, no intersections possible + } + } + } + + splitPolys.push_back(vector()); splitPolys.push_back(vector()); - int edgeIntersections = 0; - //logf("VErt intersects: %d\n", vertIntersections); + // get new verts with intersection points included + vector newVerts; + vector newLocalVerts; for (int i = 0; i < localVerts.size(); i++) { + int next = (i + 1) % localVerts.size(); vec2 e1 = localVerts[i]; - vec2 e2 = localVerts[(i + 1) % localVerts.size()]; + vec2 e2 = localVerts[next]; Line2D edge(e1, e2); - int polyIdx = edgeIntersections == 1 ? 1 : 0; + newVerts.push_back(verts[i]); + newLocalVerts.push_back(e1); if (edge.doesIntersect(cutLine)) { vec2 intersect = edge.intersect(cutLine); vec3 worldPos = (localToWorld * vec4(intersect.x, intersect.y, fdist, 1)).xyz(); - splitPolys[polyIdx].push_back(verts[i]); - splitPolys[0].push_back(worldPos); - splitPolys[1].push_back(worldPos); - - edgeIntersections++; - if (edgeIntersections > 2) { - //logf(">2 edge intersections! Has %d vert intersect\n", vertIntersections); - return vector>(); + if (!vec3Equal(worldPos, verts[i], SAME_VERT_EPSILON) && !vec3Equal(worldPos, verts[next], SAME_VERT_EPSILON)) { + newVerts.push_back(worldPos); + newLocalVerts.push_back(intersect); } } - else { - splitPolys[polyIdx].push_back(verts[i]); - } } - //logf("Edge intersects: %d\n", edgeIntersections); + // define new polys (separate by left/right of line + for (int i = 0; i < newLocalVerts.size(); i++) { + float dist = cutLine.distance(newLocalVerts[i]); - if (edgeIntersections <= 1) { - return vector>(); + if (dist < -SAME_VERT_EPSILON) { + splitPolys[0].push_back(newVerts[i]); + } + else if (dist > SAME_VERT_EPSILON) { + splitPolys[1].push_back(newVerts[i]); + } + else { + splitPolys[0].push_back(newVerts[i]); + splitPolys[1].push_back(newVerts[i]); + } } + g_app->debugCut = cutLine; + if (splitPolys[0].size() < 3 || splitPolys[1].size() < 3) { //logf("Degenerate split!\n"); return vector>(); @@ -283,7 +315,7 @@ vector> Polygon3D::split(const Polygon3D& cutPoly) { const vec3& e2 = cutPoly.verts[(i + 1) % cutPoly.verts.size()]; if (fabs(distance(e1)) < EPSILON && fabs(distance(e2)) < EPSILON) { - //logf("Edge %d is inside\n", i); + //logf("Edge %d is inside %.1f %.1f\n", i, distance(e1), distance(e2)); g_app->debugLine0 = e1; g_app->debugLine1 = e2; return cut(Line2D(project(e1), project(e2))); @@ -291,4 +323,142 @@ vector> Polygon3D::split(const Polygon3D& cutPoly) { } return vector>(); +} + +bool Polygon3D::isConvex() { + int n = localVerts.size(); + if (n < 3) { + return false; + } + + int sign = 0; // Initialize the sign of the cross product + + for (int i = 0; i < n; i++) { + const vec2& A = localVerts[i]; + const vec2& B = localVerts[(i + 1) % n]; // Next vertex + const vec2& C = localVerts[(i + 2) % n]; // Vertex after the next + + vec2 AB = vec2(B.x - A.x, B.y - A.y).normalize(); + vec2 BC = vec2(C.x - B.x, C.y - B.y).normalize(); + + float current_cross_product = crossProduct(AB, BC); + + if (fabs(current_cross_product) < COLINEAR_EPSILON) { + continue; // Skip collinear points + } + + if (sign == 0) { + sign = (current_cross_product > 0) ? 1 : -1; + } + else { + if ((current_cross_product > 0 && sign == -1) || (current_cross_product < 0 && sign == 1)) { + return false; + } + } + } + + return true; +} + +void Polygon3D::removeDuplicateVerts() { + vector newVerts; + + int sz = localVerts.size(); + for (int i = 0; i < sz; i++) { + int last = (i + (sz - 1)) % sz; + + if (!vec3Equal(verts[i], verts[last], SAME_VERT_EPSILON)) + newVerts.push_back(verts[i]); + } + + if (verts.size() != newVerts.size()) { + logf("Removed %d duplicate verts\n", verts.size() - newVerts.size()); + verts = newVerts; + init(); + } +} + +void Polygon3D::removeColinearVerts() { + vector newVerts; + + if (verts.size() < 3) { + logf("Not enough verts to remove colinear ones\n"); + return; + } + + int sz = localVerts.size(); + for (int i = 0; i < sz; i++) { + const vec2& A = localVerts[(i + (sz-1)) % sz]; + const vec2& B = localVerts[i]; + const vec2& C = localVerts[(i + 1) % sz]; + + vec2 AB = vec2(B.x - A.x, B.y - A.y).normalize(); + vec2 BC = vec2(C.x - B.x, C.y - B.y).normalize(); + float cross = crossProduct(AB, BC); + + if (fabs(cross) >= COLINEAR_EPSILON) { + newVerts.push_back(verts[i]); + } + } + + if (verts.size() != newVerts.size()) { + //logf("Removed %d colinear verts\n", verts.size() - newVerts.size()); + verts = newVerts; + init(); + } +} + +Polygon3D Polygon3D::merge(const Polygon3D& mergePoly) { + vector mergedVerts; + + float epsilon = 1.0f; + + if (fabs(fdist - mergePoly.fdist) > epsilon || dotProduct(plane_z, mergePoly.plane_z) < 0.99f) + return mergedVerts; // faces not coplaner + + int sharedEdges = 0; + int commonEdgeStart1 = -1, commonEdgeEnd1 = -1; + int commonEdgeStart2 = -1, commonEdgeEnd2 = -1; + for (int i = 0; i < verts.size(); i++) { + const vec3& e1 = verts[i]; + const vec3& e2 = verts[(i + 1) % verts.size()]; + + for (int k = 0; k < mergePoly.verts.size(); k++) { + const vec3& other1 = mergePoly.verts[k]; + const vec3& other2 = mergePoly.verts[(k + 1) % mergePoly.verts.size()]; + + if ((vec3Equal(e1, other1, epsilon) && vec3Equal(e2, other2, epsilon)) + || (vec3Equal(e1, other2, epsilon) && vec3Equal(e2, other1, epsilon))) { + commonEdgeStart1 = i; + commonEdgeEnd1 = (i + 1) % verts.size(); + commonEdgeStart2 = k; + commonEdgeEnd2 = (k + 1) % mergePoly.verts.size(); + sharedEdges++; + } + } + } + + if (sharedEdges == 0) + return Polygon3D(); + if (sharedEdges > 1) { + //logf("More than 1 shared edge for merge!\n"); + return Polygon3D(); + } + + mergedVerts.reserve(verts.size() + mergePoly.verts.size() - 2); + for (int i = commonEdgeEnd1; i != commonEdgeStart1; i = (i + 1) % verts.size()) { + mergedVerts.push_back(verts[i]); + } + for (int i = commonEdgeEnd2; i != commonEdgeStart2; i = (i + 1) % mergePoly.verts.size()) { + mergedVerts.push_back(mergePoly.verts[i]); + } + + Polygon3D newPoly(mergedVerts); + + if (!newPoly.isConvex()) { + return Polygon3D(); + } + newPoly.removeColinearVerts(); + + return newPoly; } \ No newline at end of file diff --git a/src/util/Polygon3D.h b/src/util/Polygon3D.h index b2c9465b..95650db4 100644 --- a/src/util/Polygon3D.h +++ b/src/util/Polygon3D.h @@ -8,6 +8,8 @@ struct Line2D { vec2 end; vec2 dir; + Line2D() {} + Line2D(vec2 start, vec2 end); // distance between this point and the axis of this line @@ -34,7 +36,7 @@ class Polygon3D { vec3 plane_x; vec3 plane_y; vec3 plane_z; // plane normal - float fdist; + float fdist = 0; std::vector verts; std::vector localVerts; // points relative to the plane orientation @@ -66,6 +68,11 @@ class Polygon3D { float distance(const vec3& p); + bool isConvex(); + + void removeColinearVerts(); + void removeDuplicateVerts(); + // returns split polys for first edge on cutPoly that contacts this polygon // multiple intersections (overlapping polys) are not handled // returns empty on no intersection @@ -77,6 +84,10 @@ class Polygon3D { // returns 2 new convex polygons otherwise vector> cut(Line2D cutLine); + // returns merged polygon vertices if polys are coplaner and share an edge + // otherwise returns an empty vector + Polygon3D merge(const Polygon3D& mergePoly); + // is point inside this polygon? Coordinates are in world space. // Points within EPSILON of an edge are not inside. bool isInside(vec3 p); diff --git a/src/util/util.cpp b/src/util/util.cpp index 267a3ff6..4bede66c 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -569,7 +569,7 @@ vector getTriangularVerts(vector& verts) { if (verts[i] != verts[i0] && verts[i] != verts[i1]) { vec3 ab = (verts[i1] - verts[i0]).normalize(); vec3 ac = (verts[i] - verts[i0]).normalize(); - if (fabs(dotProduct(ab, ac)) == 1) { + if (fabs(dotProduct(ab, ac)) > 0.99f) { continue; }