ARKit / SceneKit でフォーカスが外れたジオメトリの位置を知らせる UI をつくる
上の画像の様な感じ。
カメラから対象物となるジオメトリがフォーカスから外れるとジオメトリがある方向に、端末を回転する様に促す矢印の UI が出る。
今回はサクッとつくったため、あまり精度はないのでご了承いただきたい。
流れ
- overlayScene に左右矢印を非表示として配置した SpriteKit の SKScene を設定
- SceneKit のデリゲートでフォーカスが外れた時の処理と対象物の状態を調べる
- フォーカスが外れていたら左右どちらかの矢印を表示する
実装
今回は ARKit のテンプレートを使用する。
まず、ViewController クラスのグローバルに、対象物の SCNNode、overlayScene の SKScene と左右矢印となるスプライトを設定する。
private var target:SCNNode! private var overlayScene: SKScene! private var arrowL: SKSpriteNode! private var arrowR: SKSpriteNode!
対象物をシーンファイルかコードで設定。
let scene = SCNScene(named: "main.scn")! target = scene.rootNode.childNode(withName: "sphere", recursively: true)!
SKScene を初期化、左右矢印となるスプライトを設定し alpha を 0 に。
overlayScene に SKScene を設定する。
overlayScene = SKScene() overlayScene.size = UIScreen.main.bounds.size overlayScene.anchorPoint = CGPoint(x: 0.5, y: 0.5) arrowL = SKSpriteNode(imageNamed: "arrow_l.png") arrowL.anchorPoint = CGPoint(x: 0.0, y: 0.5) arrowL.position = CGPoint(x: -overlayScene.size.width / 2, y: 0.0) arrowL.alpha = 0.0 overlayScene.addChild(arrowL) arrowR = SKSpriteNode(imageNamed: "arrow_r.png") arrowR.anchorPoint = CGPoint(x: 1.0, y: 0.5) arrowR.position = CGPoint(x: overlayScene.size.width / 2, y: 0.0) arrowR.alpha = 0.0 overlayScene.addChild(arrowR) sceneView.overlaySKScene = overlayScene
SceneKit のデリゲートを設定。
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { ... }
デリゲートの中で convertTransform を使用してカメラノードから見た対象物をローカル座標を取得するコードを書く。
これで対象物の X 軸でカメラよりマイナスかプラスかわかるので矢印の処理をする。
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { let cameraNode:SCNNode! = sceneView.pointOfView let transform = cameraNode.simdConvertTransform(target.simdTransform, from: nil) ... }
そして、その下に矢印の処理を書いていく。
対象物の X 軸のマイナス、プラスのみで処理すると対象物が正面や180度回転方向でも矢印が表示されてしまう。 それを回避するため SCNView のメソッド isNode(target, insideFrustumOf:) でカメラの視体積にある場合は両矢印を表示しないようにする。
if !sceneView.isNode(target, insideFrustumOf: cameraNode) { if transform[3][0] > 0 { arrowL.alpha = 0 arrowR.alpha = 1 }else{ arrowL.alpha = 1 arrowR.alpha = 0 } }else{ arrowL.alpha = 0 arrowR.alpha = 0 }
サンプルコードではランドスケープ画面になった時の処理を入れているが、 ひとまずこれをビルドするとコードが動く。
サンプル
ランドスケープ時の左右 Safe Area を考慮し忘れたので適当に直して使って欲しい。
まとめ
雑ではあるが簡単に矢印を表示する UI が表示できた。
一応、対象物の Y, Z 軸方向も調べることができるので試してみても良いかも。
ちゃんとしたものを作成したい場合は対象物の大きさ(バウンディングボックス)から範囲を調べ、
画面に対象物が映らない範囲のちょっと内側を調べて設定たり、位置や回転を正しい状態を設定してた方がよいかと思われる。