wheeledVehicle.cpp 54 KB

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