ARKitでグラフィティアートをして、ARWorldMapで共有する

# はじめに
はじめまして、drama(@1901drama)です！
ARを勉強するにあたり日本語ドキュメントが少なくて苦戦したので、
誰かの助けになれば...と思い、Qiitaデビューしました。少しでもARが普及すると嬉しいです。
`※記載間違い等あれば教えてください。`


# ARkit 2.0の発表
もう5ヶ月程前ですが、2018/9にARKit2.0(iOS12)がリリースされましたね。
![201806221123354000.jpg](https://qiita-image-store.s3.amazonaws.com/0/222377/2d8095a5-0df7-ccac-772f-18b15cc17a51.jpeg)
https://www.moguravr.com/arkit-2-0/

～追加された機能達～
・ARのマルチプレイが可能に
・ARオブジェクトの再利用が可能に
・イメージ認識やトラッキングサポートが拡大
・AR向けフォーマット「USDZ」に対応　など

つまり、AR体験を 
・他人と同時に見れる（共有出来る）
・過去の情報を再現出来る
・専用アプリ以外(safariや他のデバイス)でも見れる　ということに。

以前unityでARをやってたときよりも、めちゃくちゃ出来ることが増えていて驚きです。
これはもう電脳コイルのようなAR時代も近いのではないか!!? と思い ARを始めました。
今回はAR開発を初めて 一つ目に作ったものを紹介します。


# 出来たもの
グラフィティーアートをどこでも出来れば楽しそう！という考えから、ARでラクガキするSNSを作りました。
3Dオブジェクトを置いたり描いたり、つくったものを共有/評価 出来ます。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">GRAF その1 <a href="https://t.co/iePHYO43D3">pic.twitter.com/iePHYO43D3</a></p>&mdash; drama (@1901drama) <a href="https://twitter.com/1901drama/status/1095708490255876096?ref_src=twsrc%5Etfw">2019年2月13日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


・置いたり、書いたり
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">GRAF その2 <a href="https://t.co/vJk1tVdYmN">pic.twitter.com/vJk1tVdYmN</a></p>&mdash; drama (@1901drama) <a href="https://twitter.com/1901drama/status/1095709433168973824?ref_src=twsrc%5Etfw">2019年2月13日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 
・共有したり、評価したり、上書きしたり
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">GRAF その3 <a href="https://t.co/CEC5LkmKWG">pic.twitter.com/CEC5LkmKWG</a></p>&mdash; drama (@1901drama) <a href="https://twitter.com/1901drama/status/1095709585279602688?ref_src=twsrc%5Etfw">2019年2月13日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 
手こずった点は山ほどある...のですが、
一先ず、このアプリの肝であるAR共有機能についてのみ解説します。

# ARの共有方法
ARKitのARWorldMapを使っています。
https://developer.apple.com/documentation/arkit/arworldmap

ARWorldMapには、物理的な空間情報と空間の特徴点を記録したARanchorが含まれています。
ざっくりいうと、ARworldMap≒取得した空間内の位置情報群 と思って頂ければいいです。

このARWorldMapを利用し下図のような形で、空間情報の共有を実装しました。
![firebase-connect.png](https://qiita-image-store.s3.amazonaws.com/0/222377/87ec6dcc-786e-b2af-e69c-47b8a59d0433.png)
>・作成ユーザーを識別する為に、Authenticationを利用。
・空間情報(ARWorldMap)とオブジェクトの情報はサイズが大きいのでCloudStorageを利用。
・その他の情報は、Firestoreを利用。
 　※図のRealtime DBは、Firestoreに変更しました。



**【処理の順番】**
１．空間に置いたり描いたりしたオブジェクトと、空間情報(ARWorldMap)を紐づけて表示させます。

```swift
//空間にオブジェクトを置く時の処理
//SCNodeを作成し名前をつける。（細かい設定等は省略します）
let testNode= SCNNode()
testNode.name = "testNode"

//アンカーにSCNNodeと同じ名前をつけて、タッチした場所(空間)に追加する。
let Anchor = ARAnchor(name: "testNode", transform: hitTestResult.worldTransform)
sceneView.session.add(anchor: Anchor)
self.testNodes.append(testNode)
```
```swift
//空間にanchorが追加された時の処理
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
  //anchorと同じ名前のSCNNodeを空間に追加する。
  for testNode in testNodes{
    if anchor.name == testNode.name {
      sceneView.scene.rootNode.addChildNode(testNode)
    }
  }
}
```

２．空間情報（ARworldMap）と 置いたり描いたりしたオブジェクトの情報を アーカイブし、共有したい人がアップロードします。

```swift
//Apple公式
func writeWorldMap(_ worldMap: ARWorldMap, to url: URL) throws {
 let data = try NSKeyedArchiver.archivedData(withRootObject: worldMap, requiringSecureCoding: true) try data.write(to: url) 
}
//①ARachor(ARworldMap)を取得→②ARachor(ARworldMap)をアーカイブ(シリアライズ)してdata化 →③dataをURLにアップロードの流れ
```
```swift
//アプリ内に記載したコード
func dataExport(){
    //AR_DATA(ARworldMap) Export
    self.sceneView.session.getCurrentWorldMap { (worldMap, error) in
        guard let worldMap = worldMap else { print("Error getting current world map."); return }
        do {
            self.ar_data = try NSKeyedArchiver.archivedData(withRootObject: worldMap, requiringSecureCoding: true)
        } catch let error as NSError { print("failed to write: \(error)")}
    }

    //OBJECT_DATA(オブジェクト) Export
    let objectData = self.testNodes
    do {
        self.object_data = try NSKeyedArchiver.archivedData(withRootObject: objectData, requiringSecureCoding: true)
    } catch let error as NSError { print("failed to write: \(error)") }
}

//この後 FirebaseのFirestoreにアップロードしてます。
```


３．最後に、共有されたい人がインポートすることで、空間を再現します。

```swift
//Apple公式
func loadWorldMap(from url: URL) -> ARWorldMap throws {
 let mapData = try Data(contentsOf: mapURL) guard let worldMap = try NSKeyedUnarchiver.unarchivedObject(of: ARWorldMap.classForKeyedUnarchiver(), from: mapData) as? ARWorldMap else { throw ARError.invalidWorldMap } return worldMap 
}
//④dataをURLからダウンロード→⑤dataをアンアーカイブ（デシリアライズ）してARachor(ARworldMap)化→⑥現在の環境にARachor(ARworldMap)を反映
```
```swift
//アプリ内に記載したコード
//この前に FirebaseのFirestoreからダウンロードしてます。(downloadsPath_AR_DATA,downloadsPath_OBJECT_DATA)
func dataImport(){
  //OBJECT_DATA(オブジェクト) Import
  let unarchievedOBJECT_DATA = try? NSKeyedUnarchiver.unarchiveObject(with:Data(contentsOf:downloadsPath_OBJECT_DATA))
  guard let downloadtestNodes:[SCNNode] = unarchievedOBJECT_DATA as? [SCNNode] else { return }
  for testNode in downloadtestNodes {
    self.testNodes.append(testNode)
  }

  //AR_DATA(ARworldMap) Import
  let unarchievedAR_DATA = try? NSKeyedUnarchiver.unarchiveObject(with:Data(contentsOf:downloadsPath_AR_DATA))
  guard let newWorldMap = unarchievedAR_DATA as? ARWorldMap else {return}

  //downloadしたARWorldMapを反映
  self.configuration.initialWorldMap = newWorldMap
  self.sceneView.session.run(self.configuration)
}
```

# まとめ
AR開発は成果物が空間に目に見えて現れるのが面白いです。
普段はディレクションやサービス設計がメインなので、アプリ開発自体も楽しいです。

ここまで読んで頂きありがとうございました！
※今回のアプリは、ちゃんとしたキャラクターが完成すればリリースする予定です。