Files
GTASource/game/physics/Floater.cpp

2942 lines
110 KiB
C++
Raw Normal View History

2025-02-23 17:40:52 +08:00
#include "grcore/debugdraw.h"
#include "fragmentnm/manager.h"
// Game headers
#include "animation/animbones.h"
#include "camera/viewports/ViewportManager.h"
#include "control/replay/replay.h"
#include "Game/ModelIndices.h"
#include "modelinfo/PedModelInfo.h"
#include "peds/ped.h"
#include "peds/Ped.h"
#include "peds/PedFactory.h"
#include "peds/PedIntelligence.h"
#include "phbound/boundcomposite.h"
#include "pharticulated/articulatedbody.h"
#include "physics/floater.h"
#include "physics/gtaInst.h"
#include "physics/physics.h"
#include "physics/physics_channel.h"
#include "physics/Tunable.h"
#include "physics/WorldProbe/worldprobe.h"
#include "renderer/River.h"
#include "renderer/water.h"
#include "script/script_cars_and_peds.h"
#include "scene/physical.h"
#include "scene/world/GameWorld.h"
#include "streaming/streaming.h"
#include "task/Motion/TaskMotionBase.h"
#include "task/Physics/TaskNMRiverRapids.h"
#include "vehicleAi/Task/TaskVehicleGoToSubmarine.h"
#include "vehicleAi/VehicleIntelligence.h"
#include "vehicles/boat.h"
#include "vehicles/heli.h"
#include "vehicles/VehicleFactory.h"
#include "vfx/Systems/VfxPed.h"
#include "vfx/Systems/VfxVehicle.h"
#include "vfx/Systems/VfxWater.h"
#include "vfx/VfxHelper.h"
#include "phbound/bound.h"
#include "Peds/pedDefines.h"
#include "profile/profiler.h"
PHYSICS_OPTIMISATIONS()
//#include "System/FindSize.h"
//FindSize(CBuoyancy); 368
// PURPOSE: Define the fractional error allowed on the matrix axes for orthonormality when processing low LOD buoyancy.
#define REJUVENATE_ERROR 0.01f
// Statics ////////////////////////////////////////////////////////////////
#if __BANK
bool CBuoyancy::ms_bDrawBuoyancyTests = false;
bool CBuoyancy::ms_bDrawBuoyancySampleSpheres = false;
bool CBuoyancy::ms_bDisplayBuoyancyForces = false;
bool CBuoyancy::ms_bDebugDisplaySubmergedLevels = false;
bool CBuoyancy::ms_bWriteBuoyancyForceToTTY = false;
s32 CBuoyancy::ms_nSampleToDisplay = -1;
// Low LOD buoyancy.
bool CBuoyancy::ms_bIndicateLowLODBuoyancy = false;
// Disable forces for debugging.
bool CBuoyancy::ms_bDisableBuoyancyForceXY = false;
bool CBuoyancy::ms_bDisableLiftForce = false;
bool CBuoyancy::ms_bDisableKeelForce = false;
bool CBuoyancy::ms_bDisableFlowInducedForce = false;
bool CBuoyancy::ms_bDisableDragForce = false;
bool CBuoyancy::ms_bDisableSurfaceStickForce = false;
bool CBuoyancy::ms_bDisablePedWeightBeltForce = false;
bool CBuoyancy::ms_bVisualiseRiverFlowField = false;
bool CBuoyancy::ms_bDebugDrawCrossSection = false;
bool CBuoyancy::ms_bRiverBoundDebugDraw = false;
float CBuoyancy::ms_fGridSpacing = 0.5f;
float CBuoyancy::ms_fHeightOffset = 0.1f;
float CBuoyancy::ms_fScaleFactor = 5.0f;
int CBuoyancy::ms_nGridSizeX = 10;
int CBuoyancy::ms_nGridSizeY = 10;
#endif // __BANK
float CBuoyancy::ms_fCarDragCoefficient = 5.0f;
float CBuoyancy::ms_fBikeDragCoefficient = 0.05f;
float CBuoyancy::ms_fDragCoefficient = 120.0f;
float CBuoyancy::ms_fPedDragCoefficient = 30.0f;
float CBuoyancy::ms_fProjectileDragCoefficient = 5.0f;
float CBuoyancy::ms_fFlowVelocityScaleFactor = 7.5;
float CBuoyancy::ms_fVehicleMaximumSpeedToApplyRiverForces = 8.0f;
// Keel force params:
float CBuoyancy::ms_fFullKeelForceAtRudderLimit = 0.5f;
float CBuoyancy::ms_fKeelDragMult = 0.4f;
float CBuoyancy::ms_fKeelForceFactorSteerMultKeelSpheres = 0.2f;
float CBuoyancy::ms_fMinKeelForceFactor = 0.2f;
float CBuoyancy::ms_fKeelForceThrottleThreshold = 0.1f;
float CBuoyancy::ms_fMaxKeelRudderExclusionFraction = 0.1f;
float CBuoyancy::ms_fBoatAnchorLodDistance = 0.0f;
float CBuoyancy::ms_fObjectLodDistance = 50.0f;
PF_PAGE(GTA_Buoyancy, "Gta Buoyancy");
PF_GROUP(Buoyancy_forces);
PF_LINK(GTA_Buoyancy, Buoyancy_forces);
PF_GROUP(Buoyancy_timers);
PF_LINK(GTA_Buoyancy, Buoyancy_timers);
PF_GROUP(Buoyancy_general);
PF_LINK(GTA_Buoyancy, Buoyancy_general);
PF_VALUE_INT(NumWaterSamples, Buoyancy_general);
PF_TIMER(ProcessBuoyancy, Buoyancy_timers);
PF_TIMER(ProcessBuoyancyTotal, Buoyancy_timers);
PF_TIMER(ComputeBuoyancyForce, Buoyancy_timers);
PF_TIMER(ComputeLiftForce, Buoyancy_timers);
PF_TIMER(ComputeKeelForce, Buoyancy_timers);
PF_TIMER(ComputeDragForce, Buoyancy_timers);
PF_TIMER(ComputeDampingForce, Buoyancy_timers);
PF_TIMER(ComputeStickToSurfaceForce, Buoyancy_timers);
PF_TIMER(ComputeCrossSection, Buoyancy_timers);
EXT_PF_TIMER(ProcessLowLodBuoyancyTimer);
PF_VALUE_FLOAT(BuoyancyForce, Buoyancy_forces);
PF_VALUE_FLOAT(LiftForce, Buoyancy_forces);
PF_VALUE_FLOAT(KeelForce, Buoyancy_forces);
PF_VALUE_FLOAT(FlowDragForce, Buoyancy_forces);
PF_VALUE_FLOAT(BasicDragForce, Buoyancy_forces);
///////////////////////////////////////////////////////////////////////////
#if __ASSERT
#define GENERIC_FORCE_CHECK(msg, vForce) \
Displayf("***** %s EXCEEDS SAFE RANGE *****", #msg); \
CBaseModelInfo* pModelInfo = pParent->GetBaseModelInfo(); \
if(pModelInfo) \
{ \
Displayf("Model index: %d (%s)", pParent->GetModelIndex(), pModelInfo->GetModelName()); \
} \
if(m_WaterTestHelper.GetRiverHitStored()) \
{ \
Displayf("Sample in river."); \
} \
else \
{ \
Displayf("Sample not in river."); \
} \
Displayf("Force=(%5.3f,%5.3f,%5.3f) (magnitude=%5.3f)", vForce.x,vForce.y,vForce.z, vForce.Mag());
#endif // __ASSERT
#if DEBUG_BUOYANCY
// Call this once for each buoyancy sample sphere to initialise variables.
#define DEBUG_SAMPLE_BUOYANCY_FORCES_INIT(nSampleId) \
if(ms_nSampleToDisplay==-1 || ms_nSampleToDisplay==nSampleId) \
{ \
if(ms_bDisplayBuoyancyForces) \
{ \
m_forceDebug.m_nNumTextLines = 0; \
if(ms_bDrawBuoyancySampleSpheres) ++m_forceDebug.m_nNumTextLines; \
/* Display the different buoyancy forces applied to this sample. */ \
sprintf(m_forceDebug.m_zForceDebugText, "Sample: %u", nSampleId); \
grcDebugDraw::Text(vTestPointWorld, m_forceDebug.m_forceDebugColour, 0, m_forceDebug.m_nNumTextLines*grcDebugDraw::GetScreenSpaceTextHeight(), m_forceDebug.m_zForceDebugText); \
++m_forceDebug.m_nNumTextLines; \
} \
}
#else // DEBUG_BUOYANCY
#define DEBUG_SAMPLE_BUOYANCY_FORCES_INIT(nSampleId)
#endif // DEBUG_BUOYANCY
#if DEBUG_BUOYANCY
// After calling DEBUG_SAMPLE_BUOYANCY_FORCES_INIT(), use this to display each of the forces applied to a buoyancy sample sphere.
#define DEBUG_SAMPLE_BUOYANCY_FORCE(nSampleId, msg, vForce) \
if(ms_nSampleToDisplay==-1 || ms_nSampleToDisplay==nSampleId) \
{ \
if(ms_bDisplayBuoyancyForces) \
{ \
sprintf(m_forceDebug.m_zForceDebugText, "%s: (%5.3f,%5.3f,%5.3f) {%5.3f}", #msg, vForce.x, vForce.y, vForce.z, vForce.Mag()); \
grcDebugDraw::Text(vTestPointWorld, m_forceDebug.m_forceDebugColour, 0, m_forceDebug.m_nNumTextLines*grcDebugDraw::GetScreenSpaceTextHeight(), m_forceDebug.m_zForceDebugText); \
++m_forceDebug.m_nNumTextLines; \
} \
if(ms_bWriteBuoyancyForceToTTY) \
{ \
Displayf("[CEntity: 0x%p] Sample %u: %s: (%5.3f,%5.3f,%5.3f) {%5.3f}", \
pParent, nSampleId, #msg, vForce.x, vForce.y, vForce.z, vForce.Mag()); \
} \
}
#else // DEBUG_BUOYANCY
#define DEBUG_SAMPLE_BUOYANCY_FORCE(nSampleId, msg, vForce)
#endif // DEBUG_BUOYANCY
// To make a change to the way we compute the depth of samples in rivers when the river poly is not horizontal safer, define a region
// around one of the big waterfalls where we allow the change to kick-in.
const Vector3 vWaterFallPosition(-540.0f, 4420.9f, 20.0f);
const float fWaterFallRadius = 20.0f;
CBuoyancy::CBuoyancy()
: m_fSubmergedLevel(0.0f),
m_fLowLODDraughtOffset(0.0f),
m_fFlowVelocityScaleFactor(ms_fFlowVelocityScaleFactor),
m_nNumComponentsWithSamples(0)
{
m_waterLevelStatus = NOT_IN_WATER;
m_fForceMult = 1.0f;
m_fSampleSubmergeLevel = NULL;
m_nNumInSampleLevelArray = 0;
m_pBuoyancyInfoOverride = NULL;
m_buoyancyFlags.bShouldStickToWaterSurface = false;
m_buoyancyFlags.bPedWeightBeltActive = false;
m_buoyancyFlags.bLowLodBuoyancyMode = false;
m_buoyancyFlags.bWasActiveWhenLodded = false;
m_buoyancyFlags.bScriptLowLodOverride = false;
m_buoyancyFlags.bUseWidestRiverBoundTolerance = false;
m_buoyancyFlags.bBuoyancyInfoUpToDate = false;
m_buoyancyFlags.bReduceLateralBuoyancyForce = false;
m_vLowLodVelocityXYZLowLodTimerW.Set(0.0f, 0.0f, 0.0f);
SetTimeInLowLodMode(0.0f);
}
CBuoyancy::~CBuoyancy()
{
if(m_fSampleSubmergeLevel)
{
delete[] m_fSampleSubmergeLevel;
}
}
dev_float TEST_PUSH_WATER_DOWN_FROM_LIFT_SPEED_MULT = 0.1f;
dev_float TEST_PUSH_WATER_DOWN_FROM_LIFT_SPEED_LIMIT = 2.0f;
dev_float TEST_PUSH_WATER_DOWN_FROM_LIFT_APPLY_MULT = 0.001f;
dev_float MAX_SPEED_FOR_DRAG = 30.0f;
dev_bool sbTestBoatSamplePosFix = true;
dev_float sfWaterSamplePlaneMult = 10.0f;
dev_float sfWaterSampleCruiseMult = 0.95f;
//
bool CBuoyancy::Process(CPhysical* pParent, float fTimeStep, bool bArticulatedBody, bool lastTimeSlice, float* pBuoyancyAccel)
{
// WARNING!! IF YOU ADD ANY EXTRA RETURN STATEMENTS TO THIS FUNCTION, PLEASE REMEMBER TO STOP THIS TIMER FIRST.
PF_START(ProcessBuoyancy);
Assert(pParent);
// This flag will let us know whether such info as "submerged level", "avg submerge level", etc. are actually
// up-to-date for this entity.
m_buoyancyFlags.bBuoyancyInfoUpToDate = false;
if(!pParent || !pParent->GetCurrentPhysicsInst())
{
PF_STOP(ProcessBuoyancy);
return false;
}
if(!pParent->GetCurrentPhysicsInst() || !pParent->GetCurrentPhysicsInst()->IsInLevel() || !pParent->GetCollider())
{
PF_STOP(ProcessBuoyancy);
return false;
}
// Don't process buoyancy for objects which have a basic attachment (the attach parent will be processed).
if(pParent->GetIsAttached() && !pParent->GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION)
&& (pParent->GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_BASIC
||pParent->GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_WORLD))
{
PF_STOP(ProcessBuoyancy);
return false;
}
CBuoyancyInfo* pBuoyancyInfo = GetBuoyancyInfo(pParent);
//Assert(pBuoyancyInfo); // cloth ends up with no buoyancy info
if(pBuoyancyInfo == NULL)
{
PF_STOP(ProcessBuoyancy);
return false;
}
// No point going any further if buoyancy has been turned off for this instance.
if(pParent && pParent->GetPhysArch() && pParent->GetPhysArch()->GetBuoyancyFactor() < SMALL_FLOAT)
{
PF_STOP(ProcessBuoyancy);
return false;
}
SelectDragCoefficient(pParent);
// Cache some frequently used data.
phInst* pInst = pParent->GetCurrentPhysicsInst();
phBound* pBound = NULL;
// Useful for some verification when applying forces, etc.
int nNumBounds = 0;
if(pInst->GetArchetype())
{
pBound = pInst->GetArchetype()->GetBound();
if(pBound)
{
// We at least have one bound if we are here...
nNumBounds = 1;
// ... if the bound is composite, we might have more.
if(pBound->GetType()==phBound::COMPOSITE)
{
nNumBounds = static_cast<phBoundComposite*>(pBound)->GetNumBounds();
}
}
}
if(m_nNumInSampleLevelArray < pBuoyancyInfo->m_nNumWaterSamples)
{
// Our buoyancy info has changed, so water samples number no longer matches the sample submerge level array
// Delete the sample submerge level array so it can be re created
delete[] m_fSampleSubmergeLevel;
m_fSampleSubmergeLevel = NULL;
m_nNumInSampleLevelArray = 0;
// Signal that we need to redefine a mapping between component index and the corresponding force accumulator
// as this has probably changed.
m_nNumComponentsWithSamples = 0;
}
// We also need to redefine the mapping between component index and the corresponding force accumulator if
// we changed physics inst since it was set up (e.g. ped switches from animated to ragdoll in water).
if(pParent && pInst && pInst!=m_pInstForComponentMap)
{
m_nNumComponentsWithSamples = 0;
}
if(m_fSampleSubmergeLevel == NULL)
{
m_fSampleSubmergeLevel = rage_new float[pBuoyancyInfo->m_nNumWaterSamples];
m_nNumInSampleLevelArray = pBuoyancyInfo->m_nNumWaterSamples;
if(m_fSampleSubmergeLevel == NULL)
{
Assertf(false, "Error allocating %d water samples.", pBuoyancyInfo->m_nNumWaterSamples);
PF_STOP(ProcessBuoyancy);
return false;
}
// initialise the water sample levels
for (int i=0; i<pBuoyancyInfo->m_nNumWaterSamples; i++)
{
m_fSampleSubmergeLevel[i] = 0.0f;
}
}
Assert(m_nNumInSampleLevelArray >= pBuoyancyInfo->m_nNumWaterSamples);
if(pBuoyancyAccel)
*pBuoyancyAccel = 0.0f;
Vector3 vTotalBuoyancyForce;
vTotalBuoyancyForce.Zero();
bool bInWater = false;
bool bUnderWater = true;
bool bInRiver = m_WaterTestHelper.GetRiverHitStored();
Vector3 vTestPointWorld;
Vector3 vecWaterNormal;
float fWaterLevel;
float fWaterSpeedVert;
int nComponent = 0;
// Used to stop peds counting as in water until their bodies are below a certain height during the transition into water
//bool bOverridePedInWater = false;
CVehicle* pVehicle = NULL;
CBoatHandlingData* pBoatHandling = NULL;
float dragReduction = 0.0f;
if(pParent->GetIsTypeVehicle() && ((CVehicle*)pParent)->GetVehicleType()==VEHICLE_TYPE_BOAT)
{
pVehicle = static_cast<CVehicle*>(pParent);
pBoatHandling = pVehicle->pHandling->GetBoatHandlingData();
if( pVehicle->GetDriver() &&
pVehicle->GetDriver()->GetPlayerInfo() )
{
static dev_float maxDragReduction = 0.3f;
dragReduction = pVehicle->GetSlipStreamEffect() * maxDragReduction;
}
}
CSeaPlaneHandlingData* pSeaplaneHandling = NULL;
if(pParent->GetIsTypeVehicle())
{
pSeaplaneHandling = static_cast<CVehicle*>(pParent)->pHandling->GetSeaPlaneHandlingData();
}
if( pParent->GetIsTypeVehicle() && ((CVehicle*)pParent)->InheritsFromAmphibiousAutomobile() )
{
pVehicle = static_cast<CVehicle*>(pParent);
pBoatHandling = static_cast<CVehicle*>(pParent)->pHandling->GetBoatHandlingData();
if( pVehicle->GetDriver() &&
pVehicle->GetDriver()->GetPlayerInfo() )
{
static dev_float maxDragReduction = 0.3f;
dragReduction = pVehicle->GetSlipStreamEffect() * maxDragReduction;
}
}
float fMass = pParent->GetMass();
CPed* pPed = pParent->GetIsTypePed() ? static_cast<CPed*>(pParent) : NULL;
if(pPed && pPed->GetRagdollState()==RAGDOLL_STATE_PHYS)
{
fMass = pPed->GetRagdollInst()->GetTypePhysics()->GetTotalUndamagedMass();
}
else if(pParent->GetIsTypeObject() && pParent->GetFragInst())
{
fMass = pParent->GetFragInst()->GetTypePhysics()->GetTotalUndamagedMass();
}
// vfx - decide whether to process
bool isAnimatedPed = pPed && !bArticulatedBody;
bool processFx = true;
if(isAnimatedPed && !lastTimeSlice)
{
// don't process fx if this is an animated ped and not the last time slice
processFx = false;
}
// bool bIsSwimmingPed = pPed && pPed->GetIsSwimming();
/*
// Living fish also count as swimming peds for the purposes of excluding them from redundant force calculations.
if(pPed)
{
CTaskMotionBase* pMotion = pPed->GetCurrentMotionTask();
if(pMotion && pMotion->GetTaskType() == CTaskTypes::TASK_ON_FOOT_FISH)
{
bIsSwimmingPed = true;
}
}
*/
float fWaterHeightNoWaves;
Vector3 vecParentPos = VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition());
WaterTestResultType waterTestResult = m_WaterTestHelper.GetWaterLevelIncludingRiversNoWaves(vecParentPos, &fWaterHeightNoWaves, POOL_DEPTH, REJECTIONABOVEWATER, NULL, pParent);
if( waterTestResult == WATERTEST_TYPE_NONE )
{
m_waterLevelStatus = NOT_IN_WATER;
PF_STOP(ProcessBuoyancy);
return false;
}
if( waterTestResult == WATERTEST_TYPE_RIVER &&
pBoatHandling )
{
spdAABB aabbObj;
pParent->GetAABB( aabbObj );
static dev_float sfWaterHeightTollerance = 0.5f;
if( ( fWaterHeightNoWaves + sfWaterHeightTollerance ) < aabbObj.GetMin().GetZf() )
{
m_waterLevelStatus = NOT_IN_WATER;
return false;
}
}
int iSampleStart = 0;
int iSampleEnd = pBuoyancyInfo->m_nNumWaterSamples;
if(pPed)
{
if(pPed->GetUsingRagdoll())
{
iSampleStart = WS_PED_RAGDOLL_SAMPLE_START;
// Currently, with a lower-LOD human or an animal just use the number of samples needed.
// TODO - come up with something that handles other ragdoll types humans better.
if(bArticulatedBody && pParent->GetFragInst() && pBound)
{
phBoundComposite* pCompositeBound = static_cast<phBoundComposite*>(pBound);
int numSamples = iSampleEnd - iSampleStart;
if(pCompositeBound->GetNumBounds() < numSamples)
{
iSampleEnd = iSampleStart + pCompositeBound->GetNumBounds();
}
}
}
else
{
iSampleStart = WS_PED_ANIMATED_SAMPLE_START;
iSampleEnd = WS_PED_RAGDOLL_SAMPLE_START;
}
}
else if(pParent->GetIsTypeVehicle())
{
float fVehicleSpeed = pParent->GetVelocity().Mag();
if(fVehicleSpeed < ms_fVehicleMaximumSpeedToApplyRiverForces)
{
m_fFlowVelocityScaleFactor = ((ms_fVehicleMaximumSpeedToApplyRiverForces - fVehicleSpeed)/ms_fVehicleMaximumSpeedToApplyRiverForces) * ms_fFlowVelocityScaleFactor;
}
else
{
m_fFlowVelocityScaleFactor = 0.0f;
}
}
if(!physicsVerifyf(iSampleStart < pBuoyancyInfo->m_nNumWaterSamples,"Ped has invalid buoyancy samples: iSampleStart=%d, iSampleEnd=%d, num water samples=%d",
iSampleStart, iSampleEnd, pBuoyancyInfo->m_nNumWaterSamples))
{
iSampleStart = pBuoyancyInfo->m_nNumWaterSamples;
}
if(!physicsVerifyf(iSampleEnd <= pBuoyancyInfo->m_nNumWaterSamples,"Ped has invalid buoyancy samples: iSampleStart=%d, iSampleEnd=%d, num water samples=%d",
iSampleStart, iSampleEnd, pBuoyancyInfo->m_nNumWaterSamples))
{
iSampleEnd = pBuoyancyInfo->m_nNumWaterSamples;
}
Assertf(iSampleEnd > 0 && iSampleEnd<=MAX_WATER_SAMPLES, "Invalid number of water samples %i, pBuoyancyInfo->m_nNumWaterSamples : %i, m_WaterSamples : %p", iSampleEnd, pBuoyancyInfo->m_nNumWaterSamples, pBuoyancyInfo->m_WaterSamples);
TUNE_GROUP_BOOL(WATER_BUG, FORCE_NO_SAMPLES_FIX, true);
if (iSampleEnd == 0 && FORCE_NO_SAMPLES_FIX)
{
PF_STOP(ProcessBuoyancy);
return false;
}
// vfx - initialise data
VfxWaterSampleData_s vfxWaterSampleData[MAX_WATER_SAMPLES];
if (processFx)
{
for (int i=0; i<MAX_WATER_SAMPLES; i++)
{
if (i<m_nNumInSampleLevelArray)
{
vfxWaterSampleData[i].prevLevel = m_fSampleSubmergeLevel[i];
}
else
{
vfxWaterSampleData[i].prevLevel = 0.0f;
}
vfxWaterSampleData[i].isInWater = false;
}
}
m_fAbsWaterLevel = -10000;
m_fAvgAbsWaterLevel = 0.0f;
m_fSubmergedLevel = 0.0f;
float fCrossSectionalLength = ComputeCrossSectionalLengthForDrag(pParent);
#if DEBUG_BUOYANCY
m_fTotalBuoyancyForce = 0.0f;
m_fTotalLiftForce = 0.0f;
m_fTotalKeelForce = 0.0f;
m_fTotalFlowDragForce = 0.0f;
m_fTotalBasicDragForce = 0.0f;
#endif // DEBUG_BUOYANCY
#if __WIN32PC
m_fKeelSideForce = 0.0f;
#endif // __WIN32PC
// Create the force accumulators. We need enough for each component which has a water sample.
// Define a mapping between component index and the corresponding force accumulator if we haven't already done so.
if(m_nNumComponentsWithSamples == 0)
{
m_componentIndexToForceAccumMap.Reset();
for(int i = iSampleStart; i < iSampleEnd; ++i)
{
CWaterSample& sample = pBuoyancyInfo->m_WaterSamples[i];
u32 nSampleComponent = sample.m_nComponent;
if(!m_componentIndexToForceAccumMap.Access(nSampleComponent) && nSampleComponent<nNumBounds)
{
m_componentIndexToForceAccumMap.Insert(nSampleComponent, m_nNumComponentsWithSamples);
++m_nNumComponentsWithSamples;
}
}
m_pInstForComponentMap = pInst;
}
Assert(m_nNumComponentsWithSamples != 0);
SForceAccumulator* forceAccumulators = Alloca(SForceAccumulator, m_nNumComponentsWithSamples);
Assert(forceAccumulators);
// Zero the accumulators.
for(int i = 0; i < m_nNumComponentsWithSamples; ++i)
{
forceAccumulators[i].Clear();
}
Matrix34 parentMat = MAT34V_TO_MATRIX34(pInst->GetMatrix());
/// ///////////////////////////////////////////// LOOP OVER EACH SAMPLE /////////////////////////////////////////////////////////////////////////
// Process each individual water sample.
u32 nSamplesUsedForSubmergeLevel = 0;
for(int iSample=iSampleStart; iSample < iSampleEnd; iSample++)
{
// Cache some frequently used values.
CWaterSample& sample = pBuoyancyInfo->m_WaterSamples[iSample];
float fSampleSize = sample.m_fSize;
float fSampleBuoyancyMult = sample.m_fBuoyancyMult;
int nSampleComponent = sample.m_nComponent;
// VFX - Skip this sample.
if(fSampleSize==0.0f)
{
vfxWaterSampleData[iSample].maxLevel = 0.0f;
continue;
}
// If this sample is for a part of the object which has broken off then just ignore it.
if(pParent->GetFragInst() && pParent->GetFragInst()->GetChildBroken(nSampleComponent))
{
continue;
}
m_fSampleSubmergeLevel[iSample] = 0.0f;
// The sample offset is computed relative to the bounding box min coords. Transform it accordingly.
Vector3 vLocalPosition = sample.m_vSampleOffset;
if(bArticulatedBody && pParent->GetFragInst() && pBound)
{
phBoundComposite* pCompositeBound = static_cast<phBoundComposite*>(pBound);
nComponent = nSampleComponent;
// set mass to the current part
if(pPed)
{
phArticulatedBody *body = ((phArticulatedCollider*)pParent->GetCollider())->GetBody();
Assert(body);
fMass = body->GetMass(nComponent).Getf();
}
else if(pParent->GetIsTypeObject() && pParent->GetFragInst())
{
fMass = pParent->GetFragInst()->GetTypePhysics()->GetChild(nComponent)->GetUndamagedMass();
}
if(Verifyf(nComponent < pCompositeBound->GetNumBounds(),"Component in water sample does not exist in physics bound"))
{
RCC_MATRIX34(pCompositeBound->GetCurrentMatrix(nComponent)).Transform(vLocalPosition);
}
else
{
// Something has gone wrong, probably using a lower LOD version of this object which has
// less parts than when the samples were set up. Just ignore it to avoid a crash.
continue;
}
}
else if(pBound && pBound->GetType() == phBound::COMPOSITE)
{
// For non-frag objects we just used the biggest component to generate samples.
phBoundComposite* pCompositeBound = static_cast<phBoundComposite*>(pBound);
nComponent = nSampleComponent;
if(Verifyf(nComponent < pCompositeBound->GetNumBounds(), "Component in water sample does not exist in physics bound"))
{
RCC_MATRIX34(pCompositeBound->GetCurrentMatrix(nComponent)).Transform(vLocalPosition);
}
}
parentMat.Transform(vLocalPosition, vTestPointWorld);
// Skip this sample if we are in a river and it is too high above the hit position returned for the river surface at the entity centre.
if(bInRiver)
{
if((vTestPointWorld - vWaterFallPosition).Mag2() < rage::square(fWaterFallRadius) &&
( !pParent->GetIsTypeVehicle() || static_cast< CVehicle* >( pParent )->GetSpecialFlightModeRatio() == 0.0f ) )
{
TUNE_FLOAT(fMaxHeightAboveRiverForWaterSample, 1.0f, 0.0f, 20.0f, 1.0f);
if(vTestPointWorld.z - m_WaterTestHelper.m_vLastRiverHitPos.z > fMaxHeightAboveRiverForWaterSample)
{
continue;
}
}
}
DEBUG_SAMPLE_BUOYANCY_FORCES_INIT(iSample);
vfxWaterSampleData[iSample].vTestPos = RCC_VEC3V(vTestPointWorld);
vfxWaterSampleData[iSample].maxLevel = fSampleSize;
////////////// COMPUTE VELOCITY OF THIS SAMPLE RELATIVE TO THE LOCAL VELOCITY OF THE WATER ////////////
Vector2 vFlow(0.0f, 0.0f);
Vector3 vFlowVelocity(VEC3_ZERO);
if(bInRiver)
{
// Extract the flow vector from the river texture and use it to compute a force which will
// push the object along the river.
River::GetRiverFlowFromPosition(VECTOR3_TO_VEC3V(vTestPointWorld), vFlow);
vFlow.Scale(1.0f/River::GetRiverPushForce());
vFlowVelocity = Vector3(vFlow.x, vFlow.y, 0.0f);
vFlowVelocity.Scale(m_fFlowVelocityScaleFactor);
}
if(bInRiver)
{
// To make ragdolling peds move down the river faster in rapids and waterfalls, up the perceived velocity
// of the river in these regions.
const float fRapidSpeed = CTaskNMRiverRapids::sm_Tunables.m_fMinRiverFlowForRapids;
if(pPed)
{
Assert(pPed);
const CPedIntelligence* pPedIntelligence = pPed->GetPedIntelligence();
bool bPedRunningRapidsTask = false;
if(pPedIntelligence && pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_RIVER_RAPIDS))
{
bPedRunningRapidsTask = true;
}
if(bPedRunningRapidsTask && vFlowVelocity.Mag2() > fRapidSpeed*fRapidSpeed)
{
TUNE_FLOAT(fMaxPedRiverFlowSpeedScalarInRapids, 3.0f, 0.0f, 10.0f, 0.1f);
TUNE_INT(nDurationOfLerp, 2000, 1, 10000, 100);
// How long have we been in the rapids? Want to ramp up the amount we scale the flow
// speed over a short amount of time.
u32 nTimeInRapids = fwTimer::GetTimeInMilliseconds() - pPed->GetRiverRapidsTimer();
nTimeInRapids = rage::Min(nTimeInRapids, (u32)nDurationOfLerp);
float fLerpFraction = float(nTimeInRapids)/float(nDurationOfLerp);
float fPedRiverFlowSpeedScalarInRapids = ((fMaxPedRiverFlowSpeedScalarInRapids-1.0f) * fLerpFraction) + 1.0f;
vFlowVelocity.Scale(fPedRiverFlowSpeedScalarInRapids);
#if __BANK
TUNE_BOOL(INDICATE_PEDS_IN_RAPIDS, false);
if(INDICATE_PEDS_IN_RAPIDS)
{
grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(pParent->GetMatrix().d()), 0.5f, Color32(1.0f, 0.0f, 0.0f, 0.1f), true);
char zText[100];
sprintf(zText, "Flow scale: %5.3f", fPedRiverFlowSpeedScalarInRapids);
grcDebugDraw::Text(VEC3V_TO_VECTOR3(pPed->GetMatrix().d())+Vector3(0.0f,0.0f,1.5f), Color_white, 0, 0, zText);
}
#endif // __BANK
}
else
{
pPed->ResetRiverRapidsTimer();
}
}
// Transform the velocity from 2D to 3D in such a way that it matches the orientation of the polygon it is
// attached to.
Quaternion q;
q.FromVectors(ZAXIS, m_WaterTestHelper.m_vLastRiverHitNormal);
// Use the quaternion to get the direction 3D world space flow vector.
q.Transform(vFlowVelocity);
// Increase the river flow speed as seen by boats close to a large waterfall.
if(m_WaterTestHelper.GetRiverHitStored())
{
float fDistToWaterfallSquared = (VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition()) - vWaterFallPosition).Mag2();
if(fDistToWaterfallSquared < rage::square(fWaterFallRadius))
{
if(pParent->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(pParent);
if(pVehicle->GetVehicleType()==VEHICLE_TYPE_BOAT)
{
TUNE_FLOAT(WATERFALL_FLOW_SPEED_SCALE, 20.0f, 0.0f, 50.0f, 1.0f);
float fWaterfallDistanceScale = (1.0f - (fDistToWaterfallSquared / rage::square(fWaterFallRadius))) * WATERFALL_FLOW_SPEED_SCALE;
vFlowVelocity.Scale(fWaterfallDistanceScale);
}
}
}
}
}
// Compute the relative velocity of this sample point with respect to the flow in local space.
Vector3 vLocalFlowVelocity = pParent->GetLocalSpeed(vTestPointWorld, true, nComponent) - vFlowVelocity;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if __BANK
PF_STOP(ProcessBuoyancy);
if(ms_bDrawBuoyancySampleSpheres)
{
grcDebugDraw::Sphere(vTestPointWorld,fSampleSize,Color32(0,255,0),false);
char debugText[3];
formatf(debugText,3,"%i",iSample);
grcDebugDraw::Text(vTestPointWorld,Color32(255,0,0),debugText);
}
PF_START(ProcessBuoyancy);
#endif // __BANK
// If we are in a river, we are not in a "Water" block so work out the depth based on the probe results.
if(bInRiver)
{
if(pVehicle && pVehicle->ContainsLocalPlayer())
{
m_WaterTestHelper.GetRiverHeightAtPosition(vTestPointWorld, &fWaterLevel);
}
else
{
// Use the plane defined by the normal of the last poly hit by the river probe and the position of the
// point hit to find the z-coord of the river surface (to first order) at the position of the current
// sample point.
fWaterLevel = -(m_WaterTestHelper.m_vLastRiverHitNormal.x*(vTestPointWorld.x-m_WaterTestHelper.m_vLastRiverHitPos.x)
+ m_WaterTestHelper.m_vLastRiverHitNormal.y*(vTestPointWorld.y-m_WaterTestHelper.m_vLastRiverHitPos.y))
/m_WaterTestHelper.m_vLastRiverHitNormal.z + m_WaterTestHelper.m_vLastRiverHitPos.z;
}
// Assume that buoyancy acts vertically; we will add a force based on the flow later.
vecWaterNormal = ZAXIS;
fWaterSpeedVert = 0.0f;
}
else
{
fWaterLevel = fWaterHeightNoWaves;
// TODO: this is actually really expensive. Either optimise the function or better still roll our
// own here for buoyancy to take advantage of the fact that we are looping over a grid of water samples.
Water::AddWaveToResult(vTestPointWorld.x, vTestPointWorld.y, &fWaterLevel, &vecWaterNormal, &fWaterSpeedVert, pParent->GetIsTypePed());
}
m_fAbsWaterLevel = Max(m_fAbsWaterLevel, fWaterLevel); // Track the highest water level of all the samples.
m_fAvgAbsWaterLevel += fWaterLevel;
float fBottomOfSampleSphere = vTestPointWorld.z - fSampleSize;
if( fWaterLevel <= fBottomOfSampleSphere &&
pParent->GetIsTypeVehicle() )
{
// always store the sample submerge level, even if it isn't submerged, for the hover mode
// as we can use it to make it hover better above the water
CVehicle* pVehicle = static_cast< CVehicle* >( pParent );
if( pVehicle->GetSpecialFlightModeRatio() == 1.0f )
{
m_fSampleSubmergeLevel[ iSample ] = fWaterLevel - fBottomOfSampleSphere;
m_fSampleSubmergeLevel[ iSample ] /= 2.0f;
}
}
if(fWaterLevel > fBottomOfSampleSphere)
{
++nSamplesUsedForSubmergeLevel;
m_fSampleSubmergeLevel[iSample] = fWaterLevel - fBottomOfSampleSphere;
// Scale water level between 0.0 and fSize.
m_fSampleSubmergeLevel[iSample] /= 2.0f;
if(m_fSampleSubmergeLevel[iSample] > fSampleSize)
{
m_fSampleSubmergeLevel[iSample] = fSampleSize;
vecWaterNormal = Vector3(0.0f, 0.0f, 1.0f);
}
else
{
bUnderWater = false;
}
// Compute depth of this sample as a fraction of its diameter.
float fDepthFactor = (fWaterLevel - vTestPointWorld.z) / (2.0f*fSampleSize);
float partBuoyancyMultiplier = fSampleBuoyancyMult;
float buoyancyConstant = pBuoyancyInfo->m_fBuoyancyConstant;
if( pParent->IsNetworkClone() &&
pVehicle &&
( MI_CAR_APC.IsValid() &&
pVehicle->GetModelIndex() == MI_CAR_APC ) )
{
buoyancyConstant *= 1.2f;
}
// Use a separate buoyancy constant for peds when simulating
if (pPed && pPed->GetUsingRagdoll())
{
// Buoyancy constant at default gravity
static float s_ragdollBuoyancyConstant = 100.0f;
buoyancyConstant = s_ragdollBuoyancyConstant;
if (!IsClose(GRAVITY, 0.0f, VERY_SMALL_FLOAT))
{
// Take into account changes in gravity (not sure if we maybe want to do this regardless of whether the ped is ragdolling or not...)
buoyancyConstant *= (CPhysics::GetGravitationalAcceleration() / -GRAVITY);
}
}
// Control the sink rate for animated corpses
if (pPed && pPed->GetRagdollState() < RAGDOLL_STATE_PHYS && pPed->IsDead())
{
pPed->GetCollider()->SetVelocity(Vec3V(V_ZERO).GetIntrin128());
buoyancyConstant = 0.0f;
}
Vector3 vSpeedAtPoint, vSpeedAtPointIncFlow;
vSpeedAtPoint = pParent->GetLocalSpeed(vTestPointWorld, true, nComponent);
if(fDepthFactor < 2.0f)
{
vSpeedAtPoint.z -= fWaterSpeedVert;
}
vSpeedAtPointIncFlow = Vector3(vLocalFlowVelocity.x, vLocalFlowVelocity.y, pParent->GetLocalSpeed(vTestPointWorld,true,nComponent).z-fWaterSpeedVert);
// Mostly used by Vfx below:
Vector3 vBuoyancyForceApplied(VEC3_ZERO);
Vector3 vDampingForceComputed(VEC3_ZERO); // NB - This value may be clamped based on the total drag computed for all samples!
// Certain sample spheres are add purely to provide keel forces on boats.
bool bKeelSample = pBuoyancyInfo->m_fKeelMult > 0.0f && sample.m_bKeel;
if(bKeelSample)
{
// KEEL FORCE //////////////////////////////////////////////////////////////////////
ComputeSampleKeelForce(fTimeStep, pParent, fMass, iSample, nComponent, vTestPointWorld, vLocalPosition, vecWaterNormal, vSpeedAtPoint,
vSpeedAtPointIncFlow, vFlowVelocity);
// No further processing for these special keel samples (specifically, we don't want VFX processing them).
if(!sample.m_bPontoon)
{
continue;
}
}
// BUOYANCY FORCE //////////////////////////////////////////////////////////////////
vBuoyancyForceApplied = ComputeSampleBuoyancyForce(fTimeStep, pParent, fMass, iSample, nComponent, vTestPointWorld, vecWaterNormal,
buoyancyConstant, partBuoyancyMultiplier, fDepthFactor);
vTotalBuoyancyForce += vBuoyancyForceApplied;
if(vSpeedAtPointIncFlow.Mag2() > 0.1f)
{
// LIFT FORCE //////////////////////////////////////////////////////////////////////
ComputeSampleLiftForce(fTimeStep, pParent, fMass, iSample, nComponent, vTestPointWorld, vecWaterNormal,vSpeedAtPointIncFlow, fWaterSpeedVert);
// KEEL FORCE //////////////////////////////////////////////////////////////////////
/// TODO - Once all boats have proper keel spheres set up, remove the call to ComputeSampleKeelForce from the "else" block below.
if(pBoatHandling && pBoatHandling->m_fKeelSphereSize <= 0.0f)
{
ComputeSampleKeelForce(fTimeStep, pParent, fMass, iSample, nComponent, vTestPointWorld, vLocalPosition, vecWaterNormal, vSpeedAtPoint,
vSpeedAtPointIncFlow, vFlowVelocity);
}
// FLOW INDUCED DRAG ///////////////////////////////////////////////////////////////
float fDragCoefficient = m_fDragCoefficientInUse;
if(sample.m_bPontoon)
{
if(pSeaplaneHandling)
{
fDragCoefficient = pSeaplaneHandling->m_fPontoonDragCoefficient;
}
}
fDragCoefficient -= fDragCoefficient * dragReduction;
vDampingForceComputed = ComputeSampleDragForce(pParent, forceAccumulators, fMass, iSample, nComponent, vTestPointWorld,
vLocalFlowVelocity, fCrossSectionalLength, fDragCoefficient);
}
// BASIC DRAG //////////////////////////////////////////////////////////////////////
if(!pParent->GetIsTypeObject() || !static_cast<CObject*>(pParent)->GetAsProjectile())
{
float fDragMultXY = 0.0f;
float fDragMultZUp = 0.0f;
float fDragMultZDown = 0.0f;
if(sample.m_bPontoon)
{
// Cache the current drag settings so we can run the damping force subroutine so that it damps the vertical motion only for
// seaplane pontoons.
fDragMultXY = pBuoyancyInfo->m_fDragMultXY;
fDragMultZUp = pBuoyancyInfo->m_fDragMultZUp;
fDragMultZDown = pBuoyancyInfo->m_fDragMultZDown;
pBuoyancyInfo->m_fDragMultXY = 0.0f;
pBuoyancyInfo->m_fDragMultZUp = pSeaplaneHandling->m_fPontoonVerticalDampingCoefficientUp;
pBuoyancyInfo->m_fDragMultZDown = pSeaplaneHandling->m_fPontoonVerticalDampingCoefficientDown;
}
vDampingForceComputed += ComputeSampleDampingForce(pParent, forceAccumulators, fMass, iSample, nComponent, vTestPointWorld,
vSpeedAtPoint, partBuoyancyMultiplier);
if(sample.m_bPontoon)
{
pBuoyancyInfo->m_fDragMultXY = fDragMultXY;
pBuoyancyInfo->m_fDragMultZUp = fDragMultZUp;
pBuoyancyInfo->m_fDragMultZDown = fDragMultZDown;
}
}
// Disturb the dynamic water we're hitting.
vSpeedAtPoint = pParent->GetLocalSpeed(vTestPointWorld, true, nComponent);
bool bIsSubmersible = pParent->GetIsTypeVehicle() && static_cast<CVehicle*>(pParent)->InheritsFromSubmarine();
bool bIsTugBoat = m_buoyancyFlags.bReduceLateralBuoyancyForce;
bool bIsSeaHeli = pParent->GetIsTypeVehicle() && static_cast<CVehicle*>( pParent )->InheritsFromHeli() && static_cast<CVehicle*>( pParent )->pHandling->GetSeaPlaneHandlingData();
float fMaxDepthFactor = bIsSubmersible || bIsTugBoat ? 0.5f : 2.0f;
float fMinDepthFactor = bIsSeaHeli ? 0.5f : 0.0f;
if(fDepthFactor > fMinDepthFactor && fDepthFactor < fMaxDepthFactor)
{
// Get some factor based on the size and weight of this water sample.
float fAddToWaterSpeedPercentage = m_fSampleSubmergeLevel[iSample];
fAddToWaterSpeedPercentage *= m_fForceMult * partBuoyancyMultiplier * buoyancyConstant * rage::Min(1.0f, fMass / 100.0f);
// Then scale by the speed through the water.
static float WATER_SPEED_CAP_SQR = 40.0f*40.0f;
static float WATER_SPEED_VERT_MAX = 2.0f;
float fVertVel = rage::Min(vSpeedAtPoint.z, WATER_SPEED_VERT_MAX);
if( !pParent->IsNetworkClone() || fVertVel < 0.0f )
{
fAddToWaterSpeedPercentage *= Clamp(fVertVel*fVertVel, 1.0f, WATER_SPEED_CAP_SQR) / WATER_SPEED_CAP_SQR;
static float WATER_SPEED_ADD_MULT = 40.0f;
static float WATER_SPEED_ADD_CAP = 0.3f;
fAddToWaterSpeedPercentage = rage::Min(WATER_SPEED_ADD_CAP, WATER_SPEED_ADD_MULT * fAddToWaterSpeedPercentage);
if(fAddToWaterSpeedPercentage > 0.0f)
Water::ModifyDynamicWaterSpeed(vTestPointWorld.x, vTestPointWorld.y, fVertVel, fAddToWaterSpeedPercentage);
}
}
// VFX - Process this sample.
if(processFx)
{
// Don't want to do Vfx on the extra dinghy buoyancy samples.
if(!(pBoatHandling && pBoatHandling->m_fDinghySphereBuoyConst>0.0f && fSampleBuoyancyMult != DINGHY_BUOYANCY_MULT_FOR_GENERIC_SAMPLES))
{
Vector3 vecSurfacePos(vTestPointWorld.x, vTestPointWorld.y, fWaterLevel);
vfxWaterSampleData[iSample].isInWater = true;
//vfxWaterSampleData[iSample].vTestPos = RCC_VEC3V(vTestPointWorld);
vfxWaterSampleData[iSample].vSurfacePos = RCC_VEC3V(vecSurfacePos);
//vfxWaterSampleData[iSample].maxLevel = fSampleSize;
vfxWaterSampleData[iSample].currLevel = m_fSampleSubmergeLevel[iSample];
vfxWaterSampleData[iSample].componentId = nComponent;
vfxWaterSampleData[iSample].boneTag = BONETAG_INVALID;
#if __BANK
if (g_vfxWater.RenderFloaterCalculatedSurfacePositions())
{
grcDebugDraw::Sphere(vecSurfacePos, 0.5f, Color32(1.0f, 1.0f, 0.0f, 1.0f), true, -1);
}
#endif
}
}
if(!bKeelSample)
{
// If we have collided with the map, reduce the radius of the buoyancy spheres when deciding if they are
// in water.
if(pParent->GetFrameCollisionHistory()->GetFirstBuildingCollisionRecord())
{
const float kfRadiusReductionFactor = 0.5f;
if(fWaterLevel > vTestPointWorld.z - fSampleSize*kfRadiusReductionFactor)
{
bInWater = true;
}
}
else
{
bInWater = true;
}
}
m_WaterTestHelper.m_vLastWaterNormal.Set(vecWaterNormal);
if(partBuoyancyMultiplier > 0.0f)
{
ComputeSampleStickToSurfaceForce(fTimeStep, pParent, fMass, iSample, nComponent, vTestPointWorld, vSpeedAtPointIncFlow);
}
m_fSubmergedLevel += m_fSampleSubmergeLevel[iSample] / fSampleSize;
}
else
{
bUnderWater = false;
}
#if __BANK
PF_STOP(ProcessBuoyancy);
if((CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING && pParent->GetStatus()==STATUS_PLAYER) || ms_bDrawBuoyancyTests)
{
if(m_fSampleSubmergeLevel[iSample] > 0.0f)
{
grcDebugDraw::Line(Vector3(vTestPointWorld.x - 0.1f, vTestPointWorld.y, fWaterLevel), Vector3(vTestPointWorld.x + 0.1f, vTestPointWorld.y, fWaterLevel), Color32(0.0f,0.0f,1.0f));
grcDebugDraw::Line(Vector3(vTestPointWorld.x, vTestPointWorld.y - 0.1f, fWaterLevel), Vector3(vTestPointWorld.x, vTestPointWorld.y + 0.1f, fWaterLevel), Color32(0.0f,0.0f,1.0f));
grcDebugDraw::Line(Vector3(vTestPointWorld.x, vTestPointWorld.y, fWaterLevel + 0.5f), Vector3(vTestPointWorld.x, vTestPointWorld.y, fWaterLevel + 0.5f + fWaterSpeedVert), Color32(0.0f,1.0f,0.0f));
}
grcDebugDraw::Line(vTestPointWorld, vTestPointWorld - Vector3(0.0f,0.0f,fSampleSize), Color32(1.0f,0.0f,0.0f), Color32(0.0f,0.0f,1.0f));
}
PF_START(ProcessBuoyancy);
#endif // __BANK
} // End of iteration over each buoyancy sample.
if(nSamplesUsedForSubmergeLevel > 0)
{
m_fSubmergedLevel /= (float)nSamplesUsedForSubmergeLevel;
m_fAvgAbsWaterLevel /= (float)nSamplesUsedForSubmergeLevel;
}
for(atMap<int, int>::Iterator it = m_componentIndexToForceAccumMap.CreateIterator(); !it.AtEnd(); it.Next())
{
int nComponentId = it.GetKey();
int nForceAccId = it.GetData();
SForceAccumulator& forceAccumulator = forceAccumulators[nForceAccId];
// Apply water based drag forces:
Vector3 vCombinedResistiveForce = forceAccumulator.vDragForce + forceAccumulator.vDampingForce;
Vector3 vCombinedResistiveTorque = forceAccumulator.vDragTorque + forceAccumulator.vDampingTorque;
ClampResistiveForceAndTorque(fTimeStep, pParent, vCombinedResistiveForce, vCombinedResistiveTorque);
if( vCombinedResistiveForce.IsNonZero() )
{
if( nComponentId < nNumBounds )
{
pParent->ApplyForceCg(vCombinedResistiveForce, fTimeStep);
}
else
{
Warningf( "Force computed on bound which this object (%s) doesn't have: componentId = %d [max=%d].", pParent->GetModelName(), nComponentId, nNumBounds);
}
}
if(vCombinedResistiveTorque.IsNonZero())
{
// Don't want the drag affecting the pitch of the submersible due to centre of mass and centre of buoyancy not being vertically aligned.
if(pParent->GetIsTypeVehicle() &&
static_cast<CVehicle*>(pParent)->InheritsFromSubmarine() )
{
// Transform the torque to model space so we can null the right component.
Matrix34 submarineMatrix = MAT34V_TO_MATRIX34(pParent->GetMatrix());
Vector3 vTorqueModelSpace;
submarineMatrix.UnTransform(vCombinedResistiveTorque, vTorqueModelSpace);
vTorqueModelSpace.x = 0.0f;
// ... and back to world space.
submarineMatrix.Transform(vTorqueModelSpace, vCombinedResistiveTorque);
}
pParent->ApplyTorque(vCombinedResistiveTorque, fTimeStep);
}
if(vCombinedResistiveForce.IsNonZero() || vCombinedResistiveTorque.IsNonZero())
{
if (nComponentId < nNumBounds)
{
pParent->NotifyWaterImpact(vCombinedResistiveForce, vCombinedResistiveTorque, nComponentId, fTimeStep);
}
else
{
Warningf("Force computed on bound which this object (%s) doesn't have: componentId = %d [max=%d].", pParent->GetModelName(), nComponentId, nNumBounds);
}
}
}
#if DEBUG_BUOYANCY
PF_SET(BuoyancyForce, m_fTotalBuoyancyForce);
PF_SET(LiftForce, m_fTotalLiftForce);
PF_SET(KeelForce, m_fTotalKeelForce);
PF_SET(FlowDragForce, m_fTotalFlowDragForce);
PF_SET(BasicDragForce, m_fTotalBasicDragForce);
#endif // DEBUG_BUOYANCY
if(bUnderWater)
{
m_waterLevelStatus = FULLY_IN_WATER;
}
else if(bInWater)
{
m_waterLevelStatus = PARTIALLY_IN_WATER;
}
else
{
m_waterLevelStatus = NOT_IN_WATER;
}
if(pBuoyancyAccel)
*pBuoyancyAccel = vTotalBuoyancyForce.z / fMass;
// This flag will let us know whether such info as "submerged level", "avg submerge level", etc. are actually
// up-to-date for this entity.
m_buoyancyFlags.bBuoyancyInfoUpToDate = true;
// VFX - Process animated peds.
bool hasAnimatedPedSamples = false;
if(processFx && isAnimatedPed)
{
// don't process fx for peds they aren't high lod
bool isHiLODPed = pPed && pPed->GetRagdollInst()->GetCurrentPhysicsLOD() == fragInst::RAGDOLL_LOD_HIGH;
if (!isHiLODPed)
{
processFx = false;
}
CPed* pPed = static_cast<CPed*>(pParent);
if(pPed)
{
// Get the vfx ped info.
CVfxPedInfo* pVfxPedInfo = g_vfxPedInfoMgr.GetInfo(pPed);
if (pVfxPedInfo)
{
// don't process fx for peds that don't have room to store the vfx samples
int numPedSkeletonBoneInfos = pVfxPedInfo->GetPedSkeletonBoneNumInfos();
if (m_nNumInSampleLevelArray-iSampleEnd < numPedSkeletonBoneInfos)
{
processFx = false;
}
// check if we're still interested in processing fx for this ped
if (processFx)
{
hasAnimatedPedSamples = true;
// do extra water tests, and store results in extra water samples
// where the first is stored in the last sample, the second in the second last sample etc
for (s32 i=0; i<numPedSkeletonBoneInfos; i++)
{
const VfxPedSkeletonBoneInfo* pSkeletonBoneInfo = pVfxPedInfo->GetPedSkeletonBoneInfo(i);
const VfxPedBoneWaterInfo* pBoneWaterInfo = pSkeletonBoneInfo->GetBoneWaterInfo();
if (pBoneWaterInfo && pBoneWaterInfo->m_sampleSize>0.0f)
{
s32 currSampleIndex = m_nNumInSampleLevelArray-1-i;
vfxWaterSampleData[currSampleIndex].prevLevel = m_fSampleSubmergeLevel[currSampleIndex];
m_fSampleSubmergeLevel[currSampleIndex] = 0.0f;
const float fSize = pBoneWaterInfo->m_sampleSize;
int boneIndex = pPed->GetBoneIndexFromBoneTag(pSkeletonBoneInfo->m_boneTagA);
if (boneIndex!=-1)
{
Matrix34 boneMtx;
CVfxHelper::GetMatrixFromBoneIndex(RC_MAT34V(boneMtx), pPed, boneIndex);
vTestPointWorld = boneMtx.d;
float fWaterLevel;
if(bInRiver)
{
// Use the plane defined by the normal of the last poly hit by the river probe and the position of the
// point hit to find the z-coord of the river surface (to first order) at the position of the current
// sample point.
fWaterLevel = -(m_WaterTestHelper.m_vLastRiverHitNormal.x*(vTestPointWorld.x-m_WaterTestHelper.m_vLastRiverHitPos.x)
+ m_WaterTestHelper.m_vLastRiverHitNormal.y*(vTestPointWorld.y-m_WaterTestHelper.m_vLastRiverHitPos.y))
/m_WaterTestHelper.m_vLastRiverHitNormal.z + m_WaterTestHelper.m_vLastRiverHitPos.z;
}
else
{
fWaterLevel = fWaterHeightNoWaves;
Water::AddWaveToResult(vTestPointWorld.x, vTestPointWorld.y, &fWaterLevel, &vecWaterNormal, &fWaterSpeedVert, true);
}
vfxWaterSampleData[currSampleIndex].vTestPos = RCC_VEC3V(vTestPointWorld);
vfxWaterSampleData[currSampleIndex].maxLevel = fSize;
if(fWaterLevel > vTestPointWorld.z - fSize)
{
m_fSampleSubmergeLevel[currSampleIndex] = fWaterLevel - (vTestPointWorld.z - fSize);
// scale water level between 0.0 and fSize
m_fSampleSubmergeLevel[currSampleIndex] *= 0.5f;
if (m_fSampleSubmergeLevel[currSampleIndex] > fSize)
{
m_fSampleSubmergeLevel[currSampleIndex] = fSize;
}
// do particles for this point
Vector3 vecSurfacePos(vTestPointWorld.x, vTestPointWorld.y, fWaterLevel);
vfxWaterSampleData[currSampleIndex].isInWater = true;
vfxWaterSampleData[currSampleIndex].vSurfacePos = RCC_VEC3V(vecSurfacePos);
vfxWaterSampleData[currSampleIndex].currLevel = m_fSampleSubmergeLevel[currSampleIndex];
vfxWaterSampleData[currSampleIndex].componentId = pPed->GetRagdollComponentFromBoneTag(pSkeletonBoneInfo->m_boneTagA);
vfxWaterSampleData[currSampleIndex].boneTag = pSkeletonBoneInfo->m_boneTagA;
#if __BANK
if (g_vfxWater.RenderFloaterCalculatedSurfacePositions())
{
grcDebugDraw::Sphere(vecSurfacePos, 0.5f, Color32(1.0f, 0.0f, 1.0f, 1.0f), true, -1);
}
#endif
}
}
}
}
}
}
else
{
processFx = false;
}
}
}
// vfx - process
if (processFx REPLAY_ONLY(&& !CReplayMgr::IsEditModeActive()))
{
g_vfxWater.ProcessVfxWater(pParent, vfxWaterSampleData, hasAnimatedPedSamples, m_nNumInSampleLevelArray);
}
PF_STOP(ProcessBuoyancy);
PF_SETTIMER(ProcessBuoyancyTotal, PF_READ_TIME(ProcessBuoyancy) + PF_READ_TIME(ProcessLowLodBuoyancyTimer));
return bInWater;
}
// Helper functions for computing the various forces on each sample. ///////////////////////////////////////////////////////////////////
float CBuoyancy::ComputeCrossSectionalLengthForDrag(CPhysical* pParent)
{
PF_START(ComputeCrossSection);
// Compute some values used in the drag calculation. These values are constant for each buoyancy sample.
ScalarV vCrossSectionalLength(V_ZERO);
Vector2 vFlow(0.0f, 0.0f);
// If we are in a river instead of a "water" block, the flow will be non-zero and defined by the river flow textures.
if(m_WaterTestHelper.GetRiverHitStored())
{
River::GetRiverFlowFromPosition(pParent->GetTransform().GetPosition(), vFlow);
vFlow.Scale(1.0f/River::GetRiverPushForce());
}
m_WaterTestHelper.m_vLastRiverVelocity.Set(vFlow.x, vFlow.y, 0.0f);
m_WaterTestHelper.m_vLastRiverVelocity.Scale(m_fFlowVelocityScaleFactor);
Vec3V vParentVel = VECTOR3_TO_VEC3V(pParent->GetVelocity());
Vec3V vLastRiverVel = VECTOR3_TO_VEC3V(m_WaterTestHelper.m_vLastRiverVelocity);
Vec3V vLocalFlowVelocity = vParentVel - vLastRiverVel;
// Project all vertices of the bounding box in the direction of the velocity relative to the flow to find
// an approximate value for the cross-sectional area seen by the flow.
phBound* pBound = pParent->GetCurrentPhysicsInst()->GetArchetype()->GetBound();
Vec3V vBoundBoxMin = pBound->GetBoundingBoxMin();
Vec3V vBoundBoxMax = pBound->GetBoundingBoxMax();
// Define the vertices.
Vec3V vVertices0, vVertices1, vVertices2, vVertices3, vVertices4, vVertices5, vVertices6, vVertices7;
vVertices0 = SelectFT(VecBoolV(V_F_T_F_T), vBoundBoxMin, vBoundBoxMax);
vVertices1 = SelectFT(VecBoolV(V_F_F_F_T), vBoundBoxMin, vBoundBoxMax);
vVertices2 = SelectFT(VecBoolV(V_T_F_F_T), vBoundBoxMin, vBoundBoxMax);
vVertices3 = SelectFT(VecBoolV(V_T_T_F_T), vBoundBoxMin, vBoundBoxMax);
vVertices4 = SelectFT(VecBoolV(V_F_T_T_T), vBoundBoxMin, vBoundBoxMax);
vVertices5 = SelectFT(VecBoolV(V_F_F_T_T), vBoundBoxMin, vBoundBoxMax);
vVertices6 = SelectFT(VecBoolV(V_T_F_T_T), vBoundBoxMin, vBoundBoxMax);
vVertices7 = SelectFT(VecBoolV(V_T_T_T_T), vBoundBoxMin, vBoundBoxMax);
// Compute the normal vector to project onto.
Vec3V vProjectionNormal(V_Y_AXIS_WZERO);
ScalarV vTempX(vLocalFlowVelocity.GetX());
ScalarV vTempY(vLocalFlowVelocity.GetY());
vTempX = InvScale(vTempY, vTempX);
vTempX = Scale(vTempX, ScalarV(V_NEGONE));
vProjectionNormal.SetX(vTempX);
vProjectionNormal = NormalizeFastSafe(vProjectionNormal, Vec3V(V_ZERO));
// Project the verts and find the max and min extents along the projection line.
ScalarV vMaxExtent(V_ZERO);
ScalarV vMinExtent(V_ZERO);
ScalarV vProjectedDist;
// Transform the vertices into world space so that they are in the same coord system as the projection vector.
Mat34V matParent = pParent->GetTransform().GetMatrix();
vVertices0 = Transform3x3(matParent, vVertices0);
vProjectedDist = Dot(vProjectionNormal, vVertices0);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vVertices1 = Transform3x3(matParent, vVertices1);
vProjectedDist = Dot(vProjectionNormal, vVertices1);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vVertices2 = Transform3x3(matParent, vVertices2);
vProjectedDist = Dot(vProjectionNormal, vVertices2);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vVertices3 = Transform3x3(matParent, vVertices3);
vProjectedDist = Dot(vProjectionNormal, vVertices3);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vVertices4 = Transform3x3(matParent, vVertices4);
vProjectedDist = Dot(vProjectionNormal, vVertices4);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vVertices5 = Transform3x3(matParent, vVertices5);
vProjectedDist = Dot(vProjectionNormal, vVertices5);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vVertices6 = Transform3x3(matParent, vVertices6);
vProjectedDist = Dot(vProjectionNormal, vVertices6);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vVertices7 = Transform3x3(matParent, vVertices7);
vProjectedDist = Dot(vProjectionNormal, vVertices7);
vMaxExtent = SelectFT(IsGreaterThan(vProjectedDist, vMaxExtent), vMaxExtent, vProjectedDist);
vMinExtent = SelectFT(IsLessThan(vProjectedDist, vMinExtent), vMinExtent, vProjectedDist);
vCrossSectionalLength = vMaxExtent - vMinExtent;
// DEBUG DISPLAY THE CROSS SECTIONAL LENGTH.
#if __DEV && DEBUG_DRAW
PF_STOP(ProcessBuoyancy);
Vec3V vecParentPos = pParent->GetTransform().GetPosition();
if(ms_bDebugDrawCrossSection)
{
grcDebugDraw::Line(vecParentPos+Scale(vProjectionNormal, vMaxExtent),vecParentPos+Scale(vProjectionNormal,vMinExtent), Color_yellow);
}
PF_START(ProcessBuoyancy);
#endif // __DEV && DEBUG_DRAW
PF_STOP(ComputeCrossSection);
return vCrossSectionalLength.Getf();
}
void CBuoyancy::SelectDragCoefficient(CPhysical* pParent)
{
// Decide which drag coefficient to use:
m_fDragCoefficientInUse = ms_fDragCoefficient;
// Peds should only get carried with the flow when they aren't standing.
if(pParent->GetIsTypePed())
{
CPed* pPed = static_cast<CPed*>(pParent);
if(!pPed->GetIsStanding())
m_fDragCoefficientInUse = ms_fPedDragCoefficient;
}
else if(pParent->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(pParent);
if(pVehicle->GetVehicleType()==VEHICLE_TYPE_BOAT || pVehicle->InheritsFromAmphibiousAutomobile())
{
// If this is a boat, override the generic drag coefficient (probably for a box shape) with the value found
// in the vehicle handling file.
CBoatHandlingData* pBoatHandling = NULL;
pBoatHandling = static_cast<CVehicle*>(pParent)->pHandling->GetBoatHandlingData();
if(pBoatHandling)
{
m_fDragCoefficientInUse = pBoatHandling->m_fDragCoefficient;
}
// Increase the drag coefficient when the boat is close to a waterfall to make sure we get quickly pushed over the edge.
if(m_WaterTestHelper.GetRiverHitStored())
{
float fDistToWaterfallSquared = (VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition()) - vWaterFallPosition).Mag2();
if(fDistToWaterfallSquared < rage::square(fWaterFallRadius))
{
TUNE_FLOAT(WATERFALL_DRAG_COEFFICIENT_SCALE, 10.0f, 0.0f, 500.0f, 1.0f);
float fWaterfallDistanceScale = (1.0f - (fDistToWaterfallSquared / rage::square(fWaterFallRadius))) * WATERFALL_DRAG_COEFFICIENT_SCALE;
m_fDragCoefficientInUse *= fWaterfallDistanceScale;
}
}
m_buoyancyFlags.bReduceLateralBuoyancyForce = ( MI_BOAT_TUG.IsValid() && ( pParent->GetModelIndex() == MI_BOAT_TUG.GetModelIndex() ) ) ||
( MI_CAR_APC.IsValid() && ( pParent->GetModelIndex() == MI_CAR_APC.GetModelIndex() ) ) ||
( MI_PLANE_TULA.IsValid() && ( pParent->GetModelIndex() == MI_CAR_APC.GetModelIndex() ) );
}
else if(pVehicle->InheritsFromBike())
{
// Reduced drag on bikes.
m_fDragCoefficientInUse = ms_fBikeDragCoefficient;
}
else
{
m_fDragCoefficientInUse = ms_fCarDragCoefficient;
}
}
else if(pParent->GetIsTypeObject())
{
if(static_cast<CObject*>(pParent)->GetAsProjectile())
{
m_fDragCoefficientInUse = ms_fProjectileDragCoefficient;
}
// Check if this object overrides the default drag coefficient.
else if(const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry(pParent->GetBaseModelInfo()->GetModelNameHash()))
{
if(pTuning->GetBuoyancyDragFactor() != 1.0f)
{
m_fDragCoefficientInUse = ms_fDragCoefficient*pTuning->GetBuoyancyDragFactor();
}
}
}
}
void ComputeSampleOffsetFromComponentCG(const CPhysical* pParent, s32 nComponent, const Vector3& vTestPointWorld, Vector3& vOffsetFromComponentCG)
{
if(nComponent > 0)
{
physicsAssert(pParent->GetCurrentPhysicsInst());
physicsAssert(pParent->GetCurrentPhysicsInst()->GetArchetype());
physicsAssert(pParent->GetCurrentPhysicsInst()->GetArchetype()->GetBound());
const phBound* pBound = pParent->GetCurrentPhysicsInst()->GetArchetype()->GetBound();
if(AssertVerify(pBound->GetType() == phBound::COMPOSITE))
{
const phBoundComposite* pCompBound = static_cast<const phBoundComposite*>(pBound);
physicsAssertf(nComponent >= 0 && nComponent < pCompBound->GetNumBounds(), "nComponent = %d", nComponent);
physicsAssertf(pCompBound->GetBound(nComponent), "Component = %d on model %s", nComponent, pParent->GetModelName());
// The offset of the centre of mass from this bound's local origin.
Vec3V vBoundCGOffset = pCompBound->GetBound(nComponent)->GetCGOffset();
// Transform from bound's local origin to the composite bound's local origin.
vBoundCGOffset = Transform(pCompBound->GetCurrentMatrix(nComponent), vBoundCGOffset);
// Now transform into world space.
vBoundCGOffset = Transform(pParent->GetMatrix(), vBoundCGOffset);
// Finally we can work out the offset of our world space point from the component's centre of mass.
vOffsetFromComponentCG = vTestPointWorld - VEC3V_TO_VECTOR3(vBoundCGOffset);
}
}
else
{
physicsAssert(pParent->GetCurrentPhysicsInst());
physicsAssert(pParent->GetCurrentPhysicsInst()->GetArchetype());
physicsAssert(pParent->GetCurrentPhysicsInst()->GetArchetype()->GetBound());
const phBound* pBound = pParent->GetCurrentPhysicsInst()->GetArchetype()->GetBound();
// The offset of the centre of mass from this bound's local origin.
Vec3V vBoundCGOffset = pBound->GetCGOffset();
// Now transform into world space.
vBoundCGOffset = Transform(pParent->GetMatrix(), vBoundCGOffset);
// Finally we can work out the offset of our world space point from the object's centre of mass.
vOffsetFromComponentCG = vTestPointWorld - VEC3V_TO_VECTOR3(vBoundCGOffset);
}
}
// Clamp a resistive force and torque so that we don't end up reversing an object's velocity due to drag-type forces.
void CBuoyancy::ClampResistiveForceAndTorque(float fTimeStep, const CPhysical* pParent, Vector3& vForce, Vector3& UNUSED_PARAM(vTorque))
{
Vector3 vVelocity = pParent->GetVelocity();
// Make sure the foliage drag force we have accumulated so far won't reverse the velocity.
// Test for a valid timestep here, since CObject::AddPhysics() calls buoyancy.Process() with a timestep of 0.0f
float fInvTimeStep = (Abs(fTimeStep) > 0.0001f) ? 1.0f / fTimeStep : 0.0f;
float fMass = pParent->GetMass();
Vector3 vMaxForce = -vVelocity * fInvTimeStep;
// vMaxForce is the max acceleration allowed at this point; clamp it so we never generate a resistive force
// which causes a "force too large" assert.
vMaxForce.ClampMag(0.0f, 0.99f*DEFAULT_ACCEL_LIMIT);
// Now we can actually turn it into the max allowed force.
vMaxForce.Scale(fMass);
float fMaxForceMag = vMaxForce.IsNonZero() ? vMaxForce.Mag() : 0.0f;
vForce.ClampMag(0.0f, fMaxForceMag);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Compute the force which will actually cause the parent object to float (or not).
Vector3 CBuoyancy::ComputeSampleBuoyancyForce(float fTimeStep, CPhysical* pParent, float fMass, s32 nSample, s32 nComponent, const Vector3& vTestPointWorld,
const Vector3& vWaterNormal, float fBuoyancyConstant, float fPartBuoyancyMultiplier, float fDepthFactor)
{
PF_START(ComputeBuoyancyForce);
Vector3 vBuoyancyForce = m_fSampleSubmergeLevel[nSample] * m_fForceMult * fMass * vWaterNormal * fBuoyancyConstant * fPartBuoyancyMultiplier;
// Allow scaling of buoyancy for specific objects.
physicsAssert(pParent->GetPhysArch());
vBuoyancyForce *= pParent->GetPhysArch()->GetBuoyancyFactor();
if(pParent->GetIsTypeVehicle() && ( static_cast<CVehicle*>(pParent)->InheritsFromBoat() || static_cast<CVehicle*>(pParent)->InheritsFromAmphibiousAutomobile() ) )
{
CBoatHandlingData* pBoatHandling = static_cast<CVehicle*>(pParent)->pHandling->GetBoatHandlingData();
if(fDepthFactor >= 1.0f && pBoatHandling)
{
vBuoyancyForce.Scale(pBoatHandling->m_fDeepWaterSampleBuoyancyMult);
}
}
#if __BANK
if(ms_bDisableBuoyancyForceXY)
{
vBuoyancyForce.x = vBuoyancyForce.y = 0.0f;
}
#endif // __BANK
// Hack to fix B*1978951 - sometimes the tug boat would fail to accelerate past 10mph
// doing this fixes it.
if( m_buoyancyFlags.bReduceLateralBuoyancyForce )
{
vBuoyancyForce.x *= 0.1f;
vBuoyancyForce.y *= 0.1f;
if( pParent->GetIsTypeVehicle() && ( static_cast<CVehicle*>(pParent)->InheritsFromPlane() ) )
{
vBuoyancyForce.x = 0.0f;
vBuoyancyForce.y = 0.0f;
}
}
if( pParent->GetIsTypeVehicle() && ( static_cast<CVehicle*>(pParent)->InheritsFromSubmarine() ) )
{
CTaskVehicleMissionBase* pVehicleTask = static_cast<CVehicle*>(pParent)->GetIntelligence()->GetActiveTask();
bool bShouldHoverAtEnd = false;
if(pVehicleTask && (pVehicleTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_GOTO_SUBMARINE) )
{
CTaskVehicleGoToSubmarine* pTask = static_cast<CTaskVehicleGoToSubmarine *>(pVehicleTask);
if(pTask && pTask->ShouldHoverAtEnd())
{
bShouldHoverAtEnd = true;
}
}
if( !static_cast<CVehicle*>(pParent)->GetDriver() || bShouldHoverAtEnd )
{
vBuoyancyForce.x = 0.0f;
vBuoyancyForce.y = 0.0f;
}
}
#if __ASSERT
// Add some extra debug info to the log if we are going to cause some of the asserts in ApplyForce to fire.
if(!pParent->CheckForceInRange(vBuoyancyForce, DEFAULT_ACCEL_LIMIT))
{
GENERIC_FORCE_CHECK("BUOYANCY FORCE", vBuoyancyForce);
Displayf("m_fSampleSubmergeLevel[%d]: %5.3f", nSample, m_fSampleSubmergeLevel[nSample]);
Displayf("m_fForceMult=%5.3f, fMass=%5.3f, buoyancyConstant=%5.3f, partBuoyancyMultiplier=%5.3f",
m_fForceMult, fMass, fBuoyancyConstant, fPartBuoyancyMultiplier);
Displayf("vecWaterNormal=(%5.3f, %5.3f, %5.3f), (magnitude=%5.3f)\n",
vWaterNormal.x, vWaterNormal.y, vWaterNormal.z, vWaterNormal.Mag());
}
#endif // __ASSERT
DEBUG_SAMPLE_BUOYANCY_FORCE(nSample, "buoyancy", vBuoyancyForce);
#if __DEV
m_fTotalBuoyancyForce += vBuoyancyForce.Mag();
#endif
// Don't apply buoyancy force if we're in Swim FPS strafe underwater or aiming underwater (ie harpoon)
bool bDontApplyBuoyancyForce = false;
if (pParent->GetIsTypePed())
{
CPed *pPed = static_cast<CPed*>(pParent);
if (pPed && pPed->GetIsSwimming())
{
if (pPed->GetPedIntelligence() && pPed->GetPedIntelligence()->GetMotionTaskActiveSimplest()->GetTaskType() == CTaskTypes::TASK_MOTION_AIMING)
{
bool bFPSMode = false;
#if FPS_MODE_SUPPORTED
if (pPed->IsFirstPersonShooterModeEnabledForPlayer(false))
{
bFPSMode = true;
}
#endif //FPS_MODE_SUPPORTED
if ((pPed->GetEquippedWeaponInfo() && pPed->GetEquippedWeaponInfo()->GetIsUnderwaterGun()) || bFPSMode)
{
bDontApplyBuoyancyForce = true;
}
}
}
}
if (!bDontApplyBuoyancyForce)
{
pParent->ApplyForce(vBuoyancyForce, vTestPointWorld - VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition()), nComponent, true, fTimeStep);
}
// // Apply weight-belt.
// if(GetPedWeightBeltActive() && nComponent == RAGDOLL_SPINE0)
// {
// vBuoyancyForce = fMass * -CPhysics::GetGravitationalAcceleration() * FRAGNMASSETMGR->GetWeightBeltMultiplier(nNMAssetID) * Vector3(0.0f,0.0f,1.0f);
//
//#if __BANK
// if(ms_bDisablePedWeightBeltForce)
// {
// vBuoyancyForce.Set(0.0f);
// }
//#endif // __BANK
//
//#if __ASSERT
// // Add some extra debug info to the log if we are going to cause some of the asserts in ApplyForce to fire.
// if(!pParent->CheckForceInRange(vBuoyancyForce, DEFAULT_ACCEL_LIMIT))
// {
// GENERIC_FORCE_CHECK("WEIGHT-BELT FORCE", vBuoyancyForce);
//
// Displayf("m_fSampleSubmergeLevel[%d]: %5.3f", nSample, m_fSampleSubmergeLevel[nSample]);
// Displayf("weight belt multiplier=%5.3f, fMass=%5.3f\n", FRAGNMASSETMGR->GetWeightBeltMultiplier(nNMAssetID), fMass);
// }
//#endif // __ASSERT
// DEBUG_SAMPLE_BUOYANCY_FORCE(nSample, "weight-belt", vBuoyancyForce);
// pParent->ApplyForce(vBuoyancyForce, vTestPointWorld - VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition()), nComponent, true, fTimeStep);
// }
PF_STOP(ComputeBuoyancyForce);
return vBuoyancyForce;
}
// Calculate drag and lift through the water based on movement of individual points.
void CBuoyancy::ComputeSampleLiftForce(float fTimeStep, CPhysical* pParent, float fMass, s32 nSample, s32 nComponent, const Vector3& vTestPointWorld,
const Vector3& vWaterNormal, const Vector3& vSpeedAtPointIncFlow, float fWaterSpeedVert)
{
PF_START(ComputeLiftForce);
CBuoyancyInfo* pBuoyancyInfo = GetBuoyancyInfo(pParent);
if(pBuoyancyInfo->m_fLiftMult > 0.0f && vSpeedAtPointIncFlow.Mag2() > 0.0f)
{
// Cache some data for better performance.
CWaterSample& sample = pBuoyancyInfo->m_WaterSamples[nSample];
//CBoat* pBoat = NULL;
CBoatHandlingData* pBoatHandling = NULL;
if(pParent->GetIsTypeVehicle() && static_cast<CVehicle*>(pParent)->GetVehicleType()==VEHICLE_TYPE_BOAT)
{
//pBoat = static_cast<CBoat*>(pParent);
pBoatHandling = static_cast<CVehicle*>(pParent)->pHandling->GetBoatHandlingData();
}
if(pParent->GetIsTypeVehicle() && static_cast<CVehicle*>(pParent)->InheritsFromAmphibiousAutomobile())
{
pBoatHandling = static_cast<CVehicle*>(pParent)->pHandling->GetBoatHandlingData();
}
float fSpeedAlongNorm = vWaterNormal.Dot(vSpeedAtPointIncFlow);
float fSpeedForLift = 0.0f;
float fAngleOfAttack = 0.0f;
Vector3 vecSpeedAcrossNormal = vSpeedAtPointIncFlow - fSpeedAlongNorm * vWaterNormal;
if(vecSpeedAcrossNormal.Mag2() > 0.0f)
{
Vector3 vecLiftVec = VEC3V_TO_VECTOR3(pParent->GetTransform().GetC());
// tilt lift vector back a bit to get some lift when traveling flat
// might want to do this for boats only
//vecLiftVec -= 0.3f*pParent->GetB();
float fSpeedAlongLiftVec = -vecLiftVec.Dot(vecSpeedAcrossNormal);
fSpeedForLift = vecSpeedAcrossNormal.Mag();
fAngleOfAttack = rage::Atan2f(fSpeedAlongLiftVec, fSpeedForLift);
}
if(fSpeedForLift > 0.0f)
{
float fStdPlaningSpeed = 20.0f;
if(pBoatHandling)
fStdPlaningSpeed = sfWaterSampleCruiseMult*static_cast<CVehicle*>(pParent)->m_Transmission.GetDriveMaxVelocity();
float fLiftForce = fSpeedForLift / fStdPlaningSpeed;
// get lift to drop off quite steeply past desired planing speed (don't want boats to race off too fast)
if(fLiftForce > 1.0f)
fLiftForce = rage::Max(0.0f, 1.0f - 2.0f*(fLiftForce - 1.0f));
float& fSampleSubmergeLevel = m_fSampleSubmergeLevel[nSample];
fLiftForce *= rage::Min(sample.m_fSize, sfWaterSamplePlaneMult*fSampleSubmergeLevel);
fAngleOfAttack += 0.5f;
fAngleOfAttack = Clamp(fAngleOfAttack, -1.0f, 1.0f);
fLiftForce *= fAngleOfAttack * fSampleSubmergeLevel * fMass;
fLiftForce *= pBuoyancyInfo->m_fLiftMult;
if(pBoatHandling && pBoatHandling->m_fDinghySphereBuoyConst>0.0f)
{
// If this boat is set up like a dinghy (with extra buoyancy spheres around the inflatable edge), don't
// allow the extra samples to provide lift while assuming a buoyancy multiplier of 1.0 for the generic samples
// which have had their buoyancy set to zero.
if(sample.m_fBuoyancyMult != DINGHY_BUOYANCY_MULT_FOR_GENERIC_SAMPLES)
fLiftForce = 0.0f;
}
else
{
fLiftForce *= sample.m_fBuoyancyMult;
}
Vector3 vecLiftForce = fLiftForce*vWaterNormal;
#if __BANK
if(ms_bDisableLiftForce)
{
vecLiftForce.Set(0.0f);
}
#endif // __BANK
//fxLiftForce = vecLiftForce; // Setup FX variable.
#if __ASSERT
// Add some extra debug info to the log if we are going to cause some of the asserts in ApplyForce to fire.
if(!pParent->CheckForceInRange(vecLiftForce, DEFAULT_ACCEL_LIMIT))
{
GENERIC_FORCE_CHECK("LIFT FORCE", vecLiftForce);
Displayf("m_fSampleSubmergeLevel[%d]: %5.3f", nSample, m_fSampleSubmergeLevel[nSample]);
Displayf("fLiftForce=%5.3f, fAngleOfAttack=%5.3f", fLiftForce, fAngleOfAttack);
Displayf("vecWaterNormal=(%5.3f, %5.3f, %5.3f), (magnitude=%5.3f)\n",
vWaterNormal.x, vWaterNormal.y, vWaterNormal.z, vWaterNormal.Mag());
}
#endif // __ASSERT
DEBUG_SAMPLE_BUOYANCY_FORCE(nSample, "lift", vecLiftForce);
#if DEBUG_BUOYANCY
m_fTotalLiftForce += vecLiftForce.Mag();
#endif // DEBUG_BUOYANCY
if(fLiftForce > 0.0f)
{
pParent->ApplyForce(vecLiftForce, vTestPointWorld - VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition()), nComponent, true, fTimeStep);
}
// Modify the water simulation based due to lift forces:
// Set defaults for pushing water around.
float fPushWaterFromLiftMult = TEST_PUSH_WATER_DOWN_FROM_LIFT_SPEED_MULT;
float fPushWaterFromLiftCap = TEST_PUSH_WATER_DOWN_FROM_LIFT_SPEED_LIMIT;
float fPushWaterFromLiftApplyMult = TEST_PUSH_WATER_DOWN_FROM_LIFT_APPLY_MULT;
// If this is a boat then get handling specific values.
if(pBoatHandling)
{
fPushWaterFromLiftMult = pBoatHandling->m_fAquaplanePushWaterMult;
fPushWaterFromLiftCap = pBoatHandling->m_fAquaplanePushWaterCap;
fPushWaterFromLiftApplyMult = pBoatHandling->m_fAquaplanePushWaterApply;
}
float fSpeedDown = -m_fSampleSubmergeLevel[nSample] * rage::Min(fPushWaterFromLiftCap, fPushWaterFromLiftMult * fSpeedForLift);
float fPushWaterDownFromLift = rage::Min(1.0f, fPushWaterFromLiftApplyMult * vecLiftForce.z);
if(fPushWaterDownFromLift > 0.0f && fSpeedDown < fWaterSpeedVert)
Water::ModifyDynamicWaterSpeed(vTestPointWorld.x, vTestPointWorld.y, fSpeedDown, fPushWaterDownFromLift);
}
}
PF_STOP(ComputeLiftForce);
}
void CBuoyancy::ComputeSampleKeelForce(float fTimeStep, CPhysical* pParent, float fMass, s32 nSample, s32 nComponent, const Vector3& vTestPointWorld,
const Vector3& vLocalPosition, const Vector3& vWaterNormal, const Vector3& vSpeedAtPoint,
const Vector3& vSpeedAtPointIncFlow, const Vector3& vFlowVelocity)
{
PF_START(ComputeKeelForce);
CBuoyancyInfo* pBuoyancyInfo = GetBuoyancyInfo(pParent);
CVehicle* pVehicle = NULL;
CBoatHandlingData* pBoatHandling = NULL;
if(pParent->GetIsTypeVehicle() &&
( static_cast<CVehicle*>(pParent)->InheritsFromBoat() ||
static_cast<CVehicle*>(pParent)->InheritsFromAmphibiousAutomobile() ) )
{
pVehicle = static_cast<CVehicle*>(pParent);
pBoatHandling = static_cast<CVehicle*>(pParent)->pHandling->GetBoatHandlingData();
}
// If this is a boat which is set up like a dinghy (with extra buoyancy spheres around the inflatable edge), don't
// allow the extra samples to provide a keel force.
if(pBoatHandling && pBoatHandling->m_fDinghySphereBuoyConst>0.0f)
{
if(pBuoyancyInfo->m_WaterSamples[nSample].m_fBuoyancyMult != DINGHY_BUOYANCY_MULT_FOR_GENERIC_SAMPLES)
return;
}
Vector3 vecSide;
vecSide.Cross(VEC3V_TO_VECTOR3(pParent->GetTransform().GetB()), vWaterNormal);
vecSide.NormalizeFast();
Vector3 vecForward;
vecForward.Cross(vWaterNormal, VEC3V_TO_VECTOR3(pParent->GetTransform().GetA()));
vecForward.NormalizeFast();
float fSideSpeed = vSpeedAtPoint.Dot(vecSide);
float fSideSpeedIncFlow = vSpeedAtPointIncFlow.Dot(vecSide);
float fKeelForce = -fSideSpeed * pBuoyancyInfo->m_fKeelMult * fMass;
// Boats have special treatment for the keel force to help them turn around quicker in fast flowing
// rivers even when low throttle is applied.
float fKeelForceFactor = 1.0f;
float fKeelForceAtRudderFactor = 1.0f;
float fKeelDragMult = ms_fKeelDragMult;
if(pBoatHandling)
{
// If these Keel spheres have been set up explicitly scale the keel force by submerge level.
if(pBoatHandling && pBoatHandling->m_fKeelSphereSize > 0.0f)
{
fKeelForce *= m_fSampleSubmergeLevel[nSample];
}
float fKeelRudderExclusionFraction = ms_fMaxKeelRudderExclusionFraction;
float fThrottle = fabs(static_cast<CVehicle*>(pParent)->GetThrottle());
if(fThrottle > ms_fKeelForceThrottleThreshold)
{
float fInterpX = (fThrottle-ms_fKeelForceThrottleThreshold)*1.0f/(1.0f-ms_fKeelForceThrottleThreshold);
fKeelForceFactor = ms_fMinKeelForceFactor*(1.0f-fInterpX) + fInterpX;
// Square the computed coefficient to reduce the effects of the river on the keel force at
// lower throttle values.
fKeelForceFactor *= fKeelForceFactor;
fKeelForceFactor = Clamp(fKeelForceFactor, ms_fMinKeelForceFactor, 1.0f);
}
// We want to reduce the keel force for buoyancy samples which are too close
// to the rudder under certain conditions as this counteracts the turning force.
float fBackOfBoatY = pParent->GetBoundingBoxMin().y;
float fBoatLength = pParent->GetBoundingBoxMax().y-pParent->GetBoundingBoxMin().y;
float fKeelRudderExclusionLength = fKeelRudderExclusionFraction*fBoatLength;
if(vLocalPosition.y < (fBackOfBoatY+fKeelRudderExclusionLength))
{
if(vFlowVelocity.Mag2()>0.001f)
{
// Work out proportion of the flow of the water which is perpendicular to the keel.
float fSideFraction = fabs(vFlowVelocity.Dot(VEC3V_TO_VECTOR3(pParent->GetTransform().GetA())));
fSideFraction /= fabs(vFlowVelocity.Mag());
fKeelForceAtRudderFactor = 1.0f - fSideFraction;
// Blend the rudder keel force using a quartic interpolation.
fKeelForceAtRudderFactor *= fKeelForceAtRudderFactor;
fKeelForceAtRudderFactor *= fKeelForceAtRudderFactor;
// Full keel force at rudder when moving almost parallel with the flow.
if(fKeelForceAtRudderFactor > ms_fFullKeelForceAtRudderLimit)
fKeelForceAtRudderFactor = 1.0f;
fKeelForceAtRudderFactor = Clamp(fKeelForceAtRudderFactor, 0.0f, 1.0f);
}
}
// If these Keel spheres have been set up reduce the keel force when not turning as its causing issues with the boats self steering
if(pVehicle && pBoatHandling && pBoatHandling->m_fKeelSphereSize > 0.0f)
{
float fSteering = fabs(pVehicle->GetSteerAngle())/pVehicle->pHandling->m_fSteeringLock;
fKeelForceAtRudderFactor *= Clamp(fSteering, ms_fKeelForceFactorSteerMultKeelSpheres, 1.0f);
}
}
// Additional keel force due to the flow.
fKeelForce -= (fSideSpeedIncFlow * fKeelDragMult * fMass) * fKeelForceFactor;
// This will reduce the keel force for any buoyancy spheres close to the rudder.
fKeelForce *= fKeelForceAtRudderFactor;
#if __BANK
if(ms_bDisableKeelForce)
{
fKeelForce = 0.0f;
}
#endif // __BANK
#if __ASSERT
// Add some extra debug info to the log if we are going to cause some of the asserts in ApplyForce to fire.
Vector3 vForce = fKeelForce*vecSide;
if(!pParent->CheckForceInRange(vForce, DEFAULT_ACCEL_LIMIT))
{
GENERIC_FORCE_CHECK("KEEL FORCE", vForce);
Displayf("m_fSampleSubmergeLevel[%d]: %5.3f", nSample, m_fSampleSubmergeLevel[nSample]);
Displayf("fKeelForce=%5.3f, fSideSpeed=%5.3f, fSideSpeedIncFlow=%5.3f, fKeelDragMult=%5.3f, m_fKeelMult=%5.3f, fMass=%5.3f",
fKeelForce, fSideSpeed, fSideSpeedIncFlow, fKeelDragMult, pBuoyancyInfo->m_fKeelMult, fMass);
Displayf("vSpeedAtPoint=(%5.3f, %5.3f, %5.3f), (magnitude=%5.3f)",
vSpeedAtPoint.x, vSpeedAtPoint.y, vSpeedAtPoint.z, vSpeedAtPoint.Mag());
Displayf("vSpeedAtPointIncFlow=(%5.3f, %5.3f, %5.3f), (magnitude=%5.3f)\n",
vSpeedAtPointIncFlow.x, vSpeedAtPointIncFlow.y, vSpeedAtPointIncFlow.z, vSpeedAtPointIncFlow.Mag());
}
#endif // __ASSERT
DEBUG_SAMPLE_BUOYANCY_FORCE(nSample, "keel", fKeelForce*vecSide);
Vector3 vKeelForce = fKeelForce*vecSide;
#if __WIN32PC
m_fKeelSideForce = fKeelForce;
#endif // __WIN32PC
#if DEBUG_BUOYANCY
m_fTotalKeelForce += vKeelForce.Mag();
#endif // DEBUG_BUOYANCY
pParent->ApplyForce(vKeelForce, vTestPointWorld - VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition()), nComponent, true, fTimeStep);
PF_STOP(ComputeKeelForce);
}
// Compute the drag forces due to motion through the water. Takes into account the relative velocity of the sample w.r.t. the flow.
Vector3 CBuoyancy::ComputeSampleDragForce(CPhysical* pParent, SForceAccumulator* forceAccumulators, float fMass, s32 nSample, s32 nComponent,
const Vector3& vTestPointWorld, const Vector3& vLocalFlowVelocity, float fCrossSectionalLength,
float fDragCoefficient)
{
PF_START(ComputeDragForce);
SForceAccumulator& forceAccumulator = forceAccumulators[m_componentIndexToForceAccumMap[nComponent]];
CBuoyancyInfo* pBuoyancyInfo = GetBuoyancyInfo(pParent);
// Compute the force induced by the flow on this buoyancy sample.
Vector3 vFlowInducedForce = vLocalFlowVelocity;
float fVelocityDiffSqr = vFlowInducedForce.Mag2();
vFlowInducedForce.NormalizeSafe();
float fFlowInducedForceMag = fDragCoefficient*fCrossSectionalLength*fVelocityDiffSqr/pBuoyancyInfo->m_nNumWaterSamples;
// Objects could have high rotational velocity or be falling from a height which would generate a force which
// exceeds our limits on the maximum acceleration generated by a force so clamp this force below that.
fFlowInducedForceMag = rage::Clamp(fFlowInducedForceMag, 0.0f, 0.9f*DEFAULT_ACCEL_LIMIT*fMass);
vFlowInducedForce.Scale(-1.0f*fFlowInducedForceMag);
// Reduce any vertical flow induced drag on pickups as the extra collision sphere inflates the cross-section
// seen by the flow and can cause some low mass pickups to jump out of the water.
static dev_float sfPickupReductionFactor = 0.1f;
if(pParent->GetIsTypeObject() && static_cast<CObject*>(pParent)->m_nObjectFlags.bIsPickUp)
{
vFlowInducedForce.z *= sfPickupReductionFactor;
}
// Reduced drag on bikes.
if(pParent->GetIsTypeVehicle() && static_cast<CVehicle*>(pParent)->InheritsFromBike())
{
vFlowInducedForce *= (m_fSampleSubmergeLevel[nSample]*m_fSampleSubmergeLevel[nSample]);
}
#if __BANK
if(ms_bDisableFlowInducedForce)
{
vFlowInducedForce.Set(0.0f);
}
#endif // __BANK
#if __ASSERT
// Add some extra debug info to the log if we are going to cause some of the asserts in ApplyForce to fire.
if(!pParent->CheckForceInRange(vFlowInducedForce, DEFAULT_ACCEL_LIMIT))
{
GENERIC_FORCE_CHECK("FLOW-INDUCED DRAG FORCE", vFlowInducedForce);
Displayf("m_fSampleSubmergeLevel[%d]: %5.3f", nSample, m_fSampleSubmergeLevel[nSample]);
Displayf("fDragCoefficient=%5.3f, fCrossSectionalLength=%5.3f, fVelocityDiffSqr=%5.3f",
fDragCoefficient, fCrossSectionalLength, fVelocityDiffSqr);
Displayf("vLocalFlowVelocity=(%5.3f, %5.3f, %5.3f), (magnitude=%5.3f)\n",
vLocalFlowVelocity.x, vLocalFlowVelocity.y, vLocalFlowVelocity.z, vLocalFlowVelocity.Mag());
}
#endif // __ASSERT
DEBUG_SAMPLE_BUOYANCY_FORCE(nSample, "drag (flow)", vFlowInducedForce);
#if DEBUG_BUOYANCY
m_fTotalFlowDragForce += vFlowInducedForce.Mag();
#endif // DEBUG_BUOYANCY
forceAccumulator.vDragForce += vFlowInducedForce;
Vector3 vOffsetFromComponentCG;
ComputeSampleOffsetFromComponentCG(pParent, 0/*nComponent*/, vTestPointWorld, vOffsetFromComponentCG);
Vector3 vDragTorque;
vDragTorque.Cross(vOffsetFromComponentCG, vFlowInducedForce);
forceAccumulator.vDragTorque += vDragTorque;
PF_STOP(ComputeDragForce);
return vFlowInducedForce;
}
// Compute the damping forces due to motion through the water.
// TODO - Merge with drag force (i.e. the one which takes into account relative velocity of the sample to the water)?
Vector3 CBuoyancy::ComputeSampleDampingForce(CPhysical* pParent, SForceAccumulator* forceAccumulators, float fMass, s32 nSample, s32 nComponent,
const Vector3& vTestPointWorld, const Vector3& vSpeedAtPoint, float fPartBuoyancyMultiplier)
{
PF_START(ComputeDampingForce);
SForceAccumulator& forceAccumulator = forceAccumulators[m_componentIndexToForceAccumMap[nComponent]];
CBuoyancyInfo* pBuoyancyInfo = GetBuoyancyInfo(pParent);
Vector3 vClampedSpeedAtPoint = vSpeedAtPoint;
if(vClampedSpeedAtPoint.Mag2() > MAX_SPEED_FOR_DRAG*MAX_SPEED_FOR_DRAG)
vClampedSpeedAtPoint *= MAX_SPEED_FOR_DRAG / vSpeedAtPoint.Mag();
// Separate into horizontal and vertical components.
float fDragMultXY = pBuoyancyInfo->m_fDragMultXY;
float fDragMultZUp = pBuoyancyInfo->m_fDragMultZUp;
float fDragMultZDown = pBuoyancyInfo->m_fDragMultZDown;
float fSubmergeLevel = m_fSampleSubmergeLevel[nSample];
if(pParent->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(pParent);
if(pVehicle->InheritsFromBike())
{
if(fSubmergeLevel < 0.5f*pBuoyancyInfo->m_WaterSamples[nSample].m_fSize)
{
PF_STOP(ComputeDampingForce);
return Vector3(VEC3_ZERO);
}
}
else if(!pVehicle->InheritsFromPlane())
{
if(fSubmergeLevel < 0.4f*pBuoyancyInfo->m_WaterSamples[nSample].m_fSize)
{
PF_STOP(ComputeDampingForce);
return Vector3(VEC3_ZERO);
}
if(Unlikely(pVehicle->GetIsJetSki() && !pVehicle->GetDriver() && !pVehicle->m_Buoyancy.GetRiverBoundProbeResult()))
{
BankFloat kfAbandonedJetSkiDragMultiplier = 3.0f;
fDragMultXY *= kfAbandonedJetSkiDragMultiplier;
}
}
}
Vector3 vecDragForceXY = vClampedSpeedAtPoint;
vecDragForceXY.And(VEC3_ANDZ); // Zero Z-component.
// Horizontal drag should be uniform in direction.
vecDragForceXY.Normalize();
float fSpeedMag2 = vClampedSpeedAtPoint.XYMag2();
vecDragForceXY.Scale(-fDragMultXY * fSpeedMag2 * fSubmergeLevel * fPartBuoyancyMultiplier);
// Vertical drag has different values for moving down into the water, or up out of the water.
float fDragForceZ = -vClampedSpeedAtPoint.z * rage::Abs(vClampedSpeedAtPoint.z);
if(fDragForceZ > 0.0f)
fDragForceZ *= fDragMultZUp;
else
fDragForceZ *= fDragMultZDown;
static dev_float sfWaterSampleZDragMult = 10.0f;
float fWaterSampleDragMult = 1.0f;
if(fDragForceZ > 0.0f)
fWaterSampleDragMult = sfWaterSampleZDragMult;
fDragForceZ *= rage::Min(pBuoyancyInfo->m_WaterSamples[nSample].m_fSize,
fWaterSampleDragMult*m_fSampleSubmergeLevel[nSample]*fPartBuoyancyMultiplier);
// Combine X, Y and Z-components again.
Vector3 vecDragForce = vecDragForceXY;
vecDragForce.z = fDragForceZ;
float fDragForceMag = vecDragForce.Mag();
static float DRAG_ACCEL_LIMIT = 100.0f;
fDragForceMag = rage::Clamp(fDragForceMag, 0.0f, 0.9f*DRAG_ACCEL_LIMIT*fMass);
vecDragForce.NormalizeSafe();
vecDragForce.Scale(fDragForceMag);
#if __BANK
if(ms_bDisableDragForce)
{
vecDragForce.Set(0.0f);
}
#endif // __BANK
#if __ASSERT
// Add some extra debug info to the log if we are going to cause some of the asserts in ApplyForce to fire.
if(!pParent->CheckForceInRange(vecDragForce, DEFAULT_ACCEL_LIMIT))
{
GENERIC_FORCE_CHECK("BASIC DRAG FORCE", vecDragForce);
Displayf("m_fSampleSubmergeLevel[%d]: %5.3f", nSample, m_fSampleSubmergeLevel[nSample]);
Displayf("fMass=%5.3f, fDragForceZ=%5.3f, fDragMultXY=%5.3f, fDragMultZUp=%5.3f, fDragMultZDown=%5.3f",
fMass, fDragForceZ, fDragMultXY, fDragMultZUp, fDragMultZDown);
Displayf("vClampedSpeedAtPoint=(%5.3f, %5.3f, %5.3f), (magnitude=%5.3f)\n",
vClampedSpeedAtPoint.x, vClampedSpeedAtPoint.y, vClampedSpeedAtPoint.z, vClampedSpeedAtPoint.Mag());
}
#endif // __ASSERT
DEBUG_SAMPLE_BUOYANCY_FORCE(nSample, "drag (basic)", vecDragForce);
#if DEBUG_BUOYANCY
m_fTotalBasicDragForce += vecDragForce.Mag();
#endif // DEBUG_BUOYANCY
forceAccumulator.vDampingForce += vecDragForce;
Vector3 vOffsetFromComponentCG;
ComputeSampleOffsetFromComponentCG(pParent, 0/*nComponent*/, vTestPointWorld, vOffsetFromComponentCG);
Vector3 vDampingTorque;
vDampingTorque.Cross(vOffsetFromComponentCG, vecDragForce);
forceAccumulator.vDampingTorque += vDampingTorque;
PF_STOP(ComputeDampingForce);
return vecDragForce;
}
void CBuoyancy::ComputeSampleStickToSurfaceForce(float fTimeStep, CPhysical* pParent, float fMass, s32 nSample, s32 nComponent, const Vector3& vTestPointWorld,
const Vector3& vSpeedAtPointIncFlow)
{
PF_START(ComputeStickToSurfaceForce);
CBuoyancyInfo* pBuoyancyInfo = GetBuoyancyInfo(pParent);
if(m_buoyancyFlags.bShouldStickToWaterSurface)
{
static dev_float fDesiredSubmergeFraction = 0.75f;
static dev_float fStickSpringStrengthBase = 10.0f;
const float fStickSpringStrength = fStickSpringStrengthBase * fMass / (float)pBuoyancyInfo->m_nNumWaterSamples;
const float fStickSpringDamping = -2.0f*rage::Sqrtf(fStickSpringStrength * 9.81f);
if(m_nNumInSampleLevelArray > 0)
{
Vector3 vStickForce = VEC3_ZERO;
vStickForce.z = fStickSpringStrength * ((m_fSampleSubmergeLevel[nSample] / pBuoyancyInfo->m_WaterSamples[nSample].m_fSize)
- fDesiredSubmergeFraction);
vStickForce.z += vSpeedAtPointIncFlow.z * fStickSpringDamping;
#if __BANK
if(ms_bDisableSurfaceStickForce)
{
vStickForce.Set(0.0f);
}
#endif // __BANK
#if __ASSERT
// Add some extra debug info to the log if we are going to cause some of the asserts in ApplyForce to fire.
if(!pParent->CheckForceInRange(vStickForce, DEFAULT_ACCEL_LIMIT))
{
GENERIC_FORCE_CHECK("STICK-TO-SURFACE FORCE", vStickForce);
Displayf("m_fSampleSubmergeLevel[%d]: %5.3f", nSample, m_fSampleSubmergeLevel[nSample]);
Displayf("fStickSpringStrength=%5.3f, fMass=%5.3f, sample size=%5.3f, fStickSpringDamping=%5.3f\n",
fStickSpringStrength, fMass, pBuoyancyInfo->m_WaterSamples[nSample].m_fSize, fStickSpringDamping);
Displayf("vSpeedAtPointIncFlow=(%5.3f, %5.3f, %5.3f), (magnitude=%5.3f)",
vSpeedAtPointIncFlow.x, vSpeedAtPointIncFlow.y, vSpeedAtPointIncFlow.z, vSpeedAtPointIncFlow.Mag());
}
#endif // __ASSERT
DEBUG_SAMPLE_BUOYANCY_FORCE(nSample, "surface stick", vStickForce);
pParent->ApplyForce(vStickForce, vTestPointWorld-VEC3V_TO_VECTOR3(pParent->GetTransform().GetPosition()), nComponent, true, fTimeStep);
}
}
PF_STOP(ComputeStickToSurfaceForce);
}
///////////////////////////////////////////////////////////////////////////////////////////////
bool CBuoyancy::CanUseLowLodBuoyancyMode(const CPhysical* pPhysical) const
{
// Only CObjects should be using low LOD buoyancy mode right now.
physicsAssert(pPhysical->GetIsTypeObject());
const CObject* pObject = static_cast<const CObject*>(pPhysical);
if(pObject->m_nObjectFlags.bIsPickUp)
return false;
if(pObject->IsAScriptEntity() && !m_buoyancyFlags.bScriptLowLodOverride)
return false;
if(pObject->IsADoor())
return false;
if(pObject->GetAsProjectile())
return false;
// Don't want parts which break off vehicles to float on the surface (e.g. tail parts from helicoptors
// shot down over water) so don't allow them to use low LOD buoyancy.
if(pObject->m_nObjectFlags.bVehiclePart)
return false;
if(m_waterLevelStatus==NOT_IN_WATER)
return false;
return true;
}
void CBuoyancy::UpdateBuoyancyLodMode(CPhysical* pPhysical)
{
PF_START(ProcessLowLodBuoyancyTimer);
Matrix34 entMat = MAT34V_TO_MATRIX34(pPhysical->GetMatrix());
float fWaterZAtEntPos;
bool bNearWater = GetWaterLevelIncludingRivers(entMat.d, &fWaterZAtEntPos, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL, pPhysical) != WATERTEST_TYPE_NONE;
if(!bNearWater)
{
PF_STOP(ProcessLowLodBuoyancyTimer);
PF_SETTIMER(ProcessBuoyancyTotal, PF_READ_TIME(ProcessBuoyancy) + PF_READ_TIME(ProcessLowLodBuoyancyTimer));
return;
}
float fBuoyancyLodDist = ms_fObjectLodDistance;
bool bIgnoreCollisionInLowLodMode = false;
if(const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry(pPhysical->GetBaseModelInfo()->GetModelNameHash()))
{
// Check if this object overrides the LOD distance.
if(pTuning->GetLowLodBuoyancyDistance() >= 0.0f)
{
fBuoyancyLodDist = pTuning->GetLowLodBuoyancyDistance();
}
// Small objects in low-LOD buoyancy mode can be configured to ignore collisions.
if(pTuning->IgnoreCollisionInLowLodBuoyancyMode())
{
bIgnoreCollisionInLowLodMode = true;
}
}
if(CPed* pLocalPlayer = CPedFactory::GetFactory()->GetLocalPlayer())
{
float fDistanceSqToPlayer = MagSquared(pPhysical->GetTransform().GetPosition() - pLocalPlayer->GetTransform().GetPosition()).Getf();
if(fDistanceSqToPlayer > fBuoyancyLodDist*fBuoyancyLodDist && bNearWater)
{
if(!m_buoyancyFlags.bLowLodBuoyancyMode && CanUseLowLodBuoyancyMode(pPhysical))
{
// Switch to low LOD buoyancy mode - deactivate the physics instance and set the flag so the matrix gets
// moved with the water surface.
m_buoyancyFlags.bWasActiveWhenLodded = false;
if(pPhysical->GetCurrentPhysicsInst() && pPhysical->GetCurrentPhysicsInst()->IsInLevel()
&& CPhysics::GetLevel()->IsActive(pPhysical->GetCurrentPhysicsInst()->GetLevelIndex()))
{
m_buoyancyFlags.bWasActiveWhenLodded = true;
CPhysics::GetSimulator()->DeactivateObject(pPhysical->GetCurrentPhysicsInst()->GetLevelIndex());
}
if(bIgnoreCollisionInLowLodMode)
{
pPhysical->DisableCollision();
}
// Cache the vertical offset of the boat with respect to the water height at the entity position
// so we get the draught right.
SetLowLodDraughtOffset(entMat.d.z-fWaterZAtEntPos);
SetTimeInLowLodMode((float)fwTimer::GetTimeInMilliseconds()/1000.0f);
m_buoyancyFlags.bLowLodBuoyancyMode = true;
}
}
else
{
if(m_buoyancyFlags.bLowLodBuoyancyMode)
{
// Switch back to full buoyancy mode.
if(m_buoyancyFlags.bWasActiveWhenLodded)
{
phCollider* pCollider = pPhysical->GetCollider();
// check if this vehicle is asleep, and wake up when necessary
if(!pCollider || (pCollider->GetSleep() && pCollider->GetSleep()->IsAsleep()))
{
if(pCollider)
{
pCollider->GetSleep()->Reset(true);
}
else
{
pPhysical->ActivatePhysics();
}
}
}
if(bIgnoreCollisionInLowLodMode)
{
pPhysical->EnableCollision();
}
m_buoyancyFlags.bLowLodBuoyancyMode = false;
}
}
}
PF_STOP(ProcessLowLodBuoyancyTimer);
PF_SETTIMER(ProcessBuoyancyTotal, PF_READ_TIME(ProcessBuoyancy) + PF_READ_TIME(ProcessLowLodBuoyancyTimer));
}
void CBuoyancy::RejuvenateMatrixIfNecessary(Matrix34& matrix)
{
// See how far away from normal and orthogonal the matrix gets and renormalise it if necessary.
if(!matrix.IsOrthonormal(REJUVENATE_ERROR))
{
#if __ASSERT
Displayf("[CBuoyancy::ProcessLowLodBuoyancy] Non orthonormal matrix detected. Rejuvenating...");
#endif // __ASSERT
if(matrix.a.IsNonZero() && matrix.b.IsNonZero() && matrix.c.IsNonZero())
{
matrix.b.Normalize();
matrix.c.Cross(matrix.a, matrix.b);
matrix.c.Normalize();
matrix.a.Cross(matrix.b, matrix.c);
}
else
{
// Protection against very bad matrices...
matrix.Identity3x3();
}
}
}
void CBuoyancy::ProcessLowLodBuoyancy(CPhysical* pPhysical)
{
#if GTA_REPLAY
if(CReplayMgr::IsReplayInControlOfWorld())
return;
#endif // GTA_REPLAY
// If a boat is in low LOD anchor mode, it is physically inactive and we place it based on the water level sampled at
// points around the edges of the bounding box.
PF_START(ProcessLowLodBuoyancyTimer);
if(m_buoyancyFlags.bLowLodBuoyancyMode)
{
const Vector3 vOriginalPosition = VEC3V_TO_VECTOR3(pPhysical->GetTransform().GetPosition());
// If this physical has woken up, force it back to sleep.
if(pPhysical->GetCurrentPhysicsInst() && pPhysical->GetCurrentPhysicsInst()->IsInLevel()
&& CPhysics::GetLevel()->IsActive(pPhysical->GetCurrentPhysicsInst()->GetLevelIndex()))
{
CPhysics::GetSimulator()->DeactivateObject(pPhysical->GetCurrentPhysicsInst()->GetLevelIndex());
}
#if __BANK
Color32 colour = Color_yellow;
#endif // __BANK
float fWaterZAtEntPos = 0.0f;
if(GetWaterLevelIncludingRivers(vOriginalPosition, &fWaterZAtEntPos, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL, pPhysical))
{
// If we know the optimum stable height offset for this object and it isn't at that offset yet, LERP it
// there over a few frames.
float fLowLodDraughtOffset = GetLowLodDraughtOffset();
if(const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry(pPhysical->GetBaseModelInfo()->GetModelNameHash()))
{
if(fabs(pTuning->GetLowLodBuoyancyHeightOffset()) > 0.0f)
{
float fDifferenceToIdeal = pTuning->GetLowLodBuoyancyHeightOffset() - fLowLodDraughtOffset;
if(fabs(fDifferenceToIdeal) > 0.001f)
{
dev_float sfStabiliseFactor = 0.1f;
fLowLodDraughtOffset += sfStabiliseFactor * fDifferenceToIdeal;
SetLowLodDraughtOffset(fLowLodDraughtOffset);
}
}
}
// Now we actually move the object to simulate buoyancy physics.
Matrix34 entMat = MAT34V_TO_MATRIX34(pPhysical->GetMatrix());
Assertf(g_WorldLimits_AABB.ContainsPoint(VECTOR3_TO_VEC3V(entMat.d)), "%s is already out of the world. Coords (%5.3f, %5.3f, %5.3f).",
pPhysical->GetModelName(), entMat.d.x, entMat.d.y, entMat.d.z);
Vector3 vPlaneNormal;
ComputeLowLodBuoyancyPlaneNormal(pPhysical, entMat, vPlaneNormal);
// Figure out how much to rotate the object's matrix by to match the plane's orientation.
Vector3 vRotationAxis;
Assertf(entMat.c.x == entMat.c.x && entMat.c.y == entMat.c.y && entMat.c.z == entMat.c.z,
"entMat.c has non-finite component: (%5.3f, %5.3f, %5.3f)", entMat.c.x, entMat.c.y, entMat.c.z);
Assertf(vPlaneNormal.x == vPlaneNormal.x && vPlaneNormal.y == vPlaneNormal.y && vPlaneNormal.z == vPlaneNormal.z,
"vPlaneNormal has non-finite component: (%5.3f, %5.3f, %5.3f)", vPlaneNormal.x, vPlaneNormal.y, vPlaneNormal.z);
vRotationAxis.Cross(entMat.c, vPlaneNormal);
vRotationAxis.NormalizeFast();
float fRotationAngle = entMat.c.Angle(vPlaneNormal);
// If we are far away from the equilibrium orientation for this object (because of big waves or other external
// forces) we limit the amount we move towards the ideal position each frame. Only do this for
// the first few seconds after switching to low LOD mode or it damps the motion when following the waves.
const float kfTimeToLerpAfterSwitchToLowLod = 2.5f;
float fTimeSpentLerping = fwTimer::GetTimeInMilliseconds()/1000.0f - GetTimeInLowLodMode();
if(fTimeSpentLerping < kfTimeToLerpAfterSwitchToLowLod)
{
float fX = fTimeSpentLerping/kfTimeToLerpAfterSwitchToLowLod;
float kfStabiliseFactor = rage::Min(fX*fX, 1.0f);
fRotationAngle *= kfStabiliseFactor;
}
if(fabs(fRotationAngle) > SMALL_FLOAT && vRotationAxis.FiniteElements() && vRotationAxis.IsNonZero())
{
entMat.RotateUnitAxis(vRotationAxis, fRotationAngle);
RejuvenateMatrixIfNecessary(entMat);
pPhysical->SetMatrix(entMat);
}
float fNewZPos = fWaterZAtEntPos+fLowLodDraughtOffset;
pPhysical->SetPosition(Vector3(vOriginalPosition.x, vOriginalPosition.y, fNewZPos));
m_vLowLodVelocityXYZLowLodTimerW.Set(0.0f, 0.0f, (fNewZPos - vOriginalPosition.z)*fwTimer::GetInvTimeStep());
// Update the "is in water" flag as would be done in CBuoyancy::Process().
pPhysical->SetIsInWater(true);
}
#if __BANK
else
{
// Object's entity matrix is not in water if we get here.
colour = Color_green;
pPhysical->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = false;
}
#endif // __BANK
#if __BANK
// Indicate this boat is in low LOD anchor mode for debugging (green = low LOD mode but not being processed
// because it's not in the water, yellow = low LOD mode and actively being moved by this function).
if(CBuoyancy::ms_bIndicateLowLODBuoyancy)
{
grcDebugDraw::Sphere(vOriginalPosition + Vector3(0.0f, 0.0f, 2.0f), 1.0f, colour);
}
#endif // __BANK
}
PF_STOP(ProcessLowLodBuoyancyTimer);
PF_SETTIMER(ProcessBuoyancyTotal, PF_READ_TIME(ProcessBuoyancy) + PF_READ_TIME(ProcessLowLodBuoyancyTimer));
}
void CBuoyancy::ComputeLowLodBuoyancyPlaneNormal(const CPhysical* pPhysical, const Matrix34& entMat, Vector3& vPlaneNormal)
{
// Work out the position of some test points in a triangle around the bounding box.
Vector3 vBoundBoxMin = pPhysical->GetBoundingBoxMin();
Vector3 vBoundBoxMax = pPhysical->GetBoundingBoxMax();
const int nNumVerts = 3;
Vector3 vBoundBoxBottomVerts[nNumVerts];
vBoundBoxBottomVerts[0] = Vector3(vBoundBoxMin.x, vBoundBoxMin.y, vBoundBoxMin.z);
vBoundBoxBottomVerts[1] = Vector3(vBoundBoxMax.x, vBoundBoxMin.y, vBoundBoxMin.z);
vBoundBoxBottomVerts[2] = Vector3(0.5f*(vBoundBoxMin.x+vBoundBoxMax.x), vBoundBoxMax.y, vBoundBoxMin.z);
for(int nVert = 0; nVert < nNumVerts; ++nVert)
{
entMat.Transform(vBoundBoxBottomVerts[nVert]);
float fWaterZ = 0.0f;
CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(vBoundBoxBottomVerts[nVert], &fWaterZ);
vBoundBoxBottomVerts[nVert].z = fWaterZ;
#if __BANK
if(CBuoyancy::ms_bIndicateLowLODBuoyancy)
{
grcDebugDraw::Sphere(vBoundBoxBottomVerts[nVert], 0.1f, Color_red);
}
#endif // __BANK
}
// Use the verts above to define a plane which can be used to set the orientation of the object.
vPlaneNormal.Cross(vBoundBoxBottomVerts[0]-vBoundBoxBottomVerts[1], vBoundBoxBottomVerts[0]-vBoundBoxBottomVerts[2]);
// If the object was upside-down, we might get a normal which is upside-down too. Reverse it in that case.
if(vPlaneNormal.z < 0.0f)
{
vPlaneNormal.Scale(-1.0f);
}
#if __ASSERT
if(!Verifyf(vPlaneNormal.x == vPlaneNormal.x && vPlaneNormal.y == vPlaneNormal.y && vPlaneNormal.z == vPlaneNormal.z,
"vPlaneNormal has non-finite component: (%5.3f, %5.3f, %5.3f)", vPlaneNormal.x, vPlaneNormal.y, vPlaneNormal.z))
{
Displayf("Entity: '%s'", pPhysical->GetModelName());
Displayf("vBoundBoxMin: (%5.3f, %5.3f, %5.3f)", vBoundBoxMin.x, vBoundBoxMin.y, vBoundBoxMin.z);
Displayf("vBoundBoxMax: (%5.3f, %5.3f, %5.3f)", vBoundBoxMax.x, vBoundBoxMax.y, vBoundBoxMax.z);
Displayf("---------- entMat: ----------");
Displayf("(%5.3f, %5.3f, %5.3f)", entMat.a.x, entMat.a.y, entMat.a.z);
Displayf("(%5.3f, %5.3f, %5.3f)", entMat.b.x, entMat.b.y, entMat.b.z);
Displayf("(%5.3f, %5.3f, %5.3f)", entMat.c.x, entMat.c.y, entMat.c.z);
Displayf("(%5.3f, %5.3f, %5.3f)", entMat.d.x, entMat.d.y, entMat.d.z);
Displayf("-----------------------------");
Displayf("vBoundBoxBottomVerts[0]: (%5.3f, %5.3f, %5.3f)", vBoundBoxBottomVerts[0].x, vBoundBoxBottomVerts[0].y, vBoundBoxBottomVerts[0].z);
Displayf("vBoundBoxBottomVerts[1]: (%5.3f, %5.3f, %5.3f)", vBoundBoxBottomVerts[1].x, vBoundBoxBottomVerts[1].y, vBoundBoxBottomVerts[1].z);
Displayf("vBoundBoxBottomVerts[2]: (%5.3f, %5.3f, %5.3f)", vBoundBoxBottomVerts[2].x, vBoundBoxBottomVerts[2].y, vBoundBoxBottomVerts[2].z);
}
#endif // __ASSERT
}
void CBuoyancy::ResetBuoyancy()
{
//Assert(m_fSampleSubmergeLevel);
if(m_fSampleSubmergeLevel)
{
for(int i=0; i<m_nNumInSampleLevelArray; i++)
{
// reset values
m_fSampleSubmergeLevel[i] = 0.0f;
}
}
m_buoyancyFlags.bBuoyancyInfoUpToDate = false;
}
float CBuoyancy::GetWaterLevelOnSample(int iSampleIndex) const
{
if(m_fSampleSubmergeLevel != NULL)
{
Assert(iSampleIndex < m_nNumInSampleLevelArray);
return m_fSampleSubmergeLevel[iSampleIndex];
}
else
{
return 0.0f;
}
}
#if __BANK
void DisplayLowLodAngularOffsetForSelectedBoat()
{
if(CDebugScene::FocusEntities_Get(0) && CDebugScene::FocusEntities_Get(0)->GetIsTypeVehicle()
&& static_cast<CVehicle*>(CDebugScene::FocusEntities_Get(0))->InheritsFromBoat())
{
CBoat* pBoat = static_cast<CBoat*>(CDebugScene::FocusEntities_Get(0));
if(pBoat && pBoat->GetBoatHandling())
{
CBoat::DisplayAngularOffsetForLowLod( pBoat );
}
}
}
void DisplayLowLodDraughtOffsetForSelectedBoat()
{
if(CDebugScene::FocusEntities_Get(0) && CDebugScene::FocusEntities_Get(0)->GetIsTypeVehicle()
&& static_cast<CVehicle*>(CDebugScene::FocusEntities_Get(0))->InheritsFromBoat())
{
CBoat* pBoat = static_cast<CBoat*>(CDebugScene::FocusEntities_Get(0));
if(pBoat && pBoat->GetBoatHandling())
{
CBoat::DisplayDraughtOffsetForLowLod( pBoat );
}
}
}
void CanSelectedVehicleAnchorHereCB()
{
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX; ++i)
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(i);
if(pEnt && pEnt->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(pEnt);
if(CAnchorHelper::IsVehicleAnchorable(pVehicle))
{
CAnchorHelper* pAnchorHelper = CAnchorHelper::GetAnchorHelperPtr(pVehicle);
if(AssertVerify(pAnchorHelper))
{
pAnchorHelper->CanAnchorHere();
}
}
}
}
}
void CalmWater()
{
Water::MakeWaterFlat();
}
void SpawnBoatsAndSelectForMeasuringLowLodAngularOffset()
{
// * Make water flat.
// * Generate list of all boat models.
// * Spawn all boats in list.
// * Wait for boats to become still.
//
// Now ready for user to press 'Display angular offset for all selected boats' button.
Water::MakeWaterFlat();
physicsDisplayf("List of all boats:");
{
fwArchetypeDynamicFactory<CVehicleModelInfo>& vehModelInfoStore = CModelInfo::GetVehicleModelInfoStore();
atArray<CVehicleModelInfo*> typeArray;
vehModelInfoStore.GatherPtrs(typeArray);
int nBoatNames = typeArray.GetCount();
const int MAX_NUM_VEHICLE_NAMES = 500;
s32 boatList[MAX_NUM_VEHICLE_NAMES];
int nActualNum = 0;
for(int i=0; i < nBoatNames; ++i)
{
if( typeArray[i]->GetVehicleType() == VEHICLE_TYPE_BOAT ||
typeArray[i]->GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE ||
typeArray[i]->GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE )
{
boatList[nActualNum] = i;
nActualNum++;
}
}
nBoatNames = nActualNum;
atArray<const char*> vehicleNames;
fwModelId modelId;
for(int i=0; i < nBoatNames; ++i)
{
vehicleNames.PushAndGrow(typeArray[boatList[i]]->GetModelName());
physicsDisplayf("%d, %s", i, vehicleNames[i]);
modelId = CModelInfo::GetModelIdFromName(vehicleNames[i]);
physicsAssert(modelId.IsValid());
// if(CVehicle::GetPool()->GetNoOfFreeSpaces() < 5)
// return NULL;
// Stream vehicle asset in.
u32 flags = CModelInfo::GetAssetStreamFlags(modelId);
CModelInfo::RequestAssets(modelId, STRFLAG_DONTDELETE);
CStreaming::LoadAllRequestedObjects();
if(CModelInfo::HaveAssetsLoaded(modelId))
{
// if previously vehicle was deletable, then set it to be deletable
if(!(flags & STRFLAG_DONTDELETE))
{
CModelInfo::SetAssetsAreDeletable(modelId);
}
Vector3 vRadialDirection(VEC3V_TO_VECTOR3(CGameWorld::FindLocalPlayer()->GetTransform().GetB()));
vRadialDirection.RotateZ(i * 2.0f*PI/float(nBoatNames));
TUNE_FLOAT(CIRCLE_RADIUS, 20.0f, 1.0f, 100.0f, 1.0f);
Matrix34 CreationMatrix;
CreationMatrix.Identity();
CreationMatrix.d = CGameWorld::FindLocalPlayerCoors() + vRadialDirection*CIRCLE_RADIUS;
CreationMatrix.d.z += 2.0f;
CVehicle* pNewVehicle = CVehicleFactory::GetFactory()->Create(modelId, ENTITY_OWNEDBY_OTHER, POPTYPE_RANDOM_AMBIENT, &CreationMatrix);
if(pNewVehicle)
{
pNewVehicle->m_nVehicleFlags.bCreatedUsingCheat = true;
pNewVehicle->m_nVehicleFlags.bHasBeenOwnedByPlayer = true;
pNewVehicle->PlaceOnRoadAdjust();
CBaseModelInfo* pModel = pNewVehicle->GetBaseModelInfo();
Assert(pModel->GetFragType());
int nTestTypeFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER;
int nTestTypeFlagsCars = ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_BOX_VEHICLE_TYPE;
Vector3 start, end;
pNewVehicle->TransformIntoWorldSpace(start, Vector3(0.0f, pModel->GetBoundingBoxMax().y, 0.0f));
pNewVehicle->TransformIntoWorldSpace(end, Vector3(0.0f, pModel->GetBoundingBoxMin().y, 0.0f));
Vector3 start2 = start + Vector3(0.0f, 0.0f, 1.0f);
Vector3 end2 = end + Vector3(0.0f, 0.0f, 1.0f);
Vector3 startCars, endCars;
pNewVehicle->TransformIntoWorldSpace(startCars, Vector3(0.0f, pModel->GetBoundingBoxMax().y+2.0f, 0.0f));
pNewVehicle->TransformIntoWorldSpace(endCars, Vector3(0.0f, pModel->GetBoundingBoxMin().y-2.0f, 0.0f));
Vector3 startCars2, endCars2;
pNewVehicle->TransformIntoWorldSpace(startCars2, Vector3(2.0f, 0.0f, 0.0f));
pNewVehicle->TransformIntoWorldSpace(endCars2, Vector3(-2.0f, 0.0f, 0.0f));
Vector3 startCars3, endCars3;
pNewVehicle->TransformIntoWorldSpace(startCars3, Vector3(0.0f, 0.0f, 2.0f));
pNewVehicle->TransformIntoWorldSpace(endCars3, Vector3(0.0f, 0.0f, -2.0f));
WorldProbe::CShapeTestProbeDesc probeDesc1;
probeDesc1.SetStartAndEnd(start, end);
probeDesc1.SetExcludeEntity(pNewVehicle);
probeDesc1.SetIncludeFlags(nTestTypeFlags);
probeDesc1.SetContext(WorldProbe::LOS_Unspecified);
WorldProbe::CShapeTestProbeDesc probeDesc2;
probeDesc2.SetStartAndEnd(start2, end2);
probeDesc2.SetIncludeFlags(nTestTypeFlags);
probeDesc2.SetContext(WorldProbe::LOS_Unspecified);
WorldProbe::CShapeTestProbeDesc probeDesc3;
probeDesc3.SetStartAndEnd(startCars, endCars);
probeDesc3.SetExcludeEntity(pNewVehicle);
probeDesc3.SetIncludeFlags(nTestTypeFlagsCars);
probeDesc3.SetContext(WorldProbe::LOS_Unspecified);
WorldProbe::CShapeTestProbeDesc probeDesc4;
probeDesc4.SetStartAndEnd(startCars2, endCars2);
probeDesc4.SetExcludeEntity(pNewVehicle);
probeDesc4.SetIncludeFlags(nTestTypeFlagsCars);
probeDesc4.SetContext(WorldProbe::LOS_Unspecified);
WorldProbe::CShapeTestProbeDesc probeDesc5;
probeDesc5.SetStartAndEnd(startCars3, endCars3);
probeDesc5.SetExcludeEntity(pNewVehicle);
probeDesc5.SetIncludeFlags(nTestTypeFlagsCars);
probeDesc5.SetContext(WorldProbe::LOS_Unspecified);
CScriptEntities::ClearSpaceForMissionEntity(pNewVehicle);
CGameWorld::Add(pNewVehicle, CGameWorld::OUTSIDE);
pNewVehicle->SetCarDoorLocks(CARLOCK_UNLOCKED);
}
}
}
}
}
#endif // __BANK
#if __BANK
void CBuoyancy::AddWidgetsToBank(bkBank& bank)
{
// Basic buoyancy debug:
bank.AddToggle("Draw Buoyancy Tests", &ms_bDrawBuoyancyTests);
bank.AddToggle("Draw Buoyancy Sample Spheres", &ms_bDrawBuoyancySampleSpheres);
#if __DEV
bank.AddButton("Reload focus buoyancy samples", CPhysics::ReloadFocusEntityWaterSamplesCB);
#endif // __DEV
bank.AddToggle("Display submerged levels selected entity.", &CBuoyancy::ms_bDebugDisplaySubmergedLevels);
bank.AddToggle("Display buoyancy forces", &ms_bDisplayBuoyancyForces);
bank.AddToggle("Write to TTY", &ms_bWriteBuoyancyForceToTTY);
bank.AddSlider("Sample ID to debug (-1 for all)", &ms_nSampleToDisplay, -1, 100, 1);
bank.PushGroup("Anchor helper");
bank.AddToggle("Enable anchor debug", &CAnchorHelper::ms_bDebugModeEnabled);
bank.AddButton("Call CanAnchorHere on selected vehicle", CanSelectedVehicleAnchorHereCB);
bank.PopGroup(); // "Anchor helper"
bank.PushGroup("Buoyancy LOD");
bank.AddSlider("Boat anchor LOD distance", &ms_fBoatAnchorLodDistance, 0.0f, 1000.0f, 1.0f);
bank.AddSlider("Object LOD distance", &ms_fObjectLodDistance, 0.0f, 1000.0f, 1.0f);
bank.AddToggle("Indicate objects using low LOD buoyancy", &ms_bIndicateLowLODBuoyancy);
bank.AddButton("Calm water", CalmWater);
bank.AddButton("Display angular offset for selected boat", DisplayLowLodAngularOffsetForSelectedBoat);
bank.AddButton("Display draught offset for selected boat", DisplayLowLodDraughtOffsetForSelectedBoat);
bank.PopGroup(); // "Buoyancy LOD"
bank.PushGroup("Disable forces");
bank.AddToggle("Disable horizontal part of buoyancy force", &ms_bDisableBuoyancyForceXY);
bank.AddToggle("Disable lift force", &ms_bDisableLiftForce);
bank.AddToggle("Disable keel force", &ms_bDisableKeelForce);
bank.AddToggle("Disable flow induced force", &ms_bDisableFlowInducedForce);
bank.AddToggle("Disable water drag force", &ms_bDisableDragForce);
bank.AddToggle("Disable surface stick force", &ms_bDisableSurfaceStickForce);
bank.AddToggle("Disable ped weight belt buoyancy force", &ms_bDisablePedWeightBeltForce);
bank.PopGroup(); // "Disable forces"
bank.PushGroup("Keel force params");
bank.AddSlider("Full keel force at rudder limit", &ms_fFullKeelForceAtRudderLimit, 0.0f, 1.0f, 0.01f);
bank.AddSlider("Keel drag multiplier", &ms_fKeelDragMult, 0.0f, 10.0f, 0.01f);
bank.AddSlider("Keel force factor steer mult", &ms_fKeelForceFactorSteerMultKeelSpheres, 0.0f, 10.0f, 0.01f);
bank.AddSlider("Min keel force factor", &ms_fMinKeelForceFactor, 0.0f, 1.0f, 0.01f);
bank.AddSlider("Keel force throttle threshold", &ms_fKeelForceThrottleThreshold, 0.0f, 1.0f, 0.01f);
bank.AddSlider("Max keel rudder exclusion fraction", &ms_fMaxKeelRudderExclusionFraction, 0.0f, 10.0f, 0.1f);
bank.PopGroup(); // "Keel force params"
// "Water" (i.e. oceans, lakes, etc.) debug:
// NOTHING IN THIS CATEGORY YET.
// River specific debug:
bank.PushGroup("Rivers");
bank.AddToggle("Disable river probes", &CWaterTestHelper::ms_bDisableRiverProbes);
bank.AddSlider("Distance before issuing new probe", &CWaterTestHelper::ms_fDistanceThresholdForRiverProbe, 0.0f, 3.0f, 0.01f);
bank.AddToggle("Render probe hit position", &ms_bRiverBoundDebugDraw);
bank.AddSlider("Default river drag coefficient", &ms_fDragCoefficient, 0.0f, 10000.0f, 0.01f);
bank.AddSlider("River drag coefficient (peds)", &ms_fPedDragCoefficient, 0.0f, 10000.0f, 0.01f);
bank.AddSlider("Flow velocity scale factor", &ms_fFlowVelocityScaleFactor, 0.0f, 100000.0f, 0.01f);
bank.AddSlider("Max speed to apply river forces", &ms_fVehicleMaximumSpeedToApplyRiverForces, 0.0f, 100000.0f, 0.01f);
bank.AddToggle("Visualise cross-sectional length seen by flow", &ms_bDebugDrawCrossSection);
bank.PushGroup("Flow field debug");
bank.AddToggle("Visualise flow field (Enable measuring tool first!)", &ms_bVisualiseRiverFlowField);
bank.AddSlider("Grid spacing", &ms_fGridSpacing, 0.01f, 100.0f, 0.01f);
bank.AddSlider("Height offset", &ms_fHeightOffset, -10.0f, 10.0f, 0.1f);
bank.AddSlider("Vector scale factor", &ms_fScaleFactor, 0.01f, 100.0f, 0.1f);
bank.AddSlider("Grid size X", &ms_nGridSizeX, 1, 200, 1);
bank.AddSlider("Grid size Y", &ms_nGridSizeY, 1, 200, 1);
bank.PopGroup(); // "Flow field debug"
bank.PopGroup(); // "Rivers"
}
void CBuoyancy::DisplayBuoyancyInfo()
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(0);
if(!pEnt)
return;
if(!pEnt->GetIsPhysical())
return;
const CBuoyancy& rBuoyancy = static_cast<CPhysical*>(pEnt)->m_Buoyancy;
if(!rBuoyancy.GetBuoyancyInfoUpdatedThisFrame())
return;
float fSubmergedLevel = rBuoyancy.GetSubmergedLevel();
char zText[255];
sprintf(zText, "Submerged level: %5.3f", fSubmergedLevel);
Color32 colour = Color_yellow;
grcDebugDraw::Text(VEC3V_TO_VECTOR3(pEnt->GetTransform().GetPosition())+Vector3(0.0f,0.0f,0.0f), colour, zText);
}
#endif //__BANK
CBuoyancyInfo* CBuoyancy::GetBuoyancyInfo( const CPhysical* pParent ) const
{
Assert(pParent);
if(m_pBuoyancyInfoOverride)
{
return m_pBuoyancyInfoOverride;
}
CBaseModelInfo* pModelInfo = pParent->GetBaseModelInfo();
if(pModelInfo)
{
return pModelInfo->GetBuoyancyInfo();
}
return NULL;
}
CBuoyancyInfo::CBuoyancyInfo()
{
m_WaterSamples = NULL;
m_nNumWaterSamples = 0;
m_fBuoyancyConstant = 1.1f;
m_fLiftMult = 0.0f;
m_fKeelMult = 0.0f;
m_fDragMultXY = 0.0f;
m_fDragMultZUp = 0.0f;
m_fDragMultZDown = 0.0f;
m_nNumBoatWaterSampleRows = 0;
m_nBoatWaterSampleRowIndices = NULL;
}
void CBuoyancyInfo::CopyFrom(const CBuoyancyInfo& otherInfo)
{
// If we already have water samples then clean them up
if(m_WaterSamples != NULL)
{
delete[] m_WaterSamples;
m_WaterSamples = NULL;
}
m_nNumWaterSamples = otherInfo.m_nNumWaterSamples;
m_fBuoyancyConstant = otherInfo.m_fBuoyancyConstant;
m_fLiftMult = otherInfo.m_fLiftMult;
m_fKeelMult = otherInfo.m_fKeelMult;
m_fDragMultXY = otherInfo.m_fDragMultXY;
m_fDragMultZUp = otherInfo.m_fDragMultZUp;
m_fDragMultZDown = otherInfo.m_fDragMultZDown;
if(otherInfo.m_WaterSamples != NULL && m_nNumWaterSamples > 0)
{
m_WaterSamples = rage_new CWaterSample[m_nNumWaterSamples];
for(int iSampleIndex = 0; iSampleIndex < m_nNumWaterSamples; iSampleIndex++)
{
m_WaterSamples[iSampleIndex].Set(otherInfo.m_WaterSamples[iSampleIndex]);
}
}
}
CBuoyancyInfo* CBuoyancyInfo::Clone()
{
CBuoyancyInfo* pClone = rage_new CBuoyancyInfo;
pClone->CopyFrom(*this);
return pClone;
}
#if __DEV
static int s_sampleCounter = 0;
#endif
CBuoyancyInfo::~CBuoyancyInfo()
{
if(m_WaterSamples)
{
Assert(m_nNumWaterSamples > 0);
delete[] m_WaterSamples;
}
}
void CWaterSample::Set(CWaterSample &sample)
{
m_vSampleOffset = sample.m_vSampleOffset;
m_fSize = sample.m_fSize;
m_fBuoyancyMult = sample.m_fBuoyancyMult;
m_nComponent = sample.m_nComponent;
m_bKeel = sample.m_bKeel;
}
CWaterSample::CWaterSample()
{
m_vSampleOffset = VEC3_ZERO;
m_fSize = 0.0f;
m_fBuoyancyMult = 1.0f;
m_nComponent = 0;
m_bKeel = FALSE;
m_bPontoon = FALSE;
#if __DEV
s_sampleCounter++;
PF_SET(NumWaterSamples, s_sampleCounter);
#endif
}
CWaterSample::~CWaterSample()
{
#if __DEV
s_sampleCounter--;
PF_SET(NumWaterSamples, s_sampleCounter);
#endif
}