写経してみた（ARKit）

## はじめに
前々から `ARKit` さわってみたいな〜と思いつつずっとさわってなかったんですが最近ずっと引きこもってて時間があるので `ARKit` をさわってみました。 
新しいことを習得するには守破離の精神からいって「守」つまり真似することが大事かなと思います。プログラミングでいうと写経ですね。

ということで `ARKit` で写経してみました。

![ar_sample](https://user-images.githubusercontent.com/34936885/87886490-7bbae480-ca58-11ea-9c4f-67e93cede915.gif)

## 環境
* Xcode 11.5
* Deployment Target 13.0

## とりあえずAR表示
今まで `ARKit` を全くさわってなかったのでほぼ何もわからない状態です。。。とりあえず [ARKitのサンプルコード集「ARKit-Sampler」](https://qiita.com/shu223/items/e55e1426c9e036254761#3-lines-ar) を参考に 3D モデルを表示してみました。
手順は下記

1. SceneKit Catalog を追加  
<img width="300" alt="scenekit" src="https://user-images.githubusercontent.com/34936885/87886521-c2104380-ca58-11ea-9a77-51fa8cd38f52.png">
2. 3D モデルを追加（参考サイトのサンプルのship追加）
3. Info.plist に `Privacy - Camera Usage Description` 追加  
<img width="300" alt="info_plist" src="https://user-images.githubusercontent.com/34936885/87886522-cc324200-ca58-11ea-93a6-65279a931717.png">
4. IB上で `ARSCNView` 追加（`ViewController` に接続）  
<img width="300" alt="ib" src="https://user-images.githubusercontent.com/34936885/87886525-dbb18b00-ca58-11ea-941b-8d23e87df0dc.png">
5. ARKit.framework 追加（これがないとlink errorになった。。。）  
<img width="300" alt="framework" src="https://user-images.githubusercontent.com/34936885/87886527-e53af300-ca58-11ea-9c7d-c27860c4cc6e.png">
6.  `ViewController` にコード記載  

```swift
import UIKit
import ARKit

final class ViewController: UIViewController {

    @IBOutlet private weak var sceneView: ARSCNView!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        sceneView.scene = SCNScene(named: "art.scnassets/ship/ship.scn")!
        let config = ARWorldTrackingConfiguration()
        sceneView.session.run(config)
    }
}
```

これだけで AR が実現できます:clap:（もっと `AVCapture` とかでゴリゴリしないといけないと思ってました。。。）

## 写経
とりあえず 3D モデルが簡単に表示できることはわかった。次に何やろうと考えたときに 3D 空間に般若心経を写経したい！と思い至りました！！ 
写経するのに必要なのは下記

1. 文字を表示する
2. 文字にフェードアニメーションを付ける

とりあえずこの２つができれば写経できるはず！

### 文字を表示する
こちら（[ARKitのAR文字をキレイにした話](https://qiita.com/From_F/items/495b088ebe57e41a854d)）を参考に文字表示してみました。とりあえず文字表示は `SCNText` を使うみたい。 
まるっとコピペですがこれで表示できるみたいです。

```swift
//カメラの現在位置を取得する
guard let camera = sceneView.pointOfView else { return }

let textGeometry = SCNText(string: message, extrusionDepth: 0.8)
textGeometry.firstMaterial?.diffuse.contents = UIColor(named: "ArizarARFontColor")
textGeometry.font = UIFont(name: "HiraginoSans-W6", size: 100)
let textNode = SCNNode(geometry: textGeometry)
let position = SCNVector3(0,0.1,-0.1)
textNode.position = camera.convertPosition(position, to: nil)
//カメラの向きに合わせる
textNode.eulerAngles = camera.eulerAngles
//大きさ設定
textNode.scale = SCNVector3(0.0001,0.0001,0.001)

sceneView.scene.rootNode.addChildNode(textNode)
```

### 文字にフェードアニメーションを付ける
こちら（[SceneKitのアニメーションサンプル集](https://qiita.com/kboy/items/73a8ce28541966c7b20a)）を参考にアニメーションを付けてみました。アニメーションには `SCNAction` を使うみたい。`fadeIn` っていうそれっぽいのがあったので使いました。

```swift
textNode.opacity = 0.0 // 0にしておく
let action = SCNAction.fadeIn(duration: 0.2)
textNode.runAction(action)
```

ちょっとひっかかったのが `fadeIn` は `opacity` を 1.0 に変更してくれるやつなのでもともとの `opacity` を下げとかないと何も起きません。

### 全体の実装

```swift
import UIKit
import ARKit
import SceneKit

final class ViewController: UIViewController {

    @IBOutlet private weak var sceneView: ARSCNView!
    private var texts = [
        "仏説摩訶般若波羅蜜多心経",
        "観自在菩薩", "行深般若波羅蜜多時", "照見五蘊皆空", "度一切苦厄",
        "舎利子", "色不異空", "空不異色", "色即是空", "空即是色",
        "受想行識亦復如是", "舎利子", "是諸法空相", "不生不滅", "不垢不浄", "不増不減",
        "是故空中", "無色", "無受想行識", "無眼耳鼻舌身意", "無色声香味触法", "無眼界", "乃至無意識界",
        "無無明", "亦無無明尽", "乃至無老死", "亦無老死尽",
        "無苦集滅道", "無智亦無得", "以無所得故", "菩提薩埵", "依般若波羅蜜多故", "心無罣礙", "無罣礙故", "無有恐怖", "遠離一切顛倒夢想",
        "究竟涅槃",
        "三世諸仏", "依般若波羅蜜多故", "得阿耨多羅三藐三菩提", "故知般若波羅蜜多",
        "是大神呪", "是大明呪", "是無上呪", "是無等等呪", "能除一切苦", "真実不虚", "故説般若波羅蜜多呪", "即説呪日",
        "羯諦", "羯諦", "波羅羯諦", "波羅僧羯諦", "菩提薩婆訶", "般若心経"
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        sceneView.scene = SCNScene()
        let config = ARWorldTrackingConfiguration()
        sceneView.session.run(config)
    }

    @IBAction private func start(_ sender: Any) {
        guard let camera = sceneView.pointOfView else { return }
        // カメラ中央手前に表示
        let startPosition = camera.convertPosition(SCNVector3(0, 0, -0.5), to: nil)
        // 文字サイズたぶん３cm
        let textSize: Float = 0.03
        var index = 0
        var x = startPosition.x
        for text in texts {
            var y = startPosition.y
            for t in text {
                let textGeometry = makeSCNText(String(t))
                let textNode = SCNNode(geometry: textGeometry)
                textNode.position = SCNVector3(x, y, startPosition.z)
                let scale = 1 / (textGeometry.boundingSphere.radius * 2) * textSize
                textNode.scale = SCNVector3(x:scale, y:scale, z:scale)
                textNode.opacity = 0.0
                y -= (textSize + textSize/3)
                let action = makeFadeAnimation(index: index)
                textNode.runAction(action)
                sceneView.scene.rootNode.addChildNode(textNode)
                index += 1
            }
            x -= (textSize + textSize/3)
        }
    }

    private func makeSCNText(_ text: String) -> SCNText {
        let textGeometry = SCNText(string: text, extrusionDepth: 0.2)
        textGeometry.firstMaterial?.diffuse.contents = UIColor.white
        textGeometry.font = .systemFont(ofSize: 1)
        return textGeometry
    }

    private func makeFadeAnimation(index: Int) -> SCNAction {
        let duration = 0.2
        let waitAction  = SCNAction.wait(duration: duration * Double(index))
        let fadeAction  = SCNAction.fadeIn(duration: duration)
        return SCNAction.sequence([waitAction, fadeAction])
    }
}
```

### 課題
とりあえず動くものはできましたがまだどの Node をどれの Child にすべきかとかはよくわかってないです。。。 
文字サイズを決めてる `let scale = 1 / (textGeometry.boundingSphere.radius * 2) * textSize` もいまいちなんでこれで計算できるのかわかってません:weary:

「観」とか一部の文字がなぜか表示できませんでした。

おいおい勉強していこうと思います:muscle:

## さいごに
これをやるために般若心経についてちょっと調べました（２冊本読んだ:sunglasses:）がすべてのものは存在するようで存在しないものなんだとか。 
みなさんも開発で仕様がコロコロ変わるのを経験したことがあると思いますがそういうときにイライラしてはいけません。仕様もまた存在するようで存在しないものなのです。そう、仕様もまた「空」なのです:innocent:

これ:point_right:[「写経」を自動化し、オートで功徳を積める仕組みを作ってみたのでございます。](https://qiita.com/youwht/items/616ccc062725fa050a34)みたときからやりたかった:blush:

## 参考
* [ARKitのサンプルコード集「ARKit-Sampler」](https://qiita.com/shu223/items/e55e1426c9e036254761)
* [SceneKitのアニメーションサンプル集](https://qiita.com/kboy/items/73a8ce28541966c7b20a)
* [iPhoneアプリ開発:AR その3 仮想体を置いてみる](https://ameblo.jp/xcc/entry-12399170916.html)
* [ARKitのAR文字をキレイにした話](https://qiita.com/From_F/items/495b088ebe57e41a854d)