Reputation: 61
long time reader first time poster and so on.
My problem is this, Currently i am moving a sprite around the screen of an iPod touch (landscape) using the accelerometer x and y values and cocos2d. The user can calibrate where the accelerometer zero point is by touching the screen, this just grabs the current x and y values ready to subtract from the accelerometer while the app is running. This allows the user to select a comfortable position to hold the iPod.
This works fine when the iPod is sat flat on its back on the desk or held in the hand and tilted slightly towards me on the x axis when calibrated, the sprite will move at the same speed up and down the screen when tilted on the x axis.
However the further towards me the screen is tilted when calibrated something odd happens (well something i didn't expect anyway). If i the screen tilt on the x axis away from me the sprite moves up the screen at the usual speed or faster, if i tilt the screen towards me on the x axis the sprite moves down the screen much slower or sometimes not at all.
Looking at the accelerometer values for x i realised that when the iPod it tipped all the way towards me it is close to 0 with tilting away or towards me counting less than or more than 0. I have a feeling it could be because of this but am unsure of how to progress, Has anyone run into this problem before and managed to find a solution?
Here is the code i have so far.
TestLayer.H
@interface TestSceneLayer : CCLayer {
CCSprite *redShip;
float movementX, movementY;
float xCallib, yCallib;
BOOL willMoveX, willMoveY;
}
TestLayer.m
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
// Set up variables
CGSize winSize = [CCDirector sharedDirector].winSize;
UIAccelerationValue rollingX, rollingY, rollingZ;
float accelX, accelY, accelZ;
invertedControlls = NO;
// High pass filter for reducing jitter
rollingX = (acceleration.x * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
rollingY = (-acceleration.y * kFilteringFactor) + (rollingY * (1.0 - kFilteringFactor));
accelX = acceleration.x - rollingX;
accelY = acceleration.y - -rollingY;
// Calculate movement for x and y axis
float accelDiffX = accelX - kRestAccelX;
float accelFractionX = accelDiffX / kMaxDiff;
movementY = kShipMaxPointsPerSec * accelFractionX;
float accelDiffY = accelY - kRestAccelY;
float accelFractionY = accelDiffY / kMaxDiff;
movementX = kShipMaxPointsPerSec * accelFractionY;
// Thresh holds for x and y axis movement
willMoveX = YES;
willMoveY = YES;
if (((movementX < 70.0f) && (movementX > -70.0f))) willMoveX = NO;
else if (((movementY < 70.0f) && (movementY > -70.0f))) willMoveY = NO;
}
- (void) applyAccelerometerToNode:(CCNode*)tempNode ForTime:(ccTime)dTime {
// temp node is the sprite being moved
CGSize screenSize = [[CCDirector sharedDirector]winSize];
float oldX = [tempNode position].x;
float oldY = [tempNode position].y;
float newX, newY;
if (willMoveY) {
newY = [tempNode position].y + (movementY * dTime);
} else newY = oldY;
if (willMoveX) {
newX = [tempNode position].x - (movementX * dTime);
} else newX = oldX;
// Constrain movement on screen to stop it shooting off like a little bastard
if ((newY > (screenSize.height - 40.0f)) || newY < 40.0f ) {
newY = oldY;
}
if ((newX > (screenSize.width -40)) || newX < 40.0f ) {
newX = oldX;
}
[tempNode setPosition:ccp(newX,newY)];
}
- (void) update:(ccTime)deltaTime {
[self applyAccelerometerToNode:redShip ForTime:deltaTime];
}
- (id) init {
if (([super init])) {
CCLOG(@"TestSceneLayer --> Layer init");
CGSize screenSize = [[CCDirector sharedDirector]winSize];
[self scheduleUpdate];
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
// Grab the default Accelerometer values
xCallib = [GameManager sharedGameManager].xCallib;
yCallib = [GameManager sharedGameManager].yCallib;
// Setup and add children
redShip = [CCSprite spriteWithFile:@"red_ship.png"];
[redShip setPosition:ccp(50.0f, screenSize.height / 2)];
[redShip setScaleX:screenSize.width/1024.0f];
[redShip setScaleY:screenSize.height/768.0f];
[self addChild:redShip];
}
return self;
}
Constants.h
#define kFilteringFactor 0.1
#define kShipMaxPointsPerSec (winSize.height*0.5)
#define kRestAccelX (xCallib)
#define kMaxDiff 0.2
#define kRestAccelY (yCallib)
#define kMaxDiffY 0.1
I am thinking perhaps that i am attacking the x y values the wrong way and the maths is just all wrong. I want the user to be able to use the application from any position. Any help or pointer in the right direction will be greatly appreciated. If you need to know anything else please ask. I hope i managed to make myself clear.
Regards, James.
Upvotes: 3
Views: 2499
Reputation: 2521
The best answer takes into account all three axes, presuming you want it with respect to "however" the user is holding at a calibration point. Sticking to a gravity-oriented x and y means your values will be skewed to some extent for any other orientation.
From Alex Okafor on Parade of Rain:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
// A much more concise version courtesy of Mikko Mononen http://digestingduck.blogspot.com/
Vec2 accel2D(0,0);
Vec3 ax(1, 0, 0);
Vec3 ay(-.63f, 0,-.92f); // this is your "neutral" / calibrated position
Vec3 az(Vec3::Cross(ay,ax).normalize());
ax = Vec3::Cross(az,ay).normalize();
accel2D.x = -Vec3::Dot(Vec3(acceleration.x, acceleration.y, acceleration.z), ax);
accel2D.y = -Vec3::Dot(Vec3(acceleration.x, acceleration.y, acceleration.z), az);
}
"Hard coding our 'neutral' orientation [in ay]. By default [the "neutral" position] is the orientation of having the device tilted slightly towards your face. If you wanted strictly a 'top down' orientation then a (0,0,-1) vector would be put here. To create a new 'neutral' position you can sample the UIAcceleration parameter for a single frame and set that to be the new [ay] for the remainder of the app (aka calibrating)".
Upvotes: 3
Reputation: 13156
If you can afford to rely on newer devices that contain a gyroscope and have iOS 4 installed, I would recommend using Core Motion interface instead of UIAccelerometer because it will be deprecated. It is far more accurate and there is a build-in calibration in CMAttitude multiplyByInverseOfAttitude:
Look at Simple iPhone motion detect and the links to the documentation for more general information to get started with Core Motion API.
Upvotes: 2
Reputation: 64477
There's an unfortunate oversimplified advice going around that all it takes to calibrate the accelerometer is to subtract an offset position. This is of course wrong because the value range of the accelerometer axes ranges from -1 to 1.
If your calibration point along an axis is 0.5, you get a range of 1.5 that has your device oriented along the negative axis, and only 0.5 along the positive axis. This inevitably makes your device more sensitive to motion towards the positive axis, while limiting its maximum speed.
You can solve this by setting a calibration range, which you'll use to divide the resulting acceleration values after subtracting your calibration point:
calibrationValueRange = CGPointMake(1.0f - fabsf(calibrationPoint_.x),
1.0f - fabsf(calibrationPoint_.y));
accelerationPoint.x = (accelerationPoint.x - calibrationPoint_.x) /
calibrationValueRange.x;
accelerationPoint.y = (accelerationPoint.y - calibrationPoint_.y) /
calibrationValueRange.y;
I hope this code still works, I copied it from an old project.
Upvotes: 0