// Copyright (c) Wojciech Figat. All rights reserved.
#pragma once
#include "ModelBase.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Graphics/Models/SkinnedMesh.h"
#include "Engine/Graphics/Models/SkeletonData.h"
///
/// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes.
///
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ModelLODBase
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ModelLODBase);
friend SkinnedModel;
private:
SkinnedModel* _model = nullptr;
public:
///
/// The meshes array.
///
API_FIELD(ReadOnly) Array Meshes;
public:
///
/// Determines if there is an intersection between the Model and a Ray in given world using given instance
///
/// The ray to test
/// World to test
/// When the method completes, contains the distance of the intersection (if any valid).
/// When the method completes, contains the intersection surface normal vector (if any valid).
/// Mesh, or null
/// True whether the two objects intersected
bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh);
///
/// Determines if there is an intersection between the Model and a Ray in given world using given instance
///
/// The ray to test
/// Instance transformation
/// When the method completes, contains the distance of the intersection (if any valid).
/// When the method completes, contains the intersection surface normal vector (if any valid).
/// Mesh, or null
/// True whether the two objects intersected
bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh);
///
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
///
/// The GPU context to draw with.
FORCE_INLINE void Render(GPUContext* context)
{
for (int32 i = 0; i < Meshes.Count(); i++)
Meshes.Get()[i].Render(context);
}
///
/// Draws all the meshes from the model LOD.
///
/// The rendering context.
/// The packed drawing info data.
/// The LOD transition dither factor.
FORCE_INLINE void Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const
{
for (int32 i = 0; i < Meshes.Count(); i++)
Meshes.Get()[i].Draw(renderContext, info, lodDitherFactor);
}
///
/// Draws all the meshes from the model LOD.
///
/// The rendering context batch.
/// The packed drawing info data.
/// The LOD transition dither factor.
FORCE_INLINE void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const
{
for (int32 i = 0; i < Meshes.Count(); i++)
Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor);
}
public:
// [ModelLODBase]
int32 GetMeshesCount() const override;
const MeshBase* GetMesh(int32 index) const override;
MeshBase* GetMesh(int32 index) override;
void GetMeshes(Array& meshes) override;
void GetMeshes(Array& meshes) const override;
};
///
/// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning.
///
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
{
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 30);
friend SkinnedMesh;
public:
// Skeleton mapping descriptor.
struct FLAXENGINE_API SkeletonMapping
{
// Target skeleton.
AssetReference TargetSkeleton;
// Source skeleton.
AssetReference SourceSkeleton;
// The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node.
Span NodesMapping;
};
private:
struct SkeletonMappingData
{
AssetReference SourceSkeleton;
Span NodesMapping;
};
Dictionary _skeletonMappingCache;
public:
///
/// Model level of details. The first entry is the highest quality LOD0 followed by more optimized versions.
///
API_FIELD(ReadOnly) Array> LODs;
///
/// The skeleton bones hierarchy.
///
SkeletonData Skeleton;
public:
///
/// Finalizes an instance of the class.
///
~SkinnedModel();
public:
///
/// Determines whether any LOD has been initialized.
///
bool HasAnyLODInitialized() const;
///
/// Gets the skeleton nodes hierarchy.
///
API_PROPERTY() const Array& GetNodes() const
{
return Skeleton.Nodes;
}
///
/// Gets the skeleton bones hierarchy.
///
API_PROPERTY() const Array& GetBones() const
{
return Skeleton.Bones;
}
///
/// Finds the node with the given name.
///
/// The name of the node.
/// The index of the node or -1 if not found.
API_FUNCTION() FORCE_INLINE int32 FindNode(const StringView& name) const
{
return Skeleton.FindNode(name);
}
///
/// Finds the bone with the given name.
///
/// The name of the node used by the bone.
/// The index of the bone or -1 if not found.
API_FUNCTION() FORCE_INLINE int32 FindBone(const StringView& name) const
{
return FindBone(FindNode(name));
}
///
/// Finds the bone that is using a given node index.
///
/// The index of the node.
/// The index of the bone or -1 if not found.
API_FUNCTION() FORCE_INLINE int32 FindBone(int32 nodeIndex) const
{
return Skeleton.FindBone(nodeIndex);
}
///
/// Gets the blend shapes names used by the skinned model meshes (from LOD 0 only).
///
API_PROPERTY() Array GetBlendShapes();
public:
///
/// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
///
/// The source asset (animation or other skinned model) to get mapping to its skeleton.
/// Enables automatic skeleton retargeting based on nodes names. Can be disabled to query existing skeleton mapping or return null if not defined.
/// The skeleton mapping for the source asset into this skeleton.
SkeletonMapping GetSkeletonMapping(Asset* source, bool autoRetarget = true);
///
/// Determines if there is an intersection between the SkinnedModel and a Ray in given world using given instance.
///
/// The ray to test
/// World to test
/// When the method completes, contains the distance of the intersection (if any valid).
/// When the method completes, contains the intersection surface normal vector (if any valid).
/// Mesh, or null
/// Level Of Detail index
/// True whether the two objects intersected
bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex = 0);
///
/// Determines if there is an intersection between the SkinnedModel and a Ray in given world using given instance.
///
/// The ray to test
/// Instance transformation
/// When the method completes, contains the distance of the intersection (if any valid).
/// When the method completes, contains the intersection surface normal vector (if any valid).
/// Mesh, or null
/// Level Of Detail index
/// True whether the two objects intersected
bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex = 0);
///
/// Gets the model bounding box in custom matrix world space (rig pose transformed by matrix, not animated).
///
/// The transformation matrix.
/// The Level Of Detail index.
/// The bounding box.
API_FUNCTION() BoundingBox GetBox(const Matrix& world, int32 lodIndex = 0) const;
///
/// Gets the model bounding box in local space (rig pose, not animated).
///
/// The Level Of Detail index.
/// The bounding box.
API_FUNCTION() BoundingBox GetBox(int32 lodIndex = 0) const;
///
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
///
/// The GPU context to draw with.
/// The Level Of Detail index.
void Render(GPUContext* context, int32 lodIndex = 0)
{
LODs[lodIndex].Render(context);
}
///
/// Draws the model.
///
/// The rendering context.
/// The packed drawing info data.
void Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info);
///
/// Draws the model.
///
/// The rendering context batch.
/// The packed drawing info data.
void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info);
///
/// Setups the model LODs collection including meshes creation.
///
/// The meshes count per lod array (amount of meshes per LOD).
/// True if failed, otherwise false.
API_FUNCTION() bool SetupLODs(const Span& meshesCountPerLod);
///
/// Setups the skinned model skeleton. Uses the same nodes layout for skeleton bones and calculates the offset matrix by auto.
///
/// The nodes hierarchy. The first node must be a root one (with parent index equal -1).
/// True if failed, otherwise false.
API_FUNCTION() bool SetupSkeleton(const Array& nodes);
///
/// Setups the skinned model skeleton.
///
/// The nodes hierarchy. The first node must be a root one (with parent index equal -1).
/// The bones hierarchy.
/// If true then the OffsetMatrix for each bone will be auto-calculated by the engine, otherwise the provided values will be used.
/// True if failed, otherwise false.
API_FUNCTION() bool SetupSkeleton(const Array& nodes, const Array& bones, bool autoCalculateOffsetMatrix);
private:
///
/// Initializes this skinned model to an empty collection of meshes. Ensure to init SkeletonData manually after the call.
///
/// The meshes count per lod array (amount of meshes per LOD).
/// True if failed, otherwise false.
bool Init(const Span& meshesCountPerLod);
// [ModelBase]
bool LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) override;
bool LoadHeader(ReadStream& stream, byte& headerVersion);
#if USE_EDITOR
friend class ImportModel;
bool SaveHeader(WriteStream& stream) const override;
static bool SaveHeader(WriteStream& stream, const ModelData& modelData);
bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const override;
static bool SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex);
#endif
void ClearSkeletonMapping();
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
#if USE_EDITOR
public:
// Skeleton retargeting setup (internal use only - accessed by Editor)
API_STRUCT(NoDefault) struct SkeletonRetarget
{
DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonRetarget);
// Source asset id.
API_FIELD() Guid SourceAsset;
// Skeleton asset id to use for remapping.
API_FIELD() Guid SkeletonAsset;
// Skeleton nodes remapping table (maps this skeleton node name to other skeleton node).
API_FIELD() Dictionary NodesMapping;
};
// Gets or sets the skeleton retarget entries (accessed in Editor only).
API_PROPERTY() const Array& GetSkeletonRetargets() const { return _skeletonRetargets; }
API_PROPERTY() void SetSkeletonRetargets(const Array& value) { Locker.Lock(); _skeletonRetargets = value; ClearSkeletonMapping(); Locker.Unlock(); }
private:
#else
struct SkeletonRetarget
{
Guid SourceAsset, SkeletonAsset;
Dictionary NodesMapping;
};
#endif
Array _skeletonRetargets;
public:
// [ModelBase]
uint64 GetMemoryUsage() const override;
void SetupMaterialSlots(int32 slotsCount) override;
int32 GetLODsCount() const override;
const ModelLODBase* GetLOD(int32 lodIndex) const override;
ModelLODBase* GetLOD(int32 lodIndex) override;
const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override;
MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override;
void GetMeshes(Array& meshes, int32 lodIndex = 0) const override;
void GetMeshes(Array& meshes, int32 lodIndex = 0) override;
void InitAsVirtual() override;
// [StreamableResource]
int32 GetMaxResidency() const override;
int32 GetAllocatedResidency() const override;
protected:
// [ModelBase]
LoadResult load() override;
void unload(bool isReloading) override;
AssetChunksFlag getChunksToPreload() const override;
};