Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,649 @@
/*
* 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 <ActorAsset.h>
#include <AtomActorInstance.h>
#include <EMotionFX/Source/TransformData.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/MorphSetup.h>
#include <EMotionFX/Source/MorphTargetStandard.h>
#include <EMotionFX/Source/SubMesh.h>
#include <EMotionFX/Source/SkinningInfoVertexAttributeLayer.h>
#include <MCore/Source/DualQuaternion.h>
// For creating a skinned mesh from an actor
#include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>
#include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
#include <Atom/RPI.Public/Model/Model.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/base.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/PackedVector3.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Matrix3x4.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Component/Entity.h>
// Copied from ModelAssetBuilderComponent.cpp
namespace
{
const AZ::u32 IndicesPerFace = 3;
const AZ::RHI::Format IndicesFormat = AZ::RHI::Format::R32_UINT;
const AZ::u32 PositionFloatsPerVert = 3;
const AZ::u32 NormalFloatsPerVert = 3;
const AZ::u32 UVFloatsPerVert = 2;
const AZ::u32 ColorFloatsPerVert = 4;
const AZ::u32 TangentFloatsPerVert = 4;
const AZ::u32 BitangentFloatsPerVert = 3;
const AZ::RHI::Format PositionFormat = AZ::RHI::Format::R32G32B32_FLOAT;
const AZ::RHI::Format NormalFormat = AZ::RHI::Format::R32G32B32_FLOAT;
const AZ::RHI::Format UVFormat = AZ::RHI::Format::R32G32_FLOAT;
const AZ::RHI::Format ColorFormat = AZ::RHI::Format::R32G32B32A32_FLOAT;
const AZ::RHI::Format TangentFormat = AZ::RHI::Format::R32G32B32A32_FLOAT;
const AZ::RHI::Format BitangentFormat = AZ::RHI::Format::R32G32B32_FLOAT;
const AZ::RHI::Format BoneIndexFormat = AZ::RHI::Format::R32G32B32A32_UINT;
const AZ::RHI::Format BoneWeightFormat = AZ::RHI::Format::R32G32B32A32_FLOAT;
const size_t LinearSkinningFloatsPerBone = 12;
const size_t DualQuaternionSkinningFloatsPerBone = 8;
const uint32_t MaxSupportedSkinInfluences = 4;
}
namespace AZ
{
namespace Render
{
// Helper function for building buffers
static Data::Asset<RPI::BufferAsset> BuildInputAssemblyBuffer(const void* rawData, const RHI::BufferViewDescriptor& viewDescriptor, RHI::BufferBindFlags bindFlags = RHI::BufferBindFlags::InputAssembly)
{
const AZ::u32 bufferSize = viewDescriptor.m_elementCount * viewDescriptor.m_elementSize;
Data::Asset<RPI::ResourcePoolAsset> bufferPoolAsset;
{
auto bufferPoolDesc = AZStd::make_unique<RHI::BufferPoolDescriptor>();
bufferPoolDesc->m_bindFlags = bindFlags;
bufferPoolDesc->m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
RPI::ResourcePoolAssetCreator creator;
creator.Begin(Uuid::CreateRandom());
creator.SetPoolDescriptor(AZStd::move(bufferPoolDesc));
creator.SetPoolName("ActorPool");
creator.End(bufferPoolAsset);
}
Data::Asset<RPI::BufferAsset> asset;
{
RHI::BufferDescriptor bufferDescriptor;
bufferDescriptor.m_bindFlags = bindFlags;
bufferDescriptor.m_byteCount = bufferSize;
RPI::BufferAssetCreator creator;
creator.Begin(Uuid::CreateRandom());
creator.SetPoolAsset(bufferPoolAsset);
creator.SetBuffer(rawData, bufferDescriptor.m_byteCount, bufferDescriptor);
creator.SetBufferViewDescriptor(viewDescriptor);
creator.End(asset);
}
return AZStd::move(asset);
}
//// Helper function for adding buffers to a modelLodCreator
//static void CreateAndAddMeshStreamBufferToLOD(RPI::ModelLodAssetCreator& modelLodCreator, size_t count, const void* data, const RHI::Format& format, const RHI::ShaderSemantic& semantic, RHI::BufferBindFlags bindFlags = RHI::BufferBindFlags::InputAssembly)
//{
// RHI::BufferViewDescriptor viewDescriptor = RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(count), format);
// Data::Asset<RPI::BufferAsset> buffer = BuildInputAssemblyBuffer(data, viewDescriptor, bindFlags);
// modelLodCreator.AddMeshStreamBuffer(semantic, AZ::Name(), { buffer, viewDescriptor });
//}
//static Data::Asset<RPI::MaterialAsset> GetDefaultMaterialAsset()
//{
// // Get the default material
// Data::AssetId defaultMaterialId;
// AZ::Data::AssetCatalogRequestBus::BroadcastResult(
// defaultMaterialId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
// "Materials/Default.azmaterial", azrtti_typeid<AZ::RPI::MaterialAsset>(), false
// );
// // Create a material asset
// Data::Asset<RPI::MaterialAsset> materialAsset;
// materialAsset.Create(defaultMaterialId, true);
// return materialAsset;
//}
static bool IsVertexCountWithinSupportedRange(size_t vertexOffset, size_t vertexCount)
{
return vertexOffset + vertexCount <= aznumeric_cast<size_t>(SkinnedMeshVertexStreamPropertyInterface::Get()->GetMaxSupportedVertexCount());
}
static void CalculateSubmeshPropertiesForLod(const Data::AssetId& actorAssetId, const EMotionFX::Actor* actor, size_t lodIndex, size_t numJoints, AZStd::vector<SkinnedSubMeshProperties>& subMeshes, uint32_t& lodIndexCount, uint32_t& lodVertexCount)
{
lodIndexCount = 0;
lodVertexCount = 0;
uint32_t subMeshIndexOffset = 0;
for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex)
{
const EMotionFX::Mesh* mesh = actor->GetMesh(lodIndex, jointIndex);
if (!mesh || mesh->GetIsCollisionMesh())
{
continue;
}
const size_t numSubMeshes = mesh->GetNumSubMeshes();
for (size_t subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
{
const EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex);
const size_t subMeshIndexCount = subMesh->GetNumIndices();
const size_t subMeshVertexCount = subMesh->GetNumVertices();
if (subMeshVertexCount > 0)
{
if (IsVertexCountWithinSupportedRange(lodVertexCount, subMeshVertexCount))
{
SkinnedSubMeshProperties skinnedSubMesh{};
skinnedSubMesh.m_indexOffset = lodIndexCount;
skinnedSubMesh.m_indexCount = aznumeric_cast<uint32_t>(subMeshIndexCount);
lodIndexCount += aznumeric_cast<uint32_t>(subMeshIndexCount);
skinnedSubMesh.m_vertexOffset = lodVertexCount;
skinnedSubMesh.m_vertexCount = aznumeric_cast<uint32_t>(subMeshVertexCount);
lodVertexCount += aznumeric_cast<uint32_t>(subMeshVertexCount);
// The default material id used by a sub-mesh is the guid of the source .fbx plus the subId which is a unique material ID from the scene API
AZ::u32 subId = subMesh->GetMaterial();
AZ::Data::AssetId materialId{ actorAssetId.m_guid, subId };
// Queue the material asset - the ModelLod seems to handle delayed material loads
skinnedSubMesh.m_material = Data::AssetManager::Instance().GetAsset(materialId, azrtti_typeid<RPI::MaterialAsset>(), skinnedSubMesh.m_material.GetAutoLoadBehavior());
subMeshes.push_back(skinnedSubMesh);
}
else
{
AZStd::string assetPath;
Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, actorAssetId);
AZ_Error("ActorAsset", false, "Lod '%d' for actor '%s' has greater than %d, the maximum supported number of vertices for a skinned sub-mesh. Sub-mesh will be ignored and not all vertices will be rendered.", lodIndex, assetPath.c_str(), SkinnedMeshVertexStreamPropertyInterface::Get()->GetMaxSupportedVertexCount());
}
}
}
}
}
static void ProcessIndicesForSubmesh(size_t indexCount, size_t atomIndexBufferOffset, size_t emfxSourceVertexStart, const uint32_t* emfxSubMeshIndices, AZStd::vector<uint32_t>& indexBufferData)
{
for (size_t index = 0; index < indexCount; ++index)
{
// The emfxSubMeshIndices is a pointer to the start of the indices for a particular sub-mesh, so we need to copy the indices from 0-indexCount instead of offsetting the start by emfxSourceVertexStart like we do with the other buffers
// Also, the emfxSubMeshIndices are relative to the start vertex of the sub-mesh, so we need to subtract emfxSourceVertexStart to get the actual index of the vertex within the lod's vertex buffer
indexBufferData[atomIndexBufferOffset + index] = emfxSubMeshIndices[index] - aznumeric_cast<uint32_t>(emfxSourceVertexStart);
}
}
static void ProcessPositionsForSubmesh(size_t vertexCount, size_t atomVertexBufferOffset, size_t emfxSourceVertexStart, const AZ::Vector3* emfxSourcePositions, AZStd::vector<PackedVector3f>& positionBufferData, SkinnedSubMeshProperties& submesh)
{
// Pack the source Vector3 positions (which have 4 components under the hood) into a PackedVector3f buffer for Atom, and build an Aabb along the way
// ATOM-3898 Investigate buffer format and alignment performance to compare current packed R32G32B32 buffer with R32G32B32A32 buffer
Aabb localAabb = Aabb::CreateNull();
for (size_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
const Vector3& sourcePosition = emfxSourcePositions[emfxSourceVertexStart + vertexIndex];
localAabb.AddPoint(sourcePosition);
positionBufferData[atomVertexBufferOffset + vertexIndex] = PackedVector3f(sourcePosition);
}
submesh.m_aabb = localAabb;
}
static void ProcessNormalsForSubmesh(size_t vertexCount, size_t atomVertexBufferOffset, size_t emfxSourceVertexStart, const AZ::Vector3* emfxSourceNormals, AZStd::vector<PackedVector3f>& normalBufferData)
{
// Pack the source Vector3 normals (which have 4 components under the hood) into a PackedVector3f buffer for Atom
// ATOM-3898 Investigate buffer format and alignment performance to compare current packed R32G32B32 buffer with R32G32B32A32 buffer
for (size_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
const Vector3& sourceNormal = emfxSourceNormals[emfxSourceVertexStart + vertexIndex];
normalBufferData[atomVertexBufferOffset + vertexIndex] = PackedVector3f(sourceNormal);
}
}
static void ProcessUVsForSubmesh(size_t vertexCount, size_t atomVertexBufferOffset, [[maybe_unused]] size_t emfxSourceVertexStart, const AZ::Vector2* emfxSourceUVs, AZStd::vector<float[2]>& uvBufferData)
{
for (size_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
emfxSourceUVs[vertexIndex].StoreToFloat2(uvBufferData[atomVertexBufferOffset + vertexIndex]);
}
}
static void ProcessTangentsForSubmesh(size_t vertexCount, size_t atomVertexBufferOffset, size_t emfxSourceVertexStart, const AZ::Vector4* emfxSourceTangents, AZStd::vector<Vector4>& tangentBufferData)
{
AZStd::copy(&emfxSourceTangents[emfxSourceVertexStart], &emfxSourceTangents[emfxSourceVertexStart + vertexCount], tangentBufferData.data() + atomVertexBufferOffset);
}
static void ProcessBitangentsForSubmesh(size_t vertexCount, size_t atomVertexBufferOffset, size_t emfxSourceVertexStart, const AZ::Vector3* emfxSourceBitangents, AZStd::vector<PackedVector3f>& bitangentBufferData)
{
AZ_Assert(emfxSourceBitangents, "GenerateBitangentsForSubmesh called with null source normals.");
// Pack the source Vector3 bitangents (which have 4 components under the hood) into a PackedVector3f buffer for Atom
// ATOM-3898 Investigate buffer format and alignment performance to compare current packed R32G32B32 buffer with R32G32B32A32 buffer
for (size_t i = 0; i < vertexCount; ++i)
{
const Vector3& sourceBitangent = emfxSourceBitangents[emfxSourceVertexStart + i];
bitangentBufferData[atomVertexBufferOffset + i] = PackedVector3f(sourceBitangent);
}
}
static void GenerateBitangentsForSubmesh(size_t vertexCount, size_t atomVertexBufferOffset, size_t emfxSourceVertexStart, const AZ::Vector3* emfxSourceNormals, const AZ::Vector4* emfxSourceTangents, AZStd::vector<PackedVector3f>& bitangentBufferData)
{
AZ_Assert(emfxSourceNormals, "GenerateBitangentsForSubmesh called with null source normals.");
AZ_Assert(emfxSourceTangents, "GenerateBitangentsForSubmesh called with null source tangents.");
// Compute bitangent from tangent and normal.
for (size_t i = 0; i < vertexCount; ++i)
{
const Vector4& sourceTangent = emfxSourceTangents[emfxSourceVertexStart + i];
const Vector3& sourceNormal = emfxSourceNormals[emfxSourceVertexStart + i];
const Vector3 bitangent = sourceNormal.Cross(sourceTangent.GetAsVector3()) * sourceTangent.GetW();
bitangentBufferData[atomVertexBufferOffset + i] = PackedVector3f(bitangent);
}
}
static void ProcessSkinInfluences(
const EMotionFX::Mesh* mesh,
const EMotionFX::SubMesh* subMesh,
size_t atomVertexBufferOffset,
AZStd::vector<AZStd::array<uint32_t, MaxSupportedSkinInfluences>>& blendIndexBufferData,
AZStd::vector<AZStd::array<float, MaxSupportedSkinInfluences>>& blendWeightBufferData,
bool hasClothData)
{
EMotionFX::SkinningInfoVertexAttributeLayer* sourceSkinningInfo = static_cast<EMotionFX::SkinningInfoVertexAttributeLayer*>(mesh->FindSharedVertexAttributeLayer(EMotionFX::SkinningInfoVertexAttributeLayer::TYPE_ID));
// EMotionFX source gives 16 bit indices and 32 bit float weights
// Atom consumes 32 bit uint indices and 32 bit float weights (range 0-1)
// Up to MaxSupportedSkinInfluences influences per vertex are supported
const uint32_t* sourceOriginalVertex = static_cast<uint32_t*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_ORGVTXNUMBERS));
const uint32_t vertexCount = subMesh->GetNumVertices();
const uint32_t vertexStart = subMesh->GetStartVertex();
for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
const uint32_t originalVertex = sourceOriginalVertex[vertexIndex + vertexStart];
const uint32_t influenceCount = AZStd::GetMin<uint32_t>(MaxSupportedSkinInfluences, sourceSkinningInfo->GetNumInfluences(originalVertex));
uint32_t influenceIndex = 0;
float weightError = 1.0f;
for (; influenceIndex < influenceCount; ++influenceIndex)
{
EMotionFX::SkinInfluence* influence = sourceSkinningInfo->GetInfluence(originalVertex, influenceIndex);
blendIndexBufferData[atomVertexBufferOffset + vertexIndex][influenceIndex] = static_cast<uint32_t>(influence->GetNodeNr());
blendWeightBufferData[atomVertexBufferOffset + vertexIndex][influenceIndex] = influence->GetWeight();
weightError -= blendWeightBufferData[atomVertexBufferOffset + vertexIndex][influenceIndex];
}
// Zero out any unused ids/weights
for (; influenceIndex < MaxSupportedSkinInfluences; ++influenceIndex)
{
blendIndexBufferData[atomVertexBufferOffset + vertexIndex][influenceIndex] = 0;
blendWeightBufferData[atomVertexBufferOffset + vertexIndex][influenceIndex] = 0.0f;
}
}
// If there is cloth data, set all the blend weights to zero to indicate
// the vertices will be updated by cpu.
//
// [TODO ATOM-14478]
// At the moment blend weights is a shared buffer and therefore all
// instances of the actor asset will be affected by it. In the future
// this buffer will be unique per instance and modified by cloth component
// when necessary.
//
// [TODO LYN-1890]
// At the moment, if there is cloth data it is assumed that every vertex in the
// submesh will be simulated by cloth in cpu, so all the weights are set to zero.
// But once the blend weights buffer can be modified per instance, it will be set by
// the cloth component, which decides whether to control the whole submesh or
// to apply an additional simplification pass to remove static triangles from simulation.
// Static triangles are the ones that all its vertices won't move during simulation and
// therefore its weights won't be altered so they are controlled by GPU.
// This additional simplification has been disabled in ClothComponentMesh.cpp for now.
if (hasClothData)
{
for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
for (uint32_t influenceIndex = 0; influenceIndex < MaxSupportedSkinInfluences; ++influenceIndex)
{
blendWeightBufferData[atomVertexBufferOffset + vertexIndex][influenceIndex] = 0.0f;
}
}
}
}
void ProcessMorphsForLod(const EMotionFX::Actor* actor, uint32_t lodIndex, const AZStd::string& fullFileName, SkinnedMeshInputLod& skinnedMeshLod)
{
EMotionFX::MorphSetup* morphSetup = actor->GetMorphSetup(lodIndex);
if (morphSetup)
{
uint32_t morphTargetCount = morphSetup->GetNumMorphTargets();
// We're going to split the data into separate streams with 4byte elements,
// which allows for a coalesced read in the morph target compute shader when each thread is loading 4 adjacent bytes at the same time
// The first stream has just the x and y position deltas, which take 2 bytes each
AZStd::vector<uint32_t> positionXYDeltas;
// The second stream has the z position deltas, plus padding
AZStd::vector<uint32_t> positionZPadDeltas;
// The vertex number stream has the target vertex index that each compute thread will write to
AZStd::vector<uint32_t> vertexIndices;
uint32_t totalDeformDataCount = 0;
for (uint32_t morphTargetIndex = 0; morphTargetIndex < morphTargetCount; ++morphTargetIndex)
{
EMotionFX::MorphTarget* morphTarget = morphSetup->GetMorphTarget(morphTargetIndex);
// check if we are dealing with a standard morph target
if (morphTarget->GetType() != EMotionFX::MorphTargetStandard::TYPE_ID)
{
continue;
}
// down cast the morph target
EMotionFX::MorphTargetStandard* morphTargetStandard = static_cast<EMotionFX::MorphTargetStandard*>(morphTarget);
uint32_t deformDataCount = morphTargetStandard->GetNumDeformDatas();
// Get the min/max weight across the entire morph
float minWeight = morphTargetStandard->GetRangeMin();
float maxWeight = morphTargetStandard->GetRangeMax();
// There are multiple deforms for a single morph. Combine them all into a single morph to be processed at once
for (uint32_t deformDataIndex = 0; deformDataIndex < deformDataCount; ++deformDataIndex)
{
EMotionFX::MorphTargetStandard::DeformData* deformData = morphTargetStandard->GetDeformData(deformDataIndex);
// Vertex data
for (uint32_t vertexIndex = 0; vertexIndex < deformData->mNumVerts; ++vertexIndex)
{
const EMotionFX::MorphTargetStandard::DeformData::VertexDelta& delta = deformData->mDeltas[vertexIndex];
// Combine the x and y components into 4 bytes with x in the most-significant 16 bits and y in the least significant 16 bits
uint32_t xy = static_cast<uint32_t>(delta.mPosition.mX);
xy <<= 16;
xy |= static_cast<uint32_t>(delta.mPosition.mY);
positionXYDeltas.push_back(xy);
// Combine the z component with padding, putting the z component in the most significant 16 bits and padding in the least significant 16 bits
uint32_t zpad = static_cast<uint32_t>(delta.mPosition.mZ);
zpad <<= 16;
positionZPadDeltas.push_back(zpad);
// Add the target vertex index
vertexIndices.push_back(delta.mVertexNr);
}
// Now that we have individual elements adjacent to each other, combine the deltas into one long buffer
positionXYDeltas.insert(positionXYDeltas.end(), positionZPadDeltas.begin(), positionZPadDeltas.end());
if (deformData->mNumVerts > 0)
{
// The skinned mesh lod gets a unique morph for each deform data, since each one has unique min/max delta values to use for decompression
AZStd::string morphString = AZStd::string::format("_Lod%u_Morph%u", lodIndex, totalDeformDataCount);
skinnedMeshLod.AddMorphTarget(minWeight, maxWeight, deformData->mMinValue, deformData->mMaxValue, deformData->mNumVerts, vertexIndices, positionXYDeltas, fullFileName + morphString);
totalDeformDataCount++;
}
else
{
AZ_Warning("ProcessMorphsForLod", false, "EMotionFX deform data '%u' in morph target '%u' for lod '%u' in '%s' modifies zero vertices and will be skipped.", deformDataIndex, morphTargetIndex, lodIndex, fullFileName.c_str());
}
positionXYDeltas.clear();
positionZPadDeltas.clear();
vertexIndices.clear();
}
}
}
}
AZStd::intrusive_ptr<SkinnedMeshInputBuffers> CreateSkinnedMeshInputFromActor(const Data::AssetId& actorAssetId, const EMotionFX::Actor* actor)
{
AZStd::intrusive_ptr<SkinnedMeshInputBuffers> skinnedMeshInputBuffers = aznew SkinnedMeshInputBuffers;
skinnedMeshInputBuffers->SetAssetId(actorAssetId);
// Get the fileName, which will be used to label the buffers
AZStd::string assetPath;
Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, actorAssetId);
AZStd::string fullFileName;
AzFramework::StringFunc::Path::GetFullFileName(assetPath.c_str(), fullFileName);
// GetNumNodes returns the number of 'joints' or 'bones' in the skeleton
const size_t numJoints = actor->GetNumNodes();
const size_t numLODs = actor->GetNumLODLevels();
// Create the containers to hold the data for all the combined sub-meshes
AZStd::vector<uint32_t> indexBufferData;
AZStd::vector<PackedVector3f> positionBufferData;
AZStd::vector<PackedVector3f> normalBufferData;
AZStd::vector<Vector4> tangentBufferData;
AZStd::vector<PackedVector3f> bitangentBufferData;
AZStd::vector<AZStd::array<uint32_t, MaxSupportedSkinInfluences>> blendIndexBufferData;
AZStd::vector<AZStd::array<float, MaxSupportedSkinInfluences>> blendWeightBufferData;
AZStd::vector<float[2]> uvBufferData;
//
// Process all LODs from the EMotionFX actor data.
//
skinnedMeshInputBuffers->SetLodCount(numLODs);
for (size_t lodIndex = 0; lodIndex < numLODs; ++lodIndex)
{
// Create a single LOD
SkinnedMeshInputLod skinnedMeshLod;
// Get the amount of vertices and indices
// Get the meshes to process
bool hasUVs = false;
bool hasUVs2 = false;
bool hasTangents = false;
bool hasBitangents = false;
bool hasClothData = false;
// Do a pass over the lod to find the number of sub-meshes, the offset and size of each sub-mesh, and total number of vertices in the lod.
// These will be combined into one input buffer for the source actor, but these offsets and sizes will be used to create multiple sub-meshes for the target skinned actor
uint32_t lodVertexCount = 0;
uint32_t lodIndexCount = 0;
AZStd::vector<SkinnedSubMeshProperties> subMeshes;
CalculateSubmeshPropertiesForLod(actorAssetId, actor, lodIndex, numJoints, subMeshes, lodIndexCount, lodVertexCount);
skinnedMeshLod.SetIndexCount(lodIndexCount);
skinnedMeshLod.SetVertexCount(lodVertexCount);
// We'll be overwriting all the elements, so no need to construct them when resizing
indexBufferData.resize_no_construct(lodIndexCount);
positionBufferData.resize_no_construct(lodVertexCount);
normalBufferData.resize_no_construct(lodVertexCount);
tangentBufferData.resize_no_construct(lodVertexCount);
bitangentBufferData.resize_no_construct(lodVertexCount);
blendIndexBufferData.resize_no_construct(lodVertexCount);
blendWeightBufferData.resize_no_construct(lodVertexCount);
uvBufferData.resize_no_construct(lodVertexCount);
// Now iterate over the actual data and populate the data for the per-actor buffers
size_t lodVertexStart = 0;
size_t indexBufferOffset = 0;
size_t vertexBufferOffset = 0;
size_t skinnedMeshSubmeshIndex = 0;
for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex)
{
const EMotionFX::Mesh* mesh = actor->GetMesh(lodIndex, jointIndex);
if (!mesh || mesh->GetIsCollisionMesh())
{
continue;
}
// Each of these is one long buffer containing the data for all sub-meshes in the joint
const AZ::Vector3* sourcePositions = static_cast<const AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS));
const AZ::Vector3* sourceNormals = static_cast<const AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_NORMALS));
const uint32_t* sourceOriginalVertex = static_cast<const uint32_t*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_ORGVTXNUMBERS));
const AZ::Vector4* sourceTangents = static_cast<const AZ::Vector4*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_TANGENTS));
const AZ::Vector3* sourceBitangents = static_cast<const AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_BITANGENTS));
const AZ::Vector2* sourceUVs = static_cast<const AZ::Vector2*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS, 0));
const AZ::Vector2* sourceUVs2 = static_cast<const AZ::Vector2*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS, 1));
const uint32_t* sourceClothData = static_cast<uint32_t*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_CLOTH_DATA));
hasUVs = (sourceUVs != nullptr);
hasUVs2 = (sourceUVs2 != nullptr);
hasTangents = (sourceTangents != nullptr);
hasBitangents = (sourceBitangents != nullptr);
hasClothData = (sourceClothData != nullptr);
// For each sub-mesh within each mesh, we want to create a separate sub-piece.
const size_t numSubMeshes = mesh->GetNumSubMeshes();
for (size_t subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
{
const EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex);
const size_t vertexCount = subMesh->GetNumVertices();
// Skip empty sub-meshes and sub-meshes that would put the total vertex count beyond the supported range
if (vertexCount > 0 && IsVertexCountWithinSupportedRange(vertexBufferOffset, vertexCount))
{
const size_t indexCount = subMesh->GetNumIndices();
const uint32_t* indices = subMesh->GetIndices();
const size_t vertexStart = subMesh->GetStartVertex();
ProcessIndicesForSubmesh(indexCount, indexBufferOffset, vertexStart, indices, indexBufferData);
ProcessPositionsForSubmesh(vertexCount, vertexBufferOffset, vertexStart, sourcePositions, positionBufferData, subMeshes[skinnedMeshSubmeshIndex]);
ProcessNormalsForSubmesh(vertexCount, vertexBufferOffset, vertexStart, sourceNormals, normalBufferData);
AZ_Assert(hasUVs, "ActorAsset missing uvs. Downstream code is assuming all actors have uvs");
if (hasUVs)
{
ProcessUVsForSubmesh(vertexCount, vertexBufferOffset, vertexStart, sourceUVs, uvBufferData);
}
// ATOM-3623 Support multiple UV sets in actors
// ATOM-3972 Support actors that don't have tangents
AZ_Assert(hasTangents, "ActorAsset missing tangents. Downstream code is assuming all actors have tangents");
if (hasTangents)
{
ProcessTangentsForSubmesh(vertexCount, vertexBufferOffset, vertexStart, sourceTangents, tangentBufferData);
if (hasBitangents)
{
ProcessBitangentsForSubmesh(vertexCount, vertexBufferOffset, vertexStart, sourceBitangents, bitangentBufferData);
}
else
{
GenerateBitangentsForSubmesh(vertexCount, vertexBufferOffset, vertexStart, sourceNormals, sourceTangents, bitangentBufferData);
}
}
ProcessSkinInfluences(mesh, subMesh, vertexBufferOffset, blendIndexBufferData, blendWeightBufferData, hasClothData);
// Increment offsets so that the next sub-mesh can start at the right place
indexBufferOffset += indexCount;
vertexBufferOffset += vertexCount;
skinnedMeshSubmeshIndex++;
}
} // for all submeshes
} // for all meshes
// Now that the data has been prepped, create the actual buffers
// Create read-only buffers and views for input buffers that are shared across all instances
AZStd::string lodString = AZStd::string::format("_Lod%zu", lodIndex);
skinnedMeshLod.CreateSkinningInputBuffer(positionBufferData.data(), SkinnedMeshInputVertexStreams::Position, fullFileName + lodString + "_SkinnedMeshInputPositions");
skinnedMeshLod.CreateSkinningInputBuffer(normalBufferData.data(), SkinnedMeshInputVertexStreams::Normal, fullFileName + lodString + "_SkinnedMeshInputNormals");
skinnedMeshLod.CreateSkinningInputBuffer(tangentBufferData.data(), SkinnedMeshInputVertexStreams::Tangent, fullFileName + lodString + "_SkinnedMeshInputTangents");
skinnedMeshLod.CreateSkinningInputBuffer(bitangentBufferData.data(), SkinnedMeshInputVertexStreams::BiTangent, fullFileName + lodString + "_SkinnedMeshInputBiTangents");
skinnedMeshLod.CreateSkinningInputBuffer(blendIndexBufferData.data(), SkinnedMeshInputVertexStreams::BlendIndices, fullFileName + lodString + "_SkinnedMeshInputBlendIndices");
skinnedMeshLod.CreateSkinningInputBuffer(blendWeightBufferData.data(), SkinnedMeshInputVertexStreams::BlendWeights, fullFileName + lodString + "_SkinnedMeshInputBlendWeights");
// Create read-only input assembly buffers that are not modified during skinning and shared across all instances
skinnedMeshLod.CreateIndexBuffer(indexBufferData.data(), fullFileName + lodString + "_SkinnedMeshIndexBuffer");
skinnedMeshLod.CreateStaticBuffer(uvBufferData.data(), SkinnedMeshStaticVertexStreams::UV_0, fullFileName + lodString + "_SkinnedMeshStaticUVs");
// Set the data that needs to be tracked on a per-sub-mesh basis
// and create the common, shared sub-mesh buffer views
skinnedMeshLod.SetSubMeshProperties(subMeshes);
ProcessMorphsForLod(actor, lodIndex, fullFileName, skinnedMeshLod);
skinnedMeshInputBuffers->SetLod(lodIndex, skinnedMeshLod);
} // for all lods
return skinnedMeshInputBuffers;
}
void GetBoneTransformsFromActorInstance(const EMotionFX::ActorInstance* actorInstance, AZStd::vector<float>& boneTransforms, EMotionFX::Integration::SkinningMethod skinningMethod)
{
const EMotionFX::TransformData* transforms = actorInstance->GetTransformData();
const AZ::Matrix3x4* skinningMatrices = transforms->GetSkinningMatrices();
// For linear skinning, we need a 3x4 row-major float matrix for each transform
const size_t numBoneTransforms = transforms->GetNumTransforms();
if (skinningMethod == EMotionFX::Integration::SkinningMethod::Linear)
{
boneTransforms.resize_no_construct(numBoneTransforms * LinearSkinningFloatsPerBone);
for (size_t i = 0; i < numBoneTransforms; ++i)
{
skinningMatrices[i].StoreToRowMajorFloat12(&boneTransforms[i * LinearSkinningFloatsPerBone]);
}
}
else if(skinningMethod == EMotionFX::Integration::SkinningMethod::DualQuat)
{
boneTransforms.resize_no_construct(numBoneTransforms * DualQuaternionSkinningFloatsPerBone);
for (size_t i = 0; i < numBoneTransforms; ++i)
{
MCore::DualQuaternion dualQuat = MCore::DualQuaternion::ConvertFromTransform(AZ::Transform::CreateFromMatrix3x4(skinningMatrices[i]));
dualQuat.mReal.StoreToFloat4(&boneTransforms[i * DualQuaternionSkinningFloatsPerBone]);
dualQuat.mDual.StoreToFloat4(&boneTransforms[i * DualQuaternionSkinningFloatsPerBone + 4]);
}
}
}
Data::Instance<RPI::Buffer> CreateBoneTransformBufferFromActorInstance(const EMotionFX::ActorInstance* actorInstance, EMotionFX::Integration::SkinningMethod skinningMethod)
{
// Get the actual transforms
AZStd::vector<float> boneTransforms;
GetBoneTransformsFromActorInstance(actorInstance, boneTransforms, skinningMethod);
size_t floatsPerBone = 0;
if (skinningMethod == EMotionFX::Integration::SkinningMethod::Linear)
{
floatsPerBone = LinearSkinningFloatsPerBone;
}
else if (skinningMethod == EMotionFX::Integration::SkinningMethod::DualQuat)
{
floatsPerBone = DualQuaternionSkinningFloatsPerBone;
}
else
{
AZ_Error("ActorAsset", false, "Unsupported EMotionFX skinning method.");
}
// Create a buffer and populate it with the transforms
RHI::BufferViewDescriptor bufferViewDescriptor = RHI::BufferViewDescriptor::CreateStructured(0, aznumeric_cast<uint32_t>(boneTransforms.size() / floatsPerBone), floatsPerBone * sizeof(float));
Data::Asset<RPI::BufferAsset> bufferAsset = BuildInputAssemblyBuffer(static_cast<void*>(boneTransforms.data()), bufferViewDescriptor, RHI::BufferBindFlags::ShaderRead);
return RPI::Buffer::FindOrCreate(bufferAsset);
}
} //namespace Render
} // namespace AZ
@@ -0,0 +1,50 @@
/*
* 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.
*
*/
#pragma once
#include <Integration/Rendering/RenderActorInstance.h>
#include <AtomCore/Instance/Instance.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/containers/vector.h>
namespace EMotionFX
{
class Actor;
class ActorInstance;
}
namespace AZ
{
namespace RPI
{
class Model;
class Buffer;
}
namespace Render
{
class SkinnedMeshInputBuffers;
//! Create a buffers and buffer views that are shared between all actor instances that use the same actor asset.
AZStd::intrusive_ptr<SkinnedMeshInputBuffers> CreateSkinnedMeshInputFromActor(const Data::AssetId& actorAssetId, const EMotionFX::Actor* actor);
//! Get the bone transforms from the actor instance and adjust them to be in the format needed by the renderer
void GetBoneTransformsFromActorInstance(const EMotionFX::ActorInstance* actorInstance, AZStd::vector<float>& boneTransforms, EMotionFX::Integration::SkinningMethod skinningMethod);
//! Create a buffer for bone transforms that can be used as input to the skinning shader
Data::Instance<RPI::Buffer> CreateBoneTransformBufferFromActorInstance(const EMotionFX::ActorInstance* actorInstance, EMotionFX::Integration::SkinningMethod skinningMethod);
} // namespace Render
} // namespace AZ
@@ -0,0 +1,55 @@
/*
* 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 <ActorSystemComponent.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Module/Module.h>
namespace AZ
{
namespace Render
{
//! Some Atom projects will not include EMotionFX, and some projects using EMotionFX will not include Atom. This module exists to prevent creating a hard dependency in either direction.
class ActorModule
: public Module
{
public:
AZ_RTTI(ActorModule, "{84DCA4A9-39A1-4A04-A7DE-66FF62A3B7AD}", Module);
AZ_CLASS_ALLOCATOR(ActorModule, SystemAllocator, 0);
ActorModule()
: Module()
{
m_descriptors.insert(m_descriptors.end(), {
ActorSystemComponent::CreateDescriptor()
});
}
//!
//! Add required SystemComponents to the SystemEntity.
//!
ComponentTypeList GetRequiredSystemComponents() const override
{
return ComponentTypeList{
azrtti_typeid<ActorSystemComponent>(),
};
}
};
} // end Render namespace
} // end AZ namespace
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_EMotionFX_Atom, AZ::Render::ActorModule)
@@ -0,0 +1,67 @@
/*
* 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 <ActorSystemComponent.h>
#include <AtomBackend.h>
#include <Integration/Rendering/RenderBackendManager.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
namespace Render
{
ActorSystemComponent::ActorSystemComponent() = default;
ActorSystemComponent::~ActorSystemComponent() = default;
void ActorSystemComponent::Reflect(ReflectContext* context)
{
if (SerializeContext* serialize = azrtti_cast<SerializeContext*>(context))
{
serialize->Class<ActorSystemComponent, Component>()
->Version(0)
;
}
}
void ActorSystemComponent::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ActorSystemService", 0x5e493d6c));
}
void ActorSystemComponent::GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ActorSystemService", 0x5e493d6c));
}
void ActorSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("SkinnedMeshService", 0xac7cea96));
required.push_back(AZ_CRC("EMotionFXAnimationService", 0x3f8a6369));
}
void ActorSystemComponent::Activate()
{
AZ_Assert(AZ::Interface<EMotionFX::Integration::RenderBackendManager>::Get(), "The EMotionFX RenderBackendManger must be initialized before a render backend can register itself.");
// The RenderBackendManager will manage the lifetime of the AtomBackend
AZ::Interface<EMotionFX::Integration::RenderBackendManager>::Get()->SetRenderBackend(aznew AtomBackend());
}
void ActorSystemComponent::Deactivate()
{
}
} // End Render namespace
} // End AZ namespace
@@ -0,0 +1,44 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
namespace AZ
{
namespace Render
{
class ActorSystemComponent
: public Component
{
public:
AZ_COMPONENT(ActorSystemComponent, "{F055EF7C-1C66-4CEB-879C-6871F3347FF9}");
ActorSystemComponent();
~ActorSystemComponent();
static void Reflect(ReflectContext* context);
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(ComponentDescriptor::DependencyArrayType& required);
protected:
////////////////////////////////////////////////////////////////////////
// Component interface implementation
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
};
} // End Render namespace
} // End AZ namespace
@@ -0,0 +1,59 @@
/*
* 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 <AzCore/base.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Math/Transform.h>
#include <Integration/System/SystemCommon.h>
#include <Integration/Assets/ActorAsset.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/SubMesh.h>
#include <EMotionFX/Source/SkinningInfoVertexAttributeLayer.h>
#include <AtomActor.h>
#include <ActorAsset.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>
namespace AZ
{
namespace Render
{
AZ_CLASS_ALLOCATOR_IMPL(AtomActor, EMotionFX::Integration::EMotionFXAllocator, 0);
AtomActor::AtomActor(EMotionFX::Integration::ActorAsset* actorAsset)
: RenderActor()
, m_actorAsset(actorAsset)
{
AZ_Assert(m_actorAsset, "AtomActor created with a null EmotionFX ActorAsset.");
const AZ::Data::AssetId& actorAssetId = m_actorAsset->GetId();
if (actorAssetId.IsValid())
{
AZ_Assert(m_actorAsset->GetActor(), "AtomActor created with a null EMotionFX Actor.");
}
}
AtomActor::~AtomActor()
{
m_skinnedMeshInputBuffers.reset();
}
AZStd::intrusive_ptr<AZ::Render::SkinnedMeshInputBuffers> AtomActor::FindOrCreateSkinnedMeshInputBuffers()
{
if (!m_skinnedMeshInputBuffers)
{
m_skinnedMeshInputBuffers = AZ::Render::CreateSkinnedMeshInputFromActor(m_actorAsset->GetId(), m_actorAsset->GetActor());
}
return m_skinnedMeshInputBuffers;
}
} // namespace Render
} // namespace AZ
@@ -0,0 +1,58 @@
/*
* 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.
*
*/
#pragma once
#include <Integration/Rendering/RenderActor.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzCore/Asset/AssetCommon.h>
namespace EMotionFX::Integration
{
class ActorAsset;
}
namespace AZ
{
namespace Render
{
class SkinnedMeshInputBuffers;
struct SkinInfluences
{
AZStd::vector<AZStd::array<AZ::u32, 4>> boneIndices;
AZStd::vector<AZStd::array<float, 4>> boneWeights;
};
class AtomActor
: public EMotionFX::Integration::RenderActor
{
public:
AZ_RTTI(EMotionFX::Integration::AtomActor, "{A24ED299-27D3-4227-9D97-D273E5D7BACC}", EMotionFX::Integration::RenderActor);
AZ_CLASS_ALLOCATOR_DECL;
AtomActor(EMotionFX::Integration::ActorAsset* actorAsset);
~AtomActor();
AZStd::intrusive_ptr<AZ::Render::SkinnedMeshInputBuffers> FindOrCreateSkinnedMeshInputBuffers();
private:
AZStd::intrusive_ptr<AZ::Render::SkinnedMeshInputBuffers> m_skinnedMeshInputBuffers;
EMotionFX::Integration::ActorAsset* m_actorAsset;
};
} // namespace Render
} // namespace AZ
@@ -0,0 +1,572 @@
/*
* 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 <AtomActorInstance.h>
#include <AtomActor.h>
#include <ActorAsset.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>
#include <Integration/System/SystemCommon.h>
#include <Integration/System/SystemComponent.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/MorphSetup.h>
#include <EMotionFX/Source/MorphSetupInstance.h>
#include <EMotionFX/Source/MorphTargetStandard.h>
#include <EMotionFX/Source/TransformData.h>
#include <EMotionFX/Source/Skeleton.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/Node.h>
#include <MCore/Source/AzCoreConversions.h>
#include <Atom/RPI.Public/Scene.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/base.h>
namespace AZ
{
namespace Render
{
AZ_CLASS_ALLOCATOR_IMPL(AtomActorInstance, EMotionFX::Integration::EMotionFXAllocator, 0)
AtomActorInstance::AtomActorInstance(AZ::EntityId entityId,
const EMotionFX::Integration::EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<EMotionFX::Integration::ActorAsset>& asset,
[[maybe_unused]] const AZ::Transform& worldTransform,
EMotionFX::Integration::SkinningMethod skinningMethod)
: RenderActorInstance(asset, actorInstance.get(), entityId)
{
RenderActorInstance::SetSkinningMethod(skinningMethod);
if (m_entityId.IsValid())
{
Activate();
AzFramework::BoundsRequestBus::Handler::BusConnect(m_entityId);
}
}
AtomActorInstance::~AtomActorInstance()
{
if (m_entityId.IsValid())
{
AzFramework::BoundsRequestBus::Handler::BusDisconnect();
Deactivate();
}
}
void AtomActorInstance::OnTick([[maybe_unused]] float timeDelta)
{
UpdateBounds();
}
void AtomActorInstance::UpdateBounds()
{
// Update RenderActorInstance world bounding box
// The bounding box is moving with the actor instance. It is static in the way that it does not change shape.
// The entity and actor transforms are kept in sync already.
m_worldAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetAABB().GetMin(), m_actorInstance->GetAABB().GetMax());
// Update RenderActorInstance local bounding box
m_localAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetStaticBasedAABB().GetMin(), m_actorInstance->GetStaticBasedAABB().GetMax());
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, m_entityId);
}
AZ::Aabb AtomActorInstance:: GetWorldBounds()
{
return m_worldAABB;
}
AZ::Aabb AtomActorInstance::GetLocalBounds()
{
return m_localAABB;
}
void AtomActorInstance::SetSkinningMethod(EMotionFX::Integration::SkinningMethod emfxSkinningMethod)
{
RenderActorInstance::SetSkinningMethod(emfxSkinningMethod);
m_boneTransforms = CreateBoneTransformBufferFromActorInstance(m_actorInstance, emfxSkinningMethod);
// Release the Atom skinned mesh and acquire a new one to apply the new skinning method
UnregisterActor();
RegisterActor();
}
SkinningMethod AtomActorInstance::GetAtomSkinningMethod() const
{
switch (GetSkinningMethod())
{
case EMotionFX::Integration::SkinningMethod::DualQuat:
return SkinningMethod::DualQuaternion;
case EMotionFX::Integration::SkinningMethod::Linear:
return SkinningMethod::LinearSkinning;
default:
AZ_Error("AtomActorInstance", false, "Unsupported skinning method. Defaulting to linear");
}
return SkinningMethod::LinearSkinning;
}
AtomActor* AtomActorInstance::GetRenderActor() const
{
EMotionFX::Integration::ActorAsset* actorAsset = m_actorAsset.Get();
if (!actorAsset)
{
AZ_Assert(false, "Actor asset is not loaded.");
return nullptr;
}
AtomActor* renderActor = azdynamic_cast<AtomActor*>(actorAsset->GetRenderActor());
if (!renderActor)
{
AZ_Assert(false, "Expecting a Atom render backend actor.");
return nullptr;
}
return renderActor;
}
void AtomActorInstance::Activate()
{
m_skinnedMeshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<SkinnedMeshFeatureProcessorInterface>(m_entityId);
AZ_Assert(m_skinnedMeshFeatureProcessor, "AtomActorInstance was unable to find a SkinnedMeshFeatureProcessor on the EntityContext provided.");
m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<MeshFeatureProcessorInterface>(m_entityId);
AZ_Assert(m_meshFeatureProcessor, "AtomActorInstance was unable to find a MeshFeatureProcessor on the EntityContext provided.");
m_transformInterface = TransformBus::FindFirstHandler(m_entityId);
AZ_Warning("AtomActorInstance", m_transformInterface, "Unable to attach to a TransformBus handler. This skinned mesh will always be rendered at the origin.");
SkinnedMeshFeatureProcessorNotificationBus::Handler::BusConnect();
MaterialReceiverRequestBus::Handler::BusConnect(m_entityId);
LmbrCentral::SkeletalHierarchyRequestBus::Handler::BusConnect(m_entityId);
Create();
}
void AtomActorInstance::Deactivate()
{
SkinnedMeshOutputStreamNotificationBus::Handler::BusDisconnect();
LmbrCentral::SkeletalHierarchyRequestBus::Handler::BusDisconnect();
MaterialReceiverRequestBus::Handler::BusDisconnect();
SkinnedMeshFeatureProcessorNotificationBus::Handler::BusDisconnect();
Destroy();
m_meshFeatureProcessor = nullptr;
m_skinnedMeshFeatureProcessor = nullptr;
}
MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const
{
return GetMaterialAssignmentsFromModel(m_skinnedMeshInstance->m_model);
}
AZStd::unordered_set<AZ::Name> AtomActorInstance::GetModelUvNames() const
{
return m_skinnedMeshInstance->m_model->GetUvNames();
}
void AtomActorInstance::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
{
// The mesh transform is used to determine where the actor instance is actually rendered
m_meshFeatureProcessor->SetTransform(*m_meshHandle, world); // handle validity is checked internally.
if (m_skinnedMeshRenderProxy.IsValid())
{
// The skinned mesh transform is used to determine which Lod needs to be skinned
m_skinnedMeshRenderProxy->SetTransform(world);
}
}
void AtomActorInstance::OnMaterialsUpdated(const MaterialAssignmentMap& materials)
{
if (m_meshFeatureProcessor)
{
m_meshFeatureProcessor->SetMaterialAssignmentMap(*m_meshHandle, materials);
}
}
void AtomActorInstance::SetModelAsset([[maybe_unused]] Data::Asset<RPI::ModelAsset> modelAsset)
{
// Atom Actor Instance is not based on an actual Model Asset yet,
// it's created at runtime from an Actor Asset.
}
const Data::Asset<RPI::ModelAsset>& AtomActorInstance::GetModelAsset() const
{
return m_skinnedMeshInstance->m_model->GetModelAsset();
}
void AtomActorInstance::SetModelAssetId([[maybe_unused]] Data::AssetId modelAssetId)
{
// Atom Actor Instance is not based on an actual Model Asset yet,
// it's created at runtime from an Actor Asset.
}
Data::AssetId AtomActorInstance::GetModelAssetId() const
{
return GetModelAsset().GetId();
}
void AtomActorInstance::SetModelAssetPath([[maybe_unused]] const AZStd::string& modelAssetPath)
{
// Atom Actor Instance is not based on an actual Model Asset yet,
// it's created at runtime from an Actor Asset.
}
AZStd::string AtomActorInstance::GetModelAssetPath() const
{
return GetModelAsset().GetHint();
}
const AZ::Data::Instance<RPI::Model> AtomActorInstance::GetModel() const
{
return m_skinnedMeshInstance->m_model;
}
void AtomActorInstance::SetSortKey(RHI::DrawItemSortKey sortKey)
{
m_meshFeatureProcessor->SetSortKey(*m_meshHandle, sortKey);
}
RHI::DrawItemSortKey AtomActorInstance::GetSortKey() const
{
return m_meshFeatureProcessor->GetSortKey(*m_meshHandle);
}
void AtomActorInstance::SetLodOverride(RPI::Cullable::LodOverride lodOverride)
{
m_meshFeatureProcessor->SetLodOverride(*m_meshHandle, lodOverride);
}
RPI::Cullable::LodOverride AtomActorInstance::GetLodOverride() const
{
return m_meshFeatureProcessor->GetLodOverride(*m_meshHandle);
}
void AtomActorInstance::SetVisibility(bool visible)
{
SetIsVisible(visible);
}
bool AtomActorInstance::GetVisibility() const
{
return IsVisible();
}
void AtomActorInstance::SetMeshAsset(const AZ::Data::AssetId& id)
{
AZ::Data::Asset<EMotionFX::Integration::ActorAsset> asset =
AZ::Data::AssetManager::Instance().GetAsset<EMotionFX::Integration::ActorAsset>(
id, m_actorAsset.GetAutoLoadBehavior());
if (asset)
{
m_actorAsset = asset;
Create();
}
}
AZ::Data::Asset<AZ::Data::AssetData> AtomActorInstance::GetMeshAsset()
{
return m_actorAsset;
}
bool AtomActorInstance::GetVisibility()
{
return static_cast<const AtomActorInstance&>(*this).GetVisibility();
}
AZ::u32 AtomActorInstance::GetJointCount()
{
return m_actorInstance->GetActor()->GetSkeleton()->GetNumNodes();
}
const char* AtomActorInstance::GetJointNameByIndex(AZ::u32 jointIndex)
{
EMotionFX::Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton();
const AZ::u32 numNodes = skeleton->GetNumNodes();
if (jointIndex < numNodes)
{
return skeleton->GetNode(jointIndex)->GetName();
}
return nullptr;
}
AZ::s32 AtomActorInstance::GetJointIndexByName(const char* jointName)
{
if (jointName)
{
EMotionFX::Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton();
const AZ::u32 numNodes = skeleton->GetNumNodes();
for (AZ::u32 nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex)
{
if (0 == azstricmp(jointName, skeleton->GetNode(nodeIndex)->GetName()))
{
return nodeIndex;
}
}
}
return -1;
}
AZ::Transform AtomActorInstance::GetJointTransformCharacterRelative(AZ::u32 jointIndex)
{
const EMotionFX::TransformData* transforms = m_actorInstance->GetTransformData();
if (transforms && jointIndex < transforms->GetNumTransforms())
{
return MCore::EmfxTransformToAzTransform(transforms->GetCurrentPose()->GetModelSpaceTransform(jointIndex));
}
return AZ::Transform::CreateIdentity();
}
void AtomActorInstance::Create()
{
Destroy();
m_skinnedMeshInputBuffers = GetRenderActor()->FindOrCreateSkinnedMeshInputBuffers();
AZ_Error("AtomActorInstance", m_skinnedMeshInputBuffers, "Failed to get SkinnedMeshInputBuffers from Actor.");
if (m_skinnedMeshInputBuffers)
{
m_boneTransforms = CreateBoneTransformBufferFromActorInstance(m_actorInstance, GetSkinningMethod());
AZ_Error("AtomActorInstance", m_boneTransforms, "Failed to create bone transform buffer.");
// If the instance is created before the default materials on the model have finished loading, the mesh feature processor will ignore it.
// Wait for them all to be ready before creating the instance
size_t lodCount = m_skinnedMeshInputBuffers->GetLodCount();
for (size_t lodIndex = 0; lodIndex < lodCount; ++lodIndex)
{
const SkinnedMeshInputLod& inputLod = m_skinnedMeshInputBuffers->GetLod(lodIndex);
const AZStd::vector< SkinnedSubMeshProperties>& subMeshProperties = inputLod.GetSubMeshProperties();
for (const SkinnedSubMeshProperties& submesh : subMeshProperties)
{
AZ_Error("AtomActorInstance", submesh.m_material, "Actor does not have a valid default material in lod %d", lodIndex);
if (submesh.m_material)
{
if (!submesh.m_material->IsReady())
{
// Start listening for the material's OnAssetReady event.
// AtomActorInstance::Create is called on the main thread, so there should be no need to synchronize with the OnAssetReady event handler
// since those events will also come from the main thread
m_waitForMaterialLoadIds.insert(submesh.m_material->GetId());
Data::AssetBus::MultiHandler::BusConnect(submesh.m_material->GetId());
}
}
}
}
// If all the default materials are ready, create the skinned mesh instance
if (m_waitForMaterialLoadIds.empty())
{
CreateSkinnedMeshInstance();
}
}
}
void AtomActorInstance::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
Data::AssetBus::MultiHandler::BusDisconnect(asset->GetId());
m_waitForMaterialLoadIds.erase(asset->GetId());
// If all the default materials are ready, create the skinned mesh instance
if (m_waitForMaterialLoadIds.empty())
{
CreateSkinnedMeshInstance();
}
}
void AtomActorInstance::Destroy()
{
if (m_skinnedMeshInstance)
{
UnregisterActor();
m_skinnedMeshInputBuffers.reset();
m_skinnedMeshInstance.reset();
m_boneTransforms.reset();
}
}
void AtomActorInstance::OnUpdateSkinningMatrices()
{
if (m_skinnedMeshRenderProxy.IsValid())
{
AZStd::vector<float> boneTransforms;
GetBoneTransformsFromActorInstance(m_actorInstance, boneTransforms, GetSkinningMethod());
m_skinnedMeshRenderProxy->SetSkinningMatrices(boneTransforms);
// Update the morph weights for every lod. This does not mean they will all be dispatched, but they will all have up to date weights
// TODO: once culling is hooked up such that EMotionFX and Atom are always in sync about which lod to update, only update the currently visible lods [ATOM-13564]
for (uint32_t lodIndex = 0; lodIndex < m_actorInstance->GetActor()->GetNumLODLevels(); ++lodIndex)
{
EMotionFX::MorphSetup* morphSetup = m_actorInstance->GetActor()->GetMorphSetup(lodIndex);
if (morphSetup)
{
uint32_t morphTargetCount = morphSetup->GetNumMorphTargets();
AZStd::vector<float> weights;
for (uint32_t morphTargetIndex = 0; morphTargetIndex < morphTargetCount; ++morphTargetIndex)
{
EMotionFX::MorphTarget* morphTarget = morphSetup->GetMorphTarget(morphTargetIndex);
// check if we are dealing with a standard morph target
if (morphTarget->GetType() != EMotionFX::MorphTargetStandard::TYPE_ID)
{
continue;
}
// down cast the morph target
EMotionFX::MorphTargetStandard* morphTargetStandard = static_cast<EMotionFX::MorphTargetStandard*>(morphTarget);
EMotionFX::MorphSetupInstance::MorphTarget* morphTargetSetupInstance = m_actorInstance->GetMorphSetupInstance()->FindMorphTargetByID(morphTargetStandard->GetID());
// Each morph target is split into several deform datas, all of which share the same weight but have unique min/max delta values
// and thus correspond with unique dispatches in the morph target pass
for (uint32_t deformDataIndex = 0; deformDataIndex < morphTargetStandard->GetNumDeformDatas(); ++deformDataIndex)
{
weights.push_back(morphTargetSetupInstance->GetWeight());
}
}
m_skinnedMeshRenderProxy->SetMorphTargetWeights(lodIndex, weights);
}
}
}
}
void AtomActorInstance::RegisterActor()
{
MaterialAssignmentMap materials;
MaterialComponentRequestBus::EventResult(materials, m_entityId, &MaterialComponentRequests::GetMaterialOverrides);
CreateRenderProxy(materials);
TransformNotificationBus::Handler::BusConnect(m_entityId);
MaterialComponentNotificationBus::Handler::BusConnect(m_entityId);
MeshComponentRequestBus::Handler::BusConnect(m_entityId);
LmbrCentral::MeshComponentRequestBus::Handler::BusConnect(m_entityId);
}
void AtomActorInstance::UnregisterActor()
{
LmbrCentral::MeshComponentRequestBus::Handler::BusDisconnect();
MeshComponentRequestBus::Handler::BusDisconnect();
MaterialComponentNotificationBus::Handler::BusDisconnect();
TransformNotificationBus::Handler::BusDisconnect();
m_skinnedMeshFeatureProcessor->ReleaseRenderProxyInterface(m_skinnedMeshRenderProxy);
if (m_meshHandle)
{
m_meshFeatureProcessor->ReleaseMesh(*m_meshHandle);
m_meshHandle = nullptr;
}
}
void AtomActorInstance::CreateRenderProxy(const MaterialAssignmentMap& materials)
{
auto meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<MeshFeatureProcessorInterface>(m_entityId);
AZ_Error("ActorComponentController", meshFeatureProcessor, "Unable to find a MeshFeatureProcessorInterface on the entityId.");
if (meshFeatureProcessor)
{
// Last boolean parameter indicates if motion vector is enabled
m_meshHandle = AZStd::make_shared<MeshFeatureProcessorInterface::MeshHandle>(
m_meshFeatureProcessor->AcquireMesh(m_skinnedMeshInstance->m_model->GetModelAsset(), materials, true));
}
// If render proxies already exist, they will be auto-freed
SkinnedMeshFeatureProcessorInterface::SkinnedMeshRenderProxyDesc desc{ m_skinnedMeshInputBuffers, m_skinnedMeshInstance, m_meshHandle, m_boneTransforms, {GetAtomSkinningMethod()} };
m_skinnedMeshRenderProxy = m_skinnedMeshFeatureProcessor->AcquireRenderProxyInterface(desc);
if (m_transformInterface)
{
OnTransformChanged(Transform::Identity(), m_transformInterface->GetWorldTM());
}
else
{
OnTransformChanged(Transform::Identity(), Transform::Identity());
}
}
void AtomActorInstance::CreateSkinnedMeshInstance()
{
SkinnedMeshOutputStreamNotificationBus::Handler::BusDisconnect();
m_skinnedMeshInstance = m_skinnedMeshInputBuffers->CreateSkinnedMeshInstance();
if (m_skinnedMeshInstance)
{
MaterialReceiverNotificationBus::Event(m_entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged);
RegisterActor();
// [TODO ATOM-14478, LYN-1890]
// Temporary workaround for cloth to make sure the output skinned buffers are filled at least once.
// When the blend weights buffer can be unique per instance and updated by cloth component,
// FillSkinnedMeshInstanceBuffers can be removed.
FillSkinnedMeshInstanceBuffers();
}
else
{
AZ_Warning("AtomActorInstance", m_skinnedMeshInstance, "Failed to create target skinned model. Will automatically attempt to re-create when skinned mesh memory is freed up.");
SkinnedMeshOutputStreamNotificationBus::Handler::BusConnect();
}
}
void AtomActorInstance::FillSkinnedMeshInstanceBuffers()
{
AZ_Assert( m_skinnedMeshInputBuffers->GetLodCount() == m_skinnedMeshInstance->m_outputStreamOffsetsInBytes.size(),
"Number of lods in Skinned Mesh Input Buffers (%d) does not match with Skinned Mesh Instance (%d)",
m_skinnedMeshInputBuffers->GetLodCount(), m_skinnedMeshInstance->m_outputStreamOffsetsInBytes.size());
for (size_t lodIndex = 0; lodIndex < m_skinnedMeshInputBuffers->GetLodCount(); ++lodIndex)
{
const SkinnedMeshInputLod& inputSkinnedMeshLod = m_skinnedMeshInputBuffers->GetLod(lodIndex);
const AZStd::vector<uint32_t>& outputBufferOffsetsInBytes = m_skinnedMeshInstance->m_outputStreamOffsetsInBytes[lodIndex];
uint32_t lodVertexCount = inputSkinnedMeshLod.GetVertexCount();
auto updateSkinnedMeshInstance =
[&inputSkinnedMeshLod, &outputBufferOffsetsInBytes, &lodVertexCount](SkinnedMeshInputVertexStreams inputStream, SkinnedMeshOutputVertexStreams outputStream)
{
const Data::Asset<RPI::BufferAsset>& inputBufferAsset = inputSkinnedMeshLod.GetSkinningInputBufferAsset(inputStream);
const RHI::BufferViewDescriptor& inputBufferViewDescriptor = inputBufferAsset->GetBufferViewDescriptor();
const uint64_t inputByteCount = aznumeric_cast<uint64_t>(inputBufferViewDescriptor.m_elementCount) * aznumeric_cast<uint64_t>(inputBufferViewDescriptor.m_elementSize);
const uint64_t inputByteOffset = aznumeric_cast<uint64_t>(inputBufferViewDescriptor.m_elementOffset) * aznumeric_cast<uint64_t>(inputBufferViewDescriptor.m_elementSize);
const uint32_t outputElementSize = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(outputStream).m_elementSize;
const uint64_t outputByteCount = aznumeric_cast<uint64_t>(lodVertexCount) * aznumeric_cast<uint64_t>(outputElementSize);
const uint64_t outputByteOffset = aznumeric_cast<uint64_t>(outputBufferOffsetsInBytes[static_cast<uint8_t>(outputStream)]);
// The byte count from input and output buffers doesn't have to match necessarily.
// For example the output positions buffer has double the amount of elements because it has
// another set of positions from the previous frame.
AZ_Assert(inputByteCount <= outputByteCount, "Trying to write too many bytes to output buffer.");
// The shared buffer that all skinning output lives in
AZ::Data::Instance<AZ::RPI::Buffer> rpiBuffer = SkinnedMeshOutputStreamManagerInterface::Get()->GetBuffer();
rpiBuffer->UpdateData(
inputBufferAsset->GetBuffer().data() + inputByteOffset,
inputByteCount,
outputByteOffset);
};
updateSkinnedMeshInstance(SkinnedMeshInputVertexStreams::Position, SkinnedMeshOutputVertexStreams::Position);
updateSkinnedMeshInstance(SkinnedMeshInputVertexStreams::Normal, SkinnedMeshOutputVertexStreams::Normal);
updateSkinnedMeshInstance(SkinnedMeshInputVertexStreams::Tangent, SkinnedMeshOutputVertexStreams::Tangent);
updateSkinnedMeshInstance(SkinnedMeshInputVertexStreams::BiTangent, SkinnedMeshOutputVertexStreams::BiTangent);
}
}
void AtomActorInstance::OnSkinnedMeshOutputStreamMemoryAvailable()
{
CreateSkinnedMeshInstance();
}
} //namespace Render
} // namespace AZ
@@ -0,0 +1,193 @@
/*
* 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.
*
*/
#pragma once
#include <AtomActor.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <Integration/Rendering/RenderActorInstance.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshFeatureProcessorBus.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshRenderProxyInterface.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshFeatureProcessorInterface.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshShaderOptions.h>
#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
namespace EMotionFX
{
class Actor;
class ActorInstance;
}
namespace AZ::RPI
{
class Model;
class Buffer;
}
namespace AZ
{
namespace Render
{
class SkinnedMeshFeatureProcessorInterface;
class SkinnedMeshInputBuffers;
class MeshFeatureProcessorInterface;
class AtomActor;
//! Render node for managing and rendering actor instances. Each Actor Component
//! creates an ActorRenderNode. The render node is responsible for drawing meshes and
//! passing skinning transforms to the skinning pipeline.
class AtomActorInstance
: public EMotionFX::Integration::RenderActorInstance
, public AZ::TransformNotificationBus::Handler
, public AZ::Render::MaterialReceiverRequestBus::Handler
, public AzFramework::BoundsRequestBus::Handler
, public AZ::Render::MaterialComponentNotificationBus::Handler
, public AZ::Render::MeshComponentRequestBus::Handler
, public LmbrCentral::MeshComponentRequestBus::Handler
, private AZ::Render::SkinnedMeshFeatureProcessorNotificationBus::Handler
, private AZ::Render::SkinnedMeshOutputStreamNotificationBus::Handler
, private LmbrCentral::SkeletalHierarchyRequestBus::Handler
, private Data::AssetBus::MultiHandler
{
public:
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(AZ::Render::AtomActorInstance, "{6C933B44-8D4A-43B0-9F0F-C1932A257ABC}", EMotionFX::Integration::RenderActorInstance)
AZ_DISABLE_COPY_MOVE(AtomActorInstance);
AtomActorInstance() = delete;
AtomActorInstance(AZ::EntityId entityId,
const EMotionFX::Integration::EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<EMotionFX::Integration::ActorAsset>& asset,
const AZ::Transform& worldTransform,
EMotionFX::Integration::SkinningMethod skinningMethod);
~AtomActorInstance() override;
// AtomActorInstanceRequestBusTEMP::Handler interface implementation
// RenderActorInstance overrides ...
void OnTick(float timeDelta) override;
void UpdateBounds() override;
void DebugDraw(const DebugOptions& debugOptions) override { AZ_UNUSED(debugOptions) };
void SetMaterials(const EMotionFX::Integration::ActorAsset::MaterialList& materialPerLOD) override { AZ_UNUSED(materialPerLOD); };
void SetSkinningMethod(EMotionFX::Integration::SkinningMethod emfxSkinningMethod);
SkinningMethod GetAtomSkinningMethod() const;
// BoundsRequestBus overrides ...
AZ::Aabb GetWorldBounds() override;
AZ::Aabb GetLocalBounds() override;
AtomActor* GetRenderActor() const;
/////////////////////////////////////////////
void Activate();
void Deactivate();
// Create all the buffers necessary for a skinned mesh render proxy and mesh render proxy, then register it with Atom
void Create();
// Release render proxies and destroy all the buffers
void Destroy();
// Acquire render proxies from Atom. Assumes the input buffers already exist.
void RegisterActor();
// Release render proxies, but don't destroy the buffers used to create them. Can be used to hide the actor while keeping its resources around for later.
void UnregisterActor();
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TransformNotificationBus::Handler overrides...
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MaterialReceiverRequestBus::Handler overrides...
MaterialAssignmentMap GetMaterialAssignments() const override;
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MaterialComponentNotificationBus::Handler overrides...
void OnMaterialsUpdated(const MaterialAssignmentMap& materials) override;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MeshComponentRequestBus::Handler overrides...
void SetModelAsset(Data::Asset<RPI::ModelAsset> modelAsset) override;
const Data::Asset<RPI::ModelAsset>& GetModelAsset() const override;
void SetModelAssetId(Data::AssetId modelAssetId) override;
Data::AssetId GetModelAssetId() const override;
void SetModelAssetPath(const AZStd::string& modelAssetPath) override;
AZStd::string GetModelAssetPath() const override;
const AZ::Data::Instance<RPI::Model> GetModel() const override;
void SetSortKey(RHI::DrawItemSortKey sortKey) override;
RHI::DrawItemSortKey GetSortKey() const override;
void SetLodOverride(RPI::Cullable::LodOverride lodOverride) override;
RPI::Cullable::LodOverride GetLodOverride() const override;
void SetVisibility(bool visible) override;
bool GetVisibility() const override;
// GetWorldBounds/GetLocalBounds already overridden by BoundsRequestBus::Handler
//////////////////////////////////////////////////////////////////////////
// LmbrCentral::MeshComponentRequestBus::Handler
void SetMeshAsset(const AZ::Data::AssetId& id) override;
AZ::Data::Asset<AZ::Data::AssetData> GetMeshAsset() override;
bool GetVisibility() override;
// SetVisibility already overridden by MeshComponentRequestBus::Handler
// GetWorldBounds/GetLocalBounds already overridden by BoundsRequestBus::Handler
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SkeletalHierarchyRequestBus::Handler overrides...
AZ::u32 GetJointCount() override;
const char* GetJointNameByIndex(AZ::u32 jointIndex) override;
AZ::s32 GetJointIndexByName(const char* jointName) override;
AZ::Transform GetJointTransformCharacterRelative(AZ::u32 jointIndex) override;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Data::AssetBus::MultiHandler::Handler overrides...
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SkinnedMeshFeatureProcessorNotificationBus
void OnUpdateSkinningMatrices() override;
void CreateRenderProxy(const MaterialAssignmentMap& materials);
private:
void CreateSkinnedMeshInstance();
// Copies input buffers to output skinned buffers when the skinned mesh instance is created.
void FillSkinnedMeshInstanceBuffers();
// SkinnedMeshOutputStreamNotificationBus
void OnSkinnedMeshOutputStreamMemoryAvailable() override;
AZStd::intrusive_ptr<AZ::Render::SkinnedMeshInputBuffers> m_skinnedMeshInputBuffers = nullptr;
AZStd::intrusive_ptr<SkinnedMeshInstance> m_skinnedMeshInstance;
AZ::Data::Instance<AZ::RPI::Buffer> m_boneTransforms = nullptr;
AZ::Render::SkinnedMeshRenderProxyInterfaceHandle m_skinnedMeshRenderProxy;
AZ::Render::SkinnedMeshFeatureProcessorInterface* m_skinnedMeshFeatureProcessor = nullptr;
AZ::Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
//m_meshHandle is wrapped in a shared pointer so that it can be shared between this and the SkinnedMeshRenderProxy (the handle itself cannot be copied)
AZStd::shared_ptr<MeshFeatureProcessorInterface::MeshHandle> m_meshHandle;
AZ::TransformInterface* m_transformInterface = nullptr;
AZStd::set<Data::AssetId> m_waitForMaterialLoadIds;
};
} // namespace Render
} // namespace AZ
@@ -0,0 +1,41 @@
/*
* 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 <EMotionFX/Source/Actor.h>
#include <AtomBackend.h>
#include <AtomActor.h>
#include <AtomActorInstance.h>
namespace AZ
{
namespace Render
{
class ActorAsset;
AZ_CLASS_ALLOCATOR_IMPL(AtomBackend, EMotionFX::Integration::EMotionFXAllocator, 0);
EMotionFX::Integration::RenderActor* AtomBackend::CreateActor(EMotionFX::Integration::ActorAsset* asset)
{
return aznew AtomActor(asset);
}
EMotionFX::Integration::RenderActorInstance* AtomBackend::CreateActorInstance(AZ::EntityId entityId,
const EMotionFX::Integration::EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<EMotionFX::Integration::ActorAsset>& asset,
[[maybe_unused]] const EMotionFX::Integration::ActorAsset::MaterialList& materialPerLOD,
[[maybe_unused]] EMotionFX::Integration::SkinningMethod skinningMethod,
const AZ::Transform& worldTransform)
{
return aznew AZ::Render::AtomActorInstance(entityId, actorInstance, asset, worldTransform, skinningMethod);
}
} // namespace Render
} // namespace AZ
@@ -0,0 +1,41 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <Integration/Rendering/RenderBackend.h>
namespace AZ
{
namespace Render
{
class AtomBackend
: public EMotionFX::Integration::RenderBackend
{
public:
AZ_RTTI(EMotionFX::Integration::AtomBackend, "{05961B40-B0B3-459A-8FB1-742778CC7BF7}", EMotionFX::Integration::RenderBackend);
AZ_CLASS_ALLOCATOR_DECL;
EMotionFX::Integration::RenderActor * CreateActor(EMotionFX::Integration::ActorAsset * asset) override;
EMotionFX::Integration::RenderActorInstance* CreateActorInstance(AZ::EntityId entityId,
const EMotionFX::Integration::EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<EMotionFX::Integration::ActorAsset>& asset,
const EMotionFX::Integration::ActorAsset::MaterialList& materialPerLOD,
EMotionFX::Integration::SkinningMethod skinningMethod,
const AZ::Transform& worldTransform) override;
};
} // namespace Render
} // namespace AZ