SceneKitにおける物体衝突検知を理解する


ARKitを触っていると3Dモデリングデータ同士の衝突検知をやりたくなる。
Appleの公式ドキュメントを読むと、

- 衝突イベントはSCNPhysicsContactDelegateのデリゲートメソッド内で処理すれば良い
- 物体同士の衝突はSCNPhysicsBodyの下記のパラメータで定義すれば良い
  1. categoryBitMask
  2. collisionBitMask
  3. contactTestBitMask

xxxBitMaskのそれぞれの定義をAppleの公式ドキュメントを調べてもよく分からなかったが、[このサイト](https://www.hackingwithswift.com/read/26/2/loading-a-level-categorybitmask-collisionbitmask-contacttestbitmask)の説明を読んだら良く理解できたので備忘録として残しておく。

### BitMaskの意味
| パラメータ | 意味 | 
|------------|-------------|
| categoryBitMask    | この物体の定義                               |
| collisionBitMask   | この物体と衝突したときに通過せず反発する物体の定義 |
| contactTestBitMask | この物体と衝突した時に通知が発信される物体の定義   |

### 実験

例えば、下のような定義があったとして

```swift
enum CollisionBitmask: Int {
    case box = 1
    case floor = 2
}
```

床に向かって正方形の物体が落ちるとした場合に床側の設定を

```swift
let floorBody = SCNPhysicsBody(type: .static, shape: nil)
floorBody.contactTestBitMask = CollisionBitmask.box.rawValue
floorBody.collisionBitMask = 0 // どの物体も対象ではない場合0をセットする
floorBody.categoryBitMask = CollisionBitmask.floor.rawValue
```

と定義すると、下のように物体が通過するが衝突のイベントは通知される。
![no_bound.gif](https://qiita-image-store.s3.amazonaws.com/0/200963/290d8594-54e3-5597-c992-9b37d2686789.gif "no_bound.gif")


また、床側の設定を

```swift
let floorBody = SCNPhysicsBody(type: .static, shape: nil)
floorBody.contactTestBitMask = CollisionBitmask.box.rawValue
floorBody.collisionBitMask = CollisionBitmask.box.rawValue
floorBody.categoryBitMask = CollisionBitmask.floor.rawValue
```

とすると、物体が衝突した後反発しその都度イベントの通知が行われる。
![bound.gif](https://qiita-image-store.s3.amazonaws.com/0/200963/4b1980db-65fe-694a-298b-1504b4afb43c.gif "bound.gif")


### 実際のコード

```swift
import UIKit
import QuartzCore
import SceneKit

class GameViewController: UIViewController, SCNPhysicsContactDelegate {
    
    enum CollisionBitmask: Int {
        case box = 1
        case floor = 2
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // create a new scene
        let scene = SCNScene()
        
        // phisicsWorld
        scene.physicsWorld.contactDelegate = self
        
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        
        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
        
        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        
        // retrieve the SCNView
        let scnView = self.view as! SCNView
        
        // set the scene to the view
        scnView.scene = scene
        
        // add flor to the scene
        let floor = SCNFloor()
        floor.reflectivity = 0.25
        let floorNode = SCNNode(geometry: floor)
        let floorBody = SCNPhysicsBody(type: .static, shape: nil)
        floorBody.contactTestBitMask = CollisionBitmask.box.rawValue
        floorBody.collisionBitMask = CollisionBitmask.box.rawValue
        floorBody.categoryBitMask = CollisionBitmask.floor.rawValue
        floorNode.physicsBody = floorBody
        floorNode.position.y = -3
        floorNode.name = "floor"
        scene.rootNode.addChildNode(floorNode)
        
        // add box to the scene
        let box = SCNBox(width: 2, height: 2, length: 2, chamferRadius: 0.1)
        let boxNode = SCNNode(geometry: box)
        let boxBody = SCNPhysicsBody(type: .dynamic, shape: nil)
        boxBody.mass = 1
        boxBody.categoryBitMask = CollisionBitmask.box.rawValue
        boxNode.physicsBody = boxBody
        boxNode.position.y = 8
        boxNode.rotation = SCNVector4Make(1, 1, 1, 1)
        boxNode.name = "box"
        scene.rootNode.addChildNode(boxNode)
        
        // allows the user to manipulate the camera
        scnView.allowsCameraControl = true
        
        // show statistics such as fps and timing information
        scnView.showsStatistics = true
        
        // configure the view
        scnView.backgroundColor = UIColor.black
        
    }
    
    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
        let firstNode = contact.nodeA
        let secondNode = contact.nodeB
        
        print(firstNode.name! + " hit to " + secondNode.name!)
    }
}


```

SceneKitを使ってARアプリ開発をしたい方向けに情報をまとめていますので、[こちら](https://www.nsunrise.work/p/index.html)もご参照ください。