-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTriangulationTests.cs
More file actions
306 lines (251 loc) · 10.7 KB
/
TriangulationTests.cs
File metadata and controls
306 lines (251 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
namespace CDT.Tests;
/// <summary>Basic triangulation tests (float and double).</summary>
public abstract class TriangulationTestsBase<T>
where T : unmanaged,
System.Numerics.IFloatingPoint<T>,
System.Numerics.IMinMaxValue<T>,
System.Numerics.IRootFunctions<T>
{
protected static T F(double v) => T.CreateChecked(v);
protected static V2d<T> Pt(double x, double y) => new(F(x), F(y));
protected static Triangulation<T> CreateCdt(
VertexInsertionOrder order = VertexInsertionOrder.Auto)
=> new(order);
// -------------------------------------------------------------------------
// Basic insertion
// -------------------------------------------------------------------------
[Fact]
public void InsertFourPoints_TopologicallyValid()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 1), Pt(3, 1), Pt(3, 0)]);
Assert.True(TopologyVerifier.VerifyTopology(cdt));
// 4 user vertices + 3 super-triangle vertices
Assert.Equal(7, cdt.Vertices.Length);
Assert.Empty(cdt.FixedEdges);
// 4 vertices inside a super-triangle → 9 triangles
Assert.Equal(9, cdt.Triangles.Length);
}
[Fact]
public void EraseSuperTriangle_LeavesConvexHull()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 1), Pt(3, 1), Pt(3, 0)]);
cdt.EraseSuperTriangle();
Assert.True(TopologyVerifier.VerifyTopology(cdt));
Assert.Equal(4, cdt.Vertices.Length);
Assert.Equal(2, cdt.Triangles.Length);
}
[Fact]
public void AddConstraintEdge_PresentAfterErase()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 1), Pt(3, 1), Pt(3, 0)]);
var constraint = new Edge(0, 2);
cdt.InsertEdges([constraint]);
cdt.EraseSuperTriangle();
Assert.True(TopologyVerifier.VerifyTopology(cdt));
Assert.Equal(4, cdt.Vertices.Length);
Assert.Single(cdt.FixedEdges);
Assert.Equal(2, cdt.Triangles.Length);
Assert.Contains(constraint, cdt.FixedEdges);
var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles.Span);
Assert.Contains(constraint, edges);
}
// -------------------------------------------------------------------------
// Duplicate detection
// -------------------------------------------------------------------------
[Fact]
public void DuplicateVertex_ThrowsDuplicateVertexException()
{
var cdt = CreateCdt();
var pts = new[] { Pt(0, 0), Pt(1, 0), Pt(0, 0) }; // pt[2] == pt[0]
Assert.Throws<DuplicateVertexException>(() => cdt.InsertVertices(pts));
}
// -------------------------------------------------------------------------
// Triangle extraction
// -------------------------------------------------------------------------
[Fact]
public void ExtractEdges_ReturnsAllEdges()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(0.5, 1)]);
cdt.EraseSuperTriangle();
var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles.Span);
Assert.Equal(3, edges.Count); // one triangle → 3 edges
}
// -------------------------------------------------------------------------
// Square with diagonal constraint
// -------------------------------------------------------------------------
[Fact]
public void Square_WithDiagonalConstraint()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]);
cdt.InsertEdges([new Edge(0, 2)]); // diagonal 0→2
cdt.EraseSuperTriangle();
Assert.True(TopologyVerifier.VerifyTopology(cdt));
Assert.Equal(2, cdt.Triangles.Length);
Assert.Single(cdt.FixedEdges);
}
// -------------------------------------------------------------------------
// Insertion order variants
// -------------------------------------------------------------------------
[Fact]
public void InsertionOrder_AsProvided_SameResult()
{
var pts = new[] { Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1) };
var cdtAuto = new Triangulation<T>(VertexInsertionOrder.Auto);
cdtAuto.InsertVertices(pts);
cdtAuto.EraseSuperTriangle();
var cdtProvided = new Triangulation<T>(VertexInsertionOrder.AsProvided);
cdtProvided.InsertVertices(pts);
cdtProvided.EraseSuperTriangle();
Assert.Equal(cdtAuto.Triangles.Length, cdtProvided.Triangles.Length);
Assert.Equal(cdtAuto.Vertices.Length, cdtProvided.Vertices.Length);
}
// -------------------------------------------------------------------------
// Utility functions
// -------------------------------------------------------------------------
[Fact]
public void FindDuplicates_DetectsExactMatches()
{
var pts = new List<V2d<T>>
{
Pt(0, 0), Pt(1, 0), Pt(0, 0), Pt(2, 0),
};
var info = CdtUtils.FindDuplicates(pts);
Assert.Single(info.Duplicates);
Assert.Equal(2, info.Duplicates[0]);
// mapping[2] should equal mapping[0] = 0
Assert.Equal(info.Mapping[0], info.Mapping[2]);
}
[Fact]
public void RemoveDuplicatesAndRemapEdges_ProducesConsistentResult()
{
var verts = new List<V2d<T>>
{
Pt(0, 0), Pt(1, 0), Pt(0, 0), // index 2 is dup of 0
Pt(0, 1),
};
var edges = new List<Edge> { new(0, 2), new(1, 3) };
CdtUtils.RemoveDuplicatesAndRemapEdges(verts, edges);
// After removing dup at index 2, vertices are: (0,0),(1,0),(0,1) → 3
Assert.Equal(3, verts.Count);
// Edge (0,2) should be remapped to (0,0) = same vertex → still (0,0)
Assert.Equal(new Edge(0, 0), edges[0]);
// Edge (1,3) → (1,2) after shifting
Assert.Equal(new Edge(1, 2), edges[1]);
}
// -------------------------------------------------------------------------
// Erase outer triangles
// -------------------------------------------------------------------------
[Fact]
public void EraseOuterTriangles_RemovesOutside()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]);
cdt.InsertEdges([new Edge(0, 1), new Edge(1, 2), new Edge(2, 3), new Edge(3, 0)]);
cdt.EraseOuterTriangles();
Assert.True(TopologyVerifier.VerifyTopology(cdt));
// Square → 2 triangles
Assert.Equal(2, cdt.Triangles.Length);
}
// -------------------------------------------------------------------------
// Single triangle
// -------------------------------------------------------------------------
[Fact]
public void ThreePoints_SingleTriangle()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(2, 0), Pt(1, 2)]);
cdt.EraseSuperTriangle();
Assert.True(TopologyVerifier.VerifyTopology(cdt));
Assert.Equal(3, cdt.Vertices.Length);
Assert.Equal(1, cdt.Triangles.Length);
}
// -------------------------------------------------------------------------
// Collinear points
// -------------------------------------------------------------------------
[Fact]
public void FivePoints_ValidTopology()
{
var cdt = CreateCdt();
cdt.InsertVertices([
Pt(0, 0), Pt(2, 0), Pt(4, 0),
Pt(2, 2), Pt(0, 2),
]);
cdt.EraseSuperTriangle();
Assert.True(TopologyVerifier.VerifyTopology(cdt));
Assert.Equal(5, cdt.Vertices.Length);
}
// -------------------------------------------------------------------------
// IsFinalized guard checks
// -------------------------------------------------------------------------
[Fact]
public void InsertVertices_AfterFinalized_ThrowsTriangulationFinalizedException()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(0.5, 1)]);
cdt.EraseSuperTriangle();
Assert.True(cdt.IsFinalized);
Assert.Throws<TriangulationFinalizedException>(() =>
cdt.InsertVertices([Pt(2, 0)]));
}
[Fact]
public void InsertEdges_AfterFinalized_ThrowsTriangulationFinalizedException()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]);
cdt.EraseSuperTriangle();
Assert.True(cdt.IsFinalized);
Assert.Throws<TriangulationFinalizedException>(() =>
cdt.InsertEdges([new Edge(0, 2)]));
}
[Fact]
public void ConformToEdges_AfterFinalized_ThrowsTriangulationFinalizedException()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]);
cdt.EraseSuperTriangle();
Assert.True(cdt.IsFinalized);
Assert.Throws<TriangulationFinalizedException>(() =>
cdt.ConformToEdges([new Edge(0, 2)]));
}
[Fact]
public void EraseSuperTriangle_AfterFinalized_ThrowsTriangulationFinalizedException()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(0.5, 1)]);
cdt.EraseSuperTriangle();
Assert.True(cdt.IsFinalized);
Assert.Throws<TriangulationFinalizedException>(() => cdt.EraseSuperTriangle());
}
[Fact]
public void EraseOuterTriangles_AfterFinalized_ThrowsTriangulationFinalizedException()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]);
cdt.InsertEdges([new Edge(0, 1), new Edge(1, 2), new Edge(2, 3), new Edge(3, 0)]);
cdt.EraseOuterTriangles();
Assert.True(cdt.IsFinalized);
Assert.Throws<TriangulationFinalizedException>(() => cdt.EraseOuterTriangles());
}
[Fact]
public void EraseOuterTrianglesAndHoles_AfterFinalized_ThrowsTriangulationFinalizedException()
{
var cdt = CreateCdt();
cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]);
cdt.InsertEdges([new Edge(0, 1), new Edge(1, 2), new Edge(2, 3), new Edge(3, 0)]);
cdt.EraseOuterTrianglesAndHoles();
Assert.True(cdt.IsFinalized);
Assert.Throws<TriangulationFinalizedException>(() => cdt.EraseOuterTrianglesAndHoles());
}
}
/// <summary>Triangulation tests for <see cref="double"/>.</summary>
public sealed class TriangulationTests_Double : TriangulationTestsBase<double> { }
/// <summary>Triangulation tests for <see cref="float"/>.</summary>
public sealed class TriangulationTests_Float : TriangulationTestsBase<float> { }