#include "Component/EditorControl.h" #include "Component/EditorUI.h" #include "Editor/TransformEditorCommand.h" #include "EditorWorld.h" #include "Project.h" #include "Game/Component/Render/Camera.h" #include "Game/Component/Render/LineRenderer.h" #include "Game/GameObject.h" #include "Game/World.h" #include "Game/Input.h" #include "Game/SMath.h" #include "Render/Renderer.h" #include "Render/IRenderContext.h" #include "Physics/Ray.h" #include namespace sh::editor { std::set EditorControl::controls; float EditorControl::snapDis = 0.25f; float EditorControl::snapAngle = 15.f; bool EditorControl::bPivot = true; bool EditorControl::bUpdatedControls = false; bool EditorControl::bSnap = true; uint32_t EditorControl::activeManipulationCount = 0; bool EditorControl::bOwnsHistoryTransaction = false; bool EditorControl::bCancelHistoryTransaction = false; using namespace game; SH_EDITOR_API EditorControl::EditorControl(GameObject& owner) : Component(owner) { ui = &static_cast(world).GetEditorUI(); canPlayInEditor = true; worldEventSubscriber.SetCallback( [&](const game::events::WorldEvent& event) { if (event.type == game::events::WorldEvent::Type::Play) SetActive(false); else if (event.type == game::events::WorldEvent::Type::Stop) SetActive(true); } ); } EditorControl::~EditorControl() { controls.erase(this); } SH_EDITOR_API void EditorControl::Awake() { Super::Awake(); controls.insert(this); if (auto obj = world.GetGameObject("_Helper"); obj == nullptr) { obj = world.AddGameObject("_Helper"); obj->hideInspector = true; obj->bNotSave = true; helper = obj->AddComponent(); helper->SetColor(Vec4{ 1.f, 0.f, 0.f, 1.f }); helper->SetStart(Vec3{ -100.f, 0.f, 0.f }); helper->SetEnd(Vec3{ 100.f, 0.f, 0.f }); helper->SetActive(false); } else { helper = obj->GetComponent(); } world.SubscribeEvent(worldEventSubscriber); } SH_EDITOR_API void EditorControl::Update() { if (!core::IsValid(camera)) return; if (mode == Mode::None && Input::GetKeyDown(Input::KeyCode::G)) { BeginManipulation(Mode::Move); } if (mode == Mode::None && Input::GetKeyDown(Input::KeyCode::S)) { BeginManipulation(Mode::Scale); } if (mode == Mode::None && Input::GetKeyDown(Input::KeyCode::R)) { BeginManipulation(Mode::Rotate); } if (mode != Mode::None) { ui->GetViewport().BlockLeftClick(true); ui->GetViewport().BlockRightClick(true); if (Input::GetKeyDown(Input::KeyCode::X)) { axis = Axis::X; helper->SetActive(true); helper->SetColor(Vec4{ 1.f, 0.f, 0.f, 1.f }); helper->SetStart(Vec3{ -100.f, 0.f, 0.f } + posLast); helper->SetEnd(Vec3{ 100.f, 0.f, 0.f } + posLast); } else if (Input::GetKeyDown(Input::KeyCode::Y)) { axis = Axis::Y; helper->SetActive(true); helper->SetColor(Vec4{ 0.f, 1.f, 0.f, 1.f }); helper->SetStart(Vec3{ 0.f, -100.f, 0.f } + posLast); helper->SetEnd(Vec3{ 0.f, 100.f, 0.f } + posLast); } else if (Input::GetKeyDown(Input::KeyCode::Z)) { axis = Axis::Z; helper->SetActive(true); helper->SetColor(Vec4{ 0.f, 0.f, 1.f, 1.f }); helper->SetStart(Vec3{ 0.f, 0.f, -100.f } + posLast); helper->SetEnd(Vec3{ 0.f, 0.f, 100.f } + posLast); } if (Input::GetMouseDown(Input::MouseType::Right)) { CompleteManipulation(true); } else if (Input::GetMouseDown(Input::MouseType::Left)) { CompleteManipulation(false); } } switch (mode) { case Mode::Move: MoveControl(); break; case Mode::Scale: ScaleControl(); break; case Mode::Rotate: RotateControl(); break; } } SH_EDITOR_API void EditorControl::LateUpdate() { if (Input::GetMouseReleased(Input::MouseType::Left)) ui->GetViewport().BlockLeftClick(false); if (Input::GetMouseReleased(Input::MouseType::Right)) ui->GetViewport().BlockRightClick(false); } SH_EDITOR_API void EditorControl::SetCamera(Camera* camera) { this->camera = camera; } SH_EDITOR_API auto EditorControl::Serialize() const -> core::Json { return core::Json{}; } SH_EDITOR_API void EditorControl::SetSnap(bool bSnap) { EditorControl::bSnap = bSnap; } SH_EDITOR_API void sh::editor::EditorControl::SetSnapDistance(float dis) { snapDis = dis; } SH_EDITOR_API void EditorControl::SetSnapAngle(float degree) { snapAngle = degree; } void EditorControl::BeginManipulation(Mode nextMode) { forward = glm::normalize(glm::vec3{ camera->GetLookPos() - camera->gameObject.transform->position }); up = camera->GetUpVector(); right = glm::normalize(glm::cross(glm::vec3{ forward }, glm::vec3{ up })); up = glm::normalize(glm::cross(glm::vec3{ right }, glm::vec3{ forward })); posLast = gameObject.transform->GetWorldPosition(); scaleLast = gameObject.transform->scale; quatLast = gameObject.transform->GetWorldQuat(); clickPos = Input::mousePosition; mode = nextMode; bParticipatingManipulation = true; auto& commandHistory = static_cast(world).GetProject().GetCommandHistory(); if (activeManipulationCount == 0) { bCancelHistoryTransaction = false; if (!commandHistory.IsTransactionOpen()) { commandHistory.BeginTransaction(GetTransactionName(nextMode)); bOwnsHistoryTransaction = true; } else bOwnsHistoryTransaction = false; } ++activeManipulationCount; } void EditorControl::CompleteManipulation(bool bCancel) { if (!bParticipatingManipulation) { ResetManipulationState(); return; } auto& commandHistory = static_cast(world).GetProject().GetCommandHistory(); if (bCancel) { if (mode == Mode::Move) gameObject.transform->SetWorldPosition(posLast); if (mode == Mode::Scale) gameObject.transform->SetScale(scaleLast); if (mode == Mode::Rotate) gameObject.transform->SetWorldRotation(quatLast); bCancelHistoryTransaction = true; } else if (HasTransformChanged()) { const auto after = CreateSnapshot(); auto command = std::make_unique( gameObject.GetUUID(), GetCommandName(mode), TransformEditorCommand::Snapshot{ posLast, scaleLast, quatLast }, TransformEditorCommand::Snapshot{ after.position, after.scale, after.rotation } ); commandHistory.Execute(std::move(command)); } if (activeManipulationCount > 0) --activeManipulationCount; const bool bLastManipulation = activeManipulationCount == 0; bParticipatingManipulation = false; ResetManipulationState(); if (bLastManipulation && bOwnsHistoryTransaction) { if (bCancelHistoryTransaction) commandHistory.CancelTransaction(); else commandHistory.EndTransaction(); bOwnsHistoryTransaction = false; bCancelHistoryTransaction = false; } } void EditorControl::MoveControl() { glm::vec3 linePoint = camera->gameObject.transform->position; glm::vec3 facePoint = gameObject.transform->position; auto clickRay = camera->ScreenPointToRay(clickPos); const glm::vec3 clickHit = SMath::GetPlaneCollisionPoint(clickRay.origin, clickRay.direction, facePoint, forward).value_or(glm::vec3{ 0.f }); auto ray = camera->ScreenPointToRay(Input::mousePosition); const glm::vec3 hit = SMath::GetPlaneCollisionPoint(ray.origin, ray.direction, facePoint, forward).value_or(glm::vec3{ 0.f }); const glm::vec3 delta = hit - clickHit; glm::vec3 pos{ 0.f }; if (axis == Axis::None) { float deltaX = glm::dot(delta, glm::vec3{ right }); float deltaY = glm::dot(delta, glm::vec3{ up }); if (bSnap) { deltaX = glm::round(deltaX / snapDis) * snapDis; deltaY = glm::round(deltaY / snapDis) * snapDis; } pos = posLast + (right * deltaX + up * deltaY); } else if (axis == Axis::X) { pos = posLast; float deltaX = glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, delta); if (bSnap) deltaX = glm::round(deltaX / snapDis) * snapDis; pos.x = posLast.x + deltaX; } else if (axis == Axis::Y) { pos = posLast; float deltaY = glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, delta); if (bSnap) deltaY = glm::round(deltaY / snapDis) * snapDis; pos.y = posLast.y + deltaY; } else if (axis == Axis::Z) { pos = posLast; float deltaZ = glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, delta); if (bSnap) deltaZ = glm::round(deltaZ / snapDis) * snapDis; pos.z = posLast.z + deltaZ; } gameObject.transform->SetWorldPosition(pos); } void EditorControl::ScaleControl() { glm::vec3 linePoint = camera->gameObject.transform->position; glm::vec3 facePoint = gameObject.transform->position; auto clickRay = camera->ScreenPointToRay(clickPos); glm::vec3 clickHit = SMath::GetPlaneCollisionPoint(clickRay.origin, clickRay.direction, facePoint, forward).value_or(glm::vec3{ 0.f }); auto ray = camera->ScreenPointToRay(Input::mousePosition); glm::vec3 hit = SMath::GetPlaneCollisionPoint(ray.origin, ray.direction, facePoint, forward).value_or(glm::vec3{ 0.f }); glm::vec3 facePointToHit = hit - facePoint; glm::vec3 delta = hit - clickHit; float len = glm::length(delta); if (glm::dot(facePointToHit, delta) < 0) len = -len; glm::vec3 scale = scaleLast; if (axis == Axis::None) scale += len; else if (axis == Axis::X) scale.x += len; else if (axis == Axis::Y) scale.y += len; else if (axis == Axis::Z) scale.z += len; gameObject.transform->SetScale(scale); } void EditorControl::RotateControl() { const glm::vec2 start = gameObject.world.renderer.GetContext()->GetViewportStart(); const glm::vec2 end = gameObject.world.renderer.GetContext()->GetViewportEnd(); glm::vec2 screenPos = glm::project(glm::vec3{ gameObject.transform->GetWorldPosition() }, camera->GetViewMatrix(), camera->GetProjMatrix(), glm::vec4{ start.x, start.y, end.x, end.y }); screenPos.y = end.y - screenPos.y; const glm::vec2 v = glm::normalize(screenPos - glm::vec2{ clickPos }); const glm::vec2 v2 = glm::normalize(screenPos - Input::mousePosition); if (glm::dot((v - v2), (v - v2)) < 0.0001f) return; const float snapRad = glm::radians(snapAngle); float angleRad = glm::acos(glm::dot(v, v2)); // 0 ~ pi if (bSnap) { angleRad = glm::round(angleRad / snapRad) * snapRad; } float crossProduct = v.x * v2.y - v.y * v2.x; if (crossProduct < 0) angleRad = -angleRad; if (axis == Axis::None) gameObject.transform->SetWorldRotation(glm::angleAxis(angleRad, glm::vec3{ forward }) * quatLast); else { glm::vec3 rotAxis; float dot; if (axis == Axis::X) { rotAxis = glm::vec3{ 1.f, 0.f, 0.f }; dot = glm::dot(glm::vec3{ forward }, rotAxis); } else if (axis == Axis::Y) { rotAxis = glm::vec3{ 0.f, 1.f, 0.f }; dot = glm::dot(glm::vec3{ forward }, rotAxis); } else if (axis == Axis::Z) { rotAxis = glm::vec3{ 0.f, 0.f, 1.f }; dot = glm::dot(glm::vec3{ forward }, rotAxis); } gameObject.transform->SetWorldRotation(glm::angleAxis(dot > 0 ? angleRad : -angleRad, rotAxis) * quatLast); } } void EditorControl::ResetManipulationState() { mode = Mode::None; axis = Axis::None; if (helper) helper->SetActive(false); } auto EditorControl::HasTransformChanged() const -> bool { const glm::vec3 posDelta = glm::vec3{ gameObject.transform->GetWorldPosition() - posLast }; const glm::vec3 scaleDelta = glm::vec3{ gameObject.transform->scale - scaleLast }; const float rotDot = std::abs(glm::dot(gameObject.transform->GetWorldQuat(), quatLast)); return glm::dot(posDelta, posDelta) > 0.000001f || glm::dot(scaleDelta, scaleDelta) > 0.000001f || std::abs(1.0f - rotDot) > 0.000001f; } auto EditorControl::CreateSnapshot() const -> Snapshot { return Snapshot { gameObject.transform->GetWorldPosition(), gameObject.transform->scale, gameObject.transform->GetWorldQuat() }; } auto EditorControl::GetCommandName(Mode mode) -> const char* { switch (mode) { case Mode::Move: return "Move"; case Mode::Rotate: return "Rotate"; case Mode::Scale: return "Scale"; default: return "Transform"; } } auto EditorControl::GetTransactionName(Mode mode) -> const char* { switch (mode) { case Mode::Move: return "Move objects"; case Mode::Rotate: return "Rotate objects"; case Mode::Scale: return "Scale objects"; default: return "Transform objects"; } } }//namespace