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.
915 lines
39 KiB
C++
915 lines
39 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/Asset/AssetSerializer.h>
|
|
#include <AzCore/Component/Entity.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/Math/Transform.h>
|
|
#include <AzCore/Preprocessor/EnumReflectUtils.h>
|
|
|
|
#include <AzFramework/Physics/Ragdoll.h>
|
|
#include <AzFramework/Physics/RagdollPhysicsBus.h>
|
|
#include <AzFramework/Physics/PhysicsScene.h>
|
|
#include <AzFramework/Visibility/BoundsBus.h>
|
|
|
|
#include <LmbrCentral/Animation/AttachmentComponentBus.h>
|
|
|
|
#include <Integration/Components/ActorComponent.h>
|
|
#include <Integration/Rendering/RenderBackendManager.h>
|
|
|
|
#include <EMotionFX/Source/Transform.h>
|
|
#include <EMotionFX/Source/RagdollInstance.h>
|
|
#include <EMotionFX/Source/DebugDraw.h>
|
|
#include <EMotionFX/Source/AttachmentSkin.h>
|
|
#include <EMotionFX/Source/Node.h>
|
|
#include <EMotionFX/Source/TransformData.h>
|
|
#include <EMotionFX/Source/AttachmentNode.h>
|
|
|
|
#include <MCore/Source/AzCoreConversions.h>
|
|
|
|
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
|
|
|
|
namespace EMotionFX
|
|
{
|
|
namespace Integration
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
class ActorComponentNotificationBehaviorHandler
|
|
: public ActorComponentNotificationBus::Handler, public AZ::BehaviorEBusHandler
|
|
{
|
|
public:
|
|
AZ_EBUS_BEHAVIOR_BINDER(ActorComponentNotificationBehaviorHandler, "{4631E2E1-62CB-451D-A6E3-CC40501879AE}", AZ::SystemAllocator,
|
|
OnActorInstanceCreated, OnActorInstanceDestroyed);
|
|
|
|
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override
|
|
{
|
|
Call(FN_OnActorInstanceCreated, actorInstance);
|
|
}
|
|
|
|
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override
|
|
{
|
|
Call(FN_OnActorInstanceDestroyed, actorInstance);
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::BoundingBoxConfiguration::Set(ActorInstance* actorInstance) const
|
|
{
|
|
actorInstance->SetExpandBoundsBy(m_expandBy * 0.01f); // Normalize percentage for internal use. (1% == 0.01f)
|
|
|
|
if (m_autoUpdateBounds)
|
|
{
|
|
actorInstance->SetupAutoBoundsUpdate(m_updateTimeFrequency, m_boundsType, m_updateItemFrequency);
|
|
}
|
|
else
|
|
{
|
|
actorInstance->SetBoundsUpdateType(m_boundsType);
|
|
actorInstance->SetBoundsUpdateEnabled(false);
|
|
}
|
|
}
|
|
|
|
void ActorComponent::BoundingBoxConfiguration::SetAndUpdate(ActorInstance* actorInstance) const
|
|
{
|
|
Set(actorInstance);
|
|
|
|
const AZ::u32 updateFrequency = actorInstance->GetBoundsUpdateEnabled() ? actorInstance->GetBoundsUpdateItemFrequency() : 1;
|
|
const ActorInstance::EBoundsType boundUpdateType = actorInstance->GetBoundsUpdateType();
|
|
|
|
actorInstance->UpdateBounds(actorInstance->GetLODLevel(), boundUpdateType, updateFrequency);
|
|
}
|
|
|
|
void ActorComponent::BoundingBoxConfiguration::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<BoundingBoxConfiguration>()
|
|
->Version(2, [](AZ::SerializeContext& sc, AZ::SerializeContext::DataElementNode& node)
|
|
{
|
|
if (node.GetVersion() < 2)
|
|
{
|
|
// m_boundsType used to be an enum class with `int' underlying type, is now `u8'
|
|
static const char* m_boundsType_name = "m_boundsType";
|
|
static AZ::Crc32 m_boundsType_nameCrc(m_boundsType_name);
|
|
|
|
int m_boundsType_as_int;
|
|
if (!node.GetChildData(m_boundsType_nameCrc, m_boundsType_as_int))
|
|
{
|
|
return false;
|
|
}
|
|
if (!node.RemoveElementByName(m_boundsType_nameCrc)) return false;
|
|
if (node.AddElementWithData(sc, m_boundsType_name, (AZ::u8)m_boundsType_as_int) == -1) return false;
|
|
}
|
|
return true;
|
|
})
|
|
->Field("m_boundsType", &BoundingBoxConfiguration::m_boundsType)
|
|
->Field("m_autoUpdateBounds", &BoundingBoxConfiguration::m_autoUpdateBounds)
|
|
->Field("m_updateTimeFrequency", &BoundingBoxConfiguration::m_updateTimeFrequency)
|
|
->Field("m_updateItemFrequency", &BoundingBoxConfiguration::m_updateItemFrequency)
|
|
->Field("expandBy", &BoundingBoxConfiguration::m_expandBy)
|
|
;
|
|
}
|
|
}
|
|
|
|
AZ::Crc32 ActorComponent::BoundingBoxConfiguration::GetVisibilityAutoUpdate() const
|
|
{
|
|
return m_boundsType != EMotionFX::ActorInstance::BOUNDS_STATIC_BASED ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
|
|
}
|
|
|
|
AZ::Crc32 ActorComponent::BoundingBoxConfiguration::GetVisibilityAutoUpdateSettings() const
|
|
{
|
|
if (m_boundsType == EMotionFX::ActorInstance::BOUNDS_STATIC_BASED || m_autoUpdateBounds == false)
|
|
{
|
|
return AZ::Edit::PropertyVisibility::Hide;
|
|
}
|
|
|
|
return AZ::Edit::PropertyVisibility::Show;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
AZ_ENUM_DEFINE_REFLECT_UTILITIES(ActorRenderFlags);
|
|
|
|
void ActorComponent::Configuration::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
BoundingBoxConfiguration::Reflect(context);
|
|
|
|
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (serializeContext)
|
|
{
|
|
ActorRenderFlagsReflect(*serializeContext);
|
|
|
|
serializeContext->Class<Configuration>()
|
|
->Version(5)
|
|
->Field("ActorAsset", &Configuration::m_actorAsset)
|
|
->Field("MaterialPerLOD", &Configuration::m_materialPerLOD)
|
|
->Field("AttachmentType", &Configuration::m_attachmentType)
|
|
->Field("AttachmentTarget", &Configuration::m_attachmentTarget)
|
|
->Field("SkinningMethod", &Configuration::m_skinningMethod)
|
|
->Field("LODLevel", &Configuration::m_lodLevel)
|
|
->Field("BoundingBoxConfig", &Configuration::m_bboxConfig)
|
|
->Field("ForceJointsUpdateOOV", &Configuration::m_forceUpdateJointsOOV)
|
|
->Field("RenderFlags", &Configuration::m_renderFlags)
|
|
;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (serializeContext)
|
|
{
|
|
// Register the AZStd::vector<AzFramework::SimpleAssetReference<MaterialAsset>> class using
|
|
// the old AZ_TYPE_INFO_SPECIALIZE TypeID specialization for the SimpleAssetReference<MaterialAsset>
|
|
// Performs a sha1 calculation of the following typeids AzFramework::SimpleAssetReference<MaterialAsset> + AZStd::allocator + AZStd::vector
|
|
AZ::TypeId deprecatedTypeId = AZ::TypeId("{B7B8ECC7-FF89-4A76-A50E-4C6CA2B6E6B4}") + AZ::AzTypeInfo<AZStd::allocator>::Uuid()
|
|
+ AZ::TypeId("{A60E3E61-1FF6-4982-B6B8-9E4350C4C679}");
|
|
serializeContext->ClassDeprecate("AZStd::vector<SimpleAssetReference_MaterialAsset>", deprecatedTypeId,
|
|
[](AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& rootElement)
|
|
{
|
|
AZStd::vector<AZ::SerializeContext::DataElementNode> childNodeElements;
|
|
for (int index = 0; index < rootElement.GetNumSubElements(); ++index)
|
|
{
|
|
childNodeElements.push_back(rootElement.GetSubElement(index));
|
|
}
|
|
|
|
rootElement.Convert<AZStd::vector<AzFramework::SimpleAssetReference<LmbrCentral::MaterialAsset>>>(context);
|
|
for (AZ::SerializeContext::DataElementNode& childNodeElement : childNodeElements)
|
|
{
|
|
rootElement.AddElement(AZStd::move(childNodeElement));
|
|
}
|
|
return true;
|
|
});
|
|
|
|
Configuration::Reflect(context);
|
|
|
|
serializeContext->Class<ActorComponent, AZ::Component>()
|
|
->Version(1)
|
|
->Field("Configuration", &ActorComponent::m_configuration)
|
|
;
|
|
|
|
AZ::EditContext* editContext = serializeContext->GetEditContext();
|
|
if (editContext)
|
|
{
|
|
editContext->Enum<EMotionFX::Integration::Space>("Space", "The transformation space.")
|
|
->Value("Local Space", Space::LocalSpace)
|
|
->Value("Model Space", Space::ModelSpace)
|
|
->Value("World Space", Space::WorldSpace);
|
|
}
|
|
}
|
|
|
|
auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
|
|
if (behaviorContext)
|
|
{
|
|
behaviorContext->EBus<ActorComponentRequestBus>("ActorComponentRequestBus")
|
|
->Event("GetJointIndexByName", &ActorComponentRequestBus::Events::GetJointIndexByName)
|
|
->Event("GetJointTransform", &ActorComponentRequestBus::Events::GetJointTransform)
|
|
->Event("AttachToEntity", &ActorComponentRequestBus::Events::AttachToEntity)
|
|
->Event("DetachFromEntity", &ActorComponentRequestBus::Events::DetachFromEntity)
|
|
->Event("GetRenderCharacter", &ActorComponentRequestBus::Events::GetRenderCharacter)
|
|
->Event("SetRenderCharacter", &ActorComponentRequestBus::Events::SetRenderCharacter)
|
|
->Event("GetRenderActorVisible", &ActorComponentRequestBus::Events::GetRenderActorVisible)
|
|
->VirtualProperty("RenderCharacter", "GetRenderCharacter", "SetRenderCharacter")
|
|
;
|
|
|
|
behaviorContext->Class<ActorComponent>()->RequestBus("ActorComponentRequestBus");
|
|
|
|
behaviorContext->EBus<ActorComponentNotificationBus>("ActorComponentNotificationBus")
|
|
->Handler<ActorComponentNotificationBehaviorHandler>()
|
|
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::List)
|
|
;
|
|
}
|
|
}
|
|
|
|
void ActorComponent::SetActorAsset(AZ::Data::Asset<ActorAsset> actorAsset)
|
|
{
|
|
m_configuration.m_actorAsset = actorAsset;
|
|
|
|
Actor* actor = m_configuration.m_actorAsset->GetActor();
|
|
if (actor)
|
|
{
|
|
CheckActorCreation();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ActorComponent::ActorComponent(const Configuration* configuration)
|
|
: m_sceneFinishSimHandler([this]([[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
|
|
float fixedDeltatime)
|
|
{
|
|
if (m_actorInstance)
|
|
{
|
|
m_actorInstance->PostPhysicsUpdate(fixedDeltatime);
|
|
}
|
|
}, aznumeric_cast<int32_t>(AzPhysics::SceneEvents::PhysicsStartFinishSimulationPriority::Animation))
|
|
{
|
|
if (configuration)
|
|
{
|
|
m_configuration = *configuration;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ActorComponent::~ActorComponent()
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::Activate()
|
|
{
|
|
m_actorInstance.reset();
|
|
|
|
auto& cfg = m_configuration;
|
|
|
|
if (cfg.m_actorAsset.GetId().IsValid())
|
|
{
|
|
AZ::Data::AssetBus::Handler::BusDisconnect();
|
|
AZ::Data::AssetBus::Handler::BusConnect(cfg.m_actorAsset.GetId());
|
|
cfg.m_actorAsset.QueueLoad();
|
|
}
|
|
|
|
AZ::TickBus::Handler::BusConnect();
|
|
|
|
const AZ::EntityId entityId = GetEntityId();
|
|
LmbrCentral::AttachmentComponentNotificationBus::Handler::BusConnect(entityId);
|
|
AzFramework::CharacterPhysicsDataRequestBus::Handler::BusConnect(entityId);
|
|
AzFramework::RagdollPhysicsNotificationBus::Handler::BusConnect(entityId);
|
|
|
|
if (cfg.m_attachmentTarget.IsValid())
|
|
{
|
|
AttachToEntity(cfg.m_attachmentTarget, cfg.m_attachmentType);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::Deactivate()
|
|
{
|
|
AzFramework::RagdollPhysicsNotificationBus::Handler::BusDisconnect();
|
|
AzFramework::CharacterPhysicsDataRequestBus::Handler::BusDisconnect();
|
|
m_sceneFinishSimHandler.Disconnect();
|
|
ActorComponentRequestBus::Handler::BusDisconnect();
|
|
AZ::TickBus::Handler::BusDisconnect();
|
|
ActorComponentNotificationBus::Handler::BusDisconnect();
|
|
LmbrCentral::AttachmentComponentNotificationBus::Handler::BusDisconnect();
|
|
AZ::TransformNotificationBus::MultiHandler::BusDisconnect();
|
|
AZ::Data::AssetBus::Handler::BusDisconnect();
|
|
|
|
DestroyActor();
|
|
m_configuration.m_actorAsset.Release();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::AttachToEntity(AZ::EntityId targetEntityId, [[maybe_unused]] AttachmentType attachmentType)
|
|
{
|
|
if (targetEntityId.IsValid() && targetEntityId != GetEntityId())
|
|
{
|
|
ActorComponentNotificationBus::Handler::BusDisconnect();
|
|
ActorComponentNotificationBus::Handler::BusConnect(targetEntityId);
|
|
|
|
AZ::TransformNotificationBus::MultiHandler::BusConnect(targetEntityId);
|
|
m_attachmentTargetEntityId = targetEntityId;
|
|
|
|
// There's no guarantee that we will receive a on transform change call for the target entity because of the entity activate order.
|
|
// Enforce a transform query on target to get the correct initial transform.
|
|
AZ::Transform transform;
|
|
AZ::TransformBus::EventResult(transform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); // default to using our own TM
|
|
AZ::TransformBus::EventResult(transform, targetEntityId, &AZ::TransformBus::Events::GetWorldTM); // attempt to get target's TM
|
|
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTM, transform); // set our TM
|
|
}
|
|
else
|
|
{
|
|
DetachFromEntity();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::DetachFromEntity()
|
|
{
|
|
if (m_attachmentTargetActor)
|
|
{
|
|
m_attachmentTargetActor->RemoveAttachment(m_actorInstance.get());
|
|
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetParent, AZ::EntityId());
|
|
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
|
|
|
|
AZ::TransformNotificationBus::MultiHandler::BusDisconnect(m_attachmentTargetEntityId);
|
|
m_attachmentTargetEntityId.SetInvalid();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool ActorComponent::GetRenderCharacter() const
|
|
{
|
|
return AZ::RHI::CheckBitsAny(m_configuration.m_renderFlags, ActorRenderFlags::Solid);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::SetRenderCharacter(bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
m_configuration.m_renderFlags |= ActorRenderFlags::Solid;
|
|
}
|
|
else
|
|
{
|
|
m_configuration.m_renderFlags &= ~ActorRenderFlags::Solid;
|
|
}
|
|
|
|
if (m_renderActorInstance)
|
|
{
|
|
m_renderActorInstance->SetIsVisible(enable);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool ActorComponent::GetRenderActorVisible() const
|
|
{
|
|
if (m_renderActorInstance)
|
|
{
|
|
return m_renderActorInstance->IsVisible();
|
|
}
|
|
return false;
|
|
}
|
|
SkinningMethod ActorComponent::GetSkinningMethod() const
|
|
{
|
|
return m_configuration.m_skinningMethod;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
|
|
{
|
|
m_configuration.m_actorAsset = asset;
|
|
AZ_Assert(m_configuration.m_actorAsset.IsReady() && m_configuration.m_actorAsset->GetActor(), "Actor asset should be loaded and actor valid.");
|
|
|
|
CheckActorCreation();
|
|
}
|
|
|
|
void ActorComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
|
|
{
|
|
OnAssetReady(asset);
|
|
}
|
|
|
|
bool ActorComponent::IsPhysicsSceneSimulationFinishEventConnected() const
|
|
{
|
|
return m_sceneFinishSimHandler.IsConnected();
|
|
}
|
|
|
|
void ActorComponent::SetRenderFlag(ActorRenderFlags renderFlags)
|
|
{
|
|
m_configuration.m_renderFlags = renderFlags;
|
|
}
|
|
|
|
void ActorComponent::CheckActorCreation()
|
|
{
|
|
// Create actor instance.
|
|
auto* actorAsset = m_configuration.m_actorAsset.GetAs<ActorAsset>();
|
|
AZ_Error("EMotionFX", actorAsset, "Actor asset is not valid.");
|
|
if (!actorAsset)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DestroyActor();
|
|
|
|
m_actorInstance = actorAsset->CreateInstance(GetEntity());
|
|
if (!m_actorInstance)
|
|
{
|
|
AZ_Error("EMotionFX", actorAsset, "Failed to create actor instance.");
|
|
return;
|
|
}
|
|
|
|
ActorComponentRequestBus::Handler::BusConnect(GetEntityId());
|
|
|
|
ActorComponentNotificationBus::Event(
|
|
GetEntityId(),
|
|
&ActorComponentNotificationBus::Events::OnActorInstanceCreated,
|
|
m_actorInstance.get());
|
|
|
|
m_actorInstance->SetLODLevel(m_configuration.m_lodLevel);
|
|
|
|
// Setup initial transform and listen for transform changes.
|
|
AZ::Transform transform = AZ::Transform::CreateIdentity();
|
|
AZ::TransformBus::EventResult(transform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
|
|
OnTransformChanged(transform, transform);
|
|
AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId());
|
|
|
|
m_actorInstance->UpdateWorldTransform();
|
|
// Set bounds update mode and compute bbox first time
|
|
m_configuration.m_bboxConfig.SetAndUpdate(m_actorInstance.get());
|
|
m_actorInstance->UpdateBounds(0, ActorInstance::EBoundsType::BOUNDS_STATIC_BASED);
|
|
|
|
// Creating the render actor AFTER both actor asset and mesh asset loaded.
|
|
RenderBackend* renderBackend = AZ::Interface<RenderBackendManager>::Get()->GetRenderBackend();
|
|
if (renderBackend)
|
|
{
|
|
actorAsset->InitRenderActor();
|
|
|
|
// If there is already a RenderActorInstance, destroy it before creating the new one so there are not two instances potentially handling events for the same entityId
|
|
m_renderActorInstance.reset(nullptr);
|
|
// Create the new RenderActorInstance
|
|
m_renderActorInstance.reset(renderBackend->CreateActorInstance(GetEntityId(),
|
|
m_actorInstance,
|
|
m_configuration.m_actorAsset,
|
|
m_configuration.m_materialPerLOD,
|
|
m_configuration.m_skinningMethod,
|
|
transform));
|
|
|
|
if (m_renderActorInstance)
|
|
{
|
|
m_renderActorInstance->SetIsVisible(AZ::RHI::CheckBitsAny(m_configuration.m_renderFlags, ActorRenderFlags::Solid));
|
|
}
|
|
}
|
|
|
|
// Reattach all attachments
|
|
for (AZ::EntityId& attachment : m_attachments)
|
|
{
|
|
LmbrCentral::AttachmentComponentRequestBus::Event(attachment, &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
|
|
}
|
|
|
|
const AZ::EntityId entityId = GetEntityId();
|
|
LmbrCentral::AttachmentComponentRequestBus::Event(entityId, &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
|
|
|
|
CheckAttachToEntity();
|
|
|
|
Physics::RagdollConfiguration ragdollConfiguration;
|
|
[[maybe_unused]] bool ragdollConfigValid = GetRagdollConfiguration(ragdollConfiguration);
|
|
AZ_Assert(ragdollConfigValid, "Ragdoll Configuration is not valid");
|
|
AzFramework::CharacterPhysicsDataNotificationBus::Event(entityId, &AzFramework::CharacterPhysicsDataNotifications::OnRagdollConfigurationReady, ragdollConfiguration);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::CheckAttachToEntity()
|
|
{
|
|
// Attach to the target actor if we're both ready.
|
|
// Note that m_attachmentTargetActor will always be null if we're not configured to attach to anything.
|
|
if (m_actorInstance && m_attachmentTargetActor)
|
|
{
|
|
DetachFromEntity();
|
|
|
|
// Make sure we don't generate some circular loop by attaching to each other.
|
|
if (!m_attachmentTargetActor.get()->CheckIfCanHandleAttachment(m_actorInstance.get()))
|
|
{
|
|
AZ_Error("EMotionFX", false, "You cannot attach to yourself or create circular dependencies!\n");
|
|
return;
|
|
}
|
|
|
|
// Create the attachment.
|
|
AZ_Assert(m_configuration.m_attachmentType == AttachmentType::SkinAttachment, "Expected a skin attachment.");
|
|
Attachment* attachment = AttachmentSkin::Create(m_attachmentTargetActor.get(), m_actorInstance.get());
|
|
m_actorInstance->SetLocalSpaceTransform(Transform::CreateIdentity());
|
|
m_attachmentTargetActor->AddAttachment(attachment);
|
|
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetParent, m_attachmentTargetActor->GetEntityId());
|
|
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::DestroyActor()
|
|
{
|
|
m_renderActorInstance.reset();
|
|
|
|
if (m_actorInstance)
|
|
{
|
|
DetachFromEntity();
|
|
|
|
m_attachmentTargetActor = nullptr;
|
|
|
|
ActorComponentNotificationBus::Event(
|
|
GetEntityId(),
|
|
&ActorComponentNotificationBus::Events::OnActorInstanceDestroyed,
|
|
m_actorInstance.get());
|
|
|
|
m_actorInstance.reset();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world)
|
|
{
|
|
AZ_UNUSED(local);
|
|
|
|
const AZ::EntityId* busIdPtr = AZ::TransformNotificationBus::GetCurrentBusId();
|
|
if (!busIdPtr || *busIdPtr == GetEntityId()) // Our own entity has moved.
|
|
{
|
|
// If we're not attached to another actor, keep the EMFX root in sync with any external changes to the entity's transform.
|
|
if (m_actorInstance)
|
|
{
|
|
const Transform localTransform = m_actorInstance->GetParentWorldSpaceTransform().Inversed() * Transform(world);
|
|
m_actorInstance->SetLocalSpacePosition(localTransform.m_position);
|
|
m_actorInstance->SetLocalSpaceRotation(localTransform.m_rotation);
|
|
|
|
// Disable updating the scale to prevent feedback from adding up.
|
|
// We need to find a better way to handle this or to prevent this feedback loop.
|
|
EMFX_SCALECODE
|
|
(
|
|
m_actorInstance->SetLocalSpaceScale(localTransform.m_scale);
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Animation);
|
|
|
|
if (!m_actorInstance || !m_actorInstance->GetIsEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_renderActorInstance)
|
|
{
|
|
m_renderActorInstance->OnTick(deltaTime);
|
|
m_renderActorInstance->UpdateBounds();
|
|
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
|
|
const bool renderActorSolid = AZ::RHI::CheckBitsAny(m_configuration.m_renderFlags, ActorRenderFlags::Solid);
|
|
|
|
// Optimization: Set the actor instance invisible when character is out of camera view. This will stop the joint transforms update, except the root joint.
|
|
// Calling it after the bounds on the render actor updated.
|
|
if (!m_configuration.m_forceUpdateJointsOOV)
|
|
{
|
|
const bool isInCameraFrustum = m_renderActorInstance->IsInCameraFrustum();
|
|
m_actorInstance->SetIsVisible(isInCameraFrustum && renderActorSolid);
|
|
}
|
|
|
|
m_renderActorInstance->SetIsVisible(renderActorSolid);
|
|
m_renderActorInstance->DebugDraw(m_configuration.m_renderFlags);
|
|
}
|
|
}
|
|
|
|
int ActorComponent::GetTickOrder()
|
|
{
|
|
return AZ::TICK_PRE_RENDER;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void ActorComponent::OnActorInstanceCreated(ActorInstance* actorInstance)
|
|
{
|
|
auto it = AZStd::find(m_attachments.begin(), m_attachments.end(), actorInstance->GetEntityId());
|
|
if (it != m_attachments.end())
|
|
{
|
|
if (m_actorInstance)
|
|
{
|
|
LmbrCentral::AttachmentComponentRequestBus::Event(actorInstance->GetEntityId(), &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_attachmentTargetActor.reset(actorInstance);
|
|
|
|
CheckAttachToEntity();
|
|
}
|
|
}
|
|
|
|
void ActorComponent::OnActorInstanceDestroyed([[maybe_unused]] ActorInstance* actorInstance)
|
|
{
|
|
DetachFromEntity();
|
|
|
|
m_attachmentTargetActor = nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool ActorComponent::GetRagdollConfiguration(Physics::RagdollConfiguration& ragdollConfiguration) const
|
|
{
|
|
if (!m_actorInstance)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actorInstance->GetActor()->GetPhysicsSetup();
|
|
ragdollConfiguration = physicsSetup->GetRagdollConfig();
|
|
return true;
|
|
}
|
|
|
|
AZStd::string ActorComponent::GetParentNodeName(const AZStd::string& childName) const
|
|
{
|
|
if (!m_actorInstance)
|
|
{
|
|
return AZStd::string();
|
|
}
|
|
|
|
const Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton();
|
|
Node* childNode = skeleton->FindNodeByName(childName);
|
|
if (childNode)
|
|
{
|
|
const Node* parentNode = childNode->GetParentNode();
|
|
if (parentNode)
|
|
{
|
|
return parentNode->GetNameString();
|
|
}
|
|
}
|
|
|
|
return AZStd::string();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Physics::RagdollState ActorComponent::GetBindPose(const Physics::RagdollConfiguration& config) const
|
|
{
|
|
Physics::RagdollState physicsPose;
|
|
|
|
if (!m_actorInstance)
|
|
{
|
|
return physicsPose;
|
|
}
|
|
|
|
const Actor* actor = m_actorInstance->GetActor();
|
|
const Skeleton* skeleton = actor->GetSkeleton();
|
|
const Pose* emfxPose = actor->GetBindPose();
|
|
|
|
size_t numNodes = config.m_nodes.size();
|
|
physicsPose.resize(numNodes);
|
|
|
|
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
|
{
|
|
const char* nodeName = config.m_nodes[nodeIndex].m_debugName.data();
|
|
Node* emfxNode = skeleton->FindNodeByName(nodeName);
|
|
AZ_Error("EMotionFX", emfxNode, "Could not find bind pose for node %s", nodeName);
|
|
if (emfxNode)
|
|
{
|
|
const Transform& nodeTransform = emfxPose->GetModelSpaceTransform(emfxNode->GetNodeIndex());
|
|
physicsPose[nodeIndex].m_position = nodeTransform.m_position;
|
|
physicsPose[nodeIndex].m_orientation = nodeTransform.m_rotation;
|
|
}
|
|
}
|
|
|
|
return physicsPose;
|
|
}
|
|
|
|
void ActorComponent::OnRagdollActivated()
|
|
{
|
|
Physics::Ragdoll* ragdoll;
|
|
AzFramework::RagdollPhysicsRequestBus::EventResult(ragdoll, m_entity->GetId(), &AzFramework::RagdollPhysicsRequestBus::Events::GetRagdoll);
|
|
if (ragdoll && m_actorInstance)
|
|
{
|
|
m_actorInstance->SetRagdoll(ragdoll);
|
|
|
|
RagdollInstance* ragdollInstance = m_actorInstance->GetRagdollInstance();
|
|
AZ_Assert(ragdollInstance, "As the ragdoll passed in ActorInstance::SetRagdoll() is valid, a valid ragdoll instance is expected to exist.");
|
|
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
|
|
{
|
|
sceneInterface->RegisterSceneSimulationFinishHandler(ragdollInstance->GetRagdollSceneHandle(), m_sceneFinishSimHandler);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ActorComponent::OnRagdollDeactivated()
|
|
{
|
|
if (m_actorInstance)
|
|
{
|
|
m_sceneFinishSimHandler.Disconnect();
|
|
m_actorInstance->SetRagdoll(nullptr);
|
|
}
|
|
}
|
|
|
|
size_t ActorComponent::GetNumJoints() const
|
|
{
|
|
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
|
|
|
|
return m_actorInstance->GetActor()->GetNumNodes();
|
|
}
|
|
|
|
size_t ActorComponent::GetJointIndexByName(const char* name) const
|
|
{
|
|
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
|
|
|
|
Node* node = m_actorInstance->GetActor()->GetSkeleton()->FindNodeByNameNoCase(name);
|
|
if (node)
|
|
{
|
|
return static_cast<size_t>(node->GetNodeIndex());
|
|
}
|
|
|
|
return ActorComponentRequests::s_invalidJointIndex;
|
|
}
|
|
|
|
|
|
AZ::Transform ActorComponent::GetJointTransform(size_t jointIndex, Space space) const
|
|
{
|
|
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
|
|
|
|
const size_t index = jointIndex;
|
|
const size_t numNodes = m_actorInstance->GetActor()->GetNumNodes();
|
|
|
|
AZ_Error("EMotionFX", index < numNodes, "GetJointTransform: The joint index %zu is out of bounds [0;%zu]. Entity: %s",
|
|
index, numNodes, GetEntity()->GetName().c_str());
|
|
|
|
if (index >= numNodes)
|
|
{
|
|
return AZ::Transform::CreateIdentity();
|
|
}
|
|
|
|
Pose* currentPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
switch (space)
|
|
{
|
|
case Space::LocalSpace:
|
|
{
|
|
return MCore::EmfxTransformToAzTransform(currentPose->GetLocalSpaceTransform(index));
|
|
}
|
|
|
|
case Space::ModelSpace:
|
|
{
|
|
return MCore::EmfxTransformToAzTransform(currentPose->GetModelSpaceTransform(index));
|
|
}
|
|
|
|
case Space::WorldSpace:
|
|
{
|
|
return MCore::EmfxTransformToAzTransform(currentPose->GetWorldSpaceTransform(index));
|
|
}
|
|
|
|
default:
|
|
AZ_Assert(false, "Unsupported space in GetJointTransform!");
|
|
}
|
|
|
|
return AZ::Transform::CreateIdentity();
|
|
}
|
|
|
|
void ActorComponent::GetJointTransformComponents(size_t jointIndex, Space space, AZ::Vector3& outPosition, AZ::Quaternion& outRotation, AZ::Vector3& outScale) const
|
|
{
|
|
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
|
|
|
|
const size_t index = jointIndex;
|
|
const size_t numNodes = m_actorInstance->GetActor()->GetNumNodes();
|
|
|
|
AZ_Error("EMotionFX", index < numNodes, "GetJointTransformComponents: The joint index %zu is out of bounds [0;%zu]. Entity: %s",
|
|
index, numNodes, GetEntity()->GetName().c_str());
|
|
|
|
if (index >= numNodes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Pose* currentPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
|
|
switch (space)
|
|
{
|
|
case Space::LocalSpace:
|
|
{
|
|
const Transform& localTransform = currentPose->GetLocalSpaceTransform(index);
|
|
outPosition = localTransform.m_position;
|
|
outRotation = localTransform.m_rotation;
|
|
EMFX_SCALECODE
|
|
(
|
|
outScale = localTransform.m_scale;
|
|
)
|
|
return;
|
|
}
|
|
|
|
case Space::ModelSpace:
|
|
{
|
|
const Transform& modelTransform = currentPose->GetModelSpaceTransform(index);
|
|
outPosition = modelTransform.m_position;
|
|
outRotation = modelTransform.m_rotation;
|
|
EMFX_SCALECODE
|
|
(
|
|
outScale = modelTransform.m_scale;
|
|
)
|
|
return;
|
|
}
|
|
|
|
case Space::WorldSpace:
|
|
{
|
|
const Transform worldTransform = currentPose->GetWorldSpaceTransform(index);
|
|
outPosition = worldTransform.m_position;
|
|
outRotation = worldTransform.m_rotation;
|
|
EMFX_SCALECODE
|
|
(
|
|
outScale = worldTransform.m_scale;
|
|
)
|
|
return;
|
|
}
|
|
|
|
default:
|
|
{
|
|
AZ_Assert(false, "Unsupported space in GetJointTransform!");
|
|
outPosition = AZ::Vector3::CreateZero();
|
|
outRotation = AZ::Quaternion::CreateIdentity();
|
|
outScale = AZ::Vector3::CreateOne();
|
|
}
|
|
}
|
|
}
|
|
|
|
Physics::AnimationConfiguration* ActorComponent::GetPhysicsConfig() const
|
|
{
|
|
if (m_actorInstance)
|
|
{
|
|
Actor* actor = m_actorInstance->GetActor();
|
|
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
|
|
if (physicsSetup)
|
|
{
|
|
return &physicsSetup->GetConfig();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// The entity has attached to the target.
|
|
void ActorComponent::OnAttached(AZ::EntityId attachedEntityId)
|
|
{
|
|
const AZ::EntityId* busIdPtr = LmbrCentral::AttachmentComponentNotificationBus::GetCurrentBusId();
|
|
if (busIdPtr)
|
|
{
|
|
const auto result = AZStd::find(m_attachments.begin(), m_attachments.end(), attachedEntityId);
|
|
if (result == m_attachments.end())
|
|
{
|
|
m_attachments.emplace_back(attachedEntityId);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!m_actorInstance)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ActorInstance* targetActorInstance = nullptr;
|
|
ActorComponentRequestBus::EventResult(targetActorInstance, attachedEntityId, &ActorComponentRequestBus::Events::GetActorInstance);
|
|
|
|
const char* jointName = nullptr;
|
|
LmbrCentral::AttachmentComponentRequestBus::EventResult(jointName, attachedEntityId, &LmbrCentral::AttachmentComponentRequestBus::Events::GetJointName);
|
|
if (targetActorInstance)
|
|
{
|
|
Node* node = jointName ? m_actorInstance->GetActor()->GetSkeleton()->FindNodeByName(jointName) : m_actorInstance->GetActor()->GetSkeleton()->GetNode(0);
|
|
if (node)
|
|
{
|
|
const size_t jointIndex = node->GetNodeIndex();
|
|
Attachment* attachment = AttachmentNode::Create(m_actorInstance.get(), jointIndex, targetActorInstance, true /* Managed externally, by this component. */);
|
|
m_actorInstance->AddAttachment(attachment);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The entity is detaching from the target.
|
|
void ActorComponent::OnDetached(AZ::EntityId targetId)
|
|
{
|
|
// Remove the targetId from the attachment list
|
|
const AZ::EntityId* busIdPtr = LmbrCentral::AttachmentComponentNotificationBus::GetCurrentBusId();
|
|
if (busIdPtr)
|
|
{
|
|
m_attachments.erase(AZStd::remove(m_attachments.begin(), m_attachments.end(), targetId), m_attachments.end());
|
|
}
|
|
|
|
if (!m_actorInstance)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ActorInstance* targetActorInstance = nullptr;
|
|
ActorComponentRequestBus::EventResult(targetActorInstance, targetId, &ActorComponentRequestBus::Events::GetActorInstance);
|
|
if (targetActorInstance)
|
|
{
|
|
m_actorInstance->RemoveAttachment(targetActorInstance);
|
|
}
|
|
}
|
|
} // namespace Integration
|
|
} // namespace EMotionFX
|