Physics Shape are not matching with Objects' shape when in contact - swift

I have two objects and I want something to happen when they are both in contact. One object is an SCNSphere and the other one SCNCylinder. The only issue is that when I throw the ball at the cylinder, they seem to be touching even if there is a gap. If I throw it very far away then it works as expected. How can I make the contacts accurate and do lot leave any gaps? It looks like the PhysicsShape does not match my object's shape. I want it to be accurate. Any help?
My code for cylinder:
let scorer = SCNCylinder(radius: 0.02, height: 0.01)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "basketballSkin.png")
scorer.materials = [material]
let scorerNode = SCNNode(geometry: scorer)
scorerNode.worldPosition = SCNVector3(x: 0, y: -1.35, z: -1.4)
let physicsShapesc = SCNPhysicsShape(node: scorerNode, options:[SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
let physicsBodysc = SCNPhysicsBody(type: .static, shape: physicsShapesc)
scorerNode.physicsBody = physicsBodysc
scorerNode.physicsBody?.categoryBitMask = BodyType.scorer.rawValue
scorerNode.physicsBody?.collisionBitMask = BodyType.scorer.rawValue | BodyType.ball.rawValue
scorerNode.physicsBody?.contactTestBitMask = BodyType.scorer.rawValue | BodyType.ball.rawValue
My code for ball:
let ball = SCNSphere(radius:0.04)
// Bucketnode.scale = SCNVector3Make(0.2,0.2,0.2);
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "basketballSkin.png")
ball.materials = [material]
let ballNode = SCNNode(geometry: ball)
ballNode.position = cameraPosition
let physicsShape = SCNPhysicsShape(node: ballNode, options:nil)
let physicsBody = SCNPhysicsBody(type: .dynamic, shape: physicsShape)
ballNode.physicsBody = physicsBody
let forceVector:Float = 2.7
ballNode.physicsBody?.applyForce(SCNVector3Make(cameraPosition.x * forceVector, cameraPosition.y * forceVector, cameraPosition.z*forceVector), asImpulse: true)
ballNode.physicsBody?.categoryBitMask = BodyType.ball.rawValue
//ballNode.physicsBody?.collisionBitMask = BodyType.ball.rawValue | BodyType.scorer.rawValue
ballNode.physicsBody?.contactTestBitMask = BodyType.ball.rawValue | BodyType.scorer.rawValue
sceneView.scene.rootNode.addChildNode(ballNode)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { // change 2 to desired number of seconds
ballNode.removeFromParentNode()
}

Taking in account the definition for the SCNPhysicsShape.ShapeType structure regards a geometry "Values for the type key specifying the level of detail that SceneKit uses when creating a physics shape based on a geometry." the option set would not probably be working as expected:
let physicsShapesc = SCNPhysicsShape(node: scorerNode, options:[SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
Besides that, how fast the ball is moving towards the bucket? the "concavePolyhedron" seems to be the most demanding type for rendering

Related

ARKit and .dae file , strange space between object

on my study project using ARKit and sceneKit I'm facing a strange behaviour when using .dae file.
i have created 2 object a cube and a plane with the following methods (both have physics):
func loadBox() {
let ballGeometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0.02)
let mat = SCNMaterial()
mat.diffuse.contents = UIImage(named: "ball")
ballGeometry.materials = [mat]
//Physic
let physSchape = SCNPhysicsShape(geometry: ballGeometry, options: nil)
let ballPhysic = SCNPhysicsBody(type: .dynamic, shape: physSchape)
ballPhysic.mass = 100
ballPhysic.friction = 0.8
ballPhysic.isAffectedByGravity = true
let nodeBall = SCNNode(geometry: ballGeometry)
nodeBall.name = "ball"
nodeBall.physicsBody = ballPhysic
box = nodeBall
}
func loadPlane(){
let geometry = SCNPlane(width: 1, height: 1)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "texture")
geometry.materials = [material]
// physics
let physSchape = SCNPhysicsShape(geometry: geometry, options: nil)
let planePhysic = SCNPhysicsBody(type: .static, shape: physSchape)
planePhysic.restitution = 0.0
planePhysic.friction = 1.0
planePhysic.isAffectedByGravity = true
let nodo = SCNNode(geometry: geometry)
nodo.eulerAngles.x = -.pi / 2
nodo.physicsBody = planePhysic
plane = nodo
}
when I place the box over the plane in the scene they are correctly positioned and touching each other as you can see from the following picture:
Here the problem:
if i use a model downloaded from internet as .dae file of the plane, when insert it in the scene it create a strange space between the box and the plane (see picture)
method I use to load the scn file.
// load the elicopterBase
func loadElicopterBase(){
guard let scene = SCNScene(named: "Model.scnassets/base.scn") else {fatalError()}
guard let nodo = scene.rootNode.childNode(withName: "Plane", recursively: true) else {fatalError()}
// physics
let physSchape = SCNPhysicsShape(node: nodo, options: nil)
let planePhysic = SCNPhysicsBody(type: .static, shape: physSchape)
planePhysic.restitution = 0.0
planePhysic.friction = 1.0
planePhysic.isAffectedByGravity = true
nodo.physicsBody = planePhysic
nodo.name = "base"
DispatchQueue.main.async {
self.eliporto = nodo
}
}
Look like the .dae file have some sort of invisible boundingBox around it.
What or where I can look to find a solution to this.
here some picture of the .dae file in Xcode
Make your floor plane of type "concavePolyhedron" like so:
let body = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: yourGeometry, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron]))

Adding SCNLight inside SK3DNode

I am creating an SK3DNode inside an SKScene:
let ball: SK3DNode = {
let scnScene = SCNScene()
let ballGeometry = SCNSphere(radius: 200)
let ballNode = SCNNode(geometry: ballGeometry)
ballNode.position = SCNVector3(0, 0, 0)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "wall")
ballGeometry.materials = [material]
let light = SCNLight()
light.type = .omni
light.color = UIColor.white
let lightNode = SCNNode()
lightNode.light = light
scnScene.rootNode.addChildNode(ballNode)
scnScene.rootNode.addChildNode(lightNode)
let node = SK3DNode(viewportSize: CGSize(width: 1000, height: 1000))
node.scnScene = scnScene
node.autoenablesDefaultLighting = false
return node
}()
However, the sphere renders black. Tried it with or without the material. Is there something I am missing?
The sphere is manually placed at (0, 0, 0) and so is the light (default value). This means that the light is placed inside the sphere. This means that the surface of the sphere is facing away from the light source and thus isn't lit.

How does SCNIKConstraint work?

I have the following code (this can be run by replacing the standard ViewController code in the Game base project for macOS):
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
/* RELEVANT CODE BEGINS */
let boxGeo = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
let boxMaterial = SCNMaterial()
boxMaterial.diffuse.contents = NSColor.gray
boxGeo.firstMaterial = boxMaterial
let boxNode = SCNNode(geometry: boxGeo)
scene.rootNode.addChildNode(boxNode)
boxNode.name = "box0"
let sphereGeo = SCNSphere(radius: 0.5)
let sphereMaterial = SCNMaterial()
sphereMaterial.diffuse.contents = NSColor.green
sphereGeo.firstMaterial = sphereMaterial
let sphereNode = SCNNode(geometry: sphereGeo)
boxNode.addChildNode(sphereNode)
sphereNode.name = "sphere0"
sphereNode.constraints = [SCNConstraint]()
let distance = SCNDistanceConstraint(target: boxNode)
distance.minimumDistance = 2.0
distance.maximumDistance = 5.0
sphereNode.constraints?.append(distance)
let ik = SCNIKConstraint.inverseKinematicsConstraint(chainRootNode: boxNode)
sphereNode.constraints?.append(ik)
let anim = CABasicAnimation(keyPath: "targetPosition.y")
anim.fromValue = -2.0
anim.toValue = 2.0
anim.duration = 1
anim.autoreverses = true
anim.repeatCount = .infinity
ik.addAnimation(anim, forKey: nil)
/* RELEVANT CODE ENDS */
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = NSColor.black
From what I can gather from the documentation, the animation (and yes, the scene kit view animation setting is set to both play and loop in IB) should move the sphere as close as possible to the points 2.0 and -2.0 on the y-axis by rotating the cube. However, the sphere simply stays stationary. I have also tried setting the initial position of the sphere and cube by manipulating their position vectors directly instead of via the distance constraint, but again the animation did nothing.
Additionally, I have attempted to use the distance constraint in combination with the box having a lookAt constraint to make it rotate to constantly look at the sphere - these caused the rendering of the box and sphere to completely freak out.
I feel as though maybe I am missing something in the documentation here, such as another constraint or some kind of transform matrix to setup some kind of initial value. But I have encountered some other issues with constraints, animations and skeletons that is making me begin to believe that there is either a bug or some undocumented aspects of SceneKit.
You have added the sphereNode as child of the boxNode. If you move the boxNode all childs are also moved and the constraint has no effect.

How to applyForce to an SCNNode so that it goes in the direction of another SCNNode

How can I use applyForce() to an SCNNode so that it goes in the direction of another SCNNode?
I do know the positions of both nodes.
I am aware that I can use SCNAction.move(to: position, duration: 3), but that doesn't meet my needs - as I don't want every node taking the same duration to get to the destination. I want all nodes to travel the same speed.
The goal is to have a bullet leave one node and go to towards another node. No need to update path (like a real bullet).
let bullet = SCNNode(geometry: SCNSphere(radius : 0.1))
bullet.position = enemy.position
bullet.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: bullet, options: nil))
bullet.physicsBody?.isAffectedByGravity = false
// player.position doesn't do what I thought it would...
let vector = SCNVector3(player.position.x * 100, player.position.y * 100, player.position.z * 100)
bullet.physicsBody?.applyForce(vector, asImpulse: true)
bullet.runAction( SCNAction.sequence([
SCNAction.wait(duration: 5),
SCNAction.removeFromParentNode()]
))
self.sceneView.scene.rootNode.addChildNode(bullet)
While I'm determined to find this answer I have thought of a way to keep the speed of the bullet constant no matter the distance of the two nodes. I wanted to provide this here in case someone runs into a similar issue but I didn't want to provide this as the answer - as it's still using the move(to:) instead of applyForce().
let bullet = SCNNode(geometry: SCNSphere(radius: 0.1))
bullet.geometry?.firstMaterial?.diffuse.contents = UIColor.green
let enemy = timer.userInfo as! Enemy
bullet.position = enemy.position
bullet.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: bullet, options: nil))
bullet.physicsBody?.isAffectedByGravity = false
bullet.physicsBody?.categoryBitMask = BitMaskCategory.enemyBullet.rawValue
bullet.physicsBody?.contactTestBitMask = BitMaskCategory.player.rawValue
let lineX = playerNode.position.x - bullet.position.x
let lineY = playerNode.position.y - bullet.position.y
let lineZ = playerNode.position.z - bullet.position.z
let distance = sqrtf(lineX*lineX + lineY*lineY + lineZ*lineZ)
let fireBulletAction = SCNAction.move(to: playerNode.position, duration: (TimeInterval(distance / BASE_BULLET_SPEED)))
bullet.runAction(SCNAction.group([
fireBulletAction,
SCNAction.sequence([
SCNAction.wait(duration: 5),
SCNAction.removeFromParentNode()]
)]
))
self.sceneView.scene.rootNode.addChildNode(bullet)

ARKit – Ball is not passing through "SCNTorus" hole

I am stuck with an issue on ARKit and I need help.
I am making a little demo where I am placing a simple SCNTorus geometry in the scene and I am trying to throw a small ball (SCNSphere) into the torus hole. The problem is that the ball is bouncing in the middle rather than pass through.
There is the code for the torus:
let ring = SCNTorus(ringRadius: 0.4, pipeRadius: 0.1)
ring.ringSegmentCount = 100
let ringMaterial = SCNMaterial()
ringMaterial.diffuse.contents = UIImage(named: "art.scnassets/Ball/BeachBallColor.jpg")
ring.materials = [ringMaterial]
let ringNode = SCNNode()
ringNode.position = SCNVector3(x: location.worldTransform.columns.3.x,
y: location.worldTransform.columns.3.y + 0.8,
z: location.worldTransform.columns.3.z)
ringNode.geometry = ring
let body = SCNPhysicsBody(type: SCNPhysicsBodyType.kinematic,
shape: nil)
body.categoryBitMask = CollisionTypes.wall.rawValue
body.collisionBitMask = CollisionTypes.beachball.rawValue
// body.contactTestBitMask = CollisionTypes.beachball.rawValue
body.isAffectedByGravity = false
body.mass = 0.5
ringNode.physicsBody = body
sceneView.scene.rootNode.addChildNode(ringNode)
And for the ball :
let node = SCNNode(geometry: sphere!)
node.renderingOrder = 10
let body = SCNPhysicsBody(type: SCNPhysicsBodyType.dynamic,shape: nil)
body.categoryBitMask = CollisionTypes.beachball.rawValue
body.collisionBitMask = CollisionTypes.solid.rawValue|CollisionTypes.wall.rawValue|CollisionTypes.beachball.rawValue
// body.contactTestBitMask = CollisionTypes.fireball.rawValue|CollisionTypes.wall.rawValue
body.isAffectedByGravity = true
body.mass = 0.5
body.restitution = 0.75
body.damping = 0.1
body.friction = 0.8
node.physicsBody = body
The code you use to create the physicsbody (body type kinametic and shape nil) results in a simplified “convex hull” representation of the geometry. Simply put, the geometry you see is a torus, but the geometry used for the collision detection is not.
This line of (obj c) code is actually from one of the Apple sample code projects:
_torus.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeStatic shape:[SCNPhysicsShape shapeWithGeometry:_torus.geometry options: #{SCNPhysicsShapeTypeKey : SCNPhysicsShapeTypeConcavePolyhedron} ] ];
In other words, you need to create a static type body and a shape based on the geometry itself using the SCNPhysicsShapeTypeConcavePolyhedron key value (which only works for static bodies) to end up with a more accurate representation of the torus geometry as the physics body.
For more details see: https://developer.apple.com/documentation/scenekit/scnphysicsshape
Thanks! it work!
there is the code :
let ring = SCNTorus(ringRadius: 0.4, pipeRadius: 0.1)
ringNode.geometry = ring
let shapeOptions = [ SCNPhysicsShape.Option.type : SCNPhysicsShape.ShapeType.concavePolyhedron]
let physicShape = SCNPhysicsShape(geometry: ring, options: shapeOptions)
let body = SCNPhysicsBody(type: SCNPhysicsBodyType.kinematic,
shape: physicShape)
body.categoryBitMask = CollisionTypes.wall.rawValue
body.collisionBitMask = CollisionTypes.beachball.rawValue
body.isAffectedByGravity = false
body.mass = 0.5
ringNode.physicsBody = body