Implement initial Unity-style asset library cache
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
@@ -14,6 +16,7 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -507,6 +510,218 @@ void ProcessNode(const aiNode& node,
|
||||
}
|
||||
}
|
||||
|
||||
Containers::String ReadBinaryString(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());
|
||||
}
|
||||
|
||||
void ApplyMaterialProperty(Material& material, const MaterialProperty& property) {
|
||||
switch (property.type) {
|
||||
case MaterialPropertyType::Float:
|
||||
material.SetFloat(property.name, property.value.floatValue[0]);
|
||||
break;
|
||||
case MaterialPropertyType::Float2:
|
||||
material.SetFloat2(property.name,
|
||||
Math::Vector2(property.value.floatValue[0], property.value.floatValue[1]));
|
||||
break;
|
||||
case MaterialPropertyType::Float3:
|
||||
material.SetFloat3(property.name,
|
||||
Math::Vector3(property.value.floatValue[0], property.value.floatValue[1], property.value.floatValue[2]));
|
||||
break;
|
||||
case MaterialPropertyType::Float4:
|
||||
material.SetFloat4(property.name,
|
||||
Math::Vector4(property.value.floatValue[0],
|
||||
property.value.floatValue[1],
|
||||
property.value.floatValue[2],
|
||||
property.value.floatValue[3]));
|
||||
break;
|
||||
case MaterialPropertyType::Int:
|
||||
material.SetInt(property.name, property.value.intValue[0]);
|
||||
break;
|
||||
case MaterialPropertyType::Bool:
|
||||
material.SetBool(property.name, property.value.boolValue);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LoadResult LoadMeshArtifact(const Containers::String& path) {
|
||||
std::ifstream input(path.CStr(), std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read mesh artifact: ") + path);
|
||||
}
|
||||
|
||||
MeshArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to parse mesh artifact header: ") + path);
|
||||
}
|
||||
|
||||
const std::string magic(header.magic, header.magic + 7);
|
||||
if (magic != "XCMESH1") {
|
||||
return LoadResult(Containers::String("Invalid mesh artifact magic: ") + path);
|
||||
}
|
||||
|
||||
auto mesh = std::make_unique<Mesh>();
|
||||
|
||||
IResource::ConstructParams params;
|
||||
params.name = GetResourceNameFromPath(path);
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = 0;
|
||||
mesh->Initialize(params);
|
||||
|
||||
Containers::Array<MeshSection> sections;
|
||||
sections.Resize(header.sectionCount);
|
||||
for (Core::uint32 index = 0; index < header.sectionCount; ++index) {
|
||||
input.read(reinterpret_cast<char*>(§ions[index]), sizeof(MeshSection));
|
||||
if (!input) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
mesh->SetVertexData(vertexData.Data(),
|
||||
vertexData.Size(),
|
||||
header.vertexCount,
|
||||
header.vertexStride,
|
||||
static_cast<VertexAttribute>(header.vertexAttributes));
|
||||
mesh->SetIndexData(indexData.Data(),
|
||||
indexData.Size(),
|
||||
header.indexCount,
|
||||
header.use32BitIndex != 0);
|
||||
|
||||
for (const MeshSection& section : sections) {
|
||||
mesh->AddSection(section);
|
||||
}
|
||||
|
||||
Math::Bounds bounds;
|
||||
bounds.SetMinMax(header.boundsMin, header.boundsMax);
|
||||
mesh->SetBounds(bounds);
|
||||
|
||||
std::vector<Containers::String> textureFiles;
|
||||
textureFiles.reserve(header.textureCount);
|
||||
for (Core::uint32 textureIndex = 0; textureIndex < header.textureCount; ++textureIndex) {
|
||||
textureFiles.push_back(ReadBinaryString(input));
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, Texture*> loadedTextures;
|
||||
TextureLoader textureLoader;
|
||||
const std::filesystem::path artifactDirectory = std::filesystem::path(path.CStr()).parent_path();
|
||||
|
||||
for (Core::uint32 materialIndex = 0; materialIndex < header.materialCount; ++materialIndex) {
|
||||
Core::uint32 materialPresent = 0;
|
||||
input.read(reinterpret_cast<char*>(&materialPresent), sizeof(materialPresent));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read mesh material flag: ") + path);
|
||||
}
|
||||
|
||||
if (materialPresent == 0) {
|
||||
mesh->AddMaterial(nullptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* material = new Material();
|
||||
material->m_name = ReadBinaryString(input);
|
||||
material->m_path = ReadBinaryString(input);
|
||||
material->m_guid = ResourceGUID::Generate(material->m_path);
|
||||
material->m_isValid = true;
|
||||
material->SetShaderPass(ReadBinaryString(input));
|
||||
|
||||
MaterialArtifactHeader materialHeader;
|
||||
input.read(reinterpret_cast<char*>(&materialHeader), sizeof(materialHeader));
|
||||
if (!input) {
|
||||
delete material;
|
||||
return LoadResult(Containers::String("Failed to read material artifact header: ") + path);
|
||||
}
|
||||
|
||||
material->SetRenderQueue(materialHeader.renderQueue);
|
||||
material->SetRenderState(materialHeader.renderState);
|
||||
|
||||
for (Core::uint32 tagIndex = 0; tagIndex < materialHeader.tagCount; ++tagIndex) {
|
||||
material->SetTag(ReadBinaryString(input), ReadBinaryString(input));
|
||||
}
|
||||
|
||||
for (Core::uint32 propertyIndex = 0; propertyIndex < materialHeader.propertyCount; ++propertyIndex) {
|
||||
MaterialProperty property;
|
||||
property.name = ReadBinaryString(input);
|
||||
|
||||
MaterialPropertyArtifact propertyArtifact;
|
||||
input.read(reinterpret_cast<char*>(&propertyArtifact), sizeof(propertyArtifact));
|
||||
if (!input) {
|
||||
delete material;
|
||||
return LoadResult(Containers::String("Failed to read material property: ") + path);
|
||||
}
|
||||
|
||||
property.type = static_cast<MaterialPropertyType>(propertyArtifact.propertyType);
|
||||
property.value = propertyArtifact.value;
|
||||
ApplyMaterialProperty(*material, property);
|
||||
}
|
||||
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < materialHeader.textureBindingCount; ++bindingIndex) {
|
||||
const Containers::String bindingName = ReadBinaryString(input);
|
||||
const Containers::String textureFile = ReadBinaryString(input);
|
||||
if (textureFile.Empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string textureKey(textureFile.CStr());
|
||||
Texture* texture = nullptr;
|
||||
auto textureIt = loadedTextures.find(textureKey);
|
||||
if (textureIt != loadedTextures.end()) {
|
||||
texture = textureIt->second;
|
||||
} else {
|
||||
const Containers::String texturePath =
|
||||
Containers::String((artifactDirectory / textureFile.CStr()).lexically_normal().string().c_str());
|
||||
LoadResult textureResult = textureLoader.Load(texturePath);
|
||||
if (textureResult && textureResult.resource != nullptr) {
|
||||
texture = static_cast<Texture*>(textureResult.resource);
|
||||
loadedTextures.emplace(textureKey, texture);
|
||||
mesh->AddTexture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
if (texture != nullptr) {
|
||||
material->SetTexture(bindingName, ResourceHandle<Texture>(texture));
|
||||
}
|
||||
}
|
||||
|
||||
material->RecalculateMemorySize();
|
||||
mesh->AddMaterial(material);
|
||||
}
|
||||
|
||||
return LoadResult(mesh.release());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MeshLoader::MeshLoader() = default;
|
||||
@@ -520,26 +735,45 @@ Containers::Array<Containers::String> MeshLoader::GetSupportedExtensions() const
|
||||
extensions.PushBack(Containers::String("glb"));
|
||||
extensions.PushBack(Containers::String("dae"));
|
||||
extensions.PushBack(Containers::String("stl"));
|
||||
extensions.PushBack(Containers::String("xcmesh"));
|
||||
return extensions;
|
||||
}
|
||||
|
||||
bool MeshLoader::CanLoad(const Containers::String& path) const {
|
||||
if (IsBuiltinMeshPath(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
return ext == "fbx" || ext == "obj" || ext == "gltf" ||
|
||||
ext == "glb" || ext == "dae" || ext == "stl";
|
||||
ext == "glb" || ext == "dae" || ext == "stl" ||
|
||||
ext == "xcmesh";
|
||||
}
|
||||
|
||||
LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
if (IsBuiltinMeshPath(path)) {
|
||||
return CreateBuiltinMeshResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported mesh format: ") + ext);
|
||||
}
|
||||
|
||||
std::ifstream file(path.CStr(), std::ios::binary);
|
||||
if (ext == "xcmesh") {
|
||||
return LoadMeshArtifact(path);
|
||||
}
|
||||
|
||||
Containers::String resolvedPath = path;
|
||||
if (!std::filesystem::path(path.CStr()).is_absolute()) {
|
||||
resolvedPath = ResourceManager::Get().ResolvePath(path);
|
||||
}
|
||||
|
||||
std::ifstream file(resolvedPath.CStr(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read file: ") + path);
|
||||
return LoadResult(Containers::String("Failed to read file: ") + resolvedPath);
|
||||
}
|
||||
|
||||
MeshImportSettings defaultSettings;
|
||||
@@ -549,7 +783,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
|
||||
}
|
||||
|
||||
Assimp::Importer importer;
|
||||
const aiScene* scene = importer.ReadFile(path.CStr(), BuildPostProcessFlags(*resolvedSettings));
|
||||
const aiScene* scene = importer.ReadFile(resolvedPath.CStr(), BuildPostProcessFlags(*resolvedSettings));
|
||||
if (scene == nullptr || scene->mRootNode == nullptr || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0) {
|
||||
const char* errorText = importer.GetErrorString();
|
||||
return LoadResult(Containers::String("Assimp failed to load mesh: ") +
|
||||
@@ -577,7 +811,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
|
||||
auto* mesh = new Mesh();
|
||||
|
||||
IResource::ConstructParams params;
|
||||
const std::string fileName = std::filesystem::path(path.CStr()).filename().string();
|
||||
const std::string fileName = std::filesystem::path(resolvedPath.CStr()).filename().string();
|
||||
params.name = Containers::String(fileName.c_str());
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
|
||||
Reference in New Issue
Block a user