From 94a340ec99b53f67f6877031ce431c0a639e53f9 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Tue, 13 Aug 2024 17:54:52 +0200 Subject: [PATCH 01/36] Add basic patterns: island, bank; and their cover radius --- cartocrow/CMakeLists.txt | 1 + cartocrow/core/core.h | 5 + .../symmetric_difference.cpp | 10 +- cartocrow/renderer/geometry_widget.cpp | 2 +- cartocrow/renderer/ipe_renderer.cpp | 4 +- cartocrow/simplesets/CMakeLists.txt | 23 ++++ cartocrow/simplesets/bank.cpp | 89 +++++++++++++ cartocrow/simplesets/bank.h | 35 +++++ cartocrow/simplesets/cat_point.h | 14 ++ cartocrow/simplesets/cropped_voronoi.h | 126 ++++++++++++++++++ cartocrow/simplesets/island.cpp | 83 ++++++++++++ cartocrow/simplesets/island.h | 27 ++++ cartocrow/simplesets/pattern.cpp | 2 + cartocrow/simplesets/pattern.h | 21 +++ cartocrow/simplesets/point_voronoi_helpers.h | 55 ++++++++ cartocrow/simplesets/settings.h | 56 ++++++++ cartocrow/simplesets/types.h | 10 ++ demos/CMakeLists.txt | 3 +- demos/simplesets/CMakeLists.txt | 18 +++ demos/simplesets/simplesets_demo.cpp | 77 +++++++++++ demos/simplesets/simplesets_demo.h | 44 ++++++ 21 files changed, 696 insertions(+), 9 deletions(-) create mode 100644 cartocrow/simplesets/CMakeLists.txt create mode 100644 cartocrow/simplesets/bank.cpp create mode 100644 cartocrow/simplesets/bank.h create mode 100644 cartocrow/simplesets/cat_point.h create mode 100644 cartocrow/simplesets/cropped_voronoi.h create mode 100644 cartocrow/simplesets/island.cpp create mode 100644 cartocrow/simplesets/island.h create mode 100644 cartocrow/simplesets/pattern.cpp create mode 100644 cartocrow/simplesets/pattern.h create mode 100644 cartocrow/simplesets/point_voronoi_helpers.h create mode 100644 cartocrow/simplesets/settings.h create mode 100644 cartocrow/simplesets/types.h create mode 100644 demos/simplesets/CMakeLists.txt create mode 100644 demos/simplesets/simplesets_demo.cpp create mode 100644 demos/simplesets/simplesets_demo.h diff --git a/cartocrow/CMakeLists.txt b/cartocrow/CMakeLists.txt index 8d8b5b5e..686ae4a6 100644 --- a/cartocrow/CMakeLists.txt +++ b/cartocrow/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(necklace_map) add_subdirectory(simplification) add_subdirectory(flow_map) add_subdirectory(isoline_simplification) +add_subdirectory(simplesets) add_library(cartocrow_lib INTERFACE) target_link_libraries( diff --git a/cartocrow/core/core.h b/cartocrow/core/core.h index e45af1df..5fb3bef9 100644 --- a/cartocrow/core/core.h +++ b/cartocrow/core/core.h @@ -28,6 +28,7 @@ Created by tvl (t.vanlankveld@esciencecenter.nl) on 07-11-2019 #include #include #include +#include #include #include #include @@ -42,6 +43,8 @@ namespace cartocrow { /// CGAL kernel for exact constructions (uses an exact number type). using Exact = CGAL::Exact_predicates_exact_constructions_kernel; +/// CGAL kernel for exact constructions (uses an exact number type that supports the square root operation). +using ExactWithSqrt = CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt; /// CGAL kernel for inexact constructions. using Inexact = CGAL::Exact_predicates_inexact_constructions_kernel; @@ -60,6 +63,8 @@ template using Line = CGAL::Line_2; template using Segment = CGAL::Segment_2; /// A ray in the plane. See \ref CGAL::Ray_2. template using Ray = CGAL::Ray_2; +/// An axis-aligned rectangle in the plane. See \ref CGAL::Iso_rectangle_2. +template using Rectangle = CGAL::Iso_rectangle_2; /// A polygon in the plane. See \ref CGAL::Polygon_2. template using Polygon = CGAL::Polygon_2; diff --git a/cartocrow/isoline_simplification/symmetric_difference.cpp b/cartocrow/isoline_simplification/symmetric_difference.cpp index 69fda924..fb5b096f 100644 --- a/cartocrow/isoline_simplification/symmetric_difference.cpp +++ b/cartocrow/isoline_simplification/symmetric_difference.cpp @@ -30,7 +30,7 @@ enum Side { TOP = 3, }; -Side closest_side(const Point& point, const Exact::Iso_rectangle_2& bb) { +Side closest_side(const Point& point, const Rectangle& bb) { std::vector dist({ to_double(point.x()) - to_double(bb.xmin()), to_double(point.y()) - to_double(bb.ymin()), to_double(bb.xmax()) - to_double(point.x()), to_double(bb.ymax()) - to_double(point.y()) }); auto it = std::min_element(dist.begin(), dist.end()); @@ -50,7 +50,7 @@ Vector side_direction(const Side& side) { } } -Point proj_on_side(Point p, Side side, const Exact::Iso_rectangle_2& rect) { +Point proj_on_side(Point p, Side side, const Rectangle& rect) { switch (side) { case LEFT: return { rect.xmin(), p.y() }; @@ -63,7 +63,7 @@ Point proj_on_side(Point p, Side side, const Exact::Iso_rectangle_ } } -Point corner(const Side& side1, const Side& side2, const Exact::Iso_rectangle_2& rect) { +Point corner(const Side& side1, const Side& side2, const Rectangle& rect) { if (side1 > side2) return corner(side2, side1, rect); auto dist = abs(side1 - side2); if (dist == 1) { @@ -77,7 +77,7 @@ Side next_side(const Side& side) { return static_cast((side + 1) % 4); } -Polygon close_isoline(const Isoline& isoline, Exact::Iso_rectangle_2& bb, Side source_side, Side target_side) { +Polygon close_isoline(const Isoline& isoline, Rectangle& bb, Side source_side, Side target_side) { std::vector> points; std::transform(isoline.m_points.begin(), isoline.m_points.end(), std::back_inserter(points), [](Point p) { return Point(p.x(), p.y()); }); @@ -133,7 +133,7 @@ double symmetric_difference(const Isoline& original, const Isoline& simpli std::transform(simplified.m_points.begin(), simplified.m_points.end(), std::back_inserter(all_points), [](Point p) { return Point(p.x(), p.y()); }); - Exact::Iso_rectangle_2 bb = CGAL::bounding_box(all_points.begin(), all_points.end()); + Rectangle bb = CGAL::bounding_box(all_points.begin(), all_points.end()); auto source_side = closest_side( Point(original.m_points.front().x(), original.m_points.front().y()), bb); diff --git a/cartocrow/renderer/geometry_widget.cpp b/cartocrow/renderer/geometry_widget.cpp index c0b429f1..05b4c833 100644 --- a/cartocrow/renderer/geometry_widget.cpp +++ b/cartocrow/renderer/geometry_widget.cpp @@ -554,7 +554,7 @@ void GeometryWidget::draw(const BezierSpline& s) { void GeometryWidget::draw(const Ray& r) { Box bounds = inverseConvertBox(rect()); - auto result = intersection(r, CGAL::Iso_rectangle_2(Point(bounds.xmin(), bounds.ymin()), Point(bounds.xmax(), bounds.ymax()))); + auto result = intersection(r, Rectangle(Point(bounds.xmin(), bounds.ymin()), Point(bounds.xmax(), bounds.ymax()))); if (result) { if (const Segment* s = boost::get>(&*result)) { int oldMode = m_style.m_mode; diff --git a/cartocrow/renderer/ipe_renderer.cpp b/cartocrow/renderer/ipe_renderer.cpp index 8ae25e82..96e91843 100644 --- a/cartocrow/renderer/ipe_renderer.cpp +++ b/cartocrow/renderer/ipe_renderer.cpp @@ -116,7 +116,7 @@ void IpeRenderer::draw(const Segment& s) { void IpeRenderer::draw(const Line& l) { // Crop to document size - auto bounds = CGAL::Iso_rectangle_2(CGAL::ORIGIN, Point(1000.0, 1000.0)); + auto bounds = Rectangle(CGAL::ORIGIN, Point(1000.0, 1000.0)); auto result = intersection(l, bounds); if (result) { if (const Segment* s = boost::get>(&*result)) { @@ -130,7 +130,7 @@ void IpeRenderer::draw(const Line& l) { void IpeRenderer::draw(const Ray& r) { // Crop to document size - auto bounds = CGAL::Iso_rectangle_2(CGAL::ORIGIN, Point(1000.0, 1000.0)); + auto bounds = Rectangle(CGAL::ORIGIN, Point(1000.0, 1000.0)); auto result = intersection(r, bounds); if (result) { if (const Segment* s = boost::get>(&*result)) { diff --git a/cartocrow/simplesets/CMakeLists.txt b/cartocrow/simplesets/CMakeLists.txt new file mode 100644 index 00000000..41f8d88a --- /dev/null +++ b/cartocrow/simplesets/CMakeLists.txt @@ -0,0 +1,23 @@ +set(SOURCES + pattern.cpp + island.cpp + bank.cpp +) +set(HEADERS + pattern.h + types.h + settings.h + cat_point.h + island.h + bank.h + point_voronoi_helpers.h + cropped_voronoi.h +) + +add_library(simplesets ${SOURCES}) +target_link_libraries(simplesets + PUBLIC core +) + +cartocrow_install_module(simplesets) +install(FILES ${HEADERS} DESTINATION ${CARTOCROW_INSTALL_DIR}/simplesets) diff --git a/cartocrow/simplesets/bank.cpp b/cartocrow/simplesets/bank.cpp new file mode 100644 index 00000000..fe7f647e --- /dev/null +++ b/cartocrow/simplesets/bank.cpp @@ -0,0 +1,89 @@ +#include "bank.h" + +namespace cartocrow::simplesets { +Bank::Bank(std::vector catPoints): m_catPoints(std::move(catPoints)) { + // Store the point positions separately, sometimes only the positions are needed. + std::transform(m_catPoints.begin(), m_catPoints.end(), std::back_inserter(m_points), [](const CatPoint& cp) { + return cp.point; + }); + + // Compute the cover radius + std::optional> maxSquaredDistance; + for (int i = 0; i < m_points.size() - 1; i++) { + auto p = m_points[i]; + auto q = m_points[i+1]; + auto dist = squared_distance(p, q); + if (!maxSquaredDistance.has_value() || dist > *maxSquaredDistance) { + maxSquaredDistance = dist; + } + } + m_coverRadius = sqrt(*maxSquaredDistance) / 2; + + // Compute bends + computeBends(); +} + +Number approximateAngleBetween(const Vector& exact_v, const Vector& exact_w) { + auto v = approximate(exact_v); + auto w = approximate(exact_w); + return acos((v * w) / (sqrt(v.squared_length()) * sqrt(w.squared_length()))); +} + +void Bank::computeBends() { + m_bends.clear(); + + std::optional orientation; + Number bendTotalAngle = 0; + Number bendMaxAngle = 0; + int startIndex = 0; + + for (int i = 0; i < m_points.size(); i++) { + if (i + 2 > m_points.size() - 1) break; + auto orient = CGAL::orientation(m_points[i], m_points[i+1], m_points[i+2]); + auto angle = approximateAngleBetween(m_points[i+1] - m_points[i], m_points[i+2] - m_points[i+1]); + if (orientation == -orient) { + // Switched orientation + m_bends.emplace_back(*orientation, bendMaxAngle, bendTotalAngle, startIndex, i+1); + orientation = orient; + bendTotalAngle = angle; + bendMaxAngle = angle; + startIndex = i; + } else { + orientation = orient; + bendTotalAngle += angle; + bendMaxAngle = std::max(bendMaxAngle, angle); + } + } + + if (orientation.has_value()) { + m_bends.emplace_back(*orientation, bendMaxAngle, bendTotalAngle, startIndex, static_cast(m_points.size()-1)); + } +} + +std::variant, Polygon> Bank::contour() { + return m_polyline; +} + +std::vector Bank::catPoints() { + return m_catPoints; +} + +bool Bank::isValid(GeneralSettings gs) { + bool inflectionIsFine = m_bends.size() <= gs.inflectionLimit; + bool anglesAreFine = true; + for (const auto& bend : m_bends) { + anglesAreFine = anglesAreFine && bend.maxAngle <= gs.maxTurnAngle; + } + bool totalAngleIsFine = true; + for (const auto& bend : m_bends) { + if (bend.totalAngle > gs.maxBendAngle) { + totalAngleIsFine = false; + } + } + return inflectionIsFine && anglesAreFine && totalAngleIsFine; +} + +Number Bank::coverRadius() { + return m_coverRadius; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/bank.h b/cartocrow/simplesets/bank.h new file mode 100644 index 00000000..f71ee8a3 --- /dev/null +++ b/cartocrow/simplesets/bank.h @@ -0,0 +1,35 @@ +#ifndef CARTOCROW_BANK_H +#define CARTOCROW_BANK_H + +#include "pattern.h" + +namespace cartocrow::simplesets { +struct Bend { + CGAL::Orientation orientation; + Number maxAngle; + Number totalAngle; + int startIndex; + int endIndex; +}; + +class Bank : public Pattern { + public: + Bank(std::vector catPoints); + + std::variant, Polygon> contour() override; + std::vector catPoints() override; + Number coverRadius() override; + bool isValid(GeneralSettings gs) override; + + private: + std::vector m_catPoints; + std::vector> m_points; + Number m_coverRadius; + Polygon m_polyline; + std::vector m_bends; + + void computeBends(); +}; +} + +#endif //CARTOCROW_BANK_H diff --git a/cartocrow/simplesets/cat_point.h b/cartocrow/simplesets/cat_point.h new file mode 100644 index 00000000..ba4436d9 --- /dev/null +++ b/cartocrow/simplesets/cat_point.h @@ -0,0 +1,14 @@ +#ifndef CARTOCROW_CAT_POINT_H +#define CARTOCROW_CAT_POINT_H + +#include "types.h" + +namespace cartocrow::simplesets { +/// Categorical point +struct CatPoint { + Point point; + int category; +}; +} + +#endif //CARTOCROW_CAT_POINT_H diff --git a/cartocrow/simplesets/cropped_voronoi.h b/cartocrow/simplesets/cropped_voronoi.h new file mode 100644 index 00000000..e38be87a --- /dev/null +++ b/cartocrow/simplesets/cropped_voronoi.h @@ -0,0 +1,126 @@ +#ifndef CARTOCROW_CROPPED_VORONOI_H +#define CARTOCROW_CROPPED_VORONOI_H + +#include "../core/core.h" +#include "types.h" +#include + +using namespace cartocrow; +using namespace cartocrow::simplesets; + +std::optional> intersectionConvex(const Polygon& polygon, const Ray& ray) { + auto sourceInside = polygon.has_on_bounded_side(ray.source()); + + std::vector> inters; + for (auto eit = polygon.edges_begin(); eit != polygon.edges_end(); eit++) { + auto edge = *eit; + auto obj = CGAL::intersection(ray, edge); + if (obj.has_value()) { + Segment seg; + Point point; + if (CGAL::assign(seg, obj)) { + return seg; + } else if (CGAL::assign(point, obj)) { + inters.push_back(point); + } + } + } + + assert(!(!sourceInside && inters.size() == 1)); + if (inters.empty()) { + return std::nullopt; + } else if (inters.size() == 2) { + return Segment(inters[0], inters[1]); + } else { + return Segment(ray.source(), inters[0]); + } +} + +std::optional> intersectionConvex(const Polygon& polygon, const Line& line) { + std::vector> inters; + for (auto eit = polygon.edges_begin(); eit != polygon.edges_end(); eit++) { + auto edge = *eit; + auto obj = CGAL::intersection(line, edge); + if (obj.has_value()) { + Segment seg; + Point point; + if (CGAL::assign(seg, obj)) { + return seg; + } else if (CGAL::assign(point, obj)) { + inters.push_back(point); + } + } + } + + assert(inters.size() != 1); + if (inters.size() == 2) { + return Segment(inters[0], inters[1]); + } else { + return std::nullopt; + } +} + +std::optional> intersectionConvex(const Polygon& polygon, const Segment& segment) { + auto sourceInside = polygon.has_on_bounded_side(segment.source()); + auto targetInside = polygon.has_on_bounded_side(segment.target()); + if (sourceInside && targetInside) { + return segment; + } + + std::vector> inters; + for (auto eit = polygon.edges_begin(); eit != polygon.edges_end(); eit++) { + auto edge = *eit; + auto obj = CGAL::intersection(segment, edge); + if (obj.has_value()) { + Segment seg; + Point point; + if (CGAL::assign(seg, obj)) { + return seg; + } else if (CGAL::assign(point, obj)) { + inters.push_back(point); + } else { + throw std::runtime_error("Intersection between two line segments is not a point nor a line segment"); + } + } + } + + if (!sourceInside && !targetInside) { + assert(inters.size() == 2); + // note that here, the orientation of the result segment may not be the same as the original segment. + return Segment(inters[0], inters[1]); + } + + if (sourceInside) { + assert(!targetInside); + assert(inters.size() == 1); + return Segment(segment.source(), inters[0]); + } + + assert(targetInside); + assert(inters.size() == 1); + return Segment(inters[0], segment.target()); +} + +// This part is adapted from: +// https://github.com/CGAL/cgal/blob/master/Triangulation_2/examples/Triangulation_2/print_cropped_voronoi.cpp +// which falls under the CC0 license. + +//A class to recover Voronoi diagram from stream. +//Rays, lines and segments are cropped to a polygon +struct Cropped_voronoi_from_delaunay{ + std::list, Segment>> m_cropped_vd; + Polygon m_clipper; + Cropped_voronoi_from_delaunay(const Polygon& clipper):m_clipper(clipper){} + template + void crop_and_extract_segment(const Point& site, const RSL& rsl){ + auto s = intersectionConvex(m_clipper, rsl); + if (s.has_value()) { + m_cropped_vd.push_back({site, *s}); + } + } + void operator<<(std::pair&, const Ray&> site_ray) { crop_and_extract_segment(site_ray.first, site_ray.second); } + void operator<<(std::pair&, const Line&> site_line) { crop_and_extract_segment(site_line.first, site_line.second); } + void operator<<(std::pair&, const Segment&> site_seg){ crop_and_extract_segment(site_seg.first, site_seg.second); } +}; + +#endif //CARTOCROW_CROPPED_VORONOI_H diff --git a/cartocrow/simplesets/island.cpp b/cartocrow/simplesets/island.cpp new file mode 100644 index 00000000..db1b36a7 --- /dev/null +++ b/cartocrow/simplesets/island.cpp @@ -0,0 +1,83 @@ +#include "island.h" +#include "point_voronoi_helpers.h" +#include +#include +#include +#include "cropped_voronoi.h" + +namespace cartocrow::simplesets { +Polygon convexHull(const std::vector>& points) { + std::vector> chPoints; + CGAL::convex_hull_2(points.begin(), points.end(), std::back_inserter(chPoints)); + return {chPoints.begin(), chPoints.end()}; +} + +std::optional> convexIntersection(const Polygon p, const Polygon q) { + std::vector> intersection_result; + CGAL::intersection(p, q, std::back_inserter(intersection_result)); + if (intersection_result.empty()) { + return std::nullopt; + } else { + return intersection_result[0].outer_boundary(); + } +} + +Number coverRadiusOfPoints(const std::vector>& points) { + DT dt; + dt.insert(points.begin(), points.end()); + + Rectangle bbox = CGAL::bounding_box(points.begin(), points.end()); + std::optional> squaredCoverRadius; + + auto hull = convexHull(points); + Cropped_voronoi_from_delaunay cropped_voronoi(hull); + + for (auto eit = dt.finite_edges_begin(); eit != dt.finite_edges_end(); ++eit) { + CGAL::Object o = dt.dual(eit); + Line l; + Ray r; + Segment s; + auto site = eit->first->vertex(dt.cw(eit->second))->point(); + if(CGAL::assign(s,o)) cropped_voronoi << std::pair(site, s); + if(CGAL::assign(r,o)) cropped_voronoi << std::pair(site, r); + if(CGAL::assign(l,o)) cropped_voronoi << std::pair(site, l); + } + + for (const auto& [site, seg] : cropped_voronoi.m_cropped_vd) { + for (const auto& v : {seg.source(), seg.target()}) { + auto d = squared_distance(v, site); + if (!squaredCoverRadius.has_value() || d > *squaredCoverRadius) { + squaredCoverRadius = d; + } + } + } + + return sqrt(*squaredCoverRadius); +} + +Island::Island(std::vector catPoints): m_catPoints(std::move(catPoints)) { + // Store the point positions separately, sometimes only the positions are needed. + std::transform(m_catPoints.begin(), m_catPoints.end(), std::back_inserter(m_points), [](const CatPoint& cp) { + return cp.point; + }); + + m_coverRadius = coverRadiusOfPoints(m_points); + m_polygon = convexHull(m_points); +} + +std::variant, Polygon> Island::contour() { + return m_polygon; +} + +std::vector Island::catPoints() { + return m_catPoints; +} + +bool Island::isValid(cartocrow::simplesets::GeneralSettings gs) { + return true; +} + +Number Island::coverRadius() { + return m_coverRadius; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/island.h b/cartocrow/simplesets/island.h new file mode 100644 index 00000000..42020406 --- /dev/null +++ b/cartocrow/simplesets/island.h @@ -0,0 +1,27 @@ +#ifndef CARTOCROW_ISLAND_H +#define CARTOCROW_ISLAND_H + +#include "pattern.h" +#include + +namespace cartocrow::simplesets { +typedef CGAL::Delaunay_triangulation_2 DT; + +class Island : public Pattern { + public: + Island(std::vector catPoints); + + std::variant, Polygon> contour() override; + std::vector catPoints() override; + Number coverRadius() override; + bool isValid(GeneralSettings gs) override; + + private: + std::vector m_catPoints; + std::vector> m_points; + Number m_coverRadius; + Polygon m_polygon; +}; +} + +#endif //CARTOCROW_ISLAND_H diff --git a/cartocrow/simplesets/pattern.cpp b/cartocrow/simplesets/pattern.cpp new file mode 100644 index 00000000..f282a422 --- /dev/null +++ b/cartocrow/simplesets/pattern.cpp @@ -0,0 +1,2 @@ +#include "pattern.h" + diff --git a/cartocrow/simplesets/pattern.h b/cartocrow/simplesets/pattern.h new file mode 100644 index 00000000..ddb307ed --- /dev/null +++ b/cartocrow/simplesets/pattern.h @@ -0,0 +1,21 @@ +#ifndef CARTOCROW_PATTERN_H +#define CARTOCROW_PATTERN_H + +#include "../core/core.h" +#include "../core/polyline.h" +#include +#include "types.h" +#include "cat_point.h" +#include "settings.h" + +namespace cartocrow::simplesets { +class Pattern { + public: + virtual std::variant, Polygon> contour() = 0; + virtual std::vector catPoints() = 0; + virtual Number coverRadius() = 0; + virtual bool isValid(GeneralSettings gs) = 0; +}; +} + +#endif //CARTOCROW_PATTERN_H diff --git a/cartocrow/simplesets/point_voronoi_helpers.h b/cartocrow/simplesets/point_voronoi_helpers.h new file mode 100644 index 00000000..addb9043 --- /dev/null +++ b/cartocrow/simplesets/point_voronoi_helpers.h @@ -0,0 +1,55 @@ +#ifndef CARTOCROW_POINT_VORONOI_HELPERS_H +#define CARTOCROW_POINT_VORONOI_HELPERS_H + +#include "../core/core.h" +#include +#include +#include +#include +#include + +using namespace cartocrow; +//typedef CGAL::Delaunay_triangulation_2 DT; +//typedef CGAL::Delaunay_triangulation_adaptation_traits_2
AT; +//typedef CGAL::Delaunay_triangulation_caching_degeneracy_removal_policy_2
AP; +//typedef CGAL::Voronoi_diagram_2 VD; + +template, + class AT = CGAL::Delaunay_triangulation_adaptation_traits_2
, + class AP = CGAL::Delaunay_triangulation_caching_degeneracy_removal_policy_2
, + class VD = CGAL::Voronoi_diagram_2> +Polygon face_to_polygon(VD vd, typename VD::Face& face, Rectangle bbox) { + std::vector> pts; + auto circ_start = face.ccb(); + auto circ = circ_start; + + do { + auto he = *circ; + // Cannot easily access the geometry of the halfedge, so go via dual + const DT& dt = vd.dual(); + auto dte = he.dual(); + auto objE = dt.dual(dte); // line segment, ray, or line + Segment seg; + Ray ray; + Line line; + Segment segI; + if (CGAL::assign(seg, objE)) { + auto objI = CGAL::intersection(seg, bbox); + segI = CGAL::object_cast>(objI); + } else if (CGAL::assign(ray, objE)) { + auto objI = CGAL::intersection(ray, bbox); + segI = CGAL::object_cast>(objI); + } else if (CGAL::assign(line, objE)) { + auto objI = CGAL::intersection(line, bbox); + segI = CGAL::object_cast>(objI); + } else { + throw std::runtime_error("Voronoi edge is neither a segment, ray, nor a line."); + } + pts.push_back(segI.source()); + } while (++circ != circ_start); + + return {pts.begin(), pts.end()}; +} + +#endif //CARTOCROW_POINT_VORONOI_HELPERS_H diff --git a/cartocrow/simplesets/settings.h b/cartocrow/simplesets/settings.h new file mode 100644 index 00000000..36a6e0d2 --- /dev/null +++ b/cartocrow/simplesets/settings.h @@ -0,0 +1,56 @@ +#ifndef CARTOCROW_SETTINGS_H +#define CARTOCROW_SETTINGS_H + +#include "types.h" + +namespace cartocrow::simplesets { +struct GeneralSettings { + /// Radius of circle that represents a point. + Number pointSize; + /// Maximum number of inflections a bank is allowed to have. + int inflectionLimit; + /// Maximum total angle of a bend (maximum monotone subsequence of a bank). + Number maxBendAngle; + /// Maximum turning angle in a bank. + Number maxTurnAngle; +}; + +struct PartitionSettings { + /// Create banks? + bool banks; + /// Create islands? + bool islands; + /// Delay merges that create patterns whose points are not distributed 'regularly'? + /// A pattern is not regular if it has clearly discernible sub-patterns. + bool regularityDelay; + /// Delay merges that create patterns that intersect points. + bool intersectionDelay; + /// Disallow merges that have a point within distance admissableFactor * dilationRadius. + Number admissableRadiusFactor; +}; + +struct ComputeDrawingSettings { + /// Aim to keep a disk around each point visible of radius cutoutRadiusFactor * dilationRadius. + Number cutoutRadiusFactor; +}; + +struct DrawSettings { + std::vector colors; + Number pointStrokeWeight(GeneralSettings gs) { + return gs.pointSize / 2.5; + } + Number contourStrokeWeight(GeneralSettings gs) { + return gs.pointSize / 3.5; + } +}; + +struct Settings { + GeneralSettings gs; + PartitionSettings ps; + ComputeDrawingSettings cds; + DrawSettings ds; +}; +} + + +#endif //CARTOCROW_SETTINGS_H diff --git a/cartocrow/simplesets/types.h b/cartocrow/simplesets/types.h new file mode 100644 index 00000000..947b43ce --- /dev/null +++ b/cartocrow/simplesets/types.h @@ -0,0 +1,10 @@ +#ifndef CARTOCROW_TYPES_H +#define CARTOCROW_TYPES_H + +#include "../core/core.h" + +namespace cartocrow::simplesets { +typedef ExactWithSqrt K; +} + +#endif //CARTOCROW_TYPES_H diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 9721212d..a7826515 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(flow_map) -add_subdirectory(isoline_simplification) \ No newline at end of file +add_subdirectory(isoline_simplification) +add_subdirectory(simplesets) diff --git a/demos/simplesets/CMakeLists.txt b/demos/simplesets/CMakeLists.txt new file mode 100644 index 00000000..bea16e48 --- /dev/null +++ b/demos/simplesets/CMakeLists.txt @@ -0,0 +1,18 @@ +set(SOURCES + simplesets_demo.cpp +) + +add_executable(simplesets_demo ${SOURCES}) + +target_link_libraries( + simplesets_demo + PRIVATE + ${COMMON_CLA_TARGET} + core + renderer + simplesets + CGAL::CGAL + Qt5::Widgets +) + +install(TARGETS simplesets DESTINATION ${INSTALL_BINARY_DIR}) diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp new file mode 100644 index 00000000..30b8ee31 --- /dev/null +++ b/demos/simplesets/simplesets_demo.cpp @@ -0,0 +1,77 @@ +/* +The CartoCrow library implements algorithmic geo-visualization methods, +developed at TU Eindhoven. +Copyright (C) 2024 TU Eindhoven + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "cartocrow/core/ipe_reader.h" +#include "cartocrow/renderer/ipe_renderer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "simplesets_demo.h" +#include "cartocrow/simplesets/island.h" +#include "cartocrow/simplesets/bank.h" + +namespace fs = std::filesystem; +using namespace cartocrow; +using namespace cartocrow::renderer; +using namespace cartocrow::simplesets; + +SimpleSetsDemo::SimpleSetsDemo() { + setWindowTitle("SimpleSets"); + + auto* dockWidget = new QDockWidget(); + addDockWidget(Qt::RightDockWidgetArea, dockWidget); + auto* vWidget = new QWidget(); + auto* vLayout = new QVBoxLayout(vWidget); + vLayout->setAlignment(Qt::AlignTop); + dockWidget->setWidget(vWidget); + + auto* basicOptions = new QLabel("

Input

"); + vLayout->addWidget(basicOptions); + auto* fileSelector = new QComboBox(); + auto* fileSelectorLabel = new QLabel("Input file"); + fileSelectorLabel->setBuddy(fileSelector); + vLayout->addWidget(fileSelectorLabel); + vLayout->addWidget(fileSelector); + + auto renderer = new GeometryWidget(); + renderer->setDrawAxes(false); + setCentralWidget(renderer); + + renderer->setMinZoom(0.01); + renderer->setMaxZoom(1000.0); + + Island island({{{1, 1}, 0}, {{2, 1}, 0}, {{3, 1.1}, 0}}); + Bank bank({{{1, 1}, 0}, {{2, 1}, 0}, {{3, 1.1}, 0}}); + std::cout << island.coverRadius() << std::endl; + std::cout << bank.coverRadius() << std::endl; +} + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + SimpleSetsDemo demo; + demo.show(); + app.exec(); +} diff --git a/demos/simplesets/simplesets_demo.h b/demos/simplesets/simplesets_demo.h new file mode 100644 index 00000000..da1380fa --- /dev/null +++ b/demos/simplesets/simplesets_demo.h @@ -0,0 +1,44 @@ +/* +The CartoCrow library implements algorithmic geo-visualization methods, +developed at TU Eindhoven. +Copyright (C) 2024 TU Eindhoven + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef CARTOCROW_SIMPLESETS_DEMO_H +#define CARTOCROW_SIMPLESETS_DEMO_H + +#include "cartocrow/core/core.h" +#include "cartocrow/core/ipe_reader.h" +#include "cartocrow/renderer/geometry_painting.h" +#include "cartocrow/renderer/geometry_widget.h" +#include "cartocrow/simplesets/types.h" +#include + +using namespace cartocrow; +using namespace cartocrow::renderer; +using namespace cartocrow::simplesets; + +class SimpleSetsDemo : public QMainWindow { + Q_OBJECT + + public: + SimpleSetsDemo(); + + private: + +}; + +#endif //CARTOCROW_SIMPLESETS_DEMO_H From 648b276e37ebfd1fc1eb380cf0e374ed0d428f48 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Thu, 15 Aug 2024 23:22:11 +0200 Subject: [PATCH 02/36] Implement basic dilation and partitioning algorithm. Start on drawing algorithm --- cartocrow/renderer/CMakeLists.txt | 2 + cartocrow/renderer/function_painting.cpp | 10 + cartocrow/renderer/function_painting.h | 18 ++ cartocrow/renderer/geometry_widget.cpp | 7 + cartocrow/renderer/geometry_widget.h | 1 + cartocrow/renderer/ipe_renderer.cpp | 40 ++- cartocrow/renderer/ipe_renderer.h | 11 + cartocrow/simplesets/CMakeLists.txt | 35 ++- cartocrow/simplesets/bank.h | 35 --- cartocrow/simplesets/cat_point.h | 5 +- cartocrow/simplesets/dilated/dilated_poly.cpp | 72 +++++ cartocrow/simplesets/dilated/dilated_poly.h | 9 + cartocrow/simplesets/drawing_algorithm.cpp | 227 ++++++++++++++ cartocrow/simplesets/drawing_algorithm.h | 31 ++ .../{ => helpers}/cropped_voronoi.h | 55 ++-- .../simplesets/helpers/cs_polygon_helpers.cpp | 67 ++++ .../simplesets/helpers/cs_polygon_helpers.h | 29 ++ .../{ => helpers}/point_voronoi_helpers.h | 8 +- cartocrow/simplesets/island.h | 27 -- cartocrow/simplesets/parse_input.cpp | 44 +++ cartocrow/simplesets/parse_input.h | 10 + cartocrow/simplesets/partition.h | 10 + cartocrow/simplesets/partition_algorithm.cpp | 292 ++++++++++++++++++ cartocrow/simplesets/partition_algorithm.h | 26 ++ cartocrow/simplesets/partition_painting.cpp | 36 +++ cartocrow/simplesets/partition_painting.h | 24 ++ cartocrow/simplesets/pattern.h | 21 -- cartocrow/simplesets/{ => patterns}/bank.cpp | 23 +- cartocrow/simplesets/patterns/bank.h | 36 +++ .../simplesets/{ => patterns}/island.cpp | 44 ++- cartocrow/simplesets/patterns/island.h | 27 ++ cartocrow/simplesets/patterns/matching.cpp | 19 ++ cartocrow/simplesets/patterns/matching.h | 19 ++ .../simplesets/{ => patterns}/pattern.cpp | 0 cartocrow/simplesets/patterns/pattern.h | 23 ++ cartocrow/simplesets/patterns/poly_pattern.h | 36 +++ .../simplesets/patterns/single_point.cpp | 22 ++ cartocrow/simplesets/patterns/single_point.h | 21 ++ cartocrow/simplesets/settings.h | 19 +- cartocrow/simplesets/types.cpp | 25 ++ cartocrow/simplesets/types.h | 25 +- demos/simplesets/CMakeLists.txt | 1 + demos/simplesets/colors.cpp | 14 + demos/simplesets/colors.h | 20 ++ demos/simplesets/simplesets_demo.cpp | 53 +++- demos/simplesets/simplesets_demo.h | 9 +- 46 files changed, 1407 insertions(+), 181 deletions(-) create mode 100644 cartocrow/renderer/function_painting.cpp create mode 100644 cartocrow/renderer/function_painting.h delete mode 100644 cartocrow/simplesets/bank.h create mode 100644 cartocrow/simplesets/dilated/dilated_poly.cpp create mode 100644 cartocrow/simplesets/dilated/dilated_poly.h create mode 100644 cartocrow/simplesets/drawing_algorithm.cpp create mode 100644 cartocrow/simplesets/drawing_algorithm.h rename cartocrow/simplesets/{ => helpers}/cropped_voronoi.h (59%) create mode 100644 cartocrow/simplesets/helpers/cs_polygon_helpers.cpp create mode 100644 cartocrow/simplesets/helpers/cs_polygon_helpers.h rename cartocrow/simplesets/{ => helpers}/point_voronoi_helpers.h (98%) delete mode 100644 cartocrow/simplesets/island.h create mode 100644 cartocrow/simplesets/parse_input.cpp create mode 100644 cartocrow/simplesets/parse_input.h create mode 100644 cartocrow/simplesets/partition.h create mode 100644 cartocrow/simplesets/partition_algorithm.cpp create mode 100644 cartocrow/simplesets/partition_algorithm.h create mode 100644 cartocrow/simplesets/partition_painting.cpp create mode 100644 cartocrow/simplesets/partition_painting.h delete mode 100644 cartocrow/simplesets/pattern.h rename cartocrow/simplesets/{ => patterns}/bank.cpp (77%) create mode 100644 cartocrow/simplesets/patterns/bank.h rename cartocrow/simplesets/{ => patterns}/island.cpp (59%) create mode 100644 cartocrow/simplesets/patterns/island.h create mode 100644 cartocrow/simplesets/patterns/matching.cpp create mode 100644 cartocrow/simplesets/patterns/matching.h rename cartocrow/simplesets/{ => patterns}/pattern.cpp (100%) create mode 100644 cartocrow/simplesets/patterns/pattern.h create mode 100644 cartocrow/simplesets/patterns/poly_pattern.h create mode 100644 cartocrow/simplesets/patterns/single_point.cpp create mode 100644 cartocrow/simplesets/patterns/single_point.h create mode 100644 cartocrow/simplesets/types.cpp create mode 100644 demos/simplesets/colors.cpp create mode 100644 demos/simplesets/colors.h diff --git a/cartocrow/renderer/CMakeLists.txt b/cartocrow/renderer/CMakeLists.txt index b4dea38f..35de22e2 100644 --- a/cartocrow/renderer/CMakeLists.txt +++ b/cartocrow/renderer/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES geometry_widget.cpp ipe_renderer.cpp painting_renderer.cpp + function_painting.cpp ) set(HEADERS geometry_painting.h @@ -10,6 +11,7 @@ set(HEADERS geometry_widget.h ipe_renderer.h painting_renderer.h + function_painting.h ) add_library(renderer ${SOURCES}) diff --git a/cartocrow/renderer/function_painting.cpp b/cartocrow/renderer/function_painting.cpp new file mode 100644 index 00000000..e5259ab0 --- /dev/null +++ b/cartocrow/renderer/function_painting.cpp @@ -0,0 +1,10 @@ +#include "function_painting.h" + +namespace cartocrow::renderer { +FunctionPainting::FunctionPainting(const std::function& draw_function) + : m_draw_function(draw_function) {} + +void FunctionPainting::paint(GeometryRenderer& renderer) const { + m_draw_function(renderer); +} +} \ No newline at end of file diff --git a/cartocrow/renderer/function_painting.h b/cartocrow/renderer/function_painting.h new file mode 100644 index 00000000..681593cf --- /dev/null +++ b/cartocrow/renderer/function_painting.h @@ -0,0 +1,18 @@ +#ifndef CARTOCROW_FUNCTION_PAINTING_H +#define CARTOCROW_FUNCTION_PAINTING_H + +#include "geometry_renderer.h" +#include "geometry_painting.h" + +namespace cartocrow::renderer { +class FunctionPainting : public GeometryPainting { + public: + FunctionPainting(const std::function& draw_function); + void paint(renderer::GeometryRenderer& renderer) const override; + + private: + const std::function m_draw_function; +}; +} + +#endif //CARTOCROW_FUNCTION_PAINTING_H diff --git a/cartocrow/renderer/geometry_widget.cpp b/cartocrow/renderer/geometry_widget.cpp index 05b4c833..06b3b787 100644 --- a/cartocrow/renderer/geometry_widget.cpp +++ b/cartocrow/renderer/geometry_widget.cpp @@ -21,6 +21,8 @@ along with this program. If not, see . #include "geometry_renderer.h" +#include "function_painting.h" + #include #include #include @@ -652,6 +654,11 @@ void GeometryWidget::addPainting(std::shared_ptr painting, con updateLayerList(); } +void GeometryWidget::addPainting(const std::function& draw_function, const std::string& name) { + auto painting = std::make_shared(draw_function); + addPainting(painting, name); +} + void GeometryWidget::clear() { m_paintings.clear(); updateLayerList(); diff --git a/cartocrow/renderer/geometry_widget.h b/cartocrow/renderer/geometry_widget.h index a700408a..304bf120 100644 --- a/cartocrow/renderer/geometry_widget.h +++ b/cartocrow/renderer/geometry_widget.h @@ -179,6 +179,7 @@ class GeometryWidget : public QWidget, public GeometryRenderer { /// Adds a new painting to this widget. void addPainting(std::shared_ptr painting, const std::string& name); + void addPainting(const std::function& draw_function, const std::string& name); /// Removes all paintings from this widget. void clear(); diff --git a/cartocrow/renderer/ipe_renderer.cpp b/cartocrow/renderer/ipe_renderer.cpp index 96e91843..ff627f69 100644 --- a/cartocrow/renderer/ipe_renderer.cpp +++ b/cartocrow/renderer/ipe_renderer.cpp @@ -20,6 +20,7 @@ along with this program. If not, see . #include "ipe_renderer.h" #include "cartocrow/renderer/geometry_renderer.h" +#include "cartocrow/renderer/function_painting.h" #include #include #include @@ -76,7 +77,15 @@ void IpeRenderer::save(const std::filesystem::path& file) { setFillOpacity(255); // add default alpha to style sheet m_page = new ipe::Page(); - for (auto painting : m_paintings) { + document.push_back(m_page); + + int current_page = 0; + + for (auto painting : m_paintings) { // Assumes m_paintings are ordered in increasing page_index + while (painting.page_index > current_page) { + m_page = new ipe::Page(); + document.push_back(m_page); + } pushStyle(); if (auto name = painting.name) { m_page->addLayer(name->c_str()); @@ -88,7 +97,6 @@ void IpeRenderer::save(const std::filesystem::path& file) { popStyle(); } - document.push_back(m_page); document.save(file.string().c_str(), ipe::FileFormat::Xml, 0); } @@ -116,7 +124,7 @@ void IpeRenderer::draw(const Segment& s) { void IpeRenderer::draw(const Line& l) { // Crop to document size - auto bounds = Rectangle(CGAL::ORIGIN, Point(1000.0, 1000.0)); + auto bounds = CGAL::Iso_rectangle_2(CGAL::ORIGIN, Point(1000.0, 1000.0)); auto result = intersection(l, bounds); if (result) { if (const Segment* s = boost::get>(&*result)) { @@ -130,7 +138,7 @@ void IpeRenderer::draw(const Line& l) { void IpeRenderer::draw(const Ray& r) { // Crop to document size - auto bounds = Rectangle(CGAL::ORIGIN, Point(1000.0, 1000.0)); + auto bounds = CGAL::Iso_rectangle_2(CGAL::ORIGIN, Point(1000.0, 1000.0)); auto result = intersection(r, bounds); if (result) { if (const Segment* s = boost::get>(&*result)) { @@ -318,17 +326,37 @@ ipe::AllAttributes IpeRenderer::getAttributesForStyle() const { attributes.iStroke = ipe::Attribute(ipe::Color(m_style.m_strokeColor)); attributes.iFill = ipe::Attribute(ipe::Color(m_style.m_fillColor)); attributes.iOpacity = m_style.m_fillOpacity; + attributes.iStrokeOpacity = m_style.m_strokeOpacity; return attributes; } + +void IpeRenderer::addPainting(const std::function& draw_function) { + auto painting = std::make_shared(draw_function); + addPainting(painting); +} + +void IpeRenderer::addPainting(const std::function& draw_function, const std::string& name) { + auto painting = std::make_shared(draw_function); + addPainting(painting, name); +} + void IpeRenderer::addPainting(const std::shared_ptr& painting) { - m_paintings.push_back(DrawnPainting{painting}); + m_paintings.push_back(DrawnPainting{painting, std::nullopt, m_page_index}); } void IpeRenderer::addPainting(const std::shared_ptr& painting, const std::string& name) { std::string spaceless; std::replace_copy(name.begin(), name.end(), std::back_inserter(spaceless), ' ', '_'); - m_paintings.push_back(DrawnPainting{painting, spaceless}); + m_paintings.push_back(DrawnPainting{painting, spaceless, m_page_index}); +} + +void IpeRenderer::nextPage() { + ++m_page_index; +} + +int IpeRenderer::currentPage() { + return m_page_index; } std::string IpeRenderer::escapeForLaTeX(const std::string& text) const { diff --git a/cartocrow/renderer/ipe_renderer.h b/cartocrow/renderer/ipe_renderer.h index e1f05056..0c61a53c 100644 --- a/cartocrow/renderer/ipe_renderer.h +++ b/cartocrow/renderer/ipe_renderer.h @@ -100,9 +100,16 @@ class IpeRenderer : public GeometryRenderer { void setFill(Color color) override; void setFillOpacity(int alpha) override; + void addPainting(const std::function& draw_function); + void addPainting(const std::function& draw_function, const std::string& name); void addPainting(const std::shared_ptr& painting); void addPainting(const std::shared_ptr& painting, const std::string& name); + /// Paintings will be added a new page. + void nextPage(); + /// Returns the current page index. + int currentPage(); + private: /// Converts a polygon to an Ipe curve. ipe::Curve* convertPolygonToCurve(const Polygon& p) const; @@ -123,6 +130,8 @@ class IpeRenderer : public GeometryRenderer { std::shared_ptr m_painting; /// The name of the painting displayed as a layer name in ipe. std::optional name; + /// The Ipe page the painting will be drawn onto. + int page_index; }; /// The paintings we're drawing. @@ -142,6 +151,8 @@ class IpeRenderer : public GeometryRenderer { ipe::StyleSheet* m_alphaSheet; /// The index of the Ipe layer we are currently drawing to. int m_layer; + /// The index of the Ipe page a painting will get drawn to. + int m_page_index = 0; }; } // namespace cartocrow::renderer diff --git a/cartocrow/simplesets/CMakeLists.txt b/cartocrow/simplesets/CMakeLists.txt index 41f8d88a..a14f537b 100644 --- a/cartocrow/simplesets/CMakeLists.txt +++ b/cartocrow/simplesets/CMakeLists.txt @@ -1,17 +1,36 @@ set(SOURCES - pattern.cpp - island.cpp - bank.cpp + types.cpp + parse_input.cpp + patterns/pattern.cpp + patterns/single_point.cpp + patterns/matching.cpp + patterns/island.cpp + patterns/bank.cpp + dilated/dilated_poly.cpp + helpers/cs_polygon_helpers.cpp + partition_algorithm.cpp + partition_painting.cpp + drawing_algorithm.cpp ) set(HEADERS - pattern.h types.h settings.h cat_point.h - island.h - bank.h - point_voronoi_helpers.h - cropped_voronoi.h + parse_input.h + patterns/pattern.h + patterns/poly_pattern.h + patterns/single_point.h + patterns/matching.h + patterns/island.h + patterns/bank.h + dilated/dilated_poly.h + helpers/point_voronoi_helpers.h + helpers/cropped_voronoi.h + helpers/cs_polygon_helpers.h + partition_algorithm.h + partition_painting.h + partition.h + drawing_algorithm.h ) add_library(simplesets ${SOURCES}) diff --git a/cartocrow/simplesets/bank.h b/cartocrow/simplesets/bank.h deleted file mode 100644 index f71ee8a3..00000000 --- a/cartocrow/simplesets/bank.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef CARTOCROW_BANK_H -#define CARTOCROW_BANK_H - -#include "pattern.h" - -namespace cartocrow::simplesets { -struct Bend { - CGAL::Orientation orientation; - Number maxAngle; - Number totalAngle; - int startIndex; - int endIndex; -}; - -class Bank : public Pattern { - public: - Bank(std::vector catPoints); - - std::variant, Polygon> contour() override; - std::vector catPoints() override; - Number coverRadius() override; - bool isValid(GeneralSettings gs) override; - - private: - std::vector m_catPoints; - std::vector> m_points; - Number m_coverRadius; - Polygon m_polyline; - std::vector m_bends; - - void computeBends(); -}; -} - -#endif //CARTOCROW_BANK_H diff --git a/cartocrow/simplesets/cat_point.h b/cartocrow/simplesets/cat_point.h index ba4436d9..dff3c8bb 100644 --- a/cartocrow/simplesets/cat_point.h +++ b/cartocrow/simplesets/cat_point.h @@ -6,8 +6,9 @@ namespace cartocrow::simplesets { /// Categorical point struct CatPoint { - Point point; - int category; + unsigned int category; + Point point; + auto operator<=>(const CatPoint&) const = default; }; } diff --git a/cartocrow/simplesets/dilated/dilated_poly.cpp b/cartocrow/simplesets/dilated/dilated_poly.cpp new file mode 100644 index 00000000..96f4c175 --- /dev/null +++ b/cartocrow/simplesets/dilated/dilated_poly.cpp @@ -0,0 +1,72 @@ +#include "dilated_poly.h" +#include + +namespace cartocrow::simplesets { +CSPolygon dilateSegment(const Segment& segment, const Number& dilationRadius) { + std::vector> points({makeExact(segment.source()), makeExact(segment.target())}); + Polygon polygon(points.begin(), points.end()); + auto dilation = CGAL::approximated_offset_2(polygon, dilationRadius, M_EPSILON); + return dilation.outer_boundary(); +} + +CSPolygon ccb_to_polygon(CSArrangement::Ccb_halfedge_const_circulator circ) { + std::vector curves; + auto curr = circ; + do { + curves.push_back(curr->curve()); + } while (++curr != circ); + CSPolygon poly(curves.begin(), curves.end()); + return poly; +} + +CSPolygon dilatePattern(const Pattern& pattern, const Number& dilationRadius) { + auto cont = pattern.contour(); + + if (holds_alternative>(cont)) { + auto exactPolygon = makeExact(std::get>(cont)); + + if (exactPolygon.size() == 1) { + CSTraits::Rational_circle_2 circle(exactPolygon.vertex(0), dilationRadius * dilationRadius); + CSTraits traits; + auto make_x_monotone = traits.make_x_monotone_2_object(); + std::vector> curves_and_points; + make_x_monotone(circle, std::back_inserter(curves_and_points)); + std::vector curves; + + // There should not be any isolated points + for (auto kinda_curve : curves_and_points) { + auto curve = boost::get(kinda_curve); + curves.push_back(curve); + } + + // todo: is order of curves always correct? + return {curves.begin(), curves.end()}; + } + + auto dilation = CGAL::approximated_offset_2(exactPolygon, dilationRadius, M_EPSILON); + if (dilation.has_holes()) { + throw std::runtime_error("Did not expect holes after dilating a polygonal pattern."); + } + return dilation.outer_boundary(); + } else if (holds_alternative>(cont)) { + // 1. Dilate each segment + // 2. Make arrangement of dilated segments + // 3. Traverse and extract outer boundary + // (Assume that the dilation result has no holes. + // To ensure this we need to constrain the (relative) point size.) + + CSArrangement arr; + + auto polyline = std::get>(cont); + for (auto eit = polyline.edges_begin(); eit != polyline.edges_end(); ++eit) { + auto seg = *eit; + auto dilated = dilateSegment(seg, dilationRadius); + for (auto cit = dilated.curves_begin(); cit != dilated.curves_end(); ++cit) { + CGAL::insert(arr, *cit); + } + } + + return ccb_to_polygon(*arr.unbounded_face()->inner_ccbs_begin()); + } +} +} diff --git a/cartocrow/simplesets/dilated/dilated_poly.h b/cartocrow/simplesets/dilated/dilated_poly.h new file mode 100644 index 00000000..2d7dfeec --- /dev/null +++ b/cartocrow/simplesets/dilated/dilated_poly.h @@ -0,0 +1,9 @@ +#ifndef CARTOCROW_DILATED_POLY_H +#define CARTOCROW_DILATED_POLY_H + +#include "../patterns/pattern.h" + +namespace cartocrow::simplesets { +CSPolygon dilatePattern(const Pattern& pattern, const Number& dilationRadius); +} +#endif //CARTOCROW_DILATED_POLY_H diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp new file mode 100644 index 00000000..52c1d1bc --- /dev/null +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -0,0 +1,227 @@ +#include "drawing_algorithm.h" +#include "dilated/dilated_poly.h" + +namespace cartocrow::simplesets { +CSPolygon face_to_polygon(const DilatedPatternArrangement::Face& face) { + assert(face.number_of_holes() == 0); + auto ccb_start = face.outer_ccb(); + auto ccb = ccb_start; + + std::vector x_monotone_curves; + do { + auto curve = ccb->curve(); + x_monotone_curves.push_back(curve); + } while(++ccb != ccb_start); + + return {x_monotone_curves.begin(), x_monotone_curves.end()}; +} + +CSTraits::X_monotone_curve_2 make_x_monotone(const Segment& segment) { + CSTraits traits; + auto make_x_monotone = traits.make_x_monotone_2_object(); + std::vector> curves_and_points; + make_x_monotone(segment, std::back_inserter(curves_and_points)); + std::vector curves; + + // There should not be any isolated points + for (auto kinda_curve : curves_and_points) { + assert(kinda_curve.which() == 1); + auto curve = boost::get(kinda_curve); + curves.push_back(curve); + } + + assert(curves.size() == 1); + return curves[0]; +} + +Point get_approx_point_on_boundary(const DilatedPatternArrangement::Face& face) { + auto curve = face.outer_ccb()->curve(); + Rectangle bbox = curve.bbox(); + Rectangle rect({bbox.xmin() - 1, bbox.ymin() - 1}, {bbox.xmax() + 1, bbox.ymax() + 1}); + Point approx_source = makeExact(approximateAlgebraic(curve.source())); + Point approx_target = makeExact(approximateAlgebraic(curve.target())); + Point middle = CGAL::midpoint(approx_source, approx_target); + if (curve.is_linear()) { + return middle; + } else { + assert(curve.is_circular()); + Circle circle = curve.supporting_circle(); + Line l(approx_source, approx_target); + Line pl = l.perpendicular(middle); + auto inter = CGAL::intersection(pl, rect); + assert(inter.has_value()); + assert(inter->which() == 1); + Segment seg = boost::get>(*inter); +// CGAL::intersection(pl, circle); + CSTraits traits; + auto make_x_monotone = traits.make_x_monotone_2_object(); + std::vector> curves_and_points; +// make_x_monotone(circle, std::back_inserter(curves_and_points)); + make_x_monotone(seg, std::back_inserter(curves_and_points)); + std::vector curves; + + // There should not be any isolated points + for (auto kinda_curve : curves_and_points) { + auto curve = boost::get(kinda_curve); + curves.push_back(curve); + } + + typedef std::pair Intersection_point; + typedef boost::variant Intersection_result; + std::vector intersection_results; + assert(curves.size() == 1); + curve.intersect(curves[0], std::back_inserter(intersection_results)); + + std::vector intersection_pts; + for (const auto& result : intersection_results) { + assert(result.which() == 0); + intersection_pts.push_back(boost::get(result).first); + } + + assert(intersection_pts.size() == 1); + return makeExact(approximateAlgebraic(intersection_pts[0])); + } +} + +Point get_point_in(const DilatedPatternArrangement::Face& face) { + auto poly = face_to_polygon(face); + auto bbox = poly.bbox(); + Point point_outside(bbox.xmin() - 1, bbox.ymin() - 1); + Point approx_point_on_boundary = get_approx_point_on_boundary(face); + Segment seg(point_outside, approx_point_on_boundary); + auto seg_curve = make_x_monotone(seg); + std::vector intersection_pts; + for (auto cit = poly.curves_begin(); cit != poly.curves_end(); cit++) { + auto curve = *cit; + typedef std::pair Intersection_point; + typedef boost::variant Intersection_result; + std::vector intersection_results; + curve.intersect(seg_curve, std::back_inserter(intersection_results)); + + for (const auto& result : intersection_results) { + assert(result.which() == 0); + intersection_pts.push_back(boost::get(result).first); + } + } + + std::sort(intersection_pts.begin(), intersection_pts.end(), [&seg](const auto& pt1, const auto& pt2) { + Vector v = seg.supporting_line().to_vector(); + Vector pt1_ = makeExact(approximateAlgebraic(pt1)) - CGAL::ORIGIN; + Number a1 = v * pt1_; + Vector pt2_ = makeExact(approximateAlgebraic(pt1)) - CGAL::ORIGIN; + Number a2 = v * pt2_; + return a1 < a2; + }); + + Point approx_source; + Point approx_target; + if (intersection_pts.size() >= 2) { + approx_source = makeExact(approximateAlgebraic(intersection_pts[0])); + approx_target = makeExact(approximateAlgebraic(intersection_pts[1])); + } else { + approx_source = makeExact(approximateAlgebraic(intersection_pts[0])); + approx_target = approx_point_on_boundary; + } + + return midpoint(Segment(approx_source, approx_target)); +} + +bool on_or_inside(const CSPolygon& polygon, const Point& point) { + Ray ray(point, Vector(1, 0)); + + Rectangle bbox = polygon.bbox(); + Rectangle rect({bbox.xmin() - 1, bbox.ymin() - 1}, {bbox.xmax() + 1, bbox.ymax() + 1}); + + auto inter = CGAL::intersection(ray, rect); + if (!inter.has_value()) return false; + auto seg = boost::get>(*inter); + auto seg_curve = make_x_monotone(seg); + + typedef std::pair Intersection_point; + typedef boost::variant Intersection_result; + std::vector intersection_results; + + for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { + const auto& curve = *cit; + curve.intersect(seg_curve, std::back_inserter(intersection_results)); + } + + return intersection_results.size() % 2 == 1; +} + +DilatedPatternArrangement +dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds) { + std::vector dilated; + for (const auto& p : partition) { + dilated.push_back(dilatePattern(*p, gs.dilationRadius())); + } + + DilatedPatternArrangement arr; +// CSTraits traits; +// auto make_x_monotone = traits.make_x_monotone_2_object(); + std::vector x_monotone_curves; + for (const auto& poly : dilated) { + std::copy(poly.curves_begin(), poly.curves_end(), std::back_inserter(x_monotone_curves)); + } + CGAL::insert(arr, x_monotone_curves.begin(), x_monotone_curves.end()); + + for (auto fit = arr.faces_begin(); fit != arr.faces_end(); ++fit) { + if (fit->is_unbounded()) continue; + auto pt = get_point_in(*fit); + std::vector origins; + for (int i = 0; i < dilated.size(); i++) { + if (on_or_inside(dilated[i], pt)) { + origins.push_back(i); + } +// dilated[i].curves_begin()-> + } + fit->set_data(FaceInfo{origins}); + } + + return arr; +} + +ArrangementPainting::ArrangementPainting(const DilatedPatternArrangement& arr, const DrawSettings& ds, + const Partition& partition) + : m_arr(arr), m_ds(ds), m_partition(partition) {}; + +void ArrangementPainting::paint(renderer::GeometryRenderer& renderer) const { + renderer.setMode(renderer::GeometryRenderer::fill); + for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { + auto face = *fit; + auto origins = face.data().origins; + if (origins.size() > 1) { + renderer.setFill(Color{0, 0, 0}); + } else if (origins.size() == 1) { + renderer.setFill(Color{0, 0, 0}); +// m_ds.colors[origins[0]] + renderer.setFill(m_ds.colors[m_partition[origins[0]]->category()]); + } else { + continue; + } + auto poly = face_to_polygon(face); + for (auto cit = poly.curves_begin(); cit != poly.curves_end(); ++cit) { + if (cit->is_linear()) { + renderer.draw(Segment(approximateAlgebraic(cit->source()), approximateAlgebraic(cit->target()))); + } else if (cit->is_circular()){ + auto circle = cit->supporting_circle(); + renderer.draw(circle); + // todo: draw arc + } + } + } + + renderer.setMode(renderer::GeometryRenderer::stroke); + renderer.setStroke(Color{0, 0, 0}, 3.0); + for (auto eit = m_arr.edges_begin(); eit != m_arr.edges_end(); ++eit) { + auto curve = eit->curve(); + if (curve.is_linear()) { + renderer.draw(Segment(approximateAlgebraic(curve.source()), approximateAlgebraic(curve.target()))); + } else if (curve.is_circular()){ + auto circle = curve.supporting_circle(); + renderer.draw(circle); + // todo: draw arc + } + } +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h new file mode 100644 index 00000000..e8dd7ad6 --- /dev/null +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -0,0 +1,31 @@ +#ifndef CARTOCROW_DRAWING_ALGORITHM_H +#define CARTOCROW_DRAWING_ALGORITHM_H + +#include "partition.h" +#include "../renderer/geometry_painting.h" + +namespace cartocrow::simplesets { +struct FaceInfo { + std::vector origins; +}; + +using DilatedPatternArrangement = + CGAL::Arrangement_2>; + +DilatedPatternArrangement +dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds); + +class ArrangementPainting : public renderer::GeometryPainting { + public: + ArrangementPainting(const DilatedPatternArrangement& arr, const DrawSettings& ds, const Partition& partition); + void paint(renderer::GeometryRenderer& renderer) const override; + + private: + const DilatedPatternArrangement& m_arr; + const DrawSettings& m_ds; + const Partition& m_partition; +}; +} + +#endif //CARTOCROW_DRAWING_ALGORITHM_H diff --git a/cartocrow/simplesets/cropped_voronoi.h b/cartocrow/simplesets/helpers/cropped_voronoi.h similarity index 59% rename from cartocrow/simplesets/cropped_voronoi.h rename to cartocrow/simplesets/helpers/cropped_voronoi.h index e38be87a..a2c130cb 100644 --- a/cartocrow/simplesets/cropped_voronoi.h +++ b/cartocrow/simplesets/helpers/cropped_voronoi.h @@ -1,23 +1,23 @@ #ifndef CARTOCROW_CROPPED_VORONOI_H #define CARTOCROW_CROPPED_VORONOI_H -#include "../core/core.h" -#include "types.h" +#include "cartocrow/core/core.h" +#include "cartocrow/simplesets/types.h" #include using namespace cartocrow; using namespace cartocrow::simplesets; -std::optional> intersectionConvex(const Polygon& polygon, const Ray& ray) { +std::optional> intersectionConvex(const Polygon& polygon, const Ray& ray) { auto sourceInside = polygon.has_on_bounded_side(ray.source()); - std::vector> inters; + std::vector> inters; for (auto eit = polygon.edges_begin(); eit != polygon.edges_end(); eit++) { auto edge = *eit; auto obj = CGAL::intersection(ray, edge); if (obj.has_value()) { - Segment seg; - Point point; + Segment seg; + Point point; if (CGAL::assign(seg, obj)) { return seg; } else if (CGAL::assign(point, obj)) { @@ -30,20 +30,20 @@ std::optional> intersectionConvex(const Polygon& polygon, const Ra if (inters.empty()) { return std::nullopt; } else if (inters.size() == 2) { - return Segment(inters[0], inters[1]); + return Segment(inters[0], inters[1]); } else { - return Segment(ray.source(), inters[0]); + return Segment(ray.source(), inters[0]); } } -std::optional> intersectionConvex(const Polygon& polygon, const Line& line) { - std::vector> inters; +std::optional> intersectionConvex(const Polygon& polygon, const Line& line) { + std::vector> inters; for (auto eit = polygon.edges_begin(); eit != polygon.edges_end(); eit++) { auto edge = *eit; auto obj = CGAL::intersection(line, edge); if (obj.has_value()) { - Segment seg; - Point point; + Segment seg; + Point point; if (CGAL::assign(seg, obj)) { return seg; } else if (CGAL::assign(point, obj)) { @@ -54,26 +54,26 @@ std::optional> intersectionConvex(const Polygon& polygon, const Li assert(inters.size() != 1); if (inters.size() == 2) { - return Segment(inters[0], inters[1]); + return Segment(inters[0], inters[1]); } else { return std::nullopt; } } -std::optional> intersectionConvex(const Polygon& polygon, const Segment& segment) { +std::optional> intersectionConvex(const Polygon& polygon, const Segment& segment) { auto sourceInside = polygon.has_on_bounded_side(segment.source()); auto targetInside = polygon.has_on_bounded_side(segment.target()); if (sourceInside && targetInside) { return segment; } - std::vector> inters; + std::vector> inters; for (auto eit = polygon.edges_begin(); eit != polygon.edges_end(); eit++) { auto edge = *eit; auto obj = CGAL::intersection(segment, edge); if (obj.has_value()) { - Segment seg; - Point point; + Segment seg; + Point point; if (CGAL::assign(seg, obj)) { return seg; } else if (CGAL::assign(point, obj)) { @@ -85,20 +85,21 @@ std::optional> intersectionConvex(const Polygon& polygon, const Se } if (!sourceInside && !targetInside) { + if (inters.empty()) return std::nullopt; assert(inters.size() == 2); // note that here, the orientation of the result segment may not be the same as the original segment. - return Segment(inters[0], inters[1]); + return Segment(inters[0], inters[1]); } if (sourceInside) { assert(!targetInside); assert(inters.size() == 1); - return Segment(segment.source(), inters[0]); + return Segment(segment.source(), inters[0]); } assert(targetInside); assert(inters.size() == 1); - return Segment(inters[0], segment.target()); + return Segment(inters[0], segment.target()); } // This part is adapted from: @@ -108,19 +109,19 @@ std::optional> intersectionConvex(const Polygon& polygon, const Se //A class to recover Voronoi diagram from stream. //Rays, lines and segments are cropped to a polygon struct Cropped_voronoi_from_delaunay{ - std::list, Segment>> m_cropped_vd; - Polygon m_clipper; - Cropped_voronoi_from_delaunay(const Polygon& clipper):m_clipper(clipper){} + std::list, Segment>> m_cropped_vd; + Polygon m_clipper; + Cropped_voronoi_from_delaunay(const Polygon& clipper):m_clipper(clipper){} template - void crop_and_extract_segment(const Point& site, const RSL& rsl){ + void crop_and_extract_segment(const Point& site, const RSL& rsl){ auto s = intersectionConvex(m_clipper, rsl); if (s.has_value()) { m_cropped_vd.push_back({site, *s}); } } - void operator<<(std::pair&, const Ray&> site_ray) { crop_and_extract_segment(site_ray.first, site_ray.second); } - void operator<<(std::pair&, const Line&> site_line) { crop_and_extract_segment(site_line.first, site_line.second); } - void operator<<(std::pair&, const Segment&> site_seg){ crop_and_extract_segment(site_seg.first, site_seg.second); } + void operator<<(std::pair&, const Ray&> site_ray) { crop_and_extract_segment(site_ray.first, site_ray.second); } + void operator<<(std::pair&, const Line&> site_line) { crop_and_extract_segment(site_line.first, site_line.second); } + void operator<<(std::pair&, const Segment&> site_seg){ crop_and_extract_segment(site_seg.first, site_seg.second); } }; #endif //CARTOCROW_CROPPED_VORONOI_H diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp new file mode 100644 index 00000000..4f603add --- /dev/null +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp @@ -0,0 +1,67 @@ +#include "cs_polygon_helpers.h" + +// Code adapted from a Stack Overflow answer by HEKTO. +// Link: https://stackoverflow.com/questions/69399922/how-does-one-obtain-the-area-of-a-general-polygon-set-in-cgal +// License info: https://stackoverflow.com/help/licensing +// The only changes made were changing the auto return type to Number and using the +// typedefs for circle, point, polygon etc. + +namespace cartocrow::simplesets { +//For two circles of radii R and r and centered at (0,0) and (d,0) intersecting +//in a region shaped like an asymmetric lens. +constexpr double lens_area(const double r, const double R, const double d) { + return r * r * std::acos((d * d + r * r - R * R) / 2 / d / r) + + R * R * std::acos((d * d + R * R - r * r) / 2 / d / R) - + 0.5 * std::sqrt((-d + r + R) * (d + r - R) * (d - r + R) * (d + r + R)); +} + +// ------ return signed area under the linear segment (P1, P2) +Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2) { + auto const dx = CGAL::to_double(P1.x()) - CGAL::to_double(P2.x()); + auto const sy = CGAL::to_double(P1.y()) + CGAL::to_double(P2.y()); + return dx * sy / 2; +} + +// ------ return signed area under the circular segment (P1, P2, C) +Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2, const CSTraits::Rational_circle_2& C) { + auto const dx = CGAL::to_double(P1.x()) - CGAL::to_double(P2.x()); + auto const dy = CGAL::to_double(P1.y()) - CGAL::to_double(P2.y()); + auto const squaredChord = dx * dx + dy * dy; + auto const chord = std::sqrt(squaredChord); + auto const squaredRadius = CGAL::to_double(C.squared_radius()); + auto const areaSector = + squaredRadius * std::asin(std::min(1.0, chord / (std::sqrt(squaredRadius) * 2))); + auto const areaTriangle = chord * std::sqrt(std::max(0.0, squaredRadius * 4 - squaredChord)) / 4; + auto const areaCircularSegment = areaSector - areaTriangle; + return area(P1, P2) + C.orientation() * areaCircularSegment; +} + +// ------ return signed area under the X-monotone curve +Number area(const CSTraits::X_monotone_curve_2& XCV) { + if (XCV.is_linear()) { + return area(XCV.source(), XCV.target()); + } else if (XCV.is_circular()) { + return area(XCV.source(), XCV.target(), XCV.supporting_circle()); + } else { + return 0; + } +} + +// ------ return area of the simple polygon +Number area(const CSPolygon P) { + Number res = 0; + for (auto it = P.curves_begin(); it != P.curves_end(); ++it) { + res += area(*it); + } + return res; +} + +// ------ return area of the polygon with (optional) holes +Number area(const CSPolygonWithHoles& P) { + auto res = area(P.outer_boundary()); + for (auto it = P.holes_begin(); it != P.holes_end(); ++it) { + res += area(*it); + } + return res; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.h b/cartocrow/simplesets/helpers/cs_polygon_helpers.h new file mode 100644 index 00000000..1beeec36 --- /dev/null +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.h @@ -0,0 +1,29 @@ +#ifndef CARTOCROW_CS_POLYGON_HELPERS_H +#define CARTOCROW_CS_POLYGON_HELPERS_H + +#include "../types.h" + +// Thanks to: https://stackoverflow.com/questions/69399922/how-does-one-obtain-the-area-of-a-general-polygon-set-in-cgal + +namespace cartocrow::simplesets { +//For two circles of radii R and r and centered at (0,0) and (d,0) intersecting +//in a region shaped like an asymmetric lens. +constexpr double lens_area(const double r, const double R, const double d); + +// ------ return signed area under the linear segment (P1, P2) +Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2); + +// ------ return signed area under the circular segment (P1, P2, C) +Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2, const CSTraits::Rational_circle_2& C); + +// ------ return signed area under the X-monotone curve +Number area(const CSTraits::X_monotone_curve_2& XCV); + +// ------ return area of the simple polygon +Number area(const CSPolygon P); + +// ------ return area of the polygon with (optional) holes +Number area(const CSPolygonWithHoles& P); +} + +#endif //CARTOCROW_CS_POLYGON_HELPERS_H diff --git a/cartocrow/simplesets/point_voronoi_helpers.h b/cartocrow/simplesets/helpers/point_voronoi_helpers.h similarity index 98% rename from cartocrow/simplesets/point_voronoi_helpers.h rename to cartocrow/simplesets/helpers/point_voronoi_helpers.h index addb9043..7f0fcf71 100644 --- a/cartocrow/simplesets/point_voronoi_helpers.h +++ b/cartocrow/simplesets/helpers/point_voronoi_helpers.h @@ -1,12 +1,12 @@ #ifndef CARTOCROW_POINT_VORONOI_HELPERS_H #define CARTOCROW_POINT_VORONOI_HELPERS_H -#include "../core/core.h" -#include +#include "cartocrow/core/core.h" #include -#include -#include #include +#include +#include +#include using namespace cartocrow; //typedef CGAL::Delaunay_triangulation_2 DT; diff --git a/cartocrow/simplesets/island.h b/cartocrow/simplesets/island.h deleted file mode 100644 index 42020406..00000000 --- a/cartocrow/simplesets/island.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef CARTOCROW_ISLAND_H -#define CARTOCROW_ISLAND_H - -#include "pattern.h" -#include - -namespace cartocrow::simplesets { -typedef CGAL::Delaunay_triangulation_2 DT; - -class Island : public Pattern { - public: - Island(std::vector catPoints); - - std::variant, Polygon> contour() override; - std::vector catPoints() override; - Number coverRadius() override; - bool isValid(GeneralSettings gs) override; - - private: - std::vector m_catPoints; - std::vector> m_points; - Number m_coverRadius; - Polygon m_polygon; -}; -} - -#endif //CARTOCROW_ISLAND_H diff --git a/cartocrow/simplesets/parse_input.cpp b/cartocrow/simplesets/parse_input.cpp new file mode 100644 index 00000000..d3d6e767 --- /dev/null +++ b/cartocrow/simplesets/parse_input.cpp @@ -0,0 +1,44 @@ +#include "parse_input.h" + +namespace cartocrow::simplesets { +std::string getNextLine(std::istream& str) { + std::string line; + std::getline(str,line); + return line; +} + +std::vector splitIntoTokens(const std::string& line, char delimiter) { + std::vector result; + std::stringstream lineStream(line); + std::string cell; + + while(std::getline(lineStream, cell, delimiter)) + { + result.push_back(cell); + } + // This checks for a trailing comma with no data after it. + if (!lineStream && cell.empty()) + { + // If there was a trailing comma then add an empty element. + result.emplace_back(""); + } + return result; +} + +std::vector parseCatPoints(const std::string& s) { + std::stringstream ss(s); + + std::vector result; + + while (ss) { + auto parts = splitIntoTokens(getNextLine(ss), ' '); + if (parts.size() <= 1) break; + if (parts.size() != 3) { + throw std::runtime_error("Input has incorrect format."); + } + result.emplace_back(stoi(parts[0]), Point(stod(parts[1]), -stod(parts[2]))); + } + + return result; +} +} diff --git a/cartocrow/simplesets/parse_input.h b/cartocrow/simplesets/parse_input.h new file mode 100644 index 00000000..0831ee37 --- /dev/null +++ b/cartocrow/simplesets/parse_input.h @@ -0,0 +1,10 @@ +#ifndef CARTOCROW_PARSE_INPUT_H +#define CARTOCROW_PARSE_INPUT_H + +#include "cat_point.h" + +namespace cartocrow::simplesets { +std::vector parseCatPoints(const std::string& s); +} + +#endif //CARTOCROW_PARSE_INPUT_H diff --git a/cartocrow/simplesets/partition.h b/cartocrow/simplesets/partition.h new file mode 100644 index 00000000..6c563bdc --- /dev/null +++ b/cartocrow/simplesets/partition.h @@ -0,0 +1,10 @@ +#ifndef CARTOCROW_PARTITION_H +#define CARTOCROW_PARTITION_H + +#include "patterns/poly_pattern.h" + +namespace cartocrow::simplesets { +typedef std::vector> Partition; +} + +#endif //CARTOCROW_PARTITION_H diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp new file mode 100644 index 00000000..3fd39517 --- /dev/null +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -0,0 +1,292 @@ +#include "partition_algorithm.h" +#include "dilated/dilated_poly.h" + +#include + +#include +#include "helpers/cs_polygon_helpers.h" + +namespace cartocrow::simplesets { + +Number squared(Number x) { + return x * x; +} + +std::variant to_bank_or_island(PolyPattern* polyPattern) { + if (auto bp = dynamic_cast(polyPattern)) { + return *bp; + } else if (auto ip = dynamic_cast(polyPattern)) { + return *ip; + } else if (auto spp = dynamic_cast(polyPattern)) { + return Bank(spp->catPoints()); + } else if (auto mp = dynamic_cast(polyPattern)) { + return Bank(mp->catPoints()); + } else { + throw std::runtime_error("Unknown PolyPattern."); + } +} + +Number squared_distance( + const std::variant, Polygon>& contour, + const Point& p) { + return std::visit([&p](auto& poly) { + Number min_sqrd_dist = std::numeric_limits::infinity(); + + for (auto eit = poly.edges_begin(); eit != poly.edges_end(); ++eit) { + auto seg = *eit; + auto dist = CGAL::squared_distance(seg, p); + if (dist < min_sqrd_dist) { + min_sqrd_dist = dist; + } + } + + return min_sqrd_dist; + }, contour); +} + +bool is_inside(const Point& point, const Polygon& polygon) { + return !polygon.has_on_unbounded_side(point); +} + +bool is_inside(const Point& point, const Polyline& polygon) { + return false; +} + +bool do_intersect( + const std::variant, Polygon>& cont1, + const std::variant, Polygon>& cont2) { + return std::visit([&cont2](auto& poly1) { + return std::visit([&poly1](auto& poly2) { + for (auto eit = poly1.edges_begin(); eit != poly1.edges_end(); ++eit) { + for (auto eitt = poly2.edges_begin(); eitt != poly2.edges_end(); ++eitt) { + if (CGAL::do_intersect(*eit, *eitt)) { + return true; + } + } + } + + return is_inside(poly1.vertex(0), poly2) || is_inside(poly2.vertex(0), poly1); + }, cont2); + }, cont1); +} + +Number intersectionDelay(const std::vector& points, const std::shared_ptr& p1, const std::shared_ptr& p2, + const std::shared_ptr& result, const GeneralSettings& gs, const PartitionSettings ps) { + // todo: check consistency with paper + if (!ps.intersectionDelay) return 0; + Number intersectionArea = 0; + auto& resultPts = result->catPoints(); + auto resultPoly = result->poly(); + for (const auto& pt : points) { + if (std::find(resultPts.begin(), resultPts.end(), pt) == resultPts.end() && + squared_distance(resultPoly, pt.point) < gs.dilationRadius() * 2) { + // note that Circle requires us to pass the squared radius + CSPolygon ptShape = dilatePattern(SinglePoint(pt), gs.dilationRadius()); + CSPolygon rShape = dilatePattern(*result, gs.dilationRadius()); + std::vector inters; + CGAL::intersection(rShape, ptShape, std::back_inserter(inters)); + Number newArea = 0; + for (const auto& gp : inters) { + newArea += area(gp); + } + inters.clear(); + CSPolygon p1Shape = dilatePattern(*p1, gs.dilationRadius()); + CSPolygon p2Shape = dilatePattern(*p2, gs.dilationRadius()); + CGAL::intersection(p1Shape, ptShape, std::back_inserter(inters)); + CGAL::intersection(p2Shape, ptShape, std::back_inserter(inters)); + Number oldArea = 0; + for (const auto& gp : inters) { + oldArea += area(gp); + } + intersectionArea += newArea - oldArea; + } + } + + return sqrt(intersectionArea / M_PI); +} + +std::vector, Partition>> +partition(const std::vector& points, const GeneralSettings& gs, const PartitionSettings& ps, Number maxTime) { + // Create initial partition consisting of single points + std::vector> initialPatterns; + std::transform(points.begin(), points.end(), std::back_inserter(initialPatterns), [](const auto& pt) + { return std::make_shared(pt); }); + Partition partition(initialPatterns); + + std::vector, Partition>> history; + history.emplace_back(0, partition); + + // Priority queue storing events based on their time + auto comparison = [](const PossibleMergeEvent& e1, const PossibleMergeEvent& e2) { return e1.time > e2.time; }; + std::priority_queue, decltype(comparison)> events{comparison}; + + // Add SinglePoint--SinglePoint merges + for (int i = 0; i < partition.size(); ++i) { + auto& p = dynamic_cast(*partition[i]); + for (int j = i + 1; j < partition.size(); ++j) { + auto& q = dynamic_cast(*partition[j]); + if (p.category() != q.category()) continue; + if (squared_distance(p.catPoint().point, q.catPoint().point) > squared(2 * maxTime)) continue; + + auto newPattern = std::make_shared(p.catPoint(), q.catPoint()); + Segment seg(p.catPoint().point, q.catPoint().point); + + bool tooClose = false; + for (const CatPoint& pt : points) { + // Check if a point is too close to the segment + if (pt != p.catPoint() && pt != q.catPoint() && + CGAL::squared_distance(seg, pt.point) < squared(ps.admissableRadiusFactor * gs.dilationRadius())) { + tooClose = true; + break; + } + } + if (tooClose) continue; + + PossibleMergeEvent event(newPattern->coverRadius(), partition[i], partition[j], newPattern, false); + events.push(event); + } + } + + while (!events.empty()) { + auto ev = events.top(); + events.pop(); + std::cout << ev.time << std::endl; + + if (ev.time > maxTime) break; + + if (!ev.final) { + // todo: compute intersection delay + ev.time += intersectionDelay(points, ev.p1, ev.p2, ev.result, gs, ps); + ev.final = true; + events.push(ev); + continue; + } + + // Check if patterns that events wants to merge still exist + bool foundP1 = false; + bool foundP2 = false; + for (const auto& pattern : partition) { + if (pattern == ev.p1) { + foundP1 = true; + } + if (pattern == ev.p2) { + foundP2 = true; + } + } + if (!foundP1 || !foundP2) continue; + + auto& newPts = ev.result->catPoints(); + auto newPoly = ev.result->poly(); + + // Check for intersections with existing patterns + bool intersects = false; + for (const auto& pattern : partition) { + if (pattern != ev.p1 && pattern != ev.p2 && do_intersect(pattern->poly(), newPoly)) { + intersects = true; + break; + } + } + if (intersects) continue; + + bool tooClose = false; + // Check whether any point is too close to the result pattern + for (const auto& pt : points) { + if (std::find(newPts.begin(), newPts.end(), pt) == newPts.end() && + squared_distance(newPoly, pt.point) < squared(ps.admissableRadiusFactor * gs.dilationRadius())) { + tooClose = true; + break; + } + } + if (tooClose) continue; + + // Merge + // Remove the two patterns that are merged + auto startTrash = std::remove_if(partition.begin(), partition.end(), + [&ev](const auto& part) { return part == ev.p1 || part == ev.p2; }); + partition.erase(startTrash, partition.end()); + // Add the result + partition.push_back(ev.result); + // Save this partition + history.emplace_back(ev.time, partition); + std::cout << ev.result->poly().index() << std::endl; + + // Create new merge events + for (const auto& pattern : partition) { + if (pattern == ev.result || pattern->category() != ev.result->category()) continue; + + if (ps.islands) { + // Do relatively cheap check first: are the points in the two patterns close enough to ever form a pattern? + Number min_sqrd_dist = std::numeric_limits::infinity(); + for (const auto& p : pattern->catPoints()) { + for (const auto& q : newPts) { + auto dist = squared_distance(p.point, q.point); + if (dist < min_sqrd_dist) { + min_sqrd_dist = dist; + } + } + } + + if (min_sqrd_dist <= squared(2 * maxTime)) { + std::vector mergedPoints; + std::copy(newPts.begin(), newPts.end(), std::back_inserter(mergedPoints)); + std::copy(pattern->catPoints().begin(), pattern->catPoints().end(), std::back_inserter(mergedPoints)); + auto newIsland = std::make_shared(mergedPoints); + + // todo? do intersection check here already? check how this affects performance + + Number regDelay = !ps.regularityDelay ? 0 : newIsland->coverRadius() - std::max(pattern->coverRadius(), ev.result->coverRadius()); + Number eventTime = newIsland->coverRadius() + regDelay; + PossibleMergeEvent newEvent(eventTime, ev.result, pattern, newIsland, false); + if (eventTime <= maxTime) { + events.push(newEvent); + } + } + } + + if (ps.banks) { + auto pat1 = to_bank_or_island(&*pattern); + auto pat2 = to_bank_or_island(&*ev.result); + + if (std::holds_alternative(pat1) && std::holds_alternative(pat2)) { + Bank bank1 = std::get(pat1); + Bank bank2 = std::get(pat2); + auto pts1 = bank1.catPoints(); + auto pts2 = bank2.catPoints(); + + std::vector b1Pts; + std::copy(pts1.begin(), pts1.end(), std::back_inserter(b1Pts)); + std::copy(pts2.begin(), pts2.end(), std::back_inserter(b1Pts)); + auto b1 = std::make_shared(b1Pts); + + std::vector b2Pts; + std::copy(pts1.begin(), pts1.end(), std::back_inserter(b2Pts)); + std::copy(pts2.rbegin(), pts2.rend(), std::back_inserter(b2Pts)); + auto b2 = std::make_shared(b2Pts); + + std::vector b3Pts; + std::copy(pts1.rbegin(), pts1.rend(), std::back_inserter(b3Pts)); + std::copy(pts2.rbegin(), pts2.rend(), std::back_inserter(b3Pts)); + auto b3 = std::make_shared(b3Pts); + + std::vector b4Pts; + std::copy(pts1.rbegin(), pts1.rend(), std::back_inserter(b4Pts)); + std::copy(pts2.begin(), pts2.end(), std::back_inserter(b4Pts)); + auto b4 = std::make_shared(b4Pts); + + for (auto& b : {b1, b2, b3, b4}) { + if (!b->isValid(gs)) continue; + Number regDelay = !ps.regularityDelay ? 0 : b->coverRadius() - std::max(ev.result->coverRadius(), pattern->coverRadius()); + Number eventTime = b->coverRadius() + regDelay; + PossibleMergeEvent newEvent(eventTime * 1, ev.result, pattern, b, false); + if (eventTime <= maxTime) { + events.push(newEvent); + } + } + } + } + } + } + + return history; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/partition_algorithm.h b/cartocrow/simplesets/partition_algorithm.h new file mode 100644 index 00000000..c8b1a70f --- /dev/null +++ b/cartocrow/simplesets/partition_algorithm.h @@ -0,0 +1,26 @@ +#ifndef CARTOCROW_PARTITION_ALGORITHM_H +#define CARTOCROW_PARTITION_ALGORITHM_H + +#include "types.h" +#include "cat_point.h" +#include "partition.h" +#include "patterns/bank.h" +#include "patterns/island.h" +#include "patterns/matching.h" +#include "patterns/single_point.h" +#include "settings.h" + +namespace cartocrow::simplesets { +struct PossibleMergeEvent { + Number time; + std::shared_ptr p1; + std::shared_ptr p2; + std::shared_ptr result; + /// We compute expensive delays lazily. This boolean indicates whether all delays have been computed. + bool final; +}; + +std::vector, Partition>> +partition(const std::vector& points, const GeneralSettings& gs, const PartitionSettings& ps, Number maxTime); +} +#endif //CARTOCROW_PARTITION_ALGORITHM_H diff --git a/cartocrow/simplesets/partition_painting.cpp b/cartocrow/simplesets/partition_painting.cpp new file mode 100644 index 00000000..904fb2ea --- /dev/null +++ b/cartocrow/simplesets/partition_painting.cpp @@ -0,0 +1,36 @@ +#include "partition_painting.h" + +namespace cartocrow::simplesets { + +void draw_poly_pattern(const PolyPattern& pattern, renderer::GeometryRenderer& renderer, const GeneralSettings& gs, const DrawSettings& ds) { + if (std::holds_alternative>(pattern.poly())) { + renderer.setMode(renderer::GeometryRenderer::stroke); + } else { + renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); + } + renderer.setFill(ds.colors.at(pattern.category())); + renderer.setFillOpacity(100); + // todo: set absolute stroke width here. +// renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs)); + renderer.setStroke(Color{0, 0, 0}, 1.0); + std::visit([&renderer](auto shape){ renderer.draw(shape); }, pattern.poly()); + for (const auto& pt : pattern.catPoints()) { + // todo: set absolute stroke width here. +// renderer.setStroke(Color{0, 0, 0}, ds.pointStrokeWeight(gs)); + renderer.setStroke(Color{0, 0, 0}, 1.0); + renderer.setFillOpacity(255); + renderer.setFill(ds.colors.at(pt.category)); + renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); + renderer.draw(Circle{pt.point, gs.pointSize}); + } +} + +PartitionPainting::PartitionPainting(const Partition& partition, const GeneralSettings& gs, const DrawSettings& ds) + : m_partition(partition), m_gs(gs), m_ds(ds) {} + +void PartitionPainting::paint(renderer::GeometryRenderer& renderer) const { + for (const auto& pattern : m_partition) { + draw_poly_pattern(*pattern, renderer, m_gs, m_ds); + } +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/partition_painting.h b/cartocrow/simplesets/partition_painting.h new file mode 100644 index 00000000..141f1ced --- /dev/null +++ b/cartocrow/simplesets/partition_painting.h @@ -0,0 +1,24 @@ +#ifndef CARTOCROW_PARTITION_PAINTING_H +#define CARTOCROW_PARTITION_PAINTING_H + +#include "types.h" +#include "settings.h" +#include "partition.h" +#include "../renderer/geometry_painting.h" + +namespace cartocrow::simplesets { +class PartitionPainting : public renderer::GeometryPainting { + public: + PartitionPainting(const Partition& partition, const GeneralSettings& gs, const DrawSettings& ds); + void paint(renderer::GeometryRenderer& renderer) const override; + + private: + const Partition& m_partition; + const GeneralSettings& m_gs; + const DrawSettings& m_ds; +}; + +void draw_poly_pattern(const PolyPattern& pattern, renderer::GeometryRenderer& renderer, const GeneralSettings& gs, const DrawSettings& ds); +} + +#endif //CARTOCROW_PARTITION_PAINTING_H diff --git a/cartocrow/simplesets/pattern.h b/cartocrow/simplesets/pattern.h deleted file mode 100644 index ddb307ed..00000000 --- a/cartocrow/simplesets/pattern.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CARTOCROW_PATTERN_H -#define CARTOCROW_PATTERN_H - -#include "../core/core.h" -#include "../core/polyline.h" -#include -#include "types.h" -#include "cat_point.h" -#include "settings.h" - -namespace cartocrow::simplesets { -class Pattern { - public: - virtual std::variant, Polygon> contour() = 0; - virtual std::vector catPoints() = 0; - virtual Number coverRadius() = 0; - virtual bool isValid(GeneralSettings gs) = 0; -}; -} - -#endif //CARTOCROW_PATTERN_H diff --git a/cartocrow/simplesets/bank.cpp b/cartocrow/simplesets/patterns/bank.cpp similarity index 77% rename from cartocrow/simplesets/bank.cpp rename to cartocrow/simplesets/patterns/bank.cpp index fe7f647e..a1f9b65d 100644 --- a/cartocrow/simplesets/bank.cpp +++ b/cartocrow/simplesets/patterns/bank.cpp @@ -7,8 +7,11 @@ Bank::Bank(std::vector catPoints): m_catPoints(std::move(catPoints)) { return cp.point; }); + // Store polyline + m_polyline = Polyline(m_points); + // Compute the cover radius - std::optional> maxSquaredDistance; + std::optional> maxSquaredDistance; for (int i = 0; i < m_points.size() - 1; i++) { auto p = m_points[i]; auto q = m_points[i+1]; @@ -23,9 +26,7 @@ Bank::Bank(std::vector catPoints): m_catPoints(std::move(catPoints)) { computeBends(); } -Number approximateAngleBetween(const Vector& exact_v, const Vector& exact_w) { - auto v = approximate(exact_v); - auto w = approximate(exact_w); +Number computeAngleBetween(const Vector& v, const Vector& w) { return acos((v * w) / (sqrt(v.squared_length()) * sqrt(w.squared_length()))); } @@ -33,14 +34,14 @@ void Bank::computeBends() { m_bends.clear(); std::optional orientation; - Number bendTotalAngle = 0; - Number bendMaxAngle = 0; + Number bendTotalAngle = 0; + Number bendMaxAngle = 0; int startIndex = 0; for (int i = 0; i < m_points.size(); i++) { if (i + 2 > m_points.size() - 1) break; auto orient = CGAL::orientation(m_points[i], m_points[i+1], m_points[i+2]); - auto angle = approximateAngleBetween(m_points[i+1] - m_points[i], m_points[i+2] - m_points[i+1]); + auto angle = computeAngleBetween(m_points[i+1] - m_points[i], m_points[i+2] - m_points[i+1]); if (orientation == -orient) { // Switched orientation m_bends.emplace_back(*orientation, bendMaxAngle, bendTotalAngle, startIndex, i+1); @@ -60,15 +61,15 @@ void Bank::computeBends() { } } -std::variant, Polygon> Bank::contour() { +std::variant, Polygon> Bank::poly() const { return m_polyline; } -std::vector Bank::catPoints() { +const std::vector& Bank::catPoints() const { return m_catPoints; } -bool Bank::isValid(GeneralSettings gs) { +bool Bank::isValid(const GeneralSettings& gs) const { bool inflectionIsFine = m_bends.size() <= gs.inflectionLimit; bool anglesAreFine = true; for (const auto& bend : m_bends) { @@ -83,7 +84,7 @@ bool Bank::isValid(GeneralSettings gs) { return inflectionIsFine && anglesAreFine && totalAngleIsFine; } -Number Bank::coverRadius() { +Number Bank::coverRadius() const { return m_coverRadius; } } \ No newline at end of file diff --git a/cartocrow/simplesets/patterns/bank.h b/cartocrow/simplesets/patterns/bank.h new file mode 100644 index 00000000..28687f8c --- /dev/null +++ b/cartocrow/simplesets/patterns/bank.h @@ -0,0 +1,36 @@ +#ifndef CARTOCROW_BANK_H +#define CARTOCROW_BANK_H + +#include "pattern.h" +#include "poly_pattern.h" + +namespace cartocrow::simplesets { +struct Bend { + CGAL::Orientation orientation; + Number maxAngle; + Number totalAngle; + int startIndex; + int endIndex; +}; + +class Bank : public PolyPattern { + public: + Bank(std::vector catPoints); + + std::variant, Polygon> poly() const override; + const std::vector& catPoints() const override; + Number coverRadius() const override; + bool isValid(const GeneralSettings& gs) const; + + private: + std::vector m_catPoints; + std::vector> m_points; + Number m_coverRadius; + Polyline m_polyline; + std::vector m_bends; + + void computeBends(); +}; +} + +#endif //CARTOCROW_BANK_H diff --git a/cartocrow/simplesets/island.cpp b/cartocrow/simplesets/patterns/island.cpp similarity index 59% rename from cartocrow/simplesets/island.cpp rename to cartocrow/simplesets/patterns/island.cpp index db1b36a7..13f013f9 100644 --- a/cartocrow/simplesets/island.cpp +++ b/cartocrow/simplesets/patterns/island.cpp @@ -1,19 +1,19 @@ #include "island.h" -#include "point_voronoi_helpers.h" +#include "cartocrow/simplesets/helpers/cropped_voronoi.h" +#include "cartocrow/simplesets/helpers/point_voronoi_helpers.h" #include -#include #include -#include "cropped_voronoi.h" +#include namespace cartocrow::simplesets { -Polygon convexHull(const std::vector>& points) { - std::vector> chPoints; +Polygon convexHull(const std::vector>& points) { + std::vector> chPoints; CGAL::convex_hull_2(points.begin(), points.end(), std::back_inserter(chPoints)); return {chPoints.begin(), chPoints.end()}; } -std::optional> convexIntersection(const Polygon p, const Polygon q) { - std::vector> intersection_result; +std::optional> convexIntersection(const Polygon& p, const Polygon& q) { + std::vector> intersection_result; CGAL::intersection(p, q, std::back_inserter(intersection_result)); if (intersection_result.empty()) { return std::nullopt; @@ -22,21 +22,22 @@ std::optional> convexIntersection(const Polygon p, const Polygon coverRadiusOfPoints(const std::vector>& points) { +Number coverRadiusOfPoints(const std::vector>& points) { DT dt; - dt.insert(points.begin(), points.end()); + auto exact_points = makeExact(points); + dt.insert(exact_points.begin(), exact_points.end()); - Rectangle bbox = CGAL::bounding_box(points.begin(), points.end()); - std::optional> squaredCoverRadius; + Rectangle bbox = CGAL::bounding_box(points.begin(), points.end()); + std::optional> squaredCoverRadius; - auto hull = convexHull(points); + auto hull = makeExact(convexHull(points)); Cropped_voronoi_from_delaunay cropped_voronoi(hull); for (auto eit = dt.finite_edges_begin(); eit != dt.finite_edges_end(); ++eit) { CGAL::Object o = dt.dual(eit); - Line l; - Ray r; - Segment s; + Line l; + Ray r; + Segment s; auto site = eit->first->vertex(dt.cw(eit->second))->point(); if(CGAL::assign(s,o)) cropped_voronoi << std::pair(site, s); if(CGAL::assign(r,o)) cropped_voronoi << std::pair(site, r); @@ -45,7 +46,7 @@ Number coverRadiusOfPoints(const std::vector>& points) { for (const auto& [site, seg] : cropped_voronoi.m_cropped_vd) { for (const auto& v : {seg.source(), seg.target()}) { - auto d = squared_distance(v, site); + auto d = squared_distance(approximate(v), approximate(site)); if (!squaredCoverRadius.has_value() || d > *squaredCoverRadius) { squaredCoverRadius = d; } @@ -65,19 +66,16 @@ Island::Island(std::vector catPoints): m_catPoints(std::move(catPoints m_polygon = convexHull(m_points); } -std::variant, Polygon> Island::contour() { +std::variant, Polygon> Island::poly() const { + // todo? return (and store) polyline when m_points are collinear? return m_polygon; } -std::vector Island::catPoints() { +const std::vector& Island::catPoints() const { return m_catPoints; } -bool Island::isValid(cartocrow::simplesets::GeneralSettings gs) { - return true; -} - -Number Island::coverRadius() { +Number Island::coverRadius() const { return m_coverRadius; } } \ No newline at end of file diff --git a/cartocrow/simplesets/patterns/island.h b/cartocrow/simplesets/patterns/island.h new file mode 100644 index 00000000..061ff00c --- /dev/null +++ b/cartocrow/simplesets/patterns/island.h @@ -0,0 +1,27 @@ +#ifndef CARTOCROW_ISLAND_H +#define CARTOCROW_ISLAND_H + +#include "pattern.h" +#include "poly_pattern.h" +#include + +namespace cartocrow::simplesets { +typedef CGAL::Delaunay_triangulation_2 DT; + +class Island : public PolyPattern { + public: + Island(std::vector catPoints); + + std::variant, Polygon> poly() const override; + const std::vector& catPoints() const override; + Number coverRadius() const override; + + private: + std::vector m_catPoints; + std::vector> m_points; + Number m_coverRadius; + Polygon m_polygon; +}; +} + +#endif //CARTOCROW_ISLAND_H diff --git a/cartocrow/simplesets/patterns/matching.cpp b/cartocrow/simplesets/patterns/matching.cpp new file mode 100644 index 00000000..16651670 --- /dev/null +++ b/cartocrow/simplesets/patterns/matching.cpp @@ -0,0 +1,19 @@ +#include "matching.h" + +namespace cartocrow::simplesets { +Matching::Matching(const CatPoint& catPoint1, const CatPoint& catPoint2) : m_catPoints({catPoint1, catPoint2}) {} + +std::variant, Polygon> Matching::poly() const { +// std::vector> pts({m_catPoints.first.point, m_catPoints.second.point}); +// return Polygon(pts.begin(), pts.end()); + return Polyline(std::vector({m_catPoints[0].point, m_catPoints[1].point})); +} + +const std::vector& Matching::catPoints() const { + return m_catPoints; +} + +Number Matching::coverRadius() const { + return sqrt(squared_distance(m_catPoints[0].point, m_catPoints[1].point)) / 2; +} +} diff --git a/cartocrow/simplesets/patterns/matching.h b/cartocrow/simplesets/patterns/matching.h new file mode 100644 index 00000000..89cfe615 --- /dev/null +++ b/cartocrow/simplesets/patterns/matching.h @@ -0,0 +1,19 @@ +#ifndef CARTOCROW_MATCHING_H +#define CARTOCROW_MATCHING_H + +#include "pattern.h" +#include "poly_pattern.h" + +namespace cartocrow::simplesets { +class Matching : public PolyPattern { + public: + Matching(const CatPoint& catPoint1, const CatPoint& catPoint2); + std::variant, Polygon> poly() const override; + const std::vector& catPoints() const override; + Number coverRadius() const override; + + private: + std::vector m_catPoints; +}; +} +#endif //CARTOCROW_MATCHING_H diff --git a/cartocrow/simplesets/pattern.cpp b/cartocrow/simplesets/patterns/pattern.cpp similarity index 100% rename from cartocrow/simplesets/pattern.cpp rename to cartocrow/simplesets/patterns/pattern.cpp diff --git a/cartocrow/simplesets/patterns/pattern.h b/cartocrow/simplesets/patterns/pattern.h new file mode 100644 index 00000000..ae419bad --- /dev/null +++ b/cartocrow/simplesets/patterns/pattern.h @@ -0,0 +1,23 @@ +#ifndef CARTOCROW_PATTERN_H +#define CARTOCROW_PATTERN_H + +#include "cartocrow/core/core.h" +#include "cartocrow/core/polyline.h" +#include "cartocrow/simplesets/cat_point.h" +#include "cartocrow/simplesets/settings.h" +#include "cartocrow/simplesets/types.h" +#include + +namespace cartocrow::simplesets { +class Pattern { + public: + virtual std::variant, Polygon, CSPolygon> contour() const = 0; + virtual const std::vector& catPoints() const = 0; + /// Return the category to which the points of this category belong.. + int category() const { + return catPoints()[0].category; + } +}; +} + +#endif //CARTOCROW_PATTERN_H diff --git a/cartocrow/simplesets/patterns/poly_pattern.h b/cartocrow/simplesets/patterns/poly_pattern.h new file mode 100644 index 00000000..15b0738a --- /dev/null +++ b/cartocrow/simplesets/patterns/poly_pattern.h @@ -0,0 +1,36 @@ +#ifndef CARTOCROW_POLYPATTERN_H +#define CARTOCROW_POLYPATTERN_H + +#include "pattern.h" + +namespace cartocrow::simplesets { +template +struct variant_cast_proxy +{ + std::variant v; + + template + operator std::variant() const + { + return std::visit([](auto&& arg) -> std::variant { return arg ; }, + v); + } +}; + +template +auto variant_cast(const std::variant& v) -> variant_cast_proxy +{ + return {v}; +} + +class PolyPattern : public Pattern { + public: + virtual std::variant, Polygon> poly() const = 0; + std::variant, Polygon, CSPolygon> contour() const override { + return variant_cast(poly()); + }; + virtual Number coverRadius() const = 0; +}; +} + +#endif //CARTOCROW_POLYPATTERN_H diff --git a/cartocrow/simplesets/patterns/single_point.cpp b/cartocrow/simplesets/patterns/single_point.cpp new file mode 100644 index 00000000..63a075f2 --- /dev/null +++ b/cartocrow/simplesets/patterns/single_point.cpp @@ -0,0 +1,22 @@ +#include "single_point.h" + +namespace cartocrow::simplesets { +SinglePoint::SinglePoint(CatPoint catPoint): m_catPoints({catPoint}) {} + +std::variant, Polygon> SinglePoint::poly() const { + std::vector> pts({m_catPoints[0].point}); + return Polygon(pts.begin(), pts.end()); +} + +const std::vector& SinglePoint::catPoints() const { + return m_catPoints; +} + +Number SinglePoint::coverRadius() const { + return 0; +} + +CatPoint SinglePoint::catPoint() const { + return m_catPoints[0]; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/patterns/single_point.h b/cartocrow/simplesets/patterns/single_point.h new file mode 100644 index 00000000..fa40d976 --- /dev/null +++ b/cartocrow/simplesets/patterns/single_point.h @@ -0,0 +1,21 @@ +#ifndef CARTOCROW_SINGLE_POINT_H +#define CARTOCROW_SINGLE_POINT_H + +#include "pattern.h" +#include "poly_pattern.h" + +namespace cartocrow::simplesets { +class SinglePoint : public PolyPattern { + public: + SinglePoint(CatPoint catPoint); + std::variant, Polygon> poly() const override; + const std::vector& catPoints() const override; + Number coverRadius() const override; + CatPoint catPoint() const; + + private: + std::vector m_catPoints; +}; +} + +#endif //CARTOCROW_SINGLE_POINT_H diff --git a/cartocrow/simplesets/settings.h b/cartocrow/simplesets/settings.h index 36a6e0d2..f40c35ba 100644 --- a/cartocrow/simplesets/settings.h +++ b/cartocrow/simplesets/settings.h @@ -6,13 +6,18 @@ namespace cartocrow::simplesets { struct GeneralSettings { /// Radius of circle that represents a point. - Number pointSize; + Number pointSize; /// Maximum number of inflections a bank is allowed to have. int inflectionLimit; /// Maximum total angle of a bend (maximum monotone subsequence of a bank). - Number maxBendAngle; + Number maxBendAngle; /// Maximum turning angle in a bank. - Number maxTurnAngle; + Number maxTurnAngle; + + /// The distance each pattern is dilated. + Number dilationRadius() const { + return pointSize * 3; + } }; struct PartitionSettings { @@ -26,20 +31,20 @@ struct PartitionSettings { /// Delay merges that create patterns that intersect points. bool intersectionDelay; /// Disallow merges that have a point within distance admissableFactor * dilationRadius. - Number admissableRadiusFactor; + Number admissableRadiusFactor; }; struct ComputeDrawingSettings { /// Aim to keep a disk around each point visible of radius cutoutRadiusFactor * dilationRadius. - Number cutoutRadiusFactor; + Number cutoutRadiusFactor; }; struct DrawSettings { std::vector colors; - Number pointStrokeWeight(GeneralSettings gs) { + Number pointStrokeWeight(GeneralSettings gs) const { return gs.pointSize / 2.5; } - Number contourStrokeWeight(GeneralSettings gs) { + Number contourStrokeWeight(GeneralSettings gs) const { return gs.pointSize / 3.5; } }; diff --git a/cartocrow/simplesets/types.cpp b/cartocrow/simplesets/types.cpp new file mode 100644 index 00000000..c8c949c9 --- /dev/null +++ b/cartocrow/simplesets/types.cpp @@ -0,0 +1,25 @@ +#include "types.h" + +namespace cartocrow::simplesets { +std::vector> makeExact(const std::vector>& points) { + std::vector> exact_points; + std::transform(points.begin(), points.end(), std::back_inserter(exact_points), + [](const Point& pt) { return makeExact(pt); }); + return exact_points; +} + +Point makeExact(const Point& point) { + return {point.x(), point.y()}; +} + +Polygon makeExact(const Polygon& polygon) { + std::vector> exact_points; + std::transform(polygon.vertices_begin(), polygon.vertices_end(), std::back_inserter(exact_points), + [](const Point& pt) { return makeExact(pt); }); + return {exact_points.begin(), exact_points.end()}; +} + +Point approximateAlgebraic(const CSTraits::Point_2& algebraic_point) { + return {CGAL::to_double(algebraic_point.x()), CGAL::to_double(algebraic_point.y())}; +} +} diff --git a/cartocrow/simplesets/types.h b/cartocrow/simplesets/types.h index 947b43ce..9c511140 100644 --- a/cartocrow/simplesets/types.h +++ b/cartocrow/simplesets/types.h @@ -2,9 +2,32 @@ #define CARTOCROW_TYPES_H #include "../core/core.h" +#include +#include +#include +#include +#include +#include namespace cartocrow::simplesets { -typedef ExactWithSqrt K; +//typedef Exact K; +typedef CGAL::Arr_circle_segment_traits_2 CSTraits; +typedef CGAL::Gps_circle_segment_traits_2 CSTraitsBoolean; +typedef CSTraitsBoolean::Polygon_2 CSPolygon; +typedef CSTraitsBoolean::Polygon_with_holes_2 CSPolygonWithHoles; +typedef CGAL::Arrangement_2 CSArrangement; +//typedef CGAL::CORE_algebraic_number_traits Nt_traits; +//typedef CGAL::Cartesian Rat_kernel; +//typedef Nt_traits::Algebraic Algebraic; +//typedef CGAL::Cartesian Alg_kernel; +//typedef CGAL::Arr_conic_traits_2 ConicTraits; +//typedef CGAL::Gps_traits_2 Gps_traits; +//typedef Alg_kernel K; + +Point makeExact(const Point& point); +std::vector> makeExact(const std::vector>& points); +Polygon makeExact(const Polygon& polygon); +Point approximateAlgebraic(const CSTraits::Point_2& algebraic_point); } #endif //CARTOCROW_TYPES_H diff --git a/demos/simplesets/CMakeLists.txt b/demos/simplesets/CMakeLists.txt index bea16e48..86f6d49e 100644 --- a/demos/simplesets/CMakeLists.txt +++ b/demos/simplesets/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES simplesets_demo.cpp + colors.cpp ) add_executable(simplesets_demo ${SOURCES}) diff --git a/demos/simplesets/colors.cpp b/demos/simplesets/colors.cpp new file mode 100644 index 00000000..1a04bdb2 --- /dev/null +++ b/demos/simplesets/colors.cpp @@ -0,0 +1,14 @@ +#include "colors.h" + +namespace cartocrow::CB { +Color light_blue(166,206,227); +Color blue(31,120,180); +Color light_green(178,223,138); +Color green(51,160,44); +Color light_red(251,154,153); +Color red(227,26,28); +Color light_orange(253,191,111); +Color orange(255,127,0); +Color light_purple(202,178,214); +Color purple(106,61,154); +} \ No newline at end of file diff --git a/demos/simplesets/colors.h b/demos/simplesets/colors.h new file mode 100644 index 00000000..3393e399 --- /dev/null +++ b/demos/simplesets/colors.h @@ -0,0 +1,20 @@ +#ifndef CARTOCROW_COLORS_H +#define CARTOCROW_COLORS_H + +#include "cartocrow/core/core.h" + +namespace cartocrow::CB { + extern Color light_blue; + extern Color blue; + extern Color light_green; + extern Color green; + extern Color light_red; + extern Color red; + extern Color light_orange; + extern Color orange; + extern Color light_purple; + extern Color purple; +} + + +#endif //CARTOCROW_COLORS_H diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 30b8ee31..2827762e 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -17,21 +17,29 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include "simplesets_demo.h" #include "cartocrow/core/ipe_reader.h" #include "cartocrow/renderer/ipe_renderer.h" +#include "cartocrow/simplesets/patterns/bank.h" +#include "cartocrow/simplesets/patterns/island.h" +#include "cartocrow/simplesets/patterns/matching.h" +#include "cartocrow/simplesets/patterns/single_point.h" +#include "cartocrow/simplesets/parse_input.h" +#include "cartocrow/simplesets/partition_algorithm.h" +#include "cartocrow/simplesets/partition_painting.h" +#include "cartocrow/simplesets/drawing_algorithm.h" #include #include #include #include #include #include +#include #include #include #include -#include -#include "simplesets_demo.h" -#include "cartocrow/simplesets/island.h" -#include "cartocrow/simplesets/bank.h" +#include "cartocrow/simplesets/dilated/dilated_poly.h" +#include "colors.h" namespace fs = std::filesystem; using namespace cartocrow; @@ -63,10 +71,39 @@ SimpleSetsDemo::SimpleSetsDemo() { renderer->setMinZoom(0.01); renderer->setMaxZoom(1000.0); - Island island({{{1, 1}, 0}, {{2, 1}, 0}, {{3, 1.1}, 0}}); - Bank bank({{{1, 1}, 0}, {{2, 1}, 0}, {{3, 1.1}, 0}}); - std::cout << island.coverRadius() << std::endl; - std::cout << bank.coverRadius() << std::endl; + std::filesystem::path filePath("/home/steven/Documents/cartocrow/data/nyc.txt"); + std::ifstream inputStream(filePath, std::ios_base::in); + if (!inputStream.good()) { + throw std::runtime_error("Failed to read input"); + } + std::stringstream buffer; + buffer << inputStream.rdbuf(); + auto nycPoints = parseCatPoints(buffer.str()); + + m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; +// m_ps = PartitionSettings{true, true, true, true, 0.5}; todo: fix intersection delay crash + m_ps = PartitionSettings{true, true, true, false, 0.5}; + m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}}; + m_cds = ComputeDrawingSettings{0.675}; + auto partitionList = partition(nycPoints, m_gs, m_ps, 8 * m_gs.dilationRadius()); + std::cout << partitionList.size() << std::endl; + + Number cover = 4.7; + + Partition* thePartition; + for (auto& [time, partition] : partitionList) { + if (time < cover * m_gs.dilationRadius()) { + thePartition = &partition; + } + } + m_partition = *thePartition; + + auto pp = std::make_shared(m_partition, m_gs, m_ds); + renderer->addPainting(pp, "Partition"); + + m_arr = dilateAndArrange(m_partition, m_gs, m_cds); + auto ap = std::make_shared(m_arr, m_ds, m_partition); + renderer->addPainting(ap, "Arrangement"); } int main(int argc, char* argv[]) { diff --git a/demos/simplesets/simplesets_demo.h b/demos/simplesets/simplesets_demo.h index da1380fa..1a7b0424 100644 --- a/demos/simplesets/simplesets_demo.h +++ b/demos/simplesets/simplesets_demo.h @@ -25,6 +25,8 @@ along with this program. If not, see . #include "cartocrow/renderer/geometry_painting.h" #include "cartocrow/renderer/geometry_widget.h" #include "cartocrow/simplesets/types.h" +#include "cartocrow/simplesets/partition.h" +#include "cartocrow/simplesets/drawing_algorithm.h" #include using namespace cartocrow; @@ -38,7 +40,12 @@ class SimpleSetsDemo : public QMainWindow { SimpleSetsDemo(); private: - + Partition m_partition; + DilatedPatternArrangement m_arr; + GeneralSettings m_gs; + DrawSettings m_ds; + PartitionSettings m_ps; + ComputeDrawingSettings m_cds; }; #endif //CARTOCROW_SIMPLESETS_DEMO_H From 0a33517df62f75b147be4ae7fc3f0e2d153ac04d Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Thu, 15 Aug 2024 23:30:44 +0200 Subject: [PATCH 03/36] Use absolute stroke width --- cartocrow/simplesets/drawing_algorithm.cpp | 8 ++++---- cartocrow/simplesets/drawing_algorithm.h | 4 +++- cartocrow/simplesets/partition_painting.cpp | 8 ++------ demos/simplesets/simplesets_demo.cpp | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 52c1d1bc..11cd77ce 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -181,9 +181,9 @@ dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const Co return arr; } -ArrangementPainting::ArrangementPainting(const DilatedPatternArrangement& arr, const DrawSettings& ds, - const Partition& partition) - : m_arr(arr), m_ds(ds), m_partition(partition) {}; +ArrangementPainting::ArrangementPainting(const DilatedPatternArrangement& arr, const GeneralSettings& gs, + const DrawSettings& ds, const Partition& partition) + : m_arr(arr), m_ds(ds), m_gs(gs), m_partition(partition) {}; void ArrangementPainting::paint(renderer::GeometryRenderer& renderer) const { renderer.setMode(renderer::GeometryRenderer::fill); @@ -212,7 +212,7 @@ void ArrangementPainting::paint(renderer::GeometryRenderer& renderer) const { } renderer.setMode(renderer::GeometryRenderer::stroke); - renderer.setStroke(Color{0, 0, 0}, 3.0); + renderer.setStroke(Color{0, 0, 0}, m_ds.contourStrokeWeight(m_gs), true); for (auto eit = m_arr.edges_begin(); eit != m_arr.edges_end(); ++eit) { auto curve = eit->curve(); if (curve.is_linear()) { diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index e8dd7ad6..201d5bdc 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -18,12 +18,14 @@ dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const Co class ArrangementPainting : public renderer::GeometryPainting { public: - ArrangementPainting(const DilatedPatternArrangement& arr, const DrawSettings& ds, const Partition& partition); + ArrangementPainting(const DilatedPatternArrangement& arr, const GeneralSettings& gs, + const DrawSettings& ds, const Partition& partition); void paint(renderer::GeometryRenderer& renderer) const override; private: const DilatedPatternArrangement& m_arr; const DrawSettings& m_ds; + const GeneralSettings& m_gs; const Partition& m_partition; }; } diff --git a/cartocrow/simplesets/partition_painting.cpp b/cartocrow/simplesets/partition_painting.cpp index 904fb2ea..bf80472d 100644 --- a/cartocrow/simplesets/partition_painting.cpp +++ b/cartocrow/simplesets/partition_painting.cpp @@ -10,14 +10,10 @@ void draw_poly_pattern(const PolyPattern& pattern, renderer::GeometryRenderer& r } renderer.setFill(ds.colors.at(pattern.category())); renderer.setFillOpacity(100); - // todo: set absolute stroke width here. -// renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs)); - renderer.setStroke(Color{0, 0, 0}, 1.0); + renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs), true); std::visit([&renderer](auto shape){ renderer.draw(shape); }, pattern.poly()); for (const auto& pt : pattern.catPoints()) { - // todo: set absolute stroke width here. -// renderer.setStroke(Color{0, 0, 0}, ds.pointStrokeWeight(gs)); - renderer.setStroke(Color{0, 0, 0}, 1.0); + renderer.setStroke(Color{0, 0, 0}, ds.pointStrokeWeight(gs), true); renderer.setFillOpacity(255); renderer.setFill(ds.colors.at(pt.category)); renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 2827762e..8bc6b57e 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -102,7 +102,7 @@ SimpleSetsDemo::SimpleSetsDemo() { renderer->addPainting(pp, "Partition"); m_arr = dilateAndArrange(m_partition, m_gs, m_cds); - auto ap = std::make_shared(m_arr, m_ds, m_partition); + auto ap = std::make_shared(m_arr, m_gs, m_ds, m_partition); renderer->addPainting(ap, "Arrangement"); } From 9d3362a95fb471e48808dc1463cec14a0fdf2c99 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Sat, 17 Aug 2024 10:52:43 +0200 Subject: [PATCH 04/36] SimpleSets drawing: connected components and store data in halfedges --- cartocrow/simplesets/dilated/dilated_poly.cpp | 23 +- cartocrow/simplesets/dilated/dilated_poly.h | 13 +- cartocrow/simplesets/drawing_algorithm.cpp | 238 ++++++++++++++++-- cartocrow/simplesets/drawing_algorithm.h | 74 +++++- cartocrow/simplesets/partition_algorithm.cpp | 8 +- cartocrow/simplesets/settings.h | 4 +- demos/simplesets/simplesets_demo.cpp | 6 +- demos/simplesets/simplesets_demo.h | 2 +- 8 files changed, 314 insertions(+), 54 deletions(-) diff --git a/cartocrow/simplesets/dilated/dilated_poly.cpp b/cartocrow/simplesets/dilated/dilated_poly.cpp index 96f4c175..c9dc0089 100644 --- a/cartocrow/simplesets/dilated/dilated_poly.cpp +++ b/cartocrow/simplesets/dilated/dilated_poly.cpp @@ -19,8 +19,10 @@ CSPolygon ccb_to_polygon(CSArrangement::Ccb_halfedge_const_circulator circ) { return poly; } -CSPolygon dilatePattern(const Pattern& pattern, const Number& dilationRadius) { - auto cont = pattern.contour(); +Dilated::Dilated(const PolyPattern& polyPattern, const Number& dilationRadius) { + m_catPoints = polyPattern.catPoints(); + + auto cont = polyPattern.poly(); if (holds_alternative>(cont)) { auto exactPolygon = makeExact(std::get>(cont)); @@ -40,14 +42,15 @@ CSPolygon dilatePattern(const Pattern& pattern, const Number& dilationR } // todo: is order of curves always correct? - return {curves.begin(), curves.end()}; + m_contour = CSPolygon{curves.begin(), curves.end()}; + return; } auto dilation = CGAL::approximated_offset_2(exactPolygon, dilationRadius, M_EPSILON); if (dilation.has_holes()) { throw std::runtime_error("Did not expect holes after dilating a polygonal pattern."); } - return dilation.outer_boundary(); + m_contour = dilation.outer_boundary(); } else if (holds_alternative>(cont)) { // 1. Dilate each segment // 2. Make arrangement of dilated segments @@ -66,7 +69,17 @@ CSPolygon dilatePattern(const Pattern& pattern, const Number& dilationR } } - return ccb_to_polygon(*arr.unbounded_face()->inner_ccbs_begin()); + m_contour = ccb_to_polygon(*arr.unbounded_face()->inner_ccbs_begin()); + } else { + throw std::runtime_error("Unknown pattern poly."); } } + +const std::vector& Dilated::catPoints() const { + return m_catPoints; +} + +std::variant, Polygon, CSPolygon> Dilated::contour() const { + return m_contour; +} } diff --git a/cartocrow/simplesets/dilated/dilated_poly.h b/cartocrow/simplesets/dilated/dilated_poly.h index 2d7dfeec..66d14ca7 100644 --- a/cartocrow/simplesets/dilated/dilated_poly.h +++ b/cartocrow/simplesets/dilated/dilated_poly.h @@ -1,9 +1,18 @@ #ifndef CARTOCROW_DILATED_POLY_H #define CARTOCROW_DILATED_POLY_H -#include "../patterns/pattern.h" +#include "../patterns/poly_pattern.h" namespace cartocrow::simplesets { -CSPolygon dilatePattern(const Pattern& pattern, const Number& dilationRadius); +class Dilated : public Pattern { + public: + Dilated(const PolyPattern& polyPattern, const Number& dilationRadius); + std::variant, Polygon, CSPolygon> contour() const override; + const std::vector& catPoints() const override; + CSPolygon m_contour; + + private: + std::vector m_catPoints; +}; } #endif //CARTOCROW_DILATED_POLY_H diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 11cd77ce..7d38c825 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -149,53 +149,239 @@ bool on_or_inside(const CSPolygon& polygon, const Point& point) { return intersection_results.size() % 2 == 1; } -DilatedPatternArrangement -dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds) { - std::vector dilated; +std::vector +connectedComponents(const std::vector& faces) { + std::vector remaining = faces; + std::vector components; + + while (!remaining.empty()) { + // We do a BFS + std::vector compFaces; + auto first = remaining.front(); + std::deque q; + q.push_back(first); + + while (!q.empty()) { + auto f = q.front(); + q.pop_front(); + compFaces.push_back(f); + + // Go through boundaries of this face + std::vector ccbs; + std::copy(f->outer_ccbs_begin(), f->outer_ccbs_end(), std::back_inserter(ccbs)); + std::copy(f->inner_ccbs_begin(), f->inner_ccbs_end(), std::back_inserter(ccbs)); + for (auto ccb_start : ccbs) { + auto ccb_it = ccb_start; + + // Go through each neighbouring face + do { + auto candidate = ccb_it->twin()->face(); + + // If this is one of the provided faces, and not yet added to queue or compFaces, add it to queue. + if (std::find(remaining.begin(), remaining.end(), candidate) != remaining.end() && + std::find(compFaces.begin(), compFaces.end(), candidate) == compFaces.end() && + std::find(q.begin(), q.end(), candidate) == q.end()) { + q.push_back(candidate); + } + } while (++ccb_it != ccb_start); + } + } + + // Done with this connected component + remaining.erase(std::remove_if(remaining.begin(), remaining.end(), [&compFaces](const auto& f) { + return std::find(compFaces.begin(), compFaces.end(), f) != compFaces.end(); + }), remaining.end()); + components.emplace_back(std::move(compFaces)); + } + + return components; +} + +Component::Component(std::vector faces): m_faces(std::move(faces)) {} + +// Best would be to give Component the same interface as a Face. +// So have outer_ccb circulator etc. +// Todo: implement this and put in core? +HalfEdgeH Component::boundaryEdge() { + for (auto fh : m_faces) { +// auto startEdge = fh-> + } +} + +// This observer only works as intended when inserting curves incrementally, not when using the CGAL sweep-line insert. +class MyObserver : public CGAL::Arr_observer { + public: + MyObserver(DilatedPatternArrangement& arr, + const std::vector>& curve_data) : + CGAL::Arr_observer(arr), m_curve_data(curve_data) {}; + + void after_create_edge(HalfEdgeH e) override { + const CSTraits::X_monotone_curve_2& curve = e->curve(); + bool found = false; + for (const auto& curve_datum : m_curve_data) { + const CSTraits::X_monotone_curve_2& other_curve = curve_datum.first; + // Cannot check curve equality for some reason, so do it manually instead. + if ((curve.source() == other_curve.source() && + curve.target() == other_curve.target() || + curve.source() == other_curve.target() && + curve.target() == other_curve.source()) && + (curve.is_linear() && other_curve.is_linear() || + curve.is_circular() && other_curve.is_circular() && + curve.supporting_circle() == other_curve.supporting_circle())) { + found = true; + e->set_data(HalfEdgeData{curve_datum.second}); + } + } + + ++m_count; + } + + void before_split_edge(HalfEdgeH e, VertexH v, const X_monotone_curve_2& c1, const X_monotone_curve_2& c2) override { + m_edge_split_data = e->data(); + } + + void after_split_edge(HalfEdgeH e1, HalfEdgeH e2) override { + if (!m_edge_split_data.has_value()) { + throw std::runtime_error("Data before split is not accessible after split"); + } + e1->set_data(*m_edge_split_data); + e2->set_data(*m_edge_split_data); + + m_edge_split_data = std::nullopt; + } + + int m_count = 0; + private: + const std::vector>& m_curve_data; + std::optional m_edge_split_data; +}; + +CSTraits::Curve_2 to_curve(CSTraits::X_monotone_curve_2 xmc) { + if (xmc.is_linear()) { + return {xmc.supporting_line(), xmc.source(), xmc.target()}; + } else if (xmc.is_circular()) { + return {xmc.supporting_circle(), xmc.source(), xmc.target()}; + } else { + throw std::runtime_error("Impossible: circle-segment x-monotone curve is neither linear nor circular."); + } +} + +//DilatedPatternArrangement +//dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds) { +DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds) + : m_gs(gs), m_cds(cds) { for (const auto& p : partition) { - dilated.push_back(dilatePattern(*p, gs.dilationRadius())); + m_dilated.emplace_back(*p, gs.dilationRadius()); } - DilatedPatternArrangement arr; -// CSTraits traits; -// auto make_x_monotone = traits.make_x_monotone_2_object(); - std::vector x_monotone_curves; - for (const auto& poly : dilated) { - std::copy(poly.curves_begin(), poly.curves_end(), std::back_inserter(x_monotone_curves)); + std::vector curves; + std::vector> curves_data; + for (int i = 0; i < m_dilated.size(); i++) { + for (auto cit = m_dilated[i].m_contour.curves_begin(); cit != m_dilated[i].m_contour.curves_end(); ++cit) { + auto x_monotone = *cit; + CSTraits::Curve_2 curve = to_curve(x_monotone); + curves.emplace_back(curve); + curves_data.emplace_back(curve, i); + } } - CGAL::insert(arr, x_monotone_curves.begin(), x_monotone_curves.end()); - for (auto fit = arr.faces_begin(); fit != arr.faces_end(); ++fit) { +// MyObserver obs(m_arr, curves_data); +// for (auto& curve : x_monotone_curves) { +// CGAL::insert(m_arr, curve); +// } + + CGAL::insert(m_arr, curves.begin(), curves.end()); + // Set for each face which pattern it is a subset of. + for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { if (fit->is_unbounded()) continue; auto pt = get_point_in(*fit); std::vector origins; - for (int i = 0; i < dilated.size(); i++) { - if (on_or_inside(dilated[i], pt)) { + for (int i = 0; i < m_dilated.size(); i++) { + if (on_or_inside(m_dilated[i].m_contour, pt)) { origins.push_back(i); + m_iToFaces[i].push_back(fit); } -// dilated[i].curves_begin()-> } - fit->set_data(FaceInfo{origins}); + fit->set_data(FaceData{origins}); } - return arr; + // Store in each half-edges form which pattern it originates. + for (auto cit = m_arr.curves_begin(); cit != m_arr.curves_end(); ++cit) { + CSTraits::Curve_2 curve = *cit; + auto curve_data = std::find_if(curves_data.begin(), curves_data.end(), [&curve](const auto& pair) { + auto other = pair.first; + return curve.source() == other.source() && curve.target() == other.target() && + (curve.is_linear() && other.is_linear() || + curve.is_circular() && other.is_circular() && curve.supporting_circle() == other.supporting_circle()); + }); + for (auto eit = m_arr.induced_edges_begin(cit); eit != m_arr.induced_edges_end(cit); ++eit) { + DilatedPatternArrangement::Halfedge_handle eh = *eit; + HalfEdgeData data(curve_data->second); + eh->set_data(data); + eh->twin()->set_data(data); + } + } + + // Sort for std::set_intersection operation + for (int i = 0; i < m_dilated.size(); i++) { + auto& facesI = m_iToFaces[i]; + std::sort(facesI.begin(), facesI.end()); + } + + for (int i = 0; i < m_dilated.size(); i++) { + for (int j = i + 1; j < m_dilated.size(); j++) { + auto cs = intersectionComponents(i, j); + for (auto& c : cs) { + auto rel = computePreference(i, j, c); + for (auto f : c.m_faces) { + f->data().relations.push_back(rel); + } + } + } + } } -ArrangementPainting::ArrangementPainting(const DilatedPatternArrangement& arr, const GeneralSettings& gs, - const DrawSettings& ds, const Partition& partition) - : m_arr(arr), m_ds(ds), m_gs(gs), m_partition(partition) {}; +Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& c) { + auto pref = CGAL::ZERO; + + // 3. Prefer to cover a line segment over covering a circular arc. + // 2. Prefer to indent a line segment over indenting a circular arc. + // 1. Prefer to avoid few points over many points. + // todo: implement + + return {i, j, pref, pref}; +} -void ArrangementPainting::paint(renderer::GeometryRenderer& renderer) const { +std::vector +DilatedPatternDrawing::intersectionComponents(int i, int j) { + std::vector faces; + const std::vector& facesI = m_iToFaces[i]; + const std::vector& facesJ = m_iToFaces[j]; + std::set_intersection(facesI.begin(), facesI.end(), facesJ.begin(), facesJ.end(), std::back_inserter(faces)); + return connectedComponents(faces); +} + +std::vector +DilatedPatternDrawing::intersectionComponents(int i) { + std::vector faces; + std::copy_if(m_iToFaces[i].begin(), m_iToFaces[i].end(), std::back_inserter(faces), + [](const FaceH& fh) { return fh->data().origins.size() > 1; }); + return connectedComponents(faces); +} + +SimpleSetsPainting::SimpleSetsPainting(const DilatedPatternDrawing& dpd, const DrawSettings& ds) + : m_ds(ds), m_dpd(dpd) {} + +void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { renderer.setMode(renderer::GeometryRenderer::fill); - for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { + for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { auto face = *fit; auto origins = face.data().origins; if (origins.size() > 1) { renderer.setFill(Color{0, 0, 0}); } else if (origins.size() == 1) { renderer.setFill(Color{0, 0, 0}); -// m_ds.colors[origins[0]] - renderer.setFill(m_ds.colors[m_partition[origins[0]]->category()]); + renderer.setFill(m_ds.colors[m_dpd.m_dilated[origins[0]].category()]); } else { continue; } @@ -212,9 +398,9 @@ void ArrangementPainting::paint(renderer::GeometryRenderer& renderer) const { } renderer.setMode(renderer::GeometryRenderer::stroke); - renderer.setStroke(Color{0, 0, 0}, m_ds.contourStrokeWeight(m_gs), true); - for (auto eit = m_arr.edges_begin(); eit != m_arr.edges_end(); ++eit) { + for (auto eit = m_dpd.m_arr.edges_begin(); eit != m_dpd.m_arr.edges_end(); ++eit) { auto curve = eit->curve(); + renderer.setStroke(m_ds.colors[m_dpd.m_dilated[eit->data().origin].category()], m_ds.contourStrokeWeight(m_dpd.m_gs), true); if (curve.is_linear()) { renderer.draw(Segment(approximateAlgebraic(curve.source()), approximateAlgebraic(curve.target()))); } else if (curve.is_circular()){ diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 201d5bdc..9109441e 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -2,31 +2,83 @@ #define CARTOCROW_DRAWING_ALGORITHM_H #include "partition.h" +#include "dilated/dilated_poly.h" #include "../renderer/geometry_painting.h" +#include namespace cartocrow::simplesets { -struct FaceInfo { +struct Relation { + int left; + int right; + CGAL::Sign preference; + CGAL::Sign ordering; +}; + +struct FaceData { std::vector origins; + std::vector relations; +}; + +// todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. +struct HalfEdgeData { + int origin; +}; + +struct VertexData { + }; using DilatedPatternArrangement = - CGAL::Arrangement_2>; + CGAL::Arrangement_with_history_2>; + +//DilatedPatternArrangement +//dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds); + +using FaceH = DilatedPatternArrangement::Face_handle; +using VertexH = DilatedPatternArrangement::Vertex_handle; +using HalfEdgeH = DilatedPatternArrangement::Halfedge_handle; + +class Component { + public: + Component(std::vector faces); + std::vector m_faces; + + HalfEdgeH boundaryEdge(); + HalfEdgeH nextBoundaryEdge(HalfEdgeH); + HalfEdgeH prevBoundaryEdge(HalfEdgeH); + std::vector boundaryEdges(); -DilatedPatternArrangement -dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds); + private: +}; + +class DilatedPatternDrawing { + public: + DilatedPatternDrawing(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds); + + std::vector intersectionComponents(int i); + std::vector intersectionComponents(int i, int j); + Relation computePreference(int i, int j, const Component& c); + + DilatedPatternArrangement m_arr; + std::unordered_map> m_iToFaces; + std::map m_curve_to_origin; + std::vector m_dilated; + const GeneralSettings& m_gs; + const ComputeDrawingSettings& m_cds; + + private: +}; -class ArrangementPainting : public renderer::GeometryPainting { +class SimpleSetsPainting : public renderer::GeometryPainting { public: - ArrangementPainting(const DilatedPatternArrangement& arr, const GeneralSettings& gs, - const DrawSettings& ds, const Partition& partition); + SimpleSetsPainting(const DilatedPatternDrawing& dpd, const DrawSettings& ds); void paint(renderer::GeometryRenderer& renderer) const override; private: - const DilatedPatternArrangement& m_arr; const DrawSettings& m_ds; - const GeneralSettings& m_gs; - const Partition& m_partition; + const DilatedPatternDrawing& m_dpd; + }; } diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp index 3fd39517..f624899f 100644 --- a/cartocrow/simplesets/partition_algorithm.cpp +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -81,8 +81,8 @@ Number intersectionDelay(const std::vector& points, const std if (std::find(resultPts.begin(), resultPts.end(), pt) == resultPts.end() && squared_distance(resultPoly, pt.point) < gs.dilationRadius() * 2) { // note that Circle requires us to pass the squared radius - CSPolygon ptShape = dilatePattern(SinglePoint(pt), gs.dilationRadius()); - CSPolygon rShape = dilatePattern(*result, gs.dilationRadius()); + CSPolygon ptShape = Dilated(SinglePoint(pt), gs.dilationRadius()).m_contour; + CSPolygon rShape = Dilated(*result, gs.dilationRadius()).m_contour; std::vector inters; CGAL::intersection(rShape, ptShape, std::back_inserter(inters)); Number newArea = 0; @@ -90,8 +90,8 @@ Number intersectionDelay(const std::vector& points, const std newArea += area(gp); } inters.clear(); - CSPolygon p1Shape = dilatePattern(*p1, gs.dilationRadius()); - CSPolygon p2Shape = dilatePattern(*p2, gs.dilationRadius()); + CSPolygon p1Shape = Dilated(*p1, gs.dilationRadius()).m_contour; + CSPolygon p2Shape = Dilated(*p2, gs.dilationRadius()).m_contour; CGAL::intersection(p1Shape, ptShape, std::back_inserter(inters)); CGAL::intersection(p2Shape, ptShape, std::back_inserter(inters)); Number oldArea = 0; diff --git a/cartocrow/simplesets/settings.h b/cartocrow/simplesets/settings.h index f40c35ba..a0735d83 100644 --- a/cartocrow/simplesets/settings.h +++ b/cartocrow/simplesets/settings.h @@ -42,10 +42,10 @@ struct ComputeDrawingSettings { struct DrawSettings { std::vector colors; Number pointStrokeWeight(GeneralSettings gs) const { - return gs.pointSize / 2.5; + return gs.pointSize / 3.5; } Number contourStrokeWeight(GeneralSettings gs) const { - return gs.pointSize / 3.5; + return gs.pointSize / 4; } }; diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 8bc6b57e..bb6db297 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -71,7 +71,7 @@ SimpleSetsDemo::SimpleSetsDemo() { renderer->setMinZoom(0.01); renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("/home/steven/Documents/cartocrow/data/nyc.txt"); + std::filesystem::path filePath("/home/steven/Downloads/test/cartocrow/data/nyc.txt"); std::ifstream inputStream(filePath, std::ios_base::in); if (!inputStream.good()) { throw std::runtime_error("Failed to read input"); @@ -101,8 +101,8 @@ SimpleSetsDemo::SimpleSetsDemo() { auto pp = std::make_shared(m_partition, m_gs, m_ds); renderer->addPainting(pp, "Partition"); - m_arr = dilateAndArrange(m_partition, m_gs, m_cds); - auto ap = std::make_shared(m_arr, m_gs, m_ds, m_partition); + m_dpd = std::make_shared(m_partition, m_gs, m_cds); + auto ap = std::make_shared(*m_dpd, m_ds); renderer->addPainting(ap, "Arrangement"); } diff --git a/demos/simplesets/simplesets_demo.h b/demos/simplesets/simplesets_demo.h index 1a7b0424..f52019e7 100644 --- a/demos/simplesets/simplesets_demo.h +++ b/demos/simplesets/simplesets_demo.h @@ -41,7 +41,7 @@ class SimpleSetsDemo : public QMainWindow { private: Partition m_partition; - DilatedPatternArrangement m_arr; + std::shared_ptr m_dpd; GeneralSettings m_gs; DrawSettings m_ds; PartitionSettings m_ps; From a5622bf58618af1ed67ad3262e34c0c42f53051c Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Sun, 18 Aug 2024 10:08:50 +0200 Subject: [PATCH 05/36] SimpleSets drawing: fix face_to_polygon and draw circular arcs --- cartocrow/simplesets/drawing_algorithm.cpp | 58 +++++++++++++++------ cartocrow/simplesets/partition_painting.cpp | 7 ++- demos/simplesets/simplesets_demo.cpp | 6 +-- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 7d38c825..932e095d 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -1,7 +1,18 @@ #include "drawing_algorithm.h" #include "dilated/dilated_poly.h" +#include "../renderer/render_path.h" + +using namespace cartocrow::renderer; namespace cartocrow::simplesets { +CSTraits::X_monotone_curve_2 reverse(CSTraits::X_monotone_curve_2 curve) { + if (curve.is_linear()) { + return CSTraits::X_monotone_curve_2(curve.supporting_line(), curve.target(), curve.source()); + } else { + return CSTraits::X_monotone_curve_2(curve.supporting_circle(), curve.target(), curve.source(), -curve.orientation()); + } +} + CSPolygon face_to_polygon(const DilatedPatternArrangement::Face& face) { assert(face.number_of_holes() == 0); auto ccb_start = face.outer_ccb(); @@ -10,7 +21,11 @@ CSPolygon face_to_polygon(const DilatedPatternArrangement::Face& face) { std::vector x_monotone_curves; do { auto curve = ccb->curve(); - x_monotone_curves.push_back(curve); + if (ccb->source()->point() == curve.source()) { + x_monotone_curves.push_back(curve); + } else { + x_monotone_curves.push_back(reverse(curve)); + } } while(++ccb != ccb_start); return {x_monotone_curves.begin(), x_monotone_curves.end()}; @@ -85,10 +100,13 @@ Point get_approx_point_on_boundary(const DilatedPatternArrangement::Face& Point get_point_in(const DilatedPatternArrangement::Face& face) { auto poly = face_to_polygon(face); - auto bbox = poly.bbox(); - Point point_outside(bbox.xmin() - 1, bbox.ymin() - 1); + Rectangle bbox = poly.bbox(); + Rectangle rect({bbox.xmin() - 1, bbox.ymin() - 1}, {bbox.xmax() + 1, bbox.ymax() + 1}); + Point point_outside(rect.xmin(), rect.ymin()); Point approx_point_on_boundary = get_approx_point_on_boundary(face); - Segment seg(point_outside, approx_point_on_boundary); + Line line(point_outside, approx_point_on_boundary); + auto line_inter_box = CGAL::intersection(rect, line); + auto seg = boost::get>(*line_inter_box); auto seg_curve = make_x_monotone(seg); std::vector intersection_pts; for (auto cit = poly.curves_begin(); cit != poly.curves_end(); cit++) { @@ -106,11 +124,7 @@ Point get_point_in(const DilatedPatternArrangement::Face& face) { std::sort(intersection_pts.begin(), intersection_pts.end(), [&seg](const auto& pt1, const auto& pt2) { Vector v = seg.supporting_line().to_vector(); - Vector pt1_ = makeExact(approximateAlgebraic(pt1)) - CGAL::ORIGIN; - Number a1 = v * pt1_; - Vector pt2_ = makeExact(approximateAlgebraic(pt1)) - CGAL::ORIGIN; - Number a2 = v * pt2_; - return a1 < a2; + return v * (makeExact(approximateAlgebraic(pt1)) - makeExact(approximateAlgebraic(pt2))) < 0; }); Point approx_source; @@ -375,26 +389,36 @@ SimpleSetsPainting::SimpleSetsPainting(const DilatedPatternDrawing& dpd, const D void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { renderer.setMode(renderer::GeometryRenderer::fill); for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { + if (fit->is_unbounded()) continue; auto face = *fit; auto origins = face.data().origins; if (origins.size() > 1) { - renderer.setFill(Color{0, 0, 0}); + renderer.setFill(Color{100, 100, 100}); } else if (origins.size() == 1) { - renderer.setFill(Color{0, 0, 0}); renderer.setFill(m_ds.colors[m_dpd.m_dilated[origins[0]].category()]); } else { continue; } auto poly = face_to_polygon(face); + + RenderPath path; + bool first = true; for (auto cit = poly.curves_begin(); cit != poly.curves_end(); ++cit) { + if (first) { + path.moveTo(approximateAlgebraic(cit->source())); + first = false; + } if (cit->is_linear()) { - renderer.draw(Segment(approximateAlgebraic(cit->source()), approximateAlgebraic(cit->target()))); + path.lineTo(approximateAlgebraic(cit->target())); } else if (cit->is_circular()){ auto circle = cit->supporting_circle(); - renderer.draw(circle); - // todo: draw arc + path.arcTo(approximate(circle.center()), cit->orientation() == CGAL::CLOCKWISE, approximateAlgebraic(cit->target())); } } + path.close(); + renderer.setFillOpacity(150); + renderer.setMode(renderer::GeometryRenderer::fill); + renderer.draw(path); } renderer.setMode(renderer::GeometryRenderer::stroke); @@ -404,9 +428,11 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { if (curve.is_linear()) { renderer.draw(Segment(approximateAlgebraic(curve.source()), approximateAlgebraic(curve.target()))); } else if (curve.is_circular()){ + RenderPath path; + path.moveTo(approximateAlgebraic(curve.source())); auto circle = curve.supporting_circle(); - renderer.draw(circle); - // todo: draw arc + path.arcTo(approximate(circle.center()), curve.orientation() == CGAL::CLOCKWISE, approximateAlgebraic(curve.target())); + renderer.draw(path); } } } diff --git a/cartocrow/simplesets/partition_painting.cpp b/cartocrow/simplesets/partition_painting.cpp index bf80472d..f2e0fceb 100644 --- a/cartocrow/simplesets/partition_painting.cpp +++ b/cartocrow/simplesets/partition_painting.cpp @@ -11,7 +11,12 @@ void draw_poly_pattern(const PolyPattern& pattern, renderer::GeometryRenderer& r renderer.setFill(ds.colors.at(pattern.category())); renderer.setFillOpacity(100); renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs), true); - std::visit([&renderer](auto shape){ renderer.draw(shape); }, pattern.poly()); + auto& pts = pattern.catPoints(); + if (pts.size() == 1) { +// renderer.draw(pts[0].point); + } else { + std::visit([&renderer](auto shape) { renderer.draw(shape); }, pattern.poly()); + } for (const auto& pt : pattern.catPoints()) { renderer.setStroke(Color{0, 0, 0}, ds.pointStrokeWeight(gs), true); renderer.setFillOpacity(255); diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index bb6db297..71d0d626 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -98,12 +98,12 @@ SimpleSetsDemo::SimpleSetsDemo() { } m_partition = *thePartition; - auto pp = std::make_shared(m_partition, m_gs, m_ds); - renderer->addPainting(pp, "Partition"); - m_dpd = std::make_shared(m_partition, m_gs, m_cds); auto ap = std::make_shared(*m_dpd, m_ds); renderer->addPainting(ap, "Arrangement"); + + auto pp = std::make_shared(m_partition, m_gs, m_ds); + renderer->addPainting(pp, "Partition"); } int main(int argc, char* argv[]) { From 11a39ddddd87642d26b87a4bb8fb99e3d5a2d1ee Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Sun, 18 Aug 2024 22:16:54 +0200 Subject: [PATCH 06/36] Start on component boundary circulator --- cartocrow/simplesets/drawing_algorithm.cpp | 11 --- cartocrow/simplesets/drawing_algorithm.h | 90 ++++++++++++++++++++-- 2 files changed, 83 insertions(+), 18 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 932e095d..da5849d8 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -211,17 +211,6 @@ connectedComponents(const std::vector& faces) { return components; } -Component::Component(std::vector faces): m_faces(std::move(faces)) {} - -// Best would be to give Component the same interface as a Face. -// So have outer_ccb circulator etc. -// Todo: implement this and put in core? -HalfEdgeH Component::boundaryEdge() { - for (auto fh : m_faces) { -// auto startEdge = fh-> - } -} - // This observer only works as intended when inserting curves incrementally, not when using the CGAL sweep-line insert. class MyObserver : public CGAL::Arr_observer { public: diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 9109441e..17ba7f63 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -6,6 +6,8 @@ #include "../renderer/geometry_painting.h" #include +#include + namespace cartocrow::simplesets { struct Relation { int left; @@ -41,15 +43,89 @@ using HalfEdgeH = DilatedPatternArrangement::Halfedge_handle; class Component { public: - Component(std::vector faces); - std::vector m_faces; - - HalfEdgeH boundaryEdge(); - HalfEdgeH nextBoundaryEdge(HalfEdgeH); - HalfEdgeH prevBoundaryEdge(HalfEdgeH); - std::vector boundaryEdges(); + typedef DilatedPatternArrangement Arr; + typedef Arr::Size Size; + + class ComponentCcbCirculator { + private: + using Self = ComponentCcbCirculator; + Arr::Halfedge_handle m_halfedge; + std::function m_in_component; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = Arr::Halfedge; + using difference_type = std::ptrdiff_t; + using pointer = Arr::Halfedge_handle; + using reference = value_type&; + + ComponentCcbCirculator(std::function in_component) : m_in_component(std::move(in_component)) {}; + + value_type operator*() const { + return *m_halfedge; + } + + pointer operator->() const { + return m_halfedge; + } + + Self& operator++() { + m_halfedge = m_halfedge->next(); + while (!m_in_component(m_halfedge->twin()->face())) { + m_halfedge = m_halfedge->twin()->next(); + } + return *this; + }; + + Self operator++(int) { + Self tmp = *this; + this->operator++(); + return tmp; + } + + Self& operator--() { + m_halfedge = m_halfedge->prev(); + while (!m_in_component(m_halfedge->twin()->face())) { + m_halfedge = m_halfedge->twin()->prev(); + } + return *this; + }; + + Self operator--(int) { + Self tmp = *this; + this->operator--(); + return tmp; + } + + bool operator==(const Self& other) const { + return m_halfedge == other.m_halfedge; + } + + bool operator!=(const Self& other) const { + return m_halfedge != other.m_halfedge; + } + }; + +// Component(std::vector faces); + Component(std::function in_component); + +// bool has_outer_ccb() const; +// Hole_iterator holes_begin(); +// Hole_iterator holes_end(); +// Inner_ccb_iterator inner_ccbs_begin(); +// Inner_ccb_iterator inner_ccbs_end(); +// Size number_of_holes(); +// Size number_of_inner_ccbs(); +// Size number_of_outer_ccbs(); +// ComponentCcbCirculator outer_ccb(); +// Outer_ccb_iterator outer_ccbs_begin(); +// Outer_ccb_iterator outer_ccbs_end(); +// Face_iterator faces_begin(); +// Face_iterator faces_end(); private: + std::vector m_faces; + std::function m_in_component; }; class DilatedPatternDrawing { From a2b25a86d53442b7fc9e131f7c78a8a04a47e0c7 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Tue, 20 Aug 2024 17:33:15 +0200 Subject: [PATCH 07/36] SimpleSets drawing: add stacking preference and helper functions --- cartocrow/simplesets/CMakeLists.txt | 6 + cartocrow/simplesets/dilated/dilated_poly.cpp | 15 +- cartocrow/simplesets/drawing_algorithm.cpp | 385 +++++++++++++----- cartocrow/simplesets/drawing_algorithm.h | 151 +++++-- cartocrow/simplesets/general_polyline.h | 33 ++ cartocrow/simplesets/grow_circles.cpp | 102 +++++ cartocrow/simplesets/grow_circles.h | 18 + .../simplesets/helpers/arrangement_helpers.h | 26 ++ .../simplesets/helpers/cs_polygon_helpers.cpp | 2 +- .../simplesets/helpers/cs_polygon_helpers.h | 2 +- cartocrow/simplesets/partition_algorithm.cpp | 2 - .../simplesets/poly_line_gon_intersection.cpp | 53 +++ .../simplesets/poly_line_gon_intersection.h | 17 + cartocrow/simplesets/types.cpp | 23 ++ cartocrow/simplesets/types.h | 6 + demos/simplesets/simplesets_demo.cpp | 15 +- 16 files changed, 701 insertions(+), 155 deletions(-) create mode 100644 cartocrow/simplesets/general_polyline.h create mode 100644 cartocrow/simplesets/grow_circles.cpp create mode 100644 cartocrow/simplesets/grow_circles.h create mode 100644 cartocrow/simplesets/helpers/arrangement_helpers.h create mode 100644 cartocrow/simplesets/poly_line_gon_intersection.cpp create mode 100644 cartocrow/simplesets/poly_line_gon_intersection.h diff --git a/cartocrow/simplesets/CMakeLists.txt b/cartocrow/simplesets/CMakeLists.txt index a14f537b..40c7275b 100644 --- a/cartocrow/simplesets/CMakeLists.txt +++ b/cartocrow/simplesets/CMakeLists.txt @@ -11,6 +11,8 @@ set(SOURCES partition_algorithm.cpp partition_painting.cpp drawing_algorithm.cpp + grow_circles.cpp + poly_line_gon_intersection.cpp ) set(HEADERS types.h @@ -31,6 +33,10 @@ set(HEADERS partition_painting.h partition.h drawing_algorithm.h + general_polyline.h + grow_circles.h + poly_line_gon_intersection.h + helpers/arrangement_helpers.h ) add_library(simplesets ${SOURCES}) diff --git a/cartocrow/simplesets/dilated/dilated_poly.cpp b/cartocrow/simplesets/dilated/dilated_poly.cpp index c9dc0089..5dd53a85 100644 --- a/cartocrow/simplesets/dilated/dilated_poly.cpp +++ b/cartocrow/simplesets/dilated/dilated_poly.cpp @@ -29,20 +29,7 @@ Dilated::Dilated(const PolyPattern& polyPattern, const Number& dilation if (exactPolygon.size() == 1) { CSTraits::Rational_circle_2 circle(exactPolygon.vertex(0), dilationRadius * dilationRadius); - CSTraits traits; - auto make_x_monotone = traits.make_x_monotone_2_object(); - std::vector> curves_and_points; - make_x_monotone(circle, std::back_inserter(curves_and_points)); - std::vector curves; - - // There should not be any isolated points - for (auto kinda_curve : curves_and_points) { - auto curve = boost::get(kinda_curve); - curves.push_back(curve); - } - - // todo: is order of curves always correct? - m_contour = CSPolygon{curves.begin(), curves.end()}; + m_contour = circleToCSPolygon(circle); return; } diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index da5849d8..8e758b42 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -1,47 +1,33 @@ #include "drawing_algorithm.h" + +#include #include "dilated/dilated_poly.h" #include "../renderer/render_path.h" +#include "grow_circles.h" +#include "poly_line_gon_intersection.h" +#include "helpers/arrangement_helpers.h" +#include using namespace cartocrow::renderer; namespace cartocrow::simplesets { -CSTraits::X_monotone_curve_2 reverse(CSTraits::X_monotone_curve_2 curve) { - if (curve.is_linear()) { - return CSTraits::X_monotone_curve_2(curve.supporting_line(), curve.target(), curve.source()); - } else { - return CSTraits::X_monotone_curve_2(curve.supporting_circle(), curve.target(), curve.source(), -curve.orientation()); - } -} - +// todo: deal with holes CSPolygon face_to_polygon(const DilatedPatternArrangement::Face& face) { assert(face.number_of_holes() == 0); - auto ccb_start = face.outer_ccb(); - auto ccb = ccb_start; - - std::vector x_monotone_curves; - do { - auto curve = ccb->curve(); - if (ccb->source()->point() == curve.source()) { - x_monotone_curves.push_back(curve); - } else { - x_monotone_curves.push_back(reverse(curve)); - } - } while(++ccb != ccb_start); - - return {x_monotone_curves.begin(), x_monotone_curves.end()}; + return ccb_to_polygon(face.outer_ccb()); } -CSTraits::X_monotone_curve_2 make_x_monotone(const Segment& segment) { +X_monotone_curve_2 make_x_monotone(const Segment& segment) { CSTraits traits; auto make_x_monotone = traits.make_x_monotone_2_object(); - std::vector> curves_and_points; + std::vector> curves_and_points; make_x_monotone(segment, std::back_inserter(curves_and_points)); - std::vector curves; + std::vector curves; // There should not be any isolated points for (auto kinda_curve : curves_and_points) { assert(kinda_curve.which() == 1); - auto curve = boost::get(kinda_curve); + auto curve = boost::get(kinda_curve); curves.push_back(curve); } @@ -70,19 +56,19 @@ Point get_approx_point_on_boundary(const DilatedPatternArrangement::Face& // CGAL::intersection(pl, circle); CSTraits traits; auto make_x_monotone = traits.make_x_monotone_2_object(); - std::vector> curves_and_points; + std::vector> curves_and_points; // make_x_monotone(circle, std::back_inserter(curves_and_points)); make_x_monotone(seg, std::back_inserter(curves_and_points)); - std::vector curves; + std::vector curves; // There should not be any isolated points for (auto kinda_curve : curves_and_points) { - auto curve = boost::get(kinda_curve); + auto curve = boost::get(kinda_curve); curves.push_back(curve); } typedef std::pair Intersection_point; - typedef boost::variant Intersection_result; + typedef boost::variant Intersection_result; std::vector intersection_results; assert(curves.size() == 1); curve.intersect(curves[0], std::back_inserter(intersection_results)); @@ -112,7 +98,7 @@ Point get_point_in(const DilatedPatternArrangement::Face& face) { for (auto cit = poly.curves_begin(); cit != poly.curves_end(); cit++) { auto curve = *cit; typedef std::pair Intersection_point; - typedef boost::variant Intersection_result; + typedef boost::variant Intersection_result; std::vector intersection_results; curve.intersect(seg_curve, std::back_inserter(intersection_results)); @@ -152,7 +138,7 @@ bool on_or_inside(const CSPolygon& polygon, const Point& point) { auto seg_curve = make_x_monotone(seg); typedef std::pair Intersection_point; - typedef boost::variant Intersection_result; + typedef boost::variant Intersection_result; std::vector intersection_results; for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { @@ -164,13 +150,21 @@ bool on_or_inside(const CSPolygon& polygon, const Point& point) { } std::vector -connectedComponents(const std::vector& faces) { - std::vector remaining = faces; +connectedComponents(const DilatedPatternArrangement& arr, std::function in_component) { + std::vector remaining; + for (auto fit = arr.faces_begin(); fit != arr.faces_end(); ++fit) { + auto fh = fit.ptr(); + if (in_component(fh)) { + remaining.emplace_back(fh); + } + } + std::vector components; while (!remaining.empty()) { // We do a BFS std::vector compFaces; + std::vector compBoundaryEdges; auto first = remaining.front(); std::deque q; q.push_back(first); @@ -190,12 +184,15 @@ connectedComponents(const std::vector& faces) { // Go through each neighbouring face do { auto candidate = ccb_it->twin()->face(); - - // If this is one of the provided faces, and not yet added to queue or compFaces, add it to queue. - if (std::find(remaining.begin(), remaining.end(), candidate) != remaining.end() && - std::find(compFaces.begin(), compFaces.end(), candidate) == compFaces.end() && - std::find(q.begin(), q.end(), candidate) == q.end()) { - q.push_back(candidate); + if (!in_component(candidate)) { + compBoundaryEdges.emplace_back(ccb_it.ptr()); + } else { + // If this is one of the provided faces, and not yet added to queue or compFaces, add it to queue. + if (std::find(compFaces.begin(), compFaces.end(), candidate) == + compFaces.end() && + std::find(q.begin(), q.end(), candidate) == q.end()) { + q.push_back(candidate); + } } } while (++ccb_it != ccb_start); } @@ -205,61 +202,13 @@ connectedComponents(const std::vector& faces) { remaining.erase(std::remove_if(remaining.begin(), remaining.end(), [&compFaces](const auto& f) { return std::find(compFaces.begin(), compFaces.end(), f) != compFaces.end(); }), remaining.end()); - components.emplace_back(std::move(compFaces)); + components.emplace_back(std::move(compFaces), std::move(compBoundaryEdges), in_component); } return components; } -// This observer only works as intended when inserting curves incrementally, not when using the CGAL sweep-line insert. -class MyObserver : public CGAL::Arr_observer { - public: - MyObserver(DilatedPatternArrangement& arr, - const std::vector>& curve_data) : - CGAL::Arr_observer(arr), m_curve_data(curve_data) {}; - - void after_create_edge(HalfEdgeH e) override { - const CSTraits::X_monotone_curve_2& curve = e->curve(); - bool found = false; - for (const auto& curve_datum : m_curve_data) { - const CSTraits::X_monotone_curve_2& other_curve = curve_datum.first; - // Cannot check curve equality for some reason, so do it manually instead. - if ((curve.source() == other_curve.source() && - curve.target() == other_curve.target() || - curve.source() == other_curve.target() && - curve.target() == other_curve.source()) && - (curve.is_linear() && other_curve.is_linear() || - curve.is_circular() && other_curve.is_circular() && - curve.supporting_circle() == other_curve.supporting_circle())) { - found = true; - e->set_data(HalfEdgeData{curve_datum.second}); - } - } - - ++m_count; - } - - void before_split_edge(HalfEdgeH e, VertexH v, const X_monotone_curve_2& c1, const X_monotone_curve_2& c2) override { - m_edge_split_data = e->data(); - } - - void after_split_edge(HalfEdgeH e1, HalfEdgeH e2) override { - if (!m_edge_split_data.has_value()) { - throw std::runtime_error("Data before split is not accessible after split"); - } - e1->set_data(*m_edge_split_data); - e2->set_data(*m_edge_split_data); - - m_edge_split_data = std::nullopt; - } - - int m_count = 0; - private: - const std::vector>& m_curve_data; - std::optional m_edge_split_data; -}; - -CSTraits::Curve_2 to_curve(CSTraits::X_monotone_curve_2 xmc) { +CSTraits::Curve_2 to_curve(const X_monotone_curve_2& xmc) { if (xmc.is_linear()) { return {xmc.supporting_line(), xmc.source(), xmc.target()}; } else if (xmc.is_circular()) { @@ -269,8 +218,43 @@ CSTraits::Curve_2 to_curve(CSTraits::X_monotone_curve_2 xmc) { } } -//DilatedPatternArrangement -//dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds) { +Component::Component(std::vector faces, std::vector boundary_edges, std::function in_component) : + m_faces(std::move(faces)), m_in_component(std::move(in_component)) { + while (!boundary_edges.empty()) { + auto he = boundary_edges.front(); + auto circ_start = ComponentCcbCirculator(he, m_in_component); + auto circ = circ_start; + + std::vector xm_curves; + auto last_it = boundary_edges.end(); + do { + last_it = std::remove(boundary_edges.begin(), last_it, circ.handle()); + xm_curves.push_back(circ->curve()); + } while (++circ != circ_start); + boundary_edges.erase(last_it, boundary_edges.end()); + + CSPolygon polygon(xm_curves.begin(), xm_curves.end()); + auto orientation = polygon.orientation(); + if (orientation == CGAL::COUNTERCLOCKWISE) { + m_outer_ccbs.push_back(circ_start); + } else if (orientation == CGAL::CLOCKWISE) { + m_inner_ccbs.push_back(circ_start); + } else { + throw std::runtime_error("Face orientation is not clockwise nor counterclockwise."); + } + } +} + +std::ostream& operator<<(std::ostream& out, const Order& o) { + return out << to_string(o); +} + +std::ostream& operator<<(std::ostream& out, const Relation& r) { + return out << r.left << " " << r.ordering + << (r.preference != r.ordering ? "(" + to_string(r.preference) + ")" : "") + << " " << r.right; +} + DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds) : m_gs(gs), m_cds(cds) { for (const auto& p : partition) { @@ -288,11 +272,6 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G } } -// MyObserver obs(m_arr, curves_data); -// for (auto& curve : x_monotone_curves) { -// CGAL::insert(m_arr, curve); -// } - CGAL::insert(m_arr, curves.begin(), curves.end()); // Set for each face which pattern it is a subset of. for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { @@ -336,40 +315,206 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G auto cs = intersectionComponents(i, j); for (auto& c : cs) { auto rel = computePreference(i, j, c); - for (auto f : c.m_faces) { - f->data().relations.push_back(rel); + for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { + fit->data().relations.push_back(rel); } } } } } +/// Returns parts of the boundary of c that originate from i. +/// This function assumes that some part of the boundary, but not all of the boundary, originates from i. +std::vector boundaryParts(const Component& c, int i) { + std::vector ccbs; + std::copy(c.outer_ccbs_begin(), c.outer_ccbs_end(), std::back_inserter(ccbs)); + std::copy(c.inner_ccbs_begin(), c.inner_ccbs_end(), std::back_inserter(ccbs)); + + std::vector polylines; + + for (const auto& ccb : ccbs) { + // First find a half-edge at the start of a 'part' of this CCB (connected component of the boundary). + auto circ = ccb; + + bool found = true; + while (circ->data().origin != i) { + ++circ; + if (circ == ccb) { + found = false; + break; + } + } + + if (!found) { + continue; + } + + while (true) { + auto prev = circ; + --prev; + if (prev->data().origin == i) { + circ = prev; + } else { + break; + } + } + assert(circ->data().origin == i); + + // Next, make a polyline for every connected part of the boundary that originates from i. + std::vector xm_curves; + auto curr = circ; + do { + if (curr->data().origin == i) { + xm_curves.push_back(curr->curve()); + } else { + if (!xm_curves.empty()) { + polylines.emplace_back(xm_curves.begin(), xm_curves.end()); + xm_curves.clear(); + } + } + } while (++curr != circ); + if (!xm_curves.empty()) { + polylines.emplace_back(xm_curves.begin(), xm_curves.end()); + xm_curves.clear(); + } + } + + return polylines; +} + +// todo: check if small circular arcs should be allowed +bool isStraight(const CSPolyline& polyline) { + for (auto cit = polyline.curves_begin(); cit != polyline.curves_end(); ++cit) { + if (cit->is_circular()) return false; + } + return true; +} + +/// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref j. +IncludeExcludeDisks +DilatedPatternDrawing::includeExcludeDisks(int i, int j, const Component& c) { + std::vector> ptsI; + const auto& catPointsI = m_dilated[i].catPoints(); + std::transform(catPointsI.begin(), catPointsI.end(), std::back_inserter(ptsI), [](const CatPoint& catPoint) { + return Point(catPoint.point.x(), catPoint.point.y()); + }); + std::vector> ptsJ; + const auto& catPointsJ = m_dilated[j].catPoints(); + std::transform(catPointsJ.begin(), catPointsJ.end(), std::back_inserter(ptsJ), [](const CatPoint& catPoint) { + return Point(catPoint.point.x(), catPoint.point.y()); + }); + + auto rSqrd = m_gs.dilationRadius() * m_gs.dilationRadius(); + auto result = growCircles(ptsI, ptsJ, rSqrd, rSqrd * m_cds.cutoutRadiusFactor); + + std::vector> relevantExclusionDisks; + for (const auto& d : result.second) { + if (CGAL::do_intersect(circleToCSPolygon(d), ccb_to_polygon(c.outer_ccb()))) { + relevantExclusionDisks.push_back(d); + } + } + return {result.first, relevantExclusionDisks}; +} + Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& c) { - auto pref = CGAL::ZERO; + // The preference indicates the relation R in iRj. + // If R is Order::GREATER then i > j and i is preferred to be on top of j. + auto pref = Order::EQUAL; // 3. Prefer to cover a line segment over covering a circular arc. + auto circArcIsCovered = [](const std::vector& bps) { + for (auto& polyline : bps) { + if (!isStraight(polyline)) { + return true; + } + } + return false; + }; + + // If j is stacked over i then it covers all edges of i. + // Check if any of them is a circular arc. + auto bpi = boundaryParts(c, i); + assert(!bpi.empty()); + bool iCircArcIsCovered = circArcIsCovered(bpi); + // Vice versa. + auto bpj = boundaryParts(c, j); + assert(!bpj.empty()); + bool jCircArcIsCovered = circArcIsCovered(bpj); + + if (iCircArcIsCovered && !jCircArcIsCovered) { + pref = Order::GREATER; + } + if (!iCircArcIsCovered && jCircArcIsCovered){ + pref = Order::SMALLER; + } + // 2. Prefer to indent a line segment over indenting a circular arc. + // Disks that would be cut out of i to expose points in j. + auto jExclusion = includeExcludeDisks(i, j, c).exclude; + // Disks that would be cut out of j to expose points in i. + auto iExclusion = includeExcludeDisks(j, i, c).exclude; + + auto circularIndented = [](const std::vector>& exclusionDisks, const std::vector& bps) { + for (const auto& d : exclusionDisks) { + if (d.squared_radius() <= 0) continue; + for (auto& polyline : bps) { + auto inters = poly_line_gon_intersection(circleToCSPolygon(d), polyline); + for (auto& inter : inters) { + if (!isStraight(inter)) { + return true; + } + } + } + } + return false; + }; + bool iCircularIndented = circularIndented(jExclusion, bpi); + bool jCircularIndented = circularIndented(iExclusion, bpj); + + if (iCircularIndented && !jCircularIndented) { + pref = Order::GREATER; + } + if (!iCircularIndented && jCircularIndented){ + pref = Order::SMALLER; + } + // 1. Prefer to avoid few points over many points. - // todo: implement + // Fewer disks would be cut out of i than out of j, so prefer to stack i on top of j + if (jExclusion.size() < iExclusion.size()) { + pref = Order::GREATER; + } + if (iExclusion.size() < jExclusion.size()) { + pref = Order::SMALLER; + } return {i, j, pref, pref}; } std::vector -DilatedPatternDrawing::intersectionComponents(int i, int j) { - std::vector faces; - const std::vector& facesI = m_iToFaces[i]; - const std::vector& facesJ = m_iToFaces[j]; - std::set_intersection(facesI.begin(), facesI.end(), facesJ.begin(), facesJ.end(), std::back_inserter(faces)); - return connectedComponents(faces); +DilatedPatternDrawing::intersectionComponents(int i, int j) const { + return connectedComponents(m_arr, [i, j](FaceH fh) { + const auto& origins = fh->data().origins; + return std::find(origins.begin(), origins.end(), i) != origins.end() && + std::find(origins.begin(), origins.end(), j) != origins.end(); + }); } std::vector -DilatedPatternDrawing::intersectionComponents(int i) { - std::vector faces; - std::copy_if(m_iToFaces[i].begin(), m_iToFaces[i].end(), std::back_inserter(faces), - [](const FaceH& fh) { return fh->data().origins.size() > 1; }); - return connectedComponents(faces); +DilatedPatternDrawing::intersectionComponents(int i) const { + return connectedComponents(m_arr, [i](FaceH fh) { + const auto& origins = fh->data().origins; + return std::find(origins.begin(), origins.end(), i) != origins.end(); + }); +} + +std::string to_string(Order ord) { + if (ord == Order::SMALLER) { + return "<"; + } else if (ord == Order::EQUAL) { + return "="; + } else { + return ">"; + } } SimpleSetsPainting::SimpleSetsPainting(const DilatedPatternDrawing& dpd, const DrawSettings& ds) @@ -408,6 +553,22 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { renderer.setFillOpacity(150); renderer.setMode(renderer::GeometryRenderer::fill); renderer.draw(path); + + renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); + renderer.setFill(Color{0, 0, 0}); + renderer.setStroke(Color{0, 0, 0}, 1); + + auto& rs = face.data().relations; + if (!rs.empty()) { + auto r = rs[0]; + std::stringstream ss; + ss << r.left << " " << to_string(r.preference) << " " << r.right; + renderer.drawText(get_point_in(face), ss.str()); + } + + if (origins.size() == 1) { + renderer.drawText(get_point_in(face), std::to_string(origins[0])); + } } renderer.setMode(renderer::GeometryRenderer::stroke); diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 17ba7f63..0dee8e77 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -9,13 +9,25 @@ #include namespace cartocrow::simplesets { +enum Order { + SMALLER = -1, + EQUAL = 0, + GREATER = 1, +}; + +std::string to_string(Order ord); + +std::ostream& operator<<(std::ostream& out, const Order& o); + struct Relation { int left; int right; - CGAL::Sign preference; - CGAL::Sign ordering; + Order preference; + Order ordering; }; +std::ostream& operator<<(std::ostream& out, const Relation& r); + struct FaceData { std::vector origins; std::vector relations; @@ -44,6 +56,7 @@ using HalfEdgeH = DilatedPatternArrangement::Halfedge_handle; class Component { public: typedef DilatedPatternArrangement Arr; +// typedef std::vector::iterator Face_iterator; typedef Arr::Size Size; class ComponentCcbCirculator { @@ -59,7 +72,8 @@ class Component { using pointer = Arr::Halfedge_handle; using reference = value_type&; - ComponentCcbCirculator(std::function in_component) : m_in_component(std::move(in_component)) {}; + ComponentCcbCirculator(Arr::Halfedge_handle halfedge, std::function in_component) + : m_halfedge(halfedge), m_in_component(std::move(in_component)) {}; value_type operator*() const { return *m_halfedge; @@ -69,9 +83,17 @@ class Component { return m_halfedge; } + pointer ptr() const { + return m_halfedge; + } + + pointer handle() const { + return m_halfedge; + } + Self& operator++() { m_halfedge = m_halfedge->next(); - while (!m_in_component(m_halfedge->twin()->face())) { + while (m_in_component(m_halfedge->twin()->face())) { m_halfedge = m_halfedge->twin()->next(); } return *this; @@ -85,7 +107,7 @@ class Component { Self& operator--() { m_halfedge = m_halfedge->prev(); - while (!m_in_component(m_halfedge->twin()->face())) { + while (m_in_component(m_halfedge->twin()->face())) { m_halfedge = m_halfedge->twin()->prev(); } return *this; @@ -106,39 +128,120 @@ class Component { } }; -// Component(std::vector faces); - Component(std::function in_component); - -// bool has_outer_ccb() const; -// Hole_iterator holes_begin(); -// Hole_iterator holes_end(); -// Inner_ccb_iterator inner_ccbs_begin(); -// Inner_ccb_iterator inner_ccbs_end(); -// Size number_of_holes(); -// Size number_of_inner_ccbs(); -// Size number_of_outer_ccbs(); -// ComponentCcbCirculator outer_ccb(); -// Outer_ccb_iterator outer_ccbs_begin(); -// Outer_ccb_iterator outer_ccbs_end(); -// Face_iterator faces_begin(); -// Face_iterator faces_end(); + class Face_const_iterator { + private: + using Self = Face_const_iterator; + std::vector::const_iterator m_faceHandleIterator; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = Arr::Face; + using difference_type = std::ptrdiff_t; + using pointer = Arr::Face_handle; + using reference = value_type&; + + Face_const_iterator(std::vector::const_iterator faceHandleIterator) : + m_faceHandleIterator(faceHandleIterator) {}; + + value_type operator*() const { + return *(*m_faceHandleIterator); + } + + pointer operator->() const { + return *m_faceHandleIterator; + } + + Self& operator++() { + ++m_faceHandleIterator; + return *this; + }; + + Self operator++(int) { + Self tmp = *this; + this->operator++(); + return tmp; + } + + bool operator==(const Self& other) const { + return m_faceHandleIterator == other.m_faceHandleIterator; + } + + bool operator!=(const Self& other) const { + return m_faceHandleIterator != other.m_faceHandleIterator; + } + }; + + Component(std::vector faces, std::vector boundaryEdges, std::function inComponent); + + bool has_outer_ccb() const { + return !m_outer_ccbs.empty(); + } + Size number_of_outer_ccbs() const { + return m_outer_ccbs.size(); + } + std::vector::const_iterator outer_ccbs_begin() const { + return m_outer_ccbs.cbegin(); + } + std::vector::const_iterator outer_ccbs_end() const { + return m_outer_ccbs.cend(); + } + + ComponentCcbCirculator outer_ccb() const { + return m_outer_ccbs[0]; + } + std::vector::const_iterator inner_ccbs_begin() const { + return m_inner_ccbs.cbegin(); + } + std::vector::const_iterator inner_ccbs_end() const { + return m_inner_ccbs.cend(); + } + Size number_of_inner_ccbs() const { + return m_inner_ccbs.size(); + } + + // Hole is an alias for inner_ccb + std::vector::const_iterator holes_begin() const { + return inner_ccbs_begin(); + } + std::vector::const_iterator holes_end() const { + return inner_ccbs_end(); + } + Size number_of_holes() { + return number_of_inner_ccbs(); + } + + Face_const_iterator faces_begin() const { + return {m_faces.cbegin()}; + } + Face_const_iterator faces_end() const { + return {m_faces.cend()}; + } private: std::vector m_faces; std::function m_in_component; + std::vector m_outer_ccbs; + std::vector m_inner_ccbs; +}; + +struct IncludeExcludeDisks { + std::vector> include; + std::vector> exclude; }; class DilatedPatternDrawing { public: DilatedPatternDrawing(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds); - std::vector intersectionComponents(int i); - std::vector intersectionComponents(int i, int j); + std::vector intersectionComponents(int i) const; + std::vector intersectionComponents(int i, int j) const; Relation computePreference(int i, int j, const Component& c); + IncludeExcludeDisks includeExcludeDisks(int i, int j, const Component& c); + DilatedPatternArrangement m_arr; std::unordered_map> m_iToFaces; - std::map m_curve_to_origin; + std::map m_curve_to_origin; std::vector m_dilated; const GeneralSettings& m_gs; const ComputeDrawingSettings& m_cds; diff --git a/cartocrow/simplesets/general_polyline.h b/cartocrow/simplesets/general_polyline.h new file mode 100644 index 00000000..b865fa8f --- /dev/null +++ b/cartocrow/simplesets/general_polyline.h @@ -0,0 +1,33 @@ +#ifndef CARTOCROW_GENERAL_POLYLINE_H +#define CARTOCROW_GENERAL_POLYLINE_H + +#include + +template +class General_polyline_2 { + public: + typedef std::vector::iterator Curve_iterator; + typedef std::vector::const_iterator Curve_const_iterator; + + template + General_polyline_2(InputIterator begin, InputIterator end) { + m_xm_curves = std::vector(begin, end); + } + + Curve_iterator curves_begin() { + return m_xm_curves.begin(); + } + Curve_iterator curves_end() { + return m_xm_curves.end(); + } + Curve_const_iterator curves_begin() const { + return m_xm_curves.cbegin(); + } + Curve_const_iterator curves_end() const { + return m_xm_curves.cend(); + } + private: + std::vector m_xm_curves; +}; + +#endif //CARTOCROW_GENERAL_POLYLINE_H diff --git a/cartocrow/simplesets/grow_circles.cpp b/cartocrow/simplesets/grow_circles.cpp new file mode 100644 index 00000000..fb2218a0 --- /dev/null +++ b/cartocrow/simplesets/grow_circles.cpp @@ -0,0 +1,102 @@ +#include "grow_circles.h" + +namespace cartocrow::simplesets { +std::pair>, std::vector>> +growCircles(const std::vector>& points1, + const std::vector>& points2, + const Number& maxSquaredRadius1, + const Number& maxSquaredRadius2) { + std::vector growingCircles1; + std::transform(points1.begin(), points1.end(), std::back_inserter(growingCircles1), [](const Point& pt) { + return GrowingCircle{pt, 0, 0}; + }); + std::vector growingCircles2; + std::transform(points2.begin(), points2.end(), std::back_inserter(growingCircles2), [](const Point& pt) { + return GrowingCircle{pt, 0, 1}; + }); + + // if statement and while(true) combined. + while (!growingCircles1.empty() && !growingCircles2.empty()) { + bool done = true; + for (const auto& c : growingCircles1) { + if (!c.frozen) { + done = false; + break; + } + } + for (const auto& c : growingCircles2) { + if (!c.frozen) { + done = false; + break; + } + } + if (done) break; + + // Note that all variables representing distances contain squared distances. + std::optional> minDist; + std::optional> minPair; + for (auto& c1 : growingCircles1) { + for (auto& c2 : growingCircles2) { + if (c1.frozen && c2.frozen) continue; + auto centerDist = squared_distance(c1.center, c2.center); + + Number growDist; + if (!c1.frozen && !c2.frozen) { + growDist = centerDist / 2; + } else if (c1.frozen) { + growDist = centerDist - c1.squaredRadius; + } else { + growDist = centerDist - c2.squaredRadius; + } + + if (!minDist.has_value() || growDist < *minDist) { + minDist = growDist; + minPair = {&c1, &c2}; + } + } + } + + auto& [c1, c2] = *minPair; + + if (!c1->frozen && !c2->frozen) { + auto d = std::min(*minDist, std::min(maxSquaredRadius1, maxSquaredRadius2)); + if (d == *minDist) { + c1->frozen = true; + c2->frozen = true; + } else if (d == maxSquaredRadius1) { + c1->frozen = true; + } else { + assert(d == maxSquaredRadius2); + c2->frozen = true; + } + c1->squaredRadius = d; + c2->squaredRadius = d; + } else if (c1->frozen) { + c2->squaredRadius = std::min(*minDist, maxSquaredRadius2); + c2->frozen = true; + } else { + assert(c2->frozen); + c1->squaredRadius = std::min(*minDist, maxSquaredRadius1); + c1->frozen = true; + } + } + // Trivial case + if(growingCircles1.empty() || growingCircles2.empty()) { + for (auto& c : growingCircles1) { + c.squaredRadius = maxSquaredRadius1; + } + for (auto& c : growingCircles2) { + c.squaredRadius = maxSquaredRadius2; + } + } + + std::vector> circles1; + std::vector> circles2; + std::transform(growingCircles1.begin(), growingCircles1.end(), std::back_inserter(circles1), + [](const GrowingCircle& gc) { return Circle(gc.center, gc.squaredRadius); }); + std::transform(growingCircles2.begin(), growingCircles2.end(), std::back_inserter(circles2), + [](const GrowingCircle& gc) { return Circle(gc.center, gc.squaredRadius); }); + + return {circles1, circles2}; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/grow_circles.h b/cartocrow/simplesets/grow_circles.h new file mode 100644 index 00000000..c05af55b --- /dev/null +++ b/cartocrow/simplesets/grow_circles.h @@ -0,0 +1,18 @@ +#ifndef CARTOCROW_GROW_CIRCLES_H +#define CARTOCROW_GROW_CIRCLES_H + +#include "../core/core.h" + +namespace cartocrow::simplesets { +struct GrowingCircle { + Point center; + Number squaredRadius; + int type; + bool frozen = false; +}; + +std::pair>, std::vector>> +growCircles(const std::vector>& points1, const std::vector>& points2, + const Number& maxSquaredRadius1, const Number& maxSquaredRadius2); +} +#endif //CARTOCROW_GROW_CIRCLES_H diff --git a/cartocrow/simplesets/helpers/arrangement_helpers.h b/cartocrow/simplesets/helpers/arrangement_helpers.h new file mode 100644 index 00000000..8f255af5 --- /dev/null +++ b/cartocrow/simplesets/helpers/arrangement_helpers.h @@ -0,0 +1,26 @@ +#ifndef CARTOCROW_ARRANGEMENT_HELPERS_H +#define CARTOCROW_ARRANGEMENT_HELPERS_H + +#include +#include + +template +CGAL::General_polygon_2 ccb_to_polygon(Ccb ccb) { + auto curr = ccb; + + std::vector x_monotone_curves; + do { + auto curve = curr->curve(); + if (curr->source()->point() == curve.source()) { + x_monotone_curves.push_back(curve); + } else { + Traits traits; + auto opposite = traits.construct_opposite_2_object(); + x_monotone_curves.push_back(opposite(curve)); + } + } while(++curr != ccb); + + return {x_monotone_curves.begin(), x_monotone_curves.end()}; +} + +#endif //CARTOCROW_ARRANGEMENT_HELPERS_H diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp index 4f603add..1f1a0691 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp @@ -37,7 +37,7 @@ Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2, c } // ------ return signed area under the X-monotone curve -Number area(const CSTraits::X_monotone_curve_2& XCV) { +Number area(const X_monotone_curve_2& XCV) { if (XCV.is_linear()) { return area(XCV.source(), XCV.target()); } else if (XCV.is_circular()) { diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.h b/cartocrow/simplesets/helpers/cs_polygon_helpers.h index 1beeec36..4a2e85d5 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.h +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.h @@ -17,7 +17,7 @@ Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2); Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2, const CSTraits::Rational_circle_2& C); // ------ return signed area under the X-monotone curve -Number area(const CSTraits::X_monotone_curve_2& XCV); +Number area(const X_monotone_curve_2& XCV); // ------ return area of the simple polygon Number area(const CSPolygon P); diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp index f624899f..32ee34e4 100644 --- a/cartocrow/simplesets/partition_algorithm.cpp +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -150,7 +150,6 @@ partition(const std::vector& points, const GeneralSettings& gs, const while (!events.empty()) { auto ev = events.top(); events.pop(); - std::cout << ev.time << std::endl; if (ev.time > maxTime) break; @@ -208,7 +207,6 @@ partition(const std::vector& points, const GeneralSettings& gs, const partition.push_back(ev.result); // Save this partition history.emplace_back(ev.time, partition); - std::cout << ev.result->poly().index() << std::endl; // Create new merge events for (const auto& pattern : partition) { diff --git a/cartocrow/simplesets/poly_line_gon_intersection.cpp b/cartocrow/simplesets/poly_line_gon_intersection.cpp new file mode 100644 index 00000000..c2eab50c --- /dev/null +++ b/cartocrow/simplesets/poly_line_gon_intersection.cpp @@ -0,0 +1,53 @@ +#include "poly_line_gon_intersection.h" + +namespace cartocrow::simplesets { +std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline line) { + assert(!gon.is_empty()); + + Arr arr; + CGAL::insert_non_intersecting_curves(arr, line.curves_begin(), line.curves_end()); + for (auto eit = arr.edges_begin(); eit != arr.edges_end(); ++eit) { + eit->set_data({true}); + eit->twin()->set_data({true}); + } + CGAL::insert(arr, gon.curves_begin(), gon.curves_end()); + + std::vector line_edges; + for (auto eit = arr.edges_begin(); eit != arr.edges_end(); ++eit) { + if (eit->data().of_polyline) { + if (eit->source()->point() == eit->curve().source()) { + line_edges.push_back(eit.ptr()); + } else { + assert(eit->twin()->source()->point() == eit->twin()->curve().source()); + line_edges.push_back(eit->twin().ptr()); + } + } + } + + std::vector parts; + while (!line_edges.empty()) { + // Find first edge on connected component of polyline (in the intersection with polygon) + auto start = line_edges.front(); + auto curr = start; + while (curr->prev()->data().of_polyline) { + curr = curr->prev(); + + // The polyline and polygon do not intersect. + if (curr == start) { + return {}; + } + } + std::vector xmcs; + auto last_it = line_edges.end(); + do { + last_it = std::remove(line_edges.begin(), last_it, curr); + xmcs.push_back(curr->curve()); + curr = curr->next(); + } while (curr->data().of_polyline); + line_edges.erase(last_it, line_edges.end()); + parts.emplace_back(xmcs.begin(), xmcs.end()); + } + + return parts; +} +} diff --git a/cartocrow/simplesets/poly_line_gon_intersection.h b/cartocrow/simplesets/poly_line_gon_intersection.h new file mode 100644 index 00000000..b16afd12 --- /dev/null +++ b/cartocrow/simplesets/poly_line_gon_intersection.h @@ -0,0 +1,17 @@ +#ifndef CARTOCROW_POLY_LINE_GON_INTERSECTION_H +#define CARTOCROW_POLY_LINE_GON_INTERSECTION_H + +#include "types.h" + +namespace cartocrow::simplesets { +// todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. +struct HalfEdgePolylineData { + bool of_polyline = false; +}; + +using Arr = CGAL::Arrangement_2>; + +std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline line); +} + +#endif //CARTOCROW_POLY_LINE_GON_INTERSECTION_H diff --git a/cartocrow/simplesets/types.cpp b/cartocrow/simplesets/types.cpp index c8c949c9..52d93bc4 100644 --- a/cartocrow/simplesets/types.cpp +++ b/cartocrow/simplesets/types.cpp @@ -22,4 +22,27 @@ Polygon makeExact(const Polygon& polygon) { Point approximateAlgebraic(const CSTraits::Point_2& algebraic_point) { return {CGAL::to_double(algebraic_point.x()), CGAL::to_double(algebraic_point.y())}; } + +CSPolygon circleToCSPolygon(const Circle& circle) { + CSTraits traits; + auto make_x_monotone = traits.make_x_monotone_2_object(); + std::vector> curves_and_points; + make_x_monotone(circle, std::back_inserter(curves_and_points)); + std::vector curves; + + // There should not be any isolated points + for (auto kinda_curve : curves_and_points) { + if (kinda_curve.which() == 1) { + auto curve = boost::get(kinda_curve); + curves.push_back(curve); + } else { + std::cout << "Splitting circle into x-monotone curves results in isolated point." << std::endl; + std::cout << circle.center() << " squared radius: " << circle.squared_radius() << std::endl; + throw std::runtime_error("Cannot convert circle of radius 0 into a polygon"); + } + } + + // todo: is order of curves always correct? Because of weird error with intersection delay. + return CSPolygon{curves.begin(), curves.end()}; +} } diff --git a/cartocrow/simplesets/types.h b/cartocrow/simplesets/types.h index 9c511140..a8cf087d 100644 --- a/cartocrow/simplesets/types.h +++ b/cartocrow/simplesets/types.h @@ -9,13 +9,17 @@ #include #include +#include "general_polyline.h" + namespace cartocrow::simplesets { //typedef Exact K; typedef CGAL::Arr_circle_segment_traits_2 CSTraits; typedef CGAL::Gps_circle_segment_traits_2 CSTraitsBoolean; typedef CSTraitsBoolean::Polygon_2 CSPolygon; typedef CSTraitsBoolean::Polygon_with_holes_2 CSPolygonWithHoles; +typedef General_polyline_2 CSPolyline; typedef CGAL::Arrangement_2 CSArrangement; +typedef CSTraits::X_monotone_curve_2 X_monotone_curve_2; //typedef CGAL::CORE_algebraic_number_traits Nt_traits; //typedef CGAL::Cartesian Rat_kernel; //typedef Nt_traits::Algebraic Algebraic; @@ -28,6 +32,8 @@ Point makeExact(const Point& point); std::vector> makeExact(const std::vector>& points); Polygon makeExact(const Polygon& polygon); Point approximateAlgebraic(const CSTraits::Point_2& algebraic_point); + +CSPolygon circleToCSPolygon(const Circle& circle); } #endif //CARTOCROW_TYPES_H diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 71d0d626..23262071 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -40,6 +40,7 @@ along with this program. If not, see . #include #include "cartocrow/simplesets/dilated/dilated_poly.h" #include "colors.h" +#include namespace fs = std::filesystem; using namespace cartocrow; @@ -64,6 +65,9 @@ SimpleSetsDemo::SimpleSetsDemo() { vLayout->addWidget(fileSelectorLabel); vLayout->addWidget(fileSelector); + auto* fitToScreenButton = new QPushButton("Fit to screen"); + vLayout->addWidget(fitToScreenButton); + auto renderer = new GeometryWidget(); renderer->setDrawAxes(false); setCentralWidget(renderer); @@ -86,7 +90,6 @@ SimpleSetsDemo::SimpleSetsDemo() { m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}}; m_cds = ComputeDrawingSettings{0.675}; auto partitionList = partition(nycPoints, m_gs, m_ps, 8 * m_gs.dilationRadius()); - std::cout << partitionList.size() << std::endl; Number cover = 4.7; @@ -104,6 +107,16 @@ SimpleSetsDemo::SimpleSetsDemo() { auto pp = std::make_shared(m_partition, m_gs, m_ds); renderer->addPainting(pp, "Partition"); + + connect(fitToScreenButton, &QPushButton::clicked, [nycPoints, renderer, this]() { + std::vector> pts; + std::transform(nycPoints.begin(), nycPoints.end(), std::back_inserter(pts), + [](const CatPoint& pt) { return pt.point; }); + Box box = CGAL::bbox_2(pts.begin(), pts.end()); + auto delta = 2 * m_gs.dilationRadius(); + Box expanded(box.xmin() - delta, box.ymin() - delta, box.xmax() + delta, box.ymax() + delta); + renderer->fitInView(expanded); + }); } int main(int argc, char* argv[]) { From 4ccbec1f067e168a6751d7453851530204d4b64b Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Wed, 21 Aug 2024 18:58:17 +0200 Subject: [PATCH 08/36] SimpleSets drawing: hyperedges and face ordering --- cartocrow/simplesets/drawing_algorithm.cpp | 235 ++++++++++++++++++++- cartocrow/simplesets/drawing_algorithm.h | 24 +++ 2 files changed, 248 insertions(+), 11 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 8e758b42..a55e6b0e 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -1,8 +1,6 @@ #include "drawing_algorithm.h" #include -#include "dilated/dilated_poly.h" -#include "../renderer/render_path.h" #include "grow_circles.h" #include "poly_line_gon_intersection.h" #include "helpers/arrangement_helpers.h" @@ -255,6 +253,14 @@ std::ostream& operator<<(std::ostream& out, const Relation& r) { << " " << r.right; } +bool operator==(const Relation& lhs, const Relation& rhs) { + return lhs.left == rhs.left && lhs.right == rhs.right && lhs.preference == rhs.preference && lhs.ordering == rhs.ordering; +} + +bool operator==(const Hyperedge& lhs, const Hyperedge& rhs) { + return lhs.relations == rhs.relations && lhs.origins == rhs.origins; +} + DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds) : m_gs(gs), m_cds(cds) { for (const auto& p : partition) { @@ -321,6 +327,69 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G } } } + + auto hEdges = hyperedges(); + for (auto edge : hEdges) { + auto order = getRelationOrder(edge); + if (!order.has_value()) { + throw std::runtime_error("Hyperedge has cycle."); + } else { + setRelationOrder(edge, *order); + } + } + + for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { + auto& data = fit->data(); + auto ordering = computeTotalOrder(data.origins, data.relations); + if (!ordering.has_value()) { + throw std::runtime_error("Impossible: no total order in a face"); + } + data.ordering = *ordering; + } + + for (int i = 0; i < m_dilated.size(); ++i) { + auto cs = intersectionComponents(i); + for (auto& c : cs) { + std::vector avoidees; + for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { + for (int j : fit->data().ordering) { + if (j == i) break; + avoidees.push_back(j); + } + } + if (avoidees.empty()) continue; + + std::vector morphedEdges; + auto bpis = boundaryParts(c, i); + for (const auto& bpi : bpis) { + auto disks = includeExcludeDisks(i, avoidees, c); + auto inclDisks = disks.include; + auto exclDisks = disks.exclude; + + if (exclDisks.empty()) continue; + auto poly = ccb_to_polygon(c.outer_ccb()); + auto morphed = morph(bpi, poly, inclDisks, exclDisks, m_gs, m_cds); + + for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { + fit->data().morphedEdges[i].push_back(morphed); + } + morphedEdges.push_back(morphed); + } + + if (morphedEdges.empty()) continue; + + // Compute the morphed version of the CSPolygon for this component. + // Set, for each face in component c, the morphed face to the intersection of this CSPolygon with the face. + // todo + } + } +} + +CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape, const std::vector>& inclDisks, + const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds) { + // todo + std::vector curves; + return { curves.begin(), curves.end() }; } /// Returns parts of the boundary of c that originate from i. @@ -390,22 +459,25 @@ bool isStraight(const CSPolyline& polyline) { return true; } -/// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref j. +/// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref js. IncludeExcludeDisks -DilatedPatternDrawing::includeExcludeDisks(int i, int j, const Component& c) { +DilatedPatternDrawing::includeExcludeDisks(int i, std::vector js, const Component& c) { std::vector> ptsI; const auto& catPointsI = m_dilated[i].catPoints(); std::transform(catPointsI.begin(), catPointsI.end(), std::back_inserter(ptsI), [](const CatPoint& catPoint) { return Point(catPoint.point.x(), catPoint.point.y()); }); - std::vector> ptsJ; - const auto& catPointsJ = m_dilated[j].catPoints(); - std::transform(catPointsJ.begin(), catPointsJ.end(), std::back_inserter(ptsJ), [](const CatPoint& catPoint) { - return Point(catPoint.point.x(), catPoint.point.y()); - }); + std::vector> ptsJs; + for (int j : js) { + const auto& catPointsJ = m_dilated[j].catPoints(); + std::transform(catPointsJ.begin(), catPointsJ.end(), std::back_inserter(ptsJs), + [](const CatPoint& catPoint) { + return Point(catPoint.point.x(), catPoint.point.y()); + }); + } auto rSqrd = m_gs.dilationRadius() * m_gs.dilationRadius(); - auto result = growCircles(ptsI, ptsJ, rSqrd, rSqrd * m_cds.cutoutRadiusFactor); + auto result = growCircles(ptsI, ptsJs, rSqrd, rSqrd * m_cds.cutoutRadiusFactor); std::vector> relevantExclusionDisks; for (const auto& d : result.second) { @@ -416,6 +488,13 @@ DilatedPatternDrawing::includeExcludeDisks(int i, int j, const Component& c) { return {result.first, relevantExclusionDisks}; } +/// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref j. +IncludeExcludeDisks +DilatedPatternDrawing::includeExcludeDisks(int i, int j, const Component& c) { + std::vector js({j}); + return includeExcludeDisks(i, js, c); +} + Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& c) { // The preference indicates the relation R in iRj. // If R is Order::GREATER then i > j and i is preferred to be on top of j. @@ -503,10 +582,144 @@ std::vector DilatedPatternDrawing::intersectionComponents(int i) const { return connectedComponents(m_arr, [i](FaceH fh) { const auto& origins = fh->data().origins; - return std::find(origins.begin(), origins.end(), i) != origins.end(); + return origins.size() > 1 && std::find(origins.begin(), origins.end(), i) != origins.end(); }); } +std::vector DilatedPatternDrawing::hyperedges() { + std::vector interesting; + + for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { + if (fit->data().origins.size() >= 3) { + interesting.push_back(fit); + } + } + + std::sort(interesting.begin(), interesting.end(), [](const auto& fh1, const auto& fh2) { + return fh1->data().origins.size() < fh2->data().origins.size(); + }); + + std::vector> hyperedgesGrouped; + + std::vector group; + std::optional lastSize; + for (const auto& fh : interesting) { + if (!lastSize.has_value() || fh->data().origins.size() == *lastSize) { + group.emplace_back(fh->data().origins, fh->data().relations); + } else { + if (!group.empty()) { + hyperedgesGrouped.push_back(group); + group.clear(); + } + } + } + if (!group.empty()) { + hyperedgesGrouped.push_back(group); + } + + std::vector> trashCan; + for (int i = 0; i < hyperedgesGrouped.size(); i++) { + if (i + 1 >= hyperedgesGrouped.size()) break; + const auto& group = hyperedgesGrouped[i]; + for (const auto& hyperedge : group) { + for (const auto& larger : hyperedgesGrouped[i+1]) { + bool fullyContained = true; + for (const auto& r : hyperedge.relations) { + if (std::find(larger.relations.begin(), larger.relations.end(), r) == larger.relations.end()) { + fullyContained = false; + } + } + if (fullyContained) { + // store hyperedge for removal + trashCan.emplace_back(i, hyperedge); + break; + } + } + } + } + + for (const auto& [i, r] : trashCan) { + auto& group = hyperedgesGrouped[i]; + group.erase(std::remove(group.begin(), group.end(), r), group.end()); + } + + std::vector hyperedges; + for (const auto& group : hyperedgesGrouped) { + std::copy(group.begin(), group.end(), std::back_inserter(hyperedges)); + } + + return hyperedges; +} + +std::optional> computeTotalOrder(const std::vector& origins, const std::vector& relations) { + struct Vertex { + int i; + std::vector neighbors; + int mark = 0; + }; + + std::unordered_map vertices; + + for (int i : origins) { + vertices[i] = Vertex{i}; + } + + for (const auto& r : relations) { + if (r.preference == Order::EQUAL) continue; + auto u = vertices.at(r.left); + auto v = vertices.at(r.right); + + if (r.ordering == Order::SMALLER) { + v.neighbors.push_back(u); + } else { + u.neighbors.push_back(v); + } + } + + std::vector ordering; + + std::function visit; + + visit = [&ordering, &visit](Vertex& u) { + if (u.mark == 2) return true; + if (u.mark == 1) return false; + + u.mark = 1; + + for (auto& v : u.neighbors) { + bool success = visit(v); + if (!success) return false; + } + + u.mark = 2; + ordering.push_back(u.i); + return true; + }; + + for (auto& [_, v] : vertices) { + bool success = visit(v); + if (!success) return std::nullopt; + } + + return ordering; +} + +std::optional> getRelationOrder(const Hyperedge& e) { + return computeTotalOrder(e.origins, e.relations); +} + +void setRelationOrder(Hyperedge& e, const std::vector& ordering) { + for (auto& r : e.relations) { + int i = std::find(ordering.begin(), ordering.end(), r.left) - ordering.begin(); + int j = std::find(ordering.begin(), ordering.end(), r.right) - ordering.begin(); + if (i < j) { + r.ordering = Order::SMALLER; + } else { + r.ordering = Order::GREATER; + } + } +} + std::string to_string(Order ord) { if (ord == Order::SMALLER) { return "<"; diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 0dee8e77..1fd5975f 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -26,11 +26,27 @@ struct Relation { Order ordering; }; +bool operator==(const Relation& lhs, const Relation& rhs); + std::ostream& operator<<(std::ostream& out, const Relation& r); +class Hyperedge { + public: + std::vector origins; + std::vector relations; +}; + +std::optional> getRelationOrder(const Hyperedge& e); +void setRelationOrder(Hyperedge& e, const std::vector& ordering); +std::optional> computeTotalOrder(const std::vector& origins, const std::vector& relations); + +bool operator==(const Hyperedge& lhs, const Hyperedge& rhs); + struct FaceData { std::vector origins; std::vector relations; + std::vector ordering; + std::unordered_map> morphedEdges; }; // todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. @@ -224,6 +240,11 @@ class Component { std::vector m_inner_ccbs; }; +std::vector boundaryParts(const Component& c, int i); + +CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape, const std::vector>& inclDisks, + const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds); + struct IncludeExcludeDisks { std::vector> include; std::vector> exclude; @@ -238,7 +259,10 @@ class DilatedPatternDrawing { Relation computePreference(int i, int j, const Component& c); IncludeExcludeDisks includeExcludeDisks(int i, int j, const Component& c); + IncludeExcludeDisks includeExcludeDisks(int i, std::vector js, const Component& c); + std::vector hyperedges(); + DilatedPatternArrangement m_arr; std::unordered_map> m_iToFaces; std::map m_curve_to_origin; From 2c8b31ca2b16ecddfe0c75087284c77f0169b529 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Wed, 4 Sep 2024 11:16:13 +0200 Subject: [PATCH 09/36] Basic cutouts and drawing, with todos. --- cartocrow/simplesets/CMakeLists.txt | 13 +- cartocrow/simplesets/cat_point.cpp | 7 + cartocrow/simplesets/cat_point.h | 2 + cartocrow/simplesets/dilated/dilated_poly.cpp | 1 + cartocrow/simplesets/drawing_algorithm.cpp | 472 +++++++++++++----- cartocrow/simplesets/drawing_algorithm.h | 68 ++- cartocrow/simplesets/grow_circles.cpp | 18 +- cartocrow/simplesets/grow_circles.h | 3 +- .../helpers/approximate_convex_hull.cpp | 297 +++++++++++ .../helpers/approximate_convex_hull.h | 38 ++ .../simplesets/helpers/cs_curve_helpers.cpp | 137 +++++ .../simplesets/helpers/cs_curve_helpers.h | 42 ++ .../simplesets/helpers/cs_polygon_helpers.cpp | 113 ++++- .../simplesets/helpers/cs_polygon_helpers.h | 13 +- .../helpers/cs_polyline_helpers.cpp | 73 +++ .../simplesets/helpers/cs_polyline_helpers.h | 15 + .../poly_line_gon_intersection.cpp | 10 + .../poly_line_gon_intersection.h | 4 +- cartocrow/simplesets/partition_algorithm.cpp | 4 - cartocrow/simplesets/types.cpp | 27 +- cartocrow/simplesets/types.h | 10 +- demos/simplesets/CMakeLists.txt | 1 + demos/simplesets/circle_convex_hull.cpp | 46 ++ demos/simplesets/circle_convex_hull.h | 25 + demos/simplesets/simplesets_demo.cpp | 209 +++++++- demos/simplesets/simplesets_demo.h | 2 + 26 files changed, 1462 insertions(+), 188 deletions(-) create mode 100644 cartocrow/simplesets/cat_point.cpp create mode 100644 cartocrow/simplesets/helpers/approximate_convex_hull.cpp create mode 100644 cartocrow/simplesets/helpers/approximate_convex_hull.h create mode 100644 cartocrow/simplesets/helpers/cs_curve_helpers.cpp create mode 100644 cartocrow/simplesets/helpers/cs_curve_helpers.h create mode 100644 cartocrow/simplesets/helpers/cs_polyline_helpers.cpp create mode 100644 cartocrow/simplesets/helpers/cs_polyline_helpers.h rename cartocrow/simplesets/{ => helpers}/poly_line_gon_intersection.cpp (71%) rename cartocrow/simplesets/{ => helpers}/poly_line_gon_intersection.h (73%) create mode 100644 demos/simplesets/circle_convex_hull.cpp create mode 100644 demos/simplesets/circle_convex_hull.h diff --git a/cartocrow/simplesets/CMakeLists.txt b/cartocrow/simplesets/CMakeLists.txt index 40c7275b..19111279 100644 --- a/cartocrow/simplesets/CMakeLists.txt +++ b/cartocrow/simplesets/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES + cat_point.cpp types.cpp parse_input.cpp patterns/pattern.cpp @@ -7,12 +8,15 @@ set(SOURCES patterns/island.cpp patterns/bank.cpp dilated/dilated_poly.cpp + helpers/approximate_convex_hull.cpp + helpers/cs_curve_helpers.cpp helpers/cs_polygon_helpers.cpp + helpers/cs_polyline_helpers.cpp + helpers/poly_line_gon_intersection.cpp partition_algorithm.cpp partition_painting.cpp drawing_algorithm.cpp grow_circles.cpp - poly_line_gon_intersection.cpp ) set(HEADERS types.h @@ -26,17 +30,20 @@ set(HEADERS patterns/island.h patterns/bank.h dilated/dilated_poly.h + helpers/approximate_convex_hull.h + helpers/arrangement_helpers.h helpers/point_voronoi_helpers.h + helpers/poly_line_gon_intersection.h helpers/cropped_voronoi.h + helpers/cs_curve_helpers.h helpers/cs_polygon_helpers.h + helpers/cs_polyline_helpers.h partition_algorithm.h partition_painting.h partition.h drawing_algorithm.h general_polyline.h grow_circles.h - poly_line_gon_intersection.h - helpers/arrangement_helpers.h ) add_library(simplesets ${SOURCES}) diff --git a/cartocrow/simplesets/cat_point.cpp b/cartocrow/simplesets/cat_point.cpp new file mode 100644 index 00000000..511cfa70 --- /dev/null +++ b/cartocrow/simplesets/cat_point.cpp @@ -0,0 +1,7 @@ +#include "cat_point.h" + +namespace cartocrow::simplesets { +std::ostream& operator<<(std::ostream& os, CatPoint const& catPoint) { + return os << catPoint.category << " " << catPoint.point; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/cat_point.h b/cartocrow/simplesets/cat_point.h index dff3c8bb..ab89236f 100644 --- a/cartocrow/simplesets/cat_point.h +++ b/cartocrow/simplesets/cat_point.h @@ -10,6 +10,8 @@ struct CatPoint { Point point; auto operator<=>(const CatPoint&) const = default; }; + +std::ostream& operator<<(std::ostream& os, CatPoint const& catPoint); } #endif //CARTOCROW_CAT_POINT_H diff --git a/cartocrow/simplesets/dilated/dilated_poly.cpp b/cartocrow/simplesets/dilated/dilated_poly.cpp index 5dd53a85..d2f1e12b 100644 --- a/cartocrow/simplesets/dilated/dilated_poly.cpp +++ b/cartocrow/simplesets/dilated/dilated_poly.cpp @@ -1,4 +1,5 @@ #include "dilated_poly.h" +#include "../helpers/cs_polygon_helpers.h" #include namespace cartocrow::simplesets { diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index a55e6b0e..0f62d5e9 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -1,16 +1,20 @@ #include "drawing_algorithm.h" -#include +#include "cartocrow/simplesets/helpers/poly_line_gon_intersection.h" #include "grow_circles.h" -#include "poly_line_gon_intersection.h" +#include "helpers/approximate_convex_hull.h" #include "helpers/arrangement_helpers.h" +#include "helpers/cs_curve_helpers.h" +#include "helpers/cs_polygon_helpers.h" +#include "helpers/cs_polyline_helpers.h" #include +#include using namespace cartocrow::renderer; namespace cartocrow::simplesets { // todo: deal with holes -CSPolygon face_to_polygon(const DilatedPatternArrangement::Face& face) { +CSPolygon face_to_polygon(const Face& face) { assert(face.number_of_holes() == 0); return ccb_to_polygon(face.outer_ccb()); } @@ -33,7 +37,7 @@ X_monotone_curve_2 make_x_monotone(const Segment& segment) { return curves[0]; } -Point get_approx_point_on_boundary(const DilatedPatternArrangement::Face& face) { +Point get_approx_point_on_boundary(const Face& face) { auto curve = face.outer_ccb()->curve(); Rectangle bbox = curve.bbox(); Rectangle rect({bbox.xmin() - 1, bbox.ymin() - 1}, {bbox.xmax() + 1, bbox.ymax() + 1}); @@ -82,7 +86,7 @@ Point get_approx_point_on_boundary(const DilatedPatternArrangement::Face& } } -Point get_point_in(const DilatedPatternArrangement::Face& face) { +Point get_point_in(const Face& face) { auto poly = face_to_polygon(face); Rectangle bbox = poly.bbox(); Rectangle rect({bbox.xmin() - 1, bbox.ymin() - 1}, {bbox.xmax() + 1, bbox.ymax() + 1}); @@ -124,29 +128,6 @@ Point get_point_in(const DilatedPatternArrangement::Face& face) { return midpoint(Segment(approx_source, approx_target)); } -bool on_or_inside(const CSPolygon& polygon, const Point& point) { - Ray ray(point, Vector(1, 0)); - - Rectangle bbox = polygon.bbox(); - Rectangle rect({bbox.xmin() - 1, bbox.ymin() - 1}, {bbox.xmax() + 1, bbox.ymax() + 1}); - - auto inter = CGAL::intersection(ray, rect); - if (!inter.has_value()) return false; - auto seg = boost::get>(*inter); - auto seg_curve = make_x_monotone(seg); - - typedef std::pair Intersection_point; - typedef boost::variant Intersection_result; - std::vector intersection_results; - - for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { - const auto& curve = *cit; - curve.intersect(seg_curve, std::back_inserter(intersection_results)); - } - - return intersection_results.size() % 2 == 1; -} - std::vector connectedComponents(const DilatedPatternArrangement& arr, std::function in_component) { std::vector remaining; @@ -357,67 +338,287 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G avoidees.push_back(j); } } - if (avoidees.empty()) continue; + if (avoidees.empty()) + continue; std::vector morphedEdges; - auto bpis = boundaryParts(c, i); + auto bpiss = originCcbs(c); + assert(bpiss.size() == 1); // todo + auto bpis = bpiss[0]; + + std::vector morphedComponentXMCurves; + auto poly = ccb_to_polygon(c.outer_ccb()); + + bool morphedFace = false; + for (const auto& bpi : bpis) { + auto bp = bpi.first; + if (bpi.second != i) { + std::copy(bp.curves_begin(), bp.curves_end(), std::back_inserter(morphedComponentXMCurves)); + continue; + } + auto disks = includeExcludeDisks(i, avoidees, c); auto inclDisks = disks.include; auto exclDisks = disks.exclude; - if (exclDisks.empty()) continue; - auto poly = ccb_to_polygon(c.outer_ccb()); - auto morphed = morph(bpi, poly, inclDisks, exclDisks, m_gs, m_cds); + if (exclDisks.empty()) { + std::copy(bp.curves_begin(), bp.curves_end(), std::back_inserter(morphedComponentXMCurves)); + continue; + } + + auto morphed = morph(bp, poly, inclDisks, exclDisks, m_gs, m_cds); + std::copy(morphed.curves_begin(), morphed.curves_end(), std::back_inserter(morphedComponentXMCurves)); for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { fit->data().morphedEdges[i].push_back(morphed); } - morphedEdges.push_back(morphed); + morphedFace = true; } - if (morphedEdges.empty()) continue; + if (!morphedFace) continue; // Compute the morphed version of the CSPolygon for this component. // Set, for each face in component c, the morphed face to the intersection of this CSPolygon with the face. - // todo + auto morphedComponentPolygon = CSPolygon(morphedComponentXMCurves.begin(), morphedComponentXMCurves.end()); + for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { + auto facePolygon = face_to_polygon(*fit); + std::vector morphedFacePolygonsWithHoles; + + CGAL::intersection(morphedComponentPolygon, facePolygon, std::back_inserter(morphedFacePolygonsWithHoles)); + assert(morphedFacePolygonsWithHoles.size() == 1); + auto morphedFacePolygonWithHoles = morphedFacePolygonsWithHoles[0]; + assert(!morphedFacePolygonWithHoles.has_holes()); // todo + fit->data().morphedFace[i] = morphedFacePolygonWithHoles.outer_boundary(); + } } } } +bool overlap(const Circle& c1, const Circle& c2) { + return CGAL::squared_distance(c1.center(), c2.center()) <= c1.squared_radius() + c2.squared_radius(); +} + +std::vector>>> connectedDisks(const std::vector>& disks) { + typedef std::vector>> Component; + std::vector components; + + auto merge = [&components](std::vector& comps) { + Component newComponent; + for (auto& comp : comps) { + std::copy(comp->begin(), comp->end(), std::back_inserter(newComponent)); + comp->clear(); + } + components.erase(std::remove_if(components.begin(), components.end(), [](const Component& cs) { + return cs.empty(); + }), components.end()); + return newComponent; + }; + + for (int i = 0; i < disks.size(); ++i) { + const auto& d = disks[i]; + std::vector overlaps; + for (auto& comp : components) { + bool anyOverlap = false; + for (const auto& other : comp) { + if (overlap(d, other.second)) { + anyOverlap = true; + break; + } + } + if (anyOverlap) { + overlaps.push_back(&comp); + } + } + auto merged = merge(overlaps); + merged.emplace_back(i, d); + components.push_back(merged); + } + + return components; +} + +CSPolygon thinRectangle(const Point& p, const OneRootPoint& n, const Number& w) { + Point nApproxInexact = approximateAlgebraic(n); + Point nApprox(nApproxInexact.x(), nApproxInexact.y()); + Vector d = nApprox - p; + auto dl = sqrt(CGAL::to_double(d.squared_length())); + Vector normalized = d / dl; + auto perp = normalized.perpendicular(CGAL::COUNTERCLOCKWISE) * w / 2; + + Point p1 = p - perp; + Point p2 = nApprox + normalized * w / 10 - perp; + Point p3 = nApprox + normalized * w / 10 + perp; + Point p4 = p + perp; + + std::vector curves({ + Curve_2(p1, p2), + Curve_2(p2, p3), + Curve_2(p3, p4), + Curve_2(p4, p1) + }); + + std::vector xm_curves; + curvesToXMonotoneCurves(curves.begin(), curves.end(), std::back_inserter(xm_curves)); + + return {xm_curves.begin(), xm_curves.end()}; +} + CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape, const std::vector>& inclDisks, const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds) { - // todo - std::vector curves; - return { curves.begin(), curves.end() }; + if (exclDisks.empty()) return boundaryPart; + + std::vector> lineCovering; + std::vector> arcCovering; + + for (const auto& d : exclDisks) { + auto inter = poly_line_gon_intersection(circleToCSPolygon(d), boundaryPart); + bool coversLine = true; + for (const auto& p : inter) { + if (!isStraight(p)) { + coversLine = false; + break; + } + } + if (coversLine) { + lineCovering.push_back(d); + } else { + arcCovering.push_back(d); + } + } + + auto dr = gs.dilationRadius(); + // Smoothing radius + auto sr = dr / 5; + + std::vector> expandedLineCoveringDisks; + for (const auto& d : lineCovering) { + expandedLineCoveringDisks.emplace_back(approximate(d.center()), squared(sqrt(CGAL::to_double(d.squared_radius())) + sr)); + } + auto diskComponents = connectedDisks(expandedLineCoveringDisks); + + std::vector cuts; + for (const auto& comp : diskComponents) { + std::vector> disks; + for (const auto& [i, _] : comp) { + disks.push_back(lineCovering[i]); + } + cuts.push_back(approximateConvexHull(disks)); + } + for (const auto& d : arcCovering) { + cuts.push_back(circleToCSPolygon(d)); + } + + CSPolygonSet polygonSet; + for (const auto& cut : cuts) { + polygonSet.join(cut); + } + for (const auto& d: exclDisks) { + if (on_or_inside(componentShape, d.center())) { + auto n = nearest(boundaryPart, d.center()); + auto rect = thinRectangle(d.center(), n, gs.pointSize / 5); + polygonSet.join(rect); + } + } + for (const auto& d: inclDisks) { + polygonSet.difference(circleToCSPolygon(d)); + } + std::vector modifiedCuts; + polygonSet.polygons_with_holes(std::back_inserter(modifiedCuts)); + + CSPolygonSet polygonSet2(componentShape); + for (const auto& modifiedCut : modifiedCuts) { + polygonSet2.difference(modifiedCut); + } + +// polygonSet.complement(); +// polygonSet.intersection(componentShape); + + std::vector polys; + polygonSet2.polygons_with_holes(std::back_inserter(polys)); + assert(polys.size() == 1); + auto poly = polys[0]; + assert(!poly.has_holes()); + + auto outer = poly.outer_boundary(); + std::vector outer_xm_curves; + for (auto cit = outer.curves_begin(); cit != outer.curves_end(); ++cit) { + outer_xm_curves.push_back(*cit); + } + auto boundaryPartStart = boundaryPart.curves_begin()->source(); + auto endIt = boundaryPart.curves_end(); + --endIt; + auto boundaryPartEnd = endIt->target(); +// int boundaryPartStartIndex = -1; +// int boundaryPartEndIndex = -1; + int startIndex = -1; + int endIndex = -1; + +// std::cout << "Looking for start and end of boundary part" << std::endl; + for (int i = 0; i < outer_xm_curves.size(); i++) { + auto c = outer_xm_curves[i]; +// std::cout << "curve " << c.source() << " -> " << c.target() << std::endl; + if (outer_xm_curves[i].source() == boundaryPartStart) { + startIndex = i; + } + if (outer_xm_curves[i].target() == boundaryPartEnd) { + endIndex = i; + } + } +// assert(boundaryPartStartIndex >= 0); +// assert(boundaryPartEndIndex >= 0); + +// std::cout << "Looking for alternative start and end" << std::endl; + for (int i = 0; i < outer_xm_curves.size() && (startIndex < 0 || endIndex < 0); i++) { + const auto& c = outer_xm_curves[i]; +// std::cout << "curve " << c.source() << "(" << liesOn(c.source(), componentShape).has_value() << ")" << " -> " << +// c.target() << "(" << liesOn(c.target(), componentShape).has_value() << ")" << " " << liesOn(c, componentShape) << std::endl; + if (startIndex < 0 && liesOn(c.source(), componentShape).has_value() && !liesOn(c, componentShape)) { + startIndex = i; +// std::cout << "Found start curve " << c.source() << " -> " << c.target() << std::endl; + } + if (endIndex < 0 && !liesOn(c, componentShape) && liesOn(c.target(), componentShape).has_value()) { + endIndex = i; +// std::cout << "Found end curve " << c.source() << " -> " << c.target() << std::endl; + } + } + + assert(startIndex >= 0); + assert(endIndex >= 0); + + std::vector xm_curves; + + if (endIndex > startIndex) { + for (int i = startIndex; i <= endIndex; ++i) { + xm_curves.push_back(outer_xm_curves[i]); + } + } else { + for (int i = startIndex; i < outer_xm_curves.size(); ++i) { + xm_curves.push_back(outer_xm_curves[i]); + } + for (int i = 0; i <= endIndex; ++i) { + xm_curves.push_back(outer_xm_curves[i]); + } + } + + //todo smooth + return {xm_curves.begin(), xm_curves.end()}; } -/// Returns parts of the boundary of c that originate from i. -/// This function assumes that some part of the boundary, but not all of the boundary, originates from i. -std::vector boundaryParts(const Component& c, int i) { +std::vector>> originCcbs(const Component& c) { std::vector ccbs; std::copy(c.outer_ccbs_begin(), c.outer_ccbs_end(), std::back_inserter(ccbs)); std::copy(c.inner_ccbs_begin(), c.inner_ccbs_end(), std::back_inserter(ccbs)); - std::vector polylines; + std::vector>> result; for (const auto& ccb : ccbs) { - // First find a half-edge at the start of a 'part' of this CCB (connected component of the boundary). - auto circ = ccb; + std::vector> polylines; - bool found = true; - while (circ->data().origin != i) { - ++circ; - if (circ == ccb) { - found = false; - break; - } - } - - if (!found) { - continue; - } + auto circ = ccb; + int i = ccb->data().origin; + // Go to start of this 'origin CCB'. Connected component of the boundary that all originates from the same shape. while (true) { auto prev = circ; --prev; @@ -427,25 +628,58 @@ std::vector boundaryParts(const Component& c, int i) { break; } } - assert(circ->data().origin == i); - // Next, make a polyline for every connected part of the boundary that originates from i. + // Next, make a polyline for every connected part of the boundary that originates from the same shape. std::vector xm_curves; auto curr = circ; do { if (curr->data().origin == i) { xm_curves.push_back(curr->curve()); } else { - if (!xm_curves.empty()) { - polylines.emplace_back(xm_curves.begin(), xm_curves.end()); - xm_curves.clear(); - } + polylines.emplace_back(CSPolyline(xm_curves.begin(), xm_curves.end()), i); + xm_curves.clear(); + i = curr->data().origin; + xm_curves.push_back(curr->curve()); } } while (++curr != circ); if (!xm_curves.empty()) { - polylines.emplace_back(xm_curves.begin(), xm_curves.end()); + polylines.emplace_back(CSPolyline(xm_curves.begin(), xm_curves.end()), i); xm_curves.clear(); } + + result.push_back(polylines); + } + + return result; +} + +/// Returns parts of the boundary of c that originate from i. +/// This function assumes that some part of the boundary, but not all of the boundary, originates from i. +std::vector boundaryParts(const Component& c, int i) { + std::vector ccbs; + std::copy(c.outer_ccbs_begin(), c.outer_ccbs_end(), std::back_inserter(ccbs)); + std::copy(c.inner_ccbs_begin(), c.inner_ccbs_end(), std::back_inserter(ccbs)); + + std::vector polylines; + + for (const auto& ccb : ccbs) { + boundaryParts(ccb, i, std::back_inserter(polylines)); + } + + return polylines; +} + +/// Returns parts of the boundary of c that originate from i. +/// This function assumes that some part of the boundary, but not all of the boundary, originates from i. +std::vector boundaryParts(FaceH fh, int i) { + std::vector ccbs; + std::copy(fh->outer_ccbs_begin(), fh->outer_ccbs_end(), std::back_inserter(ccbs)); + std::copy(fh->inner_ccbs_begin(), fh->inner_ccbs_end(), std::back_inserter(ccbs)); + + std::vector polylines; + + for (const auto& ccb : ccbs) { + boundaryParts(ccb, i, std::back_inserter(polylines)); } return polylines; @@ -461,7 +695,7 @@ bool isStraight(const CSPolyline& polyline) { /// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref js. IncludeExcludeDisks -DilatedPatternDrawing::includeExcludeDisks(int i, std::vector js, const Component& c) { +DilatedPatternDrawing::includeExcludeDisks(int i, const std::vector& js, const Component& c) { std::vector> ptsI; const auto& catPointsI = m_dilated[i].catPoints(); std::transform(catPointsI.begin(), catPointsI.end(), std::back_inserter(ptsI), [](const CatPoint& catPoint) { @@ -477,7 +711,7 @@ DilatedPatternDrawing::includeExcludeDisks(int i, std::vector js, const Com } auto rSqrd = m_gs.dilationRadius() * m_gs.dilationRadius(); - auto result = growCircles(ptsI, ptsJs, rSqrd, rSqrd * m_cds.cutoutRadiusFactor); + auto result = approximateGrowCircles(ptsI, ptsJs, rSqrd, rSqrd * m_cds.cutoutRadiusFactor * m_cds.cutoutRadiusFactor); std::vector> relevantExclusionDisks; for (const auto& d : result.second) { @@ -569,6 +803,49 @@ Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& return {i, j, pref, pref}; } +void DilatedPatternDrawing::drawFaceFill(FaceH fh, renderer::GeometryRenderer& renderer, + const GeneralSettings& gs, const DrawSettings& ds) const { + auto& d = fh->data(); + for (int i : d.ordering) { + renderer.setMode(GeometryRenderer::fill); + renderer.setFill(ds.colors[m_dilated[i].category()]); + if (!d.morphedFace.contains(i)) { + auto poly = face_to_polygon(*fh); + renderer.draw(renderPathFromCSPolygon(poly)); + } else { + renderer.draw(renderPathFromCSPolygon(d.morphedFace[i])); + } + } +} + +void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& renderer, + const GeneralSettings& gs, const DrawSettings& ds) const { + auto& d = fh->data(); + for (int i : d.ordering) { + renderer.setMode(GeometryRenderer::stroke); + renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs), true); + + // todo: take difference of polylines with the cs polygons stacked on top + std::vector polylines; + if (d.morphedEdges[i].empty()) { + auto bps = boundaryParts(fh, i); + std::copy(bps.begin(), bps.end(), std::back_inserter(polylines)); + } + std::copy(d.morphedEdges[i].begin(), d.morphedEdges[i].end(), std::back_inserter(polylines)); + +// for (const auto& polyline : polylines) { +// poly_line_gon_intersection(); +// } + +// if (d) { +// auto poly = face_to_polygon(face); +// renderer.draw(renderPathFromCSPolygon(poly)); +// } else { +// renderer.draw(renderPathFromCSPolygon(*d.morphedFace)); +// } + } +} + std::vector DilatedPatternDrawing::intersectionComponents(int i, int j) const { return connectedComponents(m_arr, [i, j](FaceH fh) { @@ -737,66 +1014,11 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { renderer.setMode(renderer::GeometryRenderer::fill); for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { if (fit->is_unbounded()) continue; - auto face = *fit; - auto origins = face.data().origins; - if (origins.size() > 1) { - renderer.setFill(Color{100, 100, 100}); - } else if (origins.size() == 1) { - renderer.setFill(m_ds.colors[m_dpd.m_dilated[origins[0]].category()]); - } else { - continue; - } - auto poly = face_to_polygon(face); - - RenderPath path; - bool first = true; - for (auto cit = poly.curves_begin(); cit != poly.curves_end(); ++cit) { - if (first) { - path.moveTo(approximateAlgebraic(cit->source())); - first = false; - } - if (cit->is_linear()) { - path.lineTo(approximateAlgebraic(cit->target())); - } else if (cit->is_circular()){ - auto circle = cit->supporting_circle(); - path.arcTo(approximate(circle.center()), cit->orientation() == CGAL::CLOCKWISE, approximateAlgebraic(cit->target())); - } - } - path.close(); - renderer.setFillOpacity(150); - renderer.setMode(renderer::GeometryRenderer::fill); - renderer.draw(path); - - renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); - renderer.setFill(Color{0, 0, 0}); - renderer.setStroke(Color{0, 0, 0}, 1); - - auto& rs = face.data().relations; - if (!rs.empty()) { - auto r = rs[0]; - std::stringstream ss; - ss << r.left << " " << to_string(r.preference) << " " << r.right; - renderer.drawText(get_point_in(face), ss.str()); - } - - if (origins.size() == 1) { - renderer.drawText(get_point_in(face), std::to_string(origins[0])); - } + m_dpd.drawFaceFill(fit.ptr(), renderer, m_dpd.m_gs, m_ds); } - - renderer.setMode(renderer::GeometryRenderer::stroke); - for (auto eit = m_dpd.m_arr.edges_begin(); eit != m_dpd.m_arr.edges_end(); ++eit) { - auto curve = eit->curve(); - renderer.setStroke(m_ds.colors[m_dpd.m_dilated[eit->data().origin].category()], m_ds.contourStrokeWeight(m_dpd.m_gs), true); - if (curve.is_linear()) { - renderer.draw(Segment(approximateAlgebraic(curve.source()), approximateAlgebraic(curve.target()))); - } else if (curve.is_circular()){ - RenderPath path; - path.moveTo(approximateAlgebraic(curve.source())); - auto circle = curve.supporting_circle(); - path.arcTo(approximate(circle.center()), curve.orientation() == CGAL::CLOCKWISE, approximateAlgebraic(curve.target())); - renderer.draw(path); - } + for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { + if (fit->is_unbounded()) continue; + m_dpd.drawFaceStroke(fit.ptr(), renderer, m_dpd.m_gs, m_ds); } } } \ No newline at end of file diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 1fd5975f..0e53f2b8 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -47,6 +47,7 @@ struct FaceData { std::vector relations; std::vector ordering; std::unordered_map> morphedEdges; + std::unordered_map morphedFace; }; // todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. @@ -65,6 +66,7 @@ using DilatedPatternArrangement = //DilatedPatternArrangement //dilateAndArrange(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds); +using Face = DilatedPatternArrangement::Face; using FaceH = DilatedPatternArrangement::Face_handle; using VertexH = DilatedPatternArrangement::Vertex_handle; using HalfEdgeH = DilatedPatternArrangement::Halfedge_handle; @@ -240,7 +242,62 @@ class Component { std::vector m_inner_ccbs; }; +template +void boundaryParts(Ccb ccb, int i, OutputIterator out) { + // First find a half-edge at the start of a 'part' of this CCB (connected component of the boundary). + auto circ = ccb; + + bool found = true; + while (circ->data().origin != i) { + ++circ; + if (circ == ccb) { + found = false; + break; + } + } + + if (!found) { + return; + } + + auto start = circ; + do { + auto prev = circ; + --prev; + if (prev->data().origin == i) { + circ = prev; + } else { + break; + } + } while (circ != start); + assert(circ->data().origin == i); + + // Next, make a polyline for every connected part of the boundary that originates from i. + std::vector xm_curves; + auto curr = circ; + do { + if (curr->data().origin == i) { + xm_curves.push_back(curr->curve()); + } else { + if (!xm_curves.empty()) { + ++out = CSPolyline(xm_curves.begin(), xm_curves.end()); + xm_curves.clear(); + } + } + } while (++curr != circ); + if (!xm_curves.empty()) { + ++out = CSPolyline(xm_curves.begin(), xm_curves.end()); + xm_curves.clear(); + } +} + std::vector boundaryParts(const Component& c, int i); +std::vector boundaryParts(FaceH f, int i); +std::vector>> originCcbs(const Component& c); + +// exposed for debugging purposes +std::vector>>> connectedDisks(const std::vector>& disks); +CSPolygon thinRectangle(const Point& p, const OneRootPoint& n, const Number& w); CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape, const std::vector>& inclDisks, const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds); @@ -250,6 +307,8 @@ struct IncludeExcludeDisks { std::vector> exclude; }; +bool isStraight(const CSPolyline& polyline); + class DilatedPatternDrawing { public: DilatedPatternDrawing(const Partition& partition, const GeneralSettings& gs, const ComputeDrawingSettings& cds); @@ -259,10 +318,15 @@ class DilatedPatternDrawing { Relation computePreference(int i, int j, const Component& c); IncludeExcludeDisks includeExcludeDisks(int i, int j, const Component& c); - IncludeExcludeDisks includeExcludeDisks(int i, std::vector js, const Component& c); + IncludeExcludeDisks includeExcludeDisks(int i, const std::vector& js, const Component& c); std::vector hyperedges(); - + + void drawFaceFill(FaceH fh, renderer::GeometryRenderer& renderer, + const GeneralSettings& gs, const DrawSettings& ds) const; + void drawFaceStroke(FaceH fh, renderer::GeometryRenderer& renderer, + const GeneralSettings& gs, const DrawSettings& ds) const; + DilatedPatternArrangement m_arr; std::unordered_map> m_iToFaces; std::map m_curve_to_origin; diff --git a/cartocrow/simplesets/grow_circles.cpp b/cartocrow/simplesets/grow_circles.cpp index fb2218a0..d5945e17 100644 --- a/cartocrow/simplesets/grow_circles.cpp +++ b/cartocrow/simplesets/grow_circles.cpp @@ -2,17 +2,17 @@ namespace cartocrow::simplesets { std::pair>, std::vector>> -growCircles(const std::vector>& points1, - const std::vector>& points2, - const Number& maxSquaredRadius1, - const Number& maxSquaredRadius2) { +approximateGrowCircles(const std::vector>& points1, + const std::vector>& points2, + const Number& maxSquaredRadius1, + const Number& maxSquaredRadius2) { std::vector growingCircles1; std::transform(points1.begin(), points1.end(), std::back_inserter(growingCircles1), [](const Point& pt) { - return GrowingCircle{pt, 0, 0}; + return GrowingCircle{pt, 0}; }); std::vector growingCircles2; std::transform(points2.begin(), points2.end(), std::back_inserter(growingCircles2), [](const Point& pt) { - return GrowingCircle{pt, 0, 1}; + return GrowingCircle{pt, 0}; }); // if statement and while(true) combined. @@ -42,11 +42,11 @@ growCircles(const std::vector>& points1, Number growDist; if (!c1.frozen && !c2.frozen) { - growDist = centerDist / 2; + growDist = centerDist / 4; } else if (c1.frozen) { - growDist = centerDist - c1.squaredRadius; + growDist = centerDist + c1.squaredRadius - 2 * sqrt(CGAL::to_double(centerDist)) * sqrt(CGAL::to_double(c1.squaredRadius)); } else { - growDist = centerDist - c2.squaredRadius; + growDist = centerDist + c2.squaredRadius - 2 * sqrt(CGAL::to_double(centerDist)) * sqrt(CGAL::to_double(c2.squaredRadius)); } if (!minDist.has_value() || growDist < *minDist) { diff --git a/cartocrow/simplesets/grow_circles.h b/cartocrow/simplesets/grow_circles.h index c05af55b..fbb01861 100644 --- a/cartocrow/simplesets/grow_circles.h +++ b/cartocrow/simplesets/grow_circles.h @@ -7,12 +7,11 @@ namespace cartocrow::simplesets { struct GrowingCircle { Point center; Number squaredRadius; - int type; bool frozen = false; }; std::pair>, std::vector>> -growCircles(const std::vector>& points1, const std::vector>& points2, +approximateGrowCircles(const std::vector>& points1, const std::vector>& points2, const Number& maxSquaredRadius1, const Number& maxSquaredRadius2); } #endif //CARTOCROW_GROW_CIRCLES_H diff --git a/cartocrow/simplesets/helpers/approximate_convex_hull.cpp b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp new file mode 100644 index 00000000..a2924334 --- /dev/null +++ b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp @@ -0,0 +1,297 @@ +#include "approximate_convex_hull.h" +#include "cs_curve_helpers.h" +#include "cs_polygon_helpers.h" + +namespace cartocrow::simplesets { +Segment tangent(const Circle& c1, const Circle& c2) { + auto distSq = CGAL::squared_distance(c1.center(), c2.center()); + auto hyp = c2.center() - c1.center(); + auto c1r = sqrt(c1.squared_radius()); + auto c2r = sqrt(c2.squared_radius()); + auto adj = c1r - c2r; + auto a = hyp * adj; + auto b = hyp.perpendicular(CGAL::POSITIVE) * sqrt(distSq - adj * adj); + auto v1 = (a - b) / distSq; + auto v2 = (a + b) / distSq; + + Segment t1(c1.center() + v1 * c1r, c2.center() + v1 * c2r); + Segment t2(c1.center() + v2 * c1r, c2.center() + v2 * c2r); + + if (CGAL::orientation(t1.source(), t1.target(), c2.center()) == CGAL::COUNTERCLOCKWISE) { + return t1; + } else { + return t2; + } +} + +std::pair, Point> +tangentPoints(const Circle& c1, const Circle& c2) { + auto distSq = CGAL::squared_distance(c1.center(), c2.center()); + auto hyp = c2.center() - c1.center(); + auto c1r = sqrt(c1.squared_radius()); + auto c2r = sqrt(c2.squared_radius()); + auto adj = c1r - c2r; + auto a = hyp * adj; + auto b = hyp.perpendicular(CGAL::POSITIVE) * sqrt(distSq - adj * adj); + auto v1 = (a - b) / distSq; + auto v2 = (a + b) / distSq; + + Segment t1(c1.center() + v1 * c1r, c2.center() + v1 * c2r); + Segment t2(c1.center() + v2 * c1r, c2.center() + v2 * c2r); + + if (CGAL::orientation(t1.source(), t1.target(), c2.center()) == CGAL::COUNTERCLOCKWISE) { + return {t1.source(), t1.target()}; + } else { + return {t2.source(), t2.target()}; + } +} + +std::pair +tangentPoints(const RationalRadiusCircle& c1, const RationalRadiusCircle& c2) { + Number distSq = CGAL::squared_distance(c1.center, c2.center); + Vector hyp = c2.center - c1.center; + Number c1r = c1.radius; + Number c2r = c2.radius; + Number adj = c1r - c2r; + Vector a = hyp * adj; + Vector bV = hyp.perpendicular(CGAL::POSITIVE); + Number bSSqr = distSq - adj * adj; + CSTraits::CoordNT bx(0, bV.x(), bSSqr); + CSTraits::CoordNT by(0, bV.y(), bSSqr); + + CSTraits::CoordNT v1x = (a.x() - bx) / distSq; + CSTraits::CoordNT v1y = (a.y() - by) / distSq; + CSTraits::CoordNT v2x = (a.x() + bx) / distSq; + CSTraits::CoordNT v2y = (a.y() + by) / distSq; + + CSTraits::CoordNT t1sx = c1.center.x() + v1x * c1r; + CSTraits::CoordNT t1sy = c1.center.y() + v1y * c1r; + CSTraits::Point_2 t1s(t1sx, t1sy); + CSTraits::CoordNT t1tx = c2.center.x() + v1x * c2r; + CSTraits::CoordNT t1ty = c2.center.y() + v1y * c2r; + CSTraits::Point_2 t1t(t1tx, t1ty); + + CSTraits::CoordNT t2sx = c1.center.x() + v2x * c1r; + CSTraits::CoordNT t2sy = c1.center.y() + v2y * c1r; + CSTraits::Point_2 t2s(t2sx, t2sy); + CSTraits::CoordNT t2tx = c2.center.x() + v2x * c2r; + CSTraits::CoordNT t2ty = c2.center.y() + v2y * c2r; + CSTraits::Point_2 t2t(t2tx, t2ty); + CSTraits::Point_2 c2c(c2.center.x(), c2.center.y()); + + return {t1s, t1t}; +} + +RationalRadiusCircle approximateRadiusCircle(const Circle& circle) { + Number r = sqrt(CGAL::to_double(circle.squared_radius())); + Number rExact = r; + return {circle.center(), rExact}; +} + +// Adapted from https://github.com/CGAL/cgal/blob/38871d9b125c5513ff8d14f9562795aa12681b38/Minkowski_sum_2/include/CGAL/Minkowski_sum_2/Approx_offset_base_2.h +// This function falls under the following license: +//// Copyright (c) 2006-2008 Tel-Aviv University (Israel). +//// All rights reserved. +//// +//// This function is part of CGAL (www.cgal.org). +//// +//// $URL$ +//// $Id$ +//// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +//// +//// Author(s) : Ron Wein +//// Andreas Fabri +//// Laurent Rineau +//// Efi Fogel +std::variant, std::pair, Segment>> +algebraicCircleTangentToRationalSegments(const CSTraits::Point_2& p1, const CSTraits::Point_2& p2, + const RationalRadiusCircle& c1, const RationalRadiusCircle& c2) { + const auto& x1 = p1.x(); + const auto& y1 = p1.y(); + const auto& x2 = p2.x(); + const auto& y2 = p2.y(); + + auto delta_x = x2 - x1; + auto delta_y = y2 - y1; + auto sqr_d = CGAL::square(delta_x) + CGAL::square(delta_y); + // This is hacky + Number app_delta_x = CGAL::to_double(x2-x1); + Number app_delta_y = CGAL::to_double(y2-y1); + Number app_d = sqrt(CGAL::to_double(sqr_d)); + + auto d_app_err = sqr_d - CGAL::square(app_d); + auto dx_app_err = app_delta_x - delta_x; + auto dy_app_err = app_delta_y - delta_y; + auto sign_d_app_err = CGAL::sign(d_app_err); + auto sign_dx_app_err = CGAL::sign(dx_app_err); + auto sign_dy_app_err = CGAL::sign(dy_app_err); + + if (sign_d_app_err == CGAL::ZERO && sign_dx_app_err == CGAL::ZERO && sign_dy_app_err == CGAL::ZERO) { + auto tp1 = Point (c1.center.x() + c1.radius * app_delta_y / app_d, c1.center.y() + c1.radius * (-app_delta_x) / app_d); + auto tp2 = Point (c2.center.x() + c2.radius * app_delta_y / app_d, c2.center.y() + c2.radius * (-app_delta_x) / app_d); + + auto seg1 = Segment (tp1, tp2); + return seg1; + } else { + // This is hacky + if (CGAL::sign(app_delta_x) == CGAL::ZERO) { + app_delta_x += M_EPSILON; + } + if (CGAL::sign(app_delta_y) == CGAL::ZERO) { + app_delta_y += M_EPSILON; + } + + bool rotate_pi2 = false; + if (CGAL::compare (CGAL::abs(delta_x), + CGAL::abs(delta_y)) == CGAL::SMALLER) + { + rotate_pi2 = true; + + // Swap the delta_x and delta_y values. + auto tmp_app = app_delta_x; + app_delta_x = -app_delta_y; + app_delta_y = tmp_app; + } + + auto lower_tan_half_phi = (app_d - app_delta_y) / (-app_delta_x); + auto upper_tan_half_phi = (-app_delta_x) / (app_d + app_delta_y); + if (upper_tan_half_phi < lower_tan_half_phi) { + auto temp = lower_tan_half_phi; + lower_tan_half_phi = upper_tan_half_phi; + upper_tan_half_phi = temp; + } + + // This is hacky + upper_tan_half_phi += M_EPSILON; + + auto sqr_tan_half_phi = CGAL::square (lower_tan_half_phi); + auto sin_phi = 2 * lower_tan_half_phi / (1 + sqr_tan_half_phi); + auto cos_phi = (1 - sqr_tan_half_phi) / (1 + sqr_tan_half_phi); + + Point tp1; + if (! rotate_pi2) + { + tp1 = Point (c1.center.x() + c1.radius*cos_phi, c1.center.y() + c1.radius*sin_phi); + } + else + { + tp1 = Point (c1.center.x() + c1.radius*sin_phi, c1.center.y() - c1.radius*cos_phi); + } + + sqr_tan_half_phi = CGAL::square (upper_tan_half_phi); + sin_phi = 2 * upper_tan_half_phi / (1 + sqr_tan_half_phi); + cos_phi = (1 - sqr_tan_half_phi) / (1 + sqr_tan_half_phi); + + Point tp2; + if (! rotate_pi2) + { + tp2 = Point (c2.center.x() + c2.radius*cos_phi, c2.center.y() + c2.radius*sin_phi); + } + else + { + tp2 = Point (c2.center.x() + c2.radius*sin_phi, c2.center.y() - c2.radius*cos_phi); + } + + auto l1 = Line(c1.center, tp1).perpendicular(tp1); + auto l2 = Line(c2.center, tp2).perpendicular(tp2); + + // Intersect the two lines. The intersection point serves as a common + // end point for the two line segments we are about to introduce. + auto obj = CGAL::intersection(l1, l2); + + Point mid_p; + auto assign_success = CGAL::assign (mid_p, obj); + assert(assign_success); + + auto seg1 = Segment (tp1, mid_p); + auto seg2 = Segment (mid_p, tp2); + + return std::pair(seg1, seg2); + } +} + +std::variant, std::pair, Segment>> +approximateTangent(const RationalRadiusCircle& c1, const RationalRadiusCircle& c2) { + auto [source, target] = tangentPoints(c1, c2); + std::cout << "Tangent points" << std::endl; + std::cout << "source: " << approximateAlgebraic(source) << " target: " << approximateAlgebraic(target) << std::endl; + return algebraicCircleTangentToRationalSegments(source, target, c1, c2); +} + +std::vector circlesOnConvexHull(const std::vector& circles) { + if (circles.size() == 1) { + return {circles.front()}; + } + + Apollonius apo; + std::unordered_map vToCircle; + for (const auto& c : circles) { + auto vh = apo.insert(ASite(c.center, c.radius)); + vToCircle[vh] = c; + } + auto circ = apo.incident_vertices(apo.infinite_vertex()); + auto curr = circ; + std::vector hullCircles; + do { + hullCircles.push_back(vToCircle[curr]); + } while (++curr != circ); + + std::reverse(hullCircles.begin(), hullCircles.end()); + + return hullCircles; +} + +CSPolygon approximateConvexHull(const std::vector>& circles) { + if (circles.size() == 1) { + return circleToCSPolygon(circles.front()); + } + std::vector rrCircles; + for (const auto& c : circles) { + rrCircles.push_back(approximateRadiusCircle(c)); + } + auto hullCircles = circlesOnConvexHull(rrCircles); + for (const auto& hc : hullCircles) { + std::cout << hc.center << std::endl; + } + + std::vector>> tangents; + + for (int i = 0; i < hullCircles.size(); ++i) { + auto& c1 = hullCircles[i]; + auto& c2 = hullCircles[(i + 1) % hullCircles.size()]; + std::cout << c1.center << " tangent to -> " << c2.center << std::endl; + auto segOrPair = approximateTangent(c1, c2); + std::vector> segs; + if (segOrPair.index() == 0) { + segs.push_back(std::get>(segOrPair)); + } else { + auto pair = std::get, Segment>>(segOrPair); + segs.push_back(pair.first); + segs.push_back(pair.second); + } + tangents.push_back(segs); + + for (const auto& seg : segs) { + std::cout << seg.source() << " -> " << seg.target() << std::endl; + } + } + + std::vector xm_curves; + for (int i = 0; i < hullCircles.size(); ++i) { + auto& c1 = hullCircles[i]; + auto& c2 = hullCircles[(i + 1) % hullCircles.size()]; + auto& t1 = tangents[i]; + auto& t2 = tangents[(i + 1) % tangents.size()]; + for (const auto& piece : t1) { + Curve_2 curve(piece); + curveToXMonotoneCurves(curve, std::back_inserter(xm_curves)); + } + OneRootPoint t1End(t1.back().target().x(), t1.back().target().y()); + OneRootPoint t2Start(t2.front().source().x(), t2.front().source().y()); + Curve_2 arc(Circle(c2.center, c2.radius * c2.radius), t1End, t2Start); + curveToXMonotoneCurves(arc, std::back_inserter(xm_curves)); + } + + return {xm_curves.begin(), xm_curves.end()}; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/approximate_convex_hull.h b/cartocrow/simplesets/helpers/approximate_convex_hull.h new file mode 100644 index 00000000..518da640 --- /dev/null +++ b/cartocrow/simplesets/helpers/approximate_convex_hull.h @@ -0,0 +1,38 @@ +#ifndef CARTOCROW_APPROXIMATE_CONVEX_HULL_H +#define CARTOCROW_APPROXIMATE_CONVEX_HULL_H + +#include "../types.h" +#include +#include +#include +#include + +namespace cartocrow::simplesets { +typedef CGAL::Apollonius_graph_traits_2 AT; +typedef CGAL::Apollonius_graph_2 Apollonius; +typedef Apollonius::Site_2 ASite; + + +struct RationalRadiusCircle { + Point center; + Number radius; +}; + +Segment tangent(const Circle& c1, const Circle& c2); + +std::pair, Point> +tangentPoints(const Circle& c1, const Circle& c2); + +std::pair +tangentPoints(const RationalRadiusCircle& c1, const RationalRadiusCircle& c2); + +CSTraits::Point_2 closestOnCircle(const Circle& circle, const Point& point); + +std::variant, std::pair, Segment>> +algebraicCircleTangentToRationalSegments(const CSTraits::Point_2& p1, const CSTraits::Point_2& p2, + const Circle& c1, const Circle& c2); + +CSPolygon approximateConvexHull(const std::vector>& circles); +} + +#endif //CARTOCROW_APPROXIMATE_CONVEX_HULL_H diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp new file mode 100644 index 00000000..43786338 --- /dev/null +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp @@ -0,0 +1,137 @@ +#include "cs_curve_helpers.h" + +namespace cartocrow::simplesets { +OneRootPoint closestOnCircle(const Circle& circle, const Point& point) { + auto bb = circle.bbox(); + Rectangle bbX(bb.xmin() - 1, bb.ymin() - 1, bb.xmax() + 1, bb.ymax() + 1); + Ray ray(circle.center(), point); + auto inter = CGAL::intersection(bbX, ray); + auto seg = get>(*inter); + + using Arr = CGAL::Arrangement_2; + Arr arr; + CGAL::insert(arr, circle); + CGAL::insert(arr, seg); + + std::optional intersectionPoint; + for (auto vit = arr.vertices_begin(); vit != arr.vertices_end(); ++vit) { + if (vit->degree() == 4) { + intersectionPoint = vit->point(); + } + } + + if (!intersectionPoint.has_value()) { + std::stringstream ss; + ss << "Could not project point " << point << " on circle " << circle; + throw std::runtime_error(ss.str()); + } + + return *intersectionPoint; +} + +OneRootPoint nearest(const X_monotone_curve_2& xm_curve, const Point& point) { + if (xm_curve.is_linear()) { + auto l = xm_curve.supporting_line(); + auto k = l.perpendicular(point); + auto inter = get>((*CGAL::intersection(l, k))); + + if (xm_curve.is_vertical()) { + auto minY = std::min(xm_curve.left().y(), xm_curve.right().y()); + auto maxY = std::max(xm_curve.left().y(), xm_curve.right().y()); + auto x = xm_curve.left().x(); + if (inter.y() >= maxY) { + return {x, maxY}; + } else if (inter.y() <= minY) { + return {x, minY}; + } else { + return {inter.x(), inter.y()}; + } + } + + if (inter.x() <= xm_curve.left().x()) { + return xm_curve.left(); + } else if (inter.x() >= xm_curve.right().x()) { + return xm_curve.right(); + } else { + return {inter.x(), inter.y()}; + } + } else { + auto c = xm_curve.supporting_circle(); + xm_curve.point_position({point.x(), point.y()}); + auto inter = closestOnCircle(c, point); + if (inter.x() <= xm_curve.left().x()) { + return xm_curve.left(); + } else if (inter.x() >= xm_curve.right().x()) { + return xm_curve.right(); + } else { + if (liesOn(inter, xm_curve)) { + return inter; + } + OneRootPoint opposite(2 * c.center().x() - inter.x(), 2 * c.center().y() - inter.y()); + if (liesOn(opposite, xm_curve)) { + return opposite; + } + auto sdLeft = CGAL::square(point.x() - xm_curve.left().x()) + CGAL::square(point.y() - xm_curve.left().y()); + auto sdRight = CGAL::square(point.x() - xm_curve.right().x()) + CGAL::square(point.y() - xm_curve.right().y()); + if (sdLeft < sdRight) { + return xm_curve.left(); + } else { + return xm_curve.right(); + } + } + } +} + +bool liesOn(const Point& p, const X_monotone_curve_2& xm_curve) { + if (p.x() < xm_curve.left().x() || p.x() > xm_curve.right().x()) + return false; + + if (xm_curve.is_linear()) { + return xm_curve.supporting_line().has_on(p); + } else { + return xm_curve.point_position({p.x(), p.y()}) == CGAL::EQUAL; + } +} + +bool liesOn(const OneRootPoint& p, const X_monotone_curve_2& xm_curve) { + if (p.x() < xm_curve.left().x() || p.x() > xm_curve.right().x()) + return false; +// CSTraits traits; +// auto lies_on = traits.compare_y_at_x_2_object(); + return xm_curve.point_position(p) == CGAL::EQUAL; +} + +renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve) { + renderer::RenderPath path; + path.moveTo(approximateAlgebraic(xm_curve.source())); + + if (xm_curve.is_circular()) { + auto circle = xm_curve.supporting_circle(); + path.arcTo(approximate(circle.center()), xm_curve.orientation() == CGAL::CLOCKWISE, + approximateAlgebraic(xm_curve.target())); + } else { + auto line = xm_curve.supporting_line(); + path.lineTo(approximateAlgebraic(xm_curve.target())); + } + + return path; +} + +void addCurveToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first) { + auto as = approximateAlgebraic(xm_curve.source()); + auto at = approximateAlgebraic(xm_curve.target()); + if (first) { + path.moveTo(as); + first = false; + } + if (xm_curve.is_linear()) { + path.lineTo(at); + } else if (xm_curve.is_circular()){ + if (CGAL::squared_distance(as, at) < M_EPSILON) { + return; + } + auto circle = xm_curve.supporting_circle(); + path.arcTo(approximate(circle.center()), xm_curve.orientation() == CGAL::CLOCKWISE, approximateAlgebraic(xm_curve.target())); + } +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.h b/cartocrow/simplesets/helpers/cs_curve_helpers.h new file mode 100644 index 00000000..48214b6a --- /dev/null +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.h @@ -0,0 +1,42 @@ +#include "../types.h" +#include "cartocrow/renderer/render_path.h" + +#ifndef CARTOCROW_CS_CURVE_HELPERS_H +#define CARTOCROW_CS_CURVE_HELPERS_H + +namespace cartocrow::simplesets { +template +void curveToXMonotoneCurves(const Curve_2& curve, OutputIterator out) { + CSTraits traits; + auto make_x_monotone = traits.make_x_monotone_2_object(); + std::vector> curves_and_points; + make_x_monotone(curve, std::back_inserter(curves_and_points)); + + // There should not be any isolated points + for (auto kinda_curve : curves_and_points) { + if (kinda_curve.which() == 1) { + *out++ = boost::get(kinda_curve); + } else { + std::cout << "Converting curve into x-monotone curves results in isolated point." + << std::endl; + throw std::runtime_error("Cannot convert a degenerate curve into x-monotone curves."); + } + } +} + +template +void curvesToXMonotoneCurves(InputIterator begin, InputIterator end, OutputIterator out) { + for (auto it = begin; it != end; ++it) { + curveToXMonotoneCurves(*it, out); + } +} + +OneRootPoint nearest(const X_monotone_curve_2& xm_curve, const Point& point); + +bool liesOn(const Point& p, const X_monotone_curve_2& xm_curve); +bool liesOn(const OneRootPoint& p, const X_monotone_curve_2& xm_curve); +renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve); +void addCurveToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first); +} + +#endif //CARTOCROW_CS_CURVE_HELPERS_H diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp index 1f1a0691..227b0c21 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp @@ -1,4 +1,5 @@ #include "cs_polygon_helpers.h" +#include "cs_curve_helpers.h" // Code adapted from a Stack Overflow answer by HEKTO. // Link: https://stackoverflow.com/questions/69399922/how-does-one-obtain-the-area-of-a-general-polygon-set-in-cgal @@ -48,7 +49,7 @@ Number area(const X_monotone_curve_2& XCV) { } // ------ return area of the simple polygon -Number area(const CSPolygon P) { +Number area(const CSPolygon& P) { Number res = 0; for (auto it = P.curves_begin(); it != P.curves_end(); ++it) { res += area(*it); @@ -64,4 +65,114 @@ Number area(const CSPolygonWithHoles& P) { } return res; } + +CSPolygon circleToCSPolygon(const Circle& circle) { + std::vector xm_curves; + curveToXMonotoneCurves(circle, std::back_inserter(xm_curves)); + return {xm_curves.begin(), xm_curves.end()}; +} + +std::optional liesOn(const Point& p, const CSPolygon& polygon) { + for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { + if (liesOn(p, *cit)) { + return cit; + } + } + return std::nullopt; +} + +std::optional liesOn(const OneRootPoint& p, const CSPolygon& polygon) { + for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { + if (liesOn(p, *cit)) { + return cit; + } + } + return std::nullopt; +} + +renderer::RenderPath renderPathFromCSPolygon(const CSPolygon& polygon) { + renderer::RenderPath path; + bool first = true; + for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { + addCurveToRenderPath(*cit, path, first); + } + path.close(); + return path; +} + +bool on_or_inside(const CSPolygon& polygon, const Point& point) { + Ray ray(point, Vector(1, 0)); + + Rectangle bbox = polygon.bbox(); + Rectangle rect({bbox.xmin() - 1, bbox.ymin() - 1}, {bbox.xmax() + 1, bbox.ymax() + 1}); + + auto inter = CGAL::intersection(ray, rect); + if (!inter.has_value()) return false; + auto seg = boost::get>(*inter); + X_monotone_curve_2 seg_xm_curve(seg.source(), seg.target()); + + typedef std::pair Intersection_point; + typedef boost::variant Intersection_result; + std::vector intersection_results; + + for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { + const auto& curve = *cit; + curve.intersect(seg_xm_curve, std::back_inserter(intersection_results)); + } + + return intersection_results.size() % 2 == 1; +} + +bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon) { + auto sc = liesOn(c.source(), polygon); + auto tc = liesOn(c.target(), polygon); + if (!sc.has_value() || !tc.has_value()) { + return false; + } + do { + auto next = *sc; + ++next; + if (next == polygon.curves_end()) { + next = polygon.curves_begin(); + } + if (liesOn(c.source(), *next)) { + sc = next; + } else { + break; + } + } while (true); + do { + auto next = *tc; + ++next; + if (next == polygon.curves_end()) { + next = polygon.curves_begin(); + } + if (liesOn(c.source(), *next)) { + tc = next; + } else { + break; + } + } while (true); + auto sit = *sc; + auto tit = *sc; + auto curr = sit; + do { +// std::cout << c.is_linear() << " " << c.is_circular() << " " << c.is_vertical() << std::endl; +// + std::cout << curr->source() << " -> " << curr->target() << std::endl; + if (curr->is_linear()) { + if (c.is_circular()) return false; + std::cout << "Supporting line" << std::endl; + std::cout << curr->supporting_line() << std::endl; + std::cout << "c" << std::endl; + std::cout << c.supporting_line() << " " << c.source() << " " << c.target() << std::endl; + if (curr->supporting_line() != c.supporting_line()) return false; + } else { + if (c.is_linear()) return false; + if (curr->supporting_circle() != c.supporting_circle()) return false; + } + } while (curr++ != tit); + + return true; +} } \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.h b/cartocrow/simplesets/helpers/cs_polygon_helpers.h index 4a2e85d5..179cda7b 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.h +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.h @@ -2,8 +2,7 @@ #define CARTOCROW_CS_POLYGON_HELPERS_H #include "../types.h" - -// Thanks to: https://stackoverflow.com/questions/69399922/how-does-one-obtain-the-area-of-a-general-polygon-set-in-cgal +#include "../../renderer/render_path.h" namespace cartocrow::simplesets { //For two circles of radii R and r and centered at (0,0) and (d,0) intersecting @@ -20,10 +19,18 @@ Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2, c Number area(const X_monotone_curve_2& XCV); // ------ return area of the simple polygon -Number area(const CSPolygon P); +Number area(const CSPolygon& P); // ------ return area of the polygon with (optional) holes Number area(const CSPolygonWithHoles& P); + +CSPolygon circleToCSPolygon(const Circle& circle); + +std::optional liesOn(const Point& p, const CSPolygon& polygon); +std::optional liesOn(const OneRootPoint& p, const CSPolygon& polygon); +bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon); +renderer::RenderPath renderPathFromCSPolygon(const CSPolygon& polygon); +bool on_or_inside(const CSPolygon& polygon, const Point& point); } #endif //CARTOCROW_CS_POLYGON_HELPERS_H diff --git a/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp new file mode 100644 index 00000000..993b636a --- /dev/null +++ b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp @@ -0,0 +1,73 @@ +#include "cs_polyline_helpers.h" +#include "cs_curve_helpers.h" + +namespace cartocrow::simplesets { +OneRootPoint nearest(const CSPolyline& polyline, const Point& point) { + std::optional minSqrdDist; + std::optional closest; + for (auto cit = polyline.curves_begin(); cit != polyline.curves_end(); ++cit) { + const auto& curve = *cit; + auto n = nearest(curve, point); + auto sqrdDist = CGAL::square(n.x() - point.x()) + CGAL::square(n.y() - point.y()); + if (!minSqrdDist.has_value() || sqrdDist < *minSqrdDist) { + minSqrdDist = sqrdDist; + closest = n; + } + } + + if (!closest.has_value()) { + throw std::runtime_error("Cannot find closest point to empty polyline."); + } + + return *closest; +} + +std::optional liesOn(const Point& p, const CSPolyline& polyline) { + for (auto cit = polyline.curves_begin(); cit != polyline.curves_end(); ++cit) { + if (liesOn(p, *cit)) { + return cit; + } + } + return std::nullopt; +} + +std::optional liesOn(const OneRootPoint& p, const CSPolyline& polyline) { + for (auto cit = polyline.curves_begin(); cit != polyline.curves_end(); ++cit) { + if (liesOn(p, *cit)) { + return cit; + } + } + return std::nullopt; +} + +renderer::RenderPath renderPathFromCSPolyline(const CSPolyline& polyline) { + renderer::RenderPath path; + bool first = true; + for (auto cit = polyline.curves_begin(); cit != polyline.curves_end(); ++cit) { + addCurveToRenderPath(*cit, path, first); + } + return path; +} + +bool liesOn(const X_monotone_curve_2& c, const CSPolyline& polyline) { + auto sc = liesOn(c.source(), polyline); + auto tc = liesOn(c.target(), polyline); + if (!sc.has_value() || !tc.has_value()) { + return false; + } + auto sit = *sc; + auto tit = *sc; + auto curr = sit; + do { + if (curr->is_linear()) { + if (c.is_circular()) return false; + if (curr->supporting_line() != c.supporting_line()) return false; + } else { + if (c.is_circular()) return false; + if (curr->supporting_circle() != c.supporting_circle()) return false; + } + } while (curr++ != tit); + + return true; +} +} \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_polyline_helpers.h b/cartocrow/simplesets/helpers/cs_polyline_helpers.h new file mode 100644 index 00000000..4c3fcc98 --- /dev/null +++ b/cartocrow/simplesets/helpers/cs_polyline_helpers.h @@ -0,0 +1,15 @@ +#ifndef CARTOCROW_CS_POLYLINE_HELPERS_H +#define CARTOCROW_CS_POLYLINE_HELPERS_H + +#include "../types.h" +#include "cartocrow/renderer/render_path.h" + +namespace cartocrow::simplesets { +OneRootPoint nearest(const CSPolyline& polyline, const Point& point); +std::optional liesOn(const Point& p, const CSPolyline& polyline); +std::optional liesOn(const OneRootPoint& p, const CSPolyline& polyline); +bool liesOn(const X_monotone_curve_2& c, const CSPolyline& polyline); +renderer::RenderPath renderPathFromCSPolyline(const CSPolyline& polyline); +} + +#endif //CARTOCROW_CS_POLYLINE_HELPERS_H diff --git a/cartocrow/simplesets/poly_line_gon_intersection.cpp b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp similarity index 71% rename from cartocrow/simplesets/poly_line_gon_intersection.cpp rename to cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp index c2eab50c..3bc7a4fa 100644 --- a/cartocrow/simplesets/poly_line_gon_intersection.cpp +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp @@ -4,6 +4,7 @@ namespace cartocrow::simplesets { std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline line) { assert(!gon.is_empty()); + using Arr = CGAL::Arrangement_2>; Arr arr; CGAL::insert_non_intersecting_curves(arr, line.curves_begin(), line.curves_end()); for (auto eit = arr.edges_begin(); eit != arr.edges_end(); ++eit) { @@ -24,6 +25,15 @@ std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline lin } } + // todo: fix. Use Arr_polycurve_traits_2 class. + // 1. line_edges is incorrect because the insertion of polygon edges changes the edge data. + // use arrangement with history and polycurve triats. + // 2. below I don't check whether a line_edge actually lies inside an interior face of a polygon -_-. + // check whether the outer_ccb of one of the adjacent faces of the edge originates from the outer boundary of the + // polygon of from a hole. + // 3. make it work for CSPolygonWithHoles + + std::vector parts; while (!line_edges.empty()) { // Find first edge on connected component of polyline (in the intersection with polygon) diff --git a/cartocrow/simplesets/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h similarity index 73% rename from cartocrow/simplesets/poly_line_gon_intersection.h rename to cartocrow/simplesets/helpers/poly_line_gon_intersection.h index b16afd12..66f795dd 100644 --- a/cartocrow/simplesets/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -1,7 +1,7 @@ #ifndef CARTOCROW_POLY_LINE_GON_INTERSECTION_H #define CARTOCROW_POLY_LINE_GON_INTERSECTION_H -#include "types.h" +#include "cartocrow/simplesets/types.h" namespace cartocrow::simplesets { // todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. @@ -9,8 +9,6 @@ struct HalfEdgePolylineData { bool of_polyline = false; }; -using Arr = CGAL::Arrangement_2>; - std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline line); } diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp index 32ee34e4..e979f78f 100644 --- a/cartocrow/simplesets/partition_algorithm.cpp +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -8,10 +8,6 @@ namespace cartocrow::simplesets { -Number squared(Number x) { - return x * x; -} - std::variant to_bank_or_island(PolyPattern* polyPattern) { if (auto bp = dynamic_cast(polyPattern)) { return *bp; diff --git a/cartocrow/simplesets/types.cpp b/cartocrow/simplesets/types.cpp index 52d93bc4..1de371f3 100644 --- a/cartocrow/simplesets/types.cpp +++ b/cartocrow/simplesets/types.cpp @@ -12,6 +12,10 @@ Point makeExact(const Point& point) { return {point.x(), point.y()}; } +Circle makeExact(const Circle& circle) { + return {makeExact(circle.center()), circle.squared_radius()}; +} + Polygon makeExact(const Polygon& polygon) { std::vector> exact_points; std::transform(polygon.vertices_begin(), polygon.vertices_end(), std::back_inserter(exact_points), @@ -22,27 +26,4 @@ Polygon makeExact(const Polygon& polygon) { Point approximateAlgebraic(const CSTraits::Point_2& algebraic_point) { return {CGAL::to_double(algebraic_point.x()), CGAL::to_double(algebraic_point.y())}; } - -CSPolygon circleToCSPolygon(const Circle& circle) { - CSTraits traits; - auto make_x_monotone = traits.make_x_monotone_2_object(); - std::vector> curves_and_points; - make_x_monotone(circle, std::back_inserter(curves_and_points)); - std::vector curves; - - // There should not be any isolated points - for (auto kinda_curve : curves_and_points) { - if (kinda_curve.which() == 1) { - auto curve = boost::get(kinda_curve); - curves.push_back(curve); - } else { - std::cout << "Splitting circle into x-monotone curves results in isolated point." << std::endl; - std::cout << circle.center() << " squared radius: " << circle.squared_radius() << std::endl; - throw std::runtime_error("Cannot convert circle of radius 0 into a polygon"); - } - } - - // todo: is order of curves always correct? Because of weird error with intersection delay. - return CSPolygon{curves.begin(), curves.end()}; -} } diff --git a/cartocrow/simplesets/types.h b/cartocrow/simplesets/types.h index a8cf087d..59af0810 100644 --- a/cartocrow/simplesets/types.h +++ b/cartocrow/simplesets/types.h @@ -17,9 +17,13 @@ typedef CGAL::Arr_circle_segment_traits_2 CSTraits; typedef CGAL::Gps_circle_segment_traits_2 CSTraitsBoolean; typedef CSTraitsBoolean::Polygon_2 CSPolygon; typedef CSTraitsBoolean::Polygon_with_holes_2 CSPolygonWithHoles; +typedef CGAL::General_polygon_set_2 CSPolygonSet; typedef General_polyline_2 CSPolyline; typedef CGAL::Arrangement_2 CSArrangement; typedef CSTraits::X_monotone_curve_2 X_monotone_curve_2; +typedef CSTraits::Curve_2 Curve_2; +typedef CSTraits::CoordNT OneRootNumber; +typedef CSTraits::Point_2 OneRootPoint; //typedef CGAL::CORE_algebraic_number_traits Nt_traits; //typedef CGAL::Cartesian Rat_kernel; //typedef Nt_traits::Algebraic Algebraic; @@ -29,11 +33,15 @@ typedef CSTraits::X_monotone_curve_2 X_monotone_curve_2; //typedef Alg_kernel K; Point makeExact(const Point& point); +Circle makeExact(const Circle& circle); std::vector> makeExact(const std::vector>& points); Polygon makeExact(const Polygon& polygon); Point approximateAlgebraic(const CSTraits::Point_2& algebraic_point); -CSPolygon circleToCSPolygon(const Circle& circle); +template +T squared(T x) { + return x * x; +} } #endif //CARTOCROW_TYPES_H diff --git a/demos/simplesets/CMakeLists.txt b/demos/simplesets/CMakeLists.txt index 86f6d49e..68d7f857 100644 --- a/demos/simplesets/CMakeLists.txt +++ b/demos/simplesets/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES simplesets_demo.cpp colors.cpp + circle_convex_hull.cpp ) add_executable(simplesets_demo ${SOURCES}) diff --git a/demos/simplesets/circle_convex_hull.cpp b/demos/simplesets/circle_convex_hull.cpp new file mode 100644 index 00000000..f19dd18d --- /dev/null +++ b/demos/simplesets/circle_convex_hull.cpp @@ -0,0 +1,46 @@ +#include "circle_convex_hull.h" +#include +#include "cartocrow/simplesets/helpers/approximate_convex_hull.h" +#include "cartocrow/simplesets/helpers/cs_polygon_helpers.h" + +CircleConvexHullDemo::CircleConvexHullDemo() { + setWindowTitle("Convex hull of circles"); + auto renderer = new GeometryWidget(); + renderer->setDrawAxes(false); + setCentralWidget(renderer); + + std::vector> cs({ + {{0, 0}, 2}, + {{10, 4}, 12}, + {{7, -6}, 8}, + {{5, -8}, 1}, + {{3, 3}, 3}, + {{15, -4}, 9}, + {{5, -4}, 8}, + {{0, -1}, 5}, + {{5, -3}, 12}, + {{8, -9}, 16}, + }); + + auto hull = approximateConvexHull(cs); + + std::function drawFunc = [cs, hull](GeometryRenderer& renderer) { + RenderPath path = renderPathFromCSPolygon(hull); + renderer.setMode(GeometryRenderer::stroke); + renderer.setFill(Color{50, 50, 50}); + renderer.draw(path); + renderer.setMode(GeometryRenderer::fill); + renderer.setFill(Color{50, 50, 50}); + for (const auto& c : cs) { + renderer.draw(c); + } + }; + renderer->addPainting(drawFunc, "Disks"); +} + +//int main(int argc, char* argv[]) { +// QApplication app(argc, argv); +// CircleConvexHullDemo demo; +// demo.show(); +// app.exec(); +//} diff --git a/demos/simplesets/circle_convex_hull.h b/demos/simplesets/circle_convex_hull.h new file mode 100644 index 00000000..716e35dc --- /dev/null +++ b/demos/simplesets/circle_convex_hull.h @@ -0,0 +1,25 @@ +#ifndef CARTOCROW_CIRCLE_CONVEX_HULL_H +#define CARTOCROW_CIRCLE_CONVEX_HULL_H + +#include "cartocrow/core/core.h" +#include "cartocrow/core/ipe_reader.h" +#include "cartocrow/renderer/geometry_painting.h" +#include "cartocrow/renderer/geometry_widget.h" +#include "cartocrow/simplesets/types.h" +#include "cartocrow/simplesets/partition.h" +#include "cartocrow/simplesets/drawing_algorithm.h" +#include + +using namespace cartocrow; +using namespace cartocrow::renderer; +using namespace cartocrow::simplesets; + +class CircleConvexHullDemo: public QMainWindow { + Q_OBJECT + + public: + CircleConvexHullDemo(); + +}; + +#endif //CARTOCROW_CIRCLE_CONVEX_HULL_H diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 23262071..d929cdfb 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -20,14 +20,22 @@ along with this program. If not, see . #include "simplesets_demo.h" #include "cartocrow/core/ipe_reader.h" #include "cartocrow/renderer/ipe_renderer.h" +#include "cartocrow/simplesets/dilated/dilated_poly.h" +#include "cartocrow/simplesets/drawing_algorithm.h" +#include "cartocrow/simplesets/helpers/approximate_convex_hull.h" +#include "cartocrow/simplesets/helpers/arrangement_helpers.h" +#include "cartocrow/simplesets/helpers/cs_polygon_helpers.h" +#include "cartocrow/simplesets/helpers/cs_polyline_helpers.h" +#include "cartocrow/simplesets/helpers/poly_line_gon_intersection.h" +#include "cartocrow/simplesets/parse_input.h" +#include "cartocrow/simplesets/partition_algorithm.h" +#include "cartocrow/simplesets/partition_painting.h" #include "cartocrow/simplesets/patterns/bank.h" #include "cartocrow/simplesets/patterns/island.h" #include "cartocrow/simplesets/patterns/matching.h" #include "cartocrow/simplesets/patterns/single_point.h" -#include "cartocrow/simplesets/parse_input.h" -#include "cartocrow/simplesets/partition_algorithm.h" -#include "cartocrow/simplesets/partition_painting.h" -#include "cartocrow/simplesets/drawing_algorithm.h" +#include "colors.h" +#include #include #include #include @@ -38,9 +46,6 @@ along with this program. If not, see . #include #include #include -#include "cartocrow/simplesets/dilated/dilated_poly.h" -#include "colors.h" -#include namespace fs = std::filesystem; using namespace cartocrow; @@ -101,12 +106,192 @@ SimpleSetsDemo::SimpleSetsDemo() { } m_partition = *thePartition; - m_dpd = std::make_shared(m_partition, m_gs, m_cds); - auto ap = std::make_shared(*m_dpd, m_ds); - renderer->addPainting(ap, "Arrangement"); +// m_dpd = std::make_shared(m_partition, m_gs, m_cds); +// auto ap = std::make_shared(*m_dpd, m_ds); +// renderer->addPainting(ap, "Arrangement"); +// +// auto pp = std::make_shared(m_partition, m_gs, m_ds); +// renderer->addPainting(pp, "Partition"); + + renderer->addPainting([this](GeometryRenderer& r) { + CSPolygon disk = circleToCSPolygon({{0, 0}, 1}); + std::vector xm_curves_pl({ + {{0, -2}, {0, 2}} + }); + CSPolyline polyline(xm_curves_pl.begin(), xm_curves_pl.end()); + + r.setMode(GeometryRenderer::stroke); + r.setStroke(Color{0, 0, 0}, 3); + r.draw(renderPathFromCSPolygon(disk)); +// r.draw(renderPathFromCSPolyline(polyline)); + + auto inters = poly_line_gon_intersection(disk, polyline); + r.setStroke(CB::blue, 3); + for (const auto& inter : inters) { + r.draw(renderPathFromCSPolyline(inter)); + } + }, "Poly-gon-line intersection"); + +// auto one = m_dpd->m_dilated[0]; +// auto other = m_dpd->m_dilated[1]; +// auto comp = m_dpd->intersectionComponents(0, 1)[0]; +// auto includeExclude = m_dpd->includeExcludeDisks(0, 1, comp); + + m_cc = std::make_shared>(2, -5); + renderer->registerEditable(m_cc); + +// renderer->addPainting([this](GeometryRenderer& r) { +// CSPolygonSet set; +// CSPolygon disk1 = circleToCSPolygon({{0, 0}, 1}); +// set.join(disk1); +// CSPolygon disk2 = circleToCSPolygon({{0, 2.6}, 0.5}); +// set.difference(disk2); +// std::vector points({{0, {-2, -2}}, {0, {2, -2}}, {0, {2, 2}}, {0, {-2, 2}}}); +// Island island(points); +// Dilated dilated(island, 1.0); +// set.complement(); +// set.intersection(dilated.m_contour); +// std::vector polys; +// set.polygons_with_holes(std::back_inserter(polys)); +// r.draw(renderPathFromCSPolygon(polys[0].outer_boundary())); +// r.draw(renderPathFromCSPolygon(polys[0].holes_begin().operator*())); +// }, "Polygon set bug?"); + +// renderer->addPainting([this, includeExclude, comp](GeometryRenderer& r) { +// r.setMode(GeometryRenderer::fill); +//// for (const auto& exclude : includeExclude.exclude) { +//// r.setFill(CB::red); +//// r.draw(exclude); +//// } +//// for (const auto& include : includeExclude.include) { +//// r.setFill(CB::green); +//// r.draw(include); +//// } +//// auto hull = approximateConvexHull(includeExclude.include); +//// auto path = renderPathFromCSPolygon(hull); +//// r.draw(path); +// +// +// +// auto inclDisks = includeExclude.include; +// auto exclDisks = includeExclude.exclude; +// auto componentShape = ccb_to_polygon(comp.outer_ccb()); +// auto boundaryPart = boundaryParts(comp, 0)[0]; +// +// if (exclDisks.empty()) return boundaryPart; +// +// std::vector> lineCovering; +// std::vector> arcCovering; +// +// for (const auto& d : exclDisks) { +// auto inter = poly_line_gon_intersection(circleToCSPolygon(d), boundaryPart); +// bool coversLine = true; +// for (const auto& p : inter) { +// if (!isStraight(p)) { +// coversLine = false; +// break; +// } +// } +// if (coversLine) { +// lineCovering.push_back(d); +// } else { +// arcCovering.push_back(d); +// } +// } +// +// auto dr = m_gs.dilationRadius(); +// // Smoothing radius +// auto sr = dr / 5; +// +// std::vector> expandedLineCoveringDisks; +// for (const auto& d : lineCovering) { +// expandedLineCoveringDisks.emplace_back(approximate(d.center()), squared(sqrt(CGAL::to_double(d.squared_radius())) + sr)); +// } +// auto diskComponents = connectedDisks(expandedLineCoveringDisks); +// +// std::vector cuts; +// for (const auto& comp : diskComponents) { +// std::vector> disks; +// for (const auto& [i, _] : comp) { +// disks.push_back(lineCovering[i]); +// } +// cuts.push_back(approximateConvexHull(disks)); +// } +// for (const auto& d : arcCovering) { +// cuts.push_back(circleToCSPolygon(d)); +// } +// +// CSPolygonSet polygonSet; +// for (const auto& cut : cuts) { +// polygonSet.join(cut); +// } +// for (const auto& d: inclDisks) { +// polygonSet.difference(circleToCSPolygon(d)); +// } +// for (const auto& d: exclDisks) { +// if (on_or_inside(componentShape, d.center())) { +// auto n = nearest(boundaryPart, d.center()); +// auto rect = thinRectangle(d.center(), n, m_gs.pointSize / 5); +// polygonSet.join(rect); +// } +// } +// polygonSet.complement(); +//// polygonSet.intersection(componentShape); +// +// std::vector polys; +// polygonSet.polygons_with_holes(std::back_inserter(polys)); +// // CGAL::difference(componentShape, ) +// assert(polys.size() == 1); +// auto poly = polys[0]; +// // std +//// assert(!poly.has_holes()); +// std::cout << "#holes: " << poly.number_of_holes() << std::endl; +// +// auto outer = *poly.holes_begin(); +// for (auto cit = outer.curves_begin(); cit != outer.curves_end(); ++cit) { +// std::cout << cit->source() << " -> " << cit->target() << std::endl; +// if (cit->is_linear()) { +// std::cout << cit->supporting_line() << std::endl; +// } else { +// std::cout << cit->supporting_circle() << std::endl; +// } +// } +// +// +// r.setFill(CB::light_purple); +// r.draw(renderPathFromCSPolygon(outer)); +// +// }, "Include exclude"); - auto pp = std::make_shared(m_partition, m_gs, m_ds); - renderer->addPainting(pp, "Partition"); +// renderer->addPainting([this](GeometryRenderer& r) { +//// X_monotone_curve_2 bot({0, 0}, {5, 0}); +//// X_monotone_curve_2 right({5, 0}, {5, 5}); +//// X_monotone_curve_2 top({5, 5}, {0, 5}); +//// X_monotone_curve_2 left({0, 5}, {0, 0}); +// std::vector points({{0, {0, 0}}, {0, {5, -3}}, {0, {5, 5}}, {0, {2, 5}}}); +// Island island(points); +// Dilated dilated(island, 1.0); +// auto polygon = dilated.m_contour; +//// Polygon polygon(points.begin(), points.end()); +//// auto csPolygon = dil +// +//// std::vector xm_curves({bot, right, top, left}); +//// CSPolygon polygon(xm_curves.begin(), xm_curves.end()); +// r.setMode(GeometryRenderer::fill); +// r.setFill(Color{100, 100, 100}); +// r.draw(renderPathFromCSPolygon(polygon)); +// +// std::vector xm_curves_bp(polygon.curves_begin(), std::next(polygon.curves_begin(), 6)); +// +//// auto xm_curves_bp +// CSPolyline polyline(xm_curves_bp.begin(), xm_curves_bp.end()); +// auto morphed = morph(polyline, polygon, {}, {Circle({m_cc->x(), m_cc->y()}, 1)}, m_gs, m_cds); +// r.setMode(GeometryRenderer::stroke); +// r.setStroke(Color{0, 0, 0}, 3.0); +// r.draw(renderPathFromCSPolyline(morphed)); +// r.draw(approximateAlgebraic(nearest(polyline, {m_cc->x(), m_cc->y()}))); +// r.draw(*m_cc); +// }, "Morph test"); connect(fitToScreenButton, &QPushButton::clicked, [nycPoints, renderer, this]() { std::vector> pts; diff --git a/demos/simplesets/simplesets_demo.h b/demos/simplesets/simplesets_demo.h index f52019e7..d0428b88 100644 --- a/demos/simplesets/simplesets_demo.h +++ b/demos/simplesets/simplesets_demo.h @@ -46,6 +46,8 @@ class SimpleSetsDemo : public QMainWindow { DrawSettings m_ds; PartitionSettings m_ps; ComputeDrawingSettings m_cds; + + std::shared_ptr> m_cc; }; #endif //CARTOCROW_SIMPLESETS_DEMO_H From 28e995d7cd24d149c7aa8a4e7d73fc65d41ad98a Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Fri, 6 Sep 2024 09:33:36 +0200 Subject: [PATCH 10/36] Fix poly_line_gon_intersection & fix duplicates in avoidees. --- cartocrow/simplesets/drawing_algorithm.cpp | 30 +++---- cartocrow/simplesets/drawing_algorithm.h | 2 +- .../simplesets/helpers/cs_curve_helpers.cpp | 13 ++- .../simplesets/helpers/cs_curve_helpers.h | 14 +++- .../simplesets/helpers/cs_polygon_helpers.cpp | 24 +++++- .../simplesets/helpers/cs_polygon_helpers.h | 4 +- .../helpers/cs_polyline_helpers.cpp | 8 +- .../simplesets/helpers/cs_polyline_helpers.h | 4 +- .../helpers/poly_line_gon_intersection.cpp | 81 +++++++++++-------- .../helpers/poly_line_gon_intersection.h | 3 +- cartocrow/simplesets/types.h | 4 + demos/simplesets/circle_convex_hull.cpp | 2 +- demos/simplesets/simplesets_demo.cpp | 30 ++++--- 13 files changed, 144 insertions(+), 75 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 0f62d5e9..d7547008 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -187,16 +187,6 @@ connectedComponents(const DilatedPatternArrangement& arr, std::function faces, std::vector boundary_edges, std::function in_component) : m_faces(std::move(faces)), m_in_component(std::move(in_component)) { while (!boundary_edges.empty()) { @@ -253,7 +243,7 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G for (int i = 0; i < m_dilated.size(); i++) { for (auto cit = m_dilated[i].m_contour.curves_begin(); cit != m_dilated[i].m_contour.curves_end(); ++cit) { auto x_monotone = *cit; - CSTraits::Curve_2 curve = to_curve(x_monotone); + CSTraits::Curve_2 curve = toCurve(x_monotone); curves.emplace_back(curve); curves_data.emplace_back(curve, i); } @@ -331,11 +321,11 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G for (int i = 0; i < m_dilated.size(); ++i) { auto cs = intersectionComponents(i); for (auto& c : cs) { - std::vector avoidees; + std::unordered_set avoidees; for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { for (int j : fit->data().ordering) { if (j == i) break; - avoidees.push_back(j); + avoidees.insert(j); } } if (avoidees.empty()) @@ -695,7 +685,7 @@ bool isStraight(const CSPolyline& polyline) { /// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref js. IncludeExcludeDisks -DilatedPatternDrawing::includeExcludeDisks(int i, const std::vector& js, const Component& c) { +DilatedPatternDrawing::includeExcludeDisks(int i, const std::unordered_set& js, const Component& c) { std::vector> ptsI; const auto& catPointsI = m_dilated[i].catPoints(); std::transform(catPointsI.begin(), catPointsI.end(), std::back_inserter(ptsI), [](const CatPoint& catPoint) { @@ -725,7 +715,7 @@ DilatedPatternDrawing::includeExcludeDisks(int i, const std::vector& js, co /// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref j. IncludeExcludeDisks DilatedPatternDrawing::includeExcludeDisks(int i, int j, const Component& c) { - std::vector js({j}); + std::unordered_set js({j}); return includeExcludeDisks(i, js, c); } @@ -811,9 +801,9 @@ void DilatedPatternDrawing::drawFaceFill(FaceH fh, renderer::GeometryRenderer& r renderer.setFill(ds.colors[m_dilated[i].category()]); if (!d.morphedFace.contains(i)) { auto poly = face_to_polygon(*fh); - renderer.draw(renderPathFromCSPolygon(poly)); + renderer.draw(renderPath(poly)); } else { - renderer.draw(renderPathFromCSPolygon(d.morphedFace[i])); + renderer.draw(renderPath(d.morphedFace[i])); } } } @@ -825,7 +815,6 @@ void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& renderer.setMode(GeometryRenderer::stroke); renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs), true); - // todo: take difference of polylines with the cs polygons stacked on top std::vector polylines; if (d.morphedEdges[i].empty()) { auto bps = boundaryParts(fh, i); @@ -833,6 +822,11 @@ void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& } std::copy(d.morphedEdges[i].begin(), d.morphedEdges[i].end(), std::back_inserter(polylines)); + std::vector modifiedPolylines; + CSPolygonSet polySet; + + // todo: take difference of polylines with the cs polygons stacked on top + // for (const auto& polyline : polylines) { // poly_line_gon_intersection(); // } diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 0e53f2b8..92d51ae8 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -318,7 +318,7 @@ class DilatedPatternDrawing { Relation computePreference(int i, int j, const Component& c); IncludeExcludeDisks includeExcludeDisks(int i, int j, const Component& c); - IncludeExcludeDisks includeExcludeDisks(int i, const std::vector& js, const Component& c); + IncludeExcludeDisks includeExcludeDisks(int i, const std::unordered_set& js, const Component& c); std::vector hyperedges(); diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp index 43786338..65cdf5ec 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp @@ -117,7 +117,7 @@ renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve) { return path; } -void addCurveToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first) { +void addToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first) { auto as = approximateAlgebraic(xm_curve.source()); auto at = approximateAlgebraic(xm_curve.target()); if (first) { @@ -134,4 +134,15 @@ void addCurveToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPa path.arcTo(approximate(circle.center()), xm_curve.orientation() == CGAL::CLOCKWISE, approximateAlgebraic(xm_curve.target())); } } + + +Curve_2 toCurve(const X_monotone_curve_2& xmc) { + if (xmc.is_linear()) { + return {xmc.supporting_line(), xmc.source(), xmc.target()}; + } else if (xmc.is_circular()) { + return {xmc.supporting_circle(), xmc.source(), xmc.target()}; + } else { + throw std::runtime_error("Impossible: circle-segment x-monotone curve is neither linear nor circular."); + } +} } \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.h b/cartocrow/simplesets/helpers/cs_curve_helpers.h index 48214b6a..f3e08fad 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.h +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.h @@ -36,7 +36,19 @@ OneRootPoint nearest(const X_monotone_curve_2& xm_curve, const Point& poi bool liesOn(const Point& p, const X_monotone_curve_2& xm_curve); bool liesOn(const OneRootPoint& p, const X_monotone_curve_2& xm_curve); renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve); -void addCurveToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first); +void addToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first); +Curve_2 toCurve(const X_monotone_curve_2& xmc); + +template +CSPolycurve arrPolycurveFromXMCurves(InputIterator begin, InputIterator end) { + PolyCSTraits traits; + auto construct = traits.construct_curve_2_object(); + std::vector curves; + std::transform(begin, end, std::back_inserter(curves), [](const X_monotone_curve_2& xm_curve) { + return toCurve(xm_curve); + }); + return construct(curves.begin(), curves.end()); +} } #endif //CARTOCROW_CS_CURVE_HELPERS_H diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp index 227b0c21..8c6ca02a 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp @@ -90,16 +90,30 @@ std::optional liesOn(const OneRootPoint& p, con return std::nullopt; } -renderer::RenderPath renderPathFromCSPolygon(const CSPolygon& polygon) { - renderer::RenderPath path; +renderer::RenderPath operator<<(renderer::RenderPath& path, const CSPolygon& polygon) { bool first = true; for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { - addCurveToRenderPath(*cit, path, first); + addToRenderPath(*cit, path, first); } path.close(); return path; } +renderer::RenderPath renderPath(const CSPolygon& polygon) { + renderer::RenderPath path; + path << polygon; + return path; +} + +renderer::RenderPath renderPath(const CSPolygonWithHoles& withHoles) { + renderer::RenderPath path; + path << withHoles.outer_boundary(); + for (auto hit = withHoles.holes_begin(); hit != withHoles.holes_end(); ++hit) { + path << *hit; + } + return path; +} + bool on_or_inside(const CSPolygon& polygon, const Point& point) { Ray ray(point, Vector(1, 0)); @@ -175,4 +189,8 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon) { return true; } + +CSPolycurve arrPolycurveFromCSPolygon(const CSPolygon& polygon) { + return arrPolycurveFromXMCurves(polygon.curves_begin(), polygon.curves_end()); +} } \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.h b/cartocrow/simplesets/helpers/cs_polygon_helpers.h index 179cda7b..190e0cf8 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.h +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.h @@ -29,8 +29,10 @@ CSPolygon circleToCSPolygon(const Circle& circle); std::optional liesOn(const Point& p, const CSPolygon& polygon); std::optional liesOn(const OneRootPoint& p, const CSPolygon& polygon); bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon); -renderer::RenderPath renderPathFromCSPolygon(const CSPolygon& polygon); +renderer::RenderPath renderPath(const CSPolygon& polygon); +renderer::RenderPath renderPath(const CSPolygonWithHoles& withHoles); bool on_or_inside(const CSPolygon& polygon, const Point& point); +CSPolycurve arrPolycurveFromCSPolygon(const CSPolygon& polygon); } #endif //CARTOCROW_CS_POLYGON_HELPERS_H diff --git a/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp index 993b636a..d8914767 100644 --- a/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp @@ -40,11 +40,11 @@ std::optional liesOn(const OneRootPoint& p, co return std::nullopt; } -renderer::RenderPath renderPathFromCSPolyline(const CSPolyline& polyline) { +renderer::RenderPath renderPath(const CSPolyline& polyline) { renderer::RenderPath path; bool first = true; for (auto cit = polyline.curves_begin(); cit != polyline.curves_end(); ++cit) { - addCurveToRenderPath(*cit, path, first); + addToRenderPath(*cit, path, first); } return path; } @@ -70,4 +70,8 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolyline& polyline) { return true; } + +CSPolycurve arrPolycurveFromCSPolyline(const CSPolyline& polyline) { + return arrPolycurveFromXMCurves(polyline.curves_begin(), polyline.curves_end()); +} } \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_polyline_helpers.h b/cartocrow/simplesets/helpers/cs_polyline_helpers.h index 4c3fcc98..09690616 100644 --- a/cartocrow/simplesets/helpers/cs_polyline_helpers.h +++ b/cartocrow/simplesets/helpers/cs_polyline_helpers.h @@ -3,13 +3,15 @@ #include "../types.h" #include "cartocrow/renderer/render_path.h" +#include namespace cartocrow::simplesets { OneRootPoint nearest(const CSPolyline& polyline, const Point& point); std::optional liesOn(const Point& p, const CSPolyline& polyline); std::optional liesOn(const OneRootPoint& p, const CSPolyline& polyline); bool liesOn(const X_monotone_curve_2& c, const CSPolyline& polyline); -renderer::RenderPath renderPathFromCSPolyline(const CSPolyline& polyline); +renderer::RenderPath renderPath(const CSPolyline& polyline); +CSPolycurve arrPolycurveFromCSPolyline(const CSPolyline& polyline); } #endif //CARTOCROW_CS_POLYLINE_HELPERS_H diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp index 3bc7a4fa..bfce48c2 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp @@ -1,43 +1,60 @@ #include "poly_line_gon_intersection.h" +#include "cs_polyline_helpers.h" +#include "cs_polygon_helpers.h" +#include namespace cartocrow::simplesets { -std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline line) { - assert(!gon.is_empty()); +std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line) { + CSPolygonWithHoles withHoles(gon); + return poly_line_gon_intersection(withHoles, line); +} - using Arr = CGAL::Arrangement_2>; +std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line) { + using Arr = CGAL::Arrangement_with_history_2>; Arr arr; - CGAL::insert_non_intersecting_curves(arr, line.curves_begin(), line.curves_end()); - for (auto eit = arr.edges_begin(); eit != arr.edges_end(); ++eit) { - eit->set_data({true}); - eit->twin()->set_data({true}); + auto linePolycurve = arrPolycurveFromCSPolyline(line); + auto outerGonPolycurve = arrPolycurveFromCSPolygon(gon.outer_boundary()); + + auto lch = CGAL::insert(arr, linePolycurve); + auto ogch = CGAL::insert(arr, outerGonPolycurve); + std::vector hgch; + + std::vector holesGonPolycurves; + for (auto hit = gon.holes_begin(); hit != gon.holes_end(); ++hit) { + CGAL::insert(arr, arrPolycurveFromCSPolygon(*hit)); } - CGAL::insert(arr, gon.curves_begin(), gon.curves_end()); - - std::vector line_edges; - for (auto eit = arr.edges_begin(); eit != arr.edges_end(); ++eit) { - if (eit->data().of_polyline) { - if (eit->source()->point() == eit->curve().source()) { - line_edges.push_back(eit.ptr()); - } else { - assert(eit->twin()->source()->point() == eit->twin()->curve().source()); - line_edges.push_back(eit->twin().ptr()); + + std::vector line_edges_in_gon; + for (auto eit = arr.induced_edges_begin(lch); eit != arr.induced_edges_end(lch); ++eit) { + auto edge = *eit; + auto fh = edge->face(); + + bool liesInGon = false; + if (!fh->has_outer_ccb()) { + continue; + } + auto ccb = fh->outer_ccb(); + auto ccbIt = ccb; + do { + // if *ccbIt lies on outer face. + for (auto curveIt = arr.originating_curves_begin(ccbIt); curveIt != arr.originating_curves_end(ccbIt); ++curveIt) { + Arr::Curve_handle ch = curveIt; + if (ch == ogch) { + liesInGon = true; + break; + } } + } while (++ccbIt != ccb); + + if (liesInGon) { + line_edges_in_gon.push_back(eit->ptr()); } } - // todo: fix. Use Arr_polycurve_traits_2 class. - // 1. line_edges is incorrect because the insertion of polygon edges changes the edge data. - // use arrangement with history and polycurve triats. - // 2. below I don't check whether a line_edge actually lies inside an interior face of a polygon -_-. - // check whether the outer_ccb of one of the adjacent faces of the edge originates from the outer boundary of the - // polygon of from a hole. - // 3. make it work for CSPolygonWithHoles - - std::vector parts; - while (!line_edges.empty()) { + while (!line_edges_in_gon.empty()) { // Find first edge on connected component of polyline (in the intersection with polygon) - auto start = line_edges.front(); + auto start = line_edges_in_gon.front(); auto curr = start; while (curr->prev()->data().of_polyline) { curr = curr->prev(); @@ -48,13 +65,13 @@ std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline lin } } std::vector xmcs; - auto last_it = line_edges.end(); + auto last_it = line_edges_in_gon.end(); do { - last_it = std::remove(line_edges.begin(), last_it, curr); - xmcs.push_back(curr->curve()); + last_it = std::remove(line_edges_in_gon.begin(), last_it, curr); + std::copy(curr->curve().subcurves_begin(), curr->curve().subcurves_end(), std::back_inserter(xmcs)); curr = curr->next(); } while (curr->data().of_polyline); - line_edges.erase(last_it, line_edges.end()); + line_edges_in_gon.erase(last_it, line_edges_in_gon.end()); parts.emplace_back(xmcs.begin(), xmcs.end()); } diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index 66f795dd..7e99c659 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -9,7 +9,8 @@ struct HalfEdgePolylineData { bool of_polyline = false; }; -std::vector poly_line_gon_intersection(CSPolygon gon, CSPolyline line); +std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line); +std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line); } #endif //CARTOCROW_POLY_LINE_GON_INTERSECTION_H diff --git a/cartocrow/simplesets/types.h b/cartocrow/simplesets/types.h index 59af0810..5ea70161 100644 --- a/cartocrow/simplesets/types.h +++ b/cartocrow/simplesets/types.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "general_polyline.h" @@ -15,10 +16,13 @@ namespace cartocrow::simplesets { //typedef Exact K; typedef CGAL::Arr_circle_segment_traits_2 CSTraits; typedef CGAL::Gps_circle_segment_traits_2 CSTraitsBoolean; +typedef CGAL::Arr_polycurve_traits_2 PolyCSTraits; typedef CSTraitsBoolean::Polygon_2 CSPolygon; typedef CSTraitsBoolean::Polygon_with_holes_2 CSPolygonWithHoles; typedef CGAL::General_polygon_set_2 CSPolygonSet; typedef General_polyline_2 CSPolyline; +typedef PolyCSTraits::Curve_2 CSPolycurve; +typedef PolyCSTraits::X_monotone_curve_2 CSPolycurveXM; typedef CGAL::Arrangement_2 CSArrangement; typedef CSTraits::X_monotone_curve_2 X_monotone_curve_2; typedef CSTraits::Curve_2 Curve_2; diff --git a/demos/simplesets/circle_convex_hull.cpp b/demos/simplesets/circle_convex_hull.cpp index f19dd18d..b10ceb46 100644 --- a/demos/simplesets/circle_convex_hull.cpp +++ b/demos/simplesets/circle_convex_hull.cpp @@ -25,7 +25,7 @@ CircleConvexHullDemo::CircleConvexHullDemo() { auto hull = approximateConvexHull(cs); std::function drawFunc = [cs, hull](GeometryRenderer& renderer) { - RenderPath path = renderPathFromCSPolygon(hull); + RenderPath path = renderPath(hull); renderer.setMode(GeometryRenderer::stroke); renderer.setFill(Color{50, 50, 50}); renderer.draw(path); diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index d929cdfb..0e3b18d0 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -106,29 +106,33 @@ SimpleSetsDemo::SimpleSetsDemo() { } m_partition = *thePartition; -// m_dpd = std::make_shared(m_partition, m_gs, m_cds); -// auto ap = std::make_shared(*m_dpd, m_ds); -// renderer->addPainting(ap, "Arrangement"); -// -// auto pp = std::make_shared(m_partition, m_gs, m_ds); -// renderer->addPainting(pp, "Partition"); + m_dpd = std::make_shared(m_partition, m_gs, m_cds); + auto ap = std::make_shared(*m_dpd, m_ds); + renderer->addPainting(ap, "Arrangement"); + + auto pp = std::make_shared(m_partition, m_gs, m_ds); + renderer->addPainting(pp, "Partition"); renderer->addPainting([this](GeometryRenderer& r) { CSPolygon disk = circleToCSPolygon({{0, 0}, 1}); + std::vector diskHoles = {circleToCSPolygon({{0, 0}, 0.4})}; + CSPolygonWithHoles withHoles(disk, diskHoles.begin(), diskHoles.end()); std::vector xm_curves_pl({ {{0, -2}, {0, 2}} }); CSPolyline polyline(xm_curves_pl.begin(), xm_curves_pl.end()); - r.setMode(GeometryRenderer::stroke); r.setStroke(Color{0, 0, 0}, 3); - r.draw(renderPathFromCSPolygon(disk)); -// r.draw(renderPathFromCSPolyline(polyline)); + r.setMode(GeometryRenderer::stroke | GeometryRenderer::fill); + r.setFill(Color{200, 200, 200}); + r.draw(renderPath(withHoles)); + r.setMode(GeometryRenderer::stroke); + r.draw(renderPath(polyline)); - auto inters = poly_line_gon_intersection(disk, polyline); - r.setStroke(CB::blue, 3); + auto inters = poly_line_gon_intersection(withHoles, polyline); + r.setStroke(CB::blue, 5); for (const auto& inter : inters) { - r.draw(renderPathFromCSPolyline(inter)); + r.draw(renderPath(inter)); } }, "Poly-gon-line intersection"); @@ -168,7 +172,7 @@ SimpleSetsDemo::SimpleSetsDemo() { //// r.draw(include); //// } //// auto hull = approximateConvexHull(includeExclude.include); -//// auto path = renderPathFromCSPolygon(hull); +//// auto path = renderPath(hull); //// r.draw(path); // // From fed53220ce4957345b5323782a4658eb087890c9 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Fri, 6 Sep 2024 14:26:26 +0200 Subject: [PATCH 11/36] Fix stroke drawing --- cartocrow/simplesets/drawing_algorithm.cpp | 91 ++++++--- .../helpers/approximate_convex_hull.cpp | 4 - .../simplesets/helpers/cs_polygon_helpers.cpp | 33 ++- .../simplesets/helpers/cs_polygon_helpers.h | 2 + .../helpers/poly_line_gon_intersection.cpp | 85 ++------ .../helpers/poly_line_gon_intersection.h | 102 +++++++++- cartocrow/simplesets/settings.h | 3 +- demos/simplesets/simplesets_demo.cpp | 188 +----------------- 8 files changed, 207 insertions(+), 301 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index d7547008..4e6d6d8f 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -462,7 +462,7 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape std::vector> arcCovering; for (const auto& d : exclDisks) { - auto inter = poly_line_gon_intersection(circleToCSPolygon(d), boundaryPart); + auto inter = poly_line_gon_intersection(circleToCSPolygon(d), boundaryPart, true); bool coversLine = true; for (const auto& p : inter) { if (!isStraight(p)) { @@ -521,9 +521,6 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape polygonSet2.difference(modifiedCut); } -// polygonSet.complement(); -// polygonSet.intersection(componentShape); - std::vector polys; polygonSet2.polygons_with_holes(std::back_inserter(polys)); assert(polys.size() == 1); @@ -539,15 +536,11 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape auto endIt = boundaryPart.curves_end(); --endIt; auto boundaryPartEnd = endIt->target(); -// int boundaryPartStartIndex = -1; -// int boundaryPartEndIndex = -1; int startIndex = -1; int endIndex = -1; -// std::cout << "Looking for start and end of boundary part" << std::endl; for (int i = 0; i < outer_xm_curves.size(); i++) { auto c = outer_xm_curves[i]; -// std::cout << "curve " << c.source() << " -> " << c.target() << std::endl; if (outer_xm_curves[i].source() == boundaryPartStart) { startIndex = i; } @@ -555,21 +548,14 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape endIndex = i; } } -// assert(boundaryPartStartIndex >= 0); -// assert(boundaryPartEndIndex >= 0); -// std::cout << "Looking for alternative start and end" << std::endl; for (int i = 0; i < outer_xm_curves.size() && (startIndex < 0 || endIndex < 0); i++) { const auto& c = outer_xm_curves[i]; -// std::cout << "curve " << c.source() << "(" << liesOn(c.source(), componentShape).has_value() << ")" << " -> " << -// c.target() << "(" << liesOn(c.target(), componentShape).has_value() << ")" << " " << liesOn(c, componentShape) << std::endl; if (startIndex < 0 && liesOn(c.source(), componentShape).has_value() && !liesOn(c, componentShape)) { startIndex = i; -// std::cout << "Found start curve " << c.source() << " -> " << c.target() << std::endl; } if (endIndex < 0 && !liesOn(c, componentShape) && liesOn(c.target(), componentShape).has_value()) { endIndex = i; -// std::cout << "Found end curve " << c.source() << " -> " << c.target() << std::endl; } } @@ -761,7 +747,7 @@ Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& for (const auto& d : exclusionDisks) { if (d.squared_radius() <= 0) continue; for (auto& polyline : bps) { - auto inters = poly_line_gon_intersection(circleToCSPolygon(d), polyline); + auto inters = poly_line_gon_intersection(circleToCSPolygon(d), polyline, true); for (auto& inter : inters) { if (!isStraight(inter)) { return true; @@ -793,12 +779,16 @@ Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& return {i, j, pref, pref}; } +Color whiten(const Color& color, double a) { + return {static_cast(255 * a + color.r * (1-a)), static_cast(255 * a + color.g * (1-a)), static_cast(255 * a + color.b * (1-a))}; +} + void DilatedPatternDrawing::drawFaceFill(FaceH fh, renderer::GeometryRenderer& renderer, const GeneralSettings& gs, const DrawSettings& ds) const { auto& d = fh->data(); for (int i : d.ordering) { renderer.setMode(GeometryRenderer::fill); - renderer.setFill(ds.colors[m_dilated[i].category()]); + renderer.setFill(whiten(ds.colors[m_dilated[i].category()], ds.whiten)); if (!d.morphedFace.contains(i)) { auto poly = face_to_polygon(*fh); renderer.draw(renderPath(poly)); @@ -811,9 +801,8 @@ void DilatedPatternDrawing::drawFaceFill(FaceH fh, renderer::GeometryRenderer& r void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& renderer, const GeneralSettings& gs, const DrawSettings& ds) const { auto& d = fh->data(); - for (int i : d.ordering) { - renderer.setMode(GeometryRenderer::stroke); - renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs), true); + for (int index = 0; index < d.ordering.size(); ++index) { + int i = d.ordering[index]; std::vector polylines; if (d.morphedEdges[i].empty()) { @@ -822,21 +811,50 @@ void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& } std::copy(d.morphedEdges[i].begin(), d.morphedEdges[i].end(), std::back_inserter(polylines)); + std::vector modifiedPolylines; - CSPolygonSet polySet; + if (index == d.ordering.size() - 1) { + modifiedPolylines = polylines; + } else { + bool nothingVisible = false; + auto poly = face_to_polygon(*fh); + auto bb = poly.bbox(); + Rectangle bbX(bb.xmin() - 1, bb.ymin() - 1, bb.xmax() + 1, bb.ymax() + 1); + std::vector xm_cs; + for (int k = 0; k < 4; ++k) { + xm_cs.emplace_back(bbX.vertex(k), bbX.vertex((k + 1) % 4)); + } + CSPolygon bbXPoly(xm_cs.begin(), xm_cs.end()); + + CSPolygonSet polySet(bbXPoly); + for (int higherIndex = index + 1; higherIndex < d.ordering.size(); ++higherIndex) { + int j = d.ordering[higherIndex]; + // Pattern j is stacked on top of i and will cover the stroke of shape i. + if (!d.morphedFace.contains(j)) { + nothingVisible = true; + break; + } else { + polySet.difference(d.morphedFace[j]); + } + } + + if (nothingVisible) continue; - // todo: take difference of polylines with the cs polygons stacked on top + std::vector polygonsWithHoles; + polySet.polygons_with_holes(std::back_inserter(polygonsWithHoles)); -// for (const auto& polyline : polylines) { -// poly_line_gon_intersection(); -// } + for (const auto& polyline : polylines) { + for (const auto& polygon : polygonsWithHoles) { + poly_line_gon_intersection(polygon, polyline, std::back_inserter(modifiedPolylines), false, false); + } + } + } -// if (d) { -// auto poly = face_to_polygon(face); -// renderer.draw(renderPathFromCSPolygon(poly)); -// } else { -// renderer.draw(renderPathFromCSPolygon(*d.morphedFace)); -// } + renderer.setMode(GeometryRenderer::stroke); + renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs), true); + for (const auto& polyline : modifiedPolylines) { + renderer.draw(renderPath(polyline)); + } } } @@ -1014,5 +1032,16 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { if (fit->is_unbounded()) continue; m_dpd.drawFaceStroke(fit.ptr(), renderer, m_dpd.m_gs, m_ds); } + + const auto& gs = m_dpd.m_gs; + renderer.setStroke(Color{0, 0, 0}, m_ds.pointStrokeWeight(gs), true); + renderer.setFillOpacity(255); + renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); + for (const auto& dp : m_dpd.m_dilated) { + for (const auto& cp : dp.catPoints()) { + renderer.setFill(m_ds.colors.at(cp.category)); + renderer.draw(Circle{cp.point, gs.pointSize}); + } + } } } \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/approximate_convex_hull.cpp b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp index a2924334..7f14be6a 100644 --- a/cartocrow/simplesets/helpers/approximate_convex_hull.cpp +++ b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp @@ -270,10 +270,6 @@ CSPolygon approximateConvexHull(const std::vector>& circles) { segs.push_back(pair.second); } tangents.push_back(segs); - - for (const auto& seg : segs) { - std::cout << seg.source() << " -> " << seg.target() << std::endl; - } } std::vector xm_curves; diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp index 8c6ca02a..eda5a2fb 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp @@ -1,5 +1,6 @@ #include "cs_polygon_helpers.h" #include "cs_curve_helpers.h" +#include // Code adapted from a Stack Overflow answer by HEKTO. // Link: https://stackoverflow.com/questions/69399922/how-does-one-obtain-the-area-of-a-general-polygon-set-in-cgal @@ -171,15 +172,8 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon) { auto tit = *sc; auto curr = sit; do { -// std::cout << c.is_linear() << " " << c.is_circular() << " " << c.is_vertical() << std::endl; -// - std::cout << curr->source() << " -> " << curr->target() << std::endl; if (curr->is_linear()) { if (c.is_circular()) return false; - std::cout << "Supporting line" << std::endl; - std::cout << curr->supporting_line() << std::endl; - std::cout << "c" << std::endl; - std::cout << c.supporting_line() << " " << c.source() << " " << c.target() << std::endl; if (curr->supporting_line() != c.supporting_line()) return false; } else { if (c.is_linear()) return false; @@ -193,4 +187,29 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon) { CSPolycurve arrPolycurveFromCSPolygon(const CSPolygon& polygon) { return arrPolycurveFromXMCurves(polygon.curves_begin(), polygon.curves_end()); } + +Polygon linearSample(const CSPolygon& polygon, int n) { + std::vector> coords; + polygon.approximate(std::back_inserter(coords), n); + std::vector> points; + + // polygon.approximate returns duplicate points. + for (int i = 0; i < coords.size(); ++i) { + auto p = coords[i]; + if (i > 0) { + auto prev = coords[i - 1]; + if (p == prev) continue; + } + if (i == coords.size() - 1 && p == coords[0]) { + continue; + } + points.emplace_back(p.first, p.second); + } + return {points.begin(), points.end()}; +} + +CSPolygonWithHoles approximateDilate(const CSPolygon& polygon, double r, double eps, int n) { + auto poly = linearSample(polygon, n); + return CGAL::approximated_offset_2(poly, r, eps); +} } \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.h b/cartocrow/simplesets/helpers/cs_polygon_helpers.h index 190e0cf8..f1def966 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.h +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.h @@ -33,6 +33,8 @@ renderer::RenderPath renderPath(const CSPolygon& polygon); renderer::RenderPath renderPath(const CSPolygonWithHoles& withHoles); bool on_or_inside(const CSPolygon& polygon, const Point& point); CSPolycurve arrPolycurveFromCSPolygon(const CSPolygon& polygon); +Polygon linearSample(const CSPolygon& polygon, int n); +CSPolygonWithHoles approximateDilate(const CSPolygon& polygon, double r, double eps, int n); } #endif //CARTOCROW_CS_POLYGON_HELPERS_H diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp index bfce48c2..45f6e089 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp @@ -1,80 +1,25 @@ #include "poly_line_gon_intersection.h" -#include "cs_polyline_helpers.h" -#include "cs_polygon_helpers.h" -#include namespace cartocrow::simplesets { -std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line) { +std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap) { CSPolygonWithHoles withHoles(gon); - return poly_line_gon_intersection(withHoles, line); + return poly_line_gon_intersection(withHoles, line, keepOverlap); } -std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line) { - using Arr = CGAL::Arrangement_with_history_2>; - Arr arr; - auto linePolycurve = arrPolycurveFromCSPolyline(line); - auto outerGonPolycurve = arrPolycurveFromCSPolygon(gon.outer_boundary()); - - auto lch = CGAL::insert(arr, linePolycurve); - auto ogch = CGAL::insert(arr, outerGonPolycurve); - std::vector hgch; - - std::vector holesGonPolycurves; - for (auto hit = gon.holes_begin(); hit != gon.holes_end(); ++hit) { - CGAL::insert(arr, arrPolycurveFromCSPolygon(*hit)); - } - - std::vector line_edges_in_gon; - for (auto eit = arr.induced_edges_begin(lch); eit != arr.induced_edges_end(lch); ++eit) { - auto edge = *eit; - auto fh = edge->face(); - - bool liesInGon = false; - if (!fh->has_outer_ccb()) { - continue; - } - auto ccb = fh->outer_ccb(); - auto ccbIt = ccb; - do { - // if *ccbIt lies on outer face. - for (auto curveIt = arr.originating_curves_begin(ccbIt); curveIt != arr.originating_curves_end(ccbIt); ++curveIt) { - Arr::Curve_handle ch = curveIt; - if (ch == ogch) { - liesInGon = true; - break; - } - } - } while (++ccbIt != ccb); - - if (liesInGon) { - line_edges_in_gon.push_back(eit->ptr()); - } - } - - std::vector parts; - while (!line_edges_in_gon.empty()) { - // Find first edge on connected component of polyline (in the intersection with polygon) - auto start = line_edges_in_gon.front(); - auto curr = start; - while (curr->prev()->data().of_polyline) { - curr = curr->prev(); +std::vector poly_line_gon_difference(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap) { + CSPolygonWithHoles withHoles(gon); + return poly_line_gon_difference(withHoles, line, keepOverlap); +} - // The polyline and polygon do not intersect. - if (curr == start) { - return {}; - } - } - std::vector xmcs; - auto last_it = line_edges_in_gon.end(); - do { - last_it = std::remove(line_edges_in_gon.begin(), last_it, curr); - std::copy(curr->curve().subcurves_begin(), curr->curve().subcurves_end(), std::back_inserter(xmcs)); - curr = curr->next(); - } while (curr->data().of_polyline); - line_edges_in_gon.erase(last_it, line_edges_in_gon.end()); - parts.emplace_back(xmcs.begin(), xmcs.end()); - } +std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap) { + std::vector polylines; + poly_line_gon_intersection(gon, line, std::back_inserter(polylines), false, keepOverlap); + return polylines; +} - return parts; +std::vector poly_line_gon_difference(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap) { + std::vector polylines; + poly_line_gon_intersection(gon, line, std::back_inserter(polylines), true, keepOverlap); + return polylines; } } diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index 7e99c659..068d3051 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -2,6 +2,9 @@ #define CARTOCROW_POLY_LINE_GON_INTERSECTION_H #include "cartocrow/simplesets/types.h" +#include "cs_polyline_helpers.h" +#include "cs_polygon_helpers.h" +#include namespace cartocrow::simplesets { // todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. @@ -9,8 +12,103 @@ struct HalfEdgePolylineData { bool of_polyline = false; }; -std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line); -std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line); +std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap); +std::vector poly_line_gon_difference(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap); +std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap); +std::vector poly_line_gon_difference(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap); + +template +void poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line, OutputIterator out, bool difference, bool keepOverlap) { + using Arr = CGAL::Arrangement_with_history_2>; + Arr arr; + auto linePolycurve = arrPolycurveFromCSPolyline(line); + auto outerGonPolycurve = arrPolycurveFromCSPolygon(gon.outer_boundary()); + + auto lch = CGAL::insert(arr, linePolycurve); + auto ogch = CGAL::insert(arr, outerGonPolycurve); + std::vector hgchs; + + std::vector holesGonPolycurves; + for (auto hit = gon.holes_begin(); hit != gon.holes_end(); ++hit) { + hgchs.push_back(CGAL::insert(arr, arrPolycurveFromCSPolygon(*hit))); + } + + std::vector line_edges_keep; + for (auto eit = arr.induced_edges_begin(lch); eit != arr.induced_edges_end(lch); ++eit) { + Arr::Halfedge_handle edge = *eit; + + bool onGonEdge = false; + for (auto curveIt = arr.originating_curves_begin(edge); curveIt != arr.originating_curves_end(edge); ++curveIt) { + auto curve = *curveIt; + Arr::Curve_handle ch = curveIt; + if (ch == ogch) { + onGonEdge = true; + } + for (const auto& hgch : hgchs) { + if (ch == hgch) { + onGonEdge = true; + } + } + } + if (onGonEdge) { + if (keepOverlap) { + line_edges_keep.push_back(eit->ptr()); + } + continue; + } + + bool liesInGon = false; + for (auto fh : {edge->face(), edge->twin()->face()}) { + if (!fh->has_outer_ccb()) { + continue; + } + auto ccb = fh->outer_ccb(); + auto ccbIt = ccb; + do { + // if *ccbIt lies on outer face. + for (auto curveIt = arr.originating_curves_begin(ccbIt); + curveIt != arr.originating_curves_end(ccbIt); ++curveIt) { + Arr::Curve_handle ch = curveIt; + if (ch == ogch) { + liesInGon = true; + break; + } + } + } while (++ccbIt != ccb); + if (liesInGon) break; + } + + if (!difference && liesInGon) { + line_edges_keep.push_back(eit->ptr()); + } + if (difference && !liesInGon) { + line_edges_keep.push_back(eit->ptr()); + } + } + + while (!line_edges_keep.empty()) { + // Find first edge on connected component of polyline (in the intersection with polygon) + auto start = line_edges_keep.front(); + auto curr = start; + while (curr->prev()->data().of_polyline) { + curr = curr->prev(); + + // The polyline and polygon do not intersect. + if (curr == start) { + return; + } + } + std::vector xmcs; + auto last_it = line_edges_keep.end(); + do { + last_it = std::remove(line_edges_keep.begin(), last_it, curr); + std::copy(curr->curve().subcurves_begin(), curr->curve().subcurves_end(), std::back_inserter(xmcs)); + curr = curr->next(); + } while (curr->data().of_polyline); + line_edges_keep.erase(last_it, line_edges_keep.end()); + ++out = CSPolyline(xmcs.begin(), xmcs.end()); + } +} } #endif //CARTOCROW_POLY_LINE_GON_INTERSECTION_H diff --git a/cartocrow/simplesets/settings.h b/cartocrow/simplesets/settings.h index a0735d83..e1ee49ac 100644 --- a/cartocrow/simplesets/settings.h +++ b/cartocrow/simplesets/settings.h @@ -16,7 +16,7 @@ struct GeneralSettings { /// The distance each pattern is dilated. Number dilationRadius() const { - return pointSize * 3; + return pointSize * 2.1; } }; @@ -41,6 +41,7 @@ struct ComputeDrawingSettings { struct DrawSettings { std::vector colors; + Number whiten; Number pointStrokeWeight(GeneralSettings gs) const { return gs.pointSize / 3.5; } diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 0e3b18d0..c1c1ebdf 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -80,7 +80,7 @@ SimpleSetsDemo::SimpleSetsDemo() { renderer->setMinZoom(0.01); renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("/home/steven/Downloads/test/cartocrow/data/nyc.txt"); + std::filesystem::path filePath("/home/steven/Documents/cartocrow/data/nyc.txt"); std::ifstream inputStream(filePath, std::ios_base::in); if (!inputStream.good()) { throw std::runtime_error("Failed to read input"); @@ -92,7 +92,7 @@ SimpleSetsDemo::SimpleSetsDemo() { m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; // m_ps = PartitionSettings{true, true, true, true, 0.5}; todo: fix intersection delay crash m_ps = PartitionSettings{true, true, true, false, 0.5}; - m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}}; + m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.5}; m_cds = ComputeDrawingSettings{0.675}; auto partitionList = partition(nycPoints, m_gs, m_ps, 8 * m_gs.dilationRadius()); @@ -113,190 +113,6 @@ SimpleSetsDemo::SimpleSetsDemo() { auto pp = std::make_shared(m_partition, m_gs, m_ds); renderer->addPainting(pp, "Partition"); - renderer->addPainting([this](GeometryRenderer& r) { - CSPolygon disk = circleToCSPolygon({{0, 0}, 1}); - std::vector diskHoles = {circleToCSPolygon({{0, 0}, 0.4})}; - CSPolygonWithHoles withHoles(disk, diskHoles.begin(), diskHoles.end()); - std::vector xm_curves_pl({ - {{0, -2}, {0, 2}} - }); - CSPolyline polyline(xm_curves_pl.begin(), xm_curves_pl.end()); - - r.setStroke(Color{0, 0, 0}, 3); - r.setMode(GeometryRenderer::stroke | GeometryRenderer::fill); - r.setFill(Color{200, 200, 200}); - r.draw(renderPath(withHoles)); - r.setMode(GeometryRenderer::stroke); - r.draw(renderPath(polyline)); - - auto inters = poly_line_gon_intersection(withHoles, polyline); - r.setStroke(CB::blue, 5); - for (const auto& inter : inters) { - r.draw(renderPath(inter)); - } - }, "Poly-gon-line intersection"); - -// auto one = m_dpd->m_dilated[0]; -// auto other = m_dpd->m_dilated[1]; -// auto comp = m_dpd->intersectionComponents(0, 1)[0]; -// auto includeExclude = m_dpd->includeExcludeDisks(0, 1, comp); - - m_cc = std::make_shared>(2, -5); - renderer->registerEditable(m_cc); - -// renderer->addPainting([this](GeometryRenderer& r) { -// CSPolygonSet set; -// CSPolygon disk1 = circleToCSPolygon({{0, 0}, 1}); -// set.join(disk1); -// CSPolygon disk2 = circleToCSPolygon({{0, 2.6}, 0.5}); -// set.difference(disk2); -// std::vector points({{0, {-2, -2}}, {0, {2, -2}}, {0, {2, 2}}, {0, {-2, 2}}}); -// Island island(points); -// Dilated dilated(island, 1.0); -// set.complement(); -// set.intersection(dilated.m_contour); -// std::vector polys; -// set.polygons_with_holes(std::back_inserter(polys)); -// r.draw(renderPathFromCSPolygon(polys[0].outer_boundary())); -// r.draw(renderPathFromCSPolygon(polys[0].holes_begin().operator*())); -// }, "Polygon set bug?"); - -// renderer->addPainting([this, includeExclude, comp](GeometryRenderer& r) { -// r.setMode(GeometryRenderer::fill); -//// for (const auto& exclude : includeExclude.exclude) { -//// r.setFill(CB::red); -//// r.draw(exclude); -//// } -//// for (const auto& include : includeExclude.include) { -//// r.setFill(CB::green); -//// r.draw(include); -//// } -//// auto hull = approximateConvexHull(includeExclude.include); -//// auto path = renderPath(hull); -//// r.draw(path); -// -// -// -// auto inclDisks = includeExclude.include; -// auto exclDisks = includeExclude.exclude; -// auto componentShape = ccb_to_polygon(comp.outer_ccb()); -// auto boundaryPart = boundaryParts(comp, 0)[0]; -// -// if (exclDisks.empty()) return boundaryPart; -// -// std::vector> lineCovering; -// std::vector> arcCovering; -// -// for (const auto& d : exclDisks) { -// auto inter = poly_line_gon_intersection(circleToCSPolygon(d), boundaryPart); -// bool coversLine = true; -// for (const auto& p : inter) { -// if (!isStraight(p)) { -// coversLine = false; -// break; -// } -// } -// if (coversLine) { -// lineCovering.push_back(d); -// } else { -// arcCovering.push_back(d); -// } -// } -// -// auto dr = m_gs.dilationRadius(); -// // Smoothing radius -// auto sr = dr / 5; -// -// std::vector> expandedLineCoveringDisks; -// for (const auto& d : lineCovering) { -// expandedLineCoveringDisks.emplace_back(approximate(d.center()), squared(sqrt(CGAL::to_double(d.squared_radius())) + sr)); -// } -// auto diskComponents = connectedDisks(expandedLineCoveringDisks); -// -// std::vector cuts; -// for (const auto& comp : diskComponents) { -// std::vector> disks; -// for (const auto& [i, _] : comp) { -// disks.push_back(lineCovering[i]); -// } -// cuts.push_back(approximateConvexHull(disks)); -// } -// for (const auto& d : arcCovering) { -// cuts.push_back(circleToCSPolygon(d)); -// } -// -// CSPolygonSet polygonSet; -// for (const auto& cut : cuts) { -// polygonSet.join(cut); -// } -// for (const auto& d: inclDisks) { -// polygonSet.difference(circleToCSPolygon(d)); -// } -// for (const auto& d: exclDisks) { -// if (on_or_inside(componentShape, d.center())) { -// auto n = nearest(boundaryPart, d.center()); -// auto rect = thinRectangle(d.center(), n, m_gs.pointSize / 5); -// polygonSet.join(rect); -// } -// } -// polygonSet.complement(); -//// polygonSet.intersection(componentShape); -// -// std::vector polys; -// polygonSet.polygons_with_holes(std::back_inserter(polys)); -// // CGAL::difference(componentShape, ) -// assert(polys.size() == 1); -// auto poly = polys[0]; -// // std -//// assert(!poly.has_holes()); -// std::cout << "#holes: " << poly.number_of_holes() << std::endl; -// -// auto outer = *poly.holes_begin(); -// for (auto cit = outer.curves_begin(); cit != outer.curves_end(); ++cit) { -// std::cout << cit->source() << " -> " << cit->target() << std::endl; -// if (cit->is_linear()) { -// std::cout << cit->supporting_line() << std::endl; -// } else { -// std::cout << cit->supporting_circle() << std::endl; -// } -// } -// -// -// r.setFill(CB::light_purple); -// r.draw(renderPathFromCSPolygon(outer)); -// -// }, "Include exclude"); - -// renderer->addPainting([this](GeometryRenderer& r) { -//// X_monotone_curve_2 bot({0, 0}, {5, 0}); -//// X_monotone_curve_2 right({5, 0}, {5, 5}); -//// X_monotone_curve_2 top({5, 5}, {0, 5}); -//// X_monotone_curve_2 left({0, 5}, {0, 0}); -// std::vector points({{0, {0, 0}}, {0, {5, -3}}, {0, {5, 5}}, {0, {2, 5}}}); -// Island island(points); -// Dilated dilated(island, 1.0); -// auto polygon = dilated.m_contour; -//// Polygon polygon(points.begin(), points.end()); -//// auto csPolygon = dil -// -//// std::vector xm_curves({bot, right, top, left}); -//// CSPolygon polygon(xm_curves.begin(), xm_curves.end()); -// r.setMode(GeometryRenderer::fill); -// r.setFill(Color{100, 100, 100}); -// r.draw(renderPathFromCSPolygon(polygon)); -// -// std::vector xm_curves_bp(polygon.curves_begin(), std::next(polygon.curves_begin(), 6)); -// -//// auto xm_curves_bp -// CSPolyline polyline(xm_curves_bp.begin(), xm_curves_bp.end()); -// auto morphed = morph(polyline, polygon, {}, {Circle({m_cc->x(), m_cc->y()}, 1)}, m_gs, m_cds); -// r.setMode(GeometryRenderer::stroke); -// r.setStroke(Color{0, 0, 0}, 3.0); -// r.draw(renderPathFromCSPolyline(morphed)); -// r.draw(approximateAlgebraic(nearest(polyline, {m_cc->x(), m_cc->y()}))); -// r.draw(*m_cc); -// }, "Morph test"); - connect(fitToScreenButton, &QPushButton::clicked, [nycPoints, renderer, this]() { std::vector> pts; std::transform(nycPoints.begin(), nycPoints.end(), std::back_inserter(pts), From 2c8e349cc328c171d2498e83874c52312554a423 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Fri, 6 Sep 2024 21:00:34 +0200 Subject: [PATCH 12/36] Fix point size, preference & order, stroke drawing & improve GUI --- cartocrow/simplesets/drawing_algorithm.cpp | 33 ++++++-- cartocrow/simplesets/partition_painting.cpp | 2 +- cartocrow/simplesets/settings.h | 6 +- demos/simplesets/simplesets_demo.cpp | 88 ++++++++++++++------- demos/simplesets/simplesets_demo.h | 7 ++ 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 4e6d6d8f..a6f394ef 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -311,6 +311,7 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { auto& data = fit->data(); + if (data.origins.empty()) continue; auto ordering = computeTotalOrder(data.origins, data.relations); if (!ordering.has_value()) { throw std::runtime_error("Impossible: no total order in a face"); @@ -361,7 +362,8 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G std::copy(morphed.curves_begin(), morphed.curves_end(), std::back_inserter(morphedComponentXMCurves)); for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { - fit->data().morphedEdges[i].push_back(morphed); + CSPolygonWithHoles pgn(face_to_polygon(*fit)); + poly_line_gon_intersection(pgn, morphed, std::back_inserter(fit->data().morphedEdges[i]), false, true); } morphedFace = true; } @@ -761,10 +763,10 @@ Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& bool jCircularIndented = circularIndented(iExclusion, bpj); if (iCircularIndented && !jCircularIndented) { - pref = Order::GREATER; + pref = Order::SMALLER; } if (!iCircularIndented && jCircularIndented){ - pref = Order::SMALLER; + pref = Order::GREATER; } // 1. Prefer to avoid few points over many points. @@ -941,9 +943,15 @@ std::vector DilatedPatternDrawing::hyperedges() { } std::optional> computeTotalOrder(const std::vector& origins, const std::vector& relations) { + if (relations.empty()) { + assert(origins.size() <= 1); + return origins; + } + struct Vertex { int i; std::vector neighbors; + bool hasIncoming = false; int mark = 0; }; @@ -955,13 +963,15 @@ std::optional> computeTotalOrder(const std::vector& origin for (const auto& r : relations) { if (r.preference == Order::EQUAL) continue; - auto u = vertices.at(r.left); - auto v = vertices.at(r.right); + auto& u = vertices.at(r.left); + auto& v = vertices.at(r.right); if (r.ordering == Order::SMALLER) { v.neighbors.push_back(u); + u.hasIncoming = true; } else { u.neighbors.push_back(v); + v.hasIncoming = true; } } @@ -986,8 +996,15 @@ std::optional> computeTotalOrder(const std::vector& origin }; for (auto& [_, v] : vertices) { - bool success = visit(v); - if (!success) return std::nullopt; + if (!v.hasIncoming) { + bool success = visit(v); + if (!success) + return std::nullopt; + } + } + + if (ordering.size() != origins.size()) { + return std::nullopt; } return ordering; @@ -1040,7 +1057,7 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { for (const auto& dp : m_dpd.m_dilated) { for (const auto& cp : dp.catPoints()) { renderer.setFill(m_ds.colors.at(cp.category)); - renderer.draw(Circle{cp.point, gs.pointSize}); + renderer.draw(Circle{cp.point, gs.pointSize * gs.pointSize}); } } } diff --git a/cartocrow/simplesets/partition_painting.cpp b/cartocrow/simplesets/partition_painting.cpp index f2e0fceb..8678f667 100644 --- a/cartocrow/simplesets/partition_painting.cpp +++ b/cartocrow/simplesets/partition_painting.cpp @@ -22,7 +22,7 @@ void draw_poly_pattern(const PolyPattern& pattern, renderer::GeometryRenderer& r renderer.setFillOpacity(255); renderer.setFill(ds.colors.at(pt.category)); renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); - renderer.draw(Circle{pt.point, gs.pointSize}); + renderer.draw(Circle{pt.point, gs.pointSize * gs.pointSize}); } } diff --git a/cartocrow/simplesets/settings.h b/cartocrow/simplesets/settings.h index e1ee49ac..ecd3c5c9 100644 --- a/cartocrow/simplesets/settings.h +++ b/cartocrow/simplesets/settings.h @@ -16,7 +16,7 @@ struct GeneralSettings { /// The distance each pattern is dilated. Number dilationRadius() const { - return pointSize * 2.1; + return pointSize * 3; } }; @@ -43,10 +43,10 @@ struct DrawSettings { std::vector colors; Number whiten; Number pointStrokeWeight(GeneralSettings gs) const { - return gs.pointSize / 3.5; + return gs.pointSize / 2.5; } Number contourStrokeWeight(GeneralSettings gs) const { - return gs.pointSize / 4; + return gs.pointSize / 3.5; } }; diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index c1c1ebdf..1178c48b 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -64,37 +64,73 @@ SimpleSetsDemo::SimpleSetsDemo() { auto* basicOptions = new QLabel("

Input

"); vLayout->addWidget(basicOptions); - auto* fileSelector = new QComboBox(); - auto* fileSelectorLabel = new QLabel("Input file"); - fileSelectorLabel->setBuddy(fileSelector); - vLayout->addWidget(fileSelectorLabel); + auto* fileSelector = new QPushButton("Select file"); vLayout->addWidget(fileSelector); auto* fitToScreenButton = new QPushButton("Fit to screen"); vLayout->addWidget(fitToScreenButton); - auto renderer = new GeometryWidget(); - renderer->setDrawAxes(false); - setCentralWidget(renderer); + m_renderer = new GeometryWidget(); + m_renderer->setDrawAxes(false); + setCentralWidget(m_renderer); - renderer->setMinZoom(0.01); - renderer->setMaxZoom(1000.0); + m_renderer->setMinZoom(0.01); + m_renderer->setMaxZoom(1000.0); std::filesystem::path filePath("/home/steven/Documents/cartocrow/data/nyc.txt"); + + m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; +// m_ps = PartitionSettings{true, true, true, true, 0.5}; todo: fix intersection delay crash + m_ps = PartitionSettings{true, true, true, false, 0.5}; + m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; + m_cds = ComputeDrawingSettings{0.675}; + + loadFile(filePath); + + compute(); + + connect(fitToScreenButton, &QPushButton::clicked, [this]() { + fitToScreen(); + }); + connect(fileSelector, &QPushButton::clicked, [this, fileSelector]() { + QString startDir = "/home/steven/Documents/cartocrow/data/"; + std::filesystem::path filePath = QFileDialog::getOpenFileName(this, tr("Select SimpleSets input"), startDir).toStdString(); + if (filePath == "") return; + loadFile(filePath); + fileSelector->setText(QString::fromStdString(filePath.filename())); + }); + + fitToScreenButton->click(); +} + +void SimpleSetsDemo::loadFile(const std::filesystem::path& filePath) { std::ifstream inputStream(filePath, std::ios_base::in); if (!inputStream.good()) { throw std::runtime_error("Failed to read input"); } std::stringstream buffer; buffer << inputStream.rdbuf(); - auto nycPoints = parseCatPoints(buffer.str()); + m_points = parseCatPoints(buffer.str()); + compute(); + fitToScreen(); +} - m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; -// m_ps = PartitionSettings{true, true, true, true, 0.5}; todo: fix intersection delay crash - m_ps = PartitionSettings{true, true, true, false, 0.5}; - m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.5}; - m_cds = ComputeDrawingSettings{0.675}; - auto partitionList = partition(nycPoints, m_gs, m_ps, 8 * m_gs.dilationRadius()); +void SimpleSetsDemo::fitToScreen() { + std::vector> pts; + std::transform(m_points.begin(), m_points.end(), std::back_inserter(pts), + [](const CatPoint& pt) { return pt.point; }); + Box box = CGAL::bbox_2(pts.begin(), pts.end()); + auto delta = 2 * m_gs.dilationRadius(); + Box expanded(box.xmin() - delta, box.ymin() - delta, box.xmax() + delta, box.ymax() + delta); + m_renderer->fitInView(expanded); +} + +void SimpleSetsDemo::resizeEvent(QResizeEvent *event) { + fitToScreen(); +} + +void SimpleSetsDemo::compute() { + auto partitionList = partition(m_points, m_gs, m_ps, 8 * m_gs.dilationRadius()); Number cover = 4.7; @@ -106,22 +142,14 @@ SimpleSetsDemo::SimpleSetsDemo() { } m_partition = *thePartition; - m_dpd = std::make_shared(m_partition, m_gs, m_cds); - auto ap = std::make_shared(*m_dpd, m_ds); - renderer->addPainting(ap, "Arrangement"); + m_renderer->clear(); auto pp = std::make_shared(m_partition, m_gs, m_ds); - renderer->addPainting(pp, "Partition"); - - connect(fitToScreenButton, &QPushButton::clicked, [nycPoints, renderer, this]() { - std::vector> pts; - std::transform(nycPoints.begin(), nycPoints.end(), std::back_inserter(pts), - [](const CatPoint& pt) { return pt.point; }); - Box box = CGAL::bbox_2(pts.begin(), pts.end()); - auto delta = 2 * m_gs.dilationRadius(); - Box expanded(box.xmin() - delta, box.ymin() - delta, box.xmax() + delta, box.ymax() + delta); - renderer->fitInView(expanded); - }); + m_renderer->addPainting(pp, "Partition"); + + m_dpd = std::make_shared(m_partition, m_gs, m_cds); + auto ap = std::make_shared(*m_dpd, m_ds); + m_renderer->addPainting(ap, "Arrangement"); } int main(int argc, char* argv[]) { diff --git a/demos/simplesets/simplesets_demo.h b/demos/simplesets/simplesets_demo.h index d0428b88..ba63583b 100644 --- a/demos/simplesets/simplesets_demo.h +++ b/demos/simplesets/simplesets_demo.h @@ -28,6 +28,7 @@ along with this program. If not, see . #include "cartocrow/simplesets/partition.h" #include "cartocrow/simplesets/drawing_algorithm.h" #include +#include using namespace cartocrow; using namespace cartocrow::renderer; @@ -38,16 +39,22 @@ class SimpleSetsDemo : public QMainWindow { public: SimpleSetsDemo(); + void resizeEvent(QResizeEvent *event) override; private: + std::vector m_points; Partition m_partition; std::shared_ptr m_dpd; GeneralSettings m_gs; DrawSettings m_ds; PartitionSettings m_ps; ComputeDrawingSettings m_cds; + GeometryWidget* m_renderer; std::shared_ptr> m_cc; + void fitToScreen(); + void loadFile(const std::filesystem::path& filePath); + void compute(); }; #endif //CARTOCROW_SIMPLESETS_DEMO_H From 749fc7d965d99154641307989d4cdc737811714b Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Sun, 8 Sep 2024 13:43:32 +0200 Subject: [PATCH 13/36] Draw using total stacking order if possible and fix issues --- cartocrow/simplesets/drawing_algorithm.cpp | 147 ++++++++++++++---- cartocrow/simplesets/drawing_algorithm.h | 16 +- cartocrow/simplesets/general_polyline.h | 4 + .../simplesets/helpers/cs_curve_helpers.cpp | 31 +++- .../simplesets/helpers/cs_curve_helpers.h | 31 ++++ .../simplesets/helpers/cs_polygon_helpers.cpp | 21 ++- .../helpers/cs_polyline_helpers.cpp | 8 + .../simplesets/helpers/cs_polyline_helpers.h | 1 + .../helpers/poly_line_gon_intersection.cpp | 16 +- .../helpers/poly_line_gon_intersection.h | 94 ++++++----- test/CMakeLists.txt | 2 + .../simplesets/poly_line_gon_intersection.cpp | 37 +++++ 12 files changed, 316 insertions(+), 92 deletions(-) create mode 100644 test/simplesets/poly_line_gon_intersection.cpp diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index a6f394ef..40f04314 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -129,7 +129,7 @@ Point get_point_in(const Face& face) { } std::vector -connectedComponents(const DilatedPatternArrangement& arr, std::function in_component) { +connectedComponents(const DilatedPatternArrangement& arr, const std::function& in_component) { std::vector remaining; for (auto fit = arr.faces_begin(); fit != arr.faces_end(); ++fit) { auto fh = fit.ptr(); @@ -363,7 +363,7 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { CSPolygonWithHoles pgn(face_to_polygon(*fit)); - poly_line_gon_intersection(pgn, morphed, std::back_inserter(fit->data().morphedEdges[i]), false, true); + intersection(morphed, pgn, std::back_inserter(fit->data().morphedEdges[i]), false, true); } morphedFace = true; } @@ -464,7 +464,7 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape std::vector> arcCovering; for (const auto& d : exclDisks) { - auto inter = poly_line_gon_intersection(circleToCSPolygon(d), boundaryPart, true); + auto inter = intersection(boundaryPart, circleToCSPolygon(d), true); bool coversLine = true; for (const auto& p : inter) { if (!isStraight(p)) { @@ -673,7 +673,7 @@ bool isStraight(const CSPolyline& polyline) { /// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref js. IncludeExcludeDisks -DilatedPatternDrawing::includeExcludeDisks(int i, const std::unordered_set& js, const Component& c) { +DilatedPatternDrawing::includeExcludeDisks(int i, const std::unordered_set& js, const Component& c) const { std::vector> ptsI; const auto& catPointsI = m_dilated[i].catPoints(); std::transform(catPointsI.begin(), catPointsI.end(), std::back_inserter(ptsI), [](const CatPoint& catPoint) { @@ -702,12 +702,12 @@ DilatedPatternDrawing::includeExcludeDisks(int i, const std::unordered_set& /// The inclusion and exclusion disks for component \ref c when \ref i is stacked on top of \ref j. IncludeExcludeDisks -DilatedPatternDrawing::includeExcludeDisks(int i, int j, const Component& c) { +DilatedPatternDrawing::includeExcludeDisks(int i, int j, const Component& c) const { std::unordered_set js({j}); return includeExcludeDisks(i, js, c); } -Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& c) { +std::shared_ptr DilatedPatternDrawing::computePreference(int i, int j, const Component& c) { // The preference indicates the relation R in iRj. // If R is Order::GREATER then i > j and i is preferred to be on top of j. auto pref = Order::EQUAL; @@ -749,7 +749,7 @@ Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& for (const auto& d : exclusionDisks) { if (d.squared_radius() <= 0) continue; for (auto& polyline : bps) { - auto inters = poly_line_gon_intersection(circleToCSPolygon(d), polyline, true); + auto inters = intersection(polyline, circleToCSPolygon(d), true); for (auto& inter : inters) { if (!isStraight(inter)) { return true; @@ -778,7 +778,7 @@ Relation DilatedPatternDrawing::computePreference(int i, int j, const Component& pref = Order::SMALLER; } - return {i, j, pref, pref}; + return std::make_shared(i, j, pref, pref); } Color whiten(const Color& color, double a) { @@ -789,8 +789,9 @@ void DilatedPatternDrawing::drawFaceFill(FaceH fh, renderer::GeometryRenderer& r const GeneralSettings& gs, const DrawSettings& ds) const { auto& d = fh->data(); for (int i : d.ordering) { - renderer.setMode(GeometryRenderer::fill); + renderer.setMode(GeometryRenderer::fill | GeometryRenderer::stroke); renderer.setFill(whiten(ds.colors[m_dilated[i].category()], ds.whiten)); + renderer.setStroke(whiten(ds.colors[m_dilated[i].category()], ds.whiten), ds.contourStrokeWeight(gs) / 1.5, true); if (!d.morphedFace.contains(i)) { auto poly = face_to_polygon(*fh); renderer.draw(renderPath(poly)); @@ -847,7 +848,7 @@ void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& for (const auto& polyline : polylines) { for (const auto& polygon : polygonsWithHoles) { - poly_line_gon_intersection(polygon, polyline, std::back_inserter(modifiedPolylines), false, false); + intersection(polyline, polygon, std::back_inserter(modifiedPolylines), false, false); } } } @@ -860,6 +861,23 @@ void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& } } +std::optional> DilatedPatternDrawing::totalStackingOrder() const { + std::vector> relations; + std::vector origins; + for (int i = 0; i < m_dilated.size(); ++i) { + origins.push_back(i); + for (const auto& f : m_iToFaces.at(i)) { + for (const auto& r : f->data().relations) { + if (std::find_if(relations.begin(), relations.end(), [&r](const auto& it) { return it->left == r->left && it->right == r->right; }) == relations.end()) { + relations.push_back(r); + } + assert(r->ordering != Order::EQUAL); + } + } + } + return computeTotalOrder(origins, relations); +} + std::vector DilatedPatternDrawing::intersectionComponents(int i, int j) const { return connectedComponents(m_arr, [i, j](FaceH fh) { @@ -877,11 +895,11 @@ DilatedPatternDrawing::intersectionComponents(int i) const { }); } -std::vector DilatedPatternDrawing::hyperedges() { - std::vector interesting; +std::vector DilatedPatternDrawing::hyperedges() const { + std::vector interesting; for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { - if (fit->data().origins.size() >= 3) { + if (fit->data().origins.size() >= 2) { interesting.push_back(fit); } } @@ -942,7 +960,7 @@ std::vector DilatedPatternDrawing::hyperedges() { return hyperedges; } -std::optional> computeTotalOrder(const std::vector& origins, const std::vector& relations) { +std::optional> computeTotalOrder(const std::vector& origins, const std::vector>& relations) { if (relations.empty()) { assert(origins.size() <= 1); return origins; @@ -950,7 +968,7 @@ std::optional> computeTotalOrder(const std::vector& origin struct Vertex { int i; - std::vector neighbors; + std::vector neighbors; bool hasIncoming = false; int mark = 0; }; @@ -962,15 +980,15 @@ std::optional> computeTotalOrder(const std::vector& origin } for (const auto& r : relations) { - if (r.preference == Order::EQUAL) continue; - auto& u = vertices.at(r.left); - auto& v = vertices.at(r.right); + if (r->ordering == Order::EQUAL) continue; + auto& u = vertices.at(r->left); + auto& v = vertices.at(r->right); - if (r.ordering == Order::SMALLER) { - v.neighbors.push_back(u); + if (r->ordering == Order::SMALLER) { + v.neighbors.push_back(&u); u.hasIncoming = true; } else { - u.neighbors.push_back(v); + u.neighbors.push_back(&v); v.hasIncoming = true; } } @@ -986,7 +1004,7 @@ std::optional> computeTotalOrder(const std::vector& origin u.mark = 1; for (auto& v : u.neighbors) { - bool success = visit(v); + bool success = visit(*v); if (!success) return false; } @@ -1016,12 +1034,12 @@ std::optional> getRelationOrder(const Hyperedge& e) { void setRelationOrder(Hyperedge& e, const std::vector& ordering) { for (auto& r : e.relations) { - int i = std::find(ordering.begin(), ordering.end(), r.left) - ordering.begin(); - int j = std::find(ordering.begin(), ordering.end(), r.right) - ordering.begin(); + int i = std::find(ordering.begin(), ordering.end(), r->left) - ordering.begin(); + int j = std::find(ordering.begin(), ordering.end(), r->right) - ordering.begin(); if (i < j) { - r.ordering = Order::SMALLER; + r->ordering = Order::SMALLER; } else { - r.ordering = Order::GREATER; + r->ordering = Order::GREATER; } } } @@ -1040,16 +1058,77 @@ SimpleSetsPainting::SimpleSetsPainting(const DilatedPatternDrawing& dpd, const D : m_ds(ds), m_dpd(dpd) {} void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { - renderer.setMode(renderer::GeometryRenderer::fill); - for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { - if (fit->is_unbounded()) continue; - m_dpd.drawFaceFill(fit.ptr(), renderer, m_dpd.m_gs, m_ds); - } - for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { - if (fit->is_unbounded()) continue; - m_dpd.drawFaceStroke(fit.ptr(), renderer, m_dpd.m_gs, m_ds); + auto stackingOrder = m_dpd.totalStackingOrder(); + // If there is a stacking order, draw the complete patterns stacked in that order + if (stackingOrder.has_value()) { + for (int i : *stackingOrder) { + bool debug = m_dpd.m_dilated[i].category() == 1; + auto comps = connectedComponents(m_dpd.m_arr, [i](const FaceH& fh) { + const auto& ors = fh->data().origins; + return std::find(ors.begin(), ors.end(), i) != ors.end(); + }); + assert(comps.size() == 1); + const auto& comp = comps[0]; + auto ccb = comp.outer_ccb(); + auto start = ccb; + // start at first edge on CCB of origin + do { + auto prev = start; + --prev; + if (prev->data().origin == start->data().origin) { + start = prev; + } else { + break; + } + } while (start != ccb); + + auto curr = start; + + std::vector xm_curves; + bool doneInFace = false; + FaceH prevFace; + do { + if (doneInFace && curr->face() == prevFace) continue; + doneInFace = false; + const auto& d = curr->face()->data(); + if (!d.morphedEdges.contains(i)) { + xm_curves.push_back(curr->curve()); + } else { + auto mes = d.morphedEdges.at(i); + CSTraits traits; + auto equal = traits.equal_2_object(); + auto it = std::find_if(mes.begin(), mes.end(), [&curr, &equal](const CSPolyline& pl) { + return equal(pl.curves_begin()->source(), curr->source()->point()); + }); + assert(it != mes.end()); + auto pl = *it; + std::copy(pl.curves_begin(), pl.curves_end(), std::back_inserter(xm_curves)); + doneInFace = true; + prevFace = curr->face(); + } + } while (++curr != start); + CSPolygon csPolygon(xm_curves.begin(), xm_curves.end()); + + renderer.setMode(GeometryRenderer::fill | GeometryRenderer::stroke); + renderer.setFill(whiten(m_ds.colors[m_dpd.m_dilated[i].category()], m_ds.whiten)); + renderer.setStroke(Color{0, 0, 0}, m_ds.contourStrokeWeight(m_dpd.m_gs), true); + renderer.draw(renderPath(csPolygon)); + } + } else { + // If there is no stacking order, draw each face of the arrangement separately + for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { + if (fit->is_unbounded()) + continue; + m_dpd.drawFaceFill(fit.ptr(), renderer, m_dpd.m_gs, m_ds); + } + for (auto fit = m_dpd.m_arr.faces_begin(); fit != m_dpd.m_arr.faces_end(); ++fit) { + if (fit->is_unbounded()) + continue; + m_dpd.drawFaceStroke(fit.ptr(), renderer, m_dpd.m_gs, m_ds); + } } + // Draw points const auto& gs = m_dpd.m_gs; renderer.setStroke(Color{0, 0, 0}, m_ds.pointStrokeWeight(gs), true); renderer.setFillOpacity(255); diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 92d51ae8..8a25159d 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -33,18 +33,18 @@ std::ostream& operator<<(std::ostream& out, const Relation& r); class Hyperedge { public: std::vector origins; - std::vector relations; + std::vector> relations; }; std::optional> getRelationOrder(const Hyperedge& e); void setRelationOrder(Hyperedge& e, const std::vector& ordering); -std::optional> computeTotalOrder(const std::vector& origins, const std::vector& relations); +std::optional> computeTotalOrder(const std::vector& origins, const std::vector>& relations); bool operator==(const Hyperedge& lhs, const Hyperedge& rhs); struct FaceData { std::vector origins; - std::vector relations; + std::vector> relations; std::vector ordering; std::unordered_map> morphedEdges; std::unordered_map morphedFace; @@ -68,6 +68,7 @@ using DilatedPatternArrangement = using Face = DilatedPatternArrangement::Face; using FaceH = DilatedPatternArrangement::Face_handle; +using FaceCH = DilatedPatternArrangement::Face_const_handle; using VertexH = DilatedPatternArrangement::Vertex_handle; using HalfEdgeH = DilatedPatternArrangement::Halfedge_handle; @@ -315,17 +316,18 @@ class DilatedPatternDrawing { std::vector intersectionComponents(int i) const; std::vector intersectionComponents(int i, int j) const; - Relation computePreference(int i, int j, const Component& c); + std::shared_ptr computePreference(int i, int j, const Component& c); - IncludeExcludeDisks includeExcludeDisks(int i, int j, const Component& c); - IncludeExcludeDisks includeExcludeDisks(int i, const std::unordered_set& js, const Component& c); + IncludeExcludeDisks includeExcludeDisks(int i, int j, const Component& c) const; + IncludeExcludeDisks includeExcludeDisks(int i, const std::unordered_set& js, const Component& c) const; - std::vector hyperedges(); + std::vector hyperedges() const; void drawFaceFill(FaceH fh, renderer::GeometryRenderer& renderer, const GeneralSettings& gs, const DrawSettings& ds) const; void drawFaceStroke(FaceH fh, renderer::GeometryRenderer& renderer, const GeneralSettings& gs, const DrawSettings& ds) const; + std::optional> totalStackingOrder() const; DilatedPatternArrangement m_arr; std::unordered_map> m_iToFaces; diff --git a/cartocrow/simplesets/general_polyline.h b/cartocrow/simplesets/general_polyline.h index b865fa8f..e8f33249 100644 --- a/cartocrow/simplesets/general_polyline.h +++ b/cartocrow/simplesets/general_polyline.h @@ -26,6 +26,10 @@ class General_polyline_2 { Curve_const_iterator curves_end() const { return m_xm_curves.cend(); } + [[nodiscard]] unsigned int size() const + { + return static_cast(m_xm_curves.size()); + } private: std::vector m_xm_curves; }; diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp index 65cdf5ec..007bd860 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp @@ -130,11 +130,40 @@ void addToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& p if (CGAL::squared_distance(as, at) < M_EPSILON) { return; } - auto circle = xm_curve.supporting_circle(); + const auto& circle = xm_curve.supporting_circle(); path.arcTo(approximate(circle.center()), xm_curve.orientation() == CGAL::CLOCKWISE, approximateAlgebraic(xm_curve.target())); } } +void addToRenderPath(const Curve_2& curve, renderer::RenderPath& path, bool& first) { + if (curve.is_full()) { + const auto& circ = curve.supporting_circle(); + const auto& cent = approximate(circ.center()); + auto r = sqrt(CGAL::to_double(circ.squared_radius())); + Point s(cent.x() - r, cent.y()); + auto clockwise = circ.orientation() == CGAL::CLOCKWISE; + path.moveTo(s); + path.arcTo(cent, clockwise, {cent.x() + r, cent.y()}); + path.arcTo(cent, clockwise, s); + path.close(); + return; + } + auto as = approximateAlgebraic(curve.source()); + auto at = approximateAlgebraic(curve.target()); + if (first) { + path.moveTo(as); + first = false; + } + if (curve.is_linear()) { + path.lineTo(at); + } else if (curve.is_circular()){ + if (CGAL::squared_distance(as, at) < M_EPSILON) { + return; + } + const auto& circle = curve.supporting_circle(); + path.arcTo(approximate(circle.center()), curve.orientation() == CGAL::CLOCKWISE, approximateAlgebraic(curve.target())); + } +} Curve_2 toCurve(const X_monotone_curve_2& xmc) { if (xmc.is_linear()) { diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.h b/cartocrow/simplesets/helpers/cs_curve_helpers.h index f3e08fad..366f7e7c 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.h +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.h @@ -37,8 +37,39 @@ bool liesOn(const Point& p, const X_monotone_curve_2& xm_curve); bool liesOn(const OneRootPoint& p, const X_monotone_curve_2& xm_curve); renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve); void addToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first); +void addToRenderPath(const Curve_2& curve, renderer::RenderPath& path, bool& first); Curve_2 toCurve(const X_monotone_curve_2& xmc); +template +void toCurves(InputIterator begin, InputIterator end, OutputIterator out) { + std::optional lastCurve; + for (auto curr = begin; curr != end; ++curr) { + X_monotone_curve_2 xmc = *curr; + if (!lastCurve.has_value()) { + lastCurve = toCurve(xmc); + } else { + if (lastCurve->is_linear() && xmc.is_linear() && lastCurve->supporting_line() == xmc.supporting_line()) { + Curve_2 newCurve(lastCurve->supporting_line(), lastCurve->source(), xmc.target()); + lastCurve = newCurve; + } else if (lastCurve->is_circular() && xmc.is_circular() && lastCurve->supporting_circle() == xmc.supporting_circle()) { + Curve_2 newCurve; + if (xmc.target() == lastCurve->source()) { + newCurve = Curve_2(lastCurve->supporting_circle()); + } else { + newCurve = Curve_2(lastCurve->supporting_circle(), lastCurve->source(), xmc.target()); + } + lastCurve = newCurve; + } else { + ++out = *lastCurve; + lastCurve = toCurve(xmc); + } + } + } + if (lastCurve.has_value()) { + ++out = *lastCurve; + } +} + template CSPolycurve arrPolycurveFromXMCurves(InputIterator begin, InputIterator end) { PolyCSTraits traits; diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp index eda5a2fb..4867fb0d 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp @@ -35,7 +35,16 @@ Number area(const CSTraits::Point_2& P1, const CSTraits::Point_2& P2, c squaredRadius * std::asin(std::min(1.0, chord / (std::sqrt(squaredRadius) * 2))); auto const areaTriangle = chord * std::sqrt(std::max(0.0, squaredRadius * 4 - squaredChord)) / 4; auto const areaCircularSegment = areaSector - areaTriangle; - return area(P1, P2) + C.orientation() * areaCircularSegment; + int sign; + if (C.orientation() == CGAL::Sign::NEGATIVE) { + sign = -1; + } else if (C.orientation() == CGAL::Sign::POSITIVE) { + sign = 1; + } else { + assert(C.orientation() == CGAL::Sign::ZERO); + sign = 0; + } + return area(P1, P2) + sign * areaCircularSegment; } // ------ return signed area under the X-monotone curve @@ -93,10 +102,14 @@ std::optional liesOn(const OneRootPoint& p, con renderer::RenderPath operator<<(renderer::RenderPath& path, const CSPolygon& polygon) { bool first = true; - for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { - addToRenderPath(*cit, path, first); + std::vector mergedCurves; + toCurves(polygon.curves_begin(), polygon.curves_end(), std::back_inserter(mergedCurves)); + for (const auto& c : mergedCurves) { + addToRenderPath(c, path, first); + } + if (!holds_alternative(path.commands().back())) { + path.close(); } - path.close(); return path; } diff --git a/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp index d8914767..7b16bfa0 100644 --- a/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp @@ -74,4 +74,12 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolyline& polyline) { CSPolycurve arrPolycurveFromCSPolyline(const CSPolyline& polyline) { return arrPolycurveFromXMCurves(polyline.curves_begin(), polyline.curves_end()); } + +CSPolyline polylineToCSPolyline(const Polyline& polyline) { + std::vector xm_curves; + for (auto eit = polyline.edges_begin(); eit != polyline.edges_end(); ++eit) { + xm_curves.emplace_back(eit->source(), eit->target()); + } + return {xm_curves.begin(), xm_curves.end()}; +} } \ No newline at end of file diff --git a/cartocrow/simplesets/helpers/cs_polyline_helpers.h b/cartocrow/simplesets/helpers/cs_polyline_helpers.h index 09690616..a410ea14 100644 --- a/cartocrow/simplesets/helpers/cs_polyline_helpers.h +++ b/cartocrow/simplesets/helpers/cs_polyline_helpers.h @@ -12,6 +12,7 @@ std::optional liesOn(const OneRootPoint& p, co bool liesOn(const X_monotone_curve_2& c, const CSPolyline& polyline); renderer::RenderPath renderPath(const CSPolyline& polyline); CSPolycurve arrPolycurveFromCSPolyline(const CSPolyline& polyline); +CSPolyline polylineToCSPolyline(const Polyline& polyline); } #endif //CARTOCROW_CS_POLYLINE_HELPERS_H diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp index 45f6e089..d8827527 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.cpp @@ -1,25 +1,25 @@ #include "poly_line_gon_intersection.h" namespace cartocrow::simplesets { -std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap) { +std::vector intersection(const CSPolyline& line, const CSPolygon& gon, bool keepOverlap) { CSPolygonWithHoles withHoles(gon); - return poly_line_gon_intersection(withHoles, line, keepOverlap); + return intersection(line, withHoles, keepOverlap); } -std::vector poly_line_gon_difference(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap) { +std::vector difference(const CSPolyline& line, const CSPolygon& gon, bool keepOverlap) { CSPolygonWithHoles withHoles(gon); - return poly_line_gon_difference(withHoles, line, keepOverlap); + return difference(line, withHoles, keepOverlap); } -std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap) { +std::vector intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, bool keepOverlap) { std::vector polylines; - poly_line_gon_intersection(gon, line, std::back_inserter(polylines), false, keepOverlap); + intersection(line, gon, std::back_inserter(polylines), false, keepOverlap); return polylines; } -std::vector poly_line_gon_difference(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap) { +std::vector difference(const CSPolyline& line, const CSPolygonWithHoles& gon, bool keepOverlap) { std::vector polylines; - poly_line_gon_intersection(gon, line, std::back_inserter(polylines), true, keepOverlap); + intersection(line, gon, std::back_inserter(polylines), true, keepOverlap); return polylines; } } diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index 068d3051..d8593588 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -8,18 +8,16 @@ namespace cartocrow::simplesets { // todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. -struct HalfEdgePolylineData { - bool of_polyline = false; -}; - -std::vector poly_line_gon_intersection(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap); -std::vector poly_line_gon_difference(const CSPolygon& gon, const CSPolyline& line, bool keepOverlap); -std::vector poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap); -std::vector poly_line_gon_difference(const CSPolygonWithHoles& gon, const CSPolyline& line, bool keepOverlap); +std::vector intersection(const CSPolyline& line, const CSPolygon& gon, bool keepOverlap); +std::vector difference(const CSPolyline& line, const CSPolygon& gon, bool keepOverlap); +std::vector intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, bool keepOverlap); +std::vector difference(const CSPolyline& line, const CSPolygonWithHoles& gon, bool keepOverlap); template -void poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& line, OutputIterator out, bool difference, bool keepOverlap) { - using Arr = CGAL::Arrangement_with_history_2>; +void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputIterator out, bool difference, bool keepOverlap) { + PolyCSTraits traits; + auto equals = traits.equal_2_object(); + using Arr = CGAL::Arrangement_with_history_2; Arr arr; auto linePolycurve = arrPolycurveFromCSPolyline(line); auto outerGonPolycurve = arrPolycurveFromCSPolygon(gon.outer_boundary()); @@ -50,52 +48,71 @@ void poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& } } } + bool liesInGon = false; if (onGonEdge) { if (keepOverlap) { - line_edges_keep.push_back(eit->ptr()); - } - continue; - } - - bool liesInGon = false; - for (auto fh : {edge->face(), edge->twin()->face()}) { - if (!fh->has_outer_ccb()) { - continue; + liesInGon = true; } - auto ccb = fh->outer_ccb(); - auto ccbIt = ccb; - do { - // if *ccbIt lies on outer face. - for (auto curveIt = arr.originating_curves_begin(ccbIt); - curveIt != arr.originating_curves_end(ccbIt); ++curveIt) { - Arr::Curve_handle ch = curveIt; - if (ch == ogch) { - liesInGon = true; - break; - } + } else { + for (auto fh : {edge->face(), edge->twin()->face()}) { + if (!fh->has_outer_ccb()) { + continue; } - } while (++ccbIt != ccb); - if (liesInGon) break; + auto ccb = fh->outer_ccb(); + auto ccbIt = ccb; + do { + if (!equals(ccb->source()->point(), ccb->curve().subcurves_begin()->source())) + continue; + // if *ccbIt lies on outer face. + for (auto curveIt = arr.originating_curves_begin(ccbIt); + curveIt != arr.originating_curves_end(ccbIt); ++curveIt) { + Arr::Curve_handle ch = curveIt; + if (ch == ogch) { + liesInGon = true; + break; + } + } + } while (++ccbIt != ccb); + if (liesInGon) + break; + } } if (!difference && liesInGon) { - line_edges_keep.push_back(eit->ptr()); + if (equals(edge->source()->point(), edge->curve().subcurves_begin()->source())) { + line_edges_keep.push_back(edge); + } else { + line_edges_keep.push_back(edge->twin()); + } } if (difference && !liesInGon) { - line_edges_keep.push_back(eit->ptr()); + if (equals(edge->source()->point(), edge->curve().subcurves_begin()->source())) { + line_edges_keep.push_back(edge); + } else { + line_edges_keep.push_back(edge->twin()); + } } } + auto originatesFromPolyline = [&arr, &lch, &equals](Arr::Halfedge_handle h) { + for (auto cit = arr.originating_curves_begin(h); cit != arr.originating_curves_end(h); ++cit) { + Arr::Curve_handle ch = cit; + if (ch == lch && equals(h->source()->point(), h->curve().subcurves_begin()->source())) { + return true; + } + } + return false; + }; + while (!line_edges_keep.empty()) { // Find first edge on connected component of polyline (in the intersection with polygon) auto start = line_edges_keep.front(); auto curr = start; - while (curr->prev()->data().of_polyline) { + while (originatesFromPolyline(curr->prev())) { curr = curr->prev(); - // The polyline and polygon do not intersect. if (curr == start) { - return; + break; } } std::vector xmcs; @@ -104,8 +121,9 @@ void poly_line_gon_intersection(const CSPolygonWithHoles& gon, const CSPolyline& last_it = std::remove(line_edges_keep.begin(), last_it, curr); std::copy(curr->curve().subcurves_begin(), curr->curve().subcurves_end(), std::back_inserter(xmcs)); curr = curr->next(); - } while (curr->data().of_polyline); + } while (originatesFromPolyline(curr)); line_edges_keep.erase(last_it, line_edges_keep.end()); + ++out = CSPolyline(xmcs.begin(), xmcs.end()); } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d93ee33d..c63df3fd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,6 +19,7 @@ set(TEST_SOURCES "cartocrow_test.cpp" "necklace_map/range.cpp" "renderer/ipe_renderer.cpp" "simplification/vw_simplification.cpp" + "simplesets/poly_line_gon_intersection.cpp" ) add_executable(cartocrow_test cartocrow_test.cpp ${TEST_SOURCES}) @@ -29,4 +30,5 @@ target_link_libraries(cartocrow_test necklace_map renderer simplification + simplesets ) diff --git a/test/simplesets/poly_line_gon_intersection.cpp b/test/simplesets/poly_line_gon_intersection.cpp new file mode 100644 index 00000000..d0801b4b --- /dev/null +++ b/test/simplesets/poly_line_gon_intersection.cpp @@ -0,0 +1,37 @@ +#include "../catch.hpp" +#include "cartocrow/simplesets/helpers/poly_line_gon_intersection.h" + +using namespace cartocrow; +using namespace cartocrow::simplesets; + +TEST_CASE("Intersection lies in polygon and has correct orientation") { + X_monotone_curve_2 xm_curve({-2, 0}, {2, 0}); + std::vector xm_curves{xm_curve}; + CSPolyline polyline(xm_curves.begin(), xm_curves.end()); + CSPolygon disk = circleToCSPolygon({{0, 0}, 1}); + auto result = intersection(polyline, disk, false); + CHECK(result.size() == 1); + CHECK(result[0].size() == 1); + CHECK(result[0].curves_begin()->source() == OneRootPoint(-1, 0)); + CHECK(result[0].curves_begin()->target() == OneRootPoint(1, 0)); +} + +TEST_CASE("Multiple and connected parts of intersection") { + Number half = 1; + half /= 2; + std::vector> points({{-1, -1}, {0, 0}, {0, -1 - half}, {1 + half, -1 - half}, {1 + half, 0}, {half, 0}}); + Polyline pl(points.begin(), points.end()); + CSPolyline polyline = polylineToCSPolyline(pl); + CSPolygon disk = circleToCSPolygon({{0, 0}, 1}); + auto result = intersection(polyline, disk, false); + CHECK(result.size() == 2); + CHECK(result[0].size() == 2); + OneRootNumber negHalfSqrt2(0, -half, 2); + CHECK(result[0].curves_begin()->source() == OneRootPoint(negHalfSqrt2, negHalfSqrt2)); + CHECK((++result[0].curves_begin())->target() == OneRootPoint(0, -1)); + CHECK(result[1].size() == 1); + CHECK(result[1].curves_begin()->source() == OneRootPoint(1, 0)); + CHECK(result[1].curves_begin()->target() == OneRootPoint(half, 0)); +} + +// todo: test difference, overlapping edges, and holes From 41766a00c001d752bcef8805e51e3048d26e61d9 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Sun, 8 Sep 2024 23:09:34 +0200 Subject: [PATCH 14/36] Small fix and debugging on diseasome data --- cartocrow/simplesets/drawing_algorithm.cpp | 3 +- .../helpers/poly_line_gon_intersection.h | 22 +++++- cartocrow/simplesets/partition_painting.cpp | 4 +- cartocrow/simplesets/settings.h | 10 +++ cartocrow/simplesets/types.h | 8 --- demos/simplesets/simplesets_demo.cpp | 8 ++- .../simplesets/poly_line_gon_intersection.cpp | 67 +++++++++++++++++++ 7 files changed, 107 insertions(+), 15 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 40f04314..12a814a5 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -915,6 +915,7 @@ std::vector DilatedPatternDrawing::hyperedges() const { for (const auto& fh : interesting) { if (!lastSize.has_value() || fh->data().origins.size() == *lastSize) { group.emplace_back(fh->data().origins, fh->data().relations); + lastSize = fh->data().origins.size(); } else { if (!group.empty()) { hyperedgesGrouped.push_back(group); @@ -1135,7 +1136,7 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); for (const auto& dp : m_dpd.m_dilated) { for (const auto& cp : dp.catPoints()) { - renderer.setFill(m_ds.colors.at(cp.category)); + renderer.setFill(m_ds.getColor(cp.category)); renderer.draw(Circle{cp.point, gs.pointSize * gs.pointSize}); } } diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index d8593588..2627ed0a 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -22,8 +22,28 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI auto linePolycurve = arrPolycurveFromCSPolyline(line); auto outerGonPolycurve = arrPolycurveFromCSPolygon(gon.outer_boundary()); - auto lch = CGAL::insert(arr, linePolycurve); +// std::cout << "!" << std::endl; +// for (auto cit = outerGonPolycurve.subcurves_begin(); cit != outerGonPolycurve.subcurves_end(); ++cit) { +// if (cit->is_circular()) { +// std::cout << cit->supporting_circle().center() << " " << cit->supporting_circle().squared_radius() << std::endl; +// } +// if (cit->is_linear()) { +// std::cout << cit->supporting_line() << std::endl; +// } +// std::cout << cit->source() << " -> " << cit->target() << std::endl; +// } +// for (auto cit = linePolycurve.subcurves_begin(); cit != linePolycurve.subcurves_end(); ++cit) { +// if (cit->is_circular()) { +// std::cout << cit->supporting_circle().center() << " " << cit->supporting_circle().squared_radius() << std::endl; +// } +// if (cit->is_linear()) { +// std::cout << cit->supporting_line() << std::endl; +// } +// std::cout << cit->source() << " -> " << cit->target() << std::endl; +// } + auto ogch = CGAL::insert(arr, outerGonPolycurve); + auto lch = CGAL::insert(arr, linePolycurve); std::vector hgchs; std::vector holesGonPolycurves; diff --git a/cartocrow/simplesets/partition_painting.cpp b/cartocrow/simplesets/partition_painting.cpp index 8678f667..134120f8 100644 --- a/cartocrow/simplesets/partition_painting.cpp +++ b/cartocrow/simplesets/partition_painting.cpp @@ -8,7 +8,7 @@ void draw_poly_pattern(const PolyPattern& pattern, renderer::GeometryRenderer& r } else { renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); } - renderer.setFill(ds.colors.at(pattern.category())); + renderer.setFill(ds.getColor(pattern.category())); renderer.setFillOpacity(100); renderer.setStroke(Color{0, 0, 0}, ds.contourStrokeWeight(gs), true); auto& pts = pattern.catPoints(); @@ -20,7 +20,7 @@ void draw_poly_pattern(const PolyPattern& pattern, renderer::GeometryRenderer& r for (const auto& pt : pattern.catPoints()) { renderer.setStroke(Color{0, 0, 0}, ds.pointStrokeWeight(gs), true); renderer.setFillOpacity(255); - renderer.setFill(ds.colors.at(pt.category)); + renderer.setFill(ds.getColor(pt.category)); renderer.setMode(renderer::GeometryRenderer::fill | renderer::GeometryRenderer::stroke); renderer.draw(Circle{pt.point, gs.pointSize * gs.pointSize}); } diff --git a/cartocrow/simplesets/settings.h b/cartocrow/simplesets/settings.h index ecd3c5c9..0e3598bc 100644 --- a/cartocrow/simplesets/settings.h +++ b/cartocrow/simplesets/settings.h @@ -48,6 +48,16 @@ struct DrawSettings { Number contourStrokeWeight(GeneralSettings gs) const { return gs.pointSize / 3.5; } + Color getColor(int category) const { + Color fillColor; + if (category > colors.size() || category < 0) { + std::cerr << "Warning! No color specified for category " << category << std::endl; + fillColor = Color{240, 240, 240}; + } else { + fillColor = colors[category]; + } + return fillColor; + } }; struct Settings { diff --git a/cartocrow/simplesets/types.h b/cartocrow/simplesets/types.h index 5ea70161..146df7c7 100644 --- a/cartocrow/simplesets/types.h +++ b/cartocrow/simplesets/types.h @@ -13,7 +13,6 @@ #include "general_polyline.h" namespace cartocrow::simplesets { -//typedef Exact K; typedef CGAL::Arr_circle_segment_traits_2 CSTraits; typedef CGAL::Gps_circle_segment_traits_2 CSTraitsBoolean; typedef CGAL::Arr_polycurve_traits_2 PolyCSTraits; @@ -28,13 +27,6 @@ typedef CSTraits::X_monotone_curve_2 X_monotone_curve_2; typedef CSTraits::Curve_2 Curve_2; typedef CSTraits::CoordNT OneRootNumber; typedef CSTraits::Point_2 OneRootPoint; -//typedef CGAL::CORE_algebraic_number_traits Nt_traits; -//typedef CGAL::Cartesian Rat_kernel; -//typedef Nt_traits::Algebraic Algebraic; -//typedef CGAL::Cartesian Alg_kernel; -//typedef CGAL::Arr_conic_traits_2 ConicTraits; -//typedef CGAL::Gps_traits_2 Gps_traits; -//typedef Alg_kernel K; Point makeExact(const Point& point); Circle makeExact(const Circle& circle); diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 1178c48b..c7e9ff9c 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -77,9 +77,10 @@ SimpleSetsDemo::SimpleSetsDemo() { m_renderer->setMinZoom(0.01); m_renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("/home/steven/Documents/cartocrow/data/nyc.txt"); + std::filesystem::path filePath("/home/steven/Documents/cartocrow/data/diseasome.txt"); - m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; +// m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc + m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome // m_ps = PartitionSettings{true, true, true, true, 0.5}; todo: fix intersection delay crash m_ps = PartitionSettings{true, true, true, false, 0.5}; m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; @@ -132,7 +133,8 @@ void SimpleSetsDemo::resizeEvent(QResizeEvent *event) { void SimpleSetsDemo::compute() { auto partitionList = partition(m_points, m_gs, m_ps, 8 * m_gs.dilationRadius()); - Number cover = 4.7; +// Number cover = 4.7; // nyc + auto cover = 5.913; // diseasome Partition* thePartition; for (auto& [time, partition] : partitionList) { diff --git a/test/simplesets/poly_line_gon_intersection.cpp b/test/simplesets/poly_line_gon_intersection.cpp index d0801b4b..55c1cef3 100644 --- a/test/simplesets/poly_line_gon_intersection.cpp +++ b/test/simplesets/poly_line_gon_intersection.cpp @@ -1,7 +1,11 @@ #include "../catch.hpp" #include "cartocrow/simplesets/helpers/poly_line_gon_intersection.h" +#include "cartocrow/simplesets/helpers/cs_curve_helpers.h" +#include "cartocrow/simplesets/helpers/arrangement_helpers.h" +#include "cartocrow/renderer/ipe_renderer.h" using namespace cartocrow; +using namespace cartocrow::renderer; using namespace cartocrow::simplesets; TEST_CASE("Intersection lies in polygon and has correct orientation") { @@ -34,4 +38,67 @@ TEST_CASE("Multiple and connected parts of intersection") { CHECK(result[1].curves_begin()->target() == OneRootPoint(half, 0)); } +TEST_CASE("Crashes") { + auto r = 5.204 * 3; + auto r_ = 5.204 * 3 * 0.675; + auto r2 = r * r; + auto r2_ = r_ * r_; + Circle c1({2597.9, -364.3}, r2, CGAL::CLOCKWISE); + Circle c2({2609.2, -342.6}, r2, CGAL::CLOCKWISE); + Circle c2_({2609.2, -342.6}, r2_, CGAL::CLOCKWISE); + + auto getIntersections = [](const Circle& one, const Circle& two) { + CSArrangement arr; + CGAL::insert(arr, one); + CGAL::insert(arr, two); + std::vector intersectionPoints; + for (auto vit = arr.vertices_begin(); vit != arr.vertices_end(); ++vit) { + if (vit->degree() == 4) { + intersectionPoints.push_back(vit->point()); + } + } + std::sort(intersectionPoints.begin(), intersectionPoints.end(), [](const OneRootPoint& p1, const OneRootPoint& p2) { return p1.x() < p2.x(); }); + assert(intersectionPoints.size() == 2); + return intersectionPoints; + }; + + auto isp12 = getIntersections(c1, c2); + + Curve_2 arc1(c1, isp12[0], isp12[1]); + Curve_2 arc2(c2, isp12[1], isp12[0]); + std::vector pgnArcs({arc1, arc2}); + + PolyCSTraits traits; + auto construct = traits.construct_curve_2_object(); + CSPolycurve pgnCurve(pgnArcs.begin(), pgnArcs.end()); + + CSArrangement arr; + CGAL::insert(arr, c1); + CGAL::insert(arr, c2); + CGAL::insert(arr, c2_); + + CSArrangement::Face_handle fh; + for (auto eit = arr.halfedges_begin(); eit != arr.halfedges_end(); ++eit) { + if (eit->source()->point() == isp12[0]) { + fh = eit->face(); + } + } + + std::vector xm_curves; + curvesToXMonotoneCurves(pgnArcs.begin(), pgnArcs.end(), std::back_inserter(xm_curves)); + CSPolygon pgn(xm_curves.begin(), xm_curves.end()); + + auto plnPoly = ccb_to_polygon(fh->outer_ccb()); + CSPolyline pln(plnPoly.curves_begin(), plnPoly.curves_end()); + intersection(pln, pgn, true); + + IpeRenderer ipeRenderer; + ipeRenderer.addPainting([&pgn, &pln](GeometryRenderer& renderer) { + auto path = renderPath(pgn); + renderer.draw(path); + renderer.draw(renderPath(pln)); + }); + ipeRenderer.save("/home/steven/Documents/cartocrow/crash.ipe"); +} + // todo: test difference, overlapping edges, and holes From 20a7682ecf4dec37c53cde0d6ed6868175c4f995 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Mon, 9 Sep 2024 12:51:24 +0200 Subject: [PATCH 15/36] Small fixes; intersection delay does not crash anymore --- cartocrow/simplesets/dilated/dilated_poly.cpp | 22 +++++++++---------- cartocrow/simplesets/drawing_algorithm.cpp | 3 +-- .../helpers/approximate_convex_hull.cpp | 6 ----- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/cartocrow/simplesets/dilated/dilated_poly.cpp b/cartocrow/simplesets/dilated/dilated_poly.cpp index d2f1e12b..28155ba1 100644 --- a/cartocrow/simplesets/dilated/dilated_poly.cpp +++ b/cartocrow/simplesets/dilated/dilated_poly.cpp @@ -1,5 +1,6 @@ #include "dilated_poly.h" #include "../helpers/cs_polygon_helpers.h" +#include "../helpers/arrangement_helpers.h" #include namespace cartocrow::simplesets { @@ -10,16 +11,6 @@ CSPolygon dilateSegment(const Segment& segment, const Number& return dilation.outer_boundary(); } -CSPolygon ccb_to_polygon(CSArrangement::Ccb_halfedge_const_circulator circ) { - std::vector curves; - auto curr = circ; - do { - curves.push_back(curr->curve()); - } while (++curr != circ); - CSPolygon poly(curves.begin(), curves.end()); - return poly; -} - Dilated::Dilated(const PolyPattern& polyPattern, const Number& dilationRadius) { m_catPoints = polyPattern.catPoints(); @@ -31,6 +22,9 @@ Dilated::Dilated(const PolyPattern& polyPattern, const Number& dilation if (exactPolygon.size() == 1) { CSTraits::Rational_circle_2 circle(exactPolygon.vertex(0), dilationRadius * dilationRadius); m_contour = circleToCSPolygon(circle); + if (m_contour.orientation() == CGAL::CLOCKWISE) { + m_contour.reverse_orientation(); + } return; } @@ -39,6 +33,9 @@ Dilated::Dilated(const PolyPattern& polyPattern, const Number& dilation throw std::runtime_error("Did not expect holes after dilating a polygonal pattern."); } m_contour = dilation.outer_boundary(); + if (m_contour.orientation() == CGAL::CLOCKWISE) { + m_contour.reverse_orientation(); + } } else if (holds_alternative>(cont)) { // 1. Dilate each segment // 2. Make arrangement of dilated segments @@ -57,7 +54,10 @@ Dilated::Dilated(const PolyPattern& polyPattern, const Number& dilation } } - m_contour = ccb_to_polygon(*arr.unbounded_face()->inner_ccbs_begin()); + m_contour = ccb_to_polygon(*(arr.unbounded_face()->inner_ccbs_begin())); + if (m_contour.orientation() == CGAL::CLOCKWISE) { + m_contour.reverse_orientation(); + } } else { throw std::runtime_error("Unknown pattern poly."); } diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 12a814a5..96e400f4 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -388,7 +388,7 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G } bool overlap(const Circle& c1, const Circle& c2) { - return CGAL::squared_distance(c1.center(), c2.center()) <= c1.squared_radius() + c2.squared_radius(); + return sqrt(CGAL::squared_distance(c1.center(), c2.center())) <= sqrt(c1.squared_radius()) + sqrt(c2.squared_radius()); } std::vector>>> connectedDisks(const std::vector>& disks) { @@ -963,7 +963,6 @@ std::vector DilatedPatternDrawing::hyperedges() const { std::optional> computeTotalOrder(const std::vector& origins, const std::vector>& relations) { if (relations.empty()) { - assert(origins.size() <= 1); return origins; } diff --git a/cartocrow/simplesets/helpers/approximate_convex_hull.cpp b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp index 7f14be6a..21d44325 100644 --- a/cartocrow/simplesets/helpers/approximate_convex_hull.cpp +++ b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp @@ -213,8 +213,6 @@ algebraicCircleTangentToRationalSegments(const CSTraits::Point_2& p1, const CSTr std::variant, std::pair, Segment>> approximateTangent(const RationalRadiusCircle& c1, const RationalRadiusCircle& c2) { auto [source, target] = tangentPoints(c1, c2); - std::cout << "Tangent points" << std::endl; - std::cout << "source: " << approximateAlgebraic(source) << " target: " << approximateAlgebraic(target) << std::endl; return algebraicCircleTangentToRationalSegments(source, target, c1, c2); } @@ -250,16 +248,12 @@ CSPolygon approximateConvexHull(const std::vector>& circles) { rrCircles.push_back(approximateRadiusCircle(c)); } auto hullCircles = circlesOnConvexHull(rrCircles); - for (const auto& hc : hullCircles) { - std::cout << hc.center << std::endl; - } std::vector>> tangents; for (int i = 0; i < hullCircles.size(); ++i) { auto& c1 = hullCircles[i]; auto& c2 = hullCircles[(i + 1) % hullCircles.size()]; - std::cout << c1.center << " tangent to -> " << c2.center << std::endl; auto segOrPair = approximateTangent(c1, c2); std::vector> segs; if (segOrPair.index() == 0) { From 3b47849ed6d5d1ad776415f2b86b6773861ff147 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Wed, 11 Sep 2024 12:44:13 +0200 Subject: [PATCH 16/36] Fixes in intersection delay and hyperedges; add cover slider --- cartocrow/simplesets/drawing_algorithm.cpp | 19 +++--- cartocrow/simplesets/partition_algorithm.cpp | 28 +++++---- cartocrow/simplesets/partition_algorithm.h | 3 + demos/simplesets/simplesets_demo.cpp | 63 +++++++++++++------- demos/simplesets/simplesets_demo.h | 4 +- test/CMakeLists.txt | 1 + test/simplesets/partition_algorithm.cpp | 24 ++++++++ 7 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 test/simplesets/partition_algorithm.cpp diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 96e400f4..6e3be0a4 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -913,15 +913,12 @@ std::vector DilatedPatternDrawing::hyperedges() const { std::vector group; std::optional lastSize; for (const auto& fh : interesting) { - if (!lastSize.has_value() || fh->data().origins.size() == *lastSize) { - group.emplace_back(fh->data().origins, fh->data().relations); - lastSize = fh->data().origins.size(); - } else { - if (!group.empty()) { - hyperedgesGrouped.push_back(group); - group.clear(); - } + if (lastSize.has_value() && fh->data().origins.size() != *lastSize && !group.empty()) { + hyperedgesGrouped.push_back(group); + group.clear(); } + group.emplace_back(fh->data().origins, fh->data().relations); + lastSize = fh->data().origins.size(); } if (!group.empty()) { hyperedgesGrouped.push_back(group); @@ -1060,9 +1057,9 @@ SimpleSetsPainting::SimpleSetsPainting(const DilatedPatternDrawing& dpd, const D void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { auto stackingOrder = m_dpd.totalStackingOrder(); // If there is a stacking order, draw the complete patterns stacked in that order - if (stackingOrder.has_value()) { + if (false) { +// if (stackingOrder.has_value()) { for (int i : *stackingOrder) { - bool debug = m_dpd.m_dilated[i].category() == 1; auto comps = connectedComponents(m_dpd.m_arr, [i](const FaceH& fh) { const auto& ors = fh->data().origins; return std::find(ors.begin(), ors.end(), i) != ors.end(); @@ -1100,7 +1097,7 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { auto it = std::find_if(mes.begin(), mes.end(), [&curr, &equal](const CSPolyline& pl) { return equal(pl.curves_begin()->source(), curr->source()->point()); }); - assert(it != mes.end()); + assert(it != mes.end()); // this assertion fails sometimes auto pl = *it; std::copy(pl.curves_begin(), pl.curves_end(), std::back_inserter(xm_curves)); doneInFace = true; diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp index e979f78f..6579da9d 100644 --- a/cartocrow/simplesets/partition_algorithm.cpp +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -66,38 +66,40 @@ bool do_intersect( }, cont1); } -Number intersectionDelay(const std::vector& points, const std::shared_ptr& p1, const std::shared_ptr& p2, - const std::shared_ptr& result, const GeneralSettings& gs, const PartitionSettings ps) { +Number intersectionDelay(const std::vector& points, const PolyPattern& p1, const PolyPattern& p2, + const PolyPattern& result, const GeneralSettings& gs, const PartitionSettings& ps) { // todo: check consistency with paper if (!ps.intersectionDelay) return 0; Number intersectionArea = 0; - auto& resultPts = result->catPoints(); - auto resultPoly = result->poly(); + auto& resultPts = result.catPoints(); + auto resultPoly = result.poly(); for (const auto& pt : points) { if (std::find(resultPts.begin(), resultPts.end(), pt) == resultPts.end() && - squared_distance(resultPoly, pt.point) < gs.dilationRadius() * 2) { - // note that Circle requires us to pass the squared radius + squared_distance(resultPoly, pt.point) < squared(2 * gs.dilationRadius())) { CSPolygon ptShape = Dilated(SinglePoint(pt), gs.dilationRadius()).m_contour; - CSPolygon rShape = Dilated(*result, gs.dilationRadius()).m_contour; + CSPolygon rShape = Dilated(result, gs.dilationRadius()).m_contour; std::vector inters; CGAL::intersection(rShape, ptShape, std::back_inserter(inters)); Number newArea = 0; for (const auto& gp : inters) { - newArea += area(gp); + newArea += abs(area(gp)); } inters.clear(); - CSPolygon p1Shape = Dilated(*p1, gs.dilationRadius()).m_contour; - CSPolygon p2Shape = Dilated(*p2, gs.dilationRadius()).m_contour; + CSPolygon p1Shape = Dilated(p1, gs.dilationRadius()).m_contour; + CSPolygon p2Shape = Dilated(p2, gs.dilationRadius()).m_contour; CGAL::intersection(p1Shape, ptShape, std::back_inserter(inters)); CGAL::intersection(p2Shape, ptShape, std::back_inserter(inters)); Number oldArea = 0; for (const auto& gp : inters) { - oldArea += area(gp); + oldArea += abs(area(gp)); } intersectionArea += newArea - oldArea; } } + if (intersectionArea <= 0) { + return 0; + } return sqrt(intersectionArea / M_PI); } @@ -150,8 +152,8 @@ partition(const std::vector& points, const GeneralSettings& gs, const if (ev.time > maxTime) break; if (!ev.final) { - // todo: compute intersection delay - ev.time += intersectionDelay(points, ev.p1, ev.p2, ev.result, gs, ps); + auto delay = intersectionDelay(points, *ev.p1, *ev.p2, *ev.result, gs, ps); + ev.time += delay; ev.final = true; events.push(ev); continue; diff --git a/cartocrow/simplesets/partition_algorithm.h b/cartocrow/simplesets/partition_algorithm.h index c8b1a70f..c879ca3f 100644 --- a/cartocrow/simplesets/partition_algorithm.h +++ b/cartocrow/simplesets/partition_algorithm.h @@ -20,6 +20,9 @@ struct PossibleMergeEvent { bool final; }; +Number intersectionDelay(const std::vector& points, const PolyPattern& p1, const PolyPattern& p2, + const PolyPattern& result, const GeneralSettings& gs, const PartitionSettings& ps); + std::vector, Partition>> partition(const std::vector& points, const GeneralSettings& gs, const PartitionSettings& ps, Number maxTime); } diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index c7e9ff9c..85ed4454 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -45,6 +45,7 @@ along with this program. If not, see . #include #include #include +#include #include namespace fs = std::filesystem; @@ -55,6 +56,13 @@ using namespace cartocrow::simplesets; SimpleSetsDemo::SimpleSetsDemo() { setWindowTitle("SimpleSets"); + m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc + // m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome + m_ps = PartitionSettings{true, true, true, true, 0.1}; + // m_ps = PartitionSettings{true, true, true, false, 0.5}; + m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; + m_cds = ComputeDrawingSettings{0.675}; + auto* dockWidget = new QDockWidget(); addDockWidget(Qt::RightDockWidgetArea, dockWidget); auto* vWidget = new QWidget(); @@ -70,6 +78,14 @@ SimpleSetsDemo::SimpleSetsDemo() { auto* fitToScreenButton = new QPushButton("Fit to screen"); vLayout->addWidget(fitToScreenButton); + auto* settingsLabel = new QLabel("

Settings

"); + vLayout->addWidget(settingsLabel); + auto* coverSlider = new QSlider(Qt::Orientation::Horizontal); + vLayout->addWidget(coverSlider); + coverSlider->setMinimum(0); + coverSlider->setMaximum(80); + coverSlider->setValue(47); + m_renderer = new GeometryWidget(); m_renderer->setDrawAxes(false); setCentralWidget(m_renderer); @@ -77,29 +93,31 @@ SimpleSetsDemo::SimpleSetsDemo() { m_renderer->setMinZoom(0.01); m_renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("/home/steven/Documents/cartocrow/data/diseasome.txt"); - -// m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc - m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome -// m_ps = PartitionSettings{true, true, true, true, 0.5}; todo: fix intersection delay crash - m_ps = PartitionSettings{true, true, true, false, 0.5}; - m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; - m_cds = ComputeDrawingSettings{0.675}; + std::filesystem::path filePath("/home/steven/Downloads/test/cartocrow/data/nyc.txt"); +// std::filesystem::path filePath("/home/steven/Downloads/test/cartocrow/data/diseasome.txt"); loadFile(filePath); - - compute(); + computePartitions(); + computeDrawing(coverSlider->value() / 10.0); + fitToScreen(); connect(fitToScreenButton, &QPushButton::clicked, [this]() { fitToScreen(); }); - connect(fileSelector, &QPushButton::clicked, [this, fileSelector]() { - QString startDir = "/home/steven/Documents/cartocrow/data/"; + connect(fileSelector, &QPushButton::clicked, [this, fileSelector, coverSlider]() { + QString startDir = "/home/steven/Downloads/test/cartocrow/data/"; std::filesystem::path filePath = QFileDialog::getOpenFileName(this, tr("Select SimpleSets input"), startDir).toStdString(); if (filePath == "") return; loadFile(filePath); + computePartitions(); + computeDrawing(coverSlider->value() / 10.0); + fitToScreen(); fileSelector->setText(QString::fromStdString(filePath.filename())); }); + connect(coverSlider, &QSlider::valueChanged, [this, coverSlider] { + double value = coverSlider->value() / 10.0; + computeDrawing(value); + }); fitToScreenButton->click(); } @@ -112,8 +130,6 @@ void SimpleSetsDemo::loadFile(const std::filesystem::path& filePath) { std::stringstream buffer; buffer << inputStream.rdbuf(); m_points = parseCatPoints(buffer.str()); - compute(); - fitToScreen(); } void SimpleSetsDemo::fitToScreen() { @@ -130,19 +146,24 @@ void SimpleSetsDemo::resizeEvent(QResizeEvent *event) { fitToScreen(); } -void SimpleSetsDemo::compute() { - auto partitionList = partition(m_points, m_gs, m_ps, 8 * m_gs.dilationRadius()); - -// Number cover = 4.7; // nyc - auto cover = 5.913; // diseasome +void SimpleSetsDemo::computePartitions(){ + m_partitions = partition(m_points, m_gs, m_ps, 8 * m_gs.dilationRadius()); +} +void SimpleSetsDemo::computeDrawing(double cover) { Partition* thePartition; - for (auto& [time, partition] : partitionList) { + bool found = false; + for (auto& [time, partition] : m_partitions) { if (time < cover * m_gs.dilationRadius()) { thePartition = &partition; + found = true; } } - m_partition = *thePartition; + if (found) { + m_partition = *thePartition; + } else { + m_partition = m_partitions.front().second; + } m_renderer->clear(); diff --git a/demos/simplesets/simplesets_demo.h b/demos/simplesets/simplesets_demo.h index ba63583b..cd23b91b 100644 --- a/demos/simplesets/simplesets_demo.h +++ b/demos/simplesets/simplesets_demo.h @@ -50,11 +50,13 @@ class SimpleSetsDemo : public QMainWindow { PartitionSettings m_ps; ComputeDrawingSettings m_cds; GeometryWidget* m_renderer; + std::vector, Partition>> m_partitions; std::shared_ptr> m_cc; void fitToScreen(); void loadFile(const std::filesystem::path& filePath); - void compute(); + void computePartitions(); + void computeDrawing(double cover); }; #endif //CARTOCROW_SIMPLESETS_DEMO_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c63df3fd..70afe478 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,6 +20,7 @@ set(TEST_SOURCES "cartocrow_test.cpp" "renderer/ipe_renderer.cpp" "simplification/vw_simplification.cpp" "simplesets/poly_line_gon_intersection.cpp" + "simplesets/partition_algorithm.cpp" ) add_executable(cartocrow_test cartocrow_test.cpp ${TEST_SOURCES}) diff --git a/test/simplesets/partition_algorithm.cpp b/test/simplesets/partition_algorithm.cpp new file mode 100644 index 00000000..5e5ffbaa --- /dev/null +++ b/test/simplesets/partition_algorithm.cpp @@ -0,0 +1,24 @@ +#include "../catch.hpp" +#include "cartocrow/simplesets/partition_algorithm.h" + +using namespace cartocrow; +using namespace cartocrow::simplesets; + +TEST_CASE("Intersection delay") { + GeneralSettings gs{1, 0, 0, 0}; + PartitionSettings ps{false, true, true, true, 0}; + std::vector points({ + {0, {0, 0}}, + {0, {0, 15}}, + {0, {15, 0}}, + {0, {15, 15}}, + {1, {7.5, 18}}, + {2, {-3, 15}}, + }); + + Island p1({points[0], points[1]}); + Island p2({points[2], points[3]}); + Island p3({points[0], points[1], points[2], points[3]}); + CHECK(intersectionDelay(p3.catPoints(), p1, p2, p3, gs, ps) == 0); + CHECK(abs(intersectionDelay(points, p1, p2, p3, gs, ps) - 3 / sqrt(2)) < M_EPSILON); +} \ No newline at end of file From f4bf79aedda69002704fff1082b6a518ad0285a2 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Mon, 30 Sep 2024 08:45:06 +0200 Subject: [PATCH 17/36] Add NYC dataset for testing purposes --- data/nyc.txt | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 data/nyc.txt diff --git a/data/nyc.txt b/data/nyc.txt new file mode 100644 index 00000000..c44d07af --- /dev/null +++ b/data/nyc.txt @@ -0,0 +1,96 @@ +0 39.211 556.19 +0 46.179 526.507 +0 61.915 524.604 +0 65.72 531.695 +0 80.419 528.928 +0 12.264 762.294 +0 41.919 776.673 +0 43.419 783.344 +0 65.187 738.367 +0 64.72 723.974 +0 73.372 708.833 +0 64.32 693.092 +0 77.498 693.492 +0 56.467 656.354 +0 115.645 682.323 +0 113.988 729.952 +0 137.154 742.503 +0 111.103 608.229 +0 143.582 593.054 +0 155.529 654.019 +0 174.164 638.711 +0 166.311 667.463 +0 178.723 694.75 +0 184.846 697.679 +0 195.096 702.338 +0 193.765 730.757 +0 174.597 745.798 +0 157.011 802.159 +0 191.335 580.202 +0 205.844 587.923 +0 221.684 576.475 +0 203.714 568.755 +0 190.803 560.236 +0 202.911 540.915 +0 231.618 535.208 +0 217.61 518.78 +2 230.821 566.587 +2 221.37 584.59 +2 228.558 582.327 +2 236.412 584.723 +2 238.142 602.693 +2 229.224 600.43 +2 223.101 610.813 +2 190.974 645.466 +2 253.43 715.841 +2 187.604 754.033 +2 199.062 797.392 +2 228.043 820.308 +2 67.299 827.721 +2 80.104 852.209 +2 180.258 578.719 +2 160.488 565.688 +2 136.224 539.403 +2 95.336 591.973 +2 97.134 561.42 +2 98.032 549.962 +1 107.019 543.391 +1 120.273 516.431 +1 187.447 511.489 +1 209.014 526.541 +1 87.698 532.382 +1 56.021 598.432 +1 101.851 602.251 +1 173.743 578.887 +1 203.398 595.062 +1 162.285 622.695 +1 163.858 628.761 +1 83.879 645.555 +1 76.015 676.558 +1 142.515 669.368 +1 136 683.185 +1 169.923 691.273 +1 124.317 685.881 +1 124.542 706.269 +1 150.378 723.792 +1 151.277 748.729 +1 109.265 754.346 +1 104.772 748.28 +1 88.372 734.127 +1 194.861 741.596 +1 177.786 784.057 +1 63.435 809.893 +1 52.271 835.573 +1 46.391 830.731 +1 41.204 826.753 +1 35.324 822.257 +1 12.324 835.919 +1 22.527 818.28 +1 43.798 804.1 +1 49.677 798.22 +1 36.016 779.5 +1 31.52 774.312 +1 51.752 738.343 +1 64.722 711.064 +1 55.557 702.59 +1 233.768 618.887 \ No newline at end of file From 32bdfab9c5168daf76c12f9ea685166301e4d13d Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Tue, 1 Oct 2024 17:02:26 +0200 Subject: [PATCH 18/36] Fix small bug in poly_line_gon_intersection & clean test --- .../helpers/poly_line_gon_intersection.h | 3 +- .../simplesets/poly_line_gon_intersection.cpp | 144 +++++++++--------- 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index 2627ed0a..1a5a0488 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -57,7 +57,6 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI bool onGonEdge = false; for (auto curveIt = arr.originating_curves_begin(edge); curveIt != arr.originating_curves_end(edge); ++curveIt) { - auto curve = *curveIt; Arr::Curve_handle ch = curveIt; if (ch == ogch) { onGonEdge = true; @@ -81,7 +80,7 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI auto ccb = fh->outer_ccb(); auto ccbIt = ccb; do { - if (!equals(ccb->source()->point(), ccb->curve().subcurves_begin()->source())) + if (!equals(ccbIt->source()->point(), ccbIt->curve().subcurves_begin()->source())) continue; // if *ccbIt lies on outer face. for (auto curveIt = arr.originating_curves_begin(ccbIt); diff --git a/test/simplesets/poly_line_gon_intersection.cpp b/test/simplesets/poly_line_gon_intersection.cpp index 55c1cef3..345e24d0 100644 --- a/test/simplesets/poly_line_gon_intersection.cpp +++ b/test/simplesets/poly_line_gon_intersection.cpp @@ -29,76 +29,82 @@ TEST_CASE("Multiple and connected parts of intersection") { CSPolygon disk = circleToCSPolygon({{0, 0}, 1}); auto result = intersection(polyline, disk, false); CHECK(result.size() == 2); - CHECK(result[0].size() == 2); - OneRootNumber negHalfSqrt2(0, -half, 2); - CHECK(result[0].curves_begin()->source() == OneRootPoint(negHalfSqrt2, negHalfSqrt2)); - CHECK((++result[0].curves_begin())->target() == OneRootPoint(0, -1)); - CHECK(result[1].size() == 1); - CHECK(result[1].curves_begin()->source() == OneRootPoint(1, 0)); - CHECK(result[1].curves_begin()->target() == OneRootPoint(half, 0)); -} - -TEST_CASE("Crashes") { - auto r = 5.204 * 3; - auto r_ = 5.204 * 3 * 0.675; - auto r2 = r * r; - auto r2_ = r_ * r_; - Circle c1({2597.9, -364.3}, r2, CGAL::CLOCKWISE); - Circle c2({2609.2, -342.6}, r2, CGAL::CLOCKWISE); - Circle c2_({2609.2, -342.6}, r2_, CGAL::CLOCKWISE); - - auto getIntersections = [](const Circle& one, const Circle& two) { - CSArrangement arr; - CGAL::insert(arr, one); - CGAL::insert(arr, two); - std::vector intersectionPoints; - for (auto vit = arr.vertices_begin(); vit != arr.vertices_end(); ++vit) { - if (vit->degree() == 4) { - intersectionPoints.push_back(vit->point()); - } - } - std::sort(intersectionPoints.begin(), intersectionPoints.end(), [](const OneRootPoint& p1, const OneRootPoint& p2) { return p1.x() < p2.x(); }); - assert(intersectionPoints.size() == 2); - return intersectionPoints; - }; - - auto isp12 = getIntersections(c1, c2); - - Curve_2 arc1(c1, isp12[0], isp12[1]); - Curve_2 arc2(c2, isp12[1], isp12[0]); - std::vector pgnArcs({arc1, arc2}); - - PolyCSTraits traits; - auto construct = traits.construct_curve_2_object(); - CSPolycurve pgnCurve(pgnArcs.begin(), pgnArcs.end()); - - CSArrangement arr; - CGAL::insert(arr, c1); - CGAL::insert(arr, c2); - CGAL::insert(arr, c2_); - - CSArrangement::Face_handle fh; - for (auto eit = arr.halfedges_begin(); eit != arr.halfedges_end(); ++eit) { - if (eit->source()->point() == isp12[0]) { - fh = eit->face(); - } - } - - std::vector xm_curves; - curvesToXMonotoneCurves(pgnArcs.begin(), pgnArcs.end(), std::back_inserter(xm_curves)); - CSPolygon pgn(xm_curves.begin(), xm_curves.end()); - - auto plnPoly = ccb_to_polygon(fh->outer_ccb()); - CSPolyline pln(plnPoly.curves_begin(), plnPoly.curves_end()); - intersection(pln, pgn, true); - - IpeRenderer ipeRenderer; - ipeRenderer.addPainting([&pgn, &pln](GeometryRenderer& renderer) { - auto path = renderPath(pgn); - renderer.draw(path); - renderer.draw(renderPath(pln)); + auto r1 = std::find_if(result.begin(), result.end(), [half](const CSPolyline& pl) { + OneRootNumber negHalfSqrt2(0, -half, 2); + return pl.curves_begin()->source() == OneRootPoint(negHalfSqrt2, negHalfSqrt2); + }); + CHECK(r1 != result.end()); + CHECK(r1->size() == 2); + CHECK((++(r1->curves_begin()))->target() == OneRootPoint(0, -1)); + auto r2 = std::find_if(result.begin(), result.end(), [half](const CSPolyline& pl) { + return pl.curves_begin()->source() == OneRootPoint(1, 0); }); - ipeRenderer.save("/home/steven/Documents/cartocrow/crash.ipe"); + CHECK(r2 != result.end()); + CHECK(r2->size() == 1); + CHECK(r2->curves_begin()->target() == OneRootPoint(half, 0)); } +//TEST_CASE("Crashes") { +// auto r = 5.204 * 3; +// auto r_ = 5.204 * 3 * 0.675; +// auto r2 = r * r; +// auto r2_ = r_ * r_; +// Circle c1({2597.9, -364.3}, r2, CGAL::CLOCKWISE); +// Circle c2({2609.2, -342.6}, r2, CGAL::CLOCKWISE); +// Circle c2_({2609.2, -342.6}, r2_, CGAL::CLOCKWISE); +// +// auto getIntersections = [](const Circle& one, const Circle& two) { +// CSArrangement arr; +// CGAL::insert(arr, one); +// CGAL::insert(arr, two); +// std::vector intersectionPoints; +// for (auto vit = arr.vertices_begin(); vit != arr.vertices_end(); ++vit) { +// if (vit->degree() == 4) { +// intersectionPoints.push_back(vit->point()); +// } +// } +// std::sort(intersectionPoints.begin(), intersectionPoints.end(), [](const OneRootPoint& p1, const OneRootPoint& p2) { return p1.x() < p2.x(); }); +// assert(intersectionPoints.size() == 2); +// return intersectionPoints; +// }; +// +// auto isp12 = getIntersections(c1, c2); +// +// Curve_2 arc1(c1, isp12[0], isp12[1]); +// Curve_2 arc2(c2, isp12[1], isp12[0]); +// std::vector pgnArcs({arc1, arc2}); +// +// PolyCSTraits traits; +// auto construct = traits.construct_curve_2_object(); +// CSPolycurve pgnCurve(pgnArcs.begin(), pgnArcs.end()); +// +// CSArrangement arr; +// CGAL::insert(arr, c1); +// CGAL::insert(arr, c2); +// CGAL::insert(arr, c2_); +// +// CSArrangement::Face_handle fh; +// for (auto eit = arr.halfedges_begin(); eit != arr.halfedges_end(); ++eit) { +// if (eit->source()->point() == isp12[0]) { +// fh = eit->face(); +// } +// } +// +// std::vector xm_curves; +// curvesToXMonotoneCurves(pgnArcs.begin(), pgnArcs.end(), std::back_inserter(xm_curves)); +// CSPolygon pgn(xm_curves.begin(), xm_curves.end()); +// +// auto plnPoly = ccb_to_polygon(fh->outer_ccb()); +// CSPolyline pln(plnPoly.curves_begin(), plnPoly.curves_end()); +// intersection(pln, pgn, true); +// +// IpeRenderer ipeRenderer; +// ipeRenderer.addPainting([&pgn, &pln](GeometryRenderer& renderer) { +// auto path = renderPath(pgn); +// renderer.draw(path); +// renderer.draw(renderPath(pln)); +// }); +// ipeRenderer.save("crash.ipe"); +//} + // todo: test difference, overlapping edges, and holes From e622395e6a4175a6630229538f8c14501406f4ab Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Tue, 1 Oct 2024 17:07:05 +0200 Subject: [PATCH 19/36] Use relative file path Note: for this to work in CLion, the working directory needs to be set to the project directory in the run configuration. --- demos/simplesets/simplesets_demo.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 85ed4454..a3b06ace 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -93,8 +93,7 @@ SimpleSetsDemo::SimpleSetsDemo() { m_renderer->setMinZoom(0.01); m_renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("/home/steven/Downloads/test/cartocrow/data/nyc.txt"); -// std::filesystem::path filePath("/home/steven/Downloads/test/cartocrow/data/diseasome.txt"); + std::filesystem::path filePath("data/nyc.txt"); loadFile(filePath); computePartitions(); @@ -105,7 +104,7 @@ SimpleSetsDemo::SimpleSetsDemo() { fitToScreen(); }); connect(fileSelector, &QPushButton::clicked, [this, fileSelector, coverSlider]() { - QString startDir = "/home/steven/Downloads/test/cartocrow/data/"; + QString startDir = "data/"; std::filesystem::path filePath = QFileDialog::getOpenFileName(this, tr("Select SimpleSets input"), startDir).toStdString(); if (filePath == "") return; loadFile(filePath); From fbd57804dc0faeb6e944b5cc3a5e4403deacb2e4 Mon Sep 17 00:00:00 2001 From: Willem Sonke Date: Wed, 2 Oct 2024 12:41:17 +0200 Subject: [PATCH 20/36] Fix missing typename which annoys clang 14 --- cartocrow/simplesets/general_polyline.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cartocrow/simplesets/general_polyline.h b/cartocrow/simplesets/general_polyline.h index e8f33249..33f06e90 100644 --- a/cartocrow/simplesets/general_polyline.h +++ b/cartocrow/simplesets/general_polyline.h @@ -6,8 +6,8 @@ template class General_polyline_2 { public: - typedef std::vector::iterator Curve_iterator; - typedef std::vector::const_iterator Curve_const_iterator; + typedef typename std::vector::iterator Curve_iterator; + typedef typename std::vector::const_iterator Curve_const_iterator; template General_polyline_2(InputIterator begin, InputIterator end) { From 2d3674aaccf9782813a1a4fa92ed5051687decbf Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Wed, 2 Oct 2024 13:36:43 +0200 Subject: [PATCH 21/36] Satisfy clang++: add constructors and change <=> to == --- cartocrow/simplesets/cat_point.h | 3 ++- cartocrow/simplesets/drawing_algorithm.cpp | 2 +- cartocrow/simplesets/drawing_algorithm.h | 7 +++++-- cartocrow/simplesets/general_polyline.h | 4 ++-- cartocrow/simplesets/partition_algorithm.cpp | 6 +++--- cartocrow/simplesets/patterns/bank.h | 2 ++ 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/cartocrow/simplesets/cat_point.h b/cartocrow/simplesets/cat_point.h index ab89236f..7776e5ec 100644 --- a/cartocrow/simplesets/cat_point.h +++ b/cartocrow/simplesets/cat_point.h @@ -8,7 +8,8 @@ namespace cartocrow::simplesets { struct CatPoint { unsigned int category; Point point; - auto operator<=>(const CatPoint&) const = default; + CatPoint(unsigned int category, Point point) : category(category), point(point) {}; + bool operator==(const CatPoint&) const = default; }; std::ostream& operator<<(std::ostream& os, CatPoint const& catPoint); diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 6e3be0a4..c69f79e9 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -275,7 +275,7 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G }); for (auto eit = m_arr.induced_edges_begin(cit); eit != m_arr.induced_edges_end(cit); ++eit) { DilatedPatternArrangement::Halfedge_handle eh = *eit; - HalfEdgeData data(curve_data->second); + HalfEdgeData data{curve_data->second}; eh->set_data(data); eh->twin()->set_data(data); } diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 8a25159d..30353013 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -24,16 +24,19 @@ struct Relation { int right; Order preference; Order ordering; + Relation(int left, int right, Order preference, Order ordering) : + left(left), right(right), preference(preference), ordering(ordering) {} }; bool operator==(const Relation& lhs, const Relation& rhs); std::ostream& operator<<(std::ostream& out, const Relation& r); -class Hyperedge { - public: +struct Hyperedge { std::vector origins; std::vector> relations; + Hyperedge(std::vector origins, std::vector> relations) : + origins(origins), relations(relations) {}; }; std::optional> getRelationOrder(const Hyperedge& e); diff --git a/cartocrow/simplesets/general_polyline.h b/cartocrow/simplesets/general_polyline.h index e8f33249..33f06e90 100644 --- a/cartocrow/simplesets/general_polyline.h +++ b/cartocrow/simplesets/general_polyline.h @@ -6,8 +6,8 @@ template class General_polyline_2 { public: - typedef std::vector::iterator Curve_iterator; - typedef std::vector::const_iterator Curve_const_iterator; + typedef typename std::vector::iterator Curve_iterator; + typedef typename std::vector::const_iterator Curve_const_iterator; template General_polyline_2(InputIterator begin, InputIterator end) { diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp index 6579da9d..729e8fb9 100644 --- a/cartocrow/simplesets/partition_algorithm.cpp +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -140,7 +140,7 @@ partition(const std::vector& points, const GeneralSettings& gs, const } if (tooClose) continue; - PossibleMergeEvent event(newPattern->coverRadius(), partition[i], partition[j], newPattern, false); + PossibleMergeEvent event{newPattern->coverRadius(), partition[i], partition[j], newPattern, false}; events.push(event); } } @@ -232,7 +232,7 @@ partition(const std::vector& points, const GeneralSettings& gs, const Number regDelay = !ps.regularityDelay ? 0 : newIsland->coverRadius() - std::max(pattern->coverRadius(), ev.result->coverRadius()); Number eventTime = newIsland->coverRadius() + regDelay; - PossibleMergeEvent newEvent(eventTime, ev.result, pattern, newIsland, false); + PossibleMergeEvent newEvent{eventTime, ev.result, pattern, newIsland, false}; if (eventTime <= maxTime) { events.push(newEvent); } @@ -273,7 +273,7 @@ partition(const std::vector& points, const GeneralSettings& gs, const if (!b->isValid(gs)) continue; Number regDelay = !ps.regularityDelay ? 0 : b->coverRadius() - std::max(ev.result->coverRadius(), pattern->coverRadius()); Number eventTime = b->coverRadius() + regDelay; - PossibleMergeEvent newEvent(eventTime * 1, ev.result, pattern, b, false); + PossibleMergeEvent newEvent{eventTime * 1, ev.result, pattern, b, false}; if (eventTime <= maxTime) { events.push(newEvent); } diff --git a/cartocrow/simplesets/patterns/bank.h b/cartocrow/simplesets/patterns/bank.h index 28687f8c..0acbf464 100644 --- a/cartocrow/simplesets/patterns/bank.h +++ b/cartocrow/simplesets/patterns/bank.h @@ -11,6 +11,8 @@ struct Bend { Number totalAngle; int startIndex; int endIndex; + Bend(CGAL::Orientation orientation, Number maxAngle, Number totalAngle, int startIndex, int endIndex) : + orientation(orientation), maxAngle(maxAngle), totalAngle(totalAngle), startIndex(startIndex), endIndex(endIndex){} }; class Bank : public PolyPattern { From 86fd6e9bcaaa481cd09507ef9373ff3aa1dc41fc Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Wed, 2 Oct 2024 14:22:47 +0200 Subject: [PATCH 22/36] Deal with collinear points in an island --- cartocrow/simplesets/patterns/island.cpp | 21 ++++++++++++++++++--- cartocrow/simplesets/patterns/island.h | 2 +- test/CMakeLists.txt | 1 + test/simplesets/collinear_island.cpp | 11 +++++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 test/simplesets/collinear_island.cpp diff --git a/cartocrow/simplesets/patterns/island.cpp b/cartocrow/simplesets/patterns/island.cpp index 13f013f9..ba7fbbf4 100644 --- a/cartocrow/simplesets/patterns/island.cpp +++ b/cartocrow/simplesets/patterns/island.cpp @@ -1,4 +1,5 @@ #include "island.h" +#include "bank.h" #include "cartocrow/simplesets/helpers/cropped_voronoi.h" #include "cartocrow/simplesets/helpers/point_voronoi_helpers.h" #include @@ -62,13 +63,27 @@ Island::Island(std::vector catPoints): m_catPoints(std::move(catPoints return cp.point; }); + if (m_catPoints.size() >= 3) { + bool collinear = true; + for (int i = 0; i < m_catPoints.size() - 2; ++i) { + if (!CGAL::collinear(m_catPoints[i].point, m_catPoints[i+1].point, m_catPoints[i+2].point)) { + collinear = false; + } + } + if (collinear) { + Bank bank(m_catPoints); + m_coverRadius = bank.coverRadius(); + m_poly = bank.poly(); + return; + } + } + m_coverRadius = coverRadiusOfPoints(m_points); - m_polygon = convexHull(m_points); + m_poly = convexHull(m_points); } std::variant, Polygon> Island::poly() const { - // todo? return (and store) polyline when m_points are collinear? - return m_polygon; + return m_poly; } const std::vector& Island::catPoints() const { diff --git a/cartocrow/simplesets/patterns/island.h b/cartocrow/simplesets/patterns/island.h index 061ff00c..3668e4c3 100644 --- a/cartocrow/simplesets/patterns/island.h +++ b/cartocrow/simplesets/patterns/island.h @@ -20,7 +20,7 @@ class Island : public PolyPattern { std::vector m_catPoints; std::vector> m_points; Number m_coverRadius; - Polygon m_polygon; + std::variant, Polygon> m_poly; }; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 70afe478..7f5c1bfb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,6 +21,7 @@ set(TEST_SOURCES "cartocrow_test.cpp" "simplification/vw_simplification.cpp" "simplesets/poly_line_gon_intersection.cpp" "simplesets/partition_algorithm.cpp" + "simplesets/collinear_island.cpp" ) add_executable(cartocrow_test cartocrow_test.cpp ${TEST_SOURCES}) diff --git a/test/simplesets/collinear_island.cpp b/test/simplesets/collinear_island.cpp new file mode 100644 index 00000000..81b2ae8d --- /dev/null +++ b/test/simplesets/collinear_island.cpp @@ -0,0 +1,11 @@ +#include "../catch.hpp" +#include "cartocrow/simplesets/patterns/island.h" + +using namespace cartocrow; +using namespace cartocrow::simplesets; + +TEST_CASE("Collinear island") { + Island island({{0, {0, 0}}, {0, {1, 0}}, {0, {2, 0}}, {0, {3, 0}}}); + CHECK(island.coverRadius() == 0.5); + CHECK(std::holds_alternative>(island.poly())); +} \ No newline at end of file From 0ad729e55b7476f453064905592a1f284e4ad52f Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Wed, 2 Oct 2024 14:23:02 +0200 Subject: [PATCH 23/36] Comment crashing spiral tree test case --- .../spiral_tree_obstructed_algorithm.cpp | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/test/flow_map/spiral_tree_obstructed_algorithm.cpp b/test/flow_map/spiral_tree_obstructed_algorithm.cpp index d017df89..ae0f4172 100644 --- a/test/flow_map/spiral_tree_obstructed_algorithm.cpp +++ b/test/flow_map/spiral_tree_obstructed_algorithm.cpp @@ -12,32 +12,32 @@ using namespace cartocrow; using namespace cartocrow::flow_map; -TEST_CASE("Computing a spiral tree with one node", "[!mayfail]") { - auto tree = std::make_shared(Point(0, 0), 0.5061454830783556); - tree->addPlace("p1", Point(0, 100), 1); - CHECK(tree->nodes().size() == 2); - - int expectedNodeCount; - SECTION("without obstacle") { - expectedNodeCount = 2; - } - - SECTION("with obstacle") { - Polygon obstacle; - obstacle.push_back(Point(-10, 50)); - obstacle.push_back(Point(0, 25)); - obstacle.push_back(Point(10, 50)); - tree->addObstacle(obstacle); - expectedNodeCount = 4; - } - - ReachableRegionAlgorithm algorithm(tree); - auto reachable = algorithm.run(); - SpiralTreeObstructedAlgorithm algorithm2(tree, reachable); - algorithm2.run(); - - cartocrow::renderer::IpeRenderer renderer(algorithm2.debugPainting()); - renderer.save("/tmp/test.ipe"); - - REQUIRE(tree->nodes().size() == expectedNodeCount); -} +//TEST_CASE("Computing a spiral tree with one node", "[!mayfail]") { +// auto tree = std::make_shared(Point(0, 0), 0.5061454830783556); +// tree->addPlace("p1", Point(0, 100), 1); +// CHECK(tree->nodes().size() == 2); +// +// int expectedNodeCount; +// SECTION("without obstacle") { +// expectedNodeCount = 2; +// } +// +// SECTION("with obstacle") { +// Polygon obstacle; +// obstacle.push_back(Point(-10, 50)); +// obstacle.push_back(Point(0, 25)); +// obstacle.push_back(Point(10, 50)); +// tree->addObstacle(obstacle); +// expectedNodeCount = 4; +// } +// +// ReachableRegionAlgorithm algorithm(tree); +// auto reachable = algorithm.run(); +// SpiralTreeObstructedAlgorithm algorithm2(tree, reachable); +// algorithm2.run(); +// +// cartocrow::renderer::IpeRenderer renderer(algorithm2.debugPainting()); +// renderer.save("/tmp/test.ipe"); +// +// REQUIRE(tree->nodes().size() == expectedNodeCount); +//} From f2ab5ddf41fb3be93502581b1f85ee633b3815d2 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Wed, 2 Oct 2024 16:21:33 +0200 Subject: [PATCH 24/36] Initialize stroke opacity; fixes crashing tests --- cartocrow/renderer/ipe_renderer.cpp | 1 + .../spiral_tree_obstructed_algorithm.cpp | 58 +++++++++---------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/cartocrow/renderer/ipe_renderer.cpp b/cartocrow/renderer/ipe_renderer.cpp index ac6ca06d..e289e5eb 100644 --- a/cartocrow/renderer/ipe_renderer.cpp +++ b/cartocrow/renderer/ipe_renderer.cpp @@ -75,6 +75,7 @@ void IpeRenderer::save(const std::filesystem::path& file) { m_alphaSheet->setName("alpha-values"); document.cascade()->insert(2, m_alphaSheet); setFillOpacity(255); // add default alpha to style sheet + setStrokeOpacity(255); // add default alpha to style sheet m_page = new ipe::Page(); document.push_back(m_page); diff --git a/test/flow_map/spiral_tree_obstructed_algorithm.cpp b/test/flow_map/spiral_tree_obstructed_algorithm.cpp index ae0f4172..d017df89 100644 --- a/test/flow_map/spiral_tree_obstructed_algorithm.cpp +++ b/test/flow_map/spiral_tree_obstructed_algorithm.cpp @@ -12,32 +12,32 @@ using namespace cartocrow; using namespace cartocrow::flow_map; -//TEST_CASE("Computing a spiral tree with one node", "[!mayfail]") { -// auto tree = std::make_shared(Point(0, 0), 0.5061454830783556); -// tree->addPlace("p1", Point(0, 100), 1); -// CHECK(tree->nodes().size() == 2); -// -// int expectedNodeCount; -// SECTION("without obstacle") { -// expectedNodeCount = 2; -// } -// -// SECTION("with obstacle") { -// Polygon obstacle; -// obstacle.push_back(Point(-10, 50)); -// obstacle.push_back(Point(0, 25)); -// obstacle.push_back(Point(10, 50)); -// tree->addObstacle(obstacle); -// expectedNodeCount = 4; -// } -// -// ReachableRegionAlgorithm algorithm(tree); -// auto reachable = algorithm.run(); -// SpiralTreeObstructedAlgorithm algorithm2(tree, reachable); -// algorithm2.run(); -// -// cartocrow::renderer::IpeRenderer renderer(algorithm2.debugPainting()); -// renderer.save("/tmp/test.ipe"); -// -// REQUIRE(tree->nodes().size() == expectedNodeCount); -//} +TEST_CASE("Computing a spiral tree with one node", "[!mayfail]") { + auto tree = std::make_shared(Point(0, 0), 0.5061454830783556); + tree->addPlace("p1", Point(0, 100), 1); + CHECK(tree->nodes().size() == 2); + + int expectedNodeCount; + SECTION("without obstacle") { + expectedNodeCount = 2; + } + + SECTION("with obstacle") { + Polygon obstacle; + obstacle.push_back(Point(-10, 50)); + obstacle.push_back(Point(0, 25)); + obstacle.push_back(Point(10, 50)); + tree->addObstacle(obstacle); + expectedNodeCount = 4; + } + + ReachableRegionAlgorithm algorithm(tree); + auto reachable = algorithm.run(); + SpiralTreeObstructedAlgorithm algorithm2(tree, reachable); + algorithm2.run(); + + cartocrow::renderer::IpeRenderer renderer(algorithm2.debugPainting()); + renderer.save("/tmp/test.ipe"); + + REQUIRE(tree->nodes().size() == expectedNodeCount); +} From ee81e1970d1d7418570c1e1b6a3a79305a497bed Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Thu, 3 Oct 2024 15:24:39 +0200 Subject: [PATCH 25/36] Work around CGAL bug --- .../helpers/poly_line_gon_intersection.h | 183 ++++++++++++++++-- .../simplesets/poly_line_gon_intersection.cpp | 133 +++++++------ 2 files changed, 233 insertions(+), 83 deletions(-) diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index 1a5a0488..08a01732 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -5,6 +5,8 @@ #include "cs_polyline_helpers.h" #include "cs_polygon_helpers.h" #include +#include +#include namespace cartocrow::simplesets { // todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. @@ -15,6 +17,167 @@ std::vector difference(const CSPolyline& line, const CSPolygonWithHo template void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputIterator out, bool difference, bool keepOverlap) { + CSTraits traits; + auto equals = traits.equal_2_object(); + enum Origin { + Polyline, + PolygonOuter, + PolygonHole, + }; + struct HalfEdgeData { + std::vector origins; + }; + using Arr = CGAL::Arrangement_2>; + using FH = typename Arr::Face_handle; + using HeH = typename Arr::Halfedge_handle; + using VH = typename Arr::Vertex_handle; + + Arr arr; + class Observer : public CGAL::Arr_observer { + public: + Observer(Arr& arr) : CGAL::Arr_observer(arr) {} + + virtual void after_create_edge(HeH e) { + e->data().origins.push_back(currentOrigin); + e->twin()->data().origins.push_back(currentOrigin); + } + + virtual void before_split_edge(HeH e, VH v, const X_monotone_curve_2& c1, const X_monotone_curve_2& c2) { + beforeSplitData = e->data(); + } + + virtual void after_split_edge(HeH e1, HeH e2) { + e1->set_data(beforeSplitData); + e1->twin()->set_data(beforeSplitData); + e2->twin()->set_data(beforeSplitData); + e2->set_data(beforeSplitData); + } + + virtual void after_modify_edge(HeH e) { + e->data().origins.push_back(currentOrigin); + e->twin()->data().origins.push_back(currentOrigin); + } + + Origin currentOrigin; + + private: + HalfEdgeData beforeSplitData; + }; + + Observer observer(arr); + + observer.currentOrigin = Origin::Polyline; + for (auto cit = line.curves_begin(); cit != line.curves_end(); ++cit) { + CGAL::insert(arr, *cit); + } + observer.currentOrigin = Origin::PolygonOuter; + for (auto cit = gon.outer_boundary().curves_begin(); cit != gon.outer_boundary().curves_end(); ++cit) { + CGAL::insert(arr, *cit); + } + observer.currentOrigin = Origin::PolygonHole; + for (auto hit = gon.holes_begin(); hit != gon.holes_end(); ++hit) { + for (auto cit = hit->curves_begin(); cit != hit->curves_end(); ++cit) { + CGAL::insert(arr, *cit); + } + } + + std::vector line_edges_keep; + for (auto eit = arr.edges_begin(); eit != arr.edges_end(); ++eit) { + HeH edge = eit; + + bool onGonEdge = false; + bool onPolyline = false; + for (auto origin : edge->data().origins) { + if (origin == Origin::Polyline) { + onPolyline = true; + } + if (origin == Origin::PolygonOuter || origin == Origin::PolygonHole) { + onGonEdge = true; + } + } + if (!onPolyline) { + continue; + } + bool liesInGon = false; + if (onGonEdge) { + if (keepOverlap) { + liesInGon = true; + } + } else { + for (auto fh : {edge->face(), edge->twin()->face()}) { + if (!fh->has_outer_ccb()) { + continue; + } + auto ccb = fh->outer_ccb(); + auto ccbIt = ccb; + do { + if (!equals(ccbIt->source()->point(), ccbIt->curve().source())) + continue; + // if *ccbIt lies on outer face. + for (auto origin : ccbIt->data().origins) { + if (origin == Origin::PolygonOuter) { + liesInGon = true; + break; + } + } + } while (++ccbIt != ccb); + if (liesInGon) + break; + } + } + + if (!difference && liesInGon) { + if (equals(edge->source()->point(), edge->curve().source())) { + line_edges_keep.push_back(edge); + } else { + line_edges_keep.push_back(edge->twin()); + } + } + if (difference && !liesInGon) { + if (equals(edge->source()->point(), edge->curve().source())) { + line_edges_keep.push_back(edge); + } else { + line_edges_keep.push_back(edge->twin()); + } + } + } + + auto originatesFromPolyline = [&arr, &equals](HeH h) { + for (auto origin : h->data().origins) { + if (origin == Origin::Polyline && equals(h->source()->point(), h->curve().source())) { + return true; + } + } + return false; + }; + + while (!line_edges_keep.empty()) { + // Find first edge on connected component of polyline (in the intersection with polygon) + auto start = line_edges_keep.front(); + auto curr = start; + while (originatesFromPolyline(curr->prev())) { + curr = curr->prev(); + + if (curr == start) { + break; + } + } + std::vector xmcs; + auto last_it = line_edges_keep.end(); + do { + last_it = std::remove(line_edges_keep.begin(), last_it, curr); + xmcs.push_back(curr->curve()); + curr = curr->next(); + } while (originatesFromPolyline(curr)); + line_edges_keep.erase(last_it, line_edges_keep.end()); + + ++out = CSPolyline(xmcs.begin(), xmcs.end()); + } +} + +// May crash due to CGAL bug: https://github.com/CGAL/cgal/issues/8468 +template +void intersectionCrashes(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputIterator out, bool difference, bool keepOverlap) { PolyCSTraits traits; auto equals = traits.equal_2_object(); using Arr = CGAL::Arrangement_with_history_2; @@ -22,26 +185,6 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI auto linePolycurve = arrPolycurveFromCSPolyline(line); auto outerGonPolycurve = arrPolycurveFromCSPolygon(gon.outer_boundary()); -// std::cout << "!" << std::endl; -// for (auto cit = outerGonPolycurve.subcurves_begin(); cit != outerGonPolycurve.subcurves_end(); ++cit) { -// if (cit->is_circular()) { -// std::cout << cit->supporting_circle().center() << " " << cit->supporting_circle().squared_radius() << std::endl; -// } -// if (cit->is_linear()) { -// std::cout << cit->supporting_line() << std::endl; -// } -// std::cout << cit->source() << " -> " << cit->target() << std::endl; -// } -// for (auto cit = linePolycurve.subcurves_begin(); cit != linePolycurve.subcurves_end(); ++cit) { -// if (cit->is_circular()) { -// std::cout << cit->supporting_circle().center() << " " << cit->supporting_circle().squared_radius() << std::endl; -// } -// if (cit->is_linear()) { -// std::cout << cit->supporting_line() << std::endl; -// } -// std::cout << cit->source() << " -> " << cit->target() << std::endl; -// } - auto ogch = CGAL::insert(arr, outerGonPolycurve); auto lch = CGAL::insert(arr, linePolycurve); std::vector hgchs; diff --git a/test/simplesets/poly_line_gon_intersection.cpp b/test/simplesets/poly_line_gon_intersection.cpp index 345e24d0..0abec824 100644 --- a/test/simplesets/poly_line_gon_intersection.cpp +++ b/test/simplesets/poly_line_gon_intersection.cpp @@ -20,6 +20,20 @@ TEST_CASE("Intersection lies in polygon and has correct orientation") { CHECK(result[0].curves_begin()->target() == OneRootPoint(1, 0)); } +TEST_CASE("Boundary overlap") { + X_monotone_curve_2 xm_curve({-2, 0}, {2, 0}); + std::vector xm_curves{xm_curve}; + CSPolyline polyline(xm_curves.begin(), xm_curves.end()); + std::vector xm_curves_pgn{{{-4, 0}, {4, 0}}, {{4, 0}, {4, 2}}, {{4, 2}, {-4, 2}}, {{-4, 2}, {-4, 0}}}; + CSPolygon polygon(xm_curves_pgn.begin(), xm_curves_pgn.end()); + auto result1 = intersection(polyline, polygon, false); + auto result2 = intersection(polyline, polygon, true); + CHECK(result1.empty()); + CHECK(!result2.empty()); + CHECK(result2[0].curves_begin()->source() == OneRootPoint(-2, 0)); + CHECK(result2[0].curves_begin()->target() == OneRootPoint(2, 0)); +} + TEST_CASE("Multiple and connected parts of intersection") { Number half = 1; half /= 2; @@ -44,67 +58,60 @@ TEST_CASE("Multiple and connected parts of intersection") { CHECK(r2->curves_begin()->target() == OneRootPoint(half, 0)); } -//TEST_CASE("Crashes") { -// auto r = 5.204 * 3; -// auto r_ = 5.204 * 3 * 0.675; -// auto r2 = r * r; -// auto r2_ = r_ * r_; -// Circle c1({2597.9, -364.3}, r2, CGAL::CLOCKWISE); -// Circle c2({2609.2, -342.6}, r2, CGAL::CLOCKWISE); -// Circle c2_({2609.2, -342.6}, r2_, CGAL::CLOCKWISE); -// -// auto getIntersections = [](const Circle& one, const Circle& two) { -// CSArrangement arr; -// CGAL::insert(arr, one); -// CGAL::insert(arr, two); -// std::vector intersectionPoints; -// for (auto vit = arr.vertices_begin(); vit != arr.vertices_end(); ++vit) { -// if (vit->degree() == 4) { -// intersectionPoints.push_back(vit->point()); -// } -// } -// std::sort(intersectionPoints.begin(), intersectionPoints.end(), [](const OneRootPoint& p1, const OneRootPoint& p2) { return p1.x() < p2.x(); }); -// assert(intersectionPoints.size() == 2); -// return intersectionPoints; -// }; -// -// auto isp12 = getIntersections(c1, c2); -// -// Curve_2 arc1(c1, isp12[0], isp12[1]); -// Curve_2 arc2(c2, isp12[1], isp12[0]); -// std::vector pgnArcs({arc1, arc2}); -// -// PolyCSTraits traits; -// auto construct = traits.construct_curve_2_object(); -// CSPolycurve pgnCurve(pgnArcs.begin(), pgnArcs.end()); -// -// CSArrangement arr; -// CGAL::insert(arr, c1); -// CGAL::insert(arr, c2); -// CGAL::insert(arr, c2_); -// -// CSArrangement::Face_handle fh; -// for (auto eit = arr.halfedges_begin(); eit != arr.halfedges_end(); ++eit) { -// if (eit->source()->point() == isp12[0]) { -// fh = eit->face(); -// } -// } -// -// std::vector xm_curves; -// curvesToXMonotoneCurves(pgnArcs.begin(), pgnArcs.end(), std::back_inserter(xm_curves)); -// CSPolygon pgn(xm_curves.begin(), xm_curves.end()); -// -// auto plnPoly = ccb_to_polygon(fh->outer_ccb()); -// CSPolyline pln(plnPoly.curves_begin(), plnPoly.curves_end()); -// intersection(pln, pgn, true); -// -// IpeRenderer ipeRenderer; -// ipeRenderer.addPainting([&pgn, &pln](GeometryRenderer& renderer) { -// auto path = renderPath(pgn); -// renderer.draw(path); -// renderer.draw(renderPath(pln)); -// }); -// ipeRenderer.save("crash.ipe"); -//} +// Test case in https://github.com/CGAL/cgal/issues/8468 +TEST_CASE("Poly-circular-arcs that partially overlap") { + auto r = 5.204 * 3; + auto r_ = 5.204 * 3 * 0.675; + auto r2 = r * r; + auto r2_ = r_ * r_; + Circle c1({2597.9, -364.3}, r2, CGAL::CLOCKWISE); + Circle c2({2609.2, -342.6}, r2, CGAL::CLOCKWISE); + Circle c2_({2609.2, -342.6}, r2_, CGAL::CLOCKWISE); + + auto getIntersections = [](const Circle& one, const Circle& two) { + CSArrangement arr; + CGAL::insert(arr, one); + CGAL::insert(arr, two); + std::vector intersectionPoints; + for (auto vit = arr.vertices_begin(); vit != arr.vertices_end(); ++vit) { + if (vit->degree() == 4) { + intersectionPoints.push_back(vit->point()); + } + } + std::sort(intersectionPoints.begin(), intersectionPoints.end(), [](const OneRootPoint& p1, const OneRootPoint& p2) { return p1.x() < p2.x(); }); + assert(intersectionPoints.size() == 2); + return intersectionPoints; + }; + + auto isp12 = getIntersections(c1, c2); + + Curve_2 arc1(c1, isp12[0], isp12[1]); + Curve_2 arc2(c2, isp12[1], isp12[0]); + std::vector pgnArcs({arc1, arc2}); + + PolyCSTraits traits; + auto construct = traits.construct_curve_2_object(); + CSPolycurve pgnCurve(pgnArcs.begin(), pgnArcs.end()); + + CSArrangement arr; + CGAL::insert(arr, c1); + CGAL::insert(arr, c2); + CGAL::insert(arr, c2_); + + CSArrangement::Face_handle fh; + for (auto eit = arr.halfedges_begin(); eit != arr.halfedges_end(); ++eit) { + if (eit->source()->point() == isp12[0]) { + fh = eit->face(); + } + } + + std::vector xm_curves; + curvesToXMonotoneCurves(pgnArcs.begin(), pgnArcs.end(), std::back_inserter(xm_curves)); + CSPolygon pgn(xm_curves.begin(), xm_curves.end()); + + auto plnPoly = ccb_to_polygon(fh->outer_ccb()); + CSPolyline pln(plnPoly.curves_begin(), plnPoly.curves_end()); + intersection(pln, pgn, true); +} -// todo: test difference, overlapping edges, and holes +// todo: test difference and holes From f51d5aa988670baa6303b5ce0091bd588b40889f Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Thu, 3 Oct 2024 15:34:16 +0200 Subject: [PATCH 26/36] Make circle convex hull demo --- demos/isoline_simplification/CMakeLists.txt | 2 +- demos/simplesets/CMakeLists.txt | 5 ++-- demos/simplesets/helpers/CMakeLists.txt | 18 +++++++++++++ .../{ => helpers}/circle_convex_hull.cpp | 20 +++++++------- .../{ => helpers}/circle_convex_hull.h | 0 demos/simplesets/simplesets_demo.cpp | 27 +++++++++++++++---- 6 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 demos/simplesets/helpers/CMakeLists.txt rename demos/simplesets/{ => helpers}/circle_convex_hull.cpp (75%) rename demos/simplesets/{ => helpers}/circle_convex_hull.h (100%) diff --git a/demos/isoline_simplification/CMakeLists.txt b/demos/isoline_simplification/CMakeLists.txt index bb8d5ab4..5d953b36 100644 --- a/demos/isoline_simplification/CMakeLists.txt +++ b/demos/isoline_simplification/CMakeLists.txt @@ -15,4 +15,4 @@ target_link_libraries( Qt5::Widgets ) -install(TARGETS isoline_simplification DESTINATION ${INSTALL_BINARY_DIR}) +install(TARGETS isoline_simplification_demo DESTINATION ${INSTALL_BINARY_DIR}) diff --git a/demos/simplesets/CMakeLists.txt b/demos/simplesets/CMakeLists.txt index 68d7f857..b3662be5 100644 --- a/demos/simplesets/CMakeLists.txt +++ b/demos/simplesets/CMakeLists.txt @@ -1,7 +1,6 @@ set(SOURCES simplesets_demo.cpp colors.cpp - circle_convex_hull.cpp ) add_executable(simplesets_demo ${SOURCES}) @@ -17,4 +16,6 @@ target_link_libraries( Qt5::Widgets ) -install(TARGETS simplesets DESTINATION ${INSTALL_BINARY_DIR}) +install(TARGETS simplesets_demo DESTINATION ${INSTALL_BINARY_DIR}) + +add_subdirectory(helpers) \ No newline at end of file diff --git a/demos/simplesets/helpers/CMakeLists.txt b/demos/simplesets/helpers/CMakeLists.txt new file mode 100644 index 00000000..88100abf --- /dev/null +++ b/demos/simplesets/helpers/CMakeLists.txt @@ -0,0 +1,18 @@ +set(SOURCES + circle_convex_hull.cpp +) + +add_executable(circle_convex_hull_demo ${SOURCES}) + +target_link_libraries( + circle_convex_hull_demo + PRIVATE + ${COMMON_CLA_TARGET} + core + renderer + simplesets + CGAL::CGAL + Qt5::Widgets +) + +install(TARGETS circle_convex_hull_demo DESTINATION ${INSTALL_BINARY_DIR}) diff --git a/demos/simplesets/circle_convex_hull.cpp b/demos/simplesets/helpers/circle_convex_hull.cpp similarity index 75% rename from demos/simplesets/circle_convex_hull.cpp rename to demos/simplesets/helpers/circle_convex_hull.cpp index b10ceb46..7c1936c4 100644 --- a/demos/simplesets/circle_convex_hull.cpp +++ b/demos/simplesets/helpers/circle_convex_hull.cpp @@ -26,21 +26,21 @@ CircleConvexHullDemo::CircleConvexHullDemo() { std::function drawFunc = [cs, hull](GeometryRenderer& renderer) { RenderPath path = renderPath(hull); - renderer.setMode(GeometryRenderer::stroke); - renderer.setFill(Color{50, 50, 50}); - renderer.draw(path); renderer.setMode(GeometryRenderer::fill); - renderer.setFill(Color{50, 50, 50}); + renderer.setFill(Color{150, 150, 150}); for (const auto& c : cs) { renderer.draw(c); } + renderer.setMode(GeometryRenderer::stroke); + renderer.setStroke(Color{0, 0, 0}, 3.0); + renderer.draw(path); }; renderer->addPainting(drawFunc, "Disks"); } -//int main(int argc, char* argv[]) { -// QApplication app(argc, argv); -// CircleConvexHullDemo demo; -// demo.show(); -// app.exec(); -//} +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + CircleConvexHullDemo demo; + demo.show(); + app.exec(); +} diff --git a/demos/simplesets/circle_convex_hull.h b/demos/simplesets/helpers/circle_convex_hull.h similarity index 100% rename from demos/simplesets/circle_convex_hull.h rename to demos/simplesets/helpers/circle_convex_hull.h diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index a3b06ace..e966c171 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -56,9 +56,9 @@ using namespace cartocrow::simplesets; SimpleSetsDemo::SimpleSetsDemo() { setWindowTitle("SimpleSets"); - m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc - // m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome - m_ps = PartitionSettings{true, true, true, true, 0.1}; +// m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc + m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome + m_ps = PartitionSettings{true, true, true, false, 0.1}; // m_ps = PartitionSettings{true, true, true, false, 0.5}; m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; m_cds = ComputeDrawingSettings{0.675}; @@ -80,11 +80,21 @@ SimpleSetsDemo::SimpleSetsDemo() { auto* settingsLabel = new QLabel("

Settings

"); vLayout->addWidget(settingsLabel); + auto* coverLabel = new QLabel("Cover"); + vLayout->addWidget(coverLabel); auto* coverSlider = new QSlider(Qt::Orientation::Horizontal); vLayout->addWidget(coverSlider); coverSlider->setMinimum(0); coverSlider->setMaximum(80); - coverSlider->setValue(47); + coverSlider->setValue(4); + + auto* ptSizeLabel = new QLabel("Point size"); + vLayout->addWidget(ptSizeLabel); + auto* ptSizeSlider = new QSlider(Qt::Orientation::Horizontal); + vLayout->addWidget(ptSizeSlider); + ptSizeSlider->setMinimum(0); + ptSizeSlider->setMaximum(80); + ptSizeSlider->setValue(21); m_renderer = new GeometryWidget(); m_renderer->setDrawAxes(false); @@ -93,7 +103,7 @@ SimpleSetsDemo::SimpleSetsDemo() { m_renderer->setMinZoom(0.01); m_renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("data/nyc.txt"); + std::filesystem::path filePath("data/diseasome.txt"); loadFile(filePath); computePartitions(); @@ -117,6 +127,13 @@ SimpleSetsDemo::SimpleSetsDemo() { double value = coverSlider->value() / 10.0; computeDrawing(value); }); + connect(ptSizeSlider, &QSlider::valueChanged, [this, ptSizeSlider, coverSlider] { + double value = ptSizeSlider->value() / 10.0; + m_gs.pointSize = value; + computePartitions(); + computeDrawing(coverSlider->value() / 10.0); + fitToScreen(); + }); fitToScreenButton->click(); } From ea0b9db48eb4b3f398fd2b9ef2f3f01c8f1d7546 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Thu, 3 Oct 2024 15:42:44 +0200 Subject: [PATCH 27/36] Add source information for nyc.txt --- data/SOURCES.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/data/SOURCES.md b/data/SOURCES.md index 542c2478..3bb3549d 100644 --- a/data/SOURCES.md +++ b/data/SOURCES.md @@ -4,14 +4,22 @@ The europe.ipe file is based on data from [naturalearthdata.com](https://www.nat The file example_isolines.ipe contains isolines generated from [The Reference Elevation Model of Antarctica (REMA)](https://www.pgc.umn.edu/data/rema/). These DEMs were provided by the Byrd Polar and Climate Research Center and the Polar Geospatial Center under NSF-OPP awards 1043681, 1542736, 1543501, 1559691, 1810976, and 2129685. -See the end of the file for the relevant bibliography. +See the end of the file for the relevant bibliography [2, 3]. + +The nyc.txt file contains the locations of hotels, subway entrances, and medical clinics (green) in lower Manhattan. +It is a common benchmark dataset that originates from the paper that introduced Bubble Sets [1]. +The point locations were manually traced from the image in the paper. ## References -Howat, Ian; Porter, Claire; Noh, Myoung-Jon; Husby, Erik; Khuvis, Samuel; Danish, Evan; +[1] C. Collins, G. Penn, and S. Carpendale. Bubble Sets: Revealing set relations with isocontours over existing visualizations. +IEEE Transactions on Visualization and Computer Graphics, 15(6):1009–1016, 2009. +doi: https://doi.org/10.1109/TVCG.2009.122 + +[2] Howat, Ian; Porter, Claire; Noh, Myoung-Jon; Husby, Erik; Khuvis, Samuel; Danish, Evan; Tomko, Karen; Gardiner, Judith; Negrete, Adelaide; Yadav, Bidhyananda; Klassen, James; Kelleher, Cole; Cloutier, Michael; Bakker, Jesse; Enos, Jeremy; Arnold, Galen; Bauer, Greg; Morin, Paul, 2022, "The Reference Elevation Model of Antarctica - Mosaics, Version 2", https://doi.org/10.7910/DVN/EBW8UC, Harvard Dataverse, V1 -Howat, I. M., Porter, C., Smith, B. E., Noh, M. J., & Morin, P. (2019). The reference elevation model of Antarctica. +[3] Howat, I. M., Porter, C., Smith, B. E., Noh, M. J., & Morin, P. (2019). The reference elevation model of Antarctica. The Cryosphere, 13(2), 665-674. From 79e2aecdaa7e1c10f0e653daab18ab4ee2ec6048 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Thu, 3 Oct 2024 15:43:52 +0200 Subject: [PATCH 28/36] Fix typo --- cartocrow/renderer/ipe_renderer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cartocrow/renderer/ipe_renderer.h b/cartocrow/renderer/ipe_renderer.h index dc75c8c6..916d1b58 100644 --- a/cartocrow/renderer/ipe_renderer.h +++ b/cartocrow/renderer/ipe_renderer.h @@ -103,7 +103,7 @@ class IpeRenderer : public GeometryRenderer { void addPainting(const std::shared_ptr& painting); void addPainting(const std::shared_ptr& painting, const std::string& name); - /// Paintings will be added a new page. + /// Paintings will be added to a new page. void nextPage(); /// Returns the current page index. int currentPage(); From af5924f454f23b63015f5332345c49a24b4eac40 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Thu, 3 Oct 2024 15:44:06 +0200 Subject: [PATCH 29/36] Remove unused ExactWithSqrt type alias --- cartocrow/core/core.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/cartocrow/core/core.h b/cartocrow/core/core.h index dc773eb7..3d169997 100644 --- a/cartocrow/core/core.h +++ b/cartocrow/core/core.h @@ -43,8 +43,6 @@ namespace cartocrow { /// CGAL kernel for exact constructions (uses an exact number type). using Exact = CGAL::Exact_predicates_exact_constructions_kernel; -/// CGAL kernel for exact constructions (uses an exact number type that supports the square root operation). -using ExactWithSqrt = CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt; /// CGAL kernel for inexact constructions. using Inexact = CGAL::Exact_predicates_inexact_constructions_kernel; From 7c3444ec80d6fbb83df4f427f0639705b7edb987 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Fri, 4 Oct 2024 11:10:40 +0200 Subject: [PATCH 30/36] Fix poly_line_gon_intersection --- .../simplesets/helpers/cs_curve_helpers.cpp | 17 +++++++++++++++++ cartocrow/simplesets/helpers/cs_curve_helpers.h | 2 ++ .../simplesets/helpers/cs_polyline_helpers.cpp | 2 +- .../helpers/poly_line_gon_intersection.h | 17 ++++++++++++++++- demos/simplesets/simplesets_demo.cpp | 12 ++++++------ 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp index 007bd860..9151d7f8 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp @@ -101,6 +101,23 @@ bool liesOn(const OneRootPoint& p, const X_monotone_curve_2& xm_curve) { return xm_curve.point_position(p) == CGAL::EQUAL; } +bool liesOn(const X_monotone_curve_2& c1, const X_monotone_curve_2& c2) { + auto sc = liesOn(c1.source(), c2); + auto tc = liesOn(c1.target(), c2); + if (!sc || !tc) { + return false; + } + if (c2.is_linear()) { + if (c1.is_circular()) return false; + if (c1.supporting_line() != c2.supporting_line()) return false; + } else { + if (c1.is_linear()) return false; + if (c1.supporting_circle() != c2.supporting_circle()) return false; + } + + return true; +} + renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve) { renderer::RenderPath path; path.moveTo(approximateAlgebraic(xm_curve.source())); diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.h b/cartocrow/simplesets/helpers/cs_curve_helpers.h index 366f7e7c..f035acfc 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.h +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.h @@ -80,6 +80,8 @@ CSPolycurve arrPolycurveFromXMCurves(InputIterator begin, InputIterator end) { }); return construct(curves.begin(), curves.end()); } + +bool liesOn(const X_monotone_curve_2& c1, const X_monotone_curve_2& c2); } #endif //CARTOCROW_CS_CURVE_HELPERS_H diff --git a/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp index 7b16bfa0..444a9562 100644 --- a/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polyline_helpers.cpp @@ -63,7 +63,7 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolyline& polyline) { if (c.is_circular()) return false; if (curr->supporting_line() != c.supporting_line()) return false; } else { - if (c.is_circular()) return false; + if (c.is_linear()) return false; if (curr->supporting_circle() != c.supporting_circle()) return false; } } while (curr++ != tit); diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index 08a01732..4efa116c 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -2,6 +2,7 @@ #define CARTOCROW_POLY_LINE_GON_INTERSECTION_H #include "cartocrow/simplesets/types.h" +#include "cs_curve_helpers.h" #include "cs_polyline_helpers.h" #include "cs_polygon_helpers.h" #include @@ -19,6 +20,7 @@ template void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputIterator out, bool difference, bool keepOverlap) { CSTraits traits; auto equals = traits.equal_2_object(); + auto opposite = traits.construct_opposite_2_object(); enum Origin { Polyline, PolygonOuter, @@ -51,6 +53,16 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI e1->twin()->set_data(beforeSplitData); e2->twin()->set_data(beforeSplitData); e2->set_data(beforeSplitData); + CSTraits traits; + auto opposite = traits.construct_opposite_2_object(); + if (liesOn(e1->curve(), xmCurve) || liesOn(opposite(e1->curve()), xmCurve)) { + e1->data().origins.push_back(currentOrigin); + e1->twin()->data().origins.push_back(currentOrigin); + } + if (liesOn(e2->curve(), xmCurve) || liesOn(opposite(e2->curve()), xmCurve)) { + e2->data().origins.push_back(currentOrigin); + e2->twin()->data().origins.push_back(currentOrigin); + } } virtual void after_modify_edge(HeH e) { @@ -59,7 +71,7 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI } Origin currentOrigin; - + X_monotone_curve_2 xmCurve; private: HalfEdgeData beforeSplitData; }; @@ -68,15 +80,18 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI observer.currentOrigin = Origin::Polyline; for (auto cit = line.curves_begin(); cit != line.curves_end(); ++cit) { + observer.xmCurve = *cit; CGAL::insert(arr, *cit); } observer.currentOrigin = Origin::PolygonOuter; for (auto cit = gon.outer_boundary().curves_begin(); cit != gon.outer_boundary().curves_end(); ++cit) { + observer.xmCurve = *cit; CGAL::insert(arr, *cit); } observer.currentOrigin = Origin::PolygonHole; for (auto hit = gon.holes_begin(); hit != gon.holes_end(); ++hit) { for (auto cit = hit->curves_begin(); cit != hit->curves_end(); ++cit) { + observer.xmCurve = *cit; CGAL::insert(arr, *cit); } } diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index e966c171..7de8bd15 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -56,9 +56,9 @@ using namespace cartocrow::simplesets; SimpleSetsDemo::SimpleSetsDemo() { setWindowTitle("SimpleSets"); -// m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc - m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome - m_ps = PartitionSettings{true, true, true, false, 0.1}; + m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc +// m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome + m_ps = PartitionSettings{true, true, true, true, 0.1}; // m_ps = PartitionSettings{true, true, true, false, 0.5}; m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; m_cds = ComputeDrawingSettings{0.675}; @@ -86,7 +86,7 @@ SimpleSetsDemo::SimpleSetsDemo() { vLayout->addWidget(coverSlider); coverSlider->setMinimum(0); coverSlider->setMaximum(80); - coverSlider->setValue(4); + coverSlider->setValue(47); auto* ptSizeLabel = new QLabel("Point size"); vLayout->addWidget(ptSizeLabel); @@ -94,7 +94,7 @@ SimpleSetsDemo::SimpleSetsDemo() { vLayout->addWidget(ptSizeSlider); ptSizeSlider->setMinimum(0); ptSizeSlider->setMaximum(80); - ptSizeSlider->setValue(21); + ptSizeSlider->setValue(static_cast(m_gs.pointSize * 10)); m_renderer = new GeometryWidget(); m_renderer->setDrawAxes(false); @@ -103,7 +103,7 @@ SimpleSetsDemo::SimpleSetsDemo() { m_renderer->setMinZoom(0.01); m_renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("data/diseasome.txt"); + std::filesystem::path filePath("data/nyc.txt"); loadFile(filePath); computePartitions(); From 696aa657c9c69bf44c9f0b90d50e244df83d551f Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Fri, 4 Oct 2024 11:37:02 +0200 Subject: [PATCH 31/36] Add hex color constructor --- cartocrow/core/core.cpp | 1 + cartocrow/core/core.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/cartocrow/core/core.cpp b/cartocrow/core/core.cpp index 99c4f73b..9361e5fe 100644 --- a/cartocrow/core/core.cpp +++ b/cartocrow/core/core.cpp @@ -26,6 +26,7 @@ namespace cartocrow { Color::Color() : r(0), g(0), b(0) {} Color::Color(int r, int g, int b) : r(r), g(g), b(b) {} +Color::Color(int rgb) : r((rgb & 0xff0000) >> 16), g((rgb & 0x00ff00) >> 8), b(rgb & 0x0000ff) {} Number wrapAngle(Number alpha, Number beta) { return wrap(alpha, beta, beta + M_2xPI); diff --git a/cartocrow/core/core.h b/cartocrow/core/core.h index 3d169997..f725c35f 100644 --- a/cartocrow/core/core.h +++ b/cartocrow/core/core.h @@ -177,6 +177,8 @@ struct Color { Color(); /// Constructs a color. Color(int r, int g, int b); + /// Constructs a color from a single integer (useful combined with hexadecimal literals, e.g. 0xFFFFFF). + Color(int rgb); }; /// Wraps the given number \f$n\f$ to the interval \f$[a, b)\f$. From 51a845fc35a01218b508dc1dd01e60d4bff8a7b3 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Fri, 4 Oct 2024 11:41:37 +0200 Subject: [PATCH 32/36] Add diseasome dataset --- data/SOURCES.md | 15 +- data/diseasome.txt | 516 +++++++++++++++++++++++++++ demos/simplesets/colors.cpp | 27 ++ demos/simplesets/colors.h | 4 + demos/simplesets/simplesets_demo.cpp | 20 +- 5 files changed, 572 insertions(+), 10 deletions(-) create mode 100644 data/diseasome.txt diff --git a/data/SOURCES.md b/data/SOURCES.md index 3bb3549d..7e845268 100644 --- a/data/SOURCES.md +++ b/data/SOURCES.md @@ -4,22 +4,31 @@ The europe.ipe file is based on data from [naturalearthdata.com](https://www.nat The file example_isolines.ipe contains isolines generated from [The Reference Elevation Model of Antarctica (REMA)](https://www.pgc.umn.edu/data/rema/). These DEMs were provided by the Byrd Polar and Climate Research Center and the Polar Geospatial Center under NSF-OPP awards 1043681, 1542736, 1543501, 1559691, 1810976, and 2129685. -See the end of the file for the relevant bibliography [2, 3]. +See the end of the file for the relevant bibliography [3, 4]. The nyc.txt file contains the locations of hotels, subway entrances, and medical clinics (green) in lower Manhattan. It is a common benchmark dataset that originates from the paper that introduced Bubble Sets [1]. The point locations were manually traced from the image in the paper. +The diseasome.txt file contains vertices of an embedded graph of disorders, from the human +disease network constructed by Goh et al. [2]. The dataset consists of 516 vertices (disorders) of +twenty-one disorder classes. We use the graph layout of a poster by Bastian and Heymann archived at +https://web.archive.org/web/20121116145141/http://diseasome.eu/data/diseasome_poster.pdf. + ## References [1] C. Collins, G. Penn, and S. Carpendale. Bubble Sets: Revealing set relations with isocontours over existing visualizations. IEEE Transactions on Visualization and Computer Graphics, 15(6):1009–1016, 2009. doi: https://doi.org/10.1109/TVCG.2009.122 -[2] Howat, Ian; Porter, Claire; Noh, Myoung-Jon; Husby, Erik; Khuvis, Samuel; Danish, Evan; +[2] K.-I. Goh, M. E. Cusick, D. Valle, B. Childs, M. Vidal, and A.-L. Barabási. +The human disease network. Proceedings of the National Academy of +Sciences, 104(21):8685–8690, 2007. doi: https://doi.org/10.1073/pnas.0701361104 + +[3] Howat, Ian; Porter, Claire; Noh, Myoung-Jon; Husby, Erik; Khuvis, Samuel; Danish, Evan; Tomko, Karen; Gardiner, Judith; Negrete, Adelaide; Yadav, Bidhyananda; Klassen, James; Kelleher, Cole; Cloutier, Michael; Bakker, Jesse; Enos, Jeremy; Arnold, Galen; Bauer, Greg; Morin, Paul, 2022, "The Reference Elevation Model of Antarctica - Mosaics, Version 2", https://doi.org/10.7910/DVN/EBW8UC, Harvard Dataverse, V1 -[3] Howat, I. M., Porter, C., Smith, B. E., Noh, M. J., & Morin, P. (2019). The reference elevation model of Antarctica. +[4] Howat, I. M., Porter, C., Smith, B. E., Noh, M. J., & Morin, P. (2019). The reference elevation model of Antarctica. The Cryosphere, 13(2), 665-674. diff --git a/data/diseasome.txt b/data/diseasome.txt new file mode 100644 index 00000000..24d00932 --- /dev/null +++ b/data/diseasome.txt @@ -0,0 +1,516 @@ +0 2502.7 -327.4 +1 2501.7 789.6 +1 2132.6 603.4 +2 2643.3 -760.5 +3 2396 -65.3 +4 1260.8 448.1 +5 2060.7 662.1 +1 2156.3 391.1 +6 1532.5 -743.2 +7 2472 425.2 +8 2740.6 -593.3 +3 2490.9 670.3 +9 2782.4 640.8 +10 1579.8 279.9 +0 2353.9 139.5 +5 2016.5 845.8 +11 1376.1 192.2 +12 2654.4 53.3 +2 2570.7 -782.1 +10 2451.3 -560.5 +2 2638.4 -476.4 +11 3194.1 -457 +13 2549.8 -803.2 +14 2572.2 -548 +0 2710.6 806.5 +15 2448.9 -253.7 +2 2469 -716.8 +16 1386.3 -315.6 +12 2801.6 -713 +15 2325.2 652.5 +1 2133 263.9 +0 2733.1 450.4 +15 3250.3 378.5 +14 1280.9 -529.7 +14 1901.4 794.4 +12 2145.4 334.8 +8 2561.7 -107 +2 2927.2 -415 +2 2660.3 -713.6 +6 1843.9 774.1 +5 1243.6 -314 +8 3079.3 -222.8 +17 3173.1 397.8 +4 1703.6 596 +8 2945.1 618.1 +11 1841 36.3 +11 2287.2 346.2 +5 1123.8 -423 +16 1867.5 289.2 +4 2399.5 -509.8 +2 2520.7 -500.8 +0 2354.6 782.5 +11 1825.3 134.6 +18 1701.9 497.1 +1 2320.8 -346.1 +2 3068.3 617.2 +19 2457.6 -529.2 +11 1631.1 -236.2 +10 2597.9 152.3 +2 3182 -546.5 +1 2621.2 864.4 +16 2798.2 -827 +8 2991.8 640.1 +5 1550.6 -314.3 +20 1444.5 -294.4 +7 2522.2 722.4 +2 2244.9 -493.2 +4 2869.9 -661 +0 2043.2 8.2 +15 3271.3 437.5 +4 2702.9 -146.3 +14 1325.8 -508.1 +13 1784.5 -117.7 +1 2556.8 886.9 +0 2347.1 759.1 +0 2456.7 576.8 +1 2344.8 424.9 +1 2834.9 -146.4 +2 2630.2 -571.1 +15 2287 -314.8 +16 1986.8 310.7 +0 1588.3 -637 +11 1902.1 -12.4 +12 2330.5 33.4 +8 3155.7 -76.4 +16 2799.4 -802.4 +16 1208.3 -340.4 +5 2072.6 736.4 +4 2029.3 566.7 +4 2040.5 -40.6 +12 2653.1 -225 +12 2815.7 -417.1 +4 2476.4 -958.1 +6 2493.1 353 +10 1375.8 278.7 +1 2625.4 728.5 +7 1502.1 -821.3 +11 1269.2 -573.3 +10 1523.2 236.8 +1 2564.5 842 +5 1344.1 -190.9 +21 2413.2 282.2 +16 2670.8 -902.8 +15 2591.8 319.3 +2 2879.7 -458.4 +0 1741.2 -100.5 +16 1766 735.2 +2 2985.3 -502.8 +4 2382.6 71.3 +5 2233.9 952.9 +2 2662.4 -372.8 +7 2311.1 582.5 +2 2441.8 -692.2 +7 2609.2 130.6 +11 2477.8 304.9 +5 2112.3 993 +10 1490.8 74.9 +2 2964 -737.8 +2 2688.7 -352.1 +2 3147.4 -653.4 +2 2652.6 -644.2 +1 1593.9 471.7 +15 2854.6 226.1 +2 3002.1 -683.2 +6 1448.7 -633.2 +6 1774 449 +5 2045.3 957.3 +4 2529.7 -909 +15 2695.8 318 +15 2690 567.6 +0 2950.8 186.5 +17 2565.5 596.1 +2 2693.3 -547.4 +4 1607 617.1 +11 2094.7 510.8 +0 2391.1 13.7 +7 2632 668.8 +19 2787.5 728 +1 2669.7 107.4 +5 1663.7 -722.5 +15 2904.6 122.7 +4 2368.9 -490.4 +10 1413 340.4 +20 3292.6 398.2 +18 1817.3 467.7 +2 2964 -357.5 +4 2730.9 585.1 +15 2342.5 -212.4 +7 1563 -541.9 +15 3082.6 258.5 +11 2763.5 470.5 +16 2256.8 -254.9 +2 3170.5 -432.6 +15 1781 -465.1 +10 1447 171.9 +2 2644.3 -427 +5 1165.4 -395.7 +8 3198.4 -97.8 +15 3242.3 345.2 +2 2963.5 533 +15 2515.2 334.4 +2 2472.9 -579.3 +21 3031.8 -896.4 +20 2328.6 115.3 +1 2284.1 -366.6 +11 2440.3 240.4 +14 1493.8 -607.7 +15 3222.5 238.5 +13 1830 -443.8 +2 2713.7 -399.4 +11 2432.1 445.2 +16 1941 754.8 +20 2594.9 -1060.8 +4 2692.3 -982.4 +2 2992.3 -797.9 +2 2951.1 702.2 +2 3177.1 -677.7 +2 2734.9 -453.2 +11 1724.3 195.6 +2 2648.9 -183.3 +1 2381.8 602 +8 3176.5 -131.7 +2 2871.4 664.9 +1 2201.7 412 +12 2619.8 -164.3 +1 2634.3 614.8 +4 1774.1 9.2 +2 1565.5 -254.4 +13 1831.8 -35.5 +8 1203.6 470.2 +13 2107.6 -78.1 +0 2741.6 407.7 +15 2851.3 617.4 +1 1674.3 406.5 +5 2161.2 869.1 +8 2759 -164.3 +21 3013.4 -832.7 +17 2802 71.4 +2 3018.7 565.3 +4 1359.8 -87.4 +10 1407.2 215 +2 3160.3 -738.1 +11 2553.3 194.5 +4 1601.7 528.9 +2 2746.7 667.6 +21 2132.8 641.3 +5 2280.1 622 +11 2529.8 402.8 +9 2477 259.8 +12 2582.7 -691.2 +15 2858.9 53.8 +12 2567.5 228.6 +6 1178.6 384.1 +2 2604.5 -593.1 +2 2487.2 -384.2 +2 3156.3 -570.8 +5 2074.8 800.2 +10 1885 370.8 +12 2411.5 324.1 +15 2951 163.3 +12 2359.8 259.7 +15 1821.8 717.4 +3 2465.6 -67.2 +2 3035.7 728.5 +5 1432.5 -64.2 +15 2047.9 529.8 +5 2275.4 715.2 +13 1725 -10.9 +8 2510.9 -88.3 +4 3143.7 321.9 +5 1377.6 -150.6 +15 2689.2 705.6 +2 2252.9 -423.7 +4 2211.6 -273.8 +1 2605.5 635.3 +4 2115.1 547.6 +4 2178.8 -294.2 +5 2077.2 894.3 +16 1852.7 818.7 +15 1951.8 -33.6 +2 2796.1 -566.4 +15 2868.2 165.4 +5 2152.3 914.3 +4 2365.9 158.5 +10 1442.9 119 +8 3099.3 -368.5 +0 2353.4 -134.1 +1 2358.3 -387.9 +8 3056.9 674.5 +15 2589.7 298.9 +15 2364 -273.5 +8 2967.6 598.4 +11 1855.3 230 +0 2655.1 788.9 +8 3133.8 -272.4 +4 2459.6 -598.8 +12 2654.5 340.3 +6 1689.3 657.8 +15 2560.7 378 +0 2717.7 34.6 +6 1754.6 428.1 +21 2604.6 805.9 +0 2233.8 780.7 +16 2594.5 -933.3 +14 2649 -1035.5 +12 2214.3 13.9 +16 1894.9 250.9 +8 1804.8 -298.8 +4 1999.3 -58.4 +2 2776.7 -498.5 +2 2471 -477.8 +2 3027.6 -428 +4 1315.1 -551.4 +11 2301.2 303 +8 3140.8 -181 +13 1677 -78.1 +15 2468.1 30.7 +15 3120.8 369.9 +2 2603 -304.9 +2 2819.8 -693.1 +16 2527.3 -1034.7 +2 2622.9 -837.6 +20 1236.9 -423.7 +2 3046.7 -623.6 +15 3258.6 259.6 +2 2799 -476.3 +15 2773.8 429.5 +16 1926.2 732.4 +5 2103.2 712 +13 1769.4 -401.8 +15 2739.5 14 +9 2452.3 -25.1 +15 2894.9 245.8 +8 3097.8 772.5 +13 1717.5 -422.5 +15 2204.9 -334.1 +17 2548.3 692.2 +4 1605.6 427 +5 2103 756.3 +2 3157.8 -594.6 +1 2468.6 -782.7 +13 1734.6 54.9 +0 2624.5 769.8 +10 1477.5 301.2 +4 1667.1 712.3 +1 2180.2 576.5 +4 2776.3 684.5 +9 2843.4 750.2 +15 2811 11.2 +5 1224.8 -475.3 +4 2913.1 -709.6 +4 2717 549.9 +21 1774.2 -355.1 +2 2588.6 -452.2 +2 2719.2 -202 +15 1794.4 -253.9 +15 3161.1 280.8 +4 2925.6 -778.4 +15 1452.2 -170.2 +11 1324.1 299.6 +15 2407 401.5 +13 1806.6 -75.1 +12 2592.2 88.9 +4 1526.1 -274.8 +5 1662.3 839.7 +8 1151.3 427.1 +8 2388.1 218.2 +4 2271.7 676.8 +5 1996.6 914.6 +12 2638.3 170.5 +11 1803.3 203 +11 1664.3 153.5 +10 1533.6 93.9 +15 1947.1 595 +11 1816.4 271.2 +4 1600.4 132.3 +1 2766.3 86.9 +2 3018.6 -533.6 +5 1175.4 -447.5 +13 1661.5 30 +0 1620.4 -293.5 +8 2474.3 100.8 +2 2801.3 -349.9 +15 2368.7 559.4 +0 2391.3 734.5 +15 3153.8 214.8 +1 2527.8 818.3 +0 1592.2 257.7 +6 1490.6 -714.8 +15 1809 529 +5 2065.3 776.2 +0 2389.8 697 +8 3177.7 -56.2 +1 2849.1 -127.2 +5 1615 -765.9 +10 1748.1 572.9 +11 1905 66.2 +11 1711.5 112.2 +1 2917.3 204.8 +15 1649.3 -705.9 +4 2449.9 -429.7 +5 2199.2 694.2 +2 2712.5 -289.7 +2 3122.6 649.7 +15 2886 303.3 +17 2504 653.2 +7 3124.4 -763.6 +4 1914.7 644 +1 2277 541.2 +2 3010.4 -391 +4 1769.4 387.1 +13 2541.6 -350.4 +2 2821.8 -329 +10 1427.2 257.4 +1 2253.3 521.1 +12 2160.4 53 +5 1987.1 874.5 +5 2039.4 933.5 +12 2610.5 71.4 +17 2479.9 631.7 +1 1660 451.2 +1 2241.2 492.1 +16 1612.7 806.5 +8 2813.8 -537.4 +2 2830.3 -518.4 +0 1957.5 -78.6 +2 2912.2 -627.5 +13 1730.3 -380.1 +15 2922.1 283.2 +10 1578.5 320.1 +8 1220 405.7 +2 2894.4 -478.5 +18 1821.8 493 +4 2764.4 604.3 +5 1414.8 -109.1 +6 1561.8 -844.2 +6 1793.5 796.1 +16 2467.9 -937.7 +2 3117.9 -625.4 +2 2764.9 -732.6 +6 1624.9 761.9 +19 2619.4 204.6 +4 3174.3 -392.4 +6 1547.1 -801.3 +14 1734.3 636.8 +20 1982.7 331.3 +1 2238.2 452.3 +2 3009.4 -658.7 +4 1396.4 151.5 +0 2426.5 180.3 +14 2628.5 -5.5 +0 1739.3 -55.1 +4 1630.3 551.2 +4 1913.9 10.3 +15 1806.6 548.9 +12 2281.2 92.2 +2 2797 -256.2 +0 1997.6 32.7 +12 2579.8 174.3 +4 3249.3 308.5 +0 2457.4 -109.8 +2 2985.8 514.1 +16 1868.9 697.8 +10 2703.1 -247.3 +5 1160.4 -314.3 +15 2858.3 9.1 +4 2618.3 -520.5 +2 2863.8 -309.1 +2 3031.8 -454.3 +4 3130.5 -345.6 +16 2017.1 286.8 +19 2200.6 369.9 +21 2492.7 467.4 +17 3219.4 417.1 +4 2742.5 -127.5 +2 2205.4 -445.3 +8 2542.4 445.6 +8 3100.7 300.8 +7 2783.8 -182.7 +15 2854.8 143.6 +0 2237.2 431.3 +16 2799.9 -894.4 +15 2607.2 276.7 +10 1555.3 573.7 +20 2781 -107 +14 1358.1 -597.2 +20 1950.1 351.8 +2 2874.3 -288 +4 1831.5 153.2 +2 3213.9 -413.3 +16 2738.7 -870.7 +6 1460 -778.7 +14 2403.9 -5.7 +5 2165.2 971.5 +17 2854 265.1 +4 3103.7 343.5 +5 1270.7 -369 +0 2588.9 749.8 +11 1776.7 341.5 +2 2804.1 -755.6 +2 2415 -410 +14 1713.1 818.9 +2 2839.2 -380.8 +2 2778.4 -435.3 +4 1728.2 678.7 +2 2704.2 -781.5 +6 1820.3 678.2 +2 2935.8 -566.4 +5 1334.7 -129.6 +0 2241.6 737.5 +12 2165 281 +5 2163.8 819.2 +4 2988.9 -874.9 +5 1120.1 -368.6 +5 1424.6 -348.7 +1 2659.8 829.5 +15 1786.5 409.1 +15 2286 -190.9 +10 1545 58.1 +16 1664.5 784.6 +10 1884.1 408.8 +0 2539.8 -25.7 +10 2337.1 -446.6 +12 2581 14.6 +12 2659.2 279.5 +12 2512.9 52.5 +4 1928.9 389.1 +2 2533.9 -740.6 +4 3084.6 -711.5 +14 1584.6 -560.9 +15 2306.4 -236.5 +21 2506.2 -45.8 +4 3055.5 -853.7 +2 2462.2 -826.8 +2 3200.2 -493.7 +2 2488.4 -761.9 +13 1689.3 -34.4 +14 2067.6 585.1 +8 2719.3 388.3 +15 2726.8 358.5 +2 1655.3 -217.7 +14 1518.6 -656.4 +1 2631.8 -125.9 +11 1758.6 175.7 +0 2349.5 372.5 +2 1235.3 361.2 +2 2613.4 -405.7 +2 2864.7 -439.7 +4 1610.4 -201 +14 1486.2 -676 +16 2575 -1008.9 +1 2142.6 308.8 +15 2262.7 -172.9 +2 2301.4 -468.5 +11 1676.4 216.9 +12 2282.8 -26.6 \ No newline at end of file diff --git a/demos/simplesets/colors.cpp b/demos/simplesets/colors.cpp index 1a04bdb2..28857f6f 100644 --- a/demos/simplesets/colors.cpp +++ b/demos/simplesets/colors.cpp @@ -11,4 +11,31 @@ Color light_orange(253,191,111); Color orange(255,127,0); Color light_purple(202,178,214); Color purple(106,61,154); +} + +namespace cartocrow::diseasome { +std::vector colors({ + {0xB9E1EE}, + {0x9AC019}, + {0xCD6814}, + {0xE53389}, + {0xC1BC56}, + {0x923B8B}, + {0xFBD2AA}, + {0x999999}, + {0xFECD0F}, + {0xCB9A03}, + {0xF3983B}, + {0x4B8EC7}, + {0x2E9A67}, + {0xE95937}, + {0xF8EE82}, + {0xE74646}, + {0xCBBC9D}, + {0x6699CD}, + {0x6FC4C6}, + {0xF1979A}, + {0x8F5A9C}, + {0xBB3087}, +}); } \ No newline at end of file diff --git a/demos/simplesets/colors.h b/demos/simplesets/colors.h index 3393e399..bca1c07d 100644 --- a/demos/simplesets/colors.h +++ b/demos/simplesets/colors.h @@ -16,5 +16,9 @@ namespace cartocrow::CB { extern Color purple; } +namespace cartocrow::diseasome { + extern std::vector colors; +} + #endif //CARTOCROW_COLORS_H diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 7de8bd15..2c7b47f5 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -56,13 +56,21 @@ using namespace cartocrow::simplesets; SimpleSetsDemo::SimpleSetsDemo() { setWindowTitle("SimpleSets"); - m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; //nyc -// m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; //diseasome - m_ps = PartitionSettings{true, true, true, true, 0.1}; - // m_ps = PartitionSettings{true, true, true, false, 0.5}; - m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; m_cds = ComputeDrawingSettings{0.675}; + // Initial file + std::filesystem::path filePath("data/nyc.txt"); + + // nyc + m_gs = GeneralSettings{2.1, 2, M_PI, 70.0 / 180 * M_PI}; + m_ds = DrawSettings{{CB::light_blue, CB::light_red, CB::light_green, CB::light_purple, CB::light_orange}, 0.7}; + m_ps = PartitionSettings{true, true, true, true, 0.1}; + + // diseasome +// m_gs = GeneralSettings{5.204, 2, M_PI, 70.0 / 180 * M_PI}; +// m_ds = DrawSettings{diseasome::colors, 0.7}; +// m_ps = PartitionSettings{true, true, true, false, 0.5}; + auto* dockWidget = new QDockWidget(); addDockWidget(Qt::RightDockWidgetArea, dockWidget); auto* vWidget = new QWidget(); @@ -103,8 +111,6 @@ SimpleSetsDemo::SimpleSetsDemo() { m_renderer->setMinZoom(0.01); m_renderer->setMaxZoom(1000.0); - std::filesystem::path filePath("data/nyc.txt"); - loadFile(filePath); computePartitions(); computeDrawing(coverSlider->value() / 10.0); From d096ecee007b84b2f180682e3a29b662a785835a Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Mon, 7 Oct 2024 17:27:28 +0200 Subject: [PATCH 33/36] Fix various bugs that occur in edge cases --- cartocrow/simplesets/drawing_algorithm.cpp | 263 +++++++++--------- cartocrow/simplesets/drawing_algorithm.h | 35 ++- .../helpers/approximate_convex_hull.cpp | 15 + .../simplesets/helpers/arrangement_helpers.h | 4 +- .../simplesets/helpers/cs_polygon_helpers.cpp | 27 +- .../simplesets/helpers/cs_polygon_helpers.h | 1 + .../helpers/poly_line_gon_intersection.h | 19 +- cartocrow/simplesets/partition_algorithm.cpp | 22 +- demos/simplesets/simplesets_demo.cpp | 21 +- 9 files changed, 254 insertions(+), 153 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index c69f79e9..b4fd980a 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -8,7 +8,9 @@ #include "helpers/cs_polygon_helpers.h" #include "helpers/cs_polyline_helpers.h" #include +#include #include +#include "cartocrow/renderer/ipe_renderer.h" using namespace cartocrow::renderer; @@ -114,6 +116,7 @@ Point get_point_in(const Face& face) { Vector v = seg.supporting_line().to_vector(); return v * (makeExact(approximateAlgebraic(pt1)) - makeExact(approximateAlgebraic(pt2))) < 0; }); + intersection_pts.erase(std::unique(intersection_pts.begin(), intersection_pts.end()), intersection_pts.end()); Point approx_source; Point approx_target; @@ -275,9 +278,8 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G }); for (auto eit = m_arr.induced_edges_begin(cit); eit != m_arr.induced_edges_end(cit); ++eit) { DilatedPatternArrangement::Halfedge_handle eh = *eit; - HalfEdgeData data{curve_data->second}; - eh->set_data(data); - eh->twin()->set_data(data); + eh->data().origins.push_back(curve_data->second); + eh->twin()->data().origins.push_back(curve_data->second); } } @@ -332,56 +334,36 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G if (avoidees.empty()) continue; - std::vector morphedEdges; - auto bpiss = originCcbs(c); - assert(bpiss.size() == 1); // todo - auto bpis = bpiss[0]; + auto bpis = boundaryParts(c, i); + auto disks = includeExcludeDisks(i, avoidees, c); + auto inclDisks = disks.include; + auto exclDisks = disks.exclude; - std::vector morphedComponentXMCurves; - auto poly = ccb_to_polygon(c.outer_ccb()); - - bool morphedFace = false; - - for (const auto& bpi : bpis) { - auto bp = bpi.first; - if (bpi.second != i) { - std::copy(bp.curves_begin(), bp.curves_end(), std::back_inserter(morphedComponentXMCurves)); - continue; - } - - auto disks = includeExcludeDisks(i, avoidees, c); - auto inclDisks = disks.include; - auto exclDisks = disks.exclude; - - if (exclDisks.empty()) { - std::copy(bp.curves_begin(), bp.curves_end(), std::back_inserter(morphedComponentXMCurves)); - continue; - } - - auto morphed = morph(bp, poly, inclDisks, exclDisks, m_gs, m_cds); - std::copy(morphed.curves_begin(), morphed.curves_end(), std::back_inserter(morphedComponentXMCurves)); - - for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { - CSPolygonWithHoles pgn(face_to_polygon(*fit)); - intersection(morphed, pgn, std::back_inserter(fit->data().morphedEdges[i]), false, true); - } - morphedFace = true; + if (exclDisks.empty()) { + continue; } - - if (!morphedFace) continue; + auto componentPolygon = ccb_to_polygon(c.outer_ccb()); + auto morphedComponentPolygon = morph(bpis, componentPolygon, inclDisks, exclDisks, m_gs, m_cds); // Compute the morphed version of the CSPolygon for this component. // Set, for each face in component c, the morphed face to the intersection of this CSPolygon with the face. - auto morphedComponentPolygon = CSPolygon(morphedComponentXMCurves.begin(), morphedComponentXMCurves.end()); + // Set the morphed edges to the intersection of the boundary of the polygon with the face. for (auto fit = c.faces_begin(); fit != c.faces_end(); ++fit) { auto facePolygon = face_to_polygon(*fit); + + for (const auto& bp : bpis) { + CSPolyline mb = associatedBoundary(componentPolygon, morphedComponentPolygon, bp); + intersection(mb, facePolygon, std::back_inserter(fit->data().morphedEdges[i]), false, true); + } + std::vector morphedFacePolygonsWithHoles; CGAL::intersection(morphedComponentPolygon, facePolygon, std::back_inserter(morphedFacePolygonsWithHoles)); - assert(morphedFacePolygonsWithHoles.size() == 1); - auto morphedFacePolygonWithHoles = morphedFacePolygonsWithHoles[0]; - assert(!morphedFacePolygonWithHoles.has_holes()); // todo - fit->data().morphedFace[i] = morphedFacePolygonWithHoles.outer_boundary(); + auto& mf = fit->data().morphedFace[i]; + for (const auto& morphedFacePolygonWithHoles : morphedFacePolygonsWithHoles) { + assert(!morphedFacePolygonWithHoles.has_holes()); // todo + mf.push_back(morphedFacePolygonWithHoles.outer_boundary()); + } } } } @@ -456,15 +438,18 @@ CSPolygon thinRectangle(const Point& p, const OneRootPoint& n, const Numb return {xm_curves.begin(), xm_curves.end()}; } -CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape, const std::vector>& inclDisks, +CSPolygon morph(const std::vector& boundaryParts, const CSPolygon& componentShape, const std::vector>& inclDisks, const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds) { - if (exclDisks.empty()) return boundaryPart; + if (exclDisks.empty()) return componentShape; std::vector> lineCovering; std::vector> arcCovering; for (const auto& d : exclDisks) { - auto inter = intersection(boundaryPart, circleToCSPolygon(d), true); + std::vector inter; + for (const auto& boundaryPart : boundaryParts) { + intersection(boundaryPart, circleToCSPolygon(d), std::back_inserter(inter), false, true); + } bool coversLine = true; for (const auto& p : inter) { if (!isStraight(p)) { @@ -489,29 +474,88 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape } auto diskComponents = connectedDisks(expandedLineCoveringDisks); + auto nearestOnBoundary = [&boundaryParts](const Point& point) { + std::optional minSqrdDist; + std::optional closest; + for (const auto& bp : boundaryParts) { + auto n = nearest(bp, point); + auto sqrdDist = CGAL::square(n.x() - point.x()) + CGAL::square(n.y() - point.y()); + if (!minSqrdDist.has_value() || sqrdDist < *minSqrdDist) { + minSqrdDist = sqrdDist; + closest = n; + } + } + return *closest; + }; + std::vector cuts; + std::vector> rectangleCutDisks; for (const auto& comp : diskComponents) { std::vector> disks; for (const auto& [i, _] : comp) { disks.push_back(lineCovering[i]); } - cuts.push_back(approximateConvexHull(disks)); + auto hull = approximateConvexHull(disks); + cuts.push_back(hull); + + // Only cut out a rectangle to the nearest disk of the component + std::optional> minDist; + std::optional> closestDisk; + + for (const auto& d : disks) { + if (inside(componentShape, d.center())) { + auto n = nearestOnBoundary(d.center()); + auto dist = CGAL::squared_distance(approximateAlgebraic(n), approximate(d.center())); + if (!minDist.has_value() || dist < *minDist) { + minDist = dist; + closestDisk = d; + } + } + } + + if (closestDisk.has_value()) { + rectangleCutDisks.push_back(*closestDisk); + } + + CSTraitsBoolean traits; + if (!is_valid_unknown_polygon(hull, traits)) { + // export debug info to ipe. + IpeRenderer ipeRenderer; + ipeRenderer.addPainting([&hull, &disks](GeometryRenderer& renderer) { + renderer.setMode(GeometryRenderer::fill | GeometryRenderer::stroke); + renderer.setStroke(Color(0), 1.0); + renderer.setFill(Color(225, 225, 225)); + renderer.draw(renderPath(hull)); + for (const auto& d : disks) { + renderer.draw(d); + } + }); + ipeRenderer.save(std::filesystem::path("ch-debug.ipe")); + for (auto cit = hull.curves_begin(); cit != hull.curves_end(); ++cit) { + std::cerr << *cit << std::endl; + } + throw std::runtime_error("CH not simple; see ch-debug.ipe"); + } } + for (const auto& d : arcCovering) { cuts.push_back(circleToCSPolygon(d)); + if (inside(componentShape, d.center())) { + rectangleCutDisks.push_back(d); + } } CSPolygonSet polygonSet; for (const auto& cut : cuts) { polygonSet.join(cut); } - for (const auto& d: exclDisks) { - if (on_or_inside(componentShape, d.center())) { - auto n = nearest(boundaryPart, d.center()); - auto rect = thinRectangle(d.center(), n, gs.pointSize / 5); - polygonSet.join(rect); - } + + for (const auto& d : rectangleCutDisks) { + auto n = nearestOnBoundary(d.center()); + auto rect = thinRectangle(d.center(), n, gs.pointSize / 5); + polygonSet.join(rect); } + for (const auto& d: inclDisks) { polygonSet.difference(circleToCSPolygon(d)); } @@ -525,14 +569,26 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape std::vector polys; polygonSet2.polygons_with_holes(std::back_inserter(polys)); - assert(polys.size() == 1); - auto poly = polys[0]; - assert(!poly.has_holes()); +; + CSPolygonWithHoles poly; + std::optional> max_area; + for (const auto& cp : polys) { + auto a = area(cp); + if (!max_area.has_value() || a > *max_area) { + max_area = a; + poly = cp; + } + } + // If poly has holes ignore them (that is, cut them out as well). auto outer = poly.outer_boundary(); - std::vector outer_xm_curves; - for (auto cit = outer.curves_begin(); cit != outer.curves_end(); ++cit) { - outer_xm_curves.push_back(*cit); + return outer; +} + +CSPolyline associatedBoundary(CSPolygon component, CSPolygon morphedComponent, CSPolyline boundaryPart) { + std::vector morphed_xm_curves; + for (auto cit = morphedComponent.curves_begin(); cit != morphedComponent.curves_end(); ++cit) { + morphed_xm_curves.push_back(*cit); } auto boundaryPartStart = boundaryPart.curves_begin()->source(); auto endIt = boundaryPart.curves_end(); @@ -541,22 +597,22 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape int startIndex = -1; int endIndex = -1; - for (int i = 0; i < outer_xm_curves.size(); i++) { - auto c = outer_xm_curves[i]; - if (outer_xm_curves[i].source() == boundaryPartStart) { + for (int i = 0; i < morphed_xm_curves.size(); i++) { + auto c = morphed_xm_curves[i]; + if (morphed_xm_curves[i].source() == boundaryPartStart) { startIndex = i; } - if (outer_xm_curves[i].target() == boundaryPartEnd) { + if (morphed_xm_curves[i].target() == boundaryPartEnd) { endIndex = i; } } - for (int i = 0; i < outer_xm_curves.size() && (startIndex < 0 || endIndex < 0); i++) { - const auto& c = outer_xm_curves[i]; - if (startIndex < 0 && liesOn(c.source(), componentShape).has_value() && !liesOn(c, componentShape)) { + for (int i = 0; i < morphed_xm_curves.size() && (startIndex < 0 || endIndex < 0); i++) { + const auto& c = morphed_xm_curves[i]; + if (startIndex < 0 && liesOn(c.source(), component).has_value() && !liesOn(c, component)) { startIndex = i; } - if (endIndex < 0 && !liesOn(c, componentShape) && liesOn(c.target(), componentShape).has_value()) { + if (endIndex < 0 && !liesOn(c, component) && liesOn(c.target(), component).has_value()) { endIndex = i; } } @@ -566,71 +622,22 @@ CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape std::vector xm_curves; - if (endIndex > startIndex) { + if (endIndex >= startIndex) { for (int i = startIndex; i <= endIndex; ++i) { - xm_curves.push_back(outer_xm_curves[i]); + xm_curves.push_back(morphed_xm_curves[i]); } } else { - for (int i = startIndex; i < outer_xm_curves.size(); ++i) { - xm_curves.push_back(outer_xm_curves[i]); + for (int i = startIndex; i < morphed_xm_curves.size(); ++i) { + xm_curves.push_back(morphed_xm_curves[i]); } for (int i = 0; i <= endIndex; ++i) { - xm_curves.push_back(outer_xm_curves[i]); + xm_curves.push_back(morphed_xm_curves[i]); } } - //todo smooth return {xm_curves.begin(), xm_curves.end()}; } -std::vector>> originCcbs(const Component& c) { - std::vector ccbs; - std::copy(c.outer_ccbs_begin(), c.outer_ccbs_end(), std::back_inserter(ccbs)); - std::copy(c.inner_ccbs_begin(), c.inner_ccbs_end(), std::back_inserter(ccbs)); - - std::vector>> result; - - for (const auto& ccb : ccbs) { - std::vector> polylines; - - auto circ = ccb; - int i = ccb->data().origin; - - // Go to start of this 'origin CCB'. Connected component of the boundary that all originates from the same shape. - while (true) { - auto prev = circ; - --prev; - if (prev->data().origin == i) { - circ = prev; - } else { - break; - } - } - - // Next, make a polyline for every connected part of the boundary that originates from the same shape. - std::vector xm_curves; - auto curr = circ; - do { - if (curr->data().origin == i) { - xm_curves.push_back(curr->curve()); - } else { - polylines.emplace_back(CSPolyline(xm_curves.begin(), xm_curves.end()), i); - xm_curves.clear(); - i = curr->data().origin; - xm_curves.push_back(curr->curve()); - } - } while (++curr != circ); - if (!xm_curves.empty()) { - polylines.emplace_back(CSPolyline(xm_curves.begin(), xm_curves.end()), i); - xm_curves.clear(); - } - - result.push_back(polylines); - } - - return result; -} - /// Returns parts of the boundary of c that originate from i. /// This function assumes that some part of the boundary, but not all of the boundary, originates from i. std::vector boundaryParts(const Component& c, int i) { @@ -641,7 +648,7 @@ std::vector boundaryParts(const Component& c, int i) { std::vector polylines; for (const auto& ccb : ccbs) { - boundaryParts(ccb, i, std::back_inserter(polylines)); + boundaryParts(ccb, i, std::back_inserter(polylines)); } return polylines; @@ -657,7 +664,7 @@ std::vector boundaryParts(FaceH fh, int i) { std::vector polylines; for (const auto& ccb : ccbs) { - boundaryParts(ccb, i, std::back_inserter(polylines)); + boundaryParts(ccb, i, std::back_inserter(polylines)); } return polylines; @@ -796,7 +803,9 @@ void DilatedPatternDrawing::drawFaceFill(FaceH fh, renderer::GeometryRenderer& r auto poly = face_to_polygon(*fh); renderer.draw(renderPath(poly)); } else { - renderer.draw(renderPath(d.morphedFace[i])); + for (const auto& p : d.morphedFace[i]) { + renderer.draw(renderPath(p)); + } } } } @@ -837,7 +846,9 @@ void DilatedPatternDrawing::drawFaceStroke(FaceH fh, renderer::GeometryRenderer& nothingVisible = true; break; } else { - polySet.difference(d.morphedFace[j]); + for (const auto& p : d.morphedFace[j]) { + polySet.difference(p); + } } } @@ -1072,7 +1083,7 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { do { auto prev = start; --prev; - if (prev->data().origin == start->data().origin) { + if (prev->data().origins.front() == start->data().origins.front()) { // todo: check for multiple origins start = prev; } else { break; diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 30353013..0b263c27 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -50,12 +50,12 @@ struct FaceData { std::vector> relations; std::vector ordering; std::unordered_map> morphedEdges; - std::unordered_map morphedFace; + std::unordered_map> morphedFace; }; // todo: edge case where edges of dilated patterns overlap, so a half-edge may have multiple origins. struct HalfEdgeData { - int origin; + std::vector origins; }; struct VertexData { @@ -246,13 +246,18 @@ class Component { std::vector m_inner_ccbs; }; -template +template void boundaryParts(Ccb ccb, int i, OutputIterator out) { // First find a half-edge at the start of a 'part' of this CCB (connected component of the boundary). auto circ = ccb; + auto originates_from = [](Ccb circ, int i) { + auto& os = circ->data().origins; + return std::find(os.begin(), os.end(), i) != os.end(); + }; + bool found = true; - while (circ->data().origin != i) { + while (!originates_from(circ, i)) { ++circ; if (circ == ccb) { found = false; @@ -268,20 +273,28 @@ void boundaryParts(Ccb ccb, int i, OutputIterator out) { do { auto prev = circ; --prev; - if (prev->data().origin == i) { + if (originates_from(prev, i)) { circ = prev; } else { break; } } while (circ != start); - assert(circ->data().origin == i); + assert(originates_from(circ, i)); + + Traits traits; + auto opposite = traits.construct_opposite_2_object(); // Next, make a polyline for every connected part of the boundary that originates from i. std::vector xm_curves; auto curr = circ; do { - if (curr->data().origin == i) { - xm_curves.push_back(curr->curve()); + if (originates_from(curr, i)) { + auto& curve = curr->curve(); + if (curr->source()->point() == curve.source()) { + xm_curves.push_back(curve); + } else { + xm_curves.push_back(opposite(curve)); + } } else { if (!xm_curves.empty()) { ++out = CSPolyline(xm_curves.begin(), xm_curves.end()); @@ -303,8 +316,10 @@ std::vector>> originCcbs(const Component& std::vector>>> connectedDisks(const std::vector>& disks); CSPolygon thinRectangle(const Point& p, const OneRootPoint& n, const Number& w); -CSPolyline morph(const CSPolyline& boundaryPart, const CSPolygon& componentShape, const std::vector>& inclDisks, - const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds); +CSPolygon morph(const std::vector& boundaryParts, const CSPolygon& componentShape, const std::vector>& inclDisks, + const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds); + +CSPolyline associatedBoundary(CSPolygon component, CSPolygon morphedComponent, CSPolyline boundaryPart); struct IncludeExcludeDisks { std::vector> include; diff --git a/cartocrow/simplesets/helpers/approximate_convex_hull.cpp b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp index 21d44325..f7aaff91 100644 --- a/cartocrow/simplesets/helpers/approximate_convex_hull.cpp +++ b/cartocrow/simplesets/helpers/approximate_convex_hull.cpp @@ -162,6 +162,7 @@ algebraicCircleTangentToRationalSegments(const CSTraits::Point_2& p1, const CSTr } // This is hacky + lower_tan_half_phi -= M_EPSILON; upper_tan_half_phi += M_EPSILON; auto sqr_tan_half_phi = CGAL::square (lower_tan_half_phi); @@ -227,6 +228,10 @@ std::vector circlesOnConvexHull(const std::vectorsite(); + return {{site.point(), site.weight()}}; + } auto circ = apo.incident_vertices(apo.infinite_vertex()); auto curr = circ; std::vector hullCircles; @@ -239,7 +244,10 @@ std::vector circlesOnConvexHull(const std::vector>& circles) { + // todo: approximating circle radii may cause problems when two circles overlap in a single point and one is contained in the other. + // solution? filter out any circle that is contained in another, before approximating the radii. if (circles.size() == 1) { return circleToCSPolygon(circles.front()); } @@ -248,6 +256,13 @@ CSPolygon approximateConvexHull(const std::vector>& circles) { rrCircles.push_back(approximateRadiusCircle(c)); } auto hullCircles = circlesOnConvexHull(rrCircles); + if (hullCircles.size() == 1) { + for (const auto& c : circles) { + if (c.center() == hullCircles[0].center) { + return circleToCSPolygon(c); + } + } + } std::vector>> tangents; diff --git a/cartocrow/simplesets/helpers/arrangement_helpers.h b/cartocrow/simplesets/helpers/arrangement_helpers.h index 8f255af5..9a17743c 100644 --- a/cartocrow/simplesets/helpers/arrangement_helpers.h +++ b/cartocrow/simplesets/helpers/arrangement_helpers.h @@ -6,6 +6,8 @@ template CGAL::General_polygon_2 ccb_to_polygon(Ccb ccb) { + Traits traits; + auto opposite = traits.construct_opposite_2_object(); auto curr = ccb; std::vector x_monotone_curves; @@ -14,8 +16,6 @@ CGAL::General_polygon_2 ccb_to_polygon(Ccb ccb) { if (curr->source()->point() == curve.source()) { x_monotone_curves.push_back(curve); } else { - Traits traits; - auto opposite = traits.construct_opposite_2_object(); x_monotone_curves.push_back(opposite(curve)); } } while(++curr != ccb); diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp index 4867fb0d..46dc4262 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.cpp @@ -136,6 +136,7 @@ bool on_or_inside(const CSPolygon& polygon, const Point& point) { auto inter = CGAL::intersection(ray, rect); if (!inter.has_value()) return false; + if (inter->type() == typeid(Point)) return true; auto seg = boost::get>(*inter); X_monotone_curve_2 seg_xm_curve(seg.source(), seg.target()); @@ -148,7 +149,27 @@ bool on_or_inside(const CSPolygon& polygon, const Point& point) { curve.intersect(seg_xm_curve, std::back_inserter(intersection_results)); } - return intersection_results.size() % 2 == 1; + int count = 0; + for (const auto& ir : intersection_results) { + if (ir.which() == 0) { + auto ip = get(ir); + //(ir) Intersection points are double-counted, so increase count by half. + bool found = false; + for (auto cit = polygon.curves_begin(); cit != polygon.curves_end(); ++cit) { + if (cit->source() == ip.first) { + found = true; + break; + } + } + if (found) { + count += 1; + continue; + } + } + count += 2; + } + + return count % 4 != 0; } bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon) { @@ -197,6 +218,10 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon) { return true; } +bool inside(const CSPolygon& polygon, const Point& point) { + return on_or_inside(polygon, point) && !liesOn(point, polygon); +} + CSPolycurve arrPolycurveFromCSPolygon(const CSPolygon& polygon) { return arrPolycurveFromXMCurves(polygon.curves_begin(), polygon.curves_end()); } diff --git a/cartocrow/simplesets/helpers/cs_polygon_helpers.h b/cartocrow/simplesets/helpers/cs_polygon_helpers.h index f1def966..0224ae6b 100644 --- a/cartocrow/simplesets/helpers/cs_polygon_helpers.h +++ b/cartocrow/simplesets/helpers/cs_polygon_helpers.h @@ -32,6 +32,7 @@ bool liesOn(const X_monotone_curve_2& c, const CSPolygon& polygon); renderer::RenderPath renderPath(const CSPolygon& polygon); renderer::RenderPath renderPath(const CSPolygonWithHoles& withHoles); bool on_or_inside(const CSPolygon& polygon, const Point& point); +bool inside(const CSPolygon& polygon, const Point& point); CSPolycurve arrPolycurveFromCSPolygon(const CSPolygon& polygon); Polygon linearSample(const CSPolygon& polygon, int n); CSPolygonWithHoles approximateDilate(const CSPolygon& polygon, double r, double eps, int n); diff --git a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h index 4efa116c..87221a1b 100644 --- a/cartocrow/simplesets/helpers/poly_line_gon_intersection.h +++ b/cartocrow/simplesets/helpers/poly_line_gon_intersection.h @@ -169,28 +169,36 @@ void intersection(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputI while (!line_edges_keep.empty()) { // Find first edge on connected component of polyline (in the intersection with polygon) auto start = line_edges_keep.front(); - auto curr = start; - while (originatesFromPolyline(curr->prev())) { - curr = curr->prev(); + auto startStart = start; + while (originatesFromPolyline(startStart->prev())) { + startStart = startStart->prev(); - if (curr == start) { + if (startStart == start) { break; } } std::vector xmcs; auto last_it = line_edges_keep.end(); + auto curr = startStart; do { last_it = std::remove(line_edges_keep.begin(), last_it, curr); xmcs.push_back(curr->curve()); curr = curr->next(); - } while (originatesFromPolyline(curr)); + } while (originatesFromPolyline(curr) && curr != startStart); line_edges_keep.erase(last_it, line_edges_keep.end()); ++out = CSPolyline(xmcs.begin(), xmcs.end()); } } +template +void intersection(const CSPolyline& line, const CSPolygon& gon, OutputIterator out, bool difference, bool keepOverlap) { + CSPolygonWithHoles withHoles(gon); + return intersection(line, withHoles, out, difference, keepOverlap); +} + // May crash due to CGAL bug: https://github.com/CGAL/cgal/issues/8468 +#if 0 template void intersectionCrashes(const CSPolyline& line, const CSPolygonWithHoles& gon, OutputIterator out, bool difference, bool keepOverlap) { PolyCSTraits traits; @@ -304,6 +312,7 @@ void intersectionCrashes(const CSPolyline& line, const CSPolygonWithHoles& gon, ++out = CSPolyline(xmcs.begin(), xmcs.end()); } } +#endif } #endif //CARTOCROW_POLY_LINE_GON_INTERSECTION_H diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp index 729e8fb9..5ed86afe 100644 --- a/cartocrow/simplesets/partition_algorithm.cpp +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -133,7 +133,9 @@ partition(const std::vector& points, const GeneralSettings& gs, const for (const CatPoint& pt : points) { // Check if a point is too close to the segment if (pt != p.catPoint() && pt != q.catPoint() && - CGAL::squared_distance(seg, pt.point) < squared(ps.admissableRadiusFactor * gs.dilationRadius())) { + CGAL::squared_distance(seg, pt.point) < squared(ps.admissableRadiusFactor * gs.dilationRadius()) && + CGAL::squared_distance(seg, pt.point) < CGAL::min(CGAL::squared_distance(p.catPoint().point, pt.point), + CGAL::squared_distance(q.catPoint().point, pt.point)) - M_EPSILON) { tooClose = true; break; } @@ -188,10 +190,20 @@ partition(const std::vector& points, const GeneralSettings& gs, const bool tooClose = false; // Check whether any point is too close to the result pattern for (const auto& pt : points) { - if (std::find(newPts.begin(), newPts.end(), pt) == newPts.end() && - squared_distance(newPoly, pt.point) < squared(ps.admissableRadiusFactor * gs.dilationRadius())) { - tooClose = true; - break; + if (std::find(newPts.begin(), newPts.end(), pt) == newPts.end()) { + auto polyPtDist = squared_distance(newPoly, pt.point); + std::optional> pointPtDist; + for (auto np : newPts) { + auto d = CGAL::squared_distance(np.point, pt.point); + if (d < pointPtDist) { + pointPtDist = d; + } + } + if (polyPtDist < squared(ps.admissableRadiusFactor * gs.dilationRadius()) && + polyPtDist < pointPtDist) { + tooClose = true; + break; + } } } if (tooClose) continue; diff --git a/demos/simplesets/simplesets_demo.cpp b/demos/simplesets/simplesets_demo.cpp index 2c7b47f5..08422ebf 100644 --- a/demos/simplesets/simplesets_demo.cpp +++ b/demos/simplesets/simplesets_demo.cpp @@ -100,7 +100,7 @@ SimpleSetsDemo::SimpleSetsDemo() { vLayout->addWidget(ptSizeLabel); auto* ptSizeSlider = new QSlider(Qt::Orientation::Horizontal); vLayout->addWidget(ptSizeSlider); - ptSizeSlider->setMinimum(0); + ptSizeSlider->setMinimum(1); ptSizeSlider->setMaximum(80); ptSizeSlider->setValue(static_cast(m_gs.pointSize * 10)); @@ -192,9 +192,22 @@ void SimpleSetsDemo::computeDrawing(double cover) { auto pp = std::make_shared(m_partition, m_gs, m_ds); m_renderer->addPainting(pp, "Partition"); - m_dpd = std::make_shared(m_partition, m_gs, m_cds); - auto ap = std::make_shared(*m_dpd, m_ds); - m_renderer->addPainting(ap, "Arrangement"); + bool wellSeparated = true; + for (const auto& p : m_points) { + for (const auto& q : m_points) { + if (p.category == q.category) continue; + if (CGAL::squared_distance(p.point, q.point) < 4 * m_gs.pointSize * m_gs.pointSize) { + wellSeparated = false; + } + } + } + if (wellSeparated) { + m_dpd = std::make_shared(m_partition, m_gs, m_cds); + auto ap = std::make_shared(*m_dpd, m_ds); + m_renderer->addPainting(ap, "Arrangement"); + } else { + std::cerr << "Points of different category are too close together; not computing a drawing." << std::endl; + } } int main(int argc, char* argv[]) { From 88f3da94d02b0dfa0c4b9541fd7d8770ce6d2129 Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Mon, 7 Oct 2024 19:21:11 +0200 Subject: [PATCH 34/36] When hyperedge has cycle, ignore preferences instead of crashing --- cartocrow/simplesets/drawing_algorithm.cpp | 34 +++++++++++++--------- cartocrow/simplesets/drawing_algorithm.h | 5 +++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index b4fd980a..9f9d6286 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -303,12 +303,16 @@ DilatedPatternDrawing::DilatedPatternDrawing(const Partition& partition, const G auto hEdges = hyperedges(); for (auto edge : hEdges) { - auto order = getRelationOrder(edge); + auto order = getRelationOrder(*edge); if (!order.has_value()) { - throw std::runtime_error("Hyperedge has cycle."); - } else { - setRelationOrder(edge, *order); + std::cerr << "Hyperedge has cycle; ignoring preferences in this hyperedge." << std::endl; + for (const auto& r : edge->relations) { + r->ordering = Order::EQUAL; + } + order = getRelationOrder(*edge); + assert(order.has_value()); } + setRelationOrder(*edge, *order); } for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { @@ -552,7 +556,7 @@ CSPolygon morph(const std::vector& boundaryParts, const CSPolygon& c for (const auto& d : rectangleCutDisks) { auto n = nearestOnBoundary(d.center()); - auto rect = thinRectangle(d.center(), n, gs.pointSize / 5); + auto rect = thinRectangle(d.center(), n, gs.pointSize); polygonSet.join(rect); } @@ -906,7 +910,7 @@ DilatedPatternDrawing::intersectionComponents(int i) const { }); } -std::vector DilatedPatternDrawing::hyperedges() const { +std::vector> DilatedPatternDrawing::hyperedges() const { std::vector interesting; for (auto fit = m_arr.faces_begin(); fit != m_arr.faces_end(); ++fit) { @@ -919,31 +923,35 @@ std::vector DilatedPatternDrawing::hyperedges() const { return fh1->data().origins.size() < fh2->data().origins.size(); }); - std::vector> hyperedgesGrouped; + std::vector>> hyperedgesGrouped; - std::vector group; + std::vector> group; std::optional lastSize; for (const auto& fh : interesting) { if (lastSize.has_value() && fh->data().origins.size() != *lastSize && !group.empty()) { hyperedgesGrouped.push_back(group); group.clear(); } - group.emplace_back(fh->data().origins, fh->data().relations); + auto he = std::make_shared(fh->data().origins, fh->data().relations); + for (auto& r : he->relations) { + r->hyperedges.push_back(he); + } + group.push_back(he); lastSize = fh->data().origins.size(); } if (!group.empty()) { hyperedgesGrouped.push_back(group); } - std::vector> trashCan; + std::vector>> trashCan; for (int i = 0; i < hyperedgesGrouped.size(); i++) { if (i + 1 >= hyperedgesGrouped.size()) break; const auto& group = hyperedgesGrouped[i]; for (const auto& hyperedge : group) { for (const auto& larger : hyperedgesGrouped[i+1]) { bool fullyContained = true; - for (const auto& r : hyperedge.relations) { - if (std::find(larger.relations.begin(), larger.relations.end(), r) == larger.relations.end()) { + for (const auto& r : hyperedge->relations) { + if (std::find(larger->relations.begin(), larger->relations.end(), r) == larger->relations.end()) { fullyContained = false; } } @@ -961,7 +969,7 @@ std::vector DilatedPatternDrawing::hyperedges() const { group.erase(std::remove(group.begin(), group.end(), r), group.end()); } - std::vector hyperedges; + std::vector> hyperedges; for (const auto& group : hyperedgesGrouped) { std::copy(group.begin(), group.end(), std::back_inserter(hyperedges)); } diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 0b263c27..e963c544 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -19,11 +19,14 @@ std::string to_string(Order ord); std::ostream& operator<<(std::ostream& out, const Order& o); +struct Hyperedge; + struct Relation { int left; int right; Order preference; Order ordering; + std::vector> hyperedges; Relation(int left, int right, Order preference, Order ordering) : left(left), right(right), preference(preference), ordering(ordering) {} }; @@ -339,7 +342,7 @@ class DilatedPatternDrawing { IncludeExcludeDisks includeExcludeDisks(int i, int j, const Component& c) const; IncludeExcludeDisks includeExcludeDisks(int i, const std::unordered_set& js, const Component& c) const; - std::vector hyperedges() const; + std::vector> hyperedges() const; void drawFaceFill(FaceH fh, renderer::GeometryRenderer& renderer, const GeneralSettings& gs, const DrawSettings& ds) const; From a97f3ac77b1f95d0a565510ebd4bbdaa86894b8a Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Mon, 7 Oct 2024 21:59:01 +0200 Subject: [PATCH 35/36] Fix stacking order drawing --- cartocrow/simplesets/drawing_algorithm.cpp | 64 +++++++++---------- cartocrow/simplesets/drawing_algorithm.h | 8 +++ .../simplesets/helpers/cs_curve_helpers.cpp | 2 +- .../simplesets/helpers/cs_curve_helpers.h | 2 +- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index 9f9d6286..af2632c1 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -1076,8 +1076,7 @@ SimpleSetsPainting::SimpleSetsPainting(const DilatedPatternDrawing& dpd, const D void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { auto stackingOrder = m_dpd.totalStackingOrder(); // If there is a stacking order, draw the complete patterns stacked in that order - if (false) { -// if (stackingOrder.has_value()) { + if (stackingOrder.has_value()) { for (int i : *stackingOrder) { auto comps = connectedComponents(m_dpd.m_arr, [i](const FaceH& fh) { const auto& ors = fh->data().origins; @@ -1085,44 +1084,39 @@ void SimpleSetsPainting::paint(renderer::GeometryRenderer& renderer) const { }); assert(comps.size() == 1); const auto& comp = comps[0]; - auto ccb = comp.outer_ccb(); - auto start = ccb; - // start at first edge on CCB of origin - do { - auto prev = start; - --prev; - if (prev->data().origins.front() == start->data().origins.front()) { // todo: check for multiple origins - start = prev; + + std::vector boundaryPieces; + for (auto fit = comp.faces_begin(); fit != comp.faces_end(); ++fit) { + auto& data = fit->data(); + if (data.morphedFace.contains(i)) { + std::copy(data.morphedEdges[i].begin(), data.morphedEdges[i].end(), std::back_inserter(boundaryPieces)); } else { - break; + FaceH fh = fit.handle(); + auto bps = boundaryParts(fh, i); + std::copy(bps.begin(), bps.end(), std::back_inserter(boundaryPieces)); } - } while (start != ccb); - - auto curr = start; + } + // no order or hash on OneRootPoint :( +// std::map sourceToI; +// for (int j = 0; j < boundaryPieces.size(); ++j) { +// auto& bp = boundaryPieces[j]; +// sourceToI[bp.curves_begin()->source()] = j; +// } std::vector xm_curves; - bool doneInFace = false; - FaceH prevFace; - do { - if (doneInFace && curr->face() == prevFace) continue; - doneInFace = false; - const auto& d = curr->face()->data(); - if (!d.morphedEdges.contains(i)) { - xm_curves.push_back(curr->curve()); - } else { - auto mes = d.morphedEdges.at(i); - CSTraits traits; - auto equal = traits.equal_2_object(); - auto it = std::find_if(mes.begin(), mes.end(), [&curr, &equal](const CSPolyline& pl) { - return equal(pl.curves_begin()->source(), curr->source()->point()); - }); - assert(it != mes.end()); // this assertion fails sometimes - auto pl = *it; - std::copy(pl.curves_begin(), pl.curves_end(), std::back_inserter(xm_curves)); - doneInFace = true; - prevFace = curr->face(); + + std::copy(boundaryPieces[0].curves_begin(), boundaryPieces[0].curves_end(), std::back_inserter(xm_curves)); + int count = 1; + while (count < boundaryPieces.size()) { + auto head = xm_curves.back().target(); + for (const auto& bp : boundaryPieces) { + if (bp.curves_begin()->source() == head) { + std::copy(bp.curves_begin(), bp.curves_end(), std::back_inserter(xm_curves)); + } } - } while (++curr != start); + ++count; + } + CSPolygon csPolygon(xm_curves.begin(), xm_curves.end()); renderer.setMode(GeometryRenderer::fill | GeometryRenderer::stroke); diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index e963c544..9de3689e 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -187,6 +187,14 @@ class Component { return tmp; } + pointer ptr() const { + return *m_faceHandleIterator; + } + + pointer handle() const { + return *m_faceHandleIterator; + } + bool operator==(const Self& other) const { return m_faceHandleIterator == other.m_faceHandleIterator; } diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp index 9151d7f8..b26a4b78 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.cpp +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.cpp @@ -118,7 +118,7 @@ bool liesOn(const X_monotone_curve_2& c1, const X_monotone_curve_2& c2) { return true; } -renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve) { +renderer::RenderPath renderPath(const X_monotone_curve_2& xm_curve) { renderer::RenderPath path; path.moveTo(approximateAlgebraic(xm_curve.source())); diff --git a/cartocrow/simplesets/helpers/cs_curve_helpers.h b/cartocrow/simplesets/helpers/cs_curve_helpers.h index f035acfc..a2d14c67 100644 --- a/cartocrow/simplesets/helpers/cs_curve_helpers.h +++ b/cartocrow/simplesets/helpers/cs_curve_helpers.h @@ -35,7 +35,7 @@ OneRootPoint nearest(const X_monotone_curve_2& xm_curve, const Point& poi bool liesOn(const Point& p, const X_monotone_curve_2& xm_curve); bool liesOn(const OneRootPoint& p, const X_monotone_curve_2& xm_curve); -renderer::RenderPath renderPathFromXMCurve(const X_monotone_curve_2& xm_curve); +renderer::RenderPath renderPath(const X_monotone_curve_2& xm_curve); void addToRenderPath(const X_monotone_curve_2& xm_curve, renderer::RenderPath& path, bool& first); void addToRenderPath(const Curve_2& curve, renderer::RenderPath& path, bool& first); Curve_2 toCurve(const X_monotone_curve_2& xmc); From 8395d51546d05836953d759d160dde4f30e5fa3d Mon Sep 17 00:00:00 2001 From: Yvee1 Date: Tue, 8 Oct 2024 11:38:39 +0200 Subject: [PATCH 36/36] Fix admissible check for non-matchings Also fix spelling: admissable -> admissible --- cartocrow/simplesets/drawing_algorithm.cpp | 16 ++++++++-------- cartocrow/simplesets/drawing_algorithm.h | 2 +- cartocrow/simplesets/partition_algorithm.cpp | 6 +++--- cartocrow/simplesets/settings.h | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cartocrow/simplesets/drawing_algorithm.cpp b/cartocrow/simplesets/drawing_algorithm.cpp index af2632c1..3fd6cedc 100644 --- a/cartocrow/simplesets/drawing_algorithm.cpp +++ b/cartocrow/simplesets/drawing_algorithm.cpp @@ -589,7 +589,7 @@ CSPolygon morph(const std::vector& boundaryParts, const CSPolygon& c return outer; } -CSPolyline associatedBoundary(CSPolygon component, CSPolygon morphedComponent, CSPolyline boundaryPart) { +CSPolyline associatedBoundary(const CSPolygon& component, const CSPolygon& morphedComponent, const CSPolyline& boundaryPart) { std::vector morphed_xm_curves; for (auto cit = morphedComponent.curves_begin(); cit != morphedComponent.curves_end(); ++cit) { morphed_xm_curves.push_back(*cit); @@ -925,22 +925,22 @@ std::vector> DilatedPatternDrawing::hyperedges() cons std::vector>> hyperedgesGrouped; - std::vector> group; + std::vector> currentGroup; std::optional lastSize; for (const auto& fh : interesting) { - if (lastSize.has_value() && fh->data().origins.size() != *lastSize && !group.empty()) { - hyperedgesGrouped.push_back(group); - group.clear(); + if (lastSize.has_value() && fh->data().origins.size() != *lastSize && !currentGroup.empty()) { + hyperedgesGrouped.push_back(currentGroup); + currentGroup.clear(); } auto he = std::make_shared(fh->data().origins, fh->data().relations); for (auto& r : he->relations) { r->hyperedges.push_back(he); } - group.push_back(he); + currentGroup.push_back(he); lastSize = fh->data().origins.size(); } - if (!group.empty()) { - hyperedgesGrouped.push_back(group); + if (!currentGroup.empty()) { + hyperedgesGrouped.push_back(currentGroup); } std::vector>> trashCan; diff --git a/cartocrow/simplesets/drawing_algorithm.h b/cartocrow/simplesets/drawing_algorithm.h index 9de3689e..5c4ee69b 100644 --- a/cartocrow/simplesets/drawing_algorithm.h +++ b/cartocrow/simplesets/drawing_algorithm.h @@ -330,7 +330,7 @@ CSPolygon thinRectangle(const Point& p, const OneRootPoint& n, const Numb CSPolygon morph(const std::vector& boundaryParts, const CSPolygon& componentShape, const std::vector>& inclDisks, const std::vector>& exclDisks, const GeneralSettings& gs, const ComputeDrawingSettings& cds); -CSPolyline associatedBoundary(CSPolygon component, CSPolygon morphedComponent, CSPolyline boundaryPart); +CSPolyline associatedBoundary(const CSPolygon& component, const CSPolygon& morphedComponent, const CSPolyline& boundaryPart); struct IncludeExcludeDisks { std::vector> include; diff --git a/cartocrow/simplesets/partition_algorithm.cpp b/cartocrow/simplesets/partition_algorithm.cpp index 5ed86afe..245984bc 100644 --- a/cartocrow/simplesets/partition_algorithm.cpp +++ b/cartocrow/simplesets/partition_algorithm.cpp @@ -133,7 +133,7 @@ partition(const std::vector& points, const GeneralSettings& gs, const for (const CatPoint& pt : points) { // Check if a point is too close to the segment if (pt != p.catPoint() && pt != q.catPoint() && - CGAL::squared_distance(seg, pt.point) < squared(ps.admissableRadiusFactor * gs.dilationRadius()) && + CGAL::squared_distance(seg, pt.point) < squared(ps.admissibleRadiusFactor * gs.dilationRadius()) && CGAL::squared_distance(seg, pt.point) < CGAL::min(CGAL::squared_distance(p.catPoint().point, pt.point), CGAL::squared_distance(q.catPoint().point, pt.point)) - M_EPSILON) { tooClose = true; @@ -195,11 +195,11 @@ partition(const std::vector& points, const GeneralSettings& gs, const std::optional> pointPtDist; for (auto np : newPts) { auto d = CGAL::squared_distance(np.point, pt.point); - if (d < pointPtDist) { + if (!pointPtDist.has_value() || d < *pointPtDist) { pointPtDist = d; } } - if (polyPtDist < squared(ps.admissableRadiusFactor * gs.dilationRadius()) && + if (polyPtDist < squared(ps.admissibleRadiusFactor * gs.dilationRadius()) && polyPtDist < pointPtDist) { tooClose = true; break; diff --git a/cartocrow/simplesets/settings.h b/cartocrow/simplesets/settings.h index 0e3598bc..802271aa 100644 --- a/cartocrow/simplesets/settings.h +++ b/cartocrow/simplesets/settings.h @@ -30,8 +30,8 @@ struct PartitionSettings { bool regularityDelay; /// Delay merges that create patterns that intersect points. bool intersectionDelay; - /// Disallow merges that have a point within distance admissableFactor * dilationRadius. - Number admissableRadiusFactor; + /// Disallow merges that have a point within distance admissibleRadiusFactor * dilationRadius. + Number admissibleRadiusFactor; }; struct ComputeDrawingSettings {