#include "Components/MeshRendererComponent.h" #include "Components/GameObject.h" #include "Core/Asset/ResourceManager.h" #include "Debug/Logger.h" #include namespace XCEngine { namespace Components { namespace { std::string ToStdString(const Containers::String& value) { return std::string(value.CStr()); } bool ShouldTraceMaterialPath(const std::string& path) { return path.rfind("builtin://", 0) == 0 || path.find("backpack") != std::string::npos || path.find("New Material.mat") != std::string::npos; } std::string EncodeAssetRef(const Resources::AssetRef& assetRef) { if (!assetRef.IsValid()) { return std::string(); } return ToStdString(assetRef.assetGuid.ToString()) + "," + std::to_string(assetRef.localID) + "," + std::to_string(static_cast(assetRef.resourceType)); } bool TryDecodeAssetRef(const std::string& value, Resources::AssetRef& outRef) { const size_t firstComma = value.find(','); const size_t secondComma = firstComma == std::string::npos ? std::string::npos : value.find(',', firstComma + 1); if (firstComma == std::string::npos || secondComma == std::string::npos) { return false; } outRef.assetGuid = Resources::AssetGUID::ParseOrDefault(Containers::String(value.substr(0, firstComma).c_str())); outRef.localID = static_cast(std::stoull(value.substr(firstComma + 1, secondComma - firstComma - 1))); outRef.resourceType = static_cast(std::stoi(value.substr(secondComma + 1))); return outRef.IsValid(); } std::vector SplitMaterialPaths(const std::string& value) { std::vector paths; if (value.empty()) { return paths; } size_t start = 0; while (true) { const size_t separator = value.find('|', start); if (separator == std::string::npos) { paths.push_back(value.substr(start)); break; } paths.push_back(value.substr(start, separator - start)); start = separator + 1; } return paths; } std::vector SplitMaterialRefs(const std::string& value) { std::vector refs; if (value.empty()) { return refs; } size_t start = 0; while (true) { const size_t separator = value.find('|', start); const std::string token = separator == std::string::npos ? value.substr(start) : value.substr(start, separator - start); Resources::AssetRef ref; TryDecodeAssetRef(token, ref); refs.push_back(ref); if (separator == std::string::npos) { break; } start = separator + 1; } return refs; } std::string JoinMaterialPaths(const std::vector& values) { std::ostringstream stream; for (size_t index = 0; index < values.size(); ++index) { if (index > 0) { stream << "|"; } stream << values[index]; } return stream.str(); } std::string JoinMaterialRefs(const std::vector& values) { std::ostringstream stream; for (size_t index = 0; index < values.size(); ++index) { if (index > 0) { stream << "|"; } stream << EncodeAssetRef(values[index]); } return stream.str(); } bool ShouldTraceAnyMaterialPath(const std::vector& values) { for (const std::string& value : values) { if (ShouldTraceMaterialPath(value)) { return true; } } return false; } std::string DescribeOwner(const MeshRendererComponent& component) { const GameObject* owner = component.GetGameObject(); if (owner == nullptr) { return ""; } return std::string("id=") + std::to_string(owner->GetID()) + ",name=" + owner->GetName(); } void TraceMeshRenderer(const MeshRendererComponent& component, const std::string& message) { Debug::Logger::Get().Info( Debug::LogCategory::FileSystem, Containers::String(("[MeshRenderer] owner={" + DescribeOwner(component) + "} " + message).c_str())); } } // namespace struct MeshRendererComponent::PendingMaterialLoadState { Resources::LoadResult result; bool completed = false; }; Resources::Material* MeshRendererComponent::GetMaterial(size_t index) const { const_cast(this)->ResolvePendingMaterials(); return index < m_materials.size() ? m_materials[index].Get() : nullptr; } const Resources::ResourceHandle& MeshRendererComponent::GetMaterialHandle(size_t index) const { const_cast(this)->ResolvePendingMaterials(); static const Resources::ResourceHandle kNullHandle; return index < m_materials.size() ? m_materials[index] : kNullHandle; } const std::string& MeshRendererComponent::GetMaterialPath(size_t index) const { static const std::string kEmptyPath; return index < m_materialPaths.size() ? m_materialPaths[index] : kEmptyPath; } void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& materialPath) { EnsureMaterialSlot(index); m_pendingMaterialLoads[index].reset(); m_materialPaths[index] = materialPath; if (materialPath.empty()) { m_materials[index].Reset(); m_materialRefs[index].Reset(); return; } m_materials[index] = Resources::ResourceManager::Get().Load(materialPath.c_str()); if (!Resources::ResourceManager::Get().TryGetAssetRef(materialPath.c_str(), Resources::ResourceType::Material, m_materialRefs[index])) { m_materialRefs[index].Reset(); } if (ShouldTraceMaterialPath(materialPath)) { TraceMeshRenderer( *this, std::string("SetMaterialPath slot=") + std::to_string(index) + " path=" + materialPath + " ref=" + EncodeAssetRef(m_materialRefs[index]) + " loaded=" + (m_materials[index].Get() != nullptr ? "1" : "0")); } } void MeshRendererComponent::SetMaterial(size_t index, const Resources::ResourceHandle& material) { EnsureMaterialSlot(index); m_pendingMaterialLoads[index].reset(); m_materials[index] = material; m_materialPaths[index] = MaterialPathFromHandle(material); if (m_materialPaths[index].empty() || !Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[index].c_str(), Resources::ResourceType::Material, m_materialRefs[index])) { m_materialRefs[index].Reset(); } } void MeshRendererComponent::SetMaterial(size_t index, Resources::Material* material) { SetMaterial(index, Resources::ResourceHandle(material)); } void MeshRendererComponent::SetMaterials(const std::vector>& materials) { m_materials = materials; m_materialPaths.resize(materials.size()); m_materialRefs.resize(materials.size()); m_pendingMaterialLoads.clear(); m_pendingMaterialLoads.resize(materials.size()); for (size_t i = 0; i < materials.size(); ++i) { m_materialPaths[i] = MaterialPathFromHandle(materials[i]); if (m_materialPaths[i].empty() || !Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[i].c_str(), Resources::ResourceType::Material, m_materialRefs[i])) { m_materialRefs[i].Reset(); } } } void MeshRendererComponent::ClearMaterials() { m_materials.clear(); m_materialPaths.clear(); m_materialRefs.clear(); m_pendingMaterialLoads.clear(); } void MeshRendererComponent::Serialize(std::ostream& os) const { os << "materials="; for (size_t i = 0; i < m_materialPaths.size(); ++i) { if (i > 0) { os << "|"; } os << m_materialPaths[i]; } os << ";"; os << "materialRefs="; for (size_t i = 0; i < m_materialRefs.size(); ++i) { if (i > 0) { os << "|"; } os << EncodeAssetRef(m_materialRefs[i]); } os << ";"; os << "castShadows=" << (m_castShadows ? 1 : 0) << ";"; os << "receiveShadows=" << (m_receiveShadows ? 1 : 0) << ";"; os << "renderLayer=" << m_renderLayer << ";"; } void MeshRendererComponent::Deserialize(std::istream& is) { ClearMaterials(); m_castShadows = true; m_receiveShadows = true; m_renderLayer = 0; std::string token; std::vector pendingMaterialRefs; while (std::getline(is, token, ';')) { if (token.empty()) { continue; } const size_t eqPos = token.find('='); if (eqPos == std::string::npos) { continue; } const std::string key = token.substr(0, eqPos); const std::string value = token.substr(eqPos + 1); if (key == "materials") { m_materialPaths = SplitMaterialPaths(value); m_materials.resize(m_materialPaths.size()); m_materialRefs.resize(m_materialPaths.size()); m_pendingMaterialLoads.resize(m_materialPaths.size()); } else if (key == "materialRefs") { pendingMaterialRefs = SplitMaterialRefs(value); } else if (key == "castShadows") { m_castShadows = (std::stoi(value) != 0); } else if (key == "receiveShadows") { m_receiveShadows = (std::stoi(value) != 0); } else if (key == "renderLayer") { m_renderLayer = static_cast(std::stoul(value)); } } if (pendingMaterialRefs.size() > m_materialPaths.size()) { m_materialPaths.resize(pendingMaterialRefs.size()); m_materials.resize(pendingMaterialRefs.size()); m_materialRefs.resize(pendingMaterialRefs.size()); m_pendingMaterialLoads.resize(pendingMaterialRefs.size()); } if (ShouldTraceAnyMaterialPath(m_materialPaths)) { TraceMeshRenderer( *this, std::string("Deserialize paths=") + JoinMaterialPaths(m_materialPaths) + " refs=" + JoinMaterialRefs(pendingMaterialRefs) + " deferred=" + (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled() ? "1" : "0")); } for (size_t i = 0; i < m_materialPaths.size(); ++i) { bool restoredOrQueued = false; if (i < pendingMaterialRefs.size() && pendingMaterialRefs[i].IsValid()) { m_materialRefs[i] = pendingMaterialRefs[i]; if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { Containers::String resolvedPath; if (Resources::ResourceManager::Get().TryResolveAssetPath(pendingMaterialRefs[i], resolvedPath)) { m_materialPaths[i] = ToStdString(resolvedPath); if (ShouldTraceMaterialPath(m_materialPaths[i])) { TraceMeshRenderer( *this, std::string("Resolved materialRef slot=") + std::to_string(i) + " path=" + m_materialPaths[i]); } BeginAsyncMaterialLoad(i, m_materialPaths[i]); restoredOrQueued = true; } } if (!restoredOrQueued) { m_materials[i] = Resources::ResourceManager::Get().Load(pendingMaterialRefs[i]); } if (m_materials[i].Get() != nullptr) { m_materialPaths[i] = MaterialPathFromHandle(m_materials[i]); restoredOrQueued = true; } if (restoredOrQueued) { continue; } } if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { if (!m_materialPaths[i].empty()) { if (!m_materialRefs[i].IsValid() && !Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[i].c_str(), Resources::ResourceType::Material, m_materialRefs[i])) { m_materialRefs[i].Reset(); } BeginAsyncMaterialLoad(i, m_materialPaths[i]); } continue; } SetMaterialPath(i, m_materialPaths[i]); } } void MeshRendererComponent::BeginAsyncMaterialLoad(size_t index, const std::string& materialPath) { EnsureMaterialSlot(index); if (materialPath.empty()) { m_pendingMaterialLoads[index].reset(); m_materials[index].Reset(); return; } m_materials[index].Reset(); m_pendingMaterialLoads[index] = std::make_shared(); if (ShouldTraceMaterialPath(materialPath)) { TraceMeshRenderer( *this, std::string("BeginAsyncMaterialLoad slot=") + std::to_string(index) + " path=" + materialPath); } std::weak_ptr weakState = m_pendingMaterialLoads[index]; Resources::ResourceManager::Get().LoadAsync(materialPath.c_str(), Resources::ResourceType::Material, [weakState](Resources::LoadResult result) { if (std::shared_ptr state = weakState.lock()) { state->result = std::move(result); state->completed = true; } }); } void MeshRendererComponent::ResolvePendingMaterials() { for (size_t index = 0; index < m_pendingMaterialLoads.size(); ++index) { if (!m_pendingMaterialLoads[index] || !m_pendingMaterialLoads[index]->completed) { continue; } std::shared_ptr completedLoad = std::move(m_pendingMaterialLoads[index]); m_pendingMaterialLoads[index].reset(); if (!completedLoad->result || completedLoad->result.resource == nullptr) { if (ShouldTraceMaterialPath(m_materialPaths[index])) { TraceMeshRenderer( *this, std::string("ResolvePendingMaterial failed slot=") + std::to_string(index) + " path=" + m_materialPaths[index] + " error=" + ToStdString(completedLoad->result.errorMessage)); } continue; } m_materials[index] = Resources::ResourceHandle( static_cast(completedLoad->result.resource)); if (m_materials[index].Get() == nullptr) { continue; } m_materialPaths[index] = MaterialPathFromHandle(m_materials[index]); if (!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[index].c_str(), Resources::ResourceType::Material, m_materialRefs[index])) { m_materialRefs[index].Reset(); } if (ShouldTraceMaterialPath(m_materialPaths[index])) { TraceMeshRenderer( *this, std::string("ResolvePendingMaterial success slot=") + std::to_string(index) + " path=" + m_materialPaths[index] + " ref=" + EncodeAssetRef(m_materialRefs[index])); } } } void MeshRendererComponent::EnsureMaterialSlot(size_t index) { if (index >= m_materials.size()) { m_materials.resize(index + 1); } if (index >= m_materialPaths.size()) { m_materialPaths.resize(index + 1); } if (index >= m_materialRefs.size()) { m_materialRefs.resize(index + 1); } if (index >= m_pendingMaterialLoads.size()) { m_pendingMaterialLoads.resize(index + 1); } } std::string MeshRendererComponent::MaterialPathFromHandle(const Resources::ResourceHandle& material) { return material.Get() != nullptr ? ToStdString(material->GetPath()) : std::string(); } } // namespace Components } // namespace XCEngine