// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #pragma once #if USE_EDITOR #include "BinaryAssetUpgrader.h" #include "Engine/Platform/Platform.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Graphics/Models/Types.h" #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Transform.h" /// /// Skinned Model Asset Upgrader /// /// class SkinnedModelAssetUpgrader : public BinaryAssetUpgrader { public: /// /// Initializes a new instance of the class. /// SkinnedModelAssetUpgrader() { static const Upgrader upgraders[] = { { 1, 2, &Upgrade_1_To_2 }, // [Deprecated on 28.04.2023, expires on 01.01.2024] { 2, 3, &Upgrade_2_To_3 }, // [Deprecated on 28.04.2023, expires on 01.01.2024] { 3, 4, &Upgrade_3_To_4 }, // [Deprecated on 28.04.2023, expires on 01.01.2024] { 4, 5, &Upgrade_4_To_5 }, // [Deprecated on 28.04.2023, expires on 28.04.2026] }; setup(upgraders, ARRAY_COUNT(upgraders)); } private: static bool Upgrade_1_To_2(AssetMigrationContext& context) { ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 2); // Copy header chunk (header format is the same) if (CopyChunk(context, 0)) return true; // Get mesh data chunk const auto srcData = context.Input.Header.Chunks[1]; if (srcData == nullptr || srcData->IsMissing()) { LOG(Warning, "Missing model data chunk"); return true; } // Upgrade meshes data MemoryReadStream stream(srcData->Get(), srcData->Size()); MemoryWriteStream output(srcData->Size()); do { uint32 vertices; stream.ReadUint32(&vertices); uint32 triangles; stream.ReadUint32(&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; const auto vb0 = stream.Move(vertices); const auto ib = stream.Move(indicesCount * ibStride); // Write back output.WriteUint32(vertices); output.WriteUint32(triangles); for (uint32 i = 0; i < vertices; i++) { const VB0SkinnedElementType1 oldVertex = vb0[i]; VB0SkinnedElementType2 newVertex; newVertex.Position = oldVertex.Position; newVertex.TexCoord = oldVertex.TexCoord; newVertex.Normal = oldVertex.Normal; newVertex.Tangent = oldVertex.Tangent; newVertex.BlendIndices = oldVertex.BlendIndices; Float4 blendWeights(oldVertex.BlendWeights.R / 255.0f, oldVertex.BlendWeights.G / 255.0f, oldVertex.BlendWeights.B / 255.0f, oldVertex.BlendWeights.A / 255.0f); const float sum = blendWeights.SumValues(); const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; blendWeights *= invSum; newVertex.BlendWeights = Half4(blendWeights); output.WriteBytes(&newVertex, sizeof(newVertex)); } output.WriteBytes(ib, indicesCount * ibStride); } while (stream.CanRead()); // Save new data if (context.AllocateChunk(1)) return true; context.Output.Header.Chunks[1]->Data.Copy(output.GetHandle(), output.GetPosition()); return false; } static bool Upgrade_2_To_3(AssetMigrationContext& context) { ASSERT(context.Input.SerializedVersion == 2 && context.Output.SerializedVersion == 3); // Copy meshes data chunk (format is the same) if (CopyChunk(context, 1)) return true; // Rewrite header chunk (added LOD count) const auto srcData = context.Input.Header.Chunks[0]; if (srcData == nullptr || srcData->IsMissing()) { LOG(Warning, "Missing model header chunk"); return true; } MemoryReadStream stream(srcData->Get(), srcData->Size()); MemoryWriteStream output(srcData->Size()); { // Min Screen Size float minScreenSize; stream.ReadFloat(&minScreenSize); output.WriteFloat(minScreenSize); // Amount of material slots int32 materialSlotsCount; stream.ReadInt32(&materialSlotsCount); output.WriteInt32(materialSlotsCount); // For each material slot for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) { // Material Guid materialId; stream.Read(materialId); output.Write(materialId); // Shadows Mode output.WriteByte(stream.ReadByte()); // Name String name; stream.ReadString(&name, 11); output.WriteString(name, 11); } // Amount of LODs output.WriteByte(1); // Screen Size output.WriteFloat(1.0f); // Amount of meshes uint16 meshesCount; stream.ReadUint16(&meshesCount); output.WriteUint16(meshesCount); // For each mesh for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { // Material Slot index int32 materialSlotIndex; stream.ReadInt32(&materialSlotIndex); output.WriteInt32(materialSlotIndex); // Box BoundingBox box; stream.Read(box); output.Write(box); // Sphere BoundingSphere sphere; stream.Read(sphere); output.Write(sphere); } // Skeleton { int32 nodesCount; stream.ReadInt32(&nodesCount); output.WriteInt32(nodesCount); // For each node for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) { int32 parentIndex; stream.ReadInt32(&parentIndex); output.WriteInt32(parentIndex); Transform localTransform; stream.Read(localTransform); output.Write(localTransform); String name; stream.ReadString(&name, 71); output.WriteString(name, 71); } int32 bonesCount; stream.ReadInt32(&bonesCount); output.WriteInt32(bonesCount); // For each bone for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) { int32 parentIndex; stream.ReadInt32(&parentIndex); output.WriteInt32(parentIndex); int32 nodeIndex; stream.ReadInt32(&nodeIndex); output.WriteInt32(nodeIndex); Transform localTransform; stream.Read(localTransform); output.Write(localTransform); Matrix offsetMatrix; stream.ReadBytes(&offsetMatrix, sizeof(Matrix)); output.WriteBytes(&offsetMatrix, sizeof(Matrix)); } } } // Save new header data if (context.AllocateChunk(0)) return true; context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); return false; } static bool Upgrade_3_To_4(AssetMigrationContext& context) { ASSERT(context.Input.SerializedVersion == 3 && context.Output.SerializedVersion == 4); // Rewrite header chunk (added blend shapes count) byte lodCount; Array meshesCounts; { const auto srcData = context.Input.Header.Chunks[0]; if (srcData == nullptr || srcData->IsMissing()) { LOG(Warning, "Missing model header chunk"); return true; } MemoryReadStream stream(srcData->Get(), srcData->Size()); MemoryWriteStream output(srcData->Size()); // Min Screen Size float minScreenSize; stream.ReadFloat(&minScreenSize); output.WriteFloat(minScreenSize); // Amount of material slots int32 materialSlotsCount; stream.ReadInt32(&materialSlotsCount); output.WriteInt32(materialSlotsCount); // For each material slot for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) { // Material Guid materialId; stream.Read(materialId); output.Write(materialId); // Shadows Mode output.WriteByte(stream.ReadByte()); // Name String name; stream.ReadString(&name, 11); output.WriteString(name, 11); } // Amount of LODs stream.ReadByte(&lodCount); output.WriteByte(lodCount); meshesCounts.Resize(lodCount); // For each LOD for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { // Screen Size float screenSize; stream.ReadFloat(&screenSize); output.WriteFloat(screenSize); // Amount of meshes uint16 meshesCount; stream.ReadUint16(&meshesCount); output.WriteUint16(meshesCount); meshesCounts[lodIndex] = meshesCount; // For each mesh for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { // Material Slot index int32 materialSlotIndex; stream.ReadInt32(&materialSlotIndex); output.WriteInt32(materialSlotIndex); // Box BoundingBox box; stream.Read(box); output.Write(box); // Sphere BoundingSphere sphere; stream.Read(sphere); output.Write(sphere); // Blend Shapes output.WriteUint16(0); } } // Skeleton { int32 nodesCount; stream.ReadInt32(&nodesCount); output.WriteInt32(nodesCount); // For each node for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) { int32 parentIndex; stream.ReadInt32(&parentIndex); output.WriteInt32(parentIndex); Transform localTransform; stream.Read(localTransform); output.Write(localTransform); String name; stream.ReadString(&name, 71); output.WriteString(name, 71); } int32 bonesCount; stream.ReadInt32(&bonesCount); output.WriteInt32(bonesCount); // For each bone for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) { int32 parentIndex; stream.ReadInt32(&parentIndex); output.WriteInt32(parentIndex); int32 nodeIndex; stream.ReadInt32(&nodeIndex); output.WriteInt32(nodeIndex); Transform localTransform; stream.Read(localTransform); output.Write(localTransform); Matrix offsetMatrix; stream.ReadBytes(&offsetMatrix, sizeof(Matrix)); output.WriteBytes(&offsetMatrix, sizeof(Matrix)); } } // Save new data if (stream.GetPosition() != stream.GetLength()) { LOG(Error, "Invalid position after upgrading skinned model header data."); return true; } if (context.AllocateChunk(0)) return true; context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); } // Rewrite meshes data chunks (blend shapes added) for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { const int32 chunkIndex = lodIndex + 1; const auto srcData = context.Input.Header.Chunks[chunkIndex]; if (srcData == nullptr || srcData->IsMissing()) { LOG(Warning, "Missing skinned model LOD meshes data chunk"); return true; } MemoryReadStream stream(srcData->Get(), srcData->Size()); MemoryWriteStream output(srcData->Size()); for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++) { uint32 vertices; stream.ReadUint32(&vertices); output.WriteUint32(vertices); uint32 triangles; stream.ReadUint32(&triangles); output.WriteUint32(triangles); uint16 blendShapesCount = 0; output.WriteUint16(blendShapesCount); const uint32 indicesCount = triangles * 3; const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); if (vertices == 0 || triangles == 0) return true; const auto vb0 = stream.Move(vertices); output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType)); const auto ib = stream.Move(indicesCount * ibStride); output.WriteBytes(ib, indicesCount * ibStride); } // Save new data if (stream.GetPosition() != stream.GetLength()) { LOG(Error, "Invalid position after upgrading skinned model LOD meshes data."); return true; } if (context.AllocateChunk(chunkIndex)) return true; context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition()); } return false; } static bool Upgrade_4_To_5(AssetMigrationContext& context) { ASSERT(context.Input.SerializedVersion == 4 && context.Output.SerializedVersion == 5); // Changes: // - added version number to header (for easier changes in future) // - added version number to mesh data (for easier changes in future) // - added skeleton retarget setups to header // Rewrite header chunk (added header version and retarget entries) byte lodCount; Array meshesCounts; { const auto srcData = context.Input.Header.Chunks[0]; if (srcData == nullptr || srcData->IsMissing()) { LOG(Warning, "Missing model header chunk"); return true; } MemoryReadStream stream(srcData->Get(), srcData->Size()); MemoryWriteStream output(srcData->Size()); // Header Version output.WriteByte(1); // Min Screen Size float minScreenSize; stream.ReadFloat(&minScreenSize); output.WriteFloat(minScreenSize); // Amount of material slots int32 materialSlotsCount; stream.ReadInt32(&materialSlotsCount); output.WriteInt32(materialSlotsCount); // For each material slot for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) { // Material Guid materialId; stream.Read(materialId); output.Write(materialId); // Shadows Mode output.WriteByte(stream.ReadByte()); // Name String name; stream.ReadString(&name, 11); output.WriteString(name, 11); } // Amount of LODs stream.ReadByte(&lodCount); output.WriteByte(lodCount); meshesCounts.Resize(lodCount); // For each LOD for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { // Screen Size float screenSize; stream.ReadFloat(&screenSize); output.WriteFloat(screenSize); // Amount of meshes uint16 meshesCount; stream.ReadUint16(&meshesCount); output.WriteUint16(meshesCount); meshesCounts[lodIndex] = meshesCount; // For each mesh for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { // Material Slot index int32 materialSlotIndex; stream.ReadInt32(&materialSlotIndex); output.WriteInt32(materialSlotIndex); // Box BoundingBox box; stream.Read(box); output.Write(box); // Sphere BoundingSphere sphere; stream.Read(sphere); output.Write(sphere); // Blend Shapes uint16 blendShapes; stream.ReadUint16(&blendShapes); output.WriteUint16(blendShapes); for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++) { String blendShapeName; stream.ReadString(&blendShapeName, 13); output.WriteString(blendShapeName, 13); float blendShapeWeight; stream.ReadFloat(&blendShapeWeight); output.WriteFloat(blendShapeWeight); } } } // Skeleton { int32 nodesCount; stream.ReadInt32(&nodesCount); output.WriteInt32(nodesCount); // For each node for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) { int32 parentIndex; stream.ReadInt32(&parentIndex); output.WriteInt32(parentIndex); Transform localTransform; stream.Read(localTransform); output.Write(localTransform); String name; stream.ReadString(&name, 71); output.WriteString(name, 71); } int32 bonesCount; stream.ReadInt32(&bonesCount); output.WriteInt32(bonesCount); // For each bone for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) { int32 parentIndex; stream.ReadInt32(&parentIndex); output.WriteInt32(parentIndex); int32 nodeIndex; stream.ReadInt32(&nodeIndex); output.WriteInt32(nodeIndex); Transform localTransform; stream.Read(localTransform); output.Write(localTransform); Matrix offsetMatrix; stream.ReadBytes(&offsetMatrix, sizeof(Matrix)); output.WriteBytes(&offsetMatrix, sizeof(Matrix)); } } // Retargeting { output.WriteInt32(0); } // Save new data if (stream.GetPosition() != stream.GetLength()) { LOG(Error, "Invalid position after upgrading skinned model header data."); return true; } if (context.AllocateChunk(0)) return true; context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); } // Rewrite meshes data chunks for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { const int32 chunkIndex = lodIndex + 1; const auto srcData = context.Input.Header.Chunks[chunkIndex]; if (srcData == nullptr || srcData->IsMissing()) { LOG(Warning, "Missing skinned model LOD meshes data chunk"); return true; } MemoryReadStream stream(srcData->Get(), srcData->Size()); MemoryWriteStream output(srcData->Size()); // Mesh Data Version output.WriteByte(1); for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++) { uint32 vertices; stream.ReadUint32(&vertices); output.WriteUint32(vertices); uint32 triangles; stream.ReadUint32(&triangles); output.WriteUint32(triangles); uint16 blendShapesCount; stream.ReadUint16(&blendShapesCount); output.WriteUint16(blendShapesCount); for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++) { output.WriteBool(stream.ReadBool()); uint32 minVertexIndex, maxVertexIndex; stream.ReadUint32(&minVertexIndex); output.WriteUint32(minVertexIndex); stream.ReadUint32(&maxVertexIndex); output.WriteUint32(maxVertexIndex); uint32 blendShapeVertices; stream.ReadUint32(&blendShapeVertices); output.WriteUint32(blendShapeVertices); const uint32 blendShapeDataSize = blendShapeVertices * sizeof(BlendShapeVertex); const auto blendShapeData = stream.Move(blendShapeDataSize); output.WriteBytes(blendShapeData, blendShapeDataSize); } const uint32 indicesCount = triangles * 3; const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); if (vertices == 0 || triangles == 0) return true; const auto vb0 = stream.Move(vertices); output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType)); const auto ib = stream.Move(indicesCount * ibStride); output.WriteBytes(ib, indicesCount * ibStride); } // Save new data if (stream.GetPosition() != stream.GetLength()) { LOG(Error, "Invalid position after upgrading skinned model LOD meshes data."); return true; } if (context.AllocateChunk(chunkIndex)) return true; context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition()); } return false; } }; #endif