ARKitで空間上に写真を置く

ARKitを使ってiPhoneのカメラロールに入っている写真を空間上に置いてみます！

![IMB_1dDqIg.GIF](https://qiita-image-store.s3.amazonaws.com/0/111366/75ae7030-08e5-45ee-71e1-49ce2b61c0e0.gif)

今回サンプルで作ってみたViewControllerは[github](https://github.com/kboy-silvergym/ARCameraApp/blob/91d8d8a100c3d099d8089c618ea1afa3c6a2d585/ARCameraApp/ViewController.swift)に置きましたので、全体はそちらをご確認ください。

## 手順

空間上に写真を置くためには以下のステップがあります。

* 1. UIImagePickerControllerを使ってカメラロールの写真を取得する
* 2. 写真のUIImageを持ったSCNNodeを作る
* 3. そのnodeをsceneViewに貼る

それぞれ順番に解説していきます。

## UIImagePickerControllerを使ってカメラロールの写真を取得する

まずは、UIImagePickerControllerを作ってpresentします。

```swift
    private func showUIImagePicker() {
        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
            let pickerView = UIImagePickerController()
            pickerView.sourceType = .photoLibrary
            pickerView.delegate = self
            pickerView.modalPresentationStyle = .overFullScreen
            self.present(pickerView, animated: true, completion: nil)
        }
    }
```

pickerViewでのアクションを受け取るためにViewConteollerはdelegateに準拠させます。

```swift
pickerView.delegate = self
```

```swift
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
            setImageToScene(image: image)
        }
        picker.dismiss(animated: true, completion: nil)
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}
```

これで、カメラロールのUIImageを取得できましたので、あらかじめ作っておいた

```swift
func setImageToScene(image: UIImage)
```

メソッドでSceneViewに貼っていきます。

## 写真のUIImageを持ったSCNNodeを作る

とその前に、SCNNodeを作るメソッドを用意しときましょう。

```swift
    private func createPhotoNode(_ image: UIImage, position: SCNVector3) -> SCNNode {
        let node = SCNNode()
        let scale: CGFloat = 0.3
        let geometry = SCNPlane(width: image.size.width * scale / image.size.height,
                                height: scale)
        geometry.firstMaterial?.diffuse.contents = image
        node.geometry = geometry
        node.position = position
        return node
    }
```

ARKit登場以前からあるSceneKitには様々なSCNNodeが用意されていますが、今回はSCNPlaneを作って写真として空間に貼っていくことにしました。

ポイントは、

```swift
geometry.firstMaterial?.diffuse.contents = image
```

で、これやらないとただの白い面ができるだけになるので、要注意！

(ぶっちゃけですが、ここである`scale`はいい感じになるようにチューニングしただけです。笑）


## そのnodeをsceneViewに貼る

最後に、sceneViewにnodeを追加していきます。

先ほど作った

```swift
func createPhotoNode(_ image: UIImage, position: SCNVector3) -> SCNNode
```

メソッドを呼んでnodeを作っていますが、その引数に渡しているpositionによって**写真がどこに貼られるか**が決まります。

今回は、https://dev.classmethod.jp/smartphone/iphone/ios11-arkit-pointofview/ を参考にさせてもらって、**カメラの中央、カメラから50cm先に置かれるように**しています。

```swift
    private func setImageToScene(image: UIImage) {
        if let camera = sceneView.pointOfView {
            // カメラから見て50cm先の座標
            let position = SCNVector3(x: 0, y: 0, z: -0.5) 
            // 世界座標系に変換
            let convertedPosition = camera.convertPosition(position, to: nil)
            let node = createPhotoNode(image, position: convertedPosition)
            self.sceneView.scene.rootNode.addChildNode(node)
        }
    }
```

以上で、カメラロールで選択した写真をカメラの中央部分に面として貼ることができました！

![IMB_1dDqIg.GIF](https://qiita-image-store.s3.amazonaws.com/0/111366/75ae7030-08e5-45ee-71e1-49ce2b61c0e0.gif)

## 参考
* [iOS11 Programing](https://peaks.cc/iOS11)
* https://dev.classmethod.jp/smartphone/iphone/ios11-arkit-pointofview/

##関連APP
* [Graffity - ARカメラ & 落書き動画コミュニティ](https://itunes.apple.com/jp/app/id1277452876?mt=8)

## github
* https://github.com/kboy-silvergym/ARCameraApp/blob/91d8d8a100c3d099d8089c618ea1afa3c6a2d585/ARCameraApp/ViewController.swift