Jump to content

Changing Players Orientation per tick in UpdateMove()


JackStone

Recommended Posts

I am currently working on a spherical terrain implementation in T3D, which is going well, but I have hit a snag with the players movement. I have implemented 3D gravity according to an old resource that I found, and this works great, it pulls the player object towards the center of the world.


In update move ,I am adding the 3D gravity by doing this:

 

VectorF a = terpos - getPosition();
  a.normalize();
 VectorF acc = a;

 

The problem here is that even though the player *moves* towards the terrain center, they are not oriented towards the terrain center. IE, their feet don't face the ground, which means they can't walk on the terrain unless the are standing at or very close to the north pole.


I have done some research into this, but I have not made a whole lot of progress.


There is some code in updatemove() that looks promising, such as:

 

 // If we don't have a runSurface but we do have a contactNormal,
  // then we are standing on something that is too steep.
  // Deflect the force of gravity by the normal so we slide.
  // We could also try aligning it to the runSurface instead,
  // but this seems to work well.

     if ( !runSurface && !contactNormal.isZero() )  
     acc = ( acc - 2 * contactNormal * mDot( acc, contactNormal ) );   

 

What I want to do, basically, is alight the players position with the contactNormal, is this correct? How would this be done?


Thanks!

Link to comment
Share on other sites

Sadly, inside the class Player there is a lot of code that implies that the orientation will be always feet towards -z direction (head goes to +z).


This awesome resource:

http://www.garagegames.com/community/blogs/view/16149

lets you rotate freely but it needs a few changes as it's a bit outdated (well, as you use a custom gravity you have to program changes, anyway).


Also, I heavily suggest you to make a copy of your player files before doing the code changes. You may need to go back to the previous player code if anything goes wrong.

Link to comment
Share on other sites

Yes, I do see a lot of that code in there.


I have actually implemented that resource, and it does seem to be working. I had forgotten that, I just realised now after looking at that link.


It seems then that all I need to do is use the mOrient from that resource to change the orientation of the player in updatemove?


I'm not sure how to do that though, any ideas?

Link to comment
Share on other sites

This would be ideal, but I'm not sure how to grab that normal?


The other thing, is that I'm not sure how to align the player to face a particular normal. I am doing this at the moment:

  Point3F gravityvec = teraincenterpos - getPosition();
gravityvec.normalize();

 QuatF q = QuatF(gravityvec);
 mOrient = q;

 

It seems to be close to working, but it doesn't work on all cases.

Link to comment
Share on other sites

Luckily for you, the updateMove() function in player.cpp already does this for you:

 

   VectorF contactNormal(0,0,0);
  bool jumpSurface = false, runSurface = false;
  if ( !isMounted() )
     findContact( &runSurface, &jumpSurface, &contactNormal );
  if ( jumpSurface )
     mJumpSurfaceNormal = contactNormal;

 

This nets you a VectorF variable with the surface normal data for whatever your player is standing on.


You can follow the trail to the findContact() and _findContact() functions in the same player.cpp file.


Another approach that I often use is to get ahold of the normal data using a raycast. Raycasts will return the normal as part of the collision struct. This can even be done in script, although for something like updating player movement it's best kept here in the source. I give mention to this approach because, believe it or not, it is used already for footprints and footstep sounds.

Link to comment
Share on other sites

Thank, you this seems to have helped me a little. I had seen that code before, but hadn't used it, since I was working on a system using a vector from the players position to the terrain center.


I have replaced my spherical terrain with a simple cube for testing, and I can obtain a contact normal, of the form: "1,0,1" with the player standing on one of the faces.


However, I now need to convert this VectorF into a QuatF.


I tried just setting the quatf to the contactnormal, which compiled, but doesn't work. There is obviously a trick to this that I am not getting.

Link to comment
Share on other sites

Ok, so I think I have reduced this to a simple test case.


When the player is standing on top of a cube, the contact normal is 0,0,1, and the quaternion should be QuatF(Point3F(0,0,0)) for the to be correctly rotated.


When the stand on the side of the cube, facing the negative x axis, the contact normal is (-1,0,0), which it should be. The quaternion here, for correct facing, should be: QuatF(Point3F(0,1.57,0)).


Which is basically a 90 degree rotation along y. The question is, how do I mathematically derive that quaternion from the contact normal and the player position, etc, alone?

Link to comment
Share on other sites

Quaternion math is not the easiest thing. You shouldn't think of them as a fancy vector as quaternions "kinda" have 4 dimensions (direction and a rotation in that direction). They're a transformation so the up vector is defined by something like "Quat.mul(0,0,1)".


Well, just let me share the code I used when I was playing with the "mOrient-resource". Feel free to ask anything if the comments are not enough.


In the .h file inside the class (I used it in "public:") add:

   void doLocalRotateX(const F32& rotX , QuatF* qchanged );
  void doLocalRotateZ(const F32& rotZ , QuatF* qchanged );

  void doQuatOrientation( const Point3F &neworient );

 

In the .cpp file add at the end (for example):

void Player::doLocalRotateX(const F32& rotX, QuatF* qchanged )
{
QuatF tempq(qchanged->x,qchanged->y,qchanged->z,qchanged->w);
if(!mIsZero(rotX))
{
	Point3F localX;
	tempq.mulP(Point3F(1.0,0.0,0.0),&localX);
	QuatF qx(localX,rotX);
	tempq *= qx;
}
tempq.normalize();

qchanged->set(tempq.x,tempq.y,tempq.z,tempq.w);
}

void Player::doLocalRotateZ(const F32& rotZ, QuatF* qchanged )
{
QuatF tempq(qchanged->x,qchanged->y,qchanged->z,qchanged->w);
if(!mIsZero(rotZ))
{
	Point3F localZ;
	tempq.mulP(Point3F(0.0,0.0,1.0),&localZ);
	QuatF qz(localZ,rotZ);
	tempq *= qz;
}
tempq.normalize();

qchanged->set(tempq.x,tempq.y,tempq.z,tempq.w);
}

void Player::doQuatOrientation( const Point3F &neworient )
{
//we will not modify mOrient untill the end
QuatF tempmOrient = mOrient;
tempmOrient.normalize();
tempmOrient.inverse(); //we're going global->local so we need the inverse of mOrient

Point3F vect=neworient; //we need to normalize neworient
vect.normalize();	

Point3F localvect;
tempmOrient.mulP(vect,&localvect);
localvect.normalize(); //this normalize may not be needed... but just in case

tempmOrient = mOrient; //we need back normalized mOrient to do the transformations
tempmOrient.normalize();

//localvect is our wanted neworient in local coordinates
//x and y values are the projections of localvect in the z=0 plane
//to know that angle (that is the rotation on the z axis) we can use then the arctangent
F32 rotZ = mAtan2(localvect.x,localvect.y);

doLocalRotateZ(rotZ,&tempmOrient);

//we rotate around our local z axis so we face our destination (y axis aligned)
//so we only need a final rotation with our local x axis

//rotation on x axis is similar to z but the plane is x=0
F32 rotX = mAtan2(mSqrt(localvect.x*localvect.x + localvect.y*localvect.y),localvect.z);

doLocalRotateX(rotX,&tempmOrient);

//now we undo the initial rotZ
  //This way the initial local-z rotation is similar... I think
doLocalRotateZ((-1)*rotZ,&tempmOrient);

//finally we transform mOrient
mOrient.set(tempmOrient.x,tempmOrient.y,tempmOrient.z,tempmOrient.w);
}

(Change "Player::" with the name of your particular class, of course.)


Then in order to align the mOrient to a "vectorUp" you just do in the c++ code:

doQuatOrientation(vectorUp);

where vectorUp is the new orientation.


You can see I normalize() a lot... maybe too much. Feel free to remove all of the normalize() you think are not needed.


Note I use up as vector. If you're using gravity(going down) then remember to use gravity.neg() or you'll "walk" on your head.



As final note let me share that using the normal as up vector is not a very good idea if you use the default camera of player. Well, it's all right if the terrain is a plane but if it's kind of bumpy like the terrain of the full template then the camera movements are unpleasant when you run.

Link to comment
Share on other sites

Irei1as, you sir are a genius! Thank you very much for that!


Your code works great! I have the orientation working perfectly now.


However, I have run into issues with the camera, as you said. I am using a plane at the moment, but even so, as the player rotates the camera, it seems to interfere with the new code that I added to orient the player.


I assume I have to combine the "look" quaternion with the new quaternion that I added, do you have any ideas how I would go about doing this?


My current code is:

 


  if (!contactNormal.isZero()) {
   Con::printf("CONTACTNORMAL %f %f %f", contactNormal.x, contactNormal.y, contactNormal.z);
   doQuatOrientation(contactNormal);
  }
  else {
   Con::printf("NO CONTACT NORMAL");
   Point3F cogvec = Point3F(0, 0, 0) - getPosition();
   cogvec.normalize();
   cogvec *= -1;
   doQuatOrientation(cogvec);
  }

Link to comment
Share on other sites

I think that's related to one of the changes of player.cpp since that resource and not directly to the part of code you have.

Inside updateMove(const Move* move) look for:

if(doStandardMove)

Inside that comes the code for the normal rotation code.

(I just deleted everything related to "TORQUE_EXTENDED_MOVE" in my code so if you're using that then I can't help you, sorry.)


Now, try to replace the doStandardMove branch with:

      if(doStandardMove)
     {
        F32 p = move->pitch * (mPose == SprintPose ? mDataBlock->sprintPitchScale : 1.0f);
        if (p > M_PI_F) 
           p -= M_2PI_F;
        mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
                 mDataBlock->maxLookAngle);

        F32 y = move->yaw * (mPose == SprintPose ? mDataBlock->sprintYawScale : 1.0f);
        if (y > M_PI_F)
           y -= M_2PI_F;

        if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
        {
           mHead.z = mClampF(mHead.z + y,
                    -mDataBlock->maxFreelookAngle,
                    mDataBlock->maxFreelookAngle);
        }
        else
        {
           mRot.z += y;
           //quat rot {...}
           {
              //Get a quaternion of our desired rotation
              //Apply it to our current orientation
              //First let's try getting an up vector
              Point3F up(0.0f,0.0f,1.0f);
              Point3F rotatedUp(0,0,0);
              mOrient.mulP(up,&rotatedUp);
              //rotatedUp should now contain our local up vector
              //We want a rotation around rotatedUp by what was added to mRot.z radians
              QuatF rot(rotatedUp,y);
              mOrient *= rot;
           }
           // Rotate the head back to the front, center horizontal
           // as well if we're controlling another object.
           mHead.z *= 0.5f;
           if (mControlObject)
              mHead.x *= 0.5f;
        }

        // constrain the range of mRot.z
        while (mRot.z < 0.0f)
        {
           mRot.z += M_2PI_F;
           //quat rot {...}
           {
              //Get a quaternion of our desired rotation
              //Apply it to our current orientation
              //First let's try getting an up vector
              Point3F up(0.0f,0.0f,1.0f);
              Point3F rotatedUp(0,0,0);
              mOrient.mulP(up,&rotatedUp);
              //rotatedUp should now contain our local up vector
              //We want a rotation around rotatedUp by what was added to mRot.z radians
              QuatF rot(rotatedUp,M_2PI_F);
              mOrient *= rot;
           }
        }
        while (mRot.z > M_2PI_F)
        {
           mRot.z -= M_2PI_F;
           //quat rot {...}
           {
              //Get a quaternion of our desired rotation
              //Apply it to our current orientation
              //First let's try getting an up vector
              Point3F up(0.0f,0.0f,1.0f);
              Point3F rotatedUp(0,0,0);
              mOrient.mulP(up,&rotatedUp);
              //rotatedUp should now contain our local up vector
              //We want a rotation around rotatedUp by what was added to mRot.z radians
              QuatF rot(rotatedUp,(-1)*M_2PI_F);
              mOrient *= rot;
           }
        }
     }

 

After that you should have these lines of code:

      delta.rot = mRot;
     delta.orient[1] = mOrient; //quat rot
     delta.rotVec.x = delta.rotVec.y = 0.0f;
     delta.rotVec.z = prevZRot - mRot.z;
     if (delta.rotVec.z > M_PI_F)
        delta.rotVec.z -= M_2PI_F;
     else if (delta.rotVec.z < -M_PI_F)
        delta.rotVec.z += M_2PI_F;

 

So be careful and don't replace too many lines. Maybe you should make a safety copy of the file before doing the changes.




But... I'm not sure if that change will be enough, sorry. In my code I use doQuatOrientation before that "if(doStandardMove)" (it's the first line in the update move as my gravity is a variable of player class and not just normals) so it's kind of different. I guess you could copy (with different variable names) the part of finding the normal at the start of the function if needed.


By the way, the camera problem I mentioned is related to motion sickness. If the geometry of the terrain is irregular the camera jumps wildly around when moving with contact surface.


Edit: Also, check for the pack unpack functions. It seems player has two pairs of each and you need to do the mOrient thing in both unlike once as the resouce points. It must be that before there wasn't that many.

Link to comment
Share on other sites

Hi,


Thanks again for your help. I am using standard move, and the code you posted *almost* fixed the issue. The camera is stable, but now, for some reason, the players head moves by itself to face a particular direction, and every time I move the camera, it return to that same spot.


So there must be something causing the player to move their head? It's not the doQuatOrientation code, I checked that.


I'm also having issues combining the move vector (the actual player movement) with the gravity vector. The two vectors seem to cancel out at certain points. making the player unable to move.


I am currently combining them simply by adding them and normalising:

 

 Point3F m = Point3F(move->x, move->y, move->z);

  gravvec += m;
  gravvec.normalize();
  VectorF acc(gravvec);

 

This, again, *almost* works, but the direction of the players movement along X and Y changes as their orientation changes.


I just saw your edit about the pack/unpack, I will have a look at that tomorrow. I am slowly making progress with this, thanks again!

Link to comment
Share on other sites

Alright, the camera issue is fixed.


I now just have one issue left, and that is that the players movement direction changes when their orientation changes. IE, pressing the forward key when the player is oriented with Z-Up moves them forward, but when Y is up the forward key moves them in a different direction.


Do I need to somehow rotate the move vector by the mOrient quaternion?

Link to comment
Share on other sites

I actually got this working, it turned out to be so simple.


I just needed to change:


runAcc.z = 0;


to

runAcc.z = runAcc.z * mDataBlock->airControl;


Inside:


else if (!mSwimming && mDataBlock->airControl > 0.0f)



That enables full 3D rotation and movement.


This is pretty cool, I can walk all around any object in 3 dimensions now.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...