Skip to content

Commit

Permalink
exposed some funcs to objc-c, added a unit test for contains(path: Path)
Browse files Browse the repository at this point in the history
  • Loading branch information
hfutrell committed Dec 19, 2018
1 parent 916be7b commit 276d877
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 24 deletions.
12 changes: 12 additions & 0 deletions BezierKit/BezierKitTests/PathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,18 @@ class PathTests: XCTestCase {
XCTAssertFalse(circleWithHole.contains(CGPoint(x: 4.0, y: 0.0), using: .winding))
}

func testContainsPath() {
let rect1 = Path(cgPath: CGPath(rect: CGRect(x: 1, y: 1, width: 5, height: 5), transform: nil))
let rect2 = Path(cgPath: CGPath(rect: CGRect(x: 2, y: 2, width: 3, height: 3), transform: nil)) // fully contained inside rect1
let rect3 = Path(cgPath: CGPath(rect: CGRect(x: 2, y: 2, width: 5, height: 3), transform: nil)) // starts inside, but not contained in rect1
let rect4 = Path(cgPath: CGPath(rect: CGRect(x: 7, y: 1, width: 5, height: 5), transform: nil)) // fully outside rect1
XCTAssertTrue(rect1.contains(rect2))
XCTAssertFalse(rect1.contains(rect3))
XCTAssertFalse(rect1.contains(rect4))
}

// TODO: more tests of contains path using .winding rule and where intersections are not crossings

// MARK: - vector boolean operations

private func componentsEqualAsideFromElementOrdering(_ component1: PathComponent, _ component2: PathComponent) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion BezierKit/Library/AugmentedGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ internal class PathLinkedListRepresentation {
assert(end !== list[location.elementIndex+1])
end = end.next
}
insertIntersectionVertex(v, between: start, and: end, at: location.t, for: path.element(at: location), inList: &list)
insertIntersectionVertex(v, between: start, and: end, at: location.t, for: path.elementAtComponentIndex(location.componentIndex, elementIndex: location.elementIndex), inList: &list)
}
self.lists[location.componentIndex] = list
}
Expand Down
38 changes: 20 additions & 18 deletions BezierKit/Library/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR

public let subpaths: [PathComponent]

public func pointIsWithinDistanceOfBoundary(point p: CGPoint, distance d: CGFloat) -> Bool {
@objc(point:isWithinDistanceOfBoundary:) public func pointIsWithinDistanceOfBoundary(point p: CGPoint, distance d: CGFloat) -> Bool {
return self.subpaths.contains {
$0.pointIsWithinDistanceOfBoundary(point: p, distance: d)
}
Expand Down Expand Up @@ -159,11 +159,11 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR
// MARK: - vector boolean operations

public func point(at location: IndexedPathLocation) -> CGPoint {
return self.element(at: location).compute(location.t)
return self.elementAtComponentIndex(location.componentIndex, elementIndex: location.elementIndex).compute(location.t)
}

internal func element(at location: IndexedPathLocation) -> BezierCurve {
return self.subpaths[location.componentIndex].curves[location.elementIndex]
internal func elementAtComponentIndex(_ componentIndex: Int, elementIndex: Int) -> BezierCurve {
return self.subpaths[componentIndex].curves[elementIndex]
}

internal func windingCount(_ point: CGPoint, ignoring: PathComponent? = nil) -> Int {
Expand All @@ -178,16 +178,23 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR
return windingCount
}

private func contains(_ other: Path) -> Bool {
guard other.subpaths.isEmpty == false else {
return true
}
guard self.intersects(path: other).isEmpty else {
return false
}
return other.subpaths.reduce(true) {
$0 && self.contains($1.curves[0].startingPoint)
@objc(containsPoint:usingRule:) public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool {
let count = self.windingCount(point)
return windingCountImpliesContainment(count, using: rule)
}

@objc(containsPath:) public func contains(_ other: Path) -> Bool {
// first, check that each component of `other` starts inside self
for component in other.subpaths {
let p = component.curves[0].startingPoint
guard self.contains(p) else {
return false
}
}
// next, for each intersection (if there are any) check that we stay inside the path
// TODO: use enumeration over intersections so we don't have to necessarily have to find each one
// TODO: make this work with winding fill rule and intersections that don't cross (suggestion, use AugmentedGraph)
return self.intersects(path: other).isEmpty
}

@objc(offsetWithDistance:) public func offset(distance d: CGFloat) -> Path {
Expand All @@ -196,11 +203,6 @@ internal func windingCountImpliesContainment(_ count: Int, using rule: PathFillR
})
}

@objc public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool {
let count = self.windingCount(point)
return windingCountImpliesContainment(count, using: rule)
}

private func performBooleanOperation(_ operation: BooleanPathOperation, withPath other: Path, threshold: CGFloat) -> Path? {
let intersections = self.intersects(path: other, threshold: threshold)
let augmentedGraph = AugmentedGraph(path1: self, path2: other, intersections: intersections)
Expand Down
6 changes: 1 addition & 5 deletions BezierKit/Library/PathComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public final class PathComponent: NSObject, NSCoding {
let intersections = self.intersects(line: line)
var windingCount = 0
intersections.forEach {
let element = self.element(at: $0)
let element = self.curves[$0.elementIndex]
let t = $0.t
assert(element.derivative($0.t).length > 1.0e-3, "possible NaN normal vector. Possible data for unit test?")
let dotProduct = delta.dot(element.normal(t))
Expand All @@ -225,10 +225,6 @@ public final class PathComponent: NSObject, NSCoding {
return windingCount
}

private func element(at location: IndexedPathComponentLocation) -> BezierCurve {
return self.curves[location.elementIndex]
}

public func contains(_ point: CGPoint, using rule: PathFillRule = .winding) -> Bool {
let windingCount = self.windingCount(at: point)
return windingCountImpliesContainment(windingCount, using: rule)
Expand Down

0 comments on commit 276d877

Please sign in to comment.