How to get the tangents for a point on a spherical surface?

Post Reply
jumpa
Posts: 4
Joined: Thu Nov 27, 2014 9:12 am

How to get the tangents for a point on a spherical surface?

Post by jumpa »

Hello,
I am trying to implement a game engine where a character can run and jump on the surface of a planet, with the gravity vector points towards the planet center.

I have managed to calculate the gravity vector towards the planet center with this formula:

Code: Select all

    body->getMotionState()->getWorldTransform(trans);
    btVector3 n = m_gravityCenter - trans.getOrigin();
    n.normalize();
    body->setGravity(9.8 * n);
Now I want to rotate the capuse/cylinder (that represents the player character) so that the base always faces the surface of the sphere e.g. the character needs to stand with his or her feet on the ground on the planet:

Code: Select all

    btVector3 p, q;
    p.setValue(-n.getY(), n.getX(), 0);
    q = p.cross(n);

    btMatrix3x3 basis(
        p.getX(), p.getY(), p.getZ(),
        n.getX(), n.getY(), n.getZ(),
        q.getX(), q.getY(), q.getZ()
    );
    btTransform objTransform;
    body->getMotionState()->getWorldTransform(objTransform);
    objTransform.setBasis(basis);
    obj->setWorldTransform(objTransform);
This does not seam to work unless movement only occurs in the x-direction, but in other directions the capsule rotated incorrectly.

I suppose the capsule needs to be rotated according the the basis that is formed by the normal and the tangent plane for the surface of the sphere where collision occurs, but I don't know how to calculate it (I only have the normal e.g. the gravitational vector). I have been searching on the internet and found some information about using btPlaneSpace1(n, p, q) but that produces strange results when used directly, so I only used the part where the vector "p" lies in the x-y plane:

Code: Select all

    p.setValue(-n.getY(), n.getX(), 0)
- Can someone please help me and describe how to obtain the vectors for the tangent plane of the sphere (representing the planet)?
- Does bullet provide a method for that, I have not found anything in the documentation?

PS. The full code can be found at: https://github.com/phoboz/tuxgal/blob/m ... Object.cpp
Last edited by jumpa on Sat Nov 29, 2014 10:03 pm, edited 2 times in total.
User avatar
drleviathan
Posts: 678
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: How to get the tangents for a point on a spherical surfa

Post by drleviathan »

What I think you need to do is use incremental quaternions.

Your character has some local UP vector (for argument lets say it is always the yAxis). The vector that is parallel to your character's UP in the world frame is given as follows:

Code: Select all

const btVector3 yAxis(0.0f, 1.0f, 0.0f);
btTransform trans;
body->getMotionState()->getWorldTransform(trans);
btQuaternion q = trans.getRotation();
btVector3 characterAxis = q * yAxis;
Meanwhile the planet has an UP vector as per your formula that we'll call n. You can compute the shortest quaternion that rotates characterAxis to align with n and then apply that delta rotation to the current character rotation to achieve the final rotation that you want. It would go something like this:

Code: Select all

const btScalar SOME_SMALL_NUMBER = 1.0e-5f;
btVector3 axis = characterAxis.cross(n);
btScalar crossLength = axis.length();
if (crossLength > SOME_SMALL_NUMBER) {
    btScalar angle = btAsin(crossLength);
    axis /= crossLength;  // this normalizes axis
    btQuaternion dq(axis, angle);
    trans.setRotation(dq * q);
    body->setWorldTransform(trans);
}
In english:

(1) Compute the cross product between the two normalized vectors.

(2) If the cross product is very short then the two vectors are parallel enough and we're done.

(3) Otherwise, use the length of the cross product to compute the angle between the vectors.

(4) Normalize the cross product to get the axis of the delta rotation.

(5) Compute the delta rotation : angle about axis.

(6) Apply the delta rotation to the current rotation.

(7) Store the final rotation.
jumpa
Posts: 4
Joined: Thu Nov 27, 2014 9:12 am

Re: How to get the tangents for a point on a spherical surfa

Post by jumpa »

I have tried it and it looks okay in 3D :D was never thinking in that direction.
Instead I was reading how to convert a plane in normal form to parametric form, it appeared to be a little bit more complex.

Still I believe that it would be useful to be able to get the tangent plane from bullet. I don't know how it works internally but if a sphere is stored as a quadratic equation the tangent vectors could be calculated using the partial derivatives?

There was a minor change I had to do in order for the code to compile e.g. I had to convert the quaternion to a matrix before the yAxis could be rotated:

Code: Select all

    // Get body up vector
    const btVector3 yAxis(0.0, 1.0, 0.0);
    btQuaternion q = trans.getRotation();
    btVector3 bodyAxis = btMatrix3x3(q) * yAxis;
Thank you very much for the help, I do appreciate it!
User avatar
drleviathan
Posts: 678
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: How to get the tangents for a point on a spherical surfa

Post by drleviathan »

Yeah sorry, I keep forgetting that Bullet doesn't provide a simple rotation operator on btVector3's by btQuaternions.

All you need to define the plane is its normal and one point known to lie on the plane.

You already know the normal n. It points out from the center of the sphere.

The point on the plane is the center of the sphere plus the normal multiplied by the sphere's radius.

The btPlane class can be initialized with a normal n and a distance d that represents the plane's closest approach to the origin. The value of d is just the dot product between the point on the plane and its normal (this is true for any point on the plane, hence the normal + distance pair of values "defines" all points on the plane):

Code: Select all

btScalar d = pointOnPlane.dot(n);
btPlane plane(n, d);
c6burns
Posts: 149
Joined: Fri May 24, 2013 6:08 am

Re: How to get the tangents for a point on a spherical surfa

Post by c6burns »

drleviathan wrote:Yeah sorry, I keep forgetting that Bullet doesn't provide a simple rotation operator on btVector3's by btQuaternions.
There's an operator overload, and a C-style quatRotate function which just uses the operator overload on the quaternion you pass in. At least in 2.82 there is ... I haven't been using bullet long.
jumpa
Posts: 4
Joined: Thu Nov 27, 2014 9:12 am

Re: How to move correctly on sphere surface?

Post by jumpa »

Hello again,
I have read how to create a plane in normal form. It wasn't that hard, but now I hope to get away from using the tangent vectors since the incremental quaternions worked so nicely. :wink:

Now I am working on getting the player character to move smoothly over the spherical planet surface.
The default way (which I copied from the bullet CharacterDemo) of determining the forward vector from the object transformation by getting the base vectors does not seem to work anymore. I have experimented with a few other methods and after a lot of trial and error I got something that looks fairly accurate.
Note that the player upDir() in the example is the vector that we earlier called the n-vector or the planet normal

Code: Select all

        //set walkDirection for our character
        const btVector3 rightDir(1.0, 0.0, 0.0);
        btVector3 upDir = m_player->getUpDir();
        btQuaternion q = btQuaternion(upDir, m_playerAngle);
        btVector3 axis = rightDir * btMatrix3x3(q);
        btVector3 forwardDir = axis.cross(upDir);
        forwardDir.normalize();
However there is a problem with this method, it seems to be some kind of singularity when I reach a certain point on the sphere (I guess it is when I cross the positive x-axsis?) When I get very close to this point the character starts to circle around it, and when I hit it the character stops and it's hard to get away.

- Is there a better method to determine the forward direction for the player character when moving over the spherical planet surface?

The full source code can be found at: https://github.com/phoboz/tuxgal/blob/m ... cation.cpp
lunkhound
Posts: 99
Joined: Thu Nov 21, 2013 8:57 pm

Re: How to get the tangents for a point on a spherical surfa

Post by lunkhound »

I think the way I would handle this is to simply keep a copy of the forward facing vector. The forward facing vector should always be orthogonal to the up direction, so whenever the up direction changes, you need to adjust the forward vector to keep it orthogonal.
In code the adjustment would look like this:

Code: Select all

mForwardDir -= upDir * upDir.dot( mForwardDir );  // subtract out any component of upDir that is parallel to forwardDir
mForwardDir.normalize();  // need to renormalize
Just make sure that the forward dir is initialized to something that isn't completely parallel to the initial up vector.
The side vector can be created by taking the cross product of forward and up.
If you need a matrix or quaternion those can be easily formed using those 3 vectors -- the 3 vectors form the rows of the matrix, and the quaternion can be made from the matrix.
User avatar
drleviathan
Posts: 678
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: How to get the tangents for a point on a spherical surfa

Post by drleviathan »

I believe lunkhound's solution would work for a while, but I would worry about gradual drift between the explicit mForwardDir and the implicit forward direction that is determined by the character's rotation itself. The implicit forward direction is determined by the character's rotation.

In the character's local-frame: if rightDir is the xAxis and upDir is the yAxis then forwardDir must be the negative zAxis (as per the right-hand rule). You can obtain the world-frame forwardDir by rotating the local-frame forwardDir by the character's rotation.

Code: Select all

const btVector3 localForward(0.0f, 0.0f, -1.0f);
btTransform t = body->getWorldTransform();
btQuaternion q = t.getRotation();
btVector3 forwardDir = q * localForward;
Note: I checked and found that c6burns is correct: there is a multiplication operator overload for btQuaternions on btVector3 inside btQuaternion.h so I don't know why the last line wouldn't compile for bullet-2.82. If it doesn't compile for you then you can use the btMatrix3x3 conversion trick.
lunkhound
Posts: 99
Joined: Thu Nov 21, 2013 8:57 pm

Re: How to get the tangents for a point on a spherical surfa

Post by lunkhound »

Well to prevent any drift, don't bother with storing an explicit forward vector then.

Step 1. Extract the forward vector from the character's rotation.
Step 2. Apply the adjustment to keep it perpendicular to the up vector as I outlined earlier
Step 3. Construct a new character rotation from the new up and forward vectors
Step 4. Set the character's new rotation
jumpa
Posts: 4
Joined: Thu Nov 27, 2014 9:12 am

Re: How to get the tangents for a point on a spherical surfa

Post by jumpa »

Thank you so much your help, the world is not flat anymore :wink:
I now have a working prototype for the 3D Platform Game Engine, you can watch a demonstration here:
http://youtu.be/pkWeBMTsfOo


PS. Please feel free to contribute to this game by forking it on: https://github.com/phoboz/tuxgal
Post Reply