diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp
index 37e0d1cc8..8ba26658c 100644
--- a/Source/Engine/Content/Assets/Model.cpp
+++ b/Source/Engine/Content/Assets/Model.cpp
@@ -16,101 +16,16 @@
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Async/GPUTask.h"
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
+#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
-#include "Engine/Tools/ModelTool/MeshAccelerationStructure.h"
-#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
-#include "Engine/Threading/ThreadPoolTask.h"
-#define STREAM_TASK_BASE ThreadPoolTask
-#else
-#include "Engine/Threading/MainThreadTask.h"
-#define STREAM_TASK_BASE MainThreadTask
-#endif
-
-#define CHECK_INVALID_BUFFER(model, buffer) \
- if (buffer->IsValidFor(model) == false) \
- { \
- buffer->Setup(model); \
- }
REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase");
-///
-/// Model LOD streaming task.
-///
-class StreamModelLODTask : public STREAM_TASK_BASE
-{
-private:
- WeakAssetReference _asset;
- int32 _lodIndex;
- FlaxStorage::LockData _dataLock;
-
-public:
- StreamModelLODTask(Model* model, int32 lodIndex)
- : _asset(model)
- , _lodIndex(lodIndex)
- , _dataLock(model->Storage->Lock())
- {
- }
-
-public:
- bool HasReference(Object* resource) const override
- {
- return _asset == resource;
- }
-
- bool Run() override
- {
- AssetReference model = _asset.Get();
- if (model == nullptr)
- return true;
-
- // Get data
- BytesContainer data;
- model->GetLODData(_lodIndex, data);
- if (data.IsInvalid())
- {
- LOG(Warning, "Missing data chunk");
- return true;
- }
- MemoryReadStream stream(data.Get(), data.Length());
-
- // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
-
- // Load model LOD (initialize vertex and index buffers)
- if (model->LODs[_lodIndex].Load(stream))
- {
- LOG(Warning, "Cannot load LOD{1} for model \'{0}\'", model->ToString(), _lodIndex);
- return true;
- }
-
- // Update residency level
- model->_loadedLODs++;
- model->ResidencyChanged();
-
- return false;
- }
-
- void OnEnd() override
- {
- // Unlink
- if (_asset)
- {
- ASSERT(_asset->_streamingTask == this);
- _asset->_streamingTask = nullptr;
- _asset = nullptr;
- }
- _dataLock.Release();
-
- // Base
- STREAM_TASK_BASE::OnEnd();
- }
-};
-
class StreamModelSDFTask : public GPUUploadTextureMipTask
{
private:
@@ -211,9 +126,10 @@ FORCE_INLINE void ModelDraw(Model* model, const RenderContext& renderContext, co
ASSERT(info.Buffer);
if (!model->CanBeRendered())
return;
+ if (!info.Buffer->IsValidFor(model))
+ info.Buffer->Setup(model);
const auto frame = Engine::FrameCount;
const auto modelFrame = info.DrawState->PrevFrame + 1;
- CHECK_INVALID_BUFFER(model, info.Buffer);
// Select a proper LOD index (model may be culled)
int32 lodIndex;
@@ -719,16 +635,14 @@ bool Model::Init(const Span& meshesCountPerLod)
SAFE_DELETE_GPU_RESOURCE(SDF.Texture);
// Setup LODs
- for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
- LODs[lodIndex].Dispose();
LODs.Resize(meshesCountPerLod.Length());
+ _initialized = true;
// Setup meshes
for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++)
{
auto& lod = LODs[lodIndex];
- lod._model = this;
- lod._lodIndex = lodIndex;
+ lod.Link(this, lodIndex);
lod.ScreenSize = 1.0f;
const int32 meshesCount = meshesCountPerLod[lodIndex];
if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES)
@@ -737,7 +651,7 @@ bool Model::Init(const Span& meshesCountPerLod)
lod.Meshes.Resize(meshesCount);
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
- lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, 0, BoundingBox::Zero, BoundingSphere::Empty, true);
+ lod.Meshes[meshIndex].Link(this, lodIndex, meshIndex);
}
}
@@ -787,99 +701,16 @@ void Model::InitAsVirtual()
BinaryAsset::InitAsVirtual();
}
-void Model::CancelStreaming()
-{
- Asset::CancelStreaming();
- CancelStreamingTasks();
-}
-
-#if USE_EDITOR
-
-void Model::GetReferences(Array& assets, Array& files) const
-{
- // Base
- BinaryAsset::GetReferences(assets, files);
-
- for (int32 i = 0; i < MaterialSlots.Count(); i++)
- assets.Add(MaterialSlots[i].Material.GetID());
-}
-
-#endif
-
int32 Model::GetMaxResidency() const
{
return LODs.Count();
}
-int32 Model::GetCurrentResidency() const
-{
- return _loadedLODs;
-}
-
int32 Model::GetAllocatedResidency() const
{
return LODs.Count();
}
-bool Model::CanBeUpdated() const
-{
- // Check if is ready and has no streaming tasks running
- return IsInitialized() && _streamingTask == nullptr;
-}
-
-Task* Model::UpdateAllocation(int32 residency)
-{
- // Models are not using dynamic allocation feature
- return nullptr;
-}
-
-Task* Model::CreateStreamingTask(int32 residency)
-{
- ScopeLock lock(Locker);
-
- ASSERT(IsInitialized() && Math::IsInRange(residency, 0, LODs.Count()) && _streamingTask == nullptr);
- Task* result = nullptr;
- const int32 lodCount = residency - GetCurrentResidency();
-
- // Switch if go up or down with residency
- if (lodCount > 0)
- {
- // Allow only to change LODs count by 1
- ASSERT(Math::Abs(lodCount) == 1);
-
- int32 lodIndex = HighestResidentLODIndex() - 1;
-
- // Request LOD data
- result = (Task*)RequestLODDataAsync(lodIndex);
-
- // Add upload data task
- _streamingTask = New(this, lodIndex);
- if (result)
- result->ContinueWith(_streamingTask);
- else
- result = _streamingTask;
- }
- else
- {
- // Do the quick data release
- for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++)
- LODs[i].Unload();
- _loadedLODs = residency;
- ResidencyChanged();
- }
-
- return result;
-}
-
-void Model::CancelStreamingTasks()
-{
- if (_streamingTask)
- {
- _streamingTask->Cancel();
- ASSERT_LOW_LAYER(_streamingTask == nullptr);
- }
-}
-
Asset::LoadResult Model::load()
{
// Get header chunk
@@ -922,6 +753,7 @@ Asset::LoadResult Model::load()
if (lods == 0 || lods > MODEL_MAX_LODS)
return LoadResult::InvalidData;
LODs.Resize(lods);
+ _initialized = true;
// For each LOD
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
@@ -946,6 +778,9 @@ Asset::LoadResult Model::load()
// For each mesh
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
+ Mesh& mesh = lod.Meshes[meshIndex];
+ mesh.Link(this, lodIndex, meshIndex);
+
// Material Slot index
int32 materialSlotIndex;
stream->ReadInt32(&materialSlotIndex);
@@ -954,19 +789,18 @@ Asset::LoadResult Model::load()
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
return LoadResult::InvalidData;
}
+ mesh.SetMaterialSlotIndex(materialSlotIndex);
- // Box
+ // Bounds
BoundingBox box;
stream->ReadBoundingBox(&box);
-
- // Sphere
BoundingSphere sphere;
stream->ReadBoundingSphere(&sphere);
+ mesh.SetBounds(box, sphere);
// Has Lightmap UVs
bool hasLightmapUVs = stream->ReadBool();
-
- lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, materialSlotIndex, box, sphere, hasLightmapUVs);
+ mesh.LightmapUVsIndex = hasLightmapUVs ? 1 : -1;
}
}
@@ -1039,33 +873,11 @@ Asset::LoadResult Model::load()
void Model::unload(bool isReloading)
{
- // End streaming (if still active)
- if (_streamingTask != nullptr)
- {
- // Cancel streaming task
- _streamingTask->Cancel();
- _streamingTask = nullptr;
- }
+ ModelBase::unload(isReloading);
// Cleanup
SAFE_DELETE_GPU_RESOURCE(SDF.Texture);
- MaterialSlots.Resize(0);
- for (int32 i = 0; i < LODs.Count(); i++)
- LODs[i].Dispose();
LODs.Clear();
- _loadedLODs = 0;
-}
-
-bool Model::init(AssetInitData& initData)
-{
- // Validate
- if (initData.SerializedVersion != SerializedVersion)
- {
- LOG(Error, "Invalid serialized model version.");
- return true;
- }
-
- return false;
}
AssetChunksFlag Model::getChunksToPreload() const
@@ -1074,32 +886,101 @@ AssetChunksFlag Model::getChunksToPreload() const
return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15);
}
-void ModelBase::SetupMaterialSlots(int32 slotsCount)
+bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh)
{
- CHECK(slotsCount >= 0 && slotsCount < 4096);
- if (!IsVirtual() && WaitForLoaded())
- return;
-
- ScopeLock lock(Locker);
-
- const int32 prevCount = MaterialSlots.Count();
- MaterialSlots.Resize(slotsCount, false);
-
- // Initialize slot names
- for (int32 i = prevCount; i < slotsCount; i++)
- MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1);
-}
-
-MaterialSlot* ModelBase::GetSlot(const StringView& name)
-{
- MaterialSlot* result = nullptr;
- for (auto& slot : MaterialSlots)
+ bool result = false;
+ Real closest = MAX_Real;
+ Vector3 closestNormal = Vector3::Up;
+ for (int32 i = 0; i < Meshes.Count(); i++)
{
- if (slot.Name == name)
+ Real dst;
+ Vector3 nrm;
+ if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
{
- result = &slot;
- break;
+ result = true;
+ *mesh = &Meshes[i];
+ closest = dst;
+ closestNormal = nrm;
}
}
+ distance = closest;
+ normal = closestNormal;
return result;
}
+
+bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh)
+{
+ bool result = false;
+ Real closest = MAX_Real;
+ Vector3 closestNormal = Vector3::Up;
+ for (int32 i = 0; i < Meshes.Count(); i++)
+ {
+ Real dst;
+ Vector3 nrm;
+ if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
+ {
+ result = true;
+ *mesh = &Meshes[i];
+ closest = dst;
+ closestNormal = nrm;
+ }
+ }
+ distance = closest;
+ normal = closestNormal;
+ return result;
+}
+
+BoundingBox ModelLOD::GetBox(const Matrix& world) const
+{
+ Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
+ Vector3 corners[8];
+ for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
+ {
+ const auto& mesh = Meshes[meshIndex];
+ mesh.GetBox().GetCorners(corners);
+ for (int32 i = 0; i < 8; i++)
+ {
+ Vector3::Transform(corners[i], world, tmp);
+ min = Vector3::Min(min, tmp);
+ max = Vector3::Max(max, tmp);
+ }
+ }
+ return BoundingBox(min, max);
+}
+
+BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
+{
+ Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
+ Vector3 corners[8];
+ for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
+ {
+ const auto& mesh = Meshes[meshIndex];
+ BoundingBox box = mesh.GetBox();
+ if (deformation)
+ deformation->GetBounds(_lodIndex, meshIndex, box);
+ box.GetCorners(corners);
+ for (int32 i = 0; i < 8; i++)
+ {
+ transform.LocalToWorld(corners[i], tmp);
+ min = Vector3::Min(min, tmp);
+ max = Vector3::Max(max, tmp);
+ }
+ }
+ return BoundingBox(min, max);
+}
+
+BoundingBox ModelLOD::GetBox() const
+{
+ Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
+ Vector3 corners[8];
+ for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
+ {
+ Meshes[meshIndex].GetBox().GetCorners(corners);
+ for (int32 i = 0; i < 8; i++)
+ {
+ min = Vector3::Min(min, corners[i]);
+ max = Vector3::Max(max, corners[i]);
+ }
+ }
+ return BoundingBox(min, max);
+}
diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h
index dd52ba34a..3f78f973d 100644
--- a/Source/Engine/Content/Assets/Model.h
+++ b/Source/Engine/Content/Assets/Model.h
@@ -6,7 +6,6 @@
#include "Engine/Graphics/Models/ModelLOD.h"
class Mesh;
-class StreamModelLODTask;
///
/// Model asset that contains model object made of meshes which can rendered on the GPU.
@@ -15,10 +14,6 @@ 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:
///
@@ -38,40 +33,6 @@ public:
~Model();
public:
- ///
- /// Gets a value indicating whether this instance is initialized.
- ///
- FORCE_INLINE bool IsInitialized() const
- {
- return LODs.HasItems();
- }
-
- ///
- /// Gets the amount of loaded model LODs.
- ///
- API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
- {
- return _loadedLODs;
- }
-
- ///
- /// 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)
- ///
- FORCE_INLINE int32 HighestResidentLODIndex() const
- {
- return LODs.Count() - _loadedLODs;
- }
-
///
/// Determines whether any LOD has been initialized.
///
@@ -80,37 +41,6 @@ public:
return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized();
}
- ///
- /// Determines whether this model can be rendered.
- ///
- 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.
@@ -250,24 +180,14 @@ public:
int32 GetLODsCount() const override;
void GetMeshes(Array& meshes, int32 lodIndex = 0) override;
void InitAsVirtual() override;
- void CancelStreaming() override;
-#if USE_EDITOR
- void GetReferences(Array& assets, Array& files) const override;
-#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;
- void CancelStreamingTasks() override;
protected:
// [ModelBase]
LoadResult load() override;
void unload(bool isReloading) override;
- bool init(AssetInitData& initData) override;
AssetChunksFlag getChunksToPreload() const override;
};
diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp
new file mode 100644
index 000000000..6e7fc3457
--- /dev/null
+++ b/Source/Engine/Content/Assets/ModelBase.cpp
@@ -0,0 +1,311 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#include "ModelBase.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Content/WeakAssetReference.h"
+#include "Engine/Serialization/MemoryReadStream.h"
+#include "Engine/Graphics/Config.h"
+#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
+#include "Engine/Threading/ThreadPoolTask.h"
+#define STREAM_TASK_BASE ThreadPoolTask
+#else
+#include "Engine/Threading/MainThreadTask.h"
+#define STREAM_TASK_BASE MainThreadTask
+#endif
+#include "SkinnedModel.h" // TODO: remove this
+#include "Model.h" // TODO: remove this
+
+///
+/// Model LOD streaming task.
+///
+class StreamModelLODTask : public STREAM_TASK_BASE
+{
+private:
+ WeakAssetReference _model;
+ int32 _lodIndex;
+ FlaxStorage::LockData _dataLock;
+
+public:
+ StreamModelLODTask(ModelBase* model, int32 lodIndex)
+ : _model(model)
+ , _lodIndex(lodIndex)
+ , _dataLock(model->Storage->Lock())
+ {
+ }
+
+ bool HasReference(Object* resource) const override
+ {
+ return _model == resource;
+ }
+
+ bool Run() override
+ {
+ AssetReference model = _model.Get();
+ if (model == nullptr)
+ return true;
+
+ // Get data
+ BytesContainer data;
+ model->GetLODData(_lodIndex, data);
+ if (data.IsInvalid())
+ {
+ LOG(Warning, "Missing data chunk");
+ return true;
+ }
+ MemoryReadStream stream(data.Get(), data.Length());
+
+ // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
+
+ // Load model LOD (initialize vertex and index buffers)
+ // TODO: reformat model data storage to be the same for both assets (only custom per-mesh type data like BlendShapes)
+ Array meshes;
+ model->GetMeshes(meshes, _lodIndex);
+ if (model->Is())
+ {
+ byte version = stream.ReadByte();
+ for (int32 i = 0; i < meshes.Count(); i++)
+ {
+ auto& mesh = (SkinnedMesh&)*meshes[i];
+
+ // #MODEL_DATA_FORMAT_USAGE
+ uint32 vertices;
+ stream.ReadUint32(&vertices);
+ uint32 triangles;
+ stream.ReadUint32(&triangles);
+ uint16 blendShapesCount;
+ stream.ReadUint16(&blendShapesCount);
+ if (blendShapesCount != mesh.BlendShapes.Count())
+ {
+ LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Incorrect blend shapes amount: {} (expected: {})", i, _lodIndex, model->ToString(), blendShapesCount, mesh.BlendShapes.Count());
+ return true;
+ }
+ for (auto& blendShape : mesh.BlendShapes)
+ {
+ blendShape.UseNormals = stream.ReadBool();
+ stream.ReadUint32(&blendShape.MinVertexIndex);
+ stream.ReadUint32(&blendShape.MaxVertexIndex);
+ uint32 blendShapeVertices;
+ stream.ReadUint32(&blendShapeVertices);
+ blendShape.Vertices.Resize(blendShapeVertices);
+ stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
+ }
+ const uint32 indicesCount = triangles * 3;
+ const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
+ const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
+ if (vertices == 0 || triangles == 0)
+ return true;
+ const auto vb0 = stream.Move(vertices);
+ const auto ib = stream.Move(indicesCount * ibStride);
+
+ // Setup GPU resources
+ if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer))
+ {
+ LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles);
+ return true;
+ }
+ }
+ }
+ else
+ {
+ for (int32 i = 0; i < meshes.Count(); i++)
+ {
+ auto& mesh = (Mesh&)*meshes[i];
+
+ // #MODEL_DATA_FORMAT_USAGE
+ uint32 vertices;
+ stream.ReadUint32(&vertices);
+ uint32 triangles;
+ stream.ReadUint32(&triangles);
+ uint32 indicesCount = triangles * 3;
+ bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
+ uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
+ if (vertices == 0 || triangles == 0)
+ return true;
+ auto vb0 = stream.Move(vertices);
+ auto vb1 = stream.Move(vertices);
+ bool hasColors = stream.ReadBool();
+ VB2ElementType18* vb2 = nullptr;
+ if (hasColors)
+ {
+ vb2 = stream.Move(vertices);
+ }
+ auto ib = stream.Move(indicesCount * ibStride);
+
+ // Setup GPU resources
+ if (mesh.Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer))
+ {
+ LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles);
+ return true;
+ }
+ }
+ }
+
+ // Update residency level
+ model->_loadedLODs++;
+ model->ResidencyChanged();
+
+ return false;
+ }
+
+ void OnEnd() override
+ {
+ // Unlink
+ ModelBase* model = _model.Get();
+ if (model)
+ {
+ ASSERT(model->_streamingTask == this);
+ model->_streamingTask = nullptr;
+ _model = nullptr;
+ }
+ _dataLock.Release();
+
+ STREAM_TASK_BASE::OnEnd();
+ }
+};
+
+void ModelBase::SetupMaterialSlots(int32 slotsCount)
+{
+ CHECK(slotsCount >= 0 && slotsCount < 4096);
+ if (!IsVirtual() && WaitForLoaded())
+ return;
+
+ ScopeLock lock(Locker);
+
+ const int32 prevCount = MaterialSlots.Count();
+ MaterialSlots.Resize(slotsCount, false);
+
+ // Initialize slot names
+ for (int32 i = prevCount; i < slotsCount; i++)
+ MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1);
+}
+
+MaterialSlot* ModelBase::GetSlot(const StringView& name)
+{
+ MaterialSlot* result = nullptr;
+ for (auto& slot : MaterialSlots)
+ {
+ if (slot.Name == name)
+ {
+ result = &slot;
+ break;
+ }
+ }
+ return result;
+}
+
+ContentLoadTask* ModelBase::RequestLODDataAsync(int32 lodIndex)
+{
+ const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
+ return RequestChunkDataAsync(chunkIndex);
+}
+
+void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const
+{
+ const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
+ GetChunkData(chunkIndex, data);
+}
+
+void ModelBase::CancelStreaming()
+{
+ BinaryAsset::CancelStreaming();
+
+ CancelStreamingTasks();
+}
+
+#if USE_EDITOR
+
+void ModelBase::GetReferences(Array& assets, Array& files) const
+{
+ BinaryAsset::GetReferences(assets, files);
+
+ for (auto& slot : MaterialSlots)
+ assets.Add(slot.Material.GetID());
+}
+
+#endif
+
+int32 ModelBase::GetCurrentResidency() const
+{
+ return _loadedLODs;
+}
+
+bool ModelBase::CanBeUpdated() const
+{
+ // Check if is ready and has no streaming tasks running
+ return IsInitialized() && _streamingTask == nullptr;
+}
+
+Task* ModelBase::UpdateAllocation(int32 residency)
+{
+ // Models are not using dynamic allocation feature
+ return nullptr;
+}
+
+Task* ModelBase::CreateStreamingTask(int32 residency)
+{
+ ScopeLock lock(Locker);
+
+ const int32 lodMax = GetLODsCount();
+ ASSERT(IsInitialized() && Math::IsInRange(residency, 0, lodMax) && _streamingTask == nullptr);
+ Task* result = nullptr;
+ const int32 lodCount = residency - GetCurrentResidency();
+
+ // Switch if go up or down with residency
+ if (lodCount > 0)
+ {
+ // Allow only to change LODs count by 1
+ ASSERT(Math::Abs(lodCount) == 1);
+
+ int32 lodIndex = HighestResidentLODIndex() - 1;
+
+ // Request LOD data
+ result = (Task*)RequestLODDataAsync(lodIndex);
+
+ // Add upload data task
+ _streamingTask = New(this, lodIndex);
+ if (result)
+ result->ContinueWith(_streamingTask);
+ else
+ result = _streamingTask;
+ }
+ else
+ {
+ // Do the quick data release
+ Array meshes;
+ for (int32 i = HighestResidentLODIndex(); i < lodMax - residency; i++)
+ {
+ GetMeshes(meshes, i);
+ for (MeshBase* mesh : meshes)
+ mesh->Release();
+ }
+ _loadedLODs = residency;
+ ResidencyChanged();
+ }
+
+ return result;
+}
+
+void ModelBase::CancelStreamingTasks()
+{
+ if (_streamingTask)
+ {
+ _streamingTask->Cancel();
+ ASSERT_LOW_LAYER(_streamingTask == nullptr);
+ }
+}
+
+void ModelBase::unload(bool isReloading)
+{
+ // End streaming (if still active)
+ if (_streamingTask != nullptr)
+ {
+ // Cancel streaming task
+ _streamingTask->Cancel();
+ _streamingTask = nullptr;
+ }
+
+ // Cleanup
+ MaterialSlots.Resize(0);
+ _initialized = false;
+ _loadedLODs = 0;
+}
diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h
index a0d0091a5..ab683c116 100644
--- a/Source/Engine/Content/Assets/ModelBase.h
+++ b/Source/Engine/Content/Assets/ModelBase.h
@@ -4,6 +4,7 @@
#include "../BinaryAsset.h"
#include "Engine/Core/Collections/Array.h"
+#include "Engine/Graphics/Models/Config.h"
#include "Engine/Graphics/Models/MaterialSlot.h"
#include "Engine/Streaming/StreamableResource.h"
@@ -26,6 +27,14 @@ struct RenderContextBatch;
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset, public StreamableResource
{
DECLARE_ASSET_HEADER(ModelBase);
+ friend MeshBase;
+ friend class StreamModelLODTask;
+
+protected:
+ bool _initialized = false;
+ int32 _loadedLODs = 0;
+ StreamModelLODTask* _streamingTask = nullptr;
+
public:
///
/// The Sign Distant Field (SDF) data for the model.
@@ -106,6 +115,48 @@ public:
return MaterialSlots.Count();
}
+ ///
+ /// Gets a value indicating whether this instance is initialized.
+ ///
+ FORCE_INLINE bool IsInitialized() const
+ {
+ return _initialized;
+ }
+
+ ///
+ /// 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(), GetLODsCount() - 1);
+ }
+
+ ///
+ /// Gets index of the highest resident LOD (it may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality)
+ ///
+ FORCE_INLINE int32 HighestResidentLODIndex() const
+ {
+ return GetLODsCount() - _loadedLODs;
+ }
+
+ ///
+ /// Gets the amount of loaded model LODs.
+ ///
+ API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
+ {
+ return _loadedLODs;
+ }
+
+ ///
+ /// Determines whether this model can be rendered.
+ ///
+ FORCE_INLINE bool CanBeRendered() const
+ {
+ return _loadedLODs > 0;
+ }
+
///
/// Resizes the material slots collection. Updates meshes that were using removed slots.
///
@@ -127,4 +178,37 @@ public:
/// Gets the meshes for a particular LOD index.
///
virtual void GetMeshes(Array& meshes, int32 lodIndex = 0) = 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);
+
+ ///
+ /// Gets the model LOD data (links bytes).
+ ///
+ /// Index of the LOD.
+ /// The data (it may be missing if failed to get it).
+ void GetLODData(int32 lodIndex, BytesContainer& data) const;
+
+public:
+ // [BinaryAsset]
+ void CancelStreaming() override;
+#if USE_EDITOR
+ void GetReferences(Array& assets, Array& files) const override;
+#endif
+
+ // [StreamableResource]
+ int32 GetCurrentResidency() const override;
+ bool CanBeUpdated() const override;
+ Task* UpdateAllocation(int32 residency) override;
+ Task* CreateStreamingTask(int32 residency) override;
+ void CancelStreamingTasks() override;
+
+protected:
+ // [BinaryAsset]
+ void unload(bool isReloading) override;
};
diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp
index 80131b03b..0b9c69275 100644
--- a/Source/Engine/Content/Assets/SkinnedModel.cpp
+++ b/Source/Engine/Content/Assets/SkinnedModel.cpp
@@ -13,101 +13,13 @@
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
#include "Engine/Graphics/Models/Config.h"
#include "Engine/Content/Content.h"
-#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
+#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
-#define CHECK_INVALID_BUFFER(model, buffer) \
- if (buffer->IsValidFor(model) == false) \
- { \
- buffer->Setup(model); \
- }
-
-///
-/// Skinned model data streaming task
-///
-class StreamSkinnedModelLODTask : public ThreadPoolTask
-{
-private:
- WeakAssetReference _asset;
- int32 _lodIndex;
- FlaxStorage::LockData _dataLock;
-
-public:
- ///
- /// Init
- ///
- /// Parent model
- /// LOD to stream index
- StreamSkinnedModelLODTask(SkinnedModel* model, int32 lodIndex)
- : _asset(model)
- , _lodIndex(lodIndex)
- , _dataLock(model->Storage->Lock())
- {
- }
-
-public:
- // [ThreadPoolTask]
- bool HasReference(Object* resource) const override
- {
- return _asset == resource;
- }
-
-protected:
- // [ThreadPoolTask]
- bool Run() override
- {
- AssetReference model = _asset.Get();
- if (model == nullptr)
- {
- return true;
- }
-
- // Get data
- BytesContainer data;
- model->GetLODData(_lodIndex, data);
- if (data.IsInvalid())
- {
- LOG(Warning, "Missing data chunk");
- return true;
- }
- MemoryReadStream stream(data.Get(), data.Length());
-
- // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
-
- // Load model LOD (initialize vertex and index buffers)
- if (model->LODs[_lodIndex].Load(stream))
- {
- LOG(Warning, "Cannot load LOD{1} for model \'{0}\'", model->ToString(), _lodIndex);
- return true;
- }
-
- // Update residency level
- model->_loadedLODs++;
- model->ResidencyChanged();
-
- return false;
- }
-
- void OnEnd() override
- {
- // Unlink
- if (_asset)
- {
- ASSERT(_asset->_streamingTask == this);
- _asset->_streamingTask = nullptr;
- _asset = nullptr;
- }
- _dataLock.Release();
-
- // Base
- ThreadPoolTask::OnEnd();
- }
-};
-
REGISTER_BINARY_ASSET_WITH_UPGRADER(SkinnedModel, "FlaxEngine.SkinnedModel", SkinnedModelAssetUpgrader, true);
SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info)
@@ -143,18 +55,6 @@ Array SkinnedModel::GetBlendShapes()
return result;
}
-ContentLoadTask* SkinnedModel::RequestLODDataAsync(int32 lodIndex)
-{
- const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
- return RequestChunkDataAsync(chunkIndex);
-}
-
-void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const
-{
- const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
- GetChunkData(chunkIndex, data);
-}
-
SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget)
{
SkeletonMapping mapping;
@@ -328,9 +228,10 @@ FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& ren
ASSERT(info.Buffer);
if (!model->CanBeRendered())
return;
+ if (!info.Buffer->IsValidFor(model))
+ info.Buffer->Setup(model);
const auto frame = Engine::FrameCount;
const auto modelFrame = info.DrawState->PrevFrame + 1;
- CHECK_INVALID_BUFFER(model, info.Buffer);
// Select a proper LOD index (model may be culled)
int32 lodIndex;
@@ -846,8 +747,6 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod)
// Setup
MaterialSlots.Resize(1);
MinScreenSize = 0.0f;
- for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
- LODs[lodIndex].Dispose();
LODs.Resize(meshesCountPerLod.Length());
_initialized = true;
@@ -865,7 +764,7 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod)
lod.Meshes.Resize(meshesCount);
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
- lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, 0, BoundingBox::Zero, BoundingSphere::Empty);
+ lod.Meshes[meshIndex].Link(this, lodIndex, meshIndex);
}
}
@@ -971,99 +870,16 @@ void SkinnedModel::InitAsVirtual()
BinaryAsset::InitAsVirtual();
}
-void SkinnedModel::CancelStreaming()
-{
- Asset::CancelStreaming();
- CancelStreamingTasks();
-}
-
-#if USE_EDITOR
-
-void SkinnedModel::GetReferences(Array& assets, Array& files) const
-{
- // Base
- BinaryAsset::GetReferences(assets, files);
-
- for (int32 i = 0; i < MaterialSlots.Count(); i++)
- assets.Add(MaterialSlots[i].Material.GetID());
-}
-
-#endif
-
int32 SkinnedModel::GetMaxResidency() const
{
return LODs.Count();
}
-int32 SkinnedModel::GetCurrentResidency() const
-{
- return _loadedLODs;
-}
-
int32 SkinnedModel::GetAllocatedResidency() const
{
return LODs.Count();
}
-bool SkinnedModel::CanBeUpdated() const
-{
- // Check if is ready and has no streaming tasks running
- return IsInitialized() && _streamingTask == nullptr;
-}
-
-Task* SkinnedModel::UpdateAllocation(int32 residency)
-{
- // SkinnedModels are not using dynamic allocation feature
- return nullptr;
-}
-
-Task* SkinnedModel::CreateStreamingTask(int32 residency)
-{
- ScopeLock lock(Locker);
-
- ASSERT(IsInitialized() && Math::IsInRange(residency, 0, LODs.Count()) && _streamingTask == nullptr);
- Task* result = nullptr;
- const int32 lodCount = residency - GetCurrentResidency();
-
- // Switch if go up or down with residency
- if (lodCount > 0)
- {
- // Allow only to change LODs count by 1
- ASSERT(Math::Abs(lodCount) == 1);
-
- int32 lodIndex = HighestResidentLODIndex() - 1;
-
- // Request LOD data
- result = (Task*)RequestLODDataAsync(lodIndex);
-
- // Add upload data task
- _streamingTask = New(this, lodIndex);
- if (result)
- result->ContinueWith(_streamingTask);
- else
- result = _streamingTask;
- }
- else
- {
- // Do the quick data release
- for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++)
- LODs[i].Unload();
- _loadedLODs = residency;
- ResidencyChanged();
- }
-
- return result;
-}
-
-void SkinnedModel::CancelStreamingTasks()
-{
- if (_streamingTask)
- {
- _streamingTask->Cancel();
- ASSERT_LOW_LAYER(_streamingTask == nullptr);
- }
-}
-
Asset::LoadResult SkinnedModel::load()
{
// Get header chunk
@@ -1134,7 +950,8 @@ Asset::LoadResult SkinnedModel::load()
// For each mesh
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
- auto& mesh = lod.Meshes[meshIndex];
+ SkinnedMesh& mesh = lod.Meshes[meshIndex];
+ mesh.Link(this, lodIndex, meshIndex);
// Material Slot index
int32 materialSlotIndex;
@@ -1144,17 +961,14 @@ Asset::LoadResult SkinnedModel::load()
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
return LoadResult::InvalidData;
}
+ mesh.SetMaterialSlotIndex(materialSlotIndex);
- // Box
+ // Bounds
BoundingBox box;
stream->ReadBoundingBox(&box);
-
- // Sphere
BoundingSphere sphere;
stream->ReadBoundingSphere(&sphere);
-
- // Create mesh object
- mesh.Init(this, lodIndex, meshIndex, materialSlotIndex, box, sphere);
+ mesh.SetBounds(box, sphere);
// Blend Shapes
uint16 blendShapes;
@@ -1221,40 +1035,143 @@ Asset::LoadResult SkinnedModel::load()
void SkinnedModel::unload(bool isReloading)
{
- // End streaming (if still active)
- if (_streamingTask != nullptr)
- {
- // Cancel streaming task
- _streamingTask->Cancel();
- _streamingTask = nullptr;
- }
+ ModelBase::unload(isReloading);
// Cleanup
- MaterialSlots.Resize(0);
- for (int32 i = 0; i < LODs.Count(); i++)
- LODs[i].Dispose();
LODs.Clear();
Skeleton.Dispose();
- _initialized = false;
- _loadedLODs = 0;
_skeletonRetargets.Clear();
ClearSkeletonMapping();
}
-bool SkinnedModel::init(AssetInitData& initData)
-{
- // Validate
- if (initData.SerializedVersion != SerializedVersion)
- {
- LOG(Error, "Invalid serialized model version.");
- return true;
- }
-
- return false;
-}
-
AssetChunksFlag SkinnedModel::getChunksToPreload() const
{
// Note: we don't preload any meshes here because it's done by the Streaming Manager
return GET_CHUNK_FLAG(0);
}
+
+bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh)
+{
+ // Check all meshes
+ bool result = false;
+ Real closest = MAX_float;
+ Vector3 closestNormal = Vector3::Up;
+ for (int32 i = 0; i < Meshes.Count(); i++)
+ {
+ // Test intersection with mesh and check if is closer than previous
+ Real dst;
+ Vector3 nrm;
+ if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
+ {
+ result = true;
+ *mesh = &Meshes[i];
+ closest = dst;
+ closestNormal = nrm;
+ }
+ }
+
+ distance = closest;
+ normal = closestNormal;
+ return result;
+}
+
+bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh)
+{
+ // Check all meshes
+ bool result = false;
+ Real closest = MAX_float;
+ Vector3 closestNormal = Vector3::Up;
+ for (int32 i = 0; i < Meshes.Count(); i++)
+ {
+ // Test intersection with mesh and check if is closer than previous
+ Real dst;
+ Vector3 nrm;
+ if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
+ {
+ result = true;
+ *mesh = &Meshes[i];
+ closest = dst;
+ closestNormal = nrm;
+ }
+ }
+
+ distance = closest;
+ normal = closestNormal;
+ return result;
+}
+
+BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const
+{
+ // Find minimum and maximum points of all the meshes
+ Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
+ Vector3 corners[8];
+ for (int32 j = 0; j < Meshes.Count(); j++)
+ {
+ const auto& mesh = Meshes[j];
+ mesh.GetBox().GetCorners(corners);
+ for (int32 i = 0; i < 8; i++)
+ {
+ Vector3::Transform(corners[i], world, tmp);
+ min = Vector3::Min(min, tmp);
+ max = Vector3::Max(max, tmp);
+ }
+ }
+
+ return BoundingBox(min, max);
+}
+
+BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
+{
+ Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
+ Vector3 corners[8];
+ for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
+ {
+ const auto& mesh = Meshes[meshIndex];
+ BoundingBox box = mesh.GetBox();
+ if (deformation)
+ deformation->GetBounds(_lodIndex, meshIndex, box);
+ box.GetCorners(corners);
+ for (int32 i = 0; i < 8; i++)
+ {
+ transform.LocalToWorld(corners[i], tmp);
+ min = Vector3::Min(min, tmp);
+ max = Vector3::Max(max, tmp);
+ }
+ }
+ return BoundingBox(min, max);
+}
+
+BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const
+{
+ // Find minimum and maximum points of the mesh
+ Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
+ Vector3 corners[8];
+ const auto& mesh = Meshes[meshIndex];
+ mesh.GetBox().GetCorners(corners);
+ for (int32 i = 0; i < 8; i++)
+ {
+ Vector3::Transform(corners[i], world, tmp);
+ min = Vector3::Min(min, tmp);
+ max = Vector3::Max(max, tmp);
+ }
+
+ return BoundingBox(min, max);
+}
+
+BoundingBox SkinnedModelLOD::GetBox() const
+{
+ // Find minimum and maximum points of the mesh in given world
+ Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
+ Vector3 corners[8];
+ for (int32 j = 0; j < Meshes.Count(); j++)
+ {
+ Meshes[j].GetBox().GetCorners(corners);
+ for (int32 i = 0; i < 8; i++)
+ {
+ min = Vector3::Min(min, corners[i]);
+ max = Vector3::Max(max, corners[i]);
+ }
+ }
+
+ return BoundingBox(min, max);
+}
diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h
index cf778027d..fc64e9339 100644
--- a/Source/Engine/Content/Assets/SkinnedModel.h
+++ b/Source/Engine/Content/Assets/SkinnedModel.h
@@ -8,8 +8,6 @@
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Graphics/Models/SkinnedModelLOD.h"
-class StreamSkinnedModelLODTask;
-
///
/// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning.
///
@@ -17,7 +15,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
{
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
friend SkinnedMesh;
- friend StreamSkinnedModelLODTask;
public:
// Skeleton mapping descriptor.
struct FLAXENGINE_API SkeletonMapping
@@ -37,9 +34,6 @@ private:
Span NodesMapping;
};
- bool _initialized = false;
- int32 _loadedLODs = 0;
- StreamSkinnedModelLODTask* _streamingTask = nullptr;
Dictionary _skeletonMappingCache;
public:
@@ -60,53 +54,11 @@ public:
~SkinnedModel();
public:
- ///
- /// Gets a value indicating whether this instance is initialized.
- ///
- FORCE_INLINE bool IsInitialized() const
- {
- return _initialized;
- }
-
- ///
- /// Gets the amount of loaded model LODs.
- ///
- API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
- {
- return _loadedLODs;
- }
-
- ///
- /// 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)
- ///
- FORCE_INLINE int32 HighestResidentLODIndex() const
- {
- return LODs.Count() - _loadedLODs;
- }
-
///
/// Determines whether any LOD has been initialized.
///
bool HasAnyLODInitialized() const;
- ///
- /// Determines whether this model can be rendered.
- ///
- FORCE_INLINE bool CanBeRendered() const
- {
- return _loadedLODs > 0;
- }
-
///
/// Gets the skeleton nodes hierarchy.
///
@@ -159,20 +111,6 @@ public:
API_PROPERTY() Array GetBlendShapes();
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);
-
- ///
- /// 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;
-
///
/// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
///
@@ -322,24 +260,14 @@ public:
int32 GetLODsCount() const override;
void GetMeshes(Array& meshes, int32 lodIndex = 0) override;
void InitAsVirtual() override;
- void CancelStreaming() override;
-#if USE_EDITOR
- void GetReferences(Array& assets, Array& files) const override;
-#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;
- void CancelStreamingTasks() override;
protected:
// [ModelBase]
LoadResult load() override;
void unload(bool isReloading) override;
- bool init(AssetInitData& initData) override;
AssetChunksFlag getChunksToPreload() const override;
};
diff --git a/Source/Engine/Graphics/Models/CollisionProxy.h b/Source/Engine/Graphics/Models/CollisionProxy.h
index af3873d58..00a6a6d51 100644
--- a/Source/Engine/Graphics/Models/CollisionProxy.h
+++ b/Source/Engine/Graphics/Models/CollisionProxy.h
@@ -31,12 +31,12 @@ public:
}
template
- void Init(uint32 vertices, uint32 triangles, Float3* positions, IndexType* indices)
+ void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices)
{
Triangles.Clear();
Triangles.EnsureCapacity(triangles, false);
- IndexType* it = indices;
+ const IndexType* it = indices;
for (uint32 i = 0; i < triangles; i++)
{
auto i0 = *(it++);
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index f948ab51d..c6dcf8979 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -136,26 +136,17 @@ namespace
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices)
{
- auto model = (Model*)_model;
-
- Unload();
+ Release();
// Setup GPU resources
- model->LODs[_lodIndex]._verticesCount -= _vertices;
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
if (!failed)
{
- model->LODs[_lodIndex]._verticesCount += _vertices;
-
// Calculate mesh bounds
BoundingBox bounds;
BoundingBox::FromPoints((const Float3*)vb0, vertexCount, bounds);
SetBounds(bounds);
-
- // Send event (actors using this model can update bounds, etc.)
- model->onLoaded();
}
-
return failed;
}
@@ -169,90 +160,19 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* ve
return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
}
-void Mesh::Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs)
-{
- _model = model;
- _lodIndex = lodIndex;
- _index = index;
- _materialSlotIndex = materialSlotIndex;
- _use16BitIndexBuffer = false;
- _hasLightmapUVs = hasLightmapUVs;
- _box = box;
- _sphere = sphere;
- _vertices = 0;
- _triangles = 0;
- _vertexBuffers[0] = nullptr;
- _vertexBuffers[1] = nullptr;
- _vertexBuffers[2] = nullptr;
- _indexBuffer = nullptr;
-}
-
bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer)
{
- // Cache data
- uint32 indicesCount = triangles * 3;
- uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
-
- GPUBuffer* vertexBuffer0 = nullptr;
- GPUBuffer* vertexBuffer1 = nullptr;
- GPUBuffer* vertexBuffer2 = nullptr;
- GPUBuffer* indexBuffer = nullptr;
-
- // Create GPU buffers
-#if GPU_ENABLE_RESOURCE_NAMING
-#define MESH_BUFFER_NAME(postfix) GetModel()->GetPath() + TEXT(postfix)
-#else
-#define MESH_BUFFER_NAME(postfix) String::Empty
-#endif
- vertexBuffer0 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB0"));
- if (vertexBuffer0->Init(GPUBufferDescription::Vertex(VB0ElementType::GetLayout(), sizeof(VB0ElementType), vertices, vb0)))
- goto ERROR_LOAD_END;
- vertexBuffer1 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB1"));
- if (vertexBuffer1->Init(GPUBufferDescription::Vertex(VB1ElementType::GetLayout(), sizeof(VB1ElementType), vertices, vb1)))
- goto ERROR_LOAD_END;
+ Array> vbData;
+ vbData.Add(vb0);
+ if (vb1)
+ vbData.Add(vb1);
if (vb2)
- {
- vertexBuffer2 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB2"));
- if (vertexBuffer2->Init(GPUBufferDescription::Vertex(VB2ElementType::GetLayout(), sizeof(VB2ElementType), vertices, vb2)))
- goto ERROR_LOAD_END;
- }
- indexBuffer = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".IB"));
- if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib)))
- goto ERROR_LOAD_END;
-
- // Init collision proxy
-#if USE_PRECISE_MESH_INTERSECTS
- if (!_collisionProxy.HasData())
- {
- if (use16BitIndexBuffer)
- _collisionProxy.Init(vertices, triangles, (Float3*)vb0, (uint16*)ib);
- else
- _collisionProxy.Init(vertices, triangles, (Float3*)vb0, (uint32*)ib);
- }
-#endif
-
- // Initialize
- _vertexBuffers[0] = vertexBuffer0;
- _vertexBuffers[1] = vertexBuffer1;
- _vertexBuffers[2] = vertexBuffer2;
- _indexBuffer = indexBuffer;
- _triangles = triangles;
- _vertices = vertices;
- _use16BitIndexBuffer = use16BitIndexBuffer;
- _cachedVertexBuffers[0].Clear();
- _cachedVertexBuffers[1].Clear();
- _cachedVertexBuffers[2].Clear();
-
- return false;
-
-#undef MESH_BUFFER_NAME
-ERROR_LOAD_END:
-
- SAFE_DELETE_GPU_RESOURCE(vertexBuffer0);
- SAFE_DELETE_GPU_RESOURCE(vertexBuffer1);
- SAFE_DELETE_GPU_RESOURCE(vertexBuffer2);
- SAFE_DELETE_GPU_RESOURCE(indexBuffer);
- return true;
+ vbData.Add(vb2);
+ Array> vbLayout;
+ vbLayout.Add(VB0ElementType::GetLayout());
+ vbLayout.Add(VB1ElementType::GetLayout());
+ vbLayout.Add(VB2ElementType::GetLayout());
+ return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
}
void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom, int8 sortOrder) const
@@ -424,6 +344,26 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, info.Flags, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder);
}
+bool Mesh::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout)
+{
+ if (MeshBase::Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
+ return true;
+
+ auto model = (Model*)_model;
+ if (model)
+ model->LODs[_lodIndex]._verticesCount += _vertices;
+ return false;
+}
+
+void Mesh::Release()
+{
+ auto model = (Model*)_model;
+ if (model)
+ model->LODs[_lodIndex]._verticesCount -= _vertices;
+
+ MeshBase::Release();
+}
+
bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
{
if (_cachedVertexBuffers[0].IsEmpty())
diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h
index 425bfbec7..8dba159a2 100644
--- a/Source/Engine/Graphics/Models/Mesh.h
+++ b/Source/Engine/Graphics/Models/Mesh.h
@@ -16,9 +16,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase);
-protected:
- bool _hasLightmapUVs;
-
public:
Mesh(const Mesh& other)
: Mesh()
@@ -40,11 +37,16 @@ public:
///
/// Determines whether this mesh contains valid lightmap texture coordinates data.
///
- API_PROPERTY() FORCE_INLINE bool HasLightmapUVs() const
+ API_PROPERTY() bool HasLightmapUVs() const
{
- return _hasLightmapUVs;
+ return LightmapUVsIndex != -1;
}
+ ///
+ /// Lightmap texture coordinates channel index.
+ ///
+ API_FIELD() int32 LightmapUVsIndex = -1;
+
public:
///
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
@@ -124,20 +126,9 @@ public:
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
public:
- ///
- /// Initializes instance of the class.
- ///
- /// The model.
- /// The LOD index.
- /// The mesh index.
- /// The material slot index to use.
- /// The bounding box.
- /// The bounding sphere.
- /// The lightmap UVs flag.
- void Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs);
-
///
/// Load mesh data and Initialize GPU buffers
+ /// [Deprecated in v1.10]
///
/// Amount of vertices in the vertex buffer
/// Amount of triangles in the index buffer
@@ -147,7 +138,7 @@ public:
/// Index buffer data
/// True if use 16 bit indices for the index buffer (true: uint16, false: uint32).
/// True if cannot load data, otherwise false.
- bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer);
+ DEPRECATED("Use Init intead.") bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer);
public:
///
@@ -181,6 +172,8 @@ public:
public:
// [MeshBase]
+ bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) override;
+ void Release() override;
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
private:
diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp
index c95b58acf..f58527337 100644
--- a/Source/Engine/Graphics/Models/MeshBase.cpp
+++ b/Source/Engine/Graphics/Models/MeshBase.cpp
@@ -48,7 +48,6 @@ void MeshBase::SetMaterialSlotIndex(int32 value)
LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
return;
}
-
_materialSlotIndex = value;
}
@@ -56,9 +55,94 @@ void MeshBase::SetBounds(const BoundingBox& box)
{
_box = box;
BoundingSphere::FromBox(box, _sphere);
+ _hasBounds = true;
}
-void MeshBase::Unload()
+void MeshBase::SetBounds(const BoundingBox& box, const BoundingSphere& sphere)
+{
+ _box = box;
+ _sphere = sphere;
+ _hasBounds = true;
+ if (_model && _model->IsLoaded())
+ {
+ // Send event (actors using this model can update bounds, etc.)
+ _model->onLoaded();
+ }
+}
+
+bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout)
+{
+ CHECK_RETURN(vbData.HasItems() && vertices, true);
+ CHECK_RETURN(ibData, true);
+ CHECK_RETURN(vbLayout.Count() >= vbData.Count(), true);
+ ASSERT(_model);
+ GPUBuffer* vertexBuffer0 = nullptr;
+ GPUBuffer* vertexBuffer1 = nullptr;
+ GPUBuffer* vertexBuffer2 = nullptr;
+ GPUBuffer* indexBuffer = nullptr;
+
+ // Create GPU buffers
+#if GPU_ENABLE_RESOURCE_NAMING
+ const String& modelPath = _model->GetPath();
+#define MESH_BUFFER_NAME(postfix) modelPath + TEXT(postfix)
+#else
+#define MESH_BUFFER_NAME(postfix) String::Empty
+#endif
+ vertexBuffer0 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB0"));
+ if (vertexBuffer0->Init(GPUBufferDescription::Vertex(vbLayout[0], vertices, vbData[0])))
+ goto ERROR_LOAD_END;
+ if (vbData.Count() >= 2 && vbData[1])
+ {
+ vertexBuffer1 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB1"));
+ if (vertexBuffer1->Init(GPUBufferDescription::Vertex(vbLayout[1], vertices, vbData[1])))
+ goto ERROR_LOAD_END;
+ }
+ if (vbData.Count() >= 3 && vbData[2])
+ {
+ vertexBuffer2 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB2"));
+ if (vertexBuffer2->Init(GPUBufferDescription::Vertex(vbLayout[2], vertices, vbData[2])))
+ goto ERROR_LOAD_END;
+ }
+ indexBuffer = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".IB"));
+ if (indexBuffer->Init(GPUBufferDescription::Index(use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32), triangles * 3, ibData)))
+ goto ERROR_LOAD_END;
+
+ // Init collision proxy
+#if USE_PRECISE_MESH_INTERSECTS
+ if (!_collisionProxy.HasData())
+ {
+ if (use16BitIndexBuffer)
+ _collisionProxy.Init(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData);
+ else
+ _collisionProxy.Init(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData);
+ }
+#endif
+
+ // Initialize
+ _vertexBuffers[0] = vertexBuffer0;
+ _vertexBuffers[1] = vertexBuffer1;
+ _vertexBuffers[2] = vertexBuffer2;
+ _indexBuffer = indexBuffer;
+ _triangles = triangles;
+ _vertices = vertices;
+ _use16BitIndexBuffer = use16BitIndexBuffer;
+ _cachedVertexBuffers[0].Clear();
+ _cachedVertexBuffers[1].Clear();
+ _cachedVertexBuffers[2].Clear();
+
+ return false;
+
+#undef MESH_BUFFER_NAME
+ERROR_LOAD_END:
+
+ SAFE_DELETE_GPU_RESOURCE(vertexBuffer0);
+ SAFE_DELETE_GPU_RESOURCE(vertexBuffer1);
+ SAFE_DELETE_GPU_RESOURCE(vertexBuffer2);
+ SAFE_DELETE_GPU_RESOURCE(indexBuffer);
+ return true;
+}
+
+void MeshBase::Release()
{
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]);
diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h
index 309fea9e5..4dc861e6a 100644
--- a/Source/Engine/Graphics/Models/MeshBase.h
+++ b/Source/Engine/Graphics/Models/MeshBase.h
@@ -31,30 +31,40 @@ class BlendShapesInstance;
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase);
+ friend class Model;
+ friend class SkinnedModel;
protected:
- ModelBase* _model;
- BoundingBox _box;
- BoundingSphere _sphere;
+ ModelBase* _model = nullptr;
+ BoundingBox _box = BoundingBox::Zero;
+ BoundingSphere _sphere = BoundingSphere::Empty;
- int32 _index;
- int32 _lodIndex;
- uint32 _vertices;
- uint32 _triangles;
- int32 _materialSlotIndex;
- bool _use16BitIndexBuffer;
+ int32 _index = 0;
+ int32 _lodIndex = 0;
+ uint32 _vertices = 0;
+ uint32 _triangles = 0;
+ int32 _materialSlotIndex = 0;
+ bool _use16BitIndexBuffer = false;
+ bool _hasBounds = false;
GPUBuffer* _vertexBuffers[3] = {};
GPUBuffer* _indexBuffer = nullptr;
mutable Array _cachedVertexBuffers[3];
mutable Array _cachedIndexBuffer;
- mutable int32 _cachedIndexBufferCount;
+ mutable int32 _cachedIndexBufferCount = 0;
#if USE_PRECISE_MESH_INTERSECTS
CollisionProxy _collisionProxy;
#endif
+ void Link(ModelBase* model, int32 lodIndex, int32 index)
+ {
+ _model = model;
+ _lodIndex = lodIndex;
+ _index = index;
+ }
+
explicit MeshBase(const SpawnParams& params)
: ScriptingObject(params)
{
@@ -169,6 +179,13 @@ public:
/// The bounding box.
void SetBounds(const BoundingBox& box);
+ ///
+ /// Sets the mesh bounds.
+ ///
+ /// The bounding box.
+ /// The bounding sphere.
+ void SetBounds(const BoundingBox& box, const BoundingSphere& sphere);
+
///
/// Gets the index buffer.
///
@@ -189,9 +206,30 @@ public:
public:
///
- /// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
+ /// Initializes the mesh buffers.
///
- void Unload();
+ /// Amount of vertices in the vertex buffer.
+ /// Amount of triangles in the index buffer.
+ /// Array with pointers to vertex buffers initial data (layout defined by ).
+ /// Pointer to index buffer data. Data is uint16 or uint32 depending on value.
+ /// True to use 16-bit indices for the index buffer (true: uint16, false: uint32).
+ /// Layout descriptors for the vertex buffers attributes (one for each vertex buffer).
+ /// True if failed, otherwise false.
+ virtual bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout);
+
+ ///
+ /// Releases the mesh data (GPU buffers and local cache).
+ ///
+ virtual void Release();
+
+ ///
+ /// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
+ /// [Deprecated in v1.10]
+ ///
+ DEPRECATED("Use Release instead.") void Unload()
+ {
+ Release();
+ }
public:
///
diff --git a/Source/Engine/Graphics/Models/ModelLOD.cpp b/Source/Engine/Graphics/Models/ModelLOD.cpp
deleted file mode 100644
index 095c14f89..000000000
--- a/Source/Engine/Graphics/Models/ModelLOD.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-
-#include "ModelLOD.h"
-#include "MeshDeformation.h"
-#include "Engine/Core/Log.h"
-#include "Engine/Core/Math/Transform.h"
-#include "Engine/Graphics/GPUDevice.h"
-#include "Engine/Serialization/MemoryReadStream.h"
-
-bool ModelLOD::Load(MemoryReadStream& stream)
-{
- // Load LOD for each mesh
- _verticesCount = 0;
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- // #MODEL_DATA_FORMAT_USAGE
- uint32 vertices;
- stream.ReadUint32(&vertices);
- _verticesCount += vertices;
- uint32 triangles;
- stream.ReadUint32(&triangles);
- uint32 indicesCount = triangles * 3;
- bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
- uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
- if (vertices == 0 || triangles == 0)
- return true;
- auto vb0 = stream.Move(vertices);
- auto vb1 = stream.Move(vertices);
- bool hasColors = stream.ReadBool();
- VB2ElementType18* vb2 = nullptr;
- if (hasColors)
- {
- vb2 = stream.Move(vertices);
- }
- auto ib = stream.Move(indicesCount * ibStride);
-
- // Setup GPU resources
- if (Meshes[i].Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer))
- {
- LOG(Warning, "Cannot initialize mesh {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles);
- return true;
- }
- }
-
- return false;
-}
-
-void ModelLOD::Unload()
-{
- // Unload LOD for each mesh
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- Meshes[i].Unload();
- }
-}
-
-void ModelLOD::Dispose()
-{
- _model = nullptr;
- ScreenSize = 0.0f;
- Meshes.Resize(0);
-}
-
-bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh)
-{
- bool result = false;
- Real closest = MAX_Real;
- Vector3 closestNormal = Vector3::Up;
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- Real dst;
- Vector3 nrm;
- if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
- {
- result = true;
- *mesh = &Meshes[i];
- closest = dst;
- closestNormal = nrm;
- }
- }
- distance = closest;
- normal = closestNormal;
- return result;
-}
-
-bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh)
-{
- bool result = false;
- Real closest = MAX_Real;
- Vector3 closestNormal = Vector3::Up;
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- Real dst;
- Vector3 nrm;
- if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
- {
- result = true;
- *mesh = &Meshes[i];
- closest = dst;
- closestNormal = nrm;
- }
- }
- distance = closest;
- normal = closestNormal;
- return result;
-}
-
-BoundingBox ModelLOD::GetBox(const Matrix& world) const
-{
- Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
- Vector3 corners[8];
- for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
- {
- const auto& mesh = Meshes[meshIndex];
- mesh.GetBox().GetCorners(corners);
- for (int32 i = 0; i < 8; i++)
- {
- Vector3::Transform(corners[i], world, tmp);
- min = Vector3::Min(min, tmp);
- max = Vector3::Max(max, tmp);
- }
- }
- return BoundingBox(min, max);
-}
-
-BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
-{
- Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
- Vector3 corners[8];
- for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
- {
- const auto& mesh = Meshes[meshIndex];
- BoundingBox box = mesh.GetBox();
- if (deformation)
- deformation->GetBounds(_lodIndex, meshIndex, box);
- box.GetCorners(corners);
- for (int32 i = 0; i < 8; i++)
- {
- transform.LocalToWorld(corners[i], tmp);
- min = Vector3::Min(min, tmp);
- max = Vector3::Max(max, tmp);
- }
- }
- return BoundingBox(min, max);
-}
-
-BoundingBox ModelLOD::GetBox() const
-{
- Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
- Vector3 corners[8];
- for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
- {
- Meshes[meshIndex].GetBox().GetCorners(corners);
- for (int32 i = 0; i < 8; i++)
- {
- min = Vector3::Min(min, corners[i]);
- max = Vector3::Max(max, corners[i]);
- }
- }
- return BoundingBox(min, max);
-}
diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h
index a6937739e..4934e6cc2 100644
--- a/Source/Engine/Graphics/Models/ModelLOD.h
+++ b/Source/Engine/Graphics/Models/ModelLOD.h
@@ -2,7 +2,6 @@
#pragma once
-#include "Engine/Core/Collections/Array.h"
#include "Mesh.h"
class MemoryReadStream;
@@ -18,7 +17,14 @@ API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject
private:
Model* _model = nullptr;
int32 _lodIndex = 0;
- uint32 _verticesCount;
+ uint32 _verticesCount = 0;
+
+ void Link(Model* model, int32 lodIndex)
+ {
+ _model = model;
+ _lodIndex = lodIndex;
+ _verticesCount = 0;
+ }
public:
///
@@ -57,24 +63,6 @@ public:
return _verticesCount;
}
-public:
- ///
- /// Initializes the LOD from the data stream.
- ///
- /// The stream.
- /// True if fails, otherwise false.
- bool Load(MemoryReadStream& stream);
-
- ///
- /// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
- ///
- void Unload();
-
- ///
- /// Cleanups the data.
- ///
- void Dispose();
-
public:
///
/// Determines if there is an intersection between the Model and a Ray in given world using given instance
diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp
index b7e2ddc6a..5473b549a 100644
--- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp
+++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp
@@ -94,71 +94,17 @@ void SkeletonData::Dispose()
Bones.Resize(0);
}
-void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere)
-{
- _model = model;
- _index = index;
- _lodIndex = lodIndex;
- _materialSlotIndex = materialSlotIndex;
- _use16BitIndexBuffer = false;
- _box = box;
- _sphere = sphere;
- _vertices = 0;
- _triangles = 0;
- _vertexBuffers[0] = nullptr;
- _indexBuffer = nullptr;
- _cachedIndexBuffer.Clear();
- _cachedVertexBuffers[0].Clear();
- BlendShapes.Clear();
-}
-
bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer)
{
- // Cache data
- uint32 indicesCount = triangles * 3;
- uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
-
- GPUBuffer* vertexBuffer = nullptr;
- GPUBuffer* indexBuffer = nullptr;
-
- // Create vertex buffer
-#if GPU_ENABLE_RESOURCE_NAMING
- vertexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->GetPath() + TEXT(".VB"));
-#else
- vertexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
-#endif
- if (vertexBuffer->Init(GPUBufferDescription::Vertex(VB0SkinnedElementType::GetLayout(), sizeof(VB0SkinnedElementType), vertices, vb0)))
- goto ERROR_LOAD_END;
-
- // Create index buffer
-#if GPU_ENABLE_RESOURCE_NAMING
- indexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->GetPath() + TEXT(".IB"));
-#else
- indexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
-#endif
- if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib)))
- goto ERROR_LOAD_END;
-
- // Initialize
- _vertexBuffers[0] = vertexBuffer;
- _indexBuffer = indexBuffer;
- _triangles = triangles;
- _vertices = vertices;
- _use16BitIndexBuffer = use16BitIndexBuffer;
-
- return false;
-
-ERROR_LOAD_END:
-
- SAFE_DELETE_GPU_RESOURCE(vertexBuffer);
- SAFE_DELETE_GPU_RESOURCE(indexBuffer);
- return true;
+ Array> vbData;
+ vbData.Add(vb0);
+ Array> vbLayout;
+ vbLayout.Add(VB0SkinnedElementType::GetLayout());
+ return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices)
{
- auto model = (SkinnedModel*)_model;
-
// Setup GPU resources
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
if (!failed)
@@ -167,11 +113,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0
BoundingBox bounds;
BoundingBox::FromPoints((const Float3*)vb, vertexCount, bounds);
SetBounds(bounds);
-
- // Send event (actors using this model can update bounds, etc.)
- model->onLoaded();
}
-
return failed;
}
@@ -267,6 +209,13 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI
renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, StaticFlags::None, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder);
}
+void SkinnedMesh::Release()
+{
+ MeshBase::Release();
+
+ BlendShapes.Clear();
+}
+
bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
{
if (_cachedVertexBuffers[0].IsEmpty())
diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h
index b8dd38696..f28ea7fcd 100644
--- a/Source/Engine/Graphics/Models/SkinnedMesh.h
+++ b/Source/Engine/Graphics/Models/SkinnedMesh.h
@@ -36,19 +36,9 @@ public:
Array BlendShapes;
public:
- ///
- /// Initializes a new instance of the class.
- ///
- /// The model.
- /// The model LOD index.
- /// The mesh index.
- /// The material slot index to use.
- /// The bounding box.
- /// The bounding sphere.
- void Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere);
-
///
/// Load mesh data and Initialize GPU buffers
+ /// [Deprecated in v1.10]
///
/// Amount of vertices in the vertex buffer
/// Amount of triangles in the index buffer
@@ -56,7 +46,7 @@ public:
/// Index buffer data
/// True if use 16 bit indices for the index buffer (true: uint16, false: uint32).
/// True if cannot load data, otherwise false.
- bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer);
+ DEPRECATED("Use Init intead.") bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer);
public:
///
@@ -128,6 +118,7 @@ public:
public:
// [MeshBase]
+ void Release() override;
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
private:
diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp
deleted file mode 100644
index e16a78291..000000000
--- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-
-#include "SkinnedModelLOD.h"
-#include "MeshDeformation.h"
-#include "Engine/Core/Log.h"
-#include "Engine/Core/Math/Transform.h"
-#include "Engine/Graphics/GPUDevice.h"
-#include "Engine/Content/Assets/Model.h"
-#include "Engine/Serialization/MemoryReadStream.h"
-
-bool SkinnedModelLOD::Load(MemoryReadStream& stream)
-{
- // Load LOD for each mesh
- byte version = stream.ReadByte();
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- auto& mesh = Meshes[i];
-
- // #MODEL_DATA_FORMAT_USAGE
- uint32 vertices;
- stream.ReadUint32(&vertices);
- uint32 triangles;
- stream.ReadUint32(&triangles);
- uint16 blendShapesCount;
- stream.ReadUint16(&blendShapesCount);
- if (blendShapesCount != mesh.BlendShapes.Count())
- {
- LOG(Warning, "Cannot initialize mesh {0}. Incorect blend shapes amount: {1} (expected: {2})", i, blendShapesCount, mesh.BlendShapes.Count());
- return true;
- }
- for (auto& blendShape : mesh.BlendShapes)
- {
- blendShape.UseNormals = stream.ReadBool();
- stream.ReadUint32(&blendShape.MinVertexIndex);
- stream.ReadUint32(&blendShape.MaxVertexIndex);
- uint32 blendShapeVertices;
- stream.ReadUint32(&blendShapeVertices);
- blendShape.Vertices.Resize(blendShapeVertices);
- stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
- }
- const uint32 indicesCount = triangles * 3;
- const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
- const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
- if (vertices == 0 || triangles == 0)
- return true;
- const auto vb0 = stream.Move(vertices);
- const auto ib = stream.Move(indicesCount * ibStride);
-
- // Setup GPU resources
- if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer))
- {
- LOG(Warning, "Cannot initialize mesh {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles);
- return true;
- }
- }
-
- return false;
-}
-
-void SkinnedModelLOD::Unload()
-{
- // Unload LOD for each mesh
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- Meshes[i].Unload();
- }
-}
-
-void SkinnedModelLOD::Dispose()
-{
- _model = nullptr;
- ScreenSize = 0.0f;
- Meshes.Resize(0);
-}
-
-bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh)
-{
- // Check all meshes
- bool result = false;
- Real closest = MAX_float;
- Vector3 closestNormal = Vector3::Up;
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- // Test intersection with mesh and check if is closer than previous
- Real dst;
- Vector3 nrm;
- if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
- {
- result = true;
- *mesh = &Meshes[i];
- closest = dst;
- closestNormal = nrm;
- }
- }
-
- distance = closest;
- normal = closestNormal;
- return result;
-}
-
-bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh)
-{
- // Check all meshes
- bool result = false;
- Real closest = MAX_float;
- Vector3 closestNormal = Vector3::Up;
- for (int32 i = 0; i < Meshes.Count(); i++)
- {
- // Test intersection with mesh and check if is closer than previous
- Real dst;
- Vector3 nrm;
- if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
- {
- result = true;
- *mesh = &Meshes[i];
- closest = dst;
- closestNormal = nrm;
- }
- }
-
- distance = closest;
- normal = closestNormal;
- return result;
-}
-
-BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const
-{
- // Find minimum and maximum points of all the meshes
- Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
- Vector3 corners[8];
- for (int32 j = 0; j < Meshes.Count(); j++)
- {
- const auto& mesh = Meshes[j];
- mesh.GetBox().GetCorners(corners);
- for (int32 i = 0; i < 8; i++)
- {
- Vector3::Transform(corners[i], world, tmp);
- min = Vector3::Min(min, tmp);
- max = Vector3::Max(max, tmp);
- }
- }
-
- return BoundingBox(min, max);
-}
-
-BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
-{
- Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
- Vector3 corners[8];
- for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
- {
- const auto& mesh = Meshes[meshIndex];
- BoundingBox box = mesh.GetBox();
- if (deformation)
- deformation->GetBounds(_lodIndex, meshIndex, box);
- box.GetCorners(corners);
- for (int32 i = 0; i < 8; i++)
- {
- transform.LocalToWorld(corners[i], tmp);
- min = Vector3::Min(min, tmp);
- max = Vector3::Max(max, tmp);
- }
- }
- return BoundingBox(min, max);
-}
-
-BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const
-{
- // Find minimum and maximum points of the mesh
- Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
- Vector3 corners[8];
- const auto& mesh = Meshes[meshIndex];
- mesh.GetBox().GetCorners(corners);
- for (int32 i = 0; i < 8; i++)
- {
- Vector3::Transform(corners[i], world, tmp);
- min = Vector3::Min(min, tmp);
- max = Vector3::Max(max, tmp);
- }
-
- return BoundingBox(min, max);
-}
-
-BoundingBox SkinnedModelLOD::GetBox() const
-{
- // Find minimum and maximum points of the mesh in given world
- Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
- Vector3 corners[8];
- for (int32 j = 0; j < Meshes.Count(); j++)
- {
- Meshes[j].GetBox().GetCorners(corners);
- for (int32 i = 0; i < 8; i++)
- {
- min = Vector3::Min(min, corners[i]);
- max = Vector3::Max(max, corners[i]);
- }
- }
-
- return BoundingBox(min, max);
-}
diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h
index 66cf77759..f7c78f3d1 100644
--- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h
+++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h
@@ -2,7 +2,6 @@
#pragma once
-#include "Engine/Core/Collections/Array.h"
#include "SkinnedMesh.h"
class MemoryReadStream;
@@ -46,23 +45,6 @@ public:
return Meshes.HasItems() && Meshes.Last().IsInitialized();
}
-public:
- ///
- /// Initializes the LOD from the data stream.
- ///
- /// The stream.
- /// True if fails, otherwise false.
- bool Load(MemoryReadStream& stream);
-
- ///
- /// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
- ///
- void Unload();
-
- ///
- /// Cleanups the data.
- ///
- void Dispose();
public:
///
diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp
index 880dc1f00..659bcb344 100644
--- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp
+++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp
@@ -87,8 +87,8 @@ void GPUVertexLayout::SetElements(const Elements& elements, uint32 offsets[GPU_M
strides[e.Slot] = Math::Max(strides[e.Slot], offsets[e.Slot]);
}
_stride = 0;
- for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++)
- _stride += strides[i];
+ for (uint32 stride : strides)
+ _stride += stride;
}
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements)