[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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user