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/Actor.cpp

2816 lines
100 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/AssetManagerBus.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <EMotionFX/Source/EMotionFXConfig.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/ActorBus.h>
#include <EMotionFX/Source/Motion.h>
#include <EMotionFX/Source/MeshDeformerStack.h>
#include <EMotionFX/Source/Material.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/SubMesh.h>
#include <EMotionFX/Source/MorphMeshDeformer.h>
#include <EMotionFX/Source/SkinningInfoVertexAttributeLayer.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/MotionSystem.h>
#include <EMotionFX/Source/EventManager.h>
#include <EMotionFX/Source/EventHandler.h>
#include <EMotionFX/Source/NodeMap.h>
#include <EMotionFX/Source/NodeGroup.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/MorphSetup.h>
#include <EMotionFX/Source/MorphTargetStandard.h>
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/Source/Skeleton.h>
#include <EMotionFX/Source/SoftSkinDeformer.h>
#include <EMotionFX/Source/DualQuatSkinDeformer.h>
#include <EMotionFX/Source/DebugDraw.h>
#include <EMotionFX/Source/SimulatedObjectSetup.h>
#include <EMotionFX/Source/SoftSkinManager.h>
#include <MCore/Source/IDGenerator.h>
#include <MCore/Source/Compare.h>
#include <MCore/Source/LogManager.h>
#include <Atom/RPI.Reflect/Model/MorphTargetDelta.h>
namespace EMotionFX
{
AZ_CLASS_ALLOCATOR_IMPL(Actor, ActorAllocator, 0)
Actor::MeshLODData::MeshLODData()
{
// Create the default LOD level
m_lodLevels.emplace_back();
}
Actor::NodeLODInfo::NodeLODInfo()
{
m_mesh = nullptr;
m_stack = nullptr;
}
Actor::NodeLODInfo::~NodeLODInfo()
{
MCore::Destroy(m_mesh);
MCore::Destroy(m_stack);
}
//----------------------------------------------------
Actor::Actor(const char* name)
{
SetName(name);
m_skeleton = Skeleton::Create();
m_motionExtractionNode = InvalidIndex;
m_retargetRootNode = InvalidIndex;
m_threadIndex = 0;
m_customData = nullptr;
m_id = aznumeric_caster(MCore::GetIDGenerator().GenerateID());
m_unitType = GetEMotionFX().GetUnitType();
m_fileUnitType = m_unitType;
m_staticAabb = AZ::Aabb::CreateNull();
m_usedForVisualization = false;
m_dirtyFlag = false;
m_physicsSetup = AZStd::make_shared<PhysicsSetup>();
m_simulatedObjectSetup = AZStd::make_shared<SimulatedObjectSetup>(this);
m_optimizeSkeleton = false;
// make sure we have at least allocated the first LOD of materials and facial setups
m_materials.reserve(4); // reserve space for 4 lods
m_morphSetups.reserve(4); //
m_materials.emplace_back();
m_morphSetups.emplace_back(nullptr);
GetEventManager().OnCreateActor(this);
ActorNotificationBus::Broadcast(&ActorNotificationBus::Events::OnActorCreated, this);
}
Actor::~Actor()
{
ActorNotificationBus::Broadcast(&ActorNotificationBus::Events::OnActorDestroyed, this);
GetEventManager().OnDeleteActor(this);
m_nodeMirrorInfos.clear();
RemoveAllMaterials();
RemoveAllMorphSetups();
RemoveAllNodeGroups();
m_invBindPoseTransforms.clear();
MCore::Destroy(m_skeleton);
}
// creates a clone of the actor (a copy).
// does NOT copy the motions and motion tree
AZStd::unique_ptr<Actor> Actor::Clone() const
{
// create the new actor and set the name and filename
AZStd::unique_ptr<Actor> result = AZStd::make_unique<Actor>(GetName());
result->SetFileName(GetFileName());
// copy the actor attributes
result->m_motionExtractionNode = m_motionExtractionNode;
result->m_unitType = m_unitType;
result->m_fileUnitType = m_fileUnitType;
result->m_staticAabb = m_staticAabb;
result->m_retargetRootNode = m_retargetRootNode;
result->m_invBindPoseTransforms = m_invBindPoseTransforms;
result->m_optimizeSkeleton = m_optimizeSkeleton;
result->m_skinToSkeletonIndexMap = m_skinToSkeletonIndexMap;
result->RecursiveAddDependencies(this);
// clone all nodes groups
for (const NodeGroup* nodeGroup : m_nodeGroups)
{
result->AddNodeGroup(aznew NodeGroup(*nodeGroup));
}
// clone the materials
result->m_materials.resize(m_materials.size());
for (size_t i = 0; i < m_materials.size(); ++i)
{
// get the number of materials in the current LOD
result->m_materials[i].reserve(m_materials[i].size());
for (const Material* material : m_materials[i])
{
result->AddMaterial(i, material->Clone());
}
}
// clone the skeleton
MCore::Destroy(result->m_skeleton);
result->m_skeleton = m_skeleton->Clone();
// clone lod data
const size_t numNodes = m_skeleton->GetNumNodes();
const size_t numLodLevels = m_meshLodData.m_lodLevels.size();
MeshLODData& resultMeshLodData = result->m_meshLodData;
result->SetNumLODLevels(static_cast<uint32>(numLodLevels));
for (size_t lodLevel = 0; lodLevel < numLodLevels; ++lodLevel)
{
const AZStd::vector<NodeLODInfo>& nodeInfos = m_meshLodData.m_lodLevels[lodLevel].m_nodeInfos;
AZStd::vector<NodeLODInfo>& resultNodeInfos = resultMeshLodData.m_lodLevels[lodLevel].m_nodeInfos;
resultNodeInfos.resize(numNodes);
for (size_t n = 0; n < numNodes; ++n)
{
NodeLODInfo& resultNodeInfo = resultNodeInfos[n];
const NodeLODInfo& sourceNodeInfo = nodeInfos[n];
resultNodeInfo.m_mesh = (sourceNodeInfo.m_mesh) ? sourceNodeInfo.m_mesh->Clone() : nullptr;
resultNodeInfo.m_stack = (sourceNodeInfo.m_stack) ? sourceNodeInfo.m_stack->Clone(resultNodeInfo.m_mesh) : nullptr;
}
}
// clone the morph setups
result->m_morphSetups.resize(m_morphSetups.size());
for (size_t i = 0; i < m_morphSetups.size(); ++i)
{
if (m_morphSetups[i])
{
result->SetMorphSetup(i, m_morphSetups[i]->Clone());
}
else
{
result->SetMorphSetup(i, nullptr);
}
}
// make sure the number of root nodes is still the same
MCORE_ASSERT(result->GetSkeleton()->GetNumRootNodes() == m_skeleton->GetNumRootNodes());
// copy the transform data
result->CopyTransformsFrom(this);
result->m_nodeMirrorInfos = m_nodeMirrorInfos;
result->m_physicsSetup = m_physicsSetup;
result->SetSimulatedObjectSetup(m_simulatedObjectSetup->Clone(result.get()));
GetEMotionFX().GetEventManager()->OnPostCreateActor(result.get());
return result;
}
void Actor::SetSimulatedObjectSetup(const AZStd::shared_ptr<SimulatedObjectSetup>& setup)
{
m_simulatedObjectSetup = setup;
}
// init node mirror info
void Actor::AllocateNodeMirrorInfos()
{
const size_t numNodes = m_skeleton->GetNumNodes();
m_nodeMirrorInfos.resize(numNodes);
// init the data
for (size_t i = 0; i < numNodes; ++i)
{
m_nodeMirrorInfos[i].m_sourceNode = static_cast<uint16>(i);
m_nodeMirrorInfos[i].m_axis = MCORE_INVALIDINDEX8;
m_nodeMirrorInfos[i].m_flags = 0;
}
}
// remove the node mirror info
void Actor::RemoveNodeMirrorInfos()
{
m_nodeMirrorInfos.clear();
m_nodeMirrorInfos.shrink_to_fit();
}
// check if we have our axes detected
bool Actor::GetHasMirrorAxesDetected() const
{
if (m_nodeMirrorInfos.empty())
{
return false;
}
return AZStd::all_of(begin(m_nodeMirrorInfos), end(m_nodeMirrorInfos), [](const NodeMirrorInfo& nodeMirrorInfo)
{
return nodeMirrorInfo.m_axis != MCORE_INVALIDINDEX8;
});
}
// removes all materials from the actor
void Actor::RemoveAllMaterials()
{
// for all LODs
for (AZStd::vector<Material*>& materials : m_materials)
{
// delete all materials
for (Material* material : materials)
{
material->Destroy();
}
}
m_materials.clear();
}
// add a LOD level and copy the data from the last LOD level to the new one
void Actor::AddLODLevel(bool copyFromLastLODLevel)
{
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
lodLevels.emplace_back();
LODLevel& newLOD = lodLevels.back();
const size_t numNodes = m_skeleton->GetNumNodes();
newLOD.m_nodeInfos.resize(numNodes);
const size_t numLODs = lodLevels.size();
const size_t lodIndex = numLODs - 1;
// get the number of nodes, iterate through them, create a new LOD level and copy over the meshes from the last LOD level
for (size_t i = 0; i < numNodes; ++i)
{
NodeLODInfo& newLODInfo = lodLevels[lodIndex].m_nodeInfos[static_cast<uint32>(i)];
if (copyFromLastLODLevel && lodIndex > 0)
{
const NodeLODInfo& prevLODInfo = lodLevels[lodIndex - 1].m_nodeInfos[static_cast<uint32>(i)];
newLODInfo.m_mesh = (prevLODInfo.m_mesh) ? prevLODInfo.m_mesh->Clone() : nullptr;
newLODInfo.m_stack = (prevLODInfo.m_stack) ? prevLODInfo.m_stack->Clone(newLODInfo.m_mesh) : nullptr;
}
else
{
newLODInfo.m_mesh = nullptr;
newLODInfo.m_stack = nullptr;
}
}
// create a new material array for the new LOD level
m_materials.resize(lodLevels.size());
// create an empty morph setup for the new LOD level
m_morphSetups.emplace_back(nullptr);
// copy data from the previous LOD level if wanted
if (copyFromLastLODLevel && numLODs > 0)
{
CopyLODLevel(this, static_cast<uint32>(lodIndex - 1), static_cast<uint32>(numLODs - 1), true);
}
}
// insert a LOD level at a given position
void Actor::InsertLODLevel(size_t insertAt)
{
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
lodLevels.emplace(lodLevels.begin()+insertAt);
LODLevel& newLOD = lodLevels[insertAt];
const size_t lodIndex = insertAt;
const size_t numNodes = m_skeleton->GetNumNodes();
newLOD.m_nodeInfos.resize(numNodes);
// get the number of nodes, iterate through them, create a new LOD level and copy over the meshes from the last LOD level
for (size_t i = 0; i < numNodes; ++i)
{
NodeLODInfo& lodInfo = lodLevels[lodIndex].m_nodeInfos[i];
lodInfo.m_mesh = nullptr;
lodInfo.m_stack = nullptr;
}
// create a new material array for the new LOD level
m_materials.emplace(AZStd::next(begin(m_materials), insertAt));
// create an empty morph setup for the new LOD level
m_morphSetups.emplace(AZStd::next(begin(m_morphSetups), insertAt), nullptr);
}
// replace existing LOD level with the current actor
void Actor::CopyLODLevel(Actor* copyActor, size_t copyLODLevel, size_t replaceLODLevel, bool copySkeletalLODFlags)
{
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
AZStd::vector<LODLevel>& copyLodLevels = copyActor->m_meshLodData.m_lodLevels;
const LODLevel& sourceLOD = copyLodLevels[copyLODLevel];
LODLevel& targetLOD = lodLevels[replaceLODLevel];
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Node* node = m_skeleton->GetNode(i);
Node* copyNode = copyActor->GetSkeleton()->FindNodeByID(node->GetID());
if (copyNode == nullptr)
{
MCore::LogWarning("Actor::CopyLODLevel() - Failed to find node '%s' in the actor we want to copy from.", node->GetName());
}
const NodeLODInfo& sourceNodeInfo = sourceLOD.m_nodeInfos[ copyNode->GetNodeIndex() ];
NodeLODInfo& targetNodeInfo = targetLOD.m_nodeInfos[i];
// first get rid of existing data
MCore::Destroy(targetNodeInfo.m_mesh);
targetNodeInfo.m_mesh = nullptr;
MCore::Destroy(targetNodeInfo.m_stack);
targetNodeInfo.m_stack = nullptr;
// if the node exists in both models
if (copyNode)
{
// copy over the mesh and collision mesh
if (sourceNodeInfo.m_mesh)
{
targetNodeInfo.m_mesh = sourceNodeInfo.m_mesh->Clone();
}
// handle the stacks
if (sourceNodeInfo.m_stack)
{
targetNodeInfo.m_stack = sourceNodeInfo.m_stack->Clone(targetNodeInfo.m_mesh);
}
// copy the skeletal LOD flag
if (copySkeletalLODFlags)
{
node->SetSkeletalLODStatus(replaceLODLevel, copyNode->GetSkeletalLODStatus(copyLODLevel));
}
}
}
// copy the materials
const size_t numMaterials = copyActor->GetNumMaterials(copyLODLevel);
for (Material* i : m_materials[replaceLODLevel])
{
i->Destroy();
}
m_materials[replaceLODLevel].clear();
m_materials[replaceLODLevel].reserve(numMaterials);
for (size_t i = 0; i < numMaterials; ++i)
{
AddMaterial(replaceLODLevel, copyActor->GetMaterial(copyLODLevel, i)->Clone());
}
// copy the morph setup
if (m_morphSetups[replaceLODLevel])
{
m_morphSetups[replaceLODLevel]->Destroy();
}
if (copyActor->GetMorphSetup(copyLODLevel))
{
m_morphSetups[replaceLODLevel] = copyActor->GetMorphSetup(copyLODLevel)->Clone();
}
else
{
m_morphSetups[replaceLODLevel] = nullptr;
}
}
// preallocate memory for all LOD levels
void Actor::SetNumLODLevels(size_t numLODs, bool adjustMorphSetup)
{
m_meshLodData.m_lodLevels.resize(numLODs);
// reserve space for the materials
m_materials.resize(numLODs);
if (adjustMorphSetup)
{
m_morphSetups.resize(numLODs);
AZStd::fill(begin(m_morphSetups), AZStd::next(begin(m_morphSetups), numLODs), nullptr);
}
}
// removes all node meshes and stacks
void Actor::RemoveAllNodeMeshes()
{
const size_t numNodes = m_skeleton->GetNumNodes();
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
for (LODLevel& lodLevel : lodLevels)
{
for (size_t i = 0; i < numNodes; ++i)
{
NodeLODInfo& info = lodLevel.m_nodeInfos[i];
MCore::Destroy(info.m_mesh);
info.m_mesh = nullptr;
MCore::Destroy(info.m_stack);
info.m_stack = nullptr;
}
}
}
void Actor::CalcMeshTotals(size_t lodLevel, uint32* outNumPolygons, uint32* outNumVertices, uint32* outNumIndices) const
{
uint32 totalPolys = 0;
uint32 totalVerts = 0;
uint32 totalIndices = 0;
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Mesh* mesh = GetMesh(lodLevel, i);
if (!mesh)
{
continue;
}
totalVerts += mesh->GetNumVertices();
totalIndices += mesh->GetNumIndices();
totalPolys += mesh->GetNumPolygons();
}
if (outNumPolygons)
{
*outNumPolygons = totalPolys;
}
if (outNumVertices)
{
*outNumVertices = totalVerts;
}
if (outNumIndices)
{
*outNumIndices = totalIndices;
}
}
void Actor::CalcStaticMeshTotals(size_t lodLevel, uint32* outNumVertices, uint32* outNumIndices)
{
// the totals
uint32 totalVerts = 0;
uint32 totalIndices = 0;
// for all nodes
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Mesh* mesh = GetMesh(lodLevel, i);
// if there is no mesh at this LOD level, skip to the next node
if (mesh == nullptr)
{
continue;
}
// the node is dynamic, and we only want static meshes, so skip to the next node
MeshDeformerStack* stack = GetMeshDeformerStack(lodLevel, i);
if (stack && stack->GetNumDeformers() > 0)
{
continue;
}
// sum the values to the totals
totalVerts += mesh->GetNumVertices();
totalIndices += mesh->GetNumIndices();
}
// output the number of vertices
if (outNumVertices)
{
*outNumVertices = totalVerts;
}
// output the number of indices
if (outNumIndices)
{
*outNumIndices = totalIndices;
}
}
void Actor::CalcDeformableMeshTotals(size_t lodLevel, uint32* outNumVertices, uint32* outNumIndices)
{
// the totals
uint32 totalVerts = 0;
uint32 totalIndices = 0;
// for all nodes
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Mesh* mesh = GetMesh(lodLevel, i);
// if there is no mesh at this LOD level, skip to the next node
if (mesh == nullptr)
{
continue;
}
// the node is not dynamic (so static), and we only want dynamic meshes, so skip to the next node
MeshDeformerStack* stack = GetMeshDeformerStack(lodLevel, i);
if (stack == nullptr || stack->GetNumDeformers() == 0)
{
continue;
}
// sum the values to the totals
totalVerts += mesh->GetNumVertices();
totalIndices += mesh->GetNumIndices();
}
// output the number of vertices
if (outNumVertices)
{
*outNumVertices = totalVerts;
}
// output the number of indices
if (outNumIndices)
{
*outNumIndices = totalIndices;
}
}
size_t Actor::CalcMaxNumInfluences(size_t lodLevel) const
{
size_t maxInfluences = 0;
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Mesh* mesh = GetMesh(lodLevel, i);
if (!mesh)
{
continue;
}
maxInfluences = AZStd::max(maxInfluences, mesh->CalcMaxNumInfluences());
}
return maxInfluences;
}
// verify if the skinning will look correctly in the given geometry LOD for a given skeletal LOD level
void Actor::VerifySkinning(AZStd::vector<uint8>& conflictNodeFlags, size_t skeletalLODLevel, size_t geometryLODLevel)
{
// get the number of nodes
const size_t numNodes = m_skeleton->GetNumNodes();
// check if the conflict node flag array's size is set to the number of nodes inside the actor
if (conflictNodeFlags.size() != numNodes)
{
conflictNodeFlags.resize(numNodes);
}
// reset the conflict node array to zero which means we don't have any conflicting nodes yet
MCore::MemSet(conflictNodeFlags.data(), 0, numNodes * sizeof(int8));
// iterate over the all nodes in the actor
for (size_t n = 0; n < numNodes; ++n)
{
// get the current node and the pointer to the mesh for the given lod level
Node* node = m_skeleton->GetNode(n);
Mesh* mesh = GetMesh(geometryLODLevel, n);
// skip nodes without meshes
if (mesh == nullptr)
{
continue;
}
// find the skinning information, if it doesn't exist, skip to the next node
SkinningInfoVertexAttributeLayer* skinningLayer = (SkinningInfoVertexAttributeLayer*)mesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID);
if (skinningLayer == nullptr)
{
continue;
}
// get the number of original vertices and iterate through them
const uint32 numOrgVerts = mesh->GetNumOrgVertices();
for (uint32 v = 0; v < numOrgVerts; ++v)
{
// for all influences for this vertex
const size_t numInfluences = skinningLayer->GetNumInfluences(v);
for (size_t i = 0; i < numInfluences; ++i)
{
// get the node number of the bone
uint16 nodeNr = skinningLayer->GetInfluence(v, i)->GetNodeNr();
// if the current skinning influence is linked to a node which is disabled in the given
// skeletal LOD we will end up with a badly skinned character, set its flag to conflict true
if (node->GetSkeletalLODStatus(skeletalLODLevel) == false)
{
conflictNodeFlags[nodeNr] = 1;
}
}
}
}
}
size_t Actor::CalcMaxNumInfluences(size_t lodLevel, AZStd::vector<size_t>& outVertexCounts) const
{
// Reset the values.
outVertexCounts.resize(CalcMaxNumInfluences(lodLevel) + 1);
AZStd::fill(begin(outVertexCounts), end(outVertexCounts), 0);
// Get the vertex counts for the influences. (e.g. 500 vertices have 1 skinning influence, 300 vertices have 2 skinning influences etc.)
size_t maxInfluences = 0;
AZStd::vector<size_t> meshVertexCounts;
const size_t numNodes = GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Mesh* mesh = GetMesh(lodLevel, i);
if (!mesh)
{
continue;
}
const size_t meshMaxInfluences = mesh->CalcMaxNumInfluences(meshVertexCounts);
maxInfluences = AZStd::max(maxInfluences, meshMaxInfluences);
for (size_t j = 0; j < meshVertexCounts.size(); ++j)
{
outVertexCounts[j] += meshVertexCounts[j];
}
}
return maxInfluences;
}
// check if there is any mesh available
bool Actor::CheckIfHasMeshes(size_t lodLevel) const
{
// check if any of the nodes has a mesh
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
if (GetMesh(lodLevel, i))
{
return true;
}
}
// aaaah, no meshes found
return false;
}
bool Actor::CheckIfHasSkinnedMeshes(size_t lodLevel) const
{
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
const Mesh* mesh = GetMesh(lodLevel, i);
if (mesh && mesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID))
{
return true;
}
}
return false;
}
void Actor::SetPhysicsSetup(const AZStd::shared_ptr<PhysicsSetup>& physicsSetup)
{
m_physicsSetup = physicsSetup;
}
const AZStd::shared_ptr<PhysicsSetup>& Actor::GetPhysicsSetup() const
{
return m_physicsSetup;
}
const AZStd::shared_ptr<SimulatedObjectSetup>& Actor::GetSimulatedObjectSetup() const
{
return m_simulatedObjectSetup;
}
// remove all morph setups
void Actor::RemoveAllMorphSetups(bool deleteMeshDeformers)
{
// get the number of lod levels
const size_t numLODs = GetNumLODLevels();
// for all LODs, get rid of all the morph setups for each geometry LOD
for (MorphSetup* morphSetup : m_morphSetups)
{
if (morphSetup)
{
morphSetup->Destroy();
}
morphSetup = nullptr;
}
// remove all modifiers from the stacks for each lod in all nodes
if (deleteMeshDeformers)
{
// for all nodes
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
// process all LOD levels
for (size_t lod = 0; lod < numLODs; ++lod)
{
// if we have a modifier stack
MeshDeformerStack* stack = GetMeshDeformerStack(lod, i);
if (stack)
{
// remove all smart mesh morph deformers from that mesh deformer stack
stack->RemoveAllDeformersByType(MorphMeshDeformer::TYPE_ID);
// if there are no deformers left in the stack, remove the stack
if (stack->GetNumDeformers() == 0)
{
MCore::Destroy(stack);
SetMeshDeformerStack(lod, i, nullptr);
}
}
}
}
}
}
// check if the material is used by the given mesh
bool Actor::CheckIfIsMaterialUsed(Mesh* mesh, size_t materialIndex) const
{
// check if the mesh is valid
if (mesh == nullptr)
{
return false;
}
// iterate through the submeshes
const size_t numSubMeshes = mesh->GetNumSubMeshes();
for (size_t s = 0; s < numSubMeshes; ++s)
{
// if the submesh material index is the same as the material index we search for, then it is being used
if (mesh->GetSubMesh(s)->GetMaterial() == materialIndex)
{
return true;
}
}
return false;
}
// check if the material is used by a mesh of this actor
bool Actor::CheckIfIsMaterialUsed(size_t lodLevel, size_t index) const
{
// iterate through all nodes of the actor and check its meshes
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
// if the mesh is in LOD range check if it uses the material
if (CheckIfIsMaterialUsed(GetMesh(lodLevel, i), index))
{
return true;
}
}
// return false, this means that no mesh uses the given material
return false;
}
// remove the given material and reassign all material numbers of the submeshes
void Actor::RemoveMaterial(size_t lodLevel, size_t index)
{
MCORE_ASSERT(lodLevel < m_materials.size());
// first of all remove the given material
m_materials[lodLevel][index]->Destroy();
m_materials[lodLevel].erase(AZStd::next(begin(m_materials[lodLevel]), index));
}
// try to find the motion extraction node automatically
Node* Actor::FindBestMotionExtractionNode() const
{
Node* result = nullptr;
// the maximum number of children of a root node, the node with the most children
// will become our repositioning node
size_t maxNumChilds = 0;
// traverse through all root nodes
const size_t numRootNodes = m_skeleton->GetNumRootNodes();
for (size_t i = 0; i < numRootNodes; ++i)
{
// get the given root node from the actor
Node* rootNode = m_skeleton->GetNode(m_skeleton->GetRootNodeIndex(i));
// get the number of child nodes recursively
const size_t numChildNodes = rootNode->GetNumChildNodesRecursive();
// if the number of child nodes of this node is bigger than the current max number
// this is our new candidate for the repositioning node
if (numChildNodes > maxNumChilds)
{
maxNumChilds = numChildNodes;
result = rootNode;
}
}
return result;
}
// automatically find and set the best motion extraction
void Actor::AutoSetMotionExtractionNode()
{
SetMotionExtractionNode(FindBestMotionExtractionNode());
}
// extract a bone list
void Actor::ExtractBoneList(size_t lodLevel, AZStd::vector<size_t>* outBoneList) const
{
// clear the existing items
outBoneList->clear();
// for all nodes
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t n = 0; n < numNodes; ++n)
{
Mesh* mesh = GetMesh(lodLevel, n);
// skip nodes without meshes
if (mesh == nullptr)
{
continue;
}
// find the skinning information, if it doesn't exist, skip to the next node
SkinningInfoVertexAttributeLayer* skinningLayer = (SkinningInfoVertexAttributeLayer*)mesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID);
if (skinningLayer == nullptr)
{
continue;
}
// iterate through all skinning data
const uint32 numOrgVerts = mesh->GetNumOrgVertices();
for (uint32 v = 0; v < numOrgVerts; ++v)
{
// for all influences for this vertex
const size_t numInfluences = skinningLayer->GetNumInfluences(v);
for (size_t i = 0; i < numInfluences; ++i)
{
// get the node number of the bone
uint16 nodeNr = skinningLayer->GetInfluence(v, i)->GetNodeNr();
// check if it is already in the bone list, if not, add it
if (AZStd::find(begin(*outBoneList), end(*outBoneList), nodeNr) == end(*outBoneList))
{
outBoneList->emplace_back(nodeNr);
}
}
}
}
}
// recursively add dependencies
void Actor::RecursiveAddDependencies(const Actor* actor)
{
// process all dependencies of the given actor
const size_t numDependencies = actor->GetNumDependencies();
for (size_t i = 0; i < numDependencies; ++i)
{
// add it to the actor instance
m_dependencies.emplace_back(*actor->GetDependency(i));
// recursive into the actor we are dependent on
RecursiveAddDependencies(actor->GetDependency(i)->m_actor);
}
}
// remove all node groups
void Actor::RemoveAllNodeGroups()
{
for (NodeGroup*& nodeGroup : m_nodeGroups)
{
delete nodeGroup;
}
m_nodeGroups.clear();
}
// try to find a match for a given node with a given name
// for example find "Bip01 L Hand" for node "Bip01 R Hand"
uint16 Actor::FindBestMatchForNode(const char* nodeName, const char* subStringA, const char* subStringB, bool firstPass) const
{
char newString[1024];
AZStd::string nameA;
AZStd::string nameB;
// search through all nodes to find the best match
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t n = 0; n < numNodes; ++n)
{
// get the node name
const char* name = m_skeleton->GetNode(n)->GetName();
// check if a substring appears inside this node's name
if (strstr(name, subStringB))
{
// remove the substrings from the names
nameA = nodeName;
nameB = name;
uint32 offset = 0;
char* stringData = nameA.data();
MCore::MemSet(newString, 0, 1024 * sizeof(char));
while (offset < nameA.size())
{
// locate the substring
stringData = strstr(stringData, subStringA);
if (stringData == nullptr)
{
break;
}
// replace the substring
// replace subStringA with subStringB
offset = static_cast<uint32>(stringData - nameA.data());
azstrncpy(newString, 1024, nameA.c_str(), offset);
azstrcat(newString, 1024, subStringB);
azstrcat(newString, 1024, stringData + strlen(subStringA));
stringData += strlen(subStringA);
// we found a match
if (nameB == newString)
{
return static_cast<uint16>(n);
}
}
}
}
if (firstPass)
{
return FindBestMatchForNode(nodeName, subStringB, subStringA, false); // try it the other way around (substring wise)
}
// return the best match
return MCORE_INVALIDINDEX16;
}
// map motion source data of node 'sourceNodeName' to 'destNodeName' and the other way around
bool Actor::MapNodeMotionSource(const char* sourceNodeName, const char* destNodeName)
{
// find the source node index
const size_t sourceNodeIndex = m_skeleton->FindNodeByNameNoCase(sourceNodeName)->GetNodeIndex();
if (sourceNodeIndex == InvalidIndex)
{
return false;
}
// find the dest node index
const size_t destNodeIndex = m_skeleton->FindNodeByNameNoCase(destNodeName)->GetNodeIndex();
if (destNodeIndex == InvalidIndex)
{
return false;
}
// allocate the data if we haven't already
if (m_nodeMirrorInfos.empty())
{
AllocateNodeMirrorInfos();
}
// apply the mapping
m_nodeMirrorInfos[ destNodeIndex ].m_sourceNode = static_cast<uint16>(sourceNodeIndex);
m_nodeMirrorInfos[ sourceNodeIndex ].m_sourceNode = static_cast<uint16>(destNodeIndex);
// we succeeded, because both source and dest have been found
return true;
}
// map two nodes for mirroring
bool Actor::MapNodeMotionSource(uint16 sourceNodeIndex, uint16 targetNodeIndex)
{
// allocate the data if we haven't already
if (m_nodeMirrorInfos.empty())
{
AllocateNodeMirrorInfos();
}
// apply the mapping
m_nodeMirrorInfos[ targetNodeIndex ].m_sourceNode = static_cast<uint16>(sourceNodeIndex);
m_nodeMirrorInfos[ sourceNodeIndex ].m_sourceNode = static_cast<uint16>(targetNodeIndex);
// we succeeded, because both source and dest have been found
return true;
}
// match the node motion sources
// substrings could be "Left " and "Right " to map the nodes "Left Hand" and "Right Hand" to eachother
void Actor::MatchNodeMotionSources(const char* subStringA, const char* subStringB)
{
// try to map all nodes
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Node* node = m_skeleton->GetNode(i);
// find the best match
const uint16 bestIndex = FindBestMatchForNode(node->GetName(), subStringA, subStringB);
// if a best match has been found
if (bestIndex != MCORE_INVALIDINDEX16)
{
MCore::LogDetailedInfo("%s <---> %s", node->GetName(), m_skeleton->GetNode(bestIndex)->GetName());
MapNodeMotionSource(node->GetName(), m_skeleton->GetNode(bestIndex)->GetName());
}
}
}
// set the name of the actor
void Actor::SetName(const char* name)
{
m_name = name;
}
// set the filename of the actor
void Actor::SetFileName(const char* filename)
{
m_fileName = filename;
}
// find the first active parent node in a given skeletal LOD
size_t Actor::FindFirstActiveParentBone(size_t skeletalLOD, size_t startNodeIndex) const
{
size_t curNodeIndex = startNodeIndex;
do
{
curNodeIndex = m_skeleton->GetNode(curNodeIndex)->GetParentIndex();
if (curNodeIndex == InvalidIndex)
{
return curNodeIndex;
}
if (m_skeleton->GetNode(curNodeIndex)->GetSkeletalLODStatus(skeletalLOD))
{
return curNodeIndex;
}
} while (curNodeIndex != InvalidIndex);
return InvalidIndex;
}
// make the geometry LOD levels compatible with the skeletal LOD levels
// it remaps skinning influences of vertices that are linked to disabled bones, to other enabled bones
void Actor::MakeGeomLODsCompatibleWithSkeletalLODs()
{
const AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
// for all geometry lod levels
const size_t numGeomLODs = lodLevels.size();
for (size_t geomLod = 0; geomLod < numGeomLODs; ++geomLod)
{
// for all nodes
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t n = 0; n < numNodes; ++n)
{
Node* node = m_skeleton->GetNode(n);
// check if this node has a mesh, if not we can skip it
Mesh* mesh = GetMesh(static_cast<uint32>(geomLod), n);
if (mesh == nullptr)
{
continue;
}
// check if the mesh is skinned, if not, we don't need to do anything
SkinningInfoVertexAttributeLayer* layer = (SkinningInfoVertexAttributeLayer*)mesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID);
if (layer == nullptr)
{
continue;
}
// get shortcuts to the original vertex numbers
const uint32* orgVertices = (uint32*)mesh->FindOriginalVertexData(Mesh::ATTRIB_ORGVTXNUMBERS);
// for all submeshes
const size_t numSubMeshes = mesh->GetNumSubMeshes();
for (size_t s = 0; s < numSubMeshes; ++s)
{
SubMesh* subMesh = mesh->GetSubMesh(s);
// for all vertices in the submesh
const uint32 startVertex = subMesh->GetStartVertex();
const uint32 numVertices = subMesh->GetNumVertices();
for (uint32 v = 0; v < numVertices; ++v)
{
const uint32 vertexIndex = startVertex + v;
const uint32 orgVertex = orgVertices[vertexIndex];
// for all skinning influences of the vertex
const size_t numInfluences = layer->GetNumInfluences(orgVertex);
for (size_t i = 0; i < numInfluences; ++i)
{
// if the bone is disabled
SkinInfluence* influence = layer->GetInfluence(orgVertex, i);
if (m_skeleton->GetNode(influence->GetNodeNr())->GetSkeletalLODStatus(static_cast<uint32>(geomLod)) == false)
{
// find the first parent bone that is enabled in this LOD
const size_t newNodeIndex = FindFirstActiveParentBone(geomLod, influence->GetNodeNr());
if (newNodeIndex == InvalidIndex)
{
MCore::LogWarning("EMotionFX::Actor::MakeGeomLODsCompatibleWithSkeletalLODs() - Failed to find an enabled parent for node '%s' in skeletal LOD %d of actor '%s' (0x%x)", node->GetName(), geomLod, GetFileName(), this);
continue;
}
// set the new node index
influence->SetNodeNr(static_cast<uint16>(newNodeIndex));
}
} // for all influences
// optimize the influences
// if they all use the same bone, just make one influence of it with weight 1.0
for (uint32 x = 0; x < numVertices; ++x)
{
layer->CollapseInfluences(orgVertices[startVertex + x]);
}
} // for all verts
// clear the bones array
subMesh->ReinitBonesArray(layer);
} // for all submeshes
// reinit the mesh deformer stacks
MeshDeformerStack* stack = GetMeshDeformerStack(static_cast<uint32>(geomLod), node->GetNodeIndex());
if (stack)
{
stack->ReinitializeDeformers(this, node, static_cast<uint32>(geomLod));
}
} // for all nodes
}
}
// generate a path from the current node towards the root
void Actor::GenerateUpdatePathToRoot(size_t endNodeIndex, AZStd::vector<size_t>& outPath) const
{
outPath.clear();
outPath.reserve(32);
// start at the end effector
Node* currentNode = m_skeleton->GetNode(endNodeIndex);
while (currentNode)
{
// add the current node to the update list
outPath.emplace_back(currentNode->GetNodeIndex());
// move up the hierarchy, towards the root and end node
currentNode = currentNode->GetParentNode();
}
}
void Actor::SetMotionExtractionNode(Node* node)
{
if (node)
{
SetMotionExtractionNodeIndex(node->GetNodeIndex());
}
else
{
SetMotionExtractionNodeIndex(InvalidIndex);
}
}
void Actor::SetMotionExtractionNodeIndex(size_t nodeIndex)
{
m_motionExtractionNode = nodeIndex;
ActorNotificationBus::Broadcast(&ActorNotificationBus::Events::OnMotionExtractionNodeChanged, this, GetMotionExtractionNode());
}
Node* Actor::GetMotionExtractionNode() const
{
if (m_motionExtractionNode != InvalidIndex &&
m_motionExtractionNode < m_skeleton->GetNumNodes())
{
return m_skeleton->GetNode(m_motionExtractionNode);
}
return nullptr;
}
void Actor::ReinitializeMeshDeformers()
{
const size_t numLODLevels = GetNumLODLevels();
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Node* node = m_skeleton->GetNode(i);
// iterate through all LOD levels
for (size_t lodLevel = 0; lodLevel < numLODLevels; ++lodLevel)
{
// reinit the mesh deformer stacks
MeshDeformerStack* stack = GetMeshDeformerStack(lodLevel, i);
if (stack)
{
stack->ReinitializeDeformers(this, node, lodLevel);
}
}
}
}
// post init
void Actor::PostCreateInit(bool makeGeomLodsCompatibleWithSkeletalLODs, bool convertUnitType)
{
if (m_threadIndex == MCORE_INVALIDINDEX32)
{
m_threadIndex = 0;
}
// calculate the inverse bind pose matrices
const Pose* bindPose = GetBindPose();
const size_t numNodes = m_skeleton->GetNumNodes();
m_invBindPoseTransforms.resize(numNodes);
for (size_t i = 0; i < numNodes; ++i)
{
m_invBindPoseTransforms[i] = bindPose->GetModelSpaceTransform(i).Inversed();
}
// make sure the skinning info doesn't use any disabled bones
if (makeGeomLodsCompatibleWithSkeletalLODs)
{
MakeGeomLODsCompatibleWithSkeletalLODs();
}
// initialize the mesh deformers
ReinitializeMeshDeformers();
// make sure our world space bind pose is updated too
if (m_morphSetups.size() > 0 && m_morphSetups[0])
{
m_skeleton->GetBindPose()->ResizeNumMorphs(m_morphSetups[0]->GetNumMorphTargets());
}
m_skeleton->GetBindPose()->ForceUpdateFullModelSpacePose();
m_skeleton->GetBindPose()->ZeroMorphWeights();
if (!GetHasMirrorInfo())
{
AllocateNodeMirrorInfos();
}
// auto detect mirror axes
if(GetHasMirrorAxesDetected() == false)
{
AutoDetectMirrorAxes();
}
m_simulatedObjectSetup->InitAfterLoad(this);
// rescale all content if needed
if (convertUnitType)
{
ScaleToUnitType(GetEMotionFX().GetUnitType());
}
// post create actor
EMotionFX::GetEventManager().OnPostCreateActor(this);
}
AZ::Data::AssetId Actor::ConstructSkinMetaAssetId(const AZ::Data::AssetId& meshAssetId)
{
AZStd::string meshAssetPath;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(meshAssetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, meshAssetId);
AZStd::string meshAssetFileName;
AzFramework::StringFunc::Path::GetFileName(meshAssetPath.c_str(), meshAssetFileName);
return AZ::RPI::SkinMetaAsset::ConstructAssetId(meshAssetId, meshAssetFileName);
}
bool Actor::DoesSkinMetaAssetExist(const AZ::Data::AssetId& meshAssetId)
{
const AZ::Data::AssetId skinMetaAssetId = ConstructSkinMetaAssetId(meshAssetId);
AZ::Data::AssetInfo skinMetaAssetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(skinMetaAssetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, skinMetaAssetId);
return skinMetaAssetInfo.m_assetId.IsValid();
}
AZ::Data::AssetId Actor::ConstructMorphTargetMetaAssetId(const AZ::Data::AssetId& meshAssetId)
{
AZStd::string meshAssetPath;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(meshAssetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, meshAssetId);
AZStd::string meshAssetFileName;
AzFramework::StringFunc::Path::GetFileName(meshAssetPath.c_str(), meshAssetFileName);
return AZ::RPI::MorphTargetMetaAsset::ConstructAssetId(meshAssetId, meshAssetFileName);
}
bool Actor::DoesMorphTargetMetaAssetExist(const AZ::Data::AssetId& meshAssetId)
{
const AZ::Data::AssetId morphTargetMetaAssetId = ConstructMorphTargetMetaAssetId(meshAssetId);
AZ::Data::AssetInfo morphTargetMetaAssetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(morphTargetMetaAssetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, morphTargetMetaAssetId);
return morphTargetMetaAssetInfo.m_assetId.IsValid();
}
void Actor::Finalize(LoadRequirement loadReq)
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_mutex);
// Load the mesh asset, skin meta asset and morph target asset.
// Those sub assets should have already been setup as dependency of actor asset, so they should already be loaded when we reach here.
// Only exception is that when the actor is not loaded by an actor asset, for which we need to do a blocking load.
if (m_meshAssetId.IsValid())
{
// Get the mesh asset.
m_meshAsset = AZ::Data::AssetManager::Instance().GetAsset<AZ::RPI::ModelAsset>(m_meshAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
// Get the skin meta asset.
const AZ::Data::AssetId skinMetaAssetId = ConstructSkinMetaAssetId(m_meshAssetId);
if (DoesSkinMetaAssetExist(m_meshAssetId) && skinMetaAssetId.IsValid())
{
m_skinMetaAsset = AZ::Data::AssetManager::Instance().GetAsset<AZ::RPI::SkinMetaAsset>(
skinMetaAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
}
// Get the morph target meta asset.
const AZ::Data::AssetId morphTargetMetaAssetId = ConstructMorphTargetMetaAssetId(m_meshAssetId);
if (DoesMorphTargetMetaAssetExist(m_meshAssetId) && morphTargetMetaAssetId.IsValid())
{
m_morphTargetMetaAsset = AZ::Data::AssetManager::Instance().GetAsset<AZ::RPI::MorphTargetMetaAsset>(
morphTargetMetaAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
}
if (loadReq == LoadRequirement::RequireBlockingLoad)
{
if (m_skinMetaAsset.IsLoading())
{
m_skinMetaAsset.BlockUntilLoadComplete();
}
if (m_morphTargetMetaAsset.IsLoading())
{
m_morphTargetMetaAsset.BlockUntilLoadComplete();
}
if (m_meshAsset.IsLoading())
{
m_meshAsset.BlockUntilLoadComplete();
}
}
}
if (m_meshAsset.IsReady())
{
if (m_skinMetaAsset.IsReady())
{
m_skinToSkeletonIndexMap = ConstructSkinToSkeletonIndexMap(m_skinMetaAsset);
}
ConstructMeshes();
if (m_morphTargetMetaAsset.IsReady())
{
ConstructMorphTargets();
}
else
{
// Optional, not all actors have morph targets.
const size_t numLODLevels = m_meshAsset->GetLodAssets().size();
m_morphSetups.resize(numLODLevels);
for (size_t i = 0; i < numLODLevels; ++i)
{
m_morphSetups[i] = nullptr;
}
}
// build the static axis aligned bounding box by creating an actor instance (needed to perform cpu skinning mesh deforms and mesh scaling etc)
// then copy it over to the actor
UpdateStaticAabb();
}
m_isReady = true;
ActorNotificationBus::Broadcast(&ActorNotificationBus::Events::OnActorReady, this);
// Do not release the mesh assets. We need the mesh data to initialize future instances of the render actor instances.
}
// update the static AABB (very heavy as it has to create an actor instance, update mesh deformers, calculate the mesh based bounds etc)
void Actor::UpdateStaticAabb()
{
ActorInstance* actorInstance = ActorInstance::Create(this, nullptr, m_threadIndex);
actorInstance->UpdateMeshDeformers(0.0f);
actorInstance->UpdateStaticBasedAabbDimensions();
actorInstance->GetStaticBasedAabb(&m_staticAabb);
actorInstance->Destroy();
}
// find the mesh points most influenced by a particular node (pretty expensive function only intended for use in the editor)
void Actor::FindMostInfluencedMeshPoints(const Node* node, AZStd::vector<AZ::Vector3>& outPoints) const
{
outPoints.clear();
const size_t geomLODLevel = 0;
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
{
// check if this node has a mesh, if not we can skip it
Mesh* mesh = GetMesh(geomLODLevel, nodeIndex);
if (mesh == nullptr)
{
continue;
}
// check if the mesh is skinned, if not, we don't need to do anything
SkinningInfoVertexAttributeLayer* layer = (SkinningInfoVertexAttributeLayer*)mesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID);
if (layer == nullptr)
{
continue;
}
// get shortcuts to the original vertex numbers
const uint32* orgVertices = (uint32*)mesh->FindOriginalVertexData(Mesh::ATTRIB_ORGVTXNUMBERS);
AZ::Vector3* positions = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS);
// for all submeshes
const size_t numSubMeshes = mesh->GetNumSubMeshes();
for (size_t subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
{
SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex);
// for all vertices in the submesh
const uint32 startVertex = subMesh->GetStartVertex();
const uint32 numVertices = subMesh->GetNumVertices();
for (uint32 vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex)
{
const uint32 orgVertex = orgVertices[startVertex + vertexIndex];
// for all skinning influences of the vertex
const size_t numInfluences = layer->GetNumInfluences(orgVertex);
float maxWeight = 0.0f;
size_t maxWeightNodeIndex = 0;
for (size_t i = 0; i < numInfluences; ++i)
{
SkinInfluence* influence = layer->GetInfluence(orgVertex, i);
float weight = influence->GetWeight();
if (weight > maxWeight)
{
maxWeight = weight;
maxWeightNodeIndex = influence->GetNodeNr();
}
} // for all influences
if (maxWeightNodeIndex == node->GetNodeIndex())
{
outPoints.push_back(positions[vertexIndex + startVertex]);
}
} // for all verts
} // for all submeshes
}
}
// auto detect the mirror axes
void Actor::AutoDetectMirrorAxes()
{
AZ::Vector3 modelSpaceMirrorPlaneNormal(1.0f, 0.0f, 0.0f);
Pose pose;
pose.LinkToActor(this);
const size_t numNodes = m_nodeMirrorInfos.size();
for (size_t i = 0; i < numNodes; ++i)
{
const uint16 motionSource = (GetHasMirrorInfo()) ? GetNodeMirrorInfo(i).m_sourceNode : static_cast<uint16>(i);
// displace the local transform a bit, and calculate its mirrored model space position
pose.InitFromBindPose(this);
Transform localTransform = pose.GetLocalSpaceTransform(motionSource);
Transform orgDelta = Transform::CreateIdentity();
orgDelta.m_position.Set(1.1f, 2.2f, 3.3f);
orgDelta.m_rotation = MCore::AzEulerAnglesToAzQuat(0.1f, 0.2f, 0.3f);
Transform delta = orgDelta;
delta.Multiply(localTransform);
pose.SetLocalSpaceTransform(motionSource, delta);
Transform endModelSpaceTransform = pose.GetModelSpaceTransform(motionSource);
endModelSpaceTransform.Mirror(modelSpaceMirrorPlaneNormal);
float minDist = FLT_MAX;
uint8 bestAxis = 0;
uint8 bestFlags = 0;
bool found = false;
for (uint8 a = 0; a < 3; ++a) // mirror along x, y and then z axis
{
AZ::Vector3 axis(0.0f, 0.0f, 0.0f);
axis.SetElement(a, 1.0f);
// mirror it over the current plane
pose.InitFromBindPose(this);
localTransform = pose.GetLocalSpaceTransform(i);
delta = orgDelta;
delta.Mirror(axis);
delta.Multiply(localTransform);
pose.SetLocalSpaceTransform(i, delta);
const Transform& modelSpaceResult = pose.GetModelSpaceTransform(i);
// check if we have a matching distance in model space
const float dist = MCore::SafeLength(modelSpaceResult.m_position - endModelSpaceTransform.m_position);
if (dist <= MCore::Math::epsilon)
{
m_nodeMirrorInfos[i].m_axis = a;
m_nodeMirrorInfos[i].m_flags = 0;
found = true;
break;
}
// record if this is a better match
if (dist < minDist)
{
minDist = dist;
bestAxis = a;
bestFlags = 0;
}
}
// try with flipped axes
if (found == false)
{
for (uint8 a = 0; a < 3; ++a) // mirror along x, y and then z axis
{
for (uint8 f = 0; f < 3; ++f) // flip axis
{
AZ::Vector3 axis(0.0f, 0.0f, 0.0f);
axis.SetElement(a, 1.0f);
uint8 flags = 0;
if (f == 0)
{
flags = MIRRORFLAG_INVERT_X;
}
if (f == 1)
{
flags = MIRRORFLAG_INVERT_Y;
}
if (f == 2)
{
flags = MIRRORFLAG_INVERT_Z;
}
// mirror it over the current plane
pose.InitFromBindPose(this);
localTransform = pose.GetLocalSpaceTransform(i);
delta = orgDelta;
delta.MirrorWithFlags(axis, flags);
delta.Multiply(localTransform);
pose.SetLocalSpaceTransform(i, delta);
const Transform& modelSpaceResult = pose.GetModelSpaceTransform(i);
// check if we have a matching distance in world space
const float dist = MCore::SafeLength(modelSpaceResult.m_position - endModelSpaceTransform.m_position);
if (dist <= MCore::Math::epsilon)
{
m_nodeMirrorInfos[i].m_axis = a;
m_nodeMirrorInfos[i].m_flags = flags;
found = true;
break;
}
// record if this is a better match
if (dist < minDist)
{
minDist = dist;
bestAxis = a;
bestFlags = flags;
}
} // for all flips
if (found)
{
break;
}
} // for all mirror axes
}
if (found == false)
{
m_nodeMirrorInfos[i].m_axis = bestAxis;
m_nodeMirrorInfos[i].m_flags = bestFlags;
}
}
}
// get the array of node mirror infos
const AZStd::vector<Actor::NodeMirrorInfo>& Actor::GetNodeMirrorInfos() const
{
return m_nodeMirrorInfos;
}
// get the array of node mirror infos
AZStd::vector<Actor::NodeMirrorInfo>& Actor::GetNodeMirrorInfos()
{
return m_nodeMirrorInfos;
}
// set the node mirror infos directly
void Actor::SetNodeMirrorInfos(const AZStd::vector<NodeMirrorInfo>& mirrorInfos)
{
m_nodeMirrorInfos = mirrorInfos;
}
// try to geometrically match left with right nodes
void Actor::MatchNodeMotionSourcesGeometrical()
{
Pose pose;
pose.InitFromBindPose(this);
const uint16 numNodes = static_cast<uint16>(m_skeleton->GetNumNodes());
for (uint16 i = 0; i < numNodes; ++i)
{
// find the best match
const uint16 bestIndex = FindBestMirrorMatchForNode(i, pose);
// if a best match has been found
if (bestIndex != MCORE_INVALIDINDEX16)
{
//LogDetailedInfo("%s <---> %s", node->GetName(), GetNode(bestIndex)->GetName());
MapNodeMotionSource(i, bestIndex);
}
}
}
// find the best matching node index
uint16 Actor::FindBestMirrorMatchForNode(uint16 nodeIndex, Pose& pose) const
{
if (m_skeleton->GetNode(nodeIndex)->GetIsRootNode())
{
return MCORE_INVALIDINDEX16;
}
// calculate the model space transform and mirror it
const Transform nodeTransform = pose.GetModelSpaceTransform(nodeIndex);
const Transform mirroredTransform = nodeTransform.Mirrored(AZ::Vector3(1.0f, 0.0f, 0.0f));
size_t numMatches = 0;
uint16 result = MCORE_INVALIDINDEX16;
// find nodes that have the mirrored transform
const size_t numNodes = m_skeleton->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
const Transform& curNodeTransform = pose.GetModelSpaceTransform(i);
if (i != nodeIndex)
{
// only check the translation for now
#ifndef EMFX_SCALE_DISABLED
if (MCore::Compare<AZ::Vector3>::CheckIfIsClose(
curNodeTransform.m_position,
mirroredTransform.m_position, MCore::Math::epsilon) &&
MCore::Compare<float>::CheckIfIsClose(MCore::SafeLength(curNodeTransform.m_scale),
MCore::SafeLength(mirroredTransform.m_scale), MCore::Math::epsilon))
#else
if (MCore::Compare<AZ::Vector3>::CheckIfIsClose(curNodeTransform.m_position, mirroredTransform.m_position, MCore::Math::epsilon))
#endif
{
numMatches++;
result = static_cast<uint16>(i);
}
}
}
if (numMatches == 1)
{
const size_t hierarchyDepth = m_skeleton->CalcHierarchyDepthForNode(nodeIndex);
const size_t matchingHierarchyDepth = m_skeleton->CalcHierarchyDepthForNode(result);
if (hierarchyDepth != matchingHierarchyDepth)
{
return MCORE_INVALIDINDEX16;
}
return result;
}
return MCORE_INVALIDINDEX16;
}
// resize the transform arrays to the current number of nodes
void Actor::ResizeTransformData()
{
Pose& bindPose = *m_skeleton->GetBindPose();
bindPose.LinkToActor(this, Pose::FLAG_LOCALTRANSFORMREADY, false);
const size_t numMorphs = bindPose.GetNumMorphWeights();
for (size_t i = 0; i < numMorphs; ++i)
{
bindPose.SetMorphWeight(i, 0.0f);
}
m_invBindPoseTransforms.resize(m_skeleton->GetNumNodes());
}
// release any transform data
void Actor::ReleaseTransformData()
{
m_skeleton->GetBindPose()->Clear();
m_invBindPoseTransforms.clear();
}
// copy transforms from another actor
void Actor::CopyTransformsFrom(const Actor* other)
{
MCORE_ASSERT(other->GetNumNodes() == m_skeleton->GetNumNodes());
ResizeTransformData();
m_invBindPoseTransforms = other->m_invBindPoseTransforms;
*m_skeleton->GetBindPose() = *other->GetSkeleton()->GetBindPose();
}
void Actor::SetNumNodes(size_t numNodes)
{
m_skeleton->SetNumNodes(numNodes);
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
for (LODLevel& lodLevel : lodLevels)
{
lodLevel.m_nodeInfos.resize(numNodes);
}
Pose* bindPose = m_skeleton->GetBindPose();
bindPose->LinkToActor(this, Pose::FLAG_LOCALTRANSFORMREADY, false);
}
void Actor::AddNode(Node* node)
{
m_skeleton->AddNode(node);
m_skeleton->GetBindPose()->LinkToActor(this, Pose::FLAG_LOCALTRANSFORMREADY, false);
// initialize the LOD data
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
for (LODLevel& lodLevel : lodLevels)
{
lodLevel.m_nodeInfos.emplace_back();
}
m_skeleton->GetBindPose()->LinkToActor(this, Pose::FLAG_LOCALTRANSFORMREADY, false);
m_skeleton->GetBindPose()->SetLocalSpaceTransform(m_skeleton->GetNumNodes() - 1, Transform::CreateIdentity());
}
Node* Actor::AddNode(size_t nodeIndex, const char* name, size_t parentIndex)
{
Node* node = Node::Create(name, GetSkeleton());
node->SetNodeIndex(nodeIndex);
node->SetParentIndex(parentIndex);
AddNode(node);
if (parentIndex == InvalidIndex)
{
GetSkeleton()->AddRootNode(node->GetNodeIndex());
}
else
{
node->GetParentNode()->AddChild(nodeIndex);
}
return node;
}
void Actor::RemoveNode(size_t nr, bool delMem)
{
m_skeleton->RemoveNode(nr, delMem);
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
for (LODLevel& lodLevel : lodLevels)
{
lodLevel.m_nodeInfos.erase(AZStd::next(begin(lodLevel.m_nodeInfos), nr));
}
}
void Actor::DeleteAllNodes()
{
m_skeleton->RemoveAllNodes();
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
for (LODLevel& lodLevel : lodLevels)
{
lodLevel.m_nodeInfos.clear();
}
}
void Actor::ReserveMaterials(size_t lodLevel, size_t numMaterials)
{
m_materials[lodLevel].reserve(numMaterials);
}
// get a material
Material* Actor::GetMaterial(size_t lodLevel, size_t nr) const
{
MCORE_ASSERT(lodLevel < m_materials.size());
MCORE_ASSERT(nr < m_materials[lodLevel].size());
return m_materials[lodLevel][nr];
}
// get a material by name
size_t Actor::FindMaterialIndexByName(size_t lodLevel, const char* name) const
{
// search through all materials
const auto foundMaterial = AZStd::find_if(m_materials[lodLevel].begin(), m_materials[lodLevel].end(), [name](const Material* material)
{
return material->GetNameString() == name;
});
return foundMaterial != m_materials[lodLevel].end() ? AZStd::distance(m_materials[lodLevel].begin(), foundMaterial) : InvalidIndex;
}
// set a material
void Actor::SetMaterial(size_t lodLevel, size_t nr, Material* mat)
{
m_materials[lodLevel][nr] = mat;
}
void Actor::AddMaterial(size_t lodLevel, Material* mat)
{
m_materials[lodLevel].emplace_back(mat);
}
size_t Actor::GetNumMaterials(size_t lodLevel) const
{
return m_materials[lodLevel].size();
}
size_t Actor::GetNumLODLevels() const
{
return m_meshLodData.m_lodLevels.size();
}
void* Actor::GetCustomData() const
{
return m_customData;
}
void Actor::SetCustomData(void* dataPointer)
{
m_customData = dataPointer;
}
const char* Actor::GetName() const
{
return m_name.c_str();
}
const AZStd::string& Actor::GetNameString() const
{
return m_name;
}
const char* Actor::GetFileName() const
{
return m_fileName.c_str();
}
const AZStd::string& Actor::GetFileNameString() const
{
return m_fileName;
}
void Actor::AddDependency(const Dependency& dependency)
{
m_dependencies.emplace_back(dependency);
}
void Actor::SetMorphSetup(size_t lodLevel, MorphSetup* setup)
{
m_morphSetups[lodLevel] = setup;
}
size_t Actor::GetNumNodeGroups() const
{
return m_nodeGroups.size();
}
NodeGroup* Actor::GetNodeGroup(size_t index) const
{
return m_nodeGroups[index];
}
void Actor::AddNodeGroup(NodeGroup* newGroup)
{
m_nodeGroups.emplace_back(newGroup);
}
void Actor::RemoveNodeGroup(size_t index, bool delFromMem)
{
if (delFromMem)
{
delete m_nodeGroups[index];
}
m_nodeGroups.erase(AZStd::next(begin(m_nodeGroups), index));
}
void Actor::RemoveNodeGroup(NodeGroup* group, bool delFromMem)
{
const auto found = AZStd::find(begin(m_nodeGroups), end(m_nodeGroups), group);
if (found != end(m_nodeGroups))
{
m_nodeGroups.erase(found);
if (delFromMem)
{
delete group;
}
}
}
// find a group index by its name
size_t Actor::FindNodeGroupIndexByName(const char* groupName) const
{
const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup)
{
return nodeGroup->GetNameString() == groupName;
});
return found != end(m_nodeGroups) ? AZStd::distance(begin(m_nodeGroups), found) : InvalidIndex;
}
// find a group index by its name, but not case sensitive
size_t Actor::FindNodeGroupIndexByNameNoCase(const char* groupName) const
{
const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup)
{
return AzFramework::StringFunc::Equal(nodeGroup->GetNameString(), groupName, false /* no case */);
});
return found != end(m_nodeGroups) ? AZStd::distance(begin(m_nodeGroups), found) : InvalidIndex;
}
// find a group by its name
NodeGroup* Actor::FindNodeGroupByName(const char* groupName) const
{
const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup)
{
return nodeGroup->GetNameString() == groupName;
});
return found != end(m_nodeGroups) ? *found : nullptr;
}
// find a group by its name, but without case sensitivity
NodeGroup* Actor::FindNodeGroupByNameNoCase(const char* groupName) const
{
const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup)
{
return AzFramework::StringFunc::Equal(nodeGroup->GetNameString(), groupName, false /* no case */);
});
return found != end(m_nodeGroups) ? *found : nullptr;
}
void Actor::SetDirtyFlag(bool dirty)
{
m_dirtyFlag = dirty;
}
bool Actor::GetDirtyFlag() const
{
return m_dirtyFlag;
}
void Actor::SetIsUsedForVisualization(bool flag)
{
m_usedForVisualization = flag;
}
bool Actor::GetIsUsedForVisualization() const
{
return m_usedForVisualization;
}
const AZ::Aabb& Actor::GetStaticAabb() const
{
return m_staticAabb;
}
void Actor::SetStaticAabb(const AZ::Aabb& aabb)
{
m_staticAabb = aabb;
}
//---------------------------------
Mesh* Actor::GetMesh(size_t lodLevel, size_t nodeIndex) const
{
const AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
return lodLevels[lodLevel].m_nodeInfos[nodeIndex].m_mesh;
}
MeshDeformerStack* Actor::GetMeshDeformerStack(size_t lodLevel, size_t nodeIndex) const
{
const AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
return lodLevels[lodLevel].m_nodeInfos[nodeIndex].m_stack;
}
// set the mesh for a given node in a given LOD
void Actor::SetMesh(size_t lodLevel, size_t nodeIndex, Mesh* mesh)
{
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
lodLevels[lodLevel].m_nodeInfos[nodeIndex].m_mesh = mesh;
}
// set the mesh deformer stack for a given node in a given LOD
void Actor::SetMeshDeformerStack(size_t lodLevel, size_t nodeIndex, MeshDeformerStack* stack)
{
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
lodLevels[lodLevel].m_nodeInfos[nodeIndex].m_stack = stack;
}
// check if the mesh has a skinning deformer (either linear or dual quat)
bool Actor::CheckIfHasSkinningDeformer(size_t lodLevel, size_t nodeIndex) const
{
// check if there is a mesh
Mesh* mesh = GetMesh(lodLevel, nodeIndex);
if (!mesh)
{
return false;
}
// check if there is a mesh deformer stack
MeshDeformerStack* stack = GetMeshDeformerStack(lodLevel, nodeIndex);
if (!stack)
{
return false;
}
return (stack->CheckIfHasDeformerOfType(SoftSkinDeformer::TYPE_ID) || stack->CheckIfHasDeformerOfType(DualQuatSkinDeformer::TYPE_ID));
}
// remove the mesh for a given node in a given LOD
void Actor::RemoveNodeMeshForLOD(size_t lodLevel, size_t nodeIndex, bool destroyMesh)
{
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
LODLevel& lod = lodLevels[lodLevel];
NodeLODInfo& nodeInfo = lod.m_nodeInfos[nodeIndex];
if (destroyMesh && nodeInfo.m_mesh)
{
MCore::Destroy(nodeInfo.m_mesh);
}
if (destroyMesh && nodeInfo.m_stack)
{
MCore::Destroy(nodeInfo.m_stack);
}
nodeInfo.m_mesh = nullptr;
nodeInfo.m_stack = nullptr;
}
void Actor::SetUnitType(MCore::Distance::EUnitType unitType)
{
m_unitType = unitType;
}
MCore::Distance::EUnitType Actor::GetUnitType() const
{
return m_unitType;
}
void Actor::SetFileUnitType(MCore::Distance::EUnitType unitType)
{
m_fileUnitType = unitType;
}
MCore::Distance::EUnitType Actor::GetFileUnitType() const
{
return m_fileUnitType;
}
// scale all data
void Actor::Scale(float scaleFactor)
{
// if we don't need to adjust the scale, do nothing
if (MCore::Math::IsFloatEqual(scaleFactor, 1.0f))
{
return;
}
// scale the bind pose positions
Pose* bindPose = GetBindPose();
const size_t numNodes = GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
Transform transform = bindPose->GetLocalSpaceTransform(i);
transform.m_position *= scaleFactor;
bindPose->SetLocalSpaceTransform(i, transform);
}
bindPose->ForceUpdateFullModelSpacePose();
// calculate the inverse bind pose matrices
for (size_t i = 0; i < numNodes; ++i)
{
m_invBindPoseTransforms[i] = bindPose->GetModelSpaceTransform(i).Inversed();
}
// update static aabb
m_staticAabb.SetMin(m_staticAabb.GetMin() * scaleFactor);
m_staticAabb.SetMax(m_staticAabb.GetMax() * scaleFactor);
// update mesh data for all LOD levels
const size_t numLODs = GetNumLODLevels();
for (size_t lod = 0; lod < numLODs; ++lod)
{
for (size_t i = 0; i < numNodes; ++i)
{
Mesh* mesh = GetMesh(lod, i);
if (mesh)
{
mesh->Scale(scaleFactor);
}
}
}
// scale morph target data
for (size_t lod = 0; lod < numLODs; ++lod)
{
MorphSetup* morphSetup = GetMorphSetup(lod);
if (morphSetup)
{
morphSetup->Scale(scaleFactor);
}
}
// initialize the mesh deformers just to be sure
ReinitializeMeshDeformers();
// trigger the event
GetEventManager().OnScaleActorData(this, scaleFactor);
}
// scale everything to the given unit type
void Actor::ScaleToUnitType(MCore::Distance::EUnitType targetUnitType)
{
if (m_unitType == targetUnitType)
{
return;
}
// calculate the scale factor and scale
const float scaleFactor = static_cast<float>(MCore::Distance::GetConversionFactor(m_unitType, targetUnitType));
Scale(scaleFactor);
// update the unit type
m_unitType = targetUnitType;
}
// Try to figure out which axis points "up" for the motion extraction node.
Actor::EAxis Actor::FindBestMatchingMotionExtractionAxis() const
{
MCORE_ASSERT(m_motionExtractionNode != InvalidIndex);
if (m_motionExtractionNode == InvalidIndex)
{
return AXIS_Y;
}
// Get the local space rotation matrix of the motion extraction node.
const Transform& localTransform = GetBindPose()->GetLocalSpaceTransform(m_motionExtractionNode);
const AZ::Matrix3x3 rotationMatrix = AZ::Matrix3x3::CreateFromQuaternion(localTransform.m_rotation);
// Calculate angles between the up axis and each of the rotation's basis vectors.
const AZ::Vector3 globalUpAxis(0.0f, 0.0f, 1.0f);
const float dotX = rotationMatrix.GetRow(0).Dot(globalUpAxis);
const float dotY = rotationMatrix.GetRow(1).Dot(globalUpAxis);
const float dotZ = rotationMatrix.GetRow(2).Dot(globalUpAxis);
const float difX = 1.0f - MCore::Clamp(MCore::Math::Abs(dotX), 0.0f, 1.0f);
const float difY = 1.0f - MCore::Clamp(MCore::Math::Abs(dotY), 0.0f, 1.0f);
const float difZ = 1.0f - MCore::Clamp(MCore::Math::Abs(dotZ), 0.0f, 1.0f);
// Pick the axis which has the smallest angle difference.
if (difX <= difY && difY <= difZ)
{
return AXIS_X;
}
else if (difY <= difX && difX <= difZ)
{
return AXIS_Y;
}
else
{
return AXIS_Z;
}
}
void Actor::SetRetargetRootNodeIndex(size_t nodeIndex)
{
m_retargetRootNode = nodeIndex;
}
void Actor::SetRetargetRootNode(Node* node)
{
m_retargetRootNode = node ? node->GetNodeIndex() : InvalidIndex;
}
void Actor::InsertJointAndParents(size_t jointIndex, AZStd::unordered_set<size_t>& includedJointIndices)
{
// If our joint is already in, then we can skip things.
if (includedJointIndices.find(jointIndex) != includedJointIndices.end())
{
return;
}
// Add the parent.
const size_t parentIndex = m_skeleton->GetNode(jointIndex)->GetParentIndex();
if (parentIndex != InvalidIndex)
{
InsertJointAndParents(parentIndex, includedJointIndices);
}
// Add itself.
includedJointIndices.emplace(jointIndex);
}
void Actor::AutoSetupSkeletalLODsBasedOnSkinningData(const AZStd::vector<AZStd::string>& alwaysIncludeJoints)
{
AZStd::unordered_set<size_t> includedJointIndices;
const size_t numLODs = GetNumLODLevels();
for (size_t lod = 0; lod < numLODs; ++lod)
{
includedJointIndices.clear();
// If we have no meshes, or only static meshes, we shouldn't do anything.
if (!CheckIfHasMeshes(lod) || !CheckIfHasSkinnedMeshes(lod))
{
continue;
}
const size_t numJoints = m_skeleton->GetNumNodes();
for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex)
{
const Mesh* mesh = GetMesh(lod, jointIndex);
if (!mesh)
{
continue;
}
// Include the mesh, as we always want to include meshes.
InsertJointAndParents(jointIndex, includedJointIndices);
// Look at the joints registered in the submeshes.
const size_t numSubMeshes = mesh->GetNumSubMeshes();
for (size_t subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
{
const AZStd::vector<size_t>& subMeshJoints = mesh->GetSubMesh(subMeshIndex)->GetBonesArray();
for (size_t subMeshJoint : subMeshJoints)
{
InsertJointAndParents(subMeshJoint, includedJointIndices);
}
}
} // for all joints
// Now that we have the list of joints to include for this LOD, let's disable all the joints in this LOD, and enable all the ones in our list.
if (!includedJointIndices.empty())
{
// Force joints in our "always include list" to be included.
for (const AZStd::string& jointName : alwaysIncludeJoints)
{
size_t jointIndex = InvalidIndex;
if (!m_skeleton->FindNodeAndIndexByName(jointName, jointIndex))
{
if (!jointName.empty())
{
AZ_Warning("EMotionFX", false, "Cannot find joint '%s' inside the skeleton. This joint name was specified inside the alwaysIncludeJoints list.", jointName.c_str());
}
continue;
}
InsertJointAndParents(jointIndex, includedJointIndices);
}
// Disable all joints first.
for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex)
{
m_skeleton->GetNode(jointIndex)->SetSkeletalLODStatus(lod, false);
}
// Enable all our included joints in this skeletal LOD.
AZ_TracePrintf("EMotionFX", "[LOD %d] Enabled joints = %zd\n", lod, includedJointIndices.size());
for (size_t jointIndex : includedJointIndices)
{
m_skeleton->GetNode(jointIndex)->SetSkeletalLODStatus(lod, true);
}
}
else // When we have an empty include list, enable everything.
{
AZ_TracePrintf("EMotionFX", "[LOD %d] Enabled joints = %zd\n", lod, m_skeleton->GetNumNodes());
for (size_t i = 0; i < m_skeleton->GetNumNodes(); ++i)
{
m_skeleton->GetNode(i)->SetSkeletalLODStatus(lod, true);
}
}
} // for each LOD
}
void Actor::PrintSkeletonLODs()
{
const size_t numLODs = GetNumLODLevels();
for (size_t lod = 0; lod < numLODs; ++lod)
{
AZ_TracePrintf("EMotionFX", "[LOD %d]:", lod);
const size_t numJoints = m_skeleton->GetNumNodes();
for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex)
{
const Node* joint = m_skeleton->GetNode(jointIndex);
if (joint->GetSkeletalLODStatus(lod))
{
AZ_TracePrintf("EMotionFX", "\t%s (index=%zu)", joint->GetName(), jointIndex);
}
}
}
}
void Actor::GenerateOptimizedSkeleton()
{
// We should have already removed all the mesh, skinning information, sim object and etc.
// At this point, we only need to remove extra joint nodes.
// First, check if we have a hit detection setup.
// NOTE:: We won't do anything unless the actor has a hit detection collider setup. If you don't have a hit collider associate with animation on server,
// you should consider not running the anim graph at all.
if (!m_physicsSetup || m_physicsSetup->GetHitDetectionConfig().m_nodes.empty())
{
return;
}
// 1) Build a set of nodes that we want to keep in the actor skeleton heirarchy.
// 2) Mark all the node in the above list and all their predecessors.
// 3) In actor skeleton, remove every node that hasn't been marked.
// 4) Meanwhile, build a map that represent the child-parent relationship.
// 5) After the node index changed, we use the map in 4) to restore the child-parent relationship.
size_t numNodes = m_skeleton->GetNumNodes();
AZStd::vector<bool> flags;
AZStd::unordered_map<AZStd::string, AZStd::string> childParentMap;
flags.resize(numNodes);
AZStd::unordered_set<Node*> nodesToKeep;
// Search the hit detection config to find and keep all the hit detection nodes.
for (const Physics::CharacterColliderNodeConfiguration& nodeConfig : m_physicsSetup->GetHitDetectionConfig().m_nodes)
{
Node* node = m_skeleton->FindNodeByName(nodeConfig.m_name);
if (node && nodesToKeep.find(node) == nodesToKeep.end())
{
nodesToKeep.emplace(node);
}
}
// find our motion extraction node and make sure we keep it.
Node* motionExtractionNode = GetMotionExtractionNode();
if (motionExtractionNode)
{
nodesToKeep.emplace(motionExtractionNode);
}
// Search the actor skeleton to find all the critical nodes.
for (size_t i = 0; i < numNodes; ++i)
{
Node* node = m_skeleton->GetNode(i);
if (node->GetIsCritical() && nodesToKeep.find(node) == nodesToKeep.end())
{
nodesToKeep.emplace(node);
}
}
for (Node* nodeToKeep : nodesToKeep)
{
Node* node = nodeToKeep;
// Mark this node and all its predecessors.
do
{
if (flags[node->GetNodeIndex()])
{
break;
}
flags[node->GetNodeIndex()] = true;
Node* parent = node->GetParentNode();
if (parent)
{
childParentMap[node->GetNameString()] = parent->GetNameString();
}
node = parent;
} while (node);
}
// Remove all the nodes that haven't been marked
for (size_t nodeIndex = numNodes - 1; nodeIndex > 0; nodeIndex--)
{
if (!flags[nodeIndex])
{
m_skeleton->RemoveNode(nodeIndex);
}
}
// Update the node index.
m_skeleton->UpdateNodeIndexValues();
// After the node index changed, the parent index become invalid. First, clear all information about children because
// it's not valid anymore.
for (size_t nodeIndex = 0; nodeIndex < m_skeleton->GetNumNodes(); ++nodeIndex)
{
Node* node = m_skeleton->GetNode(nodeIndex);
node->RemoveAllChildNodes();
}
// Then build the child-parent relationship using the prebuild map.
for (auto& pair : childParentMap)
{
Node* child = m_skeleton->FindNodeByName(pair.first);
Node* parent = m_skeleton->FindNodeByName(pair.second);
child->SetParentIndex(parent->GetNodeIndex());
parent->AddChild(child->GetNodeIndex());
}
// Resize transform data because the actor nodes has been trimmed down.
ResizeTransformData();
//reset the motion extraction node index
SetMotionExtractionNode(motionExtractionNode);
FindBestMatchingMotionExtractionAxis();
}
void Actor::SetMeshAssetId(const AZ::Data::AssetId& assetId)
{
m_meshAssetId = assetId;
}
Node* Actor::FindMeshJoint(const AZ::Data::Asset<AZ::RPI::ModelLodAsset>& lodModelAsset) const
{
const AZStd::array_view<AZ::RPI::ModelLodAsset::Mesh>& sourceMeshes = lodModelAsset->GetMeshes();
// Use the first joint that we can find for any of the Atom sub meshes and use it as owner of our mesh.
for (const AZ::RPI::ModelLodAsset::Mesh& sourceMesh : sourceMeshes)
{
const AZ::Name& meshName = sourceMesh.GetName();
Node* joint = FindJointByMeshName(meshName.GetStringView());
if (joint)
{
return joint;
}
}
// In case neither of the mesh joints are present in the actor, just use the root node as fallback.
AZ_Assert(m_skeleton->GetNode(0), "Actor needs to have at least a single joint.");
return m_skeleton->GetNode(0);
}
void Actor::ConstructMeshes()
{
AZ_Assert(m_meshAsset.IsReady(), "Mesh asset should be fully loaded and ready.");
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
const AZStd::array_view<AZ::Data::Asset<AZ::RPI::ModelLodAsset>>& lodAssets = m_meshAsset->GetLodAssets();
const size_t numLODLevels = lodAssets.size();
lodLevels.clear();
SetNumLODLevels(numLODLevels, /*adjustMorphSetup=*/false);
const size_t numNodes = m_skeleton->GetNumNodes();
// Remove all the materials and add them back based on the meshAsset. Eventually we will remove all the material from Actor and
// GLActor.
RemoveAllMaterials();
m_materials.resize(numLODLevels);
for (size_t lodLevel = 0; lodLevel < numLODLevels; ++lodLevel)
{
const AZ::Data::Asset<AZ::RPI::ModelLodAsset>& lodAsset = lodAssets[lodLevel];
lodLevels[lodLevel].m_nodeInfos.resize(numNodes);
// Create a single mesh for the actor.
Mesh* mesh = Mesh::CreateFromModelLod(lodAsset, m_skinToSkeletonIndexMap);
// Find an owning joint for the mesh.
Node* meshJoint = FindMeshJoint(lodAsset);
if (!meshJoint)
{
AZ_Error("EMotionFX", false, "Cannot find mesh joint. Skipping to add meshes to the actor.");
continue;
}
const size_t jointIndex = meshJoint->GetNodeIndex();
NodeLODInfo& jointInfo = lodLevels[lodLevel].m_nodeInfos[jointIndex];
jointInfo.m_mesh = mesh;
if (!jointInfo.m_stack)
{
jointInfo.m_stack = MeshDeformerStack::Create(mesh);
}
// Add the skinning deformers
const size_t numLayers = mesh->GetNumSharedVertexAttributeLayers();
for (size_t layerNr = 0; layerNr < numLayers; ++layerNr)
{
EMotionFX::VertexAttributeLayer* vertexAttributeLayer = mesh->GetSharedVertexAttributeLayer(layerNr);
if (vertexAttributeLayer->GetType() != EMotionFX::SkinningInfoVertexAttributeLayer::TYPE_ID)
{
continue;
}
EMotionFX::SkinningInfoVertexAttributeLayer* skinLayer =
static_cast<EMotionFX::SkinningInfoVertexAttributeLayer*>(vertexAttributeLayer);
const AZ::u32 numOrgVerts = skinLayer->GetNumAttributes();
const size_t numLocalJoints = skinLayer->CalcLocalJointIndices(numOrgVerts).size();
// The information about if we want to use dual quat skinning is baked into the mesh chunk and we don't have access to that
// anymore. Default to dual quat skinning.
const bool dualQuatSkinning = true;
if (dualQuatSkinning)
{
DualQuatSkinDeformer* skinDeformer = DualQuatSkinDeformer::Create(mesh);
jointInfo.m_stack->AddDeformer(skinDeformer);
skinDeformer->ReserveLocalBones(numLocalJoints);
skinDeformer->Reinitialize(this, meshJoint, static_cast<uint32>(lodLevel));
}
else
{
SoftSkinDeformer* skinDeformer = GetSoftSkinManager().CreateDeformer(mesh);
jointInfo.m_stack->AddDeformer(skinDeformer);
skinDeformer->ReserveLocalBones(numLocalJoints); // pre-alloc data to prevent reallocs
skinDeformer->Reinitialize(this, meshJoint, static_cast<uint32>(lodLevel));
}
}
// Add material for this mesh
AddMaterial(static_cast<uint32>(lodLevel), Material::Create(GetName()));
}
}
Node* Actor::FindJointByMeshName(const AZStd::string_view meshName) const
{
Node* joint = m_skeleton->FindNodeByName(meshName.data());
if (!joint)
{
// When mesh merging in the model builder is enabled, the name of the mesh is the concatenated version
// of all the merged meshes with a plus symbol used as delimiter. Try to find any of the merged mesh joint
// and use the first one to add the mesh to it.
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(meshName, tokens, '+');
for (const AZStd::string& token : tokens)
{
joint = m_skeleton->FindNodeByName(token);
if (joint)
{
break;
}
}
}
return joint;
}
AZStd::unordered_map<AZ::u16, AZ::u16> Actor::ConstructSkinToSkeletonIndexMap(const AZ::Data::Asset<AZ::RPI::SkinMetaAsset>& skinMetaAsset)
{
AZ_Assert(skinMetaAsset && skinMetaAsset.IsReady(), "Cannot construct skin to skeleton index mapping. Skin meta asset needs to be loaded and ready.");
// Build an atom index to emfx index map
AZStd::unordered_map<AZ::u16, AZ::u16> result;
for (const auto& pair : skinMetaAsset->GetJointNameToIndexMap())
{
const Node* node = m_skeleton->FindNodeByName(pair.first.c_str());
if (!node)
{
AZ_Assert(node, "Cannot find joint named %s in the skeleton while it is used by the skin.", pair.first.c_str());
continue;
}
result.emplace(pair.second, aznumeric_caster(node->GetNodeIndex()));
}
return result;
}
void Actor::ConstructMorphTargets()
{
AZ_Assert(m_meshAsset.IsReady() && m_morphTargetMetaAsset.IsReady(),
"Mesh as well as morph target meta asset asset should be fully loaded and ready.");
AZStd::vector<LODLevel>& lodLevels = m_meshLodData.m_lodLevels;
const AZStd::array_view<AZ::Data::Asset<AZ::RPI::ModelLodAsset>>& lodAssets = m_meshAsset->GetLodAssets();
const size_t numLODLevels = lodAssets.size();
AZ_Assert(m_morphSetups.size() == numLODLevels, "There needs to be a morph setup for every single LOD level.");
for (size_t lodLevel = 0; lodLevel < numLODLevels; ++lodLevel)
{
const AZ::Data::Asset<AZ::RPI::ModelLodAsset>& lodAsset = lodAssets[lodLevel];
const AZStd::array_view<AZ::RPI::ModelLodAsset::Mesh>& sourceMeshes = lodAsset->GetMeshes();
MorphSetup* morphSetup = m_morphSetups[static_cast<uint32>(lodLevel)];
if (!morphSetup)
{
continue;
}
// Find the owning joint for the mesh.
Node* meshJoint = FindMeshJoint(lodAsset);
if (!meshJoint)
{
AZ_Error("EMotionFX", false, "Cannot find mesh joint. Skipping to add meshes to the actor.");
continue;
}
const size_t jointIndex = meshJoint->GetNodeIndex();
NodeLODInfo& jointInfo = lodLevels[lodLevel].m_nodeInfos[jointIndex];
Mesh* mesh = jointInfo.m_mesh;
if (!jointInfo.m_stack)
{
jointInfo.m_stack = MeshDeformerStack::Create(mesh);
}
// Add the morph deformer to the mesh deformer stack (in case there is none yet).
MorphMeshDeformer* morphTargetDeformer = (MorphMeshDeformer*)jointInfo.m_stack->FindDeformerByType(MorphMeshDeformer::TYPE_ID);
if (!morphTargetDeformer)
{
morphTargetDeformer = MorphMeshDeformer::Create(mesh);
// Add insert the deformer at the first position to make sure we apply morph targets before skinning.
jointInfo.m_stack->InsertDeformer(/*deformerPosition=*/0, morphTargetDeformer);
}
// The lod has shared buffers that combine the data from each submesh. In case any of the submeshes has a
// morph target buffer view we can access the entire morph target buffer via the buffer asset.
AZStd::array_view<uint8_t> morphTargetDeltaView;
for (const AZ::RPI::ModelLodAsset::Mesh& sourceMesh : sourceMeshes)
{
if (const auto* bufferAssetView = sourceMesh.GetSemanticBufferAssetView(AZ::Name("MORPHTARGET_VERTEXDELTAS")))
{
if (const auto* bufferAsset = bufferAssetView->GetBufferAsset().Get())
{
// The buffer of the view is the buffer of the whole LOD, not just the source mesh.
morphTargetDeltaView = bufferAsset->GetBuffer();
break;
}
}
}
AZ_Assert(morphTargetDeltaView.data(), "Unable to find MORPHTARGET_VERTEXDELTAS buffer");
const AZ::RPI::PackedCompressedMorphTargetDelta* vertexDeltas = reinterpret_cast<const AZ::RPI::PackedCompressedMorphTargetDelta*>(morphTargetDeltaView.data());
const size_t numMorphTargets = morphSetup->GetNumMorphTargets();
for (size_t mtIndex = 0; mtIndex < numMorphTargets; ++mtIndex)
{
MorphTargetStandard* morphTarget = static_cast<MorphTargetStandard*>(morphSetup->GetMorphTarget(mtIndex));
// Remove all previously added deform datas for the given joint as we set a new mesh.
morphTarget->RemoveAllDeformDatasFor(meshJoint);
const AZStd::vector<AZ::RPI::MorphTargetMetaAsset::MorphTarget>& metaDatas = m_morphTargetMetaAsset->GetMorphTargets();
for (const auto& metaData : metaDatas)
{
if (metaData.m_morphTargetName == morphTarget->GetNameString())
{
const AZ::u32 numDeformedVertices = metaData.m_numVertices;
MorphTargetStandard::DeformData* deformData = aznew MorphTargetStandard::DeformData(jointIndex, numDeformedVertices);
// Set the compression/quantization range for the positions.
deformData->m_minValue = metaData.m_minPositionDelta;
deformData->m_maxValue = metaData.m_maxPositionDelta;
for (AZ::u32 deformVtx = 0; deformVtx < numDeformedVertices; ++deformVtx)
{
// Get the index into the morph delta buffer
const AZ::u32 vertexIndex = metaData.m_startIndex + deformVtx;
// Unpack the delta
const AZ::RPI::PackedCompressedMorphTargetDelta& packedCompressedDelta = vertexDeltas[vertexIndex];
AZ::RPI::CompressedMorphTargetDelta unpackedCompressedDelta = AZ::RPI::UnpackMorphTargetDelta(packedCompressedDelta);
// Set the EMotionFX deform data from the CmopressedMorphTargetDelta
deformData->m_deltas[deformVtx].m_vertexNr = unpackedCompressedDelta.m_morphedVertexIndex;
deformData->m_deltas[deformVtx].m_position = MCore::Compressed16BitVector3(
unpackedCompressedDelta.m_positionX,
unpackedCompressedDelta.m_positionY,
unpackedCompressedDelta.m_positionZ);
deformData->m_deltas[deformVtx].m_normal = MCore::Compressed8BitVector3(
unpackedCompressedDelta.m_normalX,
unpackedCompressedDelta.m_normalY,
unpackedCompressedDelta.m_normalZ);
deformData->m_deltas[deformVtx].m_tangent = MCore::Compressed8BitVector3(
unpackedCompressedDelta.m_tangentX,
unpackedCompressedDelta.m_tangentY,
unpackedCompressedDelta.m_tangentZ);
deformData->m_deltas[deformVtx].m_bitangent = MCore::Compressed8BitVector3(
unpackedCompressedDelta.m_bitangentX,
unpackedCompressedDelta.m_bitangentY,
unpackedCompressedDelta.m_bitangentZ);
}
morphTarget->AddDeformData(deformData);
}
}
}
// Sync the deformer passes with the morph target deform datas.
morphTargetDeformer->Reinitialize(this, meshJoint, static_cast<uint32>(lodLevel));
}
}
} // namespace EMotionFX