Add Nahida model import and preview pipeline

This commit is contained in:
2026-04-11 20:16:49 +08:00
parent 8f71f99de4
commit 030230eb1f
87 changed files with 7245 additions and 117 deletions

View File

@@ -325,6 +325,7 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetGUID.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetRef.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ArtifactFormats.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ArtifactContainer.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetDatabase.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetImportService.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ProjectAssetIndex.h
@@ -336,6 +337,7 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AsyncLoader.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceDependencyGraph.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetGUID.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/ArtifactContainer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetDatabase.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetImportService.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/ProjectAssetIndex.cpp
@@ -543,10 +545,12 @@ add_library(XCEngine STATIC
# Scene
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/ModelSceneInstantiation.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/SceneRuntime.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/RuntimeLoop.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/SceneManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/Scene.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/ModelSceneInstantiation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/SceneRuntime.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/RuntimeLoop.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/SceneManager.cpp

View File

@@ -22,6 +22,7 @@ public:
const Resources::AssetRef& GetMeshAssetRef() const { return m_meshRef; }
void SetMeshPath(const std::string& meshPath);
void SetMeshAssetRef(const Resources::AssetRef& meshRef);
void SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh);
void SetMesh(Resources::Mesh* mesh);
void ClearMesh();

View File

@@ -25,6 +25,7 @@ public:
const std::vector<Resources::AssetRef>& GetMaterialAssetRefs() const { return m_materialRefs; }
void SetMaterialPath(size_t index, const std::string& materialPath);
void SetMaterialAssetRef(size_t index, const Resources::AssetRef& materialRef);
void SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material);
void SetMaterial(size_t index, Resources::Material* material);
void SetMaterials(const std::vector<Resources::ResourceHandle<Resources::Material>>& materials);

View File

@@ -0,0 +1,105 @@
#pragma once
#include <XCEngine/Core/Asset/AssetGUID.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Types.h>
namespace XCEngine {
namespace Resources {
constexpr Core::uint32 kArtifactContainerSchemaVersion = 1;
enum class ArtifactContainerCompression : Core::uint32 {
None = 0
};
struct ArtifactContainerEntry {
Containers::String name;
ResourceType resourceType = ResourceType::Unknown;
LocalID localID = kInvalidLocalID;
Core::uint32 flags = 0;
ArtifactContainerCompression compression = ArtifactContainerCompression::None;
Containers::Array<Core::uint8> payload;
};
struct ArtifactContainerEntryView {
Containers::String name;
ResourceType resourceType = ResourceType::Unknown;
LocalID localID = kInvalidLocalID;
Core::uint32 flags = 0;
ArtifactContainerCompression compression = ArtifactContainerCompression::None;
Core::uint64 payloadOffset = 0;
Core::uint64 payloadSize = 0;
};
class ArtifactContainerWriter {
public:
void Clear();
void AddEntry(const ArtifactContainerEntry& entry);
void AddEntry(ArtifactContainerEntry&& entry);
const Containers::Array<ArtifactContainerEntry>& GetEntries() const { return m_entries; }
bool WriteToFile(const Containers::String& path,
Containers::String* outErrorMessage = nullptr) const;
private:
Containers::Array<ArtifactContainerEntry> m_entries;
};
class ArtifactContainerReader {
public:
bool Open(const Containers::String& path,
Containers::String* outErrorMessage = nullptr);
void Close();
bool IsOpen() const { return !m_path.Empty(); }
const Containers::String& GetPath() const { return m_path; }
const Containers::Array<ArtifactContainerEntryView>& GetEntries() const { return m_entries; }
Core::uint32 GetEntryCount() const { return static_cast<Core::uint32>(m_entries.Size()); }
const ArtifactContainerEntryView* FindEntryByName(const Containers::String& name) const;
const ArtifactContainerEntryView* FindEntry(ResourceType resourceType,
LocalID localID) const;
bool ReadEntryPayload(const ArtifactContainerEntryView& entry,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage = nullptr) const;
bool ReadEntryPayload(const Containers::String& name,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage = nullptr) const;
private:
Containers::String m_path;
Containers::Array<ArtifactContainerEntryView> m_entries;
Core::uint64 m_payloadStart = 0;
Core::uint64 m_payloadSize = 0;
};
bool WriteArtifactContainer(const Containers::String& path,
const Containers::Array<ArtifactContainerEntry>& entries,
Containers::String* outErrorMessage = nullptr);
bool IsArtifactContainerFile(const Containers::String& path);
Containers::String BuildArtifactContainerEntryPath(const Containers::String& containerPath,
const Containers::String& entryName);
bool TryParseArtifactContainerEntryPath(const Containers::String& path,
Containers::String& outContainerPath,
Containers::String& outEntryName);
bool ReadArtifactContainerEntryPayload(const Containers::String& containerPath,
const Containers::String& entryName,
ResourceType expectedType,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage = nullptr);
bool ReadArtifactContainerPayloadByPath(const Containers::String& path,
ResourceType expectedType,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage = nullptr);
bool ReadArtifactContainerMainEntryPayload(const Containers::String& path,
ResourceType expectedType,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage = nullptr);
} // namespace Resources
} // namespace XCEngine

View File

@@ -175,6 +175,7 @@ private:
Core::uint64 materialVersion = 0;
RHI::RHIResourceView* baseColorTextureView = nullptr;
RHI::RHIResourceView* shadowMapTextureView = nullptr;
std::vector<RHI::RHIResourceView*> materialTextureViews;
};
struct ResolvedShaderPass {
@@ -305,6 +306,9 @@ private:
const Resources::Texture* ResolveTexture(const Resources::Material* material) const;
RHI::RHIResourceView* ResolveTextureView(const Resources::Texture* texture);
RHI::RHIResourceView* ResolveTextureView(const VisibleRenderItem& visibleItem);
RHI::RHIResourceView* ResolveMaterialTextureView(
const Resources::Material* material,
const BuiltinPassResourceBindingDesc& binding);
static LightingConstants BuildLightingConstants(const RenderLightingData& lightingData);
static AdditionalLightConstants BuildAdditionalLightConstants(const RenderAdditionalLightData& lightData);
bool HasSkybox(const RenderSceneData& sceneData) const;

View File

@@ -5,6 +5,7 @@
#include <XCEngine/Core/Math/Bounds.h>
#include <XCEngine/Core/Math/Vector2.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/Core/Types.h>
namespace XCEngine {
@@ -38,6 +39,8 @@ struct StaticMeshVertex {
Math::Vector3 tangent = Math::Vector3::Zero();
Math::Vector3 bitangent = Math::Vector3::Zero();
Math::Vector2 uv0 = Math::Vector2::Zero();
Math::Vector2 uv1 = Math::Vector2::Zero();
Math::Vector4 color = Math::Vector4::One();
};
struct MeshSection {

View File

@@ -0,0 +1,37 @@
#pragma once
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Resources/Model/Model.h>
#include <vector>
namespace XCEngine {
namespace Components {
class GameObject;
class Scene;
}
struct ModelSceneInstantiationResult {
Components::GameObject* rootObject = nullptr;
std::vector<Components::GameObject*> nodeObjects;
std::vector<Components::GameObject*> meshObjects;
};
bool InstantiateModelHierarchy(
Components::Scene& scene,
const Resources::Model& model,
const Resources::AssetRef& modelAssetRef,
Components::GameObject* parent = nullptr,
ModelSceneInstantiationResult* outResult = nullptr,
Containers::String* outErrorMessage = nullptr);
bool InstantiateModelHierarchy(
Components::Scene& scene,
const Containers::String& modelPath,
Components::GameObject* parent = nullptr,
ModelSceneInstantiationResult* outResult = nullptr,
Containers::String* outErrorMessage = nullptr);
} // namespace XCEngine

View File

@@ -106,6 +106,31 @@ void MeshFilterComponent::SetMeshPath(const std::string& meshPath) {
}
}
void MeshFilterComponent::SetMeshAssetRef(const Resources::AssetRef& meshRef) {
m_pendingMeshLoad.reset();
m_asyncMeshLoadRequested = false;
m_mesh.Reset();
m_meshPath.clear();
m_meshRef = meshRef;
if (!m_meshRef.IsValid()) {
return;
}
Containers::String resolvedPath;
if (Resources::ResourceManager::Get().TryResolveAssetPath(m_meshRef, resolvedPath)) {
m_meshPath = ToStdString(resolvedPath);
}
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
return;
}
m_mesh = Resources::ResourceManager::Get().Load<Resources::Mesh>(m_meshRef);
if (m_mesh.Get() != nullptr && m_meshPath.empty()) {
m_meshPath = ToStdString(m_mesh->GetPath());
}
}
void MeshFilterComponent::SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh) {
m_pendingMeshLoad.reset();
m_asyncMeshLoadRequested = false;
@@ -271,7 +296,8 @@ void MeshFilterComponent::ResolvePendingMesh() {
}
m_meshPath = ToStdString(m_mesh->GetPath());
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
if (!m_meshRef.IsValid() &&
!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
m_meshRef.Reset();
}

View File

@@ -193,6 +193,32 @@ void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& mat
}
}
void MeshRendererComponent::SetMaterialAssetRef(size_t index, const Resources::AssetRef& materialRef) {
EnsureMaterialSlot(index);
m_pendingMaterialLoads[index].reset();
m_asyncMaterialLoadRequested[index] = false;
m_materials[index].Reset();
m_materialPaths[index].clear();
m_materialRefs[index] = materialRef;
if (!m_materialRefs[index].IsValid()) {
return;
}
Containers::String resolvedPath;
if (Resources::ResourceManager::Get().TryResolveAssetPath(m_materialRefs[index], resolvedPath)) {
m_materialPaths[index] = ToStdString(resolvedPath);
}
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
return;
}
m_materials[index] = Resources::ResourceManager::Get().Load<Resources::Material>(m_materialRefs[index]);
if (m_materials[index].Get() != nullptr && m_materialPaths[index].empty()) {
m_materialPaths[index] = MaterialPathFromHandle(m_materials[index]);
}
}
void MeshRendererComponent::SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material) {
EnsureMaterialSlot(index);
m_pendingMaterialLoads[index].reset();
@@ -445,7 +471,8 @@ void MeshRendererComponent::ResolvePendingMaterials() {
}
m_materialPaths[index] = MaterialPathFromHandle(m_materials[index]);
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[index].c_str(),
if (!m_materialRefs[index].IsValid() &&
!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[index].c_str(),
Resources::ResourceType::Material,
m_materialRefs[index])) {
m_materialRefs[index].Reset();

View File

@@ -0,0 +1,571 @@
#include <XCEngine/Core/Asset/ArtifactContainer.h>
#include <array>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
#include <vector>
namespace XCEngine {
namespace Resources {
namespace fs = std::filesystem;
namespace {
constexpr const char* kArtifactContainerEntryPathToken = "@entry=";
struct ArtifactContainerFileHeader {
char magic[8] = { 'X', 'C', 'A', 'R', 'T', '0', '1', '\0' };
Core::uint32 schemaVersion = kArtifactContainerSchemaVersion;
Core::uint32 entryCount = 0;
Core::uint64 directorySize = 0;
Core::uint64 payloadSize = 0;
Core::uint64 contentHashHigh = 0;
Core::uint64 contentHashLow = 0;
};
struct ArtifactContainerEntryHeader {
Core::uint32 resourceType = 0;
Core::uint32 compression = 0;
Core::uint32 flags = 0;
Core::uint32 nameLength = 0;
Core::uint64 localID = 0;
Core::uint64 payloadOffset = 0;
Core::uint64 payloadSize = 0;
};
class IncrementalArtifactHasher {
public:
void Append(const void* data, size_t size) {
if (data == nullptr || size == 0) {
return;
}
m_hasBytes = true;
const auto* bytes = static_cast<const Core::uint8*>(data);
for (size_t index = 0; index < size; ++index) {
m_high ^= static_cast<Core::uint64>(bytes[index]);
m_high *= 1099511628211ULL;
m_low ^= static_cast<Core::uint64>(bytes[index]);
m_low *= 1099511628211ULL;
}
}
AssetGUID Finish() const {
if (!m_hasBytes) {
return AssetGUID();
}
return AssetGUID(m_high, m_low);
}
private:
bool m_hasBytes = false;
Core::uint64 m_high = 14695981039346656037ULL;
Core::uint64 m_low = 1099511628211ULL ^ 0x9e3779b97f4a7c15ULL;
};
Containers::String MakeError(const char* message) {
return Containers::String(message == nullptr ? "" : message);
}
bool TryWriteBytes(std::ofstream& output, const void* data, size_t size) {
output.write(static_cast<const char*>(data), static_cast<std::streamsize>(size));
return static_cast<bool>(output);
}
void AppendBytes(const void* data, size_t size, std::vector<Core::uint8>& outBytes) {
if (data == nullptr || size == 0) {
return;
}
const auto* begin = static_cast<const Core::uint8*>(data);
outBytes.insert(outBytes.end(), begin, begin + size);
}
bool IsExpectedMagic(const ArtifactContainerFileHeader& header) {
return std::memcmp(header.magic, "XCART01", 7) == 0 &&
header.schemaVersion == kArtifactContainerSchemaVersion;
}
} // namespace
void ArtifactContainerWriter::Clear() {
m_entries.Clear();
}
void ArtifactContainerWriter::AddEntry(const ArtifactContainerEntry& entry) {
m_entries.PushBack(entry);
}
void ArtifactContainerWriter::AddEntry(ArtifactContainerEntry&& entry) {
m_entries.PushBack(std::move(entry));
}
bool ArtifactContainerWriter::WriteToFile(const Containers::String& path,
Containers::String* outErrorMessage) const {
return WriteArtifactContainer(path, m_entries, outErrorMessage);
}
bool WriteArtifactContainer(const Containers::String& path,
const Containers::Array<ArtifactContainerEntry>& entries,
Containers::String* outErrorMessage) {
std::vector<Core::uint8> directoryBytes;
Core::uint64 payloadBytes = 0;
for (const ArtifactContainerEntry& entry : entries) {
if (entry.name.Length() > std::numeric_limits<Core::uint32>::max()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry name is too long.");
}
return false;
}
const Core::uint64 entryPayloadSize = static_cast<Core::uint64>(entry.payload.Size());
if (payloadBytes > std::numeric_limits<Core::uint64>::max() - entryPayloadSize) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer payload size overflow.");
}
return false;
}
ArtifactContainerEntryHeader entryHeader = {};
entryHeader.resourceType = static_cast<Core::uint32>(entry.resourceType);
entryHeader.compression = static_cast<Core::uint32>(entry.compression);
entryHeader.flags = entry.flags;
entryHeader.nameLength = static_cast<Core::uint32>(entry.name.Length());
entryHeader.localID = entry.localID;
entryHeader.payloadOffset = payloadBytes;
entryHeader.payloadSize = entryPayloadSize;
AppendBytes(&entryHeader, sizeof(entryHeader), directoryBytes);
AppendBytes(entry.name.CStr(), entry.name.Length(), directoryBytes);
payloadBytes += entryPayloadSize;
}
IncrementalArtifactHasher hasher;
if (!directoryBytes.empty()) {
hasher.Append(directoryBytes.data(), directoryBytes.size());
}
for (const ArtifactContainerEntry& entry : entries) {
if (!entry.payload.Empty()) {
hasher.Append(entry.payload.Data(), entry.payload.Size());
}
}
ArtifactContainerFileHeader fileHeader = {};
fileHeader.entryCount = static_cast<Core::uint32>(entries.Size());
fileHeader.directorySize = static_cast<Core::uint64>(directoryBytes.size());
fileHeader.payloadSize = payloadBytes;
const AssetGUID contentHash = hasher.Finish();
fileHeader.contentHashHigh = contentHash.high;
fileHeader.contentHashLow = contentHash.low;
std::error_code ec;
const fs::path targetPath(path.CStr());
if (!targetPath.parent_path().empty()) {
fs::create_directories(targetPath.parent_path(), ec);
if (ec) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to create ArtifactContainer parent directory.");
}
return false;
}
}
std::ofstream output(targetPath, std::ios::binary | std::ios::trunc);
if (!output.is_open()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to open ArtifactContainer output file.");
}
return false;
}
if (!TryWriteBytes(output, &fileHeader, sizeof(fileHeader)) ||
(!directoryBytes.empty() &&
!TryWriteBytes(output, directoryBytes.data(), directoryBytes.size()))) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to write ArtifactContainer header or directory.");
}
return false;
}
for (const ArtifactContainerEntry& entry : entries) {
if (!entry.payload.Empty() &&
!TryWriteBytes(output, entry.payload.Data(), entry.payload.Size())) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to write ArtifactContainer payload.");
}
return false;
}
}
return true;
}
bool IsArtifactContainerFile(const Containers::String& path) {
std::ifstream input(path.CStr(), std::ios::binary);
if (!input.is_open()) {
return false;
}
ArtifactContainerFileHeader fileHeader = {};
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
return input && IsExpectedMagic(fileHeader);
}
Containers::String BuildArtifactContainerEntryPath(const Containers::String& containerPath,
const Containers::String& entryName) {
if (containerPath.Empty() || entryName.Empty()) {
return Containers::String();
}
Containers::String result = containerPath;
result += kArtifactContainerEntryPathToken;
result += entryName;
return result;
}
bool TryParseArtifactContainerEntryPath(const Containers::String& path,
Containers::String& outContainerPath,
Containers::String& outEntryName) {
outContainerPath.Clear();
outEntryName.Clear();
const std::string text(path.CStr());
const std::string token(kArtifactContainerEntryPathToken);
const size_t tokenPos = text.rfind(token);
if (tokenPos == std::string::npos) {
return false;
}
const size_t entryNamePos = tokenPos + token.length();
if (tokenPos == 0 || entryNamePos >= text.length()) {
return false;
}
outContainerPath = Containers::String(text.substr(0, tokenPos).c_str());
outEntryName = Containers::String(text.substr(entryNamePos).c_str());
return !outContainerPath.Empty() && !outEntryName.Empty();
}
bool ArtifactContainerReader::Open(const Containers::String& path,
Containers::String* outErrorMessage) {
Close();
std::ifstream input(path.CStr(), std::ios::binary);
if (!input.is_open()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to open ArtifactContainer file.");
}
return false;
}
ArtifactContainerFileHeader fileHeader = {};
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
if (!input || !IsExpectedMagic(fileHeader)) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer header is invalid.");
}
return false;
}
input.seekg(0, std::ios::end);
const std::streamoff fileSize = input.tellg();
const Core::uint64 expectedFileSize =
static_cast<Core::uint64>(sizeof(fileHeader)) +
fileHeader.directorySize +
fileHeader.payloadSize;
if (fileSize < static_cast<std::streamoff>(sizeof(fileHeader)) ||
expectedFileSize != static_cast<Core::uint64>(fileSize)) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer file size does not match header.");
}
return false;
}
input.seekg(static_cast<std::streamoff>(sizeof(fileHeader)), std::ios::beg);
std::vector<Core::uint8> directoryBytes(static_cast<size_t>(fileHeader.directorySize));
if (!directoryBytes.empty()) {
input.read(reinterpret_cast<char*>(directoryBytes.data()),
static_cast<std::streamsize>(directoryBytes.size()));
if (!input) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to read ArtifactContainer directory.");
}
return false;
}
}
Containers::Array<ArtifactContainerEntryView> parsedEntries;
parsedEntries.Reserve(fileHeader.entryCount);
size_t cursor = 0;
for (Core::uint32 entryIndex = 0; entryIndex < fileHeader.entryCount; ++entryIndex) {
if (cursor + sizeof(ArtifactContainerEntryHeader) > directoryBytes.size()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer directory is truncated.");
}
return false;
}
ArtifactContainerEntryHeader entryHeader = {};
std::memcpy(&entryHeader, directoryBytes.data() + cursor, sizeof(entryHeader));
cursor += sizeof(entryHeader);
if (cursor + entryHeader.nameLength > directoryBytes.size()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry name is truncated.");
}
return false;
}
if (entryHeader.payloadOffset > fileHeader.payloadSize ||
entryHeader.payloadSize > fileHeader.payloadSize - entryHeader.payloadOffset) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry payload range is invalid.");
}
return false;
}
ArtifactContainerEntryView& view = parsedEntries.EmplaceBack();
view.name = Containers::String(
reinterpret_cast<const char*>(directoryBytes.data() + cursor),
entryHeader.nameLength);
view.resourceType = static_cast<ResourceType>(entryHeader.resourceType);
view.localID = entryHeader.localID;
view.flags = entryHeader.flags;
view.compression = static_cast<ArtifactContainerCompression>(entryHeader.compression);
view.payloadOffset = entryHeader.payloadOffset;
view.payloadSize = entryHeader.payloadSize;
cursor += entryHeader.nameLength;
}
if (cursor != directoryBytes.size()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer directory has trailing garbage.");
}
return false;
}
IncrementalArtifactHasher hasher;
if (!directoryBytes.empty()) {
hasher.Append(directoryBytes.data(), directoryBytes.size());
}
std::array<Core::uint8, 4096> buffer = {};
Core::uint64 remainingPayloadBytes = fileHeader.payloadSize;
while (remainingPayloadBytes > 0) {
const size_t chunkSize = static_cast<size_t>(std::min<Core::uint64>(
remainingPayloadBytes,
static_cast<Core::uint64>(buffer.size())));
input.read(reinterpret_cast<char*>(buffer.data()), static_cast<std::streamsize>(chunkSize));
if (!input) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to hash ArtifactContainer payload.");
}
return false;
}
hasher.Append(buffer.data(), chunkSize);
remainingPayloadBytes -= static_cast<Core::uint64>(chunkSize);
}
const AssetGUID expectedHash(fileHeader.contentHashHigh, fileHeader.contentHashLow);
const AssetGUID actualHash = hasher.Finish();
if (expectedHash != actualHash) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer content hash mismatch.");
}
return false;
}
m_path = path;
m_entries = std::move(parsedEntries);
m_payloadStart =
static_cast<Core::uint64>(sizeof(ArtifactContainerFileHeader)) +
fileHeader.directorySize;
m_payloadSize = fileHeader.payloadSize;
return true;
}
void ArtifactContainerReader::Close() {
m_path.Clear();
m_entries.Clear();
m_payloadStart = 0;
m_payloadSize = 0;
}
const ArtifactContainerEntryView* ArtifactContainerReader::FindEntryByName(
const Containers::String& name) const {
for (const ArtifactContainerEntryView& entry : m_entries) {
if (entry.name == name) {
return &entry;
}
}
return nullptr;
}
const ArtifactContainerEntryView* ArtifactContainerReader::FindEntry(
ResourceType resourceType,
LocalID localID) const {
for (const ArtifactContainerEntryView& entry : m_entries) {
if (entry.resourceType == resourceType && entry.localID == localID) {
return &entry;
}
}
return nullptr;
}
bool ArtifactContainerReader::ReadEntryPayload(const ArtifactContainerEntryView& entry,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage) const {
outPayload.Clear();
if (!IsOpen()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer is not open.");
}
return false;
}
if (entry.payloadOffset > m_payloadSize ||
entry.payloadSize > m_payloadSize - entry.payloadOffset) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry payload range is invalid.");
}
return false;
}
if (entry.payloadSize == 0) {
return true;
}
if (entry.payloadSize > static_cast<Core::uint64>(std::numeric_limits<size_t>::max())) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry payload is too large.");
}
return false;
}
std::ifstream input(m_path.CStr(), std::ios::binary);
if (!input.is_open()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to reopen ArtifactContainer file.");
}
return false;
}
input.seekg(static_cast<std::streamoff>(m_payloadStart + entry.payloadOffset), std::ios::beg);
if (!input) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to seek ArtifactContainer payload.");
}
return false;
}
outPayload.ResizeUninitialized(static_cast<size_t>(entry.payloadSize));
input.read(reinterpret_cast<char*>(outPayload.Data()),
static_cast<std::streamsize>(entry.payloadSize));
if (!input) {
outPayload.Clear();
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("Failed to read ArtifactContainer payload.");
}
return false;
}
return true;
}
bool ArtifactContainerReader::ReadEntryPayload(const Containers::String& name,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage) const {
const ArtifactContainerEntryView* entry = FindEntryByName(name);
if (entry == nullptr) {
outPayload.Clear();
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry was not found.");
}
return false;
}
return ReadEntryPayload(*entry, outPayload, outErrorMessage);
}
bool ReadArtifactContainerEntryPayload(const Containers::String& containerPath,
const Containers::String& entryName,
ResourceType expectedType,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage) {
outPayload.Clear();
ArtifactContainerReader reader;
if (!reader.Open(containerPath, outErrorMessage)) {
return false;
}
const ArtifactContainerEntryView* entry = reader.FindEntryByName(entryName);
if (entry == nullptr) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry was not found.");
}
return false;
}
if (expectedType != ResourceType::Unknown && entry->resourceType != expectedType) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer entry resource type did not match.");
}
return false;
}
return reader.ReadEntryPayload(*entry, outPayload, outErrorMessage);
}
bool ReadArtifactContainerPayloadByPath(const Containers::String& path,
ResourceType expectedType,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage) {
Containers::String containerPath;
Containers::String entryName;
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
return ReadArtifactContainerEntryPayload(
containerPath,
entryName,
expectedType,
outPayload,
outErrorMessage);
}
return ReadArtifactContainerMainEntryPayload(path, expectedType, outPayload, outErrorMessage);
}
bool ReadArtifactContainerMainEntryPayload(const Containers::String& path,
ResourceType expectedType,
Containers::Array<Core::uint8>& outPayload,
Containers::String* outErrorMessage) {
outPayload.Clear();
ArtifactContainerReader reader;
if (!reader.Open(path, outErrorMessage)) {
return false;
}
const ArtifactContainerEntryView* entry = reader.FindEntry(expectedType, kMainAssetLocalID);
if (entry == nullptr) {
entry = reader.FindEntryByName("main");
}
if (entry == nullptr) {
if (outErrorMessage != nullptr) {
*outErrorMessage = MakeError("ArtifactContainer main entry was not found.");
}
return false;
}
return reader.ReadEntryPayload(*entry, outPayload, outErrorMessage);
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -75,6 +75,9 @@ bool SerializeMaterialArtifactPayload(
const AssetDatabase* assetDatabase);
bool SerializeShaderArtifactPayload(const Shader& shader,
Containers::Array<Core::uint8>& outPayload);
bool SerializeMeshArtifactPayload(const Mesh& mesh,
const std::vector<Containers::String>& materialArtifactPaths,
Containers::Array<Core::uint8>& outPayload);
bool WriteSingleEntryArtifactContainerFile(const fs::path& artifactPath,
ResourceType resourceType,
const Containers::Array<Core::uint8>& payload);
@@ -1225,11 +1228,28 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
bool WriteMeshArtifactFile(const fs::path& artifactPath,
const Mesh& mesh,
const std::vector<Containers::String>& materialArtifactPaths) {
Containers::Array<Core::uint8> payload;
if (!SerializeMeshArtifactPayload(mesh, materialArtifactPaths, payload)) {
return false;
}
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
if (!output.is_open()) {
return false;
}
if (!payload.Empty()) {
output.write(reinterpret_cast<const char*>(payload.Data()), static_cast<std::streamsize>(payload.Size()));
}
return static_cast<bool>(output);
}
bool SerializeMeshArtifactPayload(const Mesh& mesh,
const std::vector<Containers::String>& materialArtifactPaths,
Containers::Array<Core::uint8>& outPayload) {
std::ostringstream output(std::ios::binary | std::ios::out);
MeshArtifactHeader header;
header.vertexCount = mesh.GetVertexCount();
header.vertexStride = mesh.GetVertexStride();
@@ -1259,7 +1279,13 @@ bool WriteMeshArtifactFile(const fs::path& artifactPath,
WriteString(output, NormalizeArtifactPathString(materialArtifactPath));
}
return static_cast<bool>(output);
if (!output) {
outPayload.Clear();
return false;
}
outPayload = ToByteArray(output.str());
return true;
}
void DestroyImportedMesh(Mesh* mesh) {
@@ -1438,6 +1464,20 @@ bool AssetDatabase::TryResolveAssetPath(const AssetRef& assetRef, Containers::St
}
auto resolveFromArtifactRecord = [&](const ArtifactRecord& artifactRecord) -> bool {
const Containers::String absoluteMainArtifactPath =
NormalizePathString(fs::path(m_projectRoot.CStr()) / artifactRecord.mainArtifactPath.CStr());
ArtifactContainerReader reader;
Containers::String containerError;
if (reader.Open(absoluteMainArtifactPath, &containerError)) {
const ArtifactContainerEntryView* entry =
reader.FindEntry(assetRef.resourceType, assetRef.localID);
if (entry != nullptr) {
outPath = BuildArtifactContainerEntryPath(absoluteMainArtifactPath, entry->name);
return true;
}
}
const fs::path manifestPath =
fs::path(m_projectRoot.CStr()) /
artifactRecord.artifactDirectory.CStr() /
@@ -2170,7 +2210,7 @@ Core::uint32 AssetDatabase::GetCurrentImporterVersion(const Containers::String&
}
if (importerName == "ModelImporter") {
return 10;
return 12;
}
if (importerName == "GaussianSplatImporter") {
@@ -2557,21 +2597,24 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
std::vector<ArtifactDependencyRecord> dependencies;
CollectModelDependencies(sourceRecord, importedTexturePaths, dependencies);
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
const Containers::String mainArtifactPath =
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmodel");
const Containers::String legacyArtifactDir = BuildArtifactDirectory(artifactKey);
const Containers::String mainArtifactPath = BuildArtifactFilePath(artifactKey, ".xcmodel");
const Containers::String artifactDir =
NormalizePathString(fs::path(mainArtifactPath.CStr()).parent_path());
std::error_code ec;
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
fs::remove_all(fs::path(m_projectRoot.CStr()) / legacyArtifactDir.CStr(), ec);
ec.clear();
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
fs::create_directories(
(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()).parent_path(),
ec);
if (ec) {
importedModel.Reset();
return false;
}
ArtifactContainerWriter writer;
bool writeOk = true;
std::vector<ModelSubAssetManifestEntry> subAssetManifestEntries;
std::unordered_map<const Texture*, Containers::String> textureArtifactPaths;
std::unordered_map<const Texture*, AssetRef> textureAssetRefs;
for (size_t textureIndex = 0; writeOk && textureIndex < importedModel.textures.size(); ++textureIndex) {
@@ -2580,16 +2623,23 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
continue;
}
const Containers::String textureArtifactPath =
NormalizePathString(fs::path(artifactDir.CStr()) / ("texture_" + std::to_string(textureIndex) + ".xctex"));
writeOk = WriteTextureArtifactFile(
fs::path(m_projectRoot.CStr()) / textureArtifactPath.CStr(),
*texture);
const Containers::String entryName =
Containers::String(("texture_" + std::to_string(textureIndex) + ".xctex").c_str());
Containers::Array<Core::uint8> payload;
writeOk = SerializeTextureArtifactPayload(*texture, payload);
if (!writeOk) {
break;
}
textureArtifactPaths.emplace(texture, textureArtifactPath);
ArtifactContainerEntry entry;
entry.name = entryName;
entry.resourceType = ResourceType::Texture;
entry.payload = std::move(payload);
writer.AddEntry(std::move(entry));
textureArtifactPaths.emplace(
texture,
BuildArtifactContainerEntryPath(mainArtifactPath, entryName));
if (!texture->GetPath().Empty()) {
AssetRef textureAssetRef;
@@ -2606,11 +2656,12 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
continue;
}
const Containers::String materialArtifactPath =
NormalizePathString(fs::path(artifactDir.CStr()) / ("material_" + std::to_string(materialIndex) + ".xcmat"));
writeOk = WriteMaterialArtifactFile(
fs::path(m_projectRoot.CStr()) / materialArtifactPath.CStr(),
const Containers::String entryName =
Containers::String(("material_" + std::to_string(materialIndex) + ".xcmat").c_str());
Containers::Array<Core::uint8> payload;
writeOk = SerializeMaterialArtifactPayload(
*materialEntry.material,
payload,
textureArtifactPaths,
textureAssetRefs,
this);
@@ -2618,9 +2669,16 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
break;
}
materialArtifactPathsByLocalID.emplace(materialEntry.localID, materialArtifactPath);
subAssetManifestEntries.push_back(
ModelSubAssetManifestEntry{ materialEntry.localID, ResourceType::Material, materialArtifactPath });
ArtifactContainerEntry entry;
entry.name = entryName;
entry.resourceType = ResourceType::Material;
entry.localID = materialEntry.localID;
entry.payload = std::move(payload);
writer.AddEntry(std::move(entry));
materialArtifactPathsByLocalID.emplace(
materialEntry.localID,
BuildArtifactContainerEntryPath(mainArtifactPath, entryName));
}
for (size_t meshIndex = 0; writeOk && meshIndex < importedModel.meshes.size(); ++meshIndex) {
@@ -2639,30 +2697,37 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
: Containers::String());
}
const Containers::String meshArtifactPath =
NormalizePathString(fs::path(artifactDir.CStr()) / ("mesh_" + std::to_string(meshIndex) + ".xcmesh"));
writeOk = WriteMeshArtifactFile(
fs::path(m_projectRoot.CStr()) / meshArtifactPath.CStr(),
*meshEntry.mesh,
meshMaterialArtifactPaths);
if (writeOk) {
subAssetManifestEntries.push_back(
ModelSubAssetManifestEntry{ meshEntry.localID, ResourceType::Mesh, meshArtifactPath });
const Containers::String entryName =
Containers::String(("mesh_" + std::to_string(meshIndex) + ".xcmesh").c_str());
Containers::Array<Core::uint8> payload;
writeOk = SerializeMeshArtifactPayload(*meshEntry.mesh, meshMaterialArtifactPaths, payload);
if (!writeOk) {
break;
}
ArtifactContainerEntry entry;
entry.name = entryName;
entry.resourceType = ResourceType::Mesh;
entry.localID = meshEntry.localID;
entry.payload = std::move(payload);
writer.AddEntry(std::move(entry));
}
Containers::String modelWriteErrorMessage;
if (writeOk) {
writeOk = WriteModelArtifactFile(
NormalizePathString(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()),
*importedModel.model,
&modelWriteErrorMessage);
}
if (writeOk) {
writeOk = WriteModelSubAssetManifest(
fs::path(m_projectRoot.CStr()) / artifactDir.CStr() / kModelSubAssetManifestFileName,
subAssetManifestEntries);
Containers::Array<Core::uint8> modelPayload;
writeOk = SerializeModelArtifactPayload(*importedModel.model, modelPayload, &modelWriteErrorMessage);
if (writeOk) {
ArtifactContainerEntry mainEntry;
mainEntry.name = "main";
mainEntry.resourceType = ResourceType::Model;
mainEntry.localID = kMainAssetLocalID;
mainEntry.payload = std::move(modelPayload);
writer.AddEntry(std::move(mainEntry));
writeOk = writer.WriteToFile(
NormalizePathString(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()),
&modelWriteErrorMessage);
}
}
importedModel.Reset();
@@ -2940,14 +3005,19 @@ bool AssetDatabase::ImportUIDocumentAsset(const SourceAssetRecord& sourceRecord,
}
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
const Containers::String mainArtifactPath =
NormalizePathString(fs::path(artifactDir.CStr()) / artifactFileName);
const Containers::String legacyArtifactDir = BuildArtifactDirectory(artifactKey);
const Containers::String extension =
Containers::String(fs::path(artifactFileName == nullptr ? "" : artifactFileName).extension().string().c_str());
const Containers::String mainArtifactPath = BuildArtifactFilePath(artifactKey, extension.CStr());
const Containers::String artifactDir =
NormalizePathString(fs::path(mainArtifactPath.CStr()).parent_path());
std::error_code ec;
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
fs::remove_all(fs::path(m_projectRoot.CStr()) / legacyArtifactDir.CStr(), ec);
ec.clear();
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
fs::create_directories(
(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()).parent_path(),
ec);
if (ec) {
SetLastErrorMessage(
Containers::String("Failed to create UI artifact directory: ") + artifactDir);

View File

@@ -106,6 +106,38 @@ RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() {
texcoord.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, uv0));
inputLayout.elements.push_back(texcoord);
RHI::InputElementDesc backTexcoord = {};
backTexcoord.semanticName = "TEXCOORD";
backTexcoord.semanticIndex = 1;
backTexcoord.format = static_cast<uint32_t>(RHI::Format::R32G32_Float);
backTexcoord.inputSlot = 0;
backTexcoord.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, uv1));
inputLayout.elements.push_back(backTexcoord);
RHI::InputElementDesc tangent = {};
tangent.semanticName = "TEXCOORD";
tangent.semanticIndex = 2;
tangent.format = static_cast<uint32_t>(RHI::Format::R32G32B32_Float);
tangent.inputSlot = 0;
tangent.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, tangent));
inputLayout.elements.push_back(tangent);
RHI::InputElementDesc bitangent = {};
bitangent.semanticName = "TEXCOORD";
bitangent.semanticIndex = 3;
bitangent.format = static_cast<uint32_t>(RHI::Format::R32G32B32_Float);
bitangent.inputSlot = 0;
bitangent.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, bitangent));
inputLayout.elements.push_back(bitangent);
RHI::InputElementDesc color = {};
color.semanticName = "COLOR";
color.semanticIndex = 0;
color.format = static_cast<uint32_t>(RHI::Format::R32G32B32A32_Float);
color.inputSlot = 0;
color.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, color));
inputLayout.elements.push_back(color);
return inputLayout;
}
@@ -132,17 +164,26 @@ bool BuiltinForwardPipeline::Render(
const RenderSurface& surface,
const RenderSceneData& sceneData) {
if (!Initialize(context)) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: Initialize returned false");
return false;
}
if (m_volumetricPass != nullptr &&
!sceneData.visibleVolumes.empty() &&
!m_volumetricPass->PrepareVolumeResources(context, sceneData)) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: PrepareVolumeResources returned false");
return false;
}
if (m_gaussianSplatPass != nullptr &&
!sceneData.visibleGaussianSplats.empty() &&
!m_gaussianSplatPass->PrepareGaussianSplatResources(context, sceneData)) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: PrepareGaussianSplatResources returned false");
return false;
}
@@ -156,6 +197,9 @@ bool BuiltinForwardPipeline::Render(
};
if (!BeginForwardScenePass(passContext)) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: BeginForwardScenePass returned false");
return false;
}
@@ -167,15 +211,35 @@ bool BuiltinForwardPipeline::Render(
bool renderResult = ExecuteForwardOpaquePass(passContext);
if (renderResult) {
renderResult = ExecuteForwardSkyboxPass(passContext);
if (!renderResult) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: ExecuteForwardSkyboxPass returned false");
}
}
if (renderResult && m_gaussianSplatPass != nullptr) {
renderResult = m_gaussianSplatPass->Execute(passContext);
if (!renderResult) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: BuiltinGaussianSplatPass::Execute returned false");
}
}
if (renderResult && m_volumetricPass != nullptr) {
renderResult = m_volumetricPass->Execute(passContext);
if (!renderResult) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: BuiltinVolumetricPass::Execute returned false");
}
}
if (renderResult) {
renderResult = ExecuteForwardTransparentPass(passContext);
if (!renderResult) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline::Render failed: ExecuteForwardTransparentPass returned false");
}
}
if (sampledDirectionalShadow) {

View File

@@ -438,6 +438,37 @@ BuiltinForwardPipeline::CachedDescriptorSet* BuiltinForwardPipeline::GetOrCreate
}
}
if (setLayout.usesMaterialTextures) {
if (material == nullptr) {
return nullptr;
}
if (cachedDescriptorSet.materialTextureViews.size() != setLayout.materialTextureBindings.size()) {
cachedDescriptorSet.materialTextureViews.assign(
setLayout.materialTextureBindings.size(),
nullptr);
}
for (size_t bindingIndex = 0; bindingIndex < setLayout.materialTextureBindings.size(); ++bindingIndex) {
const BuiltinPassResourceBindingDesc& textureBinding =
setLayout.materialTextureBindings[bindingIndex];
RHI::RHIResourceView* resolvedTextureView =
ResolveMaterialTextureView(material, textureBinding);
if (resolvedTextureView == nullptr) {
return nullptr;
}
if (cachedDescriptorSet.materialTextureViews[bindingIndex] != resolvedTextureView) {
cachedDescriptorSet.descriptorSet.set->Update(
textureBinding.location.binding,
resolvedTextureView);
cachedDescriptorSet.materialTextureViews[bindingIndex] = resolvedTextureView;
}
}
} else if (!cachedDescriptorSet.materialTextureViews.empty()) {
cachedDescriptorSet.materialTextureViews.clear();
}
if (setLayout.usesLighting) {
if (!passLayout.lighting.IsValid() || passLayout.lighting.set != setIndex) {
return nullptr;
@@ -556,6 +587,33 @@ RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView(
return textureView != nullptr ? textureView : m_fallbackTexture2DView;
}
RHI::RHIResourceView* BuiltinForwardPipeline::ResolveMaterialTextureView(
const Resources::Material* material,
const BuiltinPassResourceBindingDesc& binding) {
const bool expectsCubemap = binding.resourceType == Resources::ShaderResourceType::TextureCube;
RHI::RHIResourceView* fallbackView = expectsCubemap ? m_fallbackTextureCubeView : m_fallbackTexture2DView;
if (material == nullptr) {
return fallbackView;
}
const Resources::ResourceHandle<Resources::Texture> textureHandle = material->GetTexture(binding.name);
const Resources::Texture* texture = textureHandle.Get();
if (texture == nullptr) {
return fallbackView;
}
const Resources::TextureType textureType = texture->GetTextureType();
const bool isCubemap =
textureType == Resources::TextureType::TextureCube ||
textureType == Resources::TextureType::TextureCubeArray;
if (expectsCubemap != isCubemap) {
return fallbackView;
}
RHI::RHIResourceView* textureView = ResolveTextureView(texture);
return textureView != nullptr ? textureView : fallbackView;
}
BuiltinForwardPipeline::LightingConstants BuiltinForwardPipeline::BuildLightingConstants(
const RenderLightingData& lightingData) {
LightingConstants lightingConstants = {};
@@ -664,7 +722,7 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
? sceneData.lighting.mainDirectionalShadow.shadowParams
: Math::Vector4::Zero(),
sceneData.lighting.HasMainDirectionalShadow()
? Math::Vector4(1.0f, 0.0f, 0.0f, 0.0f)
? sceneData.lighting.mainDirectionalShadow.shadowOptions
: Math::Vector4::Zero()
};
@@ -730,7 +788,8 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
const Resources::Material* materialKey =
(setLayout.usesMaterial ||
setLayout.usesBaseColorTexture ||
setLayout.usesMaterialBuffers)
setLayout.usesMaterialBuffers ||
setLayout.usesMaterialTextures)
? material
: nullptr;

View File

@@ -1,4 +1,5 @@
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Core/Asset/ArtifactContainer.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/MaterialLoader.h>
@@ -229,12 +230,27 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
}
std::filesystem::path dependencyFsPath(dependencyPath.CStr());
Containers::String containerPathText;
Containers::String entryName;
const bool isContainerEntryPath =
TryParseArtifactContainerEntryPath(dependencyPath, containerPathText, entryName);
if (isContainerEntryPath) {
dependencyFsPath = std::filesystem::path(containerPathText.CStr());
}
auto rebuildResolvedPath = [&entryName, isContainerEntryPath](const std::filesystem::path& resolvedPath) {
const Containers::String normalizedPath = NormalizePathString(resolvedPath);
return isContainerEntryPath
? BuildArtifactContainerEntryPath(normalizedPath, entryName)
: normalizedPath;
};
if (dependencyFsPath.is_absolute() && std::filesystem::exists(dependencyFsPath)) {
return NormalizePathString(dependencyFsPath);
return rebuildResolvedPath(dependencyFsPath);
}
if (std::filesystem::exists(dependencyFsPath)) {
return NormalizePathString(dependencyFsPath);
return rebuildResolvedPath(dependencyFsPath);
}
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
@@ -242,7 +258,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
const std::filesystem::path projectRelativeCandidate =
std::filesystem::path(resourceRoot.CStr()) / dependencyFsPath;
if (std::filesystem::exists(projectRelativeCandidate)) {
return NormalizePathString(projectRelativeCandidate);
return rebuildResolvedPath(projectRelativeCandidate);
}
}
@@ -254,7 +270,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
const std::filesystem::path ownerRelativeCandidate =
ownerArtifactFsPath.parent_path() / dependencyFsPath;
if (std::filesystem::exists(ownerRelativeCandidate)) {
return NormalizePathString(ownerRelativeCandidate);
return rebuildResolvedPath(ownerRelativeCandidate);
}
std::filesystem::path current = ownerArtifactFsPath.parent_path();
@@ -264,7 +280,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
if (!projectRoot.empty()) {
const std::filesystem::path projectRelativeCandidate = projectRoot / dependencyFsPath;
if (std::filesystem::exists(projectRelativeCandidate)) {
return NormalizePathString(projectRelativeCandidate);
return rebuildResolvedPath(projectRelativeCandidate);
}
}
break;
@@ -675,42 +691,66 @@ ImportedMeshData ImportSingleMesh(const aiMesh& mesh,
result.vertices.reserve(mesh.mNumVertices);
result.indices.reserve(mesh.mNumFaces * 3);
const bool hasNormals = mesh.HasNormals();
const bool hasTangentsAndBitangents = mesh.HasTangentsAndBitangents();
const bool hasUv0 = mesh.HasTextureCoords(0);
const bool hasUv1 = mesh.HasTextureCoords(1);
const bool hasVertexColors = mesh.HasVertexColors(0);
const bool useFallbackUv1 = hasUv0 && !hasUv1;
VertexAttribute attributes = VertexAttribute::Position;
if (mesh.HasNormals()) {
if (hasNormals) {
attributes = attributes | VertexAttribute::Normal;
}
if (mesh.HasTangentsAndBitangents()) {
if (hasTangentsAndBitangents) {
attributes = attributes | VertexAttribute::Tangent | VertexAttribute::Bitangent;
}
if (mesh.HasTextureCoords(0)) {
if (hasUv0) {
attributes = attributes | VertexAttribute::UV0;
}
if (hasUv1 || useFallbackUv1) {
attributes = attributes | VertexAttribute::UV1;
}
if (hasVertexColors) {
attributes = attributes | VertexAttribute::Color;
}
const Math::Matrix4 normalTransform = worldTransform.Inverse().Transpose();
const float appliedScale = globalScale;
for (Core::uint32 vertexIndex = 0; vertexIndex < mesh.mNumVertices; ++vertexIndex) {
StaticMeshVertex vertex;
StaticMeshVertex vertex = {};
const aiVector3D& position = mesh.mVertices[vertexIndex];
const Math::Vector3 transformedPosition = worldTransform.MultiplyPoint(Math::Vector3(position.x, position.y, position.z));
vertex.position = transformedPosition * appliedScale + offset;
if (mesh.HasNormals()) {
if (hasNormals) {
const aiVector3D& normal = mesh.mNormals[vertexIndex];
vertex.normal = normalTransform.MultiplyVector(Math::Vector3(normal.x, normal.y, normal.z)).Normalized();
}
if (mesh.HasTangentsAndBitangents()) {
if (hasTangentsAndBitangents) {
const aiVector3D& tangent = mesh.mTangents[vertexIndex];
const aiVector3D& bitangent = mesh.mBitangents[vertexIndex];
vertex.tangent = normalTransform.MultiplyVector(Math::Vector3(tangent.x, tangent.y, tangent.z)).Normalized();
vertex.bitangent = normalTransform.MultiplyVector(Math::Vector3(bitangent.x, bitangent.y, bitangent.z)).Normalized();
}
if (mesh.HasTextureCoords(0)) {
if (hasUv0) {
const aiVector3D& uv = mesh.mTextureCoords[0][vertexIndex];
vertex.uv0 = Math::Vector2(uv.x, uv.y);
vertex.uv1 = vertex.uv0;
}
if (hasUv1) {
const aiVector3D& uv = mesh.mTextureCoords[1][vertexIndex];
vertex.uv1 = Math::Vector2(uv.x, uv.y);
}
if (hasVertexColors) {
const aiColor4D& color = mesh.mColors[0][vertexIndex];
vertex.color = Math::Vector4(color.r, color.g, color.b, color.a);
}
result.vertices.push_back(vertex);
@@ -831,14 +871,72 @@ void ApplyMaterialProperty(Material& material, const MaterialProperty& property)
}
LoadResult LoadMeshArtifact(const Containers::String& path) {
std::ifstream input(path.CStr(), std::ios::binary);
if (!input.is_open()) {
Containers::Array<Core::uint8> data;
auto tryRead = [&data](const std::filesystem::path& filePath, bool& opened) {
opened = false;
data.Clear();
Containers::String containerError;
if (ReadArtifactContainerPayloadByPath(
Containers::String(filePath.generic_string().c_str()),
ResourceType::Mesh,
data,
&containerError)) {
opened = true;
return;
}
std::ifstream input(filePath, std::ios::binary | std::ios::ate);
if (!input.is_open()) {
return;
}
opened = true;
const std::streamsize size = input.tellg();
if (size < 0) {
data.Clear();
return;
}
input.seekg(0, std::ios::beg);
data.Resize(static_cast<size_t>(size));
if (size > 0 &&
!input.read(reinterpret_cast<char*>(data.Data()), size)) {
data.Clear();
}
};
bool opened = false;
std::filesystem::path resolvedPath(path.CStr());
tryRead(resolvedPath, opened);
if (!opened && !resolvedPath.is_absolute()) {
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
if (!resourceRoot.Empty()) {
resolvedPath = std::filesystem::path(resourceRoot.CStr()) / resolvedPath;
tryRead(resolvedPath, opened);
}
}
if (!opened || data.Size() < sizeof(MeshArtifactHeader)) {
return LoadResult(Containers::String("Failed to read mesh artifact: ") + path);
}
size_t offset = 0;
auto readBytes = [&data, &offset](void* destination, size_t byteCount) -> bool {
if (offset + byteCount > data.Size()) {
return false;
}
if (byteCount > 0) {
std::memcpy(destination, data.Data() + offset, byteCount);
offset += byteCount;
}
return true;
};
MeshArtifactHeader header;
input.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!input) {
if (!readBytes(&header, sizeof(header))) {
return LoadResult(Containers::String("Failed to parse mesh artifact header: ") + path);
}
@@ -859,28 +957,29 @@ LoadResult LoadMeshArtifact(const Containers::String& path) {
Containers::Array<MeshSection> sections;
sections.Resize(header.sectionCount);
for (Core::uint32 index = 0; index < header.sectionCount; ++index) {
input.read(reinterpret_cast<char*>(&sections[index]), sizeof(MeshSection));
if (!input) {
if (!readBytes(&sections[index], sizeof(MeshSection))) {
return LoadResult(Containers::String("Failed to read mesh sections: ") + path);
}
}
Containers::Array<Core::uint8> vertexData;
vertexData.Resize(static_cast<size_t>(header.vertexDataSize));
if (header.vertexDataSize > 0) {
input.read(reinterpret_cast<char*>(vertexData.Data()), static_cast<std::streamsize>(header.vertexDataSize));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh vertex data: ") + path);
}
if (header.vertexDataSize > static_cast<Core::uint64>(data.Size() - offset)) {
return LoadResult(Containers::String("Failed to read mesh vertex data: ") + path);
}
if (header.vertexDataSize > 0 &&
!readBytes(vertexData.Data(), static_cast<size_t>(header.vertexDataSize))) {
return LoadResult(Containers::String("Failed to read mesh vertex data: ") + path);
}
Containers::Array<Core::uint8> indexData;
indexData.Resize(static_cast<size_t>(header.indexDataSize));
if (header.indexDataSize > 0) {
input.read(reinterpret_cast<char*>(indexData.Data()), static_cast<std::streamsize>(header.indexDataSize));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh index data: ") + path);
}
if (header.indexDataSize > static_cast<Core::uint64>(data.Size() - offset)) {
return LoadResult(Containers::String("Failed to read mesh index data: ") + path);
}
if (header.indexDataSize > 0 &&
!readBytes(indexData.Data(), static_cast<size_t>(header.indexDataSize))) {
return LoadResult(Containers::String("Failed to read mesh index data: ") + path);
}
mesh->SetVertexData(vertexData.Data(),
@@ -901,11 +1000,31 @@ LoadResult LoadMeshArtifact(const Containers::String& path) {
bounds.SetMinMax(header.boundsMin, header.boundsMax);
mesh->SetBounds(bounds);
auto readBinaryString = [&data, &offset, &readBytes](Containers::String& outValue) -> bool {
Core::uint32 length = 0;
if (!readBytes(&length, sizeof(length))) {
return false;
}
if (length == 0) {
outValue.Clear();
return true;
}
if (offset + length > data.Size()) {
return false;
}
outValue = Containers::String(reinterpret_cast<const char*>(data.Data() + offset), length);
offset += length;
return true;
};
MaterialLoader materialLoader;
for (Core::uint32 materialIndex = 0; materialIndex < header.materialCount; ++materialIndex) {
const Containers::String materialArtifactPath = ReadBinaryString(input);
if (!input) {
Containers::String materialArtifactPath;
if (!readBinaryString(materialArtifactPath)) {
return LoadResult(Containers::String("Failed to read mesh material artifact path: ") + path);
}

View File

@@ -640,37 +640,61 @@ ImportedMeshData ImportSingleMesh(const aiMesh& mesh) {
result.vertices.reserve(mesh.mNumVertices);
result.indices.reserve(mesh.mNumFaces * 3);
const bool hasNormals = mesh.HasNormals();
const bool hasTangentsAndBitangents = mesh.HasTangentsAndBitangents();
const bool hasUv0 = mesh.HasTextureCoords(0);
const bool hasUv1 = mesh.HasTextureCoords(1);
const bool hasVertexColors = mesh.HasVertexColors(0);
const bool useFallbackUv1 = hasUv0 && !hasUv1;
VertexAttribute attributes = VertexAttribute::Position;
if (mesh.HasNormals()) {
if (hasNormals) {
attributes = attributes | VertexAttribute::Normal;
}
if (mesh.HasTangentsAndBitangents()) {
if (hasTangentsAndBitangents) {
attributes = attributes | VertexAttribute::Tangent | VertexAttribute::Bitangent;
}
if (mesh.HasTextureCoords(0)) {
if (hasUv0) {
attributes = attributes | VertexAttribute::UV0;
}
if (hasUv1 || useFallbackUv1) {
attributes = attributes | VertexAttribute::UV1;
}
if (hasVertexColors) {
attributes = attributes | VertexAttribute::Color;
}
for (Core::uint32 vertexIndex = 0; vertexIndex < mesh.mNumVertices; ++vertexIndex) {
StaticMeshVertex vertex;
StaticMeshVertex vertex = {};
const aiVector3D& position = mesh.mVertices[vertexIndex];
vertex.position = Math::Vector3(position.x, position.y, position.z);
if (mesh.HasNormals()) {
if (hasNormals) {
const aiVector3D& normal = mesh.mNormals[vertexIndex];
vertex.normal = Math::Vector3(normal.x, normal.y, normal.z).Normalized();
}
if (mesh.HasTangentsAndBitangents()) {
if (hasTangentsAndBitangents) {
const aiVector3D& tangent = mesh.mTangents[vertexIndex];
const aiVector3D& bitangent = mesh.mBitangents[vertexIndex];
vertex.tangent = Math::Vector3(tangent.x, tangent.y, tangent.z).Normalized();
vertex.bitangent = Math::Vector3(bitangent.x, bitangent.y, bitangent.z).Normalized();
}
if (mesh.HasTextureCoords(0)) {
if (hasUv0) {
const aiVector3D& uv = mesh.mTextureCoords[0][vertexIndex];
vertex.uv0 = Math::Vector2(uv.x, uv.y);
vertex.uv1 = vertex.uv0;
}
if (hasUv1) {
const aiVector3D& uv = mesh.mTextureCoords[1][vertexIndex];
vertex.uv1 = Math::Vector2(uv.x, uv.y);
}
if (hasVertexColors) {
const aiColor4D& color = mesh.mColors[0][vertexIndex];
vertex.color = Math::Vector4(color.r, color.g, color.b, color.a);
}
result.vertices.push_back(vertex);

View File

@@ -0,0 +1,228 @@
#include <XCEngine/Scene/ModelSceneInstantiation.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Scene/Scene.h>
namespace XCEngine {
namespace {
using namespace Components;
using namespace Resources;
Containers::String MakeNodeName(const ModelNode& node, Core::uint32 nodeIndex) {
if (!node.name.Empty()) {
return node.name;
}
return Containers::String(("ModelNode_" + std::to_string(nodeIndex)).c_str());
}
Containers::String MakeMeshObjectName(const Containers::String& nodeName, Core::uint32 meshBindingOffset) {
return Containers::String(
(std::string(nodeName.CStr()) + "_Mesh" + std::to_string(meshBindingOffset)).c_str());
}
AssetRef MakeSubAssetRef(const AssetRef& modelAssetRef, LocalID localID, ResourceType resourceType) {
AssetRef ref;
ref.assetGuid = modelAssetRef.assetGuid;
ref.localID = localID;
ref.resourceType = resourceType;
return ref;
}
void SetErrorMessage(Containers::String* outErrorMessage, const Containers::String& message) {
if (outErrorMessage != nullptr) {
*outErrorMessage = message;
}
}
bool AttachMeshBinding(
GameObject& targetObject,
const Model& model,
const AssetRef& modelAssetRef,
const ModelMeshBinding& meshBinding,
Containers::String* outErrorMessage,
std::vector<GameObject*>* outMeshObjects) {
if (!modelAssetRef.IsValid()) {
SetErrorMessage(outErrorMessage, "Model asset ref is required when instantiating mesh bindings.");
return false;
}
auto* meshFilter = targetObject.GetComponent<MeshFilterComponent>();
if (meshFilter == nullptr) {
meshFilter = targetObject.AddComponent<MeshFilterComponent>();
}
auto* meshRenderer = targetObject.GetComponent<MeshRendererComponent>();
if (meshRenderer == nullptr) {
meshRenderer = targetObject.AddComponent<MeshRendererComponent>();
}
meshFilter->SetMeshAssetRef(MakeSubAssetRef(modelAssetRef, meshBinding.meshLocalID, ResourceType::Mesh));
const auto& materialBindings = model.GetMaterialBindings();
const Core::uint32 materialBindingEnd = meshBinding.materialBindingStart + meshBinding.materialBindingCount;
if (materialBindingEnd > materialBindings.Size()) {
SetErrorMessage(outErrorMessage, "Model mesh binding references material bindings outside the model range.");
return false;
}
for (Core::uint32 materialBindingIndex = meshBinding.materialBindingStart;
materialBindingIndex < materialBindingEnd;
++materialBindingIndex) {
const ModelMaterialBinding& materialBinding = materialBindings[materialBindingIndex];
meshRenderer->SetMaterialAssetRef(
static_cast<size_t>(materialBinding.slotIndex),
MakeSubAssetRef(modelAssetRef, materialBinding.materialLocalID, ResourceType::Material));
}
if (outMeshObjects != nullptr) {
outMeshObjects->push_back(&targetObject);
}
return true;
}
} // namespace
bool InstantiateModelHierarchy(
Components::Scene& scene,
const Resources::Model& model,
const Resources::AssetRef& modelAssetRef,
Components::GameObject* parent,
ModelSceneInstantiationResult* outResult,
Containers::String* outErrorMessage) {
if (!model.IsValid()) {
SetErrorMessage(outErrorMessage, "Model is invalid.");
return false;
}
if (!model.HasRootNode()) {
SetErrorMessage(outErrorMessage, "Model does not have a root node.");
return false;
}
const auto& nodes = model.GetNodes();
const auto& meshBindings = model.GetMeshBindings();
if (model.GetRootNodeIndex() >= nodes.Size()) {
SetErrorMessage(outErrorMessage, "Model root node index is outside the node range.");
return false;
}
ModelSceneInstantiationResult localResult;
localResult.nodeObjects.resize(nodes.Size(), nullptr);
for (Core::uint32 nodeIndex = 0; nodeIndex < nodes.Size(); ++nodeIndex) {
const ModelNode& node = nodes[nodeIndex];
localResult.nodeObjects[nodeIndex] = scene.CreateGameObject(MakeNodeName(node, nodeIndex).CStr(), nullptr);
if (localResult.nodeObjects[nodeIndex] == nullptr) {
SetErrorMessage(outErrorMessage, "Failed to create a model node game object.");
return false;
}
auto* transform = localResult.nodeObjects[nodeIndex]->GetTransform();
transform->SetLocalPosition(node.localPosition);
transform->SetLocalRotation(node.localRotation);
transform->SetLocalScale(node.localScale);
}
for (Core::uint32 nodeIndex = 0; nodeIndex < nodes.Size(); ++nodeIndex) {
const ModelNode& node = nodes[nodeIndex];
GameObject* nodeObject = localResult.nodeObjects[nodeIndex];
if (nodeObject == nullptr) {
continue;
}
if (node.parentIndex >= 0) {
const Core::uint32 parentIndex = static_cast<Core::uint32>(node.parentIndex);
if (parentIndex >= localResult.nodeObjects.size() || localResult.nodeObjects[parentIndex] == nullptr) {
SetErrorMessage(outErrorMessage, "Model node references an invalid parent index.");
return false;
}
nodeObject->SetParent(localResult.nodeObjects[parentIndex], false);
} else if (parent != nullptr) {
nodeObject->SetParent(parent, false);
}
}
for (Core::uint32 nodeIndex = 0; nodeIndex < nodes.Size(); ++nodeIndex) {
const ModelNode& node = nodes[nodeIndex];
GameObject* nodeObject = localResult.nodeObjects[nodeIndex];
if (nodeObject == nullptr || node.meshBindingCount == 0u) {
continue;
}
const Core::uint32 meshBindingEnd = node.meshBindingStart + node.meshBindingCount;
if (meshBindingEnd > meshBindings.Size()) {
SetErrorMessage(outErrorMessage, "Model node references mesh bindings outside the model range.");
return false;
}
if (node.meshBindingCount == 1u) {
if (!AttachMeshBinding(
*nodeObject,
model,
modelAssetRef,
meshBindings[node.meshBindingStart],
outErrorMessage,
&localResult.meshObjects)) {
return false;
}
continue;
}
const Containers::String nodeName = MakeNodeName(node, nodeIndex);
for (Core::uint32 meshBindingIndex = node.meshBindingStart; meshBindingIndex < meshBindingEnd; ++meshBindingIndex) {
GameObject* meshObject = scene.CreateGameObject(
MakeMeshObjectName(nodeName, meshBindingIndex - node.meshBindingStart).CStr(),
nodeObject);
if (meshObject == nullptr) {
SetErrorMessage(outErrorMessage, "Failed to create an auxiliary mesh binding game object.");
return false;
}
if (!AttachMeshBinding(
*meshObject,
model,
modelAssetRef,
meshBindings[meshBindingIndex],
outErrorMessage,
&localResult.meshObjects)) {
return false;
}
}
}
localResult.rootObject = localResult.nodeObjects[model.GetRootNodeIndex()];
if (outResult != nullptr) {
*outResult = std::move(localResult);
}
return true;
}
bool InstantiateModelHierarchy(
Components::Scene& scene,
const Containers::String& modelPath,
Components::GameObject* parent,
ModelSceneInstantiationResult* outResult,
Containers::String* outErrorMessage) {
ResourceManager& resourceManager = ResourceManager::Get();
const ResourceHandle<Resources::Model> modelHandle = resourceManager.Load<Resources::Model>(modelPath);
if (!modelHandle.IsValid()) {
SetErrorMessage(outErrorMessage, Containers::String("Failed to load model asset: ") + modelPath);
return false;
}
AssetRef modelAssetRef;
if (!resourceManager.TryGetAssetRef(modelPath, ResourceType::Model, modelAssetRef)) {
SetErrorMessage(outErrorMessage, Containers::String("Failed to resolve model asset ref: ") + modelPath);
return false;
}
return InstantiateModelHierarchy(scene, *modelHandle, modelAssetRef, parent, outResult, outErrorMessage);
}
} // namespace XCEngine