// 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; };