|
|
|
|
@ -19,6 +19,7 @@
|
|
|
|
|
#include <EMotionFX/Source/ActorInstance.h>
|
|
|
|
|
#include <EMotionFX/Source/DebugDraw.h>
|
|
|
|
|
#include <EMotionFX/Source/EMotionFXManager.h>
|
|
|
|
|
#include <EMotionFX/Source/RagdollInstance.h>
|
|
|
|
|
#include <EMotionFX/Source/SubMesh.h>
|
|
|
|
|
#include <EMotionFX/Source/TransformData.h>
|
|
|
|
|
#include <EMotionFX/Source/Mesh.h>
|
|
|
|
|
@ -138,6 +139,35 @@ namespace AZ::Render
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hit detection colliders
|
|
|
|
|
if (CheckBitsAny(renderFlags, EMotionFX::ActorRenderFlags::HitDetectionColliders))
|
|
|
|
|
{
|
|
|
|
|
RenderColliders(debugDisplay, EMotionFX::PhysicsSetup::HitDetection, instance,
|
|
|
|
|
renderActorSettings.m_hitDetectionColliderColor, renderActorSettings.m_selectedHitDetectionColliderColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cloth colliders
|
|
|
|
|
if (CheckBitsAny(renderFlags, EMotionFX::ActorRenderFlags::ClothColliders))
|
|
|
|
|
{
|
|
|
|
|
RenderColliders(debugDisplay, EMotionFX::PhysicsSetup::Cloth, instance,
|
|
|
|
|
renderActorSettings.m_clothColliderColor, renderActorSettings.m_selectedClothColliderColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simulated object colliders
|
|
|
|
|
if (CheckBitsAny(renderFlags, EMotionFX::ActorRenderFlags::SimulatedObjectColliders))
|
|
|
|
|
{
|
|
|
|
|
RenderColliders(debugDisplay, EMotionFX::PhysicsSetup::SimulatedObjectCollider, instance,
|
|
|
|
|
renderActorSettings.m_simulatedObjectColliderColor, renderActorSettings.m_selectedSimulatedObjectColliderColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ragdoll
|
|
|
|
|
const bool renderRagdollColliders = AZ::RHI::CheckBitsAny(renderFlags, EMotionFX::ActorRenderFlags::RagdollColliders);
|
|
|
|
|
const bool renderRagdollJointLimits = AZ::RHI::CheckBitsAny(renderFlags, EMotionFX::ActorRenderFlags::RagdollJointLimits);
|
|
|
|
|
if (renderRagdollColliders || renderRagdollJointLimits)
|
|
|
|
|
{
|
|
|
|
|
RenderRagdoll(debugDisplay, instance, renderRagdollColliders, renderRagdollJointLimits);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float AtomActorDebugDraw::CalculateScaleMultiplier(EMotionFX::ActorInstance* instance) const
|
|
|
|
|
@ -813,4 +843,270 @@ namespace AZ::Render
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AtomActorDebugDraw::RenderColliders(AzFramework::DebugDisplayRequests* debugDisplay,
|
|
|
|
|
const AzPhysics::ShapeColliderPairList& colliders,
|
|
|
|
|
const EMotionFX::ActorInstance* actorInstance,
|
|
|
|
|
const EMotionFX::Node* node,
|
|
|
|
|
const AZ::Color& colliderColor) const
|
|
|
|
|
{
|
|
|
|
|
if (!debugDisplay)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const size_t nodeIndex = node->GetNodeIndex();
|
|
|
|
|
|
|
|
|
|
for (const auto& collider : colliders)
|
|
|
|
|
{
|
|
|
|
|
#ifndef EMFX_SCALE_DISABLED
|
|
|
|
|
const AZ::Vector3& worldScale = actorInstance->GetTransformData()->GetCurrentPose()->GetModelSpaceTransform(nodeIndex).m_scale;
|
|
|
|
|
#else
|
|
|
|
|
const AZ::Vector3 worldScale = AZ::Vector3::CreateOne();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
const EMotionFX::Transform colliderOffsetTransform(collider.first->m_position, collider.first->m_rotation);
|
|
|
|
|
const EMotionFX::Transform& actorInstanceGlobalTransform = actorInstance->GetWorldSpaceTransform();
|
|
|
|
|
const EMotionFX::Transform& emfxNodeGlobalTransform =
|
|
|
|
|
actorInstance->GetTransformData()->GetCurrentPose()->GetModelSpaceTransform(nodeIndex);
|
|
|
|
|
const EMotionFX::Transform emfxColliderGlobalTransformNoScale =
|
|
|
|
|
colliderOffsetTransform * emfxNodeGlobalTransform * actorInstanceGlobalTransform;
|
|
|
|
|
|
|
|
|
|
const AZ::TypeId colliderType = collider.second->RTTI_GetType();
|
|
|
|
|
if (colliderType == azrtti_typeid<Physics::SphereShapeConfiguration>())
|
|
|
|
|
{
|
|
|
|
|
Physics::SphereShapeConfiguration* sphere = static_cast<Physics::SphereShapeConfiguration*>(collider.second.get());
|
|
|
|
|
|
|
|
|
|
// O3DE Physics scaling rules: The maximum component from the node scale will be multiplied by the radius of the sphere.
|
|
|
|
|
const float radius = sphere->m_radius *
|
|
|
|
|
MCore::Max3<float>(static_cast<float>(worldScale.GetX()), static_cast<float>(worldScale.GetY()),
|
|
|
|
|
static_cast<float>(worldScale.GetZ()));
|
|
|
|
|
|
|
|
|
|
debugDisplay->DepthTestOff();
|
|
|
|
|
debugDisplay->SetColor(colliderColor);
|
|
|
|
|
debugDisplay->DrawWireSphere(emfxColliderGlobalTransformNoScale.m_position, radius);
|
|
|
|
|
}
|
|
|
|
|
else if (colliderType == azrtti_typeid<Physics::CapsuleShapeConfiguration>())
|
|
|
|
|
{
|
|
|
|
|
Physics::CapsuleShapeConfiguration* capsule = static_cast<Physics::CapsuleShapeConfiguration*>(collider.second.get());
|
|
|
|
|
|
|
|
|
|
// O3DE Physics scaling rules: The maximum of the X/Y scale components of the node scale will be multiplied by the radius of
|
|
|
|
|
// the capsule. The Z component of the entity scale will be multiplied by the height of the capsule.
|
|
|
|
|
const float radius =
|
|
|
|
|
capsule->m_radius * MCore::Max<float>(static_cast<float>(worldScale.GetX()), static_cast<float>(worldScale.GetY()));
|
|
|
|
|
const float height = capsule->m_height * static_cast<float>(worldScale.GetZ());
|
|
|
|
|
|
|
|
|
|
debugDisplay->DepthTestOff();
|
|
|
|
|
debugDisplay->SetColor(colliderColor);
|
|
|
|
|
debugDisplay->DrawWireCapsule(
|
|
|
|
|
emfxColliderGlobalTransformNoScale.m_position, emfxColliderGlobalTransformNoScale.ToAZTransform().GetBasisZ(), radius, height);
|
|
|
|
|
}
|
|
|
|
|
else if (colliderType == azrtti_typeid<Physics::BoxShapeConfiguration>())
|
|
|
|
|
{
|
|
|
|
|
Physics::BoxShapeConfiguration* box = static_cast<Physics::BoxShapeConfiguration*>(collider.second.get());
|
|
|
|
|
|
|
|
|
|
// O3DE Physics scaling rules: Each component of the box dimensions will be scaled by the node's world scale.
|
|
|
|
|
AZ::Vector3 dimensions = box->m_dimensions;
|
|
|
|
|
dimensions *= worldScale;
|
|
|
|
|
|
|
|
|
|
debugDisplay->DepthTestOff();
|
|
|
|
|
debugDisplay->SetColor(colliderColor);
|
|
|
|
|
debugDisplay->DrawWireBox(
|
|
|
|
|
emfxColliderGlobalTransformNoScale.m_position, emfxColliderGlobalTransformNoScale.m_position + dimensions);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AtomActorDebugDraw::RenderColliders(AzFramework::DebugDisplayRequests* debugDisplay,
|
|
|
|
|
EMotionFX::PhysicsSetup::ColliderConfigType colliderConfigType,
|
|
|
|
|
EMotionFX::ActorInstance* actorInstance,
|
|
|
|
|
const AZ::Color& defaultColor,
|
|
|
|
|
const AZ::Color& selectedColor) const
|
|
|
|
|
{
|
|
|
|
|
if (colliderConfigType == EMotionFX::PhysicsSetup::Unknown)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EMotionFX::Actor* actor = actorInstance->GetActor();
|
|
|
|
|
const AZStd::shared_ptr<EMotionFX::PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
|
|
|
|
|
const Physics::CharacterColliderConfiguration* colliderConfig = physicsSetup->GetColliderConfigByType(colliderConfigType);
|
|
|
|
|
|
|
|
|
|
if (colliderConfig)
|
|
|
|
|
{
|
|
|
|
|
const AZStd::unordered_set<size_t>* cachedSelectedJointIndices;
|
|
|
|
|
EMotionFX::JointSelectionRequestBus::BroadcastResult(
|
|
|
|
|
cachedSelectedJointIndices, &EMotionFX::JointSelectionRequests::FindSelectedJointIndices, actorInstance);
|
|
|
|
|
|
|
|
|
|
for (const Physics::CharacterColliderNodeConfiguration& nodeConfig : colliderConfig->m_nodes)
|
|
|
|
|
{
|
|
|
|
|
const EMotionFX::Node* joint = actor->GetSkeleton()->FindNodeByName(nodeConfig.m_name.c_str());
|
|
|
|
|
if (joint)
|
|
|
|
|
{
|
|
|
|
|
const bool jointSelected = cachedSelectedJointIndices &&
|
|
|
|
|
(cachedSelectedJointIndices->empty() || cachedSelectedJointIndices->find(joint->GetNodeIndex()) != cachedSelectedJointIndices->end());
|
|
|
|
|
|
|
|
|
|
const AzPhysics::ShapeColliderPairList& colliders = nodeConfig.m_shapes;
|
|
|
|
|
RenderColliders(debugDisplay, colliders, actorInstance, joint, jointSelected ? selectedColor : defaultColor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AtomActorDebugDraw::RenderJointFrame(AzFramework::DebugDisplayRequests* debugDisplay,
|
|
|
|
|
const AzPhysics::JointConfiguration& configuration,
|
|
|
|
|
const EMotionFX::ActorInstance* actorInstance,
|
|
|
|
|
const EMotionFX::Node* node,
|
|
|
|
|
const AZ::Color& color) const
|
|
|
|
|
{
|
|
|
|
|
const EMotionFX::Transform& actorInstanceWorldSpaceTransform = actorInstance->GetWorldSpaceTransform();
|
|
|
|
|
const EMotionFX::Pose* currentPose = actorInstance->GetTransformData()->GetCurrentPose();
|
|
|
|
|
const EMotionFX::Transform childJointLocalSpaceTransform(AZ::Vector3::CreateZero(), configuration.m_childLocalRotation);
|
|
|
|
|
const EMotionFX::Transform childModelSpaceTransform =
|
|
|
|
|
childJointLocalSpaceTransform * currentPose->GetModelSpaceTransform(node->GetNodeIndex());
|
|
|
|
|
const EMotionFX::Transform jointChildWorldSpaceTransformNoScale = (childModelSpaceTransform * actorInstanceWorldSpaceTransform);
|
|
|
|
|
AZ::Vector3 dir = jointChildWorldSpaceTransformNoScale.ToAZTransform().GetBasisX();
|
|
|
|
|
|
|
|
|
|
debugDisplay->SetColor(color);
|
|
|
|
|
debugDisplay->DrawArrow(jointChildWorldSpaceTransformNoScale.m_position, jointChildWorldSpaceTransformNoScale.m_position + dir, 0.1f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AtomActorDebugDraw::JointLimitRenderData::Clear()
|
|
|
|
|
{
|
|
|
|
|
m_vertexBuffer.clear();
|
|
|
|
|
m_indexBuffer.clear();
|
|
|
|
|
m_lineBuffer.clear();
|
|
|
|
|
m_lineValidityBuffer.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AtomActorDebugDraw::RenderJointLimit(AzFramework::DebugDisplayRequests* debugDisplay,
|
|
|
|
|
const AzPhysics::JointConfiguration& configuration,
|
|
|
|
|
const EMotionFX::ActorInstance* actorInstance,
|
|
|
|
|
const EMotionFX::Node* node,
|
|
|
|
|
const EMotionFX::Node* parentNode,
|
|
|
|
|
const AZ::Color& regularColor,
|
|
|
|
|
const AZ::Color& violatedColor)
|
|
|
|
|
{
|
|
|
|
|
const size_t nodeIndex = node->GetNodeIndex();
|
|
|
|
|
const size_t parentNodeIndex = parentNode->GetNodeIndex();
|
|
|
|
|
const EMotionFX::Transform& actorInstanceWorldTransform = actorInstance->GetWorldSpaceTransform();
|
|
|
|
|
const EMotionFX::Pose* currentPose = actorInstance->GetTransformData()->GetCurrentPose();
|
|
|
|
|
const AZ::Quaternion& parentOrientation = currentPose->GetModelSpaceTransform(parentNodeIndex).m_rotation;
|
|
|
|
|
const AZ::Quaternion& childOrientation = currentPose->GetModelSpaceTransform(nodeIndex).m_rotation;
|
|
|
|
|
|
|
|
|
|
m_jointLimitRenderData.Clear();
|
|
|
|
|
if (auto* jointHelpers = AZ::Interface<AzPhysics::JointHelpersInterface>::Get())
|
|
|
|
|
{
|
|
|
|
|
jointHelpers->GenerateJointLimitVisualizationData(
|
|
|
|
|
configuration, parentOrientation, childOrientation, s_scale, s_angularSubdivisions, s_radialSubdivisions,
|
|
|
|
|
m_jointLimitRenderData.m_vertexBuffer, m_jointLimitRenderData.m_indexBuffer,
|
|
|
|
|
m_jointLimitRenderData.m_lineBuffer, m_jointLimitRenderData.m_lineValidityBuffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EMotionFX::Transform jointModelSpaceTransform = currentPose->GetModelSpaceTransform(parentNodeIndex);
|
|
|
|
|
jointModelSpaceTransform.m_position = currentPose->GetModelSpaceTransform(nodeIndex).m_position;
|
|
|
|
|
const EMotionFX::Transform jointGlobalTransformNoScale = jointModelSpaceTransform * actorInstanceWorldTransform;
|
|
|
|
|
|
|
|
|
|
const size_t numLineBufferEntries = m_jointLimitRenderData.m_lineBuffer.size();
|
|
|
|
|
if (m_jointLimitRenderData.m_lineValidityBuffer.size() * 2 != numLineBufferEntries)
|
|
|
|
|
{
|
|
|
|
|
AZ_ErrorOnce("EMotionFX", false, "Unexpected buffer size in joint limit visualization for node %s", node->GetName());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < numLineBufferEntries; i += 2)
|
|
|
|
|
{
|
|
|
|
|
const AZ::Color& lineColor = m_jointLimitRenderData.m_lineValidityBuffer[i / 2] ? regularColor : violatedColor;
|
|
|
|
|
debugDisplay->DepthTestOff();
|
|
|
|
|
debugDisplay->DrawLine(
|
|
|
|
|
jointGlobalTransformNoScale.TransformPoint(m_jointLimitRenderData.m_lineBuffer[i]),
|
|
|
|
|
jointGlobalTransformNoScale.TransformPoint(m_jointLimitRenderData.m_lineBuffer[i + 1]), lineColor.GetAsVector4(), lineColor.GetAsVector4()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AtomActorDebugDraw::RenderRagdoll(AzFramework::DebugDisplayRequests* debugDisplay,
|
|
|
|
|
EMotionFX::ActorInstance* actorInstance,
|
|
|
|
|
bool renderColliders,
|
|
|
|
|
bool renderJointLimits)
|
|
|
|
|
{
|
|
|
|
|
const EMotionFX::Actor* actor = actorInstance->GetActor();
|
|
|
|
|
const EMotionFX::Skeleton* skeleton = actor->GetSkeleton();
|
|
|
|
|
const size_t numNodes = skeleton->GetNumNodes();
|
|
|
|
|
const AZStd::shared_ptr<EMotionFX::PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
|
|
|
|
|
const Physics::RagdollConfiguration& ragdollConfig = physicsSetup->GetRagdollConfig();
|
|
|
|
|
const AZStd::vector<Physics::RagdollNodeConfiguration>& ragdollNodes = ragdollConfig.m_nodes;
|
|
|
|
|
const Physics::CharacterColliderConfiguration& colliderConfig = ragdollConfig.m_colliders;
|
|
|
|
|
const EMotionFX::RagdollInstance* ragdollInstance = actorInstance->GetRagdollInstance();
|
|
|
|
|
|
|
|
|
|
const AZ::Render::RenderActorSettings& settings = EMotionFX::GetRenderActorSettings();
|
|
|
|
|
const AZ::Color& violatedColor = settings.m_violatedJointLimitColor;
|
|
|
|
|
const AZ::Color& defaultColor = settings.m_ragdollColliderColor;
|
|
|
|
|
const AZ::Color& selectedColor = settings.m_selectedRagdollColliderColor;
|
|
|
|
|
|
|
|
|
|
const AZStd::unordered_set<size_t>* cachedSelectedJointIndices;
|
|
|
|
|
EMotionFX::JointSelectionRequestBus::BroadcastResult(
|
|
|
|
|
cachedSelectedJointIndices, &EMotionFX::JointSelectionRequests::FindSelectedJointIndices, actorInstance);
|
|
|
|
|
|
|
|
|
|
for (size_t nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex)
|
|
|
|
|
{
|
|
|
|
|
const EMotionFX::Node* joint = skeleton->GetNode(nodeIndex);
|
|
|
|
|
const size_t jointIndex = joint->GetNodeIndex();
|
|
|
|
|
|
|
|
|
|
AZ::Outcome<size_t> ragdollNodeIndex = AZ::Failure();
|
|
|
|
|
if (ragdollInstance)
|
|
|
|
|
{
|
|
|
|
|
ragdollNodeIndex = ragdollInstance->GetRagdollNodeIndex(jointIndex);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ragdollNodeIndex = ragdollConfig.FindNodeConfigIndexByName(joint->GetNameString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ragdollNodeIndex.IsSuccess())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool jointSelected = cachedSelectedJointIndices &&
|
|
|
|
|
(cachedSelectedJointIndices->empty() || cachedSelectedJointIndices->find(joint->GetNodeIndex()) != cachedSelectedJointIndices->end());
|
|
|
|
|
|
|
|
|
|
AZ::Color finalColor;
|
|
|
|
|
if (jointSelected)
|
|
|
|
|
{
|
|
|
|
|
finalColor = selectedColor;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
finalColor = defaultColor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Physics::RagdollNodeConfiguration& ragdollNode = ragdollNodes[ragdollNodeIndex.GetValue()];
|
|
|
|
|
|
|
|
|
|
if (renderColliders)
|
|
|
|
|
{
|
|
|
|
|
const Physics::CharacterColliderNodeConfiguration* colliderNodeConfig =
|
|
|
|
|
colliderConfig.FindNodeConfigByName(joint->GetNameString());
|
|
|
|
|
if (colliderNodeConfig)
|
|
|
|
|
{
|
|
|
|
|
const AzPhysics::ShapeColliderPairList& colliders = colliderNodeConfig->m_shapes;
|
|
|
|
|
RenderColliders(debugDisplay, colliders, actorInstance, joint, finalColor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (renderJointLimits && jointSelected)
|
|
|
|
|
{
|
|
|
|
|
const AZStd::shared_ptr<AzPhysics::JointConfiguration>& jointLimitConfig = ragdollNode.m_jointConfig;
|
|
|
|
|
if (jointLimitConfig)
|
|
|
|
|
{
|
|
|
|
|
const EMotionFX::Node* ragdollParentNode = physicsSetup->FindRagdollParentNode(joint);
|
|
|
|
|
if (ragdollParentNode)
|
|
|
|
|
{
|
|
|
|
|
RenderJointLimit(debugDisplay, *jointLimitConfig, actorInstance, joint, ragdollParentNode, finalColor, violatedColor);
|
|
|
|
|
RenderJointFrame(debugDisplay, *jointLimitConfig, actorInstance, joint, finalColor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // namespace AZ::Render
|
|
|
|
|
|