Refactor Skeleton Mapping to be handled by Skinned Model instead of Animation asset
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
#include "Animation.h"
|
||||
#include "SkinnedModel.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Animations/CurveSerialization.h"
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
@@ -30,9 +29,7 @@ void Animation::OnScriptsReloadStart()
|
||||
for (auto& e : Events)
|
||||
{
|
||||
for (auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
Level::ScriptsReloadRegisterObject((ScriptingObject*&)k.Value.Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,72 +64,11 @@ Animation::InfoData Animation::GetInfo() const
|
||||
}
|
||||
info.MemoryUsage += Events.Capacity() * sizeof(Pair<String, StepCurve<AnimEventData>>);
|
||||
info.MemoryUsage += NestedAnims.Capacity() * sizeof(Pair<String, NestedAnimData>);
|
||||
info.MemoryUsage += MappingCache.Capacity() * (sizeof(void*) + sizeof(NodeToChannel) + 1);
|
||||
for (auto& e : Events)
|
||||
info.MemoryUsage += e.Second.GetKeyframes().Capacity() * sizeof(StepCurve<AnimEventData>);
|
||||
for (auto& e : MappingCache)
|
||||
info.MemoryUsage += e.Value.Capacity() * sizeof(int32);
|
||||
return info;
|
||||
}
|
||||
|
||||
void Animation::ClearCache()
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Unlink events
|
||||
for (auto i = MappingCache.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
i->Key->OnUnloaded.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
i->Key->OnReloading.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
}
|
||||
|
||||
// Free memory
|
||||
MappingCache.Clear();
|
||||
MappingCache.SetCapacity(0);
|
||||
}
|
||||
|
||||
const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj)
|
||||
{
|
||||
ASSERT(obj && obj->IsLoaded() && IsLoaded());
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Try quick lookup
|
||||
NodeToChannel* result = MappingCache.TryGet(obj);
|
||||
if (result == nullptr)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Add to cache
|
||||
NodeToChannel tmp;
|
||||
auto bucket = MappingCache.Add(obj, tmp);
|
||||
result = &bucket->Value;
|
||||
obj->OnUnloaded.Bind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
obj->OnReloading.Bind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
|
||||
// Initialize the mapping
|
||||
const auto& skeleton = obj->Skeleton;
|
||||
const int32 nodesCount = skeleton.Nodes.Count();
|
||||
result->Resize(nodesCount, false);
|
||||
result->SetAll(-1);
|
||||
for (int32 i = 0; i < Data.Channels.Count(); i++)
|
||||
{
|
||||
auto& nodeAnim = Data.Channels[i];
|
||||
|
||||
for (int32 j = 0; j < nodesCount; j++)
|
||||
{
|
||||
if (skeleton.Nodes[j].Name == nodeAnim.NodeName)
|
||||
{
|
||||
result->At(j) = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void Animation::LoadTimeline(BytesContainer& result) const
|
||||
@@ -552,23 +488,6 @@ bool Animation::Save(const StringView& path)
|
||||
|
||||
#endif
|
||||
|
||||
void Animation::OnSkinnedModelUnloaded(Asset* obj)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
const auto key = static_cast<SkinnedModel*>(obj);
|
||||
auto i = MappingCache.Find(key);
|
||||
ASSERT(i != MappingCache.End());
|
||||
|
||||
// Unlink event
|
||||
key->OnUnloaded.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
key->OnReloading.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
|
||||
// Clear cache
|
||||
i->Value.Resize(0, false);
|
||||
MappingCache.Remove(i);
|
||||
}
|
||||
|
||||
uint64 Animation::GetMemoryUsage() const
|
||||
{
|
||||
Locker.Lock();
|
||||
@@ -579,9 +498,6 @@ uint64 Animation::GetMemoryUsage() const
|
||||
for (const auto& e : Events)
|
||||
result += e.First.Length() * sizeof(Char) + e.Second.GetMemoryUsage();
|
||||
result += NestedAnims.Capacity() * sizeof(Pair<String, NestedAnimData>);
|
||||
result += MappingCache.Capacity() * sizeof(Pair<String, StepCurve<AnimEventData>>);
|
||||
for (const auto& e : MappingCache)
|
||||
result += e.Value.Capacity() * sizeof(int32);
|
||||
Locker.Unlock();
|
||||
return result;
|
||||
}
|
||||
@@ -738,7 +654,6 @@ void Animation::unload(bool isReloading)
|
||||
Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||
}
|
||||
#endif
|
||||
ClearCache();
|
||||
Data.Dispose();
|
||||
for (const auto& e : Events)
|
||||
{
|
||||
|
||||
@@ -98,17 +98,6 @@ public:
|
||||
/// </summary>
|
||||
Array<Pair<String, NestedAnimData>> NestedAnims;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the mapping for every skeleton node to the animation data channels.
|
||||
/// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index).
|
||||
/// </summary>
|
||||
typedef Array<int32> NodeToChannel;
|
||||
|
||||
/// <summary>
|
||||
/// The skeleton nodes to animation channel indices mapping cache. Use it as read-only. It's being maintained internally by the asset.
|
||||
/// </summary>
|
||||
Dictionary<SkinnedModel*, NodeToChannel> MappingCache;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the length of the animation (in seconds).
|
||||
@@ -139,18 +128,6 @@ public:
|
||||
/// </summary>
|
||||
API_PROPERTY() InfoData GetInfo() const;
|
||||
|
||||
/// <summary>
|
||||
/// Clears the skeleton mapping cache.
|
||||
/// </summary>
|
||||
void ClearCache();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the skeleton mapping cache.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target skinned model to get mapping to its skeleton.</param>
|
||||
/// <returns>The cached node-to-channel mapping for the fast animation sampling for the skinned model skeleton nodes.</returns>
|
||||
const NodeToChannel* GetMapping(SkinnedModel* obj);
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Gets the animation as serialized timeline data. Used to show it in Editor.
|
||||
@@ -174,9 +151,6 @@ public:
|
||||
bool Save(const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void OnSkinnedModelUnloaded(Asset* obj);
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
uint64 GetMemoryUsage() const override;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SkinnedModel.h"
|
||||
#include "Animation.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
|
||||
#define CHECK_INVALID_BUFFER(model, buffer) \
|
||||
@@ -116,6 +118,7 @@ SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info)
|
||||
SkinnedModel::~SkinnedModel()
|
||||
{
|
||||
ASSERT(_streamingTask == nullptr);
|
||||
ASSERT(_skeletonMappingCache.Count() == 0);
|
||||
}
|
||||
|
||||
bool SkinnedModel::HasAnyLODInitialized() const
|
||||
@@ -152,6 +155,55 @@ void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const
|
||||
GetChunkData(chunkIndex, data);
|
||||
}
|
||||
|
||||
Span<int32> SkinnedModel::GetSkeletonMapping(Asset* source)
|
||||
{
|
||||
if (WaitForLoaded() || !source || source->WaitForLoaded())
|
||||
return Span<int32>();
|
||||
ScopeLock lock(Locker);
|
||||
Span<int32> result;
|
||||
if (!_skeletonMappingCache.TryGet(source, result))
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Initialize the mapping
|
||||
const int32 nodesCount = Skeleton.Nodes.Count();
|
||||
result = Span<int32>((int32*)Allocator::Allocate(nodesCount * sizeof(int32)), nodesCount);
|
||||
for (int32 i = 0; i < nodesCount; i++)
|
||||
result[i] = -1;
|
||||
if (const auto* sourceAnim = Cast<Animation>(source))
|
||||
{
|
||||
// Map animation channels to the skeleton nodes (by name)
|
||||
const auto& channels = sourceAnim->Data.Channels;
|
||||
for (int32 i = 0; i < channels.Count(); i++)
|
||||
{
|
||||
auto& nodeAnim = channels[i];
|
||||
for (int32 j = 0; j < nodesCount; j++)
|
||||
{
|
||||
if (StringUtils::CompareIgnoreCase(Skeleton.Nodes[j].Name.GetText(), nodeAnim.NodeName.GetText()) == 0)
|
||||
{
|
||||
result[j] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
LOG(Error, "Invalid asset type {0} to use for skeleton mapping", source->GetTypeName());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Add to cache
|
||||
_skeletonMappingCache.Add(source, result);
|
||||
source->OnUnloaded.Bind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
source->OnReloading.Bind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SkinnedModel::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
|
||||
{
|
||||
return LODs[lodIndex].Intersects(ray, world, distance, normal, mesh);
|
||||
@@ -720,6 +772,36 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
auto i = _skeletonMappingCache.Find(obj);
|
||||
ASSERT(i != _skeletonMappingCache.End());
|
||||
|
||||
// Unlink event
|
||||
obj->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
obj->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
|
||||
// Clear cache
|
||||
Allocator::Free(i->Value.Get());
|
||||
_skeletonMappingCache.Remove(i);
|
||||
}
|
||||
|
||||
uint64 SkinnedModel::GetMemoryUsage() const
|
||||
{
|
||||
Locker.Lock();
|
||||
uint64 result = BinaryAsset::GetMemoryUsage();
|
||||
result += sizeof(SkinnedModel) - sizeof(BinaryAsset);
|
||||
result += Skeleton.GetMemoryUsage();
|
||||
result += _skeletonMappingCache.Capacity() * sizeof(Dictionary<Asset*, Span<int32>>::Bucket);
|
||||
for (const auto& e : _skeletonMappingCache)
|
||||
result += e.Value.Length();
|
||||
Locker.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void SkinnedModel::SetupMaterialSlots(int32 slotsCount)
|
||||
{
|
||||
ModelBase::SetupMaterialSlots(slotsCount);
|
||||
@@ -1024,6 +1106,15 @@ void SkinnedModel::unload(bool isReloading)
|
||||
LODs.Clear();
|
||||
Skeleton.Dispose();
|
||||
_loadedLODs = 0;
|
||||
for (auto& e : _skeletonMappingCache)
|
||||
{
|
||||
e.Key->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
e.Key->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
Allocator::Free(e.Value.Get());
|
||||
}
|
||||
_skeletonMappingCache.Clear();
|
||||
}
|
||||
|
||||
bool SkinnedModel::init(AssetInitData& initData)
|
||||
|
||||
@@ -20,6 +20,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
|
||||
private:
|
||||
int32 _loadedLODs = 0;
|
||||
StreamSkinnedModelLODTask* _streamingTask = nullptr;
|
||||
Dictionary<Asset*, Span<int32>> _skeletonMappingCache;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -76,13 +77,11 @@ public:
|
||||
/// <summary>
|
||||
/// Determines whether any LOD has been initialized.
|
||||
/// </summary>
|
||||
/// <returns>True if any LOD has been initialized, otherwise false.</returns>
|
||||
bool HasAnyLODInitialized() const;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this model can be rendered.
|
||||
/// </summary>
|
||||
/// <returns>True if can render that model, otherwise false.</returns>
|
||||
FORCE_INLINE bool CanBeRendered() const
|
||||
{
|
||||
return _loadedLODs > 0;
|
||||
@@ -154,7 +153,13 @@ public:
|
||||
/// <param name="data">The data (may be missing if failed to get it).</param>
|
||||
void GetLODData(int32 lodIndex, BytesContainer& data) const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
|
||||
/// </summary>
|
||||
/// <param name="source">The source asset (animation or other skinned model) to get mapping to its skeleton.</param>
|
||||
/// <returns>The cached node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each span item is index of the source skeleton node for this skeleton.</returns>
|
||||
Span<int32> GetSkeletonMapping(Asset* source);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between the SkinnedModel and a Ray in given world using given instance.
|
||||
/// </summary>
|
||||
@@ -194,7 +199,6 @@ public:
|
||||
/// <returns>The bounding box.</returns>
|
||||
API_FUNCTION() BoundingBox GetBox(int32 lodIndex = 0) const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
|
||||
/// </summary>
|
||||
@@ -219,7 +223,6 @@ public:
|
||||
/// <param name="info">The packed drawing info data.</param>
|
||||
void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Setups the model LODs collection including meshes creation.
|
||||
/// </summary>
|
||||
@@ -244,7 +247,6 @@ public:
|
||||
API_FUNCTION() bool SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<SkeletonBone>& bones, bool autoCalculateOffsetMatrix);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
@@ -253,7 +255,6 @@ public:
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True if cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
@@ -264,8 +265,11 @@ private:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool Init(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
|
||||
|
||||
public:
|
||||
// [ModelBase]
|
||||
uint64 GetMemoryUsage() const override;
|
||||
void SetupMaterialSlots(int32 slotsCount) override;
|
||||
int32 GetLODsCount() const override;
|
||||
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
|
||||
Reference in New Issue
Block a user