2D動画を3D化してARKitで空中に配置する


#はじめに
こんにちは、drama(@1901drama)です！
Oculas Goのスタート画面って未来感ありますよね。（↓こういうの）
![201810251102927000.jpg](https://qiita-image-store.s3.amazonaws.com/0/222377/6aa81bd1-5ebf-49fe-144d-305592756053.jpeg)
引用元：[MoguLive](https://www.moguravr.com/oculus-go-16/)

ARで現実空間に合わせても出来ないかな。。と思って作ったものを紹介します。
`※記載間違い等あれば教えてください。`


# 出来たもの
動画を空中にいっぱい出して、お金が無くても大画面の気分を味わえるアプリ
※音量注意です。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">VIDEO その1 <a href="https://t.co/NuWU76381J">pic.twitter.com/NuWU76381J</a></p>&mdash; drama (@1901drama) <a href="https://twitter.com/1901drama/status/1097060766157426688?ref_src=twsrc%5Etfw">2019年2月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


　・好きな場所に動画を配置
　・動画を削除
　・大画面化(70インチくらいまで)
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">VIDEO その2 <a href="https://t.co/qZjQdmARJr">pic.twitter.com/qZjQdmARJr</a></p>&mdash; drama (@1901drama) <a href="https://twitter.com/1901drama/status/1097061001650786305?ref_src=twsrc%5Etfw">2019年2月17日</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">VIDEO その3 <a href="https://t.co/Yx4EhVDFXL">pic.twitter.com/Yx4EhVDFXL</a></p>&mdash; drama (@1901drama) <a href="https://twitter.com/1901drama/status/1097061317821616129?ref_src=twsrc%5Etfw">2019年2月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

>**※尚、一旦アプリを消すと配置場所を復元することは出来ません。**
ARKitにはカメラを通して、空間から検出した特徴点(ARanchor)を記録する機能があり、エクポート<=>インポートすることで、その時の空間情報(worldMap)を再現することが出来ます。

>ただし、今回のアプリは特徴点(ARanchor)に紐づけてオブジェクトを配置しているのではなく、タッチした場所で検出した特徴点(ARanchor)の場所にオブジェクト配置しているだけなので、空間情報(WorldMap)の再現性を利用出来るようしていません。
オブジェクトをエクポート<=>インポートして復元しても、デバイスとの相対距離で復元されます。

>ARKit2が出る前(2018/6以前)のARKit記事はこの手法でのノード配置が多いので、使い分けに注意してくださいね。



#難しかった点
同じように始める人向けに、開発で特にはまった点を書いておきます。

**動画->SCNnodeへの変換**
①動画再生できるプレイヤーを作る
②テクスチャにプレイヤーを貼る
③立方体（奥行がとても薄いので平面ぽく見えます）にテクスチャを貼る
という流れがやや大変です。今回は良い参考があったので大分楽出来ました。
>参考：[[iOS 11][ARKit] 近所の駐車場で、大型ディスプレイでビデオを見る！](https://dev.classmethod.jp/smartphone/iphone/ios-11-arkit-video/)

ノードを作成する処理
※動画を選択してくる際に使う変数と、サイズバーを使う為の見知らぬ変数も入ってます。

```swift
func createVideoNode(size:CGFloat, videoUrl: URL) -> SCNNode {
    // AVPlayerを生成する
    let avPlayer = AVPlayer(url: videoUrl)
    
    //ループ再生
    avPlayer.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none;
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(ViewController.didPlayToEnd),
                                           name: NSNotification.Name("AVPlayerItemDidPlayToEndTimeNotification"),
                                           object: avPlayer.currentItem)
    
    // SKSceneを生成する
    let skScene = SKScene(size: CGSize(width: CGFloat(1000 * Slide.value), height: CGFloat(1000 * Slide.value)))
    
    // AVPlayerからSKVideoNodeの生成する（サイズはskSceneと同じ大きさ）
    let skNode = SKVideoNode(avPlayer: avPlayer)
    skNode.position = CGPoint(x: skScene.size.width / 2.0, y: skScene.size.height / 2.0)
    skNode.size = skScene.size
    skNode.yScale = -1.0 // 座標系を上下逆にする
    skNode.play()
    skScene.addChild(skNode)
    
    let node = SCNNode()
    node.geometry = SCNBox(width: size, height: size, length: 0.01, chamferRadius: 0)
    let material = SCNMaterial()
    material.diffuse.contents = skScene
    node.geometry?.materials = [material]
    node.scale = SCNVector3(1.66 * Slide.value, 1 * Slide.value, 0.01)
    
    return node
}
```

**ワールドマップへの変換**
ARanchorに紐づけてオブジェクトを表示するのではなく、ARanchorを検知した場所にオブジェクトを配置するので、
カメラ座標／ワールド座標／相対位置あたりを頭の中で整理して開発するのが少し難しかったです。Unity使えばオブジェクト見えるし、もう少し簡単だったかも。
>参考：[ARKitのための3D数学](https://qiita.com/k-boy/items/775633fe3fd6da9c5fb6)

ノードを配置するあたりの処理

```swift
guard let touch = touches.first else {return}
let location:CGPoint = touch.location(in: sceneView)
let hitTest  = sceneView.hitTest(location, types: [.featurePoint,.existingPlaneUsingExtent])
  if !hitTest.isEmpty {
    print("ARhitTest OK")
    let videoNode = createVideoNode(size: (CGFloat(3.0 * Slide.value)), videoUrl: self.videoURL_after!)
    let hitTestResult = hitTest.first!
    let transform = hitTestResult.worldTransform
    let thirdColumn = transform.columns.3
    let position = SCNVector3(Float(thirdColumn.x) ,Float(thirdColumn.y) ,-0.99)

    videoNode.position = sceneView.unprojectPoint(position)
    if let camera = sceneView.pointOfView {
      let rotation_y = camera.rotation.y
      videoNode.rotation = SCNVector4Make(0, 1, 0, rotation_y)
    }
   //↓動画をタッチして消す処理に使っています。
    videoNode.name = "addvideo"
                
    sceneView.scene.rootNode.addChildNode(videoNode)
   }else{
     print("error ARhitTest.isEmpty")
   }
```

#まとめ
簡単なアプリなので、ARに興味があれば是非皆さんもお試しください！
上記の参考リンクを元にベースを作成し、別途 以下の処理を追加していくと作れます。

>・カメラロールから動画を選択
・選択した任意の動画を、シーンに反映させる
・タッチした場所(空間)に、オブジェクトを配置する
・空間にあるオブジェクトを選択する
・削除直前に重力判定を追加する

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