**Refactor meshes format to support custom vertex layouts and new flexible api to access mesh data**
#3044 #2667
This commit is contained in:
@@ -13,8 +13,10 @@
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#endif
|
||||
|
||||
REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false);
|
||||
@@ -490,6 +492,64 @@ bool Animation::Save(const StringView& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Animation::SaveHeader(const ModelData& modelData, WriteStream& stream, int32 animIndex)
|
||||
{
|
||||
// Validate input
|
||||
if (animIndex < 0 || animIndex >= modelData.Animations.Count())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("animIndex"));
|
||||
return true;
|
||||
}
|
||||
auto& anim = modelData.Animations.Get()[animIndex];
|
||||
if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("Invalid animation duration."));
|
||||
return true;
|
||||
}
|
||||
if (anim.Channels.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Info
|
||||
stream.Write(103); // Header version (for fast version upgrades without serialization format change)
|
||||
stream.Write(anim.Duration);
|
||||
stream.Write(anim.FramesPerSecond);
|
||||
stream.Write((byte)anim.RootMotionFlags);
|
||||
stream.Write(anim.RootNodeName, 13);
|
||||
|
||||
// Animation channels
|
||||
stream.WriteInt32(anim.Channels.Count());
|
||||
for (const auto& channel : anim.Channels)
|
||||
{
|
||||
stream.Write(channel.NodeName, 172);
|
||||
Serialization::Serialize(stream, channel.Position);
|
||||
Serialization::Serialize(stream, channel.Rotation);
|
||||
Serialization::Serialize(stream, channel.Scale);
|
||||
}
|
||||
|
||||
// Animation events
|
||||
stream.WriteInt32(anim.Events.Count());
|
||||
for (auto& e : anim.Events)
|
||||
{
|
||||
stream.Write(e.First, 172);
|
||||
stream.Write(e.Second.GetKeyframes().Count());
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream.Write(k.Time);
|
||||
stream.Write(k.Value.Duration);
|
||||
stream.Write(k.Value.TypeName, 17);
|
||||
stream.WriteJson(k.Value.JsonData);
|
||||
}
|
||||
}
|
||||
|
||||
// Nested animations
|
||||
stream.WriteInt32(0); // Empty list
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Animation::GetReferences(Array<Guid>& assets, Array<String>& files) const
|
||||
{
|
||||
BinaryAsset::GetReferences(assets, files);
|
||||
|
||||
@@ -151,6 +151,12 @@ public:
|
||||
bool Save(const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if USE_EDITOR
|
||||
friend class ImportModel;
|
||||
static bool SaveHeader(const class ModelData& modelData, WriteStream& stream, int32 animIndex);
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
#if USE_EDITOR
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "Engine/Content/WeakAssetReference.h"
|
||||
#include "Engine/Content/Upgraders/ModelAssetUpgrader.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
|
||||
@@ -18,13 +18,13 @@
|
||||
#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"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#endif
|
||||
|
||||
REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase");
|
||||
@@ -246,330 +246,6 @@ bool Model::SetupLODs(const Span<int32>& meshesCountPerLod)
|
||||
return Init(meshesCountPerLod);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Model::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
if (withMeshDataFromGpu && IsInMainThread())
|
||||
{
|
||||
LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread).");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && !withMeshDataFromGpu)
|
||||
{
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Create model data header
|
||||
MemoryWriteStream headerStream(1024);
|
||||
MemoryWriteStream* stream = &headerStream;
|
||||
{
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(MaterialSlots.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < MaterialSlots.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
const auto id = slot.Material.GetID();
|
||||
stream->Write(id);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
const int32 lods = LODs.Count();
|
||||
stream->WriteByte(lods);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot index
|
||||
stream->WriteInt32(mesh.GetMaterialSlotIndex());
|
||||
|
||||
// Box
|
||||
const auto box = mesh.GetBox();
|
||||
stream->WriteBoundingBox(box);
|
||||
|
||||
// Sphere
|
||||
const auto sphere = mesh.GetSphere();
|
||||
stream->WriteBoundingSphere(sphere);
|
||||
|
||||
// Has Lightmap UVs
|
||||
stream->WriteBool(mesh.HasLightmapUVs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(tmpChunks, sizeof(tmpChunks));
|
||||
Array<FlaxChunk> chunks;
|
||||
if (IsVirtual())
|
||||
chunks.Resize(ASSET_FILE_DATA_CHUNKS);
|
||||
#define GET_CHUNK(index) (IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index))
|
||||
|
||||
// Check if use data from drive or from GPU
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Download all meshes buffers
|
||||
Array<Task*> tasks;
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
const int32 meshesCount = lod.Meshes.Count();
|
||||
struct MeshData
|
||||
{
|
||||
BytesContainer VB0;
|
||||
BytesContainer VB1;
|
||||
BytesContainer VB2;
|
||||
BytesContainer IB;
|
||||
|
||||
uint32 DataSize() const
|
||||
{
|
||||
return VB0.Length() + VB1.Length() + VB2.Length() + IB.Length();
|
||||
}
|
||||
};
|
||||
Array<MeshData> meshesData;
|
||||
meshesData.Resize(meshesCount);
|
||||
tasks.EnsureCapacity(meshesCount * 4);
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
auto& meshData = meshesData[meshIndex];
|
||||
|
||||
// Vertex Buffer 0 (required)
|
||||
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Vertex Buffer 1 (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB1);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Vertex Buffer 2 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB2);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Index Buffer (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Wait for all
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
tasks.Clear();
|
||||
|
||||
// Create meshes data
|
||||
{
|
||||
int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool));
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
dataSize += meshesData[meshIndex].DataSize();
|
||||
}
|
||||
|
||||
MemoryWriteStream meshesStream(dataSize);
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
const auto& meshData = meshesData[meshIndex];
|
||||
|
||||
uint32 vertices = mesh.GetVertexCount();
|
||||
uint32 triangles = mesh.GetTriangleCount();
|
||||
bool hasColors = meshData.VB2.IsValid();
|
||||
uint32 vb0Size = vertices * sizeof(VB0ElementType);
|
||||
uint32 vb1Size = vertices * sizeof(VB1ElementType);
|
||||
uint32 vb2Size = vertices * sizeof(VB2ElementType);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
|
||||
uint32 ibSize = indicesCount * (use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32));
|
||||
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.VB0.Length() < vb0Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 0 size.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.VB1.Length() < vb1Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 1 size.");
|
||||
return true;
|
||||
}
|
||||
if (hasColors && (uint32)meshData.VB2.Length() < vb2Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 2 size.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.IB.Length() < ibSize)
|
||||
{
|
||||
LOG(Warning, "Invalid index buffer size.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
meshesStream.WriteUint32(vertices);
|
||||
meshesStream.WriteUint32(triangles);
|
||||
|
||||
meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size);
|
||||
meshesStream.WriteBytes(meshData.VB1.Get(), vb1Size);
|
||||
|
||||
meshesStream.WriteBool(hasColors);
|
||||
|
||||
if (hasColors)
|
||||
{
|
||||
meshesStream.WriteBytes(meshData.VB2.Get(), vb2Size);
|
||||
}
|
||||
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
meshesStream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
}
|
||||
else if (shouldUse16BitIndexBuffer)
|
||||
{
|
||||
auto ib = (const int32*)meshData.IB.Get();
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
{
|
||||
meshesStream.WriteUint16(ib[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
}
|
||||
|
||||
// Override LOD data chunk with the fetched GPU meshes memory
|
||||
auto lodChunk = GET_CHUNK(MODEL_LOD_TO_CHUNK_INDEX(lodIndex));
|
||||
if (lodChunk == nullptr)
|
||||
return true;
|
||||
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
// Download SDF data
|
||||
if (SDF.Texture)
|
||||
{
|
||||
auto sdfChunk = GET_CHUNK(15);
|
||||
if (sdfChunk == nullptr)
|
||||
return true;
|
||||
MemoryWriteStream sdfStream;
|
||||
sdfStream.WriteInt32(1); // Version
|
||||
ModelSDFHeader data(SDF, SDF.Texture->GetDescription());
|
||||
sdfStream.WriteBytes(&data, sizeof(data));
|
||||
TextureData sdfTextureData;
|
||||
if (SDF.Texture->DownloadData(sdfTextureData))
|
||||
return true;
|
||||
for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++)
|
||||
{
|
||||
auto& mip = sdfTextureData.Items[0].Mips[mipLevel];
|
||||
ModelSDFMip mipData(mipLevel, mip);
|
||||
sdfStream.WriteBytes(&mipData, sizeof(mipData));
|
||||
sdfStream.WriteBytes(mip.Data.Get(), mip.Data.Length());
|
||||
}
|
||||
sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load all chunks with a mesh data
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SDF.Texture)
|
||||
{
|
||||
// SDF data from file (only if has no cached texture data)
|
||||
if (LoadChunk(15))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No SDF texture
|
||||
ReleaseChunk(15);
|
||||
}
|
||||
}
|
||||
|
||||
// Set mesh header data
|
||||
auto headerChunk = GET_CHUNK(0);
|
||||
ASSERT(headerChunk != nullptr);
|
||||
headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition());
|
||||
|
||||
#undef GET_CHUNK
|
||||
|
||||
// Save
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = SerializedVersion;
|
||||
if (IsVirtual())
|
||||
Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks));
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
if (IsVirtual())
|
||||
Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks));
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold, bool useGPU)
|
||||
{
|
||||
if (EnableModelSDF == 2)
|
||||
@@ -666,6 +342,172 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
{
|
||||
if (ModelBase::LoadHeader(stream, headerVersion))
|
||||
return true;
|
||||
|
||||
// LODs
|
||||
byte lods = stream.ReadByte();
|
||||
if (lods == 0 || lods > MODEL_MAX_LODS)
|
||||
return true;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
stream.ReadFloat(&lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
uint16 meshesCount;
|
||||
stream.ReadUint16(&meshesCount);
|
||||
if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES)
|
||||
return true;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
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);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= MaterialSlots.Count())
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, MaterialSlots.Count());
|
||||
return true;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Lightmap UVs channel
|
||||
int8 lightmapUVs;
|
||||
stream.ReadInt8(&lightmapUVs);
|
||||
mesh.LightmapUVsIndex = (int32)lightmapUVs;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Model::SaveHeader(WriteStream& stream)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
stream.Write(mesh.GetMaterialSlotIndex());
|
||||
stream.Write(mesh.GetBox());
|
||||
stream.Write(mesh.GetSphere());
|
||||
stream.Write((int8)mesh.LightmapUVsIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream, modelData))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)modelData.LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = modelData.LODs[lodIndex];
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
BoundingBox box;
|
||||
BoundingSphere sphere;
|
||||
mesh->CalculateBounds(box, sphere);
|
||||
stream.Write(mesh->MaterialSlotIndex);
|
||||
stream.Write(box);
|
||||
stream.Write(sphere);
|
||||
stream.Write((int8)mesh->LightmapUVsIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk)
|
||||
{
|
||||
if (ModelBase::Save(withMeshDataFromGpu, getChunk))
|
||||
return true;
|
||||
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Download SDF data
|
||||
if (SDF.Texture)
|
||||
{
|
||||
auto sdfChunk = getChunk(15);
|
||||
if (sdfChunk == nullptr)
|
||||
return true;
|
||||
MemoryWriteStream sdfStream;
|
||||
sdfStream.WriteInt32(1); // Version
|
||||
ModelSDFHeader data(SDF, SDF.Texture->GetDescription());
|
||||
sdfStream.WriteBytes(&data, sizeof(data));
|
||||
TextureData sdfTextureData;
|
||||
if (SDF.Texture->DownloadData(sdfTextureData))
|
||||
return true;
|
||||
for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++)
|
||||
{
|
||||
auto& mip = sdfTextureData.Items[0].Mips[mipLevel];
|
||||
ModelSDFMip mipData(mipLevel, mip);
|
||||
sdfStream.WriteBytes(&mipData, sizeof(mipData));
|
||||
sdfStream.WriteBytes(mip.Data.Get(), mip.Data.Length());
|
||||
}
|
||||
sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDF.Texture)
|
||||
{
|
||||
// SDF data from file (only if has no cached texture data)
|
||||
if (LoadChunk(15))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No SDF texture
|
||||
ReleaseChunk(15);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Model::SetupMaterialSlots(int32 slotsCount)
|
||||
{
|
||||
ModelBase::SetupMaterialSlots(slotsCount);
|
||||
@@ -687,6 +529,26 @@ int32 Model::GetLODsCount() const
|
||||
return LODs.Count();
|
||||
}
|
||||
|
||||
const MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex) const
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
void Model::GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex) const
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
meshes.Resize(lod.Meshes.Count());
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
meshes[meshIndex] = &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
void Model::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
@@ -722,91 +584,11 @@ Asset::LoadResult Model::load()
|
||||
if (chunk0 == nullptr || chunk0->IsMissing())
|
||||
return LoadResult::MissingDataChunk;
|
||||
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
||||
ReadStream* stream = &headerStream;
|
||||
|
||||
// Min Screen Size
|
||||
stream->ReadFloat(&MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
int32 materialSlotsCount;
|
||||
stream->ReadInt32(&materialSlotsCount);
|
||||
if (materialSlotsCount <= 0 || materialSlotsCount > 4096)
|
||||
|
||||
// Load asset data (anything but mesh contents that use streaming)
|
||||
byte headerVersion;
|
||||
if (LoadHeader(headerStream, headerVersion))
|
||||
return LoadResult::InvalidData;
|
||||
MaterialSlots.Resize(materialSlotsCount, false);
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
// Material
|
||||
Guid materialId;
|
||||
stream->Read(materialId);
|
||||
slot.Material = materialId;
|
||||
|
||||
// Shadows Mode
|
||||
slot.ShadowsMode = static_cast<ShadowsCastingMode>(stream->ReadByte());
|
||||
|
||||
// Name
|
||||
stream->ReadString(&slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
byte lods;
|
||||
stream->ReadByte(&lods);
|
||||
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++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
|
||||
// Screen Size
|
||||
stream->ReadFloat(&lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream->ReadUint16(&meshesCount);
|
||||
if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES)
|
||||
return LoadResult::InvalidData;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
|
||||
// Allocate memory
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
|
||||
// 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);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount)
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream->ReadBoundingBox(&box);
|
||||
BoundingSphere sphere;
|
||||
stream->ReadBoundingSphere(&sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Has Lightmap UVs
|
||||
bool hasLightmapUVs = stream->ReadBool();
|
||||
mesh.LightmapUVsIndex = hasLightmapUVs ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Load SDF
|
||||
auto chunk15 = GetChunk(15);
|
||||
@@ -815,7 +597,7 @@ Asset::LoadResult Model::load()
|
||||
PROFILE_CPU_NAMED("SDF");
|
||||
MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size());
|
||||
int32 version;
|
||||
sdfStream.ReadInt32(&version);
|
||||
sdfStream.Read(version);
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
|
||||
@@ -159,7 +159,7 @@ public:
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(Model, 25);
|
||||
DECLARE_BINARY_ASSET_HEADER(Model, 30);
|
||||
friend Mesh;
|
||||
|
||||
public:
|
||||
@@ -274,19 +274,6 @@ public:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupLODs(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
/// <remarks>If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread.</remarks>
|
||||
/// <param name="withMeshDataFromGpu">True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location and it's loaded.</param>
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True if cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Generates the Sign Distant Field for this model.
|
||||
/// </summary>
|
||||
@@ -312,10 +299,22 @@ private:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool Init(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
// [ModelBase]
|
||||
bool LoadHeader(ReadStream& stream, byte& headerVersion);
|
||||
#if USE_EDITOR
|
||||
friend class ImportModel;
|
||||
bool SaveHeader(WriteStream& stream) override;
|
||||
static bool SaveHeader(WriteStream& stream, const ModelData& modelData);
|
||||
bool Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk) override;
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [ModelBase]
|
||||
void SetupMaterialSlots(int32 slotsCount) override;
|
||||
int32 GetLODsCount() const override;
|
||||
const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override;
|
||||
MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override;
|
||||
void GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex = 0) const override;
|
||||
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
void InitAsVirtual() override;
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include "Engine/Content/WeakAssetReference.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Graphics/Config.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
#include "Engine/Graphics/Models/MeshBase.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
|
||||
#include "Engine/Threading/ThreadPoolTask.h"
|
||||
#define STREAM_TASK_BASE ThreadPoolTask
|
||||
@@ -12,8 +15,11 @@
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#define STREAM_TASK_BASE MainThreadTask
|
||||
#endif
|
||||
#include "SkinnedModel.h" // TODO: remove this
|
||||
#include "Model.h" // TODO: remove this
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Model LOD streaming task.
|
||||
@@ -54,93 +60,26 @@ public:
|
||||
}
|
||||
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)
|
||||
// Load meshes data and pass data to the GPU buffers
|
||||
Array<MeshBase*> meshes;
|
||||
model->GetMeshes(meshes, _lodIndex);
|
||||
if (model->Is<SkinnedModel>())
|
||||
byte meshVersion = stream.ReadByte();
|
||||
if (meshVersion < 2 || meshVersion > ModelBase::MeshVersion)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
LOG(Warning, "Unsupported mesh version {}", meshVersion);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
for (int32 i = 0; i < meshes.Count(); i++)
|
||||
if (model->LoadMesh(stream, meshVersion, meshes[meshIndex]))
|
||||
{
|
||||
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;
|
||||
}
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'", meshIndex, _lodIndex, model->ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update residency level
|
||||
// Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
|
||||
model->_loadedLODs++;
|
||||
model->ResidencyChanged();
|
||||
|
||||
@@ -209,6 +148,638 @@ void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const
|
||||
GetChunkData(chunkIndex, data);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
if (withMeshDataFromGpu && IsInMainThread())
|
||||
{
|
||||
LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread).");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && !withMeshDataFromGpu)
|
||||
{
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(tmpChunks, sizeof(tmpChunks));
|
||||
Array<FlaxChunk> chunks;
|
||||
if (IsVirtual())
|
||||
chunks.Resize(ASSET_FILE_DATA_CHUNKS);
|
||||
Function<FlaxChunk*(int32)> getChunk = [&](int32 index) -> FlaxChunk* {
|
||||
return IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index);
|
||||
};
|
||||
|
||||
// Save LODs data
|
||||
const int32 lodsCount = GetLODsCount();
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Fetch runtime mesh data (from GPU)
|
||||
MemoryWriteStream meshesStream;
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
meshesStream.SetPosition(0);
|
||||
if (SaveLOD(meshesStream, lodIndex))
|
||||
return true;
|
||||
auto lodChunk = getChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex));
|
||||
if (lodChunk == nullptr)
|
||||
return true;
|
||||
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
|
||||
}
|
||||
}
|
||||
else if (!IsVirtual())
|
||||
{
|
||||
// Load all chunks with a mesh data
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save custom data
|
||||
if (Save(withMeshDataFromGpu, getChunk))
|
||||
return true;
|
||||
|
||||
// Save header data
|
||||
{
|
||||
MemoryWriteStream headerStream(1024);
|
||||
if (SaveHeader(headerStream))
|
||||
return true;
|
||||
auto headerChunk = getChunk(0);
|
||||
ASSERT(headerChunk != nullptr);
|
||||
headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition());
|
||||
}
|
||||
|
||||
// Save file
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = GetSerializedVersion();
|
||||
if (IsVirtual())
|
||||
Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks));
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
if (IsVirtual())
|
||||
Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks));
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
{
|
||||
// Basic info
|
||||
stream.Read(headerVersion);
|
||||
if (headerVersion < 2 || headerVersion > HeaderVersion)
|
||||
{
|
||||
LOG(Warning, "Unsupported model asset header version {}", headerVersion);
|
||||
return true;
|
||||
}
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Read(MinScreenSize);
|
||||
|
||||
// Materials
|
||||
int32 materialsCount;
|
||||
stream.Read(materialsCount);
|
||||
if (materialsCount < 0 || materialsCount > 4096)
|
||||
return true;
|
||||
MaterialSlots.Resize(materialsCount, false);
|
||||
Guid materialId;
|
||||
for (auto& slot : MaterialSlots)
|
||||
{
|
||||
stream.Read(materialId);
|
||||
slot.Material = materialId;
|
||||
slot.ShadowsMode = (ShadowsCastingMode)stream.ReadByte();
|
||||
stream.Read(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
{
|
||||
// Load descriptor
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
uint32 vertices, triangles;
|
||||
stream.Read(vertices);
|
||||
stream.Read(triangles);
|
||||
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;
|
||||
Array<const void*, FixedAllocation<MODEL_MAX_VB>> vbData;
|
||||
Array<GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> vbLayout;
|
||||
byte vbCount;
|
||||
stream.Read(vbCount);
|
||||
if (vbCount > MODEL_MAX_VB)
|
||||
return true;
|
||||
vbData.Resize(vbCount);
|
||||
vbLayout.Resize(vbCount);
|
||||
for (int32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
GPUVertexLayout::Elements elements;
|
||||
stream.Read(elements);
|
||||
vbLayout[i] = GPUVertexLayout::Get(elements);
|
||||
}
|
||||
|
||||
// Move over actual mesh data
|
||||
for (int32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
auto layout = vbLayout[i];
|
||||
if (!layout)
|
||||
{
|
||||
LOG(Warning, "Failed to get vertex layout for buffer {}", i);
|
||||
return true;
|
||||
}
|
||||
vbData[i] = stream.Move(vertices * layout->GetStride());
|
||||
}
|
||||
const auto ibData = stream.Move(indicesCount * ibStride);
|
||||
|
||||
// Pass results if read-only
|
||||
if (dataIfReadOnly)
|
||||
{
|
||||
dataIfReadOnly->Vertices = vertices;
|
||||
dataIfReadOnly->Triangles = triangles;
|
||||
dataIfReadOnly->IBStride = ibStride;
|
||||
dataIfReadOnly->VBData = vbData;
|
||||
dataIfReadOnly->VBLayout = vbLayout;
|
||||
dataIfReadOnly->IBData = ibData;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup GPU resources
|
||||
return mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelBase::SaveHeader(WriteStream& stream)
|
||||
{
|
||||
// Basic info
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Write(HeaderVersion);
|
||||
stream.Write(MinScreenSize);
|
||||
|
||||
// Materials
|
||||
stream.Write(MaterialSlots.Count());
|
||||
for (const auto& slot : MaterialSlots)
|
||||
{
|
||||
stream.Write(slot.Material.GetID());
|
||||
stream.Write((byte)slot.ShadowsMode);
|
||||
stream.Write(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
{
|
||||
// Validate data
|
||||
if (modelData.LODs.Count() > MODEL_MAX_LODS)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("LODs"), TEXT("Too many LODs."));
|
||||
return true;
|
||||
}
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = modelData.LODs[lodIndex];
|
||||
if (lod.Meshes.Count() > MODEL_MAX_MESHES)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("LOD.Meshes"), TEXT("Too many meshes."));
|
||||
return true;
|
||||
}
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
auto& mesh = *lod.Meshes[meshIndex];
|
||||
if (mesh.MaterialSlotIndex < 0 || mesh.MaterialSlotIndex >= modelData.Materials.Count())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("MaterialSlotIndex"), TEXT("Incorrect material index used by the mesh."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Basic info
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Write(HeaderVersion);
|
||||
stream.Write(modelData.MinScreenSize);
|
||||
|
||||
// Materials
|
||||
stream.WriteInt32(modelData.Materials.Count());
|
||||
for (const auto& slot : modelData.Materials)
|
||||
{
|
||||
stream.Write(slot.AssetID);
|
||||
stream.Write((byte)slot.ShadowsMode);
|
||||
stream.Write(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveLOD(WriteStream& stream, int32 lodIndex) const
|
||||
{
|
||||
// Download all meshes buffers from the GPU
|
||||
Array<Task*> tasks;
|
||||
Array<const MeshBase*> meshes;
|
||||
GetMeshes(meshes, lodIndex);
|
||||
const int32 meshesCount = meshes.Count();
|
||||
struct MeshData
|
||||
{
|
||||
BytesContainer VB[3];
|
||||
BytesContainer IB;
|
||||
};
|
||||
Array<MeshData> meshesData;
|
||||
meshesData.Resize(meshesCount);
|
||||
tasks.EnsureCapacity(meshesCount * 4);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = *meshes[meshIndex];
|
||||
auto& meshData = meshesData[meshIndex];
|
||||
|
||||
// Vertex Buffer 0 (required)
|
||||
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB[0]);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Vertex Buffer 1 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB[1]);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Vertex Buffer 2 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB[2]);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Index Buffer (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Wait for async tasks
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
|
||||
// Create meshes data
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
stream.Write(MeshVersion);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = *meshes[meshIndex];
|
||||
const auto& meshData = meshesData[meshIndex];
|
||||
uint32 vertices = mesh.GetVertexCount();
|
||||
uint32 triangles = mesh.GetTriangleCount();
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
uint32 ibSize = indicesCount * ibStride;
|
||||
|
||||
// Validate data
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
Array<const GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> vbLayout;
|
||||
for (int32 vbIndex = 0; vbIndex < MODEL_MAX_VB; vbIndex++)
|
||||
{
|
||||
if (vbIndex != 0 && meshData.VB[vbIndex].IsInvalid()) // VB0 is always required
|
||||
continue;
|
||||
const GPUBuffer* vb = mesh.GetVertexBuffer(vbIndex);
|
||||
if (!vb)
|
||||
break;
|
||||
const GPUVertexLayout* layout = vb->GetVertexLayout();
|
||||
if (!layout)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer {}. Missing vertex layout.", vbIndex);
|
||||
return true;
|
||||
}
|
||||
const uint32 vbSize = vb->GetSize();
|
||||
vbLayout.Add(layout);
|
||||
if ((uint32)meshData.VB[vbIndex].Length() != vbSize)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer {} size. Got {} bytes but expected {} bytes. Stride: {}. Layout:\n{}", vbIndex, meshData.VB[vbIndex].Length(), vbSize, vb->GetStride(), layout->GetElementsString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((uint32)meshData.IB.Length() < ibSize)
|
||||
{
|
||||
LOG(Warning, "Invalid index buffer size. Got {} bytes bytes expected {} bytes. Stride: {}", meshData.IB.Length(), ibSize, ibStride);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write descriptor
|
||||
stream.Write(vertices);
|
||||
stream.Write(triangles);
|
||||
byte vbCount = (byte)vbLayout.Count();
|
||||
stream.Write(vbCount);
|
||||
for (const GPUVertexLayout* layout : vbLayout)
|
||||
stream.Write(layout->GetElements());
|
||||
|
||||
// Write actual mesh data
|
||||
for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++)
|
||||
{
|
||||
const auto& vb = meshData.VB[vbIndex];
|
||||
stream.WriteBytes(vb.Get(), vb.Length());
|
||||
}
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
stream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
}
|
||||
else if (shouldUse16BitIndexBuffer)
|
||||
{
|
||||
// Convert 32-bit indices to 16-bit
|
||||
auto ib = (const int32*)meshData.IB.Get();
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
{
|
||||
stream.WriteUint16(ib[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
|
||||
// Write custom data
|
||||
if (SaveMesh(stream, &mesh))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool saveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex))
|
||||
{
|
||||
// Create meshes data
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
stream.Write(MeshVersion);
|
||||
const auto& lod = modelData.LODs[lodIndex];
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
const auto& mesh = *lod.Meshes[meshIndex];
|
||||
uint32 vertices = (uint32)mesh.Positions.Count();
|
||||
uint32 indicesCount = (uint32)mesh.Indices.Count();
|
||||
uint32 triangles = indicesCount / 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const bool isSkinned = mesh.BlendIndices.HasItems() && mesh.BlendWeights.HasItems();
|
||||
|
||||
// Validate data
|
||||
if (vertices == 0 || triangles == 0 || indicesCount % 3 != 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
bool hasUVs = mesh.UVs.HasItems();
|
||||
if (hasUVs && (uint32)mesh.UVs.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("UVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasNormals = mesh.Normals.HasItems();
|
||||
if (hasNormals && (uint32)mesh.Normals.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
|
||||
return true;
|
||||
}
|
||||
bool hasTangents = mesh.Tangents.HasItems();
|
||||
if (hasTangents && (uint32)mesh.Tangents.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
|
||||
return true;
|
||||
}
|
||||
bool hasBitangentSigns = mesh.BitangentSigns.HasItems();
|
||||
if (hasBitangentSigns && (uint32)mesh.BitangentSigns.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
|
||||
return true;
|
||||
}
|
||||
bool hasLightmapUVs = mesh.LightmapUVs.HasItems();
|
||||
if (hasLightmapUVs && (uint32)mesh.LightmapUVs.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasColors = mesh.Colors.HasItems();
|
||||
if (hasColors && (uint32)mesh.Colors.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Colors"));
|
||||
return true;
|
||||
}
|
||||
if (isSkinned && (uint32)mesh.BlendIndices.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices"));
|
||||
return true;
|
||||
}
|
||||
if (isSkinned && (uint32)mesh.BlendWeights.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Define vertex buffers layout and packing
|
||||
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
|
||||
const bool useSeparatePositions = !isSkinned;
|
||||
const bool useSeparateColors = !isSkinned;
|
||||
{
|
||||
byte vbIndex = 0;
|
||||
// TODO: add option to quantize vertex positions (eg. 16-bit)
|
||||
// TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords)
|
||||
// TODO: add support for 16-bit blend indices (up to 65535 bones)
|
||||
|
||||
// Position
|
||||
if (useSeparatePositions)
|
||||
{
|
||||
auto& vb0 = vbElements.AddOne();
|
||||
vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
|
||||
vbIndex++;
|
||||
}
|
||||
|
||||
// General purpose components
|
||||
{
|
||||
auto& vb = vbElements.AddOne();
|
||||
if (!useSeparatePositions)
|
||||
vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
|
||||
if (hasUVs)
|
||||
vb.Add({ VertexElement::Types::TexCoord, vbIndex, 0, 0, PixelFormat::R16G16_Float });
|
||||
if (hasLightmapUVs)
|
||||
vb.Add({ VertexElement::Types::TexCoord1, vbIndex, 0, 0, PixelFormat::R16G16_Float });
|
||||
vb.Add({ VertexElement::Types::Normal, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
if (isSkinned)
|
||||
{
|
||||
vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UInt });
|
||||
vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float });
|
||||
}
|
||||
if (!useSeparateColors && hasColors)
|
||||
vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm });
|
||||
vbIndex++;
|
||||
}
|
||||
|
||||
// Colors
|
||||
if (useSeparateColors && hasColors)
|
||||
{
|
||||
auto& vb = vbElements.AddOne();
|
||||
vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm });
|
||||
vbIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Write descriptor
|
||||
stream.Write(vertices);
|
||||
stream.Write(triangles);
|
||||
byte vbCount = (byte)vbElements.Count();
|
||||
stream.Write(vbCount);
|
||||
for (const auto& elements : vbElements)
|
||||
stream.Write(elements);
|
||||
|
||||
// Write vertex buffers
|
||||
for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++)
|
||||
{
|
||||
if (useSeparatePositions && vbIndex == 0)
|
||||
{
|
||||
// Fast path for vertex positions of static models using the first buffer
|
||||
stream.WriteBytes(mesh.Positions.Get(), sizeof(Float3) * vertices);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Write vertex components interleaved
|
||||
const GPUVertexLayout::Elements& layout = vbElements[vbIndex];
|
||||
for (uint32 vertex = 0; vertex < vertices; vertex++)
|
||||
{
|
||||
for (const VertexElement& element : layout)
|
||||
{
|
||||
switch (element.Type)
|
||||
{
|
||||
case VertexElement::Types::Position:
|
||||
{
|
||||
const Float3 position = mesh.Positions.Get()[vertex];
|
||||
stream.Write(position);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Color:
|
||||
{
|
||||
const Color32 color(mesh.Colors.Get()[vertex]);
|
||||
stream.Write(color);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Normal:
|
||||
{
|
||||
const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ;
|
||||
const FloatR10G10B10A2 normalEnc(normal * 0.5f + 0.5f, 0);
|
||||
stream.Write(normalEnc.Value);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Tangent:
|
||||
{
|
||||
const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ;
|
||||
const Float3 tangent = hasTangents ? mesh.Tangents.Get()[vertex] : Float3::UnitX;
|
||||
const float bitangentSign = hasBitangentSigns ? mesh.BitangentSigns.Get()[vertex] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent);
|
||||
const Float1010102 tangentEnc(tangent * 0.5f + 0.5f, (byte)(bitangentSign < 0 ? 1 : 0));
|
||||
stream.Write(tangentEnc.Value);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::BlendIndices:
|
||||
{
|
||||
const Int4 blendIndices = mesh.BlendIndices.Get()[vertex];
|
||||
const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
||||
stream.Write(blendIndicesEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::BlendWeights:
|
||||
{
|
||||
const Float4 blendWeights = mesh.BlendWeights.Get()[vertex];
|
||||
const Half4 blendWeightsEnc(blendWeights);
|
||||
stream.Write(blendWeightsEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::TexCoord0:
|
||||
{
|
||||
const Float2 uv = hasUVs ? mesh.UVs.Get()[vertex] : Float2::Zero;
|
||||
const Half2 uvEnc(uv);
|
||||
stream.Write(uvEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::TexCoord1:
|
||||
{
|
||||
// TODO: refactor LightmapUVs into a generic TexCoord channel and support up to 4 UVs
|
||||
const Float2 lightmapUV = hasLightmapUVs ? mesh.LightmapUVs.Get()[vertex] : Float2::Zero;
|
||||
const Half2 lightmapUVEnc(lightmapUV);
|
||||
stream.Write(lightmapUVEnc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(Error, "Unsupported vertex element: {}", element.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write index buffer
|
||||
const uint32* meshIndices = mesh.Indices.Get();
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
stream.Write((uint16)meshIndices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteBytes(meshIndices, sizeof(uint32) * indicesCount);
|
||||
}
|
||||
|
||||
// Write custom data
|
||||
if (saveMesh && saveMesh(stream, modelData, lodIndex, meshIndex))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveMesh(WriteStream& stream, const MeshBase* mesh) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void ModelBase::CancelStreaming()
|
||||
{
|
||||
BinaryAsset::CancelStreaming();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1)
|
||||
|
||||
class MeshBase;
|
||||
class StreamModelLODTask;
|
||||
struct RenderContextBatch;
|
||||
|
||||
/// <summary>
|
||||
@@ -28,9 +29,12 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset
|
||||
{
|
||||
DECLARE_ASSET_HEADER(ModelBase);
|
||||
friend MeshBase;
|
||||
friend class StreamModelLODTask;
|
||||
friend StreamModelLODTask;
|
||||
|
||||
protected:
|
||||
static constexpr byte HeaderVersion = 2;
|
||||
static constexpr byte MeshVersion = 2;
|
||||
|
||||
bool _initialized = false;
|
||||
int32 _loadedLODs = 0;
|
||||
StreamModelLODTask* _streamingTask = nullptr;
|
||||
@@ -174,12 +178,27 @@ public:
|
||||
/// <summary>
|
||||
/// Gets amount of the level of details in the model.
|
||||
/// </summary>
|
||||
virtual int32 GetLODsCount() const = 0;
|
||||
API_PROPERTY(Sealed) virtual int32 GetLODsCount() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mesh for a particular LOD index.
|
||||
/// </summary>
|
||||
virtual const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mesh for a particular LOD index.
|
||||
/// </summary>
|
||||
API_FUNCTION(Sealed) virtual MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the meshes for a particular LOD index.
|
||||
/// </summary>
|
||||
virtual void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) = 0;
|
||||
virtual void GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex = 0) const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the meshes for a particular LOD index.
|
||||
/// </summary>
|
||||
API_FUNCTION(Sealed) virtual void GetMeshes(API_PARAM(Out) Array<MeshBase*>& meshes, int32 lodIndex = 0) = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -196,6 +215,38 @@ public:
|
||||
/// <param name="data">The data (it may be missing if failed to get it).</param>
|
||||
void GetLODData(int32 lodIndex, BytesContainer& data) const;
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
/// <remarks>If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread.</remarks>
|
||||
/// <param name="withMeshDataFromGpu">True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location, and it's loaded.</param>
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True when cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
friend class AssetExporters;
|
||||
friend class MeshAccessor;
|
||||
struct MeshData
|
||||
{
|
||||
uint32 Triangles, Vertices, IBStride;
|
||||
Array<const void*, FixedAllocation<MODEL_MAX_VB>> VBData;
|
||||
Array<class GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> VBLayout;
|
||||
const void* IBData;
|
||||
};
|
||||
virtual bool LoadMesh(class MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly = nullptr);
|
||||
bool LoadHeader(ReadStream& stream, byte& headerVersion);
|
||||
#if USE_EDITOR
|
||||
virtual bool SaveHeader(WriteStream& stream);
|
||||
static bool SaveHeader(WriteStream& stream, const class ModelData& modelData);
|
||||
bool SaveLOD(WriteStream& stream, int32 lodIndex) const;
|
||||
static bool SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool(saveMesh)(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex) = nullptr);
|
||||
virtual bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const;
|
||||
virtual bool Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk);
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
void CancelStreaming() override;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#endif
|
||||
|
||||
@@ -121,9 +122,9 @@ SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bo
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
#if !BUILD_RELEASE
|
||||
LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString());
|
||||
#endif
|
||||
#endif
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
@@ -231,7 +232,7 @@ FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& ren
|
||||
if (!model->CanBeRendered())
|
||||
return;
|
||||
if (!info.Buffer->IsValidFor(model))
|
||||
info.Buffer->Setup(model);
|
||||
info.Buffer->Setup(model);
|
||||
const auto frame = Engine::FrameCount;
|
||||
const auto modelFrame = info.DrawState->PrevFrame + 1;
|
||||
|
||||
@@ -395,7 +396,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
// Validate input
|
||||
if (nodes.Count() <= 0 || nodes.Count() > MAX_uint16)
|
||||
return true;
|
||||
if (bones.Count() <= 0 || bones.Count() > MAX_BONES_PER_MODEL)
|
||||
if (bones.Count() <= 0 || bones.Count() > MODEL_MAX_BONES_PER_MODEL)
|
||||
return true;
|
||||
auto model = this;
|
||||
if (!model->IsVirtual())
|
||||
@@ -431,310 +432,6 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
if (withMeshDataFromGpu && IsInMainThread())
|
||||
{
|
||||
LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread).");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && !withMeshDataFromGpu)
|
||||
{
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Create model data header
|
||||
MemoryWriteStream headerStream(1024);
|
||||
MemoryWriteStream* stream = &headerStream;
|
||||
{
|
||||
// Header Version
|
||||
stream->WriteByte(1);
|
||||
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(MaterialSlots.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < MaterialSlots.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
const auto id = slot.Material.GetID();
|
||||
stream->Write(id);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
const int32 lods = LODs.Count();
|
||||
stream->WriteByte(lods);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot index
|
||||
stream->WriteInt32(mesh.GetMaterialSlotIndex());
|
||||
|
||||
// Box
|
||||
const auto box = mesh.GetBox();
|
||||
stream->WriteBoundingBox(box);
|
||||
|
||||
// Sphere
|
||||
const auto sphere = mesh.GetSphere();
|
||||
stream->WriteBoundingSphere(sphere);
|
||||
|
||||
// Blend Shapes
|
||||
stream->WriteUint16(mesh.BlendShapes.Count());
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < mesh.BlendShapes.Count(); blendShapeIndex++)
|
||||
{
|
||||
auto& blendShape = mesh.BlendShapes[blendShapeIndex];
|
||||
stream->WriteString(blendShape.Name, 13);
|
||||
stream->WriteFloat(blendShape.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
stream->WriteInt32(Skeleton.Nodes.Count());
|
||||
|
||||
// For each node
|
||||
for (int32 nodeIndex = 0; nodeIndex < Skeleton.Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
auto& node = Skeleton.Nodes[nodeIndex];
|
||||
|
||||
stream->Write(node.ParentIndex);
|
||||
stream->WriteTransform(node.LocalTransform);
|
||||
stream->WriteString(node.Name, 71);
|
||||
}
|
||||
|
||||
stream->WriteInt32(Skeleton.Bones.Count());
|
||||
|
||||
// For each bone
|
||||
for (int32 boneIndex = 0; boneIndex < Skeleton.Bones.Count(); boneIndex++)
|
||||
{
|
||||
auto& bone = Skeleton.Bones[boneIndex];
|
||||
|
||||
stream->Write(bone.ParentIndex);
|
||||
stream->Write(bone.NodeIndex);
|
||||
stream->WriteTransform(bone.LocalTransform);
|
||||
stream->Write(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
stream->WriteInt32(_skeletonRetargets.Count());
|
||||
for (const auto& retarget : _skeletonRetargets)
|
||||
{
|
||||
stream->Write(retarget.SourceAsset);
|
||||
stream->Write(retarget.SkeletonAsset);
|
||||
stream->Write(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(tmpChunks, sizeof(tmpChunks));
|
||||
Array<FlaxChunk> chunks;
|
||||
if (IsVirtual())
|
||||
chunks.Resize(ASSET_FILE_DATA_CHUNKS);
|
||||
#define GET_CHUNK(index) (IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index))
|
||||
|
||||
// Check if use data from drive or from GPU
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Download all meshes buffers
|
||||
Array<Task*> tasks;
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
const int32 meshesCount = lod.Meshes.Count();
|
||||
struct MeshData
|
||||
{
|
||||
BytesContainer VB0;
|
||||
BytesContainer IB;
|
||||
|
||||
uint32 DataSize() const
|
||||
{
|
||||
return VB0.Length() + IB.Length();
|
||||
}
|
||||
};
|
||||
Array<MeshData> meshesData;
|
||||
meshesData.Resize(meshesCount);
|
||||
tasks.EnsureCapacity(meshesCount * 4);
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
auto& meshData = meshesData[meshIndex];
|
||||
|
||||
// Vertex Buffer 0 (required)
|
||||
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Index Buffer (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
tasks.Clear();
|
||||
|
||||
// Create meshes data
|
||||
{
|
||||
int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool));
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
dataSize += meshesData[meshIndex].DataSize();
|
||||
MemoryWriteStream meshesStream(Math::RoundUpToPowerOf2(dataSize));
|
||||
|
||||
meshesStream.WriteByte(1);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
const auto& meshData = meshesData[meshIndex];
|
||||
|
||||
const uint32 vertices = mesh.GetVertexCount();
|
||||
const uint32 triangles = mesh.GetTriangleCount();
|
||||
const uint32 vb0Size = vertices * sizeof(VB0SkinnedElementType);
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
|
||||
const uint32 ibSize = indicesCount * (use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32));
|
||||
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.VB0.Length() < vb0Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 0 size.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.IB.Length() < ibSize)
|
||||
{
|
||||
LOG(Warning, "Invalid index buffer size.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
meshesStream.WriteUint32(vertices);
|
||||
meshesStream.WriteUint32(triangles);
|
||||
meshesStream.WriteUint16(mesh.BlendShapes.Count());
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
{
|
||||
meshesStream.WriteBool(blendShape.UseNormals);
|
||||
meshesStream.WriteUint32(blendShape.MinVertexIndex);
|
||||
meshesStream.WriteUint32(blendShape.MaxVertexIndex);
|
||||
meshesStream.WriteUint32(blendShape.Vertices.Count());
|
||||
meshesStream.WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size);
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
meshesStream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
}
|
||||
else if (shouldUse16BitIndexBuffer)
|
||||
{
|
||||
const auto ib = reinterpret_cast<const int32*>(meshData.IB.Get());
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
{
|
||||
meshesStream.WriteUint16(ib[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
}
|
||||
|
||||
// Override meshes data chunk with the fetched GPU meshes memory
|
||||
auto lodChunk = GET_CHUNK(MODEL_LOD_TO_CHUNK_INDEX(lodIndex));
|
||||
if (lodChunk == nullptr)
|
||||
return true;
|
||||
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(!IsVirtual());
|
||||
|
||||
// Load all chunks with a mesh data
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set mesh header data
|
||||
auto headerChunk = GET_CHUNK(0);
|
||||
ASSERT(headerChunk != nullptr);
|
||||
headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition());
|
||||
|
||||
#undef GET_CHUNK
|
||||
|
||||
// Save
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = SerializedVersion;
|
||||
if (IsVirtual())
|
||||
Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks));
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
if (IsVirtual())
|
||||
Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks));
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||
{
|
||||
if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS)
|
||||
@@ -777,6 +474,329 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||
return false;
|
||||
}
|
||||
|
||||
void BlendShape::LoadHeader(ReadStream& stream, byte headerVersion)
|
||||
{
|
||||
stream.Read(Name, 13);
|
||||
stream.Read(Weight);
|
||||
}
|
||||
|
||||
void BlendShape::Load(ReadStream& stream, byte meshVersion)
|
||||
{
|
||||
UseNormals = stream.ReadBool();
|
||||
stream.ReadUint32(&MinVertexIndex);
|
||||
stream.ReadUint32(&MaxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
Vertices.Resize(blendShapeVertices);
|
||||
stream.ReadBytes(Vertices.Get(), Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void BlendShape::SaveHeader(WriteStream& stream) const
|
||||
{
|
||||
stream.Write(Name, 13);
|
||||
stream.Write(Weight);
|
||||
}
|
||||
|
||||
void BlendShape::Save(WriteStream& stream) const
|
||||
{
|
||||
stream.WriteBool(UseNormals);
|
||||
stream.WriteUint32(MinVertexIndex);
|
||||
stream.WriteUint32(MaxVertexIndex);
|
||||
stream.WriteUint32(Vertices.Count());
|
||||
stream.WriteBytes(Vertices.Get(), Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
{
|
||||
if (ModelBase::LoadMesh(stream, meshVersion, mesh, dataIfReadOnly))
|
||||
return true;
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
auto skinnedMesh = (SkinnedMesh*)mesh;
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapesCount;
|
||||
stream.Read(blendShapesCount);
|
||||
if (dataIfReadOnly)
|
||||
{
|
||||
// Skip blend shapes
|
||||
BlendShape tmp;
|
||||
while (blendShapesCount-- != 0)
|
||||
tmp.Load(stream, meshVersion);
|
||||
return false;
|
||||
}
|
||||
if (blendShapesCount != skinnedMesh->BlendShapes.Count())
|
||||
{
|
||||
LOG(Warning, "Incorrect blend shapes amount: {} (expected: {})", blendShapesCount, skinnedMesh->BlendShapes.Count());
|
||||
return true;
|
||||
}
|
||||
for (auto& blendShape : skinnedMesh->BlendShapes)
|
||||
blendShape.Load(stream, meshVersion);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
{
|
||||
if (ModelBase::LoadHeader(stream, headerVersion))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
byte lods = stream.ReadByte();
|
||||
if (lods > MODEL_MAX_LODS)
|
||||
return true;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
stream.Read(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
uint16 meshesCount;
|
||||
stream.Read(meshesCount);
|
||||
if (meshesCount > MODEL_MAX_MESHES)
|
||||
return true;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
SkinnedMesh& mesh = lod.Meshes[meshIndex];
|
||||
mesh.Link(this, lodIndex, meshIndex);
|
||||
|
||||
// Material Slot index
|
||||
int32 materialSlotIndex;
|
||||
stream.Read(materialSlotIndex);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= MaterialSlots.Count())
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, MaterialSlots.Count());
|
||||
return true;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapes;
|
||||
stream.Read(blendShapes);
|
||||
mesh.BlendShapes.Resize(blendShapes);
|
||||
for (auto& blendShape : mesh.BlendShapes)
|
||||
blendShape.LoadHeader(stream, headerVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
int32 nodesCount;
|
||||
stream.Read(nodesCount);
|
||||
if (nodesCount < 0)
|
||||
return true;
|
||||
Skeleton.Nodes.Resize(nodesCount, false);
|
||||
for (auto& node : Skeleton.Nodes)
|
||||
{
|
||||
stream.Read(node.ParentIndex);
|
||||
stream.Read(node.LocalTransform);
|
||||
stream.Read(node.Name, 71);
|
||||
}
|
||||
|
||||
int32 bonesCount;
|
||||
stream.Read(bonesCount);
|
||||
if (bonesCount < 0)
|
||||
return true;
|
||||
Skeleton.Bones.Resize(bonesCount, false);
|
||||
for (auto& bone : Skeleton.Bones)
|
||||
{
|
||||
stream.Read(bone.ParentIndex);
|
||||
stream.Read(bone.NodeIndex);
|
||||
stream.Read(bone.LocalTransform);
|
||||
stream.Read(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
int32 entriesCount;
|
||||
stream.Read(entriesCount);
|
||||
_skeletonRetargets.Resize(entriesCount);
|
||||
for (auto& retarget : _skeletonRetargets)
|
||||
{
|
||||
stream.Read(retarget.SourceAsset);
|
||||
stream.Read(retarget.SkeletonAsset);
|
||||
stream.Read(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool SkinnedModel::SaveHeader(WriteStream& stream)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
stream.Write(mesh.GetMaterialSlotIndex());
|
||||
stream.Write(mesh.GetBox());
|
||||
stream.Write(mesh.GetSphere());
|
||||
|
||||
// Blend Shapes
|
||||
const int32 blendShapes = mesh.BlendShapes.Count();
|
||||
stream.Write((uint16)blendShapes);
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton nodes
|
||||
const auto& skeletonNodes = Skeleton.Nodes;
|
||||
stream.Write(skeletonNodes.Count());
|
||||
for (const auto& node : skeletonNodes)
|
||||
{
|
||||
stream.Write(node.ParentIndex);
|
||||
stream.Write(node.LocalTransform);
|
||||
stream.Write(node.Name, 71);
|
||||
}
|
||||
|
||||
// Skeleton bones
|
||||
const auto& skeletonBones = Skeleton.Bones;
|
||||
stream.WriteInt32(skeletonBones.Count());
|
||||
for (const auto& bone : skeletonBones)
|
||||
{
|
||||
stream.Write(bone.ParentIndex);
|
||||
stream.Write(bone.NodeIndex);
|
||||
stream.Write(bone.LocalTransform);
|
||||
stream.Write(bone.OffsetMatrix);
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
stream.WriteInt32(_skeletonRetargets.Count());
|
||||
for (const auto& retarget : _skeletonRetargets)
|
||||
{
|
||||
stream.Write(retarget.SourceAsset);
|
||||
stream.Write(retarget.SkeletonAsset);
|
||||
stream.Write(retarget.NodesMapping);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream, modelData))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)modelData.LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = modelData.LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
BoundingBox box;
|
||||
BoundingSphere sphere;
|
||||
mesh->CalculateBounds(box, sphere);
|
||||
stream.Write(mesh->MaterialSlotIndex);
|
||||
stream.Write(box);
|
||||
stream.Write(sphere);
|
||||
|
||||
// Blend Shapes
|
||||
const int32 blendShapes = mesh->BlendShapes.Count();
|
||||
stream.WriteUint16((uint16)blendShapes);
|
||||
for (const auto& blendShape : mesh->BlendShapes)
|
||||
blendShape.SaveHeader(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton nodes
|
||||
const auto& skeletonNodes = modelData.Skeleton.Nodes;
|
||||
stream.WriteInt32(skeletonNodes.Count());
|
||||
for (const auto& node : skeletonNodes)
|
||||
{
|
||||
stream.Write(node.ParentIndex);
|
||||
stream.Write(node.LocalTransform);
|
||||
stream.Write(node.Name, 71);
|
||||
}
|
||||
|
||||
// Skeleton bones
|
||||
const auto& skeletonBones = modelData.Skeleton.Bones;
|
||||
stream.WriteInt32(skeletonBones.Count());
|
||||
for (const auto& bone : skeletonBones)
|
||||
{
|
||||
stream.Write(bone.ParentIndex);
|
||||
stream.Write(bone.NodeIndex);
|
||||
stream.Write(bone.LocalTransform);
|
||||
stream.Write(bone.OffsetMatrix);
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
stream.Write(0); // Empty list
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::SaveMesh(WriteStream& stream, const MeshBase* mesh) const
|
||||
{
|
||||
if (ModelBase::SaveMesh(stream, mesh))
|
||||
return true;
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
auto skinnedMesh = (const SkinnedMesh*)mesh;
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapesCount = skinnedMesh->BlendShapes.Count();
|
||||
stream.Write(blendShapesCount);
|
||||
for (auto& blendShape : skinnedMesh->BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex)
|
||||
{
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
auto& mesh = modelData.LODs[lodIndex].Meshes[meshIndex];
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapesCount = (uint16)mesh->BlendShapes.Count();
|
||||
stream.Write(blendShapesCount);
|
||||
for (auto& blendShape : mesh->BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void SkinnedModel::ClearSkeletonMapping()
|
||||
{
|
||||
for (auto& e : _skeletonMappingCache)
|
||||
@@ -841,6 +861,26 @@ int32 SkinnedModel::GetLODsCount() const
|
||||
return LODs.Count();
|
||||
}
|
||||
|
||||
const MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex) const
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
void SkinnedModel::GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex) const
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
meshes.Resize(lod.Meshes.Count());
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
meshes[meshIndex] = &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
void SkinnedModel::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
@@ -889,145 +929,11 @@ Asset::LoadResult SkinnedModel::load()
|
||||
if (chunk0 == nullptr || chunk0->IsMissing())
|
||||
return LoadResult::MissingDataChunk;
|
||||
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
||||
ReadStream* stream = &headerStream;
|
||||
|
||||
// Header Version
|
||||
byte version = stream->ReadByte();
|
||||
|
||||
// Min Screen Size
|
||||
stream->ReadFloat(&MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
int32 materialSlotsCount;
|
||||
stream->ReadInt32(&materialSlotsCount);
|
||||
if (materialSlotsCount < 0 || materialSlotsCount > 4096)
|
||||
// Load asset data (anything but mesh contents that use streaming)
|
||||
byte headerVersion;
|
||||
if (LoadHeader(headerStream, headerVersion))
|
||||
return LoadResult::InvalidData;
|
||||
MaterialSlots.Resize(materialSlotsCount, false);
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
// Material
|
||||
Guid materialId;
|
||||
stream->Read(materialId);
|
||||
slot.Material = materialId;
|
||||
|
||||
// Shadows Mode
|
||||
slot.ShadowsMode = static_cast<ShadowsCastingMode>(stream->ReadByte());
|
||||
|
||||
// Name
|
||||
stream->ReadString(&slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
byte lods;
|
||||
stream->ReadByte(&lods);
|
||||
if (lods > MODEL_MAX_LODS)
|
||||
return LoadResult::InvalidData;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
|
||||
// Screen Size
|
||||
stream->ReadFloat(&lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream->ReadUint16(&meshesCount);
|
||||
if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES)
|
||||
return LoadResult::InvalidData;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
|
||||
// Allocate memory
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
|
||||
// For each mesh
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
SkinnedMesh& mesh = lod.Meshes[meshIndex];
|
||||
mesh.Link(this, lodIndex, meshIndex);
|
||||
|
||||
// Material Slot index
|
||||
int32 materialSlotIndex;
|
||||
stream->ReadInt32(&materialSlotIndex);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount)
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream->ReadBoundingBox(&box);
|
||||
BoundingSphere sphere;
|
||||
stream->ReadBoundingSphere(&sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapes;
|
||||
stream->ReadUint16(&blendShapes);
|
||||
mesh.BlendShapes.Resize(blendShapes);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
|
||||
{
|
||||
auto& blendShape = mesh.BlendShapes[blendShapeIndex];
|
||||
stream->ReadString(&blendShape.Name, 13);
|
||||
stream->ReadFloat(&blendShape.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
int32 nodesCount;
|
||||
stream->ReadInt32(&nodesCount);
|
||||
if (nodesCount <= 0)
|
||||
return LoadResult::InvalidData;
|
||||
Skeleton.Nodes.Resize(nodesCount, false);
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
|
||||
{
|
||||
auto& node = Skeleton.Nodes.Get()[nodeIndex];
|
||||
stream->Read(node.ParentIndex);
|
||||
stream->ReadTransform(&node.LocalTransform);
|
||||
stream->ReadString(&node.Name, 71);
|
||||
}
|
||||
|
||||
int32 bonesCount;
|
||||
stream->ReadInt32(&bonesCount);
|
||||
if (bonesCount <= 0)
|
||||
return LoadResult::InvalidData;
|
||||
Skeleton.Bones.Resize(bonesCount, false);
|
||||
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
|
||||
{
|
||||
auto& bone = Skeleton.Bones.Get()[boneIndex];
|
||||
stream->Read(bone.ParentIndex);
|
||||
stream->Read(bone.NodeIndex);
|
||||
stream->ReadTransform(&bone.LocalTransform);
|
||||
stream->Read(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
int32 entriesCount;
|
||||
stream->ReadInt32(&entriesCount);
|
||||
_skeletonRetargets.Resize(entriesCount);
|
||||
for (int32 entryIndex = 0; entryIndex < entriesCount; entryIndex++)
|
||||
{
|
||||
auto& retarget = _skeletonRetargets[entryIndex];
|
||||
stream->Read(retarget.SourceAsset);
|
||||
stream->Read(retarget.SkeletonAsset);
|
||||
stream->Read(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
|
||||
// Request resource streaming
|
||||
StartStreaming(true);
|
||||
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
|
||||
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 30);
|
||||
friend SkinnedMesh;
|
||||
public:
|
||||
// Skeleton mapping descriptor.
|
||||
@@ -331,17 +331,6 @@ public:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<SkeletonBone>& bones, bool autoCalculateOffsetMatrix);
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
/// <remarks>If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread.</remarks>
|
||||
/// <param name="withMeshDataFromGpu">True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location and it's loaded.</param>
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True if cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// <summary>
|
||||
/// Initializes this skinned model to an empty collection of meshes. Ensure to init SkeletonData manually after the call.
|
||||
@@ -350,6 +339,17 @@ private:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool Init(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
// [ModelBase]
|
||||
bool LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) override;
|
||||
bool LoadHeader(ReadStream& stream, byte& headerVersion);
|
||||
#if USE_EDITOR
|
||||
friend class ImportModel;
|
||||
bool SaveHeader(WriteStream& stream) override;
|
||||
static bool SaveHeader(WriteStream& stream, const ModelData& modelData);
|
||||
bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const override;
|
||||
static bool SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex);
|
||||
#endif
|
||||
|
||||
void ClearSkeletonMapping();
|
||||
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
|
||||
|
||||
@@ -384,6 +384,9 @@ public:
|
||||
uint64 GetMemoryUsage() const override;
|
||||
void SetupMaterialSlots(int32 slotsCount) override;
|
||||
int32 GetLODsCount() const override;
|
||||
const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override;
|
||||
MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override;
|
||||
void GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex = 0) const override;
|
||||
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
void InitAsVirtual() override;
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "BinaryAssetUpgrader.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
|
||||
/// <summary>
|
||||
/// Model Asset Upgrader
|
||||
@@ -13,17 +16,145 @@
|
||||
class ModelAssetUpgrader : public BinaryAssetUpgrader
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelAssetUpgrader"/> class.
|
||||
/// </summary>
|
||||
ModelAssetUpgrader()
|
||||
{
|
||||
static const Upgrader upgraders[] =
|
||||
Upgrader upgraders[] =
|
||||
{
|
||||
{},
|
||||
{ 25, 30, Upgrade_25_To_30 }, // [Deprecated in v1.10]
|
||||
};
|
||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
||||
}
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
static bool Upgrade_25_To_30(AssetMigrationContext& context)
|
||||
{
|
||||
// [Deprecated in v1.10]
|
||||
ASSERT(context.Input.SerializedVersion == 25 && context.Output.SerializedVersion == 30);
|
||||
MemoryWriteStream output;
|
||||
|
||||
// Upgrade header
|
||||
byte lodsCount;
|
||||
Array<int32, FixedAllocation<6>> meshesCountPerLod;
|
||||
{
|
||||
if (context.AllocateChunk(0))
|
||||
return true;
|
||||
auto& data = context.Input.Header.Chunks[0]->Data;
|
||||
MemoryReadStream stream(data.Get(), data.Length());
|
||||
|
||||
constexpr byte headerVersion = 2;
|
||||
output.Write(headerVersion);
|
||||
|
||||
float minScreenSize;
|
||||
stream.Read(minScreenSize);
|
||||
output.Write(minScreenSize);
|
||||
|
||||
// Materials
|
||||
int32 materialSlotsCount;
|
||||
stream.Read(materialSlotsCount);
|
||||
output.Write(materialSlotsCount);
|
||||
CHECK_RETURN(materialSlotsCount >= 0 && materialSlotsCount < 4096, true);
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
Guid materialId;
|
||||
stream.Read(materialId);
|
||||
output.Write(materialId);
|
||||
byte shadowsCastingMode;
|
||||
stream.Read(shadowsCastingMode);
|
||||
output.Write(shadowsCastingMode);
|
||||
String name;
|
||||
stream.Read(name, 11);
|
||||
output.Write(name, 11);
|
||||
}
|
||||
|
||||
// LODs
|
||||
stream.Read(lodsCount);
|
||||
output.Write(lodsCount);
|
||||
CHECK_RETURN(lodsCount <= 6, true);
|
||||
meshesCountPerLod.Resize(lodsCount);
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
float screenSize;
|
||||
stream.Read(screenSize);
|
||||
output.Write(screenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream.Read(meshesCount);
|
||||
output.Write(meshesCount);
|
||||
CHECK_RETURN(meshesCount > 0 && meshesCount < 4096, true);
|
||||
meshesCountPerLod[lodIndex] = meshesCount;
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
int32 materialSlotIndex;
|
||||
stream.Read(materialSlotIndex);
|
||||
output.Write(materialSlotIndex);
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
output.Write(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
output.Write(sphere);
|
||||
bool hasLightmapUVs = stream.ReadBool();
|
||||
int8 lightmapUVsIndex = hasLightmapUVs ? 1 : -1;
|
||||
output.Write(lightmapUVsIndex);
|
||||
}
|
||||
}
|
||||
|
||||
context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
// Upgrade meshes data
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
output.SetPosition(0);
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
const FlaxChunk* lodData = context.Input.Header.Chunks[chunkIndex];
|
||||
MemoryReadStream stream(lodData->Get(), lodData->Size());
|
||||
|
||||
constexpr byte meshVersion = 2;
|
||||
output.Write(meshVersion);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCountPerLod[lodIndex]; meshIndex++)
|
||||
{
|
||||
// Descriptor
|
||||
uint32 vertices, triangles;
|
||||
stream.Read(vertices);
|
||||
stream.Read(triangles);
|
||||
output.Write(vertices);
|
||||
output.Write(triangles);
|
||||
auto vb0 = stream.Move<VB0ElementType18>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType18>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
auto ibData = stream.Move(indicesCount * ibStride);
|
||||
byte vbCount = hasColors ? 3 : 2;
|
||||
output.Write(vbCount);
|
||||
output.Write(VB0ElementType18::GetLayout()->GetElements());
|
||||
output.Write(VB1ElementType18::GetLayout()->GetElements());
|
||||
if (hasColors)
|
||||
output.Write(VB2ElementType18::GetLayout()->GetElements());
|
||||
|
||||
// Buffers
|
||||
output.WriteBytes(vb0, vertices * sizeof(VB0ElementType18));
|
||||
output.WriteBytes(vb1, vertices * sizeof(VB1ElementType18));
|
||||
if (hasColors)
|
||||
output.WriteBytes(vb2, vertices * sizeof(VB2ElementType18));
|
||||
output.WriteBytes(ibData, indicesCount * ibStride);
|
||||
}
|
||||
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return true;
|
||||
context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
// Copy SDF data
|
||||
return CopyChunk(context, 15);
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "BinaryAssetUpgrader.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Models/BlendShape.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
|
||||
/// <summary>
|
||||
/// Skinned Model Asset Upgrader
|
||||
@@ -13,17 +17,218 @@
|
||||
class SkinnedModelAssetUpgrader : public BinaryAssetUpgrader
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkinnedModelAssetUpgrader"/> class.
|
||||
/// </summary>
|
||||
SkinnedModelAssetUpgrader()
|
||||
{
|
||||
static const Upgrader upgraders[] =
|
||||
const Upgrader upgraders[] =
|
||||
{
|
||||
{},
|
||||
{ 5, 30, Upgrade_5_To_30 }, // [Deprecated in v1.10]
|
||||
};
|
||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
||||
}
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
static bool Upgrade_5_To_30(AssetMigrationContext& context)
|
||||
{
|
||||
// [Deprecated in v1.10]
|
||||
ASSERT(context.Input.SerializedVersion == 5 && context.Output.SerializedVersion == 30);
|
||||
MemoryWriteStream output;
|
||||
|
||||
// Upgrade header
|
||||
byte lodsCount;
|
||||
Array<int32, FixedAllocation<6>> meshesCountPerLod;
|
||||
{
|
||||
if (context.AllocateChunk(0))
|
||||
return true;
|
||||
auto& data = context.Input.Header.Chunks[0]->Data;
|
||||
MemoryReadStream stream(data.Get(), data.Length());
|
||||
|
||||
constexpr byte headerVersion = 2;
|
||||
byte headerVersionOld;
|
||||
stream.Read(headerVersionOld);
|
||||
CHECK_RETURN(headerVersionOld == 1, true);
|
||||
output.Write(headerVersion);
|
||||
|
||||
float minScreenSize;
|
||||
stream.Read(minScreenSize);
|
||||
output.Write(minScreenSize);
|
||||
|
||||
// Materials
|
||||
int32 materialSlotsCount;
|
||||
stream.Read(materialSlotsCount);
|
||||
output.Write(materialSlotsCount);
|
||||
CHECK_RETURN(materialSlotsCount >= 0 && materialSlotsCount < 4096, true);
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
Guid materialId;
|
||||
stream.Read(materialId);
|
||||
output.Write(materialId);
|
||||
byte shadowsCastingMode;
|
||||
stream.Read(shadowsCastingMode);
|
||||
output.Write(shadowsCastingMode);
|
||||
String name;
|
||||
stream.Read(name, 11);
|
||||
output.Write(name, 11);
|
||||
}
|
||||
|
||||
// LODs
|
||||
stream.Read(lodsCount);
|
||||
output.Write(lodsCount);
|
||||
CHECK_RETURN(materialSlotsCount <= 6, true);
|
||||
meshesCountPerLod.Resize(lodsCount);
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
float screenSize;
|
||||
stream.Read(screenSize);
|
||||
output.Write(screenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream.Read(meshesCount);
|
||||
output.Write(meshesCount);
|
||||
CHECK_RETURN(meshesCount > 0 && meshesCount < 4096, true);
|
||||
meshesCountPerLod[lodIndex] = meshesCount;
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
int32 materialSlotIndex;
|
||||
stream.Read(materialSlotIndex);
|
||||
output.Write(materialSlotIndex);
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
output.Write(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
output.Write(sphere);
|
||||
uint16 blendShapesCount;
|
||||
stream.Read(blendShapesCount);
|
||||
output.Write(blendShapesCount);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
|
||||
{
|
||||
String name;
|
||||
stream.Read(name, 13);
|
||||
output.Write(name, 13);
|
||||
float weight;
|
||||
stream.Read(weight);
|
||||
output.Write(weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
int32 nodesCount;
|
||||
stream.Read(nodesCount);
|
||||
output.Write(nodesCount);
|
||||
if (nodesCount < 0)
|
||||
return true;
|
||||
for (int32 i = 0 ; i < nodesCount; i++)
|
||||
{
|
||||
int32 parentIndex;
|
||||
Transform localTransform;
|
||||
String name;
|
||||
stream.Read(parentIndex);
|
||||
output.Write(parentIndex);
|
||||
stream.Read(localTransform);
|
||||
output.Write(localTransform);
|
||||
stream.Read(name, 71);
|
||||
output.Write(name, 71);
|
||||
}
|
||||
|
||||
int32 bonesCount;
|
||||
stream.Read(bonesCount);
|
||||
output.Write(bonesCount);
|
||||
if (bonesCount < 0)
|
||||
return true;
|
||||
for (int32 i = 0 ; i < bonesCount; i++)
|
||||
{
|
||||
int32 parentIndex, nodeIndex;
|
||||
Transform localTransform;
|
||||
String name;
|
||||
Matrix offsetMatrix;
|
||||
stream.Read(parentIndex);
|
||||
output.Write(parentIndex);
|
||||
stream.Read(nodeIndex);
|
||||
output.Write(nodeIndex);
|
||||
stream.Read(localTransform);
|
||||
output.Write(localTransform);
|
||||
stream.Read(offsetMatrix);
|
||||
output.Write(offsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
int32 entriesCount;
|
||||
stream.Read(entriesCount);
|
||||
output.Write(entriesCount);
|
||||
for (int32 i = 0 ; i < entriesCount; i++)
|
||||
{
|
||||
Guid sourceAsset, skeletonAsset;
|
||||
Dictionary<String, String> nodesMapping;
|
||||
stream.Read(sourceAsset);
|
||||
output.Write(sourceAsset);
|
||||
stream.Read(skeletonAsset);
|
||||
output.Write(skeletonAsset);
|
||||
stream.Read(nodesMapping);
|
||||
output.Write(nodesMapping);
|
||||
}
|
||||
}
|
||||
|
||||
context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
// Upgrade meshes data
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
output.SetPosition(0);
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
const FlaxChunk* lodData = context.Input.Header.Chunks[chunkIndex];
|
||||
MemoryReadStream stream(lodData->Get(), lodData->Size());
|
||||
|
||||
constexpr byte meshVersion = 2;
|
||||
byte headerVersionOld;
|
||||
stream.Read(headerVersionOld);
|
||||
CHECK_RETURN(headerVersionOld == 1, true);
|
||||
output.Write(meshVersion);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCountPerLod[lodIndex]; meshIndex++)
|
||||
{
|
||||
// Descriptor
|
||||
uint32 vertices, triangles;
|
||||
stream.Read(vertices);
|
||||
stream.Read(triangles);
|
||||
output.Write(vertices);
|
||||
output.Write(triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.Read(blendShapesCount);
|
||||
Array<BlendShape> blendShapes;
|
||||
blendShapes.Resize(blendShapesCount);
|
||||
for (auto& blendShape : blendShapes)
|
||||
blendShape.Load(stream, meshVersion);
|
||||
auto vb0 = stream.Move<VB0SkinnedElementType2>(vertices);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
auto ibData = stream.Move(indicesCount * ibStride);
|
||||
output.Write((byte)1);
|
||||
output.Write(VB0SkinnedElementType2::GetLayout()->GetElements());
|
||||
|
||||
// Buffers
|
||||
output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType2));
|
||||
output.WriteBytes(ibData, indicesCount * ibStride);
|
||||
|
||||
// Blend Shapes
|
||||
output.Write(blendShapesCount);
|
||||
for (auto& blendShape : blendShapes)
|
||||
blendShape.Save(output);
|
||||
}
|
||||
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return true;
|
||||
context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user