more cleanups, align with code conventions and refactor

1
This commit is contained in:
Shepard Liu 2025-07-23 17:08:17 +08:00
parent 616931594a
commit fb9afae4ac
22 changed files with 728 additions and 1946 deletions

View File

@ -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)

View File

@ -0,0 +1,44 @@
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Arrangement_2.h>
#include <CGAL/draw_arrangement_2.h>
using Kernel = CGAL::Exact_predicates_exact_constructions_kernel;
using Traits = CGAL::Arr_segment_traits_2<Kernel>;
using Point = Traits::Point_2;
using Arrangement = CGAL::Arrangement_2<Traits>;
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<Point, 6> 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<X_monotone_curve, 6> 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);
}

View File

@ -0,0 +1,39 @@
#include <vector>
#include "arr_polylines.h"
int main() {
Traits traits;
auto cst_x_curve = traits.construct_x_monotone_curve_2_object();
std::vector<Point> 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<Traits::Approximate_point_2> 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
}

View File

@ -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);
}

View File

@ -1115,6 +1115,7 @@ public:
using Approximate_number_type = void;
using Approximate_point_2 = void;
using Approximate_2 = void;
using Approximate_kernel = void;
};
template <typename T>
@ -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<Subcurve_traits_2>::Approximate_2;
using Approximate_point_2 =
typename has_approximate_2<Subcurve_traits_2>::Approximate_point_2;
using Approximate_kernel =
typename has_approximate_2<Subcurve_traits_2>::Approximate_kernel;
/*! obtains an Approximate_2 functor object. */
Approximate_2 approximate_2_object_impl(std::false_type) const

View File

@ -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:

View File

@ -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 <typename GeomTraits, bool Has_approximate_2>
class Arr_approximate_point_2_impl;
template <typename GeomTraits>
class Arr_approximate_point_2_impl<GeomTraits, true>
{
using Approx_traits = Arr_approximation_geometry_traits<GeomTraits>;
using Point_2 = typename Traits_adaptor<GeomTraits>::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 <typename Kernel>
class Arr_approximate_point_2_impl<Arr_rational_function_traits_2<Kernel>, true>
{
using Geom_traits = Arr_rational_function_traits_2<Kernel>;
using Approx_traits = Arr_approximation_geometry_traits<Geom_traits>;
using Point_2 = typename Traits_adaptor<Geom_traits>::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 <typename GeomTraits>
class Arr_approximate_point_2_impl<GeomTraits, false>
{
using Approx_traits = Arr_approximation_geometry_traits<GeomTraits>;
using Point_2 = typename Traits_adaptor<GeomTraits>::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 <typename GeomTraits>
using Arr_approximate_point_2 =
internal::Arr_approximate_point_2_impl<GeomTraits,
has_approximate_2_object_v<GeomTraits> &&
has_operator_point_v<GeomTraits, typename GeomTraits::Approximate_2>>;
} // namespace draw_aos
} // namespace CGAL
#endif // CGAL_DRAW_AOS_ARR_APPROXIMATE_POINT_2_H

View File

@ -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 <boost/iterator/function_output_iterator.hpp>
#include <CGAL/Arr_enums.h>
#include <CGAL/number_utils.h>
#include <CGAL/Arr_rational_function_traits_2.h>
#include <CGAL/Draw_aos/Arr_construct_segments.h>
#include <CGAL/Draw_aos/Arr_approximate_point_2.h>
#include <CGAL/Draw_aos/type_utils.h>
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 <typename GeomTraits>
class Arr_approximate_point_2_at_x
{
using Point_2 = typename Traits_adaptor<GeomTraits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<GeomTraits>::X_monotone_curve_2;
using Intersect_2 = typename Traits_adaptor<GeomTraits>::Intersect_2;
using FT = typename Traits_adaptor<GeomTraits>::FT;
using Approx_traits = Arr_approximation_geometry_traits<GeomTraits>;
using Approx_point = typename Approx_traits::Approx_point;
using Is_vertical_2 = typename Traits_adaptor<GeomTraits>::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<Approx_point> 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<Point_2, Multiplicity>;
using Intersect_curve = X_monotone_curve_2;
using Intersect_type = std::variant<Intersect_point, Intersect_curve>;
auto vertical_line = m_cst_vertical_segment(x, m_ymin, m_ymax);
std::optional<Approx_point> pt;
auto func_out_iter = boost::make_function_output_iterator([&pt, this](const Intersect_type& res) {
CGAL_assertion_msg(std::holds_alternative<Intersect_point>(res),
"Unexpected intersection type, expected Intersect_point");
pt = this->m_approx_pt(std::get<Intersect_point>(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<GeomTraits> m_approx_pt;
const Arr_construct_vertical_segment<GeomTraits> 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<double>::lowest();
// constexpr static double m_ymax = std::numeric_limits<double>::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 <typename Kernel>
class Arr_approximate_point_2_at_x<Arr_rational_function_traits_2<Kernel>>
{
using Geom_traits = CGAL::Arr_rational_function_traits_2<Kernel>;
using Point_2 = typename Traits_adaptor<Geom_traits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<Geom_traits>::X_monotone_curve_2;
using FT = typename Traits_adaptor<Geom_traits>::FT;
using Approx_point = typename Arr_approximation_geometry_traits<Geom_traits>::Approx_point;
public:
Arr_approximate_point_2_at_x(const Geom_traits&)
: to_ft() {}
std::optional<Approx_point> 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<double>::lowest());
FT xmax = curve.right_parameter_space_in_x() == ARR_INTERIOR ? curve.right_x()
: to_ft(std::numeric_limits<double>::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<Geom_traits> to_ft;
};
} // namespace draw_aos
} // namespace CGAL
#endif // CGAL_DRAW_AOS_ARR_COMPUTE_Y_AT_X_H

View File

@ -4,18 +4,25 @@
#include <boost/range/iterator_range.hpp>
#include "CGAL/Arr_enums.h"
#include "CGAL/unordered_flat_map.h"
#include "CGAL/Draw_aos/type_utils.h"
#include <CGAL/Arr_enums.h>
#include <CGAL/unordered_flat_map.h>
#include <CGAL/Draw_aos/type_utils.h>
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 <typename Arrangement>
class Arr_approximation_cache
{
using Geom_traits = typename Arrangement::Geometry_traits_2;
using Approx_traits = Arr_approximation_geometry_traits<Geom_traits>;
using Approx_traits = Arr_approximate_traits<Geom_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<Vertex_cache_obj&, bool> 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
#endif

View File

@ -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 <algorithm>
#include <array>
#include <cstddef>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <optional>
#include <vector>
#include <CGAL/Arr_enums.h>
#include <CGAL/Draw_aos/Arr_approximate_point_2_at_x.h>
#include <CGAL/Draw_aos/Arr_render_context.h>
#include <CGAL/Draw_aos/type_utils.h>
namespace CGAL {
namespace draw_aos {
template <typename Arrangement, bool Has_approximate_2_on_curve>
class Arr_bounded_approximate_curve_2_impl;
template <typename Arrangement>
class Arr_bounded_approximate_curve_2_impl<Arrangement, false>
{
using Halfedge_const_handle = typename Arrangement::Halfedge_const_iterator;
using Geom_traits = typename Arrangement::Geometry_traits_2;
using Approx_traits = Arr_approximation_geometry_traits<Geom_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<Geom_traits>;
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<Geom_traits>;
using Bounded_render_context = Arr_bounded_render_context<Arrangement>;
using Intersections_vector = std::vector<Point_2>;
struct Execution_context : public Arr_context_delegator<Bounded_render_context>
{
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<Bounded_render_context>(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<Geom_traits>(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<Geom_traits>(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_point> 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<Approx_point> min_end, max_end;
double txmin, txmax, tymin, tymax;
std::back_insert_iterator<Polyline_geom> out_it;
const Construct_coordinate<Geom_traits> to_ft;
private:
const Approx_point_2_at_x& m_approx_pt_at_x;
};
private:
static std::vector<Point_2> 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<Geom_traits>& cst_curve_end) {
using Intersect_point = std::pair<Point_2, typename Geom_traits::Multiplicity>;
using Intersect_curve = X_monotone_curve_2;
using Intersect_type = std::variant<Intersect_point, Intersect_curve>;
std::vector<Point_2> 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<Intersect_point>(&res)) {
*out_it++ = pt->first;
return;
}
if(auto* cv = std::get_if<Intersect_curve>(&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<Approx_point> 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.
/**
* @brief Functor to approximate an x-monotone curve within an bounding box.
*
* @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
* 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.
*/
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<Approx_point> 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<Geom_traits> 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 <typename Arrangement>
class Arr_bounded_approximate_curve_2_impl<Arrangement, true>
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<Geom_traits>;
using Approx_traits = Arr_approximate_traits<Geom_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<Geom_traits>;
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<Geom_traits>;
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<Arrangement>;
using Boundary_lines = std::array<Approx_line_2, 4>;
private:
struct Execution_context : public Arr_context_delegator<Bounded_render_context>
struct Context : public Bounded_render_context
{
Execution_context(const Bounded_render_context& ctx,
Context(const Bounded_render_context& ctx,
const X_monotone_curve_2& curve,
const Approximate_2& approx_2,
Polyline_geom& polyline)
: Arr_context_delegator<Bounded_render_context>(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())))
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<Polyline_geom> 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<std::function<void(Approx_point)>> 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<Approx_point> m_last_pt;
boost::function_output_iterator<std::function<void(Approx_point)>> 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]() {
static Approx_point trace_boundary_inter(const Context& ctx, Approx_point pt, Side_of_boundary side) {
Approx_point inter = std::get<Approx_point>(
*CGAL::intersection(Approx_line_2(*ctx.m_last_pt, pt), ctx.m_boundary_lines[static_cast<std::size_t>(side)]));
// Prevent floating point errors.
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;
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:
return Approx_line_2();
CGAL_assertion(false && "Unexpected side of boundary.");
return Approx_point();
}
}
CGAL_assertion(false && "Unexpected side of boundary");
}();
std::optional<std::variant<Approx_point, Approx_line_2>> res =
CGAL::intersection(Approx_line_2(last_pt, pt), boundary_line);
Approx_point inter = ctx->make_on_boundary(std::get<Approx_point>(*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<std::size_t>(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<Approx_point> 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;
}),
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 <typename Arrangement>
using Arr_bounded_approximate_curve_2 = Arr_bounded_approximate_curve_2_impl<
Arrangement,
has_approximate_2_object_v<typename Arrangement::Geometry_traits_2> &&
has_operator_xcv_v<typename Arrangement::Geometry_traits_2,
typename Arrangement::Geometry_traits_2::Approximate_2>>;
} // namespace draw_aos
} // namespace CGAL
#endif // CGAL_DRAW_AOS_ARR_BOUNDED_APPROXIMATE_CURVE_2_H
#endif

View File

@ -3,8 +3,10 @@
#include <cstddef>
#include <functional>
#include <limits>
#include <optional>
#include <type_traits>
#include <utility>
#include <variant>
#include <algorithm>
@ -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 <typename GeomTraits>
class Patch_boundary
{
using Approx_point = typename Arr_approximation_geometry_traits<GeomTraits>::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<Side_of_boundary>((static_cast<int>(side) + 3) % 4);
}
Side_of_boundary next_side(Side_of_boundary side) const {
CGAL_assertion(side != Side_of_boundary::None);
return static_cast<Side_of_boundary>((static_cast<int>(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 <typename OutputIterator>
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<int>(to_side) - static_cast<int>(from_side) + 4) % 4;
for(int i = 0; i < num_corners_to_patch; ++i) {
Side_of_boundary side = static_cast<Side_of_boundary>((static_cast<int>(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 <typename OutputIterator>
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 <typename GeomTraits, typename OutputIterator>
class Colinear_simplifier
{
using Geom_traits = GeomTraits;
using Approx_point = typename Arr_approximation_geometry_traits<Geom_traits>::Approx_point;
using Approx_point = typename Arr_approximate_traits<Geom_traits>::Approx_point;
public:
using Insert_iterator = boost::function_output_iterator<std::function<void(Approx_point)>>;
@ -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 <typename Arrangement>
class Arr_bounded_approximate_face_2
{
using Geom_traits = typename Arrangement::Geometry_traits_2;
using Approx_traits = Arr_approximation_geometry_traits<Geom_traits>;
using Approx_traits = Arr_approximate_traits<Geom_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<Arrangement>::Feature_portals_map;
using Portal_vector = typename Arr_portals<Arrangement>::Portal_vector;
using Portal = typename Arr_portals<Arrangement>::Portal;
using Point_or_portal = std::variant<Approx_point, Portal>;
using FT = typename Traits_adaptor<Geom_traits>::FT;
using Portal_exit = typename Arr_portals<Arrangement>::Portal_exit;
using Portal_exit_vector = typename Arr_portals<Arrangement>::Portal_exit_vector;
using Bounded_approximate_point_2 = Arr_bounded_approximate_point_2<Arrangement>;
using Bounded_approximate_curve_2 = Arr_bounded_approximate_curve_2<Arrangement>;
using Bounded_render_context = Arr_bounded_render_context<Arrangement>;
using Triangulator = Arr_bounded_face_triangulator<Arrangement>;
using Patch = Patch_boundary<Geom_traits>;
using Simplifier = Colinear_simplifier<Geom_traits, typename Triangulator::Insert_iterator>;
struct Left_to_right_tag
@ -226,7 +125,7 @@ class Arr_bounded_approximate_face_2
{};
private:
class Execution_context : public Arr_context_delegator<Bounded_render_context>
class Context : public Bounded_render_context
{
private:
using Output_iterator =
@ -235,52 +134,35 @@ private:
public:
using Insert_iterator = boost::function_output_iterator<std::function<void(Approx_point pt)>>;
Execution_context(const Bounded_render_context& ctx,
const Patch& patch,
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)
: Arr_context_delegator<Bounded_render_context>(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;
: 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<Approx_point> 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<Approx_point> 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;
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;
}
ctx.bounded_approx_pt(vh);
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 <typename DirectionTag>
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<DirectionTag, Left_to_right_tag>;
using Approx_point_it = std::conditional_t<Is_left_to_right, typename Polyline_geom::const_iterator,
typename Polyline_geom::const_reverse_iterator>;
using Portals_it = std::conditional_t<Is_left_to_right, typename Portal_vector::const_iterator,
typename Portal_vector::const_reverse_iterator>;
using Portals_it = std::conditional_t<Is_left_to_right, typename Portal_exit_vector::const_iterator,
typename Portal_exit_vector::const_reverse_iterator>;
using Point_or_portal = std::variant<Approx_point, Vertex_const_handle>;
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<Portal>(&point_or_portal)) {
traverse_portal(ctx, *portal);
return;
// A variant of two pointers algorithm, but std::merge() can't fit in our purpose.
std::optional<Approx_point> 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;
}
*ctx.out_it++ = std::get<Approx_point>(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());
});
// Compute the portal entry point.
std::optional<std::variant<Approx_point, Approx_line_2>> 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<Approx_point>(*res));
traverse_portal(ctx, std::get<Approx_point>(*res), exit);
}
static void approximate_inner_ccb(Execution_context& ctx, const Vertex_const_handle& vh) {
*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(Context& ctx, const Vertex_const_handle& vh) {
if(vh->is_isolated()) {
approximate_vertex(ctx, vh);
return;
}
approximate_ccb<Inner_ccb_tag>(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 <typename CcbTag, bool Unbounded = false>
static void approximate_ccb(Execution_context& ctx, Ccb_halfedge_const_circulator start_circ) {
constexpr bool Is_outer_ccb = std::is_same_v<CcbTag, Outer_ccb_tag>;
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<Outer_ccb_tag, true>(ctx, fh->outer_ccb());
} else {
approximate_ccb<Outer_ccb_tag, false>(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
#endif

View File

@ -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 <utility>
#include <CGAL/Draw_aos/type_utils.h>
#include <CGAL/Draw_aos/Arr_render_context.h>
#include <CGAL/Draw_aos/Arr_approximate_point_2.h>
namespace CGAL {
namespace draw_aos {
@ -15,14 +12,15 @@ template <typename Arrangement>
class Arr_bounded_approximate_point_2
{
using Geom_traits = typename Arrangement::Geometry_traits_2;
using Point_2 = typename Traits_adaptor<Geom_traits>::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<Geom_traits>::Point_geom;
using Point_geom = typename Arr_approximate_traits<Geom_traits>::Point_geom;
using Bounded_render_context = Arr_bounded_render_context<Arrangement>;
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<const Point_geom&, bool>
* @return const Point_geom&
*/
std::pair<const Point_geom&, bool> 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

View File

@ -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 <typename Arrangement>
class Arr_bounded_face_triangulator
{
using Geom_traits = typename Arrangement::Geometry_traits_2;
using Approx_traits = Arr_approximation_geometry_traits<Geom_traits>;
using Approx_traits = Arr_approximate_traits<Geom_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 <typename T>
friend void debug_print(const Arr_bounded_face_triangulator<T>& 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<std::size_t>(0);
auto indexes_end = boost::make_counting_iterator<std::size_t>(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<std::size_t>(0);
auto indexes_end = boost::make_counting_iterator<std::size_t>(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<KPoint_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<typename Ct::Face_handle, bool> in_domain_map;
in_domain_map.reserve(m_ct.number_of_faces());
boost::associative_property_map<decltype(in_domain_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<Arrangement>& 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
#endif

View File

@ -1,239 +1,62 @@
#ifndef CGAL_DRAW_AOS_ARR_BOUNDED_RENDERER_H
#define CGAL_DRAW_AOS_ARR_BOUNDED_RENDERER_H
#include <vector>
#include <CGAL/Arr_trapezoid_ric_point_location.h>
#include <CGAL/Bbox_2.h>
#include <CGAL/IO/Color.h>
#include <CGAL/Draw_aos/Arr_approximation_cache.h>
#include <CGAL/Draw_aos/Arr_bounded_approximate_curve_2.h>
#include <CGAL/Draw_aos/Arr_bounded_approximate_face_2.h>
#include <CGAL/Draw_aos/Arr_render_context.h>
#include <CGAL/Draw_aos/type_utils.h>
#include <CGAL/Draw_aos/Arr_portals.h>
#include <CGAL/Draw_aos/type_utils.h>
namespace CGAL {
namespace draw_aos {
/**
* @brief Render arrangement on surface within a bounding box.
*
* @note The class is not thread-safe.
*/
template <typename Arrangement>
class Arr_bounded_renderer
{
using Color = IO::Color;
using Geom_traits = typename Arrangement::Geometry_traits_2;
using FT = typename Traits_adaptor<Geom_traits>::FT;
using Point_2 = typename Traits_adaptor<Geom_traits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<Geom_traits>::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<Arrangement>;
using Feature_const = std::variant<Vertex_const_handle, Halfedge_const_handle, Face_const_handle>;
using Feature_const_vector = std::vector<Feature_const>;
using Feature_const_vector_inserter = std::back_insert_iterator<Feature_const_vector>;
using Approx_traits = Arr_approximation_geometry_traits<Geom_traits>;
using Point_geom = typename Approx_traits::Point_geom;
using Polyline_geom = typename Approx_traits::Polyline_geom;
using Feature_portal_map = typename Arr_portals<Arrangement>::Feature_portals_map;
using Bounded_render_context = Arr_bounded_render_context<Arrangement>;
using Render_context = Arr_render_context<Arrangement>;
using Bounded_render_context = Arr_bounded_render_context<Arrangement>;
using Bounded_approx_point_2 = Arr_bounded_approximate_point_2<Arrangement>;
using Bounded_approx_curve_2 = Arr_bounded_approximate_curve_2<Arrangement>;
using Bounded_approx_face_2 = Arr_bounded_approximate_face_2<Arrangement>;
using Approx_cache = Arr_approximation_cache<Arrangement>;
// QFlags implement this pattern better, but we try not to reply on Qt classes.
template <typename E>
class Arr_flags
{
public:
Arr_flags(const E& flags)
: m_flags(static_cast<int>(flags)) {}
Arr_flags(std::initializer_list<E> flags)
: m_flags(0) {
for(const auto& flag : flags) {
m_flags |= static_cast<int>(flag);
}
}
bool is_set(E flag) const { return (m_flags & static_cast<int>(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<Bounded_render_context>
{
Execution_context(const Bounded_render_context& ctx)
: Arr_context_delegator<Bounded_render_context>(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<Feature_type> feats) {
CGAL_assertion(side != Side_of_boundary::None);
using Feature = std::variant<Vertex_handle, Halfedge_handle, Face_handle>;
using Feature_vector = std::vector<Feature>;
auto func_out_iter = boost::make_function_output_iterator([&ctx, feats, side](const Feature& feature) {
if(auto* vh = std::get_if<Vertex_handle>(&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<Halfedge_handle>(&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<Face_handle>(&feature)) {
if(!feats.is_set(Feature_type::Face)) {
return;
}
discover_faces(ctx, *fh);
}
});
using Zone_visitor = Arr_compute_zone_visitor<Arrangement, decltype(func_out_iter)>;
using Zone_2 = Arrangement_zone_2<Arrangement, Zone_visitor>;
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<Arrangement&>(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<Geom_traits> to_ft;
const Bbox_2 m_bbox;
};
} // namespace draw_aos
} // namespace CGAL
#endif // CGAL_DRAW_AOS_ARR_BOUNDED_RENDERER_H
#endif

View File

@ -1,105 +0,0 @@
#ifndef CGAL_ARR_CONSTRUCT_CURVE_END_H
#define CGAL_ARR_CONSTRUCT_CURVE_END_H
#include <optional>
#include <CGAL/Arr_enums.h>
#include <CGAL/Arr_has.h>
#include <CGAL/Draw_aos/type_utils.h>
namespace CGAL {
namespace draw_aos {
namespace internal {
template <typename GeomTraits>
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 <typename GeomTraits, bool Has_parameter_space_in_x>
class Arr_construct_curve_end_impl;
template <typename GeomTraits>
class Arr_construct_curve_end_impl<GeomTraits, true> : public Arr_construct_curve_end_base<GeomTraits>
{
using Approx_geom_traits = Arr_approximation_geometry_traits<GeomTraits>;
using Point_2 = typename Traits_adaptor<GeomTraits>::Point_2;
using X_monotone_curve = typename Traits_adaptor<GeomTraits>::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<GeomTraits>(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<Point_2>
*/
std::optional<Point_2> 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 <typename GeomTraits>
class Arr_construct_curve_end_impl<GeomTraits, false> : public Arr_construct_curve_end_base<GeomTraits>
{
using Approx_geom_traits = Arr_approximation_geometry_traits<GeomTraits>;
using Point_2 = typename Traits_adaptor<GeomTraits>::Point_2;
using X_monotone_curve = typename Traits_adaptor<GeomTraits>::X_monotone_curve_2;
public:
Arr_construct_curve_end_impl(const GeomTraits& traits)
: Arr_construct_curve_end_base<GeomTraits>(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<Point_2>
*/
std::optional<Point_2> 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 <typename GeomTraits>
using Arr_construct_curve_end =
internal::Arr_construct_curve_end_impl<GeomTraits,
Traits_adaptor<GeomTraits>::Has_unbounded_curves &&
has_parameter_space_in_x_2<GeomTraits>::value>;
} // namespace draw_aos
} // namespace CGAL
#endif // CGAL_ARR_CONSTRUCT_CURVE_END_H

View File

@ -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 <CGAL/Arr_circle_segment_traits_2.h>
#include <CGAL/number_utils.h>
#include <CGAL/Polynomial_traits_d.h>
#include <CGAL/Draw_aos/type_utils.h>
namespace CGAL {
namespace draw_aos {
template <typename GeomTraits, bool HasConstructXMonotoneCurve2>
class Arr_construct_segment_impl;
// Default implementation for traits that models Construct_x_monotone_curve_2
template <typename GeomTraits>
class Arr_construct_segment_impl<GeomTraits, true>
{
using Point_2 = typename Traits_adaptor<GeomTraits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<GeomTraits>::X_monotone_curve_2;
using Construct_x_monotone_curve_2 = typename GeomTraits::Construct_x_monotone_curve_2;
using FT = typename Traits_adaptor<GeomTraits>::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 <typename Kernel>
class Arr_construct_segment_impl<Arr_circle_segment_traits_2<Kernel>, false>
{
using Geom_traits = Arr_circle_segment_traits_2<Kernel>;
using Point_2 = typename Traits_adaptor<Geom_traits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<Geom_traits>::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<Geom_traits>::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 <typename GeomTraits>
class Arr_construct_segment_impl<GeomTraits, false>
{
using Geom_traits = GeomTraits;
using X_monotone_curve_2 = typename Traits_adaptor<Geom_traits>::X_monotone_curve_2;
using FT = typename Traits_adaptor<Geom_traits>::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 <typename GeomTraits>
using Arr_construct_segment =
Arr_construct_segment_impl<GeomTraits, has_construct_x_monotone_curve_2<GeomTraits>::value>;
template <typename GeomTraits>
class Arr_construct_vertical_segment
{
using Point_2 = typename Traits_adaptor<GeomTraits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<GeomTraits>::X_monotone_curve_2;
using FT = typename Traits_adaptor<GeomTraits>::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<GeomTraits> m_cst_seg;
};
template <typename GeomTraits>
class Arr_construct_horizontal_segment
{
using Point_2 = typename Traits_adaptor<GeomTraits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<GeomTraits>::X_monotone_curve_2;
using FT = typename Traits_adaptor<GeomTraits>::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<GeomTraits> m_cst_seg;
};
// Arr_construct_vertical_segment Specialization for Arr_rational_function_traits_2
template <typename Kernel>
class Arr_construct_vertical_segment<Arr_rational_function_traits_2<Kernel>>
{
using Geom_traits = Arr_rational_function_traits_2<Kernel>;
using Point_2 = typename Traits_adaptor<Geom_traits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<Geom_traits>::X_monotone_curve_2;
using Construct_x_monotone_curve_2 = typename Geom_traits::Construct_x_monotone_curve_2;
using FT = typename Traits_adaptor<Geom_traits>::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 <typename Kernel>
class Arr_construct_horizontal_segment<Arr_rational_function_traits_2<Kernel>>
{
using Geom_traits = Arr_rational_function_traits_2<Kernel>;
using Point_2 = typename Traits_adaptor<Geom_traits>::Point_2;
using X_monotone_curve_2 = typename Traits_adaptor<Geom_traits>::X_monotone_curve_2;
using FT = typename Traits_adaptor<Geom_traits>::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

View File

@ -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);
}

View File

@ -7,9 +7,6 @@
#include <CGAL/Arr_vertical_decomposition_2.h>
#include <CGAL/Object.h>
#include <CGAL/unordered_flat_map.h>
#include <CGAL/Draw_aos/Arr_approximate_point_2_at_x.h>
#include <CGAL/Draw_aos/Arr_approximate_point_2.h>
#include <CGAL/Draw_aos/Arr_construct_segments.h>
#include <CGAL/Draw_aos/Arr_graph_conn.h>
#include <CGAL/Draw_aos/type_utils.h>
@ -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<Geom_traits>::Point_2;
using Approx_point = typename Arr_approximation_geometry_traits<Geom_traits>::Approx_point;
using X_monotone_curve_2 = typename Traits_adaptor<Geom_traits>::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<Geom_traits>::Approx_point;
using Feature_const = std::variant<Vertex_const_handle, Halfedge_const_handle, Face_const_handle>;
public:
// Pair composed of a portal entry point and the portaled vertex on the inner ccb.
using Portal = std::pair<std::optional<Approx_point>, Vertex_const_handle>;
using Portal_vector = std::vector<Portal>;
using Portal_exit = Vertex_const_handle;
using Portal_exit_vector = std::vector<Portal_exit>;
// Map from a feature to its portals sorted by the x coordinate of the virtual vertical segments.
using Feature_portals_map = unordered_flat_map<Feature_const, Portal_vector>;
using Feature_portals_map = unordered_flat_map<Feature_const, Portal_exit_vector>;
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<Vertex_const_handle>();
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<Portal>{});
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<Portal>{});
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<Geom_traits> m_approx_pt_at_x;
const Arr_approximate_point_2<Geom_traits> m_approx_pt;
};
} // namespace draw_aos
} // namespace CGAL
#endif // CGAL_DRAW_AOS_ARR_CREATE_PORTALS_H
#endif

View File

@ -1,9 +1,7 @@
#ifndef CGAL_DRAW_AOS_ARR_RENDER_CONTEXT_H
#define CGAL_DRAW_AOS_ARR_RENDER_CONTEXT_H
#include <cstdlib>
#include <limits>
#include <memory>
#include <algorithm>
#include <atomic>
#include <chrono>
@ -11,10 +9,7 @@
#include <CGAL/Arr_point_location_result.h>
#include <CGAL/Arr_trapezoid_ric_point_location.h>
#include <CGAL/Arrangement_2.h>
#include <CGAL/Draw_aos/Arr_approximate_point_2.h>
#include <CGAL/Draw_aos/Arr_approximation_cache.h>
#include <CGAL/Draw_aos/Arr_construct_curve_end.h>
#include <CGAL/Draw_aos/Arr_construct_segments.h>
#include <CGAL/Draw_aos/Arr_portals.h>
#include <CGAL/Draw_aos/type_utils.h>
@ -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<std::atomic<bool>>(false)) {}
, m_cancelled(std::make_shared<std::atomic<bool>>(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<std::atomic<bool>> m_done;
std::shared_ptr<std::atomic<bool>> 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 <typename GeomTraits>
class Arr_bounds_context_mixin
{
using Approx_point = typename Arr_approximation_geometry_traits<GeomTraits>::Approx_point;
using Point_2 = typename Traits_adaptor<GeomTraits>::Point_2;
using FT = typename Traits_adaptor<GeomTraits>::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<GeomTraits> to_ft;
};
template <typename GeomTraits>
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<GeomTraits> cst_curve_end;
const Arr_construct_vertical_segment<GeomTraits> cst_vertical_segment;
const Arr_construct_horizontal_segment<GeomTraits> cst_horizontal_segment;
const typename Traits_adaptor<GeomTraits>::Intersect_2 intersect_2;
const typename Traits_adaptor<GeomTraits>::Compare_xy_2 compare_xy_2;
const typename Traits_adaptor<GeomTraits>::Is_vertical_2 is_vertical_2;
const Arr_approximate_point_2<GeomTraits> approx_pt;
};
template <typename Arrangement>
class Arr_render_context : public Arr_cancellable_context_mixin,
public Arr_geom_traits_context_mixin<typename Arrangement::Geometry_traits_2>
class Arr_render_context : public Arr_cancellable_context_mixin
{
using Point_location = Arr_trapezoid_ric_point_location<Arrangement>;
using Feature_portals_map = typename Arr_portals<Arrangement>::Feature_portals_map;
using Cancellable_context_mixin = Arr_cancellable_context_mixin;
using Geom_traits_context_mixin = Arr_geom_traits_context_mixin<typename Arrangement::Geometry_traits_2>;
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<int> debug_counter = std::make_shared<int>(0);
@ -174,68 +134,21 @@ class Arr_bounded_render_context : public Arr_render_context<Arrangement>,
public Arr_bounds_context_mixin<typename Arrangement::Geometry_traits_2>
{
using Geom_traits = typename Arrangement::Geometry_traits_2;
using Approx_point = typename Arr_approximation_geometry_traits<Geom_traits>::Approx_point;
using Point_2 = typename Traits_adaptor<Geom_traits>::Point_2;
using Approx_point = typename Geom_traits::Approximate_point_2;
using Render_context = Arr_render_context<Arrangement>;
using Bounds_context_mixin = Arr_bounds_context_mixin<Geom_traits>;
using Approx_cache = Arr_approximation_cache<Arrangement>;
constexpr static double ep_base = std::numeric_limits<double>::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 <typename Context>
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
#endif

View File

@ -16,11 +16,11 @@
#ifndef ARR_VIEWER_H
#define ARR_VIEWER_H
#include <CGAL/license/Arrangement_on_surface_2.h>
#include <array>
#include <cstddef>
#include <limits>
#include <cstdlib>
#include <iostream>
#include <type_traits>
#include <boost/iterator/function_output_iterator.hpp>
#include <boost/range/iterator_range.hpp>
@ -32,11 +32,14 @@
#include <QtGui/QMouseEvent>
#include <QtGui/QKeyEvent>
#include <CGAL/Arr_linear_traits_2.h>
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Qt/Basic_viewer.h>
#include <CGAL/Qt/camera.h>
#include <CGAL/IO/Color.h>
#include <CGAL/Basic_viewer.h>
#include <CGAL/Bbox_2.h>
#include "CGAL/Draw_aos/type_utils.h"
#include <CGAL/Graphics_scene.h>
#include <CGAL/Graphics_scene_options.h>
#include <CGAL/Draw_aos/type_utils.h>
#include <CGAL/Draw_aos/Arr_bounded_renderer.h>
#include <CGAL/Draw_aos/Arr_render_context.h>
#include <CGAL/Graphics_scene.h>
@ -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 <typename Arrangement, typename GSOptions, typename = void>
class Arr_viewer;
template <typename Arrangement, typename GSOptions>
class Arr_viewer : public Qt::Basic_viewer {
class Arr_viewer<Arrangement,
GSOptions,
std::enable_if_t<!has_approximate_traits_v<typename Arrangement::Geometry_traits_2>>>
: 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 <typename Arrangement, typename GSOptions>
class Arr_viewer<Arrangement,
GSOptions,
std::enable_if_t<has_approximate_traits_v<typename Arrangement::Geometry_traits_2>>>
: 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<Arrangement>::Feature_portals_map;
using Graphics_scene_options = GSOptions;
using Point_location = Arr_trapezoid_ric_point_location<Arrangement>;
using Geom_traits = typename Arrangement::Geometry_traits_2;
using Approx_point = typename Arr_approximate_traits<Geom_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<QVector4D, 4> 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<double>::max();
double xmax = std::numeric_limits<double>::lowest();
double ymin = std::numeric_limits<double>::max();
double ymax = std::numeric_limits<double>::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<Geom_traits>::Approximation_sizing_factor == 0.0)
return std::numeric_limits<double>::max();
std::array<GLint, 4> viewport;
camera_->getViewport(viewport.data());
double viewport_width = static_cast<double>(viewport[2]);
double bbox_xspan = bbox.x_span();
return bbox_xspan / viewport_width * Traits_adaptor<Geom_traits>::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<Arrangement>(*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<Geom_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<Arrangement>().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

View File

@ -1,18 +1,9 @@
#ifndef CGAL_DRAW_AOS_TYPE_UTILS_H
#define CGAL_DRAW_AOS_TYPE_UTILS_H
#include <vector>
#include <type_traits>
#include <CGAL/Arr_non_caching_segment_traits_2.h>
#include <CGAL/Arr_polycurve_traits_2.h>
#include <CGAL/Arr_polyline_traits_2.h>
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Arr_linear_traits_2.h>
#include <CGAL/Arr_circle_segment_traits_2.h>
#include <CGAL/Arr_conic_traits_2.h>
#include <CGAL/Arr_geodesic_arc_on_sphere_traits_2.h>
#include <CGAL/Arr_rational_function_traits_2.h>
#include <CGAL/Arr_algebraic_segment_traits_2.h>
#include <CGAL/Arr_circular_line_arc_traits_2.h>
namespace CGAL {
namespace draw_aos {
@ -78,211 +69,47 @@ struct has_operator_xcv<T,
template <typename T, typename A>
constexpr bool has_operator_xcv_v = has_operator_xcv<T, A, void*>::value;
template <typename T>
constexpr bool has_approximate_traits_v =
has_approximate_2_object_v<T> && has_operator_point_v<T, typename T::Approximate_2> &&
has_operator_xcv_v<T, typename T::Approximate_2>;
// Detect whether T is or derives from Arr_geodesic_arc_on_sphere_traits_2<*, *, *>
template <typename T>
struct is_or_derived_from_agas
{
private:
template <typename Kernel_, int AtanX, int AtanY>
static std::true_type test(const Arr_geodesic_arc_on_sphere_traits_2<Kernel_, AtanX, AtanY>*);
static std::false_type test(...);
public:
static constexpr bool value = decltype(test(static_cast<const T*>(nullptr)))::value;
};
template <typename T>
inline constexpr bool is_or_derived_from_agas_v = is_or_derived_from_agas<T>::value;
/*!
*/
template <typename GeomTraits>
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 <typename GeomTraits>
struct Traits_adaptor;
/*!
*/
template <typename Kernel>
struct Traits_adaptor<Arr_segment_traits_2<Kernel>> : public Traits_adaptor_base<Arr_segment_traits_2<Kernel>> {
private:
using Geom_traits = Arr_segment_traits_2<Kernel>;
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 <typename Kernel>
struct Traits_adaptor<Arr_non_caching_segment_traits_2<Kernel>> :
public Traits_adaptor_base<Arr_non_caching_segment_traits_2<Kernel>> {
private:
using Geom_traits = Arr_non_caching_segment_traits_2<Kernel>;
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 <typename SegmentTraits>
struct Traits_adaptor<Arr_polyline_traits_2<SegmentTraits>> :
public Traits_adaptor_base<Arr_polyline_traits_2<SegmentTraits>> {
private:
using Geom_traits = Arr_polyline_traits_2<SegmentTraits>;
using Sub_traits = SegmentTraits;
using Adapted_sub_traits = Traits_adaptor<Sub_traits>;
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 <typename SubcurveTraits>
struct Traits_adaptor<Arr_polycurve_traits_2<SubcurveTraits>> :
public Traits_adaptor_base<Arr_polycurve_traits_2<SubcurveTraits>> {
private:
using Sub_traits = SubcurveTraits;
using Geom_traits = Arr_polycurve_traits_2<Sub_traits>;
using Adapted_sub_traits = Traits_adaptor<Sub_traits>;
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 <typename Kernel>
struct Traits_adaptor<Arr_linear_traits_2<Kernel>> : public Traits_adaptor_base<Arr_linear_traits_2<Kernel>> {
private:
using Geom_traits = Arr_segment_traits_2<Kernel>;
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 <typename RatKernel, typename AlgKernel, typename NtTraits>
struct Traits_adaptor<Arr_conic_traits_2<RatKernel, AlgKernel, NtTraits>> :
public Traits_adaptor_base<Arr_conic_traits_2<RatKernel, AlgKernel, NtTraits>> {
private:
using Geom_traits = Arr_conic_traits_2<RatKernel, AlgKernel, NtTraits>;
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 <typename Kernel>
struct Traits_adaptor<Arr_circle_segment_traits_2<Kernel>> :
public Traits_adaptor_base<Arr_circle_segment_traits_2<Kernel>> {
private:
using Geom_traits = Arr_circle_segment_traits_2<Kernel>;
using Base = Traits_adaptor_base<Geom_traits>;
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 <typename Kernel>
struct Traits_adaptor<Arr_rational_function_traits_2<Kernel>> :
public Traits_adaptor_base<Arr_rational_function_traits_2<Kernel>> {
private:
using Geom_traits = Arr_rational_function_traits_2<Kernel>;
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<double>;
using Approximate_point_2 = typename Approximate_kernel::Point_2;
};
/*!
*/
template <typename GeomTraits>
class Construct_coordinate {
using FT = typename Traits_adaptor<GeomTraits>::FT;
public:
FT operator()(double val) const { return FT(val); }
};
/*!
*/
template <typename Kernel>
class Construct_coordinate<Arr_rational_function_traits_2<Kernel>> {
using FT = typename Traits_adaptor<Arr_rational_function_traits_2<Kernel>>::FT;
using Bound = typename Kernel::Bound;
public:
FT operator()(double val) const { return FT(Bound(val)); }
};
/*!
*/
template <typename GeomTraits>
class Arr_approximation_geometry_traits {
using Adapted_traits = Traits_adaptor<GeomTraits>;
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<Point_geom>;
using Polyline_geom = Apporx_point_vec;
using Approx_point_vec = std::vector<Point_geom>;
using Polyline_geom = Approx_point_vec;
using Triangle = std::array<std::size_t, 3>;
using Triangle_vec = std::vector<Triangle>;
struct Triangulated_face {
Apporx_point_vec points;
struct Triangulated_face
{
Approx_point_vec points;
Triangle_vec triangles;
};
};

View File

@ -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 <typename Traits>
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<typename Gt::Approximate_point_2> 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 <typename GeometryTraits_2, typename TopologyTraits, class GSOptions>
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<Arrangement, GSOptions>(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<Arrangement, Vertex_const_handle, Halfedge_const_handle, Face_const_handle>;
Qt::init_ogl_context(4, 3);
int argc;
QApplication app(argc, nullptr);
Graphics_scene_options<Arrangement, Vertex_const_handle, Halfedge_const_handle, Face_const_handle> 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<Arrangement, GSOptions>(app.activeWindow(), aos, gso, title);
viewer.show();
app.exec();
}