1 #include <catch2/catch_test_macros.hpp>
2 #include <nlohmann/json.hpp>
3 #include <tools/DEBUG_EdgeMesh.hpp>
4 #include <tools/DEBUG_TriMesh.hpp>
5 #include <tools/DEBUG_Tuple.hpp>
6 #include <tools/EdgeMesh_examples.hpp>
7 #include <tools/TriMesh_examples.hpp>
11 #include <wmtk/components/isotropic_remeshing/internal/IsotropicRemeshing.hpp>
12 #include <wmtk/components/isotropic_remeshing/internal/IsotropicRemeshingOptions.hpp>
15 #include <wmtk/components/utils/Paths.hpp>
44 const std::filesystem::path
data_dir = WMTK_DATA_DIR;
49 for (
auto& child_data : p_mul_manager.children()) {
50 PrimitiveType map_ptype = child_data.mesh->top_simplex_type();
51 auto parent_to_child_accessor =
52 parent.create_const_accessor<int64_t>(child_data.map_handle);
53 for (int64_t parent_gid = 0; parent_gid < parent.capacity(map_ptype); ++parent_gid) {
54 auto parent_to_child_data = parent_to_child_accessor.const_vector_attribute(
55 parent.tuple_from_id(map_ptype, parent_gid));
56 auto [parent_tuple, child_tuple] =
62 TEST_CASE(
"smoothing_mesh",
"[components][isotropic_remeshing][2D]")
64 using namespace operations;
69 json input_component_json = {
71 {
"name",
"input_mesh"},
72 {
"cell_dimension", 2},
73 {
"file", (
data_dir /
"bumpyDice.msh").
string()},
75 {
"tetrahedron_attributes", json::array()}};
79 auto mesh_in = cache.
read_mesh(input_component_json[
"name"]);
86 AttributesUpdateWithFunction op(m);
87 op.set_function(VertexLaplacianSmooth(pos_attribute));
94 for (
int i = 0; i < 3; ++i) {
112 TEST_CASE(
"smoothing_simple_examples",
"[components][isotropic_remeshing][2D]")
114 using namespace operations;
115 using namespace tri_mesh;
117 SECTION(
"hex_plus_two")
119 DEBUG_TriMesh mesh = wmtk::tests::hex_plus_two_with_position();
125 auto pos = mesh.create_accessor<
double>(pos_attribute);
129 AttributesUpdateWithFunction op(mesh);
130 op.set_function(VertexLaplacianSmooth(pos_attribute));
142 SECTION(
"edge_region")
144 DEBUG_TriMesh mesh = wmtk::tests::edge_region_with_position();
150 auto pos = mesh.create_accessor<
double>(pos_attribute);
156 AttributesUpdateWithFunction op(mesh);
157 op.set_function(VertexLaplacianSmooth(pos_attribute));
163 for (
size_t i = 0; i < 10; ++i) {
171 CHECK((p4_after_smooth -
Eigen::Vector3d{1, 0, 0}).squaredNorm() < 1e-10);
175 CHECK((p5_after_smooth -
Eigen::Vector3d{2, 0, 0}).squaredNorm() < 1e-10);
179 TEST_CASE(
"tangential_smoothing",
"[components][isotropic_remeshing][2D]")
181 using namespace operations;
183 DEBUG_TriMesh mesh = wmtk::tests::hex_plus_two_with_position();
189 auto pos = mesh.create_accessor<
double>(pos_attribute);
206 pos.vector_attribute(v4) = p_init;
208 AttributesUpdateWithFunction op(mesh);
209 op.set_function(VertexTangentialLaplacianSmooth(pos_attribute));
219 CHECK((after_smooth - target).squaredNorm() == 0);
222 TEST_CASE(
"tangential_smoothing_boundary",
"[components][isotropic_remeshing][2D]")
224 using namespace operations;
225 using namespace tri_mesh;
227 DEBUG_TriMesh mesh = wmtk::tests::hex_plus_two_with_position();
233 auto pos = mesh.create_accessor<
double>(pos_attribute);
250 pos.vector_attribute(v1) = p_init;
252 AttributesUpdateWithFunction op(mesh);
253 op.set_function(VertexTangentialLaplacianSmooth(pos_attribute));
260 CHECK((after_smooth -
Eigen::Vector3d{1.5, p_init[1], p_init[2]}).squaredNorm() == 0);
263 TEST_CASE(
"split_long_edges",
"[components][isotropic_remeshing][split][2D]")
265 using namespace operations;
269 DEBUG_TriMesh mesh = wmtk::tests::edge_region_with_position();
275 op.set_new_attribute_strategy(
277 SplitBasicStrategy::None,
278 SplitRibBasicStrategy::Mean);
281 auto pos = mesh.create_accessor<
double>(pos_attribute);
290 double min_split_length_squared = -1;
294 min_split_length_squared = 6.4;
295 op.add_invariant(std::make_shared<MinEdgeLengthInvariant>(
297 pos_attribute.
as<
double>(),
298 min_split_length_squared));
303 size_t n_iterations = 0;
304 for (; n_iterations < 10; ++n_iterations) {
308 if (n_vertices_new == n_vertices) {
311 n_vertices = n_vertices_new;
315 CHECK(n_iterations == 1);
316 REQUIRE(n_vertices == 11);
319 auto pos = mesh.create_accessor<
double>(pos_attribute);
321 CHECK((pos.vector_attribute(v10) -
Eigen::Vector3d{1.5, 0, 0}).squaredNorm() == 0);
325 min_split_length_squared = 3.5;
326 op.add_invariant(std::make_shared<MinEdgeLengthInvariant>(
328 pos_attribute.
as<
double>(),
329 min_split_length_squared));
334 size_t n_iterations = 0;
335 for (; n_iterations < 10; ++n_iterations) {
339 if (n_vertices_new == n_vertices) {
342 n_vertices = n_vertices_new;
346 CHECK(n_iterations < 5);
347 CHECK(n_vertices == 15);
351 auto pos = mesh.create_accessor<
double>(pos_attribute);
354 const Eigen::Vector3d p1 = pos.vector_attribute(mesh.switch_vertex(e));
355 const double l_squared = (p1 - p0).squaredNorm();
356 CHECK(l_squared < min_split_length_squared);
360 TEST_CASE(
"collapse_short_edges",
"[components][isotropic_remeshing][collapse][2D]")
362 using namespace operations;
364 DEBUG_TriMesh mesh = wmtk::tests::edge_region_with_position();
369 EdgeCollapse op(mesh);
370 op.add_invariant(std::make_shared<MultiMeshLinkConditionInvariant>(mesh));
371 op.set_new_attribute_strategy(pos_attribute, CollapseBasicStrategy::Mean);
376 auto pos = mesh.create_accessor<
double>(pos_attribute);
385 std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<
double>(), 0.1));
390 size_t n_iterations = 0;
391 for (; n_iterations < 10; ++n_iterations) {
395 if (n_vertices_new == n_vertices) {
398 n_vertices = n_vertices_new;
402 REQUIRE(n_iterations == 1);
403 REQUIRE(n_vertices == 9);
407 #if defined(WMTK_ENABLE_HASH_UPDATE)
408 REQUIRE(mesh.is_valid_with_hash(v5));
410 REQUIRE(mesh.is_valid(v5));
413 auto pos = mesh.create_accessor<
double>(pos_attribute);
417 SECTION(
"towards_boundary_true")
420 auto pos = mesh.create_accessor<
double>(pos_attribute);
428 auto tmp = std::make_shared<CollapseNewAttributeStrategy<double>>(pos_attribute);
429 tmp->set_strategy(CollapseBasicStrategy::Mean);
430 tmp->set_simplex_predicate(BasicSimplexPredicate::IsInterior);
431 op.set_new_attribute_strategy(pos_attribute, tmp);
435 std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<
double>(), 0.1));
440 size_t n_iterations = 0;
441 for (; n_iterations < 10; ++n_iterations) {
445 if (n_vertices_new == n_vertices) {
448 n_vertices = n_vertices_new;
452 REQUIRE(n_iterations == 1);
453 REQUIRE(n_vertices == 9);
456 #if defined(WMTK_ENABLE_HASH_UPDATE)
457 REQUIRE(mesh.is_valid_with_hash(v0));
459 REQUIRE(mesh.is_valid(v0));
462 auto pos = mesh.create_accessor<
double>(pos_attribute);
466 SECTION(
"towards_boundary_false")
469 auto pos = mesh.create_accessor<
double>(pos_attribute);
476 std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<
double>(), 0.1));
483 size_t n_iterations = 0;
484 for (; n_iterations < 10; ++n_iterations) {
488 if (n_vertices_new == n_vertices) {
491 n_vertices = n_vertices_new;
495 REQUIRE(n_iterations == 1);
496 REQUIRE(n_vertices == 9);
499 #if defined(WMTK_ENABLE_HASH_UPDATE)
500 REQUIRE(mesh.is_valid_with_hash(v0));
502 REQUIRE(mesh.is_valid(v0));
505 auto pos = mesh.create_accessor<
double>(pos_attribute);
509 SECTION(
"collapse_boundary_true")
512 auto pos = mesh.create_accessor<
double>(pos_attribute);
519 std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<
double>(), 0.1));
528 #if defined(WMTK_ENABLE_HASH_UPDATE)
529 REQUIRE(mesh.is_valid_with_hash(v0));
531 REQUIRE(mesh.is_valid(v0));
534 auto pos = mesh.create_accessor<
double>(pos_attribute);
538 SECTION(
"collapse_boundary_false")
541 auto pos = mesh.create_accessor<
double>(pos_attribute);
548 std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<
double>(), 0.1));
560 TEST_CASE(
"swap_edge_for_valence",
"[components][isotropic_remeshing][swap][2D]")
562 using namespace operations;
564 DEBUG_TriMesh mesh = wmtk::tests::embedded_diamond();
566 composite::TriEdgeSwap op(mesh);
569 op.collapse().add_invariant(std::make_shared<MultiMeshLinkConditionInvariant>(mesh));
571 Tuple swap_edge = mesh.edge_tuple_with_vs_and_t(6, 7, 5);
578 SECTION(
"single_op_fail")
580 op.add_invariant(std::make_shared<invariants::ValenceImprovementInvariant>(mesh));
583 SECTION(
"swap_success")
588 REQUIRE(!ret.empty());
589 const Tuple& return_tuple = ret[0].tuple();
590 swap_edge = return_tuple;
591 long id0 = mesh.id_vertex(swap_edge);
592 long id1 = mesh.id_vertex(mesh.switch_vertex(swap_edge));
599 CHECK(vertex_one_ring(mesh, v3).size() == 7);
600 CHECK(vertex_one_ring(mesh, v10).size() == 7);
601 CHECK(vertex_one_ring(mesh, v6).size() == 5);
602 CHECK(vertex_one_ring(mesh, v7).size() == 5);
605 op.add_invariant(std::make_shared<invariants::ValenceImprovementInvariant>(mesh));
611 REQUIRE(!ret.empty());
612 const Tuple& return_tuple = ret[0].tuple();
613 swap_edge = return_tuple;
615 CHECK(mesh.id(
Simplex::vertex(mesh, mesh.switch_vertex(swap_edge))) == 6);
617 SECTION(
"with_scheduler")
630 CHECK(vertex_one_ring(mesh, v3).size() == 6);
631 CHECK(vertex_one_ring(mesh, v10).size() == 6);
632 CHECK(vertex_one_ring(mesh, v6).size() == 6);
633 CHECK(vertex_one_ring(mesh, v7).size() == 6);
638 const Tuple e = mesh.edge_tuple_with_vs_and_t(6, 7, 5);
639 op.add_invariant(std::make_shared<invariants::ValenceImprovementInvariant>(mesh));
645 TEST_CASE(
"component_isotropic_remeshing",
"[components][isotropic_remeshing][2D]")
649 if constexpr (
true) {
650 const std::filesystem::path input_file =
data_dir /
"small.msh";
651 json input_component_json = {
652 {
"name",
"input_mesh"},
653 {
"file", input_file.string()},
655 {
"tetrahedron_attributes", json::array()}};
659 if constexpr (
true) {
660 json mesh_isotropic_remeshing_json = R
"({
661 "input": "input_mesh",
662 "output": "output_mesh",
663 "attributes": {"position": "vertices", "inversion_position": [], "other_positions": [], "update_other_positions": false},
668 "lock_boundary": true,
669 "use_for_periodic": false,
670 "dont_disable_split": false
675 if constexpr (
true) {
676 json component_json = R
"({
677 "input": "output_mesh",
678 "attributes": {"position": "vertices"},
679 "file": "bunny_isotropic_remeshing"
698 TEST_CASE(
"remeshing_tetrahedron",
"[components][isotropic_remeshing][2D]")
705 DEBUG_TriMesh mesh = tetrahedron_with_position();
708 std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
709 pass_through_attributes.emplace_back(
713 auto acc = mesh.create_accessor<int64_t>(pass_through_attributes[0]);
714 acc.scalar_attribute(mesh.face_tuple_from_vids(0, 1, 2)) = 1;
715 acc.scalar_attribute(mesh.face_tuple_from_vids(0, 1, 3)) = 1;
721 pass_through_attributes,
738 mesh.serialize(writer);
742 TEST_CASE(
"remeshing_with_boundary",
"[components][isotropic_remeshing][2D]")
749 TriMesh mesh = edge_region_with_position();
751 std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
753 SECTION(
"lock_boundary_false")
757 pass_through_attributes,
765 size_t n_boundary_edges = 0;
771 CHECK(n_boundary_edges > 8);
786 SECTION(
"lock_boundary_true")
790 pass_through_attributes,
799 size_t n_boundary_edges = 0;
805 CHECK(n_boundary_edges == 8);
821 TEST_CASE(
"remeshing_preserve_topology",
"[components][isotropic_remeshing][2D][.]")
826 DEBUG_TriMesh mesh = edge_region_with_position();
830 std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
833 auto tag_accessor = mesh.create_accessor<int64_t>(tag_handle);
835 if (mesh.is_boundary_edge(e)) {
836 tag_accessor.scalar_attribute(e) = 1;
838 tag_accessor.scalar_attribute(e) = 0;
841 std::shared_ptr<Mesh> child_ptr =
848 REQUIRE(mesh.get_child_meshes().size() == 1);
849 mesh.multi_mesh_manager().check_map_valid(mesh);
850 const auto& child_mesh = *child_ptr;
857 pass_through_attributes,
865 REQUIRE(mesh.is_connectivity_valid());
866 mesh.multi_mesh_manager().check_map_valid(mesh);
869 size_t n_boundary_edges = 0;
871 if (mesh.is_boundary_edge(e)) {
879 ParaviewWriter writer(
"remeshing_test",
"vertices", mesh,
true,
true,
true,
false);
880 mesh.serialize(writer);
884 TEST_CASE(
"remeshing_preserve_topology_realmesh",
"[components][isotropic_remeshing][2D][.]")
887 using namespace operations;
894 json input_component_json = {
896 {
"name",
"input_mesh"},
897 {
"cell_dimension", 2},
898 {
"file", (
data_dir /
"circle.msh").
string()},
899 {
"ignore_z",
false}};
902 auto m = cache.
read_mesh(input_component_json[
"name"]);
903 tests::DEBUG_TriMesh& mesh =
static_cast<tests::DEBUG_TriMesh&
>(*m);
906 std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
909 auto tag_accessor = mesh.create_accessor<int64_t>(tag_handle);
911 if (mesh.is_boundary_edge(e)) {
912 tag_accessor.scalar_attribute(e) = 1;
914 tag_accessor.scalar_attribute(e) = 0;
917 std::shared_ptr<Mesh> child_ptr =
924 REQUIRE(mesh.get_child_meshes().size() == 1);
931 for (
int i = 0; i < 25; i++) {
934 pass_through_attributes,
941 REQUIRE(mesh.is_connectivity_valid());
942 mesh.multi_mesh_manager().check_map_valid(mesh);
946 auto child_vertex_handle =
948 auto child_vertex_accessor = child_ptr->create_accessor<int64_t>(child_vertex_handle);
950 auto parent_vertex_handle =
952 auto parent_vertex_accessor = mesh.create_accessor<int64_t>(parent_vertex_handle);
956 child_vertex_accessor.vector_attribute(v) =
957 parent_vertex_accessor.vector_attribute(parent_v);
970 mesh.serialize(writer);
973 cache.
get_cache_path() /
"remeshing_test_circle_child_mesh_final",
980 child_ptr->serialize(writer2);
984 TEST_CASE(
"remeshing_realmesh",
"[components][isotropic_remeshing][2D][.]")
987 using namespace operations;
992 json input_component_json = {
994 {
"name",
"input_mesh"},
995 {
"cell_dimension", 2},
996 {
"file", (
data_dir /
"circle.msh").
string()},
997 {
"ignore_z",
false}};
1000 auto m = cache.
read_mesh(input_component_json[
"name"]);
1004 std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
1028 pass_through_attributes,
void serialize(MeshWriter &writer, const Mesh *local_root=nullptr) const
attribute::MeshAttributeHandle get_attribute_handle(const std::string &name, const PrimitiveType ptype) const
std::vector< Tuple > get_all(PrimitiveType type) const
Generate a vector of Tuples from global vertex/edge/triangle/tetrahedron index.
SchedulerStats run_operation_on_all(operations::Operation &op)
bool is_connectivity_valid() const final override
bool is_boundary_edge(const Tuple &tuple) const
auto as() const -> const held_handle_type< held_type_from_primitive< T >()> &
std::filesystem::path get_cache_path() const
Get the path of the cache folder.
std::shared_ptr< Mesh > read_mesh(const std::string &name) const
Load a mesh from cache.
const std::vector< Simplex > & simplex_vector() const
Return const reference to the simplex vector.
static Simplex edge(const Mesh &m, const Tuple &t)
static Simplex vertex(const Mesh &m, const Tuple &t)
void isotropic_remeshing(const IsotropicRemeshingOptions &options)
std::shared_ptr< Mesh > extract_and_register_child_mesh_from_tag(Mesh &m, const std::string &tag, const int64_t tag_value, const PrimitiveType pt, bool child_is_free)
extract a child mesh based on the given tag and tag value, and register it to the input (parent) mesh
std::tuple< Tuple, Tuple > vectors_to_tuples(const Eigen::Ref< const TwoTupleVector > &v)
SimplexCollection link(const Mesh &mesh, const simplex::Simplex &simplex, const bool sort_and_clean)
Vector< double, 3 > Vector3d
void print_tuple_map_iso(const DEBUG_TriMesh &parent, const DEBUG_MultiMeshManager &p_mul_manager)
const std::filesystem::path data_dir
TEST_CASE("smoothing_mesh", "[components][isotropic_remeshing][2D]")