Last active
September 19, 2020 02:56
-
-
Save takoikatakotako/1d0234fecb2a0934b1955a4ded308287 to your computer and use it in GitHub Desktop.
iOSDC-2020 ベジェ曲線
This file contains 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
import PlaygroundSupport | |
import XCPlayground | |
import UIKit | |
struct Point { | |
let x: Double | |
let y: Double | |
var cgPoint: CGPoint { | |
CGPoint(x: Double(self.x), y: Double(self.y)) | |
} | |
func scaled(_ scaled: Double) -> Point { | |
return Point(x: x * scaled, y: y * scaled) | |
} | |
} | |
struct Stroke { | |
var points: [Point] = [] | |
var cgPoints: [CGPoint] { | |
return points.map {$0.cgPoint} | |
} | |
} | |
var strokes: [Stroke] = [] | |
// か | |
let scale = 3.0 | |
var stroke0 = Stroke() | |
stroke0.points.append(Point(x: 21.5 , y: 106.5)) | |
stroke0.points.append(Point(x: 63.5, y: 85.0)) | |
stroke0.points.append(Point(x: 93.0, y: 81.0)) | |
stroke0.points.append(Point(x: 148.5, y: 98.0)) | |
stroke0.points.append(Point(x: 159.5, y: 117.5)) | |
stroke0.points.append(Point(x: 160.0, y: 135.5)) | |
stroke0.points.append(Point(x: 148.5, y: 167.5)) | |
stroke0.points.append(Point(x: 120.0, y: 196.5)) | |
stroke0.points.append(Point(x: 110.0, y: 189.5)) | |
stroke0.points.append(Point(x: 102.5, y: 174.0)) | |
var stroke1 = Stroke() | |
stroke1.points.append(Point(x: 107.0, y: 45.5)) | |
stroke1.points.append(Point(x: 104.5, y: 80.5)) | |
stroke1.points.append(Point(x: 92.0, y: 118.5)) | |
stroke1.points.append(Point(x: 58.0, y: 166.0)) | |
var stroke2 = Stroke() | |
stroke2.points.append(Point(x: 196.0, y: 77.0)) | |
stroke2.points.append(Point(x: 202.5, y: 88.5)) | |
stroke2.points.append(Point(x: 208.5, y: 98.0)) | |
strokes.append(stroke0) | |
strokes.append(stroke1) | |
strokes.append(stroke2) | |
class CurveAlgorithm { | |
struct Segment: CustomStringConvertible { | |
var p0: CGPoint = .zero | |
var p1: CGPoint = .zero | |
var p2: CGPoint = .zero | |
var p3: CGPoint = .zero | |
var description: String { | |
"p0: \(p0), p1: \(p1), P2: \(p2), P3: \(p3)" | |
} | |
} | |
static func getSegments(points: [CGPoint]) -> [Segment] { | |
// points の要素数 - 1 がセグメントの個数 | |
let count = points.count - 1 | |
var segments: [Segment] = [Segment](repeating: Segment(), count: count) | |
// Thomasのアルゴリズムを使ってP1を計算する | |
// a, b, c, d, e, f を求める | |
var a: [CGFloat] = [] | |
var b: [CGFloat] = [] | |
var c: [CGFloat] = [] | |
var dX: [CGFloat] = [] | |
var dY: [CGFloat] = [] | |
var e: [CGFloat] = [] | |
var fX: [CGFloat] = [] | |
var fY: [CGFloat] = [] | |
for i in 0..<count { | |
if i == 0 { | |
segments[0].p0 = points[0] | |
segments[0].p3 = points[1] | |
a.append(0) | |
b.append(2) | |
c.append(1) | |
dX.append(points[0].x + 2 * points[1].x) | |
dY.append(points[0].y + 2 * points[1].y) | |
e.append(c[0] / b[0]) | |
fX.append(dX[0] / b[0]) | |
fY.append(dY[0] / b[0]) | |
} else if i == count - 1 { | |
segments[count - 1].p0 = points[count - 1] | |
segments[count - 1].p3 = points[count] | |
a.append(2) | |
b.append(7) | |
c.append(0) | |
dX.append(8 * points[count - 1].x + points[count].x) | |
dY.append(8 * points[count - 1].y + points[count].y) | |
} else { | |
segments[i].p0 = points[i] | |
segments[i].p3 = points[i + 1] | |
a.append(1) | |
b.append(4) | |
c.append(1) | |
dX.append(4 * points[i].x + 2 * points[i + 1].x) | |
dY.append(4 * points[i].y + 2 * points[i + 1].y) | |
e.append(c[i] / (b[i] - a[i] * e[i - 1])) | |
fX.append((dX[i] - a[i] * fX[i - 1]) / (b[i] - a[i] * e[i - 1])) | |
fY.append((dY[i] - a[i] * fY[i - 1]) / (b[i] - a[i] * e[i - 1])) | |
} | |
} | |
// 最後のP1を求める | |
let x = (dX[count - 1] - a[count - 1] * fX[count - 2]) / (b[count - 1] - a[count - 1] * e[count - 2]) | |
let y = (dY[count - 1] - a[count - 1] * fY[count - 2]) / (b[count - 1] - a[count - 1] * e[count - 2]) | |
segments[count - 1].p1 = CGPoint(x: x, y: y) | |
// 残りのP1を全て計算する | |
for i in (1..<count).reversed() { | |
let x = fX[i - 1] - e[i - 1] * segments[i].p1.x | |
let y = fY[i - 1] - e[i - 1] * segments[i].p1.y | |
segments[i - 1].p1 = CGPoint(x: x, y: y) | |
} | |
// P2を求める | |
for i in (0..<count) { | |
if i == count - 1 { | |
let x = 0.5 * (points[count].x + segments[i].p1.x) | |
let y = 0.5 * (points[count].y + segments[i].p1.y) | |
segments[i].p2 = CGPoint(x: x, y: y) | |
} else { | |
let x = 2 * points[i + 1].x - segments[i + 1].p1.x | |
let y = 2 * points[i + 1].y - segments[i + 1].p1.y | |
segments[i].p2 = CGPoint(x: x, y: y) | |
} | |
} | |
return segments | |
} | |
} | |
let view = UIView() | |
view.frame = CGRect(x: 0, y: 0, width: 600, height: 600) | |
view.backgroundColor = .black | |
for stroke in strokes { | |
let linePath = UIBezierPath() | |
let segments = CurveAlgorithm.getSegments(points: stroke.points.map{$0.scaled(2.5).cgPoint}) | |
for segment in segments { | |
linePath.move(to: segment.p0) | |
print("current: \(linePath.currentPoint), to: \(segment.p3), control1: \(segment.p1), control2: \(segment.p2), ") | |
linePath.addCurve(to: segment.p3, controlPoint1: segment.p1, controlPoint2: segment.p2) | |
} | |
let shapeLayer = CAShapeLayer() | |
shapeLayer.path = linePath.cgPath | |
shapeLayer.fillColor = UIColor.clear.cgColor | |
shapeLayer.lineWidth = 16 | |
shapeLayer.strokeColor = UIColor.white.cgColor | |
view.layer.addSublayer(shapeLayer) | |
} | |
PlaygroundPage.current.liveView = view |
Author
takoikatakotako
commented
Sep 19, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment