Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
hfutrell committed May 12, 2021
1 parent 3de6eb6 commit 2ec6cc4
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 82 deletions.
14 changes: 3 additions & 11 deletions BezierKit/BezierKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -351,24 +351,16 @@
path = BezierKit_Mac;
sourceTree = "<group>";
};
FD7205BF2616870F007B0585 /* Implicitization */ = {
isa = PBXGroup;
children = (
FD07BC1E261BC97400ED39FF /* BernsteinPolynomialN.swift */,
FD07BC1F261BC97400ED39FF /* BezierCurve+Implicitization.swift */,
FD07BC1D261BC97400ED39FF /* RootFinding.swift */,
);
path = Implicitization;
sourceTree = "<group>";
};
FDB6B3F61EAFD6DF00001C61 /* Library */ = {
isa = PBXGroup;
children = (
FD149EB92135CBFF009E791D /* AugmentedGraph.swift */,
FDC859622119274A00AF7642 /* BoundingBoxHierarchy.swift */,
FDB6B3F71EAFD6DF00001C61 /* BezierCurve.swift */,
FDB6011A25BB9B3700BAB067 /* BezierCurve+Polynomial.swift */,
FD7205BF2616870F007B0585 /* Implicitization */,
FD07BC1E261BC97400ED39FF /* BernsteinPolynomialN.swift */,
FD07BC1F261BC97400ED39FF /* BezierCurve+Implicitization.swift */,
FD07BC1D261BC97400ED39FF /* RootFinding.swift */,
FD5CF14B22400FCA00FE15A6 /* BezierCurve+Intersection.swift */,
FDB6B3F81EAFD6DF00001C61 /* CubicCurve.swift */,
FDB6B3FC1EAFD6DF00001C61 /* CGPoint+Overloads.swift */,
Expand Down
4 changes: 1 addition & 3 deletions BezierKit/BezierKitTests/CubicCurveTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ class CubicCurveTests: XCTestCase {
let intersections = c1.intersections(with: c2, accuracy: 1.0e-4)
XCTAssertEqual(intersections, [Intersection(t1: 0, t2: 1)])
}

func testCubicIntersectsLine() {
let epsilon: CGFloat = 0.00001
let c: CubicCurve = CubicCurve(p0: CGPoint(x: -1, y: 0),
Expand Down Expand Up @@ -634,8 +634,6 @@ class CubicCurveTests: XCTestCase {
XCTAssertEqual(line.intersections(with: curve), [Intersection(t1: 0, t2: 0), Intersection(t1: 1, t2: 1)], "curve and line should be fully coincident")
}

#warning("add unit tests for precision issues that can happen with curve intersection far into a document.")

// MARK: -

func testEquatable() {
Expand Down
10 changes: 5 additions & 5 deletions BezierKit/BezierKitTests/PerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private extension PerformanceTests {
}
return curves
}

#if canImport(CoreGraphics)

func parametricPath(numCurves: Int,
Expand Down Expand Up @@ -64,7 +64,7 @@ private extension PerformanceTests {
}
return Path(cgPath: cgPath)
}

#endif
}

Expand Down Expand Up @@ -163,9 +163,9 @@ class PerformanceTests: XCTestCase {
}
}
}

#if canImport(CoreGraphics)

func testPathProjectPerformance() {
let k: CGFloat = 2.0 * CGFloat.pi * 10
let maxRadius: CGFloat = 100.0
Expand Down Expand Up @@ -226,6 +226,6 @@ class PerformanceTests: XCTestCase {
_ = path1.subtract(path2, accuracy: 1.0e-3)
}
}

#endif
}
11 changes: 11 additions & 0 deletions BezierKit/BezierKitTests/PolynomialTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ class PolynomialTests: XCTestCase {
XCTAssertEqual(roots[1], 0.407811682610126, accuracy: 1.0e-5)
}

func testDegreeN() {
// 2x^2 + 2x + 1
let polynomial = BernsteinPolynomialN(coefficients: [1, 2, 5])
XCTAssertEqual(polynomial.derivative, BernsteinPolynomialN(coefficients: [2, 6]))
XCTAssertEqual(polynomial.reversed(), BernsteinPolynomialN(coefficients: [5, 2, 1]))
// some edge cases
XCTAssertEqual(BernsteinPolynomialN(coefficients: [42]).split(from: 0.1, to: 0.9),
BernsteinPolynomialN(coefficients: [42]))
XCTAssertEqual(polynomial.split(from: 1, to: 0), polynomial.reversed())
}

func testDegreeNRealWorldIssue() {
// this input would cause a stack overflow if the division step of the interval
// occurred before checking the interval's size
Expand Down
89 changes: 33 additions & 56 deletions BezierKit/Library/BezierCurve+Intersection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,88 +115,65 @@ fileprivate extension BezierCurve {
}
}

var total = 0

internal func helperIntersectsCurveCurve<U, T>(_ curve1: Subcurve<U>, _ curve2: Subcurve<T>, accuracy: CGFloat) -> [Intersection] where U: NonlinearBezierCurve, T: NonlinearBezierCurve {

let insignificantDistance: CGFloat = 0.5 * accuracy

// try intersecting using subdivision
let lb = curve1.curve.boundingBox
let rb = curve2.curve.boundingBox
var pairIntersections: [Intersection] = []
var iterations = 0
#warning("curve1.curve.order * curve2.curve.order also appears in Utils, it should probably be part of the config")
if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &iterations),
pairIntersections.count < curve1.curve.order * curve2.curve.order {
var subdivisionIterations = 0
if Utils.pairiteration(curve1, curve2, lb, rb, &pairIntersections, accuracy, &subdivisionIterations) {
return pairIntersections.sortedAndUniqued()
}

total += 1


// subdivision failed, check if the curves are coincident
let insignificantDistance: CGFloat = 0.5 * accuracy
if let coincidence = coincidenceCheck(curve1.curve, curve2.curve, accuracy: 0.1 * accuracy) {
return coincidence
}

// find any intersections using curve implicitization
let transform = CGAffineTransform(translationX: -curve2.curve.startingPoint.x, y: -curve2.curve.startingPoint.y)
let c2 = curve2.curve.downgradedIfPossible(maximumError: insignificantDistance).copy(using: transform)

let c1 = curve1.curve.copy(using: transform)
let equation: BernsteinPolynomialN = c2.implicitPolynomial.value(c1.xPolynomial, c1.yPolynomial)
let roots = equation.distinctRealRootsInUnitInterval(configuration: RootFindingConfiguration(errorThreshold: RootFindingConfiguration.minimumErrorThreshold))

#warning("clean up")
if let coincidence = coincidenceCheck(curve1.curve, curve2.curve, accuracy: 0.1 * insignificantDistance) {
return coincidence
}

let t1Tolerance = insignificantDistance / c1.derivativeBounds
let t2Tolerance = insignificantDistance / c2.derivativeBounds

var intersections = roots.compactMap { t1 -> Intersection? in

#warning("clean up")
var adjustedT1 = t1
if adjustedT1 < t1Tolerance {
adjustedT1 = 0.0
} else if adjustedT1 > 1.0 - t1Tolerance {
adjustedT1 = 1.0
func intersectionIfCloseEnough(at t1: CGFloat) -> Intersection? {
let point = c1.point(at: t1)
guard c2.boundingBox.contains(point) else { return nil }
var t2 = c2.project(point).t
if t2 < t2Tolerance {
t2 = 0
} else if t2 > 1 - t2Tolerance {
t2 = 1
}

let point = c1.point(at: adjustedT1)
guard c2.boundingBox.contains(point) else {
return nil
}

#warning("todo: handle double point here")
let t2 = c2.project(point).t // numerator.value(point) / deonominator.value(point)

guard distance(point, c2.point(at: t2)) < accuracy else {
return nil
}

#warning("clean up")
var adjustedT2 = t2
if Utils.approximately(Double(adjustedT2), 0.0, precision: Double(t2Tolerance)) {
adjustedT2 = 0.0
} else if Utils.approximately(Double(adjustedT2), 1.0, precision: Double(t2Tolerance)) {
adjustedT2 = 1.0
guard distance(point, c2.point(at: t2)) < accuracy else { return nil }
return Intersection(t1: t1, t2: t2)
}
var intersections = roots.compactMap { t1 -> Intersection? in
if t1 < t1Tolerance {
return nil // (t1 near 0 handled explicitly)
} else if t1 > 1 - t1Tolerance {
return nil // (t1 near 1 handled explicitly)
}

guard adjustedT2 >= 0, adjustedT2 <= 1 else { return nil }
return Intersection(t1: adjustedT1, t2: adjustedT2)
return intersectionIfCloseEnough(at: t1)
}

#warning("clean up")
if intersections.contains(where: { $0.t1 == 0 }) == false {
let projection = c2.project(c1.startingPoint)
if distance(projection.point, c1.startingPoint) < insignificantDistance {
intersections.insert(Intersection(t1: 0, t2: projection.t), at: 0)
if let intersection = intersectionIfCloseEnough(at: 0) {
intersections.append(intersection)
}
}

if intersections.contains(where: { $0.t1 == 1 }) == false {
let projection = c2.project(c1.endingPoint)
if distance(projection.point, c1.endingPoint) < insignificantDistance {
intersections.append(Intersection(t1: 1, t2: projection.t))
if let intersection = intersectionIfCloseEnough(at: 1) {
intersections.append(intersection)
}
}

// TODO: handle case where curve2 self-intersects and curve intersects it there
return intersections.sortedAndUniqued()
}

Expand Down
14 changes: 7 additions & 7 deletions BezierKit/Library/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal class Utils {
precondition(n >= 0 && k >= 0 && n <= 9 && k <= 9)
return binomialTable[n][k]
}

// float precision significant decimal
static let epsilon: Double = 1.0e-5
static let tau: Double = 2.0 * Double.pi
Expand Down Expand Up @@ -285,7 +285,7 @@ internal class Utils {
static func linearInterpolate(_ first: CGFloat, _ second: CGFloat, _ t: CGFloat) -> CGFloat {
return (1 - t) * first + t * second
}

static func arcfn(_ t: CGFloat, _ derivativeFn: (_ t: CGFloat) -> CGPoint) -> CGFloat {
let d = derivativeFn(t)
return d.length
Expand Down Expand Up @@ -317,12 +317,12 @@ internal class Utils {
_ accuracy: CGFloat,
_ totalIterations: inout Int) -> Bool {

let maximumIterations = 900
let maximumIntersections = c1.curve.order * c2.curve.order

totalIterations += 1
if totalIterations > 900 {
return false
}

guard results.count < c1.curve.order * c2.curve.order else { return true }
guard totalIterations <= maximumIterations else { return false }
guard results.count <= maximumIntersections else { return false }
guard c1b.overlaps(c2b) else { return true }

let canSplit1 = c1.canSplit
Expand Down

0 comments on commit 2ec6cc4

Please sign in to comment.