123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707 |
- //-----------------------------------------------------------------------------
- // 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()
- {
- 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;
- mShapeAsset.registerRefreshNotify(this);
- }
- 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 (!getShape())
- {
- errorStr = String::ToString("WheeledVehicleTire: Couldn't load shape \"%s\"", _getShapeAssetId());
- 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 = getShape()->mBounds.len_z() / 2;
- }
- return true;
- }
- void WheeledVehicleTire::initPersistFields()
- {
- docsURL;
- INITPERSISTFIELD_SHAPEASSET_REFACTOR(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_REFACTOR(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_REFACTOR(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(getShape(), 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 = getShape()->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 = getShape()->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 = getShape()->findSequence("steering");
- // Brakes
- brakeLightSequence = getShape()->findSequence("brakelight");
- // Extract collision planes from shape collision detail level
- if (collisionDetails[0] != -1) {
- MatrixF imat(1);
- SphereF sphere;
- sphere.center = getShape()->center;
- sphere.radius = getShape()->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(isClientObject());
- // 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;
- wheel->slipping = false;
- }
- 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);
-
- }
- 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++)
- {
- if (wheel->tire && wheel->spring) {
- // Update angular position
- wheel->apos += (wheel->avel) / M_2PI;
- wheel->apos -= mFloor(wheel->apos);
- if (wheel->apos < 0)
- wheel->apos = 1 - wheel->apos;
- }
- }
- // Rigid state is transmitted by the parent...
- setPosition(mRigid.linPosition,mRigid.angPosition);
- mDelta.pos = mRigid.linPosition;
- mDelta.rot[1] = mRigid.angPosition;
- // Stick the wheels to the ground. This is purely so they look
- // good while the vehicle is being interpolated.
- extendWheels(isClientObject());
- updateWheelThreads();
- }
- //----------------------------------------------------------------------------
- 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->getShape() == NULL) ? 0:
- new TSShapeInstance(wheel->tire->getShape());
- }
- }
- }
- // 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();
- }
|