I am developing a game where users can use their finger to move objects, and when the user releases their finger the game checks for overlap between objects and move the moved object back to it's original position if there is overlap. I am using SCNScene.PhysicsWorld.contactTest(with: )
to check for overlap between my nodes. However, the method only works correctly when nodes have physicsbodys using .convexHull
, when I change it to .concavePolyHedron
everything stops working and no contact is reported. I have set the physicbodys to be static so I am at a loss of what to do.
Here is my code configuring the physicsbody for each node
parentNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: parentNode, options: [.type: SCNPhysicsShape.ShapeType.concavePolyhedron, .collisionMargin: 0.0, .scale: scaleVector]))
Here is my code calling contact test :
if let test = currentNode?.physicsBody {
let list = view.scene!.physicsWorld.contactTest(with: test) {
...
}
}
Edit: Here is a GameViewController of a basic game demonstrating my problem. The collision detection does not work with concavePolyHedron
but changing the option to convexHull
the code works correctly.
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
var dragGesture: UIPanGestureRecognizer?
var currentNode: SCNNode?
var beginningPos: SCNVector3?
var previousLoc: CGPoint?
var scnView: SCNView?
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene()
// 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
scnView = self.view as! SCNView
// set the scene to the view
scnView?.scene = scene
// 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
let node1 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
node1.position = SCNVector3(x: 0, y: 0, z: 0)
node1.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: node1, options:
[.type: SCNPhysicsShape.ShapeType.concavePolyhedron, .collisionMargin: 0.0,]))
scene.rootNode.addChildNode(node1)
let node2 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
node2.position = SCNVector3(x: 2, y: 2, z: 0)
node2.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: node2, options:
[.type: SCNPhysicsShape.ShapeType.concavePolyhedron, .collisionMargin: 0.0,]))
scene.rootNode.addChildNode(node2)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView?.addGestureRecognizer(tapGesture)
}
func setupGesture() {
dragGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(dragGesture!)
}
func removeGesture() {
if let dragGesture = dragGesture {
view.removeGestureRecognizer(dragGesture)
self.dragGesture = nil // Set the dragGesture to nil after removing it
}
}
@objc
func handlePan(_ sender: UIPanGestureRecognizer) {
var delta = sender.translation(in: self.view)
let loc = sender.location(in: self.view)
if sender.state == .began {
if let location = currentNode?.position {
beginningPos = location
previousLoc = loc
}
}
if sender.state == .changed {
if let currentNode {
delta = CGPoint.init(x: 2 * (loc.x - previousLoc!.x), y: 2 * (loc.y - previousLoc!.y))
currentNode.position = SCNVector3.init(currentNode.position.x + Float(delta.x * 0.0075), currentNode.position.y - Float(delta.y * (0.0075)), currentNode.position.z)
}
previousLoc = loc
}
if sender.state == .ended {
if let test = currentNode?.physicsBody {
let list = scnView?.scene?.physicsWorld.contactTest(with: test)
if list!.count > 0 {
if let beginningPos {
currentNode?.position = beginningPos
}
}
}
}
}
@objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView?.hitTest(p, options: [:])
if let node = currentNode {
removeGesture()
scnView?.allowsCameraControl = true
if let material = node.geometry?.firstMaterial {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.multiply.contents = UIColor.white
SCNTransaction.commit()
}
currentNode = nil
}
// check that we clicked on at least one object
if hitResults!.count > 0 {
// retrieved the first clicked object
currentNode = hitResults!.first?.node
scnView?.allowsCameraControl = false
setupGesture()
let pos = currentNode!.position
if let material = currentNode?.geometry?.firstMaterial {
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.multiply.contents = UIColor.red
SCNTransaction.commit()
}
}
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}
Indeed, the behaviour of that convexHull
and concavePolyhedron
are a little bit strange here.
I suppose, your goal is to place one cube like on top of the existing one (or where ever.) With your config the margin at the surface overlapped the real cube size by a tiny distance, so the cube flips to its original position, even when it did not touch the existing cube. I played around a little bit and this is my best result, I achieved:
let node1 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
node1.position = SCNVector3(x: 0, y: 0, z: 0)
node1.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node1.geometry!))
scene.rootNode.addChildNode(node1)
let node2 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
node2.position = SCNVector3(x: 2, y: 2, z: 0)
node2.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node2.geometry!))
scene.rootNode.addChildNode(node2)
As you can see, I do not force some special configuration to the physicsBody
. And this is the most close, I can move the cubes together now.
Using: SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node2.geometry!))
I hope it will help.