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/NvCloth/Code/Source/Components/ClothComponentMesh/ActorClothSkinning.cpp

530 lines
22 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 <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
#include <Integration/ActorComponentBus.h>
// Needed to access the Mesh information inside Actor.
#include <EMotionFX/Source/TransformData.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <MCore/Source/DualQuaternion.h>
#include <Components/ClothComponentMesh/ActorClothSkinning.h>
#include <Utils/AssetHelper.h>
#include <AzCore/Math/PackedVector3.h>
namespace NvCloth
{
namespace Internal
{
bool ObtainSkinningInfluences(
AZ::EntityId entityId,
const MeshNodeInfo& meshNodeInfo,
const size_t numVertices,
AZStd::vector<SkinningInfluence>& skinningInfluences)
{
AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset;
AZ::Render::MeshComponentRequestBus::EventResult(
modelAsset, entityId, &AZ::Render::MeshComponentRequestBus::Events::GetModelAsset);
if (!modelAsset.IsReady())
{
return false;
}
if (modelAsset->GetLodCount() < meshNodeInfo.m_lodLevel)
{
return false;
}
const AZ::Data::Asset<AZ::RPI::ModelLodAsset>& modelLodAsset = modelAsset->GetLodAssets()[meshNodeInfo.m_lodLevel];
if (!modelLodAsset.GetId().IsValid())
{
return false;
}
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(actorInstance, entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (!actorInstance)
{
return false;
}
const EMotionFX::Actor* actor = actorInstance->GetActor();
if (!actor)
{
return false;
}
const auto& skinToSkeletonIndexMap = actor->GetSkinToSkeletonIndexMap();
size_t numberOfInfluencesPerVertex = 0;
// For each submesh...
for (const auto& subMeshInfo : meshNodeInfo.m_subMeshes)
{
if (modelLodAsset->GetMeshes().size() < subMeshInfo.m_primitiveIndex)
{
AZ_Error("ActorClothSkinning", false,
"Unable to access submesh %d from lod asset '%s' as it only has %d submeshes.",
subMeshInfo.m_primitiveIndex,
modelAsset.GetHint().c_str(),
modelLodAsset->GetMeshes().size());
return false;
}
const AZ::RPI::ModelLodAsset::Mesh& subMesh = modelLodAsset->GetMeshes()[subMeshInfo.m_primitiveIndex];
const auto sourcePositions = subMesh.GetSemanticBufferTyped<AZ::PackedVector3f>(AZ::Name("POSITION"));
if (sourcePositions.size() != subMeshInfo.m_numVertices)
{
AZ_Error("ActorClothSkinning", false,
"Number of vertices (%zu) in submesh %d doesn't match the cloth's submesh (%d)",
sourcePositions.size(), subMeshInfo.m_primitiveIndex, subMeshInfo.m_numVertices);
return false;
}
const auto sourceSkinJointIndices = subMesh.GetSemanticBufferTyped<uint16_t>(AZ::Name("SKIN_JOINTINDICES"));
const auto sourceSkinWeights = subMesh.GetSemanticBufferTyped<float>(AZ::Name("SKIN_WEIGHTS"));
if (sourceSkinJointIndices.empty() || sourceSkinWeights.empty())
{
// Ignoring skinning when there is no skin data.
// All submeshes will either have or not have skin data, since they come from the same mesh.
return false;
}
AZ_Assert(sourceSkinJointIndices.size() == sourceSkinWeights.size(),
"Size of skin joint indices buffer (%zu) different from skin weights buffer (%zu)",
sourceSkinJointIndices.size(), sourceSkinWeights.size());
const size_t subMeshInfluenceCount = sourceSkinWeights.size() / sourcePositions.size();
AZ_Assert(subMeshInfluenceCount > 0,
"Submesh %d skinning data has zero joint influences per vertex.",
subMeshInfo.m_primitiveIndex);
if (numberOfInfluencesPerVertex == 0)
{
// Resize only in the first loop once we know the number of influences per vertex.
// The other submeshes should match the number of influences.
numberOfInfluencesPerVertex = subMeshInfluenceCount;
skinningInfluences.resize(numVertices * numberOfInfluencesPerVertex);
}
else if (subMeshInfluenceCount != numberOfInfluencesPerVertex)
{
AZ_Error("ActorClothSkinning", false,
"Submesh %d number of influences (%d) is different from a previous submesh (%d).",
subMeshInfo.m_primitiveIndex,
subMeshInfluenceCount,
numberOfInfluencesPerVertex);
return false;
}
for (int vertexIndex = 0; vertexIndex < subMeshInfo.m_numVertices; ++vertexIndex)
{
const size_t subMeshVertexIndex = vertexIndex * numberOfInfluencesPerVertex;
const size_t meshVertexIndex = (subMeshInfo.m_verticesFirstIndex + vertexIndex) * numberOfInfluencesPerVertex;
for (size_t influenceIndex = 0; influenceIndex < numberOfInfluencesPerVertex; ++influenceIndex)
{
const size_t subMeshVertexInfluenceIndex = subMeshVertexIndex + influenceIndex;
const size_t meshVertexInfluenceIndex = meshVertexIndex + influenceIndex;
const AZ::u16 jointIndex = sourceSkinJointIndices[subMeshVertexInfluenceIndex];
const float weight = sourceSkinWeights[subMeshVertexInfluenceIndex];
auto skeletonIndexIt = skinToSkeletonIndexMap.find(jointIndex);
if (skeletonIndexIt == skinToSkeletonIndexMap.end())
{
AZ_Error("ActorClothSkinning", false,
"Joint index %d from model asset not found in map to skeleton indices",
jointIndex);
return false;
}
skinningInfluences[meshVertexInfluenceIndex].m_jointIndex = skeletonIndexIt->second;
skinningInfluences[meshVertexInfluenceIndex].m_jointWeight = weight;
}
}
}
return true;
}
EMotionFX::Integration::SkinningMethod ObtainSkinningMethod(AZ::EntityId entityId)
{
EMotionFX::Integration::SkinningMethod skinningMethod =
EMotionFX::Integration::SkinningMethod::DualQuat;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(skinningMethod, entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetSkinningMethod);
return skinningMethod;
}
const AZ::Matrix3x4* ObtainSkinningMatrices(AZ::EntityId entityId)
{
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(actorInstance, entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (!actorInstance)
{
return nullptr;
}
const EMotionFX::TransformData* transformData = actorInstance->GetTransformData();
if (!transformData)
{
return nullptr;
}
return transformData->GetSkinningMatrices();
}
AZStd::unordered_map<AZ::u16, MCore::DualQuaternion> ObtainSkinningDualQuaternions(
AZ::EntityId entityId,
const AZStd::vector<AZ::u16>& jointIndices)
{
const AZ::Matrix3x4* skinningMatrices = ObtainSkinningMatrices(entityId);
if (!skinningMatrices)
{
return {};
}
AZStd::unordered_map<AZ::u16, MCore::DualQuaternion> skinningDualQuaternions;
for (AZ::u16 jointIndex : jointIndices)
{
skinningDualQuaternions.emplace(jointIndex, MCore::DualQuaternion(AZ::Transform::CreateFromMatrix3x4(skinningMatrices[jointIndex])));
}
return skinningDualQuaternions;
}
}
// Specialized class that applies linear blending skinning
class ActorClothSkinningLinear
: public ActorClothSkinning
{
public:
explicit ActorClothSkinningLinear(AZ::EntityId entityId)
: ActorClothSkinning(entityId)
{
}
// ActorClothSkinning overrides ...
void UpdateSkinning() override;
void ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions) override;
void ApplySkinningOnNonSimulatedVertices(
const MeshClothInfo& originalData,
ClothComponentMesh::RenderData& renderData) override;
private:
AZ::Matrix3x4 ComputeVertexSkinnningTransform(AZ::u32 vertexIndex);
const AZ::Matrix3x4* m_skinningMatrices = nullptr;
inline static const AZ::Matrix3x4 s_zeroMatrix3x4 = AZ::Matrix3x4::CreateZero();
};
void ActorClothSkinningLinear::UpdateSkinning()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_skinningMatrices = Internal::ObtainSkinningMatrices(m_entityId);
}
void ActorClothSkinningLinear::ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions)
{
if (!m_skinningMatrices ||
originalPositions.empty() ||
originalPositions.size() != positions.size() ||
originalPositions.size() != m_simulatedVertices.size())
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
const size_t vertexCount = m_simulatedVertices.size();
for (size_t index = 0; index < vertexCount; ++index)
{
const AZ::Matrix3x4 vertexSkinningTransform = ComputeVertexSkinnningTransform(m_simulatedVertices[index]);
const AZ::Vector3 skinnedPosition = vertexSkinningTransform * originalPositions[index].GetAsVector3();
positions[index].Set(skinnedPosition, positions[index].GetW()); // Avoid overwriting the w component
}
}
void ActorClothSkinningLinear::ApplySkinningOnNonSimulatedVertices(
const MeshClothInfo& originalData,
ClothComponentMesh::RenderData& renderData)
{
if (!m_skinningMatrices ||
originalData.m_particles.empty() ||
originalData.m_particles.size() != renderData.m_particles.size() ||
originalData.m_particles.size() != m_skinningInfluences.size() / m_numberOfInfluencesPerVertex)
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
for (const AZ::u32 index : m_nonSimulatedVertices)
{
const AZ::Matrix3x4 vertexSkinningTransform = ComputeVertexSkinnningTransform(index);
const AZ::Vector3 skinnedPosition = vertexSkinningTransform * originalData.m_particles[index].GetAsVector3();
renderData.m_particles[index].Set(skinnedPosition, renderData.m_particles[index].GetW()); // Avoid overwriting the w component
// Calculate the reciprocal scale version of the matrix to transform the normals.
// Note: This operation is not strictly equivalent to the full inverse transpose when the matrix's
// basis vectors are not perpendicular, which is the case blending linearly the matrices.
// This is a fast approximation, which is also done by the GPU skinning shader.
const AZ::Matrix3x4 vertexSkinningTransformReciprocalScale = vertexSkinningTransform.GetReciprocalScaled();
renderData.m_normals[index] = vertexSkinningTransformReciprocalScale.TransformVector(originalData.m_normals[index]).GetNormalized();
// Tangents and Bitangents are recalculated immediately after this call
// by cloth mesh component, so there is no need to transform them here.
}
}
AZ::Matrix3x4 ActorClothSkinningLinear::ComputeVertexSkinnningTransform(AZ::u32 vertexIndex)
{
AZ::Matrix3x4 vertexSkinningTransform = s_zeroMatrix3x4;
for (size_t influenceIndex = 0; influenceIndex < m_numberOfInfluencesPerVertex; ++influenceIndex)
{
const size_t vertexInfluenceIndex = vertexIndex * m_numberOfInfluencesPerVertex + influenceIndex;
const AZ::u16 jointIndex = m_skinningInfluences[vertexInfluenceIndex].m_jointIndex;
const float jointWeight = m_skinningInfluences[vertexInfluenceIndex].m_jointWeight;
// Blending matrices the same way done in GPU shaders, by adding each weighted matrix element by element.
// This operation results in a non orthogonal matrix, but it's done this way because it's fast to perform.
vertexSkinningTransform += m_skinningMatrices[jointIndex] * jointWeight;
}
return vertexSkinningTransform;
}
// Specialized class that applies dual quaternion blending skinning
class ActorClothSkinningDualQuaternion
: public ActorClothSkinning
{
public:
explicit ActorClothSkinningDualQuaternion(AZ::EntityId entityId)
: ActorClothSkinning(entityId)
{
}
// ActorClothSkinning overrides ...
void UpdateSkinning() override;
void ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions) override;
void ApplySkinningOnNonSimulatedVertices(
const MeshClothInfo& originalData,
ClothComponentMesh::RenderData& renderData) override;
private:
MCore::DualQuaternion ComputeVertexSkinnningTransform(AZ::u32 vertexIndex);
AZStd::unordered_map<AZ::u16, MCore::DualQuaternion> m_skinningDualQuaternions;
inline static const MCore::DualQuaternion s_zeroDualQuaternion = MCore::DualQuaternion(AZ::Quaternion::CreateZero(), AZ::Quaternion::CreateZero());
};
void ActorClothSkinningDualQuaternion::UpdateSkinning()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_skinningDualQuaternions = Internal::ObtainSkinningDualQuaternions(m_entityId, m_jointIndices);
}
void ActorClothSkinningDualQuaternion::ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions)
{
if (m_skinningDualQuaternions.empty() ||
originalPositions.empty() ||
originalPositions.size() != positions.size() ||
originalPositions.size() != m_simulatedVertices.size())
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
const size_t vertexCount = m_simulatedVertices.size();
for (size_t index = 0; index < vertexCount; ++index)
{
const MCore::DualQuaternion vertexSkinningTransform = ComputeVertexSkinnningTransform(m_simulatedVertices[index]);
const AZ::Vector3 skinnedPosition = vertexSkinningTransform.TransformPoint(originalPositions[index].GetAsVector3());
positions[index].Set(skinnedPosition, positions[index].GetW()); // Avoid overwriting the w component
}
}
void ActorClothSkinningDualQuaternion::ApplySkinningOnNonSimulatedVertices(
const MeshClothInfo& originalData,
ClothComponentMesh::RenderData& renderData)
{
if (m_skinningDualQuaternions.empty() ||
originalData.m_particles.empty() ||
originalData.m_particles.size() != renderData.m_particles.size() ||
originalData.m_particles.size() != m_skinningInfluences.size() / m_numberOfInfluencesPerVertex)
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
for (const AZ::u32 index : m_nonSimulatedVertices)
{
const MCore::DualQuaternion vertexSkinningTransform = ComputeVertexSkinnningTransform(index);
const AZ::Vector3 skinnedPosition = vertexSkinningTransform.TransformPoint(originalData.m_particles[index].GetAsVector3());
renderData.m_particles[index].Set(skinnedPosition, renderData.m_particles[index].GetW()); // Avoid overwriting the w component
// ComputeVertexSkinnningTransform is normalizing the blended dual quaternion. This means the dual
// quaternion will not have any scale and there is no need to compute the reciprocal scale version
// for transforming normals.
// Note: The GPU skinning shader does the same operation.
renderData.m_normals[index] = vertexSkinningTransform.TransformVector(originalData.m_normals[index]).GetNormalized();
// Tangents and Bitangents are recalculated immediately after this call
// by cloth mesh component, so there is no need to transform them here.
}
}
MCore::DualQuaternion ActorClothSkinningDualQuaternion::ComputeVertexSkinnningTransform(AZ::u32 vertexIndex)
{
MCore::DualQuaternion vertexSkinningTransform = s_zeroDualQuaternion;
for (size_t influenceIndex = 0; influenceIndex < m_numberOfInfluencesPerVertex; ++influenceIndex)
{
const size_t vertexInfluenceIndex = vertexIndex * m_numberOfInfluencesPerVertex + influenceIndex;
const AZ::u16 jointIndex = m_skinningInfluences[vertexInfluenceIndex].m_jointIndex;
const float jointWeight = m_skinningInfluences[vertexInfluenceIndex].m_jointWeight;
const MCore::DualQuaternion& skinningDualQuaternion = m_skinningDualQuaternions.at(jointIndex);
float flip = AZ::GetSign(vertexSkinningTransform.mReal.Dot(skinningDualQuaternion.mReal));
vertexSkinningTransform += skinningDualQuaternion * jointWeight * flip;
}
// Normalizing the dual quaternion as the GPU shaders do. This will remove the scale from the transform.
vertexSkinningTransform.Normalize();
return vertexSkinningTransform;
}
AZStd::unique_ptr<ActorClothSkinning> ActorClothSkinning::Create(
AZ::EntityId entityId,
const MeshNodeInfo& meshNodeInfo,
const AZStd::vector<SimParticleFormat>& originalMeshParticles,
const size_t numSimulatedVertices,
const AZStd::vector<int>& meshRemappedVertices)
{
const size_t numVertices = originalMeshParticles.size();
AZStd::vector<SkinningInfluence> skinningInfluences;
if (!Internal::ObtainSkinningInfluences(entityId, meshNodeInfo, numVertices, skinningInfluences))
{
return nullptr;
}
AZStd::unique_ptr<ActorClothSkinning> actorClothSkinning;
const auto skinningMethod = Internal::ObtainSkinningMethod(entityId);
switch (skinningMethod)
{
case EMotionFX::Integration::SkinningMethod::DualQuat:
actorClothSkinning = AZStd::make_unique<ActorClothSkinningDualQuaternion>(entityId);
break;
case EMotionFX::Integration::SkinningMethod::Linear:
actorClothSkinning = AZStd::make_unique<ActorClothSkinningLinear>(entityId);
break;
default:
AZ_Error("ActorClothSkinning", false,
"Unknown skinning method (%u).", static_cast<AZ::u32>(skinningMethod));
return nullptr;
}
actorClothSkinning->m_numberOfInfluencesPerVertex = skinningInfluences.size() / numVertices;
if (actorClothSkinning->m_numberOfInfluencesPerVertex == 0)
{
AZ_Error("ActorClothSkinning", false,
"Number of skinning joint influences per vertex is zero.");
return nullptr;
}
// Collect all indices of the joints that influence the vertices
AZStd::set<AZ::u16> jointIndices;
for (const auto& skinningInfluence : skinningInfluences)
{
jointIndices.insert(skinningInfluence.m_jointIndex);
}
actorClothSkinning->m_jointIndices.assign(jointIndices.begin(), jointIndices.end());
// Collect the indices for simulated and non-simulated vertices
actorClothSkinning->m_simulatedVertices.resize(numSimulatedVertices);
actorClothSkinning->m_nonSimulatedVertices.reserve(numVertices);
for (size_t vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex)
{
const int remappedIndex = meshRemappedVertices[vertexIndex];
if (remappedIndex >= 0)
{
actorClothSkinning->m_simulatedVertices[remappedIndex] = vertexIndex;
}
if (remappedIndex < 0 ||
originalMeshParticles[vertexIndex].GetW() == 0.0f)
{
actorClothSkinning->m_nonSimulatedVertices.emplace_back(vertexIndex);
}
}
actorClothSkinning->m_nonSimulatedVertices.shrink_to_fit();
actorClothSkinning->m_skinningInfluences = AZStd::move(skinningInfluences);
return actorClothSkinning;
}
ActorClothSkinning::ActorClothSkinning(AZ::EntityId entityId)
: m_entityId(entityId)
{
}
void ActorClothSkinning::UpdateActorVisibility()
{
bool isVisible = true;
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(actorInstance, m_entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (actorInstance)
{
isVisible = actorInstance->GetIsVisible();
}
m_wasActorVisible = m_isActorVisible;
m_isActorVisible = isVisible;
}
bool ActorClothSkinning::IsActorVisible() const
{
return m_isActorVisible;
}
bool ActorClothSkinning::WasActorVisible() const
{
return m_wasActorVisible;
}
} // namespace NvCloth