mirror of https://github.com/CGAL/cgal
1142 lines
43 KiB
C++
1142 lines
43 KiB
C++
// Copyright (c) 2023 GeometryFactory
|
|
// All rights reserved.
|
|
//
|
|
// This file is part of CGAL (www.cgal.org).
|
|
//
|
|
// $URL$
|
|
// $Id$
|
|
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
|
|
//
|
|
// Author(s) : Mael Rouxel-Labbé
|
|
//
|
|
|
|
#ifndef CGAL_SLS_EXTRUSION_EXTRUDE_SKELETON_H
|
|
#define CGAL_SLS_EXTRUSION_EXTRUDE_SKELETON_H
|
|
|
|
#include <CGAL/license/Straight_skeleton_extrusion_2.h>
|
|
|
|
#ifdef CGAL_SLS_DEBUG_DRAW
|
|
#include <CGAL/draw_straight_skeleton_2.h>
|
|
#include <CGAL/draw_polygon_2.h>
|
|
#include <CGAL/draw_polygon_with_holes_2.h>
|
|
#include <CGAL/draw_triangulation_2.h>
|
|
#endif
|
|
|
|
#include <CGAL/create_weighted_straight_skeleton_2.h>
|
|
#include <CGAL/create_weighted_straight_skeleton_from_polygon_with_holes_2.h>
|
|
#include <CGAL/create_weighted_offset_polygons_2.h>
|
|
#include <CGAL/create_weighted_offset_polygons_from_polygon_with_holes_2.h>
|
|
#include <CGAL/Straight_skeleton_2/Straight_skeleton_aux.h>
|
|
|
|
#include <CGAL/General_polygon_with_holes_2.h>
|
|
|
|
#include <CGAL/Polygon_mesh_processing/border.h>
|
|
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
|
|
#include <CGAL/Polygon_mesh_processing/repair_polygon_soup.h>
|
|
#include <CGAL/Polygon_mesh_processing/stitch_borders.h>
|
|
|
|
#ifdef CGAL_SLS_OUTPUT_FILES
|
|
#include <CGAL/IO/polygon_soup_io.h>
|
|
#endif
|
|
|
|
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
|
|
#include <CGAL/Projection_traits_3.h>
|
|
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
|
|
#include <CGAL/mark_domain_in_triangulation.h>
|
|
|
|
#include <CGAL/Cartesian/function_objects.h>
|
|
#include <CGAL/enum.h>
|
|
#include <CGAL/IO/helpers.h>
|
|
#include <CGAL/Kernel_traits.h>
|
|
#include <CGAL/Polygon_2.h>
|
|
|
|
#include <boost/algorithm/clamp.hpp>
|
|
#include <boost/range/value_type.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <optional>
|
|
#include <memory>
|
|
|
|
namespace CGAL {
|
|
namespace Straight_skeleton_extrusion {
|
|
namespace internal {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Below is to handle vertical slabs.
|
|
|
|
enum class Slope
|
|
{
|
|
UNKNOWN = 0,
|
|
INWARD,
|
|
OUTWARD,
|
|
VERTICAL
|
|
};
|
|
|
|
template <typename FT>
|
|
inline constexpr FT default_extrusion_height()
|
|
{
|
|
return (std::numeric_limits<double>::max)();
|
|
}
|
|
|
|
// @todo Maybe this postprocessing is not really necessary? Do users really care if the point
|
|
// is not perfectly above the input contour edge (it generally cannot be anyway if the kernel
|
|
// is not exact except for some specific cases)?
|
|
#define CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
|
|
// The purpose of this visitor is to snap back almost-vertical (see preprocessing_weights()) edges
|
|
// to actual vertical slabs.
|
|
template <typename HDS, typename GeomTraits>
|
|
typename GeomTraits::Point_2
|
|
snap_point_to_contour_halfedge_plane(const typename GeomTraits::Point_2& op,
|
|
typename HDS::Halfedge_const_handle ch)
|
|
{
|
|
using FT = typename GeomTraits::FT;
|
|
using Segment_2 = typename GeomTraits::Segment_2;
|
|
using Line_2 = typename GeomTraits::Line_2;
|
|
|
|
using Vertex_const_handle = typename HDS::Vertex_const_handle;
|
|
|
|
const Vertex_const_handle sv = ch->opposite()->vertex();
|
|
const Vertex_const_handle tv = ch->vertex();
|
|
|
|
if(sv->point().x() == tv->point().x())
|
|
{
|
|
// vertical edge
|
|
// std::cout << "vertical edge, snapping " << op << " to " << sv->point().x() << " " << op.y() << std::endl;
|
|
return { sv->point().x(), op.y() };
|
|
}
|
|
else if(sv->point().y() == tv->point().y())
|
|
{
|
|
// horizontal edge
|
|
// std::cout << "horizontal edge, snapping " << op << " to " << op.x() << " " << sv->point().y() << std::endl;
|
|
return { op.x(), sv->point().y() };
|
|
}
|
|
else
|
|
{
|
|
// Project orthogonally onto the halfedge
|
|
// @todo should the projection be along the direction of the other offset edge sharing this point?
|
|
Segment_2 s { sv->point(), tv->point() };
|
|
std::optional<Line_2> line = CGAL_SS_i::compute_normalized_line_coeffC2(s);
|
|
CGAL_assertion(bool(line)); // otherwise the skeleton would have failed already
|
|
|
|
FT px, py;
|
|
CGAL::line_project_pointC2(line->a(),line->b(),line->c(), op.x(),op.y(), px,py);
|
|
// std::cout << "snapping " << op << " to " << px << " " << py << std::endl;
|
|
return { px, py };
|
|
}
|
|
}
|
|
|
|
template <typename HDS, typename GeomTraits>
|
|
void snap_skeleton_vertex(typename HDS::Halfedge_const_handle hds_h,
|
|
typename HDS::Halfedge_const_handle contour_h,
|
|
std::map<typename GeomTraits::Point_2,
|
|
typename GeomTraits::Point_2>& snapped_positions)
|
|
{
|
|
typename HDS::Vertex_const_handle hds_tv = hds_h->vertex();
|
|
|
|
// this re-applies snapping towards contour_h even if the point was already snapped towards another contour
|
|
auto insert_result = snapped_positions.emplace(hds_tv->point(), hds_tv->point());
|
|
insert_result.first->second = snap_point_to_contour_halfedge_plane<HDS, GeomTraits>(insert_result.first->second, contour_h);
|
|
|
|
// std::cout << "snap_skeleton_vertex(V" << hds_tv->id() << " pt: " << hds_h->vertex()->point() << ")"
|
|
// << " to " << insert_result.first->second << std::endl;
|
|
}
|
|
|
|
template <typename GeomTraits, typename PointRange>
|
|
void apply_snapping(PointRange& points,
|
|
const std::map<typename GeomTraits::Point_2,
|
|
typename GeomTraits::Point_2>& snapped_positions)
|
|
{
|
|
using Point_3 = typename GeomTraits::Point_3;
|
|
|
|
for(Point_3& p3 : points)
|
|
{
|
|
auto it = snapped_positions.find({ p3.x(), p3.y() });
|
|
if(it != snapped_positions.end())
|
|
p3 = Point_3{it->second.x(), it->second.y(), p3.z()};
|
|
}
|
|
}
|
|
#endif // CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
|
|
template <typename StraightSkeleton_2,
|
|
typename OffsetBuilderTraits,
|
|
typename GeomTraits>
|
|
class Skeleton_offset_correspondence_builder_visitor
|
|
: public CGAL::Default_polygon_offset_builder_2_visitor<OffsetBuilderTraits, StraightSkeleton_2>
|
|
{
|
|
using Base = CGAL::Default_polygon_offset_builder_2_visitor<OffsetBuilderTraits, StraightSkeleton_2>;
|
|
|
|
using FT = typename GeomTraits::FT;
|
|
using Point_2 = typename GeomTraits::Point_2;
|
|
|
|
using SS_Halfedge_const_handle = typename StraightSkeleton_2::Halfedge_const_handle;
|
|
|
|
using HDS = typename StraightSkeleton_2::Base;
|
|
using HDS_Halfedge_const_handle = typename HDS::Halfedge_const_handle;
|
|
|
|
public:
|
|
Skeleton_offset_correspondence_builder_visitor(const StraightSkeleton_2& ss,
|
|
std::unordered_map<HDS_Halfedge_const_handle, Point_2>& offset_points
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
, const FT vertical_weight
|
|
, std::map<Point_2, Point_2>& snapped_positions
|
|
#endif
|
|
)
|
|
: m_ss(ss)
|
|
, m_offset_points(offset_points)
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
, m_vertical_weight(vertical_weight)
|
|
, m_snapped_positions(snapped_positions)
|
|
#endif
|
|
{ }
|
|
|
|
public:
|
|
void on_offset_contour_started() const
|
|
{
|
|
// std::cout << "~~ new contour ~~" << std::endl;
|
|
}
|
|
|
|
// can't modify the position yet because we need arrange_polygons() to still work properly
|
|
//
|
|
// @fixme on paper one could create a polygon thin-enough w.r.t. the max weight value
|
|
// such thatthere is a skeleton vertex that wants to be snapped to two different sides...
|
|
void on_offset_point(const Point_2& op,
|
|
SS_Halfedge_const_handle hook) const
|
|
{
|
|
CGAL_precondition(hook->is_bisector());
|
|
|
|
HDS_Halfedge_const_handle canonical_hook = (hook < hook->opposite()) ? hook : hook->opposite();
|
|
m_offset_points[canonical_hook] = op;
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
CGAL_precondition(m_snapped_positions.count(op) == 0);
|
|
|
|
SS_Halfedge_const_handle contour_h1 = hook->defining_contour_edge();
|
|
CGAL_assertion(contour_h1->opposite()->is_border());
|
|
SS_Halfedge_const_handle contour_h2 = hook->opposite()->defining_contour_edge();
|
|
CGAL_assertion(contour_h2->opposite()->is_border());
|
|
|
|
const bool is_h1_vertical = (contour_h1->weight() == m_vertical_weight);
|
|
const bool is_h2_vertical = (contour_h2->weight() == m_vertical_weight);
|
|
|
|
// if both are vertical, it's the common vertex (which has to exist)
|
|
if(is_h1_vertical && is_h2_vertical)
|
|
{
|
|
CGAL_assertion(contour_h1->vertex() == contour_h2->opposite()->vertex() ||
|
|
contour_h2->vertex() == contour_h1->opposite()->vertex());
|
|
if(contour_h1->vertex() == contour_h2->opposite()->vertex())
|
|
m_snapped_positions[op] = contour_h1->vertex()->point();
|
|
else
|
|
m_snapped_positions[op] = contour_h2->vertex()->point();
|
|
}
|
|
else if(is_h1_vertical)
|
|
{
|
|
m_snapped_positions[op] = snap_point_to_contour_halfedge_plane<HDS, GeomTraits>(op, contour_h1);
|
|
}
|
|
else if(is_h2_vertical)
|
|
{
|
|
m_snapped_positions[op] = snap_point_to_contour_halfedge_plane<HDS, GeomTraits>(op, contour_h2);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
const StraightSkeleton_2& m_ss;
|
|
std::unordered_map<HDS_Halfedge_const_handle, Point_2>& m_offset_points;
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
const FT m_vertical_weight;
|
|
std::map<Point_2, Point_2>& m_snapped_positions;
|
|
#endif
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename GeomTraits>
|
|
class Extrusion_builder
|
|
{
|
|
using Geom_traits = GeomTraits;
|
|
|
|
using FT = typename Geom_traits::FT;
|
|
using Point_2 = typename Geom_traits::Point_2;
|
|
using Segment_2 = typename Geom_traits::Segment_2;
|
|
using Line_2 = typename Geom_traits::Line_2;
|
|
using Point_3 = typename Geom_traits::Point_3;
|
|
using Vector_3 = typename Geom_traits::Vector_3;
|
|
|
|
using Polygon_2 = CGAL::Polygon_2<Geom_traits>;
|
|
using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2<Geom_traits>;
|
|
|
|
using Offset_polygons = std::vector<std::shared_ptr<Polygon_2> >;
|
|
using Offset_polygons_with_holes = std::vector<std::shared_ptr<Polygon_with_holes_2> >;
|
|
|
|
using Straight_skeleton_2 = CGAL::Straight_skeleton_2<Geom_traits>;
|
|
using Straight_skeleton_2_ptr = std::shared_ptr<Straight_skeleton_2>;
|
|
|
|
using SS_Vertex_const_handle = typename Straight_skeleton_2::Vertex_const_handle;
|
|
using SS_Halfedge_const_handle = typename Straight_skeleton_2::Halfedge_const_handle;
|
|
|
|
using HDS = typename Straight_skeleton_2::Base;
|
|
using HDS_Vertex_const_handle = typename HDS::Vertex_const_handle;
|
|
using HDS_Halfedge_const_handle = typename HDS::Halfedge_const_handle;
|
|
using HDS_Face_handle = typename HDS::Face_handle;
|
|
|
|
// Standard CDT2 for the horizontal (z constant) faces
|
|
using Vb = CGAL::Triangulation_vertex_base_with_info_2<std::size_t, Geom_traits>;
|
|
using Vbb = CGAL::Triangulation_vertex_base_2<Geom_traits, Vb>;
|
|
using Fb = CGAL::Constrained_triangulation_face_base_2<Geom_traits>;
|
|
using TDS = CGAL::Triangulation_data_structure_2<Vb,Fb>;
|
|
using Itag = CGAL::No_constraint_intersection_requiring_constructions_tag;
|
|
using CDT = CGAL::Constrained_Delaunay_triangulation_2<Geom_traits, TDS, Itag>;
|
|
using CDT_Vertex_handle = typename CDT::Vertex_handle;
|
|
using CDT_Face_handle = typename CDT::Face_handle;
|
|
|
|
// Projection CDT2 for the lateral faces
|
|
using PK = CGAL::Projection_traits_3<Geom_traits>;
|
|
using PVbb = CGAL::Triangulation_vertex_base_with_info_2<std::size_t, PK>;
|
|
using PVb = CGAL::Triangulation_vertex_base_2<PK, PVbb>;
|
|
using PFb = CGAL::Constrained_triangulation_face_base_2<PK>;
|
|
using PTDS = CGAL::Triangulation_data_structure_2<PVb,PFb>;
|
|
using PCDT = CGAL::Constrained_Delaunay_triangulation_2<PK, PTDS, Itag>;
|
|
using PCDT_Vertex_handle = typename PCDT::Vertex_handle;
|
|
using PCDT_Face_handle = typename PCDT::Face_handle;
|
|
|
|
using Offset_builder_traits = CGAL::Polygon_offset_builder_traits_2<Geom_traits>;
|
|
using Visitor = Skeleton_offset_correspondence_builder_visitor<Straight_skeleton_2, Offset_builder_traits, Geom_traits>;
|
|
using Offset_builder = CGAL::Polygon_offset_builder_2<Straight_skeleton_2,
|
|
Offset_builder_traits,
|
|
Polygon_2,
|
|
Visitor>;
|
|
|
|
private:
|
|
Geom_traits m_gt;
|
|
|
|
public:
|
|
Extrusion_builder(const Geom_traits& gt) : m_gt(gt) { }
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename PolygonWithHoles,
|
|
typename PointRange, typename FaceRange>
|
|
void construct_horizontal_faces(const PolygonWithHoles& p,
|
|
const FT altitude,
|
|
PointRange& points,
|
|
FaceRange& faces,
|
|
const bool invert_faces = false)
|
|
{
|
|
#ifdef CGAL_SLS_DEBUG_DRAW
|
|
CGAL::draw(p);
|
|
#endif
|
|
|
|
CDT cdt;
|
|
|
|
try
|
|
{
|
|
cdt.insert_constraint(p.outer_boundary().begin(), p.outer_boundary().end(), true /*close*/);
|
|
for(auto h_it=p.holes_begin(); h_it!=p.holes_end(); ++h_it)
|
|
cdt.insert_constraint(h_it->begin(), h_it->end(), true /*close*/);
|
|
}
|
|
catch(const typename CDT::Intersection_of_constraints_exception&)
|
|
{
|
|
std::cerr << "Warning: Failed to triangulate horizontal face" << std::endl;
|
|
return;
|
|
}
|
|
|
|
std::size_t id = points.size(); // point ID offset (previous faces inserted their points)
|
|
for(CDT_Vertex_handle vh : cdt.finite_vertex_handles())
|
|
{
|
|
points.emplace_back(cdt.point(vh).x(), cdt.point(vh).y(), altitude);
|
|
vh->info() = id++;
|
|
}
|
|
|
|
#ifdef CGAL_SLS_DEBUG_DRAW
|
|
// CGAL::draw(cdt);
|
|
#endif
|
|
|
|
std::unordered_map<CDT_Face_handle, bool> in_domain_map;
|
|
boost::associative_property_map< std::unordered_map<CDT_Face_handle, bool> > in_domain(in_domain_map);
|
|
|
|
CGAL::mark_domain_in_triangulation(cdt, in_domain);
|
|
|
|
for(CDT_Face_handle f : cdt.finite_face_handles())
|
|
{
|
|
if(!get(in_domain, f))
|
|
continue;
|
|
|
|
// invert faces for the z=0 plane (bottom face)
|
|
if(invert_faces)
|
|
faces.push_back({f->vertex(0)->info(), f->vertex(2)->info(), f->vertex(1)->info()});
|
|
else
|
|
faces.push_back({f->vertex(0)->info(), f->vertex(1)->info(), f->vertex(2)->info()});
|
|
}
|
|
}
|
|
|
|
template <typename PointRange, typename FaceRange>
|
|
void construct_horizontal_faces(const Offset_polygons_with_holes& p_ptrs,
|
|
const FT altitude,
|
|
PointRange& points,
|
|
FaceRange& faces)
|
|
{
|
|
for(const auto& p_ptr : p_ptrs)
|
|
construct_horizontal_faces(*p_ptr, altitude, points, faces);
|
|
}
|
|
|
|
template <typename SLSFacePoints, typename PointRange, typename FaceRange>
|
|
void triangulate_skeleton_face(SLSFacePoints& face_points,
|
|
const bool invert_faces,
|
|
PointRange& points,
|
|
FaceRange& faces)
|
|
{
|
|
CGAL_precondition(face_points.size() >= 3);
|
|
|
|
// shift once to ensure that face_points[0] and face_points[1] are at z=0 and thus the normal is correct
|
|
std::rotate(face_points.rbegin(), face_points.rbegin() + 1, face_points.rend());
|
|
CGAL_assertion(face_points[0][2] == 0 && face_points[1][2] == 0);
|
|
|
|
const Vector_3 n = CGAL::cross_product(face_points[1] - face_points[0], face_points[2] - face_points[0]);
|
|
PK traits(n);
|
|
PCDT pcdt(traits);
|
|
|
|
try
|
|
{
|
|
pcdt.insert_constraint(face_points.begin(), face_points.end(), true /*close*/);
|
|
}
|
|
catch(const typename PCDT::Intersection_of_constraints_exception&)
|
|
{
|
|
std::cerr << "Warning: Failed to triangulate skeleton face" << std::endl;
|
|
return;
|
|
}
|
|
|
|
std::size_t id = points.size(); // point ID offset (previous faces inserted their points);
|
|
for(PCDT_Vertex_handle vh : pcdt.finite_vertex_handles())
|
|
{
|
|
points.push_back(pcdt.point(vh));
|
|
vh->info() = id++;
|
|
}
|
|
|
|
#ifdef CGAL_SLS_DEBUG_DRAW
|
|
// CGAL::draw(pcdt);
|
|
#endif
|
|
|
|
std::unordered_map<PCDT_Face_handle, bool> in_domain_map;
|
|
boost::associative_property_map< std::unordered_map<PCDT_Face_handle, bool> > in_domain(in_domain_map);
|
|
|
|
CGAL::mark_domain_in_triangulation(pcdt, in_domain);
|
|
|
|
for(PCDT_Face_handle f : pcdt.finite_face_handles())
|
|
{
|
|
if(!get(in_domain, f))
|
|
continue;
|
|
|
|
// invert faces for exterior skeletons
|
|
if(invert_faces)
|
|
faces.push_back({f->vertex(0)->info(), f->vertex(2)->info(), f->vertex(1)->info()});
|
|
else
|
|
faces.push_back({f->vertex(0)->info(), f->vertex(1)->info(), f->vertex(2)->info()});
|
|
}
|
|
}
|
|
|
|
// This version is for default height, so just gather all the full faces
|
|
void construct_lateral_faces(const Straight_skeleton_2& ss,
|
|
std::vector<Point_3>& points,
|
|
std::vector<std::vector<std::size_t> >& faces,
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
const FT& vertical_weight,
|
|
std::map<Point_2, Point_2>& snapped_positions,
|
|
#endif
|
|
const bool ignore_frame_faces = false,
|
|
const bool invert_faces = false)
|
|
{
|
|
const HDS& hds = static_cast<const HDS&>(ss);
|
|
|
|
std::size_t fc = 0;
|
|
|
|
for(const HDS_Face_handle hds_f : CGAL::faces(hds))
|
|
{
|
|
std::vector<Point_3> face_points;
|
|
|
|
// If they exist (exterior skeleton), the first four faces of the SLS correspond
|
|
// to the outer frame, and should be ignored.
|
|
if(ignore_frame_faces && fc++ < 4)
|
|
continue;
|
|
|
|
HDS_Halfedge_const_handle hds_h = hds_f->halfedge(), done = hds_h;
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
HDS_Halfedge_const_handle contour_h = hds_h->defining_contour_edge();
|
|
CGAL_assertion(hds_h == contour_h);
|
|
const bool is_vertical = (contour_h->weight() == vertical_weight);
|
|
#endif
|
|
|
|
do
|
|
{
|
|
HDS_Vertex_const_handle hds_tv = hds_h->vertex();
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
// this computes the snapped position but does not change the geometry of the skeleton
|
|
if(is_vertical && !hds_tv->is_contour())
|
|
snap_skeleton_vertex<HDS, Geom_traits>(hds_h, contour_h, snapped_positions);
|
|
#endif
|
|
|
|
face_points.emplace_back(hds_tv->point().x(), hds_tv->point().y(), hds_tv->time());
|
|
|
|
hds_h = hds_h->next();
|
|
}
|
|
while(hds_h != done);
|
|
|
|
if(face_points.size() < 3)
|
|
{
|
|
std::cerr << "Warning: sm_vs has size 1 or 2: offset crossing face at a single point?" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
triangulate_skeleton_face(face_points, invert_faces, points, faces);
|
|
}
|
|
}
|
|
|
|
void construct_lateral_faces(const Straight_skeleton_2& ss,
|
|
const Offset_builder& offset_builder,
|
|
const FT height,
|
|
std::vector<Point_3>& points,
|
|
std::vector<std::vector<std::size_t> >& faces,
|
|
const std::unordered_map<HDS_Halfedge_const_handle, Point_2>& offset_points,
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
const FT& vertical_weight,
|
|
std::map<Point_2, Point_2>& snapped_positions,
|
|
#endif
|
|
const bool ignore_frame_faces = false,
|
|
const bool invert_faces = false)
|
|
{
|
|
CGAL_precondition(height != default_extrusion_height<FT>());
|
|
|
|
const bool extrude_upwards = is_positive(height);
|
|
const FT abs_height = CGAL::abs(height);
|
|
|
|
const HDS& hds = static_cast<const HDS&>(ss);
|
|
|
|
std::size_t fc = 0;
|
|
for(const HDS_Face_handle hds_f : CGAL::faces(hds))
|
|
{
|
|
// If they exist (exterior skeleton), the first four faces of the SLS correspond
|
|
// to the outer frame, and should be ignored.
|
|
if(ignore_frame_faces && fc++ < 4)
|
|
continue;
|
|
|
|
std::vector<Point_3> face_points;
|
|
|
|
HDS_Halfedge_const_handle hds_h = hds_f->halfedge(), done = hds_h;
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
HDS_Halfedge_const_handle contour_h = hds_h->defining_contour_edge();
|
|
CGAL_assertion(hds_h == contour_h);
|
|
const bool is_vertical = (contour_h->weight() == vertical_weight);
|
|
#endif
|
|
|
|
do
|
|
{
|
|
HDS_Vertex_const_handle hds_sv = hds_h->opposite()->vertex();
|
|
HDS_Vertex_const_handle hds_tv = hds_h->vertex();
|
|
|
|
// Compare_offset_against_event_time compares offset to node->time(),
|
|
// so when the offset is greater or equal than the node->time(), the node is the face
|
|
auto compare_time_to_offset = [&](HDS_Vertex_const_handle node) -> CGAL::Comparison_result
|
|
{
|
|
if(node->is_contour())
|
|
return CGAL::LARGER; // offset > 0 and contour nodes' time is 0
|
|
else
|
|
return offset_builder.Compare_offset_against_event_time(abs_height, node);
|
|
};
|
|
|
|
const CGAL::Comparison_result sc = compare_time_to_offset(hds_sv);
|
|
const CGAL::Comparison_result tc = compare_time_to_offset(hds_tv);
|
|
|
|
// if the offset is crossing at the source, it will be added when seen as a target
|
|
// from the previous halfedge
|
|
|
|
if(sc != tc && sc != CGAL::EQUAL && tc != CGAL::EQUAL)
|
|
{
|
|
// std::cout << "sc != tc" << std::endl;
|
|
CGAL_assertion(sc != CGAL::EQUAL && tc != CGAL::EQUAL);
|
|
|
|
HDS_Halfedge_const_handle hds_off_h = hds_h;
|
|
if(hds_h->slope() == CGAL::NEGATIVE) // ensure same geometric point on both sides
|
|
hds_off_h = hds_off_h->opposite();
|
|
|
|
// The offset point must already been computed in the offset builder visitor
|
|
auto off_p = offset_points.find(hds_off_h);
|
|
CGAL_assertion(off_p != offset_points.end());
|
|
|
|
face_points.emplace_back(off_p->second.x(), off_p->second.y(), height);
|
|
}
|
|
|
|
if(tc != CGAL::SMALLER)
|
|
{
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
if(is_vertical && !hds_tv->is_contour())
|
|
snap_skeleton_vertex<HDS, Geom_traits>(hds_h, contour_h, snapped_positions);
|
|
#endif
|
|
|
|
const Point_2& off_p = hds_tv->point();
|
|
// ->time() could be an approximation of the true time. If we are here, the target's time
|
|
// is smaller than the height, but we still sanitize it in case of numerical errors in ->time().
|
|
const FT time = boost::algorithm::clamp(hds_tv->time(), FT(0), abs_height);
|
|
face_points.emplace_back(off_p.x(), off_p.y(), extrude_upwards ? time : - time);
|
|
}
|
|
|
|
hds_h = hds_h->next();
|
|
}
|
|
while(hds_h != done);
|
|
|
|
if(face_points.size() < 3)
|
|
{
|
|
std::cerr << "Warning: sm_vs has size 1 or 2: offset crossing face at a single point?" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
triangulate_skeleton_face(face_points, invert_faces, points, faces);
|
|
}
|
|
}
|
|
|
|
public:
|
|
// this is roughly "CGAL::create_interior_weighted_skeleton_and_offset_polygons_with_holes_2()",
|
|
// but we want to know the intermediate straight skeleton to build the lateral faces of the 3D mesh
|
|
template <typename PolygonWithHoles,
|
|
typename PointRange, typename FaceRange>
|
|
bool inward_construction(const PolygonWithHoles& pwh,
|
|
const std::vector<std::vector<FT> >& speeds,
|
|
const FT vertical_weight,
|
|
const FT height,
|
|
PointRange& points,
|
|
FaceRange& faces)
|
|
{
|
|
CGAL_precondition(!is_zero(height));
|
|
|
|
const FT abs_height = abs(height);
|
|
|
|
// bottom face (z=0)
|
|
construct_horizontal_faces(pwh, 0 /*height*/, points, faces, true /*invert faces*/);
|
|
|
|
// Avoid recomputing offset points multiple times
|
|
std::unordered_map<HDS_Halfedge_const_handle, Point_2> offset_points;
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
// This is to deal with vertical slabs: we temporarily give a non-vertical slope to be able
|
|
// to construct the SLS, and we then snap back the position to the vertical planes.
|
|
// Note that points in non-vertical faces are also changed a bit
|
|
std::map<Point_2, Point_2> snapped_positions;
|
|
#endif
|
|
|
|
Straight_skeleton_2_ptr ss_ptr;
|
|
|
|
bool is_default_extrusion_height = (height == default_extrusion_height<FT>());
|
|
|
|
// @partial_wsls_pwh interior SLS of weighted polygons with holes can have skeleton faces with holes
|
|
// The current postprocessing is in the function EnforceSimpleConnectedness, but it has not yet
|
|
// been made compatible with partial skeletons.
|
|
if(is_default_extrusion_height || pwh.number_of_holes() != 0)
|
|
{
|
|
ss_ptr = CGAL::create_interior_weighted_straight_skeleton_2(
|
|
CGAL_SS_i::vertices_begin(pwh.outer_boundary()),
|
|
CGAL_SS_i::vertices_end(pwh.outer_boundary()),
|
|
pwh.holes_begin(), pwh.holes_end(),
|
|
std::begin(speeds[0]), std::end(speeds[0]),
|
|
std::next(std::begin(speeds)), std::end(speeds),
|
|
m_gt);
|
|
}
|
|
else
|
|
{
|
|
ss_ptr = CGAL_SS_i::create_partial_interior_weighted_straight_skeleton_2(
|
|
abs_height,
|
|
CGAL_SS_i::vertices_begin(pwh.outer_boundary()),
|
|
CGAL_SS_i::vertices_end(pwh.outer_boundary()),
|
|
pwh.holes_begin(), pwh.holes_end(),
|
|
std::begin(speeds[0]), std::end(speeds[0]),
|
|
std::next(std::begin(speeds)), std::end(speeds),
|
|
m_gt);
|
|
}
|
|
|
|
if(!ss_ptr)
|
|
{
|
|
std::cerr << "Error: encountered an error during skeleton construction" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
#ifdef CGAL_SLS_DEBUG_DRAW
|
|
// print_straight_skeleton(*ss_ptr);
|
|
CGAL::draw(*ss_ptr);
|
|
#endif
|
|
|
|
if(is_default_extrusion_height)
|
|
{
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
construct_lateral_faces(*ss_ptr, points, faces, vertical_weight, snapped_positions);
|
|
#else
|
|
construct_lateral_faces(*ss_ptr, points, faces);
|
|
#endif
|
|
}
|
|
else // height != default_extrusion_height<FT>()
|
|
{
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
Visitor visitor(*ss_ptr, offset_points, vertical_weight, snapped_positions);
|
|
#else
|
|
Visitor visitor(*ss_ptr, offset_points);
|
|
#endif
|
|
Offset_builder ob(*ss_ptr, Offset_builder_traits(), visitor);
|
|
Offset_polygons raw_output;
|
|
ob.construct_offset_contours(abs_height, std::back_inserter(raw_output));
|
|
|
|
Offset_polygons_with_holes output = CGAL::arrange_offset_polygons_2<Polygon_with_holes_2>(raw_output);
|
|
construct_horizontal_faces(output, height, points, faces);
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
construct_lateral_faces(*ss_ptr, ob, height, points, faces, offset_points, vertical_weight, snapped_positions);
|
|
#else
|
|
construct_lateral_faces(*ss_ptr, ob, height, points, faces, offset_points);
|
|
#endif
|
|
}
|
|
|
|
// if the height is negative (extruding downwards), we need to invert all the faces
|
|
if(is_negative(height))
|
|
{
|
|
for(auto& f : faces)
|
|
std::swap(f[0], f[1]);
|
|
}
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
apply_snapping<Geom_traits>(points, snapped_positions);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename PolygonWithHoles,
|
|
typename PointRange, typename FaceRange>
|
|
bool outward_construction(const PolygonWithHoles& pwh,
|
|
const std::vector<std::vector<FT> >& speeds,
|
|
const FT vertical_weight,
|
|
const FT height,
|
|
PointRange& points,
|
|
FaceRange& faces)
|
|
{
|
|
CGAL_precondition(!is_zero(height));
|
|
CGAL_precondition(height != default_extrusion_height<FT>()); // was checked before, this is just a reminder
|
|
|
|
const FT abs_height = abs(height);
|
|
|
|
// bottom face (z=0)
|
|
construct_horizontal_faces(pwh, 0 /*height*/, points, faces, true /*invert faces*/);
|
|
|
|
// Avoid recomputing offset points multiple times
|
|
std::unordered_map<HDS_Halfedge_const_handle, Point_2> offset_points;
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
// This is to deal with vertical slabs: we temporarily give a non-vertical slope to be able
|
|
// to construct the SLS, and we then snap back the position to the vertical planes.
|
|
// Note that points in non-vertical faces are also changed a bit
|
|
std::map<Point_2, Point_2> snapped_positions;
|
|
#endif
|
|
|
|
Offset_polygons raw_output; // accumulates for both the outer boundary and the holes
|
|
|
|
// the exterior of a polygon with holes is the exterior of its outer boundary,
|
|
// and the interior of its inverted holes
|
|
//
|
|
// Start with the outer boundary
|
|
{
|
|
Straight_skeleton_2_ptr ss_ptr = CGAL_SS_i::create_partial_exterior_weighted_straight_skeleton_2(
|
|
abs_height,
|
|
CGAL_SS_i::vertices_begin(pwh.outer_boundary()),
|
|
CGAL_SS_i::vertices_end(pwh.outer_boundary()),
|
|
std::begin(speeds[0]), std::end(speeds[0]),
|
|
m_gt);
|
|
|
|
if(!ss_ptr)
|
|
{
|
|
std::cerr << "Error: encountered an error during outer skeleton construction" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
#ifdef CGAL_SLS_DEBUG_DRAW
|
|
// print_straight_skeleton(*ss_ptr);
|
|
CGAL::draw(*ss_ptr);
|
|
#endif
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
Visitor visitor(*ss_ptr, offset_points, vertical_weight, snapped_positions);
|
|
#else
|
|
Visitor visitor(*ss_ptr, offset_points);
|
|
#endif
|
|
Offset_builder ob(*ss_ptr, Offset_builder_traits(), visitor);
|
|
ob.construct_offset_contours(abs_height, std::back_inserter(raw_output));
|
|
|
|
// Manually filter the offset of the outer frame
|
|
std::swap(raw_output[0], raw_output.back());
|
|
raw_output.pop_back();
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
construct_lateral_faces(*ss_ptr, ob, height, points, faces, offset_points,
|
|
vertical_weight, snapped_positions,
|
|
true /*ignore frame faces*/, true /*invert faces*/);
|
|
#else
|
|
construct_lateral_faces(*ss_ptr, ob, height, points, faces, offset_points,
|
|
true /*ignore frame faces*/, true /*invert faces*/);
|
|
#endif
|
|
}
|
|
|
|
// now, deal with the holes
|
|
|
|
std::size_t hole_id = 0;
|
|
for(auto hit=pwh.holes_begin(); hit!=pwh.holes_end(); ++hit, ++hole_id)
|
|
{
|
|
Polygon_2 hole = *hit; // intentional copy
|
|
hole.reverse_orientation();
|
|
|
|
// this is roughly "CGAL::create_exterior_weighted_skeleton_and_offset_polygons_with_holes_2()",
|
|
// but we want to know the intermediate straight skeleton to build the lateral faces of the 3D mesh
|
|
|
|
std::vector<Polygon_2> no_holes;
|
|
std::vector<std::vector<FT> > no_speeds;
|
|
Straight_skeleton_2_ptr ss_ptr = CGAL_SS_i::create_partial_interior_weighted_straight_skeleton_2(
|
|
abs_height,
|
|
CGAL_SS_i::vertices_begin(hole),
|
|
CGAL_SS_i::vertices_end(hole),
|
|
std::begin(no_holes), std::end(no_holes),
|
|
std::begin(speeds[1 + hole_id]), std::end(speeds[1 + hole_id]),
|
|
std::begin(no_speeds), std::end(no_speeds),
|
|
m_gt);
|
|
|
|
if(!ss_ptr)
|
|
{
|
|
std::cerr << "Error: encountered an error during skeleton construction" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
#ifdef CGAL_SLS_DEBUG_DRAW
|
|
// print_straight_skeleton(*ss_ptr);
|
|
CGAL::draw(*ss_ptr);
|
|
#endif
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
Visitor visitor(*ss_ptr, offset_points, vertical_weight, snapped_positions);
|
|
#else
|
|
Visitor visitor(*ss_ptr, offset_points);
|
|
#endif
|
|
Offset_builder ob(*ss_ptr, Offset_builder_traits(), visitor);
|
|
ob.construct_offset_contours(abs_height, std::back_inserter(raw_output));
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
construct_lateral_faces(*ss_ptr, ob, height, points, faces, offset_points,
|
|
vertical_weight, snapped_positions,
|
|
false /*no outer frame*/, true /*invert faces*/);
|
|
#else
|
|
construct_lateral_faces(*ss_ptr, ob, height, points, faces, offset_points,
|
|
false /*no outer frame*/, true /*invert faces*/);
|
|
#endif
|
|
}
|
|
|
|
// - the exterior offset of the outer boundary is built by creating an extra frame and turning
|
|
// the outer boundary into a hole. Hence, it needs to be reversed back to proper orientation
|
|
// - the exterior offset of the holes is built by reversing the holes and computing an internal
|
|
// skeleton. Hence, the result also needs to be reversed.
|
|
for(std::shared_ptr<Polygon_2> ptr : raw_output)
|
|
ptr->reverse_orientation();
|
|
|
|
Offset_polygons_with_holes output = CGAL::arrange_offset_polygons_2<Polygon_with_holes_2>(raw_output);
|
|
construct_horizontal_faces(output, height, points, faces);
|
|
|
|
// if the height is negative (extruding downwards), we need to invert all the faces
|
|
if(is_negative(height))
|
|
{
|
|
for(auto& f : faces)
|
|
std::swap(f[0], f[1]);
|
|
}
|
|
|
|
#ifdef CGAL_SLS_SNAP_TO_VERTICAL_SLABS
|
|
apply_snapping<Geom_traits>(points, snapped_positions);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// convert angles (in degrees) to weights, and handle vertical angles
|
|
template <typename FT, typename AngleRange>
|
|
void convert_angles(AngleRange& angles)
|
|
{
|
|
CGAL_precondition(!angles.empty());
|
|
|
|
auto angle_to_weight = [](const FT& angle) -> FT
|
|
{
|
|
CGAL_precondition(0 < angle && angle < 180);
|
|
|
|
// @todo should this be an epsilon around 90°? As theta goes to 90°, tan(theta) goes to infinity
|
|
// and thus we could get numerical issues (overflows) if the kernel is not exact
|
|
if(angle == 90)
|
|
return 0;
|
|
else
|
|
return std::tan(CGAL::to_double(angle * CGAL_PI / 180));
|
|
};
|
|
|
|
for(auto& contour_angles : angles)
|
|
for(FT& angle : contour_angles)
|
|
angle = angle_to_weight(angle);
|
|
}
|
|
|
|
// handle vertical angles (inf speed)
|
|
// returns
|
|
// - whether the weights are positive or negative (inward / outward)
|
|
// - whether the input weights are valid
|
|
// - the weight of vertical slabs
|
|
template <typename FT, typename WeightRange>
|
|
std::tuple<Slope, bool, FT>
|
|
preprocess_weights(WeightRange& weights)
|
|
{
|
|
CGAL_precondition(!weights.empty());
|
|
|
|
Slope slope = Slope::UNKNOWN;
|
|
|
|
FT max_value = 0; // non-inf, maximum absolute value
|
|
for(auto& contour_weights : weights)
|
|
{
|
|
for(FT& w : contour_weights)
|
|
{
|
|
// '0' means a vertical slab, aka 90° angle (see preprocess_angles())
|
|
if(w == 0)
|
|
continue;
|
|
|
|
// determine whether weights indicate all inward or all outward
|
|
if(slope == Slope::UNKNOWN)
|
|
{
|
|
// w is neither 0 nor inf here
|
|
slope = (w > 0) ? Slope::INWARD : Slope::OUTWARD;
|
|
}
|
|
else if(slope == Slope::INWARD && w < 0)
|
|
{
|
|
std::cerr << "Error: mixing positive and negative weights is not yet supported" << std::endl;
|
|
return {Slope::UNKNOWN, false, FT(-1)};
|
|
}
|
|
else if(slope == Slope::OUTWARD && w > 0)
|
|
{
|
|
std::cerr << "Error: mixing positive and negative weights is not yet supported" << std::endl;
|
|
return {Slope::UNKNOWN, false, FT(-1)};
|
|
}
|
|
|
|
// if we are going outwards, it is just an interior skeleton with opposite weights
|
|
w = CGAL::abs(w);
|
|
if(w > max_value)
|
|
max_value = w;
|
|
}
|
|
}
|
|
|
|
if(slope == Slope::UNKNOWN)
|
|
{
|
|
std::cerr << "Warning: all edges vertical?" << std::endl;
|
|
slope = Slope::VERTICAL;
|
|
}
|
|
|
|
// Take a weight which is a large % of the max value to ensure there's no ambiguity
|
|
//
|
|
// Since the max value might not be very close to 90°, take the max between of the large-% weight
|
|
// and the weight corresponding to an angle of 89.9999999°
|
|
const FT weight_of_89d9999999 = 572957787.3425436; // tan(89.9999999°)
|
|
const FT scaled_max = (std::max)(weight_of_89d9999999, FT(1e3) * max_value);
|
|
|
|
for(auto& contour_weights : weights)
|
|
{
|
|
for(FT& w : contour_weights)
|
|
{
|
|
if(w == FT(0))
|
|
w = scaled_max;
|
|
}
|
|
}
|
|
|
|
return {slope, true, scaled_max};
|
|
}
|
|
|
|
template <typename PolygonWithHoles,
|
|
typename WeightRange,
|
|
typename PolygonMesh,
|
|
typename NamedParameters>
|
|
bool extrude_skeleton(const PolygonWithHoles& pwh,
|
|
WeightRange& weights,
|
|
PolygonMesh& out,
|
|
const NamedParameters& np)
|
|
{
|
|
namespace PMP = ::CGAL::Polygon_mesh_processing;
|
|
|
|
using Polygon_2 = typename PolygonWithHoles::Polygon_2;
|
|
using Point = typename boost::range_value<Polygon_2>::type;
|
|
|
|
using Default_kernel = typename Kernel_traits<Point>::type;
|
|
using Geom_traits = typename internal_np::Lookup_named_param_def<internal_np::geom_traits_t,
|
|
NamedParameters,
|
|
Default_kernel>::type;
|
|
using FT = typename Geom_traits::FT;
|
|
|
|
using Point_3 = typename Geom_traits::Point_3;
|
|
|
|
using parameters::choose_parameter;
|
|
using parameters::get_parameter;
|
|
|
|
const bool verbose = choose_parameter(get_parameter(np, internal_np::verbose), false);
|
|
Geom_traits gt = choose_parameter<Geom_traits>(get_parameter(np, CGAL::internal_np::geom_traits));
|
|
|
|
const FT height = choose_parameter(get_parameter(np, internal_np::maximum_height), default_extrusion_height<FT>());
|
|
|
|
Slope slope;
|
|
bool valid_input;
|
|
FT vertical_weight;
|
|
std::tie(slope, valid_input, vertical_weight) = preprocess_weights<FT>(weights);
|
|
|
|
if(!valid_input)
|
|
{
|
|
if(verbose)
|
|
std::cerr << "Error: invalid input weights" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if(verbose)
|
|
{
|
|
switch(slope)
|
|
{
|
|
case Slope::UNKNOWN: std::cout << "Slope is UNKNOWN??" << std::endl; break;
|
|
case Slope::INWARD: std::cout << "Slope is INWARD" << std::endl; break;
|
|
case Slope::OUTWARD: std::cout << "Slope is OUTWARD" << std::endl; break;
|
|
case Slope::VERTICAL: std::cout << "Slope is VERTICAL" << std::endl; break;
|
|
}
|
|
}
|
|
|
|
// End of preprocessing, start the actual skeleton computation
|
|
|
|
if(slope != Slope::INWARD && height == default_extrusion_height<FT>())
|
|
{
|
|
if(verbose)
|
|
std::cerr << "Error: height must be specified when using an outward (or vertical) slope" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// build a soup, to be converted to a mesh afterwards
|
|
std::vector<Point_3> points;
|
|
std::vector<std::vector<std::size_t> > faces;
|
|
points.reserve(2 * pwh.outer_boundary().size()); // just a reasonable guess
|
|
faces.reserve(2 * pwh.outer_boundary().size() + 2*pwh.number_of_holes());
|
|
|
|
Extrusion_builder<Geom_traits> builder(gt);
|
|
bool res;
|
|
if(slope != Slope::OUTWARD) // INWARD or VERTICAL
|
|
res = builder.inward_construction(pwh, weights, vertical_weight, height, points, faces);
|
|
else
|
|
res = builder.outward_construction(pwh, weights, vertical_weight, height, points, faces);
|
|
|
|
if(!res)
|
|
return false;
|
|
|
|
#ifdef CGAL_SLS_OUTPUT_FILES
|
|
// This soup provides one connected component per edge of the input polygon
|
|
CGAL::IO::write_polygon_soup("extruded_skeleton_soup.off", points, faces, CGAL::parameters::stream_precision(17));
|
|
#endif
|
|
|
|
// Convert the triangle soup to a triangle mesh
|
|
|
|
PMP::merge_duplicate_points_in_polygon_soup(points, faces);
|
|
if(!PMP::is_polygon_soup_a_polygon_mesh(faces))
|
|
PMP::orient_polygon_soup(points, faces);
|
|
CGAL_assertion(PMP::is_polygon_soup_a_polygon_mesh(faces));
|
|
|
|
PMP::polygon_soup_to_polygon_mesh(points, faces, out);
|
|
|
|
CGAL_warning(is_valid_polygon_mesh(out) && is_closed(out));
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace Straight_skeleton_extrusion
|
|
|
|
// Documented in the Straight_skeleton_2 package
|
|
template <typename PolygonWithHoles,
|
|
typename PolygonMesh,
|
|
typename NamedParameters = parameters::Default_named_parameters>
|
|
bool extrude_skeleton(const PolygonWithHoles& pwh,
|
|
PolygonMesh& out,
|
|
const NamedParameters& np = parameters::default_values(),
|
|
std::enable_if_t<CGAL_SS_i::has_Hole_const_iterator<PolygonWithHoles>::value>* = nullptr)
|
|
{
|
|
namespace SSEI = Straight_skeleton_extrusion::internal;
|
|
|
|
using Polygon_2 = typename PolygonWithHoles::General_polygon_2;
|
|
using K = typename Kernel_traits<typename boost::range_value<Polygon_2>::type>::type;
|
|
using FT = typename K::FT;
|
|
using Default_speed_type = std::vector<std::vector<FT> >;
|
|
|
|
using parameters::choose_parameter;
|
|
using parameters::is_default_parameter;
|
|
using parameters::get_parameter_reference;
|
|
|
|
const bool has_weights = !(is_default_parameter<NamedParameters, internal_np::weights_param_t>::value);
|
|
const bool has_angles = !(is_default_parameter<NamedParameters, internal_np::angles_param_t>::value);
|
|
|
|
Default_speed_type def_speeds; // never used, but needed for compilation
|
|
|
|
if(has_weights)
|
|
{
|
|
// not using "Angles" here is on purpose, I want to copy the range but the NP is ref only
|
|
auto weights = choose_parameter(get_parameter_reference(np, internal_np::weights_param), def_speeds);
|
|
return SSEI::extrude_skeleton(pwh, weights, out, np);
|
|
}
|
|
else if(has_angles)
|
|
{
|
|
// not using "Weights" here is on purpose, I want to copy the range but the NP is ref only
|
|
auto angles = choose_parameter(get_parameter_reference(np, internal_np::angles_param), def_speeds);
|
|
SSEI::convert_angles<FT>(angles);
|
|
return SSEI::extrude_skeleton(pwh, angles, out, np);
|
|
}
|
|
else // neither angles nor weights were provided
|
|
{
|
|
std::vector<std::vector<FT> > uniform_weights;
|
|
uniform_weights.reserve(pwh.number_of_holes() + 1);
|
|
|
|
uniform_weights.push_back(std::vector<FT>(pwh.outer_boundary().size(), FT(1)));
|
|
for(const auto& hole : pwh.holes())
|
|
uniform_weights.push_back(std::vector<FT>(hole.size(), FT(1)));
|
|
|
|
return SSEI::extrude_skeleton(pwh, uniform_weights, out, np);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename Polygon, // without holes
|
|
typename PolygonMesh,
|
|
typename NamedParameters = parameters::Default_named_parameters>
|
|
bool extrude_skeleton(const Polygon& p,
|
|
PolygonMesh& out,
|
|
const NamedParameters& np = parameters::default_values(),
|
|
std::enable_if_t<!CGAL_SS_i::has_Hole_const_iterator<Polygon>::value>* = nullptr)
|
|
{
|
|
using Polygon_with_holes = CGAL::General_polygon_with_holes_2<Polygon>;
|
|
|
|
return extrude_skeleton(Polygon_with_holes(p), out, np);
|
|
}
|
|
|
|
} // namespace CGAL
|
|
|
|
#endif // CGAL_SLS_EXTRUSION_EXTRUDE_SKELETON_H
|