double = rollingZ = acceleration.x;
double = rollingX = acceleration.y;
if (rollingZ > 0.0) {
self.centerCoordinate.inclination = atan(rollingX / rollingZ) + M_PI / 2.0; //LINE 1
}
else if (rollingZ < 0.0) {
self.centerCoordinate.inclination = atan(rollingX / rollingZ) - M_PI / 2.0; // LINE 2
}
else if (rollingX < 0) {
self.centerCoordinate.inclination = M_PI/2.0; //atan returns a radian
}
else if (rollingX >= 0) {
self.centerCoordinate.inclination = 3 * M_PI/2.0;
Im just trying to fully understand this piece of code. I'm looking to build AR apps on the iphone and this code has the function of calculating the angle of inclination of the device using the accelerometer readings.
My understanding is this:
Assuming a portrait orientation if i roll the device forward the x axis of the accelerometer increases towards a negative number of -1.0 (i.e. the device is laid flat with the screen facing up). If i tilt the device towards me the x axis value increases towards a value of 1.0 (until the device is flat facing the ground).
The y axis changes up and down its axis between -1.0 and 0.0 (0 implies the device is horizontal).
If we take some example readings say x = 0.5 (a -45 degree angle, tilting the device towards me) and y = 0.8. If i plotted this on a cartesian coordinate graph with y (rollingX as the vertical axis) and x (rollingZ as the horizontal) and draw a line between them i understand that i can use the reverse tangent function (atan) to calculate the angle. My confusion comes on line 1. I dont understand why that line adds 90 degrees (in radians) to the calculated angle given by the atan function?
I just cant seem to visualise on a graph whats going on. If someone could shed some light on this - that would be much appreciated.
I suppose that these +90 degrees or -90 degrees (in case of negative rollingZ) are added to bring inclination value to widely used Polar coordinate system with angle between -180 and 180 degrees.
Assuming that you have Z line projecting upward when you look at the screen of the device and Z line looking at you from the screen, the result of calculations above vill give you an angle between screen plane and horizontal plane.
Let us assume that acceleration value is positive when it is goes "inside" the device:
1) Device is in vertical position, we have rollingZ = 1, rollingX = 0. The code returns 90 degrees.
2) Device is tilted towards user. Let rollingZ be 0.7 and rollingX be -0.7. This will give us 45 degree angle.
3) Device is in upside-down position, now we have rollingZ = -1 and rollingX = 0, and it is -90 degrees.
Related
I want to simulate depth in a 2D space, If I have a point P1 I suppose that I need to project that given point P1 into a plane x axis rotated "theta" rads clockwise, to get P1'
It seems that P1'.x coord has to be the same as the P1.x and the P1'.y has to b shorter than P1.y. In a 3D world:
cosa = cos(theta)
sina = sin(theta)
P1'.x = P1.x
P1'.y = P1.y * cosa - P1.z * sina
P1'.z = P1.y * sina + P1.z * cosa
Is my P1.z = 0? I tried it and P1'.y = P1.y * cosa doesn't result as expected
Any response would be appreciated, Thanks!
EDIT: What I want, now I rotate camera and translate matrix
EDIT 2: an example of a single line with a start1 point and a end1 point (it's an horizontal line, result expected is a falling line to the "floor" as long as tilt angle increases)
I think it's a sign error or an offset needed (java canvas drawing (0,0) is at top-left), because my new line with a tilt of 0 is the one below of all and with a value of 90ยบ the new line and the original one match
The calculation you are performing is correct if you would like to perform a rotation around the x axis clockwise. If you think of your line as a sheet of paper, a rotation of 0 degrees is you looking directly at the line.
For the example you have given the line is horizontal to the x axis. This will not change on rotation around the x axis (the line and the axis around which it is rotating are parallel to one another). As you rotate between 0 and 90 degrees the y co-ordinates of the line will decrease with P1.y*cos(theta) down to 0 at 90 degrees (think about the piece of paper we have been rotating around it's bottom edge, the x axis, at 90 degrees the paper is flat, and the y axis is perpendicular to the page, thus both edges of the page have the same y co-ordinate, both the side that is the "x-axis" and the opposite parallel side will have y=0).
Thus as you can see for your example this has worked correctly.
EDIT: The reason that multiplying by 90 degrees does not give an exactly zero answer is simply floating point rounding
Using Sprite Kit I am trying to set an SKPhysicsBody moving according to a given angle, so for example if you wanted the sprite to travel to the right you would specify 1.571 radians. To turn the specified angle into a velocity I am using the method below to convert radians to a CGVector. The ORIGINAL version that I implemented from memory has the strange effect of offsetting all the angles by 90degrees. (i.e. if 0 degrees is used the sprite moves right (just like it would if you specified 90degrees)
Question:
I have fixed this in the NEW version by swapping the dx and dy assignments. My question is why does this happen, do I have it wrong in the original (there do seem to be others doing it that way on the web) or is there some reason based on the particular coordinate system being used.
// ORIGINAL
- (CGVector)convertAngleToVector:(CGFloat)radians {
CGVector vector;
vector.dx = cos(radians) * 10;
vector.dy = sin(radians) * 10;
NSLog(#"DX: %0.2f DY: %0.2f", vector.dx, vector.dy);
return vector;
}
// NEW, SWAPPED DX & DY
- (CGVector)convertAngleToVector:(CGFloat)radians {
CGVector vector;
vector.dy = cos(radians) * 10;
vector.dx = sin(radians) * 10;
NSLog(#"DX: %0.2f DY: %0.2f", vector.dx, vector.dy);
return vector;
}
NOTE: also in Sprite Kit clockwise rotations are negative, so far convertAngleToVector is doing positive clockwise rotations (i.e. 1.571 radians is right, where it should be left) I could just do cos(radians*-1) and sin(radians*-1) but there might be some underlying reason for this based on me swapping dx and dy.
Sprite Kit (SKView Coordinates):
Yeah, SpriteKit defaults to the right. The Physics Collision sample project solves this by implementing this method:
- (CGFloat)shipOrientation
{
// The ship art is oriented so that it faces the top of the scene, but Sprite Kit's rotation default is to the right.
// This method calculates the ship orientation for use in other calculations.
return self.zRotation + M_PI_2;
}
You can then just get the existing orientation by calling something like:
CGFloat shipDirection = [self shipOrientation];
And then adjust the zRotation property from there.
From the Sprite Kit Programming Guide (emphasis added):
Sprite Kit also has a standard rotation convention. Figure 4-2 shows the polar coordinate convention. An angle of 0 radians specifies the positive x axis. A positive angle is in the counterclockwise direction.
In this coordinate system, an angle of zero radians pointing to the right is correct. If you want to use a system in which a zero angle is straight up (along positive y axis) and increase clockwise, you'll want to transform your angles before converting them to vectors.
In a iOS prototype I use a combination of CMDeviceMotion.deviceMotion.yaw and CLHeading.trueHeading to make stable compass heading that is responsive and accurate. This works well when the iPhone is held flat, where I have a graphical arrow that point to a stable compass heading.
The problem appear when the iPhone is held vertical in portait mode. The UIDeviceOrientation constantly changes from UIDeviceOrientationFaceDown to UIDeviceOrientationFaceUp and back. This makes the yaw value to skip back and forth +/-180 degrees based on small changes of the pitch. Is it possible to lock the device to one orientation that gives a stable yaw value, predict the change without glitches or compute the gyro yaw (or roll in this orientation) in other ways?
This poor guy have the same problem, with no answers. Double points possible people! :)
https://stackoverflow.com/questions/10470938/euler-angle-yaw-not-working-when-iphone-orientation-changes
I was just searching for an answer to this problem. It broke my heart a bit to see that you posted this over a year ago, but I figured maybe you or someone else could benefit from the solution.
The issue is gimbal lock. When pitch is about 90 degrees, yaw and roll match up and the gyro loses a degree of freedom. Quaternions are one way of avoiding gimbal lock, but I honestly didn't feel like wrapping my mind around that. Instead, I noticed that yaw and roll actually match up and can simply be summed to to solve the problem (assuming you only care about yaw).
SOLUTION:
float yawDegrees = currentAttitude.yaw * (180.0 / M_PI);
float pitchDegrees = currentAttitude.pitch * (180.0 / M_PI);
float rollDegrees = currentAttitude.roll * (180.0 / M_PI);
double rotationDegrees;
if(rollDegrees < 0 && yawDegrees < 0) // This is the condition where simply
// summing yawDegrees with rollDegrees
// wouldn't work.
// Suppose yaw = -177 and pitch = -165.
// rotationDegrees would then be -342,
// making your rotation angle jump all
// the way around the circle.
{
rotationDegrees = 360 - (-1 * (yawDegrees + rollDegrees));
}
else
{
rotationDegrees = yawDegrees + rollDegrees;
}
// Use rotationDegrees with range 0 - 360 to do whatever you want.
I hope this helps someone else!
If somebody is interested in the implementation in iOS Swift the code is given below:
let queue = NSOperationQueue()
motionManager.startDeviceMotionUpdatesToQueue(queue) {
[weak self] (data: CMDeviceMotion!, error: NSError!) in
var yawDegrees: Double = self!.motionManager.deviceMotion.attitude.yaw * (180.0 / M_PI)
var pitchDegrees: Double = self!.motionManager.deviceMotion.attitude.pitch * (180.0 / M_PI)
var rollDegrees: Double = self!.motionManager.deviceMotion.attitude.roll * (180.0 / M_PI)
if(rollDegrees < 0 && yawDegrees < 0){
self!.rotationDegrees = 360 - (-1 * (yawDegrees + rollDegrees))
}
else {
self!.rotationDegrees = yawDegrees + rollDegrees
}
}
However I am having some problems and I hope #blkhp19 can help me with this because at certain points the angles go into negative values which then messes up the entire calculation and I can't figure out what the problem is.
The problem is a bit confusing because there are at least two different ways to think about Yaw. One is from the phone's perspective, and one from the world perspective.
I'll use this image from Apple to explain further:
If the phone is flat on a table:
Rotations along the phone's yaw (or Z axis): change the compass heading.
Rotations along the phone's roll (or Y axis): do not change compass heading.
Rotations along the phone's pitch (or X axis): do not change compass heading.
If the phone is flat against a wall:
Rotations along the phone's yaw (or Z axis): change the compass heading.
Rotations along the phone's roll (or Y axis): change the compass heading.
Rotations along the phone's pitch (or X axis): do not change compass heading.
For the remainder of this answer, I'll assume the phone is upright and yaw, pitch, and roll refer to exactly what's in the photo above.
Yaw
You'll need to use atan2 and inspect gravity as in this example.
let yaw = -Angle(radians: .pi - atan2(motion.gravity.x, motion.gravity.y))
Pitch
Similar to the above, I primarily just swapped x and z and it seems to be returning the correct values:
let pitch = Angle(radians: .pi - atan2(motion.gravity.z, motion.gravity.y))
Roll (aka Compass Heading)
Use blkhp19's code above which sums up the attitude yaw and roll. If you import SwiftUI, you can leverage the Angle struct to make radian + degrees conversion easier:
func roll(motion: CMDeviceMotion) -> Angle {
let attitudeYaw = Angle(radians: motion.attitude.yaw)
let attitudeRoll = Angle(radians: motion.attitude.roll)
var compassHeading: Angle = attitudeYaw + attitudeRoll
if attitudeRoll.degrees < 0 && attitudeYaw.degrees < 0 {
compassHeading = Angle(degrees: 360 - (-1 * compassHeading.degrees))
}
return compassHeading
}
Also note that if you don't need the actual angle, and all you need is the relationship (e.g. isPhoneUpright), you can simply read gravity values for those.
extension CMDeviceMotion {
var yaw: Angle {
-Angle(radians: .pi - atan2(gravity.x, gravity.y))
}
var pitch: Angle {
Angle(radians: .pi - atan2(gravity.z, gravity.y))
}
var roll: Angle {
let attitudeYaw = Angle(radians: attitude.yaw)
let attitudeRoll = Angle(radians: attitude.roll)
var compassHeading: Angle = attitudeYaw + attitudeRoll
if attitudeRoll.degrees < 0 && attitudeYaw.degrees < 0 {
compassHeading = Angle(degrees: 360 - (-1 * compassHeading.degrees))
}
return compassHeading
}
}
I have set up an event system in FMOD with 3D sound triggered when the listener is close to them. I want to be able to change the listener angle of the listener so that my entire sound landscape shifts the same amount of degrees as my listener rotates. Nice huh?
Is it possible? What would be the iPhone code for that?
Would I pass something to the method set3DListenerAttributes?
I'm not sure I fully understand, do you just want to rotate the listener in 3D space? If so you adjust the listener forward vector to point in the new direction.
** Based on your response **
FMOD works with a cartesian co-ordinate system, it has a unit length vector which points in the direction an object (or listener) is facing. I would recommend you do some reading on trigonometry to fully understand how to convert an angle (in degrees or radians) to a vector.
For your forward vector the equation is (if I remember correctly):
x = cos(angle)
z = sin(angle)
y = 0;
This assumes angle is in radians, to convert from degrees to radians:
radians = degrees * (180 / Pi)
where Pi is roughly 3.14159265
This was the code I used in the end:
float degree = 90;
float radians = (degree) * (M_PI/180);
float fx = cos(radians);
float fz = sin(radians);
forward.x = fx;
forward.z = fz;
listenerpos.x = lxPos * DISTANCEFACTOR;
listenerpos.z = lyPos * DISTANCEFACTOR;
result = eventSystem->set3DListenerAttributes(0, &listenerpos,&vel,&forward,NULL);
I'm trying figure out how to get an OpenGL specified object to be displayed correctly according to the device orientation (ie. according to the gravity vector from the accelerometer, and heading from compass).
The GLGravity sample project has an example which is almost like this (despite ignoring heading), but it has some glitches. For example, the teapot jumps 180deg as the device viewing angle crosses the horizon, and it also rotates spuriously if you tilt the device from portrait into landscape. This is fine for the context of this app, as it just shows off an object and it doesn't matter that it does these things. But it means that the code just doesn't work when you attempt to emulate real life viewing of an OpenGL object according to the device's orientation. What happens is that it almost works, but the heading rotation you apply from the compass gets "corrupted" by the spurious additional rotations seen in the GLGravity example project.
Can anyone provide sample code that shows how to adjust correctly for the device orientation (ie. gravity vector), or to fix the GLGravity example so that it doesn't include spurious heading changes?
//Clear matrix to be used to rotate from the current referential to one based on the gravity vector
bzero(matrix, sizeof(matrix));
matrix[3][3] = 1.0;
//Setup first matrix column as gravity vector
matrix[0][0] = accel[0] / length;
matrix[0][1] = accel[1] / length;
matrix[0][2] = accel[2] / length;
//Setup second matrix column as an arbitrary vector in the plane perpendicular to the gravity vector {Gx, Gy, Gz} defined by by the equation "Gx * x + Gy * y + Gz * z = 0" in which we arbitrarily set x=0 and y=1
matrix[1][0] = 0.0;
matrix[1][1] = 1.0;
matrix[1][2] = -accel[1] / accel[2];
length = sqrtf(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1] + matrix[1][2] * matrix[1][2]);
matrix[1][0] /= length;
matrix[1][1] /= length;
matrix[1][2] /= length;
//Setup third matrix column as the cross product of the first two
matrix[2][0] = matrix[0][1] * matrix[1][2] - matrix[0][2] * matrix[1][1];
matrix[2][1] = matrix[1][0] * matrix[0][2] - matrix[1][2] * matrix[0][0];
matrix[2][2] = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
//Finally load matrix
glMultMatrixf((GLfloat*)matrix);
Here's a clarification showing how to get the elevation and tilt that are needed for gluLookAt solution as shown in my last answer:
// elevation comes from z component (0 = facing horizon)
elevationRadians = asin(gravityVector.z / Vector3DMagnitude(gravityVector));
// tilt is how far screen is from vertical, looking along z axis
tiltRadians = atan2(-gravityVector.y, -gravityVector.x) - M_PI_2;
Following up on Chris's suggestion: I'm not sure if I've got this all correct due to differing conventions of row/column order and heading cw or ccw. However the following code is what I came up with:
Vector3D forward = Vector3DMake(0.0f, 0.0f, -1.0f);
// Multiply it by current rotation matrix to get teapot direction
Vector3D direction;
direction.x = matrix[0][0] * forward.x + matrix[1][0] * forward.y + matrix[2][0] * forward.z;
direction.y = matrix[0][1] * forward.x + matrix[1][1] * forward.y + matrix[2][1] * forward.z;
direction.z = matrix[0][2] * forward.x + matrix[1][2] * forward.y + matrix[2][2] * forward.z;
heading = atan2(direction.z, direction.x) * 180 / M_PI;
// Use this heading to adjust the teapot direction back to keep it fixed
// Rotate about vertical axis (Y), as it is a heading adjustment
glRotatef(heading, 0.0, 1.0, 0.0);
When I run this code, the teapot behaviour has apparently "improved" eg. heading no longer flips 180deg when device screen (in portrait view) is pitched forward/back through upright. However, it still makes major jumps in heading when device (in landscape view) is pitched forward/back. So something's not right. It suggests that the above calculation of the actual heading is incorrect...
I finally found a solution that works. :-)
I dropped the rotation matrix approach, and instead adopted gluLookAt. To make this work you need to know the device "elevation" (viewing angle relative to horizon ie. 0 on horizon, +90 overhead), and the camera's "tilt" (how far the device is from vertical its x/y plane ie. 0 when vertical/portrait, +/-90 when horizontal/landscape), both of which are obtained from the device gravity vector components.
Vector3D eye, scene, up;
CGFloat distanceFromScene = 0.8;
// Adjust eye position for elevation (y/z)
eye.x = 0;
eye.y = distanceFromScene * -sin(elevationRadians); // eye position goes down as elevation angle goes up
eye.z = distanceFromScene * cos(elevationRadians); // z position is maximum when elevation is zero
// Lookat point is origin
scene = Vector3DMake(0, 0, 0); // Scene is at origin
// Camera tilt - involves x/y plane only - arbitrary vector length
up.x = sin(tiltRadians);
up.y = cos(tiltRadians);
up.z = 0;
Then you just apply the gluLookAt transformation, and also rotate the scene according to the device heading.
// Adjust view for device orientation
gluLookAt(eye.x, eye.y, eye.z, scene.x, scene.y, scene.z, up.x, up.y, up.z);
// Apply device heading to scene
glRotatef(currentHeadingDegrees, 0.0, 1.0, 0.0);
Try rotating the object depending upon iphone acceleration values.
float angle = -atan2(accelX, accelY);
glPushMatrix();
glTranslatef(centerPoint.x, centerPoint.y, 0);
glRotatef(angle, 0, 0, 1);
glTranslatef(-centerPoint.x, -centerPoint.y, 0);
glPopMatrix();
Where centerPoint is the middle point the object.
oo, nice.
GLGravity seems to get everything right except for the yaw. Here's what I would try. Do everything GLGravity does, and then this:
Project a vector in the direction you want the teapot to face, using the compass or whatever you so choose. Then multiply a "forward" vector by the teapot's current rotation matrix, which will give you the direction the teapot is facing. Flatten the two vectors to the horizontal plane and take the angle between them.
This angle is your corrective yaw. Then just glRotatef by it.
Whether or not the 3GS's compass is reliable and robust enough for this to work is another thing. Normal compasses don't work when the north vector is perpendicular to their face. But I just tried the Maps app on my workmate's 3GS and it seems to cope, so maybe they have got a mechanical solution in there. Knowing what the device is actually doing will help interpret the results it gives.
Make sure to test your app at the north and south poles once you're done. :-)
Getting a much more stable gravity-based reference, can now be done using CMMotionManager.
When starting motion updates with startDeviceMotionUpdates(), you can specify a reference frame.
This fuses the accelerometer, gyroscope and optionally (depending on chose reference frame) magnetometer data. Accelerometer data is pretty noisy and bouncy (any sideways motion of the device temporarily tilts the gravity vector by any device acceleration) and alone doesn't make a good reference.
I've been low-pass filtering the accelerometer data, which helps a bit but makes the system slow.