20

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

Pochi
  • 13,198
  • 2
  • 58
  • 99
  • Do you know how to take attitude relative to true north on iOS4? iOS5 has nice methods for this. – vale4674 Feb 17 '12 at 15:12
  • no sorry no idea, thats why i cant create my own reference attitude... – Pochi Feb 18 '12 at 16:54
  • There is no good material online for this kind of stuff.. too bad. – vale4674 Feb 20 '12 at 12:52
  • I found a thread here: http://stackoverflow.com/questions/5004548/iphone-core-motion-range-of-yaw-pitch-and-roll By using the gravity.z (check if it is positive or negative), I can simulate the pitch value based on Y axis. you may give it a try. – Wayne Liu Mar 05 '12 at 18:26
  • @LuisOscar \have you found a solution for this? I just opened the same [question](http://stackoverflow.com/questions/9626749/transform-devices-attitude-so-that-z-axis-is-aligned-with-north-and-y-is-poin) and realized that I saw your's question a month ago. – vale4674 Mar 09 '12 at 13:00
  • @vale4674 actually no... i did manage to get a workaround, if you establish your reference attitude as one of the predefined ones, then you can apply a rotation matrix directly to the attitude.m for example, and i just take this instead. It is not quite what i want tho, but no maybe its just not even possible. – Pochi Mar 10 '12 at 14:10
  • Yea, then I'll have to make a workaround also. Thx anyway – vale4674 Mar 10 '12 at 14:48
  • This is my question: http://stackoverflow.com/questions/28959943/ios-accelerometer-values-at-any-orientation?noredirect=1#comment46172543_28959943 is this relative ? – nr5 Mar 10 '15 at 10:29

2 Answers2

4

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;
}
superjos
  • 10,834
  • 4
  • 79
  • 120
Orbitus007
  • 656
  • 6
  • 12
  • Is this correct, in other words: when the app starts, the CM reference frame is set according to the iPhone’s current attitude. i.e. the values of roll, yaw and pitch are not guaranteed to be the same when the phone is held at the same angle. However, by reading the raw accelerometer data, you can obtain a transform that gives the device’s true orientation with respect to gravity, when applied to the deviceMotion.attitude property. – Benji XVI Nov 26 '12 at 13:45
  • 1
    For those less versed in vectors, this should be equivalent to converting the initial gravity reading to a set of three Euler angles – the actual orientation of the device compared to . Call the difference between these angles and those supplied simultaneously by mgr.deviceMotion.attitude the “orientation difference”. When calculating the momentary actual attitude of the device, then, you can then simply add the orientation difference to the angles in deviceMotion.attitude. – Benji XVI Nov 26 '12 at 15:00
  • The formula for `magnitudeCurrentGravity` should multiply `currentGravity` by itself instead of summing. – sixstringtheory Jun 30 '15 at 03:06
  • @Orbitus007 looking at this part `currentGravity.y + currentGravity.y` in `innerMagnitudeProduct`, that addition seems wrong. Shouldn't that be a multiplication? – superjos Sep 22 '19 at 09:03
  • Also, shouldn't `CMAcceleration currentGravity;` be a member/field, instead of a local variable? Or am I missing something? Thanks – superjos Sep 22 '19 at 09:21
-1

I hope this will help you

You can change the reference frame used by a CMAttitude instance. To do that, cache the attitude object that contains that reference frame and pass that as the argument to multiplyByInverseOfAttitude:. The attitude argument receiving the message is changed to represent the change in attitude from that reference frame.

To see how this might be useful, consider a baseball game where the user rotates the device to swing. Normally, at the beginning of a pitch, the bat would be at some resting orientation. After that, the bat would be rendered at an orientation determined by how the device's attitude had changed from where it was at the start of a pitch.

-(void) startPitch {

// referenceAttitude is an instance variable

referenceAttitude = [motionManager.deviceMotion.attitude retain];

}

- (void)drawView {

CMAttitude *currentAttitude = motionManager.deviceMotion.attitude;

[currentAttitude multiplyByInverseOfAttitude: referenceAttitude];

// render bat using currentAttitude .....

[self updateModelsWithAttitude:currentAttitude];

[renderer render];

}

For More Look at below link that same thing that you want.

http://db-in.com/blog/2011/04/cameras-on-opengl-es-2-x/

Nitin
  • 7,269
  • 1
  • 30
  • 48
  • 3
    This does not solve the problem. this is from the apple dev documents, of course i could cache that reference attitude but if you check the method it requires runtime since its calling the current attitude using the device motion, I want to know how to create my own attitude reference frame so that i can "start" it in a different orientation. – Pochi Feb 04 '12 at 09:35
  • 2
    This is plagiarized from Apple's website without any attribution. – aledalgrande Jul 06 '14 at 02:01