Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to limit the movement of two anchored lines so they swing continually like a pendulum

I have created two lines anchored to a sprite, which are 30˚ apart. I want both lines to swing left and right like a pendulum, always swinging from end to end (in such a way that they are swinging 45˚ to the left and right of their initial position). Please see the image below of what I am trying to achieve:

enter image description here

Below is the code for what I've been able to achieve:

extension Int {
  var degreesToRadians: Double { return Double(self) * .pi / 180 }
}
extension FloatingPoint {
  var degreesToRadians: Self { return self * .pi / 180 }
  var radiansToDegrees: Self { return self * 180 / .pi }
}

class GameScene: SKScene, SKPhysicsContactDelegate {

var anchorSprite = SKSpriteNode(imageNamed: "swingPin")
var armLeft = SKSpriteNode(imageNamed: "swingArm")
var armRight = SKSpriteNode(imageNamed: "swingArm")

override func didMove(to view: SKView) {


    self.physicsWorld.gravity = CGVector(dx: 0, dy: -1.8)
    self.physicsWorld.contactDelegate = self

    var tealBg = SKSpriteNode(imageNamed: "tealBg")
    tealBg.position = CGPoint(x: frame.midX, y: frame.midY)
    tealBg.zPosition = 10
    addChild(tealBg)

    anchorSprite.position = CGPoint(x: frame.midX, y: frame.midY + frame.midY/2)
    anchorSprite.zPosition = 20

    anchorSprite.physicsBody = SKPhysicsBody(rectangleOf: anchorSprite.frame.size)
    anchorSprite.physicsBody?.categoryBitMask = pinCategory
    anchorSprite.physicsBody?.isDynamic = false
    addChild(anchorSprite)

    armRight.anchorPoint = CGPoint(x: 0.5, y: 1)
    armRight.position = anchorSprite.position
    armRight.zPosition = 20
    armRight.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
    armRight.zRotation = CGFloat(Double(15).degreesToRadians)//CGFloat(Double.pi/6)
    armRight.physicsBody!.isDynamic = true

    addChild(armRight)

    armLeft.anchorPoint = CGPoint(x: 0.5, y: 1)
    armLeft.position = anchorSprite.position
    armLeft.zPosition = 20
    armLeft.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
    armLeft.zRotation = CGFloat(Double(-15).degreesToRadians)//CGFloat(-Double.pi/6)
    armLeft.physicsBody!.isDynamic = true
    addChild(armLeft)

    // Create joint between two objects
    //Pin joint
    var pinAndRightArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armRight.frame.maxY))
    self.physicsWorld.add(pinAndRightArmJoint)

    var pinAndLeftArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armLeft.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armLeft.frame.maxY))
    self.physicsWorld.add(pinAndLeftArmJoint)

    var fixArms = SKPhysicsJointFixed.joint(withBodyA: armLeft.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint.zero)
    self.physicsWorld.add(fixArms)

    pinAndRightArmJoint.shouldEnableLimits = true
    pinAndRightArmJoint.lowerAngleLimit = CGFloat(Double(-60).degreesToRadians)
    pinAndRightArmJoint.upperAngleLimit = CGFloat(Double(60).degreesToRadians)

 }


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    //armRight.physicsBody?.angularVelocity = -100.0

     let seq = SKAction.sequence([
        SKAction.rotate(byAngle: CGFloat(Double(45).degreesToRadians), duration: 0.5),
        SKAction.rotate(byAngle: CGFloat(Double(-45).degreesToRadians), duration: 1.0),
        SKAction.rotate(byAngle: CGFloat(Double(45).degreesToRadians), duration: 1.0),
        SKAction.rotate(byAngle: CGFloat(Double(-45).degreesToRadians), duration: 1.0),
        SKAction.rotate(byAngle: CGFloat(Double(45).degreesToRadians), duration: 1.0),
        SKAction.rotate(byAngle: CGFloat(Double(-45).degreesToRadians), duration: 1.0)
])
    armRight.run(seq)
}

From the code above I set lower and upper angle limits and tried running an action, but this just makes the lines inch a bit sideways in a very unrealistic manner. I also tried applying angular velocity on the physics body, but this just made it swing briefly at an inconsistent speed (I need it to swing consistently from one end to the other).

NB

Since I need it to swing from end to end each time, I need the swing cycle to be consistent each time, not necessarily constant. A cycle would generally swing faster as the lines move to the center and slow down a bit when changing from one direction to another. That is the kind of feel I want for movement.

like image 957
iGetIt Avatar asked May 29 '17 11:05

iGetIt


People also ask

Can you explain to me the swinging motion?

Swings work by converting potential energy into kinetic energy, then kinetic energy back into potential energy, over and over again. The kinetic energy is the fast part of swinging; it's the speed you have as you rush back and forth. Potential energy is the high part of swinging.

What type of motion is a swing?

The motion of a swing is an example of rectilinear motion.

What did you observe on the motion of a swing?

Answer: The motion of a swing is Oscillatory Motion. Explanation: The swing, if pushed, follows "to and fro" motion which means the the swing returns to the same place where it starts it's movement. It repeats it's movement periodically.


2 Answers

Here is the practical answer:

Replace addChild(armRight) with anchorSprite.addChild(armRight). Replace addChild(armLeft) with anchorSprite.addChild(armLeft). Delete armRight.position = anchorSprite.position and delete armLeft.position = anchorSprite.position. Also, unless you use the physics joints for other movements in your code, delete all of them, as my solution does not require joints.

Now your arms are children of anchorSprite and subjected to its' coordinate system. If you want to rotate both arms in the same direction at the same time, you can run a rotation action on the anchorSprite. If you want the arms to rotate in different directions you will have to run the rotate action on each arm separately. For either situation, you can use this handy function I made just for the bounty on this question :-P

func runPendulumRotationOnNode(_ node:SKNode, withAngle angle:CGFloat, period:TimeInterval, key:String) {
    let initialRotate = SKAction.rotate(byAngle: angle/2, duration: period/2)
    initialRotate.timingMode = .easeOut
    let rotate = SKAction.rotate(byAngle: angle, duration: period)
    rotate.timingMode = .easeInEaseOut
    let rotateForever = SKAction.repeatForever(SKAction.sequence([rotate.reversed(), rotate]))
    let rotateSequence = SKAction.sequence([initialRotate, rotateForever])
    node.run(rotateSequence, withKey:key)
}

I have tested it, and it works great! You can call it like this to rotate both arms together:

runPendulumRotationOnNode(anchorSprite, withAngle:CGFloat.pi/2, period:0.5, key:"")

Or to rotate the arms in opposite directions, you can use it like this:

runPendulumRotationOnNode(armRight, withAngle:CGFloat.pi/2, period:0.5, key:"")
runPendulumRotationOnNode(armLeft, withAngle:-CGFloat.pi/2, period:0.5, key:"")

Some minor notes, notice how I use CGFloat.pi, an easy constant for π. Also this function assumes the pendulum is starting at its midpoint in the rotation, so π/2 (90 degrees) will rotate the arms π/4 (45 degrees) in either direction.

like image 80
mogelbuster Avatar answered Oct 31 '22 02:10

mogelbuster


A swift 5 extension version for the good mogelbuster code :

extension SKAction {
    class func pendulum(withAngle angle:CGFloat, period:TimeInterval, key:String) -> SKAction {
        let initialRotate = SKAction.rotate(byAngle: angle/2, duration: period/2)
        initialRotate.timingMode = .easeOut
        let rotate = SKAction.rotate(byAngle: angle, duration: period)
        rotate.timingMode = .easeInEaseOut
        let rotateForever = SKAction.repeatForever(SKAction.sequence([rotate.reversed(), rotate]))
        return SKAction.sequence([initialRotate, rotateForever])
    }
}

Usage:

let pendulum = SKAction.pendulum(withAngle: CGFloat.pi/2, period: 0.5, key: "pendulum")
self.mynode.run(pendulum)
like image 45
Alessandro Ornano Avatar answered Oct 31 '22 00:10

Alessandro Ornano