I'm trying to incrementally rotate the camera around x-axis by 5 degrees. it works fine except for animation at 355 jumps suddenly. it happens due to animation chaining. if my following method is called in SCNSceneRendererDelegate then it is not time based rotation. the SCNSceneRendererDelegate is triggered in each frame. and it means my scene animation action with duration is not ready yet. by lowering the animation duration, the animation is not smooth anymore. doing a timer based with same interval as the animation duration looks bad as well. is there anyway to get this animation smooth?
-(void) updateCameraRotation
{
SCNQuaternion oldRotScnQuat = _cameraNode.presentationNode.rotation;
GLKQuaternion glQuatOldRot = GLKQuaternionMakeWithAngleAndAxis(oldRotScnQuat.w, oldRotScnQuat.x, oldRotScnQuat.y, oldRotScnQuat.z);
float xan = GLKMathDegreesToRadians(5);
GLKQuaternion newx = GLKQuaternionIdentity;
GLKVector3 vec = GLKVector3Normalize(GLKVector3Make(1, 0, 0));
double result = sinf(xan/2);
newx = GLKQuaternionMakeWithAngleAndAxis(cosf(xan/2), vec.x *result, vec.y * result, vec.z * result);
newx = GLKQuaternionNormalize(newx);
glQuatOldRot = GLKQuaternionMultiply(glQuatOldRot, newx);
axis = GLKQuaternionAxis(glQuatOldRot);
angle = GLKQuaternionAngle(glQuatOldRot);
[_cameraNode runAction:[SCNAction rotateToAxisAngle:SCNVector4Make(axis.x, axis.y, axis.z, angle) duration:1]];
}
SLERP is used to make smooth rotations between two known attitudes. Try and avoid needless conversions between axis-angle and quaternions (leave them in quaternions if possible).
http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
You will also need to calculate the angle between the quaternion axes: https://math.stackexchange.com/questions/90081/quaternion-distance
I have used both these functions myself and they work well.
Rather than adding 5 degrees per frame, you can specify a scalar t in the range of [0, 1].
For instance, when rotating from quaternion P to Q, R = SLERP(P,Q,t) will give you the quaternion R from P to Q. If t = 0 then R = P, if t = 1 then R = Q.
Try to provide a CABasicAnimation with byValue set to your 5 degrees and set repititions to be infinite. Won't that work?
Related
I am using opencv::solvePnP to return a camera pose. I run PnP, and it returns the rvec and tvec values.(rotation vector and position).
I then run this function to convert the values to the camera pose:
void GetCameraPoseEigen(cv::Vec3d tvecV, cv::Vec3d rvecV, Eigen::Vector3d &Translate, Eigen::Quaterniond &quats)
{
Mat R;
Mat tvec, rvec;
tvec = DoubleMatFromVec3b(tvecV);
rvec = DoubleMatFromVec3b(rvecV);
cv::Rodrigues(rvec, R); // R is 3x3
R = R.t(); // rotation of inverse
tvec = -R*tvec; // translation of inverse
Eigen::Matrix3d mat;
cv2eigen(R, mat);
Eigen::Quaterniond EigenQuat(mat);
quats = EigenQuat;
double x_t = tvec.at<double>(0, 0);
double y_t = tvec.at<double>(1, 0);
double z_t = tvec.at<double>(2, 0);
Translate.x() = x_t * 10;
Translate.y() = y_t * 10;
Translate.z() = z_t * 10;
}
This works, yet at some rotation angles, the converted rotation values flip randomly between positive and negative values. Yet, the source rvecV value does not. I assume this means I am going wrong with my conversion. How can i get a stable Quaternion from the PnP returned cv::Vec3d?
EDIT: This seems to be Quaternion flipping, as mentioned here:
Quaternion is flipping sign for very similar rotations?
Based on that, i have tried adding:
if(quat.w() < 0)
{
quat = quat.Inverse();
}
But I see the same flipping.
Both quat and -quat represent the same rotation. You can check that by taking a unit quaternion, converting it to a rotation matrix, then doing
quat.coeffs() = -quat.coeffs();
and converting that to a rotation matrix as well.
If for some reason you always want a positive w value, negate all coefficients if w is negative.
The sign should not matter...
... rotation-wise, as long as all four fields of the 4D quaternion are getting flipped. There's more to it explained here:
Quaternion to EulerXYZ, how to differentiate the negative and positive quaternion
Think of it this way:
Angle/axis both flipped mean the same thing
and mind the clockwise to counterclockwise transition much like in a mirror image.
There may be convention to keep the quat.w() or quat[0] component positive and change other components to opposite accordingly. Assume w = cos(angle/2) then setting w > 0 just means: I want angle to be within the (-pi, pi) range. So that the -270 degrees rotation becomes +90 degrees rotation.
Doing the quat.Inverse() is probably not what you want, because this creates a rotation in the opposite direction. That is -quat != quat.Inverse().
Also: check that both systems have the same handedness (chirality)! Test if your rotation matrix determinant is +1 or -1.
(sry for the image link, I don't have enough reputation to embed them).
I'm a beginner at programming, and I've been trying to make an object orbit around another object (or just move in a circle). But I haven't succeeded very well. Any ideas?
You need some constants to specify radius and speed:
const float speed = 100.0f;
const float radius = 50.0f;
you also need some variable to store angle:
float angle;
- (void)updateObject:(NSTimeInterval)dt
{
angle += speed * dt;
angle = fmodf(angle, 360.0f);
float x = cosf(DEGREES_TO_RADIANS(angle)) * radius;
float y = sinf(DEGREES_TO_RADIANS(angle)) * radius;
float newXPosition = _yourSprite.position.x + x;
float newYPosition = _yourSprite.position.y + y;
//Assign the values to your sprite
_yourSprite.position = ...
}
Try connecting two nodes with SKPhysicsJointLimit, with the first node not movable (maybe not dynamic), set the linear damping of the second node to zero and disable gravitation forces on it. It also should not collide with any other object, of course.When the joint is stretched to its maximum and you apply an Impulse vertical to the connection between the two objects, the object should start orbiting around the other one.
I have not tested this one.
Please bear with me, I'm really awful at matrix math. I have a layer that I want to remain "stationary" with gravity at the referenceAttitude while the phone rotates to other attitudes. I have a motionManager working nicely, am using multiplyByInverseOfAttitude on the current attitude, and applying the resulting delta as a rotation to my layer using a CMRotationMatrix (doing separate CATransform3DRotates for the pitch, roll, and yaw caused considerable wackiness near the axes). It's basically inspired by code like this example.
I concat this with another transform to apply the m34 perspective trick before I apply the rotation to my layer.
[attitude multiplyByInverseOfAttitude:referenceAttitude];
CATransform3D t = CATransform3DIdentity;
CMRotationMatrix r = attitude.rotationMatrix;
t.m11=r.m11; t.m12=r.m12; t.m13=r.m13; t.m14=0;
t.m21=r.m21; t.m22=r.m22; t.m23=r.m23; t.m24=0;
t.m31=r.m31; t.m32=r.m32; t.m33=r.m33; t.m34=0;
t.m41=0; t.m42=0; t.m43=0; t.m44=1;
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0 / -650;
t = CATransform3DConcat(t, perspectiveTransform);
myUIImageView.layer.transform = t;
The result is pretty and works like you'd expect, the layer staying stationary with gravity as I move my phone around, except for a single axis, the y-axis; holding the phone flat and rolling it, the layer rolls double-time in the direction the phone is, instead of remaining stationary.
I don't know why this one axis moves wrong while the other moves correctly after applying the multiplyByInverseOfAttitude. When using separate CATransform3DRotates for the pitch, yaw, roll, I was able to easily correct the problem by multiplying the roll vector by -1, but I have no idea how to apply that to a rotation matrix. The problem obviously is only visible once you introduce perspective into the equation, so perhaps I'm doing that wrong. Inverting my m34 value fixes the roll but creates the same problem on the pitch. I either need to figure out why the rotation on this axis is backwards, invert the rotation on that axis via my matrix, or correct the perspective somehow.
You have to take into account the following:
In your case, CMRotationMatrix needs to be transposed (http://en.wikipedia.org/wiki/Transpose) which means swapping columns and rows.
You don't need to set the transform as CATransform3DIdentity, because you're overwriting each value, so you can start with an empty matrix. If you want to use CATransform3DIdentity you can omit setting 0s and 1s since they've already been defined. (CATransform3DIdentity is an identity matrix, see http://en.wikipedia.org/wiki/Identity_matrix)
To also correct the rotation around the Y axis, you need to multiply your vector with [1 0 0 0; 0 -1 0 0; 0 0 1 0; 0 0 0 1].
Make the following changes to your code:
CMRotationMatrix r = attitude.rotationMatrix;
CATransform3D t;
t.m11=r.m11; t.m12=r.m21; t.m13=r.m31; t.m14=0;
t.m21=r.m12; t.m22=r.m22; t.m23=r.m32; t.m24=0;
t.m31=r.m13; t.m32=r.m23; t.m33=r.m33; t.m34=0;
t.m41=0; t.m42=0; t.m43=0; t.m44=1;
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0 / -650;
t = CATransform3DConcat(t, perspectiveTransform);
t = CATransform3DConcat(t, CATransform3DMakeScale(1.0, -1.0, 1.0));
Or, with setting t to CATransform3DIdentity just leave the 0s and 1s out:
...
CATransform3D t = CATransform3DIdentity;
t.m11=r.m11; t.m12=r.m21; t.m13=r.m31;
t.m21=r.m12; t.m22=r.m22; t.m23=r.m32;
t.m31=r.m13; t.m32=r.m23; t.m33=r.m33;
....
I'm developing an AR app using the gyro. I have use an apple code example pARk. It use the rotation matrix to calculate the position of the coordinate and it do really well, but now I'm trying to implement a "radar" and I need to rotate this in function of the device heading. I'm using the CLLocationManager heading but it's not correct.
The question is, how can I get the heading of the device using the CMAttitude to reflect exactly what I get in the screen??
I'm new with rotation matrix and that kind of things.
This is part of the code used to calculate the AR coordinates. Update the cameraTransform with the attitude:
CMDeviceMotion *d = motionManager.deviceMotion;
if (d != nil) {
CMRotationMatrix r = d.attitude.rotationMatrix;
transformFromCMRotationMatrix(cameraTransform, &r);
[self setNeedsDisplay];
}
and then in the drawRect code:
mat4f_t projectionCameraTransform;
multiplyMatrixAndMatrix(projectionCameraTransform, projectionTransform, cameraTransform);
int i = 0;
for (PlaceOfInterest *poi in [placesOfInterest objectEnumerator]) {
vec4f_t v;
multiplyMatrixAndVector(v, projectionCameraTransform, placesOfInterestCoordinates[i]);
float x = (v[0] / v[3] + 1.0f) * 0.5f;
float y = (v[1] / v[3] + 1.0f) * 0.5f;
I also rotate the view with the pitch angle.
The motions updates are started using the north:
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical];
So I think that must be possible to get the "roll"/heading of the device in any position (with any pitch and yaw...) but I don't know how.
There are a few ways to calculate heading from the rotation matrix returned by CMDeviceMotion. This assumes you use the same definition of Apple's compass, where the +y direction (top of the iPhone) pointing due north returns a heading of 0, and rotating the iPhone to the right increases the heading, so East is 90, South is 180, and so forth.
First, when you start updates, be sure to check to make sure headings are available:
if (([CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXTrueNorthZVertical) != 0) {
...
}
Next, when you start the motion manager, ask for attitude as a rotation from X pointing true North (or Magnetic North if you need that for some reason):
[motionManager startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXTrueNorthZVertical
toQueue: self.motionQueue
withHandler: dmHandler];
When the motion manager reports a motion update, you want to find out how much the device has rotated in the X-Y plane. Since we are interested in the top of the iPhone, we'll pick a point in that direction and rotate it using the returned rotation matrix to get the point after rotation:
[m11 m12 m13] [0] [m12]
[m21 m22 m23] [1] = [m22]
[m31 m32 m33] [0] [m32]
The funky brackets are matrices; it's the best I can do using ASCII. :)
The heading is the angle between the rotated point and true North. We can use the X and Y coordinates of the rotated point to extract the arc tangent, which gives the angle between the point and the X axis. This is actually 180 degrees off from what we want, so we have to adjust accordingly. The resulting code looks like this:
CMDeviceMotionHandler dmHandler = ^(CMDeviceMotion *aMotion, NSError *error) {
// Check for an error.
if (error) {
// Add error handling here.
} else {
// Get the rotation matrix.
CMAttitude *attitude = self.motionManager.deviceMotion.attitude;
CMRotationMatrix rm = attitude.rotationMatrix;
// Get the heading.
double heading = PI + atan2(rm.m22, rm.m12);
heading = heading*180/PI;
printf("Heading: %5.0f\n", heading);
}
};
There is one gotcha: If the top of the iPhone is pointed straight up or straight down, the direction is undefined. The result is m21 and m22 are zero, or very close to it. You need to decide what this means for your app and handle the condition accordingly. You might, for example, switch to a heading based on the -Z axis (behind the iPhone) when m12*m12 + m22*m22 is close to zero.
This all assumes you want to rotate about the X-Y plane, as Apple usually does for their compass. It works because you are using the rotation matrix returned by the motion manager to rotate a vector pointed along the Y axis, which is this matrix:
[0]
[1]
[0]
To rotate a different vector--say, one pointed along -Z--use a different matrix, like
[0]
[0]
[-1]
Of course, you also have to take the arc tangent in a different plane, so instead of
double heading = PI + atan2(rm.m22, rm.m12);
you would use
double heading = PI + atan2(-rm.m33, -rm.m13);
to get the rotation in the X-Z plane.
I have two items, lets call them Obj1 and Obj2... Both have a current position pos1 and pos2.. Moreover they have current velocity vectors speed1 and speed2 ... How can I make sure that if their distances are getting closer (with checking current and NEXT distance), they will move farther away from eachother ?
I have a signed angle function that gives me the signed angle between 2 vectors.. How can I utilize it to check how much should I rotate the speed1 and speed2 to move those sprites from eachother ?
public float signedAngle(Vector2 v1, Vector2 v2)
{
float perpDot = v1.X * v2.Y - v1.Y * v2.X;
return (float)Math.Atan2(perpDot, Vector2.Dot(v1, v2));
}
I check the NEXT and CURRENT distances like that :
float currentDistance = Vector2.Distance(s1.position, s2.position);
Vector2 obj2_nextpos = s2.position + s2.speed + s2.drag;
Vector2 obj1_nextpos = s1.position + s1.speed + s1.drag;
Vector2 s2us = s2.speed;
s2us.Normalize();
Vector2 s1us = s1.speed;
s1us.Normalize();
float nextDistance = Vector2.Distance(obj1_nextpos , obj2_nextpos );
Then depending whether they are getting bigger or smaller I want to move them away (either by increasing their current speed at the same direction or MAKING THEM FURTHER WHICH I FAIL)...
if (nextDistance < currentDistance )
{
float angle = MathHelper.ToRadians(180)- signedAngle(s1us, s2us);
s1.speed += Vector2.Transform(s1us, Matrix.CreateRotationZ(angle)) * esc;
s2.speed += Vector2.Transform(s2us, Matrix.CreateRotationZ(angle)) * esc;
}
Any ideas ?
if objects A and B are getting closer, one of the object components (X or Y) is opposite.
in this case Bx is opposite to Ax, so only have to add Ax to the velocity vector of object B, and Bx to velocity vector of object A
If I understood correctly, this is the situation and you want to obtain the two green vectors.
The red vector is easy to get: redVect = pos1 - pos2. redVect and greenVect2 will point to the same direction, so the only step you have is to scale it so its length will match speed2's one: finalGreenVect2 = greenvect2.Normalize() * speed2.Length (although I'm not actually sure about this formula). greenVect1 = -redVect so finalGreenVect1 = greenVect1.Normalize() * speed1.Length. Then speed1 = finalGreenVect1 and speed2 = finalGreenVect2. This approach will give you instant turn, if you prefer a smooth turn you want to rotate the speed vector by:
angle = signedAngle(speed) + (signedAngle(greenVect) - signedAngle(speed)) * 0.5f;
The o.5f is the rotation speed, adjust it to any value you need. I'm afraid that you have to create a rotation matrix then Transform() the speed vector with this matrix.
Hope this helps ;)