PMP::isotropic_remeshing - add variable sizing field (#4891)

## Summary of Changes

This PR introduces the possibility to chose a variable sizing field for
isotropic remeshing.

## Release Management

* Affected package(s): PMP
* Feature/Small Feature (if any):
[Here](https://cgal.geometryfactory.com/CGAL/Members/wiki/Features/Small_Features/Curvature-adaptive_remeshing)
-- **pre-approval on 2023/10/24**
* Link to compiled documentation (obligatory for small feature) [*wrong
link name to be changed*](httpssss://wrong_URL_to_be_changed/Manual/Pkg)
* License and copyright ownership: unchanged
* Depends on #6760

## TODO:
- [x] check branch size (for @sloriot)
- [x] handle the update of the interpolated curvature branch (for
@sloriot)
This commit is contained in:
Sebastien Loriot 2023-11-19 16:35:32 +01:00 committed by GitHub
commit 5c62ae44c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1971 additions and 479 deletions

View File

@ -3106,6 +3106,16 @@ pages = "207--221"
bibsource = {dblp computer science bibliography, https://dblp.org/}
}
@inproceedings {dunyach2013curvRemesh,
booktitle = {Eurographics 2013 - Short Papers},
title = {{Adaptive Remeshing for Real-Time Mesh Deformation}},
author = {Dunyach, Marion and Vanderhaeghe, David and Barthe, Loïc and Botsch, Mario},
year = {2013},
publisher = {The Eurographics Association},
ISSN = {1017-4656},
DOI = {10.2312/conf/EG2013/short/029-032}
}
@book{botsch2010PMP,
title={Polygon mesh processing},
author={M. Botsch and L. Kobbelt and M. Pauly and P. Alliez and B. L{\'e}vy},

View File

@ -157,7 +157,7 @@ def protect_upper_case(title):
return title.replace("dD","{dD}").replace("2D","{2D}").replace("3D","{3D}").replace("CGAL","{CGAL}").replace("Qt","{Qt}").replace("Boost","{Boost}")
def protect_accentuated_letters(authors):
res=authors.replace("é",r"{\'e}").replace("è",r"{\`e}").replace("É",r"{\'E}").replace("ä",r"{\"a}").replace("ö",r"{\"o}").replace("ñ",r"{\~n}").replace("ã",r"{\~a}").replace("ë",r"{\"e}").replace("ı",r"{\i}").replace("Ş",r"{\c{S}}").replace("ş",r"{\c{s}}").replace("%","")
res=authors.replace("é",r"{\'e}").replace("è",r"{\`e}").replace("É",r"{\'E}").replace("ä",r"{\"a}").replace("ö",r"{\"o}").replace("ñ",r"{\~n}").replace("ã",r"{\~a}").replace("ë",r"{\"e}").replace("ı",r"{\i}").replace("Ş",r"{\c{S}}").replace("ş",r"{\c{s}}").replace("%","").replace("đ",r"{\-d}")
try:
res.encode('ascii')
except UnicodeEncodeError:

View File

@ -0,0 +1,66 @@
/// \ingroup PkgPolygonMeshProcessingConcepts
/// \cgalConcept
///
/// The concept `PMPSizingField` defines the requirements for the sizing field
/// used in `CGAL::Polygon_mesh_processing::isotropic_remeshing()` to define
/// the target length for every individual edge during the remeshing process.
///
/// \cgalHasModelsBegin
/// \cgalHasModels{CGAL::Polygon_mesh_processing::Uniform_sizing_field}
/// \cgalHasModels{CGAL::Polygon_mesh_processing::Adaptive_sizing_field}
/// \cgalHasModelsEnd
///
class PMPSizingField{
public:
/// @name Types
/// @{
/// Vertex descriptor type
typedef boost::graph_traits<PolygonMesh>::vertex_descriptor vertex_descriptor;
/// Halfedge descriptor type
typedef boost::graph_traits<PolygonMesh>::halfedge_descriptor halfedge_descriptor;
/// 3D point type matching the value type of the vertex property map passed to `CGAL::Polygon_mesh_processing::isotropic_remeshing()`
typedef unspecified_type Point_3;
/// Polygon mesh type matching the type passed to `CGAL::Polygon_mesh_processing::isotropic_remeshing()`
typedef unspecified_type PolygonMesh;
/// Number type matching the `FT` type of the geometric traits passed to `CGAL::Polygon_mesh_processing::isotropic_remeshing()`
typedef unspecified_type FT;
/// @}
/// @name Functions
/// @{
/// a function that returns the sizing value at `v`.
FT at(const vertex_descriptor v) const;
/// a function controlling edge split and edge collapse,
/// returning the ratio of the current edge length and the local target edge length between
/// the points of `va` and `vb` in case the current edge is too long, and `std::nullopt` otherwise.
std::optional<FT> is_too_long(const vertex_descriptor va,
const vertex_descriptor vb) const;
/// a function controlling edge collapse by returning the ratio of the squared length of `h` and the
/// local target edge length if it is too short, and `std::nullopt` otherwise.
std::optional<FT> is_too_short(const halfedge_descriptor h,
const PolygonMesh& pmesh) const;
/// a function returning the location of the split point of the edge of `h`.
Point_3 split_placement(const halfedge_descriptor h,
const PolygonMesh& pmesh) const;
/// a function that updates the sizing field value at the vertex `v`.
void update(const vertex_descriptor v,
const PolygonMesh& pmesh);
/// @}
};

View File

@ -37,3 +37,4 @@ HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/selfintersections.jpg \
${CGAL_PACKAGE_DOC_DIR}/fig/decimate_cheese.png \
${CGAL_PACKAGE_DOC_DIR}/fig/decimate_colors.png \
${CGAL_PACKAGE_DOC_DIR}/fig/decimate_rg_joint.png

View File

@ -79,7 +79,7 @@
\cgalPkgPicture{hole_filling_ico.png}
\cgalPkgSummaryBegin
\cgalPkgAuthors{David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katrioplas, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz}
\cgalPkgAuthors{David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katrioplas, Sébastien Loriot, Ivan Pađen, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz}
\cgalPkgDesc{This package provides a collection of methods and classes for polygon mesh processing,
ranging from basic operations on simplices, to complex geometry processing algorithms such as
Boolean operations, remeshing, repairing, collision and intersection detection, and more.}
@ -115,8 +115,6 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage.
- `CGAL::Polygon_mesh_processing::split()`
\cgalCRPSection{Meshing Functions}
- \link PMP_meshing_grp `CGAL::Polygon_mesh_processing::isotropic_remeshing()` \endlink
- \link PMP_meshing_grp `CGAL::Polygon_mesh_processing::split_long_edges()` \endlink
- `CGAL::Polygon_mesh_processing::remesh_planar_patches()`
- `CGAL::Polygon_mesh_processing::remesh_almost_planar_patches()`
- `CGAL::Polygon_mesh_processing::refine()`
@ -134,6 +132,10 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage.
- `CGAL::Polygon_mesh_processing::smooth_shape()`
- `CGAL::Polygon_mesh_processing::random_perturbation()`
\cgalCRPSection{Sizing Fields}
- `CGAL::Polygon_mesh_processing::Uniform_sizing_field`
- `CGAL::Polygon_mesh_processing::Adaptive_sizing_field`
\cgalCRPSection{Orientation Functions}
- `CGAL::Polygon_mesh_processing::orient_polygon_soup()`
- `CGAL::Polygon_mesh_processing::orient()`
@ -214,6 +216,7 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage.
\cgalCRPSection{Corrected Curvatures}
- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()`
- `CGAL::Polygon_mesh_processing::Principal_curvatures_and_directions`
\cgalCRPSection{Normal Computation Functions}
- `CGAL::Polygon_mesh_processing::compute_face_normal()`

View File

@ -4,7 +4,7 @@ namespace CGAL {
\anchor Chapter_PolygonMeshProcessing
\cgalAutoToc
\authors David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katrioplas, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz
\authors David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katrioplas, Sébastien Loriot, Ivan Pađen, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz
\image html neptun_head.jpg
\image latex neptun_head.jpg
@ -116,10 +116,18 @@ to the original surface to keep a good approximation of the input.
A triangulated region of a polygon mesh can be remeshed using the function
`CGAL::Polygon_mesh_processing::isotropic_remeshing()`, as illustrated
by \cgalFigureRef{iso_remeshing}. The algorithm has only two parameters :
the target edge length for the remeshed surface patch, and
the number of iterations of the abovementioned sequence of operations. The bigger
this number, the smoother and closer to target edge length the mesh will be.
by \cgalFigureRef{iso_remeshing}. The algorithm has two parameters:
the sizing field object for the remeshed surface patch, and
the number of iterations of the abovementioned sequence of operations.
The sizing field establishes the local target edge length for the remeshed surface. Two sizing fields are
provided: a uniform and a curvature-adaptive sizing field. With `CGAL::Polygon_mesh_processing::Uniform_sizing_field`,
all triangle edges are targeted to have equal lengths. With `CGAL::Polygon_mesh_processing::Adaptive_sizing_field`, triangle edge lengths depend on the local curvature --
shorter edges appear in regions with a higher curvature and vice versa. The outline of the adaptive sizing
field algorithm is available in \cgalCite{dunyach2013curvRemesh}. The distinction between uniform and adaptive
sizing fields is depicted in figure \cgalFigureRef{uniform_and_adaptive}.
As the number of iterations increases, the mesh tends to be smoother and closer to the target edge length.
An additional option has been added to \e protect (\e i.\e e. not modify) some given polylines.
In some cases, those polylines are too long, and reaching the desired target edge length while protecting them is not
@ -134,6 +142,12 @@ Isotropic remeshing. (a) Triangulated input surface mesh.
(d) Surface mesh with the selection uniformly remeshed.
\cgalFigureEnd
\cgalFigureBegin{uniform_and_adaptive, uniform_and_adaptive.png}
Sizing fields in isotropic remeshing.
(a) Uniform sizing field.
(b) Curvature-based adaptive sizing field.
\cgalFigureEnd
\paragraph Delaunay-Based Surface Remeshing
The mesh generation algorithm implemented in the \ref PkgMesh3 package can be used to remesh a given triangulated surface mesh.
The algorithm, based on Delaunay refinement of a restricted Delaunay triangulation,
@ -1287,5 +1301,7 @@ supervision of David Coeurjolly, Jaques-Olivier Lachaud, and Sébastien Loriot.
<a href="https://dgtal-team.github.io/doc-nightly/moduleCurvatureMeasures.html">DGtal's implementation</a> was also
used as a reference during the project.
The curvature-based sizing field version of isotropic remeshing was added by Ivan Pađen during GSoC 2023, under the supervision of Sébastien Loriot and Jane Tournois.
*/
} /* namespace CGAL */

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

View File

@ -69,6 +69,8 @@ if(TARGET CGAL::Eigen3_support)
target_link_libraries(hole_filling_example_LCC PUBLIC CGAL::Eigen3_support)
create_single_source_cgal_program("mesh_smoothing_example.cpp")
target_link_libraries(mesh_smoothing_example PUBLIC CGAL::Eigen3_support)
create_single_source_cgal_program("isotropic_remeshing_with_sizing_example.cpp")
target_link_libraries(isotropic_remeshing_with_sizing_example PUBLIC CGAL::Eigen3_support)
create_single_source_cgal_program("delaunay_remeshing_example.cpp")
target_link_libraries(delaunay_remeshing_example PUBLIC CGAL::Eigen3_support)
create_single_source_cgal_program("remesh_almost_planar_patches.cpp")

View File

@ -0,0 +1,45 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <CGAL/Polygon_mesh_processing/Adaptive_sizing_field.h>
#include <fstream>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Surface_mesh<K::Point_3> 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/nefertiti.off");
Mesh mesh;
if (!PMP::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh)) {
std::cerr << "Not a valid input file." << std::endl;
return 1;
}
std::cout << "Start remeshing of " << filename
<< " (" << num_faces(mesh) << " faces)..." << std::endl;
const double tol = 0.001;
const std::pair edge_min_max{0.001, 0.5};
PMP::Adaptive_sizing_field<Mesh> sizing_field(tol, edge_min_max, faces(mesh), mesh);
unsigned int nb_iter = 5;
PMP::isotropic_remeshing(
faces(mesh),
sizing_field,
mesh,
CGAL::parameters::number_of_iterations(nb_iter)
.number_of_relaxation_steps(3)
);
CGAL::IO::write_polygon_mesh("out.off", mesh, CGAL::parameters::stream_precision(17));
std::cout << "Remeshing done." << std::endl;
return 0;
}

View File

@ -0,0 +1,280 @@
// Copyright (c) 2023 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) : Ivan Paden
#ifndef CGAL_PMP_REMESHING_ADAPTIVE_SIZING_FIELD_H
#define CGAL_PMP_REMESHING_ADAPTIVE_SIZING_FIELD_H
#include <CGAL/license/Polygon_mesh_processing/meshing_hole_filling.h>
#include <CGAL/Polygon_mesh_processing/internal/Sizing_field_base.h>
#include <CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h>
#include <CGAL/boost/graph/selection.h>
#include <CGAL/boost/graph/Face_filtered_graph.h>
#include <CGAL/number_utils.h>
namespace CGAL
{
namespace Polygon_mesh_processing
{
/*!
* \ingroup PMP_meshing_grp
* a sizing field describing variable target mesh edge lengths for
* `CGAL::Polygon_mesh_processing::isotropic_remeshing()`.
* This adaptive sizing field is a function of local discrete curvatures,
* computed using the
* `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` function.
*
* Edges too long with respect to the local target edge length are split in two, while
* edges that are too short are collapsed.
*
* \cgalModels{PMPSizingField}
*
* \sa `isotropic_remeshing()`
* \sa `Uniform_sizing_field`
*
* @tparam PolygonMesh model of `MutableFaceGraph` that
* has an internal property map for `CGAL::vertex_point_t`.
*/
template <class PolygonMesh,
class VPMap = typename boost::property_map<PolygonMesh, CGAL::vertex_point_t>::const_type>
class Adaptive_sizing_field
#ifndef DOXYGEN_RUNNING
: public internal::Sizing_field_base<PolygonMesh, VPMap>
#endif
{
private:
typedef internal::Sizing_field_base<PolygonMesh, VPMap> Base;
typedef typename CGAL::dynamic_vertex_property_t<typename Base::FT> Vertex_property_tag;
typedef typename boost::property_map<PolygonMesh,
Vertex_property_tag>::type VertexSizingMap;
public:
typedef typename Base::K K;
typedef typename Base::FT FT;
typedef typename Base::Point_3 Point_3;
typedef typename Base::face_descriptor face_descriptor;
typedef typename Base::halfedge_descriptor halfedge_descriptor;
typedef typename Base::vertex_descriptor vertex_descriptor;
/// \name Creation
/// @{
/*!
* Constructor
*
* @tparam FaceRange range of `boost::graph_traits<PolygonMesh>::%face_descriptor`,
* model of `Range`. Its iterator type is `ForwardIterator`.
* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
*
* @param tol the error tolerance, used together with curvature to derive target edge length.
* Lower tolerance values will result in shorter mesh edges.
* @param edge_len_min_max contains the bounds for minimum and maximum
* edge lengths
* @param face_range the range of triangular faces defining one or several surface patches
* to be remeshed. It should be the same as the range of faces passed to `isotropic_remeshing()`.
* @param pmesh a polygon mesh with triangulated surface patches to be remeshed. It should be the
* same mesh as the one passed to `isotropic_remeshing()`.
* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
* \cgalNamedParamsBegin
* \cgalParamNBegin{vertex_point_map}
* \cgalParamDescription{a property map associating points to the vertices of `pmesh`}
* \cgalParamType{a class model of `ReadWritePropertyMap` with
* `boost::graph_traits<PolygonMesh>::%vertex_descriptor`
* as key type and `%Point_3` as value type}
* \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`}
* \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t`
* must be available in `PolygonMesh`.}
* \cgalParamNEnd
*
* \cgalParamNBegin{ball_radius}
* \cgalParamDescription{`ball_radius` parameter passed to `interpolated_corrected_curvatures()`}
* \cgalParamNEnd
* \cgalNamedParamsEnd
*/
template <typename FaceRange
, typename NamedParameters = parameters::Default_named_parameters>
Adaptive_sizing_field(const FT tol
, const std::pair<FT, FT>& edge_len_min_max
, const FaceRange& face_range
, PolygonMesh& pmesh
, const NamedParameters& np = parameters::default_values())
: tol(tol)
, m_short(edge_len_min_max.first)
, m_long(edge_len_min_max.second)
, m_vpmap(parameters::choose_parameter(
parameters::get_parameter(np, internal_np::vertex_point),
get_property_map(vertex_point, pmesh)))
, m_vertex_sizing_map(get(Vertex_property_tag(), pmesh))
{
if (face_range.size() == faces(pmesh).size())
{
// calculate curvature from the whole mesh
calc_sizing_map(pmesh, np);
}
else
{
// expand face selection and calculate curvature from it
std::vector<face_descriptor> selection(face_range.begin(), face_range.end());
auto is_selected = get(CGAL::dynamic_face_property_t<bool>(), pmesh);
for (face_descriptor f : faces(pmesh)) put(is_selected, f, false);
for (face_descriptor f : face_range) put(is_selected, f, true);
expand_face_selection(selection, pmesh, 1,
is_selected, std::back_inserter(selection));
Face_filtered_graph<PolygonMesh> ffg(pmesh, selection);
calc_sizing_map(ffg, np);
}
}
///@}
private:
template <typename FaceGraph
, typename NamedParameters = parameters::Default_named_parameters>
void calc_sizing_map(FaceGraph& face_graph
, const NamedParameters& np)
{
typedef Principal_curvatures_and_directions<K> Principal_curvatures;
typedef typename CGAL::dynamic_vertex_property_t<Principal_curvatures> Vertex_curvature_tag;
typedef typename boost::property_map<FaceGraph,
Vertex_curvature_tag>::type Vertex_curvature_map;
using parameters::choose_parameter;
using parameters::get_parameter;
typename Base::FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1);
#ifdef CGAL_PMP_REMESHING_VERBOSE
int oversize = 0;
int undersize = 0;
int insize = 0;
std::cout << "Calculating sizing field..." << std::endl;
#endif
Vertex_curvature_map vertex_curvature_map = get(Vertex_curvature_tag(), face_graph);
interpolated_corrected_curvatures(face_graph,
parameters::vertex_principal_curvatures_and_directions_map(vertex_curvature_map)
.ball_radius(radius));
// calculate vertex sizing field L(x_i) from the curvature field
for(vertex_descriptor v : vertices(face_graph))
{
auto vertex_curv = get(vertex_curvature_map, v);
const FT max_absolute_curv = (CGAL::max)(CGAL::abs(vertex_curv.max_curvature),
CGAL::abs(vertex_curv.min_curvature));
const FT vertex_size_sq = 6 * tol / max_absolute_curv - 3 * CGAL::square(tol);
if (vertex_size_sq > CGAL::square(m_long))
{
put(m_vertex_sizing_map, v, m_long);
#ifdef CGAL_PMP_REMESHING_VERBOSE
++oversize;
#endif
}
else if (vertex_size_sq < CGAL::square(m_short))
{
put(m_vertex_sizing_map, v, m_short);
#ifdef CGAL_PMP_REMESHING_VERBOSE
++undersize;
#endif
}
else
{
put(m_vertex_sizing_map, v, CGAL::approximate_sqrt(vertex_size_sq));
#ifdef CGAL_PMP_REMESHING_VERBOSE
++insize;
#endif
}
}
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << " done (" << insize << " from curvature, "
<< oversize << " set to max, "
<< undersize << " set to min)" << std::endl;
#endif
}
FT sqlength(const vertex_descriptor va,
const vertex_descriptor vb) const
{
return FT(CGAL::squared_distance(get(m_vpmap, va), get(m_vpmap, vb)));
}
FT sqlength(const halfedge_descriptor& h, const PolygonMesh& pmesh) const
{
return sqlength(target(h, pmesh), source(h, pmesh));
}
public:
FT at(const vertex_descriptor v) const
{
CGAL_assertion(get(m_vertex_sizing_map, v));
return get(m_vertex_sizing_map, v);
}
std::optional<FT> is_too_long(const vertex_descriptor va, const vertex_descriptor vb) const
{
const FT sqlen = sqlength(va, vb);
FT sqtarg_len = CGAL::square(4./3. * (CGAL::min)(get(m_vertex_sizing_map, va),
get(m_vertex_sizing_map, vb)));
CGAL_assertion(get(m_vertex_sizing_map, va));
CGAL_assertion(get(m_vertex_sizing_map, vb));
if (sqlen > sqtarg_len)
return sqlen / sqtarg_len;
else
return std::nullopt;
}
std::optional<FT> is_too_short(const halfedge_descriptor h, const PolygonMesh& pmesh) const
{
const FT sqlen = sqlength(h, pmesh);
FT sqtarg_len = CGAL::square(4./5. * (CGAL::min)(get(m_vertex_sizing_map, source(h, pmesh)),
get(m_vertex_sizing_map, target(h, pmesh))));
CGAL_assertion(get(m_vertex_sizing_map, source(h, pmesh)));
CGAL_assertion(get(m_vertex_sizing_map, target(h, pmesh)));
if (sqlen < sqtarg_len)
return sqlen / sqtarg_len;
else
return std::nullopt;
}
Point_3 split_placement(const halfedge_descriptor h, const PolygonMesh& pmesh) const
{
return midpoint(get(m_vpmap, target(h, pmesh)),
get(m_vpmap, source(h, pmesh)));
}
void update(const vertex_descriptor v, const PolygonMesh& pmesh)
{
// calculating it as the average of two vertices on other ends
// of halfedges as updating is done during an edge split
FT vertex_size = 0;
CGAL_assertion(CGAL::halfedges_around_target(v, pmesh).size() == 2);
for (halfedge_descriptor ha: CGAL::halfedges_around_target(v, pmesh))
{
vertex_size += get(m_vertex_sizing_map, source(ha, pmesh));
}
vertex_size /= CGAL::halfedges_around_target(v, pmesh).size();
put(m_vertex_sizing_map, v, vertex_size);
}
private:
const FT tol;
const FT m_short;
const FT m_long;
const VPMap m_vpmap;
VertexSizingMap m_vertex_sizing_map;
};
}//end namespace Polygon_mesh_processing
}//end namespace CGAL
#endif //CGAL_PMP_REMESHING_ADAPTIVE_SIZING_FIELD_H

View File

@ -0,0 +1,149 @@
// Copyright (c) 2020 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) : Jane Tournois
#ifndef CGAL_PMP_REMESHING_UNIFORM_SIZING_FIELD_H
#define CGAL_PMP_REMESHING_UNIFORM_SIZING_FIELD_H
#include <CGAL/license/Polygon_mesh_processing/meshing_hole_filling.h>
#include <CGAL/Polygon_mesh_processing/internal/Sizing_field_base.h>
#include <CGAL/number_utils.h>
namespace CGAL
{
namespace Polygon_mesh_processing
{
/*!
* \ingroup PMP_meshing_grp
* a sizing field describing a uniform target edge length for
* `CGAL::Polygon_mesh_processing::isotropic_remeshing()`.
*
* Edges longer than 4/3 of the target edge length will be split in half, while
* edges shorter than 4/5 of the target edge length will be collapsed.
*
* \cgalModels{PMPSizingField}
*
* \sa `isotropic_remeshing()`
* \sa `Adaptive_sizing_field`
*
* @tparam PolygonMesh model of `MutableFaceGraph` that
* has an internal property map for `CGAL::vertex_point_t`.
* @tparam VPMap property map associating points to the vertices of `pmesh`,
* model of `ReadWritePropertyMap` with `boost::graph_traits<PolygonMesh>::%vertex_descriptor`
* as key type and `%Point_3` as value type. Default is `boost::get(CGAL::vertex_point, pmesh)`.
*/
template <class PolygonMesh,
class VPMap = typename boost::property_map<PolygonMesh, CGAL::vertex_point_t>::const_type>
class Uniform_sizing_field
#ifndef DOXYGEN_RUNNING
: public internal::Sizing_field_base<PolygonMesh, VPMap>
#endif
{
private:
typedef internal::Sizing_field_base<PolygonMesh, VPMap> Base;
public:
typedef typename Base::FT FT;
typedef typename Base::Point_3 Point_3;
typedef typename Base::halfedge_descriptor halfedge_descriptor;
typedef typename Base::vertex_descriptor vertex_descriptor;
/// \name Creation
/// @{
/*!
* Constructor.
* \param size the target edge length for isotropic remeshing. If set to 0,
* the criterion for edge length is ignored and edges are neither split nor collapsed.
* \param vpmap is the input vertex point map that associates points to the vertices of
* the input mesh.
*/
Uniform_sizing_field(const FT size, const VPMap& vpmap)
: m_size(size)
, m_sq_short( CGAL::square(4./5. * size))
, m_sq_long( CGAL::square(4./3. * size))
, m_vpmap(vpmap)
{}
/*!
* Constructor using internal vertex point map of the input polygon mesh.
*
* @param size the target edge length for isotropic remeshing. If set to 0,
* the criterion for edge length is ignored and edges are neither split nor collapsed.
* @param pmesh a polygon mesh with triangulated surface patches to be remeshed. The default
* vertex point map of `pmesh` is used to construct the class.
*/
Uniform_sizing_field(const FT size, const PolygonMesh& pmesh)
: Uniform_sizing_field(size, get(CGAL::vertex_point, pmesh))
{}
/// @}
private:
FT sqlength(const vertex_descriptor va,
const vertex_descriptor vb) const
{
return FT(squared_distance(get(m_vpmap, va), get(m_vpmap, vb)));
}
FT sqlength(const halfedge_descriptor& h, const PolygonMesh& pmesh) const
{
return sqlength(target(h, pmesh), source(h, pmesh));
}
public:
FT at(const vertex_descriptor /* v */) const
{
return m_size;
}
std::optional<FT> is_too_long(const vertex_descriptor va, const vertex_descriptor vb) const
{
const FT sqlen = sqlength(va, vb);
if (sqlen > m_sq_long)
//no need to return the ratio for the uniform field
return sqlen;
else
return std::nullopt;
}
std::optional<FT> is_too_short(const halfedge_descriptor h, const PolygonMesh& pmesh) const
{
const FT sqlen = sqlength(h, pmesh);
if (sqlen < m_sq_short)
//no need to return the ratio for the uniform field
return sqlen;
else
return std::nullopt;
}
Point_3 split_placement(const halfedge_descriptor h, const PolygonMesh& pmesh) const
{
return midpoint(get(m_vpmap, target(h, pmesh)),
get(m_vpmap, source(h, pmesh)));
}
void update(const vertex_descriptor /* v */, const PolygonMesh& /* pmesh */)
{}
private:
const FT m_size;
const FT m_sq_short;
const FT m_sq_long;
const VPMap m_vpmap;
};
}//end namespace Polygon_mesh_processing
}//end namespace CGAL
#endif //CGAL_PMP_REMESHING_UNIFORM_SIZING_FIELD_H

View File

@ -43,7 +43,6 @@
#include <boost/range/join.hpp>
#include <memory>
#include <boost/container/flat_set.hpp>
#include <optional>
#include <boost/property_map/function_property_map.hpp>
#include <map>
@ -54,6 +53,7 @@
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <optional>
#ifdef CGAL_PMP_REMESHING_DEBUG
#include <CGAL/Polygon_mesh_processing/self_intersections.h>
@ -72,7 +72,11 @@
#endif
namespace CGAL {
namespace Polygon_mesh_processing {
template <typename PM, typename VPMap> class Uniform_sizing_field;
namespace internal {
enum Halfedge_status {
@ -226,15 +230,13 @@ namespace internal {
template<typename PM,
typename EdgeConstraintMap,
typename VertexPointMap,
typename FacePatchMap>
typename FacePatchMap,
typename SizingFunction>
bool constraints_are_short_enough(const PM& pmesh,
EdgeConstraintMap ecmap,
VertexPointMap vpmap,
const FacePatchMap& fpm,
const double& high)
const SizingFunction& sizing)
{
double sqh = high*high;
typedef typename boost::graph_traits<PM>::halfedge_descriptor halfedge_descriptor;
typedef typename boost::graph_traits<PM>::edge_descriptor edge_descriptor;
for(edge_descriptor e : edges(pmesh))
@ -244,8 +246,7 @@ namespace internal {
get(ecmap, e) ||
get(fpm, face(h,pmesh))!=get(fpm, face(opposite(h,pmesh),pmesh)) )
{
if (sqh < CGAL::squared_distance(get(vpmap, source(h, pmesh)),
get(vpmap, target(h, pmesh))))
if (sizing.is_too_long(source(h, pmesh), target(h, pmesh)))
{
return false;
}
@ -377,19 +378,17 @@ namespace internal {
}
}
// split edges of edge_range that have their length > high
// Note: only used to split a range of edges provided as input
template<typename EdgeRange>
template<typename EdgeRange, typename SizingFunction>
void split_long_edges(const EdgeRange& edge_range,
const double& high)
SizingFunction& sizing)
{
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << "Split long edges (" << high << ")...";
std::cout << "Split long edges...";
std::cout.flush();
#endif
double sq_high = high*high;
//collect long edges
typedef std::pair<halfedge_descriptor, double> H_and_sql;
@ -400,9 +399,10 @@ namespace internal {
);
for(edge_descriptor e : edge_range)
{
double sqlen = sqlength(e);
if (sqlen > sq_high)
long_edges.emplace(halfedge(e, mesh_), sqlen);
const halfedge_descriptor he = halfedge(e, mesh_);
std::optional<double> sqlen = sizing.is_too_long(source(he, mesh_), target(he, mesh_));
if(sqlen != std::nullopt)
long_edges.emplace(he, sqlen.value());
}
//split long edges
@ -414,7 +414,6 @@ namespace internal {
//the edge with longest length
auto eit = long_edges.begin();
halfedge_descriptor he = eit->first;
double sqlen = eit->second;
long_edges.erase(eit);
//split edge
@ -433,15 +432,19 @@ namespace internal {
#ifdef CGAL_PMP_REMESHING_VERY_VERBOSE
std::cout << " refinement point : " << refinement_point << std::endl;
#endif
//update sizing field with the new point
sizing.update(vnew, mesh_);
//check sub-edges
double sqlen_new = 0.25 * sqlen;
if (sqlen_new > sq_high)
{
//if it was more than twice the "long" threshold, insert them
long_edges.emplace(hnew, sqlen_new);
long_edges.emplace(next(hnew, mesh_), sqlen_new);
}
//if it was more than twice the "long" threshold, insert them
std::optional<double> sqlen_new = sizing.is_too_long(source(hnew, mesh_), target(hnew, mesh_));
if(sqlen_new != std::nullopt)
long_edges.emplace(hnew, sqlen_new.value());
const halfedge_descriptor hnext = next(hnew, mesh_);
sqlen_new = sizing.is_too_long(source(hnext, mesh_), target(hnext, mesh_));
if (sqlen_new != std::nullopt)
long_edges.emplace(hnext, sqlen_new.value());
//insert new edges to keep triangular faces, and update long_edges
if (!is_border(hnew, mesh_))
@ -478,13 +481,12 @@ namespace internal {
// "visits all edges of the mesh
//if an edge is longer than the given threshold `high`, the edge
//is split at its midpoint and the two adjacent triangles are bisected (2-4 split)"
void split_long_edges(const double& high)
template<typename SizingFunction>
void split_long_edges(SizingFunction& sizing)
{
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << "Split long edges (" << high << ")..." << std::endl;
std::cout << "Split long edges..." << std::endl;
#endif
double sq_high = high*high;
//collect long edges
typedef std::pair<halfedge_descriptor, double> H_and_sql;
std::multiset< H_and_sql, std::function<bool(H_and_sql,H_and_sql)> >
@ -497,9 +499,10 @@ namespace internal {
{
if (!is_split_allowed(e))
continue;
double sqlen = sqlength(e);
if(sqlen > sq_high)
long_edges.emplace(halfedge(e, mesh_), sqlen);
const halfedge_descriptor he = halfedge(e, mesh_);
std::optional<double> sqlen = sizing.is_too_long(source(he, mesh_), target(he, mesh_));
if(sqlen != std::nullopt)
long_edges.emplace(halfedge(e, mesh_), sqlen.value());
}
//split long edges
@ -511,7 +514,6 @@ namespace internal {
//the edge with longest length
auto eit = long_edges.begin();
halfedge_descriptor he = eit->first;
double sqlen = eit->second;
long_edges.erase(eit);
#ifdef CGAL_PMP_REMESHING_VERBOSE_PROGRESS
@ -528,7 +530,7 @@ namespace internal {
Patch_id patch_id_opp = get_patch_id(face(opposite(he, mesh_), mesh_));
//split edge
Point refinement_point = this->midpoint(he);
Point refinement_point = sizing.split_placement(he, mesh_);
halfedge_descriptor hnew = CGAL::Euler::split_edge(he, mesh_);
CGAL_assertion(he == next(hnew, mesh_));
put(ecmap_, edge(hnew, mesh_), get(ecmap_, edge(he, mesh_)) );
@ -547,14 +549,19 @@ namespace internal {
halfedge_added(hnew, status(he));
halfedge_added(hnew_opp, status(opposite(he, mesh_)));
//update sizing field with the new point
sizing.update(vnew, mesh_);
//check sub-edges
double sqlen_new = 0.25 * sqlen;
if (sqlen_new > sq_high)
{
//if it was more than twice the "long" threshold, insert them
long_edges.emplace(hnew, sqlen_new);
long_edges.emplace(next(hnew, mesh_), sqlen_new);
}
//if it was more than twice the "long" threshold, insert them
std::optional<double> sqlen_new = sizing.is_too_long(source(hnew, mesh_), target(hnew, mesh_));
if(sqlen_new != std::nullopt)
long_edges.emplace(hnew, sqlen_new.value());
const halfedge_descriptor hnext = next(hnew, mesh_);
sqlen_new = sizing.is_too_long(source(hnext, mesh_), target(hnext, mesh_));
if (sqlen_new != std::nullopt)
long_edges.emplace(hnext, sqlen_new.value());
//insert new edges to keep triangular faces, and update long_edges
if (!is_on_border(hnew))
@ -573,9 +580,9 @@ namespace internal {
if (snew == PATCH)
{
double sql = sqlength(hnew2);
if (sql > sq_high)
long_edges.emplace(hnew2, sql);
std::optional<double> sql = sizing.is_too_long(source(hnew2, mesh_), target(hnew2, mesh_));
if(sql != std::nullopt)
long_edges.emplace(hnew2, sql.value());
}
}
@ -596,9 +603,9 @@ namespace internal {
if (snew == PATCH)
{
double sql = sqlength(hnew2);
if (sql > sq_high)
long_edges.emplace(hnew2, sql);
std::optional<double> sql = sizing.is_too_long(source(hnew2, mesh_), target(hnew2, mesh_));
if (sql != std::nullopt)
long_edges.emplace(hnew2, sql.value());
}
}
}
@ -620,8 +627,8 @@ namespace internal {
// "collapses and thus removes all edges that are shorter than a
// threshold `low`. [...] testing before each collapse whether the collapse
// would produce an edge that is longer than `high`"
void collapse_short_edges(const double& low,
const double& high,
template<typename SizingFunction>
void collapse_short_edges(const SizingFunction& sizing,
const bool collapse_constraints)
{
typedef boost::bimap<
@ -630,22 +637,21 @@ namespace internal {
typedef typename Boost_bimap::value_type short_edge;
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << "Collapse short edges (" << low << ", " << high << ")..."
std::cout << "Collapse short edges..."
<< std::endl;
#endif
#ifdef CGAL_PMP_REMESHING_VERBOSE_PROGRESS
std::cout << "Fill bimap...";
std::cout.flush();
#endif
double sq_low = low*low;
double sq_high = high*high;
Boost_bimap short_edges;
for(edge_descriptor e : edges(mesh_))
{
double sqlen = sqlength(e);
if ((sqlen < sq_low) && is_collapse_allowed(e, collapse_constraints))
short_edges.insert(short_edge(halfedge(e, mesh_), sqlen));
std::optional<double> sqlen = sizing.is_too_short(halfedge(e, mesh_), mesh_);
if(sqlen != std::nullopt
&& is_collapse_allowed(e, collapse_constraints))
short_edges.insert(short_edge(halfedge(e, mesh_), sqlen.value()));
}
#ifdef CGAL_PMP_REMESHING_VERBOSE_PROGRESS
std::cout << "done." << std::endl;
@ -741,7 +747,8 @@ namespace internal {
for(halfedge_descriptor ha : halfedges_around_target(va, mesh_))
{
vertex_descriptor va_i = source(ha, mesh_);
if (sqlength(vb, va_i) > sq_high)
std::optional<double> sqha = sizing.is_too_long(vb, va_i);
if (sqha != std::nullopt)
{
collapse_ok = false;
break;
@ -796,7 +803,7 @@ namespace internal {
//fix constrained case
CGAL_assertion((is_constrained(vkept) || is_corner(vkept) || is_on_patch_border(vkept)) ==
(is_va_constrained || is_vb_constrained || is_va_on_constrained_polyline || is_vb_on_constrained_polyline));
if (fix_degenerate_faces(vkept, short_edges, sq_low, collapse_constraints))
if (fix_degenerate_faces(vkept, short_edges, sizing, collapse_constraints))
{
#ifdef CGAL_PMP_REMESHING_DEBUG
debug_status_map();
@ -806,9 +813,10 @@ namespace internal {
//insert new/remaining short edges
for (halfedge_descriptor ht : halfedges_around_target(vkept, mesh_))
{
double sqlen = sqlength(ht);
if ((sqlen < sq_low) && is_collapse_allowed(edge(ht, mesh_), collapse_constraints))
short_edges.insert(short_edge(ht, sqlen));
std::optional<double> sqlen = sizing.is_too_short(ht, mesh_);
if (sqlen != std::nullopt
&& is_collapse_allowed(edge(ht, mesh_), collapse_constraints))
short_edges.insert(short_edge(ht, sqlen.value()));
}
}
}//end if(collapse_ok)
@ -1006,8 +1014,10 @@ namespace internal {
// "applies an iterative smoothing filter to the mesh.
// The vertex movement has to be constrained to the vertex tangent plane [...]
// smoothing algorithm with uniform Laplacian weights"
template <class SizingFunction>
void tangential_relaxation_impl(const bool relax_constraints/*1d smoothing*/
, const unsigned int nb_iterations)
, const unsigned int nb_iterations
, const SizingFunction& sizing)
{
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << "Tangential relaxation (" << nb_iterations << " iter.)...";
@ -1038,16 +1048,41 @@ namespace internal {
auto constrained_vertices_pmap
= boost::make_function_property_map<vertex_descriptor>(vertex_constraint);
tangential_relaxation(
vertices(mesh_),
mesh_,
CGAL::parameters::number_of_iterations(nb_iterations)
.vertex_point_map(vpmap_)
.geom_traits(gt_)
.edge_is_constrained_map(constrained_edges_pmap)
.vertex_is_constrained_map(constrained_vertices_pmap)
.relax_constraints(relax_constraints)
);
if constexpr (std::is_same_v<SizingFunction, Uniform_sizing_field<PM, VertexPointMap>>)
{
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << " using tangential relaxation with weights equal to 1";
std::cout << std::endl;
#endif
tangential_relaxation(
vertices(mesh_),
mesh_,
CGAL::parameters::number_of_iterations(nb_iterations)
.vertex_point_map(vpmap_)
.geom_traits(gt_)
.edge_is_constrained_map(constrained_edges_pmap)
.vertex_is_constrained_map(constrained_vertices_pmap)
.relax_constraints(relax_constraints)
);
}
else
{
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << " using tangential relaxation weighted with the sizing field";
std::cout << std::endl;
#endif
tangential_relaxation(
vertices(mesh_),
mesh_,
CGAL::parameters::number_of_iterations(nb_iterations)
.vertex_point_map(vpmap_)
.geom_traits(gt_)
.edge_is_constrained_map(constrained_edges_pmap)
.vertex_is_constrained_map(constrained_vertices_pmap)
.relax_constraints(relax_constraints)
.sizing_function(sizing)
);
}
CGAL_assertion(!input_mesh_is_valid_ || is_valid_polygon_mesh(mesh_));
@ -1645,10 +1680,10 @@ private:
// else keep current status for en and eno
}
template<typename Bimap>
template<typename Bimap, typename SizingFunction>
bool fix_degenerate_faces(const vertex_descriptor& v,
Bimap& short_edges,
const double& sq_low,
const SizingFunction& sizing,
const bool collapse_constraints)
{
std::unordered_set<halfedge_descriptor> degenerate_faces;
@ -1726,9 +1761,9 @@ private:
//insert new edges in 'short_edges'
if (is_collapse_allowed(edge(hf, mesh_), collapse_constraints))
{
double sqlen = sqlength(hf);
if (sqlen < sq_low)
short_edges.insert(typename Bimap::value_type(hf, sqlen));
std::optional<double> sqlen = sizing.is_too_short(hf, mesh_);
if (sqlen != std::nullopt)
short_edges.insert(typename Bimap::value_type(hf, sqlen.value()));
}
if(!is_border(hf, mesh_) &&

View File

@ -0,0 +1,77 @@
// Copyright (c) 2020 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) : Jane Tournois
#ifndef CGAL_PMP_REMESHING_SIZING_FIELD_H
#define CGAL_PMP_REMESHING_SIZING_FIELD_H
#include <CGAL/license/Polygon_mesh_processing/meshing_hole_filling.h>
#include <CGAL/Kernel_traits.h>
#include <CGAL/boost/graph/properties.h>
#include <boost/optional.hpp>
namespace CGAL
{
namespace Polygon_mesh_processing
{
namespace internal
{
/*!
* \ingroup PMP_meshing_grp
* pure virtual class serving as a base for sizing field classes used in isotropic
* remeshing.
*
* \cgalModels{PMPSizingField}
*
* \sa `isotropic_remeshing()`
* \sa `Uniform_sizing_field`
* \sa `Adaptive_sizing_field`
*
* @tparam PolygonMesh model of `MutableFaceGraph` that
* has an internal property map for `CGAL::vertex_point_t`.
* @tparam VPMap property map associating points to the vertices of `pmesh`,
* model of `ReadWritePropertyMap` with `boost::graph_traits<PolygonMesh>::%vertex_descriptor`
* as key type and `%Point_3` as value type. Default is `boost::get(CGAL::vertex_point, pmesh)`.
*/
template <class PolygonMesh,
class VPMap = typename boost::property_map<PolygonMesh, CGAL::vertex_point_t>::const_type>
class Sizing_field_base
{
private:
typedef PolygonMesh PM;
typedef typename boost::property_traits<VPMap>::value_type Point;
public:
typedef typename CGAL::Kernel_traits<Point>::Kernel K;
typedef typename boost::graph_traits<PM>::face_descriptor face_descriptor;
typedef typename boost::graph_traits<PM>::halfedge_descriptor halfedge_descriptor;
typedef typename boost::graph_traits<PM>::vertex_descriptor vertex_descriptor;
typedef Point Point_3;
typedef typename K::FT FT;
public:
virtual FT at(const vertex_descriptor v) const = 0;
virtual std::optional<FT> is_too_long(const vertex_descriptor va,
const vertex_descriptor vb) const = 0;
virtual std::optional<FT> is_too_short(const halfedge_descriptor h,
const PolygonMesh& pmesh) const = 0;
virtual Point_3 split_placement(const halfedge_descriptor h, const PolygonMesh& pmesh) const = 0;
virtual void update(const vertex_descriptor v, const PolygonMesh& pmesh) = 0;
};
}//end namespace internal
}//end namespace Polygon_mesh_processing
}//end namespace CGAL
#endif //CGAL_PMP_REMESHING_SIZING_FIELD_H

View File

@ -617,12 +617,11 @@ void set_value(const T&, internal_np::Param_not_found)
// computes selected curvatures for one specific vertex
template<typename PolygonMesh,
typename NamedParameters = parameters::Default_named_parameters>
void interpolated_corrected_curvatures_one_vertex(
const PolygonMesh& pmesh,
const typename boost::graph_traits<PolygonMesh>::vertex_descriptor v,
const NamedParameters& np = parameters::default_values()
)
typename NamedParameters>
void interpolated_corrected_curvatures_one_vertex(
const PolygonMesh& pmesh,
const typename boost::graph_traits<PolygonMesh>::vertex_descriptor v,
const NamedParameters& np)
{
typedef typename GetGeomTraits<PolygonMesh, NamedParameters>::type GT;
typedef typename GetVertexPointMap<PolygonMesh, NamedParameters>::const_type Vertex_position_map;
@ -716,7 +715,7 @@ template<typename PolygonMesh,
}
template<typename PolygonMesh, typename NamedParameters = parameters::Default_named_parameters>
template<typename PolygonMesh, class NamedParameters>
class Interpolated_corrected_curvatures_computer
{
typedef typename GetGeomTraits<PolygonMesh, NamedParameters>::type GT;
@ -780,7 +779,6 @@ private:
mu1_map = get(CGAL::dynamic_face_property_t<FT>(), pmesh);
mu2_map = get(CGAL::dynamic_face_property_t<FT>(), pmesh);
muXY_map = get(CGAL::dynamic_face_property_t<std::array<FT, 3 * 3>>(), pmesh);
}
void set_named_params(const NamedParameters& np)
@ -824,10 +822,8 @@ private:
public:
Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh,
const NamedParameters& np = parameters::default_values()
) :
pmesh(pmesh)
Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh,const NamedParameters& np)
: pmesh(pmesh)
{
set_named_params(np);

View File

@ -18,6 +18,7 @@
#include <CGAL/disable_warnings.h>
#include <CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h>
#include <CGAL/Polygon_mesh_processing/Uniform_sizing_field.h>
#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
@ -43,12 +44,15 @@ namespace Polygon_mesh_processing {
* and `boost::graph_traits<PolygonMesh>::%halfedge_descriptor` must be
* models of `Hashable`.
* @tparam FaceRange range of `boost::graph_traits<PolygonMesh>::%face_descriptor`,
model of `Range`. Its iterator type is `ForwardIterator`.
* model of `Range`. Its iterator type is `ForwardIterator`.
* @tparam SizingFunction model of `PMPSizingField`
* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
*
* @param pmesh a polygon mesh with triangulated surface patches to be remeshed
* @param faces the range of triangular faces defining one or several surface patches to be remeshed
* @param target_edge_length the edge length that is targeted in the remeshed patch.
* @param sizing field that determines a target length for individual edges.
* If a number convertible to a `double` is passed, `Uniform_sizing_field()` will be used,
* with the number as a target edge length.
* If `0` is passed then only the edge-flip, tangential relaxation, and projection steps will be done.
* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
*
@ -193,9 +197,11 @@ namespace Polygon_mesh_processing {
*/
template<typename PolygonMesh
, typename FaceRange
, typename NamedParameters = parameters::Default_named_parameters>
, typename SizingFunction
, typename NamedParameters = parameters::Default_named_parameters
, typename = typename std::enable_if_t<!std::is_convertible_v<SizingFunction, double>>>
void isotropic_remeshing(const FaceRange& faces
, const double& target_edge_length
, SizingFunction& sizing
, PolygonMesh& pmesh
, const NamedParameters& np = parameters::default_values())
{
@ -261,9 +267,6 @@ void isotropic_remeshing(const FaceRange& faces
#endif
) ) );
double low = 4. / 5. * target_edge_length;
double high = 4. / 3. * target_edge_length;
#if !defined(CGAL_NO_PRECONDITIONS)
if(protect)
{
@ -271,7 +274,7 @@ void isotropic_remeshing(const FaceRange& faces
msg.append(" true with constraints larger than 4/3 * target_edge_length.");
msg.append(" Remeshing aborted.");
CGAL_precondition_msg(
internal::constraints_are_short_enough(pmesh, ecmap, vpmap, fpmap, high),
internal::constraints_are_short_enough(pmesh, ecmap, fpmap, sizing),
msg.c_str());
}
#endif
@ -303,8 +306,7 @@ void isotropic_remeshing(const FaceRange& faces
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << std::endl;
std::cout << "Remeshing (size = " << target_edge_length;
std::cout << ", #iter = " << nb_iterations << ")..." << std::endl;
std::cout << "Remeshing (#iter = " << nb_iterations << ")..." << std::endl;
t.reset(); t.start();
#endif
@ -313,16 +315,14 @@ void isotropic_remeshing(const FaceRange& faces
#ifdef CGAL_PMP_REMESHING_VERBOSE
std::cout << " * Iteration " << (i + 1) << " *" << std::endl;
#endif
if (target_edge_length>0)
{
if(do_split)
remesher.split_long_edges(high);
if(do_collapse)
remesher.collapse_short_edges(low, high, collapse_constraints);
}
if(do_split)
remesher.split_long_edges(sizing);
if(do_collapse)
remesher.collapse_short_edges(sizing, collapse_constraints);
if(do_flip)
remesher.flip_edges_for_valence_and_shape();
remesher.tangential_relaxation_impl(smoothing_1d, nb_laplacian);
remesher.tangential_relaxation_impl(smoothing_1d, nb_laplacian, sizing);
if ( choose_parameter(get_parameter(np, internal_np::do_project), true) )
remesher.project_to_surface(get_parameter(np, internal_np::projection_functor));
#ifdef CGAL_PMP_REMESHING_VERBOSE
@ -332,12 +332,36 @@ void isotropic_remeshing(const FaceRange& faces
#ifdef CGAL_PMP_REMESHING_VERBOSE
t.stop();
std::cout << "Remeshing done (size = " << target_edge_length;
std::cout << ", #iter = " << nb_iterations;
std::cout << "Remeshing done (#iter = " << nb_iterations;
std::cout << ", " << t.time() << " sec )." << std::endl;
#endif
}
// Overload when using target_edge_length for sizing
template<typename PolygonMesh
, typename FaceRange
, typename SizingValue
, typename NamedParameters = parameters::Default_named_parameters
, typename = typename std::enable_if_t<std::is_convertible_v<SizingValue, double>>>
void isotropic_remeshing(const FaceRange& faces
, const SizingValue target_edge_length
, PolygonMesh& pmesh
, const NamedParameters& np = parameters::default_values())
{
using parameters::choose_parameter;
using parameters::get_parameter;
typedef typename GetVertexPointMap<PolygonMesh, NamedParameters>::type VPMap;
VPMap vpmap = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_property_map(vertex_point, pmesh));
Uniform_sizing_field<PolygonMesh, VPMap> sizing(target_edge_length, vpmap);
if (target_edge_length > 0)
isotropic_remeshing(faces, sizing, pmesh, np);
else
isotropic_remeshing(faces, sizing, pmesh, np.do_split(false).do_collapse(false));
}
/*!
* \ingroup PMP_meshing_grp
* @brief splits the edges listed in `edges` into sub-edges
@ -352,12 +376,13 @@ void isotropic_remeshing(const FaceRange& faces
* has an internal property map for `CGAL::vertex_point_t`.
* @tparam EdgeRange range of `boost::graph_traits<PolygonMesh>::%edge_descriptor`,
* model of `Range`. Its iterator type is `InputIterator`.
* @tparam SizingFunction model of `PMPSizingField`
* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
*
* @param pmesh a polygon mesh
* @param edges the range of edges to be split if they are longer than given threshold
* @param max_length the edge length above which an edge from `edges` is split
* into to sub-edges
* @param sizing the sizing function that is used to split edges from 'edges' list. If a number convertible to
* a `double` is passed, `Uniform_sizing_field()` will be used, with the number as target edge length.
* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
* \cgalNamedParamsBegin
@ -402,9 +427,11 @@ void isotropic_remeshing(const FaceRange& faces
*/
template<typename PolygonMesh
, typename EdgeRange
, typename NamedParameters = parameters::Default_named_parameters>
, typename SizingFunction
, typename NamedParameters = parameters::Default_named_parameters
, typename = typename std::enable_if_t<!std::is_convertible_v<SizingFunction, double>>>
void split_long_edges(const EdgeRange& edges
, const double& max_length
, SizingFunction& sizing
, PolygonMesh& pmesh
, const NamedParameters& np = parameters::default_values())
{
@ -451,7 +478,23 @@ void split_long_edges(const EdgeRange& edges
fimap,
false/*need aabb_tree*/);
remesher.split_long_edges(edges, max_length);
remesher.split_long_edges(edges, sizing);
}
// Convenience overload when using max_length for sizing
template<typename PolygonMesh
, typename EdgeRange
, typename SizingValue
, typename NamedParameters = parameters::Default_named_parameters
, typename = typename std::enable_if_t<std::is_convertible_v<SizingValue, double>>>
void split_long_edges(const EdgeRange& edges
, const SizingValue max_length
, PolygonMesh& pmesh
, const NamedParameters& np = parameters::default_values())
{
// construct the uniform field, 3/4 as m_sq_long is stored with 4/3 of length
Uniform_sizing_field sizing(3. / 4. * max_length, pmesh);
split_long_edges(edges, sizing, pmesh, np);
}
} //end namespace Polygon_mesh_processing

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 GeometryFactory (France).
// Copyright (c) 2015-2023 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
@ -8,7 +8,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Jane Tournois
// Author(s) : Jane Tournois, Ivan Paden
#ifndef CGAL_POLYGON_MESH_PROCESSING_TANGENTIAL_RELAXATION_H
@ -17,6 +17,7 @@
#include <CGAL/license/Polygon_mesh_processing/meshing_hole_filling.h>
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
#include <CGAL/Polygon_mesh_processing/Uniform_sizing_field.h>
#include <CGAL/property_map.h>
#include <CGAL/Named_function_parameters.h>
@ -115,14 +116,23 @@ struct Allow_all_moves{
* \cgalParamDefault{If not provided, all moves are allowed.}
* \cgalParamNEnd
*
* \cgalParamNBegin{sizing_function}
* \cgalParamDescription{An object containing sizing field for individual vertices.
* Used to derive smoothing weights.}
* \cgalParamType{A model of `PMPSizingField`.}
* \cgalParamDefault{If not provided, smoothing weights are the same for all vertices.}
* \cgalParamNEnd
*
* \cgalNamedParamsEnd
*
* \todo check if it should really be a triangle mesh or if a polygon mesh is fine
*/
template <typename VertexRange, class TriangleMesh, class NamedParameters = parameters::Default_named_parameters>
template <typename VertexRange,
class TriangleMesh,
typename CGAL_NP_TEMPLATE_PARAMETERS>
void tangential_relaxation(const VertexRange& vertices,
TriangleMesh& tm,
const NamedParameters& np = parameters::default_values())
const CGAL_NP_CLASS& np = parameters::default_values())
{
typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor vertex_descriptor;
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
@ -130,18 +140,19 @@ void tangential_relaxation(const VertexRange& vertices,
using parameters::get_parameter;
using parameters::choose_parameter;
using parameters::is_default_parameter;
typedef typename GetGeomTraits<TriangleMesh, NamedParameters>::type GT;
typedef typename GetGeomTraits<TriangleMesh, CGAL_NP_CLASS>::type GT;
GT gt = choose_parameter(get_parameter(np, internal_np::geom_traits), GT());
typedef typename GetVertexPointMap<TriangleMesh, NamedParameters>::type VPMap;
typedef typename GetVertexPointMap<TriangleMesh, CGAL_NP_CLASS>::type VPMap;
VPMap vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_property_map(vertex_point, tm));
typedef Static_boolean_property_map<edge_descriptor, false> Default_ECM;
typedef typename internal_np::Lookup_named_param_def <
internal_np::edge_is_constrained_t,
NamedParameters,
CGAL_NP_CLASS,
Static_boolean_property_map<edge_descriptor, false> // default (no constraint)
> ::type ECM;
ECM ecm = choose_parameter(get_parameter(np, internal_np::edge_is_constrained),
@ -149,12 +160,20 @@ void tangential_relaxation(const VertexRange& vertices,
typedef typename internal_np::Lookup_named_param_def <
internal_np::vertex_is_constrained_t,
NamedParameters,
CGAL_NP_CLASS,
Static_boolean_property_map<vertex_descriptor, false> // default (no constraint)
> ::type VCM;
VCM vcm = choose_parameter(get_parameter(np, internal_np::vertex_is_constrained),
Static_boolean_property_map<vertex_descriptor, false>());
typedef typename internal_np::Lookup_named_param_def <
internal_np::sizing_function_t,
CGAL_NP_CLASS,
Uniform_sizing_field<TriangleMesh, VPMap>
> ::type SizingFunction;
SizingFunction sizing = choose_parameter(get_parameter(np, internal_np::sizing_function),
Uniform_sizing_field<TriangleMesh, VPMap>(-1, vpm));
const bool relax_constraints = choose_parameter(get_parameter(np, internal_np::relax_constraints), false);
const unsigned int nb_iterations = choose_parameter(get_parameter(np, internal_np::number_of_iterations), 1);
@ -201,7 +220,7 @@ void tangential_relaxation(const VertexRange& vertices,
typedef typename internal_np::Lookup_named_param_def <
internal_np::allow_move_functor_t,
NamedParameters,
CGAL_NP_CLASS,
internal::Allow_all_moves// default
> ::type Shall_move;
Shall_move shall_move = choose_parameter(get_parameter(np, internal_np::allow_move_functor),
@ -244,15 +263,41 @@ void tangential_relaxation(const VertexRange& vertices,
{
const Vector_3& vn = vnormals.at(v);
Vector_3 move = CGAL::NULL_VECTOR;
unsigned int star_size = 0;
for(halfedge_descriptor h :interior_hedges)
if constexpr (std::is_same_v<SizingFunction, Uniform_sizing_field<TriangleMesh, VPMap>>)
{
move = move + Vector_3(get(vpm, v), get(vpm, source(h, tm)));
++star_size;
unsigned int star_size = 0;
for(halfedge_descriptor h :interior_hedges)
{
move = move + Vector_3(get(vpm, v), get(vpm, source(h, tm)));
++star_size;
}
CGAL_assertion(star_size > 0); //isolated vertices have already been discarded
move = (1. / static_cast<double>(star_size)) * move;
}
CGAL_assertion(star_size > 0); //isolated vertices have already been discarded
move = (1. / static_cast<double>(star_size)) * move;
else
{
auto gt_centroid = gt.construct_centroid_3_object();
auto gt_area = gt.compute_area_3_object();
double weight = 0;
for(halfedge_descriptor h :interior_hedges)
{
// calculate weight
// need v, v1 and v2
const vertex_descriptor v1 = target(next(h, tm), tm);
const vertex_descriptor v2 = source(h, tm);
const double tri_area = gt_area(get(vpm, v), get(vpm, v1), get(vpm, v2));
const double face_weight = tri_area
/ (1. / 3. * (sizing.at(v)
+ sizing.at(v1)
+ sizing.at(v2)));
weight += face_weight;
const Point_3 centroid = gt_centroid(get(vpm, v), get(vpm, v1), get(vpm, v2));
move = move + Vector_3(get(vpm, v), centroid) * face_weight;
}
move = move / weight; //todo ip: what if weight ends up being close to 0?
}
barycenters.emplace_back(v, vn, get(vpm, v) + move);
}
else
@ -281,8 +326,6 @@ void tangential_relaxation(const VertexRange& vertices,
bary = squared_distance(p1, bary)<squared_distance(p2,bary)? p1:p2;
barycenters.emplace_back(v, vn, bary);
}
}
}
}
@ -312,8 +355,8 @@ void tangential_relaxation(const VertexRange& vertices,
//check that no inversion happened
double frac = 1.;
while (frac > 0.03 //5 attempts maximum
&& ( !check_normals(vp.first)
|| !shall_move(vp.first, initial_pos, get(vpm, vp.first)))) //if a face has been inverted
&& ( !check_normals(vp.first)
|| !shall_move(vp.first, initial_pos, get(vpm, vp.first)))) //if a face has been inverted
{
frac = 0.5 * frac;
put(vpm, vp.first, initial_pos + frac * move);//shorten the move by 2
@ -335,7 +378,8 @@ void tangential_relaxation(const VertexRange& vertices,
*/
template <class TriangleMesh,
typename CGAL_NP_TEMPLATE_PARAMETERS>
void tangential_relaxation(TriangleMesh& tm, const CGAL_NP_CLASS& np = parameters::default_values())
void tangential_relaxation(TriangleMesh& tm,
const CGAL_NP_CLASS& np = parameters::default_values())
{
tangential_relaxation(vertices(tm), tm, np);
}

View File

@ -84,6 +84,8 @@ if(TARGET CGAL::Eigen3_support)
target_link_libraries(test_interpolated_corrected_curvatures PUBLIC CGAL::Eigen3_support)
create_single_source_cgal_program("test_decimation_of_planar_patches.cpp")
target_link_libraries(test_decimation_of_planar_patches PUBLIC CGAL::Eigen3_support)
create_single_source_cgal_program("remeshing_quality_test.cpp" )
target_link_libraries(remeshing_quality_test PUBLIC CGAL::Eigen3_support)
else()
message(STATUS "NOTICE: Tests that use the Eigen library will not be compiled.")
endif()

View File

@ -0,0 +1,110 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/Polygon_mesh_processing/Adaptive_sizing_field.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <fstream>
#include <limits>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_3 Point_3;
typedef CGAL::Surface_mesh<Point_3> 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/hand.off");
Mesh mesh;
if (!PMP::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh))
{
std::cerr << "Not a valid input file." << std::endl;
return 1;
}
std::cout << "Start remeshing of " << filename
<< " (" << num_faces(mesh) << " faces)..." << std::endl;
const double tol = 0.001;
const std::pair edge_min_max{0.001, 0.5};
PMP::Adaptive_sizing_field sizing_field(tol, edge_min_max, faces(mesh), mesh);
const unsigned int nb_iter = 5;
PMP::isotropic_remeshing(
faces(mesh),
sizing_field,
mesh,
CGAL::parameters::number_of_iterations(nb_iter).number_of_relaxation_steps(3)
);
/*
* More information on quality metrics can be found here: https://ieeexplore.ieee.org/document/9167456
*/
std::cout << "Remeshing done, checking triangle quality...\n" << std::endl;
double qmin = (std::numeric_limits<double>::max)(); // minimum triangle quality
double qavg = 0.; // average quality
double min_angle = (std::numeric_limits<double>::max)(); // minimum angle
double avg_min_angle = 0.; // average minimum angle
for (auto face : mesh.faces())
{
if (PMP::is_degenerate_triangle_face(face, mesh))
{
std::cout << "Found degenerate triangle!" << std::endl;
continue;
}
// Calculate Q(t) triangle quality indicator
std::vector<Mesh::Point> pts; pts.reserve(3);
for (auto vx : mesh.vertices_around_face(mesh.halfedge(face)))
pts.push_back(mesh.point(vx));
double half_perim = 0.; // half-perimeter
double lmax = 0.; // longest edge
for (int i = 0; i < 3; ++i)
{
const double length = CGAL::sqrt(CGAL::squared_distance(pts[i], pts[(i + 1) % 2]));
half_perim += length;
if (length > lmax) lmax = length;
}
half_perim /= 2.;
const double area = CGAL::sqrt(CGAL::squared_area(pts[0], pts[1], pts[2]));
const double face_quality = 6. / CGAL::sqrt(3.) * area / half_perim / lmax;
qavg += face_quality;
if (face_quality < qmin) qmin = face_quality;
// Calculate minimum angle
const auto v0 = pts[1] - pts[0];
const auto v1 = pts[2] - pts[0];
const auto v2 = pts[2] - pts[1];
const double dotp0 = CGAL::scalar_product(v0,v1);
const double angle0 = acos(dotp0 / (sqrt(v0.squared_length()) * sqrt(v1.squared_length())));
const double dotp1 = CGAL::scalar_product(-v0, v2);
const double angle1 = acos(dotp1 / (sqrt(v0.squared_length()) * sqrt(v2.squared_length())));
const double angle2 = CGAL_PI - (angle0 + angle1);
double curr_min_angle = angle1;
if (angle1 < curr_min_angle) curr_min_angle = angle1;
if (angle2 < curr_min_angle) curr_min_angle = angle2;
avg_min_angle += curr_min_angle;
if (curr_min_angle < min_angle) min_angle = curr_min_angle;
}
qavg /= mesh.number_of_faces();
avg_min_angle /= mesh.number_of_faces();
std::cout << "Mesh size: " << mesh.number_of_faces() << std::endl;
std::cout << "Average quality: " << qavg << std::endl;
std::cout << "Minimum quality: " << qmin << std::endl;
std::cout << "Average minimum angle: " << avg_min_angle * 180. / CGAL_PI
<< " deg" << std::endl;
std::cout << "Minimum angle: " << min_angle * 180. / CGAL_PI
<< " deg" << std::endl;
return 0;
}

View File

@ -3,7 +3,7 @@
<class>DisplayPropertyWidget</class>
<widget class="QDockWidget" name="DisplayPropertyWidget">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="geometry">
<rect>
@ -39,9 +39,18 @@
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="propertyGroup">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Property</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>6</number>
@ -82,148 +91,47 @@
</item>
</widget>
</item>
<item>
<widget class="QSlider" name="expandingRadiusSlider">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="tracking">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
<property name="invertedControls">
<bool>false</bool>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="expandingRadiusLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Expanding Radius: 0</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="ColoringGroup" columnstretch="50,50">
<item row="0" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="coloringChoiceGroup">
<property name="title">
<string>Color Visualization</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="50,50">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="8" column="0">
<widget class="QRadioButton" name="randomColorsRadioButton">
<property name="text">
<string>Random colors</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="minColorButton">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="initColorLabel">
<property name="text">
<string>First color</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="maxColorLabel">
<property name="text">
<string>Max color</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QRadioButton" name="colorRampRadioButton">
<property name="text">
<string>Color Ramp</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QPushButton" name="initColorButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="maxColorButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="minColorLabel">
<property name="text">
<string>Min color</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1" rowspan="3">
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>236</width>
<height>397</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="legendLabel">
<property name="text">
<string>RAMP DISPLAYING</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item row="11" column="0">
<widget class="QGroupBox" name="extremeValuesGroup">
<property name="enabled">
@ -287,6 +195,148 @@
</layout>
</widget>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="ColoringGroup" columnstretch="0,0">
<item row="6" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="1" rowspan="3">
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>236</width>
<height>335</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="legendLabel">
<property name="text">
<string>RAMP DISPLAYING</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="5" column="0">
<widget class="QGroupBox" name="coloringChoiceGroup">
<property name="title">
<string>Color Visualization</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="50,50">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="5" column="1">
<widget class="QPushButton" name="maxColorButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="maxColorLabel">
<property name="text">
<string>Max color</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="minColorButton">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QRadioButton" name="colorRampRadioButton">
<property name="text">
<string>Color Ramp</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QPushButton" name="initColorButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="minColorLabel">
<property name="text">
<string>Min color</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="initColorLabel">
<property name="text">
<string>First color</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QRadioButton" name="randomColorsRadioButton">
<property name="text">
<string>Random colors</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<layout class="QGridLayout" name="gridLayout_6"/>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>

View File

@ -28,11 +28,11 @@
#include <QAction>
#include <QApplication>
#include <QColor>
#include <QSlider>
#include <QColorDialog>
#include <QInputDialog>
#include <QMainWindow>
#include <QMessageBox>
#include <QSlider>
#include <QObject>
#include <QPalette>
#include <QStyleFactory>
@ -47,10 +47,10 @@
#define ARBITRARY_DBL_MIN 1.0E-17
#define ARBITRARY_DBL_MAX 1.0E+17
namespace PMP = CGAL::Polygon_mesh_processing;
using namespace CGAL::Three;
Viewer_interface* (&getActiveViewer)() = Three::activeViewer;
class DockWidget
@ -89,6 +89,9 @@ private:
double gI = 1.;
double bI = 0.;
double expand_radius = 0.;
double maxEdgeLength = -1.;
Color_ramp color_ramp;
std::vector<QColor> color_map;
QPixmap legend;
@ -102,10 +105,10 @@ private:
MIN_VALUE,
MAX_VALUE
};
enum CurvatureType {
enum CurvatureType
{
MEAN_CURVATURE,
GAUSSIAN_CURVATURE
GAUSSIAN_CURVATURE,
};
public:
@ -205,6 +208,9 @@ public:
this, &Display_property_plugin::on_zoomToMinButton_pressed);
connect(dock_widget->zoomToMaxButton, &QPushButton::pressed,
this, &Display_property_plugin::on_zoomToMaxButton_pressed);
connect(dock_widget->expandingRadiusSlider, &QSlider::valueChanged,
this, &Display_property_plugin::setExpandingRadius);
}
private Q_SLOTS:
@ -247,14 +253,6 @@ private:
dock_widget->maxBox->setRange(0, 360);
dock_widget->maxBox->setValue(0);
}
else if (property_name == "Interpolated Corrected Gaussian Curvature" ||
property_name == "Interpolated Corrected Mean Curvature")
{
dock_widget->minBox->setRange(-1000, 1000);
dock_widget->minBox->setValue(0);
dock_widget->maxBox->setRange(-1000, 1000);
dock_widget->maxBox->setValue(0);
}
else if(property_name == "Scaled Jacobian")
{
dock_widget->minBox->setRange(-1000, 1000);
@ -269,6 +267,20 @@ private:
dock_widget->maxBox->setRange(-1000, 1000);
dock_widget->maxBox->setValue(0);
}
else if (property_name == "Interpolated Corrected Mean Curvature")
{
dock_widget->minBox->setRange(-99999999, 99999999);
dock_widget->minBox->setValue(0);
dock_widget->maxBox->setRange(-99999999, 99999999);
dock_widget->maxBox->setValue(0);
}
else if (property_name == "Interpolated Corrected Gaussian Curvature")
{
dock_widget->minBox->setRange(-99999999, 99999999);
dock_widget->minBox->setValue(0);
dock_widget->maxBox->setRange(-99999999, 99999999);
dock_widget->maxBox->setValue(0);
}
else
{
dock_widget->minBox->setRange(-99999999, 99999999);
@ -500,6 +512,15 @@ private Q_SLOTS:
{
dock_widget->setEnabled(true);
disableExtremeValues(); // only available after coloring
// Curvature property-specific slider
const std::string& property_name = dock_widget->propertyBox->currentText().toStdString();
const bool is_curvature_property = (property_name == "Interpolated Corrected Mean Curvature" ||
property_name == "Interpolated Corrected Gaussian Curvature");
dock_widget->expandingRadiusLabel->setVisible(is_curvature_property);
dock_widget->expandingRadiusSlider->setVisible(is_curvature_property);
dock_widget->expandingRadiusLabel->setEnabled(is_curvature_property);
dock_widget->expandingRadiusSlider->setEnabled(is_curvature_property);
}
else // no or broken property
{
@ -527,15 +548,10 @@ private:
{
CGAL_assertion(static_cast<std::size_t>(dock_widget->propertyBox->count()) == property_simplex_types.size());
const int property_index = dock_widget->propertyBox->currentIndex();
// leave it flat if it was, otherwise set to flat+edges
if(sm_item->renderingMode() != Flat && sm_item->renderingMode() != FlatPlusEdges && property_simplex_types.at(property_index) == Property_simplex_type::FACE)
if(sm_item->renderingMode() != Flat && sm_item->renderingMode() != FlatPlusEdges)
sm_item->setRenderingMode(FlatPlusEdges);
if(sm_item->renderingMode() != Gouraud && sm_item->renderingMode() != GouraudPlusEdges && property_simplex_types.at(property_index) == Property_simplex_type::VERTEX)
sm_item->setRenderingMode(GouraudPlusEdges);
const std::string& property_name = dock_widget->propertyBox->currentText().toStdString();
if(property_name == "Smallest Angle Per Face")
{
@ -555,11 +571,13 @@ private:
}
else if(property_name == "Interpolated Corrected Mean Curvature")
{
displayCurvature(sm_item, MEAN_CURVATURE);
displayInterpolatedCurvatureMeasure(sm_item, MEAN_CURVATURE);
sm_item->setRenderingMode(Gouraud);
}
else if(property_name == "Interpolated Corrected Gaussian Curvature")
{
displayCurvature(sm_item, GAUSSIAN_CURVATURE);
displayInterpolatedCurvatureMeasure(sm_item, GAUSSIAN_CURVATURE);
sm_item->setRenderingMode(Gouraud);
}
else
{
@ -663,8 +681,8 @@ private:
removeDisplayPluginProperty(item, "f:display_plugin_largest_angle");
removeDisplayPluginProperty(item, "f:display_plugin_scaled_jacobian");
removeDisplayPluginProperty(item, "f:display_plugin_area");
removeDisplayPluginProperty(item, "f:display_plugin_mean_curvature");
removeDisplayPluginProperty(item, "f:display_plugin_gaussian_curvature");
removeDisplayPluginProperty(item, "v:display_plugin_interpolated_corrected_mean_curvature");
removeDisplayPluginProperty(item, "v:display_plugin_interpolated_corrected_Gaussian_curvature");
}
void displayExtremumAnglePerFace(Scene_surface_mesh_item* sm_item,
@ -753,7 +771,7 @@ private:
halfedge_descriptor local_border_h = opposite(halfedge(local_f, local_smesh), local_smesh);
CGAL_assertion(is_border(local_border_h, local_smesh));
CGAL::Polygon_mesh_processing::triangulate_faces(local_smesh);
PMP::triangulate_faces(local_smesh);
double extremum_angle_in_face = ARBITRARY_DBL_MAX;
halfedge_descriptor local_border_end_h = local_border_h;
@ -806,7 +824,7 @@ private:
const SMesh& mesh) const
{
if(CGAL::is_triangle(halfedge(f, mesh), mesh))
return CGAL::Polygon_mesh_processing::face_area(f, mesh);
return PMP::face_area(f, mesh);
auto vpm = get(boost::vertex_point, mesh);
@ -822,8 +840,8 @@ private:
}
CGAL::Euler::add_face(local_vertices, local_smesh);
CGAL::Polygon_mesh_processing::triangulate_faces(local_smesh);
return CGAL::Polygon_mesh_processing::area(local_smesh);
PMP::triangulate_faces(local_smesh);
return PMP::area(local_smesh);
}
void displayArea(Scene_surface_mesh_item* sm_item)
@ -845,30 +863,113 @@ private:
displaySMProperty<face_descriptor>("f:display_plugin_area", *sm);
}
void displayCurvature(Scene_surface_mesh_item* sm_item,
const CurvatureType curvature_type)
private Q_SLOTS:
void setExpandingRadius()
{
SMesh* sm = sm_item->face_graph();
if(sm == nullptr)
double sliderMin = dock_widget->expandingRadiusSlider->minimum();
double sliderMax = dock_widget->expandingRadiusSlider->maximum() - sliderMin;
double val = dock_widget->expandingRadiusSlider->value() - sliderMin;
sliderMin = 0;
Scene_item* item = scene->item(scene->mainSelectionIndex());
Scene_surface_mesh_item* sm_item = qobject_cast<Scene_surface_mesh_item*>(item);
if(sm_item == nullptr)
return;
bool not_initialized;
std::string tied_string = (curvature_type == MEAN_CURVATURE) ?
"v:display_plugin_mean_curvature" : "v:display_plugin_gaussian_curvature";
SMesh& smesh = *(sm_item->face_graph());
SMesh::Property_map<vertex_descriptor, double> vcurvature;
std::tie(vcurvature, not_initialized) = sm->add_property_map<vertex_descriptor, double>(tied_string, 0);
auto vpm = get(CGAL::vertex_point, smesh);
if (curvature_type == MEAN_CURVATURE)
// @todo use the upcoming PMP::longest_edge
if(maxEdgeLength < 0)
{
CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures(*sm, CGAL::parameters::vertex_mean_curvature(vcurvature));
}
else if (curvature_type == GAUSSIAN_CURVATURE)
{
CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures(*sm, CGAL::parameters::vertex_Gaussian_curvature(vcurvature));
auto edge_range = CGAL::edges(smesh);
if(num_edges(smesh) == 0)
{
expand_radius = 0;
dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius: %1").arg(expand_radius));
return;
}
auto eit = std::max_element(edge_range.begin(), edge_range.end(),
[&, vpm, smesh](auto l, auto r)
{
auto res = EPICK().compare_squared_distance_3_object()(
get(vpm, source((l), smesh)),
get(vpm, target((l), smesh)),
get(vpm, source((r), smesh)),
get(vpm, target((r), smesh)));
return (res == CGAL::SMALLER);
});
CGAL_assertion(eit != edge_range.end());
maxEdgeLength = PMP::edge_length(*eit, smesh);
}
displaySMProperty<vertex_descriptor>(tied_string, *sm);
double outMax = 5 * maxEdgeLength, base = 1.2;
expand_radius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1);
dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius: %1").arg(expand_radius));
}
private:
void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item,
CurvatureType mu_index)
{
if(mu_index != MEAN_CURVATURE && mu_index != GAUSSIAN_CURVATURE)
return;
std::string tied_string = (mu_index == MEAN_CURVATURE) ? "v:display_plugin_interpolated_corrected_mean_curvature"
: "v:display_plugin_interpolated_corrected_Gaussian_curvature";
SMesh& smesh = *item->face_graph();
const auto vnm = smesh.property_map<vertex_descriptor, EPICK::Vector_3>("v:normal_before_perturbation").first;
const bool vnm_exists = smesh.property_map<vertex_descriptor, EPICK::Vector_3>("v:normal_before_perturbation").second;
// compute once and store the value per vertex
bool non_init;
SMesh::Property_map<vertex_descriptor, double> mu_i_map;
std::tie(mu_i_map, non_init) = smesh.add_property_map<vertex_descriptor, double>(tied_string, 0);
if(non_init)
{
if(vnm_exists)
{
if(mu_index == MEAN_CURVATURE)
{
PMP::interpolated_corrected_curvatures(smesh,
CGAL::parameters::vertex_mean_curvature_map(mu_i_map)
.ball_radius(expand_radius)
.vertex_normal_map(vnm));
}
else
{
PMP::interpolated_corrected_curvatures(smesh,
CGAL::parameters::vertex_Gaussian_curvature_map(mu_i_map)
.ball_radius(expand_radius)
.vertex_normal_map(vnm));
}
}
else
{
if(mu_index == MEAN_CURVATURE)
{
PMP::interpolated_corrected_curvatures(smesh,
CGAL::parameters::vertex_mean_curvature_map(mu_i_map)
.ball_radius(expand_radius));
}
else
{
PMP::interpolated_corrected_curvatures(smesh,
CGAL::parameters::vertex_Gaussian_curvature_map(mu_i_map)
.ball_radius(expand_radius));
}
}
}
displaySMProperty<vertex_descriptor>(tied_string, smesh);
}
private:
@ -1027,9 +1128,9 @@ private:
else if(property_name == "Face Area")
zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, "f:display_plugin_area", extremum);
else if(property_name == "Interpolated Corrected Mean Curvature")
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_mean_curvature", extremum);
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_interpolated_corrected_mean_curvature", extremum);
else if(property_name == "Interpolated Corrected Gaussian Curvature")
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_gaussian_curvature", extremum);
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_interpolated_corrected_Gaussian_curvature", extremum);
else if(property_simplex_types.at(property_index) == Property_simplex_type::VERTEX)
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, property_name, extremum);
else if(property_simplex_types.at(property_index) == Property_simplex_type::FACE)
@ -1312,7 +1413,7 @@ scaled_jacobian(const face_descriptor f,
for(std::size_t i=0; i<edges.size(); ++i)
corner_normals.push_back(CGAL::cross_product(edges[i], edges[(i+1)%(edges.size())]));
EPICK::Vector_3 unit_center_normal = CGAL::Polygon_mesh_processing::compute_face_normal(f, mesh);
EPICK::Vector_3 unit_center_normal = PMP::compute_face_normal(f, mesh);
for(std::size_t i=0; i<corner_areas.size(); ++i)
corner_areas[i] = unit_center_normal*corner_normals[i];
@ -1365,9 +1466,8 @@ isSMPropertyScalar(const std::string& name,
name == "f:display_plugin_largest_angle" ||
name == "f:display_plugin_scaled_jacobian" ||
name == "f:display_plugin_area" ||
name == "f:display_plugin_mean_curvature" ||
name == "f:display_plugin_gaussian_curvature")
name == "v:display_plugin_interpolated_corrected_mean_curvature" ||
name == "v:display_plugin_interpolated_corrected_Gaussian_curvature")
return false;
// the dispatch function does the filtering we want: if it founds a property

View File

@ -135,14 +135,19 @@ qt5_wrap_ui( repairUI_FILES RemoveNeedlesDialog.ui SelfSnapDialog.ui)
polyhedron_demo_plugin(repair_polyhedron_plugin Repair_polyhedron_plugin ${repairUI_FILES} KEYWORDS PMP)
target_link_libraries(repair_polyhedron_plugin PUBLIC scene_points_with_normal_item scene_surface_mesh_item)
qt5_wrap_ui(isotropicRemeshingUI_FILES Isotropic_remeshing_dialog.ui)
polyhedron_demo_plugin(isotropic_remeshing_plugin Isotropic_remeshing_plugin
${isotropicRemeshingUI_FILES} KEYWORDS PMP)
target_link_libraries(isotropic_remeshing_plugin PUBLIC scene_surface_mesh_item
scene_selection_item)
if(TARGET CGAL::Eigen3_support)
qt5_wrap_ui(isotropicRemeshingUI_FILES Isotropic_remeshing_dialog.ui)
polyhedron_demo_plugin(isotropic_remeshing_plugin Isotropic_remeshing_plugin
${isotropicRemeshingUI_FILES} KEYWORDS PMP)
target_link_libraries(isotropic_remeshing_plugin PUBLIC scene_surface_mesh_item
scene_selection_item CGAL::Eigen3_support)
if(TARGET CGAL::TBB_support)
target_link_libraries(isotropic_remeshing_plugin PUBLIC CGAL::TBB_support)
if(TARGET CGAL::TBB_support)
target_link_libraries(isotropic_remeshing_plugin PUBLIC CGAL::TBB_support)
endif()
else()
message(STATUS "NOTICE: Eigen 3.1 (or greater) was not found. Isotropic remeshing plugin will not be available.")
endif()
polyhedron_demo_plugin(distance_plugin Distance_plugin KEYWORDS PMP)

View File

@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>376</width>
<height>369</height>
<width>381</width>
<height>620</height>
</rect>
</property>
<property name="windowTitle">
@ -59,18 +59,29 @@
<property name="title">
<string>Isotropic remeshing</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0,0" columnstretch="0,1">
<item row="6" column="0">
<widget class="QLabel" name="preserveDuplicates_label">
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" columnstretch="0,0">
<item row="15" column="1">
<widget class="QCheckBox" name="curvSmooth_checkbox">
<property name="text">
<string>Preserve duplicated edges</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<string/>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="13" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="minEdgeLength_label">
<property name="text">
<string>Minimum edge length</string>
</property>
</widget>
</item>
<item row="25" column="1">
<widget class="QCheckBox" name="smooth1D_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="22" column="1">
<widget class="QSpinBox" name="nbSmoothing_spinbox">
<property name="minimum">
<number>0</number>
@ -83,24 +94,106 @@
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="preserveDuplicates_checkbox">
<item row="12" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="errorTol_label">
<property name="text">
<string/>
<string>Error tolerance</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="edgeLength_label">
<item row="24" column="1">
<widget class="QCheckBox" name="protect_checkbox">
<property name="text">
<string>Target edge length</string>
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="DoubleEdit" name="errorTol_edit">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>0.00</string>
</property>
</widget>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>-1</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>11</number>
</property>
<property name="rightMargin">
<number>14</number>
</property>
<item alignment="Qt::AlignLeft">
<widget class="QLabel" name="edgeSizing_type_label">
<property name="text">
<string>Edge sizing</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignLeft">
<widget class="QComboBox" name="edgeSizing_type_combo_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>168</width>
<height>16777215</height>
</size>
</property>
<item>
<property name="text">
<string>Uniform</string>
</property>
</item>
<item>
<property name="text">
<string>Adaptive</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item row="17" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="25" column="0">
<widget class="QLabel" name="smooth1D_label">
<property name="text">
<string>Allow 1D smoothing along borders</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="24" column="0">
<widget class="QLabel" name="protect_label">
<property name="text">
<string>Protect borders/selected edges</string>
@ -110,17 +203,24 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="nbSmoothing_label">
<item row="26" column="1">
<widget class="QCheckBox" name="preserveDuplicates_checkbox">
<property name="text">
<string>Number of Smoothing iterations</string>
<string/>
</property>
</widget>
</item>
<item row="26" column="0">
<widget class="QLabel" name="preserveDuplicates_label">
<property name="text">
<string>Preserve duplicated edges</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="21" column="1">
<widget class="QSpinBox" name="nbIterations_spinbox">
<property name="minimumSize">
<size>
@ -133,17 +233,7 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="smooth1D_label">
<property name="text">
<string>Allow 1D smoothing along borders</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<item row="21" column="0">
<widget class="QLabel" name="nbIterations_label">
<property name="text">
<string>Number of Main iterations</string>
@ -156,24 +246,7 @@
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="smooth1D_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="protect_checkbox">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="23" column="0" colspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -184,12 +257,22 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<height>24</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<item row="11" column="0">
<widget class="QLabel" name="edgeLength_label">
<property name="text">
<string>Target edge length</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="DoubleEdit" name="edgeLength_dspinbox">
<property name="inputMethodHints">
<set>Qt::ImhNone</set>
@ -205,6 +288,67 @@
</property>
</widget>
</item>
<item row="14" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="maxEdgeLength_label">
<property name="text">
<string>Maximum edge length</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="DoubleEdit" name="minEdgeLength_edit">
<property name="placeholderText">
<string>0.00</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="DoubleEdit" name="maxEdgeLength_edit">
<property name="placeholderText">
<string>0.00</string>
</property>
</widget>
</item>
<item row="22" column="0">
<widget class="QLabel" name="nbSmoothing_label">
<property name="text">
<string>Number of Smoothing iterations</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="curvSmoothBallR_label">
<property name="text">
<string>Ball radius</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="curvSmooth_label">
<property name="text">
<string>Curvature smoothing</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="DoubleEdit" name="curvSmoothBallR_edit">
<property name="placeholderText">
<string>-1</string>
</property>
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -14,6 +14,7 @@
#include <CGAL/iterator.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/Polygon_mesh_processing/Adaptive_sizing_field.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
#include <CGAL/utility.h>
#include <CGAL/property_map.h>
@ -293,9 +294,14 @@ public:
PMP::triangulate_face(f_and_p.first, *f_and_p.second);
}
void do_split_edges(Scene_polyhedron_selection_item* selection_item,
void do_split_edges(const int edge_sizing_type,
Scene_polyhedron_selection_item* selection_item,
SMesh& pmesh,
double target_length)
const double target_length,
const double error_tol,
const double min_length,
const double max_length,
const double curv_ball_r)
{
std::vector<edge_descriptor> p_edges;
for(edge_descriptor e : edges(pmesh))
@ -313,12 +319,33 @@ public:
}
}
if (!p_edges.empty())
CGAL::Polygon_mesh_processing::split_long_edges(
p_edges
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::geom_traits(EPICK())
{
if (edge_sizing_type == 0)
{
CGAL::Polygon_mesh_processing::split_long_edges(
p_edges
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::geom_traits(EPICK())
.edge_is_constrained_map(selection_item->constrained_edges_pmap()));
}
else if (edge_sizing_type == 1)
{
std::pair<double, double> edge_min_max{min_length, max_length};
CGAL::Polygon_mesh_processing::Adaptive_sizing_field<FaceGraph> adaptive_sizing(
error_tol
, edge_min_max
, faces(*selection_item->polyhedron())
, *selection_item->polyhedron()
, CGAL::parameters::ball_radius(curv_ball_r));
CGAL::Polygon_mesh_processing::split_long_edges(
p_edges
, adaptive_sizing
, *selection_item->polyhedron()
, CGAL::parameters::geom_traits(EPICK())
.edge_is_constrained_map(selection_item->constrained_edges_pmap()));
}
}
else
std::cout << "No selected or boundary edges to be split" << std::endl;
}
@ -358,13 +385,18 @@ public Q_SLOTS:
return;
}
int edge_sizing_type = ui.edgeSizing_type_combo_box->currentIndex();
bool edges_only = ui.splitEdgesOnly_checkbox->isChecked();
bool preserve_duplicates = ui.preserveDuplicates_checkbox->isChecked();
double target_length = ui.edgeLength_dspinbox->value();
double error_tol = ui.errorTol_edit->value();
double min_length = ui.minEdgeLength_edit->value();
double max_length = ui.maxEdgeLength_edit->value();
unsigned int nb_iter = ui.nbIterations_spinbox->value();
unsigned int nb_smooth = ui.nbSmoothing_spinbox->value();
bool protect = ui.protect_checkbox->isChecked();
bool smooth_features = ui.smooth1D_checkbox->isChecked();
double curv_ball_r = ui.curvSmoothBallR_edit->value();
// wait cursor
QApplication::setOverrideCursor(Qt::WaitCursor);
@ -396,7 +428,8 @@ public Q_SLOTS:
{
if (edges_only)
{
do_split_edges(selection_item, pmesh, target_length);
do_split_edges(edge_sizing_type, selection_item, pmesh,
target_length, error_tol, min_length, max_length, curv_ball_r);
}
else //not edges_only
{
@ -404,9 +437,8 @@ public Q_SLOTS:
!CGAL::Polygon_mesh_processing::internal::constraints_are_short_enough(
*selection_item->polyhedron(),
selection_item->constrained_edges_pmap(),
get(CGAL::vertex_point, *selection_item->polyhedron()),
CGAL::Constant_property_map<face_descriptor, std::size_t>(1),
4. / 3. * target_length))
CGAL::Polygon_mesh_processing::Uniform_sizing_field( 4. / 3. * target_length, pmesh)))
{
QApplication::restoreOverrideCursor();
//If facets are selected, splitting edges will add facets that won't be selected, and it will mess up the rest.
@ -432,7 +464,8 @@ public Q_SLOTS:
}
else
{
do_split_edges(selection_item, pmesh, target_length);
do_split_edges(edge_sizing_type, selection_item, pmesh,
target_length, error_tol, min_length, max_length, curv_ball_r);
}
}
@ -457,28 +490,62 @@ public Q_SLOTS:
}
}
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(*selection_item->polyhedron())
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(*selection_item->polyhedron())
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
);
if (edge_sizing_type == 0)
{
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(*selection_item->polyhedron())
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(*selection_item->polyhedron())
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
);
}
else if (edge_sizing_type == 1)
{
std::pair<double, double> edge_min_max{min_length, max_length};
PMP::Adaptive_sizing_field<Face_graph> adaptive_sizing_field(error_tol
, edge_min_max
, faces(*selection_item->polyhedron())
, *selection_item->polyhedron()
, CGAL::parameters::ball_radius(curv_ball_r));
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(*selection_item->polyhedron())
, adaptive_sizing_field
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(*selection_item->polyhedron())
, adaptive_sizing_field
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
);
}
}
else //selected_facets not empty
{
@ -507,27 +574,62 @@ public Q_SLOTS:
}
}
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(selection_item->selected_facets
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(selection_item->selected_facets
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap()));
if (edge_sizing_type == 0)
{
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(selection_item->selected_facets
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(selection_item->selected_facets
, target_length
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
);
}
else if (edge_sizing_type == 1)
{
std::pair<double, double> edge_min_max{min_length, max_length};
PMP::Adaptive_sizing_field<Face_graph> adaptive_sizing_field(error_tol
, edge_min_max
, faces(*selection_item->polyhedron())
, *selection_item->polyhedron()
, CGAL::parameters::ball_radius(curv_ball_r));
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(selection_item->selected_facets
, adaptive_sizing_field
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(selection_item->selected_facets
, adaptive_sizing_field
, *selection_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.edge_is_constrained_map(selection_item->constrained_edges_pmap())
.relax_constraints(smooth_features)
.number_of_relaxation_steps(nb_smooth)
.vertex_is_constrained_map(selection_item->constrained_vertices_pmap())
);
}
}
}
@ -564,6 +666,9 @@ public Q_SLOTS:
if (!edges_to_split.empty())
{
if (fpmap_valid)
{
if (edge_sizing_type == 0)
{
CGAL::Polygon_mesh_processing::split_long_edges(
edges_to_split
, target_length
@ -571,13 +676,51 @@ public Q_SLOTS:
, CGAL::parameters::geom_traits(EPICK())
. edge_is_constrained_map(eif)
. face_patch_map(fpmap));
}
else if (edge_sizing_type == 1)
{
std::pair<double, double> edge_min_max{min_length, max_length};
PMP::Adaptive_sizing_field<Face_graph> adaptive_sizing_field(error_tol
, edge_min_max
, faces(pmesh)
, pmesh
, CGAL::parameters::ball_radius(curv_ball_r));
CGAL::Polygon_mesh_processing::split_long_edges(
edges_to_split
, target_length
, pmesh
, CGAL::parameters::geom_traits(EPICK())
. edge_is_constrained_map(eif)
. face_patch_map(fpmap));
}
}
else
{
if (edge_sizing_type == 0)
{
CGAL::Polygon_mesh_processing::split_long_edges(
edges_to_split
, target_length
, pmesh
, CGAL::parameters::geom_traits(EPICK())
. edge_is_constrained_map(eif));
}
else if (edge_sizing_type == 1)
{
std::pair<double, double> edge_min_max{min_length, max_length};
PMP::Adaptive_sizing_field<Face_graph> adaptive_sizing_field(error_tol
, edge_min_max
, faces(pmesh)
, pmesh
, CGAL::parameters::ball_radius(curv_ball_r));
CGAL::Polygon_mesh_processing::split_long_edges(
edges_to_split
, adaptive_sizing_field
, pmesh
, CGAL::parameters::geom_traits(EPICK())
. edge_is_constrained_map(eif));
}
}
}
else
std::cout << "No border to be split" << std::endl;
@ -604,9 +747,8 @@ public Q_SLOTS:
!CGAL::Polygon_mesh_processing::internal::constraints_are_short_enough(
pmesh,
ecm,
get(CGAL::vertex_point, pmesh),
CGAL::Constant_property_map<face_descriptor, std::size_t>(1),
4. / 3. * target_length))
CGAL::Polygon_mesh_processing::Uniform_sizing_field(4. / 3. * target_length, pmesh)))
{
QApplication::restoreOverrideCursor();
QMessageBox::warning(mw, tr("Error"),
@ -634,10 +776,42 @@ public Q_SLOTS:
}
}
if (fpmap_valid)
if (edge_sizing_type == 0)
{
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, target_length
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.number_of_relaxation_steps(nb_smooth)
.edge_is_constrained_map(ecm)
.relax_constraints(smooth_features)
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, target_length
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.number_of_relaxation_steps(nb_smooth)
.edge_is_constrained_map(ecm)
.relax_constraints(smooth_features));
}
else if (edge_sizing_type == 1)
{
std::pair<double, double> edge_min_max{min_length, max_length};
PMP::Adaptive_sizing_field<Face_graph> adaptive_sizing_field(error_tol
, edge_min_max
, faces(*poly_item->polyhedron())
, *poly_item->polyhedron()
, CGAL::parameters::ball_radius(curv_ball_r));
if (fpmap_valid)
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, target_length
, adaptive_sizing_field
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
@ -645,16 +819,17 @@ public Q_SLOTS:
.edge_is_constrained_map(ecm)
.relax_constraints(smooth_features)
.face_patch_map(fpmap));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, target_length
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.number_of_relaxation_steps(nb_smooth)
.edge_is_constrained_map(ecm)
.relax_constraints(smooth_features));
else
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, adaptive_sizing_field
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter)
.protect_constraints(protect)
.number_of_relaxation_steps(nb_smooth)
.edge_is_constrained_map(ecm)
.relax_constraints(smooth_features));
}
//recollect sharp edges
for(edge_descriptor e : edges(pmesh))
@ -687,7 +862,11 @@ public Q_SLOTS:
{
// Remeshing parameters
bool edges_only = false, preserve_duplicates = false;
int edge_sizing_type = 0;
double target_length = 0.;
double error_tol = 0.;
double min_length = 0.;
double max_length = 0.;
unsigned int nb_iter = 1;
bool protect = false;
bool smooth_features = true;
@ -723,7 +902,11 @@ public Q_SLOTS:
edges_only = ui.splitEdgesOnly_checkbox->isChecked();
preserve_duplicates = ui.preserveDuplicates_checkbox->isChecked();
edge_sizing_type = ui.edgeSizing_type_combo_box->currentIndex();
target_length = ui.edgeLength_dspinbox->value();
error_tol = ui.errorTol_edit->value();
min_length = ui.minEdgeLength_edit->value();
max_length = ui.maxEdgeLength_edit->value();
nb_iter = ui.nbIterations_spinbox->value();
protect = ui.protect_checkbox->isChecked();
smooth_features = ui.smooth1D_checkbox->isChecked();
@ -779,14 +962,18 @@ public Q_SLOTS:
tbb::parallel_for(
tbb::blocked_range<std::size_t>(0, selection.size()),
Remesh_polyhedron_item_for_parallel_for<Remesh_polyhedron_item>(
selection, edges_to_protect, edges_only, target_length, nb_iter, protect, smooth_features));
selection, edges_to_protect, edges_only
, edge_sizing_type, target_length, error_tol
, min_length , max_length, nb_iter
, protect, smooth_features)
);
total_time = time.elapsed();
#else
Remesh_polyhedron_item remesher(edges_only,
target_length, nb_iter, protect, smooth_features);
Remesh_polyhedron_item remesher(edges_only, edge_sizing_type,
target_length, error_tol, min_length, max_length, nb_iter, protect, smooth_features);
for(Scene_facegraph_item* poly_item : selection)
{
QElapsedTimer time;
@ -823,11 +1010,16 @@ private:
typedef boost::graph_traits<FaceGraph>::halfedge_descriptor halfedge_descriptor;
typedef boost::graph_traits<FaceGraph>::face_descriptor face_descriptor;
int edge_sizing_type_;
bool edges_only_;
double target_length_;
double error_tol_;
double min_length_;
double max_length_;
unsigned int nb_iter_;
bool protect_;
bool smooth_features_;
double curv_ball_r_;
protected:
void remesh(Scene_facegraph_item* poly_item,
@ -846,25 +1038,62 @@ private:
for(halfedge_descriptor h : border)
border_edges.push_back(edge(h, *poly_item->polyhedron()));
if (edge_sizing_type_ == 0)
{
CGAL::Polygon_mesh_processing::split_long_edges(
border_edges
, target_length_
, *poly_item->polyhedron());
}
else if (edge_sizing_type_ == 1)
{
std::pair<double, double> edge_min_max{min_length_, max_length_};
PMP::Adaptive_sizing_field<Face_graph> adaptive_sizing_field(error_tol_
, edge_min_max
, faces(*poly_item->polyhedron())
, *poly_item->polyhedron()
, CGAL::parameters::ball_radius(curv_ball_r_));
CGAL::Polygon_mesh_processing::split_long_edges(
border_edges
, target_length_
, *poly_item->polyhedron());
}
}
else
{
std::cout << "Isotropic remeshing of "
<< poly_item->name().toStdString() << " started..." << std::endl;
Scene_polyhedron_selection_item::Is_constrained_map<Edge_set> ecm(&edges_to_protect);
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, target_length_
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter_)
.protect_constraints(protect_)
.edge_is_constrained_map(ecm)
.face_patch_map(get(CGAL::face_patch_id_t<int>(), *poly_item->polyhedron()))
.relax_constraints(smooth_features_));
if (edge_sizing_type_ == 0)
{
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, target_length_
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter_)
.protect_constraints(protect_)
.edge_is_constrained_map(ecm)
.face_patch_map(get(CGAL::face_patch_id_t<int>(), *poly_item->polyhedron()))
.relax_constraints(smooth_features_));
}
else
{
std::pair<double, double> edge_min_max{min_length_, max_length_};
PMP::Adaptive_sizing_field<Face_graph> adaptive_sizing_field(error_tol_
, edge_min_max
, faces(*poly_item->polyhedron())
, *poly_item->polyhedron()
, CGAL::parameters::ball_radius(curv_ball_r_));
CGAL::Polygon_mesh_processing::isotropic_remeshing(
faces(*poly_item->polyhedron())
, target_length_
, *poly_item->polyhedron()
, CGAL::parameters::number_of_iterations(nb_iter_)
.protect_constraints(protect_)
.edge_is_constrained_map(ecm)
.face_patch_map(get(CGAL::face_patch_id_t<int>(), *poly_item->polyhedron()))
.relax_constraints(smooth_features_));
}
std::cout << "Isotropic remeshing of "
<< poly_item->name().toStdString() << " done." << std::endl;
}
@ -873,20 +1102,32 @@ private:
public:
Remesh_polyhedron_item(
const bool edges_only,
const int edge_sizing_type,
const double target_length,
const double error_tol,
const double min_length,
const double max_length,
const unsigned int nb_iter,
const bool protect,
const bool smooth_features)
: edges_only_(edges_only)
: edge_sizing_type_(edge_sizing_type)
, edges_only_(edges_only)
, target_length_(target_length)
, error_tol_(error_tol)
, min_length_(min_length)
, max_length_(max_length)
, nb_iter_(nb_iter)
, protect_(protect)
, smooth_features_(smooth_features)
{}
Remesh_polyhedron_item(const Remesh_polyhedron_item& remesh)
: edges_only_(remesh.edges_only_)
: edge_sizing_type_(remesh.edge_sizing_type_)
, edges_only_(remesh.edges_only_)
, target_length_(remesh.target_length_)
, error_tol_(remesh.error_tol_)
, min_length_(remesh.min_length_)
, max_length_(remesh.max_length_)
, nb_iter_(remesh.nb_iter_)
, protect_(remesh.protect_)
, smooth_features_(remesh.smooth_features_)
@ -913,11 +1154,17 @@ private:
const std::vector<Scene_facegraph_item*>& selection,
std::map<FaceGraph*,Edge_set >& edges_to_protect,
const bool edges_only,
const int edge_sizing_type,
const double target_length,
const double error_tol,
const double min_length,
const double max_length,
const unsigned int nb_iter,
const bool protect,
const bool smooth_features)
: RemeshFunctor(edges_only, target_length, nb_iter, protect, smooth_features)
: RemeshFunctor(edges_only, edge_sizing_type, target_length
, error_tol, min_length, max_length
, nb_iter, protect, smooth_features)
, selection_(selection), edges_to_protect_(edges_to_protect)
{}
@ -986,6 +1233,55 @@ public Q_SLOTS:
}
}
void on_edgeSizing_type_combo_box_changed(int index)
{
if (index == 0)
{
ui.edgeLength_label->show();
ui.edgeLength_dspinbox->show();
ui.errorTol_label->hide();
ui.errorTol_edit->hide();
ui.minEdgeLength_label->hide();
ui.minEdgeLength_edit->hide();
ui.maxEdgeLength_label->hide();
ui.maxEdgeLength_edit->hide();
ui.curvSmooth_checkbox->hide();
ui.curvSmooth_label->hide();
ui.curvSmoothBallR_edit->hide();
ui.curvSmoothBallR_label->hide();
}
else if (index == 1)
{
ui.edgeLength_label->hide();
ui.edgeLength_dspinbox->hide();
ui.errorTol_label->show();
ui.errorTol_edit->show();
ui.minEdgeLength_label->show();
ui.minEdgeLength_edit->show();
ui.maxEdgeLength_label->show();
ui.maxEdgeLength_edit->show();
ui.curvSmooth_checkbox->show();
ui.curvSmooth_label->show();
ui.curvSmoothBallR_edit->show();
ui.curvSmoothBallR_label->show();
}
}
void update_after_curvSmooth_click()
{
if (ui.curvSmooth_checkbox->isChecked())
{
ui.curvSmoothBallR_label->setEnabled(true);
ui.curvSmoothBallR_edit->setEnabled(true);
}
else
{
ui.curvSmoothBallR_label->setEnabled(false);
ui.curvSmoothBallR_edit->setValue(-1);
ui.curvSmoothBallR_edit->setEnabled(false);
}
}
public:
void
initialize_remeshing_dialog(QDialog* dialog,
@ -1004,6 +1300,9 @@ public:
connect(ui.protect_checkbox, SIGNAL(clicked(bool)), this, SLOT(update_after_protect_checkbox_click()));
connect(ui.splitEdgesOnly_checkbox, SIGNAL(clicked(bool)), this, SLOT(update_after_splitEdgesOnly_click()));
connect(ui.edgeSizing_type_combo_box, SIGNAL(currentIndexChanged(int)),
this, SLOT(on_edgeSizing_type_combo_box_changed(int)));
connect(ui.curvSmooth_checkbox, SIGNAL(clicked(bool)), this, SLOT(update_after_curvSmooth_click()));
//Set default parameters
Scene_interface::Bbox bbox = poly_item != nullptr ? poly_item->bbox()
@ -1025,19 +1324,34 @@ public:
ui.edgeLength_dspinbox->setValue(0.05 * diago_length);
ui.errorTol_edit->setValue(0.001 * diago_length);
ui.minEdgeLength_edit->setValue(0.001 * diago_length);
ui.maxEdgeLength_edit->setValue(0.5 * diago_length);
std::ostringstream oss;
oss << "Diagonal length of the Bbox of the selection to remesh is ";
oss << diago_length << "." << std::endl;
oss << "Default is 5% of it" << std::endl;
ui.edgeLength_dspinbox->setToolTip(QString::fromStdString(oss.str()));
std::string diag_general_info = "Diagonal length of the Bbox of the selection to remesh is "
+ std::to_string(diago_length) + ".\n";
std::string specific_info;
specific_info = "Default is 5% of it\n";
ui.edgeLength_dspinbox->setToolTip(QString::fromStdString(diag_general_info + specific_info));
specific_info = "Default is 0.1% of it\n";
ui.errorTol_edit->setToolTip(QString::fromStdString(diag_general_info + specific_info));
specific_info = "Default is 0.1% of it\n";
ui.minEdgeLength_edit->setToolTip(QString::fromStdString(diag_general_info + specific_info));
specific_info = "Default is 50% of it\n";
ui.maxEdgeLength_edit->setToolTip(QString::fromStdString(diag_general_info + specific_info));
ui.nbIterations_spinbox->setSingleStep(1);
ui.nbIterations_spinbox->setRange(1/*min*/, 1000/*max*/);
ui.nbIterations_spinbox->setValue(1);
ui.edgeSizing_type_combo_box->setCurrentIndex(0);
on_edgeSizing_type_combo_box_changed(0);
ui.protect_checkbox->setChecked(false);
ui.smooth1D_checkbox->setChecked(true);
ui.curvSmooth_checkbox->setChecked(false);
ui.curvSmoothBallR_label->setEnabled(false);
ui.curvSmoothBallR_edit->setEnabled(false);
ui.curvSmoothBallR_edit->setValue(-1);
if (nullptr != selection_item)
{
@ -1047,7 +1361,6 @@ public:
}
}
private:
QAction* actionIsotropicRemeshing_;
Ui::Isotropic_remeshing_dialog ui;

View File

@ -161,6 +161,7 @@ CGAL_add_named_parameter(vertex_corner_map_t, vertex_corner_map, vertex_corner_m
CGAL_add_named_parameter(patch_normal_map_t, patch_normal_map, patch_normal_map)
CGAL_add_named_parameter(region_primitive_map_t, region_primitive_map, region_primitive_map)
CGAL_add_named_parameter(postprocess_regions_t, postprocess_regions, postprocess_regions)
CGAL_add_named_parameter(sizing_function_t, sizing_function, sizing_function)
// List of named parameters that we use in the package 'Surface Mesh Simplification'
CGAL_add_named_parameter(get_cost_policy_t, get_cost_policy, get_cost)