I'm using SceneKit's physicsBody system to detect collisions between objects, and getting some very strange results. To illustrate, I've got a minimal example that produces two spheres with kinematic physicsBodies and moves them in straight lines so that they briefly overlap.
I would expect to see physicsWorld(:didBeginContact:) called exactly once, when the spheres first overlap, and physicsWorld(:didEndContact:) called once when they stop overlapping. Instead, I'm seeing each function called 25 times!
Here's the code to reproduce: In Xcode 8.0, create a brand new project using the "Game" template. Replace the contents of GameViewController.swift with this:
import UIKit
import SceneKit
class GameViewController: UIViewController, SCNSceneRendererDelegate, SCNPhysicsContactDelegate {
var scnScene: SCNScene!
var scnView: SCNView!
var cameraNode: SCNNode!
var nodeA: SCNNode!
var nodeB: SCNNode!
var countBeginnings: Int = 0
var countEndings: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
setupScene()
setupNodes()
}
func setupScene() {
// create a new SCNScene and feed it to the view
scnView = self.view as! SCNView
scnScene = SCNScene()
scnView.scene = scnScene
// assign self as SCNView delegate to get access to render loop
scnView.delegate = self
// assign self as contactDelegate to handle collisions
scnScene.physicsWorld.contactDelegate = self
// create the camera and position it at origin
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Zero
scnScene.rootNode.addChildNode(cameraNode)
// tell scnView to update every frame
scnView.isPlaying = true
}
func setupNodes() {
// create two spheres with physicsBodies, one inside the other
nodeA = SCNNode()
nodeA.name = "Node A"
nodeA.geometry = SCNSphere(radius: 1.0)
nodeA.geometry!.firstMaterial?.diffuse.contents = UIColor.yellow.withAlphaComponent(0.6)
// expected behavior
// nodeA.position = SCNVector3(x: 0.0, y: -0.8, z: -10.0)
// weird behavior
nodeA.position = SCNVector3(x: 0.0, y: -0.9, z: -10.0)
nodeA.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: nodeA.geometry!, options: nil))
scnScene.rootNode.addChildNode(nodeA)
nodeB = SCNNode()
nodeB.name = "Node B"
nodeB.geometry = SCNSphere(radius: 0.5)
nodeB.geometry!.firstMaterial?.diffuse.contents = UIColor.red
nodeB.position = SCNVector3(x: -2.0, y: 0.0, z: -10.0)
nodeB.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: nodeB.geometry!, options: nil))
scnScene.rootNode.addChildNode(nodeB)
// node A can collide with node B but not the other way around
nodeA.physicsBody!.categoryBitMask = 2
nodeB.physicsBody!.categoryBitMask = 1
nodeA.physicsBody!.contactTestBitMask = 1
}
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
countBeginnings += 1
print("(" + String(countBeginnings) + ") " + contact.nodeA.name! + " began contact with " + contact.nodeB.name!)
}
func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
countEndings += 1
print("(" + String(countEndings) + ") " + contact.nodeA.name! + " ended contact with " + contact.nodeB.name!)
}
var frameNumber = 0
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
nodeB.position.x += 0.01
nodeB.position.y -= 0.01
}
}
There's other weirdness going on too. If I change the initial position of one of the spheres just a little bit, moving the y position from -0.9 to -0.8:
nodeA.position = SCNVector3(x: 0.0, y: -0.8, z: -10.0)
Now I get the expected behavior, one call to begin and one call to end! A slightly different collision angle results in totally different behavior.
Could this be a SceneKit bug or is this actually the expected behavior?