Apple Engine

Apple, iPhone, iOS, その周辺のことについて

ARKit / SceneKit でフォーカスが外れたジオメトリの位置を知らせる UI をつくる

f:id:x67x6fx74x6f:20190315190318p:plain

 

f:id:x67x6fx74x6f:20190315190432g:plain

 

上の画像の様な感じ。

カメラから対象物となるジオメトリがフォーカスから外れるとジオメトリがある方向に、端末を回転する様に促す矢印の 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
}

 

サンプルコードではランドスケープ画面になった時の処理を入れているが、 ひとまずこれをビルドするとコードが動く。

 

サンプル

github.com

ランドスケープ時の左右 Safe Area を考慮し忘れたので適当に直して使って欲しい。

 

まとめ

雑ではあるが簡単に矢印を表示する UI が表示できた。
一応、対象物の Y, Z 軸方向も調べることができるので試してみても良いかも。

ちゃんとしたものを作成したい場合は対象物の大きさ(バウンディングボックス)から範囲を調べ、
画面に対象物が映らない範囲のちょっと内側を調べて設定たり、位置や回転を正しい状態を設定してた方がよいかと思われる。