Skip to content

Commit

Permalink
[GeometriaClipping] Fixing interference checks on circular arc simple…
Browse files Browse the repository at this point in the history
…xes that differed slightly in radius
  • Loading branch information
LuizZak committed Aug 18, 2024
1 parent 82a84f3 commit 4eae9a9
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 11 deletions.
29 changes: 24 additions & 5 deletions Sources/GeometriaClipping/2D/Geometry/Capsule2Parametric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public struct Capsule2Parametric<Vector: Vector2Real>: ParametricClip2Geometry,

public var isReversed: Bool

internal init(
public init(
start: Vector,
end: Vector,
radius: Scalar,
Expand All @@ -34,7 +34,7 @@ public struct Capsule2Parametric<Vector: Vector2Real>: ParametricClip2Geometry,
)
}

internal init(
public init(
start: Vector,
startRadius: Scalar,
end: Vector,
Expand All @@ -60,10 +60,29 @@ public struct Capsule2Parametric<Vector: Vector2Real>: ParametricClip2Geometry,
let startArc = CircleArc2(clockwiseAngleToCenter: start, startPoint: tangents.1.start, endPoint: tangents.0.start)
let endArc = CircleArc2(clockwiseAngleToCenter: end, startPoint: tangents.0.end, endPoint: tangents.1.end)

let simplexes: [Simplex] = [
.circleArc2(.init(circleArc: startArc, startPeriod: 0, endPeriod: 0)),
var simplexes: [Simplex] = []

simplexes +=
CircleArc2Simplex.splittingArcSegments(
startArc,
startPeriod: 0,
endPeriod: 0,
maxAbsoluteSweepAngle: .pi / 2
).map(Parametric2GeometrySimplex.circleArc2)

simplexes += [
.lineSegment2(.init(lineSegment: tangents.0, startPeriod: 0, endPeriod: 0)),
.circleArc2(.init(circleArc: endArc, startPeriod: 0, endPeriod: 0)),
]

simplexes +=
CircleArc2Simplex.splittingArcSegments(
endArc,
startPeriod: 0,
endPeriod: 0,
maxAbsoluteSweepAngle: .pi / 2
).map(Parametric2GeometrySimplex.circleArc2)

simplexes += [
.lineSegment2(.init(lineSegment: tangents.1.reversed, startPeriod: 0, endPeriod: 0)),
]

Expand Down
28 changes: 28 additions & 0 deletions Sources/GeometriaClipping/2D/Graph/Simplex2Graph+Creation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,34 @@ extension Simplex2Graph {
rhsIndex, intersection.other
))
}

// TODO: Figure out why moving the interference splitting to here breaks exclusive disjunctions
#if false

// Compute vertex/edge interference intersections
for lhsSimplex in lhs.allSimplexes() {
for rhsSimplex in rhs.allSimplexes() {
if lhsSimplex.isOnSurface(rhsSimplex.start, toleranceSquared: toleranceSquared) {
let period = lhsSimplex.closestPeriod(to: rhsSimplex.start)
contours[lhsIndex].split(at: period)
}
if lhsSimplex.isOnSurface(rhsSimplex.end, toleranceSquared: toleranceSquared) {
let period = lhsSimplex.closestPeriod(to: rhsSimplex.end)
contours[lhsIndex].split(at: period)
}

if rhsSimplex.isOnSurface(lhsSimplex.start, toleranceSquared: toleranceSquared) {
let period = rhsSimplex.closestPeriod(to: lhsSimplex.start)
contours[rhsIndex].split(at: period)
}
if rhsSimplex.isOnSurface(lhsSimplex.end, toleranceSquared: toleranceSquared) {
let period = rhsSimplex.closestPeriod(to: lhsSimplex.end)
contours[rhsIndex].split(at: period)
}
}
}

#endif // #if false
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/GeometriaClipping/2D/Graph/Simplex2Graph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ public struct Simplex2Graph<Vector: Vector2Real & Hashable> {
case (
.circleArc(let lhsCenter, let lhsRadius, let lhsStart, let lhsSweep),
.circleArc(let rhsCenter, let rhsRadius, let rhsStart, let rhsSweep)
) where lhsCenter == rhsCenter && lhsRadius == rhsRadius:
) where lhsCenter == rhsCenter && lhsRadius.isApproximatelyEqualFast(to: rhsRadius, tolerance: tolerance):
let lhsSweep = AngleSweep(start: lhsStart, sweep: lhsSweep)
let rhsSweep = AngleSweep(start: rhsStart, sweep: rhsSweep)

Expand Down Expand Up @@ -651,7 +651,7 @@ public struct Simplex2Graph<Vector: Vector2Real & Hashable> {
case (
.circleArc(let lhsCenter, let lhsRadius, let lhsStartAngle, let lhsSweepAngle),
.circleArc(let rhsCenter, let rhsRadius, let rhsStartAngle, let rhsSweepAngle)
) where lhsCenter == rhsCenter && lhsRadius == rhsRadius:
) where lhsCenter == rhsCenter && lhsRadius.isApproximatelyEqualFast(to: rhsRadius, tolerance: tolerance):
let lhsSweep = AngleSweep(start: lhsStartAngle, sweep: lhsSweepAngle)
let rhsSweep = AngleSweep(start: rhsStartAngle, sweep: rhsSweepAngle)

Expand Down
53 changes: 53 additions & 0 deletions Sources/GeometriaClipping/2D/Simplexes/CircleArc2Simplex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,56 @@ public struct CircleArc2Simplex<Vector: Vector2Real>: Parametric2Simplex, Equata
)
}
}

extension CircleArc2Simplex {
/// Returns an array of `CircleArc2Simplex` instances that span the given arc
/// with a given period, with the least number of arcs that are have a sweep
/// angle of at most `maxAbsoluteSweepAngle`.
public static func splittingArcSegments(
_ arc: CircleArc2<Vector>,
startPeriod: Period,
endPeriod: Period,
maxAbsoluteSweepAngle: Scalar
) -> [Self] {
var result: [Self] = []

let startAngle = arc.sweepAngle.radians
var totalAngle: Scalar = .zero

let sign: Scalar = startAngle > .zero ? 1 : -1

var remaining = startAngle.magnitude
let step = maxAbsoluteSweepAngle.magnitude

let periodRange = endPeriod - startPeriod

while remaining > .zero {
defer { remaining -= step }

let sweep: Scalar
if remaining < step {
sweep = remaining * sign
} else {
sweep = step * sign
}

defer { totalAngle += sweep }

let sPeriod: Period = startPeriod + periodRange * (totalAngle / startAngle)
let ePeriod: Period = startPeriod + periodRange * ((totalAngle + sweep) / startAngle)

let simplex = Self(
center: arc.center,
radius: arc.radius,
startAngle: .init(radians: arc.startAngle.radians + totalAngle),
sweepAngle: .init(radians: sweep),
startPeriod: sPeriod,
endPeriod: ePeriod
)

result.append(simplex)
}

return result
}
}
13 changes: 13 additions & 0 deletions Sources/TestCommons/TestFixture/TestFixture+Clipping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ import GeometriaClipping
import XCTest

public extension TestFixture {
func add<Vector>(
_ value: [some ParametricClip2Geometry<Vector>],
category: String,
style: P5Printer.Style? = nil,
file: StaticString = #file,
line: UInt = #line
) where Vector.Scalar: CustomStringConvertible {

for geometry in value {
add(geometry, category: category, style: style, file: file, line: line)
}
}

func add<T: ParametricClip2Geometry>(
_ value: T,
category: String,
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,21 @@ class Capsule2ParametricTests: XCTestCase {
center: .init(x: -150.0, y: -30.0),
radius: 60.00000000000001,
startAngle: Angle(radians: 2.1977925247947656),
sweepAngle: Angle(radians: 2.8148954755916673)
sweepAngle: Angle(radians: 1.5707963267948966)
),
startPeriod: 0.0,
endPeriod: 0.09414335996574721
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: -150.0, y: -30.0),
radius: 60.00000000000001,
startAngle: Angle(radians: 3.768588851589662),
sweepAngle: Angle(radians: 1.2440991487967707)
),
startPeriod: 0.09414335996574721,
endPeriod: 0.16870660664537054
)
),
Expand All @@ -53,9 +65,33 @@ class Capsule2ParametricTests: XCTestCase {
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: -1.2704973067931533),
sweepAngle: Angle(radians: 3.468289831587919)
sweepAngle: Angle(radians: 1.5707963267948966)
),
startPeriod: 0.41113094230800334,
endPeriod: 0.5680365422509154
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: 0.30029902000174324),
sweepAngle: Angle(radians: 1.5707963267948966)
),
startPeriod: 0.5680365422509154,
endPeriod: 0.7249421421938274
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: 1.8710953467966398),
sweepAngle: Angle(radians: 0.3266971779981258)
),
startPeriod: 0.7249421421938274,
endPeriod: 0.7575756643373672
)
),
Expand Down Expand Up @@ -107,9 +143,33 @@ class Capsule2ParametricTests: XCTestCase {
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: 2.1977925247947656),
sweepAngle: Angle(radians: -3.468289831587919)
sweepAngle: Angle(radians: -0.3266971779981258)
),
startPeriod: 0.24242433566263277,
endPeriod: 0.27505785780617265
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: 1.8710953467966398),
sweepAngle: Angle(radians: -1.5707963267948966)
),
startPeriod: 0.27505785780617265,
endPeriod: 0.4319634577490846
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: 0.30029902000174324),
sweepAngle: Angle(radians: -1.5707963267948966)
),
startPeriod: 0.4319634577490846,
endPeriod: 0.5888690576919966
)
),
Expand All @@ -129,9 +189,21 @@ class Capsule2ParametricTests: XCTestCase {
center: .init(x: -150.0, y: -30.0),
radius: 60.00000000000001,
startAngle: Angle(radians: 5.012688000386433),
sweepAngle: Angle(radians: -2.8148954755916673)
sweepAngle: Angle(radians: -1.2440991487967707)
),
startPeriod: 0.8312933933546295,
endPeriod: 0.9058566400342528
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: -150.0, y: -30.0),
radius: 60.00000000000001,
startAngle: Angle(radians: 3.768588851589662),
sweepAngle: Angle(radians: -1.5707963267948966)
),
startPeriod: 0.9058566400342528,
endPeriod: 1.0
)
),
Expand Down
Loading

0 comments on commit 4eae9a9

Please sign in to comment.