Auto level ship in orbit in 3D

I’m working on auto leveling my spaceship while it’s in orbit. I found this old thread, but it uses BulletAppState and I’m not using BulletAppState. (It only works for 2.5D, right?) There is a link there for more info, but the link is bad.
I got this from the old topic and tried to implement it.

Vector3f spatialY = control.getPhysicsRotation().mult(Vector3f.UNIT_Y);
Vector3f cross = spatialY.crossLocal(Vector3f.UNIT_Y).normalizeLocal();
control.applyTorque(cross);

I’ve tried duplicating the above and still don’t get the right result – looking down on the planet my ship is orbiting (with my camera looking down on my ship).
I assume getPhysicsRotation() returns the local rotation (I’ve tried getting my Local as well as World rotation). I tried to look up getPhysicsRotation() but the page provides no details - only class header info. (http://javadoc.jmonkeyengine.org/com/jme3/bullet/objects/PhysicsRigidBody.html) Ah, I found it on GitHub…
I tried the second line above with no changes*.
The third line looks like it duplicates Quaternion move(v.x, v.y, v.z) which, I think, ‘add’s v to the current rotation.

Below is the autoLeveling stuff. How do those three lines work?

Vector3f startVector;
Vector3f currentVector;
Vector3f endVector;
Vector3f upVector;
Vector3f tempVector;
Vector3f spatialY;
Vector3f cross;
public void autoLevel(String action, boolean isPressed){
    autoLevel = (autoLevel == false);
    System.out.println("ControllableControl.autoLevel(): autoLevel=" + autoLevel);
    if (autoLevel){
        //start vector
        float[] rads = new float[3];
        spatial.getWorldRotation().toAngles(rads);//local
        startVector = new Vector3f(rads[0], rads[1], rads[2]);
        //current vector
        currentVector = new Vector3f(rads[0], rads[1], rads[2]);
        //end vector
        endVector = new Vector3f(rads[0], rads[1], rads[2]);
        //test
        //Vector3f spatialY = control.getPhsicsRotation().mult(Vector3f.UNIT_Y);
        //cross = spatialY.crossLocal(Vector3f.UNIT_Y).normalizeLocal();
        upVector = Vector3f.UNIT_Y;
        spatialY = new Vector3f(-rads[0], -rads[1], -rads[2]).multLocal(upVector);
        //spatialY = new Vector3f(rads[0], rads[1], rads[2]).multLocal(upVector);
        cross = spatialY.crossLocal(upVector).normalizeLocal();
    }
    else{
        pitchDown("PitchDown", false);
        pitchUp("PitchUp", false);
        yawLeft("YawLeft", false);
        yawRight("YawRight", false);
        rollLeft("RollLeft", false);
        rollRight("RollRight", false);
        massControl.setSpin(new Vector3f(0f, 0f, 0f));
    }
}


private void doAutoLevel(float tpf){
    //current vector
    float[] rads = new float[3];
    spatial.getLocalRotation().toAngles(rads);
    currentVector = new Vector3f(rads[0], rads[1], rads[2]);
    //this should set the final rotation
    spatial.rotate(cross.x, cross.y, cross.z);
    //massControl.rotate(cross.x, cross.y, cross.z);
    autoLevel = false;//shut off for testing

    System.out.println("CC.autoLevel()" + ", startVector=" + startVector + ", currentVector=" + currentVector + ", endVector=" + endVector + ", cross=" + cross);
    
    if (true) return;
    //
    Vector3f currentRotation = massControl.getRotation();
    Vector3f diffVector = currentVector.subtract(endVector);
    if (currentRotation.x < endVector.x - 0.01f)
        pitchDown("PitchUp", true);
    else if (currentRotation.x > endVector.x + 0.01f)
        pitchUp("PitchDown", true);
    else{
        currentRotation.x = endVector.x;
        pitchDown("PitchDown", false);
        pitchUp("PitchUp", false);
        massControl.setSpin(0, 0f);
    }
    
    if (currentRotation.y < endVector.y - 0.01f)
        yawRight("YawRight", true);
    else if (currentRotation.y > endVector.y + 0.01f)
        yawLeft("YawLeft", true);
    else{
        massControl.setSpin(1, 0f);
        yawLeft("YawLeft", false);
        yawRight("YawRight", false);
        currentRotation.y = endVector.y;
    }
    //
    if (currentRotation.z < endVector.z - 0.01f)
        rollLeft("RollLeft", true);
    else if (currentRotation.z > endVector.z + 0.01f)
        rollRight("RollRight", true);
    else{
        massControl.setSpin(2, 0f);
        rollLeft("RollLeft", false);
        rollRight("RollRight", false);
        currentRotation.z = endVector.z;
    }
}

If I understand this math… the normalize is not right.

My understanding:
torque is going to be rotation in three axes. By taking the cross product of the actual up vector and the ship’s up vector, you will get the rotational torque necessary to make one vector match the other.

So one up vector should be “local up for the planet in ‘world’ space (not planet space)”… the other up vector should be “world space up for the ship”. If you take the unnormalized cross product then you should get the torque necessary to make one match the other.

…and if I’d seen this post a week ago I wouldn’t even know that much but I recently figured out some magic math in my own physics engine that gave me this understanding.

OK. Thanks. I have a look. Wiki said something about a World up… I may have missed it…

Basically, you have some reference vector in one “space” and you want another space’s reference vector to match that… so transform them into the same space and then take the cross product to get the torque.

It would work for any reference vector… but up is the one you want here.

And BulletAppState is not even nearly only for 2.5D. When it’s attached bullet physics engine runs.

Bullet physics only has world coordinates, there is no nodes, hence theres no local coordinates.