diff options
Diffstat (limited to 'source/30-game')
56 files changed, 0 insertions, 9036 deletions
diff --git a/source/30-game/App.cpp b/source/30-game/App.cpp deleted file mode 100644 index 8328589..0000000 --- a/source/30-game/App.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "App.hpp" - -#include "ScopeGuard.hpp" -#include "Utils.hpp" - -#include <rapidjson/document.h> -#include <rapidjson/filereadstream.h> -#include <string> -#include <utility> - -using namespace std::literals; - -App::App() - : mActiveCamera{ &mMainCamera } { - auto& worldRoot = mWorld.GetRoot(); - - constexpr int kPlayerCount = 2; - for (int i = 0; i < kPlayerCount; ++i) { - auto player = new Player(&mWorld, i); - worldRoot.AddChild(player); - mPlayers.push_back(player); - }; - -#if defined(BRUSSEL_DEV_ENV) - SetGameRunning(false); - SetEditorVisible(true); -#else - SetGameRunning(true); -#endif - - mMainCamera.name = "Main Camera"s; - mMainCamera.SetEyePos(glm::vec3(0, 0, 1)); - mMainCamera.SetTargetDirection(glm::vec3(0, 0, -1)); - mMainCamera.SetHasPerspective(false); - - do { - auto file = Utils::OpenCstdioFile("assets/GameRendererBindings.json", Utils::Read); - if (!file) break; - DEFER { fclose(file); }; - - char readerBuffer[65536]; - rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); - - rapidjson::Document root; - root.ParseStream(stream); - - mWorldRenderer.LoadBindings(root); - } while (false); -} - -App::~App() { -} - -Camera* App::GetActiveCamera() const { - return mActiveCamera; -} - -void App::BindActiveCamera(Camera* camera) { - mActiveCamera = camera; -} - -void App::UnbindActiveCamera() { - mActiveCamera = &mMainCamera; -} - -bool App::IsGameRunning() const { - return mGameRunning; -} - -void App::SetGameRunning(bool running) { - if (mGameRunning != running) { - mGameRunning = running; - if (running) { - mWorld.Awaken(); - } else { - mWorld.Resleep(); - } - } -} - -bool App::IsEditorVisible() const { - return mEditorVisible; -} - -void App::SetEditorVisible(bool visible) { - if (mEditorVisible != visible) { - if (visible) { -#if BRUSSEL_ENABLE_EDITOR - mEditorVisible = true; - if (mEditor == nullptr) { - mEditor = IEditor::CreateInstance(this); - } -#endif - } else { - mEditorVisible = false; - } - } -} - -void App::Show() { - if (mEditorVisible) { - mEditor->Show(); - } -} - -void App::Update() { - if (IsGameRunning()) { - mWorld.Update(); - } -} - -void App::Draw(float currentTime, float deltaTime) { - mWorldRenderer.BeginFrame(*mActiveCamera, currentTime, deltaTime); - - PodVector<GameObject*> stack; - stack.push_back(&mWorld.GetRoot()); - - while (!stack.empty()) { - auto obj = stack.back(); - stack.pop_back(); - - for (auto child : obj->GetChildren()) { - stack.push_back(child); - } - - auto renderObjects = obj->GetRenderObjects(); - mWorldRenderer.Draw(renderObjects.data(), obj, renderObjects.size()); - } - - mWorldRenderer.EndFrame(); -} - -void App::HandleMouse(int button, int action) { -} - -void App::HandleMouseMotion(double xOff, double yOff) { -} - -void App::HandleKey(GLFWkeyboard* keyboard, int key, int action) { - if (!mKeyCaptureCallbacks.empty()) { - auto& callback = mKeyCaptureCallbacks.front(); - bool remove = callback(key, action); - if (remove) { - mKeyCaptureCallbacks.pop_front(); - } - } - - switch (key) { - case GLFW_KEY_F3: { - if (action == GLFW_PRESS) { - SetEditorVisible(!IsEditorVisible()); - } - return; - } - } - - for (auto& player : mPlayers) { - for (auto playerKeyboard : player->boundKeyboards) { - if (playerKeyboard == keyboard) { - player->HandleKeyInput(key, action); - } - } - } -} - -void App::PushKeyCaptureCallback(KeyCaptureCallback callback) { - mKeyCaptureCallbacks.push_back(std::move(callback)); -} diff --git a/source/30-game/App.hpp b/source/30-game/App.hpp deleted file mode 100644 index c73c5a1..0000000 --- a/source/30-game/App.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "Camera.hpp" -#include "EditorCore.hpp" -#include "Player.hpp" -#include "PodVector.hpp" -#include "Renderer.hpp" -#include "World.hpp" - -#define GLFW_INCLUDE_NONE -#include <GLFW/glfw3.h> - -#include <deque> -#include <functional> -#include <memory> -#include <vector> - -using KeyCaptureCallback = std::function<bool(int, int)>; - -class App { -private: - std::deque<KeyCaptureCallback> mKeyCaptureCallbacks; - PodVector<Player*> mPlayers; - std::unique_ptr<IEditor> mEditor; - GameWorld mWorld; - Renderer mWorldRenderer; - Camera mMainCamera; - Camera* mActiveCamera; - // NOTE: should only be true when mEditor != nullptr - bool mEditorVisible = false; - bool mGameRunning = false; - -public: - App(); - ~App(); - - IEditor* GetEditor() { return mEditor.get(); } - GameWorld* GetWorld() { return &mWorld; } - Renderer* GetWorldRenderer() { return &mWorldRenderer; } - - Camera* GetActiveCamera() const; - void BindActiveCamera(Camera* camera); - void UnbindActiveCamera(); - - bool IsGameRunning() const; - void SetGameRunning(bool running); - - bool IsEditorVisible() const; - void SetEditorVisible(bool visible); - - // Do ImGui calls - void Show(); - // Do regular calls - void Update(); - void Draw(float currentTime, float deltaTime); - - void HandleMouse(int button, int action); - void HandleMouseMotion(double xOff, double yOff); - void HandleKey(GLFWkeyboard* keyboard, int key, int action); - - void PushKeyCaptureCallback(KeyCaptureCallback callback); -}; diff --git a/source/30-game/AppConfig.hpp b/source/30-game/AppConfig.hpp deleted file mode 100644 index 794bee5..0000000 --- a/source/30-game/AppConfig.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include <imgui.h> -#include <filesystem> -#include <string> - -namespace AppConfig { -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(); - -inline float mainWindowWidth; -inline float mainWindowHeight; -inline float mainWindowAspectRatio; - -// TODO add a bold font -inline ImFont* fontRegular = nullptr; -inline ImFont* fontBold = nullptr; - -// 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/30-game/Camera.cpp b/source/30-game/Camera.cpp deleted file mode 100644 index 39f0369..0000000 --- a/source/30-game/Camera.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Camera.hpp" - -#include "AppConfig.hpp" - -#include <glm/gtc/matrix_transform.hpp> - -Camera::Camera() - : eye(0.0f, 0.0f, 0.0f) - , target(0.0, 0.0f, -2.0f) - , pixelsPerMeter{ 50.0f } // Basic default - , fov{ M_PI / 4 } // 45deg is the convention - , perspective{ false } // -{ -} - -void Camera::SetEyePos(glm::vec3 pos) { - auto lookVector = this->target - /*Old pos*/ this->eye; - this->eye = pos; - this->target = pos + lookVector; -} - -void Camera::SetTargetPos(glm::vec3 pos) { - this->target = pos; -} - -void Camera::SetTargetDirection(glm::vec3 lookVector) { - this->target = this->eye + lookVector; -} - -void Camera::SetHasPerspective(bool perspective) { - this->perspective = perspective; -} - -glm::mat4 Camera::CalcViewMatrix() const { - return glm::lookAt(eye, target, glm::vec3(0, 1, 0)); -} - -glm::mat4 Camera::CalcProjectionMatrix() const { - if (perspective) { - return glm::perspective(fov, AppConfig::mainWindowAspectRatio, 0.1f, 1000.0f); - } else { - float widthMeters = AppConfig::mainWindowWidth / pixelsPerMeter; - float heightMeters = AppConfig::mainWindowHeight / pixelsPerMeter; - return glm::ortho(-widthMeters / 2, +widthMeters / 2, -heightMeters / 2, +heightMeters / 2); - } -} diff --git a/source/30-game/Camera.hpp b/source/30-game/Camera.hpp deleted file mode 100644 index 7bf0a6c..0000000 --- a/source/30-game/Camera.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include <glm/glm.hpp> -#include <string> - -class Camera { -public: - std::string name; - glm::vec3 eye; - glm::vec3 target; - - // --- Orthographic settings --- - float pixelsPerMeter; - // --- Orthographic settings --- - - // ---- Perspective settings --- - /// In radians - float fov; - // ---- Perspective settings --- - - bool perspective; - -public: - Camera(); - - void SetEyePos(glm::vec3 pos); - void SetTargetPos(glm::vec3 pos); - void SetTargetDirection(glm::vec3 lookVector); - - bool HasPerspective() const { return perspective; } - void SetHasPerspective(bool perspective); - - glm::mat4 CalcViewMatrix() const; - glm::mat4 CalcProjectionMatrix() const; -}; diff --git a/source/30-game/CommonVertexIndex.cpp b/source/30-game/CommonVertexIndex.cpp deleted file mode 100644 index e9a3ce6..0000000 --- a/source/30-game/CommonVertexIndex.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "CommonVertexIndex.hpp" - -template <typename TNumber> -static void AssignIndices(TNumber indices[6], TNumber startIdx) { - // Triangle #1 - indices[0] = startIdx + 1; // Top right - indices[1] = startIdx + 0; // Top left - indices[2] = startIdx + 3; // Bottom left - // Triangle #2 - indices[3] = startIdx + 1; // Top right - indices[4] = startIdx + 3; // Bottom left - indices[5] = startIdx + 2; // Bottom right -} - -template <typename TNumber> -static void AssignIndices(TNumber indices[6], TNumber topLeft, TNumber topRight, TNumber bottomRight, TNumber bottomLeft) { - // Triangle #1 - indices[0] = topRight; - indices[1] = topLeft; - indices[2] = bottomLeft; - // Triangle #2 - indices[3] = topRight; - indices[4] = bottomLeft; - indices[5] = bottomRight; -} - -template <typename TVertex> -static void AssignPositions(TVertex vertices[4], const Rect<float>& rect) { - // Top left - vertices[0].x = rect.x0(); - vertices[0].y = rect.y0(); - // Top right - vertices[1].x = rect.x1(); - vertices[1].y = rect.y0(); - // Bottom right - vertices[2].x = rect.x1(); - vertices[2].y = rect.y1(); - // Bottom left - vertices[3].x = rect.x0(); - vertices[3].y = rect.y1(); -} - -template <typename TVertex> -static void AssignPositions(TVertex vertices[4], glm::vec2 bl, glm::vec2 tr) { - // Top left - vertices[0].x = bl.x; - vertices[0].y = tr.y; - // Top right - vertices[1].x = tr.x; - vertices[1].y = tr.y; - // Bottom right - vertices[2].x = tr.x; - vertices[2].y = bl.y; - // Bottom left - vertices[3].x = bl.x; - vertices[3].y = bl.y; -} - -template <typename TVertex> -static void AssignDepths(TVertex vertices[4], float z) { - for (int i = 0; i < 4; ++i) { - auto& vert = vertices[i]; - vert.z = z; - } -} - -template <typename TVertex> -static void AssignTexCoords(TVertex vertices[4], const Subregion& texcoords) { - // Top left - vertices[0].u = texcoords.u0; - vertices[0].v = texcoords.v1; - // Top right - vertices[1].u = texcoords.u1; - vertices[1].v = texcoords.v1; - // Bottom right - vertices[2].u = texcoords.u1; - vertices[2].v = texcoords.v0; - // Bottom left - vertices[3].u = texcoords.u0; - vertices[3].v = texcoords.v0; -} - -template <typename TVertex> -static void AssignColors(TVertex vertices[4], RgbaColor color) { - for (int i = 0; i < 4; ++i) { - auto& vert = vertices[i]; - vert.r = color.r; - vert.g = color.g; - vert.b = color.b; - vert.a = color.a; - } -} - -void Index_U16::Assign(uint16_t indices[6], uint16_t startIdx) { - ::AssignIndices(indices, startIdx); -} - -void Index_U16::Assign(uint16_t indices[6], uint16_t startIdx, uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft) { - ::AssignIndices<uint16_t>(indices, startIdx + topLeft, startIdx + topRight, startIdx + bottomRight, startIdx + bottomLeft); -} - -void Index_U16::Assign(uint16_t indices[6], uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft) { - ::AssignIndices<uint16_t>(indices, topLeft, topRight, bottomRight, bottomLeft); -} - -void Index_U32::Assign(uint32_t indices[6], uint32_t startIdx) { - ::AssignIndices(indices, startIdx); -} - -void Index_U32::Assign(uint32_t indices[6], uint32_t startIdx, uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft) { - ::AssignIndices<uint32_t>(indices, startIdx + topLeft, startIdx + topRight, startIdx + bottomRight, startIdx + bottomLeft); -} - -void Index_U32::Assign(uint32_t indices[6], uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft) { - ::AssignIndices<uint32_t>(indices, topLeft, topRight, bottomRight, bottomLeft); -} - -void Vertex_PC::Assign(Vertex_PC vertices[4], const Rect<float>& rect) { - ::AssignPositions(vertices, rect); -} - -void Vertex_PC::Assign(Vertex_PC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight) { - ::AssignPositions(vertices, bottomLeft, topRight); -} - -void Vertex_PC::Assign(Vertex_PC vertices[4], float z) { - ::AssignDepths(vertices, z); -} - -void Vertex_PC::Assign(Vertex_PC vertices[4], RgbaColor color) { - ::AssignColors(vertices, color); -} - -void Vertex_PTC::Assign(Vertex_PTC vertices[4], const Rect<float>& rect) { - ::AssignPositions(vertices, rect); -} - -void Vertex_PTC::Assign(Vertex_PTC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight) { - ::AssignPositions(vertices, bottomLeft, topRight); -} - -void Vertex_PTC::Assign(Vertex_PTC vertices[4], float z) { - ::AssignDepths(vertices, z); -} - -void Vertex_PTC::Assign(Vertex_PTC vertices[4], const Subregion& texcoords) { - ::AssignTexCoords(vertices, texcoords); -} - -void Vertex_PTC::Assign(Vertex_PTC vertices[4], RgbaColor color) { - ::AssignColors(vertices, color); -} diff --git a/source/30-game/CommonVertexIndex.hpp b/source/30-game/CommonVertexIndex.hpp deleted file mode 100644 index 7e6aa66..0000000 --- a/source/30-game/CommonVertexIndex.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "Color.hpp" -#include "RcPtr.hpp" -#include "Rect.hpp" -#include "Texture.hpp" -#include "VertexIndex.hpp" - -#include <cstdint> - -// Initialized in main() -inline RcPtr<VertexFormat> gVformatStandard{}; -inline RcPtr<VertexFormat> gVformatStandardSplit{}; -inline RcPtr<VertexFormat> gVformatLines{}; - -// Suffixes: -// - _P_osition -// - _T_exture coordiantes -// - _C_olor -// - _N_ormal -// When an number is attached to some suffix, it means there are N number of this element - -struct Index_U16 { - uint16_t value; - - static void Assign(uint16_t indices[6], uint16_t startIdx); - static void Assign(uint16_t indices[6], uint16_t startIdx, uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft); - static void Assign(uint16_t indices[6], uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft); -}; - -struct Index_U32 { - uint32_t value; - - static void Assign(uint32_t indices[6], uint32_t startIdx); - static void Assign(uint32_t indices[6], uint32_t startIdx, uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft); - static void Assign(uint32_t indices[6], uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft); -}; - -struct Vertex_PC { - float x, y, z; - uint8_t r, g, b, a; - - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PC vertices[4], const Rect<float>& rect); - /// Assign position in regular cartesian coordinate space (x increases from left to right, y increases from top to bottom). - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight); - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PC vertices[4], float z); - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PC vertices[4], RgbaColor color); -}; - -struct Vertex_PTC { - float x, y, z; - float u, v; - uint8_t r, g, b, a; - - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PTC vertices[4], const Rect<float>& rect); - /// Assign position in regular cartesian coordinate space (x increases from left to right, y increases from top to bottom). - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PTC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight); - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PTC vertices[4], float z); - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PTC vertices[4], const Subregion& uvs); - /// Assumes the 4 vertices come in TL, TR, BR, BL order. - static void Assign(Vertex_PTC vertices[4], RgbaColor color); -}; - -struct Vertex_PTNC { - float x, y, z; - float nx, ny, nz; - float u, v; - uint8_t r, g, b, a; -}; diff --git a/source/30-game/EditorAccessories.cpp b/source/30-game/EditorAccessories.cpp deleted file mode 100644 index 821d41e..0000000 --- a/source/30-game/EditorAccessories.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "EditorAccessories.hpp" - -#include "Input.hpp" - -#define GLFW_INCLUDE_NONE -#include <GLFW/glfw3.h> - -#include <imgui.h> - -void EditorKeyboardViewer::Show(bool* open) { - ImGui::Begin("Keyboards", open); - - int count; - GLFWkeyboard** keyboards = glfwGetKeyboards(&count); - - for (int i = 0; i < count; ++i) { - GLFWkeyboard* keyboard = keyboards[i]; - auto attachment = static_cast<GlfwKeyboardAttachment*>(glfwGetKeyboardUserPointer(keyboard)); - - ImGui::BulletText("%s", glfwGetKeyboardName(keyboard)); - ImGui::Indent(); - ImGui::Unindent(); - } - - ImGui::End(); -} diff --git a/source/30-game/EditorAccessories.hpp b/source/30-game/EditorAccessories.hpp deleted file mode 100644 index 56a8238..0000000 --- a/source/30-game/EditorAccessories.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "Player.hpp" - -class EditorKeyboardViewer { -public: - void Show(bool* open = nullptr); -}; diff --git a/source/30-game/EditorAttachment.hpp b/source/30-game/EditorAttachment.hpp deleted file mode 100644 index 61b824b..0000000 --- a/source/30-game/EditorAttachment.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include <string> - -class EditorAttachment { -public: - std::string name; - -public: - EditorAttachment(); - virtual ~EditorAttachment() = default; -}; diff --git a/source/30-game/EditorAttachmentImpl.cpp b/source/30-game/EditorAttachmentImpl.cpp deleted file mode 100644 index b09c133..0000000 --- a/source/30-game/EditorAttachmentImpl.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "EditorAttachmentImpl.hpp" -#include "EditorAttachment.hpp" - -#include <Metadata.hpp> - -EditorAttachment::EditorAttachment() { -} - -std::unique_ptr<EditorAttachment> EaGameObject::Create(GameObject* object) { - EaGameObject* result; - - auto kind = object->GetKind(); - switch (kind) { - case GameObject::KD_Player: result = new EaPlayer(); break; - case GameObject::KD_LevelWrapper: result = new EaLevelWrapper(); break; - - default: result = new EaGameObject(); break; - } - - result->name = Metadata::EnumToString(kind); - result->eulerAnglesRotation = glm::eulerAngles(object->GetRotation()); - return std::unique_ptr<EditorAttachment>(result); -} diff --git a/source/30-game/EditorAttachmentImpl.hpp b/source/30-game/EditorAttachmentImpl.hpp deleted file mode 100644 index 53bcd37..0000000 --- a/source/30-game/EditorAttachmentImpl.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "EditorAttachment.hpp" -#include "GameObject.hpp" -#include "Material.hpp" -#include "Player.hpp" -#include "Sprite.hpp" - -#include <memory> - -class EaGameObject : public EditorAttachment { -public: - // NOTE: in degrees - glm::vec3 eulerAnglesRotation; - -public: - static std::unique_ptr<EditorAttachment> Create(GameObject* object); -}; - -class EaPlayer : public EaGameObject { -public: - RcPtr<IresSpritesheet> confSprite; - RcPtr<IresMaterial> confMaterial; -}; - -class EaLevelWrapper : public EaGameObject { -public: -}; - -class EaIresObject : public EditorAttachment { -public: - std::string nameEditingScratch; - bool isEditingName = false; -}; diff --git a/source/30-game/EditorCommandPalette.cpp b/source/30-game/EditorCommandPalette.cpp deleted file mode 100644 index 0e7b894..0000000 --- a/source/30-game/EditorCommandPalette.cpp +++ /dev/null @@ -1,406 +0,0 @@ -#include "EditorCommandPalette.hpp" - -#include "AppConfig.hpp" -#include "EditorUtils.hpp" -#include "FuzzyMatch.hpp" -#include "Utils.hpp" - -#include <GLFW/glfw3.h> -#include <imgui.h> -#include <misc/cpp/imgui_stdlib.h> -#include <algorithm> -#include <limits> -#include <utility> - -#define IMGUI_DEFINE_MATH_OPERATORS -#include <imgui_internal.h> - -using namespace std::literals; - -bool EditorCommandExecuteContext::IsInitiated() const { - return mCommand != nullptr; -} - -const EditorCommand* EditorCommandExecuteContext::GetCurrentCommand() const { - return mCommand; -} - -void EditorCommandExecuteContext::Initiate(const EditorCommand& command) { - if (mCommand == nullptr) { - mCommand = &command; - } -} - -void EditorCommandExecuteContext::Prompt(std::vector<std::string> options) { - assert(mCommand != nullptr); - mCurrentOptions = std::move(options); - ++mDepth; -} - -void EditorCommandExecuteContext::Finish() { - assert(mCommand != nullptr); - mCommand = nullptr; - mCurrentOptions.clear(); - mDepth = 0; -} - -int EditorCommandExecuteContext::GetExecutionDepth() const { - return mDepth; -} - -struct EditorCommandPalette::SearchResult { - int itemIndex; - int score; - int matchCount; - uint8_t matches[32]; -}; - -struct EditorCommandPalette::Item { - bool hovered = false; - bool held = false; -}; - -EditorCommandPalette::EditorCommandPalette() = default; -EditorCommandPalette::~EditorCommandPalette() = default; - -namespace P6503_UNITY_ID { -std::string MakeCommandName(std::string_view category, std::string_view name) { - std::string result; - constexpr auto infix = ": "sv; - result.reserve(category.size() + infix.size() + name.size()); - result.append(category); - result.append(infix); - result.append(name); - return result; -} -} // namespace P6503_UNITY_ID - -void EditorCommandPalette::AddCommand(std::string_view category, std::string_view name, EditorCommand command) { - command.name = P6503_UNITY_ID::MakeCommandName(category, name); - - auto location = std::lower_bound( - mCommands.begin(), - mCommands.end(), - command, - [](const EditorCommand& a, const EditorCommand& b) -> bool { - return a.name < b.name; - }); - auto iter = mCommands.insert(location, std::move(command)); - - InvalidateSearchResults(); -} - -void EditorCommandPalette::RemoveCommand(std::string_view category, std::string_view name) { - auto commandName = P6503_UNITY_ID::MakeCommandName(category, name); - RemoveCommand(commandName); -} - -void EditorCommandPalette::RemoveCommand(const std::string& commandName) { - struct Comparator { - bool operator()(const EditorCommand& command, const std::string& str) const { - return command.name < str; - } - - bool operator()(const std::string& str, const EditorCommand& command) const { - return str < command.name; - } - }; - - auto range = std::equal_range(mCommands.begin(), mCommands.end(), commandName, Comparator{}); - mCommands.erase(range.first, range.second); - - InvalidateSearchResults(); -} - -void EditorCommandPalette::Show(bool* open) { - // Center window horizontally, align top vertically - ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x / 2, 0), ImGuiCond_Always, ImVec2(0.5f, 0.0f)); - ImGui::SetNextWindowSizeRelScreen(0.3f, 0.0f); - - ImGui::Begin("Command Palette", open, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar); - float width = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; - - if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows) || mShouldCloseNextFrame) { - // Close popup when user unfocused the command palette window (clicking elsewhere) - // or some action requested closing this window - mShouldCloseNextFrame = false; - if (open) { - *open = false; - } - } - - if (ImGui::IsWindowAppearing() || mFocusSearchBox) { - mFocusSearchBox = false; - - // Focus the search box when user first brings command palette window up - // Note: this only affects the next frame - ImGui::SetKeyboardFocusHere(0); - } - ImGui::SetNextItemWidth(width); - if (ImGui::InputText("##", &mSearchText)) { - // Search string updated, update search results - - mFocusedItemId = 0; - mSearchResults.clear(); - - size_t itemCount; - if (mExeCtx.GetExecutionDepth() == 0) { - itemCount = mCommands.size(); - } else { - itemCount = mExeCtx.mCurrentOptions.size(); - } - - for (size_t i = 0; i < itemCount; ++i) { - const char* text; - if (mExeCtx.GetExecutionDepth() == 0) { - text = mCommands[i].name.c_str(); - } else { - text = mExeCtx.mCurrentOptions[i].c_str(); - } - - SearchResult result{ - .itemIndex = (int)i, - }; - if (FuzzyMatch::Search(mSearchText.c_str(), text, result.score, result.matches, std::size(result.matches), result.matchCount)) { - mSearchResults.push_back(result); - } - } - - std::sort( - mSearchResults.begin(), - mSearchResults.end(), - [](const SearchResult& a, const SearchResult& b) -> bool { - // We want the biggest element first - return a.score > b.score; - }); - } - - ImGui::BeginChild("SearchResults", ImVec2(width, 300), false, ImGuiWindowFlags_AlwaysAutoResize); - auto window = ImGui::GetCurrentWindow(); - - auto& io = ImGui::GetIO(); - auto dlSharedData = ImGui::GetDrawListSharedData(); - - auto textColor = ImGui::GetColorU32(ImGuiCol_Text); - auto itemHoveredColor = ImGui::GetColorU32(ImGuiCol_HeaderHovered); - auto itemActiveColor = ImGui::GetColorU32(ImGuiCol_HeaderActive); - auto itemSelectedColor = ImGui::GetColorU32(ImGuiCol_Header); - - int itemCount = GetItemCount(); - if (mItems.size() < itemCount) { - mItems.resize(itemCount); - } - - // Flag used to delay item selection until after the loop ends - bool selectFocusedItem = false; - for (size_t i = 0; i < itemCount; ++i) { - auto id = window->GetID(static_cast<int>(i)); - - ImVec2 size{ - ImGui::GetContentRegionAvail().x, - dlSharedData->Font->FontSize, - }; - ImRect rect{ - window->DC.CursorPos, - window->DC.CursorPos + ImGui::CalcItemSize(size, 0.0f, 0.0f), - }; - - bool& hovered = mItems[i].hovered; - bool& held = mItems[i].held; - if (held && hovered) { - window->DrawList->AddRectFilled(rect.Min, rect.Max, itemActiveColor); - } else if (hovered) { - window->DrawList->AddRectFilled(rect.Min, rect.Max, itemHoveredColor); - } else if (mFocusedItemId == i) { - window->DrawList->AddRectFilled(rect.Min, rect.Max, itemSelectedColor); - } - - auto item = GetItem(i); - if (item.indexType == SearchResultIndex) { - // Iterating search results: draw text with highlights at matched chars - - auto& searchResult = mSearchResults[i]; - auto textPos = window->DC.CursorPos; - int rangeBegin; - int rangeEnd; - int lastRangeEnd = 0; - - auto DrawCurrentRange = [&]() -> void { - if (rangeBegin != lastRangeEnd) { - // Draw normal text between last highlighted range end and current highlighted range start - auto begin = item.text + lastRangeEnd; - auto end = item.text + rangeBegin; - window->DrawList->AddText(textPos, textColor, begin, end); - - auto segmentSize = dlSharedData->Font->CalcTextSizeA(dlSharedData->Font->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end); - textPos.x += segmentSize.x; - } - - auto begin = item.text + rangeBegin; - auto end = item.text + rangeEnd; - window->DrawList->AddText(AppConfig::fontBold, AppConfig::fontBold->FontSize, textPos, textColor, begin, end); - - auto segmentSize = AppConfig::fontBold->CalcTextSizeA(AppConfig::fontBold->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end); - textPos.x += segmentSize.x; - }; - - assert(searchResult.matchCount >= 1); - rangeBegin = searchResult.matches[0]; - rangeEnd = rangeBegin; - - int lastCharIdx = -1; - for (int j = 0; j < searchResult.matchCount; ++j) { - int charIdx = searchResult.matches[j]; - - if (charIdx == lastCharIdx + 1) { - // These 2 indices are equal, extend our current range by 1 - ++rangeEnd; - } else { - DrawCurrentRange(); - lastRangeEnd = rangeEnd; - rangeBegin = charIdx; - rangeEnd = charIdx + 1; - } - - lastCharIdx = charIdx; - } - - // Draw the remaining range (if any) - if (rangeBegin != rangeEnd) { - DrawCurrentRange(); - } - - // Draw the text after the last range (if any) - window->DrawList->AddText(textPos, textColor, item.text + rangeEnd); // Draw until \0 - } else { - // Iterating everything else: draw text as-is, there is no highlights - - window->DrawList->AddText(window->DC.CursorPos, textColor, item.text); - } - - ImGui::ItemSize(rect); - if (!ImGui::ItemAdd(rect, id)) { - continue; - } - if (ImGui::ButtonBehavior(rect, id, &hovered, &held)) { - mFocusedItemId = i; - selectFocusedItem = true; - } - } - - if (ImGui::IsKeyPressed(GLFW_KEY_UP)) { - mFocusedItemId = std::max(mFocusedItemId - 1, 0); - } else if (ImGui::IsKeyPressed(GLFW_KEY_DOWN)) { - mFocusedItemId = std::min(mFocusedItemId + 1, itemCount - 1); - } - if (ImGui::IsKeyPressed(GLFW_KEY_ENTER) || selectFocusedItem) { - SelectFocusedItem(); - } - - ImGui::EndChild(); - - ImGui::End(); -} - -size_t EditorCommandPalette::GetItemCount() const { - int depth = mExeCtx.GetExecutionDepth(); - if (depth == 0) { - if (mSearchText.empty()) { - return mCommands.size(); - } else { - return mSearchResults.size(); - } - } else { - if (mSearchText.empty()) { - return mExeCtx.mCurrentOptions.size(); - } else { - return mSearchResults.size(); - } - } -} - -EditorCommandPalette::ItemInfo EditorCommandPalette::GetItem(size_t idx) const { - ItemInfo option; - - int depth = mExeCtx.GetExecutionDepth(); - if (depth == 0) { - if (mSearchText.empty()) { - option.text = mCommands[idx].name.c_str(); - option.command = &mCommands[idx]; - option.itemId = idx; - option.indexType = DirectIndex; - } else { - auto id = mSearchResults[idx].itemIndex; - option.text = mCommands[id].name.c_str(); - option.command = &mCommands[id]; - option.itemId = id; - option.indexType = SearchResultIndex; - } - option.itemType = CommandItem; - } else { - assert(mExeCtx.GetCurrentCommand() != nullptr); - if (mSearchText.empty()) { - option.text = mExeCtx.mCurrentOptions[idx].c_str(); - option.command = mExeCtx.GetCurrentCommand(); - option.itemId = idx; - option.indexType = DirectIndex; - } else { - auto id = mSearchResults[idx].itemIndex; - option.text = mExeCtx.mCurrentOptions[id].c_str(); - option.command = mExeCtx.GetCurrentCommand(); - option.itemId = id; - option.indexType = SearchResultIndex; - } - option.itemType = CommandOptionItem; - } - - return option; -} - -void EditorCommandPalette::SelectFocusedItem() { - if (mFocusedItemId < 0 || mFocusedItemId >= GetItemCount()) { - return; - } - - auto selectedItem = GetItem(mFocusedItemId); - auto& command = *selectedItem.command; - - int depth = mExeCtx.GetExecutionDepth(); - if (depth == 0) { - assert(!mExeCtx.IsInitiated()); - - mExeCtx.Initiate(*selectedItem.command); - if (command.callback) { - command.callback(mExeCtx); - - mFocusSearchBox = true; - // Don't invalidate search results if no further actions have been requested (returning to global list of commands) - if (mExeCtx.IsInitiated()) { - InvalidateSearchResults(); - } - } else { - mExeCtx.Finish(); - } - } else { - assert(mExeCtx.IsInitiated()); - assert(command.subsequentCallback); - command.subsequentCallback(mExeCtx, selectedItem.itemId); - - mFocusSearchBox = true; - InvalidateSearchResults(); - } - - // This action terminated execution, close command palette window - if (!mExeCtx.IsInitiated()) { - if (command.terminate) { - command.terminate(); - } - mShouldCloseNextFrame = true; - } -} - -void EditorCommandPalette::InvalidateSearchResults() { - mSearchText.clear(); - mSearchResults.clear(); - mFocusedItemId = 0; -} diff --git a/source/30-game/EditorCommandPalette.hpp b/source/30-game/EditorCommandPalette.hpp deleted file mode 100644 index 101344d..0000000 --- a/source/30-game/EditorCommandPalette.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include <imgui.h> -#include <cstddef> -#include <functional> -#include <string> -#include <string_view> - -class EditorCommandExecuteContext; -class EditorCommand { -public: - std::string name; - std::function<void(EditorCommandExecuteContext& ctx)> callback; - std::function<void(EditorCommandExecuteContext& ctx, size_t selectedOptionId)> subsequentCallback; - std::function<void()> terminate; -}; - -class EditorCommandExecuteContext { - friend class EditorCommandPalette; - -private: - const EditorCommand* mCommand = nullptr; - std::vector<std::string> mCurrentOptions; - int mDepth = 0; - -public: - bool IsInitiated() const; - const EditorCommand* GetCurrentCommand() const; - void Initiate(const EditorCommand& command); - - void Prompt(std::vector<std::string> options); - void Finish(); - - /// Return the number of prompts that the user is currently completing. For example, when the user opens command - /// palette fresh and selects a command, 0 is returned. If the command asks some prompt, and then the user selects - /// again, 1 is returned. - int GetExecutionDepth() const; -}; - -class EditorCommandPalette { -private: - struct SearchResult; - struct Item; - - std::vector<EditorCommand> mCommands; - std::vector<Item> mItems; - std::vector<SearchResult> mSearchResults; - std::string mSearchText; - EditorCommandExecuteContext mExeCtx; - int mFocusedItemId = 0; - bool mFocusSearchBox = false; - bool mShouldCloseNextFrame = false; - -public: - EditorCommandPalette(); - ~EditorCommandPalette(); - - EditorCommandPalette(const EditorCommandPalette&) = delete; - EditorCommandPalette& operator=(const EditorCommandPalette&) = delete; - EditorCommandPalette(EditorCommandPalette&&) = default; - EditorCommandPalette& operator=(EditorCommandPalette&&) = default; - - void AddCommand(std::string_view category, std::string_view name, EditorCommand command); - void RemoveCommand(std::string_view category, std::string_view name); - void RemoveCommand(const std::string& commandName); - - void Show(bool* open = nullptr); - - enum ItemType { - CommandItem, - CommandOptionItem, - }; - - enum IndexType { - DirectIndex, - SearchResultIndex, - }; - - struct ItemInfo { - const char* text; - const EditorCommand* command; - int itemId; - ItemType itemType; - IndexType indexType; - }; - - size_t GetItemCount() const; - ItemInfo GetItem(size_t idx) const; - - void SelectFocusedItem(); - -private: - void InvalidateSearchResults(); -}; diff --git a/source/30-game/EditorCore.hpp b/source/30-game/EditorCore.hpp deleted file mode 100644 index 726f43e..0000000 --- a/source/30-game/EditorCore.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include <memory> - -class App; -class SpriteDefinition; - -class IEditorInspector { -public: - enum TargetType { - ITT_GameObject, - ITT_Ires, - ITT_Level, - ITT_None, - }; - -public: - virtual ~IEditorInspector() = default; - virtual void SelectTarget(TargetType type, void* object) = 0; -}; - -class IEditorContentBrowser { -public: - virtual ~IEditorContentBrowser() = default; -}; - -class IEditor { -public: - static std::unique_ptr<IEditor> CreateInstance(App* app); - virtual ~IEditor() = default; - - virtual void OnGameStateChanged(bool running) = 0; - virtual void Show() = 0; - - virtual IEditorInspector& GetInspector() = 0; - virtual IEditorContentBrowser& GetContentBrowser() = 0; - - virtual void OpenSpriteViewer(SpriteDefinition* sprite) = 0; -}; diff --git a/source/30-game/EditorCorePrivate.cpp b/source/30-game/EditorCorePrivate.cpp deleted file mode 100644 index 3efa33c..0000000 --- a/source/30-game/EditorCorePrivate.cpp +++ /dev/null @@ -1,1171 +0,0 @@ -#include "EditorCorePrivate.hpp" - -#include "App.hpp" -#include "AppConfig.hpp" -#include "EditorAccessories.hpp" -#include "EditorAttachmentImpl.hpp" -#include "EditorCommandPalette.hpp" -#include "EditorUtils.hpp" -#include "GameObject.hpp" -#include "Mesh.hpp" -#include "Player.hpp" -#include "SceneThings.hpp" -#include "VertexIndex.hpp" - -#include <ImGuiNotification.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 <rapidjson/document.h> -#include <rapidjson/filereadstream.h> -#include <rapidjson/filewritestream.h> -#include <rapidjson/writer.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(); - } - - ImGui::SameLine(); - if (ImGui::Button("...")) { - ImGui::OpenPopup("More Actions"); - } - if (ImGui::BeginPopup("More Actions")) { - if (ImGui::MenuItem("Rewrite all Ires to disk")) { - IresManager::instance->OverwriteAllToDisk(); - } - 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 - -void SaveRendererBindings(const Renderer& renderer) { - auto file = Utils::OpenCstdioFile("assets/GameRendererBindings.json", Utils::WriteTruncate); - if (!file) return; - DEFER { - fclose(file); - }; - - char writerBuffer[65536]; - rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); - rapidjson::Writer writer(stream); - - rapidjson::Document root(rapidjson::kObjectType); - renderer.SaveBindings(root, root); - - root.Accept(writer); -} -} // 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) - , mEdGuides(app, this) // -{ - 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("Keyboard Viewer", nullptr, &mWindowVisible_KeyboardViewer); - 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); - } - - if (mWindowVisible_KeyboardViewer) { - mEdKbViewer.Show(&mWindowVisible_KeyboardViewer); - } - - 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() { - using namespace ProjectBrussel_UNITY_ID; - - 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& renderer = *mApp->GetWorldRenderer(); - auto& camera = *mApp->GetActiveCamera(); - - if (ImGui::CollapsingHeader("Renderer settings")) { - if (ImGui::Checkbox("Draw shaded", &mRenderer_DrawShaded)) { - renderer.SetRenderOption(Renderer::RO_Shading, mRenderer_DrawShaded); - } - if (ImGui::Checkbox("Draw wireframe", &mRenderer_DrawWireFrame)) { - renderer.SetRenderOption(Renderer::RO_Wireframe, mRenderer_DrawWireFrame); - } - - if (auto ires = Utils::SimpleIresReceptor<IresMaterial>(renderer.binding_WireframeMaterial->GetIres(), *this, IresObject::KD_Material)) { - renderer.binding_WireframeMaterial.Attach(ires->GetInstance()); - SaveRendererBindings(renderer); - } - } - - if (ImGui::CollapsingHeader("Camera settings")) { - // 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(); - } -} diff --git a/source/30-game/EditorCorePrivate.hpp b/source/30-game/EditorCorePrivate.hpp deleted file mode 100644 index 4071e7a..0000000 --- a/source/30-game/EditorCorePrivate.hpp +++ /dev/null @@ -1,136 +0,0 @@ -#pragma once - -#include "App.hpp" -#include "EditorAccessories.hpp" -#include "EditorAttachment.hpp" -#include "EditorCommandPalette.hpp" -#include "EditorCore.hpp" -#include "EditorUtils.hpp" -#include "EditorWorldGuides.hpp" -#include "GameObject.hpp" -#include "Ires.hpp" -#include "Level.hpp" -#include "RcPtr.hpp" -#include "Sprite.hpp" -#include "World.hpp" - -#include <memory> -#include <string> - -// TODO move inspector drawing to this class -class EditorInspector final : public IEditorInspector { -public: - std::string renamingScratchBuffer; - void* selectedItPtr = nullptr; - TargetType selectedItt = ITT_None; - bool renaming = false; - - void SelectTarget(TargetType type, void* object) override; -}; - -class EditorContentBrowser final : public IEditorContentBrowser { -private: - enum Pane { - P_Settings, - P_Ires, - P_Level, - }; - - 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 mBrowserHeight = 0.5f; - float mSplitterLeft = kLeftPaneMinWidth; - float mSplitterRight = 0.0f; - bool mDocked = true; - -public: - EditorContentBrowser(EditorInspector* inspector); - ~EditorContentBrowser() override; - - void Show(bool* open = nullptr); -}; - -struct GuizmoState { - ImGuizmo::OPERATION currOperation = ImGuizmo::TRANSLATE; - ImGuizmo::MODE currMode = ImGuizmo::LOCAL; - glm::mat4 cubeMatrix; - float snap[3] = { 1.f, 1.f, 1.f }; - float boundsSnap[3] = { 0.1f, 0.1f, 0.1f }; - bool useSnap = false; - bool boundSizing = false; - bool boundSizingSnap = false; -}; - -// TODO editor undo stack -class EditorInstance : public IEditor { -public: - enum EditorCameraMode { - // "Game mode": the camera views from the side, where the forward vector is perpendicular to the world plane - ECM_2D, - // "Editor mode": equivalent to Unity's 3D mode, the camera is completely free and controlled as a 3d camera - ECM_Side3D, - }; - -private: - App* mApp; - GameObject* mPopupCurrent_GameObject = nullptr; - Camera mEditorCamera; - RcPtr<SpriteDefinition> mSpriteView_Instance; - EditorCommandPalette mEdCommandPalette; - EditorInspector mEdInspector; - EditorContentBrowser mEdContentBrowser; - EditorKeyboardViewer mEdKbViewer; - EditorWorldGuides mEdGuides; - GuizmoState mGuizmo; - glm::vec3 mDragCam_CamInitial; - ImVec2 mDragCam_CursorInitial; - int mSpriteView_Frame; - float mMoveCamScrollSpeed = 1.0f; - float mMoveCamSlideSpeed = 0.1f; - EditorCameraMode mEcm = ECM_2D; - bool mSpriteView_OpenNextFrame = false; - bool mWindowVisible_ImGuiDemo = false; - bool mWindowVisible_CommandPalette = false; - bool mWindowVisible_Inspector = true; - bool mWindowVisible_ContentBrowser = true; - bool mWindowVisible_WorldStructure = true; - bool mWindowVisible_WorldProperties = true; - bool mWindowVisible_KeyboardViewer = false; - bool mDragCam_Happening = false; - bool mMoveCamKeyboard = false; - bool mMoveCamScrollWheel = false; - bool mRenderer_DrawShaded = true; - bool mRenderer_DrawWireFrame = false; - -public: - EditorInstance(App* app); - ~EditorInstance() override; - - void OnGameStateChanged(bool running) override; - void Show() override; - - EditorInspector& GetInspector() override { return mEdInspector; } - EditorContentBrowser& GetContentBrowser() override { return mEdContentBrowser; } - - void OpenSpriteViewer(SpriteDefinition* sprite) override; - -private: - bool IsCurrentCameraEditor() const { - return !mApp->IsGameRunning(); - } - - void ShowWorldProperties(); - - void ShowInspector(IresObject* ires); - void ShowInspector(LevelManager::LoadableObject* ldObj); - void ShowInspector(GameObject* object); - - void ShowSpriteViewer(); -}; diff --git a/source/30-game/EditorUtils.cpp b/source/30-game/EditorUtils.cpp deleted file mode 100644 index 20caef7..0000000 --- a/source/30-game/EditorUtils.cpp +++ /dev/null @@ -1,447 +0,0 @@ -#include "EditorUtils.hpp" - -#define IMGUI_DEFINE_MATH_OPERATORS -#include <imgui_internal.h> - -#include <backends/imgui_impl_glfw.h> -#include <cmath> -#include <glm/gtc/quaternion.hpp> -#include <glm/gtx/quaternion.hpp> -#include <numbers> - -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::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; -} - -void ImGui::AddUnderLine(ImColor col) { - auto min = ImGui::GetItemRectMin(); - auto max = ImGui::GetItemRectMax(); - min.y = max.y; - ImGui::GetWindowDrawList()->AddLine(min, max, col, 1.0f); -} - -void ImGui::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) { - // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/drawing.cpp - // ax::NodeEditor::DrawIcon - - auto rect = ImRect(a, b); - auto rect_x = rect.Min.x; - auto rect_y = rect.Min.y; - auto rect_w = rect.Max.x - rect.Min.x; - auto rect_h = rect.Max.y - rect.Min.y; - auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; - auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; - auto rect_center = ImVec2(rect_center_x, rect_center_y); - const auto outline_scale = rect_w / 24.0f; - const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle - - if (type == IconType::Flow) { - const auto origin_scale = rect_w / 24.0f; - - const auto offset_x = 1.0f * origin_scale; - const auto offset_y = 0.0f * origin_scale; - const auto margin = 2.0f * origin_scale; - const auto rounding = 0.1f * origin_scale; - const auto tip_round = 0.7f; // percentage of triangle edge (for tip) - // const auto edge_round = 0.7f; // percentage of triangle edge (for corner) - const auto canvas = ImRect( - rect.Min.x + margin + offset_x, - rect.Min.y + margin + offset_y, - rect.Max.x - margin + offset_x, - rect.Max.y - margin + offset_y); - const auto canvas_x = canvas.Min.x; - const auto canvas_y = canvas.Min.y; - const auto canvas_w = canvas.Max.x - canvas.Min.x; - const auto canvas_h = canvas.Max.y - canvas.Min.y; - - const auto left = canvas_x + canvas_w * 0.5f * 0.3f; - const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; - const auto top = canvas_y + canvas_h * 0.5f * 0.2f; - const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; - const auto center_y = (top + bottom) * 0.5f; - // const auto angle = AX_PI * 0.5f * 0.5f * 0.5f; - - const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); - const auto tip_right = ImVec2(right, center_y); - const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); - - drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); - drawList->PathBezierCubicCurveTo( - ImVec2(left, top), - ImVec2(left, top), - ImVec2(left, top) + ImVec2(rounding, 0)); - drawList->PathLineTo(tip_top); - drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); - drawList->PathBezierCubicCurveTo( - tip_right, - tip_right, - tip_bottom + (tip_right - tip_bottom) * tip_round); - drawList->PathLineTo(tip_bottom); - drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); - drawList->PathBezierCubicCurveTo( - ImVec2(left, bottom), - ImVec2(left, bottom), - ImVec2(left, bottom) - ImVec2(0, rounding)); - - if (!filled) { - if (innerColor & 0xFF000000) { - drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); - } - - drawList->PathStroke(color, true, 2.0f * outline_scale); - } else { - drawList->PathFillConvex(color); - } - } else { - auto triangleStart = rect_center_x + 0.32f * rect_w; - - auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); - - rect.Min.x += rect_offset; - rect.Max.x += rect_offset; - rect_x += rect_offset; - rect_center_x += rect_offset * 0.5f; - rect_center.x += rect_offset * 0.5f; - - if (type == IconType::Circle) { - const auto c = rect_center; - - if (!filled) { - const auto r = 0.5f * rect_w / 2.0f - 0.5f; - - if (innerColor & 0xFF000000) - drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments); - drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale); - } else { - drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments); - } - } - - if (type == IconType::Square) { - if (filled) { - const auto r = 0.5f * rect_w / 2.0f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments); - } else { - const auto r = 0.5f * rect_w / 2.0f - 0.5f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - if (innerColor & 0xFF000000) - drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments); - - drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale); - } - } - - if (type == IconType::Grid) { - const auto r = 0.5f * rect_w / 2.0f; - const auto w = ceilf(r / 3.0f); - - const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); - const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); - - auto tl = baseTl; - auto br = baseBr; - for (int i = 0; i < 3; ++i) { - tl.x = baseTl.x; - br.x = baseBr.x; - drawList->AddRectFilled(tl, br, color); - tl.x += w * 2; - br.x += w * 2; - if (i != 1 || filled) - drawList->AddRectFilled(tl, br, color); - tl.x += w * 2; - br.x += w * 2; - drawList->AddRectFilled(tl, br, color); - - tl.y += w * 2; - br.y += w * 2; - } - - triangleStart = br.x + w + 1.0f / 24.0f * rect_w; - } - - if (type == IconType::RoundSquare) { - if (filled) { - const auto r = 0.5f * rect_w / 2.0f; - const auto cr = r * 0.5f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - drawList->AddRectFilled(p0, p1, color, cr, 15); - } else { - const auto r = 0.5f * rect_w / 2.0f - 0.5f; - const auto cr = r * 0.5f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - if (innerColor & 0xFF000000) - drawList->AddRectFilled(p0, p1, innerColor, cr, 15); - - drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale); - } - } else if (type == IconType::Diamond) { - if (filled) { - const auto r = 0.607f * rect_w / 2.0f; - const auto c = rect_center; - - drawList->PathLineTo(c + ImVec2(0, -r)); - drawList->PathLineTo(c + ImVec2(r, 0)); - drawList->PathLineTo(c + ImVec2(0, r)); - drawList->PathLineTo(c + ImVec2(-r, 0)); - drawList->PathFillConvex(color); - } else { - const auto r = 0.607f * rect_w / 2.0f - 0.5f; - const auto c = rect_center; - - drawList->PathLineTo(c + ImVec2(0, -r)); - drawList->PathLineTo(c + ImVec2(r, 0)); - drawList->PathLineTo(c + ImVec2(0, r)); - drawList->PathLineTo(c + ImVec2(-r, 0)); - - if (innerColor & 0xFF000000) - drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); - - drawList->PathStroke(color, true, 2.0f * outline_scale); - } - } else { - const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); - - drawList->AddTriangleFilled( - ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), - ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), - ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), - color); - } - } -} - -void ImGui::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) { - // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/widgets.cpp - // ax::NodeEditor::Icon - - if (ImGui::IsRectVisible(size)) { - auto cursorPos = ImGui::GetCursorScreenPos(); - auto drawList = ImGui::GetWindowDrawList(); - DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor)); - } - - ImGui::Dummy(size); -} - -void ImGui::DrawArrow(ImDrawList* drawList, ImVec2 from, ImVec2 to, ImU32 color, float lineThickness) { - // Adapted from https://stackoverflow.com/a/6333775 - - using namespace std::numbers; - constexpr float kHeadLength = 10; - - auto angle = std::atan2(to.y - from.y, to.x - from.x); - drawList->AddLine(from, to, color, lineThickness); - drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle - pi / 6), to.y - kHeadLength * std::sin(angle - pi / 6)), color, lineThickness); - drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle + pi / 6), to.y - kHeadLength * std::sin(angle + pi / 6)), color, lineThickness); -} - -struct DialogObject { - std::string message; - std::function<void(int)> callback; -}; - -static DialogObject gConfirmationDialog{}; - -void ImGui::DialogConfirmation(std::string message, std::function<void(bool)> callback) { - gConfirmationDialog.message = std::move(message); - // TODO is converting void(bool) to void(int) fine? - gConfirmationDialog.callback = std::move(callback); -} - -void ImGui::ShowDialogs() { - if (gConfirmationDialog.callback) { - if (ImGui::BeginPopupModal("Confirmation")) { - ImGui::Text("%s", gConfirmationDialog.message.c_str()); - if (ImGui::Button("Cancel")) { - gConfirmationDialog.callback(false); - gConfirmationDialog.callback = {}; - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("Confirm")) { - gConfirmationDialog.callback(true); - gConfirmationDialog.callback = {}; - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - } -} - -float Utils::CalcImageHeight(glm::vec2 original, int targetWidth) { - // Xorig / Yorig = Xnew / Ynew - // Ynew = Xnew * Yorig / Xorig - return targetWidth * original.y / original.x; -} - -float Utils::CalcImageWidth(glm::vec2 original, float targetHeight) { - // Xorig / Yorig = Xnew / Ynew - // Xnew = Xorig / Yorig * Ynew - return original.x / original.y * targetHeight; -} - -ImVec2 Utils::FitImage(glm::vec2 original) { - float newWidth = ImGui::GetContentRegionAvail().x; - return ImVec2(newWidth, CalcImageHeight(original, newWidth)); -} diff --git a/source/30-game/EditorUtils.hpp b/source/30-game/EditorUtils.hpp deleted file mode 100644 index 96e92d3..0000000 --- a/source/30-game/EditorUtils.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include "EditorCore.hpp" -#include "ImGuiGuizmo.hpp" -#include "Ires.hpp" - -#include <Color.hpp> -#include <Metadata.hpp> - -#include <imgui.h> -#include <string> - -// To check whether a payload is of this type, use starts_with() -#define BRUSSEL_TAG_PREFIX_GameObject "GameObject" -#define BRUSSEL_TAG_PREFIX_Ires "Ires" - -#define BRUSSEL_TAG_Level "Level" - -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 Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize = -1.0f); - -void AddUnderLine(ImColor col); - -enum class IconType { - Flow, - Circle, - Square, - Grid, - RoundSquare, - Diamond, -}; - -void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor); -void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0)); - -void DrawArrow(ImDrawList* drawList, ImVec2 from, ImVec2 to, ImU32 color, float lineThickness = 1.0f); - -// NOTE: string is copied into an internal storage -void DialogConfirmation(std::string message, std::function<void(bool)> callback); -void ShowDialogs(); - -} // namespace ImGui - -namespace Utils { - -float CalcImageHeight(glm::vec2 original, int targetWidth); -float CalcImageWidth(glm::vec2 original, float targetHeight); -ImVec2 FitImage(glm::vec2 original); - -// TODO get kind from T -template <typename T> -T* SimpleIresReceptor(T* existing, IEditor& editor, IresObject::Kind kind) { - if (existing) { - existing->ShowReference(editor); - } else { - IresObject::ShowReferenceNull(editor); - } - if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(kind).data())) { - return *static_cast<T* const*>(payload->Data); - } - ImGui::EndDragDropTarget(); - } - return nullptr; -} - -} // namespace Utils diff --git a/source/30-game/EditorWorldGuides.cpp b/source/30-game/EditorWorldGuides.cpp deleted file mode 100644 index f0d66b8..0000000 --- a/source/30-game/EditorWorldGuides.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "EditorWorldGuides.hpp" - -#include "App.hpp" -#include "CommonVertexIndex.hpp" -#include "GraphicsTags.hpp" -#include "VertexIndex.hpp" - -EditorWorldGuides::EditorWorldGuides(App* app, IEditor* editor) - : mApp{ app } - , mEditor{ editor } // -{ - using namespace Tags; - - mRoGrid.SetFormat(gVformatLines.Get(), IT_16Bit); - mRoGrid.SetMaterial(gDefaultMaterial.Get()); - mRoGrid.RebuildIfNecessary(); - - mRoDebugSkybox.SetFormat(gVformatStandard.Get(), IT_16Bit); - mRoDebugSkybox.SetMaterial(gDefaultMaterial.Get()); - mRoDebugSkybox.RebuildIfNecessary(); -} - -void EditorWorldGuides::Update() { - auto& camera = *mApp->GetActiveCamera(); - // TODO -} diff --git a/source/30-game/EditorWorldGuides.hpp b/source/30-game/EditorWorldGuides.hpp deleted file mode 100644 index 0dfdea2..0000000 --- a/source/30-game/EditorWorldGuides.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "Renderer.hpp" - -class EditorWorldGuides { -private: - App* mApp; - IEditor* mEditor; - RenderObject mRoGrid; - RenderObject mRoDebugSkybox; - -public: - EditorWorldGuides(App* app, IEditor* editor); - - void Update(); -}; diff --git a/source/30-game/FuzzyMatch.cpp b/source/30-game/FuzzyMatch.cpp deleted file mode 100644 index 0ab604d..0000000 --- a/source/30-game/FuzzyMatch.cpp +++ /dev/null @@ -1,174 +0,0 @@ -// Adapted from https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.h -#include "FuzzyMatch.hpp" - -#include <cctype> -#include <cstring> - -namespace FuzzyMatch { - -namespace P6503_UNITY_ID { - bool SearchRecursive(const char* pattern, const char* src, int& outScore, const char* strBegin, const uint8_t srcMatches[], uint8_t newMatches[], int maxMatches, int& nextMatch, int& recursionCount, int recursionLimit); -} // namespace P6503_UNITY_ID - -bool SearchSimple(char const* pattern, char const* haystack) { - while (*pattern != '\0' && *haystack != '\0') { - if (tolower(*pattern) == tolower(*haystack)) { - ++pattern; - } - ++haystack; - } - - return *pattern == '\0'; -} - -bool Search(char const* pattern, char const* haystack, int& outScore) { - uint8_t matches[256]; - int matchCount = 0; - return Search(pattern, haystack, outScore, matches, sizeof(matches), matchCount); -} - -bool Search(char const* pattern, char const* haystack, int& outScore, uint8_t matches[], int maxMatches, int& outMatches) { - int recursionCount = 0; - int recursionLimit = 10; - int newMatches = 0; - bool result = P6503_UNITY_ID::SearchRecursive(pattern, haystack, outScore, haystack, nullptr, matches, maxMatches, newMatches, recursionCount, recursionLimit); - outMatches = newMatches; - return result; -} - -namespace P6503_UNITY_ID { - bool SearchRecursive(const char* pattern, const char* src, int& outScore, const char* strBegin, const uint8_t srcMatches[], uint8_t newMatches[], int maxMatches, int& nextMatch, int& recursionCount, int recursionLimit) { - // Count recursions - ++recursionCount; - if (recursionCount >= recursionLimit) { - return false; - } - - // Detect end of strings - if (*pattern == '\0' || *src == '\0') { - return false; - } - - // Recursion params - bool recursiveMatch = false; - uint8_t bestRecursiveMatches[256]; - int bestRecursiveScore = 0; - - // Loop through pattern and str looking for a match - bool firstMatch = true; - while (*pattern != '\0' && *src != '\0') { - // Found match - if (tolower(*pattern) == tolower(*src)) { - // Supplied matches buffer was too short - if (nextMatch >= maxMatches) { - return false; - } - - // "Copy-on-Write" srcMatches into matches - if (firstMatch && srcMatches) { - memcpy(newMatches, srcMatches, nextMatch); - firstMatch = false; - } - - // Recursive call that "skips" this match - uint8_t recursiveMatches[256]; - int recursiveScore; - int recursiveNextMatch = nextMatch; - if (SearchRecursive(pattern, src + 1, recursiveScore, strBegin, newMatches, recursiveMatches, sizeof(recursiveMatches), recursiveNextMatch, recursionCount, recursionLimit)) { - // Pick the best recursive score - if (!recursiveMatch || recursiveScore > bestRecursiveScore) { - memcpy(bestRecursiveMatches, recursiveMatches, 256); - bestRecursiveScore = recursiveScore; - } - recursiveMatch = true; - } - - // Advance - newMatches[nextMatch++] = (uint8_t)(src - strBegin); - ++pattern; - } - ++src; - } - - // Determine if full pattern was matched - bool matched = *pattern == '\0'; - - // Calculate score - if (matched) { - const int sequentialBonus = 15; // bonus for adjacent matches - const int separatorBonus = 30; // bonus if match occurs after a separator - const int camelBonus = 30; // bonus if match is uppercase and prev is lower - const int firstLetterBonus = 15; // bonus if the first letter is matched - - const int leadingLetterPenalty = -5; // penalty applied for every letter in str before the first match - const int maxLeadingLetterPenalty = -15; // maximum penalty for leading letters - const int unmatchedLetterPenalty = -1; // penalty for every letter that doesn't matter - - // Iterate str to end - while (*src != '\0') { - ++src; - } - - // Initialize score - outScore = 100; - - // Apply leading letter penalty - int penalty = leadingLetterPenalty * newMatches[0]; - if (penalty < maxLeadingLetterPenalty) { - penalty = maxLeadingLetterPenalty; - } - outScore += penalty; - - // Apply unmatched penalty - int unmatched = (int)(src - strBegin) - nextMatch; - outScore += unmatchedLetterPenalty * unmatched; - - // Apply ordering bonuses - for (int i = 0; i < nextMatch; ++i) { - uint8_t currIdx = newMatches[i]; - - if (i > 0) { - uint8_t prevIdx = newMatches[i - 1]; - - // Sequential - if (currIdx == (prevIdx + 1)) - outScore += sequentialBonus; - } - - // Check for bonuses based on neighbor character value - if (currIdx > 0) { - // Camel case - char neighbor = strBegin[currIdx - 1]; - char curr = strBegin[currIdx]; - if (::islower(neighbor) && ::isupper(curr)) { - outScore += camelBonus; - } - - // Separator - bool neighborSeparator = neighbor == '_' || neighbor == ' '; - if (neighborSeparator) { - outScore += separatorBonus; - } - } else { - // First letter - outScore += firstLetterBonus; - } - } - } - - // Return best result - if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) { - // Recursive score is better than "this" - memcpy(newMatches, bestRecursiveMatches, maxMatches); - outScore = bestRecursiveScore; - return true; - } else if (matched) { - // "this" score is better than recursive - return true; - } else { - // no match - return false; - } - } -} // namespace P6503_UNITY_ID -} // namespace FuzzyMatch diff --git a/source/30-game/FuzzyMatch.hpp b/source/30-game/FuzzyMatch.hpp deleted file mode 100644 index 7a26b7e..0000000 --- a/source/30-game/FuzzyMatch.hpp +++ /dev/null @@ -1,10 +0,0 @@ -// Adapted from https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.h -#pragma once - -#include <cstdint> - -namespace FuzzyMatch { -bool SearchSimple(char const* pattern, char const* haystack); -bool Search(char const* pattern, char const* haystack, int& outScore); -bool Search(char const* pattern, char const* haystack, int& outScore, uint8_t matches[], int maxMatches, int& outMatches); -} // namespace FuzzyMatch diff --git a/source/30-game/GameObject.cpp b/source/30-game/GameObject.cpp deleted file mode 100644 index 3b15111..0000000 --- a/source/30-game/GameObject.cpp +++ /dev/null @@ -1,230 +0,0 @@ -#include "GameObject.hpp" - -#include "Level.hpp" -#include "Player.hpp" -#include "SceneThings.hpp" -#include "World.hpp" - -#include <Metadata.hpp> -#include <RapidJsonHelper.hpp> - -#include <rapidjson/document.h> -#include <cassert> -#include <string_view> -#include <utility> - -using namespace std::literals; - -namespace ProjectBrussel_UNITY_ID { -GameObject* CreateGameObject(GameObject::Kind kind, GameWorld* world) { - using enum Tags::GameObjectKind; - switch (kind) { - case KD_Generic: return new GameObject(world); - case KD_SimpleGeometry: return new SimpleGeometryObject(world); - case KD_Building: return new BuildingObject(world); - case KD_LevelWrapper: return new LevelWrapperObject(world); - default: break; - } - return nullptr; -} - -bool ValidateGameObjectChild(GameObject* parent, GameObject* child) { - return parent->GetWorld() == child->GetWorld(); -} -} // namespace ProjectBrussel_UNITY_ID - -void GameObject::FreeRecursive(GameObject* obj) { - if (!obj->mStopFreePropagation) { - for (auto child : obj->GetChildren()) { - FreeRecursive(obj); - } - } - delete obj; -} - -GameObject::GameObject(GameWorld* world) - : GameObject(KD_Generic, world) { -} - -GameObject::GameObject(Kind kind, GameWorld* world) - : mEditorAttachment{ nullptr } - , mWorld{ world } - , mParent{ nullptr } - , mRot(1.0f, 0.0f, 0.0f, 0.0f) - , mPos(0.0f, 0.0f, 0.0f) - , mScale(1.0f, 1.0f, 1.0f) - , mKind{ kind } { -} - -GameObject::~GameObject() { - RemoveAllChildren(); - if (mParent) { - mParent->RemoveChild(this); - // NOTE: from this point on, mParent will be nullptr - } -} - -GameObject::Kind GameObject::GetKind() const { - return mKind; -} - -GameWorld* GameObject::GetWorld() const { - return mWorld; -} - -GameObject* GameObject::GetParent() const { - return mParent; -} - -const PodVector<GameObject*>& GameObject::GetChildren() const { - return mChildren; -} - -void GameObject::AddChild(GameObject* child) { - using namespace ProjectBrussel_UNITY_ID; - - if (child->mParent) { - return; - } - if (!ValidateGameObjectChild(this, child)) { - return; - } - - mChildren.push_back(child); - child->SetParent(this); -} - -GameObject* GameObject::RemoveChild(int index) { - if (index < 0 || index >= mChildren.size()) { - return nullptr; - } - - auto it = mChildren.begin() + index; - auto child = *it; - - // cancelUpdate(ret); - - std::swap(*it, mChildren.back()); - mChildren.pop_back(); - child->SetParent(nullptr); - return child; -} - -GameObject* GameObject::RemoveChild(GameObject* child) { - if (child) { - for (auto it = mChildren.begin(); it != mChildren.end(); ++it) { - if (*it == child) { - // cancelUpdate(child); - - std::swap(*it, mChildren.back()); - mChildren.pop_back(); - child->SetParent(nullptr); - return child; - } - } - } - return nullptr; -} - -void GameObject::RemoveSelfFromParent() { - if (mParent) { - mParent->RemoveChild(this); - } -} - -PodVector<GameObject*> GameObject::RemoveAllChildren() { - for (auto& child : mChildren) { - child->SetParent(nullptr); - } - - auto result = std::move(mChildren); - // Moving from STL object leaves it in a valid but _unspecified_ state, call std::vector::clear() to guarantee it's empty - // NOTE: even though we have the source code of PodVector<T>, we still do this to follow convention - mChildren.clear(); - return result; -} - -const glm::vec3& GameObject::GetPos() const { - 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; -} - -const glm::vec3& GameObject::GetScale() const { - return mScale; -} - -void GameObject::SetScale(const glm::vec3& scale) { - mScale = scale; -} - -std::span<const RenderObject> GameObject::GetRenderObjects() const { - return {}; -} - -void GameObject::OnInitialized() { -} - -void GameObject::Awaken() { -} - -void GameObject::Resleep() { -} - -void GameObject::Update() { -} - -rapidjson::Value GameObject::Serialize(GameObject* obj, rapidjson::Document& root) { - rapidjson::Value result(rapidjson::kObjectType); - - result.AddMember("Type", rapidjson::StringRef(Metadata::EnumToString(obj->GetKind())), root.GetAllocator()); - - rapidjson::Value rvValue(rapidjson::kObjectType); - obj->WriteSaveFormat(rvValue, root); - result.AddMember("Value", rvValue, root.GetAllocator()); - - return result; -} - -std::unique_ptr<GameObject> GameObject::Deserialize(const rapidjson::Value& value, GameWorld* world) { - using namespace ProjectBrussel_UNITY_ID; - - auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); - if (!rvType) return nullptr; - - auto rvValue = rapidjson::GetProperty(value, rapidjson::kObjectType, "Value"sv); - if (!rvValue) return nullptr; - - auto kind = Metadata::EnumFromString<Kind>(rapidjson::AsStringView(*rvType)); - assert(kind.has_value()); - auto obj = std::unique_ptr<GameObject>(CreateGameObject(kind.value(), world)); - if (!obj) return nullptr; - obj->ReadSaveFormat(*rvValue); - - return obj; -} - -void GameObject::ReadSaveFormat(const rapidjson::Value& value) { -} - -void GameObject::WriteSaveFormat(rapidjson::Value& value, rapidjson::Document& root) { -} - -void GameObject::SetParent(GameObject* parent) { - if (mParent != parent) { - mParent = parent; - // needUpdate(); - } -} - -#include <generated/GameObject.gs.inl> diff --git a/source/30-game/GameObject.hpp b/source/30-game/GameObject.hpp deleted file mode 100644 index 40c52e7..0000000 --- a/source/30-game/GameObject.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include "EditorAttachment.hpp" -#include "Material.hpp" -#include "Renderer.hpp" -#include "VertexIndex.hpp" - -#include <MacrosCodegen.hpp> -#include <PodVector.hpp> - -#include <rapidjson/fwd.h> -#include <glm/glm.hpp> -#include <glm/gtc/quaternion.hpp> -#include <span> -#include <vector> - -namespace Tags { -enum class GameObjectKind { - // clang-format off - BRUSSEL_ENUM(ToString, FromString, RemovePrefix KD_, AddPrefix GameObject, ExcludeHeuristics) - // clang-format on - - KD_Generic, - KD_Player, - KD_SimpleGeometry, - KD_Building, - KD_LevelWrapper, - KD_COUNT, -}; -} // namespace Tags - -class GameWorld; -class GameObject { - BRUSSEL_CLASS(InheritanceHiearchy) - -public: - using Kind = Tags::GameObjectKind; - using enum Tags::GameObjectKind; - -private: - std::unique_ptr<EditorAttachment> mEditorAttachment; - GameWorld* mWorld; - GameObject* mParent; - PodVector<GameObject*> mChildren; - glm::quat mRot; - glm::vec3 mPos; - glm::vec3 mScale; - Kind mKind; - -protected: - bool mStopFreePropagation : 1 = false; - -public: - static void FreeRecursive(GameObject* object); - - // TODO allow moving between worlds - GameObject(GameWorld* world); - GameObject(Kind kind, GameWorld* world); - virtual ~GameObject(); - - GameObject(const GameObject&) = delete; - GameObject& operator=(const GameObject&) = delete; - GameObject(GameObject&&) = default; - GameObject& operator=(GameObject&&) = default; - - Kind GetKind() const; - - GameWorld* GetWorld() const; - GameObject* GetParent() const; - const PodVector<GameObject*>& GetChildren() const; - void AddChild(GameObject* child); - GameObject* RemoveChild(int index); - GameObject* RemoveChild(GameObject* child); - void RemoveSelfFromParent(); - PodVector<GameObject*> RemoveAllChildren(); - - EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } - void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } - - const glm::vec3& GetPos() const; - void SetPos(const glm::vec3& pos); - - const glm::quat& GetRotation() const; - void SetRotation(const glm::quat& rotation); - - const glm::vec3& GetScale() const; - void SetScale(const glm::vec3& scale); - - // Visuals - virtual std::span<const RenderObject> GetRenderObjects() const; - - // Lifetime hooks - virtual void OnInitialized(); - virtual void Awaken(); - virtual void Resleep(); - virtual void Update(); - - static rapidjson::Value Serialize(GameObject* obj, rapidjson::Document& root); - static std::unique_ptr<GameObject> Deserialize(const rapidjson::Value& value, GameWorld* world); - virtual void ReadSaveFormat(const rapidjson::Value& value); - virtual void WriteSaveFormat(rapidjson::Value& value, rapidjson::Document& root); - -protected: - void SetParent(GameObject* parent); -}; - -#include <generated/GameObject.gh.inl> diff --git a/source/30-game/GraphicsTags.cpp b/source/30-game/GraphicsTags.cpp deleted file mode 100644 index 83d52f8..0000000 --- a/source/30-game/GraphicsTags.cpp +++ /dev/null @@ -1,273 +0,0 @@ -#include "GraphicsTags.hpp" - -#include <robin_hood.h> -#include <cstddef> -#include <cstdint> - -using namespace std::literals; - -int Tags::SizeOf(VertexElementType type) { - switch (type) { - case VET_Float1: - return sizeof(float); - case VET_Float2: - return sizeof(float) * 2; - case VET_Float3: - return sizeof(float) * 3; - case VET_Float4: - return sizeof(float) * 4; - case VET_Double1: - return sizeof(double); - case VET_Double2: - return sizeof(double) * 2; - case VET_Double3: - return sizeof(double) * 3; - case VET_Double4: - return sizeof(double) * 4; - case VET_Short2: - case VET_Short2Norm: - case VET_Ushort2: - case VET_Ushort2Norm: - return sizeof(short) * 2; - case VET_Short4: - case VET_Short4Norm: - case VET_Ushort4: - case VET_Ushort4Norm: - return sizeof(short) * 4; - case VET_Int1: - case VET_Uint1: - return sizeof(int); - case VET_Int2: - case VET_Uint2: - return sizeof(int) * 2; - case VET_Int3: - case VET_Uint3: - return sizeof(int) * 3; - case VET_Int4: - case VET_Uint4: - return sizeof(int) * 4; - case VET_Byte4: - case VET_Byte4Norm: - case VET_Ubyte4: - case VET_Ubyte4Norm: - return sizeof(char) * 4; - } - return 0; -} - -int Tags::VectorLenOf(VertexElementType type) { - switch (type) { - case VET_Float1: - case VET_Double1: - case VET_Int1: - case VET_Uint1: - return 1; - case VET_Float2: - case VET_Double2: - case VET_Short2: - case VET_Short2Norm: - case VET_Ushort2: - case VET_Ushort2Norm: - case VET_Int2: - case VET_Uint2: - return 2; - case VET_Float3: - case VET_Double3: - case VET_Int3: - case VET_Uint3: - return 3; - case VET_Float4: - case VET_Double4: - case VET_Short4: - case VET_Short4Norm: - case VET_Ushort4: - case VET_Ushort4Norm: - case VET_Int4: - case VET_Uint4: - case VET_Byte4: - case VET_Byte4Norm: - case VET_Ubyte4: - case VET_Ubyte4Norm: - return 4; - } - return 0; -} - -GLenum Tags::FindGLType(VertexElementType type) { - switch (type) { - case VET_Float1: - case VET_Float2: - case VET_Float3: - case VET_Float4: - return GL_FLOAT; - case VET_Double1: - case VET_Double2: - case VET_Double3: - case VET_Double4: - return GL_DOUBLE; - case VET_Short2: - case VET_Short2Norm: - case VET_Short4: - case VET_Short4Norm: - return GL_SHORT; - case VET_Ushort2: - case VET_Ushort2Norm: - case VET_Ushort4: - case VET_Ushort4Norm: - return GL_UNSIGNED_SHORT; - case VET_Int1: - case VET_Int2: - case VET_Int3: - case VET_Int4: - return GL_INT; - case VET_Uint1: - case VET_Uint2: - case VET_Uint3: - case VET_Uint4: - return GL_UNSIGNED_INT; - case VET_Byte4: - case VET_Byte4Norm: - return GL_BYTE; - case VET_Ubyte4: - case VET_Ubyte4Norm: - return GL_UNSIGNED_BYTE; - } - return 0; -} - -bool Tags::IsNormalized(VertexElementType type) { - return type >= VET_NORM_BEGIN && type <= VET_NORM_END; -} - -int Tags::SizeOf(IndexType type) { - switch (type) { - case IT_16Bit: return sizeof(uint16_t); - case IT_32Bit: return sizeof(uint32_t); - } - return 0; -} - -GLenum Tags::FindGLType(IndexType type) { - switch (type) { - case IT_16Bit: return GL_UNSIGNED_SHORT; - case IT_32Bit: return GL_UNSIGNED_BYTE; - } - return 0; -} - -namespace ProjectBrussel_UNITY_ID { -struct GLTypeInfo { - robin_hood::unordered_flat_map<GLenum, std::string_view> enum2Name; - robin_hood::unordered_flat_map<std::string_view, GLenum> name2Enum; - - GLTypeInfo() { - InsertEntry("float"sv, GL_FLOAT); - InsertEntry("double"sv, GL_DOUBLE); - InsertEntry("int"sv, GL_INT); - InsertEntry("uint"sv, GL_UNSIGNED_INT); - InsertEntry("bool"sv, GL_BOOL); - - InsertEntry("vec2"sv, GL_FLOAT_VEC2); - InsertEntry("vec3"sv, GL_FLOAT_VEC3); - InsertEntry("vec4"sv, GL_FLOAT_VEC4); - InsertEntry("dvec2"sv, GL_DOUBLE_VEC2); - InsertEntry("dvec3"sv, GL_DOUBLE_VEC3); - InsertEntry("dvec4"sv, GL_DOUBLE_VEC4); - InsertEntry("ivec2"sv, GL_INT_VEC2); - InsertEntry("ivec3"sv, GL_INT_VEC3); - InsertEntry("ivec4"sv, GL_INT_VEC4); - InsertEntry("uvec2"sv, GL_UNSIGNED_INT_VEC2); - InsertEntry("uvec3"sv, GL_UNSIGNED_INT_VEC3); - InsertEntry("uvec4"sv, GL_UNSIGNED_INT_VEC4); - InsertEntry("bvec2"sv, GL_BOOL_VEC2); - InsertEntry("bvec3"sv, GL_BOOL_VEC3); - InsertEntry("bvec4"sv, GL_BOOL_VEC4); - - InsertEntry("mat2"sv, GL_FLOAT_MAT2); - InsertEntry("mat3"sv, GL_FLOAT_MAT3); - InsertEntry("mat4"sv, GL_FLOAT_MAT4); - InsertEntry("mat2x3"sv, GL_FLOAT_MAT2x3); - InsertEntry("mat2x4"sv, GL_FLOAT_MAT2x4); - InsertEntry("mat3x2"sv, GL_FLOAT_MAT3x2); - InsertEntry("mat3x4"sv, GL_FLOAT_MAT3x4); - InsertEntry("mat4x2"sv, GL_FLOAT_MAT4x2); - InsertEntry("mat4x3"sv, GL_FLOAT_MAT4x3); - - InsertEntry("dmat2"sv, GL_DOUBLE_MAT2); - InsertEntry("dmat3"sv, GL_DOUBLE_MAT3); - InsertEntry("dmat4"sv, GL_DOUBLE_MAT4); - InsertEntry("dmat2x3"sv, GL_DOUBLE_MAT2x3); - InsertEntry("dmat2x4"sv, GL_DOUBLE_MAT2x4); - InsertEntry("dmat3x2"sv, GL_DOUBLE_MAT3x2); - InsertEntry("dmat3x4"sv, GL_DOUBLE_MAT3x4); - InsertEntry("dmat4x2"sv, GL_DOUBLE_MAT4x2); - InsertEntry("dmat4x3"sv, GL_DOUBLE_MAT4x3); - - InsertEntry("sampler1D"sv, GL_SAMPLER_1D); - InsertEntry("sampler2D"sv, GL_SAMPLER_2D); - InsertEntry("sampler3D"sv, GL_SAMPLER_3D); - InsertEntry("samplerCube"sv, GL_SAMPLER_CUBE); - InsertEntry("sampler1DShadow"sv, GL_SAMPLER_1D_SHADOW); - InsertEntry("sampler2DShadow"sv, GL_SAMPLER_2D_SHADOW); - InsertEntry("sampler1DArray"sv, GL_SAMPLER_1D_ARRAY); - InsertEntry("sampler2DArray"sv, GL_SAMPLER_2D_ARRAY); - InsertEntry("sampler1DArrayShadow"sv, GL_SAMPLER_1D_ARRAY_SHADOW); - InsertEntry("sampler2DArrayShadow"sv, GL_SAMPLER_2D_ARRAY_SHADOW); - InsertEntry("sampler2DMultisample"sv, GL_SAMPLER_2D_MULTISAMPLE); - InsertEntry("sampler2DMultisampleArray"sv, GL_SAMPLER_2D_MULTISAMPLE_ARRAY); - InsertEntry("samplerCubeShadow"sv, GL_SAMPLER_CUBE_SHADOW); - InsertEntry("samplerBuffer"sv, GL_SAMPLER_BUFFER); - InsertEntry("sampler2DRect"sv, GL_SAMPLER_2D_RECT); - InsertEntry("sampler2DRectShadow"sv, GL_SAMPLER_2D_RECT_SHADOW); - - InsertEntry("isampler1D"sv, GL_INT_SAMPLER_1D); - InsertEntry("isampler2D"sv, GL_INT_SAMPLER_2D); - InsertEntry("isampler3D"sv, GL_INT_SAMPLER_3D); - InsertEntry("isamplerCube"sv, GL_INT_SAMPLER_CUBE); - InsertEntry("isampler1DArray"sv, GL_INT_SAMPLER_1D_ARRAY); - InsertEntry("isampler2DArray"sv, GL_INT_SAMPLER_2D_ARRAY); - InsertEntry("isampler2DMultisample"sv, GL_INT_SAMPLER_2D_MULTISAMPLE); - InsertEntry("isampler2DMultisampleArray"sv, GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY); - InsertEntry("isamplerBuffer"sv, GL_INT_SAMPLER_BUFFER); - InsertEntry("isampler2DRect"sv, GL_INT_SAMPLER_2D_RECT); - - InsertEntry("usampler1D"sv, GL_UNSIGNED_INT_SAMPLER_1D); - InsertEntry("usampler2D"sv, GL_UNSIGNED_INT_SAMPLER_2D); - InsertEntry("usampler3D"sv, GL_UNSIGNED_INT_SAMPLER_3D); - InsertEntry("usamplerCube"sv, GL_UNSIGNED_INT_SAMPLER_CUBE); - InsertEntry("usampler1DArray"sv, GL_UNSIGNED_INT_SAMPLER_1D_ARRAY); - InsertEntry("usampler2DArray"sv, GL_UNSIGNED_INT_SAMPLER_2D_ARRAY); - InsertEntry("usampler2DMultisample"sv, GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE); - InsertEntry("usampler2DMultisampleArray"sv, GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY); - InsertEntry("usamplerBuffer"sv, GL_UNSIGNED_INT_SAMPLER_BUFFER); - InsertEntry("usampler2DRect"sv, GL_UNSIGNED_INT_SAMPLER_2D_RECT); - } - - void InsertEntry(std::string_view name, GLenum value) { - enum2Name.try_emplace(value, name); - name2Enum.try_emplace(name, value); - } -} const kGLTypeInfo; -} // namespace ProjectBrussel_UNITY_ID - -std::string_view Tags::GLTypeToString(GLenum value) { - using namespace ProjectBrussel_UNITY_ID; - auto iter = kGLTypeInfo.enum2Name.find(value); - if (iter != kGLTypeInfo.enum2Name.end()) { - return iter->second; - } else { - return std::string_view(); - } -} - -GLenum Tags::GLTypeFromString(std::string_view name) { - using namespace ProjectBrussel_UNITY_ID; - auto iter = kGLTypeInfo.name2Enum.find(name); - if (iter != kGLTypeInfo.name2Enum.end()) { - return iter->second; - } else { - return 0; - } -} - -#include <generated/GraphicsTags.gs.inl> diff --git a/source/30-game/GraphicsTags.hpp b/source/30-game/GraphicsTags.hpp deleted file mode 100644 index f9628b2..0000000 --- a/source/30-game/GraphicsTags.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include <MacrosCodegen.hpp> - -#include <glad/glad.h> -#include <limits> -#include <string> -#include <string_view> - -namespace Tags { -/// Vertex element semantics, used to identify the meaning of vertex buffer contents -enum VertexElementSemantic { - // clang-format off - BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) - // clang-format on - - /// Position, typically VET_Float3 - VES_Position, - /// Blending weights - VES_BlendWeights, - /// Blending indices - VES_BlendIndices, - /// Normal, typically VET_Float3 - VES_Normal, - /// Colour, typically VET_Ubyte4 - VES_Color1, - VES_Color2, - VES_Color3, - /// Texture coordinates, typically VET_Float2 - VES_TexCoords1, - VES_TexCoords2, - VES_TexCoords3, - /// Binormal (Y axis if normal is Z) - VES_Binormal, - /// Tangent (X axis if normal is Z) - VES_Tangent, - /// Default semantic - VES_Generic, - VES_COUNT, -}; - -enum VertexElementType { - // clang-format off - BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) - // clang-format on - - VET_Float1, - VET_Float2, - VET_Float3, - VET_Float4, - - VET_Short2, - VET_Short4, - VET_Ubyte4, - - // the following are not universally supported on all hardware: - VET_Double1, - VET_Double2, - VET_Double3, - VET_Double4, - VET_Ushort2, - VET_Ushort4, - VET_Int1, - VET_Int2, - VET_Int3, - VET_Int4, - VET_Uint1, - VET_Uint2, - VET_Uint3, - VET_Uint4, - VET_Byte4, /// signed bytes - - VET_Byte4Norm, /// signed bytes (normalized to -1..1) - VET_Ubyte4Norm, /// unsigned bytes (normalized to 0..1) - VET_Short2Norm, /// signed shorts (normalized to -1..1) - VET_Short4Norm, - VET_Ushort2Norm, /// unsigned shorts (normalized to 0..1) - VET_Ushort4Norm, -}; -constexpr auto VET_NORM_BEGIN = VET_Byte4Norm; -constexpr auto VET_NORM_END = VET_Ushort4Norm; - -int SizeOf(VertexElementType type); -int VectorLenOf(VertexElementType type); -GLenum FindGLType(VertexElementType type); -bool IsNormalized(VertexElementType type); - -enum IndexType { - IT_16Bit, - IT_32Bit, -}; - -int SizeOf(IndexType type); -GLenum FindGLType(IndexType type); - -enum TexFilter { - // clang-format off - BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) - // clang-format on - - TF_Linear, - TF_Nearest, -}; - -std::string_view GLTypeToString(GLenum); -GLenum GLTypeFromString(std::string_view name); - -constexpr auto kInvalidLocation = std::numeric_limits<GLuint>::max(); -} // namespace Tags - -#include <generated/GraphicsTags.gh.inl> diff --git a/source/30-game/Image.cpp b/source/30-game/Image.cpp deleted file mode 100644 index 3673acc..0000000 --- a/source/30-game/Image.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "Image.hpp" - -#include <stb_image.h> -#include <stb_rect_pack.h> -#include <cstring> - -Image::Image() - : mSize{} - , mChannels{ 0 } { -} - -bool Image::InitFromImageFile(const char* filePath, int desiredChannels) { - // Dimensions of the image in - int width, height; - // Number of channels that the image has, we'll get `desiredChannels` channels in our output (if it's non-0, which is the default argument) - int channels; - - // NOTE: don't free, the data is passed to std::unique_ptr - auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, desiredChannels); - if (!result) { - return false; - } - - mData.reset(result); - mSize = { width, height }; - mChannels = desiredChannels == 0 ? channels : desiredChannels; - return true; -} - -bool Image::InitFromImageData(std::span<uint8_t> data, int desiredChannels) { - int width, height; - int channels; - - // NOTE: don't free, the data is passed to std::unique_ptr - auto result = (uint8_t*)stbi_load_from_memory(data.data(), data.size(), &width, &height, &channels, desiredChannels); - if (!result) { - return false; - } - - mData.reset(result); - mSize = { width, height }; - mChannels = desiredChannels == 0 ? channels : desiredChannels; - return true; -} - -bool Image::InitFromPixels(std::span<uint8_t> pixels, glm::ivec2 dimensions, int channels) { - mData = std::make_unique<uint8_t[]>(pixels.size()); - std::memcpy(mData.get(), pixels.data(), pixels.size()); - mSize = dimensions; - mChannels = channels; - return true; -} - -bool Image::InitFromPixels(std::unique_ptr<uint8_t[]> pixels, glm::ivec2 dimensions, int channels) { - mData = std::move(pixels); - mSize = dimensions; - mChannels = channels; - return true; -} - -RgbaColor Image::GetPixel(int x, int y) const { - size_t offset = (y * mSize.x + x) * mChannels; - RgbaColor color; - color.r = mData.get()[offset + 0]; - color.g = mData.get()[offset + 1]; - color.b = mData.get()[offset + 2]; - color.a = mData.get()[offset + 3]; - return color; -} - -void Image::SetPixel(int x, int y, RgbaColor color) { - size_t offset = (y * mSize.x + x) * mChannels; - mData.get()[offset + 0] = color.r; - mData.get()[offset + 1] = color.g; - mData.get()[offset + 2] = color.b; - mData.get()[offset + 3] = color.a; -} - -uint8_t* Image::GetDataPtr() const { - return mData.get(); -} - -size_t Image::GetDataLength() const { - return mSize.x * mSize.y * mChannels * sizeof(uint8_t); -} - -std::span<uint8_t> Image::GetData() const { - return { mData.get(), GetDataLength() }; -} - -glm::ivec2 Image::GetSize() const { - return mSize; -} - -int Image::GetChannels() const { - return mChannels; -} - -bool Image::IsEmpty() const { - return mSize.x == 0 || mSize.y == 0; -} diff --git a/source/30-game/Image.hpp b/source/30-game/Image.hpp deleted file mode 100644 index c577c24..0000000 --- a/source/30-game/Image.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "Color.hpp" -#include "RcPtr.hpp" - -#include <cstdint> -#include <glm/glm.hpp> -#include <memory> -#include <span> - -/// Image is a 2d array of pixels, stored as a continuous array in memory, with the first pixel -/// being the top-left pixel. If a vertically flipped image data is needed, load using stb_image -/// yourself, or flip the data here. -class Image : public RefCounted { -private: - std::unique_ptr<uint8_t[]> mData; - glm::ivec2 mSize; - int mChannels; - -public: - Image(); - - bool InitFromImageFile(const char* filePath, int desiredChannels = 0); - bool InitFromImageData(std::span<uint8_t> data, int desiredChannels = 0); - bool InitFromPixels(std::span<uint8_t> pixels, glm::ivec2 dimensions, int channels); - bool InitFromPixels(std::unique_ptr<uint8_t[]> pixels, glm::ivec2 dimensions, int channels); - - /// Get the pixel at the given location. - RgbaColor GetPixel(int x, int y) const; - void SetPixel(int x, int y, RgbaColor color); - - uint8_t* GetDataPtr() const; - size_t GetDataLength() const; - std::span<uint8_t> GetData() const; - glm::ivec2 GetSize() const; - int GetChannels() const; - bool IsEmpty() const; -}; diff --git a/source/30-game/Input.cpp b/source/30-game/Input.cpp deleted file mode 100644 index 9f304ff..0000000 --- a/source/30-game/Input.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "Input.hpp" - -#include <cassert> - -GLFWkeyboard* InputState::FindKeyboard(std::string_view name) { - assert(false); - // TODO -} - -GlfwKeyboardAttachment* InputState::ConnectKeyboard(GLFWkeyboard* keyboard) { - auto attachment = new GlfwKeyboardAttachment(); - glfwSetKeyboardUserPointer(keyboard, attachment); - - return attachment; -} - -void InputState::DisconnectKeyboard(GLFWkeyboard* keyboard) { - InputState::instance->DisconnectKeyboard(keyboard); - auto attachment = static_cast<GlfwKeyboardAttachment*>(glfwGetKeyboardUserPointer(keyboard)); - // Defensive: this GLFWkeyboard* will be deleted after this callback ends anyways - glfwSetKeyboardUserPointer(keyboard, nullptr); - delete attachment; -} diff --git a/source/30-game/Input.hpp b/source/30-game/Input.hpp deleted file mode 100644 index feb50f0..0000000 --- a/source/30-game/Input.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#define GLFW_INCLUDE_NONE -#include <GLFW/glfw3.h> - -#include <string_view> -#include <utility> -#include <vector> - -/// Extra data attached to a GLFWkeyboard object -struct GlfwKeyboardAttachment { -}; - -class InputState { -public: - static inline InputState* instance = nullptr; - -public: - GLFWkeyboard* FindKeyboard(std::string_view name); - - GlfwKeyboardAttachment* ConnectKeyboard(GLFWkeyboard* keyboard); - void DisconnectKeyboard(GLFWkeyboard* keyboard); -}; diff --git a/source/30-game/Ires.cpp b/source/30-game/Ires.cpp deleted file mode 100644 index 0529395..0000000 --- a/source/30-game/Ires.cpp +++ /dev/null @@ -1,436 +0,0 @@ -#include "Ires.hpp" - -#include "AppConfig.hpp" -#include "EditorCore.hpp" -#include "EditorUtils.hpp" -#include "Material.hpp" -#include "Shader.hpp" -#include "Sprite.hpp" -#include "Texture.hpp" - -#include <Macros.hpp> -#include <Metadata.hpp> -#include <RapidJsonHelper.hpp> -#include <ScopeGuard.hpp> -#include <Utils.hpp> - -#include <imgui.h> -#include <misc/cpp/imgui_stdlib.h> -#include <rapidjson/document.h> -#include <rapidjson/filereadstream.h> -#include <rapidjson/filewritestream.h> -#include <rapidjson/prettywriter.h> -#include <rapidjson/writer.h> -#include <algorithm> -#include <cassert> - -namespace fs = std::filesystem; -using namespace std::literals; - -IresObject::IresObject(Kind kind) - : mKind{ kind } { -} - -std::unique_ptr<IresObject> IresObject::Create(Kind kind) { - switch (kind) { - case KD_Texture: return std::make_unique<IresTexture>(); - case KD_Shader: return std::make_unique<IresShader>(); - case KD_Material: return std::make_unique<IresMaterial>(); - case KD_SpriteFiles: return std::make_unique<IresSpriteFiles>(); - case KD_Spritesheet: return std::make_unique<IresSpritesheet>(); - case KD_COUNT: break; - } - return nullptr; -} - -bool IresObject::IsAnnoymous() const { - return mName.empty(); -} - -void IresObject::SetName(std::string name) { - if (mMan) { - mMan->Rename(this, std::move(name)); - } else { - mName = std::move(name); - } -} - -void IresObject::ShowNameSafe(IresObject* ires) { - if (ires) { - ires->ShowName(); - } else { - ShowNameNull(); - } -} - -void IresObject::ShowNameNull() { - ImGui::Text("<null>"); -} - -void IresObject::ShowName() const { - if (IsAnnoymous()) { - ImGui::Text("<annoymous %p>", (void*)this); - } else { - ImGui::Text("%s", mName.c_str()); - } -} - -void IresObject::ShowReferenceSafe(IEditor& editor, IresObject* ires) { - if (ires) { - ires->ShowReference(editor); - } else { - ShowReferenceNull(editor); - } -} - -void IresObject::ShowReferenceNull(IEditor& editor) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); - ImGui::Text("<null>"); - ImGui::PopStyleColor(); - ImGui::AddUnderLine(ImGui::GetStyle().Colors[ImGuiCol_Button]); -} - -void IresObject::ShowReference(IEditor& editor) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); - if (IsAnnoymous()) { - ImGui::Text("<annoymous %p>", (void*)this); - } else { - ImGui::Text("%s", mName.c_str()); - } - ImGui::PopStyleColor(); - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - editor.GetInspector().SelectTarget(IEditorInspector::ITT_Ires, this); - } - ImGui::AddUnderLine(ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); - } else { - ImGui::AddUnderLine(ImGui::GetStyle().Colors[ImGuiCol_Button]); - } -} - -void IresObject::ShowEditor(IEditor& editor) { - ImGui::Text("%.*s", PRINTF_STRING_VIEW(Metadata::EnumToString(mKind))); - - bool isAnnoymous = mName.empty(); - if (isAnnoymous) { - ImGui::Text("Name: <annoymous: %p>", (void*)this); - } else { - ImGui::Text("Name: %s", mName.c_str()); - } - - if (mUid.IsNull()) { - ImGui::TextUnformatted("Uid: <none>"); - } else { - ImGui::Text("Uid: %lx-%lx", mUid.upper, mUid.lower); - } -} - -void IresObject::WriteFull(IresWritingContext& ctx, IresObject* ires, rapidjson::Value& value, rapidjson::Document& root) { - rapidjson::Value rvIres(rapidjson::kObjectType); - ires->Write(ctx, rvIres, root); - - value.AddMember("Type", rapidjson::StringRef(Metadata::EnumToString(ires->GetKind())), root.GetAllocator()); - value.AddMember("Uid", ires->mUid.Write(root), root.GetAllocator()); - value.AddMember("Value", rvIres, root.GetAllocator()); -} - -std::unique_ptr<IresObject> IresObject::ReadFull(IresLoadingContext& ctx, const rapidjson::Value& value) { - auto ires = ReadBasic(value); - if (!ires) { - return nullptr; - } - - if (!ReadPartial(ctx, ires.get(), value)) { - return nullptr; - } - - return ires; -} - -std::unique_ptr<IresObject> IresObject::ReadBasic(const rapidjson::Value& value) { - std::unique_ptr<IresObject> ires; - Uid uid; - for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { - if (it->name == "Type"_rj_sv) { - if (it->value.GetType() != rapidjson::kStringType) - return nullptr; - auto kind = Metadata::EnumFromString<Kind>(rapidjson::AsStringView(it->value)); - if (!kind.has_value()) - return nullptr; - if ((ires = Create(*kind)) == nullptr) - return nullptr; - } else if (it->name == "Uid"_rj_sv) { - // FIXME this needs to do type checks - uid.Read(it->value); - } - } - if (ires == nullptr || uid.IsNull()) - return nullptr; - - ires->mUid = uid; - return ires; -} - -bool IresObject::ReadPartial(IresLoadingContext& ctx, IresObject* ires, const rapidjson::Value& value) { - auto rvValue = rapidjson::GetProperty(value, "Value"sv); - if (!rvValue) return false; - ires->Read(ctx, *rvValue); - return true; -} - -void IresObject::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { -} - -void IresObject::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { -} - -void IresManager::DiscoverFilesDesignatedLocation() { - auto path = AppConfig::assetDirPath / "Ires"; - DiscoverFiles(path); -} - -void IresManager::DiscoverFiles(const fs::path& dir) { - struct LoadCandidate { - fs::path path; - rapidjson::Document data; - RcPtr<IresObject> ires; - }; - - class IresLoadTimeContext final : public IresLoadingContext { - public: - // NOTE: pointer stability required - robin_hood::unordered_node_map<Uid, LoadCandidate> candidates; - std::vector<std::vector<LoadCandidate*>> candidatesByKind; - - IresLoadTimeContext() { - candidatesByKind.resize((int)IresObject::KD_COUNT); - } - - std::vector<LoadCandidate*>& GetByKind(IresObject::Kind kind) { - int i = static_cast<int>(kind); - return candidatesByKind[i]; - } - - virtual IresObject* FindIres(const Uid& uid) const override { - auto iter = candidates.find(uid); - if (iter != candidates.end()) { - auto& cand = iter->second; - return cand.ires.Get(); - } else { - return nullptr; - } - } - } ctx; - - for (auto& item : fs::directory_iterator(dir)) { - if (!item.is_regular_file()) { - continue; - } - if (item.path().extension() != ".json") { - continue; - } - - auto file = Utils::OpenCstdioFile(item.path(), Utils::Read); - if (!file) { - fprintf(stderr, "Ires file [" PLATFORM_PATH_STR "] Failed to open file.", item.path().c_str()); - continue; - } - DEFER { - fclose(file); - }; - - char readerBuffer[65536]; - rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); - - rapidjson::Document root; - root.ParseStream(stream); - - auto ires = IresObject::ReadBasic(root); - if (!ires) { - fprintf(stderr, "Ires file: [" PLATFORM_PATH_STR "] Failed to parse header.\n", item.path().c_str()); - continue; - } - - // Load name from filename - ires->mName = item.path().filename().replace_extension().string(); - auto& iresName = ires->mName; - - // Load uid should be handled by IresObject::ReadBasic - assert(!ires->mUid.IsNull()); - auto iresUid = ires->GetUid(); - - auto iresKind = ires->GetKind(); - - auto&& [iter, DISCARD] = ctx.candidates.try_emplace( - iresUid, - LoadCandidate{ - .path = item.path(), - .data = std::move(root), - .ires = RcPtr(ires.release()), - }); - auto& cand = iter->second; - - ctx.GetByKind(iresKind).push_back(&cand); - } - - // Load Ires in order by type, there are dependencies between them - // TODO full arbitary dependency between Ires - for (int i = 0; i < (int)IresObject::KD_COUNT; ++i) { - auto kind = static_cast<IresObject::Kind>(i); - auto& list = ctx.GetByKind(kind); - for (auto cand : list) { - auto& ires = cand->ires; - - if (!IresObject::ReadPartial(ctx, ires.Get(), cand->data)) { - fprintf(stderr, "Ires file: [" PLATFORM_PATH_STR "] Failed to parse object data.\n", cand->path.c_str()); - continue; - } - - ires->mMan = this; - mObjByUid.try_emplace(ires->GetUid(), ires); - } - } -} - -std::pair<IresObject*, bool> IresManager::Add(IresObject* ires) { - auto& name = ires->mName; - if (name.empty()) { - name = Utils::MakeRandomNumberedName(Metadata::EnumToString(ires->GetKind()).data()); - } - - auto& uid = ires->mUid; - if (uid.IsNull()) { - uid = Uid::Create(); - } - - auto [iter, inserted] = mObjByUid.try_emplace(uid, ires); - if (inserted) { - ires->mMan = this; - // TODO handle full path - return { ires, true }; - } else { - return { iter->second.Get(), false }; - } -} - -IresObject* IresManager::Load(const fs::path& filePath) { - auto file = Utils::OpenCstdioFile(filePath, Utils::Read); - if (!file) return nullptr; - DEFER { - fclose(file); - }; - - char readerBuffer[65536]; - rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); - - rapidjson::Document root; - root.ParseStream(stream); - - auto ires = IresObject::ReadFull(*this, root); - if (!ires) { - return nullptr; - } - - // Load uid should be handled by IresObject::ReadFull - assert(!ires->mUid.IsNull()); - // Load name from filename - ires->mName = filePath.filename().replace_extension().string(); - Add(ires.get()); - - return ires.release(); -} - -static fs::path GetDesignatedPath(IresObject* ires) { - return AppConfig::assetDirPath / "Ires" / fs::path(ires->GetName()).replace_extension(".json"); -} - -void IresManager::Delete(IresObject* ires) { - // TODO -} - -bool IresManager::Rename(IresObject* ires, std::string newName) { - auto oldPath = GetDesignatedPath(ires); - ires->mName = std::move(newName); - auto newPath = GetDesignatedPath(ires); - if (fs::exists(oldPath)) { - fs::rename(oldPath, newPath); - } - - // TODO validate no name duplication -#if 0 - if (mObjByPath.contains(newName)) { - return false; - } - - // Keep the material from being deleted, in case the old entry in map is the only one existing - RcPtr rc(ires); - - // Remove old entry (must do before replacing Material::mName, because the std::string_view in the map is a reference to it) - mObjByPath.erase(ires->GetName()); - - // Add new entry - ires->mName = std::move(newName); - // TODO handle full path - mObjByPath.try_emplace(ires->GetName(), ires); -#endif - return true; -} - -void IresManager::Reload(IresObject* ires) { - auto file = Utils::OpenCstdioFile(GetDesignatedPath(ires), Utils::Read); - if (!file) return; - DEFER { - fclose(file); - }; - - char readerBuffer[65536]; - rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); - - rapidjson::Document root; - root.ParseStream(stream); - - IresObject::ReadPartial(*this, ires, root); -} - -void IresManager::Save(IresObject* ires) { - Save(ires, GetDesignatedPath(ires)); -} - -void IresManager::Save(IresObject* ires, const fs::path& filePath) { - rapidjson::Document root(rapidjson::kObjectType); - - IresObject::WriteFull(*this, ires, root, root); - - auto file = Utils::OpenCstdioFile(filePath, Utils::WriteTruncate); - if (!file) return; - DEFER { - fclose(file); - }; - - char writerBuffer[65536]; - rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); - rapidjson::PrettyWriter<rapidjson::FileWriteStream> writer(stream); - // We no longer need this after disabling BRUSSEL_Uid_WRITE_USE_ARRAY -// writer.SetFormatOptions(rapidjson::PrettyFormatOptions::kFormatSingleLineArray); -#if BRUSSEL_Uid_WRITE_USE_ARRAY -# warning "Writing Uid in array format but single line formatting isn't enabled, this might cause excessively long ires files." -#endif - root.Accept(writer); -} - -void IresManager::OverwriteAllToDisk() { - for (const auto& [DISCARD, ires] : mObjByUid) { - Save(ires.Get()); - } -} - -IresObject* IresManager::FindIres(const Uid& uid) const { - auto iter = mObjByUid.find(uid); - if (iter != mObjByUid.end()) { - return iter->second.Get(); - } else { - return nullptr; - } -} - -#include <generated/Ires.gs.inl> diff --git a/source/30-game/Ires.hpp b/source/30-game/Ires.hpp deleted file mode 100644 index e2e79bd..0000000 --- a/source/30-game/Ires.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once - -#include "EditorAttachment.hpp" -#include "EditorCore.hpp" - -#include <MacrosCodegen.hpp> -#include <RcPtr.hpp> -#include <Uid.hpp> -#include <Utils.hpp> - -#include <rapidjson/fwd.h> -#include <robin_hood.h> -#include <filesystem> -#include <memory> -#include <string_view> - -// Forward declarations -class IresManager; -class IresWritingContext; -class IresLoadingContext; - -namespace Tags { -enum class IresObjectKind { - // clang-format off - BRUSSEL_ENUM(ToString, FromString, RemovePrefix KD_, AddPrefix Ires, ExcludeHeuristics) - // clang-format on - - KD_Texture, - KD_Shader, - KD_Material, - KD_SpriteFiles, - KD_Spritesheet, - KD_COUNT, -}; -} // namespace Tags - -class IresObject : public RefCounted { - friend class IresManager; - -public: - using Kind = Tags::IresObjectKind; - using enum Tags::IresObjectKind; - -private: - std::string mName; // Serialized as filename - Uid mUid; // Serialized in full mode - std::unique_ptr<EditorAttachment> mEditorAttachment; // Transient - IresManager* mMan = nullptr; // Transient - Kind mKind; // Serialized in full mode - -public: - IresObject(Kind kind); - virtual ~IresObject() = default; - - static std::unique_ptr<IresObject> Create(Kind kind); - Kind GetKind() const { return mKind; } - - IresManager* GetAssociatedManager() const { return mMan; } - bool IsAnnoymous() const; - const std::string& GetName() const { return mName; } - void SetName(std::string name); - const Uid& GetUid() const { return mUid; } - - static void ShowNameSafe(IresObject* ires); - static void ShowNameNull(); - void ShowName() const; - - static void ShowReferenceSafe(IEditor& editor, IresObject* ires); - static void ShowReferenceNull(IEditor& editor); - void ShowReference(IEditor& editor); - - virtual void ShowEditor(IEditor& editor); - - EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } - void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } - - static void WriteFull(IresWritingContext& ctx, IresObject* ires, rapidjson::Value& value, rapidjson::Document& root); - - /// Sequentially call ReadBasic() and then ReadPartial() - static std::unique_ptr<IresObject> ReadFull(IresLoadingContext& ctx, const rapidjson::Value& value); - /// Reads the type and UID of the object from data, and return a newly constructed Ires object. - static std::unique_ptr<IresObject> ReadBasic(const rapidjson::Value& value); - /// Reads all object-speific data from the root-level data object into the given Ires object. - static bool ReadPartial(IresLoadingContext& ctx, IresObject* ires, const rapidjson::Value& value); - - virtual void Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const; - virtual void Read(IresLoadingContext& ctx, const rapidjson::Value& value); - -protected: - rapidjson::Value WriteIresRef(IresObject* object); -}; - -class IresWritingContext { -public: - virtual ~IresWritingContext() = default; -}; - -class IresLoadingContext { -public: - virtual ~IresLoadingContext() = default; - virtual IresObject* FindIres(const Uid& uid) const = 0; -}; - -class IresManager final : public IresWritingContext, public IresLoadingContext { -public: - static inline IresManager* instance = nullptr; - -private: - robin_hood::unordered_map<Uid, RcPtr<IresObject>> mObjByUid; - -public: - void DiscoverFilesDesignatedLocation(); - void DiscoverFiles(const std::filesystem::path& dir); - - std::pair<IresObject*, bool> Add(IresObject* mat); - IresObject* Load(const std::filesystem::path& filePath); - void Delete(IresObject* ires); - bool Rename(IresObject* ires, std::string newName); - - void Reload(IresObject* ires); - void Save(IresObject* ires); - void Save(IresObject* ires, const std::filesystem::path& filePath); - - void OverwriteAllToDisk(); - - const auto& GetObjects() const { return mObjByUid; } - virtual IresObject* FindIres(const Uid& uid) const override; -}; - -#include <generated/Ires.gh.inl> diff --git a/source/30-game/Level.cpp b/source/30-game/Level.cpp deleted file mode 100644 index 076e5d5..0000000 --- a/source/30-game/Level.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "Level.hpp" - -#include "AppConfig.hpp" - -#include <PodVector.hpp> -#include <RapidJsonHelper.hpp> -#include <ScopeGuard.hpp> -#include <Utils.hpp> - -#include <imgui.h> -#include <rapidjson/document.h> -#include <rapidjson/filereadstream.h> -#include <rapidjson/filewritestream.h> -#include <rapidjson/prettywriter.h> -#include <cstdio> -#include <filesystem> - -using namespace std::literals; -namespace fs = std::filesystem; - -constexpr auto kParentToRootObject = std::numeric_limits<size_t>::max(); -constexpr auto kInvalidEntryId = std::numeric_limits<size_t>::max(); - -struct Level::InstanciationEntry { - // If set to std::numeric_limits<size_t>::max(), this object is parented to the "root" provided when instanciating - size_t parentId; - rapidjson::Document data; -}; - -Level::Level() = default; - -Level::~Level() = default; - -void Level::Instanciate(GameObject* relRoot) const { - auto objectsLut = std::make_unique<GameObject*[]>(mEntries.size()); - for (auto& entry : mEntries) { - GameObject* parent; - if (entry.parentId == kParentToRootObject) { - parent = relRoot; - } else { - parent = objectsLut[entry.parentId]; - } - - // TODO deser object - } -} - -void Level::ShowInstanciationEntries(IEditor& editor) { - for (auto& entry : mEntries) { - // TODO - } -} - -void LevelManager::DiscoverFilesDesignatedLocation() { - auto path = AppConfig::assetDirPath / "Levels"; - DiscoverFiles(path); -} - -void LevelManager::DiscoverFiles(const std::filesystem::path& dir) { - for (auto& item : fs::directory_iterator(dir)) { - auto& path = item.path(); - if (!item.is_regular_file()) { - continue; - } - if (path.extension() != ".json") { - continue; - } - - // Parse uid from filename, map key - Uid uid; - uid.ReadString(path.filename().string()); - - // Map value - LoadableObject obj; - obj.filePath = path; - - mObjByUid.try_emplace(uid, std::move(obj)); - } -} - -Level* LevelManager::FindLevel(const Uid& uid) const { - auto iter = mObjByUid.find(uid); - if (iter != mObjByUid.end()) { - return iter->second.level.Get(); - } else { - return nullptr; - } -} - -#define BRUSSEL_DEF_LEVEL_NAME "New Level" -#define BRUSSEL_DEF_LEVEL_DESC "No description." - -Level* LevelManager::LoadLevel(const Uid& uid) { - auto iter = mObjByUid.find(uid); - if (iter != mObjByUid.end()) { - auto& ldObj = iter->second; - if (ldObj.level != nullptr) { - auto file = Utils::OpenCstdioFile(ldObj.filePath, Utils::Read, false); - if (!file) { - fprintf(stderr, "Cannot open file level file %s that was discovered on game startup.", ldObj.filePath.string().c_str()); - return nullptr; - } - DEFER { fclose(file); }; - - char readerBuffer[65536]; - rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); - - rapidjson::Document root; - root.ParseStream(stream); - - Level* level; - ldObj.level.Attach(level = new Level()); - - level->mMan = this; - level->mUid = uid; - -#if defined(BRUSSEL_DEV_ENV) - BRUSSEL_JSON_GET_DEFAULT(root, "Name", std::string, ldObj.name, BRUSSEL_DEF_LEVEL_NAME); - BRUSSEL_JSON_GET_DEFAULT(root, "Description", std::string, ldObj.description, BRUSSEL_DEF_LEVEL_DESC) -#endif - - auto rvEntries = rapidjson::GetProperty(root, rapidjson::kArrayType, "DataEntries"sv); - if (!rvEntries) return nullptr; - for (auto iter = rvEntries->Begin(); iter != rvEntries->End(); ++iter) { - Level::InstanciationEntry entry; - - BRUSSEL_JSON_GET_DEFAULT(*iter, "ParentId", int, entry.parentId, kInvalidEntryId); - - auto rvDataEntry = rapidjson::GetProperty(*iter, "Data"sv); - if (!rvDataEntry) return nullptr; - entry.data.CopyFrom(*iter, entry.data.GetAllocator()); - - level->mEntries.push_back(std::move(entry)); - } - } - return ldObj.level.Get(); - } else { - return nullptr; - } -} - -void LevelManager::PrepareLevel(const Uid& uid) { - // TODO -} - -LevelManager::LoadableObject& LevelManager::AddLevel(const Uid& uid) { - auto&& [iter, inserted] = mObjByUid.try_emplace(uid); - auto& ldObj = iter->second; - ldObj.level->mUid = uid; -#if defined(BRUSSEL_DEV_ENV) - ldObj.name = BRUSSEL_DEF_LEVEL_NAME; - ldObj.description = BRUSSEL_DEF_LEVEL_DESC; -#endif - return ldObj; -} - -void LevelManager::SaveLevel(const Uid& uid) const { - auto iter = mObjByUid.find(uid); - if (iter == mObjByUid.end()) return; - auto& obj = iter->second; - - SaveLevelImpl(obj, obj.filePath); -} - -void LevelManager::SaveLevel(const Uid& uid, const std::filesystem::path& path) const { - auto iter = mObjByUid.find(uid); - if (iter == mObjByUid.end()) return; - auto& obj = iter->second; - - SaveLevelImpl(obj, path); -} - -void LevelManager::SaveLevelImpl(const LoadableObject& obj, const std::filesystem::path& path) const { - rapidjson::Document root; - - // TODO - - auto file = Utils::OpenCstdioFile(path, Utils::WriteTruncate); - if (!file) return; - DEFER { fclose(file); }; - - char writerBuffer[65536]; - rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); - rapidjson::Writer<rapidjson::FileWriteStream> writer(stream); - root.Accept(writer); -} - -LevelWrapperObject::LevelWrapperObject(GameWorld* world) - : GameObject(KD_LevelWrapper, world) // -{ - mStopFreePropagation = true; -} - -LevelWrapperObject::~LevelWrapperObject() { - // Destruction/freeing of this object is handled by our parent - for (auto child : GetChildren()) { - FreeRecursive(child); - } -} - -void LevelWrapperObject::SetBoundLevel(Level* level) { - if (mLevel != level) { - mLevel.Attach(level); - - // Cleanup old children - // TODO needs to Resleep()? - auto children = RemoveAllChildren(); - for (auto child : children) { - FreeRecursive(child); - } - } - - level->Instanciate(this); - - PodVector<GameObject*> stack; - stack.push_back(this); - - while (!stack.empty()) { - auto obj = stack.back(); - stack.pop_back(); - - for (auto child : obj->GetChildren()) { - stack.push_back(child); - } - - obj->Awaken(); - } -} diff --git a/source/30-game/Level.hpp b/source/30-game/Level.hpp deleted file mode 100644 index c030b8e..0000000 --- a/source/30-game/Level.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include "EditorCore.hpp" -#include "GameObject.hpp" - -#include <MacrosCodegen.hpp> -#include <RcPtr.hpp> -#include <Uid.hpp> - -#include <robin_hood.h> -#include <filesystem> -#include <vector> - -// Forward declarations -class Level; -class LevelManager; - -/// Represents a seralized GameObject tree. -class Level : public RefCounted { - friend class LevelManager; - -private: - struct InstanciationEntry; - - LevelManager* mMan; - Uid mUid; - std::vector<InstanciationEntry> mEntries; - -public: - Level(); - ~Level(); - - void Instanciate(GameObject* relRoot) const; - - LevelManager* GetLinkedLevelManager() const { return mMan; } - const Uid& GetUid() const { return mUid; } - - // Editor stuff - void ShowInstanciationEntries(IEditor& editor); -}; - -class LevelManager { -public: - static inline LevelManager* instance = nullptr; - -public: // NOTE: public for the editor; actual game components should not modify the map using this - // TODO maybe cut this struct to only the first RcPtr<Level> field in release mode? - struct LoadableObject { - RcPtr<Level> level; // TODO make weak pointer - std::filesystem::path filePath; - // NOTE: these fields are only loaded in dev mode - std::string name; - std::string description; - - // Editor book keeping fields - bool edited = false; - }; - // We want pointer stability here for the editor (inspector object) - robin_hood::unordered_node_map<Uid, LoadableObject> mObjByUid; - -public: - void DiscoverFilesDesignatedLocation(); - void DiscoverFiles(const std::filesystem::path& dir); - - Level* FindLevel(const Uid& uid) const; - /// Get or load the given level - Level* LoadLevel(const Uid& uid); - /// Send the given level to be loaded on another thread - void PrepareLevel(const Uid& uid); - - /// Create and add a new level object with the given uid. - /// Should only be used by the editor. - LoadableObject& AddLevel(const Uid& uid); - /// Should only be used by the editor. - void SaveLevel(const Uid& uid) const; - /// Should only be used by the editor. - void SaveLevel(const Uid& uid, const std::filesystem::path& path) const; - -private: - void SaveLevelImpl(const LoadableObject& obj, const std::filesystem::path& path) const; -}; - -class LevelWrapperObject : public GameObject { - BRUSSEL_CLASS() - -private: - RcPtr<Level> mLevel; - -public: - LevelWrapperObject(GameWorld* world); - ~LevelWrapperObject() override; - - Level* GetBoundLevel() const; - void SetBoundLevel(Level* level); -}; diff --git a/source/30-game/Material.cpp b/source/30-game/Material.cpp deleted file mode 100644 index 4443ae5..0000000 --- a/source/30-game/Material.cpp +++ /dev/null @@ -1,526 +0,0 @@ -#include "Material.hpp" - -#include "AppConfig.hpp" -#include "EditorCore.hpp" -#include "EditorUtils.hpp" - -#include <Metadata.hpp> -#include <RapidJsonHelper.hpp> -#include <ScopeGuard.hpp> -#include <Utils.hpp> - -#include <imgui.h> -#include <rapidjson/document.h> -#include <cstdlib> -#include <cstring> -#include <utility> - -using namespace std::literals; - -Material::Material() { -} - -namespace ProjectBrussel_UNITY_ID { -bool TryFindShaderId(Shader* shader, std::string_view name, int& out) { - auto& info = shader->GetInfo(); - auto iter = info.things.find(name); - if (iter == info.things.end()) return false; - auto& id = iter->second; - - if (id.kind != ShaderThingId::KD_Uniform) return false; - - out = id.index; - return true; -} - -template <typename TUniform> -TUniform& ObtainUniform(Shader* shader, const char* name, std::vector<TUniform>& uniforms, GLint location) { - for (auto& uniform : uniforms) { - if (uniform.location == location) { - return uniform; - } - } - - auto& uniform = uniforms.emplace_back(); - uniform.location = location; - if (!TryFindShaderId(shader, name, uniform.infoUniformIndex)) { - uniform.infoUniformIndex = -1; - } - - return uniform; -} - -rapidjson::Value MakeVectorJson(const Material::VectorUniform& vector, rapidjson::Document& root) { - int len = vector.actualLength; - - rapidjson::Value result(rapidjson::kArrayType); - result.Reserve(len, root.GetAllocator()); - - for (int i = 0; i < len; ++i) { - result.PushBack(vector.value[i], root.GetAllocator()); - } - - return result; -} - -Material::VectorUniform ReadVectorFromJson(const rapidjson::Value& rv) { - assert(rv.IsArray()); - Material::VectorUniform result; - int len = result.actualLength = rv.Size(); - for (int i = 0; i < len; ++i) { - result.value[i] = rv[i].GetFloat(); - } - return result; -} - -rapidjson::Value MakeMatrixJson(const Material::MatrixUniform& matrix, rapidjson::Document& root) { - int w = matrix.actualWidth; - int h = matrix.actualHeight; - - rapidjson::Value result(rapidjson::kArrayType); - result.Reserve(h, root.GetAllocator()); - - for (int y = 0; y < h; ++y) { - rapidjson::Value row(rapidjson::kArrayType); - row.Reserve(w, root.GetAllocator()); - - for (int x = 0; x < w; ++x) { - // Each item in a column is consecutive in memory in glm::mat<> structs - row.PushBack(matrix.value[x * h + y], root.GetAllocator()); - } - - result.PushBack(row, root.GetAllocator()); - } - - return result; -} - -Material::MatrixUniform ReadMatrixFromjson(const rapidjson::Value& rv) { - assert(rv.IsArray()); - assert(rv.Size() > 0); - assert(rv[0].IsArray()); - Material::MatrixUniform result; - int w = result.actualWidth = rv[0].Size(); - int h = result.actualHeight = rv.Size(); - for (int y = 0; y < h; ++y) { - auto& row = rv[y]; - assert(row.IsArray()); - assert(row.Size() == w); - for (int x = 0; x < w; ++x) { - auto& val = row[x]; - assert(val.IsNumber()); - result.value[x * h + y] = val.GetFloat(); - } - } - return result; -} -} // namespace ProjectBrussel_UNITY_ID - -void Material::SetFloat(const char* name, float value) { - assert(IsValid()); - - GLint location = glGetUniformLocation(mShader->GetProgram(), name); - auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundScalars, location); - uniform.floatValue = value; - uniform.actualType = GL_FLOAT; -} - -void Material::SetInt(const char* name, int32_t value) { - assert(IsValid()); - - GLint location = glGetUniformLocation(mShader->GetProgram(), name); - auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundScalars, location); - uniform.intValue = value; - uniform.actualType = GL_INT; -} - -void Material::SetUInt(const char* name, uint32_t value) { - assert(IsValid()); - - GLint location = glGetUniformLocation(mShader->GetProgram(), name); - auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundScalars, location); - uniform.uintValue = value; - uniform.actualType = GL_UNSIGNED_INT; -} - -template <int length> -void Material::SetVector(const char* name, const glm::vec<length, float>& vec) { - assert(IsValid()); - - static_assert(length >= 1 && length <= 4); - - GLint location = glGetUniformLocation(mShader->GetProgram(), name); - auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundVectors, location); - uniform.actualLength = length; - std::memset(uniform.value, 0, sizeof(uniform.value)); - std::memcpy(uniform.value, &vec[0], length * sizeof(float)); -} - -template void Material::SetVector<1>(const char*, const glm::vec<1, float>&); -template void Material::SetVector<2>(const char*, const glm::vec<2, float>&); -template void Material::SetVector<3>(const char*, const glm::vec<3, float>&); -template void Material::SetVector<4>(const char*, const glm::vec<4, float>&); - -template <int width, int height> -void Material::SetMatrix(const char* name, const glm::mat<width, height, float>& mat) { - static_assert(width >= 1 && width <= 4); - static_assert(height >= 1 && height <= 4); - - GLint location = glGetUniformLocation(mShader->GetProgram(), name); - auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundMatrices, location); - uniform.actualWidth = width; - uniform.actualHeight = height; - std::memset(uniform.value, 0, sizeof(uniform.value)); - std::memcpy(uniform.value, &mat[0][0], width * height * sizeof(float)); -} - -template void Material::SetMatrix<2, 2>(const char*, const glm::mat<2, 2, float>&); -template void Material::SetMatrix<3, 3>(const char*, const glm::mat<3, 3, float>&); -template void Material::SetMatrix<4, 4>(const char*, const glm::mat<4, 4, float>&); - -template void Material::SetMatrix<2, 3>(const char*, const glm::mat<2, 3, float>&); -template void Material::SetMatrix<3, 2>(const char*, const glm::mat<3, 2, float>&); - -template void Material::SetMatrix<2, 4>(const char*, const glm::mat<2, 4, float>&); -template void Material::SetMatrix<4, 2>(const char*, const glm::mat<4, 2, float>&); - -template void Material::SetMatrix<3, 4>(const char*, const glm::mat<3, 4, float>&); -template void Material::SetMatrix<4, 3>(const char*, const glm::mat<4, 3, float>&); - -void Material::SetTexture(const char* name, Texture* texture) { - assert(IsValid()); - - GLint location = glGetUniformLocation(mShader->GetProgram(), name); - - for (auto& uniform : mBoundTextures) { - if (uniform.location == location) { - uniform.value.Attach(texture); - return; - } - } - - auto& uniform = mBoundTextures.emplace_back(); - uniform.value.Attach(texture); - uniform.location = location; -} - -std::span<const Material::VectorUniform> Material::GetVectors() const { - return mBoundVectors; -} - -std::span<const Material::MatrixUniform> Material::GetMatrices() const { - return mBoundMatrices; -} - -std::span<const Material::TextureUniform> Material::GetTextures() const { - return mBoundTextures; -} - -Shader* Material::GetShader() const { - return mShader.Get(); -} - -void Material::SetShader(Shader* shader) { - mShader.Attach(shader); - auto& info = shader->GetInfo(); - - mBoundScalars.clear(); - mBoundVectors.clear(); - mBoundMatrices.clear(); - mBoundTextures.clear(); - for (int i = 0; i < info.uniforms.size(); ++i) { - auto& decl = info.uniforms[i]; - switch (decl->kind) { - case ShaderVariable::KD_Math: { - auto& mathDecl = static_cast<ShaderMathVariable&>(*decl); - if (mathDecl.width == 1) { - if (mathDecl.height == 1) { - // Scalar - auto& scalar = mBoundScalars.emplace_back(); - scalar.location = decl->location; - scalar.infoUniformIndex = i; - } else { - // Vector - auto& vec = mBoundVectors.emplace_back(); - vec.location = decl->location; - vec.infoUniformIndex = i; - vec.actualLength = mathDecl.height; - } - } else { - // Matrix - auto& mat = mBoundMatrices.emplace_back(); - mat.location = decl->location; - mat.infoUniformIndex = i; - mat.actualWidth = mathDecl.width; - mat.actualHeight = mathDecl.height; - } - } break; - - case ShaderVariable::KD_Sampler: { - auto& uniform = mBoundTextures.emplace_back(); - uniform.location = decl->location; - uniform.infoUniformIndex = i; - } break; - } - } -} - -bool Material::IsValid() const { - return mShader != nullptr; -} - -static constexpr int IdentifyMatrixSize(int width, int height) { - return width * 10 + height; -} - -void Material::UseUniforms() const { - for (auto& uniform : mBoundScalars) { - switch (uniform.actualType) { - case GL_FLOAT: glUniform1f(uniform.location, uniform.intValue); break; - case GL_INT: glUniform1i(uniform.location, uniform.intValue); break; - case GL_UNSIGNED_INT: glUniform1ui(uniform.location, uniform.intValue); break; - default: break; - } - } - - for (auto& uniform : mBoundVectors) { - switch (uniform.actualLength) { - case 1: glUniform1fv(uniform.location, 1, &uniform.value[0]); break; - case 2: glUniform2fv(uniform.location, 1, &uniform.value[0]); break; - case 3: glUniform3fv(uniform.location, 1, &uniform.value[0]); break; - case 4: glUniform4fv(uniform.location, 1, &uniform.value[0]); break; - default: break; - } - } - - for (auto& uniform : mBoundMatrices) { - switch (IdentifyMatrixSize(uniform.actualWidth, uniform.actualHeight)) { - case IdentifyMatrixSize(2, 2): glUniformMatrix2fv(uniform.location, 1, GL_FALSE, uniform.value); break; - case IdentifyMatrixSize(3, 3): glUniformMatrix3fv(uniform.location, 1, GL_FALSE, uniform.value); break; - case IdentifyMatrixSize(4, 4): glUniformMatrix4fv(uniform.location, 1, GL_FALSE, uniform.value); break; - - case IdentifyMatrixSize(2, 3): glUniformMatrix2x3fv(uniform.location, 1, GL_FALSE, uniform.value); break; - case IdentifyMatrixSize(3, 2): glUniformMatrix3x2fv(uniform.location, 1, GL_FALSE, uniform.value); break; - - case IdentifyMatrixSize(2, 4): glUniformMatrix2x4fv(uniform.location, 1, GL_FALSE, uniform.value); break; - case IdentifyMatrixSize(4, 2): glUniformMatrix4x2fv(uniform.location, 1, GL_FALSE, uniform.value); break; - - case IdentifyMatrixSize(3, 4): glUniformMatrix3x4fv(uniform.location, 1, GL_FALSE, uniform.value); break; - case IdentifyMatrixSize(4, 3): glUniformMatrix4x3fv(uniform.location, 1, GL_FALSE, uniform.value); break; - - default: break; - } - } - - int i = 0; - for (auto& uniform : mBoundTextures) { - glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(GL_TEXTURE_2D, uniform.value->GetHandle()); - glUniform1i(uniform.location, i); - ++i; - } -} - -IresMaterial::IresMaterial() - : IresObject(KD_Material) - , mInstance(new Material()) { - mInstance->mIres = this; -} - -Material* IresMaterial::GetInstance() const { - return mInstance.Get(); -} - -void IresMaterial::InvalidateInstance() { - if (mInstance != nullptr) { - mInstance->mIres = nullptr; - } - mInstance.Attach(new Material()); - mInstance->mIres = this; -} - -void IresMaterial::ShowEditor(IEditor& editor) { - using namespace Tags; - - IresObject::ShowEditor(editor); - - auto shader = mInstance->GetShader(); - if (shader) { - shader->GetIres()->ShowReference(editor); - } else { - IresObject::ShowReferenceNull(editor); - } - if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(KD_Shader).data())) { - auto shader = *static_cast<IresShader* const*>(payload->Data); - mInstance->SetShader(shader->GetInstance()); - } - ImGui::EndDragDropTarget(); - } - - if (!shader) return; - auto& shaderInfo = shader->GetInfo(); - auto shaderIres = shader->GetIres(); - - for (auto& field : mInstance->mBoundScalars) { - auto& decl = static_cast<ShaderMathVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); - decl.ShowInfo(); - - ImGui::Indent(); - switch (decl.scalarType) { - case GL_FLOAT: ImGui::InputFloat("##", &field.floatValue); break; - case GL_INT: ImGui::InputInt("##", &field.intValue); break; - // TODO proper uint edit? - case GL_UNSIGNED_INT: ImGui::InputInt("##", (int32_t*)(&field.uintValue), 0, std::numeric_limits<int32_t>::max()); break; - default: ImGui::TextUnformatted("Unsupported scalar type"); break; - } - ImGui::Unindent(); - } - for (auto& field : mInstance->mBoundVectors) { - auto& decl = static_cast<ShaderMathVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); - decl.ShowInfo(); - - ImGui::Indent(); - switch (decl.semantic) { - case VES_Color1: - case VES_Color2: { - ImGui::ColorEdit4("##", field.value); - } break; - - default: { - ImGui::InputFloat4("##", field.value); - } break; - } - ImGui::Unindent(); - } - for (auto& field : mInstance->mBoundMatrices) { - auto& decl = static_cast<ShaderMathVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); - decl.ShowInfo(); - - // TODO - } - for (auto& field : mInstance->mBoundTextures) { - auto& decl = static_cast<ShaderSamplerVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); - decl.ShowInfo(); - - // TODO - } -} - -void IresMaterial::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { - using namespace ProjectBrussel_UNITY_ID; - - IresObject::Write(ctx, value, root); - - if (!mInstance->IsValid()) { - return; - } - - auto& shaderInfo = mInstance->mShader->GetInfo(); - auto shaderUid = mInstance->mShader->GetIres()->GetUid(); - value.AddMember("Shader", shaderUid.Write(root), root.GetAllocator()); - - rapidjson::Value fields(rapidjson::kArrayType); - for (auto& scalar : mInstance->mBoundScalars) { - rapidjson::Value rvField(rapidjson::kObjectType); - rvField.AddMember("Name", shaderInfo.uniforms[scalar.infoUniformIndex]->name, root.GetAllocator()); - rvField.AddMember("Type", "Scalar", root.GetAllocator()); - switch (scalar.actualType) { - case GL_FLOAT: rvField.AddMember("Value", scalar.floatValue, root.GetAllocator()); break; - case GL_INT: rvField.AddMember("Value", scalar.intValue, root.GetAllocator()); break; - case GL_UNSIGNED_INT: rvField.AddMember("Value", scalar.uintValue, root.GetAllocator()); break; - } - fields.PushBack(rvField, root.GetAllocator()); - } - for (auto& vector : mInstance->mBoundVectors) { - rapidjson::Value rvField(rapidjson::kObjectType); - rvField.AddMember("Name", shaderInfo.uniforms[vector.infoUniformIndex]->name, root.GetAllocator()); - rvField.AddMember("Type", "Vector", root.GetAllocator()); - rvField.AddMember("Value", MakeVectorJson(vector, root).Move(), root.GetAllocator()); - fields.PushBack(rvField, root.GetAllocator()); - } - for (auto& matrix : mInstance->mBoundMatrices) { - rapidjson::Value rvField(rapidjson::kObjectType); - rvField.AddMember("Name", shaderInfo.uniforms[matrix.infoUniformIndex]->name, root.GetAllocator()); - rvField.AddMember("Type", "Matrix", root.GetAllocator()); - rvField.AddMember("Value", MakeMatrixJson(matrix, root).Move(), root.GetAllocator()); - fields.PushBack(rvField, root.GetAllocator()); - } - for (auto& texture : mInstance->mBoundTextures) { - // TODO - } - value.AddMember("Fields", fields, root.GetAllocator()); -} - -void IresMaterial::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { - using namespace ProjectBrussel_UNITY_ID; - - IresObject::Read(ctx, value); - - { - auto rvShader = rapidjson::GetProperty(value, "Shader"sv); - if (!rvShader) return; - - Uid uid; - uid.Read(*rvShader); - - auto ires = ctx.FindIres(uid); - if (!ires) return; - if (ires->GetKind() != KD_Shader) return; - auto shader = static_cast<IresShader*>(ires); - - mInstance->mShader.Attach(shader->GetInstance()); - } - auto shader = mInstance->mShader.Get(); - auto& shaderInfo = shader->GetInfo(); - - auto fields = rapidjson::GetProperty(value, rapidjson::kArrayType, "Fields"sv); - if (!fields) return; - - for (auto& rvField : fields->GetArray()) { - if (!rvField.IsObject()) continue; - - auto rvName = rapidjson::GetProperty(rvField, rapidjson::kStringType, "Name"sv); - - auto rvType = rapidjson::GetProperty(rvField, rapidjson::kStringType, "Type"sv); - if (!rvType) continue; - auto type = rapidjson::AsStringView(*rvType); - - auto rvValue = rapidjson::GetProperty(rvField, "Value"sv); - - if (type == "Scalar"sv) { - Material::ScalarUniform uniform; - if (rvName) { - TryFindShaderId(shader, rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); - uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; - } - if (rvValue->IsFloat()) { - uniform.actualType = GL_FLOAT; - uniform.floatValue = rvValue->GetFloat(); - } else if (rvValue->IsInt()) { - uniform.actualType = GL_INT; - uniform.intValue = rvValue->GetInt(); - } else if (rvValue->IsUint()) { - uniform.actualType = GL_UNSIGNED_INT; - uniform.uintValue = rvValue->GetUint(); - } - mInstance->mBoundScalars.push_back(std::move(uniform)); - } else if (type == "Vector"sv) { - auto uniform = ReadVectorFromJson(*rvValue); - if (rvName) { - TryFindShaderId(shader, rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); - uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; - } - mInstance->mBoundVectors.push_back(std::move(uniform)); - } else if (type == "Matrix"sv) { - auto uniform = ReadMatrixFromjson(*rvValue); - if (rvName) { - TryFindShaderId(shader, rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); - uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; - } - mInstance->mBoundMatrices.push_back(uniform); - } else if (type == "Texture"sv) { - // TODO - } - } -} diff --git a/source/30-game/Material.hpp b/source/30-game/Material.hpp deleted file mode 100644 index f1cd7dd..0000000 --- a/source/30-game/Material.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once - -#include "Ires.hpp" -#include "RcPtr.hpp" -#include "Shader.hpp" -#include "Texture.hpp" - -#include <glad/glad.h> -#include <robin_hood.h> -#include <cstddef> -#include <cstdint> -#include <glm/glm.hpp> -#include <memory> -#include <span> -#include <string_view> -#include <vector> - -// Forward declarations -class Material; -class IresMaterial; - -class Material : public RefCounted { - friend class IresMaterial; - -public: - // NOTE: specialize between scalar vs matrix vs vector to save memory - - enum UniformType : uint16_t { - UT_Scalar, - UT_Vector, - UT_Matrix, - }; - - struct UniformIndex { - UniformType type; - uint16_t index; - }; - - struct ScalarUniform { - union { - float floatValue; - int32_t intValue; - uint32_t uintValue; - }; - GLenum actualType; - /* Transient */ int infoUniformIndex; - /* Transient */ GLint location; - }; - - struct VectorUniform { - float value[4]; - int actualLength; - /* Transient */ int infoUniformIndex; - /* Transient */ GLint location; - }; - - struct MatrixUniform { - float value[16]; - int actualWidth; - int actualHeight; - /* Transient */ int infoUniformIndex; - /* Transient */ GLint location; - }; - - struct TextureUniform { - RcPtr<Texture> value; - /* Transient */ int infoUniformIndex; - /* Transient */ GLint location; - }; - - IresMaterial* mIres = nullptr; - RcPtr<Shader> mShader; - std::vector<ScalarUniform> mBoundScalars; - std::vector<VectorUniform> mBoundVectors; - std::vector<MatrixUniform> mBoundMatrices; - std::vector<TextureUniform> mBoundTextures; - -public: - Material(); - - void SetFloat(const char* name, float value); - void SetInt(const char* name, int32_t value); - void SetUInt(const char* name, uint32_t value); - - /// Instanciated for length == 1, 2, 3, 4 - template <int length> - void SetVector(const char* name, const glm::vec<length, float>& vec); - - /// Instanciated for sizes (2,2) (3,3) (4,4) (2,3) (3,2) (2,4) (4,2) (3,4) (4,3) - template <int width, int height> - void SetMatrix(const char* name, const glm::mat<width, height, float>& mat); - - void SetTexture(const char* name, Texture* texture); - - std::span<const VectorUniform> GetVectors() const; - std::span<const MatrixUniform> GetMatrices() const; - std::span<const TextureUniform> GetTextures() const; - Shader* GetShader() const; - void SetShader(Shader* shader); - - IresMaterial* GetIres() const { return mIres; } - - bool IsValid() const; - - void UseUniforms() const; -}; - -// Initialized in main() -inline RcPtr<Material> gDefaultMaterial; - -class IresMaterial : public IresObject { -private: - RcPtr<Material> mInstance; - -public: - IresMaterial(); - - Material* GetInstance() const; - void InvalidateInstance(); - - void ShowEditor(IEditor& editor) override; - - void Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const override; - void Read(IresLoadingContext& ctx, const rapidjson::Value& value) override; -}; diff --git a/source/30-game/Mesh.cpp b/source/30-game/Mesh.cpp deleted file mode 100644 index 244e2e3..0000000 --- a/source/30-game/Mesh.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "Mesh.hpp" - -#include <cstring> - -// StandardCpuMesh::StandardCpuMesh() -// : mGpuMesh(new GpuMesh()) { -// mGpuMesh->vertFormat = gVformatStandard; -// mGpuMesh->vertBufBindings.SetBinding(0, new GpuVertexBuffer()); -// mGpuMesh->vertBufBindings.SetBinding(1, new GpuVertexBuffer()); -// mGpuMesh->indexBuf.Attach(new GpuIndexBuffer()); -// } - -// StandardCpuMesh::~StandardCpuMesh() { -// delete mData; -// } - -// void StandardCpuMesh::CreateCpuData() { -// if (!mData) { -// mData = new StandardCpuMeshData(); -// } -// } - -// GpuVertexBuffer* StandardCpuMesh::GetPosBuffer() const { -// return mGpuMesh->vertBufBindings.bindings[0].Get(); -// } - -// GpuVertexBuffer* StandardCpuMesh::GetExtraBuffer() const { -// return mGpuMesh->vertBufBindings.bindings[1].Get(); -// } - -// bool StandardCpuMesh::UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex) { -// if (mData) { -// std::memcpy(&mData->vertPositions[startVertIndex], pos, count * sizeof(glm::vec3)); -// } -// auto posBuf = GetPosBuffer(); -// glBindBuffer(GL_ARRAY_BUFFER, posBuf->handle); -// glBufferSubData(GL_ARRAY_BUFFER, startVertIndex * mGpuMesh->vertFormat->vertexSize, count * sizeof(glm::vec3), pos); -// return true; -// } - -// bool StandardCpuMesh::UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex) { -// if (!mData) return false; -// // TODO -// } - -// bool StandardCpuMesh::UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex) { -// if (!mData) return false; -// // TODO -// } - -// bool StandardCpuMesh::UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex) { -// if (!mData) return false; -// // TODO -// } diff --git a/source/30-game/Mesh.hpp b/source/30-game/Mesh.hpp deleted file mode 100644 index f86fd55..0000000 --- a/source/30-game/Mesh.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "Color.hpp" -#include "VertexIndex.hpp" -#include "PodVector.hpp" -#include "RcPtr.hpp" - -#include <cstddef> -#include <cstdint> -#include <glm/glm.hpp> -#include <memory> - -struct StandardVertexExtra { - float u, v; - uint8_t r, g, b, a; -}; - -class StandardCpuMeshData { -public: - PodVector<glm::vec3> vertPositions; - PodVector<StandardVertexExtra> vertExtra; - PodVector<uint32_t> index; - size_t vertexCount; - size_t triangleCount; -}; - -class StandardCpuMesh { -// private: -// StandardCpuMeshData* mData = nullptr; -// RcPtr<GpuMesh> mGpuMesh; - -// public: -// StandardCpuMesh(); -// ~StandardCpuMesh(); - -// GpuVertexBuffer* GetPosBuffer() const; -// GpuVertexBuffer* GetExtraBuffer() const; -// GpuMesh* GetGpuMesh() const { return mGpuMesh.Get(); } - -// void CreateCpuData(); -// bool UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex); -// bool UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex); -// bool UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex); -// bool UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex); -}; diff --git a/source/30-game/Player.cpp b/source/30-game/Player.cpp deleted file mode 100644 index 34c4549..0000000 --- a/source/30-game/Player.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include "Player.hpp" - -#include "AppConfig.hpp" -#include "CommonVertexIndex.hpp" -#include "ScopeGuard.hpp" -#include "Utils.hpp" - -#include <cstdio> -#include <cstdlib> - -// Keep the same number as # of fields in `struct {}` in PlayerKeyBinds -constexpr int kPlayerKeyBindCount = 4; - -// Here be dragons: this treats consecutive fiels as an array, technically UB -std::span<int> PlayerKeyBinds::GetKeyArray() { - return { &keyLeft, kPlayerKeyBindCount }; -} -std::span<bool> PlayerKeyBinds::GetKeyStatusArray() { - return { &pressedLeft, kPlayerKeyBindCount }; -} - -Player::Player(GameWorld* world, int id) - : GameObject(KD_Player, world) - , mId{ id } { - renderObject.SetMaterial(gDefaultMaterial.Get()); - renderObject.SetFormat(gVformatStandard.Get(), Tags::IT_16Bit); - renderObject.RebuildIfNecessary(); -} - -void Player::Awaken() { - LoadFromFile(); -} - -void Player::Resleep() { - SaveToFile(); -} - -void Player::Update() { - using namespace Tags; - - if (keybinds.pressedLeft) { - } - if (keybinds.pressedRight) { - } - - // TODO jump controller - - // TODO attack controller - - // TODO set default sprite to get rid of this check - if (sprite.GetDefinition()) { - int prevFrame = sprite.GetFrame(); - sprite.PlayFrame(); - int currFrame = sprite.GetFrame(); - if (prevFrame != currFrame) { - uint16_t indices[6]; - Index_U16::Assign(indices, 0); - renderObject.GetIndexBuffer()->Upload((const std::byte*)indices, IT_16Bit, std::size(indices)); - - Vertex_PTC vertices[4]; - Vertex_PTC::Assign(vertices, Rect<float>{ GetPos(), sprite.GetDefinition()->GetBoundingBox() }); - Vertex_PTC::Assign(vertices, 0.0f); - Vertex_PTC::Assign(vertices, RgbaColor(255, 255, 255)); - Vertex_PTC::Assign(vertices, sprite.GetFrameSubregion()); - renderObject.GetVertexBufferBindings().bindings[0]->Upload((const std::byte*)vertices, sizeof(vertices)); - } - } -} - -Material* Player::GetMaterial() const { - return renderObject.GetMaterial(); -} - -void Player::SetMaterial(Material* material) { - renderObject.SetMaterial(material); - renderObject.RebuildIfNecessary(); -} - -std::span<const RenderObject> Player::GetRenderObjects() const { - return { &renderObject, 1 }; -} - -void Player::HandleKeyInput(int key, int action) { - bool pressed; - if (action == GLFW_PRESS) { - pressed = true; - } else if (action == GLFW_REPEAT) { - return; - } else /* action == GLFW_RELEASE */ { - pressed = false; - } - - for (int i = 0; i < kPlayerKeyBindCount; ++i) { - int kbKey = keybinds.GetKeyArray()[i]; - bool& kbStatus = keybinds.GetKeyStatusArray()[i]; - - if (kbKey == key) { - kbStatus = pressed; - break; - } - } -} - -#pragma push_macro("PLAYERKEYBINDS_DO_IO") -#undef PLAYERKEYBINDS_DO_IO -#define PLAYERKEYBINDS_DO_IO(function, fieldPrefix) \ - function(file, "left=%d\n", fieldPrefix keybinds.keyLeft); \ - function(file, "right=%d\n", fieldPrefix keybinds.keyRight); \ - function(file, "jump=%d\n", fieldPrefix keybinds.keyJump); \ - function(file, "attack=%d\n", fieldPrefix keybinds.keyAttack); - -static FILE* OpenPlayerConfigFile(Player* player, Utils::IoMode mode) { - char path[2048]; - snprintf(path, sizeof(path), "%s/player%d.txt", AppConfig::dataDir.c_str(), player->GetId()); - - return Utils::OpenCstdioFile(path, mode); -} - -bool Player::LoadFromFile() { - auto file = OpenPlayerConfigFile(this, Utils::Read); - if (!file) return false; - DEFER { fclose(file); }; - - // TODO input validation - PLAYERKEYBINDS_DO_IO(fscanf, &); - - return true; -} - -bool Player::SaveToFile() { - auto file = OpenPlayerConfigFile(this, Utils::WriteTruncate); - if (!file) return false; - DEFER { fclose(file); }; - - PLAYERKEYBINDS_DO_IO(fprintf, ); - - return true; -} -#pragma pop_macro("PLAYERKEYBINDS_DO_IO") diff --git a/source/30-game/Player.hpp b/source/30-game/Player.hpp deleted file mode 100644 index 5a6bab7..0000000 --- a/source/30-game/Player.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "GameObject.hpp" -#include "Material.hpp" -#include "Sprite.hpp" - -#include <MacrosCodegen.hpp> -#include <RcPtr.hpp> - -#define GLFW_INCLUDE_NONE -#include <GLFW/glfw3.h> - -#include <span> -#include <vector> - -struct PlayerKeyBinds { - int keyLeft = GLFW_KEY_A; - int keyRight = GLFW_KEY_D; - int keyJump = GLFW_KEY_SPACE; - int keyAttack = GLFW_KEY_J; - - bool pressedLeft = 0; - bool pressedRight = 0; - bool pressedJump = 0; - bool pressedAttack = 0; - - std::span<int> GetKeyArray(); - std::span<bool> GetKeyStatusArray(); -}; - -class Player : public GameObject { - BRUSSEL_CLASS() - -public: - std::vector<GLFWkeyboard*> boundKeyboards; - PlayerKeyBinds keybinds; - Sprite sprite; - RenderObject renderObject; - int mId; - -public: - Player(GameWorld* world, int id); - - virtual void Awaken() override; - virtual void Resleep() override; - virtual void Update() override; - - Material* GetMaterial() const; - void SetMaterial(Material* material); - virtual std::span<const RenderObject> GetRenderObjects() const override; - - int GetId() const { return mId; } - - void HandleKeyInput(int key, int action); - - // File is designated by player ID - bool LoadFromFile(); - bool SaveToFile(); -}; diff --git a/source/30-game/Renderer.cpp b/source/30-game/Renderer.cpp deleted file mode 100644 index 0454efe..0000000 --- a/source/30-game/Renderer.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include "Renderer.hpp" - -#include "GameObject.hpp" - -#include <RapidJsonHelper.hpp> - -#include <rapidjson/document.h> -#include <cassert> -#include <glm/gtc/matrix_transform.hpp> -#include <glm/gtc/quaternion.hpp> -#include <glm/gtx/quaternion.hpp> -#include <string_view> - -using namespace std::literals; - -RenderObject::RenderObject() - : mVao{ 0 } { -} - -RenderObject::~RenderObject() { - DeleteGLObjects(); -} - -GLuint RenderObject::GetGLVao() const { - return mVao; -} - -void RenderObject::RebuildIfNecessary() { - if (mVao != 0) { - return; - } - - assert(mIndexBuf != nullptr); - assert(mVertexFormat != nullptr); - - glGenVertexArrays(1, &mVao); - glBindVertexArray(mVao); - - auto& vBindings = mVertexBufBinding.bindings; - auto& shaderInfo = mMaterial->GetShader()->GetInfo(); - - // Setup vertex buffers - for (auto& elm : mVertexFormat->elements) { - assert(elm.bindingIndex < vBindings.size()); - auto& buffer = vBindings[elm.bindingIndex]; - - int index = shaderInfo.FindInputLocation(elm.semantic); - if (index == -1) { - continue; - } - - glBindBuffer(GL_ARRAY_BUFFER, buffer->handle); - glEnableVertexAttribArray(index); - glVertexAttribPointer( - index, - Tags::VectorLenOf(elm.type), - Tags::FindGLType(elm.type), - Tags::IsNormalized(elm.type), - mVertexFormat->vertexSize, - (void*)(uintptr_t)elm.offset); - } - - // Setup index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuf->handle); - - glBindVertexArray(0); -} - -void RenderObject::SetMaterial(Material* material) { - mMaterial.Attach(material); - DeleteGLObjects(); -} - -void RenderObject::UpdateIndexBuffer(GpuIndexBuffer* indexBuffer) { - mIndexBuf.Attach(indexBuffer); - DeleteGLObjects(); -} - -void RenderObject::UpdateVertexFormat(VertexFormat* vertexFormat) { - mVertexFormat.Attach(vertexFormat); - DeleteGLObjects(); -} - -void RenderObject::UpdateVertexBufferBindings(BufferBindings** bindingsOut) { - *bindingsOut = &mVertexBufBinding; - DeleteGLObjects(); -} - -void RenderObject::SetFormat(VertexFormat* vertexFormat, Tags::IndexType indexFormat) { - mIndexBuf.Attach(new GpuIndexBuffer()); - mIndexBuf->indexType = indexFormat; - mIndexBuf->count = 0; - - mVertexFormat.Attach(vertexFormat); - mVertexBufBinding.Clear(); - for (auto& element : vertexFormat->elements) { - if (mVertexBufBinding.GetBinding(element.bindingIndex) == nullptr) { - mVertexBufBinding.SetBinding(element.bindingIndex, new GpuVertexBuffer()); - } - } -} - -void RenderObject::DeleteGLObjects() { - if (mVao != 0) { - glDeleteVertexArrays(1, &mVao); - mVao = 0; - } -} - -Renderer::Renderer() - : binding_WireframeMaterial{ gDefaultMaterial } // -{ - mRenderOptions[RO_Shading] = true; - mRenderOptions[RO_Wireframe] = false; -} - -void Renderer::LoadBindings(const rapidjson::Value& bindings) { - if (auto rvWireframe = rapidjson::GetProperty(bindings, "WireframeMaterial"sv)) { - Uid uidWireframe; - uidWireframe.Read(*rvWireframe); - // TODO don't assume - binding_WireframeMaterial.Attach(((IresMaterial*)IresManager::instance->FindIres(uidWireframe))->GetInstance()); - } -} - -void Renderer::SaveBindings(rapidjson::Value& into, rapidjson::Document& root) const { - if (auto ires = binding_WireframeMaterial->GetIres()) { - into.AddMember("WireframeMaterial", ires->GetUid().Write(root), root.GetAllocator()); - } -} - -void Renderer::BeginFrame(Camera& camera, float currentTime, float deltaTime) { - assert(mInsideFrame == false); - mInsideFrame = true; - mFrame.camera = &camera; - mFrame.matrixView = camera.CalcViewMatrix(); - mFrame.matrixProj = camera.CalcProjectionMatrix(); - mFrame.time = currentTime; - mFrame.deltaTime = deltaTime; -} - -void Renderer::EndFrame() { - assert(mInsideFrame == true); - mInsideFrame = false; -} - -void Renderer::Draw(const RenderObject* objects, const GameObject* gameObject, size_t count) { - using namespace Tags; - - assert(mInsideFrame); - - // Desired order: proj * view * (translate * rotate * scale) * vec - // <----- order of application <----- ^^^ input - glm::mat4 objectMatrix(1.0f); - objectMatrix = glm::translate(objectMatrix, gameObject->GetPos()); - objectMatrix *= glm::toMat4(gameObject->GetRotation()); - objectMatrix = glm::scale(objectMatrix, gameObject->GetScale()); - auto mvpMatrix = mFrame.matrixProj * mFrame.matrixView * objectMatrix; - - if (GetRenderOption(RO_Shading)) { - // TODO shader grouping - // TODO material grouping - for (size_t i = 0; i < count; ++i) { - auto& object = objects[i]; - auto indexBuffer = object.GetIndexBuffer(); - auto mat = object.GetMaterial(); - auto shader = mat->GetShader(); - - glUseProgram(shader->GetProgram()); - - // Material uniforms - mat->UseUniforms(); - - // Next available texture unit ID after all material textures - int texIdx = mat->GetTextures().size(); - - // Autofill uniforms - if (shader->autofill_Transform != kInvalidLocation) { - glUniformMatrix4fv(shader->autofill_Transform, 1, GL_FALSE, &mvpMatrix[0][0]); - } - if (shader->autofill_Time != kInvalidLocation) { - glUniform1f(shader->autofill_Time, mFrame.time); - } - if (shader->autofill_DeltaTime != kInvalidLocation) { - glUniform1f(shader->autofill_DeltaTime, mFrame.deltaTime); - } - if (shader->autofill_TextureAtlas != kInvalidLocation && - object.autofill_TextureAtlas != nullptr) - { - glActiveTexture(GL_TEXTURE0 + texIdx); - glBindTexture(GL_TEXTURE_2D, object.autofill_TextureAtlas->GetHandle()); - glUniform1i(shader->autofill_TextureAtlas, texIdx); - ++texIdx; - } - - glBindVertexArray(object.GetGLVao()); - glDrawElements(GL_TRIANGLES, indexBuffer->count, indexBuffer->GetIndexTypeGL(), 0); - } - } - - if (GetRenderOption(RO_Wireframe)) { - auto& mat = *binding_WireframeMaterial; - auto& shader = *mat.GetShader(); - auto& shaderInfo = shader.GetInfo(); - - glUseProgram(shader.GetProgram()); - mat.UseUniforms(); - - // TODO reduce calls with consecutive wireframe setting - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - for (size_t i = 0; i < count; ++i) { - auto& object = objects[i]; - - auto& vBindings = object.GetVertexBufferBindings().bindings; - auto vf = object.GetVertexFormat(); - - // Setup vertex buffers - for (auto& elm : vf->elements) { - assert(elm.bindingIndex < vBindings.size()); - auto& buffer = vBindings[elm.bindingIndex]; - - int index = shaderInfo.FindInputLocation(elm.semantic); - if (index == -1) { - continue; - } - - glBindBuffer(GL_ARRAY_BUFFER, buffer->handle); - glEnableVertexAttribArray(index); - glVertexAttribPointer( - index, - Tags::VectorLenOf(elm.type), - Tags::FindGLType(elm.type), - Tags::IsNormalized(elm.type), - vf->vertexSize, - (void*)(uintptr_t)elm.offset); - } - - // Setup index buffer - auto indexBuffer = object.GetIndexBuffer(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer->handle); - - glDrawElements(GL_TRIANGLES, indexBuffer->count, indexBuffer->GetIndexTypeGL(), 0); - } - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - return; - } -} - -bool Renderer::GetRenderOption(RenderOption option) const { - return mRenderOptions[option]; -} - -void Renderer::SetRenderOption(RenderOption option, bool flag) { - mRenderOptions[option] = flag; -} diff --git a/source/30-game/Renderer.hpp b/source/30-game/Renderer.hpp deleted file mode 100644 index 856dc31..0000000 --- a/source/30-game/Renderer.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include "Camera.hpp" -#include "Material.hpp" -#include "VertexIndex.hpp" - -#include <RcPtr.hpp> - -#include <glad/glad.h> -#include <rapidjson/fwd.h> -#include <cstddef> -#include <glm/glm.hpp> - -// TODO add optional support for OpenGL separate attrib binding & only depend on vertex format - -class GameObject; - -class RenderObject { -public: - RcPtr<Texture> autofill_TextureAtlas; - -private: - RcPtr<Material> mMaterial; - RcPtr<GpuIndexBuffer> mIndexBuf; - RcPtr<VertexFormat> mVertexFormat; - BufferBindings mVertexBufBinding; - GLuint mVao; - -public: - RenderObject(); - ~RenderObject(); - - GLuint GetGLVao() const; - void RebuildIfNecessary(); - - Material* GetMaterial() const { return mMaterial.Get(); } - void SetMaterial(Material* material); - - GpuIndexBuffer* GetIndexBuffer() const { return mIndexBuf.Get(); } - const VertexFormat* GetVertexFormat() const { return mVertexFormat.Get(); } - const BufferBindings& GetVertexBufferBindings() const { return mVertexBufBinding; } - void UpdateIndexBuffer(GpuIndexBuffer* indexBuffer); - void UpdateVertexFormat(VertexFormat* vertexFormat); - // Assumes the fetched BufferBinding object is modified - void UpdateVertexBufferBindings(BufferBindings** bindingsOut); - void SetFormat(VertexFormat* vertexFormat, Tags::IndexType indexFormat); - -private: - void DeleteGLObjects(); -}; - -struct RendererFrameInfo { - Camera* camera; - glm::mat4 matrixView; - glm::mat4 matrixProj; - float time; - float deltaTime; -}; - -class Renderer { -public: - // NOTE: see Renderer constructor for default values - enum RenderOption { - /// Render everything directly using objects' provided material and vertex/index data. - RO_Shading, - /// Render everything as wireframes using provided position data. - RO_Wireframe, - RO_COUNT, - }; - -public: - RcPtr<Material> binding_WireframeMaterial; - -private: - RendererFrameInfo mFrame; - bool mInsideFrame = false; - bool mRenderOptions[RO_COUNT] = {}; - -public: - Renderer(); - - void LoadBindings(const rapidjson::Value& bindings); - void SaveBindings(rapidjson::Value& into, rapidjson::Document& root) const; - - void BeginFrame(Camera& camera, float currentTime, float deltaTime); - const RendererFrameInfo& GetLastFrameInfo() const { return mFrame; } - void Draw(const RenderObject* objects, const GameObject* gameObject, size_t count); - void EndFrame(); - - bool GetRenderOption(RenderOption option) const; - void SetRenderOption(RenderOption option, bool flag); -}; diff --git a/source/30-game/SceneThings.cpp b/source/30-game/SceneThings.cpp deleted file mode 100644 index 3fa0436..0000000 --- a/source/30-game/SceneThings.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "SceneThings.hpp" - -#include "CommonVertexIndex.hpp" -#include "Rect.hpp" - -#include <utility> - -SimpleGeometryObject::SimpleGeometryObject(GameWorld* world) - : GameObject(KD_SimpleGeometry, world) - , mRenderObject() - , mSize{ 1.0f, 1.0f, 1.0f } - , mXFaceColor(kXAxisColor) - , mYFaceColor(kYAxisColor) - , mZFaceColor(kZAxisColor) - , mNeedsRebuildMesh{ true } { - mRenderObject.SetMaterial(gDefaultMaterial.Get()); - mRenderObject.SetFormat(gVformatStandard.Get(), Tags::IT_16Bit); - mRenderObject.RebuildIfNecessary(); -} - -void SimpleGeometryObject::SetSize(glm::vec3 size) { - mSize = size; - mNeedsRebuildMesh = true; -} - -void SimpleGeometryObject::SetXFaceColor(RgbaColor color) { - mXFaceColor = color; - mNeedsRebuildMesh = true; -} - -void SimpleGeometryObject::SetYFaceColor(RgbaColor color) { - mYFaceColor = color; - mNeedsRebuildMesh = true; -} - -void SimpleGeometryObject::SetZFaceColor(RgbaColor color) { - mZFaceColor = color; - mNeedsRebuildMesh = true; -} - -std::span<const RenderObject> SimpleGeometryObject::GetRenderObjects() const { - using namespace Tags; - - if (mNeedsRebuildMesh) { - mNeedsRebuildMesh = false; - - Vertex_PTC vertices[4 /*vertices per face*/ * 6 /*faces*/]; - uint16_t indices[3 /*indices per triangle*/ * 2 /*triangles per face*/ * 6 /*faces*/]; - - auto extents = mSize / 2.0f; - - int faceGenVerticesIdx = 0; - int faceGenIndicesIdx = 0; - auto GenerateFace = [&](glm::vec3 faceCenter, glm::vec3 firstExtentVec, glm::vec3 secondExtentVec, RgbaColor color) { - // Generates (if viewing top down on the face): bottom left, top left, bottom right, top right - // (-1, -1) , (-1, 1) , (1, -1) , (1, 1) - // idx=0 , idx=1 , idx=2 , idx=3 - - // These are index offsets, see above comment - constexpr int kBottomLeft = 0; - constexpr int kTopLeft = 1; - constexpr int kBottomRight = 2; - constexpr int kTopRight = 3; - - int startVertIdx = faceGenVerticesIdx; - for (float firstDir : { -1, 1 }) { - for (float secondDir : { -1, 1 }) { - auto vertPos = faceCenter + firstExtentVec * firstDir + secondExtentVec * secondDir; - auto& vert = vertices[faceGenVerticesIdx]; - vert.x = vertPos.x; - vert.y = vertPos.y; - vert.z = vertPos.z; - vert.r = color.r; - vert.g = color.g; - vert.b = color.b; - vert.a = color.a; - faceGenVerticesIdx += 1; - } - } - - // Triangle #1 - indices[faceGenIndicesIdx++] = startVertIdx + kTopRight; - indices[faceGenIndicesIdx++] = startVertIdx + kTopLeft; - indices[faceGenIndicesIdx++] = startVertIdx + kBottomLeft; - // Triangle #2 - indices[faceGenIndicesIdx++] = startVertIdx + kTopRight; - indices[faceGenIndicesIdx++] = startVertIdx + kBottomLeft; - indices[faceGenIndicesIdx++] = startVertIdx + kBottomRight; - }; - for (int xDir : { -1, 1 }) { - float x = xDir * extents.x; - GenerateFace(glm::vec3(x, 0, 0), glm::vec3(0, 0, extents.z), glm::vec3(0, extents.y, 0), mXFaceColor); - } - for (int yDir : { -1, 1 }) { - float y = yDir * extents.y; - GenerateFace(glm::vec3(0, y, 0), glm::vec3(extents.x, 0, 0), glm::vec3(0, 0, extents.z), mYFaceColor); - } - for (int zDir : { -1, 1 }) { - float z = zDir * extents.z; - GenerateFace(glm::vec3(0, 0, z), glm::vec3(extents.x, 0, 0), glm::vec3(0, extents.y, 0), mZFaceColor); - } - - for (auto& vert : vertices) { - vert.u = 0.0f; - vert.v = 0.0f; - } - - mRenderObject.GetVertexBufferBindings().bindings[0]->Upload((const std::byte*)vertices, sizeof(vertices)); - mRenderObject.GetIndexBuffer()->Upload((const std::byte*)indices, IT_16Bit, std::size(indices)); - } - - return { &mRenderObject, 1 }; -} - -BuildingObject::BuildingObject(GameWorld* world) - : GameObject(KD_Building, world) { - mRenderObject.SetMaterial(gDefaultMaterial.Get()); - mRenderObject.SetFormat(gVformatStandard.Get(), Tags::IT_32Bit); - mRenderObject.RebuildIfNecessary(); -} - -// void BuildingObject::SetMeshMaterial(Material* material) { -// mMaterial.Attach(material); -// // TODO update render -// } - -// const Material* BuildingObject::GetMeshMaterial() const { -// return mMaterial.Get(); -// } - -// void BuildingObject::SetMesh(GpuMesh* mesh) { -// mMesh.Attach(mesh); -// // TODO update render -// } - -// const GpuMesh* BuildingObject::GetMesh() const { -// return mMesh.Get(); -// } - -std::span<const RenderObject> BuildingObject::GetRenderObjects() const { - return { &mRenderObject, 1 }; -} diff --git a/source/30-game/SceneThings.hpp b/source/30-game/SceneThings.hpp deleted file mode 100644 index 761eb59..0000000 --- a/source/30-game/SceneThings.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "Color.hpp" -#include "GameObject.hpp" -#include "Renderer.hpp" - -#include <MacrosCodegen.hpp> - -#include <glm/glm.hpp> -#include <vector> - -class SimpleGeometryObject : public GameObject { - BRUSSEL_CLASS() - -private: - RenderObject mRenderObject; - glm::vec3 mSize; - RgbaColor mXFaceColor; - RgbaColor mYFaceColor; - RgbaColor mZFaceColor; - mutable bool mNeedsRebuildMesh; - -public: - SimpleGeometryObject(GameWorld* world); - - glm::vec3 GetSize() const { return mSize; } - void SetSize(glm::vec3 size); - RgbaColor GetXFaceColor() const { return mXFaceColor; } - void SetXFaceColor(RgbaColor color); - RgbaColor GetYFaceColor() const { return mYFaceColor; } - void SetYFaceColor(RgbaColor color); - RgbaColor GetZFaceColor() const { return mZFaceColor; } - void SetZFaceColor(RgbaColor color); - virtual std::span<const RenderObject> GetRenderObjects() const override; -}; - -class BuildingObject : public GameObject { - BRUSSEL_CLASS() - -private: - RenderObject mRenderObject; - -public: - BuildingObject(GameWorld* world); - - // TODO - // void SetMeshMaterial(Material* material); - // virtual const Material* GetMeshMaterial() const override; - // void SetMesh(GpuMesh* mesh); - // virtual const GpuMesh* GetMesh() const override; - virtual std::span<const RenderObject> GetRenderObjects() const override; -}; diff --git a/source/30-game/Shader.cpp b/source/30-game/Shader.cpp deleted file mode 100644 index 9bf2e0e..0000000 --- a/source/30-game/Shader.cpp +++ /dev/null @@ -1,711 +0,0 @@ -#include "Shader.hpp" - -#include "AppConfig.hpp" - -#include <Metadata.hpp> -#include <RapidJsonHelper.hpp> -#include <ScopeGuard.hpp> -#include <Utils.hpp> - -#include <fmt/format.h> -#include <imgui.h> -#include <misc/cpp/imgui_stdlib.h> -#include <rapidjson/document.h> -#include <cassert> -#include <cstddef> -#include <cstdlib> -#include <utility> - -using namespace std::literals; - -void ShaderMathVariable::ShowInfo() const { - ImGui::BulletText("Location: %d\nName: %s\nSemantic: %.*s\nType: %.*s %dx%d", - location, - name.c_str(), - PRINTF_STRING_VIEW(Metadata::EnumToString(semantic)), - PRINTF_STRING_VIEW(Tags::GLTypeToString(scalarType)), - width, - height); -} - -void ShaderSamplerVariable::ShowInfo() const { - ImGui::BulletText("Location: %d\nName: %s\nSemantic: %.*s\nType: Sampler", - location, - name.c_str(), - PRINTF_STRING_VIEW(Metadata::EnumToString(semantic))); -} - -namespace ProjectBrussel_UNITY_ID { -GLuint FindLocation(const std::vector<ShaderMathVariable>& vars, Tags::VertexElementSemantic semantic) { - for (auto& var : vars) { - if (var.semantic == semantic) { - return var.location; - } - } - return Tags::kInvalidLocation; -} - -constexpr auto kAfnTransform = "transform"; -constexpr auto kAfnTime = "time"; -constexpr auto kAfnDeltaTime = "deltaTime"; -constexpr auto kAfnTextureAtlas = "textureAtlas"; - -void InitAutoFill(const char* name, GLuint program, GLuint& location) { - GLint result = glGetUniformLocation(program, name); - if (result != -1) { - location = result; - } -} - -void InitAutoFills(Shader& shader) { - GLuint pg = shader.GetProgram(); - InitAutoFill(kAfnTransform, pg, shader.autofill_Transform); - InitAutoFill(kAfnTime, pg, shader.autofill_Time); - InitAutoFill(kAfnDeltaTime, pg, shader.autofill_DeltaTime); - InitAutoFill(kAfnTextureAtlas, pg, shader.autofill_TextureAtlas); -} -} // namespace ProjectBrussel_UNITY_ID - -GLuint ShaderInfo::FindInputLocation(Tags::VertexElementSemantic semantic) { - using namespace ProjectBrussel_UNITY_ID; - return FindLocation(inputs, semantic); -} - -GLuint ShaderInfo::FindOutputLocation(Tags::VertexElementSemantic semantic) { - using namespace ProjectBrussel_UNITY_ID; - return FindLocation(outputs, semantic); -} - -Shader::Shader() { -} - -Shader::~Shader() { - glDeleteProgram(mProgram); -} - -namespace ProjectBrussel_UNITY_ID { -// Grabs section [begin, end) -Shader::ErrorCode CreateShader(GLuint& out, const char* src, int beginIdx, int endIdx, GLenum type) { - out = glCreateShader(type); - - const GLchar* begin = &src[beginIdx]; - const GLint len = endIdx - beginIdx; - glShaderSource(out, 1, &begin, &len); - - glCompileShader(out); - GLint compileStatus; - glGetShaderiv(out, GL_COMPILE_STATUS, &compileStatus); - if (compileStatus == GL_FALSE) { - GLint len; - glGetShaderiv(out, GL_INFO_LOG_LENGTH, &len); - - std::string log(len, '\0'); - glGetShaderInfoLog(out, len, nullptr, log.data()); - - return Shader ::EC_CompilationFailed; - } - - return Shader::EC_Success; -} - -Shader::ErrorCode CreateShader(GLuint& out, std::string_view str, GLenum type) { - return CreateShader(out, str.data(), 0, str.size(), type); -} - -Shader::ErrorCode LinkShaderProgram(GLuint program) { - glLinkProgram(program); - GLint linkStatus; - glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); - if (linkStatus == GL_FALSE) { - GLint len; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); - - std::string log(len, '\0'); - glGetProgramInfoLog(program, len, nullptr, log.data()); - - return Shader::EC_LinkingFailed; - } - - return Shader::EC_Success; -} -} // namespace ProjectBrussel_UNITY_ID - -#define CATCH_ERROR_IMPL(x, name) \ - auto name = x; \ - if (name != Shader::EC_Success) { \ - return name; \ - } -#define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result)) - -Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) { - using namespace ProjectBrussel_UNITY_ID; - - if (IsValid()) { - return EC_AlreadyInitialized; - } - - GLuint program = glCreateProgram(); - ScopeGuard sg = [&]() { glDeleteProgram(program); }; - - GLuint vertex = 0; - DEFER { - glDeleteShader(vertex); - }; - if (!sources.vertex.empty()) { - CATCH_ERROR(CreateShader(vertex, sources.vertex, GL_VERTEX_SHADER)); - glAttachShader(program, vertex); - } - - GLuint geometry = 0; - DEFER { - glDeleteShader(geometry); - }; - if (!sources.geometry.empty()) { - CATCH_ERROR(CreateShader(geometry, sources.geometry, GL_GEOMETRY_SHADER)); - glAttachShader(program, geometry); - } - - GLuint tessControl = 0; - DEFER { - glDeleteShader(tessControl); - }; - if (!sources.tessControl.empty()) { - CATCH_ERROR(CreateShader(tessControl, sources.tessControl, GL_TESS_CONTROL_SHADER)); - glAttachShader(program, tessControl); - } - - GLuint tessEval = 0; - DEFER { - glDeleteShader(tessEval); - }; - if (!sources.tessEval.empty()) { - CATCH_ERROR(CreateShader(tessEval, sources.tessEval, GL_TESS_EVALUATION_SHADER)); - glAttachShader(program, tessEval); - } - - GLuint fragment = 0; - DEFER { - glDeleteShader(fragment); - }; - if (!sources.fragment.empty()) { - CATCH_ERROR(CreateShader(fragment, sources.fragment, GL_FRAGMENT_SHADER)); - glAttachShader(program, fragment); - } - - CATCH_ERROR(LinkShaderProgram(program)); - - sg.Dismiss(); - mProgram = program; - - InitAutoFills(*this); - - return EC_Success; -} - -Shader::ErrorCode Shader::InitFromSource(std::string_view source) { - using namespace ProjectBrussel_UNITY_ID; - - GLuint vertex = 0; - DEFER { - glDeleteShader(vertex); - }; - - GLuint geometry = 0; - DEFER { - glDeleteShader(geometry); - }; - - GLuint tessControl = 0; - DEFER { - glDeleteShader(tessControl); - }; - - GLuint tessEval = 0; - DEFER { - glDeleteShader(tessEval); - }; - - GLuint fragment = 0; - DEFER { - glDeleteShader(fragment); - }; - - int prevBegin = -1; // Excluding #type marker - int prevEnd = -1; // [begin, end) - std::string prevShaderVariant; - - auto CommitSection = [&]() -> ErrorCode { - if (prevBegin == -1 || prevEnd == -1) { - // Not actually "succeeding" here, but we just want to skip this call and continue - return EC_Success; - } - - if (prevShaderVariant == "vertex" && !vertex) { - CATCH_ERROR(CreateShader(vertex, source.data(), prevBegin, prevEnd, GL_VERTEX_SHADER)); - } else if (prevShaderVariant == "geometry" && !geometry) { - CATCH_ERROR(CreateShader(geometry, source.data(), prevBegin, prevEnd, GL_GEOMETRY_SHADER)); - } else if (prevShaderVariant == "tessellation_control" && !tessControl) { - CATCH_ERROR(CreateShader(tessControl, source.data(), prevBegin, prevEnd, GL_TESS_CONTROL_SHADER)); - } else if (prevShaderVariant == "tessellation_evaluation" && !tessEval) { - CATCH_ERROR(CreateShader(tessEval, source.data(), prevBegin, prevEnd, GL_TESS_EVALUATION_SHADER)); - } else if (prevShaderVariant == "fragment" && !fragment) { - CATCH_ERROR(CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER)); - } else { - return EC_InvalidShaderVariant; - } - - prevBegin = -1; - prevEnd = -1; - prevShaderVariant.clear(); - - return EC_Success; - }; - - constexpr const char* kMarker = "#type "; - bool matchingDirective = true; // If true, we are matching marker pattern; if false, we are accumulating shader variant identifier - int matchIndex = 0; // Current index of the pattern trying to match - std::string shaderVariant; - - // Don't use utf8 iterator, shader sources are expected to be ASCII only - for (size_t i = 0; i < source.size(); ++i) { - char c = source[i]; - - if (matchingDirective) { - if (c == kMarker[matchIndex]) { - // Matched the expected character, go to next char in pattern - matchIndex++; - - // If we are at the end of the marker pattern... - if (kMarker[matchIndex] == '\0') { - matchingDirective = false; - matchIndex = 0; - continue; - } - - // This might be a shader variant directive -> might be end of a section - if (c == '#') { - prevEnd = i; - continue; - } - } else { - // Unexpected character, rollback to beginning - matchIndex = 0; - } - } else { - if (c == '\n') { - // Found complete shader variant directive - - CATCH_ERROR(CommitSection()); // Try commit section, for the first apparent of #type this should do nothing, as `prevEnd` will still be -1 - prevBegin = i + 1; // +1 to skip new line (technically not needed) - prevShaderVariant = std::move(shaderVariant); - - matchingDirective = true; - shaderVariant.clear(); - } else { - // Simply accumulate to shader variant buffer - shaderVariant += c; - } - } - } - - // Commit the last section - prevEnd = static_cast<int>(source.size()); - CATCH_ERROR(CommitSection()); - - GLuint program = glCreateProgram(); - ScopeGuard sg = [&]() { glDeleteProgram(program); }; - - if (vertex) glAttachShader(program, vertex); - if (geometry) glAttachShader(program, geometry); - if (tessControl) glAttachShader(program, tessControl); - if (tessEval) glAttachShader(program, tessEval); - if (fragment) glAttachShader(program, fragment); - - CATCH_ERROR(LinkShaderProgram(program)); - - sg.Dismiss(); - mProgram = program; - - InitAutoFills(*this); - - return EC_Success; -} - -#undef CATCH_ERROR - -namespace ProjectBrussel_UNITY_ID { -bool QueryMathInfo(GLenum type, GLenum& scalarType, int& width, int& height) { - auto DoOutput = [&](GLenum scalarTypeIn, int widthIn, int heightIn) { - width = widthIn; - height = heightIn; - scalarType = scalarTypeIn; - }; - - switch (type) { - case GL_FLOAT: - case GL_DOUBLE: - case GL_INT: - case GL_UNSIGNED_INT: - case GL_BOOL: { - DoOutput(type, 1, 1); - return true; - } - - case GL_FLOAT_VEC2: DoOutput(GL_FLOAT, 1, 2); return true; - case GL_FLOAT_VEC3: DoOutput(GL_FLOAT, 1, 3); return true; - case GL_FLOAT_VEC4: DoOutput(GL_FLOAT, 1, 4); return true; - case GL_DOUBLE_VEC2: DoOutput(GL_DOUBLE, 1, 2); return true; - case GL_DOUBLE_VEC3: DoOutput(GL_DOUBLE, 1, 3); return true; - case GL_DOUBLE_VEC4: DoOutput(GL_DOUBLE, 1, 4); return true; - case GL_INT_VEC2: DoOutput(GL_INT, 1, 2); return true; - case GL_INT_VEC3: DoOutput(GL_INT, 1, 3); return true; - case GL_INT_VEC4: DoOutput(GL_INT, 1, 4); return true; - case GL_UNSIGNED_INT_VEC2: DoOutput(GL_UNSIGNED_INT, 1, 2); return true; - case GL_UNSIGNED_INT_VEC3: DoOutput(GL_UNSIGNED_INT, 1, 3); return true; - case GL_UNSIGNED_INT_VEC4: DoOutput(GL_UNSIGNED_INT, 1, 4); return true; - case GL_BOOL_VEC2: DoOutput(GL_BOOL, 1, 2); return true; - case GL_BOOL_VEC3: DoOutput(GL_BOOL, 1, 3); return true; - case GL_BOOL_VEC4: DoOutput(GL_BOOL, 1, 4); return true; - - case GL_FLOAT_MAT2: DoOutput(GL_FLOAT, 2, 2); return true; - case GL_FLOAT_MAT3: DoOutput(GL_FLOAT, 3, 3); return true; - case GL_FLOAT_MAT4: DoOutput(GL_FLOAT, 4, 4); return true; - case GL_FLOAT_MAT2x3: DoOutput(GL_FLOAT, 2, 3); return true; - case GL_FLOAT_MAT2x4: DoOutput(GL_FLOAT, 2, 4); return true; - case GL_FLOAT_MAT3x2: DoOutput(GL_FLOAT, 3, 2); return true; - case GL_FLOAT_MAT3x4: DoOutput(GL_FLOAT, 3, 4); return true; - case GL_FLOAT_MAT4x2: DoOutput(GL_FLOAT, 4, 2); return true; - case GL_FLOAT_MAT4x3: DoOutput(GL_FLOAT, 4, 3); return true; - - case GL_DOUBLE_MAT2: DoOutput(GL_DOUBLE, 2, 2); return true; - case GL_DOUBLE_MAT3: DoOutput(GL_DOUBLE, 3, 3); return true; - case GL_DOUBLE_MAT4: DoOutput(GL_DOUBLE, 4, 4); return true; - case GL_DOUBLE_MAT2x3: DoOutput(GL_DOUBLE, 2, 3); return true; - case GL_DOUBLE_MAT2x4: DoOutput(GL_DOUBLE, 2, 4); return true; - case GL_DOUBLE_MAT3x2: DoOutput(GL_DOUBLE, 3, 2); return true; - case GL_DOUBLE_MAT3x4: DoOutput(GL_DOUBLE, 3, 4); return true; - case GL_DOUBLE_MAT4x2: DoOutput(GL_DOUBLE, 4, 2); return true; - case GL_DOUBLE_MAT4x3: DoOutput(GL_DOUBLE, 4, 3); return true; - - default: break; - } - - return false; -} - -bool QuerySamplerInfo(GLenum type) { - switch (type) { - case GL_SAMPLER_1D: - case GL_SAMPLER_2D: - case GL_SAMPLER_3D: - case GL_SAMPLER_CUBE: - case GL_SAMPLER_1D_SHADOW: - case GL_SAMPLER_2D_SHADOW: - case GL_SAMPLER_1D_ARRAY: - case GL_SAMPLER_2D_ARRAY: - case GL_SAMPLER_1D_ARRAY_SHADOW: - case GL_SAMPLER_2D_ARRAY_SHADOW: - case GL_SAMPLER_2D_MULTISAMPLE: - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_SAMPLER_CUBE_SHADOW: - case GL_SAMPLER_BUFFER: - case GL_SAMPLER_2D_RECT: - case GL_SAMPLER_2D_RECT_SHADOW: - - case GL_INT_SAMPLER_1D: - case GL_INT_SAMPLER_2D: - case GL_INT_SAMPLER_3D: - case GL_INT_SAMPLER_CUBE: - case GL_INT_SAMPLER_1D_ARRAY: - case GL_INT_SAMPLER_2D_ARRAY: - case GL_INT_SAMPLER_2D_MULTISAMPLE: - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_INT_SAMPLER_BUFFER: - case GL_INT_SAMPLER_2D_RECT: - - case GL_UNSIGNED_INT_SAMPLER_1D: - case GL_UNSIGNED_INT_SAMPLER_2D: - case GL_UNSIGNED_INT_SAMPLER_3D: - case GL_UNSIGNED_INT_SAMPLER_CUBE: - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_BUFFER: - case GL_UNSIGNED_INT_SAMPLER_2D_RECT: - return true; - - default: break; - } - - return false; -} - -std::variant<ShaderMathVariable, ShaderSamplerVariable> CreateVariable(GLenum type, GLuint loc) { - GLenum scalarType; - int width; - int height; - if (QueryMathInfo(type, scalarType, width, height)) { - ShaderMathVariable res; - res.location = loc; - res.scalarType = type; - res.width = width; - res.height = height; - return res; - } - - if (QuerySamplerInfo(type)) { - ShaderSamplerVariable res; - res.location = loc; - res.samplerType = type; - return res; - } - - throw std::runtime_error(fmt::format("Unknown OpenGL shader uniform type {}", type)); -} -} // namespace ProjectBrussel_UNITY_ID - -bool Shader::GatherInfoShaderIntrospection() { - using namespace ProjectBrussel_UNITY_ID; - - mInfo = {}; - - // TODO handle differnt types of variables with the same name - - // TODO work with OpenGL < 4.3, possibly with glslang - return true; - - int inputCount; - glGetProgramInterfaceiv(mProgram, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount); - int outputCount; - glGetProgramInterfaceiv(mProgram, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &outputCount); - int uniformBlockCount; - glGetProgramInterfaceiv(mProgram, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &uniformBlockCount); - int uniformCount; - glGetProgramInterfaceiv(mProgram, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount); - - // Gather inputs - auto GatherMathVars = [&](int count, GLenum resourceType, ShaderThingId::Kind resourceKind, std::vector<ShaderMathVariable>& list) { - for (int i = 0; i < count; ++i) { - const GLenum query[] = { GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE }; - GLint props[std::size(query)]; - glGetProgramResourceiv(mProgram, resourceType, i, std::size(query), query, std::size(props), nullptr, props); - auto& nameLength = props[0]; - auto& type = props[1]; - auto& loc = props[2]; - auto& arrayLength = props[3]; - - std::string fieldName(nameLength - 1, '\0'); - glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); - - mInfo.things.try_emplace(fieldName, ShaderThingId{ resourceKind, i }); - - auto& thing = list.emplace_back(); - thing.name = std::move(fieldName); - thing.arrayLength = arrayLength; - QueryMathInfo(type, thing.scalarType, thing.width, thing.height); - } - }; - GatherMathVars(inputCount, GL_PROGRAM_INPUT, ShaderThingId::KD_Input, mInfo.inputs); - GatherMathVars(outputCount, GL_PROGRAM_OUTPUT, ShaderThingId::KD_Output, mInfo.outputs); - - // Gather uniform variables - for (int i = 0; i < uniformCount; ++i) { - const GLenum query[] = { GL_BLOCK_INDEX, GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE }; - GLint props[std::size(query)]; - glGetProgramResourceiv(mProgram, GL_UNIFORM, i, std::size(query), query, std::size(props), nullptr, props); - auto& blockIndex = props[0]; // Index in interface block - if (blockIndex != -1) { // If this is an interface block uniform, skip because it will be handled by our uniform blocks inspector - continue; - } - auto& nameLength = props[1]; - auto& type = props[2]; - auto& loc = props[3]; - auto& arrayLength = props[4]; - - std::string fieldName(nameLength - 1, '\0'); - glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); - - mInfo.things.try_emplace(fieldName, ShaderThingId{ ShaderThingId::KD_Uniform, i }); - mInfo.uniforms.push_back(CreateVariable(type, loc)); - } - - return true; -} - -bool Shader::IsValid() const { - return mProgram != 0; -} - -IresShader::IresShader() - : IresObject(KD_Shader) { - InvalidateInstance(); -} - -Shader* IresShader::GetInstance() const { - return mInstance.Get(); -} - -void IresShader::InvalidateInstance() { - if (mInstance != nullptr) { - mInstance->mIres = nullptr; - } - mInstance.Attach(new Shader()); - mInstance->mIres = this; -} - -void IresShader::ShowEditor(IEditor& editor) { - using namespace Tags; - using namespace ProjectBrussel_UNITY_ID; - - IresObject::ShowEditor(editor); - - if (ImGui::Button("Gather info")) { - mInstance->GatherInfoShaderIntrospection(); - } - - if (ImGui::InputText("Source file", &mSourceFile, ImGuiInputTextFlags_EnterReturnsTrue)) { - InvalidateInstance(); - } - // In other cases, mSourceFile will be reverted to before edit - - // TODO macros - - ImGui::Separator(); - - auto& info = mInstance->GetInfo(); - if (ImGui::CollapsingHeader("General")) { - ImGui::Text("OpenGL program ID: %u", mInstance->GetProgram()); - } - if (ImGui::CollapsingHeader("Inputs")) { - for (auto& input : info.inputs) { - input.ShowInfo(); - } - } - if (ImGui::CollapsingHeader("Outputs")) { - for (auto& output : info.outputs) { - output.ShowInfo(); - } - } - if (ImGui::CollapsingHeader("Uniforms")) { - for (auto& uniform : info.uniforms) { - std::visit([](auto&& v) { v.ShowInfo(); }, uniform); - } - if (auto loc = mInstance->autofill_Transform; loc != kInvalidLocation) { - ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnTransform); - } - if (auto loc = mInstance->autofill_Time; loc != kInvalidLocation) { - ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnTime); - } - if (auto loc = mInstance->autofill_DeltaTime; loc != kInvalidLocation) { - ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnDeltaTime); - } - if (auto loc = mInstance->autofill_TextureAtlas; loc != kInvalidLocation) { - ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnTextureAtlas); - } - } -} - -void IresShader::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { - using namespace ProjectBrussel_UNITY_ID; - - IresObject::Write(ctx, value, root); - json_dto::json_output_t out( value, root.GetAllocator() ); - out << mInstance->mInfo; -} - -void IresShader::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { - using namespace ProjectBrussel_UNITY_ID; - - IresObject::Read(ctx, value); - - auto rvSourceFile = rapidjson::GetProperty(value, rapidjson::kStringType, "SourceFile"sv); - if (!rvSourceFile) { - return; - } else { - this->mSourceFile = rapidjson::AsString(*rvSourceFile); - - char shaderFilePath[256]; - snprintf(shaderFilePath, sizeof(shaderFilePath), "%s/%s", AppConfig::assetDir.c_str(), rvSourceFile->GetString()); - - auto shaderFile = Utils::OpenCstdioFile(shaderFilePath, Utils::Read); - if (!shaderFile) return; - DEFER { - fclose(shaderFile); - }; - - fseek(shaderFile, 0, SEEK_END); - auto shaderFileSize = ftell(shaderFile); - rewind(shaderFile); - - // Also add \0 ourselves - auto buffer = std::make_unique<char[]>(shaderFileSize + 1); - fread(buffer.get(), shaderFileSize, 1, shaderFile); - buffer[shaderFileSize] = '\0'; - std::string_view source(buffer.get(), shaderFileSize); - - if (mInstance->InitFromSource(source) != Shader::EC_Success) { - return; - } - } - - auto& shaderInfo = mInstance->mInfo; - auto shaderProgram = mInstance->GetProgram(); - - auto LoadMathVars = [&](std::string_view name, ShaderThingId::Kind kind, std::vector<ShaderMathVariable>& vars) { - auto rvThings = rapidjson::GetProperty(value, rapidjson::kArrayType, name); - if (!rvThings) return; - - for (auto& rv : rvThings->GetArray()) { - if (!rv.IsObject()) continue; - ShaderMathVariable thing; - ReadShaderMathVariable(rv, thing); - - shaderInfo.things.try_emplace(thing.name, ShaderThingId{ kind, (int)vars.size() }); - vars.push_back(std::move(thing)); - } - }; - LoadMathVars("Inputs"sv, ShaderThingId::KD_Input, shaderInfo.inputs); - LoadMathVars("Outputs"sv, ShaderThingId::KD_Output, shaderInfo.outputs); - - auto rvUniforms = rapidjson::GetProperty(value, rapidjson::kArrayType, "Uniforms"sv); - if (!rvUniforms) return; - for (auto& rvUniform : rvUniforms->GetArray()) { - if (!rvUniform.IsObject()) continue; - - auto rvType = rapidjson::GetProperty(rvUniform, rapidjson::kStringType, "Type"sv); - if (!rvType) continue; - auto type = rapidjson::AsStringView(*rvType); - - auto rvValue = rapidjson::GetProperty(rvUniform, rapidjson::kObjectType, "Value"sv); - if (!rvValue) continue; - - bool autoFill; // TODO store autofill uniforms somewhere else - BRUSSEL_JSON_GET_DEFAULT(rvUniform, "AutoFill", bool, autoFill, false); - if (autoFill) continue; - - auto uniform = [&]() -> std::unique_ptr<ShaderVariable> { - if (type == "Math"sv) { - auto uniform = std::make_unique<ShaderMathVariable>(); - ReadShaderMathVariable(*rvValue, *uniform); - - return uniform; - } else if (type == "Sampler"sv) { - auto uniform = std::make_unique<ShaderSamplerVariable>(); - ReadShaderSamplerVariable(*rvValue, *uniform); - - return uniform; - } - - return nullptr; - }(); - if (uniform) { - shaderInfo.things.try_emplace(uniform->name, ShaderThingId{ ShaderThingId::KD_Uniform, (int)shaderInfo.uniforms.size() }); - shaderInfo.uniforms.emplace_back(std::move(uniform)); - } - } - - for (auto& uniform : shaderInfo.uniforms) { - uniform->location = glGetUniformLocation(shaderProgram, uniform->name.c_str()); - } -} diff --git a/source/30-game/Shader.hpp b/source/30-game/Shader.hpp deleted file mode 100644 index cb980cd..0000000 --- a/source/30-game/Shader.hpp +++ /dev/null @@ -1,180 +0,0 @@ -#pragma once - -#include "GraphicsTags.hpp" -#include "Ires.hpp" -#include "RcPtr.hpp" -#include "Utils.hpp" - -#include <glad/glad.h> -#include <robin_hood.h> -#include <json_dto/pub.hpp> -#include <memory> -#include <string_view> -#include <variant> -#include <vector> - -// TODO move to variable after pattern matching is in the language - -// Forward declarations -class Shader; -class IresShader; - -struct ShaderMathVariable { - std::string name; - GLuint location; - Tags::VertexElementSemantic semantic = Tags::VES_Generic; - GLenum scalarType; - int width; - int height; - int arrayLength = 1; - - void ShowInfo() const; - - template <typename TJsonIo> - void json_io(TJsonIo& io) { - io& json_dto::mandatory("Name", name); - io& json_dto::mandatory("Semantic", static_cast<int>(semantic)); - io& json_dto::mandatory("ScalarType", scalarType); - io& json_dto::mandatory("Width", width); - io& json_dto::mandatory("Height", height); - io& json_dto::optional("ArrayLength", arrayLength, 1); - } -}; - -struct ShaderSamplerVariable { - std::string name; - GLuint location; - Tags::VertexElementSemantic semantic = Tags::VES_Generic; - GLenum samplerType; - int arrayLength = 1; - - void ShowInfo() const; - - template <typename TJsonIo> - void json_io(TJsonIo& io) { - io& json_dto::mandatory("Name", name); - io& json_dto::mandatory("Semantic", static_cast<int>(semantic)); - io& json_dto::mandatory("SamplerType", samplerType); - io& json_dto::optional("ArrayLength", arrayLength, 1); - } -}; - -struct ShaderThingId { - enum Kind { - KD_Input, - KD_Output, - KD_Uniform, - }; - - Kind kind; - int index; -}; - -struct ShaderInfo { - robin_hood::unordered_map<std::string, ShaderThingId, StringHash, StringEqual> things; - std::vector<ShaderMathVariable> inputs; - std::vector<ShaderMathVariable> outputs; - std::vector<std::variant<ShaderMathVariable, ShaderSamplerVariable>> uniforms; - - // Find the first variable with the matching semantic - GLuint FindInputLocation(Tags::VertexElementSemantic semantic); - GLuint FindOutputLocation(Tags::VertexElementSemantic semantic); - - template <typename TJsonIo> - void json_io(TJsonIo& io) { - io& json_dto::mandatory("Inputs", inputs); - io& json_dto::mandatory("Outputs", outputs); - // TODO make json_dto support std::variant -// io& json_dto::mandatory("Uniforms", uniforms); - } -}; - -class Shader : public RefCounted { - friend class IresShader; - -private: - IresShader* mIres = nullptr; - ShaderInfo mInfo; - GLuint mProgram = 0; - -public: - GLuint autofill_Transform = Tags::kInvalidLocation; - GLuint autofill_Time = Tags::kInvalidLocation; - GLuint autofill_DeltaTime = Tags::kInvalidLocation; - GLuint autofill_TextureAtlas = Tags::kInvalidLocation; - -public: - Shader(); - ~Shader(); - Shader(const Shader&) = delete; - Shader& operator=(const Shader&) = delete; - Shader(Shader&&) = default; - Shader& operator=(Shader&&) = default; - - enum ErrorCode { - EC_Success, - /// Generated when Init*() functions are called on an already initialized Shader object. - EC_AlreadyInitialized, - /// Generated when the one-source-file text contains invalid or duplicate shader variants. - EC_InvalidShaderVariant, - EC_FileIoFailed, - EC_CompilationFailed, - EC_LinkingFailed, - }; - - struct ShaderSources { - std::string_view vertex; - std::string_view geometry; - std::string_view tessControl; - std::string_view tessEval; - std::string_view fragment; - }; - - /// Create shader by compiling each shader source file separately and then combining them together - /// into a Shader object. - ErrorCode InitFromSources(const ShaderSources& sources); - - /// 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 - /// together. - /// - /// There are a directive for each shader type - /// - `#type vertex`: Vertex shader - /// - `#type geometry`: Geometry shader - /// - `#type tessellation_control`: Tessellation control shader - /// - `#type tessellation_evaluation`: Tessellation evaluation shader - /// - `#type fragment`: Fragment shader - ErrorCode InitFromSource(std::string_view source); - - /// Rebuild info object using OpenGL shader introspection API. Requires OpenGL 4.3 or above. Overrides existing info object. - bool GatherInfoShaderIntrospection(); - const ShaderInfo& GetInfo() const { return mInfo; } - ShaderInfo& GetInfo() { return mInfo; } - /// If not empty, this name must not duplicate with any other shader object in the process. - GLuint GetProgram() const { return mProgram; } - - IresShader* GetIres() const { return mIres; } - - bool IsValid() const; -}; - -// Initialized in main() -inline RcPtr<Shader> gDefaultShader; - -class IresShader : public IresObject { -private: - RcPtr<Shader> mInstance; - std::string mSourceFile; - -public: - IresShader(); - - Shader* GetInstance() const; - void InvalidateInstance(); - - void ShowEditor(IEditor& editor) override; - - void Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const override; - void Read(IresLoadingContext& ctx, const rapidjson::Value& value) override; -}; diff --git a/source/30-game/Sprite.cpp b/source/30-game/Sprite.cpp deleted file mode 100644 index 2b4923c..0000000 --- a/source/30-game/Sprite.cpp +++ /dev/null @@ -1,328 +0,0 @@ -#include "Sprite.hpp" - -#include "AppConfig.hpp" -#include "CommonVertexIndex.hpp" -#include "EditorCore.hpp" -#include "EditorUtils.hpp" -#include "Image.hpp" -#include "RapidJsonHelper.hpp" -#include "Rect.hpp" - -#include <imgui.h> -#include <misc/cpp/imgui_stdlib.h> -#include <rapidjson/document.h> -#include <memory> - -using namespace std::literals; - -bool SpriteDefinition::IsValid() const { - return mAtlas != nullptr; -} - -bool IresSpriteFiles::IsValid() const { - return !spriteFiles.empty(); -} - -SpriteDefinition* IresSpriteFiles::CreateInstance() const { - if (IsValid()) { - return nullptr; - } - - std::vector<Texture::AtlasSource> sources; - sources.resize(spriteFiles.size()); - for (auto& file : spriteFiles) { - } - - Texture::AtlasOutput atlasOut; - Texture::AtlasInput atlasIn{ - .sources = sources, - .packingMode = Texture::PM_KeepSquare, - }; - atlasIn.sources = sources; - auto atlas = std::make_unique<Texture>(); - if (atlas->InitAtlas(atlasIn, &atlasOut) != Texture::EC_Success) { - return nullptr; - } - - auto sprite = std::make_unique<SpriteDefinition>(); - sprite->mAtlas.Attach(atlas.release()); - sprite->mBoundingBox = atlasOut.elements[0].subregionSize; - sprite->mFrames.reserve(atlasOut.elements.size()); - for (auto& elm : atlasOut.elements) { - // Validate bounding box - if (sprite->mBoundingBox != elm.subregionSize) { - return nullptr; - } - - // Copy frame subregion - sprite->mFrames.push_back(elm.subregion); - } - return sprite.release(); -} - -SpriteDefinition* IresSpriteFiles::GetInstance() { - if (mInstance == nullptr) { - mInstance.Attach(CreateInstance()); - } - return mInstance.Get(); -} - -void IresSpriteFiles::InvalidateInstance() { - mInstance.Attach(nullptr); -} - -void IresSpriteFiles::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { - IresObject::Write(ctx, value, root); - value.AddMember("Sprites", rapidjson::WriteVectorPrimitives(root, spriteFiles.begin(), spriteFiles.end()), root.GetAllocator()); -} - -void IresSpriteFiles::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { - InvalidateInstance(); - - IresObject::Read(ctx, value); - - auto rvFileList = rapidjson::GetProperty(value, rapidjson::kArrayType, "Sprites"sv); - if (!rvFileList) return; - spriteFiles.clear(); - rapidjson::ReadVectorPrimitives<decltype(spriteFiles)>(*rvFileList, spriteFiles); -} - -bool IresSpritesheet::IsValid() const { - return !spritesheetFile.empty() && - sheetWSplit != 0 && - sheetHSplit != 0; -} - -void IresSpritesheet::ResplitSpritesheet(SpriteDefinition* sprite, const IresSpritesheet* conf) { - ResplitSpritesheet(sprite, conf->sheetWSplit, conf->sheetHSplit, conf->frameCountOverride); -} - -void IresSpritesheet::ResplitSpritesheet(SpriteDefinition* sprite, int wSplit, int hSplit, int frameCount) { - auto atlas = sprite->GetAtlas(); - auto size = atlas->GetInfo().size; - int frameWidth = size.x / wSplit; - int frameHeight = size.y / hSplit; - - sprite->mBoundingBox = { frameWidth, frameHeight }; - sprite->mFrames.clear(); - sprite->mFrames.reserve(wSplit * hSplit); - - // Width and height in UV coordinates for each frame - float deltaU = 1.0f / wSplit; - float deltaV = 1.0f / hSplit; - int i = 0; - if (frameCount < 0) { - frameCount = std::numeric_limits<int>::max(); - } - for (int y = 0; y < hSplit; ++y) { - for (int x = 0; x < wSplit; ++x) { - auto& subregion = sprite->mFrames.emplace_back(); - // Top left - subregion.u0 = deltaU * x; - subregion.v0 = deltaV * y; - // Bottom right - subregion.u1 = subregion.u0 + deltaU; - subregion.v1 = subregion.v0 + deltaV; - - if ((i + 1) >= frameCount) { - return; - } - - ++i; - } - } -} - -SpriteDefinition* IresSpritesheet::CreateInstance() const { - if (!IsValid()) { - return nullptr; - } - - char path[2048]; - snprintf(path, sizeof(path), "%s/%s", AppConfig::assetDir.c_str(), spritesheetFile.c_str()); - - auto atlas = std::make_unique<Texture>(); - if (atlas->InitFromFile(path) != Texture::EC_Success) { - return nullptr; - } - - auto sprite = std::make_unique<SpriteDefinition>(); - sprite->mAtlas.Attach(atlas.release()); - ResplitSpritesheet(sprite.get(), this); - return sprite.release(); -} - -SpriteDefinition* IresSpritesheet::GetInstance() { - if (mInstance == nullptr) { - mInstance.Attach(CreateInstance()); - } - return mInstance.Get(); -} - -void IresSpritesheet::InvalidateInstance() { - mInstance.Attach(nullptr); -} - -bool IresSpritesheet::IsFrameCountOverriden() const { - return frameCountOverride > 0; -} - -int IresSpritesheet::GetFrameCount() const { - if (IsFrameCountOverriden()) { - return frameCountOverride; - } else { - return sheetWSplit * sheetHSplit; - } -} - -void IresSpritesheet::ShowEditor(IEditor& editor) { - IresObject::ShowEditor(editor); - - bool doInvalidateInstance = false; - auto instance = GetInstance(); // NOTE: may be null - - if (ImGui::Button("View Sprite", instance == nullptr)) { - editor.OpenSpriteViewer(instance); - } - - if (ImGui::InputText("Spritesheet", &spritesheetFile)) { - doInvalidateInstance = true; - } - - if (ImGui::InputInt("Horizontal split", &sheetWSplit)) { - sheetWSplit = std::max(sheetWSplit, 1); - if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); - } - - if (ImGui::InputInt("Vertical split", &sheetHSplit)) { - sheetHSplit = std::max(sheetHSplit, 1); - if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); - } - - bool frameCountOverriden = frameCountOverride > 0; - if (ImGui::Checkbox("##", &frameCountOverriden)) { - if (frameCountOverriden) { - frameCountOverride = sheetWSplit * sheetHSplit; - } else { - frameCountOverride = 0; - } - } - ImGui::SameLine(); - if (frameCountOverriden) { - if (ImGui::InputInt("Frame count", &frameCountOverride)) { - frameCountOverride = std::max(frameCountOverride, 1); - if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); - } - } else { - int dummy = sheetWSplit * sheetHSplit; - ImGui::PushDisabled(); - ImGui::InputInt("Frame count", &dummy, ImGuiInputTextFlags_ReadOnly); - ImGui::PopDisabled(); - } - - if (instance) { - auto atlas = instance->GetAtlas(); - auto imageSize = Utils::FitImage(atlas->GetInfo().size); - auto imagePos = ImGui::GetCursorScreenPos(); - ImGui::Image((ImTextureID)(uintptr_t)atlas->GetHandle(), imageSize); - - auto drawlist = ImGui::GetWindowDrawList(); - float deltaX = imageSize.x / sheetWSplit; - for (int ix = 0; ix < sheetWSplit + 1; ++ix) { - float x = ix * deltaX; - ImVec2 start{ imagePos.x + x, imagePos.y }; - ImVec2 end{ imagePos.x + x, imagePos.y + imageSize.y }; - drawlist->AddLine(start, end, IM_COL32(255, 255, 0, 255)); - } - float deltaY = imageSize.y / sheetHSplit; - for (int iy = 0; iy < sheetHSplit + 1; ++iy) { - float y = iy * deltaY; - ImVec2 start{ imagePos.x, imagePos.y + y }; - ImVec2 end{ imagePos.x + imageSize.x, imagePos.y + y }; - drawlist->AddLine(start, end, IM_COL32(255, 255, 0, 255)); - } - - int i = sheetWSplit * sheetHSplit; - int frameCount = GetFrameCount(); - for (int y = sheetHSplit - 1; y >= 0; --y) { - for (int x = sheetWSplit - 1; x >= 0; --x) { - if (i > frameCount) { - ImVec2 tl{ imagePos.x + x * deltaX + 1.0f, imagePos.y + y * deltaY + 1.0f }; - ImVec2 br{ imagePos.x + (x + 1) * deltaX, imagePos.y + (y + 1) * deltaY }; - drawlist->AddRectFilled(tl, br, IM_COL32(255, 0, 0, 100)); - } - --i; - } - } - } else { - ImGui::TextUnformatted("Sprite configuration invalid"); - } - - if (doInvalidateInstance) { - InvalidateInstance(); - } -} - -void IresSpritesheet::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { - IresObject::Write(ctx, value, root); - value.AddMember("SpriteSheet", spritesheetFile, root.GetAllocator()); - value.AddMember("WSplit", sheetWSplit, root.GetAllocator()); - value.AddMember("HSplit", sheetHSplit, root.GetAllocator()); - if (frameCountOverride > 0) { - value.AddMember("FrameCount", frameCountOverride, root.GetAllocator()); - } -} - -void IresSpritesheet::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { - InvalidateInstance(); - - IresObject::Read(ctx, value); - BRUSSEL_JSON_GET(value, "SpriteSheet", std::string, spritesheetFile, return ); - BRUSSEL_JSON_GET(value, "WSplit", int, sheetWSplit, return ); - BRUSSEL_JSON_GET(value, "HSplit", int, sheetHSplit, return ); - BRUSSEL_JSON_GET_DEFAULT(value, "FrameCount", int, frameCountOverride, 0); -} - -Sprite::Sprite() - : mDefinition(nullptr) { -} - -bool Sprite::IsValid() const { - return mDefinition != nullptr; -} - -void Sprite::SetDefinition(SpriteDefinition* definition) { - mDefinition.Attach(definition); - mCurrentFrame = 0; -} - -int Sprite::GetFrame() const { - return mCurrentFrame; -} - -const Subregion& Sprite::GetFrameSubregion() const { - return mDefinition->GetFrames()[mCurrentFrame]; -} - -void Sprite::SetFrame(int frame) { - mCurrentFrame = frame; -} - -void Sprite::PlayFrame() { - ++mTimeElapsed; - if (mTimeElapsed >= mPlaybackSpeed) { - mTimeElapsed -= mPlaybackSpeed; - - int frameCount = mDefinition->GetFrames().size(); - int nextFrame = (mCurrentFrame + 1) % frameCount; - SetFrame(nextFrame); - } -} - -int Sprite::GetPlaybackSpeed() const { - return mPlaybackSpeed; -} - -void Sprite::SetPlaybackSpeed(int speed) { - // TODO -} diff --git a/source/30-game/Sprite.hpp b/source/30-game/Sprite.hpp deleted file mode 100644 index e163a01..0000000 --- a/source/30-game/Sprite.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include "Ires.hpp" -#include "PodVector.hpp" -#include "RcPtr.hpp" -#include "Renderer.hpp" -#include "Texture.hpp" - -#include <rapidjson/fwd.h> -#include <glm/glm.hpp> -#include <string> -#include <string_view> -#include <vector> - -class SpriteDefinition : public RefCounted { - friend class IresSpriteFiles; - friend class IresSpritesheet; - -private: - RcPtr<Texture> mAtlas; - glm::ivec2 mBoundingBox; - std::vector<Subregion> mFrames; - -public: - bool IsValid() const; - Texture* GetAtlas() const { return mAtlas.Get(); } - glm::ivec2 GetBoundingBox() const { return mBoundingBox; } - const decltype(mFrames)& GetFrames() const { return mFrames; } -}; - -class IresSpriteFiles : public IresObject { -public: - RcPtr<SpriteDefinition> mInstance; - std::vector<std::string> spriteFiles; - -public: - IresSpriteFiles() - : IresObject(KD_SpriteFiles) {} - - // NOTE: does not check whether all specified files have the same dimensions - bool IsValid() const; - - SpriteDefinition* CreateInstance() const; - SpriteDefinition* GetInstance(); - void InvalidateInstance(); - - void Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const override; - void Read(IresLoadingContext& ctx, const rapidjson::Value& value) override; -}; - -class IresSpritesheet : public IresObject { -public: - RcPtr<SpriteDefinition> mInstance; - std::string spritesheetFile; - int sheetWSplit = 1; - int sheetHSplit = 1; - int frameCountOverride = 0; - -public: - IresSpritesheet() - : IresObject(KD_Spritesheet) {} - - bool IsValid() const; - - static void ResplitSpritesheet(SpriteDefinition* sprite, const IresSpritesheet* conf); - static void ResplitSpritesheet(SpriteDefinition* sprite, int wSplit, int hSplit, int frameCountOverride = -1); - - SpriteDefinition* CreateInstance() const; - SpriteDefinition* GetInstance(); - void InvalidateInstance(); - - bool IsFrameCountOverriden() const; - int GetFrameCount() const; - - void ShowEditor(IEditor& editor) override; - - void Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const override; - void Read(IresLoadingContext& ctx, const rapidjson::Value& value) override; -}; - -// TODO -class SpriteCollection { -private: - std::vector<SpriteDefinition> mSprites; -}; - -class Sprite { -private: - RcPtr<SpriteDefinition> mDefinition; - int mCurrentFrame = 0; - int mTimeElapsed = 0; - // # of frames per second - int mPlaybackSpeed = 5; - -public: - Sprite(); - - bool IsValid() const; - - SpriteDefinition* GetDefinition() const { return mDefinition.Get(); } - void SetDefinition(SpriteDefinition* definition); - - int GetFrame() const; - const Subregion& GetFrameSubregion() const; - void SetFrame(int frame); - // Update as if a render frame has passed - void PlayFrame(); - - int GetPlaybackSpeed() const; - void SetPlaybackSpeed(int speed); -}; diff --git a/source/30-game/Texture.cpp b/source/30-game/Texture.cpp deleted file mode 100644 index 6fa7c8a..0000000 --- a/source/30-game/Texture.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include "Texture.hpp" - -#include "Macros.hpp" -#include "PodVector.hpp" -#include "ScopeGuard.hpp" - -#include <stb_image.h> -#include <stb_rect_pack.h> -#include <bit> -#include <cstring> -#include <utility> - -Texture::~Texture() { - glDeleteTextures(1, &mHandle); -} - -static GLenum MapTextureFilteringToGL(Tags::TexFilter option) { - using namespace Tags; - switch (option) { - case TF_Linear: return GL_LINEAR; - case TF_Nearest: return GL_NEAREST; - } - return 0; -} - -Texture::ErrorCode Texture::InitFromFile(const char* filePath) { - if (IsValid()) { - return EC_AlreadyInitialized; - } - - int width, height; - int channels; - - auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, 4); - if (!result) { - return EC_FileIoFailed; - } - DEFER { stbi_image_free(result); }; - - glGenTextures(1, &mHandle); - glBindTexture(GL_TEXTURE_2D, mHandle); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result); - - mInfo.size = { width, height }; - - return EC_Success; -} - -Texture::ErrorCode Texture::InitFromImage(const Image& image) { - if (IsValid()) { - return EC_AlreadyInitialized; - } - - GLenum sourceFormat; - switch (image.GetChannels()) { - case 1: sourceFormat = GL_RED; break; - case 2: sourceFormat = GL_RG; break; - case 3: sourceFormat = GL_RGB; break; - case 4: sourceFormat = GL_RGBA; break; - default: return EC_InvalidImage; - } - - auto size = image.GetSize(); - uint8_t* dataPtr = image.GetDataPtr(); - - glGenTextures(1, &mHandle); - glBindTexture(GL_TEXTURE_2D, mHandle); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.x, size.y, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr); - - mInfo.size = size; - - return EC_Success; -} - -Texture::ErrorCode Texture::InitAtlas(const AtlasInput& in, AtlasOutput* out) { - // Force RGBA for easier time uploading to GL texture - constexpr int kDesiredChannels = 4; - - PodVector<stbrp_rect> rects; - rects.resize(in.sources.size()); - - for (size_t i = 0; i < in.sources.size(); ++i) { - auto size = in.sources[i].image.GetSize(); - auto& rect = rects[i]; - rect.w = static_cast<stbrp_coord>(size.x); - rect.h = static_cast<stbrp_coord>(size.y); - } - - int atlasWidth; - int atlasHeight; - - // 1. Pack the candidate rectanges onto the (not yet allocated) atlas - // Note that the coordinates here are top-left origin - switch (in.packingMode) { - case PM_KeepSquare: { - atlasWidth = 512; - atlasHeight = 512; - - PodVector<stbrp_node> nodes; - while (true) { - // No need to zero initialize stbrp_node, library will take care of that - nodes.resize(atlasWidth); - - stbrp_context ctx; - stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], (int)nodes.size()); - int result = stbrp_pack_rects(&ctx, rects.data(), (int)rects.size()); - - if (result != 1) { - atlasWidth *= 2; - atlasHeight *= 2; - } else { - // Break out of the while loop - break; - } - } - } break; - - case PM_VerticalExtension: - case PM_HorizontalExtension: { - constexpr int kMaxHeight = 1024 * 32; - atlasWidth = 0; - atlasHeight = 0; - - PodVector<stbrp_node> nodes; - stbrp_context ctx; - stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], nodes.size()); - stbrp_pack_rects(&ctx, rects.data(), rects.size()); - - // Calculate width/height needed for atlas - auto& limiter = in.packingMode == PM_VerticalExtension ? atlasHeight : atlasWidth; - for (auto& rect : rects) { - int bottom = rect.y + rect.h; - limiter = std::max(limiter, bottom); - } - limiter = std::bit_ceil<uint32_t>(limiter); - } break; - } - - // 2. Allocate atlas bitmap - - // Number of bytes in *bitmap* - auto bytes = atlasWidth * atlasHeight * kDesiredChannels * sizeof(uint8_t); - // Note that the origin (first pixel) is the bottom-left corner, to be consistent with OpenGL - auto bitmap = std::make_unique<uint8_t[]>(bytes); - std::memset(bitmap.get(), 0, bytes * sizeof(uint8_t)); - - // 3. Put all candidate images to the atlas bitmap - // TODO don't flip - // We essentially flip the candidate images vertically when putting into the atlas bitmap, so that when OpenGL reads - // these bytes, it sees the "bottom row" (if talking in top-left origin) first - // (empty spots are set with 0, "flipping" doesn't apply to them) - // - // Conceptually, we flip the atlas bitmap vertically so that the origin is at bottom-left - // i.e. all the coordinates we talk (e.g. rect.x/y) are still in top-left origin - - // Unit: bytes - size_t bitmapRowStride = atlasWidth * kDesiredChannels * sizeof(uint8_t); - for (size_t i = 0; i < in.sources.size(); ++i) { - auto& rect = rects[i]; - // Data is assumed to be stored in top-left origin - auto data = in.sources[i].image.GetDataPtr(); - - // We need to copy row by row, because the candidate image bytes won't land in a continuous chunk in our atlas bitmap - // Unit: bytes - size_t incomingRowStride = rect.w * kDesiredChannels * sizeof(uint8_t); - // Unit: bytes - size_t bitmapX = rect.x * kDesiredChannels * sizeof(uint8_t); - for (int y = 0; y < rect.h; ++y) { - auto src = data + y * incomingRowStride; - - int bitmapY = y; - auto dst = bitmap.get() + bitmapY * bitmapRowStride + bitmapX; - - std::memcpy(dst, src, incomingRowStride); - } - } - - // 4. Upload to VRAM - GLuint atlasTexture; - glGenTextures(1, &atlasTexture); - glBindTexture(GL_TEXTURE_2D, atlasTexture); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, atlasWidth, atlasHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.get()); - - // 5. Generate atlas texture info - mHandle = atlasTexture; - mInfo.size = { atlasWidth, atlasHeight }; - - // 6. Generate output information - if (out) { - out->elements.reserve(in.sources.size()); - for (size_t i = 0; i < in.sources.size(); ++i) { - auto& rect = rects[i]; - auto& source = in.sources[i]; - out->elements.push_back(AltasElement{ - .name = source.name, - .subregion = Subregion{ - .u0 = (float)(rect.x) / atlasWidth, - .v0 = (float)(rect.y + rect.h) / atlasHeight, - .u1 = (float)(rect.x + rect.w) / atlasWidth, - .v1 = (float)(rect.y) / atlasHeight, - }, - .subregionSize = glm::ivec2(rect.w, rect.h), - }); - } - } - - return EC_Success; -} - -const TextureInfo& Texture::GetInfo() const { - return mInfo; -} - -GLuint Texture::GetHandle() const { - return mHandle; -} - -bool Texture::IsValid() const { - return mHandle != 0; -} - -Texture* IresTexture::CreateInstance() const { - return new Texture(); -} - -Texture* IresTexture::GetInstance() { - if (mInstance == nullptr) { - mInstance.Attach(CreateInstance()); - } - return mInstance.Get(); -} - -void IresTexture::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { - IresObject::Write(ctx, value, root); - // TODO -} - -void IresTexture::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { - IresObject::Read(ctx, value); - // TODO -} diff --git a/source/30-game/Texture.hpp b/source/30-game/Texture.hpp deleted file mode 100644 index 108dfa7..0000000 --- a/source/30-game/Texture.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include "GraphicsTags.hpp" -#include "Image.hpp" -#include "Ires.hpp" -#include "RcPtr.hpp" - -#include <glad/glad.h> -#include <cstdint> -#include <glm/glm.hpp> -#include <span> - -// TODO abstract texture traits such as component sizes from OpenGL - -struct Subregion { - float u0 = 0.0f; - float v0 = 0.0f; - float u1 = 0.0f; - float v1 = 0.0f; -}; - -struct TextureInfo { - glm::ivec2 size; - Tags::TexFilter minifyingFilter = Tags::TF_Linear; - Tags::TexFilter magnifyingFilter = Tags::TF_Linear; -}; - -class Texture : public RefCounted { - friend class TextureStitcher; - -private: - TextureInfo mInfo; - GLuint mHandle = 0; - -public: - Texture() = default; - ~Texture(); - - Texture(const Texture&) = delete; - Texture& operator=(const Texture&) = delete; - Texture(Texture&&) = default; - Texture& operator=(Texture&&) = default; - - enum ErrorCode { - EC_Success, - EC_AlreadyInitialized, - EC_FileIoFailed, - EC_InvalidImage, - }; - - ErrorCode InitFromFile(const char* filePath); - ErrorCode InitFromImage(const Image& image); - - struct AtlasSource { - std::string name; - Image image; - }; - - struct AltasElement { - std::string name; - Subregion subregion; - glm::ivec2 subregionSize; - }; - - enum PackingMode { - PM_KeepSquare, - PM_VerticalExtension, - PM_HorizontalExtension, - }; - - struct AtlasInput { - std::span<AtlasSource> sources; - PackingMode packingMode; - }; - struct AtlasOutput { - std::vector<AltasElement> elements; - }; - ErrorCode InitAtlas(const AtlasInput& in, AtlasOutput* out = nullptr); - - const TextureInfo& GetInfo() const; - GLuint GetHandle() const; - - bool IsValid() const; -}; - -class IresTexture : public IresObject { -public: - RcPtr<Texture> mInstance; - -public: - IresTexture() - : IresObject(KD_Texture) {} - - Texture* CreateInstance() const; - Texture* GetInstance(); - - void Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const override; - void Read(IresLoadingContext& ctx, const rapidjson::Value& value) override; -}; diff --git a/source/30-game/VertexIndex.cpp b/source/30-game/VertexIndex.cpp deleted file mode 100644 index ac68289..0000000 --- a/source/30-game/VertexIndex.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "VertexIndex.hpp" - -#include <algorithm> - -GpuVertexBuffer::GpuVertexBuffer() { - glGenBuffers(1, &handle); -} - -GpuVertexBuffer::~GpuVertexBuffer() { - glDeleteBuffers(1, &handle); -} - -void GpuVertexBuffer::Upload(const std::byte* data, size_t sizeInBytes) { - glBindBuffer(GL_ARRAY_BUFFER, handle); - glBufferData(GL_ARRAY_BUFFER, sizeInBytes, data, GL_DYNAMIC_DRAW); -} - -GpuIndexBuffer::GpuIndexBuffer() { - glGenBuffers(1, &handle); -} - -GpuIndexBuffer::~GpuIndexBuffer() { - glDeleteBuffers(1, &handle); -} - -void GpuIndexBuffer::Upload(const std::byte* data, Tags::IndexType type, size_t count) { - this->indexType = type; - this->count = count; - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * Tags::SizeOf(type), data, GL_DYNAMIC_DRAW); -} - -int BufferBindings::GetMaxBindingIndex() const { - return bindings.size() - 1; -} - -GpuVertexBuffer* BufferBindings::GetBinding(int index) const { - if (index >= 0 && index < bindings.size()) { - return bindings[index].Get(); - } else { - return nullptr; - } -} - -void BufferBindings::SetBinding(int index, GpuVertexBuffer* buffer) { - int maxBindingIndex = GetMaxBindingIndex(); - if (index > maxBindingIndex) { - int countDelta = index - maxBindingIndex; - bindings.resize(bindings.size() + countDelta); - } - - bindings[index].Attach(buffer); - if (index == maxBindingIndex && buffer == nullptr) { - bindings.pop_back(); - } -} - -void BufferBindings::Clear() { - bindings.clear(); -} - -int VertexElementFormat::GetStride() const { - return Tags::SizeOf(type); -} - -void VertexFormat::AddElement(VertexElementFormat element) { - vertexSize += element.GetStride(); - - int lastIdx = (int)elements.size() - 1; - if (lastIdx >= 0) { - auto& last = elements[lastIdx]; - element.offset = last.offset + last.GetStride(); - } else { - element.offset = 0; - } - - elements.push_back(std::move(element)); -} - -void VertexFormat::RemoveElement(int index) { - auto& element = elements[index]; - vertexSize -= element.GetStride(); - elements.erase(elements.begin() + index); -} diff --git a/source/30-game/VertexIndex.hpp b/source/30-game/VertexIndex.hpp deleted file mode 100644 index 2d65617..0000000 --- a/source/30-game/VertexIndex.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "GraphicsTags.hpp" -#include "RcPtr.hpp" -#include "SmallVector.hpp" - -#include <glad/glad.h> -#include <cstddef> -#include <cstdint> -#include <vector> - -struct GpuVertexBuffer : public RefCounted { - GLuint handle; - int sizeInBytes; - - GpuVertexBuffer(); - ~GpuVertexBuffer(); - - void Upload(const std::byte* data, size_t sizeInBytes); -}; - -struct GpuIndexBuffer : public RefCounted { - GLuint handle; - Tags::IndexType indexType; - int count; - - GpuIndexBuffer(); - ~GpuIndexBuffer(); - - Tags::IndexType GetIndexType() const { return indexType; } - GLenum GetIndexTypeGL() const { return Tags::FindGLType(indexType); } - - void Upload(const std::byte* data, Tags::IndexType type, size_t count); -}; - -struct BufferBindings { - SmallVector<RcPtr<GpuVertexBuffer>, 4> bindings; - - int GetMaxBindingIndex() const; - - /// Safe. Returns nullptr if the index is not bound to any buffers. - GpuVertexBuffer* GetBinding(int index) const; - /// Adds or updates a buffer binding. Setting a binding to nullptr effectively removes the binding. - void SetBinding(int index, GpuVertexBuffer* buffer); - void Clear(); -}; - -struct VertexElementFormat { - /// NOTE: - /// "Automatic" means it will be set inside VertexFormat::AddElement() - /// "Parameter" means it must be set by the user - /* Automatic */ int offset; - /* Parameter */ int bindingIndex; - /* Parameter */ Tags::VertexElementType type; - /* Parameter */ Tags::VertexElementSemantic semantic; - - int GetStride() const; -}; - -struct VertexFormat : public RefCounted { - SmallVector<VertexElementFormat, 4> elements; - int vertexSize = 0; - - const decltype(elements)& GetElements() const { return elements; } - void AddElement(VertexElementFormat element); - void RemoveElement(int index); -}; diff --git a/source/30-game/World.cpp b/source/30-game/World.cpp deleted file mode 100644 index 83b9a10..0000000 --- a/source/30-game/World.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "World.hpp" - -#include "GameObject.hpp" -#include "PodVector.hpp" - -#include <glad/glad.h> - -namespace ProjectBrussel_UNITY_ID { -template <typename TFunction> -void CallGameObjectRecursive(GameObject* start, TFunction&& func) { - PodVector<GameObject*> stack; - stack.push_back(start); - - while (!stack.empty()) { - auto obj = stack.back(); - stack.pop_back(); - - for (auto child : obj->GetChildren()) { - stack.push_back(child); - } - - func(obj); - } -} -} // namespace ProjectBrussel_UNITY_ID - -GameWorld::GameWorld() - : mRoot{ new GameObject(this) } { -} - -GameWorld::~GameWorld() { - if (mAwakened) { - Resleep(); - } - - delete mRoot; -} - -const GameObject& GameWorld::GetRoot() const { - return *mRoot; -}; - -void GameWorld::Awaken() { - using namespace ProjectBrussel_UNITY_ID; - - if (mAwakened) { - return; - } - - CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Awaken(); }); - mAwakened = true; -} - -void GameWorld::Resleep() { - using namespace ProjectBrussel_UNITY_ID; - - if (!mAwakened) { - return; - } - - CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Resleep(); }); - mAwakened = false; -} - -void GameWorld::Update() { - using namespace ProjectBrussel_UNITY_ID; - - CallGameObjectRecursive(mRoot, [this](GameObject* obj) { - obj->Update(); - }); -} - -GameObject& GameWorld::GetRoot() { - return *mRoot; -} - -bool GameWorld::IsAwake() const { - return mAwakened; -} diff --git a/source/30-game/World.hpp b/source/30-game/World.hpp deleted file mode 100644 index 288142e..0000000 --- a/source/30-game/World.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -class GameObject; -class GameWorld { -private: - GameObject* mRoot; - bool mAwakened = false; - -public: - GameWorld(); - ~GameWorld(); - - GameWorld(const GameWorld&) = delete; - GameWorld& operator=(const GameWorld&) = delete; - GameWorld(GameWorld&&) = default; - GameWorld& operator=(GameWorld&&) = default; - - bool IsAwake() const; - void Awaken(); - void Resleep(); - void Update(); - - const GameObject& GetRoot() const; - GameObject& GetRoot(); -}; diff --git a/source/30-game/main.cpp b/source/30-game/main.cpp deleted file mode 100644 index 30ba9a6..0000000 --- a/source/30-game/main.cpp +++ /dev/null @@ -1,545 +0,0 @@ -#include "App.hpp" - -#include "AppConfig.hpp" -#include "CommonVertexIndex.hpp" -#include "ImGuiGuizmo.hpp" -#include "Input.hpp" -#include "Ires.hpp" -#include "Level.hpp" -#include "Log.hpp" -#include "Material.hpp" -#include "Shader.hpp" - -#define GLFW_INCLUDE_NONE -#include <GLFW/glfw3.h> - -#include <backends/imgui_impl_glfw.h> -#include <backends/imgui_impl_opengl2.h> -#include <backends/imgui_impl_opengl3.h> -#include <glad/glad.h> -#include <imgui.h> -#include <imgui_internal.h> -#include <cstdlib> -#include <cxxopts.hpp> -#include <filesystem> -#include <string> - -#include <tracy/Tracy.hpp> -#include <tracy/TracyClient.cpp> - -namespace fs = std::filesystem; -using namespace std::literals; - -struct GlfwUserData { - App* app = nullptr; -}; - -void GlfwErrorCallback(int error, const char* description) { - fprintf(stderr, "[GLFW] Error %d: %s\n", error, description); -} - -void GlfwKeyboardCallback(GLFWkeyboard* keyboard, int event) { - if (InputState::instance == nullptr) { - // Called before initialization, skipping because we'll do a collect pass anyways when initializing - return; - } - - switch (event) { - case GLFW_CONNECTED: { - InputState::instance->ConnectKeyboard(keyboard); - } break; - - case GLFW_DISCONNECTED: { - InputState::instance->DisconnectKeyboard(keyboard); - } break; - } -} - -void OpenGLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { - fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity, message); -} - -void GlfwFramebufferResizeCallback(GLFWwindow* window, int width, int height) { - AppConfig::mainWindowWidth = width; - AppConfig::mainWindowHeight = height; - AppConfig::mainWindowAspectRatio = (float)width / height; -} - -void GlfwMouseCallback(GLFWwindow* window, int button, int action, int mods) { - if (ImGui::GetIO().WantCaptureMouse) { - return; - } - - auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window)); - auto app = userData->app; - app->HandleMouse(button, action); -} - -void GlfwMouseMotionCallback(GLFWwindow* window, double xOff, double yOff) { - if (ImGui::GetIO().WantCaptureMouse) { - return; - } - - auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window)); - auto app = userData->app; - app->HandleMouseMotion(xOff, yOff); -} - -void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (ImGui::GetIO().WantCaptureKeyboard) { - return; - } - - GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard(); - if (keyboard) { - auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window)); - auto app = userData->app; - app->HandleKey(keyboard, key, action); - } -} - -// For platform data path selection below -// https://stackoverflow.com/questions/54499256/how-to-find-the-saved-games-folder-programmatically-in-c-c -#if defined(_WIN32) -# if defined(__MINGW32__) -# include <ShlObj.h> -# else -# include <ShlObj_core.h> -# endif -# include <objbase.h> -# pragma comment(lib, "shell32.lib") -# pragma comment(lib, "ole32.lib") -#elif defined(__linux__) -fs::path GetEnvVar(const char* name, const char* backup) { - if (const char* path = std::getenv(name)) { - fs::path dataDir(path); - fs::create_directories(dataDir); - return dataDir; - } else { - fs::path dataDir(backup); - fs::create_directories(dataDir); - return dataDir; - } -} -#endif - -int main(int argc, char* argv[]) { - using namespace Tags; - -#if BRUSSEL_DEV_ENV - Log::gDefaultBuffer.messages.resize(1024); - Log::gDefaultBufferId = Log::RegisterBuffer(Log::gDefaultBuffer); -#endif - - constexpr auto kOpenGLDebug = "opengl-debug"; - constexpr auto kImGuiBackend = "imgui-backend"; - constexpr auto kGameDataDir = "game-data-dir"; - constexpr auto kGameAssetDir = "game-asset-dir"; - - cxxopts::Options options(std::string(AppConfig::kAppName), ""); - // clang-format off - options.add_options() - (kOpenGLDebug, "Enable OpenGL debugging messages.") - (kImGuiBackend, "ImGui backend. Options: opengl2, opengl3. Leave empty to default.", cxxopts::value<std::string>()) - (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); - - bool imguiUseOpenGL3; - if (args.count(kImGuiBackend) > 0) { - auto imguiBackend = args[kImGuiBackend].as<std::string>(); - if (imguiBackend == "opengl2") { - imguiUseOpenGL3 = false; - } else if (imguiBackend == "opengl3") { - imguiUseOpenGL3 = true; - } else { - // TODO support more backends? - imguiUseOpenGL3 = true; - } - } else { - imguiUseOpenGL3 = true; - } - - if (args.count(kGameAssetDir) > 0) { - 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); - } else { - AppConfig::assetDir = "."; - AppConfig::assetDirPath = fs::path("."); - } - - if (args.count(kGameDataDir) > 0) { - auto dataDir = args[kGameDataDir].as<std::string>(); - - fs::path dataDirPath(dataDir); - fs::create_directories(dataDir); - - AppConfig::dataDir = std::move(dataDir); - AppConfig::dataDirPath = std::move(dataDirPath); - } else { -#if BRUSSEL_DEV_ENV - AppConfig::dataDir = "."; - AppConfig::dataDirPath = fs::path("."); -#else -// In a regular build, use default platform data paths -# if defined(_WIN32) - fs::path dataDirPath; - - PWSTR path = nullptr; - HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, nullptr, &path); - if (SUCCEEDED(hr)) { - dataDirPath = fs::path(path) / AppConfig::kAppName; - CoTaskMemFree(path); - - fs::create_directories(dataDirPath); - } else { - std::string msg; - msg += "Failed to find/create the default user data directory at %APPDATA%. Error code: "; - msg += hr; - throw std::runtime_error(msg); - } -# elif defined(__APPLE__) - // MacOS programming guide recommends apps to hardcode the path - user customization of "where data are stored" is done in Finder - auto dataDirPath = fs::path("~/Library/Application Support/") / AppConfig::kAppName; - fs::create_directories(dataDirPath); -# elif defined(__linux__) - auto dataDirPath = GetEnvVar("XDG_DATA_HOME", "~/.local/share") / AppConfig::kAppName; - fs::create_directories(dataDirPath); -# endif - AppConfig::dataDir = dataDirPath.string(); - AppConfig::dataDirPath = dataDirPath; -#endif - } - - if (!glfwInit()) { - return -1; - } - - glfwSetErrorCallback(&GlfwErrorCallback); - glfwSetKeyboardCallback(&GlfwKeyboardCallback); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - -#if defined(__APPLE__) - const char* imguiGlslVersion = "#version 150"; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac -#else - const char* imguiGlslVersion = "#version 130"; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); -#endif - - GlfwUserData glfwUserData; - - GLFWwindow* window = glfwCreateWindow(1280, 720, AppConfig::kAppNameC, nullptr, nullptr); - if (window == nullptr) { - return -2; - } - - glfwSetWindowUserPointer(window, &glfwUserData); - - // Window callbacks are retained by ImGui GLFW backend - glfwSetFramebufferSizeCallback(window, &GlfwFramebufferResizeCallback); - glfwSetKeyCallback(window, &GlfwKeyCallback); - glfwSetMouseButtonCallback(window, &GlfwMouseCallback); - glfwSetCursorPosCallback(window, &GlfwMouseMotionCallback); - - { - int width, height; - glfwGetFramebufferSize(window, &width, &height); - GlfwFramebufferResizeCallback(window, width, height); - } - - glfwMakeContextCurrent(window); - glfwSwapInterval(1); - - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - return -3; - } - -#if defined(BRUSSEL_DEV_ENV) - auto glVersionString = glGetString(GL_VERSION); - - int glMajorVersion; - glGetIntegerv(GL_MAJOR_VERSION, &glMajorVersion); - int glMinorVersion; - glGetIntegerv(GL_MINOR_VERSION, &glMinorVersion); - - printf("OpenGL version (via glGetString(GL_VERSION)): %s\n", glVersionString); - printf("OpenGL version (via glGetIntegerv() with GL_MAJOR_VERSION and GL_MINOR_VERSION): %d.%d\n", glMajorVersion, glMinorVersion); -#endif - - bool useOpenGLDebug = args[kOpenGLDebug].as<bool>(); - if (useOpenGLDebug) { - printf("Using OpenGL debugging\n --%s", kOpenGLDebug); - - // TODO check extension KHR_debug availability - // TODO conan glad is not including any extensions - // NOTE: KHR_debug is a core extension, which means it may be available in lower version even though the feature is added in 4.3 - - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(&OpenGLDebugCallback, 0); - } - - IMGUI_CHECKVERSION(); - auto ctx = ImGui::CreateContext(); - auto& io = ImGui::GetIO(); - ImGuizmo::SetImGuiContext(ctx); - - ImGui_ImplGlfw_InitForOpenGL(window, true); - if (imguiUseOpenGL3) { - ImGui_ImplOpenGL3_Init(imguiGlslVersion); - } else { - ImGui_ImplOpenGL2_Init(); - } - - InputState::instance = new InputState(); - { - int count; - GLFWkeyboard** list = glfwGetKeyboards(&count); - for (int i = 0; i < count; ++i) { - GLFWkeyboard* keyboard = list[i]; - InputState::instance->ConnectKeyboard(keyboard); - } - } - - IresManager::instance = new IresManager(); - IresManager::instance->DiscoverFilesDesignatedLocation(); - - LevelManager::instance = new LevelManager(); - LevelManager::instance->DiscoverFilesDesignatedLocation(); - - gVformatStandard.Attach(new VertexFormat()); - gVformatStandard->AddElement(VertexElementFormat{ - .bindingIndex = 0, - .type = VET_Float3, - .semantic = VES_Position, - }); - gVformatStandard->AddElement(VertexElementFormat{ - .bindingIndex = 0, - .type = VET_Float2, - .semantic = VES_TexCoords1, - }); - gVformatStandard->AddElement(VertexElementFormat{ - .bindingIndex = 0, - .type = VET_Ubyte4Norm, - .semantic = VES_Color1, - }); - - gVformatStandardSplit.Attach(new VertexFormat()); - gVformatStandardSplit->AddElement(VertexElementFormat{ - .bindingIndex = 0, - .type = VET_Float3, - .semantic = VES_Position, - }); - gVformatStandardSplit->AddElement(VertexElementFormat{ - .bindingIndex = 1, - .type = VET_Float2, - .semantic = VES_TexCoords1, - }); - gVformatStandardSplit->AddElement(VertexElementFormat{ - .bindingIndex = 1, - .type = VET_Ubyte4Norm, - .semantic = VES_Color1, - }); - - gVformatLines.Attach(new VertexFormat()); - gVformatLines->AddElement(VertexElementFormat{ - .bindingIndex = 0, - .type = VET_Float3, - .semantic = VES_Position, - }); - gVformatLines->AddElement(VertexElementFormat{ - .bindingIndex = 0, - .type = VET_Ubyte4Norm, - .semantic = VES_Color1, - }); - - // Matches gVformatStandard - gDefaultShader.Attach(new Shader()); - gDefaultShader->InitFromSources(Shader::ShaderSources{ - .vertex = R"""( -#version 330 core -layout(location = 0) in vec3 pos; -layout(location = 1) in vec4 color; -out vec4 v2fColor; -uniform mat4 transform; -void main() { - gl_Position = transform * vec4(pos, 1.0); - v2fColor = color; -} -)"""sv, - .fragment = R"""( -#version 330 core -in vec4 v2fColor; -out vec4 fragColor; -void main() { - fragColor = v2fColor; -} -)"""sv, - }); - { // in vec3 pos; - ShaderMathVariable var; - var.scalarType = GL_FLOAT; - var.width = 1; - var.height = 3; - var.arrayLength = 1; - var.semantic = VES_Position; - var.location = 0; - gDefaultShader->GetInfo().inputs.push_back(std::move(var)); - gDefaultShader->GetInfo().things.try_emplace( - "pos"s, - ShaderThingId{ - .kind = ShaderThingId::KD_Input, - .index = (int)gDefaultShader->GetInfo().inputs.size() - 1, - }); - } - { // in vec4 color; - ShaderMathVariable var; - var.scalarType = GL_FLOAT; - var.width = 1; - var.height = 4; - var.arrayLength = 1; - var.semantic = VES_Color1; - var.location = 1; - gDefaultShader->GetInfo().inputs.push_back(std::move(var)); - gDefaultShader->GetInfo().things.try_emplace( - "color"s, - ShaderThingId{ - .kind = ShaderThingId::KD_Input, - .index = (int)gDefaultShader->GetInfo().inputs.size() - 1, - }); - } - { // out vec4 fragColor; - ShaderMathVariable var; - var.scalarType = GL_FLOAT; - var.width = 1; - var.height = 4; - var.arrayLength = 1; - gDefaultShader->GetInfo().outputs.push_back(std::move(var)); - gDefaultShader->GetInfo().things.try_emplace( - "fragColor"s, - ShaderThingId{ - .kind = ShaderThingId::KD_Output, - .index = (int)gDefaultShader->GetInfo().outputs.size() - 1, - }); - } - // NOTE: autofill uniforms not recorded here - - gDefaultMaterial.Attach(new Material()); - gDefaultMaterial->SetShader(gDefaultShader.Get()); - - { // Main loop - App app; - glfwUserData.app = &app; - - // NOTE: don't enable backface culling, because the game mainly runs in 2D and sometimes we'd like to flip sprites around - // it also helps with debugging layers in 3D view - glEnable(GL_DEPTH_TEST); - - // 60 updates per second - constexpr double kMsPerUpdate = 1000.0 / 60; - constexpr double kSecondsPerUpdate = kMsPerUpdate / 1000; - double prevTime = glfwGetTime(); - double accumulatedTime = 0.0; - while (!glfwWindowShouldClose(window)) { - { - ZoneScopedN("GameInput"); - glfwPollEvents(); - } - - double currTime = glfwGetTime(); - double deltaTime = prevTime - currTime; - - // In seconds - accumulatedTime += currTime - prevTime; - - // Update - // Play "catch up" to ensure a deterministic number of Update()'s per second - while (accumulatedTime >= kSecondsPerUpdate) { - double beg = glfwGetTime(); - { - ZoneScopedN("GameUpdate"); - app.Update(); - } - double end = glfwGetTime(); - - // Update is taking longer than it should be, start skipping updates - auto diff = end - beg; - if (diff >= kSecondsPerUpdate) { - auto skippedUpdates = (int)(accumulatedTime / kSecondsPerUpdate); - accumulatedTime = 0.0; - fprintf(stderr, "Elapsed time %f, skipped %d updates.", diff, skippedUpdates); - } else { - accumulatedTime -= kSecondsPerUpdate; - } - } - - int fbWidth = AppConfig::mainWindowWidth; - int fbHeight = AppConfig::mainWindowHeight; - glfwGetFramebufferSize(window, &fbWidth, &fbHeight); - glViewport(0, 0, fbWidth, fbHeight); - auto clearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - glClearColor(clearColor.x * clearColor.w, clearColor.y * clearColor.w, clearColor.z * clearColor.w, clearColor.w); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - { // Regular draw - ZoneScopedN("Render"); - app.Draw(currTime, deltaTime); - } - - { // ImGui draw - ZoneScopedN("ImGui"); - if (imguiUseOpenGL3) { - ImGui_ImplOpenGL3_NewFrame(); - } else { - ImGui_ImplOpenGL2_NewFrame(); - } - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - app.Show(); - - ImGui::Render(); - if (imguiUseOpenGL3) { - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - } else { - ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); - } - } - - glfwSwapBuffers(window); - FrameMark; - - prevTime = currTime; - } - } - - if (imguiUseOpenGL3) { - ImGui_ImplOpenGL3_Shutdown(); - } else { - ImGui_ImplOpenGL2_Shutdown(); - } - ImGui_ImplGlfw_Shutdown(); - - ImGui::DestroyContext(); - - glfwDestroyWindow(window); - glfwTerminate(); - - return 0; -} |