How to X rotate object using other object as reference?

Hi,
I know it is another thread about rotation, but I am a bit confused here about Quaternions.
I have 4 cubes, one is the master, the others rotx,roty,rotz, I want when the user press space each cube should represent the respective rotation from the master cube.
I tried some code but I got very odd result, somehow, the master cube got rotated even if there is no code for it, and geomx cube got no rotation at all !

Code:
public class Main extends SimpleApplication {
Geometry geom1,geomX,geomY,geomZ; final float speed = 0.001f;

public static void main(String[] args) { Main app = new Main(); app.start(); }

@Override public void simpleInitApp() {
    flyCam.setEnabled(false); Box b = new Box(1, 1, 1);       
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.getAdditionalRenderState().setWireframe(true);
    mat.setColor("Color", ColorRGBA.White);        
    geom1 = new Geometry("Box", b); geom1.setLocalTranslation(0,2,0);
    geomX = new Geometry("Box", b); geomX.setLocalTranslation(-3.5f,-1,0);
    geomY = new Geometry("Box", b); geomY.setLocalTranslation(0,-1,0);
    geomZ = new Geometry("Box", b); geomZ.setLocalTranslation(3.5f,-1,0);        
    geom1.setMaterial(mat); geomX.setMaterial(mat); geomY.setMaterial(mat); geomZ.setMaterial(mat);        
    rootNode.attachChild(geom1); rootNode.attachChild(geomX); rootNode.attachChild(geomY); rootNode.attachChild(geomZ);
    inputManager.addMapping("RotateXup",  new KeyTrigger(KeyInput.KEY_Q)); inputManager.addMapping("RotateXdown",  new KeyTrigger(KeyInput.KEY_W));
    inputManager.addMapping("RotateYup",  new KeyTrigger(KeyInput.KEY_A)); inputManager.addMapping("RotateYdown",  new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping("RotateZup",  new KeyTrigger(KeyInput.KEY_Z)); inputManager.addMapping("RotateZdown",  new KeyTrigger(KeyInput.KEY_X));
    inputManager.addMapping("RefreshRot", new KeyTrigger(KeyInput.KEY_SPACE)); 
    inputManager.addListener(analogListener,"RotateXup", "RotateYup", "RotateZup","RotateXdown","RotateYdown","RotateZdown","RefreshRot");
}

private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {            
        if(name.equals("RotateXup")) geom1.rotate(speed, 0, 0);  if(name.equals("RotateXdown")) geom1.rotate(-speed, 0, 0);
        if(name.equals("RotateYup")) geom1.rotate(0, speed, 0);  if(name.equals("RotateYdown")) geom1.rotate(0, -speed, 0);
        if(name.equals("RotateZup")) geom1.rotate(0, 0, speed);  if(name.equals("RotateZdown")) geom1.rotate(0, 0, -speed);
        if(name.equals("RefreshRot")) {
            Quaternion origem = geom1.getLocalRotation();
            origem.set(origem.getX(), 0, 0, 0);
            geomX.setLocalRotation(origem);
        }
    }
};

Any help is appreciated !
Tx,
wagfeliz

You aren’t the first to do this and I’m not trying to be mean… but I do think it’s funny how many threads there are on the whole web “I don’t understand Quaternions so I just started poking random values into them hoping it would mean something…”

Quaternions are magic. Do not attempt to understand what the 4 values are… well not unless you can easily think in imaginary numbers and 4 dimensions. Then maybe you have a shot.

But with 10000000% certainty you can guarantee that x,y,z,w are not angles. They are not radians. They are not distances from Alpha Centauri. They are magic numbers that cannot exist on their own without the other three.

…which is a long winded way of saying that those lines of code are total nonsense. Enough so that I can only kind of half guess what you might have been trying to do.

Some time with Quaternion’s javadoc would probably be useful to you… especially centered on the fromAngles()/toAngles() methods that deal with Euler angles. I can only guess that this is what you were really trying to do… something with yaw, pitch, roll or somesuch.

I know this lines are wrong :

        Quaternion origem = geom1.getLocalRotation();
        origem.set(origem.getX(), 0, 0, 0);
        geomX.setLocalRotation(origem);

Just want to replace it with something that will make geomX to rotate at plane X, I coult not find any method on javadoc that get only the rotation on plane X from a rotated object…

Well, I just told you the exact methods. And there is no such thing as the “x plane”. I think you mean maybe that you want to rotate around the y axis?

Well, I guess so, my plan was to rotate the geomY with the Y axis from the geom1, rotate the geomX with the X axis from geom1, etc.

Use something like:

Vector3f origem=geomX.getLocalRotation().toAngles(); geomX.setLocalRotation(origen.mult(new Vector3f(1,0,0));

I don’t have javadocs now so i don’t know if i wrote the right methods.The concept is that you have to get a Vector3f with euler angles using toAngles() and working with them because working with Quaternions is really hard and time-expensive.

Conversion quaternions-euler and euler-quaternions is pretty performable in JME so you’ll not get bottlenecks and work with radians :smile:

There is literally a javadoc link right at the top of every page on the forum. (Edit: note, you have to scroll up because our forum is dumb about that.)

I assume by “time expensive” you mean “time for a person to understand them”. They are very fast and the most accurate representation of rotation. It’s why they are so convenient.

Where as Euler angles are easy to understand but slow, cumbersome, and ambiguous. They are the perfect thing for getting one angle out of a quaternion.

I already told OP about toAngles and fromAngles so maybe there is some reason those aren’t what he wanted.

Yes,i meant they need time to be understood.

Anyway i saw you told him but he didn’t even answered to that option so maybe he did not read or understand it,that’s why i made a little example of using them.

Yeah, I appreciate it. As life gets fuller some of us loathe repeating ourselves.

This comes up so often, I’m thinking maybe Quaternion’s fields should be called flibbit, green, tortoise, and blorp instead of x,y,z,w. Then maybe people would instantly understand that they are just values and not related to anything in normal space. :slight_smile:

2 Likes

Call them like the four ninja turtles.

Heheh… someone would still manage to read something into it. “Well, Michelangelo was named after a guy who made statues so maybe that’s the y axis…” :slight_smile:

2 Likes

Hi, thanks for the reply !
I think its working as intend now, follow the code so others with same problem may look at :smile:

public class Main extends SimpleApplication {
Geometry geom1,geomX,geomY,geomZ; final float speed = 0.001f;

public static void main(String[] args) { Main app = new Main(); app.start(); }

@Override public void simpleInitApp() {
    flyCam.setEnabled(false); Box b = new Box(1, 1, 1);       
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.getAdditionalRenderState().setWireframe(true);
    mat.setColor("Color", ColorRGBA.White);        
    geom1 = new Geometry("Box", b); geom1.setLocalTranslation(0,2,0);
    geomX = new Geometry("Box", b); geomX.setLocalTranslation(-3.5f,-1,0);
    geomY = new Geometry("Box", b); geomY.setLocalTranslation(0,-1,0);
    geomZ = new Geometry("Box", b); geomZ.setLocalTranslation(3.5f,-1,0);        
    geom1.setMaterial(mat); geomX.setMaterial(mat); geomY.setMaterial(mat); geomZ.setMaterial(mat);        
    rootNode.attachChild(geom1); rootNode.attachChild(geomX); rootNode.attachChild(geomY); rootNode.attachChild(geomZ);
    inputManager.addMapping("RotateXup",  new KeyTrigger(KeyInput.KEY_Q)); inputManager.addMapping("RotateXdown",  new KeyTrigger(KeyInput.KEY_W));
    inputManager.addMapping("RotateYup",  new KeyTrigger(KeyInput.KEY_A)); inputManager.addMapping("RotateYdown",  new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping("RotateZup",  new KeyTrigger(KeyInput.KEY_Z)); inputManager.addMapping("RotateZdown",  new KeyTrigger(KeyInput.KEY_X));
    inputManager.addMapping("RefreshRot", new KeyTrigger(KeyInput.KEY_SPACE)); 
    inputManager.addListener(analogListener,"RotateXup", "RotateYup", "RotateZup","RotateXdown","RotateYdown","RotateZdown","RefreshRot");
}

private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {            
        if(name.equals("RotateXup")) geom1.rotate(speed, 0, 0);  if(name.equals("RotateXdown")) geom1.rotate(-speed, 0, 0);
        if(name.equals("RotateYup")) geom1.rotate(0, speed, 0);  if(name.equals("RotateYdown")) geom1.rotate(0, -speed, 0);
        if(name.equals("RotateZup")) geom1.rotate(0, 0, speed);  if(name.equals("RotateZdown")) geom1.rotate(0, 0, -speed);
        if(name.equals("RefreshRot")) {                
            float[] angles = geom1.getLocalRotation().toAngles(null);
            geomX.setLocalRotation(new Quaternion().fromAngles(angles[0], 0, 0));                
            geomY.setLocalRotation(new Quaternion().fromAngles(0, angles[1], 0));
            geomZ.setLocalRotation(new Quaternion().fromAngles(0, 0, angles[2]));
        }
    }
};

}

Please comment if there is something wrong on the code ok ?

Actually, there is something wrong still going on in this code.
I dont know if its was clear what I was expecting, the first cube should show only the x rotation from the origem cobe, it is showing it corretly, but the second cube should show the x rotation and the third the z rotation, and they are not working after we move the origem cube in some directions.
I am not sure what is going on, it looks like when I get the angles, it get focused on a determined axix, I may need to change that to get in y axix and z axix as well.
Any tips ?

Hard to say from your description.

One thing to remember is that euler angles are ambiguous but quaternions are not. There are an infinite combination of euler angles that can produce a particular orientation but only two quaternions for that same orientation. A quaternion converted to three angles will always be sort of the ‘most efficient’ way to get that orientation which may not be the same as the original three rotations that you put in.

An extreme example of euler angle ambiguity: if you were to rotate an object 180 degrees around the Y axis then this is the same as rotating it 180 degrees around X and 180 degrees around Z.

That example is straight forward but it may not always be easy to predict which angles you will get back from some angles → quaterion → angles.

There is any other way to get this angles ?
Its so odd that it works form the x representation but mess up on y and z, I could not found a reason for that.
I mean, it would be because it is using local or even the order of rotations, but why it works on x axix perfectly ?

Maybe I am having the same issue from here :smile:

I did notice that:
Getting the x angle allways works as intend using this method.
Getting the y angle works, but get in the matrix dirt from x rotation and z rotation.
Getting the z angle dont work at all, it seens to get dirt, and also rotate only to 180º , then invert the diretion somehow.

I was planning to make a video, if someone that understand rotation and is willing to help, I could post this video ilustrating this, but you can also test this code and :

  1. presss q+space for a while will rotate on x, cube x get the x rotation allways, other cubes stays in the same position as expected.
  2. press a+space for a while will rotate on y, you will notice that all cubes moves not only the y one as expected.
  3. press z+space for a while will show something that I really dont get it :stuck_out_tongue:

I found another topic with similar problem :

and the solution and conclusion they get as far as I understood is that toAngles dosent work. The solution was to store all rotations in variables in order to retrieve when it is needed, so I wrote:

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.CartoonEdgeFilter;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.shape.Box;
import javax.vecmath.Matrix3d;
import javax.vecmath.Vector3d;

public class Main extends SimpleApplication {
Geometry geom_master,geomX,geomY,geomZ;

float storedrotationX,storedrotationY,storedrotationZ;

public static void main(String[] args) { Main app = new Main(); app.start(); }
public Main() { super(null); }

@Override public void simpleInitApp() {               
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.getAdditionalRenderState().setWireframe(true); mat.setColor("Color", ColorRGBA.White);                
    Box b = new Box(1, 1, 1); geom_master = new Geometry("Box", b); geom_master.setLocalTranslation(0,2,0);
    geomX = new Geometry("Box", b); geomX.setLocalTranslation(-3.5f,-1,0);
    geomY = new Geometry("Box", b); geomY.setLocalTranslation(0,-1,0);
    geomZ = new Geometry("Box", b); geomZ.setLocalTranslation(3.5f,-1,0);        
    geom_master.setMaterial(mat); geomX.setMaterial(mat); geomY.setMaterial(mat); geomZ.setMaterial(mat);                
    rootNode.attachChild(geom_master); rootNode.attachChild(geomX); rootNode.attachChild(geomY); rootNode.attachChild(geomZ);
    inputManager.addMapping("RotateXup",  new KeyTrigger(KeyInput.KEY_Q)); inputManager.addMapping("RotateXdown",  new KeyTrigger(KeyInput.KEY_W));
    inputManager.addMapping("RotateYup",  new KeyTrigger(KeyInput.KEY_A)); inputManager.addMapping("RotateYdown",  new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping("RotateZup",  new KeyTrigger(KeyInput.KEY_Z)); inputManager.addMapping("RotateZdown",  new KeyTrigger(KeyInput.KEY_X));
    inputManager.addMapping("RefreshRot", new KeyTrigger(KeyInput.KEY_SPACE)); 
    inputManager.addListener(analogListener,"RotateXup", "RotateYup", "RotateZup","RotateXdown","RotateYdown","RotateZdown","RefreshRot");
}

private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {  
        float anguletorotate = FastMath.DEG_TO_RAD * 0.1f;
        if(name.equals("RotateXup"))   { geom_master.rotate(anguletorotate, 0, 0);  storedrotationX=storedrotationX+anguletorotate; } 
        if(name.equals("RotateXdown")) { geom_master.rotate(-anguletorotate, 0, 0); storedrotationX=storedrotationX-anguletorotate; }
        if(name.equals("RotateYup"))   { geom_master.rotate(0, anguletorotate, 0);  storedrotationY=storedrotationY+anguletorotate; } 
        if(name.equals("RotateYdown")) { geom_master.rotate(0, -anguletorotate, 0); storedrotationY=storedrotationY-anguletorotate; }
        if(name.equals("RotateZup"))   { geom_master.rotate(0, 0, anguletorotate);  storedrotationZ=storedrotationZ+anguletorotate; } 
        if(name.equals("RotateZdown")) { geom_master.rotate(0, 0, -anguletorotate); storedrotationZ=storedrotationZ-anguletorotate; }
        if(name.equals("RefreshRot")) {
            if(storedrotationX!=0) geomX.rotate(storedrotationX,0,0); storedrotationX=0;
            if(storedrotationY!=0) geomY.rotate(0,storedrotationY,0); storedrotationY=0;
            if(storedrotationZ!=0) geomZ.rotate(0,0,storedrotationZ); storedrotationZ=0;
        }
    }
};

}

Now its working as expected, but same as this guy “system” in this “Spartial Rotation Problems”, I fell that its strange that there is no bether way to manipulate the rotations in JMonkey, there is some nice functions on other engines for this, even some engines that use Quaternions like Unit3d can handle it well.
Is storing the rotation the only way to retreive the rotations in separated axix right now ?

Generally, the simplest way to deal with rotations is not dealing with them at all but keeping and working with direction and up vectors, you can always convert them to quaternions or anything you like really, as the “Math for Dummies” tutorial says.

Your specific problem is that you want to work with x,y,z rotations. If you’d stay with the quaternions and use the methods provided to manipulate those you wouldn’t have these issues. So there IS methods to handle rotations, your issues are mostly home made.

How could I extract the x,y,z world rotations from a quaternion that way ?
Is that possible ? I could not find a way other than keep a history variable of all rotations…

Well again theres the toAngles method but you don’t seem to get what I was saying and fall into the same trap again… If you so dearly wish to work with x,y,z euler angles you will have to do just that and keep and manage them (instead of trying to retrieve them time and again while you let the jme3 math handle things you don’t want to do yourself).