Skip to content

Commit

Permalink
Improve path builders
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasf committed Oct 22, 2024
1 parent 24d6819 commit 9179320
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 137 deletions.
Original file line number Diff line number Diff line change
@@ -1,56 +1,40 @@
import Foundation

public extension BezierPath {
@resultBuilder struct Builder {
public typealias Component = BezierPath<V>.Component
typealias Builder = ArrayBuilder<BezierPath<V>.Component>

public static func buildExpression(_ expression: Component) -> [Component] {
[expression]
}

public static func buildBlock(_ children: [Component]...) -> [Component] {
children.flatMap { $0 }
}
init(from: V = .zero, mode defaultMode: PathBuilderPositioning = .absolute, @Builder builder: () -> [Component]) {
var start = from
self.init(startPoint: from, curves: builder().flatMap {
$0.bezierCurves(start: &start, defaultMode: defaultMode)
})
}

// If without else
public static func buildOptional(_ children: [Component]?) -> [Component] {
if let children {
[.init(group: children)]
} else {
[]
}
}
struct Component {
private let points: [PathBuilderVector<V>]

// If
public static func buildEither(first child: [Component]) -> [Component] {
[.init(group: child)]
internal init(_ points: [PathBuilderVector<V>]) {
self.points = points
}

// Else
public static func buildEither(second child: [Component]) -> [Component] {
[.init(group: child)]
internal func bezierCurves(start: inout V, defaultMode: PathBuilderPositioning) -> [Curve] {
let controlPoints = [start] + points.map {
$0.value(relativeTo: start, defaultMode: defaultMode)
}
start = controlPoints.last!
return [Curve(controlPoints: controlPoints)]
}

// Loops
public static func buildArray(_ children: [[Component]]) -> [Component] {
children.flatMap { $0 }
internal func withDefaultMode(_ mode: PathBuilderPositioning) -> Self {
.init(points.map { $0.withDefaultMode(mode) })
}

public static func buildExpression(_ void: Void) -> [Component] { [] }
public static func buildExpression(_ never: Never) -> [Component] {}
public var relative: Component { withDefaultMode(.relative) }
public var absolute: Component { withDefaultMode(.absolute) }
}
}

public extension BezierPath {
enum BuilderPositioning {
case relative
case absolute
}

init(from: V = .zero, _ positioning: BuilderPositioning = .absolute, @Builder builder: () -> [Component]) {
var start = from
self.init(startPoint: from, curves: builder().flatMap {
$0.bezierCurves(start: &start, positioning: positioning)
})
}
public enum PathBuilderPositioning: Sendable {
case absolute
case relative
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,55 @@ public func line<V: Vector>(_ point: V) -> BezierPath<V>.Component {
}

public func curve<V: Vector>(_ controlPoints: [V]) -> BezierPath<V>.Component {
.init(controlPoints.map(OptionalVector.init))
.init(controlPoints.map(PathBuilderVector<V>.init))
}

// MARK: - Groups

public func group<V: Vector>(@BezierPath<V>.Builder builder: () -> [BezierPath<V>.Component]) -> BezierPath<V>.Component {
.init(group: builder())
}


// MARK: - 2D

public func line(x: Double? = nil, y: Double? = nil) -> BezierPath2D.Component {
public func line(
x: any PathBuilderValue = .unchanged,
y: any PathBuilderValue = .unchanged
) -> BezierPath2D.Component {
.init([.init(x, y)])
}

public func curve(
controlX x1: Double, controlY y1: Double,
endX: Double, endY: Double
controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue,
endX: any PathBuilderValue, endY: any PathBuilderValue
) -> BezierPath2D.Component {
.init([.init(x1, y1), .init(endX, endY)])
}

public func curve(
controlX x1: Double, controlY y1: Double,
controlX x2: Double, controlY y2: Double,
endX: Double, endY: Double
controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue,
controlX x2: any PathBuilderValue, controlY y2: any PathBuilderValue,
endX: any PathBuilderValue, endY: any PathBuilderValue
) -> BezierPath2D.Component {
.init([.init(x1, y1), .init(x2, y2), .init(endX, endY)])
}


// MARK: - 3D

public func line(x: Double? = nil, y: Double? = nil, z: Double? = nil) -> BezierPath3D.Component {
public func line(
x: any PathBuilderValue = .unchanged,
y: any PathBuilderValue = .unchanged,
z: any PathBuilderValue = .unchanged
) -> BezierPath3D.Component {
.init([.init(x, y, z)])
}

public func curve(
controlX x1: Double, controlY y1: Double, controlZ z1: Double,
endX: Double, endY: Double, endZ: Double
controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue, controlZ z1: any PathBuilderValue,
endX: any PathBuilderValue, endY: any PathBuilderValue, endZ: any PathBuilderValue
) -> BezierPath3D.Component {
.init([.init(x1, y1, z1), .init(endX, endY, endZ)])
}

public func curve(
controlX x1: Double, controlY y1: Double, controlZ z1: Double,
controlX x2: Double, controlY y2: Double, controlZ z2: Double,
endX: Double, endY: Double, endZ: Double
controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue, controlZ z1: any PathBuilderValue,
controlX x2: any PathBuilderValue, controlY y2: any PathBuilderValue, controlZ z2: any PathBuilderValue,
endX: any PathBuilderValue, endY: any PathBuilderValue, endZ: any PathBuilderValue
) -> BezierPath3D.Component {
.init([.init(x1, y1, z1), .init(x2, y2, z2), .init(endX, endY, endZ)])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Foundation

public protocol PathBuilderValue: Sendable {}

extension Double: PathBuilderValue {
public var relative: any PathBuilderValue {
PositionedValue(value: self, mode: .relative)
}

public var absolute: any PathBuilderValue {
PositionedValue(value: self, mode: .absolute)
}
}

public extension PathBuilderValue where Self == Double {
static var unchanged: any PathBuilderValue { 0.relative }
}

internal struct PositionedValue: PathBuilderValue {
var value: Double
var mode: PathBuilderPositioning?

func value(relativeTo base: Double, defaultMode: PathBuilderPositioning) -> Double {
switch mode ?? defaultMode {
case .relative: base + value
case .absolute: value
}
}

func withDefaultMode(_ defaultMode: PathBuilderPositioning) -> PositionedValue {
.init(value: value, mode: mode ?? defaultMode)
}
}

internal extension PathBuilderValue {
var positionedValue: PositionedValue {
if let self = self as? Double {
PositionedValue(value: self, mode: nil)
} else if let self = self as? PositionedValue {
self
} else {
preconditionFailure("Unknown BezierBuilderValue type.")
}
}
}

internal typealias PathBuilderVector<V: Vector> = DimensionalValue<PositionedValue, V>

internal extension PathBuilderVector where Element == PositionedValue {
init(_ vector: V) {
self.init {
PositionedValue(value: vector[$0], mode: nil)
}
}

init(_ x: any PathBuilderValue, _ y: any PathBuilderValue) where V == Vector2D {
self.init(x: x.positionedValue, y: y.positionedValue)
}

init(_ x: any PathBuilderValue, _ y: any PathBuilderValue, _ z: any PathBuilderValue) where V == Vector3D {
self.init(x: x.positionedValue, y: y.positionedValue, z: z.positionedValue)
}

func withDefaultMode(_ mode: PathBuilderPositioning) -> Self {
map { $1.withDefaultMode(mode) }
}

func value(relativeTo base: V, defaultMode: PathBuilderPositioning) -> V {
map {
$1.value(relativeTo: base[$0], defaultMode: defaultMode)
}.vector
}
}
18 changes: 10 additions & 8 deletions Sources/SwiftSCAD/Values/Vectors/DimensionalValue.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

internal struct DimensionalValue<Element: Equatable & Sendable, V: Vector>: Equatable, Sendable {
private enum Value: Equatable {
internal struct DimensionalValue<Element: Sendable, V: Vector>: Sendable {
private enum Value {
case xy (Element, Element)
case xyz (Element, Element, Element)
}
Expand Down Expand Up @@ -43,12 +43,8 @@ internal struct DimensionalValue<Element: Equatable & Sendable, V: Vector>: Equa
value = .xyz(x, y, z)
}

func map<New>(_ operation: (Element) -> New) -> DimensionalValue<New, V> {
.init(elements.map(operation))
}

func mapVector(_ operation: (Int, Element) -> Double) -> V {
.init(elements: elements.enumerated().map(operation))
func map<New>(_ operation: (_ index: Int, _ element: Element) -> New) -> DimensionalValue<New, V> {
.init(elements.enumerated().map(operation))
}

subscript(_ index: Int) -> Element {
Expand All @@ -59,3 +55,9 @@ internal struct DimensionalValue<Element: Equatable & Sendable, V: Vector>: Equa
elements.contains(where: predicate)
}
}

extension DimensionalValue where Element == Double {
var vector: V {
.init(elements: elements)
}
}
26 changes: 0 additions & 26 deletions Sources/SwiftSCAD/Values/Vectors/OptionalVector.swift

This file was deleted.

10 changes: 7 additions & 3 deletions Tests/Tests/BezierPathBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Testing

struct BezierPathBuilderTests {
@Test func testAbsolute() {
let builderPath = BezierPath2D(from: [10, 4], .absolute) {
let builderPath = BezierPath2D(from: [10, 4]) {
line(x: 22, y: 1)
line(x: 2)
line(y: 76)
Expand Down Expand Up @@ -31,10 +31,13 @@ struct BezierPathBuilderTests {
}

@Test func testRelative() {
let builderPath = BezierPath2D(from: [10, 4], .relative) {
let builderPath = BezierPath2D(from: [10, 4], mode: .relative) {
line(x: 22, y: 1)
line(x: 2)
line(y: 76)
line(y: 74)
if true {
line(y: 2)
}
curve(
controlX: 7, controlY: 12,
endX: 77, endY: 18
Expand All @@ -50,6 +53,7 @@ struct BezierPathBuilderTests {
let manualPath = BezierPath2D(startPoint: [10, 4])
.addingLine(to: [32, 5])
.addingLine(to: [34, 5])
.addingLine(to: [34, 79])
.addingLine(to: [34, 81])
.addingQuadraticCurve(controlPoint: [41, 93], end: [111, 99])
.addingLine(to: [111, 99])
Expand Down

0 comments on commit 9179320

Please sign in to comment.