I need to detect when the virtual objects gets in contact with the real world object using ARKit.
Is there any way to find it out?
At first you need to create a collision category struct that conforms to OptionSet
protocol and has properties with bitset types:
import ARKit
struct Category: OptionSet {
let rawValue: Int
static let sphereCategory = Category(rawValue: 1 << 0)
static let targetCategory = Category(rawValue: 1 << 1)
}
Then set a physics delegate
to SCNPhysicsContactDelegate
protocol inside lifecycle method:
class ViewController: UIViewController, SCNPhysicsContactDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
sceneView.scene = SCNScene()
sceneView.scene.physicsWorld.contactDelegate = self
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal]
sceneView.session.run(config)
}
}
SCNPhysicsContactDelegate
contains 3 optional physicsWorld() methods (we'll use 1st later):
public protocol SCNPhysicsContactDelegate: NSObjectProtocol {
optional func physicsWorld(_ world: SCNPhysicsWorld,
didBegin contact: SCNPhysicsContact)
optional func physicsWorld(_ world: SCNPhysicsWorld,
didUpdate contact: SCNPhysicsContact)
optional func physicsWorld(_ world: SCNPhysicsWorld,
didEnd contact: SCNPhysicsContact)
}
After this define categoryBitMask
and collisionBitMask
for sphere collider:
fileprivate func createSphere() -> SCNNode {
var sphere = SCNNode()
sphere.geometry = SCNSphere(radius: 0.1)
sphere.physicsBody = .init(type: .kinematic,
shape: .init(geometry: sphere.geometry!,
options: nil))
sphere.physicsBody?.isAffectedByGravity = true
sphere.physicsBody?.categoryBitMask = Category.sphereCategory.rawValue
sphere.physicsBody?.collisionBitMask = Category.targetCategory.rawValue
sphere.physicsBody?.contactTestBitMask = Category.targetCategory.rawValue
return sphere
}
...and define bit-masks in reversed order for real world detected plane:
fileprivate func visualizeDetectedPlane() -> SCNNode {
var plane = SCNNode()
plane.geometry = SCNPlane(width: 0.7, height: 0.7)
plane.physicsBody = .init(type: .kinematic,
shape: .init(geometry: plane.geometry!,
options: nil))
plane.physicsBody?.isAffectedByGravity = false
plane.physicsBody?.categoryBitMask = Category.targetCategory.rawValue
plane.physicsBody?.collisionBitMask = Category.sphereCategory.rawValue
plane.physicsBody?.contactTestBitMask = Category.sphereCategory.rawValue
return plane
}
And only when you've added your SCNPlanes to real-world detected planes and append an SCNSphere to your SCNScene, you could use
physicsWorld(_:didBegin:)
instance method to detect collisions:
func physicsWorld(_ world: SCNPhysicsWorld,
didBegin contact: SCNPhysicsContact) {
if contact.nodeA.physicsBody?.categoryBitMask ==
Category.targetCategory.rawValue |
contact.nodeB.physicsBody?.categoryBitMask ==
Category.targetCategory.rawValue {
print("BOOM!")
}
}