Skip to content

Commit f8c89a6

Browse files
committed
Integrate Islam0mar's post-mirror Ruppert refinement fixes
Ports the two genuinely new commits from Islam0mar/CDT's ruppert-delaunay-refinement branch that weren't yet in this mirror (the rest of that branch's history was master commits pulled in via periodic merges, already present here): - aaac2dd "Refine after faces deletion + split fix" - 3bda312 "avoid equidistant triangles in bad triangles" Adapted by hand rather than cherry-picked, since this branch's own subsequent refactoring (queue/criterion renames, isFlipNeeded/addNewVertex signature changes on master) left little text in common with the original diffs: - isEncroachingOnEdge/isRefinementNeeded: use strict inequalities so a split landing exactly on a diametral circle/threshold boundary isn't re-flagged as encroached/bad, which could otherwise loop. - splitEncroachedEdge: only apply the concentric-shell (power-of-two) split distance when the subsegment's Steiner endpoint is shared with another fixed edge at a small angle, instead of for every Steiner endpoint; drop an assert that silently truncated its operands to int. - refineTriangles/resolveEncroachedEdges/splitEncroachedEdge take an optional toEraseOrNull set (matching the existing circumcenterOrNull pointer-optional convention in this file): triangles already marked for erasure are skipped as refinement candidates, and triangles that replace them via edge splits or vertex insertion inherit the mark. New collectSuperTriangle/collectOuterTriangles/ collectOuterTrianglesAndHoles methods (paired with the now-public finalizeTriangulation) let a caller compute the erase set upfront and pass it to refineTriangles, so refinement doesn't waste the Steiner point budget on triangles about to be discarded (holes/outer triangles/super-triangle). - Visualizer: collects the erase set for the selected finalize option before refining, and finalizes with the (possibly updated) set afterwards. Verified: full CDT-tests suite (4787 assertions) and the CDT_visualizer Qt build both pass; added ad hoc smoke tests exercising refineTriangles standalone and combined with collectOuterTrianglesAndHoles + a hole, confirming valid topology and no vertices placed outside the erased region.
1 parent e4f9695 commit f8c89a6

4 files changed

Lines changed: 165 additions & 47 deletions

File tree

CDT/include/CDTUtils.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ bool isEncroachingOnEdge(
278278
* the angle between v and edge end points is obtuse
279279
*/
280280
return (edgeStart.x - v.x) * (edgeEnd.x - v.x) +
281-
(edgeStart.y - v.y) * (edgeEnd.y - v.y) <=
281+
(edgeStart.y - v.y) * (edgeEnd.y - v.y) <
282282
T(0);
283283
}
284284

CDT/include/Triangulation.h

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -698,12 +698,20 @@ class CDT_EXPORT Triangulation
698698
* @param refinement_constrain refinement strategy that is used to identify
699699
* bad triangles
700700
* @param refinementThreshold threshold value for refinement
701+
* @param toEraseOrNull if not null, triangles already present in this set
702+
* are skipped as refinement candidates and any triangles that replace them
703+
* as a result of splitting are added to the set. Lets a caller combine
704+
* refinement with one of the `eraseXXX` methods without wasting the vertex
705+
* budget refining triangles that will be discarded anyway: pass in the
706+
* result of the matching `collectXXX` method and later hand the (now
707+
* updated) set to `finalizeTriangulation`.
701708
*/
702709
void refineTriangles(
703710
VertInd maxVerticesToInsert,
704711
RefinementCriterion::Enum refinementCriterion =
705712
RefinementCriterion::SmallestAngle,
706-
T refinementThreshold = 20 / 180.0 * M_PI);
713+
T refinementThreshold = 20 / 180.0 * M_PI,
714+
TriIndUSet* toEraseOrNull = NULL);
707715
/**
708716
* Erase triangles adjacent to super triangle
709717
*
@@ -719,6 +727,20 @@ class CDT_EXPORT Triangulation
719727
* @note supports overlapping or touching boundaries
720728
*/
721729
void eraseOuterTrianglesAndHoles();
730+
/**
731+
* Collect triangles adjacent to super-triangle: same triangles that
732+
* `eraseSuperTriangle` would remove.
733+
* @note returns an empty set if custom geometry is used
734+
*/
735+
TriIndUSet collectSuperTriangle() const;
736+
/// Collect triangles outside of constrained boundary: same triangles that
737+
/// `eraseOuterTriangles` would remove.
738+
TriIndUSet collectOuterTriangles() const;
739+
/**
740+
* Collect triangles outside of constrained boundary and auto-detected
741+
* holes: same triangles that `eraseOuterTrianglesAndHoles` would remove.
742+
*/
743+
TriIndUSet collectOuterTrianglesAndHoles() const;
722744
/**
723745
* Call this method after directly setting custom super-geometry via
724746
* vertices and triangles members
@@ -802,6 +824,15 @@ class CDT_EXPORT Triangulation
802824
const TriIndVec& VertTrisInternal() const;
803825
/// @}
804826

827+
/**
828+
* Remove super-triangle (if used) and triangles with specified indices.
829+
* Adjust internal triangulation state accordingly.
830+
* @param removedTriangles indices of triangles to remove
831+
* @note pair with one of the `collectXXX` methods to combine erasing with
832+
* `refineTriangles`
833+
*/
834+
void finalizeTriangulation(const TriIndUSet& removedTriangles);
835+
805836
private:
806837
/*____ Detail __*/
807838
void addSuperTriangle(const Box2d<T>& box);
@@ -940,8 +971,12 @@ class CDT_EXPORT Triangulation
940971
const V2d<T>* circumcenterOrNull = NULL,
941972
RefinementCriterion::Enum refinementCriterion =
942973
RefinementCriterion::SmallestAngle,
943-
T badTriangleThreshold = T(0));
944-
VertInd splitEncroachedEdge(Edge edge, VertInd steinerVerticesOffset);
974+
T badTriangleThreshold = T(0),
975+
TriIndUSet* toEraseOrNull = NULL);
976+
VertInd splitEncroachedEdge(
977+
Edge edge,
978+
VertInd steinerVerticesOffset,
979+
TriIndUSet* toEraseOrNull = NULL);
945980
void changeNeighbor(TriInd iT, TriInd oldNeighbor, TriInd newNeighbor);
946981
void changeNeighbor(
947982
TriInd iT,
@@ -966,12 +1001,6 @@ class CDT_EXPORT Triangulation
9661001
IndexSizeType iB) const;
9671002
TriInd addTriangle(const Triangle& t);
9681003
TriInd addTriangle();
969-
/**
970-
* Remove super-triangle (if used) and triangles with specified indices.
971-
* Adjust internal triangulation state accordingly.
972-
* @removedTriangles indices of triangles to remove
973-
*/
974-
void finalizeTriangulation(const TriIndUSet& removedTriangles);
9751004
TriIndUSet growToBoundary(std::stack<TriInd> seeds) const;
9761005
void fixEdge(const Edge& edge);
9771006
void fixEdge(const Edge& edge, const Edge& originalEdge);

CDT/include/Triangulation.hpp

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -103,27 +103,47 @@ void Triangulation<T, TNearPointLocator>::eraseSuperTriangle()
103103
{
104104
if(m_superGeomType != SuperGeometryType::SuperTriangle)
105105
return;
106+
finalizeTriangulation(collectSuperTriangle());
107+
}
108+
109+
template <typename T, typename TNearPointLocator>
110+
void Triangulation<T, TNearPointLocator>::eraseOuterTriangles()
111+
{
112+
finalizeTriangulation(collectOuterTriangles());
113+
}
114+
115+
template <typename T, typename TNearPointLocator>
116+
void Triangulation<T, TNearPointLocator>::eraseOuterTrianglesAndHoles()
117+
{
118+
finalizeTriangulation(collectOuterTrianglesAndHoles());
119+
}
120+
121+
template <typename T, typename TNearPointLocator>
122+
TriIndUSet Triangulation<T, TNearPointLocator>::collectSuperTriangle() const
123+
{
106124
// find triangles adjacent to super-triangle's vertices
107125
TriIndUSet toErase;
126+
if(m_superGeomType != SuperGeometryType::SuperTriangle)
127+
return toErase;
108128
for(TriInd iT(0); iT < TriInd(triangles.size()); ++iT)
109129
{
110130
if(touchesSuperTriangle(triangles[iT]))
111131
toErase.insert(iT);
112132
}
113-
finalizeTriangulation(toErase);
133+
return toErase;
114134
}
115135

116136
template <typename T, typename TNearPointLocator>
117-
void Triangulation<T, TNearPointLocator>::eraseOuterTriangles()
137+
TriIndUSet Triangulation<T, TNearPointLocator>::collectOuterTriangles() const
118138
{
119139
assert(m_vertTris[0] != noNeighbor);
120140
const std::stack<TriInd> seed(std::deque<TriInd>(1, m_vertTris[0]));
121-
const TriIndUSet toErase = growToBoundary(seed);
122-
finalizeTriangulation(toErase);
141+
return growToBoundary(seed);
123142
}
124143

125144
template <typename T, typename TNearPointLocator>
126-
void Triangulation<T, TNearPointLocator>::eraseOuterTrianglesAndHoles()
145+
TriIndUSet
146+
Triangulation<T, TNearPointLocator>::collectOuterTrianglesAndHoles() const
127147
{
128148
const std::vector<LayerDepth> triDepths = calculateTriangleDepths();
129149
TriIndUSet toErase;
@@ -133,7 +153,7 @@ void Triangulation<T, TNearPointLocator>::eraseOuterTrianglesAndHoles()
133153
if(triDepths[iT] % 2 == 0)
134154
toErase.insert(static_cast<TriInd>(iT));
135155
}
136-
finalizeTriangulation(toErase);
156+
return toErase;
137157
}
138158

139159
/// Remap removing super-triangle: subtract 3 from vertices
@@ -1403,9 +1423,9 @@ bool Triangulation<T, TNearPointLocator>::isRefinementNeeded(
14031423
switch(refinementCriterion)
14041424
{
14051425
case RefinementCriterion::SmallestAngle:
1406-
return smallestAngle(a, b, c) <= refinementThreshold;
1426+
return smallestAngle(a, b, c) < refinementThreshold;
14071427
case RefinementCriterion::LargestArea:
1408-
return area(a, b, c) >= refinementThreshold;
1428+
return area(a, b, c) > refinementThreshold;
14091429
}
14101430
assert(false); // unreachable code
14111431
return false;
@@ -1477,7 +1497,8 @@ TriIndVec Triangulation<T, TNearPointLocator>::resolveEncroachedEdges(
14771497
const VertInd steinerVerticesOffset,
14781498
const V2d<T>* const circumcenterOrNull,
14791499
const RefinementCriterion::Enum refinementCriterion,
1480-
const T badTriangleThreshold)
1500+
const T badTriangleThreshold,
1501+
TriIndUSet* const toEraseOrNull)
14811502
{
14821503
std::vector<TriInd> badTriangles;
14831504

@@ -1490,16 +1511,18 @@ TriIndVec Triangulation<T, TNearPointLocator>::resolveEncroachedEdges(
14901511
continue;
14911512
}
14921513
// split encroached edge
1493-
const VertInd iSplitVert =
1494-
splitEncroachedEdge(edge, steinerVerticesOffset);
1514+
const VertInd iSplitVert = splitEncroachedEdge(
1515+
edge, steinerVerticesOffset, toEraseOrNull);
14951516
--remainingVertexBudget;
14961517

14971518
const TriInd start = m_vertTris[iSplitVert];
14981519
TriInd iT = start;
14991520
do
15001521
{
15011522
const Triangle& t = triangles[iT];
1502-
if(circumcenterOrNull &&
1523+
const bool isMarkedForErasure =
1524+
toEraseOrNull && toEraseOrNull->count(iT);
1525+
if(circumcenterOrNull && !isMarkedForErasure &&
15031526
isRefinementNeeded(t, refinementCriterion, badTriangleThreshold))
15041527
{
15051528
badTriangles.push_back(iT);
@@ -1525,13 +1548,32 @@ TriIndVec Triangulation<T, TNearPointLocator>::resolveEncroachedEdges(
15251548
template <typename T, typename TNearPointLocator>
15261549
VertInd Triangulation<T, TNearPointLocator>::splitEncroachedEdge(
15271550
const Edge edge,
1528-
const VertInd steinerVerticesOffset)
1551+
const VertInd steinerVerticesOffset,
1552+
TriIndUSet* const toEraseOrNull)
15291553
{
15301554
const V2d<T>& start = vertices[edge.v1()];
15311555
const V2d<T>& end = vertices[edge.v2()];
1556+
1557+
TriInd iT, iTopo;
1558+
std::tie(iT, iTopo) = edgeTriangles(edge.v1(), edge.v2());
1559+
assert(iT != invalidIndex && iTopo != invalidIndex);
1560+
15321561
T split = T(0.5);
1533-
// check if any of the split edge vertices are Steiner vertices
1534-
if(edge.v1() >= steinerVerticesOffset || edge.v2() >= steinerVerticesOffset)
1562+
// Use the concentric-shell splitting rule only when the edge is a
1563+
// subsegment terminating at a Steiner vertex that is itself an endpoint
1564+
// of another fixed (sub)segment meeting it at a small angle: splitting
1565+
// exactly in half in that case can lead to non-termination as the two
1566+
// segments keep encroaching on each other's ever-shrinking halves.
1567+
const VertInd v3 = opposedVertex(triangles[iT], iTopo);
1568+
const VertInd v4 = opposedVertex(triangles[iTopo], iT);
1569+
if((edge.v1() < steinerVerticesOffset &&
1570+
edge.v2() >= steinerVerticesOffset &&
1571+
(fixedEdges.find(Edge(v3, edge.v1())) != fixedEdges.end() ||
1572+
fixedEdges.find(Edge(v4, edge.v1())) != fixedEdges.end())) ||
1573+
(edge.v2() < steinerVerticesOffset &&
1574+
edge.v1() >= steinerVerticesOffset &&
1575+
(fixedEdges.find(Edge(v3, edge.v2())) != fixedEdges.end() ||
1576+
fixedEdges.find(Edge(v4, edge.v2())) != fixedEdges.end())))
15351577
{
15361578
// In Ruppert's paper, he used D(0.01) factor to divide edge length, but
15371579
// that introduces FP rounding errors, so it's avoided.
@@ -1547,7 +1589,6 @@ VertInd Triangulation<T, TNearPointLocator>::splitEncroachedEdge(
15471589
{
15481590
nearestPowerOfTwo *= T(0.5);
15491591
}
1550-
assert(abs(nearestPowerOfTwo - pow(2, round(log(d) / log(2.0)))) < 1e6);
15511592
split = nearestPowerOfTwo / len;
15521593
if(edge.v1() >= steinerVerticesOffset)
15531594
split = T(1) - split;
@@ -1556,15 +1597,21 @@ VertInd Triangulation<T, TNearPointLocator>::splitEncroachedEdge(
15561597
const V2d<T> mid = V2d<T>(
15571598
detail::lerp(start.x, end.x, split),
15581599
detail::lerp(start.y, end.y, split));
1559-
TriInd iT, iTopo;
1560-
std::tie(iT, iTopo) = edgeTriangles(edge.v1(), edge.v2());
1561-
assert(iT != invalidIndex && iTopo != invalidIndex);
15621600

15631601
const VertInd iMid = addSplitEdgeVertex(mid, iT, iTopo);
15641602
if(fixedEdges.find(edge) != fixedEdges.end())
15651603
{
15661604
splitFixedEdge(edge, iMid);
15671605
}
1606+
// splitting reuses iT/iTopo for two of the four resulting triangles and
1607+
// appends the other two: propagate erasure marks to the new triangles
1608+
if(toEraseOrNull)
1609+
{
1610+
if(toEraseOrNull->count(iT))
1611+
toEraseOrNull->insert(TriInd(triangles.size() - 2));
1612+
if(toEraseOrNull->count(iTopo))
1613+
toEraseOrNull->insert(TriInd(triangles.size() - 1));
1614+
}
15681615
return iMid;
15691616
}
15701617

@@ -2393,7 +2440,8 @@ template <typename T, typename TNearPointLocator>
23932440
void Triangulation<T, TNearPointLocator>::refineTriangles(
23942441
const VertInd maxVerticesToInsert,
23952442
const RefinementCriterion::Enum refinementCriterion,
2396-
const T refinementThreshold)
2443+
const T refinementThreshold,
2444+
TriIndUSet* const toEraseOrNull)
23972445
{
23982446
if(isFinalized())
23992447
{
@@ -2412,8 +2460,8 @@ void Triangulation<T, TNearPointLocator>::refineTriangles(
24122460
{
24132461
const Edge edge = encroachedEdges.front();
24142462
encroachedEdges.pop();
2415-
const VertInd iSplitVert =
2416-
splitEncroachedEdge(edge, steinerVerticesOffset);
2463+
const VertInd iSplitVert = splitEncroachedEdge(
2464+
edge, steinerVerticesOffset, toEraseOrNull);
24172465
// if resulting halves are encroached, add them to the queue
24182466
const Edge half1(edge.v1(), iSplitVert);
24192467
if(isEdgeEncroached(half1))
@@ -2436,6 +2484,7 @@ void Triangulation<T, TNearPointLocator>::refineTriangles(
24362484
{
24372485
const Triangle& t = triangles[iT];
24382486
if(!touchesSuperTriangle(t) &&
2487+
!(toEraseOrNull && toEraseOrNull->count(iT)) &&
24392488
isRefinementNeeded(t, refinementCriterion, refinementThreshold))
24402489
{
24412490
badTriangles.push(iT);
@@ -2446,6 +2495,8 @@ void Triangulation<T, TNearPointLocator>::refineTriangles(
24462495
{
24472496
const TriInd iT = badTriangles.front();
24482497
badTriangles.pop();
2498+
if(toEraseOrNull && toEraseOrNull->count(iT))
2499+
continue;
24492500
const Triangle& badT = triangles[iT];
24502501
if(!isRefinementNeeded(badT, refinementCriterion, refinementThreshold))
24512502
{
@@ -2468,7 +2519,8 @@ void Triangulation<T, TNearPointLocator>::refineTriangles(
24682519
steinerVerticesOffset,
24692520
&triCircumenter,
24702521
refinementCriterion,
2471-
refinementThreshold);
2522+
refinementThreshold,
2523+
toEraseOrNull);
24722524
if(!remainingVertexBudget)
24732525
break;
24742526
if(!badTris.empty())
@@ -2482,17 +2534,45 @@ void Triangulation<T, TNearPointLocator>::refineTriangles(
24822534
continue;
24832535
}
24842536

2485-
--remainingVertexBudget;
2537+
// locate the triangle(s) the circumcenter falls into before spending
2538+
// the vertex budget: if it lands in a triangle that will be erased
2539+
// anyway (e.g. a hole or outer triangle) skip it instead of adding a
2540+
// pointless Steiner point there
24862541
const VertInd iVert = static_cast<VertInd>(vertices.size());
24872542
addNewVertex(triCircumenter, noNeighbor);
2488-
insertVertex(iVert);
2543+
const VertInd walkStart =
2544+
m_nearPtLocator.nearPoint(triCircumenter, vertices);
2545+
const array<TriInd, 2> trisAt =
2546+
walkingSearchTrianglesAt(iVert, walkStart);
2547+
if(toEraseOrNull &&
2548+
(toEraseOrNull->count(trisAt[0]) || toEraseOrNull->count(trisAt[1])))
2549+
{
2550+
vertices.pop_back();
2551+
m_vertTris.pop_back();
2552+
continue;
2553+
}
2554+
2555+
--remainingVertexBudget;
2556+
// re-does the same walk as above internally, but reuses proven
2557+
// insertion logic (fixed-edge split handling, callbacks) rather than
2558+
// duplicating it here
2559+
insertVertex(iVert, walkStart);
2560+
tryAddVertexToLocator(iVert);
2561+
if(toEraseOrNull)
2562+
{
2563+
if(toEraseOrNull->count(trisAt[0]))
2564+
toEraseOrNull->insert(TriInd(triangles.size() - 2));
2565+
if(toEraseOrNull->count(trisAt[1]))
2566+
toEraseOrNull->insert(TriInd(triangles.size() - 1));
2567+
}
24892568

24902569
TriInd start = m_vertTris[iVert];
24912570
TriInd currTri = start;
24922571
do
24932572
{
24942573
const Triangle& t = triangles[currTri];
2495-
if(isRefinementNeeded(t, refinementCriterion, refinementThreshold))
2574+
if(!(toEraseOrNull && toEraseOrNull->count(currTri)) &&
2575+
isRefinementNeeded(t, refinementCriterion, refinementThreshold))
24962576
{
24972577
badTriangles.push(currTri);
24982578
}

0 commit comments

Comments
 (0)