Search code examples
ioscore-motion

DeviceMotion relative to world - multiplyByInverseOfAttitude


What is the correct way to use CMAttitude:multiplyByInverseOfAttitude?

Assuming an iOS5 device laying flat on a table, after starting CMMotionManager with:

CMMotionManager *motionManager = [[CMMotionManager alloc]init];
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:
    CMAttitudeReferenceFrameXTrueNorthZVertical];

Later, CMDeviceMotion objects are obtained:

CMDeviceMotion *deviceMotion = [motionManager deviceMotion];

I expect that [deviceMotion attitude] reflects the rotation of the device from True North.

By observation, [deviceMotion userAcceleration] reports acceleration in the device reference frame. That is, moving the device side to side (keeping it flat on the table) registers acceleration in the x-axis. Turning the device 90° (still flat) and moving the device side to side still reports x acceleration.

What is the correct way to transform [deviceMotion userAcceleration] to obtain North-South/East-West acceleration rather than left-right/forward-backward?

CMAttitude multiplyByInverseOfAttitude seems unnecessary since a reference frame has already been specified and it is unclear from the documentation how to apply the attitude to CMAcceleration.


Solution

  • The question would not have arisen if CMDeviceMotion had an accessor for the userAcceleration in coordinates of the reference frame. So, I used a category to add the required method:

    In CMDeviceMotion+TransformToReferenceFrame.h:

    #import <CoreMotion/CoreMotion.h>
    
    @interface CMDeviceMotion (TransformToReferenceFrame)
    -(CMAcceleration)userAccelerationInReferenceFrame;
    @end
    

    and in CMDeviceMotion+TransformToReferenceFrame.m:

    #import "CMDeviceMotion+TransformToReferenceFrame.h"
    
    @implementation CMDeviceMotion (TransformToReferenceFrame)
    
    -(CMAcceleration)userAccelerationInReferenceFrame
    {
        CMAcceleration acc = [self userAcceleration];
        CMRotationMatrix rot = [self attitude].rotationMatrix;
    
        CMAcceleration accRef;
        accRef.x = acc.x*rot.m11 + acc.y*rot.m12 + acc.z*rot.m13;
        accRef.y = acc.x*rot.m21 + acc.y*rot.m22 + acc.z*rot.m23;
        accRef.z = acc.x*rot.m31 + acc.y*rot.m32 + acc.z*rot.m33;
    
        return accRef;
    }
    
    @end
    

    and in Swift 3

    extension CMDeviceMotion {
    
        var userAccelerationInReferenceFrame: CMAcceleration {
            let acc = self.userAcceleration
            let rot = self.attitude.rotationMatrix
    
            var accRef = CMAcceleration()
            accRef.x = acc.x*rot.m11 + acc.y*rot.m12 + acc.z*rot.m13;
            accRef.y = acc.x*rot.m21 + acc.y*rot.m22 + acc.z*rot.m23;
            accRef.z = acc.x*rot.m31 + acc.y*rot.m32 + acc.z*rot.m33;
    
            return accRef;
        }
    }
    

    Now, code that previously used [deviceMotion userAcceleration] can use [deviceMotion userAccelerationInReferenceFrame] instead.