Rotation on 2 axis problem (or 'Elite-style ship control')

Apologies if this has been answered elsewhere, but I’ve Google it to death and on this forum to no avail.

What I want to do is quite simple on the face of it. Imagine sitting in a cockpit: I can roll left and right on the z-axis. The problem is that I then want to pitch up/down on the x-axis, but using the “new” x-axis based on the ship’s current roll position. However, the x-axis remains static.

I’ve tried pivot nodes and every other combination of rotation I can think of. Any help is really appreciated as I’ve been stuck on this for days.

Thanks!

Use direction and up vectors instead of rotation, much easier.

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

// Change pitch by pitch delta:
Quaternion pitchChange = new Quaternion().fromAngles(pitchDelta, 0, 0)
shipRotation = shipRotation.mult(pitchChange);

That should be it.

The trick is, that you accept the new rotation after you’ve done something:

1st frame
Roll a little to the left, accept
Pitch a little up, accept
Strafe a little sideways, accept

next frame
Roll a little to the left, accept
Pitch a little up, accept
Strafe a little sideways, accept

next frame
Roll a little to the left, accept
Pitch a little up, accept
Strafe a little sideways, accept

=>
Otherwise you are stuck with what is called the “gimbal lock” and several other problems.

The trick is to do these things with a little delta time, e.g. 0.01 second or 0.05 seconds
So:

label a:
Roll a little to the left, accept
Pitch a little up, accept
Strafe a little sideways, accept
add 0.01 seconds to time
if(have reached time end of this frame) {
go to next frame;
}
else {
goto a;
}

Note: You can use Quaternion.multLocal and Quaternion.set to operate on the same Quaternion object - creating three Quaternion objects for each rotation calculation would be overkill (because it’s 400 Quaternion objects for one ship for one second when using 0.01 seconds as time delta). See: https://javadoc.jmonkeyengine.org/com/jme3/math/Quaternion.html

Another approach that others like @MoffKalast and @joehot200 have taken is to use the physics engine or to mimic how physics engines do rotations. See this thread: [SOLVED] Realistic-looking airplane rotation

Hope any of the two ideas help,
:chimpanzee_smile:

Thanks all for your replies. However, I don’t think I explained the problem very well. Here is a contrived example: imagine an aircraft parked on a runway facing away from us. If it was rotated 90 degrees on the x-axis (so it is now pointing upwards), then rotated 90 dogrees on the z-axis, so it is now side-on to us. Finally it is rotated -90 on the x-axis. I now want it to be parked back on the runway but 90 degrees from it’s original position; however, I can only get it to now be lying sideways on the runway.

Here is my code which might help:-

import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.scs.virtualforce2.JMEFunctions;

public class RotationTest2 extends SimpleApplication {

    private Geometry geom;
    private Node pivot_x, pivot_z;

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


    @Override
    public void simpleInitApp() {
        AmbientLight al = new AmbientLight();
        rootNode.addLight(al);
        DirectionalLight dirlight = new DirectionalLight();
        rootNode.addLight(dirlight);
        
        pivot_x = new Node("PivotX");
        rootNode.attachChild(pivot_x);
        pivot_z = new Node("PivotZ");
        pivot_x.attachChild(pivot_z);
        
        Box box = new Box(1f, .25f, 3f);
        box.setBound(new BoundingBox());
        box.updateBound();
        geom = new Geometry();
        Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");  // create a simple material
        geom.setMesh(box);
        geom.setMaterial(mat);
        pivot_z.attachChild(geom);

        this.rootNode.attachChild(JMEFunctions.GetGrid(assetManager, 10));

        // Rotate by 90 degrees on x-axis
        Quaternion qx = new Quaternion();
        qx.fromAngles(FastMath.DEG_TO_RAD*90, 0, 0); // Point straight up
        pivot_x.rotate(qx);

        // Rotate 45 degrees on the z axis
        Quaternion qz = new Quaternion();
        qz.fromAngles(0, 0, FastMath.DEG_TO_RAD*90); // rotate round by 90 degrees
        pivot_z.rotate(qz);

        Quaternion qx2 = new Quaternion();
        qx2.fromAngles(-FastMath.DEG_TO_RAD*90, 0, 0); // It's now lying on its side
        pivot_x.rotate(qx2);

    }


    @Override
    public void simpleUpdate(float tpf) {

    }

}

I think what I’m looking for is some kind of rotateWorldAxis() function that can applied to a spatial. The problem comes down to the fact that the directions of each axis never change, even if the node’s parent has been rotated,

Thanks again.

There is:

Node positionNode;
Spatial airplaneModel;

if (down) {
  airplaneModel.rotate(new Quaternion().fromAngleAxis(-1 * tpf, Vector3f.UNIT_X));
}
if (up) {
  airplaneModel.rotate(new Quaternion().fromAngleAxis(1 * tpf, Vector3f.UNIT_X));
}
if (left) {
  airplaneModel.rotate(new Quaternion().fromAngleAxis(-3 * tpf, Vector3f.UNIT_Z));
}
if (right) {
  airplaneModel.rotate(new Quaternion().fromAngleAxis(3 * tpf, Vector3f.UNIT_Z));
}
Quaternion rotation = airplane.getWorldRotation();
Vector3f flightDirection = rotation.mult(new Vector3f(0, 0, 1));
positionNode.setLocalTranslation(positionNode.getWorldTranslation().add(flightDirection.mult(flightSpeed * tpf)));

taken from:

That’s not true… or at least not as stated.

From the plane’s perspective, if it’s lying on its side then you you would rotate around the z-axis to be flat again.

Or are you saying you want to somehow do this outside the plane’s point of view? In the case, how do you determine where you want to end up? Else, just invert the rotation or reset it to zero.

I thought you were trying to fly the plane so I’m definitely confused now. For plane flight, all rotations would be relative to the local space of the plane. A yaw in that case is always a y-axis rotation, pitch is always x-axis rotation, z always roll, etc…

I do want to fly the plane, and I want all axis to be from the pilots point of view. To go back to my code from the point of view of a pilot (excuse the fact the plane isn’t moving, only rotating):

  • He’s sat on the runway, and raises the pitch by 90 degrees, so he’s now pointing straight up.
  • He then rolls 90 degrees, so he’s still pointing straight up.
  • Finally he lowers the pitch by 90 degrees, so he’s now nicely parked on the runway again, but 90 degrees from his original orientation.

However, in my code, the box/plane is now on its side. Can you tell me how do I rotate it as I just described?

Oje, this is why I think there should be an area for total newbies in this forum.
People would be so honest to say “I did never have contact with 3D geometry before”.
It’s not a bad thing to admit that someone is a total newbie in any area - I do it all the time.

Just like pspeed told you: The plane has its own coordinate system that “flies with the plane”.
This coordinate system has its own X-axis, Y-axis, Z-axis.
In comparison to the world’s coordinate system, this system may be tilted to any direction.
The coordinate system of the plane also has its own origin.
The orientation of the plane’s coordinate system relative to the world’s coordinate system is called “world Rotation” or “world Orientation” of the airplane.
The position of the plane’s coordinate system origin (0,0,0 for the plane center) relative to the world’s coordinate system is called “world Position” of the airplane or “world Translation” (which does not mean “translating language” but means "shift a position relative to another position (in this case the origin of the world).

Well, kind of reminds me of my own first steps with 3D graphics - which is now more than 10 years in the past. You should practice a lot and get a good book explaining this subject (this topic) to you. :chimpanzee_smile:

The main problem is that for some reason you decided to create 3 nested pivot_* nodes.

Spatial airplaneModel;
//Pitch 90°
airplaneModel.rotate(new Quaternion().fromAngleAxis(FastMath.DEG_TO_RAD*90, Vector3f.UNIT_X));
//Roll 90°
airplaneModel.rotate(new Quaternion().fromAngleAxis(FastMath.DEG_TO_RAD*90, Vector3f.UNIT_Y));
//Pitch -90°
airplaneModel.rotate(new Quaternion().fromAngleAxis(-FastMath.DEG_TO_RAD*90, Vector3f.UNIT_X); 

Yes, we all did.
You are doing it wrong.
A simple example: use the “rotate” method, not “setRotation”.
And don’t rotate around the world axis, rotate around the plane’s axis.

You can do this by getting the Matrix and then using the first, second, or third column: https://javadoc.jmonkeyengine.org/com/jme3/scene/Spatial.html#getLocalToWorldMatrix(com.jme3.math.Matrix4f)

Or you can do it like zzuegg described - rotate the Vector3f.UNIT_X or Vector3f.UNIT_Y or Vector3f.UNIT_Z by the local rotation of the Spatial: https://javadoc.jmonkeyengine.org/com/jme3/scene/Spatial.html#getLocalRotation() and https://javadoc.jmonkeyengine.org/com/jme3/math/Quaternion.html#mult(com.jme3.math.Vector3f,%20com.jme3.math.Vector3f)

What you need is the local coordinate system of the plane (see above). This will change as soon as you rotate the plane inside the world.

EDIT: 3D graphics is not an easy topic if you don’t know anything - get a good book (or if you are a millionaire - get a good teacher)

EDIT: please google “Gimbal Lock”

Thats it!! It works!! Thanks so much zzeugg, you’ve saved my sanity. I knew there must be a simple solution (turned out to be even simpler than I imagined) and I was tearing my hair out. I must have tried everything except fromAngleAxis().

Thanks everyone else for their input. I fully admit to be a total n00b when it comes to 3D maths, and I’ll prefix all my posts with this in the future.

fromAngles() would have worked fine too once all of the other issues were removed. Not saying you should switch back, just saying that was never the issue.