ARでマンガを覗き読み【iOS】

# はじめに
AR、いいですよね。素敵な世界。
マンガ、いいですよね。素敵な世界。

ということで、今回はiOSのARKitを利用して、紙のマンガを覗き読みできるアプリ
![nekonote1-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119392/a82fd7e0-f0ef-5bdb-ec2c-bcb7968d5353.gif)

を実装してみました。

*デモに利用させていただいているマンガは、
マンガボックスで連載中の「[ネコの手、借りてます。](https://www.mangabox.me/reader/93929/episodes/)」🙏
作家の[遥那もより](http://dondonguri.web.fc2.com/)様から許可をいただき動画掲載させていただいてます🙏🙏🙏
最高オブ最高の癒しマンガなのでみなさん是非読んでください🙏🙏🙏*

いつでも、どこでも、だれもが、この素敵な世界を実現できるよう、
実装を、コメント多めで紹介します✨

# 前準備
1.お高めなiPhone（[A9以降のプロセッサを搭載したもの](https://developer.apple.com/jp/documentation/arkit/verifying_device_support_and_user_permission/)）を購入💸します

2.プロジェクトを作ります
`[ File > New > Project > Single View App ]`

3.Storyboard に `ARSCNView` を貼り付け、ViewController と紐付けます

4.カメラ利用の許可を取るために、Info.plistに追加します
`[ Privacy - Camera Usage Description ]`

5.`Assets.xcassets`を開き、`[ New AR Resource Group ]`を追加。
フォルダの中にマーカーとして検出したい画像（今回はマンガの表紙）を入れます。
<img width="1509" alt="スクリーンショット 2019-12-21 21.36.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119392/42d35ae2-bbf8-1e57-acca-cca8ad628a11.png">


この時、画像の**名前**と**現実世界での大きさ**を入力します。
マーカーにふさわしい画像については[こちら](https://developer.apple.com/videos/play/wwdc2018/602/)を参考にしてください。

6.ARオブジェクトとして表示したい画像（今回はマンガの原稿）を`[ New Image Set ]`で追加します。

# 実装

#### 1.設定
`ARImageTrackingConfiguration`を設定します。

```swift:
import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController {

    @IBOutlet weak var sceneView: ARSCNView!

    var session: ARSession {
        return sceneView.session
    }

    let updateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".serialSceneKitQueue")

    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.delegate = self
        resetTracking()
    }

    func resetTracking() {
        // Assetsの読み込み
        guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
            fatalError("Missing expected asset catalog resources.")
        }

        // 既知の2D画像を追跡するconfigの設定
        let configuration = ARImageTrackingConfiguration()
        configuration.trackingImages = referenceImages
        // ARオブジェクトの手前に人が映り込む時、オクルージョン処理してくれる設定
        configuration.frameSemantics = .personSegmentation

        // session開始
        // .resetTracking: デバイスの位置をリセットする
        // .removeExistingAnchors: 配置したオブジェクトを取り除く
        session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // session停止
        sceneView.session.pause()
    }
}
```

`configuration.frameSemantics = .personSegmentation`は、
ARKit3から追加されたピープルオクルージョンの設定で、設定するとこのように

![nekonote2-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119392/4fb0a8fb-9c6f-acd3-f1eb-5a7a91662604.gif)

人の指が原稿の手前にくるので、マンガの中を覗き読んでいる！という世界をよりリアルに実現できます。
ただ、まだ少し精度が甘くチリチリすることは否めないので、お好みでオフにして下さい🤗

#### 2.マーカーを検知してオブジェクトを表示
表紙を検知し、`ARSCNViewDelegate`で通知を受け取ったら原稿を表示します。

```swift
// MARK: - ARSCNViewDelegate
extension ViewController: ARSCNViewDelegate {
    // 新しいARアンカーに対応するノードが追加されたことを通知
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard let imageAnchor = anchor as? ARImageAnchor else {
            return
        }
        // 表示したいオブジェクトをARアンカーの名前から決定
        let objectImage: UIImage
        switch imageAnchor.referenceImage.name {
        case "nekonote" :
            objectImage = nekonotes[nekonoteIndex]
        case "hanakaku":
            objectImage = hanakakus[hanakakuIndex]
        default:
            return
        }

        updateQueue.async {
            // sceneにノードを追加
            node.addChildNode(self.createNode(image: objectImage, name: imageAnchor.referenceImage.name ?? "no name"))
        }
    }

    private func createNode(image: UIImage, name: String) -> SCNNode {
        // 検出されたARアンカーの位置を視覚化する平面に合わせて、長方形のノード（SCNPlane）を作成
        let scale: CGFloat = 0.2
        let plane = SCNPlane(width: image.size.width * scale / image.size.height,
                             height: scale)
        // firstMaterial:平面の最初のマテリアル
        // diffuse: 表面から拡散反射される光の量、拡散光はすべての方向に等しく反射されるため、視点に依存しない、contentsに画像をset
        plane.firstMaterial?.diffuse.contents = image
        let planeNode = SCNNode(geometry: plane)
        planeNode.name = name
        // SCNPlaneはローカル座標空間で垂直方向を向いているが、ARアンカーは画像が水平であると想定しているため
        // 一致するように回転させる
        planeNode.eulerAngles.x = -.pi / 2
        // アニメーション
        planeNode.position = SCNVector3(0.0, 0.0, -0.15)
        planeNode.scale = SCNVector3(0.1, 0.1, 0.1)
        planeNode.runAction(self.imageAction)
        return planeNode
    }
}
```

#### 3.アニメーション
ARはかっこよく表示したい、ですよね、ですよね。
ということで、はじめに検知した時にフェードアニメーションを追加します🕺


```swift:
    var imageAction: SCNAction {
        return .sequence([
            .scale(to: 1.5, duration: 0.3),//スケール
            .scale(to: 1, duration: 0.2),
            .fadeOpacity(to: 0.8, duration: 0.5),//フェード
            .fadeOpacity(to: 0.1, duration: 0.5),
            .fadeOpacity(to: 0.8, duration: 0.5),
            .move(to: SCNVector3(0.0, 0.0, 0.0), duration: 1),//移動
            .fadeOpacity(to: 1.0, duration: 0.5),
        ])
    }
```

`.sequence`で連続したアニメーションを処理することができます。
センスがないとか言わないで😭

#### 4.タップ
画面をタップでページをめくりましょう。

```swift:
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard
            let location = touches.first?.location(in: sceneView),
            let result = sceneView.hitTest(location, options: nil).first else {
                return
        }
        // ノードの名前を取得し画像変更
        let node = result.node
        let objectImage: UIImage
        switch node.name {
        case "nekonote" :
            nekonoteIndex += 1
            objectImage = nekonotes[nekonoteIndex % 4]
        case "hanakaku":
            hanakakuIndex += 1
            objectImage = hanakakus[hanakakuIndex % 4]
        default:
            return
        }
        // アニメーションしながら画像差し替え
        node.runAction(self.pageStartAction, completionHandler: {
            node.geometry?.firstMaterial?.diffuse.contents = objectImage
            node.runAction(self.pageEndAction, completionHandler: nil)
        })
    }

    var pageStartAction: SCNAction {
        return .fadeOpacity(to: 0.0, duration: 0.1)
    }
    var pageEndAction: SCNAction {
        return .fadeOpacity(to: 1.0, duration: 0.2)
    }
```

#### 完成🎉
![nekonote3-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119392/a29889fb-2b30-6f12-7a0a-e231a1839273.gif)

これで、本屋さんでマンガに封がしてあっても、中身を覗き読みすることができちゃいますね👀

サンプルアプリはこちらに公開しています✨
[koooootake/MangaTrialAR-ios](https://github.com/koooootake/MangaTrialAR-ios)

動画は[こちら](https://twitter.com/koooootake/status/1208399167388889089?s=20)

*もうひと作品お借りしたのは個人利用可能で作品を公開してくださっている「[ハナカク](https://matsukaku.booth.pm/items/1014004)」様🙏
とても絵が綺麗いいい本当にありがとうございますすす✨*

# おわりに
#### やりたいこと
ここから、マンガのコマをVisionで分解してアニメーションさせたり
指先のジェスチャーを検知してページをめくったりして、現実を拡張したいいい

AppleのARグラスが売り出され🤓
コンテンツの表現が紙やディスプレイに留まらず拡張される素敵な世界が楽しみです🥴

#### おまけ
DeNAアドベントカレンダー終盤を担当したので
この記事を読んだ、ものづくり好きな人にオススメの記事を勝手に紹介👀

[【3日で実装・公開】エモいアートな画像生成アプリ開発](https://qiita.com/rantaro/items/fdb7bbd55a57c68db834)
エモい！！！じんむのアイコンも[エモくなりました](https://twitter.com/koooootake/status/1204968605223280640?s=20)

[【Electron+GCP+Slack App】Slackのコメントをニコニコ動画風にプレゼンで流す方法](https://qiita.com/kengo92i/items/b1362ce1ab14180ff4bd)
「Slack」のコメントというところが、社会人には超超超実用的✨

[退職者を送る技術 - Twilio と Socket.IO で作る電話マルチプレイシステムの小ネタ](https://qiita.com/feb19/items/dbac4255ba08623a0097)
電話を掛けて、キーパットを利用して、みんなで操作するゲームの作り方🎮発想良すぎぎぎ

来年もべしべしものづくり楽しもううう💪