Search code examples
swiftvectorscenekitquaternionsrotational-matrices

SceneKit physicBody angularVelocity as local "body fixed" angular rates


I'm working on a SceneKit iOS game where the player controls a space ship in 3D space. My eventual goal is to implement angular ship stabilization where the ship will automatically stop rolling/pitching/yawing when the user stops providing rotational inputs. For example, if the pilot is flying the ship straight and level and doing a "barrel roll", when the pilot not longer commands a roll I would like the ship to automatically provide a torque in the opposite direction to the roll until the ship no longer has any roll angular velocity.

The closest I've been able to come at programmatically identifying the ship's current angular rotation is to take the ship.physicsBody?.angularVelocity and convert it to Euler angles. I get the results I expect as long as I haven't moved the ship from it's original position/orientation. However, once I move the ship the reported angularVelocity values appear to be rotated (e.g. no longer in a "body fixed" coordinate system).

Is there a way to get angularVelocities in local "body fixed" coordinates? For example, let's assume my ship is now stopped at some arbitrary position, with some arbitrary rotation. Now let's assume I begin rolling the ship about its z-axis (so the pilot perceives their environment rotating clockwise out the front window). How can I transform the output of ship.physicsBody?.angularVelocity to only report angular velocity in the roll axis?


Solution

  • There's very little meaningful information that I could find on the web relating to this angularVelocity data.

    I have worked in the past with 3d rotations but never with quaternions. I still don't really understand quaternions but now I know enough to understand they can be expressed in an "angle axis" representation, meaning the quaternion (0.0, 0.0i, 0.0j, 1.0k) has an angle axis representation that is expressed as cos(90) + sin(90)(0.0i + 0.0j + 1.0k). This video series by Grant Sanderson and Ben Eater is extremely helpful.

    After learning this, I noticed the SCNPhysicsBody header file defines angularVelocity as follows:

    //Specifies the angular velocity of the receiver as an axis angle.
    open var angularVelocity: SCNVector4
    

    I finally realized this data is a quaternion represented in angle axis format.

    The following is the solution I came up with. It is not ideal because I first convert the quaternion into Euler angles and then rotate the Euler angles. I believe a better solution would be to first rotate the quaternion and then convert the final result to Euler angles. Unfortunately, as far as I can tell, SceneKit only provides the convertVector and convertTransform functions to rotate SCNVector3 and SCNMatrix4 data but nothing to rotate a SCNQuaternion. I still don't understand this all enough yet to manually write the code to rotate this quaternion myself.

    public extension SCNNode {
        var localAngularVelocity: SCNVector3 {
            let angularVelocity_as_angleAxis = self.physicsBody!.angularVelocity
    
            let simd_angularVelocityQuat = simd_quatf( angle: angularVelocity_as_angleAxis.w,
                                                       axis: SIMD3<Float>( x: angularVelocity_as_angleAxis.x,
                                                                           y: angularVelocity_as_angleAxis.y,
                                                                           z: angularVelocity_as_angleAxis.z ) )
    
            let n = SCNNode()
            n.simdOrientation = simd_angularVelocityQuat
            let vectorInWorld = n.eulerAngles
    
            let me = self.presentation
            // Rotate vector from world coord system to my local coord system
            return me.convertVector(vectorInWorld, from: nil)
        }
    }