From c6c31033f4229739b042030d2142cc13b555217a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Aug 2026 15:38:59 +0200 Subject: [PATCH 1/6] add function to create a LCC from a tet soup --- .../Linear_cell_complex/CMakeLists.txt | 2 + .../Linear_cell_complex/tetsoup_to_lcc.cpp | 38 ++++++++ .../include/CGAL/tetrahedron_soup_to_lcc.h | 95 +++++++++++++++++++ Stream_support/include/CGAL/IO/MEDIT.h | 2 +- 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 Linear_cell_complex/examples/Linear_cell_complex/tetsoup_to_lcc.cpp create mode 100644 Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h diff --git a/Linear_cell_complex/examples/Linear_cell_complex/CMakeLists.txt b/Linear_cell_complex/examples/Linear_cell_complex/CMakeLists.txt index 234ae9e83da1..bc3e34caea48 100644 --- a/Linear_cell_complex/examples/Linear_cell_complex/CMakeLists.txt +++ b/Linear_cell_complex/examples/Linear_cell_complex/CMakeLists.txt @@ -28,10 +28,12 @@ create_single_source_cgal_program("read_plane_graph_in_lcc_2.cpp") create_single_source_cgal_program("voronoi_2.cpp") create_single_source_cgal_program("voronoi_3.cpp") create_single_source_cgal_program("linear_cell_complex_3_vtk_io.cpp") +create_single_source_cgal_program("tetsoup_to_lcc.cpp") create_single_source_cgal_program("draw_linear_cell_complex.cpp") if(CGAL_Qt6_FOUND) target_link_libraries(draw_linear_cell_complex PRIVATE CGAL::CGAL_Basic_viewer) target_link_libraries(linear_cell_complex_3_incremental_builder PRIVATE CGAL::CGAL_Basic_viewer) target_link_libraries(linear_cell_complex_3_insert PRIVATE CGAL::CGAL_Basic_viewer) + target_link_libraries(tetsoup_to_lcc PRIVATE CGAL::CGAL_Basic_viewer) endif() diff --git a/Linear_cell_complex/examples/Linear_cell_complex/tetsoup_to_lcc.cpp b/Linear_cell_complex/examples/Linear_cell_complex/tetsoup_to_lcc.cpp new file mode 100644 index 000000000000..a57e78dd4a54 --- /dev/null +++ b/Linear_cell_complex/examples/Linear_cell_complex/tetsoup_to_lcc.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +#include + +using LCC = CGAL::Linear_cell_complex_for_combinatorial_map<3, 3>; +using Point_3 = CGAL::Exact_predicates_inexact_constructions_kernel::Point_3; + +int main(int argc, char** argv) +{ + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/elephant.mesh"); + + std::vector points; + std::vector> tetras; + std::vector subdomains; + std::ifstream in(filename); + if(!in) + { + std::cerr << "Cannot open " << filename << std::endl; + return 1; + } + + CGAL::IO::read_MEDIT(in, points, tetras, subdomains); + + //filter out subdomain 0 + std::vector> filtered_tetras; + for (std::size_t i=0; i +// Sebastien Loriot +// + +#ifndef CGAL_TETRAHEDRON_SOUP_TO_LCC_H +#define CGAL_TETRAHEDRON_SOUP_TO_LCC_H + +namespace CGAL +{ + template + typename LCC::Dart_descriptor + tetrahedron_soup_to_lcc(const PointRange& points, + const TetraRange& tetras, + LCC& lcc) + { + static_assert( LCC::dimension>=3 && LCC::ambient_dimension==3 ); + + if (points.empty() || tetras.empty()) + return LCC::null_descriptor; + + using Dart_descriptor = typename LCC::Dart_descriptor; + + std::vector vertices; + vertices.reserve(points.size()); + for(const auto& pt : points) + vertices.push_back(lcc.create_vertex_attribute(pt)); + + struct Array_hasher + { + std::size_t operator()(const std::array& a) const + { + std::size_t seed = 0; + boost::hash_combine(seed, a[0]); + boost::hash_combine(seed, a[1]); + boost::hash_combine(seed, a[2]); + + return seed; + } + }; + + std::unordered_map< std::array, Dart_descriptor, Array_hasher> face_map; + + Dart_descriptor dart = LCC::null_descriptor; + for (const auto& t : tetras) + { + Dart_descriptor res = lcc.make_tetrahedron(vertices[t[0]], + vertices[t[1]], + vertices[t[2]], + vertices[t[3]]); + + if (dart==LCC::null_descriptor) dart = res; + + for (int i=0; i<4; ++i) + { + Dart_descriptor curr= LCC::null_descriptor; + switch (i) + { + case 0: curr = lcc.template opposite<2>(lcc.next(res)); break; + case 1: curr = lcc.template opposite<2>(lcc.previous(res)); break; + case 2: curr = lcc.template opposite<2>(res); break; + default: curr = res; break; + } + + std::array f = CGAL::make_array(t[(i+1)%4],t[(i+2)%4],t[(i+3)%4]); + std::sort(f.begin(), f.end()); + auto insert_res = face_map.emplace(f, curr); + if (!insert_res.second) + { + Dart_descriptor curr2=insert_res.first->second; + while (lcc.vertex_attribute(curr2) != + lcc.vertex_attribute(lcc.other_extremity(curr)) ) + { + curr2 = lcc.next(curr2); + } + lcc.template topo_sew<3>(curr, lcc.other_orientation(curr2)); + } + } + } + + CGAL_assertion(dart!=LCC::null_descriptor); + return dart; + } +} // end of CGAL namespace + + +#endif // CGAL_TETRAHEDRON_SOUP_TO_LCC_H diff --git a/Stream_support/include/CGAL/IO/MEDIT.h b/Stream_support/include/CGAL/IO/MEDIT.h index 8e3d1fab4083..a9d0c362d0f2 100644 --- a/Stream_support/include/CGAL/IO/MEDIT.h +++ b/Stream_support/include/CGAL/IO/MEDIT.h @@ -46,7 +46,7 @@ bool read_MEDIT(std::istream& is, using FT = typename Kernel_traits::Kernel::FT; using Surface_patch_index = SurfacePatchIndex_; using Facet = std::array; - using Tet_with_ref = std::array; + using Tet_with_ref = typename std::iterator_traits::value_type; if(!is) return false; From 82b34586a09ec651a0655d20e086ce72537c29cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Aug 2026 18:18:20 +0200 Subject: [PATCH 2/6] add missing include --- Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h index e8214b6d983d..f0aa56991b43 100644 --- a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h +++ b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h @@ -14,6 +14,11 @@ #ifndef CGAL_TETRAHEDRON_SOUP_TO_LCC_H #define CGAL_TETRAHEDRON_SOUP_TO_LCC_H +#include +#include +#include +#include + namespace CGAL { template From 6ca7b19e80ebf42b37589e4f0f8d30f1252d67d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Aug 2026 18:28:33 +0200 Subject: [PATCH 3/6] add doc --- .../doc/Linear_cell_complex/Doxyfile.in | 1 + .../PackageDescription.txt | 1 + .../include/CGAL/tetrahedron_soup_to_lcc.h | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/Linear_cell_complex/doc/Linear_cell_complex/Doxyfile.in b/Linear_cell_complex/doc/Linear_cell_complex/Doxyfile.in index db43fd07ee8d..1e4c4d0545c6 100644 --- a/Linear_cell_complex/doc/Linear_cell_complex/Doxyfile.in +++ b/Linear_cell_complex/doc/Linear_cell_complex/Doxyfile.in @@ -1,3 +1,4 @@ @INCLUDE = ${CGAL_DOC_PACKAGE_DEFAULTS} PROJECT_NAME = "CGAL ${CGAL_DOC_VERSION} - Linear Cell Complex" +INPUT += ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/tetrahedron_soup_to_lcc.h \ No newline at end of file diff --git a/Linear_cell_complex/doc/Linear_cell_complex/PackageDescription.txt b/Linear_cell_complex/doc/Linear_cell_complex/PackageDescription.txt index 006190086164..6da4a7527efe 100644 --- a/Linear_cell_complex/doc/Linear_cell_complex/PackageDescription.txt +++ b/Linear_cell_complex/doc/Linear_cell_complex/PackageDescription.txt @@ -73,6 +73,7 @@ - `CGAL::read_plane_graph_in_lcc` (formerly `import_from_plane_graph`) - `CGAL::triangulation_3_to_lcc` (formerly `import_from_triangulation_3`) - `CGAL::polyhedron_3_to_lcc` (formerly `import_from_polyhedron_3`) +- `CGAL::tetrahedron_soup_to_lcc()` \cgalCRPSubsection{Operations for Linear Cell Complex} - `CGAL::compute_normal_of_cell_0` diff --git a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h index f0aa56991b43..8a62589cd8eb 100644 --- a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h +++ b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h @@ -21,6 +21,30 @@ namespace CGAL { +/** + * @ingroup PkgLinearCellComplexConstructions + * + * \brief imports a 3D tetrahedron soup into a linear cell complex. + * + * Creates a 3D linear cell complex in `lcc` from a set of points and a range + * of tetrahedra defined by indices into `points`. The topological 3-combinatorial + * map is constructed by sewing adjacent 3-volumes (tetrahedra) along matching 2-faces. + * + * @tparam LCC a model of the `LinearCellComplex` concept. + * @tparam PointRange a model of `RandomAccessContainer` with `Point_3` as value type, being compatible with the point type of LCC. + * @tparam TetraRange a model of `ConstRange` where each element is a container of 4 point indices, accessible using `operator[](int)`. + * + * @param points range of 3D points. + * @param tetras range of tetrahedra, where each tetrahedron is represented by 4 indices corresponding to entries in `points`. + * @param lcc the target linear cell complex. + * + * @return a descriptor to a dart of the created complex, or `LCC::null_descriptor` if the input is empty. + * + * @pre `LCC::dimension >= 3` and `LCC::ambient_dimension == 3`. + * @pre Indices in `tetras` must be valid 0-based indices into `points`. + * + * @sa `CGAL::triangulation_3_to_lcc()` + */ template typename LCC::Dart_descriptor tetrahedron_soup_to_lcc(const PointRange& points, From 5658118a0fddee6326c7bfa2e5cd1ab878b9dc7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Aug 2026 18:31:04 +0200 Subject: [PATCH 4/6] update changes --- Installation/CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index d56ed1fc281b..900fefb941e6 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -4,6 +4,9 @@ Release date: December 2026 +### [Linear Cell Complex](https://doc.cgal.org/6.3/Manual/packages.html#PkgLinearCellComplex) +- added `tetrahedron_soup_to_lcc()` to import a tetrahedron soup into a linear cell complex. + ### [2D and 3D Fast Intersection and Distance Computation (AABB Tree)](https://doc.cgal.org/6.3/Manual/packages.html#PkgAABBTree) - `CGAL::AABB_tree::build()` now accepts an optional `Concurrency_tag` template parameter (`CGAL::Sequential_tag` by default). When `CGAL::Parallel_tag` is specified, the tree construction is performed in parallel. From 561a938c6246cc77309775cdad4b6b73d6b9c024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 28 Aug 2026 09:14:27 +0200 Subject: [PATCH 5/6] missing includes --- Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h index 8a62589cd8eb..2c593474a970 100644 --- a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h +++ b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h @@ -14,6 +14,7 @@ #ifndef CGAL_TETRAHEDRON_SOUP_TO_LCC_H #define CGAL_TETRAHEDRON_SOUP_TO_LCC_H +#include #include #include #include @@ -99,7 +100,7 @@ namespace CGAL default: curr = res; break; } - std::array f = CGAL::make_array(t[(i+1)%4],t[(i+2)%4],t[(i+3)%4]); + std::array f{t[(i+1)%4],t[(i+2)%4],t[(i+3)%4]}; std::sort(f.begin(), f.end()); auto insert_res = face_map.emplace(f, curr); if (!insert_res.second) From af05aca5000ac0fe5d35474a3b244a2f55e2dda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 28 Aug 2026 13:23:06 +0200 Subject: [PATCH 6/6] renaming tets --- .../include/CGAL/tetrahedron_soup_to_lcc.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h index 2c593474a970..d256e7d27ee5 100644 --- a/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h +++ b/Linear_cell_complex/include/CGAL/tetrahedron_soup_to_lcc.h @@ -33,28 +33,28 @@ namespace CGAL * * @tparam LCC a model of the `LinearCellComplex` concept. * @tparam PointRange a model of `RandomAccessContainer` with `Point_3` as value type, being compatible with the point type of LCC. - * @tparam TetraRange a model of `ConstRange` where each element is a container of 4 point indices, accessible using `operator[](int)`. + * @tparam TetrahedronRange a model of `ConstRange` where each element is a container of 4 point indices, accessible using `operator[](int)`. * * @param points range of 3D points. - * @param tetras range of tetrahedra, where each tetrahedron is represented by 4 indices corresponding to entries in `points`. + * @param tetrahedra range of tetrahedra, where each tetrahedron is represented by 4 indices corresponding to entries in `points`. * @param lcc the target linear cell complex. * * @return a descriptor to a dart of the created complex, or `LCC::null_descriptor` if the input is empty. * * @pre `LCC::dimension >= 3` and `LCC::ambient_dimension == 3`. - * @pre Indices in `tetras` must be valid 0-based indices into `points`. + * @pre Indices in `tetrahedra` must be valid 0-based indices into `points`. * * @sa `CGAL::triangulation_3_to_lcc()` */ - template + template typename LCC::Dart_descriptor tetrahedron_soup_to_lcc(const PointRange& points, - const TetraRange& tetras, + const TetrahedronRange& tetrahedra, LCC& lcc) { static_assert( LCC::dimension>=3 && LCC::ambient_dimension==3 ); - if (points.empty() || tetras.empty()) + if (points.empty() || tetrahedra.empty()) return LCC::null_descriptor; using Dart_descriptor = typename LCC::Dart_descriptor; @@ -80,7 +80,7 @@ namespace CGAL std::unordered_map< std::array, Dart_descriptor, Array_hasher> face_map; Dart_descriptor dart = LCC::null_descriptor; - for (const auto& t : tetras) + for (const auto& t : tetrahedra) { Dart_descriptor res = lcc.make_tetrahedron(vertices[t[0]], vertices[t[1]],