Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting matrix_float3x3 rotation to SceneKit

Tags:

scenekit

I was experimenting with GameplayKit’s GKAgent3D class to move a SCNNode within a scene. I was able to update the SCNNode with the agent’s position, but not rotation. The issue being the agent’s rotation is stored as a matrix_float3x3, which doesn’t match any of data types SceneKit uses for storing rotational information.

So what I’d like to know is if there’s a simple function or method that could convert a rotation stored as matrix_float3x3 to any SceneKit data types?

like image 497
Phi Avatar asked Mar 06 '17 21:03

Phi


2 Answers

To expand on @rickster 's answer, here's a nice way to take the top-left 3x3 of a 4x4 matrix in Swift, taking advantage of the expanded simd support in the iOS 11/ tvOS 11/ High Sierra version of SceneKit:

extension float4 {
    var xyz: float3 {
        return float3(x, y, z)
    }

    init(_ vec3: float3, _ w: Float) {
        self = float4(vec3.x, vec3.y, vec3.z, w)
    }
}

extension float4x4 {
    var upperLeft3x3: float3x3 {
        let (a,b,c,_) = columns
        return float3x3(a.xyz, b.xyz, c.xyz)
    }

    init(rotation: float3x3, position: float3) {
        let (a,b,c) = rotation.columns
        self = float4x4(float4(a, 0),
                        float4(b, 0),
                        float4(c, 0),
                        float4(position, 1))
    }
}

Then, to update your agent to match your node's orientation, you'd write:

agent.rotation = node.simdTransform.upperLeft3x3

Or, if the node in question is not at the "root" level (as in, a direct child of the rootNode), you might want to use the node's worldTransform:

agent.rotation = node.simdWorldTransform.upperLeft3x3

EDIT: If the node in question has a dynamic physics body attached, or is being animated with an SCNTransaction block, the node's presentation node will more accurately reflect its current position on screen:

agent.position = node.presentation.simdWorldPosition
agent.rotation = node.presentation.simdWorldTransform.upperLeft3x3

EDIT: added code above for going in the other direction, moving the node to match the agent.

node.simdTransform = float4x4(rotation: agent3d.rotation, position: agent3d.position)

Note that if you have a physics body attached to the node, it should be kinematic rather than dynamic if you're going to be directly modifying the node's transform in this way.

like image 170
OliverD Avatar answered Dec 10 '22 22:12

OliverD


SceneKit takes transform matrices as SCNMatrix4, and provides utilities for converting from SIMD matrix_float4x4: init(_ m: float4x4) for Swift and SCNMatrix4FromMat4 for ObjC/C++.

Sadly, I don't see a built-in way to convert between SIMD 3x3 and 4x4 matrices using the assumption that the 3x3 is the upper left of the 4x4. (Seems like you'd expect that in the SIMD library, so it's worth filing a bug to Apple about.)

But it's not too hard to provide one yourself: just construct a 4x4 from column vectors, using the three column vectors of the 3x3 (padded out to float4 vectors with zero for the w component) and identity for the fourth column (0,0,0,1). (Implementation code left for the reader, partly because I don't want to write it for three languages.) After converting float3x3 to float4x4 you can convert to SCNMatrix4.


Edit: In iOS 11 / tvOS 11 / macOS 10.13 (why didn't they just call this year's macOS version 11, too?), SceneKit has a whole parallel set of APIs for using SIMD types like float4x4 directly; e.g. simdTransform. However, you still need to convert a 3x3 to a 4x4 matrix.

like image 38
rickster Avatar answered Dec 11 '22 00:12

rickster