From 893d4208c0ef1f529f71c64794c85e98841e759b Mon Sep 17 00:00:00 2001 From: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Date: Mon, 3 May 2021 18:48:30 +0200 Subject: [PATCH] [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. --- .../Code/EMotionFX/Source/ActorInstance.cpp | 8 +- .../EMotionFX/Source/DualQuatSkinDeformer.cpp | 306 +++++++----------- .../EMotionFX/Source/DualQuatSkinDeformer.h | 42 ++- .../Components/EditorActorComponent.cpp | 6 +- 4 files changed, 153 insertions(+), 209 deletions(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp index cfacdf9f09..ec1b00bda4 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp @@ -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) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp index 73cbad195a..27cf34961f 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp @@ -10,7 +10,8 @@ * */ -// include the required headers +#include +#include #include "EMotionFXConfig.h" #include "DualQuatSkinDeformer.h" #include "Mesh.h" @@ -23,138 +24,143 @@ #include #include - 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 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(mMesh->FindVertexData(Mesh::ATTRIB_POSITIONS)); - AZ::Vector3* normals = static_cast(mMesh->FindVertexData(Mesh::ATTRIB_NORMALS)); - AZ::Vector4* tangents = static_cast(mMesh->FindVertexData(Mesh::ATTRIB_TANGENTS)); - AZ::Vector3* bitangents = static_cast(mMesh->FindVertexData(Mesh::ATTRIB_BITANGENTS)); - AZ::u32* orgVerts = static_cast(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(numVertices) / aznumeric_cast(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& 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(mesh->FindVertexData(Mesh::ATTRIB_POSITIONS)); + AZ::Vector3* normals = static_cast(mesh->FindVertexData(Mesh::ATTRIB_NORMALS)); + AZ::Vector4* tangents = static_cast(mesh->FindVertexData(Mesh::ATTRIB_TANGENTS)); + AZ::Vector3* bitangents = static_cast(mesh->FindVertexData(Mesh::ATTRIB_BITANGENTS)); + AZ::u32* orgVerts = static_cast(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 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(m_bones.size() - 1)); } - - // set the bone number in the influence - influence->SetBoneNr(static_cast(boneIndex)); } } - - // get rid of all items in the used bones array - // mBones.Shrink(); } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h index fc80256524..ec00f1186f 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h @@ -12,13 +12,13 @@ #pragma once -// include the required headers +#include +#include #include "EMotionFXConfig.h" #include #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(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 m_bones; /**< The array of bone information used for pre-calculation. */ - MCore::Array 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& 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 FindLocalBoneIndex(uint32 nodeIndex) const; }; } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp index 3aa281c4c2..bcb1e3f300 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp @@ -650,7 +650,6 @@ namespace EMotionFX } distance = std::numeric_limits::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();