CGAL 6.3 - 3D Mesh Smoothing
Loading...
Searching...
No Matches
User Manual

Author
François Protais

Volume Mesh Smoothing and Geometric Fitting

This package implements an optimization algorithm for improving the quality of volumetric meshes and fitting them to geometric targets. The method supports tetrahedral meshes but can be adapted to mixed meshes containing tetrahedra, pyramids, wedges, and hexahedra. It can be used to improve an already valid mesh, untangle an invalid mesh, or deform a mesh so that its boundary follows a prescribed geometry.

The algorithm only modifies vertex coordinates. It does not insert or remove vertices, change cell connectivity, or alter the combinatorial structure of the input mesh. Consequently, cell indices, material labels, adjacency relations, and other data attached to the mesh are preserved throughout the optimization.

Smoothing and Fitting Algorithm

The algorithm optimizes vertex positions according to an element-quality energy. It currently uses a conformal energy (MIPS3D) that improves the dihedral angles of the cells. Interior vertices are moved to improve the volume mesh, while boundary vertices can additionally be attracted towards a target geometry.

The energy possesses a barrier term preventing element inversion. For an initially valid mesh, all accepted optimization steps preserve element orientation, providing a validity guarantee while the element quality is improved. The use of a penalization approach allows the optimizer to start from an invalid meshes, and recover a valid configuration by untangling inverted elements.

Geometric targets can be specified at several dimensions:

  • surface targets constrain boundary polygons to locally estimated tangent planes;
  • curve targets constrain selected mesh edges to target tangent directions;
  • point targets attract individual vertices to prescribed positions.

Surface patches and curves may be assigned different identifiers, allowing different parts of the mesh to use different target geometries. Constraints are soft and weighted by default, so geometric fidelity can be balanced against element quality. Vertices, or individual coordinate dimensions, can also be locked when hard constraints are required. Combining surface, curve, and point targets allows smooth regions, sharp curves, corners, and user handles to be treated within the same optimization.

By recovering the tangent planes of the target geometry, the smoother can recover curvature discontinuities enabling automatic feature recovery/preservation.

API

The main API is provided by the templated class Mesh_smoothing_3::Mesh_smoother<TetrahedralMesh, BoundaryMesh, EdgeNetwork>. The three template parameters provide lightweight views of the volume cells, the selected boundary polygons, and the selected curve edges. These views supply descriptors and ranges for traversing the input data structure, access to vertex coordinates, and the reference shape associated with each cell. The optimizer updates the original mesh in place through its coordinate setter; no conversion to an internal mesh representation is required.

After construction, the smoother can be configured through member functions. Surface, curve, and point targets are respectively defined with set_boundary_query(), set_curves_query(), and set_vertex_target_position(). Additional functions control vertex locks, constraint weights and the maximum number of iterations. The optimization is started with run(). The optimization will directly modify the input mesh through the coordinate setter provided by the mesh view.

For a CGAL::Mesh_complex_3_in_triangulation_3, the convenience class Mesh_smoothing_3::C3t3_smoother<C3T3> directly adapts the volume cells, surface patches, and feature curves stored in the complex.

Examples

Direct Smoothing of a C3t3

The following example demonstrates the direct use of the smoother on a CGAL::Mesh_complex_3_in_triangulation_3. A multi-domain C3t3 is generated from a labeled image and deformed to create an initial input. The C3t3_smoother is then constructed directly from the complex.

The volume cells, boundary facets, surface-patch indices, feature edges, and curve indices are read from the same C3t3; they do not need to be copied into separate arrays or converted to a standalone surface representation. Target queries are built from AABB trees associated with each surface patch and each feature curve. The smoother consequently improves the tetrahedra while keeping the boundary facets and feature edges aligned with their corresponding geometric targets.

Example: Mesh_smoothing_3/c3t3_smooth.cpp

Show / Hide
// Image
#include <CGAL/ImageIO.h>
#include <CGAL/Image_3.h>
// Domain
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Mesh_domain_with_polyline_features_3.h>
#include <CGAL/Labeled_mesh_domain_3.h>
#include <CGAL/Mesh_3/Detect_features_in_image.h>
// C3t3
#include <CGAL/Mesh_cell_base_3.h>
#include <CGAL/Mesh_vertex_base_3.h>
#include <CGAL/Mesh_triangulation_3.h>
#include <CGAL/Mesh_complex_3_in_triangulation_3.h>
#include <CGAL/Mesh_criteria_3.h>
#include <CGAL/make_mesh_3.h>
#include <CGAL/Mesh_3/C3T3_helpers.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits_3.h>
#include <CGAL/AABB_triangle_primitive_3.h>
#include <CGAL/AABB_segment_primitive_3.h>
#include <CGAL/Kernel/global_functions.h>
#include <map>
#include <set>
#include <CGAL/Mesh_smoothing_3/Mesh_smoothing_3.h>
// Parallel tag
#ifdef CGAL_CONCURRENT_MESH_3
typedef CGAL::Parallel_tag Concurrency_tag;
#else
typedef CGAL::Sequential_tag Concurrency_tag;
#endif
// Kernel
// Domain
typedef CGAL::Labeled_mesh_domain_3<K> Image_domain;
typedef CGAL::Mesh_domain_with_polyline_features_3<Image_domain> Mesh_domain;
// Triangulations
typedef CGAL::Mesh_vertex_base_3<K, Mesh_domain> Vertex_base;
typedef CGAL::Compact_mesh_cell_base_3<K, Mesh_domain> Cell_base;
typedef std::array<std::size_t, 2> Vertex_info;
typedef std::array<std::size_t, 2> Cell_info;
typedef CGAL::Triangulation_vertex_base_with_info_3<Vertex_info, K, Vertex_base> Vertex_base_with_info;
typedef CGAL::Triangulation_cell_base_with_info_3<Cell_info, K, Cell_base> Cell_base_with_info;
typedef CGAL::Mesh_triangulation_3<Mesh_domain, CGAL::Default, Concurrency_tag, Vertex_base_with_info, Cell_base_with_info>::type Tr;
// Criteria
typedef CGAL::Mesh_criteria_3<Tr> Mesh_criteria;
// C3t3
typedef CGAL::Mesh_complex_3_in_triangulation_3<Tr> C3t3;
// AABB traits for C3t3 facets and edges
struct Facet_to_triangle_property_map
{
typedef C3t3::Facet key_type;
typedef K::Triangle_3 value_type;
typedef value_type reference;
typedef boost::readable_property_map_tag category;
};
inline Facet_to_triangle_property_map::reference
get(const Facet_to_triangle_property_map&, const C3t3::Facet& f)
{
const Tr::Cell_handle cell = f.first;
const int i = f.second;
const K::Point_3& p0 = cell->vertex((i + 1) % 4)->point().point();
const K::Point_3& p1 = cell->vertex((i + 2) % 4)->point().point();
const K::Point_3& p2 = cell->vertex((i + 3) % 4)->point().point();
return K::Triangle_3(p0, p1, p2);
}
struct Facet_to_point_property_map
{
typedef C3t3::Facet key_type;
typedef K::Point_3 value_type;
typedef value_type reference;
typedef boost::readable_property_map_tag category;
};
inline Facet_to_point_property_map::reference
get(const Facet_to_point_property_map&, const C3t3::Facet& f)
{
const Tr::Cell_handle cell = f.first;
const int i = f.second;
return cell->vertex((i + 1) % 4)->point().point();
}
struct Edge_to_segment_property_map
{
typedef C3t3::Edge key_type;
typedef K::Segment_3 value_type;
typedef value_type reference;
typedef boost::readable_property_map_tag category;
};
inline Edge_to_segment_property_map::reference
get(const Edge_to_segment_property_map&, const C3t3::Edge& e)
{
const Tr::Cell_handle cell = e.first;
return K::Segment_3(cell->vertex(e.second)->point().point(), cell->vertex(e.third)->point().point());
}
struct Edge_to_point_property_map
{
typedef C3t3::Edge key_type;
typedef K::Point_3 value_type;
typedef value_type reference;
typedef boost::readable_property_map_tag category;
};
inline Edge_to_point_property_map::reference
get(const Edge_to_point_property_map&, const C3t3::Edge& e)
{
const Tr::Cell_handle cell = e.first;
return cell->vertex(e.second)->point().point();
}
typedef CGAL::AABB_primitive<C3t3::Facet,
Facet_to_triangle_property_map,
Facet_to_point_property_map,
CGAL::Tag_false> Facet_primitive;
typedef CGAL::AABB_traits_3<K, Facet_primitive> Facet_traits;
typedef CGAL::AABB_tree<Facet_traits> Facet_tree;
typedef CGAL::AABB_primitive<C3t3::Edge,
Edge_to_segment_property_map,
Edge_to_point_property_map,
CGAL::Tag_false> Edge_primitive;
typedef CGAL::AABB_traits_3<K, Edge_primitive> Edge_traits;
typedef CGAL::AABB_tree<Edge_traits> Edge_tree;
// To avoid verbose function and named parameters call
namespace params = CGAL::parameters;
// image of type 'unsigned char' initialised with '0'
CGAL::Image_3 create_cgal_image(const std::size_t& xdim, const std::size_t& ydim, const std::size_t& zdim,
double vx = 1.0, double vy = 1.0, double vz = 1.0)
{
CGAL::_image* im = _createImage(xdim, ydim, zdim, 1,
vx, vy, vz, 1,
CGAL::WK_FIXED, CGAL::SGN_UNSIGNED);
std::fill_n(static_cast<unsigned char*>(im->data), xdim*ydim*zdim, 0);
return CGAL::Image_3(im);
}
void add_rectangle_in_image(const K::Point_3& rectangle_min, // [0..1]^3
const K::Point_3& rectangle_max, // [0..1]^3
const unsigned char& label,
CGAL::Image_3 &image)
{
using CGAL::IMAGEIO::static_evaluate;
CGAL::_image* im = image.image();
const std::size_t min_x = rectangle_min.x() * im->xdim;
const std::size_t min_y = rectangle_min.y() * im->ydim;
const std::size_t min_z = rectangle_min.z() * im->zdim;
const std::size_t max_x = rectangle_max.x() * im->xdim;
const std::size_t max_y = rectangle_max.y() * im->ydim;
const std::size_t max_z = rectangle_max.z() * im->zdim;
for (std::size_t k = min_z; k < max_z; k++)
for (std::size_t j = min_y; j < max_y; j++)
for (std::size_t i = min_x; i < max_x; i++)
static_evaluate<unsigned char>(im, i, j, k) = label;
}
void deform_c3t3_smooth_fold(C3t3& c3t3, const K::FT& angle, const K::FT& nb_planes)
{
typedef C3t3::Point WPoint_3;
K::Point_3 center = CGAL::midpoint(K::Point_3(c3t3.bbox().min(0), c3t3.bbox().min(1), c3t3.bbox().min(2)),
K::Point_3(c3t3.bbox().max(0), c3t3.bbox().max(1), c3t3.bbox().max(2)));
K::Plane_3 start_plane(center, K::Vector_3(1,0,0));
Tr& triangulation = c3t3.triangulation();
std::list<Tr::Vertex*> vertices;
for (Tr::Vertex& vertex : triangulation.tds().vertices())
{
vertices.emplace_back(&vertex);
}
const K::FT angle_smooth_length = (c3t3.bbox().max(0) - c3t3.bbox().min(0)) * 0.1875; // * 0.25;
const K::FT angle_smooth_length_increment = angle_smooth_length / nb_planes;
const K::FT angle_increment = angle/nb_planes;
const K::FT ca_i = cos(angle_increment);
const K::FT sa_i = sin(angle_increment);
K::Point_3 plane_center = center;
plane_center -= K::Vector_3(angle_smooth_length * 0.5, 0, 0);
for (std::size_t p = 0; p < nb_planes; p++)
{
const K::Vector_3 plane_normal(cos(p*angle_increment), sin(p*angle_increment), 0);
const K::Plane_3 plane(plane_center, plane_normal);
const K::FT plane_x = plane_center[0];
const K::FT plane_y = plane_center[1];
const K::FT plane_z = plane_center[2];
for (std::list<Tr::Vertex*>::iterator it = vertices.begin(); it != vertices.end();)
{
Tr::Vertex& vertex = **it;
const WPoint_3& point = vertex.point();
if (plane.has_on_positive_side(point.point()))
{
const K::FT px = point.point()[0] - plane_x;
const K::FT py = point.point()[1] - plane_y;
const K::FT pz = point.point()[2] - plane_z;
const K::Point_3 rotated_point(px*ca_i-py*sa_i + plane_x, px*sa_i+py*ca_i + plane_y, pz + plane_z);
vertex.set_point(WPoint_3(rotated_point, point.weight()));
it++;
}
else
{
it = vertices.erase(it);
}
}
plane_center += angle_smooth_length_increment * K::Vector_3(cos((p+1) * angle_increment), sin((p+1) * angle_increment), 0);
}
}
int main(int argc, char* argv[])
{
CGAL::Image_3 image = create_cgal_image(64, 64, 64);
add_rectangle_in_image(K::Point_3(0.2, 0.45, 0.2), K::Point_3(0.5, 0.55, 0.5), 1, image);
add_rectangle_in_image(K::Point_3(0.5, 0.45, 0.2), K::Point_3(0.8, 0.55, 0.5), 2, image);
add_rectangle_in_image(K::Point_3(0.2, 0.45, 0.5), K::Point_3(0.5, 0.55, 0.8), 3, image);
add_rectangle_in_image(K::Point_3(0.5, 0.45, 0.5), K::Point_3(0.8, 0.55, 0.8), 4, image);
_writeImage(image.image(), "input_image.inr");
// Make c3t3
Mesh_domain domain = Mesh_domain::create_labeled_image_mesh_domain(image,
params::features_detector = CGAL::Mesh_3::Detect_features_in_image());
CGAL::Bbox_3 bbox = domain.bbox();
double diag = CGAL::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
CGAL::square(bbox.ymax() - bbox.ymin()) +
CGAL::square(bbox.zmax() - bbox.zmin()));
double sizing_default = diag * 0.01;
Mesh_criteria criteria(params::edge_size = sizing_default,
params::facet_angle = 30,
params::facet_size = sizing_default,
params::facet_distance = sizing_default / 10,
params::facet_topology = CGAL::FACET_VERTICES_ON_SAME_SURFACE_PATCH,
params::cell_radius_edge_ratio = 1.5,
params::cell_size = 0
);
C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria,
params::no_exude(),
params::no_perturb());
// Output
CGAL::dump_c3t3(c3t3, "c3t3_initial");
// Make deformed c3t3
C3t3 c3t3_deformed(static_cast<const C3t3>(c3t3));
deform_c3t3_smooth_fold(c3t3_deformed, 3.141592635 * 0.5, 100);
C3t3 c3t3_deformed_ref(static_cast<const C3t3>(c3t3));
deform_c3t3_smooth_fold(c3t3_deformed_ref, 3.141592635 * 0.5, 100);
CGAL::dump_c3t3(c3t3_deformed, "c3t3_deformed");
// AABB trees of the surface and curve features of the c3t3
std::map<C3t3::Surface_patch_index, std::vector<C3t3::Facet>> facets_by_patch;
for (auto f : c3t3_deformed_ref.facets_in_complex())
{
facets_by_patch[c3t3_deformed_ref.surface_patch_index(f)].push_back(f);
}
std::map<C3t3::Curve_index, std::vector<C3t3::Edge>> edges_by_curve;
for (auto e : c3t3_deformed_ref.edges_in_complex())
{
edges_by_curve[c3t3_deformed_ref.curve_index(e)].push_back(e);
}
std::map<C3t3::Surface_patch_index, Facet_tree> facet_trees;
for (auto& kv : facets_by_patch)
{
facet_trees.try_emplace(kv.first, kv.second.begin(), kv.second.end());
}
std::map<C3t3::Curve_index, Edge_tree> edge_trees;
for (auto& kv : edges_by_curve)
{
edge_trees.try_emplace(kv.first, kv.second.begin(), kv.second.end());
}
std::cout << "Built " << facet_trees.size() << " facet AABB trees and " << edge_trees.size() << " edge AABB trees." << std::endl;
// Smoothing
CGAL::Mesh_smoothing_3::C3t3_smoother smoother(c3t3_deformed);
for (auto c : c3t3_deformed.vertices_in_complex())
{
smoother.set_vertex_lock(c, true);
}
smoother.set_boundary_query([&](K::Point_3 const &pt, C3t3::Surface_patch_index id, double radius) -> std::tuple<K::Point_3, K::Vector_3, double> {
auto res = facet_trees.at(id).closest_point_and_primitive(pt);
K::Point_3 closest_point = res.first;
const auto triangle = c3t3_deformed_ref.triangulation().triangle(res.second);
K::Vector_3 normal = CGAL::unit_normal(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2));
return std::make_tuple(closest_point, normal, 1.);
});
smoother.set_curves_query([&](K::Point_3 const &pt, C3t3::Curve_index id, double radius) -> std::tuple<K::Point_3, K::Vector_3, double> {
auto res = edge_trees.at(id).closest_point_and_primitive(pt);
K::Point_3 closest_point = res.first;
const auto segment = c3t3_deformed_ref.triangulation().segment(res.second);
K::Vector_3 direction = (segment.target() - segment.source());
if (direction.squared_length() > 1e-8)
{
direction /= CGAL::sqrt(direction.squared_length());
}
return std::make_tuple(closest_point, direction, 1.);
});
smoother.set_verbose();
smoother.set_max_number_of_iteration(100);
smoother.run();
CGAL::dump_c3t3(c3t3_deformed, "c3t3_smoothed");
return 0;
}
double ymin() const
double xmax() const
double zmin() const
double zmax() const
double ymax() const
double xmin() const
smooth a c3t3 mesh structure
Definition Mesh_smoothing_3.h:554
NT square(const NT &x)
NT sqrt(const NT &x)
CGAL::Boolean_tag< false > Tag_false
Angle angle(const CGAL::Vector_2< Kernel > &u, const CGAL::Vector_2< Kernel > &v)
CGAL::Point_2< Kernel > midpoint(const CGAL::Point_2< Kernel > &p, const CGAL::Point_2< Kernel > &q)
CGAL::Vector_3< Kernel > normal(const CGAL::Point_3< Kernel > &p, const CGAL::Point_3< Kernel > &q, const CGAL::Point_3< Kernel > &r)
CGAL::Vector_3< Kernel > unit_normal(const CGAL::Point_3< Kernel > &p, const CGAL::Point_3< Kernel > &q, const CGAL::Point_3< Kernel > &r)

Feature-Preserving Improvement of an Alpha Wrap Offset

Figure 69.1 Feature-preserving offsets for several Alpha Wrap gate sizes. The upper row shows the Alpha Wrap results, in which the stair edges remain rounded. The lower row shows the meshes after volume fitting, which recovers the sharp features while improving the underlying tetrahedral mesh.


The following example combines the 3D Alpha Wrapping package with volume mesh improvement. Alpha Wrap first generates a watertight offset surface and its underlying Delaunay triangulation. The cells inside the wrap are identified, and tetrahedral remeshing is applied to improve the sampling of the volume while leaving the boundary connectivity unchanged.

A first smoothing pass improves the tetrahedral elements with the boundary vertices fixed. The boundary is then released and a geometric query defines the target offset: each query point is projected onto the input triangle soup and displaced by the requested offset distance along the outward direction. The optimizer jointly moves the boundary and interior vertices to recover the offset geometry without sacrificing the quality of the volume cells.

Unlike point redistribution performed directly on the wrapped surface, the fitting energy allows neighboring boundary facets to align with different local target planes. Sharp edges and corners that were rounded by Alpha Wrap can therefore be recovered automatically, without explicitly extracting or tagging a feature graph.

Example: Mesh_smoothing_3/alpha_wrap_improvement.cpp

Show / Hide
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/alpha_wrap_3.h>
#include <CGAL/tetrahedral_remeshing.h>
#include <CGAL/Tetrahedral_remeshing/Remeshing_cell_base_3.h>
#include <CGAL/Tetrahedral_remeshing/Remeshing_vertex_base_3.h>
#include <CGAL/Simplicial_mesh_cell_base_3.h>
#include <CGAL/Simplicial_mesh_vertex_base_3.h>
#include <CGAL/Polygon_mesh_processing/bbox.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <CGAL/property_map.h>
#include <CGAL/Real_timer.h>
#include <CGAL/Delaunay_triangulation_3.h>
#include <CGAL/IO/Triangulation_off_ostream_3.h>
#include <CGAL/IO/File_medit.h>
#include <CGAL/Mesh_smoothing_3/Mesh_smoothing_3.h>
#include <iostream>
#include <string>
#include <chrono>
namespace PMP = CGAL::Polygon_mesh_processing;
namespace AW3i = CGAL::Alpha_wraps_3::internal;
using Point_3 = K::Point_3;
using Points = std::vector<Point_3>;
using Face = std::array<std::size_t, 3>;
using Faces = std::vector<Face>;
using Mesh = CGAL::Surface_mesh<Point_3>;
// If we provide a triangulation, AW3 uses its Gt, so we have to make the Gt stack explicit
using Gtb = AW3i::Alpha_wrap_AABB_geom_traits<K>; // provides Ball_3
using Gt = CGAL::Robust_circumcenter_filtered_traits_3<Gtb>; // better inexact constructions (not mandatory)
// Since we are going to use tetrahedral remeshing on the underlying triangulation,
// we need special vertex and cell base types that meets the requirements of the
// tetrahedral remeshing concepts
using Vbbb = AW3i::Alpha_wrap_triangulation_vertex_base_3<K>;
using Vbb = CGAL::Simplicial_mesh_vertex_base_3<K, int, int, int, int, Vbbb>;
using Vb = CGAL::Tetrahedral_remeshing::Remeshing_vertex_base_3<K, Vbb>;
using Cbbb = AW3i::Alpha_wrap_triangulation_cell_base_3<K>;
using Cbb = CGAL::Simplicial_mesh_cell_base_3<K, int, int, Cbbb>;
using Cb = CGAL::Tetrahedral_remeshing::Remeshing_cell_base_3<K, Cbb>;
using Tds = CGAL::Triangulation_data_structure_3<Vb, Cb>;
using Delaunay_triangulation = CGAL::Delaunay_triangulation_3<Gt, Tds, CGAL::Fast_location>;
// because the Fast_location does all kinds of rebinding shenanigans + T3_hierarchy is in the stack...
using Triangulation = CGAL::Triangulation_3<typename Delaunay_triangulation::Geom_traits,
typename Delaunay_triangulation::Triangulation_data_structure>;
using Facet = Triangulation::Facet;
using SC_Point_3 = SC::Point_3;
using SC_Vector_3 = SC::Vector_3;
using SC_Iso_cuboid_3 = SC::Iso_cuboid_3;
class Tetrahedral_mesh_wrapper {
public:
using Cell_descriptor = Triangulation::Cell_handle;
using Vertex_descriptor = Triangulation::Vertex_handle;
using Point_3 = K::Point_3;
std::size_t nb_cells() const { return tetmesh.number_of_finite_cells(); }
std::size_t nb_vertices() const { return tetmesh.number_of_vertices(); }
Point_3 vertex_coordinates(Vertex_descriptor vertex) const { return tetmesh.point(vertex); }
void set_new_vertex_coordinates(Vertex_descriptor vertex, Point_3 coord) {
vertex->set_point(coord);
} // only non const
auto cell_range() const {
return cell_vector_range;
}
auto cell_vertices(Cell_descriptor cell) const {
std::array<Vertex_descriptor, 4> vertices;
for (int i = 0; i < 4; ++i) {
vertices[static_cast<unsigned>(i)] = cell->vertex(i);
}
return vertices;
}
std::array<Point_3, 4> cell_reference_shape(Cell_descriptor) const { return CGAL::Mesh_smoothing_3::Shapes::VTK_TETRAHEDRON<Point_3>(); }
public:
Tetrahedral_mesh_wrapper(Triangulation &tetmesh_, std::set<int> regions)
: tetmesh(tetmesh_)
{
cell_vector_range.reserve(tetmesh.number_of_cells());
for(auto c : tetmesh.finite_cell_handles()) {
if (regions.contains(c->subdomain_index())) {
cell_vector_range.push_back(c);
}
}
}
Triangulation &tetmesh;
std::vector<Triangulation::Cell_handle> cell_vector_range;
};
class Triangle_boundary_wrapper {
public:
using Face_descriptor = unsigned;
using Vertex_descriptor = Triangulation::Vertex_handle;
using Normal_3 = K::Vector_3;
using Surface_patch_index = unsigned;
std::size_t nb_faces() const { return faces.size(); }
auto face_range() const {
return CGAL::Mesh_smoothing_3::utils::Contiguous_unsigned_range{0, faces.size()};
}
unsigned patch_id(Face_descriptor) const { return 0; }
std::size_t nb_face_vertices(Face_descriptor) const { return 3; }
auto face_vertices(Face_descriptor face) const {
return faces[face];
}
public:
std::vector<std::array<Vertex_descriptor, 3>> const &faces;
};
int main(int argc, char** argv)
{
// Read the input
const std::string filename = (argc > 1) ? argv[1] : "../data/stairs.off";
std::cout << "Reading " << filename << "..." << std::endl;
Points points;
Faces faces;
if(!CGAL::IO::read_polygon_soup(filename, points, faces, CGAL::parameters::verbose(true)) || faces.empty())
{
std::cerr << "Invalid input:" << filename << std::endl;
return EXIT_FAILURE;
}
std::cout << "Input: " << points.size() << " vertices, " << faces.size() << " faces" << std::endl;
// Compute the alpha and offset values
const double relative_alpha = (argc > 2) ? std::stod(argv[2]) : 60.;
const double relative_offset = (argc > 3) ? std::stod(argv[3]) : 600.;
for(const Point_3& p : points) bbox += p.bbox();
const double diag_length = std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
CGAL::square(bbox.ymax() - bbox.ymin()) +
CGAL::square(bbox.zmax() - bbox.zmin()));
const double alpha = diag_length / relative_alpha;
const double offset = diag_length / relative_offset;
std::cout << "alpha: " << alpha << ", offset: " << offset << std::endl;
// Construct the wrap
CGAL::Real_timer t;
t.start();
using Oracle = CGAL::Alpha_wraps_3::internal::Triangle_soup_oracle<K>;
Oracle oracle(K{});
oracle.add_triangle_soup(points, faces, CGAL::parameters::default_values());
CGAL::Alpha_wraps_3::internal::Alpha_wrapper_3<Oracle, Delaunay_triangulation> aw3(oracle);
Mesh wrap;
aw3(alpha, offset, wrap);
t.stop();
std::cout << "Result: " << num_vertices(wrap) << " vertices, " << num_faces(wrap) << " faces" << std::endl;
std::cout << "Took " << t.time() << " s." << std::endl;
Delaunay_triangulation& aw3_dt = aw3.triangulation();
for(auto c : aw3_dt.finite_cell_handles())
{
if(c->is_outside())
c->set_subdomain_index(0);
else
c->set_subdomain_index(1);
}
const Triangulation& aw3_tr = static_cast<const Triangulation&>(aw3_dt);
Triangulation tr = aw3_tr; // intentional copy
std::cout << "BEFORE REMESHING: " << tr.number_of_vertices() << " vertices, " << tr.number_of_cells() << " cells" << std::endl;
// Set up the c3t3 information
for(auto v : tr.finite_vertex_handles()) v->set_dimension(3);
for(auto c : tr.finite_cell_handles())
{
for(int i=0; i<4; ++i)
{
if(c->neighbor(i)->subdomain_index() != c->subdomain_index())
{
c->set_surface_patch_index(i, 1);
for(int j=1; j<4; ++j) {
c->vertex((i+j)%4)->set_dimension(2);
}
}
}
}
// edge length of regular tetrahedron with circumradius alpha
const double l = 1.6329931618554521 * alpha; // sqrt(8/3)
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
// remeshing the interior
CGAL::tetrahedral_isotropic_remeshing(tr, l, CGAL::parameters::remesh_boundaries(false).number_of_iterations(5));
// Remeshing the surface may lead to worse quality around features because of point equidistribution on the surface.
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::milliseconds> (end - begin).count() << "[ms]" << std::endl;
std::cout << "AFTER REMESHING: " << tr.number_of_vertices() << " vertices, " << tr.number_of_cells() << " cells" << std::endl;
auto boundary_query = [&](Point_3 pt, unsigned, double) -> std::tuple<Point_3, K::Vector_3, double>{
auto pp_and_prim = oracle.tree().closest_point_and_primitive(pt);
Point_3 proj = pp_and_prim.first;
K::Vector_3 normal { proj, pt };
normal = normal / CGAL::approximate_sqrt(normal * normal);
proj = proj + offset * normal;
return {proj, normal, 1.};
};
std::vector<std::array<Triangulation::Vertex_handle, 3>> boundary_faces;
std::unordered_map<Triangulation::Vertex_handle, std::array<bool, 3>> locked;
std::vector<Triangulation::Vertex_handle> boundary_vertices;
for(auto v : tr.finite_vertex_handles()) {
locked.emplace(v, std::array<bool, 3>{true, true, true});
}
for(auto c : tr.finite_cell_handles())
{
if (c->subdomain_index() != 1) continue;
for(int i=0; i<4; ++i)
{
if(c->neighbor(i)->subdomain_index() != 1)
{
for(int j=0; j<4; ++j) {
locked.at(c->vertex(j)) = {false, false, false};
}
boundary_faces.push_back({});
for(int j=1; j<4; ++j) {
boundary_faces.back()[j-1] = c->vertex((i+j)%4);
boundary_vertices.push_back(c->vertex((i+j)%4));
}
}
}
}
Tetrahedral_mesh_wrapper mesh_wrapper(tr, {1,2});
Triangle_boundary_wrapper boundary_wrapper {boundary_faces};
CGAL::Mesh_smoothing_3::Mesh_smoother smoother(mesh_wrapper, boundary_wrapper);
smoother.set_verbose();
smoother.set_locked_vertices(boundary_vertices);
smoother.run();
std::ofstream all_after_smoothed("offset_wrapper.mesh");
CGAL::IO::write_MEDIT(all_after_smoothed, tr, CGAL::parameters::all_cells(false));
smoother.clear_locks();
smoother.set_vertices_dim_locks(locked);
smoother.set_boundary_query(boundary_query);
smoother.set_max_number_of_iteration(100);
smoother.run();
smoother.clear_locks();
smoother.run();
std::ofstream inside_after_smoothed("offset_ours.mesh");
CGAL::IO::write_MEDIT(inside_after_smoothed, tr, CGAL::parameters::all_cells(false));
return EXIT_SUCCESS;
}
smooth a tetrahedral mesh with optional constraints on the boundary and along a curve network
Definition Mesh_smoothing_3.h:75
bool read_polygon_soup(const std::string &fname, PointRange &points, PolygonRange &polygons, const NamedParameters &np=parameters::default_values())
Default_named_parameters default_values()