Skip to content

Commit

Permalink
Merge 54a4d33 into e4c9115
Browse files Browse the repository at this point in the history
  • Loading branch information
andygeers committed Nov 27, 2019
2 parents e4c9115 + 54a4d33 commit 5d2a8c5
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 52 deletions.
14 changes: 14 additions & 0 deletions Euclid.xcodeproj/project.pbxproj
Expand Up @@ -54,6 +54,10 @@
01BA297A2235E34C0088D36B /* CGPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BA29792235E34C0088D36B /* CGPathTests.swift */; };
01BA297C2235E3590088D36B /* CoreGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BA297B2235E3580088D36B /* CoreGraphics.swift */; };
01BA297D2235E3590088D36B /* CoreGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BA297B2235E3580088D36B /* CoreGraphics.swift */; };
52A3852E238D6E5700BE8407 /* LineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A3852D238D6E5700BE8407 /* LineTests.swift */; };
52A663A123857D5300FACF9D /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A663A023857D5300FACF9D /* Line.swift */; };
52A663A223858A3700FACF9D /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A663A023857D5300FACF9D /* Line.swift */; };
52C844E223854CDF009C0A73 /* VectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52C844E023854C87009C0A73 /* VectorTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -111,6 +115,9 @@
01BA29702235A63F0088D36B /* CoreText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreText.swift; sourceTree = "<group>"; };
01BA29792235E34C0088D36B /* CGPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGPathTests.swift; sourceTree = "<group>"; };
01BA297B2235E3580088D36B /* CoreGraphics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreGraphics.swift; sourceTree = "<group>"; };
52A3852D238D6E5700BE8407 /* LineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineTests.swift; sourceTree = "<group>"; };
52A663A023857D5300FACF9D /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
52C844E023854C87009C0A73 /* VectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -181,6 +188,7 @@
016FAB4721BFE7C200AF60DC /* Bounds.swift */,
016FAB4D21BFE7C200AF60DC /* Plane.swift */,
016FAB4921BFE7C200AF60DC /* Paths.swift */,
52A663A023857D5300FACF9D /* Line.swift */,
016FAB4E21BFE7C200AF60DC /* Shapes.swift */,
016FAB4421BFE7C100AF60DC /* CSG.swift */,
016FAB4B21BFE7C200AF60DC /* Transforms.swift */,
Expand All @@ -205,6 +213,8 @@
016FAB5D21BFE7CE00AF60DC /* UtilityTests.swift */,
01BA29792235E34C0088D36B /* CGPathTests.swift */,
01A429F92237A85C00C251A6 /* TextTests.swift */,
52C844E023854C87009C0A73 /* VectorTests.swift */,
52A3852D238D6E5700BE8407 /* LineTests.swift */,
01BA2969222726BD0088D36B /* XCTestManifests.swift */,
016FAB3921BFE78100AF60DC /* Info.plist */,
);
Expand Down Expand Up @@ -409,6 +419,7 @@
01BA29712235A63F0088D36B /* CoreText.swift in Sources */,
016FAB5221BFE7C200AF60DC /* Vertex.swift in Sources */,
016FAB5821BFE7C200AF60DC /* Polygon.swift in Sources */,
52A663A123857D5300FACF9D /* Line.swift in Sources */,
016FAB4F21BFE7C200AF60DC /* Vector.swift in Sources */,
016FAB5021BFE7C200AF60DC /* CSG.swift in Sources */,
016FAB5621BFE7C200AF60DC /* SceneKit.swift in Sources */,
Expand All @@ -422,6 +433,7 @@
buildActionMask = 2147483647;
files = (
01A429FA2237A85C00C251A6 /* TextTests.swift in Sources */,
52A3852E238D6E5700BE8407 /* LineTests.swift in Sources */,
016FAB6621BFE7CE00AF60DC /* ShapeTests.swift in Sources */,
016FAB6321BFE7CE00AF60DC /* UtilityTests.swift in Sources */,
016FAB6221BFE7CE00AF60DC /* PathTests.swift in Sources */,
Expand All @@ -431,6 +443,7 @@
016FAB6521BFE7CE00AF60DC /* CSGTests.swift in Sources */,
01BA297A2235E34C0088D36B /* CGPathTests.swift in Sources */,
013312DD21CA532A00626F1B /* PlaneTests.swift in Sources */,
52C844E223854CDF009C0A73 /* VectorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -447,6 +460,7 @@
01BA29722235A63F0088D36B /* CoreText.swift in Sources */,
016FAB7021BFE80000AF60DC /* Vertex.swift in Sources */,
016FAB7121BFE80000AF60DC /* Polygon.swift in Sources */,
52A663A223858A3700FACF9D /* Line.swift in Sources */,
016FAB7221BFE80000AF60DC /* Vector.swift in Sources */,
016FAB7321BFE80000AF60DC /* CSG.swift in Sources */,
016FAB7421BFE80000AF60DC /* SceneKit.swift in Sources */,
Expand Down
185 changes: 185 additions & 0 deletions Sources/Line.swift
@@ -0,0 +1,185 @@
//
// Line.swift
// Euclid
//
// Created by Nick Lockwood on 20/11/2019.
// Copyright © 2018 Nick Lockwood. All rights reserved.
//
// Distributed under the permissive MIT license
// Get the latest version from here:
//
// https://github.com/nicklockwood/Euclid
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

import Foundation

public struct LineSegment : Hashable {
public init(_ point1: Vector, _ point2: Vector) {
self.point1 = point1
self.point2 = point2
}

public var point1: Vector {
didSet { point1 = point1.quantized() }
}

public var point2: Vector {
didSet { point2 = point2.quantized() }
}

public var direction : Vector {
let diff = point2 - point1
return diff.normalized()
}

public func intersects(with: LineSegment) -> Bool {
if ((self.direction.z == 0) && (with.direction.z == 0) && (self.point1.z == with.point1.z)) {
return lineSegmentsIntersect(self.point1, self.point2, with.point1, with.point2)
} else if ((self.direction.y == 0) && (with.direction.y == 0) && (self.point1.y == with.point1.y)) {
// Switch dimensions and then solve
let p0 = Vector(self.point1.x, self.point1.z, 0)
let p1 = Vector(self.point2.x, self.point2.z, 0)
let p2 = Vector(with.point1.x, with.point1.z, 0)
let p3 = Vector(with.point2.x, with.point2.z, 0)
return lineSegmentsIntersect(p0, p1, p2, p3)
} else if ((self.direction.x == 0) && (with.direction.x == 0) && (self.point1.x == with.point1.x)) {
// Switch dimensions and then solve
let p0 = Vector(self.point1.y, self.point1.z, 0)
let p1 = Vector(self.point2.y, self.point2.z, 0)
let p2 = Vector(with.point1.y, with.point1.z, 0)
let p3 = Vector(with.point2.y, with.point2.z, 0)
return lineSegmentsIntersect(p0, p1, p2, p3)
} else {
// TOOO: Generalize to 3D
return false;
}
}
}

public struct Line : Hashable {
public init(point: Vector, direction: Vector) {
self.point = point
self.direction = direction
}

public init(from: LineSegment) {
self.point = from.point1
self.direction = from.direction
}

public var point: Vector {
didSet { point = point.quantized() }
}

public var direction: Vector {
didSet { direction = direction.normalized() }
}

public func distance(to: Vector) -> Double {
// See "Vector formulation" at https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
let aMinusP = self.point - to
let v = aMinusP - (self.direction * aMinusP.dot(self.direction))
return v.length
}

public func intersection(with: Line) -> Vector? {
if ((self.direction.z == 0) && (with.direction.z == 0) && (self.point.z == with.point.z)) {
return lineIntersection(self.point, self.point + self.direction, with.point, with.point + with.direction)
} else if ((self.direction.y == 0) && (with.direction.y == 0) && (self.point.y == with.point.y)) {
// Switch dimensions and then solve
let p0 = Vector(self.point.x, self.point.z, self.point.y)
let p1 = p0 + Vector(self.direction.x, self.direction.z, 0)
let p2 = Vector(with.point.x, with.point.z, with.point.y)
let p3 = p2 + Vector(with.direction.x, with.direction.z, 0)
let solution = lineIntersection(p0, p1, p2, p3)
if (solution != nil) {
return Vector(solution!.x, solution!.z, solution!.y)
} else {
return nil;
}
} else if ((self.direction.x == 0) && (with.direction.x == 0) && (self.point.x == with.point.x)) {
// Switch dimensions and then solve
let p0 = Vector(self.point.y, self.point.z, self.point.x)
let p1 = p0 + Vector(self.direction.y, self.direction.z, 0)
let p2 = Vector(with.point.y, with.point.z, with.point.x)
let p3 = p2 + Vector(with.direction.y, with.direction.z, 0)
let solution = lineIntersection(p0, p1, p2, p3)
if (solution != nil) {
return Vector(solution!.z, solution!.x, solution!.y)
} else {
return nil;
}
} else {
// TOOO: Generalize to 3D
return nil;
}
}
}

// MARK: Private utility functions

// Get the intersection point between two lines
// TODO: extend this to work in 3D
// TODO: improve this using https://en.wikipedia.org/wiki/Line–line_intersection
private func lineIntersection(_ p0: Vector, _ p1: Vector,
_ p2: Vector, _ p3: Vector) -> Vector? {
let x1 = p0.x, y1 = p0.y
let x2 = p1.x, y2 = p1.y
let x3 = p2.x, y3 = p2.y
let x4 = p3.x, y4 = p3.y

let x1y2 = x1 * y2, y1x2 = y1 * x2
let x1y2minusy1x2 = x1y2 - y1x2

let x3minusx4 = x3 - x4
let x1minusx2 = x1 - x2

let x3y4 = x3 * y4, y3x4 = y3 * x4
let x3y4minusy3x4 = x3y4 - y3x4

let y3minusy4 = y3 - y4
let y1minusy2 = y1 - y2

let d = x1minusx2 * y3minusy4 - y1minusy2 * x3minusx4
if abs(d) < epsilon {
return nil // lines are parallel
}
let ix = (x1y2minusy1x2 * x3minusx4 - x1minusx2 * x3y4minusy3x4) / d
let iy = (x1y2minusy1x2 * y3minusy4 - y1minusy2 * x3y4minusy3x4) / d

return Vector(ix, iy, p0.z).quantized()
}

// TODO: extend this to work in 3D
private func lineSegmentsIntersect(_ p0: Vector, _ p1: Vector,
_ p2: Vector, _ p3: Vector) -> Bool {
guard let pi = lineIntersection(p0, p1, p2, p3) else {
return false // lines are parallel
}
// TODO: is there a cheaper way to do this?
if pi.x < min(p0.x, p1.x) || pi.x > max(p0.x, p1.x) ||
pi.x < min(p2.x, p3.x) || pi.x > max(p2.x, p3.x) ||
pi.y < min(p0.y, p1.y) || pi.y > max(p0.y, p1.y) ||
pi.y < min(p2.y, p3.y) || pi.y > max(p2.y, p3.y) {
return false
}
return true
}
56 changes: 4 additions & 52 deletions Sources/Paths.swift
Expand Up @@ -303,7 +303,9 @@ internal extension Path {
if p1 == p2 || p2 == p3 || p3 == p0 {
continue
}
if lineSegmentsIntersect(p0, p1, p2, p3) {
let l1 = LineSegment(p0, p1)
let l2 = LineSegment(p2, p3)
if l1.intersects(with: l2) {
return false
}
}
Expand Down Expand Up @@ -395,56 +397,6 @@ internal extension Path {
}
}

// MARK: Private utility functions

// Get the intersection point between two lines
// TODO: extend this to work in 3D
// TODO: improve this using https://en.wikipedia.org/wiki/Line–line_intersection
private func lineIntersection(_ p0: Vector, _ p1: Vector,
_ p2: Vector, _ p3: Vector) -> Vector? {
let x1 = p0.x, y1 = p0.y
let x2 = p1.x, y2 = p1.y
let x3 = p2.x, y3 = p2.y
let x4 = p3.x, y4 = p3.y

let x1y2 = x1 * y2, y1x2 = y1 * x2
let x1y2minusy1x2 = x1y2 - y1x2

let x3minusx4 = x3 - x4
let x1minusx2 = x1 - x2

let x3y4 = x3 * y4, y3x4 = y3 * x4
let x3y4minusy3x4 = x3y4 - y3x4

let y3minusy4 = y3 - y4
let y1minusy2 = y1 - y2

let d = x1minusx2 * y3minusy4 - y1minusy2 * x3minusx4
if abs(d) < epsilon {
return nil // lines are parallel
}
let ix = (x1y2minusy1x2 * x3minusx4 - x1minusx2 * x3y4minusy3x4) / d
let iy = (x1y2minusy1x2 * y3minusy4 - y1minusy2 * x3y4minusy3x4) / d

return Vector(ix, iy)
}

// TODO: extend this to work in 3D
private func lineSegmentsIntersect(_ p0: Vector, _ p1: Vector,
_ p2: Vector, _ p3: Vector) -> Bool {
guard let pi = lineIntersection(p0, p1, p2, p3) else {
return false // lines are parallel
}
// TODO: is there a cheaper way to do this?
if pi.x < min(p0.x, p1.x) || pi.x > max(p0.x, p1.x) ||
pi.x < min(p2.x, p3.x) || pi.x > max(p2.x, p3.x) ||
pi.y < min(p0.y, p1.y) || pi.y > max(p0.y, p1.y) ||
pi.y < min(p2.y, p3.y) || pi.y > max(p2.y, p3.y) {
return false
}
return true
}

// MARK: Path utility functions

func sanitizePoints(_ points: [PathPoint]) -> [PathPoint] {
Expand Down Expand Up @@ -524,7 +476,7 @@ func extrapolate(_ p0: PathPoint, _ p1: PathPoint, _ p2: PathPoint) -> PathPoint
p0p1 = p0p1 / length
let p1p2 = (p2.position - p1.position).normalized()
let axis = p0p1.cross(p1p2)
let angle = -acos(p0p1.dot(p1p2))
let angle = -p0p1.angleWith(p1p2)
let r = Rotation(axis: axis, radians: angle) ?? .identity
let p2pe = p1p2.rotated(by: r) * length
return .curve(p2.position + p2pe)
Expand Down

0 comments on commit 5d2a8c5

Please sign in to comment.