Hunter
Hunter

Reputation: 1391

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?

Code:

                   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

    scnView.scene!.rootNode.addChildNode(Ball)
    scnView.scene!.rootNode.addChildNode(Ballbody)


    }
    CreateBall()

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)
                    Ball.runAction(SCNAction.sequence([moveAction]))


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

Upvotes: 1

Views: 1737

Answers (1)

James P
James P

Reputation: 4836

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.

UPDATE

Since using the code above I've noticed it doesn't always work, especially when the heights of the origin and target are different. From the same forum this function seems more reliable.

func calculateBestThrowSpeed(origin: SCNVector3, target: SCNVector3, timeToTarget:Float) -> SCNVector3 {

    let gravity:SCNVector3 = sceneView.scene!.physicsWorld.gravity

    let toTarget = target - origin
    var toTargetXZ = toTarget
    toTargetXZ.y = 0

    let y = toTarget.y
    let xz = toTargetXZ.length()

    let t = timeToTarget
    let v0y = y / t + 0.5 * gravity.length() * t
    let v0xz = xz / t


    var result = toTargetXZ.normalized()
    result *= v0xz
    result.y = v0y

    return result
}

Upvotes: 3

Related Questions