Files
XCEngine/engine/src/Resources/Model/ModelArtifactIO.cpp

372 lines
13 KiB
C++

#include <XCEngine/Resources/Model/ModelArtifactIO.h>
#include <XCEngine/Core/Asset/ArtifactContainer.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/Model/Model.h>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
namespace XCEngine {
namespace Resources {
namespace {
Containers::String GetResourceNameFromPath(const Containers::String& path) {
const std::filesystem::path filePath(path.CStr());
const std::string fileName = filePath.filename().string();
if (!fileName.empty()) {
return Containers::String(fileName.c_str());
}
return path;
}
std::filesystem::path ResolveArtifactPath(const Containers::String& path) {
std::filesystem::path resolvedPath(path.CStr());
if (!resolvedPath.is_absolute() && !std::filesystem::exists(resolvedPath)) {
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
if (!resourceRoot.Empty()) {
resolvedPath = std::filesystem::path(resourceRoot.CStr()) / resolvedPath;
}
}
return resolvedPath.lexically_normal();
}
bool ReadArtifactFileData(const Containers::String& path,
ResourceType resourceType,
Containers::Array<Core::uint8>& outData) {
outData.Clear();
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
Containers::String containerError;
if (ReadArtifactContainerPayloadByPath(
Containers::String(resolvedPath.generic_string().c_str()),
resourceType,
outData,
&containerError)) {
return true;
}
std::ifstream input(resolvedPath, std::ios::binary | std::ios::ate);
if (!input.is_open()) {
return false;
}
const std::streamsize size = input.tellg();
if (size < 0) {
return false;
}
input.seekg(0, std::ios::beg);
outData.Resize(static_cast<size_t>(size));
if (size == 0) {
return true;
}
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
outData.Clear();
return false;
}
return true;
}
void WriteString(std::ostream& stream, const Containers::String& value) {
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
if (length > 0) {
stream.write(value.CStr(), length);
}
}
bool ReadString(const Containers::Array<Core::uint8>& data,
size_t& offset,
Containers::String& outValue) {
Core::uint32 length = 0;
if (offset + sizeof(length) > data.Size()) {
return false;
}
std::memcpy(&length, data.Data() + offset, sizeof(length));
offset += sizeof(length);
if (length == 0) {
outValue.Clear();
return true;
}
if (offset + length > data.Size()) {
outValue.Clear();
return false;
}
outValue = Containers::String(reinterpret_cast<const char*>(data.Data() + offset), length);
offset += length;
return true;
}
template<typename T>
bool ReadValue(const Containers::Array<Core::uint8>& data, size_t& offset, T& outValue) {
if (offset + sizeof(T) > data.Size()) {
return false;
}
std::memcpy(&outValue, data.Data() + offset, sizeof(T));
offset += sizeof(T);
return true;
}
Containers::Array<Core::uint8> ToByteArray(const std::string& text) {
Containers::Array<Core::uint8> bytes;
if (text.empty()) {
return bytes;
}
bytes.ResizeUninitialized(text.size());
std::memcpy(bytes.Data(), text.data(), text.size());
return bytes;
}
ModelArtifactHeader BuildModelArtifactHeader(const Model& model) {
ModelArtifactHeader header;
header.nodeCount = static_cast<Core::uint32>(model.GetNodes().Size());
header.meshBindingCount = static_cast<Core::uint32>(model.GetMeshBindings().Size());
header.materialBindingCount = static_cast<Core::uint32>(model.GetMaterialBindings().Size());
header.rootNodeIndex = model.GetRootNodeIndex();
return header;
}
bool ValidateModelArtifactHeader(const ModelArtifactHeader& header) {
return header.rootNodeIndex == kInvalidModelNodeIndex ||
header.rootNodeIndex < header.nodeCount;
}
LoadResult CreateOwnedModelResource(const Containers::String& path,
const ModelArtifactHeader& header,
Containers::Array<ModelNode>&& nodes,
Containers::Array<ModelMeshBinding>&& meshBindings,
Containers::Array<ModelMaterialBinding>&& materialBindings) {
auto* model = new Model();
IResource::ConstructParams params;
params.name = GetResourceNameFromPath(path);
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
model->Initialize(params);
if (header.rootNodeIndex != kInvalidModelNodeIndex) {
model->SetRootNodeIndex(header.rootNodeIndex);
}
for (const ModelNode& node : nodes) {
model->AddNode(node);
}
for (const ModelMeshBinding& binding : meshBindings) {
model->AddMeshBinding(binding);
}
for (const ModelMaterialBinding& binding : materialBindings) {
model->AddMaterialBinding(binding);
}
return LoadResult(model);
}
} // namespace
bool SerializeModelArtifactPayload(const Model& model,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage) {
(void)outErrorMessage;
std::ostringstream output(std::ios::binary | std::ios::out);
ModelArtifactFileHeader fileHeader;
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
const ModelArtifactHeader header = BuildModelArtifactHeader(model);
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
for (const ModelNode& node : model.GetNodes()) {
WriteString(output, node.name);
ModelNodeArtifactHeader nodeHeader;
nodeHeader.parentIndex = node.parentIndex;
nodeHeader.meshBindingStart = node.meshBindingStart;
nodeHeader.meshBindingCount = node.meshBindingCount;
nodeHeader.localPosition = node.localPosition;
nodeHeader.localRotation = node.localRotation;
nodeHeader.localScale = node.localScale;
output.write(reinterpret_cast<const char*>(&nodeHeader), sizeof(nodeHeader));
}
for (const ModelMeshBinding& binding : model.GetMeshBindings()) {
ModelMeshBindingArtifact bindingArtifact;
bindingArtifact.meshLocalID = binding.meshLocalID;
bindingArtifact.materialBindingStart = binding.materialBindingStart;
bindingArtifact.materialBindingCount = binding.materialBindingCount;
output.write(reinterpret_cast<const char*>(&bindingArtifact), sizeof(bindingArtifact));
}
for (const ModelMaterialBinding& binding : model.GetMaterialBindings()) {
ModelMaterialBindingArtifact bindingArtifact;
bindingArtifact.slotIndex = binding.slotIndex;
bindingArtifact.materialLocalID = binding.materialLocalID;
output.write(reinterpret_cast<const char*>(&bindingArtifact), sizeof(bindingArtifact));
}
if (!output) {
outPayload.Clear();
return false;
}
outPayload = ToByteArray(output.str());
return true;
}
namespace {
LoadResult ParseModelArtifactPayload(const Containers::String& path,
const Containers::Array<Core::uint8>& data) {
size_t offset = 0;
ModelArtifactFileHeader fileHeader;
if (!ReadValue(data, offset, fileHeader)) {
return LoadResult(Containers::String("Failed to parse model artifact file header: ") + path);
}
const bool validFileHeader =
std::memcmp(fileHeader.magic, "XCMOD01", 7) == 0 &&
fileHeader.schemaVersion == kModelArtifactSchemaVersion;
if (!validFileHeader) {
return LoadResult(Containers::String("Invalid model artifact file header: ") + path);
}
ModelArtifactHeader header;
if (!ReadValue(data, offset, header)) {
return LoadResult(Containers::String("Failed to parse model artifact header: ") + path);
}
if (!ValidateModelArtifactHeader(header)) {
return LoadResult(Containers::String("Invalid model artifact root node index: ") + path);
}
Containers::Array<ModelNode> nodes;
nodes.Reserve(header.nodeCount);
for (Core::uint32 index = 0; index < header.nodeCount; ++index) {
ModelNode node;
if (!ReadString(data, offset, node.name)) {
return LoadResult(Containers::String("Failed to read model node artifact name: ") + path);
}
ModelNodeArtifactHeader nodeHeader;
if (!ReadValue(data, offset, nodeHeader)) {
return LoadResult(Containers::String("Failed to read model node artifact: ") + path);
}
node.parentIndex = nodeHeader.parentIndex;
node.meshBindingStart = nodeHeader.meshBindingStart;
node.meshBindingCount = nodeHeader.meshBindingCount;
node.localPosition = nodeHeader.localPosition;
node.localRotation = nodeHeader.localRotation;
node.localScale = nodeHeader.localScale;
nodes.PushBack(std::move(node));
}
Containers::Array<ModelMeshBinding> meshBindings;
meshBindings.Reserve(header.meshBindingCount);
for (Core::uint32 index = 0; index < header.meshBindingCount; ++index) {
ModelMeshBindingArtifact bindingArtifact;
if (!ReadValue(data, offset, bindingArtifact)) {
return LoadResult(Containers::String("Failed to read model mesh binding artifact: ") + path);
}
ModelMeshBinding binding;
binding.meshLocalID = bindingArtifact.meshLocalID;
binding.materialBindingStart = bindingArtifact.materialBindingStart;
binding.materialBindingCount = bindingArtifact.materialBindingCount;
meshBindings.PushBack(binding);
}
Containers::Array<ModelMaterialBinding> materialBindings;
materialBindings.Reserve(header.materialBindingCount);
for (Core::uint32 index = 0; index < header.materialBindingCount; ++index) {
ModelMaterialBindingArtifact bindingArtifact;
if (!ReadValue(data, offset, bindingArtifact)) {
return LoadResult(Containers::String("Failed to read model material binding artifact: ") + path);
}
ModelMaterialBinding binding;
binding.slotIndex = bindingArtifact.slotIndex;
binding.materialLocalID = bindingArtifact.materialLocalID;
materialBindings.PushBack(binding);
}
return CreateOwnedModelResource(
path,
header,
std::move(nodes),
std::move(meshBindings),
std::move(materialBindings));
}
} // namespace
bool WriteModelArtifactFile(const Containers::String& artifactPath,
const Model& model,
Containers::String* outErrorMessage) {
const std::filesystem::path resolvedPath = ResolveArtifactPath(artifactPath);
std::error_code ec;
const std::filesystem::path parentPath = resolvedPath.parent_path();
if (!parentPath.empty()) {
std::filesystem::create_directories(parentPath, ec);
if (ec) {
if (outErrorMessage != nullptr) {
*outErrorMessage =
Containers::String("Failed to create model artifact directory: ") +
Containers::String(parentPath.generic_string().c_str());
}
return false;
}
}
Containers::Array<Core::uint8> payload;
if (!SerializeModelArtifactPayload(model, payload, outErrorMessage)) {
return false;
}
std::ofstream output(resolvedPath, std::ios::binary | std::ios::trunc);
if (!output.is_open()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = Containers::String("Failed to open model artifact for write: ") + artifactPath;
}
return false;
}
if (!payload.Empty()) {
output.write(reinterpret_cast<const char*>(payload.Data()), static_cast<std::streamsize>(payload.Size()));
}
if (!output && outErrorMessage != nullptr) {
*outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath;
}
return static_cast<bool>(output);
}
LoadResult LoadModelArtifact(const Containers::String& path) {
Containers::Array<Core::uint8> data;
if (!ReadArtifactFileData(path, ResourceType::Model, data)) {
return LoadResult(Containers::String("Failed to read model artifact: ") + path);
}
return ParseModelArtifactPayload(path, data);
}
} // namespace Resources
} // namespace XCEngine