回転式メニューと逆三角関数(asin/arcsin)

最近、プライベートのプロジェクトでSpriteKitを使ったゲームの実装をしているんですが、 回転式メニューを独自実装した(せざるを得なかった)のでメモ。

実装イメージ

実装したものはこちらになります。
楕円形内でパンジェスチャーを行うと、指先の動きに追随して、楕円上のアイテムが回転します。 f:id:nerd0geek1:20160213023817g:plain

実装内容

実装の内容としては、

  • SKNodeを載せるSKScene
  • パンジェスチャーを受け取る楕円形のSKShapeNode
  • 楕円上に配置するSKSpriteNode

を作成・配置し、

  • SKShapeNodeでパンジェスチャーを検知
  • SKShapeNode内で移動に対応する楕円の中心角を計算
  • Closure経由でSKShapeNodeからSKSceneに角度を伝達

といったところです。

角度計算

パンジェスチャーの移動に対応する楕円の中心角の計算についてですが、 点A{ \displaystyle
(x_a, y_a)
}と点B{ \displaystyle
(x_b, y_b)
}の2点間の距離は{ \displaystyle
\sqrt{(x_b - x_a) ^2 + (y_b - y_a) ^2}
}により求めることができるので、
パンジェスチャーの始点をA、
楕円の中心点をB、
パンジェスチャーの終点をCとすると、
それぞれの対辺a、b、cはそれぞれの座標がCGPointで分かっていることから求めることができます。 そして、ヘロンの公式より、
{ \displaystyle
S = \sqrt{s(s-a)(s-b)(s-c)}, s = \frac{a + b + c}{ 2 }
}であるので、
三角形の高さをhとすると、
a ≧ b + cの場合、
{ \displaystyle
h = \frac{2S}{ a }, B = sin^{-1} \frac{h}{c}
}
として、三角形ABCの頂点Bの内角を求めることができます。
当初、角度を割り出すために最後に逆三角関数を利用している点が腑に落ちなかったのですが、 三角関数は角度から線分の比を知るための関数、 対する逆三角関数は線分の比から角度を知るための関数、と考えると腑に落ちました。

実装

上記の内容を実装に落としこむと以下のようになります。

private func angle(center: CGPoint, startPoint: CGPoint, movedPoint: CGPoint) -> Double {
        //distanceは2点間の距離を計算するためのメソッド
        let movedDistance: CGFloat        = distance(startPoint, otherPoint: movedPoint)
        let startPointFromCenter: CGFloat = distance(startPoint, otherPoint: center)
        let movedPointFromCenter: CGFloat = distance(movedPoint, otherPoint: center)
        //heron's formula
        let s: CGFloat                    = (movedDistance + startPointFromCenter + movedPointFromCenter) / 2
        //areaはヘロンの公式におけるS
        let area: CGFloat = pow(s * (s - movedDistance) * (s - startPointFromCenter) * (s - movedPointFromCenter), 0.5)
        let height: CGFloat
        let angle: CGFloat

        if (movedDistance >= startPointFromCenter + movedPointFromCenter) {
            height = 2 * area / movedDistance
            //asinはsinの逆関数
            angle = (1 - asin(height / startPointFromCenter) - asin(height / movedPointFromCenter))
        } else if (startPointFromCenter >= movedDistance + movedPointFromCenter) {
            height = 2 * area / startPointFromCenter
            angle = asin(height / movedPointFromCenter)
        } else {
            height = 2 * area / movedPointFromCenter
            angle = asin(height / startPointFromCenter)
        }

        return Double(angle)
    }

まとめ

フレームワークやライブラリが充実してきている昨今では、 実際に三角関数や逆三角関数を利用しての描画処理などを行うことはあまりないと思いますが、 理解しておくことで簡単に思い通りの描画処理を実装できるので、復習してみるのもありかと思います。

参考