【ARKit】拡張現実テンプレートを再現する

#この投稿は何？
Xcodeでアプリケーションを開発するとき、テンプレートを選択します。
特に、AR（拡張現実）アプリを開発する場合は __Augmented Reality App__ テンプレートを選択します。
この投稿では、最も基本的なアプリ向けテンプレートの __Single View App__ をカスタムして、Augmented Reality App テンプレートと同等にする手順を解説します。
<img width="727" alt="options_templateのコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/e1aaaa68-69f8-e1c1-bb31-39d8eaeeabc3.png">

## 何を作るの？
以下のような極めてシンプルなARアプリケーションを作成します。
<img width="375" alt="IMG_4653.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/70476/ecf82f9d-ccb5-d154-3bb3-bb91dbb1f213.png">

この投稿記事では...

- iPhone で現実空間を認識すること
- 現実空間に、仮想オブジェクトを配置すること

を実践できます。

## 実行環境
ARKit は iOS11 とともにリリースされた拡張現実アプリケーションを開発するためのフレームワークです。実行するには、iPhoneSE 以降の iOS デバイスの実機が必須です。

なお、この投稿記事は以下の環境で実践しています。
- Xcode10
- macOS Mojave 10.14.2
- iPhoneX 実機 (iOS 12.1)

## 前提の知識とスキル
Xcode の基本的な使い方を理解している。
Swift の基本的な構文を理解している。
拡張現実 (AR) のことを知っている。

ARKit については以下のリンクで詳しく紹介されています。
[iOSのための拡張現実](https://www.apple.com/jp/ios/augmented-reality/)


# 手順
作業完了後には、実機の iPhone を使用してビルドおよび実行します。
Mac と iPhone が正しく接続できることを確認しておいてください。

## プロジェクトのオプションを選択する
Single View App テンプレートを選択します。
<img width="731" alt="スクリーンショット 2019-01-14 12.29.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/c3ebb7fc-2a4b-ee4d-4abd-2fe1ff39d97a.png">

Product Name を入力します。
ここでは `Typical Augmented Reality App` としておきます。
<img width="732" alt="スクリーンショット 2019-01-14 12.32.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/194937f8-735b-151d-ac4d-4a1e5e11dd46.png">

プロジェクトは任意の場所に保存してください。
新しいプロジェクトが作成できました。

## プロパティリストをカスタムする
プロジェクトを作成した直後は、プロジェクトナビゲーターが表示されています。
_info_ タブを選択して、アプリケーションの概要を設定します。
このアプリケーションでは、ARとカメラを使用することを明示します。

### ARKit を使用する
1. _Required device capabilities_ キーを展開します。
2. _item0_ の _+_ をクリックして、サブキーを追記します。
3. _value_ に `arkit` と入力します。
<img width="1512" alt="スクリーンショット 2019-01-14 12.36.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/ca5790f0-9485-58c4-29d6-c0a1223e94ae.png">


### カメラを使用する
1. トップレベルにある任意の _key_ で _+_ をクリックします。
2. 追加された _key_ で、`Privacy - Camera Usage Description` を選択します。
3. _value_ はカラにしておきます。

<img width="963" alt="スクリーンショット 2019-01-14 12.56.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/c5b32171-4779-b7e8-0298-05f4a2a9cab6.png">


## シーンを作成する
Xcode にデフォルトで用意されている _Augmented Reality App_ テンプレートには、飛行機の3Dモデルが作成されていますが、この投稿では飛行機ではなく「単純な立方体の3Dモデル」を使用します。

### Assets フォルダを新規作成する (art.scnassets)
1. Xcode メニューから「File > New > File...」を選択します。
<img width="973" alt="スクリーンショット 2019-01-14 12.54.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/d54f15e3-c53b-4f06-2a6d-970ca898d41b.png">

2. _SceneKit Catalog_ を選択して、`art.scnassets` という名前で保存します。
<img width="731" alt="スクリーンショット 2019-01-14 12.59.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/e3e3d629-a1d3-3fd3-fb5c-6d1c928305cc.png">

3. ナビゲータエリアに作成された _art.scnassets_ フォルダを移動しておきます。
<img width="1552" alt="スクリーンショット 2019-01-14 13.02.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/6cf6cfa5-d56b-2243-888f-b8af4bd62857.png">

### Assets フォルダにシーンファイルを作成
__シーン__ は、仮想的な3D空間です。3D空間の座標系は、(x, y, z) の3軸で認識されます。
ここでは、何もないカラのシーンを作成します。
また、シーンに存在する物体のことを __ノード__ と言います。

1. _art.scnassets_ フォルダを副クリックして、_New File_ を選択します。
<img width="1013" alt="スクリーンショット 2019-01-14 13.09.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/7764afa7-e7fe-1026-7a8d-edd7da6925db.png">

2. ファイル名は `cube.scn` にしておきます。また、_Scene graph_ にある _camera_ ノードは削除しておきます。
<img width="1146" alt="スクリーンショット 2019-01-14 13.22.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/d924840c-84e8-7832-7457-02f0c3f0faa2.png">

シーンの操作方法は以下の通りです。

- ドラッグ&ドロップ操作をすると、視点を回転
- スクロース操作をすると、視点を移動
- ピンチイン・アウトすると、視点をズームイン・アウト

## 立方体のノードを配置する
飛行機の3Dモデルを作るのは大変なので、代わりに立方体の3Dモデルをシーンに配置します。
この立方体ノードは、少しだけ平べったい感じの形状にします。

1. Xcode メニューから「Editor > Create > Geometry > Box」を選択するか、オブジェクトライブラリから _box_ をドラッグ&ドロップして、立方体ノードを _cube.scn_ シーンに配置します。
<img width="1252" alt="スクリーンショット 2019-01-14 14.02.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/c4d3b629-ab79-5de1-7b6d-0c3f410f1dbd.png">


2. 立方体ノードの位置を設定する
立方体ノードのノードインスペクタを表示します。
_Position_ プロパティを `x:0, y:1, z:1` に変更します。
単位はメートルなので、原点から 1m 高いところに立方体ノードが配置されます。
<img width="1205" alt="スクリーンショット 2019-01-14 15.04.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/15f40891-ed70-18c1-7b99-07b2d6919934.png">

3. 立方体ノードのサイズを設定する
立方体ノードのアトリビュートインスペクタを表示します。
_Size_ プロパティを `width:1, height:0.5, length:1` に変更します。
縦横が 1m で高さが 50cm の立方体です。
<img width="1205" alt="スクリーンショット 2019-01-14 15.08.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/a84845c7-1ce3-39df-feaf-72df95060925.png">

4. 立方体ノードを可視化する
この時点で立方体ノードには形状と位置が設定されていますが、どのように見えるか設定していないので、見えない状態です。
マテリアルインスペクタから、_Diffuse_ プロパティから任意のカラーを設定します。
<img width="1205" alt="スクリーンショット 2019-01-14 15.10.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/d921dcfe-8d8d-c852-9c0a-403756012119.png">

5. シーンを確認する
エディタエリア下部にある _▷_ ボタンをクリックすると、シーンが描画されます。
視点を回転・移動して、どのように見えるか確認しましょう。 
<img width="1205" alt="スクリーンショット 2019-01-14 15.13.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/30556f96-11d9-b2ae-a420-b5d22d68aac6.png">

確認できたら、□ ボタンを押して描画を停止します。

## Storyboard をカスタムする
ARKit は iPhone のカメラが捉えた現実空間にシーンを投影します。こうすることで、「実際には存在しない仮想オブジェクト」が現実に存在するかのようにユーザは感じます。
この機能を実現するためには、iPhone の画面に __ARSCNView__ オブジェクトを配置します。

### arscnview を配置する
1. ナビゲータエリアで、_Main.storyboard_ を選択します。
2. オブジェクトライブラリから _ARKit SceneKit View_ を選択して、ビューコントローラに配置します。
<img width="592" alt="スクリーンショット 2019-01-14 15.23.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/9f8995b1-94ae-c068-ec55-ab7545a65ebb.png">
<img width="1272" alt="スクリーンショット 2019-01-14 15.26.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/6511feec-493b-b699-3852-485e734a7dcb.png">


### Autolayout の制約を設定する
ユーザがAR世界に没入できるように、画面いっぱいに _ARSCNView_ が表示されるようにします。
上下左右の _safe area_ までの間隔を `0` にして、_Add 4 Constraints_ をクリックします。
<img width="1272" alt="スクリーンショット 2019-01-14 15.55.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/33f28ab7-8eb4-bc98-204a-5b4e0aee7cb6.png">

あらゆるデバイスで、画面いっぱいに拡張現実ビューが表示されるようになりました。
<img width="1272" alt="スクリーンショット 2019-01-14 15.56.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/f49030e1-7d96-3dd0-355e-56bdf680c8aa.png">

## ビューをコードと接続する
1. ビューコントローラを選択した状態で、アシスタントエディタモードに切り替えます。
<img width="1272" alt="スクリーンショット 2019-01-14 16.01.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/9f017963-43b4-a0fd-2ce1-ff2c385db461.png">
エディタエリアの左側にストーリーボード、右側にそのコードが表示されていることを確認します。

2. ストーリーボード上の ARSCNView を副クリックします。
コンテキストメニューにある _New Referencing Outlet_ をドラッグして、コード側の `ViewController` クラス定義にドロップします。
<img width="926" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37303437362f62313235363464342d656665662d373966632d656232612d3032303231396430633433382e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/b4ef1a34-67de-35ef-bb0e-19fc15bbba5b.png">


3. パネルが表示されるので、Name に `sceneView` と入力します。
<img width="924" alt="スクリーンショット 2019-01-14 16.05.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/4a86fe70-e5d9-3667-43fd-0cbf4ada3e33.png">

アラートが表示されていますが、後で対処します。
<img width="925" alt="スクリーンショット 2019-01-14 16.12.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/7ce564ae-d2ff-2ae8-722f-606a23c980ea.png">

## コードを記述する
テンプレートを再現するにあたり、可能な限り忠実に記述しますが、理解度の向上を期待して一部の変数名などを変更しています。また、一部コードを追記しています。

アシスタントエディタモードからスタンダードエディタに切り替えて、_ViewController.swift_ を表示します。
<img width="1272" alt="スクリーンショット 2019-01-14 16.14.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/0fe58747-5068-73b6-c0fc-cb6b696b925f.png">


### フレームワークをインポートする
コード上で、_SceneKit_ と _ARKit_ の機能を使用するために、フレームワークをインポートします。

````ruby:ViewController.swift
import UIKit    // これは最初からある
import SceneKit
import ARKit

````

警告表示が消えました。
コードから、UI部品である _ARSCNView_ を `sceneView` として操作できるようになりました。
<img width="1272" alt="スクリーンショット 2019-01-14 16.17.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/447b4b8b-8f5d-a8d7-25ad-d5681fd3977f.png">

### デリゲートを設定する
_ARSCNView_ で発生したイベントを _ViewController_ クラス内で処理できるようにします。
1. プロトコルに準拠させて、ViewController クラスをデリゲート先に指定します。

````ruby:ViewController.swift
class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet weak var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        sceneView.delegate = self    //デリゲート先になる
    }

}
````

### シーンのインスタンスを生成する
1. 作成した _cube.scn_ ファイルをインスタンス化して、ビューの _scene_ プロパティに設定ます。

````ruby:ViewController.swift
class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet weak var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        sceneView.delegate = self
        
        let cubeScene = SCNScene(named: "art.scnassets/cube.scn")!  //インスタンスを生成
        sceneView.scene = cubeScene    //ビューに表示する
    }

}
````

### ARセッションを実行する
アプリが起動してシーンが描画されたら、ARセッションを実行するようにします。

````ruby:ViewController.viewWillApper()メソッド
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let configration = ARWorldTrackingConfiguration()
        sceneView.session.run(configration )
    }

````

### ARセッションを停止する
アプリが停止して、デバイス画面からシーンが非表示になったら、ARセッションを停止します。

````ruby:ViewController.vierWillDisappear()メソッド
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        sceneView.session.pause()
    }
````

典型的なAR機能が動作するアプリケーションのコードが完成しました。
<img width="1122" alt="スクリーンショット 2019-01-14 16.46.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/ea7c8345-c676-ac1f-3675-77c343e3f7da.png">

### 解析情報を表示する
_ARKit_ が現実空間を認識・追跡していることをわかりやすくします。
また、デバイスに対してどれくらい負荷を掛けているかを解析する情報を表示するようにします。
以下のコードを追記してください。

````ruby:ViewController.viewDidLoad()メソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        
        sceneView.delegate = self
        
        sceneView.debugOptions = .showWorldOrigin
        sceneView.showsStatistics = true
        
        let cubeScene = SCNScene(named: "art.scnassets/cube.scn")!
        sceneView.scene = cubeScene
    }

````

# 実行
Mac と iPhone を接続して、実行先スキームを実機に変更します。
<img width="486" alt="スクリーンショット 2019-01-14 16.51.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/08e67f0d-28e1-f2d8-ca94-ccff979f89c4.png">

なお、ビルド・実行すると iPhone に「"Typical Augmented Reality App"がカメラへのアクセスを求めています」と表示されるので、OKをタップしてください。

## ARアプリケーションを実行するには
ARKit による拡張現実は iPhone のカメラとモーションセンサーを連携することで実現しています。
アプリを実行するには、以下のような環境が要求されます。

- 明るい場所
カメラが撮影した映像が鮮明でない場合、ARKit は現実空間を認識・追跡できません。

- 移動していない空間
モーションセンサーはデバイスが移動した加速度を検出します。電車などの移動空間内ではセンサーが正しく動作しないことがあります。

- 空間を認識させる
アプリケーション起動後、デバイスを回転させたり移動させることで、現実空間を正しく認識できます。

## こうなった
実際に起動した画面は、このようになります。
<img width="375" alt="IMG_4652.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/70476/67ac1e2b-b910-2bac-fd51-3b9854b85b38.png">
画面奥に見える「赤、青、緑の3軸」は、アプリケーションを起動した座標です。
つまり、このスクリーンショットは「地面すれすれでアプリ起動した後、左に回り込んだ」状態です。

# まとめ
AR の基本機能である __空間認識__ と __仮想オブジェクトの投影__ を使用できました。
ARKit には __平面を検出__ する機能があります。また、投影した仮想オブジェクトを __インタラクティブ__ に扱うこともできます。
さらに、認識しているAR空間を __共有__ したり、特定の __2D画像や3Dモデルを検出__ することもできるようになりました。
発表されて以来、着実に強化されていることからも Apple が ARKit にどれほど期待しているかが見て取れます。