-
-
Save devahmedshendy/d0899e79e186606e507c07fe73c2312a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TrimmingExample.swift | |
// | |
// Created by Vasilis Akoinoglou on 13/5/24. | |
// | |
import SwiftUI | |
struct Line: Identifiable { | |
let id = UUID() | |
var segments: [Segment] | |
struct Segment: Identifiable { | |
let id = UUID() | |
var p1: CGPoint | |
var p2: CGPoint | |
var points: [CGPoint] { | |
[p1, p2] | |
} | |
} | |
} | |
struct TrimmingExample: View { | |
@State | |
private var lines: [Line] = [] | |
@State | |
private var placeholderSegment: Line.Segment? | |
var body: some View { | |
VStack { | |
ZStack { | |
if let placeholderSegment { | |
Path { path in | |
path.addLines(placeholderSegment.points) | |
} | |
.stroke(lineWidth: 2) | |
} | |
ForEach(lines.flatMap(\.segments)) { segment in | |
// Segments | |
Path { path in | |
path.addLines(segment.points) | |
} | |
.stroke(lineWidth: 4) | |
.onTapGesture { | |
for (index, line) in lines.enumerated() { | |
for (segmentIndex, _segment) in line.segments.enumerated() { | |
if _segment.id == segment.id { | |
lines[index].segments.remove(at: segmentIndex) | |
if lines[index].segments.isEmpty { | |
lines.remove(at: index) | |
} | |
break | |
} | |
} | |
} | |
} | |
// Vertices | |
Path { path in | |
for point in segment.points { | |
path.addEllipse(in: CGRect(x: point.x - 3, y: point.y - 3, width: 6, height: 6)) | |
} | |
} | |
.fill(.blue) | |
} | |
} | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.background(Color.black) | |
.gesture(dragGesture) | |
Text(lines.count.description + ", " + lines.flatMap(\.segments).count.description) | |
} | |
} | |
private var dragGesture: some Gesture { | |
DragGesture() | |
.onChanged { value in | |
if placeholderSegment == nil { | |
placeholderSegment = Line.Segment(p1: value.location, p2: value.location) | |
} | |
placeholderSegment?.p2 = value.location | |
} | |
.onEnded { value in | |
if let placeholderSegment { | |
addSegment(placeholderSegment) | |
} | |
placeholderSegment = nil | |
} | |
} | |
private func addSegment(_ proposedSegment: Line.Segment) { | |
var proposedLine = Line(segments: [proposedSegment]) | |
// For every line | |
outerLoop: for (lineIndex, line) in lines.enumerated() { | |
// and every segment | |
for (segmentIndex, segment) in line.segments.enumerated() { | |
for (proposedSegmentIndex, proposedSegment) in proposedLine.segments.enumerated() { | |
if let intersection = segment.intersectionPointForSegment(proposedSegment) { | |
// Split both segments | |
let split1 = segment.splitAt(intersection) | |
let split2 = proposedSegment.splitAt(intersection) | |
// Remove original segments | |
lines[lineIndex].segments.remove(at: segmentIndex) | |
proposedLine.segments.remove(at: proposedSegmentIndex) | |
// Add new segments | |
lines[lineIndex].segments.append(contentsOf: split1) | |
proposedLine.segments.append(contentsOf: split2) | |
continue outerLoop | |
} | |
} | |
} | |
} | |
lines.append(proposedLine) | |
} | |
} | |
// https://stackoverflow.com/questions/15690103/intersection-between-two-lines-in-coordinates | |
extension Line.Segment { | |
func intersectionPointForSegment(_ otherSegment: Line.Segment) -> CGPoint? { | |
let (p1, p2) = (self.p1, self.p2) | |
let (p3, p4) = (otherSegment.p1, otherSegment.p2) | |
let d: CGFloat = (p2.x - p1.x) * (p4.y - p3.y) - (p2.y - p1.y) * (p4.x - p3.x) | |
if (d == 0) { | |
return nil // parallel lines | |
} | |
let u: CGFloat = ((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d | |
let v: CGFloat = ((p3.x - p1.x) * (p2.y - p1.y) - (p3.y - p1.y) * (p2.x - p1.x)) / d | |
if (u < 0.0 || u > 1.0) { | |
return nil // intersection point not between p1 and p2 | |
} | |
if (v < 0.0 || v > 1.0) { | |
return nil // intersection point not between p3 and p4 | |
} | |
var intersection: CGPoint = .zero | |
intersection.x = p1.x + u * (p2.x - p1.x) | |
intersection.y = p1.y + u * (p2.y - p1.y) | |
return intersection | |
} | |
func splitAt(_ point: CGPoint) -> [Line.Segment] { | |
// Split a segment into two | |
let (p1, p2) = (self.p1, point) | |
let (p3, p4) = (point, self.p2) | |
return [Line.Segment(p1: p1, p2: p2), Line.Segment(p1: p3, p2: p4)] | |
} | |
} | |
#Preview { | |
TrimmingExample() | |
.frame(width: 800, height: 600) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment