From 079d5f2b9979514c185e933926ee26dc9da170dd Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 19 Apr 2021 14:39:38 +0200 Subject: [PATCH] Finished multipolygon assembly --- src/main.cpp | 8 +- src/multipolygon.cpp | 343 +++++++++++++++++++++++++++++++++---------- 2 files changed, 271 insertions(+), 80 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c3042b9..4d6c1b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -178,10 +178,10 @@ int main(int argc, char** argv) multipolygon.Draw(renderer); } - //for (Area& area : buildings) - //{ - // filledPolygonRGBA(renderer, area.x, area.y, area.length, area.r, area.g, area.b, 255); - //} + for (Area& area : buildings) + { + filledPolygonRGBA(renderer, area.x, area.y, area.length, area.r, area.g, area.b, 255); + } for (Highway& highway : highways) { diff --git a/src/multipolygon.cpp b/src/multipolygon.cpp index 648b70e..52196c4 100644 --- a/src/multipolygon.cpp +++ b/src/multipolygon.cpp @@ -23,6 +23,8 @@ struct TriangulationData { struct Ring { osmp::Nodes nodes; bool inner; + int index; + bool hole = false; }; struct RingGroup { @@ -40,12 +42,15 @@ inline double Map(double A, double B, double a, double b, double x) [[nodiscard]] bool Intersect(const osmp::Node& p1, const osmp::Node& p2, const osmp::Node& q1, const osmp::Node& q2); [[nodiscard]] bool SelfIntersecting(const Ring& ring); -[[nodiscard]] bool BuildRing(Ring& ring, osmp::MemberWays& unassigned); +[[nodiscard]] bool BuildRing(Ring& ring, osmp::MemberWays& unassigned, int ringCount); [[nodiscard]] bool AssignRings(std::vector& rings, const osmp::MemberWays& members); +void FindAllContainedRings(const std::vector& containmentMatrix, int container, int numRings, std::vector& buffer); +void FindAllContainedRingsThatArentContainedByUnusedRings(const std::vector& containmentMatrix, int container, int numRings, const std::vector& unusedRings, std::vector& buffer); +[[nodiscard]] int FindUncontainedRing(const std::vector& containmentMatrix, int rings, const std::vector& unusedRings); [[nodiscard]] bool PointInsideRing(const Ring& ring, const osmp::Node& point); [[nodiscard]] bool IsRingContained(const Ring& r1, const Ring& r2); -[[nodiscard]] bool GroupRings(std::vector& ringGroup, const std::vector& rings); +[[nodiscard]] bool GroupRings(std::vector& ringGroup, std::vector& rings); Multipolygon::Multipolygon(const osmp::Relation& relation, int width, int height, const osmp::Bounds& bounds) : r(255), g(0), b(255), visible(true), rendering(RenderType::FILL), id(relation->id) @@ -63,22 +68,110 @@ Multipolygon::Multipolygon(const osmp::Relation& relation, int width, int height std::cerr << "Assigning rings has failed for multipolygon " << id << std::endl; } - std::vector ringGroup; - GroupRings(ringGroup, rings); + std::vector ringGroups; + GroupRings(ringGroups, rings); - // TODO: Temporarily render rings: - for (const Ring& ring : rings) + char* triSwitches = "zpNBQ"; + for (const RingGroup& ringGroup : ringGroups) { - Polygon polygon; - for (const osmp::Node& node : ring.nodes) + TriangulationData td; + + bool valid = true; + for (const Ring& ring : ringGroup.rings) { - polygon.vertices.push_back({ - Map(bounds.minlon, bounds.maxlon, 0, width, node->lon), - height - Map(bounds.minlat, bounds.maxlat, 0, height, node->lat) - }); + std::vector vertices; + for (const osmp::Node& node : ring.nodes) { + double x = Map(bounds.minlon, bounds.maxlon, 0, width, node->lon); + double y = height - Map(bounds.minlat, bounds.maxlat, 0, height, node->lat); + + vertices.push_back(x); + vertices.push_back(y); + } + + int segment = td.vertices.size() / 2; + for (int i = 0; i < vertices.size() / 2; i += 1) { + td.segments.push_back(segment + i); + td.segments.push_back(segment + i + 1); + } + td.segments.back() = td.vertices.size() / 2; + + td.vertices.insert(td.vertices.end(), vertices.begin(), vertices.end()); + + if (ring.hole) { + double holeX = 0.0f; + double holeY = 0.0f; + for (int i = 0; i < vertices.size(); i += 2) + { + holeX += vertices[i]; + holeY += vertices[i + 1]; + } + + holeX /= vertices.size() / 2; + holeY /= vertices.size() / 2; + + td.holes.push_back(holeX); + td.holes.push_back(holeY); + } } - polygons.push_back(polygon); + // TODO: Find better way to check for duplicates + for (int i = 0; i < td.vertices.size(); i += 2) { + for (int j = 0; j < td.vertices.size(); j += 2) { + if (i == j) continue; + + if (td.vertices[i] == td.vertices[j] && td.vertices[i + 1] == td.vertices[j + 1]) + { + valid = false; + break; + } + } + } + + if (valid) + { + triangulateio in; + + in.numberofpoints = td.vertices.size() / 2; + in.pointlist = td.vertices.data(); + in.pointmarkerlist = NULL; + + in.numberofpointattributes = 0; + in.numberofpointattributes = NULL; + + in.numberofholes = td.holes.size() / 2; + in.holelist = td.holes.data(); + + in.numberofsegments = td.segments.size() / 2; + in.segmentlist = td.segments.data(); + in.segmentmarkerlist = NULL; + + in.numberofregions = 0; + in.regionlist = NULL; + + triangulateio out; + out.pointlist = NULL; + out.pointmarkerlist = NULL; + out.trianglelist = NULL; + out.segmentlist = NULL; + out.segmentmarkerlist = NULL; + + triangulate(triSwitches, &in, &out, NULL); + + polygons.push_back({}); + for (int i = 0; i < in.numberofpoints * 2; i += 2) { + polygons.back().vertices.push_back({ in.pointlist[i], in.pointlist[i + 1] }); + // polygons.back().vertices.push_back(in.pointlist[i + 1]); + } + for (int i = 0; i < out.numberoftriangles * 3; i++) { + polygons.back().indices.push_back(out.trianglelist[i]); + } + for (int i = 0; i < in.numberofsegments * 2; i++) { + polygons.back().segments.push_back(in.segmentlist[i]); + } + + trifree(out.trianglelist); + trifree(out.segmentlist); + } } // TODO: Make a color map @@ -469,67 +562,67 @@ void Multipolygon::Draw(SDL_Renderer* renderer) if (!visible) return; - for (const Polygon& polygon : polygons) { - for (auto it = polygon.vertices.begin(); it != polygon.vertices.end() - 1; it++) { - thickLineRGBA(renderer, - it->x, it->y, - (it+1)->x, (it+1)->y, - 2, - r, g, b, 255 - ); - } - } - //for (const Polygon& polygon : polygons) { - // switch(rendering) - // { - // case RenderType::FILL: - // for (int i = 0; i < polygon.indices.size(); i += 3) // Be a graphics card - // { - - // filledTrigonRGBA(renderer, - // polygon.vertices[polygon.indices[i + 0]].x, polygon.vertices[polygon.indices[i + 0]].y, - // polygon.vertices[polygon.indices[i + 1]].x, polygon.vertices[polygon.indices[i + 1]].y, - // polygon.vertices[polygon.indices[i + 2]].x, polygon.vertices[polygon.indices[i + 2]].y, - // r, g, b, 255 + // for (auto it = polygon.vertices.begin(); it != polygon.vertices.end() - 1; it++) { + // thickLineRGBA(renderer, + // it->x, it->y, + // (it+1)->x, (it+1)->y, + // 2, + // r, g, b, 255 // ); - // } - // break; - - // case RenderType::OUTLINE: - // for(int i = 0; i < polygon.segments.size(); i += 2) - // { - // thickLineRGBA(renderer, - // polygon.vertices[polygon.segments[i + 0]].x, polygon.vertices[polygon.segments[i + 0]].y, - // polygon.vertices[polygon.segments[i + 1]].x, polygon.vertices[polygon.segments[i + 1]].y, - // 5, r, g, b, 255 - // ); - // } - // break; - - // case RenderType::INDOOR: - // for (int i = 0; i < polygon.indices.size(); i += 3) // Be a graphics card - // { - - // filledTrigonRGBA(renderer, - // polygon.vertices[polygon.indices[i + 0]].x, polygon.vertices[polygon.indices[i + 0]].y, - // polygon.vertices[polygon.indices[i + 1]].x, polygon.vertices[polygon.indices[i + 1]].y, - // polygon.vertices[polygon.indices[i + 2]].x, polygon.vertices[polygon.indices[i + 2]].y, - // r, g, b, 255 - // ); - // } - - // for (int i = 0; i < polygon.segments.size(); i += 2) - // { - // lineRGBA(renderer, - // polygon.vertices[polygon.segments[i + 0]].x, polygon.vertices[polygon.segments[i + 0]].y, - // polygon.vertices[polygon.segments[i + 1]].x, polygon.vertices[polygon.segments[i + 1]].y, - // 10, 10, 15, 255 - // ); - // } - // break; // } //} + + for (const Polygon& polygon : polygons) { + switch(rendering) + { + case RenderType::FILL: + for (int i = 0; i < polygon.indices.size(); i += 3) // Be a graphics card + { + + filledTrigonRGBA(renderer, + polygon.vertices[polygon.indices[i + 0]].x, polygon.vertices[polygon.indices[i + 0]].y, + polygon.vertices[polygon.indices[i + 1]].x, polygon.vertices[polygon.indices[i + 1]].y, + polygon.vertices[polygon.indices[i + 2]].x, polygon.vertices[polygon.indices[i + 2]].y, + r, g, b, 255 + ); + } + break; + + case RenderType::OUTLINE: + for(int i = 0; i < polygon.segments.size(); i += 2) + { + thickLineRGBA(renderer, + polygon.vertices[polygon.segments[i + 0]].x, polygon.vertices[polygon.segments[i + 0]].y, + polygon.vertices[polygon.segments[i + 1]].x, polygon.vertices[polygon.segments[i + 1]].y, + 5, r, g, b, 255 + ); + } + break; + + case RenderType::INDOOR: + for (int i = 0; i < polygon.indices.size(); i += 3) // Be a graphics card + { + + filledTrigonRGBA(renderer, + polygon.vertices[polygon.indices[i + 0]].x, polygon.vertices[polygon.indices[i + 0]].y, + polygon.vertices[polygon.indices[i + 1]].x, polygon.vertices[polygon.indices[i + 1]].y, + polygon.vertices[polygon.indices[i + 2]].x, polygon.vertices[polygon.indices[i + 2]].y, + r, g, b, 255 + ); + } + + for (int i = 0; i < polygon.segments.size(); i += 2) + { + lineRGBA(renderer, + polygon.vertices[polygon.segments[i + 0]].x, polygon.vertices[polygon.segments[i + 0]].y, + polygon.vertices[polygon.segments[i + 1]].x, polygon.vertices[polygon.segments[i + 1]].y, + 10, 10, 15, 255 + ); + } + break; + } + } } bool Intersect(double p0_x, double p0_y, double p1_x, double p1_y, double p2_x, double p2_y, double p3_x, double p3_y) @@ -600,13 +693,13 @@ bool SelfIntersecting(const Ring& ring) return false; } -bool BuildRing(Ring& ring, osmp::MemberWays& unassigned) +bool BuildRing(Ring& ring, osmp::MemberWays& unassigned, int ringCount) { const osmp::MemberWays original = unassigned; // RA-2 int attempts = 0; - ring = Ring{ unassigned[attempts].way->GetNodes(), unassigned[attempts].role == "inner" }; + ring = Ring{ unassigned[attempts].way->GetNodes(), unassigned[attempts].role == "inner", ringCount }; unassigned.erase(unassigned.begin() + attempts); RA3: @@ -620,7 +713,7 @@ RA3: if (unassigned.size() == attempts) return false; - ring = Ring{ unassigned[attempts].way->GetNodes(), unassigned[attempts].role == "inner" }; + ring = Ring{ unassigned[attempts].way->GetNodes(), unassigned[attempts].role == "inner", ringCount }; goto RA3; } else @@ -658,11 +751,14 @@ bool AssignRings(std::vector& rings, const osmp::MemberWays& members) { // Ring assignment osmp::MemberWays unassigned = members; + int ringCount = 0; while (!unassigned.empty()) { rings.push_back({}); - if (!BuildRing(rings.back(), unassigned) || rings.size() > members.size()) + if (!BuildRing(rings.back(), unassigned, ringCount) || rings.size() > members.size()) return false; + + ringCount++; } return true; @@ -673,6 +769,61 @@ bool cmp(const osmp::Node& a, const osmp::Node& b) return (a->lon < b->lon); } +void FindAllContainedRings(const std::vector& containmentMatrix, int container, int numRings, std::vector& buffer) +{ + buffer.clear(); + for (int j = 0; j < numRings; j++) { + if (containmentMatrix[INDEXOF(container, j, numRings)]) + buffer.push_back(j); + } +} + +void FindAllContainedRingsThatArentContainedByUnusedRings(const std::vector& containmentMatrix, int container, int numRings, const std::vector& unusedRings, std::vector& buffer) +{ + FindAllContainedRings(containmentMatrix, container, numRings, buffer); + + for (auto j = buffer.begin(); j != buffer.end(); ) { + bool foundRing = false; + for (const Ring& ring : unusedRings) { + if (containmentMatrix[INDEXOF(ring.index, *j, numRings)]) { + foundRing = true; + break; + } + } + + if(foundRing) + j = buffer.erase(j); + else + ++j; + } +} + +bool Compare(const Ring& ring, int index) { + return (ring.index == index); +} + +int FindUncontainedRing(const std::vector& containmentMatrix, int rings, const std::vector& unusedRings) +{ + for (int j = 0; j < rings; j++) { + if (std::find_if(unusedRings.begin(), unusedRings.end(), [j](const Ring& ring) { return (ring.index == j); }) == unusedRings.end()) + continue; + + bool isContained = false; + for (int i = 0; i < rings; i++) { + if (containmentMatrix[INDEXOF(i, j, rings)]) + { + isContained = true; + break; + } + } + + if (!isContained) + return j; + } + + return -1; +} + bool PointInsideRing(const Ring& ring, const osmp::Node& point) { const osmp::Node& rightestNode = *(std::max_element(ring.nodes.begin(), ring.nodes.end(), cmp)); @@ -714,8 +865,10 @@ bool IsRingContained(const Ring& r1, const Ring& r2) return false; } -bool GroupRings(std::vector& ringGroups, const std::vector& rings) +bool GroupRings(std::vector& ringGroups, std::vector& rings) { + const std::vector original = rings; + //RG-1 int ringNum = rings.size(); std::vector containmentMatrix(ringNum * ringNum); @@ -732,7 +885,45 @@ bool GroupRings(std::vector& ringGroups, const std::vector& rin containmentMatrix[INDEXOF(i, j, ringNum)] = IsRingContained(rings[i], rings[j]); } } - __debugbreak(); + + // RG-2 / RG-3 + while (!rings.empty()) // TODO: Make this time out + { + int uncontainedRing = FindUncontainedRing(containmentMatrix, ringNum, rings); + if (uncontainedRing == -1) { + std::cerr << "Failed to find uncontained ring in step RG-2" << std::endl; + return false; + } + auto it = std::find_if(rings.begin(), rings.end(), [uncontainedRing](const Ring& ring) { return (ring.index == uncontainedRing); }); + if (it == rings.end()) + { + std::cerr << "Uncontained Ring is out of range" << std::endl; + return false; + } + + ringGroups.push_back({ {*it} }); + rings.erase(it); + + + // RG-4 + std::vector containedRings; + FindAllContainedRingsThatArentContainedByUnusedRings(containmentMatrix, uncontainedRing, ringNum, rings, containedRings); + + for (auto it = rings.begin(); it != rings.end(); ) { + if (std::find(containedRings.begin(), containedRings.end(), it->index) != containedRings.end()) { + ringGroups.back().rings.push_back(*it); + ringGroups.back().rings.back().hole = true; + it = rings.erase(it); + } + else { + it++; + } + } + + // TODO: RG-5 / RG-6 will be left out for now as they're optional. + // At least RG-6 should be implemented because not doing so might crash Triangle + + } return true; }