Getting an object's direction and moving it forward when Y-axis isn't up?

I'm trying to make a game where the world is a sphere and player's run around it. Similar to Mario Galaxy for the Wii. The center of my world is at 0,0,0, which has made it easy to keep objects on the edge of the sphere. Also, I'm pretty sure that I've got the rotation working using:


        Quaternion q = player.getLocalRotation();
        float angle = player.angle * FastMath.PI / 180;
        player.setLocalRotation(q.fromAngleAxis(angle, playerLocation));



Where angle is an angle in degrees that is updated when left or right key input is entered.

What I can't seem to get right is forward movement. I started with the example from flag rush that assumes that Y is up:

        this.localTranslation.addLocal(this.localRotation.getRotationColumn(2, tempVa).multLocal(velocity * time));



It kind of works, but eventually the player gets stuck as they walk around the world. I think I need help calculating what direction is forward when the Y-axis isn't always up (i.e. when the player walks around to the bottom of the world). Can anyone help me out?

P.S. I'm not using a very sophisticated method of keeping players on the sphere. During the update loop I force the position using the following, where centerOfTheWorld is 0,0,0:

        Vector3f playerLocation = player.getLocalTranslation();
        float playerDistanceFromCOTW = centerOfTheWorld.distance(playerLocation);
        player.getLocalTranslation().multLocal(1 + (GLOBE_RADIUS - playerDistanceFromCOTW) / GLOBE_RADIUS);


i tried this once, too. and i am bad at everything abstract that i cannot imagine as an n-dimensional structure. a*, hillclimbing, quicksort, ant-algorithm, negamax -> no problem. but if you give me a formula and ask me "what is x", i'm totally lost. i also am totally lost when it comes to quaternions, rotations, stacked matrices… so what i did was: leave the player at his position. rotate the planet in the opposite direction when the player moves or spins. it felt a lot easier then the "normal" way. (the project failed anyway. i ran into so many problems i gave up)

I'm hoping to have many sprites on the globe that are all running around. Because of this, I don't think rotating the globe is the best idea. Right now I'm working on the math that goes on behind this. Hopefully I have better luck than you did. It certainly has been fun to figure out so far.

I've made some progress, but realized I have two issues while walking around the globe. As the model is moved around the globe it won't rotate so that it is pointed up. I'm using a pyramid so that it is easy to see. When on the top of the globe the pyramid points perfectly away from the center of the globe. As the pyramid moves around the globe I can't seem to get it to point anywhere other than the original direction, e.g. when at the equator it still points at the north pole and I see the side of it.



How can I get the model to have the right orientation? I'd like to keep the top always pointing away from the center of the globe.

If your model stands at the coords x,y,z, then the vector (x,y,z) is already pointing in the right "up"-direction, so you just want to rotate your model from the "usual" up-vector (0,1,0) to (x,y,z). There could be a problem when you come close to the "south pole" (0,-1,0).



However this would mess up the forward direction. Maybe this works:

  • Place your model at the north pole
  • Face it away from the point where you want to get it. That means it should look at (-x,0,-z)
  • Now rotate it in a way that its up vector (0,1,0) becomes the new up vector (x,y,z), and translate it so that it stands at (x,y,z), too
  • now you have your model at the right place with the right up-vector, facing "north", so now you can turn it around the up-vector in order to face in the right forward direction



    The north and the south pole are problematic in this model. Maybe you can put obstacles there, so the player can never reach them.

Thanks for the advice. I finally worked the rotation out using the following code and after rereading the rotation around a point guide. Now I have a new problem. First I'll post the working rotation code in case it helps someone else. Next I'll post the new bug: the black hole effect at the poles.




        // the node stays in the translation on the globe (but the model does not!)
        Vector3f playerLocation = player.getLocalTranslation();
        float playerDistanceFromCOTW = playerLocation.distance(centerOfTheWorld);
        player.getLocalTranslation().multLocal(1 + ((GLOBE_RADIUS + 1) - playerDistanceFromCOTW) / (GLOBE_RADIUS+1));
       
        // the player is rotated around the axis of where they are (must tilt the model to make it look right)
        Quaternion q = player.getLocalRotation();
        float angle = player.angle * FastMath.PI / 180;
        q.fromAngleAxis(angle, playerLocation);
        player.model.updateGeometricState(0, true);
       
       
        // rotate the model to face th correct angle
        quatA.loadIdentity();
        quatB.loadIdentity();
        // calculate the angles from the vector
        xAngle = FastMath.asin(playerLocation.x/playerDistanceFromCOTW);
        yAngle = FastMath.asin(playerLocation.y/playerDistanceFromCOTW);
        zAngle = FastMath.asin(playerLocation.z/playerDistanceFromCOTW);
        // change the model's rotation to point away from the globe
        Quaternion qp = player.model.getLocalRotation();
        // special cases for if z > 0 or z < 0
        if (playerLocation.z >=0) {
            quatA.fromAngleNormalAxis(FastMath.PI/2 - yAngle, Vector3f.UNIT_X);
            quatB.fromAngleNormalAxis(xAngle, new Vector3f(0,0,-1));
        } else {
            quatA.fromAngleNormalAxis(FastMath.PI/2 - yAngle, new Vector3f(-1, 0, 0));
            quatB.fromAngleNormalAxis(xAngle, new Vector3f(0,0,-1));
        }
        // now multiply
        qp.loadIdentity();
        qp.multLocal(quatA);
        qp.multLocal(quatB);



I'm sure that the above code isn't optimal, but it seems to work pretty well. Now I have a different problem. As the player gets closer to the north or south poles they are more or less sucked in. I can not get the player to be able to run around the globe near either pole without the pole automatically sucking them in. If I run the player around elsewhere it is fine, but I think my movement math is a little off. Can anyone help? I think it has to do with using the y-axis as my reference point when calculating what direction is forward based on the player's current rotation.

Here is the player movement code. As before, angle is an angle between 0 and 360 that gets updated as the left and right turn keys are pressed. Velocity is the same as in the flag rush tutorial, just a scalar that is either positive or negative based on if the forward or backward keys have been pressed.

            // always going to move a unit of 0.25
            float moveDistance = player.getVelocity() * tpf;
           
            // move the player appropriately
            Vector3f moveVector = player.getLocalTranslation();
            // calculate the angle to y
            Vector3f yIsUp = moveVector.mult(Vector3f.UNIT_Y);
           
            // rotate about the axis -- seems to work well
            rotationQuat.fromAngleAxis(player.angle*FastMath.DEG_TO_RAD, moveVector);
           
            // calc the distance to the center of the world
            float d = yIsUp.distance(Vector3f.ZERO);
            yIsUp.normalizeLocal();
            yIsUp.multLocal(MultiThreadGameState.GLOBE_RADIUS);
            d = yIsUp.distance(Vector3f.ZERO);
           
            // this vector is what I use as the 'forward' axis to move the player
            Vector3f diff = moveVector.subtract(yIsUp);
            diff.normalizeLocal().multLocal(moveDistance);
            // rotate the vector around our axis
            diff = rotationQuat.mult(diff);
           
            // when crossing the y-axis flip rotation by 180 degrees to keep orientation.
            if (moveVector.y > 0 && moveVector.y + diff.y < 0 || moveVector.y < 0 && moveVector.y + diff.y > 0) {
                player.angle += 180;
            }
            // move the player the appropriate distance in the right direction
            moveVector.addLocal(diff);
            player.getLocalTranslation().set(moveVector);



My best guess is that I need to use more than just the y-axis when calculating the movement vector. I've played around with using the x-axis as well and adding 90 degrees to the rotation. It seems to work; however, the black hole effect still exists. Does anyone know how to modify the above code to avoid the black hole effect that I described?

Unfortunately since you have a Sphere, you cannot describe all points in it with a unique UP vector (the reason being the sphere having a point pointing in every possible direction). What you need to do is create a special case, in which, for instance, if your absolute Y coordinate is bigger than, say half the radius of your sphere, swap your logic to using another vector as your up vector.



There really is no better way of doing it.  ://

Thanks duenez,



Swapping up vectors doesn't seem like it'll be that bad. I'll give it a try. I just started coding jME a few days ago, and I think my big problem is that I'm learning basically everything for the first time. I think I'm on the right track now that I know what is needed.

Well, I might have posted the above a bit to quickly. I thought I had a good idea how to use the x-axis as my up direction; however, it isn't quite working. Can anyone help? It is the same problem posted above that the y-axis works for. I'm trying to get the x-axis feature working so that the player can walk over the north and south poles correctly. Or put differently, I'm following duenez's suggestion. When the player is too close to the y-axis I switch to using the x-axis to calculate the player's movement.

I've been playing around with trying to switch from the y-axis as a reference to the x-axis if a player gets too close to the north or south poles (i.e. y-axis). Are there any examples of the right way to do this? I keep coming up with mediocre solutions.

After thinking about it for awhile, I figured out a nice way to make my sprites run across the globe as desired. I'll clean the code up a little bit and try to add an entry to the wiki.