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)
}
}