#include #include #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(); } bool ReadArtifactFileData(const Containers::String& path, ResourceType resourceType, Containers::Array& 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)); if (size == 0) { return true; } if (!input.read(reinterpret_cast(outData.Data()), size)) { outData.Clear(); return false; } return true; } void WriteString(std::ostream& 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); } } bool ReadString(const Containers::Array& 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(data.Data() + offset), length); offset += length; return true; } template bool ReadValue(const Containers::Array& 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 ToByteArray(const std::string& text) { Containers::Array 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(model.GetNodes().Size()); header.meshBindingCount = static_cast(model.GetMeshBindings().Size()); header.materialBindingCount = static_cast(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&& 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 SerializeModelArtifactPayload(const Model& model, Containers::Array& outPayload, Containers::String* outErrorMessage) { (void)outErrorMessage; std::ostringstream output(std::ios::binary | std::ios::out); ModelArtifactFileHeader fileHeader; output.write(reinterpret_cast(&fileHeader), sizeof(fileHeader)); const ModelArtifactHeader header = BuildModelArtifactHeader(model); 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) { outPayload.Clear(); return false; } outPayload = ToByteArray(output.str()); return true; } namespace { LoadResult ParseModelArtifactPayload(const Containers::String& path, const Containers::Array& 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 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 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 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 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(payload.Data()), static_cast(payload.Size())); } if (!output && outErrorMessage != nullptr) { *outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath; } return static_cast(output); } LoadResult LoadModelArtifact(const Containers::String& path) { Containers::Array 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