From 57ad4b335aa6e25ceaccb301d4c9b19f97d8e649 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Wed, 20 Oct 2021 14:19:47 -0700 Subject: [PATCH] use projected intersection rather than linear average to make offset paths contiguous (fixes #78) --- BezierKit/Library/BezierCurve.swift | 16 ++++++++++++++++ BezierKit/Library/PathComponent.swift | 22 +++++++++++----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/BezierKit/Library/BezierCurve.swift b/BezierKit/Library/BezierCurve.swift index a28848a..edca914 100644 --- a/BezierKit/Library/BezierCurve.swift +++ b/BezierKit/Library/BezierCurve.swift @@ -322,3 +322,19 @@ public extension Flatness { return sqrt(flatnessSquared) } } + +extension BezierCurve { + public func projectedIntersection(with other : BezierCurve) -> CGPoint? { + // using determinant formula described at + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection + let x1 = self.startingPoint.x; let y1 = self.startingPoint.y + let x2 = self.endingPoint.x; let y2 = self.endingPoint.y + let x3 = other.startingPoint.x; let y3 = other.startingPoint.y + let x4 = other.endingPoint.x; let y4 = other.endingPoint.y + let d = ((x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4)) + guard d != 0 else { return nil } + let nx = (((x1 * y2) - (y1 * x2)) * (x3 - x4)) - ((x1 - x2) * ((x3 * y4) - (y3 * x4))) + let ny = (((x1 * y2) - (y1 * x2)) * (y3 - y4)) - ((y1 - y2) * ((x3 * y4) - (y3 * x4))) + return CGPoint(x: nx/d, y: ny/d) + } +} diff --git a/BezierKit/Library/PathComponent.swift b/BezierKit/Library/PathComponent.swift index 7f4adf6..9b21871 100644 --- a/BezierKit/Library/PathComponent.swift +++ b/BezierKit/Library/PathComponent.swift @@ -194,22 +194,22 @@ import Foundation $0 + $1.offset(distance: d) } guard offsetCurves.isEmpty == false else { return nil } + let referenceCurves = offsetCurves.map { $0.copy(using: .identity) } + func makeContiguous(_ thisCurveIdx : Int, _ nextCurveIdx : Int) { + guard offsetCurves[thisCurveIdx].endingPoint != offsetCurves[nextCurveIdx].startingPoint else { return } + guard let intersection = referenceCurves[thisCurveIdx].projectedIntersection(with: referenceCurves[nextCurveIdx]) else { fatalError("what the heck") } + offsetCurves[thisCurveIdx].endingPoint = intersection + offsetCurves[nextCurveIdx].startingPoint = intersection + } + // force the set of curves to be contiguous for i in 0..