#include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreTransformUtils.h" #include #include #include #include #include #include namespace MetaCore { namespace { [[nodiscard]] bool MetaCoreIsUsableDirection(const glm::vec3& value) { return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && glm::length(value) > 0.0001F; } [[nodiscard]] glm::vec3 MetaCoreExtractTranslation(const glm::mat4& matrix) { return glm::vec3(matrix[3]); } [[nodiscard]] std::string MetaCoreNormalizeSyncPath(std::string path) { std::replace(path.begin(), path.end(), '\\', '/'); return path; } [[nodiscard]] float MetaCoreSafeAspectRatio(float width, float height) { return height > 0.0001F ? std::max(0.0001F, width / height) : 1.0F; } [[nodiscard]] glm::mat4 MetaCoreBuildProjectionMatrix( const MetaCoreSceneView& sceneView, float aspectRatio ) { const float nearClip = std::max(0.0001F, sceneView.NearClip); const float farClip = std::max(nearClip + 0.0001F, sceneView.FarClip); if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) { const float halfHeight = std::max(0.0001F, sceneView.OrthographicSize); const float halfWidth = halfHeight * aspectRatio; return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, nearClip, farClip); } const float fovDegrees = std::clamp(sceneView.VerticalFieldOfViewDegrees, 1.0F, 179.0F); return glm::perspective(glm::radians(fovDegrees), aspectRatio, nearClip, farClip); } [[nodiscard]] glm::vec3 MetaCoreSafeForward(const MetaCoreSceneView& sceneView) { glm::vec3 forward = sceneView.CameraTarget - sceneView.CameraPosition; if (!MetaCoreIsUsableDirection(forward)) { forward = glm::vec3(0.0F, 0.0F, -1.0F); } return glm::normalize(forward); } [[nodiscard]] glm::vec3 MetaCoreSafeUp(const MetaCoreSceneView& sceneView, const glm::vec3& forward) { glm::vec3 up = sceneView.CameraUp; if (!MetaCoreIsUsableDirection(up) || glm::length(glm::cross(forward, up)) <= 0.0001F) { up = glm::vec3(0.0F, 1.0F, 0.0F); } if (glm::length(glm::cross(forward, up)) <= 0.0001F) { up = glm::vec3(0.0F, 0.0F, 1.0F); } return glm::normalize(up); } } // namespace MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const MetaCoreScene& scene) const { MetaCoreSceneRenderSyncSnapshot snapshot; snapshot.FrameId = ++FrameId_; snapshot.SceneRevision = scene.GetRevision(); snapshot.Environment = scene.GetEnvironmentSettings(); // 预处理:构建子树模型网格节点计数表,支撑在没有 Tag 时的 Fallback 回溯 std::unordered_map> subtreeModelCounts; for (MetaCoreId objectId : scene.BuildHierarchyPreorder()) { const MetaCoreGameObject gameObject = scene.FindGameObject(objectId); if (gameObject && gameObject.HasComponent()) { const auto& mesh = gameObject.GetComponent(); if (mesh.ModelNodeIndex >= 0 && !mesh.SourceModelPath.empty()) { std::string normPath = MetaCoreNormalizeSyncPath(mesh.SourceModelPath); MetaCoreId currentId = objectId; while (currentId != 0) { auto currentObj = scene.FindGameObject(currentId); if (!currentObj) break; subtreeModelCounts[currentId][normPath]++; currentId = currentObj.GetParentId(); } } } } for (MetaCoreId objectId : scene.BuildHierarchyPreorder()) { const MetaCoreGameObject gameObject = scene.FindGameObject(objectId); if (!gameObject) { continue; } glm::mat4 localMatrix{1.0F}; if (gameObject.HasComponent()) { localMatrix = MetaCoreBuildTransformMatrix(gameObject.GetComponent()); } glm::mat4 worldMatrix = localMatrix; const MetaCoreId parentId = gameObject.GetParentId(); if (parentId != 0) { const auto parentWorldIt = snapshot.WorldMatrices.find(parentId); if (parentWorldIt != snapshot.WorldMatrices.end()) { worldMatrix = parentWorldIt->second * localMatrix; } } snapshot.WorldMatrices[objectId] = worldMatrix; snapshot.LocalMatrices[objectId] = localMatrix; if (gameObject.HasComponent()) { const auto& meshRenderer = gameObject.GetComponent(); // 黄金算法:溯源确定唯一的模型根加载主体 MetaCoreGameObject hostRoot = gameObject; if (meshRenderer.ModelNodeIndex >= 0) { const std::string& currentModelPath = meshRenderer.SourceModelPath; std::string normCurrentPath = MetaCoreNormalizeSyncPath(currentModelPath); MetaCoreGameObject current = gameObject; MetaCoreGameObject foundRoot = {}; // 1. 优先通过运行时 MetaCoreModelRootTag 快速寻找顶级根 while (current) { if (current.HasComponent()) { auto& tag = current.GetComponent(); if (MetaCoreNormalizeSyncPath(tag.SourceModelPath) == normCurrentPath) { foundRoot = current; break; } } if (current.GetParentId() == 0) break; current = scene.FindGameObject(current.GetParentId()); } if (foundRoot) { hostRoot = foundRoot; } else { // 2. Fallback:若无明确 Tag,使用转折点判定算法 current = gameObject; MetaCoreGameObject bestCandidate = gameObject; int maxCount = subtreeModelCounts[gameObject.GetId()][normCurrentPath]; while (current) { int count = subtreeModelCounts[current.GetId()][normCurrentPath]; if (count > maxCount) { maxCount = count; bestCandidate = current; } if (current.GetParentId() == 0) break; current = scene.FindGameObject(current.GetParentId()); } hostRoot = bestCandidate; } } snapshot.Renderables.push_back(MetaCoreRenderSyncRenderable{ objectId, parentId, gameObject.GetName(), localMatrix, worldMatrix, meshRenderer.Visible, meshRenderer.MeshSource, meshRenderer.BuiltinMesh, meshRenderer.MeshAssetGuid, meshRenderer.SourceModelAssetGuid, meshRenderer.SourceModelPath, meshRenderer.ModelNodeIndex, hostRoot.GetId(), hostRoot.GetName(), hostRoot.HasComponent(), meshRenderer.MaterialAssetGuids, meshRenderer.BaseColor, meshRenderer.Metallic, meshRenderer.Roughness, meshRenderer.EmissiveColor, meshRenderer.AlphaMode, meshRenderer.AlphaCutoff, meshRenderer.DoubleSided, meshRenderer.BaseColorTexturePath, meshRenderer.MetallicRoughnessTexturePath, meshRenderer.NormalTexturePath, meshRenderer.EmissiveTexturePath, meshRenderer.AoTexturePath, meshRenderer.RenderingLayerMask, meshRenderer.MaxDrawDistance, meshRenderer.StaticForCulling, meshRenderer.CastShadows, meshRenderer.ReceiveShadows, {}, meshRenderer.BuiltinMesh == MetaCoreBuiltinMeshType::Cube ? 12U : (meshRenderer.BuiltinMesh == MetaCoreBuiltinMeshType::Plane ? 2U : 0U), 0U }); auto& syncRenderable = snapshot.Renderables.back(); const glm::vec3 position = glm::vec3(worldMatrix[3]); const glm::vec3 extents{ std::max(0.001F, glm::length(glm::vec3(worldMatrix[0])) * 0.5F), std::max(0.001F, glm::length(glm::vec3(worldMatrix[1])) * 0.5F), std::max(0.001F, glm::length(glm::vec3(worldMatrix[2])) * 0.5F)}; syncRenderable.WorldBounds = {position - extents, position + extents}; } if (gameObject.HasComponent()) { const auto& camera = gameObject.GetComponent(); MetaCoreRenderSyncCamera syncCamera; syncCamera.ObjectId = objectId; syncCamera.IsPrimary = camera.IsPrimary; syncCamera.FieldOfViewDegrees = camera.FieldOfViewDegrees; syncCamera.NearClip = camera.NearClip; syncCamera.FarClip = camera.FarClip; syncCamera.WorldMatrix = worldMatrix; syncCamera.Enabled = camera.Enabled; syncCamera.ProjectionMode = camera.ProjectionMode; syncCamera.OrthographicSize = camera.OrthographicSize; syncCamera.Depth = camera.Depth; syncCamera.CullingMask = camera.CullingMask; syncCamera.ClearFlags = camera.ClearFlags; syncCamera.BackgroundColor = camera.BackgroundColor; syncCamera.BackgroundAlpha = camera.BackgroundAlpha; syncCamera.PostProcessProfileGuid = camera.PostProcessProfileGuid; snapshot.Cameras.push_back(syncCamera); if (syncCamera.IsPrimary && syncCamera.Enabled && !snapshot.PrimaryCamera.has_value()) { snapshot.PrimaryCamera = syncCamera; } } if (gameObject.HasComponent()) { const auto& light = gameObject.GetComponent(); snapshot.Lights.push_back(MetaCoreRenderSyncLight{ objectId, light.Type, light.Color, light.Intensity, worldMatrix, light.Enabled, light.UseColorTemperature, light.ColorTemperatureKelvin, light.Range, light.InnerConeDegrees, light.OuterConeDegrees, light.FalloffExponent, light.CastShadows, light.ShadowBias, light.ShadowNormalBias, light.ShadowPriority, light.LightingMask }); } if (gameObject.HasComponent()) { const auto& animator = gameObject.GetComponent(); std::string sourceModelPath = animator.SourceModelPath; if (sourceModelPath.empty() && gameObject.HasComponent()) { sourceModelPath = gameObject.GetComponent().SourceModelPath; } snapshot.Animations.push_back(MetaCoreRenderSyncAnimationState{ objectId, animator.SourceModelAssetGuid, sourceModelPath, animator.ClipIndex, animator.RuntimeTimeSeconds, animator.Enabled, animator.RuntimePlaying, animator.Loop, animator.Speed }); } if(gameObject.HasComponent())snapshot.ReflectionProbes.push_back({objectId,gameObject.GetComponent(),worldMatrix}); if(gameObject.HasComponent())snapshot.LodGroups.push_back({objectId,gameObject.GetComponent(),worldMatrix}); if(gameObject.HasComponent())snapshot.ParticleEmitters.push_back({objectId,gameObject.GetComponent(),worldMatrix}); if(gameObject.HasComponent())snapshot.LineRenderers.push_back({objectId,gameObject.GetComponent(),worldMatrix}); if(gameObject.HasComponent())snapshot.TrailRenderers.push_back({objectId,gameObject.GetComponent(),worldMatrix}); if(gameObject.HasComponent())snapshot.DecalProjectors.push_back({objectId,gameObject.GetComponent(),worldMatrix}); if(gameObject.HasComponent())snapshot.Terrains.push_back({objectId,gameObject.GetComponent(),worldMatrix}); } if (!snapshot.PrimaryCamera.has_value() && !snapshot.Cameras.empty()) { const MetaCoreRenderSyncCamera* bestCamera = nullptr; for (const MetaCoreRenderSyncCamera& camera : snapshot.Cameras) { if (!camera.Enabled) { continue; } if (bestCamera == nullptr || camera.Depth < bestCamera->Depth) { bestCamera = &camera; } } if (bestCamera != nullptr) { snapshot.PrimaryCamera = *bestCamera; } } return snapshot; } bool MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera( const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreSceneView& sceneView ) { if (!snapshot.PrimaryCamera.has_value()) { return false; } const MetaCoreRenderSyncCamera& camera = *snapshot.PrimaryCamera; return TryBuildSceneViewFromCamera(camera, sceneView); } bool MetaCoreSceneRenderSync::TryBuildSceneViewFromCamera( const MetaCoreRenderSyncCamera& camera, MetaCoreSceneView& sceneView ) { if (!camera.Enabled) { return false; } const glm::vec3 cameraPosition = MetaCoreExtractTranslation(camera.WorldMatrix); glm::vec3 cameraForward = glm::vec3(camera.WorldMatrix * glm::vec4(0.0F, 0.0F, -1.0F, 0.0F)); glm::vec3 cameraUp = glm::vec3(camera.WorldMatrix * glm::vec4(0.0F, 1.0F, 0.0F, 0.0F)); if (!MetaCoreIsUsableDirection(cameraForward)) { cameraForward = glm::vec3(0.0F, 0.0F, -1.0F); } if (!MetaCoreIsUsableDirection(cameraUp)) { cameraUp = glm::vec3(0.0F, 1.0F, 0.0F); } sceneView.CameraPosition = cameraPosition; sceneView.CameraTarget = cameraPosition + glm::normalize(cameraForward); sceneView.CameraUp = glm::normalize(cameraUp); sceneView.VerticalFieldOfViewDegrees = camera.FieldOfViewDegrees; sceneView.NearClip = camera.NearClip; sceneView.FarClip = camera.FarClip; sceneView.ProjectionMode = camera.ProjectionMode; sceneView.OrthographicSize = camera.OrthographicSize; sceneView.ClearFlags = camera.ClearFlags; sceneView.BackgroundColor = camera.BackgroundColor; sceneView.BackgroundAlpha = camera.BackgroundAlpha; sceneView.CullingMask = camera.CullingMask; sceneView.SourceCameraObjectId = camera.ObjectId; return true; } MetaCoreCameraRay MetaCoreScreenPointToRay( const MetaCoreSceneView& sceneView, const MetaCoreViewportRect& viewportRect, const glm::vec2& screenPosition ) { const float width = std::max(1.0F, viewportRect.Width); const float height = std::max(1.0F, viewportRect.Height); const float localX = std::clamp(screenPosition.x - viewportRect.Left, 0.0F, width); const float localY = std::clamp(screenPosition.y - viewportRect.Top, 0.0F, height); const float normalizedX = (localX / width) * 2.0F - 1.0F; const float normalizedY = 1.0F - (localY / height) * 2.0F; const glm::vec3 forward = MetaCoreSafeForward(sceneView); const glm::vec3 up = MetaCoreSafeUp(sceneView, forward); const glm::vec3 right = glm::normalize(glm::cross(forward, up)); const float aspectRatio = MetaCoreSafeAspectRatio(width, height); if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) { const float halfHeight = std::max(0.0001F, sceneView.OrthographicSize); const float halfWidth = halfHeight * aspectRatio; return MetaCoreCameraRay{ sceneView.CameraPosition + right * (normalizedX * halfWidth) + up * (normalizedY * halfHeight), forward }; } const float fovDegrees = std::clamp(sceneView.VerticalFieldOfViewDegrees, 1.0F, 179.0F); const float tanHalfFov = std::tan(glm::radians(fovDegrees) * 0.5F); return MetaCoreCameraRay{ sceneView.CameraPosition, glm::normalize(forward + right * (normalizedX * tanHalfFov * aspectRatio) + up * (normalizedY * tanHalfFov)) }; } std::optional MetaCoreWorldToScreenPoint( const MetaCoreSceneView& sceneView, const MetaCoreViewportRect& viewportRect, const glm::vec3& worldPosition ) { const float width = std::max(1.0F, viewportRect.Width); const float height = std::max(1.0F, viewportRect.Height); const glm::vec3 forward = MetaCoreSafeForward(sceneView); const glm::vec3 up = MetaCoreSafeUp(sceneView, forward); const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, up); const glm::mat4 projectionMatrix = MetaCoreBuildProjectionMatrix(sceneView, MetaCoreSafeAspectRatio(width, height)); const glm::vec4 clipPosition = projectionMatrix * viewMatrix * glm::vec4(worldPosition, 1.0F); if (std::abs(clipPosition.w) <= 0.000001F) { return std::nullopt; } const glm::vec3 ndc = glm::vec3(clipPosition) / clipPosition.w; if (!std::isfinite(ndc.x) || !std::isfinite(ndc.y) || !std::isfinite(ndc.z)) { return std::nullopt; } return glm::vec3( viewportRect.Left + (ndc.x + 1.0F) * 0.5F * width, viewportRect.Top + (1.0F - ndc.y) * 0.5F * height, ndc.z ); } } // namespace MetaCore