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.
736 lines
22 KiB
736 lines
22 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "physics_impact_damage.h" |
|
#include "shareddefs.h" |
|
#include "vphysics/friction.h" |
|
#include "vphysics/player_controller.h" |
|
#include "world.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//============================================================================================== |
|
// PLAYER PHYSICS DAMAGE TABLE |
|
//============================================================================================== |
|
static impactentry_t playerLinearTable[] = |
|
{ |
|
{ 150*150, 5 }, |
|
{ 250*250, 10 }, |
|
{ 450*450, 20 }, |
|
{ 550*550, 50 }, |
|
{ 700*700, 100 }, |
|
{ 1000*1000, 500 }, |
|
}; |
|
|
|
static impactentry_t playerAngularTable[] = |
|
{ |
|
{ 100*100, 10 }, |
|
{ 150*150, 20 }, |
|
{ 200*200, 50 }, |
|
{ 300*300, 500 }, |
|
}; |
|
|
|
impactdamagetable_t gDefaultPlayerImpactDamageTable = |
|
{ |
|
playerLinearTable, |
|
playerAngularTable, |
|
|
|
ARRAYSIZE(playerLinearTable), |
|
ARRAYSIZE(playerAngularTable), |
|
|
|
24*24.0f, // minimum linear speed |
|
360*360.0f, // minimum angular speed |
|
2.0f, // can't take damage from anything under 2kg |
|
|
|
5.0f, // anything less than 5kg is "small" |
|
5.0f, // never take more than 5 pts of damage from anything under 5kg |
|
36*36.0f, // <5kg objects must go faster than 36 in/s to do damage |
|
|
|
0.0f, // large mass in kg (no large mass effects) |
|
1.0f, // large mass scale |
|
2.0f, // large mass falling scale |
|
320.0f, // min velocity for player speed to cause damage |
|
|
|
}; |
|
|
|
//============================================================================================== |
|
// PLAYER-IN-VEHICLE PHYSICS DAMAGE TABLE |
|
//============================================================================================== |
|
static impactentry_t playerVehicleLinearTable[] = |
|
{ |
|
{ 450*450, 5 }, |
|
{ 600*600, 10 }, |
|
{ 700*700, 25 }, |
|
{ 1000*1000, 50 }, |
|
{ 1500*1500, 100 }, |
|
{ 2000*2000, 500 }, |
|
}; |
|
|
|
static impactentry_t playerVehicleAngularTable[] = |
|
{ |
|
{ 100*100, 10 }, |
|
{ 150*150, 20 }, |
|
{ 200*200, 50 }, |
|
{ 300*300, 500 }, |
|
}; |
|
|
|
impactdamagetable_t gDefaultPlayerVehicleImpactDamageTable = |
|
{ |
|
playerVehicleLinearTable, |
|
playerVehicleAngularTable, |
|
|
|
ARRAYSIZE(playerVehicleLinearTable), |
|
ARRAYSIZE(playerVehicleAngularTable), |
|
|
|
24*24, // minimum linear speed |
|
360*360, // minimum angular speed |
|
80, // can't take damage from anything under 80 kg |
|
|
|
150, // anything less than 150kg is "small" |
|
5, // never take more than 5 pts of damage from anything under 150kg |
|
36*36, // <150kg objects must go faster than 36 in/s to do damage |
|
|
|
0, // large mass in kg (no large mass effects) |
|
1.0f, // large mass scale |
|
1.0f, // large mass falling scale |
|
0.0f, // min vel |
|
}; |
|
|
|
|
|
//============================================================================================== |
|
// NPC PHYSICS DAMAGE TABLE |
|
//============================================================================================== |
|
static impactentry_t npcLinearTable[] = |
|
{ |
|
{ 150*150, 5 }, |
|
{ 250*250, 10 }, |
|
{ 350*350, 50 }, |
|
{ 500*500, 100 }, |
|
{ 1000*1000, 500 }, |
|
}; |
|
|
|
static impactentry_t npcAngularTable[] = |
|
{ |
|
{ 100*100, 10 }, |
|
{ 150*150, 25 }, |
|
{ 200*200, 50 }, |
|
{ 250*250, 500 }, |
|
}; |
|
|
|
impactdamagetable_t gDefaultNPCImpactDamageTable = |
|
{ |
|
npcLinearTable, |
|
npcAngularTable, |
|
|
|
ARRAYSIZE(npcLinearTable), |
|
ARRAYSIZE(npcAngularTable), |
|
|
|
24*24, // minimum linear speed squared |
|
360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage) |
|
2, // can't take damage from anything under 2kg |
|
|
|
5, // anything less than 5kg is "small" |
|
5, // never take more than 5 pts of damage from anything under 5kg |
|
36*36, // <5kg objects must go faster than 36 in/s to do damage |
|
|
|
VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg |
|
4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table) |
|
5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway) |
|
0.0f, // min vel |
|
}; |
|
|
|
//============================================================================================== |
|
// GLASS DAMAGE TABLE |
|
//============================================================================================== |
|
static impactentry_t glassLinearTable[] = |
|
{ |
|
{ 25*25, 10 }, |
|
{ 50*50, 20 }, |
|
{ 100*100, 50 }, |
|
{ 200*200, 75 }, |
|
{ 500*500, 100 }, |
|
{ 250*250, 500 }, |
|
}; |
|
|
|
static impactentry_t glassAngularTable[] = |
|
{ |
|
{ 50*50, 25 }, |
|
{ 100*100, 50 }, |
|
{ 200*200, 100 }, |
|
{ 250*250, 500 }, |
|
}; |
|
|
|
impactdamagetable_t gGlassImpactDamageTable = |
|
{ |
|
glassLinearTable, |
|
glassAngularTable, |
|
|
|
ARRAYSIZE(glassLinearTable), |
|
ARRAYSIZE(glassAngularTable), |
|
|
|
8*8, // minimum linear speed squared |
|
360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage) |
|
2, // can't take damage from anything under 2kg |
|
|
|
1, // anything less than 1kg is "small" |
|
10, // never take more than 10 pts of damage from anything under 1kg |
|
8*8, // <1kg objects must go faster than 8 in/s to do damage |
|
|
|
50, // large mass in kg |
|
4, // large mass scale (anything over 50kg does 4X as much energy to read from damage table) |
|
0.0f, // min vel |
|
}; |
|
|
|
//============================================================================================== |
|
// PHYSICS TABLE NAMES |
|
//============================================================================================== |
|
struct damagetable_t |
|
{ |
|
const char *pszTableName; |
|
impactdamagetable_t *pTable; |
|
}; |
|
|
|
static damagetable_t gDamageTableRegistry[] = |
|
{ |
|
{ |
|
"player", |
|
&gDefaultPlayerImpactDamageTable, |
|
}, |
|
{ |
|
"player_vehicle", |
|
&gDefaultPlayerVehicleImpactDamageTable, |
|
}, |
|
{ |
|
"npc", |
|
&gDefaultNPCImpactDamageTable, |
|
}, |
|
{ |
|
"glass", |
|
&gGlassImpactDamageTable, |
|
}, |
|
}; |
|
|
|
//============================================================================================== |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float ReadDamageTable( impactentry_t *pTable, int tableCount, float impulse, bool bDebug ) |
|
{ |
|
if ( pTable ) |
|
{ |
|
int i; |
|
for ( i = 0; i < tableCount; i++ ) |
|
{ |
|
if ( impulse < pTable[i].impulse ) |
|
break; |
|
} |
|
if ( i > 0 ) |
|
{ |
|
i--; |
|
if ( bDebug ) |
|
{ |
|
Msg("Damage %.0f, energy %.0f\n", pTable[i].damage, FastSqrt(impulse) ); |
|
} |
|
return pTable[i].damage; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CalculatePhysicsImpactDamage( int index, gamevcollisionevent_t *pEvent, const impactdamagetable_t &table, float energyScale, bool allowStaticDamage, int &damageType, bool bDamageFromHeldObjects ) |
|
{ |
|
damageType = DMG_CRUSH; |
|
int otherIndex = !index; |
|
|
|
// UNDONE: Expose a flag for self-inflicted damage? Can't think of a valid case so far. |
|
if ( pEvent->pEntities[0] == pEvent->pEntities[1] ) |
|
return 0; |
|
|
|
if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_NO_NPC_IMPACT_DMG ) |
|
{ |
|
if( pEvent->pEntities[index]->IsNPC() || pEvent->pEntities[index]->IsPlayer() ) |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
// use implicit velocities on ragdolls since they may have high constraint velocities that aren't actually executed, just pushed through contacts |
|
if (( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) && pEvent->pEntities[index]->IsPlayer() ) |
|
{ |
|
pEvent->pObjects[otherIndex]->GetImplicitVelocity( &pEvent->preVelocity[otherIndex], &pEvent->preAngularVelocity[otherIndex] ); |
|
} |
|
|
|
// Dissolving impact damage results in death always. |
|
if ( ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_DISSOLVE ) && |
|
!pEvent->pEntities[index]->IsEFlagSet(EFL_NO_DISSOLVE) ) |
|
{ |
|
damageType |= DMG_DISSOLVE; |
|
return 1000; |
|
} |
|
|
|
if ( energyScale <= 0.0f ) |
|
return 0; |
|
|
|
const int gameFlagsNoDamage = FVPHYSICS_CONSTRAINT_STATIC | FVPHYSICS_NO_IMPACT_DMG; |
|
|
|
// NOTE: Crushing damage is handled by stress calcs in vphysics update functions, this is ONLY impact damage |
|
// this is a non-moving object due to a constraint - no damage |
|
if ( pEvent->pObjects[otherIndex]->GetGameFlags() & gameFlagsNoDamage ) |
|
return 0; |
|
|
|
// If it doesn't take damage from held objects and the object is being held - no damage |
|
if ( !bDamageFromHeldObjects && ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) ) |
|
{ |
|
// If it doesn't take damage from held objects - no damage |
|
if ( !bDamageFromHeldObjects ) |
|
return 0; |
|
} |
|
|
|
if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY ) |
|
{ |
|
// UNDONE: Add up mass here for car wheels and prop_ragdoll pieces? |
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; |
|
int count = pEvent->pEntities[otherIndex]->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( pList[i]->GetGameFlags() & gameFlagsNoDamage ) |
|
return 0; |
|
} |
|
} |
|
|
|
if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) |
|
{ |
|
// players can't damage held objects |
|
if ( pEvent->pEntities[otherIndex]->IsPlayer() ) |
|
return 0; |
|
|
|
allowStaticDamage = false; |
|
} |
|
|
|
#if 0 |
|
{ |
|
PhysGetDamageInflictorVelocityStartOfFrame( pEvent->pObjects[otherIndex], pEvent->preVelocity[otherIndex], pEvent->preAngularVelocity[otherIndex] ); |
|
} |
|
#endif |
|
|
|
float otherSpeedSqr = pEvent->preVelocity[otherIndex].LengthSqr(); |
|
float otherAngSqr = 0; |
|
|
|
// factor in angular for sharp objects |
|
if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_SLICE ) |
|
{ |
|
otherAngSqr = pEvent->preAngularVelocity[otherIndex].LengthSqr(); |
|
} |
|
|
|
float otherMass = pEvent->pObjects[otherIndex]->GetMass(); |
|
|
|
if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) |
|
{ |
|
// if the player is holding the object, use it's real mass (player holding reduced the mass) |
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); |
|
if ( pPlayer ) |
|
{ |
|
otherMass = pPlayer->GetHeldObjectMass( pEvent->pObjects[otherIndex] ); |
|
} |
|
} |
|
|
|
// NOTE: sum the mass of each object in this system for the purpose of damage |
|
if ( pEvent->pEntities[otherIndex] && (pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) ) |
|
{ |
|
otherMass = PhysGetEntityMass( pEvent->pEntities[otherIndex] ); |
|
} |
|
|
|
if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_HEAVY_OBJECT ) |
|
{ |
|
otherMass = table.largeMassMin; |
|
if ( energyScale < 2.0f ) |
|
{ |
|
energyScale = 2.0f; |
|
} |
|
} |
|
|
|
// UNDONE: allowStaticDamage is a hack - work out some method for |
|
// breakable props to impact the world and break!! |
|
if ( !allowStaticDamage ) |
|
{ |
|
if ( otherMass < table.minMass ) |
|
return 0; |
|
// check to see if the object is small |
|
if ( otherMass < table.smallMassMax && otherSpeedSqr < table.smallMassMinSpeedSqr ) |
|
return 0; |
|
|
|
if ( otherSpeedSqr < table.minSpeedSqr && otherAngSqr < table.minRotSpeedSqr ) |
|
return 0; |
|
} |
|
|
|
// Add extra oomph for floating objects |
|
if ( pEvent->pEntities[index]->IsFloating() && !pEvent->pEntities[otherIndex]->IsWorld() ) |
|
{ |
|
if ( energyScale < 3.0f ) |
|
{ |
|
energyScale = 3.0f; |
|
} |
|
} |
|
|
|
float damage = 0; |
|
bool bDebug = false;//(&table == &gDefaultPlayerImpactDamageTable); |
|
|
|
// don't ever take spin damage from slowly spinning objects |
|
if ( otherAngSqr > table.minRotSpeedSqr ) |
|
{ |
|
Vector otherInertia = pEvent->pObjects[otherIndex]->GetInertia(); |
|
float angularMom = DotProductAbs( otherInertia, pEvent->preAngularVelocity[otherIndex] ); |
|
damage = ReadDamageTable( table.angularTable, table.angularCount, angularMom * energyScale, bDebug ); |
|
if ( damage > 0 ) |
|
{ |
|
// Msg("Spin : %.1f, Damage %.0f\n", FastSqrt(angularMom), damage ); |
|
damageType |= DMG_SLASH; |
|
} |
|
} |
|
|
|
float deltaV = pEvent->preVelocity[index].Length() - pEvent->postVelocity[index].Length(); |
|
float mass = pEvent->pObjects[index]->GetMass(); |
|
|
|
// If I lost speed, and I lost less than min velocity, then filter out this energy |
|
if ( deltaV > 0 && deltaV < table.myMinVelocity ) |
|
{ |
|
deltaV = 0; |
|
} |
|
float eliminatedEnergy = deltaV * deltaV * mass; |
|
|
|
deltaV = pEvent->preVelocity[otherIndex].Length() - pEvent->postVelocity[otherIndex].Length(); |
|
float otherEliminatedEnergy = deltaV * deltaV * otherMass; |
|
|
|
// exaggerate the effects of really large objects |
|
if ( otherMass >= table.largeMassMin ) |
|
{ |
|
otherEliminatedEnergy *= table.largeMassScale; |
|
float dz = pEvent->preVelocity[otherIndex].z - pEvent->postVelocity[otherIndex].z; |
|
|
|
if ( deltaV > 0 && dz < 0 && pEvent->preVelocity[otherIndex].z < 0 ) |
|
{ |
|
float factor = fabs(dz / deltaV); |
|
otherEliminatedEnergy *= (1 + factor * (table.largeMassFallingScale - 1.0f)); |
|
} |
|
} |
|
|
|
eliminatedEnergy += otherEliminatedEnergy; |
|
|
|
// now in units of this character's speed squared |
|
float invMass = pEvent->pObjects[index]->GetInvMass(); |
|
if ( !pEvent->pObjects[index]->IsMoveable() ) |
|
{ |
|
// inv mass is zero, but impact damage is enabled on this |
|
// prop, so recompute: |
|
invMass = 1.0f / pEvent->pObjects[index]->GetMass(); |
|
} |
|
else if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) |
|
{ |
|
// if the player is holding the object, use it's real mass (player holding reduced the mass) |
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); |
|
if ( pPlayer ) |
|
{ |
|
float mass = pPlayer->GetHeldObjectMass( pEvent->pObjects[index] ); |
|
if ( mass > 0 ) |
|
{ |
|
invMass = 1.0f / mass; |
|
} |
|
} |
|
} |
|
|
|
eliminatedEnergy *= invMass * energyScale; |
|
|
|
damage += ReadDamageTable( table.linearTable, table.linearCount, eliminatedEnergy, bDebug ); |
|
|
|
if ( !pEvent->pObjects[otherIndex]->IsStatic() && otherMass < table.smallMassMax && table.smallMassCap > 0 ) |
|
{ |
|
damage = clamp( damage, 0, table.smallMassCap ); |
|
} |
|
|
|
return damage; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CalculateDefaultPhysicsDamage( int index, gamevcollisionevent_t *pEvent, float energyScale, bool allowStaticDamage, int &damageType, string_t iszDamageTableName, bool bDamageFromHeldObjects ) |
|
{ |
|
// If we have a specified damage table, find it and use it instead |
|
if ( iszDamageTableName != NULL_STRING ) |
|
{ |
|
for ( int i = 0; i < ARRAYSIZE(gDamageTableRegistry); i++ ) |
|
{ |
|
if ( !Q_strcmp( gDamageTableRegistry[i].pszTableName, STRING(iszDamageTableName) ) ) |
|
return CalculatePhysicsImpactDamage( index, pEvent, *(gDamageTableRegistry[i].pTable), energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects ); |
|
} |
|
|
|
Warning("Failed to find custom physics damage table name: %s\n", STRING(iszDamageTableName) ); |
|
} |
|
|
|
return CalculatePhysicsImpactDamage( index, pEvent, gDefaultNPCImpactDamageTable, energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects ); |
|
} |
|
|
|
static bool IsPhysicallyControlled( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) |
|
{ |
|
bool isPhysical = false; |
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
{ |
|
isPhysical = true; |
|
} |
|
else |
|
{ |
|
if ( pPhysics->GetShadowController() ) |
|
{ |
|
isPhysical = pPhysics->GetShadowController()->IsPhysicallyControlled(); |
|
} |
|
} |
|
return isPhysical; |
|
} |
|
float CalculateObjectStress( IPhysicsObject *pObject, CBaseEntity *pInputOwnerEntity, vphysics_objectstress_t *pOutput ) |
|
{ |
|
CUtlVector< CBaseEntity * > pObjectList; |
|
CUtlVector< Vector > objectForce; |
|
bool hasLargeObject = false; |
|
|
|
// add a slot for static objects |
|
pObjectList.AddToTail( NULL ); |
|
objectForce.AddToTail( vec3_origin ); |
|
// add a slot for friendly objects |
|
pObjectList.AddToTail( NULL ); |
|
objectForce.AddToTail( vec3_origin ); |
|
|
|
CBaseCombatCharacter *pBCC = pInputOwnerEntity->MyCombatCharacterPointer(); |
|
|
|
IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); |
|
float objMass = pObject->GetMass(); |
|
while ( pSnapshot->IsValid() ) |
|
{ |
|
float force = pSnapshot->GetNormalForce(); |
|
if ( force > 0.0f ) |
|
{ |
|
IPhysicsObject *pOther = pSnapshot->GetObject(1); |
|
CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData()); |
|
if ( !pOtherEntity ) |
|
{ |
|
// object was just deleted, but we still have a contact point this frame... |
|
// just assume it came from the world. |
|
pOtherEntity = GetWorldEntity(); |
|
} |
|
CBaseEntity *pOtherOwner = pOtherEntity; |
|
if ( pOtherEntity->GetOwnerEntity() ) |
|
{ |
|
pOtherOwner = pOtherEntity->GetOwnerEntity(); |
|
} |
|
|
|
int outIndex = 0; |
|
if ( !pOther->IsMoveable() ) |
|
{ |
|
outIndex = 0; |
|
} |
|
// NavIgnored objects are often being pushed by a friendly |
|
else if ( pBCC && (pBCC->IRelationType( pOtherOwner ) == D_LI || pOtherEntity->IsNavIgnored()) ) |
|
{ |
|
outIndex = 1; |
|
} |
|
// player held objects do no stress |
|
else if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) |
|
{ |
|
outIndex = 1; |
|
} |
|
else |
|
{ |
|
if ( pOther->GetMass() >= VPHYSICS_LARGE_OBJECT_MASS ) |
|
{ |
|
if ( pInputOwnerEntity->GetGroundEntity() != pOtherEntity) |
|
{ |
|
hasLargeObject = true; |
|
} |
|
} |
|
// moveable, non-friendly |
|
|
|
// aggregate contacts over each object to avoid greater stress in multiple contact cases |
|
// NOTE: Contacts should be in order, so this shouldn't ever search, but just in case |
|
outIndex = pObjectList.Count(); |
|
for ( int i = pObjectList.Count()-1; i >= 2; --i ) |
|
{ |
|
if ( pObjectList[i] == pOtherOwner ) |
|
{ |
|
outIndex = i; |
|
break; |
|
} |
|
} |
|
if ( outIndex == pObjectList.Count() ) |
|
{ |
|
pObjectList.AddToTail( pOtherOwner ); |
|
objectForce.AddToTail( vec3_origin ); |
|
} |
|
} |
|
|
|
if ( outIndex != 0 && pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && !IsPhysicallyControlled(pOtherEntity, pOther) ) |
|
{ |
|
// UNDONE: Test this! This is to remove any shadow/shadow stress. The game should handle this with blocked/damage |
|
force = 0.0f; |
|
} |
|
|
|
Vector normal; |
|
pSnapshot->GetSurfaceNormal( normal ); |
|
objectForce[outIndex] += normal * force; |
|
} |
|
pSnapshot->NextFrictionData(); |
|
} |
|
pObject->DestroyFrictionSnapshot( pSnapshot ); |
|
pSnapshot = NULL; |
|
|
|
// clear out all friendly force |
|
objectForce[1].Init(); |
|
|
|
float sum = 0; |
|
Vector negativeForce = vec3_origin; |
|
Vector positiveForce = vec3_origin; |
|
|
|
Assert( pObjectList.Count() == objectForce.Count() ); |
|
for ( int objectIndex = pObjectList.Count()-1; objectIndex >= 0; --objectIndex ) |
|
{ |
|
sum += objectForce[objectIndex].Length(); |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
if ( objectForce[objectIndex][i] < 0 ) |
|
{ |
|
negativeForce[i] -= objectForce[objectIndex][i]; |
|
} |
|
else |
|
{ |
|
positiveForce[i] += objectForce[objectIndex][i]; |
|
} |
|
} |
|
} |
|
|
|
// "external" stress is two way (something pushes on the object and something else pushes back) |
|
// so the set of minimum values per component are the projections of the two-way force |
|
// "internal" stress is one way (the object is pushing against something OR something pushing back) |
|
// the momentum must have come from inside the object (gravity, controller, etc) |
|
Vector internalForce = vec3_origin; |
|
Vector externalForce = vec3_origin; |
|
|
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
if ( negativeForce[i] < positiveForce[i] ) |
|
{ |
|
internalForce[i] = positiveForce[i] - negativeForce[i]; |
|
externalForce[i] = negativeForce[i]; |
|
} |
|
else |
|
{ |
|
internalForce[i] = negativeForce[i] - positiveForce[i]; |
|
externalForce[i] = positiveForce[i]; |
|
} |
|
} |
|
|
|
// sum is kg in / s |
|
Vector gravVector; |
|
physenv->GetGravity( &gravVector ); |
|
float gravity = gravVector.Length(); |
|
if ( pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && pObject->IsMoveable() ) |
|
{ |
|
Vector lastVel; |
|
lastVel.Init(); |
|
if ( pObject->GetShadowController() ) |
|
{ |
|
pObject->GetShadowController()->GetLastImpulse( &lastVel ); |
|
} |
|
else |
|
{ |
|
if ( ( pObject->GetCallbackFlags() & CALLBACK_IS_PLAYER_CONTROLLER ) ) |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( pInputOwnerEntity ); |
|
IPhysicsPlayerController *pController = pPlayer ? pPlayer->GetPhysicsController() : NULL; |
|
if ( pController ) |
|
{ |
|
pController->GetLastImpulse( &lastVel ); |
|
} |
|
} |
|
} |
|
|
|
// Work in progress... |
|
|
|
// Peek into the controller for this object. Look at the input velocity and make sure it's all |
|
// accounted for in the computed stress. If not, redistribute external to internal as it's |
|
// probably being reflected in a way we can't measure here. |
|
float inputLen = lastVel.Length() * (1.0f / physenv->GetSimulationTimestep()) * objMass; |
|
if ( inputLen > 0.0f ) |
|
{ |
|
float internalLen = internalForce.Length(); |
|
if ( internalLen < inputLen ) |
|
{ |
|
float ratio = internalLen / inputLen; |
|
Vector delta = internalForce * (1.0f - ratio); |
|
internalForce += delta; |
|
float deltaLen = delta.Length(); |
|
sum -= deltaLen; |
|
float extLen = VectorNormalize(externalForce) - deltaLen; |
|
if ( extLen < 0 ) |
|
{ |
|
extLen = 0; |
|
} |
|
externalForce *= extLen; |
|
} |
|
} |
|
} |
|
|
|
float invGravity = gravity; |
|
if ( invGravity <= 0 ) |
|
{ |
|
invGravity = 1.0f; |
|
} |
|
else |
|
{ |
|
invGravity = 1.0f / invGravity; |
|
} |
|
sum *= invGravity; |
|
internalForce *= invGravity; |
|
externalForce *= invGravity; |
|
if ( !pObject->IsMoveable() ) |
|
{ |
|
// the above algorithm will see almost all force as internal if the object is not moveable |
|
// (it doesn't push on anything else, so nothing is reciprocated) |
|
// exceptions for friction of a single other object with multiple contact points on this object |
|
|
|
// But the game wants to see it all as external because obviously the object can't move, so it can't have |
|
// internal stress |
|
externalForce = internalForce; |
|
internalForce.Init(); |
|
|
|
if ( !pObject->IsStatic() ) |
|
{ |
|
sum += objMass; |
|
} |
|
} |
|
else |
|
{ |
|
// assume object is at rest |
|
if ( sum > objMass ) |
|
{ |
|
sum = objMass + (sum-objMass) * 0.5; |
|
} |
|
} |
|
|
|
if ( pOutput ) |
|
{ |
|
pOutput->exertedStress = internalForce.Length(); |
|
pOutput->receivedStress = externalForce.Length(); |
|
pOutput->hasNonStaticStress = pObjectList.Count() > 2 ? true : false; |
|
pOutput->hasLargeObjectContact = hasLargeObject; |
|
} |
|
|
|
// sum is now kg |
|
return sum; |
|
}
|
|
|