/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PhysX { AZ_CLASS_ALLOCATOR_IMPL(PhysXScene, AZ::SystemAllocator, 0); /*static*/ thread_local AZStd::vector PhysXScene::s_rayCastBuffer; /*static*/ thread_local AZStd::vector PhysXScene::s_sweepBuffer; /*static*/ thread_local AZStd::vector PhysXScene::s_overlapBuffer; namespace Internal { physx::PxScene* CreatePxScene(const AzPhysics::SceneConfiguration& config, SceneSimulationFilterCallback* filterCallback, SceneSimulationEventCallback* simEventCallback) { const physx::PxTolerancesScale tolerancesScale = physx::PxTolerancesScale(); physx::PxSceneDesc sceneDesc(tolerancesScale); sceneDesc.gravity = PxMathConvert(config.m_gravity); if (config.m_enableCcd) { sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD; sceneDesc.filterShader = Collision::DefaultFilterShaderCCD; sceneDesc.ccdMaxPasses = config.m_maxCcdPasses; if (config.m_enableCcdResweep) { sceneDesc.flags.clear(physx::PxSceneFlag::eDISABLE_CCD_RESWEEP); } else { sceneDesc.flags.set(physx::PxSceneFlag::eDISABLE_CCD_RESWEEP); } } else { sceneDesc.filterShader = Collision::DefaultFilterShader; } if (config.m_enableActiveActors) { sceneDesc.flags |= physx::PxSceneFlag::eENABLE_ACTIVE_ACTORS; } if (config.m_enablePcm) { sceneDesc.flags |= physx::PxSceneFlag::eENABLE_PCM; } else { sceneDesc.flags &= ~physx::PxSceneFlag::eENABLE_PCM; } if (config.m_kinematicFiltering) { sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP; } if (config.m_kinematicStaticFiltering) { sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP; } sceneDesc.bounceThresholdVelocity = config.m_bounceThresholdVelocity; sceneDesc.filterCallback = filterCallback; sceneDesc.simulationEventCallback = simEventCallback; #ifdef ENABLE_TGS_SOLVER // Use Temporal Gauss-Seidel solver by default sceneDesc.solverType = physx::PxSolverType::eTGS; #endif #ifdef PHYSX_ENABLE_MULTI_THREADING sceneDesc.flags |= physx::PxSceneFlag::eREQUIRE_RW_LOCK; #endif if (auto* physXSystem = GetPhysXSystem()) { sceneDesc.cpuDispatcher = physXSystem->GetPxCpuDispathcher(); if (physx::PxScene * pxScene = physXSystem->GetPxPhysics()->createScene(sceneDesc)) { if (physx::PxPvdSceneClient* pvdClient = pxScene->getScenePvdClient()) { pvdClient->setScenePvdFlag(physx::PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true); pvdClient->setScenePvdFlag(physx::PxPvdSceneFlag::eTRANSMIT_CONTACTS, true); pvdClient->setScenePvdFlag(physx::PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, true); } return pxScene; } } return nullptr; } bool AddShape(AZStd::variant simulatedBody, const AzPhysics::ShapeVariantData& shapeData) { if (auto* shapeColliderPair = AZStd::get_if(&shapeData)) { bool shapeAdded = false; auto shapePtr = AZStd::make_shared(*(shapeColliderPair->first), *(shapeColliderPair->second)); AZStd::visit([shapePtr, &shapeAdded](auto&& body) { if (shapePtr->GetPxShape()) { body->AddShape(shapePtr); shapeAdded = true; } }, simulatedBody); return shapeAdded; } else if (auto* shapeColliderPairList = AZStd::get_if>(&shapeData)) { bool shapeAdded = false; for (const auto& shapeColliderConfigs : *shapeColliderPairList) { auto shapePtr = AZStd::make_shared(*(shapeColliderConfigs.first), *(shapeColliderConfigs.second)); AZStd::visit([shapePtr, &shapeAdded](auto&& body) { if (shapePtr->GetPxShape()) { body->AddShape(shapePtr); shapeAdded = true; } }, simulatedBody); } return shapeAdded; } else if (auto* shape = AZStd::get_if>(&shapeData)) { AZStd::visit([shape](auto&& body) { body->AddShape(*shape); }, simulatedBody); return true; } else if (auto* shapeList = AZStd::get_if>>(&shapeData)) { for (auto shapePtr : *shapeList) { AZStd::visit([shapePtr](auto&& body) { body->AddShape(shapePtr); }, simulatedBody); } return true; } return false; } template AzPhysics::SimulatedBody* CreateSimulatedBody(const ConfigurationType* configuration, AZ::Crc32& crc) { SimulatedBodyType* newBody = aznew SimulatedBodyType(*configuration); if (!AZStd::holds_alternative(configuration->m_colliderAndShapeData)) { [[maybe_unused]] const bool shapeAdded = AddShape(newBody, configuration->m_colliderAndShapeData); AZ_Warning("PhysXScene", shapeAdded, "No Collider or Shape information found when creating Rigid body [%s]", configuration->m_debugName.c_str()); } crc = AZ::Crc32(newBody, sizeof(*newBody)); return newBody; } AzPhysics::SimulatedBody* CreateRigidBody(const AzPhysics::RigidBodyConfiguration* configuration, AZ::Crc32& crc) { RigidBody* newBody = aznew RigidBody(*configuration); if (!AZStd::holds_alternative(configuration->m_colliderAndShapeData)) { [[maybe_unused]] const bool shapeAdded = AddShape(newBody, configuration->m_colliderAndShapeData); AZ_Warning("PhysXScene", shapeAdded, "No Collider or Shape information found when creating Rigid body [%s]", configuration->m_debugName.c_str()); } const AzPhysics::MassComputeFlags& flags = configuration->GetMassComputeFlags(); newBody->UpdateMassProperties(flags, configuration->m_centerOfMassOffset, configuration->m_inertiaTensor, configuration->m_mass); crc = AZ::Crc32(newBody, sizeof(*newBody)); return newBody; } AzPhysics::SimulatedBody* CreateCharacterBody(PhysXScene* scene, const Physics::CharacterConfiguration* characterConfig) { CharacterController* controller = Utils::Characters::CreateCharacterController(scene, *characterConfig); if (controller == nullptr) { AZ_Error("PhysXScene", false, "Failed to create character controller."); return nullptr; } controller->EnablePhysics(*characterConfig); controller->SetBasePosition(characterConfig->m_position); for (auto shape : characterConfig->m_colliders) { controller->AttachShape(shape); } return controller; } AzPhysics::SimulatedBody* CreateRagdollBody(PhysXScene* scene, const Physics::RagdollConfiguration* ragdollConfig) { return Utils::Characters::CreateRagdoll(const_cast(*ragdollConfig), scene->GetSceneHandle()); } template AzPhysics::Joint* CreateJoint(const ConfigurationType* configuration, AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle parentBodyHandle, AzPhysics::SimulatedBodyHandle childBodyHandle, AZ::Crc32& crc) { JointType* newBody = aznew JointType(*configuration, sceneHandle, parentBodyHandle, childBodyHandle); crc = AZ::Crc32(newBody, sizeof(*newBody)); return newBody; } //helper to perform a ray cast AzPhysics::SceneQueryHits RayCast(const AzPhysics::RayCastRequest* raycastRequest, AZStd::vector& raycastBuffer, physx::PxScene* physxScene, const physx::PxQueryFilterData queryData, const AZ::u64 sceneMaxResults) { // if this query need to report multiple hits, we need to prepare a buffer to hold up to the max allowed. // The filter should also use the eTOUCH flag to find all contacts with the ray. // Otherwise the default buffer (1 result) and eBLOCK flag is enough to find the first hit. physx::PxRaycastBuffer castResult; SceneQueryHelpers::PhysXQueryFilterCallback queryFilterCallback; if (raycastRequest->m_reportMultipleHits) { const AZ::u64 maxSize = AZStd::min(raycastRequest->m_maxResults, sceneMaxResults); if (raycastBuffer.size() < maxSize) //todo this needs to be limited by the config setting { raycastBuffer.resize(maxSize); } castResult = physx::PxRaycastBuffer(raycastBuffer.begin(), aznumeric_cast(maxSize)); queryFilterCallback = SceneQueryHelpers::PhysXQueryFilterCallback( raycastRequest->m_collisionGroup, raycastRequest->m_filterCallback, physx::PxQueryHitType::eTOUCH); } else { queryFilterCallback = SceneQueryHelpers::PhysXQueryFilterCallback( raycastRequest->m_collisionGroup, SceneQueryHelpers::GetSceneQueryBlockFilterCallback(raycastRequest->m_filterCallback), physx::PxQueryHitType::eBLOCK); } const physx::PxVec3 orig = PxMathConvert(raycastRequest->m_start); const physx::PxVec3 dir = PxMathConvert(raycastRequest->m_direction.GetNormalized()); const physx::PxHitFlags hitFlags = SceneQueryHelpers::GetPxHitFlags(raycastRequest->m_hitFlags); //Raycast bool status = false; { PHYSX_SCENE_READ_LOCK(physxScene); status = physxScene->raycast(orig, dir, raycastRequest->m_distance, castResult, hitFlags, queryData, &queryFilterCallback); } AzPhysics::SceneQueryHits hits; if (status) { if (castResult.hasBlock) { hits.m_hits.emplace_back(SceneQueryHelpers::GetHitFromPxHit(castResult.block)); } if (raycastRequest->m_reportMultipleHits) { for (auto i = 0u; i < castResult.getNbTouches(); ++i) { const auto& pxHit = castResult.getTouch(i); hits.m_hits.emplace_back(SceneQueryHelpers::GetHitFromPxHit(pxHit)); } } } return hits; } //helper to preform a shape cast AzPhysics::SceneQueryHits ShapeCast(const AzPhysics::ShapeCastRequest* shapecastRequest, AZStd::vector& shapecastBuffer, physx::PxScene* physxScene, const physx::PxQueryFilterData queryData, const AZ::u64 sceneMaxResults) { // if this query need to report multiple hits, we need to prepare a buffer to hold up to the max allowed. // The filter should also use the eTOUCH flag to find all contacts with the shape. // Otherwise the default buffer (1 result) and eBLOCK flag is enough to find the first hit. physx::PxSweepBuffer castResult; SceneQueryHelpers::PhysXQueryFilterCallback queryFilterCallback; if (shapecastRequest->m_reportMultipleHits) { const AZ::u64 maxSize = AZStd::min(shapecastRequest->m_maxResults, sceneMaxResults); if (shapecastBuffer.size() < maxSize) //todo this needs to be limited by the config setting { shapecastBuffer.resize(maxSize); } castResult = physx::PxSweepBuffer(shapecastBuffer.begin(), aznumeric_cast(maxSize)); queryFilterCallback = SceneQueryHelpers::PhysXQueryFilterCallback( shapecastRequest->m_collisionGroup, shapecastRequest->m_filterCallback, physx::PxQueryHitType::eTOUCH); } else { queryFilterCallback = SceneQueryHelpers::PhysXQueryFilterCallback( shapecastRequest->m_collisionGroup, SceneQueryHelpers::GetSceneQueryBlockFilterCallback(shapecastRequest->m_filterCallback), physx::PxQueryHitType::eBLOCK); } physx::PxGeometryHolder pxGeometry; Utils::CreatePxGeometryFromConfig(*(shapecastRequest->m_shapeConfiguration), pxGeometry); AzPhysics::SceneQueryHits results; if (pxGeometry.any().getType() == physx::PxGeometryType::eSPHERE || pxGeometry.any().getType() == physx::PxGeometryType::eBOX || pxGeometry.any().getType() == physx::PxGeometryType::eCAPSULE || pxGeometry.any().getType() == physx::PxGeometryType::eCONVEXMESH) { const physx::PxTransform pose = PxMathConvert(shapecastRequest->m_start); const physx::PxVec3 dir = PxMathConvert(shapecastRequest->m_direction.GetNormalized()); AZ_Warning("PhysXScene", (static_cast(shapecastRequest->m_hitFlags & AzPhysics::SceneQuery::HitFlags::MTD) != 0), "Not having MTD set for shape scene queries may result in incorrect reporting of colliders that are in contact or intersect the initial pose of the sweep."); const physx::PxHitFlags hitFlags = SceneQueryHelpers::GetPxHitFlags(shapecastRequest->m_hitFlags); bool status = false; { PHYSX_SCENE_READ_LOCK(*physxScene); status = physxScene->sweep(pxGeometry.any(), pose, dir, shapecastRequest->m_distance, castResult, hitFlags, queryData, &queryFilterCallback); } if (status) { if (castResult.hasBlock) { results.m_hits.emplace_back(SceneQueryHelpers::GetHitFromPxHit(castResult.block)); } if (shapecastRequest->m_reportMultipleHits) { for (auto i = 0u; i < castResult.getNbTouches(); ++i) { const auto& pxHit = castResult.getTouch(i); results.m_hits.emplace_back(SceneQueryHelpers::GetHitFromPxHit(pxHit)); } } } } else { AZ_Warning("World", false, "Invalid geometry type passed to shape cast. Only sphere, box, capsule or convex mesh is supported"); } return results; } bool OverlapGeneric(physx::PxScene* physxScene, const AzPhysics::OverlapRequest* overlapRequest, physx::PxOverlapCallback& overlapCallback, const physx::PxQueryFilterData& filterData) { // Prepare overlap data const physx::PxTransform pose = PxMathConvert(overlapRequest->m_pose); physx::PxGeometryHolder pxGeometry; Utils::CreatePxGeometryFromConfig(*(overlapRequest->m_shapeConfiguration), pxGeometry); SceneQueryHelpers::PhysXQueryFilterCallback filterCallback( overlapRequest->m_collisionGroup, SceneQueryHelpers::GetFilterCallbackFromOverlap(overlapRequest->m_filterCallback), physx::PxQueryHitType::eTOUCH); bool status = false; { PHYSX_SCENE_READ_LOCK(*physxScene); status = physxScene->overlap(pxGeometry.any(), pose, overlapCallback, filterData, &filterCallback); } return status; } AzPhysics::SceneQueryHits OverlapQuery(const AzPhysics::OverlapRequest* overlapRequest, AZStd::vector& overlapBuffer, physx::PxScene* physxScene, const physx::PxQueryFilterData queryData, const AZ::u64 sceneMaxResults) { const AZ::u64 maxSize = AZStd::min(overlapRequest->m_maxResults, sceneMaxResults); if (overlapBuffer.size() < maxSize) { overlapBuffer.resize(maxSize); } if (overlapRequest->m_unboundedOverlapHitCallback) { SceneQueryHelpers::UnboundedOverlapCallback callback(overlapRequest->m_unboundedOverlapHitCallback, overlapBuffer); const bool status = OverlapGeneric(physxScene, overlapRequest, callback, queryData); if (status) { return callback.m_results; } return {}; } physx::PxOverlapBuffer queryHits(overlapBuffer.begin(), aznumeric_cast(maxSize)); bool status = OverlapGeneric(physxScene, overlapRequest, queryHits, queryData); AzPhysics::SceneQueryHits results; if (status) { // Process results AZ::u32 hitNum = queryHits.getNbAnyHits(); results.m_hits.reserve(hitNum); for (AZ::u32 i = 0; i < hitNum; ++i) { const AzPhysics::SceneQueryHit hit = SceneQueryHelpers::GetHitFromPxOverlapHit(queryHits.getAnyHit(i)); if (hit.IsValid()) { results.m_hits.emplace_back(hit); } } results.m_hits.shrink_to_fit(); } return results; } } PhysXScene::PhysXScene(const AzPhysics::SceneConfiguration& config, const AzPhysics::SceneHandle& sceneHandle) : Scene(config) , m_config(config) , m_sceneHandle(sceneHandle) , m_physicsSystemConfigChanged([this](const AzPhysics::SystemConfiguration* config) { m_raycastBufferSize = config->m_raycastBufferSize; m_shapecastBufferSize = config->m_shapecastBufferSize; m_overlapBufferSize = config->m_overlapBufferSize; }) { //setup the scene query buffer sizes if (auto* physXSystem = GetPhysXSystem()) { if (const AzPhysics::SystemConfiguration* sysConfig = physXSystem->GetConfiguration()) { m_raycastBufferSize = sysConfig->m_raycastBufferSize; m_shapecastBufferSize = sysConfig->m_shapecastBufferSize; m_overlapBufferSize = sysConfig->m_overlapBufferSize; } //register for future changes to the buffer sizes. physXSystem->RegisterSystemConfigurationChangedEvent(m_physicsSystemConfigChanged); } PhysXScene::s_rayCastBuffer = {}; PhysXScene::s_sweepBuffer = {}; PhysXScene::s_overlapBuffer = {}; m_pxScene = Internal::CreatePxScene(m_config, &m_collisionFilterCallback, &m_simulationEventCallback); AZ_Assert(m_pxScene != nullptr, "PhysX::Scene creation failed."); m_pxScene->userData = this; m_gravity = m_config.m_gravity; } PhysXScene::~PhysXScene() { m_physicsSystemConfigChanged.Disconnect(); s_overlapBuffer.swap({}); s_rayCastBuffer.swap({}); s_sweepBuffer.swap({}); for (auto& simulatedBody : m_simulatedBodies) { if (simulatedBody.second != nullptr) { if (simulatedBody.second->m_simulating) { // Disable simulation on body (not signaling OnSimulationBodySimulationDisabled event) DisableSimulationOfBodyInternal(*simulatedBody.second); } m_simulatedBodyRemovedEvent.Signal(m_sceneHandle, simulatedBody.second->m_bodyHandle); delete simulatedBody.second; } } m_simulatedBodies.clear(); ClearDeferedDeletions(); if (m_controllerManager) { m_controllerManager->release(); m_controllerManager = nullptr; } if (m_pxScene) { m_pxScene->release(); m_pxScene = nullptr; } } void PhysXScene::StartSimulation(float deltatime) { AZ_PROFILE_SCOPE(Physics, "PhysXScene::StartSimulation"); if (!IsEnabled()) { return; } { AZ_PROFILE_SCOPE(Physics, "OnSceneSimulationStartEvent::Signaled"); m_sceneSimuationStartEvent.Signal(m_sceneHandle, deltatime); } m_currentDeltaTime = deltatime; PHYSX_SCENE_WRITE_LOCK(m_pxScene); m_pxScene->simulate(deltatime); } void PhysXScene::FinishSimulation() { AZ_PROFILE_SCOPE(Physics, "PhysXScene::FinishSimulation"); if (!IsEnabled()) { return; } { AZ_PROFILE_SCOPE(Physics, "PhysXScene::CheckResults"); // Wait for the simulation to complete. // In the multithreaded environment we need to make sure we don't lock the scene for write here. // This is because contact modification callbacks can be issued from the job threads and cause deadlock // due to the callback code locking the scene. // https://devtalk.nvidia.com/default/topic/1024408/pxcontactmodifycallback-and-pxscene-locking/ m_pxScene->checkResults(true); } bool activeActorsEnabled = false; { AZ_PROFILE_SCOPE(Physics, "PhysXScene::FetchResults"); PHYSX_SCENE_WRITE_LOCK(m_pxScene); activeActorsEnabled = m_pxScene->getFlags() & physx::PxSceneFlag::eENABLE_ACTIVE_ACTORS; // Swap the buffers, invoke callbacks, build the list of active actors. m_pxScene->fetchResults(true); } if (activeActorsEnabled) { AZ_PROFILE_SCOPE(Physics, "PhysXScene::ActiveActors"); PHYSX_SCENE_READ_LOCK(m_pxScene); physx::PxU32 numActiveActors = 0; physx::PxActor** activeActors = m_pxScene->getActiveActors(numActiveActors); AzPhysics::SimulatedBodyHandleList activeBodyHandles; activeBodyHandles.reserve(numActiveActors); for (physx::PxU32 i = 0; i < numActiveActors; ++i) { if (ActorData* actorData = Utils::GetUserData(activeActors[i])) { activeBodyHandles.emplace_back(actorData->GetBodyHandle()); } } m_sceneActiveSimulatedBodies.Signal(m_sceneHandle, activeBodyHandles); } FlushQueuedEvents(); ClearDeferedDeletions(); { AZ_PROFILE_SCOPE(Physics, "OnSceneSimulationFinishedEvent::Signaled"); m_sceneSimuationFinishEvent.Signal(m_sceneHandle, m_currentDeltaTime); } UpdateAzProfilerDataPoints(); } void PhysXScene::FlushQueuedEvents() { //send queued trigger events ProcessTriggerEvents(); //send queued collision events ProcessCollisionEvents(); } void PhysXScene::SetEnabled(bool enable) { m_isEnabled = enable; } bool PhysXScene::IsEnabled() const { return m_isEnabled; } const AzPhysics::SceneConfiguration& PhysXScene::GetConfiguration() const { return m_config; } void PhysXScene::UpdateConfiguration(const AzPhysics::SceneConfiguration& config) { if (m_config != config) { m_config = config; m_configChangeEvent.Signal(m_sceneHandle, m_config); // set gravity verifies this is a new value. SetGravity(m_config.m_gravity); } } AzPhysics::SimulatedBodyHandle PhysXScene::AddSimulatedBody(const AzPhysics::SimulatedBodyConfiguration* simulatedBodyConfig) { AzPhysics::SimulatedBody* newBody = nullptr; AZ::Crc32 newBodyCrc; if (azrtti_istypeof(simulatedBodyConfig)) { newBody = Internal::CreateRigidBody( azdynamic_cast(simulatedBodyConfig), newBodyCrc); } else if (azrtti_istypeof(simulatedBodyConfig)) { newBody = Internal::CreateSimulatedBody( azdynamic_cast(simulatedBodyConfig), newBodyCrc); } else if (azrtti_istypeof(simulatedBodyConfig)) { newBody = Internal::CreateCharacterBody(this, azdynamic_cast(simulatedBodyConfig)); } else if (azrtti_istypeof(simulatedBodyConfig)) { newBody = Internal::CreateRagdollBody(this, azdynamic_cast(simulatedBodyConfig)); } else { AZ_Warning("PhysXScene", false, "Unknown SimulatedBodyConfiguration."); return AzPhysics::InvalidSimulatedBodyHandle; } if (newBody != nullptr) { AzPhysics::SimulatedBodyIndex index; if (m_freeSceneSlots.empty()) { m_simulatedBodies.emplace_back(newBodyCrc, newBody); index = static_cast(m_simulatedBodies.size() - 1); } else { //fill any free slots first before increasing the size of the simulatedBodies vector. index = m_freeSceneSlots.front(); m_freeSceneSlots.pop(); AZ_Assert(index < m_simulatedBodies.size(), "PhysXScene::AddSimulatedBody: Free simulated body index is out of bounds"); AZ_Assert(m_simulatedBodies[index].second == nullptr, "PhysXScene::AddSimulatedBody: Free simulated body index is not free"); m_simulatedBodies[index] = AZStd::make_pair(newBodyCrc, newBody); } const AzPhysics::SimulatedBodyHandle newBodyHandle(newBodyCrc, index); newBody->m_sceneOwner = m_sceneHandle; newBody->m_bodyHandle = newBodyHandle; m_simulatedBodyAddedEvent.Signal(m_sceneHandle, newBodyHandle); // Enable simulation by default (not signaling OnSimulationBodySimulationEnabled event) if (simulatedBodyConfig->m_startSimulationEnabled) { EnableSimulationOfBodyInternal(*newBody); } return newBodyHandle; } return AzPhysics::InvalidSimulatedBodyHandle; } AzPhysics::SimulatedBodyHandleList PhysXScene::AddSimulatedBodies(const AzPhysics::SimulatedBodyConfigurationList& simulatedBodyConfigs) { AzPhysics::SimulatedBodyHandleList newBodyHandles; newBodyHandles.reserve(simulatedBodyConfigs.size()); for (auto* config : simulatedBodyConfigs) { newBodyHandles.emplace_back(AddSimulatedBody(config)); } return newBodyHandles; } AzPhysics::SimulatedBody* PhysXScene::GetSimulatedBodyFromHandle(AzPhysics::SimulatedBodyHandle bodyHandle) { if (bodyHandle == AzPhysics::InvalidSimulatedBodyHandle) { return nullptr; } AzPhysics::SimulatedBodyIndex index = AZStd::get(bodyHandle); if (index < m_simulatedBodies.size() && m_simulatedBodies[index].first == AZStd::get(bodyHandle)) { return m_simulatedBodies[index].second; } return nullptr; } AzPhysics::SimulatedBodyList PhysXScene::GetSimulatedBodiesFromHandle(const AzPhysics::SimulatedBodyHandleList& bodyHandles) { AzPhysics::SimulatedBodyList results; for (auto& handle : bodyHandles) { results.emplace_back(GetSimulatedBodyFromHandle(handle)); } return results; } void PhysXScene::RemoveSimulatedBody(AzPhysics::SimulatedBodyHandle& bodyHandle) { if (bodyHandle == AzPhysics::InvalidSimulatedBodyHandle) { return; } AzPhysics::SimulatedBodyIndex index = AZStd::get(bodyHandle); if (index < m_simulatedBodies.size() && m_simulatedBodies[index].first == AZStd::get(bodyHandle)) { if (m_simulatedBodies[index].second->m_simulating) { // Disable simulation on body (not signaling OnSimulationBodySimulationDisabled event) DisableSimulationOfBodyInternal(*m_simulatedBodies[index].second); } m_simulatedBodyRemovedEvent.Signal(m_sceneHandle, bodyHandle); m_deferredDeletions.push_back(m_simulatedBodies[index].second); m_simulatedBodies[index] = AZStd::make_pair(AZ::Crc32(), nullptr); m_freeSceneSlots.push(index); bodyHandle = AzPhysics::InvalidSimulatedBodyHandle; } } void PhysXScene::RemoveSimulatedBodies(AzPhysics::SimulatedBodyHandleList& bodyHandles) { for (auto& handle: bodyHandles) { RemoveSimulatedBody(handle); } } void PhysXScene::EnableSimulationOfBody(AzPhysics::SimulatedBodyHandle bodyHandle) { if (bodyHandle == AzPhysics::InvalidSimulatedBodyHandle) { return; } if (AzPhysics::SimulatedBody* body = GetSimulatedBodyFromHandle(bodyHandle)) { if (body->m_simulating) { return; } m_simulatedBodySimulationEnabledEvent.Signal(m_sceneHandle, bodyHandle); EnableSimulationOfBodyInternal(*body); } else { AZ_Warning("PhysXScene", false, "Unable to enable Simulated body, failed to find body.") } } void PhysXScene::DisableSimulationOfBody(AzPhysics::SimulatedBodyHandle bodyHandle) { if (bodyHandle == AzPhysics::InvalidSimulatedBodyHandle) { return; } if (AzPhysics::SimulatedBody* body = GetSimulatedBodyFromHandle(bodyHandle)) { if (!body->m_simulating) { return; } m_simulatedBodySimulationDisabledEvent.Signal(m_sceneHandle, bodyHandle); DisableSimulationOfBodyInternal(*body); } else { AZ_Warning("PhysXScene", false, "Unable to disable Simulated body, failed to find body.") } } AzPhysics::JointHandle PhysXScene::AddJoint(const AzPhysics::JointConfiguration* jointConfig, AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody) { AzPhysics::Joint* newJoint = nullptr; AZ::Crc32 newJointCrc; if (azrtti_istypeof(jointConfig)) { newJoint = Internal::CreateJoint( azdynamic_cast(jointConfig), m_sceneHandle, parentBody, childBody, newJointCrc); } else if (azrtti_istypeof(jointConfig)) { newJoint = Internal::CreateJoint( azdynamic_cast(jointConfig), m_sceneHandle, parentBody, childBody, newJointCrc); } else if (azrtti_istypeof(jointConfig)) { newJoint = Internal::CreateJoint( azdynamic_cast(jointConfig), m_sceneHandle, parentBody, childBody, newJointCrc); } else if (azrtti_istypeof(jointConfig)) { newJoint = Internal::CreateJoint( azdynamic_cast(jointConfig), m_sceneHandle, parentBody, childBody, newJointCrc); } else { AZ_Warning("PhysXScene", false, "Unknown JointConfiguration."); return AzPhysics::InvalidJointHandle; } if (newJoint != nullptr) { AzPhysics::JointIndex index = static_cast(m_joints.size()); m_joints.emplace_back(newJointCrc, newJoint); const AzPhysics::JointHandle newJointHandle(newJointCrc, index); newJoint->m_sceneOwner = m_sceneHandle; newJoint->m_jointHandle = newJointHandle; return newJointHandle; } return AzPhysics::InvalidJointHandle; } AzPhysics::Joint* PhysXScene::GetJointFromHandle(AzPhysics::JointHandle jointHandle) { if (jointHandle == AzPhysics::InvalidJointHandle) { return nullptr; } AzPhysics::JointIndex index = AZStd::get(jointHandle); if (index < m_joints.size() && m_joints[index].first == AZStd::get(jointHandle)) { return m_joints[index].second; } return nullptr; } void PhysXScene::RemoveJoint(AzPhysics::JointHandle jointHandle) { if (jointHandle == AzPhysics::InvalidJointHandle) { return; } AzPhysics::JointIndex index = AZStd::get(jointHandle); if (index < m_joints.size() && m_joints[index].first == AZStd::get(jointHandle)) { m_deferredDeletionsJoints.push_back(m_joints[index].second); m_joints[index] = AZStd::make_pair(AZ::Crc32(), nullptr); m_freeJointSlots.push(index); jointHandle = AzPhysics::InvalidJointHandle; } } AzPhysics::SceneQueryHits PhysXScene::QueryScene(const AzPhysics::SceneQueryRequest* request) { if (request == nullptr) { return {}; //return 0 hits } // Query flags. const physx::PxQueryFlags queryFlags = SceneQueryHelpers::GetPxQueryFlags(request->m_queryType); const physx::PxQueryFilterData queryData(queryFlags); if (azrtti_istypeof(request)) { return Internal::RayCast(azdynamic_cast(request), s_rayCastBuffer, m_pxScene, queryData, m_raycastBufferSize); } else if (azrtti_istypeof(request)) { return Internal::ShapeCast(azdynamic_cast(request), s_sweepBuffer, m_pxScene, queryData, m_shapecastBufferSize); } else if (azrtti_istypeof(request)) { return Internal::OverlapQuery(azdynamic_cast(request), s_overlapBuffer, m_pxScene, queryData, m_overlapBufferSize); } else { AZ_Warning("Physx", false, "Unknown Scene Query request type."); } return AzPhysics::SceneQueryHits(); } AzPhysics::SceneQueryHitsList PhysXScene::QuerySceneBatch(const AzPhysics::SceneQueryRequests& requests) { AzPhysics::SceneQueryHitsList results; results.reserve(requests.size()); for (auto& request : requests) { results.emplace_back(QueryScene(request.get())); } return results; } [[nodiscard]] bool PhysXScene::QuerySceneAsync([[maybe_unused]] AzPhysics::SceneQuery::AsyncRequestId requestId, [[maybe_unused]] const AzPhysics::SceneQueryRequest* request, [[maybe_unused]] AzPhysics::SceneQuery::AsyncCallback callback) { AZ_Warning("Physx", false, "Currently unimplemented."); // LYN-2306 return false; } [[nodiscard]] bool PhysXScene::QuerySceneAsyncBatch([[maybe_unused]] AzPhysics::SceneQuery::AsyncRequestId requestId, [[maybe_unused]] const AzPhysics::SceneQueryRequests& requests, [[maybe_unused]] AzPhysics::SceneQuery::AsyncBatchCallback callback) { AZ_Warning("Physx", false, "Currently unimplemented."); // LYN-2306 return false; } void PhysXScene::SuppressCollisionEvents( const AzPhysics::SimulatedBodyHandle& bodyHandleA, const AzPhysics::SimulatedBodyHandle& bodyHandleB) { AzPhysics::SimulatedBody* bodyA = GetSimulatedBodyFromHandle(bodyHandleA); AzPhysics::SimulatedBody* bodyB = GetSimulatedBodyFromHandle(bodyHandleB); if (bodyA != nullptr && bodyB != nullptr) { m_collisionFilterCallback.RegisterSuppressedCollision(bodyA, bodyB); } } void PhysXScene::UnsuppressCollisionEvents( const AzPhysics::SimulatedBodyHandle& bodyHandleA, const AzPhysics::SimulatedBodyHandle& bodyHandleB) { AzPhysics::SimulatedBody* bodyA = GetSimulatedBodyFromHandle(bodyHandleA); AzPhysics::SimulatedBody* bodyB = GetSimulatedBodyFromHandle(bodyHandleB); if (bodyA != nullptr && bodyB != nullptr) { m_collisionFilterCallback.UnregisterSuppressedCollision(bodyA, bodyB); } } void PhysXScene::SetGravity(const AZ::Vector3& gravity) { if (m_pxScene && !m_gravity.IsClose(gravity)) { m_gravity = gravity; { PHYSX_SCENE_WRITE_LOCK(m_pxScene); m_pxScene->setGravity(PxMathConvert(m_gravity)); } m_sceneGravityChangedEvent.Signal(m_sceneHandle, m_gravity); } } AZ::Vector3 PhysXScene::GetGravity() const { return m_gravity; } void PhysXScene::EnableSimulationOfBodyInternal(AzPhysics::SimulatedBody& body) { //character controller is a special actor and only needs the m_simulating flag set, if (!azrtti_istypeof(body) && !azrtti_istypeof(body)) { auto pxActor = static_cast(body.GetNativePointer()); AZ_Assert(pxActor, "Simulated Body doesn't have a valid physx actor"); { PHYSX_SCENE_WRITE_LOCK(m_pxScene); m_pxScene->addActor(*pxActor); } if (azrtti_istypeof(body)) { auto rigidBody = azdynamic_cast(&body); if (rigidBody->ShouldStartAsleep()) { rigidBody->ForceAsleep(); } } } body.m_simulating = true; } void PhysXScene::DisableSimulationOfBodyInternal(AzPhysics::SimulatedBody& body) { //character controller is a special actor and only needs the m_simulating flag set, if (!azrtti_istypeof(body) && !azrtti_istypeof(body)) { auto pxActor = static_cast(body.GetNativePointer()); AZ_Assert(pxActor, "Simulated Body doesn't have a valid physx actor"); { PHYSX_SCENE_WRITE_LOCK(m_pxScene); m_pxScene->removeActor(*pxActor); } } body.m_simulating = false; } physx::PxControllerManager* PhysXScene::GetOrCreateControllerManager() { if (m_controllerManager) { return m_controllerManager; } if (m_pxScene) { m_controllerManager = PxCreateControllerManager(*m_pxScene); } if (m_controllerManager) { m_controllerManager->setOverlapRecoveryModule(true); } else { AZ_Error("PhysX Character Controller System", false, "Unable to create a Controller Manager."); } return m_controllerManager; } void* PhysXScene::GetNativePointer() const { return m_pxScene; } void PhysXScene::ClearDeferedDeletions() { // swap the deletions in case the simulated body // manages more bodies and removes them on destruction (ie. Ragdoll). AZStd::vector deletions; deletions.swap(m_deferredDeletions); for (auto* simulatedBody : deletions) { delete simulatedBody; } AZStd::vector jointDeletions; jointDeletions.swap(m_deferredDeletionsJoints); for (auto* joint : jointDeletions) { delete joint; } } void PhysXScene::ProcessTriggerEvents() { AZ_PROFILE_SCOPE(Physics, "PhysXScene::ProcessTriggerEvents"); AzPhysics::TriggerEventList& triggers = m_simulationEventCallback.GetQueuedTriggerEvents(); if (triggers.empty()) { return; // nothing to signal } m_sceneTriggerEvent.Signal(m_sceneHandle, triggers); for (auto& triggerEvent : triggers) { if (triggerEvent.m_triggerBody != nullptr) { triggerEvent.m_triggerBody->ProcessTriggerEvent(triggerEvent); } if (triggerEvent.m_otherBody != nullptr) { triggerEvent.m_otherBody->ProcessTriggerEvent(triggerEvent); } } //cleanup events for next simulate m_simulationEventCallback.FlushQueuedTriggerEvents(); } void PhysXScene::ProcessCollisionEvents() { AZ_PROFILE_SCOPE(Physics, "PhysXScene::ProcessCollisionEvents"); AzPhysics::CollisionEventList& collisions = m_simulationEventCallback.GetQueuedCollisionEvents(); if (collisions.empty()) { return; //nothing to signal } //send all event to any scene listeners m_sceneCollisionEvent.Signal(m_sceneHandle, collisions); //send events to each body listener for (auto& collision : collisions) { //trigger on body 1 if (collision.m_body1 != nullptr) { collision.m_body1->ProcessCollisionEvent(collision); } //trigger for body 2 if (collision.m_body2 != nullptr) { //swap the data as the event expects the trigger body to be body1. //this is ok to do as this event is no longer used after calling TriggerCollisionEvent AZStd::swap(collision.m_bodyHandle1, collision.m_bodyHandle2); AZStd::swap(collision.m_body1, collision.m_body2); AZStd::swap(collision.m_shape1, collision.m_shape2); collision.m_body1->ProcessCollisionEvent(collision); } } //cleanup events for next simulate m_simulationEventCallback.FlushQueuedCollisionEvents(); } void PhysXScene::UpdateAzProfilerDataPoints() { using physx::PxGeometryType; bool isProfilingActive = false; if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem) { isProfilingActive = profilerSystem->IsActive(); } if (!isProfilingActive) { return; } AZ_PROFILE_SCOPE(Physics, "PhysX::Statistics"); physx::PxSimulationStatistics stats; { PHYSX_SCENE_READ_LOCK(m_pxScene); m_pxScene->getSimulationStatistics(stats); } [[maybe_unused]] const char* RootCategory = "PhysX/%s/%s"; [[maybe_unused]] const char* ShapesSubCategory = "Shapes"; AZ_PROFILE_DATAPOINT(Physics, stats.nbShapes[PxGeometryType::eSPHERE], RootCategory, ShapesSubCategory, "Sphere"); AZ_PROFILE_DATAPOINT(Physics, stats.nbShapes[PxGeometryType::ePLANE], RootCategory, ShapesSubCategory, "Plane"); AZ_PROFILE_DATAPOINT(Physics, stats.nbShapes[PxGeometryType::eCAPSULE], RootCategory, ShapesSubCategory, "Capsule"); AZ_PROFILE_DATAPOINT(Physics, stats.nbShapes[PxGeometryType::eBOX], RootCategory, ShapesSubCategory, "Box"); AZ_PROFILE_DATAPOINT(Physics, stats.nbShapes[PxGeometryType::eCONVEXMESH], RootCategory, ShapesSubCategory, "ConvexMesh"); AZ_PROFILE_DATAPOINT(Physics, stats.nbShapes[PxGeometryType::eTRIANGLEMESH], RootCategory, ShapesSubCategory, "TriangleMesh"); AZ_PROFILE_DATAPOINT(Physics, stats.nbShapes[PxGeometryType::eHEIGHTFIELD], RootCategory, ShapesSubCategory, "Heightfield"); [[maybe_unused]] const char* ObjectsSubCategory = "Objects"; AZ_PROFILE_DATAPOINT(Physics, stats.nbActiveConstraints, RootCategory, ObjectsSubCategory, "ActiveConstraints"); AZ_PROFILE_DATAPOINT(Physics, stats.nbActiveDynamicBodies, RootCategory, ObjectsSubCategory, "ActiveDynamicBodies"); AZ_PROFILE_DATAPOINT(Physics, stats.nbActiveKinematicBodies, RootCategory, ObjectsSubCategory, "ActiveKinematicBodies"); AZ_PROFILE_DATAPOINT(Physics, stats.nbStaticBodies, RootCategory, ObjectsSubCategory, "StaticBodies"); AZ_PROFILE_DATAPOINT(Physics, stats.nbDynamicBodies, RootCategory, ObjectsSubCategory, "DynamicBodies"); AZ_PROFILE_DATAPOINT(Physics, stats.nbKinematicBodies, RootCategory, ObjectsSubCategory, "KinematicBodies"); AZ_PROFILE_DATAPOINT(Physics, stats.nbAggregates, RootCategory, ObjectsSubCategory, "Aggregates"); AZ_PROFILE_DATAPOINT(Physics, stats.nbArticulations, RootCategory, ObjectsSubCategory, "Articulations"); [[maybe_unused]] const char* SolverSubCategory = "Solver"; AZ_PROFILE_DATAPOINT(Physics, stats.nbAxisSolverConstraints, RootCategory, SolverSubCategory, "AxisSolverConstraints"); AZ_PROFILE_DATAPOINT(Physics, stats.compressedContactSize, RootCategory, SolverSubCategory, "CompressedContactSize"); AZ_PROFILE_DATAPOINT(Physics, stats.requiredContactConstraintMemory, RootCategory, SolverSubCategory, "RequiredContactConstraintMemory"); AZ_PROFILE_DATAPOINT(Physics, stats.peakConstraintMemory, RootCategory, SolverSubCategory, "PeakConstraintMemory"); [[maybe_unused]] const char* BroadphaseSubCategory = "Broadphase"; AZ_PROFILE_DATAPOINT(Physics, stats.getNbBroadPhaseAdds(), RootCategory, BroadphaseSubCategory, "BroadPhaseAdds"); AZ_PROFILE_DATAPOINT(Physics, stats.getNbBroadPhaseRemoves(), RootCategory, BroadphaseSubCategory, "BroadPhaseRemoves"); // Compute pair stats for all geometry types #if AZ_PROFILE_DATAPOINT AZ::u32 ccdPairs = 0; AZ::u32 modifiedPairs = 0; AZ::u32 triggerPairs = 0; for (AZ::u32 i = 0; i < PxGeometryType::eGEOMETRY_COUNT; i++) { // stat[i][j] = stat[j][i], hence, discarding the symmetric entries for (AZ::u32 j = i; j < PxGeometryType::eGEOMETRY_COUNT; j++) { const PxGeometryType::Enum firstGeom = static_cast(i); const PxGeometryType::Enum secondGeom = static_cast(j); ccdPairs += stats.getRbPairStats(physx::PxSimulationStatistics::eCCD_PAIRS, firstGeom, secondGeom); modifiedPairs += stats.getRbPairStats(physx::PxSimulationStatistics::eMODIFIED_CONTACT_PAIRS, firstGeom, secondGeom); triggerPairs += stats.getRbPairStats(physx::PxSimulationStatistics::eTRIGGER_PAIRS, firstGeom, secondGeom); } } #endif [[maybe_unused]] const char* CollisionsSubCategory = "Collisions"; AZ_PROFILE_DATAPOINT(Physics, ccdPairs, RootCategory, CollisionsSubCategory, "CCDPairs"); AZ_PROFILE_DATAPOINT(Physics, modifiedPairs, RootCategory, CollisionsSubCategory, "ModifiedPairs"); AZ_PROFILE_DATAPOINT(Physics, triggerPairs, RootCategory, CollisionsSubCategory, "TriggerPairs"); AZ_PROFILE_DATAPOINT(Physics, stats.nbDiscreteContactPairsTotal, RootCategory, CollisionsSubCategory, "DiscreteContactPairsTotal"); AZ_PROFILE_DATAPOINT(Physics, stats.nbDiscreteContactPairsWithCacheHits, RootCategory, CollisionsSubCategory, "DiscreteContactPairsWithCacheHits"); AZ_PROFILE_DATAPOINT(Physics, stats.nbDiscreteContactPairsWithContacts, RootCategory, CollisionsSubCategory, "DiscreteContactPairsWithContacts"); AZ_PROFILE_DATAPOINT(Physics, stats.nbNewPairs, RootCategory, CollisionsSubCategory, "NewPairs"); AZ_PROFILE_DATAPOINT(Physics, stats.nbLostPairs, RootCategory, CollisionsSubCategory, "LostPairs"); AZ_PROFILE_DATAPOINT(Physics, stats.nbNewTouches, RootCategory, CollisionsSubCategory, "NewTouches"); AZ_PROFILE_DATAPOINT(Physics, stats.nbLostTouches, RootCategory, CollisionsSubCategory, "LostTouches"); AZ_PROFILE_DATAPOINT(Physics, stats.nbPartitions, RootCategory, CollisionsSubCategory, "Partitions"); } }