2
votes

I have been trying to set up a simple Scenekit scene with some physics so I could learn about how SCNPhysicsContactDelegate, categoryBitMask, collisionBitMask and the physicsWorld func work. Not sure if I need to set up a contactTestBitMask as well.

Learning about contact detection sent me down a long path of bitwise operators and the concept of bit masking. Adding in binary is fun! However, this is all still very foggy and I am trying to cobble together several tutorials I've found in both SpriteKit and SceneKit. This is the most comprehensive but it is in Obj-C and I don't understand it how to translate to Swift.

Here is what I have created. Any insights would be much appreciated. Can you see what I have set up incorrectly? I would like to have a simple Print statement occur when the red rolling ball hits the blue target. The floor, ramp and target are .static, while the rolling ball is .dynamic.

Animated Gif of Scene

import UIKit
import SceneKit


class ViewController: UIViewController, SCNPhysicsContactDelegate {
    //category bit masks for ball node and target node
    // ball = 0001 -> 1 and target = 0010 ->2
    let collisionRollingBall: Int = 1 << 0
    let collsionTarget: Int = 1 << 1

//declare variables
var sceneView: SCNView!
var cameraNode: SCNNode!
var groundNode: SCNNode!
var lightNode: SCNNode!
var rampNode: SCNNode!
var rollingBallNode: SCNNode!
var targetNode: SCNNode!

override func viewDidLoad() {
    super.viewDidLoad()
    //set up sceneview and scene. Define the physicsworld contact delegate as self
    sceneView = SCNView(frame: self.view.frame)
    sceneView.scene = SCNScene()
    sceneView.scene!.physicsWorld.contactDelegate = self
    self.view.addSubview(sceneView)

    //add floor
    let groundGeometry = SCNFloor()
    groundGeometry.reflectivity = 0
    let groundMaterial = SCNMaterial()
    groundMaterial.diffuse.contents = UIColor.greenColor()
    groundGeometry.materials = [groundMaterial]
    groundNode = SCNNode(geometry: groundGeometry)

    //add ramp
    let rampGeometry = SCNBox(width: 4, height: 1, length: 18, chamferRadius: 0)
    rampNode = SCNNode(geometry: rampGeometry)
    rampNode.position = SCNVector3(x: 0, y: 2.0, z: 1.0)
    rampNode.rotation = SCNVector4(1, 0, 0, 0.26)

    //add rolling ball
    let rollingBallGeometry = SCNSphere(radius: 0.5)
    let sphereMaterial = SCNMaterial()
    sphereMaterial.diffuse.contents = UIColor.redColor()
    rollingBallGeometry.materials = [sphereMaterial]
    rollingBallNode = SCNNode(geometry: rollingBallGeometry)
    rollingBallNode.position = SCNVector3(0, 6, -6)

    //add target box
    let targetBoxGeometry = SCNBox(width: 4, height: 1, length: 4, chamferRadius: 0)
    let targetMaterial = SCNMaterial()
    targetMaterial.diffuse.contents = UIColor.blueColor()
    targetBoxGeometry.materials = [targetMaterial]
    targetNode = SCNNode(geometry: targetBoxGeometry)
    targetNode.position = SCNVector3(x: 0, y: 0.5, z: 11.5)
    targetNode.rotation = SCNVector4(-1,0,0,0.592)

    //add a camera
    let camera = SCNCamera()
    self.cameraNode = SCNNode()
    self.cameraNode.camera = camera
    self.cameraNode.position = SCNVector3(x: 13, y: 5, z: 12)
    let constraint = SCNLookAtConstraint(target: rampNode)
    self.cameraNode.constraints = [constraint]
    constraint.gimbalLockEnabled = true

    //add a light
    let spotLight = SCNLight()
    spotLight.type = SCNLightTypeSpot
    spotLight.castsShadow = true
    spotLight.spotInnerAngle = 70.0
    spotLight.spotOuterAngle = 90.0
    spotLight.zFar = 500
    lightNode = SCNNode()
    lightNode.light = spotLight
    lightNode.position = SCNVector3(x: 0, y: 25, z: 25)
    lightNode.constraints = [constraint]

    //define physcis bodies
    let groundShape = SCNPhysicsShape(geometry: groundGeometry, options: nil)
    let groundBody = SCNPhysicsBody(type: .Static, shape: groundShape)
    groundNode.physicsBody = groundBody

    let rampShape = SCNPhysicsShape(geometry: rampGeometry, options: nil)
    let rampBody = SCNPhysicsBody(type: .Static, shape: rampShape)
    rampNode.physicsBody = rampBody

    let sphereShape = SCNPhysicsShape(geometry: rollingBallGeometry, options: nil)
    let sphereBody = SCNPhysicsBody(type: .Dynamic, shape: sphereShape)
    rollingBallNode.physicsBody?.categoryBitMask = collisionRollingBall
    rollingBallNode.physicsBody?.collisionBitMask = collsionTarget
    rollingBallNode.physicsBody = sphereBody

    let targetShape = SCNPhysicsShape(geometry: targetBoxGeometry, options: nil)
    let targetBody = SCNPhysicsBody(type: .Static, shape: targetShape)
    targetNode.physicsBody?.categoryBitMask = collsionTarget
    targetNode.physicsBody?.collisionBitMask = collisionRollingBall
    targetNode.physicsBody = targetBody

    //add nodes to view
    sceneView.scene?.rootNode.addChildNode(groundNode)
    sceneView.scene?.rootNode.addChildNode(rampNode)
    sceneView.scene?.rootNode.addChildNode(rollingBallNode)
    sceneView.scene?.rootNode.addChildNode(targetNode)
    sceneView.scene?.rootNode.addChildNode(self.cameraNode)
    sceneView.scene?.rootNode.addChildNode(lightNode)

}

func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact) {
print("contact")

 //   let contactMask = contact.nodeA.categoryBitMask | 
//contact.nodeB.categoryBitMask

    //if contactMask == collsionTarget | collisionRollingBall {
      //  print("The ball hit the target")
  //  }

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


}
1
UPDATE: I have managed to solve the problem. I realized that the collisionBitMasks I set up for the different nodes in the scenes were incomplete. I defined a more complete list of collision interactions for the various nodes in the scene. Also, I changed contact.nodeA.categoryBitMask to contact.nodeA.physicsBody!.categoryBitMask and everything worked. Not sure why that made a difference but it did. Do you know why?brickm

1 Answers

1
votes

I think you're having to reset the "catagoryBitMask" value in the delegate function because you're trying to set the "catagoryBitMask" and "collisionBitMask" values when the physicsBody is still nil.

rollingBallNode.physicsBody?.categoryBitMask = collisionRollingBall
rollingBallNode.physicsBody?.collisionBitMask = collsionTarget
rollingBallNode.physicsBody = sphereBody

Try putting that 3rd line 1st.