aboutsummaryrefslogtreecommitdiff
path: root/source/Game/EditorCorePrivate.cpp
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-06-03 23:26:44 -0700
committerrtk0c <[email protected]>2022-06-03 23:26:44 -0700
commit60ccc62f4934e44ad5b905fdbcf458302b8d8a09 (patch)
tree02ec83cc8387abfd08bd5ee7ea4e8115f1bfb8d0 /source/Game/EditorCorePrivate.cpp
parentc2ef7737536bf1f8c81fcfae95c0183b21c9753f (diff)
Changeset: 63 [WIP] Rename directories
Diffstat (limited to 'source/Game/EditorCorePrivate.cpp')
-rw-r--r--source/Game/EditorCorePrivate.cpp1113
1 files changed, 1113 insertions, 0 deletions
diff --git a/source/Game/EditorCorePrivate.cpp b/source/Game/EditorCorePrivate.cpp
new file mode 100644
index 0000000..43857a8
--- /dev/null
+++ b/source/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 <Macros.hpp>
+#include <Metadata.hpp>
+#include <ScopeGuard.hpp>
+#include <YCombinator.hpp>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <imgui.h>
+#include <misc/cpp/imgui_stdlib.h>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
+#include <glm/gtc/quaternion.hpp>
+#include <glm/gtc/type_ptr.hpp>
+#include <glm/gtx/quaternion.hpp>
+#include <limits>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+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<IresObject::Kind>(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<IresObject*>(origItPtr);
+ IresManager::instance->Save(ires);
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("Reload", !isIttIres)) {
+ auto ires = static_cast<IresObject*>(origItPtr);
+ IresManager::instance->Reload(ires);
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("Rename", !isIttIres) ||
+ (isIttIres && ImGui::IsKeyPressed(ImGuiKey_F2, false)))
+ {
+ auto ires = static_cast<IresObject*>(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<IresObject*>(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<LevelManager::LoadableObject*>(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 = "<unnamed level>";
+ }
+
+ 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<uintptr_t>(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> IEditor::CreateInstance(App* app) {
+ return std::make_unique<EditorInstance>(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<RendererFrameInfo&>(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<GameObject*>(mEdInspector.selectedItPtr);
+ ShowInspector(object);
+ } break;
+
+ case EditorInspector::ITT_Ires: {
+ auto ires = static_cast<IresObject*>(mEdInspector.selectedItPtr);
+ ShowInspector(ires);
+ } break;
+
+ case EditorInspector::ITT_Level: {
+ auto ldObj = static_cast<LevelManager::LoadableObject*>(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<EaGameObject*>(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<Player*>(object);
+ auto ea = static_cast<EaPlayer*>(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<IresSpritesheet* const*>(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<IresMaterial* const*>(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<SimpleGeometryObject*>(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<BuildingObject*>(object);
+ // TODO
+ } break;
+
+ case GameObject::KD_LevelWrapper: {
+ ShowInspector();
+ ShowGuizmo();
+ ImGui::Separator();
+
+ auto lwo = static_cast<LevelWrapperObject*>(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();
+ }
+}