Game Development Community

dev|Pro Game Development Curriculum

Free-rotating Player with quaternions

by Daniel Buckmaster · 01/25/2009 (2:46 am) · 28 comments

Developed and tested on TGE 1.5.2. Not yet network tested.
I'd be grateful to anyone who wants to volunteer to network-test this.

Length: Pretty long
Difficulty: Intermediate

A feature I've long wanted is the ability for Players to rotate freely, for effects such as walking around planets in Super Mario Galaxy, or running on the walls and ceilings in AvP. There is an old resource for his, but as far as I can tell, it doesn't work very well (if at all) with 1.5.2 or the network. So here's my shot at it.

This resource adds the ability for Players to rotate freely using quaternions. The behaviour of the default player is not changed; you must extend Player to use the free rotation capability for your specific needs. This might be aligning the Player to the terrain or other objects, rotate based on move input (if your Player is flying or swimming) or whatever other wackiness you want.

Note: This resource breaks the AIPlayer thinking logic. I'm working on an updated getAIMove function to take my changes into account.

And here's a video to show it off. Hopefully I can get this one right...

Here's a quick overview of my changes. Basically, we add an 'orient' member to Player, which stores a quaternion with the Player's current orientation. We also need delta quternions for handling interpolation and the like. Whenever in the past we've set the Player's mRot value, we now compose a quaternion rotation and apply it to our orientation. The orientation is sent over the network, so this resource *should* work for multiplayer. Like I said, not yet tested. We use our orient quaternion to create transform matrices whenever we need to, and to orient vectors and boxes correctly.

Files changed: player.h, player.cc.

I'm just going to jump right into the code changes. I'll go through them in the order they appear in the file. Open player.h.

First change we have to make is to add the quaternions where we'll store our rotation data. Find the struct StateDelta { and in the interpolation data, add:
QuatF orient[2];
Then in the warp data, add:
QuatF warpOrient[2];

Right below the StateDelta struct, you'll find the definitions for current position and velocity. Add to these definitions:
QuatF mOrient;                   ///< Current orientation

The Player, by default, uses an OrthoBoxConvex for collision. This is bad, since the point of the OrthoBoxConvex is that it is orthogonally aligned (aligned with the world axes). We need to use a more flexible class for our new collision. Replace the Player definition of mConvex with this:
BoxConvex mConvex;

The next change we need to make is to redefine the setPosition and setRenderPosition methods to use quaternions. Replace these lines:
void setPosition(const Point3F& pos,const Point3F& viewRot);
   void setRenderPosition(const Point3F& pos,const Point3F& viewRot,F32 dt=-1);
With this:
void setPosition(Point3F& pos, QuatF& rot);
   void setRenderPosition(Point3F& pos, QuatF& rot, F32 dt = -1);
I'm not sure whether I should have kept the consts on those parameters. Anyone want to clarify? Either way, the code works.

That's it for the header file. Simple enough, for now... go ahead and open player.cc.

First things first: we're using some maths in this file that we weren't before, namely quaternion maths. We need to include some more headers. Add to the list of includes:
#include "math/mQuat.h"
#include "math/mathIO.h"
That last one is for sending quaternions over the network. It can be done without including this file, but with four write commands instead of one.

Next, we initialise the orientations we defined in the Player class. In Payer::Player, add this:
delta.orient[0] = delta.orient[1]
      = delta.warpOrient[0] = delta.warpOrient[1].identity();
Below the place where delta.rot is initialised. And then where mRot is set to delta.rot, add this:
mOrient.identity();

In Player::processTick, we do some work with the delta struct. I'm not exactly sure what, but find this code:
// Set new pos.
      getTransform().getColumn(3,&delta.pos);
      delta.pos += delta.warpOffset;
      delta.rot += delta.rotOffset;
      setPosition(delta.pos,delta.rot);
      setRenderPosition(delta.pos,delta.rot);
And replace it with this:
// Set new pos.
      getTransform().getColumn(3,&delta.pos);
      delta.pos += delta.warpOffset;
      delta.rot += delta.rotOffset;
      delta.orient[0] = delta.orient[1];
      delta.orient[1].interpolate(delta.warpOrient[0],delta.warpOrient[1],0.5f);
      setPosition(delta.pos,delta.orient[1]);
      setRenderPosition(delta.pos,delta.orient[1]);
This is probably a damned hack. It's that way because I don't understand what warpOffset and rotOffset are doing, so I can't mimic the precise function of this code in my code. I've tried my best. Hopefully someone else can correct me.
(If you've implemented any of my resources before, you'll know that it's like the blind leading the blind...)

Next, we jump to interpolateTick to do some interpolation. This, I'm slightly more sure of. Find this code:
Point3F pos = delta.pos + delta.posVec * dt;
      Point3F rot = delta.rot + delta.rotVec * dt;

      mHead = delta.head + delta.headVec * dt;
      setRenderPosition(pos,rot,dt);
And replace it with this:
Point3F pos = delta.pos + delta.posVec * dt;
      Point3F rot = delta.rot + delta.rotVec * dt;
      QuatF orient;
      orient.interpolate(delta.orient[1],delta.orient[0],dt);

      mHead = delta.head + delta.headVec * dt;
      setRenderPosition(pos,orient,dt);
A little further down in interpolateTick, you'll find:
setRenderPosition(delta.pos, delta.rot, 0);
Replace that with:
setRenderPosition(delta.pos, delta.orient[1], 0);

Okay, now we go to Player::updateMove. This is the function that updates the Player's desired velocity and rotation. Find the start of the orientation handling part. It should look like this:
if (mDamageState == Enabled) {
      F32 prevZRot = mRot.z;
      delta.headVec = mHead;
Now make it look like thus:
if (mDamageState == Enabled) {
      delta.orient[0] = mOrient;
      F32 prevZRot = mRot.z;
      delta.headVec = mHead;
That saves off our current orientation, for when we need to interpolate between ticks.
In this clause, there's just one little line that updates the mRot value. Find it - it's this:
mRot.z += y;
You can replace it with this:
//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 move->yaw radians
         QuatF rot(AngAxisF(rotatedUp,y));
         mOrient *= rot;
That code should have explained itself. This gets a rotation of move->yaw radians about our current 'up' axis and applies it to our orientation. If you want to perform other rotations to your Player, this is a basic way to go about doing it. Notice I use an AngAxisF to get the rotation - this is the only way I can get my head around, but there are probably more efficient ways.

And we need to do some more delta management. Find this line:
delta.rot = mRot;
And add under it:
delta.orient[1] = mOrient;

updateMove then goes on to manage our actual movement. To get the movement directions, we're using, we create a matrix based on our current rotation. This is done in the lines:
MatrixF zRot;
   zRot.set(EulerF(0.0f, 0.0f, mRot.z));
Notice that this matrix only uses z rotation. Time to change that - and use our new quaternion. Replace that snippet with this:
MatrixF zRot;
   mOrient.setMatrix(&zRot);
Now the matrix is aligned according to our current orientation. Now the matrix's name is technically a misnomer (it's more than z rotation), but I couldn't be bothered changing it and having to rename every further occurrence. Feel free to if it bugs you.

A piece of code to note is the application of gravity:
// Acceleration due to gravity
   VectorF acc(0.0f, 0.0f, mGravity * mGravityMod * TickSec);
You might want to change gravity to pull your player downwards locally. I don't do this, but if you did want to, you can use the same method I did before when finding a local up vector. Then use this vector, multiplied by gravity, gravityMod, TickSec, etc., as the acceleration vector.

Next the Player finds contacts to run on. We don't need to change this part of updateMove, but we will be altering the findContact method itself later on.

Continuing down player.cc, we find a reference to setPosition which we need to fix. It's in this function:
void Player::onUnmount(ShapeBase* obj,S32 node)
{
   // Reset back to root position during dismount.
   setActionThread(PlayerData::RootAnim,true,false,false);

   // Re-orient the player straight up
   Point3F pos,vec;
   getTransform().getColumn(1,&vec);
   getTransform().getColumn(3,&pos);
   Point3F rot(0.0f,0.0f,-mAtan(-vec.x,vec.y));
   setPosition(pos,rot);

   // Parent function will call script
   Parent::onUnmount(obj,node);
}
Until now, I haven't figured out a good way to rotate the player back to vertical, as required, while preserving other rotation. Depending on your game's requirements, this may be different. For now, I've just left it:
void Player::onUnmount(ShapeBase* obj,S32 node)
{
   // Reset back to root position during dismount.
   setActionThread(PlayerData::RootAnim,true,false,false);

   // Re-orient the player straight up
   //Can't do this well yet :P
   setPosition(getPosition(),mOrient);

   // Parent function will call script
   Parent::onUnmount(obj,node);
}

Soon after this function we come to Player::step. This deals with stepping the Player up ledges and such which we don't want to get stuck on. I don't *think* we need to make changes here, but I may be wrong. I haven't had enough of a proper testing environment to see yet.

And then we find Player::updatePos. This is the function that takes the Player's velocity and tries to actually move the player in that direction, colliding against things as we go. We need to make some minor changes here to get it working when we're at odd orientations.
First, a reference to setPosition:
setPosition(Point3F(0.0f, 0.0f, 0.0f), mRot);
Becomes
setPosition(Point3F(0.0f, 0.0f, 0.0f), mOrient);

Next find this:
MatrixF collisionMatrix(true);
The collisionMatrix is used to orient our bounding box when colliding. If you initialise a matrix to (true), that makes it an identity matrix - in effect, the bounding box is aligned perfectly along the cardinal axes, not rotated at all. We need to fix this:
MatrixF collisionMatrix = getTransform();

The next place we need to modify is inside the 'early out' check that determines whether we need to do the really complex collision logic. At the moment, the code looks like this:
Box3F wBox = mScaledBox;
         wBox.min += end;
         wBox.max += end;
Bad. This does not take into account our rotation, so essentially we get collision against a world-oriented box. Change those lines to read like this:
Box3F wBox = mConvex.getBoundingBox(collisionMatrix,scale);
         wBox.min += distance;
         wBox.max += distance;

That should just about do it for updatePos. Oh, there's one last call to setPosition:
setPosition(start,mRot);
Obviously, change that to:
setPosition(start,mOrient);

Next up comes Player::findContact, which I mentioned we'd be changing. At the moment, findContact makes a few assumptions about which way we're oriented. Well, we need to get rid of that bigotry.
First a little setup. We need to get a local up vector, which we can do by using our transform (since it's already used to get other vectors). Change this:
getTransform().getColumn(3,&pos);
To this:
MatrixF trans = getTransform();
   trans.getColumn(3,&pos);
   Point3F up;
   trans.getColumn(2,&up);

Next danger zone is in this code:
wBox.min = pos + mScaledBox.min - exp;
   wBox.max.x = pos.x + mScaledBox.max.x;
   wBox.max.y = pos.y + mScaledBox.max.y;
   wBox.max.z = pos.z + mScaledBox.min.z + sTractionDistance;
Here we create a box which we will search for contacts inside. But it's not oriented to be aigned to us, so we'll fix that. Replace those lines with these:
wBox.min = mObjBox.min - exp;
   wBox.max.x = mObjBox.max.x;
   wBox.max.y = mObjBox.max.y;
   wBox.max.z = mObjBox.min.z + sTractionDistance;
   trans.mul(wBox);
When we do trans.mul(wBox), we're orienting the wBox to the trans coordinate system, and also adding to its position (which is why we removes the references to pos in the min and max points).

Next, find this:
polyList.setInterestNormal(Point3F(0.0f, 0.0f, -1.0f));
I don't know exactly what that means, but it looks like hardcoding. Change it to use our own up vector:
polyList.setInterestNormal(-up);
Make sure you include that negative sign.

Later on in this function, we search through our contacts and find the one the 'most horizontal'. Some foresighted coder saw fit t leave a comment on the exact line we have to change:
F32 vd = poly->plane.z;       // i.e.  mDot(Point3F(0,0,1), poly->plane);
It seems that this line dots the up vector with the plane's normal, and based on these results, a plane is chosen. We'll need to insert our own up vector here. Change the line to:
F32 vd = mDot(up, poly->plane);

Now we finally come to the setPosition function we've been replacing so much. Change the entire function to this:
void Player::setPosition(Point3F& pos, QuatF& rot)
{
   MatrixF mat;
   if (isMounted()) {
      // Use transform from mounted object
      MatrixF nmat;
      mMount.object->getMountTransform(mMount.node,&nmat);
      mOrient = rot;
      mOrient.setMatrix(&mat);
      mat.setColumn(3,nmat.getPosition());
   }
   else {
      mOrient = rot;
      mOrient.setMatrix(&mat);
      mat.setColumn(3,pos);
   }
   Parent::setTransform(mat);
}
This code isn't optimal, at least in the mounted clause. You might want to set your orient to be fixed to that of the mounted node; the original code didn't seem to do that, so neither does mine.

Also change the setRenderPosition code to this:
void Player::setRenderPosition(Point3F& pos, QuatF& rot, F32 dt)
{
   MatrixF mat;
   if (isMounted()) {
      // Use transform from mounted object
      MatrixF nmat;
      mMount.object->getRenderMountTransform(mMount.node,&nmat);
      mOrient = rot;
      mOrient.setMatrix(&mat);
      mat.setColumn(3,nmat.getPosition());
   }
   else {
      mOrient = rot;
      mOrient.setMatrix(&mat);
      mat.setColumn(3,pos);
      if (inDeathAnim()) {
         F32   boxRad = (mDataBlock->boxSize.x * 0.5f);
         if (MatrixF * fallMat = mDeath.fallToGround(dt, pos, rot.z, boxRad))
            mat = * fallMat;
      }
      else
         mDeath.initFall();
   }
   Parent::setRenderTransform(mat);
}

One more function to change, and that's Player's implementation of setTransform. Just make it look like this:
void Player::setTransform(const MatrixF& mat)
{
   // This method should never be called on the client.
   // This currently converts all rotation in the mat into
   // rotations around the z axis.
   //Not any more, it don't!
   Point3F pos;
   QuatF orient;
   pos = mat.getPosition();
   orient.set(mat);
   setPosition(pos,orient);
   setMaskBits(MoveMask | NoWarpMask);
}
Note the use of QuatF::set is entirely different from QuatF::setMat. The former sets the quaternion to a rotation specified by a matrix, AngAxisF, etcetera. The latter uses the quaternion to create a matrix of the same orientation. Yeah, logical.

This code change is your last chance to enjoy yourself, because from here on in it's all networking. Remember we replaced the Player's OrthoBoxConvex with the more useful BoxConvex? There's one line we need to update to deal with that. Find this line:
BoxConvex* cp = new OrthoBoxConvex;
And take away the Ortho:
BoxConvex* cp = new BoxConvex;

Now we come to the joys of networking. My favourite part of any resource. We need to send the orientation across the network, but also replace a few references to setPosition. The next few code changes span the write/readPacketData and pack/unpackUpdate methods.
Just find this:
stream->write(mHead.x);
   stream->write(mHead.z);
   stream->write(mRot.z);
And add:
mathWrite(*stream,mOrient);

Then find:
setPosition(pos,rot);
   delta.head = mHead;
   delta.rot = rot;
And change it to:
QuatF orient;
   mathRead(*stream,&orient);
   setPosition(pos,orient);
   delta.head = mHead;
   delta.rot = rot;
   delta.orient[1] = orient;

Find this:
stream->writeFloat(mRot.z / M_2PI_F, 7);
And add this:
mathWrite(*stream,mOrient);

We've got some more delta management to do. Find this big chunk:
if (stream->readFlag()) {
      mPredictionCount = sMaxPredictionTicks;
      mFalling = stream->readFlag();

      ActionState actionState = (ActionState)stream->readInt(NumStateBits);
      if (stream->readFlag()) {
         mRecoverTicks = stream->readInt(PlayerData::RecoverDelayBits);
         setState(actionState, mRecoverTicks);
      }
      else
         setState(actionState);

      Point3F pos,rot;
      stream->readCompressedPoint(&pos);
      F32 speed = mVelocity.len();
      if(stream->readFlag())
      {
         stream->readNormalVector(&mVelocity, 10);
         mVelocity *= stream->readInt(13) / 32.0f;
      }
      else
      {
         mVelocity.set(0.0f, 0.0f, 0.0f);
      }
      
      rot.y = rot.x = 0.0f;
      rot.z = stream->readFloat(7) * M_2PI_F;
      mHead.x = stream->readSignedFloat(6) * mDataBlock->maxLookAngle;
      mHead.z = stream->readSignedFloat(6) * mDataBlock->maxLookAngle;
      delta.move.unpack(stream);

And replace it with:
if (stream->readFlag()) {
      delta.warpOrient[0] = mOrient;
      mPredictionCount = sMaxPredictionTicks;
      mFalling = stream->readFlag();

      ActionState actionState = (ActionState)stream->readInt(NumStateBits);
      if (stream->readFlag()) {
         mRecoverTicks = stream->readInt(PlayerData::RecoverDelayBits);
         setState(actionState, mRecoverTicks);
      }
      else
         setState(actionState);

      Point3F pos,rot;
      QuatF orient;
      stream->readCompressedPoint(&pos);
      F32 speed = mVelocity.len();
      if(stream->readFlag())
      {
         stream->readNormalVector(&mVelocity, 10);
         mVelocity *= stream->readInt(13) / 32.0f;
      }
      else
      {
         mVelocity.set(0.0f, 0.0f, 0.0f);
      }
      
      rot.y = rot.x = 0.0f;
      rot.z = stream->readFloat(7) * M_2PI_F;
      mathRead(*stream,&orient);
      mHead.x = stream->readSignedFloat(6) * mDataBlock->maxLookAngle;
      mHead.z = stream->readSignedFloat(6) * mDataBlock->maxLookAngle;
      delta.move.unpack(stream);

Then, a ways down, you'll find this:
delta.rotOffset = rot - delta.rot;
            if(delta.rotOffset.z < - M_PI)
               delta.rotOffset.z += M_2PI;
            else if(delta.rotOffset.z > M_PI)
               delta.rotOffset.z -= M_2PI;
            delta.rotOffset /= delta.warpTicks;
Append after it:
delta.warpOrient[0] = delta.orient[1];
            delta.warpOrient[1] = orient;

Find this:
delta.pos = pos;
            delta.rot = rot;
            setPosition(pos,rot);
And replace it with...:
delta.pos = pos;
            delta.rot = rot;
            delta.orient[1] = orient;
            setPosition(pos,orient);

And finally, replace one more
setPosition(pos,rot);
With
setPosition(pos,orient);

Phew! That was a long slog. Now, there's one more slog. If you want to, you can remove every reference to Player::mRot in the code. Just do a 'find symbol' and delete it all. You can do the same with Player::StateDelta::rot, Player::StateDelta::rotVec and Player::stateDelta::rotOffset. Or, on the other hand, you can leave it all in. It should work equally well either way. If you do decide to rip all the rotation stuff out, be very careful in readPackedData and unpackUpdate. There are some reads in there that don't use the rotation values, but are written directly from them &ndash; if you ignore these reads, you'll get all kinds of insane crashes.
Go ahead and compile. It *should* all work. If I've done my part, and you've done yours, then you should find... nothing! Yes, the sign of a well-designed addition is that it doesn't break what's already there. Thank you, thank you. Oh, you actually want to see some free rotation?
If you insist. Go into the world editor and detach the camera. Fly around to somewhere, and tilt the camera down at around 45 degrees. Hit Alt-W to drop the Player at the camera, and presto! your Player will be dropped facing 45 degrees into the ground.
Okay, so I lied a little before. There do seem to be some issues. The obvious one is the AIPlayer::getAIMove is broken, since it can no longer use mRot to get a rotation value. Kork will spin on the spot until you kill him, and not get anything useful done. Also, there seem to be some problems with stepping when you're travelling at low speeds. I'll look into it.
Now go and have some fun... extending this functionality to actually make it useful!

About the author

Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!

Page «Previous 1 2
#1
01/25/2009 (10:53 am)
It's an incredible start, but can you jump in it yet? When I implemented your forum post, jumping would not work.
#2
01/26/2009 (1:01 pm)
I've just started monitoring the forum thread. For the sake of convenience, I'll repeat myself - I haven't experienced any problems with jumping, but the only implementation of this I have to test right now is in my own personal engine build, so my other changes may be distorting my testing of this resource. Try stepping through updateMove and findContact to see where the jump request is failing.
#3
01/26/2009 (2:16 pm)
It could be that I'm using a custom build as well, as I have modified the player quite a bit, I'm sure I've stayed out of contact and jump code though (at least in this build)...or did I?
#4
01/30/2009 (3:36 pm)
any chance of a premodified version of the files?
#5
01/31/2009 (1:00 am)
Still no file uploading, but I'll see what I can do...
I've implemented the resource on a completely clean 1.5.2 install. If someone wants the files, shoot me an email at dan dot buckmaster at gmail.
Morrock - jumping seems to be working for me. Any progress on the issues you're having?
#6
02/04/2009 (3:14 pm)
Excellent work Daniel.

This method is a lot more elegant than the one I came up with.
I basically replaced all the movement code for the player and replaced it with code similar to the vehicle code. In essence turned the player into a rigid body like object.

One thing I can't figure out is how to use your resource for planet like gravity. I have a custom GravityZone object similar to the physicalzone object that updates a variable in the player class(actually the Gamebase class) that is a Point3F that points to the GravityZones center relative to the players current position.
It is essentially a gravity normal.
I already use it to apply gravity to the player in the updatemove function

// Acceleration due to gravity
  VectorF acc(0.0f, 0.0f, mGravity * mGravityMod * TickSec);
   if(mGravityForce!= Point3F(0,0,0))
   acc =  mGravityForce * mGravity * mGravityMod * TickSec;

Any suggestions?
#7
02/05/2009 (9:18 am)
I would not use conforming to control gravity - in my code base, I apply a foo counteract gravity, if we happen to be clinging to the ceiling. To get gravity that is applied towards a central point, I'd just add a 'centre' member to your gravity field. Then you can get the vector between the Player and the gravity centre as the direction, and normalize it to the correct length.
[Edit: Reading back over your post, I realised that's pretty much what you've done, isn't it? In that case, your gravity should be fine...?]
My resource will simply help aligning within a gravity field.
But if you really wanted gravity to always be applied in the local down direction, here's a quick example:
Point3F grav(0.0f,0.0f,1.0f); //World gravity direction
   mOrient.mulP(grav,&grav); //Now it's local to us
   VectorF acc(grav * mGravity * mGravityMod * TickSec);
#8
02/05/2009 (9:39 am)
Yes that is pretty much what I have done.

I have already got it working for gravity.
I may have not made it clear what I was asking.
What I need is to be able to align the player based of of my mGravityForce variable.
mGravityForce is a normal pointing in the direction of the gravity being applied to the player.

Basically align the player perpendicular to the mGravityForce normal.
Quaternions are not my strong point.

Thanks
#9
02/05/2009 (10:59 am)
Ah, I read you. I might have to make some addons to this resource for different functionality ;P.

Basically, you need to compute the proper rotation for your Player, which is vertical, but facing in a direction closest to the Player's current orientation. I do this in processTick instead of updateMove, since there seem to be client/server issues with updateMove - and the fact that auto-vertical-ing doesn't have anything to do with the move.
This is the code I'm using:
//Update our non-vertical orientation, including conforming
   if(mDataBlock->autoOrientVertical
      || mClimbing
      || mDataBlock->conformToSurface[mStance])
   {
      if(isGhost() && !getControllingClient())
         bool thingo = true;
      Point3F wvert(0.0f,0.0f,1.0f);
      Point3F vert;
      mOrient.mulP(wvert,&vert);

      if(mClimbing)
      {
         //Construct a tangent
         //normal x up = horiz. tangent
         Point3F ht = mCross(mConformNormal,wvert);
         //horiz. tan. x normal = vert. tangent
         wvert = mCross(ht,mConformNormal);
      }
      else if(mDataBlock->conformToSurface[mStance])
         wvert = mConformNormal;

      Point3F diff = vert - wvert;
      if(diff.lenSquared() > 0.000001f)
      {
         F32 ang = mAcos(mDot(vert,wvert));
         Point3F axis;
         if(mFabs(vert.z) < 1.0f)
            axis = mCross(wvert,vert);
         else
         {
            Point3F forw(0.0f,1.0f,0.0f);
            mOrient.mulP(forw,&forw);
            axis = mCross(wvert,forw);
         }
         QuatF rot(AngAxisF(axis,getMin(ang,mDataBlock->autoOrientSpeed * TickSec)));
         mOrient *= rot;
         mOrient.normalize();
      }
   }
See if you can distil the basics from that. There's some specific stuff from my Player modifications in there, which you'll need to rip out. I added some datablock fields to control the motion, and added an mConformNormal to Player that is networked and is calculated in updateMove (that's probably not the best idea :P).
#10
02/05/2009 (11:13 am)
Awesome.
This is what I settled on
if(mGravityForce != Point3F(0.0f,0.0f,0.0f))
	   {
  Point3F wvert(0.0f,0.0f,1.0f);
      Point3F vert;
      mOrient.mulP(wvert,&vert);

	  wvert = mGravityForce;

	//mOrient.mulP(wvert,&vert);
	Point3F diff = vert - wvert;
	if(diff.lenSquared() > 0.00001)
	{
		F32 ang = mDot(vert,wvert);
		Point3F axis;
		if(mFabs(vert.z) < 1.0f)
			axis = mCross(wvert,vert);
		else
		{
			Point3F forw(0.0f,1.0f,0.0f);
			mOrient.mulP(forw,&forw);
			axis = mCross(wvert,forw);
		}
		QuatF rot(AngAxisF(axis,getMin(ang,TickSec)));
		mOrient *= rot;
		mOrient.normalize();
	}

Where I got stuck was
Point3F wvert(0.0f,0.0f,1.0f);
      Point3F vert;
      mOrient.mulP(wvert,&vert);

	  wvert = mGravityForce;

I think I see now how it works.

It works perfectly now.
You will get credit for this one.
#11
02/05/2009 (11:29 am)
Glad you worked it out :)
Basically, that code you picked out creates a global vertical vector (wvert), then uses mOrient to localise it and store the result in vert.
[Actually, I think I've got that wrong. wvert is actually a local up vector, then mulling it by mOrient converts it to the global coordinate system. I like to think of it the other way. :P]
Anyway, the upshot is that vert contains a vector that points along the Player's vertical axis. Then, like you've done, you compare this axis to the axis you want to conform to.

For people who are also interested in using this to conform to the terrain and have been following my comments: in retrospect, I think putting the conform normal calculations in updateMove was a bad idea. I'm moving them to processTik, alongside the code that conforms the player to the desired normal.
#12
02/05/2009 (12:47 pm)
One slight problem I just encountered.
If the player starts passed 90 degrees to the local gravity,I.E. closer to upside down, it will re-aligned him on his head.

It shouldn't be to hard to code in a little logic for this case.
#13
02/06/2009 (12:21 pm)
Huh... I don't think I've experienced that problem. Try doing an inverse cos on your dot product to compute 'ang':
F32 ang = mAcos(mDot(...));
#14
02/08/2009 (11:12 am)
I've managed to get AIPlayers working again. However, I'm now experiencing some wierd jerking when an AIPlayer is turning to its aim location. I'm not sure if this has to do with the networking, but I suspect so. I'd guess my interpolation/extrapolation code is at fault.

Also, mantling/stepping does not work at odd angles, I suspect.
#15
02/24/2009 (1:39 pm)
Mantling is fixed, but I will probably take a little time to update the resource. I'll add those changes along with a fix for the sliding-on-head issue Bill Vee mentioned in his DOW tech demo blog.
#16
03/11/2009 (7:40 pm)
I am using this resource for a school project, and need the AIPlayers as well. Is there any way I could see those fixes sooner rather than later? I don't need mantling or anything fancy like that, just to get the AI moving again.
#17
03/12/2009 (8:36 am)
Sorry about that - I've been a little busy with the real world lately (standard excuse ;P) and haven't done much programming. But I'll post up the AIPlayer fix for you. Basically is just modifies the getAIMove method. Find this code block:
F32 xDiff = mAimLocation.x - location.x;
      F32 yDiff = mAimLocation.y - location.y;
      if (!isZero(xDiff) || !isZero(yDiff)) {

      ...

         movePtr->yaw = yawDiff;
Replace it with:
Point3F diff;
      mWorldToObj.mulV(mAimLocation - location,&diff);
      if (!isZero(diff.x) || !isZero(diff.y)) {

         F32 yaw = mAtan(diff.x,diff.y);
         F32 fac = /*TickSec / mFabs(yaw)*/ 1.0f;
         movePtr->yaw = yaw * (fac < 1.0f? fac : 1.0f);
Find this:
if (mMoveState == ModeMove) {
      F32 xDiff = mMoveDestination.x - location.x;
      F32 yDiff = mMoveDestination.y - location.y;
And replace it with:
if (mMoveState == ModeMove) {
      Point3F diff;
      mWorldToObj.mulV(mMoveDestination - location,&diff);
And find this code block and remove it all
// Rotate the move into object space (this really only needs
         // a 2D matrix)
         Point3F newMove;

         ...

         movePtr->y = newMove.y;
You'll also need to findreferences to xDiff and yDiff and change then to diff.x and diff.y, since I consolidated the difference into a point3F, rather than maintaining two F32s.
I haven't cleaned up the code much, but it should be working.
#18
06/06/2009 (2:50 pm)
Hey, have you uploaded the fixes for mantling? it doesnt seem to be working correctly under some circumstances...

"Mantling is fixed, but I will probably take a little time to update the resource. I'll add those changes along with a fix for the sliding-on-head issue Bill Vee mentioned in his DOW tech demo blog."

could you upload the rest of the fixes into this absolutely great resource?

Edit: And another bug with this resource, is that it mess's up swimming underwater, (the move directions get messed up.)
#19
06/10/2009 (7:01 am)
How does this affect AIPlayer functions such as moveTo?
*EDIT* never mind i missed that sentence in your intro!
#20
06/10/2009 (11:44 am)
I'm away from my dev computer at the moment (well... to be precise, my dev computer has gone to a better place :P), so I won't have access to my code until I get another decent computer working.

Don, I have found a fix for the getAIMove function - it's relatively simple, I think. I will post it as soon as I can.
[Oh... I did already post it. See comments. I will update the resource sometime.]

Aldavidson (Al?), you're rit that mantling and stepping become broken at odd angles. This is because in Player::upsatePos, a property of a collision maxHeight is used to determine the ability to step or mantle. This height is hardcoded as lying along the z axis, and I'm not savvy enough with the collision code to fix that.
I *think* I may have managed to implement a workaround that doesn't use maxHeight - less efficient, but at least it worked. I'd have to recreate this myself though, since I have actually totally redone my own Player movement system since I wrote this resource :P.

Swimming, I never tested thoroughly... can you describe in any more detail what is wrong with the swimming direction? Can you detect a pattern?

Sliding on the character's head should be easy to fix. In updatePos (I think), if there's a collision with the ground but our collision timer is running (meaning we haven't contacted the ground for a while), then start to auto-orient ourselves vertical. That may require a per-object flag for vertical orient (see my code in post #9), rather than just a datablock flag.
Page «Previous 1 2