13
\$\begingroup\$

I'm coding a mechanic which allows a user to move around the surface of a sphere. The position on the sphere is currently stored as theta and phi, where theta is the angle between the z-axis and the xz projection of the current position (i.e. rotation about the y axis), and phi is the angle from the y-axis to the position. I explained that poorly, but it is essentially theta = yaw, phi = pitch

Vector3 position = new Vector3(0,0,1);
position.X = (float)Math.Sin(phi) * (float)Math.Sin(theta);
position.Y = (float)Math.Sin(phi) * (float)Math.Cos(theta);
position.Z = (float)Math.Cos(phi);
position *= r;

I believe this is accurate, however I could be wrong. I need to be able to move in an arbitrary pseudo two dimensional direction around the surface of a sphere at the origin of world space with radius r. For example, holding W should move around the sphere in an upwards direction relative to the orientation of the player.

I believe I should be using a Quaternion to represent the position/orientation on the sphere, but I can't think of the correct way of doing it. Spherical geometry is not my strong suit.

Essentially, I need to fill the following block:

public void Move(Direction dir)
{   
    switch (dir)
    {
        case Direction.Left:
            // update quaternion to rotate left
            break;
        case Direction.Right:   
            // update quaternion to rotate right
            break;
        case Direction.Up:
            // update quaternion to rotate upward
            break;
        case Direction.Down:
            // update quaternion to rotate downward
            break;
    }
}
\$\endgroup\$
6
  • \$\begingroup\$ What should happen if the player reaches the poles? I noticed you wrote "upwards direction", do you literally mean "upwards" (that is, away from the surface of the sphere), "straight ahead" or "towards the north pole" (the latter two are the same if the player can't change their orientation and "in front of them" or "up" on screen is always the north)? \$\endgroup\$ Oct 17, 2012 at 10:49
  • \$\begingroup\$ Perhaps it was poorly worded. The player should not leave the surface of the sphere, and should not be aware of the cardinal axis. So when you move "up", you move along the surface of the sphere in a upwards direction relative to the orientation of the player. e.g. If you are at (r,0,0) and press up, you will go towards the z+ pole, but if you keep going you should wrap around and keep going. \$\endgroup\$
    – azz
    Oct 17, 2012 at 10:56
  • \$\begingroup\$ Still one question remains: Can the player change orientation (rotate "left" and "right")? \$\endgroup\$ Oct 17, 2012 at 11:08
  • \$\begingroup\$ Maybe a better example of what I'm going for: Player at (1,1,1) holding left would rotate around the sphere, passing through (~1.2,0,~-1.2), then (-1,-1,-1), then (~-1.2,0,~1.2) and back to (1,1,1). \$\endgroup\$
    – azz
    Oct 17, 2012 at 11:22
  • 1
    \$\begingroup\$ If you intend to always keep track of theta and phi as your position updates, you are making your problem unnecessarily complex. Much easier to simply calculate the 2 rotation axes each frame (one of which (yaw) never changes) and Vector3.Transorm around the sphere. This would simplify your problem but will cause you to disconnect with phi & theta. \$\endgroup\$
    – Steve H
    Oct 17, 2012 at 13:58

2 Answers 2

5
\$\begingroup\$

Actually, it turns out that you can't have it 'both ways': if your intention is to not have any sense of 'absolute orientation' on the sphere (that is, if the players aren't always e.g. facing towards the poles), then you'll need to have a notion of player orientation. This is because, contrary to what intuition might suggest, movement on the sphere is not exactly like movement on a plane, not even locally (quite); the intrinsic curvature of the sphere means that players can take actions that will rotate themselves!

For the most extreme example of what I'm talking about, imagine that the player starts at a point on the equator (for convenience we'll imagine a clock face mapped onto the equator from above, and put the player at 6 o'clock), facing 'up' - that is, towards the North Pole. Suppose the player walks all the way to the North Pole; then they'll be facing directly towards the 12 o'clock point. Now, let the player move directly to their right, from the North Pole back to the equator; they'll wind up at the 3 o'clock point - but because their facing doesn't change when they move right (the idea is that their facing doesn't change no matter how they move), they'll still be facing the 12 o'clock point - they're now facing along the equator! Now, let them move 'backwards' back to their starting (6 o'clock) point; then they'll still be facing along the equator, so they'll be facing towards the 3 o'clock point - just moving along the sphere without ever changing their 'personal' orientation has caused them to rotate from facing towards the north pole to facing along the equator! In a sense, this is an elaboration of the old 'a hunter moves a mile south, a mile west and then a mile north' joke - but here we're taking advantage of the curvature of the sphere to effect a change of direction. Note that the same effect still happens even on much smaller scales; this is the most extreme version of it, but even just short 'one foot north, one foot east, one foot south, one foot west' rectangles won't leave the player facing precisely the way they were before - this is an expression of the fact that the sphere has nontrivial curvature.

Fortunately, quaternions do (as you noted yourself) handle this situation; since a quaternion represents an arbitrary rotation, it effectively represents an arbitrary 'point plus orientation' on the sphere: imagine starting with a 'triaxis' at the origin and giving it some arbitrary rotation, then moving one unit in whichever direction the rotated axes' Z-axis points; a little thought will convince you that this brings you to a point on the unit sphere with some 'orientation' (i.e., some arrangement of the X and Y axes of your triaxis), and that you can get to every point+orientation on the unit sphere this way (just assign your Z axis to point along the line from the origin through your point on the sphere, then transport your triaxes back to the origin along that line). What's more, since multiplication of quaternions corresponds to composition of rotations, each of the operations you describe can be represented by multiplying your 'current orientation' by an appropriately-chosen quaternion: specifically, since the (unit) quaternion (qx,qy,qz,qw) means 'rotate about the (qx,qy,qz) axis by arccos(qw)', then (depending on your specific choice of coordinate system, and letting c_a be cos(alpha) and s_a be sin(alpha)) two of the three quaternions M_x = (s_a, 0, 0, c_a), M_y = (0, s_a, 0, c_a), and M_z = (0, 0, s_a, c_a) will represent 'rotate (i.e. move) in the direction I'm currently facing by alpha' and 'rotate in a direction orthogonal to the one I'm currently facing by alpha'. (The third of those quaternions will represent 'rotate my character about his own axis') This means that every tick you'll figure out which key has been pressed and then, e.g., say Cur_q = M_x * Cur_q if the player has pressed up, or Cur_q = M_y * Cur_q if the player pressed right (or possibly something like Cur_q = M_yinv * Cur_q if the player pressed left, where M_yinv is the 'inverse' of the M_y quaternion, representing a rotation the other way). Note that you have to be careful which 'side' you apply the rotation on, whether to premultiply or postmultiply; to be frank, it may be easiest to solve that with trial-and-error, trying both multiplications and seeing which works.

Going from your updated quaternion to a point on the sphere (and to an orientation of your character) is relatively straightforward, too: by the correspondence of the last paragraph, all you have to do is use your quaternion on the basis vectors (1,0,0), (0,1,0) and (0,0,1) of your frame via 'rotate vector by quaternion' operation v → qvq-1 (where the multiplications here are quaternion multiplies and we identify the vector v=(x,y,z) with the 'degenerate quaternion' (x,y,z,0)). For instance, the position on the unit sphere is gotten by just transforming the z vector: pos = (qx, qy, qz, qw) * (0, 0, 1, 0) * (-qx, -qy, -qz, qw) = (qx, qy, qz, qw) * (qy, -qx, qw, qz) = (2(qy*qw+qz*qx), 2(qz*qy-qw*qx), (qz^2+qw^2)-(qx^2+qy^2), 0), so (2(qy*qw+qz*qx), 2(qz*qy-qw*qx), (qz^2+qw^2)-(qx^2+qy^2)) would be the coordinates of the 'transformed' user on the unit sphere (and to get the coordinates on an arbitrary sphere, of course, you'd just multiply those by the sphere's radius); similar calculations work for the other axes, to define e.g. the user's facing direction.

\$\endgroup\$
2
  • \$\begingroup\$ Precisely what I want to achieve. I just couldn't think of the correct way to get a position out of the orientation quaternion. Using what you have provided, I can write the Move() procedure, but to get the normalized axis (i.e. my position), would I just take (sin(qx),sin(qy),sin(qw)) * r? \$\endgroup\$
    – azz
    Oct 23, 2012 at 14:37
  • \$\begingroup\$ @Der not exactly - I'll update my post with the details, but the short version is that you use your quaternion to transform a unit vector, e.g. (0,0,1), by the usual v -> qvq<sup>-1</sup> operation; the fact that you're transforming a simple vector means that (naturally) there's a short-cut here, but the final coordinates are quadratic in your quaternion's values, not linear. \$\endgroup\$ Oct 23, 2012 at 15:09
1
\$\begingroup\$

I think you want something similar to this http://www.youtube.com/watch?v=L2YRZbRSD1k

I developed that for a 48h gamejam... you can donwload the code here... http://archive.globalgamejam.org/2011/evil-god

I used something similar to your code to get the 3D coords... but I rotated the planet and the player was in the same pos, I think you are interested in the creature movement, is this:

    // To add movement
    protected override void LocalUpdate(float seconds)
    {
        Creature.Alfa += Direction.X * seconds * Speed;
        Creature.Beta += Direction.Y * seconds * Speed;            
    }


    // To calculate position
       World.Planet.GetCartesian(Alfa, Beta, out Position); // as you do
       Matrix PositionMatrix = Matrix.CreateTranslation(Position) * World.Planet.RotationMatrix;           
       LastPositionAbsolute = PositionAbsolute;
       Vector3 Up = PositionAbsolute = Vector3.Transform(Vector3.Zero, PositionMatrix);           
       Up.Normalize();
       // This is to add and offset to the creature model position
       PositionAbsolute += Up * 8;  
      // calculate new forward vector if needed

       if ((PositionAbsolute - LastPositionAbsolute).Length() > 0.1f) {
           Forward = PositionAbsolute - LastPositionAbsolute;
           Forward.Normalize();
       }

       // Calculate the world transform with position, forward vector and up vector
       Matrix LocalWorld = Matrix.CreateWorld(PositionAbsolute, Forward, Up); 

       Transform = Matrix.CreateScale(Scale * ScaleFactor) * LocalWorld;
\$\endgroup\$

You must log in to answer this question.

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