ARKitでバーチャル背景をつくる

# ARSCNViewでZoomの真似をする
**アプリとソースコード公開しています。**
ARCamera.
AppStore：https://apps.apple.com/us/app/id1488699740
GitHub:https://github.com/john-rocky/ARCamera
![argif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/e8d3b62e-cd56-880d-eacd-3ab22ce44084.gif)

```swift:ViewController.swift
@IBOutlet var sceneView: ARSCNView!
var virtualBackgroundNode = SCNNode()
```
##人物を手前に残す（People Occlution）##
ARSessionにPeople Occlutionを設定しないと、ARコンテンツが人物をオーバーラップしてしまいます。

Occlutionなし
<img width="200" alt="スクリーンショット 2020-07-21 0.16.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/e6afa048-b840-1d40-626c-d86e91086406.png">

Occlutionあり
<img width="200" alt="スクリーンショット 2020-07-21 0.17.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/dfc6c99b-ca66-c875-235d-b80f842c8d60.png">

```swift:ViewController.swift
 let config = ARFaceTrackingConfiguration()                    
      if ARFaceTrackingConfiguration.supportsFrameSemantics(.personSegmentation) {
            config.frameSemantics.insert(.personSegmentation)
      } else {
            presentAlert(NSLocalizedString("この端末/OSではピープルオクルージョンを利用できません", comment: ""))
      }
 sceneView.session.run(config, options: [])
```

##SCNNodeでバーチャル背景をつくる
SCNPlaneをつくり、画像や動画を貼りつけます。
貼り付けるコンテンツはSpriteKitのSKSceneとしてつくります。

動画の場合

```swift
let avPlayer = AVPlayer(url: videoUrl)
//画像の縦横比が歪まないように調整しています
var mediaAspectRatio: Double!
guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { return (nil,nil) }
let size = track.naturalSize.applying(track.preferredTransform)
let resolution = (CGSize(width: abs(size.width), height:abs(size.height)),track.preferredTransform)
let width = resolution.0?.width
let height = resolution.0?.height
mediaAspectRatio = Double(width! / height! )
avPlayer.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none;
NotificationCenter.default.addObserver(self,
                                       selector: #selector(ViewController.didPlayToEnd),
                                       name: NSNotification.Name("AVPlayerItemDidPlayToEndTimeNotification"),
                                       object: avPlayer.currentItem)
let skScene = SKScene(size: CGSize(width: 1000 * mediaAspectRatio, height: 1000))
if resolution.1?.b != 0{
    skScene.size = CGSize(width: 1000, height: 1000 )
    skScene.zRotation = 1.5708
} else if resolution.1?.a != 1.0 {
    skScene.zRotation = 1.5708 * 2
}

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
if resolution.1?.b != 0{
     skNode.zRotation = 1.5708
} else if resolution.1?.a != 1.0 {
     skNode.zRotation = 1.5708 * 2
}
skNode.play()
skScene.addChild(skNode)
```

SCNNodeにコンテンツを貼り付けます。

```swift:ViewController.swift
virtualBackgroundNode.geometry = SCNPlane(width: size, height: size)
let material = SCNMaterial()
material.diffuse.contents = skScene
virtualBackgroundNode.geometry?.materials = [material]
virtualBackgroundNode.scale = SCNVector3(1.7  * mediaAspectRatio, 1.7, 1)
sceneView.scene.rootNode.addChildNode(node)
```

###バーチャル背景をカメラの前に置く

```swift:ViewController.swift
let cameraPosition = sceneView.pointOfView?.scale
let position = SCNVector3(cameraPosition!.x, cameraPosition!.y, cameraPosition!.z - 10)
virtualBackgroundNode.position = position
```

###バーチャル背景の位置を固定する
端末を動かすと、バーチャル背景がずれるので、カメラを追いかけるようにさせます。

<img width="200" alt="スクリーンショット 2020-07-21 0.24.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/f306cb6a-839e-e0b8-ec02-dda47a3ee60a.png">→<img width="200" alt="スクリーンショット 2020-07-21 0.29.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/4c2516a1-8e48-0436-7e66-a617d7eb1f82.png">

rendererメソッドでバーチャル背景にカメラ前ポジションを伝えます。

```swift:ViewController.swift
var frontOfCamera = SCNVector3()
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
     let position = SCNVector3(x: 0, y: 0, z: -10.0) // ノードの位置は、左右：0m 上下：0m　奥に50cm
        if let camera = sceneView.pointOfView {
            virtualBackgroundNode.position = camera.convertPosition(position, to: nil)
            virtualBackgroundNode.eulerAngles = camera.eulerAngles
        }
    }
```


###バーチャル背景の大きさを変えられるようにする
ユーザーがピンチ・ジェスチュアで背景の大きさをちょうど良いサイズに変えられるようにします。

```swift:ViewController.swift
var lastGestureScale:Float = 1
    
@objc func scenePinchGesture(_ recognizer: UIPinchGestureRecognizer) {
     switch recognizer.state {
     case .began:
         let logation = recognizer.location(in: sceneView)
         let hitResults = sceneView.hitTest(logation, options: [SCNHitTestOption.ignoreHiddenNodes:true])
         if hitResults.count > 0 {
             guard let node = hitResults.first?.node else {return}
             materialNode = node
         }
         lastGestureScale = 1
     case .changed:
         let newGestureScale: Float = Float(recognizer.scale)
         
         let diff = newGestureScale - lastGestureScale
         
         let currentScale = virtualBackgroundNode.scale
            
         virtualBackgroundNode.scale = SCNVector3Make(
             currentScale.x * (1 + diff),
             currentScale.y * (1 + diff),
             currentScale.z * (1 + diff)
         )
         lastGestureScale = newGestureScale
     default :break
     }
}
```
```swift:ViewController.swift
let pinch = UIPinchGestureRecognizer(
            target: self,
            action: #selector(type(of: self).scenePinchGesture(_:))
        )
        
pinch.delegate = self
sceneView.addGestureRecognizer(pinch)
```
ピンチ・ジェスチュアでSCNNodeを操作する方法は、天才プログラマーK-Boyさんのコードをお借りしました。ありがとうございます。

こんな感じで機械学習やARに関する情報を発信しています。
https://twitter.com/JackdeS11

rockyshikoku@gmail.com