Skip to content

Commit

Permalink
Vector lab
Browse files Browse the repository at this point in the history
  • Loading branch information
schwa committed Apr 1, 2024
1 parent ecc30a6 commit 46c557c
Show file tree
Hide file tree
Showing 45 changed files with 463 additions and 501 deletions.
17 changes: 7 additions & 10 deletions Examples/VectorLaboratory/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
import SwiftUI
import CoreGraphicsSupport
import Algorithms
import CoreGraphicsSupport
import Observation
import VectorSupport
import Sketches
import SwiftUI
import VectorSupport

struct ContentView: View {

@State
var sketch = Sketch()

var body: some View {
SketchEditorView(sketch: $sketch)
}
}


struct A: View {

// var body: some View {
// SketchCanvas()
// }

@State
var points: [CGPoint] = [[50, 50], [250, 50], [300, 100]]

var body: some View {
ZStack {
PathCanvas(points: $points)
CustomStrokeView(points: points)
.contentShape(.interaction, EmptyShape())
.contentShape(.interaction, EmptyShape())
}
}
}
20 changes: 8 additions & 12 deletions Examples/VectorLaboratory/ElbowView.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import CoreGraphicsSupport
import SwiftUI
import VectorSupport
import CoreGraphicsSupport

struct ElbowView: View {
let points: [CGPoint]
let width: CGFloat = 20

var body: some View {
Canvas { context, size in
Canvas { context, _ in
let angles = points.indexed().map { index, point in
switch index {
case points.startIndex:
return CGPoint.angle(point, points[index+1])
return CGPoint.angle(point, points[index + 1])
case points.endIndex - 1:
return CGPoint.angle(points[index-1], point)
return CGPoint.angle(points[index - 1], point)
default:
let angle0 = CGPoint.angle(points[index-1], point)
let angle1 = CGPoint.angle(point, points[index+1])
let angle0 = CGPoint.angle(points[index - 1], point)
let angle1 = CGPoint.angle(point, points[index + 1])
return (angle0 + angle1) / 2
}
}
Expand All @@ -32,7 +32,7 @@ struct ElbowView: View {
do {
let segments = points.windows(ofCount: 2).map(\.tuple)
let lengths = segments.map(CGPoint.distance)
let totalLength = segments.reduce(0) { return $0 + CGPoint.distance($1) }
let totalLength = segments.reduce(0) { $0 + CGPoint.distance($1) }

var result: [[CGFloat]] = [[]]
var iterator = lengths.makeIterator()
Expand All @@ -52,20 +52,16 @@ struct ElbowView: View {
}

for (lengths, lines) in zip(result, lines.windows(ofCount: 2).map(\.tuple)) {

let left = (lines.0.left, lines.1.left)
let right = (lines.0.right, lines.1.right)

for length in lengths {
let left = lerp(from: left.0, to: left.1, by: length)
let right = lerp(from: right.0, to: right.1, by: length)
context.stroke(Path(lineSegment: (left, right)), with: .color(.purple))


}
}
}

}
}
}
10 changes: 5 additions & 5 deletions Examples/VectorLaboratory/LineExperimentView.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import SwiftUI
import Sketches
import SwiftUI
import VectorSupport

struct LineExperimentView: View {
@Binding
var sketch: Sketch

var body: some View {
Canvas { context, size in
Canvas { context, _ in
for bounds in rectangles {
for segment in lineSegments {
let line = segment.line
Expand All @@ -18,13 +18,13 @@ struct LineExperimentView: View {
}
}
}

var rectangles: [CGRect] {
sketch.elements.map(\.shape).compactMap { shape in
shape.as(Sketch.Rectangle.self).map { CGRect($0) }
}
}

var lineSegments: [LineSegment] {
sketch.elements.map(\.shape).compactMap { shape in
shape.as(Sketch.LineSegment.self).map { LineSegment($0) }
Expand Down
56 changes: 22 additions & 34 deletions Examples/VectorLaboratory/OldContentView.swift
Original file line number Diff line number Diff line change
@@ -1,53 +1,48 @@
import SwiftUI
import CoreGraphicsSupport
import Algorithms
import CoreGraphicsSupport
import SwiftUI

struct OldContentView: View {

@State
var points: [CGPoint] = [[50, 50], [250, 50], [300, 100]]

var body: some View {
ZStack {
PathCanvas(points: $points)
CustomStrokeView(points: points)
.contentShape(.interaction, EmptyShape())

.contentShape(.interaction, EmptyShape())
}
}

}

struct CustomStrokeView: View {
let points: [CGPoint]

var segments: [LineSegment] {
points.windows(ofCount: 2).map(\.tuple).map {
LineSegment(start: $0, end: $1)
}
}

@State
var widths: [CGFloat] = []

var body: some View {
Canvas { context, size in
Canvas { context, _ in

for segment in segments {
//context.stroke(Path(segment), with: .color(.red))
// context.stroke(Path(segment), with: .color(.red))
let left = segment.parallel(offset: -10)
context.stroke(Path(left), with: .color(.red))
let right = segment.parallel(offset: 10)
context.stroke(Path(right), with: .color(.green))
}


}
.onAppear {
self.widths = Array(repeating: 10, count: segments.count)
widths = Array(repeating: 10, count: segments.count)
}
.onChange(of: points) {
self.widths = Array(repeating: 10, count: segments.count)
widths = Array(repeating: 10, count: segments.count)
}
}
}
Expand Down Expand Up @@ -101,36 +96,33 @@ func intersection(_ lhs: Line, _ rhs: Line) -> Intersection {
}
}


extension LineSegment {

func map(_ t: (CGPoint) throws -> CGPoint) rethrows -> LineSegment {
return LineSegment(start: try t(start), end: try t(end))
try LineSegment(start: t(start), end: t(end))
}

func parallel(offset: CGFloat) -> LineSegment {
let angle = angle(start, end) - .degrees(90)
let offset = CGPoint(distance: offset, angle: angle)
return map { $0 + offset }
}

}

struct ElbowView: View {
let points: [CGPoint]
let width: CGFloat = 20

var body: some View {
Canvas { context, size in
Canvas { context, _ in
let angles = points.indexed().map { index, point in
switch index {
case points.startIndex:
return angle(point, points[index+1])
return angle(point, points[index + 1])
case points.endIndex - 1:
return angle(points[index-1], point)
return angle(points[index - 1], point)
default:
let angle0 = angle(points[index-1], point)
let angle1 = angle(point, points[index+1])
let angle0 = angle(points[index - 1], point)
let angle1 = angle(point, points[index + 1])
return (angle0 + angle1) / 2
}
}
Expand All @@ -146,7 +138,7 @@ struct ElbowView: View {
do {
let segments = points.windows(ofCount: 2).map(\.tuple)
let lengths = segments.map(distance)
let totalLength = segments.reduce(0) { return $0 + distance($1) }
let totalLength = segments.reduce(0) { $0 + distance($1) }

var result: [[CGFloat]] = [[]]
var iterator = lengths.makeIterator()
Expand All @@ -166,27 +158,23 @@ struct ElbowView: View {
}

for (lengths, lines) in zip(result, lines.windows(ofCount: 2).map(\.tuple)) {

let left = (lines.0.left, lines.1.left)
let right = (lines.0.right, lines.1.right)

for length in lengths {
let left = lerp(from: left.0, to: left.1, by: length)
let right = lerp(from: right.0, to: right.1, by: length)
context.stroke(Path(line: (left, right)), with: .color(.purple))


}
}
}

}
}
}

struct StairView: View {
var body: some View {
Canvas { context, size in
Canvas { context, _ in
let rect = CGRect(x: 10, y: 10, width: 500, height: 100)
context.stroke(Path(rect), with: .color(.red.opacity(0.5)))
let points = [rect.minXMinY, rect.maxXMinY, rect.maxXMaxY, rect.minXMaxY]
Expand All @@ -207,6 +195,6 @@ struct StairView: View {

struct EmptyShape: Shape {
func path(in rect: CGRect) -> Path {
return Path()
Path()
}
}
36 changes: 17 additions & 19 deletions Examples/VectorLaboratory/PathCanvas.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import SwiftUI
import CoreGraphicsSupport
import Algorithms
import CoreGraphicsSupport
import SwiftUI
import VectorSupport

struct PathCanvas: View {

@Binding
var points: [CGPoint]

@State
var selection: Set<Int> = []

let coordinateSpace = NamedCoordinateSpace.named("canvas")

var body: some View {
ZStack(alignment: .topLeading) {
Color.white
.gesture(SpatialTapGesture(coordinateSpace: coordinateSpace).onEnded({ value in
print(value.location)
points.append(value.location)
}))

let elements = points.windows(ofCount: 2).map(\.tuple).enumerated()
ForEach(Array(elements), id: \.offset) { offset, points in
let path = Path(lineSegment: points)
Expand All @@ -39,18 +38,17 @@ struct PathCanvas: View {
self.points.insert(newPoint, at: offset + 1)
}
Button("Remove") {
self.points.remove(at: offset+1)
self.points.remove(at: offset + 1)
self.points.remove(at: offset)
}
}

}
ForEach(Array(points.enumerated()), id: \.0) { offset, point in
Circle().position(point).frame(width: 8, height: 8)
//.foregroundStyle(selection.contains(offset) ? Color.accentColor : .black)
// .foregroundStyle(selection.contains(offset) ? Color.accentColor : .black)
.background {
if selection.contains(offset) {
RelativeTimelineView(schedule: .animation) { context, time in
RelativeTimelineView(schedule: .animation) { _, time in
Path.circle(center: point, radius: 10).fill(Color.accentColor)
.colorEffect(ShaderLibrary.my_color_effect(.float(time)))
}
Expand All @@ -63,7 +61,7 @@ struct PathCanvas: View {
}
.contextMenu {
Button("Remove") {
self.points.remove(at: offset)
points.remove(at: offset)
}
}
}
Expand All @@ -90,26 +88,26 @@ struct PathCanvas: View {
}
}
.fileImporter(isPresented: $isFileImporterPresented, allowedContentTypes: [.json], onCompletion: { result in
if case let .success(url) = result {
if case .success(let url) = result {
let data = try! Data(contentsOf: url)
self.points = try! JSONDecoder().decode([CGPoint].self, from: data)
points = try! JSONDecoder().decode([CGPoint].self, from: data)
}
})
.fileExporter(isPresented: $isFileExporterPresented, item: JSONCodingTransferable(element: points)) { _ in }
}

@State
var isFileImporterPresented = false
@State
var isFileExporterPresented = false

func dragGesture(offset: Int) -> some Gesture {
DragGesture(coordinateSpace: coordinateSpace).onChanged({ value in
var location = value.location
#if os(macOS)
if NSEvent.modifierFlags.contains(.shift) {
location = location.map { round($0 / 10) * 10 }
}
if NSEvent.modifierFlags.contains(.shift) {
location = location.map { round($0 / 10) * 10 }
}
#endif
points[offset] = location
})
Expand Down
Loading

0 comments on commit 46c557c

Please sign in to comment.