diff --git a/src/ifcchat/style.css b/src/ifcchat/style.css
index ef5f6add8f..9d3ba064c9 100644
--- a/src/ifcchat/style.css
+++ b/src/ifcchat/style.css
@@ -53,6 +53,23 @@ main {
margin: 10px 0;
}
+.thinking-indicator {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ color: #555;
+ font-size: 80%;
+}
+
+.thinking-indicator[hidden] {
+ display: none;
+}
+
+.thinking-indicator .spinner {
+ width: 14px;
+ height: 14px;
+}
+
.msg .role {
font-size: 12px;
opacity: 0.7;
diff --git a/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp b/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp
index 180226fb82..f07234a8f1 100644
--- a/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp
+++ b/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp
@@ -52,6 +52,15 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPointByDistanceExpression* i
if (inst->OffsetVertical().has_value()) {
auto offset_vertical = inst->OffsetVertical().get() * length_unit_;
o += offset_vertical * z;
+
+ auto tmp1 = (z * offset_vertical).eval();
+ auto tmp2 = (Eigen::Vector3d(0, 0, 1) * offset_vertical).eval();
+ auto tmp3 = (tmp1 - tmp2).eval();
+
+ std::ostringstream oss;
+ oss << "local z: " << z.x() << "," << z.y() << "," << z.z() << "; delta: " << tmp3.x() << "," << tmp3.y() << "," << tmp3.z();
+ auto osss = oss.str();
+ std::wcout << osss.c_str() << std::endl;
}
if (inst->OffsetLongitudinal().has_value()) {
diff --git a/src/ifcopenshell-python/ifcopenshell/draw.py b/src/ifcopenshell-python/ifcopenshell/draw.py
index 5f6d761ceb..962dbbb34f 100644
--- a/src/ifcopenshell-python/ifcopenshell/draw.py
+++ b/src/ifcopenshell-python/ifcopenshell/draw.py
@@ -42,6 +42,7 @@ WHITE = numpy.array((1.0, 1.0, 1.0))
DO_NOTHING = lambda *args: None
+ARRANGE_POLYGON_SETTINGS = W.arrange_polygon_settings() if hasattr(W, 'arrange_polygon_settings') else None
@dataclass
class draw_settings:
@@ -536,7 +537,7 @@ def main(
*(tup for i, tup in enumerate(zip(path_objects, section_polies, polies)) if has_relevant_zone(i))
)
- arranged = W.arrange_polygons(polies)
+ arranged = W.arrange_polygons(*filter(None, (ARRANGE_POLYGON_SETTINGS,)), polies)
svg_data_3 = W.polygons_to_svg(arranged, False)
dom3 = parseString(svg_data_3)
svg3 = dom3.childNodes[0]
diff --git a/src/ifcparse/IfcSchema.h b/src/ifcparse/IfcSchema.h
index 3dedd47a8e..349a81532d 100644
--- a/src/ifcparse/IfcSchema.h
+++ b/src/ifcparse/IfcSchema.h
@@ -358,6 +358,7 @@ class IFC_PARSE_API entity : public declaration {
const std::vector
& subtypes() const { return subtypes_; }
const std::vector& attributes() const { return attributes_; }
+ const std::vector& inverse_attributes() const { return inverse_attributes_; }
const std::vector& derived() const { return derived_; }
const std::vector all_attributes() const {
diff --git a/src/ifcwrap/IfcGeomWrapper.i b/src/ifcwrap/IfcGeomWrapper.i
index e155c2837e..e992e4beab 100644
--- a/src/ifcwrap/IfcGeomWrapper.i
+++ b/src/ifcwrap/IfcGeomWrapper.i
@@ -1166,6 +1166,7 @@ ifcopenshell::geometry::taxonomy::item::ptr try_upcast(PyObject* obj0, swig_type
%ignore svgfill::line_segments_to_polygons;
%ignore svgfill::svg_to_polygons;
%ignore svgfill::arrange_polygons;
+%ignore svgfill::abstract_arrangement;
%template(svg_line_segments) std::vector>;
%template(svg_groups_of_line_segments) std::vector>>;
@@ -1287,9 +1288,9 @@ ifcopenshell::geometry::taxonomy::item::ptr try_upcast(PyObject* obj0, swig_type
}
}
- std::vector arrange_polygons(const std::vector& polygons) {
+ std::vector arrange_polygons(svgfill::arrange_polygon_settings settings, const std::vector& polygons) {
std::vector r;
- if (svgfill::arrange_polygons(polygons, r)) {
+ if (svgfill::arrange_polygons(settings, polygons, r)) {
return r;
} else {
throw std::runtime_error("Failed to arrange polygons");
diff --git a/src/svgfill/src/arrange_polygons.cpp b/src/svgfill/src/arrange_polygons.cpp
index 4fa20c68dc..3ce49011f7 100644
--- a/src/svgfill/src/arrange_polygons.cpp
+++ b/src/svgfill/src/arrange_polygons.cpp
@@ -350,6 +350,8 @@ find_overlaps(const std::vector& polygons) {
class DebugWriter {
public:
+ DebugWriter() : enabled_(false) {}
+
DebugWriter(bool enabled, const std::string& filename_prefix)
: enabled_(enabled) {
if (enabled_) {
@@ -368,6 +370,43 @@ class DebugWriter {
}
}
+ DebugWriter(const DebugWriter&) = delete;
+
+ DebugWriter(DebugWriter&& other) noexcept
+ : obj(std::move(other.obj)), vi(other.vi), svg(std::move(other.svg)), enabled_(other.enabled_), last_segment_name_(std::move(other.last_segment_name_))
+ {
+ other.enabled_ = false;
+ other.vi = 1;
+ other.last_segment_name_.clear();
+ }
+
+ DebugWriter& operator=(const DebugWriter&) = delete;
+
+ DebugWriter& operator=(DebugWriter&& other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
+ if (enabled_) {
+ svg << "\n";
+ obj << std::flush;
+ obj.close();
+ svg.close();
+ }
+
+ obj = std::move(other.obj);
+ svg = std::move(other.svg);
+ vi = other.vi;
+ enabled_ = other.enabled_;
+ last_segment_name_ = std::move(other.last_segment_name_);
+
+ other.enabled_ = false;
+ other.vi = 1;
+ other.last_segment_name_.clear();
+
+ return *this;
+ }
+
void write_polygon(const Polygon_2& polygon, const std::string& name) {
if (enabled_) {
write_polygon_to_obj_(obj, vi, true, polygon, name);
@@ -387,7 +426,7 @@ class DebugWriter {
obj << "l " << vi++;
obj << " " << vi++ << "\n";
- svg << "";
+ svg << "\n";
obj << std::flush;
}
@@ -468,7 +507,7 @@ class DebugWriter {
}
};
-void eliminate_overlaps(double OVERLAP_RESOLUTION_DISTANCE, std::vector& polygons) {
+void eliminate_overlaps(DebugWriter& debug_writer, double OVERLAP_RESOLUTION_DISTANCE, std::vector& polygons) {
// solve overlaps by means of subtraction
// loop over overlaps and subtract the smaller polygon from the larger one
@@ -576,11 +615,40 @@ void eliminate_overlaps(double OVERLAP_RESOLUTION_DISTANCE, std::vectorarea() << " " << poly2->area() << std::endl;
+
+ bool is_ = edge == std::make_pair(25, 27);
+
bool success = false;
if ((mp1 = maybe_take_first_if_single_item(create_and_convert_offset_polygon(OVERLAP_RESOLUTION_DISTANCE, *poly2)))) {
+ if (is_) {
+ debug_writer.write_polygon(*mp1, "mp1");
+ }
+ smooth_polygon(OVERLAP_RESOLUTION_DISTANCE / 100., *mp1);
+ if (is_) {
+ debug_writer.write_polygon(*mp1, "mp1b");
+ }
if ((mp2 = subtract_retain_largest(*poly1, *mp1))) {
+ if (is_) {
+ debug_writer.write_polygon(*mp2, "mp2");
+ }
+ smooth_polygon(OVERLAP_RESOLUTION_DISTANCE / 100., *mp2);
+ if (is_) {
+ debug_writer.write_polygon(*mp2, "mp2b");
+ }
if ((mp3 = maybe_take_first_if_single_item(create_and_convert_offset_polygon(OVERLAP_RESOLUTION_DISTANCE * 2, *mp2)))) {
+ if (is_) {
+ debug_writer.write_polygon(*mp3, "mp3");
+ }
+ smooth_polygon(OVERLAP_RESOLUTION_DISTANCE / 100., *mp3);
+ if (is_) {
+ debug_writer.write_polygon(*mp3, "mp3b");
+ }
if ((mp4 = subtract_retain_largest(*poly2, *mp3))) {
+ if (is_) {
+ debug_writer.write_polygon(*mp4, "mp4");
+ }
*poly1 = *mp2;
*poly2 = *mp4;
success = true;
@@ -589,6 +657,8 @@ void eliminate_overlaps(double OVERLAP_RESOLUTION_DISTANCE, std::vectorarea() << " " << poly2->area() << std::endl;
+
if (!success) {
eliminated_polies.insert(swap ? edge.first : edge.second);
continue;
@@ -777,14 +847,19 @@ Polygon_with_holes_2 subdivide_polygon(double max_distance, const Polygon_with_h
std::tuple<
std::map>,
std::map>,
- std::map, std::vector*>>>
-build_line_graph(const std::vector& input_polygons, SegmentLookup& segment_lookup, const std::vector& triangular_polygons) {
+ std::map, std::vector*>>,
+ std::map
+>
+build_line_graph(const std::vector& input_polygons, SegmentLookup& segment_lookup, const std::vector& triangular_polygons)
+{
+
// Build maps of triangle -> edge and edge -> triangle in order to do traversal on the 'corridor mesh'
std::map, std::vector*>> segment_to_facet;
std::map, std::vector*>> segment_to_input_facet;
std::map, Point_2> segment_to_midpoint;
std::map> midpoint_to_segment;
std::map*, std::vector>> facet_to_segment;
+ std::map midpoint_to_edge_length;
for (auto& tri : triangular_polygons) {
for (size_t i = 0; i < 3; ++i) {
@@ -813,6 +888,7 @@ build_line_graph(const std::vector& input_polygons, SegmentLookup& se
if (p1index != input_polygons.end() && p2index != input_polygons.end() && p1index != p2index) {
segment_to_midpoint[p.first] = center;
midpoint_to_segment[center] = p.first;
+ midpoint_to_edge_length[center] = std::sqrt(CGAL::to_double(CGAL::squared_distance(p.first.first, p.first.second)));
}
}
@@ -832,7 +908,7 @@ build_line_graph(const std::vector& input_polygons, SegmentLookup& se
}
}
- return {line_graph, midpoint_to_segment, segment_to_input_facet};
+ return {line_graph, midpoint_to_segment, segment_to_input_facet, midpoint_to_edge_length};
}
std::set> find_triangles(const std::map>& line_graph) {
@@ -1118,66 +1194,91 @@ std::list> extend_end_vertices_based_on_input(
const Graph2D& G,
const std::map>& midpoint_to_segment,
const std::map, std::vector*>>& segment_to_input_facet,
- const Polygon_list& inner_offset,
- const SegmentLookup& segment_lookup
+ const Polygon_list& outer_perimiter,
+ const SegmentLookup& segment_lookup,
+ const K::FT& max_projection_distance
){
std::list> constructed_segments;
- for (auto it = G.vertices_begin(); it != G.vertices_end(); ++it) {
- if (it->second.size() == 1) {
- auto& M = it->first;
+ std::set processed_vertices;
- const std::pair* q = nullptr;
+ while (true) {
+ // The idea was to peal off 1-degree vertices when projecting them did not result into
+ // nearby intersections with the outer perimiter. This in case there would be turns near
+ // the perimeter, which would be eliminated by pealing off the vertices, which would then
+ // require out of the loop because of invalidated iterators. For now we decided to stick
+ // to a projection of the vertex onto the perimeter segment when the projection distance
+ // exceeds a threshold.
+ bool broke_out = false;
- if (midpoint_to_segment.find(M) == midpoint_to_segment.end()) {
- typename K::FT min_sq_distance = std::numeric_limits::infinity();
- for (auto& pa : midpoint_to_segment) {
- if (CGAL::squared_distance(pa.first, M) < min_sq_distance) {
- q = &pa.second;
- min_sq_distance = CGAL::squared_distance(pa.first, M);
- }
+ for (auto it = G.vertices_begin(); it != G.vertices_end(); ++it) {
+ if (it->second.size() == 1) {
+ auto& M = it->first;
+
+ if (processed_vertices.find(M) != processed_vertices.end()) {
+ continue;
}
- } else {
- q = &midpoint_to_segment.find(M)->second;
- }
- if (q == nullptr) {
- continue;
- }
+ const std::pair* q = nullptr;
- bool handled_as_graph_path = false;
+ if (midpoint_to_segment.find(M) == midpoint_to_segment.end()) {
+ typename K::FT min_sq_distance = std::numeric_limits::infinity();
+ for (auto& pa : midpoint_to_segment) {
+ if (CGAL::squared_distance(pa.first, M) < min_sq_distance) {
+ q = &pa.second;
+ min_sq_distance = CGAL::squared_distance(pa.first, M);
+ }
+ }
+ } else {
+ q = &midpoint_to_segment.find(M)->second;
+ }
- // distance from unioned - shoot ray?
- if (segment_to_input_facet.find(*q)->second.size() == 2) {
- for (auto& bnd : inner_offset) {
- // if point M is contained in bnd interior:
- // if (!bnd.has_on_unbounded_side(M)) {
- if (bnd.has_on_bounded_side(M)) {
- auto& incoming = *it->second.begin();
- // create ray incoming -> M
- CGAL::Ray_2 ray(incoming, M - incoming);
- // intersect ray with boundary
- boost::optional> closest_segment;
- boost::optional> closest_intersection_point;
- K::FT sq_distance_along_ray = std::numeric_limits::infinity();
- for (auto jt = bnd.edges_begin(); jt != bnd.edges_end(); ++jt) {
- const auto& seg = *jt;
- auto x = CGAL::intersection(ray, seg);
- if (x) {
- if (auto* xp = variant_get>(&*x)) {
- auto dist = ((*xp) - M).squared_length();
- if (dist < sq_distance_along_ray) {
- closest_segment = seg;
- closest_intersection_point = *xp;
- sq_distance_along_ray = dist;
+ if (q == nullptr) {
+ continue;
+ }
+
+ bool handled_as_graph_path = false;
+
+ // distance from unioned - shoot ray?
+ if (segment_to_input_facet.find(*q)->second.size() == 2) {
+ for (auto& bnd : outer_perimiter) {
+ // if point M is contained in bnd interior:
+ // if (!bnd.has_on_unbounded_side(M)) {
+ if (bnd.has_on_bounded_side(M)) {
+ auto& incoming = *it->second.begin();
+ // create ray incoming -> M
+ CGAL::Ray_2 ray(incoming, M - incoming);
+
+ std::cerr << "Extending end vertex " << M << " along ray " << ray << " to boundary of input polygon" << std::endl;
+
+ // intersect ray with boundary
+ boost::optional> closest_segment;
+ boost::optional> closest_intersection_point;
+ K::FT sq_distance_along_ray = std::numeric_limits::infinity();
+ for (auto jt = bnd.edges_begin(); jt != bnd.edges_end(); ++jt) {
+ const auto& seg = *jt;
+ auto x = CGAL::intersection(ray, seg);
+ if (x) {
+ if (auto* xp = variant_get>(&*x)) {
+ auto dist = ((*xp) - M).squared_length();
+ std::cerr << " - found " << *xp << " on segment " << seg << " with distance " << std::sqrt(CGAL::to_double(dist)) << std::endl;
+ if (dist < sq_distance_along_ray) {
+ if (dist < (max_projection_distance * max_projection_distance)) {
+ closest_segment = seg;
+ closest_intersection_point = *xp;
+ sq_distance_along_ray = dist;
+ } else {
+
+ }
+ }
}
}
}
- }
- if (closest_intersection_point) {
- constructed_segments.push_front({M, *closest_intersection_point});
- break;
+ if (closest_intersection_point) {
+ constructed_segments.push_front({M, *closest_intersection_point});
+ processed_vertices.insert(M);
+ break;
#if 0
Graph2D GGG(bnd);
GGG.refine(*GGG.query(*closest_intersection_point, 0.01), *closest_intersection_point);
@@ -1217,12 +1318,33 @@ std::list> extend_end_vertices_based_on_input(
break;
}
#endif
- } else {
- std::cerr << "Warning: no intersection found when extending end vertex, this will likely result in invalid topology" << std::endl;
+ } else {
+
+ // Loop over boundary segments, and project point onto it, take the closest
+ K::FT closest_distance = std::numeric_limits::infinity();
+ boost::optional> closest_point;
+ for (auto& poly : outer_perimiter) {
+ for (auto jt = poly.edges_begin(); jt != poly.edges_end(); ++jt) {
+ auto seg = *jt;
+ auto Pp = seg.supporting_line().projection(M);
+ if (seg.has_on(Pp)) {
+ auto d = CGAL::squared_distance(Pp, M);
+ if (d < closest_distance) {
+ closest_distance = d;
+ closest_point = Pp;
+ }
+ }
+ }
+ }
+
+ if (closest_point) {
+ constructed_segments.push_front({M, *closest_point});
+ processed_vertices.insert(M);
+ }
+ }
}
}
}
- }
#if 0
if (!handled_as_graph_path) {
@@ -1267,6 +1389,11 @@ std::list> extend_end_vertices_based_on_input(
constructed_segments.push_front({avg, R});
}
#endif
+ }
+ }
+
+ if (!broke_out) {
+ break;
}
}
@@ -1355,8 +1482,6 @@ void fuse_corridor_halves_with_input(Arrangement_2& arr, Graph2D& G, SegmentL
}
}
-#include
-
class Segment_2_less {
public:
bool operator()(const Segment_2& a, const Segment_2& b) const {
@@ -1367,7 +1492,112 @@ class Segment_2_less {
}
};
-void clean_noisy_paths(Arrangement_2& arr, SegmentLookup& segment_lookup) {
+std::vector arrangement_cell_iou(Arrangement_2& left, Arrangement_2& right) {
+
+ using Walk_pl = CGAL::Arr_walk_along_line_point_location;
+ Walk_pl walk_pl(right);
+
+ std::set visited_faces_on_right;
+
+ std::vector return_values;
+
+ for (auto it = left.faces_begin(); it != left.faces_end(); ++it) {
+ if (!it->is_unbounded()) {
+ // convert arr facet to polygon with holes
+ auto polygon_exterior = circ_to_poly(it->outer_ccb());
+ Polygon_with_holes_2 pwh(polygon_exterior);
+ for (auto hit = it->inner_ccbs_begin(); hit != it->inner_ccbs_end(); ++hit) {
+ pwh.add_hole(circ_to_poly(*hit));
+ }
+
+ CGAL::Polygon_triangulation_decomposition_2 decompositor;
+ std::vector temp;
+ decompositor(pwh, std::back_inserter(temp));
+
+ std::set visited_points;
+
+ while (true) {
+ // select triangle edge that has largest squared edge length times distance from polygon exterior
+ K::FT max_score = -std::numeric_limits::infinity();
+ Point_2 best_point;
+ for (auto& tri : temp) {
+ for (size_t i = 0; i < 3; ++i) {
+ size_t j = (i + 1) % 3;
+ auto& pi = tri.vertex(i);
+ auto& pj = tri.vertex(j);
+
+ auto center_point = CGAL::ORIGIN + (((pi - CGAL::ORIGIN) + (pj - CGAL::ORIGIN)) / 2);
+
+ K::FT min_dist = std::numeric_limits::infinity();
+ for (auto eit = polygon_exterior.edges_begin(); eit != polygon_exterior.edges_end(); ++eit) {
+ auto ep = eit->source();
+ auto eq = eit->target();
+ Segment_2 seg(ep, eq);
+ auto dist = CGAL::squared_distance(center_point, seg);
+ if (dist < min_dist) {
+ min_dist = dist;
+ }
+ }
+
+ auto sq_length = CGAL::squared_distance(pi, pj);
+
+ auto score = sq_length * min_dist;
+ if (score > max_score && visited_points.count(center_point) == 0) {
+ max_score = score;
+ best_point = center_point;
+ }
+ }
+ }
+
+ auto res = walk_pl.locate(best_point);
+ if (auto* v = boost::get(&res)) {
+ if (visited_faces_on_right.count(*v) > 0) {
+ return_values.push_back(0);
+ } else {
+ // convert arr facet to polygon with holes
+ auto polygon_exterior = circ_to_poly((*v)->outer_ccb());
+ Polygon_with_holes_2 pwh_right(polygon_exterior);
+ for (auto hit = (*v)->inner_ccbs_begin(); hit != (*v)->inner_ccbs_end(); ++hit) {
+ pwh_right.add_hole(circ_to_poly(*hit));
+ }
+
+ // compute intersection over union of pwh and the original polygon
+ if (CGAL::do_intersect(pwh, pwh_right)) {
+ std::vector result;
+ CGAL::intersection(pwh, pwh_right, std::back_inserter(result));
+ typename K::FT intersection_area = 0;
+ for (auto& r : result) {
+ auto poly_area = r.outer_boundary().area();
+ for (auto& h : r.holes()) {
+ poly_area -= h.area();
+ }
+ intersection_area += poly_area;
+ }
+ CGAL::Polygon_with_holes_2 poly12;
+ CGAL::join(pwh, pwh_right, poly12);
+ typename K::FT union_area = poly12.outer_boundary().area();
+ for (auto& h : poly12.holes()) {
+ union_area -= h.area();
+ }
+ return_values.push_back(intersection_area / union_area);
+ } else {
+ return_values.push_back(0);
+ }
+ }
+ visited_faces_on_right.insert(*v);
+ break;
+ } else {
+ // Not in facet on right, retry another point
+ continue;
+ }
+ }
+ }
+ }
+
+ return return_values;
+}
+
+void clean_noisy_paths(DebugWriter& debug_output, Arrangement_2& arr, SegmentLookup& segment_lookup, double& threshold) {
using SK = CGAL::Simple_cartesian;
CGAL::Cartesian_converter C{};
@@ -1418,15 +1648,17 @@ void clean_noisy_paths(Arrangement_2& arr, SegmentLookup& segment_lookup) {
auto [dv, dl] = get_dir(s);
best = std::min(best, angle(dv));
}
- return (best + 0.1) / own_length;
+ return (best + 0.01) / own_length;
};
+ std::cerr << "badnesses:";
std::map badnesses;
for (auto& e : edges) {
badnesses[e] = edge_badness(e);
+ std::cerr << " (" << e.source().x() << "," << e.source().y() << ") - (" << e.target().x() << "," << e.target().y() << "): " << badnesses[e] << ";";
}
+ std::cerr << std::endl;
- double thr;
{
std::vector tmp;
tmp.reserve(badnesses.size());
@@ -1435,12 +1667,14 @@ void clean_noisy_paths(Arrangement_2& arr, SegmentLookup& segment_lookup) {
}
std::nth_element(tmp.begin(), tmp.begin() + tmp.size() / 2, tmp.end());
double med = tmp[tmp.size() / 2];
- thr = 10.0 * med;
+ threshold = 4.0 * med;
}
+ std::cerr << "badness threshold: " << threshold << std::endl;
+
std::set bad_edges;
for (auto& p : badnesses) {
- if (p.second > thr) {
+ if (p.second > threshold) {
bad_edges.insert(p.first);
}
}
@@ -1568,10 +1802,57 @@ void clean_noisy_paths(Arrangement_2& arr, SegmentLookup& segment_lookup) {
return best_x;
};
+ auto process_modifications = [&](
+ Arrangement_2& arr_,
+ const std::set>& to_remove_,
+ const std::vector>& to_insert_) {
+ for (auto& e : to_remove_) {
+ bool removed = false;
+ for (auto he = arr_.edges_begin(); he != arr_.edges_end(); ++he) {
+ auto a = he->source()->point();
+ auto b = he->target()->point();
+ if ((a == e.first && b == e.second) || (a == e.second && b == e.first)) {
+ CGAL::remove_edge(arr_, he);
+ removed = true;
+ break;
+ }
+ }
+ if (!removed) {
+ std::cerr << "Warning: unable to locate edge for removal, skipping" << std::endl;
+ }
+ }
+
+ for (auto& pq : to_insert_) {
+ if (pq.first == pq.second) {
+ continue;
+ }
+ CGAL::insert(arr_, Segment_2(pq.first, pq.second));
+ }
+ };
+
+ size_t path_index = 0;
for (auto& path : bad_paths) {
+ decltype(to_remove) to_remove_this_path;
+ decltype(to_insert) to_insert_this_path;
+
+ std::cerr << "Processing bad path:";
+ for (size_t i = 0; i < path.size() - 1; ++i) {
+ auto& a = path[i];
+ auto& b = path[i + 1];
+ std::cerr << " (" << a.x() << "," << a.y() << ") - (" << b.x() << "," << b.y() << ");";
+ }
+ std::cerr << std::endl;
+
+ for (size_t i = 0; i < path.size() - 1; ++i) {
+ auto& a = path[i];
+ auto& b = path[i + 1];
+
+ debug_output.write_segment(a, b, "arr_bad_path path_nr_" + std::to_string(path_index));
+ }
+
auto x = collapse_path(path);
if (!x) {
- // std::cerr << "Unable to collapse path, skipping" << std::endl;
+ std::cerr << "Unable to collapse path, skipping" << std::endl;
continue;
}
@@ -1585,7 +1866,7 @@ void clean_noisy_paths(Arrangement_2& arr, SegmentLookup& segment_lookup) {
double new_length = std::sqrt(CGAL::to_double((path.front() - *x).squared_length())) + std::sqrt(CGAL::to_double((path.back() - *x).squared_length()));
if (new_length > orig_length * 2 || orig_length > new_length * 2) {
- // std::cerr << "Collapsing path would increase length too much, skipping" << std::endl;
+ std::cerr << "Collapsing path would increase length too much, skipping" << std::endl;
continue;
}
@@ -1604,75 +1885,326 @@ void clean_noisy_paths(Arrangement_2& arr, SegmentLookup& segment_lookup) {
auto& b = path[i + 1];
if (a < b) {
to_remove.insert({a, b});
+ to_remove_this_path.insert({a, b});
} else {
to_remove.insert({b, a});
+ to_remove_this_path.insert({b, a});
}
}
auto s = path.front();
auto t = path.back();
if (s != *x) {
to_insert.push_back({s, *x});
+ to_insert_this_path.push_back({s, *x});
+
+ debug_output.write_segment(s, *x, "corrected_path path_nr_" + std::to_string(path_index));
}
if (t != *x) {
to_insert.push_back({t, *x});
+ to_insert_this_path.push_back({t, *x});
+
+ debug_output.write_segment(t, *x, "corrected_path path_nr_" + std::to_string(path_index));
}
+
+ path_index += 1;
+
+#if 1
+ process_modifications(arr, to_remove_this_path, to_insert_this_path);
+#else
+ auto arr_copy = arr;
+ process_modifications(arr_copy, to_remove_this_path, to_insert_this_path);
+ auto ious = arrangement_cell_iou(arr, arr_copy);
+ for (auto& iou : ious) {
+ std::cerr << " - cell iou: " << CGAL::to_double(iou) << std::endl;
+ }
+ std::swap(arr_copy, arr);
+#endif
}
- /*
- using Walk_pl = CGAL::Arr_walk_along_line_point_location;
- Walk_pl walk_pl(arr);
+ process_modifications(arr, to_remove, to_insert);
+}
- for (auto& e : to_remove) {
- // debug_output.write_segment(e->source()->point(), e->target()->point(), "arr_bad_remove");
- auto res = walk_pl.locate(e.first);
- if (auto* v = boost::get(&res)) {
- Arrangement_2::Halfedge_around_vertex_circulator first, curr;
- first = curr = (*v)->incident_halfedges();
- size_t i = 0;
- std::array pts;
- std::array hes;
- do {
- Arrangement_2::Vertex_const_handle u = curr->source();
- hes[i] = curr;
- pts[i++] = u->point();
- } while (++curr != first);
+template
+void next_circular(typename Vec::const_iterator& it, const Vec& vec) {
+ std::advance(it, 1);
+ if (it == vec.end()) {
+ it = vec.begin();
+ }
+}
+template
+void previous_circular(typename Vec::const_iterator& it, const Vec& vec) {
+ if (it == vec.begin()) {
+ it = vec.end();
+ }
+ std::advance(it, -1);
+}
- if ((*v)->point() != e.first) {
- std::cerr << "Warning: unable to locate vertex for edge removal, skipping" << std::endl;
- continue;
+template
+std::size_t circular_distance(typename Vec::const_iterator first,
+ typename Vec::const_iterator last,
+ const Vec& vec) {
+ if (first <= last) {
+ return static_cast(last - first);
+ }
+ return static_cast(vec.end() - first) + static_cast(last - vec.begin());
+}
+
+template
+std::pair
+longest_wrapping_true_run(const Vec& v, Pred pred) {
+ using It = typename Vec::const_iterator;
+
+ const auto n = v.size();
+ if (n == 0) {
+ return {v.end(), v.end()};
+ }
+
+ // Find best non-wrapping run
+ std::size_t best_len = 0;
+ std::size_t best_start = 0;
+
+ std::size_t curr_len = 0;
+ std::size_t curr_start = 0;
+
+ for (std::size_t i = 0; i < n; ++i) {
+ if (pred(v[i])) {
+ if (curr_len == 0) {
+ curr_start = i;
+ }
+ ++curr_len;
+ if (curr_len > best_len) {
+ best_len = curr_len;
+ best_start = curr_start;
}
} else {
- std::cerr << "Warning: unable to locate vertex for edge removal, skipping" << std::endl;
- continue;
+ curr_len = 0;
}
}
- */
- for (auto& e : to_remove) {
- bool removed = false;
- for (auto he = arr.edges_begin(); he != arr.edges_end(); ++he) {
- auto a = he->source()->point();
- auto b = he->target()->point();
- if ((a == e.first && b == e.second) || (a == e.second && b == e.first)) {
- CGAL::remove_edge(arr, he);
- removed = true;
- break;
+ // Count leading true
+ std::size_t leading = 0;
+ while (leading < n && pred(v[leading])) {
+ ++leading;
+ }
+
+ // All true
+ if (leading == n) {
+ return {v.begin(), v.end()};
+ }
+
+ // Count trailing true
+ std::size_t trailing = 0;
+ while (trailing < n && pred(v[n - 1 - trailing])) {
+ ++trailing;
+ }
+
+ // Wrapped run = [n - trailing, n) + [0, leading)
+ const std::size_t wrapped_len = leading + trailing;
+
+ if (wrapped_len > best_len) {
+ It first = v.begin() + static_cast(n - trailing);
+ It last = v.begin() + static_cast(leading);
+ return {first, last};
+ }
+
+ It first = v.begin() + static_cast(best_start);
+ It last = first + static_cast(best_len);
+ return {first, last};
+}
+
+void clean_noisy_bounds(DebugWriter& debug_output, Arrangement_2& arr, SegmentLookup& segment_lookup, double threshold) {
+ using SK = CGAL::Simple_cartesian;
+ CGAL::Cartesian_converter C{};
+
+ auto other = [](const Segment_2& e, const Point_2& v) {
+ return (e.source() == v) ? e.target() : e.source();
+ };
+
+ auto edge_badness = [&](const Segment_2& e) -> double {
+ auto closest = segment_lookup.n_closest_input_segments(e, 2);
+ if (closest.size() != 2) {
+ throw std::runtime_error("Unable to locate two nearby edges");
+ }
+
+ auto get_dir = [&](const Segment_2& s) {
+ auto a = C(s.source());
+ auto b = C(s.target());
+ SK::Vector_2 v = b - a;
+ double l = std::sqrt(v.squared_length());
+ if (l <= 1e-12) {
+ return std::make_pair(SK::Vector_2(0, 0), 0.);
+ }
+ return std::make_pair(v / l, l);
+ };
+
+ auto [own_dir, own_length] = get_dir(e);
+
+ auto angle = [&](const SK::Vector_2& ov) {
+ double d = std::abs(own_dir * ov);
+ if (d > 1.0) {
+ d = 1.0;
+ }
+ return std::acos(d);
+ };
+
+ double best = std::numeric_limits::infinity();
+ for (auto& s : closest) {
+ auto [dv, dl] = get_dir(s);
+ best = std::min(best, angle(dv));
+ }
+ return (best + 0.01) / own_length;
+ };
+
+ size_t facet_index = 0;
+ for (auto it = arr.faces_begin(); it != arr.faces_end(); ++it, ++facet_index) {
+ std::cout << "facet_index " << facet_index << std::endl;
+ if (!it->is_unbounded()) {
+ std::set> to_remove;
+ std::vector> to_insert;
+
+ std::vector segs;
+ std::vector vertices;
+ std::vector halfedges;
+
+ auto circ = it->outer_ccb();
+ do {
+ auto a = circ->source()->point();
+ auto b = circ->target()->point();
+ segs.emplace_back(a, b);
+ vertices.push_back(circ->source());
+ halfedges.push_back(circ);
+ ++circ;
+ } while (circ != it->outer_ccb());
+
+ std::cerr << "badnesses:";
+ std::vector badnesses;
+ for (auto& e : segs) {
+ badnesses.push_back(edge_badness(e));
+ std::cerr << " (" << e.source().x() << "," << e.source().y() << ") - (" << e.target().x() << "," << e.target().y() << "): " << badnesses.back() << ";";
+ }
+ std::cerr << std::endl;
+
+ auto bit = std::min_element(badnesses.begin(), badnesses.end());
+ if (*bit > threshold) {
+ std::cerr << "All edges are good, skipping" << std::endl;
+ continue;
+ }
+
+ auto it_pair = longest_wrapping_true_run(badnesses, [&](double d) { return d > threshold; });
+ auto N = circular_distance(it_pair.first, it_pair.second, badnesses);
+
+ if (N == 0) {
+ std::cerr << "Unable to find run of bad edges, skipping" << std::endl;
+ continue;
+ }
+
+ std::vector> incoming_paths;
+
+ std::cout << "range " << std::distance(badnesses.cbegin(), it_pair.first) << " to " << std::distance(badnesses.cbegin(), it_pair.second) << " length " << N << std::endl;
+
+ auto jt = it_pair.first;
+ for (std::size_t k = 0; k < N; ++k, next_circular(jt, badnesses)) {
+
+ std::cout << " at " << std::distance(badnesses.cbegin(), jt) << " badness: " << *jt << std::endl;
+
+ auto he = halfedges[std::distance(badnesses.cbegin(), jt)];
+ to_remove.insert({he->source()->point(), he->target()->point()});
+ debug_output.write_segment(he->source()->point(), he->target()->point(), "arr_bad_bound facet_" + std::to_string(facet_index));
+
+ Arrangement_2::Vertex_handle v = he->source();
+
+ // circle around other edges onto v
+ Arrangement_2::Halfedge_around_vertex_circulator first, curr;
+ first = curr = v->incident_halfedges();
+ do {
+ Arrangement_2::Vertex_handle u = curr->source();
+ if (curr->face() != it && curr->twin()->face() != it) {
+
+ // loop until we find a 3-degree vertex, or we come back to the start
+ std::vector path{v->point(), u->point()};
+ auto he = curr;
+
+ while (u->degree() == 2 && u != v && path.size() < 10) {
+ std::vector hes;
+
+ {
+ Arrangement_2::Halfedge_around_vertex_circulator first, curr;
+ first = curr = u->incident_halfedges();
+ do {
+ hes.push_back(curr);
+ curr++;
+ } while (curr != first);
+ }
+
+ auto next_he = hes.front() != he && hes.front() != he->twin() ? hes.front() : hes.back();
+ auto next_v = next_he->target() != u ? next_he->target() : next_he->source();
+
+ path.push_back(next_v->point());
+ u = next_v;
+ }
+ incoming_paths.push_back(std::move(path));
+ }
+ } while (++curr != first);
+ }
+
+ const std::size_t start =
+ static_cast(std::distance(badnesses.cbegin(), it_pair.first));
+
+ auto n = badnesses.size();
+
+ auto wrap = [n](std::ptrdiff_t i) -> std::size_t {
+ i %= static_cast(n);
+ if (i < 0) {
+ i += static_cast(n);
+ }
+ return static_cast(i);
+ };
+
+ const std::size_t ib = start;
+ const std::size_t ia = wrap(static_cast(start) - 1);
+ const std::size_t ic = wrap(static_cast(start + N));
+ const std::size_t id = wrap(static_cast(start + N + 1));
+
+ auto a = vertices.begin() + static_cast(ia);
+ auto b = vertices.begin() + static_cast(ib);
+ auto c = vertices.begin() + static_cast(ic);
+ auto d = vertices.begin() + static_cast(id);
+
+ CGAL::Ray_2 r1((*a)->point(), (*b)->point());
+ CGAL::Ray_2 r2((*d)->point(), (*c)->point());
+
+ std::cout << "a: (" << (*a)->point().x() << "," << (*a)->point().y() << ") b: (" << (*b)->point().x() << "," << (*b)->point().y() << ") c: (" << (*c)->point().x() << "," << (*c)->point().y() << ") d: (" << (*d)->point().x() << "," << (*d)->point().y() << ")" << std::endl;
+
+ auto x = CGAL::intersection(r1, r2);
+ if (x) {
+ if (auto* xp = variant_get>(&*x)) {
+ std::cout << "ray xp: (" << xp->x() << "," << xp->y() << ")" << std::endl;
+ to_insert.emplace_back((*b)->point(), *xp);
+ to_insert.emplace_back((*c)->point(), *xp);
+
+ debug_output.write_segment((*b)->point(), *xp, "corrected_bound facet_" + std::to_string(facet_index));
+ debug_output.write_segment((*c)->point(), *xp, "corrected_bound facet_" + std::to_string(facet_index));
+ }
+ } else {
+ CGAL::Line_2 r1((*a)->point(), (*b)->point());
+ CGAL::Line_2 r2((*d)->point(), (*c)->point());
+
+ auto x = CGAL::intersection(r1, r2);
+ if (x) {
+ if (auto* xp = variant_get>(&*x)) {
+ std::cout << "line xp: (" << xp->x() << "," << xp->y() << ")" << std::endl;
+ to_insert.emplace_back((*b)->point(), *xp);
+ to_insert.emplace_back((*c)->point(), *xp);
+
+ debug_output.write_segment((*b)->point(), *xp, "corrected_bound facet_" + std::to_string(facet_index));
+ debug_output.write_segment((*c)->point(), *xp, "corrected_bound facet_" + std::to_string(facet_index));
+ }
+ }
}
}
- if (!removed) {
- std::cerr << "Warning: unable to locate edge for removal, skipping" << std::endl;
- }
}
-
- for (auto& pq : to_insert) {
- if (pq.first == pq.second) {
- continue;
- }
- CGAL::insert(arr, Segment_2(pq.first, pq.second));
- // debug_output.write_segment(pq.first, pq.second, "arr_bad_insert");
- }
-
}
void remove_colinear_vertices(Arrangement_2& arr) {
@@ -1725,20 +2257,31 @@ class timer {
public:
class entry {
public:
+ entry() {}
+
entry(std::map::const_iterator start_it)
: start_it(start_it) {}
+
void stop() {
- auto end = std::chrono::high_resolution_clock::now();
- auto duration = std::chrono::duration(end - start_it->second).count();
- std::cerr << "Timing for " << start_it->first << ": " << duration << " ms" << std::endl;
+ if (start_it) {
+ auto end = std::chrono::high_resolution_clock::now();
+ auto duration = std::chrono::duration(end - start_it.value()->second).count();
+ std::cerr << "Timing for " << start_it.value()->first << ": " << duration << " ms" << std::endl;
+ }
}
private:
- std::map::const_iterator start_it;
+ std::optional::const_iterator> start_it;
};
+ timer(bool enabled = true) : enabled_(enabled) {}
+
entry start(const std::string& name) {
- return entry(timings_.insert({name, std::chrono::high_resolution_clock::now()}).first);
+ if (enabled_) {
+ return entry(timings_.insert({name, std::chrono::high_resolution_clock::now()}).first);
+ } else {
+ return entry();
+ }
}
private:
@@ -1746,27 +2289,30 @@ class timer {
std::string,
std::chrono::high_resolution_clock::time_point>
timings_;
+
+ bool enabled_;
};
-void arrange_cgal_polygons(const std::vector& input_polygons_, std::vector& output_polygons, double polygon_offset_distance = -1.) {
+void arrange_cgal_polygons(svgfill::arrange_polygon_settings settings, const std::vector& input_polygons_, std::vector& output_polygons, double polygon_offset_distance = -1.) {
static const double OVERLAP_RESOLUTION_DISTANCE = 1.e-1;
// even larger amount of inset so that outer perimeter is safely within all input polygons even when overlap resolution is applied
// no, `1.e-2 + 1.e-5` creates issues with the outer perimeter, are there other tolerances in play?
static const double OUTER_PERIMITER_ADDITIONAL_INSET_AMOUNT = 1.e-5;
-#ifdef SVGFILL_DEBUG
- auto t = std::time(nullptr);
- auto tm = *std::localtime(&t);
+ DebugWriter debug_output;
+ if (settings.debug_output) {
+ auto t = std::time(nullptr);
+ auto tm = *std::localtime(&t);
- std::ostringstream oss;
- oss << std::put_time(&tm, "arrangement_%Y%m%d%H%M%S");
- auto now = oss.str();
- DebugWriter debug_output(true, now);
-#else
- DebugWriter debug_output(false, "");
-#endif
+ std::ostringstream oss;
+ oss << std::put_time(&tm, "arrangement_%Y%m%d%H%M%S");
+ auto now = oss.str();
+ debug_output = DebugWriter(true, now);
+ } else {
+ debug_output = DebugWriter(false, "");
+ }
- timer timer;
+ timer timer(settings.debug_output);
auto t0 = timer.start("input");
@@ -1794,7 +2340,7 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
t0.stop();
t0 = timer.start("overlap elimination");
- eliminate_overlaps(OVERLAP_RESOLUTION_DISTANCE, input_polygons);
+ eliminate_overlaps(debug_output, OVERLAP_RESOLUTION_DISTANCE, input_polygons);
t0.stop();
@@ -1813,79 +2359,79 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
debug_output.write_polygons(input_polygons, "processed_input");
-#if 1
- t0 = timer.start("outer perimeter");
-
- // Find the outer perimeter using offset - union - negative offset
- std::vector offset_polygons;
- for (auto& r : input_polygons) {
- auto R = r;
- if (!R.is_counterclockwise_oriented()) {
- R.reverse_orientation();
- }
-
- // Overlap removal can also result in close points causing problems when converted into non-exact nt
- remove_close_points(R);
-
- auto ps = create_and_convert_offset_polygon(polygon_offset_distance, R);
- for (auto& p : ps) {
- if (!p.is_simple()) {
- throw std::runtime_error("Complex polygon originated from offset");
- }
- }
- offset_polygons.insert(offset_polygons.end(), ps.begin(), ps.end());
- }
-
- debug_output.write_polygons(offset_polygons, "offset_input");
-
- // Perform Boolean union on the offset polygons
- std::vector unioned_polygons;
- CGAL::join(offset_polygons.begin(), offset_polygons.end(), std::back_inserter(unioned_polygons));
-
- if (unioned_polygons.size() > 1) {
- // @todo this is currently one of the major limitations in the code that still can be eliminated
- // by grouping the input polygons by their perimiter polygon in unioned_polygons
- std::sort(unioned_polygons.begin(), unioned_polygons.end(), [](auto& p, auto& q) { return p.outer_boundary().area() > q.outer_boundary().area(); });
- }
-
- debug_output.write_polygon(unioned_polygons.front().outer_boundary(), "offset_joined");
-
- Polygon_2 fused_removed_close_points = unioned_polygons.front().outer_boundary();
- remove_close_points(fused_removed_close_points, 1.e-4);
-
- // Apply negative offset to get the outer perimeter polygon
- auto outer_perimiter = create_and_convert_offset_polygon(
- // Because polygon_offset is inexact, make sure our inset distance is slightly larger
- // std::nexttoward(-polygon_offset_distance, -std::numeric_limits::infinity()),
-
- // 1.e-8 even was too little and still resulted in slivers of triangle around the perimeter
- -polygon_offset_distance - OUTER_PERIMITER_ADDITIONAL_INSET_AMOUNT,
- fused_removed_close_points);
-
- debug_output.write_polygons(outer_perimiter, "outer_perimiter");
-#else
- std::map> neighbour_map;
- build_radial_neighbour_map(input_polygons, polygon_offset_distance, neighbour_map);
-
- auto start_vertex = neighbour_map.rbegin()->first;
- auto next_vertex = neighbour_map.rbegin()->second.front();
-
- std::vector cycle = {start_vertex, next_vertex};
- while (cycle.back() != cycle.front()) {
- const auto& incoming_from = *(cycle.rbegin() + 1);
- const auto& nb = neighbour_map[cycle.back()];
- auto it = std::find(nb.begin(), nb.end(), incoming_from);
- // cycle it -1 around nb
- if (it == nb.begin()) {
- it == nb.end() - 1;
- } else {
- --it;
- }
- cycle.push_back(*it);
- }
std::vector outer_perimiter;
- outer_perimiter.emplace_back(cycle.begin(), cycle.end());
-#endif
+ if (settings.outer_perimiter_algo == 0) {
+ t0 = timer.start("outer perimeter");
+
+ // Find the outer perimeter using offset - union - negative offset
+ std::vector offset_polygons;
+ for (auto& r : input_polygons) {
+ auto R = r;
+ if (!R.is_counterclockwise_oriented()) {
+ R.reverse_orientation();
+ }
+
+ // Overlap removal can also result in close points causing problems when converted into non-exact nt
+ remove_close_points(R);
+
+ auto ps = create_and_convert_offset_polygon(polygon_offset_distance, R);
+ for (auto& p : ps) {
+ if (!p.is_simple()) {
+ throw std::runtime_error("Complex polygon originated from offset");
+ }
+ }
+ offset_polygons.insert(offset_polygons.end(), ps.begin(), ps.end());
+ }
+
+ debug_output.write_polygons(offset_polygons, "offset_input");
+
+ // Perform Boolean union on the offset polygons
+ std::vector unioned_polygons;
+ CGAL::join(offset_polygons.begin(), offset_polygons.end(), std::back_inserter(unioned_polygons));
+
+ if (unioned_polygons.size() > 1) {
+ // @todo this is currently one of the major limitations in the code that still can be eliminated
+ // by grouping the input polygons by their perimiter polygon in unioned_polygons
+ std::sort(unioned_polygons.begin(), unioned_polygons.end(), [](auto& p, auto& q) { return p.outer_boundary().area() > q.outer_boundary().area(); });
+ }
+
+ debug_output.write_polygon(unioned_polygons.front().outer_boundary(), "offset_joined");
+
+ Polygon_2 fused_removed_close_points = unioned_polygons.front().outer_boundary();
+ remove_close_points(fused_removed_close_points, 1.e-4);
+
+ // Apply negative offset to get the outer perimeter polygon
+ outer_perimiter = create_and_convert_offset_polygon(
+ // Because polygon_offset is inexact, make sure our inset distance is slightly larger
+ // std::nexttoward(-polygon_offset_distance, -std::numeric_limits::infinity()),
+
+ // 1.e-8 even was too little and still resulted in slivers of triangle around the perimeter
+ -polygon_offset_distance - OUTER_PERIMITER_ADDITIONAL_INSET_AMOUNT,
+ fused_removed_close_points);
+
+ debug_output.write_polygons(outer_perimiter, "outer_perimiter");
+ } else {
+ std::map> neighbour_map;
+ build_radial_neighbour_map(input_polygons, polygon_offset_distance, neighbour_map);
+
+ auto start_vertex = neighbour_map.rbegin()->first;
+ auto next_vertex = neighbour_map.rbegin()->second.front();
+
+ std::vector cycle = {start_vertex, next_vertex};
+ while (cycle.back() != cycle.front()) {
+ const auto& incoming_from = *(cycle.rbegin() + 1);
+ const auto& nb = neighbour_map[cycle.back()];
+ auto it = std::find(nb.begin(), nb.end(), incoming_from);
+ // cycle it -1 around nb
+ if (it == nb.begin()) {
+ it = nb.end() - 1;
+ } else {
+ --it;
+ }
+ cycle.push_back(*it);
+ }
+ outer_perimiter.emplace_back(cycle.begin(), cycle.end());
+ }
t0.stop();
t0 = timer.start("corridor creation");
@@ -1911,8 +2457,10 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
// subdivide difference_result to have better more detailed triangulation and therefore less-pronounced artefacts in midpoint network
+ auto subdivision_length = polygon_offset_distance / settings.subdivision_factor;
+
for (auto& pwh : difference_result) {
- difference_result_subdivided.push_back(subdivide_polygon(polygon_offset_distance / 8., pwh));
+ difference_result_subdivided.push_back(subdivide_polygon(subdivision_length, pwh));
// difference_result_subdivided.push_back(subdivide_polygon(polygon_offset_distance / 64., pwh));
}
@@ -1940,13 +2488,43 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
SegmentLookup segment_lookup(input_polygons);
- auto [line_graph, midpoint_to_segment, segment_to_input_facet] = build_line_graph(input_polygons, segment_lookup, triangular_polygons);
+ auto [line_graph, midpoint_to_segment, segment_to_input_facet, midpoint_to_edge_length] = build_line_graph(input_polygons, segment_lookup, triangular_polygons);
for (auto& p : line_graph) {
for (auto& q : p.second) {
debug_output.write_segment(p.first, q, "network_1");
}
}
+ // Write a JSON structure with center line topology with the midpoint_to_edge_length as per-point data
+ {
+ std::ofstream ofs("center_line_topology.json");
+ ofs << "{\n";
+ ofs << " \"vertices\": [\n";
+ bool first_vertex = true;
+ for (auto& p : line_graph) {
+ if (!first_vertex) {
+ ofs << ",\n";
+ }
+ first_vertex = false;
+ ofs << " {\n";
+ ofs << " \"point\": [" << p.first.x() << ", " << p.first.y() << "],\n";
+ ofs << " \"width\":" << midpoint_to_edge_length.find(p.first)->second << ",\n";
+ ofs << " \"connected_to\": [\n";
+ bool first_connected = true;
+ for (auto& q : p.second) {
+ if (!first_connected) {
+ ofs << ",\n";
+ }
+ first_connected = false;
+ ofs << " [" << q.x() << ", " << q.y() << "]";
+ }
+ ofs << "\n ]\n";
+ ofs << " }";
+ }
+ ofs << "\n ]\n";
+ ofs << "}\n";
+ }
+
t0.stop();
t0 = timer.start("center line cleaning");
@@ -1981,7 +2559,7 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
t0 = timer.start("topology");
- auto segments = extend_end_vertices_based_on_input(G, midpoint_to_segment, segment_to_input_facet, outer_perimiter, segment_lookup);
+ auto segments = extend_end_vertices_based_on_input(G, midpoint_to_segment, segment_to_input_facet, outer_perimiter, segment_lookup, subdivision_length * 4);
// Now plot the edges on an arrangement in order to find planar cycles
// and merge the corridor-halves with their neighbouring input polygon
@@ -1997,31 +2575,31 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
debug_output.write_segment(pq.first, pq.second, "extended_segments");
}
-#if 0
- // Write input polygons to arrangement_2
- // We no longer do this because we add the outer perimiter now, subdivided by the corridor network which is extended and intersected with the outer perimiter
- for (auto& poly : input_polygons) {
- for (size_t i = 0; i != poly.size(); ++i) {
- auto j = (i + 1) % poly.size();
- if (poly.vertex(i) == poly.vertex(j)) {
- continue;
+ if (settings.topology_reconstruction_algo != 0) {
+ // Write input polygons to arrangement_2
+ // We no longer do this because we add the outer perimiter now, subdivided by the corridor network which is extended and intersected with the outer perimiter
+ for (auto& poly : input_polygons) {
+ for (size_t i = 0; i != poly.size(); ++i) {
+ auto j = (i + 1) % poly.size();
+ if (poly.vertex(i) == poly.vertex(j)) {
+ continue;
+ }
+ CGAL::insert(arr, Segment_2(poly.vertex(i), poly.vertex(j)));
+ }
+ }
+ } else {
+ // Write outer perimeter to arrangement_2
+ for (auto& p : outer_perimiter) {
+ for (auto it = p.edges_begin(); it != p.edges_end(); ++it) {
+ auto source = it->source();
+ auto target = it->target();
+ if (source == target) {
+ continue;
+ }
+ CGAL::insert(arr, Segment_2(source, target));
}
- CGAL::insert(arr, Segment_2(poly.vertex(i), poly.vertex(j)));
}
}
-#else
- // Write outer perimeter to arrangement_2
- for (auto& p : outer_perimiter) {
- for (auto it = p.edges_begin(); it != p.edges_end(); ++it) {
- auto source = it->source();
- auto target = it->target();
- if (source == target) {
- continue;
- }
- CGAL::insert(arr, Segment_2(source, target));
- }
- }
-#endif
// Just for the automatic numbering, create a full vector
std::vector temp;
@@ -2047,12 +2625,17 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
// corridor network we know it needs to be joined with an input polygon. In that
// case the edges need to be eliminated that correspond to original geometry.
-#if 0
- fuse_corridor_halves_with_input(arr, G, segment_lookup, input_polygons, debug_output);
-#else
- remove_colinear_vertices(arr);
- clean_noisy_paths(arr, segment_lookup);
-#endif
+ if (settings.topology_reconstruction_algo != 0) {
+ fuse_corridor_halves_with_input(arr, G, segment_lookup, input_polygons, debug_output);
+ }
+
+ if (settings.perform_cleanup) {
+ remove_colinear_vertices(arr);
+ double threshold;
+ clean_noisy_paths(debug_output, arr, segment_lookup, threshold);
+ remove_colinear_vertices(arr);
+ clean_noisy_bounds(debug_output, arr, segment_lookup, threshold);
+ }
t0.stop();
@@ -2068,8 +2651,7 @@ void arrange_cgal_polygons(const std::vector& input_polygons_, std::v
#ifndef SVGFILL_MAIN
-bool svgfill::arrange_polygons(const std::vector& polygons, std::vector& arranged)
-{
+bool svgfill::arrange_polygons(arrange_polygon_settings settings, const std::vector& polygons, std::vector& arranged) {
std::vector cgal_polygons, cgal_polygons_out;
std::transform(polygons.begin(), polygons.end(), std::back_inserter(cgal_polygons), [](auto& poly) {
Polygon_2 result;
@@ -2078,7 +2660,7 @@ bool svgfill::arrange_polygons(const std::vector& polygons,
});
return result;
});
- arrange_cgal_polygons(cgal_polygons, cgal_polygons_out);
+ arrange_cgal_polygons(settings, cgal_polygons, cgal_polygons_out);
std::transform(cgal_polygons_out.begin(), cgal_polygons_out.end(), std::back_inserter(arranged), [](auto& poly) {
svgfill::polygon_2 result;
std::transform(poly.begin(), poly.end(), std::back_inserter(result.boundary), [](auto& pt) {
@@ -2128,7 +2710,7 @@ int main(int argc, char** argv) {
input_polygons.back().push_back(CGAL::Point_2(x, y));
}
}
- arrange_cgal_polygons(input_polygons, output);
+ arrange_cgal_polygons(arrange_polygon_settings{}, input_polygons, output);
break;
}
return 0;
@@ -2141,7 +2723,7 @@ int main(int argc, char** argv) {
input_polygons = { rect1, rect2, rect3, rect4, rect5 };
}
- arrange_cgal_polygons(input_polygons, output);
+ arrange_cgal_polygons(arrange_polygon_settings{}, input_polygons, output);
return 0;
}
diff --git a/src/svgfill/src/svgfill.cpp b/src/svgfill/src/svgfill.cpp
index 8a2a1bb008..cf45881c93 100644
--- a/src/svgfill/src/svgfill.cpp
+++ b/src/svgfill/src/svgfill.cpp
@@ -483,6 +483,18 @@ public:
return ps;
}
+ size_t delete_same_facet_edge_pairs() {
+ size_t n_deleted = 0;
+ for (auto it = arr.edges_begin(); it != arr.edges_end();) {
+ decltype(it) current = it++;
+ if (current->face() == current->twin()->face()) {
+ arr.remove_edge(current);
+ n_deleted++;
+ }
+ }
+ return n_deleted;
+ }
+
void merge(const std::vector& edge_indices) {
if (edge_indices.empty()) {
return;
diff --git a/src/svgfill/src/svgfill.h b/src/svgfill/src/svgfill.h
index 396fc9c924..fab4dd0142 100644
--- a/src/svgfill/src/svgfill.h
+++ b/src/svgfill/src/svgfill.h
@@ -67,6 +67,7 @@ namespace svgfill {
virtual std::vector get_face_pairs() = 0;
virtual size_t num_edges() = 0;
virtual size_t num_faces() = 0;
+ virtual size_t delete_same_facet_edge_pairs() = 0;
};
class SVGFILL_API context {
@@ -101,6 +102,7 @@ namespace svgfill {
void write(std::vector>&);
size_t num_edges() { return arr_->num_edges(); }
size_t num_faces() { return arr_->num_faces(); }
+ size_t delete_same_facet_edge_pairs() { return arr_->delete_same_facet_edge_pairs(); }
~context() {
delete arr_;
@@ -113,7 +115,22 @@ namespace svgfill {
SVGFILL_API std::string polygons_to_svg(const std::vector>& polygons, bool random_color=false);
SVGFILL_API std::string polygons_to_svg(const std::vector& polygons, bool random_color = false);
SVGFILL_API bool svg_to_polygons(const std::string& data, const boost::optional& class_name, std::vector& polygons);
- SVGFILL_API bool arrange_polygons(const std::vector& polygons, std::vector& arranged);
-}
+
+ struct SVGFILL_API arrange_polygon_settings {
+ bool debug_output = false;
+ // -1: compute from average edge length
+ double polygon_offset_distance = -1.;
+ // 0: use offset - union - negative offset to find the outer perimeter
+ // 1: radial walk along vertices; exact, but can only reuse vertices, not create new positions by means of intersections
+ int outer_perimiter_algo = 0;
+ // 0: outer perimiter and corridor center lines
+ // 1: input polygons, corridor center lines and segments connecting corridor center lines to input polygons
+ int topology_reconstruction_algo = 0;
+ bool perform_cleanup = true;
+ double subdivision_factor = 16.;
+ };
+
+ SVGFILL_API bool arrange_polygons(arrange_polygon_settings settings, const std::vector& polygons, std::vector& arranged);
+ }
#endif