SCNPhysicsContactDelegate methods not being called

Hi there. Just bought the book a few days ago- mostly because I was mainly interested in Scenekit physics. I took a dive into Chapter 10 because it covers exactly what I was hoping to learn- how to make sense of the bitmask stuff for physicsBody collision detection. The Breaker tutorial was just what I needed!

Trying to apply what I learned to a sandbox project I set up (not using a .scn file, but programmatically setting physics properties) a small iPad experiment where I have a ball and a cube. When I tap the screen, a gesture handler applies force to the ball and sends it speeding toward the box. The ball hits the box and it behaves exactly as I had hoped- the box twirls through space until it hits the boundaries I’ve set, however, I’m not getting the didBeginContact call upon impact.

I looked at Apple’s docs, and a bunch of support sites and everything seems to suggest I still haven’t completely figured out the bitmasks yet. Can you please examine the attached code? I just copied the bitmask values for the ball from Breaker, and a brick from Breaker.

I can’t attach files yet, so please forgive the code pasted here:


// // GameViewController.swift //

import UIKit
import QuartzCore
import SceneKit

class GameViewController: UIViewController, SCNSceneRendererDelegate, SCNPhysicsContactDelegate {

private var cameraNode: SCNNode!
private var ballNode: SCNNode!
private var scnView: SCNView!
private var scnScene: SCNScene!

override func viewDidLoad() {
    super.viewDidLoad()
    
    // create a new scene
    scnScene = SCNScene()
    
    // retrieve the SCNView
    scnView = self.view as! SCNView
    
    // set the scene to the view
    scnView.scene = scnScene
    scnScene.physicsWorld.contactDelegate = self
    scnView.delegate = self

    // 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.blackColor()
    
    // create and add a camera to the scene
    cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    scnScene.rootNode.addChildNode(cameraNode)

    // place the camera
    cameraNode.position = SCNVector3(0, 20, 0)
    cameraNode.eulerAngles = SCNVector3(degreesToRadians(-90), 0, 0)
    
    // create and add a light to the scene
    let lightNode = SCNNode()
    lightNode.light = SCNLight()
    lightNode.light!.type = SCNLightTypeOmni
    lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
    scnScene.rootNode.addChildNode(lightNode)
    
    // create and add an ambient light to the scene
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light!.type = SCNLightTypeAmbient
    ambientLightNode.light!.color = UIColor.darkGrayColor()
    scnScene.rootNode.addChildNode(ambientLightNode)
    
    let barGeometry = SCNBox(width: 0.2, height: 0.2, length: 20, chamferRadius: 0)
    
    let bar1 = SCNNode(geometry: barGeometry)
    scnScene.rootNode.addChildNode(bar1)
    bar1.position = SCNVector3(10, 0, 0)
    bar1.name = "bar1"
    bar1.physicsBody = SCNPhysicsBody(type: .Static, shape: nil)
    bar1.physicsBody?.affectedByGravity = false
    bar1.physicsBody?.restitution = 1.0
    bar1.physicsBody?.allowsResting = false

    let bar2 = SCNNode(geometry: barGeometry)
    scnScene.rootNode.addChildNode(bar2)
    bar2.position = SCNVector3(-10, 0, 0)
    bar2.name = "bar2"
    bar2.physicsBody = SCNPhysicsBody(type: .Static, shape: nil)
    bar2.physicsBody?.affectedByGravity = false
    bar2.physicsBody?.restitution = 1.0
    bar2.physicsBody?.allowsResting = false
    
    let bar3 = SCNNode(geometry: barGeometry)
    scnScene.rootNode.addChildNode(bar3)
    bar3.position = SCNVector3(0, 0, -10)
    bar3.eulerAngles = SCNVector3(0, degreesToRadians(90), 0)
    bar3.name = "bar3"
    bar3.physicsBody = SCNPhysicsBody(type: .Static, shape: nil)
    bar3.physicsBody?.affectedByGravity = false
    bar3.physicsBody?.restitution = 1.0
    bar3.physicsBody?.allowsResting = false
    
    let bar4 = SCNNode(geometry: barGeometry)
    scnScene.rootNode.addChildNode(bar4)
    bar4.position = SCNVector3(0, 0, 10)
    bar4.eulerAngles = SCNVector3(0, degreesToRadians(90), 0)
    bar4.name = "bar4"
    bar4.physicsBody = SCNPhysicsBody(type: .Static, shape: nil)
    bar4.physicsBody?.affectedByGravity = false
    bar4.physicsBody?.restitution = 1.0
    bar4.physicsBody?.allowsResting = false
    
    let ballGeometry = SCNSphere(radius: 0.25)
    ballNode = SCNNode(geometry: ballGeometry)
    scnScene.rootNode.addChildNode(ballNode)
    ballNode.name = "ball"
    ballNode.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: nil)
    ballNode.physicsBody?.affectedByGravity = false
    ballNode.physicsBody?.mass = 1.0
    ballNode.physicsBody?.restitution = 1.0
    ballNode.physicsBody?.allowsResting = false
    ballNode.physicsBody?.angularDamping = 0.0
    ballNode.physicsBody?.damping = 0.0
    ballNode.physicsBody?.velocityFactor = SCNVector3(1, 0, 1)
    ballNode.physicsBody?.contactTestBitMask = 1
    ballNode.physicsBody?.categoryBitMask = 1
    ballNode.physicsBody?.collisionBitMask = -1

    let cubeGeometry = SCNBox(width: 0.75, height: 0.75, length: 0.75, chamferRadius: 0)
    
    let cube1 = SCNNode(geometry: cubeGeometry)
    scnScene.rootNode.addChildNode(cube1)
    cube1.position = SCNVector3(4, 0, 8)
    cube1.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: nil)
    cube1.physicsBody?.affectedByGravity = false
    cube1.physicsBody?.mass = 1.0
    cube1.physicsBody?.restitution = 1.0
    cube1.physicsBody?.allowsResting = false
    cube1.physicsBody?.angularDamping = 0.0
    cube1.physicsBody?.damping = 0.0
    cube1.physicsBody?.velocityFactor = SCNVector3(1, 0, 1)
    cube1.physicsBody?.contactTestBitMask = 4
    cube1.physicsBody?.categoryBitMask = 4
    cube1.physicsBody?.collisionBitMask = 1

    // add a tap gesture recognizer
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
    scnView.addGestureRecognizer(tapGesture)
}

func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact) {
    
    print("\(contact.nodeA.name) vs. \(contact.nodeB.name)")
    
}

func handleTap(gestureRecognize: UIGestureRecognizer) {
    print("tapped. applying force to ball...")
    ballNode.physicsBody?.applyForce(SCNVector3(4, 0, 8), impulse: true)
}

override func shouldAutorotate() -> Bool {
    return true
}

override func prefersStatusBarHidden() -> Bool {
    return true
}

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
        return .AllButUpsideDown
    } else {
        return .All
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Release any cached data, images, etc that aren't in use.
}

func degreesToRadians(degrees: Float) -> Float {
    
    return Float(degrees * Float(M_PI) / 180)
    
}

}

Figured it out. And it wasn’t the bitmasks. Maybe this is obvious to others, however to me it was not: If you add your a node to it’s parent BEFORE setting the physics properties- you are entering a world of pain and confusion.

When I have more time to contemplate this, I’ll go digging through the SceneKit docs to see if I missed an obvious situation in plain site.

Cheers.

1 Like

It is not obvious, I ran into the same issue and - frankly said - this is kind of unbelievable. From my point of view: A massive lack in the documentation. Thanks for posting your answer.