2020.8.14
Spirograph
描画で実際に街に行くもので仕上げるために、SwiftUIで簡単なスピログラフを作成する方法を説明します。「スピログラフ」は、鉛筆を円の中に入れ、別の円の円周に沿って回転させ、カジノゲームのようなルーレットと呼ばれるさまざまな幾何学模様を作成するおもちゃの商標名です。
このコードには、非常に具体的な方程式が含まれています。説明しますが、興味がない場合はこの章をスキップしてもかまいません。これは単に面白く、ここでは新しいSwiftやSwiftUIについては説明していません。
アルゴリズムには4つの入力があります。
内側の円の半径。
外側の円の半径。
外側の円の中心からの仮想ペンの距離。
どれだけのルーレットを引くか。これはオプションですが、アルゴリズムが機能するときに何が起こっているかを示すのに本当に役立つと思います。
最大公約数(GCD)
ユークリッドのアルゴリズムを使用して行われます。これは、少し単純化した形式で次のようになります。
struct Spirograph: Shape {
let innerRadius: Int
let outerRadius: Int
let distance: Int
let amount: CGFloat
func gcd(_ a: Int, _ b: Int) -> Int {
var a = a
var b = b
while b != 0 {
let temp = b
b = a % b
a = temp
}
return a
}
func path(in rect: CGRect) -> Path {
let divisor = gcd(innerRadius, outerRadius)
let outerRadius = CGFloat(self.outerRadius)
let innerRadius = CGFloat(self.innerRadius)
let distance = CGFloat(self.distance)
let difference = innerRadius - outerRadius
let endPoint = ceil(2 * CGFloat.pi * outerRadius / CGFloat(divisor)) * amount
// more code to come
var path = Path()
for theta in stride(from: 0, through: endPoint, by: 0.01) {
var x = difference * cos(theta) + distance * cos(difference / outerRadius * theta)
var y = difference * sin(theta) - distance * sin(difference / outerRadius * theta)
x += rect.width / 2
y += rect.height / 2
if theta == 0 {
path.move(to: CGPoint(x: x, y: y))
} else {
path.addLine(to: CGPoint(x: x, y: y))
}
}
return path
}
}
struct ContentView: View {
@State private var innerRadius = 125.0
@State private var outerRadius = 75.0
@State private var distance = 25.0
@State private var amount: CGFloat = 1.0
@State private var hue = 0.6
var body: some View {
VStack(spacing: 0) {
Spacer()
Spirograph(innerRadius: Int(innerRadius), outerRadius: Int(outerRadius), distance: Int(distance), amount: amount)
.stroke(Color(hue: hue, saturation: 1, brightness: 1), lineWidth: 1)
.frame(width: 300, height: 300)
Spacer()
Group {
Text("Inner radius: \(Int(innerRadius))")
Slider(value: $innerRadius, in: 10...150, step: 1)
.padding([.horizontal, .bottom])
Text("Outer radius: \(Int(outerRadius))")
Slider(value: $outerRadius, in: 10...150, step: 1)
.padding([.horizontal, .bottom])
Text("Distance: \(Int(distance))")
Slider(value: $distance, in: 1...150, step: 1)
.padding([.horizontal, .bottom])
Text("Amount: \(amount, specifier: "%.2f")")
Slider(value: $amount)
.padding([.horizontal, .bottom])
Text("Color")
Slider(value: $hue)
.padding(.horizontal)
}
}
}
}
