相机更新

This commit is contained in:
Rowland 2026-06-12 09:09:07 +08:00
parent a1cd721203
commit bcec8b6a8d
20 changed files with 1181 additions and 132 deletions

View File

@ -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<float>(
@ -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<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>().NearClip);
if (multiEdit && !sharedNearClip.has_value()) {
ImGui::TextDisabled("Near Clip: Mixed");
ImGui::TextDisabled("近裁剪: 多值");
}
if (prefabObject != nullptr && prefabObject->Camera.has_value() &&
!MetaCoreNearlyEqual(gameObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>().FarClip);
ImGui::DragFloat("近裁剪", &nearClip, 0.01F, 0.001F, gameObject.GetComponent<MetaCoreCameraComponent>().FarClip);
MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true);
if (ImGui::IsItemEdited()) {
MetaCoreApplyValueToSelectedObjects(
@ -1619,22 +1619,22 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext,
);
float farClip = sharedFarClip.value_or(gameObject.GetComponent<MetaCoreCameraComponent>().FarClip);
if (multiEdit && !sharedFarClip.has_value()) {
ImGui::TextDisabled("Far Clip: Mixed");
ImGui::TextDisabled("远裁剪: 多值");
}
if (prefabObject != nullptr && prefabObject->Camera.has_value() &&
!MetaCoreNearlyEqual(gameObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>().IsPrimary);
if (multiEdit && !sharedPrimary.has_value()) {
ImGui::TextDisabled("Primary: Mixed");
ImGui::TextDisabled("主相机: 多值");
}
if (prefabObject != nullptr && prefabObject->Camera.has_value() &&
gameObject.GetComponent<MetaCoreCameraComponent>().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<bool>(
editorContext,
[](MetaCoreGameObject& selectedObject) -> std::optional<bool> {
@ -1692,23 +1692,23 @@ void MetaCoreDrawCameraComponentInspector(MetaCoreEditorContext& editorContext,
);
bool isEnabled = sharedEnabled.value_or(gameObject.GetComponent<MetaCoreCameraComponent>().Enabled);
if (multiEdit && !sharedEnabled.has_value()) {
ImGui::TextDisabled("Enabled: Mixed");
ImGui::TextDisabled("启用: 多值");
}
// 处理 Prefab 覆盖标记以及应用和回退按钮
if (prefabObject != nullptr && prefabObject->Camera.has_value() &&
gameObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>();
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<MetaCoreCameraComponent>() ? &selectedObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>() ? &selectedObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>() ? &selectedObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>() ? &selectedObject.GetComponent<MetaCoreCameraComponent>().CullingMask : nullptr;
},
cullingMask
);
}
int clearFlagsIndex = static_cast<int>(camera.ClearFlags);
const char* clearFlagItems[] = {"天空盒", "纯色", "仅深度", "不清屏"};
if (ImGui::Combo("清屏方式", &clearFlagsIndex, clearFlagItems, IM_ARRAYSIZE(clearFlagItems))) {
const auto clearFlags = static_cast<MetaCoreCameraClearFlags>(std::clamp(clearFlagsIndex, 0, 3));
MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false);
MetaCoreApplyValueToSelectedObjects(
editorContext,
[](MetaCoreGameObject& selectedObject) -> MetaCoreCameraClearFlags* {
return selectedObject.HasComponent<MetaCoreCameraComponent>() ? &selectedObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>() ? &selectedObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreCameraComponent>() ? &selectedObject.GetComponent<MetaCoreCameraComponent>().BackgroundAlpha : nullptr;
},
backgroundAlpha
);
}
}
void MetaCoreDrawLightComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
@ -2914,6 +3019,27 @@ template <typename T>
// 应用相机的启用状态到 Prefab 模板中
prefabObjectIterator->Camera->Enabled = selectedObject.GetComponent<MetaCoreCameraComponent>().Enabled;
changed = true;
} else if (fieldId == "projection_mode") {
prefabObjectIterator->Camera->ProjectionMode = selectedObject.GetComponent<MetaCoreCameraComponent>().ProjectionMode;
changed = true;
} else if (fieldId == "orthographic_size") {
prefabObjectIterator->Camera->OrthographicSize = selectedObject.GetComponent<MetaCoreCameraComponent>().OrthographicSize;
changed = true;
} else if (fieldId == "depth") {
prefabObjectIterator->Camera->Depth = selectedObject.GetComponent<MetaCoreCameraComponent>().Depth;
changed = true;
} else if (fieldId == "culling_mask") {
prefabObjectIterator->Camera->CullingMask = selectedObject.GetComponent<MetaCoreCameraComponent>().CullingMask;
changed = true;
} else if (fieldId == "clear_flags") {
prefabObjectIterator->Camera->ClearFlags = selectedObject.GetComponent<MetaCoreCameraComponent>().ClearFlags;
changed = true;
} else if (fieldId == "background_color") {
prefabObjectIterator->Camera->BackgroundColor = selectedObject.GetComponent<MetaCoreCameraComponent>().BackgroundColor;
changed = true;
} else if (fieldId == "background_alpha") {
prefabObjectIterator->Camera->BackgroundAlpha = selectedObject.GetComponent<MetaCoreCameraComponent>().BackgroundAlpha;
changed = true;
}
} else if (componentTypeId == "Light" && selectedObject.HasComponent<MetaCoreLightComponent>()) {
if (!prefabObjectIterator->Light.has_value()) {
@ -2997,6 +3123,34 @@ template <typename T>
selectedObject.GetComponent<MetaCoreCameraComponent>().Enabled = prefabObject->Camera->Enabled;
return true;
}
if (fieldId == "projection_mode") {
selectedObject.GetComponent<MetaCoreCameraComponent>().ProjectionMode = prefabObject->Camera->ProjectionMode;
return true;
}
if (fieldId == "orthographic_size") {
selectedObject.GetComponent<MetaCoreCameraComponent>().OrthographicSize = prefabObject->Camera->OrthographicSize;
return true;
}
if (fieldId == "depth") {
selectedObject.GetComponent<MetaCoreCameraComponent>().Depth = prefabObject->Camera->Depth;
return true;
}
if (fieldId == "culling_mask") {
selectedObject.GetComponent<MetaCoreCameraComponent>().CullingMask = prefabObject->Camera->CullingMask;
return true;
}
if (fieldId == "clear_flags") {
selectedObject.GetComponent<MetaCoreCameraComponent>().ClearFlags = prefabObject->Camera->ClearFlags;
return true;
}
if (fieldId == "background_color") {
selectedObject.GetComponent<MetaCoreCameraComponent>().BackgroundColor = prefabObject->Camera->BackgroundColor;
return true;
}
if (fieldId == "background_alpha") {
selectedObject.GetComponent<MetaCoreCameraComponent>().BackgroundAlpha = prefabObject->Camera->BackgroundAlpha;
return true;
}
}
if (componentTypeId == "Light" && prefabObject->Light.has_value()) {
@ -6030,6 +6184,10 @@ public:
bool withMeshRenderer,
std::optional<MetaCoreId> forcedParentId
) override;
[[nodiscard]] std::optional<MetaCoreId> CreateCamera(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> forcedParentId
) override;
[[nodiscard]] std::optional<MetaCoreId> InstantiateModelAsset(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& assetGuid,
@ -7720,6 +7878,52 @@ std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::CreateGameObject(
return createdObjectId;
}
std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::CreateCamera(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> 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<MetaCoreCameraComponent>()) {
continue;
}
const MetaCoreCameraComponent& camera = object.GetComponent<MetaCoreCameraComponent>();
if (camera.Enabled && camera.IsPrimary) {
hasEnabledPrimaryCamera = true;
break;
}
}
MetaCoreGameObject cameraObject = editorContext.GetScene().CreateGameObject("Camera", parentId);
MetaCoreCameraComponent& camera = cameraObject.AddComponent<MetaCoreCameraComponent>(MetaCoreCameraComponent{});
camera.IsPrimary = !hasEnabledPrimaryCamera;
MetaCoreTransformComponent& transform = cameraObject.GetComponent<MetaCoreTransformComponent>();
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<MetaCoreId> MetaCoreBuiltinSceneEditingService::InstantiateModelAsset(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& assetGuid,

View File

@ -429,6 +429,17 @@ void MetaCoreApplyMaterialPreviewToScene(
return sceneEditingService->CreateGameObject(editorContext, objectName, withMeshRenderer, forcedParentId);
}
[[nodiscard]] std::optional<MetaCoreId> MetaCoreCreateCamera(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> forcedParentId
) {
const auto sceneEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreISceneEditingService>();
if (sceneEditingService == nullptr) {
return std::nullopt;
}
return sceneEditingService->CreateCamera(editorContext, forcedParentId);
}
[[nodiscard]] bool MetaCoreDuplicateSelection(MetaCoreEditorContext& editorContext) {
const auto clipboardService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIClipboardService>();
if (clipboardService == nullptr) {
@ -1452,10 +1463,20 @@ struct MetaCoreGeneratedAssetChoice {
return false;
}
return !MetaCoreNearlyEqual(instanceObject.GetComponent<MetaCoreCameraComponent>().FieldOfViewDegrees, prefabObject.Camera->FieldOfViewDegrees) ||
!MetaCoreNearlyEqual(instanceObject.GetComponent<MetaCoreCameraComponent>().NearClip, prefabObject.Camera->NearClip) ||
!MetaCoreNearlyEqual(instanceObject.GetComponent<MetaCoreCameraComponent>().FarClip, prefabObject.Camera->FarClip) ||
instanceObject.GetComponent<MetaCoreCameraComponent>().IsPrimary != prefabObject.Camera->IsPrimary;
const MetaCoreCameraComponent& instanceCamera = instanceObject.GetComponent<MetaCoreCameraComponent>();
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<MetaCoreCameraComponent>().IsPrimary;
return true;
}
if (fieldId == "enabled") {
prefabObject.Camera->Enabled = selectedObject.GetComponent<MetaCoreCameraComponent>().Enabled;
return true;
}
if (fieldId == "projection_mode") {
prefabObject.Camera->ProjectionMode = selectedObject.GetComponent<MetaCoreCameraComponent>().ProjectionMode;
return true;
}
if (fieldId == "orthographic_size") {
prefabObject.Camera->OrthographicSize = selectedObject.GetComponent<MetaCoreCameraComponent>().OrthographicSize;
return true;
}
if (fieldId == "depth") {
prefabObject.Camera->Depth = selectedObject.GetComponent<MetaCoreCameraComponent>().Depth;
return true;
}
if (fieldId == "culling_mask") {
prefabObject.Camera->CullingMask = selectedObject.GetComponent<MetaCoreCameraComponent>().CullingMask;
return true;
}
if (fieldId == "clear_flags") {
prefabObject.Camera->ClearFlags = selectedObject.GetComponent<MetaCoreCameraComponent>().ClearFlags;
return true;
}
if (fieldId == "background_color") {
prefabObject.Camera->BackgroundColor = selectedObject.GetComponent<MetaCoreCameraComponent>().BackgroundColor;
return true;
}
if (fieldId == "background_alpha") {
prefabObject.Camera->BackgroundAlpha = selectedObject.GetComponent<MetaCoreCameraComponent>().BackgroundAlpha;
return true;
}
} else if (componentTypeId == "Light" && selectedObject.HasComponent<MetaCoreLightComponent>()) {
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<MetaCoreMeshRendererComponent>() &&
@ -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());

View File

@ -2,6 +2,7 @@
#include "MetaCoreEditor/MetaCoreBuiltinModules.h"
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "MetaCoreEditorCameraController.h"
#include <imgui.h>
@ -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<MetaCoreGameCameraSelectorItem> MetaCoreBuildGameCameraSelectorItems(
const MetaCoreScene& scene
) {
std::vector<MetaCoreGameCameraSelectorItem> items;
for (const MetaCoreId objectId : scene.BuildHierarchyPreorder()) {
const MetaCoreGameObject object = scene.FindGameObject(objectId);
if (!object || !object.HasComponent<MetaCoreCameraComponent>()) {
continue;
}
const MetaCoreCameraComponent& camera = object.GetComponent<MetaCoreCameraComponent>();
if (!camera.Enabled) {
continue;
}
char suffix[96]{};
std::snprintf(
suffix,
sizeof(suffix),
" #%llu Depth %.2f%s",
static_cast<unsigned long long>(object.GetId()),
static_cast<double>(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<unsigned long long>(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<MetaCoreCameraComponent>()) {
continue;
}
MetaCoreCameraComponent& camera = object.GetComponent<MetaCoreCameraComponent>();
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<MetaCoreGameCameraSelectorItem> 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();

View File

@ -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) {

View File

@ -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

View File

@ -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;
}
}

View File

@ -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<MetaCoreId, glm::mat4> worldMatrixCache;
const auto buildWorldMatrix = [&](const auto& self, MetaCoreId objectId) -> glm::mat4 {
@ -362,15 +342,27 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
const int h = static_cast<int>(viewportState.Height);
const float gizmoAspect = (h > 0) ? (static_cast<float>(w) / static_cast<float>(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();

View File

@ -250,6 +250,11 @@ public:
std::optional<MetaCoreId> forcedParentId
) = 0;
[[nodiscard]] virtual std::optional<MetaCoreId> CreateCamera(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> forcedParentId
) = 0;
[[nodiscard]] virtual std::optional<MetaCoreId> InstantiateModelAsset(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& assetGuid,

View File

@ -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));

View File

@ -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));

View File

@ -12,6 +12,13 @@ enum class MetaCoreInputKey : unsigned int {
RightAlt,
Control,
Focus,
MoveForward,
MoveBackward,
MoveLeft,
MoveRight,
MoveDown,
MoveUp,
Shift,
Count
};

View File

@ -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<int>(ViewportRect_.Width), static_cast<int>(ViewportRect_.Height));
FilamentSceneBridge_.ApplySceneView(sceneView);
FilamentSceneBridge_.SyncScene(snapshot, scene, false, false);
}
void MetaCoreEditorViewportRenderer::RenderAll() {
FilamentSceneBridge_.RenderAll();
}

View File

@ -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<float>(viewport.width) / static_cast<float>(viewport.height)) : 1.0f;
const float aspect = (viewport.height > 0) ? (static_cast<float>(viewport.width) / static_cast<float>(viewport.height)) : 1.0f;
const double nearClip = std::max(0.0001, static_cast<double>(sceneView.NearClip));
const double farClip = std::max(nearClip + 0.0001, static_cast<double>(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<double>(sceneView.OrthographicSize));
const double halfWidth = halfHeight * std::max(0.0001, static_cast<double>(aspect));
Camera_->setProjection(
filament::Camera::Projection::ORTHO,
-halfWidth,
halfWidth,
-halfHeight,
halfHeight,
nearClip,
farClip
);
} else {
Camera_->setProjection(
std::clamp(static_cast<double>(sceneView.VerticalFieldOfViewDegrees), 1.0, 179.0),
std::max(0.0001, static_cast<double>(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<MetaCoreId, glm::mat4> ObjectWorldMatrices_{};
std::unordered_map<MetaCoreId, std::pair<filament::gltfio::FilamentAsset*, utils::Entity>> ObjectToFilamentEntity_;
std::unordered_map<MetaCoreId, filament::gltfio::FilamentAsset*> LoadedAssets_;

View File

@ -4,7 +4,10 @@
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreTransformUtils.h"
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/geometric.hpp>
#include <glm/vec4.hpp>
#include <algorithm>
#include <cmath>
@ -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<MetaCoreCameraComponent>()) {
const auto& camera = gameObject.GetComponent<MetaCoreCameraComponent>();
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<glm::vec3> 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

View File

@ -2,6 +2,7 @@
#include "MetaCoreRender/MetaCoreFilamentSceneBridge.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include <filesystem>
@ -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;

View File

@ -1,8 +1,11 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreScene/MetaCoreComponents.h"
#include <glm/vec3.hpp>
#include <cstdint>
#include <optional>
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<MetaCoreId> 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;

View File

@ -2,9 +2,11 @@
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreScene/MetaCoreComponents.h"
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <optional>
@ -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<glm::vec3> MetaCoreWorldToScreenPoint(
const MetaCoreSceneView& sceneView,
const MetaCoreViewportRect& viewportRect,
const glm::vec3& worldPosition
);
} // namespace MetaCore

View File

@ -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<int>(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<int>(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<float>();
else if (camField.Name == "IsPrimary" && camJson.contains("IsPrimary")) cam.IsPrimary = camJson["IsPrimary"].get<bool>();
else if (camField.Name == "Enabled" && camJson.contains("Enabled")) cam.Enabled = camJson["Enabled"].get<bool>();
else if (camField.Name == "ProjectionMode" && camJson.contains("ProjectionMode")) cam.ProjectionMode = static_cast<MetaCoreCameraProjectionMode>(camJson["ProjectionMode"].get<int>());
else if (camField.Name == "OrthographicSize" && camJson.contains("OrthographicSize")) cam.OrthographicSize = camJson["OrthographicSize"].get<float>();
else if (camField.Name == "Depth" && camJson.contains("Depth")) cam.Depth = camJson["Depth"].get<float>();
else if (camField.Name == "CullingMask" && camJson.contains("CullingMask")) cam.CullingMask = camJson["CullingMask"].get<std::uint32_t>();
else if (camField.Name == "ClearFlags" && camJson.contains("ClearFlags")) cam.ClearFlags = static_cast<MetaCoreCameraClearFlags>(camJson["ClearFlags"].get<int>());
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<float>();
}
obj.Camera = cam;
}

View File

@ -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()

View File

@ -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<MetaCore::MetaCoreISceneEditingService>();
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<MetaCore::MetaCoreCameraComponent>(), "创建后的对象应带 Camera 组件");
MetaCoreExpect(cameraObject.GetComponent<MetaCore::MetaCoreCameraComponent>().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<MetaCore::MetaCoreISceneEditingService>();
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<MetaCore::MetaCoreCameraComponent>(), "第二个相机对象应带 Camera 组件");
MetaCoreExpect(!cameraObject.GetComponent<MetaCore::MetaCoreCameraComponent>().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<MetaCore::MetaCoreCameraComponent>();
disabledPrimaryCamera.IsPrimary = true;
disabledPrimaryCamera.Enabled = false;
disabledPrimaryCamera.Depth = -100.0F;
MetaCore::MetaCoreGameObject depthCamera = scene.CreateGameObject("DepthCamera");
auto& depthCameraComponent = depthCamera.AddComponent<MetaCore::MetaCoreCameraComponent>();
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<MetaCore::MetaCoreCameraComponent>();
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<MetaCore::MetaCoreCameraComponent>().NearClip = 0.25F;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().FarClip = 250.0F;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().IsPrimary = true;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().ProjectionMode = MetaCore::MetaCoreCameraProjectionMode::Orthographic;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().OrthographicSize = 9.0F;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().Depth = -3.0F;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().CullingMask = 0x000000FFU;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().ClearFlags = MetaCore::MetaCoreCameraClearFlags::SolidColor;
object.GetComponent<MetaCore::MetaCoreCameraComponent>().BackgroundColor = glm::vec3(0.1F, 0.2F, 0.3F);
object.GetComponent<MetaCore::MetaCoreCameraComponent>().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<MetaCore::MetaCoreCameraComponent>().NearClip - 0.25F) <= 0.0001F, "粘贴后 NearClip 应一致");
MetaCoreExpect(std::abs(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().FarClip - 250.0F) <= 0.0001F, "粘贴后 FarClip 应一致");
MetaCoreExpect(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().IsPrimary, "粘贴后 IsPrimary 应一致");
MetaCoreExpect(
pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().ProjectionMode == MetaCore::MetaCoreCameraProjectionMode::Orthographic,
"粘贴后 ProjectionMode 应一致"
);
MetaCoreExpect(std::abs(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().OrthographicSize - 9.0F) <= 0.0001F, "粘贴后 OrthographicSize 应一致");
MetaCoreExpect(std::abs(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().Depth - -3.0F) <= 0.0001F, "粘贴后 Depth 应一致");
MetaCoreExpect(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().CullingMask == 0x000000FFU, "粘贴后 CullingMask 应一致");
MetaCoreExpect(
pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().ClearFlags == MetaCore::MetaCoreCameraClearFlags::SolidColor,
"粘贴后 ClearFlags 应一致"
);
MetaCoreExpectVec3Near(
pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().BackgroundColor,
glm::vec3(0.1F, 0.2F, 0.3F),
"粘贴后 BackgroundColor 应一致"
);
MetaCoreExpect(std::abs(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().BackgroundAlpha - 0.5F) <= 0.0001F, "粘贴后 BackgroundAlpha 应一致");
MetaCoreExpect(std::abs(secondPasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().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<MetaCore::MetaCoreCameraComponent>().FieldOfViewDegrees = 10.0F;
pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().ProjectionMode = MetaCore::MetaCoreCameraProjectionMode::Orthographic;
MetaCoreExpect(cameraDescriptor->ResetComponent(pasteTarget), "应能重置 Camera");
MetaCoreExpect(std::abs(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().FieldOfViewDegrees - 60.0F) <= 0.0001F, "重置后 Camera 应恢复默认值");
MetaCoreExpect(
pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().ProjectionMode == MetaCore::MetaCoreCameraProjectionMode::Perspective,
"重置后 Camera ProjectionMode 应恢复默认值"
);
MetaCoreExpect(scriptDescriptor->ResetComponent != nullptr, "Script descriptor 应提供 Reset");
MetaCoreExpect(scriptDescriptor->ResetComponent(pasteTarget), "应能重置 Script");
MetaCoreExpect(pasteTarget.GetComponent<MetaCore::MetaCoreScriptComponent>().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;