#include "Components/CameraComponent.h" #include "Core/Asset/ResourceManager.h" #include "Resources/Material/Material.h" #include #include #include namespace XCEngine { namespace Components { namespace { std::string ToStdString(const Containers::String& value) { return std::string(value.CStr()); } bool HasVirtualPathScheme(const std::string& path) { return path.find("://") != 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::string EncodeVector4(const Math::Vector4& value) { std::ostringstream stream; stream << value.x << "," << value.y << "," << value.z << "," << value.w; return stream.str(); } bool TryParseVector4(const std::string& value, Math::Vector4& outValue) { std::string normalized = value; std::replace(normalized.begin(), normalized.end(), ',', ' '); std::istringstream stream(normalized); stream >> outValue.x >> outValue.y >> outValue.z >> outValue.w; return !stream.fail(); } Rendering::CameraPostProcessStack BuildColorScalePostProcessStack( const std::vector& values) { Rendering::CameraPostProcessStack passes; passes.reserve(values.size()); for (const Math::Vector4& value : values) { passes.push_back(Rendering::CameraPostProcessPassDesc::MakeColorScale(value)); } return passes; } size_t FindFirstColorScalePassIndex(const Rendering::CameraPostProcessStack& passes) { for (size_t index = 0; index < passes.size(); ++index) { if (passes[index].type == Rendering::CameraPostProcessPassType::ColorScale) { return index; } } return passes.size(); } } // namespace void CameraComponent::SetPostProcessPasses(const Rendering::CameraPostProcessStack& values) { m_postProcessPasses = values; for (const Rendering::CameraPostProcessPassDesc& pass : m_postProcessPasses) { if (pass.type == Rendering::CameraPostProcessPassType::ColorScale) { m_colorScalePostProcessDefaultScale = pass.colorScale.scale; break; } } } void CameraComponent::AddPostProcessPass(const Rendering::CameraPostProcessPassDesc& value) { if (!value.IsValid()) { return; } if (value.type == Rendering::CameraPostProcessPassType::ColorScale && FindFirstColorScalePassIndex(m_postProcessPasses) == m_postProcessPasses.size()) { m_colorScalePostProcessDefaultScale = value.colorScale.scale; } m_postProcessPasses.push_back(value); } bool CameraComponent::IsColorScalePostProcessEnabled() const { return FindFirstColorScalePassIndex(m_postProcessPasses) != m_postProcessPasses.size(); } void CameraComponent::SetColorScalePostProcessEnabled(bool value) { if (value) { if (!IsColorScalePostProcessEnabled()) { m_postProcessPasses.push_back( Rendering::CameraPostProcessPassDesc::MakeColorScale( m_colorScalePostProcessDefaultScale)); } return; } ClearColorScalePostProcessPasses(); } const Math::Vector4& CameraComponent::GetColorScalePostProcessScale() const { const size_t firstColorScalePassIndex = FindFirstColorScalePassIndex(m_postProcessPasses); return firstColorScalePassIndex == m_postProcessPasses.size() ? m_colorScalePostProcessDefaultScale : m_postProcessPasses[firstColorScalePassIndex].colorScale.scale; } void CameraComponent::SetColorScalePostProcessScale(const Math::Vector4& value) { m_colorScalePostProcessDefaultScale = value; const size_t firstColorScalePassIndex = FindFirstColorScalePassIndex(m_postProcessPasses); if (firstColorScalePassIndex != m_postProcessPasses.size()) { m_postProcessPasses[firstColorScalePassIndex].colorScale.scale = value; } } std::vector CameraComponent::GetColorScalePostProcessPasses() const { std::vector values; values.reserve(m_postProcessPasses.size()); for (const Rendering::CameraPostProcessPassDesc& pass : m_postProcessPasses) { if (pass.type == Rendering::CameraPostProcessPassType::ColorScale) { values.push_back(pass.colorScale.scale); } } return values; } void CameraComponent::SetColorScalePostProcessPasses(const std::vector& values) { SetPostProcessPasses(BuildColorScalePostProcessStack(values)); if (!values.empty()) { m_colorScalePostProcessDefaultScale = values.front(); } } void CameraComponent::AddColorScalePostProcessPass(const Math::Vector4& value) { if (!IsColorScalePostProcessEnabled()) { m_colorScalePostProcessDefaultScale = value; } m_postProcessPasses.push_back(Rendering::CameraPostProcessPassDesc::MakeColorScale(value)); } void CameraComponent::ClearColorScalePostProcessPasses() { m_postProcessPasses.erase( std::remove_if( m_postProcessPasses.begin(), m_postProcessPasses.end(), [](const Rendering::CameraPostProcessPassDesc& pass) { return pass.type == Rendering::CameraPostProcessPassType::ColorScale; }), m_postProcessPasses.end()); } void CameraComponent::SetFieldOfView(float value) { m_fieldOfView = std::clamp(value, 1.0f, 179.0f); } void CameraComponent::SetOrthographicSize(float value) { m_orthographicSize = std::max(0.001f, value); } void CameraComponent::SetNearClipPlane(float value) { m_nearClipPlane = std::max(0.001f, value); if (m_farClipPlane <= m_nearClipPlane) { m_farClipPlane = m_nearClipPlane + 0.001f; } } void CameraComponent::SetFarClipPlane(float value) { m_farClipPlane = std::max(m_nearClipPlane + 0.001f, value); } void CameraComponent::SetViewportRect(const Math::Rect& value) { const float x = std::clamp(value.x, 0.0f, 1.0f); const float y = std::clamp(value.y, 0.0f, 1.0f); const float width = std::clamp(value.width, 0.0f, 1.0f); const float height = std::clamp(value.height, 0.0f, 1.0f); const float right = std::min(1.0f, x + width); const float bottom = std::min(1.0f, y + height); m_viewportRect = Math::Rect(x, y, right - x, bottom - y); } void CameraComponent::SetSkyboxMaterialPath(const std::string& materialPath) { m_skyboxMaterialPath = materialPath; m_skyboxMaterialRef.Reset(); if (materialPath.empty()) { m_skyboxMaterial.Reset(); return; } m_skyboxMaterial = Resources::ResourceManager::Get().Load(materialPath.c_str()); if (!Resources::ResourceManager::Get().TryGetAssetRef( materialPath.c_str(), Resources::ResourceType::Material, m_skyboxMaterialRef)) { m_skyboxMaterialRef.Reset(); } } void CameraComponent::SetSkyboxMaterial(const Resources::ResourceHandle& material) { m_skyboxMaterial = material; m_skyboxMaterialPath = material.Get() != nullptr ? ToStdString(material->GetPath()) : std::string(); if (m_skyboxMaterialPath.empty() || !Resources::ResourceManager::Get().TryGetAssetRef( m_skyboxMaterialPath.c_str(), Resources::ResourceType::Material, m_skyboxMaterialRef)) { m_skyboxMaterialRef.Reset(); } } void CameraComponent::SetSkyboxMaterial(Resources::Material* material) { SetSkyboxMaterial(Resources::ResourceHandle(material)); } void CameraComponent::Serialize(std::ostream& os) const { Resources::AssetRef serializedSkyboxMaterialRef = m_skyboxMaterialRef; std::string serializedSkyboxMaterialPath = m_skyboxMaterialPath; if (serializedSkyboxMaterialPath.empty() && m_skyboxMaterial.Get() != nullptr) { serializedSkyboxMaterialPath = ToStdString(m_skyboxMaterial->GetPath()); } if (!serializedSkyboxMaterialRef.IsValid() && !serializedSkyboxMaterialPath.empty() && !HasVirtualPathScheme(serializedSkyboxMaterialPath) && Resources::ResourceManager::Get().TryGetAssetRef( serializedSkyboxMaterialPath.c_str(), Resources::ResourceType::Material, serializedSkyboxMaterialRef)) { } if (serializedSkyboxMaterialRef.IsValid() || !HasVirtualPathScheme(serializedSkyboxMaterialPath)) { serializedSkyboxMaterialPath.clear(); } os << "projection=" << static_cast(m_projectionType) << ";"; os << "fov=" << m_fieldOfView << ";"; os << "orthoSize=" << m_orthographicSize << ";"; os << "near=" << m_nearClipPlane << ";"; os << "far=" << m_farClipPlane << ";"; os << "depth=" << m_depth << ";"; os << "primary=" << (m_primary ? 1 : 0) << ";"; os << "clearMode=" << static_cast(m_clearMode) << ";"; os << "stackType=" << static_cast(m_stackType) << ";"; os << "cullingMask=" << m_cullingMask << ";"; os << "viewportRect=" << m_viewportRect.x << "," << m_viewportRect.y << "," << m_viewportRect.width << "," << m_viewportRect.height << ";"; os << "clearColor=" << m_clearColor.r << "," << m_clearColor.g << "," << m_clearColor.b << "," << m_clearColor.a << ";"; os << "skyboxEnabled=" << (m_skyboxEnabled ? 1 : 0) << ";"; os << "skyboxMaterialPath=" << serializedSkyboxMaterialPath << ";"; os << "skyboxMaterialRef=" << EncodeAssetRef(serializedSkyboxMaterialRef) << ";"; os << "skyboxTopColor=" << m_skyboxTopColor.r << "," << m_skyboxTopColor.g << "," << m_skyboxTopColor.b << "," << m_skyboxTopColor.a << ";"; os << "skyboxHorizonColor=" << m_skyboxHorizonColor.r << "," << m_skyboxHorizonColor.g << "," << m_skyboxHorizonColor.b << "," << m_skyboxHorizonColor.a << ";"; os << "skyboxBottomColor=" << m_skyboxBottomColor.r << "," << m_skyboxBottomColor.g << "," << m_skyboxBottomColor.b << "," << m_skyboxBottomColor.a << ";"; os << "postProcessPassCount=" << m_postProcessPasses.size() << ";"; for (size_t index = 0; index < m_postProcessPasses.size(); ++index) { const Rendering::CameraPostProcessPassDesc& pass = m_postProcessPasses[index]; os << "postProcessPass" << index << "Type=" << static_cast(pass.type) << ";"; switch (pass.type) { case Rendering::CameraPostProcessPassType::ColorScale: os << "postProcessPass" << index << "ColorScale=" << EncodeVector4(pass.colorScale.scale) << ";"; break; default: break; } } } void CameraComponent::Deserialize(std::istream& is) { m_skyboxMaterial.Reset(); m_skyboxMaterialPath.clear(); m_skyboxMaterialRef.Reset(); m_colorScalePostProcessDefaultScale = Math::Vector4::One(); m_postProcessPasses.clear(); std::string token; std::string pendingSkyboxMaterialPath; Resources::AssetRef pendingSkyboxMaterialRef; size_t postProcessPassCount = 0; Rendering::CameraPostProcessStack deserializedPostProcessPasses; bool legacyColorScaleEnabled = false; size_t colorScalePassCount = 0; std::vector deserializedColorScalePasses; 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); std::string value = token.substr(eqPos + 1); if (key == "projection") { m_projectionType = static_cast(std::stoi(value)); } else if (key == "fov") { SetFieldOfView(std::stof(value)); } else if (key == "orthoSize") { SetOrthographicSize(std::stof(value)); } else if (key == "near") { SetNearClipPlane(std::stof(value)); } else if (key == "far") { SetFarClipPlane(std::stof(value)); } else if (key == "depth") { m_depth = std::stof(value); } else if (key == "primary") { m_primary = (std::stoi(value) != 0); } else if (key == "clearMode") { m_clearMode = static_cast(std::stoi(value)); } else if (key == "stackType") { m_stackType = static_cast(std::stoi(value)); } else if (key == "cullingMask") { m_cullingMask = static_cast(std::stoul(value)); } else if (key == "viewportRect") { std::replace(value.begin(), value.end(), ',', ' '); std::istringstream ss(value); Math::Rect viewportRect; ss >> viewportRect.x >> viewportRect.y >> viewportRect.width >> viewportRect.height; SetViewportRect(viewportRect); } else if (key == "clearColor") { std::replace(value.begin(), value.end(), ',', ' '); std::istringstream ss(value); ss >> m_clearColor.r >> m_clearColor.g >> m_clearColor.b >> m_clearColor.a; } else if (key == "skyboxEnabled") { m_skyboxEnabled = (std::stoi(value) != 0); } else if (key == "skyboxMaterialPath") { pendingSkyboxMaterialPath = value; } else if (key == "skyboxMaterialRef") { TryDecodeAssetRef(value, pendingSkyboxMaterialRef); } else if (key == "skyboxTopColor") { std::replace(value.begin(), value.end(), ',', ' '); std::istringstream ss(value); ss >> m_skyboxTopColor.r >> m_skyboxTopColor.g >> m_skyboxTopColor.b >> m_skyboxTopColor.a; } else if (key == "skyboxHorizonColor") { std::replace(value.begin(), value.end(), ',', ' '); std::istringstream ss(value); ss >> m_skyboxHorizonColor.r >> m_skyboxHorizonColor.g >> m_skyboxHorizonColor.b >> m_skyboxHorizonColor.a; } else if (key == "skyboxBottomColor") { std::replace(value.begin(), value.end(), ',', ' '); std::istringstream ss(value); ss >> m_skyboxBottomColor.r >> m_skyboxBottomColor.g >> m_skyboxBottomColor.b >> m_skyboxBottomColor.a; } else if (key == "postProcessPassCount") { postProcessPassCount = static_cast(std::stoul(value)); deserializedPostProcessPasses.clear(); deserializedPostProcessPasses.resize(postProcessPassCount); } else if (key.rfind("postProcessPass", 0) == 0) { const size_t prefixLength = std::string("postProcessPass").size(); size_t propertyPos = prefixLength; while (propertyPos < key.size() && std::isdigit(static_cast(key[propertyPos])) != 0) { ++propertyPos; } if (propertyPos > prefixLength) { const size_t index = static_cast( std::stoul(key.substr(prefixLength, propertyPos - prefixLength))); if (index >= deserializedPostProcessPasses.size()) { deserializedPostProcessPasses.resize(index + 1); } Rendering::CameraPostProcessPassDesc& pass = deserializedPostProcessPasses[index]; const std::string property = key.substr(propertyPos); if (property == "Type") { pass.type = static_cast(std::stoi(value)); } else if (property == "ColorScale") { TryParseVector4(value, pass.colorScale.scale); } } } else if (key == "colorScalePostProcessEnabled") { legacyColorScaleEnabled = (std::stoi(value) != 0); } else if (key == "colorScalePostProcessScale") { TryParseVector4(value, m_colorScalePostProcessDefaultScale); } else if (key == "colorScalePostProcessPassCount") { colorScalePassCount = static_cast(std::stoul(value)); deserializedColorScalePasses.clear(); deserializedColorScalePasses.resize(colorScalePassCount, Math::Vector4::One()); } else if (key.rfind("colorScalePostProcessPass", 0) == 0) { const std::string indexString = key.substr(std::string("colorScalePostProcessPass").size()); if (!indexString.empty()) { const size_t index = static_cast(std::stoul(indexString)); if (index >= deserializedColorScalePasses.size()) { deserializedColorScalePasses.resize(index + 1, Math::Vector4::One()); } TryParseVector4(value, deserializedColorScalePasses[index]); } } } if (pendingSkyboxMaterialRef.IsValid()) { m_skyboxMaterialRef = pendingSkyboxMaterialRef; m_skyboxMaterial = Resources::ResourceManager::Get().Load(pendingSkyboxMaterialRef); if (m_skyboxMaterial.Get() != nullptr) { m_skyboxMaterialPath = ToStdString(m_skyboxMaterial->GetPath()); } else { Containers::String resolvedPath; if (Resources::ResourceManager::Get().TryResolveAssetPath(pendingSkyboxMaterialRef, resolvedPath)) { SetSkyboxMaterialPath(ToStdString(resolvedPath)); m_skyboxMaterialRef = pendingSkyboxMaterialRef; } } } if (m_skyboxMaterial.Get() == nullptr && !pendingSkyboxMaterialPath.empty()) { SetSkyboxMaterialPath(pendingSkyboxMaterialPath); } if (m_skyboxMaterial.Get() == nullptr && pendingSkyboxMaterialRef.IsValid()) { m_skyboxMaterialRef = pendingSkyboxMaterialRef; } if (!deserializedPostProcessPasses.empty()) { SetPostProcessPasses(deserializedPostProcessPasses); } else if (!deserializedColorScalePasses.empty()) { SetColorScalePostProcessPasses(deserializedColorScalePasses); } else if (legacyColorScaleEnabled) { SetColorScalePostProcessEnabled(true); } } } // namespace Components } // namespace XCEngine