[LYN-3312] EMotionFX: Multi-threading dual quaternion software skinning / Moving the mouse while having many characters on screen makes the editor unusably slow (#506)

Multi-threaded software skinning by splitting the mesh up into batches of 10,000 vertices, which results in 27 jobs being spawned and executed using a character asset from a customer having 262,676 vertices and a speed improvement of 12x on a 32-core machine.

Single-threaded: 11,61 ms
Multi-threaded (27 jobs): 0,96 ms

* Created a SkinRange() function that can skin a part of the vertices.
* The actual skinning is now using the job system to split up the skinning into several pieces and executes them in parallel.
* Ported the bone info array to AzCore.
* Fixed some issues with the mesh based bounds update in the actor instance.
This commit is contained in:
Benjamin Jillich
2021-05-03 18:48:30 +02:00
committed by GitHub
parent 3c7ca72693
commit 893d4208c0
4 changed files with 153 additions and 209 deletions
@@ -360,7 +360,7 @@ namespace EMotionFX
ApplyMorphSetup();
UpdateSkinningMatrices();
UpdateAttachments();
}
}
// update the bounds when needed
if (GetBoundsUpdateEnabled() && mBoundsUpdateType != BOUNDS_MESH_BASED)
@@ -418,7 +418,8 @@ namespace EMotionFX
}
// Update the bounds when we are set to use mesh based bounds.
if (GetBoundsUpdateEnabled() == BOUNDS_MESH_BASED)
if (GetBoundsUpdateEnabled() &&
GetBoundsUpdateType() == BOUNDS_MESH_BASED)
{
mBoundsUpdatePassedTime += timePassedInSeconds;
if (mBoundsUpdatePassedTime >= mBoundsUpdateFrequency)
@@ -449,7 +450,8 @@ namespace EMotionFX
}
// Update the bounds when we are set to use mesh based bounds.
if (GetBoundsUpdateEnabled() == BOUNDS_MESH_BASED)
if (GetBoundsUpdateEnabled() &&
GetBoundsUpdateType() == BOUNDS_MESH_BASED)
{
mBoundsUpdatePassedTime += timePassedInSeconds;
if (mBoundsUpdatePassedTime >= mBoundsUpdateFrequency)
@@ -10,7 +10,8 @@
*
*/
// include the required headers
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Jobs/JobCompletion.h>
#include "EMotionFXConfig.h"
#include "DualQuatSkinDeformer.h"
#include "Mesh.h"
@@ -23,138 +24,143 @@
#include <EMotionFX/Source/Allocators.h>
#include <MCore/Source/LogManager.h>
namespace EMotionFX
{
AZ_CLASS_ALLOCATOR_IMPL(DualQuatSkinDeformer, DeformerAllocator, 0)
// constructor
DualQuatSkinDeformer::DualQuatSkinDeformer(Mesh* mesh)
: MeshDeformer(mesh)
{
mBones.SetMemoryCategory(EMFX_MEMCATEGORY_GEOMETRY_DEFORMERS);
}
// destructor
DualQuatSkinDeformer::~DualQuatSkinDeformer()
{
mBones.Clear();
}
AZ::Outcome<size_t> DualQuatSkinDeformer::FindLocalBoneIndex(uint32 nodeIndex) const
{
const size_t numBones = m_bones.size();
for (size_t i = 0; i < numBones; ++i)
{
if (m_bones[i].mNodeNr == nodeIndex)
{
return AZ::Success(i);
}
}
return AZ::Failure();
}
// creation
DualQuatSkinDeformer* DualQuatSkinDeformer::Create(Mesh* mesh)
{
return aznew DualQuatSkinDeformer(mesh);
}
// get the type id
uint32 DualQuatSkinDeformer::GetType() const
{
return TYPE_ID;
}
// get the subtype id
uint32 DualQuatSkinDeformer::GetSubType() const
{
return SUBTYPE_ID;
}
// clone this class
MeshDeformer* DualQuatSkinDeformer::Clone(Mesh* mesh)
{
// create the new cloned deformer
DualQuatSkinDeformer* result = aznew DualQuatSkinDeformer(mesh);
// copy the bone info (for precalc/optimization reasons)
result->mBones = mBones;
result->m_bones = m_bones;
// return the result
return result;
}
// the main method where all calculations are done
void DualQuatSkinDeformer::Update(ActorInstance* actorInstance, Node* node, float timeDelta)
void DualQuatSkinDeformer::Update(ActorInstance* actorInstance, [[maybe_unused]] Node* node, [[maybe_unused]] float timeDelta)
{
AZ_UNUSED(node);
MCORE_UNUSED(timeDelta);
const Actor* actor = actorInstance->GetActor();
const Pose* pose = actorInstance->GetTransformData()->GetCurrentPose();
const uint32 numVertices = mMesh->GetNumVertices();
AZ::Vector3 newPos, newNormal, newTangent, newBitangent;
AZ::Vector3 vtxPos, normal, tangent, bitangent;
AZ::Vector3* positions = static_cast<AZ::Vector3*>(mMesh->FindVertexData(Mesh::ATTRIB_POSITIONS));
AZ::Vector3* normals = static_cast<AZ::Vector3*>(mMesh->FindVertexData(Mesh::ATTRIB_NORMALS));
AZ::Vector4* tangents = static_cast<AZ::Vector4*>(mMesh->FindVertexData(Mesh::ATTRIB_TANGENTS));
AZ::Vector3* bitangents = static_cast<AZ::Vector3*>(mMesh->FindVertexData(Mesh::ATTRIB_BITANGENTS));
AZ::u32* orgVerts = static_cast<AZ::u32*>(mMesh->FindVertexData(Mesh::ATTRIB_ORGVTXNUMBERS));
// precalc the skinning matrices
const uint32 numBones = mBones.GetLength();
for (uint32 i = 0; i < numBones; i++)
// pre-calculate the skinning matrices
for (BoneInfo& boneInfo : m_bones)
{
const uint32 nodeIndex = mBones[i].mNodeNr;
Transform skinTransform = actor->GetInverseBindPoseTransform(nodeIndex);
skinTransform.Multiply(pose->GetModelSpaceTransform(nodeIndex));
mBones[i].mDualQuat.FromRotationTranslation(skinTransform.mRotation, skinTransform.mPosition);
const uint32 nodeIndex = boneInfo.mNodeNr;
const Transform skinTransform = actor->GetInverseBindPoseTransform(nodeIndex) * pose->GetModelSpaceTransform(nodeIndex);
boneInfo.mDualQuat.FromRotationTranslation(skinTransform.mRotation, skinTransform.mPosition);
}
// find the skinning layer
SkinningInfoVertexAttributeLayer* layer = (SkinningInfoVertexAttributeLayer*)mMesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID);
MCORE_ASSERT(layer);
AZ::JobCompletion jobCompletion;
// Split up the skinned vertices into batches.
const AZ::u32 numBatches = aznumeric_caster(ceilf(aznumeric_cast<float>(numVertices) / aznumeric_cast<float>(s_numVerticesPerBatch)));
for (AZ::u32 batchIndex = 0; batchIndex < numBatches; ++batchIndex)
{
const AZ::u32 startVertex = batchIndex * s_numVerticesPerBatch;
const AZ::u32 endVertex = AZStd::min(startVertex + s_numVerticesPerBatch, numVertices);
// Create a job for every batch and skin them simultaneously.
AZ::JobContext* jobContext = nullptr;
AZ::Job* job = AZ::CreateJobFunction([this, startVertex, endVertex]()
{
SkinRange(mMesh, startVertex, endVertex, m_bones);
}, /*isAutoDelete=*/true, jobContext);
job->SetDependent(&jobCompletion);
job->Start();
}
jobCompletion.StartAndWaitForCompletion();
}
void DualQuatSkinDeformer::SkinRange(Mesh* mesh, AZ::u32 startVertex, AZ::u32 endVertex, const AZStd::vector<BoneInfo>& boneInfos)
{
SkinningInfoVertexAttributeLayer* layer = (SkinningInfoVertexAttributeLayer*)mesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID);
AZ_Assert(layer, "Cannot find skinning layer.");
AZ::Vector3 newTangent;
AZ::Vector3 vtxPos, normal, tangent, bitangent;
AZ::u32 orgVertex;
float weight;
AZ::Vector3* positions = static_cast<AZ::Vector3*>(mesh->FindVertexData(Mesh::ATTRIB_POSITIONS));
AZ::Vector3* normals = static_cast<AZ::Vector3*>(mesh->FindVertexData(Mesh::ATTRIB_NORMALS));
AZ::Vector4* tangents = static_cast<AZ::Vector4*>(mesh->FindVertexData(Mesh::ATTRIB_TANGENTS));
AZ::Vector3* bitangents = static_cast<AZ::Vector3*>(mesh->FindVertexData(Mesh::ATTRIB_BITANGENTS));
AZ::u32* orgVerts = static_cast<AZ::u32*>(mesh->FindVertexData(Mesh::ATTRIB_ORGVTXNUMBERS));
// if there are tangents and bitangents to skin
if (tangents && bitangents)
{
const uint32 numVertices = mMesh->GetNumVertices();
uint32 v = 0;
uint32 orgVertex;
SkinInfluence* influence;
BoneInfo* boneInfo;
float weight;
for (v = 0; v < numVertices; ++v)
for (AZ::u32 v = startVertex; v < endVertex; ++v)
{
// get the original vertex number
orgVertex = *(orgVerts++);
// reset the skinned position
newPos = AZ::Vector3::CreateZero();
newNormal = AZ::Vector3::CreateZero();
newTangent = AZ::Vector3::CreateZero();
newBitangent = AZ::Vector3::CreateZero();
const float tangentW = tangents->GetW();
vtxPos.Set (positions->GetX(), positions->GetY(), positions->GetZ());
normal.Set (normals->GetX(), normals->GetY(), normals->GetZ());
tangent.Set (tangents->GetX(), tangents->GetY(), tangents->GetZ());
bitangent.Set(bitangents->GetX(), bitangents->GetY(), bitangents->GetZ());
orgVertex = orgVerts[v];
vtxPos = positions[v];
normal = normals[v];
const float tangentW = tangents[v].GetW();
tangent.Set(tangents[v].GetX(), tangents[v].GetY(), tangents[v].GetZ());
bitangent = bitangents[v];
// process the skin influences for this vertex
const size_t numInfluences = layer->GetNumInfluences(orgVertex);
if (numInfluences > 0)
{
// get the pivot quat, used for the dot product check
const MCore::DualQuaternion& pivotQuat = mBones[ layer->GetInfluence(orgVertex, 0)->GetBoneNr() ].mDualQuat;
const MCore::DualQuaternion& pivotQuat = boneInfos[ layer->GetInfluence(orgVertex, 0)->GetBoneNr() ].mDualQuat;
// our skinning dual quaternion
MCore::DualQuaternion skinQuat(AZ::Quaternion(0, 0, 0, 0), AZ::Quaternion(0, 0, 0, 0));
for (size_t i = 0; i < numInfluences; ++i)
{
// get the influence
influence = layer->GetInfluence(orgVertex, i);
boneInfo = &mBones[ influence->GetBoneNr() ];
weight = influence->GetWeight();
SkinInfluence* influence = layer->GetInfluence(orgVertex, i);
weight = influence->GetWeight();
// check if we need to invert the dual quat
MCore::DualQuaternion& influenceQuat = mBones[ influence->GetBoneNr() ].mDualQuat;
MCore::DualQuaternion influenceQuat = boneInfos[ influence->GetBoneNr() ].mDualQuat;
if (influenceQuat.mReal.Dot(pivotQuat.mReal) < 0.0f)
{
influenceQuat *= -1.0f;
@@ -168,74 +174,50 @@ namespace EMotionFX
skinQuat.Normalize();
// perform skinning
newPos = skinQuat.TransformPoint(vtxPos);
newNormal = skinQuat.TransformVector(normal);
newTangent = skinQuat.TransformVector(tangent);
newBitangent = skinQuat.TransformVector(bitangent);
positions[v] = skinQuat.TransformPoint(vtxPos);
normals[v] = skinQuat.TransformVector(normal);
newTangent = skinQuat.TransformVector(tangent);
tangents[v].Set(newTangent.GetX(), newTangent.GetY(), newTangent.GetZ(), tangentW);
bitangents[v] = skinQuat.TransformVector(bitangent);
}
else
{
// perform the skinning
newPos = vtxPos;
newNormal = normal;
newTangent = tangent;
newBitangent = bitangent;
// no skinning influences, just copy the values
positions[v] = vtxPos;
normals[v] = normal;
newTangent = tangent;
tangents[v].Set(newTangent.GetX(), newTangent.GetY(), newTangent.GetZ(), tangentW);
bitangents[v] = bitangent;
}
// output the skinned values
positions->Set (newPos.GetX(), newPos.GetY(), newPos.GetZ());
positions++;
normals->Set (newNormal.GetX(), newNormal.GetY(), newNormal.GetZ());
normals++;
tangents->Set (newTangent.GetX(), newTangent.GetY(), newTangent.GetZ(), tangentW);
tangents++;
bitangents->Set (newBitangent.GetX(), newBitangent.GetY(), newBitangent.GetZ());
bitangents++;
}
}
else if (tangents && !bitangents) // tangents but no bitangents
{
const uint32 numVertices = mMesh->GetNumVertices();
uint32 v = 0;
uint32 orgVertex;
SkinInfluence* influence;
BoneInfo* boneInfo;
float weight;
for (v = 0; v < numVertices; ++v)
for (AZ::u32 v = startVertex; v < endVertex; ++v)
{
// get the original vertex number
orgVertex = *(orgVerts++);
// reset the skinned position
newPos = AZ::Vector3::CreateZero();
newNormal = AZ::Vector3::CreateZero();
newTangent = AZ::Vector3::CreateZero();
const float tangentW = tangents->GetW();
vtxPos.Set (positions->GetX(), positions->GetY(), positions->GetZ());
normal.Set (normals->GetX(), normals->GetY(), normals->GetZ());
tangent.Set (tangents->GetX(), tangents->GetY(), tangents->GetZ());
orgVertex = orgVerts[v];
vtxPos = positions[v];
normal = normals[v];
const float tangentW = tangents[v].GetW();
tangent.Set(tangents[v].GetX(), tangents[v].GetY(), tangents[v].GetZ());
// process the skin influences for this vertex
const size_t numInfluences = layer->GetNumInfluences(orgVertex);
if (numInfluences > 0)
{
// get the pivot quat, used for the dot product check
const MCore::DualQuaternion& pivotQuat = mBones[ layer->GetInfluence(orgVertex, 0)->GetBoneNr() ].mDualQuat;
const MCore::DualQuaternion& pivotQuat = boneInfos[ layer->GetInfluence(orgVertex, 0)->GetBoneNr() ].mDualQuat;
// our skinning dual quaternion
MCore::DualQuaternion skinQuat(AZ::Quaternion(0, 0, 0, 0), AZ::Quaternion(0, 0, 0, 0));
for (size_t i = 0; i < numInfluences; ++i)
{
// get the influence
influence = layer->GetInfluence(orgVertex, i);
boneInfo = &mBones[ influence->GetBoneNr() ];
weight = influence->GetWeight();
SkinInfluence* influence = layer->GetInfluence(orgVertex, i);
weight = influence->GetWeight();
// check if we need to invert the dual quat
MCore::DualQuaternion& influenceQuat = mBones[ influence->GetBoneNr() ].mDualQuat;
MCore::DualQuaternion influenceQuat = boneInfos[ influence->GetBoneNr() ].mDualQuat;
if (influenceQuat.mReal.Dot(pivotQuat.mReal) < 0.0f)
{
influenceQuat *= -1.0f;
@@ -249,68 +231,46 @@ namespace EMotionFX
skinQuat.Normalize();
// perform skinning
newPos = skinQuat.TransformPoint(vtxPos);
newNormal = skinQuat.TransformVector(normal);
newTangent = skinQuat.TransformVector(tangent);
positions[v] = skinQuat.TransformPoint(vtxPos);
normals[v] = skinQuat.TransformVector(normal);
newTangent = skinQuat.TransformVector(tangent);
tangents[v].Set(newTangent.GetX(), newTangent.GetY(), newTangent.GetZ(), tangentW);
}
else
{
// perform the skinning
newPos = vtxPos;
newNormal = normal;
newTangent = tangent;
// no skinning influences, just copy the values
positions[v] = vtxPos;
normals[v] = normal;
newTangent = tangent;
tangents[v].Set(newTangent.GetX(), newTangent.GetY(), newTangent.GetZ(), tangentW);
}
// output the skinned values
positions->Set (newPos.GetX(), newPos.GetY(), newPos.GetZ());
positions++;
normals->Set (newNormal.GetX(), newNormal.GetY(), newNormal.GetZ());
normals++;
tangents->Set (newTangent.GetX(), newTangent.GetY(), newTangent.GetZ(), tangentW);
tangents++;
}
}
else // there are no tangents and bitangents to skin
{
const uint32 numVertices = mMesh->GetNumVertices();
uint32 v = 0;
uint32 orgVertex;
SkinInfluence* influence;
BoneInfo* boneInfo;
float weight;
for (v = 0; v < numVertices; ++v)
for (AZ::u32 v = startVertex; v < endVertex; ++v)
{
// get the original vertex number
orgVertex = *(orgVerts++);
// reset the skinned position
newPos = AZ::Vector3::CreateZero();
newNormal = AZ::Vector3::CreateZero();
vtxPos.Set(positions->GetX(), positions->GetY(), positions->GetZ());
normal.Set(normals->GetX(), normals->GetY(), normals->GetZ());
orgVertex = orgVerts[v];
vtxPos = positions[v];
normal = normals[v];
// process the skin influences for this vertex
const size_t numInfluences = layer->GetNumInfluences(orgVertex);
if (numInfluences > 0)
{
// get the pivot quat, used for the dot product check
const MCore::DualQuaternion& pivotQuat = mBones[ layer->GetInfluence(orgVertex, 0)->GetBoneNr() ].mDualQuat;
const MCore::DualQuaternion& pivotQuat = boneInfos[ layer->GetInfluence(orgVertex, 0)->GetBoneNr() ].mDualQuat;
// our skinning dual quaternion
MCore::DualQuaternion skinQuat(AZ::Quaternion(0, 0, 0, 0), AZ::Quaternion(0, 0, 0, 0));
for (size_t i = 0; i < numInfluences; ++i)
{
// get the influence
influence = layer->GetInfluence(orgVertex, i);
boneInfo = &mBones[ influence->GetBoneNr() ];
weight = influence->GetWeight();
SkinInfluence* influence = layer->GetInfluence(orgVertex, i);
weight = influence->GetWeight();
// check if we need to invert the dual quat
MCore::DualQuaternion& influenceQuat = mBones[ influence->GetBoneNr() ].mDualQuat;
MCore::DualQuaternion influenceQuat = boneInfos[ influence->GetBoneNr() ].mDualQuat;
if (influenceQuat.mReal.Dot(pivotQuat.mReal) < 0.0f)
{
influenceQuat *= -1.0f;
@@ -324,26 +284,19 @@ namespace EMotionFX
skinQuat.Normalize();
// perform skinning
newPos = skinQuat.TransformPoint(vtxPos);
newNormal = skinQuat.TransformVector(normal);
positions[v] = skinQuat.TransformPoint(vtxPos);
normals[v] = skinQuat.TransformVector(normal);
}
else
{
// perform the skinning
newPos = vtxPos;
newNormal = normal;
// no skinning influences, just copy the values
positions[v] = vtxPos;
normals[v] = normal;
}
// output the skinned values
positions->Set(newPos.GetX(), newPos.GetY(), newPos.GetZ());
positions++;
normals->Set(newNormal.GetX(), newNormal.GetY(), newNormal.GetZ());
normals++;
}
}
}
// initialize the mesh deformer
void DualQuatSkinDeformer::Reinitialize(Actor* actor, Node* node, uint32 lodLevel)
{
@@ -352,7 +305,7 @@ namespace EMotionFX
MCORE_UNUSED(lodLevel);
// clear the bone information array, but don't free the currently allocated/reserved memory
mBones.Clear(false);
m_bones.clear();
// if there is no mesh
if (mMesh == nullptr)
@@ -360,13 +313,9 @@ namespace EMotionFX
return;
}
// get the attribute number
SkinningInfoVertexAttributeLayer* skinningLayer = (SkinningInfoVertexAttributeLayer*)mMesh->FindSharedVertexAttributeLayer(SkinningInfoVertexAttributeLayer::TYPE_ID);
MCORE_ASSERT(skinningLayer);
// reserve space for the bone array
//mBones.Reserve( actor->GetNumNodes() );
// find out what bones this mesh uses
const uint32 numOrgVerts = mMesh->GetNumOrgVertices();
for (uint32 i = 0; i < numOrgVerts; i++)
@@ -379,26 +328,21 @@ namespace EMotionFX
{
SkinInfluence* influence = skinningLayer->GetInfluence(i, a);
// get the bone index in the array
uint32 boneIndex = FindLocalBoneIndex(influence->GetNodeNr());
// if the bone is not found in our array
if (boneIndex == MCORE_INVALIDINDEX32)
AZ::Outcome<size_t> boneIndexOutcome = FindLocalBoneIndex(influence->GetNodeNr());
if (boneIndexOutcome.IsSuccess())
{
influence->SetBoneNr(boneIndexOutcome.GetValue());
}
else
{
// add the bone to the array of bones in this deformer
mBones.AddEmptyExact();
BoneInfo& lastBone = mBones.GetLast();
BoneInfo lastBone;
lastBone.mNodeNr = influence->GetNodeNr();
lastBone.mDualQuat.Identity();
boneIndex = mBones.GetLength() - 1;
m_bones.emplace_back(lastBone);
influence->SetBoneNr(static_cast<AZ::u16>(m_bones.size() - 1));
}
// set the bone number in the influence
influence->SetBoneNr(static_cast<uint16>(boneIndex));
}
}
// get rid of all items in the used bones array
// mBones.Shrink();
}
} // namespace EMotionFX
@@ -12,13 +12,13 @@
#pragma once
// include the required headers
#include <AzCore/std/containers/vector.h>
#include <AzCore/Outcome/Outcome.h>
#include "EMotionFXConfig.h"
#include <MCore/Source/DualQuaternion.h>
#include "Mesh.h"
#include "MeshDeformer.h"
namespace EMotionFX
{
// forward declarations
@@ -101,37 +101,47 @@ namespace EMotionFX
* This is the number of different bones that the skinning information of the mesh where this deformer works on uses.
* @result The number of bones.
*/
MCORE_INLINE uint32 GetNumLocalBones() const { return mBones.GetLength(); }
MCORE_INLINE uint32 GetNumLocalBones() const { return static_cast<uint32>(m_bones.size()); }
/**
* Get the node number of a given local bone.
* @param index The local bone number, which must be in range of [0..GetNumLocalBones()-1].
* @result The node number, which is in range of [0..Actor::GetNumNodes()-1], depending on the actor where this deformer works on.
*/
MCORE_INLINE uint32 GetLocalBone(uint32 index) const { return mBones[index].mNodeNr; }
MCORE_INLINE uint32 GetLocalBone(uint32 index) const { return m_bones[index].mNodeNr; }
/**
* Pre-allocate space for a given number of local bones.
* This does not alter the value returned by GetNumLocalBones().
* @param numBones The number of bones to pre-allocate space for.
*/
MCORE_INLINE void ReserveLocalBones(uint32 numBones) { mBones.Reserve(numBones); }
MCORE_INLINE void ReserveLocalBones(uint32 numBones) { m_bones.reserve(numBones); }
protected:
/**
* Structure used for precalculating the skinning matrices.
* Structure used for pre-calculating the skinning matrices.
*/
struct EMFX_API BoneInfo
{
uint32 mNodeNr; /**< The node number. */
MCore::DualQuaternion mDualQuat; /**< The dual quat of the precalculated matrix that contains the "globalMatrix * inverse(bindPoseMatrix)". */
MCore::DualQuaternion mDualQuat; /**< The dual quat of the pre-calculated matrix that contains the "globalMatrix * inverse(bindPoseMatrix)". */
MCORE_INLINE BoneInfo()
: mNodeNr(MCORE_INVALIDINDEX32) {}
};
AZStd::vector<BoneInfo> m_bones; /**< The array of bone information used for pre-calculation. */
MCore::Array<BoneInfo> mBones; /**< The array of bone information used for precalculation. */
/**
* Skin a part of the mesh.
* @param mesh The mesh to be skinned.
* @param startVertex The start vertex index to start skinning.
* @param endVertex The end vertex index for the range to be skinned.
* @param boneInfos The pre-calculated skinning matrices shared across the skinning process.
*/
static void SkinRange(Mesh* mesh, AZ::u32 startVertex, AZ::u32 endVertex, const AZStd::vector<BoneInfo>& boneInfos);
//! Number of vertices per batch/job used for multi-threaded software skinning.
static constexpr AZ::u32 s_numVerticesPerBatch = 10000;
/**
* Default constructor.
@@ -149,18 +159,6 @@ namespace EMotionFX
* @param nodeIndex The node number to search for.
* @result The index inside the mBones member array, which uses the given node.
*/
MCORE_INLINE uint32 FindLocalBoneIndex(uint32 nodeIndex) const
{
const uint32 numBones = mBones.GetLength();
for (uint32 i = 0; i < numBones; ++i)
{
if (mBones[i].mNodeNr == nodeIndex)
{
return i;
}
}
return MCORE_INVALIDINDEX32;
}
AZ::Outcome<size_t> FindLocalBoneIndex(uint32 nodeIndex) const;
};
} // namespace EMotionFX
@@ -650,7 +650,6 @@ namespace EMotionFX
}
distance = std::numeric_limits<float>::max();
bool isHit = false;
// Get the MCore::Ray used by Mesh::Intersects
// Convert the input source position and direction to a line segment by using the frustum depth as line length.
@@ -659,12 +658,13 @@ namespace EMotionFX
const AZ::Vector3 dest = src + dir * frustumDepth;
const MCore::Ray ray(src, dest);
// Update the mesh deformers so the intersection test will hit the actor if it is being
// animated by a motion component that is previewing the animation in the editor
// Update the mesh deformers (apply software skinning and morphing) so the intersection test will hit the actor
// if it is being animated by a motion component that is previewing the animation in the editor.
m_actorInstance->UpdateMeshDeformers(0.0f, true);
const TransformData* transformData = m_actorInstance->GetTransformData();
const Pose* currentPose = transformData->GetCurrentPose();
bool isHit = false;
// Iterate through the meshes in the actor, looking for the closest hit
const AZ::u32 lodLevel = m_actorInstance->GetLODLevel();