Torque3D/Engine/source/T3D/vehicles/wheeledVehicle.cpp
AzaezelX afb39d398f code review:
1) got rid of evey class having it's own gravity
2) rigidshape inheritance simplifications
3) gravitymod from physicszones taking buoyancy into account natively (we still track raw bouyancy to cancel it out for player)
4) disableMove used throughout
5) items can now also be influenced by the appliedforce from physicszones
2020-10-02 13:53:46 -05:00

1662 lines
55 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "T3D/vehicles/wheeledVehicle.h"
#include "math/mMath.h"
#include "math/mathIO.h"
#include "console/simBase.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "collision/clippedPolyList.h"
#include "collision/planeExtractor.h"
#include "core/stream/bitStream.h"
#include "core/dnet.h"
#include "T3D/gameBase/gameConnection.h"
#include "ts/tsShapeInstance.h"
#include "T3D/fx/particleEmitter.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxTrack.h"
#include "sfx/sfxSource.h"
#include "sfx/sfxTypes.h"
#include "scene/sceneManager.h"
#include "core/resourceManager.h"
#include "materials/materialDefinition.h"
#include "materials/baseMatInstance.h"
#include "lighting/lightQuery.h"
// Collision masks are used to determine what type of objects the
// wheeled vehicle will collide with.
static U32 sClientCollisionMask =
TerrainObjectType | PlayerObjectType |
StaticShapeObjectType | VehicleObjectType |
VehicleBlockerObjectType;
// Misc. sound constants
static F32 sMinSquealVolume = 0.05f;
static F32 sIdleEngineVolume = 0.2f;
//----------------------------------------------------------------------------
// Vehicle Tire Data Block
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleTire);
ConsoleDocClass( WheeledVehicleTire,
"@brief Defines the properties of a WheeledVehicle tire.\n\n"
"Tires act as springs and generate lateral and longitudinal forces to move "
"the vehicle. These distortion/spring forces are what convert wheel angular "
"velocity into forces that act on the rigid body.\n"
"@ingroup Vehicles\n"
);
WheeledVehicleTire::WheeledVehicleTire()
{
shape = 0;
shapeName = "";
staticFriction = 1;
kineticFriction = 0.5f;
restitution = 1;
radius = 0.6f;
lateralForce = 10;
lateralDamping = 1;
lateralRelaxation = 1;
longitudinalForce = 10;
longitudinalDamping = 1;
longitudinalRelaxation = 1;
mass = 1.f;
}
bool WheeledVehicleTire::preload(bool server, String &errorStr)
{
// Load up the tire shape. ShapeBase has an option to force a
// CRC check, this is left out here, but could be easily added.
if (shapeName && shapeName[0])
{
// Load up the shape resource
shape = ResourceManager::get().load(shapeName);
if (!bool(shape))
{
errorStr = String::ToString("WheeledVehicleTire: Couldn't load shape \"%s\"",shapeName);
return false;
}
// Determinw wheel radius from the shape's bounding box.
// The tire should be built with it's hub axis along the
// object's Y axis.
radius = shape->mBounds.len_z() / 2;
}
return true;
}
void WheeledVehicleTire::initPersistFields()
{
addField( "shapeFile",TypeShapeFilename,Offset(shapeName,WheeledVehicleTire),
"The path to the shape to use for the wheel." );
addField( "mass", TypeF32, Offset(mass, WheeledVehicleTire),
"The mass of the wheel.\nCurrently unused." );
addField( "radius", TypeF32, Offset(radius, WheeledVehicleTire),
"@brief The radius of the wheel.\n\n"
"The radius is determined from the bounding box of the shape provided "
"in the shapefile field, and does not need to be specified in script. "
"The tire should be built with its hub axis along the object's Y-axis." );
addField( "staticFriction", TypeF32, Offset(staticFriction, WheeledVehicleTire),
"Tire friction when the wheel is not slipping (has traction)." );
addField( "kineticFriction", TypeF32, Offset(kineticFriction, WheeledVehicleTire),
"Tire friction when the wheel is slipping (no traction)." );
addField( "restitution", TypeF32, Offset(restitution, WheeledVehicleTire),
"Tire restitution.\nCurrently unused." );
addField( "lateralForce", TypeF32, Offset(lateralForce, WheeledVehicleTire),
"@brief Tire force perpendicular to the direction of movement.\n\n"
"Lateral force can in simple terms be considered left/right steering "
"force. WheeledVehicles are acted upon by forces generated by their tires "
"and the lateralForce measures the magnitude of the force exerted on the "
"vehicle when the tires are deformed along the x-axis. With real wheeled "
"vehicles, tires are constantly being deformed and it is the interplay of "
"deformation forces which determines how a vehicle moves. In Torque's "
"simulation of vehicle physics, tire deformation obviously can't be handled "
"with absolute realism, but the interplay of a vehicle's velocity, its "
"engine's torque and braking forces, and its wheels' friction, lateral "
"deformation, lateralDamping, lateralRelaxation, longitudinal deformation, "
"longitudinalDamping, and longitudinalRelaxation forces, along with its "
"wheels' angular velocity are combined to create a robust real-time "
"physical simulation.\n\n"
"For this field, the larger the value supplied for the lateralForce, the "
"larger the effect steering maneuvers can have. In Torque tire forces are "
"applied at a vehicle's wheel hubs." );
addField( "lateralDamping", TypeF32, Offset(lateralDamping, WheeledVehicleTire),
"Damping force applied against lateral forces generated by the tire.\n\n"
"@see lateralForce" );
addField( "lateralRelaxation", TypeF32, Offset(lateralRelaxation, WheeledVehicleTire),
"@brief Relaxing force applied against lateral forces generated by the tire.\n\n"
"The lateralRelaxation force measures how strongly the tire effectively "
"un-deforms.\n\n@see lateralForce" );
addField( "longitudinalForce", TypeF32, Offset(longitudinalForce, WheeledVehicleTire),
"@brief Tire force in the direction of movement.\n\n"
"Longitudinal force can in simple terms be considered forward/backward "
"movement force. WheeledVehicles are acted upon by forces generated by "
"their tires and the longitudinalForce measures the magnitude of the "
"force exerted on the vehicle when the tires are deformed along the y-axis.\n\n"
"For this field, the larger the value, the larger the effect "
"acceleration/deceleration inputs have.\n\n"
"@see lateralForce" );
addField( "longitudinalDamping", TypeF32, Offset(longitudinalDamping, WheeledVehicleTire),
"Damping force applied against longitudinal forces generated by the tire.\n\n"
"@see longitudinalForce" );
addField( "longitudinalRelaxation", TypeF32, Offset(longitudinalRelaxation, WheeledVehicleTire),
"@brief Relaxing force applied against longitudinal forces generated by the tire.\n\n"
"The longitudinalRelaxation force measures how strongly the tire effectively "
"un-deforms.\n\n"
"@see longitudinalForce" );
Parent::initPersistFields();
}
void WheeledVehicleTire::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeString(shapeName);
stream->write(mass);
stream->write(staticFriction);
stream->write(kineticFriction);
stream->write(restitution);
stream->write(radius);
stream->write(lateralForce);
stream->write(lateralDamping);
stream->write(lateralRelaxation);
stream->write(longitudinalForce);
stream->write(longitudinalDamping);
stream->write(longitudinalRelaxation);
}
void WheeledVehicleTire::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
shapeName = stream->readSTString();
stream->read(&mass);
stream->read(&staticFriction);
stream->read(&kineticFriction);
stream->read(&restitution);
stream->read(&radius);
stream->read(&lateralForce);
stream->read(&lateralDamping);
stream->read(&lateralRelaxation);
stream->read(&longitudinalForce);
stream->read(&longitudinalDamping);
stream->read(&longitudinalRelaxation);
}
//----------------------------------------------------------------------------
// Vehicle Spring Data Block
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleSpring);
ConsoleDocClass( WheeledVehicleSpring,
"@brief Defines the properties of a WheeledVehicle spring.\n\n"
"@ingroup Vehicles\n"
);
WheeledVehicleSpring::WheeledVehicleSpring()
{
length = 1;
force = 10;
damping = 1;
antiSway = 1;
}
void WheeledVehicleSpring::initPersistFields()
{
addField( "length", TypeF32, Offset(length, WheeledVehicleSpring),
"@brief Maximum spring length. ie. how far the wheel can extend from the "
"root hub position.\n\n"
"This should be set to the vertical (Z) distance the hub travels in the "
"associated spring animation." );
addField( "force", TypeF32, Offset(force, WheeledVehicleSpring),
"@brief Maximum spring force (when compressed to minimum length, 0).\n\n"
"Increasing this will make the vehicle suspension ride higher (for a given "
"vehicle mass), and also make the vehicle more bouncy when landing jumps." );
addField( "damping", TypeF32, Offset(damping, WheeledVehicleSpring),
"@brief Force applied to slow changes to the extension of this spring.\n\n"
"Increasing this makes the suspension stiffer which can help stabilise "
"bouncy vehicles." );
addField( "antiSwayForce", TypeF32, Offset(antiSway, WheeledVehicleSpring),
"@brief Force applied to equalize extension of the spring on the opposite "
"wheel.\n\n"
"This force helps to keep the suspension balanced when opposite wheels "
"are at different heights." );
Parent::initPersistFields();
}
void WheeledVehicleSpring::packData(BitStream* stream)
{
Parent::packData(stream);
stream->write(length);
stream->write(force);
stream->write(damping);
stream->write(antiSway);
}
void WheeledVehicleSpring::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
stream->read(&length);
stream->read(&force);
stream->read(&damping);
stream->read(&antiSway);
}
//----------------------------------------------------------------------------
// Wheeled Vehicle Data Block
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleData);
ConsoleDocClass( WheeledVehicleData,
"@brief Defines the properties of a WheeledVehicle.\n\n"
"@ingroup Vehicles\n"
);
WheeledVehicleData::WheeledVehicleData()
{
tireEmitter = 0;
maxWheelSpeed = 40;
engineTorque = 1;
engineBrake = 1;
brakeTorque = 1;
brakeLightSequence = -1;
steeringSequence = -1;
wheelCount = 0;
dMemset(&wheel, 0, sizeof(wheel));
for (S32 i = 0; i < MaxSounds; i++)
sound[i] = 0;
}
//----------------------------------------------------------------------------
/** Load the vehicle shape
Loads and extracts information from the vehicle shape.
Wheel Sequences
spring# Wheel spring motion: time 0 = wheel fully extended,
the hub must be displaced, but not directly animated
as it will be rotated in code.
Other Sequences
steering Wheel steering: time 0 = full right, 0.5 = center
brakeLight Brake light, time 0 = off, 1 = braking
Wheel Nodes
hub# Wheel hub
The steering and animation sequences are optional.
*/
bool WheeledVehicleData::preload(bool server, String &errorStr)
{
if (!Parent::preload(server, errorStr))
return false;
// A temporary shape instance is created so that we can
// animate the shape and extract wheel information.
TSShapeInstance* si = new TSShapeInstance(mShape, false);
// Resolve objects transmitted from server
if (!server) {
for (S32 i = 0; i < MaxSounds; i++)
{
if (!sfxResolve(&sound[i], errorStr))
{
delete si;
return false;
}
}
if (tireEmitter)
Sim::findObject(SimObjectId((uintptr_t)tireEmitter),tireEmitter);
}
// Extract wheel information from the shape
TSThread* thread = si->addThread();
Wheel* wp = wheel;
char buff[10];
for (S32 i = 0; i < MaxWheels; i++) {
// The wheel must have a hub node to operate at all.
dSprintf(buff,sizeof(buff),"hub%d",i);
wp->springNode = mShape->findNode(buff);
if (wp->springNode != -1) {
// Check for spring animation.. If there is none we just grab
// the current position of the hub. Otherwise we'll animate
// and get the position at time 0.
dSprintf(buff,sizeof(buff),"spring%d",i);
wp->springSequence = mShape->findSequence(buff);
if (wp->springSequence == -1)
si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos);
else {
si->setSequence(thread,wp->springSequence,0);
si->animate();
si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos);
// Determin the length of the animation so we can scale it
// according the actual wheel position.
Point3F downPos;
si->setSequence(thread,wp->springSequence,1);
si->animate();
si->mNodeTransforms[wp->springNode].getColumn(3, &downPos);
wp->springLength = wp->pos.z - downPos.z;
if (!wp->springLength)
wp->springSequence = -1;
}
// Match wheels that are mirrored along the Y axis.
mirrorWheel(wp);
wp++;
}
}
wheelCount = wp - wheel;
// Check for steering. Should think about normalizing the
// steering animation the way the suspension is, but I don't
// think it's as critical.
steeringSequence = mShape->findSequence("steering");
// Brakes
brakeLightSequence = mShape->findSequence("brakelight");
// Extract collision planes from shape collision detail level
if (collisionDetails[0] != -1) {
MatrixF imat(1);
SphereF sphere;
sphere.center = mShape->center;
sphere.radius = mShape->mRadius;
PlaneExtractorPolyList polyList;
polyList.mPlaneList = &rigidBody.mPlaneList;
polyList.setTransform(&imat, Point3F(1,1,1));
si->buildPolyList(&polyList,collisionDetails[0]);
}
delete si;
return true;
}
//----------------------------------------------------------------------------
/** Find a matching lateral wheel
Looks for a matching wheeling mirrored along the Y axis, within some
tolerance (current 0.5m), if one is found, the two wheels are lined up.
*/
bool WheeledVehicleData::mirrorWheel(Wheel* we)
{
we->opposite = -1;
for (Wheel* wp = wheel; wp != we; wp++)
if (mFabs(wp->pos.y - we->pos.y) < 0.5)
{
we->pos.x = -wp->pos.x;
we->pos.y = wp->pos.y;
we->pos.z = wp->pos.z;
we->opposite = wp - wheel;
wp->opposite = we - wheel;
return true;
}
return false;
}
//----------------------------------------------------------------------------
void WheeledVehicleData::initPersistFields()
{
addField( "jetSound", TYPEID< SFXTrack >(), Offset(sound[JetSound], WheeledVehicleData),
"Looping sound played when the vehicle is jetting." );
addField( "engineSound", TYPEID< SFXTrack >(), Offset(sound[EngineSound], WheeledVehicleData),
"@brief Looping engine sound.\n\n"
"The pitch is dynamically adjusted based on the current engine RPM" );
addField("squealSound", TYPEID< SFXTrack >(), Offset(sound[SquealSound], WheeledVehicleData),
"@brief Looping sound played while any of the wheels is slipping.\n\n"
"The volume is dynamically adjusted based on how much the wheels are slipping." );
addField("WheelImpactSound", TYPEID< SFXTrack >(), Offset(sound[WheelImpactSound], WheeledVehicleData),
"Sound played when the wheels impact the ground.\nCurrently unused." );
addField("tireEmitter",TYPEID< ParticleEmitterData >(), Offset(tireEmitter, WheeledVehicleData),
"ParticleEmitterData datablock used to generate particles from each wheel "
"when the vehicle is moving and the wheel is in contact with the ground.");
addField("maxWheelSpeed", TypeF32, Offset(maxWheelSpeed, WheeledVehicleData),
"@brief Maximum linear velocity of each wheel.\n\n"
"This caps the maximum speed of the vehicle." );
addField("engineTorque", TypeF32, Offset(engineTorque, WheeledVehicleData),
"@brief Torque available from the engine at 100% throttle.\n\n"
"This controls vehicle acceleration. ie. how fast it will reach maximum speed." );
addField("engineBrake", TypeF32, Offset(engineBrake, WheeledVehicleData),
"@brief Braking torque applied by the engine when the throttle and brake "
"are both 0.\n\n"
"This controls how quickly the vehicle will coast to a stop." );
addField("brakeTorque", TypeF32, Offset(brakeTorque, WheeledVehicleData),
"@brief Torque applied when braking.\n\n"
"This controls how fast the vehicle will stop when the brakes are applied." );
Parent::initPersistFields();
}
//----------------------------------------------------------------------------
void WheeledVehicleData::packData(BitStream* stream)
{
Parent::packData(stream);
if (stream->writeFlag(tireEmitter))
stream->writeRangedU32(mPacked ? SimObjectId((uintptr_t)tireEmitter):
tireEmitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
for (S32 i = 0; i < MaxSounds; i++)
sfxWrite( stream, sound[ i ] );
stream->write(maxWheelSpeed);
stream->write(engineTorque);
stream->write(engineBrake);
stream->write(brakeTorque);
}
void WheeledVehicleData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
tireEmitter = stream->readFlag()?
(ParticleEmitterData*)(uintptr_t)stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast): 0;
for (S32 i = 0; i < MaxSounds; i++)
sfxRead( stream, &sound[ i ] );
stream->read(&maxWheelSpeed);
stream->read(&engineTorque);
stream->read(&engineBrake);
stream->read(&brakeTorque);
}
//----------------------------------------------------------------------------
// Wheeled Vehicle Class
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(WheeledVehicle);
ConsoleDocClass( WheeledVehicle,
"@brief A wheeled vehicle.\n"
"@ingroup Vehicles\n"
);
WheeledVehicle::WheeledVehicle()
{
mDataBlock = 0;
mBraking = false;
mJetSound = NULL;
mEngineSound = NULL;
mSquealSound = NULL;
mTailLightThread = 0;
mSteeringThread = 0;
for (S32 i = 0; i < WheeledVehicleData::MaxWheels; i++) {
mWheel[i].springThread = 0;
mWheel[i].Dy = mWheel[i].Dx = 0;
mWheel[i].tire = 0;
mWheel[i].spring = 0;
mWheel[i].shapeInstance = 0;
mWheel[i].steering = 0;
mWheel[i].powered = true;
mWheel[i].slipping = false;
}
}
WheeledVehicle::~WheeledVehicle()
{
}
void WheeledVehicle::initPersistFields()
{
Parent::initPersistFields();
}
//----------------------------------------------------------------------------
bool WheeledVehicle::onAdd()
{
if(!Parent::onAdd())
return false;
addToScene();
if (isServerObject())
scriptOnAdd();
return true;
}
void WheeledVehicle::onRemove()
{
// Delete the wheel resources
if (mDataBlock != NULL) {
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++) {
if (!wheel->emitter.isNull())
wheel->emitter->deleteWhenEmpty();
delete wheel->shapeInstance;
}
}
// Stop the sounds
SFX_DELETE( mJetSound );
SFX_DELETE( mEngineSound );
SFX_DELETE( mSquealSound );
//
scriptOnRemove();
removeFromScene();
Parent::onRemove();
}
//----------------------------------------------------------------------------
bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr, bool reload)
{
// Delete any existing wheel resources if we're switching
// datablocks.
if (mDataBlock)
{
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (!wheel->emitter.isNull())
{
wheel->emitter->deleteWhenEmpty();
wheel->emitter = 0;
}
delete wheel->shapeInstance;
wheel->shapeInstance = 0;
}
}
// Load up the new datablock
mDataBlock = dynamic_cast<WheeledVehicleData*>(dptr);
if (!mDataBlock || !Parent::onNewDataBlock(dptr,reload))
return false;
// Set inertial tensor, default for the vehicle is sphere
if (mDataBlock->massBox.x > 0 && mDataBlock->massBox.y > 0 && mDataBlock->massBox.z > 0)
mRigid.setObjectInertia(mDataBlock->massBox);
else
mRigid.setObjectInertia(mObjBox.maxExtents - mObjBox.minExtents);
// Initialize the wheels...
for (S32 i = 0; i < mDataBlock->wheelCount; i++)
{
Wheel* wheel = &mWheel[i];
wheel->data = &mDataBlock->wheel[i];
wheel->tire = 0;
wheel->spring = 0;
wheel->surface.contact = false;
wheel->surface.object = NULL;
wheel->avel = 0;
wheel->apos = 0;
wheel->extension = 1;
wheel->slip = 0;
wheel->springThread = 0;
wheel->emitter = 0;
// Steering on the front tires by default
if (wheel->data->pos.y > 0)
wheel->steering = 1;
// Build wheel animation threads
if (wheel->data->springSequence != -1) {
wheel->springThread = mShapeInstance->addThread();
mShapeInstance->setSequence(wheel->springThread,wheel->data->springSequence,0);
}
// Each wheel get's it's own particle emitter
if( mDataBlock->tireEmitter && isGhost() )
{
wheel->emitter = new ParticleEmitter;
wheel->emitter->onNewDataBlock( mDataBlock->tireEmitter, false );
wheel->emitter->registerObject();
}
}
// Steering sequence
if (mDataBlock->steeringSequence != -1) {
mSteeringThread = mShapeInstance->addThread();
mShapeInstance->setSequence(mSteeringThread,mDataBlock->steeringSequence,0);
}
else
mSteeringThread = 0;
// Brake light sequence
if (mDataBlock->brakeLightSequence != -1) {
mTailLightThread = mShapeInstance->addThread();
mShapeInstance->setSequence(mTailLightThread,mDataBlock->brakeLightSequence,0);
}
else
mTailLightThread = 0;
if (isGhost())
{
// Create the sounds ahead of time. This reduces runtime
// costs and makes the system easier to understand.
SFX_DELETE( mEngineSound );
SFX_DELETE( mSquealSound );
SFX_DELETE( mJetSound );
if ( mDataBlock->sound[WheeledVehicleData::EngineSound] )
mEngineSound = SFX->createSource( mDataBlock->sound[WheeledVehicleData::EngineSound], &getTransform() );
if ( mDataBlock->sound[WheeledVehicleData::SquealSound] )
mSquealSound = SFX->createSource( mDataBlock->sound[WheeledVehicleData::SquealSound], &getTransform() );
if ( mDataBlock->sound[WheeledVehicleData::JetSound] )
mJetSound = SFX->createSource( mDataBlock->sound[WheeledVehicleData::JetSound], &getTransform() );
}
scriptOnNewDataBlock();
return true;
}
//----------------------------------------------------------------------------
S32 WheeledVehicle::getWheelCount()
{
// Return # of hubs defined on the car body
return mDataBlock? mDataBlock->wheelCount: 0;
}
void WheeledVehicle::setWheelSteering(S32 wheel,F32 steering)
{
AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
mWheel[wheel].steering = mClampF(steering,-1,1);
setMaskBits(WheelMask);
}
void WheeledVehicle::setWheelPowered(S32 wheel,bool powered)
{
AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
mWheel[wheel].powered = powered;
setMaskBits(WheelMask);
}
void WheeledVehicle::setWheelTire(S32 wheel,WheeledVehicleTire* tire)
{
AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
mWheel[wheel].tire = tire;
setMaskBits(WheelMask);
}
void WheeledVehicle::setWheelSpring(S32 wheel,WheeledVehicleSpring* spring)
{
AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
mWheel[wheel].spring = spring;
setMaskBits(WheelMask);
}
void WheeledVehicle::getWheelInstAndTransform( U32 index, TSShapeInstance** inst, MatrixF* xfrm ) const
{
AssertFatal( index < WheeledVehicleData::MaxWheels,
"WheeledVehicle::getWheelInstAndTransform() - Bad wheel index!" );
const Wheel* wheel = &mWheel[index];
*inst = wheel->shapeInstance;
if ( !xfrm || !wheel->shapeInstance )
return;
MatrixF world = getRenderTransform();
world.scale( mObjScale );
// Steering & spring extension
MatrixF hub(EulerF(0,0,mSteering.x * wheel->steering));
Point3F pos = wheel->data->pos;
pos.z -= wheel->spring->length * wheel->extension;
hub.setColumn(3,pos);
world.mul(hub);
// Wheel rotation
MatrixF rot(EulerF(wheel->apos * M_2PI,0,0));
world.mul(rot);
// Rotation the tire to face the right direction
// (could pre-calculate this)
MatrixF wrot(EulerF(0,0,(wheel->data->pos.x > 0)? M_PI/2: -M_PI/2));
world.mul(wrot);
*xfrm = world;
}
//----------------------------------------------------------------------------
void WheeledVehicle::processTick(const Move* move)
{
Parent::processTick(move);
}
void WheeledVehicle::updateMove(const Move* move)
{
Parent::updateMove(move);
// Brake on trigger
mBraking = move->trigger[2];
// Set the tail brake light thread direction based on the brake state.
if (mTailLightThread)
mShapeInstance->setTimeScale(mTailLightThread, mBraking? 1.0f : -1.0f);
}
//----------------------------------------------------------------------------
void WheeledVehicle::advanceTime(F32 dt)
{
PROFILE_SCOPE( WheeledVehicle_AdvanceTime );
Parent::advanceTime(dt);
// Stick the wheels to the ground. This is purely so they look
// good while the vehicle is being interpolated.
extendWheels();
// Update wheel angular position and slip, this is a client visual
// feature only, it has no affect on the physics.
F32 slipTotal = 0;
F32 torqueTotal = 0;
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
if (wheel->tire && wheel->spring) {
// Update angular position
wheel->apos += (wheel->avel * dt) / M_2PI;
wheel->apos -= mFloor(wheel->apos);
if (wheel->apos < 0)
wheel->apos = 1 - wheel->apos;
// Keep track of largest slip
slipTotal += wheel->slip;
torqueTotal += wheel->torqueScale;
}
// Update the sounds based on wheel slip and torque output
updateSquealSound(slipTotal / mDataBlock->wheelCount);
updateEngineSound(sIdleEngineVolume + (1 - sIdleEngineVolume) *
(1 - (torqueTotal / mDataBlock->wheelCount)));
updateJetSound();
updateWheelThreads();
updateWheelParticles(dt);
// Update the steering animation: sequence time 0 is full right,
// and time 0.5 is straight ahead.
if (mSteeringThread) {
F32 t = (mSteering.x * mFabs(mSteering.x)) / mDataBlock->maxSteeringAngle;
mShapeInstance->setPos(mSteeringThread,0.5 - t * 0.5);
}
// Animate the tail light. The direction of the thread is
// set based on vehicle braking.
if (mTailLightThread)
mShapeInstance->advanceTime(dt,mTailLightThread);
}
//----------------------------------------------------------------------------
/** Update the rigid body forces on the vehicle
This method calculates the forces acting on the body, including gravity,
suspension & tire forces.
*/
void WheeledVehicle::updateForces(F32 dt)
{
PROFILE_SCOPE( WheeledVehicle_UpdateForces );
extendWheels();
if (mDisableMove) return;
F32 aMomentum = mMass / mDataBlock->wheelCount;
// Get the current matrix and extact vectors
MatrixF currMatrix;
mRigid.getTransform(&currMatrix);
Point3F bx,by,bz;
currMatrix.getColumn(0,&bx);
currMatrix.getColumn(1,&by);
currMatrix.getColumn(2,&bz);
// Steering angles from current steering wheel position
F32 quadraticSteering = -(mSteering.x * mFabs(mSteering.x));
F32 cosSteering,sinSteering;
mSinCos(quadraticSteering, sinSteering, cosSteering);
// Calculate Engine and brake torque values used later by in
// wheel calculations.
F32 engineTorque,brakeVel;
if (mBraking)
{
brakeVel = (mDataBlock->brakeTorque / aMomentum) * dt;
engineTorque = 0;
}
else
{
if (mThrottle)
{
engineTorque = mDataBlock->engineTorque * mThrottle;
brakeVel = 0;
// Double the engineTorque to help out the jets
if (mThrottle > 0 && mJetting)
engineTorque *= 2;
}
else
{
// Engine brake.
brakeVel = (mDataBlock->engineBrake / aMomentum) * dt;
engineTorque = 0;
}
}
// Integrate forces, we'll do this ourselves here instead of
// relying on the rigid class which does it during movement.
Wheel* wend = &mWheel[mDataBlock->wheelCount];
mRigid.clearForces();
// Calculate vertical load for friction. Divide up the spring
// forces across all the wheels that are in contact with
// the ground.
U32 contactCount = 0;
F32 verticalLoad = 0;
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (wheel->tire && wheel->spring && wheel->surface.contact)
{
verticalLoad += wheel->spring->force * (1 - wheel->extension);
contactCount++;
}
}
if (contactCount)
verticalLoad /= contactCount;
// Sum up spring and wheel torque forces
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (!wheel->tire || !wheel->spring)
continue;
F32 Fy = 0;
if (wheel->surface.contact)
{
// First, let's compute the wheel's position, and worldspace velocity
Point3F pos, r, localVel;
currMatrix.mulP(wheel->data->pos, &pos);
mRigid.getOriginVector(pos,&r);
mRigid.getVelocity(r, &localVel);
// Spring force & damping
F32 spring = wheel->spring->force * (1 - wheel->extension);
if (wheel->extension == 0) //spring fully compressed
{
// Apply impulses to the rigid body to keep it from
// penetrating the surface.
F32 n = -mDot(localVel,Point3F(0,0,1));
if (n >= 0)
{
// Collision impulse, straight forward force stuff.
F32 d = mRigid.getZeroImpulse(r,Point3F(0,0,1));
F32 j = n * (1 + mRigid.restitution) * d;
mRigid.force += Point3F(0,0,1) * j;
}
}
F32 damping = wheel->spring->damping * -(mDot(bz, localVel) / wheel->spring->length);
if (damping < 0)
damping = 0;
// Anti-sway force based on difference in suspension extension
F32 antiSway = 0;
if (wheel->data->opposite != -1)
{
Wheel* oppositeWheel = &mWheel[wheel->data->opposite];
if (oppositeWheel->surface.contact)
antiSway = ((oppositeWheel->extension - wheel->extension) *
wheel->spring->antiSway);
if (antiSway < 0)
antiSway = 0;
}
// Spring forces act straight up and are applied at the
// spring's root position.
Point3F t, forceVector = bz * (spring + damping + antiSway);
mCross(r, forceVector, &t);
mRigid.torque += t;
mRigid.force += forceVector;
// Tire direction vectors perpendicular to surface normal
Point3F wheelXVec = bx * cosSteering;
wheelXVec += by * sinSteering * wheel->steering;
Point3F tireX, tireY;
mCross(wheel->surface.normal, wheelXVec, &tireY);
tireY.normalize();
mCross(tireY, wheel->surface.normal, &tireX);
tireX.normalize();
// Velocity of tire at the surface contact
Point3F wheelContact, wheelVelocity;
mRigid.getOriginVector(wheel->surface.pos,&wheelContact);
mRigid.getVelocity(wheelContact, &wheelVelocity);
F32 xVelocity = mDot(tireX, wheelVelocity);
F32 yVelocity = mDot(tireY, wheelVelocity);
// Tires act as springs and generate lateral and longitudinal
// forces to move the vehicle. These distortion/spring forces
// are what convert wheel angular velocity into forces that
// act on the rigid body.
// Longitudinal tire deformation force
F32 ddy = (wheel->avel * wheel->tire->radius - yVelocity) -
wheel->tire->longitudinalRelaxation *
mFabs(wheel->avel) * wheel->Dy;
wheel->Dy += ddy * dt;
Fy = (wheel->tire->longitudinalForce * wheel->Dy +
wheel->tire->longitudinalDamping * ddy);
// Lateral tire deformation force
F32 ddx = xVelocity - wheel->tire->lateralRelaxation *
mFabs(wheel->avel) * wheel->Dx;
wheel->Dx += ddx * dt;
F32 Fx = -(wheel->tire->lateralForce * wheel->Dx +
wheel->tire->lateralDamping * ddx);
// Vertical load on the tire
verticalLoad = spring + damping + antiSway;
if (verticalLoad < 0)
verticalLoad = 0;
// Adjust tire forces based on friction
F32 surfaceFriction = 1;
F32 mu = surfaceFriction * (wheel->slipping ? wheel->tire->kineticFriction : wheel->tire->staticFriction);
F32 Fn = verticalLoad * mu; Fn *= Fn;
F32 Fw = Fx * Fx + Fy * Fy;
if (Fw > Fn)
{
F32 K = mSqrt(Fn / Fw);
Fy *= K;
Fx *= K;
wheel->Dy *= K;
wheel->Dx *= K;
wheel->slip = 1 - K;
wheel->slipping = true;
}
else
{
wheel->slipping = false;
wheel->slip = 0;
}
// Tire forces act through the tire direction vectors parallel
// to the surface and are applied at the wheel hub.
forceVector = (tireX * Fx) + (tireY * Fy);
pos -= bz * (wheel->spring->length * wheel->extension);
mRigid.getOriginVector(pos,&r);
mCross(r, forceVector, &t);
mRigid.torque += t;
mRigid.force += forceVector;
}
else
{
// Wheel not in contact with the ground
wheel->torqueScale = 0;
wheel->slip = 0;
// Relax the tire deformation
wheel->Dy += (-wheel->tire->longitudinalRelaxation *
mFabs(wheel->avel) * wheel->Dy) * dt;
wheel->Dx += (-wheel->tire->lateralRelaxation *
mFabs(wheel->avel) * wheel->Dx) * dt;
}
// Adjust the wheel's angular velocity based on engine torque
// and tire deformation forces.
if (wheel->powered)
{
F32 maxAvel = mDataBlock->maxWheelSpeed / wheel->tire->radius;
wheel->torqueScale = (mFabs(wheel->avel) > maxAvel) ? 0 :
1 - (mFabs(wheel->avel) / maxAvel);
}
else
wheel->torqueScale = 0;
wheel->avel += (((wheel->torqueScale * engineTorque) - Fy *
wheel->tire->radius) / aMomentum) * dt;
// Adjust the wheel's angular velocity based on brake torque.
// This is done after avel update to make sure we come to a
// complete stop.
if (brakeVel > mFabs(wheel->avel))
wheel->avel = 0;
else
if (wheel->avel > 0)
wheel->avel -= brakeVel;
else
wheel->avel += brakeVel;
}
// Jet Force
if (mJetting)
mRigid.force += by * mDataBlock->jetForce;
// Add in force from physical zones...
mRigid.force += mAppliedForce;
// Container drag & buoyancy
mRigid.force += Point3F(0, 0, mRigid.mass * mNetGravity);
mRigid.force -= mRigid.linVelocity * mDrag;
mRigid.torque -= mRigid.angMomentum * mDrag;
// If we've added anything other than gravity, then we're no
// longer at rest. Could test this a little more efficiently...
if (mRigid.atRest && (mRigid.force.len() || mRigid.torque.len()))
mRigid.atRest = false;
// Integrate and update velocity
mRigid.linMomentum += mRigid.force * dt;
mRigid.angMomentum += mRigid.torque * dt;
mRigid.updateVelocity();
// Since we've already done all the work, just need to clear this out.
mRigid.clearForces();
// If we're still atRest, make sure we're not accumulating anything
if (mRigid.atRest)
mRigid.setAtRest();
}
//----------------------------------------------------------------------------
/** Extend the wheels
The wheels are extended until they contact a surface. The extension
is instantaneous. The wheels are extended before force calculations and
also on during client side interpolation (so that the wheels are glued
to the ground).
*/
void WheeledVehicle::extendWheels(bool clientHack)
{
PROFILE_SCOPE( WheeledVehicle_ExtendWheels );
disableCollision();
MatrixF currMatrix;
if(clientHack)
currMatrix = getRenderTransform();
else
mRigid.getTransform(&currMatrix);
// Does a single ray cast down for now... this will have to be
// changed to something a little more complicated to avoid getting
// stuck in cracks.
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (wheel->tire && wheel->spring)
{
wheel->extension = 1;
// The ray is cast from the spring mount point to the tip of
// the tire. If there is a collision the spring extension is
// adjust to remove the tire radius.
Point3F sp,vec;
currMatrix.mulP(wheel->data->pos,&sp);
currMatrix.mulV(VectorF(0,0,-wheel->spring->length),&vec);
F32 ts = wheel->tire->radius / wheel->spring->length;
Point3F ep = sp + (vec * (1 + ts));
ts = ts / (1+ts);
RayInfo rInfo;
if (mContainer->castRay(sp, ep, sClientCollisionMask & ~PlayerObjectType, &rInfo))
{
wheel->surface.contact = true;
wheel->extension = (rInfo.t < ts)? 0: (rInfo.t - ts) / (1 - ts);
wheel->surface.normal = rInfo.normal;
wheel->surface.pos = rInfo.point;
wheel->surface.material = rInfo.material;
wheel->surface.object = rInfo.object;
}
else
{
wheel->surface.contact = false;
wheel->slipping = true;
}
}
}
enableCollision();
}
//----------------------------------------------------------------------------
/** Update wheel steering and suspension threads.
These animations are purely cosmetic and this method is only invoked
on the client.
*/
void WheeledVehicle::updateWheelThreads()
{
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (wheel->tire && wheel->spring && wheel->springThread)
{
// Scale the spring animation time to match the current
// position of the wheel. We'll also check to make sure
// the animation is long enough, if it isn't, just stick
// it at the end.
F32 pos = wheel->extension * wheel->spring->length;
if (pos > wheel->data->springLength)
pos = 1;
else
pos /= wheel->data->springLength;
mShapeInstance->setPos(wheel->springThread,pos);
}
}
}
//----------------------------------------------------------------------------
/** Update wheel particles effects
These animations are purely cosmetic and this method is only invoked
on the client. Particles are emitted as long as the moving.
*/
void WheeledVehicle::updateWheelParticles(F32 dt)
{
// OMG l33t hax
extendWheels(true);
Point3F vel = Parent::getVelocity();
F32 speed = vel.len();
// Don't bother if we're not moving.
if (speed > 1.0f)
{
Point3F axis = vel;
axis.normalize();
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
// Is this wheel in contact with the ground?
if (wheel->tire && wheel->spring && !wheel->emitter.isNull() &&
wheel->surface.contact && wheel->surface.object )
{
Material* material = ( wheel->surface.material ? dynamic_cast< Material* >( wheel->surface.material->getMaterial() ) : 0 );
if( material)//&& material->mShowDust )
{
LinearColorF colorList[ ParticleData::PDC_NUM_KEYS ];
for( U32 x = 0; x < getMin( Material::NUM_EFFECT_COLOR_STAGES, ParticleData::PDC_NUM_KEYS ); ++ x )
colorList[ x ] = material->mEffectColor[ x ];
for( U32 x = Material::NUM_EFFECT_COLOR_STAGES; x < ParticleData::PDC_NUM_KEYS; ++ x )
colorList[ x ].set( 1.0, 1.0, 1.0, 0.0 );
wheel->emitter->setColors( colorList );
// Emit the dust, the density (time) is scaled by the
// the vehicles velocity.
wheel->emitter->emitParticles( wheel->surface.pos, true,
axis, vel, (U32)(3/*dt * (speed / mDataBlock->maxWheelSpeed) * 1000 * wheel->slip*/));
}
}
}
}
}
//----------------------------------------------------------------------------
/** Update engine sound
This method is only invoked by clients.
*/
void WheeledVehicle::updateEngineSound(F32 level)
{
if ( !mEngineSound )
return;
if ( !mEngineSound->isPlaying() )
mEngineSound->play();
mEngineSound->setTransform( getTransform() );
mEngineSound->setVelocity( getVelocity() );
//mEngineSound->setVolume( level );
// Adjust pitch
F32 pitch = ((level-sIdleEngineVolume) * 1.3f);
if (pitch < 0.4f)
pitch = 0.4f;
mEngineSound->setPitch( pitch );
}
//----------------------------------------------------------------------------
/** Update wheel skid sound
This method is only invoked by clients.
*/
void WheeledVehicle::updateSquealSound(F32 level)
{
if ( !mSquealSound )
return;
if ( level < sMinSquealVolume )
{
mSquealSound->stop();
return;
}
if ( !mSquealSound->isPlaying() )
mSquealSound->play();
mSquealSound->setTransform( getTransform() );
mSquealSound->setVolume( level );
}
//----------------------------------------------------------------------------
/** Update jet sound
This method is only invoked by clients.
*/
void WheeledVehicle::updateJetSound()
{
if ( !mJetSound )
return;
if ( !mJetting )
{
mJetSound->stop();
return;
}
if ( !mJetSound->isPlaying() )
mJetSound->play();
mJetSound->setTransform( getTransform() );
}
//----------------------------------------------------------------------------
U32 WheeledVehicle::getCollisionMask()
{
return sClientCollisionMask;
}
//----------------------------------------------------------------------------
/** Build a collision polylist
The polylist is filled with polygons representing the collision volume
and the wheels.
*/
bool WheeledVehicle::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere)
{
PROFILE_SCOPE( WheeledVehicle_BuildPolyList );
// Parent will take care of body collision.
Parent::buildPolyList(context, polyList,box,sphere);
// Add wheels as boxes.
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++) {
if (wheel->tire && wheel->spring) {
Box3F wbox;
F32 radius = wheel->tire->radius;
wbox.minExtents.x = -(wbox.maxExtents.x = radius / 2);
wbox.minExtents.y = -(wbox.maxExtents.y = radius);
wbox.minExtents.z = -(wbox.maxExtents.z = radius);
MatrixF mat = mObjToWorld;
Point3F sp,vec;
mObjToWorld.mulP(wheel->data->pos,&sp);
mObjToWorld.mulV(VectorF(0,0,-wheel->spring->length),&vec);
Point3F ep = sp + (vec * wheel->extension);
mat.setColumn(3,ep);
polyList->setTransform(&mat,Point3F(1,1,1));
polyList->addBox(wbox);
}
}
return !polyList->isEmpty();
}
void WheeledVehicle::prepBatchRender(SceneRenderState* state, S32 mountedImageIndex )
{
Parent::prepBatchRender( state, mountedImageIndex );
if ( mountedImageIndex != -1 )
return;
// Set up our render state *here*,
// before the push world matrix, so
// that wheel rendering will be correct.
TSRenderState rdata;
rdata.setSceneState( state );
// We might have some forward lit materials
// so pass down a query to gather lights.
LightQuery query;
query.init( getWorldSphere() );
rdata.setLightQuery( &query );
// Shape transform
GFX->pushWorldMatrix();
MatrixF mat = getRenderTransform();
mat.scale( mObjScale );
GFX->setWorldMatrix( mat );
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (wheel->shapeInstance)
{
GFX->pushWorldMatrix();
// Steering & spring extension
MatrixF hub(EulerF(0,0,mSteering.x * wheel->steering));
Point3F pos = wheel->data->pos;
pos.z -= wheel->spring->length * wheel->extension;
hub.setColumn(3,pos);
GFX->multWorld(hub);
// Wheel rotation
MatrixF rot(EulerF(wheel->apos * M_2PI,0,0));
GFX->multWorld(rot);
// Rotation the tire to face the right direction
// (could pre-calculate this)
MatrixF wrot(EulerF(0,0,(wheel->data->pos.x > 0)? M_PI/2: -M_PI/2));
GFX->multWorld(wrot);
// Render!
wheel->shapeInstance->animate();
wheel->shapeInstance->render( rdata );
if (mCloakLevel != 0.0f)
wheel->shapeInstance->setAlphaAlways(1.0f - mCloakLevel);
else
wheel->shapeInstance->setAlphaAlways(1.0f);
GFX->popWorldMatrix();
}
}
GFX->popWorldMatrix();
}
//----------------------------------------------------------------------------
void WheeledVehicle::writePacketData(GameConnection *connection, BitStream *stream)
{
Parent::writePacketData(connection, stream);
stream->writeFlag(mBraking);
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
stream->write(wheel->avel);
stream->write(wheel->Dy);
stream->write(wheel->Dx);
stream->writeFlag(wheel->slipping);
}
}
void WheeledVehicle::readPacketData(GameConnection *connection, BitStream *stream)
{
Parent::readPacketData(connection, stream);
mBraking = stream->readFlag();
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
stream->read(&wheel->avel);
stream->read(&wheel->Dy);
stream->read(&wheel->Dx);
wheel->slipping = stream->readFlag();
}
// Rigid state is transmitted by the parent...
setPosition(mRigid.linPosition,mRigid.angPosition);
mDelta.pos = mRigid.linPosition;
mDelta.rot[1] = mRigid.angPosition;
}
//----------------------------------------------------------------------------
U32 WheeledVehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
// Update wheel datablock information
if (stream->writeFlag(mask & WheelMask))
{
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (stream->writeFlag(wheel->tire && wheel->spring))
{
stream->writeRangedU32(wheel->tire->getId(),
DataBlockObjectIdFirst,DataBlockObjectIdLast);
stream->writeRangedU32(wheel->spring->getId(),
DataBlockObjectIdFirst,DataBlockObjectIdLast);
stream->writeFlag(wheel->powered);
// Steering must be sent with full precision as it's
// used directly in state force calculations.
stream->write(wheel->steering);
}
}
}
// The rest of the data is part of the control object packet update.
// If we're controlled by this client, we don't need to send it.
if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask)))
return retMask;
stream->writeFlag(mBraking);
if (stream->writeFlag(mask & PositionMask))
{
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
stream->write(wheel->avel);
stream->write(wheel->Dy);
stream->write(wheel->Dx);
}
}
return retMask;
}
void WheeledVehicle::unpackUpdate(NetConnection *con, BitStream *stream)
{
Parent::unpackUpdate(con,stream);
// Update wheel datablock information
if (stream->readFlag())
{
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
if (stream->readFlag())
{
SimObjectId tid = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast);
SimObjectId sid = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast);
if (!Sim::findObject(tid,wheel->tire) || !Sim::findObject(sid,wheel->spring))
{
con->setLastError("Invalid packet WheeledVehicle::unpackUpdate()");
return;
}
wheel->powered = stream->readFlag();
stream->read(&wheel->steering);
// Create an instance of the tire for rendering
delete wheel->shapeInstance;
wheel->shapeInstance = (wheel->tire->shape == NULL) ? 0:
new TSShapeInstance(wheel->tire->shape);
}
}
}
// After this is data that we only need if we're not the
// controlling client.
if (stream->readFlag())
return;
mBraking = stream->readFlag();
if (stream->readFlag())
{
Wheel* wend = &mWheel[mDataBlock->wheelCount];
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
{
stream->read(&wheel->avel);
stream->read(&wheel->Dy);
stream->read(&wheel->Dx);
}
}
}
//----------------------------------------------------------------------------
// Console Methods
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
DefineEngineMethod( WheeledVehicle, setWheelSteering, bool, ( S32 wheel, F32 steering ),,
"@brief Set how much the wheel is affected by steering.\n\n"
"The steering factor controls how much the wheel is rotated by the vehicle "
"steering. For example, most cars would have their front wheels set to 1.0, "
"and their rear wheels set to 0 since only the front wheels should turn.\n\n"
"Negative values will turn the wheel in the opposite direction to the steering "
"angle.\n"
"@param wheel index of the wheel to set (hub node #)\n"
"@param steering steering factor from -1 (full inverse) to 1 (full)\n"
"@return true if successful, false if failed\n\n" )
{
if ( wheel >= 0 && wheel < object->getWheelCount() ) {
object->setWheelSteering( wheel, steering );
return true;
}
else
Con::warnf("setWheelSteering: wheel index %d out of bounds, vehicle has %d hubs",
wheel, object->getWheelCount());
return false;
}
DefineEngineMethod( WheeledVehicle, setWheelPowered, bool, ( S32 wheel, bool powered ),,
"@brief Set whether the wheel is powered (has torque applied from the engine).\n\n"
"A rear wheel drive car for example would set the front wheels to false, "
"and the rear wheels to true.\n"
"@param wheel index of the wheel to set (hub node #)\n"
"@param powered flag indicating whether to power the wheel or not\n"
"@return true if successful, false if failed\n\n" )
{
if ( wheel >= 0 && wheel < object->getWheelCount() ) {
object->setWheelPowered( wheel, powered );
return true;
}
else
Con::warnf("setWheelPowered: wheel index %d out of bounds, vehicle has %d hubs",
wheel, object->getWheelCount());
return false;
}
DefineEngineMethod( WheeledVehicle, setWheelTire, bool, ( S32 wheel, WheeledVehicleTire* tire ),,
"@brief Set the WheeledVehicleTire datablock for this wheel.\n"
"@param wheel index of the wheel to set (hub node #)\n"
"@param tire WheeledVehicleTire datablock\n"
"@return true if successful, false if failed\n\n"
"@tsexample\n"
"%obj.setWheelTire( 0, FrontTire );\n"
"@endtsexample\n" )
{
if (wheel >= 0 && wheel < object->getWheelCount()) {
object->setWheelTire(wheel,tire);
return true;
}
else {
Con::warnf("setWheelTire: invalid tire datablock or wheel index, vehicle has %d hubs",
object->getWheelCount());
return false;
}
}
DefineEngineMethod( WheeledVehicle, setWheelSpring, bool, ( S32 wheel, WheeledVehicleSpring* spring ),,
"@brief Set the WheeledVehicleSpring datablock for this wheel.\n"
"@param wheel index of the wheel to set (hub node #)\n"
"@param spring WheeledVehicleSpring datablock\n"
"@return true if successful, false if failed\n\n"
"@tsexample\n"
"%obj.setWheelSpring( 0, FrontSpring );\n"
"@endtsexample\n" )
{
if (spring && wheel >= 0 && wheel < object->getWheelCount()) {
object->setWheelSpring(wheel,spring);
return true;
}
else {
Con::warnf("setWheelSpring: invalid spring datablock or wheel index, vehicle has %d hubs",
object->getWheelCount());
return false;
}
}
DefineEngineMethod( WheeledVehicle, getWheelCount, S32, (),,
"@brief Get the number of wheels on this vehicle.\n"
"@return the number of wheels (equal to the number of hub nodes defined in the model)\n\n" )
{
return object->getWheelCount();
}