🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Separating components of a free form rotation

Started by
7 comments, last by OpaqueEncounter 6 years, 1 month ago

A have a "free form" rotation method that looks like this:


Vector3 screenVector = new Vector3(delta.X, -delta.Y, 0.0f);
Vector3 rotationVector = Vector3.Cross(Vector3.UnitZ, screenVector);
float angle = rotationVector.Length();
rotation = Quaternion.Concatenate(rotation, Quaternion.CreateFromAxisAngle(Vector3.Normalize(rotationVector), angle));
world = Matrix.CreateFromQuaternion(rotation);

The values of delta.X/delta.Y are displacement values along the screen coordinates. This allows me to freely rotate an object in 3D, no matter how it is orientated using touch/mouse movements.

I wish to smooth this out by adding a tweener to the rotation. This seems straightforward: split up the X and Y components of the rotation vector (the cross product above), separate the angle value in to two values X and Y, tween the angles, and then create a composite Quaternion. However, this doesn't seem to work in practice. When concatenating the two Quaternions, there is no movement along some arbitrary axis (depending on how much the object has been rotated). Furthermore, the object doesn't rotate freely anymore and instead starts to rotate in a manner similar to a third/first person camera (once the elevation goes past 90 degrees, the orientation is "flipped").

Instead of concatenating the Quaternions, I tried to Matrix.CreateFromQuaternion for each one instead, and then multiply the matrices. But this yields the exact same effect as described above.

Going to the extremes, I tried to tween the rotation vector, instead of manipulating the angles. This doesn't work as expected either. Whenever the direction changes, the normalization of the rotation vector causes it to rotate on the opposite axis to reverse direction. This means that if you're rotating along the X axis in the positive direction and then suddenly start rotating in the negative direction, at some point during the tweening affect, there will be some rotation along the Y axis, as the rotation "falls over" in the opposite direction.

Any recommendations on making this work correctly? Tweening the angle values does feel like the right way to go about it, but how do I construct the correct final rotation matrix if I separate them out?

Advertisement

You could convert the rotation to a rotation vector and split it into two parts (Note that i use the term 'rotation vector' differently: I mean axis times angle, like used to represent angular velocity. Your usage of the term in your snippet is misleading, you should call it 'axisVector' instead.)


	quaternion initialRotation = ...whatever;
	vec3 rotationVector;
	{
	vec3 axis; float angle;
	initialRotation.ConcertToaxisandAngle(&axis, &angle);
	rotationVector = axis * angle;
	}
	

From there it's intuitive to split the rotation vector using dot products, modify them, and recombine to a single rotation:


	vec3 splitDirection0 (1,0,0);
	vec3 splitDirection1 (0,1,0);
	vec3 splitRV0 = splitDirection0.Dot(rotationVector);
	vec3 splitRV1 = splitDirection1.Dot(rotationVector);
	// do some processing with splitRV0 and splitRV1 like tweening
	// recombine:
	vec3 alteredRotationVector = splitRV0 + splitRV1;
	float angle = alteredRotationVector.Length();
	quaternion newRotation = Quaternion.CreateFromAxisAngle (alteredRotationVector / angle, angle); // not altering the vectors, newRotation would equal initialRotation
	

Of course you can calculate  splitRV0 and splitRV1 directly from mouse input, so no need to generate them from an initial quaternion - i just showed that for completeness. The advantage is that this method is order independent, so no gimbal lock or up-vector like behavior. 

Hope this helps (i'm not really sure what you want to do exactly).

 

Edit: To smooth user input, it might work best to calculate angular velocity between current and target orientation, smooth this (mixing with previous value), and integrate it to current orientation. It might also help to limit angular acceleration to a max value.

Thinking of it this should be the way to go - seperating angles does not make so much sense. Let me know what you think - i'd need to look some things up, as i'm not into physics right now...

 

Bugfix: To preserve the initial rotation this would be correct:

vec3 splitRV1 = rotationVector - splitRV0;

I accidently seperated rotations along x and y axis only, so summing them the initial z rotation would be still missing.

I don't understand your "straightforward" plan . Try to explain very carefully what you are trying to do. The first word I don't understand in your description is "affect". I am not being pedantic: I honestly don't understand what you are trying to do, even if I substitute "effect" there. "Tweening" (I had to look it up) presumably refers to interpolation, but between what and what?

If you expand the cross product, you'll see that your rotation vector is something very simple, like (delta.Y, delta.X, 0.0) or something like that. The way your code is written obscures what's going on.

On 5/21/2018 at 2:31 AM, JoeJ said:

You could convert the rotation to a rotation vector and split it into two parts (Note that i use the term 'rotation vector' differently: I mean axis times angle, like used to represent angular velocity. Your usage of the term in your snippet is misleading, you should call it 'axisVector' instead.)



	quaternion initialRotation = ...whatever;
	vec3 rotationVector;
	{
	vec3 axis; float angle;
	initialRotation.ConcertToaxisandAngle(&axis, &angle);
	rotationVector = axis * angle;
	}
	

From there it's intuitive to split the rotation vector using dot products, modify them, and recombine to a single rotation:



	vec3 splitDirection0 (1,0,0);
	vec3 splitDirection1 (0,1,0);
	vec3 splitRV0 = splitDirection0.Dot(rotationVector);
	vec3 splitRV1 = splitDirection1.Dot(rotationVector);
	// do some processing with splitRV0 and splitRV1 like tweening
	// recombine:
	vec3 alteredRotationVector = splitRV0 + splitRV1;
	float angle = alteredRotationVector.Length();
	quaternion newRotation = Quaternion.CreateFromAxisAngle (alteredRotationVector / angle, angle); // not altering the vectors, newRotation would equal initialRotation
	

Of course you can calculate  splitRV0 and splitRV1 directly from mouse input, so no need to generate them from an initial quaternion - i just showed that for completeness. The advantage is that this method is order independent, so no gimbal lock or up-vector like behavior. 

Hope this helps (i'm not really sure what you want to do exactly).

 

Edit: To smooth user input, it might work best to calculate angular velocity between current and target orientation, smooth this (mixing with previous value), and integrate it to current orientation. It might also help to limit angular acceleration to a max value.

Thinking of it this should be the way to go - seperating angles does not make so much sense. Let me know what you think - i'd need to look some things up, as i'm not into physics right now...

 

Bugfix: To preserve the initial rotation this would be correct:

vec3 splitRV1 = rotationVector - splitRV0;

I accidently seperated rotations along x and y axis only, so summing them the initial z rotation would be still missing.

I am half way through with your solution. You just have a typo where you're using the Dot product to split the axes. It's actually 


vec3 splitRV0 = splitDirection0.Cross(rotationVector);

Other than that, I verified that the decomposition and re-combination of these vectors in your solution is correct. The only problem is the actual manipulation of the splitRV0 and splitRV1 vectors. No matter what I do with them (tweener, multiply, add an unit axis to them) the result always changes the rotation from free form to some constraint. The question I have now is how to manipulate splitRV0 and splitRV1?

2 hours ago, OpaqueEncounter said:

I am half way through with your solution. You just have a typo where you're using the Dot product to split the axes. It's actually 



vec3 splitRV0 = splitDirection0.Cross(rotationVector);

Other than that, I verified that the decomposition and re-combination of these vectors in your solution is correct. The only problem is the actual manipulation of the splitRV0 and splitRV1 vectors. No matter what I do with them (tweener, multiply, add an unit axis to them) the result always changes the rotation from free form to some constraint. The question I have now is how to manipulate splitRV0 and splitRV1?

Oops, Sorry! But the fix is totally different - dot product is intended:


	vec3 splitRV0 = splitDirection0 * splitDirection0.Dot(rotationVector);
	vec3 splitRV1 = rotationVector - splitRV0;
	

Here an example to make more sense:

 

vec3 rotationVector (2, 0.5, 0);

vec3 directionOfInterest (1,0,0);

vec3 rotationOfInterest = directionOfInterest * directionOfInterest.Dot(rotationVector); // (2, 0, 0);

vec3 remainingRotation = rotationVector - rotationOfInterest; // (0, 0.5, 0);

 

// directionOfInterest.Cross(rotationVector) would result in (0,0, 0.5), which makes no sense to me here

 

My point here is only to show one way of decomposing rotations into multiple parts and how simple it is using rotation vectors.

It's in response to the topics title, but i'm not sure if this can help you - it's still unclear what you try to do. (I was thinking about a simple temporal filter for smoothing with the split components: v = oldV * 0.9 + newV * 0.1)

I think of an adventure game, where you have an item in inventory, select it to view it close up, rotate it with mouse to see it from every angle. But you want to smooth the way mouse rotates the object so it feels heavy for example. But here decomposition makes little sense - looking up angular equations of motion to model moment of inertia (or using physics engine), and converting mouse to torque would be the right approach to achieve this.

On 5/21/2018 at 6:43 AM, alvaro said:

I don't understand your "straightforward" plan . Try to explain very carefully what you are trying to do. The first word I don't understand in your description is "affect". I am not being pedantic: I honestly don't understand what you are trying to do, even if I substitute "effect" there. "Tweening" (I had to look it up) presumably refers to interpolation, but between what and what?

If you expand the cross product, you'll see that your rotation vector is something very simple, like (delta.Y, delta.X, 0.0) or something like that. The way your code is written obscures what's going on.

Poor final editing on my part. My bad. I re-edited that sentence to make it clearer.

18 hours ago, JoeJ said:

Oops, Sorry! But the fix is totally different - dot product is intended:



	vec3 splitRV0 = splitDirection0 * splitDirection0.Dot(rotationVector);
	vec3 splitRV1 = rotationVector - splitRV0;
	

Here an example to make more sense:

 

vec3 rotationVector (2, 0.5, 0);

vec3 directionOfInterest (1,0,0);

vec3 rotationOfInterest = directionOfInterest * directionOfInterest.Dot(rotationVector); // (2, 0, 0);

vec3 remainingRotation = rotationVector - rotationOfInterest; // (0, 0.5, 0);

 

// directionOfInterest.Cross(rotationVector) would result in (0,0, 0.5), which makes no sense to me here

 

My point here is only to show one way of decomposing rotations into multiple parts and how simple it is using rotation vectors.

It's in response to the topics title, but i'm not sure if this can help you - it's still unclear what you try to do. (I was thinking about a simple temporal filter for smoothing with the split components: v = oldV * 0.9 + newV * 0.1)

I think of an adventure game, where you have an item in inventory, select it to view it close up, rotate it with mouse to see it from every angle. But you want to smooth the way mouse rotates the object so it feels heavy for example. But here decomposition makes little sense - looking up angular equations of motion to model moment of inertia (or using physics engine), and converting mouse to torque would be the right approach to achieve this.

Thanks for this. I learned a few new things from your decomposition suggestions.

As for the original issue, I had to re-think the whole thing when you pointed out that you're not sure if this actually helps. And it doesn't; the change of axes in rotation always causes "jitters" when the components I provided above are decomposed. I ultimately solved this by interpolating the actual screen delta values, and then recalculating a new Quaternion every frame.

If I'm understanding the problem correctly, then how about keeping track of the TargetOrientation the same way you did originally, but then having a separate CurrentOriantation which continuously rotates toward TargetOrientation?

One way to do it would be CurrentOrientation = Quaternion.Slerp(CurrentOrientation, TargetOrientation, 0.5), so every frame you rotate half way between the last visual orientation and the target, giving an exponential deceleration as you approach the target.

 

Another way would be to get the difference between the current and target orientations (inverse transform target by current), convert to axis-angle, and then rotate around that axis but use an angular velocity that's either constant or updated separately so you can do acceleration/deceleration. Something like


Quaternion delta = Quaternion.Concatenate(targetOrientation, Quaternion.Conjugate(currentOrientation));
Vector3 axis;
float angle;
Quaternion.GetAxisAngle(delta, &axis, &angle);
currentOrientation = Quaternion.Concatenate(Quaternion.CreateFromAxisAngle(axis, angularVelocity), currentOrientation);

 

On 5/24/2018 at 7:15 AM, DekuTree64 said:

If I'm understanding the problem correctly, then how about keeping track of the TargetOrientation the same way you did originally, but then having a separate CurrentOriantation which continuously rotates toward TargetOrientation?

One way to do it would be CurrentOrientation = Quaternion.Slerp(CurrentOrientation, TargetOrientation, 0.5), so every frame you rotate half way between the last visual orientation and the target, giving an exponential deceleration as you approach the target.

 

Another way would be to get the difference between the current and target orientations (inverse transform target by current), convert to axis-angle, and then rotate around that axis but use an angular velocity that's either constant or updated separately so you can do acceleration/deceleration. Something like



Quaternion delta = Quaternion.Concatenate(targetOrientation, Quaternion.Conjugate(currentOrientation));
Vector3 axis;
float angle;
Quaternion.GetAxisAngle(delta, &axis, &angle);
currentOrientation = Quaternion.Concatenate(Quaternion.CreateFromAxisAngle(axis, angularVelocity), currentOrientation);

 

I ultimately solved as described in my previous post, but I did try your method at some point as well. The problem with it is that it is also susceptible to the effect I described in my original post, "if you're rotating along the X axis in the positive direction and then suddenly start rotating in the negative direction, at some point during the tweening affect, there will be some rotation along the Y axis, as the rotation "falls over" in the opposite direction."

This topic is closed to new replies.

Advertisement