diff --git a/Memola/Canvas/Geometries/Stroke/Core/Stroke.swift b/Memola/Canvas/Geometries/Stroke/Core/Stroke.swift index 5502e1c..077c764 100644 --- a/Memola/Canvas/Geometries/Stroke/Core/Stroke.swift +++ b/Memola/Canvas/Geometries/Stroke/Core/Stroke.swift @@ -8,7 +8,8 @@ import MetalKit import Foundation -protocol Stroke: AnyObject, Drawable { +protocol Stroke: AnyObject, Drawable, Hashable, Equatable { + var id: UUID { get set } var bounds: [CGFloat] { get set } var color: [CGFloat] { get set } var style: StrokeStyle { get set } @@ -30,7 +31,6 @@ protocol Stroke: AnyObject, Drawable { func append(to point: CGPoint) func finish(at point: CGPoint) - func loadQuads() func addQuad(at point: CGPoint, rotation: CGFloat, shape: QuadShape) func removeQuads(from index: Int) func saveQuads(to index: Int) @@ -50,7 +50,9 @@ extension Stroke { func isVisible(in bounds: CGRect) -> Bool { bounds.contains(strokeBounds) || bounds.intersects(strokeBounds) } +} +extension Stroke { func begin(at point: CGPoint) { penStyle.generator.begin(at: point, on: self) } @@ -63,7 +65,9 @@ extension Stroke { penStyle.generator.finish(at: point, on: self) keyPoints.removeAll() } +} +extension Stroke { func addQuad(at point: CGPoint, rotation: CGFloat, shape: QuadShape) { let quad = Quad( origin: point, @@ -83,7 +87,7 @@ extension Stroke { extension Stroke { func prepare(device: MTLDevice) { - guard texture != nil else { return } + guard texture == nil else { return } texture = penStyle.loadTexture(on: device) } @@ -103,3 +107,13 @@ extension Stroke { self.indexBuffer = nil } } + +extension Stroke { + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/Memola/Canvas/Geometries/Stroke/Core/StrokeGenerator.swift b/Memola/Canvas/Geometries/Stroke/Core/StrokeGenerator.swift index 5c540f8..f1c5e38 100644 --- a/Memola/Canvas/Geometries/Stroke/Core/StrokeGenerator.swift +++ b/Memola/Canvas/Geometries/Stroke/Core/StrokeGenerator.swift @@ -12,7 +12,7 @@ protocol StrokeGenerator { var configuration: Configuration { get set } - func begin(at point: CGPoint, on stroke: Stroke) - func append(to point: CGPoint, on stroke: Stroke) - func finish(at point: CGPoint, on stroke: Stroke) + func begin(at point: CGPoint, on stroke: any Stroke) + func append(to point: CGPoint, on stroke: any Stroke) + func finish(at point: CGPoint, on stroke: any Stroke) } diff --git a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift index 9f47bcf..bddd937 100644 --- a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift +++ b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift @@ -10,13 +10,13 @@ import Foundation struct SolidPointStrokeGenerator: StrokeGenerator { var configuration: Configuration - func begin(at point: CGPoint, on stroke: Stroke) { + func begin(at point: CGPoint, on stroke: any Stroke) { let point = stroke.movingAverage.addPoint(point) stroke.keyPoints.append(point) addPoint(point, on: stroke) } - func append(to point: CGPoint, on stroke: Stroke) { + func append(to point: CGPoint, on stroke: any Stroke) { guard stroke.keyPoints.endIndex > 0 else { return } @@ -29,7 +29,9 @@ struct SolidPointStrokeGenerator: StrokeGenerator { let control = CGPoint.middle(p1: start, p2: end) addCurve(from: start, to: end, by: control, on: stroke) case 3: - stroke.removeQuads(from: stroke.quadIndex + 1) + let quadIndex = stroke.quadIndex + 1 + stroke.removeQuads(from: quadIndex) + stroke.saveQuads(to: quadIndex) let index = stroke.keyPoints.endIndex - 1 var start = stroke.keyPoints[index - 2] var end = CGPoint.middle(p1: stroke.keyPoints[index - 2], p2: stroke.keyPoints[index - 1]) @@ -49,7 +51,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { } } - func finish(at point: CGPoint, on stroke: Stroke) { + func finish(at point: CGPoint, on stroke: any Stroke) { switch stroke.keyPoints.endIndex { case 0...1: break @@ -58,8 +60,10 @@ struct SolidPointStrokeGenerator: StrokeGenerator { } } - private func smoothOutPath(on stroke: Stroke) { - stroke.removeQuads(from: stroke.quadIndex + 1) + private func smoothOutPath(on stroke: any Stroke) { + let quadIndex = stroke.quadIndex + 1 + stroke.removeQuads(from: quadIndex) + stroke.saveQuads(to: quadIndex) adjustKeyPoint(on: stroke) switch stroke.keyPoints.endIndex { case 4: @@ -79,7 +83,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { stroke.quadIndex = stroke.quads.endIndex - 1 } - private func adjustKeyPoint(on stroke: Stroke) { + private func adjustKeyPoint(on stroke: any Stroke) { let index = stroke.keyPoints.endIndex - 1 let prev = stroke.keyPoints[index - 1] let current = stroke.keyPoints[index] @@ -89,7 +93,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { stroke.keyPoints[index] = point } - private func addPoint(_ point: CGPoint, on stroke: Stroke) { + private func addPoint(_ point: CGPoint, on stroke: any Stroke) { let rotation: CGFloat switch configuration.rotation { case .fixed: @@ -100,7 +104,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { stroke.addQuad(at: point, rotation: rotation, shape: .rounded) } - private func addCurve(from start: CGPoint, to end: CGPoint, by control: CGPoint, on stroke: Stroke) { + private func addCurve(from start: CGPoint, to end: CGPoint, by control: CGPoint, on stroke: any Stroke) { let distance = start.distance(to: end) let factor: CGFloat switch configuration.granularity { diff --git a/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift b/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift index 40a5e57..d36e55d 100644 --- a/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift +++ b/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift @@ -9,5 +9,42 @@ import MetalKit import Foundation final class EraserStroke: Stroke, @unchecked Sendable { - + var id: UUID = UUID() + var bounds: [CGFloat] + var color: [CGFloat] + var style: StrokeStyle + var createdAt: Date + var thickness: CGFloat + var quads: [Quad] + var penStyle: any PenStyle + + var batchIndex: Int = 0 + var quadIndex: Int = -1 + var keyPoints: [CGPoint] = [] + var movingAverage: MovingAverage = MovingAverage(windowSize: 3) + + var texture: (any MTLTexture)? + var indexBuffer: (any MTLBuffer)? + var vertexBuffer: (any MTLBuffer)? + + init( + bounds: [CGFloat], + color: [CGFloat], + style: StrokeStyle, + createdAt: Date, + thickness: CGFloat, + quads: [Quad] = [] + ) { + self.bounds = bounds + self.color = color + self.style = style + self.createdAt = createdAt + self.thickness = thickness + self.quads = quads + self.penStyle = style.penStyle + } + + func saveQuads(to index: Int) { + + } } diff --git a/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift b/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift index 43f8573..5c8153b 100644 --- a/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift +++ b/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift @@ -10,6 +10,7 @@ import CoreData import Foundation final class PenStroke: Stroke, @unchecked Sendable { + var id: UUID = UUID() var bounds: [CGFloat] var color: [CGFloat] var style: StrokeStyle @@ -70,12 +71,6 @@ final class PenStroke: Stroke, @unchecked Sendable { } } - func removeQuads(from index: Int) { - let dropCount = quads.endIndex - max(1, index) - quads.removeLast(dropCount) - saveQuads(to: index) - } - func saveQuads(to index: Int) { let quads = Array(quads[batchIndex..