2
0

wheeledVehicle.cpp 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "T3D/vehicles/wheeledVehicle.h"
  24. #include "math/mMath.h"
  25. #include "math/mathIO.h"
  26. #include "console/simBase.h"
  27. #include "console/console.h"
  28. #include "console/consoleTypes.h"
  29. #include "console/engineAPI.h"
  30. #include "collision/clippedPolyList.h"
  31. #include "collision/planeExtractor.h"
  32. #include "core/stream/bitStream.h"
  33. #include "core/dnet.h"
  34. #include "T3D/gameBase/gameConnection.h"
  35. #include "ts/tsShapeInstance.h"
  36. #include "T3D/fx/particleEmitter.h"
  37. #include "sfx/sfxSystem.h"
  38. #include "sfx/sfxTrack.h"
  39. #include "sfx/sfxSource.h"
  40. #include "sfx/sfxTypes.h"
  41. #include "scene/sceneManager.h"
  42. #include "core/resourceManager.h"
  43. #include "materials/materialDefinition.h"
  44. #include "materials/baseMatInstance.h"
  45. #include "lighting/lightQuery.h"
  46. // Collision masks are used to determine what type of objects the
  47. // wheeled vehicle will collide with.
  48. static U32 sClientCollisionMask =
  49. TerrainObjectType | PlayerObjectType |
  50. StaticShapeObjectType | VehicleObjectType |
  51. VehicleBlockerObjectType;
  52. // Misc. sound constants
  53. static F32 sMinSquealVolume = 0.05f;
  54. static F32 sIdleEngineVolume = 0.2f;
  55. //----------------------------------------------------------------------------
  56. // Vehicle Tire Data Block
  57. //----------------------------------------------------------------------------
  58. IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleTire);
  59. ConsoleDocClass( WheeledVehicleTire,
  60. "@brief Defines the properties of a WheeledVehicle tire.\n\n"
  61. "Tires act as springs and generate lateral and longitudinal forces to move "
  62. "the vehicle. These distortion/spring forces are what convert wheel angular "
  63. "velocity into forces that act on the rigid body.\n"
  64. "@ingroup Vehicles\n"
  65. );
  66. WheeledVehicleTire::WheeledVehicleTire()
  67. {
  68. staticFriction = 1;
  69. kineticFriction = 0.5f;
  70. restitution = 1;
  71. radius = 0.6f;
  72. lateralForce = 10;
  73. lateralDamping = 1;
  74. lateralRelaxation = 1;
  75. longitudinalForce = 10;
  76. longitudinalDamping = 1;
  77. longitudinalRelaxation = 1;
  78. mass = 1.f;
  79. mShapeAsset.registerRefreshNotify(this);
  80. }
  81. bool WheeledVehicleTire::preload(bool server, String &errorStr)
  82. {
  83. // Load up the tire shape. ShapeBase has an option to force a
  84. // CRC check, this is left out here, but could be easily added.
  85. if (!getShape())
  86. {
  87. errorStr = String::ToString("WheeledVehicleTire: Couldn't load shape \"%s\"", _getShapeAssetId());
  88. return false;
  89. }
  90. else
  91. {
  92. // Determinw wheel radius from the shape's bounding box.
  93. // The tire should be built with it's hub axis along the
  94. // object's Y axis.
  95. radius = getShape()->mBounds.len_z() / 2;
  96. }
  97. return true;
  98. }
  99. void WheeledVehicleTire::initPersistFields()
  100. {
  101. docsURL;
  102. INITPERSISTFIELD_SHAPEASSET_REFACTOR(Shape, WheeledVehicleTire, "The shape to use for the wheel.");
  103. addFieldV( "mass", TypeRangedF32, Offset(mass, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  104. "The mass of the wheel.\nCurrently unused." );
  105. addFieldV( "radius", TypeRangedF32, Offset(radius, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  106. "@brief The radius of the wheel.\n\n"
  107. "The radius is determined from the bounding box of the shape provided "
  108. "in the shapefile field, and does not need to be specified in script. "
  109. "The tire should be built with its hub axis along the object's Y-axis." );
  110. addFieldV( "staticFriction", TypeRangedF32, Offset(staticFriction, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  111. "Tire friction when the wheel is not slipping (has traction)." );
  112. addFieldV( "kineticFriction", TypeRangedF32, Offset(kineticFriction, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  113. "Tire friction when the wheel is slipping (no traction)." );
  114. addFieldV( "restitution", TypeRangedF32, Offset(restitution, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  115. "Tire restitution.\nCurrently unused." );
  116. addFieldV( "lateralForce", TypeRangedF32, Offset(lateralForce, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  117. "@brief Tire force perpendicular to the direction of movement.\n\n"
  118. "Lateral force can in simple terms be considered left/right steering "
  119. "force. WheeledVehicles are acted upon by forces generated by their tires "
  120. "and the lateralForce measures the magnitude of the force exerted on the "
  121. "vehicle when the tires are deformed along the x-axis. With real wheeled "
  122. "vehicles, tires are constantly being deformed and it is the interplay of "
  123. "deformation forces which determines how a vehicle moves. In Torque's "
  124. "simulation of vehicle physics, tire deformation obviously can't be handled "
  125. "with absolute realism, but the interplay of a vehicle's velocity, its "
  126. "engine's torque and braking forces, and its wheels' friction, lateral "
  127. "deformation, lateralDamping, lateralRelaxation, longitudinal deformation, "
  128. "longitudinalDamping, and longitudinalRelaxation forces, along with its "
  129. "wheels' angular velocity are combined to create a robust real-time "
  130. "physical simulation.\n\n"
  131. "For this field, the larger the value supplied for the lateralForce, the "
  132. "larger the effect steering maneuvers can have. In Torque tire forces are "
  133. "applied at a vehicle's wheel hubs." );
  134. addFieldV( "lateralDamping", TypeRangedF32, Offset(lateralDamping, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  135. "Damping force applied against lateral forces generated by the tire.\n\n"
  136. "@see lateralForce" );
  137. addFieldV( "lateralRelaxation", TypeRangedF32, Offset(lateralRelaxation, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  138. "@brief Relaxing force applied against lateral forces generated by the tire.\n\n"
  139. "The lateralRelaxation force measures how strongly the tire effectively "
  140. "un-deforms.\n\n@see lateralForce" );
  141. addFieldV( "longitudinalForce", TypeRangedF32, Offset(longitudinalForce, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  142. "@brief Tire force in the direction of movement.\n\n"
  143. "Longitudinal force can in simple terms be considered forward/backward "
  144. "movement force. WheeledVehicles are acted upon by forces generated by "
  145. "their tires and the longitudinalForce measures the magnitude of the "
  146. "force exerted on the vehicle when the tires are deformed along the y-axis.\n\n"
  147. "For this field, the larger the value, the larger the effect "
  148. "acceleration/deceleration inputs have.\n\n"
  149. "@see lateralForce" );
  150. addFieldV( "longitudinalDamping", TypeRangedF32, Offset(longitudinalDamping, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  151. "Damping force applied against longitudinal forces generated by the tire.\n\n"
  152. "@see longitudinalForce" );
  153. addFieldV( "longitudinalRelaxation", TypeRangedF32, Offset(longitudinalRelaxation, WheeledVehicleTire), &CommonValidators::PositiveFloat,
  154. "@brief Relaxing force applied against longitudinal forces generated by the tire.\n\n"
  155. "The longitudinalRelaxation force measures how strongly the tire effectively "
  156. "un-deforms.\n\n"
  157. "@see longitudinalForce" );
  158. Parent::initPersistFields();
  159. }
  160. void WheeledVehicleTire::packData(BitStream* stream)
  161. {
  162. Parent::packData(stream);
  163. PACKDATA_ASSET_REFACTOR(Shape);
  164. stream->write(mass);
  165. stream->write(staticFriction);
  166. stream->write(kineticFriction);
  167. stream->write(restitution);
  168. stream->write(radius);
  169. stream->write(lateralForce);
  170. stream->write(lateralDamping);
  171. stream->write(lateralRelaxation);
  172. stream->write(longitudinalForce);
  173. stream->write(longitudinalDamping);
  174. stream->write(longitudinalRelaxation);
  175. }
  176. void WheeledVehicleTire::unpackData(BitStream* stream)
  177. {
  178. Parent::unpackData(stream);
  179. UNPACKDATA_ASSET_REFACTOR(Shape);
  180. stream->read(&mass);
  181. stream->read(&staticFriction);
  182. stream->read(&kineticFriction);
  183. stream->read(&restitution);
  184. stream->read(&radius);
  185. stream->read(&lateralForce);
  186. stream->read(&lateralDamping);
  187. stream->read(&lateralRelaxation);
  188. stream->read(&longitudinalForce);
  189. stream->read(&longitudinalDamping);
  190. stream->read(&longitudinalRelaxation);
  191. }
  192. //----------------------------------------------------------------------------
  193. // Vehicle Spring Data Block
  194. //----------------------------------------------------------------------------
  195. IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleSpring);
  196. ConsoleDocClass( WheeledVehicleSpring,
  197. "@brief Defines the properties of a WheeledVehicle spring.\n\n"
  198. "@ingroup Vehicles\n"
  199. );
  200. WheeledVehicleSpring::WheeledVehicleSpring()
  201. {
  202. length = 1;
  203. force = 10;
  204. damping = 1;
  205. antiSway = 1;
  206. }
  207. void WheeledVehicleSpring::initPersistFields()
  208. {
  209. docsURL;
  210. addFieldV( "length", TypeRangedF32, Offset(length, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
  211. "@brief Maximum spring length. ie. how far the wheel can extend from the "
  212. "root hub position.\n\n"
  213. "This should be set to the vertical (Z) distance the hub travels in the "
  214. "associated spring animation." );
  215. addFieldV( "force", TypeRangedF32, Offset(force, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
  216. "@brief Maximum spring force (when compressed to minimum length, 0).\n\n"
  217. "Increasing this will make the vehicle suspension ride higher (for a given "
  218. "vehicle mass), and also make the vehicle more bouncy when landing jumps." );
  219. addFieldV( "damping", TypeRangedF32, Offset(damping, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
  220. "@brief Force applied to slow changes to the extension of this spring.\n\n"
  221. "Increasing this makes the suspension stiffer which can help stabilise "
  222. "bouncy vehicles." );
  223. addFieldV( "antiSwayForce", TypeRangedF32, Offset(antiSway, WheeledVehicleSpring), &CommonValidators::PositiveFloat,
  224. "@brief Force applied to equalize extension of the spring on the opposite "
  225. "wheel.\n\n"
  226. "This force helps to keep the suspension balanced when opposite wheels "
  227. "are at different heights." );
  228. Parent::initPersistFields();
  229. }
  230. void WheeledVehicleSpring::packData(BitStream* stream)
  231. {
  232. Parent::packData(stream);
  233. stream->write(length);
  234. stream->write(force);
  235. stream->write(damping);
  236. stream->write(antiSway);
  237. }
  238. void WheeledVehicleSpring::unpackData(BitStream* stream)
  239. {
  240. Parent::unpackData(stream);
  241. stream->read(&length);
  242. stream->read(&force);
  243. stream->read(&damping);
  244. stream->read(&antiSway);
  245. }
  246. //----------------------------------------------------------------------------
  247. // Wheeled Vehicle Data Block
  248. //----------------------------------------------------------------------------
  249. //----------------------------------------------------------------------------
  250. IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleData);
  251. ConsoleDocClass( WheeledVehicleData,
  252. "@brief Defines the properties of a WheeledVehicle.\n\n"
  253. "@ingroup Vehicles\n"
  254. );
  255. typedef WheeledVehicleData::Sounds WheeledVehicleSoundsEnum;
  256. DefineEnumType(WheeledVehicleSoundsEnum);
  257. ImplementEnumType(WheeledVehicleSoundsEnum, "enum types.\n"
  258. "@ingroup WheeledVehicleData\n\n")
  259. { WheeledVehicleSoundsEnum::JetSound, "JetSound", "..." },
  260. { WheeledVehicleSoundsEnum::EngineSound, "EngineSound", "..." },
  261. { WheeledVehicleSoundsEnum::SquealSound, "SquealSound", "..." },
  262. { WheeledVehicleSoundsEnum::WheelImpactSound, "WheelImpactSound", "..." },
  263. EndImplementEnumType;
  264. WheeledVehicleData::WheeledVehicleData()
  265. {
  266. tireEmitter = 0;
  267. maxWheelSpeed = 40;
  268. engineTorque = 1;
  269. engineBrake = 1;
  270. brakeTorque = 1;
  271. brakeLightSequence = -1;
  272. steeringSequence = -1;
  273. wheelCount = 0;
  274. dMemset(&wheel, 0, sizeof(wheel));
  275. for (S32 i = 0; i < MaxSounds; i++)
  276. INIT_SOUNDASSET_ARRAY(WheeledVehicleSounds, i);
  277. mDownForce = 0;
  278. }
  279. //----------------------------------------------------------------------------
  280. /** Load the vehicle shape
  281. Loads and extracts information from the vehicle shape.
  282. Wheel Sequences
  283. spring# Wheel spring motion: time 0 = wheel fully extended,
  284. the hub must be displaced, but not directly animated
  285. as it will be rotated in code.
  286. Other Sequences
  287. steering Wheel steering: time 0 = full right, 0.5 = center
  288. brakeLight Brake light, time 0 = off, 1 = braking
  289. Wheel Nodes
  290. hub# Wheel hub
  291. The steering and animation sequences are optional.
  292. */
  293. bool WheeledVehicleData::preload(bool server, String &errorStr)
  294. {
  295. if (!Parent::preload(server, errorStr))
  296. return false;
  297. // A temporary shape instance is created so that we can
  298. // animate the shape and extract wheel information.
  299. TSShapeInstance* si = new TSShapeInstance(getShape(), false);
  300. // Resolve objects transmitted from server
  301. if (!server) {
  302. for (S32 i = 0; i < MaxSounds; i++)
  303. {
  304. if (!isWheeledVehicleSoundsValid(i))
  305. {
  306. //return false; -TODO: trigger asset download
  307. }
  308. }
  309. if (tireEmitter)
  310. Sim::findObject(SimObjectId((uintptr_t)tireEmitter),tireEmitter);
  311. }
  312. // Extract wheel information from the shape
  313. TSThread* thread = si->addThread();
  314. Wheel* wp = wheel;
  315. char buff[10];
  316. for (S32 i = 0; i < MaxWheels; i++) {
  317. // The wheel must have a hub node to operate at all.
  318. dSprintf(buff,sizeof(buff),"hub%d",i);
  319. wp->springNode = getShape()->findNode(buff);
  320. if (wp->springNode != -1) {
  321. // Check for spring animation.. If there is none we just grab
  322. // the current position of the hub. Otherwise we'll animate
  323. // and get the position at time 0.
  324. dSprintf(buff,sizeof(buff),"spring%d",i);
  325. wp->springSequence = getShape()->findSequence(buff);
  326. if (wp->springSequence == -1)
  327. si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos);
  328. else {
  329. si->setSequence(thread,wp->springSequence,0);
  330. si->animate();
  331. si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos);
  332. // Determin the length of the animation so we can scale it
  333. // according the actual wheel position.
  334. Point3F downPos;
  335. si->setSequence(thread,wp->springSequence,1);
  336. si->animate();
  337. si->mNodeTransforms[wp->springNode].getColumn(3, &downPos);
  338. wp->springLength = wp->pos.z - downPos.z;
  339. if (!wp->springLength)
  340. wp->springSequence = -1;
  341. }
  342. // Match wheels that are mirrored along the Y axis.
  343. mirrorWheel(wp);
  344. wp++;
  345. }
  346. }
  347. wheelCount = wp - wheel;
  348. // Check for steering. Should think about normalizing the
  349. // steering animation the way the suspension is, but I don't
  350. // think it's as critical.
  351. steeringSequence = getShape()->findSequence("steering");
  352. // Brakes
  353. brakeLightSequence = getShape()->findSequence("brakelight");
  354. // Extract collision planes from shape collision detail level
  355. if (collisionDetails[0] != -1) {
  356. MatrixF imat(1);
  357. SphereF sphere;
  358. sphere.center = getShape()->center;
  359. sphere.radius = getShape()->mRadius;
  360. PlaneExtractorPolyList polyList;
  361. polyList.mPlaneList = &rigidBody.mPlaneList;
  362. polyList.setTransform(&imat, Point3F(1,1,1));
  363. si->buildPolyList(&polyList,collisionDetails[0]);
  364. }
  365. delete si;
  366. return true;
  367. }
  368. //----------------------------------------------------------------------------
  369. /** Find a matching lateral wheel
  370. Looks for a matching wheeling mirrored along the Y axis, within some
  371. tolerance (current 0.5m), if one is found, the two wheels are lined up.
  372. */
  373. bool WheeledVehicleData::mirrorWheel(Wheel* we)
  374. {
  375. we->opposite = -1;
  376. for (Wheel* wp = wheel; wp != we; wp++)
  377. if (mFabs(wp->pos.y - we->pos.y) < 0.5)
  378. {
  379. we->pos.x = -wp->pos.x;
  380. we->pos.y = wp->pos.y;
  381. we->pos.z = wp->pos.z;
  382. we->opposite = wp - wheel;
  383. wp->opposite = we - wheel;
  384. return true;
  385. }
  386. return false;
  387. }
  388. //----------------------------------------------------------------------------
  389. void WheeledVehicleData::initPersistFields()
  390. {
  391. docsURL;
  392. Parent::initPersistFields();
  393. addGroup("Particle Effects");
  394. addField("tireEmitter", TYPEID< ParticleEmitterData >(), Offset(tireEmitter, WheeledVehicleData),
  395. "ParticleEmitterData datablock used to generate particles from each wheel "
  396. "when the vehicle is moving and the wheel is in contact with the ground.");
  397. endGroup("Particle Effects");
  398. addGroup("Sounds");
  399. INITPERSISTFIELD_SOUNDASSET_ENUMED(WheeledVehicleSounds, WheeledVehicleSoundsEnum, MaxSounds, WheeledVehicleData, "Sounds related to wheeled vehicle.");
  400. endGroup("Sounds");
  401. addGroup("Steering");
  402. addFieldV("maxWheelSpeed", TypeRangedF32, Offset(maxWheelSpeed, WheeledVehicleData), &CommonValidators::PositiveFloat,
  403. "@brief Maximum linear velocity of each wheel.\n\n"
  404. "This caps the maximum speed of the vehicle." );
  405. addFieldV("engineTorque", TypeRangedF32, Offset(engineTorque, WheeledVehicleData), &CommonValidators::PositiveFloat,
  406. "@brief Torque available from the engine at 100% throttle.\n\n"
  407. "This controls vehicle acceleration. ie. how fast it will reach maximum speed." );
  408. addFieldV("engineBrake", TypeRangedF32, Offset(engineBrake, WheeledVehicleData), &CommonValidators::PositiveFloat,
  409. "@brief Braking torque applied by the engine when the throttle and brake "
  410. "are both 0.\n\n"
  411. "This controls how quickly the vehicle will coast to a stop." );
  412. addFieldV("brakeTorque", TypeRangedF32, Offset(brakeTorque, WheeledVehicleData), &CommonValidators::PositiveFloat,
  413. "@brief Torque applied when braking.\n\n"
  414. "This controls how fast the vehicle will stop when the brakes are applied." );
  415. addField("downforce", TypeF32, Offset(mDownForce, WheeledVehicleData),
  416. "downward force based on velocity.");
  417. endGroup("Steering");
  418. }
  419. //----------------------------------------------------------------------------
  420. void WheeledVehicleData::packData(BitStream* stream)
  421. {
  422. Parent::packData(stream);
  423. if (stream->writeFlag(tireEmitter))
  424. stream->writeRangedU32(mPacked ? SimObjectId((uintptr_t)tireEmitter):
  425. tireEmitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
  426. for (S32 i = 0; i < MaxSounds; i++)
  427. {
  428. PACKDATA_SOUNDASSET_ARRAY(WheeledVehicleSounds, i);
  429. }
  430. stream->write(maxWheelSpeed);
  431. stream->write(engineTorque);
  432. stream->write(engineBrake);
  433. stream->write(brakeTorque);
  434. stream->write(mDownForce);
  435. }
  436. void WheeledVehicleData::unpackData(BitStream* stream)
  437. {
  438. Parent::unpackData(stream);
  439. tireEmitter = stream->readFlag()?
  440. (ParticleEmitterData*)(uintptr_t)stream->readRangedU32(DataBlockObjectIdFirst,
  441. DataBlockObjectIdLast): 0;
  442. for (S32 i = 0; i < MaxSounds; i++)
  443. {
  444. UNPACKDATA_SOUNDASSET_ARRAY(WheeledVehicleSounds, i);
  445. }
  446. stream->read(&maxWheelSpeed);
  447. stream->read(&engineTorque);
  448. stream->read(&engineBrake);
  449. stream->read(&brakeTorque);
  450. stream->read(&mDownForce);
  451. }
  452. //----------------------------------------------------------------------------
  453. // Wheeled Vehicle Class
  454. //----------------------------------------------------------------------------
  455. //----------------------------------------------------------------------------
  456. IMPLEMENT_CO_NETOBJECT_V1(WheeledVehicle);
  457. ConsoleDocClass( WheeledVehicle,
  458. "@brief A wheeled vehicle.\n"
  459. "@ingroup Vehicles\n"
  460. );
  461. WheeledVehicle::WheeledVehicle()
  462. {
  463. mDataBlock = 0;
  464. mBraking = false;
  465. mJetSound = NULL;
  466. mEngineSound = NULL;
  467. mSquealSound = NULL;
  468. mTailLightThread = 0;
  469. mSteeringThread = 0;
  470. for (S32 i = 0; i < WheeledVehicleData::MaxWheels; i++) {
  471. mWheel[i].springThread = 0;
  472. mWheel[i].Dy = mWheel[i].Dx = 0;
  473. mWheel[i].tire = 0;
  474. mWheel[i].spring = 0;
  475. mWheel[i].shapeInstance = 0;
  476. mWheel[i].steering = 0;
  477. mWheel[i].powered = true;
  478. mWheel[i].slipping = false;
  479. }
  480. }
  481. WheeledVehicle::~WheeledVehicle()
  482. {
  483. }
  484. void WheeledVehicle::initPersistFields()
  485. {
  486. docsURL;
  487. Parent::initPersistFields();
  488. }
  489. //----------------------------------------------------------------------------
  490. bool WheeledVehicle::onAdd()
  491. {
  492. if(!Parent::onAdd())
  493. return false;
  494. addToScene();
  495. return true;
  496. }
  497. void WheeledVehicle::onRemove()
  498. {
  499. // Delete the wheel resources
  500. if (mDataBlock != NULL) {
  501. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  502. for (Wheel* wheel = mWheel; wheel < wend; wheel++) {
  503. if (!wheel->emitter.isNull())
  504. wheel->emitter->deleteWhenEmpty();
  505. delete wheel->shapeInstance;
  506. }
  507. }
  508. // Stop the sounds
  509. SFX_DELETE( mJetSound );
  510. SFX_DELETE( mEngineSound );
  511. SFX_DELETE( mSquealSound );
  512. //
  513. removeFromScene();
  514. Parent::onRemove();
  515. }
  516. //----------------------------------------------------------------------------
  517. bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr, bool reload)
  518. {
  519. // Delete any existing wheel resources if we're switching
  520. // datablocks.
  521. if (mDataBlock)
  522. {
  523. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  524. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  525. {
  526. if (!wheel->emitter.isNull())
  527. {
  528. wheel->emitter->deleteWhenEmpty();
  529. wheel->emitter = 0;
  530. }
  531. delete wheel->shapeInstance;
  532. wheel->shapeInstance = 0;
  533. }
  534. }
  535. // Load up the new datablock
  536. mDataBlock = dynamic_cast<WheeledVehicleData*>(dptr);
  537. if (!mDataBlock || !Parent::onNewDataBlock(dptr,reload))
  538. return false;
  539. // Set inertial tensor, default for the vehicle is sphere
  540. if (mDataBlock->massBox.x > 0 && mDataBlock->massBox.y > 0 && mDataBlock->massBox.z > 0)
  541. mRigid.setObjectInertia(mDataBlock->massBox);
  542. else
  543. mRigid.setObjectInertia(mObjBox.maxExtents - mObjBox.minExtents);
  544. // Initialize the wheels...
  545. for (S32 i = 0; i < mDataBlock->wheelCount; i++)
  546. {
  547. Wheel* wheel = &mWheel[i];
  548. wheel->data = &mDataBlock->wheel[i];
  549. wheel->tire = 0;
  550. wheel->spring = 0;
  551. wheel->surface.contact = false;
  552. wheel->surface.object = NULL;
  553. wheel->avel = 0;
  554. wheel->apos = 0;
  555. wheel->extension = 1;
  556. wheel->slip = 0;
  557. wheel->springThread = 0;
  558. wheel->emitter = 0;
  559. // Steering on the front tires by default
  560. if (wheel->data->pos.y > 0)
  561. wheel->steering = 1;
  562. // Build wheel animation threads
  563. if (wheel->data->springSequence != -1) {
  564. wheel->springThread = mShapeInstance->addThread();
  565. mShapeInstance->setSequence(wheel->springThread,wheel->data->springSequence,0);
  566. }
  567. // Each wheel get's it's own particle emitter
  568. if( mDataBlock->tireEmitter && isGhost() )
  569. {
  570. wheel->emitter = new ParticleEmitter;
  571. wheel->emitter->onNewDataBlock( mDataBlock->tireEmitter, false );
  572. wheel->emitter->registerObject();
  573. }
  574. }
  575. // Steering sequence
  576. if (mDataBlock->steeringSequence != -1) {
  577. mSteeringThread = mShapeInstance->addThread();
  578. mShapeInstance->setSequence(mSteeringThread,mDataBlock->steeringSequence,0);
  579. }
  580. else
  581. mSteeringThread = 0;
  582. // Brake light sequence
  583. if (mDataBlock->brakeLightSequence != -1) {
  584. mTailLightThread = mShapeInstance->addThread();
  585. mShapeInstance->setSequence(mTailLightThread,mDataBlock->brakeLightSequence,0);
  586. }
  587. else
  588. mTailLightThread = 0;
  589. if (isGhost())
  590. {
  591. // Create the sounds ahead of time. This reduces runtime
  592. // costs and makes the system easier to understand.
  593. SFX_DELETE( mEngineSound );
  594. SFX_DELETE( mSquealSound );
  595. SFX_DELETE( mJetSound );
  596. if ( mDataBlock->getWheeledVehicleSounds(WheeledVehicleData::EngineSound) )
  597. mEngineSound = SFX->createSource( mDataBlock->getWheeledVehicleSoundsProfile(WheeledVehicleData::EngineSound), &getTransform() );
  598. if ( mDataBlock->getWheeledVehicleSounds(WheeledVehicleData::SquealSound) )
  599. mSquealSound = SFX->createSource( mDataBlock->getWheeledVehicleSoundsProfile(WheeledVehicleData::SquealSound), &getTransform() );
  600. if ( mDataBlock->getWheeledVehicleSounds(WheeledVehicleData::JetSound) )
  601. mJetSound = SFX->createSource( mDataBlock->getWheeledVehicleSoundsProfile(WheeledVehicleData::JetSound), &getTransform() );
  602. }
  603. scriptOnNewDataBlock(reload);
  604. return true;
  605. }
  606. //----------------------------------------------------------------------------
  607. S32 WheeledVehicle::getWheelCount()
  608. {
  609. // Return # of hubs defined on the car body
  610. return mDataBlock? mDataBlock->wheelCount: 0;
  611. }
  612. void WheeledVehicle::setWheelSteering(S32 wheel,F32 steering)
  613. {
  614. AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
  615. mWheel[wheel].steering = mClampF(steering,-1,1);
  616. setMaskBits(WheelMask);
  617. }
  618. void WheeledVehicle::setWheelPowered(S32 wheel,bool powered)
  619. {
  620. AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
  621. mWheel[wheel].powered = powered;
  622. setMaskBits(WheelMask);
  623. }
  624. void WheeledVehicle::setWheelTire(S32 wheel,WheeledVehicleTire* tire)
  625. {
  626. AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
  627. mWheel[wheel].tire = tire;
  628. setMaskBits(WheelMask);
  629. }
  630. void WheeledVehicle::setWheelSpring(S32 wheel,WheeledVehicleSpring* spring)
  631. {
  632. AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
  633. mWheel[wheel].spring = spring;
  634. setMaskBits(WheelMask);
  635. }
  636. void WheeledVehicle::getWheelInstAndTransform( U32 index, TSShapeInstance** inst, MatrixF* xfrm ) const
  637. {
  638. AssertFatal( index < WheeledVehicleData::MaxWheels,
  639. "WheeledVehicle::getWheelInstAndTransform() - Bad wheel index!" );
  640. const Wheel* wheel = &mWheel[index];
  641. *inst = wheel->shapeInstance;
  642. if ( !xfrm || !wheel->shapeInstance )
  643. return;
  644. MatrixF world = getRenderTransform();
  645. world.scale( mObjScale );
  646. // Steering & spring extension
  647. MatrixF hub(EulerF(0,0,mSteering.x * wheel->steering));
  648. Point3F pos = wheel->data->pos;
  649. pos.z -= wheel->spring->length * wheel->extension;
  650. hub.setColumn(3,pos);
  651. world.mul(hub);
  652. // Wheel rotation
  653. MatrixF rot(EulerF(wheel->apos * M_2PI,0,0));
  654. world.mul(rot);
  655. // Rotation the tire to face the right direction
  656. // (could pre-calculate this)
  657. MatrixF wrot(EulerF(0,0,(wheel->data->pos.x > 0)? M_PI/2: -M_PI/2));
  658. world.mul(wrot);
  659. *xfrm = world;
  660. }
  661. //----------------------------------------------------------------------------
  662. void WheeledVehicle::processTick(const Move* move)
  663. {
  664. Parent::processTick(move);
  665. }
  666. void WheeledVehicle::updateMove(const Move* move)
  667. {
  668. Parent::updateMove(move);
  669. // Brake on trigger
  670. mBraking = move->trigger[2];
  671. // Set the tail brake light thread direction based on the brake state.
  672. if (mTailLightThread)
  673. mShapeInstance->setTimeScale(mTailLightThread, mBraking? 1.0f : -1.0f);
  674. // Update the steering animation: sequence time 0 is full right,
  675. // and time 0.5 is straight ahead.
  676. if (mSteeringThread) {
  677. F32 t = (mSteering.x * mFabs(mSteering.x)) / mDataBlock->maxSteeringAngle;
  678. mShapeInstance->setPos(mSteeringThread, 0.5 - t * 0.5);
  679. }
  680. }
  681. //----------------------------------------------------------------------------
  682. void WheeledVehicle::advanceTime(F32 dt)
  683. {
  684. PROFILE_SCOPE( WheeledVehicle_AdvanceTime );
  685. Parent::advanceTime(dt);
  686. // Stick the wheels to the ground. This is purely so they look
  687. // good while the vehicle is being interpolated.
  688. extendWheels(isClientObject());
  689. // Update wheel angular position and slip, this is a client visual
  690. // feature only, it has no affect on the physics.
  691. F32 slipTotal = 0;
  692. F32 torqueTotal = 0;
  693. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  694. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  695. if (wheel->tire && wheel->spring) {
  696. // Update angular position
  697. wheel->apos += (wheel->avel * dt) / M_2PI;
  698. wheel->apos -= mFloor(wheel->apos);
  699. if (wheel->apos < 0)
  700. wheel->apos = 1 - wheel->apos;
  701. // Keep track of largest slip
  702. slipTotal += wheel->slip;
  703. torqueTotal += wheel->torqueScale;
  704. }
  705. // Update the sounds based on wheel slip and torque output
  706. updateSquealSound(slipTotal / mDataBlock->wheelCount);
  707. updateEngineSound(sIdleEngineVolume + (1 - sIdleEngineVolume) *
  708. (1 - (torqueTotal / mDataBlock->wheelCount)));
  709. updateJetSound();
  710. updateWheelThreads();
  711. updateWheelParticles(dt);
  712. // Update the steering animation: sequence time 0 is full right,
  713. // and time 0.5 is straight ahead.
  714. if (mSteeringThread) {
  715. F32 t = (mSteering.x * mFabs(mSteering.x)) / mDataBlock->maxSteeringAngle;
  716. mShapeInstance->setPos(mSteeringThread,0.5 - t * 0.5);
  717. }
  718. // Animate the tail light. The direction of the thread is
  719. // set based on vehicle braking.
  720. if (mTailLightThread)
  721. mShapeInstance->advanceTime(dt,mTailLightThread);
  722. }
  723. //----------------------------------------------------------------------------
  724. /** Update the rigid body forces on the vehicle
  725. This method calculates the forces acting on the body, including gravity,
  726. suspension & tire forces.
  727. */
  728. void WheeledVehicle::updateForces(F32 dt)
  729. {
  730. PROFILE_SCOPE( WheeledVehicle_UpdateForces );
  731. extendWheels();
  732. if (mDisableMove) return;
  733. F32 aMomentum = mMass / mDataBlock->wheelCount;
  734. // Get the current matrix and extact vectors
  735. MatrixF currMatrix;
  736. mRigid.getTransform(&currMatrix);
  737. Point3F bx,by,bz;
  738. currMatrix.getColumn(0,&bx);
  739. currMatrix.getColumn(1,&by);
  740. currMatrix.getColumn(2,&bz);
  741. // Steering angles from current steering wheel position
  742. F32 quadraticSteering = -(mSteering.x * mFabs(mSteering.x));
  743. F32 cosSteering,sinSteering;
  744. mSinCos(quadraticSteering, sinSteering, cosSteering);
  745. // Calculate Engine and brake torque values used later by in
  746. // wheel calculations.
  747. F32 engineTorque,brakeVel;
  748. if (mBraking)
  749. {
  750. brakeVel = (mDataBlock->brakeTorque / aMomentum) * dt;
  751. engineTorque = 0;
  752. }
  753. else
  754. {
  755. if (mThrottle)
  756. {
  757. engineTorque = mDataBlock->engineTorque * mThrottle;
  758. brakeVel = 0;
  759. // Double the engineTorque to help out the jets
  760. if (mThrottle > 0 && mJetting)
  761. engineTorque *= 2;
  762. }
  763. else
  764. {
  765. // Engine brake.
  766. brakeVel = (mDataBlock->engineBrake / aMomentum) * dt;
  767. engineTorque = 0;
  768. }
  769. }
  770. // Integrate forces, we'll do this ourselves here instead of
  771. // relying on the rigid class which does it during movement.
  772. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  773. mRigid.clearForces();
  774. //calculate here so we can stiffen the springs a bit based on
  775. //the final amount of downforce
  776. //get the speed
  777. F32 downForce = mRigid.linVelocity.lenSquared() <= 1 ? 1 : mRigid.linVelocity.lenSquared();
  778. //grab the datablock var
  779. downForce *= mDataBlock->mDownForce;
  780. //make it a smaller number so we can multiply gravity by it
  781. downForce = mSqrt(downForce);
  782. downForce *= TickSec;
  783. //ensure that it is not smaller then one, cause mulltiplying gravity by fractions is baaaad
  784. downForce = downForce < 1 ? 1 : downForce;
  785. // Calculate vertical load for friction. Divide up the spring
  786. // forces across all the wheels that are in contact with
  787. // the ground.
  788. U32 contactCount = 0;
  789. F32 verticalLoad = 0;
  790. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  791. {
  792. if (wheel->tire && wheel->spring && wheel->surface.contact)
  793. {
  794. verticalLoad += wheel->spring->force * (1 - wheel->extension);
  795. contactCount++;
  796. }
  797. }
  798. if (contactCount)
  799. verticalLoad /= contactCount;
  800. // Sum up spring and wheel torque forces
  801. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  802. {
  803. if (!wheel->tire || !wheel->spring)
  804. continue;
  805. F32 Fy = 0;
  806. if (wheel->surface.contact)
  807. {
  808. // First, let's compute the wheel's position, and worldspace velocity
  809. Point3F pos, r, localVel;
  810. currMatrix.mulP(wheel->data->pos, &pos);
  811. mRigid.getOriginVector(pos,&r);
  812. mRigid.getVelocity(r, &localVel);
  813. // Spring force & damping
  814. F32 spring = wheel->spring->force * (1 - wheel->extension);
  815. spring += (spring * downForce);
  816. if (wheel->extension == 0) //spring fully compressed
  817. {
  818. // Apply impulses to the rigid body to keep it from
  819. // penetrating the surface.
  820. F32 n = -mDot(localVel,Point3F(0,0,1));
  821. if (n >= 0)
  822. {
  823. // Collision impulse, straight forward force stuff.
  824. F32 d = mRigid.getZeroImpulse(r,Point3F(0,0,1));
  825. F32 j = n * (1 + mRigid.restitution) * d;
  826. mRigid.force += Point3F(0,0,1) * j;
  827. }
  828. }
  829. F32 damping = wheel->spring->damping * -(mDot(bz, localVel) / wheel->spring->length);
  830. if (damping < 0)
  831. damping = 0;
  832. // Anti-sway force based on difference in suspension extension
  833. F32 antiSway = 0;
  834. if (wheel->data->opposite != -1)
  835. {
  836. Wheel* oppositeWheel = &mWheel[wheel->data->opposite];
  837. if (oppositeWheel->surface.contact)
  838. antiSway = ((oppositeWheel->extension - wheel->extension) *
  839. wheel->spring->antiSway);
  840. if (antiSway < 0)
  841. antiSway = 0;
  842. }
  843. // Spring forces act straight up and are applied at the
  844. // spring's root position.
  845. Point3F t, forceVector = bz * (spring + damping + antiSway);
  846. mCross(r, forceVector, &t);
  847. mRigid.torque += t;
  848. mRigid.force += forceVector;
  849. // Tire direction vectors perpendicular to surface normal
  850. Point3F wheelXVec = bx * cosSteering;
  851. wheelXVec += by * sinSteering * wheel->steering;
  852. Point3F tireX, tireY;
  853. mCross(wheel->surface.normal, wheelXVec, &tireY);
  854. tireY.normalize();
  855. mCross(tireY, wheel->surface.normal, &tireX);
  856. tireX.normalize();
  857. // Velocity of tire at the surface contact
  858. Point3F wheelContact, wheelVelocity;
  859. mRigid.getOriginVector(wheel->surface.pos,&wheelContact);
  860. mRigid.getVelocity(wheelContact, &wheelVelocity);
  861. F32 xVelocity = mDot(tireX, wheelVelocity);
  862. F32 yVelocity = mDot(tireY, wheelVelocity);
  863. // Tires act as springs and generate lateral and longitudinal
  864. // forces to move the vehicle. These distortion/spring forces
  865. // are what convert wheel angular velocity into forces that
  866. // act on the rigid body.
  867. // Longitudinal tire deformation force
  868. F32 ddy = (wheel->avel * wheel->tire->radius - yVelocity) -
  869. wheel->tire->longitudinalRelaxation *
  870. mFabs(wheel->avel) * wheel->Dy;
  871. wheel->Dy += ddy * dt;
  872. Fy = (wheel->tire->longitudinalForce * wheel->Dy +
  873. wheel->tire->longitudinalDamping * ddy);
  874. // Lateral tire deformation force
  875. F32 ddx = xVelocity - wheel->tire->lateralRelaxation *
  876. mFabs(wheel->avel) * wheel->Dx;
  877. wheel->Dx += ddx * dt;
  878. F32 Fx = -(wheel->tire->lateralForce * wheel->Dx +
  879. wheel->tire->lateralDamping * ddx);
  880. // Vertical load on the tire
  881. verticalLoad = spring + damping + antiSway;
  882. if (verticalLoad < 0)
  883. verticalLoad = 0;
  884. // Adjust tire forces based on friction
  885. F32 surfaceFriction = 1;
  886. F32 mu = surfaceFriction * (wheel->slipping ? wheel->tire->kineticFriction : wheel->tire->staticFriction);
  887. F32 Fn = verticalLoad * mu; Fn *= Fn;
  888. F32 Fw = Fx * Fx + Fy * Fy;
  889. if (Fw > Fn)
  890. {
  891. F32 K = mSqrt(Fn / Fw);
  892. Fy *= K;
  893. Fx *= K;
  894. wheel->Dy *= K;
  895. wheel->Dx *= K;
  896. wheel->slip = 1 - K;
  897. wheel->slipping = true;
  898. }
  899. else
  900. {
  901. wheel->slipping = false;
  902. wheel->slip = 0;
  903. }
  904. // Tire forces act through the tire direction vectors parallel
  905. // to the surface and are applied at the wheel hub.
  906. forceVector = (tireX * Fx) + (tireY * Fy);
  907. pos -= bz * (wheel->spring->length * wheel->extension);
  908. mRigid.getOriginVector(pos,&r);
  909. mCross(r, forceVector, &t);
  910. mRigid.torque += t;
  911. mRigid.force += forceVector;
  912. }
  913. else
  914. {
  915. // Wheel not in contact with the ground
  916. wheel->torqueScale = 0;
  917. wheel->slip = 0;
  918. // Relax the tire deformation
  919. wheel->Dy += (-wheel->tire->longitudinalRelaxation *
  920. mFabs(wheel->avel) * wheel->Dy) * dt;
  921. wheel->Dx += (-wheel->tire->lateralRelaxation *
  922. mFabs(wheel->avel) * wheel->Dx) * dt;
  923. }
  924. // Adjust the wheel's angular velocity based on engine torque
  925. // and tire deformation forces.
  926. if (wheel->powered)
  927. {
  928. F32 maxAvel = mDataBlock->maxWheelSpeed / wheel->tire->radius;
  929. wheel->torqueScale = (mFabs(wheel->avel) > maxAvel) ? 0 :
  930. 1 - (mFabs(wheel->avel) / maxAvel);
  931. }
  932. else
  933. wheel->torqueScale = 0;
  934. wheel->avel += (((wheel->torqueScale * engineTorque) - Fy *
  935. wheel->tire->radius) / aMomentum) * dt;
  936. // Adjust the wheel's angular velocity based on brake torque.
  937. // This is done after avel update to make sure we come to a
  938. // complete stop.
  939. if (brakeVel > mFabs(wheel->avel))
  940. wheel->avel = 0;
  941. else
  942. if (wheel->avel > 0)
  943. wheel->avel -= brakeVel;
  944. else
  945. wheel->avel += brakeVel;
  946. }
  947. // Jet Force
  948. if (mJetting)
  949. mRigid.force += by * mDataBlock->jetForce;
  950. // Add in force from physical zones...
  951. mRigid.force += mAppliedForce;
  952. // Container drag & buoyancy
  953. mRigid.force += Point3F(0, 0, mRigid.mass * mNetGravity * downForce);
  954. mRigid.force -= mRigid.linVelocity * mDrag;
  955. mRigid.torque -= mRigid.angMomentum * mDrag;
  956. // If we've added anything other than gravity, then we're no
  957. // longer at rest. Could test this a little more efficiently...
  958. if (mRigid.atRest && (mRigid.force.len() || mRigid.torque.len()))
  959. mRigid.atRest = false;
  960. // Integrate and update velocity
  961. mRigid.linMomentum += mRigid.force * dt;
  962. mRigid.angMomentum += mRigid.torque * dt;
  963. mRigid.updateVelocity();
  964. // Since we've already done all the work, just need to clear this out.
  965. mRigid.clearForces();
  966. // If we're still atRest, make sure we're not accumulating anything
  967. if (mRigid.atRest)
  968. mRigid.setAtRest();
  969. }
  970. //----------------------------------------------------------------------------
  971. /** Extend the wheels
  972. The wheels are extended until they contact a surface. The extension
  973. is instantaneous. The wheels are extended before force calculations and
  974. also on during client side interpolation (so that the wheels are glued
  975. to the ground).
  976. */
  977. void WheeledVehicle::extendWheels(bool clientHack)
  978. {
  979. PROFILE_SCOPE( WheeledVehicle_ExtendWheels );
  980. disableCollision();
  981. MatrixF currMatrix;
  982. if(clientHack)
  983. currMatrix = getRenderTransform();
  984. else
  985. mRigid.getTransform(&currMatrix);
  986. // Does a single ray cast down for now... this will have to be
  987. // changed to something a little more complicated to avoid getting
  988. // stuck in cracks.
  989. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  990. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  991. {
  992. if (wheel->tire && wheel->spring)
  993. {
  994. wheel->extension = 1;
  995. // The ray is cast from the spring mount point to the tip of
  996. // the tire. If there is a collision the spring extension is
  997. // adjust to remove the tire radius.
  998. Point3F sp,vec;
  999. currMatrix.mulP(wheel->data->pos,&sp);
  1000. currMatrix.mulV(VectorF(0,0,-wheel->spring->length),&vec);
  1001. F32 ts = wheel->tire->radius / wheel->spring->length;
  1002. Point3F ep = sp + (vec * (1 + ts));
  1003. ts = ts / (1+ts);
  1004. RayInfo rInfo;
  1005. if (mContainer->castRay(sp, ep, sClientCollisionMask & ~PlayerObjectType, &rInfo))
  1006. {
  1007. wheel->surface.contact = true;
  1008. wheel->extension = (rInfo.t < ts)? 0: (rInfo.t - ts) / (1 - ts);
  1009. wheel->surface.normal = rInfo.normal;
  1010. wheel->surface.pos = rInfo.point;
  1011. wheel->surface.material = rInfo.material;
  1012. wheel->surface.object = rInfo.object;
  1013. wheel->slipping = false;
  1014. }
  1015. else
  1016. {
  1017. wheel->surface.contact = false;
  1018. wheel->slipping = true;
  1019. }
  1020. }
  1021. }
  1022. enableCollision();
  1023. }
  1024. //----------------------------------------------------------------------------
  1025. /** Update wheel steering and suspension threads.
  1026. These animations are purely cosmetic and this method is only invoked
  1027. on the client.
  1028. */
  1029. void WheeledVehicle::updateWheelThreads()
  1030. {
  1031. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1032. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1033. {
  1034. if (wheel->tire && wheel->spring && wheel->springThread)
  1035. {
  1036. // Scale the spring animation time to match the current
  1037. // position of the wheel. We'll also check to make sure
  1038. // the animation is long enough, if it isn't, just stick
  1039. // it at the end.
  1040. F32 pos = wheel->extension * wheel->spring->length;
  1041. if (pos > wheel->data->springLength)
  1042. pos = 1;
  1043. else
  1044. pos /= wheel->data->springLength;
  1045. mShapeInstance->setPos(wheel->springThread,pos);
  1046. }
  1047. }
  1048. }
  1049. //----------------------------------------------------------------------------
  1050. /** Update wheel particles effects
  1051. These animations are purely cosmetic and this method is only invoked
  1052. on the client. Particles are emitted as long as the moving.
  1053. */
  1054. void WheeledVehicle::updateWheelParticles(F32 dt)
  1055. {
  1056. // OMG l33t hax
  1057. extendWheels(true);
  1058. Point3F vel = Parent::getVelocity();
  1059. F32 speed = vel.len();
  1060. // Don't bother if we're not moving.
  1061. if (speed > 1.0f)
  1062. {
  1063. Point3F axis = vel;
  1064. axis.normalize();
  1065. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1066. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1067. {
  1068. // Is this wheel in contact with the ground?
  1069. if (wheel->tire && wheel->spring && !wheel->emitter.isNull() &&
  1070. wheel->surface.contact && wheel->surface.object )
  1071. {
  1072. Material* material = ( wheel->surface.material ? dynamic_cast< Material* >( wheel->surface.material->getMaterial() ) : 0 );
  1073. if( material)//&& material->mShowDust )
  1074. {
  1075. LinearColorF colorList[ ParticleData::PDC_NUM_KEYS ];
  1076. for( U32 x = 0; x < getMin( Material::NUM_EFFECT_COLOR_STAGES, ParticleData::PDC_NUM_KEYS ); ++ x )
  1077. colorList[ x ] = material->mEffectColor[ x ];
  1078. for( U32 x = Material::NUM_EFFECT_COLOR_STAGES; x < ParticleData::PDC_NUM_KEYS; ++ x )
  1079. colorList[ x ].set( 1.0, 1.0, 1.0, 0.0 );
  1080. wheel->emitter->setColors( colorList );
  1081. // Emit the dust, the density (time) is scaled by the
  1082. // the vehicles velocity.
  1083. wheel->emitter->emitParticles( wheel->surface.pos, true,
  1084. axis, vel, (U32)(3/*dt * (speed / mDataBlock->maxWheelSpeed) * 1000 * wheel->slip*/));
  1085. }
  1086. }
  1087. }
  1088. }
  1089. }
  1090. //----------------------------------------------------------------------------
  1091. /** Update engine sound
  1092. This method is only invoked by clients.
  1093. */
  1094. void WheeledVehicle::updateEngineSound(F32 level)
  1095. {
  1096. if ( !mEngineSound )
  1097. return;
  1098. if ( !mEngineSound->isPlaying() )
  1099. mEngineSound->play();
  1100. mEngineSound->setTransform( getTransform() );
  1101. mEngineSound->setVelocity( getVelocity() );
  1102. //mEngineSound->setVolume( level );
  1103. // Adjust pitch
  1104. F32 pitch = ((level-sIdleEngineVolume) * 1.3f);
  1105. if (pitch < 0.4f)
  1106. pitch = 0.4f;
  1107. mEngineSound->setPitch( pitch );
  1108. }
  1109. //----------------------------------------------------------------------------
  1110. /** Update wheel skid sound
  1111. This method is only invoked by clients.
  1112. */
  1113. void WheeledVehicle::updateSquealSound(F32 level)
  1114. {
  1115. if ( !mSquealSound )
  1116. return;
  1117. if ( level < sMinSquealVolume )
  1118. {
  1119. mSquealSound->stop();
  1120. return;
  1121. }
  1122. if ( !mSquealSound->isPlaying() )
  1123. mSquealSound->play();
  1124. mSquealSound->setTransform( getTransform() );
  1125. mSquealSound->setVolume( level );
  1126. }
  1127. //----------------------------------------------------------------------------
  1128. /** Update jet sound
  1129. This method is only invoked by clients.
  1130. */
  1131. void WheeledVehicle::updateJetSound()
  1132. {
  1133. if ( !mJetSound )
  1134. return;
  1135. if ( !mJetting )
  1136. {
  1137. mJetSound->stop();
  1138. return;
  1139. }
  1140. if ( !mJetSound->isPlaying() )
  1141. mJetSound->play();
  1142. mJetSound->setTransform( getTransform() );
  1143. }
  1144. //----------------------------------------------------------------------------
  1145. U32 WheeledVehicle::getCollisionMask()
  1146. {
  1147. return sClientCollisionMask;
  1148. }
  1149. //----------------------------------------------------------------------------
  1150. /** Build a collision polylist
  1151. The polylist is filled with polygons representing the collision volume
  1152. and the wheels.
  1153. */
  1154. bool WheeledVehicle::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere)
  1155. {
  1156. PROFILE_SCOPE( WheeledVehicle_BuildPolyList );
  1157. // Parent will take care of body collision.
  1158. Parent::buildPolyList(context, polyList,box,sphere);
  1159. // Add wheels as boxes.
  1160. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1161. for (Wheel* wheel = mWheel; wheel < wend; wheel++) {
  1162. if (wheel->tire && wheel->spring) {
  1163. Box3F wbox;
  1164. F32 radius = wheel->tire->radius;
  1165. wbox.minExtents.x = -(wbox.maxExtents.x = radius / 2);
  1166. wbox.minExtents.y = -(wbox.maxExtents.y = radius);
  1167. wbox.minExtents.z = -(wbox.maxExtents.z = radius);
  1168. MatrixF mat = mObjToWorld;
  1169. Point3F sp,vec;
  1170. mObjToWorld.mulP(wheel->data->pos,&sp);
  1171. mObjToWorld.mulV(VectorF(0,0,-wheel->spring->length),&vec);
  1172. Point3F ep = sp + (vec * wheel->extension);
  1173. mat.setColumn(3,ep);
  1174. polyList->setTransform(&mat,Point3F(1,1,1));
  1175. polyList->addBox(wbox);
  1176. }
  1177. }
  1178. return !polyList->isEmpty();
  1179. }
  1180. void WheeledVehicle::prepBatchRender(SceneRenderState* state, S32 mountedImageIndex )
  1181. {
  1182. Parent::prepBatchRender( state, mountedImageIndex );
  1183. if ( mountedImageIndex != -1 )
  1184. return;
  1185. // Set up our render state *here*,
  1186. // before the push world matrix, so
  1187. // that wheel rendering will be correct.
  1188. TSRenderState rdata;
  1189. rdata.setSceneState( state );
  1190. // We might have some forward lit materials
  1191. // so pass down a query to gather lights.
  1192. LightQuery query;
  1193. query.init( getWorldSphere() );
  1194. rdata.setLightQuery( &query );
  1195. // Shape transform
  1196. GFX->pushWorldMatrix();
  1197. MatrixF mat = getRenderTransform();
  1198. mat.scale( mObjScale );
  1199. GFX->setWorldMatrix( mat );
  1200. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1201. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1202. {
  1203. if (wheel->shapeInstance)
  1204. {
  1205. GFX->pushWorldMatrix();
  1206. // Steering & spring extension
  1207. MatrixF hub(EulerF(0,0,mSteering.x * wheel->steering));
  1208. Point3F pos = wheel->data->pos;
  1209. pos.z -= wheel->spring->length * wheel->extension;
  1210. hub.setColumn(3,pos);
  1211. GFX->multWorld(hub);
  1212. // Wheel rotation
  1213. MatrixF rot(EulerF(wheel->apos * M_2PI,0,0));
  1214. GFX->multWorld(rot);
  1215. // Rotation the tire to face the right direction
  1216. // (could pre-calculate this)
  1217. MatrixF wrot(EulerF(0,0,(wheel->data->pos.x > 0)? M_PI/2: -M_PI/2));
  1218. GFX->multWorld(wrot);
  1219. // Render!
  1220. wheel->shapeInstance->animate();
  1221. wheel->shapeInstance->render( rdata );
  1222. if (mCloakLevel != 0.0f)
  1223. wheel->shapeInstance->setAlphaAlways(1.0f - mCloakLevel);
  1224. else
  1225. wheel->shapeInstance->setAlphaAlways(1.0f);
  1226. GFX->popWorldMatrix();
  1227. }
  1228. }
  1229. GFX->popWorldMatrix();
  1230. }
  1231. //----------------------------------------------------------------------------
  1232. void WheeledVehicle::writePacketData(GameConnection *connection, BitStream *stream)
  1233. {
  1234. Parent::writePacketData(connection, stream);
  1235. stream->writeFlag(mBraking);
  1236. }
  1237. void WheeledVehicle::readPacketData(GameConnection *connection, BitStream *stream)
  1238. {
  1239. Parent::readPacketData(connection, stream);
  1240. mBraking = stream->readFlag();
  1241. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1242. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1243. {
  1244. if (wheel->tire && wheel->spring) {
  1245. // Update angular position
  1246. wheel->apos += (wheel->avel) / M_2PI;
  1247. wheel->apos -= mFloor(wheel->apos);
  1248. if (wheel->apos < 0)
  1249. wheel->apos = 1 - wheel->apos;
  1250. }
  1251. }
  1252. // Rigid state is transmitted by the parent...
  1253. setPosition(mRigid.linPosition,mRigid.angPosition);
  1254. mDelta.pos = mRigid.linPosition;
  1255. mDelta.rot[1] = mRigid.angPosition;
  1256. // Stick the wheels to the ground. This is purely so they look
  1257. // good while the vehicle is being interpolated.
  1258. extendWheels(isClientObject());
  1259. updateWheelThreads();
  1260. }
  1261. //----------------------------------------------------------------------------
  1262. U32 WheeledVehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
  1263. {
  1264. U32 retMask = Parent::packUpdate(con, mask, stream);
  1265. // Update wheel datablock information
  1266. if (stream->writeFlag(mask & WheelMask))
  1267. {
  1268. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1269. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1270. {
  1271. if (stream->writeFlag(wheel->tire && wheel->spring))
  1272. {
  1273. stream->writeRangedU32(wheel->tire->getId(),
  1274. DataBlockObjectIdFirst,DataBlockObjectIdLast);
  1275. stream->writeRangedU32(wheel->spring->getId(),
  1276. DataBlockObjectIdFirst,DataBlockObjectIdLast);
  1277. stream->writeFlag(wheel->powered);
  1278. // Steering must be sent with full precision as it's
  1279. // used directly in state force calculations.
  1280. stream->write(wheel->steering);
  1281. }
  1282. }
  1283. }
  1284. // The rest of the data is part of the control object packet update.
  1285. // If we're controlled by this client, we don't need to send it.
  1286. if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask)))
  1287. return retMask;
  1288. stream->writeFlag(mBraking);
  1289. if (stream->writeFlag(mask & PositionMask))
  1290. {
  1291. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1292. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1293. {
  1294. stream->write(wheel->avel);
  1295. stream->write(wheel->Dy);
  1296. stream->write(wheel->Dx);
  1297. }
  1298. }
  1299. return retMask;
  1300. }
  1301. void WheeledVehicle::unpackUpdate(NetConnection *con, BitStream *stream)
  1302. {
  1303. Parent::unpackUpdate(con,stream);
  1304. // Update wheel datablock information
  1305. if (stream->readFlag())
  1306. {
  1307. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1308. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1309. {
  1310. if (stream->readFlag())
  1311. {
  1312. SimObjectId tid = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast);
  1313. SimObjectId sid = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast);
  1314. if (!Sim::findObject(tid,wheel->tire) || !Sim::findObject(sid,wheel->spring))
  1315. {
  1316. con->setLastError("Invalid packet WheeledVehicle::unpackUpdate()");
  1317. return;
  1318. }
  1319. wheel->powered = stream->readFlag();
  1320. stream->read(&wheel->steering);
  1321. // Create an instance of the tire for rendering
  1322. delete wheel->shapeInstance;
  1323. wheel->shapeInstance = (wheel->tire->getShape() == NULL) ? 0:
  1324. new TSShapeInstance(wheel->tire->getShape());
  1325. }
  1326. }
  1327. }
  1328. // After this is data that we only need if we're not the
  1329. // controlling client.
  1330. if (stream->readFlag())
  1331. return;
  1332. mBraking = stream->readFlag();
  1333. if (stream->readFlag())
  1334. {
  1335. Wheel* wend = &mWheel[mDataBlock->wheelCount];
  1336. for (Wheel* wheel = mWheel; wheel < wend; wheel++)
  1337. {
  1338. stream->read(&wheel->avel);
  1339. stream->read(&wheel->Dy);
  1340. stream->read(&wheel->Dx);
  1341. }
  1342. }
  1343. }
  1344. //----------------------------------------------------------------------------
  1345. // Console Methods
  1346. //----------------------------------------------------------------------------
  1347. //----------------------------------------------------------------------------
  1348. DefineEngineMethod( WheeledVehicle, setWheelSteering, bool, ( S32 wheel, F32 steering ),,
  1349. "@brief Set how much the wheel is affected by steering.\n\n"
  1350. "The steering factor controls how much the wheel is rotated by the vehicle "
  1351. "steering. For example, most cars would have their front wheels set to 1.0, "
  1352. "and their rear wheels set to 0 since only the front wheels should turn.\n\n"
  1353. "Negative values will turn the wheel in the opposite direction to the steering "
  1354. "angle.\n"
  1355. "@param wheel index of the wheel to set (hub node #)\n"
  1356. "@param steering steering factor from -1 (full inverse) to 1 (full)\n"
  1357. "@return true if successful, false if failed\n\n" )
  1358. {
  1359. if ( wheel >= 0 && wheel < object->getWheelCount() ) {
  1360. object->setWheelSteering( wheel, steering );
  1361. return true;
  1362. }
  1363. else
  1364. Con::warnf("setWheelSteering: wheel index %d out of bounds, vehicle has %d hubs",
  1365. wheel, object->getWheelCount());
  1366. return false;
  1367. }
  1368. DefineEngineMethod( WheeledVehicle, setWheelPowered, bool, ( S32 wheel, bool powered ),,
  1369. "@brief Set whether the wheel is powered (has torque applied from the engine).\n\n"
  1370. "A rear wheel drive car for example would set the front wheels to false, "
  1371. "and the rear wheels to true.\n"
  1372. "@param wheel index of the wheel to set (hub node #)\n"
  1373. "@param powered flag indicating whether to power the wheel or not\n"
  1374. "@return true if successful, false if failed\n\n" )
  1375. {
  1376. if ( wheel >= 0 && wheel < object->getWheelCount() ) {
  1377. object->setWheelPowered( wheel, powered );
  1378. return true;
  1379. }
  1380. else
  1381. Con::warnf("setWheelPowered: wheel index %d out of bounds, vehicle has %d hubs",
  1382. wheel, object->getWheelCount());
  1383. return false;
  1384. }
  1385. DefineEngineMethod( WheeledVehicle, setWheelTire, bool, ( S32 wheel, const char* tire ),,
  1386. "@brief Set the WheeledVehicleTire datablock for this wheel.\n"
  1387. "@param wheel index of the wheel to set (hub node #)\n"
  1388. "@param tire WheeledVehicleTire datablock\n"
  1389. "@return true if successful, false if failed\n\n"
  1390. "@tsexample\n"
  1391. "%obj.setWheelTire( 0, FrontTire );\n"
  1392. "@endtsexample\n" )
  1393. {
  1394. WheeledVehicleTire* tireObj = NULL;
  1395. if (wheel < 0 || wheel > object->getWheelCount())
  1396. {
  1397. Con::warnf("setWheelTire: invalid wheel index %d, vehicle has %d hubs", wheel, object->getWheelCount());
  1398. return false;
  1399. }
  1400. else if (!Sim::findObject(tire, tireObj))
  1401. {
  1402. Con::warnf("setWheelSpring: invalid spring %s", tire);
  1403. return false;
  1404. }
  1405. object->setWheelTire(wheel, tireObj);
  1406. return true;
  1407. }
  1408. DefineEngineMethod( WheeledVehicle, setWheelSpring, bool, ( S32 wheel, const char* spring ),,
  1409. "@brief Set the WheeledVehicleSpring datablock for this wheel.\n"
  1410. "@param wheel index of the wheel to set (hub node #)\n"
  1411. "@param spring WheeledVehicleSpring datablock\n"
  1412. "@return true if successful, false if failed\n\n"
  1413. "@tsexample\n"
  1414. "%obj.setWheelSpring( 0, FrontSpring );\n"
  1415. "@endtsexample\n" )
  1416. {
  1417. WheeledVehicleSpring* springObj = NULL;
  1418. if (wheel < 0 || wheel > object->getWheelCount())
  1419. {
  1420. Con::warnf("setWheelSpring: invalid wheel index %d, vehicle has %d hubs", wheel, object->getWheelCount());
  1421. return false;
  1422. }
  1423. else if (!Sim::findObject(spring, springObj))
  1424. {
  1425. Con::warnf("setWheelSpring: invalid spring %s", spring);
  1426. return false;
  1427. }
  1428. object->setWheelSpring(wheel, springObj);
  1429. return true;
  1430. }
  1431. DefineEngineMethod( WheeledVehicle, getWheelCount, S32, (),,
  1432. "@brief Get the number of wheels on this vehicle.\n"
  1433. "@return the number of wheels (equal to the number of hub nodes defined in the model)\n\n" )
  1434. {
  1435. return object->getWheelCount();
  1436. }