0

I'm working on a top-down space game built using Swift and SceneKit with the following setup:

SCNNode representing a spaceship

  • Rotation is constrained to the y axis; values range from -M_PI_2 to M_PI + M_PI_2
  • Movement is constrained to the x and z axes.

Game controller thumbstick input

  • Values range from -1.0 to 1.0 on the x and y axes.

When the game controller's thumbstick changes position, the spaceship should rotate using the physics body to match the thumbstick's radian.

The target radian of the thumbstick can be calculated with the following:

let targetRadian = M_PI_2 + atan2(-y, -x)

The current radian of the node can be obtained with the following:

let currentRadian = node.presentationNode.rotation.w * node.presentationNode.rotation.y

NSTimeInterval deltaTime provides the time in seconds since the last rotation calculation.

How can the node be rotated using angularVelocity, applyTorque, or another physics method to reach the targetRadian?

Kokaubeam
  • 646
  • 6
  • 8

1 Answers1

0

The difference between the targetRadian and the currentRadian ranged from 0.0 to -2π depending on the value of currentRadian. This equation will determine the shortest direction to turn, .Clockwise or .CounterClockwise, to reach the targetRadian:

let turnDirection = (radianDifference + (M_PI * 2)) % (M_PI * 2) < M_PI ? RotationDirection.CounterClockwise : RotationDirection.Clockwise

Using applyTorque, there is a possibility to over-rotate past the targetRadian resulting in a wobbling effect, like a compass magnetizing toward a point, as the rotation changes direction back and forth to reach the targetRadian. The following, while not a perfect solution, dampened the effect:

let turnDampener = abs(radianDifference) < 1.0 ? abs(radianDifference) : 1.0

The complete solution is thus:

enum RotationDirection: Double {
    case Clockwise = -1.0
    case CounterClockwise = 1.0
}

func rotateNodeTowardDirectionalVector(node: SCNNode, targetDirectionalVector: (x: Double, y: Double), deltaTime: NSTimeInterval) {
    guard abs(targetDirectionalVector.x) > 0.0 || abs(targetDirectionalVector.y) > 0.0 else { return }

    let currentRadian = Double(node.presentationNode.rotation.w * node.presentationNode.rotation.y)
    let targetRadian = M_PI_2 + atan2(-targetDirectionalVector.y, -targetDirectionalVector.x)

    let radianDifference = targetRadian - currentRadian

    let π2 = M_PI * 2
    let turnDirection = (radianDifference + π2) % π2 < M_PI ? RotationDirection.CounterClockwise : RotationDirection.Clockwise

    let absRadianDifference = abs(radianDifference)
    let turnDampener = absRadianDifference < 1.0 ? absRadianDifference : 1.0

    node.physicsBody?.applyTorque(SCNVector4Make(0, CGFloat(turnDirection.rawValue), 0, CGFloat(deltaTime * turnDampener)), impulse: true)
}
Kokaubeam
  • 646
  • 6
  • 8