262 lines
9.7 KiB
C++
262 lines
9.7 KiB
C++
#include <XCEngine/Resources/Model/ModelArtifactIO.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 <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();
|
|
}
|
|
|
|
void WriteString(std::ofstream& 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);
|
|
}
|
|
}
|
|
|
|
Containers::String ReadString(std::ifstream& stream) {
|
|
Core::uint32 length = 0;
|
|
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
|
if (!stream || length == 0) {
|
|
return Containers::String();
|
|
}
|
|
|
|
std::string buffer(length, '\0');
|
|
stream.read(buffer.data(), length);
|
|
if (!stream) {
|
|
return Containers::String();
|
|
}
|
|
|
|
return Containers::String(buffer.c_str());
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
ModelArtifactFileHeader fileHeader;
|
|
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
|
|
|
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();
|
|
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 && outErrorMessage != nullptr) {
|
|
*outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath;
|
|
}
|
|
|
|
return static_cast<bool>(output);
|
|
}
|
|
|
|
LoadResult LoadModelArtifact(const Containers::String& path) {
|
|
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
|
|
|
std::ifstream input(resolvedPath, std::ios::binary);
|
|
if (!input.is_open()) {
|
|
return LoadResult(Containers::String("Failed to read model artifact: ") + path);
|
|
}
|
|
|
|
ModelArtifactFileHeader fileHeader;
|
|
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
|
|
if (!input) {
|
|
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;
|
|
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
|
if (!input) {
|
|
return LoadResult(Containers::String("Failed to parse model artifact header: ") + path);
|
|
}
|
|
|
|
if (header.rootNodeIndex != kInvalidModelNodeIndex &&
|
|
header.rootNodeIndex >= header.nodeCount) {
|
|
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;
|
|
node.name = ReadString(input);
|
|
|
|
ModelNodeArtifactHeader nodeHeader;
|
|
input.read(reinterpret_cast<char*>(&nodeHeader), sizeof(nodeHeader));
|
|
if (!input) {
|
|
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;
|
|
input.read(reinterpret_cast<char*>(&bindingArtifact), sizeof(bindingArtifact));
|
|
if (!input) {
|
|
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;
|
|
input.read(reinterpret_cast<char*>(&bindingArtifact), sizeof(bindingArtifact));
|
|
if (!input) {
|
|
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 Resources
|
|
} // namespace XCEngine
|