The Flight Compendium
by Bruno Grieco · 01/30/2005 (1:07 am) · 85 comments
The Flight Compendium
This is an attempt to gather the most important resources I found regarding flight and flying bots I found hanging around GG site. So please keep in mind that I'm just putting together information that other people created. Also, I'm a programmer and my skills in math and physics aren't really great so I don't really understand much of the calculations that are being done, but I do understand where they start and end so I was able to encapsulate them properly.
Acknowledgements
I would like to thank Badguy for his vehicle steering code, Wendell Brown that had actually achieved the bot 3D flight on this thread, F.W. Hardijzer for his excellent Air Control resource, Erik Madison for the Swimming Resource that pointed the way in how to control simple flight and everyone else I forgot to mention that contributed to the heavier-than-air flight.
Simple vs. Complex Flight
There are two ways of flying. The simple way which is making a Player (and an AIPlayer for inheritance) take of the ground and walk in mid-air and the complex way that involves mounting an AIPlayer onto a vehicle and have it fly around. The main difference about them are the physics involved.
You should choose the simple flight method if : your player is supposed to fly on his own, for instance if he has wings (note that there is a Jet Pack resource that will handle if your player has such item), if you need precision in the spot you send your AIPlayer to (since complex flight involves inertia, the move tolerance is bigger), and you are adverse to C++ coding ( even though you will have to do some )
Meanwhile, the complex flight is for those who require a more realistic flight movement, need vehicles to fly and aren't afraid of a compiler.
Simple Flight
The main problem about flying is that you need a Z coordinate (height) to send your bot to, unfortunately the move algorithm only deals with XY plane coordinates, the Z is ignored, it's only used to check if you are falling or climbing something (a hill) in both cases it will check the datablock to see what should be done (fall, or reduce the speed).
The solution to this problem came from the swimming resource, it uses the SetAimLocation() function to determine the direction to tilt the player and the height it should achive.
Coding :
You should edit the Player.cc file ( in engine/Game ) and replace the Player::updateMove() function for this one (It looks big but copy&Paste will do the trick):
Complex Flight
Complex Flight will be implemented directly on AIPlayer. This will enable a bot to pilot flying, wheeled and hover vehicles. The Z-coord info is passed to the aim location on the SetMoveDestination function ( which would also work in Simple Flight ).
In order to maintain the original Move capability, the getAIMove function should be renamed to getAIOnFootMove and a new getAIMove and a getAIMountedMove will be provided.
Coding :
In AIPlayer.h, insert :
In AIPlayer.c substitute :
( remember to rename the previous getAIMove to getAIOnFootMove )
and insert :
in FlyingVehicle.cc, around line 36
Usage
In order to the bot to pilot it, you must create the bot and the vehicle. Then you mount the bot in the vehicle using the function below :
the bot should now drive the vehicle correctly when using %bot.setMoveDestination().
Datablocks
Before complaining that the resource doesn't work, and your plane or hoverthing doesn't go wherever you want to, keep in mind that the great villain in this matter can and will be your Datablock. They can be pretty nasty and even FREEZE the game so I'm adding below the datablocks for wheeled, flying and hover vehicles that did work for me.
But there is only one way of having the vehicles behaving the way you want : TWEAKING.
Vehicle physics are yet a great mystery to me and I have very little or even no idea of how those little parameters work out together.
Wheled Vehicle Datablock
Flying Vehicle Datablock
Hover Vehicle Datablock
Disclaimer
OK guys, that's about it. That's all I know about vehicles, hope it helps.
This is an attempt to gather the most important resources I found regarding flight and flying bots I found hanging around GG site. So please keep in mind that I'm just putting together information that other people created. Also, I'm a programmer and my skills in math and physics aren't really great so I don't really understand much of the calculations that are being done, but I do understand where they start and end so I was able to encapsulate them properly.
Acknowledgements
I would like to thank Badguy for his vehicle steering code, Wendell Brown that had actually achieved the bot 3D flight on this thread, F.W. Hardijzer for his excellent Air Control resource, Erik Madison for the Swimming Resource that pointed the way in how to control simple flight and everyone else I forgot to mention that contributed to the heavier-than-air flight.
Simple vs. Complex Flight
There are two ways of flying. The simple way which is making a Player (and an AIPlayer for inheritance) take of the ground and walk in mid-air and the complex way that involves mounting an AIPlayer onto a vehicle and have it fly around. The main difference about them are the physics involved.
You should choose the simple flight method if : your player is supposed to fly on his own, for instance if he has wings (note that there is a Jet Pack resource that will handle if your player has such item), if you need precision in the spot you send your AIPlayer to (since complex flight involves inertia, the move tolerance is bigger), and you are adverse to C++ coding ( even though you will have to do some )
Meanwhile, the complex flight is for those who require a more realistic flight movement, need vehicles to fly and aren't afraid of a compiler.
Simple Flight
The main problem about flying is that you need a Z coordinate (height) to send your bot to, unfortunately the move algorithm only deals with XY plane coordinates, the Z is ignored, it's only used to check if you are falling or climbing something (a hill) in both cases it will check the datablock to see what should be done (fall, or reduce the speed).
The solution to this problem came from the swimming resource, it uses the SetAimLocation() function to determine the direction to tilt the player and the height it should achive.
Coding :
You should edit the Player.cc file ( in engine/Game ) and replace the Player::updateMove() function for this one (It looks big but copy&Paste will do the trick):
//----------------------------------------------------------------------------
void Player::updateMove(const Move* move)
{
delta.move = *move;
// Trigger images
if (mDamageState == Enabled) {
setImageTriggerState(0,move->trigger[0]);
setImageTriggerState(1,move->trigger[1]);
}
// Update current orientation
if (mDamageState == Enabled)
{
F32 prevZRot = mRot.z;
delta.headVec = mHead;
F32 p = move->pitch;
if (p > M_PI) p -= M_2PI;
mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
mDataBlock->maxLookAngle);
F32 y = move->yaw;
if (y > M_PI) y -= M_2PI;
GameConnection* con = getControllingClient();
if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
{
mHead.z = mClampF(mHead.z + y,
-mDataBlock->maxFreelookAngle,
mDataBlock->maxFreelookAngle);
}
else
{
mRot.z += y;
// Rotate the head back to the front, center horizontal
// as well if we're controlling another object.
mHead.z *= 0.5;
if (mControlObject)
mHead.x *= 0.5;
}
// constrain the range of mRot.z
while (mRot.z < 0)
mRot.z += M_2PI;
while (mRot.z > M_2PI)
mRot.z -= M_2PI;
delta.rot = mRot;
delta.rotVec.x = delta.rotVec.y = 0;
delta.rotVec.z = prevZRot - mRot.z;
if (delta.rotVec.z > M_PI)
delta.rotVec.z -= M_2PI;
else if (delta.rotVec.z < -M_PI)
delta.rotVec.z += M_2PI;
delta.head = mHead;
delta.headVec -= mHead;
}
MatrixF zRot;
zRot.set(EulerF(0, 0, mRot.z));
// Desired move direction & speed
VectorF moveVec;
F32 moveSpeed;
if (mState == MoveState && mDamageState == Enabled)
{
zRot.getColumn(0,&moveVec);
moveVec *= move->x;
VectorF tv;
zRot.getColumn(1,&tv);
moveVec += tv * move->y;
// Clamp water movement
if (move->y > 0)
{
if( mWaterCoverage >= 0.9 )
moveSpeed = getMax(mDataBlock->maxUnderwaterForwardSpeed * move->y,
mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x));
else
moveSpeed = getMax(mDataBlock->maxForwardSpeed * move->y,
mDataBlock->maxSideSpeed * mFabs(move->x));
}
else
{
if( mWaterCoverage >= 0.9 )
moveSpeed = getMax(mDataBlock->maxUnderwaterBackwardSpeed * mFabs(move->y),
mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x));
else
moveSpeed = getMax(mDataBlock->maxBackwardSpeed * mFabs(move->y),
mDataBlock->maxSideSpeed * mFabs(move->x));
}
// Cancel any script driven animations if we are going to move.
/*if (moveVec.x + moveVec.y + moveVec.z != 0 &&
(mActionAnimation.action >= PlayerData::NumTableActionAnims
|| mActionAnimation.action == PlayerData::LandAnim))
mActionAnimation.action = PlayerData::NullAnimation;
*/
}
else
{
moveVec.set(0,0,0);
moveSpeed = 0;
}
// Acceleration due to gravity
VectorF acc(0,0,mGravity * mGravityMod * TickSec);
//VectorF acc(0,0,0.5);
// Determin ground contact normal. Only look for contacts if
// we can move.
VectorF contactNormal, normalZ(moveVec.x,-moveVec.z,moveVec.y);
bool jumpSurface = false, runSurface = false;
if (!isMounted())
findContact(&runSurface,&jumpSurface,&contactNormal);
if (jumpSurface)
mJumpSurfaceNormal = contactNormal;
// Point3F headRotation = getHeadRotation();
// acc.z = (-headRotation.x) * (mDataBlock->runForce / mMass) * TickSec;
// Acceleration on run surface
if (runSurface)
{
//Con::printf("Estou em uma Run Surface");
mContactTimer = 0;
// Remove acc into contact surface (should only be gravity)
// Clear out floating point acc errors, this will allow
// the player to "rest" on the ground.
F32 vd = -mDot(acc,contactNormal);
if (vd > 0) {
VectorF dv = contactNormal * (vd + 0.002);
acc += dv;
if (acc.len() < 0.0001)
acc.set(0,0,0);
}
// Force a 0 move if there is no energy, and only drain
// move energy if we're moving.
VectorF pv;
if (mEnergy >= mDataBlock->minRunEnergy) {
if (moveSpeed)
mEnergy -= mDataBlock->runEnergyDrain;
pv = moveVec;
}
else
pv.set(0,0,0);
// Adjust the players's requested dir. to be parallel
// to the contact surface.
F32 pvl = pv.len();
if(pvl)
{
VectorF nn;
mCross(pv,VectorF(0,0,1),&nn);
nn *= 1 / pvl;
VectorF cv = contactNormal;
cv -= nn * mDot(nn,cv);
pv -= cv * mDot(pv,cv);
pvl = pv.len();
}
// Convert to acceleration
if (pvl)
pv *= moveSpeed / pvl;
VectorF runAcc = pv - (mVelocity + acc);
F32 runSpeed = runAcc.len();
// Clamp acceleratin, player also accelerates faster when
// in his hard landing recover state.
F32 maxAcc = (mDataBlock->runForce / mMass) * TickSec;
if (mState == RecoverState)
maxAcc *= mDataBlock->recoverRunForceScale;
if (runSpeed > maxAcc)
runAcc *= maxAcc / runSpeed;
acc += runAcc;
// If we are running on the ground, then we're not jumping
if (mDataBlock->isJumpAction(mActionAnimation.action))
mActionAnimation.action = PlayerData::NullAnimation;
}
else
{
F32 airControl = 1.5;
mContactTimer=0;
//--- by DJMystic ---
VectorF pv;
pv = moveVec;
F32 pvl = pv.len();
if (pvl)
pv *= moveSpeed / pvl;
VectorF runAcc = pv - ( mVelocity + acc);
runAcc.z = runAcc.z * airControl;
runAcc.x = runAcc.x * airControl;
runAcc.y = runAcc.y * airControl;
F32 runSpeed = runAcc.len();
F32 maxAcc = (mDataBlock->runForce / mMass) * TickSec * airControl;
if (runSpeed > maxAcc)
runAcc *= maxAcc / runSpeed;
acc += runAcc;
//--- end by DJMystic ---
}
Point3F headRotation = getHeadRotation();
acc.z = (-headRotation.x) * (mDataBlock->runForce / mMass) * TickSec;
// Acceleration from Jumping
if (move->trigger[2] && !isMounted() && canJump())
{
// Scale the jump impulse base on maxJumpSpeed
F32 zSpeedScale = mVelocity.z;
if (zSpeedScale <= mDataBlock->maxJumpSpeed) {
zSpeedScale = (zSpeedScale <= mDataBlock->minJumpSpeed)? 1:
1 - (zSpeedScale - mDataBlock->minJumpSpeed) /
(mDataBlock->maxJumpSpeed - mDataBlock->minJumpSpeed);
// Desired jump direction
VectorF pv = moveVec;
F32 len = pv.len();
if (len > 0)
pv *= 1 / len;
// We want to scale the jump size by the player size, somewhat
// in reduced ratio so a smaller player can jump higher in
// proportion to his size, than a larger player.
F32 scaleZ = (getScale().z * 0.25) + 0.75;
// If we are facing into the surface jump up, otherwise
// jump away from surface.
F32 dot = mDot(pv,mJumpSurfaceNormal);
F32 impulse = mDataBlock->jumpForce / mMass;
if (dot <= 0)
acc.z += mJumpSurfaceNormal.z * scaleZ * impulse * zSpeedScale;
else {
acc.x += pv.x * impulse * dot;
acc.y += pv.y * impulse * dot;
acc.z += mJumpSurfaceNormal.z * scaleZ * impulse * zSpeedScale;
}
mJumpDelay = mDataBlock->jumpDelay;
mEnergy -= mDataBlock->jumpEnergyDrain;
setActionThread((mVelocity.len() < 0.5)?
PlayerData::StandJumpAnim: PlayerData::JumpAnim, true, false, true);
mJumpSurfaceLastContact = JumpSkipContactsMax;
}
}
else
{
if (jumpSurface)
{
if (mJumpDelay > 0)
mJumpDelay--;
mJumpSurfaceLastContact = 0;
}
else
mJumpSurfaceLastContact++;
}
// Add in force from physical zones...
acc += (mAppliedForce / mMass) * TickSec;
// Adjust velocity with all the move & gravity acceleration
// TG: I forgot why doesn't the TickSec multiply happen here...
mVelocity += acc;
// apply horizontal air resistance
F32 hvel = mSqrt(mVelocity.x * mVelocity.x + mVelocity.y * mVelocity.y);
if(hvel > mDataBlock->horizResistSpeed)
{
F32 speedCap = hvel;
if(speedCap > mDataBlock->horizMaxSpeed)
speedCap = mDataBlock->horizMaxSpeed;
speedCap -= mDataBlock->horizResistFactor * TickSec * (speedCap - mDataBlock->horizResistSpeed);
F32 scale = speedCap / hvel;
mVelocity.x *= scale;
mVelocity.y *= scale;
}
if(mVelocity.z > mDataBlock->upResistSpeed)
{
if(mVelocity.z > mDataBlock->upMaxSpeed)
mVelocity.z = mDataBlock->upMaxSpeed;
mVelocity.z -= mDataBlock->upResistFactor * TickSec * (mVelocity.z - mDataBlock->upResistSpeed);
}
// Container buoyancy & drag
if (mBuoyancy != 0)
{ // Applying buoyancy when standing still causing some jitters-
if (mBuoyancy > 1.0 || !runSurface) // || !mVelocity.isZero() )
mVelocity.z -= mBuoyancy * mGravity * mGravityMod * TickSec;
}
mVelocity -= mVelocity * mDrag * TickSec;
// If we are not touching anything and have sufficient -z vel,
// we are falling.
if (runSurface)
mFalling = false;
else
{
VectorF vel;
mWorldToObj.mulV(mVelocity,&vel);
mFalling = vel.z < sFallingThreshold;
}
if (!isGhost())
{
// Vehicle Dismount
if(move->trigger[2] && isMounted())
Con::executef(mDataBlock,2,"doDismount",scriptThis());
if(!inLiquid && mWaterCoverage != 0.0f)
{
Con::executef(mDataBlock,4,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), Con::getIntArg(mLiquidType));
inLiquid = true;
}
else if(inLiquid && mWaterCoverage == 0.0f)
{
Con::executef(mDataBlock,3,"onLeaveLiquid",scriptThis(), Con::getIntArg(mLiquidType));
inLiquid = false;
}
}
else
{
if(!inLiquid && mWaterCoverage >= 1.0f)
{
inLiquid = true;
}
else
if(inLiquid && mWaterCoverage < 0.8f)
{
if(getVelocity().len() >= mDataBlock->exitSplashSoundVel && !isMounted())
alxPlay(mDataBlock->sound[PlayerData::ExitWater], &getTransform());
inLiquid = false;
}
}
}
//----------------------------------------------------------------------------Complex Flight
Complex Flight will be implemented directly on AIPlayer. This will enable a bot to pilot flying, wheeled and hover vehicles. The Z-coord info is passed to the aim location on the SetMoveDestination function ( which would also work in Simple Flight ).
In order to maintain the original Move capability, the getAIMove function should be renamed to getAIOnFootMove and a new getAIMove and a getAIMountedMove will be provided.
Coding :
In AIPlayer.h, insert :
virtual bool getAIMountedMove(Move *move); virtual bool getAIOnFootMove(Move *move);right after the original getAIMove prototype.
In AIPlayer.c substitute :
void AIPlayer::setMoveDestination( const Point3F &location, bool slowdown )
{
mMoveDestination = location;
mMoveState = ModeMove;
mAimLocationSet = true;
mAimLocation = location;
mMoveSlowdown = slowdown;
}bool AIPlayer::getAIMove(Move *movePtr)
{
if(isMounted())
return(getAIMountedMove(movePtr));
else
return(getAIOnFootMove(movePtr));
}( remember to rename the previous getAIMove to getAIOnFootMove )
and insert :
bool AIPlayer::getAIMountedMove(Move *movePtr)
{
*movePtr = NullMove;
Point3F location;
Point3F rotation;
F32 myTol = mMoveTolerance * 7;
// now we use local space
location.x = location.y = location.z = 0.0;
rotation.x = rotation.z = 0.0;
rotation.y = 1.0;
MatrixF invTrans = getTransform();
invTrans.inverse();
// <- phdana testing
// Orient towards the aim point, aim object, or towards
// our destination.
// NOTE: you must change the protection these data members
// of AIPlayer from private to protected to get this
// to compile...
if (mAimObject || mAimLocationSet || mMoveState == ModeMove)
{
// Update the aim position if we're aiming for an object
if (mAimObject)
mAimLocation = mAimObject->getPosition();
// transform to local space
Point3F aimLocation;
invTrans.mulP(mAimLocation,&aimLocation);
F32 xDiff = aimLocation.x - location.x;
F32 yDiff = aimLocation.y - location.y;
if (!isZero(xDiff) || !isZero(yDiff)) {
// First do Yaw
// use the cur yaw between -Pi and Pi
F32 curYaw = rotation.z;
while (curYaw > M_2PI)
curYaw -= M_2PI;
while (curYaw < -M_2PI)
curYaw += M_2PI;
// find the yaw offset
F32 newYaw = mAtan( xDiff, yDiff );
F32 yawDiff = newYaw - curYaw;
// make it between 0 and 2PI
if( yawDiff < 0.0f )
yawDiff += M_2PI;
else if( yawDiff >= M_2PI )
yawDiff -= M_2PI;
// now make sure we take the short way around the circle
if( yawDiff > M_PI )
yawDiff -= M_2PI;
else if( yawDiff < -M_PI )
yawDiff += M_2PI;
movePtr->yaw = yawDiff;
// Next do pitch. This should be adjusted to run from the
// eye point to the object's center position. Though this
// works well enough for now.
F32 vertDist = aimLocation.z - location.z;
F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
F32 newPitch = mAtan( horzDist, vertDist ) - ( M_PI / 2.0f );
// phdana testing ->
movePtr->pitch = newPitch - rotation.x;
// <- phdan testsing
}
}
else
{
// Level out if we're not doing anything else
// Point3F headRotation = getRotation();
// movePtr->pitch = -headRotation.x;
}
// Move towards the destination
if (mMoveState == ModeMove)
{
// transform to local space
Point3F moveDestination;
invTrans.mulP(mMoveDestination,&moveDestination);
F32 xDiff = moveDestination.x - location.x;
F32 yDiff = moveDestination.y - location.y;
F32 zDiff = mMoveDestination.z - location.z;
// Check if we should mMove, or if we are 'close enough'
if ((mFabs(xDiff) < myTol) && (mFabs(yDiff) < myTol)) // && (mFabs(zDiff) < myTol ))
{
mMoveState = ModeStop;
Con::printf("AI Steering : ------------------------------------- Cheguei no destino pela função default");
throwCallback("onReachDestination");
}
else
{
// Build move direction in world space
if (isZero(xDiff))
{
movePtr->y = 1; // Always go forward
}
else
{
if (isZero(yDiff))
{
movePtr->x = (location.x > mMoveDestination.x)? -1 : 1;
}
else
{
if (mFabs(xDiff) > mFabs(yDiff))
{
F32 value = mFabs(yDiff / xDiff);
movePtr->x = (location.x > mMoveDestination.x)? -1 : 1;
movePtr->y = value;
}
else
{
F32 value = mFabs(xDiff / yDiff);
movePtr->x = (location.x > mMoveDestination.x)? -value : value;
movePtr->y = 1;
}
}
}
}
// Rotate the move into object space (this really only needs
// a 2D matrix)
Point3F newMove;
MatrixF moveMatrix;
moveMatrix.set(EulerF(0, 0, -(rotation.z + movePtr->yaw)));
moveMatrix.mulV( Point3F( movePtr->x, movePtr->y, 0 ), &newMove );
// Set movement speed. We'll slow down once we get close
// to try and stop on the spot...
if (mMoveSlowdown)
{
F32 speed = mMoveSpeed;
F32 dist = mSqrt(xDiff*xDiff + yDiff*yDiff);
F32 maxDist = 5;
if (dist < maxDist)
speed *= dist / maxDist;
movePtr->x *= speed;
movePtr->y *= speed;
}
else
{
movePtr->x *= mMoveSpeed;
movePtr->y *= mMoveSpeed;
}
// We should check to see if we are stuck...
if (location == mLastLocation)
{
throwCallback("onMoveStuck");
mMoveState = ModeStop;
}
}
// Replicate the trigger state into the move so that
// triggers can be controlled from scripts.
for( int i = 0; i < MaxTriggerKeys; i++ )
movePtr->trigger[i] = getImageTriggerState(i);
return true;
}in FlyingVehicle.cc, around line 36
sFlyingVehicleGravity = 0;
Usage
In order to the bot to pilot it, you must create the bot and the vehicle. Then you mount the bot in the vehicle using the function below :
function MountVehicle(%obj, %targetObject)
{
echo("Found a vehicle: " @ %targetObject);
onMountVehicle(%targetObject.getDataBlock(),%obj,%targetObject);
}the bot should now drive the vehicle correctly when using %bot.setMoveDestination().
Datablocks
Before complaining that the resource doesn't work, and your plane or hoverthing doesn't go wherever you want to, keep in mind that the great villain in this matter can and will be your Datablock. They can be pretty nasty and even FREEZE the game so I'm adding below the datablocks for wheeled, flying and hover vehicles that did work for me.
But there is only one way of having the vehicles behaving the way you want : TWEAKING.
Vehicle physics are yet a great mystery to me and I have very little or even no idea of how those little parameters work out together.
Wheled Vehicle Datablock
datablock WheeledVehicleData(DefaultCar)
{
category = "Vehicles";
shapeFile = "~/data/shapes/buggy/buggy.dts";
emap = true;
maxDamage = 1.0;
destroyedLevel = 0.5;
maxSteeringAngle = 0.785; // Maximum steering angle, should match animation
tireEmitter = TireEmitter; // All the tires use the same dust emitter
// 3rd person camera settings
cameraRoll = true; // Roll the camera with the vehicle
cameraMaxDist = 6; // Far distance from vehicle
cameraOffset = 1.5; // Vertical offset from camera mount point
cameraLag = 0.1; // Velocity lag of camera
cameraDecay = 0.75; // Decay per sec. rate of velocity lag
// Rigid Body
mass = 200;
massCenter = "0 -0.5 0"; // Center of mass for rigid body
massBox = "0 0 0"; // Size of box used for moment of inertia,
// if zero it defaults to object bounding box
drag = 0.6; // Drag coefficient
bodyFriction = 0.6;
bodyRestitution = 0.4;
minImpactSpeed = 5; // Impacts over this invoke the script callback
softImpactSpeed = 5; // Play SoftImpact Sound
hardImpactSpeed = 15; // Play HardImpact Sound
integration = 4; // Physics integration: TickSec/Rate
collisionTol = 0.1; // Collision distance tolerance
contactTol = 0.1; // Contact velocity tolerance
// Engine
engineTorque = 1000; // Engine power era 4000
engineBrake = 600; // Braking when throttle is 0
brakeTorque = 8000; // When brakes are applied
maxWheelSpeed = 30; // Engine scale by current speed / max speed
// Energy
maxEnergy = 100;
jetForce = 3000;
minJetEnergy = 30;
jetEnergyDrain = 2;
// Sounds
// jetSound = ScoutThrustSound;
// engineSound = BuggyEngineSound;
// squealSound = ScoutSquealSound;
// softImpactSound = SoftImpactSound;
// hardImpactSound = HardImpactSound;
// wheelImpactSound = WheelImpactSound;
// explosion = VehicleExplosion;
maxMountSpeed = 0.1;
mountDelay = 2;
dismountDelay = 1;
stationaryThreshold = 0.5;
maxDismountSpeed = 0.1;
numMountPoints = 1;
mountable = true;
mountPose[0] = "Sitting";
mountPointTransform[0] = "0 0 0 0 0 1 0";
isProtectedMountPoint[0] = false;
};Flying Vehicle Datablock
datablock FlyingVehicleData(Drone)
{
spawnOffset = "0 0 2";
emap = true;
category = "Vehicles";
shapeFile = "~/data/shapes/vehicles/Drone.dts";
multipassenger = false;
computeCRC = true;
debrisShapeName = "~/data/shapes/vehicles/Drone.dts";
debris = DroneShapeDebris;
renderWhenDestroyed = false;
mountPose[0] = sitting;
numMountPoints = 1;
isProtectedMountPoint[0] = true;
cameraMaxDist = 0.5;
cameraOffset = 4.5;
cameraLag = 0.0;
cameraRoll = true; // Roll the camera with the vehicle
// explosion = DroneVehicleExplosion;
explosionDamage = 10.5;
explosionRadius = 15.0;
maxDamage = 50.40;
destroyedLevel = 50.40;
isShielded = true;
energyPerDamagePoint = 160;
maxEnergy = 280; // Afterburner and any energy weapon pool
rechargeRate = 0.8;
integration = 4; // Physics integration: TickSec/Rate
collisionTol = 0.2; // Collision distance tolerance
contactTol = 0.1;
drag = 0.6;
density = 6.0;
minDrag = 30; // Linear Drag (eventually slows you down when not thrusting...constant drag)
rotationalDrag = 5; // Anguler Drag (dampens the drift after you stop moving the mouse...also tumble drag)
maxAutoSpeed = 10; // Autostabilizer kicks in when less than this speed. (meters/second)
autoAngularForce = 200; // Angular stabilizer force (this force levels you out when autostabilizer kicks in)
autoLinearForce = 200; // Linear stabilzer force (this slows you down when autostabilizer kicks in)
autoInputDamping = 0.95; // Dampen control input so you don't' whack out at very slow speeds
// Maneuvering
maxSteeringAngle = 0.785; // Max radiens you can rotate the wheel. Smaller number is more maneuverable.
horizontalSurfaceForce = 6; // Horizontal center "wing" (provides "bite" into the wind for climbing/diving and turning)
verticalSurfaceForce = 0.5; // Vertical center "wing" (controls side slip. lower numbers make MORE slide.)
maneuveringForce = 1400; // Horizontal jets (W,S,D,A key thrust)
steeringForce = 200; // Steering jets (force applied when you move the mouse)
steeringRollForce = 10; // Steering jets (how much you heel over when you turn)
rollForce = 20; // Auto-roll (self-correction to right you after you roll/invert)
hoverHeight = 45; // Height off the ground at rest
createHoverHeight = 45; // Height off the ground when created
maxForwardSpeed = 60; // speed in which forward thrust force is no longer applied (meters/second)
// Turbo Jet
jetForce = 3000; // Afterburner thrust (this is in addition to normal thrust)
minJetEnergy = 28; // Afterburner can't be used if below this threshhold.
jetEnergyDrain = 2.8; // Energy use of the afterburners (low number is less drain...can be fractional) // Auto stabilize speed
vertThrustMultiple = 3.0;
// Rigid body
mass = 90; //90 // Mass of the vehicle
bodyFriction = 0; // Don't mess with this.
bodyRestitution = 0.4; // When you hit the ground, how much you rebound. (between 0 and 1)
minRollSpeed = 2000; // Don't mess with this.
softImpactSpeed = 3; // Sound hooks. This is the soft hit.
hardImpactSpeed = 15; // Sound hooks. This is the hard hit.
// Ground Impact Damage (uses DamageType::Ground)
minImpactSpeed = 10; // If hit ground at speed above this then it's an impact. Meters/second
speedDamageScale = 0.06;
// Object Impact Damage (uses DamageType::Impact)
collDamageThresholdVel = 23.0;
collDamageMultiplier = 0.02;
//
minTrailSpeed = 15; // The speed your contrail shows up at.
triggerDustHeight = 4.0;
dustHeight = 1.0;
damageEmitterOffset[0] = "0.0 -3.0 0.0 ";
damageLevelTolerance[0] = 0.3;
damageLevelTolerance[1] = 0.7;
numDmgEmitterAreas = 3;
minMountDist = 2;
maxMountSpeed = 100;
checkRadius = 5.5;
observeParameters = "0 0 0";
shieldEffectScale = "0.937 1.125 0.60";
};Hover Vehicle Datablock
datablock HoverVehicleData(AssaultTank)
{
spawnOffset = "0 0 1";
floatingGravMag = 50.5;
catagory = "Vehicles";
shapeFile = "~/data/shapes/buggy/buggy.dts";
computeCRC = true;
renderWhenDestroyed = false;
drag = 0.6;
density = 0.3;
mountPose[0] = sitting;
numMountPoints = 1;
cameraMaxDist = 15.0;
cameraOffset = 5.7;
cameraLag = 5.5;
isProtectedMountPoint[0] = false;
explosionDamage = 0.5;
explosionRadius = 5.0;
lightOnly = 1;
maxDamage = 120;
destroyedLevel = 120;
isShielded = true;
rechargeRate = 0.7;
energyPerDamagePoint = 75;
maxEnergy = 150;
minJetEnergy = 15;
jetEnergyDrain = 1.3;
// Rigid Body
mass = 5;
bodyFriction = 0;
bodyRestitution = 0.5;
softImpactSpeed = 20; // Play SoftImpact Sound
hardImpactSpeed = 28; // Play HardImpact Sound
// Ground Impact Damage (uses DamageType::Ground)
minImpactSpeed = 29;
speedDamageScale = 0.010;
// Object Impact Damage (uses DamageType::Impact)
collDamageThresholdVel = 23;
collDamageMultiplier = 0.030;
dragForce = 4; // 1.9
// dragForce = 25 / 45.0;
vertFactor = 0.5;
floatingThrustFactor = 0.35;
mainThrustForce = 100;
reverseThrustForce = 70;
strafeThrustForce = 70;
turboFactor = 1.5;
brakingForce = 30;
brakingActivationSpeed = 30;
stabLenMin = 6.50;
stabLenMax = 7.25;
stabSpringConstant = 20;
stabDampingConstant = 20;
gyroDrag = 16; // 16 // 36
gyroForce = 50; //50
normalForce = 10;
restorativeForce = 100;
steeringForce = 90;
rollForce = 7;
pitchForce = 10;
minMountDist = 4;
checkRadius = 1.7785;
observeParameters = "1 10 10";
shieldEffectScale = "0.9375 1.125 0.6";
};Disclaimer
OK guys, that's about it. That's all I know about vehicles, hope it helps.
About the author
#42
Now I'm thinking of looking into this more, and tie it to an script and model animations. One thing that'll need to be added is end of effect speed.
eg.. Airbrakes would only be effective by two sets of speeds max. and min.
The higher would do physical damage to the aircraft (nothing like engaging airbrakes at mach 1.5 :) ) and the lower would have no effect on braking(unless aircraft is in contact with the ground, eg. wheel brakes).
08/23/2006 (6:21 pm)
Has anyone throught of using the brake code from the core vehicle code and applying it to craft as an airbrake?brakeTorque = 8000; // When brakes are applied
Now I'm thinking of looking into this more, and tie it to an script and model animations. One thing that'll need to be added is end of effect speed.
eg.. Airbrakes would only be effective by two sets of speeds max. and min.
The higher would do physical damage to the aircraft (nothing like engaging airbrakes at mach 1.5 :) ) and the lower would have no effect on braking(unless aircraft is in contact with the ground, eg. wheel brakes).
#43
My debug (VS Express, TGE1.4) worked ... but my Release exe would not make an AIPlayer follow the path. The problem happened on-and-off. It turned out that the engine was not calling the PlayerData::onReachDestination correctly. The index for the current_node was not being set.
Quick fix
function AIPlayer::moveToNode(%this,%index)
{
// Move to the given path node index
if (%index $= ""){
%index = 1;
}
%this.currentNode = %index;
%node = %this.path.getObject(%index);
%this.setMoveDestination(%node.getTransform(), %index == %this.targetNode);
}
hope this helps someone.
09/05/2006 (9:33 am)
In case anyone has the same problem I did. My pilot would just hover, wouldn't follow path.My debug (VS Express, TGE1.4) worked ... but my Release exe would not make an AIPlayer follow the path. The problem happened on-and-off. It turned out that the engine was not calling the PlayerData::onReachDestination correctly. The index for the current_node was not being set.
Quick fix
function AIPlayer::moveToNode(%this,%index)
{
// Move to the given path node index
if (%index $= ""){
%index = 1;
}
%this.currentNode = %index;
%node = %this.path.getObject(%index);
%this.setMoveDestination(%node.getTransform(), %index == %this.targetNode);
}
hope this helps someone.
#44
The OnReachDestination callback has always been somewhat strange. In GG's staff own words : "Bots don't like to reach their destination".
This situation worsens if you have more than one bot. Scheduling several think methods ( 1 for each bot ) works better in this case than having just 1 think method for all bots.
09/05/2006 (9:53 am)
@Randy,The OnReachDestination callback has always been somewhat strange. In GG's staff own words : "Bots don't like to reach their destination".
This situation worsens if you have more than one bot. Scheduling several think methods ( 1 for each bot ) works better in this case than having just 1 think method for all bots.
#45
09/06/2006 (12:45 am)
Is it possible and natural for my Flying vehicle to miss the node and just fly around it the whole time? Haven't gotten to AI yet, so I wasn't stressing about it yet. But saw the last 2 posts, and thought I'll ask.
#46
09/06/2006 (5:48 am)
This is clearly a tolerance problem. Your vehicle is near the destination but it can't reach the exact point. Increase the MoveTolerance.
#47
09/08/2006 (2:13 am)
I have one AI bot, and about four or five nodes it may want to reach all on different Z levels.... is the simple solution the way to go? The AI bot has no vehicle... she is just a flying fairy. I would like to use the moveto function, but I could use path following.... will there be a problem?
#49
09/09/2006 (3:15 am)
Thanks Bruno! My concern using the simple model is whether or not the user control player will be able to fly. For my game, I don't want the user controlled player to fly. I only want the AI controlled avatar to fly. Is there a way to do so?
#50
Then I changed this section of the processTick() in player.cc
Now I am trying to figure out how to expose a boolean variable to make it so that the player can fly upon script change of one of its datablock members.... more to come
09/12/2006 (7:34 am)
I was able to get just the AI to "fly" using the simple model, by creating the updateMove as a newUpdateMove and leaving the old updateMove in there. Then I changed this section of the processTick() in player.cc
boolean isThisAI = false;
// If we're not being controlled by a client, let the
// AI sub-module get a chance at producing a move.
Move aiMove;
if (!move && isServerObject() && getAIMove(&aiMove))
{
move = &aiMove;
isThisAI = true;
}And this sectionPROFILE_START(Player_PhysicsSection);
if(isServerObject() || (didRenderLastRender() || getControllingClient()))
{
updateWorkingCollisionSet();
updateState();
if (isThisAI)
{
newUpdateMove(move);
}
else
{
updateMove(move);
}
updateLookAnimation();
updateDeathOffsets();
updatePos();
}
PROFILE_END();Now I am trying to figure out how to expose a boolean variable to make it so that the player can fly upon script change of one of its datablock members.... more to come
#51
11/16/2006 (5:57 pm)
Has anyone else noticed that non-mounted characters, including your own, float in the air after implementing this resource? For example, in the stronghold mission if you look up then your character will start to float up. Even the pathed bot will float up in the air as it's running around. Anybody have any thoughts on this?
#52
I had the same thing happen when I wrongly put the code into player.cc.
http://www.garagegames.com/mg/forums/result.thread.php?qt=49989
EDIT : BTW This resource works in 1.5
11/16/2006 (6:00 pm)
@Robert. I had the same thing happen when I wrongly put the code into player.cc.
http://www.garagegames.com/mg/forums/result.thread.php?qt=49989
EDIT : BTW This resource works in 1.5
#53
That was the issue. Thanks for pointing that out!
11/16/2006 (6:33 pm)
In other words, I shouldn't have made the changes in player.cc.That was the issue. Thanks for pointing that out!
#54
However, I'm wondering if anyone else has aircraft that look like they're piloted by drunken ai's. He can barely get around, but it it ugly to behold. He kinda heterodynes back and forth, and can't keep the nose on target. I'm using the complex method. I've tried both default Warsparrow and the datablock provided above, with surprisingly similar results.
Note: I did not make the changes to player.cc.
Update: I've had some pretty reasonable success, the key being setting RotationalDrag = 20. He still takes some bizarre paths, but it sure does look pretty!
It also seemed to help to scale my path markers 200 200 200. Is this the best way to loosen the tolerance?
12/17/2006 (5:06 pm)
Thanks for this! This is fabulous!However, I'm wondering if anyone else has aircraft that look like they're piloted by drunken ai's. He can barely get around, but it it ugly to behold. He kinda heterodynes back and forth, and can't keep the nose on target. I'm using the complex method. I've tried both default Warsparrow and the datablock provided above, with surprisingly similar results.
Note: I did not make the changes to player.cc.
Update: I've had some pretty reasonable success, the key being setting RotationalDrag = 20. He still takes some bizarre paths, but it sure does look pretty!
It also seemed to help to scale my path markers 200 200 200. Is this the best way to loosen the tolerance?
#55
Here's a link to a short movie cm4msaa7.com/thrashycar.avi
5 MB
01/02/2007 (2:34 pm)
Has anyone else had wheeled vehicles weave back and forth while successfully following a path? I'm wondering if there is another function fighting for control of the vehicle, but I'm stuck trying to figure out how to figure out which one it is...?Here's a link to a short movie cm4msaa7.com/thrashycar.avi
5 MB
#56
well I' previously fixed the part where the event onStuck is fired (since equality in floating points almost never happenes coz it almost never equal coz 2.0 will be equal to 2.0000001 or 2.00000011 as an example )
there is a resource abt that btw
so i fixed it in the older function & in th new one too, so when i mount the AIplayer on a vehicle & try to send a destination the event onStuck is fired &the move stops, I notice that the move is already canceled just after the event is fired. would that be my problem ?? (I'll b working on it in the morning & bk towork)
but if so, does anyone have the onstuck event working with this resource ??
I'm sure that this resource will be really gr8 as soon as i have it working.
if it helps when i set a move the tires turn in the correct direction then I get the stuck event
thnx all ;)
06/23/2007 (6:12 pm)
hmmm well I've got a question (I hope its a good one :D )well I' previously fixed the part where the event onStuck is fired (since equality in floating points almost never happenes coz it almost never equal coz 2.0 will be equal to 2.0000001 or 2.00000011 as an example )
there is a resource abt that btw
so i fixed it in the older function & in th new one too, so when i mount the AIplayer on a vehicle & try to send a destination the event onStuck is fired &the move stops, I notice that the move is already canceled just after the event is fired. would that be my problem ?? (I'll b working on it in the morning & bk towork)
but if so, does anyone have the onstuck event working with this resource ??
I'm sure that this resource will be really gr8 as soon as i have it working.
if it helps when i set a move the tires turn in the correct direction then I get the stuck event
thnx all ;)
#57
I think I've got a clue about why drivers seem to be drunk drivers & why the keep on moving in a zig-zag path, I think its because the wheels of the car don't return back to face the forward direction (they keep they last direction) & they have to be reset by hand (I noticed that in the racing demo in TGE 1.5). I'm not sure about that yet but I'll find any help in the resources about vehicles
I hope this helps
06/24/2007 (6:54 am)
well I removed the part were the move is canceled after the onstuck event is fired (check the previous post) & now the car moves great.I think I've got a clue about why drivers seem to be drunk drivers & why the keep on moving in a zig-zag path, I think its because the wheels of the car don't return back to face the forward direction (they keep they last direction) & they have to be reset by hand (I noticed that in the racing demo in TGE 1.5). I'm not sure about that yet but I'll find any help in the resources about vehicles
I hope this helps
#58
I added the resource "Vehicle Steering Upgrade" found here
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6305
(check the posts on the same link, I applied all of them as they were needed)
& I increased the return speed instead of 0.04 to 0.2
then I made some changed to the getAIMountedMove & it should look as follows:
(edit: I removed the unformated code & wrote it again my next post here, the post after the next)
06/24/2007 (11:31 am)
I have relatively fixed the drunken driving on the WheeledVehicles but I didn't try the hovers or flying onesI added the resource "Vehicle Steering Upgrade" found here
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6305
(check the posts on the same link, I applied all of them as they were needed)
& I increased the return speed instead of 0.04 to 0.2
then I made some changed to the getAIMountedMove & it should look as follows:
(edit: I removed the unformated code & wrote it again my next post here, the post after the next)
#59
You were the first one that had problems with the onMoveStuck routine. It probably is due to some version update.
The whole point of this resource is to solve the "drunkness" problem of the drivers. If your drivers were drunk, probably something else was wrong in the implementation of the code.
The newer versions of the engine already implemente an AIWheeledVehicle that could work better for you if you don't need flying around.
BTW, to format the code, use the markers
finish with :
[/code]
06/24/2007 (6:53 pm)
Hello Ehab,You were the first one that had problems with the onMoveStuck routine. It probably is due to some version update.
The whole point of this resource is to solve the "drunkness" problem of the drivers. If your drivers were drunk, probably something else was wrong in the implementation of the code.
The newer versions of the engine already implemente an AIWheeledVehicle that could work better for you if you don't need flying around.
BTW, to format the code, use the markers
normal text [code] write code here
finish with :
[/code]
#60
thats the code again so it would look better
what I did exactly was
1- the wheels of the vehicle now return back to the forward direction just after steering, & quickly (the resource I mentioned
2- the virtual path between the current location & the move destination is now thicker so the car wont have to keep turning from left to right & vice versa to stay on track
3- I made the car to start de-acceleration earlier that before
I still have a small issue:
the car makes a odd (but kinda nice) turn to stop (a 180 turn) & I'm trying to solve that, but probably I need to increase the friction in the datablock (thinking loudly)
& I'll allow the AI driver to move backwards if the destination is in his back & is not far away (& I'll try to make it for the wheeled vehicles only)
06/24/2007 (7:22 pm)
thnx Bruno, I've already fixed the Drunk Drivers problem (although it really was funny :D ) as I mentioned in the previous post (they really move in straight lines now), but before I applied this resource the ai was driving in circles away from the destinationbool AIPlayer::getAIMountedMove(Move *movePtr)
{
*movePtr = NullMove;
Point3F location;
Point3F rotation;
F32 myTol = mMoveTolerance * 14;
// now we use local space
location.x = location.y = location.z = 0.0;
rotation.x = rotation.z = 0.0;
rotation.y = 1.0;
MatrixF invTrans = getTransform();
invTrans.inverse();
// <- phdana testing
// Orient towards the aim point, aim object, or towards
// our destination.
// NOTE: you must change the protection these data members
// of AIPlayer from private to protected to get this
// to compile...
if (mAimObject || mAimLocationSet || mMoveState == ModeMove)
{
// Update the aim position if we're aiming for an object
if (mAimObject)
mAimLocation = mAimObject->getPosition();
// transform to local space
Point3F aimLocation;
invTrans.mulP(mAimLocation,&aimLocation);
F32 xDiff = aimLocation.x - location.x;
F32 yDiff = aimLocation.y - location.y;
if (!isZero(xDiff) || !isZero(yDiff)) {
// First do Yaw
// use the cur yaw between -Pi and Pi
F32 curYaw = rotation.z;
while (curYaw > M_2PI)
curYaw -= M_2PI;
while (curYaw < -M_2PI)
curYaw += M_2PI;
// find the yaw offset
F32 newYaw = mAtan( xDiff, yDiff );
F32 yawDiff = newYaw - curYaw;
// make it between 0 and 2PI
if( yawDiff < 0.0f )
yawDiff += M_2PI;
else if( yawDiff >= M_2PI )
yawDiff -= M_2PI;
// now make sure we take the short way around the circle
if( yawDiff > M_PI )
yawDiff -= M_2PI;
else if( yawDiff < -M_PI )
yawDiff += M_2PI;
movePtr->yaw = yawDiff;
// Next do pitch. This should be adjusted to run from the
// eye point to the object's center position. Though this
// works well enough for now.
F32 vertDist = aimLocation.z - location.z;
F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
F32 newPitch = mAtan( horzDist, vertDist ) - ( M_PI / 2.0f );
// phdana testing ->
movePtr->pitch = newPitch - rotation.x;
// <- phdan testsing
}
}
else
{
// Level out if we're not doing anything else
// Point3F headRotation = getRotation();
// movePtr->pitch = -headRotation.x;
}
// Move towards the destination
if (mMoveState == ModeMove)
{
// transform to local space
Point3F moveDestination;
invTrans.mulP(mMoveDestination,&moveDestination);
F32 xDiff = moveDestination.x - location.x;
F32 yDiff = moveDestination.y - location.y;
F32 zDiff = mMoveDestination.z - location.z;
// Check if we should mMove, or if we are 'close enough'
if ((mFabs(xDiff) < myTol) && (mFabs(yDiff) < myTol)) // && (mFabs(zDiff) < myTol ))
{
mMoveState = ModeStop;
// I COMMENTED THIS LINE COZ I DONT REALLY NEED THE MESSAGE (& I DONT UNDERSTAND IT :D )
//Con::printf("AI Steering : ------------------------------------- Cheguei no destino pela funo default");
throwCallback("onReachDestination");
}
else
{
// Build move direction in world space
//if (isZero(xDiff))
if (xDiff < 10.0) //CHANGED THE CONDITION COZ I NEED IT TO BE A LITTLE FLEXIBLE, PROBABLY 5.0 WILL BE ENOUGH
{
movePtr->y = 1; // Always go forward
}
else
{
//if (isZero(yDiff))
if (yDiff < 10.0)//CHANGED THE CONDITION COZ I NEED IT TO BE A LITTLE FLEXIBLE, PROBABLY 5.0 WILL BE ENOUGH
{
movePtr->x = (location.x > mMoveDestination.x)? -1.0 : 1.0;
}
else
{
if (mFabs(xDiff) > mFabs(yDiff))
{
F32 value = mFabs(yDiff / xDiff);
movePtr->x = (location.x > mMoveDestination.x)? -1.0 : 1.0;
movePtr->y = value;
}
else
{
F32 value = mFabs(xDiff / yDiff);
movePtr->x = (location.x > mMoveDestination.x)? -value : value;
movePtr->y = 1;
}
}
}
}
// Rotate the move into object space (this really only needs
// a 2D matrix)
Point3F newMove;
MatrixF moveMatrix;
moveMatrix.set(EulerF(0, 0, -(rotation.z + movePtr->yaw)));
moveMatrix.mulV( Point3F( movePtr->x, movePtr->y, 0 ), &newMove );
// Set movement speed. We'll slow down once we get close
// to try and stop on the spot...
if (mMoveSlowdown)
{
F32 speed = mMoveSpeed;
F32 dist = mSqrt(xDiff*xDiff + yDiff*yDiff);
F32 maxDist = 15.0; //INCREASED THE MAX DISTANCE TO START BREAKING EARLIER
if (dist < maxDist)
speed *= dist / maxDist;
movePtr->x *= speed;
movePtr->y *= speed;
}
else
{
movePtr->x *= mMoveSpeed;
movePtr->y *= mMoveSpeed;
}
// We should check to see if we are stuck...
/*xDiff=mFabs(mLastLocation.x - location.x) / mMoveSpeed;
yDiff=mFabs(mLastLocation.y - location.y) / mMoveSpeed;
if ((xDiff < 0.01) && (yDiff < 0.01)) {
throwCallback("onMoveStuck");
mMoveState = ModeStop;
}
mLastLocation=location;*/
// We should check to see if we are stuck...
if (location == mLastLocation)
{
throwCallback("onMoveStuck");
//mMoveState = ModeStop; //I DONT CANCEL THE MOVE WHEN IS STUCK BECAUSE THE CAR DATABLOCK I'M USING ACCELERATES SLOWLY SO THE LOCATION IS THE SAME ON THE 1ST TIME THE FUNCTION EXECUTES
}
}
// Replicate the trigger state into the move so that
// triggers can be controlled from scripts.
for( int i = 0; i < MaxTriggerKeys; i++ )
movePtr->trigger[i] = getImageTriggerState(i);
return true;
}thats the code again so it would look better
what I did exactly was
1- the wheels of the vehicle now return back to the forward direction just after steering, & quickly (the resource I mentioned
2- the virtual path between the current location & the move destination is now thicker so the car wont have to keep turning from left to right & vice versa to stay on track
3- I made the car to start de-acceleration earlier that before
I still have a small issue:
the car makes a odd (but kinda nice) turn to stop (a 180 turn) & I'm trying to solve that, but probably I need to increase the friction in the datablock (thinking loudly)
& I'll allow the AI driver to move backwards if the destination is in his back & is not far away (& I'll try to make it for the wheeled vehicles only)

Torque 3D Owner CodingChris