Advertisement

[Getting angular velocity from a quaternion]

Started by July 14, 2022 09:17 AM
5 comments, last by JoeJ 2 years, 4 months ago

Hello guys,

I hope you're all doing well.

So there is an example in Qt where they created an application that rotates a cube using Mouse event.

In order to get the quaternion to rotate the object, they did this:

void MainWidget::timerEvent(QTimerEvent *)

{
// Decrease angular speed (friction)

angularSpeed *= 0.99;
// Stop rotation when speed goes below threshold

if (angularSpeed < 0.01)

{ angularSpeed = 0.0;

}

else { // Update rotation

rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;

qDebug()<<"rotation"<<rotation;

// Request an update

update();

}

}

So the quaternion was calculated using the angluar speed and using the function:

QQuaternion QQuaternion::fromAxisAndAngle(const QVector3D &axis, float angle).

=> Creates a normalized quaternion that corresponds to rotating through angle degrees about the specified 3D axis.

In this example the angle is the angular speed.

There is another function that determines the rotation axis and the angle from the quaternion.

void QQuaternion::getAxisAndAngle(QVector3D *axis, float *angle) const.

=> Extracts a 3D axis axis and a rotating angle angle (in degrees) that corresponds to this quaternion.

So my question is does the function “ getAxisandAngle” determines an angle which is THE ANGULAR SPEED or it's only the rotation angle. Because I need to determine the angular velocity from the quaternion. I thought this would be the right function to do so.

Because as I mentioned, in the example of the cube, they calculated quaternion using “FromAxisAndAngle” function where angle is the angular speed.

Please help.

Thank you all.

ogldev1 said:
does the function “ getAxisandAngle” determines an angle which is THE ANGULAR SPEED or it's only the rotation angle.

It gives the rotation angle.

In physics simulation it is common to express angular velocity or torque with ‘rotation vectors’. But i'm not sure if this is an established term at all - i just use it personally.
It is a simple 3D vector of axis times angle. Visualizing this vector, we would see it's direction is perpendicular to the plane of rotation, and the longer it is, the faster the object spins due to larger angle.

rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;

This is typical code to integrate angular velocity to the orientation of a body. (It's mostly done using quaternions, because they make it easy to convert from / to axis and angle. Notice axis and angle is the most intuitive way to imagine 3D rotations, unlike Euler angles for example.)
Personally i think it's bad practice to call this ‘rotation’ like they did. Because it mentally helps to distinguish between rotations and orientations.
To make it more clear, i would eventually rewrite it like so:

void IntegrateAngularVelocity (
	sQuat &predictedOrn, // resulting new body orientation
	const sQuat &curOrn, // current orientation of the body
	const sVec3 &angVel, // angular velocity rotation vector
	const float timeStep)
{
	float angle = angvel.Length(); // length of the rotation vector gives us rotation angle 
	
	if ( angle < FP_EPSILON ) // if velocity is zero, return current orentation
	{
		predictedOrn = curOrn;

	}
	else
	{
		// the angle we got is per unit time, so we need to multiply by our timestep. 
		// Your example ignores this by using 'angular speed' to represent one frame, not a unit of time (usually this unit is one second)
		//
		// due to the quaternion property of using half angles, the code is hard to read, but actually it represents axis angle -> quaternion conversation
		// It should work to do this instead the next two lines: temp = QQuaternion::fromAxisAndAngle(angVel / angle, angle * timestep)
		// notice angVel / angle just gives us a unit vector to represent the rotation axis
		
		sVec3 axis = angVel * (sin (angle * timeStep * .5f) / angle);
		sQuat temp (axis.x, axis.y, axis.z, cos (angle * timeStep * .5f));
		
		predictedOrn = temp * curOrn;
		predictedOrn.Normalize();
	}
}

Copied this from some old project, and because i did not use helper function for conversation it's probably more confusing than helpful. I failed at making it more clear ; )

You can ignore the timestep topic as well for now. It would help e.g. if your app has fluctuating frame times like 10ms, 30ms, 9ms, 50ms…
Ignoring timesteps but assuming a constant frame time, your animation would look jumpy and discontinuous.
But using variable timesteps, you can sync application time to realtime, so the animation would look as smooth as possible.

Just fyi. Probably you don't want to dig into simulation topics yet, but good to keep in mind.

Advertisement

First of all, thank you for your explanation.

And yes, exactly that's what I'm willing to do.

I have a csv file that contains data. I extract quaternions from the data and rotate the cube.

So when running the application the user clicks on a push button to import file.csv and see an animation of the cube's orientation. ( 24 orientations for now).

However the animation is jumpy and discontinous like you've just said.

My goal is to make it very smooth so I thought about integrating the angular velocity.

I found this code (unity):

QVector3D MainWidget::DetermineAngularVelocity(QQuaternion myquaternion, float deltaTime)

{ float angleInDegrees;

QVector3D rotationAxis;

myquaternion.getAxisAndAngle(&rotationAxis, &angleInDegrees);

QVector3D angularDisplacement = rotationAxis * qDegreesToRadians(angleInDegrees) ;

angularSpeed = angularDisplacement / deltaTime;
return angularSpeed;
}

I changed some simple stuff since it was developed using unity and C# I think.

Apparently it works on unity But when I tried on Qt, I get the error: “assigning to 'qreal' (aka 'double') from incompatible type 'QVector3D”.

The error occurs when dividing the angular displacement by the delta time, However I don't see what's wrong with dividing a vector by a float.

Also how can I predict the orientations that exists between the first orientation and the second one.

I mean for example; I have quaternion q1 for the first orientation

q2 that correpsonds to the next one.

How can I predict the different quaternions that corresponds to the different orientations that exists between q1 and q2. I don't know if I'm being clear or not.

That's another problem I'm facing :')

ogldev1 said:
Apparently it works on unity But when I tried on Qt, I get the error: “assigning to 'qreal' (aka 'double') from incompatible type 'QVector3D”.

Some vector libraries lack a division by scalar operator, then you need to divide using multiplication instead:

//angularSpeed = angularDisplacement / deltaTime;
angularSpeed = angularDisplacement * 1.f / deltaTime;

Though, i'm confused about the missing declaration of angularSpeed to be a vector. Probably a global or member variable, but make sure it is indeed a vector and not a scalar.
Also, i guess your ‘myquaternion’ represents orientation, e.g. one keyframe.
But it makes no sense to calculate angular velocity from a single orientation.
Instead, you could calculate the rotation between two orientations, and then you could calculate angular velocity representing this rotation to rotate from the first to the second orientation.

ogldev1 said:
How can I predict the different quaternions that corresponds to the different orientations that exists between q1 and q2. I don't know if I'm being clear or not.

I guess you have a sequence of orientation quats form your file, and you want to interpolate along the sequence smoothly, just like turning keyframes into animation.

Notice this is not necessarily a matter of integrating angular velocity. We could also use simple interpolation instead, which usually is how animation works (contrary to simulation).

To interpolate quaternions, we have two options:

Spherical interpolation, e.g.:

float t = 0.3; // we want to interpolate 30% of q0 plus 70% of q1
quaternion q = quaternion::Slerp (q0, q1, t);

So by raising t gradually from 0 to 1, we get a smooth interpolation from q0 to q1.

That's easy, and i'm sure your library has such function with a similar name.

But doing it this way, we get a discontinuity at the keyframes, because there we have a hard switch from the former to the next keyframe in the sequence.
The usual solution is to use splines, made from 4 keyframes. For a spline to generate a curve from points this works, this works like so, e.g. using a Catmull Rom spline:

We want to calculate the curve point on a segment 2-3, at a t value of 0.3

To do so, we first calculate two vectors 1-3 (green) and 2-4 (blue). Then we half them, and transport them to points 2 and 3. (shorter green and blue lines)

Then we use t on those short vectors to calculate points P0 and P1 (orange thick line), and then we use smoothstep(t) to mix P0 and P1 to get our curve point (orange X).

The same process can be done using quaternions.
Instead using vectors to represent the difference between two points, we use the rotation between two orientations.
Instead using

float k = smoothstep(t);
vec3 X = P0 * (1-k) + P1 * k;

to interpolate two points, we multiply the rotation angle of our rotation:

	sQuat QuatFromAToB (const sQuat &qA, const sQuat &qB) // gives the shortst arc rotation in global space
	{
		sQuat q;
		if (qA.Dot(qB) < 0.0f) // ptifall: we need this check to pick the shrter, not the longer arc
		{
			q[0] = qA[0]; q[1] = qA[1]; q[2] = qA[2];
			q[3] = -qA[3]; 
			// My convention is [0],[1],[2] : x,y,z; [3] : w; So the order of values is xyzw. 
			// But some libs use the convention of wxyz to toder things in memory, so my code would need to be changed accordingly
		}
		else
		{
			q[0] = -qA[0]; q[1] = -qA[1]; q[2] = -qA[2];
			q[3] = qA[3];
		}
		
		return qB * q;
	}
	
	sQuat rotation = QuatFromAToB(Q0, Q1);
	
	// change angle
	sQuat axis; float angle;
	rotation.ToAxisAndAngle (axis, angle);
	angle *= smoothstep(t);
	rotation.FromAxisAndAngle (axis, angle);
	
	sQuat X = rotation * Q0;

As you see, it's quite involved if you're still new to 3D rotations.
The ‘pitfall’ handles the fact that we could rotate 90 degrees to get from A to B, but we could also rotate 270 degrees the other way around to get from A to B as well.
So there are two rotations which give the same results. But if we multiply the angles of those two fro interpolation needs, we see the result differs. Thus we need to handle this and usually we always want the shorter arc.

@joej Okay, first of all thank you for your explanation.

I thought about the interpolation but then I totally forgot about it and you reminded me of it.

Okay as a beginner, I'm going to start with the simple interpolation.

The duration between two consecutive orientations is around 10 seconds. (and I have 24 orientations).

So I thought about doing many interpolations ( about 10 interpolations) even tho I know that there's going to be discontinuities. I mean the interpolation function will be a recursive one.

So if the duration of each orientation is 10 seconds, after doing 10 interpolations, the duration of each new orientation ( corresponding to the interpolated quaternions) will be 1 seconds right ?

1 second is enough to minimize the discontinuity right ?

ogldev1 said:
I mean the interpolation function will be a recursive one.

Interpolation is not recursive by definition.
But just saying, recursive methods can well be used to smooth out stuff. E.g. Catmull Clark subdivision or constructing Bezier curves recursively.

ogldev1 said:
1 second is enough to minimize the discontinuity right ?

Assuming we are at the idea to interpolate just two quaternion keys (which i agree is the proper choice for a start), there is no way to avoid or fix the discontinuity at the keys.
So i would just accept this, but consider to eventually fix it later using such method as the splines i have proposed. The things you learn on your way are needed to implement advanced methods anyway, so nothing's lost.

But i don't see a need to display the same orientation over a whole second. You can just rotate it smoothly for each frame. Effort is the same, but looks better.

Advertisement

@joej Thank you so much, you made it very clear :D

This topic is closed to new replies.

Advertisement