Files
XCEngine/engine/src/Components/MeshRendererComponent.cpp

439 lines
16 KiB
C++
Raw Normal View History

#include "Components/MeshRendererComponent.h"
2026-04-02 18:50:41 +08:00
#include "Components/GameObject.h"
#include "Core/Asset/ResourceManager.h"
2026-04-02 18:50:41 +08:00
#include "Debug/Logger.h"
#include <sstream>
namespace XCEngine {
namespace Components {
namespace {
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
2026-04-02 18:50:41 +08:00
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<int>(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<Resources::LocalID>(std::stoull(value.substr(firstComma + 1, secondComma - firstComma - 1)));
outRef.resourceType = static_cast<Resources::ResourceType>(std::stoi(value.substr(secondComma + 1)));
return outRef.IsValid();
}
std::vector<std::string> SplitMaterialPaths(const std::string& value) {
std::vector<std::string> 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<Resources::AssetRef> SplitMaterialRefs(const std::string& value) {
std::vector<Resources::AssetRef> 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;
}
2026-04-02 18:50:41 +08:00
std::string JoinMaterialPaths(const std::vector<std::string>& 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<Resources::AssetRef>& 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<std::string>& 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 "<no-owner>";
}
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
2026-04-02 18:50:41 +08:00
struct MeshRendererComponent::PendingMaterialLoadState {
Resources::LoadResult result;
bool completed = false;
};
Resources::Material* MeshRendererComponent::GetMaterial(size_t index) const {
2026-04-02 18:50:41 +08:00
const_cast<MeshRendererComponent*>(this)->ResolvePendingMaterials();
return index < m_materials.size() ? m_materials[index].Get() : nullptr;
}
const Resources::ResourceHandle<Resources::Material>& MeshRendererComponent::GetMaterialHandle(size_t index) const {
2026-04-02 18:50:41 +08:00
const_cast<MeshRendererComponent*>(this)->ResolvePendingMaterials();
static const Resources::ResourceHandle<Resources::Material> 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);
2026-04-02 18:50:41 +08:00
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<Resources::Material>(materialPath.c_str());
if (!Resources::ResourceManager::Get().TryGetAssetRef(materialPath.c_str(), Resources::ResourceType::Material, m_materialRefs[index])) {
m_materialRefs[index].Reset();
}
2026-04-02 18:50:41 +08:00
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<Resources::Material>& material) {
EnsureMaterialSlot(index);
2026-04-02 18:50:41 +08:00
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<Resources::Material>(material));
}
void MeshRendererComponent::SetMaterials(const std::vector<Resources::ResourceHandle<Resources::Material>>& materials) {
m_materials = materials;
m_materialPaths.resize(materials.size());
m_materialRefs.resize(materials.size());
2026-04-02 18:50:41 +08:00
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();
2026-04-02 18:50:41 +08:00
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<Resources::AssetRef> 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());
2026-04-02 18:50:41 +08:00
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<uint32_t>(std::stoul(value));
}
}
if (pendingMaterialRefs.size() > m_materialPaths.size()) {
m_materialPaths.resize(pendingMaterialRefs.size());
m_materials.resize(pendingMaterialRefs.size());
m_materialRefs.resize(pendingMaterialRefs.size());
2026-04-02 18:50:41 +08:00
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) {
2026-04-02 18:50:41 +08:00
bool restoredOrQueued = false;
if (i < pendingMaterialRefs.size() && pendingMaterialRefs[i].IsValid()) {
m_materialRefs[i] = pendingMaterialRefs[i];
2026-04-02 18:50:41 +08:00
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<Resources::Material>(pendingMaterialRefs[i]);
}
if (m_materials[i].Get() != nullptr) {
m_materialPaths[i] = MaterialPathFromHandle(m_materials[i]);
2026-04-02 18:50:41 +08:00
restoredOrQueued = true;
}
if (restoredOrQueued) {
continue;
}
}
2026-04-02 18:50:41 +08:00
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]);
}
}
2026-04-02 18:50:41 +08:00
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<PendingMaterialLoadState>();
if (ShouldTraceMaterialPath(materialPath)) {
TraceMeshRenderer(
*this,
std::string("BeginAsyncMaterialLoad slot=") + std::to_string(index) +
" path=" + materialPath);
}
std::weak_ptr<PendingMaterialLoadState> weakState = m_pendingMaterialLoads[index];
Resources::ResourceManager::Get().LoadAsync(materialPath.c_str(),
Resources::ResourceType::Material,
[weakState](Resources::LoadResult result) {
if (std::shared_ptr<PendingMaterialLoadState> 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<PendingMaterialLoadState> 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<Resources::Material>(
static_cast<Resources::Material*>(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);
2026-04-02 18:50:41 +08:00
}
if (index >= m_materialPaths.size()) {
m_materialPaths.resize(index + 1);
2026-04-02 18:50:41 +08:00
}
if (index >= m_materialRefs.size()) {
m_materialRefs.resize(index + 1);
}
2026-04-02 18:50:41 +08:00
if (index >= m_pendingMaterialLoads.size()) {
m_pendingMaterialLoads.resize(index + 1);
}
}
std::string MeshRendererComponent::MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Material>& material) {
return material.Get() != nullptr ? ToStdString(material->GetPath()) : std::string();
}
} // namespace Components
} // namespace XCEngine