SceneKit Snapshot After Rotation

After coding in the SceneKit lab, I was able to produce a scene that displays a sphere that rotates over time. The Apple Engineer suggested that I use the new scene view snapshot() method in order to export the scene as an image. However, the image appears to render before the sphereNode.runAction(SCNAction.rotate()) method completed despite it having a duration of 0.0. I believe this is because that method is an animation method.

After testing, I have found that the use of the sphereNode.rotate() method does work with the snapshot. However, that method uses quaternions rather than the angles I was working with before.

I am having a hard time understanding how to convert from my original angles to the new quaternions to achieve the same result.

As of now, I am using
Code Block swift
    sphereNode.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)
    sphereNode.runAction(SCNAction.rotate(by: 1.1*CGFloat(Double.pi), around: SCNVector3(x: 0.0, y: 1.0, z: 0.0), duration: 0.0))

If somebody could explain how to either continue rotating with an angle while working with the snapshot method or how to convert from my current rotation to the quaternion method, I would greatly appreciate the help. Below is the full code for my scene.

Code Block swift
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
extension SCNMaterial {
    convenience init(color: UIColor) {
...
    }
    convenience init(image: UIImage) {
...
    }
}
func addBloom() -> [CIFilter]? {
...
    return [bloomFilter]
}
func sceneSetup(completion: () -> ()) {
    let scene = SCNScene()
    sceneView.backgroundColor = UIColor(red: 0.09, green: 0.09, blue: 0.09, alpha: 1.00)
    let sphere = SCNSphere(radius: 1)
    sphere.segmentCount = 100
    let mat = SCNMaterial(image: UIImage(named: "desaturated.png")!)
    sphere.materials = [mat]
    sphere.firstMaterial?.diffuse.wrapS = SCNWrapMode.clamp
    sphere.firstMaterial?.diffuse.wrapT = SCNWrapMode.clamp
    let sphereNode = SCNNode(geometry: sphere)
    sphereNode.filters = addBloom()
    scene.rootNode.addChildNode(sphereNode)
    sceneView.scene = scene
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light!.type = SCNLight.LightType.ambient
    ambientLightNode.light!.color = UIColor(white: 0.8, alpha: 1.0)
    scene.rootNode.addChildNode(ambientLightNode)
    let omniLightNode = SCNNode()
    omniLightNode.light = SCNLight()
    omniLightNode.light!.type = SCNLight.LightType.omni
    omniLightNode.light!.color = UIColor(white: 0.99, alpha: 1.0)
    omniLightNode.position = SCNVector3Make(0, 0, 50)
    scene.rootNode.addChildNode(omniLightNode)
    sphereNode.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)
    sphereNode.runAction(SCNAction.rotate(by: 1.1*CGFloat(Double.pi), around: SCNVector3(x: 0.0, y: 1.0, z: 0.0), duration: 0.0))
    completion()
}

Answered by levikline in 616792022
After reading through conversions between Euler angles and unit quaternions, I was able to write a function that converted my desired Euler xyz angles to a quaternion. This way, the snapshot function captures the rotation. Below is my method written in Swift to perform the conversion.

Code Block swift
func angleConversion(x: Float, y: Float, z: Float, w: Float) -> (Float, Float, Float, Float) {
    let c1 = cos( x / 2 )
    let c2 = cos( y / 2 )
    let c3 = cos( z / 2 )
    let s1 = sin( x / 2 )
    let s2 = sin( y / 2 )
    let s3 = sin( z / 2 )
    let xF = s1 * c2 * c3 + c1 * s2 * s3
    let yF = c1 * s2 * c3 - s1 * c2 * s3
    let zF = c1 * c2 * s3 + s1 * s2 * c3
    let wF = c1 * c2 * c3 - s1 * s2 * s3
    return (xF, yF, zF, wF)
}

Implementation

Code Block swift
let (x, y, z, w) = angleConversion(x: 0, y: 1.1*Float(Double.pi), z: 0, w: 0)
sphereNode.localRotate(by: SCNQuaternion(x, y, z, w))

Accepted Answer
After reading through conversions between Euler angles and unit quaternions, I was able to write a function that converted my desired Euler xyz angles to a quaternion. This way, the snapshot function captures the rotation. Below is my method written in Swift to perform the conversion.

Code Block swift
func angleConversion(x: Float, y: Float, z: Float, w: Float) -> (Float, Float, Float, Float) {
    let c1 = cos( x / 2 )
    let c2 = cos( y / 2 )
    let c3 = cos( z / 2 )
    let s1 = sin( x / 2 )
    let s2 = sin( y / 2 )
    let s3 = sin( z / 2 )
    let xF = s1 * c2 * c3 + c1 * s2 * s3
    let yF = c1 * s2 * c3 - s1 * c2 * s3
    let zF = c1 * c2 * s3 + s1 * s2 * c3
    let wF = c1 * c2 * c3 - s1 * s2 * s3
    return (xF, yF, zF, wF)
}

Implementation

Code Block swift
let (x, y, z, w) = angleConversion(x: 0, y: 1.1*Float(Double.pi), z: 0, w: 0)
sphereNode.localRotate(by: SCNQuaternion(x, y, z, w))

SceneKit Snapshot After Rotation
 
 
Q