Still seem to have a gimbal-lock using quaternions
I maintain the position of an object using a reference quaternion ...
I rotate a reference quaternion from 2D x-y touch swipes using the following code ...
-(void)rotateViewOnX:(CGFloat)x andOnY:(CGFloat)y
{
// x and y should be a simple Cartesian pixel movements ...
// the screen swipe moved m pixels in the x direction and n pixels in the y direction ...
// ignore micro movements ...
if(abs(x) < 0.1 && abs(y) < 0.1)
return;
// ignore excessive movements ...
if(abs(x) > VIEW_PORTAL_SIZE/3.0 || abs(y) > VIEW_PORTAL_SIZE/3.0)
return;
// simulate a very, very large trackball ...
double radius = VIEW_PORTAL_SIZE/2.0;
x = x/radius;
y = y/radius;
double z = sqrt(1 - x*x - y*y);
Vector *trackball = [[Vector alloc] initWithX:x andY:y andZ:z];
// use dot prod开发者_开发知识库uct to get the angle of rotation on the trackball
double theta = acos([self.zReferenceVector dotProduct:trackball]);
// use cross product to get the axis of rotation - convert to a unit vector
// this is an autoreleased object ...
Vector *rotationAxis = [[self.zReferenceVector crossProduct:trackball] normalise];
// create a quaternion to represent the latest movement from the simulated trackball ...
Quaternion *change = [[Quaternion alloc] initWithAngle:theta aroundVector:rotationAxis];
[change unitise];
// rotate the reference quaternion by this quaternion ...
self.zReferenceQuaternion = [[change times:self.zReferenceQuaternion] times:[change inverse]];
[self.zReferenceQuaternion unitise];
// clean-up ...
[trackball release]; trackball = nil;
[change release]; change = nil;
// and paint ...
[self setNeedsDisplay];
}
To rotate the object I apply the following code to each Vector point in the object ...
// align the cube to the rotated reference quaternion ...
v = [self.zReferenceQuaternion rotateVector:v];
Which uses the following method ...
-(Vector*)rotateVector:(Vector*)v
{
Quaternion *myInverse = [self inverse];
Quaternion *pureQuat = [[Quaternion alloc] initWithValues:0.0 :v.x :v.y :v.z];
Quaternion *step1 = [self times: pureQuat];
Quaternion *step2 = [step1 times: myInverse];
[pureQuat release];
return [[[Vector alloc] initWithX:step2.x andY:step2.y andZ:step2.z] autorelease];
}
My problem is that when the object is at around 180 degrees if I rotated it there in an x-direction I cannot move it in the y-direction. Conversely, if I rotated it there in the y-direction, at around 180 degrees rotated I cannot move it in the x-direction. Not sure what I am doing wrong!
Post script ...
In answer to the commentator, the entire Quaternion class, as it currently stands follows:
#import "Quaternion.h"
@implementation Quaternion
@synthesize w=_w;
@synthesize x=_x;
@synthesize y=_y;
@synthesize z=_z;
// ----- initialisers
// this is the designated initaliser for w, x, y, z ...
-(id) initWithValues:(double)a :(double)b :(double)c :(double)d
{
self = [super init];
if(self)
{
_w = a; //1
_x = b; //i
_y = c; //j
_z = d; //k
}
return self;
}
-(id) initWithAngle:(double)angle aroundVector:(Vector*)vector
{
// some preliminaries ...
double halfAngle = angle / 2.0;
double sinAngleOnTwo = sin(halfAngle); // do once
Vector *v = [vector normalise];
// calculate the quaternion ...
double w = cos(halfAngle);
double x = v.x * sinAngleOnTwo;
double y = v.y * sinAngleOnTwo;
double z = v.z * sinAngleOnTwo;
// return result ...
return [self initWithValues:w :x :y :z];
}
-(Quaternion*)copy
{
Quaternion *a = self;
return [[Quaternion alloc] initWithValues:a.w :a.x :a.y :a.z];
}
// ----- algebra
-(Quaternion*)conjugate
{
Quaternion *a = self;
return [[[Quaternion alloc] initWithValues:a.w :-a.x :-a.y :-a.z ] autorelease];
}
-(Quaternion*)plus:(Quaternion*)b
{
Quaternion *a = self;
return [[[Quaternion alloc] initWithValues:a.w+b.w :a.x+b.x :a.y+b.y :a.z+b.z] autorelease];
}
-(Quaternion*)minus:(Quaternion*)b
{
Quaternion *a = self;
return [[[Quaternion alloc] initWithValues:a.w-b.w :a.x-b.x :a.y-b.y :a.z-b.z] autorelease];
}
-(Quaternion*)times:(Quaternion*) b
{
Quaternion *a = self;
double real = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z;
double i = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y;
double j = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x;
double k = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w;
return [[[Quaternion alloc] initWithValues:real :i :j :k] autorelease];
}
-(double)norm
{
Quaternion *a = self;
return a.w*a.w + a.x*a.x + a.y*a.y + a.z*a.z;
}
-(Quaternion*)inverse
{
Quaternion *a = self;
double n = [a norm];
return [[[Quaternion alloc] initWithValues:a.w/n :-a.x/n :-a.y/n :-a.z/n] autorelease];
}
-(Quaternion*)divides:(Quaternion*) b
{
Quaternion *a = self;
return [[a inverse] times: b];
}
-(double)magnitude
{
Quaternion *a = self;
return sqrt([a norm]);
}
-(void)unitise
{
double m = [self magnitude];
self.w /= m;
self.x /= m;
self.y /= m;
self.z /= m;
}
-(Quaternion*)unitQuaternion
{
Quaternion *u = [[self copy] autorelease];
[u unitise];
return u;
}
-(Vector*)rotateVector:(Vector*)v
{
Quaternion *vAsPureQuat = [[Quaternion alloc] initWithValues:0.0 :v.x :v.y :v.z];
Quaternion *r = [[self times: vAsPureQuat] times:[self inverse]];
[vAsPureQuat release];
return [[[Vector alloc] initWithX:r.x andY:r.y andZ:r.z] autorelease];
}
// ----- misc
-(NSString*)toString
{
Quaternion *a = self;
return [NSString stringWithFormat:@"%f + %fi + %fj +%fk", a.w, a.x, a.y, a.z];
}
@end
I have discovered the error of my ways ...
It lay in the code that maintains the rotated reference quaternion ... I should not have been multiplying it by the inverse ... in the following code snippet, the buggy code from the above question has been commented out.
// rotate the zReferenceQuaternion quaternion by this change quaternion ...
//self.zReferenceQuaternion = [[change times:self.zReferenceQuaternion] times:[change inverse]];
self.zReferenceQuaternion = [change times:self.zReferenceQuaternion ];
[self.zReferenceQuaternion unitise];
The movements are now accumulated in the self.zReferenceQuaternion iVar. No gimbal-lock like situation occurs on the screen moving the object around.
精彩评论