5fd8c35878
- Fixing cloth working with Actors by reading directly from ModelAsset instead from EMFX Mesh. - Actor caches map from joint indices in skin metadata to skeleton indices so they can be query later. - Actor cloth skinning reads indices and weights from Model Asset instead from EMFX Mesh. - Actor cloth skinning with unlimited skinning bones. - Sort out cloth unit tests by disabling them until there is a way to create an Atom mesh - Addressing feedback.
456 lines
17 KiB
C++
456 lines
17 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include <Cry_Math.h> // Needed for DualQuat
|
|
#include <MathConversion.h>
|
|
|
|
#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 <Components/ClothComponentMesh/ActorClothSkinning.h>
|
|
#include <Utils/AssetHelper.h>
|
|
|
|
#include <AzCore/Math/PackedVector3.h>
|
|
|
|
namespace NvCloth
|
|
{
|
|
namespace Internal
|
|
{
|
|
bool ObtainSkinningData(
|
|
AZ::EntityId entityId,
|
|
const MeshNodeInfo& meshNodeInfo,
|
|
const size_t numSimParticles,
|
|
const AZStd::vector<int>& meshRemappedVertices,
|
|
AZStd::vector<SkinningInfo>& skinningData)
|
|
{
|
|
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();
|
|
|
|
skinningData.resize(numSimParticles);
|
|
|
|
// 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())
|
|
{
|
|
continue;
|
|
}
|
|
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 influenceCount = sourceSkinWeights.size() / sourcePositions.size();
|
|
if (influenceCount == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int vertexIndex = 0; vertexIndex < subMeshInfo.m_numVertices; ++vertexIndex)
|
|
{
|
|
const int skinnedDataIndex = meshRemappedVertices[subMeshInfo.m_verticesFirstIndex + vertexIndex];
|
|
if (skinnedDataIndex < 0)
|
|
{
|
|
// Removed particle
|
|
continue;
|
|
}
|
|
|
|
SkinningInfo& skinningInfo = skinningData[skinnedDataIndex];
|
|
skinningInfo.m_jointIndices.resize(influenceCount);
|
|
skinningInfo.m_jointWeights.resize(influenceCount);
|
|
|
|
for (size_t influenceIndex = 0; influenceIndex < influenceCount; ++influenceIndex)
|
|
{
|
|
const AZ::u16 jointIndex = sourceSkinJointIndices[vertexIndex * influenceCount + influenceIndex];
|
|
const float weight = sourceSkinWeights[vertexIndex * influenceCount + influenceIndex];
|
|
|
|
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;
|
|
}
|
|
|
|
skinningInfo.m_jointIndices[influenceIndex] = skeletonIndexIt->second;
|
|
skinningInfo.m_jointWeights[influenceIndex] = 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, DualQuat> ObtainSkinningDualQuaternions(
|
|
AZ::EntityId entityId,
|
|
const AZStd::vector<AZ::u16>& jointIndices)
|
|
{
|
|
const AZ::Matrix3x4* skinningMatrices = ObtainSkinningMatrices(entityId);
|
|
if (!skinningMatrices)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
AZStd::unordered_map<AZ::u16, DualQuat> skinningDualQuaternions;
|
|
for (AZ::u16 jointIndex : jointIndices)
|
|
{
|
|
skinningDualQuaternions.emplace(jointIndex, AZMatrix3x4ToLYMatrix3x4(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;
|
|
|
|
private:
|
|
AZ::Vector3 ComputeSkinnedPosition(
|
|
const AZ::Vector3& originalPosition,
|
|
const SkinningInfo& skinningInfo,
|
|
const AZ::Matrix3x4* skinningMatrices);
|
|
|
|
const AZ::Matrix3x4* m_skinningMatrices = nullptr;
|
|
};
|
|
|
|
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)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
|
|
|
|
for (size_t index = 0; index < originalPositions.size(); ++index)
|
|
{
|
|
const AZ::Vector3 skinnedPosition = ComputeSkinnedPosition(
|
|
originalPositions[index].GetAsVector3(),
|
|
m_skinningData[index],
|
|
m_skinningMatrices);
|
|
|
|
// Avoid overwriting the w component
|
|
positions[index].Set(skinnedPosition, positions[index].GetW());
|
|
}
|
|
}
|
|
|
|
AZ::Vector3 ActorClothSkinningLinear::ComputeSkinnedPosition(
|
|
const AZ::Vector3& originalPosition,
|
|
const SkinningInfo& skinningInfo,
|
|
const AZ::Matrix3x4* skinningMatrices)
|
|
{
|
|
AZ::Matrix3x4 clothSkinningMatrix = AZ::Matrix3x4::CreateZero();
|
|
for (size_t weightIndex = 0; weightIndex < skinningInfo.m_jointWeights.size(); ++weightIndex)
|
|
{
|
|
const AZ::u16 jointIndex = skinningInfo.m_jointIndices[weightIndex];
|
|
const float jointWeight = skinningInfo.m_jointWeights[weightIndex];
|
|
|
|
if (AZ::IsClose(jointWeight, 0.0f))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Blending matrices the same way done in GPU shaders, by adding each weighted matrix element by element.
|
|
// This way the skinning results are much similar to the skinning performed in GPU.
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
clothSkinningMatrix.SetRow(i, clothSkinningMatrix.GetRow(i) + skinningMatrices[jointIndex].GetRow(i) * jointWeight);
|
|
}
|
|
}
|
|
|
|
return clothSkinningMatrix * originalPosition;
|
|
}
|
|
|
|
// 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;
|
|
|
|
private:
|
|
AZ::Vector3 ComputeSkinnedPosition(
|
|
const AZ::Vector3& originalPosition,
|
|
const SkinningInfo& skinningInfo,
|
|
const AZStd::unordered_map<AZ::u16, DualQuat>& skinningDualQuaternions);
|
|
|
|
AZStd::unordered_map<AZ::u16, DualQuat> m_skinningDualQuaternions;
|
|
};
|
|
|
|
|
|
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())
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
|
|
|
|
for (size_t index = 0; index < originalPositions.size(); ++index)
|
|
{
|
|
const AZ::Vector3 skinnedPosition = ComputeSkinnedPosition(
|
|
originalPositions[index].GetAsVector3(),
|
|
m_skinningData[index],
|
|
m_skinningDualQuaternions);
|
|
|
|
// Avoid overwriting the w component
|
|
positions[index].Set(skinnedPosition, positions[index].GetW());
|
|
}
|
|
}
|
|
|
|
AZ::Vector3 ActorClothSkinningDualQuaternion::ComputeSkinnedPosition(
|
|
const AZ::Vector3& originalPosition,
|
|
const SkinningInfo& skinningInfo,
|
|
const AZStd::unordered_map<AZ::u16, DualQuat>& skinningDualQuaternions)
|
|
{
|
|
DualQuat clothSkinningDualQuaternion(type_zero::ZERO);
|
|
for (size_t weightIndex = 0; weightIndex < skinningInfo.m_jointWeights.size(); ++weightIndex)
|
|
{
|
|
const AZ::u16 jointIndex = skinningInfo.m_jointIndices[weightIndex];
|
|
const float jointWeight = skinningInfo.m_jointWeights[weightIndex];
|
|
|
|
if (AZ::IsClose(jointWeight, 0.0f))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
clothSkinningDualQuaternion += skinningDualQuaternions.at(jointIndex) * jointWeight;
|
|
}
|
|
clothSkinningDualQuaternion.Normalize();
|
|
|
|
return LYVec3ToAZVec3(clothSkinningDualQuaternion * AZVec3ToLYVec3(originalPosition));
|
|
}
|
|
|
|
AZStd::unique_ptr<ActorClothSkinning> ActorClothSkinning::Create(
|
|
AZ::EntityId entityId,
|
|
const MeshNodeInfo& meshNodeInfo,
|
|
const size_t numSimParticles,
|
|
const AZStd::vector<int>& meshRemappedVertices)
|
|
{
|
|
AZStd::vector<SkinningInfo> skinningData;
|
|
if (!Internal::ObtainSkinningData(entityId, meshNodeInfo, numSimParticles, meshRemappedVertices, skinningData))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (numSimParticles != skinningData.size())
|
|
{
|
|
AZ_Error("ActorClothSkinning", false,
|
|
"Number of simulation particles (%zu) doesn't match with skinning data obtained (%zu)",
|
|
numSimParticles, skinningData.size());
|
|
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;
|
|
}
|
|
|
|
// Insert the indices of the joints that influence the particle (weight is not 0)
|
|
AZStd::set<AZ::u16> jointIndices;
|
|
for (size_t particleIndex = 0; particleIndex < numSimParticles; ++particleIndex)
|
|
{
|
|
const SkinningInfo& skinningInfo = skinningData[particleIndex];
|
|
for (size_t weightIndex = 0; weightIndex < skinningInfo.m_jointWeights.size(); ++weightIndex)
|
|
{
|
|
const AZ::u16 jointIndex = skinningInfo.m_jointIndices[weightIndex];
|
|
const float jointWeight = skinningInfo.m_jointWeights[weightIndex];
|
|
|
|
if (AZ::IsClose(jointWeight, 0.0f))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
jointIndices.insert(jointIndex);
|
|
}
|
|
}
|
|
actorClothSkinning->m_jointIndices.assign(jointIndices.begin(), jointIndices.end());
|
|
|
|
actorClothSkinning->m_skinningData = AZStd::move(skinningData);
|
|
|
|
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
|