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


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

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

xxxBitMaskのそれぞれの定義をAppleの公式ドキュメントを調べてもよく分からなかったが、このサイトの説明を読んだら良く理解できたので備忘録として残しておく。

BitMaskの意味

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

実験

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

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

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

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

と定義すると、下のように物体が通過するが衝突のイベントは通知される。

また、床側の設定を

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

とすると、物体が衝突した後反発しその都度イベントの通知が行われる。

実際のコード

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アプリ開発をしたい方向けに情報をまとめていますので、こちらもご参照ください。