JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
Triangulation.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2024 Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
26
27#include <vector>
28#include <span>
29#include <cmath>
30#include <algorithm>
31#include <memory>
32
33namespace JPL
34{
35 namespace Triangulation
36 {
37 //======================================================================
38 // Helper types / maths
39 //======================================================================
40
41 static float GetPolygonArea(std::span<const Vec2> p)
42 {
43 float a = 0.0f;
44 const size_t n = p.size();
45 for (size_t i = 0, j = n - 1; i < n; j = i++)
46 a += CrossProduct(p[j], p[i]);
47 return 0.5f * a;
48 }
49
50 static bool IsPointInTri(const Vec2& p, const Vec2& a, const Vec2& b, const Vec2& c)
51 {
52 // barycentric sign tests (CCW triangle assumed)
53 const float w0 = CrossProduct(Vec2{ b.X - a.X, b.Y - a.Y }, Vec2{ p.X - a.X, p.Y - a.Y });
54 const float w1 = CrossProduct(Vec2{ c.X - b.X, c.Y - b.Y }, Vec2{ p.X - b.X, p.Y - b.Y });
55 const float w2 = CrossProduct(Vec2{ a.X - c.X, a.Y - c.Y }, Vec2{ p.X - c.X, p.Y - c.Y });
56 return (w0 >= 0 && w1 >= 0 && w2 >= 0);
57 }
58
59 static float GetMinAngleDeg(const Vec2& a, const Vec2& b, const Vec2& c)
60 {
61 auto angle = [&](const Vec2& p, const Vec2& q, const Vec2& r)
62 {
63 Vec2 u{ p.X - q.X, p.Y - q.Y };
64 Vec2 v{ r.X - q.X, r.Y - q.Y };
65 float d = DotProduct(u, v) / std::sqrt(DotProduct(u, u) * DotProduct(v, v));
66 d = std::clamp(d, -1.0f, 1.0f);
67 return std::acos(d); // radians
68 };
69 const float a1 = angle(b, a, c);
70 const float a2 = angle(a, b, c);
71 const float a3 = angle(a, c, b);
72 return std::min({ a1, a2, a3 }) * 57.29578f; // to degrees
73 }
74
75 //======================================================================
76 // Perform an ear-clipping triangulation that always clips the ear
77 // whose triangle has the largest minimum interior angle
78 // - a cheap heuristic that strongly discourages long, skinny slivers.
79 template<CVec3 Vec3, template<class...> class ContainerType, class Vec3i, class ...Args>
80 void TriangulatePoly(std::span<const Vec3> inPositions, std::span<int> poly, ContainerType<Vec3i, Args...>& outTris)
81 {
82 using IndexType = std::remove_cvref_t<decltype(std::declval<Vec3i>()[0])>;
83 using AllocatorType = typename ContainerType<Vec3i, Args...>::allocator_type;
84
85 // 1. Build projection basis
86
87 // Robust polygon normal (Newell)
88 Vec3 n{ 0,0,0 };
89 for (size_t i = 0, j = poly.size() - 1; i < poly.size(); j = i++)
90 {
91 const Vec3& p = inPositions[poly[i]];
92 const Vec3& q = inPositions[poly[j]];
93 SetX(n, GetX(n) + (GetY(p) - GetY(q)) * (GetZ(p) + GetZ(q)));
94 SetY(n, GetY(n) + (GetZ(p) - GetZ(q)) * (GetX(p) + GetX(q)));
95 SetZ(n, GetZ(n) + (GetX(p) - GetX(q)) * (GetY(p) + GetY(q)));
96 }
97 Normalize(n);
98
99#if 0
100 // Drop the dominant axis of the normal to get a 2-D view
101 enum Axis { X = 0, Y, Z } drop =
102 (Math::Abs(GetX(n)) > Math::Abs(GetY(n)) && Math::Abs(GetX(n)) > Math::Abs(GetZ(n))) ? X :
103 (Math::Abs(GetY(n)) > Math::Abs(GetZ(n))) ? Y : Z;
104
105 auto project = [&](const Vec3& v) -> Vec2
106 {
107 switch (drop)
108 {
109 case X: return { GetY(v), GetZ(v) };
110 case Y: return { GetZ(v), GetX(v) };
111 case Z: return { GetX(v), GetY(v) };
112 }
113 return { 0,0 };
114 };
115#else
116 // Better angle precision, but slightly more expansive than simply dropping an axis
117
118 // build orthonormal basis (u,v) in the plane of 'n'
119 const Vec3 u = Normalized(Math::Abs(GetZ(n)) > 0.9f ? Vec3{ 1,0,0 } : CrossProduct(Vec3{ 0,0,1 }, n));
120 const Vec3 v = CrossProduct(n, u); // already unit length
121 auto project = [&](const Vec3& p) -> Vec2
122 {
123 return { DotProduct(p, u), DotProduct(p, v) };
124 };
125#endif
126 std::pmr::vector<Vec2> pts2(GetDefaultMemoryResource());
127 pts2.reserve(poly.size());
128 for (int idx : poly)
129 pts2.push_back(project(inPositions[idx]));
130
131 // 3. Ensure counter-clockwise orientation
132
133 if (GetPolygonArea(pts2) < 0.0f)
134 {
135 std::reverse(poly.begin(), poly.end());
136 std::reverse(pts2.begin(), pts2.end());
137 }
138
139 // 4. Ear-clipping with quality heuristic
140
141 struct Node
142 {
143 int Idx; // original vertex index
144 Vec2 P; // projected coords
145 int Prev, Next; // doubly-linked list in 'nodes'
146 };
147 std::pmr::vector<Node> nodes(GetDefaultMemoryResource());
148 nodes.reserve(poly.size());
149 for (size_t i = 0; i < poly.size(); ++i)
150 {
151 nodes.push_back({ poly[i], pts2[i],
152 int(i == 0 ? poly.size() - 1 : i - 1),
153 int((i + 1) % poly.size()) });
154 }
155
156 auto isConvex = [&](int i)
157 {
158 const Vec2& a = nodes[nodes[i].Prev].P;
159 const Vec2& b = nodes[i].P;
160 const Vec2& c = nodes[nodes[i].Next].P;
161 return CrossProduct(Vec2{ b.X - a.X, b.Y - a.Y },
162 Vec2{ c.X - b.X, c.Y - b.Y }) >= 0.0f;
163 };
164
165 int remaining = int(nodes.size());
166 while (remaining > 3)
167 {
168 // Find the "best" ear this sweep
169 int best = -1;
170 float bestQuality = -1.0f; // larger is better
171 for (int i = 0; i < nodes.size(); ++i)
172 {
173 if (nodes[i].Idx == -1)
174 continue; // already clipped
175
176 if (!isConvex(i))
177 continue;
178
179 const Vec2& a = nodes[nodes[i].Prev].P;
180 const Vec2& b = nodes[i].P;
181 const Vec2& c = nodes[nodes[i].Next].P;
182
183 bool ear = true;
184 for (int j = 0; j < nodes.size() && ear; ++j)
185 {
186 if (nodes[j].Idx == -1 || j == i ||
187 j == nodes[i].Prev || j == nodes[i].Next)
188 continue;
189
190 ear &= !IsPointInTri(nodes[j].P, a, b, c);
191 }
192 if (!ear)
193 continue;
194
195 const float q = GetMinAngleDeg(a, b, c); // quality metric
196 if (q > bestQuality)
197 {
198 bestQuality = q;
199 best = i;
200 }
201 }
202
203 if (best == -1)
204 break; // should not happen
205
206 // Emit triangle (original vertex indices, still CCW)
207 outTris.emplace_back(
208 Vec3i{
212 });
213
214 // Remove the ear-tip from the polygon
215 const int prev = nodes[best].Prev;
216 const int next = nodes[best].Next;
217 nodes[prev].Next = next;
218 nodes[next].Prev = prev;
219 nodes[best].Idx = -1; // mark removed
220 --remaining;
221 }
222
223 // 5. Final triangle
224
225 int vi[3], k = 0;
226 for (const Node& node : nodes)
227 {
228 if (node.Idx != -1)
229 vi[k++] = node.Idx;
230 }
231
232 if (k == 3)
233 {
234 outTris.emplace_back(
235 Vec3i{
236 IndexType(vi[0]),
237 IndexType(vi[1]),
238 IndexType(vi[2])
239 });
240 }
241 }
242
243 } // namespace Triangulation
244} // namespace JPL
Definition Vec3Traits.h:44
JPL_INLINE constexpr auto Abs(const T &value) noexcept
Standard abs is not constexpr in C++20.
Definition Math.h:87
void TriangulatePoly(std::span< const Vec3 > inPositions, std::span< int > poly, ContainerType< Vec3i, Args... > &outTris)
Definition Triangulation.h:80
Definition AcousticMaterial.h:36
JPL_INLINE void SetZ(Vec3Type &v, Vec3FloatType< Vec3Type > value) noexcept
Definition Vec3Traits.h:41
JPL_INLINE auto GetX(const Vec3Type &v) noexcept
Definition Vec3Traits.h:35
JPL_INLINE void SetY(Vec3Type &v, Vec3FloatType< Vec3Type > value) noexcept
Definition Vec3Traits.h:40
JPL_INLINE auto GetZ(const Vec3Type &v) noexcept
Definition Vec3Traits.h:37
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
JPL_INLINE auto GetY(const Vec3Type &v) noexcept
Definition Vec3Traits.h:36
JPL_INLINE void SetX(Vec3Type &v, Vec3FloatType< Vec3Type > value) noexcept
Definition Vec3Traits.h:39
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Definition MinimalVec2.h:29
float X
Definition MinimalVec2.h:30
float Y
Definition MinimalVec2.h:31
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97