You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
18 KiB
C++
448 lines
18 KiB
C++
/*
|
|
* 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 <AzCore/Component/Entity.h>
|
|
#include <PhysXCharacters/API/CharacterUtils.h>
|
|
#include <AzCore/Component/TransformBus.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzFramework/Physics/CharacterPhysicsDataBus.h>
|
|
#include <AzFramework/Physics/PhysicsScene.h>
|
|
#include <AzFramework/Physics/SystemBus.h>
|
|
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
|
|
#include <PhysXCharacters/Components/RagdollComponent.h>
|
|
|
|
namespace PhysX
|
|
{
|
|
bool RagdollComponent::VersionConverter(AZ::SerializeContext& context,
|
|
AZ::SerializeContext::DataElementNode& classElement)
|
|
{
|
|
// The element "PhysXRagdoll" was changed from a shared pointer to a unique pointer, but a version converter was
|
|
// not added at the time. This means there may be serialized data with either the shared or unique pointer, but
|
|
// both labelled version 1. This converter was added later and needs to deal with either eventuality, producing
|
|
// a valid version 2 in either case.
|
|
if (classElement.GetVersion() <= 1)
|
|
{
|
|
int ragdollElementIndex = classElement.FindElement(AZ_CRC("PhysXRagdoll", 0xe081b8b0));
|
|
|
|
if (ragdollElementIndex >= 0)
|
|
{
|
|
AZ::SerializeContext::DataElementNode& ragdollElement = classElement.GetSubElement(ragdollElementIndex);
|
|
|
|
// If we find a shared pointer, change it to a unique pointer. If we don't, we already have a unique
|
|
// pointer and it's fine to do nothing but bump the version number from 1 to 2.
|
|
AZStd::shared_ptr<Ragdoll> ragdollShared = nullptr;
|
|
bool isShared = ragdollElement.GetData<AZStd::shared_ptr<Ragdoll>>(ragdollShared);
|
|
if (isShared)
|
|
{
|
|
// The shared pointer doesn't actually contain any serialized data - it is a runtime only object and
|
|
// should probably never have been serialized, but removing it may cause issues with slices. So
|
|
// there is no need to extract any data and it can be replaced with an empty unique pointer.
|
|
classElement.RemoveElement(ragdollElementIndex);
|
|
classElement.AddElement<AZStd::unique_ptr<Ragdoll>>(context, "PhysXRagdoll");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (classElement.GetVersion() < 3)
|
|
{
|
|
int ragdollElementIndex = classElement.FindElement(AZ_CRC_CE("PhysXRagdoll"));
|
|
|
|
if (ragdollElementIndex >= 0)
|
|
{
|
|
classElement.RemoveElement(ragdollElementIndex);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RagdollComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
Ragdoll::Reflect(context);
|
|
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (serializeContext)
|
|
{
|
|
serializeContext->Class<RagdollComponent, AZ::Component>()
|
|
->Version(3, &VersionConverter)
|
|
->Field("PositionIterations", &RagdollComponent::m_positionIterations)
|
|
->Field("VelocityIterations", &RagdollComponent::m_velocityIterations)
|
|
->Field("EnableJointProjection", &RagdollComponent::m_enableJointProjection)
|
|
->Field("ProjectionLinearTol", &RagdollComponent::m_jointProjectionLinearTolerance)
|
|
->Field("ProjectionAngularTol", &RagdollComponent::m_jointProjectionAngularToleranceDegrees)
|
|
;
|
|
|
|
AZ::EditContext* editContext = serializeContext->GetEditContext();
|
|
if (editContext)
|
|
{
|
|
editContext->Class<RagdollComponent>(
|
|
"PhysX Ragdoll", "Creates a PhysX ragdoll simulation for an animation actor.")
|
|
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
|
|
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXRagdoll.svg")
|
|
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/PhysXRagdoll.svg")
|
|
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
|
|
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/physx/ragdoll/")
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_positionIterations, "Position Iteration Count",
|
|
"The frequency at which ragdoll collider positions are resolved. Higher values can increase fidelity but decrease "
|
|
"performance. Very high values might introduce instability.")
|
|
->Attribute(AZ::Edit::Attributes::Min, 1)
|
|
->Attribute(AZ::Edit::Attributes::Max, 255)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_velocityIterations, "Velocity Iteration Count",
|
|
"The frequency at which ragdoll collider velocities are resolved. Higher values can increase fidelity but decrease "
|
|
"performance. Very high values might introduce instability.")
|
|
->Attribute(AZ::Edit::Attributes::Min, 1)
|
|
->Attribute(AZ::Edit::Attributes::Max, 255)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_enableJointProjection,
|
|
"Enable Joint Projection", "When active, preserves joint constraints in volatile simulations. "
|
|
"Might not be physically correct in all simulations.")
|
|
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_jointProjectionLinearTolerance,
|
|
"Joint Projection Linear Tolerance",
|
|
"Maximum linear joint error. Projection is applied to linear joint errors above this value.")
|
|
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
|
|
->Attribute(AZ::Edit::Attributes::Step, 1e-3f)
|
|
->Attribute(AZ::Edit::Attributes::Visibility, &RagdollComponent::IsJointProjectionVisible)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_jointProjectionAngularToleranceDegrees,
|
|
"Joint Projection Angular Tolerance",
|
|
"Maximum angular joint error. Projection is applied to angular joint errors above this value.")
|
|
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
|
|
->Attribute(AZ::Edit::Attributes::Step, 0.1f)
|
|
->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, &RagdollComponent::IsJointProjectionVisible)
|
|
;
|
|
}
|
|
}
|
|
|
|
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
}
|
|
}
|
|
|
|
bool RagdollComponent::IsJointProjectionVisible()
|
|
{
|
|
return m_enableJointProjection;
|
|
}
|
|
|
|
// AZ::Component
|
|
void RagdollComponent::Init()
|
|
{
|
|
}
|
|
|
|
Physics::RagdollState GetBindPoseWorld(const Physics::RagdollState& bindPose, const AZ::Transform& worldTransform)
|
|
{
|
|
const AZ::Quaternion worldRotation = worldTransform.GetRotation();
|
|
const AZ::Vector3 worldTranslation = worldTransform.GetTranslation();
|
|
|
|
Physics::RagdollState bindPoseWorld;
|
|
const size_t numNodes = bindPose.size();
|
|
bindPoseWorld.resize(numNodes);
|
|
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
|
{
|
|
bindPoseWorld[nodeIndex].m_orientation = worldRotation * bindPose[nodeIndex].m_orientation;
|
|
bindPoseWorld[nodeIndex].m_position = worldRotation.TransformVector(bindPose[nodeIndex].m_position) + worldTranslation;
|
|
}
|
|
|
|
return bindPoseWorld;
|
|
}
|
|
|
|
void RagdollComponent::Activate()
|
|
{
|
|
AzFramework::CharacterPhysicsDataNotificationBus::Handler::BusConnect(GetEntityId());
|
|
}
|
|
|
|
void RagdollComponent::Deactivate()
|
|
{
|
|
DestroyRagdoll();
|
|
AzFramework::CharacterPhysicsDataNotificationBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
// RagdollPhysicsBus
|
|
void RagdollComponent::EnableSimulation(const Physics::RagdollState& initialState)
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
ragdoll->EnableSimulation(initialState);
|
|
}
|
|
}
|
|
|
|
void RagdollComponent::EnableSimulationQueued(const Physics::RagdollState& initialState)
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
ragdoll->EnableSimulationQueued(initialState);
|
|
}
|
|
}
|
|
|
|
void RagdollComponent::DisableSimulation()
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
ragdoll->DisableSimulation();
|
|
}
|
|
}
|
|
|
|
void RagdollComponent::DisableSimulationQueued()
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
ragdoll->DisableSimulationQueued();
|
|
}
|
|
}
|
|
|
|
Physics::Ragdoll* RagdollComponent::GetRagdoll()
|
|
{
|
|
return GetPhysXRagdoll();
|
|
}
|
|
|
|
void RagdollComponent::GetState(Physics::RagdollState& ragdollState) const
|
|
{
|
|
if (const auto* ragdoll = GetPhysXRagdollConst())
|
|
{
|
|
ragdoll->GetState(ragdollState);
|
|
}
|
|
}
|
|
|
|
void RagdollComponent::SetState(const Physics::RagdollState& ragdollState)
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
ragdoll->SetState(ragdollState);
|
|
}
|
|
}
|
|
|
|
void RagdollComponent::SetStateQueued(const Physics::RagdollState& ragdollState)
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
ragdoll->SetStateQueued(ragdollState);
|
|
}
|
|
}
|
|
|
|
void RagdollComponent::GetNodeState(size_t nodeIndex, Physics::RagdollNodeState& nodeState) const
|
|
{
|
|
if (const auto* ragdoll = GetPhysXRagdollConst())
|
|
{
|
|
ragdoll->GetNodeState(nodeIndex, nodeState);
|
|
}
|
|
}
|
|
|
|
void RagdollComponent::SetNodeState(size_t nodeIndex, const Physics::RagdollNodeState& nodeState)
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
ragdoll->SetNodeState(nodeIndex, nodeState);
|
|
}
|
|
}
|
|
|
|
Physics::RagdollNode* RagdollComponent::GetNode(size_t nodeIndex) const
|
|
{
|
|
if (const auto* ragdoll = GetPhysXRagdollConst())
|
|
{
|
|
return ragdoll->GetNode(nodeIndex);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void RagdollComponent::EnablePhysics()
|
|
{
|
|
// do nothing here, ragdolls are enabled via RagdollPhysicsBus::EnableSimulation
|
|
// don't raise an error though, because the character controller component may also be handling the world body
|
|
// request bus and it would be legitimate to call this function on this entity ID
|
|
}
|
|
|
|
void RagdollComponent::DisablePhysics()
|
|
{
|
|
// do nothing here, ragdolls are disabled via RagdollPhysicsBus::DisableSimulation
|
|
// don't raise an error though, because the character controller component may also be handling the world body
|
|
// request bus and it would be legitimate to call this function on this entity ID
|
|
}
|
|
|
|
bool RagdollComponent::IsPhysicsEnabled() const
|
|
{
|
|
if (const auto* ragdoll = GetPhysXRagdollConst())
|
|
{
|
|
return ragdoll->IsSimulated();
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
AZ::Aabb RagdollComponent::GetAabb() const
|
|
{
|
|
if (const auto* ragdoll = GetPhysXRagdollConst())
|
|
{
|
|
return ragdoll->GetAabb();
|
|
}
|
|
return AZ::Aabb::CreateNull();
|
|
}
|
|
|
|
AzPhysics::SimulatedBody* RagdollComponent::GetSimulatedBody()
|
|
{
|
|
return GetRagdoll();
|
|
}
|
|
|
|
AzPhysics::SimulatedBodyHandle RagdollComponent::GetSimulatedBodyHandle() const
|
|
{
|
|
return m_ragdollHandle;
|
|
}
|
|
|
|
AzPhysics::SceneQueryHit RagdollComponent::RayCast(const AzPhysics::RayCastRequest& request)
|
|
{
|
|
if (auto* ragdoll = GetPhysXRagdoll())
|
|
{
|
|
return ragdoll->RayCast(request);
|
|
}
|
|
return AzPhysics::SceneQueryHit();
|
|
}
|
|
|
|
void RagdollComponent::OnRagdollConfigurationReady(const Physics::RagdollConfiguration& ragdollConfiguration)
|
|
{
|
|
CreateRagdoll(ragdollConfiguration);
|
|
}
|
|
|
|
void RagdollComponent::CreateRagdoll(const Physics::RagdollConfiguration& ragdollConfigurationRef)
|
|
{
|
|
DestroyRagdoll();
|
|
|
|
Physics::RagdollConfiguration ragdollConfiguration(ragdollConfigurationRef);
|
|
|
|
const size_t numNodes = ragdollConfiguration.m_nodes.size();
|
|
|
|
if (numNodes == 0)
|
|
{
|
|
AZ_Error("PhysX Ragdoll Component", false,
|
|
"Ragdoll configuration has 0 nodes, ragdoll will not be created for entity \"%s\".",
|
|
GetEntity()->GetName().c_str());
|
|
return;
|
|
}
|
|
|
|
|
|
ragdollConfiguration.m_parentIndices.resize(numNodes);
|
|
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
|
{
|
|
AZStd::string parentName;
|
|
AZStd::string nodeName = ragdollConfiguration.m_nodes[nodeIndex].m_debugName;
|
|
AzFramework::CharacterPhysicsDataRequestBus::EventResult(parentName, GetEntityId(),
|
|
&AzFramework::CharacterPhysicsDataRequests::GetParentNodeName, nodeName);
|
|
AZ::Outcome<size_t> parentIndex = Utils::Characters::GetNodeIndex(ragdollConfiguration, parentName);
|
|
ragdollConfiguration.m_parentIndices[nodeIndex] = parentIndex ? parentIndex.GetValue() : SIZE_MAX;
|
|
|
|
ragdollConfiguration.m_nodes[nodeIndex].m_entityId = GetEntityId();
|
|
}
|
|
|
|
Physics::RagdollState bindPose;
|
|
AzFramework::CharacterPhysicsDataRequestBus::EventResult(bindPose, GetEntityId(),
|
|
&AzFramework::CharacterPhysicsDataRequests::GetBindPose, ragdollConfiguration);
|
|
|
|
AZ::Transform entityTransform = AZ::Transform::CreateIdentity();
|
|
AZ::TransformBus::EventResult(entityTransform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
|
|
ragdollConfiguration.m_initialState = GetBindPoseWorld(bindPose, entityTransform);
|
|
|
|
m_attachedSceneHandle = AzPhysics::InvalidSceneHandle;
|
|
Physics::DefaultWorldBus::BroadcastResult(m_attachedSceneHandle, &Physics::DefaultWorldRequests::GetDefaultSceneHandle);
|
|
|
|
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
|
|
{
|
|
m_ragdollHandle = sceneInterface->AddSimulatedBody(m_attachedSceneHandle, &ragdollConfiguration);
|
|
}
|
|
auto* ragdoll = GetPhysXRagdoll();
|
|
if (ragdoll == nullptr ||
|
|
m_ragdollHandle == AzPhysics::InvalidSimulatedBodyHandle)
|
|
{
|
|
AZ_Error("PhysX Ragdoll Component", false, "Failed to create ragdoll.");
|
|
return;
|
|
}
|
|
|
|
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
|
{
|
|
if (physx::PxRigidDynamic* pxRigidBody = ragdoll->GetPxRigidDynamic(nodeIndex))
|
|
{
|
|
pxRigidBody->setSolverIterationCounts(m_positionIterations, m_velocityIterations);
|
|
}
|
|
}
|
|
|
|
if (m_enableJointProjection)
|
|
{
|
|
const float linearTolerance = AZ::GetMax(0.0f, m_jointProjectionLinearTolerance);
|
|
const float angularTolerance = AZ::DegToRad(AZ::GetMax(0.0f, m_jointProjectionAngularToleranceDegrees));
|
|
|
|
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
|
{
|
|
if (const AzPhysics::Joint* joint = ragdoll->GetNode(nodeIndex)->GetJoint())
|
|
{
|
|
if (auto* pxJoint = static_cast<physx::PxD6Joint*>(joint->GetNativePointer()))
|
|
{
|
|
pxJoint->setConstraintFlag(physx::PxConstraintFlag::ePROJECTION, true);
|
|
pxJoint->setConstraintFlag(physx::PxConstraintFlag::ePROJECT_TO_ACTOR0, true);
|
|
pxJoint->setProjectionLinearTolerance(linearTolerance);
|
|
pxJoint->setProjectionAngularTolerance(angularTolerance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AzFramework::RagdollPhysicsRequestBus::Handler::BusConnect(GetEntityId());
|
|
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(GetEntityId());
|
|
|
|
AzFramework::RagdollPhysicsNotificationBus::Event(GetEntityId(),
|
|
&AzFramework::RagdollPhysicsNotifications::OnRagdollActivated);
|
|
}
|
|
|
|
void RagdollComponent::DestroyRagdoll()
|
|
{
|
|
if (m_ragdollHandle != AzPhysics::InvalidSimulatedBodyHandle &&
|
|
m_attachedSceneHandle != AzPhysics::InvalidSceneHandle)
|
|
{
|
|
AzFramework::RagdollPhysicsRequestBus::Handler::BusDisconnect();
|
|
AzFramework::RagdollPhysicsNotificationBus::Event(
|
|
GetEntityId(), &AzFramework::RagdollPhysicsNotifications::OnRagdollDeactivated);
|
|
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect();
|
|
|
|
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
|
|
{
|
|
sceneInterface->RemoveSimulatedBody(m_attachedSceneHandle, m_ragdollHandle);
|
|
m_attachedSceneHandle = AzPhysics::InvalidSceneHandle;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ragdoll* RagdollComponent::GetPhysXRagdoll()
|
|
{
|
|
return const_cast<Ragdoll*>(static_cast<const RagdollComponent&>(*this).GetPhysXRagdollConst());
|
|
}
|
|
|
|
const Ragdoll* RagdollComponent::GetPhysXRagdollConst() const
|
|
{
|
|
if (m_ragdollHandle == AzPhysics::InvalidSimulatedBodyHandle ||
|
|
m_attachedSceneHandle == AzPhysics::InvalidSceneHandle)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
|
|
{
|
|
return azdynamic_cast<PhysX::Ragdoll*>(sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_ragdollHandle));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// deprecated Cry functions
|
|
void RagdollComponent::EnterRagdoll()
|
|
{
|
|
AZ_WarningOnce("PhysX Ragdoll", false, "Legacy Cry character function not supported in PhysX.");
|
|
}
|
|
|
|
void RagdollComponent::ExitRagdoll()
|
|
{
|
|
AZ_WarningOnce("PhysX Ragdoll", false, "Legacy Cry character function not supported in PhysX.");
|
|
}
|
|
} // namespace PhysX
|