feat: add scene view editor camera controls

This commit is contained in:
2026-03-28 17:40:14 +08:00
parent 3c45a051a2
commit 3cc823aebd
8 changed files with 406 additions and 20 deletions

View File

@@ -2,10 +2,13 @@
#include "Core/IEditorContext.h"
#include "Core/ISceneManager.h"
#include "Core/ISelectionManager.h"
#include "IViewportHostService.h"
#include "SceneViewportCameraController.h"
#include "UI/ImGuiBackendBridge.h"
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/RHI/D3D12/D3D12Device.h>
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
#include <XCEngine/RHI/RHIEnums.h>
@@ -38,6 +41,7 @@ public:
entry = {};
}
m_sceneViewCamera = {};
m_device = nullptr;
m_backend = nullptr;
m_sceneRenderer.reset();
@@ -75,6 +79,33 @@ public:
return frame;
}
void UpdateSceneViewInput(IEditorContext& context, const SceneViewportInput& input) override {
if (!EnsureSceneViewCamera()) {
return;
}
if (input.focusSelectionRequested) {
FocusSceneView(context);
}
SceneViewportCameraInputState controllerInput = {};
controllerInput.viewportHeight = input.viewportSize.y;
controllerInput.zoomDelta = input.hovered ? input.mouseWheel : 0.0f;
if (input.orbiting) {
controllerInput.orbitDeltaX = input.mouseDelta.x;
controllerInput.orbitDeltaY = input.mouseDelta.y;
}
if (input.panning) {
controllerInput.panDeltaX = input.mouseDelta.x;
controllerInput.panDeltaY = input.mouseDelta.y;
}
m_sceneViewCamera.controller.ApplyInput(controllerInput);
ApplySceneViewCameraController();
}
void RenderRequestedViewports(
IEditorContext& context,
const Rendering::RenderContext& renderContext) override {
@@ -118,6 +149,12 @@ private:
std::string statusText;
};
struct SceneViewCameraState {
std::unique_ptr<Components::GameObject> gameObject;
Components::CameraComponent* camera = nullptr;
SceneViewportCameraController controller;
};
ViewportEntry& GetEntry(EditorViewportKind kind) {
const size_t index = kind == EditorViewportKind::Scene ? 0u : 1u;
m_entries[index].kind = kind;
@@ -130,6 +167,72 @@ private:
}
}
bool EnsureSceneViewCamera() {
if (m_sceneViewCamera.gameObject != nullptr && m_sceneViewCamera.camera != nullptr) {
return true;
}
m_sceneViewCamera.gameObject = std::make_unique<Components::GameObject>("EditorSceneCamera");
m_sceneViewCamera.camera = m_sceneViewCamera.gameObject->AddComponent<Components::CameraComponent>();
if (m_sceneViewCamera.camera == nullptr) {
m_sceneViewCamera.gameObject.reset();
return false;
}
m_sceneViewCamera.camera->SetPrimary(false);
m_sceneViewCamera.camera->SetProjectionType(Components::CameraProjectionType::Perspective);
m_sceneViewCamera.camera->SetFieldOfView(60.0f);
m_sceneViewCamera.camera->SetNearClipPlane(0.03f);
m_sceneViewCamera.camera->SetFarClipPlane(2000.0f);
m_sceneViewCamera.controller.Reset();
ApplySceneViewCameraController();
return true;
}
void ApplySceneViewCameraController() {
if (m_sceneViewCamera.gameObject == nullptr) {
return;
}
m_sceneViewCamera.controller.ApplyTo(*m_sceneViewCamera.gameObject->GetTransform());
}
void FocusSceneView(IEditorContext& context) {
Components::GameObject* target = nullptr;
const uint64_t selectedEntity = context.GetSelectionManager().GetSelectedEntity();
if (selectedEntity != 0) {
target = context.GetSceneManager().GetEntity(selectedEntity);
}
if (target != nullptr) {
m_sceneViewCamera.controller.Focus(target->GetTransform()->GetPosition());
return;
}
const auto& roots = context.GetSceneManager().GetRootEntities();
if (roots.empty()) {
m_sceneViewCamera.controller.Focus(Math::Vector3::Zero());
return;
}
Math::Vector3 center = Math::Vector3::Zero();
uint32_t count = 0;
for (const Components::GameObject* root : roots) {
if (root == nullptr) {
continue;
}
center += root->GetTransform()->GetPosition();
++count;
}
if (count > 0) {
center /= static_cast<float>(count);
}
m_sceneViewCamera.controller.Focus(center);
}
bool EnsureViewportResources(ViewportEntry& entry) {
if (entry.requestedWidth == 0 || entry.requestedHeight == 0) {
return false;
@@ -224,6 +327,15 @@ private:
return true;
}
Rendering::RenderSurface BuildSurface(const ViewportEntry& entry) const {
Rendering::RenderSurface surface(entry.width, entry.height);
surface.SetColorAttachment(entry.colorView);
surface.SetDepthAttachment(entry.depthView);
surface.SetColorStateBefore(entry.colorState);
surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
return surface;
}
void RenderViewportEntry(
ViewportEntry& entry,
const Components::Scene* scene,
@@ -239,6 +351,27 @@ private:
return;
}
Rendering::RenderSurface surface = BuildSurface(entry);
if (entry.kind == EditorViewportKind::Scene) {
if (!EnsureSceneViewCamera()) {
entry.statusText = "Scene view camera is unavailable";
ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f);
return;
}
ApplySceneViewCameraController();
if (!m_sceneRenderer->Render(*scene, m_sceneViewCamera.camera, renderContext, surface)) {
entry.statusText = "Scene renderer failed";
ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f);
return;
}
entry.colorState = RHI::ResourceStates::PixelShaderResource;
entry.statusText.clear();
return;
}
const auto cameras = scene->FindObjectsOfType<Components::CameraComponent>();
if (cameras.empty()) {
entry.statusText = "No camera in scene";
@@ -246,12 +379,6 @@ private:
return;
}
Rendering::RenderSurface surface(entry.width, entry.height);
surface.SetColorAttachment(entry.colorView);
surface.SetDepthAttachment(entry.depthView);
surface.SetColorStateBefore(entry.colorState);
surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
if (!m_sceneRenderer->Render(*scene, nullptr, renderContext, surface)) {
entry.statusText = "Scene renderer failed";
ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f);
@@ -332,6 +459,7 @@ private:
RHI::D3D12Device* m_device = nullptr;
std::unique_ptr<Rendering::SceneRenderer> m_sceneRenderer;
std::array<ViewportEntry, 2> m_entries = {};
SceneViewCameraState m_sceneViewCamera;
};
} // namespace Editor