ARKitを扱う際の心構えとTips

# ARKitを扱う際の心構えとTips
ARKitでアプリ開発を行う際に意識した方が良いことを中心にまとめてみました。:airplane:

## 更新
2019/01/10更新
こちらも参考にしてください
[ネイティブでiOSのARアプリを作るためのTIPS](https://qiita.com/kaoryuuu/items/f81b12c6b827701f4c48)

## ARとSceneKit・SpriteKitとの関わり合い
ARでオブジェクトを扱う際、
・ 3Dの場合 → ARSCNView (AR + SceneKit)
・ 2Dの場合 → ARSKView (AR + SpriteKit)
のようなイメージになります。

カメラ機能と同様にsessionにより管理されており、sessionが切れたりすると平面を新たに認識したり、現実世界の座標を認識することができなくなります。

３Dを扱う場合、ARKit自体そこまで難しいことはしておらず、認識した座標を元にSceneKitでノードを追加したりするようなイメージになります。

## SCNVector3を理解する
ARKitを扱う際に現実世界にポジションを指定して配置することがよくありますが、
SCNVecor3と呼ばれるものをメインに使って行きます。
SceneKitを扱ったことのある方はおなじみではあるかと思いますが、
ARを扱う際にもこのSCNVecor3を利用していきます。

![スクリーンショット 2017-08-31 17.32.41.png](https://qiita-image-store.s3.amazonaws.com/0/186277/f2b869d0-ef4f-4903-a515-6dd3240ed33d.png)

## SCNVector3同士の比較

```
extension SCNVector3: Equatable {
    public static func ==(to: SCNVector3, from: SCNVector3) -> Bool {
        return (to.x == from.x) && (to.y == from.y) && (to.z == from.z)
    }
}
```


## タップした箇所の現実世界の座標を検出する
hitTestを用いて検出を行います

ARSceneViewの画面上に表示されている位置(ex: タップした箇所)が実際に現実世界でどの座標に当たるのか判定をします。

こちらの記事も参考にしてみてください。
[ARKitでタップした座標を検出する方法](http://qiita.com/kaoryuuu/items/0de97d618b057430aeba)

```swift

 /* hitTest
  *  
  * x: 現実世界の横の軸になる
  * y: 現実世界の縦(高さ)の軸になる
  * z: 現実世界の奥行きの軸になる
  */
  let vector3 = SCNVector3.(hitResult.worldTransform.columns.3.x,
                            hitResult.worldTransform.columns.3.y,
                            hitResult.worldTransform.columns.3.z)

extension ARSCNView {

    /// 現実世界の座標に変換
    ///
    /// - Parameter screenPosition: CGPoint
    /// - Returns: SCNVector3
    func realWorldVector(screenPosition: CGPoint) -> SCNVector3? {
        let results = self.hitTest(screenPosition, types: [.existingPlane])
        guard let result = results.first else {
            return nil
        }

        // SCNVector3
        return SCNVector3.positionFromTransform(result.worldTransform)
    }
}
```

results.firstとしてありますが、これは、認識した平面の数だけ結果が返ってくるようになります。
例えば、床と机のそれぞれ２箇所の平面を認識した場合は、認識した順番で結果が入ります。画面上で机の位置をタップした場合、机の上のタップした座標と、机を突き抜けた先にある床の平面の座標の２つが結果として得られます。

## オブジェクトを配置する

```swift
let box = SCNBox(width: 0.25, height: 0.25, length: 0.25, chamferRadius: 0)
let node = SCNNode(geometry: box)
arscnView.scene.rootNode.addChildNode(node)
```

## オブジェクトをカメラの方向に向くようにする
SCNTextを作成してラベルをカメラの方向に向かせたい場合

```swift
// 特定のNodeに対して向かせることも可能
let constraint = SCNLookAtConstraint(target: arscnView.pointOfView)     
constraint.isGimbalLockEnabled = true
textNode.constraints = [constraint]        
```

## オブジェクトを削除する

```swift
textNode.removeFromParentNode()
```

## 現実世界でのスケール
先ほど箱を生成した大きさをみてみましょう

```swift
let box = SCNBox(width: 0.25, height: 0.25, length: 0.25, chamferRadius: 0)
let node = SCNNode(geometry: box)
arscnView.scene.rootNode.addChildNode(node)
```

この箱は実際にAR上で確認すると、
縦横幅25cmの箱が生成されます。

1 = 1m　換算ですので注意してください。

いつものアプリ開発のようにviewで生成してるようなサイズにすると、画面に到底収まらない巨大なオブジェクトが生成されてしまいます。

## ARSCNViewDelegate

### renderer
#### 新しくアンカーが追加された時
これが呼ばれた際、
アンカーが追加された = 平面がどこかしら認識できた
と言えるので、ユーザーに対して認識ができたと伝えるタイミングなどに用いると良いでしょう。

```swift
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

}
```

#### オブジェクトの更新
オブジェクト（または他のオブジェクト）が一時停止していない限り、このメソッドをフレームごとに呼び出す

```swift
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

}
```

#### シーンをレンダリングした際に呼ばれる

```swift
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: 
    SCNScene, atTime time: TimeInterval) {

}
```

### ARSession Observer
#### セッションが失敗した時

```swift
func session(_ session: ARSession, didFailWithError error: Error) {
    // Present an error message to the user
    print(error)
}
```

#### セッションが中断した時

```swift
func sessionWasInterrupted(_ session: ARSession) {
    // Inform the user that the session has been interrupted, for example, by presenting an overlay
        
}
```

#### セッションの中断が再開し、再度デバイスの位置を追跡した時

```swift
func sessionInterruptionEnded(_ session: ARSession) {
    // Reset tracking and/or remove existing anchors if consistent tracking is required

}
```