added refitting

This commit is contained in:
Sven Oesau 2025-12-01 15:28:39 +01:00
parent 2efeaba656
commit bf47181a90
7 changed files with 454 additions and 314 deletions

View File

@ -102,7 +102,7 @@ This example shows the usage of `CGAL::convex_decomposition_3(Nef_polyhedron_3&)
<img src="https://soesau.github.io/acd_top.jpg" style="max-width:70%;"/>
</center>
\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}
<center>
@ -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
<b>Here will be images and more tables to show the impact of different parameters</b>
The method has been evaluated on several models:
<TABLE CELLSPACING=5 >
<TR><TD ALIGN=LEFT NOWRAP COLSPAN=5><HR>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Data set
<TD class="math" ALIGN=CENTER NOWRAP>
Faces
<TD class="math" ALIGN=CENTER NOWRAP>
Volume
<TD class="math" ALIGN=CENTER NOWRAP>
Convex hull volume
<TD class="math" ALIGN=CENTER NOWRAP>
Overhead
<TR><TD ALIGN=LEFT NOWRAP COLSPAN=5><HR>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Camel
<TD class="math" ALIGN=RIGHT NOWRAP>
19.536
<TD class="math" ALIGN=RIGHT NOWRAP>
0.0468
<TD class="math" ALIGN=RIGHT NOWRAP>
0.15541
<TD class="math" ALIGN=CENTER NOWRAP>
2.32388
<TR>
| 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 |
<TD class="math" ALIGN=CENTER NOWRAP>
Elephant
<TD class="math" ALIGN=RIGHT NOWRAP>
5.558
<TD class="math" ALIGN=RIGHT NOWRAP>
0.0462
<TD class="math" ALIGN=RIGHT NOWRAP>
0.12987
<TD class="math" ALIGN=CENTER NOWRAP>
1.81087
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Triceratops
<TD class="math" ALIGN=RIGHT NOWRAP>
5.660
<TD class="math" ALIGN=RIGHT NOWRAP>
136.732
<TD class="math" ALIGN=RIGHT NOWRAP>
336.925
<TD class="math" ALIGN=CENTER NOWRAP>
1.46412
<TR>
<TD ALIGN=LEFT NOWRAP COLSPAN=12><HR>
</TABLE>
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:
<TABLE CELLSPACING=5 >
<TR><TD ALIGN=LEFT NOWRAP COLSPAN=7><HR>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Data set
<TD class="math" ALIGN=CENTER NOWRAP>
Split location
<TD class="math" ALIGN=CENTER NOWRAP>
5 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
7 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
9 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
11 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
12 volumes
<TR><TD ALIGN=LEFT NOWRAP COLSPAN=7><HR>
<TR>
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:
<TD class="math" ALIGN=CENTER NOWRAP>
Camel
<TD class="math" ALIGN=RIGHT NOWRAP>
Concavity
<TD class="math" ALIGN=LEFT NOWRAP>
0.8006
<TD class="math" ALIGN=LEFT NOWRAP>
0.6680
<TD class="math" ALIGN=LEFT NOWRAP>
0.5871
<TD class="math" ALIGN=LEFT NOWRAP>
0.5736
<TD class="math" ALIGN=LEFT NOWRAP>
0.5463
<TD class="math" ALIGN=LEFT NOWRAP>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Elephant
<TD class="math" ALIGN=RIGHT NOWRAP>
Concavity
<TD class="math" ALIGN=LEFT NOWRAP>
1.1927
<TD class="math" ALIGN=LEFT NOWRAP>
0.9731
<TD class="math" ALIGN=LEFT NOWRAP>
0.8461
<TD class="math" ALIGN=LEFT NOWRAP>
0.7506
<TD class="math" ALIGN=LEFT NOWRAP>
0.6947
<TD class="math" ALIGN=LEFT NOWRAP>
<TR>
| 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 |
<TD class="math" ALIGN=CENTER NOWRAP>
Triceratops
<TD class="math" ALIGN=RIGHT NOWRAP>
Concavity
<TD class="math" ALIGN=LEFT NOWRAP>
0.9770
<TD class="math" ALIGN=LEFT NOWRAP>
0.7676
<TD class="math" ALIGN=LEFT NOWRAP>
0.6722
<TD class="math" ALIGN=LEFT NOWRAP>
0.5971
<TD class="math" ALIGN=LEFT NOWRAP>
0.5658
<TD class="math" ALIGN=LEFT NOWRAP>
<TR>
<TD ALIGN=LEFT NOWRAP COLSPAN=7><HR>
</TABLE>
And by using the mid split:
<TABLE CELLSPACING=5 >
<TR><TD ALIGN=LEFT NOWRAP COLSPAN=7><HR>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Data set
<TD class="math" ALIGN=CENTER NOWRAP>
Split location
<TD class="math" ALIGN=CENTER NOWRAP>
5 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
7 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
9 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
11 volumes
<TD class="math" ALIGN=CENTER NOWRAP>
12 volumes
<TR><TD ALIGN=LEFT NOWRAP COLSPAN=7><HR>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Camel
<TD class="math" ALIGN=RIGHT NOWRAP>
Mid
<TD class="math" ALIGN=LEFT NOWRAP>
1.1158
<TD class="math" ALIGN=LEFT NOWRAP>
1.0468
<TD class="math" ALIGN=LEFT NOWRAP>
0.8660
<TD class="math" ALIGN=LEFT NOWRAP>
0.6764
<TD class="math" ALIGN=LEFT NOWRAP>
0.6057
<TD class="math" ALIGN=LEFT NOWRAP>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Elephant
<TD class="math" ALIGN=RIGHT NOWRAP>
Mid
<TD class="math" ALIGN=LEFT NOWRAP>
1.2121
<TD class="math" ALIGN=LEFT NOWRAP>
1.0087
<TD class="math" ALIGN=LEFT NOWRAP>
0.8444
<TD class="math" ALIGN=LEFT NOWRAP>
0.7465
<TD class="math" ALIGN=LEFT NOWRAP>
0.6931
<TD class="math" ALIGN=LEFT NOWRAP>
<TR>
<TD class="math" ALIGN=CENTER NOWRAP>
Triceratops
<TD class="math" ALIGN=RIGHT NOWRAP>
Mid
<TD class="math" ALIGN=LEFT NOWRAP>
0.8358
<TD class="math" ALIGN=LEFT NOWRAP>
0.6870
<TD class="math" ALIGN=LEFT NOWRAP>
0.7071
<TD class="math" ALIGN=LEFT NOWRAP>
0.7375
<TD class="math" ALIGN=LEFT NOWRAP>
0.6586
<TD class="math" ALIGN=LEFT NOWRAP>
<TR>
<TD ALIGN=LEFT NOWRAP COLSPAN=7><HR>
</TABLE>
| 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.

View File

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

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>448</width>
<height>215</height>
<height>225</height>
</rect>
</property>
<property name="windowTitle">
@ -26,30 +26,13 @@
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="DoubleEdit" name="volumeError">
<property name="text">
<string>0.01</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="angleLabel">
<property name="text">
<string>Maximum number of components</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
@ -59,7 +42,30 @@
<string>Number of voxels</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="sizingLabel_2">
<property name="text">
<string>Maximum decomposition depth</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QSpinBox" name="maximumConvexHulls">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>16</number>
</property>
</widget>
</item>
@ -69,37 +75,44 @@
<string>Volume error</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="6" column="2">
<spacer name="verticalSpacer">
<item row="8" column="3">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</spacer>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="sizingLabel_2">
<property name="text">
<string>Maximum decomposition depth</string>
<item row="4" column="2" colspan="2">
<widget class="QSpinBox" name="maximumDepth">
<property name="minimum">
<number>2</number>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="DoubleEdit" name="volumeError">
<property name="text">
<string>0.01</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QCheckBox" name="splitConcavity">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
<enum>Qt::LayoutDirection::RightToLeft</enum>
</property>
<property name="text">
<string>Split at concavity</string>
@ -125,29 +138,29 @@
</property>
</widget>
</item>
<item row="4" column="2" colspan="2">
<widget class="QSpinBox" name="maximumDepth">
<property name="minimum">
<number>2</number>
<item row="6" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="maximum">
<number>50</number>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</spacer>
</item>
<item row="1" column="2" colspan="2">
<widget class="QSpinBox" name="maximumConvexHulls">
<property name="minimum">
<number>1</number>
<item row="7" column="2">
<widget class="QCheckBox" name="refitting">
<property name="layoutDirection">
<enum>Qt::LayoutDirection::RightToLeft</enum>
</property>
<property name="maximum">
<number>10000</number>
<property name="text">
<string>Refit to mesh</string>
</property>
<property name="value">
<number>16</number>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>

View File

@ -7,6 +7,7 @@
#include <CGAL/Timer.h>
#include <CGAL/Three/Three.h>
#include <CGAL/Three/Scene_group_item.h>
#include <QObject>
#include <QAction>
@ -103,40 +104,45 @@ approximate_convex_decomposition()
if (i == QDialog::Rejected)
return;
const unsigned int maximumDepth = static_cast<unsigned int>(ui.maximumDepth->value());
const unsigned int maximumConvexHulls = static_cast<unsigned int>(ui.maximumConvexHulls->value());
const unsigned int numVoxels = static_cast<unsigned int>(ui.numVoxels->value());
const double volumeError = ui.volumeError->value();
const bool splitConcavity = ui.splitConcavity->isChecked();
const unsigned int maximum_depth = static_cast<unsigned int>(ui.maximumDepth->value());
const unsigned int maximum_convex_hulls = static_cast<unsigned int>(ui.maximumConvexHulls->value());
const unsigned int num_voxels = static_cast<unsigned int>(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_hull> 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<QColor> 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();

View File

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

View File

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

View File

@ -18,8 +18,6 @@
#include <unordered_set>
#include <queue>
#include <CGAL/Iso_cuboid_3.h>
#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
@ -44,6 +42,8 @@
#include <unordered_map>
#endif
#include <CGAL/Polygon_mesh_processing/triangle.h>
namespace CGAL {
namespace internal {
@ -325,7 +325,7 @@ Bbox_uint grid_bbox_face(const FaceGraph& mesh, const typename boost::graph_trai
}
template<typename GeomTraits>
Iso_cuboid_3<GeomTraits> 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<GeomTraits> bbox_voxel(const Vec3_uint& voxel, const Bbox_3& bb, co
);
}
template<typename GeomTraits>
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<int8_t>& grid, const Vec3_uint& grid_size, std::deque<Vec3_uint>& 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<typename GeomTraits>
struct Convex_hull_candidate {
using FT = typename GeomTraits::FT;
using Point_3 = typename GeomTraits::Point_3;
Iso_cuboid_3<GeomTraits> 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<std::array<unsigned int, 3>> 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<GeomTraits>& operator= (Convex_hull_candidate<GeomTraits>&& o) noexcept {
bbox = o.bbox;
@ -892,7 +906,7 @@ void fill_grid(Candidate<GeomTraits> &c, std::vector<int8_t> &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<GeomTraits>({ x, y, z }, bb, voxel_size);
typename GeomTraits::Iso_cuboid_3 box = bbox_voxel<GeomTraits>({ x, y, z }, bb, voxel_size);
const typename GeomTraits::Point_3 &p = point<GeomTraits>(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<GeomTraits> &c, const NamedParameters& np) {
return false;
}
template<typename GeomTraits, typename FaceGraph>
void shrink_candidates(const FaceGraph& tmesh, std::vector<Candidate<GeomTraits>>& candidates, const Bbox_3& bbox, const typename GeomTraits::FT& voxel_size, CGAL::Sequential_tag) {
using face_descriptor = typename boost::graph_traits<FaceGraph>::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<FaceGraph>;
using Traits = CGAL::AABB_traits_3<GeomTraits, Primitive>;
using Tree = CGAL::AABB_tree<Traits>;
Tree tree(faces(tmesh).first, faces(tmesh).second, tmesh);
for (Candidate<GeomTraits>& c : candidates) {
std::vector<Point_3> 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<Box>::Type;
std::vector<IP_id> intersections;
tree.all_intersections(c.ch.bbox, std::back_inserter(intersections));
std::vector<Point_3> corners(8);
std::vector<bool> 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<Point_3>* v;
if (p = std::get_if<Point_3>(&(i.first))) {
pts.push_back(*p);
}
else if (s = std::get_if<Segment_3>(&(i.first))) {
pts.push_back(s->source());
pts.push_back(s->target());
}
else if (t = std::get_if<Triangle_3>(&(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<std::vector<Point_3>>(&(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<GeomTraits>(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<typename GeomTraits, typename FaceGraph>
void shrink_candidates(const FaceGraph& tmesh, std::vector<Candidate<GeomTraits>>& 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<FaceGraph>::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<FaceGraph>;
using Traits = CGAL::AABB_traits_3<GeomTraits, Primitive>;
using Tree = CGAL::AABB_tree<Traits>;
Tree tree(faces(tmesh).first, faces(tmesh).second, tmesh);
const auto shrink = [&tree, voxel_size, &bbox, &tmesh](Candidate<GeomTraits>& c) {
std::vector<Point_3> 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<Box>::Type;
std::vector<IP_id> intersections;
tree.all_intersections(c.ch.bbox, std::back_inserter(intersections));
std::vector<Point_3> corners(8);
std::vector<bool> 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<Point_3>* v;
if (p = std::get_if<Point_3>(&(i.first))) {
pts.push_back(*p);
}
else if (s = std::get_if<Segment_3>(&(i.first))) {
pts.push_back(s->source());
pts.push_back(s->target());
}
else if (t = std::get_if<Triangle_3>(&(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<std::vector<Point_3>>(&(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<GeomTraits>(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<typename GeomTraits, typename NamedParameters>
void recurse(std::vector<Candidate<GeomTraits>>& candidates, std::vector<int8_t>& 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<Convex_hull_candidate<GeomTraits>>& 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<std::size_t, Convex_hull_candidate<GeomTraits>> hulls;
@ -1285,12 +1513,12 @@ void merge(std::vector<Convex_hull_candidate<GeomTraits>>& candidates, const typ
candidates.reserve(max_convex_hulls);
std::vector<Merged_candidate> todo;
tbb::concurrent_priority_queue<Merged_candidate> queue;
std::priority_queue<Merged_candidate> queue;
const auto do_merge = [hull_volume, &hulls, &num_hulls](Merged_candidate& m) {
Convex_hull_candidate<GeomTraits>& ci = hulls[m.ch_a];
Convex_hull_candidate<GeomTraits>& cj = hulls[m.ch_b];
m.ch = num_hulls.fetch_add(1);
const Convex_hull_candidate<GeomTraits>& ci = hulls[m.ch_a];
const Convex_hull_candidate<GeomTraits>& cj = hulls[m.ch_b];
Convex_hull_candidate<GeomTraits>& ch = hulls[m.ch];
ch.bbox = box_union(ci.bbox, cj.bbox);
@ -1299,9 +1527,18 @@ void merge(std::vector<Convex_hull_candidate<GeomTraits>>& 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<GeomTraits>(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<GeomTraits>(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<Convex_hull_candidate<GeomTraits>>& candidates, const typ
if (j <= i)
continue;
const Convex_hull_candidate<GeomTraits>& 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<Convex_hull_candidate<GeomTraits>>& 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<Convex_hull_candidate<GeomTraits>>& 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<Convex_hull_candidate<GeomTraits>>& candidates, const typ
for (std::size_t id : keep) {
const Convex_hull_candidate<GeomTraits>& 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<Convex_hull_candidate<GeomTraits>>& 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<Convex_hull_candidate<GeomTraits>>& 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<std::size_t, Convex_hull_candidate<GeomTraits>> hulls;
@ -1423,7 +1676,6 @@ void merge(std::vector<Convex_hull_candidate<GeomTraits>>& candidates, const typ
Convex_hull_candidate<GeomTraits>& ci = hulls[m.ch_a];
Convex_hull_candidate<GeomTraits>& cj = hulls[m.ch_b];
m.ch = num_hulls++;
Convex_hull_candidate<GeomTraits>& ch = hulls[m.ch];
ch.bbox = box_union(ci.bbox, cj.bbox);
@ -1432,9 +1684,18 @@ void merge(std::vector<Convex_hull_candidate<GeomTraits>>& 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<GeomTraits>(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<GeomTraits>(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<Convex_hull_candidate<GeomTraits>>& 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<Convex_hull_candidate<GeomTraits>>& 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<Convex_hull_candidate<GeomTraits>>& 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<Convex_hull_candidate<GeomTraits>>& 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<internal_np::concurrency_tag_t, NamedParameters, Parallel_if_available_tag>::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<Geom_traits> ch;
using parameters::choose_parameter;
@ -1651,25 +1927,28 @@ std::size_t approximate_convex_decomposition(const FaceGraph& tmesh, OutputItera
std::vector<int8_t> grid(grid_size[0] * grid_size[1] * grid_size[2], internal::Grid_cell::INSIDE);
std::vector<internal::Candidate<Geom_traits>> 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<internal::Convex_hull_candidate<Geom_traits>> hulls;
if (refitting)
internal::shrink_candidates<Geom_traits, FaceGraph>(tmesh, candidates, bb, voxel_size, Concurrency_tag());
std::vector<internal::Convex_hull_candidate<Geom_traits>> volumes;
for (internal::Candidate<Geom_traits> &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