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.
o3de/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp

1842 lines
61 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 "EMotionFXConfig.h"
#include <MCore/Source/IDGenerator.h>
#include <MCore/Source/Random.h>
#include <MCore/Source/LogManager.h>
#include "Actor.h"
#include "ActorInstance.h"
#include "ActorManager.h"
#include "ActorUpdateScheduler.h"
#include "AnimGraphInstance.h"
#include "Attachment.h"
#include "AttachmentNode.h"
#include "AttachmentSkin.h"
#include "EventManager.h"
#include "Mesh.h"
#include "MeshDeformerStack.h"
#include "MorphMeshDeformer.h"
#include "MorphSetup.h"
#include "MorphSetupInstance.h"
#include "MotionLayerSystem.h"
#include "Node.h"
#include "NodeGroup.h"
#include "Recorder.h"
#include "TransformData.h"
#include <EMotionFX/Source/ActorInstanceBus.h>
#include <EMotionFX/Source/DebugDraw.h>
#include <EMotionFX/Source/RagdollInstance.h>
namespace EMotionFX
{
AZ_CLASS_ALLOCATOR_IMPL(ActorInstance, ActorInstanceAllocator, 0)
ActorInstance::ActorInstance(Actor* actor, AZ::Entity* entity, uint32 threadIndex)
: BaseObject()
, m_entity(entity)
, m_ragdollInstance(nullptr)
{
MCORE_ASSERT(actor);
m_enabledNodes.reserve(actor->GetNumNodes());
// set the actor and create the motion system
m_boolFlags = 0;
m_actor = actor;
m_lodLevel = 0;
m_requestedLODLevel = 0;
m_numAttachmentRefs = 0;
m_threadIndex = threadIndex;
m_attachedTo = nullptr;
m_selfAttachment = nullptr;
m_customData = nullptr;
m_id = aznumeric_caster(MCore::GetIDGenerator().GenerateID());
m_visualizeScale = 1.0f;
m_motionSamplingRate = 0.0f;
m_motionSamplingTimer = 0.0f;
m_trajectoryDelta.IdentityWithZeroScale();
m_staticAabb = AZ::Aabb::CreateNull();
m_animGraphInstance = nullptr;
// set the boolean defaults
SetFlag(BOOL_ISVISIBLE, true);
SetFlag(BOOL_BOUNDSUPDATEENABLED, true);
SetFlag(BOOL_NORMALIZEDMOTIONLOD, true);
SetFlag(BOOL_RENDER, true);
SetFlag(BOOL_USEDFORVISUALIZATION, false);
SetFlag(BOOL_ENABLED, true);
SetFlag(BOOL_MOTIONEXTRACTION, true);
#if defined(EMFX_DEVELOPMENT_BUILD)
SetFlag(BOOL_OWNEDBYRUNTIME, false);
#endif // EMFX_DEVELOPMENT_BUILD
// enable all nodes on default
EnableAllNodes();
// apply actor node group default states (disable groups of nodes that are disabled on default)
const size_t numGroups = m_actor->GetNumNodeGroups();
for (size_t i = 0; i < numGroups; ++i)
{
if (m_actor->GetNodeGroup(i)->GetIsEnabledOnDefault() == false) // if this group is disabled on default
{
m_actor->GetNodeGroup(i)->DisableNodes(this); // disable all nodes inside this group
}
}
// disable nodes that are disabled in LOD 0
Skeleton* skeleton = m_actor->GetSkeleton();
const size_t numNodes = skeleton->GetNumNodes();
for (size_t n = 0; n < numNodes; ++n)
{
if (skeleton->GetNode(n)->GetSkeletalLODStatus(0) == false)
{
DisableNode(static_cast<uint16>(n));
}
}
// setup auto bounds update (it is enabled on default)
m_boundsUpdateFrequency = 0.0f;
m_boundsUpdatePassedTime = 0.0f;
m_boundsUpdateType = BOUNDS_STATIC_BASED;
m_boundsUpdateItemFreq = 1;
// initialize the actor local and global transform
m_parentWorldTransform.Identity();
m_localTransform.Identity();
m_worldTransform.Identity();
m_worldTransformInv.Identity();
// init the morph setup instance
m_morphSetup = MorphSetupInstance::Create();
m_morphSetup->Init(actor->GetMorphSetup(0));
// initialize the transformation data of this instance
m_transformData = TransformData::Create();
m_transformData->InitForActorInstance(this);
// create the motion system
m_motionSystem = MotionLayerSystem::Create(this);
// update the global and local matrices
UpdateTransformations(0.0f);
// update the actor dependencies
UpdateDependencies();
// update the static based AABB dimensions
m_staticAabb = m_actor->GetStaticAabb();
if (!m_staticAabb.IsValid())
{
UpdateMeshDeformers(0.0f, true); // TODO: not really thread safe because of shared meshes, although it probably will output correctly
UpdateStaticBasedAabbDimensions();
}
// update the bounds
UpdateBounds(/*lodLevel=*/0, m_boundsUpdateType);
// register it
GetActorManager().RegisterActorInstance(this);
GetActorManager().GetScheduler()->RecursiveInsertActorInstance(this);
ActorInstanceNotificationBus::Broadcast(&ActorInstanceNotificationBus::Events::OnActorInstanceCreated, this);
}
ActorInstance::~ActorInstance()
{
ActorInstanceNotificationBus::Broadcast(&ActorInstanceNotificationBus::Events::OnActorInstanceDestroyed, this);
// get rid of the motion system
if (m_motionSystem)
{
m_motionSystem->Destroy();
}
if (m_animGraphInstance)
{
m_animGraphInstance->Destroy();
}
GetDebugDraw().UnregisterActorInstance(this);
// delete all attachments
// actor instances that are attached will be detached, and not deleted from memory
const size_t numAttachments = m_attachments.size();
for (size_t i = 0; i < numAttachments; ++i)
{
ActorInstance* attachmentActorInstance = m_attachments[i]->GetAttachmentActorInstance();
if (attachmentActorInstance)
{
attachmentActorInstance->SetAttachedTo(nullptr);
attachmentActorInstance->SetSelfAttachment(nullptr);
attachmentActorInstance->DecreaseNumAttachmentRefs();
GetActorManager().UpdateActorInstanceStatus(attachmentActorInstance);
}
m_attachments[i]->Destroy();
}
m_attachments.clear();
if (m_morphSetup)
{
m_morphSetup->Destroy();
}
if (m_transformData)
{
m_transformData->Destroy();
}
// remove the attachment from the actor instance where it is attached to
if (GetIsAttachment())
{
m_attachedTo->RemoveAttachment(this /*, false*/);
}
// automatically unregister the actor instance
GetActorManager().UnregisterActorInstance(this);
}
ActorInstance* ActorInstance::Create(Actor* actor, AZ::Entity* entity, uint32 threadIndex)
{
return aznew ActorInstance(actor, entity, threadIndex);
}
// update the transformation data
void ActorInstance::UpdateTransformations(float timePassedInSeconds, bool updateJointTransforms, bool sampleMotions)
{
// Update the LOD level in case a change was requested.
UpdateLODLevel();
const Recorder& recorder = GetRecorder();
timePassedInSeconds *= GetEMotionFX().GetGlobalSimulationSpeed();
// if we are using the recorder to playback
if (recorder.GetIsInPlayMode() && recorder.GetHasRecorded(this))
{
// output the anim graph instance, this doesn't overwrite transforms, just some things internally
if (recorder.GetRecordSettings().m_recordAnimGraphStates && m_animGraphInstance)
{
m_animGraphInstance->Update(0.0f);
m_animGraphInstance->Output(nullptr);
}
// apply the main transformation
recorder.SampleAndApplyMainTransform(recorder.GetCurrentPlayTime(), this);
// apply the node transforms
if (recorder.GetRecordSettings().m_recordTransforms)
{
recorder.SampleAndApplyTransforms(recorder.GetCurrentPlayTime(), this);
}
// sample the morph targets
if (recorder.GetRecordSettings().m_recordMorphs)
{
recorder.SampleAndApplyMorphs(recorder.GetCurrentPlayTime(), this);
}
// perform forward kinematics etc
UpdateWorldTransform();
UpdateSkinningMatrices();
UpdateAttachments(); // update the attachment parent matrices
// update the bounds when needed
if (GetBoundsUpdateEnabled())
{
m_boundsUpdatePassedTime += timePassedInSeconds;
if (m_boundsUpdatePassedTime >= m_boundsUpdateFrequency)
{
UpdateBounds(m_lodLevel, m_boundsUpdateType, m_boundsUpdateItemFreq);
m_boundsUpdatePassedTime = 0.0f;
}
}
return;
} // if the recorder is in playback mode and we recorded this actor instance
// check if we are an attachment
Attachment* attachment = GetSelfAttachment();
// if we are not an attachment, or if we are but not one influenced by multiple joints
if (!attachment || !attachment->GetIsInfluencedByMultipleJoints())
{
// update the motion system, which performs all blending, and updates all local transforms (excluding the local matrices)
if (m_animGraphInstance)
{
m_animGraphInstance->Update(timePassedInSeconds);
UpdateWorldTransform();
if (updateJointTransforms && sampleMotions)
{
m_animGraphInstance->Output(m_transformData->GetCurrentPose());
if (m_ragdollInstance)
{
m_ragdollInstance->PostAnimGraphUpdate(timePassedInSeconds);
}
}
}
else if (m_motionSystem)
{
m_motionSystem->Update(timePassedInSeconds, (updateJointTransforms && sampleMotions));
}
else
{
UpdateWorldTransform();
}
// when the actor instance isn't visible, we don't want to do more things
if (!updateJointTransforms)
{
if (GetBoundsUpdateEnabled() && m_boundsUpdateType == BOUNDS_STATIC_BASED)
{
UpdateBounds(m_lodLevel, m_boundsUpdateType);
}
return;
}
m_transformData->GetCurrentPose()->ApplyMorphWeightsToActorInstance();
ApplyMorphSetup();
UpdateSkinningMatrices();
UpdateAttachments();
}
else // we are a skin attachment
{
m_localTransform.Identity();
if (m_animGraphInstance)
{
m_animGraphInstance->Update(timePassedInSeconds);
UpdateWorldTransform();
if (updateJointTransforms && sampleMotions)
{
m_animGraphInstance->Output(m_transformData->GetCurrentPose());
}
}
else if (m_motionSystem)
{
m_motionSystem->Update(timePassedInSeconds, (updateJointTransforms && sampleMotions));
}
else
{
UpdateWorldTransform();
}
// when the actor instance isn't visible, we don't want to do more things
if (!updateJointTransforms)
{
if (GetBoundsUpdateEnabled() && m_boundsUpdateType == BOUNDS_STATIC_BASED)
{
UpdateBounds(m_lodLevel, m_boundsUpdateType);
}
return;
}
m_selfAttachment->UpdateJointTransforms(*m_transformData->GetCurrentPose());
m_transformData->GetCurrentPose()->ApplyMorphWeightsToActorInstance();
ApplyMorphSetup();
UpdateSkinningMatrices();
UpdateAttachments();
}
// update the bounds when needed
if (GetBoundsUpdateEnabled())
{
m_boundsUpdatePassedTime += timePassedInSeconds;
if (m_boundsUpdatePassedTime >= m_boundsUpdateFrequency)
{
UpdateBounds(m_lodLevel, m_boundsUpdateType, m_boundsUpdateItemFreq);
m_boundsUpdatePassedTime = 0.0f;
}
}
}
// update the world transformation
void ActorInstance::UpdateWorldTransform()
{
m_worldTransform = m_localTransform;
m_worldTransform.Multiply(m_parentWorldTransform);
m_worldTransformInv = m_worldTransform.Inversed();
}
// updates the skinning matrices of all nodes
void ActorInstance::UpdateSkinningMatrices()
{
AZ_PROFILE_SCOPE(Animation, "ActorInstance::UpdateSkinningMatrices");
AZ::Matrix3x4* skinningMatrices = m_transformData->GetSkinningMatrices();
const Pose* pose = m_transformData->GetCurrentPose();
const size_t numNodes = GetNumEnabledNodes();
for (size_t i = 0; i < numNodes; ++i)
{
const size_t nodeNumber = GetEnabledNode(i);
Transform skinningTransform = m_actor->GetInverseBindPoseTransform(nodeNumber);
skinningTransform.Multiply(pose->GetModelSpaceTransform(nodeNumber));
skinningMatrices[nodeNumber] = AZ::Matrix3x4::CreateFromTransform(skinningTransform.ToAZTransform());
}
}
// Update the mesh deformers, which updates the vertex positions on the CPU, so performing CPU skinning and morphing etc.
void ActorInstance::UpdateMeshDeformers(float timePassedInSeconds, bool processDisabledDeformers)
{
timePassedInSeconds *= GetEMotionFX().GetGlobalSimulationSpeed();
// Update the mesh deformers.
const Skeleton* skeleton = m_actor->GetSkeleton();
for (uint16 nodeNr : m_enabledNodes)
{
Node* node = skeleton->GetNode(nodeNr);
MeshDeformerStack* stack = m_actor->GetMeshDeformerStack(m_lodLevel, nodeNr);
if (stack)
{
stack->Update(this, node, timePassedInSeconds, processDisabledDeformers);
}
}
}
// Update the mesh morph deformers, which updates the vertex positions on the CPU, so performing CPU morphing.
void ActorInstance::UpdateMorphMeshDeformers(float timePassedInSeconds, bool processDisabledDeformers)
{
timePassedInSeconds *= GetEMotionFX().GetGlobalSimulationSpeed();
// Update the mesh morph deformers.
const Skeleton* skeleton = m_actor->GetSkeleton();
for (uint16 nodeNr : m_enabledNodes)
{
Node* node = skeleton->GetNode(nodeNr);
MeshDeformerStack* stack = m_actor->GetMeshDeformerStack(m_lodLevel, nodeNr);
if (stack)
{
stack->UpdateByModifierType(this, node, timePassedInSeconds, MorphMeshDeformer::TYPE_ID, true, processDisabledDeformers);
}
}
}
void ActorInstance::PostPhysicsUpdate(float timePassedInSeconds)
{
if (m_ragdollInstance)
{
m_ragdollInstance->PostPhysicsUpdate(timePassedInSeconds);
}
}
// add an attachment
void ActorInstance::AddAttachment(Attachment* attachment)
{
AZ_Assert(attachment, "Attachment cannot be a nullptr");
AZ_Assert(attachment->GetAttachmentActorInstance() != this, "Cannot attach to itself.");
// first remove the current attachment tree from the scheduler
ActorInstance* root = FindAttachmentRoot();
GetActorManager().GetScheduler()->RecursiveRemoveActorInstance(root);
// add the attachment
m_attachments.emplace_back(attachment);
ActorInstance* attachmentActorInstance = attachment->GetAttachmentActorInstance();
if (attachmentActorInstance)
{
attachmentActorInstance->IncreaseNumAttachmentRefs();
attachmentActorInstance->SetAttachedTo(this);
GetActorManager().UpdateActorInstanceStatus(attachmentActorInstance);
}
// and re-add the root to the scheduler
//GetActorManager().GetScheduler()->RecursiveInsertActorInstance(root, 0);
// re-add the root if it was visible already
//if (root->GetIsVisible())
GetActorManager().GetScheduler()->RecursiveInsertActorInstance(root);
}
// try to find the attachment number for a given actor instance
size_t ActorInstance::FindAttachmentNr(ActorInstance* actorInstance)
{
// for all attachments
const auto foundAttachment = AZStd::find_if(m_attachments.begin(), m_attachments.end(), [actorInstance](const Attachment* attachment)
{
return attachment->GetAttachmentActorInstance() == actorInstance;
});
return foundAttachment != m_attachments.end() ? AZStd::distance(m_attachments.begin(), foundAttachment) : InvalidIndex;
}
// remove an attachment by actor instance pointer
bool ActorInstance::RemoveAttachment(ActorInstance* actorInstance, bool delFromMem)
{
// try to find the attachment
const size_t attachmentNr = FindAttachmentNr(actorInstance);
if (attachmentNr == InvalidIndex)
{
return false;
}
// remove the actual attachment
RemoveAttachment(attachmentNr, delFromMem);
return true;
}
// remove an attachment
void ActorInstance::RemoveAttachment(size_t nr, bool delFromMem)
{
MCORE_ASSERT(nr < m_attachments.size());
// first remove the current attachment tree from the scheduler
ActorInstance* root = FindAttachmentRoot();
GetActorManager().GetScheduler()->RecursiveRemoveActorInstance(root);
// get the attachment
Attachment* attachment = m_attachments[nr];
// its not an attachment anymore
ActorInstance* attachmentInstance = attachment->GetAttachmentActorInstance();
if (attachmentInstance)
{
attachmentInstance->SetSelfAttachment(nullptr);
// unregister the attachment inside the actor instance that represents the attachment
attachmentInstance->DecreaseNumAttachmentRefs();
attachmentInstance->SetAttachedTo(nullptr);
GetActorManager().UpdateActorInstanceStatus(attachmentInstance);
attachmentInstance->SetParentWorldSpaceTransform(Transform::CreateIdentity());
}
// delete it from memory when desired
if (delFromMem)
{
attachment->Destroy();
}
// remove it from the attachment list
m_attachments.erase(AZStd::next(begin(m_attachments), nr));
// and re-add the root to the scheduler
GetActorManager().GetScheduler()->RecursiveInsertActorInstance(root, 0);
// re-add the attachment
if (attachmentInstance)
{
GetActorManager().GetScheduler()->RecursiveInsertActorInstance(attachmentInstance, 0);
}
}
// remove all attachments
void ActorInstance::RemoveAllAttachments(bool delFromMem)
{
// keep removing the last attachment until there are none left
while (m_attachments.size())
{
RemoveAttachment(m_attachments.size() - 1, delFromMem);
}
}
// update the dependencies
void ActorInstance::UpdateDependencies()
{
// get rid of existing dependencies
m_dependencies.clear();
// add the main dependency
Actor::Dependency mainDependency;
mainDependency.m_actor = m_actor;
mainDependency.m_animGraph = (m_animGraphInstance) ? m_animGraphInstance->GetAnimGraph() : nullptr;
m_dependencies.emplace_back(mainDependency);
// add all dependencies stored inside the actor
const size_t numDependencies = m_actor->GetNumDependencies();
for (size_t i = 0; i < numDependencies; ++i)
{
m_dependencies.emplace_back(*m_actor->GetDependency(i));
}
}
// set the attachment matrices
void ActorInstance::UpdateAttachments()
{
for (Attachment* attachment : m_attachments)
{
attachment->Update();
}
}
// find the root attachment actor instance, for example if you have a
// knight with knife attached to his hands, where the knight is attached to a horse, the horse will be the
// attachment root
ActorInstance* ActorInstance::FindAttachmentRoot() const
{
if (m_attachedTo)
{
return m_attachedTo->FindAttachmentRoot();
}
return const_cast<ActorInstance*>(this);
}
// change visibility state
void ActorInstance::SetIsVisible(bool isVisible)
{
// if the state doesn't change, do nothing
if (isVisible == GetIsVisible())
{
return;
}
// if we change visibility state
SetFlag(BOOL_ISVISIBLE, isVisible);
}
// update the bounding volume
void ActorInstance::UpdateBounds(size_t geomLODLevel, EBoundsType boundsType, uint32 itemFrequency)
{
AZ_PROFILE_SCOPE(Animation, "ActorInstance::UpdateBounds");
// depending on the bounding volume update type
switch (boundsType)
{
// calculate the static based AABB
case BOUNDS_STATIC_BASED:
CalcStaticBasedAabb(&m_aabb);
break;
// based on the world space positions of the nodes (least accurate, but fastest)
case BOUNDS_NODE_BASED:
CalcNodeBasedAabb(&m_aabb, itemFrequency);
break;
// based on the world space positions of the vertices of the meshes (most accurate)
case BOUNDS_MESH_BASED:
UpdateMeshDeformers(0.0f);
CalcMeshBasedAabb(geomLODLevel, &m_aabb, itemFrequency);
break;
// when we're dealing with an unspecified bounding volume update method
default:
MCore::LogInfo("*** EMotionFX::ActorInstance::UpdateBounds() - Unknown boundsType specified! (%d) ***", (uint32)boundsType);
}
// Expand the bounding volume by a tolerance area in case set.
if (!AZ::IsClose(m_boundsExpandBy, 0.0f) && m_aabb.IsValid())
{
const AZ::Vector3 center = m_aabb.GetCenter();
const AZ::Vector3 halfExtents = m_aabb.GetExtents() * 0.5f;
const AZ::Vector3 scaledHalfExtents = halfExtents * (1.0f + m_boundsExpandBy);
m_aabb.SetMin(center - scaledHalfExtents);
m_aabb.SetMax(center + scaledHalfExtents);
}
}
// calculate the axis aligned bounding box based on the world space positions of the nodes
void ActorInstance::CalcNodeBasedAabb(AZ::Aabb* outResult, uint32 nodeFrequency)
{
*outResult = AZ::Aabb::CreateNull();
const Pose* pose = m_transformData->GetCurrentPose();
const Skeleton* skeleton = m_actor->GetSkeleton();
// for all nodes, encapsulate the world space positions
const size_t numNodes = GetNumEnabledNodes();
for (size_t i = 0; i < numNodes; i += nodeFrequency)
{
const uint16 nodeNr = GetEnabledNode(i);
if (skeleton->GetNode(nodeNr)->GetIncludeInBoundsCalc())
{
outResult->AddPoint(pose->GetWorldSpaceTransform(nodeNr).m_position);
}
}
}
// calculate the AABB that contains all world space vertices of all meshes
void ActorInstance::CalcMeshBasedAabb(size_t geomLODLevel, AZ::Aabb* outResult, uint32 vertexFrequency)
{
*outResult = AZ::Aabb::CreateNull();
const Pose* pose = m_transformData->GetCurrentPose();
const Skeleton* skeleton = m_actor->GetSkeleton();
// for all nodes, encapsulate the world space positions
const size_t numNodes = GetNumEnabledNodes();
for (size_t i = 0; i < numNodes; ++i)
{
const uint16 nodeNr = GetEnabledNode(i);
Node* node = skeleton->GetNode(nodeNr);
// skip nodes without meshes
Mesh* mesh = m_actor->GetMesh(geomLODLevel, nodeNr);
if (mesh == nullptr)
{
continue;
}
// if this node should be excluded
if (node->GetIncludeInBoundsCalc() == false)
{
continue;
}
const Transform worldTransform = pose->GetMeshNodeWorldSpaceTransform(geomLODLevel, nodeNr);
// calculate and encapsulate the mesh bounds inside the total mesh box
AZ::Aabb meshBox;
mesh->CalcAabb(&meshBox, worldTransform, vertexFrequency);
outResult->AddAabb(meshBox);
}
}
// setup bounding volume auto update settings
void ActorInstance::SetupAutoBoundsUpdate(float updateFrequencyInSeconds, EBoundsType boundsType, uint32 itemFrequency)
{
MCORE_ASSERT(itemFrequency > 0); // zero would cause an infinite loop
m_boundsUpdateFrequency = updateFrequencyInSeconds;
m_boundsUpdateType = boundsType;
m_boundsUpdateItemFreq = itemFrequency;
SetBoundsUpdateEnabled(true);
}
// apply the morph setup
void ActorInstance::ApplyMorphSetup()
{
// get the morph setup instance and original setup
MorphSetupInstance* morphSetupInstance = GetMorphSetupInstance();
if (morphSetupInstance == nullptr)
{
return;
}
// if there is no morph setup, we have nothing to do
MorphSetup* morphSetup = m_actor->GetMorphSetup(m_lodLevel);
if (morphSetup == nullptr)
{
return;
}
// apply all morph targets
//bool allZero = true;
const size_t numTargets = morphSetup->GetNumMorphTargets();
for (size_t i = 0; i < numTargets; ++i)
{
// get the morph target
MorphTarget* morphTarget = morphSetup->GetMorphTarget(i);
MorphSetupInstance::MorphTarget* morphTargetInstance = morphSetupInstance->FindMorphTargetByID(morphTarget->GetID());
if (morphTargetInstance == nullptr)
{
continue;
}
// get the weight for this morph target
const float weight = morphTargetInstance->GetWeight();
// apply the morph target if its weight is non-zero
if (MCore::Math::Abs(weight) > 0.0001f)
{
//allZero = false;
morphTarget->Apply(this, weight);
}
}
}
//---------------------
// check intersection with a ray, but don't get the intersection point or closest intersecting node
Node* ActorInstance::IntersectsCollisionMesh(size_t lodLevel, const MCore::Ray& ray) const
{
const Skeleton* skeleton = m_actor->GetSkeleton();
const Pose* pose = m_transformData->GetCurrentPose();
// for all nodes
const size_t numNodes = GetNumEnabledNodes();
for (size_t i = 0; i < numNodes; ++i)
{
const uint16 nodeNr = GetEnabledNode(i);
// check if there is a mesh for this node
Mesh* mesh = m_actor->GetMesh(lodLevel, nodeNr);
if (mesh == nullptr)
{
continue;
}
if (mesh->GetIsCollisionMesh() == false)
{
continue;
}
const Transform worldTransform = pose->GetMeshNodeWorldSpaceTransform(lodLevel, nodeNr);
// return when there is an intersection
if (mesh->Intersects(worldTransform, ray))
{
return skeleton->GetNode(nodeNr);
}
}
return nullptr;
}
Node* ActorInstance::IntersectsCollisionMesh(size_t lodLevel, const MCore::Ray& ray, AZ::Vector3* outIntersect, AZ::Vector3* outNormal, AZ::Vector2* outUV, float* outBaryU, float* outBaryV, uint32* outIndices) const
{
Node* closestNode = nullptr;
AZ::Vector3 point;
AZ::Vector3 closestPoint(0.0f, 0.0f, 0.0f);
float dist, baryU, baryV, closestBaryU = 0, closestBaryV = 0;
float closestDist = FLT_MAX;
Transform closestTransform = Transform::CreateIdentity();
uint32 closestIndices[3];
uint32 triIndices[3];
const Skeleton* skeleton = m_actor->GetSkeleton();
const Pose* pose = m_transformData->GetCurrentPose();
// check all nodes
const size_t numNodes = GetNumEnabledNodes();
for (size_t i = 0; i < numNodes; i++)
{
const uint16 nodeNr = GetEnabledNode(i);
Node* curNode = skeleton->GetNode(nodeNr);
Mesh* mesh = m_actor->GetMesh(lodLevel, nodeNr);
if (mesh == nullptr)
{
continue;
}
if (mesh->GetIsCollisionMesh() == false)
{
continue;
}
const Transform worldTransform = pose->GetMeshNodeWorldSpaceTransform(lodLevel, nodeNr);
// if there is an intersection between the ray and the mesh
if (mesh->Intersects(worldTransform, ray, &point, &baryU, &baryV, triIndices))
{
// calculate the squared distance between the intersection point and the ray origin
dist = (point - ray.GetOrigin()).GetLengthSq();
// if it is the closest point till now, record it as closest
if (dist < closestDist)
{
closestTransform = worldTransform;
closestPoint = point;
closestDist = dist;
closestNode = curNode;
closestBaryU = baryU;
closestBaryV = baryV;
closestIndices[0] = triIndices[0];
closestIndices[1] = triIndices[1];
closestIndices[2] = triIndices[2];
}
}
}
// output the closest values
if (closestNode)
{
if (outIntersect)
{
*outIntersect = closestPoint;
}
if (outBaryU)
{
*outBaryU = closestBaryU;
}
if (outBaryV)
{
*outBaryV = closestBaryV;
}
if (outIndices)
{
outIndices[0] = closestIndices[0];
outIndices[1] = closestIndices[1];
outIndices[2] = closestIndices[2];
}
// calculate the interpolated normal
if (outNormal || outUV)
{
Mesh* mesh = m_actor->GetMesh(lodLevel, closestNode->GetNodeIndex());
// calculate the normal at the intersection point
if (outNormal)
{
AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(Mesh::ATTRIB_NORMALS);
AZ::Vector3 norm = MCore::BarycentricInterpolate<AZ::Vector3>(closestBaryU, closestBaryV, normals[closestIndices[0]], normals[closestIndices[1]], normals[closestIndices[2]]);
norm = closestTransform.TransformVector(norm);
norm.Normalize();
*outNormal = norm;
}
// calculate the UV coordinate at the intersection point
if (outUV)
{
// get the UV coordinates of the first layer
AZ::Vector2* uvData = static_cast<AZ::Vector2*>(mesh->FindVertexData(Mesh::ATTRIB_UVCOORDS, 0));
if (uvData)
{
// calculate the interpolated texture coordinate
*outUV = MCore::BarycentricInterpolate<AZ::Vector2>(closestBaryU, closestBaryV, uvData[closestIndices[0]], uvData[closestIndices[1]], uvData[closestIndices[2]]);
}
}
}
}
// return the result
return closestNode;
}
// check intersection with a ray, but don't get the intersection point or closest intersecting node
Node* ActorInstance::IntersectsMesh(size_t lodLevel, const MCore::Ray& ray) const
{
const Pose* pose = m_transformData->GetCurrentPose();
const Skeleton* skeleton = m_actor->GetSkeleton();
// for all nodes
const size_t numNodes = GetNumEnabledNodes();
for (size_t i = 0; i < numNodes; ++i)
{
const uint16 nodeNr = GetEnabledNode(i);
Node* node = skeleton->GetNode(nodeNr);
// check if there is a mesh for this node
Mesh* mesh = m_actor->GetMesh(lodLevel, nodeNr);
if (mesh == nullptr)
{
continue;
}
const Transform worldTransform = pose->GetMeshNodeWorldSpaceTransform(lodLevel, nodeNr);
// return when there is an intersection
if (mesh->Intersects(worldTransform, ray))
{
return node;
}
}
// no intersection found
return nullptr;
}
void ActorInstance::SetRagdoll(Physics::Ragdoll* ragdoll)
{
if (ragdoll && ragdoll->GetNumNodes() > 0)
{
m_ragdollInstance = AZStd::make_unique<RagdollInstance>(ragdoll, this);
}
else
{
m_ragdollInstance.reset();
}
}
RagdollInstance* ActorInstance::GetRagdollInstance() const
{
return m_ragdollInstance.get();
}
// intersection test that returns the closest intersection
Node* ActorInstance::IntersectsMesh(size_t lodLevel, const MCore::Ray& ray, AZ::Vector3* outIntersect, AZ::Vector3* outNormal, AZ::Vector2* outUV, float* outBaryU, float* outBaryV, uint32* outIndices) const
{
Node* closestNode = nullptr;
AZ::Vector3 point;
AZ::Vector3 closestPoint(0.0f, 0.0f, 0.0f);
Transform closestTransform = Transform::CreateIdentity();
float dist, baryU, baryV, closestBaryU = 0, closestBaryV = 0;
float closestDist = FLT_MAX;
uint32 closestIndices[3];
uint32 triIndices[3];
const Pose* pose = m_transformData->GetCurrentPose();
const Skeleton* skeleton = m_actor->GetSkeleton();
// check all nodes
const size_t numNodes = GetNumEnabledNodes();
for (size_t i = 0; i < numNodes; i++)
{
const uint16 nodeNr = GetEnabledNode(i);
Node* curNode = skeleton->GetNode(nodeNr);
Mesh* mesh = m_actor->GetMesh(lodLevel, nodeNr);
if (mesh == nullptr)
{
continue;
}
const Transform worldTransform = pose->GetMeshNodeWorldSpaceTransform(lodLevel, nodeNr);
// if there is an intersection between the ray and the mesh
if (mesh->Intersects(worldTransform, ray, &point, &baryU, &baryV, triIndices))
{
// calculate the squared distance between the intersection point and the ray origin
dist = (point - ray.GetOrigin()).GetLengthSq();
// if it is the closest point till now, record it as closest
if (dist < closestDist)
{
closestTransform = worldTransform;
closestPoint = point;
closestDist = dist;
closestNode = curNode;
closestBaryU = baryU;
closestBaryV = baryV;
closestIndices[0] = triIndices[0];
closestIndices[1] = triIndices[1];
closestIndices[2] = triIndices[2];
}
}
}
// output the closest values
if (closestNode)
{
if (outIntersect)
{
*outIntersect = closestPoint;
}
if (outBaryU)
{
*outBaryU = closestBaryU;
}
if (outBaryV)
{
*outBaryV = closestBaryV;
}
if (outIndices)
{
outIndices[0] = closestIndices[0];
outIndices[1] = closestIndices[1];
outIndices[2] = closestIndices[2];
}
// calculate the interpolated normal
if (outNormal || outUV)
{
Mesh* mesh = m_actor->GetMesh(lodLevel, closestNode->GetNodeIndex());
// calculate the normal at the intersection point
if (outNormal)
{
AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(Mesh::ATTRIB_NORMALS);
AZ::Vector3 norm = MCore::BarycentricInterpolate<AZ::Vector3>(
closestBaryU, closestBaryV,
normals[closestIndices[0]], normals[closestIndices[1]], normals[closestIndices[2]]);
norm = closestTransform.TransformVector(norm);
norm.Normalize();
*outNormal = norm;
}
// calculate the UV coordinate at the intersection point
if (outUV)
{
// get the UV coordinates of the first layer
AZ::Vector2* uvData = static_cast<AZ::Vector2*>(mesh->FindVertexData(Mesh::ATTRIB_UVCOORDS, 0));
if (uvData)
{
// calculate the interpolated texture coordinate
*outUV = MCore::BarycentricInterpolate<AZ::Vector2>(closestBaryU, closestBaryV, uvData[closestIndices[0]], uvData[closestIndices[1]], uvData[closestIndices[2]]);
}
}
}
}
// return the result
return closestNode;
}
//---------------------
//
void ActorInstance::EnableNode(uint16 nodeIndex)
{
// if this node already is at an enabled state, do nothing
if (AZStd::find(begin(m_enabledNodes), end(m_enabledNodes), nodeIndex) != end(m_enabledNodes))
{
return;
}
Skeleton* skeleton = m_actor->GetSkeleton();
// find the location where to insert (as the flattened hierarchy needs to be preserved in the array)
bool found = false;
size_t curNode = nodeIndex;
do
{
// get the parent of the current node
size_t parentIndex = skeleton->GetNode(curNode)->GetParentIndex();
if (parentIndex != InvalidIndex)
{
const auto parentArrayIter = AZStd::find(begin(m_enabledNodes), end(m_enabledNodes), static_cast<uint16>(parentIndex));
if (parentArrayIter != end(m_enabledNodes))
{
if (parentArrayIter + 1 == end(m_enabledNodes))
{
m_enabledNodes.emplace_back(nodeIndex);
}
else
{
m_enabledNodes.emplace(parentArrayIter + 1, nodeIndex);
}
found = true;
}
else
{
curNode = parentIndex;
}
}
else // if we're dealing with a root node, insert it in the front of the array
{
m_enabledNodes.emplace(AZStd::next(begin(m_enabledNodes), 0), nodeIndex);
found = true;
}
} while (found == false);
}
// disable a given node
void ActorInstance::DisableNode(uint16 nodeIndex)
{
// try to remove the node from the array
const auto it = AZStd::find(begin(m_enabledNodes), end(m_enabledNodes), nodeIndex);
if (it != end(m_enabledNodes))
{
m_enabledNodes.erase(it);
}
}
// enable all nodes
void ActorInstance::EnableAllNodes()
{
m_enabledNodes.resize(m_actor->GetNumNodes());
std::iota(m_enabledNodes.begin(), m_enabledNodes.end(), uint16(0));
}
// disable all nodes
void ActorInstance::DisableAllNodes()
{
m_enabledNodes.clear();
}
// change the skeletal LOD level
void ActorInstance::SetSkeletalLODLevelNodeFlags(size_t level)
{
// make sure the lod level is in range of 0..63
const size_t newLevel = MCore::Clamp<size_t>(level, 0, 63);
// if the lod level is the same as it currently is, do nothing
if (newLevel == m_lodLevel)
{
return;
}
Skeleton* skeleton = m_actor->GetSkeleton();
// change the state of all nodes that need state changes
const size_t numNodes = GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Node* node = skeleton->GetNode(i);
// check the curent and the new enabled state
const bool curEnabled = node->GetSkeletalLODStatus(m_lodLevel);
const bool newEnabled = node->GetSkeletalLODStatus(newLevel);
// if the state changed, enable or disable it
if (curEnabled != newEnabled)
{
if (newEnabled) // if the new LOD says that this node should be enabled, enable it
{
EnableNode(static_cast<uint16>(i));
}
else // otherwise disable it
{
DisableNode(static_cast<uint16>(i));
}
}
}
}
void ActorInstance::SetLODLevel(size_t level)
{
m_requestedLODLevel = level;
}
void ActorInstance::UpdateLODLevel()
{
// Switch LOD level in case a change was requested.
if (m_lodLevel != m_requestedLODLevel)
{
// Enable and disable all nodes accordingly (do not call this after setting the new m_lodLevel)
SetSkeletalLODLevelNodeFlags(m_requestedLODLevel);
// Make sure the LOD level is valid and update it.
m_lodLevel = MCore::Clamp<size_t>(m_requestedLODLevel, 0, m_actor->GetNumLODLevels() - 1);
}
}
// enable or disable nodes based on the skeletal LOD flags
void ActorInstance::UpdateSkeletalLODFlags()
{
// change the state of all nodes that need state changes
Skeleton* skeleton = m_actor->GetSkeleton();
const size_t numNodes = skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Node* node = skeleton->GetNode(i);
// if the new LOD says that this node should be enabled, enable it
if (node->GetSkeletalLODStatus(m_lodLevel))
{
EnableNode(static_cast<uint16>(i));
}
else // otherwise disable it
{
DisableNode(static_cast<uint16>(i));
}
}
}
// calculate the number of disabled nodes for a given skeletal lod level
size_t ActorInstance::CalcNumDisabledNodes(size_t skeletalLODLevel) const
{
uint32 numDisabledNodes = 0;
const Skeleton* skeleton = m_actor->GetSkeleton();
// get the number of nodes and iterate through them
const size_t numNodes = GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
// get the current node
Node* node = skeleton->GetNode(i);
// check if the current node is disabled in the given skeletal lod level, if yes increase the resulting number
if (node->GetSkeletalLODStatus(skeletalLODLevel) == false)
{
numDisabledNodes++;
}
}
return numDisabledNodes;
}
// calculate the number of skeletal LOD levels
size_t ActorInstance::CalcNumSkeletalLODLevels() const
{
size_t numSkeletalLODLevels = 0;
// iterate over all skeletal LOD levels
size_t currentNumDisabledNodes = 0;
size_t previousNumDisabledNodes = InvalidIndex;
for (size_t i = 0; i < sizeof(size_t) * 8; ++i)
{
// get the number of disabled nodes in the current skeletal LOD level
currentNumDisabledNodes = CalcNumDisabledNodes(i);
// compare the number of disabled nodes from the current skeletal LOD level with the previous one and increase the number of
// skeletal LOD levels in case they are not equal, if they are equal we have reached the last skeletal LOD level and we can skip the further calculations
if (previousNumDisabledNodes != currentNumDisabledNodes)
{
numSkeletalLODLevels++;
previousNumDisabledNodes = currentNumDisabledNodes;
}
else
{
break;
}
}
return numSkeletalLODLevels;
}
// change the current motion system
void ActorInstance::SetMotionSystem(MotionSystem* newSystem, bool delCurrentFromMem)
{
if (delCurrentFromMem && m_motionSystem)
{
m_motionSystem->Destroy();
}
m_motionSystem = newSystem;
}
// check if this actor instance is a skin attachment
bool ActorInstance::GetIsSkinAttachment() const
{
if (m_selfAttachment == nullptr)
{
return false;
}
return m_selfAttachment->GetIsInfluencedByMultipleJoints();
}
// draw a skeleton using lines, calling the drawline callbacks in the event handlers
void ActorInstance::DrawSkeleton(const Pose& pose, const AZ::Color& color)
{
DebugDraw& debugDraw = GetDebugDraw();
DebugDraw::ActorInstanceData* drawData = debugDraw.GetActorInstanceData(this);
drawData->Lock();
drawData->DrawPose(pose, color);
drawData->Unlock();
}
// Remove the trajectory transform from the input transformation.
void ActorInstance::MotionExtractionCompensate(Transform& inOutMotionExtractionNodeTransform, const Transform& localSpaceBindPoseTransform, EMotionExtractionFlags motionExtractionFlags)
{
Transform trajectoryTransform = inOutMotionExtractionNodeTransform;
// Make sure the z axis is really pointing up and project it onto the ground plane.
const AZ::Vector3 forwardAxis = MCore::CalcForwardAxis(trajectoryTransform.m_rotation);
if (forwardAxis.GetZ() > 0.0f) // Pick the closest, so if we point more upwards already, we take 1.0, otherwise take -1.0. Sometimes Y would point up, sometimes down.
{
MCore::RotateFromTo(trajectoryTransform.m_rotation, forwardAxis, AZ::Vector3(0.0f, 0.0f, 1.0f));
}
else
{
MCore::RotateFromTo(trajectoryTransform.m_rotation, forwardAxis, AZ::Vector3(0.0f, 0.0f, -1.0f));
}
trajectoryTransform.ApplyMotionExtractionFlags(motionExtractionFlags);
// Get the projected bind pose transform.
Transform bindTransformProjected = localSpaceBindPoseTransform;
bindTransformProjected.ApplyMotionExtractionFlags(motionExtractionFlags);
// Remove the projected rotation and translation from the transform to prevent the double transform.
inOutMotionExtractionNodeTransform.m_rotation = (bindTransformProjected.m_rotation.GetConjugate() * trajectoryTransform.m_rotation).GetConjugate() * inOutMotionExtractionNodeTransform.m_rotation;
inOutMotionExtractionNodeTransform.m_position = inOutMotionExtractionNodeTransform.m_position - (trajectoryTransform.m_position - bindTransformProjected.m_position);
inOutMotionExtractionNodeTransform.m_rotation.Normalize();
}
void ActorInstance::MotionExtractionCompensate(Transform& inOutMotionExtractionNodeTransform, EMotionExtractionFlags motionExtractionFlags) const
{
MCORE_ASSERT(m_actor->GetMotionExtractionNodeIndex() != InvalidIndex);
Transform bindPoseTransform = m_transformData->GetBindPose()->GetLocalSpaceTransform(m_actor->GetMotionExtractionNodeIndex());
MotionExtractionCompensate(inOutMotionExtractionNodeTransform, bindPoseTransform, motionExtractionFlags);
}
// Remove the trajectory transform from the motion extraction node to prevent double transformation.
void ActorInstance::MotionExtractionCompensate(EMotionExtractionFlags motionExtractionFlags)
{
const size_t motionExtractIndex = m_actor->GetMotionExtractionNodeIndex();
if (motionExtractIndex == InvalidIndex)
{
return;
}
Pose* currentPose = m_transformData->GetCurrentPose();
Transform transform = currentPose->GetLocalSpaceTransform(motionExtractIndex);
MotionExtractionCompensate(transform, motionExtractionFlags);
currentPose->SetLocalSpaceTransform(motionExtractIndex, transform);
}
void ActorInstance::ApplyMotionExtractionDelta(Transform& inOutTransform, const Transform& trajectoryDelta)
{
Transform curTransform = inOutTransform;
#ifndef EMFX_SCALE_DISABLED
curTransform.m_position += trajectoryDelta.m_position * curTransform.m_scale;
#else
curTransform.m_position += trajectoryDelta.m_position;
#endif
curTransform.m_rotation *= trajectoryDelta.m_rotation;
curTransform.m_rotation.Normalize();
inOutTransform = curTransform;
}
// Apply the motion extraction delta transform to the actor instance.
void ActorInstance::ApplyMotionExtractionDelta(const Transform& trajectoryDelta)
{
if (m_actor->GetMotionExtractionNodeIndex() == InvalidIndex)
{
return;
}
ApplyMotionExtractionDelta(m_localTransform, trajectoryDelta);
}
// apply the currently set motion extraction delta transform to the actor instance
void ActorInstance::ApplyMotionExtractionDelta()
{
ApplyMotionExtractionDelta(m_trajectoryDelta);
}
void ActorInstance::SetMotionExtractionEnabled(bool enabled)
{
SetFlag(BOOL_MOTIONEXTRACTION, enabled);
}
bool ActorInstance::GetMotionExtractionEnabled() const
{
return (m_boolFlags & BOOL_MOTIONEXTRACTION) != 0;
}
// update the static based aabb dimensions
void ActorInstance::UpdateStaticBasedAabbDimensions()
{
Transform orgTransform = GetLocalSpaceTransform();
SetLocalSpacePosition(AZ::Vector3::CreateZero());
EMFX_SCALECODE(SetLocalSpaceScale(AZ::Vector3(1.0f, 1.0f, 1.0f));)
UpdateTransformations(0.0f, true);
UpdateMeshDeformers(0.0f);
// calculate the aabb of this
if (m_actor->CheckIfHasMeshes(0))
{
CalcMeshBasedAabb(0, &m_staticAabb);
}
else
{
CalcNodeBasedAabb(&m_staticAabb);
}
m_localTransform = orgTransform;
}
// calculate the moved static based aabb
void ActorInstance::CalcStaticBasedAabb(AZ::Aabb* outResult)
{
if (GetIsSkinAttachment())
{
m_selfAttachment->GetAttachToActorInstance()->CalcStaticBasedAabb(outResult);
return;
}
*outResult = m_staticAabb;
EMFX_SCALECODE(
if (m_staticAabb.IsValid())
{
outResult->SetMin(m_staticAabb.GetMin() * m_worldTransform.m_scale);
outResult->SetMax(m_staticAabb.GetMax() * m_worldTransform.m_scale);
}
)
outResult->Translate(m_worldTransform.m_position);
}
// adjust the anim graph instance
void ActorInstance::SetAnimGraphInstance(AnimGraphInstance* instance)
{
m_animGraphInstance = instance;
UpdateDependencies();
}
Actor* ActorInstance::GetActor() const
{
return m_actor;
}
void ActorInstance::SetID(uint32 id)
{
m_id = id;
}
MotionSystem* ActorInstance::GetMotionSystem() const
{
return m_motionSystem;
}
size_t ActorInstance::GetLODLevel() const
{
return m_lodLevel;
}
void ActorInstance::SetCustomData(void* customData)
{
m_customData = customData;
}
void* ActorInstance::GetCustomData() const
{
return m_customData;
}
AZ::Entity* ActorInstance::GetEntity() const
{
return m_entity;
}
AZ::EntityId ActorInstance::GetEntityId() const
{
if (m_entity)
{
return m_entity->GetId();
}
return AZ::EntityId();
}
bool ActorInstance::GetBoundsUpdateEnabled() const
{
return (m_boolFlags & BOOL_BOUNDSUPDATEENABLED);
}
float ActorInstance::GetBoundsUpdateFrequency() const
{
return m_boundsUpdateFrequency;
}
float ActorInstance::GetBoundsUpdatePassedTime() const
{
return m_boundsUpdatePassedTime;
}
ActorInstance::EBoundsType ActorInstance::GetBoundsUpdateType() const
{
return m_boundsUpdateType;
}
uint32 ActorInstance::GetBoundsUpdateItemFrequency() const
{
return m_boundsUpdateItemFreq;
}
void ActorInstance::SetBoundsUpdateFrequency(float seconds)
{
m_boundsUpdateFrequency = seconds;
}
void ActorInstance::SetBoundsUpdatePassedTime(float seconds)
{
m_boundsUpdatePassedTime = seconds;
}
void ActorInstance::SetBoundsUpdateType(EBoundsType bType)
{
m_boundsUpdateType = bType;
}
void ActorInstance::SetBoundsUpdateItemFrequency(uint32 freq)
{
MCORE_ASSERT(freq >= 1);
m_boundsUpdateItemFreq = freq;
}
void ActorInstance::SetBoundsUpdateEnabled(bool enable)
{
SetFlag(BOOL_BOUNDSUPDATEENABLED, enable);
}
void ActorInstance::SetStaticBasedAabb(const AZ::Aabb& aabb)
{
m_staticAabb = aabb;
}
void ActorInstance::GetStaticBasedAabb(AZ::Aabb* outAabb)
{
*outAabb = m_staticAabb;
}
const AZ::Aabb& ActorInstance::GetStaticBasedAabb() const
{
return m_staticAabb;
}
const AZ::Aabb& ActorInstance::GetAabb() const
{
return m_aabb;
}
void ActorInstance::SetAabb(const AZ::Aabb& aabb)
{
m_aabb = aabb;
}
size_t ActorInstance::GetNumAttachments() const
{
return m_attachments.size();
}
Attachment* ActorInstance::GetAttachment(size_t nr) const
{
return m_attachments[nr];
}
bool ActorInstance::GetIsAttachment() const
{
return (m_attachedTo != nullptr);
}
ActorInstance* ActorInstance::GetAttachedTo() const
{
return m_attachedTo;
}
Attachment* ActorInstance::GetSelfAttachment() const
{
return m_selfAttachment;
}
size_t ActorInstance::GetNumDependencies() const
{
return m_dependencies.size();
}
Actor::Dependency* ActorInstance::GetDependency(size_t nr)
{
return &m_dependencies[nr];
}
MorphSetupInstance* ActorInstance::GetMorphSetupInstance() const
{
return m_morphSetup;
}
void ActorInstance::SetParentWorldSpaceTransform(const Transform& transform)
{
m_parentWorldTransform = transform;
}
const Transform& ActorInstance::GetParentWorldSpaceTransform() const
{
return m_parentWorldTransform;
}
void ActorInstance::SetRender(bool enabled)
{
SetFlag(BOOL_RENDER, enabled);
}
bool ActorInstance::GetRender() const
{
return (m_boolFlags & BOOL_RENDER) != 0;
}
void ActorInstance::SetIsUsedForVisualization(bool enabled)
{
SetFlag(BOOL_USEDFORVISUALIZATION, enabled);
}
bool ActorInstance::GetIsUsedForVisualization() const
{
return (m_boolFlags & BOOL_USEDFORVISUALIZATION) != 0;
}
void ActorInstance::SetIsOwnedByRuntime(bool isOwnedByRuntime)
{
#if defined(EMFX_DEVELOPMENT_BUILD)
SetFlag(BOOL_OWNEDBYRUNTIME, isOwnedByRuntime);
#else
AZ_UNUSED(isOwnedByRuntime);
#endif
}
bool ActorInstance::GetIsOwnedByRuntime() const
{
#if defined(EMFX_DEVELOPMENT_BUILD)
return (m_boolFlags & BOOL_OWNEDBYRUNTIME) != 0;
#else
return true;
#endif
}
uint32 ActorInstance::GetThreadIndex() const
{
return m_threadIndex;
}
void ActorInstance::SetThreadIndex(uint32 index)
{
m_threadIndex = index;
}
void ActorInstance::SetTrajectoryDeltaTransform(const Transform& transform)
{
m_trajectoryDelta = transform;
}
const Transform& ActorInstance::GetTrajectoryDeltaTransform() const
{
return m_trajectoryDelta;
}
AnimGraphPose* ActorInstance::RequestPose(uint32 threadIndex)
{
return GetEMotionFX().GetThreadData(threadIndex)->GetPosePool().RequestPose(this);
}
void ActorInstance::FreePose(uint32 threadIndex, AnimGraphPose* pose)
{
GetEMotionFX().GetThreadData(threadIndex)->GetPosePool().FreePose(pose);
}
void ActorInstance::SetMotionSamplingTimer(float timeInSeconds)
{
m_motionSamplingTimer = timeInSeconds;
}
void ActorInstance::SetMotionSamplingRate(float updateRateInSeconds)
{
m_motionSamplingRate = updateRateInSeconds;
}
float ActorInstance::GetMotionSamplingTimer() const
{
return m_motionSamplingTimer;
}
float ActorInstance::GetMotionSamplingRate() const
{
return m_motionSamplingRate;
}
void ActorInstance::IncreaseNumAttachmentRefs(uint8 numToIncreaseWith)
{
m_numAttachmentRefs += numToIncreaseWith;
MCORE_ASSERT(m_numAttachmentRefs == 0 || m_numAttachmentRefs == 1);
}
void ActorInstance::DecreaseNumAttachmentRefs(uint8 numToDecreaseWith)
{
m_numAttachmentRefs -= numToDecreaseWith;
MCORE_ASSERT(m_numAttachmentRefs == 0 || m_numAttachmentRefs == 1);
}
uint8 ActorInstance::GetNumAttachmentRefs() const
{
return m_numAttachmentRefs;
}
void ActorInstance::SetAttachedTo(ActorInstance* actorInstance)
{
m_attachedTo = actorInstance;
}
void ActorInstance::SetSelfAttachment(Attachment* selfAttachment)
{
m_selfAttachment = selfAttachment;
}
void ActorInstance::EnableFlag(uint8 flag)
{
m_boolFlags |= flag;
}
void ActorInstance::DisableFlag(uint8 flag)
{
m_boolFlags &= ~flag;
}
void ActorInstance::SetFlag(uint8 flag, bool enabled)
{
if (enabled)
{
m_boolFlags |= flag;
}
else
{
m_boolFlags &= ~flag;
}
}
void ActorInstance::RecursiveSetIsVisible(bool isVisible)
{
SetIsVisible(isVisible);
// recurse to all child attachments
for (Attachment* attachment : m_attachments)
{
attachment->GetAttachmentActorInstance()->RecursiveSetIsVisible(isVisible);
}
}
void ActorInstance::RecursiveSetIsVisibleTowardsRoot(bool isVisible)
{
SetIsVisible(isVisible);
if (m_selfAttachment)
{
m_selfAttachment->GetAttachToActorInstance()->RecursiveSetIsVisibleTowardsRoot(isVisible);
}
}
void ActorInstance::SetIsEnabled(bool enabled)
{
SetFlag(BOOL_ENABLED, enabled);
}
// update the normal scale factor based on the bounds
void ActorInstance::UpdateVisualizeScale()
{
m_visualizeScale = 0.0f;
UpdateMeshDeformers(0.0f);
AZ::Aabb box = AZ::Aabb::CreateNull();
CalcNodeBasedAabb(&box);
if (box.IsValid())
{
const float boxRadius = AZ::Vector3(box.GetMax() - box.GetMin()).GetLength() * 0.5f;
m_visualizeScale = MCore::Max<float>(m_visualizeScale, boxRadius);
}
CalcMeshBasedAabb(0, &box);
if (box.IsValid())
{
const float boxRadius = AZ::Vector3(box.GetMax() - box.GetMin()).GetLength() * 0.5f;
m_visualizeScale = MCore::Max<float>(m_visualizeScale, boxRadius);
}
m_visualizeScale *= 0.01f;
}
// get the normal scale factor
float ActorInstance::GetVisualizeScale() const
{
return m_visualizeScale;
}
// manually set the visualize scale factor
void ActorInstance::SetVisualizeScale(float factor)
{
m_visualizeScale = factor;
}
// Recursively check if we have a given attachment in the hierarchy going downwards.
bool ActorInstance::RecursiveHasAttachment(const ActorInstance* attachmentInstance) const
{
if (attachmentInstance == this)
{
return true;
}
// Iterate down the chain of attachments.
const size_t numAttachments = GetNumAttachments();
for (size_t i = 0; i < numAttachments; ++i)
{
if (GetAttachment(i)->GetAttachmentActorInstance()->RecursiveHasAttachment(attachmentInstance))
{
return true;
}
}
return false;
}
// Check if we can safely attach an attachment that uses the specified actor instance.
// This will check for infinite recursion/circular chains.
bool ActorInstance::CheckIfCanHandleAttachment(const ActorInstance* attachmentInstance) const
{
if (RecursiveHasAttachment(attachmentInstance) || attachmentInstance->RecursiveHasAttachment(this))
{
return false;
}
return true;
}
} // namespace EMotionFX