Hunter Hunter - 1 month ago 12x
Swift Question

How to apply a SCNAction & An Impulse Force to a SCNNode

Hey I have a ball that gets moved by a force-applied. What im trying to get it to do is basically have the causal effect of gravity acting upon it while its moving through the air to its destination. basically when the "move to" action is playing gravity does not take affect so instead of slowly falling down to the ground it instead moves to its final position then it just falls straight down when the "move to" action stops. do to the gravity in the scene.

Im trying to get the ball to be thrown in an arc and land on the target?


func CreateBall() {
let BallScene = SCNScene(named: "art.scnassets/Footballs.dae")
Ball = BallScene!.rootNode.childNodeWithName("Armature", recursively: true)! //the Amature/Bones
Ballbody = BallScene!.rootNode.childNodeWithName("Ball", recursively: true)!

let collisionCapsuleRadius3 = CGFloat(0.01) // Width of physicsBody
let collisionCapsuleHeight3 = CGFloat(0.01) // Height of physicsBody
Ball.position = SCNVector3Make(Guy.position.x, Guy.position.y, Guy.position.z)
Ball.scale = SCNVector3Make(5, 5, 5)
Ball.rotation = SCNVector4Make(0.0,0.0,0.0,0.0) // x,y,z,w

Ball.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius3, height: collisionCapsuleHeight3), options:nil))
Ball.physicsBody?.affectedByGravity = true
Ball.physicsBody?.friction = 1 //
Ball.physicsBody?.restitution = 0 //bounceness of the object. 1.0 will boounce forever
Ball.physicsBody?.angularDamping = 1 // ability to rotate
Ball.physicsBody?.mass = 1
Ball.physicsBody?.rollingFriction = 1
Ball.physicsBody!.categoryBitMask = BitmaskCollision4
Ball.physicsBody?.contactTestBitMask = BitmaskCollision3 //| BitmaskCollision2
Ballbody.physicsBody?.collisionBitMask = BitmaskCollision2 | BitmaskCollision3 | BitmaskCollision//| BitmaskCollision2



now this is where the magic happens:

scnView.scene!.physicsWorld.gravity = SCNVector3(x: 0, y: -9.8, z: 0)

let location = SCNVector3(Guy2.presentationNode.position.x, 0.0, Guy2.presentationNode.position.z + Float(50) )
let moveAction = SCNAction.moveTo(location, duration: 2.0)

let forceApplyed = SCNVector3(x: 0.0, y: 100.0 , z: 0.0)
Ball.physicsBody?.applyForce(forceApplyed, atPosition: Ball.presentationNode.position, impulse: true)


Combining SCNActions and physics doesn't work, you need to use one or the other. Using physics you can calculate the exact force needed to propel your node to a target.

I have adapted a solution for Unity found here and utilised an SCNVector3 extension that makes some of the calculations much easier.

Basically you pass in an SCNNode that you want to throw, an SCNVector3 for the target and an angle (in radians) that you want the node to be thrown at. This function will then work out the force required to reach the target.

func shootProjectile() {
    let velocity = ballisticVelocity(ball, target: target.position, angle: Float(0.4))
    ball.physicsBody?.applyForce(velocity, impulse: true)

func ballisticVelocity(projectile:SCNNode, target: SCNVector3, angle: Float) -> SCNVector3 {
        let origin = projectile.presentationNode.position
        var dir = target - origin       // get target direction
        let h = dir.y                   // get height difference
        dir.y = 0                       // retain only the horizontal direction
        var dist = dir.length()         // get horizontal distance
        dir.y = dist * tan(angle)       // set dir to the elevation angle
        dist += h / tan(angle)          // correct for small height differences
        // calculate the velocity magnitude
        let vel = sqrt(dist * -scene.physicsWorld.gravity.y / sin(2 * angle))
        return dir.normalized() * vel * Float(projectile.physicsBody!.mass)

It is also important to set the damping of the physicsBody to 0, otherwise it will be affected by air resistance.

I’m not going to pretend to know exactly how this works, but Wikipedia has articles that explain all the maths behind it.