Skip to content

Commit

Permalink
[Clipping] Adding tolerance to Parametric2Contour.split(at:) and fixi…
Browse files Browse the repository at this point in the history
…ng incorrect .sameSpan detection of adjacent circular arcs
  • Loading branch information
LuizZak committed Dec 10, 2024
1 parent f5fbf9e commit 63f033a
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 31 deletions.
32 changes: 16 additions & 16 deletions Sources/GeometriaClipping/2D/Graph/Simplex2Graph+Creation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,16 @@ extension Simplex2Graph {
continue
}

contours[lhsIndex].split(at: intersection.`self`)
contours[rhsIndex].split(at: intersection.other)
contours[lhsIndex].split(at: intersection.`self`, tolerance: tolerance)
contours[rhsIndex].split(at: intersection.other, tolerance: tolerance)

allIntersections.append((
lhsIndex, intersection.`self`,
rhsIndex, intersection.other
))
}

// Compute vertex/edge interference intersections
// Compute edge/edge interference intersections
for lhsSimplex in contours[lhsIndex].allSimplexes() {
for rhsSimplex in contours[rhsIndex].allSimplexes() {
let coincidence = lhsSimplex.coincidenceRelationship(with: rhsSimplex, tolerance: tolerance)
Expand All @@ -139,8 +139,8 @@ extension Simplex2Graph {
break

case .lhsContainsRhs(let lhsStart, let lhsEnd):
contours[lhsIndex].split(at: lhsStart)
contours[lhsIndex].split(at: lhsEnd)
contours[lhsIndex].split(at: lhsStart, tolerance: tolerance)
contours[lhsIndex].split(at: lhsEnd, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsStart, rhsIndex, rhsSimplex.startPeriod)
Expand All @@ -150,8 +150,8 @@ extension Simplex2Graph {
)

case .rhsContainsLhs(let rhsStart, let rhsEnd):
contours[rhsIndex].split(at: rhsStart)
contours[rhsIndex].split(at: rhsEnd)
contours[rhsIndex].split(at: rhsStart, tolerance: tolerance)
contours[rhsIndex].split(at: rhsEnd, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsSimplex.startPeriod, rhsIndex, rhsStart)
Expand All @@ -161,36 +161,36 @@ extension Simplex2Graph {
)

case .lhsPrefixesRhs(let rhsEnd):
contours[rhsIndex].split(at: rhsEnd)
contours[rhsIndex].split(at: rhsEnd, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsSimplex.endPeriod, rhsIndex, rhsEnd)
)

case .lhsSuffixesRhs(let rhsStart):
contours[rhsIndex].split(at: rhsStart)
contours[rhsIndex].split(at: rhsStart, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsSimplex.startPeriod, rhsIndex, rhsStart)
)

case .rhsPrefixesLhs(let lhsEnd):
contours[lhsIndex].split(at: lhsEnd)
contours[lhsIndex].split(at: lhsEnd, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsEnd, rhsIndex, rhsSimplex.endPeriod)
)

case .rhsSuffixesLhs(let lhsStart):
contours[lhsIndex].split(at: lhsStart)
contours[lhsIndex].split(at: lhsStart, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsStart, rhsIndex, rhsSimplex.startPeriod)
)

case .rhsContainsLhsStart(let rhsStart, let lhsEnd):
contours[rhsIndex].split(at: rhsStart)
contours[lhsIndex].split(at: lhsEnd)
contours[rhsIndex].split(at: rhsStart, tolerance: tolerance)
contours[lhsIndex].split(at: lhsEnd, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsSimplex.startPeriod, rhsIndex, rhsStart)
Expand All @@ -200,8 +200,8 @@ extension Simplex2Graph {
)

case .rhsContainsLhsEnd(let lhsEnd, let rhsStart):
contours[lhsIndex].split(at: lhsEnd)
contours[rhsIndex].split(at: rhsStart)
contours[lhsIndex].split(at: lhsEnd, tolerance: tolerance)
contours[rhsIndex].split(at: rhsStart, tolerance: tolerance)

allIntersections.append(
(lhsIndex, lhsEnd, rhsIndex, rhsSimplex.startPeriod)
Expand Down Expand Up @@ -503,7 +503,7 @@ extension Simplex2Graph {
hasMergedEdges = true
}

prune()
//prune()

return (hasMergedNodes, hasMergedEdges)
}
Expand Down
24 changes: 12 additions & 12 deletions Sources/GeometriaClipping/2D/Graph/Simplex2Graph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,18 @@ public struct Simplex2Graph<Vector: Vector2Real & Hashable> {
sweepAngle: rhsSweepAngle
)

// Ignore angles that are joined end-to-end
func withinTolerance(_ v1: Scalar, _ v2: Scalar) -> Bool {
(v1 - v2).magnitude <= tolerance
}

if
withinTolerance(lhsSweep.start.normalized(from: .zero), rhsSweep.stop.normalized(from: .zero)) ||
withinTolerance(lhsSweep.stop.normalized(from: .zero), rhsSweep.start.normalized(from: .zero))
{
return .notCoincident
}

lhsStartCoincident =
areClose(lhs.startPoint, rhs.startPoint) ||
areClose(lhs.startPoint, rhs.endPoint)
Expand All @@ -698,18 +710,6 @@ public struct Simplex2Graph<Vector: Vector2Real & Hashable> {
return .sameSpan
}

// Ignore angles that are joined end-to-end
func withinTolerance(_ v1: Scalar, _ v2: Scalar) -> Bool {
(v1 - v2).magnitude <= tolerance
}

if
withinTolerance(lhsSweep.start.normalized(from: .zero), rhsSweep.stop.normalized(from: .zero)) ||
withinTolerance(lhsSweep.stop.normalized(from: .zero), rhsSweep.start.normalized(from: .zero))
{
return .notCoincident
}

lhsPeriod = { scalar in
self.geometry.map { geometry in
return .init(
Expand Down
7 changes: 4 additions & 3 deletions Sources/GeometriaClipping/2D/Parametric2Contour.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,18 @@ public struct Parametric2Contour<Vector: Vector2Real>: BoundableType, CustomStri
/// Splits this contour at a period, ensuring that a simplex split happens
/// at that period.
///
/// If the given period is the start of a period, no change is made.
/// If the given period is the start of a period, +/- tolerance, no change
/// is made.
@inlinable
public mutating func split(at period: Period) {
public mutating func split(at period: Period, tolerance: Scalar) {
let period = normalizedPeriod(period)

guard let simplexIndex = simplexes.firstIndex(where: { $0.periodRange.contains(period) }) else {
return
}

let simplex = simplexes[simplexIndex]
if period == simplex.startPeriod {
if period.isApproximatelyEqualFast(to: simplex.startPeriod, tolerance: tolerance) {
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,39 @@ class Union2ParametricTests: XCTestCase {
}
}

func testUnion_capsuleSequence_short_3() {
let polys: [Capsule2Parametric<Vector2D>] = [
.init(
start: .init(x: 840.9345087440722, y: 120.0420038990834),
startRadius: 25.29491955052341,
end: .init(x: 825.7795755118234, y: 294.99630400154695),
endRadius: 40.07160791260946,
startPeriod: 0.0,
endPeriod: 1.0
),
.init(
start: .init(x: 825.7795755118234, y: 294.99630400154695),
startRadius: 40.07160791260946,
end: .init(x: 443.8865955891325, y: 214.2706642265273),
endRadius: 36.77680233971253,
startPeriod: 0.0,
endPeriod: 1.0
),
]

let sut = Union2Parametric(contours: polys.flatMap { $0.allContours() }, tolerance: 1e-5)

TestFixture.beginFixture(lineScale: 1.0, renderScale: 0.45) { fixture in
fixture.add(polys, category: "inputs")

fixture.assertions(on: sut)
.assertAllSimplexes(
accuracy: accuracy,
[[Parametric2GeometrySimplex<Vector2<Double>>.circleArc2(CircleArc2Simplex<Vector2<Double>>(center: Vector2<Double>(x: 840.9345087440722, y: 120.0420038990834), radius: 25.29491955052341, startAngle: Angle<Double>(radians: -2.970941306184629), sweepAngle: Angle<Double>(radians: 1.5707963267948966), startPeriod: 0.0, endPeriod: 0.03033636815053754)), Parametric2GeometrySimplex<Vector2<Double>>.circleArc2(CircleArc2Simplex<Vector2<Double>>(center: Vector2<Double>(x: 840.9345087440722, y: 120.0420038990834), radius: 25.29491955052341, startAngle: Angle<Double>(radians: -1.4001449793897325), sweepAngle: Angle<Double>(radians: 1.4023067425093831), startPeriod: 0.03033636815053754, endPeriod: 0.05741874215095776)), Parametric2GeometrySimplex<Vector2<Double>>.lineSegment2(LineSegment2Simplex<Vector2<Double>>(start: Vector2<Double>(x: 866.2293691902594, y: 120.09668548069239), end: Vector2<Double>(x: 865.8510897927539, y: 295.0829292582077), startPeriod: 0.05741874215095776, endPeriod: 0.19102147143202303)), Parametric2GeometrySimplex<Vector2<Double>>.circleArc2(CircleArc2Simplex<Vector2<Double>>(center: Vector2<Double>(x: 825.7795755118234, y: 294.99630400154695), radius: 40.07160791260946, startAngle: Angle<Double>(radians: 0.002161763119650789), sweepAngle: Angle<Double>(radians: 0.19771339814696445), startPeriod: 0.19102147143202303, endPeriod: 0.19707046714015988)), Parametric2GeometrySimplex<Vector2<Double>>.circleArc2(CircleArc2Simplex<Vector2<Double>>(center: Vector2<Double>(x: 825.7795755118234, y: 294.99630400154695), radius: 40.07160791260946, startAngle: Angle<Double>(radians: 0.19987516126661525), sweepAngle: Angle<Double>(radians: 1.373082928647932), startPeriod: 0.19707046714015988, endPeriod: 0.2390796219073725)), Parametric2GeometrySimplex<Vector2<Double>>.circleArc2(CircleArc2Simplex<Vector2<Double>>(center: Vector2<Double>(x: 825.7795755118234, y: 294.99630400154695), radius: 40.07160791260946, startAngle: Angle<Double>(radians: 1.5729580899145474), sweepAngle: Angle<Double>(radians: 0.21459567676896898), startPeriod: 0.2390796219073725, endPeriod: 0.2456451270270373)), Parametric2GeometrySimplex<Vector2<Double>>.lineSegment2(LineSegment2Simplex<Vector2<Double>>(start: Vector2<Double>(x: 817.1616122130969, y: 334.1302338998297), end: Vector2<Double>(x: 435.9772266040055, y: 250.1868871987636), startPeriod: 0.2456451270270373, endPeriod: 0.5436537396120984)), Parametric2GeometrySimplex<Vector2<Double>>.circleArc2(CircleArc2Simplex<Vector2<Double>>(center: Vector2<Double>(x: 443.8865955891325, y: 214.2706642265273), radius: 36.77680233971253, startAngle: Angle<Double>(radians: 1.7875537666835175), sweepAngle: Angle<Double>(radians: 1.5707963267948966), startPeriod: 0.5436537396120984, endPeriod: 0.5877604074727591)), Parametric2GeometrySimplex<Vector2<Double>>.circleArc2(CircleArc2Simplex<Vector2<Double>>(center: Vector2<Double>(x: 443.8865955891325, y: 214.2706642265273), radius: 36.77680233971253, startAngle: Angle<Double>(radians: 3.358350093478414), sweepAngle: Angle<Double>(radians: 1.5539140481728904), startPeriod: 0.5877604074727591, endPeriod: 0.6313930348463052)), Parametric2GeometrySimplex<Vector2<Double>>.lineSegment2(LineSegment2Simplex<Vector2<Double>>(start: Vector2<Double>(x: 451.18851859011914, y: 178.2260375698126), end: Vector2<Double>(x: 793.297000370629, y: 247.5303978489986), startPeriod: 0.6313930348463052, endPeriod: 0.8978994590751505)), Parametric2GeometrySimplex<Vector2<Double>>.lineSegment2(LineSegment2Simplex<Vector2<Double>>(start: Vector2<Double>(x: 793.297000370629, y: 247.5303978489986), end: Vector2<Double>(x: 816.0070140542555, y: 115.74631262099889), startPeriod: 0.8978994590751505, endPeriod: 1.0))]]
)
}
}

func testUnion_capsuleSequence_long() {
let inputs = Capsule2Parametric.makeCapsuleSequence([
(.init(x: -150, y: -10), 20.0),
Expand Down

0 comments on commit 63f033a

Please sign in to comment.