From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/physics_impact_damage.cpp | 742 +++++++++++++++++++++++++++ 1 file changed, 742 insertions(+) create mode 100644 mp/src/game/server/physics_impact_damage.cpp (limited to 'mp/src/game/server/physics_impact_damage.cpp') diff --git a/mp/src/game/server/physics_impact_damage.cpp b/mp/src/game/server/physics_impact_damage.cpp new file mode 100644 index 00000000..13c22d7d --- /dev/null +++ b/mp/src/game/server/physics_impact_damage.cpp @@ -0,0 +1,742 @@ +//========= 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(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; +} -- cgit v1.2.3