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.
743 lines
22 KiB
743 lines
22 KiB
![]()
4 years ago
|
//========= Copyright 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 ( gpGlobals->maxClients == 1 )
|
||
|
{
|
||
|
// 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 ( gpGlobals->maxClients == 1 )
|
||
|
{
|
||
|
// 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.f, 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;
|
||
|
}
|