diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index 578d037..0b6797f 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -1530,7 +1530,7 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, const bool multiEdit = editorContext.GetSelectedObjectIds().size() > 1; if (multiEdit) { - ImGui::TextDisabled("Multi-edit %zu objects", editorContext.GetSelectedObjectIds().size()); + ImGui::TextDisabled("多选编辑 %zu 个对象", editorContext.GetSelectedObjectIds().size()); } float fov = MetaCoreGetSharedSelectedValue( @@ -1547,22 +1547,22 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, }, [](float lhs, float rhs) { return MetaCoreNearlyEqual(lhs, rhs); } ).has_value()) { - ImGui::TextDisabled("FOV: Mixed"); + ImGui::TextDisabled("视野 FOV: 多值"); } if (prefabObject != nullptr && prefabObject->Camera.has_value() && !MetaCoreNearlyEqual(gameObject.GetComponent().FieldOfViewDegrees, prefabObject->Camera->FieldOfViewDegrees)) { - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "FOV Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "视野 FOV 已覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton("Apply##CameraFOV")) { + if (ImGui::SmallButton("应用##CameraFOV")) { (void)MetaCoreApplySelectedPrefabField(editorContext, "Camera", "fov"); } ImGui::SameLine(); - if (ImGui::SmallButton("Revert##CameraFOV")) { + if (ImGui::SmallButton("回退##CameraFOV")) { (void)MetaCoreRevertSelectedPrefabField(editorContext, "Camera", "fov"); fov = prefabObject->Camera->FieldOfViewDegrees; } } - ImGui::DragFloat("FOV", &fov, 0.2F, 1.0F, 179.0F); + ImGui::DragFloat("视野 FOV", &fov, 0.2F, 1.0F, 179.0F); MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); if (ImGui::IsItemEdited()) { MetaCoreApplyValueToSelectedObjects( @@ -1583,22 +1583,22 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, ); float nearClip = sharedNearClip.value_or(gameObject.GetComponent().NearClip); if (multiEdit && !sharedNearClip.has_value()) { - ImGui::TextDisabled("Near Clip: Mixed"); + ImGui::TextDisabled("近裁剪: 多值"); } if (prefabObject != nullptr && prefabObject->Camera.has_value() && !MetaCoreNearlyEqual(gameObject.GetComponent().NearClip, prefabObject->Camera->NearClip)) { - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Near Clip Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "近裁剪已覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton("Apply##CameraNear")) { + if (ImGui::SmallButton("应用##CameraNear")) { (void)MetaCoreApplySelectedPrefabField(editorContext, "Camera", "near_clip"); } ImGui::SameLine(); - if (ImGui::SmallButton("Revert##CameraNear")) { + if (ImGui::SmallButton("回退##CameraNear")) { (void)MetaCoreRevertSelectedPrefabField(editorContext, "Camera", "near_clip"); nearClip = prefabObject->Camera->NearClip; } } - ImGui::DragFloat("Near Clip", &nearClip, 0.01F, 0.001F, gameObject.GetComponent().FarClip); + ImGui::DragFloat("近裁剪", &nearClip, 0.01F, 0.001F, gameObject.GetComponent().FarClip); MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); if (ImGui::IsItemEdited()) { MetaCoreApplyValueToSelectedObjects( @@ -1619,22 +1619,22 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, ); float farClip = sharedFarClip.value_or(gameObject.GetComponent().FarClip); if (multiEdit && !sharedFarClip.has_value()) { - ImGui::TextDisabled("Far Clip: Mixed"); + ImGui::TextDisabled("远裁剪: 多值"); } if (prefabObject != nullptr && prefabObject->Camera.has_value() && !MetaCoreNearlyEqual(gameObject.GetComponent().FarClip, prefabObject->Camera->FarClip)) { - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Far Clip Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "远裁剪已覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton("Apply##CameraFar")) { + if (ImGui::SmallButton("应用##CameraFar")) { (void)MetaCoreApplySelectedPrefabField(editorContext, "Camera", "far_clip"); } ImGui::SameLine(); - if (ImGui::SmallButton("Revert##CameraFar")) { + if (ImGui::SmallButton("回退##CameraFar")) { (void)MetaCoreRevertSelectedPrefabField(editorContext, "Camera", "far_clip"); farClip = prefabObject->Camera->FarClip; } } - ImGui::DragFloat("Far Clip", &farClip, 0.1F, nearClip, 10000.0F); + ImGui::DragFloat("远裁剪", &farClip, 0.1F, nearClip, 10000.0F); MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); if (ImGui::IsItemEdited()) { MetaCoreApplyValueToSelectedObjects( @@ -1655,22 +1655,22 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, ); bool isPrimary = sharedPrimary.value_or(gameObject.GetComponent().IsPrimary); if (multiEdit && !sharedPrimary.has_value()) { - ImGui::TextDisabled("Primary: Mixed"); + ImGui::TextDisabled("主相机: 多值"); } if (prefabObject != nullptr && prefabObject->Camera.has_value() && gameObject.GetComponent().IsPrimary != prefabObject->Camera->IsPrimary) { - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Primary Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "主相机已覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton("Apply##CameraPrimary")) { + if (ImGui::SmallButton("应用##CameraPrimary")) { (void)MetaCoreApplySelectedPrefabField(editorContext, "Camera", "primary"); } ImGui::SameLine(); - if (ImGui::SmallButton("Revert##CameraPrimary")) { + if (ImGui::SmallButton("回退##CameraPrimary")) { (void)MetaCoreRevertSelectedPrefabField(editorContext, "Camera", "primary"); isPrimary = prefabObject->Camera->IsPrimary; } } - ImGui::Checkbox("Primary", &isPrimary); + ImGui::Checkbox("主相机", &isPrimary); MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); if (ImGui::IsItemEdited()) { MetaCoreApplyValueToSelectedObjects( @@ -1682,7 +1682,7 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, ); } - // 获取所有选中相机的共享启用状态(支持多选和 Mixed 提示) + // 获取所有选中相机的共享启用状态(支持多选和多值提示) const auto sharedEnabled = MetaCoreGetSharedSelectedValue( editorContext, [](MetaCoreGameObject& selectedObject) -> std::optional { @@ -1692,23 +1692,23 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, ); bool isEnabled = sharedEnabled.value_or(gameObject.GetComponent().Enabled); if (multiEdit && !sharedEnabled.has_value()) { - ImGui::TextDisabled("Enabled: Mixed"); + ImGui::TextDisabled("启用: 多值"); } // 处理 Prefab 覆盖标记以及应用和回退按钮 if (prefabObject != nullptr && prefabObject->Camera.has_value() && gameObject.GetComponent().Enabled != prefabObject->Camera->Enabled) { - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Enabled Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "启用已覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton("Apply##CameraEnabled")) { + if (ImGui::SmallButton("应用##CameraEnabled")) { (void)MetaCoreApplySelectedPrefabField(editorContext, "Camera", "enabled"); } ImGui::SameLine(); - if (ImGui::SmallButton("Revert##CameraEnabled")) { + if (ImGui::SmallButton("回退##CameraEnabled")) { (void)MetaCoreRevertSelectedPrefabField(editorContext, "Camera", "enabled"); isEnabled = prefabObject->Camera->Enabled; } } - ImGui::Checkbox("Enabled##Camera", &isEnabled); + ImGui::Checkbox("启用##Camera", &isEnabled); MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); if (ImGui::IsItemEdited()) { // 应用新的启用状态到所有选中的相机组件 @@ -1720,6 +1720,111 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext, isEnabled ); } + + auto& camera = gameObject.GetComponent(); + + int projectionModeIndex = camera.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic ? 1 : 0; + const char* projectionItems[] = {"透视", "正交"}; + if (ImGui::Combo("投影模式", &projectionModeIndex, projectionItems, IM_ARRAYSIZE(projectionItems))) { + const auto projectionMode = projectionModeIndex == 1 + ? MetaCoreCameraProjectionMode::Orthographic + : MetaCoreCameraProjectionMode::Perspective; + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> MetaCoreCameraProjectionMode* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().ProjectionMode : nullptr; + }, + projectionMode + ); + } + + float orthographicSize = camera.OrthographicSize; + ImGui::DragFloat("正交大小", &orthographicSize, 0.05F, 0.01F, 10000.0F); + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); + if (ImGui::IsItemEdited()) { + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> float* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().OrthographicSize : nullptr; + }, + orthographicSize + ); + } + + float depth = camera.Depth; + ImGui::DragFloat("深度", &depth, 0.1F, -10000.0F, 10000.0F); + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); + if (ImGui::IsItemEdited()) { + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> float* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().Depth : nullptr; + }, + depth + ); + } + + std::uint32_t cullingMask = camera.CullingMask; + ImGui::InputScalar( + "剔除遮罩", + ImGuiDataType_U32, + &cullingMask, + nullptr, + nullptr, + "%08X", + ImGuiInputTextFlags_CharsHexadecimal + ); + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); + if (ImGui::IsItemEdited()) { + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> std::uint32_t* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().CullingMask : nullptr; + }, + cullingMask + ); + } + + int clearFlagsIndex = static_cast(camera.ClearFlags); + const char* clearFlagItems[] = {"天空盒", "纯色", "仅深度", "不清屏"}; + if (ImGui::Combo("清屏方式", &clearFlagsIndex, clearFlagItems, IM_ARRAYSIZE(clearFlagItems))) { + const auto clearFlags = static_cast(std::clamp(clearFlagsIndex, 0, 3)); + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> MetaCoreCameraClearFlags* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().ClearFlags : nullptr; + }, + clearFlags + ); + } + + glm::vec3 backgroundColor = camera.BackgroundColor; + ImGui::ColorEdit3("背景色", glm::value_ptr(backgroundColor)); + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); + if (ImGui::IsItemEdited()) { + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> glm::vec3* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().BackgroundColor : nullptr; + }, + backgroundColor + ); + } + + float backgroundAlpha = camera.BackgroundAlpha; + ImGui::DragFloat("背景 Alpha", &backgroundAlpha, 0.01F, 0.0F, 1.0F); + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); + if (ImGui::IsItemEdited()) { + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> float* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().BackgroundAlpha : nullptr; + }, + backgroundAlpha + ); + } } void MetaCoreDrawLightComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { @@ -2914,6 +3019,27 @@ template // 应用相机的启用状态到 Prefab 模板中 prefabObjectIterator->Camera->Enabled = selectedObject.GetComponent().Enabled; changed = true; + } else if (fieldId == "projection_mode") { + prefabObjectIterator->Camera->ProjectionMode = selectedObject.GetComponent().ProjectionMode; + changed = true; + } else if (fieldId == "orthographic_size") { + prefabObjectIterator->Camera->OrthographicSize = selectedObject.GetComponent().OrthographicSize; + changed = true; + } else if (fieldId == "depth") { + prefabObjectIterator->Camera->Depth = selectedObject.GetComponent().Depth; + changed = true; + } else if (fieldId == "culling_mask") { + prefabObjectIterator->Camera->CullingMask = selectedObject.GetComponent().CullingMask; + changed = true; + } else if (fieldId == "clear_flags") { + prefabObjectIterator->Camera->ClearFlags = selectedObject.GetComponent().ClearFlags; + changed = true; + } else if (fieldId == "background_color") { + prefabObjectIterator->Camera->BackgroundColor = selectedObject.GetComponent().BackgroundColor; + changed = true; + } else if (fieldId == "background_alpha") { + prefabObjectIterator->Camera->BackgroundAlpha = selectedObject.GetComponent().BackgroundAlpha; + changed = true; } } else if (componentTypeId == "Light" && selectedObject.HasComponent()) { if (!prefabObjectIterator->Light.has_value()) { @@ -2997,6 +3123,34 @@ template selectedObject.GetComponent().Enabled = prefabObject->Camera->Enabled; return true; } + if (fieldId == "projection_mode") { + selectedObject.GetComponent().ProjectionMode = prefabObject->Camera->ProjectionMode; + return true; + } + if (fieldId == "orthographic_size") { + selectedObject.GetComponent().OrthographicSize = prefabObject->Camera->OrthographicSize; + return true; + } + if (fieldId == "depth") { + selectedObject.GetComponent().Depth = prefabObject->Camera->Depth; + return true; + } + if (fieldId == "culling_mask") { + selectedObject.GetComponent().CullingMask = prefabObject->Camera->CullingMask; + return true; + } + if (fieldId == "clear_flags") { + selectedObject.GetComponent().ClearFlags = prefabObject->Camera->ClearFlags; + return true; + } + if (fieldId == "background_color") { + selectedObject.GetComponent().BackgroundColor = prefabObject->Camera->BackgroundColor; + return true; + } + if (fieldId == "background_alpha") { + selectedObject.GetComponent().BackgroundAlpha = prefabObject->Camera->BackgroundAlpha; + return true; + } } if (componentTypeId == "Light" && prefabObject->Light.has_value()) { @@ -6030,6 +6184,10 @@ public: bool withMeshRenderer, std::optional forcedParentId ) override; + [[nodiscard]] std::optional CreateCamera( + MetaCoreEditorContext& editorContext, + std::optional forcedParentId + ) override; [[nodiscard]] std::optional InstantiateModelAsset( MetaCoreEditorContext& editorContext, const MetaCoreAssetGuid& assetGuid, @@ -7720,6 +7878,52 @@ std::optional MetaCoreBuiltinSceneEditingService::CreateGameObject( return createdObjectId; } +std::optional MetaCoreBuiltinSceneEditingService::CreateCamera( + MetaCoreEditorContext& editorContext, + std::optional forcedParentId +) { + MetaCoreId createdObjectId = 0; + const bool created = editorContext.ExecuteSnapshotCommand("创建相机", [&]() { + MetaCoreId parentId = forcedParentId.value_or(editorContext.GetActiveObjectId()); + if (parentId != 0 && !editorContext.GetScene().FindGameObject(parentId)) { + parentId = 0; + } + + bool hasEnabledPrimaryCamera = false; + for (const MetaCoreGameObject object : editorContext.GetScene().GetGameObjects()) { + if (!object.HasComponent()) { + continue; + } + + const MetaCoreCameraComponent& camera = object.GetComponent(); + if (camera.Enabled && camera.IsPrimary) { + hasEnabledPrimaryCamera = true; + break; + } + } + + MetaCoreGameObject cameraObject = editorContext.GetScene().CreateGameObject("Camera", parentId); + MetaCoreCameraComponent& camera = cameraObject.AddComponent(MetaCoreCameraComponent{}); + camera.IsPrimary = !hasEnabledPrimaryCamera; + + MetaCoreTransformComponent& transform = cameraObject.GetComponent(); + transform.Position = glm::vec3(0.0F, 2.2F, 6.5F); + transform.RotationEulerDegrees = glm::vec3(-15.0F, 0.0F, 0.0F); + + createdObjectId = cameraObject.GetId(); + editorContext.SelectOnly(createdObjectId); + editorContext.SetSelectionAnchorId(createdObjectId); + return true; + }); + + if (!created || createdObjectId == 0) { + return std::nullopt; + } + + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Scene", "已创建相机"); + return createdObjectId; +} + std::optional MetaCoreBuiltinSceneEditingService::InstantiateModelAsset( MetaCoreEditorContext& editorContext, const MetaCoreAssetGuid& assetGuid, diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index 6781084..3a0388c 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -429,6 +429,17 @@ void MetaCoreApplyMaterialPreviewToScene( return sceneEditingService->CreateGameObject(editorContext, objectName, withMeshRenderer, forcedParentId); } +[[nodiscard]] std::optional MetaCoreCreateCamera( + MetaCoreEditorContext& editorContext, + std::optional forcedParentId +) { + const auto sceneEditingService = editorContext.GetModuleRegistry().ResolveService(); + if (sceneEditingService == nullptr) { + return std::nullopt; + } + return sceneEditingService->CreateCamera(editorContext, forcedParentId); +} + [[nodiscard]] bool MetaCoreDuplicateSelection(MetaCoreEditorContext& editorContext) { const auto clipboardService = editorContext.GetModuleRegistry().ResolveService(); if (clipboardService == nullptr) { @@ -1452,10 +1463,20 @@ struct MetaCoreGeneratedAssetChoice { return false; } - return !MetaCoreNearlyEqual(instanceObject.GetComponent().FieldOfViewDegrees, prefabObject.Camera->FieldOfViewDegrees) || - !MetaCoreNearlyEqual(instanceObject.GetComponent().NearClip, prefabObject.Camera->NearClip) || - !MetaCoreNearlyEqual(instanceObject.GetComponent().FarClip, prefabObject.Camera->FarClip) || - instanceObject.GetComponent().IsPrimary != prefabObject.Camera->IsPrimary; + const MetaCoreCameraComponent& instanceCamera = instanceObject.GetComponent(); + const MetaCoreCameraComponent& prefabCamera = *prefabObject.Camera; + return !MetaCoreNearlyEqual(instanceCamera.FieldOfViewDegrees, prefabCamera.FieldOfViewDegrees) || + !MetaCoreNearlyEqual(instanceCamera.NearClip, prefabCamera.NearClip) || + !MetaCoreNearlyEqual(instanceCamera.FarClip, prefabCamera.FarClip) || + instanceCamera.IsPrimary != prefabCamera.IsPrimary || + instanceCamera.Enabled != prefabCamera.Enabled || + instanceCamera.ProjectionMode != prefabCamera.ProjectionMode || + !MetaCoreNearlyEqual(instanceCamera.OrthographicSize, prefabCamera.OrthographicSize) || + !MetaCoreNearlyEqual(instanceCamera.Depth, prefabCamera.Depth) || + instanceCamera.CullingMask != prefabCamera.CullingMask || + instanceCamera.ClearFlags != prefabCamera.ClearFlags || + !MetaCoreNearlyEqualVec3(instanceCamera.BackgroundColor, prefabCamera.BackgroundColor) || + !MetaCoreNearlyEqual(instanceCamera.BackgroundAlpha, prefabCamera.BackgroundAlpha); } [[nodiscard]] bool MetaCoreHasLightOverride(const MetaCoreGameObject& instanceObject, const MetaCoreGameObjectData& prefabObject) { @@ -1862,6 +1883,38 @@ void MetaCoreTrackInspectorEdit(MetaCoreEditorContext& editorContext, const char prefabObject.Camera->IsPrimary = selectedObject.GetComponent().IsPrimary; return true; } + if (fieldId == "enabled") { + prefabObject.Camera->Enabled = selectedObject.GetComponent().Enabled; + return true; + } + if (fieldId == "projection_mode") { + prefabObject.Camera->ProjectionMode = selectedObject.GetComponent().ProjectionMode; + return true; + } + if (fieldId == "orthographic_size") { + prefabObject.Camera->OrthographicSize = selectedObject.GetComponent().OrthographicSize; + return true; + } + if (fieldId == "depth") { + prefabObject.Camera->Depth = selectedObject.GetComponent().Depth; + return true; + } + if (fieldId == "culling_mask") { + prefabObject.Camera->CullingMask = selectedObject.GetComponent().CullingMask; + return true; + } + if (fieldId == "clear_flags") { + prefabObject.Camera->ClearFlags = selectedObject.GetComponent().ClearFlags; + return true; + } + if (fieldId == "background_color") { + prefabObject.Camera->BackgroundColor = selectedObject.GetComponent().BackgroundColor; + return true; + } + if (fieldId == "background_alpha") { + prefabObject.Camera->BackgroundAlpha = selectedObject.GetComponent().BackgroundAlpha; + return true; + } } else if (componentTypeId == "Light" && selectedObject.HasComponent()) { if (!prefabObject.Light.has_value()) { prefabObject.Light = MetaCoreLightComponent{}; @@ -2077,6 +2130,9 @@ public: } ImGui::EndMenu(); } + if (ImGui::MenuItem("创建相机")) { + (void)MetaCoreCreateCamera(editorContext, std::nullopt); + } ImGui::Separator(); if (ImGui::MenuItem("应用 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { (void)MetaCoreApplySelectedPrefabInstance(editorContext); @@ -2281,6 +2337,9 @@ private: if (ImGui::MenuItem("创建平面")) { (void)MetaCoreCreateObject(editorContext, "Plane", true, objectId); } + if (ImGui::MenuItem("创建相机")) { + (void)MetaCoreCreateCamera(editorContext, objectId); + } ImGui::Separator(); if (gameObject && gameObject.HasComponent() && @@ -2336,6 +2395,9 @@ private: if (ImGui::MenuItem("创建平面")) { (void)MetaCoreCreateObject(editorContext, "Plane", true, 0); } + if (ImGui::MenuItem("创建相机")) { + (void)MetaCoreCreateCamera(editorContext, 0); + } ImGui::Separator(); if (ImGui::MenuItem("重命名", "F2", false, editorContext.GetActiveObjectId() != 0)) { BeginRename(editorContext, editorContext.GetActiveObjectId()); diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp index 5794f46..171cdb8 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp @@ -2,6 +2,7 @@ #include "MetaCoreEditor/MetaCoreBuiltinModules.h" #include "MetaCoreEditor/MetaCoreEditorServices.h" +#include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCoreEditorCameraController.h" #include @@ -349,7 +350,8 @@ void MetaCoreDrawInfernuxToolbar(MetaCoreEditorContext& editorContext, const ImV sceneView.CameraPosition.y, sceneView.CameraPosition.z ); - ImGui::TextDisabled("快捷键: Q/W/E/R, Z, F, A, G, O"); + ImGui::TextDisabled("场景相机: 右键+WASD/QE 飞行,Shift 加速"); + ImGui::TextDisabled("常用: 中键平移,滚轮缩放,F 聚焦"); ImGui::EndPopup(); } } @@ -585,33 +587,184 @@ bool MetaCoreProjectWorldPointToViewport( return false; } - const glm::mat4 viewMatrix = glm::lookAt( - sceneView.CameraPosition, - sceneView.CameraTarget, - sceneView.CameraUp + const auto projectedPoint = MetaCoreWorldToScreenPoint( + sceneView, + MetaCoreViewportRect{viewportState.Left, viewportState.Top, viewportState.Width, viewportState.Height}, + worldPoint ); - const glm::mat4 projectionMatrix = glm::perspective( - glm::radians(sceneView.VerticalFieldOfViewDegrees), - viewportState.Width / viewportState.Height, - 0.05F, - 500.0F - ); - - const glm::vec4 clipPosition = projectionMatrix * viewMatrix * glm::vec4(worldPoint, 1.0F); - if (clipPosition.w <= 0.0001F) { + if (!projectedPoint.has_value()) { return false; } - const glm::vec3 ndc = glm::vec3(clipPosition) / clipPosition.w; - if (ndc.z < -1.0F || ndc.z > 1.0F) { + if (projectedPoint->z < -1.0F || projectedPoint->z > 1.0F) { return false; } - screenPoint.x = viewportState.Left + ((ndc.x + 1.0F) * 0.5F) * viewportState.Width; - screenPoint.y = viewportState.Top + ((1.0F - ndc.y) * 0.5F) * viewportState.Height; + screenPoint.x = projectedPoint->x; + screenPoint.y = projectedPoint->y; return true; } +struct MetaCoreGameCameraSelectorItem { + MetaCoreId ObjectId = 0; + std::string Label{}; +}; + +[[nodiscard]] std::vector MetaCoreBuildGameCameraSelectorItems( + const MetaCoreScene& scene +) { + std::vector items; + for (const MetaCoreId objectId : scene.BuildHierarchyPreorder()) { + const MetaCoreGameObject object = scene.FindGameObject(objectId); + if (!object || !object.HasComponent()) { + continue; + } + + const MetaCoreCameraComponent& camera = object.GetComponent(); + if (!camera.Enabled) { + continue; + } + + char suffix[96]{}; + std::snprintf( + suffix, + sizeof(suffix), + " #%llu Depth %.2f%s", + static_cast(object.GetId()), + static_cast(camera.Depth), + camera.IsPrimary ? " 主相机" : "" + ); + + MetaCoreGameCameraSelectorItem item; + item.ObjectId = object.GetId(); + item.Label = object.GetName() + suffix; + items.push_back(std::move(item)); + } + return items; +} + +[[nodiscard]] std::string MetaCoreGetGameCameraPreviewLabel( + const MetaCoreScene& scene, + const MetaCoreSceneRenderSyncSnapshot& snapshot +) { + if (!snapshot.PrimaryCamera.has_value()) { + return "没有可用相机"; + } + + const MetaCoreGameObject cameraObject = scene.FindGameObject(snapshot.PrimaryCamera->ObjectId); + if (!cameraObject) { + return "相机已失效"; + } + + return cameraObject.GetName() + " #" + std::to_string(static_cast(cameraObject.GetId())); +} + +[[nodiscard]] bool MetaCoreSetGamePrimaryCamera( + MetaCoreEditorContext& editorContext, + MetaCoreId cameraObjectId +) { + return editorContext.ExecuteSnapshotCommand("切换游戏相机", [&]() { + MetaCoreScene& scene = editorContext.GetScene(); + bool foundTargetCamera = false; + bool changed = false; + + for (MetaCoreGameObject object : scene.GetGameObjects()) { + if (!object.HasComponent()) { + continue; + } + + MetaCoreCameraComponent& camera = object.GetComponent(); + const bool shouldBePrimary = object.GetId() == cameraObjectId; + if (shouldBePrimary) { + if (!camera.Enabled) { + return false; + } + foundTargetCamera = true; + } + + if (camera.IsPrimary != shouldBePrimary) { + camera.IsPrimary = shouldBePrimary; + changed = true; + } + } + + if (!foundTargetCamera || !changed) { + return false; + } + + scene.IncrementRevision(); + return true; + }); +} + +void MetaCoreDrawGameCameraSelector( + MetaCoreEditorContext& editorContext, + const MetaCoreSceneRenderSyncSnapshot& snapshot, + const ImVec2& viewportPos, + const ImVec2& viewportSize +) { + if (viewportSize.x < 160.0F || viewportSize.y < 80.0F) { + return; + } + + MetaCoreScene& scene = editorContext.GetScene(); + const std::vector items = MetaCoreBuildGameCameraSelectorItems(scene); + const MetaCoreId currentCameraId = snapshot.PrimaryCamera.has_value() ? snapshot.PrimaryCamera->ObjectId : 0; + const std::string previewLabel = MetaCoreGetGameCameraPreviewLabel(scene, snapshot); + + constexpr float selectorWidth = 300.0F; + const ImVec2 selectorPos( + viewportPos.x + std::max(8.0F, viewportSize.x - selectorWidth - 12.0F), + viewportPos.y + 12.0F + ); + + ImGui::SetNextWindowPos(selectorPos, ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(selectorWidth, 0.0F), ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.88F); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0F); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0F, 8.0F)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10F, 0.10F, 0.11F, 0.88F)); + + constexpr ImGuiWindowFlags windowFlags = + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_AlwaysAutoResize; + + if (ImGui::Begin("##MetaCoreGameCameraSelector", nullptr, windowFlags)) { + ImGui::TextUnformatted("游戏相机"); + ImGui::SetNextItemWidth(selectorWidth - 20.0F); + if (items.empty()) { + ImGui::BeginDisabled(); + if (ImGui::BeginCombo("##MetaCoreGameCameraCombo", "没有启用的相机")) { + ImGui::EndCombo(); + } + ImGui::EndDisabled(); + } else if (ImGui::BeginCombo("##MetaCoreGameCameraCombo", previewLabel.c_str())) { + for (const MetaCoreGameCameraSelectorItem& item : items) { + const bool selected = item.ObjectId == currentCameraId; + if (ImGui::Selectable(item.Label.c_str(), selected)) { + (void)MetaCoreSetGamePrimaryCamera(editorContext, item.ObjectId); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + if (currentCameraId != 0 && ImGui::SmallButton("选中当前游戏相机")) { + editorContext.SetSelectedObjectId(currentCameraId); + } + } + ImGui::End(); + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); +} + void MetaCoreDrawWorldOriginOverlay( const MetaCoreEditorContext& editorContext, const MetaCoreSceneView& sceneView, @@ -1415,6 +1568,10 @@ void MetaCoreEditorApp::DrawEditorFrame() { bool gizmoCanvasOpen = true; bool gizmoUsing = false; bool gizmoHovering = false; + MetaCoreSceneRenderSyncSnapshot gameViewSnapshot; + bool shouldDrawGameCameraSelector = false; + ImVec2 gameCameraSelectorViewportPos(0.0F, 0.0F); + ImVec2 gameCameraSelectorViewportSize(0.0F, 0.0F); if (ImGui::Begin("MetaCoreSceneGizmoCanvas", &gizmoCanvasOpen, gizmoCanvasFlags)) { // 2. 拿到最精确的屏幕绝对位置和画布大小 @@ -1441,9 +1598,13 @@ void MetaCoreEditorApp::DrawEditorFrame() { mousePosition.y >= viewportState.Top && mousePosition.y <= viewportState.Top + viewportState.Height; viewportState.Focused = viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse; + const bool isGameView = activeTab == 1; // 处理聚焦快捷键 - if (viewportState.Focused && !ImGui::GetIO().WantCaptureKeyboard && EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) { + if (!isGameView && + viewportState.Focused && + !ImGui::GetIO().WantCaptureKeyboard && + EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) { if (MetaCoreGameObject selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject) { EditorContext_->GetCameraController().FocusGameObject(selectedObject); } @@ -1456,14 +1617,34 @@ void MetaCoreEditorApp::DrawEditorFrame() { // 5. 更新相机并构建 SceneView (在渲染之前!) gizmoUsing = ImGuizmo::IsUsing(); - if (!gizmoUsing) { - EditorContext_->GetCameraController().Update(viewportState, EditorContext_->GetInput()); + if (!isGameView && !gizmoUsing) { + EditorContext_->GetCameraController().Update( + viewportState, + EditorContext_->GetInput(), + Window_.GetDeltaSeconds() + ); } MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView(); sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId(); + bool hasGameCamera = true; + if (isGameView) { + static MetaCoreSceneRenderSync gameViewRenderSync; + gameViewSnapshot = gameViewRenderSync.BuildSnapshot(Scene_); + hasGameCamera = MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(gameViewSnapshot, sceneView); + sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId(); + shouldDrawGameCameraSelector = true; + gameCameraSelectorViewportPos = viewportPos; + gameCameraSelectorViewportSize = viewportSize; + } // 6. 驱动 Filament 渲染 - ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView); + if (isGameView) { + if (hasGameCamera) { + ViewportRenderer_.RenderSnapshotToViewport(gameViewSnapshot, &Scene_, sceneView); + } + } else { + ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView); + } // 7. 绘制画面 (应用 UV 翻转:Filament Y轴起点在底部,ImGui 在顶部) void* texPtr = ViewportRenderer_.GetFilamentTexturePointer(); @@ -1484,18 +1665,32 @@ void MetaCoreEditorApp::DrawEditorFrame() { lastDebugPlayModeState = currentPlayModeState; } } - if (texPtr) { + if (isGameView && !hasGameCamera) { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const ImVec2 viewportMax(viewportPos.x + viewportSize.x, viewportPos.y + viewportSize.y); + drawList->AddRectFilled(viewportPos, viewportMax, IM_COL32(25, 26, 30, 255)); + const char* message = "没有可用游戏相机"; + const ImVec2 textSize = ImGui::CalcTextSize(message); + drawList->AddText( + ImVec2( + viewportPos.x + (viewportSize.x - textSize.x) * 0.5F, + viewportPos.y + (viewportSize.y - textSize.y) * 0.5F + ), + IM_COL32(220, 220, 220, 255), + message + ); + } else if (texPtr) { ImGui::Image(texPtr, viewportSize, ImVec2(0, 1), ImVec2(1, 0)); } // 7.5. 视口拖拽接收器:从项目面板拖拽资产进入 3D 视口时,在此处响应放置并完成实例化 - if (ImGui::BeginDragDropTarget()) { + if (!isGameView && ImGui::BeginDragDropTarget()) { (void)MetaCoreHandleProjectAssetDrop(*EditorContext_, std::nullopt); ImGui::EndDragDropTarget(); } // 8. 绘制 Gizmo (此时坐标和矩阵已完全对齐) - if (GMetaCoreEnableImGuizmo) { + if (!isGameView && GMetaCoreEnableImGuizmo) { SceneInteractionService_.HandleGizmoManipulation(*EditorContext_); gizmoHovering = ImGuizmo::IsOver(); gizmoUsing = ImGuizmo::IsUsing(); @@ -1528,16 +1723,20 @@ void MetaCoreEditorApp::DrawEditorFrame() { } // 绘制 Overlays - MetaCoreDrawViewportGridOverlay(*EditorContext_, viewportState); - MetaCoreDrawWorldOriginOverlay(*EditorContext_, sceneView, viewportState); - MetaCoreDrawSelectionBoundsOverlay(*EditorContext_, Scene_, sceneView, viewportState); - MetaCoreDrawSelectionHierarchyOverlay(*EditorContext_, Scene_, sceneView, viewportState); + if (!isGameView) { + MetaCoreDrawViewportGridOverlay(*EditorContext_, viewportState); + MetaCoreDrawWorldOriginOverlay(*EditorContext_, sceneView, viewportState); + MetaCoreDrawSelectionBoundsOverlay(*EditorContext_, Scene_, sceneView, viewportState); + MetaCoreDrawSelectionHierarchyOverlay(*EditorContext_, Scene_, sceneView, viewportState); + } // 启用资产拖拽到 3D 场景视口的高亮响应遮罩,并显示操作提示 - MetaCoreDrawSceneViewportDropTarget(*EditorContext_, viewportPos, viewportSize); + if (!isGameView) { + MetaCoreDrawSceneViewportDropTarget(*EditorContext_, viewportPos, viewportSize); + } // 处理拾取 - if (viewportState.Hovered && !gizmoHovering && !gizmoUsing && + if (!isGameView && viewportState.Hovered && !gizmoHovering && !gizmoUsing && !ImGui::GetIO().WantCaptureMouse && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { const MetaCoreId pickedObjectId = SceneInteractionService_.PickGameObjectFromViewport( Scene_, @@ -1552,7 +1751,17 @@ void MetaCoreEditorApp::DrawEditorFrame() { ImGui::PopStyleVar(3); SceneInteractionService_.HandleGizmoEndUse(*EditorContext_, gizmoUsing); - SceneInteractionService_.DrawViewportToolbar(*EditorContext_); + if (activeTab == 1 && shouldDrawGameCameraSelector) { + MetaCoreDrawGameCameraSelector( + *EditorContext_, + gameViewSnapshot, + gameCameraSelectorViewportPos, + gameCameraSelectorViewportSize + ); + } + if (activeTab == 0) { + SceneInteractionService_.DrawViewportToolbar(*EditorContext_); + } // --- 补丁三重构结束 --- } else { SceneInteractionService_.ResetFrameState(); diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp index b94250e..4af44cf 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp @@ -12,7 +12,11 @@ namespace MetaCore { -void MetaCoreEditorCameraController::Update(const MetaCoreSceneViewportState& viewportState, const MetaCoreInput& input) { +void MetaCoreEditorCameraController::Update( + const MetaCoreSceneViewportState& viewportState, + const MetaCoreInput& input, + float deltaSeconds +) { if (!viewportState.Hovered && !viewportState.Focused) { return; } @@ -57,6 +61,38 @@ void MetaCoreEditorCameraController::Update(const MetaCoreSceneViewportState& vi Target_ -= right * io.MouseDelta.x * panScale; Target_ += up * io.MouseDelta.y * panScale; } + + if (viewportState.Focused && !altDown && ImGui::IsMouseDown(ImGuiMouseButton_Right)) { + glm::vec3 movement(0.0F); + const glm::vec3 forward = GetForwardDirection(); + const glm::vec3 right = GetRightDirection(); + constexpr glm::vec3 worldUp(0.0F, 0.0F, 1.0F); + + if (input.IsKeyDown(MetaCoreInputKey::MoveForward)) { + movement += forward; + } + if (input.IsKeyDown(MetaCoreInputKey::MoveBackward)) { + movement -= forward; + } + if (input.IsKeyDown(MetaCoreInputKey::MoveRight)) { + movement += right; + } + if (input.IsKeyDown(MetaCoreInputKey::MoveLeft)) { + movement -= right; + } + if (input.IsKeyDown(MetaCoreInputKey::MoveUp)) { + movement += worldUp; + } + if (input.IsKeyDown(MetaCoreInputKey::MoveDown)) { + movement -= worldUp; + } + + if (glm::length(movement) > 0.0001F) { + const float clampedDeltaSeconds = std::clamp(deltaSeconds, 0.0F, 0.1F); + const float speed = MoveSpeed_ * (input.IsKeyDown(MetaCoreInputKey::Shift) ? MoveSpeedBoost_ : 1.0F); + Target_ += glm::normalize(movement) * speed * clampedDeltaSeconds; + } + } } void MetaCoreEditorCameraController::FocusGameObject(const MetaCoreGameObject& gameObject) { diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h index 28633c7..6da5901 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h @@ -12,7 +12,7 @@ class MetaCoreGameObject; class MetaCoreEditorCameraController { public: - void Update(const MetaCoreSceneViewportState& viewportState, const MetaCoreInput& input); + void Update(const MetaCoreSceneViewportState& viewportState, const MetaCoreInput& input, float deltaSeconds); void FocusGameObject(const MetaCoreGameObject& gameObject); void FocusBounds(const glm::vec3& center, float radius); [[nodiscard]] MetaCoreSceneView BuildSceneView() const; @@ -29,6 +29,8 @@ private: float YawDegrees_ = 45.0F; float PitchDegrees_ = -35.0F; float FieldOfViewDegrees_ = 60.0F; + float MoveSpeed_ = 6.0F; + float MoveSpeedBoost_ = 4.0F; }; } // namespace MetaCore diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp index 3bc3f8e..52efc2f 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp @@ -103,6 +103,21 @@ private: return true; } +[[nodiscard]] bool MetaCoreCameraComponentEqual(const MetaCoreCameraComponent& lhs, const MetaCoreCameraComponent& rhs) { + return MetaCoreNearlyEqual(lhs.FieldOfViewDegrees, rhs.FieldOfViewDegrees) && + MetaCoreNearlyEqual(lhs.NearClip, rhs.NearClip) && + MetaCoreNearlyEqual(lhs.FarClip, rhs.FarClip) && + lhs.IsPrimary == rhs.IsPrimary && + lhs.Enabled == rhs.Enabled && + lhs.ProjectionMode == rhs.ProjectionMode && + MetaCoreNearlyEqual(lhs.OrthographicSize, rhs.OrthographicSize) && + MetaCoreNearlyEqual(lhs.Depth, rhs.Depth) && + lhs.CullingMask == rhs.CullingMask && + lhs.ClearFlags == rhs.ClearFlags && + MetaCoreNearlyEqualVec3(lhs.BackgroundColor, rhs.BackgroundColor) && + MetaCoreNearlyEqual(lhs.BackgroundAlpha, rhs.BackgroundAlpha); +} + [[nodiscard]] bool MetaCoreGameObjectEqual(const MetaCoreGameObjectData& lhs, const MetaCoreGameObjectData& rhs) { if (lhs.Id != rhs.Id || lhs.ParentId != rhs.ParentId || lhs.Name != rhs.Name || !MetaCoreTransformEqual(lhs.Transform, rhs.Transform)) { return false; @@ -112,11 +127,7 @@ private: return false; } if (lhs.Camera.has_value()) { - if (!MetaCoreNearlyEqual(lhs.Camera->FieldOfViewDegrees, rhs.Camera->FieldOfViewDegrees) || - !MetaCoreNearlyEqual(lhs.Camera->NearClip, rhs.Camera->NearClip) || - !MetaCoreNearlyEqual(lhs.Camera->FarClip, rhs.Camera->FarClip) || - lhs.Camera->IsPrimary != rhs.Camera->IsPrimary || - lhs.Camera->Enabled != rhs.Camera->Enabled) { + if (!MetaCoreCameraComponentEqual(*lhs.Camera, *rhs.Camera)) { return false; } } diff --git a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp index 83a7487..e3ce7d5 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp @@ -4,6 +4,7 @@ #include "MetaCoreEditor/MetaCoreEditorServices.h" #include "MetaCoreEditorCameraController.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" +#include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreGameObject.h" #include "MetaCoreScene/MetaCoreComponents.h" @@ -104,34 +105,13 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( return 0; } - const glm::mat4 viewMatrix = glm::lookAt( - sceneView.CameraPosition, - sceneView.CameraTarget, - sceneView.CameraUp + const MetaCoreCameraRay ray = MetaCoreScreenPointToRay( + sceneView, + MetaCoreViewportRect{viewportState.Left, viewportState.Top, viewportState.Width, viewportState.Height}, + cursorPosition ); - const glm::mat4 projectionMatrix = glm::perspective( - glm::radians(sceneView.VerticalFieldOfViewDegrees), - viewportState.Width / viewportState.Height, - 0.05F, - 500.0F - ); - - const float normalizedX = (localCursor.x / viewportState.Width) * 2.0F - 1.0F; - const float normalizedY = 1.0F - (localCursor.y / viewportState.Height) * 2.0F; - const glm::vec4 rayStartClip(normalizedX, normalizedY, -1.0F, 1.0F); - const glm::vec4 rayEndClip(normalizedX, normalizedY, 1.0F, 1.0F); - const glm::mat4 inverseViewProjection = glm::inverse(projectionMatrix * viewMatrix); - glm::vec4 rayStartWorld = inverseViewProjection * rayStartClip; - glm::vec4 rayEndWorld = inverseViewProjection * rayEndClip; - if (std::abs(rayStartWorld.w) < 0.0001F || std::abs(rayEndWorld.w) < 0.0001F) { - return 0; - } - - rayStartWorld /= rayStartWorld.w; - rayEndWorld /= rayEndWorld.w; - - const glm::vec3 rayOrigin(rayStartWorld); - const glm::vec3 rayDirection = glm::normalize(glm::vec3(rayEndWorld - rayStartWorld)); + const glm::vec3 rayOrigin = ray.Origin; + const glm::vec3 rayDirection = ray.Direction; std::unordered_map worldMatrixCache; const auto buildWorldMatrix = [&](const auto& self, MetaCoreId objectId) -> glm::mat4 { @@ -362,15 +342,27 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont const int h = static_cast(viewportState.Height); const float gizmoAspect = (h > 0) ? (static_cast(w) / static_cast(h)) : 1.0f; const glm::mat4 gizmoViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp); - const glm::mat4 gizmoProjectionMatrix = glm::perspective( - glm::radians(sceneView.VerticalFieldOfViewDegrees), - gizmoAspect, 0.1F, 1000.0F - ); + const glm::mat4 gizmoProjectionMatrix = + sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic + ? glm::ortho( + -sceneView.OrthographicSize * gizmoAspect, + sceneView.OrthographicSize * gizmoAspect, + -sceneView.OrthographicSize, + sceneView.OrthographicSize, + sceneView.NearClip, + sceneView.FarClip + ) + : glm::perspective( + glm::radians(sceneView.VerticalFieldOfViewDegrees), + gizmoAspect, + sceneView.NearClip, + sceneView.FarClip + ); glm::mat4 worldMatrixMetaCore = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), selectedObject.GetId()); const glm::mat4 originalWorldMatrixMetaCore = worldMatrixMetaCore; - ImGuizmo::SetOrthographic(false); + ImGuizmo::SetOrthographic(sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic); ImGuizmo::Enable(true); ImGuizmo::AllowAxisFlip(false); const MetaCoreGizmoOperation currentOp = editorContext.GetGizmoOperation(); diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h index a7dfe24..5fed434 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h @@ -250,6 +250,11 @@ public: std::optional forcedParentId ) = 0; + [[nodiscard]] virtual std::optional CreateCamera( + MetaCoreEditorContext& editorContext, + std::optional forcedParentId + ) = 0; + [[nodiscard]] virtual std::optional InstantiateModelAsset( MetaCoreEditorContext& editorContext, const MetaCoreAssetGuid& assetGuid, diff --git a/Source/MetaCorePlatform/Private/MetaCoreWindow.cpp b/Source/MetaCorePlatform/Private/MetaCoreWindow.cpp index 4abc60d..98a69b5 100644 --- a/Source/MetaCorePlatform/Private/MetaCoreWindow.cpp +++ b/Source/MetaCorePlatform/Private/MetaCoreWindow.cpp @@ -248,6 +248,13 @@ void MetaCoreWindow::BeginFrame() { Impl_->Input.SetKeyState(MetaCoreInputKey::RightAlt, MetaCoreIsVirtualKeyDown(VK_RMENU)); Impl_->Input.SetKeyState(MetaCoreInputKey::Control, MetaCoreIsVirtualKeyDown(VK_LCONTROL) || MetaCoreIsVirtualKeyDown(VK_RCONTROL)); Impl_->Input.SetKeyState(MetaCoreInputKey::Focus, MetaCoreIsVirtualKeyDown('F')); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveForward, MetaCoreIsVirtualKeyDown('W')); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveBackward, MetaCoreIsVirtualKeyDown('S')); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveLeft, MetaCoreIsVirtualKeyDown('A')); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveRight, MetaCoreIsVirtualKeyDown('D')); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveDown, MetaCoreIsVirtualKeyDown('Q')); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveUp, MetaCoreIsVirtualKeyDown('E')); + Impl_->Input.SetKeyState(MetaCoreInputKey::Shift, MetaCoreIsVirtualKeyDown(VK_LSHIFT) || MetaCoreIsVirtualKeyDown(VK_RSHIFT)); Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Left, MetaCoreIsVirtualKeyDown(VK_LBUTTON)); Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Right, MetaCoreIsVirtualKeyDown(VK_RBUTTON)); Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Middle, MetaCoreIsVirtualKeyDown(VK_MBUTTON)); diff --git a/Source/MetaCorePlatform/Private/MetaCoreWindowGlfw.cpp b/Source/MetaCorePlatform/Private/MetaCoreWindowGlfw.cpp index 65bb821..6f45f6b 100644 --- a/Source/MetaCorePlatform/Private/MetaCoreWindowGlfw.cpp +++ b/Source/MetaCorePlatform/Private/MetaCoreWindowGlfw.cpp @@ -176,6 +176,16 @@ void MetaCoreWindow::BeginFrame() { MetaCoreIsGlfwKeyDown(window, GLFW_KEY_LEFT_CONTROL) || MetaCoreIsGlfwKeyDown(window, GLFW_KEY_RIGHT_CONTROL) ); Impl_->Input.SetKeyState(MetaCoreInputKey::Focus, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_F)); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveForward, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_W)); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveBackward, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_S)); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveLeft, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_A)); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveRight, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_D)); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveDown, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_Q)); + Impl_->Input.SetKeyState(MetaCoreInputKey::MoveUp, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_E)); + Impl_->Input.SetKeyState( + MetaCoreInputKey::Shift, + MetaCoreIsGlfwKeyDown(window, GLFW_KEY_LEFT_SHIFT) || MetaCoreIsGlfwKeyDown(window, GLFW_KEY_RIGHT_SHIFT) + ); Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Left, MetaCoreIsGlfwMouseButtonDown(window, GLFW_MOUSE_BUTTON_LEFT)); Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Right, MetaCoreIsGlfwMouseButtonDown(window, GLFW_MOUSE_BUTTON_RIGHT)); Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Middle, MetaCoreIsGlfwMouseButtonDown(window, GLFW_MOUSE_BUTTON_MIDDLE)); diff --git a/Source/MetaCorePlatform/Public/MetaCorePlatform/MetaCoreInput.h b/Source/MetaCorePlatform/Public/MetaCorePlatform/MetaCoreInput.h index 7b2ce27..d50f2ce 100644 --- a/Source/MetaCorePlatform/Public/MetaCorePlatform/MetaCoreInput.h +++ b/Source/MetaCorePlatform/Public/MetaCorePlatform/MetaCoreInput.h @@ -12,6 +12,13 @@ enum class MetaCoreInputKey : unsigned int { RightAlt, Control, Focus, + MoveForward, + MoveBackward, + MoveLeft, + MoveRight, + MoveDown, + MoveUp, + Shift, Count }; diff --git a/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp b/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp index b4f2ee5..05217bc 100644 --- a/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp @@ -103,6 +103,21 @@ void MetaCoreEditorViewportRenderer::RenderSceneToViewport(MetaCoreScene& scene, FilamentSceneBridge_.SyncScene(scene, false, useScenePrimaryCamera); } +void MetaCoreEditorViewportRenderer::RenderSnapshotToViewport( + const MetaCoreSceneRenderSyncSnapshot& snapshot, + MetaCoreScene* scene, + const MetaCoreSceneView& sceneView +) { + if (RenderDevice_ == nullptr) { + return; + } + + RenderDevice_->SetSceneViewportRect(ViewportRect_); + FilamentSceneBridge_.Resize(static_cast(ViewportRect_.Width), static_cast(ViewportRect_.Height)); + FilamentSceneBridge_.ApplySceneView(sceneView); + FilamentSceneBridge_.SyncScene(snapshot, scene, false, false); +} + void MetaCoreEditorViewportRenderer::RenderAll() { FilamentSceneBridge_.RenderAll(); } diff --git a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp index e399426..833773d 100644 --- a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp @@ -1052,6 +1052,7 @@ public: void ApplySceneView(const MetaCoreSceneView& sceneView) { if (!Camera_ || !View_) return; + CurrentSceneView_ = sceneView; // 1. 视图矩阵同步 const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp); @@ -1060,16 +1061,45 @@ public: // 2. 投影矩阵:回归 Filament 原生设置 const auto& viewport = View_->getViewport(); - float aspect = (viewport.height > 0) ? (static_cast(viewport.width) / static_cast(viewport.height)) : 1.0f; + const float aspect = (viewport.height > 0) ? (static_cast(viewport.width) / static_cast(viewport.height)) : 1.0f; + const double nearClip = std::max(0.0001, static_cast(sceneView.NearClip)); + const double farClip = std::max(nearClip + 0.0001, static_cast(sceneView.FarClip)); - Camera_->setProjection( - sceneView.VerticalFieldOfViewDegrees, - aspect, - sceneView.NearClip, - sceneView.FarClip, - filament::Camera::Fov::VERTICAL - ); + if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) { + const double halfHeight = std::max(0.0001, static_cast(sceneView.OrthographicSize)); + const double halfWidth = halfHeight * std::max(0.0001, static_cast(aspect)); + Camera_->setProjection( + filament::Camera::Projection::ORTHO, + -halfWidth, + halfWidth, + -halfHeight, + halfHeight, + nearClip, + farClip + ); + } else { + Camera_->setProjection( + std::clamp(static_cast(sceneView.VerticalFieldOfViewDegrees), 1.0, 179.0), + std::max(0.0001, static_cast(aspect)), + nearClip, + farClip, + filament::Camera::Fov::VERTICAL + ); + } } + + [[nodiscard]] filament::Renderer::ClearOptions BuildSceneClearOptions() const { + filament::Renderer::ClearOptions options; + options.clearColor = { + CurrentSceneView_.BackgroundColor.x, + CurrentSceneView_.BackgroundColor.y, + CurrentSceneView_.BackgroundColor.z, + CurrentSceneView_.BackgroundAlpha + }; + options.clear = CurrentSceneView_.ClearFlags != MetaCoreCameraClearFlags::DontClear; + return options; + } + void RenderAll() { if (!Renderer_ || !SwapChain_ || !View_) { return; @@ -1095,9 +1125,7 @@ public: if (Renderer_->beginFrame(SwapChain_)) { if (RenderTarget_) { // 1. 渲染 3D 离屏视口 (输出到 RenderTarget) - filament::Renderer::ClearOptions options; - options.clearColor = { 0.11f, 0.12f, 0.14f, 1.0f }; // 深灰色 - options.clear = true; + filament::Renderer::ClearOptions options = BuildSceneClearOptions(); Renderer_->setClearOptions(options); Renderer_->render(View_); @@ -1111,9 +1139,7 @@ public: } } else { // 3. 直接上屏渲染 - filament::Renderer::ClearOptions options; - options.clearColor = { 0.11f, 0.12f, 0.14f, 1.0f }; // 深灰色 - options.clear = true; + filament::Renderer::ClearOptions options = BuildSceneClearOptions(); Renderer_->setClearOptions(options); Renderer_->render(View_); @@ -1532,6 +1558,7 @@ private: MetaCoreSceneRenderSync RenderSync_{}; MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{}; + MetaCoreSceneView CurrentSceneView_{}; std::unordered_map ObjectWorldMatrices_{}; std::unordered_map> ObjectToFilamentEntity_; std::unordered_map LoadedAssets_; diff --git a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp index bd64c51..825a61f 100644 --- a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp @@ -4,7 +4,10 @@ #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreTransformUtils.h" +#include +#include #include +#include #include #include @@ -29,6 +32,45 @@ namespace { 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 { @@ -144,15 +186,21 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met if (gameObject.HasComponent()) { const auto& camera = gameObject.GetComponent(); - MetaCoreRenderSyncCamera syncCamera{ - objectId, - camera.IsPrimary, - camera.FieldOfViewDegrees, - camera.NearClip, - camera.FarClip, - worldMatrix, - camera.Enabled - }; + 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; snapshot.Cameras.push_back(syncCamera); if (syncCamera.IsPrimary && syncCamera.Enabled && !snapshot.PrimaryCamera.has_value()) { snapshot.PrimaryCamera = syncCamera; @@ -172,14 +220,17 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met } if (!snapshot.PrimaryCamera.has_value() && !snapshot.Cameras.empty()) { - for (const auto& cam : snapshot.Cameras) { - if (cam.Enabled) { - snapshot.PrimaryCamera = cam; - break; + const MetaCoreRenderSyncCamera* bestCamera = nullptr; + for (const MetaCoreRenderSyncCamera& camera : snapshot.Cameras) { + if (!camera.Enabled) { + continue; + } + if (bestCamera == nullptr || camera.Depth < bestCamera->Depth) { + bestCamera = &camera; } } - if (!snapshot.PrimaryCamera.has_value()) { - snapshot.PrimaryCamera = snapshot.Cameras.front(); + if (bestCamera != nullptr) { + snapshot.PrimaryCamera = *bestCamera; } } @@ -195,6 +246,17 @@ bool MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera( } 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)); @@ -212,7 +274,76 @@ bool MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera( 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 diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h index 768626d..b496e1f 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h @@ -2,6 +2,7 @@ #include "MetaCoreRender/MetaCoreFilamentSceneBridge.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" +#include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include @@ -19,6 +20,7 @@ public: void Shutdown(); void SetViewportRect(const MetaCoreViewportRect& viewportRect); void RenderSceneToViewport(MetaCoreScene& scene, const MetaCoreSceneView& sceneView, bool useScenePrimaryCamera = false); + void RenderSnapshotToViewport(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, const MetaCoreSceneView& sceneView); void RenderAll(); void SetProjectRootPath(const std::filesystem::path& projectRootPath); [[nodiscard]] uint32_t GetFilamentGLTextureId() const; diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRenderTypes.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRenderTypes.h index 86afaa2..5412fde 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRenderTypes.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRenderTypes.h @@ -1,8 +1,11 @@ #pragma once #include "MetaCoreFoundation/MetaCoreId.h" +#include "MetaCoreScene/MetaCoreComponents.h" #include +#include +#include namespace MetaCore { @@ -20,9 +23,21 @@ struct MetaCoreSceneView { float VerticalFieldOfViewDegrees = 60.0F; float NearClip = 0.1F; float FarClip = 1000.0F; + MetaCoreCameraProjectionMode ProjectionMode = MetaCoreCameraProjectionMode::Perspective; + float OrthographicSize = 5.0F; + MetaCoreCameraClearFlags ClearFlags = MetaCoreCameraClearFlags::Skybox; + glm::vec3 BackgroundColor{0.1F, 0.1F, 0.1F}; + float BackgroundAlpha = 1.0F; + std::uint32_t CullingMask = 0xFFFFFFFFU; + std::optional SourceCameraObjectId{}; MetaCoreId SelectedObjectId = 0; }; +struct MetaCoreCameraRay { + glm::vec3 Origin{0.0F, 0.0F, 0.0F}; + glm::vec3 Direction{0.0F, 0.0F, -1.0F}; +}; + struct MetaCoreMeshVertex { float px, py, pz; float nx, ny, nz; diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h index 61071c1..871dcfa 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h @@ -2,9 +2,11 @@ #include "MetaCoreFoundation/MetaCoreAssetGuid.h" #include "MetaCoreFoundation/MetaCoreId.h" +#include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreScene/MetaCoreComponents.h" #include +#include #include #include @@ -15,7 +17,6 @@ namespace MetaCore { class MetaCoreScene; -struct MetaCoreSceneView; struct MetaCoreRenderSyncRenderable { MetaCoreId ObjectId = 0; @@ -45,6 +46,13 @@ struct MetaCoreRenderSyncCamera { float FarClip = 100.0F; glm::mat4 WorldMatrix{1.0F}; bool Enabled = true; + MetaCoreCameraProjectionMode ProjectionMode = MetaCoreCameraProjectionMode::Perspective; + float OrthographicSize = 5.0F; + float Depth = 0.0F; + std::uint32_t CullingMask = 0xFFFFFFFFU; + MetaCoreCameraClearFlags ClearFlags = MetaCoreCameraClearFlags::Skybox; + glm::vec3 BackgroundColor{0.1F, 0.1F, 0.1F}; + float BackgroundAlpha = 1.0F; }; struct MetaCoreRenderSyncLight { @@ -73,9 +81,25 @@ public: const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreSceneView& sceneView ); + [[nodiscard]] static bool TryBuildSceneViewFromCamera( + const MetaCoreRenderSyncCamera& camera, + MetaCoreSceneView& sceneView + ); private: mutable std::uint64_t FrameId_ = 0; }; +[[nodiscard]] MetaCoreCameraRay MetaCoreScreenPointToRay( + const MetaCoreSceneView& sceneView, + const MetaCoreViewportRect& viewportRect, + const glm::vec2& screenPosition +); + +[[nodiscard]] std::optional MetaCoreWorldToScreenPoint( + const MetaCoreSceneView& sceneView, + const MetaCoreViewportRect& viewportRect, + const glm::vec3& worldPosition +); + } // namespace MetaCore diff --git a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp index 5093367..0963156 100644 --- a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp +++ b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp @@ -142,6 +142,13 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo else if (camField.Name == "FarClip") camJson["FarClip"] = obj.Camera->FarClip; else if (camField.Name == "IsPrimary") camJson["IsPrimary"] = obj.Camera->IsPrimary; else if (camField.Name == "Enabled") camJson["Enabled"] = obj.Camera->Enabled; + else if (camField.Name == "ProjectionMode") camJson["ProjectionMode"] = static_cast(obj.Camera->ProjectionMode); + else if (camField.Name == "OrthographicSize") camJson["OrthographicSize"] = obj.Camera->OrthographicSize; + else if (camField.Name == "Depth") camJson["Depth"] = obj.Camera->Depth; + else if (camField.Name == "CullingMask") camJson["CullingMask"] = obj.Camera->CullingMask; + else if (camField.Name == "ClearFlags") camJson["ClearFlags"] = static_cast(obj.Camera->ClearFlags); + else if (camField.Name == "BackgroundColor") camJson["BackgroundColor"] = Vec3ToJson(obj.Camera->BackgroundColor); + else if (camField.Name == "BackgroundAlpha") camJson["BackgroundAlpha"] = obj.Camera->BackgroundAlpha; } objJson["Camera"] = camJson; } @@ -288,6 +295,13 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me else if (camField.Name == "FarClip" && camJson.contains("FarClip")) cam.FarClip = camJson["FarClip"].get(); else if (camField.Name == "IsPrimary" && camJson.contains("IsPrimary")) cam.IsPrimary = camJson["IsPrimary"].get(); else if (camField.Name == "Enabled" && camJson.contains("Enabled")) cam.Enabled = camJson["Enabled"].get(); + else if (camField.Name == "ProjectionMode" && camJson.contains("ProjectionMode")) cam.ProjectionMode = static_cast(camJson["ProjectionMode"].get()); + else if (camField.Name == "OrthographicSize" && camJson.contains("OrthographicSize")) cam.OrthographicSize = camJson["OrthographicSize"].get(); + else if (camField.Name == "Depth" && camJson.contains("Depth")) cam.Depth = camJson["Depth"].get(); + else if (camField.Name == "CullingMask" && camJson.contains("CullingMask")) cam.CullingMask = camJson["CullingMask"].get(); + else if (camField.Name == "ClearFlags" && camJson.contains("ClearFlags")) cam.ClearFlags = static_cast(camJson["ClearFlags"].get()); + else if (camField.Name == "BackgroundColor" && camJson.contains("BackgroundColor")) cam.BackgroundColor = JsonToVec3(camJson["BackgroundColor"]); + else if (camField.Name == "BackgroundAlpha" && camJson.contains("BackgroundAlpha")) cam.BackgroundAlpha = camJson["BackgroundAlpha"].get(); } obj.Camera = cam; } diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h index 4bd6e04..3db0f30 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h @@ -31,6 +31,20 @@ enum class MetaCoreMeshAlphaMode { Blend }; +MC_ENUM() +enum class MetaCoreCameraProjectionMode { + Perspective = 0, + Orthographic +}; + +MC_ENUM() +enum class MetaCoreCameraClearFlags { + Skybox = 0, + SolidColor, + DepthOnly, + DontClear +}; + MC_STRUCT() struct MetaCoreTransformComponent { MC_GENERATED_BODY() @@ -57,6 +71,20 @@ struct MetaCoreCameraComponent { bool IsPrimary = false; MC_PROPERTY() bool Enabled = true; + MC_PROPERTY() + MetaCoreCameraProjectionMode ProjectionMode = MetaCoreCameraProjectionMode::Perspective; + MC_PROPERTY() + float OrthographicSize = 5.0F; + MC_PROPERTY() + float Depth = 0.0F; + MC_PROPERTY() + std::uint32_t CullingMask = 0xFFFFFFFFU; + MC_PROPERTY() + MetaCoreCameraClearFlags ClearFlags = MetaCoreCameraClearFlags::Skybox; + MC_PROPERTY() + glm::vec3 BackgroundColor{0.1F, 0.1F, 0.1F}; + MC_PROPERTY() + float BackgroundAlpha = 1.0F; }; MC_STRUCT() diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index 81e9def..5372ca6 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -647,6 +647,82 @@ void MetaCoreTestComponentRegistryDescriptors() { _putenv_s("METACORE_PROJECT_PATH", ""); } +void MetaCoreTestSceneEditingServiceCreateCamera() { + { + _putenv_s("METACORE_PROJECT_PATH", ""); + + MetaCore::MetaCoreWindow window; + MetaCore::MetaCoreRenderDevice renderDevice; + MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; + MetaCore::MetaCoreScene scene; + MetaCore::MetaCoreLogService logService; + MetaCore::MetaCoreEditorModuleRegistry moduleRegistry; + auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule(); + coreServicesModule->Startup(moduleRegistry); + + MetaCore::MetaCoreEditorContext editorContext( + window, + renderDevice, + viewportRenderer, + scene, + logService, + moduleRegistry + ); + + const auto sceneEditingService = moduleRegistry.ResolveService(); + MetaCoreExpect(sceneEditingService != nullptr, "应解析到 SceneEditingService"); + const auto cameraId = sceneEditingService->CreateCamera(editorContext, std::nullopt); + MetaCoreExpect(cameraId.has_value(), "应能创建相机对象"); + MetaCore::MetaCoreGameObject cameraObject = scene.FindGameObject(*cameraId); + MetaCoreExpect(cameraObject, "创建后的相机对象应存在"); + MetaCoreExpect(cameraObject.HasComponent(), "创建后的对象应带 Camera 组件"); + MetaCoreExpect(cameraObject.GetComponent().IsPrimary, "空场景创建的首个相机应成为主相机"); + MetaCoreExpect(editorContext.GetActiveObjectId() == *cameraId, "创建相机后应选中新相机"); + MetaCoreExpect(editorContext.GetCommandService().CanUndo(), "创建相机应进入 Undo 栈"); + + MetaCoreExpect(editorContext.UndoCommand(), "创建相机应可 Undo"); + MetaCoreExpect(!scene.FindGameObject(*cameraId), "Undo 后相机对象应被移除"); + + coreServicesModule->Shutdown(moduleRegistry); + moduleRegistry.ShutdownServices(); + _putenv_s("METACORE_PROJECT_PATH", ""); + } + + { + _putenv_s("METACORE_PROJECT_PATH", ""); + + MetaCore::MetaCoreWindow window; + MetaCore::MetaCoreRenderDevice renderDevice; + MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; + MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene(); + MetaCore::MetaCoreLogService logService; + MetaCore::MetaCoreEditorModuleRegistry moduleRegistry; + auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule(); + coreServicesModule->Startup(moduleRegistry); + + MetaCore::MetaCoreEditorContext editorContext( + window, + renderDevice, + viewportRenderer, + scene, + logService, + moduleRegistry + ); + + const auto sceneEditingService = moduleRegistry.ResolveService(); + MetaCoreExpect(sceneEditingService != nullptr, "默认场景应解析到 SceneEditingService"); + const auto cameraId = sceneEditingService->CreateCamera(editorContext, std::nullopt); + MetaCoreExpect(cameraId.has_value(), "默认场景应能创建第二个相机"); + const MetaCore::MetaCoreGameObject cameraObject = scene.FindGameObject(*cameraId); + MetaCoreExpect(cameraObject.HasComponent(), "第二个相机对象应带 Camera 组件"); + MetaCoreExpect(!cameraObject.GetComponent().IsPrimary, "已有主相机时新相机不应抢占主相机"); + + coreServicesModule->Shutdown(moduleRegistry); + moduleRegistry.ShutdownServices(); + _putenv_s("METACORE_PROJECT_PATH", ""); + } +} + void MetaCoreTestPlayModeStateMachineAndLifecycle() { _putenv_s("METACORE_PROJECT_PATH", ""); @@ -1173,6 +1249,143 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() { MetaCoreExpect(std::abs(primarySceneView.FarClip - 250.0F) <= 0.0001F, "主相机 FarClip 应同步"); } +void MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults() { + MetaCore::MetaCoreTypeRegistry registry; + MetaCore::MetaCoreRegisterFoundationGeneratedTypes(registry); + MetaCore::MetaCoreRegisterSceneGeneratedTypes(registry); + + const std::filesystem::path tempRoot = std::filesystem::temp_directory_path() / "MetaCoreCameraJsonRoundTrip"; + std::filesystem::remove_all(tempRoot); + std::filesystem::create_directories(tempRoot); + const std::filesystem::path scenePath = tempRoot / "camera.mcscene"; + + MetaCore::MetaCoreSceneDocument sceneDocument; + sceneDocument.Name = "CameraFields"; + MetaCore::MetaCoreGameObjectData cameraObject; + cameraObject.Id = 1001; + cameraObject.Name = "Camera"; + cameraObject.Camera = MetaCore::MetaCoreCameraComponent{}; + cameraObject.Camera->FieldOfViewDegrees = 72.0F; + cameraObject.Camera->NearClip = 0.2F; + cameraObject.Camera->FarClip = 600.0F; + cameraObject.Camera->IsPrimary = true; + cameraObject.Camera->ProjectionMode = MetaCore::MetaCoreCameraProjectionMode::Orthographic; + cameraObject.Camera->OrthographicSize = 8.0F; + cameraObject.Camera->Depth = -2.0F; + cameraObject.Camera->CullingMask = 0x0000000FU; + cameraObject.Camera->ClearFlags = MetaCore::MetaCoreCameraClearFlags::SolidColor; + cameraObject.Camera->BackgroundColor = glm::vec3(0.2F, 0.3F, 0.4F); + cameraObject.Camera->BackgroundAlpha = 0.75F; + sceneDocument.GameObjects.push_back(cameraObject); + + MetaCoreExpect( + MetaCore::MetaCoreSceneSerializer::SaveSceneToJson(scenePath, sceneDocument, registry), + "Camera JSON round-trip 应能保存场景" + ); + const auto loadedSceneDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(scenePath, registry); + MetaCoreExpect(loadedSceneDocument.has_value(), "Camera JSON round-trip 应能读取场景"); + MetaCoreExpect(loadedSceneDocument->GameObjects.size() == 1, "Camera JSON round-trip 应保留对象"); + const auto& loadedCamera = *loadedSceneDocument->GameObjects.front().Camera; + MetaCoreExpect(loadedCamera.ProjectionMode == MetaCore::MetaCoreCameraProjectionMode::Orthographic, "Camera ProjectionMode 应保留"); + MetaCoreExpect(std::abs(loadedCamera.OrthographicSize - 8.0F) <= 0.0001F, "Camera OrthographicSize 应保留"); + MetaCoreExpect(std::abs(loadedCamera.Depth - -2.0F) <= 0.0001F, "Camera Depth 应保留"); + MetaCoreExpect(loadedCamera.CullingMask == 0x0000000FU, "Camera CullingMask 应保留"); + MetaCoreExpect(loadedCamera.ClearFlags == MetaCore::MetaCoreCameraClearFlags::SolidColor, "Camera ClearFlags 应保留"); + MetaCoreExpectVec3Near(loadedCamera.BackgroundColor, glm::vec3(0.2F, 0.3F, 0.4F), "Camera BackgroundColor 应保留"); + MetaCoreExpect(std::abs(loadedCamera.BackgroundAlpha - 0.75F) <= 0.0001F, "Camera BackgroundAlpha 应保留"); + + const std::filesystem::path legacyScenePath = tempRoot / "legacy_camera.mcscene"; + { + std::ofstream legacyScene(legacyScenePath, std::ios::trunc); + legacyScene + << "{\n" + << " \"SceneName\": \"LegacyCamera\",\n" + << " \"GameObjects\": [{\n" + << " \"Id\": 42,\n" + << " \"ParentId\": 0,\n" + << " \"Name\": \"Legacy Camera\",\n" + << " \"Transform\": {\"Position\": [0, 0, 0], \"RotationEulerDegrees\": [0, 0, 0], \"Scale\": [1, 1, 1]},\n" + << " \"Camera\": {\"FieldOfViewDegrees\": 61.0, \"NearClip\": 0.3, \"FarClip\": 333.0, \"IsPrimary\": true, \"Enabled\": true}\n" + << " }]\n" + << "}\n"; + } + const auto legacyDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(legacyScenePath, registry); + MetaCoreExpect(legacyDocument.has_value(), "旧 Camera JSON 缺字段应能读取"); + const auto& legacyCamera = *legacyDocument->GameObjects.front().Camera; + MetaCoreExpect(legacyCamera.ProjectionMode == MetaCore::MetaCoreCameraProjectionMode::Perspective, "旧 Camera ProjectionMode 应使用默认值"); + MetaCoreExpect(std::abs(legacyCamera.OrthographicSize - 5.0F) <= 0.0001F, "旧 Camera OrthographicSize 应使用默认值"); + MetaCoreExpect(legacyCamera.ClearFlags == MetaCore::MetaCoreCameraClearFlags::Skybox, "旧 Camera ClearFlags 应使用默认值"); + + std::filesystem::remove_all(tempRoot); +} + +void MetaCoreTestCameraSelectionAndProjectionUtilities() { + MetaCore::MetaCoreScene scene; + + MetaCore::MetaCoreGameObject disabledPrimary = scene.CreateGameObject("DisabledPrimary"); + auto& disabledPrimaryCamera = disabledPrimary.AddComponent(); + disabledPrimaryCamera.IsPrimary = true; + disabledPrimaryCamera.Enabled = false; + disabledPrimaryCamera.Depth = -100.0F; + + MetaCore::MetaCoreGameObject depthCamera = scene.CreateGameObject("DepthCamera"); + auto& depthCameraComponent = depthCamera.AddComponent(); + depthCameraComponent.Depth = -5.0F; + depthCameraComponent.ProjectionMode = MetaCore::MetaCoreCameraProjectionMode::Orthographic; + depthCameraComponent.OrthographicSize = 4.0F; + depthCameraComponent.ClearFlags = MetaCore::MetaCoreCameraClearFlags::SolidColor; + depthCameraComponent.BackgroundColor = glm::vec3(0.4F, 0.5F, 0.6F); + + MetaCore::MetaCoreGameObject fallbackCamera = scene.CreateGameObject("FallbackCamera"); + auto& fallbackCameraComponent = fallbackCamera.AddComponent(); + fallbackCameraComponent.Depth = 3.0F; + + MetaCore::MetaCoreSceneRenderSync renderSync; + MetaCore::MetaCoreSceneRenderSyncSnapshot snapshot = renderSync.BuildSnapshot(scene); + MetaCoreExpect(snapshot.PrimaryCamera.has_value(), "无启用主相机时应按 Depth 自动选择相机"); + MetaCoreExpect(snapshot.PrimaryCamera->ObjectId == depthCamera.GetId(), "Depth 最小的启用相机应成为 Game 相机"); + + fallbackCameraComponent.IsPrimary = true; + snapshot = renderSync.BuildSnapshot(scene); + MetaCoreExpect(snapshot.PrimaryCamera.has_value(), "启用主相机应可被选中"); + MetaCoreExpect(snapshot.PrimaryCamera->ObjectId == fallbackCamera.GetId(), "启用主相机应优先于 Depth"); + + fallbackCameraComponent.Enabled = false; + depthCameraComponent.Enabled = false; + snapshot = renderSync.BuildSnapshot(scene); + MetaCoreExpect(!snapshot.PrimaryCamera.has_value(), "全部相机禁用时不应选择 Game 相机"); + MetaCore::MetaCoreSceneView noCameraView; + MetaCoreExpect(!MetaCore::MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(snapshot, noCameraView), "无 Game 相机时不应构建 SceneView"); + + MetaCore::MetaCoreSceneView perspectiveView; + perspectiveView.CameraPosition = glm::vec3(0.0F, 0.0F, 0.0F); + perspectiveView.CameraTarget = glm::vec3(0.0F, 1.0F, 0.0F); + perspectiveView.CameraUp = glm::vec3(0.0F, 0.0F, 1.0F); + perspectiveView.VerticalFieldOfViewDegrees = 60.0F; + const MetaCore::MetaCoreViewportRect viewport{0.0F, 0.0F, 100.0F, 100.0F}; + const MetaCore::MetaCoreCameraRay centerRay = + MetaCore::MetaCoreScreenPointToRay(perspectiveView, viewport, glm::vec2(50.0F, 50.0F)); + MetaCoreExpectVec3Near(centerRay.Origin, glm::vec3(0.0F), "透视中心射线起点应为相机位置"); + MetaCoreExpectVec3Near(centerRay.Direction, glm::vec3(0.0F, 1.0F, 0.0F), "透视中心射线方向应等于相机 forward"); + + MetaCore::MetaCoreSceneView orthographicView = perspectiveView; + orthographicView.ProjectionMode = MetaCore::MetaCoreCameraProjectionMode::Orthographic; + orthographicView.OrthographicSize = 5.0F; + const MetaCore::MetaCoreCameraRay orthoRightRay = + MetaCore::MetaCoreScreenPointToRay(orthographicView, viewport, glm::vec2(100.0F, 50.0F)); + MetaCoreExpectVec3Near(orthoRightRay.Origin, glm::vec3(5.0F, 0.0F, 0.0F), "正交右侧射线起点应按视平面偏移"); + MetaCoreExpectVec3Near(orthoRightRay.Direction, glm::vec3(0.0F, 1.0F, 0.0F), "正交射线方向应等于相机 forward"); + + const auto projectedPoint = MetaCore::MetaCoreWorldToScreenPoint( + perspectiveView, + viewport, + glm::vec3(0.0F, 5.0F, 0.0F) + ); + MetaCoreExpect(projectedPoint.has_value(), "WorldToScreen 应能投影相机前方点"); + MetaCoreExpect(std::abs(projectedPoint->x - 50.0F) <= 0.001F, "WorldToScreen X 应位于中心"); + MetaCoreExpect(std::abs(projectedPoint->y - 50.0F) <= 0.001F, "WorldToScreen Y 应位于中心"); +} + void MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson() { const std::filesystem::path tempProjectRoot = std::filesystem::temp_directory_path() / "MetaCoreSceneRoundTripProject"; @@ -2465,6 +2678,13 @@ void MetaCoreTestComponentRegistryOperations() { object.GetComponent().NearClip = 0.25F; object.GetComponent().FarClip = 250.0F; object.GetComponent().IsPrimary = true; + object.GetComponent().ProjectionMode = MetaCore::MetaCoreCameraProjectionMode::Orthographic; + object.GetComponent().OrthographicSize = 9.0F; + object.GetComponent().Depth = -3.0F; + object.GetComponent().CullingMask = 0x000000FFU; + object.GetComponent().ClearFlags = MetaCore::MetaCoreCameraClearFlags::SolidColor; + object.GetComponent().BackgroundColor = glm::vec3(0.1F, 0.2F, 0.3F); + object.GetComponent().BackgroundAlpha = 0.5F; MetaCoreExpect(componentRegistry->CopyComponent("Camera", object), "应能复制 Camera 组件值"); MetaCore::MetaCoreGameObject pasteTarget = scene.CreateGameObject("PasteTarget"); @@ -2480,6 +2700,23 @@ void MetaCoreTestComponentRegistryOperations() { MetaCoreExpect(std::abs(pasteTarget.GetComponent().NearClip - 0.25F) <= 0.0001F, "粘贴后 NearClip 应一致"); MetaCoreExpect(std::abs(pasteTarget.GetComponent().FarClip - 250.0F) <= 0.0001F, "粘贴后 FarClip 应一致"); MetaCoreExpect(pasteTarget.GetComponent().IsPrimary, "粘贴后 IsPrimary 应一致"); + MetaCoreExpect( + pasteTarget.GetComponent().ProjectionMode == MetaCore::MetaCoreCameraProjectionMode::Orthographic, + "粘贴后 ProjectionMode 应一致" + ); + MetaCoreExpect(std::abs(pasteTarget.GetComponent().OrthographicSize - 9.0F) <= 0.0001F, "粘贴后 OrthographicSize 应一致"); + MetaCoreExpect(std::abs(pasteTarget.GetComponent().Depth - -3.0F) <= 0.0001F, "粘贴后 Depth 应一致"); + MetaCoreExpect(pasteTarget.GetComponent().CullingMask == 0x000000FFU, "粘贴后 CullingMask 应一致"); + MetaCoreExpect( + pasteTarget.GetComponent().ClearFlags == MetaCore::MetaCoreCameraClearFlags::SolidColor, + "粘贴后 ClearFlags 应一致" + ); + MetaCoreExpectVec3Near( + pasteTarget.GetComponent().BackgroundColor, + glm::vec3(0.1F, 0.2F, 0.3F), + "粘贴后 BackgroundColor 应一致" + ); + MetaCoreExpect(std::abs(pasteTarget.GetComponent().BackgroundAlpha - 0.5F) <= 0.0001F, "粘贴后 BackgroundAlpha 应一致"); MetaCoreExpect(std::abs(secondPasteTarget.GetComponent().FieldOfViewDegrees - 77.0F) <= 0.0001F, "第二个对象粘贴后 FOV 应一致"); MetaCoreExpect(componentRegistry->CopyComponent("Script", object), "应能复制 Script 组件值"); @@ -2497,8 +2734,13 @@ void MetaCoreTestComponentRegistryOperations() { MetaCoreExpect(cameraDescriptor->ResetComponent != nullptr, "Camera descriptor 应提供 Reset"); pasteTarget.GetComponent().FieldOfViewDegrees = 10.0F; + pasteTarget.GetComponent().ProjectionMode = MetaCore::MetaCoreCameraProjectionMode::Orthographic; MetaCoreExpect(cameraDescriptor->ResetComponent(pasteTarget), "应能重置 Camera"); MetaCoreExpect(std::abs(pasteTarget.GetComponent().FieldOfViewDegrees - 60.0F) <= 0.0001F, "重置后 Camera 应恢复默认值"); + MetaCoreExpect( + pasteTarget.GetComponent().ProjectionMode == MetaCore::MetaCoreCameraProjectionMode::Perspective, + "重置后 Camera ProjectionMode 应恢复默认值" + ); MetaCoreExpect(scriptDescriptor->ResetComponent != nullptr, "Script descriptor 应提供 Reset"); MetaCoreExpect(scriptDescriptor->ResetComponent(pasteTarget), "应能重置 Script"); MetaCoreExpect(pasteTarget.GetComponent().Instances.empty(), "重置后 Script 实例列表应清空"); @@ -3977,6 +4219,8 @@ int main() { MetaCoreTestAssetDatabaseMovePathUpdatesProjectDescriptor(); std::cout << "[RUN] MetaCoreTestComponentRegistryDescriptors..." << std::endl; MetaCoreTestComponentRegistryDescriptors(); + std::cout << "[RUN] MetaCoreTestSceneEditingServiceCreateCamera..." << std::endl; + MetaCoreTestSceneEditingServiceCreateCamera(); std::cout << "[RUN] MetaCoreTestPlayModeStateMachineAndLifecycle..." << std::endl; MetaCoreTestPlayModeStateMachineAndLifecycle(); std::cout << "[RUN] MetaCoreTestPlayModeRotatorRestoresPrePlaySnapshotOnStop..." << std::endl; @@ -3991,6 +4235,10 @@ int main() { MetaCoreTestNativeScriptBehavioursRestorePrePlaySnapshotOnStop(); std::cout << "[RUN] MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot..." << std::endl; MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot(); + std::cout << "[RUN] MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults..." << std::endl; + MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults(); + std::cout << "[RUN] MetaCoreTestCameraSelectionAndProjectionUtilities..." << std::endl; + MetaCoreTestCameraSelectionAndProjectionUtilities(); std::cout << "[RUN] MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson..." << std::endl; MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson(); std::cout << "[RUN] MetaCoreTestImportPipelineAndCook..." << std::endl;