1
votes

I am working on a SceneKit project and I would like to rotate an object on three axises (world axis) but once I rotate the object, the next rotation is done relative to the new, rotated axis.

Imagine a cube that you should be able rotate on the absolute x and y and z axises using three buttons relative to the world.

As far as I know there are the following options for SCNAction:

  • SCNAction.rotateTo(x: 1.0, y: 0.0, z: 0.0, duration: 0.5, usesShortestUnitArc: true)
  • SCNAction.rotateTo(x: 1.0, y: 0.0, z: 0.0, duration: 0.5)
  • SCNAction.rotateBy(x: 1.0, y: 0.0, z: 0.0, duration: 0.5)
  • SCNAction.rotate(by: 0.0, around: SCNVector3, duration: 0.5)
  • SCNAction.rotate(toAxisAngle: SCNVector4, duration: 0.5)

Unfortunately none of these are absolute, all of them rely on the previous rotation.

Do you know any way to implement real absolute world axis rotation?

Thanks for the help in advance!

1

1 Answers

3
votes

To rotate a node around the world axis, multiply the nodes worldTransform with the rotation matrix.

I haven't found a solution with SCNAction's but using SCNTransaction is quite simple.

func rotate(_ node: SCNNode, around axis: SCNVector3, by angle: CGFloat, duration: TimeInterval, completionBlock: (()->())?) {
    let rotation = SCNMatrix4MakeRotation(angle, axis.x, axis.y, axis.z)
    let newTransform = node.worldTransform * rotation

    // Animate the transaction
    SCNTransaction.begin()
    // Set the duration and the completion block
    SCNTransaction.animationDuration = duration
    SCNTransaction.completionBlock = completionBlock

    // Set the new transform
    node.transform = newTransform

    SCNTransaction.commit()
}

This does not work if the node has a parent with a different transform, but we can fix this by converting the resulting transform to the parents coordinate space.

func rotate(_ node: SCNNode, around axis: SCNVector3, by angle: CGFloat, duration: TimeInterval, completionBlock: (()->())?) {
    let rotation = SCNMatrix4MakeRotation(angle, axis.x, axis.y, axis.z)
    let newTransform = node.worldTransform * rotation

    // Animate the transaction
    SCNTransaction.begin()
    // Set the duration and the completion block
    SCNTransaction.animationDuration = duration
    SCNTransaction.completionBlock = completionBlock

    // Set the new transform
    if let parent = node.parent {
        node.transform = parent.convertTransform(newTransform, from: nil)
    } else {
        node.transform = newTransform
    }

    SCNTransaction.commit()
}

You can try this in this Swift Playground.

I hope this is what you are looking for.