// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "ModelBase.h"
#include "Engine/Graphics/Models/ModelLOD.h"
// Note: we use the first chunk as a header, next is the highest quality lod and then lower ones
//
// Example:
// Chunk 0: Header
// Chunk 1: LOD0
// Chunk 2: LOD1
// ..
//
#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1)
class Mesh;
class StreamModelLODTask;
///
/// Model asset that contains model object made of meshes which can rendered on the GPU.
///
API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase
{
DECLARE_BINARY_ASSET_HEADER(Model, 25);
friend Mesh;
friend StreamModelLODTask;
private:
int32 _loadedLODs = 0;
StreamModelLODTask* _streamingTask = nullptr;
public:
///
/// Model level of details. The first entry is the highest quality LOD0 followed by more optimized versions.
///
API_FIELD(ReadOnly) Array> LODs;
public:
///
/// Finalizes an instance of the class.
///
~Model();
public:
///
/// Gets a value indicating whether this instance is initialized.
///
FORCE_INLINE bool IsInitialized() const
{
return LODs.HasItems();
}
///
/// Gets amount of the level of details in the model
///
/// Amount of the level of details in the model
FORCE_INLINE int32 GetLODsCount() const
{
return LODs.Count();
}
///
/// Gets the amount of loaded model LODs.
///
/// Loaded LODs count
API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
{
return _loadedLODs;
}
///
/// Determines whether the specified index is a valid LOD index.
///
/// The index.
/// True if the specified index is a valid LOD index, otherwise false.
FORCE_INLINE bool IsValidLODIndex(int32 index) const
{
return Math::IsInRange(index, 0, LODs.Count() - 1);
}
///
/// Clamps the index of the LOD to be valid for rendering (only loaded LODs).
///
/// The index.
/// The resident LOD index.
FORCE_INLINE int32 ClampLODIndex(int32 index) const
{
return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1);
}
///
/// Gets index of the highest resident LOD (may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality)
///
/// LOD index
FORCE_INLINE int32 HighestResidentLODIndex() const
{
return GetLODsCount() - _loadedLODs;
}
///
/// Determines whether any LOD has been initialized.
///
/// True if any LOD has been initialized, otherwise false.
FORCE_INLINE bool HasAnyLODInitialized() const
{
return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized();
}
///
/// Determines whether this model can be rendered.
///
/// True if can render that model, otherwise false.
FORCE_INLINE bool CanBeRendered() const
{
return _loadedLODs > 0;
}
public:
///
/// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here).
///
/// Index of the LOD.
/// Task that will gather chunk data or null if already here.
ContentLoadTask* RequestLODDataAsync(int32 lodIndex)
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
return RequestChunkDataAsync(chunkIndex);
}
///
/// Gets the model LOD data (links bytes).
///
/// Index of the LOD.
/// The data (may be missing if failed to get it).
void GetLODData(int32 lodIndex, BytesContainer& data) const
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
GetChunkData(chunkIndex, data);
}
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
/// Level Of Detail index
/// True whether the two objects intersected
bool Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal, Mesh** mesh, int32 lodIndex = 0);
///
/// Gets the model bounding box in custom matrix world space.
///
/// 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.
///
/// The Level Of Detail index.
/// The bounding box.
API_FUNCTION() BoundingBox GetBox(int32 lodIndex = 0) const;
public:
///
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
///
/// 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 material to use for rendering.
/// The world transformation of the model.
/// The object static flags.
/// True if rendered geometry can receive decals, otherwise false.
API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true) const;
///
/// Draws the model.
///
/// The rendering context.
/// The packed drawing info data.
void Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info);
public:
///
/// 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);
#if USE_EDITOR
///
/// Saves this asset to the file. Supported only in Editor.
///
/// If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread.
/// True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location and it's loaded.
/// 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.
/// True if cannot save data, otherwise false.
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
#endif
private:
///
/// Initializes this model to an empty collection of LODs with meshes.
///
/// The meshes count per lod array (amount of meshes per LOD).
/// True if failed, otherwise false.
bool Init(const Span& meshesCountPerLod);
public:
// [ModelBase]
void SetupMaterialSlots(int32 slotsCount) override;
void InitAsVirtual() override;
#if USE_EDITOR
void GetReferences(Array& output) const override
{
// Base
BinaryAsset::GetReferences(output);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
{
output.Add(MaterialSlots[i].Material.GetID());
}
}
#endif
// [StreamableResource]
int32 GetMaxResidency() const override;
int32 GetCurrentResidency() const override;
int32 GetAllocatedResidency() const override;
bool CanBeUpdated() const override;
Task* UpdateAllocation(int32 residency) override;
Task* CreateStreamingTask(int32 residency) override;
protected:
// [ModelBase]
LoadResult load() override;
void unload(bool isReloading) override;
bool init(AssetInitData& initData) override;
AssetChunksFlag getChunksToPreload() const override;
};