Wildmeshing Toolkit
Loading...
Searching...
No Matches
WindingNumber.hpp
1#pragma once
2
3// A local copy of libigl's winding-number evaluation, but with the per-query
4// parallelism driven by wmtk's own threading framework instead of igl::parallel_for.
5// This means the winding number honours the requested `num_threads` (like every other
6// parallel section in wmtk) rather than always grabbing all hardware cores.
7//
8// The 3D algorithm is igl's hierarchy (WindingNumberHierarchy, a deterministic copy of
9// igl::WindingNumberAABB), so it is as fast as igl::winding_number; unlike igl's, its
10// result does not depend on rand() or on the number of threads, to the last bit.
11
12#include <Eigen/Core>
13
14#include <wmtk/utils/WindingNumberHierarchy.hpp>
15
16// clang-format off
17#include <wmtk/utils/DisableWarnings.hpp>
18#include <igl/remove_unreferenced.h>
19#include <wmtk/utils/EnableWarnings.hpp>
20// clang-format on
21
22#include <wmtk/threading/dynamic_parallel_for.hpp>
23
24#include <algorithm>
25#include <cassert>
26#include <cmath>
27#include <vector>
28
29namespace wmtk::utils {
30
32inline constexpr size_t kWindingNumberChunk = 256;
33
34namespace detail {
35
47inline double signed_angle_2d(double ax, double ay, double bx, double by, double px, double py)
48{
49 // Gather vectors to source and destination, and their lengths.
50 double o2A[2] = {px - ax, py - ay};
51 double o2B[2] = {px - bx, py - by};
52 double o2Al = 0;
53 double o2Bl = 0;
54 for (int i = 0; i < 2; i++) {
55 o2Al += o2A[i] * o2A[i];
56 o2Bl += o2B[i] * o2B[i];
57 }
58 o2Al = std::sqrt(o2Al);
59 o2Bl = std::sqrt(o2Bl);
60 // Normalize, guarding the degenerate case where P coincides with an endpoint.
61 for (int i = 0; i < 2; i++) {
62 if (o2Al != 0) {
63 o2A[i] /= o2Al;
64 }
65 if (o2Bl != 0) {
66 o2B[i] /= o2Bl;
67 }
68 }
69 constexpr double two_pi = 2.0 * 3.14159265358979323846;
70 return -std::atan2(o2B[0] * o2A[1] - o2B[1] * o2A[0], o2B[0] * o2A[0] + o2B[1] * o2A[1]) /
71 two_pi;
72}
73
74} // namespace detail
75
83inline double
84winding_number_2d_point(const Eigen::MatrixXd& V, const Eigen::MatrixXi& E, double px, double py)
85{
86 double w = 0;
87 for (Eigen::Index f = 0; f < E.rows(); ++f) {
88 const Eigen::Index a = E(f, 0);
89 const Eigen::Index b = E(f, 1);
90 w += detail::signed_angle_2d(V(a, 0), V(a, 1), V(b, 0), V(b, 1), px, py);
91 }
92 return w;
93}
94
100inline void winding_number(
101 const Eigen::MatrixXd& V,
102 const Eigen::MatrixXi& F,
103 const Eigen::MatrixXd& O,
104 Eigen::VectorXd& W,
105 int num_threads)
106{
107 W.setZero(O.rows());
108 if (O.rows() == 0 || F.rows() == 0 || V.rows() == 0) return;
109
110 // Build the accelerator once (as igl::winding_number does for triangle meshes).
111 const WindingNumberHierarchy hier(V, F);
112
113 // hier.winding_number(p) is const and touches no shared state, so concurrent queries
114 // against the one hierarchy are safe.
115 //
116 // Handed out in chunks on demand rather than one fixed slice per thread: a query costs
117 // far more near the surface, where the hierarchy is descended to its leaves, than away
118 // from it, and the queries of a mesh are not in random order -- the cells of an
119 // arrangement cluster by location -- so fixed slices left most threads waiting on the one
120 // that drew the surface. Each query is evaluated on its own, so W does not depend on the
121 // schedule or the number of threads.
122 threading::dynamic_parallel_for(
123 static_cast<size_t>(O.rows()),
124 std::max(num_threads, 1),
125 kWindingNumberChunk,
126 [&](const size_t begin, const size_t end) {
127 for (size_t o = begin; o < end; ++o) {
128 W(o) = hier.winding_number(O.row(o));
129 }
130 });
131}
132
139inline bool winding_number_inside(double w)
140{
141 return w > 0.5;
142}
143
145inline bool any_winding_number_inside(const Eigen::VectorXd& W)
146{
147 return (W.array() > 0.5).any();
148}
149
159inline bool orient_winding_number(Eigen::VectorXd& W)
160{
161 if (W.size() == 0 || any_winding_number_inside(W)) return false;
162 W = -W;
163 return true;
164}
165
179inline void winding_number_by_group(
180 const Eigen::MatrixXd& V,
181 const Eigen::MatrixXi& F,
182 const std::vector<int>& group,
183 int n_groups,
184 const Eigen::MatrixXd& O,
185 Eigen::MatrixXd& W,
186 int num_threads)
187{
188 assert(group.size() == size_t(F.rows()));
189 W.setZero(O.rows(), n_groups);
190 std::vector<std::vector<int>> faces(n_groups);
191 for (Eigen::Index f = 0; f < F.rows(); ++f) {
192 assert(group[f] >= 0 && group[f] < n_groups);
193 faces[group[f]].push_back(int(f));
194 }
195 for (int g = 0; g < n_groups; ++g) {
196 if (faces[g].empty()) continue;
197 Eigen::MatrixXi Fg(faces[g].size(), 3);
198 for (size_t i = 0; i < faces[g].size(); ++i) Fg.row(i) = F.row(faces[g][i]);
199 // Only the group's own vertices, so that its hierarchy's root box is the group's.
200 Eigen::MatrixXd Vg;
201 Eigen::MatrixXi Fg_compact;
202 Eigen::VectorXi I;
203 igl::remove_unreferenced(V, Fg, Vg, Fg_compact, I);
204 Eigen::VectorXd w;
205 winding_number(Vg, Fg_compact, O, w, num_threads);
206 W.col(g) = w;
207 }
208}
209
228inline void winding_number_2d(
229 const Eigen::MatrixXd& V,
230 const Eigen::MatrixXi& E,
231 const Eigen::MatrixXd& O,
232 Eigen::VectorXd& W,
233 int num_threads)
234{
235 W.setZero(O.rows());
236 if (O.rows() == 0 || E.rows() == 0 || V.rows() == 0) return;
237
238 // In chunks on demand, as the 3D winding_number: see there.
239 threading::dynamic_parallel_for(
240 static_cast<size_t>(O.rows()),
241 std::max(num_threads, 1),
242 kWindingNumberChunk,
243 [&](const size_t begin, const size_t end) {
244 for (size_t o = begin; o < end; ++o) {
245 const Eigen::Index i = static_cast<Eigen::Index>(o);
246 W(i) = winding_number_2d_point(V, E, O(i, 0), O(i, 1));
247 }
248 });
249}
250
251} // namespace wmtk::utils