How to create a new CMAttitude Reference Frame to make the gravity be on the Y axis

Pochi picture Pochi · Dec 29, 2011 · Viewed 9.6k times · Source

I want to be able to change the Device Motion Manager Reference frame (for the gyro) so that I have my gravity vector on the Y axis.

Usually when you start Device Motion Manager Updates you will only have the z axis of the phone aligned with gravity.

You can change this to use the magnetometer to make the x axis be aligned with either the magnetic or true north pole. With this I have my X axis Pointing north and my Z axis pointing down.

What I want to do is have my Y axis (negative) pointing down (so that its aligned with the gravity) and also have my X axis pointing the true magnetic pole.

The result I want is such that when have my phone standing still in a vertical (portrait) orientation the right of the phone will be aligned to the north pole and all my readings (roll, pitch, yaw) will read as 0. Then with this if i rotate my phone on the X axis the pitch will change, and if I rotate around the Y axis the yaw will change.

So far I know i can set my own reference frame if I multiply by the inverse of the attitude a previously stored attitude, (like i could set my phone in this orientation MANUALLY, save that attitude and simply keep multiplying the new attitude by the inverse of this stored one and all my readings will be exactly like the ones i want).

But setting it manually is not an option, so how do i make this programatically?

I don't think there is a function to create my own attitude reference frame, or if at least there was a function to multiply the attitude by a rotation matrix then i could probably solve this. (because i would just multiply all the attitude by a 90 degrees change in the pitch).

I hope I explained myself clearly,

I will appreciate any suggestions. thanks

PD: These are the iPhone Orientation coordinates:

enter image description here

Answer

Orbitus007 picture Orbitus007 · May 27, 2012

Pseudo code:

  1. start device motion updates
  2. start a camera preview in the background ;)
  3. capture the current gravity reading from the device as a CMAcceleration... once you have gravity store it in a local variable.
  4. Then you have to take the 2 vectors and get the angle in between them, in this case the device gravity of (0,0,-1) and the real gravity vector...
  5. we then turn theta into thetaPrime... a transform that matches the CoreMotion reference orientation
  6. Setup a timer to animate....
  7. during animation get the inverse of the rotationMatrix of motionManager's deviceMotion property.
  8. Apply the Transformations in the correct order to reflect the device's current attitude (yaw,pitch,roll in eulerian mode or the devices quaternion rotation... 3 different ways to say the same thing basically)

Here is the code:

- (void) initMotionCapture
{
    firstGravityReading = NO;
    referenceAttitude = nil;

    if (motionManager == nil)
    {
        self.motionManager = [CMMotionManager new];
    }
    motionManager.deviceMotionUpdateInterval = 0.01;
    self.gravityTimer = [NSTimer scheduledTimerWithTimeInterval:1/60.0 
                                 target:self 
                                 selector:@selector(getFirstGravityReading) 
                                 userInfo:nil repeats:YES];
}


- (void) getFirstGravityReading
{
    CMAcceleration currentGravity; 

    CMDeviceMotion *dm = motionManager.deviceMotion;
    referenceAttitude = dm.attitude;
    currentGravity = dm.gravity;

    [motionManager startDeviceMotionUpdates];

    if (currentGravity.x !=0 && 
        currentGravity.y !=0 && currentGravity.z !=0)
    {
        NSLog(@"Gravity = (%f,%f,%f)", 
              currentGravity.x, currentGravity.y, currentGravity.z);

        firstGravityReading = YES;
        [gravityTimer invalidate];
        self.gravityTimer = nil;
        [self setupCompass];
    }
}

- (void) setupCompass
{
    //Draw your cube... I am using a quartz 3D perspective hack!
    CATransform3D initialTransform = perspectiveTransformedLayer.sublayerTransform;
    initialTransform.m34 = 1.0/-10000;


    //HERE IS WHAT YOU GUYS NEED... the vector equations!
    NSLog(@"Gravity = (%f,%f,%f)", 
          currentGravity.x, currentGravity.y, currentGravity.z);

    //we have current gravity vector and our device gravity vector of (0, 0, -1)
    // get the dot product
    float dotProduct = currentGravity.x*0 + 
                       currentGravity.y*0 + 
                       currentGravity.z*-1;
    float innerMagnitudeProduct = currentGravity.x*currentGravity.x + 
                                  currentGravity.y + currentGravity.y + 
                                  currentGravity.z*currentGravity.z;
    float magnitudeCurrentGravity = sqrt(innerMagnitudeProduct);
    float magnitudeDeviceVector = 1; //since (0,0,-1) computes to: 0*0 + 0*0 + -1*-1 = 1

    thetaOffset = acos(dotProduct/(magnitudeCurrentGravity*magnitudeDeviceVector));
    NSLog(@"theta(degrees) = %f", thetaOffset*180.0/M_PI);

    //Now we have the device angle to the gravity vector (0,0,-1)
    //We must transform these coordinates to match our 
    //device's attitude by transforming to theta prime
    float theta_deg = thetaOffset*180.0/M_PI;
    float thetaPrime_deg = -theta_deg + 90; // ThetaPrime = -Theta + 90 <==> y=mx+b

    NSLog(@"thetaPrime(degrees) = %f", thetaOffset*180.0/M_PI);

    deviceOffsetRotation = 
        CATransform3DMakeRotation((thetaPrime_deg) * M_PI / 180.0, 1, 0, 0);
    initialTransform = CATransform3DConcat(deviceOffsetRotation, initialTransform);

    perspectiveTransformedLayer.sublayerTransform = initialTransform;

    self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1/60.0 
                                   target:self 
                                   selector:@selector(tick) 
                                   userInfo:nil 
                                   repeats:YES];

}

- (void) tick
{
    CMRotationMatrix rotation;

    CMDeviceMotion *deviceMotion = motionManager.deviceMotion;
    CMAttitude *attitude = deviceMotion.attitude;

    if (referenceAttitude != nil)
    {
        [attitude multiplyByInverseOfAttitude:referenceAttitude];
    }
    rotation = attitude.rotationMatrix;

    CATransform3D rotationalTransform = perspectiveTransformedLayer.sublayerTransform;

    //inverse (or called the transpose) of the attitude.rotationalMatrix
    rotationalTransform.m11 = rotation.m11;
    rotationalTransform.m12 = rotation.m21;
    rotationalTransform.m13 = rotation.m31;

    rotationalTransform.m21 = rotation.m12;
    rotationalTransform.m22 = rotation.m22;
    rotationalTransform.m23 = rotation.m32;

    rotationalTransform.m31 = rotation.m13;
    rotationalTransform.m32 = rotation.m23;
    rotationalTransform.m33 = rotation.m33;

    rotationalTransform = 
        CATransform3DConcat(deviceOffsetRotation, rotationalTransform);
    rotationalTransform = 
        CATransform3DConcat(rotationalTransform, 
                            CATransform3DMakeScale(1.0, -1.0, 1.0));


    perspectiveTransformedLayer.sublayerTransform = rotationalTransform;
}