diff options
author | hnOsmium0001 <[email protected]> | 2022-04-09 13:29:41 -0700 |
---|---|---|
committer | hnOsmium0001 <[email protected]> | 2022-04-09 13:29:41 -0700 |
commit | 906557f094e407ce21d429ef647bc75fe3179cf1 (patch) | |
tree | 5e6aaed6537a0328318b6cd6561f6a76bf8aa27d /source | |
parent | e47a98793e58a5dbbe76bfed27e59408e43538e4 (diff) |
More work on editor
Diffstat (limited to 'source')
-rw-r--r-- | source/App.cpp | 8 | ||||
-rw-r--r-- | source/App.hpp | 4 | ||||
-rw-r--r-- | source/AppConfig.hpp | 5 | ||||
-rw-r--r-- | source/CMakeLists.txt | 11 | ||||
-rw-r--r-- | source/Color.hpp | 144 | ||||
-rw-r--r-- | source/EditorAttachment.hpp | 12 | ||||
-rw-r--r-- | source/EditorAttachmentImpl.cpp | 19 | ||||
-rw-r--r-- | source/EditorAttachmentImpl.hpp | 29 | ||||
-rw-r--r-- | source/EditorCore.cpp | 79 | ||||
-rw-r--r-- | source/EditorCore.hpp | 26 | ||||
-rw-r--r-- | source/EditorCoreAPI.hpp | 18 | ||||
-rw-r--r-- | source/EditorCore_Egoa.hpp | 11 | ||||
-rw-r--r-- | source/EditorInspector.cpp | 15 | ||||
-rw-r--r-- | source/EditorInspector.hpp | 17 | ||||
-rw-r--r-- | source/EditorResources.cpp | 71 | ||||
-rw-r--r-- | source/EditorResources.hpp | 32 | ||||
-rw-r--r-- | source/EditorUtils.cpp | 195 | ||||
-rw-r--r-- | source/EditorUtils.hpp | 32 | ||||
-rw-r--r-- | source/GameObject.cpp | 16 | ||||
-rw-r--r-- | source/GameObject.hpp | 17 | ||||
-rw-r--r-- | source/Shader.cpp | 142 | ||||
-rw-r--r-- | source/Shader.hpp | 45 | ||||
-rw-r--r-- | source/Utils.hpp | 4 | ||||
-rw-r--r-- | source/main.cpp | 85 |
24 files changed, 787 insertions, 250 deletions
diff --git a/source/App.cpp b/source/App.cpp index 3907af9..ff0a5a5 100644 --- a/source/App.cpp +++ b/source/App.cpp @@ -1,5 +1,4 @@ #include "App.hpp" -#include "GLFW/glfw3.h" #include <utility> @@ -18,16 +17,13 @@ void App::Init() { } mCurrentWorld->Awaken(); - mEditor = EditorInstance_Alloc(this, mCurrentWorld.get()); + mEditor = std::make_unique<EditorInstance>(this, mCurrentWorld.get()); } void App::Shutdown() { if (!mInitialized) return; mInitialized = false; - - EditorInstance_Free(mEditor); mEditor = nullptr; - mCurrentWorld->Resleep(); mCurrentWorld = nullptr; mPlayers.clear(); @@ -37,7 +33,7 @@ void App::Show() { mCurrentWorld->Draw(); if (mEditorShown) { - EditorInstance_Show(mEditor); + mEditor->Show(); } } diff --git a/source/App.hpp b/source/App.hpp index b861730..731ab06 100644 --- a/source/App.hpp +++ b/source/App.hpp @@ -1,6 +1,6 @@ #pragma once -#include "EditorCoreAPI.hpp" +#include "EditorCore.hpp" #include "Player.hpp" #include "PodVector.hpp" #include "World.hpp" @@ -19,7 +19,7 @@ class App { private: std::deque<KeyCaptureCallback> mKeyCaptureCallbacks; PodVector<Player*> mPlayers; - EditorInstance* mEditor; + std::unique_ptr<EditorInstance> mEditor; std::unique_ptr<GameWorld> mCurrentWorld; bool mEditorShown = true; bool mInitialized = false; diff --git a/source/AppConfig.hpp b/source/AppConfig.hpp index 53e84b1..8d69fad 100644 --- a/source/AppConfig.hpp +++ b/source/AppConfig.hpp @@ -8,5 +8,10 @@ constexpr std::string_view kAppName = "ProjectBrussel"; // Since kAppName is initialized by a C string literal, we know it's null termianted constexpr const char* kAppNameC = kAppName.data(); +// Duplicate each as path and string so that on non-UTF-8 platforms (e.g. Windows) we can easily do string manipulation on the paths +// NOTE: even though non-const, these should not be modified outside of main() +inline std::filesystem::path dataDirPath; inline std::string dataDir; +inline std::filesystem::path assetDirPath; +inline std::string assetDir; } // namespace AppConfig diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 7c47026..0749b62 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -2,6 +2,11 @@ target_sources(${PROJECT_NAME} PRIVATE App.cpp CpuMesh.cpp + EditorAccessories.cpp + EditorCore.cpp + EditorNotification.cpp + EditorResources.cpp + EditorUtils.cpp GameObject.cpp Level.cpp Material.cpp @@ -17,11 +22,7 @@ PRIVATE set(ProjectBrussel_SINGLE_UNIT_SRC stb_implementation.c main.cpp - Utils.cpp - EditorAccessories.cpp - EditorCore.cpp - EditorNotification.cpp - EditorResources.cpp + Utils.cpp # May include platform headers ) target_sources(${PROJECT_NAME} PRIVATE ${ProjectBrussel_SINGLE_UNIT_SRC}) set_source_files_properties(${ProjectBrussel_SINGLE_UNIT_SRC} diff --git a/source/Color.hpp b/source/Color.hpp new file mode 100644 index 0000000..c7d633c --- /dev/null +++ b/source/Color.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "Utils.hpp" + +#include <algorithm> +#include <cstdint> +#include <glm/glm.hpp> +#include <limits> + +class HsvColor; +class RgbaColor { +public: + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + +public: + constexpr RgbaColor() noexcept + : r{ 255 } + , g{ 255 } + , b{ 255 } + , a{ 255 } { + } + + constexpr RgbaColor(float r, float g, float b, float a = 1.0f) noexcept + : r{ static_cast<uint8_t>(r * 255.0f) } + , g{ static_cast<uint8_t>(g * 255.0f) } + , b{ static_cast<uint8_t>(b * 255.0f) } + , a{ static_cast<uint8_t>(a * 255.0f) } { + } + + constexpr RgbaColor(int r, int g, int b, int a = 255) noexcept + : r{ static_cast<uint8_t>(r & 0xFF) } + , g{ static_cast<uint8_t>(g & 0xFF) } + , b{ static_cast<uint8_t>(b & 0xFF) } + , a{ static_cast<uint8_t>(a & 0xFF) } { + } + + constexpr RgbaColor(uint32_t rgba) noexcept + : r{ static_cast<uint8_t>((rgba >> 0) & 0xFF) } + , g{ static_cast<uint8_t>((rgba >> 8) & 0xFF) } + , b{ static_cast<uint8_t>((rgba >> 16) & 0xFF) } + , a{ static_cast<uint8_t>((rgba >> 24) & 0xFF) } { + } + + constexpr uint32_t GetScalar() const noexcept { + uint32_t res = 0; + res |= r << 0; + res |= g << 8; + res |= b << 16; + res |= a << 24; + return res; + } + + constexpr void SetScalar(uint32_t scalar) noexcept { + r = (scalar >> 0) & 0xFF; + g = (scalar >> 8) & 0xFF; + b = (scalar >> 16) & 0xFF; + a = (scalar >> 24) & 0xFF; + } + + constexpr float GetNormalizedRed() const noexcept { + return r / 255.0f; + } + + constexpr float GetNormalizedGreen() const noexcept { + return g / 255.0f; + } + + constexpr float GetNormalizedBlue() const noexcept { + return b / 255.0f; + } + + constexpr float GetNormalizedAlpha() const noexcept { + return a / 255.0f; + } + + constexpr glm::ivec4 ToIVec() const noexcept { + return { r, g, b, a }; + } + + constexpr glm::vec4 ToVec() const noexcept { + return { GetNormalizedRed(), GetNormalizedGreen(), GetNormalizedBlue(), GetNormalizedAlpha() }; + } + + // Forward declaring because cyclic reference between RgbaColor and HsvColor + constexpr HsvColor ToHsv() const noexcept; + + friend constexpr bool operator==(const RgbaColor&, const RgbaColor&) noexcept = default; +}; + +class HsvColor { +public: + float h; + float s; + float v; + float a; + +public: + constexpr HsvColor() noexcept + : h{ 0.0f } + , s{ 0.0f } + , v{ 1.0f } + , a{ 1.0f } { + } + + constexpr HsvColor(float h, float s, float v, float a) noexcept + : h{ h } + , s{ s } + , v{ v } + , a{ a } { + } + + // Forward declaring because cyclic reference between RgbaColor and HsvColor + constexpr RgbaColor ToRgba() const noexcept; +}; + +constexpr HsvColor RgbaColor::ToHsv() const noexcept { + float r = GetNormalizedRed(); + float g = GetNormalizedBlue(); + float b = GetNormalizedGreen(); + float a = GetNormalizedAlpha(); + + auto p = g < b ? glm::vec4(b, g, -1, 2.0f / 3.0f) : glm::vec4(g, b, 0, -1.0f / 3.0f); + auto q = r < p.x ? glm::vec4(p.x, p.y, p.w, r) : glm::vec4(r, p.y, p.z, p.x); + float c = q.x - std::min(q.w, q.y); + float h = Utils::Abs((q.w - q.y) / (6 * c + std::numeric_limits<float>::epsilon()) + q.z); + + glm::vec3 hcv{ h, c, q.x }; + float s = hcv.y / (hcv.z + std::numeric_limits<float>::epsilon()); + return HsvColor(hcv.x, s, hcv.z, a); +} + +constexpr RgbaColor HsvColor::ToRgba() const noexcept { + float r = Utils::Abs(h * 6 - 3) - 1; + float g = 2 - Utils::Abs(h * 6 - 2); + float b = 2 - Utils::Abs(h * 6 - 4); + + auto rgb = glm::vec3{ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }; + auto vc = (rgb - glm::vec3{ 0, 0, 0 }) * s + glm::vec3{ 1, 1, 1 } * v; + + return RgbaColor(vc.x, vc.y, vc.z, a); +} diff --git a/source/EditorAttachment.hpp b/source/EditorAttachment.hpp new file mode 100644 index 0000000..61b824b --- /dev/null +++ b/source/EditorAttachment.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include <string> + +class EditorAttachment { +public: + std::string name; + +public: + EditorAttachment(); + virtual ~EditorAttachment() = default; +}; diff --git a/source/EditorAttachmentImpl.cpp b/source/EditorAttachmentImpl.cpp new file mode 100644 index 0000000..79c72ff --- /dev/null +++ b/source/EditorAttachmentImpl.cpp @@ -0,0 +1,19 @@ +#include "EditorAttachmentImpl.hpp" + +std::unique_ptr<EditorAttachment> EaGameObject::Create(GameObject* object) { + EditorAttachment* result; + + using namespace Tags; + switch (object->GetTypeTag()) { + case GOT_Player: result = new EaPlayer(); break; + case GOT_LevelWrapper: result = new EaLevelWrapper(); break; + + default: result = new EditorAttachment(); break; + } + + result->name = NameOf(object->GetTypeTag()); + return std::unique_ptr<EditorAttachment>(result); +} + +void EaShader::ShowInspector() { +} diff --git a/source/EditorAttachmentImpl.hpp b/source/EditorAttachmentImpl.hpp new file mode 100644 index 0000000..7763a8f --- /dev/null +++ b/source/EditorAttachmentImpl.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "EditorAttachment.hpp" +#include "EditorInspector.hpp" +#include "GameObject.hpp" +#include "Player.hpp" + +#include <memory> + +class EaGameObject : public EditorAttachment { +public: + static std::unique_ptr<EditorAttachment> Create(GameObject* object); +}; + +class EaPlayer : public EditorAttachment { +public: +}; + +class EaLevelWrapper : public EditorAttachment { +public: +}; + +class EaShader : public EditorAttachment, public IEditorInspectorTarget { +public: + Shader* shader; + +public: + void ShowInspector() override; +}; diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp index 3bf9ecb..7744793 100644 --- a/source/EditorCore.cpp +++ b/source/EditorCore.cpp @@ -1,12 +1,12 @@ #include "EditorCore.hpp" -#include "EditorCoreAPI.hpp" #include "App.hpp" #include "AppConfig.hpp" #include "CpuMesh.hpp" #include "EditorAccessories.hpp" -#include "EditorCore_Egoa.hpp" +#include "EditorAttachmentImpl.hpp" #include "EditorNotification.hpp" +#include "EditorUtils.hpp" #include "GameObjectTypeTag.hpp" #include "Level.hpp" #include "Mesh.hpp" @@ -16,8 +16,6 @@ #include <GLFW/glfw3.h> #include <ImGuizmo.h> -#include <backends/imgui_impl_glfw.h> -#include <imgui.h> #include <functional> #include <memory> #include <utility> @@ -40,23 +38,13 @@ void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) { } } // namespace ProjectBrussel_UNITY_ID -const char* ImGui::GetKeyNameGlfw(int key) { - return GetKeyName(ImGui_ImplGlfw_KeyToImGuiKey(key)); -} - -std::unique_ptr<EditorGameObjectAttachment> EditorGameObjectAttachment::Create(GameObject* object) { - EditorGameObjectAttachment* result; - - using namespace Tags; - switch (object->GetTypeTag()) { - case GOT_Player: result = new EgoaPlayer(); break; - case GOT_LevelWrapper: result = new EgoaLevelWrapper(); break; - - default: result = new EditorGameObjectAttachment(); break; - } +EditorInstance::EditorInstance(App* app, GameWorld* world) + : mApp{ app } + , mWorld{ world } + , mEdInspector() + , mEdContentBrowser(&mEdInspector) {} - result->name = NameOf(object->GetTypeTag()); - return std::unique_ptr<EditorGameObjectAttachment>(result); +EditorInstance::~EditorInstance() { } void EditorInstance::Show() { @@ -71,26 +59,26 @@ void EditorInstance::Show() { ImGui::End(); ImGui::Begin("Inspector"); - ShowInspector(); + if (mSelectedGameObject) { + ShowInspector(mSelectedGameObject); + } ImGui::End(); } void EditorInstance::ShowWorldProperties() { } -void EditorInstance::ShowInspector() { +void EditorInstance::ShowInspector(GameObject* object) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; - if (!mSelectedGameObject) return; - - auto type = mSelectedGameObject->GetTypeTag(); + auto type = object->GetTypeTag(); switch (type) { case Tags::GOT_Player: { - ShowGameObjecetFields(mSelectedGameObject); + ShowGameObjecetFields(object); ImGui::Separator(); - auto player = static_cast<Player*>(mSelectedGameObject); + auto player = static_cast<Player*>(object); auto& kb = player->keybinds; ImGui::Text("Player #%d", player->GetId()); @@ -135,26 +123,35 @@ void EditorInstance::ShowInspector() { } break; case Tags::GOT_LevelWrapper: { - ShowGameObjecetFields(mSelectedGameObject); + ShowGameObjecetFields(object); ImGui::Separator(); - auto lwo = static_cast<LevelWrapperObject*>(mSelectedGameObject); + auto lwo = static_cast<LevelWrapperObject*>(object); // TODO } break; default: - ShowGameObjecetFields(mSelectedGameObject); + ShowGameObjecetFields(object); break; } } void EditorInstance::ShowGameObjecetFields(GameObject* object) { + auto pos = object->GetPos(); + if (ImGui::InputFloat3("Position", &pos.x)) { + object->SetPos(pos); + } + + auto quat = object->GetRotation(); + if (ImGui::InputFloat4("Rotation", &quat.x)) { + object->SetRotation(quat); + } } void EditorInstance::ShowGameObjectInTree(GameObject* object) { auto attachment = object->GetEditorAttachment(); if (!attachment) { - attachment = EditorGameObjectAttachment::Create(object).release(); + attachment = EaGameObject::Create(object).release(); object->SetEditorAttachment(attachment); // NOTE: takes ownership } @@ -178,23 +175,3 @@ void EditorInstance::ShowGameObjectInTree(GameObject* object) { ImGui::TreePop(); } } - -// ======================== // -// EditorCoreAPI.hpp things // -// ======================== // - -void EditorGameObjectAttachmentDeleter::operator()(EditorGameObjectAttachment* obj) { - delete obj; -} - -EditorInstance* EditorInstance_Alloc(App* app, GameWorld* world) { - return new EditorInstance(app, world); -} - -void EditorInstance_Free(EditorInstance* editor) { - delete editor; -} - -void EditorInstance_Show(EditorInstance* editor) { - editor->Show(); -} diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp index 3f9eb11..a9ef93a 100644 --- a/source/EditorCore.hpp +++ b/source/EditorCore.hpp @@ -1,5 +1,8 @@ #pragma once +#include "EditorAttachment.hpp" +#include "EditorInspector.hpp" +#include "EditorResources.hpp" #include "GameObject.hpp" #include "World.hpp" @@ -7,37 +10,24 @@ #include <string> class App; - -namespace ImGui { -const char* GetKeyNameGlfw(int key); -} // namespace ImGui - -class EditorGameObjectAttachment { -public: - std::string name; - -public: - static std::unique_ptr<EditorGameObjectAttachment> Create(GameObject* object); - virtual ~EditorGameObjectAttachment() = default; -}; - class EditorInstance { private: App* mApp; GameWorld* mWorld; GameObject* mSelectedGameObject = nullptr; + EditorInspector mEdInspector; + EditorContentBrowser mEdContentBrowser; public: - EditorInstance(App* app, GameWorld* world) - : mApp{ app } - , mWorld{ world } {} + EditorInstance(App* app, GameWorld* world); + ~EditorInstance(); void Show(); private: void ShowWorldProperties(); - void ShowInspector(); + void ShowInspector(GameObject* object); void ShowGameObjecetFields(GameObject* object); void ShowGameObjectInTree(GameObject* object); diff --git a/source/EditorCoreAPI.hpp b/source/EditorCoreAPI.hpp deleted file mode 100644 index fa68822..0000000 --- a/source/EditorCoreAPI.hpp +++ /dev/null @@ -1,18 +0,0 @@ -// This file contains minimal definitions for other game components to integrate with editor -// Doing this instead of directly includign EditorCore.hpp and drastically remove the amount of code that needs to be dragged into every header - -#pragma once - -// Forward declarations -class App; -class GameWorld; - -class EditorGameObjectAttachment; -struct EditorGameObjectAttachmentDeleter { - void operator()(EditorGameObjectAttachment* obj); -}; - -class EditorInstance; -EditorInstance* EditorInstance_Alloc(App* app, GameWorld* world); -void EditorInstance_Free(EditorInstance* editor); -void EditorInstance_Show(EditorInstance* editor); diff --git a/source/EditorCore_Egoa.hpp b/source/EditorCore_Egoa.hpp deleted file mode 100644 index 3a8dc36..0000000 --- a/source/EditorCore_Egoa.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "EditorCore.hpp" - -class EgoaPlayer : public EditorGameObjectAttachment { -public: -}; - -class EgoaLevelWrapper : public EditorGameObjectAttachment { -public: -}; diff --git a/source/EditorInspector.cpp b/source/EditorInspector.cpp new file mode 100644 index 0000000..2f1d65c --- /dev/null +++ b/source/EditorInspector.cpp @@ -0,0 +1,15 @@ +#include "EditorInspector.hpp" + +void EditorInspector::Show() { + if (mCurrentTarget) { + mCurrentTarget->ShowInspector(); + } +} + +IEditorInspectorTarget* EditorInspector::GetCurrentTarget() const { + return mCurrentTarget; +} + +void EditorInspector::SetCurrentTarget(IEditorInspectorTarget* target) { + mCurrentTarget = target; +} diff --git a/source/EditorInspector.hpp b/source/EditorInspector.hpp new file mode 100644 index 0000000..d74b7a8 --- /dev/null +++ b/source/EditorInspector.hpp @@ -0,0 +1,17 @@ +#pragma once + +class IEditorInspectorTarget { +public: + virtual void ShowInspector() = 0; +}; + +class EditorInspector { +private: + IEditorInspectorTarget* mCurrentTarget = nullptr; + +public: + void Show(); + + IEditorInspectorTarget* GetCurrentTarget() const; + void SetCurrentTarget(IEditorInspectorTarget* target); +}; diff --git a/source/EditorResources.cpp b/source/EditorResources.cpp index 0f91523..722c1f1 100644 --- a/source/EditorResources.cpp +++ b/source/EditorResources.cpp @@ -1 +1,72 @@ #include "EditorResources.hpp" + +#include "EditorAttachmentImpl.hpp" +#include "EditorCore.hpp" +#include "EditorInspector.hpp" +#include "EditorNotification.hpp" +#include "EditorUtils.hpp" +#include "Shader.hpp" + +#include <imgui.h> + +EditorContentBrowser::EditorContentBrowser(EditorInspector* inspector) + : mInspector{ inspector } { +} + +void EditorContentBrowser::Show() { + if (visible) { + ImGuiWindowFlags windowFlags; + if (docked) { + // 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, 0.5f); + windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; + } else { + windowFlags = 0; + } + ImGui::Begin("Content Browser", &visible, windowFlags); + + ImGui::Splitter(true, kSplitterThickness, &splitterLeft, &splitterRight, kLeftPaneMinWidth, kRightPaneMinWidth); + + ImGui::BeginChild("LeftPane", ImVec2(splitterLeft - kPadding, 0.0f)); + + if (ImGui::Selectable("Settings", mPane == P_Settings)) { + mPane = P_Settings; + } + if (ImGui::Selectable("Shaders", mPane == P_Shader)) { + mPane = P_Shader; + } + + ImGui::EndChild(); // Window "LeftPane" + + ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding); + ImGui::BeginChild("RightPane"); // Fill remaining space + + switch (mPane) { + case P_Settings: { + ImGui::Checkbox("Docked", &docked); + } break; + + case P_Shader: { + auto& shaders = ShaderManager::instance->GetShaders(); + for (auto it = shaders.begin(); it != shaders.end(); ++it) { + auto name = it.key(); + auto shader = it.value().Get(); + + shader->GatherDetailsIfAbsent(); + auto details = shader->GetDetails(); + auto ea = static_cast<EaShader*>(shader->GetEditorAttachment()); + + if (ImGui::Selectable(name, mInspector->GetCurrentTarget() == ea)) { + mInspector->SetCurrentTarget(ea); + } + } + } break; + } + + ImGui::EndChild(); // Window "RightPane" + + ImGui::End(); + } +} diff --git a/source/EditorResources.hpp b/source/EditorResources.hpp index e868d74..f4e1ffe 100644 --- a/source/EditorResources.hpp +++ b/source/EditorResources.hpp @@ -1,5 +1,35 @@ #pragma once -class EditorResourcePane { +#include "Shader.hpp" +class EditorInspector; +class EditorContentBrowser { +private: + enum Pane { + P_Settings, + P_Shader, + }; + + static constexpr float kSplitterThickness = 3.0f; + static constexpr float kPadding = 4.0f; + + // <root> + static constexpr float kLeftPaneMinWidth = 200.0f; + static constexpr float kRightPaneMinWidth = 200.0f; + + EditorInspector* mInspector; + Pane mPane = P_Settings; + float splitterLeft = kLeftPaneMinWidth; + float splitterRight = 0.0f; + bool docked = true; + bool visible = false; + +public: + EditorContentBrowser(EditorInspector* inspector); + ~EditorContentBrowser(); + + bool IsVisible() const; + void SetVisible(bool visible); + + void Show(); }; diff --git a/source/EditorUtils.cpp b/source/EditorUtils.cpp new file mode 100644 index 0000000..ab6ffad --- /dev/null +++ b/source/EditorUtils.cpp @@ -0,0 +1,195 @@ +#include "EditorUtils.hpp" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include <imgui_internal.h> + +#include <backends/imgui_impl_glfw.h> + +const char* ImGui::GetKeyNameGlfw(int key) { + return GetKeyName(ImGui_ImplGlfw_KeyToImGuiKey(key)); +} + +void ImGui::SetNextWindowSizeRelScreen(float xPercent, float yPercent, ImGuiCond cond) { + auto vs = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowSize({ vs.x * xPercent, vs.y * yPercent }, cond); +} + +void ImGui::SetNextWindowCentered(ImGuiCond cond) { + auto vs = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos({ vs.x / 2, vs.y / 2 }, cond, { 0.5f, 0.5f }); +} + +void ImGui::PushDisabled() { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f * ImGui::GetStyle().Alpha); +} + +void ImGui::PopDisabled() { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); +} + +bool ImGui::Button(const char* label, bool disabled) { + return Button(label, ImVec2{}, disabled); +} + +bool ImGui::Button(const char* label, const ImVec2& sizeArg, bool disabled) { + if (disabled) PushDisabled(); + bool res = ImGui::Button(label, sizeArg); + if (disabled) PopDisabled(); + + return res; +} + +#define EDIT_RGBA_COLOR(EditorFunction, kUsesAlpha) \ + float components[4]; \ + components[0] = color->GetNormalizedRed(); \ + components[1] = color->GetNormalizedGreen(); \ + components[2] = color->GetNormalizedBlue(); \ + if constexpr (kUsesAlpha) components[3] = color->GetNormalizedAlpha(); \ + if (EditorFunction(label, components, flags)) { \ + color->r = components[0] * 255; \ + color->g = components[1] * 255; \ + color->b = components[2] * 255; \ + if constexpr (kUsesAlpha) color->a = components[3] * 255; \ + return true; \ + } else { \ + return false; \ + } + +bool ImGui::ColorEdit3(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorEdit3, false); +} + +bool ImGui::ColorEdit4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorEdit4, true); +} + +bool ImGui::ColorPicker3(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorPicker3, false); +} + +bool ImGui::ColorPicker4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorPicker4, true); +} + +#undef EDIT_RGBA_COLOR + +struct InputTextCallbackUserData { + std::string* str; + ImGuiInputTextCallback chainCallback; + void* chainCallbackUserData; +}; + +static int InputTextCallback(ImGuiInputTextCallbackData* data) { + auto user_data = (InputTextCallbackUserData*)data->UserData; + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + // Resize string callback + // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want. + auto str = user_data->str; + IM_ASSERT(data->Buf == str->c_str()); + str->resize(data->BufTextLen); + data->Buf = (char*)str->c_str(); + } else if (user_data->chainCallback) { + // Forward to user callback, if any + data->UserData = user_data->chainCallbackUserData; + return user_data->chainCallback(data); + } + return 0; +} + +bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallbackUserData cbUserData; + cbUserData.str = str; + cbUserData.chainCallback = callback; + cbUserData.chainCallbackUserData = user_data; + return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cbUserData); +} + +bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* userData) { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallbackUserData cbUserData; + cbUserData.str = str; + cbUserData.chainCallback = callback; + cbUserData.chainCallbackUserData = userData; + return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cbUserData); +} + +bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* userData) { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallbackUserData cbUserData; + cbUserData.str = str; + cbUserData.chainCallback = callback; + cbUserData.chainCallbackUserData = userData; + return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cbUserData); +} + +bool ImGui::Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize) { + // Adapted from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/blueprints-example.cpp + // ::Splitter + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (splitVertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + CalcItemSize(splitVertically ? ImVec2(thickness, splitterLongAxisSize) : ImVec2(splitterLongAxisSize, thickness), 0.0f, 0.0f); + + // Adapted from ImGui::SplitterBehavior, changes: + // - Simplified unneeded logic (hover_extend and hover_visibility_delay) + // - Changed clamped delta to clamping result size1 and deriving size2 from size1, allowing automatically adapting to the latest window content region width + + auto itemFlagsBackup = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool itemAdd = ItemAdd(bb, id); + g.CurrentItemFlags = itemFlagsBackup; + if (!itemAdd) { + return false; + } + + bool hovered, held; + auto bbInteract = bb; + ButtonBehavior(bbInteract, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (hovered) { + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; + } // for IsItemHovered(), because bbInteract is larger than bb + if (g.ActiveId != id) { + SetItemAllowOverlap(); + } + + if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= 0.0f)) { + SetMouseCursor((splitVertically ? ImGuiAxis_X : ImGuiAxis_Y) == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + } + + float contentSize = splitVertically ? window->ContentRegionRect.GetWidth() : window->ContentRegionRect.GetHeight(); + if (held) { + ImVec2 mouseDelta2D = g.IO.MousePos - g.ActiveIdClickOffset - bbInteract.Min; + float mouseDelta = ((splitVertically ? ImGuiAxis_X : ImGuiAxis_Y) == ImGuiAxis_Y) ? mouseDelta2D.y : mouseDelta2D.x; + + // Apply resize + if (mouseDelta != 0.0f) { + *size1 = ImClamp(*size1 + mouseDelta, minSize1, contentSize - minSize2 - thickness); + *size2 = contentSize - *size1 - thickness; + MarkItemEdited(id); + } + } + + ImU32 col; + if (held) { + col = GetColorU32(ImGuiCol_SeparatorActive); + } else if (hovered && g.HoveredIdTimer >= 0.0f) { + col = GetColorU32(ImGuiCol_SeparatorHovered); + } else { + col = GetColorU32(ImGuiCol_Separator); + } + window->DrawList->AddRectFilled(bb.Min, bb.Max, col, 0.0f); + + return held; +} diff --git a/source/EditorUtils.hpp b/source/EditorUtils.hpp new file mode 100644 index 0000000..27510fe --- /dev/null +++ b/source/EditorUtils.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Color.hpp" + +#include <imgui.h> +#include <string> + +namespace ImGui { + +const char* GetKeyNameGlfw(int key); + +void SetNextWindowSizeRelScreen(float xPercent, float yPercent, ImGuiCond cond = ImGuiCond_None); +void SetNextWindowCentered(ImGuiCond cond = ImGuiCond_None); + +void PushDisabled(); +void PopDisabled(); + +bool Button(const char* label, bool disabled); +bool Button(const char* label, const ImVec2& sizeArg, bool disabled); + +bool ColorEdit3(const char* label, RgbaColor* color, ImGuiColorEditFlags flags = 0); +bool ColorEdit4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags = 0); +bool ColorPicker3(const char* label, RgbaColor* color, ImGuiColorEditFlags flags = 0); +bool ColorPicker4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags = 0); + +bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); +bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* userData = nullptr); +bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* userData = nullptr); + +bool Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize = -1.0f); + +} // namespace ImGui diff --git a/source/GameObject.cpp b/source/GameObject.cpp index 9f8a8bf..2aaedeb 100644 --- a/source/GameObject.cpp +++ b/source/GameObject.cpp @@ -128,6 +128,22 @@ PodVector<GameObject*> GameObject::RemoveAllChildren() { return result; } +const glm::vec3& GameObject::GetPos() { + return mPos; +} + +void GameObject::SetPos(const glm::vec3& pos) { + mPos = pos; +} + +const glm::quat& GameObject::GetRotation() const { + return mRot; +} + +void GameObject::SetRotation(const glm::quat& rotation) { + mRot = rotation; +} + Tags::GameObjectMemoryManagement GameObject::GetMemoryManagement() const { return Tags::GOMM_None; }; diff --git a/source/GameObject.hpp b/source/GameObject.hpp index 750ae4a..cb83da3 100644 --- a/source/GameObject.hpp +++ b/source/GameObject.hpp @@ -1,6 +1,6 @@ #pragma once -#include "EditorCoreAPI.hpp" +#include "EditorAttachment.hpp" #include "GameObjectTypeTag.hpp" #include "Material.hpp" #include "Mesh.hpp" @@ -13,7 +13,7 @@ class GameWorld; class GameObject { public: // NOTE: public for editors - std::unique_ptr<EditorGameObjectAttachment, EditorGameObjectAttachmentDeleter> mEditorAttachment = nullptr; + std::unique_ptr<EditorAttachment> mEditorAttachment = nullptr; GameWorld* mWorld = nullptr; GameObject* mParent = nullptr; PodVector<GameObject*> mChildren; @@ -40,12 +40,17 @@ public: GameObject* RemoveChild(GameObject* child); PodVector<GameObject*> RemoveAllChildren(); - EditorGameObjectAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } - void SetEditorAttachment(EditorGameObjectAttachment* attachment) { mEditorAttachment.reset(attachment); } + EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } + void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } + + const glm::vec3& GetPos(); + void SetPos(const glm::vec3& pos); + + const glm::quat& GetRotation() const; + void SetRotation(const glm::quat& rotation); // Tag - virtual Tags::GameObjectMemoryManagement - GetMemoryManagement() const; + virtual Tags::GameObjectMemoryManagement GetMemoryManagement() const; virtual Tags::GameObjectType GetTypeTag() const; // Visuals diff --git a/source/Shader.cpp b/source/Shader.cpp index 72b679e..a2d1c97 100644 --- a/source/Shader.cpp +++ b/source/Shader.cpp @@ -1,34 +1,27 @@ #include "Shader.hpp" +#include "AppConfig.hpp" #include "ScopeGuard.hpp" +#include "Utils.hpp" +#include <imgui.h> #include <cstddef> -#include <fstream> -#include <iostream> -#include <sstream> +#include <cstdlib> +#include <filesystem> #include <utility> +namespace fs = std::filesystem; using namespace std::literals::string_literals; using namespace std::literals::string_view_literals; +void ShaderDetails::ShowInspector() { +} + Shader::~Shader() { glDeleteProgram(mHandle); } namespace ProjectBrussel_UNITY_ID { -Shader::ErrorCode LoadFile(std::string& out, const char* filePath) { - std::ifstream ifs(filePath); - if (!ifs) { - return Shader::FileIOFailed; - } - - std::stringstream buf; - buf << ifs.rdbuf(); - out = buf.str(); - - return Shader::Success; -} - // Grabs section [begin, end) Shader::ErrorCode CreateShader(GLuint& out, const char* src, int beginIdx, int endIdx, GLenum type) { out = glCreateShader(type); @@ -135,69 +128,6 @@ Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) { return Success; } -Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) { - using namespace ProjectBrussel_UNITY_ID; - - if (IsValid()) { - return ShaderAlreadyCreated; - } - - GLuint program = glCreateProgram(); - ScopeGuard sg = [&]() { glDeleteProgram(program); }; - - GLuint vertex = 0; - DEFER { glDeleteShader(vertex); }; - if (files.vertex) { - std::string src; - CATCH_ERROR(LoadFile(src, files.vertex)); - CATCH_ERROR(CreateShader(vertex, src, GL_VERTEX_SHADER)); - glAttachShader(program, vertex); - } - - GLuint geometry = 0; - DEFER { glDeleteShader(geometry); }; - if (files.geometry) { - std::string src; - CATCH_ERROR(LoadFile(src, files.geometry)); - CATCH_ERROR(CreateShader(geometry, src, GL_GEOMETRY_SHADER)); - glAttachShader(program, geometry); - } - - GLuint tessControl = 0; - DEFER { glDeleteShader(tessControl); }; - if (files.tessControl) { - std::string src; - CATCH_ERROR(LoadFile(src, files.tessControl)); - CATCH_ERROR(CreateShader(tessControl, src, GL_TESS_CONTROL_SHADER)); - glAttachShader(program, tessControl); - } - - GLuint tessEval = 0; - DEFER { glDeleteShader(tessEval); }; - if (files.tessEval) { - std::string src; - CATCH_ERROR(LoadFile(src, files.tessEval)); - CATCH_ERROR(CreateShader(tessEval, src, GL_TESS_EVALUATION_SHADER)); - glAttachShader(program, tessEval); - } - - GLuint fragment = 0; - DEFER { glDeleteShader(fragment); }; - if (files.fragment) { - std::string src; - CATCH_ERROR(LoadFile(src, files.fragment)); - CATCH_ERROR(CreateShader(fragment, src, GL_FRAGMENT_SHADER)); - glAttachShader(program, fragment); - } - - CATCH_ERROR(LinkShaderProgram(program)); - - sg.Dismiss(); - mHandle = program; - - return Success; -} - Shader::ErrorCode Shader::InitFromSource(std::string_view source) { using namespace ProjectBrussel_UNITY_ID; @@ -315,14 +245,17 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) { return Success; } -Shader::ErrorCode Shader::InitFromFile(const char* filePath) { - std::string src; - CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, filePath)); +#undef CATCH_ERROR - return InitFromSource(src); +void Shader::GatherDetailsIfAbsent() { + if (mDetails) return; + + mDetails = std::make_unique<ShaderDetails>(); } -#undef CATCH_ERROR +const ShaderDetails* Shader::GetDetails() const { + return mDetails.get(); +} GLuint Shader::GetProgram() const { return mHandle; @@ -331,3 +264,44 @@ GLuint Shader::GetProgram() const { bool Shader::IsValid() const { return mHandle != 0; } + +void ShaderManager::DiscoverShaders() { + mShaders.clear(); + + auto path = AppConfig::assetDirPath / "Shaders"; + for (auto& item : fs::directory_iterator(path)) { + if (item.is_regular_file() && item.path().extension() == ".glsl") { + auto file = Utils::OpenCstdioFile(item.path(), Utils::Read); + if (!file) continue; + + fseek(file, 0, SEEK_END); + auto fileSize = ftell(file); + rewind(file); + + // Also add \0 ourselves + auto buffer = std::make_unique<char[]>(fileSize + 1); + fread(buffer.get(), fileSize, 1, file); + fclose(file); + buffer[fileSize] = '\0'; + std::string_view source(buffer.get(), fileSize); + + RcPtr shader(new Shader()); + auto err = shader->InitFromSource(source); + if (err != Shader::Success) { + continue; + } + + auto shaderName = item.path().stem().string(); + mShaders.insert(shaderName, std::move(shader)); + } + } +} + +Shader* ShaderManager::FindShader(std::string_view name) { + auto iter = mShaders.find(name); + if (iter != mShaders.end()) { + return iter.value().Get(); + } else { + return nullptr; + } +} diff --git a/source/Shader.hpp b/source/Shader.hpp index b481bd6..e7a069b 100644 --- a/source/Shader.hpp +++ b/source/Shader.hpp @@ -1,17 +1,26 @@ #pragma once +#include "EditorAttachment.hpp" +#include "EditorInspector.hpp" #include "RcPtr.hpp" #include <glad/glad.h> +#include <tsl/array_map.h> #include <memory> #include <string_view> -class ShaderDetails : public RefCounted { +class ShaderDetails : public EditorAttachment, public IEditorInspectorTarget { public: + std::string fileName; + +public: + virtual void ShowInspector() override; }; class Shader : public RefCounted { private: + std::unique_ptr<ShaderDetails> mDetails; + std::unique_ptr<EditorAttachment> mEditorAttachment; GLuint mHandle = 0; public: @@ -45,19 +54,6 @@ public: /// into a Shader object. ErrorCode InitFromSources(const ShaderSources& sources); - struct ShaderFilePaths { - const char* vertex = nullptr; - const char* geometry = nullptr; - const char* tessControl = nullptr; - const char* tessEval = nullptr; - const char* fragment = nullptr; - }; - - /// Create shader by loading each specified file and combining them together to form a shader object. - /// Use the mental model that this function simply loads the files from disk (synchronously) and then - /// passing them to `FromSource(const ShaderSources& source)`. - ErrorCode InitFromFiles(const ShaderFilePaths& files); - /// The given text will be split into different shader sections according to #type directives, /// and combined to form a Shader object. /// For OpenGL, this process involves compililng each section separately and then linking them @@ -71,11 +67,26 @@ public: /// - `#type fragment`: Fragment shader ErrorCode InitFromSource(std::string_view source); - /// Create shader by loading the file at the `file`, and then giving the contents to - /// `FromSource(std::string_view source)`. - ErrorCode InitFromFile(const char* file); + EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } + void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } + void GatherDetailsIfAbsent(); + const ShaderDetails* GetDetails() const; GLuint GetProgram() const; bool IsValid() const; }; + +class ShaderManager { +public: + static inline ShaderManager* instance = nullptr; + +private: + tsl::array_map<char, RcPtr<Shader>> mShaders; + +public: + void DiscoverShaders(); + + const tsl::array_map<char, RcPtr<Shader>>& GetShaders() const { return mShaders; } + Shader* FindShader(std::string_view name); +}; diff --git a/source/Utils.hpp b/source/Utils.hpp index 3d04e62..03fdfed 100644 --- a/source/Utils.hpp +++ b/source/Utils.hpp @@ -14,4 +14,8 @@ enum IoMode { FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary = false); FILE* OpenCstdioFile(const char* path, IoMode mode, bool binary = false); +constexpr float Abs(float v) noexcept { + return v < 0.0f ? -v : v; +} + } // namespace Utils diff --git a/source/main.cpp b/source/main.cpp index a2ba5b2..f7cde70 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -2,16 +2,18 @@ #include "AppConfig.hpp" #include "EditorNotification.hpp" +#include "Shader.hpp" #define GLFW_INCLUDE_NONE #include <GLFW/glfw3.h> +#include <filesystem> #include <backends/imgui_impl_glfw.h> #include <backends/imgui_impl_opengl3.h> #include <glad/glad.h> #include <imgui.h> +#include <cstdlib> #include <cxxopts.hpp> -#include <iostream> #include <string> namespace fs = std::filesystem; @@ -24,7 +26,7 @@ static void GlfwMouseCallback(GLFWwindow* window, int button, int action, int mo if (ImGui::GetIO().WantCaptureMouse) { return; } - + App* app = static_cast<App*>(glfwGetWindowUserPointer(window)); app->HandleMouse(button, action); } @@ -33,7 +35,7 @@ static void GlfwMouseMotionCallback(GLFWwindow* window, double xOff, double yOff if (ImGui::GetIO().WantCaptureMouse) { return; } - + App* app = static_cast<App*>(glfwGetWindowUserPointer(window)); app->HandleMouseMotion(xOff, yOff); } @@ -42,7 +44,7 @@ static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int actio if (ImGui::GetIO().WantCaptureKeyboard) { return; } - + GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard(); if (keyboard) { App* app = static_cast<App*>(glfwGetWindowUserPointer(window)); @@ -51,33 +53,51 @@ static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int actio } int main(int argc, char* argv[]) { - constexpr const char* kGameDataDir = "game-data-directory"; - + constexpr auto kGameDataDir = "game-data-directory"; + constexpr auto kGameAssetDir = "game-asset-directory"; + cxxopts::Options options(std::string(AppConfig::kAppName), ""); // clang-format off options.add_options() + (kGameAssetDir, "Directory in which assets are looked up from. Can be relative paths to the executable.", cxxopts::value<std::string>()->default_value(".")) (kGameDataDir, "Directory in which game data (such as saves and options) are saved to. Leave empty to use the default directory on each platform.", cxxopts::value<std::string>()) - ; + ; // clang-format on auto args = options.parse(argc, argv); - + + { + auto assetDir = args[kGameAssetDir].as<std::string>(); + + fs::path assetDirPath(assetDir); + if (!fs::exists(assetDirPath)) { + fprintf(stderr, "Invalid asset directory.\n"); + return -4; + } + + AppConfig::assetDir = std::move(assetDir); + AppConfig::assetDirPath = std::move(assetDirPath); + } + if (args.count(kGameDataDir) > 0) { auto dataDir = args[kGameDataDir].as<std::string>(); + fs::path dataDirPath(dataDir); - if (!fs::exists(dataDir)) { - fs::create_directories(dataDir); - } + fs::create_directories(dataDir); + AppConfig::dataDir = std::move(dataDir); + AppConfig::dataDirPath = std::move(dataDirPath); } else { + // TODO platform default path AppConfig::dataDir = "."; + AppConfig::dataDirPath = fs::path("."); } - + if (!glfwInit()) { return -1; } - + glfwSetErrorCallback(&GlfwErrorCallback); - + // Decide GL+GLSL versions #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 @@ -98,45 +118,48 @@ int main(int argc, char* argv[]) { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); #endif - + App app; - + GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr); if (window == nullptr) { return -2; } - + glfwSetWindowUserPointer(window, &app); - + // Window callbacks are retained by ImGui GLFW backend glfwSetKeyCallback(window, &GlfwKeyCallback); glfwSetMouseButtonCallback(window, &GlfwMouseCallback); glfwSetCursorPosCallback(window, &GlfwMouseMotionCallback); - + glfwMakeContextCurrent(window); glfwSwapInterval(1); - + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { return -3; } - + IMGUI_CHECKVERSION(); ImGui::CreateContext(); - + ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); - + + ShaderManager::instance = new ShaderManager(); + ShaderManager::instance->DiscoverShaders(); + app.Init(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - + app.Show(); ImGui::ShowNotifications(); - + ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); @@ -145,18 +168,18 @@ int main(int argc, char* argv[]) { glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - + glfwSwapBuffers(window); } app.Shutdown(); - + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); - + ImGui::DestroyContext(); - + glfwDestroyWindow(window); glfwTerminate(); - + return 0; } |