// Title : Physical.cpp // Author : Richard Jobling // Started : 17/11/98 #include "Physical.h" // Rage headers #include "control/replay/ReplayExtensions.h" #include "crskeleton/skeleton.h" #include "debug/DebugEntityState.h" #include "fragment/cache.h" #include "grcore/debugdraw.h" #include "entity/altskeletonextension.h" #include "fwanimation/directorcomponentsyncedscene.h" #include "fwscene/stores/staticboundsstore.h" #include "math/simplemath.h" #include "phbound/boundbvh.h" #include "phbound/boundcomposite.h" #include "phcore/phmath.h" #include "phsolver/contactmgr.h" #include "physics/archetype.h" #include "physics/sleep.h" #include "fwmaths/Random.h" #include "fwmaths/Vector.h" #include "fwscene/world/WorldLimits.h" #include "physics/constraintspherical.h" #include "physics/constraintfixed.h" #include "physics/constraintattachment.h" #include "vehicles/metadata/vehicleseatinfo.h" #include "phbound/support.h" #if __BANK #include "renderer/DrawLists/drawListNY.h" #include "scene/EntityIterator.h" #endif #if __ASSERT // Used for traking physics creation backtraces #include "system/stack.h" #endif // Game headers #include "modelinfo/ModelInfo.h" #include "Cloth/Cloth.h" #include "Cloth/ClothMgr.h" #include "control/gamelogic.h" #include "Debug/debugscene.h" #include "event/EventGroup.h" #include "event/EventScript.h" #include "Frontend/MiniMapCommon.h" #include "game/Cheat.h" #include "game/modelindices.h" #include "modelinfo/Vehiclemodelinfo.h" #include "Network/NetworkInterface.h" #include "network/Debug/NetworkDebug.h" #include "network/general/NetworkUtil.h" #include "objects/object.h" #include "objects/Door.h" #include "pathserver/ExportCollision.h" #include "peds/AssistedMovement.h" #include "peds/Ped.h" #include "peds/PedIntelligence.h" #include "peds/PlayerSpecialAbility.h" #include "physics/Breakable.h" #include "physics/GtaInst.h" #include "physics/GtaMaterialManager.h" #include "physics/physics_channel.h" #include "physics/Physics.h" #include "physics/WorldProbe/worldprobe.h" #include "Physics/RagdollConstraints.h" #include "pickups/Pickup.h" #include "scene/Physical.h" #include "scene/scene.h" #include "scene/world/gameWorld.h" #include "scene/world/gameWorldWaterHeight.h" #include "script/Handlers/GameScriptEntity.h" #include "script/script.h" #include "security/plugins/linkdatareporterplugin.h" #include "security/obfuscatedtypes.h" #include "system/TamperActions.h" #include "system/Timer.h" #include "Task/Motion/TaskMotionBase.h" #include "vehicles/Automobile.h" #include "vehicles/Bike.h" #include "vehicles/Boat.h" #include "vehicles/Heli.h" #include "vehicles/Vehicle.h" #include "vehicles/train.h" #include "renderer/water.h" #include "renderer/RenderPhases/RenderPhaseWaterReflection.h" #include "vfx/vehicleglass/VehicleGlassManager.h" #include "Vfx/VfxHelper.h" #include "Task/Physics/TaskNM.h" #if __BANK #include "fwnet/netobject.h" #include "system/stack.h" #endif // __BANK // defines to calc how many collision steps are required #define MIN_RAD_VEHICLE (0.4f) #define MIN_RAD_VEHICLE_SQR (MIN_RAD_VEHICLE*MIN_RAD_VEHICLE) #define MIN_RAD_PED (0.3f) #define MIN_RAD_PED_SQR (MIN_RAD_PED*MIN_RAD_PED) #define MIN_RAD_OBJECT (0.3f) #define MIN_RAD_OBJECT_SQR (MIN_RAD_OBJECT*MIN_RAD_OBJECT) #define MIN_RAD_LAMPPOST (0.3f) #define MIN_RAD_LAMPPOST_SQR (MIN_RAD_LAMPPOST*MIN_RAD_LAMPPOST) #define HIGHSPEED_ELASTICITY_MULT_PED (2.0f) // defines to shape collision responses #define PHYSICAL_VEHICLEVERTICALCOLLISIONDAMPING 0.3f #define PHYSICAL_VEHICLECOLLISION_FORCEMULT 1.0f //1.4f #define PHYSICAL_VEHICLECOLLISION_TORQUEDAMP 0.8f #define PHYSICAL_VEHICLEBUILDING_FORCEMULT 1.2f #define PHYSICAL_VEHICLEBULIDING_TORQUEDAMP 0.8f #define PHYSICAL_VEHICLEFRICTION_FORCEDAMP (-0.3f) #define PHYSICAL_DEFAULT_HEALTH (100.0f) PARAM(debugFixedByNetwork, "Log local player fixed by network changes"); ENTITY_OPTIMISATIONS() SCENE_OPTIMISATIONS() AI_VEHICLE_OPTIMISATIONS() INSTANTIATE_RTTI_CLASS(CPhysical,0xA00A9444); AUTOID_IMPL(CObjectCoverExtension); AUTOID_IMPL(CBrokenAndHiddenComponentFlagsExtension); namespace entity_commands { void SetEntityAsNoLongerNeeded(CPhysical *pEntity, int &EntityIndex); }; #if DR_ENABLED bool CPhysical::smb_RecordAttachments=false; CPhysical::RecordEntityAttachmentFunc CPhysical::sm_RecordAttachmentsFunc=0; #endif #if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE CPhysical::CSecondSurfaceConfig CPhysical::sm_defaultSecondSurfaceConfig( 0.2f, // Sink lift factor 1.0f, // TangV2 lift factor 1.5f, // Max TangV2 lift factor 0.5f // Drag factor ); CPhysical::CSecondSurfaceConfig::CSecondSurfaceConfig() : m_fSinkLiftFactor(0.0f), m_fTangV2LiftFactor(0.0f), m_fMaxTangV2LiftFactor(0.0f), m_fDragFactor(0.0f) { } CPhysical::CSecondSurfaceConfig::CSecondSurfaceConfig(float fSinkLiftFactor, float fTangV2LiftFactor, float fMaxTangV2LiftFactor, float fDragFactor) : m_fSinkLiftFactor(fSinkLiftFactor), m_fTangV2LiftFactor(fTangV2LiftFactor), m_fMaxTangV2LiftFactor(fMaxTangV2LiftFactor), m_fDragFactor(fDragFactor) { } #if __BANK void CPhysical::CSecondSurfaceConfig::AddWidgetsToBank(bkBank& bank) { bank.AddSlider("Sink lift factor", &m_fSinkLiftFactor, 0.0f, 100.0f, 0.1f); bank.AddSlider("Tang2V lift factor", &m_fTangV2LiftFactor, 0.0f, 100.0f, 0.1f); bank.AddSlider("Max Tank2V lift factor", &m_fMaxTangV2LiftFactor, 0.0f, 100.0f, 0.1f); bank.AddSlider("Drag coefficient", &m_fDragFactor, 0.0f, 100.0f, 0.1f); } #endif // BANK #endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE BANK_ONLY(CDebugObjectIDQueue CPhysical::ms_aDebugObjectIDs); #if __ASSERT bool CPhysical::sm_enableNoCollisionPlaneFixDebug = false; #endif CPhysical::CPhysical(const eEntityOwnedBy ownedBy) : CDynamicEntity(ownedBy), m_fHealth(PHYSICAL_DEFAULT_HEALTH), m_fMaxHealth(PHYSICAL_DEFAULT_HEALTH), m_MatrixTransformPtr(NULL), m_bDontResetDamageFlagsOnCleanupMissionState(false), m_pWeaponDamageInfos(NULL) #if __BANK ,m_iDebugObjectId(sInvalidObjectID) #endif { #if ENABLE_MATRIX_MEMBER Mat34V m(V_IDENTITY); SetTransform(m); SetTransformScale(1.0f, 1.0f); m_MatrixTransformPtr = GetTransform().GetMatrixPtr(); #else Mat34V m(V_IDENTITY); fwMatrixTransform* trans = rage_new fwMatrixTransform(m); SetTransform(trans); m_MatrixTransformPtr = trans->GetMatrixPtr(); #endif EnableCollision(); // RA - unused //m_fPrevDistFromCam = PHYSICAL_MAX_DIST_FROM_CAMERA; m_nPhysicalFlags.Clear(); m_specificRelGroupHash = 0; CRenderPhaseWaterReflectionInterface::UpdateInWater(this, m_nPhysicalFlags.bIsInWater); m_canOnlyBeDamagedByEntity = NULL; m_cantBeDamagedByEntity = NULL; #if !__NO_OUTPUT m_LogSetMatrixCalls = false; m_LogVisibiltyCalls = false; m_LogDeletions = false; m_LogVariationCalls = false; #endif #if __ASSERT m_physicsBacktrace = 0; #endif BANK_ONLY(sprintf(m_DebugLogName, "")); } CPhysical::~CPhysical() { #if GTA_REPLAY replayAssertf(!CReplayExtensionManager::HasAnyExtension(this), "Replay Extensions not removed before entity deletion. Pls bug to Andrew Collinson if this happens"); #endif /*RAGE if (m_pRealTimeShadow) { Assert(m_pRealTimeShadow->m_pPhysical==this); g_realTimeShadowMan.ReturnRealTimeShadow(m_pRealTimeShadow); }*/ // not allowed to delete objects if they are registered with the network object manager Assert(!GetNetworkObject()); if(GetType() != ENTITY_TYPE_VEHICLE) { // Vehicles can be destroyed from threads other than the update thread, and fwAltSkeletonExtension::RemoveExtension // is only safe to call from the update thread. However, vehicles don't have an fwAltSkeletonExtension anyway... fwAltSkeletonExtension::RemoveExtension(*this); } if(m_pWeaponDamageInfos) { delete m_pWeaponDamageInfos; m_pWeaponDamageInfos = NULL; } DeleteAllChildren(true); // The DeleteAllChildren call will entail child entities being destructed before parent entities // and therefore child attachment extensions before parent attachment extensions DetachFromParentAndChildren( #if __ASSERT DETACH_FLAG_DONT_ASSERT_IF_IN_VEHICLE | DETACH_FLAG_IN_PHYSICAL_DESTRUCTOR #else DETACH_FLAG_IN_PHYSICAL_DESTRUCTOR #endif ); Assertf(m_MatrixTransformPtr == static_cast(GetTransform()).GetMatrixPtr(), "The matrix for this entity has been changed - so the m_MatrixTransformPtr was out of date"); // GTA_RAGE_PHYSICS // DeleteInst(); // called in CEntity::~CEntity anyway BANK_ONLY(ResetDebugObjectID()); } void CPhysical::SetIsInWater(const bool isInWater) { if ( static_cast< bool >( m_nPhysicalFlags.bIsInWater ) == isInWater ) return; CRenderPhaseWaterReflectionInterface::UpdateInWater(this, isInWater); m_nPhysicalFlags.bIsInWater = isInWater; UpdateWorldFromEntity(); } // add and remove for physics world void CPhysical::AddPhysics() { //---------------------------------------------------------------------------------- if(GetCurrentPhysicsInst()) { Assert(GetTransform().IsOrthonormal()); // update physics position UpdatePhysicsFromEntity(true); if(GetFragInst()) { } else if(GetCurrentPhysicsInst()->GetArchetype() && !GetCurrentPhysicsInst()->IsInLevel()) { // update physics instance's matrix to match physics entity matrix GetCurrentPhysicsInst()->SetMatrix(GetMatrix()); // hack - bug 877346 if (GetOwnedBy() == ENTITY_OWNEDBY_COMPENTITY){ CPhysics::GetSimulator()->AddFixedObject(GetCurrentPhysicsInst()); // to get stairs working when part of a rayfire start / end object } else // end of hack if(IsBaseFlagSet(fwEntity::IS_FIXED)) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, true); CPhysics::GetSimulator()->AddInactiveObject(GetCurrentPhysicsInst()); if(GetIsAttached() && GetAttachParent() != NULL && static_cast(GetAttachParent())->GetIsPhysical()) { CPhysical *pParent = static_cast(GetAttachParent()); if(pParent->GetCurrentPhysicsInst() && pParent->GetCurrentPhysicsInst()->GetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL)) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,true); } } } else if(!IsBaseFlagSet(fwEntity::SPAWN_PHYS_ACTIVE) || GetIsAnyFixedFlagSet() || !IsCollisionEnabled()) { CPhysics::GetSimulator()->AddInactiveObject(GetCurrentPhysicsInst()); } else { CPhysics::GetSimulator()->AddActiveObject(GetCurrentPhysicsInst(), false); } } if(GetCurrentPhysicsInst()->IsInLevel()) { fwAttachmentEntityExtension *extension = GetAttachmentExtension(); if(extension && extension->IsAttachStateBasicDerived()) { extension->SetAttachFlag(ATTACH_FLAG_INST_ADDED_LATE, true); if( !extension->GetAttachFlag(ATTACH_FLAG_COL_ON) ) { CPhysics::GetLevel()->SetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES)); } GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE,true); if(CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex())) { PHSIM->DeactivateObject(GetCurrentPhysicsInst()); } } } #if __DEV if(GetIsTypeVehicle() && GetCollider()) ((CVehicle *)this)->m_pDebugColliderDO_NOT_USE = GetCollider(); #endif if(!IsInPathServer()) { if(CPathServerGta::MaybeAddDynamicObject(this)) CAssistedMovementRouteStore::MaybeAddRouteForDoor(this); } // If game code / script has requested that this boat has collision disabled, we set the appropriate include flags here. if(!IsCollisionEnabled()) { bool completely = false; if(NetworkInterface::IsGameInProgress() && GetNetworkObject()) { CNetObjEntity* netEnt = SafeCast(CNetObjEntity, GetNetworkObject()); if(netEnt) { completely = netEnt->GetDisableCollisionCompletely(); } } DisableCollision(NULL, completely); } } #if __ASSERT if(GetIsTypePed() || GetIsTypeVehicle()) { // Track this AddPhysics() call for peds and vehicles m_physicsBacktrace = sysStack::RegisterBacktrace(); } #endif const CBaseModelInfo* pModelInfo = GetBaseModelInfo(); if (!pModelInfo->GetHasDrawableProxyForWaterReflections()) { if (GetIsTypeVehicle()) { const CVehicleModelInfo* pVehicleModelInfo = static_cast(pModelInfo); if (pVehicleModelInfo->GetVehicleType() != VEHICLE_TYPE_BOAT && pVehicleModelInfo->GetVehicleType() != VEHICLE_TYPE_PLANE && pVehicleModelInfo->GetVehicleType() != VEHICLE_TYPE_SUBMARINE) { m_nFlags.bUseMaxDistanceForWaterReflection = true; } } else if (GetIsTypePed()) { m_nFlags.bUseMaxDistanceForWaterReflection = true; } else if (GetIsTypeObject()) { bool bHasBuoyancy = false; GET_2D_EFFECTS_NOLIGHTS(const_cast(pModelInfo)); for (int i = 0; i < iNumEffects; i++) { const C2dEffect* pEffect = (*pa2dEffects)[i]; if (pEffect->GetType() == ET_BUOYANCY) { bHasBuoyancy = true; break; } } m_nFlags.bUseMaxDistanceForWaterReflection = !bHasBuoyancy; } } } void CPhysical::RemovePhysics() { phInst *pPhysInst = GetCurrentPhysicsInst(); //Necessary to re-instate inst include and activation flags if( GetIsAttached() ) DetachFromParent(0); if(pPhysInst) { if (IsUsingKinematicPhysics()) { DisableKinematicPhysics(); } DetachFromParentAndChildren(DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS); if(IsFragInst(pPhysInst)) { // don't do anything for fragInst's // the fragManager should take care of removing from physics world } else if(pPhysInst->IsInLevel()) { CPhysics::GetSimulator()->DeleteObject(pPhysInst->GetLevelIndex()); } } if(IsInPathServer()) { CAssistedMovementRouteStore::MaybeRemoveRouteForDoor(this); CPathServerGta::MaybeRemoveDynamicObject(this); } } void CPhysical::RemoveBlip(u32 blipType) { Assert((eBLIP_TYPE)blipType < BLIP_TYPE_MAX); if(!GetCreatedBlip()) return; CMiniMap::RemoveBlipAttachedToEntity(this, (eBLIP_TYPE)blipType); SetCreatedBlip(false); #if __ASSERT CMiniMapBlip* pBlip = CMiniMap::GetBlipAttachedToEntity(this, (eBLIP_TYPE)blipType, 0); if(pBlip) { Assert(!CMiniMap::IsBlipIdInUse(pBlip->m_iUniqueId)); } #endif } void CPhysical::RemoveConstraints(phInst* pInst) { DetachFromParentAndChildren(DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS); // Reset cuff pointers for Peds if (GetIsTypePed()) { CPed* pPed = static_cast(this); if (static_cast(pPed->GetRagdollInst()) == pInst && pPed->GetRagdollConstraintData()) { pPed->GetRagdollConstraintData()->RemoveRagdollConstraints(); } } PHCONSTRAINT->RemoveActiveConstraints(pInst); } void CPhysical::UpdatePhysicsFromEntity(bool bWarp) { phInst* pCurrentPhysInst = GetCurrentPhysicsInst(); if(pCurrentPhysInst) { if(bWarp) { // delete this collider from the manifold manager? CPhysics::ClearManifoldsForInst(pCurrentPhysInst); GetFrameCollisionHistory()->Reset(); } const Mat34V& matrix = GetMatrixRef(); SetPreviousPosition(VEC3V_TO_VECTOR3(matrix.GetCol3())); pCurrentPhysInst->SetMatrix(matrix); if(phCollider* pCollider = GetCollider()) { pCollider->SetColliderMatrixFromInstance(); // With new update order we need this to be set correctly even when not warping pCollider->SetLastInstanceMatrix(matrix); if( bWarp ) { pCollider->ClearNonPenetratingAfterTeleport(); } Assertf(bWarp || !IsCollisionEnabled()|| (VEC3V_TO_VECTOR3(pCurrentPhysInst->GetMatrix().GetCol3() - pCollider->GetLastInstanceMatrix().GetCol3())).Mag2() < 100.0f, "%s:UpdatePhysicsFromEntity - moved too far without using warp", GetDebugName()); #if __DEV if(GetIsTypeVehicle()) ((CVehicle *)this)->m_pDebugColliderDO_NOT_USE = GetCollider(); #endif } if(pCurrentPhysInst->IsInLevel()) { CPhysics::GetLevel()->UpdateObjectLocation(pCurrentPhysInst->GetLevelIndex()); // if we're using a fragInst need to let it know we've moved as well if(pCurrentPhysInst->GetClassType() >= PH_INST_FRAG_GTA) ((fragInst *)pCurrentPhysInst)->ReportMovedBySim(); } // Update the entity's bounds in the pathserver system. if(IsInPathServer()) { CPathServerGta::UpdateDynamicObject(this); } } } void CPhysical::ActivatePhysics() { /* #if NAVMESH_EXPORT // Avoid crashes in non __FINAL collision exporter tools if(CNavMeshDataExporter::IsExportingCollision()) return; #endif */ #if GTA_REPLAY //we need a physics object for replay in some cases (e.g. to attach rope) but we don't want to ever activate it if(CReplayMgr::IsReplayInControlOfWorld()) { return; } #endif // might want to override this in CPed, in case we need to do stuff for ragdoll if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel()) { #if __DEV if(GetIsAttached() && !GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION) && (GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_BASIC||GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_WORLD)) { Displayf("Trying to activate object(%s) which has a BASIC attachment.", GetModelName()); sysStack::PrintStackTrace(); } #endif // __DEV if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) CPhysics::GetSimulator()->ActivateObject(GetCurrentPhysicsInst()->GetLevelIndex(), GetCollider()); #if __DEV if(GetIsTypeVehicle() && GetCollider()) ((CVehicle *)this)->m_pDebugColliderDO_NOT_USE = GetCollider(); #endif } // else // { // Assertf(false, "Trying to activate physical not in the physics level"); // AddPhysics(); // } } void CPhysical::DeActivatePhysics() { // might want to override this in CPed, in case we need to do stuff for ragdoll if(GetCurrentPhysicsInst()) { if(GetCurrentPhysicsInst()->IsInLevel()) { if(CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex())) CPhysics::GetSimulator()->DeactivateObject(GetCurrentPhysicsInst()->GetLevelIndex()); } } } void CPhysical::SetFixedPhysics(bool bFixed, bool bNetwork) { #if DR_ENABLED debugPlayback::RecordFixedStateChange(*this, bFixed, bNetwork); #endif // DR_ENABLED if(GetCurrentPhysicsInst()) { if(bFixed) { if (bNetwork) { if (PARAM_debugFixedByNetwork.Get()) { Displayf("%s[0x%p] SETTING FIXED BY NETWORK", GetLogName(), this); sysStack::PrintStackTrace(); } SetBaseFlag(fwEntity::IS_FIXED_BY_NETWORK); } else SetBaseFlag(fwEntity::IS_FIXED); DisablePhysicsIfFixed(); if(GetCurrentPhysicsInst()) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, true); } } else { if (bNetwork) { if (PARAM_debugFixedByNetwork.Get()) { Displayf("%s[0x%p] UNSETTING FIXED BY NETWORK", GetLogName(), this); sysStack::PrintStackTrace(); } ClearBaseFlag(fwEntity::IS_FIXED_BY_NETWORK); } else { ClearBaseFlag(fwEntity::IS_FIXED); #if __BANK if(GetIsAnyFixedFlagSet()) { sysStack::PrintStackTrace(); Warningf("Unfixing something but it is still fixed: 0x%p '%s'. Fixed: %s Fixed by Network: %s", this, GetModelName(), IsBaseFlagSet( fwEntity::IS_FIXED ) ? "TRUE" : "FALSE", IsBaseFlagSet( fwEntity::IS_FIXED_BY_NETWORK ) ? "TRUE" : "FALSE" ); } #endif // #if __BANK } #if __ASSERT if(!GetIsAnyFixedFlagSet() && GetIsTypeVehicle() && static_cast(this)->InheritsFromTrain() && !static_cast(this)->AllowedToActivate()) { sysStack::PrintStackTrace(); Warningf("Someone may be trying to activate a train: 0x%p '%s'.",this,GetModelName()); } if(GetIsTypeVehicle() && !IsCollisionEnabled()) { sysStack::PrintStackTrace(); Warningf("Unfixing vehicle with collision disabled 0x%p '%s'. It will probably fall through the world.",this,GetModelName()); } #endif // __ASSERT // might have been originally added as fixed, so need to if(GetCurrentPhysicsInst()) { // Don't reactivate the ped capsule if the disable ped capsule flag is set. CPed* pPed = GetIsTypePed() ? ((CPed*)this) : NULL; bool bReactivatePhysics = !pPed || !pPed->GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsule) || (pPed->GetCurrentPhysicsInst()!=pPed->GetAnimatedInst()); if (bReactivatePhysics) { bool bAttachedToGround = false; if (GetAttachmentExtension() && GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_PED_ON_GROUND) { bAttachedToGround = true; } bool bBoatInLowLodMode = false; if(GetIsTypeVehicle() && ((CVehicle*)this)->InheritsFromBoat() && ((CBoat*)this)->GetAnchorHelper().UsingLowLodMode()) { bBoatInLowLodMode = true; } //The IS_FIXED state was toggled so this object can be activated // If this object is attached to another object (using basic attach mode), it will be flagged such that it should // never activate. Don't touch this flag in that case, let the detach call reset it. if((bAttachedToGround || (!(GetIsAttached() && (GetAttachmentExtension()->IsAttachStateBasicDerived() || GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_WORLD )))) && !bBoatInLowLodMode) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false); } // ped's need physics to be active all the time (for now - sandy) // sconde: I'm not sure we need to activate peds at all any longer (shouldn't that happen as needed as part of CPed::ProcessIsAsleep?) but we certainly // don't want to activate dead peds as their mover collision has been setup to only collide with the map so if they are on top of a physical object/vehicle // they will fall right through on activation if(GetIsTypePed() && !GetIsAnyFixedFlagSet() && GetCurrentPhysicsInst()->IsInLevel() && (static_cast(this)->GetDeathState() != DeathState_Dead || !static_cast(this)->ShouldBeDead())) { if(!GetIsAttached() || !(GetAttachmentExtension()->IsAttachStateBasicDerived() || GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_WORLD) ) { ActivatePhysics(); } } } } } } else { if (bNetwork) AssignBaseFlag(fwEntity::IS_FIXED_BY_NETWORK, bFixed); else AssignBaseFlag(fwEntity::IS_FIXED, bFixed); } } void CPhysical::DisablePhysicsIfFixed() { if(GetIsAnyFixedFlagSet()) { if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel() && CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex())) { if(GetIsTypePed()) static_cast(this)->DeactivatePhysicsAndDealWithRagdoll(); else DeActivatePhysics(); Assertf(GetCollider() == NULL,"Was unable to deactivate object '%s' even though it should be fixed.",GetDebugName()); } } } void CPhysical::SetVelocity(const Vector3& vecMoveSpeed) { if(GetCurrentPhysicsInst()==NULL || !GetCurrentPhysicsInst()->IsInLevel()) return; physicsAssertf( GetIgnoreVelocityCheck() || GetCurrentPhysicsInst()->GetArchetype() && vecMoveSpeed.Mag2() <= (square(GetCurrentPhysicsInst()->GetArchetype()->GetMaxSpeed())+0.01f), "vecMoveSpeed (%f) is above archetype's maximum allowed speed of %f (%s)", vecMoveSpeed.Mag(), GetCurrentPhysicsInst()->GetArchetype()->GetMaxSpeed(), GetModelName()); Assert(vecMoveSpeed.x==vecMoveSpeed.x && vecMoveSpeed.y==vecMoveSpeed.y && vecMoveSpeed.z==vecMoveSpeed.z); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex()) && (vecMoveSpeed.x!=0.0f || vecMoveSpeed.y!=0.0f || vecMoveSpeed.z!=0.0f)) ActivatePhysics(); if(GetCollider() && GetCurrentPhysicsInst()->IsInLevel()) { Assertf(!GetIsTypePed() || IsLessThanAll(MagSquared(RCC_VEC3V(vecMoveSpeed)), ScalarV(DEFAULT_VELOCITY_LIMIT * DEFAULT_VELOCITY_LIMIT)), "Setting invalid collider velocity: (%f, %f, %f).", VEC3V_ARGS(RCC_VEC3V(vecMoveSpeed))); GetCollider()->SetVelocity(vecMoveSpeed); GetCollider()->ClampVelocity(); } } const Vector3& CPhysical::GetVelocity() const { const phCollider* pCollider = GetCollider(); if (pCollider) { Assertf(!GetIsTypePed() || IsLessThanAll(MagSquared(pCollider->GetVelocity()), ScalarV(DEFAULT_VELOCITY_LIMIT * DEFAULT_VELOCITY_LIMIT)), "Collider velocity was invalid: (%f, %f, %f). Articulated(%s)", VEC3V_ARGS(pCollider->GetVelocity()), (pCollider->IsArticulated() ? "true" : "false")); Assertf(IsFiniteAll(pCollider->GetVelocity()), "Collider velocity was invalid: (%f, %f, %f). Articulated(%s)", VEC3V_ARGS(pCollider->GetVelocity()), pCollider->IsArticulated() ? "true" : "false"); return RCC_VECTOR3(pCollider->GetVelocity()); } else if(m_Buoyancy.m_buoyancyFlags.bLowLodBuoyancyMode) { Assertf(IsFiniteAll(RCC_VEC3V(m_Buoyancy.GetVelocityWhenInLowLodMode())), "Low LOD buoyancy velocity was invalid: (%f, %f, %f).", VEC3V_ARGS(RCC_VEC3V(m_Buoyancy.GetVelocityWhenInLowLodMode()))); return m_Buoyancy.GetVelocityWhenInLowLodMode(); } return VEC3_ZERO; } const Vector3& CPhysical::GetReferenceFrameVelocity() const { const phCollider* pCollider = GetCollider(); if (pCollider) { return RCC_VECTOR3(pCollider->GetReferenceFrameVelocity()); } if(GetIsAttached() && GetAttachParent() != NULL && static_cast(GetAttachParent())->GetIsTypeVehicle() && static_cast(GetAttachParent())->InheritsFromTrain()) { CPhysical* pAttachParent = static_cast(GetAttachParent()); return pAttachParent->GetVelocity(); } return VEC3_ZERO; } const Vector3& CPhysical::GetReferenceFrameAngularVelocity() const { const phCollider* pCollider = GetCollider(); if (pCollider) { return RCC_VECTOR3(pCollider->GetReferenceFrameAngularVelocity()); } if(GetIsAttached() && GetAttachParent() != NULL && static_cast(GetAttachParent())->GetIsTypeVehicle() && static_cast(GetAttachParent())->InheritsFromTrain()) { CPhysical* pAttachParent = static_cast(GetAttachParent()); return pAttachParent->GetAngVelocity(); } return VEC3_ZERO; } Vec3V_Out CPhysical::CalculateVelocityFromMatrices(Mat34V_In mOld, Mat34V_In mNew, ScalarV_In scInvTimeStep) const { //Calculate the velocity. Vec3V vVelocity = (mNew.GetCol3() - mOld.GetCol3()) * scInvTimeStep; return vVelocity; } void CPhysical::SetVelocityFromMatrices(Mat34V_In mOld, Mat34V_In mNew, ScalarV_In scInvTimeStep) { //Set the velocity. SetVelocity(VEC3V_TO_VECTOR3(CalculateVelocityFromMatrices(mOld, mNew, scInvTimeStep))); } void CPhysical::SetAngVelocity(const Vector3& vecTurnSpeed) { if(GetCurrentPhysicsInst()==NULL || !GetCurrentPhysicsInst()->IsInLevel()) return; Assertf(vecTurnSpeed.Mag() < DEFAULT_MAX_ANG_SPEED * 15.0f,"CPhysical::SetAngVelocity vecTurnSpeed[%f %f %f][%f] !< (DEFAULT_MAX_ANG_SPEED[%f] * 15.0f)[%f]",vecTurnSpeed.x,vecTurnSpeed.y,vecTurnSpeed.z,vecTurnSpeed.Mag(),DEFAULT_MAX_ANG_SPEED,(DEFAULT_MAX_ANG_SPEED * 15.0f)); Assert(vecTurnSpeed.x==vecTurnSpeed.x && vecTurnSpeed.y==vecTurnSpeed.y && vecTurnSpeed.z==vecTurnSpeed.z); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex()) && (vecTurnSpeed.x!=0.0f || vecTurnSpeed.y!=0.0f || vecTurnSpeed.z!=0.0f)) ActivatePhysics(); if(GetCollider() && GetCurrentPhysicsInst()->IsInLevel()) GetCollider()->SetAngVelocity(vecTurnSpeed); } const Vector3& CPhysical::GetAngVelocity() const { const phCollider* pCollider = GetCollider(); if (pCollider) return RCC_VECTOR3(pCollider->GetAngVelocity()); return VEC3_ZERO; } Vec3V_Out CPhysical::CalculateAngVelocityFromQuaternions(QuatV_In qOrientationOld, QuatV_In qOrientationNew, ScalarV_In scInvTimeStep) const { //Calculate the angular velocity from the orientations. Vec3V vAngularVelocity; if(!IsCloseAll(qOrientationOld, qOrientationNew, ScalarV(V_FLT_SMALL_6))) { //Calculate the change in orientation. QuatV qOrientationChange = Multiply(qOrientationNew, Invert(qOrientationOld)); //Calculate the angular velocity. vAngularVelocity = Scale(ComputeAxisAnglePrecise(qOrientationChange),scInvTimeStep); } else { //Clear the angular velocity. vAngularVelocity = Vec3V(V_ZERO); } return vAngularVelocity; } Vec3V_Out CPhysical::CalculateAngVelocityFromMatrices(Mat34V_In mOld, Mat34V_In mNew, ScalarV_In scInvTimeStep) const { //Grab the old orientation. QuatV qOrientationOld = QuatVFromMat33V(mOld.GetMat33()); //Grab the new orientation. QuatV qOrientationNew = QuatVFromMat33V(mNew.GetMat33()); return CalculateAngVelocityFromQuaternions(qOrientationOld,qOrientationNew,scInvTimeStep); } void CPhysical::SetAngVelocityFromMatrices(Mat34V_In mOld, Mat34V_In mNew, ScalarV_In scInvTimeStep) { //Set the angular velocity. SetAngVelocity(VEC3V_TO_VECTOR3(CalculateAngVelocityFromMatrices(mOld, mNew, scInvTimeStep))); } #if ENABLE_MATRIX_MEMBER void CPhysical::SetTransform(Mat34V_In transform, bool bUpdateGameWorld /* = true */, bool bUpdatePhysics /* = true */, bool bWarp /* = false */) #else void CPhysical::SetTransform(fwTransform* transform, bool bUpdateGameWorld /* = true */, bool bUpdatePhysics /* = true */, bool bWarp /* = false */) #endif { #if ENABLE_MATRIX_MEMBER CDynamicEntity::SetTransform(transform, bUpdateGameWorld, bUpdatePhysics, bWarp); m_MatrixTransformPtr = CDynamicEntity::GetTransform().GetMatrixPtr(); #else Assertf(transform->IsMatrixTransform(), "All CPhysicals are expected to have matrix transforms!"); CDynamicEntity::SetTransform(transform, bUpdateGameWorld, bUpdatePhysics, bWarp); m_MatrixTransformPtr = static_cast(transform)->GetMatrixPtr(); #endif } Mat34V_Out CPhysical::GetMatrixPrePhysicsUpdate() const { //Grab the physics instance. phInst* pInst = GetCurrentPhysicsInst(); if(pInst) { return pInst->GetMatrix(); } else { return GetMatrix(); } } void CPhysical::SetMass(float fNewMass) { Assert(fNewMass > 0.0f); phArchetypeDamp* pGtaArchetype = GetPhysArch(); if(pGtaArchetype) { CBaseModelInfo* pModelInfo = GetBaseModelInfo(); // check if object still has original physics archetype if(pModelInfo && pModelInfo->GetPhysics() == pGtaArchetype) { // if so then we need to clone it so that we can modify values pGtaArchetype = static_cast(pGtaArchetype->Clone()); // pass new archetype to the objects physinst -> should have 1 ref only GetCurrentPhysicsInst()->SetArchetype(pGtaArchetype); } pGtaArchetype->SetMass(fNewMass); Vector3 angInertia = VEC3V_TO_VECTOR3(pGtaArchetype->GetBound()->GetComputeAngularInertia(fNewMass)); pGtaArchetype->SetAngInertia(angInertia); if(GetCollider()) { // If object is active then collider needs updating with new mass and ang inertia GetCollider()->InitInertia(); } } } void CPhysical::SetHealth(const float fNewHealth, bool ASSERT_ONLY(bNetCall), bool ASSERT_ONLY(bAssertMissingDamageEvents)) { // can't change health on objects controlled by another machine #if __ASSERT if (!bNetCall && GetNetworkObject()) { Assertf(!GetNetworkObject()->IsClone(), "Clone %s is having health altered illegally", GetNetworkObject()->GetLogName()); Assertf(!GetNetworkObject()->IsPendingOwnerChange() || m_fHealth==fNewHealth, "%s is having health altered illegally while migrating", GetNetworkObject()->GetLogName()); } #endif // trigger a damaged event if (fNewHealth < m_fHealth) { GetEventGlobalGroup()->Add(CEventEntityDamaged(this)); } // trigger a destroyed event if (m_fHealth > 0.0001f && fNewHealth < 0.0001f) { GetEventGlobalGroup()->Add(CEventEntityDestroyed(this)); } if (GetIsTypePed()) { CPlayerSpecialAbilityManager::ChargeEvent(ACET_RESET_LOW_HEALTH, static_cast(this)); // set or remove the injured status if appropriate CPed* pPed= static_cast(this); #if __ASSERT && __BANK //Debug missing damage events due to ped health being set to 0 without calling UpdateDamageTracker() if (pPed->GetNetworkObject() && NetworkDebug::DebugPedMissingDamageEvents() && bAssertMissingDamageEvents) { const bool isTheFatalBlow = (m_fHealth > pPed->GetDyingHealthThreshold() && fNewHealth < pPed->GetDyingHealthThreshold()); if (isTheFatalBlow) { static_cast(pPed->GetNetworkObject())->CheckDamageEventOnDie(); } } #endif // __ASSERT && __BANK if (pPed->ShouldUseInjuredMovement()) { if (!pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsInjured)) { pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_IsInjured, true); // don't reset the on foot clip set immediately, it will be applied once its streamed in // (See CPed::ProcessControl_Animation) } } else { if (pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsInjured)) { pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_IsInjured, false); // if we're using the injured movement set, get rid of it. CTaskMotionBase* pTask = pPed->GetPrimaryMotionTaskIfExists(); if ( pTask) { pTask->ResetOnFootClipSet(true); } } } } #if __BANK if (IsNetworkClone() && m_fHealth > 0.0f && fNewHealth == 0.0f && GetIsTypePed()) { const CPed* pPed = static_cast(this); AI_LOG("[Health]\n"); AI_LOG_WITH_ARGS_IF_SCRIPT_OR_PLAYER_PED(pPed, "Clone ped %s is setting health to zero, previous health %.2f\n", AILogging::GetDynamicEntityNameSafe(pPed), GetHealth()); AI_LOG_STACK_TRACE(1); AI_LOG("[/Health]\n"); } #endif // __BANK m_fHealth=fNewHealth; } void CPhysical::ChangeHealth( float fHealthChange ) { // can't change health on objects controlled by another machine Assert(!IsNetworkClone() || fHealthChange == 0.0f); // trigger a damaged event if (fHealthChange < 0.0f) { GetEventGlobalGroup()->Add(CEventEntityDamaged(this)); } // trigger a destroyed event if (m_fHealth > 0.0001f && -fHealthChange > m_fHealth) { GetEventGlobalGroup()->Add(CEventEntityDestroyed(this)); } m_fHealth += fHealthChange; if (GetIsTypePed()) { CPlayerSpecialAbilityManager::ChargeEvent(ACET_CHANGED_HEALTH, static_cast(this), m_fHealth); } } float CPhysical::GetMaxSpeed() const { CBaseModelInfo* pBaseModelInfo = GetBaseModelInfo(); phInst* pCurrentPhysicsInst = GetCurrentPhysicsInst(); phArchetype* pArchetype = NULL; if( pBaseModelInfo && pCurrentPhysicsInst ) { pArchetype = pCurrentPhysicsInst->GetArchetype(); } if( pArchetype ) { phArchetypeDamp* pPhysics = pBaseModelInfo->GetPhysics(); if( pPhysics ) { return pPhysics->GetMaxSpeed(); } } return DEFAULT_MAX_SPEED; } void CPhysical::SetWeaponDamageInfo(CEntity* pEntity, u32 hash, u32 time, bool meleeHit) { WeaponDamageInfo weaponDamageInfo; weaponDamageInfo.pWeaponDamageEntity = pEntity; weaponDamageInfo.weaponDamageHash = hash; weaponDamageInfo.weaponDamageTime = time; weaponDamageInfo.bMeleeHit = meleeHit; SetWeaponDamageInfo(weaponDamageInfo); } void CPhysical::SetWeaponDamageInfo(WeaponDamageInfo weaponDamageInfo) { if(!m_pWeaponDamageInfos) { m_pWeaponDamageInfos = rage_new WeaponDamageInfos; Assert(m_pWeaponDamageInfos); } int iIndex = m_pWeaponDamageInfos->Find(weaponDamageInfo); if (iIndex != -1) { // Keep it at the top most of the list. if(iIndex != m_pWeaponDamageInfos->GetCount() - 1) { m_pWeaponDamageInfos->Delete(iIndex); m_pWeaponDamageInfos->Push(weaponDamageInfo); } else { // Update damage time. (*m_pWeaponDamageInfos)[iIndex].weaponDamageTime = weaponDamageInfo.weaponDamageTime; (*m_pWeaponDamageInfos)[iIndex].bMeleeHit = weaponDamageInfo.bMeleeHit; } return; } if(m_pWeaponDamageInfos->IsFull()) { m_pWeaponDamageInfos->Delete(0); } m_pWeaponDamageInfos->Push(weaponDamageInfo); CEntity* pEntity = weaponDamageInfo.pWeaponDamageEntity; if (pEntity) { CPed *pInflictorPed = NULL; if (pEntity->GetIsTypeVehicle()) { CVehicle *pInflictorVehicle = (CVehicle *)pEntity; pInflictorPed = pInflictorVehicle->GetDriver(); } else if (pEntity->GetIsTypePed()) { pInflictorPed = (CPed *)pEntity; } if (pInflictorPed && pInflictorPed->IsPlayer()) { CPlayerInfo *pPlayerInfo = pInflictorPed->GetPlayerInfo(); Assertf(pPlayerInfo, "CPhysical::SetWeaponDamageInfo - player ped doesn't have valid PlayerInfo"); if (pPlayerInfo) { #if __BANK if (NetworkInterface::IsGameInProgress() && NetworkDebug::DebugWeaponDamageEvent() && GetIsTypeVehicle()) { const CVehicle* vec = static_cast(this); if (vec) { netObject* obj = vec->GetNetworkObject(); netObject* objinflictor = pInflictorPed->GetNetworkObject(); if (obj && objinflictor) { gnetWarning("[DebugWeaponDamageEvent] SetWeaponDamageInfo: Vehicle:%s, Inflictor:%s", obj->GetLogName(), objinflictor->GetLogName()); sysStack::PrintStackTrace(); } } } #endif // __BANK //It's possible that the ped is punching a wall and doing damage to them self, don't allow this. if (this->GetIsTypePed() && pInflictorPed != this) { pPlayerInfo->bHasDamagedAtLeastOnePed = true; if(!CPedType::IsAnimalType(((CPed*)this)->GetPedType())) { pPlayerInfo->bHasDamagedAtLeastOneNonAnimalPed = true; } } else if (this->GetIsTypeVehicle()) { pPlayerInfo->bHasDamagedAtLeastOneVehicle = true; } } } } } u32 CPhysical::GetWeaponDamagedTimeByEntity(CEntity* pEntity) const { if(m_pWeaponDamageInfos) { for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].pWeaponDamageEntity == pEntity) { return (*m_pWeaponDamageInfos)[i].weaponDamageTime; } } } return 0; } u32 CPhysical::GetWeaponDamagedTimeByHash(u32 uWeaponHash) const { if(m_pWeaponDamageInfos) { for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].weaponDamageHash == uWeaponHash) { return (*m_pWeaponDamageInfos)[i].weaponDamageTime; } } } return 0; } void CPhysical::MakeAnonymousDamageByEntity(CEntity* pEntity) { if(m_pWeaponDamageInfos) { for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].pWeaponDamageEntity == pEntity) { (*m_pWeaponDamageInfos)[i].pWeaponDamageEntity = NULL; } } } } //void CPhysical::SetWeaponDamageEntity(CEntity* pEntity) //{ // if(!m_pWeaponDamageEntities) // { // m_pWeaponDamageEntities = rage_new DamageEntities; // Assert(m_pWeaponDamageEntities); // } // // RegdEnt pRegedEnt(pEntity); // int iIndex = m_pWeaponDamageEntities->Find(pRegedEnt); // if (iIndex != -1) // { // // Keep it at the top most of the list. // if(iIndex != m_pWeaponDamageEntities->GetCount() - 1) // { // m_pWeaponDamageEntities->Delete(iIndex); // m_pWeaponDamageEntities->Push(pRegedEnt); // } // return; // } // // if(m_pWeaponDamageEntities->IsFull()) // { // m_pWeaponDamageEntities->Delete(0); // } // m_pWeaponDamageEntities->Push(pRegedEnt); // // if (pEntity) // { // CPed *pInflictorPed = NULL; // if (pEntity->GetIsTypeVehicle()) // { // CVehicle *pInflictorVehicle = (CVehicle *)pEntity; // pInflictorPed = pInflictorVehicle->GetDriver(); // } // else if (pEntity->GetIsTypePed()) // { // pInflictorPed = (CPed *)pEntity; // } // // if (pInflictorPed && pInflictorPed->IsPlayer()) // { // CPlayerInfo *pPlayerInfo = pInflictorPed->GetPlayerInfo(); // Assertf(pPlayerInfo, "CPhysical::SetWeaponDamageEntity - player ped doesn't have valid PlayerInfo"); // if (pPlayerInfo) // { //#if __BANK // if (NetworkInterface::IsGameInProgress() && NetworkDebug::DebugWeaponDamageEvent() && GetIsTypeVehicle()) // { // const CVehicle* vec = static_cast(this); // if (vec) // { // netObject* obj = vec->GetNetworkObject(); // netObject* objinflictor = pInflictorPed->GetNetworkObject(); // if (obj && objinflictor) // { // gnetWarning("[DebugWeaponDamageEvent] SetWeaponDamageEntity: Vehicle:%s, Inflictor:%s", obj->GetLogName(), objinflictor->GetLogName()); // sysStack::PrintStackTrace(); // } // } // } //#endif // __BANK // // //It's possible that the ped is punching a wall and doing damage to them self, don't allow this. // if (this->GetIsTypePed() && pInflictorPed != this) // { // pPlayerInfo->bHasDamagedAtLeastOnePed = true; // } // else if (this->GetIsTypeVehicle()) // { // pPlayerInfo->bHasDamagedAtLeastOneVehicle = true; // } // } // } // } //} CEntity* CPhysical::GetWeaponDamageEntityPedSafe() const { CEntity* pEntity = GetWeaponDamageEntity(); // To be super safe now at the end of the project, this check could probably be removed after GTA V ships if (NetworkInterface::IsGameInProgress()) { // Last resort digging out last ped damager if (m_pWeaponDamageInfos && (!pEntity || !pEntity->GetIsTypePed())) { for(int i = m_pWeaponDamageInfos->GetCount() - 1; i >= 0; --i) { if((*m_pWeaponDamageInfos)[i].pWeaponDamageEntity && (*m_pWeaponDamageInfos)[i].pWeaponDamageEntity->GetIsTypePed()) { pEntity = (*m_pWeaponDamageInfos)[i].pWeaponDamageEntity; break; } } } } return pEntity; } bool CPhysical::HasBeenDamagedByEntity(CEntity* pEntity) const { if(!m_pWeaponDamageInfos) { return false; } for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].pWeaponDamageEntity == pEntity) { return true; } } return false; } bool CPhysical::HasBeenDamagedByHash(u32 hash) const { if(!m_pWeaponDamageInfos) { return false; } for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].weaponDamageHash == hash) { return true; } } return false; } bool CPhysical::HasBeenDamagedByGeneralWeaponType(int generalWeaponType) const { if(!m_pWeaponDamageInfos) { return false; } for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if(CWeaponInfoManager::CheckDamagedGeneralWeaponType((*m_pWeaponDamageInfos)[i].weaponDamageHash, generalWeaponType)) { return true; } } return false; } bool CPhysical::HasBeenDamagedByAnyPed() const { if(!m_pWeaponDamageInfos) { return false; } for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].pWeaponDamageEntity && (*m_pWeaponDamageInfos)[i].pWeaponDamageEntity->GetIsTypePed()) { return true; } } return false; } bool CPhysical::HasBeenDamagedByAnyObject() const { if(!m_pWeaponDamageInfos) { return false; } for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].pWeaponDamageEntity && (*m_pWeaponDamageInfos)[i].pWeaponDamageEntity->GetIsTypeObject()) { return true; } } return false; } bool CPhysical::HasBeenDamagedByAnyVehicle() const { if(!m_pWeaponDamageInfos) { return false; } // We store the driver rather than the vehicle now (in CPedDamageCalculator::ApplyDamageAndComputeResponse). So we need to check the weapon hash for vehicle damage. for(int i = 0; i < m_pWeaponDamageInfos->GetCount(); i++) { if((*m_pWeaponDamageInfos)[i].pWeaponDamageEntity && (*m_pWeaponDamageInfos)[i].pWeaponDamageEntity->GetIsTypeVehicle()) { return true; } if((*m_pWeaponDamageInfos)[i].weaponDamageHash == WEAPONTYPE_RUNOVERBYVEHICLE || (*m_pWeaponDamageInfos)[i].weaponDamageHash == WEAPONTYPE_RAMMEDBYVEHICLE) { return true; } } return false; } #if __ASSERT void CPhysical::SpewDamageHistory( ) const { if (!m_pWeaponDamageInfos) return; pedDebugf3("[PedDamageCalculator] Damage History for: %s", GetDebugName()); const u32 currTime = sysTimer::GetSystemMsTime(); for (int i=0; iGetCount(); i++) { const WeaponDamageInfo& damageInfo = (*m_pWeaponDamageInfos)[i]; const char* dname = "NULL"; CEntity* entity = damageInfo.pWeaponDamageEntity; if (entity) { dname = entity->GetDebugName(); if (entity->GetIsPhysical()) { CPhysical* physical = SafeCast(CPhysical, entity); dname = physical->GetDebugName(); } } const CWeaponInfo* wi = CWeaponInfoManager::GetInfo< CWeaponInfo >(damageInfo.weaponDamageHash); const char* wname = (wi && wi->GetName()) ? wi->GetName() : "unknown"; pedDebugf3("[PedDamageCalculator] .... Damage info for index: %d", i); pedDebugf3("[PedDamageCalculator] .......... Inflictor: %s", dname); pedDebugf3("[PedDamageCalculator] ............. Weapon: %s, %u", wname, damageInfo.weaponDamageHash); pedDebugf3("[PedDamageCalculator] ............... Time: %u (%u)", currTime - damageInfo.weaponDamageTime, currTime); } } #endif void CPhysical::SetupMissionState() { CDynamicEntity::SetupMissionState(); BANK_ONLY(InitDebugName()); } void CPhysical::ClearAllCachedObjectCoverObstructed() { // Get a handle to the extension, if any CObjectCoverExtension* pExtension = GetExtension(); if( pExtension ) { pExtension->ClearAllCachedObjectCoverObstructed(); } } void CPhysical::SetCachedObjectCoverObstructed(int cachedObjectCoverIndex) { // Get a handle to the extension, if any CObjectCoverExtension* pExtension = GetExtension(); if( !pExtension ) { // need to create the extension first pExtension = rage_new CObjectCoverExtension; GetExtensionList().Add( *pExtension ); } // mark the given index bit if( AssertVerify(pExtension) ) { pExtension->SetCachedObjectCoverObstructed(cachedObjectCoverIndex); } } bool CPhysical::HasExpiredObjectCoverObstructed() const { // Get a handle to the extension, if any const CObjectCoverExtension* pExtension = GetExtension(); if( pExtension ) { // report whether obstruction was marked and has expired return pExtension->IsCoverObstructedTimeExpired(); } // by default no obstruction marked return false; } bool CPhysical::IsCachedObjectCoverObstructed(int cachedObjectCoverIndex) const { // Get a handle to the extension, if any const CObjectCoverExtension* pExtension = GetExtension(); if( pExtension ) { // report the status for the requested index return pExtension->IsCachedObjectCoverObstructed(cachedObjectCoverIndex); } // by default no obstruction marked return false; } void CPhysical::UpdateEntityFromPhysics(phInst *pInst, int nLoop) { bool bInvalidResult = false; /* VecBoolV finite = ( IsFinite(pInst->GetMatrix().GetCol0()) & IsFinite(pInst->GetMatrix().GetCol1()) & IsFinite(pInst->GetMatrix().GetCol2()) & IsFinite(pInst->GetMatrix().GetCol3()) ); // don't care about the 4th column finite |= VecBoolV(V_F_F_F_T);*/ bool isFinite = ( FPIsFinite(pInst->GetMatrix().GetCol0().GetXf()) & FPIsFinite(pInst->GetMatrix().GetCol0().GetYf()) & FPIsFinite(pInst->GetMatrix().GetCol0().GetZf()) & FPIsFinite(pInst->GetMatrix().GetCol1().GetXf()) & FPIsFinite(pInst->GetMatrix().GetCol1().GetYf()) & FPIsFinite(pInst->GetMatrix().GetCol1().GetZf()) & FPIsFinite(pInst->GetMatrix().GetCol2().GetXf()) & FPIsFinite(pInst->GetMatrix().GetCol2().GetYf()) & FPIsFinite(pInst->GetMatrix().GetCol2().GetZf()) & FPIsFinite(pInst->GetMatrix().GetCol3().GetXf()) & FPIsFinite(pInst->GetMatrix().GetCol3().GetYf()) & FPIsFinite(pInst->GetMatrix().GetCol3().GetZf()) ); if (!isFinite)//IsEqualIntAll(finite,VecBoolV(V_T_T_T_T))) { Assertf(false, "%s:Physics returned an invalid matrix for object:\n[%5.3f\t %5.3f\t %5.3f\t %5.3f\t]\n[%5.3f\t %5.3f\t %5.3f\t %5.3f\t]\n[%5.3f\t %5.3f\t %5.3f\t %5.3f\t]\n", GetDebugName(), pInst->GetMatrix().GetCol0().GetXf(), pInst->GetMatrix().GetCol1().GetXf(), pInst->GetMatrix().GetCol2().GetXf(), pInst->GetMatrix().GetCol3().GetXf(), pInst->GetMatrix().GetCol0().GetYf(), pInst->GetMatrix().GetCol1().GetYf(), pInst->GetMatrix().GetCol2().GetYf(), pInst->GetMatrix().GetCol3().GetYf(), pInst->GetMatrix().GetCol0().GetZf(), pInst->GetMatrix().GetCol1().GetZf(), pInst->GetMatrix().GetCol2().GetZf(), pInst->GetMatrix().GetCol3().GetZf()); bInvalidResult = true; } else if(!IsEqualIntAll(pInst->GetMatrix().IsOrthonormal3x3V(ScalarVFromF32(REJUVENATE_ERROR_NEW_VEC)), VecBoolV(V_T_T_T_T))) { Assertf(false, "%s:Physics returned a non-ortho matrix:\n[%5.3f\t %5.3f\t %5.3f\t %5.3f\t]\n[%5.3f\t %5.3f\t %5.3f\t %5.3f\t]\n[%5.3f\t %5.3f\t %5.3f\t %5.3f\t]\n", GetDebugName(), pInst->GetMatrix().GetCol0().GetXf(), pInst->GetMatrix().GetCol1().GetXf(), pInst->GetMatrix().GetCol2().GetXf(), pInst->GetMatrix().GetCol3().GetXf(), pInst->GetMatrix().GetCol0().GetYf(), pInst->GetMatrix().GetCol1().GetYf(), pInst->GetMatrix().GetCol2().GetYf(), pInst->GetMatrix().GetCol3().GetYf(), pInst->GetMatrix().GetCol0().GetZf(), pInst->GetMatrix().GetCol1().GetZf(), pInst->GetMatrix().GetCol2().GetZf(), pInst->GetMatrix().GetCol3().GetZf()); bInvalidResult = true; } else if(MagSquared(pInst->GetMatrix().GetCol3()).Getf() > 1.0e12f) // 1,000km squared { ASSERT_ONLY( const Vec3V currentPos = CEntity::GetTransform().GetPosition(); ) Assertf(false, "%s:Physics returned a position outside world (%5.3f, %5.3f, %5.3f). Entity is currently at (%5.3f, %5.3f, %5.3f).", GetDebugName(), pInst->GetMatrix().GetCol3().GetXf(), pInst->GetMatrix().GetCol3().GetYf(), pInst->GetMatrix().GetCol3().GetZf(), currentPos.GetXf(), currentPos.GetYf(), currentPos.GetZf() ); bInvalidResult = true; } else if( (pInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid()!=pInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid()) || (!m_nPhysicalFlags.bModifiedBounds && pInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid() > 5.0f * GetBaseModelInfo()->GetBoundingSphereRadius())) { // For vehicles we can sometimes validly sweep wheels a long way away from the centroid so increase the radius tolerance with speed. if(!GetIsTypeVehicle() || (!m_nPhysicalFlags.bModifiedBounds && pInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid() > (4.0f + (fwTimer::GetTimeStep() * GetVelocity().Mag())) * GetBaseModelInfo()->GetBoundingSphereRadius()) ) { Displayf( "%s:Physics returned an invalid bounding sphere. Centroid radius=%5.3f (model radius=%5.3f)", GetDebugName(), pInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid(), GetBaseModelInfo()->GetBoundingSphereRadius()); bInvalidResult = true; } } if(bInvalidResult) { Warningf("CPhysical::UpdateEntityFromPhysics - invalid result detected - will attempt to switch back to animation"); #if __ASSERT static bool dumpedInvalidResultManifolds = false; if(!dumpedInvalidResultManifolds) { dumpedInvalidResultManifolds = true; CPhysics::GetSimulator()->DumpManifoldsWithInstance(pInst); } if (GetIsTypePed()) { CPed* pPed = static_cast(this); static bool bAssertedAlready = false; if (!bAssertedAlready) { phBoundComposite* pRagdollBound = (phBoundComposite*)pPed->GetRagdollInst()->GetArchetype()->GetBound(); for(int iBound=0; iBoundGetNumBounds(); iBound++) { Vec3V boundPos = pRagdollBound->GetCurrentMatrix(iBound).GetCol3(); Vector3 boundOffset = RCC_VECTOR3(boundPos) - GetObjectMtx(0).d; if(boundOffset.Mag2() > 20.0f*20.0f) { bAssertedAlready = true; Printf("\n****************** Begin debug spew for CPhysical::UpdateEntityFromPhysics() ******************\n"); Printf("Animated Inst Matrix:"); Printf(" %f %f %f %f", pPed->GetAnimatedInst()->GetMatrix().GetCol0().GetXf(), pPed->GetAnimatedInst()->GetMatrix().GetCol1().GetXf(), pPed->GetAnimatedInst()->GetMatrix().GetCol2().GetXf(), pPed->GetAnimatedInst()->GetMatrix().GetCol3().GetXf()); Printf(" %f %f %f %f", pPed->GetAnimatedInst()->GetMatrix().GetCol0().GetYf(), pPed->GetAnimatedInst()->GetMatrix().GetCol1().GetYf(), pPed->GetAnimatedInst()->GetMatrix().GetCol2().GetYf(), pPed->GetAnimatedInst()->GetMatrix().GetCol3().GetYf()); Printf(" %f %f %f %f", pPed->GetAnimatedInst()->GetMatrix().GetCol0().GetZf(), pPed->GetAnimatedInst()->GetMatrix().GetCol1().GetZf(), pPed->GetAnimatedInst()->GetMatrix().GetCol2().GetZf(), pPed->GetAnimatedInst()->GetMatrix().GetCol3().GetZf()); Printf("Ragdoll Inst Matrix:"); Printf(" %f %f %f %f", pPed->GetRagdollInst()->GetMatrix().GetCol0().GetXf(), pPed->GetRagdollInst()->GetMatrix().GetCol1().GetXf(), pPed->GetRagdollInst()->GetMatrix().GetCol2().GetXf(), pPed->GetRagdollInst()->GetMatrix().GetCol3().GetXf()); Printf(" %f %f %f %f", pPed->GetRagdollInst()->GetMatrix().GetCol0().GetYf(), pPed->GetRagdollInst()->GetMatrix().GetCol1().GetYf(), pPed->GetRagdollInst()->GetMatrix().GetCol2().GetYf(), pPed->GetRagdollInst()->GetMatrix().GetCol3().GetYf()); Printf(" %f %f %f %f", pPed->GetRagdollInst()->GetMatrix().GetCol0().GetZf(), pPed->GetRagdollInst()->GetMatrix().GetCol1().GetZf(), pPed->GetRagdollInst()->GetMatrix().GetCol2().GetZf(), pPed->GetRagdollInst()->GetMatrix().GetCol3().GetZf()); Printf("Entity Transform:"); Printf(" %f %f %f %f", pPed->GetTransform().GetMatrix().GetCol0().GetXf(), pPed->GetTransform().GetMatrix().GetCol1().GetXf(), pPed->GetTransform().GetMatrix().GetCol2().GetXf(), pPed->GetTransform().GetMatrix().GetCol3().GetXf()); Printf(" %f %f %f %f", pPed->GetTransform().GetMatrix().GetCol0().GetYf(), pPed->GetTransform().GetMatrix().GetCol1().GetYf(), pPed->GetTransform().GetMatrix().GetCol2().GetYf(), pPed->GetTransform().GetMatrix().GetCol3().GetYf()); Printf(" %f %f %f %f", pPed->GetTransform().GetMatrix().GetCol0().GetZf(), pPed->GetTransform().GetMatrix().GetCol1().GetZf(), pPed->GetTransform().GetMatrix().GetCol2().GetZf(), pPed->GetTransform().GetMatrix().GetCol3().GetZf()); for (int ibone = 0; ibone < GetSkeleton()->GetBoneCount(); ibone++) { Vector3 vObjectOffset = pPed->GetObjectMtx(ibone).d - pPed->GetObjectMtx(0).d; Vector3 vLocalOffset = pPed->GetLocalMtx(ibone).d; Printf("Offset between root bone position and %s bone = %f,%f,%f (locals %f,%f,%f)\n", GetSkeleton()->GetSkeletonData().GetBoneData(ibone)->GetName(), vObjectOffset.x, vObjectOffset.y, vObjectOffset.z, vLocalOffset.x, vLocalOffset.y, vLocalOffset.z); } Printf("\n****************** SpewRagdollTaskInfo ******************\n"); pPed->SpewRagdollTaskInfo(); Assertf(false, "%s ragdoll component %d offset too big (%f,%f,%f)", GetModelName(), iBound, pRagdollBound->GetCurrentMatrix(iBound).GetCol3().GetXf(), pRagdollBound->GetCurrentMatrix(iBound).GetCol3().GetYf(), pRagdollBound->GetCurrentMatrix(iBound).GetCol3().GetZf()); Printf("\n****************** End debug spew for CPed::UpdateRagdollBoundsFromAnimatedSkel() ******************\n"); break; } } } } #endif // get rid of orientation, just keep position if possible Matrix34 lastEntityMatrix(Matrix34::IdentityType); lastEntityMatrix.d = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); if (lastEntityMatrix.d.x != lastEntityMatrix.d.x || lastEntityMatrix.d.x*0.0f != 0.0f || lastEntityMatrix.d.y != lastEntityMatrix.d.y || lastEntityMatrix.d.y*0.0f != 0.0f || lastEntityMatrix.d.z != lastEntityMatrix.d.z || lastEntityMatrix.d.z*0.0f != 0.0f) { Warningf("CPhysical::UpdateEntityFromPhysics() - lastEntityMatrix.d isn't finite - setting to 0,0,100"); lastEntityMatrix.d = Vector3(0.0f,0.0f,100.0f); } phCollider* pCollider = CPhysics::GetSimulator()->GetCollider(pInst); if(pCollider) { // Set the inst matrix and collider matrix from the last entity matrix pInst->SetMatrix(RCC_MAT34V(lastEntityMatrix)); pCollider->SetColliderMatrixFromInstanceRigid(); Warningf("CPhysical::UpdateEntityFromPhysics - Collider being reset"); pCollider->Reset(); fragInst* pFragInst = pInst->GetClassType() >= PH_INST_FRAG_GTA ? static_cast(pInst) : NULL; phBoundComposite* pCompositeBound = pInst->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE ? static_cast(pInst->GetArchetype()->GetBound()) : NULL; if(pFragInst && pCompositeBound) { if (GetIsTypePed()) { Warningf("CPhysical::UpdateEntityFromPhysics() - Will try to give the bounds and skeleton reasonable values"); pFragInst->PoseBoundsFromArticulatedBody(); pFragInst->PoseSkeletonFromArticulatedBody(); } float oldRadius = pCompositeBound->GetRadiusAroundCentroid(); pCompositeBound->CalculateCompositeExtents(); if(pCompositeBound->HasBVHStructure()) { CPhysics::GetLevel()->RebuildCompositeBvh(pInst->GetLevelIndex()); } if(pFragInst->GetClassType()==PH_INST_FRAG_PED || !pFragInst->GetManualSkeletonUpdate()) pFragInst->ReportMovedBySim(); else pFragInst->SyncSkeletonToArticulatedBody(); if (oldRadius != pCompositeBound->GetRadiusAroundCentroid()) CPhysics::GetLevel()->UpdateObjectLocationAndRadius(pInst->GetLevelIndex(),(Mat34V_Ptr)(NULL)); } } if(GetIsTypePed()) { static_cast(this)->DeactivatePhysicsAndDealWithRagdoll(); } else DeActivatePhysics(); // force matrix back to old matrix that was stored in the entity before physics update SetMatrix(lastEntityMatrix, true, true, true); } else { // only update previous position on the first physics update loop if(CPhysics::GetIsFirstTimeSlice(nLoop)) SetPreviousPosition(VEC3V_TO_VECTOR3(GetTransform().GetPosition())); Assertf(pInst->GetMatrix().IsOrthonormal3x3(ScalarVFromF32(REJUVENATE_ERROR_NEW_VEC)), "%s:Physics returned a non ortho matrix for object", GetDebugName()); // the player's allowed to drive outside the world now, so only check things going too high now #if __ASSERT { static int s_NumOutOfWorldDumps = 0; if(s_NumOutOfWorldDumps++ < 10) { const Vec3V currentPos = CEntity::GetTransform().GetPosition(); if(pInst->GetMatrix().GetM23f() >= WORLDLIMITS_ZMAX) { Displayf("===== object (%s) outside world limits (ZMAZ: z=%f) =====", GetDebugName(), pInst->GetMatrix().GetM23f()); DumpOutOfWorldInfo(); Assertf(false, "%s:Physics returned a position outside top of world (z=%f)." " Entity is currently at (%5.3f, %5.3f, %5.3f). See log for more info.", GetDebugName(), pInst->GetMatrix().GetM23f(), currentPos.GetXf(), currentPos.GetYf(), currentPos.GetZf()); } if(pInst->GetMatrix().GetM23f() <= WORLDLIMITS_ZMIN) { Displayf("===== object (%s) outside world limits (ZMIN: z=%f) =====", GetDebugName(), pInst->GetMatrix().GetM23f()); DumpOutOfWorldInfo(); Assertf(false, "%s:Physics returned a position below bottom of world (z=%f)." " Entity is currently at (%5.3f, %5.3f, %5.3f). See log for more info.", GetDebugName(), pInst->GetMatrix().GetM23f(), currentPos.GetXf(), currentPos.GetYf(), currentPos.GetZf()); } } } #endif // __ASSERT CDynamicEntity::SetMatrix(RCC_MATRIX34(pInst->GetMatrix())); if( CPhysics::GetIsLastTimeSlice(nLoop) ) { // Update the entity's bounds in the path server system. // Only necessary in the final timeslice. if(IsInPathServer()) { CPathServerGta::UpdateDynamicObject(this); } // Attempt to add the object, if we have failed to before else if(WasUnableToAddToPathServer()) { if(CPathServerGta::MaybeAddDynamicObject(this)) { CAssistedMovementRouteStore::MaybeAddRouteForDoor(this); } } } } #if __ASSERT if( !m_nPhysicalFlags.bModifiedBounds ) { // bound radius is already checked above, as 4*std, so test for 4 and a bit* here Vec3V instBBmax = pInst->GetArchetype()->GetBound()->GetBoundingBoxMax(); Vec3V instBBmin = pInst->GetArchetype()->GetBound()->GetBoundingBoxMin(); Vec3V modelBBmax = VECTOR3_TO_VEC3V( GetBaseModelInfo()->GetBoundingBoxMax() ); Vec3V modelBBmin = VECTOR3_TO_VEC3V( GetBaseModelInfo()->GetBoundingBoxMin() ); bool boxOk = VEC3V_TO_VECTOR3(instBBmax - instBBmin).Mag2() < 17.0f * VEC3V_TO_VECTOR3( modelBBmax - modelBBmin).Mag2() ? true: false; if( !boxOk ) { Displayf( "\n instBBmax %f %f %f \n instBBmin %f %f %f ", VEC3V_ARGS(instBBmax), VEC3V_ARGS(instBBmin) ); Displayf( "\n modelBBmax %f %f %f \n modelBBmin %f %f %f ", VEC3V_ARGS(modelBBmax), VEC3V_ARGS(modelBBmin) ); Assertf( !boxOk, "%s:Physics returned an bounding box bigger than expected", GetDebugName() ); } } #endif } // // This function ignores any X or Y rotation which was previously // applied to the Placeable void CPhysical::SetHeading(float new_heading) { CDynamicEntity::SetHeading(new_heading); Assert(GetTransform().IsOrthonormal()); // update the representations in the physics world UpdatePhysicsFromEntity(); } void CPhysical::SetPosition(const Vector3& vec, bool bUpdateGameWorld, bool bUpdatePhysics, bool bWarp) { #if __ASSERT const char* networkName = GetNetworkObject() ? GetNetworkObject()->GetLogName() : "none"; Assertf( g_WorldLimits_AABB.ContainsPoint(RCC_VEC3V(vec)), "Setting position of entity '%s' (debug name '%s' network name '%s') outside world limits (%5.2f, %5.2f, %5.2f). Old position (%5.2f, %5.2f, %5.2f)", GetModelName(), GetDebugName(), networkName, VEC3V_ARGS(RCC_VEC3V(vec)), VEC3V_ARGS(GetTransform().GetPosition())); #endif CDynamicEntity::SetPosition(vec, bUpdateGameWorld, bUpdatePhysics, bWarp); ResetWaterStatus(); if(bUpdatePhysics) { UpdatePhysicsFromEntity(bWarp); } } void CPhysical::SetMatrix(const Matrix34& mat, bool bUpdateGameWorld, bool bUpdatePhysics, bool bWarp) { #if __ASSERT bool bOrthonormal = mat.IsOrthonormal(); if( !bOrthonormal ) { Matrix34 matTrans(mat); matTrans.Transpose3x4(); Matrix34 I; I.Dot(matTrans, mat); Assertf(bOrthonormal, "Matrix wasn't orthonormal: \n a (%.4f, %.4f, %.4f) \n b (%.4f, %.4f, %.4f) \n c (%.4f, %.4f, %.4f) \n d (%.4f, %.4f, %.4f)\n" "Matrix times matrix-transpose - I: \n a (%.5f, %.5f, %.5f) \n b (%.5f, %.5f, %.5f) \n c (%.5f, %.5f, %.5f)", mat.a.x, mat.a.y, mat.a.z, mat.b.x, mat.b.y, mat.b.z, mat.c.x, mat.c.y, mat.c.z, mat.d.x, mat.d.y, mat.d.z, I.a.x - 1.0f, I.a.y, I.a.z, I.b.x, I.b.y - 1.0f, I.b.z, I.c.x, I.c.y, I.c.z - 1.0f); } #endif #if !__NO_OUTPUT if(m_LogSetMatrixCalls) { EntityDebugfWithCallStack(this, "SetMatrix"); } #endif CDynamicEntity::SetMatrix(mat, false, bUpdatePhysics, bWarp); ResetWaterStatus(); if(bUpdatePhysics){ UpdatePhysicsFromEntity(bWarp); } if (bUpdateGameWorld){ UpdateWorldFromEntity(); } } void CPhysical::ProcessCollision(phInst const * myInst, CEntity* pHitEnt, phInst const * hitInst, const Vector3& vMyHitPos, const Vector3& vOtherHitPos, float fImpulseMag, const Vector3& vMyNormal, int iMyComponent, int iOtherComponent, phMaterialMgr::Id iOtherMaterial, bool bIsPositiveDepth, bool bIsNewContact) { if( pHitEnt ) { if( pHitEnt == m_cantBeDamagedByEntity ) { if( pHitEnt->GetIsPhysical() && pHitEnt->GetIsDynamic() ) { Displayf("[CPhysical::ProcessCollision] [Physical: %s %s] is ignoring collision with [Physical: %s %s]", GetModelName(), GetNetworkObject() ? GetNetworkObject()->GetLogName() : "none", static_cast(pHitEnt)->GetModelName(), static_cast(pHitEnt)->GetNetworkObject() ? static_cast(pHitEnt)->GetNetworkObject()->GetLogName() : "none" ); } return; } // If we just broke a piece of breakable glass, it is possible that the hit inst has been swapped out and so may not // still be in the level. If that's the case, there is no point continuing; we will likely get a collision with the // new inst. if(pHitEnt->GetIsDynamic()) { if(static_cast(pHitEnt)->m_nDEflags.bIsBreakableGlass && !hitInst->IsInLevel()) return; if( pHitEnt->GetIsPhysical() && this == static_cast(pHitEnt)->GetCantBeDamagedByCollisionEntity() ) { Displayf("[CPhysical::ProcessCollision] [Physical: %s %s] is ignoring collision with [Physical: %s %s]", static_cast(pHitEnt)->GetModelName(), static_cast(pHitEnt)->GetNetworkObject() ? static_cast(pHitEnt)->GetNetworkObject()->GetLogName() : "none", GetModelName(), GetNetworkObject() ? GetNetworkObject()->GetLogName() : "none" ); return; } if (NetworkInterface::IsGameInProgress()) { //Update the last damage entity... // NOTE: I'm changing the previous behavior due to Gas tanks where the last damage would screw // the last inflictor causing people to come with statistics saying that they committed suicide and other ode behavior. if(GetIsTypeObject() && GetFragInst() && pHitEnt->GetIsTypeVehicle()) { CEntity* lastInflictor = GetWeaponDamageEntity(); const u32 timeSinceLastDamage = fwTimer::GetTimeInMilliseconds() - GetWeaponDamagedTime(); const u32 TIME_INTERVAL = 10000; if (!lastInflictor || timeSinceLastDamage > TIME_INTERVAL) { SetWeaponDamageInfo(pHitEnt, 0, fwTimer::GetTimeInMilliseconds()); } } } else { //Update the last damage entity... // NOTE: I'm not sure GetFragInst is necessary but I didn't want to change the previous behavior if(GetIsTypeObject() && GetFragInst() && pHitEnt->GetIsTypeVehicle()) { SetWeaponDamageInfo(pHitEnt, 0, fwTimer::GetTimeInMilliseconds()); } } } } // Looking up if a group is damageable is expensive so don't do it for things we know can't be damaged (peds, vehicles) if(GetIsTypeObject() && !static_cast(this)->m_nObjectFlags.bVehiclePart) { if( m_canOnlyBeDamagedByEntity == NULL || pHitEnt == m_canOnlyBeDamagedByEntity ) { if (fragInst* pFragInst = GetFragInst()) { // Early out here in the common case where the object isn't damageable if(pFragInst->GetTypePhysics()->IsChildsGroupDamageable(iMyComponent)) { // ModifyImpulseMag will sometimes want to distribute the force between contacts regardless of the incoming impulse. During rage breaking // it is important that ModifyImpulseMag knows how many impulses there are between the two components because we're accumulating impulse // per-component and we want the exact impulse sum. // Here though, we aren't accumulating impulses between contacts. Instead we're calling NotifyImpulse per-contact. Distributing the force here // would dilute the impulse based on the number of contacts. We might want to consider accumulating impulses here, but for now I'm keeping // the current behavior. int numComponentImpulses = 1; ScalarV vImpulseMag = pFragInst->ModifyImpulseMag(iMyComponent, iOtherComponent, numComponentImpulses, ScalarVFromF32(fImpulseMag*fImpulseMag), pHitEnt ? pHitEnt->GetCurrentPhysicsInst() : NULL); Vector3 vImpulse; if(IsTrue(vImpulseMag > ScalarV(V_NEGONE))) { vImpulse = VEC3V_TO_VECTOR3(vImpulseMag * Vec3V(V_X_AXIS_WZERO)); } else { // If instance returns -1.0 then just use contact impulse vImpulse = fImpulseMag*vMyNormal; } pFragInst->NotifyImpulse(vImpulse, vMyNormal, iMyComponent, 0); } } } } #if __ASSERT else if(fragInst* pFragInst = GetFragInst()) { if(iMyComponent < pFragInst->GetTypePhysics()->GetNumChildren()) { Assert(pFragInst->GetTypePhysics() && pFragInst->GetTypePhysics()->IsChildsGroupDamageable(iMyComponent) == false); } } #endif if(pHitEnt) { if(IsRecordingCollisionHistory()) { GetFrameCollisionHistory()->AddCollisionRecord(myInst, pHitEnt, vMyNormal*fImpulseMag, vMyNormal, vMyHitPos, vOtherHitPos, iMyComponent, iOtherComponent, iOtherMaterial, bIsPositiveDepth, bIsNewContact); } NetworkInterface::RegisterCollision(*this, pHitEnt, fImpulseMag); } } void CPhysical::AddCollisionRecordBeforeDisablingContact(phInst* pThisInst, CEntity* pOtherEntity, phContactIterator impacts) { if(pOtherEntity && IsRecordingCollisionHistory()) { // Extract info from the impact and add info about this event to the collision recording system because // it won't be added in IterateOverManifolds() once we disable the impact. Vector3 vMyImpulse, vMyNormal; impacts.GetMyImpulse(vMyImpulse); impacts.GetMyNormal(vMyNormal); GetFrameCollisionHistory()->AddCollisionRecord(pThisInst, pOtherEntity, vMyImpulse, vMyNormal, VEC3V_TO_VECTOR3(impacts.GetMyPosition()), VEC3V_TO_VECTOR3(impacts.GetOtherPosition()), impacts.GetMyComponent(), impacts.GetOtherComponent(), impacts.GetOtherMaterialId(), impacts.GetContact().IsPositiveDepth(), impacts.GetContact().GetLifetime() == 1); } } void CPhysical::AddCollisionRecordBeforeDisablingContact(phInst* pThisInst, CEntity* pOtherEntity, phCachedContactIteratorElement* pIterator) { if(pOtherEntity && IsRecordingCollisionHistory()) { // Extract info from the impact and add info about this event to the collision recording system because // it won't be added in IterateOverManifolds() once we disable the impact. Vector3 vMyImpulse = VEC3V_TO_VECTOR3(pIterator->m_Contact->GetAccumImpulse()*pIterator->m_MyNormal); GetFrameCollisionHistory()->AddCollisionRecord(pThisInst, pOtherEntity, vMyImpulse, VEC3V_TO_VECTOR3(pIterator->m_MyNormal), VEC3V_TO_VECTOR3(pIterator->m_MyPosition), VEC3V_TO_VECTOR3(pIterator->m_OtherPosition), pIterator->m_MyComponent, pIterator->m_OtherComponent, pIterator->m_Contact->GetMaterialIdB(), pIterator->m_Contact->IsPositiveDepth(), pIterator->m_Contact->GetLifetime() == 1); } } #if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE // Don't need to do any of this if we don't have second surface. void CPhysical::ProcessPreComputeImpacts(phContactIterator impacts) { ProcessSecondSurfaceImpacts(impacts); } void CPhysical::ProcessSecondSurfaceImpacts(phContactIterator impacts, const CSecondSurfaceConfig& secondSurfaceConfig ) { //Simulate the effect of the soft material between the top surface //and the second surface of a bvh. We want the dynamic object hitting the bvh to slow down. // For objects hitting a second surface BVH, impacts will be generated for both the top surface and the interpolated surface // These top surface impacts can be identified because they have a dist to top surface of -1 // Dist to top surface: // -1.0f : The top surface of a BVH poly intersection that has a 2nd surface // 0.0f : This contact has no second surface // > 0.0f : A contact with the interpolated surface int iImpactCount=0; bool bSecondSurfaceHit = false; impacts.Reset(); while(!impacts.AtEnd()) { //Test that the physical is active in the simulator if(impacts.GetMyInstance() && PHLEVEL->IsActive(impacts.GetMyInstance()->GetLevelIndex())) { Vector3 vOffset = impacts.GetMyPosition() - VEC3V_TO_VECTOR3(GetTransform().GetPosition()); //Test if we've got a contact with a bvh that has a second surface. #if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE float fDistToTopSurface=impacts.GetDistToTopSurface(); #else float fDistToTopSurface=impacts.GetUserData(); #endif if(fDistToTopSurface < 0.0f) { // A 'fake' top surface contact // Disable it but use its depth as the top surface // We don't disable the contact in the neo second surface implementation, because we just store the depth in the regular contact instead of making a new one #if !HACK_GTA4_BOUND_GEOM_SECOND_SURFACE_DATA || HACK_GTA4_BOUND_GEOM_SECOND_SURFACE fDistToTopSurface = impacts.GetDepth(); impacts.DisableImpact(); #endif // Support force varies with tang speed and depth // There is also a constant support force float fSpringForce = secondSurfaceConfig.m_fSinkLiftFactor * fDistToTopSurface; // Can never do more than support physical's weight Vector3 vNormal = ZAXIS; //impacts.GetMyNormal(vNormal); Vector3 vVel = GetVelocity(); float fSpeedIntoSecondSurface = vVel.Dot(vNormal); // Tang velocity gives lift force // Make a squared dependence to avoid sqrt vVel -= fSpeedIntoSecondSurface * vNormal; fSpringForce += rage::Min(vVel.Mag2() * secondSurfaceConfig.m_fTangV2LiftFactor, secondSurfaceConfig.m_fMaxTangV2LiftFactor); Vector3 vForce = ZAXIS * fSpringForce * GetMass(); impacts.GetMyInstance()->ApplyForce(vForce,vOffset,impacts.GetMyComponent(),0,0.0f); } // Note this code is hit if the code above has been hit too if(fDistToTopSurface>0.0f) { bSecondSurfaceHit = true; /// Apply forces at contact: // Apply drag at contact point proportional to dist to top surface and vel Vector3 vDragForce = impacts.GetRelVelocity(); vDragForce.Scale(GetMass() * -secondSurfaceConfig.m_fDragFactor * fDistToTopSurface); impacts.GetMyInstance()->ApplyForce(vDragForce,vOffset,impacts.GetMyComponent(),0,0.0f); } } iImpactCount++; impacts++; } m_nPhysicalFlags.bSecondSurfaceImpact=false; if(bSecondSurfaceHit) { m_nPhysicalFlags.bSecondSurfaceImpact = true; } } #endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE void CPhysical::DoCollisionEffects() { // check if we've hit anything this frame const CCollisionHistory* pCollisionHistory = GetFrameCollisionHistory(); const CCollisionRecord* pColRecord = pCollisionHistory->GetMostSignificantCollisionRecord(); if (pColRecord != NULL && pColRecord->m_pRegdCollisionEntity.Get() != NULL) { // check that this material can smash glass bool shouldProcess = false; phInst* pPhysInst = GetCurrentPhysicsInst(); if (pPhysInst) { phArchetype* pArchetype = pPhysInst->GetArchetype(); if (pArchetype) { phBound* pBound = pArchetype->GetBound(); if (pBound) { phMaterialMgr::Id mtlId = phMaterialMgr::DEFAULT_MATERIAL_ID; if (pBound->GetType() == phBound::COMPOSITE) { phBoundComposite* pCompositeBound = static_cast(pBound); for (int partIndex = 0; partIndex < pCompositeBound->GetNumBounds(); ++partIndex) { if (phBound* pPartBound = pCompositeBound->GetBound(partIndex)) { Assertf(pPartBound->GetType()!=phBound::COMPOSITE, "%s is a composite bound with an child bound that is also composite", GetDebugName()); mtlId = PGTAMATERIALMGR->UnpackMtlId(pPartBound->GetMaterialId(0)); break; } } } else { mtlId = PGTAMATERIALMGR->UnpackMtlId(pBound->GetMaterialId(0)); } if (PGTAMATERIALMGR->GetMtlVfxGroup(mtlId)!=VFXGROUP_PAPER && PGTAMATERIALMGR->GetMtlVfxGroup(mtlId)!=VFXGROUP_CARDBOARD) { shouldProcess = true; } } } } if (!shouldProcess) { return; } // float impulseMag = GetLatestCollisionImpulseMag(); // Printf("ColnFx: ImpulseMag %.2f\n", impulseMag); // float speedMag = GetLatestCollisionImpulseMag()/GetMass(); // Printf("ColnFx: SpeedMag %.2f\n", speedMag); if ( pCollisionHistory->GetMaxCollisionImpulseMagLastFrame()>VEHICLEGLASSTHRESH_PHYSICAL_COLN_MIN_IMPULSE || pCollisionHistory->GetMaxCollisionImpulseMagLastFrame()/GetMass()>5.0f) { // check if the collision material was smashable if (/*PGTAMATERIALMGR->GetIsSmashableGlass(GetOtherCollisionMaterialId()) && */IsEntitySmashable(pColRecord->m_pRegdCollisionEntity.Get())) { // set up the collision info structure VfxCollInfo_s vfxCollInfo; vfxCollInfo.regdEnt = pColRecord->m_pRegdCollisionEntity.Get(); vfxCollInfo.vPositionWld = RCC_VEC3V(pColRecord->m_MyCollisionPos); vfxCollInfo.vNormalWld = -RCC_VEC3V(pColRecord->m_MyCollisionNormal); vfxCollInfo.vDirectionWld = -vfxCollInfo.vNormalWld; vfxCollInfo.materialId = pColRecord->m_OtherCollisionMaterialId; vfxCollInfo.roomId = 0; vfxCollInfo.componentId = pColRecord->m_OtherCollisionComponent; Assert(vfxCollInfo.vNormalWld.GetXf()!=0.0f || vfxCollInfo.vNormalWld.GetYf()!=0.0f || vfxCollInfo.vNormalWld.GetZf()!=0.0f); vfxCollInfo.weaponGroup = WEAPON_EFFECT_GROUP_PUNCH_KICK; // TODO: pass in correct weapon group? vfxCollInfo.force = (pCollisionHistory->GetMaxCollisionImpulseMagLastFrame()-VEHICLEGLASSTHRESH_PHYSICAL_COLN_MIN_IMPULSE)/(VEHICLEGLASSTHRESH_PHYSICAL_COLN_MAX_IMPULSE-VEHICLEGLASSTHRESH_PHYSICAL_COLN_MIN_IMPULSE); vfxCollInfo.force = MAX(vfxCollInfo.force, 0.1f); vfxCollInfo.force = MIN(vfxCollInfo.force, 1.0f); vfxCollInfo.isBloody = false; vfxCollInfo.isWater = false; vfxCollInfo.isExitFx = false; vfxCollInfo.noPtFx = false; vfxCollInfo.noPedDamage = false; vfxCollInfo.noDecal = false; vfxCollInfo.isSnowball = false; vfxCollInfo.isFMJAmmo = false; // try to smash the object g_vehicleGlassMan.StoreCollision(vfxCollInfo); } } } } /* void CPhysical::ApplyMoveForce(Vector3 vecForce) { if(m_nPhysicalFlags.bDoorPhysics || m_nPhysicalFlags.bHangingPhysics) return; else if(m_nPhysicalFlags.bPoolBallPhysics) vecForce.z = 0.0f; vecForce /= m_fMass; GetVelocity() += vecForce; Assert( rage::Abs(GetVelocity().x) < 10.0f ); Assert( rage::Abs(GetVelocity().y) < 10.0f ); Assert( rage::Abs(GetVelocity().z) < 10.0f ); } void CPhysical::ApplyTurnForce(Vector3 vecForce, Vector3 vecOffset) { // might want to change this so only need do COM stuff if set non-zero if(m_nPhysicalFlags.bPedPhysics) return; Vector3 vecCOM(0.0f,0.0f,0.0f); float fTurnMass = m_fTurnMass; // parallel axis theorm since we're not rotating about the Centre of Mass if(m_nPhysicalFlags.bHangingPhysics) fTurnMass += 0.5f*m_fMass*m_vecCOM.z*m_vecCOM.z; else GetMatrix().Transform3x3(m_vecCOM, vecCOM); if(m_nPhysicalFlags.bDoorPhysics) { vecForce.z = 0.0f; vecOffset.z = 0.0f; } vecForce = CrossProduct(vecOffset - vecCOM, vecForce) / m_fTurnMass; AddToTurnSpeed(vecForce); Assert( rage::Abs(GetAngVelocity().x) < 10.0f ); Assert( rage::Abs(GetAngVelocity().y) < 10.0f ); Assert( rage::Abs(GetAngVelocity().z) < 10.0f ); } */ #if __ASSERT float CPhysical::GetRadiusAroundLocalOrigin() { if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->GetArchetype()) return GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid() + Mag(GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetCentroidOffset()).Getf(); else return GetBoundRadius(); } bool CPhysical::CheckForceInRange(const Vector3 &vecForce, const float maxAccel) const { return vecForce.Mag2() <= square(maxAccel * rage::Max(1.0f, GetMass())); } #endif // __ASSERT void CPhysical::NotifyWaterImpact(const Vector3& UNUSED_PARAM(vecForce), const Vector3& UNUSED_PARAM(vecTorque), int UNUSED_PARAM(nComponent), float UNUSED_PARAM(fTimeStep)) { } // apply both move and turn forces at same time so we can use different physics models void CPhysical::ApplyForce(const Vector3& vecForce, const Vector3& vecOffset, int nComponent, bool ASSERT_ONLY(bCheckForces), float timeStepOverride) { phInst *curPhysInst = GetCurrentPhysicsInst(); Assert(curPhysInst); if(curPhysInst==NULL) return; ScalarV vTimeStep = ScalarV(Selectf(timeStepOverride, timeStepOverride, fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices())); if(Verifyf(curPhysInst->IsInLevel(),"Should never apply force when phInst is not in level")) { // Ignore forces being applied to components that do not exist exist on the object // LOD changes can reduce the number of bounds while the user is holding onto the component const phBound *curPhysBound = curPhysInst->GetArchetype()->GetBound(); if(curPhysBound->GetType() == phBound::COMPOSITE) { const phBoundComposite* compBound = static_cast(curPhysBound); if(nComponent >= compBound->GetNumBounds() || compBound->GetBound(nComponent) == NULL) { return; } } #if __ASSERT if (bCheckForces) { FORCE_CHECK("[CPhysical::ApplyForce()]", vecForce, DEFAULT_ACCEL_LIMIT); OFFSET_FROM_CENTROID_CHECK("[CPhysical::ApplyForce()]", vecOffset, 1.2f); } #endif #if __DEV if(fragInstNMGta::ms_bLogRagdollForces && GetCurrentPhysicsInst()->GetClassType()==PH_INST_FRAG_PED) { Displayf("Ragdoll Force %g Offset %g (Inst %p Time %d)\n", vecForce.Mag(), vecOffset.Mag(), GetCurrentPhysicsInst(), fwTimer::GetTimeInMilliseconds()); Vector3 vecDirn(vecForce); vecDirn.NormalizeFast(); const Vector3 vStartOfLine = vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()); grcDebugDraw::Line(vStartOfLine, vStartOfLine - vecDirn, Color_white); } #endif // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(curPhysInst->GetLevelIndex())) ActivatePhysics(); phCollider *pCollider = GetCollider(); if(pCollider) { pCollider->ApplyForce(vecForce, vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()), vTimeStep.GetIntrin128(), nComponent); } } } void CPhysical::ApplyForceCg(const Vector3& vecForce, float timeStepOverride) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; ScalarV vTimeStep = ScalarV(Selectf(timeStepOverride, timeStepOverride, fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices())); if(Verifyf(GetCurrentPhysicsInst()->IsInLevel(),"Should never apply force when phInst is not in level")) { FORCE_CHECK("[CPhysical::ApplyForceCg()]", vecForce, DEFAULT_ACCEL_LIMIT); Assert(vecForce.x==vecForce.x && vecForce.y==vecForce.y && vecForce.z==vecForce.z); //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); phCollider * pCollider = GetCollider(); if(pCollider) { pCollider->ApplyForceCenterOfMass(vecForce, vTimeStep.GetIntrin128()); #if DR_ENABLED && __ASSERT static bool sbOnceOnly; if (!sbOnceOnly) { if (!CheckForceInRange(vecForce, DEFAULT_ACCEL_LIMIT)) { debugPlayback::RecordTaggedVectorValue(*GetCurrentPhysicsInst(), RCC_VEC3V(vecForce), "FlaggedForce", debugPlayback::eVectorTypeVelocity); debugPlayback::SaveRecordingForInst(GetCurrentPhysicsInst()); sbOnceOnly = true; } } #endif } } } void CPhysical::ApplyTorque(const Vector3& vecForce, const Vector3& vecOffset, float timeStepOverride) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; ScalarV vTimeStep = ScalarV(Selectf(timeStepOverride, timeStepOverride, fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices())); if(Verifyf(GetCurrentPhysicsInst()->IsInLevel(),"Should never apply torque when phInst is not in level")) { FORCE_CHECK("[CPhysical::ApplyTorque()]", vecForce, DEFAULT_ACCEL_LIMIT); OFFSET_FROM_CENTROID_CHECK("[CPhysical::ApplyTorque()]", vecOffset, 1.2f); Assert(vecForce.x==vecForce.x && vecForce.y==vecForce.y && vecForce.z==vecForce.z); Assert(vecOffset.x==vecOffset.x && vecOffset.y==vecOffset.y && vecOffset.z==vecOffset.z); //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); if(GetCollider()) { Vector3 vecTorque(vecOffset); vecTorque.Cross(vecForce); GetCollider()->ApplyTorque(vecTorque, vTimeStep.GetIntrin128()); } } } void CPhysical::ApplyTorque(const Vector3& vecTorque, float timeStepOverride) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; ScalarV vTimeStep = ScalarV(Selectf(timeStepOverride, timeStepOverride, fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices())); Assert(GetCurrentPhysicsInst()->IsInLevel()); // JP: Removed this because we use this command to apply angular impulses too, which can be v large // #if __ASSERT // // Check torque isn't going to cause a massive change in ang velocity // Vector3 vecAngAccel; // GetMatrix().UnTransform3x3(vecTorque,vecAngAccel); // const float fAngAccel = vecAngAccel.Dot(GetCurrentPhysicsInst()->GetArchetype()->GetInvAngInertia()); // Assertf(Abs(fAngAccel) < 15.0f,"fAngAccel: %f",fAngAccel); // #endif Assert(vecTorque.x==vecTorque.x && vecTorque.y==vecTorque.y && vecTorque.z==vecTorque.z); //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); if(GetCollider()) GetCollider()->ApplyTorque(vecTorque, vTimeStep.GetIntrin128()); } void CPhysical::ApplyTorqueAndForce(const Vector3& vecTorque, const Vector3& vecWorldPosition, float timeStepOverride) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; ScalarV vTimeStep = ScalarV(Selectf(timeStepOverride, timeStepOverride, fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices())); Assert(GetCurrentPhysicsInst()->IsInLevel()); #if __ASSERT // Check torque isn't going to cause a massive change in ang velocity Vector3 vecAngAccel = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecTorque))); const float fAngAccel = vecAngAccel.Dot(GetCurrentPhysicsInst()->GetArchetype()->GetInvAngInertia()); Assertf(fAngAccel < 15.0f,"fAngAccel: %f",fAngAccel); #endif Assert(vecTorque.x==vecTorque.x && vecTorque.y==vecTorque.y && vecTorque.z==vecTorque.z); //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); if(GetCollider()) GetCollider()->ApplyTorqueAndForce(vecTorque,vecWorldPosition, vTimeStep.GetIntrin128()); } void CPhysical::SetDamping( int dampingType, float dampingValue ) { phInst* inst = GetCurrentPhysicsInst(); if(Verifyf(inst, "CPhysical::SetDamping - No Instance on this CPhysical")) { phArchetypeDamp* pGtaArchetype = (phArchetypeDamp*)inst->GetArchetype(); if(pGtaArchetype) { Vec3V dampingConst = Vec3VFromF32( dampingValue ); pGtaArchetype->ActivateDamping( (phArchetypeDamp::MotionDampingType) dampingType, RC_VECTOR3(dampingConst) ); } } } Vector3 CPhysical::GetDamping( int dampingType) { Vector3 vDamping = VEC3_ZERO; phInst* inst = GetCurrentPhysicsInst(); if(Verifyf(inst, "CPhysical::GetDamping - No Instance on this CPhysical")) { phArchetypeDamp* pGtaArchetype = (phArchetypeDamp*)inst->GetArchetype(); if(pGtaArchetype) { vDamping = const_cast(pGtaArchetype->GetDampingConstant(dampingType)); } } return vDamping; } void CPhysical::ApplyImpulse(const Vector3& vecImpulse, const Vector3& vecOffset, int nComponent, bool bLocalOffset) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; Assert(GetCurrentPhysicsInst()->IsInLevel()); IMPULSE_CHECK("[CPhysical::ApplyImpulse()]", vecImpulse, DEFAULT_IMPULSE_LIMIT); Assertf(vecOffset.Mag2() < square(2.0f*GetRadiusAroundLocalOrigin()), "Radius around local origin: %5.3f, force being applied at offset: %5.3f", GetRadiusAroundLocalOrigin(), vecOffset.Mag()); Assert(vecImpulse.x==vecImpulse.x && vecImpulse.y==vecImpulse.y && vecImpulse.z==vecImpulse.z); Assert(vecOffset.x==vecOffset.x && vecOffset.y==vecOffset.y && vecOffset.z==vecOffset.z); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); if(GetCollider()) { bool bAppliedViaNM = false; // Send the impulse via NM message if it's an active agent if (GetCurrentPhysicsInst()->GetClassType()==PH_INST_FRAG_PED) { fragInstNMGta *pedInst = static_cast(GetCurrentPhysicsInst()); if (pedInst->GetType()->GetARTAssetID() >= 0 && pedInst->GetNMAgentID() >= 0) { #if __DEV if(fragInstNMGta::ms_bLogRagdollForces) { Displayf("Ragdoll Impulse %g Offset %g Local %d (Inst %p Time %d)\n", vecImpulse.Mag(), vecOffset.Mag(), bLocalOffset, GetCurrentPhysicsInst(), fwTimer::GetTimeInMilliseconds()); Vector3 vecDirn(vecImpulse); vecDirn.NormalizeFast(); Vector3 vStartOfLine; if (bLocalOffset) { const phBoundComposite* pRagdollBound = static_cast(pedInst->GetArchetype()->GetBound()); Assertf(nComponent >= 0 && nComponent < pRagdollBound->GetNumActiveBounds(), "CPhysical::ApplyImpulse - bad component. input component is %d. Num active bounds is %d.", nComponent, pRagdollBound->GetNumActiveBounds()); Mat34V matResult; Transform(matResult, pedInst->GetMatrix(), pRagdollBound->GetCurrentMatrix(nComponent)); RCC_MATRIX34(matResult).Transform(vecOffset, vStartOfLine); } else { vStartOfLine = vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()); } grcDebugDraw::Line(vStartOfLine, vStartOfLine - vecDirn, Color_white); } #endif Vector3 hitPoint = bLocalOffset ? vecOffset : vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()); ART::MessageParams msg; msg.addBool(NMSTR_PARAM(NM_START), true); msg.addInt(NMSTR_PARAM(NM_APPLYIMPULSE_PART_INDEX), nComponent); msg.addVector3(NMSTR_PARAM(NM_APPLYIMPULSE_IMPULSE), vecImpulse.x, vecImpulse.y, vecImpulse.z); msg.addVector3(NMSTR_PARAM(NM_APPLYIMPULSE_HIT_POINT), hitPoint.x, hitPoint.y, hitPoint.z); msg.addBool(NMSTR_PARAM(NM_APPLYIMPULSE_LOCAL_HIT_POINT_INFO), bLocalOffset); pedInst->PostARTMessage(NMSTR_MSG(NM_APPLYIMPULSE_MSG), &msg); bAppliedViaNM = true; } } if (!bAppliedViaNM) { Vector3 vecWorldPos = vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()); if (bLocalOffset) { const phBound* pCurPhysBound = GetCurrentPhysicsInst()->GetArchetype()->GetBound(); if (pCurPhysBound->GetType() == phBound::COMPOSITE) { Assertf(nComponent >= 0 && nComponent < static_cast(pCurPhysBound)->GetNumActiveBounds(), "CPhysical::ApplyAngImpulse - bad component. input component is %d. Num active bounds is %d.", nComponent, static_cast(pCurPhysBound)->GetNumActiveBounds()); Mat34V matResult; Transform(matResult, GetCurrentPhysicsInst()->GetMatrix(), static_cast(pCurPhysBound)->GetCurrentMatrix(nComponent)); RCC_MATRIX34(matResult).Transform(vecOffset, vecWorldPos); } } GetCollider()->ApplyImpulse(vecImpulse, vecWorldPos, nComponent); } } } void CPhysical::ApplyImpulseCg(const Vector3& vecImpulse, s32 ASSERT_ONLY(impulseFlags)) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; Assert(GetCurrentPhysicsInst()->IsInLevel()); #if __ASSERT if(!(impulseFlags&IF_InitialImpulse)) { IMPULSE_CHECK("[CPhysical::ApplyImpulseCg()]", vecImpulse, DEFAULT_IMPULSE_LIMIT); } #endif // __ASSERT Assert(vecImpulse.x==vecImpulse.x && vecImpulse.y==vecImpulse.y && vecImpulse.z==vecImpulse.z); //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); if(GetCollider()) { GetCollider()->ApplyImpulseCenterOfMass(vecImpulse); } } void CPhysical::ApplyAngImpulse(const Vector3& vecAngImpulse, s32 ASSERT_ONLY(impulseFlags)) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; Assert(GetCurrentPhysicsInst()->IsInLevel()); #if __ASSERT if(!(impulseFlags&IF_InitialImpulse)) { IMPULSE_CHECK("[CPhysical::ApplyAngImpulse()]", vecAngImpulse, DEFAULT_IMPULSE_LIMIT); } #endif // __ASSERT Assert(IsFiniteAll(RCC_VEC3V(vecAngImpulse))); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); if(GetCollider()) { GetCollider()->ApplyAngImpulse(vecAngImpulse); } } void CPhysical::ApplyAngImpulse(const Vector3& vecImpulse, const Vector3& vecOffset, int nComponent, bool bLocalOffset, s32 ASSERT_ONLY(impulseFlags)) { Assert(GetCurrentPhysicsInst()); if(GetCurrentPhysicsInst()==NULL) return; Assert(GetCurrentPhysicsInst()->IsInLevel()); #if __ASSERT if(!(impulseFlags&IF_InitialImpulse)) { IMPULSE_CHECK("[CPhysical::ApplyAngImpulse()]", vecImpulse, DEFAULT_IMPULSE_LIMIT); } // Check that the application offset isn't far outside the bound. float fCentroidRadius = 0.0f; Vector3 vWorldCentroid; fCentroidRadius = GetBoundCentreAndRadius(vWorldCentroid); Assertf(vecOffset.Mag2() < 1.1f*1.1f*fCentroidRadius*fCentroidRadius, "[CPhysical::ApplyAngImpulse()] model name: %s, offset from centroid=%5.3f (allowed radius=%5.3f).", GetModelName(), vecOffset.Mag(), 1.1f*fCentroidRadius); #endif // __ASSERT Assert(vecImpulse.x==vecImpulse.x && vecImpulse.y==vecImpulse.y && vecImpulse.z==vecImpulse.z); Assert(vecOffset.x==vecOffset.x && vecOffset.y==vecOffset.y && vecOffset.z==vecOffset.z); //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); // wake up if it's not active if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())) ActivatePhysics(); if(GetCollider()) { bool bAppliedViaNM = false; // Send the impulse via NM message if it's an active agent if (GetCurrentPhysicsInst()->GetClassType()==PH_INST_FRAG_PED) { fragInstNMGta *pedInst = static_cast(GetCurrentPhysicsInst()); if (pedInst->GetType()->GetARTAssetID() >= 0 && pedInst->GetNMAgentID() >= 0) { Vector3 hitPoint = bLocalOffset ? vecOffset : vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()); ART::MessageParams msg; msg.addBool(NMSTR_PARAM(NM_START), true); msg.addInt(NMSTR_PARAM(NM_APPLYIMPULSE_PART_INDEX), nComponent); msg.addVector3(NMSTR_PARAM(NM_APPLYIMPULSE_IMPULSE), vecImpulse.x, vecImpulse.y, vecImpulse.z); msg.addVector3(NMSTR_PARAM(NM_APPLYIMPULSE_HIT_POINT), hitPoint.x, hitPoint.y, hitPoint.z); msg.addBool(NMSTR_PARAM(NM_APPLYIMPULSE_LOCAL_HIT_POINT_INFO), bLocalOffset); msg.addBool(NMSTR_PARAM(NM_APPLYIMPULSE_ANGULAR_IMPULSE), true); pedInst->PostARTMessage(NMSTR_MSG(NM_APPLYIMPULSE_MSG), &msg); bAppliedViaNM = true; } } if (!bAppliedViaNM) { Vector3 vecObjectOffset = vecOffset; if (bLocalOffset) { const phBound* pCurPhysBound = GetCurrentPhysicsInst()->GetArchetype()->GetBound(); if (pCurPhysBound->GetType() == phBound::COMPOSITE) { Assertf(nComponent >= 0 && nComponent < static_cast(pCurPhysBound)->GetNumActiveBounds(), "CPhysical::ApplyAngImpulse - bad component. input component is %d. Num active bounds is %d.", nComponent, static_cast(pCurPhysBound)->GetNumActiveBounds()); RCC_MATRIX34(static_cast(pCurPhysBound)->GetCurrentMatrix(nComponent)).Transform(vecOffset, vecObjectOffset); } } GetCollider()->ApplyAngImpulse(CrossProduct(vecObjectOffset, vecImpulse), nComponent); } } } void CPhysical::ApplyExternalForce(const Vector3& vecForce, const Vector3& vecOffset, int nComponent, int nPartIndex, phInst* pInst, float fBreakingForce, float timeStepOverride ASSERT_ONLY(, bool ignoreCentroidCheck) ) { #if __ASSERT if(!IsBaseFlagSet(fwEntity::IS_FIXED)) { FORCE_CHECK("[CPhysical::ApplyExternalForce()]", vecForce, DEFAULT_ACCEL_LIMIT); } if(!ignoreCentroidCheck) { OFFSET_FROM_CENTROID_CHECK("CPhysical::ApplyExternalForce()]", vecOffset, 1.1f); } Assert(vecForce.x==vecForce.x && vecForce.y==vecForce.y && vecForce.z==vecForce.z); Assert(vecOffset.x==vecOffset.x && vecOffset.y==vecOffset.y && vecOffset.z==vecOffset.z); //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); #endif // __ASSERT ScalarV vTimeStep = ScalarV(Selectf(timeStepOverride, timeStepOverride, fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices())); if(pInst==NULL) { Assert(GetCurrentPhysicsInst()); pInst = GetCurrentPhysicsInst(); } if(pInst) { Assert(pInst->IsInLevel()); if(fBreakingForce == -1.0f) { CPhysics::GetSimulator()->ApplyForceAndBreakingForce(vTimeStep.GetIntrin128(), pInst->GetLevelIndex(), vecForce, vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()), nComponent, nPartIndex, vecForce.Mag()); } else { CPhysics::GetSimulator()->ApplyForceAndBreakingForce(vTimeStep.GetIntrin128(), pInst->GetLevelIndex(), vecForce, vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()), nComponent, nPartIndex, fBreakingForce); } } } void CPhysical::ApplyExternalImpulse(const Vector3& vecImpulse, const Vector3& vecOffset, int nComponent, int nPartIndex, phInst* pInst, float fBreakingImpulse, bool bCheckOffset, float timeStepOverride) { #if __ASSERT if(!IsBaseFlagSet(fwEntity::IS_FIXED)) { IMPULSE_CHECK("[CPhysical::ApplyExternalImpulse()]", vecImpulse, DEFAULT_IMPULSE_LIMIT); } #endif // __ASSERT if(bCheckOffset) { OFFSET_FROM_CENTROID_CHECK("[CPhysical::ApplyExternalImpulse()]", vecOffset, 1.1f); Assert(vecImpulse.x==vecImpulse.x && vecImpulse.y==vecImpulse.y && vecImpulse.z==vecImpulse.z); Assert(vecOffset.x==vecOffset.x && vecOffset.y==vecOffset.y && vecOffset.z==vecOffset.z); } //GY - commenting out assert that checks for applying impulses to ragdollls. //I can't think of a reason not to apply an impulse to a ragdoll. //Besides, its quite limiting for scripts if they can't exert any control over ragdolling peds. //Assert(GetCurrentPhysicsInst()->GetClassType()!=PH_INST_FRAG_PED); ScalarV vTimeStep = ScalarV(Selectf(timeStepOverride, timeStepOverride, fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices())); if(pInst==NULL) { Assert(GetCurrentPhysicsInst()); pInst = GetCurrentPhysicsInst(); } if(pInst) { if (!Verifyf(pInst->IsInLevel(), "Someone is trying to apply in impulse to an instance that is not inserted presently in the physics engine")) { return; } if(-1==fBreakingImpulse) { //No breaking impulse specified. //Use the magnitude of the impulse as the magnitude of the breaking impulse. PHSIM->ApplyImpetusAndBreakingImpetus(vTimeStep.GetIntrin128(), pInst->GetLevelIndex(), vecImpulse, vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()), nComponent, nPartIndex, vecImpulse.Mag()); } else { #if __BANK if(CPhysics::ms_bPrintBreakingImpulsesApplied) { Displayf("BreakingImpulse %f", fBreakingImpulse); } #endif //Breaking impulse specified. //Use the magnitude of the specified breaking impulse. PHSIM->ApplyImpetusAndBreakingImpetus(vTimeStep.GetIntrin128(), pInst->GetLevelIndex(), vecImpulse, vecOffset + VEC3V_TO_VECTOR3(GetTransform().GetPosition()), nComponent, nPartIndex, fBreakingImpulse); } } } #if __ASSERT void CPhysical::DumpOutOfWorldInfo() { const Vec3V currentPos = GetTransform().GetPosition(); // Check collision flags const u32 mapCollisionFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_MAP_TYPE_VEHICLE; u32 archetypeIncludeFlags = 0; u32 boundIncludeFlags = 0; if(const phInst* pInst = GetCurrentPhysicsInst()) { if(pInst->IsInLevel()) { boundIncludeFlags = archetypeIncludeFlags = CPhysics::GetLevel()->GetInstanceIncludeFlags(pInst->GetLevelIndex()); if(const phArchetype* pArchetype = pInst->GetArchetype()) { if(const phBound* pBound = pArchetype->GetBound()) { if(phBound::IsTypeComposite(pBound->GetType())) { const phBoundComposite* pBoundComposite = static_cast(pBound); if(pBoundComposite->GetTypeAndIncludeFlags()) { boundIncludeFlags = 0; for(int componentIndex = 0; componentIndex < pBoundComposite->GetNumBounds(); ++componentIndex) { if(pBoundComposite->GetBound(componentIndex)) { boundIncludeFlags |= pBoundComposite->GetIncludeFlags(componentIndex); } } } } } } } } Displayf("Entity is currently at (%5.3f, %5.3f, %5.3f).", VEC3V_ARGS(currentPos)); Displayf("Entity pop-type:%s.", CTheScripts::GetPopTypeName(PopTypeGet())); Displayf("Owned by Script:[%s].", GetOwnedBy() == ENTITY_OWNEDBY_SCRIPT ? "T" : "F"); Displayf("GetAllowFreezeWaitingOnCollision:[%s].", GetAllowFreezeWaitingOnCollision() ? "T" : "F"); Displayf("ShouldFixIfNoCollisionLoadedAroundPosition:[%s]",ShouldFixIfNoCollisionLoadedAroundPosition() ? "T" : "F"); Displayf("ShouldLoadCollision:[%s]", ShouldLoadCollision() ? "T" : "F"); Displayf("GetIsAnyFixedFlagSet:[%s]",GetIsAnyFixedFlagSet() ? "T" : "F"); Displayf("GetIsFixedUntilCollisionFlagSet:[%s]",GetIsFixedUntilCollisionFlagSet() ? "T" : "F"); Displayf("GetIsFixedFlagSet:[%s]",GetIsFixedFlagSet() ? "T" : "F"); Displayf("GetIsFixedByNetworkFlagSet:[%s]",GetIsFixedByNetworkFlagSet() ? "T" : "F"); Displayf("IsCollisionLoadedAroundPosition:[%s]",IsCollisionLoadedAroundPosition() ? "T" : "F"); Displayf("CPhysical::IsCollisionLoadedAroundPosition:[%s]", CPhysical::IsCollisionLoadedAroundPosition() ? "T" : "F"); Displayf("IsProtectedBaseFlagSet(fwEntity::USES_COLLISION):[%s]",IsProtectedBaseFlagSet(fwEntity::USES_COLLISION) ? "T" : "F"); Displayf("Archetype Include Flags: 0x%X, Map[%s]",archetypeIncludeFlags, (archetypeIncludeFlags & mapCollisionFlags) != 0 ? "T" : "F"); Displayf("Bound Include Flags: 0x%X, Map[%s]",boundIncludeFlags, (boundIncludeFlags & mapCollisionFlags) != 0 ? "T" : "F"); if(const phCollider* pCollider = GetCollider()) { Displayf("Collider Velocity: %5.2f, %5.2f, %5.2f", VEC3V_ARGS(pCollider->GetVelocity())); Displayf("Collider Angular Velocity: %5.2f, %5.2f, %5.2f", VEC3V_ARGS(pCollider->GetAngVelocity())); Displayf("Last Instance Position: %5.2f, %5.2f, %5.2f", VEC3V_ARGS(pCollider->GetLastInstanceMatrix().GetCol3())); } else { Displayf("No Collider"); } if(GetIsAttached()) { Displayf("Attach details: parent=%s.", GetAttachParent() ? GetAttachParent()->GetModelName() : "unknown"); if(fwAttachmentEntityExtension* attachment = GetAttachmentExtension()) { for(int constraintIndex = 0; constraintIndex < attachment->GetNumConstraintHandles(); ++constraintIndex) { phConstraintHandle constraintHandle = attachment->GetConstraintHandle(constraintIndex); if(phConstraintBase* pConstraint = CPhysics::GetConstraintManager()->GetTemporaryPointer(constraintHandle)) { Displayf("%s Constraint:",pConstraint->GetTypeName()); if(const phInst* pInstA = pConstraint->GetInstanceA()) { Displayf("\tInstance A: %s, (%5.2f, %5.2f, %5.2f)", pInstA->GetArchetype()->GetFilename(), VEC3V_ARGS(pInstA->GetPosition())); } else { Displayf("\tInst A: NULL"); } if(const phInst* pInstB = pConstraint->GetInstanceB()) { Displayf("\tInstance B: %s, (%5.2f, %5.2f, %5.2f)", pInstB->GetArchetype()->GetFilename(), VEC3V_ARGS(pInstB->GetPosition())); } else { Displayf("\tInst B: NULL"); } } } } } else { Displayf("Not attached."); } } bool CPhysical::IsOffsetWithinCentroidRadius(const char* msg, const Vector3& vecOffset, float fCentroidRadiusScale) const { Vec3V vWorldPos = GetTransform().GetPosition() + RCC_VEC3V(vecOffset); Mat34V worldMatrix; const phCollider* pCollider = GetCollider(); if( pCollider ) { worldMatrix = pCollider->GetMatrix(); vWorldPos = worldMatrix.d() + RCC_VEC3V(vecOffset); } Vec3V vWorldCentroid; float fCentroidRadius = 0.0f; if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->GetArchetype()) { if( pCollider && !GetIsTypePed() ) { vWorldCentroid = GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetWorldCentroid( worldMatrix ); } else { vWorldCentroid = GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetWorldCentroid(GetCurrentPhysicsInst()->GetMatrix()); } fCentroidRadius = GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid(); } else { fCentroidRadius = GetBoundCentreAndRadius(RC_VECTOR3(vWorldCentroid)); } fCentroidRadius *= fCentroidRadiusScale; bool bIsOffsetWithinCentroidRadius = DistSquared(vWorldPos, vWorldCentroid).Getf() <= fCentroidRadius*fCentroidRadius; if( !bIsOffsetWithinCentroidRadius ) { Warningf("%s model name: %s, offset from centroid=%5.3f (allowed radius=%5.3f).", msg, GetModelName(), Dist(vWorldPos, vWorldCentroid).Getf(), fCentroidRadius); } return bIsOffsetWithinCentroidRadius; } #endif // __ASSERT /* void CPhysical::ApplyBreakingImpulse(const Vector3& vecImpulse, const Vector3& vecBreakingImpulse, const Vector3& vecOffset, int nComponent, phInst* pInst) { Assert(IsBaseFlagSet(fwEntity::IS_FIXED) || vecImpulse.Mag()/rage::Max(1.0f, GetMass()) < 150.0f); #if __ASSERT Vector3 vecCentroidOffset = vecOffset + GetPosition() - GetBoundCentre(); Assert(vecCentroidOffset.Mag() < 1.5f*GetBoundRadius());//GetBoundRadius()); #endif Assert(vecImpulse.x==vecImpulse.x && vecImpulse.y==vecImpulse.y && vecImpulse.z==vecImpulse.z); Assert(vecOffset.x==vecOffset.x && vecOffset.y==vecOffset.y && vecOffset.z==vecOffset.z); if(pInst==NULL) pInst = GetCurrentPhysicsInst(); if(pInst==NULL || !pInst->IsInLevel() || CPhysics::GetLevel()->IsFixed(pInst->GetLevelIndex())) return; Vector3 vecPosition = vecOffset + GetPosition(); ///////////////////// // FIRST PART IS COPIED FROM phSimulator::ApplyImpetus() // Create an impact data for the impetus application, and make the impetus an impulse. phImpactData impactData; impactData.Set(0,0,0,0.0f,vecPosition,ORIGIN,ORIGIN,ORIGIN); // USE breaking impulse for break calculation ///////////////////// impactData.SetImpetus(vecBreakingImpulse); impactData.SetInstanceA(pInst); impactData.SetComponentA(nComponent); impactData.SetIsForce(false); // Get the collider, making the object active if it isn't already and should be when hit. phCollider* collider = CPhysics::GetSimulator()->TestPromoteInstance(pInst,&impactData); bool isActive = (collider!=NULL); if(pInst->IsBreakable(isActive)) { // Get the instance from the impact data, because TestPromoteInstance may have put in another instance. pInst = impactData.GetInstanceA(); // Make a list of breakable instances for handling multiple breaks with a single impetus application. phInstBreakable* breakableInstList[phInstBreakable::MAX_NUM_BREAKABLE_INSTS]; int numBreakInsts = 0; phInstBreakable* breakableInst = static_cast(pInst); breakableInstList[numBreakInsts++] = breakableInst; ///////////////////// // HERE ON IS COPIED FROM phSimulator::BreakObjects() // Try to break the object into pieces, recursing to try to break any breakable broken pieces. // BreakObjects(NULL,breakableInstList,numBreakInsts,&impactData); // Create the list of broken parts, for use in recalculating collisions. phInst* brokenInstList[phInstBreakable::MAX_NUM_BROKEN_INSTS]; int numBrokenInsts = 0; phBreakData breakData0,breakData1; phBreakData* breakData = &breakData0; phBreakData* testBreakData = &breakData1; float breakStrength; Vector3 preBreakImpulse; phInstBreakable* weakestInst = CPhysics::GetSimulator()->FindWeakestInst(NULL, breakableInstList, numBreakInsts, &breakStrength, &impactData, &breakData, &testBreakData); if (weakestInst) { // Apply the pre-breaking impulse to the weakest object, changing its motion instantly, and // set the impulse to be the post-breaking impulse. Assert(breakStrength>=0.0f && breakStrength<=1.0f); // APPLY normal impulse after that /////////////////////////// preBreakImpulse.Scale(vecImpulse, breakStrength); phCollider *collider = CPhysics::GetSimulator()->GetCollider(weakestInst); if (collider) { // Apply the pre-breaking impulse to the unbroken collider. collider->ApplyImpulseChangeMotion(preBreakImpulse,impactData.GetPositionA(),impactData.GetComponentA()); } else { // Apply the pre-breaking impulse to the unbroken instance. weakestInst->ApplyImpulseChangeMotion(preBreakImpulse,impactData.GetPositionA(),impactData.GetComponentA()); } // Break the weakest breakable instance. numBrokenInsts = weakestInst->BreakApart(NULL, breakableInstList, &numBreakInsts, brokenInstList, &impactData, *breakData); Assert(numBrokenInsts<=phInstBreakable::MAX_NUM_BROKEN_INSTS); Assert(numBreakInsts<=phInstBreakable::MAX_NUM_BREAKABLE_INSTS); // Convert the total impulse to the post-breaking impulse. Vector3 impetus(vecImpulse); impetus.Subtract(preBreakImpulse); impactData.SetImpetus(impetus); } else { // APPLY normal impulse after that /////////////////////////// impactData.SetImpetus(vecImpulse); } // Apply the post-breaking part of the applied impulse to the specified broken parts. //Assert(numBrokenInsts==0 || numBreakInsts==0); for (int index=0; indexGetCollider(brokenInstList[index]); if (collider) { collider->ApplyImpetus(impactData); } else { brokenInstList[index]->ApplyImpetus(impactData); } } // Apply the post-breaking part of the applied impulse to any unbroken breakable parts. for (int index=0; indexGetCollider(breakableInstList[index]); if (collider) { collider->ApplyImpetus(impactData); } else { breakableInstList[index]->ApplyImpetus(impactData); } } } else { CPhysics::GetSimulator()->ApplyImpulse(pInst->GetLevelIndex(), vecImpulse, vecOffset + GetPosition(), nComponent); } } */ Vector3 CPhysical::GetLocalSpeed(const Vector3& vecOffset, bool bWorldOffset, int nComponent) const { Assertf(vecOffset == vecOffset, "Input vecOffset is invalid."); Vector3 vecReturnVel = VEC3_ZERO; Vector3 vecWorldOffset = vecOffset; if(!bWorldOffset) vecWorldOffset += VEC3V_TO_VECTOR3(GetTransform().GetPosition()); if(const phCollider* pCollider = GetCollider()) { vecReturnVel = VEC3V_TO_VECTOR3(pCollider->GetLocalVelocity(vecWorldOffset,nComponent)); Assertf(IsFiniteAll(RCC_VEC3V(vecReturnVel)), "%s collider local velocity on '%s' is invalid:" "\n\tVel: %5.2f, %5.2f, %5.2f" "\n\tAngVel: %5.2f, %5.2f, %5.2f" "\n\tCollider Pos: %5.2f, %5.2f, %5.2f" "\n\tPoint Pos: %5.2f, %5.2f, %5.2f", pCollider->IsArticulated() ? "Articulated" : "Rigid", GetDebugName(), VEC3V_ARGS(pCollider->GetVelocity()), VEC3V_ARGS(pCollider->GetAngVelocity()), VEC3V_ARGS(pCollider->GetPosition()), VEC3V_ARGS(RCC_VEC3V(vecWorldOffset))); } else if(const phInst* pInst = GetCurrentPhysicsInst()) { // Use the simulator functions as these query the externally controlled velocity if needed RC_VEC3V(vecReturnVel) = pInst->GetExternallyControlledLocalVelocity(vecWorldOffset,nComponent); Assertf(IsFiniteAll(RCC_VEC3V(vecReturnVel)), "Velocity is invalid."); } return vecReturnVel; } Vector3 CPhysical::GetLocalSpeedIntegrated(const float fTimeStep, Vec3V_In vecOffset, bool bWorldOffset, int nComponent) const { //Assert that the incoming position is valid. Assertf(IsFiniteAll(vecOffset), "Initial position is invalid."); Assert(IsLessThanAll( Abs(vecOffset),Vec3V(V_FLT_LARGE_6)) ); //Assign the velocity, angular velocity, and position. Vec3V vVel(V_ZERO); Vec3V vAngVel(V_ZERO); Vec3V vPos(V_ZERO); //Grab the collider and physics instance. const phCollider* pCollider = GetCollider(); const phInst* pInst = GetCurrentPhysicsInst(); if(pCollider) { vVel = pCollider->GetVelocity(); Assertf(IsFiniteAll(vVel), "Collider velocity is invalid."); Assert(IsLessThanAll( Abs(vVel),Vec3V(V_FLT_LARGE_6)) ); vAngVel = pCollider->GetAngVelocity(); Assertf(IsFiniteAll(vAngVel), "Collider angular velocity is invalid."); Assert(IsLessThanAll( Abs(vAngVel),Vec3V(V_FLT_LARGE_6)) ); vPos = pCollider->GetPosition(); Assertf(IsFiniteAll(vPos), "Collider position is invalid."); Assert(IsLessThanAll( Abs(vPos),Vec3V(V_FLT_LARGE_6)) ); } else if(pInst) { //Grab the pre-physics update position. vPos = GetMatrixPrePhysicsUpdate().GetCol3(); Assertf(IsFiniteAll(vPos), "Instance position is invalid."); // Use the simulator functions as these query the externally controlled velocity if needed //Grab the velocity including the component's movement vVel = pInst->GetExternallyControlledVelocity(bWorldOffset ? vecOffset : vecOffset + vPos, nComponent); Assertf(IsFiniteAll(vVel), "Instance velocity is invalid."); //Grab the angular velocity. vAngVel = pInst->GetExternallyControlledAngVelocity(); Assertf(IsFiniteAll(vAngVel), "Instance angular velocity is invalid."); } else { return VEC3_ZERO; } //Generate the integrated velocity for the angular velocity component, applied across the timestep. Vec3V vVelIntegrated(V_ZERO); //Ensure the angular velocity is non-zero. if(!IsZeroAll(vAngVel)) { //Generate the offset. Vec3V vOffset; if(bWorldOffset) { vOffset = Subtract(vecOffset, vPos); } else { vOffset = vecOffset; } Assertf(IsFiniteAll(vOffset), "Offset is invalid."); Assert(IsLessThanAll( Abs(vOffset),Vec3V(V_FLT_LARGE_6)) ); //Calculate the initial velocity component from the angular velocity. vVelIntegrated = Cross(vAngVel, vOffset); Assertf(IsFiniteAll(vVelIntegrated), "Initial velocity is invalid."); Assert(IsLessThanAll( Abs(vVelIntegrated),Vec3V(V_FLT_LARGE_6)) ); //Generate the axis for the angular velocity. Vec3V vAxis = NormalizeSafe(vAngVel, Vec3V(V_ZERO)); Assertf(IsFiniteAll(vAxis), "Axis is invalid."); Assert(IsLessThanAll( Abs(vAxis), Vec3V(V_FLT_LARGE_6)) ); //Ensure the axis is valid. if(!IsZeroAll(vAxis)) { //Generate the magnitude of the angular velocity. ScalarV scMagAngVel = Mag(vAngVel); Assertf(IsFiniteAll(scMagAngVel), "Angular velocity magnitude."); Assert(IsLessThanAll( Abs(scMagAngVel),ScalarV(V_FLT_LARGE_6)) ); //Ensure the magnitude is valid. if(!IsZeroAll(scMagAngVel)) { //Create a rotation from the axis/angle pair. QuatV qRotator = QuatVFromAxisAngle(vAxis, Scale(scMagAngVel, ScalarVFromF32(fTimeStep))); Assertf(IsFiniteAll(qRotator), "qRotator is invalid."); //Rotate the offset. vOffset = Transform(qRotator, vOffset); Assertf(IsFiniteAll(vOffset), "Rotated offset is invalid."); Assert(IsLessThanAll( Abs(vOffset), Vec3V(V_FLT_LARGE_6)) ); //Calculate the final velocity component from the angular velocity, after the timestep. Vec3V vFinal = Cross(vAngVel, vOffset); Assertf(IsFiniteAll(vFinal), "Final velocity is invalid."); Assert(IsLessThanAll( Abs(vFinal), Vec3V(V_FLT_LARGE_6)) ); //Average the initial and final velocities. vVelIntegrated = Scale(::Add(vVelIntegrated, vFinal), ScalarV(V_HALF)); Assertf(IsFiniteAll(vVelIntegrated), "Integrated velocity is invalid."); Assert(IsLessThanAll( Abs(vVelIntegrated), Vec3V(V_FLT_LARGE_6)) ); } } } return VEC3V_TO_VECTOR3(::Add(vVel, vVelIntegrated)); } float CPhysical::GetMassAlongVector(const Vector3& vecNormal, const Vector3& vecWorldPos) { float fMassRot = 1.0f; if(GetCollider()) { Vector3 vecCgOffset = vecWorldPos - RCC_VECTOR3(GetCollider()->GetPosition()); Vector3 vecTemp = vecCgOffset; vecTemp.Cross(vecNormal); vecTemp.Multiply(GetCurrentPhysicsInst()->GetArchetype()->GetInvAngInertia()); vecTemp.Cross(vecCgOffset); fMassRot = vecTemp.Dot(vecNormal); fMassRot = 1.0f / (GetCurrentPhysicsInst()->GetArchetype()->GetInvMass() + fMassRot); } else if(GetCurrentPhysicsInst()) { fMassRot = GetCurrentPhysicsInst()->GetArchetype()->GetMass(); } return fMassRot; } float CPhysical::GetMassAlongVectorLocal(const Vector3& vecNormal, const Vector3& vecLocalPos) { float fMassRot = 1.0f; if(GetCollider()) { Vector3 vecTemp = vecLocalPos; vecTemp.Cross( vecNormal ); vecTemp.Multiply(GetCurrentPhysicsInst()->GetArchetype()->GetInvAngInertia()); vecTemp.Cross( vecLocalPos ); fMassRot = vecTemp.Dot( vecNormal ); fMassRot = 1.0f / ( GetCurrentPhysicsInst()->GetArchetype()->GetInvMass() + fMassRot ); } else if(GetCurrentPhysicsInst()) { fMassRot = GetCurrentPhysicsInst()->GetArchetype()->GetMass(); } return fMassRot; } ///////////////////////////////////////////////////////////////////////////////// // Name : ResetWaterStatus // Purpose : Reset the water status into an unknown state so a full // rescan and update occurs. // Parameters : None. // Returns : Nothing. ///////////////////////////////////////////////////////////////////////////////// void CPhysical::ResetWaterStatus() { m_nFlags.bPossiblyTouchesWater = true; m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = false; m_Buoyancy.m_buoyancyFlags.bBuoyancyInfoUpToDate = false; //m_Buoyancy.SetStatusHighNDry(); B* 1236461, this messes up underwater timers and lots of code sets matricies or position } ///////////////////////////////////////////////////////////////////////////////// // Name : ProcessControl // Purpose : Perfroms a frame update of the physical object. // Parameters : None. // Returns : Nothing. ///////////////////////////////////////////////////////////////////////////////// bool CPhysical::ProcessControl() { return true; } void CPhysical::ProcessPrePhysics() { // 6/12/12 - cthomas - Prefetch the physics instance now, so it is ready to be used later // (UpdatePossiblyTouchesWaterFlag(), etc.). Switched the call to UpdateFixedWaitingForCollision() // to happen before the call to UpdatePossiblyTouchesWaterFlag(), since they don't appear to // have any dependencies, and that allows a little more time for the prefetch to do its work. PrefetchObject(GetCurrentPhysicsInst()); PrefetchObject(&m_Buoyancy); //bool bIsAsleep = false; //if(GetCollider() && GetCollider()->GetSleep() && GetCollider()->GetSleep()->IsAsleep()) // bIsAsleep = true; // Reset the in water flags if(GetIsInWater()) { m_nPhysicalFlags.bWasInWater = true; // the is in water flag is dictated by the owner of the object in network games if(!IsNetworkClone()) { SetIsInWater( false ); } } else { m_nPhysicalFlags.bWasInWater = false; } // Update the fixed waiting for collision flag, this is done directly before the physics update so it // is current for the simulator update. // NOTE: UpdateFixedWaitingForCollision can deactivate the collider so we shouldn't store a pointer to it before the function UpdateFixedWaitingForCollision(GetCollider() != NULL); phCollider* pCollider = GetCollider(); if(pCollider) { // Ragdoll peds are handled differently here since they aren't getting their groundPhysical set like animated peds. // Instead they use an asynch probe every few frames so they can't have their ref frame velocities getting cleared each frame. if (!(GetIsTypePed() && static_cast(this)->GetUsingRagdoll()) && !(GetIsTypeObject() && static_cast(this)->GetAsProjectile())) { //make sure the reference velocity frame is 0 as it gets set every frame pCollider->SetReferenceFrameVelocity(ORIGIN); pCollider->SetReferenceFrameAngularVelocity(ORIGIN); } } // Update kinematic physics activation / deactivation here if (ShouldUseKinematicPhysics() && CanUseKinematicPhysics()) { if (!IsUsingKinematicPhysics()) { if (EnableKinematicPhysics()) { UpdatePhysicsFromEntity(false); } } } else { if (IsUsingKinematicPhysics()) { DisableKinematicPhysics(); } } if ( IsUsingKinematicPhysics() && !GetIsTypePed()) { // need to set the reserved last instance matrix in the physics level here // for some reason this never gets set when the entity is in kinematic mode if (GetInstForKinematicMode() && GetInstForKinematicMode()->HasLastMatrix()) { PHLEVEL->SetLastInstanceMatrix(GetInstForKinematicMode(), GetMatrix()); } //reset the velocity of the entity to the requested animated velocity if (GetCollider() && GetAnimDirector()) { fwDynamicEntityComponent *dynComp = GetDynamicComponent(); if (dynComp) { GetCollider()->SetVelocity(dynComp->GetAnimatedVelocity()); GetCollider()->SetAngVelocity(Vector3(0.0f, 0.0f, dynComp->GetAnimatedAngularVelocity())); } } } // Update the water flags only if it's actually moving. // If the simulator is moving it, it will have a collider. If the game is moving it, it is responsible for marking the flag as out of date. if(!m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate || pCollider) { // This needs to be updated to render objects correctly as far as being above/below water is concerned. UpdatePossiblyTouchesWaterFlag(); } if (GetNetworkObject()) static_cast(GetNetworkObject())->UpdateBeforePhysics(); } void CPhysical::ProcessPostPhysics() { SetShouldUseKinematicPhysics(false); if (GetNetworkObject()) static_cast(GetNetworkObject())->UpdateAfterPhysics(); } bool CPhysical::CanStraddlePortals() const { const fwEntity* pAttachParent = GetAttachParentForced(); return (pAttachParent && pAttachParent == CGameWorld::FindLocalPlayer()); } bool CPhysical::EnableKinematicPhysics() { phInst *pKinematicInst = GetInstForKinematicMode(); if (pKinematicInst && pKinematicInst->IsInLevel()) { m_nPhysicalFlags.bIsUsingKinematicPhysics = 1; return true; } return false; } bool CPhysical::DisableKinematicPhysics() { phInst *pKinematicInst = GetInstForKinematicMode(); if(pKinematicInst && pKinematicInst->IsInLevel() ) { m_nPhysicalFlags.bIsUsingKinematicPhysics = 0; return true; } return false; } bool CPhysical::IsMountEntryExitAttachment() const { return (GetAttachState() == ATTACH_STATE_PED_ON_MOUNT); } bool CPhysical::IsVehicleEntryExitAttachment() const { int attachState = GetAttachState(); if (attachState==ATTACH_STATE_PED_ENTER_CAR || attachState==ATTACH_STATE_PED_IN_CAR || attachState==ATTACH_STATE_PED_EXIT_CAR) { return true; } return false; } #if __BANK || !__NO_OUTPUT const char *CPhysical::GetDebugName() const { netObject *networkObject = GetNetworkObject(); if(networkObject) { return networkObject->GetLogName(); } else { return GetModelName(); } } #endif // __BANK #if __DEV dev_bool FORCE_COLLISION_NOT_LOADED = false; #endif bool CPhysical::IsCollisionLoadedAroundPosition() { #if __DEV if( FORCE_COLLISION_NOT_LOADED ) { return false; } #endif // The interior is suspending this object until it can be placed if( GetIsRetainedByInteriorProxy() ) { return false; } // if in an interior and interior is in an active state, then collisions are available fwInteriorLocation intLoc = GetInteriorLocation(); if (intLoc.IsValid()) { CInteriorInst* pIntInst = CInteriorInst::GetInteriorForLocation(intLoc); Assert(pIntInst); if (!pIntInst->GetProxy()->HasStaticBounds()) { if (pIntInst->GetCurrentPhysicsInst() && (pIntInst->GetCurrentPhysicsInst()->IsInLevel()) ) { return(true); } else { return(false); } } } // World collision isn't loaded around this entity Vec3V vThisPosition = GetTransform().GetPosition(); if (g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(vThisPosition, fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER)) { return true; } else { return false; } } /////////////////////////////////////////////////////////////////////////// // FUNCTION : PlacePhysicalRelativeToOtherPhysical // PURPOSE : Used by the script. An object gets placed relative to another object // and it's taking its speed at the same time. /////////////////////////////////////////////////////////////////////////// void CPhysical::PlacePhysicalRelativeToOtherPhysical(CPhysical *pBasePhysical, CPhysical *pToBePlacedPhysical, const Vector3& Offset) { Vector3 NewCoors; // Find the coordinates of the second bloke NewCoors = pBasePhysical->TransformIntoWorldSpace(Offset); // Unfortunately the Scripts are being processed BEFORE the world. This gives // the car another frame to move and the objects seem to slide. To fight this we // take the velocity of the object into account for the next frame. NewCoors += pBasePhysical->GetVelocity() * (50.0f*fwTimer::GetTimeStep() * 0.9f); CGameWorld::Remove(pToBePlacedPhysical); pToBePlacedPhysical->SetMatrix(MAT34V_TO_MATRIX34(pBasePhysical->GetMatrix()), false, false, false); pToBePlacedPhysical->SetPosition(NewCoors, false, true, false); // only update physics reps on the second call! pToBePlacedPhysical->SetVelocity(pBasePhysical->GetVelocity()); // assume it's going outside - otherwise we need to do a probe CGameWorld::Add(pToBePlacedPhysical, CGameWorld::OUTSIDE ); // adding to game world will update game world rep } bool CPhysical::TestNoCollision(const phInst *pOtherInst) { bool bNoCollision = false; u32 noCollisionFlags = GetNoCollisionFlags(); const CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInst); if(noCollisionFlags &NO_COLLISION_NETWORK_OBJECTS) { if(pOtherEntity && pOtherEntity->IsCollisionEnabled() && pOtherEntity->GetIsPhysical() && pOtherEntity!=this && NetworkUtils::GetNetworkObjectFromEntity(pOtherEntity)) { bNoCollision = true; } } else if(pOtherInst && pOtherInst->GetUserData()==GetNoCollisionEntity()) bNoCollision = true; if(bNoCollision) { // if we're interested in whether any impacts occured SetNoCollisionFlags((u8) (noCollisionFlags | NO_COLLISION_HIT_SHOULD_FIND_IMPACTS)); return true; } return false; } bool CPhysical::GetIsPhysicalAParentAttachment(CPhysical* pPhysical) const { CPhysical* pParent = static_cast(GetAttachParentForced()); bool bReturn = false; while(!bReturn && pParent) { bReturn = (pParent == pPhysical); pParent = static_cast(pParent->GetAttachParentForced()); } return bReturn; } #if __BANK void CPhysical::DebugAttachToPhysicalBasic(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, const Quaternion* pQuatOrientation, s16 nMyBone) #else void CPhysical::AttachToPhysicalBasic(CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, const Quaternion* pQuatOrientation, s16 nMyBone ) #endif { Assert(GetAttachState()!=ATTACH_STATE_PHYSICAL); Assert(pPhysical); ASSERT_ONLY( if(!Verifyf(pPhysical != this, "Attempting to perform a basic attachment of an entity to itself, which the attachment tree will not support")) return; ) if(pPhysical==NULL) return; fwAttachmentEntityExtension *extension = CreateAttachmentExtensionIfMissing(); // Need to prevent loops of attachments // See if we are already attached to pPhysical if(extension->GetAttachParentForced() != pPhysical && GetIsPhysicalAParentAttachment(pPhysical)) { // If we are actually in a detaching state then its OK to just clean up instantly if(extension->GetAttachState() == ATTACH_STATE_DETACHING) { // Instantly detach DetachFromParent(0); } else { if( extension->GetAttachParentForced() ) { Assertf(false, "%s has parent %s already", CModelInfo::GetBaseModelInfoName(GetModelId()), extension->GetAttachParentForced()->GetModelName()); } Assertf(false,"[0] Attaching %s to %s . This will create a closed loop of attachments! Attachment will abort", CModelInfo::GetBaseModelInfoName(GetModelId()), CModelInfo::GetBaseModelInfoName(pPhysical->GetModelId()) ); return; } } // See if pPhysical is already attached to us: if(pPhysical->GetIsPhysicalAParentAttachment(this)) { // If pPhysical is actually in a detaching state then its OK to just clean up instantly if(pPhysical->GetAttachState() == ATTACH_STATE_DETACHING) { // Instantly detach pPhysical->DetachFromParent(0); } else { Assertf(false,"[1] Attaching %s to %s . This will create a closed loop of attachments! Attachment will abort", CModelInfo::GetBaseModelInfoName(GetModelId()), CModelInfo::GetBaseModelInfoName(pPhysical->GetModelId()) ); return; } } #if GTA_REPLAY bool dontRecord = false; if(GetAttachParentForced() == pPhysical) { // Don't record the attachment if it's already attached to this physical dontRecord = true; } #endif // GTA_REPLAY #if __ASSERT //Ensure that the parent's attach bone isn't one that can scale if(nEntBone > -1 && pPhysical->GetIsDynamic() && pPhysical->GetSkeleton()) { const crBoneData *boneData = pPhysical->GetSkeleton()->GetSkeletonData().GetBoneData( nEntBone ); physicsAssertf(boneData->HasDofs(crBoneData::SCALE) == false, "Attaching a %s to a %s's %s bone, which can scale", CModelInfo::GetBaseModelInfoName(GetModelId()), CModelInfo::GetBaseModelInfoName(pPhysical->GetModelId()), boneData->GetName()); } #endif Assertf(!pVecOffset || pVecOffset->IsEqual(*pVecOffset), "Invalid offset specified for attachment."); Assertf(!pVecOffset || pVecOffset->Mag2() < 1000000.0f, "Invalid offset specified for attachment (offset too large)."); Assertf(!pQuatOrientation || pQuatOrientation->IsEqual(*pQuatOrientation), "Invalid orientation specified for attachment."); extension->AttachToEntity(this, pPhysical, nEntBone, nAttachFlags, pVecOffset, pQuatOrientation, NULL, nMyBone ); #if __BANK extension->DebugSetInvocationData(strCodeFile, strCodeFunction, nCodeLine); #endif if(GetIsTypePed()) ((CPed*)this)->DeactivatePhysicsAndDealWithRagdoll(); else DeActivatePhysics(); // When attached we never want to activate if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel()) { physicsAssertf(!CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex()), "object(%s) should have been deactivated already.", GetModelName()); GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, true); if(!extension->GetAttachFlag(ATTACH_FLAG_COL_ON)) { // Set cached include flags in the level if( GetCurrentPhysicsInst()->IsInLevel() ) { CPhysics::GetLevel()->SetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES)); } } extension->SetAttachFlag(ATTACH_FLAG_NOT_IN_LEVEL_INITIALLY, !GetCurrentPhysicsInst()->IsInLevel()); extension->SetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET, false); if(pPhysical->GetCurrentPhysicsInst() && pPhysical->GetCurrentPhysicsInst()->GetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL) && extension->GetAttachFlag(ATTACH_FLAG_COL_ON)) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,true); } } else { // Physics inst not available yet. Flag this so that we can deal with this correctly when processing // attachment when it does stream in. extension->SetAttachFlag(ATTACH_FLAG_NOT_IN_LEVEL_INITIALLY, true); extension->SetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET, true); } if(extension->GetAttachFlag(ATTACH_FLAG_COL_ON)) { EnableCollision(); } else { bool completely = false; if(NetworkInterface::IsGameInProgress() && GetNetworkObject() && GetNetworkObject()->IsClone()) { CNetObjEntity* netEnt = SafeCast(CNetObjEntity, GetNetworkObject()); if(netEnt) { completely = netEnt->GetDisableCollisionCompletely(); } } // This prevents rigid body collision DisableCollision(NULL, completely); } if(IsInPathServer()) { CAssistedMovementRouteStore::MaybeRemoveRouteForDoor(this); CPathServerGta::MaybeRemoveDynamicObject(this); } //If the entity being attached is a cloth then we need to transform the cloth //verts to the entity frame. bool bIsEnvClothObject = false; if(GetCurrentPhysicsInst()) { if((GetCurrentPhysicsInst()->IsInLevel() && CPhysics::GetLevel()->GetInstanceTypeFlags(GetCurrentPhysicsInst()->GetLevelIndex())==ArchetypeFlags::GTA_ENVCLOTH_OBJECT_TYPE) || (GetCurrentPhysicsInst()->GetArchetype() && GetCurrentPhysicsInst()->GetArchetype()->GetTypeFlags()==ArchetypeFlags::GTA_ENVCLOTH_OBJECT_TYPE)) { bIsEnvClothObject = true; } } /* // NOTE: Fix for B*2588068 (disable the following if block ) . this is some very very old code which shouldn't be here at all - svetli ( it has beed removed on rdr3 for sometime now ) if(bIsEnvClothObject) { //Process child attachments of physical to force a setting of the cloth entity transform. pPhysical->ProcessAllAttachments(); //Now transform the cloth verts to the frame of the cloth entity. phInst* pEntityInst=GetCurrentPhysicsInst(); Assertf(dynamic_cast(pEntityInst), "Physical with cloth must be a frag"); fragInst* pEntityFragInst=static_cast(pEntityInst); Assertf(pEntityFragInst->GetCached(), "Cloth entity has no cache entry"); if(pEntityFragInst->GetCached()) { fragCacheEntry* pEntityCacheEntry=pEntityFragInst->GetCacheEntry(); Assertf(pEntityCacheEntry, "Cloth entity has a null cache entry"); fragHierarchyInst* pEntityHierInst=pEntityCacheEntry->GetHierInst(); Assertf(pEntityHierInst, "Cloth entity has a null hier inst"); environmentCloth* pEnvCloth=pEntityHierInst->envCloth; #if NO_PIN_VERTS_IN_VERLET const int numPinVerts = pEnvCloth->GetCloth()->GetClothData().GetNumPinVerts(); #else const int numPinVerts = pEnvCloth->GetCloth()->GetPinCount(); #endif pEnvCloth->GetCloth()->GetClothData().TransformVertexPositions(GetMatrix(), numPinVerts); } } */ if(nAttachFlags & ATTACH_FLAG_INITIAL_WARP) { Matrix34 matOffset; matOffset.Identity(); matOffset.FromQuaternion(extension->GetAttachQuat()); matOffset.d = extension->GetAttachOffset(); Assert(matOffset.IsOrthonormal()); Matrix34 matParent = MAT34V_TO_MATRIX34(extension->GetAttachParentForced()->GetMatrix()); if(extension->GetOtherAttachBone() > -1 && ((CPhysical *) extension->GetAttachParentForced())->GetIsDynamic() && (static_cast(GetAttachParentForced()))->GetSkeleton()) { const CDynamicEntity* pDynEnt = static_cast(extension->GetAttachParentForced()); // check if global skeleton matrices are actually going to be valid in the first place (only peds seem to store this info) pDynEnt->GetGlobalMtx(extension->GetOtherAttachBone(), matParent); // B*1853481 & 1880214: Ensure we get a valid matrix even for damaged frag objects. // If the standard GetGlobalMtx call gives a zeroed matrix, try grabbing the matrix from the damaged skeleton. // Was previously using CVfxHelper::GetMatrixFromBoneIndex, but that doesn't use the entity GetGlobalMtx method; it accesses the skeleton directly. // This caused a problem as CVehicle implements a special version of that method to handle cases where mod kits are in use. For some reason modded parts have zeroed out bone matrices in the frag inst. if(matParent.a.IsZero()) { if(pDynEnt->GetFragInst() && pDynEnt->GetFragInst()->GetCacheEntry() && pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst()) { if(pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst()->damagedSkeleton) { Mat34V matParentV; pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst()->damagedSkeleton->GetGlobalMtx(extension->GetOtherAttachBone(), matParentV); matParent.Set(MAT34V_TO_MATRIX34(matParentV)); } else { matParent.Identity3x3(); physicsDisplayf("Parent %s has bone %d with non-orthonormality %f, setting to identity", CModelInfo::GetBaseModelInfoName(pPhysical->GetModelId()), extension->GetOtherAttachBone(), matParent.MeasureNonOrthonormality()); } } } } Assertf(matParent.IsOrthonormal(),"Parent %s has bone %d with non-orthonormality %f", CModelInfo::GetBaseModelInfoName(pPhysical->GetModelId()), extension->GetOtherAttachBone(), matParent.MeasureNonOrthonormality()); if( extension->GetMyAttachBone() == -1 ) { Matrix34 matNew; matNew.Dot(matOffset, matParent); matNew.Normalize(); // Normalise it to avoid any orthonormal asserts lower down, this fn is called infrequently anyway Assert(matNew.IsOrthonormal()); extension->SetAttachFlag(ATTACH_FLAG_DONT_ASSERT_ON_MATRIX_CHANGE, true); // Necessary to stop spurious assert in fwEntity::SetMatrix(). SetMatrix(matNew); extension->SetAttachFlag(ATTACH_FLAG_DONT_ASSERT_ON_MATRIX_CHANGE, false); } } #if GTA_REPLAY if(CReplayMgr::ShouldRecord() && !dontRecord) { CReplayMgr::RecordEntityAttachment(GetReplayID(), pPhysical->GetReplayID()); } #endif // GTA_REPLAY #if __BANK if(NetworkInterface::IsGameInProgress()) { if(pPhysical->GetIsTypeVehicle() && pPhysical->PopTypeIsMission() && this->GetIsTypeObject() && this->PopTypeIsMission()) { physicsDisplayf("[ATTACH DEBUG] Attaching %s to %s.", GetDebugNameFromObjectID(), pPhysical->GetDebugNameFromObjectID()); } } #endif } #if __BANK void CPhysical::DebugAttachToWorldBasic(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, u32 nAttachFlags, const Vector3 &vecWorldPos, const Quaternion* pQuatOrientation) #else void CPhysical::AttachToWorldBasic(u32 nAttachFlags, const Vector3 &vecWorldPos, const Quaternion* pQuatOrientation) #endif { fwAttachmentEntityExtension *extension = CreateAttachmentExtensionIfMissing(); Assert(extension->GetAttachState() != ATTACH_STATE_PHYSICAL); extension->AttachToEntity(this, NULL, -1, nAttachFlags, &vecWorldPos, pQuatOrientation); #if __BANK extension->DebugSetInvocationData(strCodeFile, strCodeFunction, nCodeLine); #endif DeActivatePhysics(); if(GetCurrentPhysicsInst()) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, true); extension->SetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET, false); extension->SetAttachFlag(ATTACH_FLAG_NOT_IN_LEVEL_INITIALLY, !GetCurrentPhysicsInst()->IsInLevel()); } else { // Physics inst not available yet. Flag this so that we can deal with this correctly when processing // attachment when it does stream in. extension->SetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET, true); extension->SetAttachFlag(ATTACH_FLAG_NOT_IN_LEVEL_INITIALLY, true); } if(extension->GetAttachFlag(ATTACH_FLAG_COL_ON)) { EnableCollision(); } else { DisableCollision(); } if(IsInPathServer()) { CAssistedMovementRouteStore::MaybeRemoveRouteForDoor(this); CPathServerGta::MaybeRemoveDynamicObject(this); } extension->SetAttachFlags((u32) (extension->GetAttachFlags() | ATTACH_FLAG_ATTACHED_TO_WORLD_BASIC)); #if __ASSERT // Checking for QNans...bogus attachment offsets. Assertf(vecWorldPos == vecWorldPos, "Invalid world position for attachment."); Vector3 vOffset = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); Assertf(vOffset == vOffset, "Invalid physical position for attachment."); vOffset -= vecWorldPos; Assertf(vOffset.Mag2() < 1000000.0f, "Invalid offset specified for attachment (offset too large)."); Assertf(!pQuatOrientation || pQuatOrientation->IsEqual(*pQuatOrientation), "Invalid orientation specified for attachment."); #endif } #define MAKE_ALLOWED_SEPARATION_ZERO_IF_WARPING (1) #if __BANK bool CPhysical::DebugAttachToPhysicalUsingPhysics(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPhysical* pPhysical, s16 nParentBone, s16 nMyBone, u32 nAttachFlags, const Vector3* pParentAnchorLocalSpace, const Quaternion* pQuatOrientation, const Vector3* pMyAnchorLocalSpace, float fStrength, bool bAllowInitialSeparation, float massInvScaleA, float massInvScaleB) #else bool CPhysical::AttachToPhysicalUsingPhysics(CPhysical* pPhysical, s16 nParentBone, s16 nMyBone, u32 nAttachFlags, const Vector3* pParentAnchorLocalSpace, const Quaternion* pQuatOrientation, const Vector3* pMyAnchorLocalSpace, float fStrength, bool bAllowInitialSeparation, float massInvScaleA, float massInvScaleB) #endif { fwAttachmentEntityExtension *extension = CreateAttachmentExtensionIfMissing(); //1. Check preconditions // Make sure we aren't already attached physically, or if we are that we are attached to the same object as we are attaching to now Assert(extension->GetAttachState()!=ATTACH_STATE_PHYSICAL || pPhysical == extension->GetAttachParent()); Assert(pPhysical); ASSERT_ONLY( if(!Verifyf(pPhysical != this, "Attempting to perform a physical attachment of an entity to itself, which the attachment tree will not support")) return false; ) if(pPhysical==NULL) { return false; } // Need to prevent loops of attachments if(extension->GetAttachParentForced() != pPhysical && GetIsPhysicalAParentAttachment(pPhysical)) { Assertf(false,"Attempting to attach a physical to a grandparent"); return false; } if(nMyBone != -1 && (!Verifyf(GetSkeleton(), "[%s] has no skeleton but has been passed a bone index of [%d]", GetModelName(), nMyBone) || !Verifyf(nMyBone < GetSkeleton()->GetBoneCount(), "Bone index [%d] is not valid for skeleton [%s], range [0..%d]", nMyBone, GetModelName(), GetSkeleton()->GetBoneCount()))) { // Clear the bone index nMyBone = -1; } if(nParentBone != -1 && (!Verifyf(pPhysical->GetSkeleton(), "[%s] has no skeleton but has been passed a bone index of [%d]", pPhysical->GetModelName(), nParentBone) || !Verifyf(nParentBone < pPhysical->GetSkeleton()->GetBoneCount(), "Bone index [%d] is not valid for skeleton [%s], range [0..%d]", nParentBone, pPhysical->GetModelName(), pPhysical->GetSkeleton()->GetBoneCount()))) { // Clear the bone index nParentBone = -1; } //2. Create the two nodes and edge between them in the tree of entities if necessary extension->AttachToEntity(this, pPhysical, nParentBone, nAttachFlags, pParentAnchorLocalSpace, pQuatOrientation, pMyAnchorLocalSpace, nMyBone); #if __BANK extension->DebugSetInvocationData(strCodeFile, strCodeFunction, nCodeLine); #endif if(!Verifyf(pPhysical->GetCurrentPhysicsInst(), "Parent [%s] has no physics", pPhysical->GetModelName()) || !Verifyf(GetCurrentPhysicsInst(), "Physical [%s] has no physics", GetModelName())) { // Cannot physically attach objects that are not physically active extension->SetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET, true); return false; } extension->SetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET, false); Assert(extension->GetAttachFlag(ATTACH_FLAG_POS_CONSTRAINT)); Assert(extension->GetAttachState()==ATTACH_STATE_PHYSICAL || extension->GetAttachState()==ATTACH_STATE_RAGDOLL); Vector3 vecEntAttachPosLocal = pParentAnchorLocalSpace ? *pParentAnchorLocalSpace : VEC3_ZERO; Vector3 vecMyAttachPosLocal = pMyAnchorLocalSpace ? * pMyAnchorLocalSpace : VEC3_ZERO; //3. Calculate permitted movement float fAllowedSeparation = 0.0f; if( bAllowInitialSeparation ) { Vector3 vecEntAttachPosWorld; Vector3 vecMyAttachPosWorld; Matrix34 matMyAttach; Matrix34 matOtherAttach; if(nMyBone > -1) { GetGlobalMtx(nMyBone, matMyAttach); } else { matMyAttach = MAT34V_TO_MATRIX34(GetMatrix()); } if(nParentBone > -1) { pPhysical->GetGlobalMtx(nParentBone, matOtherAttach); } else { matOtherAttach = MAT34V_TO_MATRIX34(pPhysical->GetMatrix()); } matMyAttach.Transform(vecMyAttachPosLocal, vecMyAttachPosWorld); matOtherAttach.Transform(vecEntAttachPosLocal, vecEntAttachPosWorld); fAllowedSeparation = vecEntAttachPosWorld.Dist(vecMyAttachPosWorld); } //5. Do initial warp if requested so that separation is initially 0.0f if(extension->GetAttachFlag(ATTACH_FLAG_INITIAL_WARP)) { Matrix34 matNew(Matrix34::IdentityType); // First get transform from other pivot point back to entity root // Therefore everything here is negated / transposed if(pMyAnchorLocalSpace) { matNew.d -= *pMyAnchorLocalSpace; } Matrix34 matOtherOffset(Matrix34::IdentityType); if(pQuatOrientation) matOtherOffset.FromQuaternion(*pQuatOrientation); if(pParentAnchorLocalSpace) matOtherOffset.d = *pParentAnchorLocalSpace; matNew.Dot(matOtherOffset); Matrix34 matParent; matParent = MAT34V_TO_MATRIX34(pPhysical->GetMatrix()); matNew.Dot(matParent); SetMatrix(matNew,true,true,true); #if MAKE_ALLOWED_SEPARATION_ZERO_IF_WARPING //Hack to make the function work as it used to fAllowedSeparation = 0.0f; #endif } //6. Ensure inst is active before making constraint ActivatePhysics(); //7. Work out components from bone indices u16 uParentComponent = 0; if(pPhysical->GetFragInst() && nParentBone > -1) { int nParentComponent = pPhysical->GetFragInst()->GetControllingComponentFromBoneIndex(nParentBone); if(nParentComponent < 0) nParentComponent = 0; uParentComponent = (u16)nParentComponent; } u16 uMyComponent = 0; if(GetFragInst() && nMyBone > -1) { int nMyComponent = GetFragInst()->GetControllingComponentFromBoneIndex(nMyBone); if(nMyComponent < 0) nMyComponent = 0; uMyComponent = (u16)nMyComponent; physicsAssertf(uMyComponent < GetFragInst()->GetNumCompositeParts(), "entity=%s, component=%d(max=%d)", GetModelName(), uMyComponent, GetFragInst()->GetNumCompositeParts()); } //8. If a constraint already exists between the components, update it, or make a new constraint and store its handle in the tree phConstraintBase* pConstraint = extension->FindConstraint(GetCurrentPhysicsInst(), pPhysical->GetCurrentPhysicsInst(), uMyComponent, uParentComponent); if( pConstraint ) { phConstraintAttachment* pConstraintAttach = static_cast( pConstraint ); pConstraintAttach->SetBreakable(fStrength > 0.0f, fStrength); pConstraintAttach->SetLocalPosA( VECTOR3_TO_VEC3V(vecMyAttachPosLocal) ); pConstraintAttach->SetLocalPosB( VECTOR3_TO_VEC3V(vecEntAttachPosLocal) ); } else //Make a new constraint { if( physicsVerifyf( extension->GetNumConstraintHandles() < ATTACHMENT_MAX_CONSTRAINTS, "No room in the attachment extension for more constraints between these entities. Max number is %d", ATTACHMENT_MAX_CONSTRAINTS) ) { phConstraintAttachment::Params params; params.instanceA = GetCurrentPhysicsInst(); params.instanceB = pPhysical->GetCurrentPhysicsInst(); params.componentA = uMyComponent; params.componentB = uParentComponent; params.breakable = fStrength > 0.0f; params.breakingStrength = fStrength; params.constrainRotation = extension->GetAttachFlag(ATTACH_FLAG_ROT_CONSTRAINT); params.maxSeparation = fAllowedSeparation; params.localPosA = VECTOR3_TO_VEC3V(vecMyAttachPosLocal); params.localPosB = VECTOR3_TO_VEC3V(vecEntAttachPosLocal); params.massInvScaleA = massInvScaleA; params.massInvScaleB = massInvScaleB; phConstraintHandle attachmentHandle = PHCONSTRAINT->Insert(params); if(physicsVerifyf(attachmentHandle.IsValid(), "Unable to allocate an attachment constraint")) { extension->AddConstraintHandle(attachmentHandle); } else { return false; } } else { return false; } } //9. Notify other code of changes if(IsInPathServer()) { CAssistedMovementRouteStore::MaybeRemoveRouteForDoor(this); CPathServerGta::MaybeRemoveDynamicObject(this); } return true; } #if __BANK bool CPhysical::DebugAttachToPhysicalUsingPhysics(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPhysical* pPhysical, s16 nParentBone, s16 nMyBone, u32 nAttachFlags, const Vector3& vecWorldPos, float fStrength) #else bool CPhysical::AttachToPhysicalUsingPhysics(CPhysical* pPhysical, s16 nParentBone, s16 nMyBone, u32 nAttachFlags, const Vector3& vecWorldPos, float fStrength) #endif { //Get matrix Matrix34 matMyAttach; Matrix34 matOtherAttach; if(nMyBone > -1) { GetGlobalMtx(nMyBone, matMyAttach); } else { matMyAttach = MAT34V_TO_MATRIX34(GetMatrix()); } fragInst* pParentFragInst = pPhysical->GetFragInst(); if (pParentFragInst) { s32 componentIndex = pParentFragInst->GetControllingComponentFromBoneIndex(nParentBone); if (componentIndex !=-1) { if (pParentFragInst->GetTypePhysics() && pParentFragInst->GetTypePhysics()->GetAllChildren()[componentIndex]) { u32 boneId = pParentFragInst->GetTypePhysics()->GetAllChildren()[componentIndex]->GetBoneID(); nParentBone = (s16)pPhysical->GetBoneIndexFromBoneTag((eAnimBoneTag)boneId); } } } if(nParentBone > -1) { pPhysical->GetGlobalMtx(nParentBone, matOtherAttach); } else { matOtherAttach = MAT34V_TO_MATRIX34(pPhysical->GetMatrix()); } //Calculate positions in local space and then call the other AttachToPhysicalUsingPhysics Vector3 vecEntAttachPosLocal; Vector3 vecMyAttachPosLocal; matMyAttach.UnTransform(vecWorldPos, vecMyAttachPosLocal); matOtherAttach.UnTransform(vecWorldPos, vecEntAttachPosLocal); #if __BANK return DebugAttachToPhysicalUsingPhysics(strCodeFile, strCodeFunction, nCodeLine, pPhysical, nParentBone, nMyBone, nAttachFlags, &vecEntAttachPosLocal, NULL, &vecMyAttachPosLocal, fStrength, /* bAllowInitialSeparation */ false ); #else return AttachToPhysicalUsingPhysics(pPhysical, nParentBone, nMyBone, nAttachFlags, &vecEntAttachPosLocal, NULL, &vecMyAttachPosLocal, fStrength, /* bAllowInitialSeparation */ false ); #endif } #if __BANK bool CPhysical::DebugAttachToWorldUsingPhysics(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, s16 nMyBone, bool bRotConstraint, const Vector3& vecWorldPos, const Vector3* pVecMyOffset, float fStrength, bool bAutoActivateOnDetach, const Vector3* ASSERT_ONLY(vecRotMinLimits), const Vector3* ASSERT_ONLY(vecRotMaxLimits)) #else bool CPhysical::AttachToWorldUsingPhysics(s16 nMyBone, bool bRotConstraint, const Vector3& vecWorldPos, const Vector3* pVecMyOffset, float fStrength, bool bAutoActivateOnDetach, const Vector3* ASSERT_ONLY(vecRotMinLimits), const Vector3* ASSERT_ONLY(vecRotMaxLimits)) #endif { fwAttachmentEntityExtension *extension = CreateAttachmentExtensionIfMissing(); //1. Check preconditions if(extension->GetAttachParentForced() != NULL) return false; //There is already an attach parent that isn't the world (detach from it before calling this) if(!GetCurrentPhysicsInst()) return false; //Can't create a physical constraint without an inst. Abort physicsAssertf((vecRotMinLimits == NULL) || (vecRotMinLimits->GetX() == 0.0f && vecRotMinLimits->GetY() == 0.0f && vecRotMinLimits->GetZ() == 0.0f), "attachment to world can no longer specify rotational limits"); physicsAssertf((vecRotMaxLimits == NULL) || (vecRotMaxLimits->GetX() == 0.0f && vecRotMaxLimits->GetY() == 0.0f && vecRotMaxLimits->GetZ() == 0.0f), "attachment to world can no longer specify rotational limits"); //2. Create node and edge between it and a "NULL node" in the tree (if necessary) u32 nAttachFlags = ATTACH_STATE_WORLD_PHYSICAL | ATTACH_FLAG_POS_CONSTRAINT | (bRotConstraint? ATTACH_FLAG_ROT_CONSTRAINT : 0) | (bAutoActivateOnDetach? ATTACH_FLAG_ACTIVATE_ON_DETACH : 0); extension->AttachToEntity(this, NULL, nMyBone, nAttachFlags, &vecWorldPos, NULL); #if __BANK extension->DebugSetInvocationData(strCodeFile, strCodeFunction, nCodeLine); #endif nMyBone = GetSkeleton() && Verifyf(nMyBone < GetSkeleton()->GetBoneCount(), "Bone index [%d] is not valid for skeleton [%s], range [0..%d]", nMyBone, GetModelName(), GetSkeleton()->GetBoneCount()) ? nMyBone : -1; //3. Activate so that constraint is certainly enforced GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false); ActivatePhysics(); //4. Calculate component from bone u16 uMyComponent = 0; if(GetFragInst() && nMyBone > -1) { int nMyComponent = GetFragInst()->GetComponentFromBoneIndex(nMyBone); if(nMyComponent < 0) nMyComponent = 0; uMyComponent = (u16)nMyComponent; } Vector3 vecMyAttachPosLocal; if( pVecMyOffset ) { vecMyAttachPosLocal = *pVecMyOffset; } else { Matrix34 matMyAttach; if(nMyBone > -1) { GetGlobalMtx(nMyBone, matMyAttach); } else { matMyAttach = MAT34V_TO_MATRIX34(GetMatrix()); } matMyAttach.UnTransform(vecWorldPos, vecMyAttachPosLocal); } //5. If a constraint already exists between the components, replace it, or make a new constraint and store its handle in the tree phConstraintAttachment* pConstraint = static_cast( extension->FindConstraint(GetCurrentPhysicsInst(), NULL, uMyComponent, 0) ); if( pConstraint ) { if(pVecMyOffset) { pConstraint->SetLocalPosA(VECTOR3_TO_VEC3V(*pVecMyOffset)); } else { pConstraint->SetWorldPosA(RCC_VEC3V(vecWorldPos)); } pConstraint->SetWorldPosB(RCC_VEC3V(vecWorldPos)); } else //Make a new constraint { if( physicsVerifyf( extension->GetNumConstraintHandles() < ATTACHMENT_MAX_CONSTRAINTS, "No room in the attachment extension for more constraints between these entities. Max number is %d", ATTACHMENT_MAX_CONSTRAINTS) ) { phConstraintAttachment::Params params; params.instanceA = GetCurrentPhysicsInst(); params.componentA = uMyComponent; params.breakable = fStrength > 0.0f; params.breakingStrength = fStrength; params.constrainRotation = bRotConstraint; params.maxSeparation = 0.0f; params.localPosA = RCC_VEC3V(vecMyAttachPosLocal); params.localPosB = RCC_VEC3V(vecWorldPos); phConstraintHandle attachmentHandle = PHCONSTRAINT->Insert(params); if(physicsVerifyf(attachmentHandle.IsValid(), "Unable to allocate an attachment constraint")) { extension->AddConstraintHandle(attachmentHandle); } else { return false; } } else { return false; } } return true; } bool CPhysical::GetGlobalAttachMatrix(Mat34V_Ref attachMatInOut) const { fwEntity* pParentEntity = GetAttachParent(); if (pParentEntity) { const s16 iAttachBone = GetAttachBone(); if (iAttachBone > -1) { Matrix34 tempMtx(Matrix34::IdentityType); pParentEntity->GetGlobalMtx(iAttachBone, tempMtx); attachMatInOut = MATRIX34_TO_MAT34V(tempMtx); } else { attachMatInOut = pParentEntity->GetTransform().GetMatrix(); } return true; } return false; } void CPhysical::AttachParentPartBrokeOff(CPhysical* pNewParent) { fwAttachmentEntityExtension *extension = CreateAttachmentExtensionIfMissing(); // If this physical object is attached to a part which broke off, do something about it now. physicsAssert(pNewParent); physicsAssert(extension->GetAttachParentForced()); // For now just switch our attach parent to the new entity. Could use flags to decide how // to respond, or make this function virtual and override it in a derived class. Start by storing // properties of current attachment so we can duplicate them on the new attachment. s16 nEntBone = extension->GetOtherAttachBone(); u32 nAttachFlags = extension->GetAttachFlags(); Vector3 vAttachOffset = extension->GetAttachOffset(); Quaternion attachQuat = extension->GetAttachQuat(); DetachFromParent(0); AttachToPhysicalBasic(pNewParent, nEntBone, nAttachFlags, &vAttachOffset, &attachQuat); } void CPhysical::DetachFromParentAndChildren(u16 nDetachFlags) { fwAttachmentEntityExtension *extension = GetAttachmentExtension(); if (!extension) { return; } CPhysical * child = static_cast( extension->GetChildAttachment() ); //This may delete the attachment DetachFromParent(nDetachFlags); while(child) { fwAttachmentEntityExtension &childAttachExt = child->GetAttachmentExtensionRef(); //Some tree edges may go into the detaching state whilst others will be completely removed from the tree CPhysical* sibling = static_cast( childAttachExt.GetSiblingAttachment() ); child->DetachFromParent(nDetachFlags); child = sibling; } } void CPhysical::DetachFromParent(u16 nDetachFlags) { if(GetType() != ENTITY_TYPE_VEHICLE) { // Vehicles can be destroyed from threads other than the update thread, and fwAltSkeletonExtension::RemoveExtension // is only safe to call from the update thread. However, vehicles don't have an fwAltSkeletonExtension anyway... fwAltSkeletonExtension::RemoveExtension(*this); } fwAttachmentEntityExtension *extension = GetAttachmentExtension(); if(!extension) return; if( extension->GetAttachFlag( ATTACH_FLAG_NOT_NEEDED_ON_DETACH ) ) { int EntityIndex; entity_commands::SetEntityAsNoLongerNeeded(this, EntityIndex); } Assertf(!extension->GetIsAttachedToGround() || !(nDetachFlags & DETACH_FLAG_NO_COLLISION_UNTIL_CLEAR), "Detaching ped from its ground physical and disabling collision between them is probably not what you want."); #if __BANK if( extension->GetAttachParent() && extension->GetAttachParent()->GetType() == ENTITY_TYPE_VEHICLE ) { CVehicle* vehicle = static_cast< CVehicle* >( extension->GetAttachParent() ); if( vehicle && vehicle->GetIsHeli() ) { CHeli* heli = static_cast< CHeli* >( vehicle ); if( heli->GetIsCargobob() ) { vehicleDisplayf("[PICKUP ROPE DEBUG] CPhysical::DetachFromParent - Detaching %s entity attached to parent Vehicle: %s", GetModelName(), heli->GetNetworkObject() ? heli->GetNetworkObject()->GetLogName() : heli->GetModelName() ); sysStack::PrintStackTrace(); } } } #endif // #if __BANK if( nDetachFlags & DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS ) { if(extension->IsAttachStateBasicDerived()) return; } physicsAssertf(!extension->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION), "DetachFromParent called recursively"); extension->SetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION, true); if(!IsProtectedBaseFlagSet(fwEntity::USES_COLLISION) REPLAY_ONLY(&& GetOwnedBy() != ENTITY_OWNEDBY_REPLAY)) { EnableCollision(); } SetForceAddToBoundRadius(0.0f); // if(m_pAttachParent && m_pAttachParent->GetIsTypePed()) // { // ((CPed*)m_pAttachParent.Get())->RemoveEntityFromAttachedList(this); // } if(GetCurrentPhysicsInst()) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false); if(GetCurrentPhysicsInst()->IsInLevel()) { // Reset the archetype flags back to model info default CBaseModelInfo* pModelInfo = GetBaseModelInfo(); Assert(pModelInfo); u32 uOrigIncludeFlags = 0; fragInst* pFragInst = GetFragInst(); if(GetCurrentPhysicsInst() == pFragInst) { // Deal with frag, get original include flags from type Assert(pFragInst); Assert(pFragInst->GetTypePhysics()); Assert(pFragInst->GetTypePhysics()->GetArchetype()); uOrigIncludeFlags = pFragInst->GetTypePhysics()->GetArchetype()->GetIncludeFlags(); } else if(Verifyf(pModelInfo->GetPhysics(), "Model '%s' has no physics archetype", pModelInfo->GetModelName())) { uOrigIncludeFlags = pModelInfo->GetPhysics()->GetIncludeFlags(); } CPhysics::GetLevel()->SetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), uOrigIncludeFlags); } } bool const bEnvCloth = ( GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->GetArchetype() && GetCurrentPhysicsInst()->GetArchetype()->GetTypeFlags() == ArchetypeFlags::GTA_ENVCLOTH_OBJECT_TYPE ); bool const bActivateFlagSet = ( (nDetachFlags & DETACH_FLAG_ACTIVATE_PHYSICS) || extension->GetAttachFlag(ATTACH_FLAG_ACTIVATE_ON_DETACH) ) && !bEnvCloth; if(bActivateFlagSet) { if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel()) { ActivatePhysics(); } #if __ASSERT else { physicsDisplayf("Can't activate %s on detach. phInst=%p", GetModelName(), GetCurrentPhysicsInst()); } #endif // __ASSERT } CPhysical* pAttachParent = (CPhysical*)extension->GetAttachParentForced(); #if __BANK if( pAttachParent && pAttachParent->GetIsTypeVehicle() && !GetIsTypePed() && !( GetIsTypeObject() && static_cast(this)->GetAsProjectile() ) ) { Displayf("[CPhysical::DetachFromParent] [Object: %s is detaching from Vehicle: %s]", GetModelName(), pAttachParent->GetModelName() ); size_t m_lastSetFlagsCallstack[8]; sysStack::CaptureStackTrace(m_lastSetFlagsCallstack, 8, 1); sysStack::PrintCapturedStackTrace(m_lastSetFlagsCallstack, 8); } #endif // #if __BANK if(extension->GetAttachState() == ATTACH_STATE_WORLD_PHYSICAL || extension->GetAttachState() == ATTACH_STATE_PHYSICAL || extension->GetAttachState() == ATTACH_STATE_RAGDOLL) { extension->RemoveAttachmentConstraints(); } else if(pAttachParent && (nDetachFlags &DETACH_FLAG_APPLY_VELOCITY) && (nDetachFlags &DETACH_FLAG_ACTIVATE_PHYSICS)) { bool bVelocitiesDone = false; if(nDetachFlags & DETACH_FLAG_USE_ROOT_PARENT_VELOCITY) { fwAttachmentEntityExtension *pRootextension = pAttachParent->GetAttachmentExtension(); if(pRootextension) { CPhysical* pAttachRoot = (CPhysical*)pRootextension->GetAttachParentForced(); if(pAttachRoot) { SetVelocity(pAttachRoot->GetVelocity()); bVelocitiesDone = true; } } } if(!bVelocitiesDone && pAttachParent->GetIsTypePed() && extension->GetOtherAttachBone() > -1 && pAttachParent->GetFragInst()) { // if we were attached to a ped bone, try to use the ragdoll current and last matrices to calculate velocity CPed* pAttachedToPed = static_cast(pAttachParent); Matrix34 matCurrent, matPrevious; // if((pAttachedToPed->GetFragInst()->GetPosition() - pAttachedToPed->GetFragInst()->GetLastMatrix().d).Mag2() < 1.0f if(IsLessThanAll(MagSquared(pAttachedToPed->GetFragInst()->GetMatrix().GetCol3() - PHSIM->GetLastInstanceMatrix(pAttachedToPed->GetFragInst()).GetCol3()), ScalarV(V_ONE)) && pAttachedToPed->GetFragInst()->GetBoneMatrix(matPrevious, extension->GetOtherAttachBone(), true) && pAttachedToPed->GetFragInst()->GetBoneMatrix(matCurrent, extension->GetOtherAttachBone(), false)) { Matrix34 matOffset; matOffset.FromQuaternion(extension->GetAttachQuat()); matOffset.d = extension->GetAttachOffset(); matPrevious.Dot(matOffset); matPrevious.Dot(RCC_MATRIX34(PHSIM->GetLastInstanceMatrix(pAttachedToPed->GetFragInst()))); matCurrent.Dot(matOffset); matCurrent.Dot(RCC_MATRIX34(pAttachedToPed->GetFragInst()->GetMatrix())); Vector3 vecMoveSpeed = (matCurrent.d - matPrevious.d) * fwTimer::GetInvTimeStep(); if(vecMoveSpeed.Mag2() > 30.0f*30.0f) { vecMoveSpeed.NormalizeFast(); if(NetworkUtils::IsNetworkClone((CEntity *) extension->GetAttachParentForced())) vecMoveSpeed.Scale(5.0f); else vecMoveSpeed.Scale(30.0f); } SetVelocity(vecMoveSpeed); Matrix34 lastToCurrent; lastToCurrent.DotTranspose(matCurrent, matPrevious); Quaternion tempQuat; lastToCurrent.ToQuaternion(tempQuat); Vector3 angularDelta; float angle; tempQuat.ToRotation(angularDelta, angle); angularDelta.Scale(angle); Vector3 vecTurnSpeed = angularDelta * fwTimer::GetInvTimeStep(); if(vecTurnSpeed.Mag2() > 10.0f*10.0f) { vecTurnSpeed.NormalizeFast(); if(NetworkUtils::IsNetworkClone((CEntity *) GetAttachParentForced())) vecMoveSpeed.Scale(5.0f); else vecMoveSpeed.Scale(10.0f); } SetAngVelocity(vecTurnSpeed); bVelocitiesDone = true; } } if(pAttachParent->GetIsPhysical() && pAttachParent->GetCurrentPhysicsInst() && !bVelocitiesDone) { CPhysical *pAttachedToPhysical = static_cast(pAttachParent); SetVelocity(pAttachedToPhysical->GetLocalSpeed(VEC3V_TO_VECTOR3(GetTransform().GetPosition()), true)); SetAngVelocity(pAttachedToPhysical->GetAngVelocity()); } } // want to test this bound against the thing we were attached to // and also test against the world to see if we're going to fall through // set attach type to DETACHING so it doesn't collide with the thing it was attached to Matrix34 m = MAT34V_TO_MATRIX34(GetMatrix()); bool bBoundTestHitSomething = false; // No point in doing a test against individual objects if we don't have any to test against... if(pAttachParent && (nDetachFlags&DETACH_FLAG_NO_COLLISION_UNTIL_CLEAR) && pAttachParent->GetCurrentPhysicsInst() && pAttachParent->GetCurrentPhysicsInst()->IsInLevel()) { WorldProbe::CShapeTestBoundDesc boundTestDesc; boundTestDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS); boundTestDesc.SetBoundFromEntity(this); boundTestDesc.SetIncludeEntity(pAttachParent); boundTestDesc.SetTransform(&m); bBoundTestHitSomething = WorldProbe::GetShapeTestManager()->SubmitTest(boundTestDesc); } extension->SetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION, false); if(pAttachParent && (nDetachFlags&DETACH_FLAG_NO_COLLISION_UNTIL_CLEAR) && bBoundTestHitSomething) { extension->SetAttachFlags(ATTACH_STATE_DETACHING); //State in which ShouldFindImpacts will return false between the pair } else { extension->ClearParentAttachmentVars(); } if(!IsInPathServer()) { if(CPathServerGta::MaybeAddDynamicObject(this)) CAssistedMovementRouteStore::MaybeAddRouteForDoor(this); } // allow child object to clean up if (GetIsTypeObject()) { // B* 1717278 - this is pretty horrible. when we clean up a vehicle with objects attached, and detach the child objects // we want to allow the objects to be potentially cleaned up even if the script has said otherwise. // // A better fix in the future would be to expose the required behaviours (ie all the attach / detach flags) to script. CObject* pObject = static_cast(this); if (pObject && !pObject->GetIsLeakedObject() && pObject->m_nObjectFlags.bOnlyCleanUpWhenOutOfRange) { pObject->m_nObjectFlags.bOnlyCleanUpWhenOutOfRange = false; } } #if GTA_REPLAY if(CReplayMgr::ShouldRecord() && (nDetachFlags & DETACH_FLAG_IN_PHYSICAL_DESTRUCTOR) == 0 && this->GetReplayID() != ReplayIDInvalid && pAttachParent && pAttachParent->GetReplayID() != ReplayIDInvalid) { CReplayMgr::RecordEntityDetachment(GetReplayID(), pAttachParent->GetReplayID()); } #endif // GTA_REPLAY #if __BANK if(NetworkInterface::IsGameInProgress()) { if(pAttachParent && pAttachParent->GetIsTypeVehicle() && pAttachParent->PopTypeIsMission() && this->GetIsTypeObject() && this->PopTypeIsMission()) { physicsDisplayf("[ATTACH DEBUG] Detaching %s from %s.", GetDebugNameFromObjectID(), pAttachParent->GetDebugNameFromObjectID()); } } #endif } #if __BANK void CPhysical::DebugProcessAttachment(const char * ATTACHMENT_ENABLE_UPDATE_RECORDS_PARAM(strCodeFile), const char* ATTACHMENT_ENABLE_UPDATE_RECORDS_PARAM(strCodeFunction), u32 ATTACHMENT_ENABLE_UPDATE_RECORDS_PARAM(nCodeLine) ) #else void CPhysical::ProcessAttachment() #endif { fwAttachmentEntityExtension* extension = GetAttachmentExtension(); if(!extension) return; extension->SetAttachFlag(ATTACH_FLAG_DONT_ASSERT_ON_MATRIX_CHANGE, true); #if __BANK #if ATTACHMENT_ENABLE_UPDATE_RECORDS DebugProcessAttachmentImp(strCodeFile, strCodeFunction, nCodeLine); #else // ATTACHMENT_ENABLE_UPDATE_RECORDS DebugProcessAttachmentImp("", "", 0); #endif // ATTACHMENT_ENABLE_UPDATE_RECORDS #else // __BANK ProcessAttachmentImp(); #endif // __BANK // verify that extension was deleted inside process attachement if(extension == GetAttachmentExtension()) { extension->SetAttachFlag(ATTACH_FLAG_DONT_ASSERT_ON_MATRIX_CHANGE, false); } } #if __BANK void CPhysical::DebugProcessAttachmentImp(const char * ATTACHMENT_ENABLE_UPDATE_RECORDS_PARAM(strCodeFile), const char* ATTACHMENT_ENABLE_UPDATE_RECORDS_PARAM(strCodeFunction), u32 ATTACHMENT_ENABLE_UPDATE_RECORDS_PARAM(nCodeLine) ) #else void CPhysical::ProcessAttachmentImp() #endif { fwAttachmentEntityExtension* extension = GetAttachmentExtension(); if(!extension) { return; } CPhysical* pAttachParent = (CPhysical*)extension->GetAttachParentForced(); #if __DEV && !__SPU // JB: 22/8/07 added this assert in attempt to catch crash where attached entity's drawable has a bad skeleton-data ptr if(pAttachParent) { CBaseModelInfo * pModelInfo = GetBaseModelInfo(); rmcDrawable * pDrawable = pModelInfo->GetDrawable(); if(pDrawable && m_pDrawHandler) { Assertf(pDrawable->GetSkeletonData() == m_pDrawHandler->GetDrawable()->GetSkeletonData(), "Mismatch between entity's m_SkeletonData and modelinfo's m_SkeletonData."); } } #endif if(extension->IsAttachStateBasicDerived()) { physicsAssertf(pAttachParent, "A basic attachment has lost its parent"); if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel()) { if( !extension->GetAttachFlag(ATTACH_FLAG_COL_ON) ) { bool bNeedToSetIncludeFlags = extension->GetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET) || extension->GetAttachFlag(ATTACH_FLAG_NOT_IN_LEVEL_INITIALLY); u32 nLevelIndex = GetCurrentPhysicsInst()->GetLevelIndex(); u32 nInstIncludeFlags = CPhysics::GetLevel()->GetInstanceIncludeFlags(nLevelIndex); if(bNeedToSetIncludeFlags || !Verifyf(nInstIncludeFlags == u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES) || nInstIncludeFlags == u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_AND_CAMERA_INCLUDE_TYPES) || nInstIncludeFlags == u32(ArchetypeFlags::GTA_CAMERA_TEST) || nInstIncludeFlags==0u, "Include flags != GTA_BASIC_ATTACHMENT_INCLUDE_TYPES or 0: 0x%X for %s. NetClone?%s. Attach fn:%s", CPhysics::GetLevel()->GetInstanceIncludeFlags(nLevelIndex), GetDebugName(), IsNetworkClone()?"yes":"no", extension->DebugGetInvokingFunction()) ) { if(NetworkInterface::IsGameInProgress() && GetNetworkObject()) { CNetObjEntity* netEnt = SafeCast(CNetObjEntity, GetNetworkObject()); if(!netEnt || !netEnt->GetDisableCollisionCompletely()) { CPhysics::GetLevel()->SetInstanceIncludeFlags(nLevelIndex, u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES)); } } else { CPhysics::GetLevel()->SetInstanceIncludeFlags(nLevelIndex, u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES)); } // Reset these flags no that the physics has loaded and been added to the level. extension->SetAttachFlag(ATTACH_FLAG_NO_PHYSICS_INST_YET, false); extension->SetAttachFlag(ATTACH_FLAG_NOT_IN_LEVEL_INITIALLY, !GetCurrentPhysicsInst()->IsInLevel()); } } //Let the vehicle attachments override the activation flags if(extension->GetAttachState() == ATTACH_STATE_BASIC) { if(!AssertVerify(GetCurrentPhysicsInst()->GetInstFlag(phInst::FLAG_NEVER_ACTIVATE) != 0u)) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE,true); } if(!Verifyf(!CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex()), "Model(%s)", GetModelName())) { CPhysics::GetSimulator()->DeactivateObject(GetCurrentPhysicsInst()); } } } // If the entity is attached to a ped and has the auto detach flag set if (pAttachParent->GetIsTypePed() && ((CPed*)pAttachParent)->GetUsingRagdoll() && extension->GetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_RAGDOLL)) { DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS); return; } Matrix34 matOffset; matOffset.Identity(); matOffset.FromQuaternion(extension->GetAttachQuat()); matOffset.d = extension->GetAttachOffset(); // Check for NaNs in the position vector. Assert(extension->GetAttachOffset().IsEqual(extension->GetAttachOffset())); Matrix34 matParent = MAT34V_TO_MATRIX34(pAttachParent->GetMatrix()); Assertf(matParent.IsOrthonormal(), "Parent entity matrix found to be non-orthonormal (%5.3f).", matParent.MeasureNonOrthonormality()); ASSERT_ONLY(bool bMatParentFromSkel = false;) #if ENABLE_FRAG_OPTIMIZATION if(extension->GetOtherAttachBone() > -1 && pAttachParent->GetIsDynamic() && extension->GetOtherAttachBone() < (static_cast(pAttachParent))->GetBoneCount()) #else if(extension->GetOtherAttachBone() > -1 && pAttachParent->GetIsDynamic() && (static_cast(pAttachParent))->GetSkeleton()) #endif { const CDynamicEntity* pDynEnt = static_cast(pAttachParent); const fwTransform* pTransform = pAttachParent->GetTransformPtr(); if(pTransform->IsMatrixScaledTransform()) { // find out the attaching position using scale and local matrix. // GetGlobalMtx() doesn't return correct position if the parent is using a scaled transform. // B*1853481: Replicating some logic from CVfxHelper::GetMatrixFromBoneIndex to ensure we get a valid matrix even for damaged frag objects. // CVfxHelper::GetMatrixFromBoneIndex provides a global matrix, but we need an object matrix here. const Vector3 vParentPos = matParent.d; #if ENABLE_FRAG_OPTIMIZATION Matrix34 matLocal(Matrix34::IdentityType); int iBoneIndex = extension->GetOtherAttachBone(); if (aiVerifyf(pDynEnt->GetBoneCount() > 0 && (u32)iBoneIndex < pDynEnt->GetBoneCount(), "Entity %s doesn't have a skeleton or bone index out of range %u(%u)", pDynEnt->GetModelName(), iBoneIndex, pDynEnt->GetBoneCount())) { pDynEnt->ComputeObjectMtx(iBoneIndex, RC_MAT34V(matLocal)); } #else Matrix34 matLocal = pDynEnt->GetObjectMtx(extension->GetOtherAttachBone()); #endif if(pDynEnt->GetFragInst() && pDynEnt->GetFragInst()->GetCacheEntry() && pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst()) { const crSkeleton *const pDamagedSkel = pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst()->damagedSkeleton; if(matLocal.a.IsZero() && pDamagedSkel) { Mat34V boneMatDmg; boneMatDmg = pDamagedSkel->GetObjectMtx(extension->GetOtherAttachBone()); matLocal.Set(MAT34V_TO_MATRIX34(boneMatDmg)); } } // If we got back a zeroed out object matrix, but it has a valid global matrix, use the global matrix to calculate a valid object matrix. // This will happen for vehicles with modded parts. if(matLocal.a.IsZero()) { Matrix34 matGlobal; pDynEnt->GetGlobalMtx(extension->GetOtherAttachBone(), matGlobal); if(!matGlobal.a.IsZero()) { Matrix34 entMatrix = MAT34V_TO_MATRIX34(pDynEnt->GetMatrix()); entMatrix.Inverse(); matGlobal.Dot(entMatrix); matLocal.Set(matGlobal); } } Matrix34 tempMat = matParent; tempMat.Dot(matLocal, matParent); matParent = tempMat; Assertf(matParent.IsOrthonormal(), "[1] Parent entity matrix found to be non-orthonormal (%5.3f).", matParent.MeasureNonOrthonormality()); Vector3 vScaledOffset = matParent.d - vParentPos; float fScaleXY = ((fwMatrixScaledTransform*)pTransform)->GetScaleXY(); float fScaleZ = ((fwMatrixScaledTransform*)pTransform)->GetScaleZ(); vScaledOffset.x *= fScaleXY; vScaledOffset.y *= fScaleXY; vScaledOffset.z *= fScaleZ; matParent.d = vParentPos + vScaledOffset; Assertf(matParent.IsOrthonormal(), "[2] Parent entity matrix found to be non-orthonormal (%5.3f).", matParent.MeasureNonOrthonormality()); if( extension->GetMyAttachBone() != -1 ) { Matrix34 matMyBone = GetObjectMtx( extension->GetMyAttachBone() ); matMyBone.Inverse3x3(); //matOffset.Transform3x3( matMyBone.d ); pDynEnt->GetGlobalMtx( extension->GetOtherAttachBone(), matParent ); // the object rotation needs to be the rotation of the parent bone, in world space, minus the rotation of the child bone in local space // the position offset needs to be the bone offset, on the child, rotated by the parent object rotation matOffset.Dot3x3( matMyBone ); matOffset.Dot3x3( matParent ); matMyBone.d = -matMyBone.d; matOffset.Transform3x3( matMyBone.d ); matOffset.d = matMyBone.d; } } else { pDynEnt->GetGlobalMtx(extension->GetOtherAttachBone(), matParent); Assertf(matParent.IsOrthonormal(), "[3] Parent entity %s matrix found to be non-orthonormal (%5.3f).", pAttachParent->GetModelName(), matParent.MeasureNonOrthonormality()); // B*1853481 & 1880214: Ensure we get a valid matrix even for damaged frag objects. // If the standard GetGlobalMtx call gives a zeroed matrix, try grabbing the matrix from the damaged skeleton. // Was previously using CVfxHelper::GetMatrixFromBoneIndex, but that doesn't use the entity GetGlobalMtx method; it accesses the skeleton directly. // This caused a problem as CVehicle implements a special version of that method to handle cases where mod kits are in use. For some reason modded parts have zeroed out bone matrices in the frag inst. if(matParent.a.IsZero()) { if(pDynEnt->GetFragInst() && pDynEnt->GetFragInst()->GetCacheEntry() && pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst() && pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst()->damagedSkeleton) { Mat34V matParentV; pDynEnt->GetFragInst()->GetCacheEntry()->GetHierInst()->damagedSkeleton->GetGlobalMtx(extension->GetOtherAttachBone(), matParentV); matParent.Set(MAT34V_TO_MATRIX34(matParentV)); Assertf(matParent.IsOrthonormal(), "[4] Parent entity %s matrix found to be non-orthonormal (%5.3f).", pAttachParent->GetModelName(), matParent.MeasureNonOrthonormality()); } } } ASSERT_ONLY(bMatParentFromSkel = true;) } // Check to see if the physical parent disappeared from underneath us if( matParent.a.IsZero() ) { DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS); return; } Matrix34 matNew; if(extension->GetAttachFlag(ATTACH_FLAG_OFFSET_IS_ABSOLUTE)) { matNew = matOffset; matNew.d = matParent.d + matOffset.d; } else { matNew.Dot(matOffset, matParent); } #if DR_ENABLED if (smb_RecordAttachments && sm_RecordAttachmentsFunc) { sm_RecordAttachmentsFunc(GetCurrentPhysicsInst(), pAttachParent->GetCurrentPhysicsInst(), matNew); } #endif #if __ASSERT || ATTACHMENT_ENABLE_UPDATE_RECORDS float nonOrthonormalityParent = matParent.MeasureNonOrthonormality(); float nonOrthonormalityOffset = matOffset.MeasureNonOrthonormality(); float nonOrthonormalityMatNew = matNew.MeasureNonOrthonormality(); #if __ASSERT bool bMatNewOrthonormal = matNew.IsOrthonormal(); if(!bMatNewOrthonormal) { //Print information about the non-orthonormality Assertf(bMatNewOrthonormal, "ProcessAttachment.ATTACH_STATE_BASIC calculated matNew, which wasn't orthonormal: " "\n a(%.4f, %.4f, %.4f) b %.4f, %.4f, %.4f) c(%.4f, %.4f, %.4f) d(%.4f, %.4f, %.4f)\n" "Parent.NonOrth: %.4f Offset.NonOrth: %.4f New.NonOrth: %.4f\n" "Did matParent come from the skeleton?%s", matNew.a.x, matNew.a.y, matNew.a.z, matNew.b.x, matNew.b.y, matNew.b.z, matNew.c.x, matNew.c.y, matNew.c.z, matNew.d.x, matNew.d.y, matNew.d.z, nonOrthonormalityParent, nonOrthonormalityOffset, nonOrthonormalityMatNew, bMatParentFromSkel?"yes":"no"); Assertf(bMatNewOrthonormal, "Parent: %s, Child: %s", CModelInfo::GetBaseModelInfoName(pAttachParent->GetModelId()), CModelInfo::GetBaseModelInfoName(GetModelId())); } // Check for NaNs in the new matrix. if(matNew.IsNotEqual(matNew)) { Assertf(false, "ProcessAttachment::ATTACH_STATE_BASIC calculated matNew, which contains non-numbers:" "\n a(%.4f, %.4f, %.4f) b %.4f, %.4f, %.4f) c(%.4f, %.4f, %.4f) d(%.4f, %.4f, %.4f)\n" "Did matParent come from the skeleton?%s", matNew.a.x, matNew.a.y, matNew.a.z, matNew.b.x, matNew.b.y, matNew.b.z, matNew.c.x, matNew.c.y, matNew.c.z, matNew.d.x, matNew.d.y, matNew.d.z, bMatParentFromSkel?"yes":"no"); } #endif #if ATTACHMENT_ENABLE_UPDATE_RECORDS float nonOrthonormalityMatCurrent = MAT34V_TO_MATRIX34( GetMatrix() ).MeasureNonOrthonormality(); extension->AddUpdateRecord(strCodeFile, strCodeFunction, nCodeLine, nonOrthonormalityParent, nonOrthonormalityOffset, nonOrthonormalityMatCurrent, nonOrthonormalityMatNew); #endif #endif #if GTA_REPLAY if(!CReplayMgr::IsReplayInControlOfWorld() || GetOwnedBy()!=ENTITY_OWNEDBY_REPLAY) #endif // GTA_REPLAY { SetMatrix(matNew); } //Rage cloth begin. //Is the physical a rage env cloth instance? //If the physical is a rage env cloth then we'll need to update the pinned verts with the latest //transform. phInst* pInst = GetCurrentPhysicsInst(); if(pInst && pInst->IsInLevel() && CPhysics::GetLevel()->GetInstanceTypeFlags(pInst->GetLevelIndex())==ArchetypeFlags::GTA_ENVCLOTH_OBJECT_TYPE) { //Yup, the physical is a rage cloth. Assertf(dynamic_cast(GetCurrentPhysicsInst()), "Object with cloth must be a frag"); fragInst* pObjectFragInst=static_cast(GetCurrentPhysicsInst()); Assertf(pObjectFragInst->GetCached(), "Cloth object has no cache entry"); if(pObjectFragInst->GetCached()) { //The attachment frame. const Matrix34& attachFrame=matNew; //Pass the cloth attach frame to the env cloth. 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"); environmentCloth* pEnvCloth=pObjectHierInst->envCloth; Assertf(pEnvCloth, "Cloth object has null env cloth ptr"); pEnvCloth->SetInitialPosition( VECTOR3_TO_VEC3V(attachFrame.d) ); if( pEnvCloth->GetBehavior()->IsActivateOnHitOverridden() ) { pEnvCloth->GetBehavior()->SetActivateOnHit( false ); pEnvCloth->GetBehavior()->SetActivateOnHitOverridden( false ); } } } //Rage cloth end. if(GetPortalTracker() && pAttachParent->GetIsDynamic() REPLAY_ONLY(&& !CReplayMgr::IsReplayInControlOfWorld()) ) { // In case we are attached to something, portal tracking works a little differently than normal // objects: first, we force the position and interior location of the child's tracker to be the // same of the parent's, then we move it to the current position to let it correctly traverse portals. // In this way we avoid bugs where the parent was teleported to an interior but the attachments weren't, // and still have an accurate interior location for them. CPortalTracker* tracker = GetPortalTracker(); CPortalTracker* parentTracker = static_cast< CDynamicEntity* >( pAttachParent )->GetPortalTracker(); CInteriorInst* pIntInst = parentTracker->GetInteriorInst(); fwInteriorLocation parentLocation = pAttachParent->GetInteriorLocation(); bool bShouldProcessDelta = GetIsReadyForInsertion(); // only process deltas if ready to go in world bool parentIsAttachedToProtal = parentLocation.IsAttachedToPortal(); // you cannot add an object to a portal container. if (!parentIsAttachedToProtal && bShouldProcessDelta && tracker && parentTracker) { tracker->MoveToNewLocation( pIntInst, parentTracker->m_roomIdx ); tracker->Teleport(); tracker->Update( parentTracker->GetCurrentPos() ); //only peds and entities attached to the local player will track from parent location //to their current location (too expensive otherwise) if ((tracker->GetOwner() && tracker->GetOwner()->GetType()==ENTITY_TYPE_PED) || (pAttachParent == CGameWorld::FindLocalPlayer())) { UpdatePortalTracker(); } } } } else if(extension->GetAttachState()==ATTACH_STATE_WORLD) { // For world attachments we auto detach this ped on ragdoll if (GetIsTypePed() && static_cast(this)->GetUsingRagdoll() && extension->GetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_RAGDOLL)) { DetachFromParent(0); return; } // Getting too many invalid cases of this assert. Removing it for now. // if(!Verifyf(!CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex()), "Model(%s)", GetModelName())) // { // CPhysics::GetSimulator()->DeactivateObject(GetCurrentPhysicsInst()); // } Matrix34 matOffset; matOffset.Identity(); matOffset.FromQuaternion(extension->GetAttachQuat()); matOffset.d = extension->GetAttachOffset(); #if __ASSERT // Check for NaNs in the new matrix. if(matOffset.IsNotEqual(matOffset)) { Assertf(false, "ProcessAttachment::ATTACH_STATE_WORLD calculated matNew, which contains non-numbers:" "\n a(%.4f, %.4f, %.4f) b %.4f, %.4f, %.4f) c(%.4f, %.4f, %.4f) d(%.4f, %.4f, %.4f)\n", matOffset.a.x, matOffset.a.y, matOffset.a.z, matOffset.b.x, matOffset.b.y, matOffset.b.z, matOffset.c.x, matOffset.c.y, matOffset.c.z, matOffset.d.x, matOffset.d.y, matOffset.d.z); } #endif SetMatrix(matOffset); UpdatePortalTracker(); } else if(extension->GetAttachState()==ATTACH_STATE_WORLD_PHYSICAL) { } else if(extension->GetAttachState()==ATTACH_STATE_PHYSICAL || extension->GetAttachState()==ATTACH_STATE_RAGDOLL) { physicsAssertf(pAttachParent, "A physical attachment has lost its parent"); } else if(extension->GetAttachState()==ATTACH_STATE_DETACHING) { physicsAssertf(pAttachParent, "A detaching attachment has lost its parent"); float fRange = GetBoundRadius() + pAttachParent->GetBoundRadius(); if((GetBoundCentre() - pAttachParent->GetBoundCentre()).Mag2() > fRange*fRange) { extension->ClearParentAttachmentVars(); } } else { Assertf(false, "unsupported attachment type in CPhysical"); } } #if __BANK void CPhysical::DebugProcessAllAttachments(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine) #else void CPhysical::ProcessAllAttachments() #endif { fwAttachmentEntityExtension *extension = GetAttachmentExtension(); if (!extension) { return; } // Need to grab child and sibling here since the ProcessAttachment could delete the extension fwEntity *childAttachment = extension->GetChildAttachment(); fwEntity *siblingAttachment = extension->GetSiblingAttachment(); // Process this attachment and then any children if(extension->GetShouldProcessAttached()) { #if __BANK DebugProcessAttachment(strCodeFile, strCodeFunction, nCodeLine); #else ProcessAttachment(); #endif } if(childAttachment) { #if __BANK ((CPhysical *) childAttachment)->DebugProcessAllAttachments(strCodeFile, strCodeFunction, nCodeLine); #else ((CPhysical *) childAttachment)->ProcessAllAttachments(); #endif } if(siblingAttachment) { #if __BANK ((CPhysical *) siblingAttachment)->DebugProcessAllAttachments(strCodeFile, strCodeFunction, nCodeLine); #else ((CPhysical *) siblingAttachment)->ProcessAllAttachments(); #endif } } void CPhysical::DeleteAllChildren(bool bCheckAttachFlags) { fwAttachmentEntityExtension *attachExt = GetAttachmentExtension(); if (attachExt) { CPhysical* pCurChild = (CPhysical *) attachExt->GetChildAttachment(); while(pCurChild) { fwAttachmentEntityExtension &curChildAttachExt = pCurChild->GetAttachmentExtensionRef(); CPhysical* pNextChild = (CPhysical *) curChildAttachExt.GetSiblingAttachment(); if(curChildAttachExt.GetAttachFlag(ATTACH_FLAG_DELETE_WITH_PARENT) || !bCheckAttachFlags) { CGameWorld::RemoveEntity(pCurChild); } pCurChild = pNextChild; } } } void CPhysical::RemoveDeleteWithParentProgenyFromList(CEntity * list[], u32 count) const { fwAttachmentEntityExtension const *attachExt = GetAttachmentExtension(); if (attachExt) { CPhysical const* pCurChild = (const CPhysical *) attachExt->GetChildAttachment(); while(pCurChild) { fwAttachmentEntityExtension const &curChildAttachExt = pCurChild->GetAttachmentExtensionRef(); if(curChildAttachExt.GetAttachFlag(ATTACH_FLAG_DELETE_WITH_PARENT)) { for(u32 i = 0 ; i < count ; i++) { if(list[i] == static_cast(pCurChild)) { list[i] = NULL; } } pCurChild->RemoveDeleteWithParentProgenyFromList(list,count); } pCurChild = (const CPhysical *) curChildAttachExt.GetSiblingAttachment(); } } } void CPhysical::GeneratePhysExclusionList(const CEntity** ppExclusionListStorage, int& nStorageUsed, int nMaxStorageSize, u32 includeFlags, u32 typeFlags, const CEntity* pOptionalExclude) const { CDynamicEntity::GeneratePhysExclusionList(ppExclusionListStorage, nStorageUsed, nMaxStorageSize,includeFlags,typeFlags, pOptionalExclude); // Iterate through all child attachments CPhysical* pNextChild = (CPhysical *) GetChildAttachment(); while(pNextChild && nStorageUsed < nMaxStorageSize) { if(pNextChild->GetPhysArch() && pNextChild->GetPhysArch()->MatchFlags(includeFlags,typeFlags)) { // Only add the exclude list of the type and include flags match the test ppExclusionListStorage[nStorageUsed++] = pNextChild; } // Let this child generate an exclusion list of all of its children pNextChild->GeneratePhysExclusionList(ppExclusionListStorage,nStorageUsed,nMaxStorageSize,includeFlags,typeFlags,pOptionalExclude); // Move on to the sibling pNextChild = (CPhysical *) pNextChild->GetSiblingAttachment(); } } bool CPhysical::DoRelationshipGroupsMatchForOnlyDamagedByCheck(const CRelationshipGroup* pRelGroupOfInflictor) const { // Assertf(sizeof(RelGroupOfInflictor) == sizeof(m_specificRelGroupIndex), "CPhysical::DoRelationshipGroupsMatchForOnlyDamagedByCheck - need to change type of RelGroupOfInflictor"); // if (is network session) // { // if (m_specificRelGroupIndex == CRelationshipManager::s_playerGroupIndex) // { // if ( ( (RelGroupOfInflictor >= RELGROUP_NETWORKPLAYER_1) && (RelGroupOfInflictor <= RELGROUP_NETWORKPLAYER_16) ) // || ( (RelGroupOfInflictor >= RELGROUP_NETWORKTEAM_1) && (RelGroupOfInflictor <= RELGROUP_NETWORKTEAM_8) ) ) // { // return true; // } // } // } if (m_specificRelGroupHash == pRelGroupOfInflictor->GetName().GetHash()) { return true; } return false; } bool CPhysical::CanPhysicalBeDamaged(const CEntity* pInflictor, u32 nWeaponUsedHash, bool bDoNetworkCloneCheck, bool bDisableDamagingOwner NOTFINAL_ONLY(, u32* uRejectionReason)) const { // can't damage objects controlled by another machine if (IsNetworkClone() && bDoNetworkCloneCheck) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NetworkClone; }) return false; } if(m_nPhysicalFlags.bNotDamagedByAnything ) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByAnything; }) return false; } //@@: range CPHYSICAL_CANPHYSICALBEDAMAGED { #if TAMPERACTIONS_ENABLED && TAMPERACTIONS_INVINCIBLECOP if(GetIsTypePed() && TamperActions::IsInvincibleCop(((CPed*)this))) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_TamperInvincibleCop; }) return false; } #endif //@@: } CPHYSICAL_CANPHYSICALBEDAMAGED // Cache the CWeaponInfo const CItemInfo* pItemInfo = CWeaponInfoManager::GetInfo(nWeaponUsedHash); if(!pItemInfo || !pItemInfo->GetIsClassId(CWeaponInfo::GetStaticClassId())) return false; const CWeaponInfo* pWeaponInfo = static_cast(pItemInfo); Assert(pWeaponInfo); // Ped can't damage the thing they're attached to if(pInflictor && pInflictor->GetIsPhysical() && ((CPhysical*)pInflictor)->GetAttachParent()==this) { if(pWeaponInfo->GetDamageType()!=DAMAGE_TYPE_EXPLOSIVE && pWeaponInfo->GetDamageType()!=DAMAGE_TYPE_FIRE) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_AttachedToInflictor; }) return false; } } if( m_canOnlyBeDamagedByEntity != NULL && pInflictor != m_canOnlyBeDamagedByEntity ) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByOtherEnt; }) return false; } if( pInflictor == m_cantBeDamagedByEntity && m_cantBeDamagedByEntity != NULL && ( nWeaponUsedHash == WEAPONTYPE_RAMMEDBYVEHICLE || nWeaponUsedHash == WEAPONTYPE_RUNOVERBYVEHICLE || nWeaponUsedHash == WEAPONTYPE_ROTORS ) ) { if( pInflictor && pInflictor->GetIsPhysical() ) { Displayf("[CPhysical::CanPhysicalBeDamaged] [Physical: %s %s] is ignoring collision with [Physical: %s %s]", GetModelName(), GetNetworkObject() ? GetNetworkObject()->GetLogName() : "none", static_cast< const CPhysical*>( pInflictor )->GetModelName(), static_cast( pInflictor )->GetNetworkObject() ? static_cast( pInflictor )->GetNetworkObject()->GetLogName() : "none" ); } return false; } // Ignore explosions for player damaged by checks // the ragdoll must be activated in the explosion code so the ped must be damaged. if(m_nPhysicalFlags.bOnlyDamagedByPlayer //&& nDamageType!= DAMAGE_TYPE_EXPLOSIVE // The damaged by check includes grenades rockets etc. that aren't covered in the weeapontype check belo && nWeaponUsedHash!=WEAPONTYPE_DROWNING && nWeaponUsedHash!=WEAPONTYPE_DROWNINGINVEHICLE && nWeaponUsedHash!=WEAPONTYPE_BLEEDING && nWeaponUsedHash!=WEAPONTYPE_FIRE) { if(pInflictor==NULL) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByPlayerNullInflictor; }) return false; } else if(pInflictor->GetIsTypePed()) { if(!((CPed*)pInflictor)->IsPlayer()) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByPlayerNotPlayer; }) return false; } } else if(pInflictor->GetIsTypeVehicle()) { const CVehicle* pVehicle = static_cast(pInflictor); if(pVehicle->GetStatus()!=STATUS_PLAYER) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByPlayerNonPlayerVehicle; }) return false; } } else { // If entity being damaged is a ped attached to a vehicle with no roof, we allow them to ragdoll out if (GetIsTypePed()) { const CPed* pPed = static_cast(this); if (pPed->GetIsInVehicle()) { const CVehicle* pVehicle = pPed->GetMyVehicle(); const s32 iSeatIndex = pVehicle->GetSeatManager()->GetPedsSeatIndex(pPed); if (pVehicle->IsSeatIndexValid(iSeatIndex)) { const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(iSeatIndex); if (pSeatAnimInfo && pSeatAnimInfo->GetRagdollWhenVehicleUpsideDown()) { return true; } } } } NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByPlayer; }) return false; } } // Ignore explosions for relationship damaged by checks - // the ragdoll must be activated in the explosion code so the ped must be damaged if( m_nPhysicalFlags.bOnlyDamagedByRelGroup //&& nDamageType!=DAMAGE_TYPE_EXPLOSIVE // The damaged by check includes grenades rockets etc. that aren't covered in the weeapontype check belo && nWeaponUsedHash!=WEAPONTYPE_DROWNING && (nWeaponUsedHash!=WEAPONTYPE_EXPLOSION || (nWeaponUsedHash == WEAPONTYPE_EXPLOSION && pInflictor && pInflictor->GetIsTypePed())) && nWeaponUsedHash!=WEAPONTYPE_DROWNINGINVEHICLE && nWeaponUsedHash!=WEAPONTYPE_BLEEDING && (nWeaponUsedHash!=WEAPONTYPE_FIRE || (nWeaponUsedHash == WEAPONTYPE_FIRE && pInflictor && pInflictor->GetIsTypePed()))) { Assertf( !m_nPhysicalFlags.bNotDamagedByRelGroup, "CPhysical::CanPhysicalBeDamaged - can't set entity to be not damaged and only damaged by a relationship group at the same time"); if(pInflictor==NULL) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByRelGroupNullInflictor; }) return false; } else if(pInflictor->GetIsTypePed()) { if (!DoRelationshipGroupsMatchForOnlyDamagedByCheck(static_cast(pInflictor)->GetPedIntelligence()->GetRelationshipGroup())) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByRelGroupMismatch; }) return false; } } else if(pInflictor->GetIsTypeVehicle()) { CVehicle* pVehicle = (CVehicle*)pInflictor; if (!pVehicle->GetDriver()) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByRelGroupVehNoDriver; }) return false; } if (!DoRelationshipGroupsMatchForOnlyDamagedByCheck(pVehicle->GetDriver()->GetPedIntelligence()->GetRelationshipGroup())) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByRelGroupVehDriverGroupMismatch; }) return false; } } else { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedByRelGroup; }) return false; } } // Ignore explosions for relationship damaged by checks - // the ragdoll must be activated in the explosion code so the ped must be damaged if( m_nPhysicalFlags.bNotDamagedByRelGroup //&& nDamageType!=DAMAGE_TYPE_EXPLOSIVE // The damaged by check includes grenades rockets etc. that aren't covered in the weeapontype check below && nWeaponUsedHash!=WEAPONTYPE_DROWNING && nWeaponUsedHash!=WEAPONTYPE_EXPLOSION && nWeaponUsedHash!=WEAPONTYPE_DROWNINGINVEHICLE && nWeaponUsedHash!=WEAPONTYPE_BLEEDING && nWeaponUsedHash!=WEAPONTYPE_FIRE) { Assertf( !m_nPhysicalFlags.bOnlyDamagedByRelGroup, "CPhysical::CanPhysicalBeDamaged - can't set entity to be not damaged and only damaged by a relationship group at the same time"); if(pInflictor==NULL) { } else if(pInflictor->GetIsTypePed()) { if(((CPed*)pInflictor)->GetPedIntelligence()->GetRelationshipGroup()->GetName().GetHash() == m_specificRelGroupHash ) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByRelGroup; }) return false; } } else if(pInflictor->GetIsTypeVehicle()) { CVehicle* pVehicle = (CVehicle*)pInflictor; if( pVehicle->GetDriver() && (pVehicle->GetDriver()->GetPedIntelligence()->GetRelationshipGroup()->GetName().GetHash() == m_specificRelGroupHash )) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByRelGroupDriver; }) return false; } } else { } } if(NetworkInterface::IsGameInProgress()) { if(m_nPhysicalFlags.bOnlyDamagedWhenRunningScript && nWeaponUsedHash != WEAPONTYPE_DROWNING && nWeaponUsedHash != WEAPONTYPE_DROWNINGINVEHICLE && nWeaponUsedHash != WEAPONTYPE_BLEEDING) { if(pInflictor != NULL) { CPed* pPed = NULL; if(pInflictor->GetIsTypePed()) pPed = (CPed*)(pInflictor); else if(pInflictor->GetIsTypeVehicle()) { CVehicle* pVehicle = (CVehicle*)(pInflictor); pPed = pVehicle->GetDriver(); } if(pPed && pPed->IsPlayer()) { PhysicalPlayerIndex playerIndex = pPed->GetNetworkObject()->GetPlayerOwner()->GetPhysicalPlayerIndex(); const CScriptEntityExtension* pExtension = GetExtension(); if(pExtension) { scriptHandler* pScriptHandler = pExtension->GetScriptHandler(); if(pScriptHandler) { scriptHandlerNetComponent* pNetComponent = pScriptHandler->GetNetworkComponent(); if(pNetComponent) { if(!pNetComponent->IsPlayerAParticipant(playerIndex)) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedOnScript; }) return false; } } } else { CGameScriptHandlerMgr::CRemoteScriptInfo* pRSI = CTheScripts::GetScriptHandlerMgr().GetRemoteScriptInfo(pExtension->GetScriptInfo()->GetScriptId(), true); if(pRSI && !pRSI->IsPlayerAParticipant(playerIndex)) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_OnlyDamagedOnScriptRSI; }) return false; } } } } } } } // Go through the different weapon types switch(pWeaponInfo->GetDamageType()) { case DAMAGE_TYPE_MELEE: { bool bIgnoreNotDamagedByMeleeProof = false; if(GetIsTypePed()) { bIgnoreNotDamagedByMeleeProof = static_cast(this)->GetPedConfigFlag(CPED_CONFIG_FLAG_AllowMeleeReactionIfMeleeProofIsOn); } //! melee proof still does anim, so carry through and reject later. if ( (m_nPhysicalFlags.bNotDamagedByMelee && !bIgnoreNotDamagedByMeleeProof) ) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByMelee; }) return false; } if ((bDisableDamagingOwner && pInflictor==this)) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_DisableDamagingOwner; }) return false; } } break; case DAMAGE_TYPE_BULLET: if (m_nPhysicalFlags.bNotDamagedByBullets) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByBullets; }) return false; } break; case DAMAGE_TYPE_EXPLOSIVE: if (GetIgnoresExplosions()) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_IgnoresExplosions; }) return false; } if ((bDisableDamagingOwner && pInflictor==this)) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_DisableDamagingOwner; }) return false; } break; case DAMAGE_TYPE_FIRE: if (m_nPhysicalFlags.bNotDamagedByFlames) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByFlames; }) return false; } break; case DAMAGE_TYPE_SMOKE: if (m_nPhysicalFlags.bNotDamagedBySmoke) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedBySmoke; }) return false; } break; default: break; } if(nWeaponUsedHash == WEAPONTYPE_RAMMEDBYVEHICLE || nWeaponUsedHash == WEAPONTYPE_RUNOVERBYVEHICLE || nWeaponUsedHash == WEAPONTYPE_FALL #if 0 // CS || nWeaponUsedHash == WEAPONTYPE_OBJECT #endif // 0 ) { if (m_nPhysicalFlags.bNotDamagedByCollisions) { NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByCollisions; }) return false; } } return true; } float CPhysical::GetDistanceFromCentreOfMassToBaseOfModel(void) const { CBaseModelInfo* pModelInfo=GetBaseModelInfo(); const float z=pModelInfo->GetBoundingBoxMin().z; return (-z); /* const phBound* pBound=GetCurrentPhysicsInst()->GetArchetype()->GetBound(); const float cmZ=pBound->GetCGOffset().z; const float LowestZ=pBound->GetBoundingBoxMin().z; return (cmZ-LowestZ); */ // float LowestZ; // // CColModel& ColMod = GetColModel(); // LowestZ = ColMod.GetBoundBoxMin().z; // return(-LowestZ); } // // This flag keeps track of whether the physical is above water (even though the z coordinate might be low ie in a tunnel) // void CPhysical::UpdatePossiblyTouchesWaterFlag() { if(GetBaseModelInfo())//Make sure we have a valid base model info. { Mat34V matrix; Vec3V boundingBoxLocalMin; Vec3V boundingBoxLocalMax; if(const phInst* instance = GetCurrentPhysicsInst()) { const phBound* bound = instance->GetArchetype()->GetBound(); boundingBoxLocalMin = bound->GetBoundingBoxMin(); boundingBoxLocalMax = bound->GetBoundingBoxMax(); matrix = instance->GetMatrix(); } else { spdAABB boundingBox = GetBaseModelInfo()->GetBoundingBox(); boundingBoxLocalMin = boundingBox.GetMin(); boundingBoxLocalMax = boundingBox.GetMax(); matrix = GetMatrix(); } const Vec3V boundBoxLocalCenter = Average(boundingBoxLocalMin,boundingBoxLocalMax); const Vec3V boundBoxHalfExtents = geomBoxes::ComputeAABBExtentsFromOBB(matrix.GetMat33(),Subtract(boundingBoxLocalMax,boundBoxLocalCenter)); const Vec3V curPos = Transform(matrix,boundBoxLocalCenter); const ScalarV curBottomZ = Subtract(curPos,boundBoxHalfExtents).GetZ(); // Reset flag which shows we have completed filling in certain water sample based information in CBuoyancy::Process(). m_Buoyancy.m_buoyancyFlags.bBuoyancyInfoUpToDate = false; // First check if we are in a river. If we are, set the possibly in water flag and return immediately. Otherwise // continue to the main water block tests. We do this here so that CBuoyancy::Process() will actually get called. if(m_Buoyancy.GetRiverBoundProbeResult()) { Vec3V vRiverHitPos, vRiverHitNormal; m_Buoyancy.GetCachedRiverBoundProbeResult(RC_VECTOR3(vRiverHitPos),RC_VECTOR3(vRiverHitNormal)); #if __BANK if(CBuoyancy::ms_bRiverBoundDebugDraw) { DEBUG_DRAW_ONLY(grcDebugDraw::Sphere(vRiverHitPos, 0.3f, Color_yellow, true, 100)); } #endif // __BANK if(IsLessThanOrEqualAll(curBottomZ,vRiverHitPos.GetZ())) { m_nFlags.bPossiblyTouchesWater = true; m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = true; return; } } // Check where the object is in relation to the main water level. // Add a little extra on to account for the max possible wave height // MN - using CGameWorldWaterHeight::GetWaterHeight() here instead of Water::GetWaterLevel() as it's more accurate const ScalarV vMaxWaveHeight(V_TEN); if (IsGreaterThanOrEqualAll(curBottomZ, rage::Add(ScalarVFromF32(CGameWorldWaterHeight::GetWaterHeight()),vMaxWaveHeight))) { // The object is well above the main water level. m_nFlags.bPossiblyTouchesWater = false; m_Buoyancy.SetStatusHighNDry(); m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = true; return; } // Try to get the actual water level at the position of the object to use in the tests below float curPosWaterLevelZ; if(!Water::GetWaterLevel(RCC_VECTOR3(curPos), &curPosWaterLevelZ, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL, this)) { // There is no water here. m_nFlags.bPossiblyTouchesWater = false; m_Buoyancy.SetStatusHighNDry(); m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = true; return; } // Check if we can do a fast incremental update or if we need to do a full test. if(GetIsAnyFixedFlagSet()) { // Do nothing as our position is not changing. m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = false; return; } else { // Build a value without prior knowledge here. const bool bottomBelowWater = IsLessThanOrEqualAll(curBottomZ, ScalarVFromF32(curPosWaterLevelZ)) != 0; const bool alreadyInWater = (m_nPhysicalFlags.bWasInWater || GetIsInWater()); CPortalTracker* pPortalTracker = GetPortalTracker(); bool bInsideInteriorWithWater = pPortalTracker->GetInteriorInst() ? pPortalTracker->GetInteriorInst()->IsWaterInside() : false; if(!bottomBelowWater && !alreadyInWater && !bInsideInteriorWithWater) { // Looks like we aren't in water m_nFlags.bPossiblyTouchesWater = false; m_Buoyancy.SetStatusHighNDry(); } else { // The object possibly intersects with the water. m_nFlags.bPossiblyTouchesWater = true; } } m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = true; } } void CPhysical::SetIsFixedWaitingForCollision( const bool bFixed ) { // This flag needs to be set before OnFixIfNoCollisioNLoadedAroundPosition const bool bWasFixed = IsProtectedBaseFlagSet(fwEntity::IS_FIXED_UNTIL_COLLISION); CEntity::SetIsFixedWaitingForCollision(bFixed); if( bWasFixed != bFixed ) { OnFixIfNoCollisionLoadedAroundPosition(bFixed); Assertf(!bFixed || GetCollider() == NULL,"Was unable to deactivate object '%s' even though it should be fixed waiting for collision.",GetDebugName()); } } void CPhysical::SetDontLoadCollision(bool bDontLoad) { m_nPhysicalFlags.bDontLoadCollision = bDontLoad; if (AssertVerify(GetPortalTracker())) { if (!bDontLoad) { GetPortalTracker()->SetLoadsCollisions(true); //force loading of interior collisions CPortalTracker::AddToActivatingTrackerList(GetPortalTracker()); // causes interiors to activate & stay active } else { GetPortalTracker()->SetLoadsCollisions(false); //disable loading of interior collisions CPortalTracker::RemoveFromActivatingTrackerList(GetPortalTracker()); } } } void CPhysical::CleanupMissionState() { CDynamicEntity::CleanupMissionState(); SetDontLoadCollision(true); // don't do the following for objects, some of them (eg safe doors) need to leave certain flags set if (!GetIsTypeObject()) { if (!GetIsTypeVehicle() || ((static_cast(this))->m_nVehicleFlags.bUnfreezeWhenCleaningUp) ) { SetFixedPhysics(false); } SetIsVisibleForModule(SETISVISIBLE_MODULE_SCRIPT, true); // Don't enable collision for a ped inside a vehicle, the collision state is handled as part of the vehicle attachment code bool bEnableCollision = true; if (GetIsTypePed() && (static_cast(this))->GetIsInVehicle()) { bEnableCollision = false; } if (bEnableCollision) { EnableCollision(); } if(!m_bDontResetDamageFlagsOnCleanupMissionState) { m_nPhysicalFlags.bIgnoresExplosions = false; m_nPhysicalFlags.bOnlyDamagedByPlayer = false; m_nPhysicalFlags.bOnlyDamagedByRelGroup = false; m_nPhysicalFlags.bNotDamagedByRelGroup = false; m_nPhysicalFlags.bOnlyDamagedWhenRunningScript = false; m_nPhysicalFlags.bNotDamagedByBullets = false; m_nPhysicalFlags.bNotDamagedByFlames = false; m_nPhysicalFlags.bNotDamagedByCollisions = false; m_nPhysicalFlags.bNotDamagedByMelee = false; m_nPhysicalFlags.bNotDamagedByAnything = false; m_nPhysicalFlags.bNotDamagedByAnythingButHasReactions = false; m_nPhysicalFlags.bNotDamagedBySteam = false; m_nPhysicalFlags.bNotDamagedBySmoke = false; } m_specificRelGroupHash = 0; } } bool CPhysical::DoesGroundCauseRelativeMotion(const CPhysical* pGround) { //Ensure the ground is valid. if(!pGround) { return false; } //Ensure the ground is not a ped. if(pGround->GetIsTypePed()) { return false; } //Ensure the bounds are significant. if(pGround->GetBoundRadius() <= 0.25f) { return false; } return true; } void CPhysical::CalculateGroundVelocity(const CPhysical* pGround, Vec3V_In vPos, int nComponent, Vec3V_InOut vVel) { //Ensure the ground causes relative motion. if(!DoesGroundCauseRelativeMotion(pGround)) { vVel = Vec3V(V_ZERO); return; } //Calculate the ground velocity. vVel = VECTOR3_TO_VEC3V(pGround->GetLocalSpeed(VEC3V_TO_VECTOR3(vPos), true, nComponent)); Assertf(IsFiniteAll(vVel), "Ground velocity is invalid."); } void CPhysical::CalculateGroundVelocityIntegrated(const CPhysical* pGround, Vec3V_In vPos, int nComponent, float fTimeStep, Vec3V_InOut vVel) { //Ensure the ground causes relative motion. if(!DoesGroundCauseRelativeMotion(pGround)) { vVel = Vec3V(V_ZERO); return; } //Calculate the ground velocity integrated. vVel = VECTOR3_TO_VEC3V(pGround->GetLocalSpeedIntegrated(fTimeStep, vPos, true, nComponent)); Assertf(IsFiniteAll(vVel), "Integrated ground velocity is invalid."); } void CPhysical::CalculateGroundAngularVelocity(const CPhysical* pGround, int nComponent, Vec3V_Ref vAngVel) const { //Ensure the ground causes relative motion. if(!DoesGroundCauseRelativeMotion(pGround)) { vAngVel = Vec3V(V_ZERO); return; } //Generate the ped's up-vector. Vec3V vUp = GetTransform().GetC(); //Grab the ground angular velocity. Vec3V groundAngularVelocity; if(const phCollider* pGroundCollider = pGround->GetCollider()) { groundAngularVelocity = pGroundCollider->GetAngVelocity(); } else if(const phInst* pGroundInst = pGround->GetCurrentPhysicsInst()) { groundAngularVelocity = pGroundInst->GetExternallyControlledAngVelocity(nComponent); } else { groundAngularVelocity = Vec3V(V_ZERO); } Assertf(IsFiniteAll(groundAngularVelocity), "Ground angular velocity is invalid."); //Project the ground's angular velocity on to the up vector. vAngVel = Dot(vUp, groundAngularVelocity) * vUp; } CPhysical* CPhysical::GetGroundPhysicalForPhysical(const CPhysical& rPhysical) { //Check the type of the physical. if(rPhysical.GetIsTypePed()) { return static_cast(rPhysical).GetGroundPhysical(); } else if(rPhysical.GetIsTypeObject()) { return static_cast(rPhysical).GetGroundPhysical(); } else { return NULL; } } CPhysical* CPhysical::GetRootGroundPhysicalForPhysical(const CPhysical& rPhysical) { //Keep track of the ground hierarchy. static const int sMaxGroundHierarchy = 8; atRangeArray aGroundHierarchy; s32 iGroundHierarchy = 0; //Find the root ground. const CPhysical* pCurrent = &rPhysical; while(iGroundHierarchy < sMaxGroundHierarchy) { //Find the ground. CPhysical* pGround = CPhysical::GetGroundPhysicalForPhysical(*pCurrent); //The break conditions are: // 1) The end of the ground hierarchy has been reached (there is no ground). // 2) The ground has been encountered before (cycle). //Ensure the ground is valid. if(!pGround) { break; } //Ensure the ground has not been encountered before. bool bFound = false; for(s32 i = 0; i < iGroundHierarchy; ++i) { if(aGroundHierarchy[i] == pGround) { bFound = true; break; } } if(bFound) { break; } //Add the ground. aGroundHierarchy[iGroundHierarchy++] = pGround; //Assign the current physical. pCurrent = pGround; } //Check if ground has been found. if(iGroundHierarchy == 0) { //No ground was found. return NULL; } else { //Return the last ground that was found successfully. return aGroundHierarchy[iGroundHierarchy - 1]; } } bool CPhysical::IsEntityAParentAttachment(const fwEntity* entity) const { fwEntity* pParent = GetAttachParentForced(); bool bReturn = false; while(!bReturn && pParent) { bReturn = (pParent == entity); pParent = pParent->GetAttachParentForced(); } return bReturn; } #if USE_PHYSICAL_HISTORY float CPhysicalHistory::HISTORY_FREQUENCY_MIN = 1.0f; float CPhysicalHistory::HISTORY_FREQUENCY_MAX = 5.0f; void CPhysicalHistory::UpdateHistory( Matrix34& mThisMat, CEntity* pEntity, Color32 colour ) { #if __FINAL I shouldn't be active in a final build, i use a lot of memory, someone has checked in USE_PHYSICAL_HISTORY 1 by mistake #endif if( m_iHistorySize == 0 ) { m_amHistory[0] = mThisMat; m_iHistorySize = 1; m_iHistoryStart = 0; m_aColors[0] = Color_white; return; } float fFrequency = HISTORY_FREQUENCY_MIN; CBaseModelInfo* pModelInfo = pEntity->GetBaseModelInfo(); if( pModelInfo ) { Vector3 vBoundMin = pModelInfo->GetBoundingBoxMin(); Vector3 vBoundMax = pModelInfo->GetBoundingBoxMax(); fFrequency = MAX( vBoundMax.y - vBoundMin.y, HISTORY_FREQUENCY_MIN ); fFrequency = MIN( fFrequency, HISTORY_FREQUENCY_MAX ); } if( m_amHistory[m_iHistoryStart].d.Dist2(mThisMat.d) < rage::square(fFrequency) ) { return; } ++m_iHistoryStart; ++m_iHistorySize; if( m_iHistoryStart >= MAX_HISTORY_SIZE ) m_iHistoryStart = 0; if( m_iHistorySize > MAX_HISTORY_SIZE ) m_iHistorySize = MAX_HISTORY_SIZE; m_amHistory[m_iHistoryStart] = mThisMat; m_aColors[m_iHistoryStart] = colour; } void CPhysicalHistory::RenderHistory( CEntity* pEntity ) { Vector3 vLastPos; Color32 lastColour; bool bHasPos = false; for( s32 i = 0; i < m_iHistorySize; i++ ) { s32 iIndex = m_iHistoryStart - i; if( iIndex < 0 ) iIndex += MAX_HISTORY_SIZE; CBaseModelInfo* pModelInfo = pEntity->GetBaseModelInfo(); if( pModelInfo ) { Vector3 vBoundMin = pModelInfo->GetBoundingBoxMin(); Vector3 vBoundMax = pModelInfo->GetBoundingBoxMax(); CDebugScene::DrawBoundingBox(vBoundMin, vBoundMax, m_amHistory[iIndex], m_aColors[iIndex]); } grcDebugDraw::Axis(m_amHistory[iIndex], 1.0f, m_aColors[iIndex]); if( bHasPos ) grcDebugDraw::Line(vLastPos, m_amHistory[iIndex].d, lastColour, m_aColors[iIndex]); bHasPos = true; vLastPos = m_amHistory[iIndex].d; lastColour = m_aColors[iIndex]; } } #endif #if __ASSERT void CPhysical::PrintRegisteredPhysicsCreationBacktrace(CPhysical* pPhysical) { if(pPhysical) { sysStack::PrintRegisteredBacktrace(pPhysical->m_physicsBacktrace); } } void CPhysical::RegisterPhysicsCreationBacktrace(CPhysical* pPhysical) { if(pPhysical) { pPhysical->m_physicsBacktrace = sysStack::RegisterBacktrace(); } } #endif #if __BANK void CPhysical::RenderDebugObjectIDNames() { if(NetworkInterface::IsGameInProgress()) return; const NetworkDebug::NetworkDisplaySettings &displaySettings = NetworkDebug::GetDebugDisplaySettings(); if(!displaySettings.m_displayObjectInfo) return; s32 iIteratorFlags = IteratePeds | IterateVehicles; CEntityIterator entityIterator(iIteratorFlags, NULL); CEntity* pEntity = entityIterator.GetNext(); while(pEntity) { if(pEntity->GetIsPhysical()) { const CPhysical* pPhysical = static_cast(pEntity); Vector3 centre = VEC3_ZERO; float radius = pPhysical->GetBoundRadius(); pPhysical->GetBoundCentre(centre); if (camInterface::IsSphereVisibleInGameViewport(centre, radius)) { Vector3 WorldCoors; if(pPhysical->GetIsTypePed() && static_cast(pPhysical)->GetUsingRagdoll() && static_cast(pPhysical)->GetRagdollInst()) { Matrix34 matComponent = MAT34V_TO_MATRIX34(static_cast(pPhysical)->GetMatrix()); static_cast(pPhysical)->GetRagdollComponentMatrix(matComponent, RAGDOLL_BUTTOCKS); WorldCoors = matComponent.d; } else { WorldCoors = VEC3V_TO_VECTOR3(pPhysical->GetTransform().GetPosition()); } Vector2 screenCoords; float fScale = 1.0f; if(NetworkUtils::GetScreenCoordinatesForOHD(WorldCoors, screenCoords, fScale)) { Color32 colour = CRGBA(128,128,255,255); DLC ( CDrawNetworkDebugOHD_NY, (screenCoords, colour, fScale, pPhysical->GetDebugNameFromObjectID())); } } } pEntity = entityIterator.GetNext(); } } #endif void ReportHealthMismatch(u32 /*_param*/, u32 /*tampered*/) { //LinkDataReporterPlugin_Report(LinkDataReporterCategories::LDRC_HEALTH); return; }