Search code examples
iosswiftscenekit

How to convert between rotation and euler angles in SceneKit


I have the following helper functions that works fine:

func rotationToEuler(rotation: SCNVector4) -> SCNVector3 {
  let node = SCNNode()
  node.rotation = rotation
  return node.eulerAngles
}

func eulerToRotation(eulerAngles: SCNVector3) -> SCNVector4 {
  let node = SCNNode()
  node.eulerAngles = eulerAngles
  return node.rotation
}

However, it creates the dummy node for conversion, which feels a bit "hacky" because we are not doing anything else with the node (e.g. adding it to the scene, etc).

I am wondering if there's a better way to do so?


Solution

  • You can use the Spatial API.

    import Spatial
    import simd
    
    func rotationToEuler(rotation: SCNVector4) -> SCNVector3 {
        SCNVector3(
            Rotation3D(angle: .radians(Double(rotation.w)), axis: .init(x: rotation.x, y: rotation.y, z: rotation.z))
                .eulerAngles(order: .xyz)
                .angles
        )
    }
    
    func eulerToRotation(eulerAngles: SCNVector3) -> SCNVector4 {
        let rotation = Rotation3D(eulerAngles: .init(angles: [eulerAngles.x, eulerAngles.y, eulerAngles.z], order: .xyz))
        return SCNVector4(
            simd_double4(rotation.axis.vector, rotation.angle.radians)
        )
    }
    

    Note that simd isn't strictly necessary here. I just used it so that I don't have to convert between floats and doubles 4 times in eulerToRotation.


    For iOS 16:

    func rotationToEuler(rotation: SCNVector4) -> SCNVector3 {
        SCNVector3(
            Rotation3D(angle: .init(radians: Double(rotation.w)), axis: .init(x: rotation.x, y: rotation.y, z: rotation.z))
                .eulerAngles(order: .pitchYawRoll)
                .angles
        )
    }
    
    func eulerToRotation(eulerAngles: SCNVector3) -> SCNVector4 {
        let rotation = Rotation3D(eulerAngles: .init(angles: [eulerAngles.x, eulerAngles.y, eulerAngles.z], order: .pitchYawRoll))
        return SCNVector4(
            simd_double4(rotation.axis.vector, rotation.angle.radians)
        )
    }