I need to calculate "facing" (it doesn't matter if it will be based on true north or magnetic one). As it can be seen on the iOS devices the CLHeading objects returned by the CLLocationManager gives us both the true and the magnetic heading by corresponding properties. Also, we can very easily see, that those values are related to the top of the device (the positive Y axis of the devices coordinate system) which is not good for my purposes.
What I actually need is to calculate the facing related to the screen of the device (Z axis) as I don't need the compass, but a king of AG application. The issue is when you rotate the device to landscape you get heading values to the left or to the right from your facing direction, which is what I need in the end.
As I know, I can get the magnetometer "raw" data (given to me in microtesla units with values from 128 to -128 for each device axis) along with the gyroscope "raw" data ( which comes in three types: Euler angels, Rotation matrix or Quaternion). What I need is to know, which calculations I need to apply to those to get the "facing" direction instead of "heading".
I've made it a while ago and because I see no answers, I've decided to put my solution here for those who'll search answer for the same question...
_motionManager = [[CMMotionManager alloc]init];
if (_motionManager.gyroAvailable) {
_motionManager.deviceMotionUpdateInterval = 1.0/20.0;
[_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
CMAcceleration gravity = motion.gravity;
CGPoint tiltVector = CGPointMake(-gravity.x, -gravity.y);
_tiltAngle = [self angleYAxisToVector:tiltVector];
CLLocationDirection heaqding = [[SVSession sharedSession] heading].trueHeading;
double newHeading = fmod(heaqding + _tiltAngle, 360.0);
self.azimuth = degreesToRadian(newHeading);
[self updateLocations]; //this function updates my ui for the new heading
}];
} else {
NSLog(#"No gyroscope on device.");
[_motionManager release],_motionManager = nil;
}
And here are some additional snippets that may help to understand this example:
-(double)angleYAxisToVector:(CGPoint)vector{
double dX = vector.x;
double dY = vector.y;
if(dY == 0){
if(dX > 0){
return 0.0;
}else{
if(dX < 0){
return 180.0;
}else{
return -1;
}
}
}
double beta = radiansToDegrees(atan(dX/dY));
double angle;
if(dX > 0){
if (dY < 0){
angle = 180 + beta;
}else{
angle = beta;
}
}else{
if (dY < 0){
angle = 180 + beta;
}else{
angle = 360 + beta;
}
}
// NSLog(#"angle = %f, normalized = %f",beta,angle);
return angle;
}
#define degreesToRadian(x) (M_PI * (x) / 180.0)
#define radiansToDegrees(x) ((x) * 180.0 / M_PI)
#define degreesToRadians(x) degreesToRadian(x)
#define radiansToDegree(x) radiansToDegrees(x)
Happy coding...
Related
I was faced with this question in one of my interviews and was completely stumped. The only solution I could think of was storing the currentAngle in a NSArray to calculate the next angle.
Question:
Move a 35px ball across the screen utilizing the iPhone's compass. Once the ball is in the center of the screen, let the user tap it to 'reset' the position. Once reset, the ball will go back to the Min position. Remember that the compass may start somewhere between 0-359, the task is to find the nearest capture angle and focus on that angle until the ball is aligned. Once the ball is aligned & reset, the iPhone will move to the next angle and so forth until the ball has been reset 18 times. 18 resets * 20 degree angles = 360.
Assigned Variables:
int currentAngle = (Ranging between 0-359) (Constant updates as the user twirls around)
int captureAngle = 20
int centerX = view.center.x (160) - 35 (size of ball)
int ballSize = 35 (ball.width/2)
The paper looked something like this:
Function so far:
-(void)testMotion{
motionQueue = [[NSOperationQueue alloc] init];
motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 1.0f / 60.0f;
if (([CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXTrueNorthZVertical) != 0) {
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical
toQueue:motionQueue
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
if (!error) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
CMAttitude *attitude = motion.attitude;
CMRotationMatrix rm = attitude.rotationMatrix;
// Get the heading.
double heading = M_PI + atan2(rm.m22, rm.m12);
heading = heading*180/M_PI;
int currentAngle = (int)heading;
NSLog(#"Current Angle: %d",currentAngle);
int captureAngle = 20; // 20 Degress Capture Angle
}];
}
}];
}
}
If I understood you, then it's something like this:
calculate the x and y movement from the angle
(see
https://en.wikipedia.org/wiki/Rotation_of_axes
http://keisan.casio.com/has10/SpecExec.cgi?id=system/2006/1223522781
)
Then move the ball according to those values, and if it moved by an angle of 20 - allow reset it or get out of the loop (for your choice)
while(1) {
x = r \cos(currentAngle)
y = r \sin(currentAngle)
//change the ball position,
ball.position.x += x*speed
ball.position.y += y*speed
//check if angel is +20 or -20
if (((currentAngle + 20) % 360) != captureAngle && (abs(currentAngle - 20) % 360) != captureAngle)) {
allow_reset_ball = true
break;
}
}
I have a simple logic for my 2d game that is enabled on the iphone with the orientation locked to Landscape Left.
If the phone is rotate away from you, imagine holding it in landscape mode, I want my object to be moved up otherwise I want the object to be moved down.
-(void)outputRotationData:(CMRotationRate)rotation
{
if (fabs(currentMaxRotY - rotation.y) < .3) {
return;
}
if(rotation.y > 0 ){
if (!(ship.position.y < 10)) {
[ship runAction:actionMoveDown];
NSLog(#"moving down");
}
}else{
if (!(ship.position.y > screenHeight)) {
[ship runAction:actionMoveUp];
NSLog(#"moving up");
}
}
currentMaxRotY = rotation.y;
}
The code above works. Just not as you would expect. Even when the phone is rotated away from you, sometimes the object moves down; however it correctly moves up most of the times. I assume due to the sensitivity which I thought would have been fixed by returning if our last rotation and current rotation don't differ by more than .3
As #LearnCocos2D mentioned in a comment, gyro is not what we want to use here.
The code below will place the object at a location based on a the angle.
I ended up not using this either and just use tap to move!
-(void)outputAccelertionData:(CMAcceleration)acceleration
{
double x, y, z, angle;
x = roundThree(acceleration.x);
y = roundThree(acceleration.y);
z = roundThree(acceleration.z);
angle = atan(x/z);
angle = [self RadiansToDegrees:angle];
if (z == 0) {
angle = 90;
}
if (angle < 40) {
[self runThisAction:angle];
}else if (angle > 50){
[self runThisAction:-angle/2];
}
}
I'm trying to develop an App with an "Around Me"-like feature of a location list with small directional arrows on the side.
Bearing and offset to the different locations hadn't been a problem thanks to Stackoverflow and compensating the compass-lag did well with following tutorial:
http://www.sundh.com/blog/2011/09/stabalize-compass-of-iphone-with-gyroscope/
All the stuff works fine with only one location in that UITableView.
But when there are more than one location, the arrows won't turn smooth and it feels like my iPhone isn't fast enough for calculating the stuff and turning these multiple arrows but I don't know how to do that better.
At the moment I'm trying this (without the locations specific directional offset):
I'm saving all the UIImageViews of all the cells in an array
when getting a new yaw value I loop through the array an actualize all the Images Rotation
if(motionManager.isDeviceMotionAvailable) {
// Listen to events from the motionManager
motionHandler = ^ (CMDeviceMotion *motion, NSError *error) {
CMAttitude *currentAttitude = motion.attitude;
float yawValue = currentAttitude.yaw; // Use the yaw value
// Yaw values are in radians (-180 - 180), here we convert to degrees
float yawDegrees = CC_RADIANS_TO_DEGREES(yawValue);
currentYaw = yawDegrees;
// We add new compass value together with new yaw value
yawDegrees = newCompassTarget + (yawDegrees - offsetG);
// Degrees should always be positive
if(yawDegrees < 0) {
yawDegrees = yawDegrees + 360;
}
compassDif.text = [NSString stringWithFormat:#"Gyro: %f",yawDegrees]; // Debug
float gyroDegrees = (yawDegrees*radianConst);
// If there is a new compass value the gyro graphic animates to this position
if(updateCompass) {
[self setRotateArrow:gyroDegrees animated:YES];
[self commitAnimations];
updateCompass = 0;
} else {
[self setRotateArrow:gyroDegrees animated:NO];
[UIView commitAnimations];
}
};
and the setRotateArrow:animated method:
- (void) setRotateArrow:(float)degrees animated:(BOOL)animated{
UIImage *arrowImage = [UIImage imageNamed:#"DirectionArrow.png"];
for (int i = 0; i<arrowImageViews.count; i++) {
[(UIImageView *)[arrowImageViews objectAtIndex:i] setImage:arrowImage];
CGFloat arrowTransform = degrees;
//Rotate the Arrow
CGAffineTransform rotate = CGAffineTransformMakeRotation(arrowTransform);
[(UIImageView *)[arrowImageViews objectAtIndex:i] setTransform:rotate];
}
}
If anyone got an idea how to get the arrows rotation following smoothly the device rotation I would be very thankful.
Suppose you are holding an iphone/ipad vertically in front of you with the screen facing you, in portrait orientation. You tilt the device to one side, keeping the screen facing you. How do you measure that static tilt angle using CMMotionManager? It seems a simple question which should have a simple answer, yet I cannot find any method that does not disappear into quaternions and rotation matrices.
Can anyone point me to a worked example?
Look at gravity:
self.deviceQueue = [[NSOperationQueue alloc] init];
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 5.0 / 60.0;
// UIDevice *device = [UIDevice currentDevice];
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical
toQueue:self.deviceQueue
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
CGFloat x = motion.gravity.x;
CGFloat y = motion.gravity.y;
CGFloat z = motion.gravity.z;
}];
}];
With this reference frame (CMAttitudeReferenceFrameXArbitraryZVertical), if z is near zero, you're holding it on a plane perpendicular with the ground (e.g. as if you were holding it against a wall) and as you rotate it on that plane, x and y values change. Vertical is where x is near zero and y is near -1.
Looking at this post, I notice that if you want to convert this vector into angles, you can use the following algorithms.
If you want to calculate how many degrees from vertical the device is rotated (where positive is clockwise, negative is counter-clockwise), you can calculate this as:
// how much is it rotated around the z axis
CGFloat angle = atan2(y, x) + M_PI_2; // in radians
CGFloat angleDegrees = angle * 180.0f / M_PI; // in degrees
You can use this to figure out how much to rotate the view via the Quartz 2D transform property:
self.view.layer.transform = CATransform3DRotate(CATransform3DIdentity, -rotateRadians, 0, 0, 1);
(Personally, I update the rotation angle in the startDeviceMotionUpdates method, and update this transform in a CADisplayLink, which decouples the screen updates from the angle updates.)
You can see how far you've tilted it backward/forward via:
// how far it it tilted forward and backward
CGFloat r = sqrtf(x*x + y*y + z*z);
CGFloat tiltForwardBackward = acosf(z/r) * 180.0f / M_PI - 90.0f;
It is kind of a late answer but you can found a working example on github and the blog article that comes with it.
To summarize the article mentioned above, you can use quaternions to avoid the gimbal lock problem that you are probably facing when holding the iPhone vertically.
Here is the coding part that compute the tilt (or yaw) :
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
// use the yaw value
// ...
You can even add a simple Kalman filter to ease the yaw :
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
if (self.motionLastYaw == 0) {
self.motionLastYaw = yaw;
}
// kalman filtering
static float q = 0.1; // process noise
static float r = 0.1; // sensor noise
static float p = 0.1; // estimated error
static float k = 0.5; // kalman filter gain
float x = self.motionLastYaw;
p = p + q;
k = p / (p + r);
x = x + k*(yaw - x);
p = (1 - k)*p;
self.motionLastYaw = x;
// use the x value as the "updated and smooth" yaw
// ...
Here is an example that rotates a UIView self.horizon to keep it level with the horizon as you tilt the device.
- (void)startDeviceMotionUpdates
{
CMMotionManager* coreMotionManager = [[CMMotionManager alloc] init];
NSOperationQueue* motionQueue = [[NSOperationQueue alloc] init]
CGFloat updateInterval = 1/60.0;
CMAttitudeReferenceFrame frame = CMAttitudeReferenceFrameXArbitraryCorrectedZVertical;
[coreMotionManager setDeviceMotionUpdateInterval:updateInterval];
[coreMotionManager startDeviceMotionUpdatesUsingReferenceFrame:frame
toQueue:motionQueue
withHandler:
^(CMDeviceMotion* motion, NSError* error){
CGFloat angle = atan2( motion.gravity.x, motion.gravity.y );
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
self.horizon.transform = transform;
}];
}
This is a little oversimplified - you should be sure to have only one instance of CMMotionManager in your app so you want to pre-initialise this and access it via a property.
Since iOS8 CoreMotion also returns you a CMAttitude object, which contains pitch, roll and yaw properties, as well as the quaternion. Using this will mean you don't have to do the manual maths to convert acceleration to orientation.
Alright, here we go. I have a cocos2d app, and there are targets that move toward the player. When the player moves, I would like for them to slowly change their destination toward the player again, so they aren't just moving into empty space. Is it possible to change the destination of a sprite mid-runAction?
edit:
This is the code in - (void)changeTargetDest
- (void)changeTargetDest {
NSMutableArray* deleteArray = [[NSMutableArray alloc] init];
for(CCSprite* s in _targets) {
float offX = s.position.x - player.position.x;
float offY = s.position.y - player.position.y;
float adjustX;
float adjustY;
float offDistance = sqrt(powf(offX, 2.0f) + powf(offY, 2.0f));
if(offDistance < 15) {
[deleteArray addObject:s];
deaths++;
[deathLabel setString:[NSString stringWithFormat:#"Deaths: %ld", deaths]];
if(deaths == 0)
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %ld.00", score]];
else
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %.2f", ((float)score / (float)deaths)]];
}
else {
adjustX = offX * .99;
adjustY = offY * .99;
CGPoint point = CGPointMake(player.position.x + adjustX, player.position.y + adjustY);
[s setPosition:point];
}//else
}//for
for (CCSprite *target in deleteArray) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
}
This works well, except for one problem. Because the new position is calculated by just taking .99 of the previous offset, the closer the target gets to the player, the more slowly it moves. How can I make its speed constant?
You can stop the action and run a new action each few frames in a scheduled method.
but the better way is to compute the position of targets according to players position and use setPosition to manualy change their positions each frame in your update method.