4 f(
f), max_depth(max_depth)
6 this->xmin = bounds[0];
7 this->xmax = bounds[1];
8 this->ymin = bounds[2];
9 this->ymax = bounds[3];
10 this->N = 1 << this->max_depth;
15 this->set_iso_value(lvl);
16 MSGraph first_pass_graph = this->build_adjacency(this->compute_segments());
17 std::set<Path> first_pass_paths = this->extract_paths(first_pass_graph);
18 std::set<Path> second_pass_paths = this->correct_topology(first_pass_paths);
19 return second_pass_paths;
22std::unique_ptr<Cell> MarchingSquaresExtractor::build(
int i,
int j,
int depth,
int force_refine) {
23 std::array<Index, 4> verts = this->cell_vertices({i, j}, depth);
24 std::array<double, 4>
vals;
25 for (
size_t i = 0; i < 4; i++)
26 vals[i] = this->vertex_value(verts[i]);
28 std::cout <<
"Vertex values: ";
29 for (
auto &&v :
vals) {
30 std::cout <<
v <<
",";
32 std::cout << std::endl;
35 auto positive = [] (
double a) {
return a > 0; };
36 bool topology = !std::all_of(
vals.begin(),
vals.end(), positive) && std::any_of(
vals.begin(),
vals.end(), positive);
38 std::cout << std::boolalpha <<
"Topology = " << topology << std::endl;
40 if (!topology && depth == this->max_depth)
43 bool refine = topology || depth < force_refine;
44 if (!refine && depth < this->max_depth - 1) {
45 int I0 = verts[0].first,
I1 = verts[1].first, J0 = verts[0].second, J1 = verts[2].second;
46 int Im = (
I0 +
I1) / 2, Jm = (J0 + J1) / 2;
47 double fc = this->vertex_value({Im, Jm});
48 bool inside_loop = fc *
vals[0] < 0;
51 std::set<Index> edge_midpoints {{Im, J0}, {
I1, Jm}, {Im, J1}, {
I0, Jm}};
52 auto opposite = [
this,
vals] (
Index ijm) {
return this->vertex_value(ijm) *
vals[0] < 0; };
53 inside_loop = std::any_of(edge_midpoints.begin(), edge_midpoints.end(), opposite);
56 int Iq1 = (3 *
I0 +
I1) / 4, Iq2 = (
I0 + 3 *
I1) / 4;
57 int Jq1 = (3 * J0 + J1) / 4, Jq2 = (J0 + 3 * J1) / 4;
58 std::set<Index> quarter_points {{Iq1, Jq1}, {Iq2, Jq1}, {Iq2, Jq2}, {Iq1, Jq2}};
59 inside_loop = std::any_of(quarter_points.begin(), quarter_points.end(), opposite);
63 refine = refine || inside_loop;
66 if (!refine || depth == this->max_depth) {
67 std::cout << (topology ?
"Active" :
"Empty") <<
" leaf" << std::endl;
71 std::array<std::unique_ptr<Cell>, 4> children;
72 std::array<Index, 4> deltas {{{0, 0}, {1, 0}, {1, 1}, {0, 1}}};
73 for (
size_t k = 0; k < 4; k++)
74 children[k] = this->build(2 * i + deltas[k].first, 2 * j + deltas[k].second, depth + 1, force_refine);
79Index MarchingSquaresExtractor::point_to_idx(
Point xy)
const {
80 double u = (xy.first - this->xmin) / (this->xmax - this->xmin);
81 double v = (xy.second - this->ymin) / (this->ymax - this->ymin);
83 int I =
static_cast<int>(u * this->N);
84 int J =
static_cast<int>(
v * this->N);
89Point MarchingSquaresExtractor::idx_to_point(
Index ij)
const {
90 double x = this->xmin + (this->xmax - this->xmin) * ij.first / this->N;
91 double y = this->ymin + (this->ymax - this->ymin) * ij.second / this->N;
96std::array<Index, 4> MarchingSquaresExtractor::cell_vertices(
Index ij, std::size_t depth)
const {
97 int scale = 1 << (this->max_depth - depth);
99 int I0 = ij.first * scale;
100 int I1 = (ij.first + 1) * scale;
101 int J0 = ij.second * scale;
102 int J1 = (ij.second + 1) * scale;
104 return {{{
I0, J0}, {
I1, J0}, {
I1, J1}, {
I0, J1}}};
107double MarchingSquaresExtractor::vertex_value(
Index ij) {
108 if (!this->vertices.contains(ij)) {
109 Point xy = idx_to_point(ij);
110 this->vertices.emplace(ij, this->f(xy.first, xy.second) - this->k);
113 return this->vertices.at(ij);
116Cell* MarchingSquaresExtractor::locate_leaf(
Point xy)
const {
117 Cell* cell = this->root.get();
118 Index IJ = point_to_idx(xy);
120 while (cell->
children.has_value()) {
122 int Im = (verts[0].first + verts[2].first) / 2;
123 int Jm = (verts[0].second + verts[2].second) / 2;
126 cell = IJ.second < Jm ? cell->
children.value()[0].get() : cell->
children.value()[3].get();
128 cell = IJ.second < Jm ? cell->
children.value()[1].get() : cell->
children.value()[2].get();
134Point MarchingSquaresExtractor::edge_point(
Cell *cell,
int edge) {
139 Index IJa = verts[ab.first];
140 Index IJb = verts[ab.second];
142 double fa =
vals[ab.first];
143 double fb =
vals[ab.second];
144 double t = -fa / (fb - fa);
146 Point xy_a = idx_to_point(IJa);
147 Point xy_b = idx_to_point(IJb);
150 xy_a.first + t * (xy_b.first - xy_a.first),
151 xy_a.second + t * (xy_b.second - xy_a.second)
155std::set<std::pair<int, int>> MarchingSquaresExtractor::get_crossed_edges(
Cell *cell) {
157 for (
size_t i = 0; i < 4; i++)
158 if (cell->
values[i] > 0) mask |= (1 << i);
160 if (mask == 5 || mask == 10) {
162 int Im = (verts[0].first + verts[2].first) / 2;
163 int Jm = (verts[0].second + verts[2].second) / 2;
164 double fc = vertex_value({Im, Jm});
165 mask = fc * cell->
values[0] > 0 ? 5 : 10;
171bool MarchingSquaresExtractor::point_on_boundary(
Point xy,
double tol)
const {
172 double x = xy.first,
y = xy.second;
174 std::abs(x - this->xmin) < tol ||
175 std::abs(x - this->xmax) < tol ||
176 std::abs(y - this->ymin) < tol ||
177 std::abs(y - this->ymax) < tol
181void MarchingSquaresExtractor::set_iso_value(
double lvl) {
183 this->vertices.clear();
184 this->root = this->build(0, 0, 0);
185 this->active_cells.clear();
186 this->update_active_cells(this->root.get());
188 std::cout <<
"Active cells: " << this->active_cells.size() << std::endl;
191void MarchingSquaresExtractor::update_active_cells(
Cell *cell) {
196 this->active_cells.emplace(cell);
200 for (std::unique_ptr<Cell>& child : cell->children.value()) {
201 this->update_active_cells(child.get());
205std::vector<Segment> MarchingSquaresExtractor::compute_segments() {
206 std::vector<Segment> segments;
208 for (
Cell* cell : this->active_cells) {
209 auto crossed_edges = this->get_crossed_edges(cell);
210 for (
auto edge_pair : crossed_edges) {
211 Point p0 = this->edge_point(cell, edge_pair.first);
212 Point p1 = this->edge_point(cell, edge_pair.second);
213 segments.emplace_back(p0, p1);
220MSGraph MarchingSquaresExtractor::build_adjacency(std::vector<Segment> segments)
const {
221 double coord_max = std::max({std::abs(this->xmin), std::abs(this->xmax), std::abs(this->ymin), std::abs(this->ymax)});
222 double epsilon = 2 * coord_max / INT32_MAX;
223 auto quantize = [
epsilon] (
Point p) -> std::pair<int, int> {
224 return {
static_cast<int>(p.first /
epsilon),
static_cast<int>(p.second /
epsilon)};
227 std::map<Index, Point> vertices;
228 std::map<Index, std::vector<Index>> adjacency;
230 for (
auto& s : segments) {
231 Index k0 = quantize(
s.first);
232 Index k1 = quantize(
s.second);
233 vertices.emplace(k0,
s.first);
234 vertices.emplace(k1,
s.second);
236 if (adjacency.contains(k0))
237 adjacency.at(k0).emplace_back(k1);
239 adjacency.emplace(k0, std::vector {k1});
241 if (adjacency.contains(k1))
242 adjacency.at(k1).emplace_back(k0);
244 adjacency.emplace(k1, std::vector {k0});
247 return MSGraph {std::move(vertices), std::move(adjacency)};
250std::set<Path> MarchingSquaresExtractor::extract_paths(
MSGraph graph)
const {
252 std::set<Path> paths;
253 std::set<Index> path_ends;
255 while (!unused.empty()) {
256 auto start = *unused.begin();
259 if (graph.
adjacency.at(start).size() == 2) {
260 std::stack<Index> stack;
262 std::set<Index> seen = {start};
264 bool has_open_start {
false};
265 while (!stack.empty()) {
266 Index u = stack.top();
271 has_open_start =
true;
275 for (
auto v : graph.adjacency.at(u)) {
276 if (!seen.contains(v)) {
286 std::vector<Index> path = {start};
290 if (path_ends.contains(start))
continue;
295 bool has_next {
false};
296 for (
Index v : neigh) {
303 if (!has_next)
break;
305 path.emplace_back(next);
310 if (cur == start)
break;
313 path_ends.emplace(start);
314 path_ends.emplace(cur);
317 for (
Index idx : path)
318 point_path.emplace_back(graph.vertices.at(idx));
320 paths.emplace(point_path);
327std::set<Point> MarchingSquaresExtractor::detect_dangling_ends(std::set<Path> paths)
const {
328 std::set<Point> dangling;
329 for (
Path p : paths) {
330 if (p.front() != p.back() && !(point_on_boundary(p.front()) && point_on_boundary(p.back()))) {
331 dangling.emplace(p.front());
332 dangling.emplace(p.back());
338void MarchingSquaresExtractor::refine_leaf(
Cell *leaf) {
343 int scale = 1 << (this->max_depth -
d);
345 double i = IJ0.first / scale, j = IJ0.second / scale;
346 std::unique_ptr<Cell> refined = this->build(i, j, d, std::min(d + 2, this->max_depth));
347 *leaf = std::move(*refined);
350std::set<Path> MarchingSquaresExtractor::correct_topology(std::set<Path> raw_paths) {
351 std::set<Point> dangling = this->detect_dangling_ends(raw_paths);
352 std::set<Cell*> to_refine;
354 for (
Point p : dangling)
355 to_refine.emplace(this->locate_leaf(p));
357 for (
Cell* cell : to_refine)
358 this->refine_leaf(cell);
360 this->update_active_cells(this->root.get());
361 MSGraph updated_graph = this->build_adjacency(this->compute_segments());
362 return this->extract_paths(updated_graph);
std::unordered_set< T > get_keys(const std::map< T, U > &map)
Extracts the key set from a std::map into an unordered_set.
constexpr std::complex< double > I
std::pair< int, int > Index
const std::vector< std::set< Index > > MASK_LOOKUP
std::vector< Point > Path
std::function< double(double, double)> ScalarField2D
std::pair< double, double > Point
double f(double x)
Wilson special function f depending on x.
std::array< Index, 4 > vertices
std::array< double, 4 > values
static const std::vector< Index > EDGES
std::optional< std::array< std::unique_ptr< Cell >, 4 > > children
std::map< Index, std::vector< Index > > adjacency