SceneKit - Rotate object around X and Z axis

8.2k views Asked by At

I’m using ARKit with SceneKit. When user presses a button I create an anchor and to the SCNNode corresponding to it I add a 3D object (loaded from a .scn file in the project).

The 3D object is placed facing the camera, with the same orientation the camera has. I would like to make it look like the object is laying on a plane surface and not inclined if it is that way. So, if I got it right, I’d need to apply a rotation transformation so that it’s rotation around the X and Z axis become 0.

My attempt at this is: take the node’s x and z eulerAngles, invert them, and rotate that amount around each axis

let rotationZ = rotationMatrixAroundZ(radians: -node.eulerAngles.z)
let rotationX = rotationMatrixAroundX(radians: -node.eulerAngles.x) 

let rotationTransform = simd_mul(rotationTransformX, rotationTransformZ)
node.transform = SCNMatrix4(simd_mul(simd_float4x4(node.transform), rotationTransform))

This works all right for most cases, but in some the object is rotated in completely strange ways. Should I be setting the rotation angle to anything else than just the inverse of the current Euler Angle? Setting the angles to 0 directly did not work at all.

3

There are 3 answers

2
leonaka On BEST ANSWER

I've come across this and figured out I was running into gimbal lock. The solution was to rotate the node around one axis, parent it to another SCNNode(), then rotate the parent around the other axis. Hope that helps.

1
Alan On

You don't have to do the node transform on a matrix, you can simply rotate around a specific axis and that might be a bit simpler in terms of the logic of doing the rotation.

You could do something like:

node.runAction(SCNAction.rotateBy(x: x, y: y, z: z, duration: 0.0))

Not sure if this is the kind of thing you're looking for, but it is simpler than doing the rotation with the SCNMatrix4

0
leandrodemarco On

Well, I managed a workaround, but I'm not truly happy with it, so I'll leave the question unanswered. Basically I define a threshold of 2 degrees and keep applying those rotations until both Euler Angles around X and Z are below the aforementioned threshold.

func layDownNode(_ node: SCNNode) {
    let maxErrDegrees: Float = 2.0
    let maxErrRadians = GLKMathDegreesToRadians(maxErrDegrees)

    while (abs(node.eulerAngles.x) > maxErrRadians || abs(node.eulerAngles.z) > maxErrRadians) {
        let rotationZ = -node.eulerAngles.z
        let rotationX = -node.eulerAngles.x

        let rotationTransformZ = rotationMatrixAroundZ(radians: rotationZ)
        let rotationTransformX = rotationMatrixAroundX(radians: rotationX)

        let rotationTransform = simd_mul(rotationTransformX, rotationTransformZ)

        node.transform = SCNMatrix4(simd_mul(simd_float4x4(node.transform), rotationTransform))
    }
}