From 791b3f354b378769bffe623b05f1305c91b77101 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Fri, 3 Jun 2022 23:30:01 -0700 Subject: Changeset: 64 [WIP] Rename directories --- ProjectBrussel/Game/EditorCorePrivate.cpp | 1113 +++++++++++++++++++++++++++++ 1 file changed, 1113 insertions(+) create mode 100644 ProjectBrussel/Game/EditorCorePrivate.cpp (limited to 'ProjectBrussel/Game/EditorCorePrivate.cpp') diff --git a/ProjectBrussel/Game/EditorCorePrivate.cpp b/ProjectBrussel/Game/EditorCorePrivate.cpp new file mode 100644 index 0000000..43857a8 --- /dev/null +++ b/ProjectBrussel/Game/EditorCorePrivate.cpp @@ -0,0 +1,1113 @@ +#include "EditorCorePrivate.hpp" + +#include "App.hpp" +#include "AppConfig.hpp" +#include "EditorAccessories.hpp" +#include "EditorAttachmentImpl.hpp" +#include "EditorCommandPalette.hpp" +#include "EditorNotification.hpp" +#include "EditorUtils.hpp" +#include "GameObject.hpp" +#include "Mesh.hpp" +#include "Player.hpp" +#include "SceneThings.hpp" +#include "VertexIndex.hpp" + +#include +#include +#include +#include + +#define GLFW_INCLUDE_NONE +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace ProjectBrussel_UNITY_ID { +// TODO handle internal state internally and move this to EditorUtils.hpp +enum RenamableSelectableAction { + RSA_None, + RSA_Selected, + RSA_RenameCommitted, + RSA_RenameCancelled, +}; +RenamableSelectableAction RenamableSelectable(const char* displayName, bool selected, bool& renaming, std::string& renamingScratchBuffer) // +{ + RenamableSelectableAction result = RSA_None; + + ImGuiSelectableFlags flags = 0; + // When renaming, disable all other entries that is not the one being renamed + if (renaming && !selected) { + flags |= ImGuiSelectableFlags_Disabled; + } + + if (renaming && selected) { + // State: being renamed + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); + ImGui::SetKeyboardFocusHere(); + if (ImGui::InputText("##Rename", &renamingScratchBuffer, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) { + // Confirm + renaming = false; + result = RSA_RenameCommitted; + } + ImGui::PopStyleVar(2); + + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + // Cancel + renaming = false; + result = RSA_RenameCancelled; + } + } else { + // State: normal + + if (ImGui::Selectable(displayName, selected, flags)) { + result = RSA_Selected; + } + } + + return result; +} +} // namespace ProjectBrussel_UNITY_ID + +void EditorInspector::SelectTarget(TargetType type, void* object) { + selectedItt = type; + selectedItPtr = object; + renaming = false; + renamingScratchBuffer.clear(); +} + +EditorContentBrowser::EditorContentBrowser(EditorInspector* inspector) + : mInspector{ inspector } { +} + +EditorContentBrowser::~EditorContentBrowser() { +} + +void EditorContentBrowser::Show(bool* open) { + using namespace ProjectBrussel_UNITY_ID; + + ImGuiWindowFlags windowFlags; + if (mDocked) { + // Center window horizontally, align bottom vertically + auto& viewportSize = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2, viewportSize.y), ImGuiCond_Always, ImVec2(0.5f, 1.0f)); + ImGui::SetNextWindowSizeRelScreen(0.8f, mBrowserHeight); + windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; + } else { + windowFlags = 0; + } + ImGui::Begin("Content Browser", open, windowFlags); + + ImGui::Splitter(true, kSplitterThickness, &mSplitterLeft, &mSplitterRight, kLeftPaneMinWidth, kRightPaneMinWidth); + + ImGui::BeginChild("LeftPane", ImVec2(mSplitterLeft - kPadding, 0.0f)); + { + if (ImGui::Selectable("Settings", mPane == P_Settings)) { + mPane = P_Settings; + } + if (ImGui::Selectable("Ires", mPane == P_Ires)) { + mPane = P_Ires; + } + if (ImGui::Selectable("Levels", mPane == P_Level)) { + mPane = P_Level; + } + } + ImGui::EndChild(); + + ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding); + ImGui::BeginChild("RightPane"); // Fill remaining space + auto origItt = mInspector->selectedItt; + auto origItPtr = mInspector->selectedItPtr; + switch (mPane) { + case P_Settings: { + ImGui::Checkbox("Docked", &mDocked); + ImGui::SliderFloat("Height", &mBrowserHeight, 0.1f, 1.0f); + } break; + + case P_Ires: { + bool isIttIres = origItt == EditorInspector::ITT_Ires; + + if (ImGui::Button("New")) { + ImGui::OpenPopup("New Ires"); + } + if (ImGui::BeginPopup("New Ires")) { + for (int i = 0; i < (int)IresObject::KD_COUNT; ++i) { + auto kind = static_cast(i); + if (ImGui::MenuItem(Metadata::EnumToString(kind).data())) { + auto ires = IresObject::Create(kind); + auto [DISCARD, success] = IresManager::instance->Add(ires.get()); + if (success) { + (void)ires.release(); + } + } + } + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Refresh list") || + ImGui::IsKeyPressed(ImGuiKey_F5)) + { + // TODO + } + + ImGui::SameLine(); + if (ImGui::Button("Save", !isIttIres)) { + auto ires = static_cast(origItPtr); + IresManager::instance->Save(ires); + } + + ImGui::SameLine(); + if (ImGui::Button("Reload", !isIttIres)) { + auto ires = static_cast(origItPtr); + IresManager::instance->Reload(ires); + } + + ImGui::SameLine(); + if (ImGui::Button("Rename", !isIttIres) || + (isIttIres && ImGui::IsKeyPressed(ImGuiKey_F2, false))) + { + auto ires = static_cast(origItPtr); + mInspector->renaming = true; + mInspector->renamingScratchBuffer = ires->GetName(); + } + + ImGui::SameLine(); + if (ImGui::Button("Delete", !isIttIres) || + (isIttIres && ImGui::IsKeyPressed(ImGuiKey_Delete, false))) + { + ImGui::OpenPopup("Delete Ires"); + } + bool openedDummy = true; + if (ImGui::BeginPopupModal("Delete Ires", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { + if (ImGui::Button("Confirm")) { + auto ires = static_cast(origItPtr); + IresManager::instance->Delete(ires); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + auto& objects = IresManager::instance->GetObjects(); + for (auto it = objects.begin(); it != objects.end(); ++it) { + auto ires = it->second.Get(); + auto& name = ires->GetName(); + + bool selected = origItPtr == ires; + + switch (RenamableSelectable(name.c_str(), selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { + case RSA_Selected: { + mInspector->SelectTarget(EditorInspector::ITT_Ires, ires); + } break; + + case RSA_RenameCommitted: { + ires->SetName(std::move(mInspector->renamingScratchBuffer)); + } break; + + // Do nothing + case RSA_RenameCancelled: + case RSA_None: break; + } + if (!mInspector->renaming) { + if (ImGui::BeginDragDropSource()) { + auto kindName = Metadata::EnumToString(ires->GetKind()); + // Reason: intentionally using pointer as payload + ImGui::SetDragDropPayload(kindName.data(), &ires, sizeof(ires)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text("%s '%s'", kindName.data(), name.c_str()); + ImGui::EndDragDropSource(); + } + } + } + } break; + + case P_Level: { + bool isIttLevel = origItt == EditorInspector::ITT_Level; + + if (ImGui::Button("New")) { + auto uid = Uid::Create(); + auto& ldObj = LevelManager::instance->AddLevel(uid); + mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); + mInspector->renaming = true; + mInspector->renamingScratchBuffer = ldObj.name; + } + + if (ImGui::Button("Save", !isIttLevel)) { + auto ldObj = static_cast(origItPtr); + LevelManager::instance->SaveLevel(ldObj->level->GetUid()); + } + + auto& objects = LevelManager::instance->mObjByUid; + for (auto it = objects.begin(); it != objects.end(); ++it) { + auto& uid = it->first; + auto& ldObj = it->second; + auto* level = ldObj.level.Get(); + bool selected = origItPtr == &ldObj; + const char* displayName = ldObj.name.c_str(); + if (strcmp(displayName, "") == 0) { + displayName = ""; + } + + switch (RenamableSelectable(displayName, selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { + case RSA_Selected: { + mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); + } break; + + case RSA_RenameCommitted: { + ldObj.name = std::move(mInspector->renamingScratchBuffer); + } break; + + // Do nothing + case RSA_RenameCancelled: + case RSA_None: break; + } + if (!mInspector->renaming) { + if (ImGui::BeginDragDropSource()) { + // Reason: intentionally using pointer as payload + ImGui::SetDragDropPayload(BRUSSEL_TAG_Level, &ldObj, sizeof(ldObj)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text(BRUSSEL_Uid_FORMAT_STR, BRUSSEL_Uid_FORMAT_EXPAND(uid)); + ImGui::TextUnformatted(ldObj.name.c_str()); + ImGui::EndDragDropSource(); + } + } + } + } break; + } + ImGui::EndChild(); + + ImGui::End(); +} + +namespace ProjectBrussel_UNITY_ID { +glm::quat CalcQuaternionFromDegreesEulerAngle(glm::vec3 eulerAngleDegrees) { + glm::vec3 eulerAngleRadians; + eulerAngleRadians.x = eulerAngleDegrees.x / 180 * M_PI; + eulerAngleRadians.y = eulerAngleDegrees.y / 180 * M_PI; + eulerAngleRadians.z = eulerAngleDegrees.z / 180 * M_PI; + return glm::quat(eulerAngleRadians); +} + +void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) { + app->PushKeyCaptureCallback([=](int key, int action) { + // Allow the user to cancel by pressing Esc + if (key == GLFW_KEY_ESCAPE) { + return true; + } + + if (action == GLFW_PRESS) { + *writeKey = key; + *writeKeyStatus = writeKeyStatus; + return true; + } + return false; + }); +} + +struct GobjTreeNodeShowInfo { + EditorInstance* in_editor; + GameObject* out_openPopup = nullptr; +}; + +void GobjTreeNode(GobjTreeNodeShowInfo& showInfo, GameObject* object) { + auto& inspector = showInfo.in_editor->GetInspector(); + + auto attachment = object->GetEditorAttachment(); + if (!attachment) { + attachment = EaGameObject::Create(object).release(); + object->SetEditorAttachment(attachment); // NOTE: takes ownership + } + + ImGuiTreeNodeFlags flags = + ImGuiTreeNodeFlags_DefaultOpen | + ImGuiTreeNodeFlags_OpenOnDoubleClick | + ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_SpanAvailWidth | + ImGuiTreeNodeFlags_NoTreePushOnOpen; + if (inspector.selectedItPtr == object) { + flags |= ImGuiTreeNodeFlags_Selected; + } + + ImGui::PushID(reinterpret_cast(object)); + // BEGIN tree node + + bool opened = ImGui::TreeNodeEx(attachment->name.c_str(), flags); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + inspector.SelectTarget(EditorInspector::ITT_GameObject, object); + } + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + showInfo.out_openPopup = object; + } + + if (opened) { + ImGui::Indent(); + for (auto& child : object->GetChildren()) { + GobjTreeNode(showInfo, child); + } + ImGui::Unindent(); + } + + // END tree node + ImGui::PopID(); +}; + +#define GAMEOBJECT_CONSTRUCTOR(ClassName) [](GameWorld* world) -> GameObject* { return new ClassName(world); } +struct CreatableGameObject { + GameObject* (*factory)(GameWorld*); + const char* name; + GameObject::Kind kind; +} creatableGameObjects[] = { + { + .factory = GAMEOBJECT_CONSTRUCTOR(GameObject), + .name = "GameObject", + .kind = GameObject::KD_Generic, + }, + { + .factory = GAMEOBJECT_CONSTRUCTOR(SimpleGeometryObject), + .name = "Simple Geometry", + .kind = GameObject::KD_SimpleGeometry, + }, + { + .factory = GAMEOBJECT_CONSTRUCTOR(BuildingObject), + .name = "Building", + .kind = GameObject::KD_Building, + }, + { + .factory = GAMEOBJECT_CONSTRUCTOR(LevelWrapperObject), + .name = "Level Wrapper", + .kind = GameObject::KD_LevelWrapper, + }, +}; +#undef GAMEOBJECT_CONSTRUCTOR +} // namespace ProjectBrussel_UNITY_ID + +std::unique_ptr IEditor::CreateInstance(App* app) { + return std::make_unique(app); +} + +EditorInstance::EditorInstance(App* app) + : mApp{ app } + , mEdContentBrowser(&mEdInspector) { + mEditorCamera.name = "Editor Camera"s; + mEditorCamera.SetEyePos(glm::vec3(0, 0, 1)); + mEditorCamera.SetTargetDirection(glm::vec3(0, 0, -1)); + app->BindActiveCamera(&mEditorCamera); +} + +EditorInstance::~EditorInstance() { +} + +void EditorInstance::OnGameStateChanged(bool running) { + if (running) { + mApp->UnbindActiveCamera(); + } else { + mApp->BindActiveCamera(&mEditorCamera); + } +} + +void EditorInstance::Show() { + using namespace ProjectBrussel_UNITY_ID; + using namespace Tags; + + auto world = mApp->GetWorld(); + auto& io = ImGui::GetIO(); + + ImGui::BeginMainMenuBar(); + if (ImGui::BeginMenu("View")) { + ImGui::MenuItem("ImGui Demo", nullptr, &mWindowVisible_ImGuiDemo); + ImGui::MenuItem("Command Palette", "Ctrl+Shift+P", &mWindowVisible_CommandPalette); + ImGui::MenuItem("Inspector", nullptr, &mWindowVisible_Inspector); + ImGui::MenuItem("Content Browser", "Ctrl+Space", &mWindowVisible_ContentBrowser); + ImGui::MenuItem("World Structure", nullptr, &mWindowVisible_WorldStructure); + ImGui::MenuItem("World Properties", nullptr, &mWindowVisible_WorldProperties); + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + + if (mWindowVisible_ImGuiDemo) { + ImGui::ShowDemoWindow(&mWindowVisible_ImGuiDemo); + } + + if (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(GLFW_KEY_P, false)) { + mWindowVisible_CommandPalette = !mWindowVisible_CommandPalette; + } + if (mWindowVisible_CommandPalette) { + mEdCommandPalette.Show(&mWindowVisible_CommandPalette); + } + + if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) { + mWindowVisible_ContentBrowser = !mWindowVisible_ContentBrowser; + } + if (mWindowVisible_ContentBrowser) { + mEdContentBrowser.Show(&mWindowVisible_ContentBrowser); + } + + auto& camera = *mApp->GetActiveCamera(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::SetDrawlist(ImGui::GetBackgroundDrawList()); + + if (IsCurrentCameraEditor() && mEcm == ECM_Side3D) { + float viewManipulateRight = io.DisplaySize.x; + float viewManipulateTop = 0; + + // TODO get rid of this massive hack: how to manage const better for intuitively read-only, but write doesn't-care data? + auto& lastFrameInfo = const_cast(mApp->GetWorldRenderer()->GetLastFrameInfo()); + auto& view = lastFrameInfo.matrixView; + auto& proj = lastFrameInfo.matrixProj; + +#if 0 + ImGuizmo::ViewManipulate( + glm::value_ptr(view), + 200.0f, // TODO + ImVec2(viewManipulateRight - 128, viewManipulateTop), + ImVec2(128, 128), + 0x10101010); + // Extract eye and target position from view matrix + // - View matrix transforms world space to view space + // - Inverse view matrix should transform view space into world space + // - In view space, camera's pos is (0,0,0) and the look/forward vector should be (0,0,-1) + auto invView = glm::inverse(view); + camera.eye = invView * glm::vec4(0, 0, 0, 1); + camera.target = camera.eye + glm::vec3(invView * glm::vec4(0, 0, -1, 1)); +#endif + + // TODO draw this as a part of the world so it doesn't block objects +#if 0 + glm::mat4 identity(1.00f); + ImGuizmo::DrawGrid( + glm::value_ptr(view), + glm::value_ptr(proj), + glm::value_ptr(identity), + 100.f); +#endif + + { // Camera controls + auto cameraPos = camera.eye; + auto cameraForward = glm::normalize(camera.target - camera.eye); + // Always move on the horzontal flat plane + cameraForward.y = 0.0f; + + if (mMoveCamKeyboard) { + if (ImGui::IsKeyDown(ImGuiKey_W)) { + cameraPos += mMoveCamSlideSpeed * cameraForward; + } + if (ImGui::IsKeyDown(ImGuiKey_S)) { + auto cameraBack = glm::normalize(-cameraForward); + cameraPos += mMoveCamSlideSpeed * cameraBack; + } + if (ImGui::IsKeyDown(ImGuiKey_A)) { + auto cameraRight = glm::normalize(glm::cross(cameraForward, glm::vec3(0, 1, 0))); + auto cameraLeft = -cameraRight; + cameraPos += mMoveCamSlideSpeed * cameraLeft; + } + if (ImGui::IsKeyDown(ImGuiKey_D)) { + auto cameraRight = glm::normalize(glm::cross(cameraForward, glm::vec3(0, 1, 0))); + cameraPos += mMoveCamSlideSpeed * cameraRight; + } + if (ImGui::IsKeyDown(ImGuiKey_Space)) { + cameraPos.y += mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { + cameraPos.y -= mMoveCamSlideSpeed; + } + } + + if (mMoveCamScrollWheel) { + cameraPos += cameraForward * io.MouseWheel * mMoveCamScrollSpeed; + } + + camera.SetEyePos(cameraPos); + } + } else { + { // Camera controls + auto cameraPos = camera.eye; + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !io.WantCaptureMouse && !mDragCam_Happening) { + mDragCam_CamInitial = camera.eye; + mDragCam_CursorInitial = ImGui::GetMousePos(); + mDragCam_Happening = true; + } + if (mDragCam_Happening) { + auto newPos = ImGui::GetMousePos(); + // NOTE: we are emulating as if the mouse is dragging the "canvas", through moving the camera in the opposite direction of the natural position delta + float deltaX = mDragCam_CursorInitial.x - newPos.x; + cameraPos.x = mDragCam_CamInitial.x + deltaX / camera.pixelsPerMeter; + float deltaY = -(mDragCam_CursorInitial.y - newPos.y); // Invert Y delta because ImGui uses top-left origin (mouse moving down translates to positive value, but in our coordinate system down is negative) + cameraPos.y = mDragCam_CamInitial.y + deltaY / camera.pixelsPerMeter; + } + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { + mDragCam_Happening = false; + } + + if (mMoveCamKeyboard) { + if (ImGui::IsKeyDown(ImGuiKey_W)) { + cameraPos.y += mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_S)) { + cameraPos.y -= mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_A)) { + cameraPos.x -= mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_D)) { + cameraPos.x += mMoveCamSlideSpeed; + } + } + + if (mMoveCamScrollWheel) { + cameraPos.z = std::clamp(cameraPos.z + io.MouseWheel, 0.1f, 100.0f); + } + + camera.SetEyePos(cameraPos); + } + } + + if (mWindowVisible_Inspector) { + ImGui::Begin("Inspector"); + switch (mEdInspector.selectedItt) { + case EditorInspector::ITT_GameObject: { + auto object = static_cast(mEdInspector.selectedItPtr); + ShowInspector(object); + } break; + + case EditorInspector::ITT_Ires: { + auto ires = static_cast(mEdInspector.selectedItPtr); + ShowInspector(ires); + } break; + + case EditorInspector::ITT_Level: { + auto ldObj = static_cast(mEdInspector.selectedItPtr); + ShowInspector(ldObj); + } break; + + case EditorInspector::ITT_None: break; + } + ImGui::End(); + } + + if (mWindowVisible_WorldProperties) { + ImGui::Begin("World properties"); + ShowWorldProperties(); + ImGui::End(); + } + + if (mWindowVisible_WorldStructure) { + ImGui::Begin("World structure"); + GobjTreeNodeShowInfo showInfo{ + .in_editor = this, + }; + GobjTreeNode(showInfo, &world->GetRoot()); + + if (showInfo.out_openPopup) { + mPopupCurrent_GameObject = showInfo.out_openPopup; + + ImGui::OpenPopup("GameObject Popup"); + ImGui::SetNextWindowPos(ImGui::GetMousePos()); + } + if (ImGui::BeginPopup("GameObject Popup")) { + // Target no longer selected during popup open + if (!mPopupCurrent_GameObject) { + ImGui::CloseCurrentPopup(); + } + + if (ImGui::BeginMenu("Add child")) { + for (size_t i = 0; i < std::size(creatableGameObjects); ++i) { + auto& info = creatableGameObjects[i]; + if (ImGui::MenuItem(info.name)) { + auto object = info.factory(world); + mPopupCurrent_GameObject->AddChild(object); + } + } + ImGui::EndMenu(); + } + ImGui::Separator(); + if (ImGui::MenuItem("Remove")) { + ImGui::DialogConfirmation("Are you sure you want to delete this GameObject?", [object = mPopupCurrent_GameObject](bool yes) { + object->RemoveSelfFromParent(); + delete object; + }); + } + ImGui::EndPopup(); + } + ImGui::End(); + } + + ShowSpriteViewer(); + + ImGui::ShowDialogs(); + ImGui::ShowNotifications(); +} + +void EditorInstance::ShowWorldProperties() { + if (mApp->IsGameRunning()) { + if (ImGui::Button("Pause")) { + mApp->SetGameRunning(false); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("The game is currently running. Click to pause."); + } + } else { + if (ImGui::Button("Play")) { + mApp->SetGameRunning(true); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("The game is currently paused. Click to run."); + } + } + + auto& camera = *mApp->GetActiveCamera(); + + // vvv Camera settings (per instance) + ImGui::TextUnformatted("Active camera:"); + ImGui::Indent(); + + ImGui::TextUnformatted(camera.name.c_str()); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Object at <%p>", &camera); + } + + ImGui::InputFloat3("Eye", glm::value_ptr(camera.eye)); + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { + ImGui::OpenPopup("##CTXMENU"); + } + ImGui::InputFloat3("Target", glm::value_ptr(camera.target)); + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { + ImGui::OpenPopup("##CTXMENU"); + } + if (ImGui::BeginPopup("##CTXMENU", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) { + if (ImGui::MenuItem("Reset to origin")) { + camera.SetEyePos(glm::vec3(1.0f, 1.0f, 1.0f)); + } + if (ImGui::MenuItem("Reset completely")) { + camera.eye = glm::vec3(0, 0, 1); + camera.target = glm::vec3(0, 0, 0); + } + ImGui::EndPopup(); + } + + if (IsCurrentCameraEditor()) { + const char* preview; + switch (mEcm) { + case ECM_2D: preview = "2D view"; break; + case ECM_Side3D: preview = "Side 3D view"; break; + } + if (ImGui::BeginCombo("Mode", preview)) { + if (ImGui::Selectable("2D view", mEcm == ECM_2D)) { + if (mEcm != ECM_2D) { + mEcm = ECM_2D; + + // TODO project eye to world plane + + camera.SetHasPerspective(false); + } + } + if (ImGui::Selectable("Side 3D view", mEcm == ECM_Side3D, ImGuiSelectableFlags_None)) { + if (mEcm != ECM_Side3D) { + mEcm = ECM_Side3D; + + auto origEye = camera.eye; + auto origTarget = camera.target; + + // New setup: focus on the point of world plane that we were originally "hovering above" + camera.target = origEye; + camera.target.z = 0.0f; + + // New setup: move the eye back at an angle + camera.eye = camera.target; + camera.eye.x += 4.0f * std::cos(60.0f); + camera.eye.y += 0.0f; + camera.eye.z += 4.0f * std::sin(60.0f); + + camera.SetHasPerspective(true); + } + } + ImGui::EndCombo(); + } + } + + ImGui::Checkbox("Perspective", &camera.perspective); + if (camera.perspective) { + float fovDegress = camera.fov / M_PI * 180.0f; + if (ImGui::SliderFloat("FOV", &fovDegress, 1.0f, 180.0f)) { + camera.fov = fovDegress / 180.0f * M_PI; + } + } else { + if (ImGui::InputFloat("Pixels per meter", &camera.pixelsPerMeter)) { + camera.pixelsPerMeter = std::max(camera.pixelsPerMeter, 0.0f); + } + } + + ImGui::Unindent(); + // ^^^ Camera settings (per instance) + // vvv Camera control settings + ImGui::TextUnformatted("Camera controls:"); + ImGui::Indent(); + + ImGui::Checkbox("Move camera with WASD", &mMoveCamKeyboard); + ImGui::SliderFloat("Slide speed", &mMoveCamSlideSpeed, 0.1f, 10.0f); + + ImGui::Checkbox("Move camera with scoll wheel", &mMoveCamScrollWheel); + ImGui::SliderFloat("Scroll speed", &mMoveCamScrollSpeed, 0.01, 10.0f); + + ImGui::Unindent(); + // ^^^ Camera control settings +} + +// TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism + +void EditorInstance::ShowInspector(IresObject* ires) { + ires->ShowEditor(*this); +} + +void EditorInstance::ShowInspector(LevelManager::LoadableObject* ldObj) { + using namespace Tags; + using namespace ProjectBrussel_UNITY_ID; + + ImGui::InputText("Name", &ldObj->name); + ImGui::InputTextMultiline("Desciption", &ldObj->description); + + if (ImGui::CollapsingHeader("Instanciation Entries")) { + ldObj->level->ShowInstanciationEntries(*this); + } +} + +void EditorInstance::ShowInspector(GameObject* object) { + using namespace Tags; + using namespace ProjectBrussel_UNITY_ID; + + auto& io = ImGui::GetIO(); + + auto objectEa = static_cast(object->GetEditorAttachment()); + + auto ShowInspector = [&](/*array[6]*/ float* bounds = nullptr) { + glm::mat4 identityMatrix(1.00f); + + if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_T)) + mGuizmo.currOperation = ImGuizmo::TRANSLATE; + if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_R)) + mGuizmo.currOperation = ImGuizmo::ROTATE; + if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_S)) + mGuizmo.currOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mGuizmo.currOperation == ImGuizmo::TRANSLATE)) + mGuizmo.currOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mGuizmo.currOperation == ImGuizmo::ROTATE)) + mGuizmo.currOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mGuizmo.currOperation == ImGuizmo::SCALE)) + mGuizmo.currOperation = ImGuizmo::SCALE; + ImGui::SameLine(); + if (ImGui::RadioButton("Universal", mGuizmo.currOperation == ImGuizmo::UNIVERSAL)) + mGuizmo.currOperation = ImGuizmo::UNIVERSAL; + + if (mGuizmo.currOperation != ImGuizmo::SCALE) { + if (ImGui::RadioButton("Local", mGuizmo.currMode == ImGuizmo::LOCAL)) + mGuizmo.currMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mGuizmo.currMode == ImGuizmo::WORLD)) + mGuizmo.currMode = ImGuizmo::WORLD; + } + + ImGui::Checkbox("##UseSnap", &mGuizmo.useSnap); + ImGui::SameLine(); + switch (mGuizmo.currOperation) { + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Pos Snap", &mGuizmo.snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &mGuizmo.snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &mGuizmo.snap[0]); + break; + default: break; + } + + if (bounds) { + ImGui::Checkbox("Bound Sizing", &mGuizmo.boundSizing); + if (mGuizmo.boundSizing) { + ImGui::PushID(3); + ImGui::Checkbox("##BoundSizing", &mGuizmo.boundSizingSnap); + ImGui::SameLine(); + ImGui::InputFloat3("Bound Snap", mGuizmo.boundsSnap); + ImGui::PopID(); + } + } + + ImGui::Separator(); + + auto objectPos = object->GetPos(); + if (ImGui::InputFloat3("Translation", &objectPos.x)) { + object->SetPos(objectPos); + } + + auto objectRot = object->GetRotation(); + if (ImGui::InputFloat3("Rotation", &objectEa->eulerAnglesRotation.x)) { + objectRot = CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation); + object->SetRotation(objectRot); + } + + auto objectScale = object->GetScale(); + if (ImGui::InputFloat3("Scale", &objectScale.x)) { + object->SetScale(objectScale); + } + }; + + auto ShowGuizmo = [&](/*array[6]*/ float* bounds = nullptr) { + glm::mat4 identityMatrix(1.00f); + + auto& lastFrameInfo = mApp->GetWorldRenderer()->GetLastFrameInfo(); + auto& cameraViewMat = lastFrameInfo.matrixView; + const float* cameraView = &cameraViewMat[0][0]; + auto& cameraProjMat = lastFrameInfo.matrixProj; + const float* cameraProj = &cameraProjMat[0][0]; + + auto objectPos = object->GetPos(); + auto objectScale = object->GetScale(); + + float matrix[16]; + ImGuizmo::RecomposeMatrixFromComponents(&objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x, matrix); + + bool edited = ImGuizmo::Manipulate( + cameraView, + cameraProj, + mGuizmo.currOperation, + mGuizmo.currMode, + matrix, + nullptr, + mGuizmo.useSnap ? mGuizmo.snap : nullptr, + mGuizmo.boundSizing ? bounds : nullptr, + (bounds && mGuizmo.boundSizingSnap) ? mGuizmo.boundsSnap : nullptr); + + if (edited) { + ImGuizmo::DecomposeMatrixToComponents(matrix, &objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x); + object->SetPos(objectPos); + object->SetRotation(CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation)); + object->SetScale(objectScale); + } + }; + + auto type = object->GetKind(); + switch (type) { + case GameObject::KD_Player: { + ShowInspector(); + ShowGuizmo(); + ImGui::Separator(); + + auto player = static_cast(object); + auto ea = static_cast(objectEa); + auto& kb = player->keybinds; + + ImGui::Text("Player #%d", player->GetId()); + + ImGui::TextUnformatted("Spritesheet: "); + ImGui::SameLine(); + IresObject::ShowReferenceSafe(*this, ea->confSprite.Get()); + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Spritesheet).data())) { + auto spritesheet = *static_cast(payload->Data); + ea->confSprite.Attach(spritesheet); + auto def = spritesheet->GetInstance(); + player->sprite.SetDefinition(def); + player->renderObject.autofill_TextureAtlas.Attach(def->GetAtlas()); + } + ImGui::EndDragDropTarget(); + } + + ImGui::TextUnformatted("Material: "); + ImGui::SameLine(); + IresObject::ShowReferenceSafe(*this, ea->confMaterial.Get()); + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Material).data())) { + auto material = *static_cast(payload->Data); + ea->confMaterial.Attach(material); + player->SetMaterial(material->GetInstance()); + } + ImGui::EndDragDropTarget(); + } + + if (ImGui::Button("Load config")) { + bool success = player->LoadFromFile(); + if (success) { + ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully loaded player config")); + } + } + ImGui::SameLine(); + if (ImGui::Button("Save config")) { + bool success = player->SaveToFile(); + if (success) { + ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully saved player config")); + } + } + + ImGui::Text("Move left (%s)", ImGui::GetKeyNameGlfw(kb.keyLeft)); + ImGui::SameLine(); + if (ImGui::Button("Change##Move left")) { + PushKeyCodeRecorder(mApp, &kb.keyLeft, &kb.pressedLeft); + } + + ImGui::Text("Move right (%s)", ImGui::GetKeyNameGlfw(kb.keyRight)); + ImGui::SameLine(); + if (ImGui::Button("Change##Move right")) { + PushKeyCodeRecorder(mApp, &kb.keyRight, &kb.pressedRight); + } + + ImGui::Text("Jump (%s)", ImGui::GetKeyNameGlfw(kb.keyJump)); + ImGui::SameLine(); + if (ImGui::Button("Change##Jump")) { + PushKeyCodeRecorder(mApp, &kb.keyJump, &kb.pressedJump); + } + + ImGui::Text("Attack (%s)", ImGui::GetKeyNameGlfw(kb.keyAttack)); + ImGui::SameLine(); + if (ImGui::Button("Change##Attack")) { + PushKeyCodeRecorder(mApp, &kb.keyAttack, &kb.pressedAttack); + } + } break; + + case GameObject::KD_SimpleGeometry: { + auto sg = static_cast(object); + + float bounds[6] = { + /*x1*/ -sg->GetSize().x / 2.0f, + /*y1*/ -sg->GetSize().y / 2.0f, + /*z1*/ -sg->GetSize().z / 2.0f, + /*x2*/ +sg->GetSize().x / 2.0f, + /*y2*/ +sg->GetSize().y / 2.0f, + /*z2*/ +sg->GetSize().z / 2.0f, + }; + + ShowInspector(bounds); + ShowGuizmo(bounds); + ImGui::Separator(); + + sg->SetSize(glm::vec3(bounds[3] - bounds[0], bounds[4] - bounds[1], bounds[5] - bounds[2])); + + auto size = sg->GetSize(); + if (ImGui::InputFloat3("Size", &size.x)) { + sg->SetSize(size); + } + + auto xFaceColor = sg->GetXFaceColor(); + if (ImGui::ColorEdit4("X color", &xFaceColor)) { + sg->SetXFaceColor(xFaceColor); + } + auto yFaceColor = sg->GetYFaceColor(); + if (ImGui::ColorEdit4("Y color", &yFaceColor)) { + sg->SetYFaceColor(yFaceColor); + } + auto zFaceColor = sg->GetZFaceColor(); + if (ImGui::ColorEdit4("Z color", &zFaceColor)) { + sg->SetZFaceColor(zFaceColor); + } + + if (ImGui::Button("Sync from X color")) { + sg->SetYFaceColor(xFaceColor); + sg->SetZFaceColor(xFaceColor); + } + ImGui::SameLine(); + if (ImGui::Button("Reset")) { + sg->SetXFaceColor(kXAxisColor); + sg->SetYFaceColor(kYAxisColor); + sg->SetZFaceColor(kZAxisColor); + } + } break; + + case GameObject::KD_Building: { + ShowInspector(); + ShowGuizmo(); + ImGui::Separator(); + + auto b = static_cast(object); + // TODO + } break; + + case GameObject::KD_LevelWrapper: { + ShowInspector(); + ShowGuizmo(); + ImGui::Separator(); + + auto lwo = static_cast(object); + // TODO + } break; + + default: { + ShowInspector(); + ShowGuizmo(); + } break; + } +} + +void EditorInstance::OpenSpriteViewer(SpriteDefinition* sprite) { + mSpriteView_Instance.Attach(sprite); + mSpriteView_Frame = 0; + mSpriteView_OpenNextFrame = true; +} + +void EditorInstance::ShowSpriteViewer() { + if (mSpriteView_Instance == nullptr) return; + if (!mSpriteView_Instance->IsValid()) return; + + if (mSpriteView_OpenNextFrame) { + mSpriteView_OpenNextFrame = false; + ImGui::OpenPopup("Sprite Viewer"); + } + + bool windowOpen = true; + if (ImGui::BeginPopupModal("Sprite Viewer", &windowOpen)) { + auto atlas = mSpriteView_Instance->GetAtlas(); + auto atlasSize = atlas->GetInfo().size; + auto& frames = mSpriteView_Instance->GetFrames(); + + int frameCount = mSpriteView_Instance->GetFrames().size(); + if (ImGui::Button("<")) { + --mSpriteView_Frame; + } + ImGui::SameLine(); + ImGui::Text("%d/%d", mSpriteView_Frame + 1, frameCount); + ImGui::SameLine(); + if (ImGui::Button(">")) { + ++mSpriteView_Frame; + } + // Scrolling down (negative value) should advance, so invert the sign + mSpriteView_Frame += -std::round(ImGui::GetIO().MouseWheel); + // Clamp mSpriteView_Frame to range [0, frameCount) + if (mSpriteView_Frame < 0) { + mSpriteView_Frame = frameCount - 1; + } else if (mSpriteView_Frame >= frameCount) { + mSpriteView_Frame = 0; + } + + auto& currFrame = frames[mSpriteView_Frame]; + auto boundingBox = mSpriteView_Instance->GetBoundingBox(); + ImGui::Text("Frame size: (%d, %d)", boundingBox.x, boundingBox.y); + ImGui::Text("Frame location: (%f, %f) to (%f, %f)", currFrame.u0, currFrame.v0, currFrame.u1, currFrame.v1); + ImGui::Image( + (ImTextureID)(uintptr_t)atlas->GetHandle(), + Utils::FitImage(atlasSize), + ImVec2(currFrame.u0, currFrame.v0), + ImVec2(currFrame.u1, currFrame.v1)); + + ImGui::EndPopup(); + } +} -- cgit v1.2.3-70-g09d2