PhysicsUpdate.cpp 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // PhysicsBehavior.cpp
  24. // Simple rigid body physics
  25. // Author: Michael S. Booth, November 2001
  26. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  27. // please talk to MDC (x36804) before taking this out
  28. #define NO_DEBUG_CRC
  29. #include "Common/PerfTimer.h"
  30. #include "Common/ThingTemplate.h"
  31. #include "Common/Xfer.h"
  32. #include "GameLogic/GameLogic.h"
  33. #include "GameLogic/Module/AIUpdate.h"
  34. #include "GameLogic/Module/BodyModule.h"
  35. #include "GameLogic/Module/ContainModule.h"
  36. #include "GameLogic/Module/CrushDie.h" // for CrushEnum
  37. #include "GameLogic/Module/PhysicsUpdate.h"
  38. #include "GameLogic/Object.h"
  39. #include "GameLogic/ScriptEngine.h"
  40. #include "GameLogic/TerrainLogic.h"
  41. #include "GameLogic/Weapon.h"
  42. #include "GameLogic/LogicRandomValue.h"
  43. const Real DEFAULT_MASS = 1.0f;
  44. const Real DEFAULT_SHOCK_YAW = 0.05f;
  45. const Real DEFAULT_SHOCK_PITCH = 0.025f;
  46. const Real DEFAULT_SHOCK_ROLL = 0.025f;
  47. const Real DEFAULT_FORWARD_FRICTION = 0.15f;
  48. const Real DEFAULT_LATERAL_FRICTION = 0.15f;
  49. const Real DEFAULT_Z_FRICTION = 0.8f;
  50. const Real DEFAULT_AERO_FRICTION = 0.0f;
  51. // Air friction used to be completely separate and unclamped, so this constant must be split.
  52. // The fact it defaults to 0 shows it.
  53. const Real MIN_AERO_FRICTION = 0.00f;
  54. const Real MIN_NON_AERO_FRICTION = 0.01f;
  55. const Real MAX_FRICTION = 0.99f;
  56. const Real STUN_RELIEF_EPSILON = 0.5f;
  57. #include "Common/CRCDebug.h"
  58. const Int MOTIVE_FRAMES = LOGICFRAMES_PER_SECOND / 3;
  59. #define SLEEPY_PHYSICS
  60. #ifdef _INTERNAL
  61. // for occasional debugging...
  62. //#pragma optimize("", off)
  63. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  64. #endif
  65. //-------------------------------------------------------------------------------------------------
  66. static Real angleBetweenVectors(const Coord3D& inCurDir, const Coord3D& inGoalDir)
  67. {
  68. Vector3 curDir;
  69. curDir.X = inCurDir.x;
  70. curDir.Y = inCurDir.y;
  71. curDir.Z = inCurDir.z;
  72. curDir.Normalize();
  73. Vector3 goalDir;
  74. goalDir.X = inGoalDir.x;
  75. goalDir.Y = inGoalDir.y;
  76. goalDir.Z = inGoalDir.z;
  77. goalDir.Normalize();
  78. // dot of two unit vectors is cos of angle between them.
  79. Real cosine = Vector3::Dot_Product(curDir, goalDir);
  80. // bound it in case of numerical error
  81. Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f));
  82. return angleBetween;
  83. }
  84. //-------------------------------------------------------------------------------------------------
  85. static Real heightToSpeed(Real height)
  86. {
  87. // don't bother trying to remember how far we've fallen; instead,
  88. // back-calc it from our speed & gravity... v = sqrt(2*g*h)
  89. return sqrt(fabs(2.0f * TheGlobalData->m_gravity * height));
  90. }
  91. //-------------------------------------------------------------------------------------------------
  92. PhysicsBehaviorModuleData::PhysicsBehaviorModuleData()
  93. {
  94. m_mass = DEFAULT_MASS;
  95. m_shockResistance = 0.0f;
  96. m_shockMaxYaw = DEFAULT_SHOCK_YAW;
  97. m_shockMaxPitch = DEFAULT_SHOCK_PITCH;
  98. m_shockMaxRoll = DEFAULT_SHOCK_ROLL;
  99. m_forwardFriction = DEFAULT_FORWARD_FRICTION;
  100. m_lateralFriction = DEFAULT_LATERAL_FRICTION;
  101. m_ZFriction = DEFAULT_Z_FRICTION;
  102. m_aerodynamicFriction = DEFAULT_AERO_FRICTION;
  103. m_centerOfMassOffset = 0.0f;
  104. m_allowBouncing = false;
  105. m_allowCollideForce = true;
  106. m_killWhenRestingOnGround = false;
  107. m_minFallSpeedForDamage = heightToSpeed(40.0f);
  108. m_fallHeightDamageFactor = 1.0f; // was 10. now is 1.
  109. /*
  110. thru some bizarre editing mishap, we have been double-apply pitch/roll/yaw rates
  111. to objects for, well, a long time, it looks like. I have corrected that problem
  112. in the name of efficiency, but to maintain the same visual appearance without having
  113. to edit every freaking INI in the world at this point, I am just multiplying
  114. all the results by a factor so that the effect is the same (but with less execution time).
  115. I have put this factor into INI in the unlikely event we ever need to change it,
  116. but defaulting it to 2 is, in fact, the right thing for now... (srj)
  117. */
  118. m_pitchRollYawFactor = 2.0f;
  119. m_vehicleCrashesIntoBuildingWeaponTemplate = TheWeaponStore->findWeaponTemplate("VehicleCrashesIntoBuildingWeapon");
  120. m_vehicleCrashesIntoNonBuildingWeaponTemplate = TheWeaponStore->findWeaponTemplate("VehicleCrashesIntoNonBuildingWeapon");
  121. }
  122. //-------------------------------------------------------------------------------------------------
  123. static void parseHeightToSpeed( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ )
  124. {
  125. // don't bother trying to remember how far we've fallen; instead,
  126. // back-calc it from our speed & gravity... v = sqrt(2*g*h)
  127. Real height = INI::scanReal(ini->getNextToken());
  128. *(Real *)store = heightToSpeed(height);
  129. }
  130. //-------------------------------------------------------------------------------------------------
  131. static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ )
  132. {
  133. Real fricPerSec = INI::scanReal(ini->getNextToken());
  134. Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL;
  135. *(Real *)store = fricPerFrame;
  136. }
  137. //-------------------------------------------------------------------------------------------------
  138. /*static*/ void PhysicsBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
  139. {
  140. UpdateModuleData::buildFieldParse(p);
  141. static const FieldParse dataFieldParse[] =
  142. {
  143. { "Mass", INI::parsePositiveNonZeroReal, NULL, offsetof( PhysicsBehaviorModuleData, m_mass ) },
  144. { "ShockResistance", INI::parsePositiveNonZeroReal, NULL, offsetof( PhysicsBehaviorModuleData, m_shockResistance ) },
  145. { "ShockMaxYaw", INI::parsePositiveNonZeroReal, NULL, offsetof( PhysicsBehaviorModuleData, m_shockMaxYaw ) },
  146. { "ShockMaxPitch", INI::parsePositiveNonZeroReal, NULL, offsetof( PhysicsBehaviorModuleData, m_shockMaxPitch ) },
  147. { "ShockMaxRoll", INI::parsePositiveNonZeroReal, NULL, offsetof( PhysicsBehaviorModuleData, m_shockMaxRoll ) },
  148. { "ForwardFriction", parseFrictionPerSec, NULL, offsetof( PhysicsBehaviorModuleData, m_forwardFriction ) },
  149. { "LateralFriction", parseFrictionPerSec, NULL, offsetof( PhysicsBehaviorModuleData, m_lateralFriction ) },
  150. { "ZFriction", parseFrictionPerSec, NULL, offsetof( PhysicsBehaviorModuleData, m_ZFriction ) },
  151. { "AerodynamicFriction", parseFrictionPerSec, NULL, offsetof( PhysicsBehaviorModuleData, m_aerodynamicFriction ) },
  152. { "CenterOfMassOffset", INI::parseReal, NULL, offsetof( PhysicsBehaviorModuleData, m_centerOfMassOffset ) },
  153. { "AllowBouncing", INI::parseBool, NULL, offsetof( PhysicsBehaviorModuleData, m_allowBouncing ) },
  154. { "AllowCollideForce", INI::parseBool, NULL, offsetof( PhysicsBehaviorModuleData, m_allowCollideForce ) },
  155. { "KillWhenRestingOnGround", INI::parseBool, NULL, offsetof( PhysicsBehaviorModuleData, m_killWhenRestingOnGround) },
  156. { "MinFallHeightForDamage", parseHeightToSpeed, NULL, offsetof( PhysicsBehaviorModuleData, m_minFallSpeedForDamage) },
  157. { "FallHeightDamageFactor", INI::parseReal, NULL, offsetof( PhysicsBehaviorModuleData, m_fallHeightDamageFactor) },
  158. { "PitchRollYawFactor", INI::parseReal, NULL, offsetof( PhysicsBehaviorModuleData, m_pitchRollYawFactor) },
  159. { "VehicleCrashesIntoBuildingWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof(PhysicsBehaviorModuleData, m_vehicleCrashesIntoBuildingWeaponTemplate) },
  160. { "VehicleCrashesIntoNonBuildingWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof(PhysicsBehaviorModuleData, m_vehicleCrashesIntoNonBuildingWeaponTemplate) },
  161. { 0, 0, 0, 0 }
  162. };
  163. p.add(dataFieldParse);
  164. }
  165. //-------------------------------------------------------------------------------------------------
  166. //-------------------------------------------------------------------------------------------------
  167. //-------------------------------------------------------------------------------------------------
  168. const Real INVALID_VEL_MAG = -1.0f;
  169. //-------------------------------------------------------------------------------------------------
  170. PhysicsBehavior::PhysicsBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
  171. {
  172. m_accel.zero();
  173. m_prevAccel = m_accel;
  174. m_vel.zero();
  175. m_velMag = 0.0f;
  176. m_yawRate = 0.0f;
  177. m_rollRate = 0.0f;
  178. m_pitchRate = 0.0f;
  179. m_mass = getPhysicsBehaviorModuleData()->m_mass;
  180. m_motiveForceExpires = 0;
  181. m_flags = 0;
  182. m_extraBounciness = 0.0f;
  183. m_extraFriction = 0.0f;
  184. m_currentOverlap = INVALID_ID;
  185. m_previousOverlap = INVALID_ID;
  186. m_lastCollidee = INVALID_ID;
  187. m_ignoreCollisionsWith = INVALID_ID;
  188. setAllowBouncing(getPhysicsBehaviorModuleData()->m_allowBouncing);
  189. setAllowCollideForce(getPhysicsBehaviorModuleData()->m_allowCollideForce);
  190. m_pui = NULL;
  191. m_bounceSound = NULL;
  192. #ifdef SLEEPY_PHYSICS
  193. setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
  194. #endif
  195. }
  196. //-------------------------------------------------------------------------------------------------
  197. static ProjectileUpdateInterface* getPui(Object* obj)
  198. {
  199. if (!obj->isKindOf(KINDOF_PROJECTILE))
  200. return NULL;
  201. ProjectileUpdateInterface* objPui = NULL;
  202. for (BehaviorModule** u = obj->getBehaviorModules(); *u; ++u)
  203. {
  204. if ((objPui = (*u)->getProjectileUpdateInterface()) != NULL)
  205. return objPui;
  206. }
  207. return NULL;
  208. }
  209. //-------------------------------------------------------------------------------------------------
  210. void PhysicsBehavior::onObjectCreated()
  211. {
  212. m_pui = getPui(getObject());
  213. }
  214. //-------------------------------------------------------------------------------------------------
  215. PhysicsBehavior::~PhysicsBehavior()
  216. {
  217. if (m_bounceSound)
  218. {
  219. m_bounceSound->deleteInstance();
  220. m_bounceSound = NULL;
  221. }
  222. }
  223. //-------------------------------------------------------------------------------------------------
  224. void PhysicsBehavior::setIgnoreCollisionsWith(const Object* obj)
  225. {
  226. m_ignoreCollisionsWith = obj ? obj->getID() : INVALID_ID;
  227. }
  228. //-------------------------------------------------------------------------------------------------
  229. Bool PhysicsBehavior::isIgnoringCollisionsWith(ObjectID id) const
  230. {
  231. return id != INVALID_ID && id == m_ignoreCollisionsWith;
  232. }
  233. //-------------------------------------------------------------------------------------------------
  234. Real PhysicsBehavior::getAerodynamicFriction() const
  235. {
  236. Real f = getPhysicsBehaviorModuleData()->m_aerodynamicFriction + m_extraFriction;
  237. if (f < MIN_AERO_FRICTION) f = MIN_AERO_FRICTION;
  238. if (f > MAX_FRICTION) f = MAX_FRICTION;
  239. return f;
  240. }
  241. //-------------------------------------------------------------------------------------------------
  242. Real PhysicsBehavior::getForwardFriction() const
  243. {
  244. Real f = getPhysicsBehaviorModuleData()->m_forwardFriction + m_extraFriction;
  245. if (f < MIN_NON_AERO_FRICTION) f = MIN_NON_AERO_FRICTION;
  246. if (f > MAX_FRICTION) f = MAX_FRICTION;
  247. return f;
  248. }
  249. //-------------------------------------------------------------------------------------------------
  250. Real PhysicsBehavior::getLateralFriction() const
  251. {
  252. Real f = getPhysicsBehaviorModuleData()->m_lateralFriction + m_extraFriction;
  253. if (f < MIN_NON_AERO_FRICTION) f = MIN_NON_AERO_FRICTION;
  254. if (f > MAX_FRICTION) f = MAX_FRICTION;
  255. return f;
  256. }
  257. //-------------------------------------------------------------------------------------------------
  258. Real PhysicsBehavior::getZFriction() const
  259. {
  260. Real f = getPhysicsBehaviorModuleData()->m_ZFriction + m_extraFriction;
  261. if (f < MIN_NON_AERO_FRICTION) f = MIN_NON_AERO_FRICTION;
  262. if (f > MAX_FRICTION) f = MAX_FRICTION;
  263. return f;
  264. }
  265. //-------------------------------------------------------------------------------------------------
  266. /**
  267. * Apply a force at the object's CG
  268. */
  269. void PhysicsBehavior::applyForce( const Coord3D *force )
  270. {
  271. DEBUG_ASSERTCRASH(!(_isnan(force->x) || _isnan(force->y) || _isnan(force->z)), ("PhysicsBehavior::applyForce force NAN!\n"));
  272. if (_isnan(force->x) || _isnan(force->y) || _isnan(force->z)) {
  273. return;
  274. }
  275. // F = ma --> a = F/m (divide force by mass)
  276. Real mass = getMass();
  277. Coord3D modForce = *force;
  278. if (isMotive())
  279. {
  280. const Coord3D *dir = getObject()->getUnitDirectionVector2D();
  281. // Only accept the lateral acceleration.
  282. Real lateralDot = force->x * (-dir->y) + force->y * dir->x;
  283. modForce.x = lateralDot * -dir->y;
  284. modForce.y = lateralDot * dir->x;
  285. }
  286. Real massInv = 1.0f / mass;
  287. m_accel.x += modForce.x * massInv;
  288. m_accel.y += modForce.y * massInv;
  289. m_accel.z += modForce.z * massInv;
  290. //DEBUG_ASSERTCRASH(!(_isnan(m_accel.x) || _isnan(m_accel.y) || _isnan(m_accel.z)), ("PhysicsBehavior::applyForce accel NAN!\n"));
  291. //DEBUG_ASSERTCRASH(!(_isnan(m_vel.x) || _isnan(m_vel.y) || _isnan(m_vel.z)), ("PhysicsBehavior::applyForce vel NAN!\n"));
  292. //DEBUG_ASSERTCRASH(fabs(force->z) < 3, ("unlikely z-force"));
  293. #ifdef SLEEPY_PHYSICS
  294. if (getFlag(IS_IN_UPDATE))
  295. {
  296. // we're applying a force from inside our own update (probably for a bounce or friction).
  297. // just do nothing, since update will calculate the correct sleep behavior at the end.
  298. }
  299. else
  300. {
  301. // when a force is applied by an external module, we must wake up, even if the force is zero.
  302. setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
  303. }
  304. #endif
  305. }
  306. //-------------------------------------------------------------------------------------------------
  307. /**
  308. * Apply a shocwave force at the object's CG
  309. */
  310. void PhysicsBehavior::applyShock( const Coord3D *force )
  311. {
  312. Coord3D resistedForce = *force;
  313. resistedForce.scale( 1.0f - min( 1.0f, max( 0.0f, getPhysicsBehaviorModuleData()->m_shockResistance ) ) );
  314. // Apply the processed shock force to the object
  315. applyForce(&resistedForce);
  316. }
  317. //-------------------------------------------------------------------------------------------------
  318. /**
  319. * Apply a random rotation at the object's CG
  320. */
  321. void PhysicsBehavior::applyRandomRotation()
  322. {
  323. // Ignore any pitch, roll & yaw rotation if behavior is stick to ground
  324. if (getFlag(STICK_TO_GROUND)) return;
  325. // Set bounce to true for a while until the unit is complete bouncing
  326. setAllowBouncing(true);
  327. Real randomModifier;
  328. randomModifier = GameLogicRandomValue(-1.0f, 1.0f);
  329. m_yawRate += getPhysicsBehaviorModuleData()->m_shockMaxYaw * randomModifier;
  330. randomModifier = GameLogicRandomValue(-1.0f, 1.0f);
  331. m_pitchRate += getPhysicsBehaviorModuleData()->m_shockMaxPitch * randomModifier;
  332. randomModifier = GameLogicRandomValue(-1.0f, 1.0f);
  333. m_rollRate += getPhysicsBehaviorModuleData()->m_shockMaxRoll * randomModifier;
  334. #ifdef SLEEPY_PHYSICS
  335. if (getFlag(IS_IN_UPDATE))
  336. {
  337. // we're applying a force from inside our own update (probably for a bounce or friction).
  338. // just do nothing, since update will calculate the correct sleep behavior at the end.
  339. }
  340. else
  341. {
  342. // when a force is applied by an external module, we must wake up, even if the force is zero.
  343. setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
  344. }
  345. #endif
  346. }
  347. //-------------------------------------------------------------------------------------------------
  348. Bool PhysicsBehavior::isMotive() const
  349. {
  350. return m_motiveForceExpires > TheGameLogic->getFrame();
  351. }
  352. //-------------------------------------------------------------------------------------------------
  353. void PhysicsBehavior::applyMotiveForce( const Coord3D *force )
  354. {
  355. m_motiveForceExpires = 0; // make it accept this force unquestioningly :)
  356. applyForce(force);
  357. m_motiveForceExpires = TheGameLogic->getFrame() + MOTIVE_FRAMES;
  358. }
  359. //-------------------------------------------------------------------------------------------------
  360. void PhysicsBehavior::resetDynamicPhysics()
  361. {
  362. m_accel.zero();
  363. m_prevAccel.zero();
  364. m_vel.zero();
  365. m_velMag = 0.0f;
  366. m_turning = TURN_NONE;
  367. m_yawRate = 0;
  368. m_rollRate = 0;
  369. m_pitchRate = 0;
  370. setFlag(HAS_PITCHROLLYAW, false);
  371. #ifdef SLEEPY_PHYSICS
  372. DEBUG_ASSERTCRASH(!getFlag(IS_IN_UPDATE), ("hmm, should not happen, may not work"));
  373. setWakeFrame(getObject(), calcSleepTime());
  374. #endif
  375. }
  376. //-------------------------------------------------------------------------------------------------
  377. void PhysicsBehavior::applyGravitationalForces()
  378. {
  379. m_accel.z += TheGlobalData->m_gravity;
  380. }
  381. //-------------------------------------------------------------------------------------------------
  382. void PhysicsBehavior::applyFrictionalForces()
  383. {
  384. //Are we a plane that is taxiing on a deck with a height offset?
  385. Bool deckTaxiing = getObject()->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET )
  386. && getObject()->getAI()
  387. && getObject()->getAI()->getCurLocomotorSetType() == LOCOMOTORSET_TAXIING;
  388. if (getFlag(APPLY_FRICTION2D_WHEN_AIRBORNE) || !getObject()->isSignificantlyAboveTerrain() || deckTaxiing )
  389. {
  390. applyYPRDamping(1.0f - DEFAULT_LATERAL_FRICTION);
  391. if (m_vel.x || m_vel.y)
  392. {
  393. const Coord3D *dir = getObject()->getUnitDirectionVector2D();
  394. Real mass = getMass();
  395. Real lateralDot = m_vel.x * (-dir->y) + m_vel.y * dir->x;
  396. Real lateralVel_x = lateralDot * -dir->y;
  397. Real lateralVel_y = lateralDot * dir->x;
  398. Real lf = mass * getLateralFriction();
  399. Coord3D accel;
  400. accel.x = -(lf * lateralVel_x);
  401. accel.y = -(lf * lateralVel_y);
  402. accel.z = 0.0f;
  403. if (!isMotive())
  404. {
  405. Real forwardDot = m_vel.x * dir->x + m_vel.y * dir->y;
  406. Real forwardVel_x = forwardDot * dir->x;
  407. Real forwardVel_y = forwardDot * dir->y;
  408. Real ff = mass * getForwardFriction();
  409. accel.x += -(ff * forwardVel_x);
  410. accel.y += -(ff * forwardVel_y);
  411. }
  412. applyForce(&accel);
  413. }
  414. }
  415. else
  416. {
  417. Real aerodynamics = -getAerodynamicFriction(); // negated!
  418. // Air resistance is proportional to velocity in the opposite direction
  419. m_accel.x += m_vel.x * aerodynamics;
  420. m_accel.y += m_vel.y * aerodynamics;
  421. m_accel.z += m_vel.z * aerodynamics;
  422. applyYPRDamping(1.0f + aerodynamics); // since aero is negated, this results in 1.0-getAerodynamicFriction()
  423. }
  424. }
  425. //-------------------------------------------------------------------------------------------------
  426. Bool PhysicsBehavior::handleBounce(Real oldZ, Real newZ, Real groundZ, Coord3D* bounceForce)
  427. {
  428. if (getFlag(ALLOW_BOUNCE) && newZ <= groundZ)
  429. {
  430. const Real MIN_STIFF = 0.01f;
  431. const Real MAX_STIFF = 0.99f;
  432. Real stiffness = TheGlobalData->m_groundStiffness;
  433. if (stiffness < MIN_STIFF) stiffness = MIN_STIFF;
  434. if (stiffness > MAX_STIFF) stiffness = MAX_STIFF;
  435. Real desiredAccelZ = 0.0f;
  436. Real vz = getVelocity()->z;
  437. if (oldZ > groundZ && vz < 0.0f)
  438. {
  439. desiredAccelZ = fabs(vz) * stiffness;
  440. }
  441. bounceForce->x = 0.0f;
  442. bounceForce->y = 0.0f;
  443. bounceForce->z = getMass() * desiredAccelZ;
  444. const Real DAMPING = 0.7f;
  445. applyYPRDamping(DAMPING);
  446. if (vz < 0.0f)
  447. {
  448. Vector3 zvec = getObject()->getTransformMatrix()->Get_Z_Vector();
  449. const Real rollAngle = (zvec.Z > 0) ? 0 : PI;
  450. // don't flip both pitch and roll... we'll "flip" twice.
  451. const Real pitchAngle = 0;
  452. Real yawAngle = getObject()->getTransformMatrix()->Get_Z_Rotation();
  453. setAngles(yawAngle, pitchAngle, rollAngle);
  454. }
  455. if(bounceForce->z > 0.0f)
  456. {
  457. testStunnedUnitForDestruction();
  458. return true;
  459. }
  460. else
  461. {
  462. setAllowBouncing(m_originalAllowBounce);
  463. return false;
  464. }
  465. }
  466. else
  467. {
  468. bounceForce->zero();
  469. return false;
  470. }
  471. }
  472. //-------------------------------------------------------------------------------------------------
  473. inline Bool isVerySmall3D(const Coord3D& v)
  474. {
  475. const Real THRESH = 0.01f;
  476. return (fabs(v.x) < THRESH && fabs(v.y) < THRESH && fabs(v.z) < THRESH);
  477. }
  478. //-------------------------------------------------------------------------------------------------
  479. inline Bool isZero3D(const Coord3D& v)
  480. {
  481. return v.x == 0.0f && v.y == 0.0f && v.z == 0.0f;
  482. }
  483. //-------------------------------------------------------------------------------------------------
  484. void PhysicsBehavior::setPitchRate(Real pitch)
  485. {
  486. m_pitchRate = pitch;
  487. setFlag(HAS_PITCHROLLYAW, (m_pitchRate != 0.0f || m_rollRate != 0.0f || m_yawRate != 0.0f));
  488. }
  489. //-------------------------------------------------------------------------------------------------
  490. void PhysicsBehavior::setRollRate(Real roll)
  491. {
  492. m_rollRate = roll;
  493. setFlag(HAS_PITCHROLLYAW, (m_pitchRate != 0.0f || m_rollRate != 0.0f || m_yawRate != 0.0f));
  494. }
  495. //-------------------------------------------------------------------------------------------------
  496. void PhysicsBehavior::setYawRate(Real yaw)
  497. {
  498. m_yawRate = yaw;
  499. setFlag(HAS_PITCHROLLYAW, (m_pitchRate != 0.0f || m_rollRate != 0.0f || m_yawRate != 0.0f));
  500. }
  501. //-------------------------------------------------------------------------------------------------
  502. void PhysicsBehavior::applyYPRDamping(Real factor)
  503. {
  504. m_pitchRate *= factor;
  505. m_rollRate *= factor;
  506. m_yawRate *= factor;
  507. setFlag(HAS_PITCHROLLYAW, (m_pitchRate != 0.0f || m_rollRate != 0.0f || m_yawRate != 0.0f));
  508. }
  509. //-------------------------------------------------------------------------------------------------
  510. void PhysicsBehavior::setBounceSound(const AudioEventRTS* bounceSound)
  511. {
  512. if (bounceSound)
  513. {
  514. if (m_bounceSound == NULL)
  515. m_bounceSound = newInstance(DynamicAudioEventRTS);
  516. m_bounceSound->m_event = *bounceSound;
  517. }
  518. else
  519. {
  520. if (m_bounceSound)
  521. {
  522. m_bounceSound->deleteInstance();
  523. m_bounceSound = NULL;
  524. }
  525. }
  526. }
  527. //-------------------------------------------------------------------------------------------------
  528. /**
  529. * Basic rigid body physics using an Euler integrator.
  530. * @todo Currently, only translations are integrated. Rotations should also be integrated. (MSB)
  531. */
  532. DECLARE_PERF_TIMER(PhysicsBehavior)
  533. UpdateSleepTime PhysicsBehavior::update()
  534. {
  535. USE_PERF_TIMER(PhysicsBehavior)
  536. Object* obj = getObject();
  537. const PhysicsBehaviorModuleData* d = getPhysicsBehaviorModuleData();
  538. Bool airborneAtStart = obj->isAboveTerrain();
  539. Real activeVelZ = 0;
  540. Coord3D bounceForce;
  541. Bool gotBounceForce = false;
  542. DEBUG_ASSERTCRASH(!getFlag(IS_IN_UPDATE), ("impossible"));
  543. setFlag(IS_IN_UPDATE, true);
  544. if (!getFlag(UPDATE_EVER_RUN))
  545. {
  546. // set the flag so that we don't get bogus "collisions" on the first frame.
  547. setFlag(WAS_AIRBORNE_LAST_FRAME, airborneAtStart);
  548. }
  549. Coord3D prevPos = *obj->getPosition();
  550. m_prevAccel = m_accel;
  551. if (!obj->isDisabledByType(DISABLED_HELD))
  552. {
  553. Matrix3D mtx = *obj->getTransformMatrix();
  554. applyGravitationalForces();
  555. applyFrictionalForces();
  556. // integrate acceleration into velocity
  557. m_vel.x += m_accel.x;
  558. m_vel.y += m_accel.y;
  559. m_vel.z += m_accel.z;
  560. // when vel gets tiny, just clamp to zero
  561. const Real THRESH = 0.001f;
  562. if (fabsf(m_vel.x) < THRESH) m_vel.x = 0.0f;
  563. if (fabsf(m_vel.y) < THRESH) m_vel.y = 0.0f;
  564. if (fabsf(m_vel.z) < THRESH) m_vel.z = 0.0f;
  565. m_velMag = INVALID_VEL_MAG;
  566. Real oldPosZ = mtx.Get_Z_Translation();
  567. // integrate velocity into position
  568. if (obj->testStatus(OBJECT_STATUS_BRAKING))
  569. {
  570. // Don't update position if the locomotor is braking.
  571. if (!obj->isKindOf(KINDOF_PROJECTILE))
  572. {
  573. // Things other than projectiles don't cheat in z. jba.
  574. mtx.Adjust_Z_Translation(m_vel.z);
  575. }
  576. }
  577. else
  578. {
  579. mtx.Adjust_X_Translation(m_vel.x);
  580. mtx.Adjust_Y_Translation(m_vel.y);
  581. mtx.Adjust_Z_Translation(m_vel.z);
  582. }
  583. if (_isnan(mtx.Get_X_Translation()) || _isnan(mtx.Get_Y_Translation()) ||
  584. _isnan(mtx.Get_Z_Translation())) {
  585. DEBUG_CRASH(("Object position is NAN, deleting."));
  586. TheGameLogic->destroyObject(obj);
  587. }
  588. // Check when to clear the stunned status
  589. if (getIsStunned())
  590. {
  591. if ( (fabs(m_vel.x) < STUN_RELIEF_EPSILON &&
  592. fabs(m_vel.y) < STUN_RELIEF_EPSILON &&
  593. fabs(m_vel.z) < STUN_RELIEF_EPSILON)
  594. ||
  595. obj->isSignificantlyAboveTerrain() == FALSE )
  596. {
  597. setStunned(false);
  598. getObject()->clearModelConditionState(MODELCONDITION_STUNNED);
  599. }
  600. }
  601. if (getFlag(HAS_PITCHROLLYAW))
  602. {
  603. /*
  604. You may be tempted to do something like this:
  605. Real rollAngle = -mtx.Get_X_Rotation();
  606. Real pitchAngle = mtx.Get_Y_Rotation();
  607. Real yawAngle = mtx.Get_Z_Rotation();
  608. // do stuff to angles, then rebuild the mtx with 'em
  609. You must resist this temptation, because your code will be wrong!
  610. The problem is that you can't use these calls to later reconstruct
  611. the matrix... because doing such a thing is highly order-dependent,
  612. and furthermore, you'd have to use Euler angles (Not the Get_?_Rotation
  613. calls) to be able to reconstruct 'em, and that's too slow to do for
  614. every object every frame.
  615. The one exception is that it is OK to use Get_Z_Rotation() to get
  616. the yaw angle.
  617. */
  618. // only update the position if we are not HELD
  619. // (otherwise, slowdeath sinking into ground won't work)
  620. Real yawRateToUse = m_yawRate * d->m_pitchRollYawFactor;
  621. Real pitchRateToUse = m_pitchRate * d->m_pitchRollYawFactor;
  622. Real rollRateToUse = m_rollRate * d->m_pitchRollYawFactor;
  623. // With a center of mass listing, pitchRate needs to dampen towards straight down/straight up
  624. Real offset = getCenterOfMassOffset();
  625. // Magnitude sets initial rate, here we care about sign
  626. if (offset != 0.0f)
  627. {
  628. Vector3 xvec = mtx.Get_X_Vector();
  629. Real xy = sqrtf(sqr(xvec.X) + sqr(xvec.Y));
  630. Real pitchAngle = atan2(xvec.Z, xy);
  631. Real remainingAngle = (offset > 0) ? ((PI/2) - pitchAngle) : (-(PI/2) + pitchAngle);
  632. Real s = Sin(remainingAngle);
  633. pitchRateToUse *= s;
  634. }
  635. // update rotation
  636. /// @todo Rotation should use torques, and integrate just like forces (MSB)
  637. // note, we DON'T want to Pre-rotate (either inplace or not),
  638. // since we want to add our mods to the existing matrix.
  639. mtx.Rotate_X(rollRateToUse);
  640. mtx.Rotate_Y(pitchRateToUse);
  641. mtx.Rotate_Z(yawRateToUse);
  642. }
  643. // do not allow object to pass through the ground
  644. Real groundZ = TheTerrainLogic->getLayerHeight(mtx.Get_X_Translation(), mtx.Get_Y_Translation(), obj->getLayer());
  645. if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
  646. {
  647. groundZ += obj->getCarrierDeckHeight();
  648. }
  649. gotBounceForce = handleBounce(oldPosZ, mtx.Get_Z_Translation(), groundZ, &bounceForce);
  650. // remember our z-vel prior to doing ground-slam adjustment
  651. activeVelZ = m_vel.z;
  652. if (mtx.Get_Z_Translation() <= groundZ)
  653. {
  654. // Note - when vehicles are going down a slope, they will maintain a small negative
  655. // z velocity as they go down. So don't slam it to 0 if they aren't slamming into the
  656. // ground.
  657. Real dz = groundZ - mtx.Get_Z_Translation(); // Our excess z velocity.
  658. m_vel.z += dz; // Remove the excess z velocity.
  659. if (m_vel.z > 0.0f)
  660. m_vel.z = 0.0f;
  661. m_velMag = INVALID_VEL_MAG;
  662. mtx.Set_Z_Translation(groundZ);
  663. // this flag is ALWAYS cleared once we hit the ground.
  664. setFlag(ALLOW_TO_FALL, false);
  665. // When a stunned object hits the ground the first time, chage it's model state from stunned flailing to just stunned.
  666. if (getFlag(IS_STUNNED))
  667. {
  668. obj->clearModelConditionState(MODELCONDITION_STUNNED_FLAILING);
  669. obj->setModelConditionState(MODELCONDITION_STUNNED);
  670. }
  671. }
  672. else if (mtx.Get_Z_Translation() > groundZ)
  673. {
  674. if (getFlag(IS_IN_FREEFALL))
  675. {
  676. obj->setDisabled(DISABLED_FREEFALL);
  677. obj->setModelConditionState(MODELCONDITION_FREEFALL);
  678. }
  679. else if (getFlag(STICK_TO_GROUND) && !getFlag(ALLOW_TO_FALL))
  680. {
  681. mtx.Set_Z_Translation(groundZ);
  682. }
  683. }
  684. if (gotBounceForce)
  685. {
  686. // Right the object after the bounce since the pitch and roll may have been affected
  687. Real yawAngle = getObject()->getTransformMatrix()->Get_Z_Rotation();
  688. setAngles(yawAngle, 0.0f, 0.0f);
  689. // Set the translation of the after bounce matrix to the one calculated above
  690. Matrix3D afterBounceMatrix = *getObject()->getTransformMatrix();
  691. afterBounceMatrix.Set_Translation(mtx.Get_Translation());
  692. // Set the result of the after bounce matrix as the object's final matrix
  693. obj->setTransformMatrix(&afterBounceMatrix);
  694. }
  695. else
  696. {
  697. obj->setTransformMatrix(&mtx);
  698. }
  699. } // if not held
  700. // reset the acceleration for accumulation next frame
  701. m_accel.zero();
  702. // clear overlap object, which will be set by PhysicsCollide later
  703. m_previousOverlap = m_currentOverlap;
  704. m_currentOverlap = INVALID_ID;
  705. if (gotBounceForce && getFlag(ALLOW_BOUNCE))
  706. {
  707. applyForce(&bounceForce);
  708. }
  709. Bool airborneAtEnd = obj->isAboveTerrain();
  710. // it's not good enough to check for airborne being different between
  711. // the start and end of this func... we have to compare since last frame,
  712. // since (if we're held by a parachute, for instance) we might have been
  713. // moved by other bits of code!
  714. if (getFlag(WAS_AIRBORNE_LAST_FRAME) && !airborneAtEnd && !getFlag(IMMUNE_TO_FALLING_DAMAGE))
  715. {
  716. doBounceSound(prevPos);
  717. // the normal always points straight down, though we could
  718. // get the alignWithTerrain() normal if it proves interesting
  719. Coord3D normal;
  720. normal.x = normal.y = 0.0f;
  721. normal.z = -1.0f;
  722. obj->onCollide(NULL, obj->getPosition(), &normal);
  723. //
  724. // don't bother trying to remember how far we've fallen; instead,
  725. // we back-calc it from our speed & gravity... v = sqrt(2*g*h).
  726. // (note that m_minFallSpeedForDamage is always POSITIVE.)
  727. //
  728. // also note: since projectiles are immune to falling damage, don't
  729. // even bother doing this check here.
  730. //
  731. Real netSpeed = -activeVelZ - d->m_minFallSpeedForDamage;
  732. if (netSpeed > 0.0f && m_pui == NULL)
  733. {
  734. // only apply force if it's a pretty steep fall, so that things
  735. // going down hills don't injure themselves (unless the hill is really steep)
  736. const Real MIN_ANGLE_TAN = 3.0f; // roughly 71 degrees
  737. const Real TINY_DELTA = 0.01f;
  738. if ((fabs(m_vel.x) <= TINY_DELTA || fabs(activeVelZ / m_vel.x) >= MIN_ANGLE_TAN) &&
  739. (fabs(m_vel.y) <= TINY_DELTA || fabs(activeVelZ / m_vel.y) >= MIN_ANGLE_TAN))
  740. {
  741. Real damageAmt = netSpeed * getMass() * d->m_fallHeightDamageFactor;
  742. DamageInfo damageInfo;
  743. damageInfo.in.m_damageType = DAMAGE_FALLING;
  744. damageInfo.in.m_deathType = DEATH_SPLATTED;
  745. damageInfo.in.m_sourceID = obj->getID();
  746. damageInfo.in.m_amount = damageAmt;
  747. damageInfo.in.m_shockWaveAmount = 0.0f;
  748. obj->attemptDamage( &damageInfo );
  749. //DEBUG_LOG(("Dealing %f (%f %f) points of falling damage to %s!\n",damageAmt,damageInfo.out.m_actualDamageDealt, damageInfo.out.m_actualDamageClipped,obj->getTemplate()->getName().str()));
  750. // if this killed us, add SPLATTED to get a cool death.
  751. if (obj->isEffectivelyDead())
  752. {
  753. obj->setModelConditionState(MODELCONDITION_SPLATTED);
  754. }
  755. }
  756. }
  757. }
  758. if (!airborneAtEnd)
  759. {
  760. // just in case.
  761. setFlag(IS_IN_FREEFALL, false);
  762. if (obj->isDisabledByType(DISABLED_FREEFALL))
  763. obj->clearDisabled(DISABLED_FREEFALL);
  764. obj->clearModelConditionState(MODELCONDITION_FREEFALL);
  765. }
  766. // If we are effectively dead, we shouldn't recall kill.
  767. if (d->m_killWhenRestingOnGround && !airborneAtEnd && isVerySmall3D(m_vel))
  768. {
  769. if( !obj->isKindOf( KINDOF_DRONE ) || obj->isEffectivelyDead() || obj->isDisabledByType( DISABLED_UNMANNED ) )
  770. {
  771. //Must be one of the following cases in order to splat:
  772. //1) Not a drone
  773. //2) Dead drone
  774. //3) Unmanned drone
  775. obj->kill();
  776. }
  777. }
  778. setFlag(UPDATE_EVER_RUN, true);
  779. setFlag(WAS_AIRBORNE_LAST_FRAME, airborneAtEnd);
  780. setFlag(IS_IN_UPDATE, false);
  781. return calcSleepTime();
  782. }
  783. //-------------------------------------------------------------------------------------------------
  784. UpdateSleepTime PhysicsBehavior::calcSleepTime() const
  785. {
  786. #ifdef SLEEPY_PHYSICS
  787. if (isZero3D(m_vel)
  788. && isZero3D(m_accel)
  789. && !getFlag(HAS_PITCHROLLYAW)
  790. && !isMotive()
  791. && (getObject()->getLayer() == LAYER_GROUND && !getObject()->isAboveTerrain())
  792. && getCurrentOverlap() == INVALID_ID
  793. && getPreviousOverlap() == INVALID_ID
  794. && getFlag(UPDATE_EVER_RUN))
  795. {
  796. return UPDATE_SLEEP_FOREVER;
  797. }
  798. else
  799. #endif
  800. {
  801. return UPDATE_SLEEP_NONE;
  802. }
  803. }
  804. //-------------------------------------------------------------------------------------------------
  805. Real PhysicsBehavior::getVelocityMagnitude() const
  806. {
  807. if (m_velMag == INVALID_VEL_MAG)
  808. {
  809. m_velMag = (Real)sqrtf( sqr(m_vel.x) + sqr(m_vel.y) + sqr(m_vel.z) );
  810. }
  811. return m_velMag;
  812. }
  813. //-------------------------------------------------------------------------------------------------
  814. /**
  815. * Return the current velocity magnitude in the forward direction.
  816. * If velocity is opposite facing vector, the returned value will be negative.
  817. */
  818. Real PhysicsBehavior::getForwardSpeed2D() const
  819. {
  820. const Coord3D *dir = getObject()->getUnitDirectionVector2D();
  821. Real vx = m_vel.x * dir->x;
  822. Real vy = m_vel.y * dir->y;
  823. Real dot = vx + vy;
  824. Real speedSquared = vx*vx + vy*vy;
  825. // DEBUG_ASSERTCRASH( speedSquared != 0, ("zero speedSquared will overflow sqrtf()!") );// lorenzen... sanity check
  826. Real speed = (Real)sqrtf( speedSquared );
  827. if (dot >= 0.0f)
  828. return speed;
  829. return -speed;
  830. }
  831. //-------------------------------------------------------------------------------------------------
  832. /**
  833. * Return the current velocity magnitude in the forward direction.
  834. * If velocity is opposite facing vector, the returned value will be negative.
  835. */
  836. Real PhysicsBehavior::getForwardSpeed3D() const
  837. {
  838. Vector3 dir = getObject()->getTransformMatrix()->Get_X_Vector();
  839. Real vx = m_vel.x * dir.X;
  840. Real vy = m_vel.y * dir.Y;
  841. Real vz = m_vel.z * dir.Z;
  842. Real dot = vx + vy + vz;
  843. Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz );
  844. if (dot >= 0.0f)
  845. return speed;
  846. return -speed;
  847. }
  848. //-------------------------------------------------------------------------------------------------
  849. Bool PhysicsBehavior::isCurrentlyOverlapped(Object *obj) const
  850. {
  851. return obj != NULL && obj->getID() == m_currentOverlap;
  852. }
  853. //-------------------------------------------------------------------------------------------------
  854. Bool PhysicsBehavior::wasPreviouslyOverlapped(Object *obj) const
  855. {
  856. return obj != NULL && obj->getID() == m_previousOverlap;
  857. }
  858. //-------------------------------------------------------------------------------------------------
  859. void PhysicsBehavior::scrubVelocityZ( Real desiredVelocity )
  860. {
  861. if (fabs(desiredVelocity) < 0.001f)
  862. {
  863. m_vel.z = 0;
  864. }
  865. else
  866. {
  867. if ((desiredVelocity < 0 && m_vel.z < desiredVelocity) || (desiredVelocity > 0 && m_vel.z > desiredVelocity))
  868. {
  869. m_vel.z = desiredVelocity;
  870. }
  871. }
  872. m_velMag = INVALID_VEL_MAG;
  873. }
  874. //-------------------------------------------------------------------------------------------------
  875. void PhysicsBehavior::scrubVelocity2D( Real desiredVelocity )
  876. {
  877. if (desiredVelocity < 0.001f)
  878. {
  879. m_vel.x = 0;
  880. m_vel.y = 0;
  881. }
  882. else
  883. {
  884. Real curVelocity = sqrtf(m_vel.x*m_vel.x + m_vel.y*m_vel.y);
  885. if (desiredVelocity > curVelocity)
  886. {
  887. return;
  888. }
  889. desiredVelocity /= curVelocity;
  890. m_vel.x *= desiredVelocity;
  891. m_vel.y *= desiredVelocity;
  892. }
  893. m_velMag = INVALID_VEL_MAG;
  894. }
  895. //-------------------------------------------------------------------------------------------------
  896. void PhysicsBehavior::addOverlap(Object *obj)
  897. {
  898. if (obj && !isCurrentlyOverlapped(obj))
  899. {
  900. m_currentOverlap = obj->getID();
  901. }
  902. }
  903. //-------------------------------------------------------------------------------------------------
  904. void PhysicsBehavior::transferVelocityTo(PhysicsBehavior* that) const
  905. {
  906. if (that != NULL)
  907. that->m_vel.add(&m_vel);
  908. that->m_velMag = INVALID_VEL_MAG;
  909. }
  910. //-------------------------------------------------------------------------------------------------
  911. void PhysicsBehavior::addVelocityTo( const Coord3D *vel)
  912. {
  913. if (vel != NULL)
  914. m_vel.add( vel );
  915. }
  916. //-------------------------------------------------------------------------------------------------
  917. void PhysicsBehavior::setAngles( Real yaw, Real pitch, Real roll )
  918. {
  919. const Coord3D* pos = getObject()->getPosition();
  920. Matrix3D xfrm;
  921. xfrm.Make_Identity();
  922. xfrm.Translate( pos->x, pos->y, pos->z );
  923. // here we DO want to use in-place-etc, cuz we're not adding to any existing rot/etc
  924. xfrm.In_Place_Pre_Rotate_X( -roll );
  925. xfrm.In_Place_Pre_Rotate_Y( pitch );
  926. xfrm.In_Place_Pre_Rotate_Z( yaw );
  927. getObject()->setTransformMatrix( &xfrm );
  928. }
  929. //-------------------------------------------------------------------------------------------------
  930. Real PhysicsBehavior::getMass() const
  931. {
  932. Real mass = m_mass;
  933. ContainModuleInterface* contain = getObject()->getContain();
  934. if (contain)
  935. mass += contain->getContainedItemsMass();
  936. return mass;
  937. }
  938. //-----------------------------------------------------------------------------
  939. inline Real calcDistSqr(const Coord3D& a, const Coord3D& b)
  940. {
  941. return sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z);
  942. }
  943. //-------------------------------------------------------------------------------------------------
  944. void PhysicsBehavior::doBounceSound(const Coord3D& prevPos)
  945. {
  946. if (!m_bounceSound)
  947. return;
  948. const Real NORMAL_VEL_Z = 0.25f;
  949. const Real NORMAL_MASS = 50.0f;
  950. // get the per-unit sound for the collision which was stuffed in on Object creation.
  951. AudioEventRTS collisionSound = m_bounceSound->m_event;
  952. //Real vel = fabs(getVelocity()->z);
  953. // can't use velocity, because it's already been updated this frame, and will be zero... (srj)
  954. Real vel = fabs(prevPos.z - getObject()->getPosition()->z);
  955. Real mass = fabs(getMass());
  956. if (vel > NORMAL_VEL_Z) {
  957. vel = NORMAL_VEL_Z;
  958. }
  959. if (mass > NORMAL_MASS) {
  960. mass = NORMAL_MASS;
  961. }
  962. if (vel < 0) {
  963. vel = 0;
  964. }
  965. if (mass < 0) {
  966. mass = 0;
  967. }
  968. #ifdef FIX_AUDIO
  969. Real volAdjust = NormalizeToRange(MuLaw(vel, NORMAL_VEL_Z, 500), -1, 1, 0.25, 1.0);
  970. volAdjust *= NormalizeToRange(MuLaw(mass, NORMAL_MASS, 500), -1, 1, 0.25, 1.0);
  971. collisionSound.setVolume(volAdjust);
  972. #endif
  973. collisionSound.setObjectID(getObject()->getID());
  974. TheAudio->addAudioEvent(&collisionSound);
  975. }
  976. //-------------------------------------------------------------------------------------------------
  977. /**
  978. * Resolve the collision between getObject() and other by computing
  979. * the amount the objects have overlapd, and applying proportional forces to
  980. * push them apart.
  981. * Note that this call only applies forces to our object, not to "other". Since the
  982. * forces should be equal and opposite, this could be optimized.
  983. * @todo Make this work properly for non-cylindrical objects (MSB)
  984. * @todo Physics collision resolution is 2D - should it be 3D? (MSB)
  985. */
  986. //DECLARE_PERF_TIMER(PhysicsBehavioronCollide)
  987. void PhysicsBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
  988. {
  989. //USE_PERF_TIMER(PhysicsBehavioronCollide)
  990. if (m_pui != NULL)
  991. {
  992. // projectiles always get a chance to handle their own collisions, and not go thru here
  993. if (m_pui->projectileHandleCollision(other))
  994. return;
  995. }
  996. Object *obj = getObject();
  997. Object* objContainedBy = obj->getContainedBy();
  998. // Note that other == null means "collide with ground"
  999. if (other == NULL)
  1000. {
  1001. // if we are in a container, tell the container we collided with the ground.
  1002. // (handy for parachutes.)
  1003. if (objContainedBy)
  1004. {
  1005. objContainedBy->onCollide(other, loc, normal);
  1006. }
  1007. return;
  1008. }
  1009. // if we are containing this object, ignore collisions with it.
  1010. Object* otherContainedBy = other->getContainedBy();
  1011. if (otherContainedBy == obj || objContainedBy == other)
  1012. {
  1013. return;
  1014. }
  1015. // if we're both on parachutes, we never collide with each other.
  1016. /// @todo srj -- ugh, an awful hack for paradrop problems. fix someday.
  1017. if (obj->testStatus(OBJECT_STATUS_PARACHUTING) && other->testStatus(OBJECT_STATUS_PARACHUTING))
  1018. {
  1019. return;
  1020. }
  1021. // ignore collisions with our "ignore" thingie, if any (and vice versa)
  1022. AIUpdateInterface* ai = obj->getAIUpdateInterface();
  1023. if (ai != NULL && ai->getIgnoredObstacleID() == other->getID())
  1024. {
  1025. /// @todo srj -- what the hell is this code doing here? ack!
  1026. //Before we return, check for a very special case of an infantry colliding with an unmanned vehicle.
  1027. //If this is the case, it'll become its new pilot!
  1028. if( obj->isKindOf( KINDOF_INFANTRY ) && other->isDisabledByType( DISABLED_UNMANNED ) )
  1029. {
  1030. //This is in fact the case, and we are doing it here because it applies to all infantry in any unmanned vehicle.
  1031. //This could be done via a special/new module, but doing it here doesn't require a new module update to every infantry.
  1032. other->clearDisabled( DISABLED_UNMANNED );
  1033. //We need to be able to test whether an object on a team has been captured, so set here that this object
  1034. //was captured.
  1035. other->setCaptured(true);
  1036. other->defect( obj->getTeam(), 0 );
  1037. //other->setTeam( obj->getTeam() );
  1038. //In order to make things easier for the designers, we are going to transfer the name
  1039. //of the infantry to the vehicle... so the designer can control the vehicle with their scripts.
  1040. TheScriptEngine->transferObjectName( obj->getName(), other );
  1041. TheGameLogic->destroyObject( obj );
  1042. }
  1043. return;
  1044. }
  1045. AIUpdateInterface* aiOther = other->getAIUpdateInterface();
  1046. if (aiOther != NULL && aiOther->getIgnoredObstacleID() == obj->getID())
  1047. {
  1048. return;
  1049. }
  1050. if (isIgnoringCollisionsWith(other->getID()))
  1051. {
  1052. return;
  1053. }
  1054. Bool immobile = obj->isKindOf( KINDOF_IMMOBILE );
  1055. Bool otherImmobile = other->isKindOf( KINDOF_IMMOBILE );
  1056. PhysicsBehavior* otherPhysics = other->getPhysics();
  1057. if (otherPhysics)
  1058. {
  1059. if (otherPhysics->isIgnoringCollisionsWith(obj->getID()))
  1060. {
  1061. return;
  1062. }
  1063. }
  1064. else
  1065. {
  1066. // if the object we have collided with has no physics, it is insubstantial
  1067. // (exception: if it's immobile.)
  1068. if (!otherImmobile)
  1069. {
  1070. return;
  1071. }
  1072. }
  1073. if (checkForOverlapCollision(other))
  1074. {
  1075. // we should overlap them, rather than bounce them, so punt here.
  1076. // (note that we do this prior to checking for Physics update,
  1077. // because Overlap doesn't require 'other' to have Physics...)
  1078. return;
  1079. }
  1080. Real mass = getMass();
  1081. if (immobile)
  1082. mass = 999999.0f;
  1083. if (ai)
  1084. {
  1085. // AI objects move under their own initiative, not by getting bounced. jba.
  1086. // unless they are dead & colliding with something immobile. (srj)
  1087. // or parachuting and colliding with something immobile. (srj)
  1088. if (!((obj->isEffectivelyDead() || obj->testStatus(OBJECT_STATUS_PARACHUTING)) && otherImmobile))
  1089. {
  1090. Bool doForce = ai->processCollision(this, other);
  1091. if (!doForce)
  1092. return;
  1093. }
  1094. }
  1095. Coord3D usCenter, themCenter;
  1096. obj->getGeometryInfo().getCenterPosition(*obj->getPosition(), usCenter);
  1097. other->getGeometryInfo().getCenterPosition(*other->getPosition(), themCenter);
  1098. Coord3D delta;
  1099. delta.x = themCenter.x - usCenter.x;
  1100. delta.y = themCenter.y - usCenter.y;
  1101. delta.z = themCenter.z - usCenter.z;
  1102. Real distSqr, usRadius, themRadius;
  1103. if (obj->isAboveTerrain())
  1104. {
  1105. // do 3d testing.
  1106. usRadius = obj->getGeometryInfo().getBoundingSphereRadius();
  1107. themRadius = other->getGeometryInfo().getBoundingSphereRadius();
  1108. distSqr = sqr(delta.x) + sqr(delta.y) + sqr(delta.z);
  1109. }
  1110. else
  1111. {
  1112. // do 2d testing.
  1113. usRadius = obj->getGeometryInfo().getBoundingCircleRadius();
  1114. themRadius = other->getGeometryInfo().getBoundingCircleRadius();
  1115. distSqr = sqr(delta.x) + sqr(delta.y);
  1116. delta.z = 0;
  1117. }
  1118. if (distSqr > sqr(usRadius + themRadius))
  1119. {
  1120. // We don't overlap at all. How did we get here?
  1121. return;
  1122. }
  1123. m_lastCollidee = other->getID();
  1124. Real dist = sqrtf(distSqr);
  1125. Real overlap = usRadius + themRadius - dist;
  1126. // if objects are coincident, dist is zero, so force would be infinite -- clearly
  1127. // not what we want. so just cap it here for now. (srj)
  1128. if (dist < 1.0f)
  1129. dist = 1.0f;
  1130. if (getAllowCollideForce())
  1131. {
  1132. Real factor;
  1133. Coord3D force;
  1134. if (otherImmobile && !obj->isDestroyed())
  1135. {
  1136. if (obj->testStatus(OBJECT_STATUS_PARACHUTING))
  1137. {
  1138. // don't let us intersect buildings. cheat. applying a force won't work
  1139. // cuz we are usually braking. jam it.
  1140. Object* objToBounce = obj;
  1141. while (objToBounce->getContainedBy() != NULL)
  1142. objToBounce = objToBounce->getContainedBy();
  1143. Real bounceOutDist = usRadius * 0.1f;
  1144. Coord3D tmp = *objToBounce->getPosition();
  1145. tmp.x -= bounceOutDist * delta.x / dist;
  1146. tmp.y -= bounceOutDist * delta.y / dist;
  1147. objToBounce->setPosition(&tmp);
  1148. objToBounce->getPhysics()->scrubVelocity2D(0);
  1149. return;
  1150. }
  1151. // if other is immobile, we must apply enough force to at least stop our motion,
  1152. // and preferably negate it, otherwise we will pass thru the object. so cheat,
  1153. // and act like we're bouncing off the ground.
  1154. const Real MIN_STIFF = 0.01f;
  1155. const Real MAX_STIFF = 0.99f;
  1156. Real stiffness = TheGlobalData->m_structureStiffness;
  1157. if (stiffness < MIN_STIFF) stiffness = MIN_STIFF;
  1158. if (stiffness > MAX_STIFF) stiffness = MAX_STIFF;
  1159. // huh huh, he said "stiff"
  1160. Real mag = getVelocityMagnitude();
  1161. const Real MINBOUNCESPEED = 1.0f/(LOGICFRAMES_PER_SECOND*5.0f);
  1162. if (mag < MINBOUNCESPEED)
  1163. mag = MINBOUNCESPEED;
  1164. factor = -mag * getMass() * stiffness;
  1165. // if we are moving down, we may want to blow ourselves into smithereens....
  1166. if (delta.z < 0.0f &&
  1167. obj->getPosition()->z >= TheGlobalData->m_defaultStructureRubbleHeight)
  1168. {
  1169. if (other->isKindOf(KINDOF_STRUCTURE))
  1170. {
  1171. // fall into a building. if a vehicle, blow up. then destroy ourself (not die), regardless.
  1172. if (obj->isKindOf(KINDOF_VEHICLE))
  1173. {
  1174. TheWeaponStore->createAndFireTempWeapon(getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoBuildingWeaponTemplate, obj, obj->getPosition());
  1175. }
  1176. TheGameLogic->destroyObject(obj);
  1177. return;
  1178. }
  1179. else
  1180. {
  1181. // fall into a nonbuilding -- whatever. if we're a vehicle, quietly do a little damage.
  1182. if (obj->isKindOf(KINDOF_VEHICLE))
  1183. {
  1184. TheWeaponStore->createAndFireTempWeapon(getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate, obj, obj->getPosition());
  1185. }
  1186. }
  1187. }
  1188. // nuke the velocity. why? very simple: we want to ignore the previous vel in favor of
  1189. // this. in theory, we could be clever and calculate the right force to apply to achieve this,
  1190. // but then if we were still colliding next frame, we'd get a sudden 'aceleration' of bounce
  1191. // that would look freakish. so cheat.
  1192. m_vel.x = 0;
  1193. m_vel.y = 0;
  1194. m_vel.z = 0;
  1195. m_velMag = INVALID_VEL_MAG;
  1196. }
  1197. else
  1198. {
  1199. if (overlap > 5.0f)
  1200. overlap = 5.0f;
  1201. factor = -overlap;
  1202. }
  1203. force.x = factor * delta.x / dist;
  1204. force.y = factor * delta.y / dist;
  1205. force.z = factor * delta.z / dist; // will be zero for 2d case.
  1206. DEBUG_ASSERTCRASH(!(_isnan(force.x) || _isnan(force.y) || _isnan(force.z)), ("PhysicsBehavior::onCollide force NAN!\n"));
  1207. applyForce( &force );
  1208. }
  1209. }
  1210. //-------------------------------------------------------------------------------------------------
  1211. static Bool perpsLogicallyEqual( Real perpOne, Real perpTwo )
  1212. {
  1213. // Equality with a wiggle fudge.
  1214. const Real PERP_RANGE = 0.15f;
  1215. return fabs( perpOne - perpTwo ) <= PERP_RANGE;
  1216. }
  1217. //-------------------------------------------------------------------------------------------------
  1218. /** Do the collision
  1219. * Cases:
  1220. * * The crusheeOther is !OVERLAPPABLE or !CRUSHABLE. Result: return false
  1221. * * The crusherMe is !CRUSHER. Result: return false
  1222. * * crusher is CRUSHER, crushee is CRUSHABLE but allied. Result: return false
  1223. * * crusher is CRUSHER, crushee is OVERLAPPABLE but not CRUSHABLE. Result: return false
  1224. * * crushee has no PhysicsBehavior. Result: return false
  1225. * * crusher is CRUSHER, crushee is CRUSHABLE, but is too hard. Result: return false
  1226. * * crusher is CRUSHER, crushee is CRUSHABLE, hardness ok. Result: crush, return true
  1227. *
  1228. * Return true if we want to skip having physics push us apart // LORENZEN
  1229. */
  1230. //-------------------------------------------------------------------------------------------------
  1231. Bool PhysicsBehavior::checkForOverlapCollision(Object *other)
  1232. {
  1233. //This is the most Supreme Truth... that unless I am moving right now, I may not crush anyhing!
  1234. if ( isVerySmall3D( *getVelocity() ) )
  1235. return false;
  1236. Object* crusherMe = getObject();
  1237. Object* crusheeOther = other;
  1238. //Determine if we can crush the other object.
  1239. Bool selfCrushingOther = crusherMe->canCrushOrSquish( crusheeOther, TEST_CRUSH_ONLY );
  1240. Bool selfBeingCrushed = crusheeOther->canCrushOrSquish( crusherMe, TEST_CRUSH_ONLY );
  1241. if( selfCrushingOther && selfBeingCrushed )
  1242. {
  1243. //Is it possible to crush and be crushed at the same time?
  1244. DEBUG_CRASH( ("%s (Crusher:%d, Crushable:%d) is attempting to crush %s (Crusher:%d, Crushable:%d) but it is reciprocating -- shouldn't be possible!",
  1245. crusherMe->getTemplate()->getName().str(), crusherMe->getCrusherLevel(), crusherMe->getCrushableLevel(),
  1246. crusheeOther->getTemplate()->getName().str(), crusheeOther->getCrusherLevel(), crusheeOther->getCrushableLevel() ) );
  1247. return false;
  1248. }
  1249. // if we are being crushed, then skip all this and return true;
  1250. // this allows us to NOT react in the normal way and just be passive to the overlap...
  1251. if( selfBeingCrushed )
  1252. {
  1253. return true;
  1254. }
  1255. // grab physics modules if there
  1256. PhysicsBehavior *crusherPhysics = this;
  1257. if( crusherPhysics == NULL )
  1258. {
  1259. return false;
  1260. }
  1261. if( !selfCrushingOther )
  1262. {
  1263. return false;
  1264. }
  1265. // ok, add this to our list-of-overlapped-things.
  1266. crusherPhysics->addOverlap(crusheeOther);
  1267. if (!crusherPhysics->wasPreviouslyOverlapped(crusheeOther))
  1268. {
  1269. DamageInfo damageInfo;
  1270. damageInfo.in.m_damageType = DAMAGE_CRUSH;
  1271. damageInfo.in.m_deathType = DEATH_CRUSHED;
  1272. damageInfo.in.m_sourceID = crusherMe->getID();
  1273. damageInfo.in.m_amount = 0.0f; // yes, that's right -- we don't want to do damage, just to trigger the minor DamageFX, if any
  1274. crusheeOther->attemptDamage( &damageInfo );
  1275. }
  1276. const Coord3D *crusheePos = crusheeOther->getPosition();
  1277. const Coord3D *crusherPos = crusherMe->getPosition();
  1278. BodyModuleInterface* crusheeBody = crusheeOther->getBodyModule();
  1279. Bool frontCrushed = crusheeBody->getFrontCrushed();
  1280. Bool backCrushed = crusheeBody->getBackCrushed();
  1281. if( !(frontCrushed && backCrushed) )
  1282. {
  1283. Bool crushIt = FALSE;
  1284. const Coord3D *dir = crusherMe->getUnitDirectionVector2D();
  1285. const Coord3D *crusheeDir = crusheeOther->getUnitDirectionVector2D();
  1286. Real crushPointOffsetDistance = crusheeOther->getGeometryInfo().getMajorRadius() / 2;
  1287. Coord3D crushPointOffset;
  1288. crushPointOffset.x = crusheeDir->x * crushPointOffsetDistance;
  1289. crushPointOffset.y = crusheeDir->y * crushPointOffsetDistance;
  1290. crushPointOffset.z = 0;
  1291. Coord3D comparisonCoord;
  1292. Real dx, dy;
  1293. /// @todo GS To account for different sized crushers, this should be redone as a box or circle test, not a point
  1294. // First decide which crush point has the shortest perp to our direction ray.
  1295. // Real bestPerp = 9999;
  1296. CrushEnum crushTarget = NO_CRUSH;
  1297. if( frontCrushed || backCrushed )
  1298. {
  1299. // Degenerate case; there is only one point to consider.
  1300. if( frontCrushed )
  1301. crushTarget = BACK_END_CRUSH;
  1302. else
  1303. crushTarget = FRONT_END_CRUSH;
  1304. }
  1305. else
  1306. {
  1307. // Else, there are three points.
  1308. Real frontPerpLength, backPerpLength, centerPerpLength;
  1309. Coord3D frontVector, backVector, centerVector;
  1310. {
  1311. comparisonCoord = *crusheePos;
  1312. comparisonCoord.x += crushPointOffset.x;
  1313. comparisonCoord.y += crushPointOffset.y;
  1314. frontVector = comparisonCoord;
  1315. frontVector.x -= crusherPos->x;
  1316. frontVector.y -= crusherPos->y; //vector from me to the front crush point
  1317. frontVector.z = 0;
  1318. Real rayLength = frontVector.x * dir->x + frontVector.y * dir->y;
  1319. Coord3D dirVector;
  1320. dirVector.x = rayLength * dir->x;
  1321. dirVector.y = rayLength * dir->y; //vector from me to point of perp along direction ray
  1322. dirVector.z = 0;
  1323. Coord3D perpVector;
  1324. perpVector.x = dirVector.x - frontVector.x;
  1325. perpVector.y = dirVector.y - frontVector.y; //vector from the front point perp to my direction
  1326. perpVector.z = 0;
  1327. frontPerpLength = perpVector.length();
  1328. }
  1329. {
  1330. comparisonCoord = *crusheePos;
  1331. comparisonCoord.x -= crushPointOffset.x;
  1332. comparisonCoord.y -= crushPointOffset.y;
  1333. backVector = comparisonCoord;
  1334. backVector.x -= crusherPos->x;
  1335. backVector.y -= crusherPos->y; //vector from me to the front crush point
  1336. backVector.z = 0;
  1337. Real rayLength = backVector.x * dir->x + backVector.y * dir->y;
  1338. Coord3D dirVector;
  1339. dirVector.x = rayLength * dir->x;
  1340. dirVector.y = rayLength * dir->y; //vector from me to point of perp along direction ray
  1341. dirVector.z = 0;
  1342. Coord3D perpVector;
  1343. perpVector.x = dirVector.x - backVector.x;
  1344. perpVector.y = dirVector.y - backVector.y; //vector from the front point perp to my direction
  1345. perpVector.z = 0;
  1346. backPerpLength = perpVector.length();
  1347. }
  1348. {
  1349. comparisonCoord = *crusheePos;
  1350. centerVector = comparisonCoord;
  1351. centerVector.x -= crusherPos->x;
  1352. centerVector.y -= crusherPos->y; //vector from me to the front crush point
  1353. centerVector.z = 0;
  1354. Real rayLength = centerVector.x * dir->x + centerVector.y * dir->y;
  1355. Coord3D dirVector;
  1356. dirVector.x = rayLength * dir->x;
  1357. dirVector.y = rayLength * dir->y; //vector from me to point of perp along direction ray
  1358. dirVector.z = 0;
  1359. Coord3D perpVector;
  1360. perpVector.x = dirVector.x - centerVector.x;
  1361. perpVector.y = dirVector.y - centerVector.y; //vector from the front point perp to my direction
  1362. perpVector.z = 0;
  1363. centerPerpLength = perpVector.length();
  1364. }
  1365. // Now find the shortest. Use the straightline distance to crush point as tie breaker
  1366. if( (frontPerpLength <= centerPerpLength) && (frontPerpLength <= backPerpLength) )
  1367. {
  1368. if( perpsLogicallyEqual(frontPerpLength, centerPerpLength)
  1369. || perpsLogicallyEqual(frontPerpLength, backPerpLength)
  1370. )
  1371. {
  1372. Real frontVectorLength = frontVector.length();
  1373. if( perpsLogicallyEqual(frontPerpLength, centerPerpLength) )
  1374. {
  1375. Real centerVectorLength = centerVector.length();
  1376. if( frontVectorLength < centerVectorLength )
  1377. crushTarget = FRONT_END_CRUSH;
  1378. else
  1379. crushTarget = TOTAL_CRUSH;
  1380. }
  1381. else if( perpsLogicallyEqual(frontPerpLength, backPerpLength) )
  1382. {
  1383. Real backVectorLength = backVector.length();
  1384. if( frontVectorLength < backVectorLength )
  1385. crushTarget = FRONT_END_CRUSH;
  1386. else
  1387. crushTarget = BACK_END_CRUSH;
  1388. }
  1389. }
  1390. else
  1391. {
  1392. crushTarget = FRONT_END_CRUSH;
  1393. }
  1394. }
  1395. else if( (backPerpLength <= centerPerpLength) && (backPerpLength <= frontPerpLength) )
  1396. {
  1397. if( perpsLogicallyEqual(backPerpLength, centerPerpLength)
  1398. || perpsLogicallyEqual(backPerpLength, frontPerpLength)
  1399. )
  1400. {
  1401. Real backVectorLength = backVector.length();
  1402. if( perpsLogicallyEqual(backPerpLength, centerPerpLength) )
  1403. {
  1404. Real centerVectorLength = centerVector.length();
  1405. if( backVectorLength < centerVectorLength )
  1406. crushTarget = BACK_END_CRUSH;
  1407. else
  1408. crushTarget = TOTAL_CRUSH;
  1409. }
  1410. else if( perpsLogicallyEqual(backPerpLength, frontPerpLength) )
  1411. {
  1412. Real frontVectorLength = frontVector.length();
  1413. if( backVectorLength < frontVectorLength )
  1414. crushTarget = BACK_END_CRUSH;
  1415. else
  1416. crushTarget = FRONT_END_CRUSH;
  1417. }
  1418. }
  1419. else
  1420. {
  1421. crushTarget = BACK_END_CRUSH;
  1422. }
  1423. }
  1424. else // centerperp is shortest
  1425. {
  1426. if( perpsLogicallyEqual(centerPerpLength, backPerpLength)
  1427. || perpsLogicallyEqual(centerPerpLength, frontPerpLength)
  1428. )
  1429. {
  1430. Real centerVectorLength = centerVector.length();
  1431. if( perpsLogicallyEqual(centerPerpLength, frontPerpLength) )
  1432. {
  1433. Real frontVectorLength = frontVector.length();
  1434. if( centerVectorLength < frontVectorLength )
  1435. crushTarget = TOTAL_CRUSH;
  1436. else
  1437. crushTarget = FRONT_END_CRUSH;
  1438. }
  1439. else if( perpsLogicallyEqual(centerPerpLength, backPerpLength) )
  1440. {
  1441. Real backVectorLength = backVector.length();
  1442. if( centerVectorLength < backVectorLength )
  1443. crushTarget = TOTAL_CRUSH;
  1444. else
  1445. crushTarget = BACK_END_CRUSH;
  1446. }
  1447. }
  1448. else
  1449. {
  1450. crushTarget = TOTAL_CRUSH;
  1451. }
  1452. }
  1453. }
  1454. Real distanceTooFarSquared = 2.25 * crushPointOffsetDistance * crushPointOffsetDistance;
  1455. // And just because there is only one crush point left doesn't
  1456. // mean we automatically get it. (1.5x)^2 means if we are outside the crushed
  1457. //front point, we will not auto get the back point (only 3/8 car spread)
  1458. // Then ask ourselves if we have passed the correct crush point (dot < 0).
  1459. if( crushTarget == TOTAL_CRUSH )
  1460. {
  1461. // Check the middle crush point
  1462. comparisonCoord = *crusheePos; //copy so can move to each crush point
  1463. dx = comparisonCoord.x - crusherPos->x;
  1464. dy = comparisonCoord.y - crusherPos->y;
  1465. Real dot = dir->x * dx + dir->y * dy;
  1466. Real distanceSquared = (dx * dx) + (dy * dy);
  1467. if( (dot < 0) && (distanceSquared < distanceTooFarSquared) )
  1468. {
  1469. // Past target point, but not too far in distance or angle.
  1470. crushIt = TRUE;
  1471. }
  1472. }
  1473. else if( crushTarget == FRONT_END_CRUSH )
  1474. {
  1475. // Check the front point.
  1476. comparisonCoord = *crusheePos;
  1477. comparisonCoord.x += crushPointOffset.x;
  1478. comparisonCoord.y += crushPointOffset.y;
  1479. dx = comparisonCoord.x - crusherPos->x;
  1480. dy = comparisonCoord.y - crusherPos->y;
  1481. Real dot = dir->x * dx + dir->y * dy;
  1482. Real distanceSquared = (dx * dx) + (dy * dy);
  1483. if( (dot < 0) && (distanceSquared < distanceTooFarSquared) )
  1484. {
  1485. // Past target point, but not too far in distance or angle.
  1486. crushIt = TRUE;
  1487. }
  1488. }
  1489. else if( crushTarget == BACK_END_CRUSH )
  1490. {
  1491. // Check back point
  1492. comparisonCoord = *crusheePos;
  1493. comparisonCoord.x -= crushPointOffset.x;
  1494. comparisonCoord.y -= crushPointOffset.y;
  1495. dx = comparisonCoord.x - crusherPos->x;
  1496. dy = comparisonCoord.y - crusherPos->y;
  1497. Real dot = dir->x * dx + dir->y * dy;
  1498. Real distanceSquared = (dx * dx) + (dy * dy);
  1499. if( (dot < 0) && (distanceSquared < distanceTooFarSquared) )
  1500. {
  1501. // Past target point, but not too far in distance or angle.
  1502. crushIt = TRUE;
  1503. }
  1504. }
  1505. if( crushIt )
  1506. {
  1507. // do a boat load of crush damage, and the onDie will handle cases like crushed car object
  1508. DamageInfo damageInfo;
  1509. damageInfo.in.m_damageType = DAMAGE_CRUSH;
  1510. damageInfo.in.m_deathType = DEATH_CRUSHED;
  1511. damageInfo.in.m_sourceID = crusherMe->getID();
  1512. damageInfo.in.m_amount = HUGE_DAMAGE_AMOUNT; // make sure they die
  1513. crusheeOther->attemptDamage( &damageInfo );
  1514. }
  1515. } // if crushable
  1516. return true;
  1517. }
  1518. // ------------------------------------------------------------------------------------------------
  1519. /** Test whether unit needs to die because of being on illegal cell, upside down, outside legal bounds **/
  1520. // ------------------------------------------------------------------------------------------------
  1521. void PhysicsBehavior::testStunnedUnitForDestruction(void)
  1522. {
  1523. // Only do test if unit is stunned
  1524. if (!getFlag(IS_STUNNED))
  1525. return;
  1526. // Grab the object
  1527. Object *obj = getObject();
  1528. const Coord3D *pos = obj->getPosition();
  1529. // If a stunned object is upside down when it hits the ground, kill it
  1530. if(obj->getTransformMatrix()->Get_Z_Vector().Z < 0.0f)
  1531. {
  1532. obj->kill();
  1533. return;
  1534. }
  1535. // Check if unit has exited playable area. If so, kill it
  1536. if (obj->isOffMap())
  1537. {
  1538. obj->kill();
  1539. return;
  1540. }
  1541. // Check for being in cells that the unit has no locomotor for
  1542. AIUpdateInterface *aiInt = obj->getAI();
  1543. if (!aiInt) return;
  1544. // Check for object being stuck on cliffs. If so kill it
  1545. if (TheTerrainLogic->isCliffCell(pos->x, pos->y) && !aiInt->hasLocomotorForSurface(LOCOMOTORSURFACE_CLIFF))
  1546. {
  1547. obj->kill();
  1548. return;
  1549. }
  1550. // Check for object being stuck on water. If so kill it
  1551. if (TheTerrainLogic->isUnderwater(pos->x, pos->y) && !aiInt->hasLocomotorForSurface(LOCOMOTORSURFACE_WATER))
  1552. {
  1553. obj->kill();
  1554. return;
  1555. }
  1556. }
  1557. // ------------------------------------------------------------------------------------------------
  1558. /** CRC */
  1559. // ------------------------------------------------------------------------------------------------
  1560. void PhysicsBehavior::crc( Xfer *xfer )
  1561. {
  1562. // extend base class
  1563. UpdateModule::crc( xfer );
  1564. } // end crc
  1565. // ------------------------------------------------------------------------------------------------
  1566. /** Xfer method
  1567. * Version Info:
  1568. * 1: Initial version */
  1569. // ------------------------------------------------------------------------------------------------
  1570. void PhysicsBehavior::xfer( Xfer *xfer )
  1571. {
  1572. // version
  1573. const XferVersion currentVersion = 2;
  1574. XferVersion version = currentVersion;
  1575. xfer->xferVersion( &version, currentVersion );
  1576. // extend base class
  1577. UpdateModule::xfer( xfer );
  1578. // yaw rate
  1579. xfer->xferReal( &m_yawRate );
  1580. // roll rate
  1581. xfer->xferReal( &m_rollRate );
  1582. // pitch rate
  1583. xfer->xferReal( &m_pitchRate );
  1584. // we dont' need to mess with sound stuff
  1585. // m_bounceSound <---- do nothing with this
  1586. // accel
  1587. xfer->xferCoord3D( &m_accel );
  1588. // prev accel
  1589. xfer->xferCoord3D( &m_prevAccel );
  1590. // velocity
  1591. xfer->xferCoord3D( &m_vel );
  1592. // prevPos (now defunct)
  1593. if (version < 2)
  1594. {
  1595. Coord3D tmp;
  1596. tmp.zero();
  1597. xfer->xferCoord3D( &tmp );
  1598. }
  1599. // turning
  1600. xfer->xferUser( &m_turning, sizeof( PhysicsTurningType ) );
  1601. // ignore collisions with
  1602. xfer->xferObjectID( &m_ignoreCollisionsWith );
  1603. // flags
  1604. xfer->xferInt( &m_flags );
  1605. // mass
  1606. xfer->xferReal( &m_mass );
  1607. // current overlap
  1608. xfer->xferObjectID( &m_currentOverlap );
  1609. // previous overlap
  1610. xfer->xferObjectID( &m_previousOverlap );
  1611. // motive force applied
  1612. xfer->xferUnsignedInt( &m_motiveForceExpires );
  1613. // extra bounciness
  1614. xfer->xferReal( &m_extraBounciness );
  1615. // extra friction
  1616. xfer->xferReal( &m_extraFriction );
  1617. // we don't need to save/load this, it is acquired on object creation
  1618. // m_pui <---- do nothing with this
  1619. // mag of current vel
  1620. xfer->xferReal( &m_velMag );
  1621. } // end xfer
  1622. // ------------------------------------------------------------------------------------------------
  1623. /** Load post process */
  1624. // ------------------------------------------------------------------------------------------------
  1625. void PhysicsBehavior::loadPostProcess( void )
  1626. {
  1627. // extend base class
  1628. UpdateModule::loadPostProcess();
  1629. } // end loadPostProcess