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 (parallel_for)
5// instead of igl::parallel_for. This means the winding number honours the
6// requested `num_threads` (like every other parallel section in wmtk) rather than
7// always grabbing all hardware cores.
8//
9// The actual algorithm is unchanged: it reuses igl::WindingNumberAABB (a
10// header-only hierarchical accelerator) so it stays as fast as igl::winding_number
11// while producing identical results.
12
13#include <Eigen/Core>
14
15// clang-format off
16#include <wmtk/utils/DisableWarnings.hpp>
17#include <igl/winding_number.h> // must precede WindingNumberAABB.h (its guard needs it)
18#include <igl/WindingNumberAABB.h>
19#include <wmtk/utils/EnableWarnings.hpp>
20// clang-format on
21
22#include <wmtk/threading/parallel_for.hpp>
23
24#include <algorithm>
25#include <cmath>
26
27namespace wmtk::utils {
28
29namespace detail {
30
42inline double signed_angle_2d(double ax, double ay, double bx, double by, double px, double py)
43{
44 // Gather vectors to source and destination, and their lengths.
45 double o2A[2] = {px - ax, py - ay};
46 double o2B[2] = {px - bx, py - by};
47 double o2Al = 0;
48 double o2Bl = 0;
49 for (int i = 0; i < 2; i++) {
50 o2Al += o2A[i] * o2A[i];
51 o2Bl += o2B[i] * o2B[i];
52 }
53 o2Al = std::sqrt(o2Al);
54 o2Bl = std::sqrt(o2Bl);
55 // Normalize, guarding the degenerate case where P coincides with an endpoint.
56 for (int i = 0; i < 2; i++) {
57 if (o2Al != 0) {
58 o2A[i] /= o2Al;
59 }
60 if (o2Bl != 0) {
61 o2B[i] /= o2Bl;
62 }
63 }
64 constexpr double two_pi = 2.0 * 3.14159265358979323846;
65 return -std::atan2(o2B[0] * o2A[1] - o2B[1] * o2A[0], o2B[0] * o2A[0] + o2B[1] * o2A[1]) /
66 two_pi;
67}
68
69} // namespace detail
70
78inline double
79winding_number_2d_point(const Eigen::MatrixXd& V, const Eigen::MatrixXi& E, double px, double py)
80{
81 double w = 0;
82 for (Eigen::Index f = 0; f < E.rows(); ++f) {
83 const Eigen::Index a = E(f, 0);
84 const Eigen::Index b = E(f, 1);
85 w += detail::signed_angle_2d(V(a, 0), V(a, 1), V(b, 0), V(b, 1), px, py);
86 }
87 return w;
88}
89
95inline void winding_number(
96 const Eigen::MatrixXd& V,
97 const Eigen::MatrixXi& F,
98 const Eigen::MatrixXd& O,
99 Eigen::VectorXd& W,
100 int num_threads)
101{
102 W.setZero(O.rows());
103 if (O.rows() == 0 || F.rows() == 0 || V.rows() == 0) return;
104
105 // Build the accelerator once (same as igl::winding_number for triangle meshes).
106 igl::WindingNumberAABB<Eigen::Matrix<double, 1, 3>, Eigen::MatrixXd, Eigen::MatrixXi> hier(
107 V,
108 F);
109 hier.grow();
110
111 // hier.winding_number(p) is const and used by igl the same way from parallel_for,
112 // so concurrent queries against the shared hierarchy are safe.
113 threading::parallel_for(
114 threading::range(0, static_cast<size_t>(O.rows())),
115 [&](const threading::range& r) {
116 for (int o = r.begin(); o < r.end(); ++o) {
117 W(o) = hier.winding_number(O.row(o));
118 }
119 },
120 std::max(num_threads, 1));
121}
122
141inline void winding_number_2d(
142 const Eigen::MatrixXd& V,
143 const Eigen::MatrixXi& E,
144 const Eigen::MatrixXd& O,
145 Eigen::VectorXd& W,
146 int num_threads)
147{
148 W.setZero(O.rows());
149 if (O.rows() == 0 || E.rows() == 0 || V.rows() == 0) return;
150
151 threading::parallel_for(
152 threading::range(0, static_cast<size_t>(O.rows())),
153 [&](const threading::range& r) {
154 for (size_t o = r.begin(); o < r.end(); ++o) {
155 const Eigen::Index i = static_cast<Eigen::Index>(o);
156 W(i) = winding_number_2d_point(V, E, O(i, 0), O(i, 1));
157 }
158 },
159 std::max(num_threads, 1));
160}
161
162} // namespace wmtk::utils