Skip to content

Commit

Permalink
Cleanup and formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklockwood committed Jan 3, 2020
1 parent 633c58f commit 5ea4587
Show file tree
Hide file tree
Showing 18 changed files with 463 additions and 352 deletions.
4 changes: 3 additions & 1 deletion .swiftformat
@@ -1,4 +1,6 @@
--hexgrouping ignore
--decimalgrouping ignore
--enable isEmpty
--ifdef no-indent
--ifdef no-indent
--wraparguments before-first
--maxwidth 120
6 changes: 6 additions & 0 deletions Euclid.xcodeproj/project.pbxproj
Expand Up @@ -54,6 +54,8 @@
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 */; };
01F2382023BF4160005EC9DB /* LineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2381F23BF4160005EC9DB /* LineSegment.swift */; };
01F2382123BF4160005EC9DB /* LineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2381F23BF4160005EC9DB /* LineSegment.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 */; };
Expand Down Expand Up @@ -115,6 +117,7 @@
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>"; };
01F2381F23BF4160005EC9DB /* LineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSegment.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>"; };
Expand Down Expand Up @@ -189,6 +192,7 @@
016FAB4D21BFE7C200AF60DC /* Plane.swift */,
016FAB4921BFE7C200AF60DC /* Paths.swift */,
52A663A023857D5300FACF9D /* Line.swift */,
01F2381F23BF4160005EC9DB /* LineSegment.swift */,
016FAB4E21BFE7C200AF60DC /* Shapes.swift */,
016FAB4421BFE7C100AF60DC /* CSG.swift */,
016FAB4B21BFE7C200AF60DC /* Transforms.swift */,
Expand Down Expand Up @@ -417,6 +421,7 @@
016FAB5321BFE7C200AF60DC /* Bounds.swift in Sources */,
01BA297C2235E3590088D36B /* CoreGraphics.swift in Sources */,
01BA29712235A63F0088D36B /* CoreText.swift in Sources */,
01F2382023BF4160005EC9DB /* LineSegment.swift in Sources */,
016FAB5221BFE7C200AF60DC /* Vertex.swift in Sources */,
016FAB5821BFE7C200AF60DC /* Polygon.swift in Sources */,
52A663A123857D5300FACF9D /* Line.swift in Sources */,
Expand Down Expand Up @@ -458,6 +463,7 @@
016FAB6F21BFE80000AF60DC /* Bounds.swift in Sources */,
01BA297D2235E3590088D36B /* CoreGraphics.swift in Sources */,
01BA29722235A63F0088D36B /* CoreText.swift in Sources */,
01F2382123BF4160005EC9DB /* LineSegment.swift in Sources */,
016FAB7021BFE80000AF60DC /* Vertex.swift in Sources */,
016FAB7121BFE80000AF60DC /* Polygon.swift in Sources */,
52A663A223858A3700FACF9D /* Line.swift in Sources */,
Expand Down
57 changes: 36 additions & 21 deletions Sources/CSG.swift
Expand Up @@ -226,8 +226,11 @@ private func reduce(_ meshes: [Mesh], using fn: (Mesh, Mesh) -> Mesh) -> Mesh {
return reduce(&meshesAndBounds, at: 0, using: fn)
}

private func reduce(_ meshesAndBounds: inout [(Mesh, Bounds)],
at i: Int, using fn: (Mesh, Mesh) -> Mesh) -> Mesh {
private func reduce(
_ meshesAndBounds: inout [(Mesh, Bounds)],
at i: Int,
using fn: (Mesh, Mesh) -> Mesh
) -> Mesh {
var (m, mb) = meshesAndBounds[i]
var j = i + 1, count = meshesAndBounds.count
while j < count {
Expand Down Expand Up @@ -299,10 +302,12 @@ private class BSPNode {
return clip(polygons, keeping, clipBackfaces, &id)
}

private func clip(_ polygons: [Polygon],
_ keeping: ClipRule,
_ clipBackfaces: Bool,
_ id: inout Int) -> [Polygon] {
private func clip(
_ polygons: [Polygon],
_ keeping: ClipRule,
_ clipBackfaces: Bool,
_ id: inout Int
) -> [Polygon] {
var polygons = polygons
var node = self
var total = [Polygon]()
Expand Down Expand Up @@ -331,8 +336,12 @@ private class BSPNode {
}
for polygon in coplanar {
var inside = [Polygon](), outside = [Polygon]()
polygon.clip(to: node.polygons.flatMap { $0.tessellate() },
&inside, &outside, &id)
polygon.clip(
to: node.polygons.flatMap { $0.tessellate() },
&inside,
&outside,
&id
)
switch keeping {
case .greaterThan:
if node.plane!.normal.dot(polygon.plane.normal) > 0 {
Expand Down Expand Up @@ -455,10 +464,12 @@ extension Polygon {
return planes
}

func clip(to polygons: [Polygon],
_ inside: inout [Polygon],
_ outside: inout [Polygon],
_ id: inout Int) {
func clip(
to polygons: [Polygon],
_ inside: inout [Polygon],
_ outside: inout [Polygon],
_ id: inout Int
) {
precondition(isConvex)
var toTest = [self]
for polygon in polygons where !toTest.isEmpty {
Expand All @@ -472,10 +483,12 @@ extension Polygon {
outside = toTest
}

func clip(_ polygon: Polygon,
_ inside: inout [Polygon],
_ outside: inout [Polygon],
_ id: inout Int) {
func clip(
_ polygon: Polygon,
_ inside: inout [Polygon],
_ outside: inout [Polygon],
_ id: inout Int
) {
precondition(isConvex)
guard polygon.isConvex else {
polygon.tessellate().forEach {
Expand All @@ -496,11 +509,13 @@ extension Polygon {
inside.append(polygon)
}

func split(along plane: Plane,
_ coplanar: inout [Polygon],
_ front: inout [Polygon],
_ back: inout [Polygon],
_ id: inout Int) {
func split(
along plane: Plane,
_ coplanar: inout [Polygon],
_ front: inout [Polygon],
_ back: inout [Polygon],
_ id: inout Int
) {
enum PolygonType: Int {
case coplanar = 0
case front = 1
Expand Down
24 changes: 13 additions & 11 deletions Sources/CoreText.swift
Expand Up @@ -38,12 +38,13 @@ public extension Path {

public extension Mesh {
/// Create an extruded text model from a String
init(text: String,
font: CTFont? = nil,
width: Double? = nil,
depth: Double = 1,
detail: Int = 2,
material: Polygon.Material = nil
init(
text: String,
font: CTFont? = nil,
width: Double? = nil,
depth: Double = 1,
detail: Int = 2,
material: Polygon.Material = nil
) {
let font = font ?? CTFontCreateWithName("Helvetica" as CFString, 1, nil)
let attributes = [NSAttributedString.Key.font: font]
Expand All @@ -52,11 +53,12 @@ public extension Mesh {
}

/// Create an extruded text model from an attributed string
init(text: NSAttributedString,
width: Double? = nil,
depth: Double = 1,
detail: Int = 2,
material: Polygon.Material = nil
init(
text: NSAttributedString,
width: Double? = nil,
depth: Double = 1,
detail _: Int = 2,
material: Polygon.Material = nil
) {
var meshes = [Mesh]()
var cache = [CGPath: Mesh]()
Expand Down
145 changes: 21 additions & 124 deletions Sources/Line.swift
Expand Up @@ -29,157 +29,54 @@
// 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 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
point = from.point1
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))
let aMinusP = point - to
let v = aMinusP - (direction * aMinusP.dot(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)) {
if direction.z == 0, with.direction.z == 0, point.z == with.point.z {
return lineIntersection(point, point + direction, with.point, with.point + with.direction)
} else if direction.y == 0, with.direction.y == 0, 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 p0 = Vector(point.x, point.z, point.y)
let p1 = p0 + Vector(direction.x, 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)) {
return solution.map { Vector($0.x, $0.z, $0.y) }
} else if direction.x == 0, with.direction.x == 0, 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 p0 = Vector(point.y, point.z, point.x)
let p1 = p0 + Vector(direction.y, 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;
}
return solution.map { Vector($0.z, $0.x, $0.y) }
} else {
// TOOO: Generalize to 3D
return nil;
// TODO: 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
}

0 comments on commit 5ea4587

Please sign in to comment.