From fb9afae4acb2fe50c45f4fbf12be05e7bdeb4c2f Mon Sep 17 00:00:00 2001 From: Shepard Liu Date: Wed, 23 Jul 2025 17:08:17 +0800 Subject: [PATCH] more cleanups, align with code conventions and refactor 1 --- .../Arrangement_on_surface_2/CMakeLists.txt | 2 + .../draw_arr_degen_holes.cpp | 44 ++ .../polylines_approx_dir.cpp | 39 ++ .../unbounded_linear.cpp | 51 ++ .../CGAL/Arr_polycurve_basic_traits_2.h | 4 + .../include/CGAL/Arr_polyline_traits_2.h | 1 + .../CGAL/Draw_aos/Arr_approximate_point_2.h | 117 ----- .../Draw_aos/Arr_approximate_point_2_at_x.h | 123 ----- .../CGAL/Draw_aos/Arr_approximation_cache.h | 23 +- .../Arr_bounded_approximate_curve_2.h | 495 ++++-------------- .../Draw_aos/Arr_bounded_approximate_face_2.h | 409 +++++---------- .../Arr_bounded_approximate_point_2.h | 34 +- .../Draw_aos/Arr_bounded_face_triangulator.h | 71 +-- .../CGAL/Draw_aos/Arr_bounded_renderer.h | 203 +------ .../CGAL/Draw_aos/Arr_construct_curve_end.h | 105 ---- .../CGAL/Draw_aos/Arr_construct_segments.h | 173 ------ .../include/CGAL/Draw_aos/Arr_graph_conn.h | 29 +- .../include/CGAL/Draw_aos/Arr_portals.h | 70 +-- .../CGAL/Draw_aos/Arr_render_context.h | 165 ++---- .../include/CGAL/Draw_aos/Arr_viewer.h | 214 +++++--- .../include/CGAL/Draw_aos/type_utils.h | 239 ++------- .../include/CGAL/draw_arrangement_2.h | 63 ++- 22 files changed, 728 insertions(+), 1946 deletions(-) create mode 100644 Arrangement_on_surface_2/examples/Arrangement_on_surface_2/draw_arr_degen_holes.cpp create mode 100644 Arrangement_on_surface_2/examples/Arrangement_on_surface_2/polylines_approx_dir.cpp create mode 100644 Arrangement_on_surface_2/examples/Arrangement_on_surface_2/unbounded_linear.cpp delete mode 100644 Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2.h delete mode 100644 Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2_at_x.h delete mode 100644 Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_curve_end.h delete mode 100644 Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_segments.h diff --git a/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/CMakeLists.txt b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/CMakeLists.txt index 90e21c2f1d5..c622197481c 100644 --- a/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/CMakeLists.txt +++ b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/CMakeLists.txt @@ -18,6 +18,8 @@ endforeach() if(CGAL_Qt6_FOUND) target_link_libraries(draw_arr PRIVATE CGAL::CGAL_Basic_viewer) + target_link_libraries(draw_arr_degen_holes PRIVATE CGAL::CGAL_Basic_viewer) + target_link_libraries(unbounded_linear PRIVATE CGAL::CGAL_Basic_viewer) target_link_libraries(unbounded_non_intersecting PRIVATE CGAL::CGAL_Basic_viewer) target_link_libraries(linear_conics PRIVATE CGAL::CGAL_Basic_viewer) target_link_libraries(parabolas PRIVATE CGAL::CGAL_Basic_viewer) diff --git a/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/draw_arr_degen_holes.cpp b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/draw_arr_degen_holes.cpp new file mode 100644 index 00000000000..cb12cc07947 --- /dev/null +++ b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/draw_arr_degen_holes.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_exact_constructions_kernel; +using Traits = CGAL::Arr_segment_traits_2; +using Point = Traits::Point_2; +using Arrangement = CGAL::Arrangement_2; +using X_monotone_curve = Traits::X_monotone_curve_2; + +int main() { + Arrangement arr; + auto traits = arr.traits(); + auto cst_x_curve = traits->construct_x_monotone_curve_2_object(); + + // make a hexagon centered at the origin with radius 10 + double radius = 10.0; + std::array hexagon_points = {{ + {radius * cos(0 * CGAL_PI / 3), radius * sin(0 * CGAL_PI / 3)}, // 0° + {radius * cos(1 * CGAL_PI / 3), radius * sin(1 * CGAL_PI / 3)}, // 60° + {radius * cos(2 * CGAL_PI / 3), radius * sin(2 * CGAL_PI / 3)}, // 120° + {radius * cos(3 * CGAL_PI / 3), radius * sin(3 * CGAL_PI / 3)}, // 180° + {radius * cos(4 * CGAL_PI / 3), radius * sin(4 * CGAL_PI / 3)}, // 240° + {radius * cos(5 * CGAL_PI / 3), radius * sin(5 * CGAL_PI / 3)}, // 300° + }}; + std::array hexagon; + for(size_t i = 0; i < hexagon_points.size(); ++i) { + size_t next_i = (i + 1) % hexagon_points.size(); + hexagon[i] = cst_x_curve(hexagon_points[i], hexagon_points[next_i]); + } + // rect hole + auto hole_rectangle = {cst_x_curve({-2, -2}, {2, -2}), cst_x_curve({2, -2}, {2, 2}), cst_x_curve({2, 2}, {-2, 2}), + cst_x_curve({-2, 2}, {-2, -2})}; + // iso vertex inside rect hole + auto iso_vertex_inside_hole = Point{0.5, 0.5}; + // degenerate segment below the rect hole + auto degenerate_segment = cst_x_curve({0, -3}, {1, -3}); + + CGAL::insert_point(arr, iso_vertex_inside_hole); + CGAL::insert(arr, hexagon.begin(), hexagon.end()); + CGAL::insert(arr, hole_rectangle.begin(), hole_rectangle.end()); + CGAL::insert(arr, degenerate_segment); + CGAL::draw(arr); +} \ No newline at end of file diff --git a/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/polylines_approx_dir.cpp b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/polylines_approx_dir.cpp new file mode 100644 index 00000000000..1b31294ea8b --- /dev/null +++ b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/polylines_approx_dir.cpp @@ -0,0 +1,39 @@ + +#include + +#include "arr_polylines.h" + +int main() { + Traits traits; + auto cst_x_curve = traits.construct_x_monotone_curve_2_object(); + std::vector segs{Point(5, 5), Point(4, 4), Point(3, 3), Point(2, 2), Point(1, 1), Point(0, 0)}; + auto polyline = cst_x_curve(segs.begin(), segs.end()); + + auto approx = traits.approximate_2_object(); + std::vector approx_points; + // Specify l2r = true here. + approx(polyline, 1, std::back_inserter(approx_points), true); + + std::cout << "Approximate points:\n"; + for(const auto& pt : approx_points) { + std::cout << pt << "\n"; + } + + // Expected output: + // Approximate points: + // 0 0 + // 1 1 + // 2 2 + // 3 3 + // 4 4 + // 5 5 + + // Got: + // Approximate points: + // 5 5 + // 4 4 + // 3 3 + // 2 2 + // 1 1 + // 0 0 +} \ No newline at end of file diff --git a/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/unbounded_linear.cpp b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/unbounded_linear.cpp new file mode 100644 index 00000000000..55c281e5132 --- /dev/null +++ b/Arrangement_on_surface_2/examples/Arrangement_on_surface_2/unbounded_linear.cpp @@ -0,0 +1,51 @@ +#include "arr_linear.h" +#include "arr_print.h" + +#include "CGAL/draw_arrangement_2.h" + +int main() { + Arrangement arr; + auto& traits = *arr.traits(); + + // Insert a n*n grid, each cell is a square of size 5 + int n = 5; + for(int i = 0; i < n; ++i) { + Point p1(i * 5, 0); + Point p2(i * 5, 1); + CGAL::insert(arr, X_monotone_curve(Line(p1, p2))); + } + + for(int i = 0; i < n; ++i) { + Point p1(0, i * 5); + Point p2(1, i * 5); + CGAL::insert(arr, X_monotone_curve(Line(p1, p2))); + } + + Vertex_const_handle vh; + // Generate a inner square(2*2) for all cells + // And an inner triangle for each square + for(int i = 0; i < n; ++i) { + for(int j = 0; j < n; ++j) { + Point p1(i * 5 + 1, j * 5 + 1); + Point p2(i * 5 + 4, j * 5 + 4); + CGAL::insert(arr, X_monotone_curve(Segment(p1, Point(p2.x(), p1.y())))); + CGAL::insert(arr, X_monotone_curve(Segment(Point(p1.x(), p2.y()), p2))); + CGAL::insert(arr, X_monotone_curve(Segment(p1, Point(p1.x(), p2.y())))); + CGAL::insert(arr, X_monotone_curve(Segment(Point(p2.x(), p1.y()), p2))); + // Insert a triangle inside the square + Point tri_p1(i * 5 + 2, j * 5 + 2); + Point tri_p2(i * 5 + 3, j * 5 + 2); + Point tri_p3(i * 5 + 2.5, j * 5 + 3); + CGAL::insert(arr, X_monotone_curve(Segment(tri_p1, tri_p2))); + CGAL::insert(arr, X_monotone_curve(Segment(tri_p2, tri_p3))); + CGAL::insert(arr, X_monotone_curve(Segment(tri_p3, tri_p1))); + // Connect the triangle to the square + Point top(i * 5 + 2.5, j * 5 + 4); + CGAL::insert(arr, X_monotone_curve(Segment(tri_p1, top))); + } + } + + print_arrangement_size(arr); + + CGAL::draw(arr); +} \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h index 94173b2e083..b59b5079d89 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h @@ -1115,6 +1115,7 @@ public: using Approximate_number_type = void; using Approximate_point_2 = void; using Approximate_2 = void; + using Approximate_kernel = void; }; template @@ -1123,6 +1124,7 @@ public: using Approximate_number_type = typename T::Approximate_number_type; using Approximate_2 = typename T::Approximate_2; using Approximate_point_2 = typename T::Approximate_point_2; + using Approximate_kernel = typename T::Approximate_kernel; }; using Approximate_number_type = @@ -1131,6 +1133,8 @@ public: typename has_approximate_2::Approximate_2; using Approximate_point_2 = typename has_approximate_2::Approximate_point_2; + using Approximate_kernel = + typename has_approximate_2::Approximate_kernel; /*! obtains an Approximate_2 functor object. */ Approximate_2 approximate_2_object_impl(std::false_type) const diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_polyline_traits_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_polyline_traits_2.h index 90864a7f64e..b84e30259bf 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_polyline_traits_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_polyline_traits_2.h @@ -597,6 +597,7 @@ public: // using Approximate_number_type = typename Base::Approximate_number_type; using Approximate_point_2 = typename Base::Approximate_point_2; + using Approximate_kernel = typename Base::Approximate_kernel; class Approximate_2 : public Base::Approximate_2 { protected: diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2.h deleted file mode 100644 index 04ae489730f..00000000000 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef CGAL_DRAW_AOS_ARR_APPROXIMATE_POINT_2_H -#define CGAL_DRAW_AOS_ARR_APPROXIMATE_POINT_2_H - -#include "CGAL/Arr_rational_function_traits_2.h" -#include "CGAL/number_utils.h" -#include "CGAL/Draw_aos/type_utils.h" - -namespace CGAL { -namespace draw_aos { -namespace internal { - -template -class Arr_approximate_point_2_impl; - -template -class Arr_approximate_point_2_impl -{ - using Approx_traits = Arr_approximation_geometry_traits; - using Point_2 = typename Traits_adaptor::Point_2; - using Approx_point = typename Approx_traits::Approx_point; - -public: - Arr_approximate_point_2_impl(const GeomTraits& traits) - : m_approx(traits.approximate_2_object()) {} - - /** - * @brief Approximate a point. - * TODO: make it work for spherical traits. - * @param pt - * @return Point_geom - */ - Approx_point operator()(const Point_2& pt) const { return {m_approx(pt, 0), m_approx(pt, 1)}; } - - /** - * @brief Approximate a specific dimension of a point. - * - * @param pt - * @param dim 0 for x, 1 for y, 2 for z if applicable. An exception is thrown if the dimension is invalid. - * @return double - */ - double operator()(const Point_2& pt, int dim) const { return m_approx(pt, dim); } - -private: - const typename GeomTraits::Approximate_2 m_approx; -}; - -// Specialized for Arr_rational_function_traits_2. -template -class Arr_approximate_point_2_impl, true> -{ - using Geom_traits = Arr_rational_function_traits_2; - using Approx_traits = Arr_approximation_geometry_traits; - using Point_2 = typename Traits_adaptor::Point_2; - using Approx_point = typename Approx_traits::Approx_point; - using Approximate_2 = typename Geom_traits::Approximate_2; - -public: - Arr_approximate_point_2_impl(const Geom_traits& traits) {} - - Approx_point operator()(const Point_2& pt) const { return {pt.x().to_double(), pt.y().to_double()}; } - - double operator()(const Point_2& pt, int dim) const { return (dim == 0) ? pt.x().to_double() : pt.y().to_double(); } -}; - -// Fallback to use CGAL::to_double for traits that do not have an approximate_2_object. -template -class Arr_approximate_point_2_impl -{ - using Approx_traits = Arr_approximation_geometry_traits; - using Point_2 = typename Traits_adaptor::Point_2; - using Approx_point = typename Approx_traits::Approx_point; - -public: - // traits object is not used in the fallback implementation, but we keep it for consistency. - Arr_approximate_point_2_impl(const GeomTraits& traits) {} - - /** - * @brief Approximate a point. - * - * @note this functions does not check if the point is within the bounding box. - * @param pt - * @return Point_geom - */ - Approx_point operator()(const Point_2& pt) const { return {CGAL::to_double(pt.x()), CGAL::to_double(pt.y())}; } - - /** - * @brief Approximate a specific dimension of a point. - * - * @param pt - * @param dim 0 for x, 1 for y, 2 for z if applicable. An exception is thrown if the dimension is invalid. - * @return double - */ - double operator()(const Point_2& pt, int dim) const { - if(dim == 0) { - return CGAL::to_double(pt.x()); - } else if(dim == 1) { - return CGAL::to_double(pt.y()); - } else if(dim == 2 && pt.dimension() == 3) { - return CGAL::to_double(pt.z()); - } else { - throw std::invalid_argument("Invalid dimension for approximate_point"); - } - } -}; - -} // namespace internal - -template -using Arr_approximate_point_2 = - internal::Arr_approximate_point_2_impl && - has_operator_point_v>; - -} // namespace draw_aos -} // namespace CGAL - -#endif // CGAL_DRAW_AOS_ARR_APPROXIMATE_POINT_2_H \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2_at_x.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2_at_x.h deleted file mode 100644 index 2141826219e..00000000000 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximate_point_2_at_x.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef CGAL_DRAW_AOS_ARR_APPROXIMATE_POINT_2_AT_X_H -#define CGAL_DRAW_AOS_ARR_APPROXIMATE_POINT_2_AT_X_H -#include - -#include -#include -#include -#include -#include -#include - -namespace CGAL { -namespace draw_aos { - -/** - * @brief Functor to compute the point at a given x-coordinate on an x-monotone curve within a bounding box. - */ -template -class Arr_approximate_point_2_at_x -{ - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using Intersect_2 = typename Traits_adaptor::Intersect_2; - using FT = typename Traits_adaptor::FT; - using Approx_traits = Arr_approximation_geometry_traits; - using Approx_point = typename Approx_traits::Approx_point; - using Is_vertical_2 = typename Traits_adaptor::Is_vertical_2; - -public: - Arr_approximate_point_2_at_x(const GeomTraits& traits) - : m_approx_pt(traits) - , m_cst_vertical_segment(traits) - , m_is_vertical_2(traits.is_vertical_2_object()) - , m_intersect_2(traits.intersect_2_object()) {} - - /** - * @brief Computes the point at a given x-coordinate on an x-monotone curve - * - * @precondition: The curve is not verical - * @param curve - * @param x - * @return true if there is an intersection at given x, - * @return false otherwise. - */ - std::optional operator()(const X_monotone_curve_2& curve, FT x) const { - CGAL_assertion(!m_is_vertical_2(curve)); - - using Multiplicity = typename GeomTraits::Multiplicity; - using Intersect_point = std::pair; - using Intersect_curve = X_monotone_curve_2; - using Intersect_type = std::variant; - - auto vertical_line = m_cst_vertical_segment(x, m_ymin, m_ymax); - std::optional pt; - auto func_out_iter = boost::make_function_output_iterator([&pt, this](const Intersect_type& res) { - CGAL_assertion_msg(std::holds_alternative(res), - "Unexpected intersection type, expected Intersect_point"); - pt = this->m_approx_pt(std::get(res).first); - }); - m_intersect_2(vertical_line, curve, func_out_iter); - - if(pt.has_value()) { - pt = Approx_point(CGAL::to_double(x), pt->y()); - } - return pt; - } - -private: - const Arr_approximate_point_2 m_approx_pt; - const Arr_construct_vertical_segment m_cst_vertical_segment; - const Is_vertical_2 m_is_vertical_2; - const Intersect_2 m_intersect_2; - - // Should be enough for visualization purposes. - // constexpr static double m_ymin = std::numeric_limits::lowest(); - // constexpr static double m_ymax = std::numeric_limits::max(); - // maximum of double is too large for CORE number types, no idea why - constexpr static double m_ymin = -1e8; - constexpr static double m_ymax = 1e8; -}; - -// Specialization for rational function traits, which has no vertical curves but provides -// evaluation at x directly. -template -class Arr_approximate_point_2_at_x> -{ - using Geom_traits = CGAL::Arr_rational_function_traits_2; - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using FT = typename Traits_adaptor::FT; - using Approx_point = typename Arr_approximation_geometry_traits::Approx_point; - -public: - Arr_approximate_point_2_at_x(const Geom_traits&) - : to_ft() {} - - std::optional operator()(const X_monotone_curve_2& curve, FT x) const { - FT xmin = curve.left_parameter_space_in_x() == ARR_INTERIOR ? curve.left_x() - : to_ft(std::numeric_limits::lowest()); - FT xmax = curve.right_parameter_space_in_x() == ARR_INTERIOR ? curve.right_x() - : to_ft(std::numeric_limits::max()); - - if(x < xmin || x > xmax) { - return std::nullopt; // x is out of bounds - } - - using Bound = typename Geom_traits::Bound; - const auto& numerator = curve.numerator(); - const auto& denominator = curve.denominator(); - Bound approx_x = (x.lower() + x.upper()) / 2.0; - double enum_at_x = CGAL::to_double(numerator.evaluate(approx_x)); - double denom_at_x = CGAL::to_double(denominator.evaluate(approx_x)); - return std::make_optional(Approx_point(CGAL::to_double(x), enum_at_x / denom_at_x)); - } - -private: - const Construct_coordinate to_ft; -}; - -} // namespace draw_aos -} // namespace CGAL - -#endif // CGAL_DRAW_AOS_ARR_COMPUTE_Y_AT_X_H \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximation_cache.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximation_cache.h index 5b34edfa724..851afb2ba62 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximation_cache.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_approximation_cache.h @@ -4,18 +4,25 @@ #include -#include "CGAL/Arr_enums.h" -#include "CGAL/unordered_flat_map.h" -#include "CGAL/Draw_aos/type_utils.h" +#include +#include +#include namespace CGAL { namespace draw_aos { +/** + * @brief Cache class for approximating arrangement on surface. + * + * When iterating over the arrangement dcel, a feature(vertex, halfedge, face) might be visited multiple times. + * This cache stores the approximated geometry for each feature to avoid redundant calculations. + * @tparam Arrangement + */ template class Arr_approximation_cache { using Geom_traits = typename Arrangement::Geometry_traits_2; - using Approx_traits = Arr_approximation_geometry_traits; + using Approx_traits = Arr_approximate_traits; public: using Vertex_cache_obj = typename Approx_traits::Point_geom; @@ -46,9 +53,9 @@ private: public: Arr_approximation_cache() = default; - void reserve_vertex_cache(std::size_t size) { m_vertex_cache.reserve(size); } - void reserve_halfedge_cache(std::size_t size) { m_halfedge_cache.reserve(size); } - void reserve_face_cache(std::size_t size) { m_face_cache.reserve(size); } + void reserve_vertexs(std::size_t size) { m_vertex_cache.reserve(size); } + void reserve_halfedges(std::size_t size) { m_halfedge_cache.reserve(size); } + void reserve_faces(std::size_t size) { m_face_cache.reserve(size); } std::pair try_emplace(const Vertex_const_handle& vh) { const auto& [it, inserted] = m_vertex_cache.try_emplace(vh, Vertex_cache_obj()); @@ -118,4 +125,4 @@ private: } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_APPROXIMATION_CACHE_H \ No newline at end of file +#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_curve_2.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_curve_2.h index fbc839a9d3e..ef2f6da5277 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_curve_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_curve_2.h @@ -1,450 +1,169 @@ #ifndef CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_CURVE_2_H #define CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_CURVE_2_H -#include +#include #include +#include #include #include #include -#include #include -#include #include #include namespace CGAL { namespace draw_aos { -template -class Arr_bounded_approximate_curve_2_impl; - +/** + * @brief Functor to approximate an x-monotone curve within an bounding box. + * + * The Approximation is done from xmin to xmax with a given step. For parts outbound the y limits and precedes or + * succeeds a part within, the approximation may be skipped but there will be at least one point outside the bbox + * for indication. + */ template -class Arr_bounded_approximate_curve_2_impl -{ - using Halfedge_const_handle = typename Arrangement::Halfedge_const_iterator; - using Geom_traits = typename Arrangement::Geometry_traits_2; - - using Approx_traits = Arr_approximation_geometry_traits; - using Approx_nt = typename Approx_traits::Approx_nt; - using Approx_point = typename Approx_traits::Approx_point; - using Polyline_geom = typename Approx_traits::Polyline_geom; - - using Adapted_traits = Traits_adaptor; - using FT = typename Adapted_traits::FT; - using Point_2 = typename Adapted_traits::Point_2; - using X_monotone_curve_2 = typename Adapted_traits::X_monotone_curve_2; - - using Approx_point_2_at_x = Arr_approximate_point_2_at_x; - using Bounded_render_context = Arr_bounded_render_context; - - using Intersections_vector = std::vector; - - struct Execution_context : public Arr_context_delegator - { - Execution_context(const Bounded_render_context& ctx, - const X_monotone_curve_2& curve, - const Approx_point_2_at_x& approx_pt_at_x, - const Intersections_vector& top_inters, - const Intersections_vector& bottom_inters, - Polyline_geom& polyline) - : Arr_context_delegator(ctx) - , to_ft() - , curve(curve) - , m_approx_pt_at_x(approx_pt_at_x) - , top_inters(top_inters) - , bottom_inters(bottom_inters) - , out_it(std::back_inserter(polyline)) { - auto min_end_pt = Arr_construct_curve_end(ctx.traits)(curve, ARR_MIN_END); - if(min_end_pt.has_value()) { - min_end = (*this)->approx_pt(min_end_pt.value()); - } - auto max_end_pt = Arr_construct_curve_end(ctx.traits)(curve, ARR_MAX_END); - if(max_end_pt.has_value()) { - max_end = (*this)->approx_pt(max_end_pt.value()); - } - txmin = is_min_end_bounded() ? std::clamp(min_end->x(), ctx.xmin(), ctx.xmax()) : ctx.xmin(); - txmax = is_max_end_bounded() ? std::clamp(max_end->x(), ctx.xmin(), ctx.xmax()) : ctx.xmax(); - tymin = is_min_end_bounded() ? std::clamp(min_end->y(), ctx.ymin(), ctx.ymax()) : ctx.ymin(); - tymax = is_max_end_bounded() ? std::clamp(max_end->y(), ctx.ymin(), ctx.ymax()) : ctx.ymax(); - } - - bool has_y_intersections() const { return !top_inters.empty() || !bottom_inters.empty(); } - bool is_min_end_bounded() const { return min_end.has_value(); } - bool is_max_end_bounded() const { return max_end.has_value(); } - bool is_bounded_curve() const { return is_min_end_bounded() && is_max_end_bounded(); } - std::optional approx_pt_at_x(double x) const { - if(x == txmin && is_min_end_bounded() && (*this)->contains_x(min_end->x())) { - return min_end; - } - if(x == txmax && is_max_end_bounded() && (*this)->contains_x(max_end->x())) { - return max_end; - } - return m_approx_pt_at_x(curve, to_ft(x)); - } - - const X_monotone_curve_2& curve; - const Intersections_vector &top_inters, bottom_inters; - std::optional min_end, max_end; - double txmin, txmax, tymin, tymax; - std::back_insert_iterator out_it; - const Construct_coordinate to_ft; - - private: - const Approx_point_2_at_x& m_approx_pt_at_x; - }; - -private: - static std::vector compute_intersections(const X_monotone_curve_2& cv1, - const X_monotone_curve_2& cv2, - const typename Geom_traits::Intersect_2& intersect_2, - const Arr_construct_curve_end& cst_curve_end) { - using Intersect_point = std::pair; - using Intersect_curve = X_monotone_curve_2; - using Intersect_type = std::variant; - - std::vector intersections; - auto out_it = std::back_inserter(intersections); - intersect_2(cv1, cv2, boost::make_function_output_iterator([&out_it, &cst_curve_end](const Intersect_type& res) { - if(auto* pt = std::get_if(&res)) { - *out_it++ = pt->first; - return; - } - if(auto* cv = std::get_if(&res)) { - *out_it++ = cst_curve_end(*cv, ARR_MIN_END).value(); - *out_it++ = cst_curve_end(*cv, ARR_MAX_END).value(); - return; - } - CGAL_assertion(false && "Unexpected intersection type"); - })); - return intersections; - } - - static std::optional first_intersection(Execution_context& ctx) { - if(!ctx.top_inters.empty() && !ctx.bottom_inters.empty()) { - return ctx->approx_pt(ctx->compare_xy_2(ctx.top_inters.front(), ctx.bottom_inters.front()) == CGAL::SMALLER - ? ctx.top_inters.front() - : ctx.bottom_inters.front()); - } - if(!ctx.top_inters.empty()) { - return ctx->approx_pt(ctx.top_inters.front()); - } - if(!ctx.bottom_inters.empty()) { - return ctx->approx_pt(ctx.bottom_inters.front()); - } - return std::nullopt; - } - - /** - * @brief approximate strictly x-monotone curve segment that does not cross the y bounds. - * A fallback implementation that approximates curve with Arr_approximate_point_2_at_x, which is inefficient. - * - * @precondition: The segment is either inbound or outbound the bbox in the given range. - * @param start the x-coordinate of the segment start(exclusive) - * @param end the x-coordinate of the segment end(exclusive) - * @param step the step to approximate the curve segment, negative values allowed. - * @returns true if this part of the curve is within the closed bbox - */ - static void approximate_simple_curve_segment(Execution_context& ctx, double start, double end, double step) { - for(double x = start + step; x <= end - step; x += step) { - auto pt = ctx.approx_pt_at_x(x); - if(!pt.has_value()) { - // break as soon as there's no more intersections - break; - } - if(pt->y() == ctx->ymin() || pt->y() == ctx->ymax()) { - // The segment overlaps with the bbox edge. There's no need to insert a dummy point. - break; - } - *ctx.out_it++ = pt.value(); - if(!ctx->contains_y(pt->y())) { - // We are outside the bbox. The dummy point was already inserted to indicate that. - break; - } - } - } - - static void approximate_vertical_curve(Execution_context& ctx) { - if(ctx.is_bounded_curve() && !ctx->contains_x(ctx.min_end->x())) { - // The curve is outside the bbox in x direction, no need to approximate - return; - } - if(!ctx.is_bounded_curve() && !ctx.has_y_intersections()) { - // The curve has unbounded end and has no intersections with the top or bottom edges, - // it must be outbound in x direction. - return; - } - // The vertical curve is now within the x bounds. - - if(ctx.tymax == ctx.tymin) { - // But the curve is not degenerate. So, either it has only one point within the bbox or it is - // entirely outside the bbox in y direction. - return; - } - // Now we gaurantee that the curve has at least two points within the bbox in y direction. - // We have to obtain the x coordinate of this vertical curve. - double x = ctx.is_bounded_curve() ? ctx.min_end->x() : first_intersection(ctx).value().x(); - *ctx.out_it++ = Approx_point(x, ctx.tymin); - *ctx.out_it++ = Approx_point(x, ctx.tymax); - } - -public: - Arr_bounded_approximate_curve_2_impl(const Bounded_render_context& ctx) - : to_ft() - , m_ctx(ctx) - , m_approx_pt_at_x(ctx.traits) - , m_top(ctx.cst_horizontal_segment(to_ft(ctx.ymax()), to_ft(ctx.xmin()), to_ft(ctx.xmax()))) - , m_bottom(ctx.cst_horizontal_segment(to_ft(ctx.ymin()), to_ft(ctx.xmin()), to_ft(ctx.xmax()))) {} - - /** - * @brief Approximate an x-monotone curve from left to right within the bounding box. - * - * @param he non-fictitious halfedge handle - * @return const Polyline_geom& - */ - const Polyline_geom& operator()(const Halfedge_const_handle& he) const { - CGAL_assertion(!he->is_fictitious()); - - auto [polyline, inserted] = m_ctx.cache.try_emplace(he); - if(!inserted) { - return polyline; - } - - if(m_ctx.is_cancelled()) { - return polyline; - } - - const X_monotone_curve_2& curve = he->curve(); - - auto top_inters = compute_intersections(m_top, curve, m_ctx.intersect_2, m_ctx.cst_curve_end); - auto bottom_inters = compute_intersections(m_bottom, curve, m_ctx.intersect_2, m_ctx.cst_curve_end); - Execution_context ctx(m_ctx, curve, m_approx_pt_at_x, top_inters, bottom_inters, polyline); - - if(ctx->is_vertical_2(curve)) { - approximate_vertical_curve(ctx); - return polyline; - } - - polyline.reserve(top_inters.size() + bottom_inters.size()); - - double last_x; - std::optional first_inter = first_intersection(ctx); - - if(auto pt_at_txmin = ctx.approx_pt_at_x(ctx.txmin); - pt_at_txmin.has_value() && pt_at_txmin->y() != ctx->ymin() && pt_at_txmin->y() != ctx->ymax()) - { - // The tight starting point of the curve is within the bbox and - // it's not on the top or bottom edge. - *ctx.out_it++ = ctx.txmin == ctx->xmin() ? ctx->make_on_boundary(pt_at_txmin.value()) : pt_at_txmin.value(); - double segment_end = first_inter.has_value() ? first_inter->x() : ctx.txmax; - approximate_simple_curve_segment(ctx, ctx.txmin, segment_end, ctx->approx_error); - last_x = segment_end; - } else if(first_inter.has_value()) { - last_x = first_inter->x(); - } else { - return polyline; // The curve is entirely outside the bbox in x direction. - } - - // iterate through the intersections and insert segments in-between. - std::merge(top_inters.begin(), top_inters.end(), bottom_inters.begin(), bottom_inters.end(), - boost::make_function_output_iterator([&last_x, &ctx](const Point_2& inter) { - auto approx_inter = ctx->approx_pt(inter); - approximate_simple_curve_segment(ctx, last_x, approx_inter.x(), ctx->approx_error); - *ctx.out_it++ = ctx->make_on_boundary(approx_inter); - last_x = approx_inter.x(); - }), - [&ctx](const Point_2& pt1, const Point_2& pt2) { return ctx->compare_xy_2(pt1, pt2) == CGAL::SMALLER; }); - - if(auto pt_at_txmax = ctx.approx_pt_at_x(ctx.txmax); - pt_at_txmax.has_value() && pt_at_txmax->y() != ctx->ymin() && pt_at_txmax->y() != ctx->ymax()) - { - approximate_simple_curve_segment(ctx, last_x, ctx.txmax, ctx->approx_error); - *ctx.out_it++ = ctx.txmax == ctx->xmax() ? ctx->make_on_boundary(pt_at_txmax.value()) : pt_at_txmax.value(); - } - - return polyline; - } - -private: - const Construct_coordinate to_ft; - const Bounded_render_context& m_ctx; - const Approx_point_2_at_x m_approx_pt_at_x; - const X_monotone_curve_2 m_top; - const X_monotone_curve_2 m_bottom; -}; - -template -class Arr_bounded_approximate_curve_2_impl +class Arr_bounded_approximate_curve_2 { using Geom_traits = typename Arrangement::Geometry_traits_2; - using Halfedge_const_handle = typename Arrangement::Halfedge_const_iterator; + using Halfedge_const_handle = typename Arrangement::Halfedge_const_handle; - using Approx_traits = Arr_approximation_geometry_traits; + using Approx_traits = Arr_approximate_traits; using Approx_nt = typename Approx_traits::Approx_nt; using Approx_point = typename Approx_traits::Approx_point; using Approx_kernel = typename Approx_traits::Approx_kernel; using Approx_line_2 = typename Approx_kernel::Line_2; using Polyline_geom = typename Approx_traits::Polyline_geom; - using Adapted_traits = Traits_adaptor; - using Approximate_2 = typename Adapted_traits::Approximate_2; - using X_monotone_curve_2 = typename Adapted_traits::X_monotone_curve_2; - using Construct_curve_end = Arr_construct_curve_end; + using X_monotone_curve_2 = typename Geom_traits::X_monotone_curve_2; + using Point_2 = typename Geom_traits::Point_2; using Bounded_render_context = Arr_bounded_render_context; + using Boundary_lines = std::array; private: - struct Execution_context : public Arr_context_delegator + struct Context : public Bounded_render_context { - Execution_context(const Bounded_render_context& ctx, - const X_monotone_curve_2& curve, - const Approximate_2& approx_2, - Polyline_geom& polyline) - : Arr_context_delegator(ctx) - , curve(curve) - , approx_2(approx_2) - , top(Approx_line_2(Approx_point(ctx.xmin(), ctx.ymax()), Approx_point(ctx.xmax(), ctx.ymax()))) - , right(Approx_line_2(Approx_point(ctx.xmax(), ctx.ymin()), Approx_point(ctx.xmax(), ctx.ymax()))) - , bottom(Approx_line_2(Approx_point(ctx.xmin(), ctx.ymin()), Approx_point(ctx.xmax(), ctx.ymin()))) - , left(Approx_line_2(Approx_point(ctx.xmin(), ctx.ymin()), Approx_point(ctx.xmin(), ctx.ymax()))) + Context(const Bounded_render_context& ctx, + const X_monotone_curve_2& curve, + Polyline_geom& polyline, + const Boundary_lines& boundary_lines) + : Bounded_render_context(ctx) + , m_curve(curve) + , m_boundary_lines(boundary_lines) + , m_approx(ctx.m_traits.approximate_2_object()) , m_base_out_it(std::back_inserter(polyline)) - , out_it(boost::make_function_output_iterator(std::function([this](Approx_point pt) { - if(!(*this)->contains_x(pt.x())) { + , m_out_it(boost::make_function_output_iterator(std::function([this](Approx_point pt) { + if(pt.x() < this->xmin()) { + // We need the last point if not yet x-inbound. + m_last_pt = pt; return; - } - *this->m_base_out_it++ = pt; + } else if(pt.x() > this->xmax()) + return; + + *m_base_out_it++ = pt; + m_last_pt = pt; }))) {} private: std::back_insert_iterator m_base_out_it; public: - const X_monotone_curve_2& curve; - const Approximate_2& approx_2; - const Approx_line_2 top, right, bottom, left; - boost::function_output_iterator> out_it; + const X_monotone_curve_2& m_curve; + const typename Geom_traits::Approximate_2 m_approx; + const Boundary_lines& m_boundary_lines; + std::optional m_last_pt; + boost::function_output_iterator> m_out_it; }; - static void update_on_crossing_boundary(Execution_context& ctx, - Approx_point& last_pt, - const Approx_point& pt, - Side_of_boundary side) { - const auto& boundary_line = [&ctx, side]() { - switch(side) { - case Side_of_boundary::Top: - return ctx.top; - case Side_of_boundary::Right: - return ctx.right; - case Side_of_boundary::Bottom: - return ctx.bottom; - case Side_of_boundary::Left: - return ctx.left; - default: - return Approx_line_2(); - } - CGAL_assertion(false && "Unexpected side of boundary"); - }(); + static Approx_point trace_boundary_inter(const Context& ctx, Approx_point pt, Side_of_boundary side) { + Approx_point inter = std::get( + *CGAL::intersection(Approx_line_2(*ctx.m_last_pt, pt), ctx.m_boundary_lines[static_cast(side)])); + // Prevent floating point errors. + switch(side) { + case Side_of_boundary::Left: + return Approx_point(ctx.xmin(), inter.y()); + case Side_of_boundary::Right: + return Approx_point(ctx.xmax(), inter.y()); + case Side_of_boundary::Top: + return Approx_point(inter.x(), ctx.ymax()); + case Side_of_boundary::Bottom: + return Approx_point(inter.x(), ctx.ymin()); + default: + CGAL_assertion(false && "Unexpected side of boundary."); + return Approx_point(); + } + } - std::optional> res = - CGAL::intersection(Approx_line_2(last_pt, pt), boundary_line); - Approx_point inter = ctx->make_on_boundary(std::get(*res)); - if(!ctx->contains_x(inter.x())) { + static void update(Context& ctx, Approx_point pt) { + if(!ctx.m_last_pt.has_value()) { + *ctx.m_out_it++ = pt; return; } - last_pt = inter; - *ctx.out_it++ = last_pt; + + if(ctx.m_last_pt->x() < ctx.xmin() && pt.x() >= ctx.xmin()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Left); + } + + if(ctx.m_last_pt->y() < ctx.ymin()) { + if(pt.y() > ctx.ymin()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Bottom); + } + if(pt.y() > ctx.ymax()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Top); + } + } else if(ctx.m_last_pt->y() > ctx.ymax()) { + if(pt.y() < ctx.ymax()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Top); + } + if(pt.y() < ctx.ymin()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Bottom); + } + } else { + if(pt.y() < ctx.ymin()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Bottom); + } else if(pt.y() > ctx.ymax()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Top); + } + } + + if(ctx.m_last_pt->x() <= ctx.xmax() && pt.x() > ctx.xmax()) { + *ctx.m_out_it++ = trace_boundary_inter(ctx, pt, Side_of_boundary::Right); + } + + *ctx.m_out_it++ = pt; } public: - Arr_bounded_approximate_curve_2_impl(const Bounded_render_context& ctx) + Arr_bounded_approximate_curve_2(const Bounded_render_context& ctx) : m_ctx(ctx) - , m_approximate_2(ctx.traits.approximate_2_object()) {} + , m_boundary_lines({ + Approx_line_2(Approx_point(ctx.xmin(), ctx.ymax()), Approx_point(ctx.xmax(), ctx.ymax())), // Top = 0 + Approx_line_2(Approx_point(ctx.xmin(), ctx.ymin()), Approx_point(ctx.xmin(), ctx.ymax())), // Left = 1 + Approx_line_2(Approx_point(ctx.xmin(), ctx.ymin()), Approx_point(ctx.xmax(), ctx.ymin())), // Bottom = 2 + Approx_line_2(Approx_point(ctx.xmax(), ctx.ymin()), Approx_point(ctx.xmax(), ctx.ymax())), // Right = 3 + }) {} const Polyline_geom& operator()(const Halfedge_const_handle& he) const { CGAL_assertion(!he->is_fictitious()); - auto [polyline, inserted] = m_ctx.cache.try_emplace(he); - if(!inserted) { - return polyline; - } + auto [polyline, inserted] = m_ctx.m_cache.try_emplace(he); + if(!inserted) return polyline; + if(m_ctx.is_cancelled()) return polyline; - if(m_ctx.is_cancelled()) { - return polyline; - } - - polyline.reserve(static_cast(m_ctx.bbox().x_span() / m_ctx.approx_error)); const X_monotone_curve_2& curve = he->curve(); - Execution_context ctx(m_ctx, curve, m_approximate_2, polyline); - std::optional last_pt; - - m_approximate_2(curve, m_ctx.approx_error, boost::make_function_output_iterator([&ctx, &last_pt](Approx_point pt) { - if(last_pt.has_value()) { - if(last_pt->x() < ctx->xmin() && pt.x() >= ctx->xmin()) { - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Left); - } - if(last_pt->y() < ctx->ymin()) { - if(pt.y() >= ctx->ymin()) { - *ctx.out_it++ = last_pt.value(); - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Bottom); - } - if(pt.y() > ctx->ymax()) { - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Top); - } - } else if(last_pt->y() > ctx->ymax()) { - if(pt.y() <= ctx->ymax()) { - *ctx.out_it++ = last_pt.value(); - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Top); - } - if(pt.y() < ctx->ymin()) { - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Bottom); - } - } else { - if(pt.y() < ctx->ymin()) { - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Bottom); - } else if(pt.y() > ctx->ymax()) { - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Top); - } - } - if(last_pt->x() <= ctx->xmax() && pt.x() > ctx->xmax()) { - update_on_crossing_boundary(ctx, last_pt.value(), pt, Side_of_boundary::Right); - } - - if(!(pt.y() > ctx->ymax() && last_pt->y() > ctx->ymax() || - pt.y() < ctx->ymin() && last_pt->y() < ctx->ymin())) { - *ctx.out_it++ = pt; - } - } else { - *ctx.out_it++ = pt; - } - - last_pt = pt; - }), - true); - + Context ctx(m_ctx, curve, polyline, m_boundary_lines); + m_ctx.m_traits.approximate_2_object()( + curve, m_ctx.m_approx_error, boost::make_function_output_iterator([&ctx](Approx_point pt) { update(ctx, pt); }), + true); return polyline; } private: const Bounded_render_context& m_ctx; - const Approximate_2 m_approximate_2; + const Boundary_lines m_boundary_lines; }; -/** - * @brief Functor to approximate an x-monotone curve within an bounding box. - * The bbox here has closed boundary. - * - * The Approximation is done from xmin to xmax with a given step. For parts outbound the y limits and precedes or - * succeeds a part within, the approximation may be skipped but there will be at least one point outside the bbox - * for indication. - * - */ -template -using Arr_bounded_approximate_curve_2 = Arr_bounded_approximate_curve_2_impl< - Arrangement, - has_approximate_2_object_v && - has_operator_xcv_v>; - } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_CURVE_2_H \ No newline at end of file +#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_face_2.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_face_2.h index 9bb295ffeb3..6bcc444bc77 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_face_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_face_2.h @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include #include #include @@ -21,120 +23,18 @@ namespace CGAL { namespace draw_aos { + /** - * @brief Patches corners between two boundary points of the bbox - * counter-clockwisely. + * @brief A stateful geometry simplifier that simplifies horizontal and vertical segments + * + * @tparam GeomTraits + * @tparam OutputIterator */ -template -class Patch_boundary -{ - using Approx_point = typename Arr_approximation_geometry_traits::Approx_point; - -private: - Side_of_boundary side_of_boundary(Approx_point pt) const { - if(m_bbox.xmin() <= pt.x() && pt.x() < m_bbox.xmax() && pt.y() == m_bbox.ymax()) { - return Side_of_boundary::Top; - } else if(pt.x() == m_bbox.xmin() && m_bbox.ymin() <= pt.y() && pt.y() < m_bbox.ymax()) { - return Side_of_boundary::Left; - } else if(m_bbox.xmin() < pt.x() && pt.x() <= m_bbox.xmax() && pt.y() == m_bbox.ymin()) { - return Side_of_boundary::Bottom; - } else if(pt.x() == m_bbox.xmax() && m_bbox.ymin() < pt.y() && pt.y() <= m_bbox.ymax()) { - return Side_of_boundary::Right; - } else { - return Side_of_boundary::None; - } - } - - Approx_point corner_of_side(Side_of_boundary side) const { - switch(side) { - case Side_of_boundary::Top: - // return the top-left corner - return Approx_point(m_bbox.xmin(), m_bbox.ymax()); - case Side_of_boundary::Left: - // return the bottom-left corner - return Approx_point(m_bbox.xmin(), m_bbox.ymin()); - case Side_of_boundary::Bottom: - // return the bottom-right corner - return Approx_point(m_bbox.xmax(), m_bbox.ymin()); - case Side_of_boundary::Right: - // return the top-right corner - return Approx_point(m_bbox.xmax(), m_bbox.ymax()); - default: - CGAL_assertion(false && "Invalid side of rectangle"); - return Approx_point(); - } - } - - Side_of_boundary prev_side(Side_of_boundary side) const { - CGAL_assertion(side != Side_of_boundary::None); - return static_cast((static_cast(side) + 3) % 4); - } - - Side_of_boundary next_side(Side_of_boundary side) const { - CGAL_assertion(side != Side_of_boundary::None); - return static_cast((static_cast(side) + 1) % 4); - } - - // Computes the distance between two points with the precondition that they are on the same side of the boundary. - double distance_on_same_side(Approx_point pt1, Approx_point pt2) const { - return std::abs(pt1.x() - pt2.x()) + std::abs(pt1.y() - pt2.y()); - } - -public: - Patch_boundary(Bbox_2 bbox) - : m_bbox(bbox) {} - - /** - * @brief Patch the boundary between two points on the boundary of the bbox counter-clockwisely. - */ - template - void operator()(Approx_point from, Approx_point to, OutputIterator out_it) const { - auto from_side = side_of_boundary(from); - auto to_side = side_of_boundary(to); - - if(from_side == Side_of_boundary::None || to_side == Side_of_boundary::None) { - return; - } - - bool from_is_before_to = from_side == to_side && distance_on_same_side(from, corner_of_side(from_side)) <= - distance_on_same_side(to, corner_of_side(to_side)); - // Special case: if both on the same side and the current point is before - // the last point in counter-clockwise boundary of the bbox, We have to go - // around the bbox. Note that this includes when two points overlaps - int num_corners_to_patch = - from_is_before_to ? 4 : (static_cast(to_side) - static_cast(from_side) + 4) % 4; - - for(int i = 0; i < num_corners_to_patch; ++i) { - Side_of_boundary side = static_cast((static_cast(from_side) + i) % 4); - Approx_point corner = corner_of_side(side); - if(corner == from) { - continue; - } - *out_it++ = corner; - } - return; - } - - /** - * @brief Patch all four sides. - */ - template - void operator()(OutputIterator out_it) const { - for(auto side : {Side_of_boundary::Top, Side_of_boundary::Left, Side_of_boundary::Bottom, Side_of_boundary::Right}) - { - *out_it++ = corner_of_side(side); - } - } - -private: - const Bbox_2 m_bbox; -}; - template class Colinear_simplifier { using Geom_traits = GeomTraits; - using Approx_point = typename Arr_approximation_geometry_traits::Approx_point; + using Approx_point = typename Arr_approximate_traits::Approx_point; public: using Insert_iterator = boost::function_output_iterator>; @@ -158,10 +58,10 @@ public: Insert_iterator insert_iterator() { return boost::make_function_output_iterator(std::function([this](Approx_point p) { if(m_mid.has_value()) { - if(p.y() == m_mid->y() && p.y() == m_start->y() || p.x() == m_mid->x() && p.x() == m_start->x()) { + if(p.y() == m_mid->y() && p.y() == m_start->y() || p.x() == m_mid->x() && p.x() == m_start->x()) // Three points are collinear horizontally or vertically. m_mid = p; - } else { + else { *m_out_it++ = m_start.value(); m_start = m_mid; m_mid = p; @@ -169,11 +69,10 @@ public: return; } - if(m_start.has_value()) { + if(m_start.has_value()) m_mid = p; - } else { + else m_start = p; - } })); } @@ -193,26 +92,26 @@ template class Arr_bounded_approximate_face_2 { using Geom_traits = typename Arrangement::Geometry_traits_2; - using Approx_traits = Arr_approximation_geometry_traits; + using Approx_traits = Arr_approximate_traits; using Face_const_handle = typename Arrangement::Face_const_handle; using Halfedge_const_handle = typename Arrangement::Halfedge_const_handle; using Vertex_const_handle = typename Arrangement::Vertex_const_handle; using Polyline_geom = typename Approx_traits::Polyline_geom; using Ccb_halfedge_const_circulator = typename Arrangement::Ccb_halfedge_const_circulator; using Approx_point = typename Approx_traits::Approx_point; + using Approx_nt = typename Approx_traits::Approx_nt; + using Approx_kernel = typename Approx_traits::Approx_kernel; + using Approx_line_2 = typename Approx_kernel::Line_2; using Triangulated_face = typename Approx_traits::Triangulated_face; using Feature_portal_map = typename Arr_portals::Feature_portals_map; - using Portal_vector = typename Arr_portals::Portal_vector; - using Portal = typename Arr_portals::Portal; - using Point_or_portal = std::variant; - using FT = typename Traits_adaptor::FT; + using Portal_exit = typename Arr_portals::Portal_exit; + using Portal_exit_vector = typename Arr_portals::Portal_exit_vector; using Bounded_approximate_point_2 = Arr_bounded_approximate_point_2; using Bounded_approximate_curve_2 = Arr_bounded_approximate_curve_2; using Bounded_render_context = Arr_bounded_render_context; using Triangulator = Arr_bounded_face_triangulator; - using Patch = Patch_boundary; using Simplifier = Colinear_simplifier; struct Left_to_right_tag @@ -226,7 +125,7 @@ class Arr_bounded_approximate_face_2 {}; private: - class Execution_context : public Arr_context_delegator + class Context : public Bounded_render_context { private: using Output_iterator = @@ -235,52 +134,35 @@ private: public: using Insert_iterator = boost::function_output_iterator>; - Execution_context(const Bounded_render_context& ctx, - const Patch& patch, - Output_iterator out_it, - const Bounded_approximate_point_2& bounded_approx_pt, - const Bounded_approximate_curve_2& bounded_approx_curve) - : Arr_context_delegator(ctx) - , base_out_it(out_it) - , patch(patch) - , bounded_approx_pt(bounded_approx_pt) - , bounded_approx_curve(bounded_approx_curve) { - this->out_it = boost::make_function_output_iterator(std::function([this](Approx_point pt) { - if(!(*this)->contains_x(pt.x())) { - return; - } - - pt = Approx_point(pt.x(), std::clamp(pt.y(), (*this)->ymin(), (*this)->ymax())); - if(pt == last_pt) { - passed_fictitious_edge = false; - return; - } - - if(passed_fictitious_edge && last_pt.has_value()) { - this->patch(last_pt.value(), pt, base_out_it); - } - passed_fictitious_edge = false; - - *base_out_it++ = pt; - - if(!first_pt.has_value()) { - first_pt = pt; - } - last_pt = pt; + Context(const Bounded_render_context& ctx, + Output_iterator out_it, + const Bounded_approximate_point_2& bounded_approx_pt, + const Bounded_approximate_curve_2& bounded_approx_curve) + : Bounded_render_context(ctx) + , m_base_out_it(out_it) + , m_approx(ctx.m_traits.approximate_2_object()) + , m_bounded_approx_pt(bounded_approx_pt) + , m_bounded_approx_curve(bounded_approx_curve) { + m_out_it = boost::make_function_output_iterator(std::function([this](Approx_point pt) { + if(!this->contains_x(pt.x())) return; + pt = Approx_point(pt.x(), std::clamp(pt.y(), this->ymin(), this->ymax())); + m_last_pt = pt; + *m_base_out_it++ = pt; })); } // Let's not accidentally copy this object. - Execution_context(const Execution_context&) = delete; - Execution_context& operator=(const Execution_context&) = delete; + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + + private: + Output_iterator m_base_out_it; public: - const Bounded_approximate_point_2& bounded_approx_pt; - const Bounded_approximate_curve_2& bounded_approx_curve; - Insert_iterator out_it; - std::optional last_pt, first_pt; - bool passed_fictitious_edge{false}; - Output_iterator base_out_it; - const Patch& patch; + const Bounded_approximate_point_2& m_bounded_approx_pt; + const Bounded_approximate_curve_2& m_bounded_approx_curve; + std::optional m_last_pt; + Insert_iterator m_out_it; + const typename Geom_traits::Approximate_2 m_approx; }; private: @@ -292,56 +174,81 @@ private: */ static Halfedge_const_handle get_inner_ccb_first_halfedge(const Vertex_const_handle& vh) { auto circ = vh->incident_halfedges(); - if(vh->degree() == 1) { - return circ->twin(); - } + if(vh->degree() == 1) return circ->twin(); + auto curr = circ; auto next = curr; ++next; // Traverse the halfedges incident to the vertex in clockwise direction. // Note that circulator around a vertex points to a halfedge whose target is the vertex. do { - if(curr->direction() == ARR_LEFT_TO_RIGHT && next->direction() == ARR_RIGHT_TO_LEFT) { - // This indicates that "curr" crosses 12 o'clock and reaches "next". - break; - } + // If "curr" crosses 12 o'clock and reaches "next", we found the wanted halfedge. + if(curr->direction() == ARR_LEFT_TO_RIGHT && next->direction() == ARR_RIGHT_TO_LEFT) break; ++curr; ++next; } while(curr != circ); - + // The opposite halfedge is the one we want. return next->twin(); } - static void approximate_vertex(Execution_context& ctx, const Vertex_const_handle& vh) { - if(vh->is_at_open_boundary()) { - return; - } - ctx.bounded_approx_pt(vh); + static Arr_parameter_space side_of_fictitious_edge(const Halfedge_const_handle& he) { + const auto& source = he->source(); + const auto& target = he->target(); + Arr_parameter_space src_x_space = source->parameter_space_in_x(); + Arr_parameter_space src_y_space = source->parameter_space_in_y(); + Arr_parameter_space tgt_x_space = target->parameter_space_in_x(); + Arr_parameter_space tgt_y_space = target->parameter_space_in_y(); + if(src_x_space == tgt_x_space && src_x_space != ARR_INTERIOR) return src_x_space; + if(src_y_space == tgt_y_space && src_y_space != ARR_INTERIOR) return src_y_space; + CGAL_assertion(false && "Unexpected parameter space for fictitious edge vertices."); + return ARR_INTERIOR; + } - auto portals_it = ctx->feature_portals.find(vh); - if(portals_it == ctx->feature_portals.end()) { - return; + // Generate dummy curve(directed left to right) for the fictitious edge. + static Polyline_geom approximate_fictitious_edge(const Context& ctx, const Halfedge_const_handle& he) { + Arr_parameter_space side = side_of_fictitious_edge(he); + // There's no need to handle fictitious edges on left or right boundaries. + if(side == ARR_LEFT_BOUNDARY || side == ARR_RIGHT_BOUNDARY) return Polyline_geom{}; + if(side == ARR_BOTTOM_BOUNDARY) { + Approx_point from_pt(ctx.m_last_pt.has_value() ? ctx.m_last_pt->x() : ctx.xmin(), ctx.ymin()); + Approx_point to_pt(ctx.xmax(), ctx.ymin()); + return Polyline_geom{from_pt, to_pt}; } + if(side == ARR_TOP_BOUNDARY) { + Approx_point from_pt(ctx.xmin(), ctx.ymax()); + Approx_point to_pt(ctx.m_last_pt.has_value() ? ctx.m_last_pt->x() : ctx.xmax(), ctx.ymax()); + return Polyline_geom{from_pt, to_pt}; + } + CGAL_assertion(false && "Unexpected side for a fictitious edge."); + return Polyline_geom{}; + } + + static void approximate_vertex(Context& ctx, const Vertex_const_handle& vh) { + if(vh->is_at_open_boundary()) return; + ctx.m_bounded_approx_pt(vh); + // Handle portal from this vertex if there is one. + auto portals_it = ctx.m_feature_portals.find(vh); + if(portals_it == ctx.m_feature_portals.end()) return; const auto& portals = portals_it->second; - if(portals.empty()) { - return; - } + if(portals.empty()) return; CGAL_assertion_msg(portals.size() == 1, "Vertex should have at most one portal."); - traverse_portal(ctx, portals.front()); + traverse_portal(ctx, ctx.m_approx(vh->point()), portals.front()); } template - static void approximate_halfedge(Execution_context& ctx, const Halfedge_const_handle& he, DirectionTag dir_tag) { + static void approximate_halfedge(Context& ctx, const Halfedge_const_handle& he, DirectionTag dir_tag) { constexpr bool Is_left_to_right = std::is_same_v; using Approx_point_it = std::conditional_t; - using Portals_it = std::conditional_t; + using Portals_it = std::conditional_t; + using Point_or_portal = std::variant; - const Polyline_geom& polyline = he->is_fictitious() ? Polyline_geom{} : ctx.bounded_approx_curve(he); - auto portal_map_it = ctx->feature_portals.find(he); - const Portal_vector& portals = - portal_map_it != ctx->feature_portals.end() ? portal_map_it->second : Portal_vector{}; + const Polyline_geom& polyline = + he->is_fictitious() ? approximate_fictitious_edge(ctx, he) : ctx.m_bounded_approx_curve(he); + auto portal_map_it = ctx.m_feature_portals.find(he); + const Portal_exit_vector& portals = + portal_map_it != ctx.m_feature_portals.end() ? portal_map_it->second : Portal_exit_vector{}; Approx_point_it points_iter, points_end; Portals_it portals_iter, portals_end; @@ -356,74 +263,64 @@ private: portals_iter = portals.rbegin(); portals_end = portals.rend(); } + auto less_than = [](Approx_nt x1, Approx_nt x2) { return Is_left_to_right ? x1 < x2 : x1 > x2; }; - std::merge(points_iter, points_end, portals_iter, portals_end, - boost::make_function_output_iterator([&ctx](const Point_or_portal& point_or_portal) { - if(auto* portal = std::get_if(&point_or_portal)) { - traverse_portal(ctx, *portal); - return; - } - *ctx.out_it++ = std::get(point_or_portal); - }), - [](const Portal& portal, const Approx_point& pt) { - return Is_left_to_right ? portal.second->point().x() < FT(pt.x()) - : portal.second->point().x() > FT(pt.x()); - }); + // A variant of two pointers algorithm, but std::merge() can't fit in our purpose. + std::optional prev_pt; + for(; points_iter != points_end; ++points_iter) { + Approx_point curr_pt = *points_iter; + + for(; portals_iter != portals_end; ++portals_iter) { + const Portal_exit& exit = *portals_iter; + Approx_point exit_pt = ctx.m_approx(exit->point()); + if(!less_than(exit_pt.x(), curr_pt.x()) || !prev_pt.has_value()) break; + if(less_than(exit_pt.x(), prev_pt->x())) { + traverse_portal(ctx, exit_pt, exit); + continue; + } + // Compute the portal entry point. + std::optional> res = CGAL::intersection( + Approx_line_2(*prev_pt, curr_pt), Approx_line_2(exit_pt, Approx_point(exit_pt.x(), exit_pt.y() + 1))); + CGAL_assertion(res.has_value() && std::holds_alternative(*res)); + traverse_portal(ctx, std::get(*res), exit); + } + + *ctx.m_out_it++ = curr_pt; + prev_pt = curr_pt; + } + + for(; portals_iter != portals_end; ++portals_iter) { + const Portal_exit& exit = *portals_iter; + traverse_portal(ctx, ctx.m_approx(exit->point()), exit); + } } - static void approximate_inner_ccb(Execution_context& ctx, const Vertex_const_handle& vh) { + static void approximate_inner_ccb(Context& ctx, const Vertex_const_handle& vh) { if(vh->is_isolated()) { approximate_vertex(ctx, vh); return; } - approximate_ccb(ctx, get_inner_ccb_first_halfedge(vh)); + approximate_ccb(ctx, get_inner_ccb_first_halfedge(vh)); } - static void traverse_portal(Execution_context& ctx, const Portal& portal) { // We have a portal. - const auto& [source, dest] = portal; - Approx_point dest_pt = ctx->approx_pt(dest->point()); - Approx_point source_pt = source.has_value() ? source.value() : Approx_point(dest_pt.x(), ctx->ymax()); - - *ctx.out_it++ = source_pt; - approximate_inner_ccb(ctx, dest); + static void traverse_portal(Context& ctx, Approx_point entry, const Portal_exit& exit) { + *ctx.m_out_it++ = entry; + approximate_inner_ccb(ctx, exit); // We come back here after inner ccb is approximated. - *ctx.out_it++ = source_pt; - - if(!source.has_value()) { - ctx.passed_fictitious_edge = true; - } + *ctx.m_out_it++ = entry; } - template - static void approximate_ccb(Execution_context& ctx, Ccb_halfedge_const_circulator start_circ) { - constexpr bool Is_outer_ccb = std::is_same_v; - static_assert(Is_outer_ccb || !Unbounded, "Inner CCBs are impossible to be unbounded."); - - // For unbound ccb, we start on a fictitious edge - if constexpr(Unbounded) { - while(!start_circ->is_fictitious()) { - ++start_circ; - } - } + static void approximate_ccb(Context& ctx, Ccb_halfedge_const_circulator start_circ) { + // Try to start on a concrete halfedge. + // For any unbounded face, there can't be more than 4 adjacent fictitious edges. + for(int i = 0; i < 4 && start_circ->is_fictitious(); ++i) ++start_circ; auto circ = start_circ; do { - if(circ->is_fictitious()) { - ctx.passed_fictitious_edge = true; - } - circ->direction() == ARR_LEFT_TO_RIGHT ? approximate_halfedge(ctx, circ, Left_to_right_tag{}) : approximate_halfedge(ctx, circ, Right_to_left_tag{}); approximate_vertex(ctx, circ->target()); } while(++circ != start_circ); - - if constexpr(Unbounded) { - if(ctx.first_pt.has_value()) { - ctx.patch(ctx.last_pt.value(), ctx.first_pt.value(), ctx.base_out_it); - } else { - ctx.patch(ctx.base_out_it); - } - } } public: @@ -431,19 +328,13 @@ public: const Bounded_approximate_point_2& bounded_approx_pt, const Bounded_approximate_curve_2& bounded_approx_curve) : m_ctx(ctx) - , m_patch(ctx.bbox()) , m_bounded_approx_pt(bounded_approx_pt) , m_bounded_approx_curve(bounded_approx_curve) {} const Triangulated_face& operator()(const Face_const_handle& fh) const { - auto [triangulated_face, inserted] = m_ctx.cache.try_emplace(fh); - if(!inserted) { - return triangulated_face; - } - - if(m_ctx.is_cancelled()) { - return triangulated_face; - } + auto [triangulated_face, inserted] = m_ctx.m_cache.try_emplace(fh); + if(!inserted) return triangulated_face; + if(m_ctx.is_cancelled()) return triangulated_face; CGAL_precondition_msg(!fh->is_fictitious(), "Cannot approximate a fictitious face."); @@ -452,33 +343,19 @@ public: for(auto inner_ccb = fh->inner_ccbs_begin(); inner_ccb != fh->inner_ccbs_end(); ++inner_ccb) { auto circ = *inner_ccb; do { - if(circ->face() != circ->twin()->face()) { - // Found non degenerate edge, skip. - continue; - } + if(circ->face() != circ->twin()->face()) continue; m_bounded_approx_curve(circ); } while(++circ != *inner_ccb); } - for(auto vh = fh->isolated_vertices_begin(); vh != fh->isolated_vertices_end(); ++vh) { - m_bounded_approx_pt(vh); - } - + for(auto vh = fh->isolated_vertices_begin(); vh != fh->isolated_vertices_end(); ++vh) m_bounded_approx_pt(vh); return triangulated_face; } auto triangulator = Triangulator(m_ctx); auto simplifier = Simplifier(triangulator.insert_iterator(), m_ctx.bbox()); - auto ctx = - Execution_context(m_ctx, m_patch, simplifier.insert_iterator(), m_bounded_approx_pt, m_bounded_approx_curve); - - if(fh->is_unbounded()) { - approximate_ccb(ctx, fh->outer_ccb()); - } else { - approximate_ccb(ctx, fh->outer_ccb()); - } - + auto ctx = Context(m_ctx, simplifier.insert_iterator(), m_bounded_approx_pt, m_bounded_approx_curve); + approximate_ccb(ctx, fh->outer_ccb()); simplifier.dump(); - return triangulated_face = std::move(triangulator); } @@ -486,9 +363,9 @@ private: const Bounded_render_context& m_ctx; const Bounded_approximate_point_2& m_bounded_approx_pt; const Bounded_approximate_curve_2& m_bounded_approx_curve; - const Patch m_patch; }; } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_FACE_2_H \ No newline at end of file + +#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_point_2.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_point_2.h index 514b7e62f6e..acb3b4aeefd 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_point_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_approximate_point_2.h @@ -2,11 +2,8 @@ #ifndef CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_POINT_2_H #define CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_POINT_2_H -#include - #include #include -#include namespace CGAL { namespace draw_aos { @@ -15,14 +12,15 @@ template class Arr_bounded_approximate_point_2 { using Geom_traits = typename Arrangement::Geometry_traits_2; - using Point_2 = typename Traits_adaptor::Point_2; + using Point_2 = typename Geom_traits::Point_2; using Vertex_const_handle = typename Arrangement::Vertex_const_handle; - using Point_geom = typename Arr_approximation_geometry_traits::Point_geom; + using Point_geom = typename Arr_approximate_traits::Point_geom; using Bounded_render_context = Arr_bounded_render_context; public: Arr_bounded_approximate_point_2(const Bounded_render_context& ctx) - : m_ctx(ctx) {} + : m_ctx(ctx) + , m_approx(ctx.m_traits.approximate_2_object()) {} /** * @brief Approximate a vertex within the x-bounded range. @@ -30,31 +28,21 @@ public: * The function uses cached values if available. * @precondition: The vertex must have an associated point. * - * If the point is outside the bounding box, it returns Point_geom{} and false. - * Otherwise, it returns the approximated point and true. * @param vh the vertex handle - * @return std::pair + * @return const Point_geom& */ - std::pair operator()(const Vertex_const_handle& vh) const { - const auto& pt = vh->point(); - - if(!m_ctx.strictly_contains(pt)) { - return {Point_geom(), false}; - } - - auto [point, inserted] = m_ctx.cache.try_emplace(vh); - if(!inserted) { - return {point, true}; - } - - return {point = m_ctx.approx_pt(pt), true}; + const Point_geom& operator()(const Vertex_const_handle& vh) const { + auto [point, inserted] = m_ctx.m_cache.try_emplace(vh); + if(!inserted) return point; + return point = m_approx(vh->point()); } private: const Bounded_render_context& m_ctx; + const typename Geom_traits::Approximate_2 m_approx; }; } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_POINT_2_H +#endif diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_face_triangulator.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_face_triangulator.h index a7c641d255f..a7f11055bb9 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_face_triangulator.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_face_triangulator.h @@ -33,19 +33,20 @@ namespace CGAL { namespace draw_aos { /** - * @brief Triangulate a face of an arrangement within a bounding box. + * @brief Triangulator for a face of an arrangement within a bounding box. * - * @note The face must have an outer CCB. + * The original topology of a face is preserved, but the geometry will be bounded by the bbox. */ template class Arr_bounded_face_triangulator { using Geom_traits = typename Arrangement::Geometry_traits_2; - using Approx_traits = Arr_approximation_geometry_traits; + using Approx_traits = Arr_approximate_traits; using Approx_point = typename Approx_traits::Approx_point; - using Point_vec = typename Approx_traits::Apporx_point_vec; + using Point_vec = typename Approx_traits::Approx_point_vec; using Triangle = typename Approx_traits::Triangle; using Triangulated_face = typename Approx_traits::Triangulated_face; + #if defined(CGAL_DRAW_AOS_DEBUG) template friend void debug_print(const Arr_bounded_face_triangulator& triangulator); @@ -96,11 +97,10 @@ private: void insert_ccb() { auto begin = m_points.begin(); auto end = m_points.end(); - auto helpers_iter = m_helper_indices.begin(); auto helpers_end = m_helper_indices.end(); - - auto concrete_pt_filter = [&helpers_iter, helpers_end](std::size_t idx) { + // A two pointers algorithm implemented with boost filters. + auto point_filter = [&helpers_iter, helpers_end](std::size_t idx) { if(helpers_iter == helpers_end) { return true; } @@ -110,51 +110,37 @@ private: } return true; }; - - auto indexes_begin = boost::make_counting_iterator(0); - auto indexes_end = boost::make_counting_iterator(m_points.size()); - auto filtered_begin = boost::make_filter_iterator(concrete_pt_filter, indexes_begin, indexes_end); - auto filtered_end = boost::make_filter_iterator(concrete_pt_filter, indexes_end, indexes_end); auto index_to_point_with_info = [this](std::size_t idx) -> KPoint_with_info { return std::make_pair(transform_point(m_points[idx]), idx); }; + auto indexes_begin = boost::make_counting_iterator(0); + auto indexes_end = boost::make_counting_iterator(m_points.size()); + auto filtered_begin = boost::make_filter_iterator(point_filter, indexes_begin, indexes_end); + auto filtered_end = boost::make_filter_iterator(point_filter, indexes_end, indexes_end); auto transformed_begin = boost::make_transform_iterator(filtered_begin, index_to_point_with_info); auto transformed_end = boost::make_transform_iterator(filtered_end, index_to_point_with_info); - m_ct.template insert_with_info(transformed_begin, transformed_end); } - Side_of_boundary shared_boundary_side(const Approx_point& pt1, const Approx_point& pt2) const { - if(pt1.x() == m_ctx.xmin() && pt2.x() == m_ctx.xmin() && m_ctx.contains_y(pt1.y()) && m_ctx.contains_y(pt2.y())) { + Side_of_boundary shared_boundary(const Approx_point& pt1, const Approx_point& pt2) const { + if(pt1.x() == m_ctx.xmin() && pt2.x() == m_ctx.xmin() && m_ctx.contains_y(pt1.y()) && m_ctx.contains_y(pt2.y())) return Side_of_boundary::Left; - } else if(pt1.x() == m_ctx.xmax() && pt2.x() == m_ctx.xmax() && m_ctx.contains_y(pt1.y()) && - m_ctx.contains_y(pt2.y())) - { + if(pt1.x() == m_ctx.xmax() && pt2.x() == m_ctx.xmax() && m_ctx.contains_y(pt1.y()) && m_ctx.contains_y(pt2.y())) return Side_of_boundary::Right; - } else if(pt1.y() == m_ctx.ymin() && pt2.y() == m_ctx.ymin() && m_ctx.contains_x(pt1.x()) && - m_ctx.contains_x(pt2.x())) - { + if(pt1.y() == m_ctx.ymin() && pt2.y() == m_ctx.ymin() && m_ctx.contains_x(pt1.x()) && m_ctx.contains_x(pt2.x())) return Side_of_boundary::Bottom; - } else if(pt1.y() == m_ctx.ymax() && pt2.y() == m_ctx.ymax() && m_ctx.contains_x(pt1.x()) && - m_ctx.contains_x(pt2.x())) - { + if(pt1.y() == m_ctx.ymax() && pt2.y() == m_ctx.ymax() && m_ctx.contains_x(pt1.x()) && m_ctx.contains_x(pt2.x())) return Side_of_boundary::Top; - } return Side_of_boundary::None; } void add_boundary_helper_point(Approx_point from, Approx_point to) { - if(from == to) { - return; - } - auto shared_side = shared_boundary_side(from, to); - if(shared_side == Side_of_boundary::None) { - return; - } + auto shared_side = shared_boundary(from, to); + if(shared_side == Side_of_boundary::None) return; m_helper_indices.push_back(m_points.size()); m_points.emplace_back( offset_boundary_point(Approx_point((from.x() + to.x()) / 2, (from.y() + to.y()) / 2), shared_side, m_offset)); - m_offset += 0.5; + m_offset += 0.5; // It can be arbitrary increment. } public: @@ -172,6 +158,11 @@ public: })); } + /** + * @brief Converts the triangulator to a triangulated face, moving internal data to the result. + * + * @return Triangulated_face + */ operator Triangulated_face() && { if(m_points.empty()) { return Triangulated_face(); @@ -192,25 +183,21 @@ public: } unordered_flat_map in_domain_map; + in_domain_map.reserve(m_ct.number_of_faces()); boost::associative_property_map in_domain(in_domain_map); CGAL::mark_domain_in_triangulation(m_ct, in_domain); Triangulated_face tf; tf.triangles.reserve(m_ct.number_of_faces()); for(auto fit = m_ct.finite_faces_begin(); fit != m_ct.finite_faces_end(); ++fit) { - if(!get(in_domain, fit)) { - continue; - } + if(!get(in_domain, fit)) continue; Point_index v1 = fit->vertex(0)->info(); Point_index v2 = fit->vertex(1)->info(); Point_index v3 = fit->vertex(2)->info(); - if(!v1.is_valid() || !v2.is_valid() || !v3.is_valid()) { - continue; - } + if(!v1.is_valid() || !v2.is_valid() || !v3.is_valid()) continue; Triangle tri{v1, v2, v3}; tf.triangles.emplace_back(tri); } - tf.points = std::move(m_points); return tf; } @@ -259,9 +246,9 @@ void debug_print(const Arr_bounded_face_triangulator& triangulator) ofs_ccb_constraint << pt.x() << " " << pt.y() << "\n"; } } -#endif // CGAL_DRAW_AOS_DEBUG && CGAL_DRAW_AOS_TRIANGULATOR_DEBUG_FILE_DIR +#endif } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_FACE_TRIANGULATOR_H \ No newline at end of file +#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_renderer.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_renderer.h index 98030b55f5c..d5d23373ff3 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_renderer.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_bounded_renderer.h @@ -1,239 +1,62 @@ #ifndef CGAL_DRAW_AOS_ARR_BOUNDED_RENDERER_H #define CGAL_DRAW_AOS_ARR_BOUNDED_RENDERER_H -#include - -#include #include -#include #include -#include #include #include -#include #include +#include namespace CGAL { namespace draw_aos { /** * @brief Render arrangement on surface within a bounding box. - * - * @note The class is not thread-safe. */ template class Arr_bounded_renderer { - using Color = IO::Color; using Geom_traits = typename Arrangement::Geometry_traits_2; - using FT = typename Traits_adaptor::FT; - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using Vertex_const_handle = typename Arrangement::Vertex_const_handle; - using Edge_const_handle = typename Arrangement::Edge_const_iterator; - using Halfedge_const_handle = typename Arrangement::Halfedge_const_handle; using Face_const_handle = typename Arrangement::Face_const_handle; - using Vertex_handle = typename Arrangement::Vertex_handle; - using Halfedge_handle = typename Arrangement::Halfedge_handle; - using Face_handle = typename Arrangement::Face_handle; - using Point_Location = Arr_trapezoid_ric_point_location; - using Feature_const = std::variant; - using Feature_const_vector = std::vector; - using Feature_const_vector_inserter = std::back_insert_iterator; - using Approx_traits = Arr_approximation_geometry_traits; - using Point_geom = typename Approx_traits::Point_geom; - using Polyline_geom = typename Approx_traits::Polyline_geom; - using Feature_portal_map = typename Arr_portals::Feature_portals_map; - using Bounded_render_context = Arr_bounded_render_context; using Render_context = Arr_render_context; + using Bounded_render_context = Arr_bounded_render_context; using Bounded_approx_point_2 = Arr_bounded_approximate_point_2; using Bounded_approx_curve_2 = Arr_bounded_approximate_curve_2; using Bounded_approx_face_2 = Arr_bounded_approximate_face_2; using Approx_cache = Arr_approximation_cache; - // QFlags implement this pattern better, but we try not to reply on Qt classes. - template - class Arr_flags - { - public: - Arr_flags(const E& flags) - : m_flags(static_cast(flags)) {} - Arr_flags(std::initializer_list flags) - : m_flags(0) { - for(const auto& flag : flags) { - m_flags |= static_cast(flag); - } - } - - bool is_set(E flag) const { return (m_flags & static_cast(flag)) != 0; } - - private: - int m_flags; - }; - - enum class Feature_type { - Vertex = 1 << 0, - Halfedge = 1 << 1, - Face = 1 << 2, - }; - - struct Execution_context : Arr_context_delegator - { - Execution_context(const Bounded_render_context& ctx) - : Arr_context_delegator(ctx) - , bounded_approx_pt(ctx) - , bounded_approx_curve(ctx) - , bounded_approx_face(ctx, bounded_approx_pt, bounded_approx_curve) {} - - const Bounded_approx_point_2 bounded_approx_pt; - const Bounded_approx_curve_2 bounded_approx_curve; - const Bounded_approx_face_2 bounded_approx_face; - }; - -private: - static Face_const_handle inbound_face_of_edge(const Halfedge_const_handle& he, Side_of_boundary side) { - bool is_left_to_right = he->direction() == ARR_LEFT_TO_RIGHT; - switch(side) { - case Side_of_boundary::Top: - case Side_of_boundary::Left: - return is_left_to_right ? he->twin()->face() : he->face(); - case Side_of_boundary::Bottom: - case Side_of_boundary::Right: - return is_left_to_right ? he->face() : he->twin()->face(); - default: - CGAL_assertion(false && "Invalid side of boundary"); - } - return Face_const_handle(); - } - static void approx_intersecting_features(Execution_context& ctx, - const X_monotone_curve_2& curve, - Side_of_boundary side, - Arr_flags feats) { - CGAL_assertion(side != Side_of_boundary::None); - using Feature = std::variant; - using Feature_vector = std::vector; - - auto func_out_iter = boost::make_function_output_iterator([&ctx, feats, side](const Feature& feature) { - if(auto* vh = std::get_if(&feature)) { - if(!feats.is_set(Feature_type::Vertex) || (*vh)->is_at_open_boundary()) { - return; - } - ctx.bounded_approx_pt(*vh); - } else if(auto* he = std::get_if(&feature)) { - if(!feats.is_set(Feature_type::Halfedge) || (*he)->is_fictitious()) { - return; - } - ctx.bounded_approx_curve(*he); - - // There's a rare case that some faces' outer ccb overlaps with the bounding box edges, causing - // no face to be discovered. We need to approximate the inbound face incident to the halfedge - discover_faces(ctx, inbound_face_of_edge(*he, side)); - } else if(auto* fh = std::get_if(&feature)) { - if(!feats.is_set(Feature_type::Face)) { - return; - } - discover_faces(ctx, *fh); - } - }); - - using Zone_visitor = Arr_compute_zone_visitor; - using Zone_2 = Arrangement_zone_2; - - Zone_visitor zone_visitor(func_out_iter); - // use const_cast to fit in the interface but we will not modify the arrangement - Zone_2 zone(const_cast(ctx->arr), &zone_visitor); - zone.init(curve, ctx->point_location); - zone.compute_zone(); - } - - static void discover_faces(Execution_context& ctx, const Face_const_handle& fh) { - if(ctx->cache.has(fh)) { - // The face has already been discovered and approximated. - return; - } - ctx.bounded_approx_face(fh); - - for(auto inner_ccb = fh->inner_ccbs_begin(); inner_ccb != fh->inner_ccbs_end(); ++inner_ccb) { - auto circ = *inner_ccb; - - // We have to traverse the entire inner ccb instead of using arbitary vertex, - // because the inner ccb may contain degenerate edges. - do { - auto inner_face = circ->twin()->face(); - if(inner_face != fh && ctx->strictly_contains(circ->source()->point())) { - discover_faces(ctx, inner_face); - } - } while(++circ != *inner_ccb); - } - - // We don't need to handle isolated vertices. - // for(...) - - if(!fh->has_outer_ccb()) { - // The unbounded face of bounded arrangements has no outer CCB. - return; - } - // find fully contained adjacent faces - auto outer_ccb = fh->outer_ccb(); - auto circ = outer_ccb; - do { - auto adj_face = circ->twin()->face(); - if(adj_face->is_fictitious() || adj_face->is_unbounded()) { - // Unbounded faces cannot be contained in out bounding box. - continue; - } - - if(!ctx->strictly_contains(adj_face->outer_ccb()->source()->point())) { - continue; - } - // For a face that is not one of the seeding faces, - // the face must be contained iff one of its vertices is contained. - // For a face that is, we'll touch it sooner or later. - discover_faces(ctx, adj_face); - } while(++circ != outer_ccb); - } - public: Arr_bounded_renderer(const Render_context& ctx, Bbox_2 bbox) : m_ctx(ctx) - , to_ft() + , m_bbox(bbox) {} Approx_cache render() const { Approx_cache cache; - cache.reserve_face_cache(m_ctx.arr.number_of_faces()); - cache.reserve_halfedge_cache(m_ctx.arr.number_of_halfedges()); - cache.reserve_vertex_cache(m_ctx.arr.number_of_vertices()); - if(m_ctx.is_cancelled()) { - return cache; - } + if(m_ctx.is_cancelled()) return cache; - Bounded_render_context bounded_ctx(m_ctx, m_bbox, cache); - Execution_context ctx(bounded_ctx); + cache.reserve_faces(m_ctx.m_arr.number_of_faces()); + cache.reserve_halfedges(m_ctx.m_arr.number_of_halfedges()); + cache.reserve_vertexs(m_ctx.m_arr.number_of_vertices()); - auto top = ctx->cst_horizontal_segment(to_ft(ctx->ymax()), to_ft(ctx->xmin()), to_ft(ctx->xmax())); - auto bottom = ctx->cst_horizontal_segment(to_ft(ctx->ymin()), to_ft(ctx->xmin()), to_ft(ctx->xmax())); - auto left = ctx->cst_vertical_segment(to_ft(ctx->xmin()), to_ft(ctx->ymin()), to_ft(ctx->ymax())); - auto right = ctx->cst_vertical_segment(to_ft(ctx->xmax()), to_ft(ctx->ymin()), to_ft(ctx->ymax())); + Bounded_render_context ctx(m_ctx, m_bbox, cache); + const Bounded_approx_point_2 bounded_approx_pt(ctx); + const Bounded_approx_curve_2 bounded_approx_curve(ctx); + const Bounded_approx_face_2 bounded_approx_face(ctx, bounded_approx_pt, bounded_approx_curve); - // top, left are open edges while bottom, right are closed. - approx_intersecting_features(ctx, top, Side_of_boundary::Top, Feature_type::Face); - approx_intersecting_features(ctx, left, Side_of_boundary::Left, Feature_type::Face); - approx_intersecting_features(ctx, bottom, Side_of_boundary::Bottom, {Feature_type::Face, Feature_type::Halfedge}); - approx_intersecting_features(ctx, right, Side_of_boundary::Right, - {Feature_type::Face, Feature_type::Halfedge, Feature_type::Vertex}); + for(Face_const_handle fh = m_ctx.m_arr.faces_begin(); fh != m_ctx.m_arr.faces_end(); ++fh) bounded_approx_face(fh); return cache; } private: const Render_context& m_ctx; - const Construct_coordinate to_ft; const Bbox_2 m_bbox; }; } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_BOUNDED_RENDERER_H \ No newline at end of file +#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_curve_end.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_curve_end.h deleted file mode 100644 index 0fa916082ce..00000000000 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_curve_end.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef CGAL_ARR_CONSTRUCT_CURVE_END_H -#define CGAL_ARR_CONSTRUCT_CURVE_END_H - -#include - -#include -#include -#include - -namespace CGAL { -namespace draw_aos { -namespace internal { - -template -class Arr_construct_curve_end_base -{ - using Construct_min_vertex_2 = typename GeomTraits::Construct_min_vertex_2; - using Construct_max_vertex_2 = typename GeomTraits::Construct_max_vertex_2; - -protected: - Arr_construct_curve_end_base(const GeomTraits& traits) - : m_cst_min_vertex(traits.construct_min_vertex_2_object()) - , m_cst_max_vertex(traits.construct_max_vertex_2_object()) {} - - Construct_max_vertex_2 m_cst_max_vertex; - Construct_min_vertex_2 m_cst_min_vertex; -}; - -template -class Arr_construct_curve_end_impl; - -template -class Arr_construct_curve_end_impl : public Arr_construct_curve_end_base -{ - using Approx_geom_traits = Arr_approximation_geometry_traits; - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve = typename Traits_adaptor::X_monotone_curve_2; - using Parameter_space_in_x_2 = typename GeomTraits::Parameter_space_in_x_2; - using Parameter_space_in_y_2 = typename GeomTraits::Parameter_space_in_y_2; - -public: - Arr_construct_curve_end_impl(const GeomTraits& traits) - : Arr_construct_curve_end_base(traits) - , m_param_space_in_x(traits.parameter_space_in_x_2_object()) - , m_param_space_in_y(traits.parameter_space_in_y_2_object()) {} - - /** - * @brief Returns an optional type contains the approximated point the given end of the curve is bounded. - * Otherwise it's std::nullopt. - * - * @param curve - * @param ce - * @return std::optional - */ - std::optional operator()(const X_monotone_curve& curve, Arr_curve_end ce) const { - auto ps_x = m_param_space_in_x(curve, ce); - auto ps_y = m_param_space_in_y(curve, ce); - - if(ps_x == ARR_INTERIOR && ps_y == ARR_INTERIOR) { - return std::make_optional(ce == ARR_MIN_END ? this->m_cst_min_vertex(curve) : this->m_cst_max_vertex(curve)); - } - - return std::nullopt; - } - -private: - Parameter_space_in_x_2 m_param_space_in_x; - Parameter_space_in_y_2 m_param_space_in_y; -}; - -template -class Arr_construct_curve_end_impl : public Arr_construct_curve_end_base -{ - using Approx_geom_traits = Arr_approximation_geometry_traits; - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve = typename Traits_adaptor::X_monotone_curve_2; - -public: - Arr_construct_curve_end_impl(const GeomTraits& traits) - : Arr_construct_curve_end_base(traits) {} - - /** - * @brief Construct the min or max vertex of the x-monotone curve based on the curve end enum. - * - * @param curve - * @param ce - * @return std::optional - */ - std::optional operator()(const X_monotone_curve& curve, Arr_curve_end ce) const { - return ce == ARR_MIN_END ? this->m_cst_min_vertex(curve) : this->m_cst_max_vertex(curve); - } -}; - -} // namespace internal - -template -using Arr_construct_curve_end = - internal::Arr_construct_curve_end_impl::Has_unbounded_curves && - has_parameter_space_in_x_2::value>; - -} // namespace draw_aos -} // namespace CGAL - -#endif // CGAL_ARR_CONSTRUCT_CURVE_END_H \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_segments.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_segments.h deleted file mode 100644 index d3d179bb9b3..00000000000 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_construct_segments.h +++ /dev/null @@ -1,173 +0,0 @@ -#ifndef CGAL_DRAW_AOS_ARR_CONSTRUCT_SEGMENTS_H -#define CGAL_DRAW_AOS_ARR_CONSTRUCT_SEGMENTS_H - -#include "CGAL/CORE/BigRat.h" -#include -#include -#include -#include - -namespace CGAL { -namespace draw_aos { - -template -class Arr_construct_segment_impl; - -// Default implementation for traits that models Construct_x_monotone_curve_2 -template -class Arr_construct_segment_impl -{ - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using Construct_x_monotone_curve_2 = typename GeomTraits::Construct_x_monotone_curve_2; - using FT = typename Traits_adaptor::FT; - -public: - Arr_construct_segment_impl(const GeomTraits& traits) - : m_cst_x_curve(traits.construct_x_monotone_curve_2_object()) {} - - X_monotone_curve_2 operator()(FT x1, FT y1, FT x2, FT y2) const { - return m_cst_x_curve(Point_2(x1, y1), Point_2(x2, y2)); - } - -private: - const Construct_x_monotone_curve_2 m_cst_x_curve; -}; - -// Specialization for Arr_circle_segment_traits_2 -template -class Arr_construct_segment_impl, false> -{ - using Geom_traits = Arr_circle_segment_traits_2; - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using Line_2 = typename X_monotone_curve_2::Line_2; - using Approximate_2 = typename Geom_traits::Approximate_2; - using NT = typename Traits_adaptor::FT; - using FT = typename Kernel::FT; - -public: - Arr_construct_segment_impl(const Geom_traits& traits) {} - - X_monotone_curve_2 operator()(NT x1, NT y1, NT x2, NT y2) const { - using Kernel_point_2 = typename Kernel::Point_2; - return X_monotone_curve_2(Kernel_point_2(CGAL::to_double(x1), CGAL::to_double(y1)), - Kernel_point_2(CGAL::to_double(x2), CGAL::to_double(y2))); - } -}; - -template -class Arr_construct_segment_impl -{ - using Geom_traits = GeomTraits; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using FT = typename Traits_adaptor::FT; - -public: - Arr_construct_segment_impl(const GeomTraits&) { CGAL_assertion_msg(false, "Not implemented yet"); } - - X_monotone_curve_2 operator()(FT, FT, FT, FT) const {} -}; - -template -using Arr_construct_segment = - Arr_construct_segment_impl::value>; - -template -class Arr_construct_vertical_segment -{ - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using FT = typename Traits_adaptor::FT; - -public: - Arr_construct_vertical_segment(const GeomTraits& traits) - : m_cst_seg(traits) {} - - X_monotone_curve_2 operator()(FT x, FT ymin, FT ymax) const { return m_cst_seg(x, ymin, x, ymax); } - -private: - const Arr_construct_segment m_cst_seg; -}; - -template -class Arr_construct_horizontal_segment -{ - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using FT = typename Traits_adaptor::FT; - -public: - Arr_construct_horizontal_segment(const GeomTraits& traits) - : m_cst_seg(traits) {} - - X_monotone_curve_2 operator()(FT y, FT xmin, FT xmax) const { return m_cst_seg(xmin, y, xmax, y); } - -private: - const Arr_construct_segment m_cst_seg; -}; - -// Arr_construct_vertical_segment Specialization for Arr_rational_function_traits_2 -template -class Arr_construct_vertical_segment> -{ - using Geom_traits = Arr_rational_function_traits_2; - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using Construct_x_monotone_curve_2 = typename Geom_traits::Construct_x_monotone_curve_2; - using FT = typename Traits_adaptor::FT; - using Bound = typename Geom_traits::Bound; - using Polynomial_1 = typename Geom_traits::Polynomial_1; - -public: - Arr_construct_vertical_segment(const Geom_traits&) {} - - X_monotone_curve_2 operator()(FT x0, FT ymin, FT ymax) const { - Geom_traits traits; - auto cst_x_curve = traits.construct_x_monotone_curve_2_object(); - - // We could only construct a near vertical segment when dealing with rational functions. - Polynomial_1 x = CGAL::shift(Polynomial_1(1), 1); - Polynomial_1 x0_num(CORE::numerator(x0.lower())); - Polynomial_1 x0_denum(CORE::denominator(x0.lower())); - double k = 100; - Bound xmin = ymin.lower() / k + x0.lower(); - Bound xmax = ymax.lower() / k + x0.lower(); - Polynomial_1 p_num = x0_num * k * x - k * x0_denum; - Polynomial_1 p_denum = x0_denum; - auto cv = cst_x_curve(p_num, p_denum, FT(xmin), FT(xmax)); - return cv; - } -}; - -// Arr_construct_horizontal_segment Specialization for Arr_rational_function_traits_2 -template -class Arr_construct_horizontal_segment> -{ - using Geom_traits = Arr_rational_function_traits_2; - using Point_2 = typename Traits_adaptor::Point_2; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; - using FT = typename Traits_adaptor::FT; - using Polynomial_1 = typename Geom_traits::Polynomial_1; - using Construct_x_monotone_curve_2 = typename Geom_traits::Construct_x_monotone_curve_2; - -public: - Arr_construct_horizontal_segment(const Geom_traits&) {} - - X_monotone_curve_2 operator()(FT y, FT xmin, FT xmax) const { - // The traits object is stateful. Currently there's a problem with internal cache that an exception will be - // triggered after constructing a number of segments. - // So we create a new traits object instead of reusing an old one. - Geom_traits traits; - auto cst_x_curve = traits.construct_x_monotone_curve_2_object(); - // TODO: only works for CORE now, support other number types - Polynomial_1 num(CORE::numerator(y.lower())); - Polynomial_1 denum(CORE::denominator(y.lower())); - return cst_x_curve(num, denum, xmin, xmax); - } -}; - -} // namespace draw_aos -} // namespace CGAL - -#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_graph_conn.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_graph_conn.h index 05ec4a63417..39dc75ccc46 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_graph_conn.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_graph_conn.h @@ -24,16 +24,10 @@ private: void insert_halfedge(Halfedge_const_handle he) { const auto& source = he->source(); const auto& target = he->target(); - auto [source_it, source_inserted] = m_lookup.try_emplace(source, Union_find_handle()); - if(source_inserted) { - source_it->second = m_uf.make_set(source); - } + if(source_inserted) source_it->second = m_uf.make_set(source); auto [target_it, target_inserted] = m_lookup.try_emplace(target, Union_find_handle()); - if(target_inserted) { - target_it->second = m_uf.make_set(target); - } - + if(target_inserted) target_it->second = m_uf.make_set(target); m_uf.unify_sets(source_it->second, target_it->second); } @@ -42,32 +36,21 @@ private: public: Arr_graph_conn(const Arr& arr) { m_lookup.reserve(arr.number_of_vertices()); - for(const auto& he : arr.halfedge_handles()) { - if(he->direction() != ARR_LEFT_TO_RIGHT) { - continue; - } + if(he->direction() != ARR_LEFT_TO_RIGHT) continue; insert_halfedge(he); } - for(const auto& vh : arr.vertex_handles()) { - if(!vh->is_isolated()) { - continue; - } + if(!vh->is_isolated()) continue; insert_isolated_vertex(vh); } - // add fictitious edges and open vertices for(auto fh = arr.unbounded_faces_begin(); fh != arr.unbounded_faces_end(); ++fh) { - if(!fh->has_outer_ccb()) { - continue; - } + if(!fh->has_outer_ccb()) continue; auto outer_ccb = fh->outer_ccb(); auto curr = outer_ccb; do { - if(!curr->is_fictitious()) { - continue; - } + if(!curr->is_fictitious()) continue; insert_halfedge(curr); } while(++curr != outer_ccb); } diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_portals.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_portals.h index e61b703c7fa..ff546d8d7fc 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_portals.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_portals.h @@ -7,9 +7,6 @@ #include #include #include -#include -#include -#include #include #include @@ -32,22 +29,19 @@ class Arr_portals using Vertex_const_handle = typename Arrangement::Vertex_const_handle; using Halfedge_const_handle = typename Arrangement::Halfedge_const_handle; using Face_const_handle = typename Arrangement::Face_const_handle; - using Point_2 = typename Traits_adaptor::Point_2; - using Approx_point = typename Arr_approximation_geometry_traits::Approx_point; - using X_monotone_curve_2 = typename Traits_adaptor::X_monotone_curve_2; + using Point_2 = typename Geom_traits::Point_2; + using X_monotone_curve_2 = typename Geom_traits::X_monotone_curve_2; + using Approx_point = typename Arr_approximate_traits::Approx_point; using Feature_const = std::variant; public: - // Pair composed of a portal entry point and the portaled vertex on the inner ccb. - using Portal = std::pair, Vertex_const_handle>; - using Portal_vector = std::vector; + using Portal_exit = Vertex_const_handle; + using Portal_exit_vector = std::vector; // Map from a feature to its portals sorted by the x coordinate of the virtual vertical segments. - using Feature_portals_map = unordered_flat_map; + using Feature_portals_map = unordered_flat_map; public: - Arr_portals(const Geom_traits& traits) - : m_approx_pt_at_x(traits) - , m_approx_pt(traits) {} + Arr_portals() {} public: Feature_portals_map create(const Arrangement& arr) const { @@ -57,48 +51,28 @@ public: Arr_graph_conn conn(arr); auto visited_ccbs = std::unordered_set(); Feature_portals_map feature_portals; - const auto& traits = *arr.geometry_traits(); - auto intersect = traits.intersect_2_object(); auto func_out_iter = boost::make_function_output_iterator([&](const Vert_decomp_entry& entry) { const auto& [vh, obj_pair] = entry; const auto& above_feat = obj_pair.second; - - const auto& ccb_main_vertex = conn.ccb_representative_vertex(vh); - if(visited_ccbs.find(ccb_main_vertex) != visited_ccbs.end()) { - // This ccb has already been processed - return; - } + Vertex_const_handle ccb_main_vertex = conn.ccb_representative_vertex(vh); + // Skip processed ccb. + if(visited_ccbs.find(ccb_main_vertex) != visited_ccbs.end()) return; if(Vertex_const_handle above_vh; CGAL::assign(above_vh, above_feat)) { - if(conn.is_connected(above_vh, vh)) { - // This upper vertex is connected to vh, skip it - return; - } - - const auto& [it, _] = feature_portals.try_emplace(above_vh, std::vector{}); - if(above_vh->is_at_open_boundary()) { - it->second.emplace_back(std::nullopt, vh); - } else { - it->second.emplace_back(m_approx_pt(above_vh->point()), vh); - } + // Skip vertex connected to vh + if(conn.is_connected(above_vh, vh)) return; + const auto& [it, _] = feature_portals.try_emplace(above_vh, Portal_exit_vector{}); + it->second.emplace_back(vh); } else if(Halfedge_const_handle above_he; CGAL::assign(above_he, above_feat)) { - if(conn.is_connected((above_he)->source(), vh)) { - return; - } - - const auto& [it, _] = feature_portals.try_emplace(above_he, std::vector{}); - if(above_he->is_fictitious()) { - it->second.emplace_back(std::nullopt, vh); - } else { - it->second.emplace_back(m_approx_pt_at_x(above_he->curve(), vh->point().x()).value(), vh); - } - } else if(Face_const_handle above_fh; CGAL::assign(above_fh, above_feat)) { + if(conn.is_connected((above_he)->source(), vh)) return; + const auto& [it, _] = feature_portals.try_emplace(above_he, Portal_exit_vector{}); + it->second.emplace_back(vh); + } else if(Face_const_handle above_fh; CGAL::assign(above_fh, above_feat)) // We don't create portals for the unbounded face in bounded arrangements. CGAL_assertion(above_fh->is_unbounded() && !above_fh->has_outer_ccb()); - } else { + else return; - } visited_ccbs.insert(ccb_main_vertex); }); @@ -106,12 +80,8 @@ public: decompose(arr, func_out_iter); return feature_portals; } - -private: - const Arr_approximate_point_2_at_x m_approx_pt_at_x; - const Arr_approximate_point_2 m_approx_pt; }; } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_CREATE_PORTALS_H \ No newline at end of file +#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_render_context.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_render_context.h index 79ff791f464..a89bd0095cf 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_render_context.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_render_context.h @@ -1,9 +1,7 @@ #ifndef CGAL_DRAW_AOS_ARR_RENDER_CONTEXT_H #define CGAL_DRAW_AOS_ARR_RENDER_CONTEXT_H #include -#include #include -#include #include #include @@ -11,10 +9,7 @@ #include #include #include -#include #include -#include -#include #include #include @@ -25,6 +20,13 @@ namespace CGAL { namespace draw_aos { +/** + * @brief A cancellable context mixin for asynchronous operations. It also tracks elapsed time for performance + * profiling. + * + * The idea is borrowed from golang with a simple implementation. + * @see https://pkg.go.dev/context + */ class Arr_cancellable_context_mixin { using Clock = std::chrono::steady_clock; @@ -34,111 +36,68 @@ class Arr_cancellable_context_mixin protected: Arr_cancellable_context_mixin() : m_start_time(Clock::now()) - , m_done(std::make_shared>(false)) {} + , m_cancelled(std::make_shared>(false)) {} public: Time_point start_time() const { return m_start_time; } - Time_point end_time() const { return m_end_time; } - Duration elapsed_time() const { return Clock::now() - m_start_time; } - - bool is_cancelled() const { return m_done->load(); } + bool is_cancelled() const { return m_cancelled->load(); } void cancel() { - m_done->store(true, std::memory_order_relaxed); + m_cancelled->store(true, std::memory_order_relaxed); m_end_time = Clock::now(); } private: Time_point m_start_time, m_end_time; - std::shared_ptr> m_done; + std::shared_ptr> m_cancelled; }; +/** + * @brief Boundary context mixin for rendering arrangements within a bounding box. + * Provides extended functionality for checking point-bbox relations. + * + * @tparam GeomTraits the geometry traits class. + */ template class Arr_bounds_context_mixin { - using Approx_point = typename Arr_approximation_geometry_traits::Approx_point; - using Point_2 = typename Traits_adaptor::Point_2; - using FT = typename Traits_adaptor::FT; + using Geom_traits = GeomTraits; + using Approx_point = typename Geom_traits::Approximate_point_2; + using Approx_nt = typename Geom_traits::Approximate_number_type; protected: Arr_bounds_context_mixin(const Bbox_2& bbox) - : m_bbox(bbox) - , to_ft() {} + : m_bbox(bbox) {} public: double xmin() const { return m_bbox.xmin(); } double xmax() const { return m_bbox.xmax(); } double ymin() const { return m_bbox.ymin(); } double ymax() const { return m_bbox.ymax(); } - const Bbox_2& bbox() const { return m_bbox; } - bool strictly_contains_x(double x) const { return xmin() < x && x <= xmax(); } - bool strictly_contains_x(FT x) const { return to_ft(xmin()) < x && x <= to_ft(xmax()); } - - bool strictly_contains_y(double y) const { return ymin() < y && y <= ymax(); } - bool strictly_contains_y(FT y) const { return to_ft(ymin()) < y && y <= to_ft(ymax()); } - - bool strictly_contains(Point_2 pt) const { return strictly_contains_x(pt.x()) && strictly_contains_y(pt.y()); } - bool strictly_contains(Approx_point pt) const { return strictly_contains_x(pt.x()) && strictly_contains_y(pt.y()); } - - bool contains_x(double x) const { return xmin() <= x && x <= xmax(); } - bool contains_x(FT x) const { return to_ft(xmin()) <= x && x <= to_ft(xmax()); } - - bool contains_y(double y) const { return ymin() <= y && y <= ymax(); } - bool contains_y(FT y) const { return to_ft(ymin()) <= y && y <= to_ft(ymax()); } - + bool contains_x(Approx_nt x) const { return xmin() <= x && x <= xmax(); } + bool contains_y(Approx_nt y) const { return ymin() <= y && y <= ymax(); } bool contains(Approx_point pt) const { return contains_x(pt.x()) && contains_y(pt.y()); } - bool contains(Point_2 pt) const { return contains_x(pt.x()) && contains_y(pt.y()); } bool is_on_boundary(Approx_point pt) const { return (pt.x() == xmin() || pt.x() == xmax()) && contains_y(pt.y()) || (pt.y() == ymin() || pt.y() == ymax()) && contains_x(pt.x()); } - bool is_on_boundary(Point_2 pt) const { - return (pt.x() == to_ft(xmin()) || pt.x() == to_ft(xmax())) && contains_y(pt.y()) || - (pt.y() == to_ft(ymin()) || pt.y() == to_ft(ymax())) && contains_x(pt.x()); - } private: const Bbox_2 m_bbox; - const Construct_coordinate to_ft; -}; - -template -class Arr_geom_traits_context_mixin -{ -public: - Arr_geom_traits_context_mixin(const GeomTraits& _traits) - : traits(_traits) - , cst_curve_end(_traits) - , cst_horizontal_segment(_traits) - , cst_vertical_segment(_traits) - , intersect_2(_traits.intersect_2_object()) - , compare_xy_2(_traits.compare_xy_2_object()) - , is_vertical_2(_traits.is_vertical_2_object()) - , approx_pt(_traits) {} - - const GeomTraits& traits; - const Arr_construct_curve_end cst_curve_end; - const Arr_construct_vertical_segment cst_vertical_segment; - const Arr_construct_horizontal_segment cst_horizontal_segment; - const typename Traits_adaptor::Intersect_2 intersect_2; - const typename Traits_adaptor::Compare_xy_2 compare_xy_2; - const typename Traits_adaptor::Is_vertical_2 is_vertical_2; - const Arr_approximate_point_2 approx_pt; }; template -class Arr_render_context : public Arr_cancellable_context_mixin, - public Arr_geom_traits_context_mixin +class Arr_render_context : public Arr_cancellable_context_mixin { using Point_location = Arr_trapezoid_ric_point_location; using Feature_portals_map = typename Arr_portals::Feature_portals_map; using Cancellable_context_mixin = Arr_cancellable_context_mixin; - using Geom_traits_context_mixin = Arr_geom_traits_context_mixin; + using Geom_traits = typename Arrangement::Geometry_traits_2; public: Arr_render_context(const Arrangement& arr, @@ -146,11 +105,11 @@ public: const Feature_portals_map& feature_portals, double approx_error) : Cancellable_context_mixin() - , Geom_traits_context_mixin(*arr.geometry_traits()) - , arr(arr) - , point_location(pl) - , feature_portals(feature_portals) - , approx_error(approx_error) { + , m_arr(arr) + , m_traits(*arr.geometry_traits()) + , m_point_location(pl) + , m_feature_portals(feature_portals) + , m_approx_error(approx_error) { #if defined(CGAL_DRAW_AOS_DEBUG) && defined(CGAL_DRAW_AOS_TRIANGULATOR_DEBUG_FILE_DIR) std::filesystem::path debug_file_dir(CGAL_DRAW_AOS_TRIANGULATOR_DEBUG_FILE_DIR); // clear the index file. @@ -159,10 +118,11 @@ public: } public: - const double approx_error; - const Arrangement& arr; - const Point_location& point_location; - const Feature_portals_map& feature_portals; + const double m_approx_error; + const Arrangement& m_arr; + const Geom_traits& m_traits; + const Point_location& m_point_location; + const Feature_portals_map& m_feature_portals; #if defined(CGAL_DRAW_AOS_DEBUG) std::shared_ptr debug_counter = std::make_shared(0); @@ -174,68 +134,21 @@ class Arr_bounded_render_context : public Arr_render_context, public Arr_bounds_context_mixin { using Geom_traits = typename Arrangement::Geometry_traits_2; - using Approx_point = typename Arr_approximation_geometry_traits::Approx_point; - using Point_2 = typename Traits_adaptor::Point_2; + using Approx_point = typename Geom_traits::Approximate_point_2; using Render_context = Arr_render_context; using Bounds_context_mixin = Arr_bounds_context_mixin; using Approx_cache = Arr_approximation_cache; - constexpr static double ep_base = std::numeric_limits::epsilon(); - public: Arr_bounded_render_context(const Render_context& ctx, const Bbox_2& bbox, Approx_cache& cache) : Render_context(ctx) , Bounds_context_mixin(bbox) - , ep_xmin(std::max(std::abs(ep_base * bbox.xmin()), ep_base)) - , ep_xmax(std::max(std::abs(ep_base * bbox.xmax()), ep_base)) - , ep_ymin(std::max(std::abs(ep_base * bbox.ymin()), ep_base)) - , ep_ymax(std::max(std::abs(ep_base * bbox.ymax()), ep_base)) - , cache(cache) {} - - Approx_point make_on_boundary(const Approx_point& pt) const { - double x = pt.x(), y = pt.y(); - - if(std::abs(x - this->xmin()) < ep_xmin) { - x = this->xmin(); - } else if(std::abs(x - this->xmax()) < ep_xmax) { - x = this->xmax(); - } else if(std::abs(y - this->ymin()) < ep_ymin) { - y = this->ymin(); - } else if(std::abs(y - this->ymax()) < ep_ymax) { - y = this->ymax(); - } else { - // We shall not call this function if not approximated from a boundary point. - CGAL_assertion(false && "Failed to match point to the boundary"); - } - - return Approx_point(x, y); - } + , m_cache(cache) {} public: - Approx_cache& cache; - -private: - // floating point epsilon at boundary coordinates - double ep_xmin, ep_xmax, ep_ymin, ep_ymax; -}; - -template -class Arr_context_delegator -{ -public: - Arr_context_delegator(const Context& ctx) - : m_ctx(ctx) {} - - const Context* operator->() const { return &m_ctx; } - const Context& operator*() const { return m_ctx; } - - // implicit conversion to the context type - operator const Context&() const { return m_ctx; } - -private: - const Context& m_ctx; + Approx_cache& m_cache; }; } // namespace draw_aos } // namespace CGAL -#endif // CGAL_DRAW_AOS_ARR_RENDER_CONTEXT_H \ No newline at end of file +#endif \ No newline at end of file diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_viewer.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_viewer.h index 9c63f02524d..d0a5dadfacf 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_viewer.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/Arr_viewer.h @@ -16,11 +16,11 @@ #ifndef ARR_VIEWER_H #define ARR_VIEWER_H -#include - #include #include -#include +#include +#include +#include #include #include @@ -32,11 +32,14 @@ #include #include -#include -#include +#include +#include +#include #include #include -#include "CGAL/Draw_aos/type_utils.h" +#include +#include +#include #include #include #include @@ -46,18 +49,53 @@ namespace CGAL { namespace draw_aos { +/** + * @brief Viewer for visualizing arrangements on surface. + * + * @tparam Arrangement should be parameterized by a geometry traits that models Approximate_2, or it silently fails. + * @tparam GSOptions Graphics scene options + */ +template +class Arr_viewer; + template -class Arr_viewer : public Qt::Basic_viewer { +class Arr_viewer>> + : public Qt::Basic_viewer +{ +public: + Arr_viewer(QWidget* parent, const Arrangement& arr, GSOptions options, const char* title = "Arrangement Viewer") + : Qt::Basic_viewer(parent, Graphics_scene(), title) { + std::cerr << "Arr_viewer requires geometry traits that supports approximation of Point_2 and " + "X_monotone_curve_2." + << std::endl; + exit(1); + } + + virtual ~Arr_viewer() = default; +}; + +template +class Arr_viewer>> + : public Qt::Basic_viewer +{ using Basic_viewer = Qt::Basic_viewer; using Vertex_const_handle = typename Arrangement::Vertex_const_handle; using Halfedge_const_handle = typename Arrangement::Halfedge_const_handle; using Face_const_handle = typename Arrangement::Face_const_handle; using Feature_portal_map = typename Arr_portals::Feature_portals_map; - using Graphics_scene_options = GSOptions; using Point_location = Arr_trapezoid_ric_point_location; using Geom_traits = typename Arrangement::Geometry_traits_2; + using Approx_point = typename Arr_approximate_traits::Approx_point; private: + static bool contains(const Bbox_2& bbox, const Approx_point& pt) { + return bbox.xmin() <= pt.x() && pt.x() <= bbox.xmax() && bbox.ymin() <= pt.y() && pt.y() <= bbox.ymax(); + } + // Function to check if the camera's state has changed bool is_camera_changed() { QMatrix4x4 proj_mat, mv_mat; @@ -65,16 +103,36 @@ private: this->camera_->computeModelViewMatrix(); this->camera_->getProjectionMatrix(proj_mat.data()); this->camera_->getModelViewMatrix(mv_mat.data()); - if (proj_mat == m_last_proj_matrix && mv_mat == m_last_modelview_matrix) return false; + if(proj_mat == m_last_proj_matrix && mv_mat == m_last_modelview_matrix) return false; m_last_proj_matrix = proj_mat; m_last_modelview_matrix = mv_mat; return true; } + void fill_background(Bbox_2 bbox, CGAL::IO::Color color = CGAL::IO::Color(255, 255, 255)) { + m_gs.face_begin(color); + m_gs.add_point_in_face(Approx_point(bbox.xmin(), bbox.ymin())); + m_gs.add_point_in_face(Approx_point(bbox.xmax(), bbox.ymin())); + m_gs.add_point_in_face(Approx_point(bbox.xmax(), bbox.ymax())); + m_gs.add_point_in_face(Approx_point(bbox.xmin(), bbox.ymax())); + m_gs.face_end(); + } + + Bbox_2 initial_bbox() const { + Bbox_2 bbox; + // Computes a rough bounding box from the vertices. + for(const auto& vh : m_arr.vertex_handles()) { + Approx_point pt = m_arr.geometry_traits()->approximate_2_object()(vh->point()); + bbox += pt.bbox(); + } + // Make sure the bbox is not degenerate. + if(bbox.x_span() == 0) bbox += Bbox_2(bbox.xmin() - 1, bbox.ymin(), bbox.xmax() + 1, bbox.ymax()); + if(bbox.y_span() == 0) bbox += Bbox_2(bbox.xmin(), bbox.ymin() - 1, bbox.xmax(), bbox.ymax() + 1); + return bbox; + } + /** * @brief Computes the bounding box of the view from orthogonal camera. - * - * Currently we reverse the entire projection to find out the view bounding box. * @return Bbox_2 */ Bbox_2 view_bbox_from_camera() const { @@ -82,59 +140,45 @@ private: camera_->getModelViewMatrix(modelview_matrix.data()); camera_->getProjectionMatrix(projection_matrix.data()); QMatrix4x4 inverse_mvp = (projection_matrix * modelview_matrix).inverted(); - // Define 4 corners of the near plane in NDC (-1 to 1 in x and y) - QVector4D clip_space_corners[] = { - QVector4D(-1.0, -1.0, 0.0, 1.0), QVector4D(-1.0, 1.0, 0.0, 1.0), - QVector4D(1.0, -1.0, 0.0, 1.0), QVector4D(1.0, 1.0, 0.0, 1.0) - }; - + std::array clip_space_corners{QVector4D(-1.0, -1.0, 0.0, 1.0), QVector4D(-1.0, 1.0, 0.0, 1.0), + QVector4D(1.0, -1.0, 0.0, 1.0), QVector4D(1.0, 1.0, 0.0, 1.0)}; double xmin = std::numeric_limits::max(); double xmax = std::numeric_limits::lowest(); double ymin = std::numeric_limits::max(); double ymax = std::numeric_limits::lowest(); - - for (const QVector4D& corner : clip_space_corners) { + for(const QVector4D& corner : clip_space_corners) { QVector4D world = inverse_mvp * corner; - if (world.w() != 0.0) world /= world.w(); + if(world.w() != 0.0) world /= world.w(); double x = world.x(); double y = world.y(); - xmin = std::min(xmin, x); xmax = std::max(xmax, x); ymin = std::min(ymin, y); ymax = std::max(ymax, y); } - return Bbox_2(xmin, ymin, xmax, ymax); } - /*! + /** + * @brief Fits the camera to the given bounding box. + * + * Works only for orthogonal camera directed onto a 2D plane by now. + * @param bbox */ + void fit_camera(Bbox_2 bbox) { + // CGAL_assertion(camera_->type() == qglviewer::Camera::ORTHOGRAPHIC); + using Vec = qglviewer::Vec; + camera_->fitBoundingBox(Vec(bbox.xmin(), bbox.ymin(), 0.0), Vec(bbox.xmax(), bbox.ymax(), 0.0)); + } + double get_approx_error(const Bbox_2& bbox) const { - if constexpr(Traits_adaptor::Approximation_sizing_factor == 0.0) - return std::numeric_limits::max(); std::array viewport; camera_->getViewport(viewport.data()); double viewport_width = static_cast(viewport[2]); - double bbox_xspan = bbox.x_span(); - return bbox_xspan / viewport_width * Traits_adaptor::Approximation_sizing_factor; + return bbox.x_span() / viewport_width; } -public: - /*! - */ - Arr_viewer(QWidget* parent, - const Arrangement& arr, - Graphics_scene_options options, - const char* title = "Arrangement Viewer") : - Basic_viewer(parent, m_scene, title), - m_scene_options(options), - m_arr(arr), - m_feature_portals(Arr_portals(*arr.geometry_traits()).create(arr)), - m_pl(arr) - {} - /*! */ void render_arr(const Bbox_2& bbox) { @@ -152,75 +196,84 @@ public: for (const auto& [fh, face_tris] : cache.face_cache()) { const auto& points = face_tris.points; const auto& tris = face_tris.triangles; - bool draw_face = m_scene_options.colored_face(m_arr, fh); - for (const auto& t : tris) { - if (draw_face) m_scene.face_begin(m_scene_options.face_color(m_arr, fh)); - else m_scene.face_begin(); - for (const auto idx : t) m_scene.add_point_in_face(points[idx]); - m_scene.face_end(); + bool draw_face = m_gso.colored_face(m_arr, fh); + for(const auto& t : tris) { + if(draw_face) + m_gs.face_begin(m_gso.face_color(m_arr, fh)); + else + m_gs.face_begin(); + for(const auto idx : t) m_gs.add_point_in_face(points[idx]); + m_gs.face_end(); } } // add edges - for (const auto& [he, polyline] : cache.halfedge_cache()) { - if (polyline.size() < 2) continue; - - bool draw_colored_edge = m_scene_options.colored_edge(m_arr, he); - auto color = draw_colored_edge ? m_scene_options.edge_color(m_arr, he) : CGAL::IO::Color(); - for (size_t i = 0; i < polyline.size() - 1; ++i) { + for(const auto& [he, polyline] : cache.halfedge_cache()) { + if(polyline.size() < 2) continue; + bool draw_colored_edge = m_gso.colored_edge(m_arr, he); + auto color = draw_colored_edge ? m_gso.edge_color(m_arr, he) : CGAL::IO::Color(); + for(size_t i = 0; i < polyline.size() - 1; ++i) { const auto& cur_pt = polyline[i]; const auto& next_pt = polyline[i + 1]; auto mid_pt = CGAL::midpoint(cur_pt, next_pt); - if (mid_pt.x() <= bbox.xmin() || mid_pt.x() > bbox.xmax() || mid_pt.y() <= bbox.ymin() || - mid_pt.y() > bbox.ymax()) - { - continue; - } - if (draw_colored_edge) m_scene.add_segment(cur_pt, next_pt, color); - else m_scene.add_segment(cur_pt, next_pt); + if(!contains(bbox, mid_pt)) continue; + if(draw_colored_edge) + m_gs.add_segment(cur_pt, next_pt, color); + else + m_gs.add_segment(cur_pt, next_pt); } } // add vertices - for (const auto& [vh, pt] : cache.vertex_cache()) { - if (m_scene_options.colored_vertex(m_arr, vh)) m_scene.add_point(pt, m_scene_options.vertex_color(m_arr, vh)); - else m_scene.add_point(pt); + for(const auto& [vh, pt] : cache.vertex_cache()) { + if(!contains(bbox, pt)) continue; + if(m_gso.colored_vertex(m_arr, vh)) + m_gs.add_point(pt, m_gso.vertex_color(m_arr, vh)); + else + m_gs.add_point(pt); } - // If there's nothing to render, we fill the bbox with background color. - // This is to keep the Basic_viewer working in 2D mode. - if (m_scene.empty()) { - m_scene.face_begin(CGAL::IO::Color(255, 255, 255)); // White, by now - using Approx_point = typename Arr_approximation_geometry_traits::Approx_point; - m_scene.add_point_in_face(Approx_point(bbox.xmin(), bbox.ymin())); - m_scene.add_point_in_face(Approx_point(bbox.xmax(), bbox.ymin())); - m_scene.add_point_in_face(Approx_point(bbox.xmax(), bbox.ymax())); - m_scene.add_point_in_face(Approx_point(bbox.xmin(), bbox.ymax())); - m_scene.face_end(); - } + // keep scene non-empty to make sure that the Basic_viewer works in 2D mode for planar arrangements. + if(m_gs.empty()) fill_background(bbox); } /*! */ void rerender(Bbox_2 bbox) { - m_scene.clear(); + m_gs.clear(); render_arr(bbox); Basic_viewer::redraw(); } - /*! - */ +public: + Arr_viewer(QWidget* parent, const Arrangement& arr, GSOptions gso, const char* title = "Arrangement Viewer") + : Basic_viewer(parent, m_gs, title) + , m_gso(gso) + , m_arr(arr) + , m_feature_portals(Arr_portals().create(arr)) + , m_pl(arr) {} + virtual void draw() override { if (is_camera_changed()) { Bbox_2 bbox = view_bbox_from_camera(); + #if defined(CGAL_DRAW_AOS_DEBUG) double dx = (bbox.xmax() - bbox.xmin()) * 0.1; double dy = (bbox.ymax() - bbox.ymin()) * 0.1; bbox = Bbox_2(bbox.xmin() + dx, bbox.ymin() + dy, bbox.xmax() - dx, bbox.ymax() - dy); std::cout << "Camera changed, recomputing arrangement bounding box: " << bbox << std::endl; #endif + rerender(bbox); } + + if(!m_initialized) { + // The initial render must be done with original camera parameters or the width of edges gets exaggerated. + // So we fit the camera after initial render. + fit_camera(initial_bbox()); + m_initialized = true; + } + Basic_viewer::draw(); } @@ -229,13 +282,14 @@ public: virtual ~Arr_viewer() {} private: - Graphics_scene m_scene; - Graphics_scene_options m_scene_options; - Arrangement m_arr; + Graphics_scene m_gs; + GSOptions m_gso; + const Arrangement& m_arr; Point_location m_pl; Feature_portal_map m_feature_portals; QMatrix4x4 m_last_proj_matrix; QMatrix4x4 m_last_modelview_matrix; + bool m_initialized{false}; }; } // namespace draw_aos diff --git a/Arrangement_on_surface_2/include/CGAL/Draw_aos/type_utils.h b/Arrangement_on_surface_2/include/CGAL/Draw_aos/type_utils.h index 379993d24d8..ff1f8fc3717 100644 --- a/Arrangement_on_surface_2/include/CGAL/Draw_aos/type_utils.h +++ b/Arrangement_on_surface_2/include/CGAL/Draw_aos/type_utils.h @@ -1,18 +1,9 @@ #ifndef CGAL_DRAW_AOS_TYPE_UTILS_H #define CGAL_DRAW_AOS_TYPE_UTILS_H +#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include namespace CGAL { namespace draw_aos { @@ -78,211 +69,47 @@ struct has_operator_xcv constexpr bool has_operator_xcv_v = has_operator_xcv::value; +template +constexpr bool has_approximate_traits_v = + has_approximate_2_object_v && has_operator_point_v && + has_operator_xcv_v; + +// Detect whether T is or derives from Arr_geodesic_arc_on_sphere_traits_2<*, *, *> +template +struct is_or_derived_from_agas +{ +private: + template + static std::true_type test(const Arr_geodesic_arc_on_sphere_traits_2*); + + static std::false_type test(...); + +public: + static constexpr bool value = decltype(test(static_cast(nullptr)))::value; +}; + +template +inline constexpr bool is_or_derived_from_agas_v = is_or_derived_from_agas::value; + /*! */ template -struct Traits_adaptor_base { -public: +class Arr_approximate_traits +{ using Geom_traits = GeomTraits; - using Point_2 = typename Geom_traits::Point_2; - using X_monotone_curve_2 = typename Geom_traits::X_monotone_curve_2; - using Intersect_2 = typename Geom_traits::Intersect_2; - using Is_vertical_2 = typename Geom_traits::Is_vertical_2; - using Compare_xy_2 = typename Geom_traits::Compare_xy_2; -}; - -/*! - */ -template -struct Traits_adaptor; - -/*! - */ -template -struct Traits_adaptor> : public Traits_adaptor_base> { -private: - using Geom_traits = Arr_segment_traits_2; public: - constexpr static bool Has_unbounded_curves = false; - constexpr static double Approximation_sizing_factor = 1.0; - using FT = typename Kernel::FT; - using Approximate_2 = typename Geom_traits::Approximate_2; - using Approximate_number_type = typename Geom_traits::Approximate_number_type; - using Approximate_kernel = typename Geom_traits::Approximate_kernel; - using Approximate_point_2 = typename Geom_traits::Approximate_point_2; -}; - -/*! - */ -template -struct Traits_adaptor> : - public Traits_adaptor_base> { -private: - using Geom_traits = Arr_non_caching_segment_traits_2; - -public: - constexpr static bool Has_unbounded_curves = false; - constexpr static double Approximation_sizing_factor = 1.0; - using FT = typename Kernel::FT; - using Approximate_2 = typename Geom_traits::Approximate_2; - using Approximate_number_type = typename Geom_traits::Approximate_number_type; - using Approximate_kernel = typename Geom_traits::Approximate_kernel; - using Approximate_point_2 = typename Geom_traits::Approximate_point_2; -}; - -/*! - */ -template -struct Traits_adaptor> : - public Traits_adaptor_base> { -private: - using Geom_traits = Arr_polyline_traits_2; - using Sub_traits = SegmentTraits; - using Adapted_sub_traits = Traits_adaptor; - -public: - constexpr static bool Has_unbounded_curves = false; - constexpr static double Approximation_sizing_factor = 1.0; - using FT = typename Adapted_sub_traits::FT; - using Approximate_2 = typename Geom_traits::Approximate_2; - using Approximate_number_type = typename Adapted_sub_traits::Approximate_number_type; - using Approximate_kernel = typename Adapted_sub_traits::Approximate_kernel; - using Approximate_point_2 = typename Adapted_sub_traits::Approximate_point_2; -}; - -/*! - */ -template -struct Traits_adaptor> : - public Traits_adaptor_base> { -private: - using Sub_traits = SubcurveTraits; - using Geom_traits = Arr_polycurve_traits_2; - using Adapted_sub_traits = Traits_adaptor; - -public: - constexpr static bool Has_unbounded_curves = false; - constexpr static double Approximation_sizing_factor = 1.0; - using FT = typename Adapted_sub_traits::FT; - using Approximate_2 = typename Geom_traits::Approximate_2; - using Approximate_number_type = typename Adapted_sub_traits::Approximate_number_type; - using Approximate_kernel = typename Adapted_sub_traits::Approximate_kernel; - using Approximate_point_2 = typename Adapted_sub_traits::Approximate_point_2; -}; - -/*! - */ -template -struct Traits_adaptor> : public Traits_adaptor_base> { -private: - using Geom_traits = Arr_segment_traits_2; - -public: - constexpr static bool Has_unbounded_curves = true; - constexpr static double Approximation_sizing_factor = 0.0; - using FT = typename Kernel::FT; - using Approximate_2 = typename Geom_traits::Approximate_2; - using Approximate_number_type = typename Geom_traits::Approximate_number_type; - using Approximate_kernel = typename Geom_traits::Approximate_kernel; - using Approximate_point_2 = typename Geom_traits::Approximate_point_2; -}; - -/*! - */ -template -struct Traits_adaptor> : - public Traits_adaptor_base> { -private: - using Geom_traits = Arr_conic_traits_2; - -public: - constexpr static bool Has_unbounded_curves = false; - constexpr static double Approximation_sizing_factor = 1.0; - using FT = typename AlgKernel::FT; - using Approximate_2 = typename Geom_traits::Approximate_2; - using Approximate_number_type = typename Geom_traits::Approximate_number_type; - using Approximate_kernel = typename Geom_traits::Approximate_kernel; - using Approximate_point_2 = typename Geom_traits::Approximate_point_2; -}; - -/*! - */ -template -struct Traits_adaptor> : - public Traits_adaptor_base> { -private: - using Geom_traits = Arr_circle_segment_traits_2; - using Base = Traits_adaptor_base; - -public: - constexpr static bool Has_unbounded_curves = false; - constexpr static double Approximation_sizing_factor = 0.5; - using FT = typename Base::Point_2::CoordNT; - using Approximate_2 = typename Geom_traits::Approximate_2; - using Approximate_number_type = typename Geom_traits::Approximate_number_type; - using Approximate_kernel = typename Geom_traits::Approximate_kernel; - using Approximate_point_2 = typename Geom_traits::Approximate_point_2; -}; - -/*! - */ -template -struct Traits_adaptor> : - public Traits_adaptor_base> { -private: - using Geom_traits = Arr_rational_function_traits_2; - -public: - constexpr static bool Has_unbounded_curves = true; - constexpr static double Approximation_sizing_factor = 5.0; - using FT = typename Geom_traits::Algebraic_real_1; - using Approximate_2 = typename Geom_traits::Approximate_2; - // Currently, Approximate_number_type is defined as Bound in Arr_rational_function_traits_2, - // And there's no Approximate_kernel defined. - using Approximate_number_type = double; - using Approximate_kernel = Cartesian; - using Approximate_point_2 = typename Approximate_kernel::Point_2; -}; - -/*! - */ -template -class Construct_coordinate { - using FT = typename Traits_adaptor::FT; - -public: - FT operator()(double val) const { return FT(val); } -}; - -/*! - */ -template -class Construct_coordinate> { - using FT = typename Traits_adaptor>::FT; - using Bound = typename Kernel::Bound; - -public: - FT operator()(double val) const { return FT(Bound(val)); } -}; - -/*! - */ -template -class Arr_approximation_geometry_traits { - using Adapted_traits = Traits_adaptor; - -public: - using Approx_point = typename Adapted_traits::Approximate_point_2; - using Approx_nt = typename Adapted_traits::Approximate_number_type; - using Approx_kernel = typename Adapted_traits::Approximate_kernel; + using Approx_point = typename Geom_traits::Approximate_point_2; + using Approx_nt = typename Geom_traits::Approximate_number_type; + using Approx_kernel = typename Geom_traits::Approximate_kernel; using Point_geom = Approx_point; - using Apporx_point_vec = std::vector; - using Polyline_geom = Apporx_point_vec; + using Approx_point_vec = std::vector; + using Polyline_geom = Approx_point_vec; using Triangle = std::array; using Triangle_vec = std::vector; - struct Triangulated_face { - Apporx_point_vec points; + struct Triangulated_face + { + Approx_point_vec points; Triangle_vec triangles; }; }; diff --git a/Arrangement_on_surface_2/include/CGAL/draw_arrangement_2.h b/Arrangement_on_surface_2/include/CGAL/draw_arrangement_2.h index acbcc4415ce..03e747fa924 100644 --- a/Arrangement_on_surface_2/include/CGAL/draw_arrangement_2.h +++ b/Arrangement_on_surface_2/include/CGAL/draw_arrangement_2.h @@ -142,7 +142,7 @@ public: /// Add a face. void add_face(Face_const_handle face) { // std::cout << "add_face()\n"; - for (Inner_ccb_const_iterator it = face->inner_ccbs_begin(); it != face->inner_ccbs_end(); ++it) add_ccb(*it); + for(Inner_ccb_const_iterator it = face->inner_ccbs_begin(); it != face->inner_ccbs_end(); ++it) add_ccb(*it); for (Outer_ccb_const_iterator it = face->outer_ccbs_begin(); it != face->outer_ccbs_end(); ++it) { add_ccb(*it); @@ -156,7 +156,7 @@ public: auto curr = circ; do { auto new_face = curr->twin()->face(); - if (m_visited.find(new_face) != m_visited.end()) continue; + if(m_visited.find(new_face) != m_visited.end()) continue; m_visited[new_face] = true; add_face(new_face); } while(++curr != circ); @@ -190,6 +190,7 @@ public: do { // Skip halfedges that are "antenas": while(curr->face() == curr->twin()->face()) curr = curr->twin()->next(); + while(curr->face() == curr->twin()->face()) curr = curr->twin()->next(); draw_region_impl1(*traits, curr); curr = curr->next(); } while(curr != ext); @@ -243,10 +244,10 @@ public: double error(0.01); // TODO? (this->pixel_ratio()); bool l2r = curr->direction() == ARR_LEFT_TO_RIGHT; approx(curr->curve(), error, std::back_inserter(polyline), l2r); - if (polyline.empty()) return; + if(polyline.empty()) return; auto it = polyline.begin(); auto prev = it++; - for (; it != polyline.end(); prev = it++) m_gs.add_point_in_face(*prev); + for(; it != polyline.end(); prev = it++) m_gs.add_point_in_face(*prev); } /*! Draw an exact curve. @@ -268,8 +269,9 @@ public: /// Add all faces. template - void add_faces(const Traits&) - { for (auto it = m_aos.unbounded_faces_begin(); it != m_aos.unbounded_faces_end(); ++it) add_face(it); } + void add_faces(const Traits&) { + for(auto it = m_aos.unbounded_faces_begin(); it != m_aos.unbounded_faces_end(); ++it) add_face(it); + } /// Compile time dispatching @@ -354,7 +356,8 @@ public: // Find the first halfedge directed from left to right auto curr = circ; - do if (curr->direction() == CGAL::ARR_LEFT_TO_RIGHT) break; + do + if(curr->direction() == CGAL::ARR_LEFT_TO_RIGHT) break; while(++curr != circ); Halfedge_const_handle ext = curr; @@ -362,13 +365,13 @@ public: // such that there is no other halfedge underneath. do { // Discard edges not directed from left to right: - if (curr->direction() != CGAL::ARR_LEFT_TO_RIGHT) continue; + if(curr->direction() != CGAL::ARR_LEFT_TO_RIGHT) continue; auto res = cmp_xy(curr->source()->point(), ext->source()->point()); // Discard the edges inciden to a point strictly larger than the point // incident to the stored extreme halfedge: - if (res == LARGER) continue; + if(res == LARGER) continue; // Store the edge inciden to a point strictly smaller: if (res == SMALLER) { @@ -377,7 +380,7 @@ public: } // The incident points are equal; compare the halfedges themselves: - if (cmp_y(curr->curve(), ext->curve(), curr->source()->point()) == SMALLER) ext = curr; + if(cmp_y(curr->curve(), ext->curve(), curr->source()->point()) == SMALLER) ext = curr; } while(++curr != circ); return ext; @@ -389,10 +392,9 @@ public: // std::cout << "ratio: " << this->pixel_ratio() << std::endl; m_visited.clear(); - if (m_aos.is_empty()) return; + if(m_aos.is_empty()) return; - if (m_gso.are_faces_enabled()) - add_faces(*(this->m_aos.geometry_traits())); + if(m_gso.are_faces_enabled()) add_faces(*(this->m_aos.geometry_traits())); // Add edges that do not separate faces. if (m_gso.are_edges_enabled()) { @@ -432,7 +434,7 @@ public: std::vector polyline; double error(0.01); // TODO? (this->pixel_ratio()); approx(curve, error, std::back_inserter(polyline)); - if (polyline.empty()) return; + if(polyline.empty()) return; auto it = polyline.begin(); auto prev = it++; for (; it != polyline.end(); prev = it++) { @@ -553,10 +555,12 @@ template void draw(const CGAL_ARR_TYPE& aos, const GSOptions& gso, const char* title = "2D Arrangement on Surface Basic Viewer") { + using Arrangement = CGAL_ARR_TYPE; + Qt::init_ogl_context(4, 3); int argc; QApplication app(argc, nullptr); - auto viewer = draw_aos::Arr_viewer(app.activeWindow(), aos, gso, title); + auto viewer = draw_aos::Arr_viewer(app.activeWindow(), aos, gso, title); viewer.show(); app.exec(); } @@ -568,24 +572,31 @@ void draw(const CGAL_ARR_TYPE& aos, const char* title = "2D Arrangement on Surfa using Face_const_handle = typename Arrangement::Face_const_handle; using Vertex_const_handle = typename Arrangement::Vertex_const_handle; using Halfedge_const_handle = typename Arrangement::Halfedge_const_handle; + using GSOptions = + CGAL::Graphics_scene_options; - Qt::init_ogl_context(4, 3); - int argc; - QApplication app(argc, nullptr); - Graphics_scene_options gso; + GSOptions gso; gso.enable_faces(); - gso.enable_edges(); - gso.enable_vertices(); + gso.colored_face = [](const Arrangement&, const Face_const_handle&) { return true; }; gso.face_color = [](const Arrangement&, const Face_const_handle& fh) -> CGAL::IO::Color { CGAL::Random random((size_t(fh.ptr()))); return get_random_color(random); }; - gso.colored_face = [](const Arrangement&, const Face_const_handle&) { return true; }; - gso.vertex_color = [](const Arrangement&, const Vertex_const_handle& vh) -> CGAL::IO::Color { - CGAL::Random random((size_t(vh.ptr()))); - return get_random_color(random); + gso.enable_edges(); + gso.colored_edge = [](const Arrangement&, const Halfedge_const_handle&) { return true; }; + gso.edge_color = [](const Arrangement&, const Halfedge_const_handle& heh) -> CGAL::IO::Color { + return CGAL::IO::Color(0, 0, 0); }; - auto viewer = draw_aos::Arr_viewer(app.activeWindow(), aos, gso, title); + gso.enable_vertices(); + gso.colored_vertex = [](const Arrangement&, const Vertex_const_handle&) { return true; }; + gso.vertex_color = [](const Arrangement&, const Vertex_const_handle& vh) -> CGAL::IO::Color { + return CGAL::IO::Color(255, 0, 0); + }; + + Qt::init_ogl_context(4, 3); + int argc; + QApplication app(argc, nullptr); + auto viewer = draw_aos::Arr_viewer(app.activeWindow(), aos, gso, title); viewer.show(); app.exec(); }