From 03d1579906963f4b57e9a25cd390d956d27615d6 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Fri, 13 Jun 2025 15:52:02 +0200 Subject: [PATCH 01/49] first commit of working approximate convex decomposition --- .../Polygon_mesh_processing/CMakeLists.txt | 2 + .../approximate_convex_decomposition.cpp | 51 + .../approximate_convex_decomposition.h | 1084 +++++++++++++++++ .../internal/parameters_interface.h | 5 + 4 files changed, 1142 insertions(+) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index f5035cd158c..10b34a632bf 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -58,6 +58,7 @@ create_single_source_cgal_program("isotropic_remeshing_with_custom_sizing_exampl create_single_source_cgal_program("isotropic_remeshing_with_allow_move.cpp") create_single_source_cgal_program("triangle_mesh_autorefinement.cpp") create_single_source_cgal_program("soup_autorefinement.cpp") +create_single_source_cgal_program("approximate_convex_decomposition.cpp") find_package(Eigen3 3.2.0 QUIET) #(requires 3.2.0 or greater) include(CGAL_Eigen3_support) @@ -140,6 +141,7 @@ if(TARGET CGAL::TBB_support) target_link_libraries(hausdorff_distance_remeshing_example PRIVATE CGAL::TBB_support) target_link_libraries(hausdorff_bounded_error_distance_example PRIVATE CGAL::TBB_support) target_link_libraries(soup_autorefinement PRIVATE CGAL::TBB_support) + target_link_libraries(approximate_convex_decomposition PRIVATE CGAL::TBB_support) create_single_source_cgal_program("corefinement_parallel_union_meshes.cpp") target_link_libraries(corefinement_parallel_union_meshes PRIVATE CGAL::TBB_support) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp new file mode 100644 index 00000000000..b717eca80e4 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -0,0 +1,51 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef K::Point_3 Point; + +typedef CGAL::Surface_mesh Mesh; +typedef CGAL::Polyhedron_3 Polyhedron; +typedef boost::graph_traits::face_descriptor face_descriptor; +namespace PMP = CGAL::Polygon_mesh_processing; + +#include + +#include +#include +#include // for std::move + +int main(int argc, char* argv[]) +{ + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); +/* + std::vector pc, pts; + + if (!CGAL::IO::read_points("surface_points.ply", std::back_inserter(pc))) + return 0; + + std::vector > indices; + convex_hull_3(pc.begin(), pc.end(), pts, indices);*/ + + Mesh mesh; + if(!PMP::IO::read_polygon_mesh(filename, mesh)) + { + std::cerr << "Invalid input." << std::endl; + return 1; + } + + std::vector convex_hulls; + + std::size_t n = PMP::approximate_convex_decomposition(mesh, 5, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.5).maximum_number_of_convex_hulls(7)); + + return 0; +} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h new file mode 100644 index 00000000000..fa75c167948 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -0,0 +1,1084 @@ +// Copyright (c) 2025 GeometryFactory (France). +// 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) : Sven Oesau +// + +#ifndef CGAL_POLYGON_MESH_PROCESSING_CONVEX_DECOMPOSITION_H +#define CGAL_POLYGON_MESH_PROCESSING_CONVEX_DECOMPOSITION_H + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#undef CGAL_LINKED_WITH_TBB + +#ifdef CGAL_LINKED_WITH_TBB +#include +#include +#include +#include +#else +#include +#endif + +#include +#include + +#include + +struct hash { + std::size_t operator()(const std::array &a) const { + return (((a[0] * 1193) ^ a[1]) * 1212) ^ a[2]; + } +}; + +namespace CGAL { +namespace Polygon_mesh_processing { +namespace internal { + +using Vec3_uint = std::array; + +struct Bbox_uint { + Vec3_uint lower; + Vec3_uint upper; + Bbox_uint(const Vec3_uint &lower, const Vec3_uint &upper) : lower(lower), upper(upper) {} +}; + +enum Grid_cell : int8_t { + OUTSIDE = -1, + UNKNOWN = 0, + INSIDE = 1, + SURFACE = 2, + UNSET = 3 +}; + +void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + std::ofstream stream(filename); + + stream << + "ply" << std::endl << + "format ascii 1.0" << std::endl << + "element vertex " << (grid_size[0] * grid_size[1] * grid_size[2]) << std::endl << + "property double x" << std::endl << + "property double y" << std::endl << + "property double z" << std::endl << + "property uchar red" << std::endl << + "property uchar green" << std::endl << + "property uchar blue" << std::endl << + "end_header" << std::endl; + + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + stream << (bb.xmin() + (x + 0.5) * voxel_size) << " " << (bb.ymin() + (y + 0.5) * voxel_size) << " " << (bb.zmin() + (z + 0.5) * voxel_size) << " "; + switch (vox(x, y, z)) { + case INSIDE: + stream << "175 175 100" << std::endl; + break; + case OUTSIDE: + stream << "125 125 175" << std::endl; + break; + case SURFACE: + stream << "200 100 100" << std::endl; + break; + default: + stream << "0 0 0" << std::endl; + break; + } + } + + stream.close(); +} + +void export_voxels(const std::string& filename, const Bbox_3& bb, std::vector& voxels, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + std::ofstream stream(filename); + + stream << + "ply" << std::endl << + "format ascii 1.0" << std::endl << + "element vertex " << voxels.size() << std::endl << + "property double x" << std::endl << + "property double y" << std::endl << + "property double z" << std::endl << + "end_header" << std::endl; + + for (const Vec3_uint& v : voxels) { + stream << (bb.xmin() + (v[0] + 0.5) * voxel_size) << " " << (bb.ymin() + (v[1] + 0.5) * voxel_size) << " " << (bb.zmin() + (v[2] + 0.5) * voxel_size) << " "; + } + + stream.close(); +} + +template +void export_points(const std::string& filename, const Bbox_3& bb, std::vector& points) { + std::ofstream stream(filename); + + stream << + "ply" << std::endl << + "format ascii 1.0" << std::endl << + "element vertex " << points.size() << std::endl << + "property double x" << std::endl << + "property double y" << std::endl << + "property double z" << std::endl << + "end_header" << std::endl; + + for (const Point_3& p : points) { + stream << (bb.xmin() + p.x()) << " " << (bb.ymin() + p.y()) << " " << (bb.zmin() + p.z()) << " "; + } + + stream.close(); +} + +template +void export_points(const std::string& filename, Range& points) { + std::ofstream stream(filename); + + stream << + "ply" << std::endl << + "format ascii 1.0" << std::endl << + "element vertex " << points.size() << std::endl << + "property double x" << std::endl << + "property double y" << std::endl << + "property double z" << std::endl << + "end_header" << std::endl; + + for (const auto& p : points) { + stream << p.x() << " " << p.y() << " " << p.z() << std::endl; + } + + stream.close(); +} + + +template +std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t number_of_voxels) { + std::size_t max_voxels_axis = std::pow(number_of_voxels, 1.0 / 3.0); + // get largest axis + FT largest = 0; + + if (bb.x_span() >= bb.y_span() && bb.x_span() >= bb.z_span()) + largest = bb.x_span(); + else if (bb.y_span() >= bb.x_span() && bb.y_span() >= bb.z_span()) + largest = bb.y_span(); + else if (bb.z_span() >= bb.x_span() && bb.z_span() >= bb.y_span()) + largest = bb.z_span(); + + const FT voxel_size = largest / (max_voxels_axis - 3); + + FT s = 1.5 * voxel_size; + + bb = Bbox_3(bb.xmin() - s, bb.ymin() - s, bb.zmin() - s, bb.xmax() + s, bb.ymax() + s, bb.zmax() + s); + + return { Vec3_uint{static_cast(bb.x_span() / voxel_size + 0.5), static_cast(bb.y_span() / voxel_size + 0.5), static_cast(bb.z_span() / voxel_size + 0.5)}, voxel_size}; +} + + +template +Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_traits::face_descriptor fd, const Bbox_3& bb, const FT& voxel_size) { + Bbox_3 face_bb = face_bbox(fd, mesh); + return Bbox_uint({ + static_cast((face_bb.xmin() - bb.xmin()) / voxel_size - 0.5), + static_cast((face_bb.ymin() - bb.ymin()) / voxel_size - 0.5), + static_cast((face_bb.zmin() - bb.zmin()) / voxel_size - 0.5) + }, { + static_cast((face_bb.xmax() - bb.xmin()) / voxel_size + 0.5), + static_cast((face_bb.ymax() - bb.ymin()) / voxel_size + 0.5), + static_cast((face_bb.zmax() - bb.zmin()) / voxel_size + 0.5) + }); +} + +template +Bbox_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const FT& voxel_size) { + return Bbox_3( + bb.xmin() + voxel[0] * voxel_size, + bb.ymin() + voxel[1] * voxel_size, + bb.zmin() + voxel[2] * voxel_size, + bb.xmax() + (voxel[0] + 1) * voxel_size, + bb.ymax() + (voxel[1] + 1) * voxel_size, + bb.zmax() + (voxel[2] + 1) * voxel_size + ); +} + +void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_uint& grid_size, std::deque& todo) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + while (!todo.empty()) { + auto [x, y, z0] = todo.front(); + todo.pop_front(); + if (vox(x, y, z0) != INSIDE) + return; + + bool xneg = false, xpos = false; + bool yneg = false, ypos = false; + bool zneg = false, zpos = false; + + // positive direction + for (unsigned int z = z0; z < grid_size[2]; z++) { + if (vox(x, y, z) != INSIDE) + break; + + vox(x, y, z) = label; + if (x > 0) + if (vox(x - 1, y, z) == INSIDE) { + if (!xneg) { + xneg = true; + todo.push_back({ x - 1, y, z }); + } + } + else xneg = false; + + if (x < grid_size[0] - 1) + if (vox(x + 1, y, z) == INSIDE) { + if (!xpos) { + xpos = true; + todo.push_back({ x + 1, y, z }); + } + } + else xpos = false; + + if (y > 0) + if (vox(x, y - 1, z) == INSIDE) { + if (!yneg) { + yneg = true; + todo.push_front({ x, y - 1, z }); + } + } + else yneg = false; + + if (y < grid_size[1] - 1) + if (vox(x, y + 1, z) == INSIDE) { + if (!ypos) { + ypos = true; + todo.push_front({ x, y + 1, z }); + } + } + else ypos = false; + } + + xneg = xpos = yneg = ypos = zneg = zpos = false; + + if (z0 == 0) + continue; + + for (unsigned int z = z0 - 1; z > 0; z--) { + if (vox(x, y, z) != INSIDE) + break; + + vox(x, y, z) = label; + if (x > 0) + if (vox(x - 1, y, z) == INSIDE) { + if (!xneg) { + xneg = true; + todo.push_back({ x - 1, y, z }); + } + } + else xneg = false; + + if (x < grid_size[0] - 1) + if (vox(x + 1, y, z) == INSIDE) { + if (!xpos) { + xpos = true; + todo.push_back({ x + 1, y, z }); + } + } + else xpos = false; + + if (y > 0) + if (vox(x, y - 1, z) == INSIDE) { + if (!yneg) { + yneg = true; + todo.push_front({ x, y - 1, z }); + } + } + else yneg = false; + + if (y < grid_size[1] - 1) + if (vox(x, y + 1, z) == INSIDE) { + if (!ypos) { + ypos = true; + todo.push_front({ x, y + 1, z }); + } + } + else ypos = false; + } + } +} + +// Valid voxel grids separate OUTSIDE from INSIDE via SURFACE +bool check_grid(std::vector& grid, const Vec3_uint& grid_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + std::size_t in = 0, out = 0, surface = 0, other = 0, violations = 0; + + for (unsigned int x = 1; x < grid_size[0] - 1; x++) + for (unsigned int y = 1; y < grid_size[1] - 1; y++) + for (unsigned int z = 1; z < grid_size[2] - 1; z++) { + switch (vox(x, y, z)) { + case INSIDE: + if ( vox(x - 1, y, z) == OUTSIDE + || vox(x + 1, y, z) == OUTSIDE + || vox(x, y - 1, z) == OUTSIDE + || vox(x, y + 1, z) == OUTSIDE + || vox(x, y, z - 1) == OUTSIDE + || vox(x, y, z + 1) == OUTSIDE) { + std::cout << "touching I-O: " << x << " " << y << " " << z << std::endl; + violations++; + } + in++; + break; + case OUTSIDE: + if (vox(x - 1, y, z) == INSIDE + || vox(x + 1, y, z) == INSIDE + || vox(x, y - 1, z) == INSIDE + || vox(x, y + 1, z) == INSIDE + || vox(x, y, z - 1) == INSIDE + || vox(x, y, z + 1) == INSIDE) { + std::cout << "touching O-I: " << x << " " << y << " " << z << std::endl; + violations++; + } + out++; + break; + case SURFACE: + surface++; + break; + default: + std::cout << "other " << x << " " << y << " " << z << std::endl; + other++; + break; + } + } + std::cout << "i: " << in << " o: " << out << " s: " << surface << " " << other << std::endl; + std::cout << "violations: " << violations << std::endl; + + return violations == 0; +} + +// Only works for closed meshes +void label_floodfill(std::vector& grid, const Vec3_uint& grid_size) { + // Walk around boundary and start floodfill when voxel label is INSIDE + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + std::deque todo; + + // xmin/xmax + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (vox(0, y, z) == INSIDE) { + todo.push_front({0, y, z}); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + + if (vox(grid_size[0] - 1, y, z) == INSIDE) { + todo.push_front({ grid_size[0] - 1, y, z }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + } + + // ymin/ymax + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (vox(x, 0, z) == INSIDE) { + todo.push_front({ x, 0, z }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + + if (vox(x, grid_size[1] - 1, z) == INSIDE) { + todo.push_front({ x, grid_size[1] - 1, z }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + } + + // ymin/ymax + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) { + if (vox(x, y, 0) == INSIDE) { + todo.push_front({ x, y, 0 }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + + if (vox(x, y, grid_size[2] - 1) == INSIDE) { + todo.push_front({ x, y, grid_size[2] - 1 }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + } +} + +void naive_floodfill(std::vector& grid, const Vec3_uint& grid_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + std::queue queue; + queue.push({0, 0, 0}); + + while (!queue.empty()) { + auto [x, y, z] = queue.front(); + queue.pop(); + + if (vox(x, y, z) != INSIDE) + continue; + + vox(x, y, z) = OUTSIDE; + if (x > 0) + if (vox(x - 1, y, z) == INSIDE) { + queue.push({ x - 1, y, z }); + } + + if (x < grid_size[0] - 1) + if (vox(x + 1, y, z) == INSIDE) { + queue.push({ x + 1, y, z }); + } + + if (y > 0) + if (vox(x, y - 1, z) == INSIDE) { + queue.push({ x, y - 1, z }); + } + + if (y < grid_size[1] - 1) + if (vox(x, y + 1, z) == INSIDE) { + queue.push({ x, y + 1, z }); + } + + if (z > 0) + if (vox(x, y, z - 1) == INSIDE) { + queue.push({ x, y, z - 1 }); + } + + if (z < grid_size[2] - 1) + if (vox(x, y, z + 1) == INSIDE) { + queue.push({ x, y, z + 1 }); + } + } +} + +template +struct Convex_hull { + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + Bbox_3 bbox; + FT voxel_volume; + FT volume; + FT volume_error; + std::vector points; + std::vector> indices; + + Convex_hull() noexcept : voxel_volume(0), volume(0), volume_error(0) {} + Convex_hull(Convex_hull&& o) noexcept { + bbox = o.bbox; + voxel_volume = o.voxel_volume; + volume = o.volume; + volume_error = o.volume_error; + points = std::move(o.points); + indices = std::move(o.indices); + } + + Convex_hull(const Convex_hull& o) { + bbox = o.bbox; + voxel_volume = o.voxel_volume; + volume = o.volume; + volume_error = o.volume_error; + points = o.points; + indices = o.indices; + } + + Convex_hull& operator= (const Convex_hull& o) { + bbox = o.bbox; + voxel_volume = o.voxel_volume; + volume = o.volume; + volume_error = o.volume_error; + points = o.points; + indices = o.indices; + + return *this; + } + + Convex_hull& operator= (Convex_hull&& o) noexcept { + bbox = o.bbox; + voxel_volume = o.voxel_volume; + volume = o.volume; + volume_error = o.volume_error; + points = std::move(o.points); + indices = std::move(o.indices); + + return *this; + } +}; + +template +struct Candidate { + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + std::vector surface; + std::vector new_surface; + std::vector inside; + std::size_t depth; + Bbox_uint bbox; + Convex_hull ch; + + Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {} + Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) {} +}; + +template +typename GeomTraits::FT volume(const std::vector &pts, const std::vector> &indices) { + ::CGAL::internal::Evaluate evaluate; + + typename GeomTraits::FT vol = 0; + typename GeomTraits::Compute_volume_3 cv3; + + typename GeomTraits::Point_3 origin(0, 0, 0); + + for (const std::array &i : indices) { + vol += cv3(origin, pts[i[0]], pts[i[1]], pts[i[2]]); + evaluate(vol); + } + + return vol; +} + + +template +void convex_hull(const PointRange& pts, std::vector &hull_points, std::vector > &hull_indices) { + using Mesh = CGAL::Surface_mesh; + Mesh m; + + convex_hull_3(pts.begin(), pts.end(), m); + //std::vector pts; + //std::vector > indices; + //convex_hull_3(voxel_points.begin(), voxel_points.end(), pts, indices);// Why does this not work? + //convex_hull_3(voxel_points.begin(), voxel_points.end(), c.hull_points, c.hull_indices);// Why does this not work? + hull_points.resize(m.number_of_vertices()); + hull_indices.resize(m.number_of_faces()); + + std::size_t idx = 0; + for (const typename Mesh::vertex_index v : m.vertices()) + hull_points[idx++] = m.point(v); + + idx = 0; + for (const typename Mesh::face_index f : m.faces()) { + auto he = m.halfedge(f); + hull_indices[idx][0] = m.source(he); + he = m.next(he); + hull_indices[idx][1] = m.source(he); + he = m.next(he); + hull_indices[idx++][2] = m.source(he); + } +} + +template +void compute_candidate(Candidate &c, const Bbox_3& bb, typename GeomTraits::FT voxel_size) { + using Point_3 = typename GeomTraits::Point_3; + using FT = typename GeomTraits::FT; + + std::unordered_set voxel_points; + + // Is it more efficient than using std::vector with plenty of duplicates? + for (const Vec3_uint& v : c.surface) { + FT xmin = bb.xmin() + v[0] * voxel_size; + FT ymin = bb.ymin() + v[1] * voxel_size; + FT zmin = bb.zmin() + v[2] * voxel_size; + FT xmax = bb.xmin() + (v[0] + 1) * voxel_size; + FT ymax = bb.ymin() + (v[1] + 1) * voxel_size; + FT zmax = bb.zmin() + (v[2] + 1) * voxel_size; + voxel_points.insert(Point_3(xmin, ymin, zmin)); + voxel_points.insert(Point_3(xmin, ymax, zmin)); + voxel_points.insert(Point_3(xmin, ymin, zmax)); + voxel_points.insert(Point_3(xmin, ymax, zmax)); + voxel_points.insert(Point_3(xmax, ymin, zmin)); + voxel_points.insert(Point_3(xmax, ymax, zmin)); + voxel_points.insert(Point_3(xmax, ymin, zmax)); + voxel_points.insert(Point_3(xmax, ymax, zmax)); + } + + //export_points("surface_points.ply", voxel_points); + + convex_hull(voxel_points, c.ch.points, c.ch.indices); + + c.ch.volume = volume(c.ch.points, c.ch.indices); + + assert(c.ch.volume > 0); + + c.ch.voxel_volume = (voxel_size * voxel_size * voxel_size) * (c.inside.size() + c.surface.size() + c.new_surface.size()); + c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; +} + +template +void fill_grid(Candidate &c, std::vector &grid, const FaceGraph &mesh, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + for (const typename boost::graph_traits::face_descriptor fd : faces(mesh)) { + Bbox_uint face_bb = grid_bbox_face(mesh, fd, bb, voxel_size); + assert(face_bb.lower[0] <= face_bb.upper[0]); + assert(face_bb.lower[1] <= face_bb.upper[1]); + assert(face_bb.lower[2] <= face_bb.upper[2]); + assert(face_bb.upper[0] < grid_size[0]); + assert(face_bb.upper[1] < grid_size[1]); + assert(face_bb.upper[2] < grid_size[2]); + for (unsigned int x = face_bb.lower[0]; x <= face_bb.upper[0]; x++) + for (unsigned int y = face_bb.lower[1]; y <= face_bb.upper[1]; y++) + for (unsigned int z = face_bb.lower[2]; z <= face_bb.upper[2]; z++) + if (do_intersect(triangle(fd, mesh), bbox_voxel({x, y, z}, bb, voxel_size))) + vox(x, y, z) = Grid_cell::SURFACE; + } + + //export_grid("before.ply", bb, grid, grid_size, voxel_size); + + // For now, only do floodfill + //label_floodfill(grid, grid_size); + naive_floodfill(grid, grid_size); + + c.bbox.upper = grid_size; + + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) + if (vox(x, y, z) == INSIDE) + c.inside.push_back({x, y, z}); + else if (vox(x, y, z) == SURFACE) + c.surface.push_back({ x, y, z }); + + check_grid(grid, grid_size); + + //export_grid("after_fill.ply", bb, grid, grid_size, voxel_size); +} + +template +void init(Candidate &c, const FaceGraph& mesh, std::vector& grid, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size) { + internal::fill_grid(c, grid, mesh, bb, grid_size, voxel_size); + compute_candidate(c, bb, voxel_size); +} + +template +void split(std::vector > &candidates, Candidate& c, unsigned int axis, unsigned int location, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np) { + //Just split the voxel bbox along 'axis' after voxel index 'location' + Candidate upper(c.depth + 1, c.bbox); + Candidate lower(c.depth + 1, c.bbox); + + upper.bbox.lower[axis] = location; + lower.bbox.upper[axis] = location - 1; + + for (const Vec3_uint& v : c.surface) { + assert(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + assert(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + assert(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location <= v[axis]) + upper.surface.push_back(v); + else + lower.surface.push_back(v); + } + + for (const Vec3_uint& v : c.new_surface) { + assert(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + assert(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + assert(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location <= v[axis]) + upper.new_surface.push_back(v); + else + lower.new_surface.push_back(v); + } + + for (const Vec3_uint& v : c.inside) { + assert(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + assert(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + assert(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location <= v[axis]) { + if (upper.bbox.lower[axis] == v[axis]) + upper.new_surface.push_back(v); + else + upper.inside.push_back(v); + } + else { + if (lower.bbox.upper[axis] == v[axis]) + lower.new_surface.push_back(v); + else + lower.inside.push_back(v); + //new_surface is used for convex hull calculation + } + } + + if (!upper.surface.empty()) { + compute_candidate(upper, bbox, voxel_size); + candidates.emplace_back(std::move(upper)); + } + + if (!lower.surface.empty()) { + compute_candidate(lower, bbox, voxel_size); + candidates.emplace_back(std::move(lower)); + } +} + +template +void choose_splitting_plane(Candidate& c, unsigned int &axis, unsigned int &location, std::vector& grid, const Vec3_uint& grid_size, const NamedParameters& np) { + //Just split the voxel bbox along 'axis' after voxel index 'location' + const std::array span = {c.bbox.upper[0] - c.bbox.lower[0], c.bbox.upper[1] - c.bbox.lower[1], c.bbox.upper[2] - c.bbox.lower[2]}; + + // Split largest axis + axis = (span[0] >= span[1]) ? 0 : 1; + axis = (span[axis] >= span[2]) ? axis : 2; + + location = (c.bbox.upper[axis] + c.bbox.lower[axis]) / 2; +} + +template +bool finished(Candidate &c, const NamedParameters& np) { + const typename GeomTraits::FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); + const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + + if (c.ch.volume_error <= max_error) + return true; + + if (c.depth >= max_depth) + return true; + + return false; +} + +template +void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3 &bbox, const typename GeomTraits::FT &voxel_size, const NamedParameters& np) { + using FT = typename GeomTraits::FT; + const FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); + const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + + std::vector> final_candidates; + + while (!candidates.empty()) { + std::vector> former_candidates = std::move(candidates); + for (Candidate& c : former_candidates) { + // check loop conditions here? + if (finished(c, np)) { + c.ch.bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); + c.ch.bbox.scale(1.1); // Enlarge bounding boxes by a small factor for the following merge + final_candidates.push_back(std::move(c)); + continue; + } + unsigned int axis = 0, location = 0; + choose_splitting_plane(c, axis, location, grid, grid_size, np); + split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); + } + } + + std::swap(candidates, final_candidates); +} + +template +void merge(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const typename GeomTraits::FT &hull_volume, const NamedParameters& np) { + const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); + if (candidates.size() <= max_convex_hulls) + return; + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + + struct Merged_candidate { + std::size_t ch_a, ch_b; + int ch; + FT volume_error; + + bool operator < (const Merged_candidate& other) const { + return (volume_error > other.volume_error); + } + + Merged_candidate() : ch_a(-1), ch_b(-1) {} + Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} + }; + + // Consider all non-equal pairs + std::size_t max_merge_candidates = (candidates.size() * (candidates.size() - 1)) >> 1; + +#ifdef CGAL_LINKED_WITH_TBB + tbb::concurrent_unordered_map> hulls; + std::atomic num_hulls = candidates.size(); +#else + std::unordered_map> hulls; + std::size_t num_hulls = candidates.size(); +#endif + + std::unordered_set keep; + + for (std::size_t i = 0;i todo; + tbb::concurrent_priority_queue queue; +#else + std::priority_queue queue; +#endif + + const auto do_merge = [hull_volume, &hulls, &num_hulls, &queue](Merged_candidate &m) { + Convex_hull& ci = hulls[m.ch_a]; + Convex_hull& cj = hulls[m.ch_b]; +#ifdef CGAL_LINKED_WITH_TBB + m.ch = num_hulls.fetch_add(1); + Convex_hull& ch = hulls[m.ch]; +#else + m.ch = num_hulls++; + Convex_hull& ch = hulls[m.ch]; +#endif + ch.bbox = ci.bbox + cj.bbox; + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull(pts, ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + }; + + //queue.reserve(max_merge_candidates); + + for (std::size_t i : keep) { + const Convex_hull& ci = hulls[i]; + for (std::size_t j : keep) { + if (j <= i) + continue; + const Convex_hull& cj = hulls[j]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) { +#ifdef CGAL_LINKED_WITH_TBB + // Move this into a task list for later parallelization? + todo.emplace_back(Merged_candidate(i, j)); +#else + Merged_candidate m(i, j); + + m.ch = num_hulls++; + Convex_hull& ch = hulls[m.ch]; + ch.bbox = ci.bbox + cj.bbox; + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull(pts, ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + queue.push(std::move(m)); +#endif + } + else { + Merged_candidate m(i, j); + Bbox_3 bbox = ci.bbox + cj.bbox; + m.ch = -1; + m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(m)); + } + } + } + + // parallel for if available +#ifdef CGAL_LINKED_WITH_TBB + tbb::parallel_for_each(todo, do_merge); + for (Merged_candidate &m : todo) + queue.push(std::move(m)); + todo.clear(); +#endif + + while (!queue.empty() && keep.size() > max_convex_hulls) { +#ifdef CGAL_LINKED_WITH_TBB + Merged_candidate m; + while (!queue.try_pop(m) && !queue.empty()); +#else + Merged_candidate m = queue.top(); + queue.pop(); +#endif + + auto ch_a = hulls.find(m.ch_a); + if (ch_a == hulls.end()) + continue; + + auto ch_b = hulls.find(m.ch_b); + if (ch_b == hulls.end()) + continue; + + if (m.ch == -1) + do_merge(m); + + hulls.erase(ch_a); + keep.erase(m.ch_a); + hulls.erase(ch_b); + keep.erase(m.ch_b); + + const Convex_hull& cj = hulls[m.ch]; + + for (std::size_t id : keep) { + const Convex_hull& ci = hulls[id]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) { +#ifdef CGAL_LINKED_WITH_TBB + // Move this into a task list for later parallelization? + todo.emplace_back(Merged_candidate(id, m.ch)); +#else + Merged_candidate merged(id, m.ch); + + merged.ch = num_hulls++; + Convex_hull& ch = hulls[merged.ch]; + ch.bbox = ci.bbox + cj.bbox; + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull(pts, ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = merged.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + queue.push(std::move(merged)); +#endif + } + else { + Merged_candidate merged(id, m.ch); + Bbox_3 bbox = ci.bbox + cj.bbox; + merged.ch = -1; + merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(merged)); + } + } + + keep.insert(m.ch); + + std::cout << keep.size() << std::endl; + + // parallel for +#ifdef CGAL_LINKED_WITH_TBB + tbb::parallel_for_each(todo, do_merge); + for (Merged_candidate& m : todo) + queue.push(std::move(m)); + todo.clear(); +#endif + } + + num_hulls = 0; + + for (std::size_t i : keep) + candidates.push_back(std::move(hulls[i])); +} + +} + +template +std::size_t approximate_convex_decomposition(const FaceGraph& mesh, std::size_t number_of_convex_hulls, OutputIterator out, const NamedParameters& np = parameters::default_values()) { + CGAL::Memory_sizer memory; + std::size_t virt_mem = memory.virtual_size(); + std::size_t res_mem = memory.resident_size(); + CGAL::Timer timer; + + using Geom_traits = typename GetGeomTraits::type; + using Vertex_point_map = typename GetVertexPointMap::type; + + Vertex_point_map vpm = parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, mesh)); + + using FT = typename Geom_traits::FT; + + // A single voxel grid should be sufficient + // Why are they creating a voxel mesh and an AABB tree? + // - just tracing the voxel grid should be sufficient? especially as the voxel grid + // - tracing a voxel grid is very memory intensive, especially when it is sparse + // Do they actually have a grid and fill it or only container of voxels? + //const std::size_t voxel_size = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 50); + const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 1000000); + const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + + //std::vector::face_descriptor> degenerate_faces; + //CGAL::Polygon_mesh_processing::degenerate_faces(mesh, std::back_inserter(degenerate_faces)); + + std::cout << (CGAL::is_closed(mesh) ? "input mesh is closed" : "input mesh is not closed") << std::endl; + //std::cout << degenerate_faces.size() << " degenerate faces" << std::endl; + + Bbox_3 bb = bbox(mesh); + //FT voxel_size; + const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); + + std::cout << "grid_size: " << grid_size[0] << " " << grid_size[1] << " " << grid_size[2] << std::endl; + + timer.start(); + + // if floodfill take INSIDE, otherwise UNKNOWN + std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); + + std::vector> candidates(1); + + init(candidates[0], mesh, grid, bb, grid_size, voxel_size); + + const FT hull_volume = candidates[0].ch.volume; + + double after_init = timer.time(); + + recurse(candidates, grid, grid_size, bb, voxel_size, np); + + double after_recurse = timer.time(); + +// for (std::size_t i = 0; i < candidates.size(); i++) { +// CGAL::IO::write_polygon_soup(std::to_string(max_depth) + "-" + std::to_string(i) + "-d" + std::to_string(candidates[i].depth) + "-e" + std::to_string(candidates[i].ch.volume_error) + ".off", candidates[i].ch.points, candidates[i].ch.indices); +// } + + std::size_t virt_mem2 = memory.virtual_size(); + std::size_t res_mem2 = memory.resident_size(); + + std::vector> hulls; + for (internal::Candidate &c : candidates) + hulls.emplace_back(std::move(c.ch)); + + candidates.clear(); + + // merge until target number is reached + merge(hulls, grid, grid_size, bb, voxel_size, hull_volume, np); + + double after_merge = timer.time(); + + std::cout << (virt_mem2 - virt_mem) << " additional virtual memory allocated" << std::endl; + std::cout << (res_mem2 - res_mem) << " additional resident memory occupied\n" << std::endl; + + std::cout << "timing:" << std::endl; + std::cout << after_init << " initialization" << std::endl; + std::cout << (after_recurse - after_init) << " recurse" << std::endl; + std::cout << (after_merge - after_recurse) << " merge" << std::endl; + + std::cout << memory.virtual_size() << " virtual " << memory.resident_size() << " resident memory allocated in total" << std::endl; + + for (std::size_t i = 0;i Date: Fri, 13 Jun 2025 16:18:52 +0200 Subject: [PATCH 02/49] clean up and moving results into outputiterator --- .../approximate_convex_decomposition.cpp | 35 ++++++------------- .../approximate_convex_decomposition.h | 19 +++++----- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index b717eca80e4..e100dda3170 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -8,33 +7,19 @@ #include #include #include - -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; - -typedef K::Point_3 Point; - -typedef CGAL::Surface_mesh Mesh; -typedef CGAL::Polyhedron_3 Polyhedron; -typedef boost::graph_traits::face_descriptor face_descriptor; -namespace PMP = CGAL::Polygon_mesh_processing; - -#include - -#include #include -#include // for std::move + +using K = CGAL::Exact_predicates_inexact_constructions_kernel; + +using Point = K::Point_3; + +using Convex_hull = std::pair, std::vector > >; +using Mesh = CGAL::Surface_mesh; +namespace PMP = CGAL::Polygon_mesh_processing; int main(int argc, char* argv[]) { const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); -/* - std::vector pc, pts; - - if (!CGAL::IO::read_points("surface_points.ply", std::back_inserter(pc))) - return 0; - - std::vector > indices; - convex_hull_3(pc.begin(), pc.end(), pts, indices);*/ Mesh mesh; if(!PMP::IO::read_polygon_mesh(filename, mesh)) @@ -43,9 +28,11 @@ int main(int argc, char* argv[]) return 1; } - std::vector convex_hulls; + std::vector convex_hulls; std::size_t n = PMP::approximate_convex_decomposition(mesh, 5, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.5).maximum_number_of_convex_hulls(7)); + std::cout << convex_hulls.size() << std::endl; + return 0; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index fa75c167948..a5095cf9c32 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -505,6 +505,7 @@ struct Convex_hull { } Convex_hull(const Convex_hull& o) { + exit(4); bbox = o.bbox; voxel_volume = o.voxel_volume; volume = o.volume; @@ -514,6 +515,7 @@ struct Convex_hull { } Convex_hull& operator= (const Convex_hull& o) { + exit(3); bbox = o.bbox; voxel_volume = o.voxel_volume; volume = o.volume; @@ -865,8 +867,6 @@ void merge(std::vector>& candidates, std::vector ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; }; - //queue.reserve(max_merge_candidates); - for (std::size_t i : keep) { const Convex_hull& ci = hulls[i]; for (std::size_t j : keep) { @@ -875,7 +875,6 @@ void merge(std::vector>& candidates, std::vector const Convex_hull& cj = hulls[j]; if (CGAL::do_intersect(ci.bbox, cj.bbox)) { #ifdef CGAL_LINKED_WITH_TBB - // Move this into a task list for later parallelization? todo.emplace_back(Merged_candidate(i, j)); #else Merged_candidate m(i, j); @@ -943,7 +942,6 @@ void merge(std::vector>& candidates, std::vector const Convex_hull& ci = hulls[id]; if (CGAL::do_intersect(ci.bbox, cj.bbox)) { #ifdef CGAL_LINKED_WITH_TBB - // Move this into a task list for later parallelization? todo.emplace_back(Merged_candidate(id, m.ch)); #else Merged_candidate merged(id, m.ch); @@ -973,8 +971,6 @@ void merge(std::vector>& candidates, std::vector keep.insert(m.ch); - std::cout << keep.size() << std::endl; - // parallel for #ifdef CGAL_LINKED_WITH_TBB tbb::parallel_for_each(todo, do_merge); @@ -1072,11 +1068,14 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, std::size_t std::cout << memory.virtual_size() << " virtual " << memory.resident_size() << " resident memory allocated in total" << std::endl; - for (std::size_t i = 0;i Date: Fri, 13 Jun 2025 16:26:59 +0200 Subject: [PATCH 03/49] updated dependencies Surface_mesh will be removed later on --- .../package_info/Polygon_mesh_processing/dependencies | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies b/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies index 31ee94b381a..a95eb459dc5 100644 --- a/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies +++ b/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies @@ -6,6 +6,8 @@ Box_intersection_d CGAL_Core Cartesian_kernel Circulator +Convex_hull_2 +Convex_hull_3 Distance_2 Distance_3 Filtered_kernel @@ -32,6 +34,7 @@ Spatial_searching Spatial_sorting Stream_support Subdivision_method_3 +Surface_mesh TDS_2 TDS_3 Triangulation_2 From 06b31cc01fe3e241d4be20d2b9471d04d3c6892f Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Wed, 25 Jun 2025 17:33:07 +0200 Subject: [PATCH 04/49] fixed bugs in the splitting method added rayshooting to fill inside and outside of unclosed meshes tweaking the find best splitting plane method --- .../approximate_convex_decomposition.cpp | 5 +- .../approximate_convex_decomposition.h | 497 +++++++++++++++--- .../internal/parameters_interface.h | 1 + 3 files changed, 431 insertions(+), 72 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index e100dda3170..1fcd27452cb 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -17,9 +17,12 @@ using Convex_hull = std::pair, std::vector; namespace PMP = CGAL::Polygon_mesh_processing; +std::size_t cidx = 0; + int main(int argc, char* argv[]) { const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); + std::cout << filename << std::endl; Mesh mesh; if(!PMP::IO::read_polygon_mesh(filename, mesh)) @@ -30,7 +33,7 @@ int main(int argc, char* argv[]) std::vector convex_hulls; - std::size_t n = PMP::approximate_convex_decomposition(mesh, 5, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.5).maximum_number_of_convex_hulls(7)); + std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.001).maximum_number_of_convex_hulls(8).find_best_splitter(1)); std::cout << convex_hulls.size() << std::endl; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index a5095cf9c32..1227874cdd1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -17,11 +17,14 @@ #include +#include + #include #include #include #include +#include #include #include #include @@ -30,7 +33,9 @@ #include #include -#undef CGAL_LINKED_WITH_TBB +#include +#include +#include #ifdef CGAL_LINKED_WITH_TBB #include @@ -46,6 +51,8 @@ #include +extern std::size_t cidx; + struct hash { std::size_t operator()(const std::array &a) const { return (((a[0] * 1193) ^ a[1]) * 1212) ^ a[2]; @@ -113,6 +120,55 @@ void export_grid(const std::string& filename, const Bbox_3& bb, std::vector +void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size, Filter &filter) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + std::ofstream stream(filename); + + std::size_t count = 0; + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) + if (filter(vox(x, y, z))) count++; + + stream << + "ply" << std::endl << + "format ascii 1.0" << std::endl << + "element vertex " << count << std::endl << + "property double x" << std::endl << + "property double y" << std::endl << + "property double z" << std::endl << + "property uchar red" << std::endl << + "property uchar green" << std::endl << + "property uchar blue" << std::endl << + "end_header" << std::endl; + + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (!filter(vox(x, y, z))) continue; + stream << (bb.xmin() + (x + 0.5) * voxel_size) << " " << (bb.ymin() + (y + 0.5) * voxel_size) << " " << (bb.zmin() + (z + 0.5) * voxel_size) << " "; + switch (vox(x, y, z)) { + case INSIDE: + stream << "175 175 100" << std::endl; + break; + case OUTSIDE: + stream << "125 125 175" << std::endl; + break; + case SURFACE: + stream << "200 100 100" << std::endl; + break; + default: + stream << "0 0 0" << std::endl; + break; + } + } + + stream.close(); +} + void export_voxels(const std::string& filename, const Bbox_3& bb, std::vector& voxels, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; @@ -175,6 +231,17 @@ void export_points(const std::string& filename, Range& points) { stream.close(); } +template +Box box_union(const Box& a, const Box& b) { + using FT = decltype(a.xmin()); + return Box( + (std::min)(a.xmin(), b.xmin()), + (std::min)(a.ymin(), b.ymin()), + (std::min)(a.zmin(), b.zmin()), + (std::max)(a.xmax(), b.xmax()), + (std::max)(a.ymax(), b.ymax()), + (std::max)(a.zmax(), b.zmax())); +} template std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t number_of_voxels) { @@ -198,6 +265,19 @@ std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t numb return { Vec3_uint{static_cast(bb.x_span() / voxel_size + 0.5), static_cast(bb.y_span() / voxel_size + 0.5), static_cast(bb.z_span() / voxel_size + 0.5)}, voxel_size}; } +template +const typename GeomTraits::Point_3 &point(typename boost::graph_traits::face_descriptor fd, + const PolygonMesh& pmesh, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + return get(vpm, target(halfedge(fd, pmesh), pmesh)); +} template Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_traits::face_descriptor fd, const Bbox_3& bb, const FT& voxel_size) { @@ -213,15 +293,15 @@ Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_trai }); } -template -Bbox_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const FT& voxel_size) { +template +Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { return Bbox_3( bb.xmin() + voxel[0] * voxel_size, bb.ymin() + voxel[1] * voxel_size, bb.zmin() + voxel[2] * voxel_size, - bb.xmax() + (voxel[0] + 1) * voxel_size, - bb.ymax() + (voxel[1] + 1) * voxel_size, - bb.zmax() + (voxel[2] + 1) * voxel_size + bb.xmin() + (voxel[0] + 1) * voxel_size, + bb.ymin() + (voxel[1] + 1) * voxel_size, + bb.zmin() + (voxel[2] + 1) * voxel_size ); } @@ -483,11 +563,72 @@ void naive_floodfill(std::vector& grid, const Vec3_uint& grid_size) { } } + +template +void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph &mesh) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + using face_descriptor = typename boost::graph_traits::face_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + using Ray_3 = typename GeomTraits::Ray_3; + + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits_3; + using Tree = CGAL::AABB_tree; + using Primitive_id = typename Tree::Primitive_id; + using Ray_intersection = std::optional::Type>; + + Tree tree(faces(mesh).first, faces(mesh).second, mesh); + + std::array dirs = {Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1}}; + + for (std::size_t x = 0;x(&(intersection->first))) { + face_descriptor fd = intersection->second; + Vector_3 n = compute_face_normal(fd, mesh); + if (dirs[i] * n > 0) { + inside++; + } + else { + outside++; + } + } + } + } + + if (inside >= 3 && outside == 0) { + vox(x, y, z) = INSIDE; + } + else + vox(x, y, z) = OUTSIDE; + } +} + + template struct Convex_hull { using FT = typename GeomTraits::FT; using Point_3 = typename GeomTraits::Point_3; - Bbox_3 bbox; + Iso_cuboid_3 bbox; FT voxel_volume; FT volume; FT volume_error; @@ -542,6 +683,7 @@ template struct Candidate { using FT = typename GeomTraits::FT; using Point_3 = typename GeomTraits::Point_3; + std::size_t index; std::vector surface; std::vector new_surface; std::vector inside; @@ -549,8 +691,8 @@ struct Candidate { Bbox_uint bbox; Convex_hull ch; - Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {} - Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) {} + Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {index = cidx++;} + Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) { index = cidx++; } }; template @@ -599,15 +741,46 @@ void convex_hull(const PointRange& pts, std::vector &hull_points, std:: } } +void enlarge(Bbox_uint& bbox, const Vec3_uint& v) { + bbox.lower[0] = (std::min)(bbox.lower[0], v[0]); + bbox.lower[1] = (std::min)(bbox.lower[1], v[1]); + bbox.lower[2] = (std::min)(bbox.lower[2], v[2]); + bbox.upper[0] = (std::max)(bbox.upper[0], v[0]); + bbox.upper[1] = (std::max)(bbox.upper[1], v[1]); + bbox.upper[2] = (std::max)(bbox.upper[2], v[2]); +} + template void compute_candidate(Candidate &c, const Bbox_3& bb, typename GeomTraits::FT voxel_size) { using Point_3 = typename GeomTraits::Point_3; using FT = typename GeomTraits::FT; + c.bbox.lower = c.bbox.upper = c.surface[0]; + + // update bounding box std::unordered_set voxel_points; // Is it more efficient than using std::vector with plenty of duplicates? for (const Vec3_uint& v : c.surface) { + enlarge(c.bbox, v); + FT xmin = bb.xmin() + v[0] * voxel_size; + FT ymin = bb.ymin() + v[1] * voxel_size; + FT zmin = bb.zmin() + v[2] * voxel_size; + FT xmax = bb.xmin() + (v[0] + 1) * voxel_size; + FT ymax = bb.ymin() + (v[1] + 1) * voxel_size; + FT zmax = bb.zmin() + (v[2] + 1) * voxel_size; + voxel_points.insert(Point_3(xmin, ymin, zmin)); + voxel_points.insert(Point_3(xmin, ymax, zmin)); + voxel_points.insert(Point_3(xmin, ymin, zmax)); + voxel_points.insert(Point_3(xmin, ymax, zmax)); + voxel_points.insert(Point_3(xmax, ymin, zmin)); + voxel_points.insert(Point_3(xmax, ymax, zmin)); + voxel_points.insert(Point_3(xmax, ymin, zmax)); + voxel_points.insert(Point_3(xmax, ymax, zmax)); + } + + for (const Vec3_uint& v : c.new_surface) { + enlarge(c.bbox, v); FT xmin = bb.xmin() + v[0] * voxel_size; FT ymin = bb.ymin() + v[1] * voxel_size; FT zmin = bb.zmin() + v[2] * voxel_size; @@ -630,7 +803,7 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom c.ch.volume = volume(c.ch.points, c.ch.indices); - assert(c.ch.volume > 0); + CGAL_assertion(c.ch.volume > 0); c.ch.voxel_volume = (voxel_size * voxel_size * voxel_size) * (c.inside.size() + c.surface.size() + c.new_surface.size()); c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; @@ -644,26 +817,37 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr for (const typename boost::graph_traits::face_descriptor fd : faces(mesh)) { Bbox_uint face_bb = grid_bbox_face(mesh, fd, bb, voxel_size); - assert(face_bb.lower[0] <= face_bb.upper[0]); - assert(face_bb.lower[1] <= face_bb.upper[1]); - assert(face_bb.lower[2] <= face_bb.upper[2]); - assert(face_bb.upper[0] < grid_size[0]); - assert(face_bb.upper[1] < grid_size[1]); - assert(face_bb.upper[2] < grid_size[2]); + CGAL_assertion(face_bb.lower[0] <= face_bb.upper[0]); + CGAL_assertion(face_bb.lower[1] <= face_bb.upper[1]); + CGAL_assertion(face_bb.lower[2] <= face_bb.upper[2]); + CGAL_assertion(face_bb.upper[0] < grid_size[0]); + CGAL_assertion(face_bb.upper[1] < grid_size[1]); + CGAL_assertion(face_bb.upper[2] < grid_size[2]); for (unsigned int x = face_bb.lower[0]; x <= face_bb.upper[0]; x++) for (unsigned int y = face_bb.lower[1]; y <= face_bb.upper[1]; y++) - for (unsigned int z = face_bb.lower[2]; z <= face_bb.upper[2]; z++) - if (do_intersect(triangle(fd, mesh), bbox_voxel({x, y, z}, bb, voxel_size))) + for (unsigned int z = face_bb.lower[2]; z <= face_bb.upper[2]; z++) { + Iso_cuboid_3 box = bbox_voxel({ x, y, z }, bb, voxel_size); + const typename GeomTraits::Point_3 &p = point(fd, mesh); + if (do_intersect(triangle(fd, mesh), box) || box.has_on_bounded_side(p)) vox(x, y, z) = Grid_cell::SURFACE; + } } //export_grid("before.ply", bb, grid, grid_size, voxel_size); + auto filterS = [](const int8_t& l) -> bool {return l == SURFACE; }; + auto filterO = [](const int8_t& l) -> bool {return l == OUTSIDE; }; + auto filterI = [](const int8_t& l) -> bool {return l == INSIDE; }; + //export_grid("before_surface_ray.ply", bb, grid, grid_size, voxel_size, filterS); + //export_grid("before_outside.ply", bb, grid, grid_size, voxel_size, filterO); // For now, only do floodfill //label_floodfill(grid, grid_size); - naive_floodfill(grid, grid_size); + //naive_floodfill(grid, grid_size); + rayshooting_fill(grid, grid_size, bb, voxel_size, mesh); + // export_grid("after_inside_ray.ply", bb, grid, grid_size, voxel_size, filterI); + //export_grid("after_outside_ray.ply", bb, grid, grid_size, voxel_size, filterO); - c.bbox.upper = grid_size; + c.bbox.upper = {grid_size[0] - 1, grid_size[1] - 1, grid_size[2] - 1}; for (unsigned int x = 0; x < grid_size[0]; x++) for (unsigned int y = 0; y < grid_size[1]; y++) @@ -673,9 +857,11 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr else if (vox(x, y, z) == SURFACE) c.surface.push_back({ x, y, z }); - check_grid(grid, grid_size); + //std::cout << "remove check grid and export" << std::endl; + //check_grid(grid, grid_size); - //export_grid("after_fill.ply", bb, grid, grid_size, voxel_size); + //export_grid("after_inside.ply", bb, grid, grid_size, voxel_size, filterI); + //export_grid("after_outside.ply", bb, grid, grid_size, voxel_size, filterO); } template @@ -690,41 +876,44 @@ void split(std::vector > &candidates, Candidate upper(c.depth + 1, c.bbox); Candidate lower(c.depth + 1, c.bbox); - upper.bbox.lower[axis] = location; - lower.bbox.upper[axis] = location - 1; + CGAL_assertion(c.bbox.lower[axis] < location); + CGAL_assertion(c.bbox.upper[axis] > location); + + upper.bbox.lower[axis] = location + 1; + lower.bbox.upper[axis] = location; for (const Vec3_uint& v : c.surface) { - assert(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); - assert(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); - assert(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); - if (location <= v[axis]) + CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location < v[axis]) upper.surface.push_back(v); else lower.surface.push_back(v); } for (const Vec3_uint& v : c.new_surface) { - assert(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); - assert(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); - assert(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); - if (location <= v[axis]) + CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location < v[axis]) upper.new_surface.push_back(v); else lower.new_surface.push_back(v); } for (const Vec3_uint& v : c.inside) { - assert(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); - assert(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); - assert(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); - if (location <= v[axis]) { - if (upper.bbox.lower[axis] == v[axis]) + CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location < v[axis]) { + if ((location + 1) == v[axis]) upper.new_surface.push_back(v); else upper.inside.push_back(v); } else { - if (lower.bbox.upper[axis] == v[axis]) + if (location == v[axis]) lower.new_surface.push_back(v); else lower.inside.push_back(v); @@ -743,16 +932,172 @@ void split(std::vector > &candidates, Candidate& grid, const Vec3_uint& grid_size) { + const auto vox = [&grid, &grid_size](const Vec3_uint &v) -> int8_t& { + return grid[v[2] + (v[1] * grid_size[2]) + (v[0] * grid_size[1] * grid_size[2])]; + }; + std::size_t i; + for (i = s[axis];i s[axis]; j--) { + Vec3_uint v = s; + v[axis] = j; + if (vox(v) != OUTSIDE) + break; + } + + std::size_t res = (i - s[axis]) + (e[axis] - j); + if(res >= grid_size[axis]) + std::cout << "violation!" << std::endl; + return (i - s[axis]) + (e[axis] - j); +} + +void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& location, const Bbox_uint &bbox, std::vector& grid, const Vec3_uint& grid_size) { + std::size_t length = bbox.upper[axis] - bbox.lower[axis] + 1; + + CGAL_assertion(length >= 8); + + std::size_t idx0, idx1, idx2; + + switch(axis) { + case 0: + idx0 = 1; + idx1 = 2; + idx2 = 0; + break; + case 1: + idx0 = 0; + idx1 = 2; + idx2 = 1; + break; + case 2: + idx0 = 0; + idx1 = 1; + idx2 = 2; + break; + default: + CGAL_assertion(false); + } + + std::vector diam(length, 0), diam2(length, 0); + + for (std::size_t i = bbox.lower[idx2]; i <= bbox.upper[idx2]; i++) { + for (std::size_t j = bbox.lower[idx0]; j <= bbox.upper[idx0]; j++) { + Vec3_uint s, e; + s[idx2] = i; + s[idx1] = bbox.lower[idx1]; + s[idx0] = j; + e[idx2] = i; + e[idx1] = bbox.upper[idx1]; + e[idx0] = j; + diam[i - bbox.lower[idx2]] += concavity(s, e, idx1, grid, grid_size); + } + } + + for (std::size_t i = bbox.lower[idx2]; i <= bbox.upper[idx2]; i++) { + for (std::size_t j = bbox.lower[idx1]; j <= bbox.upper[idx1]; j++) { + Vec3_uint s, e; + s[idx0] = bbox.lower[idx0]; + s[idx2] = i; + s[idx1] = j; + e[idx0] = bbox.upper[idx0]; + e[idx2] = i; + e[idx1] = j; + diam2[i - bbox.lower[idx2]] += concavity(s, e, idx0, grid, grid_size); + } + } + + // Skip initial border + std::size_t border = (length / 10) + 0.5; + std::size_t pos1, end1 = length; + int grad = diam[0] - diam[1]; + for (pos1 = 2; pos1 < border; pos1++) { + int grad1 = diam[pos1 - 1] - diam[pos1]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad1 > 0 && grad1 > (grad>>1))) + break; + if (grad < grad1) + grad = grad1; + } + + grad = diam[length - 1] - diam[length - 2]; + for (end1 = length - 3; end1 > (length - border - 1); end1--) { + int grad1 = diam[end1 + 1] - diam[end1]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad1 > 0 && grad1 > (grad >> 1))) + break; + if (grad < grad1) + grad = grad1; + } + + std::size_t pos2, end2 = length; + grad = diam2[0] - diam2[1]; + for (pos2 = 2; pos2 < border; pos2++) { + int grad2 = diam[pos2 - 1] - diam[pos2]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad2 > 0 && grad2 > (grad >> 1))) + break; + if (grad < grad2) + grad = grad2; + } + + grad = diam2[length - 1] - diam2[length - 2]; + for (end2 = length - 3; end2 > (length - border - 1); end2--) { + int grad2 = diam[end2 + 1] - diam[end2]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad2 > 0 && grad2 > (grad >> 1))) + break; + if (grad < grad2) + grad = grad2; + } + + std::size_t conc1 = abs(diam[pos1 + 1] - diam[pos1]); + std::size_t conc2 = abs(diam2[pos2 + 1] - diam2[pos2]); + + for (std::size_t i = pos1;i conc1) { + pos1 = i - 1; + conc1 = abs(diam[i] - diam[i - 1]); + } + + for (std::size_t i = pos2; i < end2; i++) + if (abs(diam2[i] - diam2[i - 1]) > conc2) { + pos2 = i - 1; + conc2 = abs(diam2[i] - diam2[i - 1]); + } + + if (conc2 > conc1) + pos1 = pos2; + + if (pos1 < 2 || (length - 3) < pos1) + location = (bbox.upper[axis] + bbox.lower[axis]) / 2; + else + location = ((conc1 > conc2) ? pos1 : pos2) + bbox.lower[axis]; +} + template void choose_splitting_plane(Candidate& c, unsigned int &axis, unsigned int &location, std::vector& grid, const Vec3_uint& grid_size, const NamedParameters& np) { //Just split the voxel bbox along 'axis' after voxel index 'location' + const bool find_best_plane = parameters::choose_parameter(parameters::get_parameter(np, internal_np::find_best_splitter), true); const std::array span = {c.bbox.upper[0] - c.bbox.lower[0], c.bbox.upper[1] - c.bbox.lower[1], c.bbox.upper[2] - c.bbox.lower[2]}; // Split largest axis axis = (span[0] >= span[1]) ? 0 : 1; axis = (span[axis] >= span[2]) ? axis : 2; - location = (c.bbox.upper[axis] + c.bbox.lower[axis]) / 2; + if (span[axis] >= 8 && find_best_plane) + choose_splitting_location_by_concavity(axis, location, c.bbox, grid, grid_size); + else + location = (c.bbox.upper[axis] + c.bbox.lower[axis]) / 2; } template @@ -763,7 +1108,7 @@ bool finished(Candidate &c, const NamedParameters& np) { if (c.ch.volume_error <= max_error) return true; - if (c.depth >= max_depth) + if (c.depth > max_depth) return true; return false; @@ -775,21 +1120,37 @@ void recurse(std::vector>& candidates, std::vector const FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + static std::size_t step = 0; + std::vector> final_candidates; while (!candidates.empty()) { + std::cout << step++ << ": " << candidates.size() << std::endl; std::vector> former_candidates = std::move(candidates); for (Candidate& c : former_candidates) { - // check loop conditions here? + if (finished(c, np)) { - c.ch.bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); - c.ch.bbox.scale(1.1); // Enlarge bounding boxes by a small factor for the following merge + CGAL::Bbox_3 bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); + bbox.scale(1.1); + c.ch.bbox = bbox; final_candidates.push_back(std::move(c)); continue; } unsigned int axis = 0, location = 0; choose_splitting_plane(c, axis, location, grid, grid_size, np); + //std::cout << c.index << " " << c.ch.volume_error << " split " << axis << "a " << location << " (" << c.bbox.lower[0] << "," << c.bbox.lower[1] << "," << c.bbox.lower[2] << "," << c.bbox.upper[0] << "," << c.bbox.upper[1] << "," << c.bbox.upper[2] << ")" << std::endl; split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); + +// if (!candidates.empty()) { +// auto& b = candidates[candidates.size() - 1]; +// std::cout << b.index << " (" << b.bbox.lower[0] << "," << b.bbox.lower[1] << "," << b.bbox.lower[2] << "," << b.bbox.upper[0] << "," << b.bbox.upper[1] << "," << b.bbox.upper[2] << ")" << b.ch.volume_error << std::endl; +// CGAL::IO::write_polygon_soup(std::to_string(b.index) + " " + std::to_string(c.index) + ".off", b.ch.points, b.ch.indices); +// } +// if (candidates.size() >= 2) { +// auto& a = candidates[candidates.size() - 2]; +// std::cout << a.index << " (" << a.bbox.lower[0] << "," << a.bbox.lower[1] << "," << a.bbox.lower[2] << "," << a.bbox.upper[0] << "," << a.bbox.upper[1] << "," << a.bbox.upper[2] << ")" << a.ch.volume_error << std::endl; +// CGAL::IO::write_polygon_soup(std::to_string(a.index) + " " + std::to_string(c.index) + ".off", a.ch.points, a.ch.indices); +// } } } @@ -856,7 +1217,7 @@ void merge(std::vector>& candidates, std::vector m.ch = num_hulls++; Convex_hull& ch = hulls[m.ch]; #endif - ch.bbox = ci.bbox + cj.bbox; + ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); @@ -881,7 +1242,7 @@ void merge(std::vector>& candidates, std::vector m.ch = num_hulls++; Convex_hull& ch = hulls[m.ch]; - ch.bbox = ci.bbox + cj.bbox; + ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); @@ -895,7 +1256,7 @@ void merge(std::vector>& candidates, std::vector } else { Merged_candidate m(i, j); - Bbox_3 bbox = ci.bbox + cj.bbox; + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); m.ch = -1; m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; queue.push(std::move(m)); @@ -931,11 +1292,17 @@ void merge(std::vector>& candidates, std::vector if (m.ch == -1) do_merge(m); - hulls.erase(ch_a); keep.erase(m.ch_a); - hulls.erase(ch_b); keep.erase(m.ch_b); +#ifdef CGAL_LINKED_WITH_TBB + hulls.unsafe_erase(ch_a); + hulls.unsafe_erase(ch_b); +#else + hulls.erase(ch_a); + hulls.erase(ch_b); +#endif + const Convex_hull& cj = hulls[m.ch]; for (std::size_t id : keep) { @@ -948,7 +1315,7 @@ void merge(std::vector>& candidates, std::vector merged.ch = num_hulls++; Convex_hull& ch = hulls[merged.ch]; - ch.bbox = ci.bbox + cj.bbox; + ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); @@ -962,7 +1329,7 @@ void merge(std::vector>& candidates, std::vector } else { Merged_candidate merged(id, m.ch); - Bbox_3 bbox = ci.bbox + cj.bbox; + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); merged.ch = -1; merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; queue.push(std::move(merged)); @@ -989,7 +1356,7 @@ void merge(std::vector>& candidates, std::vector } template -std::size_t approximate_convex_decomposition(const FaceGraph& mesh, std::size_t number_of_convex_hulls, OutputIterator out, const NamedParameters& np = parameters::default_values()) { +std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterator out, const NamedParameters& np = parameters::default_values()) { CGAL::Memory_sizer memory; std::size_t virt_mem = memory.virtual_size(); std::size_t res_mem = memory.resident_size(); @@ -1001,31 +1368,16 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, std::size_t Vertex_point_map vpm = parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, mesh)); using FT = typename Geom_traits::FT; - - // A single voxel grid should be sufficient - // Why are they creating a voxel mesh and an AABB tree? - // - just tracing the voxel grid should be sufficient? especially as the voxel grid - // - tracing a voxel grid is very memory intensive, especially when it is sparse - // Do they actually have a grid and fill it or only container of voxels? - //const std::size_t voxel_size = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 50); - const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 1000000); + const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 1200000); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); - //std::vector::face_descriptor> degenerate_faces; - //CGAL::Polygon_mesh_processing::degenerate_faces(mesh, std::back_inserter(degenerate_faces)); - - std::cout << (CGAL::is_closed(mesh) ? "input mesh is closed" : "input mesh is not closed") << std::endl; - //std::cout << degenerate_faces.size() << " degenerate faces" << std::endl; Bbox_3 bb = bbox(mesh); - //FT voxel_size; - const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); - std::cout << "grid_size: " << grid_size[0] << " " << grid_size[1] << " " << grid_size[2] << std::endl; + const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); timer.start(); - // if floodfill take INSIDE, otherwise UNKNOWN std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); std::vector> candidates(1); @@ -1047,6 +1399,9 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, std::size_t std::size_t virt_mem2 = memory.virtual_size(); std::size_t res_mem2 = memory.resident_size(); + std::cout << (virt_mem2 - virt_mem) << " additional virtual memory allocated" << std::endl; + std::cout << (res_mem2 - res_mem) << " additional resident memory occupied\n" << std::endl; + std::vector> hulls; for (internal::Candidate &c : candidates) hulls.emplace_back(std::move(c.ch)); @@ -1068,9 +1423,9 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, std::size_t std::cout << memory.virtual_size() << " virtual " << memory.resident_size() << " resident memory allocated in total" << std::endl; -// for (std::size_t i = 0; i < hulls.size(); i++) { -// CGAL::IO::write_polygon_soup(std::to_string(i) + "-e" + std::to_string(hulls[i].volume_error) + ".off", hulls[i].points, hulls[i].indices); -// } + for (std::size_t i = 0; i < hulls.size(); i++) { + CGAL::IO::write_polygon_soup(std::to_string(i) + "-e" + std::to_string(hulls[i].volume_error) + ".off", hulls[i].points, hulls[i].indices); + } for (std::size_t i = 0; i < hulls.size(); i++) *out++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index 2e400b766e6..266d3c1bab2 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -182,6 +182,7 @@ CGAL_add_named_parameter(voxel_size_t, voxel_size, voxel_size) CGAL_add_named_parameter(maximum_depth_t, maximum_depth, maximum_depth) CGAL_add_named_parameter(volume_error_t, volume_error, volume_error) CGAL_add_named_parameter(maximum_number_of_convex_hulls_t, maximum_number_of_convex_hulls, maximum_number_of_convex_hulls) +CGAL_add_named_parameter(find_best_splitter_t, find_best_splitter, find_best_splitter) // List of named parameters that we use in the package 'Surface Mesh Simplification' From 0b6c79e64f37c9ca22387e044bd7c73b5f3f1b45 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 26 Jun 2025 16:01:52 +0200 Subject: [PATCH 05/49] parallelization of ray shooting and hierarchical recursion --- .../approximate_convex_decomposition.h | 112 +++++++++++------- 1 file changed, 70 insertions(+), 42 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 1227874cdd1..7388807f954 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -214,15 +214,7 @@ void export_points(const std::string& filename, const Bbox_3& bb, std::vector void export_points(const std::string& filename, Range& points) { std::ofstream stream(filename); - - stream << - "ply" << std::endl << - "format ascii 1.0" << std::endl << - "element vertex " << points.size() << std::endl << - "property double x" << std::endl << - "property double y" << std::endl << - "property double z" << std::endl << - "end_header" << std::endl; + stream << std::setprecision(18); for (const auto& p : points) { stream << p.x() << " " << p.y() << " " << p.z() << std::endl; @@ -586,7 +578,12 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con std::array dirs = {Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1}}; +#ifdef CGAL_LINKED_WITH_TBB + tbb::parallel_for(std::size_t(0), std::size_t(grid_size[0]), [&](const std::size_t x) +#else for (std::size_t x = 0;x& grid, const Vec3_uint& grid_size, con else vox(x, y, z) = OUTSIDE; } + } +#ifdef CGAL_LINKED_WITH_TBB + ); +#endif } @@ -719,10 +720,11 @@ void convex_hull(const PointRange& pts, std::vector &hull_points, std:: Mesh m; convex_hull_3(pts.begin(), pts.end(), m); - //std::vector pts; - //std::vector > indices; - //convex_hull_3(voxel_points.begin(), voxel_points.end(), pts, indices);// Why does this not work? - //convex_hull_3(voxel_points.begin(), voxel_points.end(), c.hull_points, c.hull_indices);// Why does this not work? +// if (pts.size() < 20) { +// export_points("convex_hull" + std::to_string(cnt) + ".xyz", pts); +// std::cout << pts.size() << " " << cnt++ << std::endl; +// convex_hull_3(pts.begin(), pts.end(), hull_points, hull_indices);// needs bugfix in convex_hull_3 for indexed triangle list +// } hull_points.resize(m.number_of_vertices()); hull_indices.resize(m.number_of_faces()); @@ -815,6 +817,9 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; }; + CGAL::Timer timer; + timer.start(); + for (const typename boost::graph_traits::face_descriptor fd : faces(mesh)) { Bbox_uint face_bb = grid_bbox_face(mesh, fd, bb, voxel_size); CGAL_assertion(face_bb.lower[0] <= face_bb.upper[0]); @@ -832,6 +837,7 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr vox(x, y, z) = Grid_cell::SURFACE; } } + double fill = timer.time(); //export_grid("before.ply", bb, grid, grid_size, voxel_size); auto filterS = [](const int8_t& l) -> bool {return l == SURFACE; }; @@ -842,9 +848,18 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr // For now, only do floodfill //label_floodfill(grid, grid_size); - //naive_floodfill(grid, grid_size); - rayshooting_fill(grid, grid_size, bb, voxel_size, mesh); - // export_grid("after_inside_ray.ply", bb, grid, grid_size, voxel_size, filterI); + double before_shooting = timer.time(); + if (CGAL::is_closed(mesh)) { + naive_floodfill(grid, grid_size); + //check_grid(grid, grid_size); + } + else + rayshooting_fill(grid, grid_size, bb, voxel_size, mesh); + double after_shooting = timer.time(); + + std::cout << "filling: " << fill << std::endl; + std::cout << "shooting: " << (after_shooting - before_shooting) << std::endl; + //export_grid("after_inside_ray.ply", bb, grid, grid_size, voxel_size, filterI); //export_grid("after_outside_ray.ply", bb, grid, grid_size, voxel_size, filterO); c.bbox.upper = {grid_size[0] - 1, grid_size[1] - 1, grid_size[2] - 1}; @@ -855,10 +870,9 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr if (vox(x, y, z) == INSIDE) c.inside.push_back({x, y, z}); else if (vox(x, y, z) == SURFACE) - c.surface.push_back({ x, y, z }); + c.surface.push_back({x, y, z}); //std::cout << "remove check grid and export" << std::endl; - //check_grid(grid, grid_size); //export_grid("after_inside.ply", bb, grid, grid_size, voxel_size, filterI); //export_grid("after_outside.ply", bb, grid, grid_size, voxel_size, filterO); @@ -921,15 +935,11 @@ void split(std::vector > &candidates, Candidate& grid, const Vec3_uint& grid_size) { @@ -1105,10 +1115,19 @@ bool finished(Candidate &c, const NamedParameters& np) { const typename GeomTraits::FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + if (c.depth > max_depth) + return true; + if (c.ch.volume_error <= max_error) return true; - if (c.depth > max_depth) + std::size_t max_span = 0; + for (std::size_t i = 0;i<3;i++) { + const std::size_t span = c.bbox.upper[i] - c.bbox.lower[i]; + max_span = (std::max)(max_span, span); + } + + if (max_span <= 1) return true; return false; @@ -1124,8 +1143,12 @@ void recurse(std::vector>& candidates, std::vector std::vector> final_candidates; + double choose = 0, spli = 0; + while (!candidates.empty()) { - std::cout << step++ << ": " << candidates.size() << std::endl; + //std::cout << step++ << ": " << candidates.size() << "\t"; + CGAL::Timer timer; + timer.start(); std::vector> former_candidates = std::move(candidates); for (Candidate& c : former_candidates) { @@ -1137,9 +1160,13 @@ void recurse(std::vector>& candidates, std::vector continue; } unsigned int axis = 0, location = 0; + double b = timer.time(); choose_splitting_plane(c, axis, location, grid, grid_size, np); + double a = timer.time(); + choose += a - b; //std::cout << c.index << " " << c.ch.volume_error << " split " << axis << "a " << location << " (" << c.bbox.lower[0] << "," << c.bbox.lower[1] << "," << c.bbox.lower[2] << "," << c.bbox.upper[0] << "," << c.bbox.upper[1] << "," << c.bbox.upper[2] << ")" << std::endl; split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); + spli += timer.time() - a; // if (!candidates.empty()) { // auto& b = candidates[candidates.size() - 1]; @@ -1152,8 +1179,21 @@ void recurse(std::vector>& candidates, std::vector // CGAL::IO::write_polygon_soup(std::to_string(a.index) + " " + std::to_string(c.index) + ".off", a.ch.points, a.ch.indices); // } } +#ifdef CGAL_LINKED_WITH_TBB + tbb::parallel_for_each(candidates, [&](Candidate& c) { + compute_candidate(c, bbox, voxel_size); + }); +#else + for (Candidate& c : candidates) + compute_candidate(c, bbox, voxel_size); +#endif + + //std::cout << timer.time() << std::endl; } + std::cout << "find_plane: " << choose << std::endl; + std::cout << "split: " << spli << std::endl; + std::swap(candidates, final_candidates); } @@ -1368,40 +1408,31 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat Vertex_point_map vpm = parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, mesh)); using FT = typename Geom_traits::FT; - const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 1200000); + const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 1000000); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); - Bbox_3 bb = bbox(mesh); - const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); timer.start(); std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); + std::cout << "#voxels " << grid.size() << std::endl; + std::cout << "grid_size: " << grid_size[0] << " " << grid_size[1] << " " << grid_size[2] << std::endl; std::vector> candidates(1); - init(candidates[0], mesh, grid, bb, grid_size, voxel_size); const FT hull_volume = candidates[0].ch.volume; double after_init = timer.time(); - recurse(candidates, grid, grid_size, bb, voxel_size, np); - double after_recurse = timer.time(); // for (std::size_t i = 0; i < candidates.size(); i++) { // CGAL::IO::write_polygon_soup(std::to_string(max_depth) + "-" + std::to_string(i) + "-d" + std::to_string(candidates[i].depth) + "-e" + std::to_string(candidates[i].ch.volume_error) + ".off", candidates[i].ch.points, candidates[i].ch.indices); // } - std::size_t virt_mem2 = memory.virtual_size(); - std::size_t res_mem2 = memory.resident_size(); - - std::cout << (virt_mem2 - virt_mem) << " additional virtual memory allocated" << std::endl; - std::cout << (res_mem2 - res_mem) << " additional resident memory occupied\n" << std::endl; - std::vector> hulls; for (internal::Candidate &c : candidates) hulls.emplace_back(std::move(c.ch)); @@ -1413,11 +1444,8 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat double after_merge = timer.time(); - std::cout << (virt_mem2 - virt_mem) << " additional virtual memory allocated" << std::endl; - std::cout << (res_mem2 - res_mem) << " additional resident memory occupied\n" << std::endl; - std::cout << "timing:" << std::endl; - std::cout << after_init << " initialization" << std::endl; + std::cout << (after_init) << " initialization" << std::endl; std::cout << (after_recurse - after_init) << " recurse" << std::endl; std::cout << (after_merge - after_recurse) << " merge" << std::endl; From 73deec506a9e0e7b430d3bba65fed259c20622bf Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Fri, 27 Jun 2025 09:32:48 +0200 Subject: [PATCH 06/49] moving saving into example --- .../approximate_convex_decomposition.cpp | 5 +++++ .../approximate_convex_decomposition.h | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index 1fcd27452cb..bbc36ad0fe0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -35,6 +35,11 @@ int main(int argc, char* argv[]) std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.001).maximum_number_of_convex_hulls(8).find_best_splitter(1)); + for (std::size_t i = 0;i Date: Fri, 27 Jun 2025 09:57:42 +0200 Subject: [PATCH 07/49] clean up --- .../approximate_convex_decomposition.cpp | 2 -- .../approximate_convex_decomposition.h | 16 ++++------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index bbc36ad0fe0..41f5e948ebf 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -17,8 +17,6 @@ using Convex_hull = std::pair, std::vector; namespace PMP = CGAL::Polygon_mesh_processing; -std::size_t cidx = 0; - int main(int argc, char* argv[]) { const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 8edb92ffccb..c2f7969c225 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -51,14 +51,6 @@ #include -extern std::size_t cidx; - -struct hash { - std::size_t operator()(const std::array &a) const { - return (((a[0] * 1193) ^ a[1]) * 1212) ^ a[2]; - } -}; - namespace CGAL { namespace Polygon_mesh_processing { namespace internal { @@ -73,10 +65,8 @@ struct Bbox_uint { enum Grid_cell : int8_t { OUTSIDE = -1, - UNKNOWN = 0, - INSIDE = 1, - SURFACE = 2, - UNSET = 3 + SURFACE = 0, + INSIDE = 1 }; void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { @@ -694,6 +684,8 @@ struct Candidate { Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {index = cidx++;} Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) { index = cidx++; } +private: + inline static std::size_t cidx = 0; }; template From d276fb1c1a484809eca61ff3abda86f89370d134 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 30 Jun 2025 16:48:58 +0200 Subject: [PATCH 08/49] cleanup adding documentation --- .../PackageDescription.txt | 4 + .../approximate_convex_decomposition.cpp | 5 +- .../approximate_convex_decomposition.h | 113 +++++++++++++----- .../internal/parameters_interface.h | 5 +- 4 files changed, 90 insertions(+), 37 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 700ec7fdac0..724fb7d85ea 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -66,6 +66,10 @@ /// Classes and functions that answer queries about a polygon mesh or its elements. /// \ingroup PkgPolygonMeshProcessingRef +/// \defgroup PMP_convex_decomposition_grp Approximate Convex Decomposition +/// Function to approximate a mesh by a number of convex hulls. +/// \ingroup PkgPolygonMeshProcessingRef + /// \defgroup PMP_IO_grp I/O Functions /// \ingroup PkgPolygonMeshProcessingRef diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index 41f5e948ebf..b8738a68349 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -23,15 +23,14 @@ int main(int argc, char* argv[]) std::cout << filename << std::endl; Mesh mesh; - if(!PMP::IO::read_polygon_mesh(filename, mesh)) - { + if(!PMP::IO::read_polygon_mesh(filename, mesh)) { std::cerr << "Invalid input." << std::endl; return 1; } std::vector convex_hulls; - std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.001).maximum_number_of_convex_hulls(8).find_best_splitter(1)); + std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.001).maximum_number_of_convex_hulls(8).split_at_concavity(1)); for (std::size_t i = 0;i& grid, const Vec3_uint& grid_size, con for (std::size_t i = 0; i < 6; i++) { Ray_intersection intersection = tree.first_intersection(Ray_3(c, dirs[i])); - if (intersection) - { + if (intersection) { // A segment intersection is not helpful as it means the triangle normal is orthogonal to the ray if (std::get_if(&(intersection->first))) { face_descriptor fd = intersection->second; Vector_3 n = compute_face_normal(fd, mesh); - if (dirs[i] * n > 0) { + if (dirs[i] * n > 0) inside++; - } - else { + else outside++; - } } } } @@ -791,8 +788,6 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom voxel_points.insert(Point_3(xmax, ymax, zmax)); } - //export_points("surface_points.ply", voxel_points); - convex_hull(voxel_points, c.ch.points, c.ch.indices); c.ch.volume = volume(c.ch.points, c.ch.indices); @@ -923,7 +918,6 @@ void split(std::vector > &candidates, Candidate void choose_splitting_plane(Candidate& c, unsigned int &axis, unsigned int &location, std::vector& grid, const Vec3_uint& grid_size, const NamedParameters& np) { //Just split the voxel bbox along 'axis' after voxel index 'location' - const bool find_best_plane = parameters::choose_parameter(parameters::get_parameter(np, internal_np::find_best_splitter), true); + const bool search_concavity = parameters::choose_parameter(parameters::get_parameter(np, internal_np::split_at_concavity), true); const std::array span = {c.bbox.upper[0] - c.bbox.lower[0], c.bbox.upper[1] - c.bbox.lower[1], c.bbox.upper[2] - c.bbox.lower[2]}; // Split largest axis axis = (span[0] >= span[1]) ? 0 : 1; axis = (span[axis] >= span[2]) ? axis : 2; - if (span[axis] >= 8 && find_best_plane) + if (span[axis] >= 8 && search_concavity) choose_splitting_location_by_concavity(axis, location, c.bbox, grid, grid_size); else location = (c.bbox.upper[axis] + c.bbox.lower[axis]) / 2; @@ -1131,16 +1125,9 @@ void recurse(std::vector>& candidates, std::vector const FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); - static std::size_t step = 0; - std::vector> final_candidates; - double choose = 0, spli = 0; - while (!candidates.empty()) { - //std::cout << step++ << ": " << candidates.size() << "\t"; - CGAL::Timer timer; - timer.start(); std::vector> former_candidates = std::move(candidates); for (Candidate& c : former_candidates) { @@ -1152,13 +1139,9 @@ void recurse(std::vector>& candidates, std::vector continue; } unsigned int axis = 0, location = 0; - double b = timer.time(); choose_splitting_plane(c, axis, location, grid, grid_size, np); - double a = timer.time(); - choose += a - b; //std::cout << c.index << " " << c.ch.volume_error << " split " << axis << "a " << location << " (" << c.bbox.lower[0] << "," << c.bbox.lower[1] << "," << c.bbox.lower[2] << "," << c.bbox.upper[0] << "," << c.bbox.upper[1] << "," << c.bbox.upper[2] << ")" << std::endl; split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); - spli += timer.time() - a; // if (!candidates.empty()) { // auto& b = candidates[candidates.size() - 1]; @@ -1179,13 +1162,8 @@ void recurse(std::vector>& candidates, std::vector for (Candidate& c : candidates) compute_candidate(c, bbox, voxel_size); #endif - - //std::cout << timer.time() << std::endl; } - std::cout << "find_plane: " << choose << std::endl; - std::cout << "split: " << spli << std::endl; - std::swap(candidates, final_candidates); } @@ -1386,9 +1364,82 @@ void merge(std::vector>& candidates, std::vector } } - +/** + * \ingroup PMP_convex_decomposition_grp + * + * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the convex hull of the mesh is hierarchically split until a volume threshold is met. + * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. + * + * \tparam FaceGraph a model of `HalfedgeListGraph`, `FaceListGraph`, and `MutableFaceGraph` + * + * \tparam OutputIterator must be an output iterator accepting variables of type `std::pair, std::vector > >`. + * + * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" + * + * \param mesh the input mesh to approximate by convex hulls + * \param hulls_out output iterator into which convex hulls are recorded + * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below + * + * \cgalNamedParamsBegin + * + * \cgalParamNBegin{maximum_number_of_voxels} + * \cgalParamDescription{Gives an upper bound of the number of voxels. The largest bounding box side will have a length of the cubic root of `maximum_number_of_voxels`.} + * \cgalParamType{unsigned int} + * \cgalParamDefault{1000000} + * \cgalParamNEnd + * + * \cgalParamNBegin{maximum_depth} + * \cgalParamDescription{maximum depth of hierarchical splits} + * \cgalParamType{unsigned int} + * \cgalParamDefault{10} + * \cgalParamNEnd + * + * \cgalParamNBegin{maximum_number_of_convex_hulls} + * \cgalParamDescription{maximum number of convex hulls produced by the method} + * \cgalParamType{unsigned int} + * \cgalParamDefault{16} + * \cgalParamNEnd + * + * \cgalParamNBegin{volume_error} + * \cgalParamDescription{maximum difference in percent of volume of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split.} + * \cgalParamType{double} + * \cgalParamDefault{0.01} + * \cgalParamNEnd + * + * \cgalParamNBegin{split_at_concavity} + * \cgalParamDescription{split the local box of the convex hull in the mid of the longest axis (faster) or search the concavity along the largest axis for splitting.} + * \cgalParamType{Boolean} + * \cgalParamDefault{true} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_point_map} + * \cgalParamDescription{a property map associating points to the vertices of `tmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` + * as key type and `%Point_3` as value type} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, tmesh)`} + * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` + * must be available in `TriangleMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{concurrency_tag} + * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} + * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} + * \cgalParamDefault{`CGAL::Parallel_if_available_tag`} + * \cgalParamNEnd + * + * \cgalParamNBegin{geom_traits} + * \cgalParamDescription{an instance of a geometric traits class} + * \cgalParamType{a class model of `Kernel`} + * \cgalParamDefault{a \cgal Kernel deduced from the point type of `FaceGraph`, using `CGAL::Kernel_traits`} + * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` + */ template -std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterator out, const NamedParameters& np = parameters::default_values()) { +std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { CGAL::Memory_sizer memory; std::size_t virt_mem = memory.virtual_size(); std::size_t res_mem = memory.resident_size(); @@ -1400,7 +1451,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat Vertex_point_map vpm = parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, mesh)); using FT = typename Geom_traits::FT; - const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_voxels), 1000000); + const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); Bbox_3 bb = bbox(mesh); @@ -1444,7 +1495,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat std::cout << memory.virtual_size() << " virtual " << memory.resident_size() << " resident memory allocated in total" << std::endl; for (std::size_t i = 0; i < hulls.size(); i++) - *out++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); + *out_hulls++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); return hulls.size(); } diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index 266d3c1bab2..2aa8762c7ee 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -177,12 +177,11 @@ CGAL_add_named_parameter(weight_limiting_t, weight_limiting, weight_limiting) CGAL_add_named_parameter(progressive_t, progressive, progressive) CGAL_add_named_parameter(tiling_t, tiling, tiling) CGAL_add_named_parameter(dimension_t, dimension, dimension) -CGAL_add_named_parameter(maximum_voxels_t, maximum_voxels, maximum_voxels) -CGAL_add_named_parameter(voxel_size_t, voxel_size, voxel_size) +CGAL_add_named_parameter(maximum_number_of_voxels_t, maximum_number_of_voxels, maximum_number_of_voxels) CGAL_add_named_parameter(maximum_depth_t, maximum_depth, maximum_depth) CGAL_add_named_parameter(volume_error_t, volume_error, volume_error) CGAL_add_named_parameter(maximum_number_of_convex_hulls_t, maximum_number_of_convex_hulls, maximum_number_of_convex_hulls) -CGAL_add_named_parameter(find_best_splitter_t, find_best_splitter, find_best_splitter) +CGAL_add_named_parameter(split_at_concavity_t, split_at_concavity, split_at_concavity) // List of named parameters that we use in the package 'Surface Mesh Simplification' From 1b439b94b014addc1f8aee65802907e5bdf0b233 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Mon, 30 Jun 2025 16:03:26 +0100 Subject: [PATCH 09/49] typo --- .../doc/Convex_decomposition_3/Convex_decomposition_3.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index 98f1e2e2d6a..3a36080cd66 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -13,8 +13,8 @@ namespace CGAL { For many applications on non-convex polyhedra, there are efficient solutions that first decompose the polyhedron into convex pieces. As an example, the Minkowski sum of two polyhedra can be computed by -decomposing both polyhedra into convex pieces, compute pair-wise -Minkowski sums of the convex pieces, and unite the pair-wise sums. +decomposing both polyhedra into convex pieces, compute pairwise +Minkowski sums of the convex pieces, and unite the pairwise sums. While it is desirable to have a decomposition into a minimum number of pieces, this problem is known to be NP-hard \cgalCite{c-cpplb-84}. Our From 2087ff7458cbf1a833fcd9e60ef5e98474474131 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Wed, 2 Jul 2025 15:50:30 +0200 Subject: [PATCH 10/49] cleaning code and doc --- .../approximate_convex_decomposition.cpp | 11 +- .../approximate_convex_decomposition.h | 415 ++++++++++++------ 2 files changed, 276 insertions(+), 150 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index b8738a68349..0cbe2ff4223 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -29,15 +29,18 @@ int main(int argc, char* argv[]) } std::vector convex_hulls; - - std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10).volume_error(0.001).maximum_number_of_convex_hulls(8).split_at_concavity(1)); + std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::parameters::maximum_depth(10) + .volume_error(0.1) + .maximum_number_of_convex_hulls(9) + .split_at_concavity(true) + .maximum_number_of_voxels(1000000) + .concurrency_tag(CGAL::Parallel_if_available_tag())); for (std::size_t i = 0;i std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t number_of_voxels) { std::size_t max_voxels_axis = std::pow(number_of_voxels, 1.0 / 3.0); - // get largest axis - FT largest = 0; + // get longest axis + FT longest = 0; if (bb.x_span() >= bb.y_span() && bb.x_span() >= bb.z_span()) - largest = bb.x_span(); + longest = bb.x_span(); else if (bb.y_span() >= bb.x_span() && bb.y_span() >= bb.z_span()) - largest = bb.y_span(); + longest = bb.y_span(); else if (bb.z_span() >= bb.x_span() && bb.z_span() >= bb.y_span()) - largest = bb.z_span(); + longest = bb.z_span(); - const FT voxel_size = largest / (max_voxels_axis - 3); + const FT voxel_size = longest / (max_voxels_axis - 3); FT s = 1.5 * voxel_size; @@ -545,9 +545,9 @@ void naive_floodfill(std::vector& grid, const Vec3_uint& grid_size) { } } - template -void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph &mesh) { +void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph& mesh, CGAL::Parallel_tag) { +#ifdef CGAL_LINKED_WITH_TBB const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; }; @@ -566,16 +566,12 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con Tree tree(faces(mesh).first, faces(mesh).second, mesh); - std::array dirs = {Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1}}; + std::array dirs = { Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1} }; -#ifdef CGAL_LINKED_WITH_TBB tbb::parallel_for(std::size_t(0), std::size_t(grid_size[0]), [&](const std::size_t x) -#else - for (std::size_t x = 0;x& grid, const Vec3_uint& grid_size, con vox(x, y, z) = OUTSIDE; } } -#ifdef CGAL_LINKED_WITH_TBB - ); + ); #endif } +template +void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph& mesh, CGAL::Sequential_tag) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + using face_descriptor = typename boost::graph_traits::face_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + using Ray_3 = typename GeomTraits::Ray_3; + + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits_3; + using Tree = CGAL::AABB_tree; + using Primitive_id = typename Tree::Primitive_id; + using Ray_intersection = std::optional::Type>; + + Tree tree(faces(mesh).first, faces(mesh).second, mesh); + + std::array dirs = { Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1} }; + + for (std::size_t x = 0; x < grid_size[0]; x++) + { + for (std::size_t y = 0; y < grid_size[1]; y++) + for (std::size_t z = 0; z < grid_size[2]; z++) { + if (vox(x, y, z) == SURFACE) + continue; + + Point_3 c(bb.xmin() + (x + 0.5) * voxel_size, bb.ymin() + (y + 0.5) * voxel_size, bb.zmin() + (z + 0.5) * voxel_size); + + unsigned int inside = 0; + unsigned int outside = 0; + + for (std::size_t i = 0; i < 6; i++) { + Ray_intersection intersection = tree.first_intersection(Ray_3(c, dirs[i])); + if (intersection) { + // A segment intersection is not helpful as it means the triangle normal is orthogonal to the ray + if (std::get_if(&(intersection->first))) { + face_descriptor fd = intersection->second; + Vector_3 n = compute_face_normal(fd, mesh); + if (dirs[i] * n > 0) + inside++; + else + outside++; + } + } + } + + if (inside >= 3 && outside == 0) { + vox(x, y, z) = INSIDE; + } + else + vox(x, y, z) = OUTSIDE; + } + } +} template struct Convex_hull { @@ -798,15 +850,12 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; } -template -void fill_grid(Candidate &c, std::vector &grid, const FaceGraph &mesh, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size) { +template +void fill_grid(Candidate &c, std::vector &grid, const FaceGraph &mesh, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size, Concurrency_tag tag) { const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; }; - CGAL::Timer timer; - timer.start(); - for (const typename boost::graph_traits::face_descriptor fd : faces(mesh)) { Bbox_uint face_bb = grid_bbox_face(mesh, fd, bb, voxel_size); CGAL_assertion(face_bb.lower[0] <= face_bb.upper[0]); @@ -824,28 +873,21 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr vox(x, y, z) = Grid_cell::SURFACE; } } - double fill = timer.time(); //export_grid("before.ply", bb, grid, grid_size, voxel_size); - auto filterS = [](const int8_t& l) -> bool {return l == SURFACE; }; - auto filterO = [](const int8_t& l) -> bool {return l == OUTSIDE; }; - auto filterI = [](const int8_t& l) -> bool {return l == INSIDE; }; + //auto filterS = [](const int8_t& l) -> bool {return l == SURFACE; }; + //auto filterO = [](const int8_t& l) -> bool {return l == OUTSIDE; }; + //auto filterI = [](const int8_t& l) -> bool {return l == INSIDE; }; //export_grid("before_surface_ray.ply", bb, grid, grid_size, voxel_size, filterS); //export_grid("before_outside.ply", bb, grid, grid_size, voxel_size, filterO); - // For now, only do floodfill - //label_floodfill(grid, grid_size); - double before_shooting = timer.time(); if (CGAL::is_closed(mesh)) { naive_floodfill(grid, grid_size); //check_grid(grid, grid_size); } else - rayshooting_fill(grid, grid_size, bb, voxel_size, mesh); - double after_shooting = timer.time(); + rayshooting_fill(grid, grid_size, bb, voxel_size, mesh, tag); - std::cout << "filling: " << fill << std::endl; - std::cout << "shooting: " << (after_shooting - before_shooting) << std::endl; //export_grid("after_inside_ray.ply", bb, grid, grid_size, voxel_size, filterI); //export_grid("after_outside_ray.ply", bb, grid, grid_size, voxel_size, filterO); @@ -859,15 +901,13 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr else if (vox(x, y, z) == SURFACE) c.surface.push_back({x, y, z}); - //std::cout << "remove check grid and export" << std::endl; - //export_grid("after_inside.ply", bb, grid, grid_size, voxel_size, filterI); //export_grid("after_outside.ply", bb, grid, grid_size, voxel_size, filterO); } -template -void init(Candidate &c, const FaceGraph& mesh, std::vector& grid, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size) { - internal::fill_grid(c, grid, mesh, bb, grid_size, voxel_size); +template +void init(Candidate &c, const FaceGraph& mesh, std::vector& grid, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size, Concurrency_tag tag) { + internal::fill_grid(c, grid, mesh, bb, grid_size, voxel_size, tag); compute_candidate(c, bb, voxel_size); } @@ -1086,7 +1126,7 @@ void choose_splitting_plane(Candidate& c, unsigned int &axis, unsign const bool search_concavity = parameters::choose_parameter(parameters::get_parameter(np, internal_np::split_at_concavity), true); const std::array span = {c.bbox.upper[0] - c.bbox.lower[0], c.bbox.upper[1] - c.bbox.lower[1], c.bbox.upper[2] - c.bbox.lower[2]}; - // Split largest axis + // Split longest axis axis = (span[0] >= span[1]) ? 0 : 1; axis = (span[axis] >= span[2]) ? axis : 2; @@ -1120,7 +1160,8 @@ bool finished(Candidate &c, const NamedParameters& np) { } template -void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3 &bbox, const typename GeomTraits::FT &voxel_size, const NamedParameters& np) { +void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Parallel_tag) { +#ifdef CGAL_LINKED_WITH_TBB using FT = typename GeomTraits::FT; const FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); @@ -1140,35 +1181,52 @@ void recurse(std::vector>& candidates, std::vector } unsigned int axis = 0, location = 0; choose_splitting_plane(c, axis, location, grid, grid_size, np); - //std::cout << c.index << " " << c.ch.volume_error << " split " << axis << "a " << location << " (" << c.bbox.lower[0] << "," << c.bbox.lower[1] << "," << c.bbox.lower[2] << "," << c.bbox.upper[0] << "," << c.bbox.upper[1] << "," << c.bbox.upper[2] << ")" << std::endl; split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); - -// if (!candidates.empty()) { -// auto& b = candidates[candidates.size() - 1]; -// std::cout << b.index << " (" << b.bbox.lower[0] << "," << b.bbox.lower[1] << "," << b.bbox.lower[2] << "," << b.bbox.upper[0] << "," << b.bbox.upper[1] << "," << b.bbox.upper[2] << ")" << b.ch.volume_error << std::endl; -// CGAL::IO::write_polygon_soup(std::to_string(b.index) + " " + std::to_string(c.index) + ".off", b.ch.points, b.ch.indices); -// } -// if (candidates.size() >= 2) { -// auto& a = candidates[candidates.size() - 2]; -// std::cout << a.index << " (" << a.bbox.lower[0] << "," << a.bbox.lower[1] << "," << a.bbox.lower[2] << "," << a.bbox.upper[0] << "," << a.bbox.upper[1] << "," << a.bbox.upper[2] << ")" << a.ch.volume_error << std::endl; -// CGAL::IO::write_polygon_soup(std::to_string(a.index) + " " + std::to_string(c.index) + ".off", a.ch.points, a.ch.indices); -// } } -#ifdef CGAL_LINKED_WITH_TBB tbb::parallel_for_each(candidates, [&](Candidate& c) { - compute_candidate(c, bbox, voxel_size); + compute_candidate(c, bbox, voxel_size); }); -#else + } + + std::swap(candidates, final_candidates); +#endif +} + +template +void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Sequential_tag) { + using FT = typename GeomTraits::FT; + const FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 0.1); + const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + + std::vector> final_candidates; + + while (!candidates.empty()) { + std::vector> former_candidates = std::move(candidates); + for (Candidate& c : former_candidates) { + + if (finished(c, np)) { + CGAL::Bbox_3 bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); + bbox.scale(1.1); + c.ch.bbox = bbox; + final_candidates.push_back(std::move(c)); + continue; + } + unsigned int axis = 0, location = 0; + choose_splitting_plane(c, axis, location, grid, grid_size, np); + split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); + } + for (Candidate& c : candidates) compute_candidate(c, bbox, voxel_size); -#endif } std::swap(candidates, final_candidates); } template -void merge(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const typename GeomTraits::FT &hull_volume, const NamedParameters& np) { +void merge(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, + const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Parallel_tag) { +#ifdef CGAL_LINKED_WITH_TBB const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); if (candidates.size() <= max_convex_hulls) return; @@ -1192,17 +1250,12 @@ void merge(std::vector>& candidates, std::vector // Consider all non-equal pairs std::size_t max_merge_candidates = (candidates.size() * (candidates.size() - 1)) >> 1; -#ifdef CGAL_LINKED_WITH_TBB tbb::concurrent_unordered_map> hulls; std::atomic num_hulls = candidates.size(); -#else - std::unordered_map> hulls; - std::size_t num_hulls = candidates.size(); -#endif std::unordered_set keep; - for (std::size_t i = 0;i>& candidates, std::vector candidates.clear(); candidates.reserve(max_convex_hulls); -#ifdef CGAL_LINKED_WITH_TBB std::vector todo; tbb::concurrent_priority_queue queue; -#else - std::priority_queue queue; -#endif - const auto do_merge = [hull_volume, &hulls, &num_hulls, &queue](Merged_candidate &m) { + const auto do_merge = [hull_volume, &hulls, &num_hulls, &queue](Merged_candidate& m) { Convex_hull& ci = hulls[m.ch_a]; Convex_hull& cj = hulls[m.ch_b]; -#ifdef CGAL_LINKED_WITH_TBB m.ch = num_hulls.fetch_add(1); Convex_hull& ch = hulls[m.ch]; -#else - m.ch = num_hulls++; - Convex_hull& ch = hulls[m.ch]; -#endif + ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); @@ -1236,7 +1281,7 @@ void merge(std::vector>& candidates, std::vector ch.volume = volume(ch.points, ch.indices); ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; - }; + }; for (std::size_t i : keep) { const Convex_hull& ci = hulls[i]; @@ -1244,26 +1289,8 @@ void merge(std::vector>& candidates, std::vector if (j <= i) continue; const Convex_hull& cj = hulls[j]; - if (CGAL::do_intersect(ci.bbox, cj.bbox)) { -#ifdef CGAL_LINKED_WITH_TBB + if (CGAL::do_intersect(ci.bbox, cj.bbox)) todo.emplace_back(Merged_candidate(i, j)); -#else - Merged_candidate m(i, j); - - m.ch = num_hulls++; - Convex_hull& ch = hulls[m.ch]; - ch.bbox = box_union(ci.bbox, cj.bbox); - std::vector pts(ci.points.begin(), ci.points.end()); - pts.reserve(pts.size() + cj.points.size()); - std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull(pts, ch.points, ch.indices); - - ch.volume = volume(ch.points, ch.indices); - - ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; - queue.push(std::move(m)); -#endif - } else { Merged_candidate m(i, j); Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); @@ -1275,21 +1302,14 @@ void merge(std::vector>& candidates, std::vector } // parallel for if available -#ifdef CGAL_LINKED_WITH_TBB tbb::parallel_for_each(todo, do_merge); - for (Merged_candidate &m : todo) + for (Merged_candidate& m : todo) queue.push(std::move(m)); todo.clear(); -#endif while (!queue.empty() && keep.size() > max_convex_hulls) { -#ifdef CGAL_LINKED_WITH_TBB Merged_candidate m; while (!queue.try_pop(m) && !queue.empty()); -#else - Merged_candidate m = queue.top(); - queue.pop(); -#endif auto ch_a = hulls.find(m.ch_a); if (ch_a == hulls.end()) @@ -1305,22 +1325,158 @@ void merge(std::vector>& candidates, std::vector keep.erase(m.ch_a); keep.erase(m.ch_b); -#ifdef CGAL_LINKED_WITH_TBB hulls.unsafe_erase(ch_a); hulls.unsafe_erase(ch_b); + + const Convex_hull& cj = hulls[m.ch]; + + for (std::size_t id : keep) { + const Convex_hull& ci = hulls[id]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) + todo.emplace_back(Merged_candidate(id, m.ch)); + else { + Merged_candidate merged(id, m.ch); + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); + merged.ch = -1; + merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(merged)); + } + } + + keep.insert(m.ch); + + tbb::parallel_for_each(todo, do_merge); + for (Merged_candidate& m : todo) + queue.push(std::move(m)); + todo.clear(); + } + + num_hulls = 0; + + for (std::size_t i : keep) + candidates.push_back(std::move(hulls[i])); #else + assert(false); +#endif +} + +template +void merge(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, + const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Sequential_tag) { + const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); + if (candidates.size() <= max_convex_hulls) + return; + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + + struct Merged_candidate { + std::size_t ch_a, ch_b; + int ch; + FT volume_error; + + bool operator < (const Merged_candidate& other) const { + return (volume_error > other.volume_error); + } + + Merged_candidate() : ch_a(-1), ch_b(-1) {} + Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} + }; + + // Consider all non-equal pairs + std::size_t max_merge_candidates = (candidates.size() * (candidates.size() - 1)) >> 1; + + std::unordered_map> hulls; + std::size_t num_hulls = candidates.size(); + + std::unordered_set keep; + + for (std::size_t i = 0; i < candidates.size(); i++) { + hulls.emplace(i, std::move(candidates[i])); + keep.insert(i); + } + + candidates.clear(); + candidates.reserve(max_convex_hulls); + + std::priority_queue queue; + + const auto do_merge = [hull_volume, &hulls, &num_hulls, &queue](Merged_candidate& m) { + Convex_hull& ci = hulls[m.ch_a]; + Convex_hull& cj = hulls[m.ch_b]; + + m.ch = num_hulls++; + Convex_hull& ch = hulls[m.ch]; + + ch.bbox = box_union(ci.bbox, cj.bbox); + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull(pts, ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + }; + + for (std::size_t i : keep) { + const Convex_hull& ci = hulls[i]; + for (std::size_t j : keep) { + if (j <= i) + continue; + const Convex_hull& cj = hulls[j]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) { + Merged_candidate m(i, j); + + m.ch = num_hulls++; + Convex_hull& ch = hulls[m.ch]; + ch.bbox = box_union(ci.bbox, cj.bbox); + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull(pts, ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + queue.push(std::move(m)); + } + else { + Merged_candidate m(i, j); + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); + m.ch = -1; + m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(m)); + } + } + } + + while (!queue.empty() && keep.size() > max_convex_hulls) { + Merged_candidate m = queue.top(); + queue.pop(); + + auto ch_a = hulls.find(m.ch_a); + if (ch_a == hulls.end()) + continue; + + auto ch_b = hulls.find(m.ch_b); + if (ch_b == hulls.end()) + continue; + + if (m.ch == -1) + do_merge(m); + + keep.erase(m.ch_a); + keep.erase(m.ch_b); + hulls.erase(ch_a); hulls.erase(ch_b); -#endif const Convex_hull& cj = hulls[m.ch]; for (std::size_t id : keep) { const Convex_hull& ci = hulls[id]; if (CGAL::do_intersect(ci.bbox, cj.bbox)) { -#ifdef CGAL_LINKED_WITH_TBB - todo.emplace_back(Merged_candidate(id, m.ch)); -#else Merged_candidate merged(id, m.ch); merged.ch = num_hulls++; @@ -1335,7 +1491,6 @@ void merge(std::vector>& candidates, std::vector ch.volume_error = merged.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; queue.push(std::move(merged)); -#endif } else { Merged_candidate merged(id, m.ch); @@ -1347,14 +1502,6 @@ void merge(std::vector>& candidates, std::vector } keep.insert(m.ch); - - // parallel for -#ifdef CGAL_LINKED_WITH_TBB - tbb::parallel_for_each(todo, do_merge); - for (Merged_candidate& m : todo) - queue.push(std::move(m)); - todo.clear(); -#endif } num_hulls = 0; @@ -1377,13 +1524,13 @@ void merge(std::vector>& candidates, std::vector * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * \param mesh the input mesh to approximate by convex hulls - * \param hulls_out output iterator into which convex hulls are recorded + * \param out_hulls output iterator into which convex hulls are recorded * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * * \cgalParamNBegin{maximum_number_of_voxels} - * \cgalParamDescription{Gives an upper bound of the number of voxels. The largest bounding box side will have a length of the cubic root of `maximum_number_of_voxels`.} + * \cgalParamDescription{gives an upper bound of the number of voxels. The longest bounding box side will have a length of the cubic root of `maximum_number_of_voxels`.} * \cgalParamType{unsigned int} * \cgalParamDefault{1000000} * \cgalParamNEnd @@ -1407,7 +1554,7 @@ void merge(std::vector>& candidates, std::vector * \cgalParamNEnd * * \cgalParamNBegin{split_at_concavity} - * \cgalParamDescription{split the local box of the convex hull in the mid of the longest axis (faster) or search the concavity along the largest axis for splitting.} + * \cgalParamDescription{split the local box of the convex hull in the mid of the longest axis (faster) or search the concavity along the longest axis of the bounding box for splitting.} * \cgalParamType{Boolean} * \cgalParamDefault{true} * \cgalParamNEnd @@ -1440,11 +1587,6 @@ void merge(std::vector>& candidates, std::vector */ template std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { - CGAL::Memory_sizer memory; - std::size_t virt_mem = memory.virtual_size(); - std::size_t res_mem = memory.resident_size(); - CGAL::Timer timer; - using Geom_traits = typename GetGeomTraits::type; using Vertex_point_map = typename GetVertexPointMap::type; @@ -1452,29 +1594,19 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat using FT = typename Geom_traits::FT; const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); - const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; Bbox_3 bb = bbox(mesh); const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); - timer.start(); - std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); - std::cout << "#voxels " << grid.size() << std::endl; - std::cout << "grid_size: " << grid_size[0] << " " << grid_size[1] << " " << grid_size[2] << std::endl; std::vector> candidates(1); - init(candidates[0], mesh, grid, bb, grid_size, voxel_size); + init(candidates[0], mesh, grid, bb, grid_size, voxel_size, Concurrency_tag()); const FT hull_volume = candidates[0].ch.volume; - double after_init = timer.time(); - recurse(candidates, grid, grid_size, bb, voxel_size, np); - double after_recurse = timer.time(); - -// for (std::size_t i = 0; i < candidates.size(); i++) { -// CGAL::IO::write_polygon_soup(std::to_string(max_depth) + "-" + std::to_string(i) + "-d" + std::to_string(candidates[i].depth) + "-e" + std::to_string(candidates[i].ch.volume_error) + ".off", candidates[i].ch.points, candidates[i].ch.indices); -// } + recurse(candidates, grid, grid_size, bb, voxel_size, np, Concurrency_tag()); std::vector> hulls; for (internal::Candidate &c : candidates) @@ -1483,16 +1615,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat candidates.clear(); // merge until target number is reached - merge(hulls, grid, grid_size, bb, voxel_size, hull_volume, np); - - double after_merge = timer.time(); - - std::cout << "timing:" << std::endl; - std::cout << (after_init) << " initialization" << std::endl; - std::cout << (after_recurse - after_init) << " recurse" << std::endl; - std::cout << (after_merge - after_recurse) << " merge" << std::endl; - - std::cout << memory.virtual_size() << " virtual " << memory.resident_size() << " resident memory allocated in total" << std::endl; + merge(hulls, grid, grid_size, bb, voxel_size, hull_volume, np, Concurrency_tag()); for (std::size_t i = 0; i < hulls.size(); i++) *out_hulls++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); From 190653172a9f98bc82c18377b985e746f0acc8ea Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 3 Jul 2025 16:45:34 +0200 Subject: [PATCH 11/49] adding test making compatible with EPECK --- .../approximate_convex_decomposition.h | 114 +++++++++++++----- .../Polygon_mesh_processing/CMakeLists.txt | 2 + .../test_approximate_convex_decomposition.cpp | 70 +++++++++++ 3 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 371dead5507..e4e4b054102 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -228,6 +228,7 @@ Box box_union(const Box& a, const Box& b) { template std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t number_of_voxels) { std::size_t max_voxels_axis = std::pow(number_of_voxels, 1.0 / 3.0); + assert(max_voxels_axis > 3); // get longest axis FT longest = 0; @@ -238,13 +239,13 @@ std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t numb else if (bb.z_span() >= bb.x_span() && bb.z_span() >= bb.y_span()) longest = bb.z_span(); - const FT voxel_size = longest / (max_voxels_axis - 3); + const FT voxel_size = longest * FT(1.0 / (max_voxels_axis - 3)); FT s = 1.5 * voxel_size; - bb = Bbox_3(bb.xmin() - s, bb.ymin() - s, bb.zmin() - s, bb.xmax() + s, bb.ymax() + s, bb.zmax() + s); + bb = Bbox_3(to_double(bb.xmin() - s), to_double(bb.ymin() - s), to_double(bb.zmin() - s), to_double(bb.xmax() + s), to_double(bb.ymax() + s), to_double(bb.zmax() + s)); - return { Vec3_uint{static_cast(bb.x_span() / voxel_size + 0.5), static_cast(bb.y_span() / voxel_size + 0.5), static_cast(bb.z_span() / voxel_size + 0.5)}, voxel_size}; + return { Vec3_uint{static_cast(to_double(bb.x_span() / voxel_size + 0.5)), static_cast(to_double(bb.y_span() / voxel_size + 0.5)), static_cast(to_double(bb.z_span() / voxel_size + 0.5))}, voxel_size}; } template @@ -265,25 +266,25 @@ template Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_traits::face_descriptor fd, const Bbox_3& bb, const FT& voxel_size) { Bbox_3 face_bb = face_bbox(fd, mesh); return Bbox_uint({ - static_cast((face_bb.xmin() - bb.xmin()) / voxel_size - 0.5), - static_cast((face_bb.ymin() - bb.ymin()) / voxel_size - 0.5), - static_cast((face_bb.zmin() - bb.zmin()) / voxel_size - 0.5) + static_cast((face_bb.xmin() - bb.xmin()) / to_double(voxel_size) - 0.5), + static_cast((face_bb.ymin() - bb.ymin()) / to_double(voxel_size) - 0.5), + static_cast((face_bb.zmin() - bb.zmin()) / to_double(voxel_size) - 0.5) }, { - static_cast((face_bb.xmax() - bb.xmin()) / voxel_size + 0.5), - static_cast((face_bb.ymax() - bb.ymin()) / voxel_size + 0.5), - static_cast((face_bb.zmax() - bb.zmin()) / voxel_size + 0.5) + static_cast((face_bb.xmax() - bb.xmin()) / to_double(voxel_size) + 0.5), + static_cast((face_bb.ymax() - bb.ymin()) / to_double(voxel_size) + 0.5), + static_cast((face_bb.zmax() - bb.zmin()) / to_double(voxel_size) + 0.5) }); } template Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { return Bbox_3( - bb.xmin() + voxel[0] * voxel_size, - bb.ymin() + voxel[1] * voxel_size, - bb.zmin() + voxel[2] * voxel_size, - bb.xmin() + (voxel[0] + 1) * voxel_size, - bb.ymin() + (voxel[1] + 1) * voxel_size, - bb.zmin() + (voxel[2] + 1) * voxel_size + bb.xmin() + voxel[0] * to_double(voxel_size), + bb.ymin() + voxel[1] * to_double(voxel_size), + bb.zmin() + voxel[2] * to_double(voxel_size), + bb.xmin() + (voxel[0] + 1) * to_double(voxel_size), + bb.ymin() + (voxel[1] + 1) * to_double(voxel_size), + bb.zmin() + (voxel[2] + 1) * to_double(voxel_size) ); } @@ -793,6 +794,26 @@ void enlarge(Bbox_uint& bbox, const Vec3_uint& v) { bbox.upper[2] = (std::max)(bbox.upper[2], v[2]); } +template > +struct is_std_hashable : std::false_type {}; + +template +struct is_std_hashable>()(std::declval()))>> : std::true_type {}; + +template::type::FT>, typename std::is_same::type::Kernel_tag, CGAL::Cartesian_tag>::type>::type> +struct Set { +}; + +template +struct Set { + using type = std::unordered_set; +}; + +template +struct Set { + using type = std::set; +}; + template void compute_candidate(Candidate &c, const Bbox_3& bb, typename GeomTraits::FT voxel_size) { using Point_3 = typename GeomTraits::Point_3; @@ -800,18 +821,16 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom c.bbox.lower = c.bbox.upper = c.surface[0]; - // update bounding box - std::unordered_set voxel_points; + typename Set::type voxel_points; - // Is it more efficient than using std::vector with plenty of duplicates? for (const Vec3_uint& v : c.surface) { enlarge(c.bbox, v); - FT xmin = bb.xmin() + v[0] * voxel_size; - FT ymin = bb.ymin() + v[1] * voxel_size; - FT zmin = bb.zmin() + v[2] * voxel_size; - FT xmax = bb.xmin() + (v[0] + 1) * voxel_size; - FT ymax = bb.ymin() + (v[1] + 1) * voxel_size; - FT zmax = bb.zmin() + (v[2] + 1) * voxel_size; + FT xmin = bb.xmin() + FT(v[0]) * voxel_size; + FT ymin = bb.ymin() + FT(v[1]) * voxel_size; + FT zmin = bb.zmin() + FT(v[2]) * voxel_size; + FT xmax = bb.xmin() + FT(v[0] + 1) * voxel_size; + FT ymax = bb.ymin() + FT(v[1] + 1) * voxel_size; + FT zmax = bb.zmin() + FT(v[2] + 1) * voxel_size; voxel_points.insert(Point_3(xmin, ymin, zmin)); voxel_points.insert(Point_3(xmin, ymax, zmin)); voxel_points.insert(Point_3(xmin, ymin, zmax)); @@ -824,12 +843,12 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom for (const Vec3_uint& v : c.new_surface) { enlarge(c.bbox, v); - FT xmin = bb.xmin() + v[0] * voxel_size; - FT ymin = bb.ymin() + v[1] * voxel_size; - FT zmin = bb.zmin() + v[2] * voxel_size; - FT xmax = bb.xmin() + (v[0] + 1) * voxel_size; - FT ymax = bb.ymin() + (v[1] + 1) * voxel_size; - FT zmax = bb.zmin() + (v[2] + 1) * voxel_size; + FT xmin = bb.xmin() + FT(v[0]) * voxel_size; + FT ymin = bb.ymin() + FT(v[1]) * voxel_size; + FT zmin = bb.zmin() + FT(v[2]) * voxel_size; + FT xmax = bb.xmin() + FT(v[0] + 1) * voxel_size; + FT ymax = bb.ymin() + FT(v[1] + 1) * voxel_size; + FT zmax = bb.zmin() + FT(v[2] + 1) * voxel_size; voxel_points.insert(Point_3(xmin, ymin, zmin)); voxel_points.insert(Point_3(xmin, ymax, zmin)); voxel_points.insert(Point_3(xmin, ymin, zmax)); @@ -846,7 +865,7 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom CGAL_assertion(c.ch.volume > 0); - c.ch.voxel_volume = (voxel_size * voxel_size * voxel_size) * (c.inside.size() + c.surface.size() + c.new_surface.size()); + c.ch.voxel_volume = (voxel_size * voxel_size * voxel_size) * FT(double(c.inside.size() + c.surface.size() + c.new_surface.size())); c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; } @@ -1122,7 +1141,6 @@ void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& lo template void choose_splitting_plane(Candidate& c, unsigned int &axis, unsigned int &location, std::vector& grid, const Vec3_uint& grid_size, const NamedParameters& np) { - //Just split the voxel bbox along 'axis' after voxel index 'location' const bool search_concavity = parameters::choose_parameter(parameters::get_parameter(np, internal_np::split_at_concavity), true); const std::array span = {c.bbox.upper[0] - c.bbox.lower[0], c.bbox.upper[1] - c.bbox.lower[1], c.bbox.upper[2] - c.bbox.lower[2]}; @@ -1530,7 +1548,7 @@ void merge(std::vector>& candidates, std::vector * \cgalNamedParamsBegin * * \cgalParamNBegin{maximum_number_of_voxels} - * \cgalParamDescription{gives an upper bound of the number of voxels. The longest bounding box side will have a length of the cubic root of `maximum_number_of_voxels`.} + * \cgalParamDescription{gives an upper bound of the number of voxels. The longest bounding box side will have a length of the cubic root of `maximum_number_of_voxels`. Cannot be smaller than 64. } * \cgalParamType{unsigned int} * \cgalParamDefault{1000000} * \cgalParamNEnd @@ -1596,6 +1614,36 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; + const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); + assert(max_convex_hulls > 0); + + if (max_convex_hulls == 1) { + internal::Convex_hull< Geom_traits> ch; + using Mesh = Surface_mesh; + Mesh m; + convex_hull_3(mesh, m); + + ch.points.resize(m.number_of_vertices()); + ch.indices.resize(m.number_of_faces()); + + std::size_t idx = 0; + for (const typename Mesh::vertex_index v : m.vertices()) + ch.points[idx++] = m.point(v); + + idx = 0; + for (const typename Mesh::face_index f : m.faces()) { + auto he = m.halfedge(f); + ch.indices[idx][0] = m.source(he); + he = m.next(he); + ch.indices[idx][1] = m.source(he); + he = m.next(he); + ch.indices[idx++][2] = m.source(he); + } + + *out_hulls = std::make_pair(std::move(ch.points), std::move(ch.indices)); + return 1; + } + Bbox_3 bb = bbox(mesh); const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 51e2e1491a2..acd56a28181 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -72,6 +72,7 @@ create_single_source_cgal_program("test_isolevel_refinement.cpp") create_single_source_cgal_program("test_corefinement_nm_bo.cpp") create_single_source_cgal_program("test_corefinement_cavities.cpp") create_single_source_cgal_program("issue_8730.cpp") +create_single_source_cgal_program("test_approximate_convex_decomposition.cpp") # create_single_source_cgal_program("test_pmp_repair_self_intersections.cpp") find_package(Eigen3 3.2.0 QUIET) #(requires 3.2.0 or greater) @@ -117,6 +118,7 @@ if(TARGET CGAL::TBB_support) target_link_libraries(self_intersection_surface_mesh_test PRIVATE CGAL::TBB_support) target_link_libraries(test_autorefinement PRIVATE CGAL::TBB_support) target_link_libraries(test_snap_rounding PRIVATE CGAL::TBB_support) + target_link_libraries(test_approximate_convex_decomposition PRIVATE CGAL::TBB_support) else() message(STATUS "NOTICE: Intel TBB was not found. Tests will use sequential code.") endif() diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp new file mode 100644 index 00000000000..8bceb875ba3 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +namespace PMP = CGAL::Polygon_mesh_processing; + +template +void test(const std::string &filename) { + using Point = typename K::Point_3; + + using Convex_hull = std::pair, std::vector > >; + using Mesh = CGAL::Surface_mesh; + + Mesh mesh; + if (!PMP::IO::read_polygon_mesh(filename, mesh)) { + std::cerr << "Invalid input." << std::endl; + assert(false); + } + + using Traits = typename CGAL::Convex_hull_3::internal::Default_traits_for_Chull_3::type; + + for (std::size_t i = 1; i < 10; i++) { + std::vector convex_hulls; + std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::parameters::maximum_depth(10) + .volume_error(0.1) + .maximum_number_of_convex_hulls(i) + .split_at_concavity(true) + .maximum_number_of_voxels(1000000) + .concurrency_tag(CGAL::Parallel_if_available_tag())); + + assert(n == i); + assert(convex_hulls.size() == i); + for (std::size_t i = 0; i < convex_hulls.size(); i++) { + Mesh m; + PMP::polygon_soup_to_polygon_mesh(convex_hulls[i].first, convex_hulls[i].second, m); + + assert(CGAL::is_strongly_convex_3(m, Traits())); + } + std::cout << convex_hulls.size() << std::endl; + } +} + +int main(int argc, char* argv[]) +{ + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); + std::cout << "Testing approximate convex decomposition with EPICK Kernel of " << filename << std::endl; + test(filename); + std::cout << "Testing approximate convex decomposition with EPECK Kernel of " << filename << std::endl; + test(filename); + + std::cout << "done" << std::endl; + + return 0; +} From 2f0282c0cc05905dc274ce50fbe3f39c191f5d4d Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 7 Jul 2025 10:35:45 +0200 Subject: [PATCH 12/49] fixing warnings --- .../approximate_convex_decomposition.h | 49 +++++++------------ .../test_approximate_convex_decomposition.cpp | 6 +-- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index e4e4b054102..2e158e34ce4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -160,9 +160,6 @@ void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& voxels, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; std::ofstream stream(filename); stream << @@ -309,7 +306,7 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u break; vox(x, y, z) = label; - if (x > 0) + if (x > 0) { if (vox(x - 1, y, z) == INSIDE) { if (!xneg) { xneg = true; @@ -317,8 +314,9 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else xneg = false; + } - if (x < grid_size[0] - 1) + if (x < grid_size[0] - 1) { if (vox(x + 1, y, z) == INSIDE) { if (!xpos) { xpos = true; @@ -326,8 +324,9 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else xpos = false; + } - if (y > 0) + if (y > 0) { if (vox(x, y - 1, z) == INSIDE) { if (!yneg) { yneg = true; @@ -335,8 +334,9 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else yneg = false; + } - if (y < grid_size[1] - 1) + if (y < grid_size[1] - 1) { if (vox(x, y + 1, z) == INSIDE) { if (!ypos) { ypos = true; @@ -344,6 +344,7 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else ypos = false; + } } xneg = xpos = yneg = ypos = zneg = zpos = false; @@ -356,7 +357,7 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u break; vox(x, y, z) = label; - if (x > 0) + if (x > 0) { if (vox(x - 1, y, z) == INSIDE) { if (!xneg) { xneg = true; @@ -364,8 +365,9 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else xneg = false; + } - if (x < grid_size[0] - 1) + if (x < grid_size[0] - 1) { if (vox(x + 1, y, z) == INSIDE) { if (!xpos) { xpos = true; @@ -373,8 +375,9 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else xpos = false; + } - if (y > 0) + if (y > 0) { if (vox(x, y - 1, z) == INSIDE) { if (!yneg) { yneg = true; @@ -382,8 +385,9 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else yneg = false; + } - if (y < grid_size[1] - 1) + if (y < grid_size[1] - 1) { if (vox(x, y + 1, z) == INSIDE) { if (!ypos) { ypos = true; @@ -391,6 +395,7 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } } else ypos = false; + } } } } @@ -553,7 +558,6 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; }; using face_descriptor = typename boost::graph_traits::face_descriptor; - using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; using Point_3 = typename GeomTraits::Point_3; using Vector_3 = typename GeomTraits::Vector_3; @@ -562,7 +566,6 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con using Primitive = CGAL::AABB_face_graph_triangle_primitive; using Traits = CGAL::AABB_traits_3; using Tree = CGAL::AABB_tree; - using Primitive_id = typename Tree::Primitive_id; using Ray_intersection = std::optional::Type>; Tree tree(faces(mesh).first, faces(mesh).second, mesh); @@ -800,7 +803,7 @@ struct is_std_hashable : std::false_type {}; template struct is_std_hashable>()(std::declval()))>> : std::true_type {}; -template::type::FT>, typename std::is_same::type::Kernel_tag, CGAL::Cartesian_tag>::type>::type> +template::type::FT>, typename std::is_same::type::Kernel_tag, CGAL::Cartesian_tag>::type>::type> struct Set { }; @@ -1020,6 +1023,7 @@ void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& lo std::size_t length = bbox.upper[axis] - bbox.lower[axis] + 1; CGAL_assertion(length >= 8); + CGAL_precondition(0 <= axis && axis <= 2); std::size_t idx0, idx1, idx2; @@ -1039,8 +1043,6 @@ void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& lo idx1 = 1; idx2 = 2; break; - default: - CGAL_assertion(false); } std::vector diam(length, 0), diam2(length, 0); @@ -1180,10 +1182,6 @@ bool finished(Candidate &c, const NamedParameters& np) { template void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Parallel_tag) { #ifdef CGAL_LINKED_WITH_TBB - using FT = typename GeomTraits::FT; - const FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); - const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); - std::vector> final_candidates; while (!candidates.empty()) { @@ -1265,9 +1263,6 @@ void merge(std::vector>& candidates, std::vector Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} }; - // Consider all non-equal pairs - std::size_t max_merge_candidates = (candidates.size() * (candidates.size() - 1)) >> 1; - tbb::concurrent_unordered_map> hulls; std::atomic num_hulls = candidates.size(); @@ -1284,7 +1279,7 @@ void merge(std::vector>& candidates, std::vector std::vector todo; tbb::concurrent_priority_queue queue; - const auto do_merge = [hull_volume, &hulls, &num_hulls, &queue](Merged_candidate& m) { + const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { Convex_hull& ci = hulls[m.ch_a]; Convex_hull& cj = hulls[m.ch_b]; m.ch = num_hulls.fetch_add(1); @@ -1401,9 +1396,6 @@ void merge(std::vector>& candidates, std::vector Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} }; - // Consider all non-equal pairs - std::size_t max_merge_candidates = (candidates.size() * (candidates.size() - 1)) >> 1; - std::unordered_map> hulls; std::size_t num_hulls = candidates.size(); @@ -1606,9 +1598,6 @@ void merge(std::vector>& candidates, std::vector template std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { using Geom_traits = typename GetGeomTraits::type; - using Vertex_point_map = typename GetVertexPointMap::type; - - Vertex_point_map vpm = parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, mesh)); using FT = typename Geom_traits::FT; const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp index 8bceb875ba3..6be913de20e 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp @@ -32,8 +32,6 @@ void test(const std::string &filename) { assert(false); } - using Traits = typename CGAL::Convex_hull_3::internal::Default_traits_for_Chull_3::type; - for (std::size_t i = 1; i < 10; i++) { std::vector convex_hulls; std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), @@ -43,14 +41,14 @@ void test(const std::string &filename) { .split_at_concavity(true) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); - + CGAL_USE(n); assert(n == i); assert(convex_hulls.size() == i); for (std::size_t i = 0; i < convex_hulls.size(); i++) { Mesh m; PMP::polygon_soup_to_polygon_mesh(convex_hulls[i].first, convex_hulls[i].second, m); - assert(CGAL::is_strongly_convex_3(m, Traits())); + assert(CGAL::is_strongly_convex_3(m, typename CGAL::Convex_hull_3::internal::Default_traits_for_Chull_3::type())); } std::cout << convex_hulls.size() << std::endl; } From 32f828678374e2679672c4f29d5bf40c0574c2af Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 7 Jul 2025 12:42:12 +0200 Subject: [PATCH 13/49] updated CHANGES.md --- Installation/CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index ed6e884697b..e163437e6d0 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -1,5 +1,10 @@ # Release History +## [Release 6.2](https://github.com/CGAL/cgal/releases/tag/v6.2) + +### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) +- Added the function `CGAL::Polygon_mesh_processing::approximate_convex_decomposition` that decomposes an input mesh into a specified number of convex hulls. It performs an hierarchical splitting of the convex hull guided by an volumetric error and performs a subsequent merging of convex hulls to obtain the targeted number. + ## [Release 6.1](https://github.com/CGAL/cgal/releases/tag/v6.1) From 43dae0f7d0994b3f4cb58e6cbaa6073db21de9e8 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 7 Jul 2025 12:54:54 +0200 Subject: [PATCH 14/49] typo fix --- Installation/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index e163437e6d0..e3d95261d44 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -2,7 +2,7 @@ ## [Release 6.2](https://github.com/CGAL/cgal/releases/tag/v6.2) -### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) +### [Polygon Mesh Processing](https://doc.cgal.org/6.2/Manual/packages.html#PkgPolygonMeshProcessing) - Added the function `CGAL::Polygon_mesh_processing::approximate_convex_decomposition` that decomposes an input mesh into a specified number of convex hulls. It performs an hierarchical splitting of the convex hull guided by an volumetric error and performs a subsequent merging of convex hulls to obtain the targeted number. From 35ef1cb8c7fb92c7f01ce7e0bb3e0e2856fb1041 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 7 Jul 2025 14:50:39 +0200 Subject: [PATCH 15/49] pass on doc --- .../PackageDescription.txt | 1 + .../doc/Polygon_mesh_processing/examples.txt | 1 + .../approximate_convex_decomposition.h | 15 +++++++++------ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 724fb7d85ea..5e6deacbc8d 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -282,6 +282,7 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::detect_corners_of_regions()` - `CGAL::Polygon_mesh_processing::refine_mesh_at_isolevel()` - `CGAL::Polygon_mesh_processing::refine_with_plane()` +- `CGAL::Polygon_mesh_processing::approximate_convex_decomposition()` \cgalCRPSection{I/O Functions} - \link PMP_IO_grp `CGAL::Polygon_mesh_processing::IO::read_polygon_mesh()`\endlink diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 4d657bcef5e..a597fe7b355 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -54,4 +54,5 @@ \example Polygon_mesh_processing/sample_example.cpp \example Polygon_mesh_processing/soup_autorefinement.cpp \example Polygon_mesh_processing/snap_polygon_soup.cpp +\example Polygon_mesh_processing/approximate_convex_decomposition.cpp */ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 2e158e34ce4..5b6f585a8e8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -1524,7 +1524,10 @@ void merge(std::vector>& candidates, std::vector /** * \ingroup PMP_convex_decomposition_grp * - * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the convex hull of the mesh is hierarchically split until a volume threshold is met. + * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the voxel intersecting with the mesh are labeled as surface. + * The remaining voxels are labeled as outside or inside by floodfill, in case the input mesh is closed, or by ray shooting along the axis. A voxel is only + * labeled as inside if at least 3 faces facing away from the voxel have been hit and no face facing towards the voxel. In a next step, the convex hull of the mesh + * is hierarchically split until the `volume_error` threshold is satisfied. * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. * * \tparam FaceGraph a model of `HalfedgeListGraph`, `FaceListGraph`, and `MutableFaceGraph` @@ -1558,7 +1561,7 @@ void merge(std::vector>& candidates, std::vector * \cgalParamNEnd * * \cgalParamNBegin{volume_error} - * \cgalParamDescription{maximum difference in percent of volume of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split.} + * \cgalParamDescription{maximum difference in fraction of volume of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split.} * \cgalParamType{double} * \cgalParamDefault{0.01} * \cgalParamNEnd @@ -1570,12 +1573,12 @@ void merge(std::vector>& candidates, std::vector * \cgalParamNEnd * * \cgalParamNBegin{vertex_point_map} - * \cgalParamDescription{a property map associating points to the vertices of `tmesh`} - * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` + * \cgalParamDescription{a property map associating points to the vertices of `mesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} - * \cgalParamDefault{`boost::get(CGAL::vertex_point, tmesh)`} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, mesh)`} * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` - * must be available in `TriangleMesh`.} + * must be available in `FaceGraph`.} * \cgalParamNEnd * * \cgalParamNBegin{concurrency_tag} From d7edb144648074d513dfd7dc4235844426a874c6 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Wed, 9 Jul 2025 11:53:46 +0200 Subject: [PATCH 16/49] fixed warnings reduced test complexity (to prevent timeout) --- .../approximate_convex_decomposition.h | 75 ++++++++++++------- .../test_approximate_convex_decomposition.cpp | 6 +- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 5b6f585a8e8..afe89747af2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -159,7 +159,7 @@ void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& voxels, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { +void export_voxels(const std::string& filename, const Bbox_3& bb, std::vector& voxels, double voxel_size) { std::ofstream stream(filename); stream << @@ -607,6 +607,12 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con } } ); +#else + CGAL_USE(grid); + CGAL_USE(grid_size); + CGAL_USE(bb); + CGAL_USE(voxel_size); + CGAL_USE(mesh); #endif } @@ -616,7 +622,6 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; }; using face_descriptor = typename boost::graph_traits::face_descriptor; - using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; using Point_3 = typename GeomTraits::Point_3; using Vector_3 = typename GeomTraits::Vector_3; @@ -625,7 +630,6 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con using Primitive = CGAL::AABB_face_graph_triangle_primitive; using Traits = CGAL::AABB_traits_3; using Tree = CGAL::AABB_tree; - using Primitive_id = typename Tree::Primitive_id; using Ray_intersection = std::optional::Type>; Tree tree(faces(mesh).first, faces(mesh).second, mesh); @@ -933,11 +937,11 @@ void init(Candidate &c, const FaceGraph& mesh, std::vector& compute_candidate(c, bb, voxel_size); } -template -void split(std::vector > &candidates, Candidate& c, unsigned int axis, unsigned int location, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np) { +template +void split(std::vector &candidates, Candidate_& c, unsigned int axis, unsigned int location) { //Just split the voxel bbox along 'axis' after voxel index 'location' - Candidate upper(c.depth + 1, c.bbox); - Candidate lower(c.depth + 1, c.bbox); + Candidate_ upper(c.depth + 1, c.bbox); + Candidate_ lower(c.depth + 1, c.bbox); CGAL_assertion(c.bbox.lower[axis] < location); CGAL_assertion(c.bbox.upper[axis] > location); @@ -1023,9 +1027,9 @@ void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& lo std::size_t length = bbox.upper[axis] - bbox.lower[axis] + 1; CGAL_assertion(length >= 8); - CGAL_precondition(0 <= axis && axis <= 2); + CGAL_precondition(axis <= 2); - std::size_t idx0, idx1, idx2; + std::size_t idx0 = 0, idx1 = 1, idx2 = 2; switch(axis) { case 0: @@ -1121,13 +1125,13 @@ void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& lo std::size_t conc2 = abs(diam2[pos2 + 1] - diam2[pos2]); for (std::size_t i = pos1;i conc1) { + if (unsigned(abs(diam[i] - diam[i - 1])) > conc1) { pos1 = i - 1; conc1 = abs(diam[i] - diam[i - 1]); } for (std::size_t i = pos2; i < end2; i++) - if (abs(diam2[i] - diam2[i - 1]) > conc2) { + if (unsigned(abs(diam2[i] - diam2[i - 1])) > conc2) { pos2 = i - 1; conc2 = abs(diam2[i] - diam2[i - 1]); } @@ -1159,10 +1163,6 @@ void choose_splitting_plane(Candidate& c, unsigned int &axis, unsign template bool finished(Candidate &c, const NamedParameters& np) { const typename GeomTraits::FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); - const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); - - if (c.depth > max_depth) - return true; if (c.ch.volume_error <= max_error) return true; @@ -1182,9 +1182,14 @@ bool finished(Candidate &c, const NamedParameters& np) { template void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Parallel_tag) { #ifdef CGAL_LINKED_WITH_TBB + const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + std::vector> final_candidates; - while (!candidates.empty()) { + std::size_t depth = 0; + + while (!candidates.empty() && depth < max_depth) { + depth++; std::vector> former_candidates = std::move(candidates); for (Candidate& c : former_candidates) { @@ -1197,7 +1202,7 @@ void recurse(std::vector>& candidates, std::vector } unsigned int axis = 0, location = 0; choose_splitting_plane(c, axis, location, grid, grid_size, np); - split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); + split(candidates, c, axis, location); } tbb::parallel_for_each(candidates, [&](Candidate& c) { compute_candidate(c, bbox, voxel_size); @@ -1205,18 +1210,26 @@ void recurse(std::vector>& candidates, std::vector } std::swap(candidates, final_candidates); +#else + CGAL_USE(candidates); + CGAL_USE(grid); + CGAL_USE(grid_size); + CGAL_USE(bbox); + CGAL_USE(voxel_size); + CGAL_USE(np); #endif } template void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Sequential_tag) { - using FT = typename GeomTraits::FT; - const FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 0.1); const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); std::vector> final_candidates; - while (!candidates.empty()) { + std::size_t depth = 0; + + while (!candidates.empty() && depth < max_depth) { + depth++; std::vector> former_candidates = std::move(candidates); for (Candidate& c : former_candidates) { @@ -1229,7 +1242,7 @@ void recurse(std::vector>& candidates, std::vector } unsigned int axis = 0, location = 0; choose_splitting_plane(c, axis, location, grid, grid_size, np); - split(candidates, c, axis, location, grid, grid_size, bbox, voxel_size, np); + split(candidates, c, axis, location); } for (Candidate& c : candidates) @@ -1240,8 +1253,7 @@ void recurse(std::vector>& candidates, std::vector } template -void merge(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, - const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Parallel_tag) { +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Parallel_tag) { #ifdef CGAL_LINKED_WITH_TBB const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); if (candidates.size() <= max_convex_hulls) @@ -1369,13 +1381,15 @@ void merge(std::vector>& candidates, std::vector for (std::size_t i : keep) candidates.push_back(std::move(hulls[i])); #else + CGAL_USE(candidates); + CGAL_USE(hull_volume); + CGAL_USE(np); assert(false); #endif } template -void merge(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, - const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Sequential_tag) { +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Sequential_tag) { const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); if (candidates.size() <= max_convex_hulls) return; @@ -1411,7 +1425,7 @@ void merge(std::vector>& candidates, std::vector std::priority_queue queue; - const auto do_merge = [hull_volume, &hulls, &num_hulls, &queue](Merged_candidate& m) { + const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { Convex_hull& ci = hulls[m.ch_a]; Convex_hull& cj = hulls[m.ch_b]; @@ -1606,6 +1620,13 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; +#ifndef CGAL_LINKED_WITH_TBB + if constexpr (std::is_same_v || std::is_same_v) { + CGAL_error_msg("CGAL was not compiled with TBB support. Use Sequential_tag instead."); + return 0; + } +#endif + const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); assert(max_convex_hulls > 0); @@ -1655,7 +1676,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat candidates.clear(); // merge until target number is reached - merge(hulls, grid, grid_size, bb, voxel_size, hull_volume, np, Concurrency_tag()); + merge(hulls, hull_volume, np, Concurrency_tag()); for (std::size_t i = 0; i < hulls.size(); i++) *out_hulls++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp index 6be913de20e..05aa70f269a 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp @@ -32,17 +32,17 @@ void test(const std::string &filename) { assert(false); } - for (std::size_t i = 1; i < 10; i++) { + for (std::size_t i = 1; i < 4; i++) { std::vector convex_hulls; std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10) .volume_error(0.1) - .maximum_number_of_convex_hulls(i) + .maximum_number_of_convex_hulls(i * 3) .split_at_concavity(true) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); CGAL_USE(n); - assert(n == i); + assert(n == i * 3); assert(convex_hulls.size() == i); for (std::size_t i = 0; i < convex_hulls.size(); i++) { Mesh m; From 1695487e40be735997692d83117c8247f666df3a Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 10 Jul 2025 09:59:14 +0200 Subject: [PATCH 17/49] fix test --- .../test_approximate_convex_decomposition.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp index 05aa70f269a..47a3f4b54eb 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp @@ -34,23 +34,21 @@ void test(const std::string &filename) { for (std::size_t i = 1; i < 4; i++) { std::vector convex_hulls; - std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10) .volume_error(0.1) .maximum_number_of_convex_hulls(i * 3) .split_at_concavity(true) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); - CGAL_USE(n); - assert(n == i * 3); - assert(convex_hulls.size() == i); + std::cout << convex_hulls.size() << std::endl; + assert(convex_hulls.size() == i * 3); for (std::size_t i = 0; i < convex_hulls.size(); i++) { Mesh m; PMP::polygon_soup_to_polygon_mesh(convex_hulls[i].first, convex_hulls[i].second, m); assert(CGAL::is_strongly_convex_3(m, typename CGAL::Convex_hull_3::internal::Default_traits_for_Chull_3::type())); } - std::cout << convex_hulls.size() << std::endl; } } From c5c3b39bed8f2065657a06b65e18db373ee8aceb Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Fri, 11 Jul 2025 10:01:56 +0200 Subject: [PATCH 18/49] fix of detection of TBB shortened EPECK test --- .../approximate_convex_decomposition.h | 2 +- .../test_approximate_convex_decomposition.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index afe89747af2..87e4c064f70 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -1621,7 +1621,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; #ifndef CGAL_LINKED_WITH_TBB - if constexpr (std::is_same_v || std::is_same_v) { + if constexpr (std::is_same_v) { CGAL_error_msg("CGAL was not compiled with TBB support. Use Sequential_tag instead."); return 0; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp index 47a3f4b54eb..71f5cec1fab 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_approximate_convex_decomposition.cpp @@ -20,7 +20,7 @@ using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; namespace PMP = CGAL::Polygon_mesh_processing; template -void test(const std::string &filename) { +void test(const std::string &filename, const std::vector n) { using Point = typename K::Point_3; using Convex_hull = std::pair, std::vector > >; @@ -32,20 +32,20 @@ void test(const std::string &filename) { assert(false); } - for (std::size_t i = 1; i < 4; i++) { + for (std::size_t i : n) { std::vector convex_hulls; PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10) .volume_error(0.1) - .maximum_number_of_convex_hulls(i * 3) + .maximum_number_of_convex_hulls(i) .split_at_concavity(true) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); std::cout << convex_hulls.size() << std::endl; - assert(convex_hulls.size() == i * 3); - for (std::size_t i = 0; i < convex_hulls.size(); i++) { + assert(convex_hulls.size() == i); + for (std::size_t j = 0; j < convex_hulls.size(); j++) { Mesh m; - PMP::polygon_soup_to_polygon_mesh(convex_hulls[i].first, convex_hulls[i].second, m); + PMP::polygon_soup_to_polygon_mesh(convex_hulls[j].first, convex_hulls[j].second, m); assert(CGAL::is_strongly_convex_3(m, typename CGAL::Convex_hull_3::internal::Default_traits_for_Chull_3::type())); } @@ -56,9 +56,9 @@ int main(int argc, char* argv[]) { const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); std::cout << "Testing approximate convex decomposition with EPICK Kernel of " << filename << std::endl; - test(filename); + test(filename, {3, 6, 9}); std::cout << "Testing approximate convex decomposition with EPECK Kernel of " << filename << std::endl; - test(filename); + test(filename, {5}); std::cout << "done" << std::endl; From 5c34b06c9a4014a14b96477d742fa65209dc7670 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Tue, 15 Jul 2025 09:46:45 +0200 Subject: [PATCH 19/49] removing unused variable --- .../approximate_convex_decomposition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index 0cbe2ff4223..ba606976d29 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -29,7 +29,7 @@ int main(int argc, char* argv[]) } std::vector convex_hulls; - std::size_t n = PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10) .volume_error(0.1) .maximum_number_of_convex_hulls(9) From 75bb8427c0a91b78a9d102b25d3ca105d0ceb6d8 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 21 Jul 2025 16:46:21 +0200 Subject: [PATCH 20/49] integrate reviews from comments --- Installation/CHANGES.md | 2 +- .../approximate_convex_decomposition.cpp | 3 +- .../approximate_convex_decomposition.h | 142 ++++++++---------- 3 files changed, 64 insertions(+), 83 deletions(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index f43710a6b60..0a8dd094465 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -3,7 +3,7 @@ ## [Release 6.2](https://github.com/CGAL/cgal/releases/tag/v6.2) ### [Polygon Mesh Processing](https://doc.cgal.org/6.2/Manual/packages.html#PkgPolygonMeshProcessing) -- Added the function `CGAL::Polygon_mesh_processing::approximate_convex_decomposition` that decomposes an input mesh into a specified number of convex hulls. It performs an hierarchical splitting of the convex hull guided by an volumetric error and performs a subsequent merging of convex hulls to obtain the targeted number. +- Added the function [`CGAL::Polygon_mesh_processing::approximate_convex_decomposition()`](https://doc.cgal.org/6.2/Polygon_mesh_processing/group__PMP__convex__decomposition__grp.html#ga2b8cc81e89431f0623b467f385b7f6f5) that decomposes an input mesh into a specified number of convex hulls. It performs a hierarchical splitting of the convex hull guided by an volumetric error and performs a subsequent merging of convex hulls to obtain the targeted number. ## [Release 6.1](https://github.com/CGAL/cgal/releases/tag/v6.1) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp index ba606976d29..1b3e34005d4 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -29,6 +28,8 @@ int main(int argc, char* argv[]) } std::vector convex_hulls; + convex_hulls.reserve(9); + PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), CGAL::parameters::maximum_depth(10) .volume_error(0.1) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 87e4c064f70..19e6efe8448 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -76,16 +76,16 @@ void export_grid(const std::string& filename, const Bbox_3& bb, std::vector Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_traits::face_descriptor fd, const Bbox_3& bb, const FT& voxel_size) { Bbox_3 face_bb = face_bbox(fd, mesh); + double vs = to_double(voxel_size); return Bbox_uint({ - static_cast((face_bb.xmin() - bb.xmin()) / to_double(voxel_size) - 0.5), - static_cast((face_bb.ymin() - bb.ymin()) / to_double(voxel_size) - 0.5), - static_cast((face_bb.zmin() - bb.zmin()) / to_double(voxel_size) - 0.5) + static_cast((face_bb.xmin() - bb.xmin()) / vs - 0.5), + static_cast((face_bb.ymin() - bb.ymin()) / vs - 0.5), + static_cast((face_bb.zmin() - bb.zmin()) / vs - 0.5) }, { - static_cast((face_bb.xmax() - bb.xmin()) / to_double(voxel_size) + 0.5), - static_cast((face_bb.ymax() - bb.ymin()) / to_double(voxel_size) + 0.5), - static_cast((face_bb.zmax() - bb.zmin()) / to_double(voxel_size) + 0.5) + static_cast((face_bb.xmax() - bb.xmin()) / vs + 0.5), + static_cast((face_bb.ymax() - bb.ymin()) / vs + 0.5), + static_cast((face_bb.zmax() - bb.zmin()) / vs + 0.5) }); } template Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { + double vs = to_double(voxel_size); return Bbox_3( - bb.xmin() + voxel[0] * to_double(voxel_size), - bb.ymin() + voxel[1] * to_double(voxel_size), - bb.zmin() + voxel[2] * to_double(voxel_size), - bb.xmin() + (voxel[0] + 1) * to_double(voxel_size), - bb.ymin() + (voxel[1] + 1) * to_double(voxel_size), - bb.zmin() + (voxel[2] + 1) * to_double(voxel_size) + bb.xmin() + voxel[0] * vs, + bb.ymin() + voxel[1] * vs, + bb.zmin() + voxel[2] * vs, + bb.xmin() + (voxel[0] + 1) * vs, + bb.ymin() + (voxel[1] + 1) * vs, + bb.zmin() + (voxel[2] + 1) * vs ); } @@ -401,7 +403,7 @@ void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_u } // Valid voxel grids separate OUTSIDE from INSIDE via SURFACE -bool check_grid(std::vector& grid, const Vec3_uint& grid_size) { +bool is_valid(std::vector& grid, const Vec3_uint& grid_size) { const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; }; @@ -693,28 +695,6 @@ struct Convex_hull { indices = std::move(o.indices); } - Convex_hull(const Convex_hull& o) { - exit(4); - bbox = o.bbox; - voxel_volume = o.voxel_volume; - volume = o.volume; - volume_error = o.volume_error; - points = o.points; - indices = o.indices; - } - - Convex_hull& operator= (const Convex_hull& o) { - exit(3); - bbox = o.bbox; - voxel_volume = o.voxel_volume; - volume = o.volume; - volume_error = o.volume_error; - points = o.points; - indices = o.indices; - - return *this; - } - Convex_hull& operator= (Convex_hull&& o) noexcept { bbox = o.bbox; voxel_volume = o.voxel_volume; @@ -909,7 +889,7 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr if (CGAL::is_closed(mesh)) { naive_floodfill(grid, grid_size); - //check_grid(grid, grid_size); + //is_valid(grid, grid_size); } else rayshooting_fill(grid, grid_size, bb, voxel_size, mesh, tag); From 02327609dce13bd86d61e8965264f7a1d18316e3 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Mon, 21 Jul 2025 17:25:02 +0200 Subject: [PATCH 21/49] Update STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h --- .../include/CGAL/STL_Extension/internal/parameters_interface.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index a095d04421a..d714905d3fe 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -178,7 +178,8 @@ CGAL_add_named_parameter(progressive_t, progressive, progressive) CGAL_add_named_parameter(tiling_t, tiling, tiling) CGAL_add_named_parameter(dimension_t, dimension, dimension) CGAL_add_named_parameter(apply_iterative_snap_rounding_t, apply_iterative_snap_rounding, apply_iterative_snap_rounding) -CGAL_add_named_parameter(snap_grid_size_t, snap_grid_size, snap_grid_size)CGAL_add_named_parameter(maximum_number_of_voxels_t, maximum_number_of_voxels, maximum_number_of_voxels) +CGAL_add_named_parameter(snap_grid_size_t, snap_grid_size, snap_grid_size) +CGAL_add_named_parameter(maximum_number_of_voxels_t, maximum_number_of_voxels, maximum_number_of_voxels) CGAL_add_named_parameter(maximum_depth_t, maximum_depth, maximum_depth) CGAL_add_named_parameter(volume_error_t, volume_error, volume_error) CGAL_add_named_parameter(maximum_number_of_convex_hulls_t, maximum_number_of_convex_hulls, maximum_number_of_convex_hulls) From 08272ed6195a8b9e3e40fd49cad5e7a948dc1b8e Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Tue, 22 Jul 2025 13:21:11 +0200 Subject: [PATCH 22/49] integrating reviews --- .../approximate_convex_decomposition.h | 91 ++++++++++--------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 19e6efe8448..2a62ecbef40 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -224,7 +224,7 @@ Box box_union(const Box& a, const Box& b) { template std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t number_of_voxels) { - std::size_t max_voxels_axis = std::pow(number_of_voxels, 1.0 / 3.0); + std::size_t max_voxels_axis = std::cbrt(number_of_voxels); assert(max_voxels_axis > 3); // get longest axis FT longest = 0; @@ -675,7 +675,7 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con } template -struct Convex_hull { +struct Convex_hull_candidate { using FT = typename GeomTraits::FT; using Point_3 = typename GeomTraits::Point_3; Iso_cuboid_3 bbox; @@ -685,8 +685,8 @@ struct Convex_hull { std::vector points; std::vector> indices; - Convex_hull() noexcept : voxel_volume(0), volume(0), volume_error(0) {} - Convex_hull(Convex_hull&& o) noexcept { + Convex_hull_candidate() noexcept : voxel_volume(0), volume(0), volume_error(0) {} + Convex_hull_candidate(Convex_hull_candidate&& o) noexcept { bbox = o.bbox; voxel_volume = o.voxel_volume; volume = o.volume; @@ -695,7 +695,7 @@ struct Convex_hull { indices = std::move(o.indices); } - Convex_hull& operator= (Convex_hull&& o) noexcept { + Convex_hull_candidate& operator= (Convex_hull_candidate&& o) noexcept { bbox = o.bbox; voxel_volume = o.voxel_volume; volume = o.volume; @@ -717,7 +717,7 @@ struct Candidate { std::vector inside; std::size_t depth; Bbox_uint bbox; - Convex_hull ch; + Convex_hull_candidate ch; Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {index = cidx++;} Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) { index = cidx++; } @@ -788,16 +788,16 @@ template struct is_std_hashable>()(std::declval()))>> : std::true_type {}; template::type::FT>, typename std::is_same::type::Kernel_tag, CGAL::Cartesian_tag>::type>::type> -struct Set { +struct Unordered_set_if_available { }; template -struct Set { +struct Unordered_set_if_available { using type = std::unordered_set; }; template -struct Set { +struct Unordered_set_if_available { using type = std::set; }; @@ -808,7 +808,7 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom c.bbox.lower = c.bbox.upper = c.surface[0]; - typename Set::type voxel_points; + typename Unordered_set_if_available::type voxel_points; for (const Vec3_uint& v : c.surface) { enlarge(c.bbox, v); @@ -1233,7 +1233,7 @@ void recurse(std::vector>& candidates, std::vector } template -void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Parallel_tag) { +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Parallel_tag) { #ifdef CGAL_LINKED_WITH_TBB const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); if (candidates.size() <= max_convex_hulls) @@ -1255,7 +1255,7 @@ void merge(std::vector>& candidates, const typename Geom Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} }; - tbb::concurrent_unordered_map> hulls; + tbb::concurrent_unordered_map> hulls; std::atomic num_hulls = candidates.size(); std::unordered_set keep; @@ -1272,10 +1272,10 @@ void merge(std::vector>& candidates, const typename Geom tbb::concurrent_priority_queue queue; const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { - Convex_hull& ci = hulls[m.ch_a]; - Convex_hull& cj = hulls[m.ch_b]; + Convex_hull_candidate& ci = hulls[m.ch_a]; + Convex_hull_candidate& cj = hulls[m.ch_b]; m.ch = num_hulls.fetch_add(1); - Convex_hull& ch = hulls[m.ch]; + Convex_hull_candidate& ch = hulls[m.ch]; ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); @@ -1289,11 +1289,11 @@ void merge(std::vector>& candidates, const typename Geom }; for (std::size_t i : keep) { - const Convex_hull& ci = hulls[i]; + const Convex_hull_candidate& ci = hulls[i]; for (std::size_t j : keep) { if (j <= i) continue; - const Convex_hull& cj = hulls[j]; + const Convex_hull_candidate& cj = hulls[j]; if (CGAL::do_intersect(ci.bbox, cj.bbox)) todo.emplace_back(Merged_candidate(i, j)); else { @@ -1333,10 +1333,10 @@ void merge(std::vector>& candidates, const typename Geom hulls.unsafe_erase(ch_a); hulls.unsafe_erase(ch_b); - const Convex_hull& cj = hulls[m.ch]; + const Convex_hull_candidate& cj = hulls[m.ch]; for (std::size_t id : keep) { - const Convex_hull& ci = hulls[id]; + const Convex_hull_candidate& ci = hulls[id]; if (CGAL::do_intersect(ci.bbox, cj.bbox)) todo.emplace_back(Merged_candidate(id, m.ch)); else { @@ -1369,7 +1369,7 @@ void merge(std::vector>& candidates, const typename Geom } template -void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Sequential_tag) { +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Sequential_tag) { const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); if (candidates.size() <= max_convex_hulls) return; @@ -1390,7 +1390,7 @@ void merge(std::vector>& candidates, const typename Geom Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} }; - std::unordered_map> hulls; + std::unordered_map> hulls; std::size_t num_hulls = candidates.size(); std::unordered_set keep; @@ -1406,11 +1406,11 @@ void merge(std::vector>& candidates, const typename Geom std::priority_queue queue; const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { - Convex_hull& ci = hulls[m.ch_a]; - Convex_hull& cj = hulls[m.ch_b]; + Convex_hull_candidate& ci = hulls[m.ch_a]; + Convex_hull_candidate& cj = hulls[m.ch_b]; m.ch = num_hulls++; - Convex_hull& ch = hulls[m.ch]; + Convex_hull_candidate& ch = hulls[m.ch]; ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); @@ -1424,16 +1424,16 @@ void merge(std::vector>& candidates, const typename Geom }; for (std::size_t i : keep) { - const Convex_hull& ci = hulls[i]; + const Convex_hull_candidate& ci = hulls[i]; for (std::size_t j : keep) { if (j <= i) continue; - const Convex_hull& cj = hulls[j]; + const Convex_hull_candidate& cj = hulls[j]; if (CGAL::do_intersect(ci.bbox, cj.bbox)) { Merged_candidate m(i, j); m.ch = num_hulls++; - Convex_hull& ch = hulls[m.ch]; + Convex_hull_candidate& ch = hulls[m.ch]; ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); @@ -1476,15 +1476,15 @@ void merge(std::vector>& candidates, const typename Geom hulls.erase(ch_a); hulls.erase(ch_b); - const Convex_hull& cj = hulls[m.ch]; + const Convex_hull_candidate& cj = hulls[m.ch]; for (std::size_t id : keep) { - const Convex_hull& ci = hulls[id]; + const Convex_hull_candidate& ci = hulls[id]; if (CGAL::do_intersect(ci.bbox, cj.bbox)) { Merged_candidate merged(id, m.ch); merged.ch = num_hulls++; - Convex_hull& ch = hulls[merged.ch]; + Convex_hull_candidate& ch = hulls[merged.ch]; ch.bbox = box_union(ci.bbox, cj.bbox); std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); @@ -1514,14 +1514,14 @@ void merge(std::vector>& candidates, const typename Geom candidates.push_back(std::move(hulls[i])); } -} +} // namespace internal /** * \ingroup PMP_convex_decomposition_grp * - * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the voxel intersecting with the mesh are labeled as surface. - * The remaining voxels are labeled as outside or inside by floodfill, in case the input mesh is closed, or by ray shooting along the axis. A voxel is only - * labeled as inside if at least 3 faces facing away from the voxel have been hit and no face facing towards the voxel. In a next step, the convex hull of the mesh - * is hierarchically split until the `volume_error` threshold is satisfied. + * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. + * The remaining voxels are labeled as outside or inside by floodfill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, + * y and z-axis in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and + * no face facing towards the voxel. In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied. * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. * * \tparam FaceGraph a model of `HalfedgeListGraph`, `FaceListGraph`, and `MutableFaceGraph` @@ -1537,9 +1537,9 @@ void merge(std::vector>& candidates, const typename Geom * \cgalNamedParamsBegin * * \cgalParamNBegin{maximum_number_of_voxels} - * \cgalParamDescription{gives an upper bound of the number of voxels. The longest bounding box side will have a length of the cubic root of `maximum_number_of_voxels`. Cannot be smaller than 64. } + * \cgalParamDescription{gives an upper bound of the number of voxels. The longest bounding box side will have a length of the cubic root of `maximum_number_of_voxels` rounded down. Cannot be smaller than 64. } * \cgalParamType{unsigned int} - * \cgalParamDefault{1000000} + * \cgalParamDefault{1,000,000} * \cgalParamNEnd * * \cgalParamNBegin{maximum_depth} @@ -1555,13 +1555,13 @@ void merge(std::vector>& candidates, const typename Geom * \cgalParamNEnd * * \cgalParamNBegin{volume_error} - * \cgalParamDescription{maximum difference in fraction of volume of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split.} + * \cgalParamDescription{maximum difference in fraction of volume of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split if the `maximum_depth` has not been reached yet.} * \cgalParamType{double} * \cgalParamDefault{0.01} * \cgalParamNEnd * * \cgalParamNBegin{split_at_concavity} - * \cgalParamDescription{split the local box of the convex hull in the mid of the longest axis (faster) or search the concavity along the longest axis of the bounding box for splitting.} + * \cgalParamDescription{split the local box of the convex hull at the concavity along the longest axis of the bounding box. Otherwise, split in the middle of the longest axis (faster, but less precise).} * \cgalParamType{Boolean} * \cgalParamDefault{true} * \cgalParamNEnd @@ -1590,6 +1590,8 @@ void merge(std::vector>& candidates, const typename Geom * * \cgalNamedParamsEnd * + * \return number of convex hulls. Can be lower than the `maximum_number_of_convex_hulls` if the specified `volume_error` is quickly met. + * * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` */ template @@ -1611,7 +1613,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat assert(max_convex_hulls > 0); if (max_convex_hulls == 1) { - internal::Convex_hull< Geom_traits> ch; + internal::Convex_hull_candidate ch; using Mesh = Surface_mesh; Mesh m; convex_hull_3(mesh, m); @@ -1649,7 +1651,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat recurse(candidates, grid, grid_size, bb, voxel_size, np, Concurrency_tag()); - std::vector> hulls; + std::vector> hulls; for (internal::Candidate &c : candidates) hulls.emplace_back(std::move(c.ch)); @@ -1663,7 +1665,8 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat return hulls.size(); } -} -} -#endif \ No newline at end of file +} // namespace Polygon_mesh_processing +} // namespace CGAL + +#endif // CGAL_POLYGON_MESH_PROCESSING_CONVEX_DECOMPOSITION_H From 7f4eb78cdd9d7f0e2b1d2e834838daee55ed5ed1 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 8 Sep 2025 11:55:32 +0200 Subject: [PATCH 23/49] bugfixes --- .../approximate_convex_decomposition.h | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 2a62ecbef40..264612085f4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -851,6 +851,7 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom c.ch.volume = volume(c.ch.points, c.ch.indices); CGAL_assertion(c.ch.volume > 0); + c.ch.bbox = bbox_3(c.ch.points.begin(), c.ch.points.end()); c.ch.voxel_volume = (voxel_size * voxel_size * voxel_size) * FT(double(c.inside.size() + c.surface.size() + c.new_surface.size())); c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; @@ -1116,13 +1117,19 @@ void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& lo conc2 = abs(diam2[i] - diam2[i - 1]); } - if (conc2 > conc1) - pos1 = pos2; - if (pos1 < 2 || (length - 3) < pos1) - location = (bbox.upper[axis] + bbox.lower[axis]) / 2; - else - location = ((conc1 > conc2) ? pos1 : pos2) + bbox.lower[axis]; + if (conc1 <= conc2) { + if (pos1 < 2 || (length - 3) < pos1) + location = (bbox.upper[axis] + bbox.lower[axis]) / 2; + else + location = pos1 + bbox.lower[axis]; + } + else { + if (pos2 < 2 || (length - 3) < pos2) + location = (bbox.upper[axis] + bbox.lower[axis]) / 2; + else + location = pos2 + bbox.lower[axis]; + } } template @@ -1189,7 +1196,11 @@ void recurse(std::vector>& candidates, std::vector }); } - std::swap(candidates, final_candidates); + if (candidates.empty()) + std::swap(candidates, final_candidates); + else + std::move(final_candidates.begin(), final_candidates.end(), std::back_inserter(candidates)); + #else CGAL_USE(candidates); CGAL_USE(grid); @@ -1229,7 +1240,10 @@ void recurse(std::vector>& candidates, std::vector compute_candidate(c, bbox, voxel_size); } - std::swap(candidates, final_candidates); + if (candidates.empty()) + std::swap(candidates, final_candidates); + else + std::move(final_candidates.begin(), final_candidates.end(), std::back_inserter(candidates)); } template From e27d1e762a60de7627d5a42cd37c4f72a877ed3c Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Wed, 10 Sep 2025 18:07:32 +0200 Subject: [PATCH 24/49] switching from convex_hull_3 using Surface_mesh to indexed triangle list removing Surface_mesh dependency --- .../approximate_convex_decomposition.h | 49 +++---------------- .../Polygon_mesh_processing/dependencies | 1 - 2 files changed, 6 insertions(+), 44 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 264612085f4..fa1a012fc3c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -16,6 +16,7 @@ #include #include +#include #include @@ -30,9 +31,6 @@ #include #include -#include -#include - #include #include #include @@ -46,11 +44,6 @@ #include #endif -#include -#include - -#include - namespace CGAL { namespace Polygon_mesh_processing { namespace internal { @@ -742,36 +735,6 @@ typename GeomTraits::FT volume(const std::vector & return vol; } - -template -void convex_hull(const PointRange& pts, std::vector &hull_points, std::vector > &hull_indices) { - using Mesh = CGAL::Surface_mesh; - Mesh m; - - convex_hull_3(pts.begin(), pts.end(), m); -// if (pts.size() < 20) { -// export_points("convex_hull" + std::to_string(cnt) + ".xyz", pts); -// std::cout << pts.size() << " " << cnt++ << std::endl; -// convex_hull_3(pts.begin(), pts.end(), hull_points, hull_indices);// needs bugfix in convex_hull_3 for indexed triangle list -// } - hull_points.resize(m.number_of_vertices()); - hull_indices.resize(m.number_of_faces()); - - std::size_t idx = 0; - for (const typename Mesh::vertex_index v : m.vertices()) - hull_points[idx++] = m.point(v); - - idx = 0; - for (const typename Mesh::face_index f : m.faces()) { - auto he = m.halfedge(f); - hull_indices[idx][0] = m.source(he); - he = m.next(he); - hull_indices[idx][1] = m.source(he); - he = m.next(he); - hull_indices[idx++][2] = m.source(he); - } -} - void enlarge(Bbox_uint& bbox, const Vec3_uint& v) { bbox.lower[0] = (std::min)(bbox.lower[0], v[0]); bbox.lower[1] = (std::min)(bbox.lower[1], v[1]); @@ -846,7 +809,7 @@ void compute_candidate(Candidate &c, const Bbox_3& bb, typename Geom voxel_points.insert(Point_3(xmax, ymax, zmax)); } - convex_hull(voxel_points, c.ch.points, c.ch.indices); + convex_hull_3(voxel_points.begin(), voxel_points.end(), c.ch.points, c.ch.indices); c.ch.volume = volume(c.ch.points, c.ch.indices); @@ -1295,7 +1258,7 @@ void merge(std::vector>& candidates, const typ std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull(pts, ch.points, ch.indices); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); ch.volume = volume(ch.points, ch.indices); @@ -1430,7 +1393,7 @@ void merge(std::vector>& candidates, const typ std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull(pts, ch.points, ch.indices); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); ch.volume = volume(ch.points, ch.indices); @@ -1452,7 +1415,7 @@ void merge(std::vector>& candidates, const typ std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull(pts, ch.points, ch.indices); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); ch.volume = volume(ch.points, ch.indices); @@ -1503,7 +1466,7 @@ void merge(std::vector>& candidates, const typ std::vector pts(ci.points.begin(), ci.points.end()); pts.reserve(pts.size() + cj.points.size()); std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull(pts, ch.points, ch.indices); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); ch.volume = volume(ch.points, ch.indices); diff --git a/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies b/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies index a95eb459dc5..939741eec28 100644 --- a/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies +++ b/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies @@ -34,7 +34,6 @@ Spatial_searching Spatial_sorting Stream_support Subdivision_method_3 -Surface_mesh TDS_2 TDS_3 Triangulation_2 From bd8075e3163b4d3b1fbd8b8424abbbecf7f350e7 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 11 Sep 2025 10:43:11 +0200 Subject: [PATCH 25/49] removing use of Surface_mesh --- .../approximate_convex_decomposition.h | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index fa1a012fc3c..ade37809dd1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -1591,26 +1591,16 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat if (max_convex_hulls == 1) { internal::Convex_hull_candidate ch; - using Mesh = Surface_mesh; - Mesh m; - convex_hull_3(mesh, m); - ch.points.resize(m.number_of_vertices()); - ch.indices.resize(m.number_of_faces()); + using parameters::choose_parameter; + using parameters::get_parameter; + using VPM = typename GetVertexPointMap::const_type; + typedef CGAL::Property_map_to_unary_function Vpmap_fct; - std::size_t idx = 0; - for (const typename Mesh::vertex_index v : m.vertices()) - ch.points[idx++] = m.point(v); + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, mesh)); + Vpmap_fct v2p(vpm); - idx = 0; - for (const typename Mesh::face_index f : m.faces()) { - auto he = m.halfedge(f); - ch.indices[idx][0] = m.source(he); - he = m.next(he); - ch.indices[idx][1] = m.source(he); - he = m.next(he); - ch.indices[idx++][2] = m.source(he); - } + convex_hull_3(boost::make_transform_iterator(vertices(mesh).begin(), v2p), boost::make_transform_iterator(vertices(mesh).end(), v2p), ch.points, ch.indices); *out_hulls = std::make_pair(std::move(ch.points), std::move(ch.indices)); return 1; From 9eb9babb2fdf8c565219698471ab379d15430886 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Wed, 12 Nov 2025 17:15:49 +0100 Subject: [PATCH 26/49] integrating review cleaning up --- .../approximate_convex_decomposition.h | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index ade37809dd1..5695c2a1c06 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -216,7 +216,7 @@ Box box_union(const Box& a, const Box& b) { } template -std::tuple calculate_grid_size(Bbox_3& bb, const std::size_t number_of_voxels) { +std::tuple calculate_grid_size(Bbox_3& bb, const unsigned int number_of_voxels) { std::size_t max_voxels_axis = std::cbrt(number_of_voxels); assert(max_voxels_axis > 3); // get longest axis @@ -844,23 +844,11 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr } } - //export_grid("before.ply", bb, grid, grid_size, voxel_size); - //auto filterS = [](const int8_t& l) -> bool {return l == SURFACE; }; - //auto filterO = [](const int8_t& l) -> bool {return l == OUTSIDE; }; - //auto filterI = [](const int8_t& l) -> bool {return l == INSIDE; }; - //export_grid("before_surface_ray.ply", bb, grid, grid_size, voxel_size, filterS); - //export_grid("before_outside.ply", bb, grid, grid_size, voxel_size, filterO); - - if (CGAL::is_closed(mesh)) { + if (CGAL::is_closed(mesh)) naive_floodfill(grid, grid_size); - //is_valid(grid, grid_size); - } else rayshooting_fill(grid, grid_size, bb, voxel_size, mesh, tag); - //export_grid("after_inside_ray.ply", bb, grid, grid_size, voxel_size, filterI); - //export_grid("after_outside_ray.ply", bb, grid, grid_size, voxel_size, filterO); - c.bbox.upper = {grid_size[0] - 1, grid_size[1] - 1, grid_size[2] - 1}; for (unsigned int x = 0; x < grid_size[0]; x++) @@ -870,9 +858,6 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr c.inside.push_back({x, y, z}); else if (vox(x, y, z) == SURFACE) c.surface.push_back({x, y, z}); - - //export_grid("after_inside.ply", bb, grid, grid_size, voxel_size, filterI); - //export_grid("after_outside.ply", bb, grid, grid_size, voxel_size, filterO); } template @@ -1112,7 +1097,7 @@ void choose_splitting_plane(Candidate& c, unsigned int &axis, unsign template bool finished(Candidate &c, const NamedParameters& np) { - const typename GeomTraits::FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 1); + const typename GeomTraits::FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 0.01); if (c.ch.volume_error <= max_error) return true; @@ -1209,10 +1194,9 @@ void recurse(std::vector>& candidates, std::vector std::move(final_candidates.begin(), final_candidates.end(), std::back_inserter(candidates)); } -template -void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Parallel_tag) { +template +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const unsigned int max_convex_hulls, CGAL::Parallel_tag) { #ifdef CGAL_LINKED_WITH_TBB - const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); if (candidates.size() <= max_convex_hulls) return; @@ -1345,9 +1329,8 @@ void merge(std::vector>& candidates, const typ #endif } -template -void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const NamedParameters& np, CGAL::Sequential_tag) { - const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); +template +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const unsigned int max_convex_hulls, CGAL::Sequential_tag) { if (candidates.size() <= max_convex_hulls) return; @@ -1501,13 +1484,13 @@ void merge(std::vector>& candidates, const typ * no face facing towards the voxel. In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied. * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. * - * \tparam FaceGraph a model of `HalfedgeListGraph`, `FaceListGraph`, and `MutableFaceGraph` + * \tparam FaceGraph a model of `HalfedgeListGraph`, and `FaceListGraph` * * \tparam OutputIterator must be an output iterator accepting variables of type `std::pair, std::vector > >`. * * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * - * \param mesh the input mesh to approximate by convex hulls + * \param tmesh the input triangle mesh to approximate by convex hulls * \param out_hulls output iterator into which convex hulls are recorded * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * @@ -1572,11 +1555,11 @@ void merge(std::vector>& candidates, const typ * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` */ template -std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { +std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { using Geom_traits = typename GetGeomTraits::type; using FT = typename Geom_traits::FT; - const std::size_t num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); + const unsigned int num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; #ifndef CGAL_LINKED_WITH_TBB @@ -1586,7 +1569,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat } #endif - const std::size_t max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 64); + const unsigned int max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 16); assert(max_convex_hulls > 0); if (max_convex_hulls == 1) { @@ -1597,22 +1580,22 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat using VPM = typename GetVertexPointMap::const_type; typedef CGAL::Property_map_to_unary_function Vpmap_fct; - VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, mesh)); + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, tmesh)); Vpmap_fct v2p(vpm); - convex_hull_3(boost::make_transform_iterator(vertices(mesh).begin(), v2p), boost::make_transform_iterator(vertices(mesh).end(), v2p), ch.points, ch.indices); + convex_hull_3(boost::make_transform_iterator(vertices(tmesh).begin(), v2p), boost::make_transform_iterator(vertices(tmesh).end(), v2p), ch.points, ch.indices); *out_hulls = std::make_pair(std::move(ch.points), std::move(ch.indices)); return 1; } - Bbox_3 bb = bbox(mesh); + Bbox_3 bb = bbox(tmesh); const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); std::vector> candidates(1); - init(candidates[0], mesh, grid, bb, grid_size, voxel_size, Concurrency_tag()); + init(candidates[0], tmesh, grid, bb, grid_size, voxel_size, Concurrency_tag()); const FT hull_volume = candidates[0].ch.volume; @@ -1625,7 +1608,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& mesh, OutputIterat candidates.clear(); // merge until target number is reached - merge(hulls, hull_volume, np, Concurrency_tag()); + merge(hulls, hull_volume, max_convex_hulls, Concurrency_tag()); for (std::size_t i = 0; i < hulls.size(); i++) *out_hulls++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); From 7441d510ef153ce6939b06c639ff1ebd6537c164 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Wed, 12 Nov 2025 17:17:44 +0100 Subject: [PATCH 27/49] Apply suggestions from code review Co-authored-by: Mael --- .../approximate_convex_decomposition.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index 5695c2a1c06..b838b255e51 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -1475,11 +1475,12 @@ void merge(std::vector>& candidates, const typ } } // namespace internal + /** * \ingroup PMP_convex_decomposition_grp * * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. - * The remaining voxels are labeled as outside or inside by floodfill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, + * The remaining voxels are labeled as outside or inside by flood fill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, * y and z-axis in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and * no face facing towards the voxel. In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied. * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. @@ -1515,13 +1516,13 @@ void merge(std::vector>& candidates, const typ * \cgalParamNEnd * * \cgalParamNBegin{volume_error} - * \cgalParamDescription{maximum difference in fraction of volume of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split if the `maximum_depth` has not been reached yet.} + * \cgalParamDescription{maximum difference in fraction of volumes of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split if the `maximum_depth` has not been reached yet.} * \cgalParamType{double} * \cgalParamDefault{0.01} * \cgalParamNEnd * * \cgalParamNBegin{split_at_concavity} - * \cgalParamDescription{split the local box of the convex hull at the concavity along the longest axis of the bounding box. Otherwise, split in the middle of the longest axis (faster, but less precise).} + * \cgalParamDescription{If `true`, the local box of a convex hull is split at the concavity along the longest axis of the bounding box. Otherwise, it is split in the middle of the longest axis, which is faster, but less precise.} * \cgalParamType{Boolean} * \cgalParamDefault{true} * \cgalParamNEnd @@ -1550,7 +1551,7 @@ void merge(std::vector>& candidates, const typ * * \cgalNamedParamsEnd * - * \return number of convex hulls. Can be lower than the `maximum_number_of_convex_hulls` if the specified `volume_error` is quickly met. + * \return the number of convex hulls. Note that this value may be lower than the `maximum_number_of_convex_hulls`, for example if the specified `volume_error` is quickly met. * * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` */ From f3be343e73616717009e308d371fe8deca74a654 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Sun, 16 Nov 2025 21:40:59 +0100 Subject: [PATCH 28/49] added demo plugin for approximate convex decomposition --- ...Approximate_convex_decomposition_dialog.ui | 198 ++++++++++++++++++ ...pproximate_convex_decomposition_plugin.cpp | 151 +++++++++++++ .../Convex_decomposition/CMakeLists.txt | 10 +- 3 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui create mode 100644 Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui new file mode 100644 index 00000000000..86618c748c6 --- /dev/null +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui @@ -0,0 +1,198 @@ + + + Approximate_convex_decomposition_dialog + + + + 0 + 0 + 448 + 215 + + + + Approximate Convex Decomposition + + + + + + + 15 + + + + Approximate Convex Decomposition + + + + + + + 0.01 + + + + + + + Maximum number of components + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Number of voxels + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Volume error + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Maximum decomposition depth + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::RightToLeft + + + Split at concavity + + + true + + + + + + + 512 + + + 1000000000 + + + 100000 + + + 1000000 + + + + + + + 2 + + + 50 + + + 10 + + + + + + + 1 + + + 10000 + + + 16 + + + + + + + + DoubleEdit + QLineEdit +
CGAL_double_edit.h
+
+
+ + + + buttonBox + accepted() + Approximate_convex_decomposition_dialog + accept() + + + 384 + 191 + + + 157 + 195 + + + + + buttonBox + rejected() + Approximate_convex_decomposition_dialog + reject() + + + 384 + 191 + + + 286 + 195 + + + + +
diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp new file mode 100644 index 00000000000..8e2671ff008 --- /dev/null +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp @@ -0,0 +1,151 @@ +#include "config.h" + +#include +#include +#include +#include "ui_Approximate_convex_decomposition_dialog.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "Scene_surface_mesh_item.h" +#include "Color_map.h" +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace CGAL::Three; + +class CGAL_Lab_approximate_convex_decomposition_plugin + : public QObject, + protected CGAL_Lab_plugin_interface +{ + Q_OBJECT + Q_INTERFACES(CGAL::Three::CGAL_Lab_plugin_interface) + Q_PLUGIN_METADATA(IID "com.geometryfactory.CGALLab.PluginInterface/1.0") + + using Convex_hull = std::pair, std::vector > >; + +private: + QAction* actionApproximateConvexDecomposition; + + Scene_interface *scene; + QMainWindow *mw; + +public: + void init(QMainWindow* mainWindow, + Scene_interface* scene_interface, + Messages_interface*) + { + this->scene = scene_interface; + this->mw = mainWindow; + + actionApproximateConvexDecomposition = new QAction(tr("Approximate Convex Decomposition"), mw); + actionApproximateConvexDecomposition->setProperty("subMenuName", "Polygon Mesh Processing"); + connect(actionApproximateConvexDecomposition, SIGNAL(triggered()), + this, SLOT(approximate_convex_decomposition())); + } + + bool applicable(QAction* action) const + { + if(action == actionApproximateConvexDecomposition) + { + if(scene->selectionIndices().size() == 1) + { + const int index = scene->mainSelectionIndex(); + return (qobject_cast(scene->item(index))); + } + } + + return false; + } + + QList actions() const + { + return QList() << actionApproximateConvexDecomposition; + } + +public Q_SLOTS: + void approximate_convex_decomposition(); +}; // class CGAL_Lab_approximate_convex_decomposition_plugin + +void +CGAL_Lab_approximate_convex_decomposition_plugin:: +approximate_convex_decomposition() +{ + Scene_surface_mesh_item* sm_item = nullptr; + + if (scene->selectionIndices().size() != 1) + return; + + sm_item = qobject_cast(scene->item(scene->selectionIndices().front())); + if (sm_item == nullptr) + return; + + QDialog dialog(mw); + Ui::Approximate_convex_decomposition_dialog ui; + ui.setupUi(&dialog); + + int i = dialog.exec(); + if (i == QDialog::Rejected) + return; + + const unsigned int maximumDepth = static_cast(ui.maximumDepth->value()); + const unsigned int maximumConvexHulls = static_cast(ui.maximumConvexHulls->value()); + const unsigned int numVoxels = static_cast(ui.numVoxels->value()); + const double volumeError = ui.volumeError->value(); + const bool splitConcavity = ui.splitConcavity->isChecked(); + + QApplication::setOverrideCursor(Qt::WaitCursor); + + std::vector convex_hulls; + convex_hulls.reserve(9); + + CGAL::Polygon_mesh_processing::approximate_convex_decomposition(*(sm_item->face_graph()), std::back_inserter(convex_hulls), + CGAL::parameters::maximum_depth(maximumDepth) + .volume_error(volumeError) + .maximum_number_of_convex_hulls(maximumConvexHulls) + .split_at_concavity(splitConcavity) + .maximum_number_of_voxels(numVoxels) + .concurrency_tag(CGAL::Parallel_if_available_tag())); + + + std::vector distinct_colors; + // the first color is either the background or the unique domain + + compute_deterministic_color_map(QColor(80, 250, 80), convex_hulls.size(), std::back_inserter(distinct_colors)); + + for (std::size_t i = 0; i < convex_hulls.size(); i++) { + const Convex_hull& ch = convex_hulls[i]; + SMesh sm; + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(ch.first, ch.second, sm); + + Scene_surface_mesh_item* component_item = new Scene_surface_mesh_item(sm); + component_item->setName(tr("%1 %2").arg(sm_item->name()).arg(i)); + component_item->setColor(distinct_colors[i]); + Three::scene()->addItem(component_item); + } + + QApplication::restoreOverrideCursor(); + + QApplication::restoreOverrideCursor(); + + QApplication::setOverrideCursor(Qt::BusyCursor); + QApplication::restoreOverrideCursor(); +} + + +#include "Approximate_convex_decomposition_plugin.moc" diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt b/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt index b17a2a80c14..d4d7740ff8e 100644 --- a/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt @@ -1,5 +1,11 @@ include(CGALlab_macros) -cgal_lab_plugin(nef_plugin Nef_plugin) -target_link_libraries(nef_plugin PRIVATE scene_nef_polyhedron_item scene_surface_mesh_item) +set(CMAKE_AUTOMOC ON) +cgal_lab_plugin(nef_plugin Nef_plugin) + +qt6_wrap_ui(acdUI_FILES Approximate_convex_decomposition_dialog.ui) +cgal_lab_plugin(acd_plugin Approximate_convex_decomposition_plugin ${acdUI_FILES}) + +target_link_libraries(nef_plugin PRIVATE scene_nef_polyhedron_item scene_surface_mesh_item) +target_link_libraries(acd_plugin PRIVATE scene_surface_mesh_item) From 2b97365ef11bab25327185f834cd3628f9eb34d4 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 17 Nov 2025 09:05:58 +0100 Subject: [PATCH 29/49] bugfix removed CGAL_USE --- .../Polygon_mesh_processing/approximate_convex_decomposition.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h index b838b255e51..6d8ac2a5916 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h @@ -1324,7 +1324,6 @@ void merge(std::vector>& candidates, const typ #else CGAL_USE(candidates); CGAL_USE(hull_volume); - CGAL_USE(np); assert(false); #endif } From 24dc3cddddaf4010f142b9f646b2cff4799497b1 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 12:08:02 +0100 Subject: [PATCH 30/49] created Surface_mesh_decomposition package added approximate_convex_decomposition doc to Convex_decomposition_3 added license file --- .../Convex_decomposition_3.txt | 283 ++- .../doc/Convex_decomposition_3/Doxyfile.in | 4 + .../PackageDescription.txt | 6 +- .../doc/Convex_decomposition_3/dependencies | 1 + .../doc/Convex_decomposition_3/examples.txt | 1 + .../CGAL/license/Surface_mesh_decomposition.h | 54 + .../include/CGAL/license/gpl_package_list.txt | 1 + .../Surface_mesh_decomposition/CMakeLists.txt | 19 + .../approximate_convex_decomposition.cpp | 47 + .../CGAL/approximate_convex_decomposition.h | 1678 +++++++++++++++++ .../Surface_mesh_decomposition/copyright | 1 + .../Surface_mesh_decomposition/dependencies | 25 + .../Surface_mesh_decomposition/license.txt | 1 + .../Surface_mesh_decomposition/maintainer | 1 + .../Surface_mesh_decomposition/CMakeLists.txt | 10 + .../Surface_mesh_decomposition/test_acd.cpp | 58 + 16 files changed, 2185 insertions(+), 5 deletions(-) create mode 100644 Installation/include/CGAL/license/Surface_mesh_decomposition.h create mode 100644 Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt create mode 100644 Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp create mode 100644 Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h create mode 100644 Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/copyright create mode 100644 Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies create mode 100644 Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt create mode 100644 Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/maintainer create mode 100644 Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt create mode 100644 Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index 3a36080cd66..bfd47c38e97 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -17,8 +17,20 @@ decomposing both polyhedra into convex pieces, compute pairwise Minkowski sums of the convex pieces, and unite the pairwise sums. While it is desirable to have a decomposition into a minimum number of -pieces, this problem is known to be NP-hard \cgalCite{c-cpplb-84}. Our -implementation decomposes a Nef polyhedron \f$ N\f$ into \cgalBigO{r^2} convex +pieces, this problem is known to be NP-hard \cgalCite{c-cpplb-84}. This +package offers two methods for decomposing polyhedra. The +\ref Convex_decomposition_3Nef "Convex Decomposition of Nef Polyhedra" +splits polyhedra into convex pieces with an upper bound on the number +of pieces. The \ref Convex_decomposition_3ACD_Intro +"Approximate convex decomposition" method offers a fast +approximate decomposition of the convex hull into convex volumes. While +any number of convex volumes can be generated, these convex volumes +are more compact than the convex hull, but still include additional +empty space than just the input polyhedron. + +\section Convex_decomposition_3Nef Convex Decomposition of Nef Polyhedra + +Our implementation decomposes a Nef polyhedron \f$ N\f$ into \cgalBigO{r^2} convex pieces, where \f$ r\f$ is the number of edges that have two adjacent facets that span an angle of more than 180 degrees with respect to the interior of the polyhedron. Those edges are also called reflex edges. @@ -38,7 +50,7 @@ illustrates the two steps. At the moment our implementation is restricted to the decomposition of bounded polyhedra. An extension to unbounded polyhedra is planned. -\section Convex_decomposition_3InterfaceandUsage Interface and Usage +\subsection Convex_decomposition_3InterfaceandUsage Interface and Usage An instance of `Nef_polyhedron_3` represents a subdivision of the three-dimensional space into vertices, edges, facets, and @@ -79,6 +91,271 @@ support the use of extended kernels in the convex decomposition. \cgalExample{Convex_decomposition_3/list_of_convex_parts.cpp} +\section Convex_decomposition_3ACD_Intro Approximate Convex Decomposition + +\cgalFigureAnchor{Acd_topfig} +
+ +
+\cgalFigureCaptionBegin{Acd_topfig} +Approximate convex decomposition of the elephant.off model.\n From left to right: 1. input mesh 2. 5 convex volumes 3. 8 convex volumes 4. 12 convex volumes +\cgalFigureCaptionEnd + +The H-VACD, introduced by computes a set of convex volumes that fit the polyhedron. Contrary to the decomposition of the polyhedron into convex parts, the convex volumes cover the polyhedron, but also include additional volume outside of the polyhedron. +A sufficiently tight enclosure of the polyhedron by several convex volumes allows fast intersection calculation and collision detection among polyhedra while offering a non-hierachical approach and may thus be easier to use in a parallel setting. +The resulting set of convex volumes minimizes the volume between their union and the polyhedron while fully including the input polyhedron. While the optimal solution with `n` convex hulls that cover the polydron with the smallest additional volume remains NP-hard, this method provides a fast error-driven approximation. + +\subsection Convex_decomposition_3ACD_Algorithm Algorithm +The algorithm computes a set of convex volumes \f$ C=\{C_i\f$ with \f$ i \in[0..n-1]\} \f$ that cover the input polyhedron while minimizing the additional covered volume: + +\f{equation}{ +\arg \min_C d(\bigcup_{C_i \in C} C_i, P) \\ +\f} +
with
+\f{equation}{ +d(A, B) = |A| - |B| +\f} + +Where \f$|A|\f$ is the volume of A, P is the input polyhedron and \f$C_i\f$ are convex volumes. The convex volumes \f$C_i\f$ are pairwise disjunct, i.e., \f$|C_i \cap C_j| = 0\f$ if \f$i \neq j\f$. And the union of convex volumes contain the input polyhedron \f$ P \subset \bigcup_{C_i \in C} \f$. + +\cgalFigureAnchor{Acd_pipelinefig} +
+ +
+\cgalFigureCaptionBegin{Acd_pipelinefig} +Approximate convex decomposition pipeline.\n From left to right: 1. input mesh 2. voxel grid 3. convex volumes after error-driven splitting 4. final convex volumes after merging +\cgalFigureCaptionEnd + +The method employs a top-down splitting phase followed by a bottom-up merging to achieve the target number of convex volumes. The splitting phase aims at decomposing the input mesh into smaller mostly convex parts. Each part of the input mesh is approximated with its convex hull. In a hierarchical manner, each part of the mesh is split into two parts when its convexity is low. The convexity is measured by the volume difference of the part and its convex hull. Splitting a part into two can be done by simply cutting the longest side of the bounding box in half. A better choice is often found by searching the longest side of the bounding box for a concave spot. However, it comes with a higher computational cost. The hierarchical splitting stops when either the convexity is sufficiently high or the maximum depth is reached. +The volume calculation, convex hull computation and the concavity search is accelerated by a voxel grid. The grid is prepared before the splitting phase and is labelled into outside, inside or surface. The convex hulls are calculated from voxel corners. Thus, a mesh with a high resolution is less penalized by its number of vertices. +The splitting phase typically results in a number of convex volumes larger than targeted. The second phase employs a bottom-up merging that reduces the number of convex volumes to the targeted number while aiming at maintaining a low volume difference between convex volumes and the input mesh. The greedy merging maintains a priority queue to incrementally merge the pair of convex volumes with the smallest increase of volume difference. + +The splitting phase is not limited by the chosen `maximum_number_of_convex_hulls`, because a splitting into a larger number of more convex parts with a subsequent merging leads to better results. + + +\subsection Convex_decomposition_3ACD_Parameters Parameters +Several parameters of the algorithm impact the quality of the result as well as the running time. +- `maximum_number_of_convex_hulls`: The maximum number of convex volumes output by the method. The actual number may be lower for mostly convex input meshes, e.g., a sphere. The impact on the running time is rather low. The default is 16. +- `maximum_depth`: The maximum depth for the hierarchical splitting phase. For complex meshes, a higher maximum depth is required to split small concavities into convex parts. The choice of `maximum_depth` has a larger impact on the running time. The default is 10. +- `maximum_number_of_voxels`: This parameter controls the resolution of the voxel grid used for speed-up. Larger numbers result in a higher memory footprint and a higher running time. A small number also limits the `maximum_depth`. The voxel grid is isotropic and the longest axis of the bounding box will be split into a number of voxels equal to the cubic root of `maximum_number_of_voxels`. The default value is 1.000.000. +- `volume_error`: The splitting of a convex volume into smaller parts is controlled by the `volume_error` which provides the tolerance for difference in volume. The difference is calculated by \f$ (|C_i| - |P_i|) / |P_i|\f$. The default value is 0.01. Thus, if a convex volume has 1 percent more volume that the part of the input mesh it approximates, it will be further divided. +- `split_at_concavity`: The splitting can be either performed after searching a concavity on the longest side of the bounding box or simply by splitting the longest side of the bounding box in half. The default value is true, i.e., splitting at the concavity. + +\subsection Convex_decomposition_3ACD_Performance Performance + +Here will be images and more tables to show the impact of different parameters + +The method has been evaluated on several models: + + + + + + + + + +

+
+Data set + +Faces + +Volume + +Convex hull volume + +Overhead +

+
+Camel + +19.536 + +0.0468 + +0.15541 + +2.32388 +
+Elephant + +5.558 + +0.0462 + +0.12987 + +1.81087 +
+Triceratops + +5.660 + +136.732 + +336.925 + +1.46412 +

+
+ +If not mentioned otherwise, all tests used a volume error of 0.01, a maximum depth of 10, 1 million voxels and split at the concavity. + +Impact of varying the number of generated convex volumes with splitting at the concavity on volume overhead: + + + + + + + + + +

+
+Data set + +Split location + +5 volumes + +7 volumes + +9 volumes + +11 volumes + +12 volumes +

+
+Camel + +Concavity + +0.8006 + +0.6680 + +0.5871 + +0.5736 + +0.5463 + +
+Elephant + +Concavity + +1.1927 + +0.9731 + +0.84613 + +0.7506 + +0.6947 + +
+Triceratops + +Concavity + +0.9770 + +0.7676 + +0.6722 + +0.5971 + +0.5658 + +

+
+ +And by using the mid split: + + + + + + + + + +

+
+Data set + +Split location + +5 volumes + +7 volumes + +9 volumes + +11 volumes + +12 volumes +

+
+Camel + +Mid + +1.1158 + +1.0468 + +0.8660 + +0.6764 + +0.6057 + +
+Elephant + +Mid + +1.2121 + +1.00867 + +0.8444 + +0.7465 + +0.6931 + +
+Triceratops + +Mid + +1.2191 + +0.6870 + +0.8463 + +0.7375 + +0.6871 + +

+
+ +The running time for all cases in the above tables were between 1.4 and 3 seconds while being slightly lower when splitting at the concavity. Although searching the voxel grid for the concavity takes additional computational time, it is more than compensated by fewer splits. + +\subsection Convex_decomposition_3ACD_Example Example + +\cgalExample{Surface_mesh_decomposition/approximate_convex_decomposition.cpp } + */ } /* namespace CGAL */ diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in b/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in index aa7cdb5e1b8..ffc85d262bd 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in @@ -3,3 +3,7 @@ PROJECT_NAME = "CGAL ${CGAL_DOC_VERSION} - Convex Decomposition of Polyhedra" EXTRACT_ALL = false HIDE_UNDOC_CLASSES = true + +INPUT += ${CGAL_Surface_mesh_decomposition_INCLUDE_DIR}/CGAL/approximate_convex_decomposition.h + +EXAMPLE_PATH += ${CGAL_Surface_mesh_decomposition_EXAMPLE_DIR} \ No newline at end of file diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt index fac4f8479c7..4db19541a8e 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt @@ -5,8 +5,9 @@ \cgalPkgDescriptionBegin{Convex Decomposition of Polyhedra,PkgConvexDecomposition3} \cgalPkgPicture{Convex_decomposition_3/fig/Convex_decomposition_3-teaser.png} \cgalPkgSummaryBegin -\cgalPkgAuthor{Peter Hachenberger} -\cgalPkgDesc{This packages provides a function for decomposing a bounded polyhedron into convex sub-polyhedra. The decomposition yields \cgalBigO{r^2} convex pieces, where \f$ r\f$ is the number of edges, whose adjacent facets form an angle of more than 180 degrees with respect to the polyhedron's interior. This bound is worst-case optimal. } +\cgalPkgAuthor{Peter Hachenberger, Sven Oesau} +\cgalPkgDesc{This packages provides two functions for computing a set of convex volumes that cover a bounded polyhedron. The `CGAL::convex_decomposition_3()` splits provides a decomposition into \cgalBigO{r^2} convex pieces, where \f$ r\f$ is the number of edges, whose adjacent facets form an angle of more than 180 degrees with respect to the polyhedron's interior. This bound is worst-case optimal. +The `CGAL::approximate_convex_decomposition()` instead splits the convex hull of the polyhedron into a set of convex volumes. While these convex volumes cover additional space outside of the polyhedron, the computation is fast for any chosen number of convex volumes.} \cgalPkgManuals{Chapter_Convex_Decomposition_of_Polyhedra,PkgConvexDecomposition3Ref} \cgalPkgSummaryEnd \cgalPkgShortInfoBegin @@ -21,6 +22,7 @@ \cgalCRPSection{Functions} - `CGAL::convex_decomposition_3(Nef_polyhedron_3& N)` +- `CGAL::approximate_convex_decomposition()` */ diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies b/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies index 94e7ccf5922..6d540982926 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies @@ -5,3 +5,4 @@ Algebraic_foundations Circulator Stream_support Nef_3 +BGL diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt index b40169d2c81..ab8e7489906 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt @@ -1,3 +1,4 @@ /*! \example Convex_decomposition_3/list_of_convex_parts.cpp +\example Surface_mesh_decomposition/approximate_convex_decomposition.cpp */ diff --git a/Installation/include/CGAL/license/Surface_mesh_decomposition.h b/Installation/include/CGAL/license/Surface_mesh_decomposition.h new file mode 100644 index 00000000000..ee8697fe7df --- /dev/null +++ b/Installation/include/CGAL/license/Surface_mesh_decomposition.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016 GeometryFactory SARL (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org) +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Andreas Fabri +// +// Warning: this file is generated, see include/CGAL/license/README.md + +#ifndef CGAL_LICENSE_SURFACE_MESH_DECOMPOSITION_H +#define CGAL_LICENSE_SURFACE_MESH_DECOMPOSITION_H + +#include +#include + +#ifdef CGAL_SURFACE_MESH_DECOMPOSITION_COMMERCIAL_LICENSE + +# if CGAL_SURFACE_MESH_DECOMPOSITION_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +# if defined(CGAL_LICENSE_WARNING) + + CGAL_pragma_warning("Your commercial license for CGAL does not cover " + "this release of the Triangulated Surface Mesh Decomposition package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the Triangulated Surface Mesh Decomposition package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_SURFACE_MESH_DECOMPOSITION_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_SURFACE_MESH_DECOMPOSITION_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_SURFACE_MESH_DECOMPOSITION_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL Triangulated Surface Mesh Decomposition package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_SURFACE_MESH_DECOMPOSITION_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL Triangulated Surface Mesh Decomposition package under the terms of \ + the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +#endif // no CGAL_SURFACE_MESH_DECOMPOSITION_COMMERCIAL_LICENSE + +#endif // CGAL_LICENSE_SURFACE_MESH_DECOMPOSITION_H diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index 791271f4ac9..a5e73be196b 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -93,6 +93,7 @@ Straight_skeleton_extrusion_2 2D Straight Skeleton Extrusion Stream_lines_2 2D Placement of Streamlines Surface_mesh_approximation Triangulated Surface Mesh Approximation Surface_mesh_deformation Triangulated Surface Mesh Deformation +Surface_mesh_decomposition Triangulated Surface Mesh Decomposition Surface_mesher 3D Surface Mesh Generation Surface_mesh Surface Mesh Surface_mesh_parameterization Triangulated Surface Mesh Parameterization diff --git a/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt b/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt new file mode 100644 index 00000000000..5e6d6cd5d2c --- /dev/null +++ b/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt @@ -0,0 +1,19 @@ +# Created by the script cgal_create_CMakeLists +# This is the CMake script for compiling a set of CGAL applications. + +cmake_minimum_required(VERSION 3.12...3.31) +project(Surface_mesh_decomposition_Examples) + +# CGAL and its components +find_package(CGAL REQUIRED) + +find_package(TBB QUIET) +include(CGAL_TBB_support) + +create_single_source_cgal_program("approximate_convex_decomposition.cpp") + +if(TARGET CGAL::TBB_support) + target_link_libraries(approximate_convex_decomposition PRIVATE CGAL::TBB_support) +else() + message(STATUS "NOTICE: Intel TBB was not found. Sequential code will be used.") +endif() diff --git a/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp b/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp new file mode 100644 index 00000000000..c7a002ad401 --- /dev/null +++ b/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp @@ -0,0 +1,47 @@ +#include +#include + +#include +#include + +#include +#include +#include + +using K = CGAL::Exact_predicates_inexact_constructions_kernel; + +using Point = K::Point_3; + +using Convex_hull = std::pair, std::vector > >; +using Mesh = CGAL::Surface_mesh; +namespace PMP = CGAL::Polygon_mesh_processing; + +int main(int argc, char* argv[]) +{ + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); + std::cout << filename << std::endl; + + Mesh mesh; + if(!PMP::IO::read_polygon_mesh(filename, mesh)) { + std::cerr << "Invalid input." << std::endl; + return 1; + } + + std::vector convex_hulls; + convex_hulls.reserve(9); + + CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::parameters::maximum_depth(10) + .volume_error(0.1) + .maximum_number_of_convex_hulls(9) + .split_at_concavity(true) + .maximum_number_of_voxels(1000000) + .concurrency_tag(CGAL::Parallel_if_available_tag())); + + for (std::size_t i = 0;i + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef CGAL_LINKED_WITH_TBB +#include +#include +#include +#include +#else +#include +#endif + +namespace CGAL { +namespace internal { + +using Vec3_uint = std::array; + +struct Bbox_uint { + Vec3_uint lower; + Vec3_uint upper; + Bbox_uint(const Vec3_uint &lower, const Vec3_uint &upper) : lower(lower), upper(upper) {} +}; + +enum Grid_cell : int8_t { + OUTSIDE = -1, + SURFACE = 0, + INSIDE = 1 +}; + +void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + std::ofstream stream(filename); + + stream << + "ply\n" << + "format ascii 1.0\n" << + "element vertex " << (grid_size[0] * grid_size[1] * grid_size[2]) << "\n" << + "property double x\n" << + "property double y\n" << + "property double z\n" << + "property uchar red\n" << + "property uchar green\n" << + "property uchar blue\n" << + "end_header\n"; + + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + stream << (bb.xmin() + (x + 0.5) * voxel_size) << " " << (bb.ymin() + (y + 0.5) * voxel_size) << " " << (bb.zmin() + (z + 0.5) * voxel_size) << " "; + switch (vox(x, y, z)) { + case INSIDE: + stream << "175 175 100\n"; + break; + case OUTSIDE: + stream << "125 125 175\n"; + break; + case SURFACE: + stream << "200 100 100\n"; + break; + default: + stream << "0 0 0\n"; + break; + } + } + + stream.close(); +} + +template +void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size, Filter& filter) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + std::ofstream stream(filename); + + std::size_t count = 0; + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) + if (filter(vox(x, y, z))) count++; + + stream << + "ply\n" << + "format ascii 1.0\n" << + "element vertex " << count << "\n" << + "property double x\n" << + "property double y\n" << + "property double z\n" << + "property uchar red\n" << + "property uchar green\n" << + "property uchar blue\n" << + "end_header\n"; + + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (!filter(vox(x, y, z))) continue; + stream << (bb.xmin() + (x + 0.5) * voxel_size) << " " << (bb.ymin() + (y + 0.5) * voxel_size) << " " << (bb.zmin() + (z + 0.5) * voxel_size) << " "; + switch (vox(x, y, z)) { + case INSIDE: + stream << "175 175 100\n"; + break; + case OUTSIDE: + stream << "125 125 175\n"; + break; + case SURFACE: + stream << "200 100 100\n"; + break; + default: + stream << "0 0 0\n"; + break; + } + } + + stream.close(); +} + +template +void export_grid_voxels(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size, Filter& filter) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + std::ofstream stream(filename); + + std::size_t count = 0; + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) + if (filter(vox(x, y, z))) count++; + + stream << + "ply\n" << + "format ascii 1.0\n" << + "element vertex " << count * 8 << "\n" << + "property double x\n" << + "property double y\n" << + "property double z\n" << + "element face " << count * 6 << "\n" << + "property list uchar int vertex_indices\n" << + "end_header\n"; + + // export vertices + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (!filter(vox(x, y, z))) continue; + stream << (bb.xmin() + (x)*voxel_size) << " " << (bb.ymin() + (y)*voxel_size) << " " << (bb.zmin() + (z)*voxel_size) << "\n"; + stream << (bb.xmin() + (x + 1.0) * voxel_size) << " " << (bb.ymin() + (y)*voxel_size) << " " << (bb.zmin() + (z)*voxel_size) << "\n"; + stream << (bb.xmin() + (x)*voxel_size) << " " << (bb.ymin() + (y + 1.0) * voxel_size) << " " << (bb.zmin() + (z)*voxel_size) << "\n"; + stream << (bb.xmin() + (x + 1.0) * voxel_size) << " " << (bb.ymin() + (y + 1.0) * voxel_size) << " " << (bb.zmin() + (z)*voxel_size) << "\n"; + stream << (bb.xmin() + (x)*voxel_size) << " " << (bb.ymin() + (y)*voxel_size) << " " << (bb.zmin() + (z + 1.0)*voxel_size) << "\n"; + stream << (bb.xmin() + (x + 1.0) * voxel_size) << " " << (bb.ymin() + (y)*voxel_size) << " " << (bb.zmin() + (z + 1.0)*voxel_size) << "\n"; + stream << (bb.xmin() + (x)*voxel_size) << " " << (bb.ymin() + (y + 1.0) * voxel_size) << " " << (bb.zmin() + (z + 1.0)*voxel_size) << "\n"; + stream << (bb.xmin() + (x + 1.0) * voxel_size) << " " << (bb.ymin() + (y + 1.0) * voxel_size) << " " << (bb.zmin() + (z + 1.0)*voxel_size) << "\n"; + } + + // export faces + count = 0; + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (!filter(vox(x, y, z))) continue; + stream << "4 " << (count * 8 + 0) << " " << (count * 8 + 1) << " " << (count * 8 + 3) << " " << (count * 8 + 2) << "\n"; + stream << "4 " << (count * 8 + 4) << " " << (count * 8 + 5) << " " << (count * 8 + 7) << " " << (count * 8 + 6) << "\n"; + stream << "4 " << (count * 8 + 0) << " " << (count * 8 + 1) << " " << (count * 8 + 5) << " " << (count * 8 + 4) << "\n"; + stream << "4 " << (count * 8 + 2) << " " << (count * 8 + 3) << " " << (count * 8 + 7) << " " << (count * 8 + 6) << "\n"; + stream << "4 " << (count * 8 + 0) << " " << (count * 8 + 2) << " " << (count * 8 + 6) << " " << (count * 8 + 4) << "\n"; + stream << "4 " << (count * 8 + 1) << " " << (count * 8 + 3) << " " << (count * 8 + 7) << " " << (count * 8 + 5) << "\n"; + + count++; + } + + stream.close(); +} + +void export_voxels(const std::string& filename, const Bbox_3& bb, std::vector& voxels, double voxel_size) { + std::ofstream stream(filename); + + stream << + "ply\n" << + "format ascii 1.0\n" << + "element vertex " << voxels.size() << "\n" << + "property double x\n" << + "property double y\n" << + "property double z\n" << + "end_header\n"; + + for (const Vec3_uint& v : voxels) { + stream << (bb.xmin() + (v[0] + 0.5) * voxel_size) << " " << (bb.ymin() + (v[1] + 0.5) * voxel_size) << " " << (bb.zmin() + (v[2] + 0.5) * voxel_size) << "\n"; + } + + stream.close(); +} + +template +void export_points(const std::string& filename, const Bbox_3& bb, std::vector& points) { + std::ofstream stream(filename); + + stream << + "ply\n" << + "format ascii 1.0\n" << + "element vertex " << points.size() << "\n" << + "property double x\n" << + "property double y\n" << + "property double z\n" << + "end_header\n"; + + for (const Point_3& p : points) { + stream << (bb.xmin() + p.x()) << " " << (bb.ymin() + p.y()) << " " << (bb.zmin() + p.z()) << "\n"; + } + + stream.close(); +} + +template +void export_points(const std::string& filename, Range& points) { + std::ofstream stream(filename); + stream << std::setprecision(18); + + for (const auto& p : points) { + stream << p.x() << " " << p.y() << " " << p.z() << "\n"; + } + + stream.close(); +} + +template +Box box_union(const Box& a, const Box& b) { + using FT = decltype(a.xmin()); + return Box( + (std::min)(a.xmin(), b.xmin()), + (std::min)(a.ymin(), b.ymin()), + (std::min)(a.zmin(), b.zmin()), + (std::max)(a.xmax(), b.xmax()), + (std::max)(a.ymax(), b.ymax()), + (std::max)(a.zmax(), b.zmax())); +} + +template +std::tuple calculate_grid_size(Bbox_3& bb, const unsigned int number_of_voxels) { + std::size_t max_voxels_axis = std::cbrt(number_of_voxels); + assert(max_voxels_axis > 3); + // get longest axis + FT longest = 0; + + if (bb.x_span() >= bb.y_span() && bb.x_span() >= bb.z_span()) + longest = bb.x_span(); + else if (bb.y_span() >= bb.x_span() && bb.y_span() >= bb.z_span()) + longest = bb.y_span(); + else if (bb.z_span() >= bb.x_span() && bb.z_span() >= bb.y_span()) + longest = bb.z_span(); + + const FT voxel_size = longest * FT(1.0 / (max_voxels_axis - 3)); + + FT s = 1.5 * voxel_size; + + bb = Bbox_3(to_double(bb.xmin() - s), to_double(bb.ymin() - s), to_double(bb.zmin() - s), to_double(bb.xmax() + s), to_double(bb.ymax() + s), to_double(bb.zmax() + s)); + + return { Vec3_uint{static_cast(to_double(bb.x_span() / voxel_size + 0.5)), static_cast(to_double(bb.y_span() / voxel_size + 0.5)), static_cast(to_double(bb.z_span() / voxel_size + 0.5))}, voxel_size}; +} + +template +const typename GeomTraits::Point_3 &point(typename boost::graph_traits::face_descriptor fd, + const PolygonMesh& pmesh, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + return get(vpm, target(halfedge(fd, pmesh), pmesh)); +} + +template +Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_traits::face_descriptor fd, const Bbox_3& bb, const FT& voxel_size) { + Bbox_3 face_bb = Polygon_mesh_processing::face_bbox(fd, mesh); + double vs = to_double(voxel_size); + return Bbox_uint({ + static_cast((face_bb.xmin() - bb.xmin()) / vs - 0.5), + static_cast((face_bb.ymin() - bb.ymin()) / vs - 0.5), + static_cast((face_bb.zmin() - bb.zmin()) / vs - 0.5) + }, { + static_cast((face_bb.xmax() - bb.xmin()) / vs + 0.5), + static_cast((face_bb.ymax() - bb.ymin()) / vs + 0.5), + static_cast((face_bb.zmax() - bb.zmin()) / vs + 0.5) + }); +} + +template +Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { + double vs = to_double(voxel_size); + return Bbox_3( + bb.xmin() + voxel[0] * vs, + bb.ymin() + voxel[1] * vs, + bb.zmin() + voxel[2] * vs, + bb.xmin() + (voxel[0] + 1) * vs, + bb.ymin() + (voxel[1] + 1) * vs, + bb.zmin() + (voxel[2] + 1) * vs + ); +} + +void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_uint& grid_size, std::deque& todo) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + while (!todo.empty()) { + auto [x, y, z0] = todo.front(); + todo.pop_front(); + if (vox(x, y, z0) != INSIDE) + return; + + bool xneg = false, xpos = false; + bool yneg = false, ypos = false; + bool zneg = false, zpos = false; + + // positive direction + for (unsigned int z = z0; z < grid_size[2]; z++) { + if (vox(x, y, z) != INSIDE) + break; + + vox(x, y, z) = label; + if (x > 0) { + if (vox(x - 1, y, z) == INSIDE) { + if (!xneg) { + xneg = true; + todo.push_back({ x - 1, y, z }); + } + } + else xneg = false; + } + + if (x < grid_size[0] - 1) { + if (vox(x + 1, y, z) == INSIDE) { + if (!xpos) { + xpos = true; + todo.push_back({ x + 1, y, z }); + } + } + else xpos = false; + } + + if (y > 0) { + if (vox(x, y - 1, z) == INSIDE) { + if (!yneg) { + yneg = true; + todo.push_front({ x, y - 1, z }); + } + } + else yneg = false; + } + + if (y < grid_size[1] - 1) { + if (vox(x, y + 1, z) == INSIDE) { + if (!ypos) { + ypos = true; + todo.push_front({ x, y + 1, z }); + } + } + else ypos = false; + } + } + + xneg = xpos = yneg = ypos = zneg = zpos = false; + + if (z0 == 0) + continue; + + for (unsigned int z = z0 - 1; z > 0; z--) { + if (vox(x, y, z) != INSIDE) + break; + + vox(x, y, z) = label; + if (x > 0) { + if (vox(x - 1, y, z) == INSIDE) { + if (!xneg) { + xneg = true; + todo.push_back({ x - 1, y, z }); + } + } + else xneg = false; + } + + if (x < grid_size[0] - 1) { + if (vox(x + 1, y, z) == INSIDE) { + if (!xpos) { + xpos = true; + todo.push_back({ x + 1, y, z }); + } + } + else xpos = false; + } + + if (y > 0) { + if (vox(x, y - 1, z) == INSIDE) { + if (!yneg) { + yneg = true; + todo.push_front({ x, y - 1, z }); + } + } + else yneg = false; + } + + if (y < grid_size[1] - 1) { + if (vox(x, y + 1, z) == INSIDE) { + if (!ypos) { + ypos = true; + todo.push_front({ x, y + 1, z }); + } + } + else ypos = false; + } + } + } +} + +// Valid voxel grids separate OUTSIDE from INSIDE via SURFACE +bool is_valid(std::vector& grid, const Vec3_uint& grid_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + std::size_t in = 0, out = 0, surface = 0, other = 0, violations = 0; + + for (unsigned int x = 1; x < grid_size[0] - 1; x++) + for (unsigned int y = 1; y < grid_size[1] - 1; y++) + for (unsigned int z = 1; z < grid_size[2] - 1; z++) { + switch (vox(x, y, z)) { + case INSIDE: + if ( vox(x - 1, y, z) == OUTSIDE + || vox(x + 1, y, z) == OUTSIDE + || vox(x, y - 1, z) == OUTSIDE + || vox(x, y + 1, z) == OUTSIDE + || vox(x, y, z - 1) == OUTSIDE + || vox(x, y, z + 1) == OUTSIDE) { + std::cout << "touching I-O: " << x << " " << y << " " << z << std::endl; + violations++; + } + in++; + break; + case OUTSIDE: + if (vox(x - 1, y, z) == INSIDE + || vox(x + 1, y, z) == INSIDE + || vox(x, y - 1, z) == INSIDE + || vox(x, y + 1, z) == INSIDE + || vox(x, y, z - 1) == INSIDE + || vox(x, y, z + 1) == INSIDE) { + std::cout << "touching O-I: " << x << " " << y << " " << z << std::endl; + violations++; + } + out++; + break; + case SURFACE: + surface++; + break; + default: + std::cout << "other " << x << " " << y << " " << z << std::endl; + other++; + break; + } + } + std::cout << "i: " << in << " o: " << out << " s: " << surface << " " << other << std::endl; + std::cout << "violations: " << violations << std::endl; + + return violations == 0; +} + +// Only works for closed meshes +void label_floodfill(std::vector& grid, const Vec3_uint& grid_size) { + // Walk around boundary and start floodfill when voxel label is INSIDE + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + std::deque todo; + + // xmin/xmax + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (vox(0, y, z) == INSIDE) { + todo.push_front({0, y, z}); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + + if (vox(grid_size[0] - 1, y, z) == INSIDE) { + todo.push_front({ grid_size[0] - 1, y, z }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + } + + // ymin/ymax + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int z = 0; z < grid_size[2]; z++) { + if (vox(x, 0, z) == INSIDE) { + todo.push_front({ x, 0, z }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + + if (vox(x, grid_size[1] - 1, z) == INSIDE) { + todo.push_front({ x, grid_size[1] - 1, z }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + } + + // ymin/ymax + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) { + if (vox(x, y, 0) == INSIDE) { + todo.push_front({ x, y, 0 }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + + if (vox(x, y, grid_size[2] - 1) == INSIDE) { + todo.push_front({ x, y, grid_size[2] - 1 }); + scanline_floodfill(OUTSIDE, grid, grid_size, todo); + } + } +} + +void naive_floodfill(std::vector& grid, const Vec3_uint& grid_size) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + std::queue queue; + queue.push({0, 0, 0}); + + while (!queue.empty()) { + auto [x, y, z] = queue.front(); + queue.pop(); + + if (vox(x, y, z) != INSIDE) + continue; + + vox(x, y, z) = OUTSIDE; + if (x > 0) + if (vox(x - 1, y, z) == INSIDE) { + queue.push({ x - 1, y, z }); + } + + if (x < grid_size[0] - 1) + if (vox(x + 1, y, z) == INSIDE) { + queue.push({ x + 1, y, z }); + } + + if (y > 0) + if (vox(x, y - 1, z) == INSIDE) { + queue.push({ x, y - 1, z }); + } + + if (y < grid_size[1] - 1) + if (vox(x, y + 1, z) == INSIDE) { + queue.push({ x, y + 1, z }); + } + + if (z > 0) + if (vox(x, y, z - 1) == INSIDE) { + queue.push({ x, y, z - 1 }); + } + + if (z < grid_size[2] - 1) + if (vox(x, y, z + 1) == INSIDE) { + queue.push({ x, y, z + 1 }); + } + } +} + +template +void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph& mesh, CGAL::Parallel_tag) { +#ifdef CGAL_LINKED_WITH_TBB + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + using face_descriptor = typename boost::graph_traits::face_descriptor; + + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + using Ray_3 = typename GeomTraits::Ray_3; + + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits_3; + using Tree = CGAL::AABB_tree; + using Ray_intersection = std::optional::Type>; + + Tree tree(faces(mesh).first, faces(mesh).second, mesh); + + std::array dirs = { Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1} }; + + tbb::parallel_for(std::size_t(0), std::size_t(grid_size[0]), [&](const std::size_t x) + { + for (std::size_t y = 0; y < grid_size[1]; y++) + for (std::size_t z = 0; z < grid_size[2]; z++) { + if (vox(x, y, z) == SURFACE) + continue; + + Point_3 c(bb.xmin() + (x + 0.5) * voxel_size, bb.ymin() + (y + 0.5) * voxel_size, bb.zmin() + (z + 0.5) * voxel_size); + + unsigned int inside = 0; + unsigned int outside = 0; + + for (std::size_t i = 0; i < 6; i++) { + Ray_intersection intersection = tree.first_intersection(Ray_3(c, dirs[i])); + if (intersection) { + // A segment intersection is not helpful as it means the triangle normal is orthogonal to the ray + if (std::get_if(&(intersection->first))) { + face_descriptor fd = intersection->second; + Vector_3 n = Polygon_mesh_processing::compute_face_normal(fd, mesh); + if (dirs[i] * n > 0) + inside++; + else + outside++; + } + } + } + + if (inside >= 3 && outside == 0) { + vox(x, y, z) = INSIDE; + } + else + vox(x, y, z) = OUTSIDE; + } + } + ); +#else + CGAL_USE(grid); + CGAL_USE(grid_size); + CGAL_USE(bb); + CGAL_USE(voxel_size); + CGAL_USE(mesh); +#endif +} + +template +void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph& mesh, CGAL::Sequential_tag) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + using face_descriptor = typename boost::graph_traits::face_descriptor; + + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + using Ray_3 = typename GeomTraits::Ray_3; + + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits_3; + using Tree = CGAL::AABB_tree; + using Ray_intersection = std::optional::Type>; + + Tree tree(faces(mesh).first, faces(mesh).second, mesh); + + std::array dirs = { Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1} }; + + for (std::size_t x = 0; x < grid_size[0]; x++) + { + for (std::size_t y = 0; y < grid_size[1]; y++) + for (std::size_t z = 0; z < grid_size[2]; z++) { + if (vox(x, y, z) == SURFACE) + continue; + + Point_3 c(bb.xmin() + (x + 0.5) * voxel_size, bb.ymin() + (y + 0.5) * voxel_size, bb.zmin() + (z + 0.5) * voxel_size); + + unsigned int inside = 0; + unsigned int outside = 0; + + for (std::size_t i = 0; i < 6; i++) { + Ray_intersection intersection = tree.first_intersection(Ray_3(c, dirs[i])); + if (intersection) { + // A segment intersection is not helpful as it means the triangle normal is orthogonal to the ray + if (std::get_if(&(intersection->first))) { + face_descriptor fd = intersection->second; + Vector_3 n = compute_face_normal(fd, mesh); + if (dirs[i] * n > 0) + inside++; + else + outside++; + } + } + } + + if (inside >= 3 && outside == 0) { + vox(x, y, z) = INSIDE; + } + else + vox(x, y, z) = OUTSIDE; + } + } +} + +template +struct Convex_hull_candidate { + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + Iso_cuboid_3 bbox; + FT voxel_volume; + FT volume; + FT volume_error; + std::vector points; + std::vector> indices; + + Convex_hull_candidate() noexcept : voxel_volume(0), volume(0), volume_error(0) {} + Convex_hull_candidate(Convex_hull_candidate&& o) noexcept { + bbox = o.bbox; + voxel_volume = o.voxel_volume; + volume = o.volume; + volume_error = o.volume_error; + points = std::move(o.points); + indices = std::move(o.indices); + } + + Convex_hull_candidate& operator= (Convex_hull_candidate&& o) noexcept { + bbox = o.bbox; + voxel_volume = o.voxel_volume; + volume = o.volume; + volume_error = o.volume_error; + points = std::move(o.points); + indices = std::move(o.indices); + + return *this; + } +}; + +template +struct Candidate { + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + std::size_t index; + std::vector surface; + std::vector new_surface; + std::vector inside; + std::size_t depth; + Bbox_uint bbox; + Convex_hull_candidate ch; + + Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {index = cidx++;} + Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) { index = cidx++; } +private: + inline static std::size_t cidx = 0; +}; + +template +typename GeomTraits::FT volume(const std::vector &pts, const std::vector> &indices) { + ::CGAL::internal::Evaluate evaluate; + + typename GeomTraits::FT vol = 0; + typename GeomTraits::Compute_volume_3 cv3; + + typename GeomTraits::Point_3 origin(0, 0, 0); + + for (const std::array &i : indices) { + vol += cv3(origin, pts[i[0]], pts[i[1]], pts[i[2]]); + evaluate(vol); + } + + return vol; +} + +void enlarge(Bbox_uint& bbox, const Vec3_uint& v) { + bbox.lower[0] = (std::min)(bbox.lower[0], v[0]); + bbox.lower[1] = (std::min)(bbox.lower[1], v[1]); + bbox.lower[2] = (std::min)(bbox.lower[2], v[2]); + bbox.upper[0] = (std::max)(bbox.upper[0], v[0]); + bbox.upper[1] = (std::max)(bbox.upper[1], v[1]); + bbox.upper[2] = (std::max)(bbox.upper[2], v[2]); +} + +template > +struct is_std_hashable : std::false_type {}; + +template +struct is_std_hashable>()(std::declval()))>> : std::true_type {}; + +template::type::FT>, typename std::is_same::type::Kernel_tag, CGAL::Cartesian_tag>::type>::type> +struct Unordered_set_if_available { +}; + +template +struct Unordered_set_if_available { + using type = std::unordered_set; +}; + +template +struct Unordered_set_if_available { + using type = std::set; +}; + +template +void compute_candidate(Candidate &c, const Bbox_3& bb, typename GeomTraits::FT voxel_size) { + using Point_3 = typename GeomTraits::Point_3; + using FT = typename GeomTraits::FT; + + c.bbox.lower = c.bbox.upper = c.surface[0]; + + typename Unordered_set_if_available::type voxel_points; + + for (const Vec3_uint& v : c.surface) { + enlarge(c.bbox, v); + FT xmin = bb.xmin() + FT(v[0]) * voxel_size; + FT ymin = bb.ymin() + FT(v[1]) * voxel_size; + FT zmin = bb.zmin() + FT(v[2]) * voxel_size; + FT xmax = bb.xmin() + FT(v[0] + 1) * voxel_size; + FT ymax = bb.ymin() + FT(v[1] + 1) * voxel_size; + FT zmax = bb.zmin() + FT(v[2] + 1) * voxel_size; + voxel_points.insert(Point_3(xmin, ymin, zmin)); + voxel_points.insert(Point_3(xmin, ymax, zmin)); + voxel_points.insert(Point_3(xmin, ymin, zmax)); + voxel_points.insert(Point_3(xmin, ymax, zmax)); + voxel_points.insert(Point_3(xmax, ymin, zmin)); + voxel_points.insert(Point_3(xmax, ymax, zmin)); + voxel_points.insert(Point_3(xmax, ymin, zmax)); + voxel_points.insert(Point_3(xmax, ymax, zmax)); + } + + for (const Vec3_uint& v : c.new_surface) { + enlarge(c.bbox, v); + FT xmin = bb.xmin() + FT(v[0]) * voxel_size; + FT ymin = bb.ymin() + FT(v[1]) * voxel_size; + FT zmin = bb.zmin() + FT(v[2]) * voxel_size; + FT xmax = bb.xmin() + FT(v[0] + 1) * voxel_size; + FT ymax = bb.ymin() + FT(v[1] + 1) * voxel_size; + FT zmax = bb.zmin() + FT(v[2] + 1) * voxel_size; + voxel_points.insert(Point_3(xmin, ymin, zmin)); + voxel_points.insert(Point_3(xmin, ymax, zmin)); + voxel_points.insert(Point_3(xmin, ymin, zmax)); + voxel_points.insert(Point_3(xmin, ymax, zmax)); + voxel_points.insert(Point_3(xmax, ymin, zmin)); + voxel_points.insert(Point_3(xmax, ymax, zmin)); + voxel_points.insert(Point_3(xmax, ymin, zmax)); + voxel_points.insert(Point_3(xmax, ymax, zmax)); + } + + convex_hull_3(voxel_points.begin(), voxel_points.end(), c.ch.points, c.ch.indices); + + c.ch.volume = volume(c.ch.points, c.ch.indices); + + CGAL_assertion(c.ch.volume > 0); + c.ch.bbox = bbox_3(c.ch.points.begin(), c.ch.points.end()); + + c.ch.voxel_volume = (voxel_size * voxel_size * voxel_size) * FT(double(c.inside.size() + c.surface.size() + c.new_surface.size())); + c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; +} + +template +void fill_grid(Candidate &c, std::vector &grid, const FaceGraph &mesh, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size, Concurrency_tag tag) { + const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { + return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; + }; + + for (const typename boost::graph_traits::face_descriptor fd : faces(mesh)) { + Bbox_uint face_bb = grid_bbox_face(mesh, fd, bb, voxel_size); + CGAL_assertion(face_bb.lower[0] <= face_bb.upper[0]); + CGAL_assertion(face_bb.lower[1] <= face_bb.upper[1]); + CGAL_assertion(face_bb.lower[2] <= face_bb.upper[2]); + CGAL_assertion(face_bb.upper[0] < grid_size[0]); + CGAL_assertion(face_bb.upper[1] < grid_size[1]); + CGAL_assertion(face_bb.upper[2] < grid_size[2]); + for (unsigned int x = face_bb.lower[0]; x <= face_bb.upper[0]; x++) + for (unsigned int y = face_bb.lower[1]; y <= face_bb.upper[1]; y++) + for (unsigned int z = face_bb.lower[2]; z <= face_bb.upper[2]; z++) { + Iso_cuboid_3 box = bbox_voxel({ x, y, z }, bb, voxel_size); + const typename GeomTraits::Point_3 &p = point(fd, mesh); + if (do_intersect(Polygon_mesh_processing::triangle(fd, mesh), box) || box.has_on_bounded_side(p)) + vox(x, y, z) = Grid_cell::SURFACE; + } + } + + if (CGAL::is_closed(mesh)) + naive_floodfill(grid, grid_size); + else + rayshooting_fill(grid, grid_size, bb, voxel_size, mesh, tag); + + c.bbox.upper = {grid_size[0] - 1, grid_size[1] - 1, grid_size[2] - 1}; + + for (unsigned int x = 0; x < grid_size[0]; x++) + for (unsigned int y = 0; y < grid_size[1]; y++) + for (unsigned int z = 0; z < grid_size[2]; z++) + if (vox(x, y, z) == INSIDE) + c.inside.push_back({x, y, z}); + else if (vox(x, y, z) == SURFACE) + c.surface.push_back({x, y, z}); +} + +template +void init(Candidate &c, const FaceGraph& mesh, std::vector& grid, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size, Concurrency_tag tag) { + internal::fill_grid(c, grid, mesh, bb, grid_size, voxel_size, tag); + compute_candidate(c, bb, voxel_size); +} + +template +void split(std::vector &candidates, Candidate_& c, unsigned int axis, unsigned int location) { + //Just split the voxel bbox along 'axis' after voxel index 'location' + Candidate_ upper(c.depth + 1, c.bbox); + Candidate_ lower(c.depth + 1, c.bbox); + + CGAL_assertion(c.bbox.lower[axis] < location); + CGAL_assertion(c.bbox.upper[axis] > location); + + upper.bbox.lower[axis] = location + 1; + lower.bbox.upper[axis] = location; + + for (const Vec3_uint& v : c.surface) { + CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location < v[axis]) + upper.surface.push_back(v); + else + lower.surface.push_back(v); + } + + for (const Vec3_uint& v : c.new_surface) { + CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location < v[axis]) + upper.new_surface.push_back(v); + else + lower.new_surface.push_back(v); + } + + for (const Vec3_uint& v : c.inside) { + CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); + CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); + CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); + if (location < v[axis]) { + if ((location + 1) == v[axis]) + upper.new_surface.push_back(v); + else + upper.inside.push_back(v); + } + else { + if (location == v[axis]) + lower.new_surface.push_back(v); + else + lower.inside.push_back(v); + } + } + + if (!upper.surface.empty()) + candidates.emplace_back(std::move(upper)); + + if (!lower.surface.empty()) + candidates.emplace_back(std::move(lower)); +} + +std::size_t concavity(const Vec3_uint& s, const Vec3_uint& e, int axis, std::vector& grid, const Vec3_uint& grid_size) { + const auto vox = [&grid, &grid_size](const Vec3_uint &v) -> int8_t& { + return grid[v[2] + (v[1] * grid_size[2]) + (v[0] * grid_size[1] * grid_size[2])]; + }; + std::size_t i; + for (i = s[axis];i s[axis]; j--) { + Vec3_uint v = s; + v[axis] = j; + if (vox(v) != OUTSIDE) + break; + } + + std::size_t res = (i - s[axis]) + (e[axis] - j); + if(res >= grid_size[axis]) + std::cout << "violation!" << std::endl; + return (i - s[axis]) + (e[axis] - j); +} + +void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& location, const Bbox_uint &bbox, std::vector& grid, const Vec3_uint& grid_size) { + std::size_t length = bbox.upper[axis] - bbox.lower[axis] + 1; + + CGAL_assertion(length >= 8); + CGAL_precondition(axis <= 2); + + std::size_t idx0 = 0, idx1 = 1, idx2 = 2; + + switch(axis) { + case 0: + idx0 = 1; + idx1 = 2; + idx2 = 0; + break; + case 1: + idx0 = 0; + idx1 = 2; + idx2 = 1; + break; + case 2: + idx0 = 0; + idx1 = 1; + idx2 = 2; + break; + } + + std::vector diam(length, 0), diam2(length, 0); + + for (std::size_t i = bbox.lower[idx2]; i <= bbox.upper[idx2]; i++) { + for (std::size_t j = bbox.lower[idx0]; j <= bbox.upper[idx0]; j++) { + Vec3_uint s, e; + s[idx2] = i; + s[idx1] = bbox.lower[idx1]; + s[idx0] = j; + e[idx2] = i; + e[idx1] = bbox.upper[idx1]; + e[idx0] = j; + diam[i - bbox.lower[idx2]] += concavity(s, e, idx1, grid, grid_size); + } + } + + for (std::size_t i = bbox.lower[idx2]; i <= bbox.upper[idx2]; i++) { + for (std::size_t j = bbox.lower[idx1]; j <= bbox.upper[idx1]; j++) { + Vec3_uint s, e; + s[idx0] = bbox.lower[idx0]; + s[idx2] = i; + s[idx1] = j; + e[idx0] = bbox.upper[idx0]; + e[idx2] = i; + e[idx1] = j; + diam2[i - bbox.lower[idx2]] += concavity(s, e, idx0, grid, grid_size); + } + } + + // Skip initial border + std::size_t border = (length / 10) + 0.5; + std::size_t pos1, end1 = length; + int grad = diam[0] - diam[1]; + for (pos1 = 2; pos1 < border; pos1++) { + int grad1 = diam[pos1 - 1] - diam[pos1]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad1 > 0 && grad1 > (grad>>1))) + break; + if (grad < grad1) + grad = grad1; + } + + grad = diam[length - 1] - diam[length - 2]; + for (end1 = length - 3; end1 > (length - border - 1); end1--) { + int grad1 = diam[end1 + 1] - diam[end1]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad1 > 0 && grad1 > (grad >> 1))) + break; + if (grad < grad1) + grad = grad1; + } + + std::size_t pos2, end2 = length; + grad = diam2[0] - diam2[1]; + for (pos2 = 2; pos2 < border; pos2++) { + int grad2 = diam[pos2 - 1] - diam[pos2]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad2 > 0 && grad2 > (grad >> 1))) + break; + if (grad < grad2) + grad = grad2; + } + + grad = diam2[length - 1] - diam2[length - 2]; + for (end2 = length - 3; end2 > (length - border - 1); end2--) { + int grad2 = diam[end2 + 1] - diam[end2]; + // Stop if the gradient flips or flattens significantly + if (!(grad * grad2 > 0 && grad2 > (grad >> 1))) + break; + if (grad < grad2) + grad = grad2; + } + + std::size_t conc1 = abs(diam[pos1 + 1] - diam[pos1]); + std::size_t conc2 = abs(diam2[pos2 + 1] - diam2[pos2]); + + for (std::size_t i = pos1;i conc1) { + pos1 = i - 1; + conc1 = abs(diam[i] - diam[i - 1]); + } + + for (std::size_t i = pos2; i < end2; i++) + if (unsigned(abs(diam2[i] - diam2[i - 1])) > conc2) { + pos2 = i - 1; + conc2 = abs(diam2[i] - diam2[i - 1]); + } + + + if (conc1 <= conc2) { + if (pos1 < 2 || (length - 3) < pos1) + location = (bbox.upper[axis] + bbox.lower[axis]) / 2; + else + location = pos1 + bbox.lower[axis]; + } + else { + if (pos2 < 2 || (length - 3) < pos2) + location = (bbox.upper[axis] + bbox.lower[axis]) / 2; + else + location = pos2 + bbox.lower[axis]; + } +} + +template +void choose_splitting_plane(Candidate& c, unsigned int &axis, unsigned int &location, std::vector& grid, const Vec3_uint& grid_size, const NamedParameters& np) { + const bool search_concavity = parameters::choose_parameter(parameters::get_parameter(np, internal_np::split_at_concavity), true); + const std::array span = {c.bbox.upper[0] - c.bbox.lower[0], c.bbox.upper[1] - c.bbox.lower[1], c.bbox.upper[2] - c.bbox.lower[2]}; + + // Split longest axis + axis = (span[0] >= span[1]) ? 0 : 1; + axis = (span[axis] >= span[2]) ? axis : 2; + + if (span[axis] >= 8 && search_concavity) + choose_splitting_location_by_concavity(axis, location, c.bbox, grid, grid_size); + else + location = (c.bbox.upper[axis] + c.bbox.lower[axis]) / 2; +} + +template +bool finished(Candidate &c, const NamedParameters& np) { + const typename GeomTraits::FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 0.01); + + if (c.ch.volume_error <= max_error) + return true; + + std::size_t max_span = 0; + for (std::size_t i = 0;i<3;i++) { + const std::size_t span = c.bbox.upper[i] - c.bbox.lower[i]; + max_span = (std::max)(max_span, span); + } + + if (max_span <= 1) + return true; + + return false; +} + +template +void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Parallel_tag) { +#ifdef CGAL_LINKED_WITH_TBB + const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + + std::vector> final_candidates; + + std::size_t depth = 0; + + while (!candidates.empty() && depth < max_depth) { + depth++; + std::vector> former_candidates = std::move(candidates); + for (Candidate& c : former_candidates) { + + if (finished(c, np)) { + CGAL::Bbox_3 bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); + bbox.scale(1.1); + c.ch.bbox = bbox; + final_candidates.push_back(std::move(c)); + continue; + } + unsigned int axis = 0, location = 0; + choose_splitting_plane(c, axis, location, grid, grid_size, np); + split(candidates, c, axis, location); + } + tbb::parallel_for_each(candidates, [&](Candidate& c) { + compute_candidate(c, bbox, voxel_size); + }); + } + + if (candidates.empty()) + std::swap(candidates, final_candidates); + else + std::move(final_candidates.begin(), final_candidates.end(), std::back_inserter(candidates)); + +#else + CGAL_USE(candidates); + CGAL_USE(grid); + CGAL_USE(grid_size); + CGAL_USE(bbox); + CGAL_USE(voxel_size); + CGAL_USE(np); +#endif +} + +template +void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Sequential_tag) { + const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); + + std::vector> final_candidates; + + std::size_t depth = 0; + + while (!candidates.empty() && depth < max_depth) { + depth++; + std::vector> former_candidates = std::move(candidates); + for (Candidate& c : former_candidates) { + + if (finished(c, np)) { + CGAL::Bbox_3 bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); + bbox.scale(1.1); + c.ch.bbox = bbox; + final_candidates.push_back(std::move(c)); + continue; + } + unsigned int axis = 0, location = 0; + choose_splitting_plane(c, axis, location, grid, grid_size, np); + split(candidates, c, axis, location); + } + + for (Candidate& c : candidates) + compute_candidate(c, bbox, voxel_size); + } + + if (candidates.empty()) + std::swap(candidates, final_candidates); + else + std::move(final_candidates.begin(), final_candidates.end(), std::back_inserter(candidates)); +} + +template +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const unsigned int max_convex_hulls, CGAL::Parallel_tag) { +#ifdef CGAL_LINKED_WITH_TBB + if (candidates.size() <= max_convex_hulls) + return; + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + + struct Merged_candidate { + std::size_t ch_a, ch_b; + int ch; + FT volume_error; + + bool operator < (const Merged_candidate& other) const { + return (volume_error > other.volume_error); + } + + Merged_candidate() : ch_a(-1), ch_b(-1) {} + Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} + }; + + tbb::concurrent_unordered_map> hulls; + std::atomic num_hulls = candidates.size(); + + std::unordered_set keep; + + for (std::size_t i = 0; i < candidates.size(); i++) { + hulls.emplace(i, std::move(candidates[i])); + keep.insert(i); + } + + candidates.clear(); + candidates.reserve(max_convex_hulls); + + std::vector todo; + tbb::concurrent_priority_queue queue; + + const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { + Convex_hull_candidate& ci = hulls[m.ch_a]; + Convex_hull_candidate& cj = hulls[m.ch_b]; + m.ch = num_hulls.fetch_add(1); + Convex_hull_candidate& ch = hulls[m.ch]; + + ch.bbox = box_union(ci.bbox, cj.bbox); + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + }; + + for (std::size_t i : keep) { + const Convex_hull_candidate& ci = hulls[i]; + for (std::size_t j : keep) { + if (j <= i) + continue; + const Convex_hull_candidate& cj = hulls[j]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) + todo.emplace_back(Merged_candidate(i, j)); + else { + Merged_candidate m(i, j); + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); + m.ch = -1; + m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(m)); + } + } + } + + // parallel for if available + tbb::parallel_for_each(todo, do_merge); + for (Merged_candidate& m : todo) + queue.push(std::move(m)); + todo.clear(); + + while (!queue.empty() && keep.size() > max_convex_hulls) { + Merged_candidate m; + while (!queue.try_pop(m) && !queue.empty()); + + auto ch_a = hulls.find(m.ch_a); + if (ch_a == hulls.end()) + continue; + + auto ch_b = hulls.find(m.ch_b); + if (ch_b == hulls.end()) + continue; + + if (m.ch == -1) + do_merge(m); + + keep.erase(m.ch_a); + keep.erase(m.ch_b); + + hulls.unsafe_erase(ch_a); + hulls.unsafe_erase(ch_b); + + const Convex_hull_candidate& cj = hulls[m.ch]; + + for (std::size_t id : keep) { + const Convex_hull_candidate& ci = hulls[id]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) + todo.emplace_back(Merged_candidate(id, m.ch)); + else { + Merged_candidate merged(id, m.ch); + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); + merged.ch = -1; + merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(merged)); + } + } + + keep.insert(m.ch); + + tbb::parallel_for_each(todo, do_merge); + for (Merged_candidate& m : todo) + queue.push(std::move(m)); + todo.clear(); + } + + num_hulls = 0; + + for (std::size_t i : keep) + candidates.push_back(std::move(hulls[i])); +#else + CGAL_USE(candidates); + CGAL_USE(hull_volume); + assert(false); +#endif +} + +template +void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const unsigned int max_convex_hulls, CGAL::Sequential_tag) { + if (candidates.size() <= max_convex_hulls) + return; + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + + struct Merged_candidate { + std::size_t ch_a, ch_b; + int ch; + FT volume_error; + + bool operator < (const Merged_candidate& other) const { + return (volume_error > other.volume_error); + } + + Merged_candidate() : ch_a(-1), ch_b(-1) {} + Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} + }; + + std::unordered_map> hulls; + std::size_t num_hulls = candidates.size(); + + std::unordered_set keep; + + for (std::size_t i = 0; i < candidates.size(); i++) { + hulls.emplace(i, std::move(candidates[i])); + keep.insert(i); + } + + candidates.clear(); + candidates.reserve(max_convex_hulls); + + std::priority_queue queue; + + const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { + Convex_hull_candidate& ci = hulls[m.ch_a]; + Convex_hull_candidate& cj = hulls[m.ch_b]; + + m.ch = num_hulls++; + Convex_hull_candidate& ch = hulls[m.ch]; + + ch.bbox = box_union(ci.bbox, cj.bbox); + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + }; + + for (std::size_t i : keep) { + const Convex_hull_candidate& ci = hulls[i]; + for (std::size_t j : keep) { + if (j <= i) + continue; + const Convex_hull_candidate& cj = hulls[j]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) { + Merged_candidate m(i, j); + + m.ch = num_hulls++; + Convex_hull_candidate& ch = hulls[m.ch]; + ch.bbox = box_union(ci.bbox, cj.bbox); + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + queue.push(std::move(m)); + } + else { + Merged_candidate m(i, j); + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); + m.ch = -1; + m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(m)); + } + } + } + + while (!queue.empty() && keep.size() > max_convex_hulls) { + Merged_candidate m = queue.top(); + queue.pop(); + + auto ch_a = hulls.find(m.ch_a); + if (ch_a == hulls.end()) + continue; + + auto ch_b = hulls.find(m.ch_b); + if (ch_b == hulls.end()) + continue; + + if (m.ch == -1) + do_merge(m); + + keep.erase(m.ch_a); + keep.erase(m.ch_b); + + hulls.erase(ch_a); + hulls.erase(ch_b); + + const Convex_hull_candidate& cj = hulls[m.ch]; + + for (std::size_t id : keep) { + const Convex_hull_candidate& ci = hulls[id]; + if (CGAL::do_intersect(ci.bbox, cj.bbox)) { + Merged_candidate merged(id, m.ch); + + merged.ch = num_hulls++; + Convex_hull_candidate& ch = hulls[merged.ch]; + ch.bbox = box_union(ci.bbox, cj.bbox); + std::vector pts(ci.points.begin(), ci.points.end()); + pts.reserve(pts.size() + cj.points.size()); + std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); + convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); + + ch.volume = volume(ch.points, ch.indices); + + ch.volume_error = merged.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + queue.push(std::move(merged)); + } + else { + Merged_candidate merged(id, m.ch); + Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); + merged.ch = -1; + merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + queue.push(std::move(merged)); + } + } + + keep.insert(m.ch); + } + + num_hulls = 0; + + for (std::size_t i : keep) + candidates.push_back(std::move(hulls[i])); +} + +} // namespace internal + +/** + * \ingroup PkgConvexDecomposition3Ref + * + * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. + * The remaining voxels are labeled as outside or inside by flood fill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, + * y and z-axis in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and + * no face facing towards the voxel. In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied. + * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. + * + * \tparam FaceGraph a model of `HalfedgeListGraph`, and `FaceListGraph` + * + * \tparam OutputIterator must be an output iterator accepting variables of type `std::pair, std::vector > >`. + * + * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" + * + * \param tmesh the input triangle mesh to approximate by convex hulls + * \param out_hulls output iterator into which convex hulls are recorded + * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below + * + * \cgalNamedParamsBegin + * + * \cgalParamNBegin{maximum_number_of_voxels} + * \cgalParamDescription{gives an upper bound of the number of voxels. The longest bounding box side will have a length of the cubic root of `maximum_number_of_voxels` rounded down. Cannot be smaller than 512.} + * \cgalParamType{unsigned int} + * \cgalParamDefault{1,000,000} + * \cgalParamNEnd + * + * \cgalParamNBegin{maximum_depth} + * \cgalParamDescription{maximum depth of hierarchical splits} + * \cgalParamType{unsigned int} + * \cgalParamDefault{10} + * \cgalParamNEnd + * + * \cgalParamNBegin{maximum_number_of_convex_volumes} + * \cgalParamDescription{maximum number of convex volumes produced by the method} + * \cgalParamType{unsigned int} + * \cgalParamDefault{16} + * \cgalParamNEnd + * + * \cgalParamNBegin{volume_error} + * \cgalParamDescription{maximum difference in fraction of volumes of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split if the `maximum_depth` has not been reached yet.} + * \cgalParamType{double} + * \cgalParamDefault{0.01} + * \cgalParamNEnd + * + * \cgalParamNBegin{split_at_concavity} + * \cgalParamDescription{If `true`, the local box of a convex hull is split at the concavity along the longest axis of the bounding box. Otherwise, it is split in the middle of the longest axis, which is faster, but less precise.} + * \cgalParamType{Boolean} + * \cgalParamDefault{true} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_point_map} + * \cgalParamDescription{a property map associating points to the vertices of `mesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` + * as key type and `%Point_3` as value type} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, mesh)`} + * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` + * must be available in `FaceGraph`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{concurrency_tag} + * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} + * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} + * \cgalParamDefault{`CGAL::Parallel_if_available_tag`} + * \cgalParamNEnd + * + * \cgalParamNBegin{geom_traits} + * \cgalParamDescription{an instance of a geometric traits class} + * \cgalParamType{a class model of `Kernel`} + * \cgalParamDefault{a \cgal Kernel deduced from the point type of `FaceGraph`, using `CGAL::Kernel_traits`} + * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * \return the number of convex hulls. Note that this value may be lower than the `maximum_number_of_convex_hulls`, for example if the specified `volume_error` is quickly met. + * + * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` + */ +template +std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { + using Geom_traits = typename GetGeomTraits::type; + + using FT = typename Geom_traits::FT; + const unsigned int num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); + using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; + +#ifndef CGAL_LINKED_WITH_TBB + if constexpr (std::is_same_v) { + CGAL_error_msg("CGAL was not compiled with TBB support. Use Sequential_tag instead."); + return 0; + } +#endif + + const unsigned int max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 16); + assert(max_convex_hulls > 0); + + if (max_convex_hulls == 1) { + internal::Convex_hull_candidate ch; + + using parameters::choose_parameter; + using parameters::get_parameter; + using VPM = typename GetVertexPointMap::const_type; + typedef CGAL::Property_map_to_unary_function Vpmap_fct; + + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, tmesh)); + Vpmap_fct v2p(vpm); + + convex_hull_3(boost::make_transform_iterator(vertices(tmesh).begin(), v2p), boost::make_transform_iterator(vertices(tmesh).end(), v2p), ch.points, ch.indices); + + *out_hulls = std::make_pair(std::move(ch.points), std::move(ch.indices)); + return 1; + } + + Bbox_3 bb = Polygon_mesh_processing::bbox(tmesh); + const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); + + std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); + + std::vector> candidates(1); + init(candidates[0], tmesh, grid, bb, grid_size, voxel_size, Concurrency_tag()); + + const FT hull_volume = candidates[0].ch.volume; + + recurse(candidates, grid, grid_size, bb, voxel_size, np, Concurrency_tag()); + + std::vector> hulls; + for (internal::Candidate &c : candidates) + hulls.emplace_back(std::move(c.ch)); + + candidates.clear(); + + // merge until target number is reached + merge(hulls, hull_volume, max_convex_hulls, Concurrency_tag()); + + for (std::size_t i = 0; i < hulls.size(); i++) + *out_hulls++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); + + return hulls.size(); +} + +} // namespace CGAL + +#endif // CGAL_SURFACE_MESH_DECOMPOSITION_APPROXIMATE_CONVEX_DECOMPOSITION_H diff --git a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/copyright b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/copyright new file mode 100644 index 00000000000..82b520cf64c --- /dev/null +++ b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/copyright @@ -0,0 +1 @@ +GeometryFactory \ No newline at end of file diff --git a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies new file mode 100644 index 00000000000..88ebdd57cc6 --- /dev/null +++ b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies @@ -0,0 +1,25 @@ +Algebraic_foundations +Arithmetic_kernel +BGL +CGAL_Core +Cartesian_kernel +Circulator +Convex_decomposition_3 +Convex_hull_3 +Distance_3 +HalfedgeDS +Hash_map +Homogeneous_kernel +Installation +Intersections_3 +Interval_support +Kernel_23 +Modifier +Number_types +Polyhedron +Polygon_mesh_processing +Profiling_tools +Property_map +STL_Extension +Spatial_sorting +Stream_support diff --git a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt new file mode 100644 index 00000000000..ba1f6f2dc96 --- /dev/null +++ b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt @@ -0,0 +1 @@ +GPL (v3 or later) \ No newline at end of file diff --git a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/maintainer b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/maintainer new file mode 100644 index 00000000000..82b520cf64c --- /dev/null +++ b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/maintainer @@ -0,0 +1 @@ +GeometryFactory \ No newline at end of file diff --git a/Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt new file mode 100644 index 00000000000..5be1aa914d4 --- /dev/null +++ b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt @@ -0,0 +1,10 @@ +# Created by the script cgal_create_CMakeLists +# This is the CMake script for compiling a set of CGAL applications. + +cmake_minimum_required(VERSION 3.12...3.31) +project(Surface_mesh_decomposition_Examples) + +# CGAL and its components +find_package(CGAL REQUIRED) +create_single_source_cgal_program("test_acd.cpp") + diff --git a/Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp new file mode 100644 index 00000000000..93100cf5cc5 --- /dev/null +++ b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp @@ -0,0 +1,58 @@ +#include +#include + +#include +#include + +#include +#include +#include + +using K = CGAL::Exact_predicates_inexact_constructions_kernel; + +using Point = K::Point_3; + +using Convex_hull = std::pair, std::vector > >; +using Mesh = CGAL::Surface_mesh; +namespace PMP = CGAL::Polygon_mesh_processing; + +int main(int argc, char* argv[]) +{ + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); + std::cout << filename << std::endl; + + Mesh mesh; + + std::vector convex_hulls; + + // Try with empty mesh + CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::parameters::maximum_depth(10) + .volume_error(0.1) + .maximum_number_of_convex_hulls(9) + .split_at_concavity(true) + .maximum_number_of_voxels(1000000) + .concurrency_tag(CGAL::Parallel_if_available_tag())); + + assert(convex_hulls.size() == 0); + + if (!PMP::IO::read_polygon_mesh(filename, mesh)) { + std::cerr << "Invalid input." << std::endl; + return 1; + } + + convex_hulls.reserve(9); + + CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::parameters::maximum_depth(10) + .volume_error(0.1) + .maximum_number_of_convex_hulls(9) + .split_at_concavity(true) + .maximum_number_of_voxels(1000000) + .concurrency_tag(CGAL::Parallel_if_available_tag())); + + assert(convex_hulls.size() > 0); + assert(convex_hulls.size() <= 9); + + return 0; +} From 5f5608eb4cc9ae570b2912d39afc3b6022714a78 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 12:25:50 +0100 Subject: [PATCH 31/49] added reference --- .../Convex_decomposition_3/Convex_decomposition_3.txt | 2 +- Documentation/doc/biblio/cgal_manual.bib | 10 ++++++++++ .../Surface_mesh_decomposition/license.txt | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index bfd47c38e97..af1dfe4f7cf 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -101,7 +101,7 @@ support the use of extended kernels in the convex decomposition. Approximate convex decomposition of the elephant.off model.\n From left to right: 1. input mesh 2. 5 convex volumes 3. 8 convex volumes 4. 12 convex volumes \cgalFigureCaptionEnd -The H-VACD, introduced by computes a set of convex volumes that fit the polyhedron. Contrary to the decomposition of the polyhedron into convex parts, the convex volumes cover the polyhedron, but also include additional volume outside of the polyhedron. +The H-VACD method \cgalCite{cgal:mamou2016volumetric}, "Hierarchical volumetrix approximate convex decomposition", computes a set of convex volumes that fit the polyhedron. Contrary to the decomposition of the polyhedron into convex parts, the convex volumes cover the polyhedron, but also include additional volume outside of it. A sufficiently tight enclosure of the polyhedron by several convex volumes allows fast intersection calculation and collision detection among polyhedra while offering a non-hierachical approach and may thus be easier to use in a parallel setting. The resulting set of convex volumes minimizes the volume between their union and the polyhedron while fully including the input polyhedron. While the optimal solution with `n` convex hulls that cover the polydron with the smallest additional volume remains NP-hard, this method provides a fast error-driven approximation. diff --git a/Documentation/doc/biblio/cgal_manual.bib b/Documentation/doc/biblio/cgal_manual.bib index a16cfe0015f..4813821987b 100644 --- a/Documentation/doc/biblio/cgal_manual.bib +++ b/Documentation/doc/biblio/cgal_manual.bib @@ -3516,6 +3516,16 @@ pages = "207--221" year = {2015} } +@article{cgal:mamou2016volumetric, + title={Volumetric hierarchical approximate convex decomposition}, + author={Mamou, Khaled and Lengyel, E and Peters, A}, + journal={Game engine gems}, + volume={3}, + pages={141--158}, + year={2016}, + publisher={AK Peters} +} + % ---------------------------------------------------------------------------- % END OF BIBFILE % ---------------------------------------------------------------------------- diff --git a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt index ba1f6f2dc96..8bb8efcb72b 100644 --- a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt +++ b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/license.txt @@ -1 +1 @@ -GPL (v3 or later) \ No newline at end of file +GPL (v3 or later) From 9475ccb9b6bd129503cd9decbb60db8e8d7e6461 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 12:42:18 +0100 Subject: [PATCH 32/49] fixed cmake project name --- .../test/Surface_mesh_decomposition/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt index 5be1aa914d4..3aea2f17c57 100644 --- a/Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt +++ b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/CMakeLists.txt @@ -2,7 +2,7 @@ # This is the CMake script for compiling a set of CGAL applications. cmake_minimum_required(VERSION 3.12...3.31) -project(Surface_mesh_decomposition_Examples) +project(Surface_mesh_decomposition_Tests) # CGAL and its components find_package(CGAL REQUIRED) From aa05cbaedab6717def4c5e3c9a1eff91b8c51aa3 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 13:08:36 +0100 Subject: [PATCH 33/49] fixing named parameter removing approximate convex decomposition from PMP --- .../Convex_decomposition_3.txt | 4 +- ...pproximate_convex_decomposition_plugin.cpp | 14 +- .../Polygon_mesh_processing/CMakeLists.txt | 2 - .../approximate_convex_decomposition.cpp | 47 - .../approximate_convex_decomposition.h | 1622 ----------------- .../internal/parameters_interface.h | 2 +- .../approximate_convex_decomposition.cpp | 12 +- .../CGAL/approximate_convex_decomposition.h | 4 +- .../Surface_mesh_decomposition/test_acd.cpp | 16 +- 9 files changed, 26 insertions(+), 1697 deletions(-) delete mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp delete mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/approximate_convex_decomposition.h diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index af1dfe4f7cf..219f6cab9b6 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -130,12 +130,12 @@ The method employs a top-down splitting phase followed by a bottom-up merging to The volume calculation, convex hull computation and the concavity search is accelerated by a voxel grid. The grid is prepared before the splitting phase and is labelled into outside, inside or surface. The convex hulls are calculated from voxel corners. Thus, a mesh with a high resolution is less penalized by its number of vertices. The splitting phase typically results in a number of convex volumes larger than targeted. The second phase employs a bottom-up merging that reduces the number of convex volumes to the targeted number while aiming at maintaining a low volume difference between convex volumes and the input mesh. The greedy merging maintains a priority queue to incrementally merge the pair of convex volumes with the smallest increase of volume difference. -The splitting phase is not limited by the chosen `maximum_number_of_convex_hulls`, because a splitting into a larger number of more convex parts with a subsequent merging leads to better results. +The splitting phase is not limited by the chosen `maximum_number_of_convex_volumes`, because a splitting into a larger number of more convex parts with a subsequent merging leads to better results. \subsection Convex_decomposition_3ACD_Parameters Parameters Several parameters of the algorithm impact the quality of the result as well as the running time. -- `maximum_number_of_convex_hulls`: The maximum number of convex volumes output by the method. The actual number may be lower for mostly convex input meshes, e.g., a sphere. The impact on the running time is rather low. The default is 16. +- `maximum_number_of_convex_volumes`: The maximum number of convex volumes output by the method. The actual number may be lower for mostly convex input meshes, e.g., a sphere. The impact on the running time is rather low. The default is 16. - `maximum_depth`: The maximum depth for the hierarchical splitting phase. For complex meshes, a higher maximum depth is required to split small concavities into convex parts. The choice of `maximum_depth` has a larger impact on the running time. The default is 10. - `maximum_number_of_voxels`: This parameter controls the resolution of the voxel grid used for speed-up. Larger numbers result in a higher memory footprint and a higher running time. A small number also limits the `maximum_depth`. The voxel grid is isotropic and the longest axis of the bounding box will be split into a number of voxels equal to the cubic root of `maximum_number_of_voxels`. The default value is 1.000.000. - `volume_error`: The splitting of a convex volume into smaller parts is controlled by the `volume_error` which provides the tolerance for difference in volume. The difference is calculated by \f$ (|C_i| - |P_i|) / |P_i|\f$. The default value is 0.01. Thus, if a convex volume has 1 percent more volume that the part of the input mesh it approximates, it will be further divided. diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp index 8e2671ff008..6d673295291 100644 --- a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp @@ -111,13 +111,13 @@ approximate_convex_decomposition() QApplication::setOverrideCursor(Qt::WaitCursor); - std::vector convex_hulls; - convex_hulls.reserve(9); + std::vector convex_volumes; + convex_volumes.reserve(9); - CGAL::Polygon_mesh_processing::approximate_convex_decomposition(*(sm_item->face_graph()), std::back_inserter(convex_hulls), + CGAL::Polygon_mesh_processing::approximate_convex_decomposition(*(sm_item->face_graph()), std::back_inserter(convex_volumes), CGAL::parameters::maximum_depth(maximumDepth) .volume_error(volumeError) - .maximum_number_of_convex_hulls(maximumConvexHulls) + .maximum_number_of_convex_volumes(maximumConvexHulls) .split_at_concavity(splitConcavity) .maximum_number_of_voxels(numVoxels) .concurrency_tag(CGAL::Parallel_if_available_tag())); @@ -126,10 +126,10 @@ approximate_convex_decomposition() std::vector distinct_colors; // the first color is either the background or the unique domain - compute_deterministic_color_map(QColor(80, 250, 80), convex_hulls.size(), std::back_inserter(distinct_colors)); + compute_deterministic_color_map(QColor(80, 250, 80), convex_volumes.size(), std::back_inserter(distinct_colors)); - for (std::size_t i = 0; i < convex_hulls.size(); i++) { - const Convex_hull& ch = convex_hulls[i]; + for (std::size_t i = 0; i < convex_volumes.size(); i++) { + const Convex_hull& ch = convex_volumes[i]; SMesh sm; CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(ch.first, ch.second, sm); diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 2fcaa4c6912..88430e902e0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -59,7 +59,6 @@ create_single_source_cgal_program("isotropic_remeshing_with_allow_move.cpp") create_single_source_cgal_program("triangle_mesh_autorefinement.cpp") create_single_source_cgal_program("soup_autorefinement.cpp") create_single_source_cgal_program("snap_polygon_soup.cpp") -create_single_source_cgal_program("approximate_convex_decomposition.cpp") find_package(Eigen3 QUIET) include(CGAL_Eigen3_support) @@ -144,7 +143,6 @@ if(TARGET CGAL::TBB_support) target_link_libraries(hausdorff_bounded_error_distance_example PRIVATE CGAL::TBB_support) target_link_libraries(soup_autorefinement PRIVATE CGAL::TBB_support) target_link_libraries(snap_polygon_soup PRIVATE CGAL::TBB_support) - target_link_libraries(approximate_convex_decomposition PRIVATE CGAL::TBB_support) create_single_source_cgal_program("corefinement_parallel_union_meshes.cpp") target_link_libraries(corefinement_parallel_union_meshes PRIVATE CGAL::TBB_support) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp deleted file mode 100644 index 1b3e34005d4..00000000000 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/approximate_convex_decomposition.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include - -#include -#include - -#include -#include -#include - -using K = CGAL::Exact_predicates_inexact_constructions_kernel; - -using Point = K::Point_3; - -using Convex_hull = std::pair, std::vector > >; -using Mesh = CGAL::Surface_mesh; -namespace PMP = CGAL::Polygon_mesh_processing; - -int main(int argc, char* argv[]) -{ - const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/knot2.off"); - std::cout << filename << std::endl; - - Mesh mesh; - if(!PMP::IO::read_polygon_mesh(filename, mesh)) { - std::cerr << "Invalid input." << std::endl; - return 1; - } - - std::vector convex_hulls; - convex_hulls.reserve(9); - - PMP::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), - CGAL::parameters::maximum_depth(10) - .volume_error(0.1) - .maximum_number_of_convex_hulls(9) - .split_at_concavity(true) - .maximum_number_of_voxels(1000000) - .concurrency_tag(CGAL::Parallel_if_available_tag())); - - for (std::size_t i = 0;i - -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifdef CGAL_LINKED_WITH_TBB -#include -#include -#include -#include -#else -#include -#endif - -namespace CGAL { -namespace Polygon_mesh_processing { -namespace internal { - -using Vec3_uint = std::array; - -struct Bbox_uint { - Vec3_uint lower; - Vec3_uint upper; - Bbox_uint(const Vec3_uint &lower, const Vec3_uint &upper) : lower(lower), upper(upper) {} -}; - -enum Grid_cell : int8_t { - OUTSIDE = -1, - SURFACE = 0, - INSIDE = 1 -}; - -void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - std::ofstream stream(filename); - - stream << - "ply\n" << - "format ascii 1.0\n" << - "element vertex " << (grid_size[0] * grid_size[1] * grid_size[2]) << "\n" << - "property double x\n" << - "property double y\n" << - "property double z\n" << - "property uchar red\n" << - "property uchar green\n" << - "property uchar blue\n" << - "end_header\n"; - - for (unsigned int x = 0; x < grid_size[0]; x++) - for (unsigned int y = 0; y < grid_size[1]; y++) - for (unsigned int z = 0; z < grid_size[2]; z++) { - stream << (bb.xmin() + (x + 0.5) * voxel_size) << " " << (bb.ymin() + (y + 0.5) * voxel_size) << " " << (bb.zmin() + (z + 0.5) * voxel_size) << " "; - switch (vox(x, y, z)) { - case INSIDE: - stream << "175 175 100\n"; - break; - case OUTSIDE: - stream << "125 125 175\n"; - break; - case SURFACE: - stream << "200 100 100\n"; - break; - default: - stream << "0 0 0\n"; - break; - } - } - - stream.close(); -} - -template -void export_grid(const std::string& filename, const Bbox_3& bb, std::vector& grid, const Vec3_uint& grid_size, double voxel_size, Filter &filter) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - std::ofstream stream(filename); - - std::size_t count = 0; - for (unsigned int x = 0; x < grid_size[0]; x++) - for (unsigned int y = 0; y < grid_size[1]; y++) - for (unsigned int z = 0; z < grid_size[2]; z++) - if (filter(vox(x, y, z))) count++; - - stream << - "ply\n" << - "format ascii 1.0\n" << - "element vertex " << count << "\n" << - "property double x\n" << - "property double y\n" << - "property double z\n" << - "property uchar red\n" << - "property uchar green\n" << - "property uchar blue\n" << - "end_header\n"; - - for (unsigned int x = 0; x < grid_size[0]; x++) - for (unsigned int y = 0; y < grid_size[1]; y++) - for (unsigned int z = 0; z < grid_size[2]; z++) { - if (!filter(vox(x, y, z))) continue; - stream << (bb.xmin() + (x + 0.5) * voxel_size) << " " << (bb.ymin() + (y + 0.5) * voxel_size) << " " << (bb.zmin() + (z + 0.5) * voxel_size) << " "; - switch (vox(x, y, z)) { - case INSIDE: - stream << "175 175 100\n"; - break; - case OUTSIDE: - stream << "125 125 175\n"; - break; - case SURFACE: - stream << "200 100 100\n"; - break; - default: - stream << "0 0 0\n"; - break; - } - } - - stream.close(); -} - -void export_voxels(const std::string& filename, const Bbox_3& bb, std::vector& voxels, double voxel_size) { - std::ofstream stream(filename); - - stream << - "ply\n" << - "format ascii 1.0\n" << - "element vertex " << voxels.size() << "\n" << - "property double x\n" << - "property double y\n" << - "property double z\n" << - "end_header\n"; - - for (const Vec3_uint& v : voxels) { - stream << (bb.xmin() + (v[0] + 0.5) * voxel_size) << " " << (bb.ymin() + (v[1] + 0.5) * voxel_size) << " " << (bb.zmin() + (v[2] + 0.5) * voxel_size) << "\n"; - } - - stream.close(); -} - -template -void export_points(const std::string& filename, const Bbox_3& bb, std::vector& points) { - std::ofstream stream(filename); - - stream << - "ply\n" << - "format ascii 1.0\n" << - "element vertex " << points.size() << "\n" << - "property double x\n" << - "property double y\n" << - "property double z\n" << - "end_header\n"; - - for (const Point_3& p : points) { - stream << (bb.xmin() + p.x()) << " " << (bb.ymin() + p.y()) << " " << (bb.zmin() + p.z()) << "\n"; - } - - stream.close(); -} - -template -void export_points(const std::string& filename, Range& points) { - std::ofstream stream(filename); - stream << std::setprecision(18); - - for (const auto& p : points) { - stream << p.x() << " " << p.y() << " " << p.z() << "\n"; - } - - stream.close(); -} - -template -Box box_union(const Box& a, const Box& b) { - using FT = decltype(a.xmin()); - return Box( - (std::min)(a.xmin(), b.xmin()), - (std::min)(a.ymin(), b.ymin()), - (std::min)(a.zmin(), b.zmin()), - (std::max)(a.xmax(), b.xmax()), - (std::max)(a.ymax(), b.ymax()), - (std::max)(a.zmax(), b.zmax())); -} - -template -std::tuple calculate_grid_size(Bbox_3& bb, const unsigned int number_of_voxels) { - std::size_t max_voxels_axis = std::cbrt(number_of_voxels); - assert(max_voxels_axis > 3); - // get longest axis - FT longest = 0; - - if (bb.x_span() >= bb.y_span() && bb.x_span() >= bb.z_span()) - longest = bb.x_span(); - else if (bb.y_span() >= bb.x_span() && bb.y_span() >= bb.z_span()) - longest = bb.y_span(); - else if (bb.z_span() >= bb.x_span() && bb.z_span() >= bb.y_span()) - longest = bb.z_span(); - - const FT voxel_size = longest * FT(1.0 / (max_voxels_axis - 3)); - - FT s = 1.5 * voxel_size; - - bb = Bbox_3(to_double(bb.xmin() - s), to_double(bb.ymin() - s), to_double(bb.zmin() - s), to_double(bb.xmax() + s), to_double(bb.ymax() + s), to_double(bb.zmax() + s)); - - return { Vec3_uint{static_cast(to_double(bb.x_span() / voxel_size + 0.5)), static_cast(to_double(bb.y_span() / voxel_size + 0.5)), static_cast(to_double(bb.z_span() / voxel_size + 0.5))}, voxel_size}; -} - -template -const typename GeomTraits::Point_3 &point(typename boost::graph_traits::face_descriptor fd, - const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values()) -{ - using parameters::choose_parameter; - using parameters::get_parameter; - typename GetVertexPointMap::const_type - vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); - - return get(vpm, target(halfedge(fd, pmesh), pmesh)); -} - -template -Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_traits::face_descriptor fd, const Bbox_3& bb, const FT& voxel_size) { - Bbox_3 face_bb = face_bbox(fd, mesh); - double vs = to_double(voxel_size); - return Bbox_uint({ - static_cast((face_bb.xmin() - bb.xmin()) / vs - 0.5), - static_cast((face_bb.ymin() - bb.ymin()) / vs - 0.5), - static_cast((face_bb.zmin() - bb.zmin()) / vs - 0.5) - }, { - static_cast((face_bb.xmax() - bb.xmin()) / vs + 0.5), - static_cast((face_bb.ymax() - bb.ymin()) / vs + 0.5), - static_cast((face_bb.zmax() - bb.zmin()) / vs + 0.5) - }); -} - -template -Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { - double vs = to_double(voxel_size); - return Bbox_3( - bb.xmin() + voxel[0] * vs, - bb.ymin() + voxel[1] * vs, - bb.zmin() + voxel[2] * vs, - bb.xmin() + (voxel[0] + 1) * vs, - bb.ymin() + (voxel[1] + 1) * vs, - bb.zmin() + (voxel[2] + 1) * vs - ); -} - -void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_uint& grid_size, std::deque& todo) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - - while (!todo.empty()) { - auto [x, y, z0] = todo.front(); - todo.pop_front(); - if (vox(x, y, z0) != INSIDE) - return; - - bool xneg = false, xpos = false; - bool yneg = false, ypos = false; - bool zneg = false, zpos = false; - - // positive direction - for (unsigned int z = z0; z < grid_size[2]; z++) { - if (vox(x, y, z) != INSIDE) - break; - - vox(x, y, z) = label; - if (x > 0) { - if (vox(x - 1, y, z) == INSIDE) { - if (!xneg) { - xneg = true; - todo.push_back({ x - 1, y, z }); - } - } - else xneg = false; - } - - if (x < grid_size[0] - 1) { - if (vox(x + 1, y, z) == INSIDE) { - if (!xpos) { - xpos = true; - todo.push_back({ x + 1, y, z }); - } - } - else xpos = false; - } - - if (y > 0) { - if (vox(x, y - 1, z) == INSIDE) { - if (!yneg) { - yneg = true; - todo.push_front({ x, y - 1, z }); - } - } - else yneg = false; - } - - if (y < grid_size[1] - 1) { - if (vox(x, y + 1, z) == INSIDE) { - if (!ypos) { - ypos = true; - todo.push_front({ x, y + 1, z }); - } - } - else ypos = false; - } - } - - xneg = xpos = yneg = ypos = zneg = zpos = false; - - if (z0 == 0) - continue; - - for (unsigned int z = z0 - 1; z > 0; z--) { - if (vox(x, y, z) != INSIDE) - break; - - vox(x, y, z) = label; - if (x > 0) { - if (vox(x - 1, y, z) == INSIDE) { - if (!xneg) { - xneg = true; - todo.push_back({ x - 1, y, z }); - } - } - else xneg = false; - } - - if (x < grid_size[0] - 1) { - if (vox(x + 1, y, z) == INSIDE) { - if (!xpos) { - xpos = true; - todo.push_back({ x + 1, y, z }); - } - } - else xpos = false; - } - - if (y > 0) { - if (vox(x, y - 1, z) == INSIDE) { - if (!yneg) { - yneg = true; - todo.push_front({ x, y - 1, z }); - } - } - else yneg = false; - } - - if (y < grid_size[1] - 1) { - if (vox(x, y + 1, z) == INSIDE) { - if (!ypos) { - ypos = true; - todo.push_front({ x, y + 1, z }); - } - } - else ypos = false; - } - } - } -} - -// Valid voxel grids separate OUTSIDE from INSIDE via SURFACE -bool is_valid(std::vector& grid, const Vec3_uint& grid_size) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - - std::size_t in = 0, out = 0, surface = 0, other = 0, violations = 0; - - for (unsigned int x = 1; x < grid_size[0] - 1; x++) - for (unsigned int y = 1; y < grid_size[1] - 1; y++) - for (unsigned int z = 1; z < grid_size[2] - 1; z++) { - switch (vox(x, y, z)) { - case INSIDE: - if ( vox(x - 1, y, z) == OUTSIDE - || vox(x + 1, y, z) == OUTSIDE - || vox(x, y - 1, z) == OUTSIDE - || vox(x, y + 1, z) == OUTSIDE - || vox(x, y, z - 1) == OUTSIDE - || vox(x, y, z + 1) == OUTSIDE) { - std::cout << "touching I-O: " << x << " " << y << " " << z << std::endl; - violations++; - } - in++; - break; - case OUTSIDE: - if (vox(x - 1, y, z) == INSIDE - || vox(x + 1, y, z) == INSIDE - || vox(x, y - 1, z) == INSIDE - || vox(x, y + 1, z) == INSIDE - || vox(x, y, z - 1) == INSIDE - || vox(x, y, z + 1) == INSIDE) { - std::cout << "touching O-I: " << x << " " << y << " " << z << std::endl; - violations++; - } - out++; - break; - case SURFACE: - surface++; - break; - default: - std::cout << "other " << x << " " << y << " " << z << std::endl; - other++; - break; - } - } - std::cout << "i: " << in << " o: " << out << " s: " << surface << " " << other << std::endl; - std::cout << "violations: " << violations << std::endl; - - return violations == 0; -} - -// Only works for closed meshes -void label_floodfill(std::vector& grid, const Vec3_uint& grid_size) { - // Walk around boundary and start floodfill when voxel label is INSIDE - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - - std::deque todo; - - // xmin/xmax - for (unsigned int y = 0; y < grid_size[1]; y++) - for (unsigned int z = 0; z < grid_size[2]; z++) { - if (vox(0, y, z) == INSIDE) { - todo.push_front({0, y, z}); - scanline_floodfill(OUTSIDE, grid, grid_size, todo); - } - - if (vox(grid_size[0] - 1, y, z) == INSIDE) { - todo.push_front({ grid_size[0] - 1, y, z }); - scanline_floodfill(OUTSIDE, grid, grid_size, todo); - } - } - - // ymin/ymax - for (unsigned int x = 0; x < grid_size[0]; x++) - for (unsigned int z = 0; z < grid_size[2]; z++) { - if (vox(x, 0, z) == INSIDE) { - todo.push_front({ x, 0, z }); - scanline_floodfill(OUTSIDE, grid, grid_size, todo); - } - - if (vox(x, grid_size[1] - 1, z) == INSIDE) { - todo.push_front({ x, grid_size[1] - 1, z }); - scanline_floodfill(OUTSIDE, grid, grid_size, todo); - } - } - - // ymin/ymax - for (unsigned int x = 0; x < grid_size[0]; x++) - for (unsigned int y = 0; y < grid_size[1]; y++) { - if (vox(x, y, 0) == INSIDE) { - todo.push_front({ x, y, 0 }); - scanline_floodfill(OUTSIDE, grid, grid_size, todo); - } - - if (vox(x, y, grid_size[2] - 1) == INSIDE) { - todo.push_front({ x, y, grid_size[2] - 1 }); - scanline_floodfill(OUTSIDE, grid, grid_size, todo); - } - } -} - -void naive_floodfill(std::vector& grid, const Vec3_uint& grid_size) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - - std::queue queue; - queue.push({0, 0, 0}); - - while (!queue.empty()) { - auto [x, y, z] = queue.front(); - queue.pop(); - - if (vox(x, y, z) != INSIDE) - continue; - - vox(x, y, z) = OUTSIDE; - if (x > 0) - if (vox(x - 1, y, z) == INSIDE) { - queue.push({ x - 1, y, z }); - } - - if (x < grid_size[0] - 1) - if (vox(x + 1, y, z) == INSIDE) { - queue.push({ x + 1, y, z }); - } - - if (y > 0) - if (vox(x, y - 1, z) == INSIDE) { - queue.push({ x, y - 1, z }); - } - - if (y < grid_size[1] - 1) - if (vox(x, y + 1, z) == INSIDE) { - queue.push({ x, y + 1, z }); - } - - if (z > 0) - if (vox(x, y, z - 1) == INSIDE) { - queue.push({ x, y, z - 1 }); - } - - if (z < grid_size[2] - 1) - if (vox(x, y, z + 1) == INSIDE) { - queue.push({ x, y, z + 1 }); - } - } -} - -template -void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph& mesh, CGAL::Parallel_tag) { -#ifdef CGAL_LINKED_WITH_TBB - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - using face_descriptor = typename boost::graph_traits::face_descriptor; - - using Point_3 = typename GeomTraits::Point_3; - using Vector_3 = typename GeomTraits::Vector_3; - using Ray_3 = typename GeomTraits::Ray_3; - - using Primitive = CGAL::AABB_face_graph_triangle_primitive; - using Traits = CGAL::AABB_traits_3; - using Tree = CGAL::AABB_tree; - using Ray_intersection = std::optional::Type>; - - Tree tree(faces(mesh).first, faces(mesh).second, mesh); - - std::array dirs = { Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1} }; - - tbb::parallel_for(std::size_t(0), std::size_t(grid_size[0]), [&](const std::size_t x) - { - for (std::size_t y = 0; y < grid_size[1]; y++) - for (std::size_t z = 0; z < grid_size[2]; z++) { - if (vox(x, y, z) == SURFACE) - continue; - - Point_3 c(bb.xmin() + (x + 0.5) * voxel_size, bb.ymin() + (y + 0.5) * voxel_size, bb.zmin() + (z + 0.5) * voxel_size); - - unsigned int inside = 0; - unsigned int outside = 0; - - for (std::size_t i = 0; i < 6; i++) { - Ray_intersection intersection = tree.first_intersection(Ray_3(c, dirs[i])); - if (intersection) { - // A segment intersection is not helpful as it means the triangle normal is orthogonal to the ray - if (std::get_if(&(intersection->first))) { - face_descriptor fd = intersection->second; - Vector_3 n = compute_face_normal(fd, mesh); - if (dirs[i] * n > 0) - inside++; - else - outside++; - } - } - } - - if (inside >= 3 && outside == 0) { - vox(x, y, z) = INSIDE; - } - else - vox(x, y, z) = OUTSIDE; - } - } - ); -#else - CGAL_USE(grid); - CGAL_USE(grid_size); - CGAL_USE(bb); - CGAL_USE(voxel_size); - CGAL_USE(mesh); -#endif -} - -template -void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size, const FaceGraph& mesh, CGAL::Sequential_tag) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - using face_descriptor = typename boost::graph_traits::face_descriptor; - - using Point_3 = typename GeomTraits::Point_3; - using Vector_3 = typename GeomTraits::Vector_3; - using Ray_3 = typename GeomTraits::Ray_3; - - using Primitive = CGAL::AABB_face_graph_triangle_primitive; - using Traits = CGAL::AABB_traits_3; - using Tree = CGAL::AABB_tree; - using Ray_intersection = std::optional::Type>; - - Tree tree(faces(mesh).first, faces(mesh).second, mesh); - - std::array dirs = { Vector_3{1, 0, 0}, Vector_3{-1, 0, 0}, Vector_3{0, 1, 0}, Vector_3{0,-1, 0}, Vector_3{0, 0, 1}, Vector_3{0, 0,-1} }; - - for (std::size_t x = 0; x < grid_size[0]; x++) - { - for (std::size_t y = 0; y < grid_size[1]; y++) - for (std::size_t z = 0; z < grid_size[2]; z++) { - if (vox(x, y, z) == SURFACE) - continue; - - Point_3 c(bb.xmin() + (x + 0.5) * voxel_size, bb.ymin() + (y + 0.5) * voxel_size, bb.zmin() + (z + 0.5) * voxel_size); - - unsigned int inside = 0; - unsigned int outside = 0; - - for (std::size_t i = 0; i < 6; i++) { - Ray_intersection intersection = tree.first_intersection(Ray_3(c, dirs[i])); - if (intersection) { - // A segment intersection is not helpful as it means the triangle normal is orthogonal to the ray - if (std::get_if(&(intersection->first))) { - face_descriptor fd = intersection->second; - Vector_3 n = compute_face_normal(fd, mesh); - if (dirs[i] * n > 0) - inside++; - else - outside++; - } - } - } - - if (inside >= 3 && outside == 0) { - vox(x, y, z) = INSIDE; - } - else - vox(x, y, z) = OUTSIDE; - } - } -} - -template -struct Convex_hull_candidate { - using FT = typename GeomTraits::FT; - using Point_3 = typename GeomTraits::Point_3; - Iso_cuboid_3 bbox; - FT voxel_volume; - FT volume; - FT volume_error; - std::vector points; - std::vector> indices; - - Convex_hull_candidate() noexcept : voxel_volume(0), volume(0), volume_error(0) {} - Convex_hull_candidate(Convex_hull_candidate&& o) noexcept { - bbox = o.bbox; - voxel_volume = o.voxel_volume; - volume = o.volume; - volume_error = o.volume_error; - points = std::move(o.points); - indices = std::move(o.indices); - } - - Convex_hull_candidate& operator= (Convex_hull_candidate&& o) noexcept { - bbox = o.bbox; - voxel_volume = o.voxel_volume; - volume = o.volume; - volume_error = o.volume_error; - points = std::move(o.points); - indices = std::move(o.indices); - - return *this; - } -}; - -template -struct Candidate { - using FT = typename GeomTraits::FT; - using Point_3 = typename GeomTraits::Point_3; - std::size_t index; - std::vector surface; - std::vector new_surface; - std::vector inside; - std::size_t depth; - Bbox_uint bbox; - Convex_hull_candidate ch; - - Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {index = cidx++;} - Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) { index = cidx++; } -private: - inline static std::size_t cidx = 0; -}; - -template -typename GeomTraits::FT volume(const std::vector &pts, const std::vector> &indices) { - ::CGAL::internal::Evaluate evaluate; - - typename GeomTraits::FT vol = 0; - typename GeomTraits::Compute_volume_3 cv3; - - typename GeomTraits::Point_3 origin(0, 0, 0); - - for (const std::array &i : indices) { - vol += cv3(origin, pts[i[0]], pts[i[1]], pts[i[2]]); - evaluate(vol); - } - - return vol; -} - -void enlarge(Bbox_uint& bbox, const Vec3_uint& v) { - bbox.lower[0] = (std::min)(bbox.lower[0], v[0]); - bbox.lower[1] = (std::min)(bbox.lower[1], v[1]); - bbox.lower[2] = (std::min)(bbox.lower[2], v[2]); - bbox.upper[0] = (std::max)(bbox.upper[0], v[0]); - bbox.upper[1] = (std::max)(bbox.upper[1], v[1]); - bbox.upper[2] = (std::max)(bbox.upper[2], v[2]); -} - -template > -struct is_std_hashable : std::false_type {}; - -template -struct is_std_hashable>()(std::declval()))>> : std::true_type {}; - -template::type::FT>, typename std::is_same::type::Kernel_tag, CGAL::Cartesian_tag>::type>::type> -struct Unordered_set_if_available { -}; - -template -struct Unordered_set_if_available { - using type = std::unordered_set; -}; - -template -struct Unordered_set_if_available { - using type = std::set; -}; - -template -void compute_candidate(Candidate &c, const Bbox_3& bb, typename GeomTraits::FT voxel_size) { - using Point_3 = typename GeomTraits::Point_3; - using FT = typename GeomTraits::FT; - - c.bbox.lower = c.bbox.upper = c.surface[0]; - - typename Unordered_set_if_available::type voxel_points; - - for (const Vec3_uint& v : c.surface) { - enlarge(c.bbox, v); - FT xmin = bb.xmin() + FT(v[0]) * voxel_size; - FT ymin = bb.ymin() + FT(v[1]) * voxel_size; - FT zmin = bb.zmin() + FT(v[2]) * voxel_size; - FT xmax = bb.xmin() + FT(v[0] + 1) * voxel_size; - FT ymax = bb.ymin() + FT(v[1] + 1) * voxel_size; - FT zmax = bb.zmin() + FT(v[2] + 1) * voxel_size; - voxel_points.insert(Point_3(xmin, ymin, zmin)); - voxel_points.insert(Point_3(xmin, ymax, zmin)); - voxel_points.insert(Point_3(xmin, ymin, zmax)); - voxel_points.insert(Point_3(xmin, ymax, zmax)); - voxel_points.insert(Point_3(xmax, ymin, zmin)); - voxel_points.insert(Point_3(xmax, ymax, zmin)); - voxel_points.insert(Point_3(xmax, ymin, zmax)); - voxel_points.insert(Point_3(xmax, ymax, zmax)); - } - - for (const Vec3_uint& v : c.new_surface) { - enlarge(c.bbox, v); - FT xmin = bb.xmin() + FT(v[0]) * voxel_size; - FT ymin = bb.ymin() + FT(v[1]) * voxel_size; - FT zmin = bb.zmin() + FT(v[2]) * voxel_size; - FT xmax = bb.xmin() + FT(v[0] + 1) * voxel_size; - FT ymax = bb.ymin() + FT(v[1] + 1) * voxel_size; - FT zmax = bb.zmin() + FT(v[2] + 1) * voxel_size; - voxel_points.insert(Point_3(xmin, ymin, zmin)); - voxel_points.insert(Point_3(xmin, ymax, zmin)); - voxel_points.insert(Point_3(xmin, ymin, zmax)); - voxel_points.insert(Point_3(xmin, ymax, zmax)); - voxel_points.insert(Point_3(xmax, ymin, zmin)); - voxel_points.insert(Point_3(xmax, ymax, zmin)); - voxel_points.insert(Point_3(xmax, ymin, zmax)); - voxel_points.insert(Point_3(xmax, ymax, zmax)); - } - - convex_hull_3(voxel_points.begin(), voxel_points.end(), c.ch.points, c.ch.indices); - - c.ch.volume = volume(c.ch.points, c.ch.indices); - - CGAL_assertion(c.ch.volume > 0); - c.ch.bbox = bbox_3(c.ch.points.begin(), c.ch.points.end()); - - c.ch.voxel_volume = (voxel_size * voxel_size * voxel_size) * FT(double(c.inside.size() + c.surface.size() + c.new_surface.size())); - c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; -} - -template -void fill_grid(Candidate &c, std::vector &grid, const FaceGraph &mesh, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size, Concurrency_tag tag) { - const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { - return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; - }; - - for (const typename boost::graph_traits::face_descriptor fd : faces(mesh)) { - Bbox_uint face_bb = grid_bbox_face(mesh, fd, bb, voxel_size); - CGAL_assertion(face_bb.lower[0] <= face_bb.upper[0]); - CGAL_assertion(face_bb.lower[1] <= face_bb.upper[1]); - CGAL_assertion(face_bb.lower[2] <= face_bb.upper[2]); - CGAL_assertion(face_bb.upper[0] < grid_size[0]); - CGAL_assertion(face_bb.upper[1] < grid_size[1]); - CGAL_assertion(face_bb.upper[2] < grid_size[2]); - for (unsigned int x = face_bb.lower[0]; x <= face_bb.upper[0]; x++) - for (unsigned int y = face_bb.lower[1]; y <= face_bb.upper[1]; y++) - for (unsigned int z = face_bb.lower[2]; z <= face_bb.upper[2]; z++) { - Iso_cuboid_3 box = bbox_voxel({ x, y, z }, bb, voxel_size); - const typename GeomTraits::Point_3 &p = point(fd, mesh); - if (do_intersect(triangle(fd, mesh), box) || box.has_on_bounded_side(p)) - vox(x, y, z) = Grid_cell::SURFACE; - } - } - - if (CGAL::is_closed(mesh)) - naive_floodfill(grid, grid_size); - else - rayshooting_fill(grid, grid_size, bb, voxel_size, mesh, tag); - - c.bbox.upper = {grid_size[0] - 1, grid_size[1] - 1, grid_size[2] - 1}; - - for (unsigned int x = 0; x < grid_size[0]; x++) - for (unsigned int y = 0; y < grid_size[1]; y++) - for (unsigned int z = 0; z < grid_size[2]; z++) - if (vox(x, y, z) == INSIDE) - c.inside.push_back({x, y, z}); - else if (vox(x, y, z) == SURFACE) - c.surface.push_back({x, y, z}); -} - -template -void init(Candidate &c, const FaceGraph& mesh, std::vector& grid, const Bbox_3& bb, const Vec3_uint& grid_size, const typename GeomTraits::FT& voxel_size, Concurrency_tag tag) { - internal::fill_grid(c, grid, mesh, bb, grid_size, voxel_size, tag); - compute_candidate(c, bb, voxel_size); -} - -template -void split(std::vector &candidates, Candidate_& c, unsigned int axis, unsigned int location) { - //Just split the voxel bbox along 'axis' after voxel index 'location' - Candidate_ upper(c.depth + 1, c.bbox); - Candidate_ lower(c.depth + 1, c.bbox); - - CGAL_assertion(c.bbox.lower[axis] < location); - CGAL_assertion(c.bbox.upper[axis] > location); - - upper.bbox.lower[axis] = location + 1; - lower.bbox.upper[axis] = location; - - for (const Vec3_uint& v : c.surface) { - CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); - CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); - CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); - if (location < v[axis]) - upper.surface.push_back(v); - else - lower.surface.push_back(v); - } - - for (const Vec3_uint& v : c.new_surface) { - CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); - CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); - CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); - if (location < v[axis]) - upper.new_surface.push_back(v); - else - lower.new_surface.push_back(v); - } - - for (const Vec3_uint& v : c.inside) { - CGAL_assertion(c.bbox.lower[0] <= v[0] && v[0] <= c.bbox.upper[0]); - CGAL_assertion(c.bbox.lower[1] <= v[1] && v[1] <= c.bbox.upper[1]); - CGAL_assertion(c.bbox.lower[2] <= v[2] && v[2] <= c.bbox.upper[2]); - if (location < v[axis]) { - if ((location + 1) == v[axis]) - upper.new_surface.push_back(v); - else - upper.inside.push_back(v); - } - else { - if (location == v[axis]) - lower.new_surface.push_back(v); - else - lower.inside.push_back(v); - } - } - - if (!upper.surface.empty()) - candidates.emplace_back(std::move(upper)); - - if (!lower.surface.empty()) - candidates.emplace_back(std::move(lower)); -} - -std::size_t concavity(const Vec3_uint& s, const Vec3_uint& e, int axis, std::vector& grid, const Vec3_uint& grid_size) { - const auto vox = [&grid, &grid_size](const Vec3_uint &v) -> int8_t& { - return grid[v[2] + (v[1] * grid_size[2]) + (v[0] * grid_size[1] * grid_size[2])]; - }; - std::size_t i; - for (i = s[axis];i s[axis]; j--) { - Vec3_uint v = s; - v[axis] = j; - if (vox(v) != OUTSIDE) - break; - } - - std::size_t res = (i - s[axis]) + (e[axis] - j); - if(res >= grid_size[axis]) - std::cout << "violation!" << std::endl; - return (i - s[axis]) + (e[axis] - j); -} - -void choose_splitting_location_by_concavity(unsigned int& axis, unsigned int& location, const Bbox_uint &bbox, std::vector& grid, const Vec3_uint& grid_size) { - std::size_t length = bbox.upper[axis] - bbox.lower[axis] + 1; - - CGAL_assertion(length >= 8); - CGAL_precondition(axis <= 2); - - std::size_t idx0 = 0, idx1 = 1, idx2 = 2; - - switch(axis) { - case 0: - idx0 = 1; - idx1 = 2; - idx2 = 0; - break; - case 1: - idx0 = 0; - idx1 = 2; - idx2 = 1; - break; - case 2: - idx0 = 0; - idx1 = 1; - idx2 = 2; - break; - } - - std::vector diam(length, 0), diam2(length, 0); - - for (std::size_t i = bbox.lower[idx2]; i <= bbox.upper[idx2]; i++) { - for (std::size_t j = bbox.lower[idx0]; j <= bbox.upper[idx0]; j++) { - Vec3_uint s, e; - s[idx2] = i; - s[idx1] = bbox.lower[idx1]; - s[idx0] = j; - e[idx2] = i; - e[idx1] = bbox.upper[idx1]; - e[idx0] = j; - diam[i - bbox.lower[idx2]] += concavity(s, e, idx1, grid, grid_size); - } - } - - for (std::size_t i = bbox.lower[idx2]; i <= bbox.upper[idx2]; i++) { - for (std::size_t j = bbox.lower[idx1]; j <= bbox.upper[idx1]; j++) { - Vec3_uint s, e; - s[idx0] = bbox.lower[idx0]; - s[idx2] = i; - s[idx1] = j; - e[idx0] = bbox.upper[idx0]; - e[idx2] = i; - e[idx1] = j; - diam2[i - bbox.lower[idx2]] += concavity(s, e, idx0, grid, grid_size); - } - } - - // Skip initial border - std::size_t border = (length / 10) + 0.5; - std::size_t pos1, end1 = length; - int grad = diam[0] - diam[1]; - for (pos1 = 2; pos1 < border; pos1++) { - int grad1 = diam[pos1 - 1] - diam[pos1]; - // Stop if the gradient flips or flattens significantly - if (!(grad * grad1 > 0 && grad1 > (grad>>1))) - break; - if (grad < grad1) - grad = grad1; - } - - grad = diam[length - 1] - diam[length - 2]; - for (end1 = length - 3; end1 > (length - border - 1); end1--) { - int grad1 = diam[end1 + 1] - diam[end1]; - // Stop if the gradient flips or flattens significantly - if (!(grad * grad1 > 0 && grad1 > (grad >> 1))) - break; - if (grad < grad1) - grad = grad1; - } - - std::size_t pos2, end2 = length; - grad = diam2[0] - diam2[1]; - for (pos2 = 2; pos2 < border; pos2++) { - int grad2 = diam[pos2 - 1] - diam[pos2]; - // Stop if the gradient flips or flattens significantly - if (!(grad * grad2 > 0 && grad2 > (grad >> 1))) - break; - if (grad < grad2) - grad = grad2; - } - - grad = diam2[length - 1] - diam2[length - 2]; - for (end2 = length - 3; end2 > (length - border - 1); end2--) { - int grad2 = diam[end2 + 1] - diam[end2]; - // Stop if the gradient flips or flattens significantly - if (!(grad * grad2 > 0 && grad2 > (grad >> 1))) - break; - if (grad < grad2) - grad = grad2; - } - - std::size_t conc1 = abs(diam[pos1 + 1] - diam[pos1]); - std::size_t conc2 = abs(diam2[pos2 + 1] - diam2[pos2]); - - for (std::size_t i = pos1;i conc1) { - pos1 = i - 1; - conc1 = abs(diam[i] - diam[i - 1]); - } - - for (std::size_t i = pos2; i < end2; i++) - if (unsigned(abs(diam2[i] - diam2[i - 1])) > conc2) { - pos2 = i - 1; - conc2 = abs(diam2[i] - diam2[i - 1]); - } - - - if (conc1 <= conc2) { - if (pos1 < 2 || (length - 3) < pos1) - location = (bbox.upper[axis] + bbox.lower[axis]) / 2; - else - location = pos1 + bbox.lower[axis]; - } - else { - if (pos2 < 2 || (length - 3) < pos2) - location = (bbox.upper[axis] + bbox.lower[axis]) / 2; - else - location = pos2 + bbox.lower[axis]; - } -} - -template -void choose_splitting_plane(Candidate& c, unsigned int &axis, unsigned int &location, std::vector& grid, const Vec3_uint& grid_size, const NamedParameters& np) { - const bool search_concavity = parameters::choose_parameter(parameters::get_parameter(np, internal_np::split_at_concavity), true); - const std::array span = {c.bbox.upper[0] - c.bbox.lower[0], c.bbox.upper[1] - c.bbox.lower[1], c.bbox.upper[2] - c.bbox.lower[2]}; - - // Split longest axis - axis = (span[0] >= span[1]) ? 0 : 1; - axis = (span[axis] >= span[2]) ? axis : 2; - - if (span[axis] >= 8 && search_concavity) - choose_splitting_location_by_concavity(axis, location, c.bbox, grid, grid_size); - else - location = (c.bbox.upper[axis] + c.bbox.lower[axis]) / 2; -} - -template -bool finished(Candidate &c, const NamedParameters& np) { - const typename GeomTraits::FT max_error = parameters::choose_parameter(parameters::get_parameter(np, internal_np::volume_error), 0.01); - - if (c.ch.volume_error <= max_error) - return true; - - std::size_t max_span = 0; - for (std::size_t i = 0;i<3;i++) { - const std::size_t span = c.bbox.upper[i] - c.bbox.lower[i]; - max_span = (std::max)(max_span, span); - } - - if (max_span <= 1) - return true; - - return false; -} - -template -void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Parallel_tag) { -#ifdef CGAL_LINKED_WITH_TBB - const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); - - std::vector> final_candidates; - - std::size_t depth = 0; - - while (!candidates.empty() && depth < max_depth) { - depth++; - std::vector> former_candidates = std::move(candidates); - for (Candidate& c : former_candidates) { - - if (finished(c, np)) { - CGAL::Bbox_3 bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); - bbox.scale(1.1); - c.ch.bbox = bbox; - final_candidates.push_back(std::move(c)); - continue; - } - unsigned int axis = 0, location = 0; - choose_splitting_plane(c, axis, location, grid, grid_size, np); - split(candidates, c, axis, location); - } - tbb::parallel_for_each(candidates, [&](Candidate& c) { - compute_candidate(c, bbox, voxel_size); - }); - } - - if (candidates.empty()) - std::swap(candidates, final_candidates); - else - std::move(final_candidates.begin(), final_candidates.end(), std::back_inserter(candidates)); - -#else - CGAL_USE(candidates); - CGAL_USE(grid); - CGAL_USE(grid_size); - CGAL_USE(bbox); - CGAL_USE(voxel_size); - CGAL_USE(np); -#endif -} - -template -void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Sequential_tag) { - const std::size_t max_depth = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_depth), 10); - - std::vector> final_candidates; - - std::size_t depth = 0; - - while (!candidates.empty() && depth < max_depth) { - depth++; - std::vector> former_candidates = std::move(candidates); - for (Candidate& c : former_candidates) { - - if (finished(c, np)) { - CGAL::Bbox_3 bbox = CGAL::bbox_3(c.ch.points.begin(), c.ch.points.end()); - bbox.scale(1.1); - c.ch.bbox = bbox; - final_candidates.push_back(std::move(c)); - continue; - } - unsigned int axis = 0, location = 0; - choose_splitting_plane(c, axis, location, grid, grid_size, np); - split(candidates, c, axis, location); - } - - for (Candidate& c : candidates) - compute_candidate(c, bbox, voxel_size); - } - - if (candidates.empty()) - std::swap(candidates, final_candidates); - else - std::move(final_candidates.begin(), final_candidates.end(), std::back_inserter(candidates)); -} - -template -void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const unsigned int max_convex_hulls, CGAL::Parallel_tag) { -#ifdef CGAL_LINKED_WITH_TBB - if (candidates.size() <= max_convex_hulls) - return; - - using FT = typename GeomTraits::FT; - using Point_3 = typename GeomTraits::Point_3; - - struct Merged_candidate { - std::size_t ch_a, ch_b; - int ch; - FT volume_error; - - bool operator < (const Merged_candidate& other) const { - return (volume_error > other.volume_error); - } - - Merged_candidate() : ch_a(-1), ch_b(-1) {} - Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} - }; - - tbb::concurrent_unordered_map> hulls; - std::atomic num_hulls = candidates.size(); - - std::unordered_set keep; - - for (std::size_t i = 0; i < candidates.size(); i++) { - hulls.emplace(i, std::move(candidates[i])); - keep.insert(i); - } - - candidates.clear(); - candidates.reserve(max_convex_hulls); - - std::vector todo; - tbb::concurrent_priority_queue queue; - - const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { - Convex_hull_candidate& ci = hulls[m.ch_a]; - Convex_hull_candidate& cj = hulls[m.ch_b]; - m.ch = num_hulls.fetch_add(1); - Convex_hull_candidate& ch = hulls[m.ch]; - - ch.bbox = box_union(ci.bbox, cj.bbox); - std::vector pts(ci.points.begin(), ci.points.end()); - pts.reserve(pts.size() + cj.points.size()); - std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); - - ch.volume = volume(ch.points, ch.indices); - - ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; - }; - - for (std::size_t i : keep) { - const Convex_hull_candidate& ci = hulls[i]; - for (std::size_t j : keep) { - if (j <= i) - continue; - const Convex_hull_candidate& cj = hulls[j]; - if (CGAL::do_intersect(ci.bbox, cj.bbox)) - todo.emplace_back(Merged_candidate(i, j)); - else { - Merged_candidate m(i, j); - Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - m.ch = -1; - m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; - queue.push(std::move(m)); - } - } - } - - // parallel for if available - tbb::parallel_for_each(todo, do_merge); - for (Merged_candidate& m : todo) - queue.push(std::move(m)); - todo.clear(); - - while (!queue.empty() && keep.size() > max_convex_hulls) { - Merged_candidate m; - while (!queue.try_pop(m) && !queue.empty()); - - auto ch_a = hulls.find(m.ch_a); - if (ch_a == hulls.end()) - continue; - - auto ch_b = hulls.find(m.ch_b); - if (ch_b == hulls.end()) - continue; - - if (m.ch == -1) - do_merge(m); - - keep.erase(m.ch_a); - keep.erase(m.ch_b); - - hulls.unsafe_erase(ch_a); - hulls.unsafe_erase(ch_b); - - const Convex_hull_candidate& cj = hulls[m.ch]; - - for (std::size_t id : keep) { - const Convex_hull_candidate& ci = hulls[id]; - if (CGAL::do_intersect(ci.bbox, cj.bbox)) - todo.emplace_back(Merged_candidate(id, m.ch)); - else { - Merged_candidate merged(id, m.ch); - Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - merged.ch = -1; - merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; - queue.push(std::move(merged)); - } - } - - keep.insert(m.ch); - - tbb::parallel_for_each(todo, do_merge); - for (Merged_candidate& m : todo) - queue.push(std::move(m)); - todo.clear(); - } - - num_hulls = 0; - - for (std::size_t i : keep) - candidates.push_back(std::move(hulls[i])); -#else - CGAL_USE(candidates); - CGAL_USE(hull_volume); - assert(false); -#endif -} - -template -void merge(std::vector>& candidates, const typename GeomTraits::FT& hull_volume, const unsigned int max_convex_hulls, CGAL::Sequential_tag) { - if (candidates.size() <= max_convex_hulls) - return; - - using FT = typename GeomTraits::FT; - using Point_3 = typename GeomTraits::Point_3; - - struct Merged_candidate { - std::size_t ch_a, ch_b; - int ch; - FT volume_error; - - bool operator < (const Merged_candidate& other) const { - return (volume_error > other.volume_error); - } - - Merged_candidate() : ch_a(-1), ch_b(-1) {} - Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} - }; - - std::unordered_map> hulls; - std::size_t num_hulls = candidates.size(); - - std::unordered_set keep; - - for (std::size_t i = 0; i < candidates.size(); i++) { - hulls.emplace(i, std::move(candidates[i])); - keep.insert(i); - } - - candidates.clear(); - candidates.reserve(max_convex_hulls); - - std::priority_queue queue; - - const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { - Convex_hull_candidate& ci = hulls[m.ch_a]; - Convex_hull_candidate& cj = hulls[m.ch_b]; - - m.ch = num_hulls++; - Convex_hull_candidate& ch = hulls[m.ch]; - - ch.bbox = box_union(ci.bbox, cj.bbox); - std::vector pts(ci.points.begin(), ci.points.end()); - pts.reserve(pts.size() + cj.points.size()); - std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); - - ch.volume = volume(ch.points, ch.indices); - - ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; - }; - - for (std::size_t i : keep) { - const Convex_hull_candidate& ci = hulls[i]; - for (std::size_t j : keep) { - if (j <= i) - continue; - const Convex_hull_candidate& cj = hulls[j]; - if (CGAL::do_intersect(ci.bbox, cj.bbox)) { - Merged_candidate m(i, j); - - m.ch = num_hulls++; - Convex_hull_candidate& ch = hulls[m.ch]; - ch.bbox = box_union(ci.bbox, cj.bbox); - std::vector pts(ci.points.begin(), ci.points.end()); - pts.reserve(pts.size() + cj.points.size()); - std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); - - ch.volume = volume(ch.points, ch.indices); - - ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; - queue.push(std::move(m)); - } - else { - Merged_candidate m(i, j); - Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - m.ch = -1; - m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; - queue.push(std::move(m)); - } - } - } - - while (!queue.empty() && keep.size() > max_convex_hulls) { - Merged_candidate m = queue.top(); - queue.pop(); - - auto ch_a = hulls.find(m.ch_a); - if (ch_a == hulls.end()) - continue; - - auto ch_b = hulls.find(m.ch_b); - if (ch_b == hulls.end()) - continue; - - if (m.ch == -1) - do_merge(m); - - keep.erase(m.ch_a); - keep.erase(m.ch_b); - - hulls.erase(ch_a); - hulls.erase(ch_b); - - const Convex_hull_candidate& cj = hulls[m.ch]; - - for (std::size_t id : keep) { - const Convex_hull_candidate& ci = hulls[id]; - if (CGAL::do_intersect(ci.bbox, cj.bbox)) { - Merged_candidate merged(id, m.ch); - - merged.ch = num_hulls++; - Convex_hull_candidate& ch = hulls[merged.ch]; - ch.bbox = box_union(ci.bbox, cj.bbox); - std::vector pts(ci.points.begin(), ci.points.end()); - pts.reserve(pts.size() + cj.points.size()); - std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); - convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); - - ch.volume = volume(ch.points, ch.indices); - - ch.volume_error = merged.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; - queue.push(std::move(merged)); - } - else { - Merged_candidate merged(id, m.ch); - Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - merged.ch = -1; - merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; - queue.push(std::move(merged)); - } - } - - keep.insert(m.ch); - } - - num_hulls = 0; - - for (std::size_t i : keep) - candidates.push_back(std::move(hulls[i])); -} - -} // namespace internal - -/** - * \ingroup PMP_convex_decomposition_grp - * - * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. - * The remaining voxels are labeled as outside or inside by flood fill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, - * y and z-axis in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and - * no face facing towards the voxel. In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied. - * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. - * - * \tparam FaceGraph a model of `HalfedgeListGraph`, and `FaceListGraph` - * - * \tparam OutputIterator must be an output iterator accepting variables of type `std::pair, std::vector > >`. - * - * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" - * - * \param tmesh the input triangle mesh to approximate by convex hulls - * \param out_hulls output iterator into which convex hulls are recorded - * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below - * - * \cgalNamedParamsBegin - * - * \cgalParamNBegin{maximum_number_of_voxels} - * \cgalParamDescription{gives an upper bound of the number of voxels. The longest bounding box side will have a length of the cubic root of `maximum_number_of_voxels` rounded down. Cannot be smaller than 64. } - * \cgalParamType{unsigned int} - * \cgalParamDefault{1,000,000} - * \cgalParamNEnd - * - * \cgalParamNBegin{maximum_depth} - * \cgalParamDescription{maximum depth of hierarchical splits} - * \cgalParamType{unsigned int} - * \cgalParamDefault{10} - * \cgalParamNEnd - * - * \cgalParamNBegin{maximum_number_of_convex_hulls} - * \cgalParamDescription{maximum number of convex hulls produced by the method} - * \cgalParamType{unsigned int} - * \cgalParamDefault{16} - * \cgalParamNEnd - * - * \cgalParamNBegin{volume_error} - * \cgalParamDescription{maximum difference in fraction of volumes of the local convex hull with the sum of voxel volumes. If surpassed, the convex hull will be split if the `maximum_depth` has not been reached yet.} - * \cgalParamType{double} - * \cgalParamDefault{0.01} - * \cgalParamNEnd - * - * \cgalParamNBegin{split_at_concavity} - * \cgalParamDescription{If `true`, the local box of a convex hull is split at the concavity along the longest axis of the bounding box. Otherwise, it is split in the middle of the longest axis, which is faster, but less precise.} - * \cgalParamType{Boolean} - * \cgalParamDefault{true} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_point_map} - * \cgalParamDescription{a property map associating points to the vertices of `mesh`} - * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` - * as key type and `%Point_3` as value type} - * \cgalParamDefault{`boost::get(CGAL::vertex_point, mesh)`} - * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` - * must be available in `FaceGraph`.} - * \cgalParamNEnd - * - * \cgalParamNBegin{concurrency_tag} - * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} - * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} - * \cgalParamDefault{`CGAL::Parallel_if_available_tag`} - * \cgalParamNEnd - * - * \cgalParamNBegin{geom_traits} - * \cgalParamDescription{an instance of a geometric traits class} - * \cgalParamType{a class model of `Kernel`} - * \cgalParamDefault{a \cgal Kernel deduced from the point type of `FaceGraph`, using `CGAL::Kernel_traits`} - * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} - * \cgalParamNEnd - * - * \cgalNamedParamsEnd - * - * \return the number of convex hulls. Note that this value may be lower than the `maximum_number_of_convex_hulls`, for example if the specified `volume_error` is quickly met. - * - * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` - */ -template -std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { - using Geom_traits = typename GetGeomTraits::type; - - using FT = typename Geom_traits::FT; - const unsigned int num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); - using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; - -#ifndef CGAL_LINKED_WITH_TBB - if constexpr (std::is_same_v) { - CGAL_error_msg("CGAL was not compiled with TBB support. Use Sequential_tag instead."); - return 0; - } -#endif - - const unsigned int max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 16); - assert(max_convex_hulls > 0); - - if (max_convex_hulls == 1) { - internal::Convex_hull_candidate ch; - - using parameters::choose_parameter; - using parameters::get_parameter; - using VPM = typename GetVertexPointMap::const_type; - typedef CGAL::Property_map_to_unary_function Vpmap_fct; - - VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, tmesh)); - Vpmap_fct v2p(vpm); - - convex_hull_3(boost::make_transform_iterator(vertices(tmesh).begin(), v2p), boost::make_transform_iterator(vertices(tmesh).end(), v2p), ch.points, ch.indices); - - *out_hulls = std::make_pair(std::move(ch.points), std::move(ch.indices)); - return 1; - } - - Bbox_3 bb = bbox(tmesh); - const auto [grid_size, voxel_size] = internal::calculate_grid_size(bb, num_voxels); - - std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); - - std::vector> candidates(1); - init(candidates[0], tmesh, grid, bb, grid_size, voxel_size, Concurrency_tag()); - - const FT hull_volume = candidates[0].ch.volume; - - recurse(candidates, grid, grid_size, bb, voxel_size, np, Concurrency_tag()); - - std::vector> hulls; - for (internal::Candidate &c : candidates) - hulls.emplace_back(std::move(c.ch)); - - candidates.clear(); - - // merge until target number is reached - merge(hulls, hull_volume, max_convex_hulls, Concurrency_tag()); - - for (std::size_t i = 0; i < hulls.size(); i++) - *out_hulls++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); - - return hulls.size(); -} - -} // namespace Polygon_mesh_processing -} // namespace CGAL - -#endif // CGAL_POLYGON_MESH_PROCESSING_CONVEX_DECOMPOSITION_H diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index 6424c0e4448..f02f1fd4676 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -182,7 +182,7 @@ CGAL_add_named_parameter(snap_grid_size_t, snap_grid_size, snap_grid_size) CGAL_add_named_parameter(maximum_number_of_voxels_t, maximum_number_of_voxels, maximum_number_of_voxels) CGAL_add_named_parameter(maximum_depth_t, maximum_depth, maximum_depth) CGAL_add_named_parameter(volume_error_t, volume_error, volume_error) -CGAL_add_named_parameter(maximum_number_of_convex_hulls_t, maximum_number_of_convex_hulls, maximum_number_of_convex_hulls) +CGAL_add_named_parameter(maximum_number_of_convex_volumes_t, maximum_number_of_convex_volumes, maximum_number_of_convex_volumes) CGAL_add_named_parameter(split_at_concavity_t, split_at_concavity, split_at_concavity) diff --git a/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp b/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp index c7a002ad401..ea2518d1718 100644 --- a/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp +++ b/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp @@ -27,19 +27,19 @@ int main(int argc, char* argv[]) return 1; } - std::vector convex_hulls; - convex_hulls.reserve(9); + std::vector convex_volumes; + convex_volumes.reserve(9); - CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_volumes), CGAL::parameters::maximum_depth(10) .volume_error(0.1) - .maximum_number_of_convex_hulls(9) + .maximum_number_of_convex_volumes(9) .split_at_concavity(true) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); - for (std::size_t i = 0;i>& candidates, const typ * * \cgalNamedParamsEnd * - * \return the number of convex hulls. Note that this value may be lower than the `maximum_number_of_convex_hulls`, for example if the specified `volume_error` is quickly met. + * \return the number of convex hulls. Note that this value may be lower than the `maximum_number_of_convex_volumes`, for example if the specified `volume_error` is quickly met. * * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` */ @@ -1626,7 +1626,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputItera } #endif - const unsigned int max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_hulls), 16); + const unsigned int max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_volumes), 16); assert(max_convex_hulls > 0); if (max_convex_hulls == 1) { diff --git a/Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp index 93100cf5cc5..9bea6abac6e 100644 --- a/Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp +++ b/Surface_mesh_decomposition/test/Surface_mesh_decomposition/test_acd.cpp @@ -23,27 +23,27 @@ int main(int argc, char* argv[]) Mesh mesh; - std::vector convex_hulls; + std::vector convex_volumes; // Try with empty mesh - CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_volumes), CGAL::parameters::maximum_depth(10) .volume_error(0.1) - .maximum_number_of_convex_hulls(9) + .maximum_number_of_convex_volumes(9) .split_at_concavity(true) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); - assert(convex_hulls.size() == 0); + assert(convex_volumes.size() == 0); if (!PMP::IO::read_polygon_mesh(filename, mesh)) { std::cerr << "Invalid input." << std::endl; return 1; } - convex_hulls.reserve(9); + convex_volumes.reserve(9); - CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_hulls), + CGAL::approximate_convex_decomposition(mesh, std::back_inserter(convex_volumes), CGAL::parameters::maximum_depth(10) .volume_error(0.1) .maximum_number_of_convex_hulls(9) @@ -51,8 +51,8 @@ int main(int argc, char* argv[]) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); - assert(convex_hulls.size() > 0); - assert(convex_hulls.size() <= 9); + assert(convex_volumes.size() > 0); + assert(convex_volumes.size() <= 9); return 0; } From f1e16cdd4f7373936bf9821661f2c766472ac818 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 14:02:37 +0100 Subject: [PATCH 34/49] adapting dependencies --- .../Polygon_mesh_processing/dependencies | 2 -- .../Surface_mesh_decomposition/dependencies | 15 ++++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies b/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies index 939741eec28..31ee94b381a 100644 --- a/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies +++ b/Polygon_mesh_processing/package_info/Polygon_mesh_processing/dependencies @@ -6,8 +6,6 @@ Box_intersection_d CGAL_Core Cartesian_kernel Circulator -Convex_hull_2 -Convex_hull_3 Distance_2 Distance_3 Filtered_kernel diff --git a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies index 88ebdd57cc6..652e2ada534 100644 --- a/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies +++ b/Surface_mesh_decomposition/package_info/Surface_mesh_decomposition/dependencies @@ -1,25 +1,30 @@ +AABB_tree Algebraic_foundations Arithmetic_kernel BGL CGAL_Core Cartesian_kernel Circulator -Convex_decomposition_3 +Convex_hull_2 Convex_hull_3 +Distance_2 Distance_3 -HalfedgeDS +Filtered_kernel Hash_map Homogeneous_kernel Installation +Intersections_2 Intersections_3 Interval_support Kernel_23 -Modifier +Kernel_d +Modular_arithmetic Number_types -Polyhedron Polygon_mesh_processing Profiling_tools Property_map +Spatial_searching STL_Extension -Spatial_sorting Stream_support +TDS_2 +Random_numbers From 90bd5de294164e247d2ecb2f784504013ce3a799 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 14:04:53 +0100 Subject: [PATCH 35/49] fixed include --- .../Approximate_convex_decomposition_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp index 6d673295291..6dff06deed5 100644 --- a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp @@ -1,7 +1,7 @@ #include "config.h" #include -#include +#include #include #include "ui_Approximate_convex_decomposition_dialog.h" @@ -114,7 +114,7 @@ approximate_convex_decomposition() std::vector convex_volumes; convex_volumes.reserve(9); - CGAL::Polygon_mesh_processing::approximate_convex_decomposition(*(sm_item->face_graph()), std::back_inserter(convex_volumes), + CGAL::approximate_convex_decomposition(*(sm_item->face_graph()), std::back_inserter(convex_volumes), CGAL::parameters::maximum_depth(maximumDepth) .volume_error(volumeError) .maximum_number_of_convex_volumes(maximumConvexHulls) From 589da39f717bf46781d7673bea43154f4edb1e15 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 14:23:06 +0100 Subject: [PATCH 36/49] fixed namespace --- .../include/CGAL/approximate_convex_decomposition.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h index e9fc596baf2..586cf81c9fb 100644 --- a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h +++ b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h @@ -706,7 +706,7 @@ void rayshooting_fill(std::vector& grid, const Vec3_uint& grid_size, con // A segment intersection is not helpful as it means the triangle normal is orthogonal to the ray if (std::get_if(&(intersection->first))) { face_descriptor fd = intersection->second; - Vector_3 n = compute_face_normal(fd, mesh); + Vector_3 n = Polygon_mesh_processing::compute_face_normal(fd, mesh); if (dirs[i] * n > 0) inside++; else From 2c81547af4ba920fe5bfb085fe509ad849e8b644 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 14:40:40 +0100 Subject: [PATCH 37/49] removed unused variable --- .../include/CGAL/approximate_convex_decomposition.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h index 586cf81c9fb..930e7798b35 100644 --- a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h +++ b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h @@ -769,10 +769,8 @@ struct Candidate { Bbox_uint bbox; Convex_hull_candidate ch; - Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {index = cidx++;} - Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) { index = cidx++; } -private: - inline static std::size_t cidx = 0; + Candidate() : depth(0), bbox({ 0, 0, 0 }, { 0, 0, 0 }) {} + Candidate(std::size_t depth, Bbox_uint &bbox) : depth(depth), bbox(bbox) {} }; template From 9570c993442b0ae77a5b3b4c583cdc8541e6160f Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 15:05:03 +0100 Subject: [PATCH 38/49] removed old references to approximate convex decomposition --- .../doc/Polygon_mesh_processing/PackageDescription.txt | 5 ----- .../doc/Polygon_mesh_processing/examples.txt | 1 - 2 files changed, 6 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 5e6deacbc8d..700ec7fdac0 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -66,10 +66,6 @@ /// Classes and functions that answer queries about a polygon mesh or its elements. /// \ingroup PkgPolygonMeshProcessingRef -/// \defgroup PMP_convex_decomposition_grp Approximate Convex Decomposition -/// Function to approximate a mesh by a number of convex hulls. -/// \ingroup PkgPolygonMeshProcessingRef - /// \defgroup PMP_IO_grp I/O Functions /// \ingroup PkgPolygonMeshProcessingRef @@ -282,7 +278,6 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::detect_corners_of_regions()` - `CGAL::Polygon_mesh_processing::refine_mesh_at_isolevel()` - `CGAL::Polygon_mesh_processing::refine_with_plane()` -- `CGAL::Polygon_mesh_processing::approximate_convex_decomposition()` \cgalCRPSection{I/O Functions} - \link PMP_IO_grp `CGAL::Polygon_mesh_processing::IO::read_polygon_mesh()`\endlink diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index a597fe7b355..4d657bcef5e 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -54,5 +54,4 @@ \example Polygon_mesh_processing/sample_example.cpp \example Polygon_mesh_processing/soup_autorefinement.cpp \example Polygon_mesh_processing/snap_polygon_soup.cpp -\example Polygon_mesh_processing/approximate_convex_decomposition.cpp */ From 3036f8612f37aed85ee0fc87c81f64e51c63e9cb Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 15:18:33 +0100 Subject: [PATCH 39/49] doc fix --- .../Convex_decomposition_3.txt | 4 ++-- .../include/CGAL/approximate_convex_decomposition.h | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index 219f6cab9b6..c71113be3ee 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -20,8 +20,8 @@ While it is desirable to have a decomposition into a minimum number of pieces, this problem is known to be NP-hard \cgalCite{c-cpplb-84}. This package offers two methods for decomposing polyhedra. The \ref Convex_decomposition_3Nef "Convex Decomposition of Nef Polyhedra" -splits polyhedra into convex pieces with an upper bound on the number -of pieces. The \ref Convex_decomposition_3ACD_Intro +splits polyhedra into convex pieces with an upper bound on their number. +The \ref Convex_decomposition_3ACD_Intro "Approximate convex decomposition" method offers a fast approximate decomposition of the convex hull into convex volumes. While any number of convex volumes can be generated, these convex volumes diff --git a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h index 930e7798b35..5b786a69f42 100644 --- a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h +++ b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h @@ -1533,11 +1533,11 @@ void merge(std::vector>& candidates, const typ /** * \ingroup PkgConvexDecomposition3Ref * - * \brief approximates the input mesh by a number of convex hulls. The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. + * \brief approximates the input mesh by a number of convex volumes. The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. * The remaining voxels are labeled as outside or inside by flood fill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, * y and z-axis in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and * no face facing towards the voxel. In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied. - * Afterwards, a greedy pair-wise merging combines smaller convex hulls until the given number of convex hulls is met. + * Afterwards, a greedy pair-wise merging combines smaller convex volumes until the given number of convex volumes is met. * * \tparam FaceGraph a model of `HalfedgeListGraph`, and `FaceListGraph` * @@ -1545,8 +1545,8 @@ void merge(std::vector>& candidates, const typ * * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * - * \param tmesh the input triangle mesh to approximate by convex hulls - * \param out_hulls output iterator into which convex hulls are recorded + * \param tmesh the input triangle mesh to approximate by convex volumes + * \param out_volumes output iterator into which convex volumes are recorded * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin @@ -1610,7 +1610,7 @@ void merge(std::vector>& candidates, const typ * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` */ template -std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputIterator out_hulls, const NamedParameters& np = parameters::default_values()) { +std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputIterator out_volumes, const NamedParameters& np = parameters::default_values()) { using Geom_traits = typename GetGeomTraits::type; using FT = typename Geom_traits::FT; @@ -1666,7 +1666,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputItera merge(hulls, hull_volume, max_convex_hulls, Concurrency_tag()); for (std::size_t i = 0; i < hulls.size(); i++) - *out_hulls++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); + *out_volumes++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); return hulls.size(); } From 973f5676aaa2d5d6d2aaf73ac834619c5558ebf4 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 15:59:04 +0100 Subject: [PATCH 40/49] remove 'we' and 'our' added implementation history --- .../Convex_decomposition_3.txt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index c71113be3ee..d3c6a6506a9 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -30,7 +30,7 @@ empty space than just the input polyhedron. \section Convex_decomposition_3Nef Convex Decomposition of Nef Polyhedra -Our implementation decomposes a Nef polyhedron \f$ N\f$ into \cgalBigO{r^2} convex +The method decomposes a Nef polyhedron \f$ N\f$ into \cgalBigO{r^2} convex pieces, where \f$ r\f$ is the number of edges that have two adjacent facets that span an angle of more than 180 degrees with respect to the interior of the polyhedron. Those edges are also called reflex edges. @@ -41,14 +41,14 @@ optimal \cgalCite{c-cpplb-84}. Vertical decomposition based on the insertion of vertical facets (viewed from the top). Left: Non-convex polyhedron. Middle: Non-vertical reflex edges have been resolved. Right: Vertical reflex edges have been resolved. The sub-volumes are convex. \cgalFigureEnd -Our decomposition runs in two steps. In the first step, each +The decomposition runs in two steps. In the first step, each non-vertical reflex edge \f$ e\f$ is resolved by insertion of vertical -facets through \f$ e\f$. In the second step, we do the same with the -vertical reflex edges. \cgalFigureRef{figverticalDecomposition} +facets through \f$ e\f$. In the second step, the +vertical reflex edges are handled in the same way. \cgalFigureRef{figverticalDecomposition} illustrates the two steps. -At the moment our implementation is restricted to the decomposition of -bounded polyhedra. An extension to unbounded polyhedra is planned. +At the moment the implementation is restricted to the decomposition of +bounded polyhedra. \subsection Convex_decomposition_3InterfaceandUsage Interface and Usage @@ -86,8 +86,8 @@ of accessing the convex pieces is to convert them into separate Nef polyhedra, as illustrated by the example code given below. Note that due to the restriction to bounded polyhedra, the use of -extended kernels is unnecessary and expensive. We therefore do not -support the use of extended kernels in the convex decomposition. +extended kernels is unnecessary and expensive. Therefore the use of +extended kernels in the convex decomposition is not supported. \cgalExample{Convex_decomposition_3/list_of_convex_parts.cpp} @@ -356,6 +356,11 @@ The running time for all cases in the above tables were between 1.4 and 3 second \cgalExample{Surface_mesh_decomposition/approximate_convex_decomposition.cpp } + +\section Convex_decomposition_3_history Design and Implementation History + +This package was created by Peter Hachenberger with the `CGAL::convex_decomposition_3()` method. In 2025, it has been extended by Sven Oesau with the `CGAL::approximate_convex_decomposition()` method. + */ } /* namespace CGAL */ From feb3b766a57eed4660f196f0c0b01c0ae1cf1e7d Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Thu, 20 Nov 2025 16:49:05 +0100 Subject: [PATCH 41/49] doc update bugfix --- .../Convex_decomposition_3.txt | 9 +++++---- .../Convex_decomposition_3/PackageDescription.txt | 2 +- .../doc/Convex_decomposition_3/dependencies | 1 + .../include/CGAL/approximate_convex_decomposition.h | 13 +++++++------ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index d3c6a6506a9..d78c46626a8 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -22,7 +22,7 @@ package offers two methods for decomposing polyhedra. The \ref Convex_decomposition_3Nef "Convex Decomposition of Nef Polyhedra" splits polyhedra into convex pieces with an upper bound on their number. The \ref Convex_decomposition_3ACD_Intro -"Approximate convex decomposition" method offers a fast +"Approximate Convex Decomposition" method offers a fast approximate decomposition of the convex hull into convex volumes. While any number of convex volumes can be generated, these convex volumes are more compact than the convex hull, but still include additional @@ -98,7 +98,7 @@ extended kernels in the convex decomposition is not supported. \cgalFigureCaptionBegin{Acd_topfig} -Approximate convex decomposition of the elephant.off model.\n From left to right: 1. input mesh 2. 5 convex volumes 3. 8 convex volumes 4. 12 convex volumes +Approximate convex decomposition of the elephant.off model.\n From left to right: input mesh, 5, 8, and 12 convex volumes \cgalFigureCaptionEnd The H-VACD method \cgalCite{cgal:mamou2016volumetric}, "Hierarchical volumetrix approximate convex decomposition", computes a set of convex volumes that fit the polyhedron. Contrary to the decomposition of the polyhedron into convex parts, the convex volumes cover the polyhedron, but also include additional volume outside of it. @@ -116,7 +116,7 @@ The algorithm computes a set of convex volumes \f$ C=\{C_i\f$ with \f$ i \in[0.. d(A, B) = |A| - |B| \f} -Where \f$|A|\f$ is the volume of A, P is the input polyhedron and \f$C_i\f$ are convex volumes. The convex volumes \f$C_i\f$ are pairwise disjunct, i.e., \f$|C_i \cap C_j| = 0\f$ if \f$i \neq j\f$. And the union of convex volumes contain the input polyhedron \f$ P \subset \bigcup_{C_i \in C} \f$. +Where \f$|A|\f$ is the volume of A, P is the input polyhedron and \f$C_i\f$ are convex volumes. The convex volumes \f$C_i\f$ are pairwise disjoint, i.e., \f$|C_i \cap C_j| = 0\f$ if \f$i \neq j\f$. And the union of convex volumes contain the input polyhedron \f$ P \subset \bigcup_{C_i \in C} \f$. \cgalFigureAnchor{Acd_pipelinefig}
@@ -127,7 +127,8 @@ Approximate convex decomposition pipeline.\n From left to right: 1. input mesh 2 \cgalFigureCaptionEnd The method employs a top-down splitting phase followed by a bottom-up merging to achieve the target number of convex volumes. The splitting phase aims at decomposing the input mesh into smaller mostly convex parts. Each part of the input mesh is approximated with its convex hull. In a hierarchical manner, each part of the mesh is split into two parts when its convexity is low. The convexity is measured by the volume difference of the part and its convex hull. Splitting a part into two can be done by simply cutting the longest side of the bounding box in half. A better choice is often found by searching the longest side of the bounding box for a concave spot. However, it comes with a higher computational cost. The hierarchical splitting stops when either the convexity is sufficiently high or the maximum depth is reached. -The volume calculation, convex hull computation and the concavity search is accelerated by a voxel grid. The grid is prepared before the splitting phase and is labelled into outside, inside or surface. The convex hulls are calculated from voxel corners. Thus, a mesh with a high resolution is less penalized by its number of vertices. +The volume calculation, convex hull computation and the concavity search is accelerated by a voxel grid. The grid is prepared before the splitting phase and voxel cells overlapping with triangles are labeled as surface. The remaining voxels are labeled as outside or inside by flood fill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, y and z-axes in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and +no face facing towards the voxel. The convex hulls are calculated from voxel corners. Thus, a mesh with a high resolution is less penalized by its number of vertices. The splitting phase typically results in a number of convex volumes larger than targeted. The second phase employs a bottom-up merging that reduces the number of convex volumes to the targeted number while aiming at maintaining a low volume difference between convex volumes and the input mesh. The greedy merging maintains a priority queue to incrementally merge the pair of convex volumes with the smallest increase of volume difference. The splitting phase is not limited by the chosen `maximum_number_of_convex_volumes`, because a splitting into a larger number of more convex parts with a subsequent merging leads to better results. diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt index 4db19541a8e..308d8ecbf33 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt @@ -22,7 +22,7 @@ The `CGAL::approximate_convex_decomposition()` instead splits the convex hull of \cgalCRPSection{Functions} - `CGAL::convex_decomposition_3(Nef_polyhedron_3& N)` -- `CGAL::approximate_convex_decomposition()` +- `CGAL::approximate_convex_decomposition(FaceGraph)` */ diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies b/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies index 6d540982926..9f1e130e7fe 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/dependencies @@ -6,3 +6,4 @@ Circulator Stream_support Nef_3 BGL +Polygon_mesh_processing diff --git a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h index 5b786a69f42..b00b662beba 100644 --- a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h +++ b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h @@ -1533,11 +1533,10 @@ void merge(std::vector>& candidates, const typ /** * \ingroup PkgConvexDecomposition3Ref * - * \brief approximates the input mesh by a number of convex volumes. The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. - * The remaining voxels are labeled as outside or inside by flood fill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, - * y and z-axis in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and - * no face facing towards the voxel. In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied. - * Afterwards, a greedy pair-wise merging combines smaller convex volumes until the given number of convex volumes is met. + * \brief approximates the input mesh by a number of convex volumes. + * The input mesh is voxelized and the voxels intersecting with the mesh are labeled as surface. The remaining voxels are labeled as outside or inside. + * In a next step, the convex hull of the mesh is hierarchically split until the `volume_error` threshold is satisfied or the `maximum_depth` is reached. + * Finally, a greedy pair-wise merging combines smaller convex volumes until `maximum_number_of_convex_volumes` is met. * * \tparam FaceGraph a model of `HalfedgeListGraph`, and `FaceListGraph` * @@ -1607,6 +1606,8 @@ void merge(std::vector>& candidates, const typ * * \return the number of convex hulls. Note that this value may be lower than the `maximum_number_of_convex_volumes`, for example if the specified `volume_error` is quickly met. * + * \pre `tmesh` is a triangle mesh. + * \pre `tmesh` is not self-intersecting. * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` */ template @@ -1640,7 +1641,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputItera convex_hull_3(boost::make_transform_iterator(vertices(tmesh).begin(), v2p), boost::make_transform_iterator(vertices(tmesh).end(), v2p), ch.points, ch.indices); - *out_hulls = std::make_pair(std::move(ch.points), std::move(ch.indices)); + *out_volumes = std::make_pair(std::move(ch.points), std::move(ch.indices)); return 1; } From 128bc81ec1c83d9563074c26eccc9bfa510c6977 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Fri, 21 Nov 2025 14:00:25 +0100 Subject: [PATCH 42/49] adding text to examples --- .../doc/Convex_decomposition_3/Convex_decomposition_3.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index d78c46626a8..ed460cb40f2 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -89,6 +89,10 @@ Note that due to the restriction to bounded polyhedra, the use of extended kernels is unnecessary and expensive. Therefore the use of extended kernels in the convex decomposition is not supported. +\subsection Convex_decomposition_3Example Example + +This example shows the usage of `CGAL::convex_decomposition_3(Nef_polyhedron_3&)` to decompose a Nef polyhedron into convex parts. + \cgalExample{Convex_decomposition_3/list_of_convex_parts.cpp} \section Convex_decomposition_3ACD_Intro Approximate Convex Decomposition @@ -355,6 +359,8 @@ The running time for all cases in the above tables were between 1.4 and 3 second \subsection Convex_decomposition_3ACD_Example Example +The example shows the approximate convex decomposition of the knot2.off mesh into 9 convex volumes that are saved as off files. + \cgalExample{Surface_mesh_decomposition/approximate_convex_decomposition.cpp } From 2226dda58f5c3a84dabcc010098773cfee178a70 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Fri, 21 Nov 2025 14:19:01 +0100 Subject: [PATCH 43/49] corrected numbers in table --- .../Convex_decomposition_3/Convex_decomposition_3.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index ed460cb40f2..51c8e1eb410 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -254,7 +254,7 @@ Concavity 0.9731 -0.84613 +0.8461 0.7506 @@ -326,7 +326,7 @@ Mid 1.2121 -1.00867 +1.0087 0.8444 @@ -341,15 +341,15 @@ Triceratops Mid -1.2191 +0.8358 0.6870 -0.8463 +0.7071 0.7375 -0.6871 +0.6586
From aa374ddbe3c5bc9968ed9116e68e7fe2581cd622 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Fri, 21 Nov 2025 14:30:12 +0100 Subject: [PATCH 44/49] adapted named parameter doc [skip ci] --- .../include/CGAL/approximate_convex_decomposition.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h index b00b662beba..df810f94104 100644 --- a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h +++ b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h @@ -1581,10 +1581,10 @@ void merge(std::vector>& candidates, const typ * \cgalParamNEnd * * \cgalParamNBegin{vertex_point_map} - * \cgalParamDescription{a property map associating points to the vertices of `mesh`} + * \cgalParamDescription{a property map associating points to the vertices of `tmesh`} * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} - * \cgalParamDefault{`boost::get(CGAL::vertex_point, mesh)`} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, tmesh)`} * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` * must be available in `FaceGraph`.} * \cgalParamNEnd @@ -1598,7 +1598,7 @@ void merge(std::vector>& candidates, const typ * \cgalParamNBegin{geom_traits} * \cgalParamDescription{an instance of a geometric traits class} * \cgalParamType{a class model of `Kernel`} - * \cgalParamDefault{a \cgal Kernel deduced from the point type of `FaceGraph`, using `CGAL::Kernel_traits`} + * \cgalParamDefault{a \cgal Kernel deduced from the value type of the vertex-point map, using `CGAL::Kernel_traits`} * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} * \cgalParamNEnd * From 346f530d0736ee8287d0ab3c57d8ca90d012dbc3 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Fri, 21 Nov 2025 14:54:11 +0100 Subject: [PATCH 45/49] moving example from Surface_mesh_decomposition into Convex_decomposition_3 --- .../Convex_decomposition_3.txt | 4 ++-- .../doc/Convex_decomposition_3/Doxyfile.in | 2 -- .../PackageDescription.txt | 2 +- .../doc/Convex_decomposition_3/examples.txt | 2 +- .../Convex_decomposition_3/CMakeLists.txt | 9 +++++++++ .../approximate_convex_decomposition.cpp | 0 .../Surface_mesh_decomposition/CMakeLists.txt | 19 ------------------- 7 files changed, 13 insertions(+), 25 deletions(-) rename {Surface_mesh_decomposition/examples/Surface_mesh_decomposition => Convex_decomposition_3/examples/Convex_decomposition_3}/approximate_convex_decomposition.cpp (100%) delete mode 100644 Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index 51c8e1eb410..ea5b8e8d42b 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -6,7 +6,7 @@ namespace CGAL { \anchor Chapter_Convex_Decomposition_of_Polyhedra \anchor chapterConvexDecomposition3 \cgalAutoToc -\author Peter Hachenberger +\author Peter Hachenberger, Sven Oesau \section Convex_decomposition_3Introduction Introduction @@ -361,7 +361,7 @@ The running time for all cases in the above tables were between 1.4 and 3 second The example shows the approximate convex decomposition of the knot2.off mesh into 9 convex volumes that are saved as off files. -\cgalExample{Surface_mesh_decomposition/approximate_convex_decomposition.cpp } +\cgalExample{Convex_decomposition_3/approximate_convex_decomposition.cpp } \section Convex_decomposition_3_history Design and Implementation History diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in b/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in index ffc85d262bd..143c35c2ff6 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Doxyfile.in @@ -5,5 +5,3 @@ EXTRACT_ALL = false HIDE_UNDOC_CLASSES = true INPUT += ${CGAL_Surface_mesh_decomposition_INCLUDE_DIR}/CGAL/approximate_convex_decomposition.h - -EXAMPLE_PATH += ${CGAL_Surface_mesh_decomposition_EXAMPLE_DIR} \ No newline at end of file diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt index 308d8ecbf33..6252de7426c 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt @@ -21,8 +21,8 @@ The `CGAL::approximate_convex_decomposition()` instead splits the convex hull of \cgalClassifedRefPages \cgalCRPSection{Functions} -- `CGAL::convex_decomposition_3(Nef_polyhedron_3& N)` - `CGAL::approximate_convex_decomposition(FaceGraph)` +- `CGAL::convex_decomposition_3(Nef_polyhedron_3)` */ diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt index ab8e7489906..3c4f462dfed 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/examples.txt @@ -1,4 +1,4 @@ /*! +\example Convex_decomposition_3/approximate_convex_decomposition.cpp \example Convex_decomposition_3/list_of_convex_parts.cpp -\example Surface_mesh_decomposition/approximate_convex_decomposition.cpp */ diff --git a/Convex_decomposition_3/examples/Convex_decomposition_3/CMakeLists.txt b/Convex_decomposition_3/examples/Convex_decomposition_3/CMakeLists.txt index 3e4532f6ffc..04bc377f7cb 100644 --- a/Convex_decomposition_3/examples/Convex_decomposition_3/CMakeLists.txt +++ b/Convex_decomposition_3/examples/Convex_decomposition_3/CMakeLists.txt @@ -14,3 +14,12 @@ file( foreach(cppfile ${cppfiles}) create_single_source_cgal_program("${cppfile}") endforeach() + +find_package(TBB QUIET) +include(CGAL_TBB_support) + +if(TARGET CGAL::TBB_support) + target_link_libraries(approximate_convex_decomposition PRIVATE CGAL::TBB_support) +else() + message(STATUS "NOTICE: Intel TBB was not found. Sequential code will be used.") +endif() diff --git a/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp b/Convex_decomposition_3/examples/Convex_decomposition_3/approximate_convex_decomposition.cpp similarity index 100% rename from Surface_mesh_decomposition/examples/Surface_mesh_decomposition/approximate_convex_decomposition.cpp rename to Convex_decomposition_3/examples/Convex_decomposition_3/approximate_convex_decomposition.cpp diff --git a/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt b/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt deleted file mode 100644 index 5e6d6cd5d2c..00000000000 --- a/Surface_mesh_decomposition/examples/Surface_mesh_decomposition/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Created by the script cgal_create_CMakeLists -# This is the CMake script for compiling a set of CGAL applications. - -cmake_minimum_required(VERSION 3.12...3.31) -project(Surface_mesh_decomposition_Examples) - -# CGAL and its components -find_package(CGAL REQUIRED) - -find_package(TBB QUIET) -include(CGAL_TBB_support) - -create_single_source_cgal_program("approximate_convex_decomposition.cpp") - -if(TARGET CGAL::TBB_support) - target_link_libraries(approximate_convex_decomposition PRIVATE CGAL::TBB_support) -else() - message(STATUS "NOTICE: Intel TBB was not found. Sequential code will be used.") -endif() From 2f1435c8380f9a4942d5d3fba87e7a3eaa199da8 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Fri, 21 Nov 2025 14:30:36 +0000 Subject: [PATCH 46/49] Improve documentation of Nef --- Nef_3/doc/Nef_3/CGAL/Nef_nary_union_3.h | 2 +- Nef_3/doc/Nef_3/CGAL/Nef_polyhedron_3.h | 10 ++++----- Nef_3/doc/Nef_3/CGAL/OFF_to_nef_3.h | 8 ++++--- .../convert_nef_polyhedron_to_polygon_mesh.h | 22 +++++++++---------- Nef_3/doc/Nef_3/dependencies | 1 + Nef_S2/doc/Nef_S2/CGAL/Nef_polyhedron_S2.h | 10 ++++----- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Nef_3/doc/Nef_3/CGAL/Nef_nary_union_3.h b/Nef_3/doc/Nef_3/CGAL/Nef_nary_union_3.h index 3919d156f76..013846df886 100644 --- a/Nef_3/doc/Nef_3/CGAL/Nef_nary_union_3.h +++ b/Nef_3/doc/Nef_3/CGAL/Nef_nary_union_3.h @@ -20,7 +20,7 @@ polyhedra in a sorted way and add them one by one to `Nef_nary_union_3`. */ -template< typename Nef_polyhedron_3 > +template< typename NefPolyhedron_3 > class Nef_nary_union_3 { public: diff --git a/Nef_3/doc/Nef_3/CGAL/Nef_polyhedron_3.h b/Nef_3/doc/Nef_3/CGAL/Nef_polyhedron_3.h index 06f67260279..550f2c9c719 100644 --- a/Nef_3/doc/Nef_3/CGAL/Nef_polyhedron_3.h +++ b/Nef_3/doc/Nef_3/CGAL/Nef_polyhedron_3.h @@ -34,7 +34,7 @@ namespace CGAL { `Quotient` or any other number type modeling \f$\mathbb{Q}\f$. The second parameter and the third parameter are for future considerations. - Neither `Nef_polyhedronItems_3` nor `Nef_polyhedronMarks` is + Neither `NefPolyhedronItems_3` nor `Nef_polyhedronMarks` is specified, yet. Do not use any other than the default types for these two template parameters. @@ -49,8 +49,8 @@ namespace CGAL { \sa `CGAL::Polyhedron_3` */ -template< class Nef_polyhedronTraits_3, - class Nef_polyhedronItems_3 = CGAL::Default_items +template< class NefPolyhedronTraits_3, + class NefPolyhedronItems_3 = CGAL::Default_items class Nef_polyhedronMarks = bool > class Nef_polyhedron_3 { public: @@ -79,7 +79,7 @@ public: function `twin()` returns the opposite halfedge. Looking at the incidence structure on a sphere map, the member function - `out_sedge` returns the first outgoing shalfedge, and `incident_sface` + `out_sedge()` returns the first outgoing shalfedge, and `incident_sface()` returns the incident sface. \cgalHeading{Creation} @@ -946,7 +946,7 @@ public: }; /* end Volume */ /*! - traits class selected for `Nef_polyhedronTraits_3`. + traits class selected for `NefPolyhedronTraits_3`. */ typedef unspecified_type Traits; diff --git a/Nef_3/doc/Nef_3/CGAL/OFF_to_nef_3.h b/Nef_3/doc/Nef_3/CGAL/OFF_to_nef_3.h index ee4d429515b..5f1f5c2bf21 100644 --- a/Nef_3/doc/Nef_3/CGAL/OFF_to_nef_3.h +++ b/Nef_3/doc/Nef_3/CGAL/OFF_to_nef_3.h @@ -4,7 +4,7 @@ namespace CGAL { \ingroup PkgNef3IOFunctions This function creates a 3D Nef polyhedron from an OFF file which -is read from input stream `in`. The purpose of `OFF_to_nef_3` +is read from input stream `in`. The purpose of `OFF_to_nef_3()` is to create a Nef polyhedron from an OFF file that cannot be handled by the `Nef_polyhedron_3` constructors. It handles double coordinates while using a homogeneous kernel, non-coplanar facets, @@ -12,10 +12,12 @@ surfaces with boundaries, self-intersecting surfaces, and single facets. Every closed volume gets marked. The function returns the number of facets it could not handle. +@tparam NefPolyhedron_3 an object of type `Nef_polyhedron_3`. + \sa `CGAL::Nef_polyhedron_3` */ -template -std::size_t OFF_to_nef_3(std::istream& in, Nef_polyhedron_3& N); +template +std::size_t OFF_to_nef_3(std::istream& in, NefPolyhedron_3& N); } /* namespace CGAL */ diff --git a/Nef_3/doc/Nef_3/CGAL/boost/graph/convert_nef_polyhedron_to_polygon_mesh.h b/Nef_3/doc/Nef_3/CGAL/boost/graph/convert_nef_polyhedron_to_polygon_mesh.h index 558c6321280..712c287c0a4 100644 --- a/Nef_3/doc/Nef_3/CGAL/boost/graph/convert_nef_polyhedron_to_polygon_mesh.h +++ b/Nef_3/doc/Nef_3/CGAL/boost/graph/convert_nef_polyhedron_to_polygon_mesh.h @@ -1,12 +1,12 @@ namespace CGAL { /// \ingroup PkgNef3IOFunctions -/// Converts an object of type `Nef_polyhedron_3` into a polygon mesh model of `MutableFaceGraph`. +/// Converts an object of type `NefPolyhedron_3` into a polygon mesh model of `MutableFaceGraph`. /// Note that contrary to `Nef_polyhedron_3::convert_to_polyhedron()`, the output is not triangulated /// (but faces with more than one connected component of the boundary). -/// The polygon mesh can be triangulated by setting `triangulate_all_faces` to `true` or by calling the function `triangulate_faces()`. -/// @tparam Nef_polyhedron an object of type `Nef_polyhedron_3`. -/// @tparam Polygon_mesh a model of `MutableFaceGraph` with an internal property map for `CGAL::vertex_point_t`. +/// The polygon mesh can be triangulated by setting `triangulate_all_faces` to `true` or by calling the function `Polygon_mesh_processing::triangulate_faces()`. +/// @tparam NefPolyhedron_3 an object of type `Nef_polyhedron_3`. +/// @tparam PolygonMesh a model of `MutableFaceGraph` with an internal property map for `CGAL::vertex_point_t`. /// /// The points from `nef` to `pm` are converted using /// `CGAL::Cartesian_converter`. @@ -17,16 +17,16 @@ namespace CGAL { /// @param pm the output. /// @param triangulate_all_faces indicates whether all the faces must be triangulated. /// -/// \pre `Polygon_mesh` must have an internal point property map with value type being `Nef_polyhedron_3::Point_3`. +/// \pre `PolygonMesh` must have an internal point property map with value type being `NefPolyhedron_3::Point_3`. /// \pre `nef.simple()` - template - void convert_nef_polyhedron_to_polygon_mesh(const Nef_polyhedron& nef, Polygon_mesh& pm, bool triangulate_all_faces = false); + template + void convert_nef_polyhedron_to_polygon_mesh(const NefPolyhedron_3& nef, PolygonMesh& pm, bool triangulate_all_faces = false); /// \ingroup PkgNef3IOFunctions - /// Converts an object of type `Nef_polyhedron_3` into a polygon soup. + /// Converts an object of type `NefPolyhedron_3` into a polygon soup. /// The polygons can be triangulated by setting `triangulate_all_faces` to `true`. - /// @tparam Nef_polyhedron an object of type `Nef_polyhedron_3`. + /// @tparam NefPolyhedron_3 an object of type `Nef_polyhedron_3`. /// @tparam PointRange a model of the concept `BackInsertionSequence` /// whose `value_type` is the point type /// @tparam PolygonRange a model of the concept @@ -43,8 +43,8 @@ namespace CGAL { /// @param points the output points of the soup /// @param polygons the output polygons of the soup. /// @param triangulate_all_faces indicates whether all polygons must be triangulated. - template - void convert_nef_polyhedron_to_polygon_soup(const Nef_polyhedron& nef, + template + void convert_nef_polyhedron_to_polygon_soup(const NefPolyhedron_3& nef, PointRange& points, PolygonRange& polygons, bool triangulate_all_faces = false); diff --git a/Nef_3/doc/Nef_3/dependencies b/Nef_3/doc/Nef_3/dependencies index 467cacb769e..0ec6513d194 100644 --- a/Nef_3/doc/Nef_3/dependencies +++ b/Nef_3/doc/Nef_3/dependencies @@ -12,3 +12,4 @@ BGL Surface_mesh Polygon_mesh_processing GraphicsView +Basic_viewer diff --git a/Nef_S2/doc/Nef_S2/CGAL/Nef_polyhedron_S2.h b/Nef_S2/doc/Nef_S2/CGAL/Nef_polyhedron_S2.h index a8454f79d2e..0adcf175e01 100644 --- a/Nef_S2/doc/Nef_S2/CGAL/Nef_polyhedron_S2.h +++ b/Nef_S2/doc/Nef_S2/CGAL/Nef_polyhedron_S2.h @@ -16,8 +16,8 @@ under the topological operations `boundary`, `closure`, and \cgalHeading{Parameters} \code -template< class Nef_polyhedronTraits_S2, - class Nef_polyhedronItems_S2 = CGAL::SM_items, +template< class NefPolyhedronTraits_S2, + class NefPolyhedronItems_S2 = CGAL::SM_items, class Nef_polyhedronMarks = bool > class Nef_polyhedron_S2; \endcode @@ -30,7 +30,7 @@ modeling \f$\mathbb{Z}\f$, or `Cartesian`, `Simple_cartesian` parametrized with type modeling \f$\mathbb{Q}\f$. The second parameter and the third parameter are for future considerations. -Neither `Nef_polyhedronItems_S2` nor `Nef_polyhedronMarks` is +Neither `NefPolyhedronItems_S2` nor `Nef_polyhedronMarks` is specified, yet. Do not use other than the default types for these two template parameters. @@ -55,8 +55,8 @@ output operator is defined in the file Nef polyhedra are implemented on top of a halfedge data structure and use linear space in the number of vertices, edges and facets. Operations like `empty` take constant time. The operations -`clear`, `complement`, `interior`, `closure`, -`boundary`, `regularization`, input and output take linear +`clear()`, `complement()`, `interior()`, `closure()`, +`boundary()`, `regularization()`, input and output take linear time. All binary set operations and comparison operations take time \cgalBigO{n \log n} where \f$ n\f$ is the size of the output plus the size of the input. From 927d1afb32eca1d3d517c691d469893f1439af82 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Fri, 21 Nov 2025 14:39:25 +0000 Subject: [PATCH 47/49] Improve documentation of Nef --- .../include/CGAL/convex_decomposition_3.h | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Convex_decomposition_3/include/CGAL/convex_decomposition_3.h b/Convex_decomposition_3/include/CGAL/convex_decomposition_3.h index 08d6750b7fb..e3081d147fa 100644 --- a/Convex_decomposition_3/include/CGAL/convex_decomposition_3.h +++ b/Convex_decomposition_3/include/CGAL/convex_decomposition_3.h @@ -38,7 +38,7 @@ namespace CGAL { \ingroup PkgConvexDecomposition3Ref The function `convex_decomposition_3()` inserts additional facets -into the given `Nef_polyhedron_3` `N`, such that each bounded +into the given Nef polyhedron` `N`, such that each bounded marked volume (the outer volume is unbounded) is subdivided into convex pieces. The modified polyhedron represents a decomposition into \cgalBigO{r^2} convex pieces, where \f$ r\f$ is the number of edges that have two @@ -47,10 +47,12 @@ respect to the interior of the polyhedron. The worst-case running time of our implementation is \cgalBigO{n^2r^4\sqrt[3]{nr^2}\log{(nr)}}, where \f$ n\f$ is the complexity of -the polyhedron (the complexity of a `Nef_polyhedron_3` is the sum +the polyhedron (the complexity of a Nef polyhedron is the sum of its `Vertices`, `Halfedges` and `SHalfedges`) and \f$ r\f$ is the number of reflex edges. +@tparam NefPolyhedron_3 an object of type `Nef_polyhedron_3`. + \pre The polyhedron `N` is bounded. Otherwise, the outer volume is ignored. \post If the polyhedron `N` is non-convex, it is modified to represent the @@ -59,22 +61,22 @@ convex decomposition. If `N` is convex, it is not modified. \sa `CGAL::Nef_polyhedron_3` */ -template -void convex_decomposition_3(Nef_polyhedron& N) +template +void convex_decomposition_3(NefPolyhedron_3& N) { - typedef typename Nef_polyhedron::SNC_structure SNC_structure; + typedef typename NefPolyhedron_3::SNC_structure SNC_structure; typedef typename SNC_structure::Halfedge_handle Halfedge_handle; - typedef typename Nef_polyhedron::Point_3 Point_3; - typedef typename Nef_polyhedron::Vector_3 Vector_3; - typedef typename Nef_polyhedron::Sphere_point Sphere_point; - typedef typename Nef_polyhedron::FT FT; + typedef typename NefPolyhedron_3::Point_3 Point_3; + typedef typename NefPolyhedron_3::Vector_3 Vector_3; + typedef typename NefPolyhedron_3::Sphere_point Sphere_point; + typedef typename NefPolyhedron_3::FT FT; - typedef typename CGAL::Single_wall_creator Single_wall; - typedef typename CGAL::YVertical_wall_builder YVertical_wall_builder; - typedef typename CGAL::Reflex_vertex_searcher Reflex_vertex_searcher; - typedef typename CGAL::Ray_hit_generator2 Ray_hit2; - typedef typename CGAL::External_structure_builder External_structure_builder; - typedef typename CGAL::SFace_separator SFace_separator; + typedef typename CGAL::Single_wall_creator Single_wall; + typedef typename CGAL::YVertical_wall_builder YVertical_wall_builder; + typedef typename CGAL::Reflex_vertex_searcher Reflex_vertex_searcher; + typedef typename CGAL::Ray_hit_generator2 Ray_hit2; + typedef typename CGAL::External_structure_builder External_structure_builder; + typedef typename CGAL::SFace_separator SFace_separator; typedef Compare_halfedges_in_reflex_edge_sorter > Less_edges; @@ -86,11 +88,11 @@ void convex_decomposition_3(Nef_polyhedron& N) typedef typename Positively_sorted_set::const_iterator Positive_reflex_edge_iterator; typedef typename Negatively_sorted_set::const_iterator Negative_reflex_edge_iterator; - typedef typename CGAL::Reflex_edge_searcher + typedef typename CGAL::Reflex_edge_searcher Reflex_edge_searcher; - typedef typename CGAL::Edge_sorter, Negatively_sorted_set> Edge_sorter; - typedef typename CGAL::Edge_sorter, Positively_sorted_set> Edge_sorter2; + typedef typename CGAL::Edge_sorter, Negatively_sorted_set> Edge_sorter; + typedef typename CGAL::Edge_sorter, Positively_sorted_set> Edge_sorter2; External_structure_builder esb; SFace_separator sf_sep; @@ -176,10 +178,10 @@ void convex_decomposition_3(Nef_polyhedron& N) N.delegate(esb); - CGAL_assertion_code(typename Nef_polyhedron::SHalfedge_const_iterator cse); + CGAL_assertion_code(typename NefPolyhedron_3::SHalfedge_const_iterator cse); CGAL_assertion_code(CGAL_forall_shalfedges(cse, N) if(cse->incident_sface()->mark()) - CGAL_assertion(!CGAL::is_reflex_sedge_in_any_direction(cse))); + CGAL_assertion(!CGAL::is_reflex_sedge_in_any_direction(cse))); } } //namespace CGAL From 2efeaba656380de189ee0842f2282008c67de630 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Fri, 21 Nov 2025 14:48:20 +0000 Subject: [PATCH 48/49] Improve documentation of Nef --- .../doc/Convex_decomposition_3/PackageDescription.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt index 6252de7426c..b3cd73b80fa 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/PackageDescription.txt @@ -22,7 +22,7 @@ The `CGAL::approximate_convex_decomposition()` instead splits the convex hull of \cgalCRPSection{Functions} - `CGAL::approximate_convex_decomposition(FaceGraph)` -- `CGAL::convex_decomposition_3(Nef_polyhedron_3)` +- `CGAL::convex_decomposition_3(NefPolyhedron_3)` */ From bf47181a9072bcd049949b77d8f6fd9996612a57 Mon Sep 17 00:00:00 2001 From: Sven Oesau Date: Mon, 1 Dec 2025 15:28:39 +0100 Subject: [PATCH 49/49] added refitting --- .../Convex_decomposition_3.txt | 222 ++-------- .../approximate_convex_decomposition.cpp | 1 + ...Approximate_convex_decomposition_dialog.ui | 123 +++--- ...pproximate_convex_decomposition_plugin.cpp | 34 +- .../Convex_decomposition/CMakeLists.txt | 6 + .../internal/parameters_interface.h | 1 + .../CGAL/approximate_convex_decomposition.h | 381 +++++++++++++++--- 7 files changed, 454 insertions(+), 314 deletions(-) diff --git a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt index ea5b8e8d42b..16e859e049d 100644 --- a/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt +++ b/Convex_decomposition_3/doc/Convex_decomposition_3/Convex_decomposition_3.txt @@ -102,7 +102,7 @@ This example shows the usage of `CGAL::convex_decomposition_3(Nef_polyhedron_3&)
\cgalFigureCaptionBegin{Acd_topfig} -Approximate convex decomposition of the elephant.off model.\n From left to right: input mesh, 5, 8, and 12 convex volumes +Approximate convex decomposition of the elephant.off model.\n From left to right: input mesh, 6, 9, and 12 convex volumes \cgalFigureCaptionEnd The H-VACD method \cgalCite{cgal:mamou2016volumetric}, "Hierarchical volumetrix approximate convex decomposition", computes a set of convex volumes that fit the polyhedron. Contrary to the decomposition of the polyhedron into convex parts, the convex volumes cover the polyhedron, but also include additional volume outside of it. @@ -120,7 +120,7 @@ The algorithm computes a set of convex volumes \f$ C=\{C_i\f$ with \f$ i \in[0.. d(A, B) = |A| - |B| \f} -Where \f$|A|\f$ is the volume of A, P is the input polyhedron and \f$C_i\f$ are convex volumes. The convex volumes \f$C_i\f$ are pairwise disjoint, i.e., \f$|C_i \cap C_j| = 0\f$ if \f$i \neq j\f$. And the union of convex volumes contain the input polyhedron \f$ P \subset \bigcup_{C_i \in C} \f$. +Where \f$|A|\f$ is the volume of A, P is the input polyhedron and \f$C_i\f$ are convex volumes. The convex volumes \f$C_i\f$ are may slightly overlap and their union contain the input polyhedron \f$ P \subset \bigcup_{C_i \in C} \f$. \cgalFigureAnchor{Acd_pipelinefig}
@@ -133,7 +133,9 @@ Approximate convex decomposition pipeline.\n From left to right: 1. input mesh 2 The method employs a top-down splitting phase followed by a bottom-up merging to achieve the target number of convex volumes. The splitting phase aims at decomposing the input mesh into smaller mostly convex parts. Each part of the input mesh is approximated with its convex hull. In a hierarchical manner, each part of the mesh is split into two parts when its convexity is low. The convexity is measured by the volume difference of the part and its convex hull. Splitting a part into two can be done by simply cutting the longest side of the bounding box in half. A better choice is often found by searching the longest side of the bounding box for a concave spot. However, it comes with a higher computational cost. The hierarchical splitting stops when either the convexity is sufficiently high or the maximum depth is reached. The volume calculation, convex hull computation and the concavity search is accelerated by a voxel grid. The grid is prepared before the splitting phase and voxel cells overlapping with triangles are labeled as surface. The remaining voxels are labeled as outside or inside by flood fill, in case the input mesh is closed, or by axis-aligned ray shooting, i.e., along x, y and z-axes in positive and negative directions. A voxel is only labeled as inside if at least 3 faces facing away from the voxel have been hit and no face facing towards the voxel. The convex hulls are calculated from voxel corners. Thus, a mesh with a high resolution is less penalized by its number of vertices. -The splitting phase typically results in a number of convex volumes larger than targeted. The second phase employs a bottom-up merging that reduces the number of convex volumes to the targeted number while aiming at maintaining a low volume difference between convex volumes and the input mesh. The greedy merging maintains a priority queue to incrementally merge the pair of convex volumes with the smallest increase of volume difference. +The splitting phase typically results in a number of convex volumes larger than targeted. + +To optionally improve the fit of the convex volumes, they can be refitted to the mesh before starting the second phase. The second phase employs a bottom-up merging that reduces the number of convex volumes to the targeted number while aiming at maintaining a low volume difference between convex volumes and the input mesh. The greedy merging maintains a priority queue to incrementally merge the pair of convex volumes with the smallest increase of volume difference. The splitting phase is not limited by the chosen `maximum_number_of_convex_volumes`, because a splitting into a larger number of more convex parts with a subsequent merging leads to better results. @@ -142,6 +144,7 @@ The splitting phase is not limited by the chosen `maximum_number_of_convex_volum Several parameters of the algorithm impact the quality of the result as well as the running time. - `maximum_number_of_convex_volumes`: The maximum number of convex volumes output by the method. The actual number may be lower for mostly convex input meshes, e.g., a sphere. The impact on the running time is rather low. The default is 16. - `maximum_depth`: The maximum depth for the hierarchical splitting phase. For complex meshes, a higher maximum depth is required to split small concavities into convex parts. The choice of `maximum_depth` has a larger impact on the running time. The default is 10. +- `refitting`: The convex hulls can be refitted after the splitting phase to more tightly enclose the input mesh. It increases the running time, but significantly reduces the overhead volume included by the computed convex volulmes. It is enabled by default. - `maximum_number_of_voxels`: This parameter controls the resolution of the voxel grid used for speed-up. Larger numbers result in a higher memory footprint and a higher running time. A small number also limits the `maximum_depth`. The voxel grid is isotropic and the longest axis of the bounding box will be split into a number of voxels equal to the cubic root of `maximum_number_of_voxels`. The default value is 1.000.000. - `volume_error`: The splitting of a convex volume into smaller parts is controlled by the `volume_error` which provides the tolerance for difference in volume. The difference is calculated by \f$ (|C_i| - |P_i|) / |P_i|\f$. The default value is 0.01. Thus, if a convex volume has 1 percent more volume that the part of the input mesh it approximates, it will be further divided. - `split_at_concavity`: The splitting can be either performed after searching a concavity on the longest side of the bounding box or simply by splitting the longest side of the bounding box in half. The default value is true, i.e., splitting at the concavity. @@ -151,209 +154,40 @@ Several parameters of the algorithm impact the quality of the result as well as Here will be images and more tables to show the impact of different parameters The method has been evaluated on several models: - - - - +| Data set | Faces | Volume | Convex hull volume | Overhead | +| :---- | ----: | ----: | -: | -: | +| Camel | 19.536 | 0.0468 | 0.15541 | 2.32388 | +| Elephant | 5.558 | 0.0462 | 0.12987 | 1.81087 | +| Triceratops | 5.660 | 136.732 | 336.925 | 1.46412 | - - - -

-
-Data set - -Faces - -Volume - -Convex hull volume - -Overhead -

-
-Camel - -19.536 - -0.0468 - -0.15541 - -2.32388 -
-Elephant - -5.558 - -0.0462 - -0.12987 - -1.81087 -
-Triceratops - -5.660 - -136.732 - -336.925 - -1.46412 -

-
If not mentioned otherwise, all tests used a volume error of 0.01, a maximum depth of 10, 1 million voxels and split at the concavity. -Impact of varying the number of generated convex volumes with splitting at the concavity on volume overhead: - - - +Impact of varying the number of generated convex volumes with splitting at the concavity and refitting to the convex hull of the input mesh on volume overhead: - - +| Data set | Split location | Refitting | 4 volumes | 6 volumes | 8 volumes | 10 volumes | 12 volumes | +| :---- | ----: | ----: | -: | -: | -: | -: | -: | +| Camel | Concavity | + | 0.6951 | 0.4316 | 0.3016 | 0.2173 | 0.1939 | +| Camel | Concavity | - | 0.9932 | 0.7482 | 0.6174 | 0.5507 | 0.5261 | +| Elephant | Concavity | + | 0.7897 | 0.6505 | 0.4973 | 0.3986 | 0.3299 | +| Elephant | Concavity | - | 1.2140 | 1.0028 | 0.8071 | 0.7290 | 0.6870 | +| Triceratops | Concavity | + | 0.5952 | 0.3978 | 0.3548 | 0.2385 | 0.2057 | +| Triceratops | Concavity | - | 1.0073 | 0.7490 | 0.7035 | 0.5966 | 0.5429 | - -

-
-Data set - -Split location - -5 volumes - -7 volumes - -9 volumes - -11 volumes - -12 volumes -

-
-Camel - -Concavity - -0.8006 - -0.6680 - -0.5871 - -0.5736 - -0.5463 - -
-Elephant - -Concavity - -1.1927 - -0.9731 - -0.8461 - -0.7506 - -0.6947 - -
-Triceratops - -Concavity - -0.9770 - -0.7676 - -0.6722 - -0.5971 - -0.5658 - -

-
And by using the mid split: - - - - - - - -

-
-Data set - -Split location - -5 volumes - -7 volumes - -9 volumes - -11 volumes - -12 volumes -

-
-Camel - -Mid - -1.1158 - -1.0468 - -0.8660 - -0.6764 - -0.6057 - -
-Elephant - -Mid - -1.2121 - -1.0087 - -0.8444 - -0.7465 - -0.6931 - -
-Triceratops - -Mid - -0.8358 - -0.6870 - -0.7071 - -0.7375 - -0.6586 - -

-
+| Data set | Split location | Refitting | 4 volumes | 6 volumes | 8 volumes | 10 volumes | 12 volumes | +| :---- | ----: | ----: | -: | -: | -: | -: | -: | +| Camel | Mid | + | 0.9970 | 0.5762 | 0.5332 | 0.4040 | 0.2390 | +| Camel | Mid | - | 1.0875 | 0.9280 | 0.8394 | 0.6801 | 0.5529 | +| Elephant | Mid | + | 0.7424 | 0.5672 | 0.4207 | 0.3619 | 0.3043 | +| Elephant | Mid | - | 1.2075 | 1.0410 | 0.8247 | 0.6950 | 0.6434 | +| Triceratops | Mid | + | 0.7671 | 0.5096 | 0.3965 | 0.3309 | 0.2494 | +| Triceratops | Mid | - | 1.0470 | 0.8990 | 0.6749 | 0.5803 | 0.5230 | The running time for all cases in the above tables were between 1.4 and 3 seconds while being slightly lower when splitting at the concavity. Although searching the voxel grid for the concavity takes additional computational time, it is more than compensated by fewer splits. diff --git a/Convex_decomposition_3/examples/Convex_decomposition_3/approximate_convex_decomposition.cpp b/Convex_decomposition_3/examples/Convex_decomposition_3/approximate_convex_decomposition.cpp index ea2518d1718..9cb86c54e3b 100644 --- a/Convex_decomposition_3/examples/Convex_decomposition_3/approximate_convex_decomposition.cpp +++ b/Convex_decomposition_3/examples/Convex_decomposition_3/approximate_convex_decomposition.cpp @@ -35,6 +35,7 @@ int main(int argc, char* argv[]) .volume_error(0.1) .maximum_number_of_convex_volumes(9) .split_at_concavity(true) + .refitting(true) .maximum_number_of_voxels(1000000) .concurrency_tag(CGAL::Parallel_if_available_tag())); diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui index 86618c748c6..bb7668ebf5e 100644 --- a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_dialog.ui @@ -7,7 +7,7 @@ 0 0 448 - 215 + 225 @@ -26,30 +26,13 @@ - - - - 0.01 - - - Maximum number of components - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -59,7 +42,30 @@ Number of voxels - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + Maximum decomposition depth + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + 1 + + + 10000 + + + 16 @@ -69,37 +75,44 @@ Volume error - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - - + + - Qt::Vertical + Qt::Orientation::Horizontal - - - 20 - 40 - + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok - + - - - - Maximum decomposition depth + + + + 2 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 50 + + + 10 + + + + + + + 0.01 - Qt::RightToLeft + Qt::LayoutDirection::RightToLeft Split at concavity @@ -125,29 +138,29 @@ - - - - 2 + + + + Qt::Orientation::Vertical - - 50 + + + 20 + 40 + - - 10 - - + - - - - 1 + + + + Qt::LayoutDirection::RightToLeft - - 10000 + + Refit to mesh - - 16 + + true diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp index 6dff06deed5..8d31ef536dc 100644 --- a/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/Approximate_convex_decomposition_plugin.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -103,40 +104,45 @@ approximate_convex_decomposition() if (i == QDialog::Rejected) return; - const unsigned int maximumDepth = static_cast(ui.maximumDepth->value()); - const unsigned int maximumConvexHulls = static_cast(ui.maximumConvexHulls->value()); - const unsigned int numVoxels = static_cast(ui.numVoxels->value()); - const double volumeError = ui.volumeError->value(); - const bool splitConcavity = ui.splitConcavity->isChecked(); + const unsigned int maximum_depth = static_cast(ui.maximumDepth->value()); + const unsigned int maximum_convex_hulls = static_cast(ui.maximumConvexHulls->value()); + const unsigned int num_voxels = static_cast(ui.numVoxels->value()); + const double volume_error = ui.volumeError->value(); + const bool split_concavity = ui.splitConcavity->isChecked(); + const bool refitting = ui.refitting->isChecked(); QApplication::setOverrideCursor(Qt::WaitCursor); std::vector convex_volumes; - convex_volumes.reserve(9); + convex_volumes.reserve(maximum_convex_hulls); CGAL::approximate_convex_decomposition(*(sm_item->face_graph()), std::back_inserter(convex_volumes), - CGAL::parameters::maximum_depth(maximumDepth) - .volume_error(volumeError) - .maximum_number_of_convex_volumes(maximumConvexHulls) - .split_at_concavity(splitConcavity) - .maximum_number_of_voxels(numVoxels) + CGAL::parameters::maximum_depth(maximum_depth) + .volume_error(volume_error) + .maximum_number_of_convex_volumes(maximum_convex_hulls) + .split_at_concavity(split_concavity) + .refitting(refitting) + .maximum_number_of_voxels(num_voxels) .concurrency_tag(CGAL::Parallel_if_available_tag())); - std::vector distinct_colors; // the first color is either the background or the unique domain compute_deterministic_color_map(QColor(80, 250, 80), convex_volumes.size(), std::back_inserter(distinct_colors)); + Scene_group_item* group = new Scene_group_item(tr("%1 %2 decomposition").arg(sm_item->name()).arg(maximum_convex_hulls)); + scene->addItem(group); + for (std::size_t i = 0; i < convex_volumes.size(); i++) { - const Convex_hull& ch = convex_volumes[i]; + Convex_hull& ch = convex_volumes[i]; SMesh sm; CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(ch.first, ch.second, sm); - Scene_surface_mesh_item* component_item = new Scene_surface_mesh_item(sm); + Scene_surface_mesh_item* component_item = new Scene_surface_mesh_item(std::move(sm)); component_item->setName(tr("%1 %2").arg(sm_item->name()).arg(i)); component_item->setColor(distinct_colors[i]); Three::scene()->addItem(component_item); + Three::scene()->changeGroup(component_item, group); } QApplication::restoreOverrideCursor(); diff --git a/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt b/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt index d4d7740ff8e..d5e2be10a60 100644 --- a/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt +++ b/Lab/demo/Lab/Plugins/Convex_decomposition/CMakeLists.txt @@ -9,3 +9,9 @@ cgal_lab_plugin(acd_plugin Approximate_convex_decomposition_plugin ${acdUI_FILES target_link_libraries(nef_plugin PRIVATE scene_nef_polyhedron_item scene_surface_mesh_item) target_link_libraries(acd_plugin PRIVATE scene_surface_mesh_item) + +find_package(TBB QUIET) +include(CGAL_TBB_support) +if(TARGET CGAL::TBB_support) + target_link_libraries(acd_plugin PRIVATE CGAL::TBB_support) +endif() diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index f02f1fd4676..4695d38325e 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -181,6 +181,7 @@ CGAL_add_named_parameter(apply_iterative_snap_rounding_t, apply_iterative_snap_r CGAL_add_named_parameter(snap_grid_size_t, snap_grid_size, snap_grid_size) CGAL_add_named_parameter(maximum_number_of_voxels_t, maximum_number_of_voxels, maximum_number_of_voxels) CGAL_add_named_parameter(maximum_depth_t, maximum_depth, maximum_depth) +CGAL_add_named_parameter(refitting_t, refitting, refitting) CGAL_add_named_parameter(volume_error_t, volume_error, volume_error) CGAL_add_named_parameter(maximum_number_of_convex_volumes_t, maximum_number_of_convex_volumes, maximum_number_of_convex_volumes) CGAL_add_named_parameter(split_at_concavity_t, split_at_concavity, split_at_concavity) diff --git a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h index df810f94104..c63ca2f6acb 100644 --- a/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h +++ b/Surface_mesh_decomposition/include/CGAL/approximate_convex_decomposition.h @@ -18,8 +18,6 @@ #include #include -#include - #include #include @@ -44,6 +42,8 @@ #include #endif +#include + namespace CGAL { namespace internal { @@ -325,7 +325,7 @@ Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_trai } template -Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { +typename GeomTraits::Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { double vs = to_double(voxel_size); return Bbox_3( bb.xmin() + voxel[0] * vs, @@ -337,6 +337,20 @@ Iso_cuboid_3 bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, co ); } + +template +typename GeomTraits::Iso_cuboid_3 bbox_voxel_bbox(const Bbox_uint& voxelbb, const Bbox_3& bb, const typename GeomTraits::FT& voxel_size) { + double vs = to_double(voxel_size); + return Bbox_3( + bb.xmin() + voxelbb.lower[0] * vs, + bb.ymin() + voxelbb.lower[1] * vs, + bb.zmin() + voxelbb.lower[2] * vs, + bb.xmin() + (voxelbb.upper[0] + 1) * vs, + bb.ymin() + (voxelbb.upper[1] + 1) * vs, + bb.zmin() + (voxelbb.upper[2] + 1) * vs + ); +} + void scanline_floodfill(Grid_cell label, std::vector& grid, const Vec3_uint& grid_size, std::deque& todo) { const auto vox = [&grid, &grid_size](unsigned int x, unsigned int y, unsigned int z) -> int8_t& { return grid[z + (y * grid_size[2]) + (x * grid_size[1] * grid_size[2])]; @@ -728,7 +742,7 @@ template struct Convex_hull_candidate { using FT = typename GeomTraits::FT; using Point_3 = typename GeomTraits::Point_3; - Iso_cuboid_3 bbox; + typename GeomTraits::Iso_cuboid_3 bbox; FT voxel_volume; FT volume; FT volume_error; @@ -736,14 +750,14 @@ struct Convex_hull_candidate { std::vector> indices; Convex_hull_candidate() noexcept : voxel_volume(0), volume(0), volume_error(0) {} - Convex_hull_candidate(Convex_hull_candidate&& o) noexcept { - bbox = o.bbox; - voxel_volume = o.voxel_volume; - volume = o.volume; - volume_error = o.volume_error; - points = std::move(o.points); - indices = std::move(o.indices); - } + Convex_hull_candidate(Convex_hull_candidate&& o) noexcept : + bbox(o.bbox), + voxel_volume(o.voxel_volume), + volume(o.volume), + volume_error(o.volume_error), + points(std::move(o.points)), + indices(std::move(o.indices)) + {} Convex_hull_candidate& operator= (Convex_hull_candidate&& o) noexcept { bbox = o.bbox; @@ -892,7 +906,7 @@ void fill_grid(Candidate &c, std::vector &grid, const FaceGr for (unsigned int x = face_bb.lower[0]; x <= face_bb.upper[0]; x++) for (unsigned int y = face_bb.lower[1]; y <= face_bb.upper[1]; y++) for (unsigned int z = face_bb.lower[2]; z <= face_bb.upper[2]; z++) { - Iso_cuboid_3 box = bbox_voxel({ x, y, z }, bb, voxel_size); + typename GeomTraits::Iso_cuboid_3 box = bbox_voxel({ x, y, z }, bb, voxel_size); const typename GeomTraits::Point_3 &p = point(fd, mesh); if (do_intersect(Polygon_mesh_processing::triangle(fd, mesh), box) || box.has_on_bounded_side(p)) vox(x, y, z) = Grid_cell::SURFACE; @@ -1169,6 +1183,217 @@ bool finished(Candidate &c, const NamedParameters& np) { return false; } +template +void shrink_candidates(const FaceGraph& tmesh, std::vector>& candidates, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, CGAL::Sequential_tag) { + using face_descriptor = typename boost::graph_traits::face_descriptor; + + using Point_3 = typename GeomTraits::Point_3; + using Segment_3 = typename GeomTraits::Segment_3; + using Triangle_3 = typename GeomTraits::Triangle_3; + using FT = typename GeomTraits::FT; + + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits_3; + using Tree = CGAL::AABB_tree; + + Tree tree(faces(tmesh).first, faces(tmesh).second, tmesh); + + for (Candidate& c : candidates) { + std::vector pts; + pts.reserve(c.new_surface.size() * 8); + using Box = typename GeomTraits::Iso_cuboid_3; + using IP_id = typename Tree::template Intersection_and_primitive_id::Type; + std::vector intersections; + tree.all_intersections(c.ch.bbox, std::back_inserter(intersections)); + + std::vector corners(8); + std::vector taken(8, false); + + corners[0] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymin(), c.ch.bbox.zmin()); + corners[1] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymax(), c.ch.bbox.zmin()); + corners[2] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymin(), c.ch.bbox.zmax()); + corners[3] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymax(), c.ch.bbox.zmax()); + corners[4] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymin(), c.ch.bbox.zmin()); + corners[5] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymax(), c.ch.bbox.zmin()); + corners[6] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymin(), c.ch.bbox.zmax()); + corners[7] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymax(), c.ch.bbox.zmax()); + + for (auto& i : intersections) { + const Point_3* p; + const Segment_3* s; + const Triangle_3* t; + const std::vector* v; + if (p = std::get_if(&(i.first))) { + pts.push_back(*p); + } + else if (s = std::get_if(&(i.first))) { + pts.push_back(s->source()); + pts.push_back(s->target()); + } + else if (t = std::get_if(&(i.first))) { + pts.push_back((*t)[0]); + pts.push_back((*t)[1]); + pts.push_back((*t)[2]); + auto pl = t->supporting_plane(); + for (std::size_t c = 0; c < 8; c++) + if (!taken[c]) + if (pl.oriented_side(corners[c]) != CGAL::ON_POSITIVE_SIDE) + taken[c] = true; + } + else if (v = std::get_if>(&(i.first))) { + const Triangle_3 &t = CGAL::Polygon_mesh_processing::triangle(i.second, tmesh); + auto pl = t.supporting_plane(); + for (std::size_t c = 0; c < 8; c++) + if (!taken[c]) + if (pl.oriented_side(corners[c]) != CGAL::ON_POSITIVE_SIDE) + taken[c] = true; + std::copy(v->begin(), v->end(), std::back_inserter(pts)); + } + } + + for (std::size_t c = 0; c < 8; c++) + if (taken[c]) + pts.push_back(corners[c]); + + pts.reserve(pts.size() + c.new_surface.size() * 8); + + for (const Vec3_uint& v : c.new_surface) { + FT xmin = bbox.xmin() + FT(v[0]) * voxel_size; + FT ymin = bbox.ymin() + FT(v[1]) * voxel_size; + FT zmin = bbox.zmin() + FT(v[2]) * voxel_size; + FT xmax = bbox.xmin() + FT(v[0] + 1) * voxel_size; + FT ymax = bbox.ymin() + FT(v[1] + 1) * voxel_size; + FT zmax = bbox.zmin() + FT(v[2] + 1) * voxel_size; + pts.push_back(Point_3(xmin, ymin, zmin)); + pts.push_back(Point_3(xmin, ymax, zmin)); + pts.push_back(Point_3(xmin, ymin, zmax)); + pts.push_back(Point_3(xmin, ymax, zmax)); + pts.push_back(Point_3(xmax, ymin, zmin)); + pts.push_back(Point_3(xmax, ymax, zmin)); + pts.push_back(Point_3(xmax, ymin, zmax)); + pts.push_back(Point_3(xmax, ymax, zmax)); + } + + convex_hull_3(pts.begin(), pts.end(), c.ch.points, c.ch.indices); + c.ch.bbox = bbox_3(c.ch.points.begin(), c.ch.points.end()); + + if (c.ch.indices.size() <= 3 || (c.ch.volume = volume(c.ch.points, c.ch.indices)) == 0) + c.ch.volume_error = -1; + else + c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; + } +} + +template +void shrink_candidates(const FaceGraph& tmesh, std::vector>& candidates, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, CGAL::Parallel_tag) { +#ifdef CGAL_LINKED_WITH_TBB + using face_descriptor = typename boost::graph_traits::face_descriptor; + + using Point_3 = typename GeomTraits::Point_3; + using Segment_3 = typename GeomTraits::Segment_3; + using Triangle_3 = typename GeomTraits::Triangle_3; + using FT = typename GeomTraits::FT; + + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits_3; + using Tree = CGAL::AABB_tree; + + Tree tree(faces(tmesh).first, faces(tmesh).second, tmesh); + + const auto shrink = [&tree, voxel_size, &bbox, &tmesh](Candidate& c) { + std::vector pts; + pts.reserve(c.new_surface.size() * 8); + using Box = typename GeomTraits::Iso_cuboid_3; + using IP_id = typename Tree::template Intersection_and_primitive_id::Type; + std::vector intersections; + tree.all_intersections(c.ch.bbox, std::back_inserter(intersections)); + + std::vector corners(8); + std::vector taken(8, false); + + corners[0] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymin(), c.ch.bbox.zmin()); + corners[1] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymax(), c.ch.bbox.zmin()); + corners[2] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymin(), c.ch.bbox.zmax()); + corners[3] = Point_3(c.ch.bbox.xmin(), c.ch.bbox.ymax(), c.ch.bbox.zmax()); + corners[4] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymin(), c.ch.bbox.zmin()); + corners[5] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymax(), c.ch.bbox.zmin()); + corners[6] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymin(), c.ch.bbox.zmax()); + corners[7] = Point_3(c.ch.bbox.xmax(), c.ch.bbox.ymax(), c.ch.bbox.zmax()); + + for (auto& i : intersections) { + const Point_3* p; + const Segment_3* s; + const Triangle_3* t; + const std::vector* v; + if (p = std::get_if(&(i.first))) { + pts.push_back(*p); + } + else if (s = std::get_if(&(i.first))) { + pts.push_back(s->source()); + pts.push_back(s->target()); + } + else if (t = std::get_if(&(i.first))) { + pts.push_back((*t)[0]); + pts.push_back((*t)[1]); + pts.push_back((*t)[2]); + auto pl = t->supporting_plane(); + for (std::size_t c = 0; c < 8; c++) + if (!taken[c]) + if (pl.oriented_side(corners[c]) != CGAL::ON_POSITIVE_SIDE) + taken[c] = true; + } + else if (v = std::get_if>(&(i.first))) { + const Triangle_3& t = CGAL::Polygon_mesh_processing::triangle(i.second, tmesh); + auto pl = t.supporting_plane(); + for (std::size_t c = 0; c < 8; c++) + if (!taken[c]) + if (pl.oriented_side(corners[c]) != CGAL::ON_POSITIVE_SIDE) + taken[c] = true; + std::copy(v->begin(), v->end(), std::back_inserter(pts)); + } + } + + for (std::size_t c = 0;c<8;c++) + if (taken[c]) + pts.push_back(corners[c]); + + pts.reserve(pts.size() + c.new_surface.size() * 8); + + for (const Vec3_uint& v : c.new_surface) { + FT xmin = bbox.xmin() + FT(v[0]) * voxel_size; + FT ymin = bbox.ymin() + FT(v[1]) * voxel_size; + FT zmin = bbox.zmin() + FT(v[2]) * voxel_size; + FT xmax = bbox.xmin() + FT(v[0] + 1) * voxel_size; + FT ymax = bbox.ymin() + FT(v[1] + 1) * voxel_size; + FT zmax = bbox.zmin() + FT(v[2] + 1) * voxel_size; + pts.push_back(Point_3(xmin, ymin, zmin)); + pts.push_back(Point_3(xmin, ymax, zmin)); + pts.push_back(Point_3(xmin, ymin, zmax)); + pts.push_back(Point_3(xmin, ymax, zmax)); + pts.push_back(Point_3(xmax, ymin, zmin)); + pts.push_back(Point_3(xmax, ymax, zmin)); + pts.push_back(Point_3(xmax, ymin, zmax)); + pts.push_back(Point_3(xmax, ymax, zmax)); + } + + convex_hull_3(pts.begin(), pts.end(), c.ch.points, c.ch.indices); + c.ch.bbox = bbox_3(c.ch.points.begin(), c.ch.points.end()); + + if (c.ch.indices.size() <= 3 || (c.ch.volume = volume(c.ch.points, c.ch.indices)) == 0) + c.ch.volume_error = -1; + else + c.ch.volume_error = CGAL::abs(c.ch.volume - c.ch.voxel_volume) / c.ch.voxel_volume; + }; + + tbb::parallel_for_each(candidates, shrink); +#else + assert(false); + CGAL_USE(candidates); + CGAL_USE(bbox); + CGAL_USE(voxel_size); +#endif +} + template void recurse(std::vector>& candidates, std::vector& grid, const Vec3_uint& grid_size, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, const NamedParameters& np, CGAL::Parallel_tag) { #ifdef CGAL_LINKED_WITH_TBB @@ -1264,11 +1489,14 @@ void merge(std::vector>& candidates, const typ FT volume_error; bool operator < (const Merged_candidate& other) const { + if (volume_error == other.volume_error) + return other.ch > ch; + else return (volume_error > other.volume_error); } - Merged_candidate() : ch_a(-1), ch_b(-1) {} - Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} + Merged_candidate() : ch_a(-1), ch_b(-1), ch(-1) {} + Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b), ch(-1) {} }; tbb::concurrent_unordered_map> hulls; @@ -1285,12 +1513,12 @@ void merge(std::vector>& candidates, const typ candidates.reserve(max_convex_hulls); std::vector todo; - tbb::concurrent_priority_queue queue; + std::priority_queue queue; const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) { - Convex_hull_candidate& ci = hulls[m.ch_a]; - Convex_hull_candidate& cj = hulls[m.ch_b]; - m.ch = num_hulls.fetch_add(1); + const Convex_hull_candidate& ci = hulls[m.ch_a]; + const Convex_hull_candidate& cj = hulls[m.ch_b]; + Convex_hull_candidate& ch = hulls[m.ch]; ch.bbox = box_union(ci.bbox, cj.bbox); @@ -1299,9 +1527,18 @@ void merge(std::vector>& candidates, const typ std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); - ch.volume = volume(ch.points, ch.indices); + if (ch.indices.size() <= 3) { + m.volume_error = ch.volume_error = -1; + return; + } - ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + ch.volume = volume(ch.points, ch.indices); + if (ci.volume_error == -1 || cj.volume_error == -1) { + m.volume_error = -1; + ch.volume_error = 0; + } + else + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; }; for (std::size_t i : keep) { @@ -1310,13 +1547,18 @@ void merge(std::vector>& candidates, const typ if (j <= i) continue; const Convex_hull_candidate& cj = hulls[j]; - if (CGAL::do_intersect(ci.bbox, cj.bbox)) + if (CGAL::do_intersect(ci.bbox, cj.bbox)) { todo.emplace_back(Merged_candidate(i, j)); + todo.back().ch = num_hulls++; + } else { Merged_candidate m(i, j); Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - m.ch = -1; - m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + + if (ci.volume_error == -1 || cj.volume_error == -1) + m.volume_error = -1; + else + m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; queue.push(std::move(m)); } } @@ -1329,8 +1571,8 @@ void merge(std::vector>& candidates, const typ todo.clear(); while (!queue.empty() && keep.size() > max_convex_hulls) { - Merged_candidate m; - while (!queue.try_pop(m) && !queue.empty()); + Merged_candidate m = queue.top(); + queue.pop(); auto ch_a = hulls.find(m.ch_a); if (ch_a == hulls.end()) @@ -1340,8 +1582,10 @@ void merge(std::vector>& candidates, const typ if (ch_b == hulls.end()) continue; - if (m.ch == -1) + if (m.ch == -1) { + m.ch = num_hulls++; do_merge(m); + } keep.erase(m.ch_a); keep.erase(m.ch_b); @@ -1353,13 +1597,17 @@ void merge(std::vector>& candidates, const typ for (std::size_t id : keep) { const Convex_hull_candidate& ci = hulls[id]; - if (CGAL::do_intersect(ci.bbox, cj.bbox)) + if (CGAL::do_intersect(ci.bbox, cj.bbox)) { todo.emplace_back(Merged_candidate(id, m.ch)); + todo.back().ch = num_hulls++; + } else { Merged_candidate merged(id, m.ch); Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - merged.ch = -1; - merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + if (ci.volume_error == -1 || cj.volume_error == -1) + merged.volume_error = -1; + else + merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; queue.push(std::move(merged)); } } @@ -1374,6 +1622,8 @@ void merge(std::vector>& candidates, const typ num_hulls = 0; + candidates.reserve(max_convex_hulls); + for (std::size_t i : keep) candidates.push_back(std::move(hulls[i])); #else @@ -1397,11 +1647,14 @@ void merge(std::vector>& candidates, const typ FT volume_error; bool operator < (const Merged_candidate& other) const { + if (volume_error == other.volume_error) + return other.ch > ch; + else return (volume_error > other.volume_error); } - Merged_candidate() : ch_a(-1), ch_b(-1) {} - Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b) {} + Merged_candidate() : ch_a(-1), ch_b(-1), ch(-1) {} + Merged_candidate(std::size_t ch_a, std::size_t ch_b) : ch_a(ch_a), ch_b(ch_b), ch(-1) {} }; std::unordered_map> hulls; @@ -1423,7 +1676,6 @@ void merge(std::vector>& candidates, const typ Convex_hull_candidate& ci = hulls[m.ch_a]; Convex_hull_candidate& cj = hulls[m.ch_b]; - m.ch = num_hulls++; Convex_hull_candidate& ch = hulls[m.ch]; ch.bbox = box_union(ci.bbox, cj.bbox); @@ -1432,9 +1684,18 @@ void merge(std::vector>& candidates, const typ std::copy(cj.points.begin(), cj.points.end(), std::back_inserter(pts)); convex_hull_3(pts.begin(), pts.end(), ch.points, ch.indices); - ch.volume = volume(ch.points, ch.indices); + if (ch.indices.size() <= 3) { + m.volume_error = ch.volume_error = -1; + return; + } - ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; + ch.volume = volume(ch.points, ch.indices); + if (ci.volume_error == -1 || cj.volume_error == -1) { + m.volume_error = -1; + ch.volume_error = 0; + } + else + ch.volume_error = m.volume_error = CGAL::abs(ci.volume + cj.volume - ch.volume) / hull_volume; }; for (std::size_t i : keep) { @@ -1462,8 +1723,11 @@ void merge(std::vector>& candidates, const typ else { Merged_candidate m(i, j); Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - m.ch = -1; - m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + + if (ci.volume_error == -1 || cj.volume_error == -1) + m.volume_error = -1; + else + m.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; queue.push(std::move(m)); } } @@ -1481,8 +1745,10 @@ void merge(std::vector>& candidates, const typ if (ch_b == hulls.end()) continue; - if (m.ch == -1) + if (m.ch == -1) { + m.ch = num_hulls++; do_merge(m); + } keep.erase(m.ch_a); keep.erase(m.ch_b); @@ -1513,8 +1779,11 @@ void merge(std::vector>& candidates, const typ else { Merged_candidate merged(id, m.ch); Bbox_3 bbox = box_union(ci.bbox, cj.bbox).bbox(); - merged.ch = -1; - merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; + + if (ci.volume_error == -1 || cj.volume_error == -1) + merged.volume_error = -1; + else + merged.volume_error = CGAL::abs(ci.volume + cj.volume - bbox.x_span() * bbox.y_span() * bbox.z_span()) / hull_volume; queue.push(std::move(merged)); } } @@ -1562,6 +1831,12 @@ void merge(std::vector>& candidates, const typ * \cgalParamDefault{10} * \cgalParamNEnd * + * \cgalParamNBegin{refitting} + * \cgalParamDescription{refitting of convex volumes after split phase} + * \cgalParamType{Boolean} + * \cgalParamDefault{true} + * \cgalParamNEnd + * * \cgalParamNBegin{maximum_number_of_convex_volumes} * \cgalParamDescription{maximum number of convex volumes produced by the method} * \cgalParamType{unsigned int} @@ -1616,6 +1891,7 @@ std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputItera using FT = typename Geom_traits::FT; const unsigned int num_voxels = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_voxels), 1000000); + const bool refitting = parameters::choose_parameter(parameters::get_parameter(np, internal_np::refitting), true); using Concurrency_tag = typename internal_np::Lookup_named_param_def::type; #ifndef CGAL_LINKED_WITH_TBB @@ -1625,10 +1901,10 @@ std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputItera } #endif - const unsigned int max_convex_hulls = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_volumes), 16); - assert(max_convex_hulls > 0); + const unsigned int max_convex_volumes = parameters::choose_parameter(parameters::get_parameter(np, internal_np::maximum_number_of_convex_volumes), 16); + assert(max_convex_volumes > 0); - if (max_convex_hulls == 1) { + if (max_convex_volumes == 1) { internal::Convex_hull_candidate ch; using parameters::choose_parameter; @@ -1651,25 +1927,28 @@ std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputItera std::vector grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE); std::vector> candidates(1); - init(candidates[0], tmesh, grid, bb, grid_size, voxel_size, Concurrency_tag()); + internal::init(candidates[0], tmesh, grid, bb, grid_size, voxel_size, Concurrency_tag()); const FT hull_volume = candidates[0].ch.volume; - recurse(candidates, grid, grid_size, bb, voxel_size, np, Concurrency_tag()); + internal::recurse(candidates, grid, grid_size, bb, voxel_size, np, Concurrency_tag()); - std::vector> hulls; + if (refitting) + internal::shrink_candidates(tmesh, candidates, bb, voxel_size, Concurrency_tag()); + + std::vector> volumes; for (internal::Candidate &c : candidates) - hulls.emplace_back(std::move(c.ch)); + volumes.emplace_back(std::move(c.ch)); candidates.clear(); // merge until target number is reached - merge(hulls, hull_volume, max_convex_hulls, Concurrency_tag()); + internal::merge(volumes, hull_volume, max_convex_volumes, Concurrency_tag()); - for (std::size_t i = 0; i < hulls.size(); i++) - *out_volumes++ = std::make_pair(std::move(hulls[i].points), std::move(hulls[i].indices)); + for (std::size_t i = 0; i < volumes.size(); i++) + *out_volumes++ = std::make_pair(std::move(volumes[i].points), std::move(volumes[i].indices)); - return hulls.size(); + return volumes.size(); } } // namespace CGAL