Torque3D/Engine/source/T3D/vehicles/wheeledVehicle.cpp
AzaezelX 2ae10c7ce1 add reload status to onNewDataBlock callback
and skip calling onremove/onadd if we're not reloading
2025-04-27 19:49:13 -05:00

1707 lines
57 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()
{
INIT_ASSET(Shape);
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 (!mShape)
{
errorStr = String::ToString("WheeledVehicleTire: Couldn't load shape \"%s\"", mShapeAssetId);
return false;
}
else
{
// 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 = mShape->mBounds.len_z() / 2;
}
return true;
}
void WheeledVehicleTire::initPersistFields()
{
docsURL;
INITPERSISTFIELD_SHAPEASSET(Shape, WheeledVehicleTire, "The shape to use for the wheel.");
addFieldV( "mass", TypeRangedF32, Offset(mass, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"The mass of the wheel.\nCurrently unused." );
addFieldV( "radius", TypeRangedF32, Offset(radius, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"@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." );
addFieldV( "staticFriction", TypeRangedF32, Offset(staticFriction, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"Tire friction when the wheel is not slipping (has traction)." );
addFieldV( "kineticFriction", TypeRangedF32, Offset(kineticFriction, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"Tire friction when the wheel is slipping (no traction)." );
addFieldV( "restitution", TypeRangedF32, Offset(restitution, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"Tire restitution.\nCurrently unused." );
addFieldV( "lateralForce", TypeRangedF32, Offset(lateralForce, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"@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." );
addFieldV( "lateralDamping", TypeRangedF32, Offset(lateralDamping, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"Damping force applied against lateral forces generated by the tire.\n\n"
"@see lateralForce" );
addFieldV( "lateralRelaxation", TypeRangedF32, Offset(lateralRelaxation, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"@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" );
addFieldV( "longitudinalForce", TypeRangedF32, Offset(longitudinalForce, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"@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" );
addFieldV( "longitudinalDamping", TypeRangedF32, Offset(longitudinalDamping, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"Damping force applied against longitudinal forces generated by the tire.\n\n"
"@see longitudinalForce" );
addFieldV( "longitudinalRelaxation", TypeRangedF32, Offset(longitudinalRelaxation, WheeledVehicleTire), &CommonValidators::PositiveFloat,
"@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);
PACKDATA_ASSET(Shape);
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);
UNPACKDATA_ASSET(Shape);
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()
{
docsURL;
addFieldV( "length", TypeRangedF32, Offset(length, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
"@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." );
addFieldV( "force", TypeRangedF32, Offset(force, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
"@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." );
addFieldV( "damping", TypeRangedF32, Offset(damping, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
"@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." );
addFieldV( "antiSwayForce", TypeRangedF32, Offset(antiSway, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
"@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"
);
typedef WheeledVehicleData::Sounds WheeledVehicleSoundsEnum;
DefineEnumType(WheeledVehicleSoundsEnum);
ImplementEnumType(WheeledVehicleSoundsEnum, "enum types.\n"
"@ingroup WheeledVehicleData\n\n")
{ WheeledVehicleSoundsEnum::JetSound, "JetSound", "..." },
{ WheeledVehicleSoundsEnum::EngineSound, "EngineSound", "..." },
{ WheeledVehicleSoundsEnum::SquealSound, "SquealSound", "..." },
{ WheeledVehicleSoundsEnum::WheelImpactSound, "WheelImpactSound", "..." },
EndImplementEnumType;
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++)
INIT_SOUNDASSET_ARRAY(WheeledVehicleSounds, i);
mDownForce = 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 (!isWheeledVehicleSoundsValid(i))
{
//return false; -TODO: trigger asset download
}
}
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()
{
docsURL;
Parent::initPersistFields();
addGroup("Particle Effects");
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.");
endGroup("Particle Effects");
addGroup("Sounds");
INITPERSISTFIELD_SOUNDASSET_ENUMED(WheeledVehicleSounds, WheeledVehicleSoundsEnum, MaxSounds, WheeledVehicleData, "Sounds related to wheeled vehicle.");
endGroup("Sounds");
addGroup("Steering");
addFieldV("maxWheelSpeed", TypeRangedF32, Offset(maxWheelSpeed, WheeledVehicleData), &CommonValidators::PositiveFloat,
"@brief Maximum linear velocity of each wheel.\n\n"
"This caps the maximum speed of the vehicle." );
addFieldV("engineTorque", TypeRangedF32, Offset(engineTorque, WheeledVehicleData), &CommonValidators::PositiveFloat,
"@brief Torque available from the engine at 100% throttle.\n\n"
"This controls vehicle acceleration. ie. how fast it will reach maximum speed." );
addFieldV("engineBrake", TypeRangedF32, Offset(engineBrake, WheeledVehicleData), &CommonValidators::PositiveFloat,
"@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." );
addFieldV("brakeTorque", TypeRangedF32, Offset(brakeTorque, WheeledVehicleData), &CommonValidators::PositiveFloat,
"@brief Torque applied when braking.\n\n"
"This controls how fast the vehicle will stop when the brakes are applied." );
addField("downforce", TypeF32, Offset(mDownForce, WheeledVehicleData),
"downward force based on velocity.");
endGroup("Steering");
}
//----------------------------------------------------------------------------
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++)
{
PACKDATA_SOUNDASSET_ARRAY(WheeledVehicleSounds, i);
}
stream->write(maxWheelSpeed);
stream->write(engineTorque);
stream->write(engineBrake);
stream->write(brakeTorque);
stream->write(mDownForce);
}
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++)
{
UNPACKDATA_SOUNDASSET_ARRAY(WheeledVehicleSounds, i);
}
stream->read(&maxWheelSpeed);
stream->read(&engineTorque);
stream->read(&engineBrake);
stream->read(&brakeTorque);
stream->read(&mDownForce);
}
//----------------------------------------------------------------------------
// 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()
{
docsURL;
Parent::initPersistFields();
}
//----------------------------------------------------------------------------
bool WheeledVehicle::onAdd()
{
if(!Parent::onAdd())
return false;
addToScene();
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 );
//
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->getWheeledVehicleSounds(WheeledVehicleData::EngineSound) )
mEngineSound = SFX->createSource( mDataBlock->getWheeledVehicleSoundsProfile(WheeledVehicleData::EngineSound), &getTransform() );
if ( mDataBlock->getWheeledVehicleSounds(WheeledVehicleData::SquealSound) )
mSquealSound = SFX->createSource( mDataBlock->getWheeledVehicleSoundsProfile(WheeledVehicleData::SquealSound), &getTransform() );
if ( mDataBlock->getWheeledVehicleSounds(WheeledVehicleData::JetSound) )
mJetSound = SFX->createSource( mDataBlock->getWheeledVehicleSoundsProfile(WheeledVehicleData::JetSound), &getTransform() );
}
scriptOnNewDataBlock(reload);
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);
// 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);
}
}
//----------------------------------------------------------------------------
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 here so we can stiffen the springs a bit based on
//the final amount of downforce
//get the speed
F32 downForce = mRigid.linVelocity.lenSquared() <= 1 ? 1 : mRigid.linVelocity.lenSquared();
//grab the datablock var
downForce *= mDataBlock->mDownForce;
//make it a smaller number so we can multiply gravity by it
downForce = mSqrt(downForce);
downForce *= TickSec;
//ensure that it is not smaller then one, cause mulltiplying gravity by fractions is baaaad
downForce = downForce < 1 ? 1 : downForce;
// 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);
spring += (spring * downForce);
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 * downForce);
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->mShape == NULL) ? 0:
new TSShapeInstance(wheel->tire->mShape);
}
}
}
// 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, const char* 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" )
{
WheeledVehicleTire* tireObj = NULL;
if (wheel < 0 || wheel > object->getWheelCount())
{
Con::warnf("setWheelTire: invalid wheel index %d, vehicle has %d hubs", wheel, object->getWheelCount());
return false;
}
else if (!Sim::findObject(tire, tireObj))
{
Con::warnf("setWheelSpring: invalid spring %s", tire);
return false;
}
object->setWheelTire(wheel, tireObj);
return true;
}
DefineEngineMethod( WheeledVehicle, setWheelSpring, bool, ( S32 wheel, const char* 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" )
{
WheeledVehicleSpring* springObj = NULL;
if (wheel < 0 || wheel > object->getWheelCount())
{
Con::warnf("setWheelSpring: invalid wheel index %d, vehicle has %d hubs", wheel, object->getWheelCount());
return false;
}
else if (!Sim::findObject(spring, springObj))
{
Con::warnf("setWheelSpring: invalid spring %s", spring);
return false;
}
object->setWheelSpring(wheel, springObj);
return true;
}
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();
}