vehicle.cpp 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044
  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/vehicle.h"
  24. #include "math/mMath.h"
  25. #include "console/simBase.h"
  26. #include "console/console.h"
  27. #include "console/consoleTypes.h"
  28. #include "console/engineAPI.h"
  29. #include "collision/clippedPolyList.h"
  30. #include "collision/planeExtractor.h"
  31. #include "core/stream/bitStream.h"
  32. #include "core/dnet.h"
  33. #include "T3D/gameBase/gameConnection.h"
  34. #include "T3D/fx/cameraFXMgr.h"
  35. #include "ts/tsShapeInstance.h"
  36. #include "T3D/fx/particleEmitter.h"
  37. #include "sfx/sfxSystem.h"
  38. #include "sfx/sfxProfile.h"
  39. #include "sfx/sfxSource.h"
  40. #include "math/mathIO.h"
  41. #include "scene/sceneRenderState.h"
  42. #include "T3D/trigger.h"
  43. #include "T3D/item.h"
  44. #include "gfx/primBuilder.h"
  45. #include "gfx/gfxDrawUtil.h"
  46. #include "materials/materialDefinition.h"
  47. #include "T3D/physics/physicsPlugin.h"
  48. #include "T3D/physics/physicsBody.h"
  49. #include "T3D/physics/physicsCollision.h"
  50. namespace {
  51. static U32 sWorkingQueryBoxStaleThreshold = 10; // The maximum number of ticks that go by before
  52. // the mWorkingQueryBox is considered stale and
  53. // needs updating. Set to -1 to disable.
  54. static F32 sWorkingQueryBoxSizeMultiplier = 2.0f; // How much larger should the mWorkingQueryBox be
  55. // made when updating the working collision list.
  56. // The larger this number the less often the working list
  57. // will be updated due to motion, but any non-static shape
  58. // that moves into the query box will not be noticed.
  59. // Client prediction
  60. const S32 sMaxWarpTicks = 3; // Max warp duration in ticks
  61. const S32 sMaxPredictionTicks = 30; // Number of ticks to predict
  62. const F32 sVehicleGravity = -20;
  63. // Physics and collision constants
  64. static F32 sRestTol = 0.5; // % of gravity energy to be at rest
  65. static S32 sRestCount = 10; // Consecutive ticks before comming to rest
  66. } // namespace {}
  67. // Trigger objects that are not normally collided with.
  68. static U32 sTriggerMask = ItemObjectType |
  69. TriggerObjectType |
  70. CorpseObjectType;
  71. IMPLEMENT_CONOBJECT(VehicleData);
  72. ConsoleDocClass( VehicleData,
  73. "@brief Base properties shared by all Vehicles (FlyingVehicle, HoverVehicle, "
  74. "WheeledVehicle).\n\n"
  75. "This datablock defines properties shared by all Vehicle types, but should "
  76. "not be instantiated directly. Instead, set the desired properties in the "
  77. "FlyingVehicleData, HoverVehicleData or WheeledVehicleData datablock.\n"
  78. "@section VehicleData_damage Damage\n\n"
  79. "The VehicleData class extends the basic energy/damage functionality provided "
  80. "by ShapeBaseData to include damage from collisions, as well as particle "
  81. "emitters activated automatically when damage levels reach user specified "
  82. "thresholds.\n\n"
  83. "The example below shows how to setup a Vehicle to:\n"
  84. "<ul>\n"
  85. " <li>take damage when colliding with another object\n"
  86. " <li>emit gray smoke particles from two locations on the Vehicle when damaged above 50%</li>\n"
  87. " <li>emit black smoke particles from two locations on the Vehicle when damaged above 85%</li>\n"
  88. " <li>emit bubbles when any active damage emitter point is underwater</li>\n"
  89. "</ul>\n\n"
  90. "@tsexample\n"
  91. "// damage from collisions\n"
  92. "collDamageMultiplier = 0.05;\n"
  93. "collDamageThresholdVel = 15;\n\n"
  94. "// damage levels\n"
  95. "damageLevelTolerance[0] = 0.5;\n"
  96. "damageEmitter[0] = GraySmokeEmitter; // emitter used when damage is >= 50%\n"
  97. "damageLevelTolerance[1] = 0.85;\n"
  98. "damageEmitter[1] = BlackSmokeEmitter; // emitter used when damage is >= 85%\n"
  99. "damageEmitter[2] = DamageBubbleEmitter; // emitter used instead of damageEmitter[0:1]\n"
  100. " // when offset point is underwater\n"
  101. "// emit offsets (used for all active damage level emitters)\n"
  102. "damageEmitterOffset[0] = \"0.5 3 1\";\n"
  103. "damageEmitterOffset[1] = \"-0.5 3 1\";\n"
  104. "numDmgEmitterAreas = 2;\n"
  105. "@endtsexample\n"
  106. "@ingroup Vehicles\n"
  107. );
  108. IMPLEMENT_CALLBACK( VehicleData, onEnterLiquid, void, ( Vehicle* obj, F32 coverage, const char* type ), ( obj, coverage, type ),
  109. "Called when the vehicle enters liquid.\n"
  110. "@param obj the Vehicle object\n"
  111. "@param coverage percentage of the vehicle's bounding box covered by the liquid\n"
  112. "@param type type of liquid the vehicle has entered\n" );
  113. IMPLEMENT_CALLBACK( VehicleData, onLeaveLiquid, void, ( Vehicle* obj, const char* type ), ( obj, type ),
  114. "Called when the vehicle leaves liquid.\n"
  115. "@param obj the Vehicle object\n"
  116. "@param type type of liquid the vehicle has left\n" );
  117. //----------------------------------------------------------------------------
  118. VehicleData::VehicleData()
  119. {
  120. shadowEnable = true;
  121. shadowSize = 256;
  122. shadowProjectionDistance = 14.0f;
  123. body.friction = 0;
  124. body.restitution = 1;
  125. minImpactSpeed = 25;
  126. softImpactSpeed = 25;
  127. hardImpactSpeed = 50;
  128. minRollSpeed = 0;
  129. maxSteeringAngle = M_PI_F/4.0f; // 45 deg.
  130. cameraRoll = true;
  131. cameraLag = 0;
  132. cameraDecay = 0;
  133. cameraOffset = 0;
  134. minDrag = 0;
  135. maxDrag = 0;
  136. integration = 1;
  137. collisionTol = 0.1f;
  138. contactTol = 0.1f;
  139. massCenter.set(0,0,0);
  140. massBox.set(0,0,0);
  141. drag = 0.7f;
  142. density = 4;
  143. jetForce = 500;
  144. jetEnergyDrain = 0.8f;
  145. minJetEnergy = 1;
  146. steeringReturn = 0.0f;
  147. steeringReturnSpeedScale = 0.01f;
  148. powerSteering = false;
  149. for (S32 i = 0; i < Body::MaxSounds; i++)
  150. body.sound[i] = 0;
  151. dustEmitter = NULL;
  152. dustID = 0;
  153. triggerDustHeight = 3.0;
  154. dustHeight = 1.0;
  155. dMemset( damageEmitterList, 0, sizeof( damageEmitterList ) );
  156. dMemset( damageEmitterOffset, 0, sizeof( damageEmitterOffset ) );
  157. dMemset( damageEmitterIDList, 0, sizeof( damageEmitterIDList ) );
  158. dMemset( damageLevelTolerance, 0, sizeof( damageLevelTolerance ) );
  159. dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) );
  160. dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) );
  161. numDmgEmitterAreas = 0;
  162. splashFreqMod = 300.0;
  163. splashVelEpsilon = 0.50;
  164. exitSplashSoundVel = 2.0;
  165. softSplashSoundVel = 1.0;
  166. medSplashSoundVel = 2.0;
  167. hardSplashSoundVel = 3.0;
  168. dMemset(waterSound, 0, sizeof(waterSound));
  169. collDamageThresholdVel = 20;
  170. collDamageMultiplier = 0.05f;
  171. enablePhysicsRep = true;
  172. }
  173. //----------------------------------------------------------------------------
  174. bool VehicleData::preload(bool server, String &errorStr)
  175. {
  176. if (!Parent::preload(server, errorStr))
  177. return false;
  178. // Vehicle objects must define a collision detail
  179. if (!collisionDetails.size() || collisionDetails[0] == -1)
  180. {
  181. Con::errorf("VehicleData::preload failed: Vehicle models must define a collision-1 detail");
  182. errorStr = String::ToString("VehicleData: Couldn't load shape \"%s\"",shapeName);
  183. return false;
  184. }
  185. // Resolve objects transmitted from server
  186. if (!server) {
  187. for (S32 i = 0; i < Body::MaxSounds; i++)
  188. if (body.sound[i])
  189. Sim::findObject(SimObjectId((uintptr_t)body.sound[i]),body.sound[i]);
  190. }
  191. if( !dustEmitter && dustID != 0 )
  192. {
  193. if( !Sim::findObject( dustID, dustEmitter ) )
  194. {
  195. Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(dustEmitter): 0x%x", dustID );
  196. }
  197. }
  198. U32 i;
  199. for( i=0; i<VC_NUM_DAMAGE_EMITTERS; i++ )
  200. {
  201. if( !damageEmitterList[i] && damageEmitterIDList[i] != 0 )
  202. {
  203. if( !Sim::findObject( damageEmitterIDList[i], damageEmitterList[i] ) )
  204. {
  205. Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(damageEmitter): 0x%x", damageEmitterIDList[i] );
  206. }
  207. }
  208. }
  209. for( i=0; i<VC_NUM_SPLASH_EMITTERS; i++ )
  210. {
  211. if( !splashEmitterList[i] && splashEmitterIDList[i] != 0 )
  212. {
  213. if( !Sim::findObject( splashEmitterIDList[i], splashEmitterList[i] ) )
  214. {
  215. Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(splashEmitter): 0x%x", splashEmitterIDList[i] );
  216. }
  217. }
  218. }
  219. return true;
  220. }
  221. //----------------------------------------------------------------------------
  222. void VehicleData::packData(BitStream* stream)
  223. {
  224. S32 i;
  225. Parent::packData(stream);
  226. stream->write(body.restitution);
  227. stream->write(body.friction);
  228. for (i = 0; i < Body::MaxSounds; i++)
  229. if (stream->writeFlag(body.sound[i]))
  230. stream->writeRangedU32(packed? SimObjectId((uintptr_t)body.sound[i]):
  231. body.sound[i]->getId(),DataBlockObjectIdFirst,
  232. DataBlockObjectIdLast);
  233. stream->write(minImpactSpeed);
  234. stream->write(softImpactSpeed);
  235. stream->write(hardImpactSpeed);
  236. stream->write(minRollSpeed);
  237. stream->write(maxSteeringAngle);
  238. stream->write(maxDrag);
  239. stream->write(minDrag);
  240. stream->write(integration);
  241. stream->write(collisionTol);
  242. stream->write(contactTol);
  243. mathWrite(*stream,massCenter);
  244. mathWrite(*stream,massBox);
  245. stream->write(jetForce);
  246. stream->write(jetEnergyDrain);
  247. stream->write(minJetEnergy);
  248. stream->write(steeringReturn);
  249. stream->write(steeringReturnSpeedScale);
  250. stream->writeFlag(powerSteering);
  251. stream->writeFlag(cameraRoll);
  252. stream->write(cameraLag);
  253. stream->write(cameraDecay);
  254. stream->write(cameraOffset);
  255. stream->write( triggerDustHeight );
  256. stream->write( dustHeight );
  257. stream->write( numDmgEmitterAreas );
  258. stream->write(exitSplashSoundVel);
  259. stream->write(softSplashSoundVel);
  260. stream->write(medSplashSoundVel);
  261. stream->write(hardSplashSoundVel);
  262. stream->write(enablePhysicsRep);
  263. // write the water sound profiles
  264. for(i = 0; i < MaxSounds; i++)
  265. if(stream->writeFlag(waterSound[i]))
  266. stream->writeRangedU32(waterSound[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
  267. if (stream->writeFlag( dustEmitter ))
  268. {
  269. stream->writeRangedU32( dustEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
  270. }
  271. for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++)
  272. {
  273. if( stream->writeFlag( damageEmitterList[i] != NULL ) )
  274. {
  275. stream->writeRangedU32( damageEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
  276. }
  277. }
  278. for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++)
  279. {
  280. if( stream->writeFlag( splashEmitterList[i] != NULL ) )
  281. {
  282. stream->writeRangedU32( splashEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
  283. }
  284. }
  285. for (S32 j = 0; j < VC_NUM_DAMAGE_EMITTER_AREAS; j++)
  286. {
  287. stream->write( damageEmitterOffset[j].x );
  288. stream->write( damageEmitterOffset[j].y );
  289. stream->write( damageEmitterOffset[j].z );
  290. }
  291. for (S32 k = 0; k < VC_NUM_DAMAGE_LEVELS; k++)
  292. {
  293. stream->write( damageLevelTolerance[k] );
  294. }
  295. stream->write(splashFreqMod);
  296. stream->write(splashVelEpsilon);
  297. stream->write(collDamageThresholdVel);
  298. stream->write(collDamageMultiplier);
  299. }
  300. void VehicleData::unpackData(BitStream* stream)
  301. {
  302. Parent::unpackData(stream);
  303. stream->read(&body.restitution);
  304. stream->read(&body.friction);
  305. S32 i;
  306. for (i = 0; i < Body::MaxSounds; i++) {
  307. body.sound[i] = NULL;
  308. if (stream->readFlag())
  309. body.sound[i] = (SFXProfile*)stream->readRangedU32(DataBlockObjectIdFirst,
  310. DataBlockObjectIdLast);
  311. }
  312. stream->read(&minImpactSpeed);
  313. stream->read(&softImpactSpeed);
  314. stream->read(&hardImpactSpeed);
  315. stream->read(&minRollSpeed);
  316. stream->read(&maxSteeringAngle);
  317. stream->read(&maxDrag);
  318. stream->read(&minDrag);
  319. stream->read(&integration);
  320. stream->read(&collisionTol);
  321. stream->read(&contactTol);
  322. mathRead(*stream,&massCenter);
  323. mathRead(*stream,&massBox);
  324. stream->read(&jetForce);
  325. stream->read(&jetEnergyDrain);
  326. stream->read(&minJetEnergy);
  327. stream->read(&steeringReturn);
  328. stream->read(&steeringReturnSpeedScale);
  329. powerSteering = stream->readFlag();
  330. cameraRoll = stream->readFlag();
  331. stream->read(&cameraLag);
  332. stream->read(&cameraDecay);
  333. stream->read(&cameraOffset);
  334. stream->read( &triggerDustHeight );
  335. stream->read( &dustHeight );
  336. stream->read( &numDmgEmitterAreas );
  337. stream->read(&exitSplashSoundVel);
  338. stream->read(&softSplashSoundVel);
  339. stream->read(&medSplashSoundVel);
  340. stream->read(&hardSplashSoundVel);
  341. stream->read(&enablePhysicsRep);
  342. // write the water sound profiles
  343. for(i = 0; i < MaxSounds; i++)
  344. if(stream->readFlag())
  345. {
  346. U32 id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
  347. waterSound[i] = dynamic_cast<SFXProfile*>( Sim::findObject(id) );
  348. }
  349. if( stream->readFlag() )
  350. {
  351. dustID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
  352. }
  353. for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++)
  354. {
  355. if( stream->readFlag() )
  356. {
  357. damageEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
  358. }
  359. }
  360. for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++)
  361. {
  362. if( stream->readFlag() )
  363. {
  364. splashEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
  365. }
  366. }
  367. for( S32 j=0; j<VC_NUM_DAMAGE_EMITTER_AREAS; j++ )
  368. {
  369. stream->read( &damageEmitterOffset[j].x );
  370. stream->read( &damageEmitterOffset[j].y );
  371. stream->read( &damageEmitterOffset[j].z );
  372. }
  373. for( S32 k=0; k<VC_NUM_DAMAGE_LEVELS; k++ )
  374. {
  375. stream->read( &damageLevelTolerance[k] );
  376. }
  377. stream->read(&splashFreqMod);
  378. stream->read(&splashVelEpsilon);
  379. stream->read(&collDamageThresholdVel);
  380. stream->read(&collDamageMultiplier);
  381. }
  382. //----------------------------------------------------------------------------
  383. void VehicleData::initPersistFields()
  384. {
  385. addGroup("Physics");
  386. addField("enablePhysicsRep", TypeBool, Offset(enablePhysicsRep, VehicleData),
  387. "@brief Creates a representation of the object in the physics plugin.\n");
  388. endGroup("Physics");
  389. addField( "jetForce", TypeF32, Offset(jetForce, VehicleData),
  390. "@brief Additional force applied to the vehicle when it is jetting.\n\n"
  391. "For WheeledVehicles, the force is applied in the forward direction. For "
  392. "FlyingVehicles, the force is applied in the thrust direction." );
  393. addField( "jetEnergyDrain", TypeF32, Offset(jetEnergyDrain, VehicleData),
  394. "@brief Energy amount to drain for each tick the vehicle is jetting.\n\n"
  395. "Once the vehicle's energy level reaches 0, it will no longer be able to jet." );
  396. addField( "minJetEnergy", TypeF32, Offset(minJetEnergy, VehicleData),
  397. "Minimum vehicle energy level to begin jetting." );
  398. addField( "steeringReturn", TypeF32, Offset(steeringReturn, VehicleData),
  399. "Rate at which the vehicle's steering returns to forwards when it is moving." );
  400. addField( "steeringReturnSpeedScale", TypeF32, Offset(steeringReturnSpeedScale, VehicleData),
  401. "Amount of effect the vehicle's speed has on its rate of steering return." );
  402. addField( "powerSteering", TypeBool, Offset(powerSteering, VehicleData),
  403. "If true, steering does not auto-centre while the vehicle is being steered by its driver." );
  404. addField( "massCenter", TypePoint3F, Offset(massCenter, VehicleData),
  405. "Defines the vehicle's center of mass (offset from the origin of the model)." );
  406. addField( "massBox", TypePoint3F, Offset(massBox, VehicleData),
  407. "@brief Define the box used to estimate the vehicle's moment of inertia.\n\n"
  408. "Currently only used by WheeledVehicle; other vehicle types use a "
  409. "unit sphere to compute inertia." );
  410. addField( "bodyRestitution", TypeF32, Offset(body.restitution, VehicleData),
  411. "Collision 'bounciness'.\nNormally in the range 0 (not bouncy at all) to "
  412. "1 (100% bounciness)." );
  413. addField( "bodyFriction", TypeF32, Offset(body.friction, VehicleData),
  414. "Collision friction coefficient.\nHow well this object will slide against "
  415. "objects it collides with." );
  416. addField( "softImpactSound", TYPEID< SFXProfile >(), Offset(body.sound[Body::SoftImpactSound], VehicleData),
  417. "@brief Sound to play on a 'soft' impact.\n\n"
  418. "This sound is played if the impact speed is < hardImpactSpeed and >= "
  419. "softImpactSpeed.\n\n"
  420. "@see softImpactSpeed" );
  421. addField( "hardImpactSound", TYPEID< SFXProfile >(), Offset(body.sound[Body::HardImpactSound], VehicleData),
  422. "@brief Sound to play on a 'hard' impact.\n\n"
  423. "This sound is played if the impact speed >= hardImpactSpeed.\n\n"
  424. "@see hardImpactSpeed" );
  425. addField( "minImpactSpeed", TypeF32, Offset(minImpactSpeed, VehicleData),
  426. "Minimum collision speed for the onImpact callback to be invoked." );
  427. addField( "softImpactSpeed", TypeF32, Offset(softImpactSpeed, VehicleData),
  428. "Minimum collision speed for the softImpactSound to be played." );
  429. addField( "hardImpactSpeed", TypeF32, Offset(hardImpactSpeed, VehicleData),
  430. "Minimum collision speed for the hardImpactSound to be played." );
  431. addField( "minRollSpeed", TypeF32, Offset(minRollSpeed, VehicleData),
  432. "Unused" );
  433. addField( "maxSteeringAngle", TypeF32, Offset(maxSteeringAngle, VehicleData),
  434. "Maximum yaw (horizontal) and pitch (vertical) steering angle in radians." );
  435. addField( "maxDrag", TypeF32, Offset(maxDrag, VehicleData),
  436. "Maximum drag coefficient.\nCurrently unused." );
  437. addField( "minDrag", TypeF32, Offset(minDrag, VehicleData),
  438. "Minimum drag coefficient.\nCurrently only used by FlyingVehicle." );
  439. addField( "integration", TypeS32, Offset(integration, VehicleData),
  440. "Number of integration steps per tick.\nIncrease this to improve simulation "
  441. "stability (also increases simulation processing time)." );
  442. addField( "collisionTol", TypeF32, Offset(collisionTol, VehicleData),
  443. "Minimum distance between objects for them to be considered as colliding." );
  444. addField( "contactTol", TypeF32, Offset(contactTol, VehicleData),
  445. "Maximum relative velocity between objects for collisions to be resolved "
  446. "as contacts.\nVelocities greater than this are handled as collisions." );
  447. addField( "cameraRoll", TypeBool, Offset(cameraRoll, VehicleData),
  448. "If true, the camera will roll with the vehicle. If false, the camera will "
  449. "always have the positive Z axis as up." );
  450. addField( "cameraLag", TypeF32, Offset(cameraLag, VehicleData),
  451. "@brief How much the camera lags behind the vehicle depending on vehicle speed.\n\n"
  452. "Increasing this value will make the camera fall further behind the vehicle "
  453. "as it accelerates away.\n\n@see cameraDecay." );
  454. addField("cameraDecay", TypeF32, Offset(cameraDecay, VehicleData),
  455. "How quickly the camera moves back towards the vehicle when stopped.\n\n"
  456. "@see cameraLag." );
  457. addField("cameraOffset", TypeF32, Offset(cameraOffset, VehicleData),
  458. "Vertical (Z axis) height of the camera above the vehicle." );
  459. addField( "dustEmitter", TYPEID< ParticleEmitterData >(), Offset(dustEmitter, VehicleData),
  460. "Dust particle emitter.\n\n@see triggerDustHeight\n\n@see dustHeight");
  461. addField( "triggerDustHeight", TypeF32, Offset(triggerDustHeight, VehicleData),
  462. "@brief Maximum height above surface to emit dust particles.\n\n"
  463. "If the vehicle is less than triggerDustHeight above a static surface "
  464. "with a material that has 'showDust' set to true, the vehicle will emit "
  465. "particles from the dustEmitter." );
  466. addField( "dustHeight", TypeF32, Offset(dustHeight, VehicleData),
  467. "Height above ground at which to emit particles from the dustEmitter." );
  468. addField( "damageEmitter", TYPEID< ParticleEmitterData >(), Offset(damageEmitterList, VehicleData), VC_NUM_DAMAGE_EMITTERS,
  469. "@brief Array of particle emitters used to generate damage (dust, smoke etc) "
  470. "effects.\n\n"
  471. "Currently, the first two emitters (indices 0 and 1) are used when the damage "
  472. "level exceeds the associated damageLevelTolerance. The 3rd emitter is used "
  473. "when the emitter point is underwater.\n\n"
  474. "@see damageEmitterOffset" );
  475. addField( "damageEmitterOffset", TypePoint3F, Offset(damageEmitterOffset, VehicleData), VC_NUM_DAMAGE_EMITTER_AREAS,
  476. "@brief Object space \"x y z\" offsets used to emit particles for the "
  477. "active damageEmitter.\n\n"
  478. "@tsexample\n"
  479. "// damage levels\n"
  480. "damageLevelTolerance[0] = 0.5;\n"
  481. "damageEmitter[0] = SmokeEmitter;\n"
  482. "// emit offsets (used for all active damage level emitters)\n"
  483. "damageEmitterOffset[0] = \"0.5 3 1\";\n"
  484. "damageEmitterOffset[1] = \"-0.5 3 1\";\n"
  485. "numDmgEmitterAreas = 2;\n"
  486. "@endtsexample\n" );
  487. addField( "damageLevelTolerance", TypeF32, Offset(damageLevelTolerance, VehicleData), VC_NUM_DAMAGE_LEVELS,
  488. "@brief Damage levels (as a percentage of maxDamage) above which to begin "
  489. "emitting particles from the associated damageEmitter.\n\n"
  490. "Levels should be in order of increasing damage.\n\n"
  491. "@see damageEmitterOffset" );
  492. addField( "numDmgEmitterAreas", TypeF32, Offset(numDmgEmitterAreas, VehicleData),
  493. "Number of damageEmitterOffset values to use for each damageEmitter.\n\n"
  494. "@see damageEmitterOffset" );
  495. addField( "splashEmitter", TYPEID< ParticleEmitterData >(), Offset(splashEmitterList, VehicleData), VC_NUM_SPLASH_EMITTERS,
  496. "Array of particle emitters used to generate splash effects." );
  497. addField( "splashFreqMod", TypeF32, Offset(splashFreqMod, VehicleData),
  498. "@brief Number of splash particles to generate based on vehicle speed.\n\n"
  499. "This value is multiplied by the current speed to determine how many "
  500. "particles to generate each frame." );
  501. addField( "splashVelEpsilon", TypeF32, Offset(splashVelEpsilon, VehicleData),
  502. "Minimum speed when moving through water to generate splash particles." );
  503. addField( "exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, VehicleData),
  504. "Minimum velocity when leaving the water for the exitingWater sound to play." );
  505. addField( "softSplashSoundVelocity", TypeF32, Offset(softSplashSoundVel, VehicleData),
  506. "Minimum velocity when entering the water for the imapactWaterEasy sound "
  507. "to play.\n\n@see impactWaterEasy" );
  508. addField( "mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, VehicleData),
  509. "Minimum velocity when entering the water for the imapactWaterMedium sound "
  510. "to play.\n\n@see impactWaterMedium" );
  511. addField( "hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, VehicleData),
  512. "Minimum velocity when entering the water for the imapactWaterHard sound "
  513. "to play.\n\n@see impactWaterHard" );
  514. addField( "exitingWater", TYPEID< SFXProfile >(), Offset(waterSound[ExitWater], VehicleData),
  515. "Sound to play when exiting the water." );
  516. addField( "impactWaterEasy", TYPEID< SFXProfile >(), Offset(waterSound[ImpactSoft], VehicleData),
  517. "Sound to play when entering the water with speed >= softSplashSoundVelocity "
  518. "and < mediumSplashSoundVelocity." );
  519. addField( "impactWaterMedium", TYPEID< SFXProfile >(), Offset(waterSound[ImpactMedium], VehicleData),
  520. "Sound to play when entering the water with speed >= mediumSplashSoundVelocity "
  521. "and < hardSplashSoundVelocity." );
  522. addField( "impactWaterHard", TYPEID< SFXProfile >(), Offset(waterSound[ImpactHard], VehicleData),
  523. "Sound to play when entering the water with speed >= hardSplashSoundVelocity." );
  524. addField( "waterWakeSound", TYPEID< SFXProfile >(), Offset(waterSound[Wake], VehicleData),
  525. "Looping sound to play while moving through the water." );
  526. addField( "collDamageThresholdVel", TypeF32, Offset(collDamageThresholdVel, VehicleData),
  527. "Minimum collision velocity to cause damage to this vehicle.\nCurrently unused." );
  528. addField( "collDamageMultiplier", TypeF32, Offset(collDamageMultiplier, VehicleData),
  529. "@brief Damage to this vehicle after a collision (multiplied by collision "
  530. "velocity).\n\nCurrently unused." );
  531. Parent::initPersistFields();
  532. }
  533. //----------------------------------------------------------------------------
  534. //----------------------------------------------------------------------------
  535. //----------------------------------------------------------------------------
  536. IMPLEMENT_CONOBJECT(Vehicle);
  537. ConsoleDocClass( Vehicle,
  538. "@brief Base functionality shared by all Vehicles (FlyingVehicle, HoverVehicle, "
  539. "WheeledVehicle).\n\n"
  540. "This object implements functionality shared by all Vehicle types, but should "
  541. "not be instantiated directly. Create a FlyingVehicle, HoverVehicle, or "
  542. "WheeledVehicle instead.\n"
  543. "@note The model used for any Vehicle must include a collision mesh at detail "
  544. "size -1.\n"
  545. "@ingroup Vehicles\n"
  546. );
  547. Vehicle::Vehicle()
  548. {
  549. mDataBlock = 0;
  550. mTypeMask |= VehicleObjectType | DynamicShapeObjectType;
  551. mDelta.pos = Point3F(0,0,0);
  552. mDelta.posVec = Point3F(0,0,0);
  553. mDelta.warpTicks = mDelta.warpCount = 0;
  554. mDelta.dt = 1;
  555. mDelta.move = NullMove;
  556. mPredictionCount = 0;
  557. mDelta.cameraOffset.set(0,0,0);
  558. mDelta.cameraVec.set(0,0,0);
  559. mDelta.cameraRot.set(0,0,0);
  560. mDelta.cameraRotVec.set(0,0,0);
  561. mRigid.linPosition.set(0, 0, 0);
  562. mRigid.linVelocity.set(0, 0, 0);
  563. mRigid.angPosition.identity();
  564. mRigid.angVelocity.set(0, 0, 0);
  565. mRigid.linMomentum.set(0, 0, 0);
  566. mRigid.angMomentum.set(0, 0, 0);
  567. mContacts.clear();
  568. mSteering.set(0,0);
  569. mThrottle = 0;
  570. mJetting = false;
  571. mCameraOffset.set(0,0,0);
  572. dMemset( mDustEmitterList, 0, sizeof( mDustEmitterList ) );
  573. dMemset( mDamageEmitterList, 0, sizeof( mDamageEmitterList ) );
  574. dMemset( mSplashEmitterList, 0, sizeof( mSplashEmitterList ) );
  575. mDisableMove = false;
  576. restCount = 0;
  577. inLiquid = false;
  578. mWakeSound = NULL;
  579. mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
  580. mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
  581. mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
  582. mPhysicsRep = NULL;
  583. }
  584. U32 Vehicle::getCollisionMask()
  585. {
  586. AssertFatal(false, "Vehicle::getCollisionMask is pure virtual!");
  587. return 0;
  588. }
  589. Point3F Vehicle::getVelocity() const
  590. {
  591. return mRigid.linVelocity;
  592. }
  593. void Vehicle::_createPhysics()
  594. {
  595. SAFE_DELETE(mPhysicsRep);
  596. if (!PHYSICSMGR || !mDataBlock->enablePhysicsRep)
  597. return;
  598. TSShape *shape = mShapeInstance->getShape();
  599. PhysicsCollision *colShape = NULL;
  600. colShape = shape->buildColShape(false, getScale());
  601. if (colShape)
  602. {
  603. PhysicsWorld *world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
  604. mPhysicsRep = PHYSICSMGR->createBody();
  605. mPhysicsRep->init(colShape, 0, PhysicsBody::BF_KINEMATIC, this, world);
  606. mPhysicsRep->setTransform(getTransform());
  607. }
  608. }
  609. //----------------------------------------------------------------------------
  610. bool Vehicle::onAdd()
  611. {
  612. if (!Parent::onAdd())
  613. return false;
  614. mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
  615. mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
  616. // When loading from a mission script, the base SceneObject's transform
  617. // will have been set and needs to be transfered to the rigid body.
  618. mRigid.setTransform(mObjToWorld);
  619. // Initialize interpolation vars.
  620. mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
  621. mDelta.pos = mRigid.linPosition;
  622. mDelta.posVec = Point3F(0,0,0);
  623. // Create Emitters on the client
  624. if( isClientObject() )
  625. {
  626. if( mDataBlock->dustEmitter )
  627. {
  628. for( S32 i=0; i<VehicleData::VC_NUM_DUST_EMITTERS; i++ )
  629. {
  630. mDustEmitterList[i] = new ParticleEmitter;
  631. mDustEmitterList[i]->onNewDataBlock( mDataBlock->dustEmitter, false );
  632. if( !mDustEmitterList[i]->registerObject() )
  633. {
  634. Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() );
  635. delete mDustEmitterList[i];
  636. mDustEmitterList[i] = NULL;
  637. }
  638. }
  639. }
  640. U32 j;
  641. for( j=0; j<VehicleData::VC_NUM_DAMAGE_EMITTERS; j++ )
  642. {
  643. if( mDataBlock->damageEmitterList[j] )
  644. {
  645. mDamageEmitterList[j] = new ParticleEmitter;
  646. mDamageEmitterList[j]->onNewDataBlock( mDataBlock->damageEmitterList[j], false );
  647. if( !mDamageEmitterList[j]->registerObject() )
  648. {
  649. Con::warnf( ConsoleLogEntry::General, "Could not register damage emitter for class: %s", mDataBlock->getName() );
  650. delete mDamageEmitterList[j];
  651. mDamageEmitterList[j] = NULL;
  652. }
  653. }
  654. }
  655. for( j=0; j<VehicleData::VC_NUM_SPLASH_EMITTERS; j++ )
  656. {
  657. if( mDataBlock->splashEmitterList[j] )
  658. {
  659. mSplashEmitterList[j] = new ParticleEmitter;
  660. mSplashEmitterList[j]->onNewDataBlock( mDataBlock->splashEmitterList[j], false );
  661. if( !mSplashEmitterList[j]->registerObject() )
  662. {
  663. Con::warnf( ConsoleLogEntry::General, "Could not register splash emitter for class: %s", mDataBlock->getName() );
  664. delete mSplashEmitterList[j];
  665. mSplashEmitterList[j] = NULL;
  666. }
  667. }
  668. }
  669. }
  670. // Create a new convex.
  671. AssertFatal(mDataBlock->collisionDetails[0] != -1, "Error, a vehicle must have a collision-1 detail!");
  672. mConvex.mObject = this;
  673. mConvex.pShapeBase = this;
  674. mConvex.hullId = 0;
  675. mConvex.box = mObjBox;
  676. mConvex.box.minExtents.convolve(mObjScale);
  677. mConvex.box.maxExtents.convolve(mObjScale);
  678. mConvex.findNodeTransform();
  679. _createPhysics();
  680. return true;
  681. }
  682. void Vehicle::onRemove()
  683. {
  684. SAFE_DELETE(mPhysicsRep);
  685. U32 i=0;
  686. for( i=0; i<VehicleData::VC_NUM_DUST_EMITTERS; i++ )
  687. {
  688. if( mDustEmitterList[i] )
  689. {
  690. mDustEmitterList[i]->deleteWhenEmpty();
  691. mDustEmitterList[i] = NULL;
  692. }
  693. }
  694. for( i=0; i<VehicleData::VC_NUM_DAMAGE_EMITTERS; i++ )
  695. {
  696. if( mDamageEmitterList[i] )
  697. {
  698. mDamageEmitterList[i]->deleteWhenEmpty();
  699. mDamageEmitterList[i] = NULL;
  700. }
  701. }
  702. for( i=0; i<VehicleData::VC_NUM_SPLASH_EMITTERS; i++ )
  703. {
  704. if( mSplashEmitterList[i] )
  705. {
  706. mSplashEmitterList[i]->deleteWhenEmpty();
  707. mSplashEmitterList[i] = NULL;
  708. }
  709. }
  710. mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
  711. mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
  712. Parent::onRemove();
  713. }
  714. //----------------------------------------------------------------------------
  715. void Vehicle::processTick(const Move* move)
  716. {
  717. PROFILE_SCOPE( Vehicle_ProcessTick );
  718. Parent::processTick(move);
  719. if ( isMounted() )
  720. return;
  721. // Warp to catch up to server
  722. if (mDelta.warpCount < mDelta.warpTicks)
  723. {
  724. mDelta.warpCount++;
  725. // Set new pos.
  726. mObjToWorld.getColumn(3,&mDelta.pos);
  727. mDelta.pos += mDelta.warpOffset;
  728. mDelta.rot[0] = mDelta.rot[1];
  729. mDelta.rot[1].interpolate(mDelta.warpRot[0],mDelta.warpRot[1],F32(mDelta.warpCount)/mDelta.warpTicks);
  730. setPosition(mDelta.pos,mDelta.rot[1]);
  731. // Pos backstepping
  732. mDelta.posVec.x = -mDelta.warpOffset.x;
  733. mDelta.posVec.y = -mDelta.warpOffset.y;
  734. mDelta.posVec.z = -mDelta.warpOffset.z;
  735. }
  736. else
  737. {
  738. if (!move)
  739. {
  740. if (isGhost())
  741. {
  742. // If we haven't run out of prediction time,
  743. // predict using the last known move.
  744. if (mPredictionCount-- <= 0)
  745. return;
  746. move = &mDelta.move;
  747. }
  748. else
  749. move = &NullMove;
  750. }
  751. // Process input move
  752. updateMove(move);
  753. // Save current rigid state interpolation
  754. mDelta.posVec = mRigid.linPosition;
  755. mDelta.rot[0] = mRigid.angPosition;
  756. // Update the physics based on the integration rate
  757. S32 count = mDataBlock->integration;
  758. --mWorkingQueryBoxCountDown;
  759. updateWorkingCollisionSet(getCollisionMask());
  760. for (U32 i = 0; i < count; i++)
  761. updatePos(TickSec / count);
  762. // Wrap up interpolation info
  763. mDelta.pos = mRigid.linPosition;
  764. mDelta.posVec -= mRigid.linPosition;
  765. mDelta.rot[1] = mRigid.angPosition;
  766. // Update container database
  767. setPosition(mRigid.linPosition, mRigid.angPosition);
  768. setMaskBits(PositionMask);
  769. updateContainer();
  770. //TODO: Only update when position has actually changed
  771. //no need to check if mDataBlock->enablePhysicsRep is false as mPhysicsRep will be NULL if it is
  772. if (mPhysicsRep)
  773. mPhysicsRep->moveKinematicTo(getTransform());
  774. }
  775. }
  776. void Vehicle::interpolateTick(F32 dt)
  777. {
  778. PROFILE_SCOPE( Vehicle_InterpolateTick );
  779. Parent::interpolateTick(dt);
  780. if ( isMounted() )
  781. return;
  782. if(dt == 0.0f)
  783. setRenderPosition(mDelta.pos, mDelta.rot[1]);
  784. else
  785. {
  786. QuatF rot;
  787. rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt);
  788. Point3F pos = mDelta.pos + mDelta.posVec * dt;
  789. setRenderPosition(pos,rot);
  790. }
  791. mDelta.dt = dt;
  792. }
  793. void Vehicle::advanceTime(F32 dt)
  794. {
  795. PROFILE_SCOPE( Vehicle_AdvanceTime );
  796. Parent::advanceTime(dt);
  797. updateLiftoffDust( dt );
  798. updateDamageSmoke( dt );
  799. updateFroth(dt);
  800. // Update 3rd person camera offset. Camera update is done
  801. // here as it's a client side only animation.
  802. mCameraOffset -=
  803. (mCameraOffset * mDataBlock->cameraDecay +
  804. mRigid.linVelocity * mDataBlock->cameraLag) * dt;
  805. }
  806. //----------------------------------------------------------------------------
  807. bool Vehicle::onNewDataBlock(GameBaseData* dptr,bool reload)
  808. {
  809. mDataBlock = dynamic_cast<VehicleData*>(dptr);
  810. if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
  811. return false;
  812. // Update Rigid Info
  813. mRigid.mass = mDataBlock->mass;
  814. mRigid.oneOverMass = 1 / mRigid.mass;
  815. mRigid.friction = mDataBlock->body.friction;
  816. mRigid.restitution = mDataBlock->body.restitution;
  817. mRigid.setCenterOfMass(mDataBlock->massCenter);
  818. // Ignores massBox, just set sphere for now. Derived objects
  819. // can set what they want.
  820. mRigid.setObjectInertia();
  821. if (isGhost())
  822. {
  823. // Create the sound ahead of time. This reduces runtime
  824. // costs and makes the system easier to understand.
  825. SFX_DELETE( mWakeSound );
  826. if ( mDataBlock->waterSound[VehicleData::Wake] )
  827. mWakeSound = SFX->createSource( mDataBlock->waterSound[VehicleData::Wake], &getTransform() );
  828. }
  829. return true;
  830. }
  831. //----------------------------------------------------------------------------
  832. void Vehicle::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
  833. {
  834. *min = mDataBlock->cameraMinDist;
  835. *max = mDataBlock->cameraMaxDist;
  836. off->set(0,0,mDataBlock->cameraOffset);
  837. rot->identity();
  838. }
  839. //----------------------------------------------------------------------------
  840. void Vehicle::getCameraTransform(F32* pos,MatrixF* mat)
  841. {
  842. // Returns camera to world space transform
  843. // Handles first person / third person camera position
  844. if (isServerObject() && mShapeInstance)
  845. mShapeInstance->animateNodeSubtrees(true);
  846. if (*pos == 0) {
  847. getRenderEyeTransform(mat);
  848. return;
  849. }
  850. // Get the shape's camera parameters.
  851. F32 min,max;
  852. MatrixF rot;
  853. Point3F offset;
  854. getCameraParameters(&min,&max,&offset,&rot);
  855. // Start with the current eye position
  856. MatrixF eye;
  857. getRenderEyeTransform(&eye);
  858. // Build a transform that points along the eye axis
  859. // but where the Z axis is always up.
  860. if (mDataBlock->cameraRoll)
  861. mat->mul(eye,rot);
  862. else
  863. {
  864. MatrixF cam(1);
  865. VectorF x,y,z(0,0,1);
  866. eye.getColumn(1, &y);
  867. mCross(y, z, &x);
  868. x.normalize();
  869. mCross(x, y, &z);
  870. z.normalize();
  871. cam.setColumn(0,x);
  872. cam.setColumn(1,y);
  873. cam.setColumn(2,z);
  874. mat->mul(cam,rot);
  875. }
  876. // Camera is positioned straight back along the eye's -Y axis.
  877. // A ray is cast to make sure the camera doesn't go through
  878. // anything solid.
  879. VectorF vp,vec;
  880. vp.x = vp.z = 0;
  881. vp.y = -(max - min) * *pos;
  882. eye.mulV(vp,&vec);
  883. // Use the camera node as the starting position if it exists.
  884. Point3F osp,sp;
  885. if (mDataBlock->cameraNode != -1)
  886. {
  887. mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);
  888. getRenderTransform().mulP(osp,&sp);
  889. }
  890. else
  891. eye.getColumn(3,&sp);
  892. // Make sure we don't hit ourself...
  893. disableCollision();
  894. if (isMounted())
  895. getObjectMount()->disableCollision();
  896. // Cast the ray into the container database to see if we're going
  897. // to hit anything.
  898. RayInfo collision;
  899. Point3F ep = sp + vec + offset + mCameraOffset;
  900. if (mContainer->castRay(sp, ep,
  901. ~(WaterObjectType | GameBaseObjectType | DefaultObjectType | sTriggerMask),
  902. &collision) == true) {
  903. // Shift the collision point back a little to try and
  904. // avoid clipping against the front camera plane.
  905. F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1;
  906. if (t > 0.0f)
  907. ep = sp + offset + mCameraOffset + (vec * t);
  908. else
  909. eye.getColumn(3,&ep);
  910. }
  911. mat->setColumn(3,ep);
  912. // Re-enable our collision.
  913. if (isMounted())
  914. getObjectMount()->enableCollision();
  915. enableCollision();
  916. // Apply Camera FX.
  917. mat->mul( gCamFXMgr.getTrans() );
  918. }
  919. //----------------------------------------------------------------------------
  920. void Vehicle::getVelocity(const Point3F& r, Point3F* v)
  921. {
  922. mRigid.getVelocity(r, v);
  923. }
  924. void Vehicle::applyImpulse(const Point3F &pos, const Point3F &impulse)
  925. {
  926. Point3F r;
  927. mRigid.getOriginVector(pos,&r);
  928. mRigid.applyImpulse(r, impulse);
  929. }
  930. //----------------------------------------------------------------------------
  931. void Vehicle::updateMove(const Move* move)
  932. {
  933. PROFILE_SCOPE( Vehicle_UpdateMove );
  934. mDelta.move = *move;
  935. // Image Triggers
  936. if (mDamageState == Enabled) {
  937. setImageTriggerState(0,move->trigger[0]);
  938. setImageTriggerState(1,move->trigger[1]);
  939. }
  940. // Throttle
  941. if(!mDisableMove)
  942. mThrottle = move->y;
  943. // Steering
  944. if (move != &NullMove) {
  945. F32 y = move->yaw;
  946. mSteering.x = mClampF(mSteering.x + y,-mDataBlock->maxSteeringAngle,
  947. mDataBlock->maxSteeringAngle);
  948. F32 p = move->pitch;
  949. mSteering.y = mClampF(mSteering.y + p,-mDataBlock->maxSteeringAngle,
  950. mDataBlock->maxSteeringAngle);
  951. }
  952. else {
  953. mSteering.x = 0;
  954. mSteering.y = 0;
  955. }
  956. // Steering return
  957. if(mDataBlock->steeringReturn > 0.0f &&
  958. (!mDataBlock->powerSteering || (move->yaw == 0.0f && move->pitch == 0.0f)))
  959. {
  960. Point2F returnAmount(mSteering.x * mDataBlock->steeringReturn * TickSec,
  961. mSteering.y * mDataBlock->steeringReturn * TickSec);
  962. if(mDataBlock->steeringReturnSpeedScale > 0.0f)
  963. {
  964. Point3F vel;
  965. mWorldToObj.mulV(getVelocity(), &vel);
  966. returnAmount += Point2F(mSteering.x * vel.y * mDataBlock->steeringReturnSpeedScale * TickSec,
  967. mSteering.y * vel.y * mDataBlock->steeringReturnSpeedScale * TickSec);
  968. }
  969. mSteering -= returnAmount;
  970. }
  971. // Jetting flag
  972. if (move->trigger[3]) {
  973. if (!mJetting && getEnergyLevel() >= mDataBlock->minJetEnergy)
  974. mJetting = true;
  975. if (mJetting) {
  976. F32 newEnergy = getEnergyLevel() - mDataBlock->jetEnergyDrain;
  977. if (newEnergy < 0) {
  978. newEnergy = 0;
  979. mJetting = false;
  980. }
  981. setEnergyLevel(newEnergy);
  982. }
  983. }
  984. else
  985. mJetting = false;
  986. }
  987. //----------------------------------------------------------------------------
  988. void Vehicle::setPosition(const Point3F& pos,const QuatF& rot)
  989. {
  990. MatrixF mat;
  991. rot.setMatrix(&mat);
  992. mat.setColumn(3,pos);
  993. Parent::setTransform(mat);
  994. }
  995. void Vehicle::setRenderPosition(const Point3F& pos, const QuatF& rot)
  996. {
  997. MatrixF mat;
  998. rot.setMatrix(&mat);
  999. mat.setColumn(3,pos);
  1000. Parent::setRenderTransform(mat);
  1001. }
  1002. void Vehicle::setTransform(const MatrixF& newMat)
  1003. {
  1004. mRigid.setTransform(newMat);
  1005. Parent::setTransform(newMat);
  1006. mRigid.atRest = false;
  1007. mContacts.clear();
  1008. }
  1009. //-----------------------------------------------------------------------------
  1010. void Vehicle::disableCollision()
  1011. {
  1012. Parent::disableCollision();
  1013. for (SceneObject* ptr = getMountList(); ptr; ptr = ptr->getMountLink())
  1014. ptr->disableCollision();
  1015. }
  1016. void Vehicle::enableCollision()
  1017. {
  1018. Parent::enableCollision();
  1019. for (SceneObject* ptr = getMountList(); ptr; ptr = ptr->getMountLink())
  1020. ptr->enableCollision();
  1021. }
  1022. //----------------------------------------------------------------------------
  1023. /** Update the physics
  1024. */
  1025. void Vehicle::updatePos(F32 dt)
  1026. {
  1027. PROFILE_SCOPE( Vehicle_UpdatePos );
  1028. Point3F origVelocity = mRigid.linVelocity;
  1029. // Update internal forces acting on the body.
  1030. mRigid.clearForces();
  1031. updateForces(dt);
  1032. // Update collision information based on our current pos.
  1033. bool collided = false;
  1034. if (!mRigid.atRest) {
  1035. collided = updateCollision(dt);
  1036. // Now that all the forces have been processed, lets
  1037. // see if we're at rest. Basically, if the kinetic energy of
  1038. // the vehicles is less than some percentage of the energy added
  1039. // by gravity for a short period, we're considered at rest.
  1040. // This should really be part of the rigid class...
  1041. if (mCollisionList.getCount())
  1042. {
  1043. F32 k = mRigid.getKineticEnergy();
  1044. F32 G = sVehicleGravity * dt;
  1045. F32 Kg = 0.5 * mRigid.mass * G * G;
  1046. if (k < sRestTol * Kg && ++restCount > sRestCount)
  1047. mRigid.setAtRest();
  1048. }
  1049. else
  1050. restCount = 0;
  1051. }
  1052. // Integrate forward
  1053. if (!mRigid.atRest)
  1054. mRigid.integrate(dt);
  1055. // Deal with client and server scripting, sounds, etc.
  1056. if (isServerObject()) {
  1057. // Check triggers and other objects that we normally don't
  1058. // collide with. This function must be called before notifyCollision
  1059. // as it will queue collision.
  1060. checkTriggers();
  1061. // Invoke the onCollision notify callback for all the objects
  1062. // we've just hit.
  1063. notifyCollision();
  1064. // Server side impact script callback
  1065. if (collided) {
  1066. VectorF collVec = mRigid.linVelocity - origVelocity;
  1067. F32 collSpeed = collVec.len();
  1068. if (collSpeed > mDataBlock->minImpactSpeed)
  1069. onImpact(collVec);
  1070. }
  1071. // Water script callbacks
  1072. if (!inLiquid && mWaterCoverage != 0.0f) {
  1073. mDataBlock->onEnterLiquid_callback( this, mWaterCoverage, mLiquidType.c_str() );
  1074. inLiquid = true;
  1075. }
  1076. else if (inLiquid && mWaterCoverage == 0.0f) {
  1077. mDataBlock->onLeaveLiquid_callback( this, mLiquidType.c_str() );
  1078. inLiquid = false;
  1079. }
  1080. }
  1081. else {
  1082. // Play impact sounds on the client.
  1083. if (collided) {
  1084. F32 collSpeed = (mRigid.linVelocity - origVelocity).len();
  1085. S32 impactSound = -1;
  1086. if (collSpeed >= mDataBlock->hardImpactSpeed)
  1087. impactSound = VehicleData::Body::HardImpactSound;
  1088. else
  1089. if (collSpeed >= mDataBlock->softImpactSpeed)
  1090. impactSound = VehicleData::Body::SoftImpactSound;
  1091. if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL)
  1092. SFX->playOnce( mDataBlock->body.sound[impactSound], &getTransform() );
  1093. }
  1094. // Water volume sounds
  1095. F32 vSpeed = getVelocity().len();
  1096. if (!inLiquid && mWaterCoverage >= 0.8f) {
  1097. if (vSpeed >= mDataBlock->hardSplashSoundVel)
  1098. SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactHard], &getTransform() );
  1099. else
  1100. if (vSpeed >= mDataBlock->medSplashSoundVel)
  1101. SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactMedium], &getTransform() );
  1102. else
  1103. if (vSpeed >= mDataBlock->softSplashSoundVel)
  1104. SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactSoft], &getTransform() );
  1105. inLiquid = true;
  1106. }
  1107. else
  1108. if(inLiquid && mWaterCoverage < 0.8f) {
  1109. if (vSpeed >= mDataBlock->exitSplashSoundVel)
  1110. SFX->playOnce( mDataBlock->waterSound[VehicleData::ExitWater], &getTransform() );
  1111. inLiquid = false;
  1112. }
  1113. }
  1114. }
  1115. //----------------------------------------------------------------------------
  1116. void Vehicle::updateForces(F32 /*dt*/)
  1117. {
  1118. // Nothing here.
  1119. }
  1120. //-----------------------------------------------------------------------------
  1121. /** Update collision information
  1122. Update the convex state and check for collisions. If the object is in
  1123. collision, impact and contact forces are generated.
  1124. */
  1125. bool Vehicle::updateCollision(F32 dt)
  1126. {
  1127. PROFILE_SCOPE( Vehicle_UpdateCollision );
  1128. // Update collision information
  1129. MatrixF mat,cmat;
  1130. mConvex.transform = &mat;
  1131. mRigid.getTransform(&mat);
  1132. cmat = mConvex.getTransform();
  1133. mCollisionList.clear();
  1134. CollisionState *state = mConvex.findClosestState(cmat, getScale(), mDataBlock->collisionTol);
  1135. if (state && state->dist <= mDataBlock->collisionTol)
  1136. {
  1137. //resolveDisplacement(ns,state,dt);
  1138. mConvex.getCollisionInfo(cmat, getScale(), &mCollisionList, mDataBlock->collisionTol);
  1139. }
  1140. // Resolve collisions
  1141. bool collided = resolveCollision(mRigid,mCollisionList);
  1142. resolveContacts(mRigid,mCollisionList,dt);
  1143. return collided;
  1144. }
  1145. //----------------------------------------------------------------------------
  1146. /** Resolve collision impacts
  1147. Handle collision impacts, as opposed to contacts. Impulses are calculated based
  1148. on standard collision resolution formulas.
  1149. */
  1150. bool Vehicle::resolveCollision(Rigid& ns,CollisionList& cList)
  1151. {
  1152. PROFILE_SCOPE( Vehicle_ResolveCollision );
  1153. // Apply impulses to resolve collision
  1154. bool collided = false;
  1155. for (S32 i = 0; i < cList.getCount(); i++)
  1156. {
  1157. Collision& c = cList[i];
  1158. if (c.distance < mDataBlock->collisionTol)
  1159. {
  1160. // Velocity into surface
  1161. Point3F v,r;
  1162. ns.getOriginVector(c.point,&r);
  1163. ns.getVelocity(r,&v);
  1164. F32 vn = mDot(v,c.normal);
  1165. // Only interested in velocities greater than sContactTol,
  1166. // velocities less than that will be dealt with as contacts
  1167. // "constraints".
  1168. if (vn < -mDataBlock->contactTol)
  1169. {
  1170. // Apply impulses to the rigid body to keep it from
  1171. // penetrating the surface.
  1172. ns.resolveCollision(cList[i].point,
  1173. cList[i].normal);
  1174. collided = true;
  1175. // Keep track of objects we collide with
  1176. if (!isGhost() && c.object->getTypeMask() & ShapeBaseObjectType)
  1177. {
  1178. ShapeBase* col = static_cast<ShapeBase*>(c.object);
  1179. queueCollision(col,v - col->getVelocity());
  1180. }
  1181. }
  1182. }
  1183. }
  1184. return collided;
  1185. }
  1186. //----------------------------------------------------------------------------
  1187. /** Resolve contact forces
  1188. Resolve contact forces using the "penalty" method. Forces are generated based
  1189. on the depth of penetration and the moment of inertia at the point of contact.
  1190. */
  1191. bool Vehicle::resolveContacts(Rigid& ns,CollisionList& cList,F32 dt)
  1192. {
  1193. PROFILE_SCOPE( Vehicle_ResolveContacts );
  1194. // Use spring forces to manage contact constraints.
  1195. bool collided = false;
  1196. Point3F t,p(0,0,0),l(0,0,0);
  1197. for (S32 i = 0; i < cList.getCount(); i++)
  1198. {
  1199. const Collision& c = cList[i];
  1200. if (c.distance < mDataBlock->collisionTol)
  1201. {
  1202. // Velocity into the surface
  1203. Point3F v,r;
  1204. ns.getOriginVector(c.point,&r);
  1205. ns.getVelocity(r,&v);
  1206. F32 vn = mDot(v,c.normal);
  1207. // Only interested in velocities less than mDataBlock->contactTol,
  1208. // velocities greater than that are dealt with as collisions.
  1209. if (mFabs(vn) < mDataBlock->contactTol)
  1210. {
  1211. collided = true;
  1212. // Penetration force. This is actually a spring which
  1213. // will seperate the body from the collision surface.
  1214. F32 zi = 2 * mFabs(mRigid.getZeroImpulse(r,c.normal));
  1215. F32 s = (mDataBlock->collisionTol - c.distance) * zi - ((vn / mDataBlock->contactTol) * zi);
  1216. Point3F f = c.normal * s;
  1217. // Friction impulse, calculated as a function of the
  1218. // amount of force it would take to stop the motion
  1219. // perpendicular to the normal.
  1220. Point3F uv = v - (c.normal * vn);
  1221. F32 ul = uv.len();
  1222. if (s > 0 && ul)
  1223. {
  1224. uv /= -ul;
  1225. F32 u = ul * ns.getZeroImpulse(r,uv);
  1226. s *= mRigid.friction;
  1227. if (u > s)
  1228. u = s;
  1229. f += uv * u;
  1230. }
  1231. // Accumulate forces
  1232. p += f;
  1233. mCross(r,f,&t);
  1234. l += t;
  1235. }
  1236. }
  1237. }
  1238. // Contact constraint forces act over time...
  1239. ns.linMomentum += p * dt;
  1240. ns.angMomentum += l * dt;
  1241. ns.updateVelocity();
  1242. return true;
  1243. }
  1244. //----------------------------------------------------------------------------
  1245. bool Vehicle::resolveDisplacement(Rigid& ns,CollisionState *state, F32 dt)
  1246. {
  1247. PROFILE_SCOPE( Vehicle_ResolveDisplacement );
  1248. SceneObject* obj = (state->a->getObject() == this)?
  1249. state->b->getObject(): state->a->getObject();
  1250. if (obj->isDisplacable() && ((obj->getTypeMask() & ShapeBaseObjectType) != 0))
  1251. {
  1252. // Try to displace the object by the amount we're trying to move
  1253. Point3F objNewMom = ns.linVelocity * obj->getMass() * 1.1f;
  1254. Point3F objOldMom = obj->getMomentum();
  1255. Point3F objNewVel = objNewMom / obj->getMass();
  1256. Point3F myCenter;
  1257. Point3F theirCenter;
  1258. getWorldBox().getCenter(&myCenter);
  1259. obj->getWorldBox().getCenter(&theirCenter);
  1260. if (mDot(myCenter - theirCenter, objNewMom) >= 0.0f || objNewVel.len() < 0.01)
  1261. {
  1262. objNewMom = (theirCenter - myCenter);
  1263. objNewMom.normalize();
  1264. objNewMom *= 1.0f * obj->getMass();
  1265. objNewVel = objNewMom / obj->getMass();
  1266. }
  1267. obj->setMomentum(objNewMom);
  1268. if (obj->displaceObject(objNewVel * 1.1f * dt) == true)
  1269. {
  1270. // Queue collision and change in velocity
  1271. VectorF dv = (objOldMom - objNewMom) / obj->getMass();
  1272. queueCollision(static_cast<ShapeBase*>(obj), dv);
  1273. return true;
  1274. }
  1275. }
  1276. return false;
  1277. }
  1278. //----------------------------------------------------------------------------
  1279. void Vehicle::updateWorkingCollisionSet(const U32 mask)
  1280. {
  1281. PROFILE_SCOPE( Vehicle_UpdateWorkingCollisionSet );
  1282. // First, we need to adjust our velocity for possible acceleration. It is assumed
  1283. // that we will never accelerate more than 20 m/s for gravity, plus 30 m/s for
  1284. // jetting, and an equivalent 10 m/s for vehicle accel. We also assume that our
  1285. // working list is updated on a Tick basis, which means we only expand our box by
  1286. // the possible movement in that tick, plus some extra for caching purposes
  1287. Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale());
  1288. F32 len = (mRigid.linVelocity.len() + 50) * TickSec;
  1289. F32 l = (len * 1.1) + 0.1; // fudge factor
  1290. convexBox.minExtents -= Point3F(l, l, l);
  1291. convexBox.maxExtents += Point3F(l, l, l);
  1292. // Check to see if it is actually necessary to construct the new working list,
  1293. // or if we can use the cached version from the last query. We use the x
  1294. // component of the min member of the mWorkingQueryBox, which is lame, but
  1295. // it works ok.
  1296. bool updateSet = false;
  1297. // Check containment
  1298. if ((sWorkingQueryBoxStaleThreshold == -1 || mWorkingQueryBoxCountDown > 0) && mWorkingQueryBox.minExtents.x != -1e9f)
  1299. {
  1300. if (mWorkingQueryBox.isContained(convexBox) == false)
  1301. // Needed region is outside the cached region. Update it.
  1302. updateSet = true;
  1303. }
  1304. else
  1305. {
  1306. // Must update
  1307. updateSet = true;
  1308. }
  1309. // Actually perform the query, if necessary
  1310. if (updateSet == true)
  1311. {
  1312. mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
  1313. const Point3F lPoint( sWorkingQueryBoxSizeMultiplier * l );
  1314. mWorkingQueryBox = convexBox;
  1315. mWorkingQueryBox.minExtents -= lPoint;
  1316. mWorkingQueryBox.maxExtents += lPoint;
  1317. disableCollision();
  1318. mConvex.updateWorkingList(mWorkingQueryBox, mask);
  1319. enableCollision();
  1320. }
  1321. }
  1322. //----------------------------------------------------------------------------
  1323. /** Check collisions with trigger and items
  1324. Perform a container search using the current bounding box
  1325. of the main body, wheels are not included. This method should
  1326. only be called on the server.
  1327. */
  1328. void Vehicle::checkTriggers()
  1329. {
  1330. Box3F bbox = mConvex.getBoundingBox(getTransform(), getScale());
  1331. gServerContainer.findObjects(bbox,sTriggerMask,findCallback,this);
  1332. }
  1333. /** The callback used in by the checkTriggers() method.
  1334. The checkTriggers method uses a container search which will
  1335. invoke this callback on each obj that matches.
  1336. */
  1337. void Vehicle::findCallback(SceneObject* obj,void *key)
  1338. {
  1339. Vehicle* vehicle = reinterpret_cast<Vehicle*>(key);
  1340. U32 objectMask = obj->getTypeMask();
  1341. // Check: triggers, corpses and items, basically the same things
  1342. // that the player class checks for
  1343. if (objectMask & TriggerObjectType) {
  1344. Trigger* pTrigger = static_cast<Trigger*>(obj);
  1345. pTrigger->potentialEnterObject(vehicle);
  1346. }
  1347. else if (objectMask & CorpseObjectType) {
  1348. ShapeBase* col = static_cast<ShapeBase*>(obj);
  1349. vehicle->queueCollision(col,vehicle->getVelocity() - col->getVelocity());
  1350. }
  1351. else if (objectMask & ItemObjectType) {
  1352. Item* item = static_cast<Item*>(obj);
  1353. if (vehicle != item->getCollisionObject())
  1354. vehicle->queueCollision(item,vehicle->getVelocity() - item->getVelocity());
  1355. }
  1356. }
  1357. //----------------------------------------------------------------------------
  1358. void Vehicle::writePacketData(GameConnection *connection, BitStream *stream)
  1359. {
  1360. Parent::writePacketData(connection, stream);
  1361. mathWrite(*stream, mSteering);
  1362. mathWrite(*stream, mRigid.linPosition);
  1363. mathWrite(*stream, mRigid.angPosition);
  1364. mathWrite(*stream, mRigid.linMomentum);
  1365. mathWrite(*stream, mRigid.angMomentum);
  1366. stream->writeFlag(mRigid.atRest);
  1367. stream->writeFlag(mContacts.getCount() == 0);
  1368. stream->writeFlag(mDisableMove);
  1369. stream->setCompressionPoint(mRigid.linPosition);
  1370. }
  1371. void Vehicle::readPacketData(GameConnection *connection, BitStream *stream)
  1372. {
  1373. Parent::readPacketData(connection, stream);
  1374. mathRead(*stream, &mSteering);
  1375. mathRead(*stream, &mRigid.linPosition);
  1376. mathRead(*stream, &mRigid.angPosition);
  1377. mathRead(*stream, &mRigid.linMomentum);
  1378. mathRead(*stream, &mRigid.angMomentum);
  1379. mRigid.atRest = stream->readFlag();
  1380. if (stream->readFlag())
  1381. mContacts.clear();
  1382. mRigid.updateInertialTensor();
  1383. mRigid.updateVelocity();
  1384. mRigid.updateCenterOfMass();
  1385. mDisableMove = stream->readFlag();
  1386. stream->setCompressionPoint(mRigid.linPosition);
  1387. }
  1388. //----------------------------------------------------------------------------
  1389. U32 Vehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
  1390. {
  1391. U32 retMask = Parent::packUpdate(con, mask, stream);
  1392. stream->writeFlag(mJetting);
  1393. // The rest of the data is part of the control object packet update.
  1394. // If we're controlled by this client, we don't need to send it.
  1395. if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask)))
  1396. return retMask;
  1397. F32 yaw = (mSteering.x + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle);
  1398. F32 pitch = (mSteering.y + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle);
  1399. stream->writeFloat(yaw,9);
  1400. stream->writeFloat(pitch,9);
  1401. mDelta.move.pack(stream);
  1402. if (stream->writeFlag(mask & PositionMask))
  1403. {
  1404. stream->writeCompressedPoint(mRigid.linPosition);
  1405. mathWrite(*stream, mRigid.angPosition);
  1406. mathWrite(*stream, mRigid.linMomentum);
  1407. mathWrite(*stream, mRigid.angMomentum);
  1408. stream->writeFlag(mRigid.atRest);
  1409. }
  1410. stream->writeFloat(mClampF(getEnergyValue(), 0.f, 1.f), 8);
  1411. return retMask;
  1412. }
  1413. void Vehicle::unpackUpdate(NetConnection *con, BitStream *stream)
  1414. {
  1415. Parent::unpackUpdate(con,stream);
  1416. mJetting = stream->readFlag();
  1417. if (stream->readFlag())
  1418. return;
  1419. F32 yaw = stream->readFloat(9);
  1420. F32 pitch = stream->readFloat(9);
  1421. mSteering.x = (2 * yaw * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
  1422. mSteering.y = (2 * pitch * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
  1423. mDelta.move.unpack(stream);
  1424. if (stream->readFlag())
  1425. {
  1426. mPredictionCount = sMaxPredictionTicks;
  1427. F32 speed = mRigid.linVelocity.len();
  1428. mDelta.warpRot[0] = mRigid.angPosition;
  1429. // Read in new position and momentum values
  1430. stream->readCompressedPoint(&mRigid.linPosition);
  1431. mathRead(*stream, &mRigid.angPosition);
  1432. mathRead(*stream, &mRigid.linMomentum);
  1433. mathRead(*stream, &mRigid.angMomentum);
  1434. mRigid.atRest = stream->readFlag();
  1435. mRigid.updateVelocity();
  1436. if (isProperlyAdded())
  1437. {
  1438. // Determine number of ticks to warp based on the average
  1439. // of the client and server velocities.
  1440. Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
  1441. mDelta.warpOffset = mRigid.linPosition - cp;
  1442. // Calc the distance covered in one tick as the average of
  1443. // the old speed and the new speed from the server.
  1444. F32 dt,as = (speed + mRigid.linVelocity.len()) * 0.5 * TickSec;
  1445. // Cal how many ticks it will take to cover the warp offset.
  1446. // If it's less than what's left in the current tick, we'll just
  1447. // warp in the remaining time.
  1448. if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks)
  1449. dt = mDelta.dt + sMaxWarpTicks;
  1450. else
  1451. dt = (dt <= mDelta.dt)? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt;
  1452. // Adjust current frame interpolation
  1453. if (mDelta.dt) {
  1454. mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt));
  1455. mDelta.posVec = (cp - mDelta.pos) / mDelta.dt;
  1456. QuatF cr;
  1457. cr.interpolate(mDelta.rot[1],mDelta.rot[0],mDelta.dt);
  1458. mDelta.rot[1].interpolate(cr,mRigid.angPosition,mDelta.dt / dt);
  1459. mDelta.rot[0].extrapolate(mDelta.rot[1],cr,mDelta.dt);
  1460. }
  1461. // Calculated multi-tick warp
  1462. mDelta.warpCount = 0;
  1463. mDelta.warpTicks = (S32)(mFloor(dt));
  1464. if (mDelta.warpTicks)
  1465. {
  1466. mDelta.warpOffset = mRigid.linPosition - mDelta.pos;
  1467. mDelta.warpOffset /= (F32)mDelta.warpTicks;
  1468. mDelta.warpRot[0] = mDelta.rot[1];
  1469. mDelta.warpRot[1] = mRigid.angPosition;
  1470. }
  1471. }
  1472. else
  1473. {
  1474. // Set the vehicle to the server position
  1475. mDelta.dt = 0;
  1476. mDelta.pos = mRigid.linPosition;
  1477. mDelta.posVec.set(0,0,0);
  1478. mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
  1479. mDelta.warpCount = mDelta.warpTicks = 0;
  1480. setPosition(mRigid.linPosition, mRigid.angPosition);
  1481. }
  1482. mRigid.updateCenterOfMass();
  1483. }
  1484. setEnergyLevel(stream->readFloat(8) * mDataBlock->maxEnergy);
  1485. }
  1486. //----------------------------------------------------------------------------
  1487. void Vehicle::consoleInit()
  1488. {
  1489. Con::addVariable("$vehicle::workingQueryBoxStaleThreshold",TypeS32,&sWorkingQueryBoxStaleThreshold,
  1490. "@brief The maximum number of ticks that go by before the mWorkingQueryBox is considered stale and needs updating.\n\n"
  1491. "Other factors can cause the collision working query box to become invalidated, such as the vehicle moving far "
  1492. "enough outside of this cached box. The smaller this number, the more times the working list of triangles that are "
  1493. "considered for collision is refreshed. This has the greatest impact with colliding with high triangle count meshes.\n\n"
  1494. "@note Set to -1 to disable any time-based forced check.\n\n"
  1495. "@ingroup GameObjects\n");
  1496. Con::addVariable("$vehicle::workingQueryBoxSizeMultiplier",TypeF32,&sWorkingQueryBoxSizeMultiplier,
  1497. "@brief How much larger the mWorkingQueryBox should be made when updating the working collision list.\n\n"
  1498. "The larger this number the less often the working list will be updated due to motion, but any non-static shape that "
  1499. "moves into the query box will not be noticed.\n\n"
  1500. "@ingroup GameObjects\n");
  1501. }
  1502. void Vehicle::initPersistFields()
  1503. {
  1504. addField( "disableMove", TypeBool, Offset(mDisableMove, Vehicle),
  1505. "When this flag is set, the vehicle will ignore throttle changes." );
  1506. Parent::initPersistFields();
  1507. }
  1508. void Vehicle::mountObject(SceneObject *obj, S32 node, const MatrixF &xfm )
  1509. {
  1510. Parent::mountObject( obj, node, xfm );
  1511. // Clear objects off the working list that are from objects mounted to us.
  1512. // (This applies mostly to players...)
  1513. for ( CollisionWorkingList* itr = mConvex.getWorkingList().wLink.mNext;
  1514. itr != &mConvex.getWorkingList();
  1515. itr = itr->wLink.mNext)
  1516. {
  1517. if (itr->mConvex->getObject() == obj)
  1518. {
  1519. CollisionWorkingList* cl = itr;
  1520. itr = itr->wLink.mPrev;
  1521. cl->free();
  1522. }
  1523. }
  1524. }
  1525. //----------------------------------------------------------------------------
  1526. void Vehicle::updateLiftoffDust( F32 dt )
  1527. {
  1528. Point3F offset( 0.0, 0.0, mDataBlock->dustHeight );
  1529. emitDust( mDustEmitterList[ 0 ], mDataBlock->triggerDustHeight, offset,
  1530. ( U32 )( dt * 1000 ) );
  1531. }
  1532. //----------------------------------------------------------------------------
  1533. void Vehicle::updateDamageSmoke( F32 dt )
  1534. {
  1535. for( S32 j=VehicleData::VC_NUM_DAMAGE_LEVELS-1; j>=0; j-- )
  1536. {
  1537. F32 damagePercent = mDamage / mDataBlock->maxDamage;
  1538. if( damagePercent >= mDataBlock->damageLevelTolerance[j] )
  1539. {
  1540. for( S32 i=0; i<mDataBlock->numDmgEmitterAreas; i++ )
  1541. {
  1542. MatrixF trans = getTransform();
  1543. Point3F offset = mDataBlock->damageEmitterOffset[i];
  1544. trans.mulP( offset );
  1545. Point3F emitterPoint = offset;
  1546. if( pointInWater(offset ) )
  1547. {
  1548. U32 emitterOffset = VehicleData::VC_BUBBLE_EMITTER;
  1549. if( mDamageEmitterList[emitterOffset] )
  1550. {
  1551. mDamageEmitterList[emitterOffset]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)( dt * 1000 ) );
  1552. }
  1553. }
  1554. else
  1555. {
  1556. if( mDamageEmitterList[j] )
  1557. {
  1558. mDamageEmitterList[j]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)(dt * 1000));
  1559. }
  1560. }
  1561. }
  1562. break;
  1563. }
  1564. }
  1565. }
  1566. //--------------------------------------------------------------------------
  1567. void Vehicle::updateFroth( F32 dt )
  1568. {
  1569. // update bubbles
  1570. Point3F moveDir = getVelocity();
  1571. Point3F contactPoint;
  1572. if( !collidingWithWater( contactPoint ) )
  1573. {
  1574. if ( mWakeSound )
  1575. mWakeSound->stop();
  1576. return;
  1577. }
  1578. F32 speed = moveDir.len();
  1579. if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0;
  1580. U32 emitRate = (U32)(speed * mDataBlock->splashFreqMod * dt);
  1581. U32 i;
  1582. if ( mWakeSound )
  1583. {
  1584. if ( !mWakeSound->isPlaying() )
  1585. mWakeSound->play();
  1586. mWakeSound->setTransform( getTransform() );
  1587. mWakeSound->setVelocity( getVelocity() );
  1588. }
  1589. for( i=0; i<VehicleData::VC_NUM_SPLASH_EMITTERS; i++ )
  1590. {
  1591. if( mSplashEmitterList[i] )
  1592. {
  1593. mSplashEmitterList[i]->emitParticles( contactPoint, contactPoint, Point3F( 0.0, 0.0, 1.0 ),
  1594. moveDir, emitRate );
  1595. }
  1596. }
  1597. }
  1598. //--------------------------------------------------------------------------
  1599. // Returns true if vehicle is intersecting a water surface (roughly)
  1600. //--------------------------------------------------------------------------
  1601. bool Vehicle::collidingWithWater( Point3F &waterHeight )
  1602. {
  1603. Point3F curPos = getPosition();
  1604. F32 height = mFabs( mObjBox.maxExtents.z - mObjBox.minExtents.z );
  1605. RayInfo rInfo;
  1606. if( gClientContainer.castRay( curPos + Point3F(0.0, 0.0, height), curPos, WaterObjectType, &rInfo) )
  1607. {
  1608. waterHeight = rInfo.point;
  1609. return true;
  1610. }
  1611. return false;
  1612. }
  1613. void Vehicle::setEnergyLevel(F32 energy)
  1614. {
  1615. Parent::setEnergyLevel(energy);
  1616. setMaskBits(EnergyMask);
  1617. }
  1618. void Vehicle::prepBatchRender( SceneRenderState *state, S32 mountedImageIndex )
  1619. {
  1620. Parent::prepBatchRender( state, mountedImageIndex );
  1621. if ( !gShowBoundingBox )
  1622. return;
  1623. if ( mountedImageIndex != -1 )
  1624. {
  1625. ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
  1626. ri->renderDelegate.bind( this, &Vehicle::_renderMuzzleVector );
  1627. ri->objectIndex = mountedImageIndex;
  1628. ri->type = RenderPassManager::RIT_Editor;
  1629. state->getRenderPass()->addInst( ri );
  1630. return;
  1631. }
  1632. ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
  1633. ri->renderDelegate.bind( this, &Vehicle::_renderMassAndContacts );
  1634. ri->type = RenderPassManager::RIT_Editor;
  1635. state->getRenderPass()->addInst( ri );
  1636. }
  1637. void Vehicle::_renderMassAndContacts( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
  1638. {
  1639. GFXStateBlockDesc desc;
  1640. desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
  1641. desc.setZReadWrite(false,true);
  1642. desc.fillMode = GFXFillWireframe;
  1643. // Render the mass center.
  1644. GFX->getDrawUtil()->drawCube(desc, Point3F(0.1f,0.1f,0.1f),mDataBlock->massCenter, ColorI(255, 255, 255), &mRenderObjToWorld);
  1645. // Now render all the contact points.
  1646. for (S32 i = 0; i < mCollisionList.getCount(); i++)
  1647. {
  1648. const Collision& collision = mCollisionList[i];
  1649. GFX->getDrawUtil()->drawCube(desc, Point3F(0.05f,0.05f,0.05f),collision.point, ColorI(0, 0, 255));
  1650. }
  1651. // Finally render the normals as one big batch.
  1652. PrimBuild::begin(GFXLineList, mCollisionList.getCount() * 2);
  1653. for (S32 i = 0; i < mCollisionList.getCount(); i++)
  1654. {
  1655. const Collision& collision = mCollisionList[i];
  1656. PrimBuild::color3f(1, 1, 1);
  1657. PrimBuild::vertex3fv(collision.point);
  1658. PrimBuild::vertex3fv(collision.point + collision.normal * 0.05f);
  1659. }
  1660. PrimBuild::end();
  1661. // Build and render the collision polylist which is returned
  1662. // in the server's world space.
  1663. ClippedPolyList polyList;
  1664. polyList.mPlaneList.setSize(6);
  1665. polyList.mPlaneList[0].set(getWorldBox().minExtents,VectorF(-1,0,0));
  1666. polyList.mPlaneList[1].set(getWorldBox().minExtents,VectorF(0,-1,0));
  1667. polyList.mPlaneList[2].set(getWorldBox().minExtents,VectorF(0,0,-1));
  1668. polyList.mPlaneList[3].set(getWorldBox().maxExtents,VectorF(1,0,0));
  1669. polyList.mPlaneList[4].set(getWorldBox().maxExtents,VectorF(0,1,0));
  1670. polyList.mPlaneList[5].set(getWorldBox().maxExtents,VectorF(0,0,1));
  1671. Box3F dummyBox;
  1672. SphereF dummySphere;
  1673. buildPolyList(PLC_Collision, &polyList, dummyBox, dummySphere);
  1674. //polyList.render();
  1675. }
  1676. void Vehicle::_renderMuzzleVector( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
  1677. {
  1678. const U32 index = ri->objectIndex;
  1679. AssertFatal( index > 0 && index < MaxMountedImages, "Vehicle::_renderMuzzleVector() - Bad object index!" );
  1680. AssertFatal( mMountedImageList[index].dataBlock, "Vehicle::_renderMuzzleVector() - Bad object index!" );
  1681. Point3F muzzlePoint, muzzleVector, endpoint;
  1682. getMuzzlePoint(index, &muzzlePoint);
  1683. getMuzzleVector(index, &muzzleVector);
  1684. endpoint = muzzlePoint + muzzleVector * 250;
  1685. if (mSolidSB.isNull())
  1686. {
  1687. GFXStateBlockDesc desc;
  1688. desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
  1689. desc.setZReadWrite(false);
  1690. mSolidSB = GFX->createStateBlock(desc);
  1691. }
  1692. GFX->setStateBlock(mSolidSB);
  1693. PrimBuild::begin(GFXLineList, 2);
  1694. PrimBuild::color4f(0, 1, 0, 1);
  1695. PrimBuild::vertex3fv(muzzlePoint);
  1696. PrimBuild::vertex3fv(endpoint);
  1697. PrimBuild::end();
  1698. }