ARKitのはじめかた その3「オブジェクトを配置する(ARKit2版)」


# はじめに
こちらの続きです▶︎ [ARKitのはじめかた その2「オブジェクトを配置する(ARKit1版)」](https://qiita.com/1901drama/items/abe65ff529a09ec2b798)

こんにちは！
[ARKitのまとめ記事](https://qiita.com/1901drama/items/cc9f598e3808fe7e32cb) にて書いた実装方法について
次はARKit2時代から使われるようになった「オブジェクト配置の方法」を書きます。
前の記事の方法より汎用性が高いのでなるべくこちらの方法を推奨します。

# ゴール
タッチした場所に飛行機が出てきます。
![iOS のファイル (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222377/4e7c5226-36b2-5f25-e78e-6d5e76f9fa92.gif)


# 前提
[ARKitのはじめかた その1「5分で出来るARアプリ」](https://qiita.com/1901drama/items/c0eb01b16370c48f84cd)で作成した環境をベースとします。
※依存関係は無いのでオブジェクトを置き換えれば他でも使えると思います。

# コード

```ViewController.swift
import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {
   @IBOutlet var sceneView: ARSCNView!

     override func viewDidLoad() {
       super.viewDidLoad()
       
       sceneView.delegate = self
       sceneView.scene = SCNScene()
   }
   
   override func viewWillAppear(_ animated: Bool) {
       super.viewWillAppear(animated)
       
       let configuration = ARWorldTrackingConfiguration()
       sceneView.session.run(configuration)
       
       let gesture = UITapGestureRecognizer(target: self, action:#selector(onTap))
       self.sceneView.addGestureRecognizer(gesture)
   }
  
    @objc func onTap(sender: UITapGestureRecognizer) {

        let pos = sender.location(in: sceneView)
        let results = sceneView.hitTest(pos, types: .featurePoint)
        if !results.isEmpty {
            let anchor = ARAnchor(name:"shipAnchor",
                                  transform:results.first!.worldTransform)
            sceneView.session.add(anchor: anchor)
        }
    }

    
   // MARK: - ARSCNViewDelegate

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        if anchor.name == "shipAnchor" {
            guard let scene = SCNScene(named: "ship.scn",inDirectory: "art.scnassets") else { return }
            let shipNode = (scene.rootNode.childNode(withName: "ship", recursively: false))!
               node.addChildNode(shipNode)
        }
   }
    
}
```

# 解説

**１.空のシーンを読み込んで、iPhoneの画面に反映させます。**※前回と一緒です

```swift
    override func viewDidLoad() {
        super.viewDidLoad()
        
        sceneView.delegate = self
        sceneView.scene = SCNScene()
	    //画面に、何もオブジェクトが無い空のシーン（映像空間）を適用させます。

        let gesture = UITapGestureRecognizer(target: self, action:#selector(onTap))
        self.sceneView.addGestureRecognizer(gesture)
        //タップジェスチャーを追加
    }
```

**２.現実とカメラ越しの映像を連携させます。（AR機能をONにする）**※前回と一緒です

```swift
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let configuration = ARWorldTrackingConfiguration()
	    //AR環境の設定ファイル作ります。これによりAR機能が使えるようになります。

        sceneView.session.run(configuration)
	    //画面の設定にARの設定を反映させます。
    }
```

**3.タッチした場所にARAnchorを配置します。**

```swift
    @objc func onTap(sender: UITapGestureRecognizer) {
    //画面にタッチした時に発動します。タッチした場所を、senderという変数に入れます。

        let pos = sender.location(in: sceneView)
        //一番始めにタッチしたsceneView上の場所をposとします。

        let results = sceneView.hitTest(pos, types: .featurePoint)
        //posの延長線上にある特徴点を手前から順番にresultsに入れます。

        if !results.isEmpty {
            let anchor = ARAnchor(name:"shipAnchor",transform:results.first!.worldTransform)
            //resultsがあれば、ARAnchorを作り、一番初め（手前）の特徴点の場所を代入します。
            sceneView.session.add(anchor: anchor)
           //ARAnchor（shipAnchorという名前）を配置します。
        }
    }
```

**4.ARAnchor上にオブジェクトを表示します。**

```swift
   // MARK: - ARSCNViewDelegate

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    //Anchorが追加される度に呼び出されるrendererを追加します。
        if anchor.name == "shipAnchor" {
            guard let scene = SCNScene(named: "ship.scn",inDirectory: "art.scnassets") else { return }
            let shipNode = (scene.rootNode.childNode(withName: "ship", recursively: false))!
            node.addChildNode(shipNode)
            //追加されたARAnchorの名前が"shipAnchor"であれば、ARAnchor上にshipNodeを配置します。
        }
   }
```
■rendererとは？
ARSCNView上でイベントが発生した際に自動的に処理を行う関数です。
今回のようにARAnchorが追加された場合だけでなく、フレーム単位での処理やsessionを管理するものなどがあります。
参考：[ARSCNViewDelegate](https://developer.apple.com/documentation/arkit/arscnviewdelegate) 配下のAPIです。

## なぜこの方法がふさわしいか
ARKit2では、"ARWorldMap"というARKitで取得した情報を他のデバイスと共有したり過去の情報を再現出来る方法が発表されました。
<img width="512" alt="20180605174208.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222377/03f01fbd-51eb-8882-2372-2bc7ed9c223d.png">

この際、ARAnchorは環境に紐づいた情報として記録し共有されるので、ある机の上にあったARAnchorは多少違う位置で復元しても同じ環境として認識されれば 同じ机の上に表示されます。
ARKit1時代の方法では、環境ではなくデバイスを中心とした位置関係で記録されているので、デバイスの1m前に置いたオブジェクトを記録して復元しても、復元した時の1m前に表示されていました。
また、他のデバイスと空間を共有するには、何か共通の基準となる物が必要となる為、自分で基準を作る（共有したい2つのiPhoneの場所を一度合わせる等）のでなければ、ARAnchorを利用する事が簡単かつ汎用性が高い手法となります。
参考：[ARKitでグラフィティアートをして、ARWorldMapで共有する](https://qiita.com/1901drama/items/2d176a7c992ea2cef0f2)

ただし、そこまで環境と合致する必要がないオブジェクト（すぐ消えるシューティングの弾や、個人で表示したり再度表示しないモノなど）は、従来通りのARKit1の方法の方が良いです。


#まとめ
ARKit2時代はこの方法がメインとなりました。
また、ImageTrackingやFaceTrackingも何か基準になるもの場所をARAnchorとして捉え、その上にオブジェクト表示する方法です。
ARKit3で出てきたCollaborative Sessionなども有効に活用する為には、この方法を基準に開発する必要があります。（私はARKit1をやっと理解した瞬間にARKit2の必要性が分かり手戻りした記憶があります。。）

次回は、2019年に出てきたRealityKitを使ったオブジェクト配置の方法を書こうと思います。ARKitのはじめかたシリーズは次で最終回です。

ここまで読んで頂きありがとうございました！

続きはこちら▶︎ [ARKitのはじめかた その4「オブジェクトを配置する(ARKit3版)」](https://qiita.com/1901drama/items/e91c2ed9e6506644e34c)