1618 lines
52 KiB
C++
Executable File
1618 lines
52 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "game/vehicles/wheeledVehicle.h"
|
|
|
|
#include "platform/platform.h"
|
|
#include "dgl/dgl.h"
|
|
#include "game/game.h"
|
|
#include "math/mMath.h"
|
|
#include "math/mathIO.h"
|
|
#include "console/simBase.h"
|
|
#include "console/console.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "collision/clippedPolyList.h"
|
|
#include "collision/planeExtractor.h"
|
|
#include "game/moveManager.h"
|
|
#include "core/bitStream.h"
|
|
#include "core/dnet.h"
|
|
#include "game/gameConnection.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "game/fx/particleEngine.h"
|
|
#include "audio/audioDataBlock.h"
|
|
#include "sceneGraph/sceneGraph.h"
|
|
#include "sim/decalManager.h"
|
|
#include "dgl/materialPropertyMap.h"
|
|
#include "terrain/terrData.h"
|
|
#include "sceneGraph/detailManager.h"
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Collision masks are used to determin what type of objects the
|
|
// wheeled vehicle will collide with.
|
|
static U32 sClientCollisionMask =
|
|
TerrainObjectType | InteriorObjectType |
|
|
PlayerObjectType | StaticShapeObjectType |
|
|
VehicleObjectType | VehicleBlockerObjectType |
|
|
StaticTSObjectType;
|
|
|
|
// Gravity constant
|
|
static F32 sWheeledVehicleGravity = -20;
|
|
|
|
// Misc. sound constants
|
|
static F32 sMinSquealVolume = 0.05;
|
|
static F32 sIdleEngineVolume = 0.2;
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Vehicle Tire Data Block
|
|
//----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleTire);
|
|
|
|
WheeledVehicleTire::WheeledVehicleTire()
|
|
{
|
|
shape = 0;
|
|
shapeName = "";
|
|
staticFriction = 1;
|
|
kineticFriction = 0.5;
|
|
restitution = 1;
|
|
radius = 0.6;
|
|
lateralForce = 10;
|
|
lateralDamping = 1;
|
|
lateralRelaxation = 1;
|
|
longitudinalForce = 10;
|
|
longitudinalDamping = 1;
|
|
longitudinalRelaxation = 1;
|
|
}
|
|
|
|
bool WheeledVehicleTire::preload(bool server, char errorBuffer[256])
|
|
{
|
|
// 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->load(shapeName);
|
|
if (!bool(shape)) {
|
|
dSprintf(errorBuffer, 256, "WheeledVehicleTire: Couldn't load shape \"%s\"",shapeName);
|
|
return false;
|
|
}
|
|
|
|
// Determin 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->bounds.len_z() / 2;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WheeledVehicleTire::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addField("shapeFile",TypeFilename,Offset(shapeName,WheeledVehicleTire));
|
|
addField("mass", TypeF32, Offset(mass, WheeledVehicleTire));
|
|
addField("radius", TypeF32, Offset(radius, WheeledVehicleTire));
|
|
addField("staticFriction", TypeF32, Offset(staticFriction, WheeledVehicleTire));
|
|
addField("kineticFriction", TypeF32, Offset(kineticFriction, WheeledVehicleTire));
|
|
addField("restitution", TypeF32, Offset(restitution, WheeledVehicleTire));
|
|
addField("lateralForce", TypeF32, Offset(lateralForce, WheeledVehicleTire));
|
|
addField("lateralDamping", TypeF32, Offset(lateralDamping, WheeledVehicleTire));
|
|
addField("lateralRelaxation", TypeF32, Offset(lateralRelaxation, WheeledVehicleTire));
|
|
addField("longitudinalForce", TypeF32, Offset(longitudinalForce, WheeledVehicleTire));
|
|
addField("longitudinalDamping", TypeF32, Offset(longitudinalDamping, WheeledVehicleTire));
|
|
addField("logitudinalRelaxation", TypeF32, Offset(longitudinalRelaxation, WheeledVehicleTire));
|
|
}
|
|
|
|
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);
|
|
|
|
WheeledVehicleSpring::WheeledVehicleSpring()
|
|
{
|
|
length = 1;
|
|
force = 10;
|
|
damping = 1;
|
|
antiSway = 1;
|
|
}
|
|
|
|
void WheeledVehicleSpring::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addField("length", TypeF32, Offset(length, WheeledVehicleSpring));
|
|
addField("force", TypeF32, Offset(force, WheeledVehicleSpring));
|
|
addField("damping", TypeF32, Offset(damping, WheeledVehicleSpring));
|
|
addField("antiSwayForce", TypeF32, Offset(antiSway, WheeledVehicleSpring));
|
|
}
|
|
|
|
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);
|
|
|
|
WheeledVehicleData::WheeledVehicleData()
|
|
{
|
|
tireEmitter = 0;
|
|
maxWheelSpeed = 40;
|
|
engineTorque = 1;
|
|
engineBrake = 1;
|
|
brakeTorque = 1;
|
|
brakeLightSequence = -1;
|
|
|
|
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
|
|
breakLight Break light, time 0 = off, 1 = breaking
|
|
|
|
Wheel Nodes
|
|
hub# Wheel hub
|
|
|
|
The steering and animation sequences are optional.
|
|
*/
|
|
bool WheeledVehicleData::preload(bool server, char errorBuffer[256])
|
|
{
|
|
if (!Parent::preload(server, errorBuffer))
|
|
return false;
|
|
|
|
// A temporary shape instance is created so that we can
|
|
// animate the shape and extract wheel information.
|
|
TSShapeInstance* si = new TSShapeInstance(shape, false);
|
|
|
|
// Resolve objects transmitted from server
|
|
if (!server) {
|
|
for (S32 i = 0; i < MaxSounds; i++)
|
|
if (sound[i])
|
|
Sim::findObject(SimObjectId(sound[i]),sound[i]);
|
|
|
|
if (tireEmitter)
|
|
Sim::findObject(SimObjectId(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 = shape->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 = shape->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 = shape->findSequence("steering");
|
|
|
|
// Brakes
|
|
brakeLightSequence = shape->findSequence("brakelight");
|
|
|
|
// Extract collision planes from shape collision detail level
|
|
if (collisionDetails[0] != -1) {
|
|
MatrixF imat(1);
|
|
SphereF sphere;
|
|
sphere.center = shape->center;
|
|
sphere.radius = shape->radius;
|
|
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()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addField("jetSound", TypeAudioProfilePtr, Offset(sound[JetSound], WheeledVehicleData));
|
|
addField("engineSound", TypeAudioProfilePtr, Offset(sound[EngineSound], WheeledVehicleData));
|
|
addField("squealSound", TypeAudioProfilePtr, Offset(sound[SquealSound], WheeledVehicleData));
|
|
addField("WheelImpactSound", TypeAudioProfilePtr, Offset(sound[WheelImpactSound], WheeledVehicleData));
|
|
|
|
addField("tireEmitter",TypeParticleEmitterDataPtr, Offset(tireEmitter, WheeledVehicleData));
|
|
addField("maxWheelSpeed", TypeF32, Offset(maxWheelSpeed, WheeledVehicleData));
|
|
addField("engineTorque", TypeF32, Offset(engineTorque, WheeledVehicleData));
|
|
addField("engineBrake", TypeF32, Offset(engineBrake, WheeledVehicleData));
|
|
addField("brakeTorque", TypeF32, Offset(brakeTorque, WheeledVehicleData));
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void WheeledVehicleData::packData(BitStream* stream)
|
|
{
|
|
Parent::packData(stream);
|
|
|
|
if (stream->writeFlag(tireEmitter))
|
|
stream->writeRangedU32(packed? SimObjectId(tireEmitter):
|
|
tireEmitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
|
|
for (S32 i = 0; i < MaxSounds; i++)
|
|
if (stream->writeFlag(sound[i]))
|
|
stream->writeRangedU32(packed? SimObjectId(sound[i]):
|
|
sound[i]->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
|
|
stream->write(maxWheelSpeed);
|
|
stream->write(engineTorque);
|
|
stream->write(engineBrake);
|
|
stream->write(brakeTorque);
|
|
}
|
|
|
|
void WheeledVehicleData::unpackData(BitStream* stream)
|
|
{
|
|
Parent::unpackData(stream);
|
|
|
|
tireEmitter = stream->readFlag()?
|
|
(ParticleEmitterData*) stream->readRangedU32(DataBlockObjectIdFirst,
|
|
DataBlockObjectIdLast): 0;
|
|
|
|
for (S32 i = 0; i < MaxSounds; i++)
|
|
sound[i] = stream->readFlag()?
|
|
(AudioProfile*) stream->readRangedU32(DataBlockObjectIdFirst,
|
|
DataBlockObjectIdLast): 0;
|
|
|
|
stream->read(&maxWheelSpeed);
|
|
stream->read(&engineTorque);
|
|
stream->read(&engineBrake);
|
|
stream->read(&brakeTorque);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Wheeled Vehicle Class
|
|
//----------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(WheeledVehicle);
|
|
|
|
WheeledVehicle::WheeledVehicle()
|
|
{
|
|
mDataBlock = 0;
|
|
mBraking = false;
|
|
mJetSound = 0;
|
|
mEngineSound = 0;
|
|
mSquealSound = 0;
|
|
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
|
|
if (mJetSound)
|
|
alxStop(mJetSound);
|
|
if (mEngineSound)
|
|
alxStop(mEngineSound);
|
|
if (mSquealSound)
|
|
alxStop(mSquealSound);
|
|
|
|
//
|
|
scriptOnRemove();
|
|
removeFromScene();
|
|
Parent::onRemove();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr)
|
|
{
|
|
// 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))
|
|
return false;
|
|
|
|
F32 frontStatic = 0;
|
|
F32 backStatic = 0;
|
|
F32 fCount = 0;
|
|
F32 bCount = 0;
|
|
|
|
// 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.max - mObjBox.min);
|
|
|
|
// 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) {
|
|
wheel->emitter = new ParticleEmitter;
|
|
wheel->emitter->onNewDataBlock(mDataBlock->tireEmitter);
|
|
wheel->emitter->registerObject();
|
|
}
|
|
}
|
|
|
|
// Steering sequence
|
|
if (mDataBlock->steeringSequence != -1) {
|
|
mSteeringThread = mShapeInstance->addThread();
|
|
mShapeInstance->setSequence(mSteeringThread,mDataBlock->steeringSequence,0);
|
|
}
|
|
else
|
|
mSteeringThread = 0;
|
|
|
|
// Break light sequence
|
|
if (mDataBlock->brakeLightSequence != -1) {
|
|
mTailLightThread = mShapeInstance->addThread();
|
|
mShapeInstance->setSequence(mTailLightThread,mDataBlock->brakeLightSequence,0);
|
|
}
|
|
else
|
|
mTailLightThread = 0;
|
|
|
|
// Stop any existing sounds in case where switching datablocks
|
|
if (mJetSound) {
|
|
alxStop(mJetSound);
|
|
mJetSound = 0;
|
|
}
|
|
if (mEngineSound) {
|
|
alxStop(mEngineSound);
|
|
mEngineSound = 0;
|
|
}
|
|
if (mSquealSound) {
|
|
alxStop(mSquealSound);
|
|
mSquealSound = 0;
|
|
}
|
|
if (isGhost()) {
|
|
// Start the engine
|
|
if (mDataBlock->sound[WheeledVehicleData::EngineSound])
|
|
mEngineSound = alxPlay(mDataBlock->sound[WheeledVehicleData::EngineSound], &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::processTick(const Move* move)
|
|
{
|
|
Parent::processTick(move);
|
|
}
|
|
|
|
void WheeledVehicle::updateMove(const Move* move)
|
|
{
|
|
Parent::updateMove(move);
|
|
|
|
// Break 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: -1);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void WheeledVehicle::advanceTime(F32 dt)
|
|
{
|
|
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)
|
|
{
|
|
extendWheels();
|
|
|
|
F32 oneOverSprungMass = 1 / (mMass * 0.8);
|
|
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 break.
|
|
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.force.set(0, 0, 0);
|
|
mRigid.torque.set(0, 0, 0);
|
|
|
|
// 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 break 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;
|
|
|
|
// Container drag & buoyancy
|
|
mRigid.force += Point3F(0, 0, -mBuoyancy * sWheeledVehicleGravity * mRigid.mass);
|
|
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;
|
|
|
|
// Gravity
|
|
mRigid.force += Point3F(0, 0, sWheeledVehicleGravity * mRigid.mass);
|
|
|
|
// 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.force.set(0, 0, 0);
|
|
mRigid.torque.set(0, 0, 0);
|
|
|
|
// 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)
|
|
{
|
|
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();
|
|
if (speed > 1.0f)
|
|
{
|
|
MaterialPropertyMap* matMap = static_cast<MaterialPropertyMap*>(Sim::findObject("MaterialPropertyMap"));
|
|
Point3F axis = vel;
|
|
axis.normalize();
|
|
|
|
// Only emit dust on the terrain
|
|
Wheel* wend = &mWheel[mDataBlock->wheelCount];
|
|
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
|
|
{
|
|
if (wheel->tire && wheel->spring && !wheel->emitter.isNull() &&
|
|
wheel->surface.contact && wheel->surface.object &&
|
|
wheel->surface.object->getTypeMask() & TerrainObjectType)
|
|
{
|
|
TerrainBlock* tBlock = static_cast<TerrainBlock*>(wheel->surface.object);
|
|
S32 mapIndex = tBlock->mMPMIndex[0];
|
|
|
|
// Override the dust color with the material property
|
|
const MaterialPropertyMap::MapEntry* pEntry;
|
|
if (matMap && mapIndex != -1 &&
|
|
(pEntry = matMap->getMapEntryFromIndex(mapIndex)) != 0)
|
|
{
|
|
ColorF colorList[ParticleEngine::PC_COLOR_KEYS];
|
|
|
|
for (S32 x = 0; x < 2; ++x)
|
|
colorList[x].set(pEntry->puffColor[x].red,
|
|
pEntry->puffColor[x].green,
|
|
pEntry->puffColor[x].blue,
|
|
pEntry->puffColor[x].alpha);
|
|
|
|
for(S32 x = 2; x < ParticleEngine::PC_COLOR_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)(dt * (speed / mDataBlock->maxWheelSpeed) * 1000 * wheel->slip));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
/** Update engine sound
|
|
This method is only invoked by clients.
|
|
*/
|
|
void WheeledVehicle::updateEngineSound(F32 level)
|
|
{
|
|
if (mEngineSound)
|
|
{
|
|
alxSourceMatrixF(mEngineSound, &getTransform());
|
|
alxSourcef(mEngineSound, AL_GAIN_LINEAR, level);
|
|
|
|
// Creative Labs win32 OpenAL workaround
|
|
F32 pitch = ((level-sIdleEngineVolume) * 1.3);
|
|
if (pitch < 0.4)
|
|
pitch = 0.4;
|
|
// End workaround
|
|
|
|
alxSourcef(mEngineSound, AL_PITCH, pitch);
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
/** Update wheel skid sound
|
|
This method is only invoked by clients.
|
|
*/
|
|
void WheeledVehicle::updateSquealSound(F32 level)
|
|
{
|
|
if (!mDataBlock->sound[WheeledVehicleData::SquealSound])
|
|
return;
|
|
|
|
// Allocate/Deallocate voice on demand.
|
|
if (level < sMinSquealVolume)
|
|
{
|
|
if (mSquealSound)
|
|
{
|
|
alxStop(mSquealSound);
|
|
mSquealSound = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!mSquealSound)
|
|
mSquealSound = alxPlay(mDataBlock->sound[WheeledVehicleData::SquealSound], &getTransform());
|
|
|
|
alxSourceMatrixF(mSquealSound, &getTransform());
|
|
alxSourcef(mSquealSound, AL_GAIN_LINEAR, level);
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
/** Update jet sound
|
|
This method is only invoked by clients.
|
|
*/
|
|
void WheeledVehicle::updateJetSound()
|
|
{
|
|
if (!mDataBlock->sound[WheeledVehicleData::JetSound])
|
|
return;
|
|
|
|
// Allocate/Deallocate voice on demand.
|
|
if (!mJetting)
|
|
{
|
|
if (mJetSound)
|
|
{
|
|
alxStop(mJetSound);
|
|
mJetSound = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!mJetSound)
|
|
mJetSound = alxPlay(mDataBlock->sound[WheeledVehicleData::JetSound], &getTransform());
|
|
|
|
alxSourceMatrixF(mJetSound, &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(AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere)
|
|
{
|
|
// Parent will take care of body collision.
|
|
Parent::buildPolyList(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.min.x = -(wbox.max.x = radius / 2);
|
|
wbox.min.y = -(wbox.max.y = radius);
|
|
wbox.min.z = -(wbox.max.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();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool WheeledVehicle::prepRenderImage(SceneState* state, const U32 stateKey,
|
|
const U32 startZone, const bool modifyBaseState)
|
|
{
|
|
AssertFatal(modifyBaseState == false, "Error, should never be called with this parameter set");
|
|
AssertFatal(startZone == 0xFFFFFFFF, "Error, startZone should indicate -1");
|
|
|
|
if (isLastState(state, stateKey))
|
|
return false;
|
|
setLastState(state, stateKey);
|
|
|
|
if( ( getDamageState() == Destroyed ) && ( !mDataBlock->renderWhenDestroyed ) )
|
|
return false;
|
|
|
|
// Select detail levels on mounted items
|
|
// but... always draw the control object's mounted images
|
|
// in high detail (I can't believe I'm commenting this hack :)
|
|
F32 saveError = TSShapeInstance::smScreenError;
|
|
GameConnection *con = GameConnection::getConnectionToServer();
|
|
bool fogExemption = false;
|
|
ShapeBase *co = NULL;
|
|
if(con && ( (co = con->getControlObject()) != NULL) )
|
|
{
|
|
if(co == this || co->getObjectMount() == this)
|
|
{
|
|
TSShapeInstance::smScreenError = 0.001;
|
|
fogExemption = true;
|
|
}
|
|
}
|
|
|
|
if (state->isObjectRendered(this))
|
|
{
|
|
mLastRenderFrame = sLastRenderFrame;
|
|
// get shape detail and fog information...we might not even need to be drawn
|
|
Point3F cameraOffset;
|
|
getRenderTransform().getColumn(3,&cameraOffset);
|
|
cameraOffset -= state->getCameraPosition();
|
|
F32 dist = cameraOffset.len();
|
|
if (dist < 0.01)
|
|
dist = 0.01;
|
|
F32 fogAmount = state->getHazeAndFog(dist,cameraOffset.z);
|
|
F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z));
|
|
if (mShapeInstance)
|
|
DetailManager::selectPotentialDetails(mShapeInstance,dist,invScale);
|
|
|
|
Wheel* wend = &mWheel[mDataBlock->wheelCount];
|
|
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
|
|
{
|
|
if (wheel->shapeInstance)
|
|
DetailManager::selectPotentialDetails(wheel->shapeInstance,dist,invScale);
|
|
}
|
|
|
|
if (mShapeInstance)
|
|
mShapeInstance->animate();
|
|
|
|
if ((fogAmount>0.99f && fogExemption == false) ||
|
|
(mShapeInstance && mShapeInstance->getCurrentDetail()<0) ||
|
|
(!mShapeInstance && !gShowBoundingBox)) {
|
|
// no, don't draw anything
|
|
return false;
|
|
}
|
|
|
|
|
|
for (U32 i = 0; i < MaxMountedImages; i++)
|
|
{
|
|
MountedImage& image = mMountedImageList[i];
|
|
if (image.dataBlock && image.shapeInstance)
|
|
{
|
|
DetailManager::selectPotentialDetails(image.shapeInstance,dist,invScale);
|
|
|
|
if (mCloakLevel == 0.0f && image.shapeInstance->hasSolid() && mFadeVal == 1.0f)
|
|
{
|
|
ShapeImageRenderImage* rimage = new ShapeImageRenderImage;
|
|
rimage->obj = this;
|
|
rimage->mSBase = this;
|
|
rimage->mIndex = i;
|
|
rimage->isTranslucent = false;
|
|
rimage->textureSortKey = (U32)(dsize_t)(image.dataBlock);
|
|
state->insertRenderImage(rimage);
|
|
}
|
|
|
|
if ((mCloakLevel != 0.0f || mFadeVal != 1.0f || mShapeInstance->hasTranslucency()) ||
|
|
(mMount.object == NULL))
|
|
{
|
|
ShapeImageRenderImage* rimage = new ShapeImageRenderImage;
|
|
rimage->obj = this;
|
|
rimage->mSBase = this;
|
|
rimage->mIndex = i;
|
|
rimage->isTranslucent = true;
|
|
rimage->sortType = SceneRenderImage::Point;
|
|
rimage->textureSortKey = (U32)(dsize_t)(image.dataBlock);
|
|
state->setImageRefPoint(this, rimage);
|
|
state->insertRenderImage(rimage);
|
|
}
|
|
}
|
|
}
|
|
TSShapeInstance::smScreenError = saveError;
|
|
|
|
if (mCloakLevel == 0.0f && mShapeInstance->hasSolid() && mFadeVal == 1.0f)
|
|
{
|
|
SceneRenderImage* image = new SceneRenderImage;
|
|
image->obj = this;
|
|
image->isTranslucent = false;
|
|
image->textureSortKey = mSkinHash ^ (U32)(dsize_t)(mDataBlock);
|
|
state->insertRenderImage(image);
|
|
}
|
|
|
|
if ((mCloakLevel != 0.0f || mFadeVal != 1.0f || mShapeInstance->hasTranslucency()) ||
|
|
(mMount.object == NULL))
|
|
{
|
|
SceneRenderImage* image = new SceneRenderImage;
|
|
image->obj = this;
|
|
image->isTranslucent = true;
|
|
image->sortType = SceneRenderImage::Point;
|
|
image->textureSortKey = mSkinHash ^ (U32)(dsize_t)(mDataBlock);
|
|
state->setImageRefPoint(this, image);
|
|
state->insertRenderImage(image);
|
|
}
|
|
|
|
calcClassRenderData();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void WheeledVehicle::renderImage(SceneState* state, SceneRenderImage* image)
|
|
{
|
|
Parent::renderImage(state, image);
|
|
|
|
// Shape transform
|
|
glPushMatrix();
|
|
dglMultMatrix(&getRenderTransform());
|
|
|
|
Wheel* wend = &mWheel[mDataBlock->wheelCount];
|
|
for (Wheel* wheel = mWheel; wheel < wend; wheel++)
|
|
{
|
|
if (wheel->shapeInstance)
|
|
{
|
|
glPushMatrix();
|
|
|
|
// 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);
|
|
dglMultMatrix(&hub);
|
|
|
|
// Wheel rotation
|
|
MatrixF rot(EulerF(wheel->apos * M_2PI,0,0));
|
|
dglMultMatrix(&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));
|
|
dglMultMatrix(&wrot);
|
|
|
|
// Render it
|
|
wheel->shapeInstance->animate();
|
|
wheel->shapeInstance->render();
|
|
glPopMatrix();
|
|
}
|
|
}
|
|
|
|
glPopMatrix();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
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.isNull())? 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
|
|
//----------------------------------------------------------------------------
|
|
|
|
ConsoleMethod(WheeledVehicle, setWheelSteering, bool, 4, 4, "obj.setWheelSteering(wheel#,float)")
|
|
{
|
|
S32 wheel = dAtoi(argv[2]);
|
|
if (wheel >= 0 && wheel < object->getWheelCount())
|
|
{
|
|
object->setWheelSteering(wheel,dAtof(argv[3]));
|
|
return true;
|
|
}
|
|
else
|
|
Con::warnf("setWheelSteering: wheel index out of bounds, vehicle has %d hubs",
|
|
argv[3],object->getWheelCount());
|
|
return false;
|
|
}
|
|
|
|
ConsoleMethod(WheeledVehicle, setWheelPowered, bool, 4, 4, "obj.setWheelPowered(wheel#,bool)")
|
|
{
|
|
S32 wheel = dAtoi(argv[2]);
|
|
if (wheel >= 0 && wheel < object->getWheelCount())
|
|
{
|
|
object->setWheelPowered(wheel,dAtob(argv[3]));
|
|
return true;
|
|
}
|
|
else
|
|
Con::warnf("setWheelPowered: wheel index out of bounds, vehicle has %d hubs",
|
|
argv[3],object->getWheelCount());
|
|
return false;
|
|
}
|
|
|
|
ConsoleMethod(WheeledVehicle, setWheelTire, bool, 4, 4, "obj.setWheelTire(wheel#,tire)")
|
|
{
|
|
WheeledVehicleTire* tire;
|
|
if (Sim::findObject(argv[3],tire))
|
|
{
|
|
S32 wheel = dAtoi(argv[2]);
|
|
if (wheel >= 0 && wheel < object->getWheelCount())
|
|
{
|
|
object->setWheelTire(wheel,tire);
|
|
return true;
|
|
}
|
|
else
|
|
Con::warnf("setWheelTire: wheel index out of bounds, vehicle has %d hubs",
|
|
argv[3],object->getWheelCount());
|
|
}
|
|
else
|
|
Con::warnf("setWheelTire: %s datablock does not exist (or is not a tire)",argv[3]);
|
|
return false;
|
|
}
|
|
|
|
ConsoleMethod(WheeledVehicle, setWheelSpring, bool, 4, 4, "obj.setWheelSpring(wheel#,spring)")
|
|
{
|
|
WheeledVehicleSpring* spring;
|
|
if (Sim::findObject(argv[3],spring))
|
|
{
|
|
S32 wheel = dAtoi(argv[2]);
|
|
if (wheel >= 0 && wheel < object->getWheelCount())
|
|
{
|
|
object->setWheelSpring(wheel,spring);
|
|
return true;
|
|
}
|
|
else
|
|
Con::warnf("setWheelSpring: wheel index out of bounds, vehicle has %d hubs",
|
|
argv[3],object->getWheelCount());
|
|
}
|
|
else
|
|
Con::warnf("setWheelSpring: %s datablock does not exist (or is not a spring)",argv[3]);
|
|
return false;
|
|
}
|
|
|
|
ConsoleMethod(WheeledVehicle, getWheelCount, S32, 2, 2, "obj.getWheelCount()")
|
|
{
|
|
return object->getWheelCount();
|
|
}
|