How to make collision in scenekit - ios

i am trying to make collision of two objects, but "func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact)" is not being called.
my code is,
let carbonNode = SCNNode(geometry: carbonAtom())
carbonNode.position = SCNVector3Make(-6, 8, 0)
let coneAtomNode = SCNNode(geometry: coneAtom())
pinNode = coneAtomNode
pinNode.physicsBody = SCNPhysicsBody.dynamicBody()
pinNode.physicsBody?.restitution = 0.9;
pinNode.categoryBitMask = 0x4;
pinNode.physicsBody?.collisionBitMask = ~(0x4);
coneAtomNode.position = SCNVector3Make(-6, -15, 0)
scene.rootNode.addChildNode(coneAtomNode)
balloonNode = carbonNode
sceneView.scene = scene
sceneView.scene?.physicsWorld.contactDelegate = self
pinNode.runAction(SCNAction.repeatAction(SCNAction.moveTo(SCNVector3Make(-6, 10+5, 0), duration: 1.5), count: 1), completionHandler: {
})

You can't move "dynamic" bodies programmatically (i.e no action, no animation and no manual updates of position/rotation/scale). You can either move dynamic bodies with forces or use a kinematicBody instead.
Kinematic bodies behave just like static bodies but you can move them programmatically.
Also if you want to get physics contacts between two nodes, the two nodes need to have a physicsBody.

Related

Can't detect collision between rootNode and pointOfView child nodes in SceneKit / ARKit

In an AR app, I want to detect collisions between the user walking around and the walls of an AR node that I construct. In order to do that, I create an invisible cylinder right in front of the user and set it all up to detect collisions.
The walls are all part of a node which is a child of sceneView.scene.rootNode.
The cylinder, I want it to be a child of sceneView.pointOfView so that it would always follow the camera.
However, when I do so, no collisions are detected.
I know that I set it all up correctly, because if instead I set the cylinder node as a child of sceneView.scene.rootNode as well, I do get collisions correctly. In that case, I continuously move that cylinder node to always be in front of the camera in a renderer(updateAtTime ...) function. So I do have a workaround, but I'd prefer it to be a child of pointOfView.
Is it impossible to detect collisions if nodes are children of different root nodes?
Or maybe I'm missing something in my code?
The contactDelegate is set like that:
sceneView.scene.physicsWorld.contactDelegate = self so maybe this only includes sceneView.scene, but will exclude sceneView.pointOfView???
Is that the issue?
Here's what I do:
I have a separate file to create and configure my cylinder node which I call pov:
import Foundation
import SceneKit
func createPOV() -> SCNNode {
let pov = SCNNode()
pov.geometry = SCNCylinder(radius: 0.1, height: 4)
pov.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
pov.opacity = 0.3 // will be set to 0 when it'll work correctly
pov.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
pov.physicsBody?.isAffectedByGravity = false
pov.physicsBody?.mass = 1
pov.physicsBody?.categoryBitMask = BodyType.cameraCategory.rawValue
pov.physicsBody?.collisionBitMask = BodyType.wallsCategory.rawValue
pov.physicsBody?.contactTestBitMask = BodyType.wallsCategory.rawValue
pov.simdPosition = simd_float3(0, -1.5, -0.3) // this position only makes sense when setting as a child of pointOfView, otherwise the position will always be changed by renderer
return pov
}
Now in my viewController.swift file I call this function and set is as a child of either root nodes:
pov = createPOV()
sceneView.pointOfView?.addChildNode(pov!)
(Don't worry right now about not checking and unwrapping).
The above does not detect collisions.
But if instead I add it like so:
sceneView.scene.rootNode.addChildNode(pov!)
then collisions are detected just fine.
But then I need to always move this cylinder to be in front of the camera and I do it like that:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else {return}
let currentPosition = pointOfView.simdPosition
let currentTransform = pointOfView.simdTransform
let orientation = SCNVector3(-currentTransform.columns.2.x, -currentTransform.columns.2.y, -currentTransform.columns.2.z)
let currentPositionOfCamera = orientation + SCNVector3(currentPosition)
DispatchQueue.main.async {
self.pov?.position = currentPositionOfCamera
}
}
For completeness, here's the code I use to configure the node of walls in ViewController (they're built elsewhere in another function):
node?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: node!, options: nil))
node?.physicsBody?.isAffectedByGravity = false
node?.physicsBody?.mass = 1
node?.physicsBody?.damping = 1.0 // remove linear velocity, needed to stop moving after collision
node?.physicsBody?.angularDamping = 1.0 // remove angular velocity, needed to stop rotating after collision
node?.physicsBody?.velocityFactor = SCNVector3(1.0, 0.0, 1.0) // will allow movement only in X and Z coordinates
node?.physicsBody?.angularVelocityFactor = SCNVector3(0.0, 1.0, 0.0) // will allow rotation only around Y axis
node?.physicsBody?.categoryBitMask = BodyType.wallsCategory.rawValue
node?.physicsBody?.collisionBitMask = BodyType.cameraCategory.rawValue
node?.physicsBody?.contactTestBitMask = BodyType.cameraCategory.rawValue
And here's my physycsWorld(didBegin contact) code:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if contact.nodeA.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue || contact.nodeB.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue {
print("Begin COLLISION")
contactBeginLabel.isHidden = false
}
}
So I print something to the console and I also turn on a label on the view so I'll see that collision was detected (and the walls indeed move as a whole when it works).
So Again, it all works fine when the pov node is a child of sceneView.scene.rootNode, but not if it's a child of sceneView.pointOfView.
Am I doing something wrong or is this a limitation of collision detection?
Is there something else I can do to make this work, besides the workaround I already implemented?
Thanks!
Regarding the positioning of your cyliner:
instead to use the render update at time, you better use a position constraint for your cylinder node to move with the point of view. the result will be the same, as if it were a child of the point of view, but collisions will be detected, because you add it to the main rootnode scenegraph.
let constraint = SCNReplicatorConstraint(target: pointOfView) // must be a node
constraint.positionOffset = positionOffset // some SCNVector3
constraint.replicatesOrientation = false
constraint.replicatesScale = false
constraint.replicatesPosition = true
cylinder.constraints = [constraint]
There is also an influence factor you can configure. By default the influence is 100%, the position will immediatly follow.

Physics bodies keep falling away from the sprite node

I am trying to create nodes, that are put into an array, and then are added to the scene with their physics bodies.
Here is the code for creating the initial sprites:
let name = createTarget()
let targetNode = SKSpriteNode(imageNamed: name)
targetNode.name = name
chickenNodes.append(targetNode)
targetNode.position = generateRandomLocation()
let range = SKRange(lowerLimit: targetNode.position.y, upperLimit: targetNode.position.y)
let lockToCenter = SKConstraint.positionY(range)
targetNode.constraints = [lockToCenter]
if movingItems { animateTargets(targetNode) }
Once all of these nodes are in the array, I add them in didMove to a background node, fgNode, in the scene, like this:
for chicken in chickenNodes {
let texture = SKTexture(imageNamed: chicken.name!)
chicken.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
chicken.physicsBody?.isDynamic = true
chicken.physicsBody?.affectedByGravity = true
chicken.physicsBody?.allowsRotation = false
chicken.physicsBody?.linearDamping = 0.0
chicken.physicsBody?.restitution = 1.0
chicken.physicsBody?.friction = 0.0
fgNode.addChild(chicken)
}
When I view the physics through the scene, the physics bodies keep falling away from the sprite (as though they're responding to the gravity in the scene); and the sprite is just locked where it is. How do I ensure that the physicsBody sticks to the sprite?

How to calculate an SKPhysicsBody on the fly while animating a sprite?

In my original build, my sprites were static, so to make the collision physics as accurate as I could rather than use rectangleOfSize for SKPhysicsBody passing in the node, I used this tool to define a path SpriteKit's SKPhysicsBody with polygon helper tool.
However now I'm animating the sprites so that they move back and forth during gameplay (obviously my physics path remains static given the above) so my physicsbody no longer matches what the player sees on screen.
The helper tool seemed like a bit of hack that Apple would eventually fix in the API, has there been anything recent in SpriteKit that would help me out here so that I can pass in the node and define a precise physicsbody rather than the hard-coded approach? If not, any other alternatives?
Not sure how performant this is but you can pre-generate the physics bodies for each texture then animate the sprite along with its physicsBody using a custom action.
Here is an example:
func testAnimation() {
var frameTextures = [SKTexture]()
var physicsBodies = [SKPhysicsBody]()
for index in 1...8 {
// The animation has 8 frames
let texture = SKTexture(imageNamed: "guy\(index)")
frameTextures.append(texture)
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.affectedByGravity = false
physicsBodies.append(physicsBody)
}
let sprite = SKSpriteNode(texture: frameTextures[0])
let framesPerSecond = 16.0
let animation = SKAction.customAction(withDuration: 1.0, actionBlock: { node, time in
let index = Int((framesPerSecond * Double(time))) % frameTextures.count
if let spriteNode = node as? SKSpriteNode {
spriteNode.texture = frameTextures[index]
spriteNode.physicsBody = physicsBodies[index]
}
})
sprite.run(SKAction.repeatForever(animation));
sprite.position = CGPoint(x: 0, y: 0)
addChild(sprite)
}

Collision not detected correctly with multi-geometry shapes

I have a spaceship object which has a complex geometry, and since SceneKit's physics doesn't work with complex bodies, I have adopted a workaround: I'm using some basic shapes like cylinders and cubes so simulate the whole spaceship's body. In Blender I created a set of objects that approximate the shape of the spaceship:
Then when I load the scene I remove these objects, but use their geometry to construct a SCNPhysicsShape to be used as the physics body of the spaceship:
// First I retrieve all of these bodies, which I named "Body1" up to 9:
let bodies = _scene.rootNode.childNodes(passingTest: { (node:SCNNode, stop:UnsafeMutablePointer<ObjCBool>) -> Bool in
if let name = node.name
{
return name.contains("Body")
}
return false
})
// Then I create an array of SCNPhysicsShape objects, and an array
// containing the transformation associated to each shape
var shapes = [SCNPhysicsShape]()
var transforms = [NSValue]()
for body in bodies
{
shapes.append(SCNPhysicsShape(geometry: body.geometry!, options: nil))
transforms.append(NSValue(scnMatrix4:body.transform))
// I remove it from the scene because it shouldn't be visible, as it has
// the sole goal is simulating the spaceship's physics
body.removeFromParentNode()
}
// Finally I create a SCNPhysicsShape that contains all of the shapes
let shape = SCNPhysicsShape(shapes: shapes, transforms: transforms)
let body = SCNPhysicsBody(type: .dynamic, shape: shape)
body.isAffectedByGravity = false
body.categoryBitMask = SpaceshipCategory
body.collisionBitMask = 0
body.contactTestBitMask = RockCategory
self.spaceship.physicsBody = body
The SCNPhysicsShape object should contain all the shapes that I created in the Blender file. But when I test the program, the spaceship just behaves like an empty body, and collisions are not detected.
PS: my goal is only to detect collisions. I don't want the physics engine to simulate physics.
You want to use a concave shape for your ship. Example below. In order to use concave your body must be static or kinematic. Kinematic is ideal for animated objects.
No need for:
body.collisionBitMask = 0
A contact bit mask is all you will need for contact only.
The code below should work for what you are looking for. This is swift 3 code fyi.
let body = SCNPhysicsBodyType.kinematic
let shape = SCNPhysicsShape(node: spaceship, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
spaceship.physicsBody = SCNPhysicsBody(type: body, shape: shape)
Without seeing your bit mask, I am unable to tell if you are handling those properly. You should have something like:
spaceship.physicsBody.categoryBitMask = 1 << 1
Anything you want to contact your spaceship should have:
otherObject.physicsBody.contactTestBitMask = 1 << 1
This will only register a contact of the "otherObject" to the "spaceship" and not the "spaceship" to the "otherObject". So make sure your physics world contact delegate is handled that way or vise versa. No need for both objects to look for each other. That would be less efficient.
Example of delegate below:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
var tempOtherObject: SCNNode!
var tempSpaceship: SCNNode!
// This will assign the tempOtherObject and tempSpaceship to the proper contact nodes
if contact.nodeA.node == otherObject {
tempOtherObject = contact.nodeA
tempSpaceship = contact.nodeB
}else{
tempOtherObject = contact.nodeB
tempSpaceship = contact.nodeA
}
print("tempOtherObject = ", tempOtherObject)
print("tempSpaceship = ", tempSpaceship)
}

Swift - SpriteKit : Make sprites reacting to eachothers if added with different "addchild" parents

I've added several sprite (SKSpriteNode) in my Scene like this:
let NPuzzle_Texture = SKTexture(imageNamed: "SKTexture.png")
NPuzzle0 = SKSpriteNode(texture: NPuzzle_Texture)
NPuzzle1 = SKSpriteNode(texture: NPuzzle_Texture)
NPuzzle2 = SKSpriteNode(texture: NPuzzle_Texture)
NPuzzle0.position = CGPoint (x: 100, y:125)
NPuzzle0.position = CGPoint (x: 300, y:125)
NPuzzle0.position = CGPoint (x: 500, y:125)
background.addChild(NPuzzle0)
background.addChild(NPuzzle1)
background.addChild(NPuzzle2)
I've also added several sprites in a different texture:
let Grey_Back = SKTexture(imageNamed: "Grey_Back.png")
grey_back = SKSpriteNode(texture: Grey_back)
grey_back.position = CGPoint (x: 1024, y:125)
grey_back.alpha = 0.5
background.addChild(grey_back)
Now I add new sprites (SKSpriteNode) to precedent grey_back like this:
grey_back.addChild(new_sprite1)
grey_back.addChild(new_sprite2)
grey_back.addChild(new_sprite3)
When I try to see if positions of new_sprite intersects with NPuzzle sprites, nothing is happening. But if the new_sprite are added to the scene with:
background.addChild(new_sprite0)
background.addChild(new_sprite1)
background.addChild(new_sprite2)
it works. In fact it doesn't work if the sprites have been added with differents parents (background and grey_back). What I don't understand is grey_back is the child of background, so, it should work. Why it doesn't ?
Here is an example of the code to check if sprites intersects eachothers:
switch selectedNode.name {
case "new_sprite0":
if selectedNode.frame.intersects(NPuzzle0.frame) && (selectedNode.angle == 0) {
Thanks !
switch selectedNode.name {
let framePoint = CGPointMake(selectedNode.frame.origin.x, selectedNode.frame.origin.y)
let translatedPoint = selectedNode.parent!.convertPoint(framePoint, toNode:NPuzzle0.parent!)
let translatedFrame = CGRectMake(translatedPoint.x, translatedPoint.y, selectedNode.frame.size.width, selectedNode.frame.size.height)
if translatedFrame.intersects(NPuzzle0.frame) && (selectedNode.zRotation == 0)
{
print("ok")
}
Obviously, because frames are relative to parents, the node MUST be added to the scene. You can see I also used a forced unwrap for parent...

Resources