Convert SceneKit/matrix/transform code into pure Metal/MetalKit

Hi,

I'm trying to convert my SceneKit code into pure Metal/MetalKit and I feel confused about this code and how I should rewrite it.

I've made the code simple for this case with some pseudocode. The part where it says "SceneKit magic" is where I feel lost.

The SCNNode is just an empty placeholder just as it is – and it works for the calculations it is doing inside.

Any help and pointers are much appreciated :-)

class Scene
{
    let ball = Ball()
    let scnNode = SCNNode()

    var quaternionAngleVelocity = float2(0, 0)

    func update()
    {
        // pseudocode, the user press the up key
        quaternionAngleVelocity.x -= 3

        // pseudocode, the user press the down key
        quaternionAngleVelocity.x += 3

        // pseudocode, the user press the left key
        quaternionAngleVelocity.y -= 3

        // pseudocode, the user press the right key
        quaternionAngleVelocity.y += 3

        ...

        // apply friction
        quaternionAngleVelocity *= (1 - 0.05)  // i.e. decrease with 5%

        // rotate
        let newQuaternionAxisX = simd_quatf(angle: quaternionAngleVelocity.x.degreesToRadians,
                                            axis: simd_float3(x: 1, y: 0, z: 0))

        let newQuaternionAxisY = simd_quatf(angle: quaternionAngleVelocity.y.degreesToRadians,
                                            axis: simd_float3(x: 0, y: 1, z: 0))

        let finalQuaternion = simd_normalize(newQuaternionAxisX * newQuaternionAxisY)

        // SceneKit magic…?
        let matrix4 = SCNMatrix4Mult(scnNode.worldTransform, SCNMatrix4(simd_float4x4(finalQuaternion)))
        scnNode.transform = matrix4

        ball.quaternion = scnNode.simdWorldOrientation
    }
}

class Ball
{
    var position: float3 = [0, 0, 0]

    var rotation: float3 = [0, 0, 0]
    {
        didSet
        {
            let rotationMatrix = float4x4(rotation: rotation)
            quaternion = simd_quatf(rotationMatrix)
        }
    }

    var scale: float3 = [1, 1, 1]

    var forwardVector: float3 { return normalize([sin(rotation.y), 0, cos(rotation.y)]) }
    var rightVector: float3 { return [forwardVector.z, forwardVector.y, -forwardVector.x] }
    var upVector: float3 { return [0, forwardVector.z, 0] }

    var quaternion = simd_quatf()

    var modelMatrix: float4x4
    {
        let translateMatrix = float4x4(translation: position)
        // let rotateMatrix = float4x4(rotation: rotation)
        let rotateMatrix = float4x4(quaternion)
        let scaleMatrix = float4x4(scaling: scale)
        return (translateMatrix * rotateMatrix * scaleMatrix)
    }

    var worldTransform: float4x4
    {
        return modelMatrix
    }
}

To be a little bit more specific, what does the SCNMatrix4Mult(scnNode.worldTransform… do and scnNode.transform = matrix4 and = scnNode.simdWorldOrientation do?

How can I write this in simd?

Hello, there are two matrix data types in your example: SCNMatrix4 and float4x4. They both represent a 4x4 matrix and are easy to convert. However, since you've said you'd like to do this with simd, you can use the simd_mul function to multiply those matrices.

// Previously, you had:
let matrix4 = SCNMatrix4Mult(scnNode.worldTransform, SCNMatrix4(simd_float4x4(finalQuaternion)))
scnNode.transform = matrix4

ball.quaternion = scnNode.simdWorldOrientation

// But you can also do this:
let worldTransform = scnNode.simdWorldTransform
let quaternionRotation = simd_float4x4(finalQuaternion)
scnNode.simdTransform = simd_mul(worldTransform, quaternionRotation)

ball.quaternion = scnNode.simdWorldOrientation

The latter half of that code will generate a simd_float4x4 result. And as you've probably noticed, the simd... variations on SCNNode return simd types versus the SCNMatrix4 types (e.g. simdWorldTransform vs worldTransform).

I hope that helps.

Accepted Answer

I think I see what you're saying. The order of the matrices is what moves objects into a different space. Consider the following transform:

float4x4 projectionMatrix;
float4x4 viewMatrix; // The inverse camera matrix. It moves an object in world space into the camera space.
float4x4 additionalMatrix; // This extra matrix would move an object in world space.
float4x4 modelMatrix; // Move the object from its local space to world space.

float4x4 projectionViewMatrix = simd_mul(projectionMatrix, viewMatrix);
float4x4 worldMatrix = simd_mul(additionalMatrix, modelMatrix);

// Calculate the same transformation as Projection * View * Additional * Model.
// Note that the order of the matrix algebra is right to left.
float4x4 projectionViewModelMatrix = simd_mul(projectionViewMatrix, worldMatrix);

So let's say you wanted to have a translation in the X-axis, followed by a rotation on the Y-axis, followed by a translation the Z-axis, you would do the following:

// (Please note that the functions to compute the transformations are pseudo-code.)
// Move 5 units to the right.
float4x4 Tx = make_translation(5, 0, 0);
// Turn 30 degrees to the left.
float4x4 Ry = make_rotation(30, 0, 1, 0);
// Move 10 units back.
float4x4 Tz = make_translation(0, 0, -10);

// Calculate Tz * Ry * Tx.
float4x4 worldMatrix = simd_mult(Tz, simd_mul(Ry, Tx));
Convert SceneKit/matrix/transform code into pure Metal/MetalKit
 
 
Q