Spatial Rotation Problems using Quaternion.fromAxisAngle()

I am converting an old project from Java3D in which we used AxisAngles to store rotation.



In Jmonkey I am having some trouble getting things working sensibly.



Here’s the behavior I am getting that I think might be a bug:



So I have a class that extends spatial, in the main loop it calls a method called perTimeStepBehavior(float timestep)

I am currently making it rotate about it’s axis of rotation using the following code:



[java]Quaternion q = this.getLocalRotation();

Vector3f axis = new Vector3f();

float angle = q.toAngleAxis(axis);

angle+=time;

angle %=2*Math.PI;

this.setLocalRotation(q.fromAngleAxis(angle, axis));[/java]



In the constructor for the class I’m using you give it an initial angle, and it sets the quaternion using an axis angle where it rotates by that amount around the Z axis.



I started using quaternion.fromAxisAngle:



[java]object extending spatial (float angle)

Quaternion quat = this.getLocalRotation();

quat.fromAngleAxis(angle, Vector3f.UNIT_Z);[/java]



When given an angle of 0, it rotated about the X axis instead of the Z axis. If I do angle = 0 + Float.MIN_VALUE (the smallest positive value of a float) then it rotates correctly around the Z axis.



Also if I use [java]quat.fromAngleNormalAxis(0,Vector3f.UNIT_Z);[/java]

instead of fromAngleAxis

It once again rotates correctly around the Z axis.



If this isn’t buggy behavior I am describing, please correct me.

The only difference between fromAngleAxis() and fromAngleNormalAxis() is that fromAngleAxis() normalizes the axis before calling fromAngleNormalAxis(). In the case of UNIT_Z, these calls should be identical unless you’ve somehow corrupted UNIT_Z by modifying it somewhere else by accident. And in that case, I’d expect fromAngleNormalAxis() to be the one with odd behavior.



Looking at the math, I have trouble understanding how an angle of 0 will give you any rotation at all. It looks like you should end up with an identity Quaternion to me. There must be relevant code we aren’t seeing or your analysis of results is incorrect.



Relevant bit of Quaternion code:

[java]

float halfAngle = 0.5f * angle;

float sin = FastMath.sin(halfAngle);

w = FastMath.cos(halfAngle);

x = sin * axis.x;

y = sin * axis.y;

z = sin * axis.z;

[/java]



An angle of 0 should result in x,y,z,w of 0,0,0,1… which is “no rotation”.



By the way, if you just want rotation around the Z axis then it’s easier to use fromAngles(0,0,angle)… though do note that in all cases, Z is not “up” if that’s what you are expecting. Z points out of the screen, so to speak.

haven’t looked at the logic, but what if u do



[java]Quaternion q = this.getLocalRotation().clone();[/java]

as you may be changing the original reference, not sure

1 Like

I want to be changing the original one, that’s how it’s rotating.



I am just saying it is very very odd that it does something different when I use fromAngleAxis and fromAngleNormalAxis.



The euler angles option doesn’t work exactly how I would expect either.



I am going to run some more tests tomorrow and see if I can do things how I want them using the euler angles.

@valentiinro said:
The euler angles option doesn't work exactly how I would expect either.


I would need to more to comment further.

Just remember, y is up and z is forward. That catches a lot of people familiar with different vis-sim frameworks.

Forward implies a direction… I usually say “into the screen”



Correct me if I am wrong, y is up, x is right, and z is into the screen… or out?

@valentiinro said:
Forward implies a direction... I usually say "into the screen"

Correct me if I am wrong, y is up, x is right, and z is into the screen... or out?


Z is out.

Direction is important when trying to determine how some of the lookAt related methods work. JME considers Z to be "forward" when that sort of thing matters. And in that case, positive X would be to the left.

don’t say you looked at these, it cannot be, they both mention z being towards you :stuck_out_tongue:

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:scenegraph_for_dummies

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies

@normen @pspeed It doesn’t matter one bit which way Z is in this context. That would only change which way the thing rotates about the Z axis.

I know my axes, the +/- direction doesn’t affect that the object should be rotating about the z axis, and is not always doing so.



Here is a different code I tried using Euler angles instead.

This function gets called often with the amount of time since the last call being the time float input.

[java]

public void perTimeStepBehavior(float time){

Quaternion q = this.getLocalRotation();

float[] angles = new float[3];

angles = q.toAngles(angles);

angles[2]= angles[2]+time;

System.out.println("The angle is "+angles[2]);

q = q.fromAngles(angles);

this.setLocalRotation(q);

}[/java]



Why isn’t this causing a constant z rotation?



Here is the output I am getting:

The angle is 1.6000797

The angle is 1.6000919

The angle is 1.600084

The angle is 1.6010742

The angle is 1.6001043

The angle is 1.6000881



Obviously the frame time isn’t adding to the angle.



Does the quaternion ignore values that are too small or something?

@pspeed said:
The only difference between fromAngleAxis() and fromAngleNormalAxis() is that fromAngleAxis() normalizes the axis before calling fromAngleNormalAxis(). In the case of UNIT_Z, these calls should be identical unless you've somehow corrupted UNIT_Z by modifying it somewhere else by accident. And in that case, I'd expect fromAngleNormalAxis() to be the one with odd behavior.

Looking at the math, I have trouble understanding how an angle of 0 will give you any rotation at all. It looks like you should end up with an identity Quaternion to me. There must be relevant code we aren't seeing or your analysis of results is incorrect.

Relevant bit of Quaternion code:
[java]
float halfAngle = 0.5f * angle;
float sin = FastMath.sin(halfAngle);
w = FastMath.cos(halfAngle);
x = sin * axis.x;
y = sin * axis.y;
z = sin * axis.z;
[/java]

An angle of 0 should result in x,y,z,w of 0,0,0,1... which is "no rotation".

By the way, if you just want rotation around the Z axis then it's easier to use fromAngles(0,0,angle)... though do note that in all cases, Z is not "up" if that's what you are expecting. Z points out of the screen, so to speak.


The code with 0 angle is the initial setup that happens on construction.

the
Quaternion q = this.getLocalRotation();
Vector3f axis = new Vector3f();
float angle = q.toAngleAxis(axis);
angle+=time;
angle %=2*Math.PI;
this.setLocalRotation(q.fromAngleAxis(angle, axis));

code happens later, and occurs every frame or so, with time being the time since last call of the function.
@valentiinro said:
[java]
public void perTimeStepBehavior(float time){
Quaternion q = this.getLocalRotation();
float[] angles = new float[3];
angles = q.toAngles(angles);
angles[2]= angles[2]+time;
System.out.println("The angle is "+angles[2]);
q = q.fromAngles(angles);
this.setLocalRotation(q);
}[/java]


[java]
// Use either...
float[] angles = q.toAngles(null);
// or...
float[] angles = new float[3];
q.toAngles(angles);
[/java]

You're already converting it from a Quaternion... why not do this?

[java]
public void perTimeStepBehavior(float time){
float[] angles = this.getLocalRotation().toAngles(null);
angles[2] += time;
// This quaternion already exists... might as well reuse it since it's going back to the same place
this.setLocalRotation(this.getLocalRotation().fromAngles(angles));
}[/java]
1 Like
@t0neg0d said:
[java]
// Use either...
float[] angles = q.toAngles(null);
// or...
float[] angles = new float[3];
q.toAngles(angles);
[/java]

You're already converting it from a Quaternion... why not do this?

[java]
public void perTimeStepBehavior(float time){
float[] angles = this.getLocalRotation().toAngles(null);
angles[2] += time;
// This quaternion already exists... might as well reuse it since it's going back to the same place
this.setLocalRotation(this.getLocalRotation().fromAngles(angles));
}[/java]


I tried doing it. The reason I spelled it out all weird is so I could try to figure out what step of the process was breaking.

That code does not cause rotation in my program.

I used

this.rotate(0,0,time);

which did work, but I should be able to access and modify the rotation directly instead of accessing it through the rotate method.

I have been going through the various code for quaternion and spatial, and as of right now, I guess the only source of my problem could be Transform, so I am going to go over the code for that and see if there’s some sort of truncation going on there for small differences or something,



EDIT:



Spatial.setTransformRefresh()



This sets some flags and such which I imagine have to get passed somewhere else.



Could I be retrieving the angles before they are done being set or something?



Alternatively maybe I am having some sort of threading error.

I think you may have a misunderstanding of how quaternion works since you seem to want it to be the holder for your angle.



Rotation != orientation in this case, however. Just because you rotate an axis to form a quaternion does not mean that you will get the same axis and angle back out of it. A trivial example is if you rotate 9000 degrees around an axis… it will only produce a quaternion that rotates +/- 180 degrees.



If you want to accumulate an angle based on time then you will have to accumulate an angle based on time. And then use that to calculate your quaternion. You cannot use the quaternion to store your angle because in most cases you won’t get it back again.



Alternately, figure out how much you want to move each frame (angle * tpf) and then make a Quaternion from that and multiply it by the existing one. This will accumulate the rotation in the quaternion… and allow you to rotate the object separately also.

1 Like
@pspeed said:
I think you may have a misunderstanding of how quaternion works since you seem to want it to be the holder for your angle.

Rotation != orientation in this case, however. Just because you rotate an axis to form a quaternion does not mean that you will get the same axis and angle back out of it. A trivial example is if you rotate 9000 degrees around an axis... it will only produce a quaternion that rotates +/- 180 degrees.

If you want to accumulate an angle based on time then you will have to accumulate an angle based on time. And then use that to calculate your quaternion. You cannot use the quaternion to store your angle because in most cases you won't get it back again.

Alternately, figure out how much you want to move each frame (angle * tpf) and then make a Quaternion from that and multiply it by the existing one. This will accumulate the rotation in the quaternion... and allow you to rotate the object separately also.


Okay, that's what I figured but my friend who I am working with was like "they already store the rotation information, just extract it from there" and I'm like "It's not that easy"

But shouldn't I be able to do Quaternion.toAngles and get the Euler version of the Quaternion and extract the angle from that, or will that not work?

I mean, shouldn't this work:
[java]public void perTimeStepBehavior(float time){
float[] angles = this.getLocalRotation().toAngles(null);
angles[2] += time;
// This quaternion already exists... might as well reuse it since it's going back to the same place
this.setLocalRotation(this.getLocalRotation().fromAngles(angles));
}[/java]
@valentiinro said:
I mean, shouldn't this work:


No.
" Just because you rotate an axis to form a quaternion does not mean that you will get the same axis and angle back "
@jmaasing said:
No.
" Just because you rotate an axis to form a quaternion does not mean that you will get the same axis and angle back "

In the code after "shouldn't this work:" you'll see I'm not using an axis angle situation but a Euler angle situation.

[java]public void perTimeStepBehavior(float time){
float[] angles = this.getLocalRotation().toAngles(null);
angles[2] += time;
// This quaternion already exists... might as well reuse it since it's going back to the same place
this.setLocalRotation(this.getLocalRotation().fromAngles(angles));
}[/java]

In any case, just to be sure, if I called this in Application.simpleUpdate would that be the wrong place to call it?

It might work. But what is GUARANTEED to work is to keep the angle as a private field and just add to it each time or create a Quaternion.fromAngles( 0, time, 0) and multiply it by the existing quaternion.



You make a lot of assumptions doing it the way you are… for one, it assumes that the other two eulers are always 0. For another, it assumes that toAngles() will produce the same results that were passed to fromAngles(). This is probably true for the y-axis but is definitely not guaranteed to be true for the other axes.



A set of Euler angles will produce exactly one Quaternion… but one Quaternion can produce a potentially large number of Euler angle sets. It’s dangerous to assume that you will get out what you put in and it will be confusing later if something else changes that causes odd behavior.



Just as an academic example of what I’m talking about, think of a quaternion that represents Euler angles: 0, 90, 90. Then think of a quaternion that represents -90, 0, 90. These are the same quaternion. (Not exactly sure about the - on the x-axis but my point should be clear.) This same reordering can happen with many kinds of combinations if all three axes are in play. If you mange gimbal lock then an infinite number of Euler angle sets can produce the same Quaternion. Euler angles are not as exact as a Quaternion at representing an orientation… so you cannot expect them to always be reciprocal.

1 Like

Yeah I understand that now.



Thanks, I’ll store the angle however I feel like storing it and avoid attempting to extract information I can use from the rotation quaternion.