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:
Right below the StateDelta struct, you'll find the definitions for current position and velocity. Add to these definitions:
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:
The next change we need to make is to redefine the setPosition and setRenderPosition methods to use quaternions. Replace these lines:
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:
Next, we initialise the orientations we defined in the Player class. In Payer::Player, add this:
In Player::processTick, we do some work with the delta struct. I'm not exactly sure what, but find this code:
(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:
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:
In this clause, there's just one little line that updates the mRot value. Find it - it's this:
And we need to do some more delta management. Find this line:
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:
A piece of code to note is the application of gravity:
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:
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:
Next find this:
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:
That should just about do it for updatePos. Oh, there's one last call to setPosition:
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:
Next danger zone is in this code:
Next, find this:
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:
Now we finally come to the setPosition function we've been replacing so much. Change the entire function to this:
Also change the setRenderPosition code to this:
One more function to change, and that's Player's implementation of setTransform. Just make it look like this:
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:
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:
Then find:
Find this:
We've got some more delta management to do. Find this big chunk:
And replace it with:
Then, a ways down, you'll find this:
Find this:
And finally, replace one more
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 – 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!
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 – 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!
#22
Mantling I'm really not sure how I fixed, since the code relies on a hardcoded part of the collision system which takes 'up' to be '0 0 1'.
I just noticed though that the AIPlayer fix as posted will not work really well. You'll need to change the following code to look like this:
09/01/2009 (2:24 am)
Sorry I haven't been great about supporting this. The main problem is that I'm not using most of my resources - I've moved on too far from the stock Player system to make use of my old code.Mantling I'm really not sure how I fixed, since the code relies on a hardcoded part of the collision system which takes 'up' to be '0 0 1'.
I just noticed though that the AIPlayer fix as posted will not work really well. You'll need to change the following code to look like this:
if (isZero(diff.x))
movePtr->y = (diff.y < 0.0f)? -1 : 1;
else
if (diff.y == 0.0f)
movePtr->x = (diff.x < 0.0f)? -1 : 1;
else
if (mFabs(diff.x) > mFabs(diff.y)) {
F32 value = mFabs(diff.y / diff.x);
movePtr->y = (diff.y < 0.0f)? -value : value;
movePtr->x = (diff.x < 0.0f)? -1 : 1;
}
else {
F32 value = mFabs(diff.x / diff.y);
movePtr->x = (diff.x < 0.0f)? -value : value;
movePtr->y = (diff.y < 0.0f)? -1 : 1;
}
#23
10/23/2009 (4:35 am)
You develop excellent code. Keep it up. I really like your examples of how to do things. I am not coding like I used to, and your resources help explain things well. My focus is elsewhere so any help is appreciated. My kids asked me to develop a simple game for them and this resource should help with working toward a player based 6DOF ship for them to play with.
#24
10/23/2009 (9:21 pm)
Glad to know it's of some use!
#25
I am encountering a problem with the code I posted for my alteration to your code.
Where I run into problems is if mGravityForce has two variables that are nearly 0.0f and the third is near -1.0f or 1.0f.
some examples
mGravityForce = Point3F(0.0f,0.0f,-1.0f)
mGravityForce = Point3F(0.0f,0.0f,1.0f)
mGravityForce = Point3F(0.0f,-1.0f,0.0f)
mGravityForce = Point3F(0.0f,1.0f,0.0f)
mGravityForce = Point3F(-1.0f,0.0f,0.0f)
mGravityForce = Point3F(1.0f,0.0f,0.0f)
What happens is the player will jitter back and forth along its x axis about 10 degrees randomly.
Obviously I don't know enough about quaternions to understand whats going on.
Any thoughts?
11/04/2009 (10:07 pm)
Daniel.I am encountering a problem with the code I posted for my alteration to your code.
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 run into problems is if mGravityForce has two variables that are nearly 0.0f and the third is near -1.0f or 1.0f.
some examples
mGravityForce = Point3F(0.0f,0.0f,-1.0f)
mGravityForce = Point3F(0.0f,0.0f,1.0f)
mGravityForce = Point3F(0.0f,-1.0f,0.0f)
mGravityForce = Point3F(0.0f,1.0f,0.0f)
mGravityForce = Point3F(-1.0f,0.0f,0.0f)
mGravityForce = Point3F(1.0f,0.0f,0.0f)
What happens is the player will jitter back and forth along its x axis about 10 degrees randomly.
Obviously I don't know enough about quaternions to understand whats going on.
Any thoughts?
#26
11/06/2009 (2:40 am)
That could be an issue with the raycast. Does that happen consistently, or only when you're standing on an edge in the terrain? I found that sometimes if you were standing on an edge, you would align to one polygon, then the raycast would detect another polygon, and you'd align to that one, which would swing the raycast back onto the first polygon... etc. At least, I think that was the issue.
#28
11/17/2009 (5:36 pm)
Sorry about the slow response. Hmm. So x-axis jittering must be caused by the second loop, where mFabs(vert.z) == 1.0f, which makes sense. I can't see anything wrong with the code... it seems to be very similar to mine (I've just re-implemented this). Here's what I'm using, which is working fine with gravity 0,0,1://Calculate conform in terms of angle and axis
Point3F lvert(0.0f,0.0f,1.0f); //Local up
Point3F vert; //Local up in world terms
mOrient.mulP(lvert,&vert);
Point3F diff = vert - mConformNormal;
if(diff.lenSquared() > 0.0001f) //Therefore diff.len() > 0.01
{
F32 ang = mAcos(mDot(vert,mConformNormal));
Point3F axis;
if(mFabs(vert.z) < 1.0f)
axis = mCross(mConformNormal,vert);
else
{
Point3F forw(0.0f,1.0f,0.0f);
mOrient.mulP(forw,&forw);
axis = mCross(mConformNormal,forw);
}
QuatF rot(AngAxisF(axis,getMin(ang,mDataBlock->conformSpeedRads * TickSec)));
mOrient *= rot;
mOrient.normalize();
} 
Torque 3D Owner Aldavidson