Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

750 lines
22 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "cbase.h"
  9. #include "physics_impact_damage.h"
  10. #include "shareddefs.h"
  11. #include "vphysics/friction.h"
  12. #include "vphysics/player_controller.h"
  13. #include "world.h"
  14. #ifdef PORTAL2
  15. #include "portal_grabcontroller_shared.h"
  16. #endif
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. //==============================================================================================
  20. // PLAYER PHYSICS DAMAGE TABLE
  21. //==============================================================================================
  22. static impactentry_t playerLinearTable[] =
  23. {
  24. { 150*150, 5 },
  25. { 250*250, 10 },
  26. { 450*450, 20 },
  27. { 550*550, 50 },
  28. { 700*700, 100 },
  29. { 1000*1000, 500 },
  30. };
  31. static impactentry_t playerAngularTable[] =
  32. {
  33. { 100*100, 10 },
  34. { 150*150, 20 },
  35. { 200*200, 50 },
  36. { 300*300, 500 },
  37. };
  38. impactdamagetable_t gDefaultPlayerImpactDamageTable =
  39. {
  40. playerLinearTable,
  41. playerAngularTable,
  42. ARRAYSIZE(playerLinearTable),
  43. ARRAYSIZE(playerAngularTable),
  44. 24*24.0f, // minimum linear speed
  45. 360*360.0f, // minimum angular speed
  46. 2.0f, // can't take damage from anything under 2kg
  47. 5.0f, // anything less than 5kg is "small"
  48. 5.0f, // never take more than 5 pts of damage from anything under 5kg
  49. 36*36.0f, // <5kg objects must go faster than 36 in/s to do damage
  50. 0.0f, // large mass in kg (no large mass effects)
  51. 1.0f, // large mass scale
  52. 2.0f, // large mass falling scale
  53. 320.0f, // min velocity for player speed to cause damage
  54. };
  55. //==============================================================================================
  56. // PLAYER-IN-VEHICLE PHYSICS DAMAGE TABLE
  57. //==============================================================================================
  58. static impactentry_t playerVehicleLinearTable[] =
  59. {
  60. { 450*450, 5 },
  61. { 600*600, 10 },
  62. { 700*700, 25 },
  63. { 1000*1000, 50 },
  64. { 1500*1500, 100 },
  65. { 2000*2000, 500 },
  66. };
  67. static impactentry_t playerVehicleAngularTable[] =
  68. {
  69. { 100*100, 10 },
  70. { 150*150, 20 },
  71. { 200*200, 50 },
  72. { 300*300, 500 },
  73. };
  74. impactdamagetable_t gDefaultPlayerVehicleImpactDamageTable =
  75. {
  76. playerVehicleLinearTable,
  77. playerVehicleAngularTable,
  78. ARRAYSIZE(playerVehicleLinearTable),
  79. ARRAYSIZE(playerVehicleAngularTable),
  80. 24*24, // minimum linear speed
  81. 360*360, // minimum angular speed
  82. 80, // can't take damage from anything under 80 kg
  83. 150, // anything less than 150kg is "small"
  84. 5, // never take more than 5 pts of damage from anything under 150kg
  85. 36*36, // <150kg objects must go faster than 36 in/s to do damage
  86. 0, // large mass in kg (no large mass effects)
  87. 1.0f, // large mass scale
  88. 1.0f, // large mass falling scale
  89. 0.0f, // min vel
  90. };
  91. //==============================================================================================
  92. // NPC PHYSICS DAMAGE TABLE
  93. //==============================================================================================
  94. static impactentry_t npcLinearTable[] =
  95. {
  96. { 150*150, 5 },
  97. { 250*250, 10 },
  98. { 350*350, 50 },
  99. { 500*500, 100 },
  100. { 1000*1000, 500 },
  101. };
  102. static impactentry_t npcAngularTable[] =
  103. {
  104. { 100*100, 10 },
  105. { 150*150, 25 },
  106. { 200*200, 50 },
  107. { 250*250, 500 },
  108. };
  109. impactdamagetable_t gDefaultNPCImpactDamageTable =
  110. {
  111. npcLinearTable,
  112. npcAngularTable,
  113. ARRAYSIZE(npcLinearTable),
  114. ARRAYSIZE(npcAngularTable),
  115. 24*24, // minimum linear speed squared
  116. 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
  117. 2, // can't take damage from anything under 2kg
  118. 5, // anything less than 5kg is "small"
  119. 5, // never take more than 5 pts of damage from anything under 5kg
  120. 36*36, // <5kg objects must go faster than 36 in/s to do damage
  121. VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
  122. 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
  123. 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
  124. 0.0f, // min vel
  125. };
  126. //==============================================================================================
  127. // GLASS DAMAGE TABLE
  128. //==============================================================================================
  129. static impactentry_t glassLinearTable[] =
  130. {
  131. { 25*25, 10 },
  132. { 50*50, 20 },
  133. { 100*100, 50 },
  134. { 200*200, 75 },
  135. { 500*500, 100 },
  136. { 250*250, 500 },
  137. };
  138. static impactentry_t glassAngularTable[] =
  139. {
  140. { 50*50, 25 },
  141. { 100*100, 50 },
  142. { 200*200, 100 },
  143. { 250*250, 500 },
  144. };
  145. impactdamagetable_t gGlassImpactDamageTable =
  146. {
  147. glassLinearTable,
  148. glassAngularTable,
  149. ARRAYSIZE(glassLinearTable),
  150. ARRAYSIZE(glassAngularTable),
  151. 8*8, // minimum linear speed squared
  152. 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
  153. 2, // can't take damage from anything under 2kg
  154. 1, // anything less than 1kg is "small"
  155. 10, // never take more than 10 pts of damage from anything under 1kg
  156. 8*8, // <1kg objects must go faster than 8 in/s to do damage
  157. 50, // large mass in kg
  158. 4, // large mass scale (anything over 50kg does 4X as much energy to read from damage table)
  159. 0.0f, // min vel
  160. };
  161. //==============================================================================================
  162. // PHYSICS TABLE NAMES
  163. //==============================================================================================
  164. struct damagetable_t
  165. {
  166. const char *pszTableName;
  167. impactdamagetable_t *pTable;
  168. };
  169. static damagetable_t gDamageTableRegistry[] =
  170. {
  171. {
  172. "player",
  173. &gDefaultPlayerImpactDamageTable,
  174. },
  175. {
  176. "player_vehicle",
  177. &gDefaultPlayerVehicleImpactDamageTable,
  178. },
  179. {
  180. "npc",
  181. &gDefaultNPCImpactDamageTable,
  182. },
  183. {
  184. "glass",
  185. &gGlassImpactDamageTable,
  186. },
  187. };
  188. //==============================================================================================
  189. //-----------------------------------------------------------------------------
  190. // Purpose:
  191. //-----------------------------------------------------------------------------
  192. float ReadDamageTable( impactentry_t *pTable, int tableCount, float impulse, bool bDebug )
  193. {
  194. if ( pTable )
  195. {
  196. int i;
  197. for ( i = 0; i < tableCount; i++ )
  198. {
  199. if ( impulse < pTable[i].impulse )
  200. break;
  201. }
  202. if ( i > 0 )
  203. {
  204. i--;
  205. if ( bDebug )
  206. {
  207. Msg("Damage %.0f, energy %.0f\n", pTable[i].damage, FastSqrt(impulse) );
  208. }
  209. return pTable[i].damage;
  210. }
  211. }
  212. return 0;
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Purpose:
  216. //-----------------------------------------------------------------------------
  217. float CalculatePhysicsImpactDamage( int index, gamevcollisionevent_t *pEvent, const impactdamagetable_t &table, float energyScale, bool allowStaticDamage, int &damageType, bool bDamageFromHeldObjects )
  218. {
  219. damageType = DMG_CRUSH;
  220. int otherIndex = !index;
  221. // UNDONE: Expose a flag for self-inflicted damage? Can't think of a valid case so far.
  222. if ( pEvent->pEntities[0] == pEvent->pEntities[1] )
  223. return 0;
  224. if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_NO_NPC_IMPACT_DMG )
  225. {
  226. if( pEvent->pEntities[index]->IsNPC() || pEvent->pEntities[index]->IsPlayer() )
  227. {
  228. return 0;
  229. }
  230. }
  231. // use implicit velocities on ragdolls since they may have high constraint velocities that aren't actually executed, just pushed through contacts
  232. if (( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) && pEvent->pEntities[index]->IsPlayer() )
  233. {
  234. pEvent->pObjects[otherIndex]->GetImplicitVelocity( &pEvent->preVelocity[otherIndex], &pEvent->preAngularVelocity[otherIndex] );
  235. }
  236. // Dissolving impact damage results in death always.
  237. if ( ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_DISSOLVE ) &&
  238. !pEvent->pEntities[index]->IsEFlagSet(EFL_NO_DISSOLVE) )
  239. {
  240. damageType |= DMG_DISSOLVE;
  241. return 1000;
  242. }
  243. if ( energyScale <= 0.0f )
  244. return 0;
  245. const int gameFlagsNoDamage = FVPHYSICS_CONSTRAINT_STATIC | FVPHYSICS_NO_IMPACT_DMG;
  246. // NOTE: Crushing damage is handled by stress calcs in vphysics update functions, this is ONLY impact damage
  247. // this is a non-moving object due to a constraint - no damage
  248. if ( pEvent->pObjects[otherIndex]->GetGameFlags() & gameFlagsNoDamage )
  249. return 0;
  250. // If it doesn't take damage from held objects and the object is being held - no damage
  251. if ( !bDamageFromHeldObjects && ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
  252. {
  253. // If it doesn't take damage from held objects - no damage
  254. if ( !bDamageFromHeldObjects )
  255. return 0;
  256. }
  257. if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY )
  258. {
  259. // UNDONE: Add up mass here for car wheels and prop_ragdoll pieces?
  260. IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
  261. int count = pEvent->pEntities[otherIndex]->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
  262. for ( int i = 0; i < count; i++ )
  263. {
  264. if ( pList[i]->GetGameFlags() & gameFlagsNoDamage )
  265. return 0;
  266. }
  267. }
  268. if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  269. {
  270. // players can't damage held objects
  271. if ( pEvent->pEntities[otherIndex]->IsPlayer() )
  272. return 0;
  273. allowStaticDamage = false;
  274. }
  275. #if 0
  276. {
  277. PhysGetDamageInflictorVelocityStartOfFrame( pEvent->pObjects[otherIndex], pEvent->preVelocity[otherIndex], pEvent->preAngularVelocity[otherIndex] );
  278. }
  279. #endif
  280. float otherSpeedSqr = pEvent->preVelocity[otherIndex].LengthSqr();
  281. float otherAngSqr = 0;
  282. // factor in angular for sharp objects
  283. if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_SLICE )
  284. {
  285. otherAngSqr = pEvent->preAngularVelocity[otherIndex].LengthSqr();
  286. }
  287. float otherMass = pEvent->pObjects[otherIndex]->GetMass();
  288. if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  289. {
  290. if ( gpGlobals->maxClients == 1 )
  291. {
  292. // if the player is holding the object, use it's real mass (player holding reduced the mass)
  293. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  294. if ( pPlayer )
  295. {
  296. otherMass = pPlayer->GetHeldObjectMass( pEvent->pObjects[otherIndex] );
  297. }
  298. }
  299. }
  300. // NOTE: sum the mass of each object in this system for the purpose of damage
  301. if ( pEvent->pEntities[otherIndex] && (pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) )
  302. {
  303. otherMass = PhysGetEntityMass( pEvent->pEntities[otherIndex] );
  304. }
  305. if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_HEAVY_OBJECT )
  306. {
  307. otherMass = table.largeMassMin;
  308. if ( energyScale < 2.0f )
  309. {
  310. energyScale = 2.0f;
  311. }
  312. }
  313. // UNDONE: allowStaticDamage is a hack - work out some method for
  314. // breakable props to impact the world and break!!
  315. if ( !allowStaticDamage )
  316. {
  317. if ( otherMass < table.minMass )
  318. return 0;
  319. // check to see if the object is small
  320. if ( otherMass < table.smallMassMax && otherSpeedSqr < table.smallMassMinSpeedSqr )
  321. return 0;
  322. if ( otherSpeedSqr < table.minSpeedSqr && otherAngSqr < table.minRotSpeedSqr )
  323. return 0;
  324. }
  325. // Add extra oomph for floating objects
  326. if ( pEvent->pEntities[index]->IsFloating() && !pEvent->pEntities[otherIndex]->IsWorld() )
  327. {
  328. if ( energyScale < 3.0f )
  329. {
  330. energyScale = 3.0f;
  331. }
  332. }
  333. float damage = 0;
  334. bool bDebug = false;//(&table == &gDefaultPlayerImpactDamageTable);
  335. // don't ever take spin damage from slowly spinning objects
  336. if ( otherAngSqr > table.minRotSpeedSqr )
  337. {
  338. Vector otherInertia = pEvent->pObjects[otherIndex]->GetInertia();
  339. float angularMom = DotProductAbs( otherInertia, pEvent->preAngularVelocity[otherIndex] );
  340. damage = ReadDamageTable( table.angularTable, table.angularCount, angularMom * energyScale, bDebug );
  341. if ( damage > 0 )
  342. {
  343. // Msg("Spin : %.1f, Damage %.0f\n", FastSqrt(angularMom), damage );
  344. damageType |= DMG_SLASH;
  345. }
  346. }
  347. float deltaV = pEvent->preVelocity[index].Length() - pEvent->postVelocity[index].Length();
  348. float mass = pEvent->pObjects[index]->GetMass();
  349. // If I lost speed, and I lost less than min velocity, then filter out this energy
  350. if ( deltaV > 0 && deltaV < table.myMinVelocity )
  351. {
  352. deltaV = 0;
  353. }
  354. float eliminatedEnergy = deltaV * deltaV * mass;
  355. deltaV = pEvent->preVelocity[otherIndex].Length() - pEvent->postVelocity[otherIndex].Length();
  356. float otherEliminatedEnergy = deltaV * deltaV * otherMass;
  357. // exaggerate the effects of really large objects
  358. if ( otherMass >= table.largeMassMin )
  359. {
  360. otherEliminatedEnergy *= table.largeMassScale;
  361. float dz = pEvent->preVelocity[otherIndex].z - pEvent->postVelocity[otherIndex].z;
  362. if ( deltaV > 0 && dz < 0 && pEvent->preVelocity[otherIndex].z < 0 )
  363. {
  364. float factor = fabs(dz / deltaV);
  365. otherEliminatedEnergy *= (1 + factor * (table.largeMassFallingScale - 1.0f));
  366. }
  367. }
  368. eliminatedEnergy += otherEliminatedEnergy;
  369. // now in units of this character's speed squared
  370. float invMass = pEvent->pObjects[index]->GetInvMass();
  371. if ( !pEvent->pObjects[index]->IsMoveable() )
  372. {
  373. // inv mass is zero, but impact damage is enabled on this
  374. // prop, so recompute:
  375. invMass = 1.0f / pEvent->pObjects[index]->GetMass();
  376. }
  377. else if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  378. {
  379. if ( gpGlobals->maxClients == 1 )
  380. {
  381. // if the player is holding the object, use it's real mass (player holding reduced the mass)
  382. #ifdef PORTAL2
  383. CBasePlayer *pPlayer = GetPlayerHoldingEntity( static_cast<CBaseEntity *>( pEvent->pObjects[index]->GetGameData() ) );
  384. #else
  385. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  386. #endif
  387. if ( pPlayer )
  388. {
  389. float mass = pPlayer->GetHeldObjectMass( pEvent->pObjects[index] );
  390. if ( mass > 0 )
  391. {
  392. invMass = 1.0f / mass;
  393. }
  394. }
  395. }
  396. }
  397. eliminatedEnergy *= invMass * energyScale;
  398. damage += ReadDamageTable( table.linearTable, table.linearCount, eliminatedEnergy, bDebug );
  399. if ( !pEvent->pObjects[otherIndex]->IsStatic() && otherMass < table.smallMassMax && table.smallMassCap > 0 )
  400. {
  401. damage = clamp( damage, 0, table.smallMassCap );
  402. }
  403. return damage;
  404. }
  405. //-----------------------------------------------------------------------------
  406. // Purpose:
  407. //-----------------------------------------------------------------------------
  408. float CalculateDefaultPhysicsDamage( int index, gamevcollisionevent_t *pEvent, float energyScale, bool allowStaticDamage, int &damageType, string_t iszDamageTableName, bool bDamageFromHeldObjects )
  409. {
  410. // If we have a specified damage table, find it and use it instead
  411. if ( iszDamageTableName != NULL_STRING )
  412. {
  413. for ( int i = 0; i < ARRAYSIZE(gDamageTableRegistry); i++ )
  414. {
  415. if ( !Q_strcmp( gDamageTableRegistry[i].pszTableName, STRING(iszDamageTableName) ) )
  416. return CalculatePhysicsImpactDamage( index, pEvent, *(gDamageTableRegistry[i].pTable), energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects );
  417. }
  418. Warning("Failed to find custom physics damage table name: %s\n", STRING(iszDamageTableName) );
  419. }
  420. return CalculatePhysicsImpactDamage( index, pEvent, gDefaultNPCImpactDamageTable, energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects );
  421. }
  422. static bool IsPhysicallyControlled( CBaseEntity *pEntity, IPhysicsObject *pPhysics )
  423. {
  424. bool isPhysical = false;
  425. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  426. {
  427. isPhysical = true;
  428. }
  429. else
  430. {
  431. if ( pPhysics->GetShadowController() )
  432. {
  433. isPhysical = pPhysics->GetShadowController()->IsPhysicallyControlled();
  434. }
  435. }
  436. return isPhysical;
  437. }
  438. float CalculateObjectStress( IPhysicsObject *pObject, CBaseEntity *pInputOwnerEntity, vphysics_objectstress_t *pOutput )
  439. {
  440. CUtlVector< CBaseEntity * > pObjectList;
  441. CUtlVector< Vector > objectForce;
  442. bool hasLargeObject = false;
  443. // add a slot for static objects
  444. pObjectList.AddToTail( NULL );
  445. objectForce.AddToTail( vec3_origin );
  446. // add a slot for friendly objects
  447. pObjectList.AddToTail( NULL );
  448. objectForce.AddToTail( vec3_origin );
  449. CBaseCombatCharacter *pBCC = pInputOwnerEntity->MyCombatCharacterPointer();
  450. IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot();
  451. float objMass = pObject->GetMass();
  452. while ( pSnapshot->IsValid() )
  453. {
  454. float force = pSnapshot->GetNormalForce();
  455. if ( force > 0.0f )
  456. {
  457. IPhysicsObject *pOther = pSnapshot->GetObject(1);
  458. CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
  459. if ( !pOtherEntity )
  460. {
  461. // object was just deleted, but we still have a contact point this frame...
  462. // just assume it came from the world.
  463. pOtherEntity = GetWorldEntity();
  464. }
  465. CBaseEntity *pOtherOwner = pOtherEntity;
  466. if ( pOtherEntity->GetOwnerEntity() )
  467. {
  468. pOtherOwner = pOtherEntity->GetOwnerEntity();
  469. }
  470. int outIndex = 0;
  471. if ( !pOther->IsMoveable() )
  472. {
  473. outIndex = 0;
  474. }
  475. // NavIgnored objects are often being pushed by a friendly
  476. else if ( pBCC && (pBCC->IRelationType( pOtherOwner ) == D_LI || pOtherEntity->IsNavIgnored()) )
  477. {
  478. outIndex = 1;
  479. }
  480. // player held objects do no stress
  481. else if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  482. {
  483. outIndex = 1;
  484. }
  485. else
  486. {
  487. if ( pOther->GetMass() >= VPHYSICS_LARGE_OBJECT_MASS )
  488. {
  489. if ( pInputOwnerEntity->GetGroundEntity() != pOtherEntity)
  490. {
  491. hasLargeObject = true;
  492. }
  493. }
  494. // moveable, non-friendly
  495. // aggregate contacts over each object to avoid greater stress in multiple contact cases
  496. // NOTE: Contacts should be in order, so this shouldn't ever search, but just in case
  497. outIndex = pObjectList.Count();
  498. for ( int i = pObjectList.Count()-1; i >= 2; --i )
  499. {
  500. if ( pObjectList[i] == pOtherOwner )
  501. {
  502. outIndex = i;
  503. break;
  504. }
  505. }
  506. if ( outIndex == pObjectList.Count() )
  507. {
  508. pObjectList.AddToTail( pOtherOwner );
  509. objectForce.AddToTail( vec3_origin );
  510. }
  511. }
  512. if ( outIndex != 0 && pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && !IsPhysicallyControlled(pOtherEntity, pOther) )
  513. {
  514. // UNDONE: Test this! This is to remove any shadow/shadow stress. The game should handle this with blocked/damage
  515. force = 0.0f;
  516. }
  517. Vector normal;
  518. pSnapshot->GetSurfaceNormal( normal );
  519. objectForce[outIndex] += normal * force;
  520. }
  521. pSnapshot->NextFrictionData();
  522. }
  523. pObject->DestroyFrictionSnapshot( pSnapshot );
  524. pSnapshot = NULL;
  525. // clear out all friendly force
  526. objectForce[1].Init();
  527. float sum = 0;
  528. Vector negativeForce = vec3_origin;
  529. Vector positiveForce = vec3_origin;
  530. Assert( pObjectList.Count() == objectForce.Count() );
  531. for ( int objectIndex = pObjectList.Count()-1; objectIndex >= 0; --objectIndex )
  532. {
  533. sum += objectForce[objectIndex].Length();
  534. for ( int i = 0; i < 3; i++ )
  535. {
  536. if ( objectForce[objectIndex][i] < 0 )
  537. {
  538. negativeForce[i] -= objectForce[objectIndex][i];
  539. }
  540. else
  541. {
  542. positiveForce[i] += objectForce[objectIndex][i];
  543. }
  544. }
  545. }
  546. // "external" stress is two way (something pushes on the object and something else pushes back)
  547. // so the set of minimum values per component are the projections of the two-way force
  548. // "internal" stress is one way (the object is pushing against something OR something pushing back)
  549. // the momentum must have come from inside the object (gravity, controller, etc)
  550. Vector internalForce = vec3_origin;
  551. Vector externalForce = vec3_origin;
  552. for ( int i = 0; i < 3; i++ )
  553. {
  554. if ( negativeForce[i] < positiveForce[i] )
  555. {
  556. internalForce[i] = positiveForce[i] - negativeForce[i];
  557. externalForce[i] = negativeForce[i];
  558. }
  559. else
  560. {
  561. internalForce[i] = negativeForce[i] - positiveForce[i];
  562. externalForce[i] = positiveForce[i];
  563. }
  564. }
  565. // sum is kg in / s
  566. Vector gravVector;
  567. physenv->GetGravity( &gravVector );
  568. float gravity = gravVector.Length();
  569. if ( pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && pObject->IsMoveable() )
  570. {
  571. Vector lastVel;
  572. lastVel.Init();
  573. if ( pObject->GetShadowController() )
  574. {
  575. pObject->GetShadowController()->GetLastImpulse( &lastVel );
  576. }
  577. else
  578. {
  579. if ( ( pObject->GetCallbackFlags() & CALLBACK_IS_PLAYER_CONTROLLER ) )
  580. {
  581. CBasePlayer *pPlayer = ToBasePlayer( pInputOwnerEntity );
  582. IPhysicsPlayerController *pController = pPlayer ? pPlayer->GetPhysicsController() : NULL;
  583. if ( pController )
  584. {
  585. pController->GetLastImpulse( &lastVel );
  586. }
  587. }
  588. }
  589. // Work in progress...
  590. // Peek into the controller for this object. Look at the input velocity and make sure it's all
  591. // accounted for in the computed stress. If not, redistribute external to internal as it's
  592. // probably being reflected in a way we can't measure here.
  593. float inputLen = lastVel.Length() * (1.0f / physenv->GetSimulationTimestep()) * objMass;
  594. if ( inputLen > 0.0f )
  595. {
  596. float internalLen = internalForce.Length();
  597. if ( internalLen < inputLen )
  598. {
  599. float ratio = internalLen / inputLen;
  600. Vector delta = internalForce * (1.0f - ratio);
  601. internalForce += delta;
  602. float deltaLen = delta.Length();
  603. sum -= deltaLen;
  604. float extLen = VectorNormalize(externalForce) - deltaLen;
  605. if ( extLen < 0 )
  606. {
  607. extLen = 0;
  608. }
  609. externalForce *= extLen;
  610. }
  611. }
  612. }
  613. float invGravity = gravity;
  614. if ( invGravity <= 0 )
  615. {
  616. invGravity = 1.0f;
  617. }
  618. else
  619. {
  620. invGravity = 1.0f / invGravity;
  621. }
  622. sum *= invGravity;
  623. internalForce *= invGravity;
  624. externalForce *= invGravity;
  625. if ( !pObject->IsMoveable() )
  626. {
  627. // the above algorithm will see almost all force as internal if the object is not moveable
  628. // (it doesn't push on anything else, so nothing is reciprocated)
  629. // exceptions for friction of a single other object with multiple contact points on this object
  630. // But the game wants to see it all as external because obviously the object can't move, so it can't have
  631. // internal stress
  632. externalForce = internalForce;
  633. internalForce.Init();
  634. if ( !pObject->IsStatic() )
  635. {
  636. sum += objMass;
  637. }
  638. }
  639. else
  640. {
  641. // assume object is at rest
  642. if ( sum > objMass )
  643. {
  644. sum = objMass + (sum-objMass) * 0.5;
  645. }
  646. }
  647. if ( pOutput )
  648. {
  649. pOutput->exertedStress = internalForce.Length();
  650. pOutput->receivedStress = externalForce.Length();
  651. pOutput->hasNonStaticStress = pObjectList.Count() > 2 ? true : false;
  652. pOutput->hasLargeObjectContact = hasLargeObject;
  653. }
  654. // sum is now kg
  655. return sum;
  656. }