Files
Memola/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift
2024-05-31 21:01:08 +07:00

120 lines
4.1 KiB
Swift

//
// SolidPointStrokeGenerator.swift
// Memola
//
// Created by Dscyre Scotti on 5/4/24.
//
import Foundation
struct SolidPointStrokeGenerator: StrokeGenerator {
var configuration: Configuration
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: any Stroke) {
guard stroke.keyPoints.endIndex > 0 else {
return
}
let point = stroke.movingAverage.addPoint(point)
stroke.keyPoints.append(point)
switch stroke.keyPoints.endIndex {
case 2:
let start = stroke.keyPoints[0]
let end = stroke.keyPoints[1]
let control = CGPoint.middle(p1: start, p2: end)
addCurve(from: start, to: end, by: control, on: stroke)
case 3:
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])
var control = CGPoint.middle(p1: start, p2: end)
addCurve(from: start, to: end, by: control, on: stroke)
start = CGPoint.middle(p1: stroke.keyPoints[index - 2], p2: stroke.keyPoints[index - 1])
control = stroke.keyPoints[index - 1]
end = CGPoint.middle(p1: stroke.keyPoints[index - 1], p2: stroke.keyPoints[index])
addCurve(from: start, to: end, by: control, on: stroke)
default:
adjustKeyPoint(on: stroke)
let index = stroke.keyPoints.endIndex - 1
let start = CGPoint.middle(p1: stroke.keyPoints[index - 2], p2: stroke.keyPoints[index - 1])
let control = stroke.keyPoints[index - 1]
let end = CGPoint.middle(p1: stroke.keyPoints[index - 1], p2: stroke.keyPoints[index])
addCurve(from: start, to: end, by: control, on: stroke)
}
}
func finish(at point: CGPoint, on stroke: any Stroke) {
switch stroke.keyPoints.endIndex {
case 0...1:
break
default:
append(to: point, on: 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]
let averageX = (prev.x + current.x) / 2
let averageY = (prev.y + current.y) / 2
let point = CGPoint(x: averageX, y: averageY)
stroke.keyPoints[index] = point
}
private func addPoint(_ point: CGPoint, on stroke: any Stroke) {
let rotation: CGFloat
switch configuration.rotation {
case .fixed:
rotation = 0
case .random:
rotation = CGFloat.random(in: 0...360) * .pi / 180
}
stroke.addQuad(at: point, rotation: rotation, shape: .rounded)
}
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 {
case .automatic:
factor = min(5, 1 / (stroke.thickness * 1 / 50))
case .fixed:
factor = 1 / (stroke.thickness * stroke.penStyle.stepRate)
case .none:
factor = 1 / (stroke.thickness * 10 / 500)
}
let segments = max(Int(distance * factor), 1)
for i in 0..<segments {
let t = CGFloat(i) / CGFloat(segments)
let x = pow(1 - t, 2) * start.x + 2.0 * (1 - t) * t * control.x + t * t * end.x
let y = pow(1 - t, 2) * start.y + 2.0 * (1 - t) * t * control.y + t * t * end.y
let point = CGPoint(x: x, y: y)
addPoint(point, on: stroke)
}
}
}
extension SolidPointStrokeGenerator {
struct Configuration {
var rotation: Rotation = .fixed
var granularity: Granularity = .automatic
}
enum Rotation {
case fixed
case random
}
enum Granularity {
case automatic
case fixed
case none
}
}