#include #include #include #include #include #include #include #include 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(value.Length()); stream.write(reinterpret_cast(&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(&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&& nodes, Containers::Array&& meshBindings, Containers::Array&& 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(&fileHeader), sizeof(fileHeader)); ModelArtifactHeader header; header.nodeCount = static_cast(model.GetNodes().Size()); header.meshBindingCount = static_cast(model.GetMeshBindings().Size()); header.materialBindingCount = static_cast(model.GetMaterialBindings().Size()); header.rootNodeIndex = model.GetRootNodeIndex(); output.write(reinterpret_cast(&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(&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(&bindingArtifact), sizeof(bindingArtifact)); } for (const ModelMaterialBinding& binding : model.GetMaterialBindings()) { ModelMaterialBindingArtifact bindingArtifact; bindingArtifact.slotIndex = binding.slotIndex; bindingArtifact.materialLocalID = binding.materialLocalID; output.write(reinterpret_cast(&bindingArtifact), sizeof(bindingArtifact)); } if (!output && outErrorMessage != nullptr) { *outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath; } return static_cast(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(&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(&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 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(&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 meshBindings; meshBindings.Reserve(header.meshBindingCount); for (Core::uint32 index = 0; index < header.meshBindingCount; ++index) { ModelMeshBindingArtifact bindingArtifact; input.read(reinterpret_cast(&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 materialBindings; materialBindings.Reserve(header.materialBindingCount); for (Core::uint32 index = 0; index < header.materialBindingCount; ++index) { ModelMaterialBindingArtifact bindingArtifact; input.read(reinterpret_cast(&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