Francisco Ryan Tolmasky I - 8 months ago 83

iOS Question

I would like to use CMAttitude to know the vector normal to the glass of the iPad/iPhone's screen (relative to the ground). As such, I would get vectors like the following:

Notice that this is different from orientation, in that I don't care how the device is rotated about the z axis. So if I was holding the iPad above my head facing down, it would read (0,-1,0), and even as I spun it around above my head (like a helicopter), it would continue to read (0,-1,0):

I feel like this might be pretty easy, but as I am new to quaternions and don't fully understand the reference frame options for device motion, its been evading me all day.

Answer

- In your case we can say rotation of the device is equal to rotation of the device normal (rotation around the normal itself is just ignored like you specified it)
- CMAttitude which you can get via
CMMotionManager.deviceMotion provides the rotation
**relative to a reference frame**. Its properties quaternion, roation matrix and Euler angles are just different representations. - The reference frame can be specified when you start device motion updates using CMMotionManager's startDeviceMotionUpdatesUsingReferenceFrame method. Until iOS 4 you had to use multiplyByInverseOfAttitude

Putting this together you just have to multiply the quaternion in the *right way* with the normal vector when the device lies face up on the table. Now we need this *right way* of quaternion multiplication that represents a rotation: According to Rotating vectors this is done by:

**n = q * e * q'** where **q** is the quaternion delivered by CMAttitude [w, (x, y, z)], **q'** is its conjugate [w, (-x, -y, -z)] and **e** is the quaternion representation of the face up normal [0, (0, 0, 1)]. Unfortunately Apple's CMQuaternion is struct and thus you need a small helper class.

```
Quaternion e = [[Quaternion alloc] initWithValues:0 y:0 z:1 w:0];
CMQuaternion cm = deviceMotion.attitude.quaternion;
Quaternion quat = [[Quaternion alloc] initWithValues:cm.x y:cm.y z:cm.z w: cm.w];
Quaternion quatConjugate = [[Quaternion alloc] initWithValues:-cm.x y:-cm.y z:-cm.z w: cm.w];
[quat multiplyWithRight:e];
[quat multiplyWithRight:quatConjugate];
// quat.x, .y, .z contain your normal
```

Quaternion.h:

```
@interface Quaternion : NSObject {
double w;
double x;
double y;
double z;
}
@property(readwrite, assign)double w;
@property(readwrite, assign)double x;
@property(readwrite, assign)double y;
@property(readwrite, assign)double z;
```

Quaternion.m:

```
- (Quaternion*) multiplyWithRight:(Quaternion*)q {
double newW = w*q.w - x*q.x - y*q.y - z*q.z;
double newX = w*q.x + x*q.w + y*q.z - z*q.y;
double newY = w*q.y + y*q.w + z*q.x - x*q.z;
double newZ = w*q.z + z*q.w + x*q.y - y*q.x;
w = newW;
x = newX;
y = newY;
z = newZ;
// one multiplication won't denormalise but when multipling again and again
// we should assure that the result is normalised
return self;
}
- (id) initWithValues:(double)w2 x:(double)x2 y:(double)y2 z:(double)z2 {
if ((self = [super init])) {
x = x2; y = y2; z = z2; w = w2;
}
return self;
}
```

I know quaternions are a bit weird at the beginning but once you have got an idea they are really brilliant. It helped me to imagine a quaternion as a rotation around the vector (x, y, z) and w is (cosine of) the angle.

If you need to do more with them take a look at cocoamath open source project. The classes Quaternion and its extension QuaternionOperations are a good starting point.

For the sake of completeness, yes you can do it with matrix multiplication as well:

**n = M * e**

But I would prefer the quaternion way it saves you all the trigonometric hassle and performs better.