Split mesh artifacts into material and texture artifacts
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -18,6 +21,107 @@ std::string ToStdString(const Containers::Array<Core::uint8>& data) {
|
||||
return std::string(reinterpret_cast<const char*>(data.Data()), data.Size());
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> ReadMaterialArtifactFileData(const Containers::String& path) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
auto tryRead = [&data](const std::filesystem::path& filePath, bool& opened) {
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
data.Resize(static_cast<size_t>(size));
|
||||
if (!file.read(reinterpret_cast<char*>(data.Data()), size)) {
|
||||
data.Clear();
|
||||
}
|
||||
};
|
||||
|
||||
bool opened = false;
|
||||
const std::filesystem::path inputPath(path.CStr());
|
||||
tryRead(inputPath, opened);
|
||||
if (opened || path.Empty() || inputPath.is_absolute()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (resourceRoot.Empty()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
tryRead(std::filesystem::path(resourceRoot.CStr()) / inputPath, opened);
|
||||
return data;
|
||||
}
|
||||
|
||||
Containers::String NormalizePathString(const std::filesystem::path& path) {
|
||||
return Containers::String(path.lexically_normal().generic_string().c_str());
|
||||
}
|
||||
|
||||
Containers::String ResolveArtifactDependencyPath(const Containers::String& dependencyPath,
|
||||
const Containers::String& ownerArtifactPath) {
|
||||
if (dependencyPath.Empty()) {
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
std::filesystem::path dependencyFsPath(dependencyPath.CStr());
|
||||
if (dependencyFsPath.is_absolute() && std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate =
|
||||
std::filesystem::path(resourceRoot.CStr()) / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
const std::filesystem::path ownerArtifactFsPath(ownerArtifactPath.CStr());
|
||||
if (!ownerArtifactFsPath.is_absolute()) {
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path ownerRelativeCandidate =
|
||||
ownerArtifactFsPath.parent_path() / dependencyFsPath;
|
||||
if (std::filesystem::exists(ownerRelativeCandidate)) {
|
||||
return NormalizePathString(ownerRelativeCandidate);
|
||||
}
|
||||
|
||||
std::filesystem::path current = ownerArtifactFsPath.parent_path();
|
||||
while (!current.empty()) {
|
||||
if (current.filename() == "Library") {
|
||||
const std::filesystem::path projectRoot = current.parent_path();
|
||||
if (!projectRoot.empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate = projectRoot / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const std::filesystem::path parent = current.parent_path();
|
||||
if (parent == current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
size_t SkipWhitespace(const std::string& text, size_t pos) {
|
||||
while (pos < text.size() && std::isspace(static_cast<unsigned char>(text[pos])) != 0) {
|
||||
++pos;
|
||||
@@ -551,6 +655,181 @@ bool MaterialFileExists(const Containers::String& path) {
|
||||
return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath);
|
||||
}
|
||||
|
||||
ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath);
|
||||
|
||||
template<typename T>
|
||||
bool ReadMaterialArtifactValue(const Containers::Array<Core::uint8>& 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;
|
||||
}
|
||||
|
||||
bool ReadMaterialArtifactString(const Containers::Array<Core::uint8>& data,
|
||||
size_t& offset,
|
||||
Containers::String& outValue) {
|
||||
Core::uint32 length = 0;
|
||||
if (!ReadMaterialArtifactValue(data, offset, length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
outValue.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (offset + length > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = Containers::String(
|
||||
std::string(reinterpret_cast<const char*>(data.Data() + offset), length).c_str());
|
||||
offset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
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 LoadMaterialArtifact(const Containers::String& path) {
|
||||
const Containers::Array<Core::uint8> data = ReadMaterialArtifactFileData(path);
|
||||
if (data.Empty()) {
|
||||
return LoadResult("Failed to read material artifact: " + path);
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
MaterialArtifactFileHeader fileHeader;
|
||||
if (!ReadMaterialArtifactValue(data, offset, fileHeader)) {
|
||||
return LoadResult("Failed to parse material artifact header: " + path);
|
||||
}
|
||||
|
||||
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
|
||||
if (magic != "XCMAT01") {
|
||||
return LoadResult("Invalid material artifact magic: " + path);
|
||||
}
|
||||
|
||||
auto material = std::make_unique<Material>();
|
||||
material->m_path = path;
|
||||
material->m_name = path;
|
||||
material->m_guid = ResourceGUID::Generate(path);
|
||||
|
||||
Containers::String materialName;
|
||||
Containers::String materialSourcePath;
|
||||
Containers::String shaderPath;
|
||||
Containers::String shaderPass;
|
||||
if (!ReadMaterialArtifactString(data, offset, materialName) ||
|
||||
!ReadMaterialArtifactString(data, offset, materialSourcePath) ||
|
||||
!ReadMaterialArtifactString(data, offset, shaderPath) ||
|
||||
!ReadMaterialArtifactString(data, offset, shaderPass)) {
|
||||
return LoadResult("Failed to parse material artifact strings: " + path);
|
||||
}
|
||||
|
||||
material->m_name = materialName.Empty() ? path : materialName;
|
||||
if (!materialSourcePath.Empty()) {
|
||||
material->m_path = materialSourcePath;
|
||||
material->m_guid = ResourceGUID::Generate(materialSourcePath);
|
||||
}
|
||||
|
||||
if (!shaderPath.Empty()) {
|
||||
const ResourceHandle<Shader> shaderHandle = LoadShaderHandle(shaderPath);
|
||||
if (shaderHandle.IsValid()) {
|
||||
material->SetShader(shaderHandle);
|
||||
}
|
||||
}
|
||||
if (!shaderPass.Empty()) {
|
||||
material->SetShaderPass(shaderPass);
|
||||
}
|
||||
|
||||
MaterialArtifactHeader header;
|
||||
if (!ReadMaterialArtifactValue(data, offset, header)) {
|
||||
return LoadResult("Failed to parse material artifact body: " + path);
|
||||
}
|
||||
|
||||
material->SetRenderQueue(header.renderQueue);
|
||||
material->SetRenderState(header.renderState);
|
||||
|
||||
for (Core::uint32 tagIndex = 0; tagIndex < header.tagCount; ++tagIndex) {
|
||||
Containers::String tagName;
|
||||
Containers::String tagValue;
|
||||
if (!ReadMaterialArtifactString(data, offset, tagName) ||
|
||||
!ReadMaterialArtifactString(data, offset, tagValue)) {
|
||||
return LoadResult("Failed to read material artifact tags: " + path);
|
||||
}
|
||||
|
||||
material->SetTag(tagName, tagValue);
|
||||
}
|
||||
|
||||
for (Core::uint32 propertyIndex = 0; propertyIndex < header.propertyCount; ++propertyIndex) {
|
||||
Containers::String propertyName;
|
||||
MaterialPropertyArtifact propertyArtifact;
|
||||
if (!ReadMaterialArtifactString(data, offset, propertyName) ||
|
||||
!ReadMaterialArtifactValue(data, offset, propertyArtifact)) {
|
||||
return LoadResult("Failed to read material artifact properties: " + path);
|
||||
}
|
||||
|
||||
MaterialProperty property;
|
||||
property.name = propertyName;
|
||||
property.type = static_cast<MaterialPropertyType>(propertyArtifact.propertyType);
|
||||
property.value = propertyArtifact.value;
|
||||
ApplyMaterialProperty(*material, property);
|
||||
}
|
||||
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < header.textureBindingCount; ++bindingIndex) {
|
||||
Containers::String bindingName;
|
||||
Containers::String texturePath;
|
||||
if (!ReadMaterialArtifactString(data, offset, bindingName) ||
|
||||
!ReadMaterialArtifactString(data, offset, texturePath)) {
|
||||
return LoadResult("Failed to read material artifact texture bindings: " + path);
|
||||
}
|
||||
|
||||
if (!texturePath.Empty()) {
|
||||
material->SetTexturePath(bindingName, ResolveArtifactDependencyPath(texturePath, path));
|
||||
}
|
||||
}
|
||||
|
||||
material->m_isValid = true;
|
||||
material->RecalculateMemorySize();
|
||||
return LoadResult(material.release());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MaterialLoader::MaterialLoader() = default;
|
||||
@@ -562,6 +841,7 @@ Containers::Array<Containers::String> MaterialLoader::GetSupportedExtensions() c
|
||||
extensions.PushBack("mat");
|
||||
extensions.PushBack("material");
|
||||
extensions.PushBack("json");
|
||||
extensions.PushBack("xcmat");
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@@ -570,8 +850,8 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path);
|
||||
return ext == "mat" || ext == "material" || ext == "json";
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
return ext == "mat" || ext == "material" || ext == "json" || ext == "xcmat";
|
||||
}
|
||||
|
||||
LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
@@ -581,6 +861,11 @@ LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSett
|
||||
return CreateBuiltinMaterialResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
if (ext == "xcmat") {
|
||||
return LoadMaterialArtifact(path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> data = ReadFileData(path);
|
||||
Material* material = new Material();
|
||||
material->m_path = path;
|
||||
|
||||
Reference in New Issue
Block a user