Team Fortress 2 Source Code as on 22/4/2020
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.

742 lines
22 KiB

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