Skip to content

Commit a0bc414

Browse files
author
Michael Conrad
authored
Merge pull request #5 from MichaCo/copilot/refactor-public-api-surface
Refactor CDT.Core public API: split Predicates, merge TriangleUtils into CdtUtils, harden immutability
2 parents 61cf614 + 5d098d2 commit a0bc414

7 files changed

Lines changed: 866 additions & 468 deletions

File tree

src/CDT.Core/CdtUtils.cs

Lines changed: 457 additions & 14 deletions
Large diffs are not rendered by default.

src/CDT.Core/Predicates.cs renamed to src/CDT.Core/Predicates/PredicatesAdaptive.cs

Lines changed: 112 additions & 131 deletions
Large diffs are not rendered by default.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
//
5+
// Geometric predicates — exact (arbitrary-precision) paths.
6+
7+
namespace CDT.Predicates;
8+
9+
/// <summary>
10+
/// Geometric predicates using arbitrary-precision floating-point arithmetic.
11+
/// These produce exact results regardless of degeneracy but are significantly
12+
/// slower than <see cref="PredicatesAdaptive"/>. Provided primarily for
13+
/// reference, verification, and degenerate-case testing.
14+
/// Corresponds to C++ <c>predicates::exact</c>.
15+
/// </summary>
16+
/// <remarks>
17+
/// Reference: https://www.cs.cmu.edu/~quake/robust.html
18+
/// </remarks>
19+
/// <seealso cref="PredicatesAdaptive"/>
20+
public static class PredicatesExact
21+
{
22+
// =========================================================================
23+
// Orient2d
24+
// =========================================================================
25+
26+
/// <summary>
27+
/// Exact orient2d predicate for <see cref="double"/> coordinates using
28+
/// arbitrary-precision arithmetic. Always produces a correct sign even for
29+
/// nearly-collinear points.
30+
/// Returns the determinant of {{ax-cx, ay-cy}, {bx-cx, by-cy}}.
31+
/// Positive = c is left/above the line a→b; zero = collinear; negative = right/below.
32+
/// </summary>
33+
/// <param name="ax">X-coordinate of point a.</param>
34+
/// <param name="ay">Y-coordinate of point a.</param>
35+
/// <param name="bx">X-coordinate of point b.</param>
36+
/// <param name="by">Y-coordinate of point b.</param>
37+
/// <param name="cx">X-coordinate of point c.</param>
38+
/// <param name="cy">Y-coordinate of point c.</param>
39+
/// <returns>
40+
/// A positive value if c is to the left of line a→b,
41+
/// zero if collinear, or a negative value if to the right.
42+
/// </returns>
43+
/// <seealso cref="PredicatesAdaptive.Orient2d(double, double, double, double, double, double)"/>
44+
public static double Orient2d(
45+
double ax, double ay, double bx, double by, double cx, double cy)
46+
{
47+
double acx = ax - cx, bcx = bx - cx;
48+
double acy = ay - cy, bcy = by - cy;
49+
50+
Span<double> s1 = stackalloc double[4];
51+
Span<double> s2 = stackalloc double[4];
52+
Span<double> s3 = stackalloc double[4];
53+
Span<double> b = stackalloc double[4];
54+
55+
int bLen = PredicatesAdaptive.TwoTwoDiff(acx, bcy, acy, bcx, b);
56+
57+
double acxtail = PredicatesAdaptive.MinusTail(ax, cx, acx);
58+
double bcxtail = PredicatesAdaptive.MinusTail(bx, cx, bcx);
59+
double acytail = PredicatesAdaptive.MinusTail(ay, cy, acy);
60+
double bcytail = PredicatesAdaptive.MinusTail(by, cy, bcy);
61+
62+
int s1Len = PredicatesAdaptive.TwoTwoDiff(acxtail, bcy, acytail, bcx, s1);
63+
int s2Len = PredicatesAdaptive.TwoTwoDiff(acx, bcytail, acy, bcxtail, s2);
64+
int s3Len = PredicatesAdaptive.TwoTwoDiff(acxtail, bcytail, acytail, bcxtail, s3);
65+
66+
Span<double> t1 = stackalloc double[8];
67+
int t1Len = PredicatesAdaptive.ExpansionSum(b, bLen, s1, s1Len, t1);
68+
Span<double> t2 = stackalloc double[12];
69+
int t2Len = PredicatesAdaptive.ExpansionSum(t1, t1Len, s2, s2Len, t2);
70+
Span<double> d = stackalloc double[16];
71+
int dLen = PredicatesAdaptive.ExpansionSum(t2, t2Len, s3, s3Len, d);
72+
return PredicatesAdaptive.MostSignificant(d, dLen);
73+
}
74+
75+
/// <summary>
76+
/// Exact orient2d predicate for <see cref="float"/> coordinates using
77+
/// arbitrary-precision arithmetic.
78+
/// Returns the determinant of {{ax-cx, ay-cy}, {bx-cx, by-cy}}.
79+
/// Positive = c is left/above the line a→b; zero = collinear; negative = right/below.
80+
/// </summary>
81+
/// <param name="ax">X-coordinate of point a.</param>
82+
/// <param name="ay">Y-coordinate of point a.</param>
83+
/// <param name="bx">X-coordinate of point b.</param>
84+
/// <param name="by">Y-coordinate of point b.</param>
85+
/// <param name="cx">X-coordinate of point c.</param>
86+
/// <param name="cy">Y-coordinate of point c.</param>
87+
/// <returns>
88+
/// A positive value if c is to the left of line a→b,
89+
/// zero if collinear, or a negative value if to the right.
90+
/// </returns>
91+
/// <seealso cref="PredicatesAdaptive.Orient2d(float, float, float, float, float, float)"/>
92+
public static float Orient2d(
93+
float ax, float ay, float bx, float by, float cx, float cy)
94+
{
95+
// Each product of two 24-bit floats is exact in 53-bit double.
96+
float acx = ax - cx, bcx = bx - cx;
97+
float acy = ay - cy, bcy = by - cy;
98+
double exact = (double)acx * bcy - (double)acy * bcx;
99+
return (float)exact;
100+
}
101+
102+
// =========================================================================
103+
// InCircle
104+
// =========================================================================
105+
106+
/// <summary>
107+
/// Exact incircle predicate for <see cref="double"/> coordinates using
108+
/// arbitrary-precision arithmetic.
109+
/// Returns positive if <c>d</c> is inside the circumcircle of (a, b, c),
110+
/// zero if on, negative if outside.
111+
/// </summary>
112+
/// <param name="ax">X-coordinate of triangle vertex a.</param>
113+
/// <param name="ay">Y-coordinate of triangle vertex a.</param>
114+
/// <param name="bx">X-coordinate of triangle vertex b.</param>
115+
/// <param name="by">Y-coordinate of triangle vertex b.</param>
116+
/// <param name="cx">X-coordinate of triangle vertex c.</param>
117+
/// <param name="cy">Y-coordinate of triangle vertex c.</param>
118+
/// <param name="dx">X-coordinate of query point d.</param>
119+
/// <param name="dy">Y-coordinate of query point d.</param>
120+
/// <returns>
121+
/// A positive value if d is inside the circumcircle of (a, b, c),
122+
/// zero if on, or a negative value if outside.
123+
/// </returns>
124+
/// <seealso cref="PredicatesAdaptive.InCircle(double, double, double, double, double, double, double, double)"/>
125+
public static double InCircle(
126+
double ax, double ay, double bx, double by,
127+
double cx, double cy, double dx, double dy)
128+
{
129+
Span<double> abE = stackalloc double[4];
130+
Span<double> bcE = stackalloc double[4];
131+
Span<double> cdE = stackalloc double[4];
132+
Span<double> daE = stackalloc double[4];
133+
Span<double> acE = stackalloc double[4];
134+
Span<double> bdE = stackalloc double[4];
135+
int abLen = PredicatesAdaptive.TwoTwoDiff(ax, by, bx, ay, abE);
136+
int bcLen = PredicatesAdaptive.TwoTwoDiff(bx, cy, cx, by, bcE);
137+
int cdLen = PredicatesAdaptive.TwoTwoDiff(cx, dy, dx, cy, cdE);
138+
int daLen = PredicatesAdaptive.TwoTwoDiff(dx, ay, ax, dy, daE);
139+
int acLen = PredicatesAdaptive.TwoTwoDiff(ax, cy, cx, ay, acE);
140+
int bdLen = PredicatesAdaptive.TwoTwoDiff(bx, dy, dx, by, bdE);
141+
142+
// abc = ab + bc - ac
143+
Span<double> negAc = stackalloc double[4];
144+
PredicatesAdaptive.NegateInto(acE, acLen, negAc);
145+
Span<double> abbc = stackalloc double[8];
146+
int abbcLen = PredicatesAdaptive.ExpansionSum(abE, abLen, bcE, bcLen, abbc);
147+
Span<double> abc = stackalloc double[12];
148+
int abcLen = PredicatesAdaptive.ExpansionSum(abbc, abbcLen, negAc, acLen, abc);
149+
150+
// bcd = bc + cd - bd
151+
Span<double> negBd = stackalloc double[4];
152+
PredicatesAdaptive.NegateInto(bdE, bdLen, negBd);
153+
Span<double> bccd = stackalloc double[8];
154+
int bccdLen = PredicatesAdaptive.ExpansionSum(bcE, bcLen, cdE, cdLen, bccd);
155+
Span<double> bcd = stackalloc double[12];
156+
int bcdLen = PredicatesAdaptive.ExpansionSum(bccd, bccdLen, negBd, bdLen, bcd);
157+
158+
// cda = cd + da + ac
159+
Span<double> cdda = stackalloc double[8];
160+
int cddaLen = PredicatesAdaptive.ExpansionSum(cdE, cdLen, daE, daLen, cdda);
161+
Span<double> cda = stackalloc double[12];
162+
int cdaLen = PredicatesAdaptive.ExpansionSum(cdda, cddaLen, acE, acLen, cda);
163+
164+
// dab = da + ab + bd
165+
Span<double> daab = stackalloc double[8];
166+
int daabLen = PredicatesAdaptive.ExpansionSum(daE, daLen, abE, abLen, daab);
167+
Span<double> dab = stackalloc double[12];
168+
int dabLen = PredicatesAdaptive.ExpansionSum(daab, daabLen, bdE, bdLen, dab);
169+
170+
// adet = bcd*ax*ax + bcd*ay*ay
171+
Span<double> adet = stackalloc double[96];
172+
int adetLen = PredicatesAdaptive.ScaleExpansionSum(bcd, bcdLen, ax, ay, adet);
173+
174+
// bdet = -(cda*bx*bx + cda*by*by)
175+
Span<double> bdetPos = stackalloc double[96];
176+
int bdetPosLen = PredicatesAdaptive.ScaleExpansionSum(cda, cdaLen, bx, by, bdetPos);
177+
Span<double> bdet = stackalloc double[96];
178+
PredicatesAdaptive.NegateInto(bdetPos, bdetPosLen, bdet);
179+
int bdetLen = bdetPosLen;
180+
181+
// cdet = dab*cx*cx + dab*cy*cy
182+
Span<double> cdet = stackalloc double[96];
183+
int cdetLen = PredicatesAdaptive.ScaleExpansionSum(dab, dabLen, cx, cy, cdet);
184+
185+
// ddet = -(abc*dx*dx + abc*dy*dy)
186+
Span<double> ddetPos = stackalloc double[96];
187+
int ddetPosLen = PredicatesAdaptive.ScaleExpansionSum(abc, abcLen, dx, dy, ddetPos);
188+
Span<double> ddet = stackalloc double[96];
189+
PredicatesAdaptive.NegateInto(ddetPos, ddetPosLen, ddet);
190+
int ddetLen = ddetPosLen;
191+
192+
// deter = (adet + bdet) + (cdet + ddet)
193+
Span<double> ab2 = stackalloc double[192];
194+
int ab2Len = PredicatesAdaptive.ExpansionSum(adet, adetLen, bdet, bdetLen, ab2);
195+
Span<double> cd2 = stackalloc double[192];
196+
int cd2Len = PredicatesAdaptive.ExpansionSum(cdet, cdetLen, ddet, ddetLen, cd2);
197+
Span<double> deter = stackalloc double[384];
198+
int deterLen = PredicatesAdaptive.ExpansionSum(ab2, ab2Len, cd2, cd2Len, deter);
199+
200+
return PredicatesAdaptive.MostSignificant(deter, deterLen);
201+
}
202+
203+
/// <summary>
204+
/// Exact incircle predicate for <see cref="float"/> coordinates using
205+
/// arbitrary-precision arithmetic.
206+
/// Returns positive if <c>d</c> is inside the circumcircle of (a, b, c),
207+
/// zero if on, negative if outside.
208+
/// </summary>
209+
/// <param name="ax">X-coordinate of triangle vertex a.</param>
210+
/// <param name="ay">Y-coordinate of triangle vertex a.</param>
211+
/// <param name="bx">X-coordinate of triangle vertex b.</param>
212+
/// <param name="by">Y-coordinate of triangle vertex b.</param>
213+
/// <param name="cx">X-coordinate of triangle vertex c.</param>
214+
/// <param name="cy">Y-coordinate of triangle vertex c.</param>
215+
/// <param name="dx">X-coordinate of query point d.</param>
216+
/// <param name="dy">Y-coordinate of query point d.</param>
217+
/// <returns>
218+
/// A positive value if d is inside the circumcircle of (a, b, c),
219+
/// zero if on, or a negative value if outside.
220+
/// </returns>
221+
/// <seealso cref="PredicatesAdaptive.InCircle(float, float, float, float, float, float, float, float)"/>
222+
public static float InCircle(
223+
float ax, float ay, float bx, float by,
224+
float cx, float cy, float dx, float dy)
225+
{
226+
float adx = ax - dx, bdx = bx - dx, cdx = cx - dx;
227+
float ady = ay - dy, bdy = by - dy, cdy = cy - dy;
228+
decimal madx = (decimal)adx, mbdx = (decimal)bdx, mcdx = (decimal)cdx;
229+
decimal mady = (decimal)ady, mbdy = (decimal)bdy, mcdy = (decimal)cdy;
230+
decimal mdet = (madx * madx + mady * mady) * (mbdx * mcdy - mcdx * mbdy)
231+
+ (mbdx * mbdx + mbdy * mbdy) * (mcdx * mady - madx * mcdy)
232+
+ (mcdx * mcdx + mcdy * mcdy) * (madx * mbdy - mbdx * mady);
233+
return mdet > 0m ? float.Epsilon : mdet < 0m ? -float.Epsilon : 0f;
234+
}
235+
}

src/CDT.Core/TopologyVerifier.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ public static bool VerifyTopology<T>(Triangulation<T> cdt)
4646
return false;
4747
// Verify shared edge: neighbor[i] shares vertices {v[i], v[ccw(i)]}
4848
int va = t.GetVertex(i);
49-
int vb = t.GetVertex(TriangleUtils.Ccw(i));
49+
int vb = t.GetVertex(CdtUtils.Ccw(i));
5050
if (!tN.ContainsVertex(va) || !tN.ContainsVertex(vb))
5151
return false;
5252
}
5353
}
5454
return true;
5555
}
5656
}
57+

0 commit comments

Comments
 (0)