Files
GTASource/game/physics/physics.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

7395 lines
255 KiB
C++

//
// filename: physics.cpp
// description: Class controlling physics
//
// --- Include Files ------------------------------------------------------------
// C headers
// Rage headers
#include "grcore/light.h"
#include "breakableglass/glassmanager.h"
#include "breakableglass/piecegeometry.h"
#include "breakableglass/breakable.h"
#include "breakableglass/bgdrawable.h"
#include "crskeleton/skeleton.h"
#include "diag/art_channel.h"
#include "fragment/manager.h"
#include "fragment/tune.h"
#include "fragmentnm/instance.h"
#include "fragmentnm/manager.h"
#include "fragmentnm/messageparams.h"
#include "fwdebug/vectormap.h"
#include "fwgeovis/geovis.h"
#include "fwpheffects/taskmanager.h"
#include "fwpheffects/clothmanager.h"
#include "fwpheffects/ropemanager.h"
#include "fwscene/world/WorldLimits.h"
#include "fwscene/world/WorldRepMulti.h"
#include "fwdebug/picker.h"
#include "input/keyboard.h"
#include "input/keys.h"
#if __BANK && !__RESOURCECOMPILER
#include "audio/pedfootstepaudio.h"
#include "physics/Rope.h"
#endif
#include "pharticulated/articulatedcollider.h"
#include "pheffects/mouseinput.h"
#include "phglass/glassinstance.h"
#include "phsolver/contactmgr.h"
#include "phbound/Support.h"
#include "phcore/pool.h"
#include "phcore/surface.h"
#include "physics/broadphase.h"
#include "physics/overlappingpairarray.h"
#include "physics/btBroadphaseProxy.h"
#include "physics/btAxisSweep3.h"
#include "physics/levelnew.h"
#include "physics/sleep.h"
#include "physics/debugevents.h"
#include "profile/timebars.h"
#include "Vehicles/Automobile.h"
#include "Vehicles/Bike.h"
#include "vehicles/propeller.h"
#include "scene/playerswitch/PlayerSwitchInterface.h"
#include "streaming/streaming.h"
#include "streaming/streamingvisualize.h"
#include "system/alloca.h"
#include "system/cache.h"
#include "system/criticalsection.h"
#include "system/dependency.h"
#include "grprofile/pix.h"
#if __BANK
#include "task/Physics/TaskNMRiverRapids.h"
#endif // __BANK
#include "weapons/Weapon.h"
#include "vfx/vfxutil.h"
#include "control/record.h"
#if __BANK
#include "bank/bkmgr.h"
#include "bank/bank.h"
#include "bank/combo.h"
#include "bank/slider.h"
#include "phbound/boundtaperedcapsule.h"
#endif
// Framework Header
#include "fwmaths/angle.h"
#include "fwnet/netblender.h"
#include "fwscene/search/Search.h"
#include "fwscene/stores/fragmentstore.h"
#include "fwscene/stores/staticboundsstore.h"
// Game headers
#include "camera/CamInterface.h"
#include "camera/viewports/ViewportManager.h"
#include "Cloth/ClothMgr.h"
#include "Cloth/ClothArchetype.h"
#include "control/gamelogic.h"
#include "core/game.h"
#include "camera/debug/debugdirector.h"
#include "cutscene/CutSceneManagerNew.h"
#include "debug/DebugScene.h"
#include "debug/DebugGlobals.h"
#include "event/Events.h"
#include "game/ModelIndices.h"
#include "modelinfo/modelinfo.h"
#include "pathserver/exportcollision.h"
#include "pathserver/NavGenParam.h"
#include "pathserver/pathserver.h"
#include "peds/Horse/horseComponent.h"
#include "peds/PedFactory.h"
#include "Peds/PedGeometryAnalyser.h"
#include "peds/Ped.h"
#include "peds/PedCloth.h"
#include "peds/PedIntelligence.h"
#include "Peds/pedpopulation.h"
#include "performance/clearinghouse.h"
#include "physics/CollisionRecords.h"
#include "physics/breakable.h"
#include "physics/gtaInst.h"
#include "physics/gtaMaterialManager.h"
#include "physics/physics.h"
#include "physics/WorldProbe/worldprobe.h"
#include "physics/Tunable.h"
#include "Physics/Rope.h"
#include "ModelInfo/BaseModelInfo.h"
#include "renderer/RenderThread.h"
#include "renderer/River.h"
#include "scene/AnimatedBuilding.h"
#include "scene/Building.h"
#include "scene/ContinuityMgr.h"
#include "scene/FocusEntity.h"
#include "scene/LoadScene.h"
#include "scene/scene.h"
#include "scene/portals/Portal.h"
#include "scene/RegdRefTypes.h"
#include "scene/world/VisibilityMasks.h"
#include "scene/world/GameWorld.h"
#include "script/script.h"
#include "streaming/BoxStreamer.h"
#include "streaming/CacheLoader.h"
#include "streaming/streamingrequestlist.h"
#include "system/controlMgr.h"
#include "Task/Physics/TaskNMRelax.h"
#include "Task/Physics/TaskNMBalance.h"
#include "tools/SectorTools.h"
#if __BANK
#include "vehicleAi/task/TaskVehicleTempAction.h"
#include "vehicleAi/VehicleIntelligence.h"
#endif // __BANK
#include "vehicles/heli.h"
#include "vehicles/vehicle.h"
#include "vehicles/VehicleFactory.h"
#include "weapons/Bullet.h"
#include "weapons/explosion.h"
#include "weapons/projectiles/Projectile.h"
#include "weapons/projectiles/ProjectileManager.h"
#include "vehicles/wheel.h"
#include "Vfx/VisualEffects.h"
#include "Vfx/Decals/DecalManager.h"
#include "Vfx/Misc/Puddles.h"
#include "Vfx/Systems/VfxMaterial.h"
#include "Game/Weather.h"
#if __DEV
#include "peds/PedDebugVisualiser.h"
#endif
// network headers
#include "network/NetworkInterface.h"
#include "network/general/NetworkUtil.h"
#if GPU_DAMAGE_WRITE_ENABLED
rage::sysIpcMutex CPhysics::sm_VehicleMutex = NULL;
rage::atArray<CVehicle*> CPhysics::ms_pDamagedVehicles;
#endif
#define DEFAULT_NUM_PHYS_TIMESLICES 2
#define DEFAULT_NUM_PHYS_TIMESLICES_IN_STUNT_MODE 6
PHYSICS_OPTIMISATIONS()
PARAM(physicsrigsatload, "Load human physics rigs directly from XML files. Only used for local testing new rigs.");
PARAM(mapMoverOnlyInMultiplayer, "Use to only use MOVER map collision when in multiplayer games; MOVER will be set to collide with weapon shapetests.");
PARAM(pfDrawDefaultSyncPoint, "Set the pfDraw render synchronisation point (see PHYSICS_RENDER_UPDATE_SYNC_POINT_STRINGS for options). Defaults to NONE");
PARAM(setMaterialDensityScale, "Scale the density of all materials defined in materials.dat by this value.");
PARAM(maxphysicalglassshards, "The maximum number of physical glass shards that can exist at a given time.");
namespace rage
{
XPARAM(nopopups);
#if __PFDRAW
EXT_PFD_DECLARE_GROUP(Physics);
EXT_PFD_DECLARE_GROUP(Bounds);
EXT_PFD_DECLARE_ITEM(Solid);
EXT_PFD_DECLARE_ITEM(Wireframe);
EXT_PFD_DECLARE_ITEM(EdgeAngles);
EXT_PFD_DECLARE_ITEM(Active);
EXT_PFD_DECLARE_ITEM(Inactive);
EXT_PFD_DECLARE_ITEM(Fixed);
EXT_PFD_DECLARE_GROUP(PolygonDensity);
EXT_PFD_DECLARE_ITEM(PolygonAngleDensity);
EXT_PFD_DECLARE_ITEM_SLIDER_INT_FULL(NumRecursions);
EXT_PFD_DECLARE_ITEM_SLIDER(ColorMidPoint);
EXT_PFD_DECLARE_ITEM_SLIDER_FULL(ColorExpBias);
EXT_PFD_DECLARE_ITEM(MaxNeighborAngleEnabled);
EXT_PFD_DECLARE_ITEM_SLIDER_FULL(MaxNeighborAngle);
EXT_PFD_DECLARE_GROUP(PrimitiveDensity);
EXT_PFD_DECLARE_ITEM(IncludePolygons);
EXT_PFD_DECLARE_GROUP(TypeFlagFilter);
EXT_PFD_DECLARE_ITEM(TypeFlag1);
EXT_PFD_DECLARE_ITEM(TypeFlag2);
EXT_PFD_DECLARE_ITEM(TypeFlag3);
EXT_PFD_DECLARE_ITEM(TypeFlag4);
EXT_PFD_DECLARE_ITEM(TypeFlag5);
EXT_PFD_DECLARE_ITEM(TypeFlag30);
EXT_PFD_DECLARE_ITEM(ComponentIndices);
EXT_PFD_DECLARE_ITEM_SLIDER(BoundDrawDistance);
EXT_PFD_DECLARE_ITEM(DrawBoundMaterials);
EXT_PFD_DECLARE_ITEM(SolidBoundLighting);
EXT_PFD_DECLARE_ITEM(SolidBoundRandom);
#endif
}
namespace gtaWorldStats
{
EXT_PF_TIMER(Phys_Total);
EXT_PF_TIMER(Phys_Requests);
EXT_PF_TIMER(Phys_Pre);
EXT_PF_TIMER(Phys_Pre_Sim);
EXT_PF_TIMER(Phys_Sim);
EXT_PF_TIMER(Phys_Post_Sim);
EXT_PF_TIMER(Phys_Iterate);
EXT_PF_TIMER(Phys_Post);
EXT_PF_TIMER(Phys_RageSimUpdate);
EXT_PF_TIMER(Phys_RageFragUpdate);
EXT_PF_TIMER(Phys_RageClothUpdate);
EXT_PF_TIMER(Phys_RageRopeUpdate);
EXT_PF_TIMER(Phys_RageVerletWaitForTasks);
EXT_PF_TIMER(Phys_RagInstBehvrs);
EXT_PF_TIMER(Proc_VehRecording);
};
using namespace gtaWorldStats;
namespace gtaPhysicsObjects
{
PF_PAGE(GTA_Phys_Objects, "Gta Physics Objects");
PF_GROUP(Vehicles);
PF_LINK(GTA_Phys_Objects, Vehicles);
PF_VALUE_INT(RealVehicles, Vehicles);
PF_VALUE_INT(DummyVehicles, Vehicles);
PF_VALUE_INT(Helicopters, Vehicles);
PF_GROUP(Peds);
PF_LINK(GTA_Phys_Objects, Peds);
PF_VALUE_INT(Peds, Peds);
PF_VALUE_INT(NMRagdolls, Peds);
PF_VALUE_INT(HighLodRagdolls, Peds);
PF_VALUE_INT(MedLodRagdolls, Peds);
PF_VALUE_INT(LowLodRagdolls, Peds);
PF_GROUP(Misc);
PF_LINK(GTA_Phys_Objects, Misc);
PF_VALUE_INT(Explosions, Misc);
PF_VALUE_INT(ArticulatedBodies, Misc);
PF_VALUE_INT(RigidBodies, Misc);
}
using namespace gtaPhysicsObjects;
#if __DEV
// This command line parameter has external linkage here because it is defined
// in the RAGE Natural Motion project (see fragmentnm/manager.cpp).
extern ::rage::sysParam PARAM_nmfolder;
#endif
enum
{
PHYSICS_RENDER_UPDATE_SYNC_NONE = 0,
PHYSICS_RENDER_UPDATE_SYNC_BEFORE_PRE_PHYSICS_UPDATE,
PHYSICS_RENDER_UPDATE_SYNC_BEFORE_PRESIM,
PHYSICS_RENDER_UPDATE_SYNC_AFTER_FIRST_UPDATE,
PHYSICS_RENDER_UPDATE_SYNC_AFTER_ALL_UPDATES,
PHYSICS_RENDER_UPDATE_SYNC_AFTER_POST_UPDATE,
PHYSICS_RENDER_UPDATE_SYNC_POINT_COUNT
};
#if __PFDRAW
const char* PHYSICS_RENDER_UPDATE_SYNC_POINT_STRINGS[] =
{
"NONE",
"BEFORE_PRE_PHYSICS_UPDATE",
"BEFORE_PRESIM",
"AFTER_FIRST_UPDATE",
"AFTER_ALL_UPDATES",
"AFTER_POST_UPDATE"
};
static int snPhysicsSynchronizeRenderAndUpdateThreads = PHYSICS_RENDER_UPDATE_SYNC_NONE;
#endif // __PFDRAW
extern atFixedArray<RegdPed, 40> g_SettledPeds;
// --- Defines ------------------------------------------------------------------
// --- Constants ----------------------------------------------------------------
// --- Structure/Class Definitions ----------------------------------------------
struct CGTAStaticBoundsStoreInterface : public fwStaticBoundsStoreInterface
{
virtual phInst* AddBoundToPhysicsLevel(phBound* pBound, s32 index, bool& bContainsMover, u32 nTypeFlags, u32 nIncludeFlags,
bool bSlotIsDummy, bool bSlotIsDependency)
{
if (nTypeFlags)
{
// by default, assume mover collision
bContainsMover = (nTypeFlags != ArchetypeFlags::GTA_MAP_TYPE_WEAPON);
}
// Allow ragdoll and ped types to collide with deep surface bounds. This is mainly to prevent the ped's head (and therfore the camera in first-person mode)
// from going through the ground while ragdolled and when getting up
if (nTypeFlags & ArchetypeFlags::GTA_DEEP_SURFACE_TYPE)
{
nIncludeFlags |= ArchetypeFlags::GTA_RAGDOLL_TYPE | ArchetypeFlags::GTA_PED_TYPE;
}
// Create physics archetype.
phArchetype* pArch = rage_new phArchetype;
// Make sure any additional type or include flags are added to the main archetype flags too.
pArch->SetTypeFlags(nTypeFlags);
pArch->SetIncludeFlags(nIncludeFlags);
if (nTypeFlags&ArchetypeFlags::GTA_RIVER_TYPE)
{
g_StaticBoundsStore.GetSlot(strLocalIndex(index))->m_bHasRiverBounds = true;
}
bool bStaticInteriorCollider = false;
Matrix34 mat;
if (bSlotIsDependency)
{
pArch->SetFilename("Archetype");
mat.Identity();
}
else if (bSlotIsDummy)
{
// Give a name so we can tell static things apart from other unnamed things in the level
pArch->SetFilename("Instanced");
//mat.Identity();
//mat.d.z += 1.0f;
InteriorProxyIndex proxyID;
strLocalIndex depSlot;
g_StaticBoundsStore.GetDummyBoundData(strLocalIndex(index), proxyID, depSlot);
CInteriorProxy* pProxy = CInteriorProxy::GetPool()->GetSlot(proxyID);
Assert(pProxy);
if (pProxy)
{
Mat34V tempMatrix = pProxy->GetMatrix();
mat = RCC_MATRIX34(tempMatrix);
bStaticInteriorCollider = true;
}
}
else
{
// Give a name so we can tell static things apart from other unnamed things in the level
pArch->SetFilename("Static");
mat.Identity();
}
// Create physics instance and add to physics world.
phInst* pInst = rage_new phInstGta(PH_INST_MAPCOL);
pArch->SetBound(pBound);
pInst->Init(*pArch, mat);
if (!bSlotIsDependency && nTypeFlags)
{
CPhysics::GetSimulator()->AddFixedObject(pInst);
// Create a GTA entity to wrap it all up (for regref'ing mainly).
CBuilding *pBuilding = rage_new CBuilding( ENTITY_OWNEDBY_STATICBOUNDS );
// Use IPL index as the index of the static bound.
pBuilding->SetIplIndex(index);
pBuilding->SetPhysicsInst(pInst, false);
pBuilding->SetIsInteriorStaticCollider(bStaticInteriorCollider);
}
return pInst;
}
virtual void RemoveInstFromPhysicsWorld(phInst* pInst)
{
// delete instance from physics world
if (pInst->GetLevelIndex() != phInst::INVALID_INDEX)
{
CPhysics::GetSimulator()->DeleteObject(pInst->GetLevelIndex());
}
if(pInst->GetUserData())
{
((CEntity *)pInst->GetUserData())->ClearCurrentPhysicsInst();
delete ((CBuilding *)pInst->GetUserData());
}
// deleting inst also deletes archetype
pInst->SetArchetype(NULL, true);
delete pInst;
}
};
bool CPhysics::AdditionalSetupModelInfoPhysics(phArchetype& archetype)
{
if (!archetype.GetPropertyFlag(ArchetypeProperties::IS_ALREADY_PROCESSED))
{
phBound* pBound = archetype.GetBound();
Assert(pBound);
if(pBound->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(pBound);
// Allow collision to be tagged as MOVER, CAMERA, WEAPON map type on individual bounds of a phCompositeBound.
if(pBoundComp->GetTypeAndIncludeFlags())
{
u32 nAdditionalArchTypeFlags = 0;
u32 nAdditionalArchIncludeFlags = 0;
for(int i = 0; i < pBoundComp->GetNumBounds(); ++i)
{
u32 nBoundTypeFlags = pBoundComp->GetTypeFlags(i);
// I don't think we need this assert
//artAssertf((nBoundTypeFlags&ArchetypeFlags::GTA_RIVER_TYPE)==0, "Filename = (%s) Trying to set river bound collision type on a CObject.", archetype.GetFilename());
nAdditionalArchTypeFlags |= nBoundTypeFlags;
nAdditionalArchIncludeFlags |= pBoundComp->GetIncludeFlags(i);
}
// Make sure any additional type or include flags are added to the main archetype flags too.
archetype.SetTypeFlags(archetype.GetTypeFlags()|nAdditionalArchTypeFlags);
archetype.SetIncludeFlags(archetype.GetIncludeFlags()|nAdditionalArchIncludeFlags);
}
}
else
{
// Nothing else needs to be done if the bound isn't a composite.
}
archetype.SetPropertyFlag(ArchetypeProperties::IS_ALREADY_PROCESSED);
}
return false;
}
// --- Globals ------------------------------------------------------------------
RAGE_DEFINE_CHANNEL(physics)
static CGTAStaticBoundsStoreInterface g_StaticBoundsStoreInterface;
static CGTABoxStreamerInterfaceNew g_BoxStreamerInterfaceNew;
// --- Static Globals -----------------------------------------------------------
bool CPhysics::ms_bIgnoreForcedSingleStepThisFrame = false;
#if __BANK
static bool gbPhysicsInstCounters = false;
static bool gbPrintPhysicsActive = false;
static bool gbPrintPhysicsInActive = false;
static bool gbPrintPhysicsFixed = false;
static bool gbDisplayVectorMapPhysicsInsts = false;
static bool g_NoPopups = false;
static bool g_PauseUnpauseGame = false;
bool CPhysics::ms_bDisplaySelectedPhysicalCollisionRecords = false;
bool CPhysics::ms_bDisplayAllCollisionRecords = false;
bool CPhysics::ms_bDisplayNumberOfBrokenPartsFocusFrag = false;
bool CPhysics::ms_bDisplayPedHealth = false;
bool CPhysics::ms_bDisplayPedFallHeight = false;
bool CPhysics::ms_bDoVehicleDamage = true;
bool CPhysics::ms_bDoCollisionEffects = true;
bool CPhysics::ms_bDoWeaponImpactEffects = true;
bool CPhysics::ms_bPrintBreakingImpulsesForPeds = false;
bool CPhysics::ms_bPrintBreakingImpulsesForVehicles = false;
bool CPhysics::ms_bPrintBreakingImpulsesApplied = false;
int CPhysics::ms_iSelectedGravLevel = CPhysics::GRAV_EARTH;
// A list of objects which can be excluded from shape tests.
static const CEntity* g_pExcludeEntityList[PROCESS_LOS_MAX_EXCEPTIONS];
static int g_nNumExcludedEntities = 0;
bool CPhysics::ms_OverrideCrackSelection = false;
s32 CPhysics::ms_OverrideSelectedCrack = 0;
bool CPhysics::ms_MouseBreakEnable = false;
float CPhysics::ms_MouseBreakDamage = 300.0f;
float CPhysics::ms_MouseBreakImpulse = 1.0f;
float CPhysics::ms_MouseBreakRadius = 0.1f;
int CPhysics::ms_MouseBreakCrackType = 0;
bool CPhysics::ms_MouseBreakHiddenHit = false;
Vector3 CPhysics::ms_MouseBreakPosition;
Vector3 CPhysics::ms_MouseBreakVelocity;
#endif
#if __DEV
CDebugDrawStore CPhysics::ms_debugDrawStore(256);
bool CPhysics::ms_bDoPedProbesWithImpacts = true;
#endif
// --- Static class members -----------------------------------------------------
phLevelNew* CPhysics::ms_PhysLevel = NULL;
phSimulator* CPhysics::ms_PhysSim = NULL;
fragManager* CPhysics::ms_FragManager = NULL;
phConstraintMgr* CPhysics::ms_ConstraintManager = NULL;
verletTaskManager* CPhysics::ms_TasksManager = NULL;
clothInstanceTaskManager* CPhysics::ms_InstanceTasksManager = NULL;
clothManager* CPhysics::ms_ClothManager = NULL;
ropeManager* CPhysics::ms_RopeManager = NULL;
#if __BANK && !__RESOURCECOMPILER
ropeBankManager* CPhysics::ms_RopeBankManager = NULL;
#endif
fragInstGta* CPhysics::ms_pSuppliedFragCacheInsts = NULL;
grmShader* CPhysics::ms_GlassShader = NULL;
s32 CPhysics::ms_GlassTxd = -1;
s32 CPhysics::ms_SelectedCrack = 0;
grmShader* CPhysics::ms_RopeShader = NULL;
u32 CPhysics::ms_TimeSliceId = 0;
int CPhysics::ms_CurrentTimeSlice = -1;
int CPhysics::ms_NumTimeSlices = DEFAULT_NUM_PHYS_TIMESLICES;
bool CPhysics::ms_OverriderNumTimeSlices = false;
bool CPhysics::ms_bInStuntMode = false;
bool CPhysics::ms_bInArenaMode = false;
bool CPhysics::ms_OnlyPushOnLastUpdate = true;
bool CPhysics::ms_DisableOnlyPushOnLastUpdateForOneFrame = false;
bool CPhysics::ms_bSimUpdateActive = false;
float CPhysics::ms_fRagdollActivationAnimVelClamp = 3.0f;
float CPhysics::ms_fRagdollActivationAnimAngVelClamp = 6.0f;
bool CPhysics::ms_bClearManifoldsEveryFrame = false;
int CPhysics::ms_ClearManifoldsFramesLeft = 0;
float CPhysics::ms_fGravitationalAccleration = -GRAVITY;
#if PHYSICS_LOD_GROUP_SHARING
CPhysics::ManagedPhysicsLODGroup CPhysics::m_ManagedPhysicsLODGroup[CPhysics::kNumManagedPhysicsLODGroups];
#endif
#if __DEV
static bool sbSwitchPedToNM = false;
#endif
#if USE_FRAG_TUNE && __BANK
#define MAX_FRAG_TYPES (100)
// frag combo box variables
static u32 fragSorts[MAX_FRAG_TYPES];
static atArray<const char*> fragNames;
static s32 numFragNames = 0;
static bkCombo* pFragCombo = NULL;
static s32 currFragNameSelection = 0;
// static s32 fragModelIndex = 0;
// end of frag name combo box vars
#endif
PARAM(phbroadphase, "Specify the type of broadphase to use (level, axisSweep1, or axisSweep3.");
PARAM(physicsupdates, "Number of times physics is updated per frame");
PARAM(phscratchpad, "Size of physics scratchpad (bytes)");
namespace rage { extern bool g_CreateMatrixSetsInFragCache; } // Set to false to turn off matrix sets in the fragment cache
// --- Code --------------------------------------------------------------------
namespace
{
#if RAGE_GLASS_USE_PRIVATE_HEAP
int sm_bufferMem = 1;
#endif
int sm_maxPanes = 1;
}
#define MAX_GLASS_PANES 10
void GlassDefaultMemory()
{
#if RAGE_GLASS_USE_PRIVATE_HEAP
sm_bufferMem = 1200;
#endif
sm_maxPanes = MAX_GLASS_PANES; // Amount of panes that can be broken at the same time
}
int GetGlassHeapMem()
{
#if BREAKABLE_GLASS_USE_BVH
static const int iMemPerPane = 31; // We need around 31k per pane
#else
static const int iMemPerPane = 16; // We need around 16k per pane
#endif // BREAKABLE_GLASS_USE_BVH
return iMemPerPane * sm_maxPanes; // Derive the total memory for the amount of panes and the size of each pane
}
#if __BANK
#if RAGE_GLASS_USE_PRIVATE_HEAP
void GlassReallocMemory()
{
bgGlassManager::Free();
bgGlassManager::Malloc(sm_maxPanes, sm_bufferMem, GetGlassHeapMem());
}
#endif // RAGE_GLASS_USE_PRIVATE_HEAP
void GlassResetAll()
{
CObject* pObj;
s32 i = (s32) CObject::GetPool()->GetSize();
while(i--)
{
pObj = CObject::GetPool()->GetSlot(i);
if( pObj && pObj->m_nDEflags.bIsBreakableGlass )
CObjectPopulation::ConvertToDummyObject(pObj);
}
}
#endif
int CPhysics::GlassSelectCrack()
{
#if __BANK
if( ms_OverrideCrackSelection )
ms_SelectedCrack = ms_OverrideSelectedCrack;
#endif // __BANK
int maxType = bgGlassManager::GetCrackTypeCount() - 1;
int result = ms_SelectedCrack > maxType ? maxType : ms_SelectedCrack;
ms_SelectedCrack = 0; // Reset to default crack type.
return result;
}
#if __OPTIMIZED
namespace rage { XPARAM(nomtphys); }
#endif
#if __BANK
namespace rage
{
extern bool g_FixRagdollStuckInGeometry;
extern bool g_FixRagdollStuckInGeometryElementMatch;
extern float g_FixRagdollStuckInGeometryDepth;
extern bool g_PreventRagdollPushCollisions;
}
#endif // __BANK
//
// name: CPhysics::Init
// description:
void CPhysics::InitCore()
{
USE_MEMBUCKET(MEMBUCKET_PHYSICS);
Assert(ms_PhysLevel==NULL);
Assert(ms_PhysSim==NULL);
Assert(ms_FragManager==NULL);
Assert(ms_ConstraintManager==NULL);
Assert(ms_ClothManager==NULL);
Assert(ms_RopeManager==NULL);
#if __BANK && !__RESOURCECOMPILER
Assert(ms_RopeBankManager==NULL);
#endif
Assert(ms_TasksManager==NULL);
Assert(ms_InstanceTasksManager==NULL);
#if !__FINAL
ArchetypeFlags::SetupCustomBoundFlagNames();
#endif
// GTA_RAGE_PHYSICS
#if __PFDRAW
// Don't really want to turn these (debug collision bounds rendering etc..) by default
PFD_GROUP_ENABLE(Physics, false);
PFD_GROUP_ENABLE(Bounds, true);
PFD_ITEM_ENABLE(Solid, false);
PFD_ITEM_ENABLE(EdgeAngles, false);
PFD_ITEM_ENABLE(Active, true);
PFD_ITEM_ENABLE(Inactive, true);
PFD_ITEM_ENABLE(Fixed, false);
PFD_ITEM_SLIDER_SET_VALUE(BoundDrawDistance, 30.0f);
PFD_ITEM_ENABLE(DrawBoundMaterials, false);
PFD_ITEM_ENABLE(SolidBoundLighting, false);
PFD_ITEM_ENABLE(SolidBoundRandom, true);
#endif
const s32 maxPeds = CPed::GetPool()->GetSize();
const s32 maxVehicles = (s32) CVehicle::GetPool()->GetSize();
s32 maxActiveObjects = MAX_ACTIVE_COBJECTS;
s32 maxConstraints = 562;
#if SECTOR_TOOLS_EXPORT
// This causes us to run out of Game Virtual - used to work for GTAIV though. Not essential right now (Object posoiton checker ) so disabled.
// if ( CSectorTools::GetEnabled() )
// maxActiveObjects = 256;
#endif
static s32 iMaxNumOctreeNodes = MAX_OCTREE_NODES;
static s32 iMaxActiveObjects = maxPeds*2 + maxVehicles*2 + maxActiveObjects + MAX_FRAGMENT_CACHE_OBJECTS;
#if NAVMESH_EXPORT
const bool bExtraPoolSize = CNavMeshDataExporter::WillExportCollision();
const bool bDelayedSAP = !CNavMeshDataExporter::WillExportCollision();
if(CNavMeshDataExporter::WillExportCollision()) maxConstraints = 1024;
#else
const bool bExtraPoolSize = false;
const bool bDelayedSAP = true;
#endif
// These values used to be computed by the FW_INSTANTIATE_... macros,
// but since MAXNOOFPEDS and VEHICLE_POOL_SIZE aren't known then, the
// computations had to move.
const s32 maxNumPhInstGta = 2234;
const s32 maxNumFragInstGta = 1600;
const s32 maxNumNMInstGta = (MAXNOOFPEDS);
u32 maxGlassShards = 25;
PARAM_maxphysicalglassshards.Get(maxGlassShards);
#if __BANK
if(PARAM_physicsupdates.Get())
PARAM_physicsupdates.Get(CPhysics::ms_NumTimeSlices);
#endif // __BANK
#if __PFDRAW
const char* pSyncPointName = NULL;
if(PARAM_pfDrawDefaultSyncPoint.Get())
{
PARAM_pfDrawDefaultSyncPoint.Get(pSyncPointName);
for(int i = 0; i < PHYSICS_RENDER_UPDATE_SYNC_POINT_COUNT; ++i)
{
if(stricmp(pSyncPointName, PHYSICS_RENDER_UPDATE_SYNC_POINT_STRINGS[i]) == 0)
{
snPhysicsSynchronizeRenderAndUpdateThreads = i;
break;
}
}
}
#endif // __PFDRAW
if(bExtraPoolSize)
{
phInstGta::InitPool( 35000, MEMBUCKET_PHYSICS );
fragInstGta::InitPool( maxNumFragInstGta * 8, MEMBUCKET_PHYSICS );
fragInstNMGta::InitPool( maxNumNMInstGta, MEMBUCKET_PHYSICS );
iMaxNumOctreeNodes *= 3;
iMaxActiveObjects *= 3;
}
else
{
phInstGta::InitPool( maxNumPhInstGta, MEMBUCKET_PHYSICS );
fragInstGta::InitPool( maxNumFragInstGta, MEMBUCKET_PHYSICS );
fragInstNMGta::InitPool( maxNumNMInstGta, MEMBUCKET_PHYSICS );
}
#if RSG_PC
int nNumCacheEntries = maxPeds + maxVehicles + 128;
#else
int nNumCacheEntries = maxPeds + maxVehicles + 64;
#endif
static const s32 iMaxManagedColliders = 100 + maxActiveObjects + MAX_FRAGMENT_CACHE_OBJECTS;
static const s32 iMaxManifolds = iMaxActiveObjects * MAX_MANIFOLDS_PER_ACTIVE_OBJECT;
static const s32 iMaxCompositeManifolds = iMaxManifolds / COMPOSITE_MANIFOLD_RATIO;
static const s32 iMaxNonCompositeManifolds = iMaxManifolds - iMaxCompositeManifolds;
static const s32 iMaxContacts = iMaxNonCompositeManifolds * MAX_CONTACTS_PER_MANIFOLD;
// Compute a maximum phInst-in-level count based on the maximum number of phInsts we can allocate.
static s32 iMaxObjects = phInstGta::GetPool()->GetSize() +
fragInstGta::GetPool()->GetSize() +
fragInstNMGta::GetPool()->GetSize() +
nNumCacheEntries +
maxGlassShards +
MAX_GLASS_PANES;
// Not sure why this isn't 65k but we won't need 32k+ for while
iMaxObjects = Min(iMaxObjects, 32766);
//////////////////////
// create physics level
//////////////////////
phLevelNew* pLevelNew = rage_new phLevelNew;
// immediately set this level to be the active one
pLevelNew->SetActiveInstance();
pLevelNew->SetExtents(Vec3V(WORLDLIMITS_XMIN, WORLDLIMITS_YMIN, WORLDLIMITS_ZMIN), Vec3V(WORLDLIMITS_XMAX, WORLDLIMITS_YMAX, WORLDLIMITS_ZMAX));
// pLevelNew->SetExtents(Vector3(-10000.0f, -10000.0f, -3000.0f), Vector3(10000.0f, 10000.0f, 3000.0f));
pLevelNew->SetMaxObjects(iMaxObjects);
pLevelNew->SetMaxActive(iMaxActiveObjects);
pLevelNew->SetMaxCollisionPairs(4000);
pLevelNew->SetNumOctreeNodes(iMaxNumOctreeNodes);
pLevelNew->SetMaxInstLastMatrices(phLevelNew::MAX_INST_LAST_MATRICES);
const char* bpChoice = "axisSweep3";
PARAM_phbroadphase.Get(bpChoice);
if (stricmp(bpChoice, "level") == 0)
{
pLevelNew->SetBroadPhaseType(phLevelNew::LEVEL);
}
else if (stricmp(bpChoice, "axisSweep1") == 0 || stricmp(bpChoice, "axis1") == 0)
{
pLevelNew->SetBroadPhaseType(phLevelNew::AXISSWEEP1);
}
else if (stricmp(bpChoice, "axisSweep3") == 0 || stricmp(bpChoice, "axis3") == 0)
{
pLevelNew->SetBroadPhaseType(phLevelNew::AXISSWEEP3);
}
#if LEVELNEW_ENABLE_DEFERRED_OCTREE_UPDATE
pLevelNew->SetMaxDeferredInstanceCount(iMaxActiveObjects * 4);
#endif // LEVELNEW_ENABLE_DEFERRED_OCTREE_UPDATE
pLevelNew->Init();
ms_PhysLevel = pLevelNew;
// Setup the BP pair keeper function callback
if(Verifyf(dynamic_cast<btAxisSweep3*>(pLevelNew->GetBroadPhase()), "The game relies a broadphase with KeepPairFunc implemented."))
{
static_cast<btAxisSweep3*>(pLevelNew->GetBroadPhase())->SetKeepPairFunc(MakeFunctorRet(CPhysics::KeepBroadphasePair));
}
//////////////////////
// create physics simulator
//////////////////////
phSimulator::InitClass();
ms_PhysSim = rage_new phSimulator;
// immediately set this simulator to be the active one
ms_PhysSim->SetActiveInstance();
#if __64BIT
// In decreasing this from 2 MB to 1 MB on other platforms, I didn't test 64 bit and it's likely to need a bit more space anyway.
int scratchPadSize = 2*1024*1024;
#else
int scratchPadSize = 1*1024*1024;
#endif
PARAM_phscratchpad.Get(scratchPadSize);
phSimulator::InitParams params;
params.maxManagedColliders = iMaxManagedColliders;
params.scratchpadSize = scratchPadSize;
params.maxManifolds = iMaxManifolds;
params.highPriorityManifolds = static_cast<int>((maxPeds + maxVehicles) * 0.5f * MAX_MANIFOLDS_PER_ACTIVE_OBJECT);
params.maxCompositeManifolds = iMaxCompositeManifolds;
params.maxContacts = iMaxContacts;
params.maxExternVelManifolds = EXTERN_VELOCITY_MANIFOLDS;
params.maxInstBehaviors = MAX_PHYS_INST_BEHAVIOURS;
params.maxConstraints = maxConstraints;
// Zero means, allocate the new kinds of constraints from the game heap
params.maxBigConstraints = 0;
params.maxLittleConstraints = 0;
ms_PhysSim->Init(pLevelNew, params);
// The constraint manager is instantiated during phSimulator::Init().
ms_ConstraintManager = ms_PhysSim->GetConstraintMgr();
// Install the explosion's process collisions as the simulator's post collision callback
ms_PhysSim->SetAllCollisionsDoneCallback(MakeFunctor(phInstBehaviorExplosionGta::ProcessAllCollisions));
ms_PhysSim->SetAllowBreakingCallback(MakeFunctorRet(CBreakable::AllowBreaking));
SetGravitationalAcceleration(-GRAVITY);
SetUnitUp(ZAXIS);
// old gta gravity levels
//m_PhysSim->SetGravity(Vector3(0.0f,0.0f,-PHYSICAL_GRAVITYFORCE*FRAMES_PER_SECOND*FRAMES_PER_SECOND));
//////////////////////
// Initialise materials manager
phMaterialMgrGta::Create();
MATERIALMGR.Load();
// initialise articulated collider for ragdolls
// AF: Does nothing and has been deprecated
//phArticulatedCollider::InitClass();
// enable rage physics reference counting
phConfig::EnableRefCounting(true);
phConfig::FreezeRefCounting();
// set max force solver phases, based on scratchpad size and avoiding the warning "Physics force solver
// max phases too large, reducing from X to X due to scratch pad size limitations."
phConfig::SetForceSolverMaxPhases(1024);
// activate delayed SAP add, to prevent streaming from causing spikes in the physics
phSimulator::SetDelaySAPAddRemoveEnabled(bDelayedSAP);
// set the collision system to aggressively throw away polygons facing in the direction of motion
phSimulator::SetMinimumConcaveThickness(0.001f);
// We can't use delayed collision activations at the moment because SplitPairs runs before those activations and needs to know which objects are active
// Delayed collision activations are no longer an option but they now happen later anyway so, if you wanted to reap the rewards of 'late' collision
// activation (primarily objects will not be activated due to contacts that have gotten disabled), you're in luck.
// phSimulator::SetDelayCollisionActivations(false);
// Set the default number of force solver iterations
phContactMgr::SetLCPSolverIterations(2);
phContactMgr::SetLCPSolverIterationsFinal(2);
#if CONTACT_VALIDATION
phContact::sm_MinPosition = g_WorldLimits_AABB.GetMin();
phContact::sm_MaxPosition = g_WorldLimits_AABB.GetMax();
#endif // CONTACT_VALIDATION
fwBoxStreamer::SetInterface(&g_BoxStreamerInterfaceNew);
g_StaticBoundsStore.SetInterface(&g_StaticBoundsStoreInterface);
g_StaticBoundsStore.Init();
WorldProbe::GetShapeTestManager()->Init(INIT_CORE);
#if __PPU
phBound::DisableMessages(true);
#endif //__PPU
#if USE_FRAG_TUNE
fragTune::Instantiate();
FRAGTUNE->SetForcedTuningPath("assets:/fragments");
FRAGTUNE->Load("common:/data/fragment.xml");
#if __BANK
ms_mouseInput = rage_new phMouseInput;
ms_mouseInput->SetBoxTypeIncludeFlags(ArchetypeFlags::GTA_OBJECT_TYPE, ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES);
#endif
#endif // USE_FRAG_TUNE
//////////////////////
// create fragment manager
//////////////////////
CPedClothCollision::Init();
fragManager::ClothInterface clthInterface;
ms_TasksManager = rage_new verletTaskManager;
Assert(ms_TasksManager);
ms_InstanceTasksManager = rage_new clothInstanceTaskManager;
Assert(ms_InstanceTasksManager);
ms_ClothManager = rage_new clothManager;
Assert(ms_ClothManager);
ms_ClothManager->Init(ms_TasksManager, ms_InstanceTasksManager);
{
// This one is owned by the rope system, so no need to delete it.
RAGE_TRACK(RopeShader);
ASSET.PushFolder("common:/shaders");
ms_RopeShader = rage_new grmShader;
ms_RopeShader->Load("rope_default");
ASSET.PopFolder();
}
ms_RopeManager = rage_new ropeManager;
Assert(ms_RopeManager);
ms_RopeManager->Init(ms_TasksManager,ms_RopeShader);
#if __BANK && !__RESOURCECOMPILER
#if USE_ROPE_DEBUG
ms_RopeManager->ms_MouseInput = CPhysics::ms_mouseInput;
#endif
ms_RopeBankManager = rage_new ropeBankManager;
Assert(ms_RopeBankManager);
ms_RopeBankManager->InitWidgets(ms_RopeManager);
#if USE_CLOTHMANAGER_BANK
ms_ClothManager->ms_MouseInput = CPhysics::ms_mouseInput;
#endif
#endif
ms_ClothManager->InitInterfaceCallbacks( clthInterface );
ms_ClothManager->LoadGlobalCustomBounds();
Assert((unsigned)nNumCacheEntries < 65536);
ms_FragManager = rage_new fragManager(
fragManager::UserDataInit::NullFunctor(), // callback
MakeFunctor(&CBreakable::Insert), // insertMovable
MakeFunctor(&CBreakable::Remove), // removeMovable
MakeFunctor(&CBreakable::InitFragInst), // newMovable
MakeFunctorRet(&CBreakable::AddToDrawBucketFragCallback), // drawCallback
&clthInterface, // clothInterface
MakeFunctorRet(fragManager::ReturnEntityClassZero), // convertEntityClass
(rage::u32)nNumCacheEntries, // numCacheEntries
(rage::u16)nNumCacheEntries, // numTypeStubs
fragManager::TypePagedInFunctor::NullFunctor(), // pagedInCallback
fragManager::TypePagingOutFunctor::NullFunctor(), // pagingOutCallback
true // skipInitCacheMgrInit
);
// want to put our own fragInsts in the cache
ms_pSuppliedFragCacheInsts = rage_new fragInstGta[nNumCacheEntries];
fragInst** pFragCacheInsts = rage_new fragInst*[nNumCacheEntries];
for(int i=0; i<nNumCacheEntries; i++)
{
ms_pSuppliedFragCacheInsts[i].SetClassType(PH_INST_FRAG_CACHE_OBJECT);
pFragCacheInsts[i] = &ms_pSuppliedFragCacheInsts[i];
}
ms_FragManager->InitCacheMgr(pFragCacheInsts);
delete [] pFragCacheInsts;
pFragCacheInsts = NULL;
ms_FragManager->SetDefaultTypeFlags(ArchetypeFlags::GTA_OBJECT_TYPE);
InitSharedRagdollFragTypeData();
phBound::DisableMessages();
#if __BANK
PGTAMATERIALMGR->GetDebugInterface().Init();
#endif
CExplosionManager::Init();
#if NORTH_CLOTHS
CClothMgr::Init();
#endif
// Glass textures, also in txd version.
ms_GlassTxd = vfxUtil::InitTxd("breakableglass");
Assert( ms_GlassTxd != -1 );
RAGE_TRACK(BreakableGlass);
ASSET.PushFolder("common:/data/glass");
g_TxdStore.PushCurrentTxd();
g_TxdStore.SetCurrentTxd(strLocalIndex(ms_GlassTxd));
bgGlassManager::InitClass();
g_TxdStore.PopCurrentTxd();
bgGlassManager::SetMaxLod(bgLod::LOD_MED);
bgInitGlassBoundForGameFunctor initGlassBoundFunctor;
initGlassBoundFunctor.Bind(&CBreakable::InitGlassBound);
bgGlassManager::SetInitGlassBoundForGameFunctor(initGlassBoundFunctor);
bgInitGlassInstForGameFunctor initGlassInstFunctor;
initGlassInstFunctor.Bind(&CBreakable::InitGlassInst);
bgGlassManager::SetInitGlassInstForGameFunctor(initGlassInstFunctor);
bgInitShardInstForGameFunctor initShardFunctor;
initShardFunctor.Bind(&CBreakable::InitShardInst);
bgGlassManager::SetInitShardInstForGameFunctor(initShardFunctor);
fragManager::ComputeGlassCrackTypeCallbackFunctor glassSelectCrack;
glassSelectCrack.Bind(&CPhysics::GlassSelectCrack);
//@@: location CPHYSICSCORE_INIT
ms_FragManager->SetComputeGlassCrackTypeCallback(glassSelectCrack);
bgGlassManager::InitPhysicsInstPool((u16)maxGlassShards);
ASSET.PopFolder();
{
RAGE_TRACK(GlassShader);
ASSET.PushFolder("common:/shaders");
ms_GlassShader = rage_new grmShader;
ms_GlassShader->Load("glass_breakable_crack");
ms_GlassShader->SetDrawBucketMask(CRenderer::GenerateBucketMask(CRenderer::RB_ALPHA));
bgGlassManager::SetGlassShader(ms_GlassShader);
ASSET.PopFolder();
}
#if __BANK
bkBank& bank = BANKMGR.CreateBank("Breakable Glass");
#if RAGE_GLASS_USE_PRIVATE_HEAP
bank.PushGroup("Memory");
bank.AddSlider("Maximum Panes", &sm_maxPanes, 1, 100, 1);
bank.AddSlider("Buffer Memory (KB)", &sm_bufferMem, 1, 4194304, 1);
bank.AddButton("Reallocate", &GlassReallocMemory);
bank.PopGroup();
#endif
bank.AddButton("Reset All Glass", &GlassResetAll);
bank.PushGroup("Mouse Control");
bank.AddToggle("Enable",&ms_MouseBreakEnable);
bank.AddSlider("Damage Size", &ms_MouseBreakDamage, 0.0f, 10000.0f, 1.0f);
bank.AddSlider("Detection Radius", &ms_MouseBreakRadius, 0.0f, 20.0f, 0.1f);
bank.AddSlider("Impulse", &ms_MouseBreakImpulse, 0.0f, 20.0f, 0.1f);
bank.AddSlider("Crack Selection", &ms_MouseBreakCrackType,0,20,1);
bank.AddToggle("Hidden Hit", &ms_MouseBreakHiddenHit);
bank.PushGroup("Display Values");
bank.AddText("Impact position X",&ms_MouseBreakPosition.x,true);
bank.AddText("Impact position Y",&ms_MouseBreakPosition.y,true);
bank.AddText("Impact position Z",&ms_MouseBreakPosition.z,true);
bank.AddText("Impact direction X",&ms_MouseBreakVelocity.x,true);
bank.AddText("Impact direction Y",&ms_MouseBreakVelocity.y,true);
bank.AddText("Impact direction Z",&ms_MouseBreakVelocity.z,true);
bank.PopGroup();
bank.PopGroup();
bank.AddToggle("Override Crack selection",&ms_OverrideCrackSelection);
bank.AddSlider("Crack index",&ms_OverrideSelectedCrack,0,20,1);
bgGlassManager::AddWidgets(bank);
#endif
#if __OPTIMIZED
if (!PARAM_nomtphys.Get())
{
#if __XENON
phConfig::SetWorkerThreadCount(4);
#elif __PS3
phConfig::SetWorkerThreadCount(3);
#elif RSG_DURANGO
phConfig::SetWorkerThreadCount(4);
#else
phConfig::SetWorkerThreadCount(sysTaskManager::GetNumThreads());
#endif
}
#endif
#if !USER_JBN // Disable having the main thread do work for spu profiling.
// It seems to be some benefit, with little risk, to let the PPU do some work while it would otherwise be idle waiting on SPU collisions
phConfig::SetMainThreadAlsoWorks(true);
#if __PS3
// However, on the PS3, there are some issues with the force solver helping on the main thread, so simply disable it for now.
phConfig::SetForceSolverMainThreadAlsoWorks(false);
#endif // __PS3
#endif // !USER_JBN
fragInstGta::LoadVehicleFragImpulseFunction();
#if GPU_DAMAGE_WRITE_ENABLED
sm_VehicleMutex = rage::sysIpcCreateMutex();
#endif
//Init Perlin smoothed noise static texture for use in all vehicles
CVehicleDeformation::InitSmoothedPerlinNoise();
}
//
// name: CPhysics::Shutdown
// description: Shutdown physics systems
void CPhysics::ShutdownCore()
{
#if GPU_DAMAGE_WRITE_ENABLED
sysIpcDeleteMutex(sm_VehicleMutex);
#endif
fragInstGta::ReleaseVehicleFragImpulseFunction();
bgGlassManager::ShutdownClass();
delete ms_GlassShader;
ms_GlassShader = 0;
#if NORTH_CLOTHS
// Shutdown the cloth manager.
CClothMgr::Shutdown();
#endif
// Shutdown the physics explosion manager
CExplosionManager::Shutdown();
WorldProbe::GetShapeTestManager()->Shutdown(SHUTDOWN_CORE);
ShutdownSharedRagdollFragTypeData();
#if USE_FRAG_TUNE && __BANK
fragNames.Reset();
#endif //USE_FRAG_TUNE && __BANK
#if USE_FRAG_TUNE
fragTune::Destroy();
#endif
Assert( ms_ClothManager );
ms_ClothManager->Shutdown();
delete ms_ClothManager;
ms_ClothManager = NULL;
Assert( ms_RopeManager );
ms_RopeManager->Shutdown();
delete ms_RopeManager;
ms_RopeManager = NULL;
#if __BANK && !__RESOURCECOMPILER
Assert( ms_RopeBankManager );
ms_RopeBankManager->DestroyWidgets();
delete ms_RopeBankManager;
ms_RopeBankManager = NULL;
#endif
Assert( ms_TasksManager );
delete ms_TasksManager;
ms_TasksManager = NULL;
delete [] ms_pSuppliedFragCacheInsts;
delete ms_FragManager;
ms_FragManager = NULL;
// g_breakable.Shutdown();
// AF: deprecated
//phArticulatedCollider::ShutdownClass();
#if __PPU
phSimulator::ShutdownClass();
#endif //__PPU...
g_StaticBoundsStore.Shutdown();
ms_PhysLevel->Shutdown();
delete ms_PhysLevel;
ms_PhysLevel = NULL;
//delete ms_PhysSim;
//ms_PhysSim = NULL;
MATERIALMGR.Destroy();
}
void CPhysics::Init(unsigned initMode)
{
USE_MEMBUCKET(MEMBUCKET_PHYSICS);
if(initMode == INIT_CORE)
{
InitCore();
#if PHYSICS_REQUEST_LIST_ENABLED
s32 iPhysicsReqListSize = PHYSIC_REQUEST_LIST_SIZE;
#if NAVMESH_EXPORT
if(CNavMeshDataExporter::WillExportCollision())
{
iPhysicsReqListSize*=8; //= 50000;
}
#endif // NAVMESH_EXPORT
ms_physicsReqList.Init(iPhysicsReqListSize);
#endif //PHYSICS_REQUEST_LIST_ENABLED
}
else if(initMode == INIT_SESSION)
{
#if __DEV
ms_debugDrawStore.ClearAll();
#endif
#if PHYSICS_REQUEST_LIST_ENABLED
ms_physicsReqList.Clear();
#endif //PHYSICS_REQUEST_LIST_ENABLED
SetGravityLevel(GRAV_EARTH);
CWheel::uXmasWeather = g_weather.GetTypeIndex(ATSTRINGHASH("XMAS", 0xaac9c895));// HACK cache off the xmas weather.
}
else if (initMode == INIT_BEFORE_MAP_LOADED)
{
GlassDefaultMemory();
#if RAGE_GLASS_USE_PRIVATE_HEAP
bgGlassManager::Malloc(sm_maxPanes, sm_bufferMem, GetGlassHeapMem());
#elif RAGE_GLASS_USE_USER_HEAP
bgGlassManager::Malloc(sm_maxPanes, &strStreamingEngine::GetAllocator(), GetGlassHeapMem());
#else
bgGlassManager::Malloc(sm_maxPanes, GetGlassHeapMem());
#endif
}
}
//
// name: CPhysics::Shutdown
// description: Before the world is emptied.
void CPhysics::Shutdown(unsigned shutdownMode)
{
if(shutdownMode == SHUTDOWN_CORE)
{
#if PHYSICS_REQUEST_LIST_ENABLED
ms_physicsReqList.Shutdown();
#endif //PHYSICS_REQUEST_LIST_ENABLED
ShutdownCore();
}
else if(shutdownMode == SHUTDOWN_WITH_MAP_LOADED)
{
g_StaticBoundsStore.Shutdown();
bgGlassManager::Free();
}
else if(shutdownMode == SHUTDOWN_SESSION)
{
CExplosionManager::Shutdown(SHUTDOWN_SESSION);
}
}
#if GPU_DAMAGE_WRITE_ENABLED
void CPhysics::QueueDamagedVehicleByGPU(CVehicle *pVehicle, bool &bUpdatedByGPU)
{
rage::sysIpcLockMutex(sm_VehicleMutex);
if(!bUpdatedByGPU)
{
bUpdatedByGPU = true;
Assertf(ms_pDamagedVehicles.Find(pVehicle) == -1, "[VEHICLE_DEFORMATION]Duplicated entry is entered to CPhysics::ms_pDamagedVehicles");
ms_pDamagedVehicles.PushAndGrow(pVehicle);
}
rage::sysIpcUnlockMutex(sm_VehicleMutex);
}
void CPhysics::ProcessDamagedVehicles()
{
rage::sysIpcLockMutex(sm_VehicleMutex);
int iVehicleIndex = 0;
int iCount = ms_pDamagedVehicles.GetCount();
for (int i=0; i < iCount; ++i)
{
CVehicle* pVehicle = ms_pDamagedVehicles[iVehicleIndex];
if(i < MAX_BOUND_DEFORMATION_PER_FRAME || (pVehicle && pVehicle->HasBoundUpdatePending()))
{
if (Verifyf(pVehicle && pVehicle->WasDamageUpdatedByGPU(), "[VEHICLE_DEFORMATION]vehicle in CPhysics::ms_pDamagedVehicles should have deformation texture updated, 0x%p, %s", pVehicle, pVehicle->GetVehicleModelInfo()->GetModelName()))
{
pVehicle->HandleDamageUpdatedByGPU();
}
ms_pDamagedVehicles.Delete(iVehicleIndex);
// Do not increment the iVehicleIndex if we deleted an entry
}
else
{
iVehicleIndex++;
}
}
rage::sysIpcUnlockMutex(sm_VehicleMutex);
}
void CPhysics::RemoveVehicleDeformation(CVehicle* pVehicle)
{
rage::sysIpcLockMutex(sm_VehicleMutex);
int idx = ms_pDamagedVehicles.Find(pVehicle);
if(idx != -1)
{
ms_pDamagedVehicles.Delete(idx);
}
rage::sysIpcUnlockMutex(sm_VehicleMutex);
}
#endif
void CPhysics::PreSimUpdate(float fTimeStep, int nSlice)
{
#if GPU_DAMAGE_WRITE_ENABLED
if(CPhysics::GetIsFirstTimeSlice(nSlice))
{
ProcessDamagedVehicles();
}
#endif
if(CVehicleRecordingMgr::sm_bUpdateWithPhysics && CVehicleRecordingMgr::sm_bUpdateBeforePreSimUpdate)
{
PF_START(Proc_VehRecording);
CVehicleRecordingMgr::RetrieveDataForThisFrame();
PF_STOP(Proc_VehRecording);
}
PreSimControl preSimControl;
preSimControl.nNumPostponedEntities = 0;
preSimControl.nNumPostponedEntities2 = 0;
preSimControl.nNumSecondPassEntities = 0;
preSimControl.nNumThirdPassEntities = 0;
preSimControl.fTimeStep = fTimeStep;
preSimControl.nSlice = nSlice;
const int maxSecondPassEntities = PreSimControl::GetMaxSecondPassEntities();
preSimControl.aSecondPassEntities = Alloca(CPhysical*, maxSecondPassEntities);
const int maxThirdPassEntities = PreSimControl::GetMaxThirdPassEntities();
preSimControl.aThirdPassEntities = Alloca(CPhysical*, maxThirdPassEntities);
fwSceneUpdate::Update(CGameWorld::SU_PRESIM_PHYSICS_UPDATE, &preSimControl);
#if __BANK
// Clear the debug draw impact arrays before they get populated in IterateOverManifolds().
CDeformableObjectManager::GetInstance().ResetImpactArrays();
#endif // __BANK
CPhysical** aPostponedEntities = preSimControl.aPostponedEntities;
int nNumPostponedEntities = preSimControl.nNumPostponedEntities;
CPhysical** aSecondPassEntities = preSimControl.aSecondPassEntities;
int nNumSecondPassEntities = preSimControl.nNumSecondPassEntities;
CPhysical** aThirdPassEntities = preSimControl.aThirdPassEntities;
int nNumThirdPassEntities = preSimControl.nNumThirdPassEntities;
CPhysical** aPostponedEntities2 = preSimControl.aPostponedEntities2;
int nNumPostponedEntities2 = preSimControl.nNumPostponedEntities2;
// now process postponed entities
for(int i=0; i<nNumPostponedEntities; i++)
{
CPhysical* pEntity = aPostponedEntities[i];
bool networkBlendFirst = GetShouldNetworkBlendBeforePhysics( pEntity );
if( networkBlendFirst )
{
netObject *networkObject = NetworkUtils::GetNetworkObjectFromEntity(pEntity);
if (networkObject && networkObject->IsClone() && networkObject->GetNetBlender())
{
// update the network blender that overrides the physics and blends the entity from
// one position to another
if (networkObject->CanBlend())
{
networkObject->GetNetBlender()->Update();
}
}
}
ePhysicsResult nReturn = pEntity->ProcessPhysics(fTimeStep, false, nSlice);
Assertf(nReturn != PHYSICS_NEED_THIRD_PASS, "Entity type: %i | Entity name: %s", pEntity->GetType(), pEntity->GetDebugName());// why are we requesting a third pass when we aren't doing a second?
if (nReturn == PHYSICS_NEED_SECOND_PASS)
{
Assert(nNumSecondPassEntities < maxSecondPassEntities);
aSecondPassEntities[nNumSecondPassEntities++] = pEntity;
}
else
{
if(CPhysics::GetIsFirstTimeSlice(nSlice) && !networkBlendFirst)
{
netObject *networkObject = NetworkUtils::GetNetworkObjectFromEntity(pEntity);
if (networkObject && networkObject->IsClone() && networkObject->GetNetBlender())
{
// update the network blender that overrides the physics and blends the entity from
// one position to another
if (networkObject->CanBlend())
networkObject->GetNetBlender()->Update();
}
}
}
}
// and anything that needs a second pass (i.e. vehicles waiting on SPU wheel integrator)
for(int i=0; i<nNumSecondPassEntities; i++)
{
CPhysical* pEntity = aSecondPassEntities[i];
ePhysicsResult nReturn = pEntity->ProcessPhysics2(fTimeStep, nSlice);
if (nReturn == PHYSICS_NEED_THIRD_PASS)
{
Assert(nNumThirdPassEntities < maxThirdPassEntities);
aThirdPassEntities[nNumThirdPassEntities++] = pEntity;
}
else
{
bool networkBlendFirst = GetShouldNetworkBlendBeforePhysics( pEntity );
if( !networkBlendFirst &&
CPhysics::GetIsFirstTimeSlice(nSlice))
{
netObject *networkObject = NetworkUtils::GetNetworkObjectFromEntity(aSecondPassEntities[i]);
if (networkObject && networkObject->IsClone() && networkObject->GetNetBlender())
{
// update the network blender that overrides the physics and blends the entity from
// one position to another
if (networkObject->CanBlend())
networkObject->GetNetBlender()->Update();
}
}
}
}
// and anything that needs a third pass (i.e. vehicles applying forces from their wheels onto other vehicles)
for(int i=0; i<nNumThirdPassEntities; i++)
{
aThirdPassEntities[i]->ProcessPhysics3(fTimeStep, nSlice);
if(CPhysics::GetIsFirstTimeSlice(nSlice))
{
bool networkBlendFirst = GetShouldNetworkBlendBeforePhysics( aThirdPassEntities[i] );
netObject *networkObject = NetworkUtils::GetNetworkObjectFromEntity(aThirdPassEntities[i]);
if( !networkBlendFirst && networkObject && networkObject->IsClone() && networkObject->GetNetBlender() )
{
// update the network blender that overrides the physics and blends the entity from
// one position to another
if (networkObject->CanBlend())
networkObject->GetNetBlender()->Update();
}
}
}
// now process postponed 2 entities.
// Note: we don't currently support doing a second or third pass on these entities. This list was added to support processing physics on objects that are on top of
// clone objects that require 2 passes (as these objects don't update the network blender until after the 2nd pass, and we require this information before proceeding).
for(int i=0; i<nNumPostponedEntities2; i++)
{
CPhysical* pEntity = aPostponedEntities2[i];
ASSERT_ONLY(ePhysicsResult nReturn = ) pEntity->ProcessPhysics(fTimeStep, false, nSlice);
Assert(nReturn != PHYSICS_NEED_SECOND_PASS && nReturn != PHYSICS_NEED_THIRD_PASS);
if(CPhysics::GetIsFirstTimeSlice(nSlice))
{
netObject *networkObject = NetworkUtils::GetNetworkObjectFromEntity(pEntity);
if (networkObject && networkObject->IsClone() && networkObject->GetNetBlender())
{
// update the network blender that overrides the physics and blends the entity from
// one position to another
if (networkObject->CanBlend())
networkObject->GetNetBlender()->Update();
}
}
}
FRAGNMASSETMGR->StepPhase1(fTimeStep);
ms_PhysSim->PreUpdateInstanceBehaviors(fTimeStep);
if(ms_bClearManifoldsEveryFrame || ms_ClearManifoldsFramesLeft > 0)
{
// This was implemented for pool game, if we aren't in an interior then something has probably gone wrong
// Allowed if using the frame countdown version
Assertf(FindPlayerPed()->GetIsInInterior() || ms_ClearManifoldsFramesLeft > 0,"Warning: Manifolds being cleared when player not in interior: Check for script not cleaning up properly");
btBroadphasePair *prunedPairs = ms_PhysLevel->GetBroadPhase()->getPrunedPairs();
const int iPrunedPairCount = ms_PhysLevel->GetBroadPhase()->getPrunedPairCount();
for(int iPairIndex = 0; iPairIndex < iPrunedPairCount; ++iPairIndex)
{
btBroadphasePair *pPair = prunedPairs + iPairIndex;
if(pPair->GetManifold())
{
pPair->GetManifold()->Reset();
}
}
}
if(ms_ClearManifoldsFramesLeft > 0)
{
ms_ClearManifoldsFramesLeft--;
}
#if __ASSERT
// Check CCD handles rotation is not on outside interior - dumb check to see if activity scripts have turned this off after shutting down
// if(FindPlayerPed() && ms_PhysSim->GetAutoCCDFirstContactOnlyEnabled())
// {
// Assertf(FindPlayerPed()->GetIsInInterior(),"Warning: AutoCCDFirstContactOnly is on when player is not inside an interior. Did an activity forget to clean up?");
// }
#endif
// Mark the collision records as out-of-date (But only on the first simulator iteration of a game frame)
// We won't repopulate them till IterateOverManifolds() in SimUpdate()
// but we have to wipe here before PreSimUpdate() marks active objects via NotifyHistoryWillBeCompleteThisTick()
// - Otherwise for single iteration frames m_LastValidFrame of records won't match the global ms_CurrentFrame
// (And even multi-iteration frames would still have those out of sync until the next PreSimUpdate call which could result in wiped records)
if(CPhysics::GetIsFirstTimeSlice(nSlice))
{
CCollisionHistory::ClearRecords();
}
//This must occur after the ProcessPhysics calls, as they can activate insts
CCollisionHistory::PreSimUpdate();
ms_RopeManager->UpdateViewport(gVpMan.GetCurrentViewport()->GetGrcViewport());
}
bool CPhysics::BreakGlass(CDynamicEntity *entity, Vec3V_In position, float radius, Vec3V_In impulse, float damage, int crackType, bool forceStoreHit)
{
fragInst* pFragInst = (fragInst*)entity->GetCurrentPhysicsInst();
if( pFragInst == NULL )
return false;
if( pFragInst->GetCached() == false)
{
pFragInst->PutIntoCache();
}
if( pFragInst->GetCached() == false ) // Still not in the cache ? abort!
return false;
const fragCacheEntry* entry = pFragInst->GetCacheEntry();
const fragHierarchyInst* hierInst = entry->GetHierInst();
const fragHierarchyInst::GlassInfo* glassInfos = hierInst->glassInfos;
bool hit = false;
for(int glassIndex = 0; glassIndex != hierInst->numGlassInfos; ++ glassIndex)
{
const bgGlassHandle handle = glassInfos[glassIndex].handle;
if(handle != bgGlassHandle_Invalid)
{
const bgBreakable &breakable = bgGlassManager::GetGlassBreakable(handle);
const Mat34V breakableMatrix = RCC_MAT34V(breakable.GetTransform());
const Vec3V bsPos = UnTransformOrtho(breakableMatrix,position);
bgBreakable::TriangleId breakTriId = bgBreakable::kInvalidTriangle;
breakTriId = breakable.GetNextPaneTriangle(breakTriId);
if(forceStoreHit) // Skip the intersection tests and just store this hit
{
bgGlassManager::HitGlass(handle, crackType, position, impulse);
GLASS_RECORDER_ONLY(if(glassRecordingRage::IsActive()) { glassRecordingRage::Get()->OnHitGlass(handle, crackType, position, impulse); })
hit = true;
}
else
{
while (breakTriId != bgBreakable::kInvalidTriangle)
{
Vector3 verts[3];
Vector3 normal;
if (breakable.GetPaneTriangleInfo(breakTriId, verts[0], verts[1], verts[2], normal))
{
const bool impact = geomSpheres::TestSphereToTriangle(Vec4V(bsPos, ScalarV(radius)), (const Vec3V*)verts, RCC_VEC3V(normal));
if( impact )
{
bgGlassManager::HitGlass(handle, crackType, position, impulse);
GLASS_RECORDER_ONLY(if(glassRecordingRage::IsActive()) { glassRecordingRage::Get()->OnHitGlass(handle, crackType, position, impulse); })
hit = true;
break; // No point in adding the same hit multiple times
}
}
breakTriId = breakable.GetNextPaneTriangle(breakTriId);
}
}
}
}
if( hit )
return true;
const fragPhysicsLOD* physics = pFragInst->GetTypePhysics();
int numGroup = physics->GetNumChildGroups();
const Mat34V entityMtx = entity->GetMatrix();
hit = false;
for(int groupIdx=0;groupIdx<numGroup;groupIdx++)
{
const fragTypeGroup *group = physics->GetAllGroups()[groupIdx];
if (group->GetMadeOfGlass())
{
const int childFragIdx = group->GetChildFragmentIndex();
const int numChildren = group->GetNumChildren();
const phBoundComposite *bounds = entry->GetBound();
for (int groupChildIndex = childFragIdx; groupChildIndex < childFragIdx + numChildren; groupChildIndex ++)
{
const phBound * bound = bounds->GetBound(groupChildIndex);
if( bound )
{
const Vec3V boundMin = bound->GetBoundingBoxMin();
const Vec3V boundMax = bound->GetBoundingBoxMax();
const Mat34V objectMtx = bounds->GetCurrentMatrix(groupChildIndex);
Mat34V mtx;
Transform(mtx,entityMtx, objectMtx);
const bool impact = geomBoxes::TestSphereToBox(position,ScalarV(radius),boundMin,boundMax,RCC_MATRIX34(mtx));
if( impact )
{
const float health = group->GetDamageHealth();
ms_SelectedCrack = crackType;
pFragInst->ReduceGroupDamageHealth( groupIdx,
health - damage,
position,
impulse);
hit = true;
}
}
}
}
}
return hit;
}
//
// name: CPhysics::InitSharedRagdollFragTypeData
// description:
//
void CPhysics::InitSharedRagdollFragTypeData()
{
#if PHYSICS_LOD_GROUP_SHARING
//////////////////////
// Add reference fragTypes for types that share physics data
//////////////////////
strLocalIndex fred = g_FragmentStore.FindSlot(strStreamingObjectName("platform:/models/z_z_fred",0xCF8CE587));
strLocalIndex wilma = g_FragmentStore.FindSlot(strStreamingObjectName("platform:/models/z_z_wilma",0x3E640874));
strLocalIndex fred_large = g_FragmentStore.FindSlot(strStreamingObjectName("platform:/models/z_z_fred_large",0x8063EF5));
strLocalIndex wilma_large = g_FragmentStore.FindSlot(strStreamingObjectName("platform:/models/z_z_wilma_large",0x9D471AB));
strLocalIndex alien = g_FragmentStore.FindSlot(strStreamingObjectName("platform:/models/z_z_alien",0xE89296D8));
CStreaming::LoadObject(fred, g_FragmentStore.GetStreamingModuleId());
CStreaming::LoadObject(wilma, g_FragmentStore.GetStreamingModuleId());
CStreaming::LoadObject(fred_large, g_FragmentStore.GetStreamingModuleId());
CStreaming::LoadObject(wilma_large, g_FragmentStore.GetStreamingModuleId());
CStreaming::LoadObject(alien, g_FragmentStore.GetStreamingModuleId());
g_FragmentStore.AddRef(fred, REF_OTHER);
g_FragmentStore.AddRef(wilma, REF_OTHER);
g_FragmentStore.AddRef(fred_large, REF_OTHER);
g_FragmentStore.AddRef(wilma_large, REF_OTHER);
g_FragmentStore.AddRef(alien, REF_OTHER);
m_ManagedPhysicsLODGroup[0].Init(g_FragmentStore.Get(fred)->GetPhysicsLODGroup(), fred.Get(), CreateInvalidRagdollFilter(*g_FragmentStore.Get(fred)));
m_ManagedPhysicsLODGroup[1].Init(g_FragmentStore.Get(wilma)->GetPhysicsLODGroup(), wilma.Get(), CreateInvalidRagdollFilter(*g_FragmentStore.Get(wilma)));
m_ManagedPhysicsLODGroup[2].Init(g_FragmentStore.Get(fred_large)->GetPhysicsLODGroup(), fred_large.Get(), CreateInvalidRagdollFilter(*g_FragmentStore.Get(fred_large)));
m_ManagedPhysicsLODGroup[3].Init(g_FragmentStore.Get(wilma_large)->GetPhysicsLODGroup(), wilma_large.Get(), CreateInvalidRagdollFilter(*g_FragmentStore.Get(wilma_large)));
m_ManagedPhysicsLODGroup[4].Init(g_FragmentStore.Get(alien)->GetPhysicsLODGroup(), alien.Get(), CreateInvalidRagdollFilter(*g_FragmentStore.Get(alien)));
#if __DEV
if (PARAM_physicsrigsatload.Get())
{
// Load directly from XML files. The physics LOD groups will not get released
// when the fragType does due to PHYSICS_LOD_GROUP_SHARING.
fragType *fredType = rage_new fragType;
fragType *wilmaType = rage_new fragType;
fragType *fredLargeType = rage_new fragType;
fragType *wilmaLargeType = rage_new fragType;
fragType *alienType = rage_new fragType;
const char* fredEntity = "C:\\VRigs\\Fred\\entity\\entity.type";
const char* wilmaEntity = "C:\\VRigs\\Wilma\\entity\\entity.type";
const char* fredLargeEntity = "C:\\VRigs\\FredLarge\\entity\\entity.type";
const char* wilmaLargeEntity = "C:\\VRigs\\WilmaLarge\\entity\\entity.type";
const char* alienEntity = "C:\\VRigs\\Alien\\entity\\entity.type";
const char* fredRig = "common:/data/ragdoll/ragdoll_type_male";
const char* wilmaRig = "common:/data/ragdoll/ragdoll_type_female";
const char* fredLargeRig = "common:/data/ragdoll/ragdoll_type_male_large";
const char* wilmaLargeRig = "common:/data/ragdoll/ragdoll_type_female_large";
const char* alienRig = "common:/data/ragdoll/ragdoll_type_alien";
fredType->LoadWithXML(fredEntity, fredRig);
wilmaType->LoadWithXML(wilmaEntity, wilmaRig);
fredLargeType->LoadWithXML(fredLargeEntity, fredLargeRig);
wilmaLargeType->LoadWithXML(wilmaLargeEntity, wilmaLargeRig);
alienType->LoadWithXML(alienEntity, alienRig);
m_ManagedPhysicsLODGroup[0].SetPhysicsLODGroup(fredType->GetPhysicsLODGroup()); // overwriting previous values but that's fine
m_ManagedPhysicsLODGroup[1].SetPhysicsLODGroup(wilmaType->GetPhysicsLODGroup());
m_ManagedPhysicsLODGroup[2].SetPhysicsLODGroup(fredLargeType->GetPhysicsLODGroup());
m_ManagedPhysicsLODGroup[3].SetPhysicsLODGroup(wilmaLargeType->GetPhysicsLODGroup());
m_ManagedPhysicsLODGroup[4].SetPhysicsLODGroup(alienType->GetPhysicsLODGroup());
delete fredType; // ditch the non-physics component of the types
delete wilmaType;
delete fredLargeType;
delete wilmaLargeType;
delete alienType;
}
#endif
// Assign materials
for (int humanType = 0; humanType <= 4; humanType++)
{
// Load high ragdoll LOD materials
fragPhysicsLOD *fragLOD = m_ManagedPhysicsLODGroup[humanType].GetPhysicsLODGroup()->GetLODByIndex(0);
phBoundComposite *composite = fragLOD->GetCompositeBounds();
for (int ibound = 0; ibound < composite->GetNumBounds(); ibound++)
composite->GetBound(ibound)->SetMaterial(PGTAMATERIALMGR->g_idButtocks + ibound);
// Load medium ragdoll LOD materials
fragLOD = m_ManagedPhysicsLODGroup[humanType].GetPhysicsLODGroup()->GetLODByIndex(1);
if (fragLOD)
{
composite = fragLOD->GetCompositeBounds();
for (int ibound = 0; ibound < composite->GetNumBounds(); ibound++)
{
int ragdollComponent = fragInstNM::MapRagdollLODComponentCurrentToHigh(ibound, 1);
if (Verifyf(ragdollComponent != -1, "Invalid ragdoll component %d", ragdollComponent))
{
composite->GetBound(ibound)->SetMaterial(PGTAMATERIALMGR->g_idButtocks + ragdollComponent);
}
}
}
// Load low ragdoll LOD materials
fragLOD = m_ManagedPhysicsLODGroup[humanType].GetPhysicsLODGroup()->GetLODByIndex(2);
if (fragLOD)
{
composite = fragLOD->GetCompositeBounds();
for (int ibound = 0; ibound < composite->GetNumBounds(); ibound++)
{
int ragdollComponent = fragInstNM::MapRagdollLODComponentCurrentToHigh(ibound, 2);
if (Verifyf(ragdollComponent != -1, "Invalid ragdoll component %d", ragdollComponent))
{
composite->GetBound(ibound)->SetMaterial(PGTAMATERIALMGR->g_idButtocks + ragdollComponent);
}
}
}
}
#endif
//////////////////////
// create Natural Motion asset manager
//////////////////////
const char* artFileFred = "fred";
const char* artFileWilma = "wilma";
const char* artFileFredLarge = "fred_large";
const char* artFileWilmaLarge = "wilma_large";
const char* artFileAlien = "alien";
const char *pPath = "platform:/data/naturalmotion";
#if __DEV
PARAM_nmfolder.Get(pPath);
#endif
CFileMgr::SetDir(pPath);
fragNMAssetManager::Create();
// different number of agents on different platforms
FRAGNMASSETMGR->Load(artFileFred);
FRAGNMASSETMGR->Load(artFileWilma);
FRAGNMASSETMGR->Load(artFileFredLarge);
FRAGNMASSETMGR->Load(artFileWilmaLarge);
FRAGNMASSETMGR->Load(artFileAlien);
CFileMgr::SetDir("");
// load NM task parameters
CTaskNMBehaviour::LoadTaskParameters();
FRAGNMASSETMGR->SetIncomingAnimVelocityScale(1.0f);
FRAGNMASSETMGR->SetFromAnimationMaxSpeed(ms_fRagdollActivationAnimVelClamp);
FRAGNMASSETMGR->SetFromAnimationMaxAngSpeed(ms_fRagdollActivationAnimAngVelClamp);
// Initialize the self-collision groups
FRAGNMASSETMGR->InitSelfCollisionsGroups(m_ManagedPhysicsLODGroup[0].GetPhysicsLODGroup()->GetLODByIndex(0));
// test lower friction for ragdoll feet
//const_cast<phMaterial*>(&PGTAMATERIALMGR->GetMaterial(PGTAMATERIALMGR->g_idFootLeft))->SetFriction(1.0f);
//const_cast<phMaterial*>(&PGTAMATERIALMGR->GetMaterial(PGTAMATERIALMGR->g_idFootRight))->SetFriction(1.0f);
}
void CPhysics::ShutdownSharedRagdollFragTypeData()
{
#if PHYSICS_LOD_GROUP_SHARING
for (int i = 0; i < kNumManagedPhysicsLODGroups; i++)
{
m_ManagedPhysicsLODGroup[i].Shutdown();
}
#endif
fragNMAssetManager::Destroy();
}
#if PHYSICS_LOD_GROUP_SHARING
CPhysics::ManagedPhysicsLODGroup& CPhysics::GetManagedPhysicsLODGroup(int iAssetID)
{
physicsAssertf(iAssetID >= 0 && iAssetID < kNumManagedPhysicsLODGroups, "CPhysics::GetManagedPhysicsLODGroup: Invalid asset ID");
return m_ManagedPhysicsLODGroup[iAssetID];
}
#endif
crFrameFilter* CPhysics::CreateInvalidRagdollFilter(const fragType& type)
{
CMovePed::RagdollFilter* pInvalidRagdollFilter = rage_new CMovePed::RagdollFilter();
const crSkeletonData& skeletonData = type.GetSkeletonData();
pInvalidRagdollFilter->Init(skeletonData);
fragPhysicsLOD* pPhysicsLOD = type.GetPhysics(0);
int numChildren = pPhysicsLOD->GetNumChildren();
int numBones = skeletonData.GetNumBones();
// Unfortunately this information is only set up on the cache entry which hasn't been created yet
// Should this information not live on the fragType and be set up at resource time??
int* boneIndexToComponentMap = Alloca(int, numBones);
int* componentToBoneIndexMap = Alloca(int, numChildren);
sysMemSet(boneIndexToComponentMap, -1, sizeof(int) * numBones);
// Find the bone index of each child
for (int childIndex = 0; childIndex < numChildren; childIndex++)
{
int boneIndex;
if (fragVerifyf(skeletonData.ConvertBoneIdToIndex(pPhysicsLOD->GetChild(childIndex)->GetBoneID(), boneIndex), "CPhysics::CreateInvalidRagdollFilter: Failed to convert component %i bone id %i to a bone index", childIndex, pPhysicsLOD->GetChild(childIndex)->GetBoneID()))
{
fragAssert(boneIndex < numBones && boneIndex >= 0);
componentToBoneIndexMap[childIndex] = boneIndex;
if (boneIndexToComponentMap[boneIndex] == -1)
{
boneIndexToComponentMap[boneIndex] = childIndex;
}
}
else
{
componentToBoneIndexMap[childIndex] = -1;
}
}
// Find the component index of any bone that doesn't have a direct link with a child
for (int boneIndex = 0; boneIndex < numBones; boneIndex++)
{
for (int parentBoneIndex = skeletonData.GetParentIndex(boneIndex); parentBoneIndex != -1 && boneIndexToComponentMap[boneIndex] == -1; parentBoneIndex = skeletonData.GetParentIndex(parentBoneIndex))
{
boneIndexToComponentMap[boneIndex] = boneIndexToComponentMap[parentBoneIndex];
}
}
for (int childIndex = 0; childIndex < numChildren; childIndex++)
{
if (componentToBoneIndexMap[childIndex] != -1)
{
// Walk up the hierarchy until we either get to the root bone or a bone that is mapped to the ragdoll rig
// We're essentially looking for bones that aren't mapped to the ragdoll rig but that have a child AND parent bone that is mapped to the ragdoll rig
// We add these bones to a filter so that animation on those bones can be blended out (blended to the default pose) when going into ragdoll
// since the ragdoll rig doesn't handle non-mapped parent bones that aren't in the default pose
for (int boneIndex = skeletonData.GetParentIndex(componentToBoneIndexMap[childIndex]); (boneIndex != -1 && boneIndexToComponentMap[boneIndex] != -1 && componentToBoneIndexMap[boneIndexToComponentMap[boneIndex]] != boneIndex) || boneIndex == 0; boneIndex = skeletonData.GetParentIndex(boneIndex))
{
pInvalidRagdollFilter->AddBoneIndex(boneIndex);
}
}
}
return pInvalidRagdollFilter;
}
//
// name: CPhysics::Update
// description:
//
void CPhysics::UpdateBreakableGlass()
{
#if __BANK
if( ms_MouseBreakEnable )
{
Vector3 pos;
CEntity * entity = CDebugScene::GetEntityUnderMouse(&pos);
if( entity && entity->GetIsDynamic() )
{
CDynamicEntity *dyna = (CDynamicEntity*)entity;
if( dyna->m_nDEflags.bIsBreakableGlass )
{
grcDebugDraw::Sphere(pos,ms_MouseBreakRadius,Color32(0xffffffff),false);
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_LEFT )
{
if(ms_MouseBreakHiddenHit)
{
bgGlassManager::SetSilentHit(true);
}
Vec3V direction = RCC_VEC3V(pos) - RCC_VEC3V(camInterface::GetPos());
Vec3V velocity = Normalize(direction);
ScalarV impulse(ms_MouseBreakImpulse);
BreakGlass(dyna,RCC_VEC3V(pos),ms_MouseBreakRadius, velocity*impulse, ms_MouseBreakDamage, ms_MouseBreakCrackType);
ms_MouseBreakPosition = pos;
ms_MouseBreakVelocity = RCC_VECTOR3(velocity);
if(bgGlassManager::IsSilentHit())
{
bgGlassManager::SetSilentHit(false);
}
}
}
}
}
#endif // __BANK
// breakable glass
bgGlassManager::UpdateBuffers();
bgGlassManager::SyncBreakableGlass();
}
#if RAGE_TIMEBARS && INCLUDE_DETAIL_TIMEBARS
static const char* s_Timeslices[] = {
"Timeslice 1",
"Timeslice 2",
"Timeslice 3",
"Timeslice 4",
"Timeslice X"
};
#endif // RAGE_TIMEBARS
#if __PFDRAW
static sysIpcSema sPhysicsPFDDrawReady = NULL;
static sysIpcSema sPhysicsPFDDrawDone = NULL;
static sysCriticalSectionToken sPhysicsPFDSemaCritSec;
static u32 sPhysicsSynchronizationMaxWait = 200;
void UpdateSynchronizeRenderAndUpdateThreadSemas()
{
sysCriticalSection critSec(sPhysicsPFDSemaCritSec);
bool needsDrawReadySema = snPhysicsSynchronizeRenderAndUpdateThreads != 0;
if (!PFDGROUP_Physics.WillDraw())
{
needsDrawReadySema = false;
}
if (!sPhysicsPFDDrawReady && needsDrawReadySema)
{
sPhysicsPFDDrawReady = sysIpcCreateSema(0);
}
else if (sPhysicsPFDDrawReady && !needsDrawReadySema)
{
sysIpcDeleteSema(sPhysicsPFDDrawReady);
sPhysicsPFDDrawReady = 0;
}
bool needsDrawDoneSema = snPhysicsSynchronizeRenderAndUpdateThreads != 0;
if (!PFDGROUP_Physics.WillDraw())
{
needsDrawDoneSema = false;
}
if (!sPhysicsPFDDrawDone && needsDrawDoneSema)
{
sPhysicsPFDDrawDone = sysIpcCreateSema(0);
}
else if (sPhysicsPFDDrawDone && !needsDrawDoneSema)
{
sysIpcDeleteSema(sPhysicsPFDDrawDone);
sPhysicsPFDDrawDone = 0;
}
}
void PhysicsSynchronizationPoint(int syncPoint)
{
if (syncPoint == snPhysicsSynchronizeRenderAndUpdateThreads)
{
if (sPhysicsPFDDrawReady)
{
sysIpcSignalSema(sPhysicsPFDDrawReady);
}
if (sPhysicsPFDDrawDone)
{
if (!sysIpcWaitSemaTimed(sPhysicsPFDDrawDone, sPhysicsSynchronizationMaxWait))
{
sysCriticalSection critSec(sPhysicsPFDSemaCritSec);
sysIpcDeleteSema(sPhysicsPFDDrawDone);
sPhysicsPFDDrawDone = NULL;
}
}
}
}
#else
__forceinline void UpdateSynchronizeRenderAndUpdateThreadSemas()
{
}
__forceinline void PhysicsSynchronizationPoint(int)
{
}
#endif
void UpdatePhysicsObjectsEKG()
{
#if __STATS
int realVehicles = 0;
int dummyVehicles = 0;
int helicopters = 0;
int peds = 0;
int nmRagdolls = 0;
int highLodRagdolls = 0;
int medLodRagdolls = 0;
int lowLodRagdolls = 0;
int articulatedBodies = 0;
int rigidBodies = 0;
for( int activeIndex = PHLEVEL->GetNumActive() - 1; activeIndex >= 0; --activeIndex )
{
int levelIndex = PHLEVEL->GetActiveLevelIndex(activeIndex);
phCollider* collider = PHSIM->GetCollider(levelIndex);
if (collider->IsArticulated())
{
articulatedBodies++;
}
else
{
rigidBodies++;
}
phInst* inst = PHLEVEL->GetInstance(levelIndex);
if (CEntity* entity = CPhysics::GetEntityFromInst(inst))
{
if (entity->GetIsTypePed())
{
CPed* ped = (CPed*)entity;
if (ped->GetUsingRagdoll())
{
const fragInstNM *instNM = dynamic_cast<const fragInstNM*>(inst);
if (instNM && instNM->GetNMAgentID() >= 0)
{
nmRagdolls++;
}
else
{
fragInst* frag = static_cast<fragInst*>(inst);
switch(frag->GetCurrentPhysicsLOD())
{
case fragInst::RAGDOLL_LOD_HIGH:
highLodRagdolls++;
break;
case fragInst::RAGDOLL_LOD_MEDIUM:
medLodRagdolls++;
break;
case fragInst::RAGDOLL_LOD_LOW:
lowLodRagdolls++;
break;
default:
break;
}
}
}
else
{
peds++;
}
}
else if (entity->GetIsTypeVehicle())
{
CVehicle* vehicle = (CVehicle*)entity;
if (vehicle->IsDummy())
{
dummyVehicles++;
}
else
{
realVehicles++;
}
if (vehicle->InheritsFromHeli())
{
helicopters++;
}
}
}
}
PF_SET(RealVehicles, realVehicles);
PF_SET(DummyVehicles, dummyVehicles);
PF_SET(Helicopters, helicopters);
PF_SET(Peds, peds);
PF_SET(NMRagdolls, nmRagdolls);
PF_SET(HighLodRagdolls, highLodRagdolls);
PF_SET(MedLodRagdolls, medLodRagdolls);
PF_SET(LowLodRagdolls, lowLodRagdolls);
PF_SET(Explosions, CExplosionManager::CountExplosions());
PF_SET(ArticulatedBodies, articulatedBodies);
PF_SET(RigidBodies, rigidBodies);
#endif
}
//
// name: CPhysics::Update
// description:
//
void CPhysics::Update()
{
PF_FUNC(Phys_Total);
PF_PUSH_TIMEBAR_DETAIL("PrePhysicsUpdate");
bool disableRopeUpdate = false;
#if GTA_REPLAY
//On replay playback we need to disable to rope update when we jump around the timeline
//but still process the rest of the physics. (B*2156112) but not when fine scrubbing which is classed as a jump
if( CReplayMgr::IsReplayCursorJumping() && !CReplayMgr::IsFineScrubbing() )
disableRopeUpdate = true;
#endif //GTA_REPLAY
#if !USER_JBN // Always time slice for profiling.
NOTFINAL_ONLY(if(!PARAM_physicsupdates.Get() && !ms_OverriderNumTimeSlices))
{
// Have to restore number of timeslices first!
// In case we ran more than 60 fps last update, but are less this update.
ms_NumTimeSlices = DEFAULT_NUM_PHYS_TIMESLICES;
}
#endif // !USER_JBN
//if( ms_bInStuntMode &&
// !ms_OverriderNumTimeSlices )
//{
// ms_NumTimeSlices = DEFAULT_NUM_PHYS_TIMESLICES_IN_STUNT_MODE;
//}
// Calculate the timestep and call fwTimer to set it here so that it
// will be available for use by anyone, specifically anyone that may
// get called back in the physics pre-update.
float fTimeMult = 1.0f / float(ms_NumTimeSlices);
const float fTimeStep = fwTimer::GetTimeStep();
float fSimUpdateTimeStep = fTimeStep * fTimeMult;
fwTimer::SetRagePhysicsUpdateTimeStep(fSimUpdateTimeStep);
#if !USER_JBN // Always time slice for profiling.
NOTFINAL_ONLY(if(!PARAM_physicsupdates.Get()))
{
// If time delta are less than a 60th of a second only update physics for one time slice
const float fSingleSteppingMaxTimeSlice = (1.0f / 30.0f);
const float fMinTimeSlice = 1.0f/60.0f;
// Need to check the non-scaled time step here so this doesn't fire due to a time-warp
if (fwTimer::GetSystemTimeStep() < fMinTimeSlice)
{
if (!ms_bIgnoreForcedSingleStepThisFrame)
{
ms_NumTimeSlices = 1;
fSimUpdateTimeStep = rage::Min(fTimeStep, fSingleSteppingMaxTimeSlice);
fwTimer::SetRagePhysicsUpdateTimeStep(fSimUpdateTimeStep);
}
ms_bIgnoreForcedSingleStepThisFrame = false;
}
}
#endif // !USER_JBN
UpdateSynchronizeRenderAndUpdateThreadSemas();
USE_MEMBUCKET(MEMBUCKET_PHYSICS);
PhysicsSynchronizationPoint(PHYSICS_RENDER_UPDATE_SYNC_BEFORE_PRE_PHYSICS_UPDATE);
perfClearingHouse::SetValue(perfClearingHouse::CHARACTER_CLOTHS, GetClothManager()->GetTotalActiveCharacterClothCount());
perfClearingHouse::SetValue(perfClearingHouse::CHARACTER_CLOTH_VERTICES, GetClothManager()->GetTotalActiveCharacterClothVertexCount());
perfClearingHouse::SetValue(perfClearingHouse::ENVIRONMENT_CLOTHS, GetClothManager()->GetTotalActiveEnvironmentClothCount());
perfClearingHouse::SetValue(perfClearingHouse::ENVIRONMENT_CLOTH_VERTICES, GetClothManager()->GetTotalActiveEnvironmentClothVertexCount());
#if __BANK
FRAGNMASSETMGR->ResetProfileTimers();
#endif
UpdatePhysicsObjectsEKG();
PF_START(Phys_Pre);
// 8/7/12 - cthomas - We know only the main thread would possibly be updating physics at this
// point in time, so set the flag in the physics system to indicate that. This is an optimization
// that will allow us to skip some locking overhead in the physics system as we do various
// "callbacks" during the physics update - pre-physics, post-physics, and the pre- and post-sim
// update for each timeslice call back to various game objects to update them, which will only
// be done in the main thread. The physics system is not updating during this time.
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(true);
// Call pre physics updates on all physics entities
fwSceneUpdate::Update(CGameWorld::SU_PHYSICS_PRE_UPDATE);
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(false);
PF_STOP(Phys_Pre);
ms_RopeManager->ResetLength(ROPE_UPDATE_WITHSIM);
PhysicsSynchronizationPoint(PHYSICS_RENDER_UPDATE_SYNC_BEFORE_PRESIM);
// DW - just an idea for neatness, would be better if you passed me a viewport which you want populated with a suitable fragmanager viewport rather than I store it.
if (const grcViewport *pViewport = gVpMan.CalcViewportForFragManager())
{
ms_FragManager->SetInterestFrustum(*pViewport, *gVpMan.GetFragManagerMatrix());
bgGlassManager::SetViewport(pViewport);
}
PF_POP_TIMEBAR_DETAIL();
for(int i=0; i<ms_NumTimeSlices; i++)
{
ms_TimeSliceId++;
ms_CurrentTimeSlice = i;
PDR_ONLY(debugPlayback::RecordNewPhysicsFrame();)
PF_PUSH_TIMEBAR_DETAIL(s_Timeslices[rage::Min(i, NELEM(s_Timeslices)-1)]);
if( !disableRopeUpdate )
ms_RopeManager->UpdatePhysics(fwTimer::GetTimeStep(), ROPE_UPDATE_WITHSIM);
PF_START_TIMEBAR_BUDGETED("PreSimUpdate",1.0f);
PF_START(Phys_Pre_Sim);
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(true);
PreSimUpdate(fSimUpdateTimeStep, i);
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(false);
PF_STOP(Phys_Pre_Sim);
PF_PUSH_TIMEBAR_BUDGETED("SimUpdate",3.5f);
SimUpdate(fSimUpdateTimeStep, i);
PF_POP_TIMEBAR();
PF_START_TIMEBAR("PostSimUpdate");
PF_START(Phys_Post_Sim);
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(true);
PostSimUpdate(i, fSimUpdateTimeStep);
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(false);
PF_STOP(Phys_Post_Sim);
PF_POP_TIMEBAR_DETAIL();
if (i == 0)
{
PhysicsSynchronizationPoint(PHYSICS_RENDER_UPDATE_SYNC_AFTER_FIRST_UPDATE);
}
}
ms_CurrentTimeSlice = -1;
ms_DisableOnlyPushOnLastUpdateForOneFrame = false;
#if USE_GJK_CACHE
GJKCacheSystemPostCollisionUpdate(GJKCACHESYSTEM);
#endif
PF_START_TIMEBAR_DETAIL("PostProcessAfterAllUpdates");
PhysicsSynchronizationPoint(PHYSICS_RENDER_UPDATE_SYNC_AFTER_ALL_UPDATES);
PF_START(Phys_Post);
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(true);
fwSceneUpdate::Update(CGameWorld::SU_PHYSICS_POST_UPDATE);
PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(false);
PF_STOP(Phys_Post);
BANK_ONLY(if(!CPhysics::ms_bFragManagerUpdateEachSimUpdate))
{
PF_START(Phys_RageFragUpdate);
FRAGMGR->Update(fTimeStep,ms_NumTimeSlices);
PF_STOP(Phys_RageFragUpdate);
}
// NOTE: moved in void CGtaRenderThreadGameInterface::PerformSafeModeOperations()
// ms_InstanceTasksManager->WaitForTasks();
VerletSimPreUpdate( fwTimer::GetTimeStep() );
#if GTA_REPLAY
CReplayMgr::PreClothSimUpdate(CGameWorld::FindLocalPlayer());
#endif // GTA_REPLAY
VerletSimUpdate( clothManager::Environment | clothManager::Vehicle );
if( !disableRopeUpdate )
{
RopeSimUpdate( fwTimer::GetTimeStep(), ROPE_UPDATE_EARLY );
}
// NOTE: fix for B*1824939. this wait is not needed most likely anymore. there is another wait in core/game.cpp
// WaitForVerletTasks();
PhysicsSynchronizationPoint(PHYSICS_RENDER_UPDATE_SYNC_AFTER_POST_UPDATE);
#if __DEV
if(ms_bRenderFocusImpulses)
{
RenderFocusEntityExternalForces();
}
#endif
// TIME.SetSeconds(fOriginalSeconds, fOriginalInvSeconds); // Can be removed once rage doesn't use TIME. anymore
}
#if NAVMESH_EXPORT
namespace
{
void sNavMeshExportAppendToActiveBuildingsArray(atArray<CEntity*>& entityArray)
{
CNavMeshDataExporter::GetActiveExporter()->AppendToActiveBuildingsArray(entityArray);
}
}
#endif // NAVMESH_EXPORT
void CPhysics::UpdateRequests()
{
// This code was moved out of CPhysics::Update() so it can be shared with
// the NavMeshGenerator tool. /FF
PF_START(Phys_Requests);
#if NAVMESH_EXPORT
if(CNavMeshDataExporter::IsExportingCollision())
{
ScanAreaPtrArray(&sNavMeshExportAppendToActiveBuildingsArray);
}
#endif
#if PHYSICS_REQUEST_LIST_ENABLED
PF_START_TIMEBAR_DETAIL("UpdateRequestList");
UpdatePhysicsRequestList();
PF_STOP(Phys_Requests);
#endif //PHYSICS_REQUEST_LIST_ENABLED
}
void CPhysics::VerletSimPreUpdate(float TimeSeconds)
{
const grcViewport& grcVp = gVpMan.GetCurrentViewport()->GetGrcViewport();
ms_ClothManager->SetCamPos( grcVp.GetCameraPosition() );
ms_ClothManager->PreUpdate(grcVp, TimeSeconds);
}
void CPhysics::RopeSimUpdate(float TimeSeconds, int updateOrder )
{
ms_RopeManager->UpdatePhysics(TimeSeconds, (enRopeUpdateOrder)updateOrder);
#if __BANK && !__RESOURCECOMPILER
ms_RopeBankManager->Update( TimeSeconds );
#endif
PF_START_TIMEBAR_DETAIL("RageRopeUpdate");
PF_START(Phys_RageRopeUpdate);
ms_RopeManager->UpdateCloth((enRopeUpdateOrder)updateOrder);
PF_STOP(Phys_RageRopeUpdate);
}
void CPhysics::VerletSimUpdate( int verletType )
{
#if __BANK && !__RESOURCECOMPILER
clothBankManager::PickVehicle();
clothBankManager::PickPed();
#endif
PF_START_TIMEBAR_DETAIL("RageClothUpdate");
PF_START(Phys_RageClothUpdate);
ms_ClothManager->Update( (clothManager::clothType) verletType );
PF_STOP(Phys_RageClothUpdate);
}
void CPhysics::WaitForVerletTasks()
{
PF_START_TIMEBAR_DETAIL("RageVerletWaitForTasks");
PF_START(Phys_RageVerletWaitForTasks);
ms_TasksManager->WaitForTasks();
PF_STOP(Phys_RageVerletWaitForTasks);
}
void CPhysics::VerletSimPostUpdate()
{
ms_ClothManager->PostUpdate();
ms_RopeManager->PostUpdate(); // empty func ... do something ?!
}
void CPhysics::SimUpdate(float TimeSeconds, int nSlice)
{
ms_bSimUpdateActive = true;
// Update Rage Physics
PF_START_TIMEBAR_DETAIL("RageSimUpdate");
PF_START(Phys_RageSimUpdate);
bool onlyPushOnLastUpdate = !ms_DisableOnlyPushOnLastUpdateForOneFrame && ms_OnlyPushOnLastUpdate && !ms_bInStuntMode;
ms_PhysSim->Update(TimeSeconds, !onlyPushOnLastUpdate || nSlice == ms_NumTimeSlices - 1);
PF_STOP(Phys_RageSimUpdate);
ms_bSimUpdateActive = false;
PF_START_TIMEBAR_DETAIL("IterateOverManifolds");
PF_START(Phys_Iterate);
IterateOverManifolds();
PF_STOP(Phys_Iterate);
PF_START_TIMEBAR_DETAIL("RageFragUpdate");
PF_START(Phys_RageFragUpdate);
FRAGMGR->SimUpdate(TimeSeconds);
#if __BANK
if(CPhysics::ms_bFragManagerUpdateEachSimUpdate)
{
FRAGMGR->Update(TimeSeconds,1);
}
#endif // __BANK
PF_STOP(Phys_RageFragUpdate);
PF_START_TIMEBAR_DETAIL("RageInstBehvrs");
PF_START(Phys_RagInstBehvrs);
ms_PhysSim->UpdateInstanceBehaviors(TimeSeconds);
PF_STOP(Phys_RagInstBehvrs);
if(CVehicleRecordingMgr::sm_bUpdateWithPhysics && !CVehicleRecordingMgr::sm_bUpdateBeforePreSimUpdate)
{
PF_START(Proc_VehRecording);
CVehicleRecordingMgr::RetrieveDataForThisFrame();
PF_STOP(Proc_VehRecording);
}
#if __DEV
if(sbSwitchPedToNM)
{
CTask* pTaskNM = rage_new CTaskNMBalance(3000, 6000, NULL, 0);
CPedDebugVisualiserMenu::SwitchPedToNMBehaviour(pTaskNM);
sbSwitchPedToNM = false;
}
#endif
}
#define MAX_RAGDOLLS_TO_SWITCH (32)
dev_float sfMinKnockOffVehicleImpact = 5.0f;
dev_float sfMinKnockOffVehicleImpactInStuntMode = 30000.0f;
dev_float dfBackEndPopUpMaxXRange = 0.3f;
dev_float dfBackEndPopUpMinXRange = 0.6f;
dev_float dfBackEndPopUpMinOffsetMulti = 0.25f;
void CPhysics::PostSimUpdate(int nLoop, float fTimeStep)
{
// the switch to ragdoll removes the animated inst from the physics world (and adds the ragdoll to the active list potentially)
// so save a list of the inst's that want to activate, and do it later.
phInst* aRagdollInstsToSwitch[MAX_RAGDOLLS_TO_SWITCH];
int nNumRagdollInstsToSwitch = 0;
CProjectile* aProjectiles[CProjectileManager::MAX_STORAGE];
int nNumProjectilesToProcess = 0;
ASSERT_ONLY(const int initialNumActive = CPhysics::GetLevel()->GetNumActive());
for (int levelIndex = CPhysics::GetLevel()->GetFirstActiveIndex(); levelIndex != phInst::INVALID_INDEX; levelIndex = CPhysics::GetLevel()->GetNextActiveIndex())
{
phInst* pInst = CPhysics::GetLevel()->GetInstance(levelIndex);
CEntity* pEntity = CPhysics::GetEntityFromInst(pInst);
if(pEntity)
{
if(pEntity->GetIsTypePed())
{
CPed* pPed = (CPed *)pEntity;
CCollisionHistory* pCollisionHistory = pPed->GetFrameCollisionHistory();
//run over all the currently active ragdolls
if(pInst == pPed->GetRagdollInst() && pPed->GetRagdollState() > RAGDOLL_STATE_ANIM_DRIVEN && !pPed->GetIsInPopulationCache())
{
const CCollisionRecord* pMostSignificantRecord = pCollisionHistory->GetMostSignificantCollisionRecord();
// has a collision with another object happened this frame (e.g. hit by a car / flying object)
if(pMostSignificantRecord)
{
// Process the damage and, if necessary, generate an appropriate ragdoll event based on the impact.
pPed->GetPedIntelligence()->GetEventScanner()->GetCollisionEventScanner().ProcessRagdollImpact(pPed,
pCollisionHistory->GetCollisionImpulseMagSum(),
pMostSignificantRecord->m_pRegdCollisionEntity.Get(),
pMostSignificantRecord->m_MyCollisionNormal,
pMostSignificantRecord->m_MyCollisionComponent,
pMostSignificantRecord->m_OtherCollisionMaterialId);
pCollisionHistory->SetZeroCollisionImpulseMagSum();
Assertf(pPed->VerifyRagdollHandled(), "A ragdoll has been activated by a collision record, but ProcessRagdollImpact didn't return a ragdoll event!");
if(!pPed->GetUsingRagdoll())
{
if(nNumRagdollInstsToSwitch < MAX_RAGDOLLS_TO_SWITCH)
{
aRagdollInstsToSwitch[nNumRagdollInstsToSwitch] = pInst;
nNumRagdollInstsToSwitch++;
}
else
Assertf(false, "ran out of ragdolls to switch on");
}
}
else if(pPed->GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE)
{
// No impact recorded, but the ragdoll has been activated during this physics step anyway.
// if a ragdoll event hasn't been provided yet, we must add a default one here.
pPed->GetPedIntelligence()->GetEventScanner()->GetCollisionEventScanner().ProcessRagdollImpact(
pPed, 0.0f, NULL, Vector3(Vector3::ZeroType), 0, MATERIALMGR.GetMaterialId(MATERIALMGR.GetDefaultMaterial()));
Assertf(pPed->VerifyRagdollHandled(), "A ragdoll has been newly activated, but ProcessRagdollImpact didn't return a ragdoll event!");
pCollisionHistory->SetZeroCollisionImpulseMagSum();
if(!pPed->GetUsingRagdoll())
{
if(nNumRagdollInstsToSwitch < MAX_RAGDOLLS_TO_SWITCH)
{
aRagdollInstsToSwitch[nNumRagdollInstsToSwitch] = pInst;
nNumRagdollInstsToSwitch++;
}
else
Assertf(false, "ran out of ragdolls to switch on");
}
}
else
{
// The ragdoll should have been already activated and switched to nm
Assertf(pPed->VerifyRagdollHandled(), "Unmanaged active ragdoll detected!");
}
}
}
else if(pEntity->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(pEntity);
const CCollisionHistory* pCollisionHistory = pVehicle->GetFrameCollisionHistory();
// Want all bike impacts to be processed
bool easyToLand = pVehicle->m_easyToLand &&
( !CPhysics::ms_bInArenaMode ||
CVehicle::GetVehicleOrientation( *pVehicle ) != CVehicle::VO_UpsideDown ||
pVehicle->GetNumContactWheels() > 0 );
bool bIsLikeABikeForPassengerKnockOff = !easyToLand &&
( ( pVehicle->InheritsFromBike() && static_cast< CBike* >( pVehicle )->m_fOffStuntTime == 0.0f ) ||
pVehicle->InheritsFromQuadBike() ||
pVehicle->GetIsJetSki() ||
pVehicle->InheritsFromAmphibiousQuadBike() );
bool bIngoreUpsideDownCheck = easyToLand &&
pVehicle->InheritsFromAmphibiousQuadBike() &&
!pVehicle->IsInAir();
float fKnockoffMinImpulse = bIsLikeABikeForPassengerKnockOff ? 0.0f : sfMinKnockOffVehicleImpact;
const CCollisionRecord* pMostSignificantRecord = pCollisionHistory->GetMostSignificantCollisionRecord();
if( easyToLand ||
( pVehicle->InheritsFromBike() &&
!pVehicle->HasGlider() &&
pVehicle->GetSpecialFlightModeRatio() > 0.5f ) )
{
fKnockoffMinImpulse = sfMinKnockOffVehicleImpactInStuntMode;
}
else if( pMostSignificantRecord &&
pMostSignificantRecord->m_pRegdCollisionEntity.Get() &&
pMostSignificantRecord->m_pRegdCollisionEntity.Get()->GetIsTypeVehicle() &&
static_cast< CVehicle* >( pMostSignificantRecord->m_pRegdCollisionEntity.Get() )->InheritsFromBike() )
{
static dev_float minImpulseBikeVsBike = 100.0f;
fKnockoffMinImpulse = minImpulseBikeVsBike;
}
audVehicleAudioEntity * audio = pVehicle->GetVehicleAudioEntity();
if(naVerifyf(audio, "Vehicle missing audio entity during post sim update"))
{
audio->GetCollisionAudio().PostProcessImpacts();
}
static dev_float sfMinKnockOffVehicleImpactWithRampCar = 30000.0f;
if( bIsLikeABikeForPassengerKnockOff && pVehicle->GetDriver() && pVehicle->GetDriver()->IsAPlayerPed() && pMostSignificantRecord )
{
CEntity* pCollisionEntity = pMostSignificantRecord->m_pRegdCollisionEntity.Get();
if(pCollisionEntity->GetIsTypeVehicle())
{
CVehicle* pCollisionVehicle = static_cast<CVehicle*>(pCollisionEntity);
if(pCollisionVehicle->HasRamp() )
{
fKnockoffMinImpulse = sfMinKnockOffVehicleImpactWithRampCar;
}
}
}
if(pMostSignificantRecord
&& (pCollisionHistory->GetCollisionImpulseMagSum() > fKnockoffMinImpulse * pVehicle->GetMass() || ( !bIngoreUpsideDownCheck && pVehicle->GetTransform().GetC().GetZf() < 0.0f ) ) )
{
bool bFellOff = false;
for( s32 i = 0; i < pVehicle->GetSeatManager()->GetMaxSeats(); i++ )
{
CPed* pRiderPed = pVehicle->GetSeatManager()->GetPedInSeat(i);
if(pRiderPed)
{
if(pRiderPed->GetPedIntelligence()->GetEventScanner()->GetCollisionEventScanner().ProcessVehicleImpact(pRiderPed,
pMostSignificantRecord->m_pRegdCollisionEntity.Get(),
pCollisionHistory->GetCollisionImpulseMagSum(),
pMostSignificantRecord->m_MyCollisionNormal,
pMostSignificantRecord->m_MyCollisionComponent, bFellOff, i))
{
// if one ped falls off, force the other to as well (we do the driver first, so this just means to avoid expensive checks we already did for the driver)
bFellOff = true;
if(!pRiderPed->GetUsingRagdoll())
{
// ok we want to get knocked off and switch to ragdoll - do it after this loop
if(nNumRagdollInstsToSwitch < MAX_RAGDOLLS_TO_SWITCH)
{
aRagdollInstsToSwitch[nNumRagdollInstsToSwitch] = pRiderPed->GetRagdollInst();
nNumRagdollInstsToSwitch++;
}
else
Assertf(false, "ran out of ragdolls to switch on");
}
Assertf(pRiderPed->VerifyRagdollHandled(), "A rider ragdoll has been activated by a vehicle collision, but no ragdoll event was generated!");
}
}
}
}
// Make the back end of the car tilting up a bit when receives head on collision
if(pVehicle->GetVehicleType() == VEHICLE_TYPE_CAR && pVehicle->pHandling->GetCarHandlingData() && !pVehicle->GetIsAnyFixedFlagSet() && pVehicle->GetCollider() && pMostSignificantRecord)
{
const Matrix34& matMyMatrix = RCC_MATRIX34(pVehicle->GetMatrixRef());
// if car is running forward
if(VEC3V_TO_VECTOR3(pVehicle->GetCollider()->GetLastVelocity()).Dot(matMyMatrix.b) > 0.0f)
{
if(pMostSignificantRecord->m_MyCollisionPosLocal.y > 0.0f)
{
CEntity *pHitEnt = pMostSignificantRecord->m_pRegdCollisionEntity.Get();
if((pHitEnt && (pHitEnt->GetIsTypeVehicle() && ((CVehicle *)pHitEnt)->GetVehicleType() == VEHICLE_TYPE_CAR) && !pHitEnt->GetIsAnyFixedFlagSet()) ||
((pHitEnt->GetCurrentPhysicsInst() && pHitEnt->GetCurrentPhysicsInst()->GetClassType() == PH_INST_FRAG_BUILDING) || pHitEnt->GetCurrentPhysicsInst()->GetClassType() == PH_INST_BUILDING))
{
Vector3 vTransferredImpulseOffset = pMostSignificantRecord->m_MyCollisionPosLocal;
vTransferredImpulseOffset.x *= -1.0f;
vTransferredImpulseOffset.y *= -1.0f;
matMyMatrix.Transform3x3(vTransferredImpulseOffset); // transform to global rotational space
if(vTransferredImpulseOffset.Mag2() > square(pVehicle->GetBoundRadius()))
{
vTransferredImpulseOffset *= pVehicle->GetBoundRadius() / vTransferredImpulseOffset.Mag();
}
Vector3 vTransferredImpulse(VEC3_ZERO);
float fImpulseOffsetMult = 1.0f - Clamp((Abs(pMostSignificantRecord->m_MyCollisionPosLocal.x) - dfBackEndPopUpMinXRange) / (dfBackEndPopUpMaxXRange - dfBackEndPopUpMinXRange), 0.0f, 1.0f);
fImpulseOffsetMult = dfBackEndPopUpMinOffsetMulti + (1.0f - dfBackEndPopUpMinOffsetMulti) * fImpulseOffsetMult;
if(pHitEnt && pHitEnt->GetIsTypeVehicle() && ((CVehicle *)pHitEnt)->GetVehicleType() == VEHICLE_TYPE_CAR)
{
if( !( static_cast< CVehicle* >( pHitEnt )->GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMMING_SCOOP ) ) )
{
vTransferredImpulse.z = pMostSignificantRecord->m_fCollisionImpulseMag * pVehicle->pHandling->GetCarHandlingData()->m_fBackEndPopUpCarImpulseMult * fImpulseOffsetMult;
}
}
else
{
vTransferredImpulse.z = pMostSignificantRecord->m_fCollisionImpulseMag * pVehicle->pHandling->GetCarHandlingData()->m_fBackEndPopUpBuildingImpulseMult * fImpulseOffsetMult;
}
vTransferredImpulse.z = Min(vTransferredImpulse.z, pVehicle->pHandling->GetCarHandlingData()->m_fBackEndPopUpMaxDeltaSpeed * pVehicle->GetMass());
pVehicle->ApplyImpulse(vTransferredImpulse, vTransferredImpulseOffset);
Vector3 vCGPosition = VEC3V_TO_VECTOR3(pVehicle->GetVehiclePosition());
if(CPhysics::GetLevel()->IsInactive(pVehicle->GetVehicleFragInst()->GetLevelIndex()) && pVehicle->GetCollider())
{
vCGPosition = VEC3V_TO_VECTOR3(pVehicle->GetCollider()->GetPosition());
}
// Apply equal but opposite impulse to the centre of mass to prevent car being launched off
pVehicle->ApplyImpulse(vTransferredImpulse * -1.0f, VEC3_ZERO);
}
}
}
}
// Check for an opportunity to function as a simple collider
const phCollider* collider = NULL;
if (pVehicle->GetVehicleFragInst() && pVehicle->GetVehicleFragInst()->GetCacheEntry() && pVehicle->GetVehicleFragInst()->GetCacheEntry()->GetHierInst())
{
collider = pVehicle->GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider;
if(collider)
{
int initialType = collider->GetType();
pVehicle->GetVehicleFragInst()->CheckSimpleColliderMode();
int finalType = collider->GetType();
if(finalType == phCollider::TYPE_RIGID_BODY && initialType != phCollider::TYPE_RIGID_BODY && !CVehicle::ms_bAlwaysUpdateCompositeBound)
{
pVehicle->CalculateNonArticulatedMaximumExtents();
}
}
}
}
else if(pEntity->GetIsTypeObject())
{
CObject* pObj = static_cast<CObject*>(pEntity);
CProjectile* pProj = pObj->GetAsProjectile();
if(pProj)
{
aProjectiles[nNumProjectilesToProcess++] = pProj;
}
}
}
// If this assert fails you need to either a) figure out who's changing the number of active objects and make them stop doing that or b) cache
// the active objects beforehand and use the cached list like it done in the loop below.
// Assertf(CPhysics::GetLevel()->GetNumActive() <= initialNumActive, "Somebody increased the number of active object during iteration!");
Assertf(CPhysics::GetLevel()->GetNumActive() >= initialNumActive, "Somebody decreased the number of active object during iteration!");
}
for(int i=0; i<nNumRagdollInstsToSwitch; i++)
{
CEntity* pEntity = CPhysics::GetEntityFromInst(aRagdollInstsToSwitch[i]);
CPed* pPed = (CPed *)pEntity;
if (pPed->GetIsInPopulationCache())
continue;
Assertf(pPed->VerifyRagdollHandled(), "Performing a deferred activation of the ragdoll, but no task or event exists to handle it!");
// special case function that checks for the specific RAGDOLL_STATE_PHYS_ACTIVATE status
// should only be called here.
// Our switch to nm event will have been sent at the point where the above flag was set.
pPed->SwitchToRagdollPostPhysicsSimUpdate();
// get clone peds to try to balance for starters when their ragdoll gets activated
if(pPed->IsNetworkClone() && pPed->GetRagdollInst() && pPed->GetRagdollInst()->GetNMAgentID() > -1)
{
ART::MessageParams msg;
msg.addBool(NMSTR_PARAM(NM_START), true);
pPed->GetRagdollInst()->PostARTMessage(NMSTR_MSG(NM_BALANCE_MSG), &msg);
}
}
for(int j=0; j<nNumProjectilesToProcess; j++)
{
aProjectiles[j]->PostSimUpdate();
}
// Cache off a list of the active objects first rather than use the iterator 'live' because the UpdateEntityFromPhysics() call below can deactivate
// objects which will mess with the active object list and confuse the iteration.
const phLevelNew *physicsLevel = CPhysics::GetLevel();
const int numActive = physicsLevel->GetNumActive();
int *activeLevelIndices = Alloca(int, numActive);
for(int activeIndex = 0; activeIndex < numActive; ++activeIndex)
{
activeLevelIndices[activeIndex] = physicsLevel->GetActiveLevelIndex(activeIndex);
}
// need to update entity matrices after physics update
int activeIndex = 0;
int levelIndex = activeLevelIndices[0];
for (; activeIndex < numActive; ++activeIndex, levelIndex = activeLevelIndices[activeIndex])
{
phInst* pInst = CPhysics::GetLevel()->GetInstance(levelIndex);
CEntity* pEntity = CPhysics::GetEntityFromInst(pInst);
if(pEntity)
{
// run the network blender on network clones on the last physics update loop
if(CPhysics::GetIsLastTimeSlice(nLoop))
{
netObject *networkObject = NetworkUtils::GetNetworkObjectFromEntity(pEntity);
if (networkObject && networkObject->IsClone() && networkObject->GetNetBlender())
{
if (networkObject->CanBlend())
{
networkObject->GetNetBlender()->ProcessPostPhysics();
}
}
}
// Still want to call UpdateEntityFromPhysics for asleep insts because they can move a small amount each frame
// e.g. if they're getting pushed. We're expecting them to become inactive soon after going to sleep anyway.
// if(!CPhysics::GetSimulator()->GetCollider(levelIndex)->GetSleep()
// || !CPhysics::GetSimulator()->GetCollider(levelIndex)->GetSleep()->IsAsleep())
if(Verifyf(pEntity->GetIsPhysical(), "Active entity is not physical."))
{
// update entity's matrix
static_cast<CPhysical*>(pEntity)->UpdateEntityFromPhysics(pInst, nLoop);
#if ENABLE_PIX_TAGGING
// time ped and vehicle probes separately
if(pEntity->GetIsTypePed())
PIXBeginC(1, 0xFFFF,"CPed::ProcessProbes");
else if(pEntity->GetIsTypeVehicle())
PIXBeginC(1, 0xFFFF,"CVehicle::ProcessProbes");
#endif
// do peds standing, vehicle suspension lines etc..
((CPhysical *)pEntity)->ProcessProbes(MAT34V_TO_MATRIX34(pEntity->GetMatrix()));
#if ENABLE_PIX_TAGGING
// end timers
if(pEntity->GetIsTypePed() || pEntity->GetIsTypeVehicle())
PIXEnd();
#endif
}
if(pEntity->GetIsPhysical() && ((CPhysical*)pEntity)->GetNoCollisionEntity())
{
((CPhysical*)pEntity)->ResetNoCollision();
}
// Handle the adding of objects to the pathfinder, which were not added previously
// but have not moved/reorientated since then.
if(pEntity->WasUnableToAddToPathServer())
{
CPathServerGta::MaybeAddDynamicObject(pEntity);
}
}
}
FRAGNMASSETMGR->StepPhase2(fTimeStep); //fwTimer::GetTimeStep());
CExplosionManager::Update(fTimeStep);
CPropellerCollisionProcessor::GetInstance().ProcessImpacts(fTimeStep);
// Switch sleeping corpses to animated
for (int i = 0; i < g_SettledPeds.GetCount(); i++)
{
CPed* pPed = g_SettledPeds[i];
if (pPed)
{
Assert(pPed->GetRagdollInst());
pPed->DeactivatePhysicsAndDealWithRagdoll(true);
}
}
g_SettledPeds.Reset();
}
void CPhysics::CommitDeferredOctreeUpdates()
{
GetLevel()->CommitDeferredOctreeUpdates();
}
void CPhysics::ProcessDeferredCompositeBvhUpdates()
{
#if LEVELNEW_ENABLE_DEFERRED_COMPOSITE_BVH_UPDATE
GetLevel()->ProcessDeferredCompositeBvhUpdates();
#endif // LEVELNEW_ENABLE_DEFERRED_COMPOSITE_BVH_UPDATE
}
// name: IterateManifold
// description: Extract information from one manifold for game events.
// notes:
// 1. Primary purposes are expected to be generating sounds and particles, replacing what was once
// done in PreApplyImpacts.
// 2. Each composite part of every object will get its own manifold when colliding with something.
// 3. Manifolds are allocated as soon as a pair of objects overlaps in the broadphase, whether they
// are colliding or not.
void CPhysics::IterateManifold(phManifold* manifold, CEntity* pEntityA, CEntity* pEntityB, phInst* pInstA, phInst* pInstB)
{
// Here is where you would put code that is related to the entire manifold (aka contact patch)
// between the two objects. Some things you can find out at this level:
// static vs. sliding friction (scraping vs. rolling or bouncing)
// the two objects involved in the collision or constraint, and their component part numbers
// which contact point was most recently addedgta
// a composite of the normal and tangential forces over the manifold
// s32 newestContactPointIndex = manifold->GetNewestContactPoint();
// if (newestContactPointIndex==-1)
// {
// return;
// }
Assert(pInstA && pInstA->IsInLevel() && pInstA->GetLevelIndex() == manifold->GetLevelIndexA());
Assert(pInstB && pInstB->IsInLevel() && pInstB->GetLevelIndex() == manifold->GetLevelIndexB());
const ScalarV svOne(V_ONE);
// First contact loop, find the accumulated and average impulse
ScalarV vAccumImpulse = ScalarV(V_ZERO);
ScalarV vAvgActiveImpulse = ScalarV(V_ZERO);
ScalarV numContactsActive = ScalarV(V_ZERO);
bool anyActiveContacts = false;
int numContacts = manifold->GetNumContacts();
for (int i=0; i<numContacts; i++)
{
phContact& contact = manifold->GetContactPoint(i);
if (contact.IsContactActive())
{
// accumulate the impulses across all active contacts
vAccumImpulse += Abs(contact.GetAccumImpulse());
numContactsActive = Add(numContactsActive,svOne);
anyActiveContacts = true;
}
}
// Only continue if we found active contacts
if (anyActiveContacts)
{
vAvgActiveImpulse = InvScale(vAccumImpulse,numContactsActive);
Mat34V transformA;
Mat34V transformB;
// Check that the component indices makes sense before continuing. The indices could be invalid
// if the object changes LOD or breaks without calling phContactMgr::ReplaceInstanceComponent or RemoveAllContactsWithInstance.
// This shouldn't cost that much since phManifold::GetLocalToWorldTransforms has to look at the composite anyways.
const phBound* boundA = pInstA->GetArchetype()->GetBound();
if(boundA->GetType() == phBound::COMPOSITE)
{
int componentA = manifold->GetComponentA();
const phBoundComposite* compositeBoundA = static_cast<const phBoundComposite*>(boundA);
if(!Verifyf(componentA >= 0 && componentA < compositeBoundA->GetMaxNumBounds() && compositeBoundA->GetBound(componentA), "Invalid component index %i for '%s' on manifold. Num Components: %i.", componentA, pInstA->GetArchetype()->GetFilename(), compositeBoundA->GetNumBounds()))
{
return;
}
Transform(transformA,pInstA->GetMatrix(),compositeBoundA->GetCurrentMatrix(componentA));
}
else
{
transformA = pInstA->GetMatrix();
}
const phBound* boundB = pInstB->GetArchetype()->GetBound();
if(boundB->GetType() == phBound::COMPOSITE)
{
int componentB = manifold->GetComponentB();
const phBoundComposite* compositeBoundB = static_cast<const phBoundComposite*>(boundB);
if(!Verifyf(componentB >= 0 && componentB < compositeBoundB->GetMaxNumBounds() && compositeBoundB->GetBound(componentB), "Invalid component index %i for '%s' on manifold. Num Components: %i.", componentB, pInstB->GetArchetype()->GetFilename(), compositeBoundB->GetNumBounds()))
{
return;
}
Transform(transformB,pInstB->GetMatrix(),compositeBoundB->GetCurrentMatrix(componentB));
}
else
{
transformB = pInstB->GetMatrix();
}
#if DEFORMABLE_OBJECTS
bool shouldProcessDeformation = pEntityA != pEntityB &&
((pEntityA && pEntityA->GetIsTypeObject() && static_cast<CObject*>(pEntityA)->HasDeformableData())
|| (pEntityB && pEntityB->GetIsTypeObject() && static_cast<CObject*>(pEntityB)->HasDeformableData()));
#endif // DEFORMABLE_OBJECTS
// Second contact loop (requires average and accumulated impulse)
for(int contactIndex = 0; contactIndex < manifold->GetNumContacts(); contactIndex++)
{
phContact& contact = manifold->GetContactPoint(contactIndex);
// This is where you would put code that needs to operate on the actual points of contact within
// the manifold. You will find this information here:
// the positions of contact points between the two objects
// the lifetime (in sim iterations) of each contact
// the normal direction of the contact
// whether the contact is active (e.g. ped probe contacts will be disabled)
// the element number (polygon index) when colliding with world polygons
// the depth of the contact
// the impulse computed by the force solver most recently
// the push applied by the push solver most recently
// Some test code to make sure this is working
// int youth = Max(0, 255 - contact.m_LifeTime);
// grcDebugDraw::Sphere(contact.GetWorldPosA(), 0.02f, Color32(youth, 0, 255 - youth), false);
if(contact.IsContactActive())
{
contact.RefreshContactPoint(RCC_MATRIX34(transformA), RCC_MATRIX34(transformB), manifold);
// check the validity of the normal
#if __ASSERT
float normalMag = Mag(contact.GetWorldNormal()).Getf();
if(normalMag < 0.97f || normalMag > 1.03f)
{
manifold->AssertContactNormalVerbose(contact, __FILE__, __LINE__);
}
#endif // __ASSERT
// process vfx per contact
#if __BANK
if(g_vfxMaterial.GetColnVfxProcessPerContact())
{
// only process non ped collisions
if (pInstA->GetClassType()!=PH_INST_PED &&
pInstB->GetClassType()!=PH_INST_PED)
{
// don't process any collisions with inst that don't have an entity
if (pEntityA==NULL || pEntityB==NULL)
{
continue;
}
// don't process any collisions with non frag insts when there is a frag inst
if ((pEntityA->m_nFlags.bIsFrag && !IsFragInst(pInstA)) ||
(pEntityB->m_nFlags.bIsFrag && !IsFragInst(pInstB)))
{
continue;
}
VfxCollisionInfo_s vfxColnInfo;
vfxColnInfo.pEntityA = pEntityA;
vfxColnInfo.pEntityB = pEntityB;
vfxColnInfo.componentIdA = manifold->GetComponentA();
vfxColnInfo.componentIdB = manifold->GetComponentB();
vfxColnInfo.pColliderA = manifold->GetColliderA();
vfxColnInfo.pColliderB = manifold->GetColliderB();
vfxColnInfo.vRelVelocity = phContact::ComputeRelVelocity( contact.GetWorldPosA(), contact.GetWorldPosB(), manifold);
vfxColnInfo.vWorldPosA = contact.GetWorldPosA();
vfxColnInfo.vWorldPosB = contact.GetWorldPosB();
vfxColnInfo.vWorldNormalA = contact.GetWorldNormal();
vfxColnInfo.vWorldNormalB = -contact.GetWorldNormal();
vfxColnInfo.mtlIdA = PGTAMATERIALMGR->UnpackMtlId(contact.GetMaterialIdA());
vfxColnInfo.mtlIdB = PGTAMATERIALMGR->UnpackMtlId(contact.GetMaterialIdB());
vfxColnInfo.vAccumImpulse = Abs(contact.GetAccumImpulse());
if (g_vfxMaterial.GetColnVfxRecomputePositions())
{
vfxColnInfo.vWorldPosA = UnTransformOrtho(pInstA->GetMatrix(), contact.GetWorldPosA());
vfxColnInfo.vWorldPosA = VECTOR3_TO_VEC3V(vfxColnInfo.pEntityA->TransformIntoWorldSpace(RCC_VECTOR3(vfxColnInfo.vWorldPosA)));
vfxColnInfo.vWorldPosB = UnTransformOrtho(pInstB->GetMatrix(), contact.GetWorldPosB());
vfxColnInfo.vWorldPosB = VECTOR3_TO_VEC3V(vfxColnInfo.pEntityB->TransformIntoWorldSpace(RCC_VECTOR3(vfxColnInfo.vWorldPosB)));
}
CPhysics::ProcessCollisionVfxandSfx(vfxColnInfo);
}
}
#endif // __BANK
// Do this after VFX because this can actually invalidate the contact we're on,
// if an instance is deleted in here (e.g. from disappears when dead).
CPhysics::ProcessCollisionData(contact, *manifold, pEntityA, pEntityB, vAvgActiveImpulse);
#if DEFORMABLE_OBJECTS
if(shouldProcessDeformation)
{
CDeformableObjectManager& refDeformableObjMgr = CDeformableObjectManager::GetInstance();
// Need to process each individual contact's position to see if it falls within the sphere of influence of
// a deformable bone on either instance of the collision pair.
refDeformableObjMgr.ProcessDeformableBones(pInstA, Scale(contact.GetWorldNormal(0), vAvgActiveImpulse),
contact.GetWorldPosA(), pEntityB, manifold->GetComponentA(), manifold->GetComponentB());
refDeformableObjMgr.ProcessDeformableBones(pInstB, Scale(contact.GetWorldNormal(1), vAvgActiveImpulse),
contact.GetWorldPosB(), pEntityA, manifold->GetComponentB(), manifold->GetComponentA());
}
#endif // DEFORMABLE_OBJECTS
}
} // Loop over individual contacts.
#if __BANK
if ((g_vfxMaterial.GetColnVfxDisableTimeSlice1() && CPhysics::GetCurrentTimeSlice()==0) ||
(g_vfxMaterial.GetColnVfxDisableTimeSlice2() && CPhysics::GetCurrentTimeSlice()==1))
{
return;
}
#endif // __BANK
// process vfx per manifold
#if __BANK
if (g_vfxMaterial.GetColnVfxProcessPerContact()==false)
#endif // __BANK
{
// A non-zero accumulated impulse is required
if(vAccumImpulse.Getf()>0.0f)
{
// check if we should process this collision
bool shouldProcessVfx = false, shouldProcessAudio = false;
// only process collisions involving ped capsules if the ped is swimming and the camera is underwater
bool isPedCapsuleColn = false;
if (pInstA->GetClassType()==PH_INST_PED)
{
isPedCapsuleColn = true;
if (pEntityA && CVfxHelper::GetGameCamWaterDepth()>0.0f)
{
CPed* pPed = static_cast<CPed*>(pEntityA);
if (pPed->GetIsSwimming())
{
shouldProcessVfx = true;
}
}
}
else if (pInstB->GetClassType()==PH_INST_PED)
{
isPedCapsuleColn = true;
if (pEntityB && CVfxHelper::GetGameCamWaterDepth()>0.0f)
{
CPed* pPed = static_cast<CPed*>(pEntityB);
if (pPed->GetIsSwimming())
{
shouldProcessVfx = true;
}
}
}
// non ped capsule collision should all be processed
else
{
shouldProcessVfx = true;
shouldProcessAudio = true;
}
if( (pEntityA && pEntityA->GetIsTypeObject() && !pInstA->GetInstFlag(phInst::FLAG_NEVER_ACTIVATE)) || (pEntityB && pEntityB->GetIsTypeObject() && !pInstB->GetInstFlag(phInst::FLAG_NEVER_ACTIVATE)) )
{
shouldProcessAudio = true;
}
if(pEntityA && pEntityA->GetIsTypePed() && ((CPed*)pEntityA)->IsLocalPlayer() && pEntityB && (pEntityB->GetIsTypeObject() || pEntityB->GetIsTypePed() || pEntityB->GetIsTypeVehicle()))
{
g_CollisionAudioEntity.HandlePlayerEvent(pEntityB);
}
if(pEntityB && pEntityB->GetIsTypePed() && ((CPed*)pEntityB)->IsLocalPlayer() && pEntityA && (pEntityA->GetIsTypeObject() || pEntityA->GetIsTypePed() || pEntityA->GetIsTypeVehicle()))
{
g_CollisionAudioEntity.HandlePlayerEvent(pEntityA);
}
// the audio want to detect shards of glass colliding with the ground
bool breakableCollisionA = (pInstB->GetClassType() == PH_INST_MAPCOL && pInstA->GetArchetype()->GetTypeFlags() == ArchetypeFlags::GTA_GLASS_TYPE);
bool breakableCollisionB = (pInstA->GetClassType() == PH_INST_MAPCOL && pInstB->GetArchetype()->GetTypeFlags() == ArchetypeFlags::GTA_GLASS_TYPE);
// check for not wanting to continue processing
if (shouldProcessVfx || shouldProcessAudio)
{
// don't process any collisions with insts that don't have an entity (unless they are breakable glass)
if ((pEntityA==NULL || pEntityB==NULL) && !breakableCollisionA && !breakableCollisionB)
{
shouldProcessVfx = false;
shouldProcessAudio = false;
}
// don't process any collisions with non frag insts when there is a frag inst (unless this is a ped capsule collision)
else if (isPedCapsuleColn==false &&
((pEntityA && (pEntityA->m_nFlags.bIsFrag && !IsFragInst(pInstA))) ||
(pEntityB && (pEntityB->m_nFlags.bIsFrag && !IsFragInst(pInstB)))))
{
shouldProcessVfx = false;
shouldProcessAudio = false;
}
}
if (shouldProcessVfx || shouldProcessAudio)
{
// Third contact loop for audio/vfx only values. Only do this if necessary.
Vec3V vAvgNewWorldPosA = Vec3V(V_ZERO);
Vec3V vAvgNewWorldPosB = Vec3V(V_ZERO);
Vec3V vAvgNewWorldNormal = Vec3V(V_ZERO);
phMaterialMgr::Id firstNewMtlIdA = (phMaterialMgr::Id)0;
phMaterialMgr::Id firstNewMtlIdB = (phMaterialMgr::Id)0;
ScalarV numContactsNew = ScalarV(V_ZERO);
bool anyNewContacts = false;
for (int contactIndex=0; contactIndex<manifold->GetNumContacts(); contactIndex++)
{
phContact& contact = manifold->GetContactPoint(contactIndex);
// accumulate the positions and normals across all new contacts
if (contact.IsContactActive())
{
if (!anyNewContacts)
{
anyNewContacts = true;
// set up common data
firstNewMtlIdA = PGTAMATERIALMGR->UnpackMtlId(contact.GetMaterialIdA());
firstNewMtlIdB = PGTAMATERIALMGR->UnpackMtlId(contact.GetMaterialIdB());
}
vAvgNewWorldPosA += contact.GetWorldPosA();
vAvgNewWorldPosB += contact.GetWorldPosB();
vAvgNewWorldNormal += contact.GetWorldNormal();
numContactsNew = Add(numContactsNew,svOne);
}
}
// only process if new contacts were found
if (anyNewContacts)
{
// average out the positions and normals
ScalarV invNumContactsNew = Invert(numContactsNew);
vAvgNewWorldPosA *= invNumContactsNew;
vAvgNewWorldPosB *= invNumContactsNew;
vAvgNewWorldNormal = NormalizeSafe(vAvgNewWorldNormal, Vec3V(V_X_AXIS_WZERO));
Vec3V vAvgNewRelVelocity = phContact::ComputeRelVelocity(vAvgNewWorldPosA,vAvgNewWorldPosB,manifold);
// only process if the accumulated impulse, relative velocity and world normal are non zero
// a valid matrix will not be able to be calculated if they aren't
if (IsZeroAll(vAvgNewRelVelocity)==false)
{
VfxCollisionInfo_s vfxColnInfo;
vfxColnInfo.pEntityA = pEntityA;
vfxColnInfo.pEntityB = pEntityB;
vfxColnInfo.componentIdA = manifold->GetComponentA();
vfxColnInfo.componentIdB = manifold->GetComponentB();
vfxColnInfo.pColliderA = manifold->GetColliderA();
vfxColnInfo.pColliderB = manifold->GetColliderB();
vfxColnInfo.vRelVelocity = vAvgNewRelVelocity;
vfxColnInfo.vWorldPosA = vAvgNewWorldPosA;
vfxColnInfo.vWorldPosB = vAvgNewWorldPosB;
vfxColnInfo.vWorldNormalA = vAvgNewWorldNormal;
vfxColnInfo.vWorldNormalB = -vAvgNewWorldNormal;
vfxColnInfo.mtlIdA = firstNewMtlIdA;
vfxColnInfo.mtlIdB = firstNewMtlIdB;
vfxColnInfo.vAccumImpulse = vAccumImpulse;
#if __BANK
if (g_vfxMaterial.GetColnVfxRecomputePositions())
{
vfxColnInfo.vWorldPosA = UnTransformOrtho(pInstA->GetMatrix(), vAvgNewWorldPosA);
vfxColnInfo.vWorldPosA = VECTOR3_TO_VEC3V(vfxColnInfo.pEntityA->TransformIntoWorldSpace(RCC_VECTOR3(vfxColnInfo.vWorldPosA)));
vfxColnInfo.vWorldPosB = UnTransformOrtho(pInstB->GetMatrix(), vAvgNewWorldPosB);
vfxColnInfo.vWorldPosB = VECTOR3_TO_VEC3V(vfxColnInfo.pEntityB->TransformIntoWorldSpace(RCC_VECTOR3(vfxColnInfo.vWorldPosB)));
}
#endif
CPhysics::ProcessCollisionVfxandSfx(vfxColnInfo, shouldProcessVfx);
g_CollisionAudioEntity.UpdateManifold(vfxColnInfo, *manifold, breakableCollisionA, breakableCollisionB);
}
}
}
}
}
}
}
bool ValidateAndRefreshRootManifold(phManifold* pManifold, phInst*& pInstA, phInst*& pInstB, CEntity*& pEntityA, CEntity*& pEntityB)
{
const u32 levelIndexA = pManifold->GetLevelIndexA();
const u32 levelIndexB = pManifold->GetLevelIndexB();
if(levelIndexA != phInst::INVALID_INDEX && levelIndexB != phInst::INVALID_INDEX)
{
// Make sure neither instance has been removed from the level
if(PHLEVEL->IsLevelIndexGenerationIDCurrent(levelIndexA,pManifold->GetGenerationIdA()) && PHLEVEL->IsLevelIndexGenerationIDCurrent(levelIndexB,pManifold->GetGenerationIdB()))
{
Assert(pManifold->GetInstanceA() && pManifold->GetInstanceB());
Assert((pManifold->GetInstanceA() == PHLEVEL->GetInstance(levelIndexA)) && (pManifold->GetInstanceB() == PHLEVEL->GetInstance(levelIndexB)));
pInstA = pManifold->GetInstanceA();
pInstB = pManifold->GetInstanceB();
pEntityA = CPhysics::GetEntityFromInst(pInstA);
pEntityB = CPhysics::GetEntityFromInst(pInstB);
// Make sure we aren't looking at stale collider pointers. We could skip over these stale manifolds but since the simulator won't skip them
// it might cause inconsistencies.
pManifold->RefreshColliderPointers();
return true;
}
}
return false;
}
// name: IterateOverManifolds
// description: Go over all the collisions in the physics, extracting information for game events.
void CPhysics::IterateOverManifolds()
{
#if USER_JBN
static bool g_DisableIterateOverManifolds = false;
if (g_DisableIterateOverManifolds)
return;
#endif // USER_JBN
phOverlappingPairArray::PairArray& pairArray = GetSimulator()->GetOverlappingPairArray()->pairs;
int numPairs = pairArray.GetCount();
// Go over all the pairs of objects overlapping in the broadphase
for(int pairIndex = 0; pairIndex < numPairs; ++pairIndex)
{
// A pair will only have a manifold if the are actively colliding
if (phManifold* manifold = pairArray[pairIndex].manifold)
{
if(!manifold->IsConstraint())
{
phInst* pInstA = NULL;
phInst* pInstB = NULL;
CEntity* pEntityA = NULL;
CEntity* pEntityB = NULL;
if (manifold->CompositeManifoldsEnabled())
{
bool isManifoldKnownToBeValid = false;
for (int manifoldIndex = 0; manifoldIndex < manifold->GetNumCompositeManifolds(); ++manifoldIndex)
{
phManifold* compositeManifold = manifold->GetCompositeManifold(manifoldIndex);
if(compositeManifold->GetNumContacts()>0)
{
if(!isManifoldKnownToBeValid)
{
// Try to wait as long as possible to check for a valid manifold. It's very expensive looking stuff up in the phLevel/phSimulator and a significant number of
// manifolds have 0 contacts.
if(ValidateAndRefreshRootManifold(manifold,pInstA,pInstB,pEntityA,pEntityB))
{
isManifoldKnownToBeValid = true;
}
else
{
break;
}
}
FastAssert(pInstA && pInstB);
IterateManifold(compositeManifold,pEntityA,pEntityB,pInstA,pInstB);
}
}
}
else
{
if(manifold->GetNumContacts()>0 && ValidateAndRefreshRootManifold(manifold,pInstA,pInstB,pEntityA,pEntityB))
{
FastAssert(pInstA && pInstB);
IterateManifold(manifold,pEntityA,pEntityB,pInstA,pInstB);
}
}
}
}
}
}
bool CPhysics::IsWheelComponent(const CEntity* pEntity, s32 componentId)
{
if (pEntity->GetIsTypeVehicle())
{
const phArchetype* pArch = pEntity->GetPhysArch();
if (pArch)
{
const phBound* pBound = pArch->GetBound();
if (pBound)
{
if (pBound->GetType()==phBound::COMPOSITE)
{
const phBoundComposite* pBoundComp = static_cast<const phBoundComposite*>(pBound);
Assert(pBoundComp->GetTypeAndIncludeFlags());
if (pBoundComp->GetTypeFlags(componentId) & ArchetypeFlags::GTA_WHEEL_TEST)
{
return true;
}
}
}
}
}
return false;
}
bool CPhysics::IsAttachedTo(const CEntity* pEntityA, const CEntity* pEntityB)
{
if (pEntityA->GetIsPhysical())
{
const CPhysical* pPhysicalA = static_cast<const CPhysical*>(pEntityA);
if (pPhysicalA->GetAttachParent()==pEntityB)
{
return true;
}
}
return false;
}
bool CPhysics::ShouldProcessWheelCollisionVfx(const CEntity* pEntity, Vec3V_In vCollNormal)
{
#if __BANK
// don't process if disabled in the widgets
if (g_vfxMaterial.GetColnVfxDisableWheels())
{
return false;
}
#endif
// don't process if the collision normal is too close to the car's up vector
if (Abs(Dot(pEntity->GetTransform().GetC(), vCollNormal).Getf())>VFXMATERIAL_WHEEL_COLN_DOT_THRESH)
{
return false;
}
return true;
}
//Only used to limit vfx
bool CPhysics::ShouldProcessCollisionVfx(VfxCollisionInfo_s& vfxColnInfo)
{
// ignore weapon collisions
if (vfxColnInfo.pEntityA->GetIsTypeObject())
{
CObject* pObjectA = static_cast<CObject*>(vfxColnInfo.pEntityA);
if (pObjectA->GetWeapon())
{
return false;
}
}
if (vfxColnInfo.pEntityB->GetIsTypeObject())
{
CObject* pObjectB = static_cast<CObject*>(vfxColnInfo.pEntityB);
if (pObjectB->GetWeapon())
{
return false;
}
}
// early rejection threshold tests - if neither bangs nor scrapes would produce vfx we can skip
bool passedThresholdsBangs = false;
VfxMaterialInfo_s* pFxMaterialInfo = g_vfxMaterial.GetBangInfo(PGTAMATERIALMGR->GetMtlVfxGroup(vfxColnInfo.mtlIdA), PGTAMATERIALMGR->GetMtlVfxGroup(vfxColnInfo.mtlIdB));
if (pFxMaterialInfo)
{
#if __BANK
if (g_vfxMaterial.GetBangVfxAlwaysPassThresholds())
{
passedThresholdsBangs = true;
}
else
#endif
{
if (vfxColnInfo.pEntityA->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(vfxColnInfo.pEntityA);
if (pVehicle->InheritsFromAutomobile())
{
CAutomobile* pAutomobile = static_cast<CAutomobile*>(pVehicle);
if (pAutomobile->m_nAutomobileFlags.bHydraulicsBounceRaising || pAutomobile->m_nAutomobileFlags.bHydraulicsBounceLanding)
{
passedThresholdsBangs = true;
}
}
}
else if (vfxColnInfo.pEntityB->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(vfxColnInfo.pEntityB);
if (pVehicle->InheritsFromAutomobile())
{
CAutomobile* pAutomobile = static_cast<CAutomobile*>(pVehicle);
if (pAutomobile->m_nAutomobileFlags.bHydraulicsBounceRaising || pAutomobile->m_nAutomobileFlags.bHydraulicsBounceLanding)
{
passedThresholdsBangs = true;
}
}
}
if (!passedThresholdsBangs)
{
passedThresholdsBangs = vfxColnInfo.bangMagA>=pFxMaterialInfo->velThreshMin && vfxColnInfo.vAccumImpulse.Getf()>=pFxMaterialInfo->impThreshMin;
}
}
}
bool passedThresholdsScrapes = false;
pFxMaterialInfo = g_vfxMaterial.GetScrapeInfo(PGTAMATERIALMGR->GetMtlVfxGroup(vfxColnInfo.mtlIdA), PGTAMATERIALMGR->GetMtlVfxGroup(vfxColnInfo.mtlIdB));
if (pFxMaterialInfo)
{
#if __BANK
if (g_vfxMaterial.GetScrapeVfxAlwaysPassThresholds())
{
passedThresholdsScrapes = true;
}
else
#endif
{
passedThresholdsScrapes = vfxColnInfo.scrapeMagA>=pFxMaterialInfo->velThreshMin && vfxColnInfo.vAccumImpulse.Getf()>=pFxMaterialInfo->impThreshMin;
}
}
if (!passedThresholdsBangs && !passedThresholdsScrapes)
{
return false;
}
return true;
}
//Limits both vfx and sfx
bool CPhysics::ShouldProcessCollisionVfxAndSfx(VfxCollisionInfo_s& vfxColnInfo)
{
// ignore entities attached to each other
if (IsAttachedTo(vfxColnInfo.pEntityA, vfxColnInfo.pEntityB) ||
IsAttachedTo(vfxColnInfo.pEntityB, vfxColnInfo.pEntityA))
{
return false;
}
// ignore wheel collisions in certain cases
if (IsWheelComponent(vfxColnInfo.pEntityA, vfxColnInfo.componentIdA))
{
return ShouldProcessWheelCollisionVfx(vfxColnInfo.pEntityA, vfxColnInfo.vWorldNormalA);
}
else if (IsWheelComponent(vfxColnInfo.pEntityB, vfxColnInfo.componentIdB))
{
return ShouldProcessWheelCollisionVfx(vfxColnInfo.pEntityB, vfxColnInfo.vWorldNormalB);
}
// ignore collision with certain materials
if (vfxColnInfo.mtlIdA==PGTAMATERIALMGR->g_idPhysVehicleSpeedUp ||
vfxColnInfo.mtlIdA==PGTAMATERIALMGR->g_idPhysVehicleSlowDown ||
vfxColnInfo.mtlIdA==PGTAMATERIALMGR->g_idPhysVehicleRefill ||
vfxColnInfo.mtlIdA==PGTAMATERIALMGR->g_idPhysVehicleBoostCancel ||
vfxColnInfo.mtlIdA==PGTAMATERIALMGR->g_idPhysPropPlacement
)
{
return false;
}
if (vfxColnInfo.mtlIdB==PGTAMATERIALMGR->g_idPhysVehicleSpeedUp ||
vfxColnInfo.mtlIdB==PGTAMATERIALMGR->g_idPhysVehicleSlowDown ||
vfxColnInfo.mtlIdB == PGTAMATERIALMGR->g_idPhysVehicleRefill ||
vfxColnInfo.mtlIdA == PGTAMATERIALMGR->g_idPhysVehicleBoostCancel ||
vfxColnInfo.mtlIdA==PGTAMATERIALMGR->g_idPhysPropPlacement
)
{
return false;
}
// none of the above - process this collision
return true;
}
void CPhysics::ProcessCollisionVfxandSfx(VfxCollisionInfo_s& vfxColnInfo, bool shouldProcessVfx)
{
// return if either entity isn't valid
if (vfxColnInfo.pEntityA==NULL || vfxColnInfo.pEntityB==NULL)
{
return;
}
// check that entity a is a physical object (b may or may not be)
Assertf(vfxColnInfo.pEntityA->GetIsPhysical() || vfxColnInfo.pEntityB->GetIsPhysical(), "Neither entity is physical (A:%s - B:%s)", vfxColnInfo.pEntityA->GetModelName(), vfxColnInfo.pEntityB->GetModelName());
// check if we should continue with this collision
if (!ShouldProcessCollisionVfxAndSfx(vfxColnInfo))
{
return;
}
// return if we have two invalid colliders
if (vfxColnInfo.pColliderA==NULL && vfxColnInfo.pColliderB==NULL)
{
return;
}
// calc the collision velocity (the impulse is mass x velocity so divide by the mass to get a velocity)
vfxColnInfo.vColnVel = -vfxColnInfo.vWorldNormalA * vfxColnInfo.vAccumImpulse;
if (vfxColnInfo.pColliderA)
{
vfxColnInfo.vColnVel = Scale(vfxColnInfo.vColnVel, ScalarVFromF32(vfxColnInfo.pColliderA->GetInvMass()));
vfxColnInfo.vColnVel += vfxColnInfo.vRelVelocity;
}
else
{
vfxColnInfo.vColnVel = Scale(vfxColnInfo.vColnVel, ScalarVFromF32(vfxColnInfo.pColliderB->GetInvMass()));
vfxColnInfo.vColnVel -= vfxColnInfo.vRelVelocity;
}
Assertf(FPIsFinite(vfxColnInfo.vColnVel.GetXf() && vfxColnInfo.vColnVel.GetYf() && vfxColnInfo.vColnVel.GetZf()), "collision velocity is not valid");
// return if the calculated collision velocity is close to zero (we won't be able to create a valid matrix if it is)
const ScalarV vEpsilon = ScalarV(V_FLT_SMALL_4);
if (IsLessThanAll(Mag(vfxColnInfo.vColnVel), vEpsilon))
{
return;
}
// calc the bang and scrape magnitudes
ScalarV dotNV = Dot(vfxColnInfo.vWorldNormalA, vfxColnInfo.vColnVel);
Vec3V vColnVelInN = vfxColnInfo.vWorldNormalA * dotNV;
Vec3V vColnVelPerpToN = vfxColnInfo.vColnVel-vColnVelInN;
vfxColnInfo.bangMagA = Mag(vColnVelInN).Getf();
vfxColnInfo.scrapeMagA = Mag(vColnVelPerpToN).Getf();
dotNV = Dot(vfxColnInfo.vWorldNormalB, vfxColnInfo.vColnVel);
vColnVelInN = vfxColnInfo.vWorldNormalB * dotNV;
vColnVelPerpToN = vfxColnInfo.vColnVel-vColnVelInN;
vfxColnInfo.bangMagB = Mag(vColnVelInN).Getf();
vfxColnInfo.scrapeMagB = Mag(vColnVelPerpToN).Getf();
#if __BANK
if (CPhysics::ms_bDoCollisionEffects)
#endif
{
// get the collision direction
vfxColnInfo.vColnDir = Vec3V(V_ZERO);
if (vfxColnInfo.pEntityA->GetIsPhysical())
{
const CPhysical* pCollPhysical = static_cast<const CPhysical*>(vfxColnInfo.pEntityA);
vfxColnInfo.vColnDir = VECTOR3_TO_VEC3V(pCollPhysical->GetLocalSpeed(RCC_VECTOR3(vfxColnInfo.vWorldPosA), true, vfxColnInfo.componentIdA) * fwTimer::GetTimeStep());
}
else
{
Assertf(vfxColnInfo.pEntityB->GetIsPhysical(), "Neither collision entity is physical");
const CPhysical* pCollPhysical = static_cast<const CPhysical*>(vfxColnInfo.pEntityB);
vfxColnInfo.vColnDir = VECTOR3_TO_VEC3V(pCollPhysical->GetLocalSpeed(RCC_VECTOR3(vfxColnInfo.vWorldPosB), true, vfxColnInfo.componentIdB) * fwTimer::GetTimeStep());
}
// process vfx for the entity A
#if __BANK
if (!g_vfxMaterial.GetColnVfxDisableEntityA())
#endif
{
if (vfxColnInfo.pEntityA->GetIsTypePed())
{
PuddlePassSingleton::InstanceRef().AddPlayerRipple(vfxColnInfo.vWorldPosA, Mag(vfxColnInfo.vColnVel).Getf());
}
if (shouldProcessVfx && ShouldProcessCollisionVfx(vfxColnInfo))
{
vfxColnInfo.pEntityA->ProcessCollisionVfx(vfxColnInfo);
}
}
// process vfx for the entity B
#if __BANK
if (!g_vfxMaterial.GetColnVfxDisableEntityB())
#endif
{
if (vfxColnInfo.pEntityB->GetIsTypePed())
{
PuddlePassSingleton::InstanceRef().AddPlayerRipple(vfxColnInfo.vWorldPosA, Mag(vfxColnInfo.vColnVel).Getf());
}
if (shouldProcessVfx)
{
float bangMagTemp = vfxColnInfo.bangMagA;
vfxColnInfo.bangMagA = vfxColnInfo.bangMagB;
vfxColnInfo.bangMagB = bangMagTemp;
float scrapeMagTemp = vfxColnInfo.scrapeMagA;
vfxColnInfo.scrapeMagA = vfxColnInfo.scrapeMagB;
vfxColnInfo.scrapeMagB = scrapeMagTemp;
if (ShouldProcessCollisionVfx(vfxColnInfo))
{
CEntity* pEntityTemp = vfxColnInfo.pEntityA;
vfxColnInfo.pEntityA = vfxColnInfo.pEntityB;
vfxColnInfo.pEntityB = pEntityTemp;
int componentIdTemp = vfxColnInfo.componentIdA;
vfxColnInfo.componentIdA = vfxColnInfo.componentIdB;
vfxColnInfo.componentIdB = componentIdTemp;
phCollider* pColliderTemp = vfxColnInfo.pColliderA;
vfxColnInfo.pColliderA = vfxColnInfo.pColliderB;
vfxColnInfo.pColliderB = pColliderTemp;
Vec3V vWorldPosTemp = vfxColnInfo.vWorldPosA;
vfxColnInfo.vWorldPosA = vfxColnInfo.vWorldPosB;
vfxColnInfo.vWorldPosB = vWorldPosTemp;
Vec3V vWorldNormalTemp = vfxColnInfo.vWorldNormalA;
vfxColnInfo.vWorldNormalA = vfxColnInfo.vWorldNormalB;
vfxColnInfo.vWorldNormalB = vWorldNormalTemp;
phMaterialMgr::Id mtlTemp = vfxColnInfo.mtlIdA;
vfxColnInfo.mtlIdA = vfxColnInfo.mtlIdB;
vfxColnInfo.mtlIdB = mtlTemp;
vfxColnInfo.pEntityA->ProcessCollisionVfx(vfxColnInfo);
}
}
}
#if __BANK
// debug stuff
if (g_vfxMaterial.GetColnVfxRenderDebugVectors())
{
Color32 colYellow(1.0f, 1.0f, 0.0f, 1.0f);
grcDebugDraw::Line(RCC_VECTOR3(vfxColnInfo.vWorldPosA), VEC3V_TO_VECTOR3(vfxColnInfo.vWorldPosA+vfxColnInfo.vColnVel), colYellow, -1);
}
if (g_vfxMaterial.GetColnVfxRenderDebugImpulses())
{
static float debugImpulseMax = 200.0f;
// show zero impulses (red), low-high impulses (yellow->green)
Color32 col;
if (vfxColnInfo.vAccumImpulse.Getf()==0.0f)
{
col = Color32(1.0f, 0.0f, 0.0f, 1.0f);
}
else
{
float t = Min(1.0f, vfxColnInfo.vAccumImpulse.Getf()/debugImpulseMax);
col = Color32(1.0f-t, 1.0f, 0.0f, 1.0f);
}
if (g_vfxMaterial.GetColnVfxDisableEntityA()==false)
{
grcDebugDraw::Cross(vfxColnInfo.vWorldPosA, 0.1f, col, -1);
}
if (g_vfxMaterial.GetColnVfxDisableEntityB()==false)
{
grcDebugDraw::Cross(vfxColnInfo.vWorldPosB, 0.1f, col, -1);
}
}
if (g_vfxMaterial.GetColnVfxOutputDebug())
{
char mtlIdAName[128];
char mtlIdBName[128];
PGTAMATERIALMGR->GetMaterialName(vfxColnInfo.mtlIdA, mtlIdAName, 128);
PGTAMATERIALMGR->GetMaterialName(vfxColnInfo.mtlIdB, mtlIdBName, 128);
vfxDebugf1("Vfx Collision Info (%d)", fwTimer::GetFrameCount());
vfxDebugf1(" %s v %s", mtlIdAName, mtlIdBName);
vfxDebugf1(" Accum Impulse %.3f", vfxColnInfo.vAccumImpulse.Getf());
vfxDebugf1(" Rel Speed %.3f", Mag(vfxColnInfo.vRelVelocity).Getf());
}
#endif
}
}
void CPhysics::ProcessCollisionData(phContact& contact, phManifold& manifold, CEntity* pEntityA, CEntity* pEntityB, const ScalarV& vAvgImpulseMag)
{
// extract info from the contact
const Vec3V vCollPosA = contact.GetWorldPosA();
const Vec3V vCollPosB = contact.GetWorldPosB();
const int collComponentA = manifold.GetComponentA();
const int collComponentB = manifold.GetComponentB();
// Calculate the impulse vectors.
physicsAssert(IsGreaterThanOrEqualAll(vAvgImpulseMag, ScalarV(V_ZERO)));
float fAvgImpulseMag = vAvgImpulseMag.Getf();
#if __ASSERT
Assertf(IsFiniteAll(contact.GetWorldNormal()*vAvgImpulseMag),"%p: Invalid impact in phInstGta::PreApplyImpacts",&contact);
// catch this here, early, instead of in the vfx code
float normalMag = Mag(contact.GetWorldNormal()).Getf();
if(normalMag < 0.97f || normalMag > 1.03f)
{
manifold.AssertContactNormalVerbose(contact, __FILE__, __LINE__);
}
#endif
//Tell the CPhysicals about all impulses applied (for damage, deformation and breaking and collision recording)
if(pEntityA != pEntityB)
{
// Cache the info from contact, as it can be deleted from ProcessCollision
const Vec3V vContactWorldNormal = contact.GetWorldNormal();
const phMaterialMgr::Id iContactMaterialIdA = contact.GetMaterialIdA();
const phMaterialMgr::Id iContactMaterialIdB = contact.GetMaterialIdB();
const bool bContactIsPositiveDepth = contact.IsPositiveDepth();
const bool bIsNewContact = contact.GetLifetime() == 1;
if(pEntityA && pEntityA->GetIsPhysical())
{
const Vec3V vCollNormalA = vContactWorldNormal;
static_cast<CPhysical*>(pEntityA)->ProcessCollision(manifold.GetInstanceA(), pEntityB, manifold.GetInstanceB(),
RCC_VECTOR3(vCollPosA), RCC_VECTOR3(vCollPosB), fAvgImpulseMag, RCC_VECTOR3(vCollNormalA), collComponentA, collComponentB,
iContactMaterialIdB, bContactIsPositiveDepth, bIsNewContact);
}
// For B*299855, since we could have deleted an object in the ProcessCollision call above.
// Note that this doesn't check if the object has been deleted, which might be a bad thing...
if(pEntityB && pEntityB->GetIsPhysical() && (!manifold.GetInstanceA() || manifold.GetInstanceA()->IsInLevel()))
{
const Vec3V vCollNormalB = Negate(vContactWorldNormal);
static_cast<CPhysical*>(pEntityB)->ProcessCollision(manifold.GetInstanceB(), pEntityA, manifold.GetInstanceA(),
RCC_VECTOR3(vCollPosB), RCC_VECTOR3(vCollPosA), fAvgImpulseMag, RCC_VECTOR3(vCollNormalB), collComponentB, collComponentA,
iContactMaterialIdA, bContactIsPositiveDepth, bIsNewContact);
}
}
}
// Verify we don't intend these types to collide with MAP_TYPE_WEAPON
CompileTimeAssert(((ArchetypeFlags::GTA_PED_INCLUDE_TYPES | ArchetypeFlags::GTA_PARACHUTING_PED_INCLUDE_TYPES) & ArchetypeFlags::GTA_MAP_TYPE_WEAPON) == 0);
CompileTimeAssert((ArchetypeFlags::GTA_RAGDOLL_INCLUDE_TYPES & ArchetypeFlags::GTA_MAP_TYPE_WEAPON) == 0);
CompileTimeAssert((ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES & ArchetypeFlags::GTA_MAP_TYPE_WEAPON) == 0);
CompileTimeAssert((ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES & ArchetypeFlags::GTA_MAP_TYPE_WEAPON) == 0);
CompileTimeAssert((ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES & ArchetypeFlags::GTA_MAP_TYPE_WEAPON) == 0);
//CompileTimeAssert(((ArchetypeFlags::GTA_PROJECTILE_INCLUDE_TYPES | ArchetypeFlags::GTA_PROJECTILE_NEAR_INCLUDE_TYPES) & ArchetypeFlags::GTA_MAP_TYPE_WEAPON) == 0);
bool TypeFlagsNeverCollideHelper(u32 typeFlagsA, u32 typeFlagsB)
{
// If one instance is pure weapon collision then we know many things will never get past type/include flag checks.
if(typeFlagsA == ArchetypeFlags::GTA_MAP_TYPE_WEAPON)
{
const u32 typesThatNeverCollideWithMapTypeWeapon = ArchetypeFlags::GTA_PED_TYPE |
ArchetypeFlags::GTA_RAGDOLL_TYPE |
ArchetypeFlags::GTA_HORSE_TYPE |
ArchetypeFlags::GTA_HORSE_RAGDOLL_TYPE |
ArchetypeFlags::GTA_VEHICLE_TYPE |
ArchetypeFlags::GTA_BOX_VEHICLE_TYPE |
ArchetypeFlags::GTA_OBJECT_TYPE;
//ArchetypeFlags::GTA_PROJECTILE_TYPE projectiles collide with weapon collision when near the player, see GTA_PROJECTILE_NEAR_INCLUDE_TYPES
if((typeFlagsB & typesThatNeverCollideWithMapTypeWeapon) != 0)
{
return true;
}
}
return false;
}
// NOTES:
// This function should get called whenever 2 objects start overlapping in the broadphase. Dynamic conditions should probably be avoided
// since you will be at the mercy of the broadphase if you want to re-add the broadphase pair.
bool CPhysics::KeepBroadphasePair(int levelIndexA, int levelIndexB)
{
// Keep self collisions
if(levelIndexA != levelIndexB)
{
phInst* pInstA = PHLEVEL->GetInstance(levelIndexA);
phInst* pInstB = PHLEVEL->GetInstance(levelIndexB);
// Get rid of collisions between multiple instances on a CEntity (ped capsules and ragdolls)
if(pInstB->GetUserData() != NULL && pInstA->GetUserData() == pInstB->GetUserData())
{
return false;
}
// Certain types never collide
u32 typeFlagsA = PHLEVEL->GetInstanceTypeFlags(levelIndexA);
u32 typeFlagsB = PHLEVEL->GetInstanceTypeFlags(levelIndexB);
if(TypeFlagsNeverCollideHelper(typeFlagsA,typeFlagsB) || TypeFlagsNeverCollideHelper(typeFlagsB,typeFlagsA))
{
return false;
}
}
return true;
}
void CPhysics::ClearManifoldsForInst(phInst* pInst)
{
btBroadphasePair* prunedPairs = PHLEVEL->GetBroadPhase()->getPrunedPairs();
const int prunedPairCount = PHLEVEL->GetBroadPhase()->getPrunedPairCount();
// Go over all the pairs of objects overlapping in the broadphase
for( int pairIndex = 0; pairIndex < prunedPairCount; ++pairIndex )
{
btBroadphasePair *pair = prunedPairs + pairIndex;
// A pair will only have a manifold if the are actively colliding
if (phManifold* manifold = pair->GetManifold())
{
#if 0
if (manifold->CompositeManifoldsEnabled())
{
for (int manifoldIndex = 0; manifoldIndex < manifold->GetNumCompositeManifolds(); ++manifoldIndex)
{
phManifold* manifoldInComposite = manifold->GetCompositeManifold(manifoldIndex);
if(manifoldInComposite->GetInstanceA()==pInst || manifoldInComposite->GetInstanceB()==pInst)
manifoldInComposite->Reset();
}
}
else
{
if(manifold->GetInstanceA()==pInst || manifold->GetInstanceB()==pInst)
manifold->Reset();
}
#else
if(manifold->GetInstanceA()==pInst || manifold->GetInstanceB()==pInst)
{
manifold->RemoveAllContacts();
}
#endif
}
}
}
void CPhysics::WarpInstance(phInst* pInst, Vec3V_In pos)
{
Mat34V mat = pInst->GetMatrix();
mat.SetCol3(pos);
PHSIM->TeleportObject(*pInst,RCC_MATRIX34(mat));
}
//
// name: CPhysics::StreamData
// description: streams all the physics data required
void CPhysics::UpdateStreaming()
{
// In multi-player mode we only want to load the MOVER map collision to save on memory. The
// MOVER collision will be flagged to collide with weapon shapetests in AddBoundToPhysicsLevel() for
// the static bounds store.
if(PARAM_mapMoverOnlyInMultiplayer.Get())
g_StaticBoundsStore.SetWeaponBounds(!NetworkInterface::IsGameInProgress());
Vector3 playerPosn = CFocusEntityMgr::GetMgr().GetPos();
LoadAboutPosition(RCC_VEC3V(playerPosn));
}
// query streaming etc to see if it is a good time to start increasing the range at which
// we load mover static collisions. this is subject to warm up / cool off timers so
// most discontinuities are smoothed out.
bool CPhysics::SafeToIncreaseStreamingRangeForMoverCollision()
{
const float fMaxVelocity = 8.0f;
const s32 maxPrioRequests = 0;
const s32 maxNormalRequests = 5; // because static bounds store will be generating new requests
const bool bLoadingScene = g_LoadScene.IsActive();
const bool bSwitching = g_PlayerSwitch.IsActive();
const bool bCutsceneSrlStreaming = gStreamingRequestList.IsActive();
const bool bPlayerOnFoot = g_ContinuityMgr.IsPlayerOnFoot();
const bool bPlayerHasLowVelocity = g_ContinuityMgr.GetPlayerVelocity() <= fMaxVelocity;
const bool bLowStreamingPressure = ( strStreamingEngine::GetInfo().GetNumberPriorityObjectsRequested()<=maxPrioRequests
&& strStreamingEngine::GetInfo().GetNumberObjectsRequested()<=maxNormalRequests );
const bool bFocusIsDefault = CFocusEntityMgr::GetMgr().IsPlayerPed();
return (bLowStreamingPressure && !bLoadingScene && !bSwitching && !bCutsceneSrlStreaming
&& bPlayerOnFoot && bPlayerHasLowVelocity && bFocusIsDefault);
}
//
// name: CPhysics::LoadAboutPosition
// description: Load physics bounds about a position
void CPhysics::LoadAboutPosition(Vec3V_In posn)
{
// request static bounds
g_StaticBoundsStore.UpdateStreamingRanges( SafeToIncreaseStreamingRangeForMoverCollision() );
g_StaticBoundsStore.GetBoxStreamer().RequestAboutPos(posn);
const bool bCutsceneAllowPauseForStreaming = CutSceneManager::GetInstance() ? CutSceneManager::GetInstance()->GetAllowGameToPauseForStreaming() : true;
const bool bPlayerSwitchAllowPauseForStreaming = !( g_PlayerSwitch.GetLongRangeMgr().IsActive() && !CFocusEntityMgr::GetMgr().IsPlayerPed() );
// ensure in memory
if (CTheScripts::GetAllowGameToPauseForStreaming() && bCutsceneAllowPauseForStreaming && bPlayerSwitchAllowPauseForStreaming REPLAY_ONLY(&& !CReplayMgr::IsReplayInControlOfWorld()))
{
g_StaticBoundsStore.GetBoxStreamer().EnsureLoadedAboutPos(posn);
}
#if DEBUG_DRAW
else
{
grcDebugDraw::AddDebugOutput("!!! Collision streaming: EnsureLoadedAboutPos is disabled !!!");
}
#endif //DEBUG_DRAW
}
//
// name: CPhysics::ScanAreaPtrArray
// description: Scan the ptrlist for entities that need bounds loaded
void CPhysics::ScanAreaPtrArray(void appendFunc (atArray<CEntity*>&) )
{
CEntity *entityArrayContents[MAX_ACTIVE_INTERIOR_PHYSICS];
atUserArray<CEntity*> entityArray(entityArrayContents,MAX_ACTIVE_INTERIOR_PHYSICS);
appendFunc(entityArray);
u32 count = entityArray.GetCount();
if (count == 0){
return;
}
for(u32 i=0; i<count; i++)
{
CEntity* pEntity = entityArray[i];
if (pEntity)
{
// If entity has a physics dictionary and hasnt had its physics instance setup
if(pEntity->GetCurrentPhysicsInst() == NULL &&
(pEntity->GetIsTypeBuilding() || pEntity->GetIsTypeMLO()) &&
pEntity->IsBaseFlagSet(fwEntity::HAS_PHYSICS_DICT) && !pEntity->IsBaseFlagSet(fwEntity::NO_INSTANCED_PHYS))
{
{
pEntity->InitPhys();
if(pEntity->GetCurrentPhysicsInst())
{
pEntity->AddPhysics();
}
}
}
}
}
}
float afGravityLevels[4] =
{
9.8f,
2.4f,
0.1f,
0.0f
};
CompileTimeAssert(CPhysics::NUM_GRAVITY_LEVELS == 4);
void CPhysics::SetGravityLevel(eGravityLevel nLevel)
{
if(physicsVerifyf(nLevel >= 0 && nLevel < NUM_GRAVITY_LEVELS,"Invalid gravity level"))
{
SetGravitationalAcceleration(afGravityLevels[nLevel]);
}
}
CPhysics::eGravityLevel CPhysics::GetGravityLevel()
{
float fGravity = GetGravitationalAcceleration();
for(s32 i = 0; i < NUM_GRAVITY_LEVELS; i++)
{
static dev_float EPSILON = 0.01f;
if(rage::IsNearZero(fGravity - afGravityLevels[i], EPSILON))
{
return static_cast<eGravityLevel>(i);
}
}
return GRAV_EARTH;
}
void CPhysics::SetGravitationalAcceleration(const float fGravitationalAcceleration)
{
Assert(fGravitationalAcceleration>=0.0f);
ms_fGravitationalAccleration=fGravitationalAcceleration;
//Set game gravity.
CVehicle::SetGravitationalAcceleration(ms_fGravitationalAccleration);
//Set rage gravity.
SetRageGravityVector();
SetRageSleepTolerances();
}
void CPhysics::SetRageGravityVector()
{
Assert(ms_fGravitationalAccleration>=0.0f);
Vector3 vecSetGravity(0,0,-ms_fGravitationalAccleration);
GetSimulator()->SetGravity(vecSetGravity);
}
float CPhysics::GetFoliageRadius( phInst* pOtherInstance, int component, int element )
{
float fFoliageBoundRadius = 0.0f;
phBound* pBound = pOtherInstance->GetArchetype()->GetBound();
int nBoundType = pBound->GetType();
if(nBoundType==phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pBound);
phBound* pFoliageBound = pBoundComposite->GetBound(component);
artAssertf(pFoliageBound->GetType()==phBound::SPHERE||pFoliageBound->GetType()==phBound::CAPSULE||pFoliageBound->GetType()==phBound::BVH,
"Foliage bounds can only be sphere / capsule. Object:'%s' at (%5.2f, %5.2f, %5.2f).", pOtherInstance->GetArchetype()->GetFilename(),
pOtherInstance->GetPosition().GetXf(), pOtherInstance->GetPosition().GetYf(), pOtherInstance->GetPosition().GetZf());
// There may be multiple foliage bounds within our composite in which case they will be stored in a BVH structure or we may
// have a single capsule or sphere foliage bound. Let's extract the bound and let the code below handle it.
pBound = pFoliageBound;
nBoundType = pBound->GetType();
}
if(nBoundType==phBound::BVH)
{
phBoundPolyhedron* pBvhBound = static_cast<phBoundPolyhedron*>(pBound);
phPrimitive bvhPrimitive = pBvhBound->GetPolygon(element).GetPrimitive();
artAssertf(bvhPrimitive.GetType()==PRIM_TYPE_SPHERE||bvhPrimitive.GetType()==PRIM_TYPE_CAPSULE,
"Foliage bounds can only be sphere / capsule. Object:'%s' at (%5.2f, %5.2f, %5.2f).", pOtherInstance->GetArchetype()->GetFilename(),
pBvhBound->GetCentroidOffset().GetXf(), pBvhBound->GetCentroidOffset().GetYf(), pBvhBound->GetCentroidOffset().GetZf());
if(bvhPrimitive.GetType()==PRIM_TYPE_CAPSULE)
{
fFoliageBoundRadius = bvhPrimitive.GetCapsule().GetRadius();
}
else if(bvhPrimitive.GetType()==PRIM_TYPE_SPHERE)
{
fFoliageBoundRadius = bvhPrimitive.GetSphere().GetRadius();
}
}
else if(nBoundType==phBound::SPHERE || nBoundType==phBound::CAPSULE)
{
fFoliageBoundRadius = pBound->GetRadiusAroundCentroid();
}
else
{
Assertf(false, "Unexpected bound type (%d) processing foliage collision at (%5.2f, %5.2f, %5.2f).", nBoundType,
pOtherInstance->GetPosition().GetXf(), pOtherInstance->GetPosition().GetYf(), pOtherInstance->GetPosition().GetZf());
}
return fFoliageBoundRadius;
}
void CPhysics::SetRageSleepTolerances()
{
const float dt=FRAME_LIMITER_MIN_FRAME_TIME/(1.0f*CPhysics::ms_NumTimeSlices);
//There are some magic numbers in the following code (0.005, 0.01, and 0.2).
//These numbers are copied from rage and used in a way that guarantees
//that the threshold values match the default rage sleep thresholds for gravity=9.81.
{
const float fAlphaVel0=Sqrtf(0.005f)/(-GRAVITY*dt);
const float fAlphaVel=fAlphaVel0*ms_fGravitationalAccleration*dt;
phSleep::SetDefaultVelTolerance2(fAlphaVel*fAlphaVel);
}
{
const float fAlphaOmega0=Sqrtf(0.01f)/(-GRAVITY*dt);
const float fAlphaOmega=fAlphaOmega0*ms_fGravitationalAccleration*dt;
phSleep::SetDefaultAngVelTolerance2(fAlphaOmega*fAlphaOmega);
}
{
const float fAlphaInternalVel0=Sqrtf(0.2f)/(-GRAVITY*dt);
const float fAlphaInternalVel=fAlphaInternalVel0*ms_fGravitationalAccleration*dt;
phSleep::SetDefaultInternalVelTolerance2(fAlphaInternalVel*fAlphaInternalVel);
}
}
static dev_float sfGlassCollisionStrength = 300.0f;
#if __DEV
static dev_bool sbRenderHits = false;
#endif
void CPhysics::CollideInstAgainstGlass(phInst* pInst)
{
PF_PUSH_TIMEBAR_DETAIL("Glass test");
Assert(pInst);
if (!pInst)
return;
PF_START_TIMEBAR_DETAIL("Glass test init");
// Check ragdoll for collision against glass
phShapeTest<phShapeObject> shapeTest;
u32 nIncludeFlags = ArchetypeFlags::GTA_OBJECT_TYPE;
u32 nTypeFlag = ms_PhysLevel ? ms_PhysLevel->GetInstanceTypeFlags(pInst->GetLevelIndex()) : 0;
const int iNumIntersections = 16;
phIntersection aIntersections[iNumIntersections];
int iNumHits = 0;
Assert(pInst->GetArchetype());
if (!pInst->GetArchetype())
return;
shapeTest.InitObject(*pInst->GetArchetype()->GetBound(), MAT34V_TO_MATRIX34(pInst->GetMatrix()), aIntersections, iNumIntersections);
PF_START_TIMEBAR_DETAIL("Glass TestInLevel");
iNumHits = shapeTest.TestInLevel(NULL, nIncludeFlags, nTypeFlag );
// Search for glass
PF_START_TIMEBAR_DETAIL("Glass Post TestInLevel");
for(int i = 0; i < iNumHits; i++)
{
phInst* pHitInst = aIntersections[i].GetInstance();
if (pHitInst)
{
int iComponent = aIntersections[i].GetComponent();
Vector3 vForce = -RCC_VECTOR3(aIntersections[i].GetNormal());
vForce.Scale(sfGlassCollisionStrength);
if(pHitInst->GetClassType() >= PH_INST_FRAG_GTA)
{
fragInst* pFragInst = static_cast<fragInst*>(pHitInst);
if (pFragInst && pFragInst->GetTypePhysics())
{
Assert(iComponent < pFragInst->GetTypePhysics()->GetNumChildren());
if (iComponent < pFragInst->GetTypePhysics()->GetNumChildren())
{
int iGroup = pFragInst->GetTypePhysics()->GetAllChildren()[iComponent]->GetOwnerGroupPointerIndex();
fragTypeGroup* pGroup = pFragInst->GetTypePhysics()->GetAllGroups()[iGroup];
Assert(pGroup);
if(pGroup && pGroup->GetMadeOfGlass())
{
// We've found some unbroken glass
// Apply breaking force to the frag which will get passed down to the glass
// Really want to anim drive this but if we test the whole composite bound at once we can't tell what our component is
// So just base it off the normal
pFragInst->ApplyImpulse(vForce, RCC_VECTOR3(aIntersections[i].GetPosition()), aIntersections[i].GetComponent(), aIntersections[i].GetPartIndex(), NULL, 1.0f);
}
}
}
}
else if(pHitInst->GetClassType() == phInst::PH_INST_GLASS)
{
// This is an already fractured glass pane.
// Want to fracture it more to break out any additional pieces
pHitInst->ApplyImpulse(vForce, RCC_VECTOR3(aIntersections[i].GetPosition()), aIntersections[i].GetComponent(), aIntersections[i].GetPartIndex(), NULL, 1.0f);
}
}
#if __DEV
PF_START_TIMEBAR_DETAIL("Glass hit debug render");
if(sbRenderHits)
{
CPhysics::ms_debugDrawStore.AddSphere(aIntersections[i].GetPosition(),0.05f, Color_red, fwTimer::GetTimeStepInMilliseconds() *2);
}
#endif
}
PF_POP_TIMEBAR_DETAIL();
}
void CPhysics::SetClearManifoldsEveryFrame(bool bClear)
{
ms_bClearManifoldsEveryFrame = bClear;
}
void CPhysics::SetClearManifoldsFramesLeft(int framesToClear)
{
Assertf(framesToClear < 1800, "CleatManifolds being set on for %d frames - Really need it left on that long?",framesToClear);
ms_ClearManifoldsFramesLeft = framesToClear > ms_ClearManifoldsFramesLeft ? framesToClear : ms_ClearManifoldsFramesLeft;
}
bool CPhysics::CanUseRagdolls()
{
// no ragdolls in a network game for now
if(NetworkInterface::IsGameInProgress() && !NetworkInterface::AllowNaturalMotion())
return false;
#if GTA_REPLAY
//Dont allow ragdolls when replay is in control of the world - all the bones should be recorded.
if(CReplayMgr::IsReplayInControlOfWorld())
return false;
#endif
#if !__FINAL
return CPedPopulation::ms_bAllowRagdolls;
#else
return true;
#endif
};
void CPhysics::SetAllowedPenetration( float fAllowedPenetration )
{
phSimulator::SetAllowedPenetration(fAllowedPenetration);
}
PF_DRAW_ONLY(static void ToggleDrawMoverMapCollisionCB());
//
// name: CPhysics::RenderDebug
// description: Render physics debug
void CPhysics::RenderDebug()
{
#if __PFDRAW
sysCriticalSection critSec(sPhysicsPFDSemaCritSec);
if (sPhysicsPFDDrawReady)
{
if (!sysIpcWaitSemaTimed(sPhysicsPFDDrawReady, sPhysicsSynchronizationMaxWait))
{
sysIpcDeleteSema(sPhysicsPFDDrawReady);
sPhysicsPFDDrawReady = NULL;
}
}
grcDebugDraw::SetDefaultDebugRenderStates();
// Override lighting mode
grcLightState::SetEnabled(true);
const Matrix34 & cameraMtx = RCC_MATRIX34(grcViewport::GetCurrent()->GetCameraMtx());
GetRageProfileDraw().SendDrawCameraForServer(cameraMtx);
if (CControlMgr::GetKeyboard().GetKeyJustDown(KEY_C, KEYBOARD_MODE_DEBUG_CNTRL_SHIFT)) // shortcut to toggle mover bounds
{
CPhysics::ms_bDrawMoverMapCollision = !CPhysics::ms_bDrawMoverMapCollision;
ToggleDrawMoverMapCollisionCB();
}
if(PFDGROUP_Physics.WillDraw())
ms_PhysSim->ProfileDraw();
ms_ClothManager->ProfileDraw();
ms_RopeManager->ProfileDraw();
bgGlassManager::ProfileDraw();
// Already called from CRenderPhaseDebug3d::StaticRender
// I'm not aware of a reason to draw twice
// ms_mouseInput->Draw();
#if DR_ENABLED
if (grcViewport::GetCurrent())
{
debugPlayback::SetCameraCullingInfo( grcViewport::GetCurrent()->GetFrustumLRTB() );
}
debugPlayback::DebugDraw(fwTimer::IsUserPaused());
#endif
if (sPhysicsPFDDrawDone)
{
sysIpcSignalSema(sPhysicsPFDDrawDone);
}
#endif
#if __BANK
if(gbPrintPhysicsActive || gbPrintPhysicsInActive || gbPrintPhysicsFixed || gbPhysicsInstCounters)
PrintPhysicsInstList(gbPrintPhysicsActive || gbPrintPhysicsInActive || gbPrintPhysicsFixed);
if(gbDisplayVectorMapPhysicsInsts)
DisplayVectorMapPhysicsInsts();
if(ms_bDebugRagdollCount)
DisplayRagdollCount();
if(CBuoyancy::ms_bDebugDisplaySubmergedLevels)
{
CBuoyancy::DisplayBuoyancyInfo();
}
if(ms_bDisplaySelectedPhysicalCollisionRecords || ms_bDisplayAllCollisionRecords )
DisplayCollisionRecords();
if(ms_bDisplayNumberOfBrokenPartsFocusFrag)
DisplayNumPartsBrokenOffFocusFrag();
if(ms_bDisplayPedHealth || ms_bDisplayPedFallHeight)
{
DisplayRagdollDamageInfo();
}
#endif //__BANK
#if NORTH_CLOTHS
CClothMgr::RenderDebug();
#endif
#if RAGE_CLOTHS
CClothRageMgr::RenderDebug();
#endif
}
#if __BANK
void CPhysics::PrintPhysicsInstList(bool bIncludeList)
{
atMap<u32, s32> objMap;
s32 instCount=0;
s32 instActiveCount=0;
s32 instFixedCount=0;
s32 instInactiveCount=0;
for(int i = 0; i < ms_PhysLevel->GetMaxObjects(); ++i)
{
if(!ms_PhysLevel->IsNonexistent(i))
{
phInst* pInst = ms_PhysLevel->GetInstance(i);
CEntity* pEntity = CPhysics::GetEntityFromInst(pInst);
if(pEntity && pInst->GetClassType() != PH_INST_MAPCOL)
{
bool bAddToObjMap = false;
if(ms_PhysLevel->IsActive(pInst->GetLevelIndex()))
{
instActiveCount++;
if(gbPrintPhysicsActive)
bAddToObjMap = true;
}
if(ms_PhysLevel->IsInactive(pInst->GetLevelIndex()))
{
instInactiveCount++;
if(gbPrintPhysicsInActive)
bAddToObjMap = true;
}
if(ms_PhysLevel->IsFixed(pInst->GetLevelIndex()))
{
instFixedCount++;
if(gbPrintPhysicsFixed)
bAddToObjMap = true;
}
instCount++;
if(bAddToObjMap)
{
s32 modelIndex = pEntity->GetModelIndex();
if(objMap.Access(modelIndex))
objMap[modelIndex]++;
else
objMap[modelIndex]=1;
}
}
}
}
grcDebugDraw::AddDebugOutput("%d physics instances, active %d, inactive %d, fixed %d", instCount, instActiveCount, instInactiveCount, instFixedCount);
if(bIncludeList)
{
for(s32 i=0; i<objMap.GetNumSlots(); i++)
{
const atMap<u32, s32>::Entry* pEntry = objMap.GetEntry(i);
if(pEntry)
grcDebugDraw::AddDebugOutput("%s: %d", CModelInfo::GetBaseModelInfoName(fwModelId(strLocalIndex(pEntry->key))), pEntry->data);
}
}
}
//
// name: CPhysics::DisplayVectorMapPhysicsInsts
// description: Display physics insts on VectorMap
void CPhysics::DisplayVectorMapPhysicsInsts()
{
Color32 activeColour(0xff, 0x00, 0x00);
Color32 inactiveColour(0x00, 0xff, 0x00);
Color32 fixedColour(0x00, 0x00, 0xff);
for(int i = 0; i < ms_PhysLevel->GetMaxObjects(); ++i)
{
if(!ms_PhysLevel->IsNonexistent(i))
{
phInst* pInst = ms_PhysLevel->GetInstance(i);
CEntity* pEntity = CPhysics::GetEntityFromInst(pInst);
if(pEntity && pInst->GetClassType() != PH_INST_MAPCOL)
{
spdAABB tempBox;
const spdAABB &bb = pEntity->GetBoundBox(tempBox);
if(ms_PhysLevel->IsActive(pInst->GetLevelIndex()))
{
fwVectorMap::Draw3DBoundingBoxOnVMap(bb, activeColour);
}
else if(ms_PhysLevel->IsInactive(pInst->GetLevelIndex()))
{
fwVectorMap::Draw3DBoundingBoxOnVMap(bb, inactiveColour);
}
else if(ms_PhysLevel->IsFixed(pInst->GetLevelIndex()))
{
fwVectorMap::Draw3DBoundingBoxOnVMap(bb, fixedColour);
}
}
}
}
}
#if USE_FRAG_TUNE
void AddFragTuneWidgetCB()
{
if(numFragNames > 0 && currFragNameSelection < numFragNames)
{
CBaseModelInfo *pModelInfo = CModelInfo::GetBaseModelInfo(fwModelId((strLocalIndex(fragSorts[currFragNameSelection]))));
if(pModelInfo->GetFragType())
{
BANK_ONLY(FRAGTUNE->AddArchetypeWidgets(pModelInfo->GetFragType()));
}
}
}
//extern s32 carModelId;
void TestBoundPerformaceCB()
{
fwModelId carModelId((strLocalIndex(CVehicleFactory::carModelId)));
if(carModelId.IsValid())
{
bool bForceLoad = false;
if(!CModelInfo::HaveAssetsLoaded(carModelId))
{
CModelInfo::RequestAssets(carModelId, STRFLAG_FORCE_LOAD|STRFLAG_PRIORITY_LOAD);
bForceLoad = true;
}
if(bForceLoad)
{
CStreaming::LoadAllRequestedObjects(true);
}
if(!CModelInfo::HaveAssetsLoaded(carModelId))
{
return;
}
float fTimeCreate, fTimeTest, fTimeDestroy;
sysTimer boundPerfTimer;
Matrix34 tempMat;
tempMat.Identity();
CVehicle* pVeh1 = CVehicleFactory::GetFactory()->Create(carModelId, ENTITY_OWNEDBY_PHYSICS, POPTYPE_RANDOM_AMBIENT, &tempMat);
CVehicle* pVeh2 = CVehicleFactory::GetFactory()->Create(carModelId, ENTITY_OWNEDBY_PHYSICS, POPTYPE_RANDOM_AMBIENT, &tempMat);
fTimeCreate = boundPerfTimer.GetMsTime();
WorldProbe::CShapeTestBoundDesc boundTestDesc;
boundTestDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS);
boundTestDesc.SetBoundFromEntity(pVeh1);
boundTestDesc.SetIncludeEntity(pVeh2);
boundTestDesc.SetTransform(&tempMat);
// RA: I've changed this test over to the new API. Note that the original didn't do anything
// with the result of the test either!
WorldProbe::GetShapeTestManager()->SubmitTest(boundTestDesc);
fTimeTest = boundPerfTimer.GetMsTime() - fTimeCreate;
CVehicleFactory::GetFactory()->Destroy(pVeh1);
CVehicleFactory::GetFactory()->Destroy(pVeh2);
fTimeDestroy = boundPerfTimer.GetMsTime() - fTimeTest;
Displayf("Bound Perf Time - Create: %f, Test: %f, Destroy: %f", fTimeCreate, fTimeTest, fTimeDestroy);
}
}
int CompareModelInfosCB(const void* pVoidA, const void* pVoidB)
{
const u32* pA = (const u32*)pVoidA;
const u32* pB = (const u32*)pVoidB;
return stricmp(CModelInfo::GetBaseModelInfoName(fwModelId(strLocalIndex(*pA))),CModelInfo::GetBaseModelInfoName(fwModelId(strLocalIndex(*pB))));
}
bool AddToFragListCB(CEntity* pEntity, void* UNUSED_PARAM(data))
{
if(pEntity && pEntity->IsArchetypeSet())
{
CBaseModelInfo *pModelInfo = pEntity->GetBaseModelInfo();
// check that this model is a fragment type
if(!pModelInfo || pModelInfo->GetFragType()==NULL)
return true;
for(int i=0; i<numFragNames; i++)
{
if(fragSorts[i]==pEntity->GetModelIndex())
return true;
}
if(numFragNames < MAX_FRAG_TYPES)
{
fragSorts[numFragNames] = pEntity->GetModelIndex();
numFragNames++;
}
}
// return true to keep looping through all entities
return true;
}
void UpdateFragList()
{
// fill the list of local model indicies
numFragNames = 0;
fwIsSphereIntersecting testSphere(spdSphere(VECTOR3_TO_VEC3V(CGameWorld::FindLocalPlayerCoors()), ScalarV(50.0f)));
CGameWorld::ForAllEntitiesIntersecting(&testSphere, AddToFragListCB, NULL,
(ENTITY_TYPE_MASK_VEHICLE|ENTITY_TYPE_MASK_PED|ENTITY_TYPE_MASK_OBJECT|ENTITY_TYPE_MASK_DUMMY_OBJECT), SEARCH_LOCATION_EXTERIORS,
SEARCH_LODTYPE_HIGHDETAIL, SEARCH_OPTION_NONE, WORLDREP_SEARCHMODULE_DEBUG);
/*false, true, true, true, true, false, false, false);*/
// sort this list
qsort(fragSorts, numFragNames, sizeof(s32), CompareModelInfosCB);
// reset names array, and re-fill
fragNames.Reset();
for(int i=0; i<numFragNames; i++)
{
fragNames.PushAndGrow(CModelInfo::GetBaseModelInfoName(fwModelId(strLocalIndex(fragSorts[i]))));
}
currFragNameSelection = 0;
if(numFragNames == 0)
{
pFragCombo->UpdateCombo("Local Frag Types", &currFragNameSelection, numFragNames, NULL);
}
else
{
pFragCombo->UpdateCombo("Local Frag Types", &currFragNameSelection, numFragNames, &fragNames[0]);
}
}
#endif // USE_FRAG_TUNE
bool CPhysics::ms_bDebugMeasuringTool = false;
s32 CPhysics::ms_iNumInstances = 1;
float CPhysics::ms_fWidth = 0.0f;
float CPhysics::ms_fCapsuleWidth = 0.1f;
float CPhysics::ms_fCapsuleWidth2 = 0.1f;
float CPhysics::ms_fBoundingBoxWidth = 2.0f;
float CPhysics::ms_fBoundingBoxLength = 1.0f;
float CPhysics::ms_fBoundingBoxHeight = 1.0f;
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
float CPhysics::ms_fSecondSurfaceInterp = 0.0f;
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
bool CPhysics::ms_bDoLineProbeTest = false;
bool CPhysics::ms_bDoAsyncLOSTest = false;
bool CPhysics::ms_bDoAsyncSweptSphereTest = false;
bool CPhysics::ms_bDoLineProbeWaterTest = false;
bool CPhysics::ms_bDoCapsuleTest = false;
bool CPhysics::ms_bDoCapsuleBatchShapeTest = false;
bool CPhysics::ms_bDoBoundingBoxTest = false;
bool CPhysics::ms_bDoTaperedCapsuleBatchShapeTest = false;
bool CPhysics::ms_bDoProbeBatchShapeTest = false;
bool CPhysics::ms_bDoBooleanTest = false;
bool CPhysics::ms_bTestCapsuleAgainstFocusEntity = false;
bool CPhysics::ms_bTestCapsuleAgainstFocusEntityBBoxes = false;
bool CPhysics::ms_bDoBringVehicleToHaltTest = false;
bool CPhysics::ms_bDoPlayerEntityTest = false;
bool CPhysics::ms_bRenderAllHistories = false;
bool CPhysics::ms_bRenderFocusHistory = false;
bool CPhysics::ms_bDebugRagdollCount = false;
bool CPhysics::ms_bRenderFocusImpulses = false;
bool CPhysics::ms_bLOSIgnoreSeeThru = false;
bool CPhysics::ms_bLOSIgnoreShootThru = false;
bool CPhysics::ms_bLOSIgnoreShootThruFX = false;
bool CPhysics::ms_bLOSGoThroughGlass = false;
int CPhysics::ms_iNumIsecsTaperedCapsule = 64;
bool CPhysics::ms_bDoInitialSphereTestForCapsuleTest = false;
bool CPhysics::ms_bDoSphereTest = false;
bool CPhysics::ms_bDoSphereTestNotFixed = false;
bool CPhysics::ms_bShowFoliageImpactsPeds = false;
bool CPhysics::ms_bShowFoliageImpactsVehicles = false;
bool CPhysics::ms_bShowFoliageImpactsRagdolls = false;
float CPhysics::ms_fSphereRadius = 1.0f;
Vector3 CPhysics::g_vClickedPos[2];
void* CPhysics::g_pPointers[2];
Vector3 CPhysics::g_vClickedNormal[2] = {Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 0.0f, 0.0f)};
bool CPhysics::g_bHasPosition[2] = {false, false};
char CPhysics::ms_Pos1[256] = "\0";
char CPhysics::ms_Ptr1[20] = "\0";
char CPhysics::ms_Pos2[256] = "\0";
char CPhysics::ms_Ptr2[20] = "\0";
char CPhysics::ms_Diff[256] = "\0";
char CPhysics::ms_HeadingDiffRadians[256] = "\0";
char CPhysics::ms_HeadingDiffDegrees[256] = "\0";
char CPhysics::ms_Distance[256] = "\0";
char CPhysics::ms_HorizDistance[256] = "\0";
char CPhysics::ms_VerticalDistance[256] = "\0";
char CPhysics::ms_Normal1[256] = "\0";
char CPhysics::ms_Normal2[256] = "\0";
char CPhysics::ms_PlayerEntityTestResult[256] = "\0";
char CPhysics::ms_zExcludedEntityList[352] = "\0";
void UpdatePos1(void)
{
Vector3 vecPos(0.0f,0.0f,0.0f);
if(sscanf(CPhysics::ms_Pos1, "%f, %f, %f", &vecPos.x, &vecPos.y, &vecPos.z))
{
CPhysics::g_vClickedPos[0] = vecPos;
CPhysics::g_bHasPosition[0] = true;
}
}
void UpdatePos2(void)
{
Vector3 vecPos(0.0f,0.0f,0.0f);
if(sscanf(CPhysics::ms_Pos2, "%f, %f, %f", &vecPos.x, &vecPos.y, &vecPos.z))
{
CPhysics::g_vClickedPos[1] = vecPos;
CPhysics::g_bHasPosition[1] = true;
}
}
void UpdateNormal1(void)
{
Vector3 vecPos(0.0f,0.0f,0.0f);
if(sscanf(CPhysics::ms_Normal1, "%f, %f, %f", &vecPos.x, &vecPos.y, &vecPos.z))
{
CPhysics::g_vClickedNormal[0] = vecPos;
}
}
void UpdateNormal2(void)
{
Vector3 vecPos(0.0f,0.0f,0.0f);
if(sscanf(CPhysics::ms_Normal2, "%f, %f, %f", &vecPos.x, &vecPos.y, &vecPos.z))
{
CPhysics::g_vClickedNormal[1] = vecPos;
}
}
bool CPhysics::GetMeasuringToolPos(s32 iPos, Vector3& vOutPos)
{
if( CPhysics::g_bHasPosition[iPos] )
{
vOutPos = CPhysics::g_vClickedPos[iPos];
return true;
}
return false;
}
PF_PAGE(GTAphysicstests, "Gta physics tests");
PF_GROUP(PhysicsTests);
PF_LINK(GTAphysicstests, PhysicsTests);
PF_TIMER(IndividualLineProbes, PhysicsTests);
PF_TIMER(IndividualCapsules, PhysicsTests);
PF_TIMER(IndividualSpheres, PhysicsTests);
PF_TIMER(LineProbeBatch, PhysicsTests);
PF_TIMER(CapsuleBatch, PhysicsTests);
void CPhysics::UpdateDebugMeasuringTool()
{
Vector3 vPos;
Vector3 vNormal;
void *entity;
bool bHasPosition = CDebugScene::GetWorldPositionUnderMouse(vPos, ArchetypeFlags::GTA_ALL_TYPES_MOVER, &vNormal, &entity);
static bool sbLeftButtonHeld = false;
static bool sbRightButtonHeld = false;
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_LEFT )
{
sbLeftButtonHeld = true;
}
if( sbLeftButtonHeld &&
ioMouse::GetReleasedButtons() & ioMouse::MOUSE_LEFT )
{
sbLeftButtonHeld = false;
}
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_RIGHT )
{
sbRightButtonHeld = true;
}
if( sbRightButtonHeld &&
ioMouse::GetReleasedButtons() & ioMouse::MOUSE_RIGHT )
{
sbRightButtonHeld = false;
}
if( bHasPosition )
{
if( sbLeftButtonHeld )
{
g_vClickedPos[0] = vPos;
g_vClickedNormal[0] = vNormal;
g_pPointers[0] = entity;
g_bHasPosition[0] = true;
sprintf(ms_Pos1, "%.5f, %.5f, %.5f", g_vClickedPos[0].x, g_vClickedPos[0].y, g_vClickedPos[0].z);
sprintf(ms_Ptr1, "%p", g_pPointers[0]);
sprintf(ms_Normal1, "%.5f, %.5f, %.5f", g_vClickedNormal[0].x, g_vClickedNormal[0].y, g_vClickedNormal[0].z);
}
else if( sbRightButtonHeld )
{
g_vClickedPos[1] = vPos;
g_vClickedNormal[1] = vNormal;
g_pPointers[1] = entity;
g_bHasPosition[1] = true;
sprintf(ms_Pos2, "%.5f, %.5f, %.5f", g_vClickedPos[1].x, g_vClickedPos[1].y, g_vClickedPos[1].z);
sprintf(ms_Ptr2, "%p", g_pPointers[1]);
sprintf(ms_Normal2, "%.5f, %.5f, %.5f", g_vClickedNormal[1].x, g_vClickedNormal[1].y, g_vClickedNormal[1].z);
}
else if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_MIDDLE )
{
g_bHasPosition[0] = false;
g_bHasPosition[1] = false;
sbLeftButtonHeld = false;
sbRightButtonHeld = false;
ms_Pos1[0] = '\0';
ms_Pos2[0] = '\0';
ms_Diff[0] = '\0';
ms_HeadingDiffRadians[0] = '\0';
ms_HeadingDiffDegrees[0] = '\0';
ms_Distance[0] = '\0';
ms_HorizDistance[0] = '\0';
ms_VerticalDistance[0] = '\0';
ms_Normal1[0] = '\0';
ms_Normal2[0] = '\0';
}
}
if( g_bHasPosition[0] )
{
grcDebugDraw::Sphere(g_vClickedPos[0], 0.1f, Color32(0.0f, 0.0f, 1.0f) );
grcDebugDraw::Line(g_vClickedPos[0], g_vClickedPos[0]+(g_vClickedNormal[0]*0.4f), Color32(0.0f, 0.0f, 1.0f) );
updatePlayerEntityTest();
if(ms_bDoBringVehicleToHaltTest)
{
DoBringVehicleToHaltTest(g_vClickedPos[0]);
}
}
if( g_bHasPosition[1] )
{
grcDebugDraw::Sphere(g_vClickedPos[1], 0.05f, Color32(0.0f, 1.0f, 0.0f) );
grcDebugDraw::Line(g_vClickedPos[1], g_vClickedPos[1]+(g_vClickedNormal[1]*0.4f), Color32(0.0f, 1.0f, 0.0f) );
}
if( g_bHasPosition[0] && g_bHasPosition[1] )
{
grcDebugDraw::Line(g_vClickedPos[0], g_vClickedPos[1], Color32(0.0f, 0.0f, 1.0f), Color32(0.0f, 1.0f, 0.0f) );
Vector3 vDiff = g_vClickedPos[1] - g_vClickedPos[0];
Vector3 vDiffNorm = vDiff;
vDiffNorm.z = 0.0f;
vDiffNorm.Normalize();
sprintf(ms_Diff, "%.2f, %.2f, %.2f", vDiff.x, vDiff.y, vDiff.z);
sprintf(ms_HeadingDiffRadians, "%.2f", rage::Atan2f(-vDiffNorm.x, vDiffNorm.y));
sprintf(ms_HeadingDiffDegrees, "%.2f", rage::Atan2f(-vDiffNorm.x, vDiffNorm.y)*180.0f/(float)PI);
sprintf(ms_Distance, "%.2f", vDiff.Mag());
sprintf(ms_HorizDistance, "%.2f", vDiff.XYMag());
sprintf(ms_VerticalDistance, "%.2f", vDiff.z);
}
if( g_bHasPosition[0] && g_bHasPosition[1] )
{
PF_PUSH_TIMEBAR_DETAIL("PhysicsShapeTests");
// Do one tapered capsule test now and then retest intersections during a loop
// This simulates the camera collision code
if(ms_bDoTaperedCapsuleBatchShapeTest)
{
#if USE_TAPERED_CAPSULE
DoMeasuringToolTaperedCapsuleTests();
#endif
}
if( CPhysics::ms_bDoLineProbeTest || CPhysics::ms_bDoLineProbeWaterTest || CPhysics::ms_bDoCapsuleBatchShapeTest ||
CPhysics::ms_bDoProbeBatchShapeTest || CPhysics::ms_bDoCapsuleTest || CPhysics::ms_bDoSphereTest || CPhysics::ms_bDoBoundingBoxTest )
{
DoMeasuringToolBatchTests();
}
PF_POP_TIMEBAR_DETAIL();
static WorldProbe::CShapeTestFixedResults<> sShapeTestHandleProbe;
if(ms_bDoAsyncLOSTest)
{
static Vector3 vResult = VEC3_ZERO;
static bool bHitSomething = false;
if(!sShapeTestHandleProbe.GetResultsWaitingOrReady())
{
s32 nInclude = ArchetypeFlags::GTA_ALL_TYPES_MOVER;
int nOptions = 0;
if(CPhysics::ms_bLOSIgnoreSeeThru)
{
nOptions |= WorldProbe::LOS_IGNORE_SEE_THRU;
}
if(CPhysics::ms_bLOSIgnoreShootThru)
{
nOptions |= WorldProbe::LOS_IGNORE_SHOOT_THRU;
}
if(CPhysics::ms_bLOSIgnoreShootThruFX)
{
nOptions |= WorldProbe::LOS_IGNORE_SHOOT_THRU_FX;
}
if(CPhysics::ms_bLOSGoThroughGlass)
{
nOptions |= WorldProbe::LOS_GO_THRU_GLASS;
}
sShapeTestHandleProbe.Reset();
WorldProbe::CShapeTestProbeDesc probeData;
probeData.SetResultsStructure(&sShapeTestHandleProbe);
probeData.SetStartAndEnd(g_vClickedPos[0],g_vClickedPos[1]);
probeData.SetExcludeEntities(g_pExcludeEntityList, g_nNumExcludedEntities);
probeData.SetContext(WorldProbe::ENotSpecified);
probeData.SetIncludeFlags(nInclude);
probeData.SetOptions(nOptions);
probeData.SetIsDirected(true);
WorldProbe::GetShapeTestManager()->SubmitTest(probeData, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
bHitSomething = false;
}
else if(sShapeTestHandleProbe.GetResultsReady())
{
bHitSomething = sShapeTestHandleProbe.GetNumHits() > 0u;
if(bHitSomething)
{
vResult = sShapeTestHandleProbe[0].GetHitPosition();
}
}
if(bHitSomething)
{
grcDebugDraw::Sphere(vResult, 0.2f, Color_red);
}
}
else
{
sShapeTestHandleProbe.Reset();
}
static WorldProbe::CShapeTestFixedResults<> sShapeTestHandleSweptSphere;
if(ms_bDoAsyncSweptSphereTest)
{
static Vector3 vResult = VEC3_ZERO;
static bool bHitSomething = false;
if(!sShapeTestHandleSweptSphere.GetResultsWaitingOrReady())
{
s32 nInclude = ArchetypeFlags::GTA_ALL_TYPES_MOVER;
int nOptions = 0;
if(CPhysics::ms_bLOSIgnoreSeeThru)
{
nOptions |= WorldProbe::LOS_IGNORE_SEE_THRU;
}
if(CPhysics::ms_bLOSIgnoreShootThru)
{
nOptions |= WorldProbe::LOS_IGNORE_SHOOT_THRU;
}
if(CPhysics::ms_bLOSIgnoreShootThruFX)
{
nOptions |= WorldProbe::LOS_IGNORE_SHOOT_THRU_FX;
}
if(CPhysics::ms_bLOSGoThroughGlass)
{
nOptions |= WorldProbe::LOS_GO_THRU_GLASS;
}
sShapeTestHandleSweptSphere.Reset();
WorldProbe::CShapeTestCapsuleDesc sweptSphereDesc;
sweptSphereDesc.SetResultsStructure(&sShapeTestHandleSweptSphere);
sweptSphereDesc.SetCapsule(g_vClickedPos[0], g_vClickedPos[1], ms_fCapsuleWidth);
sweptSphereDesc.SetExcludeEntities(g_pExcludeEntityList, g_nNumExcludedEntities);
sweptSphereDesc.SetContext(WorldProbe::ENotSpecified);
sweptSphereDesc.SetIncludeFlags(nInclude);
sweptSphereDesc.SetOptions(nOptions);
sweptSphereDesc.SetIsDirected(true);
WorldProbe::GetShapeTestManager()->SubmitTest(sweptSphereDesc, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
bHitSomething = false;
}
else if(sShapeTestHandleSweptSphere.GetResultsReady())
{
bHitSomething = sShapeTestHandleSweptSphere.GetNumHits() > 0;
if(bHitSomething)
{
vResult = sShapeTestHandleSweptSphere[0].GetHitPosition();
}
}
if(bHitSomething)
{
grcDebugDraw::Sphere(vResult, 0.2f, Color_red);
}
}
else
{
sShapeTestHandleSweptSphere.Reset();
}
if(ms_bTestCapsuleAgainstFocusEntity)
{
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX; ++i)
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(i);
for( s32 i = 0; i < ms_iNumInstances; i++ )
{
if(pEnt)
{
const u32 nNumIntersections = 10;
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
WorldProbe::CShapeTestFixedResults<nNumIntersections> capsuleResults;
capsuleDesc.SetResultsStructure(&capsuleResults);
capsuleDesc.SetCapsule(g_vClickedPos[0], g_vClickedPos[1], ms_fCapsuleWidth);
capsuleDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS);
capsuleDesc.SetIncludeEntity(pEnt);
capsuleDesc.SetDoInitialSphereCheck(false);
capsuleDesc.SetIsDirected(true);
if(WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc))
{
for(WorldProbe::ResultIterator it = capsuleResults.begin(); it < capsuleResults.last_result(); ++it)
{
if(it->GetHitDetected())
{
grcDebugDraw::Cross(RCC_VEC3V(it->GetHitPosition()),0.05f,Color_red);
char partStr[4];
formatf(partStr,4,"%i",it->GetHitPartIndex());
grcDebugDraw::Text(it->GetHitPosition(),Color_white,partStr,false);
}
}
}
}
}
}
}
if(ms_bTestCapsuleAgainstFocusEntityBBoxes)
{
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX; ++i)
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(i);
if(pEnt && (pEnt->GetType()==ENTITY_TYPE_VEHICLE))
{
CVehicle * pVehicle = (CVehicle*)pEnt;
static const u32 iNumComponents = 5;
static const eHierarchyId iIDs[iNumComponents] =
{
VEH_CHASSIS, VEH_DOOR_DSIDE_F, VEH_DOOR_DSIDE_R, VEH_DOOR_PSIDE_F, VEH_DOOR_PSIDE_R
};
int iComponents[8];
int iNum = 0;
for(unsigned int i=0; i<iNumComponents; i++)
{
const int iComponent = pVehicle->GetFragInst()->GetComponentFromBoneIndex(pVehicle->GetBoneIndex(iIDs[i]));
if(iComponent >= 0)
iComponents[iNum++] = iComponent;
}
for( s32 i = 0; i < ms_iNumInstances; i++ )
{
phIntersection intersection;
CPedGeometryAnalyser::TestCapsuleAgainstComposite(pEnt, g_vClickedPos[0], g_vClickedPos[1], ms_fCapsuleWidth, true, 0, NULL, iNum, iComponents);
}
}
}
}
}
// Visualise a river's velocity vector field around the debug point.
if(CBuoyancy::ms_bVisualiseRiverFlowField && g_bHasPosition[0])
{
for(int i = 0; i < CBuoyancy::ms_nGridSizeX; ++i)
{
for(int j = 0; j < CBuoyancy::ms_nGridSizeY; ++j)
{
Vector3 vGridPos = g_vClickedPos[0];
float fOffsetX = i*2.0f - float(CBuoyancy::ms_nGridSizeX);
float fOffsetY = j*2.0f - float(CBuoyancy::ms_nGridSizeY);
vGridPos.x += fOffsetX*CBuoyancy::ms_fGridSpacing;
vGridPos.y += fOffsetY*CBuoyancy::ms_fGridSpacing;
Vector2 vFlow2D;
River::GetRiverFlowFromPosition(RCC_VEC3V(vGridPos), vFlow2D);
vFlow2D.Scale(1.0f/River::GetRiverPushForce());
Vector3 vFlow3D(vFlow2D.x, vFlow2D.y, 0.0f);
Vector3 vArrowPos(vGridPos.x, vGridPos.y, vGridPos.z+CBuoyancy::ms_fHeightOffset);
Vector3 vArrowDir = vFlow3D;
vArrowDir.Scale(CBuoyancy::ms_fScaleFactor);
Color32 colour = Color_yellow;
const float fRapidSpeed = CTaskNMRiverRapids::sm_Tunables.m_fMinRiverFlowForRapids;
Vector3 vRiverSpeed = vFlow3D;
vRiverSpeed.Scale(CBuoyancy::GetDefaultRiverFlowVelocityScaleFactor());
if(vRiverSpeed.Mag2() > fRapidSpeed*fRapidSpeed)
colour = Color_red;
grcDebugDraw::Arrow(VECTOR3_TO_VEC3V(vArrowPos), VECTOR3_TO_VEC3V(vArrowPos+vArrowDir), 0.1f, colour);
}
}
}
}
#if USE_TAPERED_CAPSULE
void CPhysics::DoMeasuringToolTaperedCapsuleTests()
{
// Do one tapered capsule test now and then retest intersections during a loop
// This simulates the camera collision code
Vector3 vMainStart = g_vClickedPos[0];
Vector3 vMainEnd = g_vClickedPos[1];
Vector3 vStartToEnd = vMainEnd - vMainStart;
vStartToEnd.Normalize();
Vector3 vCross;
if( ABS(vStartToEnd.z) > 0.95f )
{
vCross.Cross(vStartToEnd, XAXIS);
}
else
{
vCross.Cross(vStartToEnd, ZAXIS);
}
// Set up tapered capsule tester
phShapeTest<phShapeObject> taperedCapsuleTester;
phBoundTaperedCapsule taperedCapsuleBound;
Matrix34 taperedCapsuleMat;
phIntersection* taperedCapsuleIntersections = Alloca(phIntersection,ms_iNumIsecsTaperedCapsule);
taperedCapsuleMat.b.Subtract(vMainStart,vMainEnd);
taperedCapsuleMat.b.NormalizeSafe();
taperedCapsuleMat.b.MakeOrthonormals(taperedCapsuleMat.a, taperedCapsuleMat.c);
taperedCapsuleMat.d.Average(vMainStart,vMainEnd);
const float fLength = (vMainStart - vMainEnd).Mag();
Vector3 vDefaultOffset;
vDefaultOffset.Cross(vCross, vStartToEnd);
taperedCapsuleBound.SetCapsuleSize(ms_fCapsuleWidth+ms_fWidth,ms_fCapsuleWidth2+ms_fWidth,fLength);
taperedCapsuleTester.KeepNextCullData();
taperedCapsuleTester.InitObject(taperedCapsuleBound,taperedCapsuleMat,taperedCapsuleIntersections,ms_iNumIsecsTaperedCapsule);
taperedCapsuleTester.GetShape().SetCullType(phCullShape::PHCULLTYPE_CAPSULE);
int iNumHits = taperedCapsuleTester.TestInLevel(NULL, ArchetypeFlags::GTA_ALL_TYPES_MOVER, TYPE_FLAGS_ALL, phLevelBase::STATE_FLAGS_ALL,0,CPhysics::GetLevel());
grcDebugDraw::Line(vMainStart, vMainEnd, Color_green, Color_green);
for(int i =0; i < iNumHits; i++)
{
if(taperedCapsuleIntersections[i].IsAHit())
{
grcDebugDraw::Sphere(RCC_VECTOR3(taperedCapsuleIntersections[i].GetPosition()), 0.2f, Color_red);
}
}
// Loop over the intersections again using retestInteresections
if(ms_iNumInstances > 1)
{
for( s32 i = 0; i < ms_iNumInstances; i++ )
{
Matrix34 mat;
mat.Identity();
mat.MakeRotate(vStartToEnd, ((float)i/(float)ms_iNumInstances) * TWO_PI);
Vector3 vThisOffset = vDefaultOffset;
mat.Transform3x3(vThisOffset);
vThisOffset.Scale(ms_fWidth);
Vector3 vStart = vMainStart + vThisOffset;
Vector3 vEnd = vMainEnd + vThisOffset;
if( CPhysics::ms_bDoTaperedCapsuleBatchShapeTest)
{
taperedCapsuleMat.d.Average(vStart,vEnd);
// For each test we want to retest the original main intersection
// so need to copy these into a temp array to avoid stomping over them
phIntersection* taperedCapsuleLoopIntersections = Alloca(phIntersection,ms_iNumIsecsTaperedCapsule);
for(int iIntersectionIndex = 0; iIntersectionIndex < ms_iNumIsecsTaperedCapsule; iIntersectionIndex++)
{
taperedCapsuleLoopIntersections[iIntersectionIndex].Copy(taperedCapsuleIntersections[iIntersectionIndex]);
}
const float fLength = (vStart - vEnd).Mag();
taperedCapsuleBound.SetCapsuleSize(ms_fCapsuleWidth,ms_fCapsuleWidth2,fLength);
taperedCapsuleTester.InitObject(taperedCapsuleBound,taperedCapsuleMat);
taperedCapsuleTester.GetShape().SetCullType(phCullShape::PHCULLTYPE_CAPSULE);
taperedCapsuleTester.GetShape().SetIntersections(taperedCapsuleLoopIntersections,ms_iNumIsecsTaperedCapsule);
iNumHits = taperedCapsuleTester.RetestIntersections();
grcDebugDraw::Line(vStart, vEnd, Color_maroon2, Color_maroon2);
for(int i =0; i < iNumHits; i++)
{
if(taperedCapsuleLoopIntersections[i].IsAHit())
{
grcDebugDraw::Sphere(RCC_VECTOR3(taperedCapsuleLoopIntersections[i].GetPosition()), 0.2f, Color_red);
}
}
}
}
}
taperedCapsuleBound.Release(false);
}
#endif // USE_TAPERED_CAPSULE
void CPhysics::AddFocusEntityToExclusionList()
{
// Called by a RAG button widget to build up a list of CEntity objects which
// should be excluded from the shape tests done in DoMeasuringToolBatchTests().
CEntity* pEnt = CDebugScene::FocusEntities_Get(0);
if(!pEnt)
return;
// Search through list and see if that entity is already in the list.
bool bAlreadyInList = false;
for(int i = 0; i < g_nNumExcludedEntities; ++i)
{
if(g_pExcludeEntityList[i] == pEnt)
{
bAlreadyInList = true;
break;
}
}
if(!bAlreadyInList)
{
g_pExcludeEntityList[g_nNumExcludedEntities++] = pEnt;
}
for(int i = 0; i < g_nNumExcludedEntities; ++i)
{
sprintf(ms_zExcludedEntityList+11*i, "0x%p ", g_pExcludeEntityList[i]);
}
ms_zExcludedEntityList[g_nNumExcludedEntities*11 - 1] = '\0';
}
void CPhysics::ClearEntityExclusionList()
{
for(int i = 0; i < PROCESS_LOS_MAX_EXCEPTIONS; ++i)
{
g_pExcludeEntityList[i] = 0;
}
g_nNumExcludedEntities = 0;
ms_zExcludedEntityList[0] = '\0';
}
void CPhysics::DoMeasuringToolBatchTests()
{
int nOptions = 0;
if(CPhysics::ms_bLOSIgnoreSeeThru)
{
nOptions |= WorldProbe::LOS_IGNORE_SEE_THRU;
}
if(CPhysics::ms_bLOSIgnoreShootThru)
{
nOptions |= WorldProbe::LOS_IGNORE_SHOOT_THRU;
}
if(CPhysics::ms_bLOSIgnoreShootThruFX)
{
nOptions |= WorldProbe::LOS_IGNORE_SHOOT_THRU_FX;
}
if(CPhysics::ms_bLOSGoThroughGlass)
{
nOptions |=WorldProbe::LOS_GO_THRU_GLASS;
}
Vector3 vMainStart = g_vClickedPos[0];
Vector3 vMainEnd = g_vClickedPos[1];
Vector3 vStartToEnd = vMainEnd - vMainStart;
vStartToEnd.Normalize();
Vector3 vCross;
if( ABS(vStartToEnd.z) > 0.95f )
{
vCross.Cross(vStartToEnd, XAXIS);
}
else
{
vCross.Cross(vStartToEnd, ZAXIS);
}
const int maxBatchedCapsules = 128;
const int maxBatchedProbes = 128;
WorldProbe::CShapeTestHitPoint aProbeIntersections[maxBatchedProbes];
phSegment aProbeSegments[maxBatchedProbes];
phShapeTest< phShapeBatch > batchCapsuleTester;
batchCapsuleTester.GetShape().AllocateSweptSpheres(maxBatchedProbes);
phIntersection aCapsuleIntersections[maxBatchedCapsules];
phSegment aCapsuleSegments[maxBatchedCapsules];
WorldProbe::CShapeTestBatchDesc batchProbeDesc;
batchProbeDesc.SetOptions(0);
batchProbeDesc.SetIncludeFlags(INCLUDE_FLAGS_ALL);
batchProbeDesc.SetTypeFlags(TYPE_FLAGS_ALL);
batchProbeDesc.SetExcludeTypeFlags(TYPE_FLAGS_NONE);
ALLOC_AND_SET_PROBE_DESCRIPTORS(batchProbeDesc,ms_iNumInstances);
Vector3 vDefaultOffset;
vDefaultOffset.Cross(vCross, vStartToEnd);
for( s32 i = 0; i < ms_iNumInstances; i++ )
{
Matrix34 mat;
mat.Identity();
mat.MakeRotate(vStartToEnd, ((float)i/(float)ms_iNumInstances) * TWO_PI);
Vector3 vThisOffset = vDefaultOffset;
mat.Transform3x3(vThisOffset);
vThisOffset.Scale(ms_fWidth);
Vector3 vStart = vMainStart + vThisOffset;
Vector3 vEnd = vMainEnd + vThisOffset;
if(CPhysics::ms_bDoLineProbeTest)
{
const int nNumResults = 16;
PF_START(IndividualLineProbes);
WorldProbe::CShapeTestProbeDesc shapeTestDesc;
WorldProbe::CShapeTestFixedResults<nNumResults> probeResults;
shapeTestDesc.SetStartAndEnd(vStart, vEnd);
shapeTestDesc.SetResultsStructure(CPhysics::ms_bDoBooleanTest ? NULL : &probeResults);
shapeTestDesc.SetExcludeEntities(g_pExcludeEntityList, g_nNumExcludedEntities);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
shapeTestDesc.SetSecondSurfaceInterp(ms_fSecondSurfaceInterp);
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
shapeTestDesc.SetIncludeFlags(INCLUDE_FLAGS_ALL);
shapeTestDesc.SetOptions(nOptions);
// Register this shape test with the manager.
bool foundHits = WorldProbe::GetShapeTestManager()->SubmitTest(shapeTestDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST);
PF_STOP(IndividualLineProbes);
Color32 color;
if(foundHits && CPhysics::ms_bDoBooleanTest)
{
color = Color_yellow;
}
else
{
color = Color_green;
}
grcDebugDraw::Line(vStart, vEnd, color);
// Visualise probe and collision results.
if(!CPhysics::ms_bDoBooleanTest)
{
physicsAssert(probeResults.GetResultsReady());
WorldProbe::ResultIterator it;
for(it = probeResults.begin(); it < probeResults.end(); ++it)
{
if(it->GetHitDetected())
{
grcDebugDraw::Sphere(it->GetHitPosition(), 0.1f, Color_yellow);
}
}
}
}
if(CPhysics::ms_bDoLineProbeWaterTest)
{
Vector3 vEndOfWaterProbeArrow = vStart - Vector3(0.0f, 0.0f, 1.0f);
grcDebugDraw::Arrow(VECTOR3_TO_VEC3V(vStart), VECTOR3_TO_VEC3V(vEndOfWaterProbeArrow), 0.3f, Color_red2);
PF_START(IndividualLineProbes);
float fWaterHeight = -1.0f;
static bool sbConsiderWaves = true;
bool bFoundWater = CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(vStart, &fWaterHeight, sbConsiderWaves);
PF_STOP(IndividualLineProbes);
// Visualise results.
if(bFoundWater)
{
Vector3 vHitPosition = vStart;
vHitPosition.z = fWaterHeight;
grcDebugDraw::Sphere(vHitPosition, 0.1f, Color_yellow);
}
}
if( CPhysics::ms_bDoCapsuleTest )
{
grcDebugDraw::Line(vStart, vEnd, Color_maroon2);
PF_START(IndividualCapsules);
WorldProbe::CShapeTestCapsuleDesc shapeTestDesc;
WorldProbe::CShapeTestFixedResults<64> capsuleResults;
shapeTestDesc.SetCapsule(vStart, vEnd, ms_fCapsuleWidth);
shapeTestDesc.SetResultsStructure(&capsuleResults);
shapeTestDesc.SetOptions(nOptions);
shapeTestDesc.SetExcludeEntities(g_pExcludeEntityList, g_nNumExcludedEntities);
shapeTestDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_TYPES_MOVER);
shapeTestDesc.SetTypeFlags(TYPE_FLAGS_ALL);
shapeTestDesc.SetIsDirected(true);
shapeTestDesc.SetDoInitialSphereCheck(ms_bDoInitialSphereTestForCapsuleTest);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
shapeTestDesc.SetSecondSurfaceInterp(ms_fSecondSurfaceInterp);
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
WorldProbe::GetShapeTestManager()->SubmitTest(shapeTestDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST);
PF_STOP(IndividualCapsules);
// Visualise probe and collision results.
physicsAssert(capsuleResults.GetResultsReady());
WorldProbe::ResultIterator it;
for(it = capsuleResults.begin(); it < capsuleResults.end(); ++it)
{
if(it->GetHitDetected())
{
grcDebugDraw::Sphere(it->GetHitPosition(), 0.1f, Color_yellow);
}
}
}
if( CPhysics::ms_bDoSphereTest )
{
grcDebugDraw::Sphere(vStart, ms_fSphereRadius, Color_orange, false);
u32 nFlags = ArchetypeFlags::GTA_ALL_TYPES_MOVER;
if(CPhysics::ms_bDoSphereTestNotFixed)
{
nFlags &= ~ArchetypeFlags::GTA_ALL_MAP_TYPES;
}
PF_START(IndividualSpheres);
const u8 nNumSphereResults = 64;
WorldProbe::CShapeTestSphereDesc shapeTestDesc;
WorldProbe::CShapeTestFixedResults<nNumSphereResults> sphereResults;
shapeTestDesc.SetSphere(vStart, ms_fSphereRadius);
shapeTestDesc.SetResultsStructure(&sphereResults);
shapeTestDesc.SetOptions(nOptions);
shapeTestDesc.SetExcludeEntities(g_pExcludeEntityList, g_nNumExcludedEntities);
shapeTestDesc.SetIncludeFlags(nFlags);
shapeTestDesc.SetTypeFlags(TYPE_FLAGS_ALL);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
shapeTestDesc.SetSecondSurfaceInterp(ms_fSecondSurfaceInterp);
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
WorldProbe::GetShapeTestManager()->SubmitTest(shapeTestDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST);
PF_STOP(IndividualSpheres);
// Visualise results.
physicsAssert(sphereResults.GetResultsReady());
WorldProbe::ResultIterator it;
for(it = sphereResults.begin(); it < sphereResults.last_result(); ++it)
{
if(it->GetHitDetected())
{
grcDebugDraw::Sphere(it->GetHitPosition(), 0.1f, Color_yellow);
}
}
}
if(CPhysics::ms_bDoBoundingBoxTest)
{
phBoundBox* pBox = rage_new phBoundBox(Vector3(ms_fBoundingBoxWidth, ms_fBoundingBoxLength, ms_fBoundingBoxHeight));
Matrix34 testMatrix(Matrix34::IdentityType);
testMatrix.d = vStart;
WorldProbe::CShapeTestBoundingBoxDesc shapeTestDesc;
WorldProbe::CShapeTestFixedResults<1> results;
shapeTestDesc.SetBound(pBox);
shapeTestDesc.SetTransform(&testMatrix);
shapeTestDesc.SetResultsStructure(&results);
shapeTestDesc.SetExcludeEntities(g_pExcludeEntityList, g_nNumExcludedEntities);
u32 nIncludeFlags = INCLUDE_FLAGS_ALL & ~(ArchetypeFlags::GTA_MAP_TYPE_WEAPON | ArchetypeFlags::GTA_MAP_TYPE_MOVER
| ArchetypeFlags::GTA_MAP_TYPE_HORSE | ArchetypeFlags::GTA_MAP_TYPE_COVER | ArchetypeFlags::GTA_MAP_TYPE_VEHICLE);
shapeTestDesc.SetIncludeFlags(nIncludeFlags);
shapeTestDesc.SetOptions(nOptions);
// Register this shape test with the manager and visualise the results.
Color32 colour = Color_blue;
if(WorldProbe::GetShapeTestManager()->SubmitTest(shapeTestDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST))
{
colour = Color_yellow;
physicsAssert(results.GetResultsReady());
WorldProbe::ResultIterator it;
for(it = results.begin(); it < results.end(); ++it)
{
if(it->GetHitDetected())
{
grcDebugDraw::Sphere(it->GetHitPosition(), 0.1f, Color_yellow);
}
}
}
grcDebugDraw::BoxOriented(pBox->GetBoundingBoxMin(), pBox->GetBoundingBoxMax(), MATRIX34_TO_MAT34V(testMatrix), colour, false);
pBox->Release();
}
aProbeSegments[i].Set(vStart, vEnd);
aCapsuleSegments[i].Set(vStart, vEnd);
aProbeIntersections[i].Reset();
aCapsuleIntersections[i].Reset();
if( CPhysics::ms_bDoCapsuleBatchShapeTest && i < 128 )
{
grcDebugDraw::Line(vStart, vEnd, Color_blue);
batchCapsuleTester.InitSweptSphere(aCapsuleSegments[i],ms_fCapsuleWidth,&aCapsuleIntersections[i], BAD_INDEX);
}
}
Matrix34 batchMatrix;
batchMatrix.b = vMainEnd - vMainStart;
batchMatrix.d = vMainStart + 0.5f*batchMatrix.b;
batchMatrix.b.Normalize();
batchMatrix.a.Cross(batchMatrix.b, Vector3(0.0f,0.0f,1.0f));
batchMatrix.a.Normalize();
batchMatrix.c.Cross(batchMatrix.a, batchMatrix.b);
if( CPhysics::ms_bDoProbeBatchShapeTest )
{
WorldProbe::CShapeTestResults batchTestResults(aProbeIntersections, maxBatchedProbes);
for(int i=0; i < ms_iNumInstances && i < 128; ++i)
{
grcDebugDraw::Line(aProbeSegments[i].A, aProbeSegments[i].B, Color_orange);
WorldProbe::CShapeTestProbeDesc probeTestDesc;
probeTestDesc.SetStartAndEnd(aProbeSegments[i].A, aProbeSegments[i].B);
probeTestDesc.SetResultsStructure(&batchTestResults);
probeTestDesc.SetFirstFreeResultOffset(i);
probeTestDesc.SetMaxNumResultsToUse(1);
probeTestDesc.SetExcludeEntities(g_pExcludeEntityList, g_nNumExcludedEntities);
probeTestDesc.SetOptions(nOptions);
batchProbeDesc.AddLineTest(probeTestDesc);
}
// Specify a custom cull-volume for this batch test.
WorldProbe::CCullVolumeBoxDesc cullVolDesc;
Vector3 vecHalfWidth;
vecHalfWidth.x = ms_fWidth;
vecHalfWidth.y = 0.5f*(vMainStart - vMainEnd).Mag();
vecHalfWidth.z = ms_fWidth;
cullVolDesc.SetBox(batchMatrix, vecHalfWidth);
batchProbeDesc.SetCustomCullVolume(&cullVolDesc);
WorldProbe::GetShapeTestManager()->SubmitTest(batchProbeDesc);
// Visualise results.
physicsAssert(batchTestResults.GetResultsReady());
WorldProbe::ResultIterator it;
for(it = batchTestResults.begin(); it < batchTestResults.last_result(); ++it)
{
if(it->GetHitDetected())
{
grcDebugDraw::Sphere(it->GetHitPosition(), 0.1f, Color_yellow);
}
}
}
if( CPhysics::ms_bDoCapsuleBatchShapeTest )
{
Vector3 vecHalfWidth;
vecHalfWidth.x = ms_fWidth + ms_fCapsuleWidth;
vecHalfWidth.y = (vMainStart - vMainEnd).Mag() + ms_fCapsuleWidth;
vecHalfWidth.z = ms_fWidth + ms_fCapsuleWidth;
batchCapsuleTester.GetShape().SetCullBox(batchMatrix, vecHalfWidth);
PF_START(CapsuleBatch);
batchCapsuleTester.TestInLevel(NULL, ArchetypeFlags::GTA_ALL_TYPES_MOVER, TYPE_FLAGS_ALL, phLevelBase::STATE_FLAGS_ALL,0,CPhysics::GetLevel());
PF_STOP(CapsuleBatch);
for( s32 i = 0; i < ms_iNumInstances; i++ )
{
if( aCapsuleIntersections[i].IsAHit() )
grcDebugDraw::Sphere(RCC_VECTOR3(aCapsuleIntersections[i].GetPosition()), 0.2f, Color_red);
}
}
}
void CPhysics::updatePlayerEntityTest()
{
// Should we be doing this test?
if(!ms_bDoPlayerEntityTest){
ms_PlayerEntityTestResult[0] = '\0';
return;
}
// Test player's entity against clicked point using testEntityAgainstEntity
CEntity* playerEntity = CGameWorld::FindLocalPlayer();
CEntity* targetEntity = static_cast<CEntity*>(g_pPointers[0]);
Vector3 targetPosition = g_vClickedPos[0];
// Check we have target
if(playerEntity == NULL){
return;
}
// Move current player matrix to target position
Matrix34 testPlayerMatrix = MAT34V_TO_MATRIX34(playerEntity->GetMatrix());
testPlayerMatrix.d = targetPosition;
WorldProbe::CShapeTestBoundDesc boundTestDesc;
boundTestDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS);
boundTestDesc.SetBoundFromEntity(playerEntity);
boundTestDesc.SetIncludeEntity(targetEntity);
boundTestDesc.SetTransform(&testPlayerMatrix);
if(WorldProbe::GetShapeTestManager()->SubmitTest(boundTestDesc))
{
sprintf(ms_PlayerEntityTestResult,"Player does overlap");
}
else{
sprintf(ms_PlayerEntityTestResult,"Player doesn't overlap");
}
}
static dev_float sfStoppingDistance = 5.0f;
static dev_u32 snTimeToStopFor = 1;
static dev_float sfTriggerDistFromLocate = 2.0f;
static dev_bool sbControlVerticalVelocity = false;
void CPhysics::DoBringVehicleToHaltTest(const Vector3& vLocatePos)
{
// Find the player's vehicle if one exists:
CPed* pPlayer = CGameWorld::FindLocalPlayer();
if(!pPlayer || !pPlayer->GetIsDrivingVehicle())
return;
CVehicle* pVehicle = pPlayer->GetMyVehicle();
if(!pVehicle)
return;
if((VEC3V_TO_VECTOR3(pVehicle->GetVehiclePosition()) - vLocatePos).Mag2() < rage::square(sfTriggerDistFromLocate))
{
CTask* pTask = rage_new CTaskBringVehicleToHalt(sfStoppingDistance, snTimeToStopFor, sbControlVerticalVelocity);
if(pTask)
{
pVehicle->GetIntelligence()->AddTask(VEHICLE_TASK_TREE_SECONDARY, pTask, VEHICLE_TASK_SECONDARY_ANIM, true);
}
}
}
bool CPhysics::ms_bForceDummyObjects = false;
bool CPhysics::ms_bDrawMoverMapCollision = false;
bool CPhysics::ms_bDrawWeaponMapCollision = false;
bool CPhysics::ms_bDrawHorseMapCollision = false;
bool CPhysics::ms_bDrawCoverMapCollision = false;
bool CPhysics::ms_bDrawVehicleMapCollision = false;
bool CPhysics::ms_bDrawStairsSlopeMapCollision = false;
bool CPhysics::ms_bDrawDynamicCollision = false;
bool CPhysics::ms_bDrawPedDensity = false;
bool CPhysics::ms_bDrawPavement = false;
bool CPhysics::ms_bDrawPolygonDensity = false;
bool CPhysics::ms_bDrawEdgeAngles = false;
bool CPhysics::ms_bDrawPrimitiveDensity = false;
bool CPhysics::ms_bIncludePolygons = false;
bool CPhysics::ms_bFragManagerUpdateEachSimUpdate = false;
bool CPhysics::ms_bDebugPullThingsAround = false;
float CPhysics::ms_pullForceMultiply = 7.5f;
float CPhysics::ms_pullDistMax = 100.0f;
phMouseInput* CPhysics::ms_mouseInput = NULL;
static bool g_FragDrawToggle = false;
static RegdEnt g_pThingToPullAround;
static s32 g_iComponent= s32(NULL);
static Vector3 g_vOffset = Vector3( 0.0f, 0.0f, 0.0f );
static s32 iButtonClicked = 0;
void CPhysics::UpdatePullingThingsAround()
{
static float fDistanceFromCamera = 0.0f;
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_LEFT ||
ioMouse::GetPressedButtons() & ioMouse::MOUSE_RIGHT ||
ioMouse::GetPressedButtons() & ioMouse::MOUSE_MIDDLE )
{
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_LEFT )
{
iButtonClicked = ioMouse::MOUSE_LEFT;
}
else if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_RIGHT )
{
iButtonClicked = ioMouse::MOUSE_RIGHT;
}
else
{
iButtonClicked = ioMouse::MOUSE_MIDDLE;
}
if( !g_pThingToPullAround )
{
//CEntity* pEntity = CDebugScene::GetEntityUnderMouse( &g_vOffset, &g_iComponent, ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE);//ArchetypeFlags::GTA_ALL_TYPES|ArchetypeFlags::GTA_RAGDOLL_TYPE|ArchetypeFlags::GTA_PROJECTILE_TYPE);
CEntity* pEntity = CDebugScene::GetEntityUnderMouse( &g_vOffset, &g_iComponent, ArchetypeFlags::GTA_ALL_TYPES_MOVER|ArchetypeFlags::GTA_RAGDOLL_TYPE|ArchetypeFlags::GTA_PROJECTILE_TYPE|ArchetypeFlags::GTA_BOX_VEHICLE_TYPE);
if(pEntity && pEntity->GetIsPhysical())
{
g_pThingToPullAround= pEntity;
// Convert the offset into a local offset
const Vector3 vThingPosition = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().GetPosition());
g_vOffset = g_vOffset - vThingPosition;
g_vOffset = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(g_vOffset)));
// Work out how far the object was from the camera initially
Vector3 vMouseNear, vMouseFar;
CDebugScene::GetMousePointing( vMouseNear, vMouseFar );
fDistanceFromCamera = vMouseNear.Dist(vThingPosition);
}
}
}
// LEFT BUTTON PULLS THE PART OF THE OBJECT INITIALLY GRABBED TO THE BIT NOW POINTED AT
else if( !(ioMouse::GetReleasedButtons() & ioMouse::MOUSE_LEFT) && iButtonClicked == ioMouse::MOUSE_LEFT )
{
Vector3 Pos;
if(g_pThingToPullAround && g_pThingToPullAround->GetCurrentPhysicsInst() && g_pThingToPullAround->GetCurrentPhysicsInst()->IsInLevel() &&
CDebugScene::GetWorldPositionUnderMouse(Pos, ArchetypeFlags::GTA_MAP_TYPE_WEAPON|ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE))
{
Vector3 vWorldOffset = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().Transform3x3(VECTOR3_TO_VEC3V(g_vOffset)));
const Vector3 vThingPosition = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().GetPosition());
Vector3 vForceDir = (Pos - ( vWorldOffset + vThingPosition));
float fForce = MIN( vForceDir.Mag(), CPhysics::ms_pullDistMax );
vForceDir.Normalize();
fForce *= CPhysics::ms_pullForceMultiply * ((CPhysical*)g_pThingToPullAround.Get())->GetMass();
vForceDir *= fForce;
((CPhysical*)g_pThingToPullAround.Get())->ApplyExternalForce(vForceDir, vWorldOffset, g_iComponent);
grcDebugDraw::Sphere( vWorldOffset + vThingPosition, 0.1f, Color32(1.0f, 0.0f, 0.0f) );
grcDebugDraw::Line( vWorldOffset + vThingPosition, Pos, Color32(1.0f, 0.0f, 0.0f) );
char buff[256];
sprintf( buff, "0x%p", g_pThingToPullAround->GetCurrentPhysicsInst() );
grcDebugDraw::Text( vThingPosition, Color32(1.0f, 1.0f, 1.0f), 0,0, buff );
}
}
// RIGHT BUTTON PULLS THE PART OF THE OBJECT INITIALLY GRABBED TO A POSITION RELATIVE TO THE CAMERA
else if( !(ioMouse::GetReleasedButtons() & ioMouse::MOUSE_RIGHT) && iButtonClicked == ioMouse::MOUSE_RIGHT )
{
if( g_pThingToPullAround && g_pThingToPullAround->GetCurrentPhysicsInst() && g_pThingToPullAround->GetCurrentPhysicsInst()->IsInLevel())
{
// Work out the position to aim for from where the mouse is pointing
Vector3 vPos, vMouseNear, vMouseFar;
CDebugScene::GetMousePointing( vMouseNear, vMouseFar );
Vector3 vWorldOffset = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().Transform3x3(VECTOR3_TO_VEC3V(g_vOffset)));
// Shift and control move the object forward and backward
static float fUpdatePerFrame = 1.0f;
if (ioMapper::DebugKeyDown(KEY_SHIFT))
fDistanceFromCamera += fUpdatePerFrame;
else if (ioMapper::DebugKeyDown(KEY_CONTROL))
fDistanceFromCamera -= fUpdatePerFrame;
fDistanceFromCamera = MAX( fDistanceFromCamera, 5.0f );
Vector3 vDirection = vMouseFar - vMouseNear;
vDirection.Normalize();
vDirection.Scale(fDistanceFromCamera);
vPos = vDirection + vMouseNear;
const Vector3 vThingPosition = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().GetPosition());
Vector3 vForceDir = (vPos - ( vWorldOffset + vThingPosition));
float fForce = MIN( vForceDir.Mag(), CPhysics::ms_pullDistMax );
vForceDir.Normalize();
fForce *= CPhysics::ms_pullForceMultiply * ((CPhysical*)g_pThingToPullAround.Get())->GetMass();
vForceDir *= fForce;
((CPhysical*)g_pThingToPullAround.Get())->ApplyExternalForce(vForceDir, vWorldOffset, g_iComponent);
grcDebugDraw::Sphere( vWorldOffset + vThingPosition, 0.1f, Color32(0.0f, 0.0f, 1.0f) );
grcDebugDraw::Line( vWorldOffset + vThingPosition, vPos, Color32(0.0f, 0.0f, 1.0f) );
}
}
// MIDDLE BUTTON PULLS THE CENTRE OF THE OBJECT INITIALLY GRABBED TO A POSITION RELATIVE TO THE CAMERA
else if( !(ioMouse::GetReleasedButtons() & ioMouse::MOUSE_MIDDLE) && iButtonClicked == ioMouse::MOUSE_MIDDLE )
{
if( g_pThingToPullAround && g_pThingToPullAround->GetCurrentPhysicsInst() && g_pThingToPullAround->GetCurrentPhysicsInst()->IsInLevel())
{
CPhysical* pPhysical = ((CPhysical*)g_pThingToPullAround.Get());
// Work out the position to aim for from where the mouse is pointing
Vector3 vPos, vMouseNear, vMouseFar;
CDebugScene::GetMousePointing( vMouseNear, vMouseFar );
// Shift and control move the object forward and backward
static float fUpdatePerFrame = 0.25f;
if (ioMapper::DebugKeyDown(KEY_SHIFT))
fDistanceFromCamera += fUpdatePerFrame;
else if (ioMapper::DebugKeyDown(KEY_CONTROL))
fDistanceFromCamera -= fUpdatePerFrame;
fDistanceFromCamera = MAX( fDistanceFromCamera, 5.0f );
Vector3 vDirection = vMouseFar - vMouseNear;
vDirection.Normalize();
vDirection.Scale(fDistanceFromCamera);
vPos = vDirection + vMouseNear;
// Apply the pulling force to the object
Vector3 vForceDir = (vPos - ( VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().GetPosition()) ));
float fForce = MIN( vForceDir.Mag(), CPhysics::ms_pullDistMax );
vForceDir.Normalize();
fForce *= CPhysics::ms_pullForceMultiply * pPhysical->GetMass();
vForceDir *= fForce;
pPhysical->ApplyForceCg(vForceDir);
if (ioMapper::DebugKeyDown(KEY_C))
{
float fHeading = pPhysical->GetTransform().GetHeading();
Matrix34 matNew = MAT34V_TO_MATRIX34(pPhysical->GetMatrix());
matNew.Identity3x3();
pPhysical->SetMatrix(matNew);
pPhysical->SetHeading(fHeading);
}
else if( ioMapper::DebugKeyDown(KEY_S) )
{
pPhysical->SetHeading(0.0f);
}
else
{
// Apply torque if z or x are held
float fTorque = 0.0f;
if (ioMapper::DebugKeyDown(KEY_X))
fTorque = 1.0f;
else if (ioMapper::DebugKeyDown(KEY_Z))
fTorque = -1.0f;
if( fTorque != 0.0f )
{
if( pPhysical->GetIsTypePed() )
{
static float fPedRotationSpeed = PI/30.0f;
CPed* pPed = (CPed*)pPhysical;
pPed->SetDesiredHeading( pPed->GetCurrentHeading() + ( fPedRotationSpeed * fTorque ) );
pPed->SetHeading( pPed->GetDesiredHeading() );
}
else
{
static float fTorqueMultiplier = 10.0f;
fTorque *= fTorqueMultiplier * pPhysical->GetMass();
pPhysical->ApplyTorque( fTorque*camInterface::GetRight(), camInterface::GetFront() );
}
}
}
const Vector3 vThingPosition = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().GetPosition());
grcDebugDraw::Sphere( vThingPosition, 0.1f, Color32(0.0f, 1.0f, 0.0f) );
grcDebugDraw::Line( vThingPosition, vPos, Color32(0.0f, 1.0f, 0.0f) );
}
}
else
{
if( g_pThingToPullAround )
{
#if __BANK
const Vector3 vThingPosition = VEC3V_TO_VECTOR3(g_pThingToPullAround->GetTransform().GetPosition());
static char debug_string[128];
sprintf( debug_string, "Position of entity when released (%.2f, %.2f, %.2f) heading (%.2f)\n",
vThingPosition.x, vThingPosition.y, vThingPosition.z,
( RtoD * g_pThingToPullAround->GetTransform().GetHeading()) );
Displayf( "%s", debug_string );
FileHandle fileId;
s32 temp_string_length;
fileId = CFileMgr::OpenFileForAppending(CScriptDebug::GetNameOfScriptDebugOutputFile());
if(CFileMgr::IsValidFileHandle(fileId))
{
temp_string_length = istrlen(debug_string); // debug_string);
ASSERT_ONLY(s32 NumBytes =) CFileMgr::Write(fileId, (char *) debug_string, temp_string_length);
Assertf(NumBytes > 0, "CPhysics::UpdatePullingThingsAround - Wrote 0 bytes to temp_debug.txt");
CFileMgr::CloseFile(fileId);
}
#endif // __BANK
g_pThingToPullAround = NULL;
}
}
}
bool CPhysics::ms_bMouseShooter= false;
bool CPhysics::ms_bAdvancedMouseShooter= false;
bool CPhysics::ms_bAttachedShooterMode= false;
bool CPhysics::ms_bOverrideMouseShooterWeapon = false;
s32 CPhysics::ms_iWeaponComboIndex = 0;
bool s_bRightClicked= false;
bool s_bLeftClicked= false;
bool s_bMiddleClicked= false;
Vector3 s_vPosition(0.0f, 0.0f, 0.0f);
Vector3 s_vOffset(0.0f, 0.0f, 0.0f);
Vector3 s_vOrientation(0.0f, 1.0f, 0.0f);
Vector3 s_vLocalOrientation(0.0f, 0.0f, 0.0f);
static float SHOT_LENGTH = 3.0f;
static float EXIT_WOUND_LENGTH = 2.0f;
static float FAST_ROTATION_SPEED = TWO_PI;
static float SLOW_ROTATION_SPEED = QUARTER_PI;
static RegdEnt g_pShootAttachedEntity(NULL);
static s32 g_iAttachedComponent = 0;
void CPhysics::UpdateMouseShooter()
{
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_LEFT )
{
s_bLeftClicked = true;
}
if( s_bLeftClicked &&
ioMouse::GetReleasedButtons() & ioMouse::MOUSE_LEFT )
{
s_bLeftClicked = false;
}
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_RIGHT )
{
s_bRightClicked = true;
}
if( s_bRightClicked &&
ioMouse::GetReleasedButtons() & ioMouse::MOUSE_RIGHT )
{
s_bRightClicked = false;
}
if( ioMouse::GetPressedButtons() & ioMouse::MOUSE_MIDDLE )
{
s_bMiddleClicked = true;
}
if( s_bMiddleClicked &&
ioMouse::GetReleasedButtons() & ioMouse::MOUSE_MIDDLE )
{
s_bMiddleClicked = false;
}
if( ms_bAdvancedMouseShooter )
{
const float fRotationSpeed = ( ( s_bMiddleClicked || ioMapper::DebugKeyDown(KEY_SPACE) )? SLOW_ROTATION_SPEED : FAST_ROTATION_SPEED ) * fwTimer::GetTimeStep();
if(ioMapper::DebugKeyDown(KEY_Z))
{
s_vOrientation.RotateZ(fRotationSpeed);
}
else if( ioMapper::DebugKeyDown(KEY_X) )
{
s_vOrientation.RotateZ(-fRotationSpeed);
}
if( ioMapper::DebugKeyDown(KEY_W) )
{
Vector3 vCross = s_vOrientation;
vCross.Cross(ZAXIS);
s_vOrientation.RotateX(vCross.x*fRotationSpeed);
s_vOrientation.RotateY(vCross.y*fRotationSpeed);
}
else if( ioMapper::DebugKeyDown(KEY_S) )
{
Vector3 vCross = s_vOrientation;
vCross.Cross(ZAXIS);
s_vOrientation.RotateX(vCross.x*-fRotationSpeed);
s_vOrientation.RotateY(vCross.y*-fRotationSpeed);
}
s_vOrientation.Normalize();
if( s_bRightClicked )
{
Vector3 vWorldPos;
if( CDebugScene::GetWorldPositionUnderMouse(vWorldPos, ArchetypeFlags::GTA_MAP_TYPE_WEAPON|ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_RAGDOLL_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE) )
{
s_vPosition = vWorldPos;
}
if( g_pShootAttachedEntity )
{
g_pShootAttachedEntity = NULL;
}
if( ms_bAttachedShooterMode )
{
Vector3 vOffset;
CEntity* pEntity = CDebugScene::GetEntityUnderMouse(&vOffset, &g_iAttachedComponent, ArchetypeFlags::GTA_MAP_TYPE_WEAPON|ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_RAGDOLL_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE);
if( pEntity )
{
g_pShootAttachedEntity = pEntity;
if( pEntity->GetIsTypePed() )
{
CPed* pPed = (CPed*)pEntity;
Matrix34 mBone;
pPed->GetBoneMatrixFromRagdollComponent(mBone, g_iAttachedComponent);
// Convert the offset into a local offset
vOffset = vOffset - mBone.d;
mBone.UnTransform3x3(vOffset);
s_vOffset = vOffset;
s_vLocalOrientation = s_vOrientation;
mBone.UnTransform3x3(s_vLocalOrientation);
s_vLocalOrientation.Normalize();
}
else
{
// Convert the offset into a local offset
vOffset = vOffset - VEC3V_TO_VECTOR3(g_pShootAttachedEntity->GetTransform().GetPosition());
vOffset = VEC3V_TO_VECTOR3(g_pShootAttachedEntity->GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vOffset)));
s_vOffset = vOffset;
s_vLocalOrientation = VEC3V_TO_VECTOR3(g_pShootAttachedEntity->GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(s_vOrientation)));
s_vLocalOrientation.Normalize();
}
}
}
}
if( g_pShootAttachedEntity && g_pShootAttachedEntity->GetIsTypePed() )
{
CPed* pPed = (CPed*)g_pShootAttachedEntity.Get();
Matrix34 mBone;
pPed->GetBoneMatrixFromRagdollComponent(mBone, g_iAttachedComponent);
s_vPosition = s_vOffset;
mBone.Transform3x3(s_vPosition);
s_vPosition += mBone.d;
s_vOrientation = s_vLocalOrientation;
mBone.Transform3x3(s_vOrientation);
}
else if( g_pShootAttachedEntity )
{
s_vPosition = VEC3V_TO_VECTOR3(g_pShootAttachedEntity->GetTransform().Transform3x3(VECTOR3_TO_VEC3V(s_vOffset)));
s_vPosition += VEC3V_TO_VECTOR3(g_pShootAttachedEntity->GetTransform().GetPosition());
s_vOrientation = VEC3V_TO_VECTOR3(g_pShootAttachedEntity->GetTransform().Transform3x3(VECTOR3_TO_VEC3V(s_vLocalOrientation)));
}
if( s_bLeftClicked )
{
Vector3 TempCoors = s_vPosition - (s_vOrientation * SHOT_LENGTH);
Vector3 NewCoors = s_vPosition + (s_vOrientation * 100.0f);
//FireOneInstantHitRound(&TempCoors, &NewCoors, DamageCaused);
u32 WEAPON_HASH = WEAPONTYPE_ASSAULTRIFLE;
if( ms_bOverrideMouseShooterWeapon )
{
CWeaponInfoManager::StringList& weaponNames = CWeaponInfoManager::GetNames( CWeaponInfoBlob::IT_Weapon );
if( ms_iWeaponComboIndex >= 0 && ms_iWeaponComboIndex < weaponNames.GetCount() )
{
WEAPON_HASH = atStringHash(weaponNames[ms_iWeaponComboIndex]);
}
}
CWeapon tempWeapon(WEAPON_HASH);
Matrix34 tempMatrix;
tempMatrix.Identity();
tempMatrix.d = TempCoors;
tempMatrix.b = NewCoors - TempCoors;
tempMatrix.b.Normalize();
tempMatrix.a.Cross(tempMatrix.b, Vector3(0.0f, 0.0f, 1.0f));
tempMatrix.a.Normalize();
tempMatrix.c.Cross(tempMatrix.a, tempMatrix.b);
tempMatrix.c.Normalize();
tempWeapon.Fire(CWeapon::sFireParams(NULL, tempMatrix, &TempCoors, &NewCoors));
}
grcDebugDraw::Line(s_vPosition - (s_vOrientation * SHOT_LENGTH), s_vPosition, Color32(0.0f, 1.0f, 0.0f) );
grcDebugDraw::Line(s_vPosition + (s_vOrientation * EXIT_WOUND_LENGTH), s_vPosition, Color32(1.0f, 0.0f, 0.0f) );
}
else
{
if( s_bLeftClicked )
{
Vector3 TempCoors;
Vector3 NewCoors;
CDebugScene::GetMousePointing( TempCoors, NewCoors );
//FireOneInstantHitRound(&TempCoors, &NewCoors, DamageCaused);
u32 WEAPON_HASH = WEAPONTYPE_ASSAULTRIFLE;
if( ms_bOverrideMouseShooterWeapon )
{
CWeaponInfoManager::StringList& weaponNames = CWeaponInfoManager::GetNames( CWeaponInfoBlob::IT_Weapon );
if( ms_iWeaponComboIndex >= 0 && ms_iWeaponComboIndex < weaponNames.GetCount() )
{
WEAPON_HASH = atStringHash(weaponNames[ms_iWeaponComboIndex]);
}
}
CWeapon tempWeapon(WEAPON_HASH);
Matrix34 tempMatrix;
tempMatrix.Identity();
tempMatrix.d = TempCoors;
tempMatrix.b = NewCoors - TempCoors;
tempMatrix.b.Normalize();
tempMatrix.a.Cross(tempMatrix.b, Vector3(0.0f, 0.0f, 1.0f));
tempMatrix.a.Normalize();
tempMatrix.c.Cross(tempMatrix.a, tempMatrix.b);
tempMatrix.c.Normalize();
tempWeapon.Fire(CWeapon::sFireParams(NULL, tempMatrix, &TempCoors, &NewCoors));
}
}
}
void CPhysics::UpdateDebugPositionOne()
{
Vector3 vPosFromText(Vector3::ZeroType);
if( sscanf(ms_Pos1, "%f, %f, %f", &vPosFromText.x, &vPosFromText.y, &vPosFromText.z) == 3 )
{
g_vClickedPos[0] = vPosFromText;
g_vClickedNormal[0] = Vector3(0.0f,0.0f,1.0f);
g_pPointers[0] = NULL;
g_bHasPosition[0] = true;
sprintf(ms_Ptr1, "");
sprintf(ms_Normal1, "");
}
}
void CPhysics::UpdateDebugPositionTwo()
{
Vector3 vPosFromText(Vector3::ZeroType);
if( sscanf(ms_Pos2, "%f, %f, %f", &vPosFromText.x, &vPosFromText.y, &vPosFromText.z) == 3 )
{
g_vClickedPos[1] = vPosFromText;
g_vClickedNormal[1] = Vector3(0.0f,0.0f,1.0f);
g_pPointers[1] = NULL;
g_bHasPosition[1] = true;
sprintf(ms_Ptr2, "");
sprintf(ms_Normal2, "");
}
}
void ActivateFocusEntityCB()
{
if(CEntity* pEntity = CDebugScene::FocusEntities_Get(0))
{
if(pEntity->GetIsPhysical())
{
static_cast<CPhysical*>(pEntity)->ActivatePhysics();
}
}
}
void ActivatePickedEntityCB()
{
CEntity* pEntity = static_cast<CEntity*>(g_PickerManager.GetSelectedEntity());
if (pEntity && pEntity->GetIsPhysical())
{
static_cast<CPhysical*>(pEntity)->ActivatePhysics();
}
}
void ToggleFragDraw()
{
#if __PFDRAW
PFDGROUP_Physics.SetEnabled(g_FragDrawToggle);
PFD_ComponentIndices.SetEnabled(g_FragDrawToggle);
if (g_FragDrawToggle)
{
gVpMan.ClearVisibilityFlagForAllPhases( VIS_ENTITY_MASK_OBJECT );
}
else
{
gVpMan.SetVisibilityFlagForAllPhases( VIS_ENTITY_MASK_OBJECT );
}
#endif // __PFDRAW
}
#if __PFDRAW
void UpdateDrawCollisionOptions()
{
bool bAnyMapFlagsOn =
CPhysics::ms_bDrawMoverMapCollision|
CPhysics::ms_bDrawWeaponMapCollision|
CPhysics::ms_bDrawHorseMapCollision|
CPhysics::ms_bDrawCoverMapCollision|
CPhysics::ms_bDrawVehicleMapCollision|
CPhysics::ms_bDrawStairsSlopeMapCollision;
PFDGROUP_Physics.SetEnabled(bAnyMapFlagsOn|CPhysics::ms_bDrawDynamicCollision);
PFD_Active.SetEnabled(CPhysics::ms_bDrawDynamicCollision);
PFD_Inactive.SetEnabled(CPhysics::ms_bDrawDynamicCollision);
PFD_Fixed.SetEnabled(bAnyMapFlagsOn);
if(bAnyMapFlagsOn)
{
PFDGROUP_TypeFlagFilter.DisableChildren();
PFD_TypeFlag1.SetEnabled(CPhysics::ms_bDrawWeaponMapCollision);
PFD_TypeFlag2.SetEnabled(CPhysics::ms_bDrawMoverMapCollision);
PFD_TypeFlag3.SetEnabled(CPhysics::ms_bDrawHorseMapCollision);
PFD_TypeFlag4.SetEnabled(CPhysics::ms_bDrawCoverMapCollision);
PFD_TypeFlag5.SetEnabled(CPhysics::ms_bDrawVehicleMapCollision);
PFD_TypeFlag30.SetEnabled(CPhysics::ms_bDrawStairsSlopeMapCollision);
}
else
{
PFDGROUP_TypeFlagFilter.EnableChildren();
}
}
void ToggleDrawMoverMapCollisionCB()
{
UpdateDrawCollisionOptions();
}
#endif // __PFDRAW
void ToggleDrawWeaponMapCollisionCB()
{
#if __PFDRAW
UpdateDrawCollisionOptions();
#endif // __PFDRAW
}
void ToggleDrawHorseMapCollisionCB()
{
#if __PFDRAW
UpdateDrawCollisionOptions();
#endif // __PFDRAW
}
void ToggleDrawCoverMapCollisionCB()
{
#if __PFDRAW
UpdateDrawCollisionOptions();
#endif // __PFDRAW
}
void ToggleDrawVehicleMapCollisionCB()
{
#if __PFDRAW
UpdateDrawCollisionOptions();
#endif // __PFDRAW
}
void ToggleDrawStairsSlopeMapCollisionCB()
{
#if __PFDRAW
UpdateDrawCollisionOptions();
#endif // __PFDRAW
}
void ToggleDrawDynamicCollisionCB()
{
#if __PFDRAW
UpdateDrawCollisionOptions();
#endif // __PFDRAW
}
void ToggleDrawPolygonDensityCB()
{
#if __PFDRAW
PFDGROUP_PolygonDensity.SetEnabled(CPhysics::ms_bDrawPolygonDensity);
PFD_Solid.SetEnabled(CPhysics::ms_bDrawPrimitiveDensity || CPhysics::ms_bDrawPolygonDensity);
PFDGROUP_Physics.SetEnabled(CPhysics::ms_bDrawPrimitiveDensity || CPhysics::ms_bDrawPolygonDensity);
if (CPhysics::ms_bDrawPolygonDensity)
{
bool bAnyMapFlagsOn =
CPhysics::ms_bDrawMoverMapCollision|
CPhysics::ms_bDrawWeaponMapCollision|
CPhysics::ms_bDrawHorseMapCollision|
CPhysics::ms_bDrawCoverMapCollision|
CPhysics::ms_bDrawVehicleMapCollision|
CPhysics::ms_bDrawStairsSlopeMapCollision;
if (!bAnyMapFlagsOn)
{
CPhysics::ms_bDrawMoverMapCollision = true; // turn on mover collision draw if nothing else
ToggleDrawMoverMapCollisionCB();
}
}
#endif // __PFDRAW
}
void ToggleDrawPedDensityCB()
{
PGTAMATERIALMGR->GetDebugInterface().SetRenderPedDensity(CPhysics::ms_bDrawPedDensity);
}
void ToggleDrawPavementCB()
{
PGTAMATERIALMGR->GetDebugInterface().SetRenderPavement(CPhysics::ms_bDrawPavement);
}
void ToggleDrawEdgeAnglesCB()
{
#if __PFDRAW
PFD_EdgeAngles.SetEnabled(CPhysics::ms_bDrawEdgeAngles);
#endif // __PFDRAW
}
void ToggleDrawPrimitiveDensityCB()
{
#if __PFDRAW
PFDGROUP_PrimitiveDensity.SetEnabled(CPhysics::ms_bDrawPrimitiveDensity);
PFD_Solid.SetEnabled(CPhysics::ms_bDrawPrimitiveDensity || CPhysics::ms_bDrawPolygonDensity);
PFDGROUP_Physics.SetEnabled(CPhysics::ms_bDrawPrimitiveDensity || CPhysics::ms_bDrawPolygonDensity);
if (CPhysics::ms_bDrawPrimitiveDensity)
{
bool bAnyMapFlagsOn =
CPhysics::ms_bDrawMoverMapCollision|
CPhysics::ms_bDrawWeaponMapCollision|
CPhysics::ms_bDrawHorseMapCollision|
CPhysics::ms_bDrawCoverMapCollision|
CPhysics::ms_bDrawVehicleMapCollision|
CPhysics::ms_bDrawStairsSlopeMapCollision;
if (!bAnyMapFlagsOn)
{
CPhysics::ms_bDrawMoverMapCollision = true; // turn on mover collision draw if nothing else
ToggleDrawMoverMapCollisionCB();
}
}
#endif // __PFDRAW
}
void ToggleIncludePolygonsCB()
{
#if __PFDRAW
PFD_IncludePolygons.SetEnabled(CPhysics::ms_bIncludePolygons);
#endif // __PFDRAW
}
void ToggleDrawSelectedPhysicalCollisionRecordsCB()
{
#if __PFDRAW
PFDGROUP_Physics.SetEnabled(CPhysics::ms_bDisplaySelectedPhysicalCollisionRecords);
#endif // __PFDRAW
}
void ToggleDrawAllCollisionRecordsCB()
{
#if __PFDRAW
PFDGROUP_Physics.SetEnabled(CPhysics::ms_bDisplayAllCollisionRecords);
#endif // __PFDRAW
}
void FixAllCVehiclesCB()
{
for (s32 vehicleIndex = 0; vehicleIndex < CVehicle::GetPool()->GetSize(); ++vehicleIndex)
{
if(CVehicle* pVehicle = CVehicle::GetPool()->GetSlot(vehicleIndex))
{
pVehicle->Fix();
}
}
}
void FixAllCObjectsCB()
{
for (s32 objectIndex = 0; objectIndex < CObject::GetPool()->GetSize(); ++objectIndex)
{
if(CObject* pObject = CObject::GetPool()->GetSlot(objectIndex))
{
pObject->Fix();
}
}
}
void FixAllCPhysicalsCB()
{
FixAllCObjectsCB();
FixAllCVehiclesCB();
}
void CPhysics::UpdateEntityFromFrag(void* userData)
{
if (userData)
{
CEntity* pEntity = static_cast<CEntity*>(userData);
if (pEntity->GetIsPhysical())
{
static_cast<CPhysical*>(pEntity)->UpdateEntityFromPhysics(pEntity->GetCurrentPhysicsInst(), 0);
}
}
}
void CPhysics::MarkEntityDirty(void* userData)
{
if (CEntity* pEntity = static_cast<CEntity*>(userData))
{
pEntity->GetBaseModelInfo()->SetDrawableDependenciesAsDirty();
}
}
#if WORLD_PROBE_DEBUG
void CPhysics::SetAllWorldProbeContextDrawEnabled()
{
WorldProbe::CShapeTestManager* pShapeTestMgr = WorldProbe::GetShapeTestManager();
for(int i = 0 ; i < WorldProbe::LOS_NumContexts; ++i)
{
pShapeTestMgr->ms_abContextFilters[i] = true;
}
}
void CPhysics::SetAllWorldProbeContextDrawDisabled()
{
WorldProbe::CShapeTestManager* pShapeTestMgr = WorldProbe::GetShapeTestManager();
for(int i = 0 ; i < WorldProbe::LOS_NumContexts; ++i)
{
pShapeTestMgr->ms_abContextFilters[i] = false;
}
}
#endif // WORLD_PROBE_DEBUG
bkBank *CPhysics::ms_pPhysicsBank = NULL;
bkButton *CPhysics::ms_pCreatePhysicsBankButton = NULL;
bkBank *CPhysics::ms_pFragTuneBank = NULL;
bkButton *CPhysics::ms_pCreateFragTuneBankButton = NULL;
int GetLargestBitIndex(u32 bits)
{
int index = 0;
while((bits >>= 1) > 0) ++index;
return index;
}
void CPhysics::InitWidgets()
{
bkBank *pBankOptimize = BANKMGR.FindBank("Optimization");
pBankOptimize->AddToggle("Force Dummy Objects", &ms_bForceDummyObjects);
pBankOptimize->AddToggle("Draw map collision (MOVER)", &ms_bDrawMoverMapCollision, ToggleDrawMoverMapCollisionCB);
pBankOptimize->AddToggle("Draw map collision (WEAPON)", &ms_bDrawWeaponMapCollision, ToggleDrawWeaponMapCollisionCB);
pBankOptimize->AddToggle("Draw map collision (HORSE)", &ms_bDrawHorseMapCollision, ToggleDrawHorseMapCollisionCB);
pBankOptimize->AddToggle("Draw map collision (COVER)", &ms_bDrawCoverMapCollision, ToggleDrawCoverMapCollisionCB);
pBankOptimize->AddToggle("Draw map collision (VEHICLE)",&ms_bDrawVehicleMapCollision, ToggleDrawVehicleMapCollisionCB);
pBankOptimize->AddToggle("Draw map collision (STAIRS SLOPE)", &ms_bDrawStairsSlopeMapCollision, ToggleDrawStairsSlopeMapCollisionCB);
pBankOptimize->AddToggle("Draw dynamic collision", &ms_bDrawDynamicCollision, ToggleDrawDynamicCollisionCB);
pBankOptimize->AddToggle("Draw pavement", &ms_bDrawPavement, ToggleDrawPavementCB);
pBankOptimize->AddToggle("Draw ped density", &ms_bDrawPedDensity, ToggleDrawPedDensityCB);
#if __PFDRAW
pBankOptimize->PushGroup("Polygon Density",false);
pBankOptimize->AddToggle("Draw polygon density", &ms_bDrawPolygonDensity, ToggleDrawPolygonDensityCB);
pBankOptimize->AddToggle("Draw edge angles", &ms_bDrawEdgeAngles, ToggleDrawEdgeAnglesCB);
pBankOptimize->AddTitle("Rage Profile Draw Aliases");
{
PFD_Solid.AddWidgets(*pBankOptimize);
PFD_Wireframe.AddWidgets(*pBankOptimize);
PFD_ColorMidPoint.AddWidgets(*pBankOptimize);
PFD_ColorExpBias.AddWidgets(*pBankOptimize);
PFD_PolygonAngleDensity.AddWidgets(*pBankOptimize);
PFD_NumRecursions.AddWidgets(*pBankOptimize);
PFD_MaxNeighborAngleEnabled.AddWidgets(*pBankOptimize);
PFD_MaxNeighborAngle.AddWidgets(*pBankOptimize);
}
pBankOptimize->AddSeparator();
phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_MOVER)] = 3.5f;
phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_WEAPON)] = 15.0f;
phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_HORSE)] = 3.5f;
phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_COVER)] = 3.5f;
phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_VEHICLE)] = 3.5f;
pBankOptimize->AddSlider("Rating Multiplier (MOVER)", &phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_MOVER)], 0.1f, 20.0f, 0.1f);
pBankOptimize->AddSlider("Rating Multiplier (WEAPON)", &phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_WEAPON)], 0.1f, 20.0f, 0.1f);
pBankOptimize->AddSlider("Rating Multiplier (HORSE)", &phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_HORSE)], 0.1f, 20.0f, 0.1f);
pBankOptimize->AddSlider("Rating Multiplier (COVER)", &phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_COVER)], 0.1f, 20.0f, 0.1f);
pBankOptimize->AddSlider("Rating Multiplier (VEHICLE)", &phBoundPolyhedron::sm_TypeFlagDensityScaling[GetLargestBitIndex(ArchetypeFlags::GTA_MAP_TYPE_VEHICLE)], 0.1f, 20.0f, 0.1f);
pBankOptimize->PopGroup();
pBankOptimize->PushGroup("Primitive Density",false);
pBankOptimize->AddToggle("Draw Primitive Density", &ms_bDrawPrimitiveDensity, ToggleDrawPrimitiveDensityCB);
pBankOptimize->AddToggle("Include Include Polygons", &ms_bIncludePolygons, ToggleIncludePolygonsCB);
pBankOptimize->PopGroup();
#endif // __PFDRAW
pBankOptimize->AddToggle("Toggle AllowVehs", &gbAllowVehGenerationOrRemoval);
pBankOptimize->AddToggle("Toggle AllowPeds", &gbAllowPedGeneration);
pBankOptimize->AddToggle("Toggle AllowAmbientPeds", &gbAllowAmbientPedGeneration);
pBankOptimize->AddToggle("Toggle AllowScriptBrains", &gbAllowScriptBrains);
ms_pPhysicsBank = &BANKMGR.CreateBank("Physics");
if(physicsVerifyf(ms_pPhysicsBank, "Failed to create Physics bank"))
{
ms_pCreatePhysicsBankButton = ms_pPhysicsBank->AddButton("Create physics widgets", &CPhysics::CreatePhysicsBank);
}
ms_pFragTuneBank = &BANKMGR.CreateBank("Rage Frag Tune");
if(physicsVerifyf(ms_pFragTuneBank, "Failed to create Rage Frag Tune bank"))
{
ms_pCreateFragTuneBankButton = ms_pFragTuneBank->AddButton("Create fragment tune widgets", &CPhysics::CreateFragTuneBank);
}
fragTuneStruct::UpdateEntityFunctor updateEntityFunctor;
updateEntityFunctor.Bind(&CPhysics::UpdateEntityFromFrag);
FRAGTUNE->SetUpdateEntityFunctor(updateEntityFunctor);
fragTuneStruct::SelectEntityFunctor selectEntityFunctor;
selectEntityFunctor.Bind(&CPhysics::MarkEntityDirty);
FRAGTUNE->SetSelectEntityFunctor(selectEntityFunctor);
}
bool gbVehicleImpulseModification = true;
void CPhysics::DisplayNumPartsBrokenOffFocusFrag()
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(0);
if(!pEnt)
return;
fragInst* pFragInst = pEnt->GetFragInst();
if(!pFragInst)
return;
int nNumBrokenOffParts = 0;
int nTotalNumParts = pFragInst->GetTypePhysics()->GetNumChildren();
for(int nChild = 0; nChild < nTotalNumParts; ++nChild)
{
if(pFragInst->GetChildBroken(nChild))
++nNumBrokenOffParts;
}
char zText[255];
sprintf(zText, "num broken parts/total: %d/%d", nNumBrokenOffParts, nTotalNumParts);
Color32 colour = Color_green;
if(pFragInst->GetCached() && !pFragInst->GetCacheEntry()->IsFurtherBreakable())
colour = Color_red;
grcDebugDraw::Text(VEC3V_TO_VECTOR3(pEnt->GetTransform().GetPosition())+Vector3(0.0f,0.0f,1.0f), colour, zText);
}
void DisplayRagdollDamageInfoForThisPed(const CPed* pPed)
{
Vector3 vPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition())+Vector3(0.0f,0.0f,1.0f);
u32 nNoOfLines = 0;
char zText[256];
Color32 colour = pPed->IsDead() ? Color_red : Color_green;
colour.SetAlpha(180);
if(CPhysics::ms_bDisplayPedHealth)
{
sprintf(zText, "Health: %f", pPed->GetHealth());
grcDebugDraw::Text(vPos, colour, 0, nNoOfLines*grcDebugDraw::GetScreenSpaceTextHeight(), zText);
nNoOfLines++;
}
if(CPhysics::ms_bDisplayPedFallHeight)
{
sprintf(zText, "Fall height: %f", pPed->GetFallingHeight());
grcDebugDraw::Text(vPos, colour, 0, nNoOfLines*grcDebugDraw::GetScreenSpaceTextHeight(), zText);
nNoOfLines++;
}
}
void CPhysics::DisplayRagdollDamageInfo()
{
CPed* pLocalPlayer = CGameWorld::FindLocalPlayer();
if(!pLocalPlayer)
return;
DisplayRagdollDamageInfoForThisPed(pLocalPlayer);
CEntityScannerIterator entityList = pLocalPlayer->GetPedIntelligence()->GetNearbyPeds();
CEntity* pEnt = entityList.GetFirst();
while(pEnt)
{
physicsAssert(pEnt->GetIsTypePed());
CPed* pPed = static_cast<CPed*>(pEnt);
DisplayRagdollDamageInfoForThisPed(pPed);
// Advance to the next nearby ped.
pEnt = entityList.GetNext();
}
}
bool noBreaking = true;
void CPhysics::ToggleNoPopups()
{
if (g_NoPopups)
{
PARAM_nopopups.Set("");
}
else
{
PARAM_nopopups.Set(NULL);
}
}
void CPhysics::TogglePauseUnpauseGame()
{
if (g_PauseUnpauseGame)
{
fwTimer::StartUserPause(true);
}
else
{
fwTimer::EndUserPause();
}
}
void CPhysics::CreatePhysicsBank()
{
Assertf(ms_pPhysicsBank, "Physics bank needs to be created first");
if(ms_pCreatePhysicsBankButton)//delete the create bank button
{
ms_pCreatePhysicsBankButton->Destroy();
ms_pCreatePhysicsBankButton = NULL;
}
else
{
//bank must already be setup as the create button doesn't exist so just return.
return;
}
ms_pPhysicsBank->AddToggle("Toggle NoPopups", &g_NoPopups, datCallback(CFA(CPhysics::ToggleNoPopups)), "Toggle nopopups" );
if( !PARAM_nopopups.Get() )
{
g_NoPopups = false;
}
else
{
g_NoPopups = true;
}
ms_pPhysicsBank->AddToggle("Toggle Pause/Unpause Game", &g_PauseUnpauseGame, datCallback(CFA(CPhysics::TogglePauseUnpauseGame)), "Toggle pause/unpause game");
ms_pPhysicsBank->PushGroup("Performance tests");
ms_pPhysicsBank->AddButton("Switch player to ragdoll", SwitchPlayerToRagDollCB);
ms_pPhysicsBank->AddText("Crate Stack",ms_stackModelName, sizeof(ms_stackModelName), false, &CreateStackCB);
//pBank->AddButton("Crate Stack", CreateStackCB);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Rage cloth");
ms_pPhysicsBank->AddButton("Attach flag to vehicle", AttachFlagToVehicleCB);
ms_pPhysicsBank->AddButton("Attach cape to char", AttachCapeToCharCB);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Foliage");
ms_pPhysicsBank->AddToggle("Show foliage impacts for peds", &CPhysics::ms_bShowFoliageImpactsPeds);
ms_pPhysicsBank->AddToggle("Show foliage impacts for ragdolls", &CPhysics::ms_bShowFoliageImpactsRagdolls);
ms_pPhysicsBank->AddToggle("Show foliage impacts for vehicles", &CPhysics::ms_bShowFoliageImpactsVehicles);
ms_pPhysicsBank->AddSlider("Min vehicle speed to apply foliage drag", &CVehicle::ms_fMinVehicleSpeedForFoliageDrag, 0.0f, 100.0f, 0.1f);
ms_pPhysicsBank->AddSlider("Max angular accel induced by foliage drag", &CVehicle::ms_fMaxAngAccel, 0.0, 100.0f, 0.01f);
ms_pPhysicsBank->AddSlider("Vehicle drag coefficient", &CVehicle::ms_fFoliageVehicleDragCoeff, 0.0f, 20.0f, 0.1f);
ms_pPhysicsBank->AddSlider("Vehicle drag distance scaling coeff", &CVehicle::ms_fFoliageDragDistanceScaleCoeff, 0.0f, 10.0f, 0.1f);
ms_pPhysicsBank->AddSlider("Vehicle drag bound radius scaling coeff", &CVehicle::ms_fFoliageBoundRadiusScaleCoeff, 0.0f, 100.0f, 0.1f);
ms_pPhysicsBank->AddSlider("Vehicle door force scale coeff", &CVehicle::ms_fFoliageDoorForceScaleCoeff, 0.0f, 20.0f, 0.1f);
ms_pPhysicsBank->AddSlider("Vehicle door max accel", &CVehicle::ms_fFoliageMaxDoorAccel, 0.0f, 100.0f, 0.01f);
ms_pPhysicsBank->AddSlider("Bike drag coefficient", &CBike::ms_fFoliageBikeDragCoeff, 0.0f, 20.0f, 0.1f);
ms_pPhysicsBank->AddSlider("Bike drag distance scaling coeff", &CBike::ms_fFoliageBikeDragDistanceScaleCoeff, 0.0f, 10.0f, 0.1f);
ms_pPhysicsBank->AddSlider("Bike drag bound radius scaling coeff", &CBike::ms_fFoliageBikeBoundRadiusScaleCoeff, 0.0f, 100.0f, 0.1f);
ms_pPhysicsBank->PopGroup(); // "Foliage"
ms_pPhysicsBank->PushGroup("Gravity");
const char* strGravLevelNames[] =
{
"Earth",
"Moon",
"Low",
"ZeroG"
};
CompileTimeAssert(NUM_GRAVITY_LEVELS == 4);
ms_pPhysicsBank->AddCombo("Gravity level",&ms_iSelectedGravLevel,NUM_GRAVITY_LEVELS,strGravLevelNames,SetGravityLevelCB);
ms_pPhysicsBank->AddSlider("Gravity/ ms^-2", &ms_fGravitationalAccleration, 0.0f, 20.0f, 0.1f, &SetGravitationalAccelerationCB, "Change the grav. accel.");
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Debug",false);
ms_pPhysicsBank->AddToggle("Physics counters", &gbPhysicsInstCounters);
ms_pPhysicsBank->AddToggle("Print active", &gbPrintPhysicsActive);
ms_pPhysicsBank->AddToggle("Print inactive", &gbPrintPhysicsInActive);
ms_pPhysicsBank->AddToggle("Print fixed", &gbPrintPhysicsFixed);
ms_pPhysicsBank->AddToggle("Display insts on vectormap", &gbDisplayVectorMapPhysicsInsts);
// ms_pPhysicsBank->AddToggle("Force dummy objects", &ms_bForceDummyObjects);
// ms_pPhysicsBank->AddToggle("Do Composite Octree Cull", &phBoundComposite::ms_bDoOctreePreCull);
// ms_pPhysicsBank->AddToggle("Do Composite Whole BBox Test", &phBoundComposite::ms_bDoWholeBBoxTest);
// ms_pPhysicsBank->AddToggle("Do Composite Indv BBox Test", &phBoundComposite::ms_bDoIndividualBBoxTest);
// ms_pPhysicsBank->AddToggle("Do Level Probe BBox Test", &phLevelNew::ms_bDoTestProbeBBoxTest);
// ms_pPhysicsBank->AddToggle("Do PolyBound PolyBBox Test", &phBoundPolyhedron::ms_bDoPolyBBoxCull);
ms_pPhysicsBank->AddToggle("Do Vehicle Damage", &ms_bDoVehicleDamage);
ms_pPhysicsBank->AddToggle("Do Collision Effects", &ms_bDoCollisionEffects);
ms_pPhysicsBank->AddToggle("Do Weapon Impact Effects", &ms_bDoWeaponImpactEffects);
#if __DEV
ms_pPhysicsBank->AddToggle("Do Ped Probes With Impacts", &ms_bDoPedProbesWithImpacts);
#endif
ms_pPhysicsBank->AddButton("Test Bound Performance", TestBoundPerformaceCB);
#if __DEV
ms_pPhysicsBank->AddToggle("Switch Ped to NM Ragdoll", &sbSwitchPedToNM);
#endif
ms_pPhysicsBank->AddToggle("Vehicle impact mods", &gbVehicleImpulseModification);
#if __PFDRAW
ms_pPhysicsBank->AddCombo("Synchronize render and update threads", &snPhysicsSynchronizeRenderAndUpdateThreads, PHYSICS_RENDER_UPDATE_SYNC_POINT_COUNT, PHYSICS_RENDER_UPDATE_SYNC_POINT_STRINGS);
#endif
ms_pPhysicsBank->PopGroup();
PGTAMATERIALMGR->GetDebugInterface().InitWidgets(*ms_pPhysicsBank);
if (PGTAMATERIALMGR->GetDebugInterface().GetProcObjComboNeedsUpdate())
{
PGTAMATERIALMGR->GetDebugInterface().UpdateProcObjComboBox();
}
// standard rage physics widgets
ms_pPhysicsBank->PushGroup("Level",false);
phLevelNew::AddWidgets(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Simulator",false);
phSimulator::AddWidgets(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Fragments", false);
ms_pPhysicsBank->AddToggle("noBreaking",&noBreaking);
ms_pPhysicsBank->AddToggle("fragManager::Update each SimUpdate",&CPhysics::ms_bFragManagerUpdateEachSimUpdate);
ms_pPhysicsBank->AddButton("Fix all CPhysicals",&FixAllCPhysicalsCB);
ms_pPhysicsBank->AddButton("Fix all CVehicles",&FixAllCVehiclesCB);
ms_pPhysicsBank->AddButton("Fix all CObjects",&FixAllCObjectsCB);
FRAGMGR->AddWidgets(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Materials",false);
MATERIALMGR.AddWidgets(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Breakable glass", false);
bgGlassManager::AddWidgets(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
PDR_ONLY(debugPlayback::AddWidgets(*ms_pPhysicsBank));
ms_pPhysicsBank->AddToggle("Override number of Timeslices", &CPhysics::ms_OverriderNumTimeSlices);
ms_pPhysicsBank->AddSlider("GTA Physics Timeslices", &CPhysics::ms_NumTimeSlices, 1, 8, 1);
ms_pPhysicsBank->AddToggle("Only push on last update", &CPhysics::ms_OnlyPushOnLastUpdate);
#if __DEV
CPedModelInfo::InitPedBuoyancyWidget();
#endif
#if __BANK
// Setup Explosion widgets
if(CExplosionManager::ms_needToSetupRagWidgets)
CExplosionManager::AddWidgets();
else
CExplosionManager::ms_needToSetupRagWidgets = true;
#endif
#if USE_FRAG_TUNE
ms_pPhysicsBank->PushGroup("Ragdolls");
ms_pPhysicsBank->AddSlider("Extra Allowed Penetration", &fragInstNM::sm_ExtraAllowedRagdollPenetration, 0.0f, 1.0f, 0.01f);
ms_pPhysicsBank->AddToggle("Prevent Push Collisions", &g_PreventRagdollPushCollisions);
ms_pPhysicsBank->AddToggle("Manual ragdoll skeleton update",&fragInstNM::ms_UseManualSkeletonUpdate);
ms_pPhysicsBank->PushGroup("Fix Stuck Ragdolls");
ms_pPhysicsBank->AddToggle("Enable Fix Stuck Ragdolls", &g_FixRagdollStuckInGeometry);
ms_pPhysicsBank->AddToggle("Disregard min depth for fix stuck ragdolls", &CPed::ms_bAlwaysFixStuckInGeometry);
ms_pPhysicsBank->AddSlider("Min depth for fix stuck ragdolls",&CPed::ms_fMinDepthForFixStuckInGeometry,0.0f,1.0f,0.01f);
ms_pPhysicsBank->AddToggle("Element Match", &g_FixRagdollStuckInGeometryElementMatch);
ms_pPhysicsBank->AddSlider("Fake Depth", &g_FixRagdollStuckInGeometryDepth, 0.0f, 1.0f, 0.01f);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("GTA Frag Tune",false);
ms_pPhysicsBank->AddSlider("No. Local Frags", &numFragNames, 1, 256, 0);
ms_pPhysicsBank->AddButton("Update Local Frags", UpdateFragList);
fragNames.Reset();
fragNames.PushAndGrow("Inactive");
fragNames.PushAndGrow("Activate");
pFragCombo = ms_pPhysicsBank->AddCombo("Local Frag Types", &currFragNameSelection, 2, &fragNames[0], UpdateFragList);
ms_pPhysicsBank->AddButton("Add Fragtune Widget", AddFragTuneWidgetCB);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->AddToggle( "Pull things around", &CPhysics::ms_bDebugPullThingsAround );
ms_pPhysicsBank->AddSlider( "Pull Force Multiply", &CPhysics::ms_pullForceMultiply, 0.0f, 10000.0f, 1.0f);
ms_pPhysicsBank->AddSlider( "Pull Dist Max", &CPhysics::ms_pullDistMax, 0.0f, 10000.0f, 1.0f);
ms_pPhysicsBank->PushGroup("Physics debug tools",false);
ms_pPhysicsBank->PushGroup("Mouse input",false);
phMouseInput::AddClassWidgets(*ms_pPhysicsBank);
phMouseInput::SetBoundFlagNameFunc(ArchetypeFlags::GetBoundFlagName);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Point and click gun/footsteps",false);
ms_pPhysicsBank->AddToggle( "Enable gun", &CPhysics::ms_bMouseShooter );
ms_pPhysicsBank->AddToggle( "Advanced mode", &CPhysics::ms_bAdvancedMouseShooter );
ms_pPhysicsBank->AddToggle( "Attach to entities", &CPhysics::ms_bAttachedShooterMode );
ms_pPhysicsBank->AddToggle( "Override Weapon", &CPhysics::ms_bOverrideMouseShooterWeapon );
CWeaponInfoManager::StringList& weaponNames = CWeaponInfoManager::GetNames(CWeaponInfoBlob::IT_Weapon);
if(weaponNames.GetCount() > 0)
{
ms_pPhysicsBank->AddCombo( "Weapon", &CPhysics::ms_iWeaponComboIndex, weaponNames.GetCount(), &weaponNames[0]);
}
ms_pPhysicsBank->AddToggle( "Test Mover Collision",&CBullet::ms_CollideWithMoverCollision);
ms_pPhysicsBank->AddToggle( "Enable footstep mouse triggering", &audPedFootStepAudio::sm_bMouseFootstep );
ms_pPhysicsBank->AddToggle( "Do both, bullet and footstep", &audPedFootStepAudio::sm_bMouseFootstepBullet );
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Measuring Tool",false);
ms_pPhysicsBank->AddToggle( "Turn on tool", &CPhysics::ms_bDebugMeasuringTool );
ms_pPhysicsBank->AddText("Pos1", CPhysics::ms_Pos1, sizeof(CPhysics::ms_Pos1), false, UpdatePos1);
ms_pPhysicsBank->AddText("Ptr1", CPhysics::ms_Ptr1, sizeof(CPhysics::ms_Ptr1), false);
ms_pPhysicsBank->AddText("Pos2", CPhysics::ms_Pos2, sizeof(CPhysics::ms_Pos2), false, UpdatePos2);
ms_pPhysicsBank->AddText("Ptr2", CPhysics::ms_Ptr2, sizeof(CPhysics::ms_Ptr2), false);
ms_pPhysicsBank->AddText("Diff", CPhysics::ms_Diff, sizeof(CPhysics::ms_Diff), false);
ms_pPhysicsBank->AddText("HeadingBetween (Radians)", CPhysics::ms_HeadingDiffRadians, sizeof(CPhysics::ms_HeadingDiffRadians), false);
ms_pPhysicsBank->AddText("HeadingBetween (Degrees)", CPhysics::ms_HeadingDiffDegrees, sizeof(CPhysics::ms_HeadingDiffDegrees), false);
ms_pPhysicsBank->AddText("Distance", CPhysics::ms_Distance, sizeof(CPhysics::ms_Distance), false);
ms_pPhysicsBank->AddText("Horizontal dist", CPhysics::ms_HorizDistance, sizeof(CPhysics::ms_HorizDistance), false);
ms_pPhysicsBank->AddText("Vertical dist", CPhysics::ms_VerticalDistance, sizeof(CPhysics::ms_VerticalDistance), false);
ms_pPhysicsBank->AddText("Normal1", CPhysics::ms_Normal1, sizeof(CPhysics::ms_Normal1), false, UpdateNormal1);
ms_pPhysicsBank->AddText("Normal2", CPhysics::ms_Normal2, sizeof(CPhysics::ms_Normal2), false, UpdateNormal2);
ms_pPhysicsBank->PushGroup("Physics tests",false);
ms_pPhysicsBank->PushGroup("Script command tests");
ms_pPhysicsBank->AddToggle("BRING_VEHICLE_TO_HALT at Pos1", &CPhysics::ms_bDoBringVehicleToHaltTest);
ms_pPhysicsBank->PopGroup(); // "Script command tests"
ms_pPhysicsBank->PushGroup("Shape tests");
ms_pPhysicsBank->PushGroup("LOS Ignore options");
ms_pPhysicsBank->AddToggle("Ignore see thru",&CPhysics::ms_bLOSIgnoreSeeThru);
ms_pPhysicsBank->AddToggle("Ignore shoot thru",&CPhysics::ms_bLOSIgnoreShootThru);
ms_pPhysicsBank->AddToggle("Ignore shoot thru FX",&CPhysics::ms_bLOSIgnoreShootThruFX);
ms_pPhysicsBank->AddToggle("Shoot thru glass",&CPhysics::ms_bLOSGoThroughGlass);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->AddButton( "Add focus entity to exclusion list (new API only)", AddFocusEntityToExclusionList);
ms_pPhysicsBank->AddText ( "Excluded entity list:", CPhysics::ms_zExcludedEntityList, sizeof(CPhysics::ms_zExcludedEntityList), true);
ms_pPhysicsBank->AddButton( "Clear entity exclusion list", ClearEntityExclusionList);
ms_pPhysicsBank->AddToggle( "Los test", &CPhysics::ms_bDoLineProbeTest );
ms_pPhysicsBank->AddToggle( "ASync LOS test", &CPhysics::ms_bDoAsyncLOSTest);
ms_pPhysicsBank->AddToggle( "Probe for water (synchronous)", &CPhysics::ms_bDoLineProbeWaterTest );
ms_pPhysicsBank->AddToggle( "capsule test", &CPhysics::ms_bDoCapsuleTest );
ms_pPhysicsBank->AddToggle( "bounding box test", &CPhysics::ms_bDoBoundingBoxTest );
ms_pPhysicsBank->AddToggle( "ASync swept-sphere test", &CPhysics::ms_bDoAsyncSweptSphereTest );
ms_pPhysicsBank->AddToggle( "tapered capsule batch test", &CPhysics::ms_bDoTaperedCapsuleBatchShapeTest);
ms_pPhysicsBank->AddToggle( "capsule shape batch test", &CPhysics::ms_bDoCapsuleBatchShapeTest );
ms_pPhysicsBank->AddToggle( "probe batch shape test", &CPhysics::ms_bDoProbeBatchShapeTest );
ms_pPhysicsBank->AddToggle( "test capsule against focus", &CPhysics::ms_bTestCapsuleAgainstFocusEntity );
ms_pPhysicsBank->AddToggle( "test capsule against focus bboxes", &CPhysics::ms_bTestCapsuleAgainstFocusEntityBBoxes );
ms_pPhysicsBank->AddToggle( "do boolean test", &CPhysics::ms_bDoBooleanTest);
ms_pPhysicsBank->AddSlider( "No. tests", &CPhysics::ms_iNumInstances, 1, 128, 1);
ms_pPhysicsBank->AddSlider( "test width", &CPhysics::ms_fWidth, 0.0f, 10000.0f, 0.5f);
ms_pPhysicsBank->AddSlider( "capsule width", &CPhysics::ms_fCapsuleWidth, 0.0f, 10000.0f, 0.5f);
ms_pPhysicsBank->AddSlider( "capsule width 2", &CPhysics::ms_fCapsuleWidth2, 0.0f, 10000.0f, 0.5f);
ms_pPhysicsBank->AddSlider( "bounding box test width", &CPhysics::ms_fBoundingBoxWidth, 0.0f, 10000.0f, 0.5f);
ms_pPhysicsBank->AddSlider( "bounding box test length", &CPhysics::ms_fBoundingBoxLength, 0.0f, 10000.0f, 0.5f);
ms_pPhysicsBank->AddSlider( "bounding box test height", &CPhysics::ms_fBoundingBoxHeight, 0.0f, 10000.0f, 0.5f);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
ms_pPhysicsBank->AddSlider( "second surface interp", &CPhysics::ms_fSecondSurfaceInterp, 0.0f,1.0f,0.001f);
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
ms_pPhysicsBank->AddToggle( "Player entity overlap on selected test", &CPhysics::ms_bDoPlayerEntityTest);
ms_pPhysicsBank->AddSlider( "Isec num tapered capsule", &ms_iNumIsecsTaperedCapsule,1,128,1);
ms_pPhysicsBank->AddText ( "Overlap test result",CPhysics::ms_PlayerEntityTestResult,sizeof(CPhysics::ms_PlayerEntityTestResult),false);
ms_pPhysicsBank->AddToggle( "Do initial sphere check for capsule test",&CPhysics::ms_bDoInitialSphereTestForCapsuleTest);
ms_pPhysicsBank->AddToggle( "sphere test", &CPhysics::ms_bDoSphereTest );
ms_pPhysicsBank->AddSlider( "sphere radius", &CPhysics::ms_fSphereRadius, 0.0f, 10000.0f, 0.01f );
ms_pPhysicsBank->AddToggle( "sphere test ignore fixed collision", &CPhysics::ms_bDoSphereTestNotFixed );
ms_pPhysicsBank->PopGroup(); // "Shape tests"
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Ped and vehicle history (need to run with USE_PHYSICAL_HISTORY defined)", false);
ms_pPhysicsBank->AddToggle( "Render all histories", &CPhysics::ms_bRenderAllHistories);
ms_pPhysicsBank->AddToggle( "Render focus histories", &CPhysics::ms_bRenderFocusHistory);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->AddButton("Activate focus entity", ActivateFocusEntityCB);
ms_pPhysicsBank->AddButton("Activate picked entity", ActivatePickedEntityCB);
ms_pPhysicsBank->AddToggle("Display number of broken parts on focus frag", &CPhysics::ms_bDisplayNumberOfBrokenPartsFocusFrag);
ms_pPhysicsBank->AddToggle("Display number of available ragdolls", &CPhysics::ms_bDebugRagdollCount);
ms_pPhysicsBank->AddToggle("Render focus entity external forces", &CPhysics::ms_bRenderFocusImpulses);
ms_pPhysicsBank->PushGroup("Collision records");
ms_pPhysicsBank->AddToggle("Display collision record contacts for all active CPhysicals", &CPhysics::ms_bDisplayAllCollisionRecords, ToggleDrawAllCollisionRecordsCB);
ms_pPhysicsBank->AddToggle("Display collision records for selected CPhysical", &CPhysics::ms_bDisplaySelectedPhysicalCollisionRecords, ToggleDrawSelectedPhysicalCollisionRecordsCB);
ms_pPhysicsBank->AddSlider("Contact radius", &CCollisionHistory::ms_fDebugRenderContactRadius, 0.0f, 100.0f, 0.001f);
ms_pPhysicsBank->AddSlider("Contact normal length", &CCollisionHistory::ms_fDebugRenderContactNormal, 0.0f, 100.0f, 0.001f);
ms_pPhysicsBank->AddSlider("Contact render persist frames", &CCollisionHistory::ms_nDebugRenderPersistFrames, 1, 100000, 1);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Deformable Prop Objects");
CDeformableObjectManager::GetInstance().AddWidgetsToBank(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Tunable Prop Objects");
CTunableObjectManager::GetInstance().AddWidgetsToBank(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
ms_pPhysicsBank->PushGroup("Buoyancy");
CBuoyancy::AddWidgetsToBank(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();
#if WORLD_PROBE_DEBUG
WorldProbe::CShapeTestManager* pShapeTestMgr = WorldProbe::GetShapeTestManager();
ms_pPhysicsBank->PushGroup("WorldProbe debug tools", false);
#if WORLD_PROBE_FREQUENCY_LOGGING
ms_pPhysicsBank->AddButton("Dump shape test frequency stats to TTY", WorldProbe::CShapeTestManager::DumpFreqLogToTTY);
ms_pPhysicsBank->AddButton("Clear shape test frequency stats", WorldProbe::CShapeTestManager::ClearFreqLog);
#endif // WORLD_PROBE_FREQUENCY_LOGGING
ms_pPhysicsBank->AddButton("Perform bound test using selected entity", DoObjectBoundTestCB);
ms_pPhysicsBank->AddToggle("Force synchronous shapetests", &WorldProbe::CShapeTestManager::ms_bForceSynchronousShapeTests);
ms_pPhysicsBank->AddToggle("Enable synchronous test debug rendering", &WorldProbe::CShapeTestManager::ms_bDebugDrawSynchronousTestsEnabled);
ms_pPhysicsBank->AddToggle("Enable asynchronous test debug rendering", &WorldProbe::CShapeTestManager::ms_bDebugDrawAsyncTestsEnabled);
ms_pPhysicsBank->AddToggle("Enable debug async queue rendering", &WorldProbe::CShapeTestManager::ms_bDebugDrawAsyncQueueSummary);
ms_pPhysicsBank->PushGroup("Intersection Draw Options",false);
ms_pPhysicsBank->AddSlider("Max Intersections per shape", &WorldProbe::CShapeTestManager::ms_bMaxIntersectionsToDraw,0,MAX_DEBUG_DRAW_INTERSECTIONS_PER_SHAPE,1);
ms_pPhysicsBank->AddSlider("Only Intersection index to draw", &WorldProbe::CShapeTestManager::ms_bIntersectionToDrawIndex,-1,MAX_DEBUG_DRAW_INTERSECTIONS_PER_SHAPE,1);
ms_pPhysicsBank->AddToggle("Draw Hit Point", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitPoint);
ms_pPhysicsBank->AddToggle("Draw Hit Normal", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitNormal);
ms_pPhysicsBank->AddToggle("Draw Hit Poly Normal", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitPolyNormal);
ms_pPhysicsBank->AddToggle("Draw Hit Instance Name", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitInstanceName);
ms_pPhysicsBank->AddToggle("Draw Hit Handle", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitHandle);
ms_pPhysicsBank->AddToggle("Draw Hit Component Index", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitComponentIndex);
ms_pPhysicsBank->AddToggle("Draw Hit Part Index", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitPartIndex);
ms_pPhysicsBank->AddToggle("Draw Hit Material Name", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitMaterialName);
ms_pPhysicsBank->AddToggle("Draw Hit Material Id", &WorldProbe::CShapeTestManager::ms_bDebugDrawHitMaterialId);
ms_pPhysicsBank->AddSlider("Hit point radius", &WorldProbe::CShapeTestManager::ms_fHitPointRadius, 0.01f, 10.0f, 0.01f);
ms_pPhysicsBank->AddSlider("Hit point normal length", &WorldProbe::CShapeTestManager::ms_fHitPointNormalLength, 0.01f, 10.0f, 0.01f);
ms_pPhysicsBank->PopGroup();
#if ENABLE_ASYNC_STRESS_TEST
ms_pPhysicsBank->AddSlider("Stress test probe count", &WorldProbe::CShapeTestManager::ms_uStressTestProbeCount, 1, MAX_ASYNCHRONOUS_REQUESTED_SHAPE_TESTS, 1u);
ms_pPhysicsBank->AddToggle("Stress test every frame", &WorldProbe::CShapeTestManager::ms_bAsyncStressTestEveryFrame);
ms_pPhysicsBank->AddToggle("Stress test immediate abortion", &WorldProbe::CShapeTestManager::ms_bAbortAllTestsInReverseOrderImmediatelyAfterStarting);
ms_pPhysicsBank->AddButton("Stress test once", &WorldProbe::DebugAsyncStressTestOnce);
#endif // ENABLE_ASYNC_STRESS_TEST
ms_pPhysicsBank->PushGroup("Context filtering",true);
ms_pPhysicsBank->AddButton("Enable all context types", SetAllWorldProbeContextDrawEnabled);
ms_pPhysicsBank->AddButton("Disable all context types", SetAllWorldProbeContextDrawDisabled);
for(int i = 0 ; i < WorldProbe::LOS_NumContexts; i++)
{
ms_pPhysicsBank->AddToggle(WorldProbe::GetContextString(static_cast<WorldProbe::ELosContext>(i)), &pShapeTestMgr->ms_abContextFilters[i]);
}
ms_pPhysicsBank->PopGroup();// Context filtering
ms_pPhysicsBank->PushGroup("Batched tests",true);
ms_pPhysicsBank->AddToggle("Use custom cull volume", &WorldProbe::CShapeTestManager::ms_bUseCustomCullVolume);
ms_pPhysicsBank->AddToggle("Draw cull volume", &WorldProbe::CShapeTestManager::ms_bDrawCullVolume);
ms_pPhysicsBank->PopGroup();// Batched tests.
ms_pPhysicsBank->PopGroup();// WorldProbe debug tools.
ms_pPhysicsBank->PopGroup(); // Physics debug group.
#endif // WORLD_PROBE_DEBUG
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
ms_pPhysicsBank->PushGroup("2nd surface physics", false);
CPhysical::sm_defaultSecondSurfaceConfig.AddWidgetsToBank(*ms_pPhysicsBank);
ms_pPhysicsBank->PopGroup();//2nd surface physics
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
#if NORTH_CLOTHS
CClothMgr::InitWidgets();
#endif
#endif //USE_FRAG_TUNE
}
void CPhysics::CreateFragTuneBank()
{
if (ms_pCreateFragTuneBankButton)
{
Assertf(ms_pFragTuneBank, "Physics bank needs to be created first");
Assertf(ms_pCreateFragTuneBankButton, "Physics bank needs to be created first");
ms_pCreateFragTuneBankButton->Destroy();
ms_pCreateFragTuneBankButton = NULL;
ms_pFragTuneBank->AddToggle("Frag Debug Draw", &g_FragDrawToggle, ToggleFragDraw);
ms_pFragTuneBank->AddToggle("Print breaking impulses for peds", &CPhysics::ms_bPrintBreakingImpulsesForPeds);
ms_pFragTuneBank->AddToggle("Print breaking impulses for vehicles", &CPhysics::ms_bPrintBreakingImpulsesForVehicles);
ms_pFragTuneBank->AddToggle("Print applied breaking impulses", &CPhysics::ms_bPrintBreakingImpulsesApplied);
fragInstGta::AddVehicleFragImpulseFunctionWidgets(*ms_pFragTuneBank);
ms_pFragTuneBank->AddButton("Display and place on ground", &CDebug::DisplayObjectAndPlaceOnGround);
FRAGTUNE->AddWidgets(*ms_pFragTuneBank);
}
}
#if USE_PHYSICAL_HISTORY
static float DISTANCE_TO_RENDER_AROUND_CAM = 50.0f;
#endif //USE_PHYSICAL_HISTORY
void CPhysics::UpdateHistoryRendering()
{
#if USE_PHYSICAL_HISTORY
if( ms_bRenderAllHistories )
{
CEntityIterator iterator(CEntityIterator::IteratePeds|CEntityIterator::IterateVehicles, NULL, camInterface::GetPos(), DISTANCE_TO_RENDER_AROUND_CAM);
CEntity* pEntity = iterator.GetNext();
while( pEntity )
{
if( pEntity->GetIsTypePed())
{
static_cast<CPed*>(pEntity)->m_physicalHistory.RenderHistory(pEntity);
}
else if( pEntity->GetIsTypeVehicle() )
{
static_cast<CVehicle*>(pEntity)->m_physicalHistory.RenderHistory(pEntity);
}
pEntity = iterator.GetNext();
}
}
else if( ms_bRenderFocusHistory )
{
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX; ++i)
{
CEntity* pEntity =CDebugScene::FocusEntities_Get(i);
if( pEntity )
{
if( pEntity->GetIsTypePed())
{
static_cast<CPed*>(pEntity)->m_physicalHistory.RenderHistory(pEntity);
}
else if( pEntity->GetIsTypeVehicle() )
{
static_cast<CVehicle*>(pEntity)->m_physicalHistory.RenderHistory(pEntity);
}
}
}
}
#endif
// Needs to go here and not in render debug function because it uses the batch renderer
// so no need for it to be thread safe
// if we put in render debug we might cause blocks on main thread when trying to add to debugdrawstore
// as its being rendered
#if __DEV
ms_debugDrawStore.Render();
#endif
}
void CPhysics::DisplayRagdollCount()
{
CPed * pPlayer = FindPlayerPed();
if(pPlayer)
{
const s32 nTotalNumMaleRagdolls = FRAGNMASSETMGR->GetAgentCapacity(pPlayer->GetRagdollInst()->GetType()->GetARTAssetID());
const s32 nNumAvail = FRAGNMASSETMGR->GetAgentCount(pPlayer->GetRagdollInst()->GetType()->GetARTAssetID());
grcDebugDraw::AddDebugOutput("\n\nPlayer type ragdolls in use: %i of %i\n",(nTotalNumMaleRagdolls - nNumAvail),nTotalNumMaleRagdolls);
}
}
void CPhysics::DisplayCollisionRecords()
{
if( ms_bDisplaySelectedPhysicalCollisionRecords )
{
// Null this pointer in case there is no focus ped.
CPhysical* pFocusPhysical = 0;
CEntity* pFocusEntity = CDebugScene::FocusEntities_Get(0);
// Early out if no CPhysical is selected.
if(!pFocusEntity) return;
if(!pFocusEntity->GetIsPhysical()) return;
// We must have a selected CPhysical by this stage:
pFocusPhysical = static_cast<CPhysical*>(pFocusEntity);
physicsAssertf(pFocusPhysical, "A CPhysical should be selected but pointer is NULL.");
// Have the CPhysical instance print its records on-screen.
pFocusPhysical->GetFrameCollisionHistory()->DebugRenderCollisionRecords(pFocusPhysical, true);
}
if( ms_bDisplayAllCollisionRecords )
{
#if ENABLE_PHYSICS_LOCK
phIterator activeIterator(phIterator::PHITERATORLOCKTYPE_READLOCK);
#else // ENABLE_PHYSICS_LOCK
phIterator activeIterator;
#endif // ENABLE_PHYSICS_LOCK
activeIterator.SetStateIncludeFlags(phLevelBase::STATE_FLAG_ACTIVE);
activeIterator.InitCull_All();
for(int levelIndex = activeIterator.GetFirstLevelIndex(CPhysics::GetLevel()) ;
levelIndex != phInst::INVALID_INDEX ;
levelIndex = activeIterator.GetNextLevelIndex(CPhysics::GetLevel()) )
{
phInst* pInst = CPhysics::GetLevel()->GetInstance(levelIndex);
CEntity* pEntity = CPhysics::GetEntityFromInst(pInst);
if(pEntity && pEntity->GetIsPhysical())
{
CPhysical* pPhysical = static_cast<CPhysical*>(pEntity);
pPhysical->GetFrameCollisionHistory()->DebugRenderCollisionRecords(pPhysical, false);
}
}
}
}
#if __DEV
void CPhysics::RenderFocusEntityExternalForces()
{
static dev_float fForceDrawScale = 1.0f;
static dev_float fTorqueDrawScale = 1.0f;
static dev_s32 iExpiry = 500;
// TODO: Would be nice to have a TweakScalarV.
ScalarV fForceDrawScaleV = ScalarVFromF32(fForceDrawScale);
ScalarV fTorqueDrawScaleV = ScalarVFromF32(fTorqueDrawScale);
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX; i++)
{
CEntity* pFocusEntity = CDebugScene::FocusEntities_Get(i);
if(pFocusEntity)
{
phCollider* pCollider = pFocusEntity->GetCollider();
if(pCollider)
{
Vec3V vForce;
Vec3V vTorque;
float fInvTimestep = 1.0f / fwTimer::GetRagePhysicsUpdateTimeStep();
ScalarV vInvTimestep = ScalarVFromF32(fInvTimestep);
vForce = pCollider->GetForce(vInvTimestep.GetIntrin128());
vTorque = pCollider->GetTorque(vInvTimestep.GetIntrin128());
Vec3V vPosition = pCollider->GetPosition();
ms_debugDrawStore.AddLine(vPosition,vPosition+ fForceDrawScaleV*vForce,Color_purple,iExpiry);
ms_debugDrawStore.AddLine(vPosition,vPosition+ fTorqueDrawScaleV*vTorque,Color_cyan,iExpiry);
}
}
}
}
#endif
#if __BANK
void CPhysics::SwitchPlayerToRagDollCB()
{
CEventSwitch2NM event(10000, rage_new CTaskNMRelax(1000, 10000));
FindPlayerPed()->SwitchToRagdoll(event);
}
// Performs a bound shape test using the bound obtained from the selected entity if any.
void CPhysics::DoObjectBoundTestCB()
{
CEntity* pEntity = CDebugScene::FocusEntities_Get(0);
if(!pEntity)
return;
const Matrix34 trans = MAT34V_TO_MATRIX34( pEntity->GetMatrix() );
WorldProbe::CShapeTestBoundDesc boundTestDesc;
boundTestDesc.SetDomainForTest(WorldProbe::TEST_IN_LEVEL);
boundTestDesc.SetBoundFromEntity(pEntity);
boundTestDesc.SetTransform(&trans);
boundTestDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
boundTestDesc.SetContext(WorldProbe::LOS_Unspecified);
// Issue the test. Results can be visualised using the WorldProbe debug bank in RAG.
WorldProbe::GetShapeTestManager()->SubmitTest(boundTestDesc);
}
char CPhysics::ms_stackModelName[STORE_NAME_LENGTH];
void CPhysics::CreateStackCB()
{
//Get the crate model and test it is loaded.
/*
char sCurrentDisplayObject[256];
sprintf(sCurrentDisplayObject,"CJ_ALP_CRATE_1");
*/
fwModelId nModelId;
CBaseModelInfo* pModelInfo = CModelInfo::GetBaseModelInfoFromName(ms_stackModelName, &nModelId);
if(pModelInfo==NULL)
return;
//Compute the height of the model's bounding box.
const float fHeightSeparation=0.2f+pModelInfo->GetBoundingBoxMax().z-pModelInfo->GetBoundingBoxMin().z;
//Set the number of crates we're going to use.
const int iNumCrates=10;
//Create all the crates, separated vertically by fHeightSeparation, and offset from the player.
int i;
float fHeightCount=1;
const Vector3 vPlayerHeading=VEC3V_TO_VECTOR3(FindPlayerPed()->GetTransform().GetB());
for(i=0;i<iNumCrates;i++,fHeightCount++)
{
//Set the rotation of the body to be identity.
Matrix34 bodyMat;
bodyMat.Identity();
//Set the position of the body to be offset from the player and incremented in height.
const Vector3 vPlayerPos = VEC3V_TO_VECTOR3(FindPlayerPed()->GetTransform().GetPosition());
bodyMat.d=vPlayerPos;
bodyMat.d+=vPlayerHeading*2.0f;
const float fHeight=fHeightCount*fHeightSeparation;
bodyMat.d.z+=fHeight;
//Forced load of the object.
bool bForceLoad = false;
if(!CModelInfo::HaveAssetsLoaded(nModelId))
{
CModelInfo::RequestAssets(nModelId, STRFLAG_DONTDELETE|STRFLAG_FORCE_LOAD|STRFLAG_PRIORITY_LOAD);
bForceLoad = true;
}
if(bForceLoad)
{
CStreaming::LoadAllRequestedObjects(true);
}
//Instantiate the entity.
CEntity* pDisplayObject=0;
if(pModelInfo->GetModelType()==MI_TYPE_VEHICLE)
{
pDisplayObject = CVehicleFactory::GetFactory()->Create(nModelId, ENTITY_OWNEDBY_PHYSICS, POPTYPE_RANDOM_AMBIENT, &bodyMat);
}
else if(pModelInfo->GetModelType()==MI_TYPE_PED)
{
const CControlledByInfo localNpcControl(false, false);
pDisplayObject = CPedFactory::GetFactory()->CreatePed(localNpcControl, nModelId, &bodyMat, true, true, false);
}
else if(pModelInfo->GetIsTypeObject())
{
pDisplayObject = CObjectPopulation::CreateObject(nModelId, ENTITY_OWNEDBY_RANDOM, true);
}
else
{
pDisplayObject = rage_new CBuilding( ENTITY_OWNEDBY_PHYSICS );
pDisplayObject->SetModelId(nModelId);
}
//Set up the entity's physics.
if(pDisplayObject)
{
pDisplayObject->InitPhys();
pDisplayObject->SetMatrix(bodyMat);
Printf("Test mat pos %f %f %f \n", VEC3V_ARGS(pDisplayObject->GetCurrentPhysicsInst()->GetPosition()));
CGameWorld::Add(pDisplayObject, CGameWorld::OUTSIDE );
phCollider* pCollider=PHSIM->ActivateObject(pDisplayObject->GetCurrentPhysicsInst()->GetLevelIndex());
Assertf(pCollider,"Couldn't activate object");
pCollider=0;
}
}
}
void CPhysics::AttachCapeToCharCB()
{
// do something ?? ( old code removed ... it was VERY old )
}
void CPhysics::SetGravityLevelCB()
{
SetGravityLevel((eGravityLevel)ms_iSelectedGravLevel);
}
void CPhysics::AttachFlagToVehicleCB()
{
#if 1
//Get the vehicle closest to the player.
CVehicle* pVehicle=FindPlayerPed()->GetPedIntelligence()->GetClosestVehicleInRange();
if(pVehicle)
{
//Get the cloth model info by hard-coded name.
fwModelId nModelId;
ASSERT_ONLY( CBaseModelInfo* pModelInfo = ) CModelInfo::GetBaseModelInfoFromName(strStreamingObjectName("cloth_flag",0x78475A17), &nModelId);
//Forced load of the object.
bool bForceLoad = false;
if(!CModelInfo::HaveAssetsLoaded(nModelId))
{
CModelInfo::RequestAssets(nModelId, STRFLAG_DONTDELETE|STRFLAG_FORCE_LOAD|STRFLAG_PRIORITY_LOAD);
bForceLoad = true;
}
if(bForceLoad)
{
CStreaming::LoadAllRequestedObjects(true);
}
//Create the cloth object.
Assert(pModelInfo->GetIsTypeObject());
CObject* pObject = CObjectPopulation::CreateObject(nModelId, ENTITY_OWNEDBY_RANDOM, true);
Assert(pObject);
//Set up the entity's physics and attach it to the vehicle.
if(pObject)
{
pObject->InitPhys();
Matrix34 mat = MAT34V_TO_MATRIX34(pVehicle->GetMatrix());
pObject->SetMatrix(mat);
CGameWorld::Add(pObject, CGameWorld::OUTSIDE );
//Attach cloth object as child of vehicle.
const int iVehicleBoneIndex=pVehicle->GetBoneIndex(VEH_CHASSIS);
const Vector3 vVehicleOffset(1.0f,2.0f,1.5f);
pObject->AttachToPhysicalBasic(pVehicle, (s16)iVehicleBoneIndex, ATTACH_STATE_BASIC, &vVehicleOffset, 0);
/*
//Process child attachments of vehicle to force a setting of the cloth object transform.
pVehicle->ProcessAllAttachments();
//Now transform the cloth verts to the frame of the cloth object.
phInst* pObjectInst=pObject->GetCurrentPhysicsInst();
Assertf(dynamic_cast<fragInst*>(pObjectInst), "Object with cloth must be a frag");
fragInst* pObjectFragInst=static_cast<fragInst*>(pObjectInst);
Assertf(pObjectFragInst->GetCached(), "Cloth object has no cache entry");
if(pObjectFragInst->GetCached())
{
fragCacheEntry* pObjectCacheEntry=pObjectFragInst->GetCacheEntry();
Assertf(pObjectCacheEntry, "Cloth object has a null cache entry");
fragHierarchyInst* pObjectHierInst=pObjectCacheEntry->GetHierInst();
Assertf(pObjectHierInst, "Cloth object has a null hier inst");
Assertf(1==pObjectHierInst->numEnvClothes, "Cloth object should have 1 env cloth");
environmentCloth* pEnvCloth=pObjectHierInst->envClothes[0];
pEnvCloth->GetCloth()->GetClothData().TransformVertexPositions(pObject->GetMatrix());
}
*/
}
}
#endif
}
#endif
#if __DEV
void CPhysics::ReloadFocusEntityWaterSamplesCB()
{
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX; ++i)
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(i);
if(pEnt)
{
pEnt->GetBaseModelInfo()->ReloadWaterSamples();
}
}
}
#endif
#endif //__BANK
#if RSG_PC
bool CPhysics::GetShouldNetworkBlendBeforePhysics( CPhysical* pEntity )
{
if( CPhysics::GetNumTimeSlices() == 1 &&
pEntity->GetCollider() &&
pEntity->GetCollider()->IsArticulated() )
{
if( ( MI_CAR_TECHNICAL2.IsValid() &&
pEntity->GetModelIndex() == MI_CAR_TECHNICAL2 ) ||
( MI_CAR_APC.IsValid() &&
pEntity->GetModelIndex() == MI_CAR_APC ) ||
( MI_BOAT_PATROLBOAT.IsValid() &&
pEntity->GetModelIndex() == MI_BOAT_PATROLBOAT ) ||
( MI_BOAT_DINGHY5.IsValid() &&
pEntity->GetModelIndex() == MI_BOAT_DINGHY5 ) ||
( pEntity->GetModelIndex() == MI_PLANE_TULA ) ||
( pEntity->GetModelIndex() == MI_PLANE_SEABREEZE ) ||
( pEntity->GetModelIndex() == MI_CAR_ZHABA ) )
{
CVehicle* pVehicle = static_cast< CVehicle* >( pEntity );
if( pVehicle->GetIsInWater() ||
( pVehicle->GetNetworkObject() &&
static_cast<CNetObjPhysical *>( pVehicle->GetNetworkObject() )->IsNetworkObjectInWater() ) )
{
if( fwTimer::GetFrameCount() % 2 == 0 )
{
return true;
}
}
}
}
return false;
}
#else
bool CPhysics::GetShouldNetworkBlendBeforePhysics( CPhysical* UNUSED_PARAM( pEntity ) )
{
return false;
}
#endif
#if PHYSICS_REQUEST_LIST_ENABLED
CLinkList<RegdEnt> CPhysics::ms_physicsReqList;
//
// name: CPhysics::AddToPhysicsRequestList
// description: Add object to the physics request list
void CPhysics::AddToPhysicsRequestList(CEntity* pEntity)
{
// some entities have scaling, or are baked into the static collision mesh. in
// such cases, we do not want to add them to the physics request list
if (pEntity->IsBaseFlagSet(fwEntity::NO_INSTANCED_PHYS))
{
return;
}
// don't add buildings to physics request list. Just request physics directly
if(pEntity->GetIsTypeBuilding() || pEntity->GetIsTypeMLO())
{
// TIDY
}
else
{
RegdEnt reggedEnt(pEntity);
CLink<RegdEnt>* pLink = ms_physicsReqList.Insert(reggedEnt);
#if !__FINAL
if (!pLink)
{
Warningf("Ran out of space in physics request list. Probably due to lack of memory causing a backlog of requests");
}
#endif
pLink=NULL;
}
}
//
// name: UpdateRequestList
// description: Go through request list and check if physics is in memory and then add object to physics world
void CPhysics::UpdatePhysicsRequestList()
{
STRVIS_AUTO_CONTEXT(strStreamingVisualize::PHYSICS);
CLink<RegdEnt>* pLink = ms_physicsReqList.GetFirst()->GetNext();
while(pLink != ms_physicsReqList.GetLast())
{
RegdEnt pEntity(pLink->item);
CLink<RegdEnt>* pLastLink = pLink;
pLink = pLink->GetNext();
if(pEntity && !pEntity->GetCurrentPhysicsInst())
{
CBaseModelInfo* pModelInfo = pEntity->GetBaseModelInfo();
// If physics have loaded
if (pModelInfo->GetHasLoaded() && (pModelInfo->GetHasBoundInDrawable() || pModelInfo->GetFragType()) )
{
// Initialise physics and remove from list
int res = pEntity->InitPhys();
if( res == CPhysical::INIT_OK )
{
pEntity->AddPhysics();
ms_physicsReqList.Remove(pLastLink);
}
}
else
{
CModelInfo::RequestAssets(pEntity->GetBaseModelInfo(), 0); // we may prefer to let scene streaming handle this - IanK
}
Assert(!pEntity->GetIsTypeBuilding());
}
else
{
ms_physicsReqList.Remove(pLastLink);
}
}
}
#endif //PHYSICS_REQUEST_LIST_ENABLED