ARKitでオブジェクトの方向を求めたい


Swift4.0でARKitを使いたくて、簡単なARシューティングゲームを作ってみました。そのときに、的となるオブジェクトを見失った時にヒントととしてオブジェクトの方向を出すようにしたいと思って実装して見たところ、結構教育的て面白かったので記事にしてみます。
同じように方向を計算したいという場合は参考にしてください。

全体のコード

最初に全体のコードを先に載せてしまいます
https://github.com/kamata1729/AR_Buster

アプリの流れ

初めはRootViewControllerに入り、STARTボタンを押してARViewControllerに遷移し、ゲームが終了するとResultViewControllerに入る。

ARViewControllerでは、下の図のようにオブジェクトがランダムな位置に出現してランダムに動くので、それを制限時間内にタップして消していくというゲームです。

今回は、主にこれの右下の矢印の部分の実装を解説します。

三次元の方向を二次元に射影して表現する

やりたいことは、カメラ(iPhone)からオブジェクトまでの3次元空間での方向を、iPhoneの二次元の画面上に射影して表現することです。そのためにはまず、オブジェクトの位置が記述される座標系を、絶対座標系$E$から、カメラ(iPhone)とともに動く座標系$E'$に座標変換する必要があります。

座標変換した後は、座標系$E'$上でカメラとオブジェクトの角度$\theta$は、
$\theta = \arctan \dfrac{y'_2 - y'_1}{x'_2 - x'_1}$
として求めることが可能です。

この部分の実装方法は以下のようになっています。

if let node = self.sceneView.scene.rootNode.childNodes.first { 
    if let camera = self.sceneView.pointOfView {                     
        let invMat :SCNMatrix4 = SCNMatrix4Invert(camera.transform) 
        let x = node.position.x 
        let y = node.position.y 
        let z = node.position.z 
        let transPosition :SCNVector3 = SCNVector3(x: x*invMat.m11 + y*invMat.m21 + z*invMat.m31, y: x*invMat.m12 + y*invMat.m22 + z*invMat.m32, z: x*invMat.m13 + y*invMat.m23 + z*invMat.m33) 
        let angle = (-1) * atan2(transPosition.y - camera.position.y, transPosition.x - camera.position.x) 
        imageView.transform = CGAffineTransform(rotationAngle: CGFloat(angle)) 
    } 
} 

1行目:オブジェクトを取得
2行目:カメラ(iPhone)を取得
3行目:
camera.transformで、原点からカメラの位置への4×4の変換行列を取得する。
(4×4の変換行列について参考:https://qiita.com/kengo1008L/items/2d7319772e08f9465294)
これの逆行列をSCNMatrix4Invertで計算する。(変換行列は直交行列なので逆行列は存在)
4~6行目:オブジェクトの絶対座標系$E$での座標$(x,y,z)^T$を取得
7行目:
先ほど計算した逆行列のうち、回転角に関係する3×3の成分を$(x,y,z)^T$にかけることで、カメラとともに動く座標系$E'$に変換
8行目:回転角を計算
9行目:矢印の画像をCGAffineTransformで回転

camera.transformを使って、カメラの座標系に座標変換することが肝です。方向を計算したいときは参考にしてみてください。