libigl v2.5.0
Loading...
Searching...
No Matches
WindingNumberAntipodalScene.h
Go to the documentation of this file.
1// This file is part of libigl, a simple c++ geometry processing library.
2//
3// Copyright (C) 2026 Philip Trettner <trettner@shapedcode.com>, Cedric Martens <cedric.martens@umontreal.ca>
4//
5// This Source Code Form is subject to the terms of the Mozilla Public License
6// v. 2.0. If a copy of the MPL was not distributed with this file, You can
7// obtain one at http://mozilla.org/MPL/2.0/.
8#ifndef IGL_WINDINGNUMBERANTIPODALSCENE_H
9#define IGL_WINDINGNUMBERANTIPODALSCENE_H
10
11#include "PI.h"
12#include "parallel_for.h"
13
14#include <Eigen/Core>
15
16#include <cassert>
17#include <cmath>
18#include <cstdint>
19#include <unordered_map>
20#include <vector>
21
22namespace igl
23{
48 template <typename Scalar>
50 {
51 public:
52 using Point = Eigen::Matrix<Scalar, 1, 3>;
53 using Direction = Eigen::Matrix<Scalar, 1, 3>;
54
55 private:
56 struct WeightedSeg
57 {
58 Point a;
59 Point b;
60 Scalar w;
61 };
62
63 public:
69 template <typename DerivedV, typename DerivedF>
71 const Eigen::MatrixBase<DerivedV> & V,
72 const Eigen::MatrixBase<DerivedF> & F,
73 const Direction & x0 = default_x0())
74 : m_x0(x0), m_face_count(static_cast<size_t>(F.rows()))
75 {
76 assert(V.cols() == 3 && "WindingNumberAntipodalScene: only 3D vertex positions are supported");
77 assert(F.cols() == 3 && "WindingNumberAntipodalScene: only triangle meshes are supported");
79 }
80
83 template <typename Intersector, typename Derivedp>
85 const Intersector & intersector,
86 const Eigen::MatrixBase<Derivedp> & p) const
87 {
88 const Point pp(static_cast<Scalar>(p(0)),
89 static_cast<Scalar>(p(1)),
90 static_cast<Scalar>(p(2)));
91 const Direction x1 = -m_x0;
92 Scalar area = Scalar(0);
93 for (const auto & ws : m_boundary)
94 {
95 const Point v0 = ws.a - pp;
96 const Point v1 = ws.b - pp;
97 area += ws.w * half_solid_angle_unorm(x1, v0, v1);
98 }
99 const Scalar frac = area / (Scalar(2) * Scalar(igl::PI));
100
101 using IO = typename Intersector::OriginType;
102 using ID = typename Intersector::DirectionType;
103 const IO io(static_cast<typename IO::Scalar>(pp(0)),
104 static_cast<typename IO::Scalar>(pp(1)),
105 static_cast<typename IO::Scalar>(pp(2)));
106 const ID id(static_cast<typename ID::Scalar>(m_x0(0)),
107 static_cast<typename ID::Scalar>(m_x0(1)),
108 static_cast<typename ID::Scalar>(m_x0(2)));
109 const int c = intersector.signedIntersectionsRay(io, id);
110
111 return frac + Scalar(c);
112 }
113
121 template <typename Intersector, typename DerivedO, typename DerivedW>
123 const Intersector & intersector,
124 const Eigen::MatrixBase<DerivedO> & O,
125 Eigen::PlainObjectBase<DerivedW> & W) const
126 {
127 W.resize(O.rows(), 1);
128
129 // Adaptive parallel-for threshold.
130 //
131 // The libigl thread pool has a roughly fixed ~1 ms TOTAL overhead per
132 // parallel_for invocation (not per iteration). So we only spawn the
133 // pool when the WHOLE batch is expected to take ≥ 1 ms.
134 //
135 // Per-query work heuristic:
136 // t_q ≈ 50 * B + 100 * sqrt(F) ns
137 // with B = boundary segment count, F = triangle count. Pool wins once
138 // t_q * O > 10^6 ns ⇒ O > 10^6 / t_q
139 // which is exactly parallel_for's `min_parallel` semantics.
140 //
141 // (This is a rough heuristic and should be revisited once parallel_for becomes lower-overhead)
142 const double t_q_ns =
143 50.0 * static_cast<double>(m_boundary.size()) +
144 100.0 * std::sqrt(static_cast<double>(m_face_count));
145 const size_t min_parallel = static_cast<size_t>(
146 std::ceil(1.0e6 / std::max(t_q_ns, 1.0)));
147
148 igl::parallel_for(O.rows(), [&](const int o)
149 {
150 W(o) = winding_number(intersector, O.row(o));
151 }, min_parallel);
152 }
153
155 const Direction & x0() const { return m_x0; }
157 size_t num_boundary_segments() const { return m_boundary.size(); }
159 size_t num_faces() const { return m_face_count; }
160
165 {
166 Direction d(Scalar(1),
167 Scalar(std::sqrt(2.0)),
168 Scalar(std::sqrt(3.0)));
169 return d / d.norm();
170 }
171
178 const Direction & x1, const Point & v0, const Point & v1)
179 {
180 const Scalar l0 = v0.norm();
181 const Scalar l1 = v1.norm();
182 const Scalar num = x1.dot(v0.cross(v1));
183 const Scalar denom = l0 * l1
184 + l1 * x1.dot(v0)
185 + l0 * x1.dot(v1)
186 + v0.dot(v1);
187 return std::atan2(num, denom);
188 }
189
199 template <typename DerivedV, typename DerivedF>
201 const Eigen::MatrixBase<DerivedV> & V,
202 const Eigen::MatrixBase<DerivedF> & F,
203 std::vector<WeightedSeg> & out)
204 {
205 out.clear();
206 std::unordered_map<std::uint64_t, int> counts;
207 counts.reserve(static_cast<size_t>(F.rows()) * 3);
208
209 const auto pack = [](int lo, int hi) -> std::uint64_t
210 {
211 return (static_cast<std::uint64_t>(static_cast<std::uint32_t>(lo)) << 32)
212 | static_cast<std::uint64_t>(static_cast<std::uint32_t>(hi));
213 };
214
215 for (Eigen::Index t = 0; t < F.rows(); ++t)
216 {
217 const int tri[3] = {
218 static_cast<int>(F(t, 0)),
219 static_cast<int>(F(t, 1)),
220 static_cast<int>(F(t, 2))
221 };
222 for (int e = 0; e < 3; ++e)
223 {
224 const int u = tri[e];
225 const int v = tri[(e + 1) % 3];
226 const int lo = u < v ? u : v;
227 const int hi = u < v ? v : u;
228 const std::uint64_t k = pack(lo, hi);
229 counts[k] += (u < v) ? +1 : -1;
230 }
231 }
232
233 out.reserve(counts.size());
234 for (const auto & kv : counts)
235 {
236 const int sum = kv.second;
237 if (sum == 0) continue;
238 const std::uint32_t lo = static_cast<std::uint32_t>(kv.first >> 32);
239 const std::uint32_t hi = static_cast<std::uint32_t>(kv.first & 0xFFFFFFFFu);
240 const Point a(static_cast<Scalar>(V(lo, 0)),
241 static_cast<Scalar>(V(lo, 1)),
242 static_cast<Scalar>(V(lo, 2)));
243 const Point b(static_cast<Scalar>(V(hi, 0)),
244 static_cast<Scalar>(V(hi, 1)),
245 static_cast<Scalar>(V(hi, 2)));
246 WeightedSeg ws;
247 if (sum > 0) { ws.a = a; ws.b = b; ws.w = static_cast<Scalar>(sum); }
248 else { ws.a = b; ws.b = a; ws.w = static_cast<Scalar>(-sum); }
249 out.push_back(ws);
250 }
251 }
252
253 std::vector<WeightedSeg> m_boundary;
255 size_t m_face_count = 0;
256 };
257}
258
259#endif
Eigen::Matrix< Scalar, 1, 3 > Point
Definition WindingNumberAntipodalScene.h:52
void winding_number(const Intersector &intersector, const Eigen::MatrixBase< DerivedO > &O, Eigen::PlainObjectBase< DerivedW > &W) const
Batch query, parallelized via igl::parallel_for.
Definition WindingNumberAntipodalScene.h:122
Direction m_x0
Definition WindingNumberAntipodalScene.h:254
static void build_boundary_segments(const Eigen::MatrixBase< DerivedV > &V, const Eigen::MatrixBase< DerivedF > &F, std::vector< WeightedSeg > &out)
Extract the open boundary as oriented, weighted edge segments.
Definition WindingNumberAntipodalScene.h:200
std::vector< WeightedSeg > m_boundary
Definition WindingNumberAntipodalScene.h:253
static Scalar half_solid_angle_unorm(const Direction &x1, const Point &v0, const Point &v1)
Half the signed solid angle subtended by the spherical triangle (x1, v0, v1) at the origin,...
Definition WindingNumberAntipodalScene.h:177
size_t num_faces() const
Number of triangles in the original mesh.
Definition WindingNumberAntipodalScene.h:159
const Direction & x0() const
Reference direction x0 used to evaluate this scene.
Definition WindingNumberAntipodalScene.h:155
Eigen::Matrix< Scalar, 1, 3 > Direction
Definition WindingNumberAntipodalScene.h:53
size_t m_face_count
Definition WindingNumberAntipodalScene.h:255
size_t num_boundary_segments() const
Number of weighted boundary edge segments.
Definition WindingNumberAntipodalScene.h:157
static Direction default_x0()
Default reference direction: normalize(1, sqrt(2), sqrt(3)).
Definition WindingNumberAntipodalScene.h:164
WindingNumberAntipodalScene(const Eigen::MatrixBase< DerivedV > &V, const Eigen::MatrixBase< DerivedF > &F, const Direction &x0=default_x0())
Build a scene from a 3D triangle mesh.
Definition WindingNumberAntipodalScene.h:70
Scalar winding_number(const Intersector &intersector, const Eigen::MatrixBase< Derivedp > &p) const
Single-point query: full generalized winding number at p (fractional + signed integer crossings).
Definition WindingNumberAntipodalScene.h:84
Definition AABB.h:18
bool parallel_for(const Index loop_size, const FunctionType &func, const size_t min_parallel=0)
Functional implementation of a basic, open-mp style, parallel for loop.
Definition parallel_for.h:123
void sum(const Eigen::SparseMatrix< T > &X, const int dim, Eigen::SparseVector< T > &S)
Sum the columns or rows of a sparse matrix.
constexpr double PI
π
Definition PI.h:18