I’m trying to make a ship auto level so that the belly of the ship stays facing the planet. I found the answer in 2.5D*, but I can’t get it to work in 3D.

//world rotation * up
Vector3f spatialY = control.getPhsicsRotation().mult(Vector3f.UNIT_Y);
Vector3f cross = spatialY.crossLocal(Vector3f.UNIT_Y).normalizeLocal();

* Stuck on the ground with no roll (like Doom, COD, etc.).

I’m trying to find a rotation perpendicular to the center/surface of the planet. The math is at the edge of my understanding. I found a great link on wiki, but I don’t understand the formulas.

I can not tell if I am looking for

  • The final vector to adjust, over time,
  • The cross-product difference between what I have and what I want,
  • A ‘normal’ perpendicular to a plane,
  • A normal to the tangent plane,

Or if I am trying to align the x and z angles to the plane. How do I get the plane?

The next step will be to rotate the ship on the Y (up) axis and face prograde – with orbit, retrograde – opposite orbit, orbit +/- to align the plane with another orbiting body.

Vector3f startVector;
Vector3f currentVector;
Vector3f endVector;
Vector3f upVector;
Vector3f tempVector;
Vector3f spatialY;
Vector3f cross;
Vector3f v;
public void autoLevel(String action, boolean isPressed){
    autoLevel = (autoLevel == false);
    if (autoLevel){
        upVector = Vector3f.UNIT_Y;
        float[] rad = new float[3];
        //start vector
        startVector = new Vector3f(rad[0], rad[1], rad[2]);
        //current vector
        currentVector = new Vector3f(rad[0], rad[1], rad[2]);
        //end vector
        endVector = new Vector3f(rad[0], rad[1], rad[2]);
        spatialY = endVector.mult(Vector3f.UNIT_Y);
        cross = spatialY.cross(Vector3f.UNIT_Y);

private void doAutoLevel(float tpf){
    float[] rad = new float[3];
    currentVector = new Vector3f(rad[0], rad[1], rad[2]);
    //if cross it the difference between my current and end rotations...
    autoLevel = false;//shut off for testing
    if (true) return;
    if (currentVector.x < endVector.x - 0.01f)
        pitchDown("PitchUp", true);
    else if (currentVector.x > endVector.x + 0.01f)
        pitchUp("PitchDown", true);
        currentVector.x = endVector.x;
        pitchDown("PitchDown", false);
        pitchUp("PitchUp", false);
        massControl.setSpin(0, 0f);

    if (currentVector.y < endVector.y - 0.01f)
        yawRight("YawRight", true);
    else if (currentVector.y > endVector.y + 0.01f)
        yawLeft("YawLeft", true);
        massControl.setSpin(1, 0f);
        yawLeft("YawLeft", false);
        yawRight("YawRight", false);
        currentVector.y = endVector.y;
    if (currentVector.z < endVector.z - 0.01f)
        rollLeft("RollLeft", true);
    else if (currentVector.z > endVector.z + 0.01f)
        rollRight("RollRight", true);
        massControl.setSpin(2, 0f);
        rollLeft("RollLeft", false);
        rollRight("RollRight", false);
        currentVector.z = endVector.z;

//And the gravity routine that I found after searching for years...

private synchronized void doGravity(){
    //delay -= 1.0f;
    //if (delay > 0) return;
    //delay = app.getTimer().getFrameRate();
    //ArrayList<Node> bodyList = getBodyList();
    List list = getMasses(rootNode);
    for (int a = 0;a < list.size();a++){
        Node p1 = (Node)list.get(a);
        MassControl massControl = p1.getControl(MassControl.class);
        float m1 = massControl.getMass();
        //System.out.println("EnvironmentalControl.doGravity()" + ", processing p1=" + p1.getName());
        Vector3f acc = new Vector3f();
        if (list.size() > 1){
            //if (p.getName().equals("Moon")) System.out.print("acc=");
            for (int b = 0;b < list.size();b++){
                Node p2 = (Node)list.get(b);
                if (p1.equals(p2)) continue;
                Vector3f unit = new Vector3f(p2.getWorldTranslation().subtract(p1.getWorldTranslation()));
                float magnitude = (float)Math.sqrt((unit.x * unit.x) + (unit.y * unit.y) + (unit.z * unit.z));
                float m2 = p2.getControl(MassControl.class).getMass();
                //if (p1.getName().equals("Ship 01") && p2.getName().equals("Earth") || p2.getName().equals("Ship 01") && p1.getName().equals("Earth")) 
                    //System.out.println("OrbitMaker.doGravity(" + p1.getName() + ")(" + p2.getName() + ") m1=" + m1 + ", m2=" + m2 + ", unit=" + unit + ", magnitude=" + magnitude);//do gravity
                if (magnitude != 0.0f && (m1 * m2 != 0.0)){
                    float factor = (G * (
                                    (m1 * m2) / (magnitude * magnitude * magnitude)
                                    )) / m1;
                    unit = unit.mult(factor);
                    //if (p1.getName().equals("Ship 01")) System.out.println("OrbitMaker.doGravity(" + p1.getName() + ")(" + p2.getName() + "): factor=" + factor + ", unit=" + unit + ", acc=" + acc);
                    unit.set(0.0f, 0.0f, 0.0f);
                acc = acc.add(unit.clone());//.mult(tpf));
                //collision check
                if (areClose(p1, p2)){
                    collisionQueue.add(new CollisionGroup(p1, p2));

        //if (p.getName().equals("Moon")) System.out.println(acc + ", ");
        //Vector3f velocity = p.getControl(MassControl.class).getVelocity();
        //if (p.getName().equals("Moon")) System.out.println("OrbitMaker.doGravity(" + p.getName() + "): acc=" + acc);
  • \$\begingroup\$ is this what you're looking for? math.stackexchange.com/questions/225614/tangent-plane-to-sphere i'm not even close to understanding anything on that question/answer, but i assume that's what you need to achieve what you want. The sphere would be programmaticly created with it's center on the planet's center, and it's radius would be the distance between said core and your ship \$\endgroup\$
    – Brian H.
    Jan 4, 2017 at 13:46

2 Answers 2


Here is a simple way to keep the belly of the ship facing the planet as the ship orbits. On real orbiting spaceships this does not happen but I can see where it would look good on a video game.

The only thing you have to do outside this method is increment the ship's orbital position by whatever physics approximation method you what (like your doGravity()). If you want to steer the ship to give it a new orbital direction, you could squeeze that in after the 2nd line in the method.

This method assumes you've already incremented the ship's position from last frame and this is passed as newShipPosition. It also assumes you saved the result from last frame and this is passed as lastFrameMatrix.

Matrix GetNewWorldMatrix(Matrix lastFrameMatrix, Vector3 newShipPosition, Vector3 planetCenter)
   Matrix result = Matrix.Identity;

   Vector3 directionShipIsTraveling = lasteFrameMatrix.Forward;
   Vector3 newMatrixUp = newShipPosition - planetCenter;
   Vector3 newMatrixRight = Vector3.Cross(directionShipIsTraveling, newMatrixUp);
   Vector3 newMatrixForward = Vector3.Cross(newMatrixUp, newMatrixRight);

   result.Up = newMatrixUp;
   result.Right = newMatrixRight;
   result.Forward = newMatrixForward;
   result.Translation = newShipPosition;

   return result;

  • \$\begingroup\$ Thanks Gnemlock. That was the plane but I still needed the perpendicular vector. I think that's it, Steve! One question though. My matrix library doesn't have .UP, .RIGHT, .FORWARD, etc. Do you have the column numbers for up/right/forward? I looked all over wiki and Can't find them. It almost works. I guess that there are only 9 combos to try. \$\endgroup\$
    – Xorange
    Jan 6, 2017 at 7:00
  • \$\begingroup\$ @Xorange For some reason I thought you were using XNA which has the Forward, Right, & Up properties. Even so, typically, Right is row 1 or column 1, Up is row 2 or column 2, Forward is row3 or Column 3, & Translation is row or column 4. \$\endgroup\$
    – Steve H
    Jan 6, 2017 at 12:08
  • \$\begingroup\$ Well, that's working. I wan't to update the code to Java and JMonkeyEngine for the next person through here. I'd like to update the doAutoLevel code so that it shuts of and doesn't overshoot but, I'm doing something else right now. \$\endgroup\$
    – Xorange
    Jan 6, 2017 at 21:50

final code

public void autoLevel(){
    currentMatrix = getNewWorldMatrix(currentMatrix, spatial.getWorldTranslation(), parent.getWorldTranslation());

//static final int LR = 0;//right/left
//static final int UD = 1;//up/down
//static final int FR = 2;//front/back

public Matrix3f getNewWorldMatrix(Matrix3f oldMatrix, Vector3f currentTranslation, Vector3f parentTranslation){
    Matrix3f result = new Matrix3f();
    //lastFrameMatrix = spatial.getLocalRotation().toRotationMatrix();
    Vector3f walkDirection = oldMatrix.getColumn(FR);
    Vector3f newUp = currentTranslation.subtract(parentTranslation).normalize();
    Vector3f newRight = walkDirection.cross(newUp).normalize();
    Vector3f newForward = newUp.cross(newRight).normalize();
    result.setColumn(LR, newRight);        //LR = 0
    result.setColumn(UD, newUp);        //UD = 1
    result.setColumn(FR, newForward);    //FR = 2
    return result;

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .