Refactor models and meshes to share more code in a base class
This commit is contained in:
@@ -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");
|
||||
|
||||
/// <summary>
|
||||
/// Model LOD streaming task.
|
||||
/// </summary>
|
||||
class StreamModelLODTask : public STREAM_TASK_BASE
|
||||
{
|
||||
private:
|
||||
WeakAssetReference<Model> _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> 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<int32>& 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<int32>& 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<Guid>& assets, Array<String>& 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<StreamModelLODTask>(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);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "Engine/Graphics/Models/ModelLOD.h"
|
||||
|
||||
class Mesh;
|
||||
class StreamModelLODTask;
|
||||
|
||||
/// <summary>
|
||||
/// 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:
|
||||
/// <summary>
|
||||
@@ -38,40 +33,6 @@ public:
|
||||
~Model();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is initialized.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsInitialized() const
|
||||
{
|
||||
return LODs.HasItems();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of loaded model LODs.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
|
||||
{
|
||||
return _loadedLODs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the index of the LOD to be valid for rendering (only loaded LODs).
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The resident LOD index.</returns>
|
||||
FORCE_INLINE int32 ClampLODIndex(int32 index) const
|
||||
{
|
||||
return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 HighestResidentLODIndex() const
|
||||
{
|
||||
return LODs.Count() - _loadedLODs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any LOD has been initialized.
|
||||
/// </summary>
|
||||
@@ -80,37 +41,6 @@ public:
|
||||
return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this model can be rendered.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool CanBeRendered() const
|
||||
{
|
||||
return _loadedLODs > 0;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here).
|
||||
/// </summary>
|
||||
/// <param name="lodIndex">Index of the LOD.</param>
|
||||
/// <returns>Task that will gather chunk data or null if already here.</returns>
|
||||
ContentLoadTask* RequestLODDataAsync(int32 lodIndex)
|
||||
{
|
||||
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
|
||||
return RequestChunkDataAsync(chunkIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model LOD data (links bytes).
|
||||
/// </summary>
|
||||
/// <param name="lodIndex">Index of the LOD.</param>
|
||||
/// <param name="data">The data (may be missing if failed to get it).</param>
|
||||
void GetLODData(int32 lodIndex, BytesContainer& data) const
|
||||
{
|
||||
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
|
||||
GetChunkData(chunkIndex, data);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// 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<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
void InitAsVirtual() override;
|
||||
void CancelStreaming() override;
|
||||
#if USE_EDITOR
|
||||
void GetReferences(Array<Guid>& assets, Array<String>& 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;
|
||||
};
|
||||
|
||||
311
Source/Engine/Content/Assets/ModelBase.cpp
Normal file
311
Source/Engine/Content/Assets/ModelBase.cpp
Normal file
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Model LOD streaming task.
|
||||
/// </summary>
|
||||
class StreamModelLODTask : public STREAM_TASK_BASE
|
||||
{
|
||||
private:
|
||||
WeakAssetReference<ModelBase> _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<ModelBase> 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<MeshBase*> meshes;
|
||||
model->GetMeshes(meshes, _lodIndex);
|
||||
if (model->Is<SkinnedModel>())
|
||||
{
|
||||
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<VB0SkinnedElementType>(vertices);
|
||||
const auto ib = stream.Move<byte>(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<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Move<byte>(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<Guid>& assets, Array<String>& 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<StreamModelLODTask>(this, lodIndex);
|
||||
if (result)
|
||||
result->ContinueWith(_streamingTask);
|
||||
else
|
||||
result = _streamingTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do the quick data release
|
||||
Array<MeshBase*> 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;
|
||||
}
|
||||
@@ -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:
|
||||
/// <summary>
|
||||
/// The Sign Distant Field (SDF) data for the model.
|
||||
@@ -106,6 +115,48 @@ public:
|
||||
return MaterialSlots.Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is initialized.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsInitialized() const
|
||||
{
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the index of the LOD to be valid for rendering (only loaded LODs).
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The resident LOD index.</returns>
|
||||
FORCE_INLINE int32 ClampLODIndex(int32 index) const
|
||||
{
|
||||
return Math::Clamp(index, HighestResidentLODIndex(), GetLODsCount() - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 HighestResidentLODIndex() const
|
||||
{
|
||||
return GetLODsCount() - _loadedLODs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of loaded model LODs.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
|
||||
{
|
||||
return _loadedLODs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this model can be rendered.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool CanBeRendered() const
|
||||
{
|
||||
return _loadedLODs > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the material slots collection. Updates meshes that were using removed slots.
|
||||
/// </summary>
|
||||
@@ -127,4 +178,37 @@ public:
|
||||
/// Gets the meshes for a particular LOD index.
|
||||
/// </summary>
|
||||
virtual void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here).
|
||||
/// </summary>
|
||||
/// <param name="lodIndex">Index of the LOD.</param>
|
||||
/// <returns>Task that will gather chunk data or null if already here.</returns>
|
||||
ContentLoadTask* RequestLODDataAsync(int32 lodIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model LOD data (links bytes).
|
||||
/// </summary>
|
||||
/// <param name="lodIndex">Index of the LOD.</param>
|
||||
/// <param name="data">The data (it may be missing if failed to get it).</param>
|
||||
void GetLODData(int32 lodIndex, BytesContainer& data) const;
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
void CancelStreaming() override;
|
||||
#if USE_EDITOR
|
||||
void GetReferences(Array<Guid>& assets, Array<String>& 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;
|
||||
};
|
||||
|
||||
@@ -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); \
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skinned model data streaming task
|
||||
/// </summary>
|
||||
class StreamSkinnedModelLODTask : public ThreadPoolTask
|
||||
{
|
||||
private:
|
||||
WeakAssetReference<SkinnedModel> _asset;
|
||||
int32 _lodIndex;
|
||||
FlaxStorage::LockData _dataLock;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
/// <param name="model">Parent model</param>
|
||||
/// <param name="lodIndex">LOD to stream index</param>
|
||||
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<SkinnedModel> 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<String> 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<int32>& 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<int32>& 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<Guid>& assets, Array<String>& 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<StreamSkinnedModelLODTask>(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);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||
#include "Engine/Graphics/Models/SkinnedModelLOD.h"
|
||||
|
||||
class StreamSkinnedModelLODTask;
|
||||
|
||||
/// <summary>
|
||||
/// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning.
|
||||
/// </summary>
|
||||
@@ -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<int32> NodesMapping;
|
||||
};
|
||||
|
||||
bool _initialized = false;
|
||||
int32 _loadedLODs = 0;
|
||||
StreamSkinnedModelLODTask* _streamingTask = nullptr;
|
||||
Dictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
|
||||
|
||||
public:
|
||||
@@ -60,53 +54,11 @@ public:
|
||||
~SkinnedModel();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is initialized.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsInitialized() const
|
||||
{
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of loaded model LODs.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
|
||||
{
|
||||
return _loadedLODs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the index of the LOD to be valid for rendering (only loaded LODs).
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The resident LOD index.</returns>
|
||||
FORCE_INLINE int32 ClampLODIndex(int32 index) const
|
||||
{
|
||||
return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 HighestResidentLODIndex() const
|
||||
{
|
||||
return LODs.Count() - _loadedLODs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any LOD has been initialized.
|
||||
/// </summary>
|
||||
bool HasAnyLODInitialized() const;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this model can be rendered.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool CanBeRendered() const
|
||||
{
|
||||
return _loadedLODs > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the skeleton nodes hierarchy.
|
||||
/// </summary>
|
||||
@@ -159,20 +111,6 @@ public:
|
||||
API_PROPERTY() Array<String> GetBlendShapes();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here).
|
||||
/// </summary>
|
||||
/// <param name="lodIndex">Index of the LOD.</param>
|
||||
/// <returns>Task that will gather chunk data or null if already here.</returns>
|
||||
ContentLoadTask* RequestLODDataAsync(int32 lodIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model LOD data (links bytes).
|
||||
/// </summary>
|
||||
/// <param name="lodIndex">Index of the LOD.</param>
|
||||
/// <param name="data">The data (may be missing if failed to get it).</param>
|
||||
void GetLODData(int32 lodIndex, BytesContainer& data) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
|
||||
/// </summary>
|
||||
@@ -322,24 +260,14 @@ public:
|
||||
int32 GetLODsCount() const override;
|
||||
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
void InitAsVirtual() override;
|
||||
void CancelStreaming() override;
|
||||
#if USE_EDITOR
|
||||
void GetReferences(Array<Guid>& assets, Array<String>& 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;
|
||||
};
|
||||
|
||||
@@ -31,12 +31,12 @@ public:
|
||||
}
|
||||
|
||||
template<typename IndexType>
|
||||
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++);
|
||||
|
||||
@@ -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<uint32>(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<const void*, FixedAllocation<3>> 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<uint16>(vertices, triangles, (Float3*)vb0, (uint16*)ib);
|
||||
else
|
||||
_collisionProxy.Init<uint32>(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<GPUVertexLayout*, FixedAllocation<3>> 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<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& 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())
|
||||
|
||||
@@ -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:
|
||||
/// <summary>
|
||||
/// Determines whether this mesh contains valid lightmap texture coordinates data.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE bool HasLightmapUVs() const
|
||||
API_PROPERTY() bool HasLightmapUVs() const
|
||||
{
|
||||
return _hasLightmapUVs;
|
||||
return LightmapUVsIndex != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap texture coordinates channel index.
|
||||
/// </summary>
|
||||
API_FIELD() int32 LightmapUVsIndex = -1;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// 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:
|
||||
/// <summary>
|
||||
/// Initializes instance of the <see cref="Mesh"/> class.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="lodIndex">The LOD index.</param>
|
||||
/// <param name="index">The mesh index.</param>
|
||||
/// <param name="materialSlotIndex">The material slot index to use.</param>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
/// <param name="sphere">The bounding sphere.</param>
|
||||
/// <param name="hasLightmapUVs">The lightmap UVs flag.</param>
|
||||
void Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs);
|
||||
|
||||
/// <summary>
|
||||
/// Load mesh data and Initialize GPU buffers
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertices">Amount of vertices in the vertex buffer</param>
|
||||
/// <param name="triangles">Amount of triangles in the index buffer</param>
|
||||
@@ -147,7 +138,7 @@ public:
|
||||
/// <param name="ib">Index buffer data</param>
|
||||
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
|
||||
/// <returns>True if cannot load data, otherwise false.</returns>
|
||||
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:
|
||||
/// <summary>
|
||||
@@ -181,6 +172,8 @@ public:
|
||||
|
||||
public:
|
||||
// [MeshBase]
|
||||
bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout) override;
|
||||
void Release() override;
|
||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -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<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& 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<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData);
|
||||
else
|
||||
_collisionProxy.Init<uint32>(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]);
|
||||
|
||||
@@ -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<byte> _cachedVertexBuffers[3];
|
||||
mutable Array<byte> _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:
|
||||
/// <param name="box">The bounding box.</param>
|
||||
void SetBounds(const BoundingBox& box);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mesh bounds.
|
||||
/// </summary>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
/// <param name="sphere">The bounding sphere.</param>
|
||||
void SetBounds(const BoundingBox& box, const BoundingSphere& sphere);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index buffer.
|
||||
/// </summary>
|
||||
@@ -189,9 +206,30 @@ public:
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
|
||||
/// Initializes the mesh buffers.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
/// <param name="vertices">Amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangles">Amount of triangles in the index buffer.</param>
|
||||
/// <param name="vbData">Array with pointers to vertex buffers initial data (layout defined by <paramref name="vertexLayout"/>).</param>
|
||||
/// <param name="ibData">Pointer to index buffer data. Data is uint16 or uint32 depending on <paramref name="use16BitIndexBuffer"/> value.</param>
|
||||
/// <param name="use16BitIndexBuffer">True to use 16-bit indices for the index buffer (true: uint16, false: uint32).</param>
|
||||
/// <param name="vbLayout">Layout descriptors for the vertex buffers attributes (one for each vertex buffer).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
virtual bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the mesh data (GPU buffers and local cache).
|
||||
/// </summary>
|
||||
virtual void Release();
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
DEPRECATED("Use Release instead.") void Unload()
|
||||
{
|
||||
Release();
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -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<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Move<byte>(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);
|
||||
}
|
||||
@@ -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:
|
||||
/// <summary>
|
||||
@@ -57,24 +63,6 @@ public:
|
||||
return _verticesCount;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes the LOD from the data stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
bool Load(MemoryReadStream& stream);
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups the data.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between the Model and a Ray in given world using given instance
|
||||
|
||||
@@ -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<const void*, FixedAllocation<3>> vbData;
|
||||
vbData.Add(vb0);
|
||||
Array<GPUVertexLayout*, FixedAllocation<3>> 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())
|
||||
|
||||
@@ -36,19 +36,9 @@ public:
|
||||
Array<BlendShape> BlendShapes;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkinnedMesh"/> class.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="lodIndex">The model LOD index.</param>
|
||||
/// <param name="index">The mesh index.</param>
|
||||
/// <param name="materialSlotIndex">The material slot index to use.</param>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
/// <param name="sphere">The bounding sphere.</param>
|
||||
void Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere);
|
||||
|
||||
/// <summary>
|
||||
/// Load mesh data and Initialize GPU buffers
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertices">Amount of vertices in the vertex buffer</param>
|
||||
/// <param name="triangles">Amount of triangles in the index buffer</param>
|
||||
@@ -56,7 +46,7 @@ public:
|
||||
/// <param name="ib">Index buffer data</param>
|
||||
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
|
||||
/// <returns>True if cannot load data, otherwise false.</returns>
|
||||
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:
|
||||
/// <summary>
|
||||
@@ -128,6 +118,7 @@ public:
|
||||
|
||||
public:
|
||||
// [MeshBase]
|
||||
void Release() override;
|
||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -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<VB0SkinnedElementType>(vertices);
|
||||
const auto ib = stream.Move<byte>(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);
|
||||
}
|
||||
@@ -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:
|
||||
/// <summary>
|
||||
/// Initializes the LOD from the data stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
bool Load(MemoryReadStream& stream);
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups the data.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user