Wildmeshing Toolkit
Loading...
Searching...
No Matches
SurfaceTopology.hpp
1#pragma once
2
3#include <wmtk/simplex/Simplex.hpp>
4#include <wmtk/utils/Logger.hpp>
5
6#include <cstddef>
7#include <map>
8#include <set>
9#include <vector>
10
11namespace wmtk::utils {
12
23{
24 long long components = 0;
25 long long V = 0;
26 long long E = 0;
27 long long F = 0;
28 long long euler = 0; // V - E + F
29 long long boundary_loops = 0;
30 bool operator==(const SurfaceTopoSignature&) const = default;
31};
32
33namespace detail {
34// A tiny union-find over dense [0, n) indices, used to count connected components of the
35// surface and of its boundary-edge subgraph.
36struct DSU
37{
38 std::vector<long long> parent;
39 void init(long long n)
40 {
41 parent.resize(n);
42 for (long long i = 0; i < n; ++i) {
43 parent[i] = i;
44 }
45 }
46 long long find(long long x)
47 {
48 while (parent[x] != x) {
49 parent[x] = parent[parent[x]];
50 x = parent[x];
51 }
52 return x;
53 }
54 void unite(long long a, long long b) { parent[find(a)] = find(b); }
55};
56} // namespace detail
57
65template <class Mesh, class IsSurfaceFace>
66SurfaceTopoSignature surface_topology_signature(const Mesh& m, IsSurfaceFace is_surface)
67{
68 using Tuple = typename Mesh::Tuple;
69 constexpr size_t n_faces = Mesh::FACES_PER_CELL;
70
71 // Collect the tracked surface triangles (canonical faces, deduplicated by global fid),
72 // each as a sorted vertex triple.
73 std::vector<simplex::Face> faces;
74 for (size_t i = 0; i < m.cell_capacity(); ++i) {
75 if (!m.tuple_from_cell(i).is_valid(m)) {
76 continue;
77 }
78 for (size_t j = 0; j < n_faces; ++j) {
79 const Tuple f = m.tuple_from_face(i, j);
80 const size_t fid = f.fid(m);
81 if (fid != n_faces * i + j) {
82 continue; // visit each face once (canonical)
83 }
84 if (!is_surface(fid)) {
85 continue;
86 }
87 faces.emplace_back(m.simplex_from_face(f));
88 }
89 }
90
91 SurfaceTopoSignature sig;
92 sig.F = static_cast<long long>(faces.size());
93
94 // Dense-index the surface vertices; count edge incidences.
95 std::map<size_t, long long> vidx;
96 auto index_of = [&](size_t v) -> long long {
97 auto it = vidx.find(v);
98 if (it != vidx.end()) {
99 return it->second;
100 }
101 const long long id = static_cast<long long>(vidx.size());
102 vidx.emplace(v, id);
103 return id;
104 };
105 std::map<simplex::Edge, int> edge_count;
106 for (const simplex::Face& f : faces) {
107 const auto& vs = f.vertices();
108 index_of(vs[0]);
109 index_of(vs[1]);
110 index_of(vs[2]);
111 edge_count[simplex::Edge(vs[0], vs[1])]++;
112 edge_count[simplex::Edge(vs[1], vs[2])]++;
113 edge_count[simplex::Edge(vs[0], vs[2])]++;
114 }
115 sig.V = static_cast<long long>(vidx.size());
116 sig.E = static_cast<long long>(edge_count.size());
117 sig.euler = sig.V - sig.E + sig.F;
118
119 // Connected components of the surface (vertices joined by any surface edge).
120 {
121 detail::DSU dsu;
122 dsu.init(sig.V);
123 for (const simplex::Face& f : faces) {
124 const auto& vs = f.vertices();
125 const long long a = vidx[vs[0]], b = vidx[vs[1]], c = vidx[vs[2]];
126 dsu.unite(a, b);
127 dsu.unite(b, c);
128 }
129 std::set<long long> roots;
130 for (long long i = 0; i < sig.V; ++i) {
131 roots.insert(dsu.find(i));
132 }
133 sig.components = static_cast<long long>(roots.size());
134 }
135
136 // Boundary loops: a boundary edge is incident to exactly one surface face. On a
137 // manifold-with-boundary surface each connected component of the boundary-edge subgraph
138 // is a simple cycle, so #components == #loops.
139 {
140 detail::DSU bd;
141 bd.init(sig.V);
142 std::set<long long> bverts;
143 for (const auto& [e, cnt] : edge_count) {
144 if (cnt != 1) {
145 continue;
146 }
147 const long long ia = vidx[e.vertices()[0]], ib = vidx[e.vertices()[1]];
148 bd.unite(ia, ib);
149 bverts.insert(ia);
150 bverts.insert(ib);
151 }
152 std::set<long long> broots;
153 for (long long v : bverts) {
154 broots.insert(bd.find(v));
155 }
156 sig.boundary_loops = static_cast<long long>(broots.size());
157 }
158
159 return sig;
160}
161
166inline void warn_if_surface_topology_changed(
167 const SurfaceTopoSignature& before,
168 const SurfaceTopoSignature& after,
169 const char* where)
170{
171 if (after == before) {
172 return;
173 }
174 logger().error(
175 "[surface-swap] surface topology CHANGED in {}: components {}->{}, V {}->{}, "
176 "E {}->{}, F {}->{}, euler {}->{}, boundary_loops {}->{}",
177 where,
178 before.components,
179 after.components,
180 before.V,
181 after.V,
182 before.E,
183 after.E,
184 before.F,
185 after.F,
186 before.euler,
187 after.euler,
188 before.boundary_loops,
189 after.boundary_loops);
190}
191
192} // namespace wmtk::utils
A topological fingerprint of a tracked surface inside a tet mesh.
Definition SurfaceTopology.hpp:23
Definition SurfaceTopology.hpp:37