ARKitを触っていると3Dモデリングデータ同士の衝突検知をやりたくなる。
Appleの公式ドキュメントを読むと、
- 衝突イベントはSCNPhysicsContactDelegateのデリゲートメソッド内で処理すれば良い
- 物体同士の衝突はSCNPhysicsBodyの下記のパラメータで定義すれば良い
- categoryBitMask
- collisionBitMask
- 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アプリ開発をしたい方向けに情報をまとめていますので、こちらもご参照ください。