From 60ccc62f4934e44ad5b905fdbcf458302b8d8a09 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Fri, 3 Jun 2022 23:26:44 -0700 Subject: Changeset: 63 [WIP] Rename directories --- source/10-common/Color.hpp | 148 - source/10-common/Enum.hpp | 103 - source/10-common/LookupTable.hpp | 64 - source/10-common/Macros.hpp | 31 - source/10-common/PodVector.hpp | 297 -- source/10-common/RTTI.hpp | 44 - source/10-common/RapidJsonHelper.hpp | 110 - source/10-common/RcPtr.hpp | 120 - source/10-common/Rect.hpp | 164 -- source/10-common/ScopeGuard.hpp | 60 - source/10-common/SmallVector.cpp | 145 - source/10-common/SmallVector.hpp | 1332 --------- source/10-common/StbImplementations.c | 14 - source/10-common/Type2ObjectMap.hpp | 38 - source/10-common/TypeTraits.hpp | 27 - source/10-common/Uid.cpp | 58 - source/10-common/Uid.hpp | 42 - source/10-common/Utils.cpp | 107 - source/10-common/Utils.hpp | 65 - source/10-common/YCombinator.hpp | 14 - source/10-common/buildfile | 1 - source/20-codegen-compiler/CodegenConfig.hpp | 11 - source/20-codegen-compiler/CodegenDecl.cpp | 49 - source/20-codegen-compiler/CodegenDecl.hpp | 101 - source/20-codegen-compiler/CodegenInput.cpp | 99 - source/20-codegen-compiler/CodegenInput.hpp | 32 - source/20-codegen-compiler/CodegenLexer.cpp | 183 -- source/20-codegen-compiler/CodegenLexer.hpp | 47 - source/20-codegen-compiler/CodegenOutput.cpp | 46 - source/20-codegen-compiler/CodegenOutput.hpp | 39 - source/20-codegen-compiler/CodegenUtils.cpp | 146 - source/20-codegen-compiler/CodegenUtils.hpp | 54 - source/20-codegen-compiler/buildfile | 1 - source/20-codegen-compiler/main.cpp | 1112 -------- .../test/examples/TestClass.hpp.txt | 38 - .../test/examples/TestEnum.hpp.txt | 44 - source/20-codegen-runtime/MacrosCodegen.hpp | 10 - source/20-codegen-runtime/Metadata.cpp | 45 - source/20-codegen-runtime/Metadata.hpp | 33 - source/20-codegen-runtime/MetadataBase.cpp | 5 - source/20-codegen-runtime/MetadataBase.hpp | 53 - source/20-codegen-runtime/MetadataDetails.hpp | 7 - source/20-codegen-runtime/buildfile | 1 - source/30-game/App.cpp | 149 - source/30-game/App.hpp | 62 - source/30-game/AppConfig.hpp | 26 - source/30-game/Camera.cpp | 46 - source/30-game/Camera.hpp | 35 - source/30-game/CommonVertexIndex.cpp | 152 - source/30-game/CommonVertexIndex.hpp | 76 - source/30-game/EditorAccessories.cpp | 6 - source/30-game/EditorAccessories.hpp | 6 - source/30-game/EditorAttachment.hpp | 12 - source/30-game/EditorAttachmentImpl.cpp | 23 - source/30-game/EditorAttachmentImpl.hpp | 34 - source/30-game/EditorCommandPalette.cpp | 406 --- source/30-game/EditorCommandPalette.hpp | 94 - source/30-game/EditorCore.hpp | 39 - source/30-game/EditorCorePrivate.cpp | 1113 -------- source/30-game/EditorCorePrivate.hpp | 130 - source/30-game/EditorGuizmo.cpp | 2897 -------------------- source/30-game/EditorGuizmo.hpp | 232 -- source/30-game/EditorNotification.cpp | 277 -- source/30-game/EditorNotification.hpp | 81 - source/30-game/EditorUtils.cpp | 447 --- source/30-game/EditorUtils.hpp | 63 - source/30-game/FuzzyMatch.cpp | 174 -- source/30-game/FuzzyMatch.hpp | 10 - source/30-game/GameObject.cpp | 230 -- source/30-game/GameObject.hpp | 102 - source/30-game/GraphicsTags.cpp | 273 -- source/30-game/GraphicsTags.hpp | 102 - source/30-game/Image.cpp | 101 - source/30-game/Image.hpp | 38 - source/30-game/Ires.cpp | 409 --- source/30-game/Ires.hpp | 125 - source/30-game/Level.cpp | 228 -- source/30-game/Level.hpp | 92 - source/30-game/Material.cpp | 526 ---- source/30-game/Material.hpp | 125 - source/30-game/Mesh.cpp | 54 - source/30-game/Mesh.hpp | 45 - source/30-game/Player.cpp | 139 - source/30-game/Player.hpp | 55 - source/30-game/Renderer.cpp | 170 -- source/30-game/Renderer.hpp | 68 - source/30-game/SceneThings.cpp | 142 - source/30-game/SceneThings.hpp | 46 - source/30-game/Shader.cpp | 773 ------ source/30-game/Shader.hpp | 173 -- source/30-game/Sprite.cpp | 328 --- source/30-game/Sprite.hpp | 111 - source/30-game/Texture.cpp | 250 -- source/30-game/Texture.hpp | 99 - source/30-game/VertexIndex.cpp | 84 - source/30-game/VertexIndex.hpp | 67 - source/30-game/World.cpp | 74 - source/30-game/World.hpp | 25 - source/30-game/buildfile | 0 source/30-game/main.cpp | 461 ---- source/CodegenCompiler/CodegenConfig.hpp | 11 + source/CodegenCompiler/CodegenDecl.cpp | 49 + source/CodegenCompiler/CodegenDecl.hpp | 101 + source/CodegenCompiler/CodegenInput.cpp | 99 + source/CodegenCompiler/CodegenInput.hpp | 32 + source/CodegenCompiler/CodegenLexer.cpp | 183 ++ source/CodegenCompiler/CodegenLexer.hpp | 47 + source/CodegenCompiler/CodegenOutput.cpp | 46 + source/CodegenCompiler/CodegenOutput.hpp | 39 + source/CodegenCompiler/CodegenUtils.cpp | 146 + source/CodegenCompiler/CodegenUtils.hpp | 54 + source/CodegenCompiler/buildfile | 1 + source/CodegenCompiler/main.cpp | 1112 ++++++++ .../test/examples/TestClass.hpp.txt | 38 + .../CodegenCompiler/test/examples/TestEnum.hpp.txt | 44 + source/CodegenRuntime/MacrosCodegen.hpp | 10 + source/CodegenRuntime/Metadata.cpp | 45 + source/CodegenRuntime/Metadata.hpp | 33 + source/CodegenRuntime/MetadataBase.cpp | 5 + source/CodegenRuntime/MetadataBase.hpp | 53 + source/CodegenRuntime/MetadataDetails.hpp | 7 + source/CodegenRuntime/buildfile | 1 + source/Common/Color.hpp | 148 + source/Common/Enum.hpp | 103 + source/Common/LookupTable.hpp | 64 + source/Common/Macros.hpp | 31 + source/Common/PodVector.hpp | 297 ++ source/Common/RTTI.hpp | 44 + source/Common/RapidJsonHelper.hpp | 110 + source/Common/RcPtr.hpp | 120 + source/Common/Rect.hpp | 164 ++ source/Common/ScopeGuard.hpp | 60 + source/Common/SmallVector.cpp | 145 + source/Common/SmallVector.hpp | 1332 +++++++++ source/Common/StbImplementations.c | 14 + source/Common/Type2ObjectMap.hpp | 38 + source/Common/TypeTraits.hpp | 27 + source/Common/Uid.cpp | 58 + source/Common/Uid.hpp | 42 + source/Common/Utils.cpp | 107 + source/Common/Utils.hpp | 65 + source/Common/YCombinator.hpp | 14 + source/Common/buildfile | 1 + source/Game/App.cpp | 149 + source/Game/App.hpp | 62 + source/Game/AppConfig.hpp | 26 + source/Game/Camera.cpp | 46 + source/Game/Camera.hpp | 35 + source/Game/CommonVertexIndex.cpp | 152 + source/Game/CommonVertexIndex.hpp | 76 + source/Game/EditorAccessories.cpp | 6 + source/Game/EditorAccessories.hpp | 6 + source/Game/EditorAttachment.hpp | 12 + source/Game/EditorAttachmentImpl.cpp | 23 + source/Game/EditorAttachmentImpl.hpp | 34 + source/Game/EditorCommandPalette.cpp | 406 +++ source/Game/EditorCommandPalette.hpp | 94 + source/Game/EditorCore.hpp | 39 + source/Game/EditorCorePrivate.cpp | 1113 ++++++++ source/Game/EditorCorePrivate.hpp | 130 + source/Game/EditorGuizmo.cpp | 2897 ++++++++++++++++++++ source/Game/EditorGuizmo.hpp | 232 ++ source/Game/EditorNotification.cpp | 277 ++ source/Game/EditorNotification.hpp | 81 + source/Game/EditorUtils.cpp | 447 +++ source/Game/EditorUtils.hpp | 63 + source/Game/FuzzyMatch.cpp | 174 ++ source/Game/FuzzyMatch.hpp | 10 + source/Game/GameObject.cpp | 230 ++ source/Game/GameObject.hpp | 102 + source/Game/GraphicsTags.cpp | 273 ++ source/Game/GraphicsTags.hpp | 102 + source/Game/Image.cpp | 101 + source/Game/Image.hpp | 38 + source/Game/Ires.cpp | 409 +++ source/Game/Ires.hpp | 125 + source/Game/Level.cpp | 228 ++ source/Game/Level.hpp | 92 + source/Game/Material.cpp | 526 ++++ source/Game/Material.hpp | 125 + source/Game/Mesh.cpp | 54 + source/Game/Mesh.hpp | 45 + source/Game/Player.cpp | 139 + source/Game/Player.hpp | 55 + source/Game/Renderer.cpp | 170 ++ source/Game/Renderer.hpp | 68 + source/Game/SceneThings.cpp | 142 + source/Game/SceneThings.hpp | 46 + source/Game/Shader.cpp | 773 ++++++ source/Game/Shader.hpp | 173 ++ source/Game/Sprite.cpp | 328 +++ source/Game/Sprite.hpp | 111 + source/Game/Texture.cpp | 250 ++ source/Game/Texture.hpp | 99 + source/Game/VertexIndex.cpp | 84 + source/Game/VertexIndex.hpp | 67 + source/Game/World.cpp | 74 + source/Game/World.hpp | 25 + source/Game/buildfile | 0 source/Game/main.cpp | 461 ++++ 200 files changed, 17245 insertions(+), 17245 deletions(-) delete mode 100644 source/10-common/Color.hpp delete mode 100644 source/10-common/Enum.hpp delete mode 100644 source/10-common/LookupTable.hpp delete mode 100644 source/10-common/Macros.hpp delete mode 100644 source/10-common/PodVector.hpp delete mode 100644 source/10-common/RTTI.hpp delete mode 100644 source/10-common/RapidJsonHelper.hpp delete mode 100644 source/10-common/RcPtr.hpp delete mode 100644 source/10-common/Rect.hpp delete mode 100644 source/10-common/ScopeGuard.hpp delete mode 100644 source/10-common/SmallVector.cpp delete mode 100644 source/10-common/SmallVector.hpp delete mode 100644 source/10-common/StbImplementations.c delete mode 100644 source/10-common/Type2ObjectMap.hpp delete mode 100644 source/10-common/TypeTraits.hpp delete mode 100644 source/10-common/Uid.cpp delete mode 100644 source/10-common/Uid.hpp delete mode 100644 source/10-common/Utils.cpp delete mode 100644 source/10-common/Utils.hpp delete mode 100644 source/10-common/YCombinator.hpp delete mode 100644 source/10-common/buildfile delete mode 100644 source/20-codegen-compiler/CodegenConfig.hpp delete mode 100644 source/20-codegen-compiler/CodegenDecl.cpp delete mode 100644 source/20-codegen-compiler/CodegenDecl.hpp delete mode 100644 source/20-codegen-compiler/CodegenInput.cpp delete mode 100644 source/20-codegen-compiler/CodegenInput.hpp delete mode 100644 source/20-codegen-compiler/CodegenLexer.cpp delete mode 100644 source/20-codegen-compiler/CodegenLexer.hpp delete mode 100644 source/20-codegen-compiler/CodegenOutput.cpp delete mode 100644 source/20-codegen-compiler/CodegenOutput.hpp delete mode 100644 source/20-codegen-compiler/CodegenUtils.cpp delete mode 100644 source/20-codegen-compiler/CodegenUtils.hpp delete mode 100644 source/20-codegen-compiler/buildfile delete mode 100644 source/20-codegen-compiler/main.cpp delete mode 100644 source/20-codegen-compiler/test/examples/TestClass.hpp.txt delete mode 100644 source/20-codegen-compiler/test/examples/TestEnum.hpp.txt delete mode 100644 source/20-codegen-runtime/MacrosCodegen.hpp delete mode 100644 source/20-codegen-runtime/Metadata.cpp delete mode 100644 source/20-codegen-runtime/Metadata.hpp delete mode 100644 source/20-codegen-runtime/MetadataBase.cpp delete mode 100644 source/20-codegen-runtime/MetadataBase.hpp delete mode 100644 source/20-codegen-runtime/MetadataDetails.hpp delete mode 100644 source/20-codegen-runtime/buildfile delete mode 100644 source/30-game/App.cpp delete mode 100644 source/30-game/App.hpp delete mode 100644 source/30-game/AppConfig.hpp delete mode 100644 source/30-game/Camera.cpp delete mode 100644 source/30-game/Camera.hpp delete mode 100644 source/30-game/CommonVertexIndex.cpp delete mode 100644 source/30-game/CommonVertexIndex.hpp delete mode 100644 source/30-game/EditorAccessories.cpp delete mode 100644 source/30-game/EditorAccessories.hpp delete mode 100644 source/30-game/EditorAttachment.hpp delete mode 100644 source/30-game/EditorAttachmentImpl.cpp delete mode 100644 source/30-game/EditorAttachmentImpl.hpp delete mode 100644 source/30-game/EditorCommandPalette.cpp delete mode 100644 source/30-game/EditorCommandPalette.hpp delete mode 100644 source/30-game/EditorCore.hpp delete mode 100644 source/30-game/EditorCorePrivate.cpp delete mode 100644 source/30-game/EditorCorePrivate.hpp delete mode 100644 source/30-game/EditorGuizmo.cpp delete mode 100644 source/30-game/EditorGuizmo.hpp delete mode 100644 source/30-game/EditorNotification.cpp delete mode 100644 source/30-game/EditorNotification.hpp delete mode 100644 source/30-game/EditorUtils.cpp delete mode 100644 source/30-game/EditorUtils.hpp delete mode 100644 source/30-game/FuzzyMatch.cpp delete mode 100644 source/30-game/FuzzyMatch.hpp delete mode 100644 source/30-game/GameObject.cpp delete mode 100644 source/30-game/GameObject.hpp delete mode 100644 source/30-game/GraphicsTags.cpp delete mode 100644 source/30-game/GraphicsTags.hpp delete mode 100644 source/30-game/Image.cpp delete mode 100644 source/30-game/Image.hpp delete mode 100644 source/30-game/Ires.cpp delete mode 100644 source/30-game/Ires.hpp delete mode 100644 source/30-game/Level.cpp delete mode 100644 source/30-game/Level.hpp delete mode 100644 source/30-game/Material.cpp delete mode 100644 source/30-game/Material.hpp delete mode 100644 source/30-game/Mesh.cpp delete mode 100644 source/30-game/Mesh.hpp delete mode 100644 source/30-game/Player.cpp delete mode 100644 source/30-game/Player.hpp delete mode 100644 source/30-game/Renderer.cpp delete mode 100644 source/30-game/Renderer.hpp delete mode 100644 source/30-game/SceneThings.cpp delete mode 100644 source/30-game/SceneThings.hpp delete mode 100644 source/30-game/Shader.cpp delete mode 100644 source/30-game/Shader.hpp delete mode 100644 source/30-game/Sprite.cpp delete mode 100644 source/30-game/Sprite.hpp delete mode 100644 source/30-game/Texture.cpp delete mode 100644 source/30-game/Texture.hpp delete mode 100644 source/30-game/VertexIndex.cpp delete mode 100644 source/30-game/VertexIndex.hpp delete mode 100644 source/30-game/World.cpp delete mode 100644 source/30-game/World.hpp delete mode 100644 source/30-game/buildfile delete mode 100644 source/30-game/main.cpp create mode 100644 source/CodegenCompiler/CodegenConfig.hpp create mode 100644 source/CodegenCompiler/CodegenDecl.cpp create mode 100644 source/CodegenCompiler/CodegenDecl.hpp create mode 100644 source/CodegenCompiler/CodegenInput.cpp create mode 100644 source/CodegenCompiler/CodegenInput.hpp create mode 100644 source/CodegenCompiler/CodegenLexer.cpp create mode 100644 source/CodegenCompiler/CodegenLexer.hpp create mode 100644 source/CodegenCompiler/CodegenOutput.cpp create mode 100644 source/CodegenCompiler/CodegenOutput.hpp create mode 100644 source/CodegenCompiler/CodegenUtils.cpp create mode 100644 source/CodegenCompiler/CodegenUtils.hpp create mode 100644 source/CodegenCompiler/buildfile create mode 100644 source/CodegenCompiler/main.cpp create mode 100644 source/CodegenCompiler/test/examples/TestClass.hpp.txt create mode 100644 source/CodegenCompiler/test/examples/TestEnum.hpp.txt create mode 100644 source/CodegenRuntime/MacrosCodegen.hpp create mode 100644 source/CodegenRuntime/Metadata.cpp create mode 100644 source/CodegenRuntime/Metadata.hpp create mode 100644 source/CodegenRuntime/MetadataBase.cpp create mode 100644 source/CodegenRuntime/MetadataBase.hpp create mode 100644 source/CodegenRuntime/MetadataDetails.hpp create mode 100644 source/CodegenRuntime/buildfile create mode 100644 source/Common/Color.hpp create mode 100644 source/Common/Enum.hpp create mode 100644 source/Common/LookupTable.hpp create mode 100644 source/Common/Macros.hpp create mode 100644 source/Common/PodVector.hpp create mode 100644 source/Common/RTTI.hpp create mode 100644 source/Common/RapidJsonHelper.hpp create mode 100644 source/Common/RcPtr.hpp create mode 100644 source/Common/Rect.hpp create mode 100644 source/Common/ScopeGuard.hpp create mode 100644 source/Common/SmallVector.cpp create mode 100644 source/Common/SmallVector.hpp create mode 100644 source/Common/StbImplementations.c create mode 100644 source/Common/Type2ObjectMap.hpp create mode 100644 source/Common/TypeTraits.hpp create mode 100644 source/Common/Uid.cpp create mode 100644 source/Common/Uid.hpp create mode 100644 source/Common/Utils.cpp create mode 100644 source/Common/Utils.hpp create mode 100644 source/Common/YCombinator.hpp create mode 100644 source/Common/buildfile create mode 100644 source/Game/App.cpp create mode 100644 source/Game/App.hpp create mode 100644 source/Game/AppConfig.hpp create mode 100644 source/Game/Camera.cpp create mode 100644 source/Game/Camera.hpp create mode 100644 source/Game/CommonVertexIndex.cpp create mode 100644 source/Game/CommonVertexIndex.hpp create mode 100644 source/Game/EditorAccessories.cpp create mode 100644 source/Game/EditorAccessories.hpp create mode 100644 source/Game/EditorAttachment.hpp create mode 100644 source/Game/EditorAttachmentImpl.cpp create mode 100644 source/Game/EditorAttachmentImpl.hpp create mode 100644 source/Game/EditorCommandPalette.cpp create mode 100644 source/Game/EditorCommandPalette.hpp create mode 100644 source/Game/EditorCore.hpp create mode 100644 source/Game/EditorCorePrivate.cpp create mode 100644 source/Game/EditorCorePrivate.hpp create mode 100644 source/Game/EditorGuizmo.cpp create mode 100644 source/Game/EditorGuizmo.hpp create mode 100644 source/Game/EditorNotification.cpp create mode 100644 source/Game/EditorNotification.hpp create mode 100644 source/Game/EditorUtils.cpp create mode 100644 source/Game/EditorUtils.hpp create mode 100644 source/Game/FuzzyMatch.cpp create mode 100644 source/Game/FuzzyMatch.hpp create mode 100644 source/Game/GameObject.cpp create mode 100644 source/Game/GameObject.hpp create mode 100644 source/Game/GraphicsTags.cpp create mode 100644 source/Game/GraphicsTags.hpp create mode 100644 source/Game/Image.cpp create mode 100644 source/Game/Image.hpp create mode 100644 source/Game/Ires.cpp create mode 100644 source/Game/Ires.hpp create mode 100644 source/Game/Level.cpp create mode 100644 source/Game/Level.hpp create mode 100644 source/Game/Material.cpp create mode 100644 source/Game/Material.hpp create mode 100644 source/Game/Mesh.cpp create mode 100644 source/Game/Mesh.hpp create mode 100644 source/Game/Player.cpp create mode 100644 source/Game/Player.hpp create mode 100644 source/Game/Renderer.cpp create mode 100644 source/Game/Renderer.hpp create mode 100644 source/Game/SceneThings.cpp create mode 100644 source/Game/SceneThings.hpp create mode 100644 source/Game/Shader.cpp create mode 100644 source/Game/Shader.hpp create mode 100644 source/Game/Sprite.cpp create mode 100644 source/Game/Sprite.hpp create mode 100644 source/Game/Texture.cpp create mode 100644 source/Game/Texture.hpp create mode 100644 source/Game/VertexIndex.cpp create mode 100644 source/Game/VertexIndex.hpp create mode 100644 source/Game/World.cpp create mode 100644 source/Game/World.hpp create mode 100644 source/Game/buildfile create mode 100644 source/Game/main.cpp diff --git a/source/10-common/Color.hpp b/source/10-common/Color.hpp deleted file mode 100644 index ef0c5a9..0000000 --- a/source/10-common/Color.hpp +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once - -#include "Utils.hpp" - -#include -#include -#include -#include - -class HsvColor; -class RgbaColor { -public: - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - -public: - constexpr RgbaColor() noexcept - : r{ 255 } - , g{ 255 } - , b{ 255 } - , a{ 255 } { - } - - constexpr RgbaColor(float r, float g, float b, float a = 1.0f) noexcept - : r{ static_cast(r * 255.0f) } - , g{ static_cast(g * 255.0f) } - , b{ static_cast(b * 255.0f) } - , a{ static_cast(a * 255.0f) } { - } - - constexpr RgbaColor(int r, int g, int b, int a = 255) noexcept - : r{ static_cast(r & 0xFF) } - , g{ static_cast(g & 0xFF) } - , b{ static_cast(b & 0xFF) } - , a{ static_cast(a & 0xFF) } { - } - - constexpr RgbaColor(uint32_t rgba) noexcept - : r{ static_cast((rgba >> 0) & 0xFF) } - , g{ static_cast((rgba >> 8) & 0xFF) } - , b{ static_cast((rgba >> 16) & 0xFF) } - , a{ static_cast((rgba >> 24) & 0xFF) } { - } - - constexpr uint32_t GetScalar() const noexcept { - uint32_t res = 0; - res |= r << 0; - res |= g << 8; - res |= b << 16; - res |= a << 24; - return res; - } - - constexpr void SetScalar(uint32_t scalar) noexcept { - r = (scalar >> 0) & 0xFF; - g = (scalar >> 8) & 0xFF; - b = (scalar >> 16) & 0xFF; - a = (scalar >> 24) & 0xFF; - } - - constexpr float GetNormalizedRed() const noexcept { - return r / 255.0f; - } - - constexpr float GetNormalizedGreen() const noexcept { - return g / 255.0f; - } - - constexpr float GetNormalizedBlue() const noexcept { - return b / 255.0f; - } - - constexpr float GetNormalizedAlpha() const noexcept { - return a / 255.0f; - } - - constexpr glm::ivec4 ToIVec() const noexcept { - return { r, g, b, a }; - } - - constexpr glm::vec4 ToVec() const noexcept { - return { GetNormalizedRed(), GetNormalizedGreen(), GetNormalizedBlue(), GetNormalizedAlpha() }; - } - - // Forward declaring because cyclic reference between RgbaColor and HsvColor - constexpr HsvColor ToHsv() const noexcept; - - friend constexpr bool operator==(const RgbaColor&, const RgbaColor&) noexcept = default; -}; - -constexpr RgbaColor kXAxisColor(0xFF, 0x80, 0x80, 0xFF); -constexpr RgbaColor kYAxisColor(0x80, 0xFF, 0x80, 0xFF); -constexpr RgbaColor kZAxisColor(0x80, 0x80, 0xFF, 0xFF); - -class HsvColor { -public: - float h; - float s; - float v; - float a; - -public: - constexpr HsvColor() noexcept - : h{ 0.0f } - , s{ 0.0f } - , v{ 1.0f } - , a{ 1.0f } { - } - - constexpr HsvColor(float h, float s, float v, float a) noexcept - : h{ h } - , s{ s } - , v{ v } - , a{ a } { - } - - // Forward declaring because cyclic reference between RgbaColor and HsvColor - constexpr RgbaColor ToRgba() const noexcept; -}; - -constexpr HsvColor RgbaColor::ToHsv() const noexcept { - float r = GetNormalizedRed(); - float g = GetNormalizedBlue(); - float b = GetNormalizedGreen(); - float a = GetNormalizedAlpha(); - - auto p = g < b ? glm::vec4(b, g, -1, 2.0f / 3.0f) : glm::vec4(g, b, 0, -1.0f / 3.0f); - auto q = r < p.x ? glm::vec4(p.x, p.y, p.w, r) : glm::vec4(r, p.y, p.z, p.x); - float c = q.x - std::min(q.w, q.y); - float h = Utils::Abs((q.w - q.y) / (6 * c + std::numeric_limits::epsilon()) + q.z); - - glm::vec3 hcv{ h, c, q.x }; - float s = hcv.y / (hcv.z + std::numeric_limits::epsilon()); - return HsvColor(hcv.x, s, hcv.z, a); -} - -constexpr RgbaColor HsvColor::ToRgba() const noexcept { - float r = Utils::Abs(h * 6 - 3) - 1; - float g = 2 - Utils::Abs(h * 6 - 2); - float b = 2 - Utils::Abs(h * 6 - 4); - - auto rgb = glm::vec3{ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }; - auto vc = (rgb - glm::vec3{ 0, 0, 0 }) * s + glm::vec3{ 1, 1, 1 } * v; - - return RgbaColor(vc.x, vc.y, vc.z, a); -} diff --git a/source/10-common/Enum.hpp b/source/10-common/Enum.hpp deleted file mode 100644 index 8ad75ba..0000000 --- a/source/10-common/Enum.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include -#include - -template -class EnumFlags { -public: - using Enum = TEnum; - using Underlying = std::underlying_type_t; - -private: - Underlying mValue; - -public: - EnumFlags() - : mValue{ 0 } { - } - - EnumFlags(TEnum e) - : mValue{ static_cast(1) << static_cast(e) } { - } - - bool IsSet(EnumFlags mask) const { - return (mValue & mask.mValue) == mask.mValue; - } - - bool IsSet(std::initializer_list enums) { - EnumFlags flags; - for (auto& e : enums) { - flags.mValue |= static_cast(e); - } - return IsSet(flags); - } - - bool IsSetExclusive(EnumFlags mask) const { - return mValue == mask.mValue; - } - - bool IsSetExclusive(std::initializer_list enums) { - EnumFlags flags; - for (auto& e : enums) { - flags.mValue |= static_cast(e); - } - return IsSetExclusive(flags); - } - - void SetOn(EnumFlags mask) { - mValue |= mask.mValue; - } - - void SetOff(EnumFlags mask) { - mValue &= ~mask.mValue; - } - - void Set(EnumFlags mask, bool enabled) { - if (enabled) { - SetOn(mask); - } else { - SetOff(mask); - } - } - - EnumFlags& operator|=(EnumFlags that) { - mValue |= that.mValue; - return *this; - } - - EnumFlags& operator&=(EnumFlags that) { - mValue &= that.mValue; - return *this; - } - - EnumFlags& operator^=(EnumFlags that) { - mValue ^= that.mValue; - return *this; - } - - EnumFlags& operator|=(TEnum e) { - mValue |= 1 << static_cast(e); - return *this; - } - - EnumFlags& operator&=(TEnum e) { - mValue &= 1 << static_cast(e); - return *this; - } - - EnumFlags& operator^=(TEnum e) { - mValue ^= 1 << static_cast(e); - return *this; - } - - EnumFlags operator|(EnumFlags that) const { return EnumFlags(mValue | that.mValue); } - EnumFlags operator&(EnumFlags that) const { return EnumFlags(mValue & that.mValue); } - EnumFlags operator^(EnumFlags that) const { return EnumFlags(mValue ^ that.mValue); } - - EnumFlags operator|(TEnum e) const { return EnumFlags(mValue | 1 << static_cast(e)); } - EnumFlags operator&(TEnum e) const { return EnumFlags(mValue & 1 << static_cast(e)); } - EnumFlags operator^(TEnum e) const { return EnumFlags(mValue ^ 1 << static_cast(e)); } - - EnumFlags operator~() const { return EnumFlags(~mValue); } -}; diff --git a/source/10-common/LookupTable.hpp b/source/10-common/LookupTable.hpp deleted file mode 100644 index 54548f2..0000000 --- a/source/10-common/LookupTable.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include - -// BIDI stands for bi-directional -#define BIDI_LUT_DECL(name, aType, aCount, bType, bCount) \ - int gLutBidi_##name##_A2B[aCount]; \ - int gLutBidi_##name##_B2A[bCount]; \ - using name##AType = aType; \ - using name##BType = bType; \ - void InitializeLutBidi##name() -#define BIDI_LUT_MAP_FOR(name) \ - int* lutMappingA2B = gLutBidi_##name##_A2B; \ - int* lutMappingB2A = gLutBidi_##name##_B2A -#define BIDI_LUT_MAP(from, to) \ - lutMappingA2B[from] = to; \ - lutMappingB2A[to] = from -#define BIDI_LUT_INIT(name) InitializeLutBidi##name() -#define BIDI_LUT_A2B_LOOKUP(name, from) (name##BType)(gLutBidi_##name##_A2B[from]) -#define BIDI_LUT_B2A_LOOKUP(name, to) (name##AType)(gLutBidi_##name##_B2A[to]) - -// Forward string lookup -#define FSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ - constexpr int kLutFwMinVal_##name = enumMinValue; \ - const char* gLutFw_##name[(int)enumMaxValue - (int)enumMinValue]; \ - void InitializeLutFw##name() -#define FSTR_LUT_MAP_FOR(name) \ - const char** lutMapping = gLutFw_##name; \ - int lutMappingMinValue = kLutFwMinVal_##name -#define FSTR_LUT_MAP(value, text) lutMapping[value - lutMappingMinValue] = text -#define FSTR_LUT_MAP_ENUM(enumValue) FSTR_LUT_MAP(enumValue, #enumValue) -#define FSTR_LUT_LOOKUP(name, enumValue) gLutFw_##name[enumValue - kLutFwMinVal_##name] -#define FSTR_LUT_INIT(name) InitializeLutFw##name() - -// RSTR stands for reverse-string lookup -#define RSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ - robin_hood::unordered_flat_map gLutRv_##name; \ - void InitializeLutRv##name() -#define RSTR_LUT_MAP_FOR(name) auto& lutMapping = gLutRv_##name; -#define RSTR_LUT_MAP(value, text) lutMapping.insert_or_assign(std::string_view(text), value); -#define RSTR_LUT(name) gLutRv_##name -#define BSTR_LUT_LOOKUP(name, string) gLutRv_##name.find(std::string_view(text))->second -#define RSTR_LUT_INIT(name) InitializeLutRv##name() - -// BSTR stands for bi-directional string lookup -#define BSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ - constexpr int kLutBstrMinVal_##name = enumMinValue; \ - const char* gLutBstr_##name##_V2S[(int)enumMaxValue - (int)enumMinValue]; \ - robin_hood::unordered_flat_map gLutBstr_##name##_S2V; \ - void InitializeLutBstr##name() -#define BSTR_LUT_MAP_FOR(name) \ - const char** lutMappingV2S = gLutBstr_##name##_V2S; \ - auto& lutMappingS2V = gLutBstr_##name##_S2V; \ - int lutMappingMinValue = kLutBstrMinVal_##name -#define BSTR_LUT_MAP(value, text) \ - lutMappingV2S[value - lutMappingMinValue] = text; \ - lutMappingS2V.insert_or_assign(std::string_view(text), value); -#define BSTR_LUT_MAP_ENUM(enumValue) BSTR_LUT_MAP(enumValue, #enumValue) -#define BSTR_LUT_V2S(name) gLutBstr_##name##_V2S -#define BSTR_LUT_S2V(name) gLutBstr_##name##_S2V -#define BSTR_LUT_V2S_LOOKUP(name, enumValue) gLutBstr_##name##_V2S[enumValue - kLutBstrMinVal_##name] -#define BSTR_LUT_S2V_LOOKUP(name, string) gLutBstr_##name##_S2V.find(std::string_view(text))->second -#define BSTR_LUT_INIT(name) InitializeLutBstr##name() diff --git a/source/10-common/Macros.hpp b/source/10-common/Macros.hpp deleted file mode 100644 index a255ada..0000000 --- a/source/10-common/Macros.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#define STRINGIFY_IMPL(text) #text -#define STRINGIFY(text) STRINGIFY_IMPL(text) - -#define CONCAT_IMPL(a, b) a##b -#define CONCAT(a, b) CONCAT_IMPL(a, b) -#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c)) -#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d)) - -#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__) -#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__) -#define DISCARD UNIQUE_NAME(_discard) - -#define UNUSED(x) (void)x; - -#define PRINTF_STRING_VIEW(s) (int)s.size(), s.data() - -#if defined(_MSC_VER) -# define UNREACHABLE __assume(0) -#elif defined(__GNUC__) || defined(__clang__) -# define UNREACHABLE __builtin_unreachable() -#else -# define UNREACHABLE -#endif - -#if _WIN32 -# define PLATFORM_PATH_STR "%ls" -#else -# define PLATFORM_PATH_STR "%s" -#endif diff --git a/source/10-common/PodVector.hpp b/source/10-common/PodVector.hpp deleted file mode 100644 index 74e99d6..0000000 --- a/source/10-common/PodVector.hpp +++ /dev/null @@ -1,297 +0,0 @@ -// File adapted from dear-imgui's ImVector, implemented in https://github.com/ocornut/imgUI/blob/master/imgui.h -#pragma once - -#include -#include -#include -#include -#include -#include - -template -class PodVector { -public: - using value_type = T; - using iterator = value_type*; - using const_iterator = const value_type*; - -private: - int mSize; - int mCapacity; - T* mData; - -public: - PodVector() { - mSize = mCapacity = 0; - mData = nullptr; - } - - PodVector(const PodVector& src) { - mSize = mCapacity = 0; - mData = nullptr; - operator=(src); - } - - PodVector& operator=(const PodVector& src) { - clear(); - resize(src.mSize); - std::memcpy(mData, src.mData, (size_t)mSize * sizeof(T)); - return *this; - } - - PodVector(PodVector&& src) { - mSize = src.mSize; - mCapacity = src.mCapacity; - mData = src.mData; - - src.mSize = src.mCapacity = 0; - src.mData = nullptr; - } - - PodVector& operator=(PodVector&& src) { - if (this != &src) { - std::free(mData); - - mSize = src.mSize; - mCapacity = src.mCapacity; - mData = src.mData; - - src.mSize = src.mCapacity = 0; - src.mData = nullptr; - } - return *this; - } - - ~PodVector() { - std::free(mData); - } - - bool empty() const { return mSize == 0; } - int size() const { return mSize; } - int size_in_bytes() const { return mSize * (int)sizeof(T); } - int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); } - int capacity() const { return mCapacity; } - - T& operator[](int i) { - assert(i >= 0 && i < mSize); - return mData[i]; - } - - const T& operator[](int i) const { - assert(i >= 0 && i < mSize); - return mData[i]; - } - - void clear() { - if (mData) { - mSize = mCapacity = 0; - std::free(mData); - mData = nullptr; - } - } - - T* begin() { return mData; } - const T* begin() const { return mData; } - T* end() { return mData + mSize; } - const T* end() const { return mData + mSize; } - - T* data() { return mData; } - - T& front() { - assert(mSize > 0); - return mData[0]; - } - - const T& front() const { - assert(mSize > 0); - return mData[0]; - } - - T& back() { - assert(mSize > 0); - return mData[mSize - 1]; - } - - const T& back() const { - assert(mSize > 0); - return mData[mSize - 1]; - } - - void swap(PodVector& rhs) { - int rhs_size = rhs.mSize; - rhs.mSize = mSize; - mSize = rhs_size; - int rhs_cap = rhs.mCapacity; - rhs.mCapacity = mCapacity; - mCapacity = rhs_cap; - T* rhs_mDataTmp = rhs.mData; - rhs.mData = mData; - mData = rhs_mDataTmp; - } - - int grow_capacity(int sz) const { - int newCapacity = mCapacity ? (mCapacity + mCapacity / 2) : 8; - return newCapacity > sz ? newCapacity : sz; - } - - void resize(int new_size) { - if (new_size > mCapacity) reserve(grow_capacity(new_size)); - mSize = new_size; - } - - void resize_more(int size) { - resize(mSize + size); - } - - void resize(int new_size, const T& v) { - if (new_size > mCapacity) reserve(grow_capacity(new_size)); - if (new_size > mSize) { - for (int n = mSize; n < new_size; n++) { - std::memcpy(&mData[n], &v, sizeof(v)); - } - } - mSize = new_size; - } - - void resize_more(int size, const T& v) { - resize(mSize + size, v); - } - - void shrink(int new_size) { - assert(new_size <= mSize); - mSize = new_size; - } - - /// Resize a vector to a smaller mSize, guaranteed not to cause a reallocation - void reserve(int newCapacity) { - if (newCapacity <= mCapacity) return; - auto tmp = (T*)std::malloc((size_t)newCapacity * sizeof(T)); - if (mData) { - std::memcpy(tmp, mData, (size_t)mSize * sizeof(T)); - std::free(mData); - } - mData = tmp; - mCapacity = newCapacity; - } - - void reserve_more(int size) { - reserve(mSize + size); - } - - /// NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the PodVector data itself! e.g. v.push_back(v[10]) is forbidden. - void push_back(const T& v) { - if (mSize == mCapacity) reserve(grow_capacity(mSize + 1)); - std::memcpy(&mData[mSize], &v, sizeof(v)); - mSize++; - } - - void pop_back() { - assert(mSize > 0); - mSize--; - } - - void push_front(const T& v) { - if (mSize == 0) { - push_back(v); - } else { - insert(mData, v); - } - } - - T* erase(const T* it) { - assert(it >= mData && it < mData + mSize); - const ptrdiff_t off = it - mData; - std::memmove(mData + off, mData + off + 1, ((size_t)mSize - (size_t)off - 1) * sizeof(T)); - mSize--; - return mData + off; - } - - T* erase(const T* it, const T* it_last) { - assert(it >= mData && it < mData + mSize && it_last > it && it_last <= mData + mSize); - const ptrdiff_t count = it_last - it; - const ptrdiff_t off = it - mData; - std::memmove(mData + off, mData + off + count, ((size_t)mSize - (size_t)off - count) * sizeof(T)); - mSize -= (int)count; - return mData + off; - } - - T* erase_unsorted(const T* it) { - assert(it >= mData && it < mData + mSize); - const ptrdiff_t off = it - mData; - if (it < mData + mSize - 1) std::memcpy(mData + off, mData + mSize - 1, sizeof(T)); - mSize--; - return mData + off; - } - - T* insert(const T* it, const T& v) { - assert(it >= mData && it <= mData + mSize); - const ptrdiff_t off = it - mData; - if (mSize == mCapacity) reserve(grow_capacity(mSize + 1)); - if (off < (int)mSize) std::memmove(mData + off + 1, mData + off, ((size_t)mSize - (size_t)off) * sizeof(T)); - std::memcpy(&mData[off], &v, sizeof(v)); - mSize++; - return mData + off; - } - - bool contains(const T& v) const { - const T* data = mData; - const T* dataEnd = mData + mSize; - while (data < dataEnd) { - if (*data++ == v) return true; - } - return false; - } - - T* find(const T& v) { - T* data = mData; - const T* dataEnd = mData + mSize; - while (data < dataEnd) - if (*data == v) - break; - else - ++data; - return data; - } - - const T* find(const T& v) const { - const T* data = mData; - const T* dataEnd = mData + mSize; - while (data < dataEnd) - if (*data == v) - break; - else - ++data; - return data; - } - - bool find_erase(const T& v) { - const T* it = find(v); - if (it < mData + mSize) { - erase(it); - return true; - } - return false; - } - - bool find_erase_unsorted(const T& v) { - const T* it = find(v); - if (it < mData + mSize) { - erase_unsorted(it); - return true; - } - return false; - } - - int index_from_ptr(const T* it) const { - assert(it >= mData && it < mData + mSize); - const ptrdiff_t off = it - mData; - return (int)off; - } - - // Custom utility functions - - std::span as_span() { return { mData, (size_t)mSize }; } - std::span as_data_span() { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; } - std::span as_span() const { return { mData, (size_t)mSize }; } - std::span as_data_span() const { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; } -}; diff --git a/source/10-common/RTTI.hpp b/source/10-common/RTTI.hpp deleted file mode 100644 index bc0d289..0000000 --- a/source/10-common/RTTI.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -template -bool is_a(TBase* t) { - assert(t != nullptr); - return T::IsInstance(t); -} - -template -bool is_a_nullable(TBase* t) { - if (t) { - return is_a(t); - } else { - return false; - } -} - -template -T* dyn_cast(TBase* t) { - assert(t != nullptr); - if (T::IsInstance(t)) { - return static_cast(t); - } else { - return nullptr; - } -} - -template -const T* dyn_cast(const TBase* t) { - assert(t != nullptr); - if (T::IsInstance(t)) { - return static_cast(t); - } else { - return nullptr; - } -} - -template -T* dyn_cast_nullable(TBase* t) { - if (!t) return nullptr; - return dyn_cast(t); -} diff --git a/source/10-common/RapidJsonHelper.hpp b/source/10-common/RapidJsonHelper.hpp deleted file mode 100644 index 75cd93a..0000000 --- a/source/10-common/RapidJsonHelper.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#define BRUSSEL_JSON_GET(object, name, type, out, failAction) \ - { \ - auto it = (object).FindMember(name); \ - if (it == (object).MemberEnd()) failAction; \ - auto& value = it->value; \ - if (!value.Is()) failAction; \ - (out) = value.Get(); \ - } - -#define BRUSSEL_JSON_GET_DEFAULT(object, name, type, out, theDefault) \ - do { \ - auto it = (object).FindMember(name); \ - if (it == (object).MemberEnd()) { \ - (out) = theDefault; \ - break; \ - } \ - auto& value = it->value; \ - if (!value.Is()) { \ - (out) = theDefault; \ - break; \ - } \ - (out) = value.Get(); \ - } while (0); - -namespace rapidjson { - -inline const Value* GetProperty(const Value& value, std::string_view name) { - for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { - if (it->name.GetStringLength() != name.size()) continue; - if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue; - - return &it->value; - } - return nullptr; -} - -inline const Value* GetProperty(const Value& value, Type type, std::string_view name) { - for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { - if (it->name.GetStringLength() != name.size()) continue; - if (it->value.GetType() != type) continue; - if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue; - - return &it->value; - } - return nullptr; -} - -inline std::string_view AsStringView(const Value& value) { - return std::string_view(value.GetString(), value.GetStringLength()); -} - -inline std::string_view AsStringView(const GenericStringRef& strRef) { - return std::string_view(strRef.s, strRef.length); -} - -inline std::string AsString(const Value& value) { - return std::string(value.GetString(), value.GetStringLength()); -} - -inline std::string AsString(const GenericStringRef& strRef) { - return std::string(strRef.s, strRef.length); -} - -// RapidJson itself already provides std::string and const char* overloads -inline GenericStringRef StringRef(std::string_view str) { - return GenericStringRef( - str.data() ? str.data() : "", - str.size()); -} - -template -rapidjson::Value WriteVectorPrimitives(rapidjson::Document& root, TIter begin, TSentienl end) { - using TElement = typename TIter::value_type; - - rapidjson::Value list; - while (begin != end) { - if constexpr (std::is_same_v) { - auto& elm = *begin; - list.PushBack(rapidjson::Value(elm.c_str(), elm.size()), root.GetAllocator()); - } else { - list.PushBack(*begin, root.GetAllocator()); - } - ++begin; - } - return list; -} - -template -bool ReadVectorPrimitives(const rapidjson::Value& value, TContainer& list) { - using TElement = typename TContainer::value_type; - - if (!value.IsArray()) return false; - - list.reserve(value.Size()); - for (auto& elm : value.GetArray()) { - if (!elm.Is()) return {}; - list.push_back(elm.Get()); - } - - return true; -} - -} // namespace rapidjson diff --git a/source/10-common/RcPtr.hpp b/source/10-common/RcPtr.hpp deleted file mode 100644 index 130b2b2..0000000 --- a/source/10-common/RcPtr.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once - -#include "Macros.hpp" -#include "TypeTraits.hpp" - -#include -#include -#include -#include - -class RefCounted { -public: - // DO NOT MODIFY this field, unless explicitly documented the use - uint32_t refCount = 0; - uint32_t weakCount = 0; // TODO implement -}; - -template > -class RcPtr : TDeleter { -private: - static_assert(std::is_base_of_v); - T* mPtr; - -public: - RcPtr() - : mPtr{ nullptr } { - } - - explicit RcPtr(T* ptr) - : mPtr{ ptr } { - if (ptr) { - ++ptr->RefCounted::refCount; - } - } - - ~RcPtr() { - CleanUp(); - } - - void Attach(T* ptr) { - CleanUp(); - mPtr = ptr; - if (ptr) { - ++ptr->RefCounted::refCount; - } - } - - void Detatch() { - CleanUp(); - mPtr = nullptr; - } - - RcPtr(const RcPtr& that) - : mPtr{ that.mPtr } { - if (mPtr) { - ++mPtr->RefCounted::refCount; - } - } - - RcPtr& operator=(const RcPtr& that) { - CleanUp(); - mPtr = that.mPtr; - if (mPtr) { - ++mPtr->RefCounted::refCount; - } - return *this; - } - - RcPtr(RcPtr&& that) - : mPtr{ that.mPtr } { - that.mPtr = nullptr; - } - - RcPtr& operator=(RcPtr&& that) { - CleanUp(); - mPtr = that.mPtr; - that.mPtr = nullptr; - return *this; - } - - template - requires std::is_base_of_v - operator RcPtr() const { - return RcPtr(mPtr); - } - - bool operator==(std::nullptr_t ptr) const { - return mPtr == nullptr; - } - - bool operator==(const T* ptr) const { - return mPtr == ptr; - } - - bool operator==(T* ptr) const { - return mPtr == ptr; - } - - template - bool operator==(const RcPtr& ptr) const { - return mPtr == ptr.Get(); - } - - T* Get() const { - return mPtr; - } - - T& operator*() const { return *mPtr; } - T* operator->() const { return mPtr; } - -private: - void CleanUp() { - if (mPtr) { - --mPtr->RefCounted::refCount; - if (mPtr->RefCounted::refCount == 0) { - TDeleter::operator()(mPtr); - } - } - } -}; diff --git a/source/10-common/Rect.hpp b/source/10-common/Rect.hpp deleted file mode 100644 index 89d9b01..0000000 --- a/source/10-common/Rect.hpp +++ /dev/null @@ -1,164 +0,0 @@ -#pragma once - -#include - -/// Rect is a rectangle representation based on a point and a dimensions, in television coordinate space -/// (x increases from left to right, y increases from top to bottom). -template -class Rect { -public: - using ScalarType = T; - using VectorType = glm::vec<2, T>; - -public: - T x; - T y; - T width; - T height; - -public: - Rect() - : x{ 0 }, y{ 0 }, width{ 0 }, height{ 0 } { - } - - Rect(T x, T y, T width, T height) - : x{ x }, y{ y }, width{ width }, height{ height } { - } - - Rect(VectorType pos, VectorType size) - : x{ pos.x } - , y{ pos.y } - , width{ size.x } - , height{ size.y } { - } - - T x0() const { return x; } - T y0() const { return y; } - T x1() const { return x + width; } - T y1() const { return y + height; } - - VectorType TopLeft() const { - return VectorType{ x, y }; - } - - VectorType TopRight() const { - return VectorType{ x + width, y }; - } - - VectorType BottomLeft() const { - return VectorType{ x, y + height }; - } - - VectorType BottomRight() const { - return VectorType{ x + width, y + height }; - } - - VectorType Center() const { - return TopLeft() + VectorType{ width / 2, height / 2 }; - } - - VectorType Dimensions() const { - return VectorType{ width, height }; - } - - VectorType Extents() const { - return VectorType{ width / 2, height / 2 }; - } - - /// Assumes `bySize * 2` is smaller than both `width` and `height` (does not produce a negative-dimension rectangle). - Rect Shrink(T bySize) const { - T two = bySize * 2; - return Rect{ x + bySize, y + bySize, width - two, height - two }; - } - - Rect Shrink(T left, T top, T right, T bottom) const { - return Rect{ - x + left, - y + top, - width - left - right, - height - top - bottom, - }; - } - - Rect Expand(T bySize) const { - T two = bySize * 2; - return Rect{ x - bySize, y - bySize, width + two, height + two }; - } - - Rect Expand(T left, T top, T right, T bottom) const { - return Rect{ - x - left, - y - top, - width + left + right, - height + top + bottom, - }; - } - - bool Contains(VectorType point) const { - return point.x >= x && - point.y >= y && - point.x < x + width && - point.y < y + height; - } - - bool Intersects(const Rect& that) const { - bool xBetween = x > that.x0() && x < that.x1(); - bool yBetween = y > that.y0() && y < that.y1(); - return xBetween && yBetween; - } - - // Write min()/max() tenary by hand so that we don't have to include - // This file is practically going to be included in every file in this project - - static Rect Intersection(const Rect& a, const Rect& b) { - auto x0 = a.x0() > b.x0() ? a.x0() : b.x0(); // Max - auto y0 = a.y0() > b.y0() ? a.y0() : b.y0(); // Max - auto x1 = a.x1() < b.x1() ? a.x1() : b.x1(); // Min - auto y1 = a.y1() < b.y1() ? a.y1() : b.y1(); // Min - auto width = x1 - x0; - auto height = y1 - x0; - return Rect{ x0, y0, width, height }; - } - - static Rect Union(const Rect& a, const Rect& b) { - auto x0 = a.x0() < b.x0() ? a.x0() : b.x0(); // Min - auto y0 = a.y0() < b.y0() ? a.y0() : b.y0(); // Min - auto x1 = a.x1() > b.x1() ? a.x1() : b.x1(); // Max - auto y1 = a.y1() > b.y1() ? a.y1() : b.y1(); // Max - auto width = x1 - x0; - auto height = y1 - x0; - return Rect{ x0, y0, width, height }; - } - - friend bool operator==(const Rect&, const Rect&) = default; - - Rect operator+(glm::vec<2, T> offset) const { - return { x + offset.x, y + offset.y, width, height }; - } - - Rect operator-(glm::vec<2, T> offset) const { - return { x - offset.x, y - offset.y, width, height }; - } - - Rect& operator+=(glm::vec<2, T> offset) { - x += offset.x; - y += offset.y; - return *this; - } - - Rect& operator-=(glm::vec<2, T> offset) { - x -= offset.x; - y -= offset.y; - return *this; - } - - template - Rect Cast() const { - return { - static_cast(x), - static_cast(y), - static_cast(width), - static_cast(height), - }; - } -}; diff --git a/source/10-common/ScopeGuard.hpp b/source/10-common/ScopeGuard.hpp deleted file mode 100644 index 28f3385..0000000 --- a/source/10-common/ScopeGuard.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "Macros.hpp" - -#include - -template -class ScopeGuard { -private: - TCleanupFunc mFunc; - bool mDismissed = false; - -public: - /// Specifically left this implicit so that constructs like - /// \code - /// ScopeGuard sg = [&]() { res.Cleanup(); }; - /// \endcode - /// would work. It is highly discourage and unlikely that one would want to use ScopeGuard as a function - /// parameter, so the normal argument that implicit conversion are harmful doesn't really apply here. - // Deliberately not explicit to allow usages like: ScopeGuard var = lambda; - ScopeGuard(TCleanupFunc&& function) noexcept - : mFunc{ std::move(function) } { - } - - ~ScopeGuard() noexcept { - if (!mDismissed) { - mFunc(); - } - } - - ScopeGuard(const ScopeGuard&) = delete; - ScopeGuard& operator=(const ScopeGuard&) = delete; - - ScopeGuard(ScopeGuard&& that) noexcept - : mFunc{ std::move(that.mFunc) } { - that.Cancel(); - } - - ScopeGuard& operator=(ScopeGuard&& that) noexcept { - if (!mDismissed) { - mFunc(); - } - this->mFunc = std::move(that.mFunc); - this->cancelled = std::exchange(that.cancelled, true); - } - - void Dismiss() noexcept { - mDismissed = true; - } -}; - -template -auto GuardDeletion(T* ptr) { - return ScopeGuard([ptr]() { - delete ptr; - }); -} - -#define SCOPE_GUARD(name) ScopeGuard name = [&]() -#define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]() diff --git a/source/10-common/SmallVector.cpp b/source/10-common/SmallVector.cpp deleted file mode 100644 index c38e8a7..0000000 --- a/source/10-common/SmallVector.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/lib/Support/SmallVector.cpp -// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c -// adapted in code style and utilities to fix this project - -//===- llvm/ADT/SmallVector.cpp - 'Normally small' vectors ----------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file implements the SmallVector class. -// -//===----------------------------------------------------------------------===// - -#include "SmallVector.hpp" - -#include -#include -#include - -// Check that no bytes are wasted and everything is well-aligned. -namespace { -// These structures may cause binary compat warnings on AIX. Suppress the -// warning since we are only using these types for the static assertions below. -#if defined(_AIX) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Waix-compat" -#endif -struct Struct16B { - alignas(16) void* X; -}; -struct Struct32B { - alignas(32) void* X; -}; -#if defined(_AIX) -# pragma GCC diagnostic pop -#endif -} // namespace -static_assert(sizeof(SmallVector) == - sizeof(unsigned) * 2 + sizeof(void*), - "wasted space in SmallVector size 0"); -static_assert(alignof(SmallVector) >= alignof(Struct16B), - "wrong alignment for 16-byte aligned T"); -static_assert(alignof(SmallVector) >= alignof(Struct32B), - "wrong alignment for 32-byte aligned T"); -static_assert(sizeof(SmallVector) >= alignof(Struct16B), - "missing padding for 16-byte aligned T"); -static_assert(sizeof(SmallVector) >= alignof(Struct32B), - "missing padding for 32-byte aligned T"); -static_assert(sizeof(SmallVector) == - sizeof(unsigned) * 2 + sizeof(void*) * 2, - "wasted space in SmallVector size 1"); - -static_assert(sizeof(SmallVector) == - sizeof(void*) * 2 + sizeof(void*), - "1 byte elements have word-sized type for size and capacity"); - -/// Report that MinSize doesn't fit into this vector's size type. Throws -/// std::length_error or calls report_fatal_error. -[[noreturn]] static void report_size_overflow(size_t MinSize, size_t MaxSize); -static void report_size_overflow(size_t MinSize, size_t MaxSize) { - std::string Reason = "SmallVector unable to grow. Requested capacity (" + - std::to_string(MinSize) + - ") is larger than maximum value for size type (" + - std::to_string(MaxSize) + ")"; - throw std::length_error(Reason); -} - -/// Report that this vector is already at maximum capacity. Throws -/// std::length_error or calls report_fatal_error. -[[noreturn]] static void report_at_maximum_capacity(size_t MaxSize); -static void report_at_maximum_capacity(size_t MaxSize) { - std::string Reason = - "SmallVector capacity unable to grow. Already at maximum size " + - std::to_string(MaxSize); - throw std::length_error(Reason); -} - -// Note: Moving this function into the header may cause performance regression. -template -static size_t getNewCapacity(size_t MinSize, size_t TSize, size_t OldCapacity) { - constexpr size_t MaxSize = std::numeric_limits::max(); - - // Ensure we can fit the new capacity. - // This is only going to be applicable when the capacity is 32 bit. - if (MinSize > MaxSize) - report_size_overflow(MinSize, MaxSize); - - // Ensure we can meet the guarantee of space for at least one more element. - // The above check alone will not catch the case where grow is called with a - // default MinSize of 0, but the current capacity cannot be increased. - // This is only going to be applicable when the capacity is 32 bit. - if (OldCapacity == MaxSize) - report_at_maximum_capacity(MaxSize); - - // In theory 2*capacity can overflow if the capacity is 64 bit, but the - // original capacity would never be large enough for this to be a problem. - size_t NewCapacity = 2 * OldCapacity + 1; // Always grow. - return std::min(std::max(NewCapacity, MinSize), MaxSize); -} - -// Note: Moving this function into the header may cause performance regression. -template -void* SmallVectorBase::mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity) { - NewCapacity = getNewCapacity(MinSize, TSize, this->capacity()); - return malloc(NewCapacity * TSize); -} - -// Note: Moving this function into the header may cause performance regression. -template -void SmallVectorBase::grow_pod(void* FirstEl, size_t MinSize, size_t TSize) { - size_t NewCapacity = getNewCapacity(MinSize, TSize, this->capacity()); - void* NewElts; - if (BeginX == FirstEl) { - NewElts = malloc(NewCapacity * TSize); - - // Copy the elements over. No need to run dtors on PODs. - memcpy(NewElts, this->BeginX, size() * TSize); - } else { - // If this wasn't grown from the inline copy, grow the allocated space. - NewElts = realloc(this->BeginX, NewCapacity * TSize); - } - - this->BeginX = NewElts; - this->Capacity = NewCapacity; -} - -template class SmallVectorBase; - -// Disable the uint64_t instantiation for 32-bit builds. -// Both uint32_t and uint64_t instantiations are needed for 64-bit builds. -// This instantiation will never be used in 32-bit builds, and will cause -// warnings when sizeof(Size_T) > sizeof(size_t). -#if SIZE_MAX > UINT32_MAX -template class SmallVectorBase; - -// Assertions to ensure this #if stays in sync with SmallVectorSizeType. -static_assert(sizeof(SmallVectorSizeType) == sizeof(uint64_t), - "Expected SmallVectorBase variant to be in use."); -#else -static_assert(sizeof(SmallVectorSizeType) == sizeof(uint32_t), - "Expected SmallVectorBase variant to be in use."); -#endif diff --git a/source/10-common/SmallVector.hpp b/source/10-common/SmallVector.hpp deleted file mode 100644 index e33a25d..0000000 --- a/source/10-common/SmallVector.hpp +++ /dev/null @@ -1,1332 +0,0 @@ -// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/ADT/SmallVector.h -// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c -// adapted in code style and utilities to fix this project - -//===- llvm/ADT/SmallVector.h - 'Normally small' vectors --------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// This file defines the SmallVector class. -/// -//===----------------------------------------------------------------------===// - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4267) // The compiler detected a conversion from size_t to a smaller type. -#endif - -#if __has_builtin(__builtin_expect) || defined(__GNUC__) -# define LLVM_LIKELY(EXPR) __builtin_expect((bool)(EXPR), true) -# define LLVM_UNLIKELY(EXPR) __builtin_expect((bool)(EXPR), false) -#else -# define LLVM_LIKELY(EXPR) (EXPR) -# define LLVM_UNLIKELY(EXPR) (EXPR) -#endif - -template -class iterator_range; - -/// This is all the stuff common to all SmallVectors. -/// -/// The template parameter specifies the type which should be used to hold the -/// Size and Capacity of the SmallVector, so it can be adjusted. -/// Using 32 bit size is desirable to shrink the size of the SmallVector. -/// Using 64 bit size is desirable for cases like SmallVector, where a -/// 32 bit size would limit the vector to ~4GB. SmallVectors are used for -/// buffering bitcode output - which can exceed 4GB. -template -class SmallVectorBase { -protected: - void* BeginX; - Size_T Size = 0, Capacity; - - /// The maximum value of the Size_T used. - static constexpr size_t SizeTypeMax() { - return std::numeric_limits::max(); - } - - SmallVectorBase() = delete; - SmallVectorBase(void* FirstEl, size_t TotalCapacity) - : BeginX(FirstEl), Capacity(TotalCapacity) {} - - /// This is a helper for \a grow() that's out of line to reduce code - /// duplication. This function will report a fatal error if it can't grow at - /// least to \p MinSize. - void* mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity); - - /// This is an implementation of the grow() method which only works - /// on POD-like data types and is out of line to reduce code duplication. - /// This function will report a fatal error if it cannot increase capacity. - void grow_pod(void* FirstEl, size_t MinSize, size_t TSize); - -public: - size_t size() const { return Size; } - size_t capacity() const { return Capacity; } - - [[nodiscard]] bool empty() const { return !Size; } - -protected: - /// Set the array size to \p N, which the current array must have enough - /// capacity for. - /// - /// This does not construct or destroy any elements in the vector. - void set_size(size_t N) { - assert(N <= capacity()); - Size = N; - } -}; - -template -using SmallVectorSizeType = - typename std::conditional= 8, uint64_t, uint32_t>::type; - -/// Figure out the offset of the first element. -template -struct SmallVectorAlignmentAndSize { - alignas(SmallVectorBase>) char Base[sizeof( - SmallVectorBase>)]; - alignas(T) char FirstEl[sizeof(T)]; -}; - -/// This is the part of SmallVectorTemplateBase which does not depend on whether -/// the type T is a POD. The extra dummy template argument is used by ArrayRef -/// to avoid unnecessarily requiring T to be complete. -template -class SmallVectorTemplateCommon - : public SmallVectorBase> { - using Base = SmallVectorBase>; - - /// Find the address of the first element. For this pointer math to be valid - /// with small-size of 0 for T with lots of alignment, it's important that - /// SmallVectorStorage is properly-aligned even for small-size of 0. - void* getFirstEl() const { - return const_cast(reinterpret_cast( - reinterpret_cast(this) + - offsetof(SmallVectorAlignmentAndSize, FirstEl))); - } - // Space after 'FirstEl' is clobbered, do not add any instance vars after it. - -protected: - SmallVectorTemplateCommon(size_t Size) - : Base(getFirstEl(), Size) {} - - void grow_pod(size_t MinSize, size_t TSize) { - Base::grow_pod(getFirstEl(), MinSize, TSize); - } - - /// Return true if this is a smallvector which has not had dynamic - /// memory allocated for it. - bool isSmall() const { return this->BeginX == getFirstEl(); } - - /// Put this vector in a state of being small. - void resetToSmall() { - this->BeginX = getFirstEl(); - this->Size = this->Capacity = 0; // FIXME: Setting Capacity to 0 is suspect. - } - - /// Return true if V is an internal reference to the given range. - bool isReferenceToRange(const void* V, const void* First, const void* Last) const { - // Use std::less to avoid UB. - std::less<> LessThan; - return !LessThan(V, First) && LessThan(V, Last); - } - - /// Return true if V is an internal reference to this vector. - bool isReferenceToStorage(const void* V) const { - return isReferenceToRange(V, this->begin(), this->end()); - } - - /// Return true if First and Last form a valid (possibly empty) range in this - /// vector's storage. - bool isRangeInStorage(const void* First, const void* Last) const { - // Use std::less to avoid UB. - std::less<> LessThan; - return !LessThan(First, this->begin()) && !LessThan(Last, First) && - !LessThan(this->end(), Last); - } - - /// Return true unless Elt will be invalidated by resizing the vector to - /// NewSize. - bool isSafeToReferenceAfterResize(const void* Elt, size_t NewSize) { - // Past the end. - if (LLVM_LIKELY(!isReferenceToStorage(Elt))) - return true; - - // Return false if Elt will be destroyed by shrinking. - if (NewSize <= this->size()) - return Elt < this->begin() + NewSize; - - // Return false if we need to grow. - return NewSize <= this->capacity(); - } - - /// Check whether Elt will be invalidated by resizing the vector to NewSize. - void assertSafeToReferenceAfterResize(const void* Elt, size_t NewSize) { - assert(isSafeToReferenceAfterResize(Elt, NewSize) && - "Attempting to reference an element of the vector in an operation " - "that invalidates it"); - } - - /// Check whether Elt will be invalidated by increasing the size of the - /// vector by N. - void assertSafeToAdd(const void* Elt, size_t N = 1) { - this->assertSafeToReferenceAfterResize(Elt, this->size() + N); - } - - /// Check whether any part of the range will be invalidated by clearing. - void assertSafeToReferenceAfterClear(const T* From, const T* To) { - if (From == To) - return; - this->assertSafeToReferenceAfterResize(From, 0); - this->assertSafeToReferenceAfterResize(To - 1, 0); - } - template < - class ItTy, - std::enable_if_t, T*>::value, - bool> = false> - void assertSafeToReferenceAfterClear(ItTy, ItTy) {} - - /// Check whether any part of the range will be invalidated by growing. - void assertSafeToAddRange(const T* From, const T* To) { - if (From == To) - return; - this->assertSafeToAdd(From, To - From); - this->assertSafeToAdd(To - 1, To - From); - } - template < - class ItTy, - std::enable_if_t, T*>::value, - bool> = false> - void assertSafeToAddRange(ItTy, ItTy) {} - - /// Reserve enough space to add one element, and return the updated element - /// pointer in case it was a reference to the storage. - template - static const T* reserveForParamAndGetAddressImpl(U* This, const T& Elt, size_t N) { - size_t NewSize = This->size() + N; - if (LLVM_LIKELY(NewSize <= This->capacity())) - return &Elt; - - bool ReferencesStorage = false; - int64_t Index = -1; - if (!U::TakesParamByValue) { - if (LLVM_UNLIKELY(This->isReferenceToStorage(&Elt))) { - ReferencesStorage = true; - Index = &Elt - This->begin(); - } - } - This->grow(NewSize); - return ReferencesStorage ? This->begin() + Index : &Elt; - } - -public: - using size_type = size_t; - using difference_type = ptrdiff_t; - using value_type = T; - using iterator = T*; - using const_iterator = const T*; - - using const_reverse_iterator = std::reverse_iterator; - using reverse_iterator = std::reverse_iterator; - - using reference = T&; - using const_reference = const T&; - using pointer = T*; - using const_pointer = const T*; - - using Base::capacity; - using Base::empty; - using Base::size; - - // forward iterator creation methods. - iterator begin() { return (iterator)this->BeginX; } - const_iterator begin() const { return (const_iterator)this->BeginX; } - iterator end() { return begin() + size(); } - const_iterator end() const { return begin() + size(); } - - // reverse iterator creation methods. - reverse_iterator rbegin() { return reverse_iterator(end()); } - const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - reverse_iterator rend() { return reverse_iterator(begin()); } - const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - - size_type size_in_bytes() const { return size() * sizeof(T); } - size_type max_size() const { - return std::min(this->SizeTypeMax(), size_type(-1) / sizeof(T)); - } - - size_t capacity_in_bytes() const { return capacity() * sizeof(T); } - - /// Return a pointer to the vector's buffer, even if empty(). - pointer data() { return pointer(begin()); } - /// Return a pointer to the vector's buffer, even if empty(). - const_pointer data() const { return const_pointer(begin()); } - - reference operator[](size_type idx) { - assert(idx < size()); - return begin()[idx]; - } - const_reference operator[](size_type idx) const { - assert(idx < size()); - return begin()[idx]; - } - - reference front() { - assert(!empty()); - return begin()[0]; - } - const_reference front() const { - assert(!empty()); - return begin()[0]; - } - - reference back() { - assert(!empty()); - return end()[-1]; - } - const_reference back() const { - assert(!empty()); - return end()[-1]; - } -}; - -/// SmallVectorTemplateBase - This is where we put -/// method implementations that are designed to work with non-trivial T's. -/// -/// We approximate is_trivially_copyable with trivial move/copy construction and -/// trivial destruction. While the standard doesn't specify that you're allowed -/// copy these types with memcpy, there is no way for the type to observe this. -/// This catches the important case of std::pair, which is not -/// trivially assignable. -template ::value) && (std::is_trivially_move_constructible::value) && std::is_trivially_destructible::value> -class SmallVectorTemplateBase : public SmallVectorTemplateCommon { - friend class SmallVectorTemplateCommon; - -protected: - static constexpr bool TakesParamByValue = false; - using ValueParamT = const T&; - - SmallVectorTemplateBase(size_t Size) - : SmallVectorTemplateCommon(Size) {} - - static void destroy_range(T* S, T* E) { - while (S != E) { - --E; - E->~T(); - } - } - - /// Move the range [I, E) into the uninitialized memory starting with "Dest", - /// constructing elements as needed. - template - static void uninitialized_move(It1 I, It1 E, It2 Dest) { - std::uninitialized_copy(std::make_move_iterator(I), - std::make_move_iterator(E), - Dest); - } - - /// Copy the range [I, E) onto the uninitialized memory starting with "Dest", - /// constructing elements as needed. - template - static void uninitialized_copy(It1 I, It1 E, It2 Dest) { - std::uninitialized_copy(I, E, Dest); - } - - /// Grow the allocated memory (without initializing new elements), doubling - /// the size of the allocated memory. Guarantees space for at least one more - /// element, or MinSize more elements if specified. - void grow(size_t MinSize = 0); - - /// Create a new allocation big enough for \p MinSize and pass back its size - /// in \p NewCapacity. This is the first section of \a grow(). - T* mallocForGrow(size_t MinSize, size_t& NewCapacity) { - return static_cast( - SmallVectorBase>::mallocForGrow( - MinSize, sizeof(T), NewCapacity)); - } - - /// Move existing elements over to the new allocation \p NewElts, the middle - /// section of \a grow(). - void moveElementsForGrow(T* NewElts); - - /// Transfer ownership of the allocation, finishing up \a grow(). - void takeAllocationForGrow(T* NewElts, size_t NewCapacity); - - /// Reserve enough space to add one element, and return the updated element - /// pointer in case it was a reference to the storage. - const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) { - return this->reserveForParamAndGetAddressImpl(this, Elt, N); - } - - /// Reserve enough space to add one element, and return the updated element - /// pointer in case it was a reference to the storage. - T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) { - return const_cast( - this->reserveForParamAndGetAddressImpl(this, Elt, N)); - } - - static T&& forward_value_param(T&& V) { return std::move(V); } - static const T& forward_value_param(const T& V) { return V; } - - void growAndAssign(size_t NumElts, const T& Elt) { - // Grow manually in case Elt is an internal reference. - size_t NewCapacity; - T* NewElts = mallocForGrow(NumElts, NewCapacity); - std::uninitialized_fill_n(NewElts, NumElts, Elt); - this->destroy_range(this->begin(), this->end()); - takeAllocationForGrow(NewElts, NewCapacity); - this->set_size(NumElts); - } - - template - T& growAndEmplaceBack(ArgTypes&&... Args) { - // Grow manually in case one of Args is an internal reference. - size_t NewCapacity; - T* NewElts = mallocForGrow(0, NewCapacity); - ::new ((void*)(NewElts + this->size())) T(std::forward(Args)...); - moveElementsForGrow(NewElts); - takeAllocationForGrow(NewElts, NewCapacity); - this->set_size(this->size() + 1); - return this->back(); - } - -public: - void push_back(const T& Elt) { - const T* EltPtr = reserveForParamAndGetAddress(Elt); - ::new ((void*)this->end()) T(*EltPtr); - this->set_size(this->size() + 1); - } - - void push_back(T&& Elt) { - T* EltPtr = reserveForParamAndGetAddress(Elt); - ::new ((void*)this->end()) T(::std::move(*EltPtr)); - this->set_size(this->size() + 1); - } - - void pop_back() { - this->set_size(this->size() - 1); - this->end()->~T(); - } -}; - -// Define this out-of-line to dissuade the C++ compiler from inlining it. -template -void SmallVectorTemplateBase::grow(size_t MinSize) { - size_t NewCapacity; - T* NewElts = mallocForGrow(MinSize, NewCapacity); - moveElementsForGrow(NewElts); - takeAllocationForGrow(NewElts, NewCapacity); -} - -// Define this out-of-line to dissuade the C++ compiler from inlining it. -template -void SmallVectorTemplateBase::moveElementsForGrow( - T* NewElts) { - // Move the elements over. - this->uninitialized_move(this->begin(), this->end(), NewElts); - - // Destroy the original elements. - destroy_range(this->begin(), this->end()); -} - -// Define this out-of-line to dissuade the C++ compiler from inlining it. -template -void SmallVectorTemplateBase::takeAllocationForGrow( - T* NewElts, size_t NewCapacity) { - // If this wasn't grown from the inline copy, deallocate the old space. - if (!this->isSmall()) - free(this->begin()); - - this->BeginX = NewElts; - this->Capacity = NewCapacity; -} - -/// SmallVectorTemplateBase - This is where we put -/// method implementations that are designed to work with trivially copyable -/// T's. This allows using memcpy in place of copy/move construction and -/// skipping destruction. -template -class SmallVectorTemplateBase : public SmallVectorTemplateCommon { - friend class SmallVectorTemplateCommon; - -protected: - /// True if it's cheap enough to take parameters by value. Doing so avoids - /// overhead related to mitigations for reference invalidation. - static constexpr bool TakesParamByValue = sizeof(T) <= 2 * sizeof(void*); - - /// Either const T& or T, depending on whether it's cheap enough to take - /// parameters by value. - using ValueParamT = - typename std::conditional::type; - - SmallVectorTemplateBase(size_t Size) - : SmallVectorTemplateCommon(Size) {} - - // No need to do a destroy loop for POD's. - static void destroy_range(T*, T*) {} - - /// Move the range [I, E) onto the uninitialized memory - /// starting with "Dest", constructing elements into it as needed. - template - static void uninitialized_move(It1 I, It1 E, It2 Dest) { - // Just do a copy. - uninitialized_copy(I, E, Dest); - } - - /// Copy the range [I, E) onto the uninitialized memory - /// starting with "Dest", constructing elements into it as needed. - template - static void uninitialized_copy(It1 I, It1 E, It2 Dest) { - // Arbitrary iterator types; just use the basic implementation. - std::uninitialized_copy(I, E, Dest); - } - - /// Copy the range [I, E) onto the uninitialized memory - /// starting with "Dest", constructing elements into it as needed. - template - static void uninitialized_copy( - T1* I, T1* E, T2* Dest, std::enable_if_t::type, T2>::value>* = nullptr) { - // Use memcpy for PODs iterated by pointers (which includes SmallVector - // iterators): std::uninitialized_copy optimizes to memmove, but we can - // use memcpy here. Note that I and E are iterators and thus might be - // invalid for memcpy if they are equal. - if (I != E) - memcpy(reinterpret_cast(Dest), I, (E - I) * sizeof(T)); - } - - /// Double the size of the allocated memory, guaranteeing space for at - /// least one more element or MinSize if specified. - void grow(size_t MinSize = 0) { this->grow_pod(MinSize, sizeof(T)); } - - /// Reserve enough space to add one element, and return the updated element - /// pointer in case it was a reference to the storage. - const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) { - return this->reserveForParamAndGetAddressImpl(this, Elt, N); - } - - /// Reserve enough space to add one element, and return the updated element - /// pointer in case it was a reference to the storage. - T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) { - return const_cast( - this->reserveForParamAndGetAddressImpl(this, Elt, N)); - } - - /// Copy \p V or return a reference, depending on \a ValueParamT. - static ValueParamT forward_value_param(ValueParamT V) { return V; } - - void growAndAssign(size_t NumElts, T Elt) { - // Elt has been copied in case it's an internal reference, side-stepping - // reference invalidation problems without losing the realloc optimization. - this->set_size(0); - this->grow(NumElts); - std::uninitialized_fill_n(this->begin(), NumElts, Elt); - this->set_size(NumElts); - } - - template - T& growAndEmplaceBack(ArgTypes&&... Args) { - // Use push_back with a copy in case Args has an internal reference, - // side-stepping reference invalidation problems without losing the realloc - // optimization. - push_back(T(std::forward(Args)...)); - return this->back(); - } - -public: - void push_back(ValueParamT Elt) { - const T* EltPtr = reserveForParamAndGetAddress(Elt); - memcpy(reinterpret_cast(this->end()), EltPtr, sizeof(T)); - this->set_size(this->size() + 1); - } - - void pop_back() { this->set_size(this->size() - 1); } -}; - -/// This class consists of common code factored out of the SmallVector class to -/// reduce code duplication based on the SmallVector 'N' template parameter. -template -class SmallVectorImpl : public SmallVectorTemplateBase { - using SuperClass = SmallVectorTemplateBase; - -public: - using iterator = typename SuperClass::iterator; - using const_iterator = typename SuperClass::const_iterator; - using reference = typename SuperClass::reference; - using size_type = typename SuperClass::size_type; - -protected: - using SmallVectorTemplateBase::TakesParamByValue; - using ValueParamT = typename SuperClass::ValueParamT; - - // Default ctor - Initialize to empty. - explicit SmallVectorImpl(unsigned N) - : SmallVectorTemplateBase(N) {} - - void assignRemote(SmallVectorImpl&& RHS) { - this->destroy_range(this->begin(), this->end()); - if (!this->isSmall()) - free(this->begin()); - this->BeginX = RHS.BeginX; - this->Size = RHS.Size; - this->Capacity = RHS.Capacity; - RHS.resetToSmall(); - } - -public: - SmallVectorImpl(const SmallVectorImpl&) = delete; - - ~SmallVectorImpl() { - // Subclass has already destructed this vector's elements. - // If this wasn't grown from the inline copy, deallocate the old space. - if (!this->isSmall()) - free(this->begin()); - } - - void clear() { - this->destroy_range(this->begin(), this->end()); - this->Size = 0; - } - -private: - // Make set_size() private to avoid misuse in subclasses. - using SuperClass::set_size; - - template - void resizeImpl(size_type N) { - if (N == this->size()) - return; - - if (N < this->size()) { - this->truncate(N); - return; - } - - this->reserve(N); - for (auto I = this->end(), E = this->begin() + N; I != E; ++I) - if (ForOverwrite) - new (&*I) T; - else - new (&*I) T(); - this->set_size(N); - } - -public: - void resize(size_type N) { resizeImpl(N); } - - /// Like resize, but \ref T is POD, the new values won't be initialized. - void resize_for_overwrite(size_type N) { resizeImpl(N); } - - /// Like resize, but requires that \p N is less than \a size(). - void truncate(size_type N) { - assert(this->size() >= N && "Cannot increase size with truncate"); - this->destroy_range(this->begin() + N, this->end()); - this->set_size(N); - } - - void resize(size_type N, ValueParamT NV) { - if (N == this->size()) - return; - - if (N < this->size()) { - this->truncate(N); - return; - } - - // N > this->size(). Defer to append. - this->append(N - this->size(), NV); - } - - void reserve(size_type N) { - if (this->capacity() < N) - this->grow(N); - } - - void pop_back_n(size_type NumItems) { - assert(this->size() >= NumItems); - truncate(this->size() - NumItems); - } - - [[nodiscard]] T pop_back_val() { - T Result = ::std::move(this->back()); - this->pop_back(); - return Result; - } - - void swap(SmallVectorImpl& RHS); - - /// Add the specified range to the end of the SmallVector. - template ::iterator_category, - std::input_iterator_tag>::value>> - void append(in_iter in_start, in_iter in_end) { - this->assertSafeToAddRange(in_start, in_end); - size_type NumInputs = std::distance(in_start, in_end); - this->reserve(this->size() + NumInputs); - this->uninitialized_copy(in_start, in_end, this->end()); - this->set_size(this->size() + NumInputs); - } - - /// Append \p NumInputs copies of \p Elt to the end. - void append(size_type NumInputs, ValueParamT Elt) { - const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumInputs); - std::uninitialized_fill_n(this->end(), NumInputs, *EltPtr); - this->set_size(this->size() + NumInputs); - } - - void append(std::initializer_list IL) { - append(IL.begin(), IL.end()); - } - - void append(const SmallVectorImpl& RHS) { append(RHS.begin(), RHS.end()); } - - void assign(size_type NumElts, ValueParamT Elt) { - // Note that Elt could be an internal reference. - if (NumElts > this->capacity()) { - this->growAndAssign(NumElts, Elt); - return; - } - - // Assign over existing elements. - std::fill_n(this->begin(), std::min(NumElts, this->size()), Elt); - if (NumElts > this->size()) - std::uninitialized_fill_n(this->end(), NumElts - this->size(), Elt); - else if (NumElts < this->size()) - this->destroy_range(this->begin() + NumElts, this->end()); - this->set_size(NumElts); - } - - // FIXME: Consider assigning over existing elements, rather than clearing & - // re-initializing them - for all assign(...) variants. - - template ::iterator_category, - std::input_iterator_tag>::value>> - void assign(in_iter in_start, in_iter in_end) { - this->assertSafeToReferenceAfterClear(in_start, in_end); - clear(); - append(in_start, in_end); - } - - void assign(std::initializer_list IL) { - clear(); - append(IL); - } - - void assign(const SmallVectorImpl& RHS) { assign(RHS.begin(), RHS.end()); } - - iterator erase(const_iterator CI) { - // Just cast away constness because this is a non-const member function. - iterator I = const_cast(CI); - - assert(this->isReferenceToStorage(CI) && "Iterator to erase is out of bounds."); - - iterator N = I; - // Shift all elts down one. - std::move(I + 1, this->end(), I); - // Drop the last elt. - this->pop_back(); - return (N); - } - - iterator erase(const_iterator CS, const_iterator CE) { - // Just cast away constness because this is a non-const member function. - iterator S = const_cast(CS); - iterator E = const_cast(CE); - - assert(this->isRangeInStorage(S, E) && "Range to erase is out of bounds."); - - iterator N = S; - // Shift all elts down. - iterator I = std::move(E, this->end(), S); - // Drop the last elts. - this->destroy_range(I, this->end()); - this->set_size(I - this->begin()); - return (N); - } - -private: - template - iterator insert_one_impl(iterator I, ArgType&& Elt) { - // Callers ensure that ArgType is derived from T. - static_assert( - std::is_same>, - T>::value, - "ArgType must be derived from T!"); - - if (I == this->end()) { // Important special case for empty vector. - this->push_back(::std::forward(Elt)); - return this->end() - 1; - } - - assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds."); - - // Grow if necessary. - size_t Index = I - this->begin(); - std::remove_reference_t* EltPtr = - this->reserveForParamAndGetAddress(Elt); - I = this->begin() + Index; - - ::new ((void*)this->end()) T(::std::move(this->back())); - // Push everything else over. - std::move_backward(I, this->end() - 1, this->end()); - this->set_size(this->size() + 1); - - // If we just moved the element we're inserting, be sure to update - // the reference (never happens if TakesParamByValue). - static_assert(!TakesParamByValue || std::is_same::value, - "ArgType must be 'T' when taking by value!"); - if (!TakesParamByValue && this->isReferenceToRange(EltPtr, I, this->end())) - ++EltPtr; - - *I = ::std::forward(*EltPtr); - return I; - } - -public: - iterator insert(iterator I, T&& Elt) { - return insert_one_impl(I, this->forward_value_param(std::move(Elt))); - } - - iterator insert(iterator I, const T& Elt) { - return insert_one_impl(I, this->forward_value_param(Elt)); - } - - iterator insert(iterator I, size_type NumToInsert, ValueParamT Elt) { - // Convert iterator to elt# to avoid invalidating iterator when we reserve() - size_t InsertElt = I - this->begin(); - - if (I == this->end()) { // Important special case for empty vector. - append(NumToInsert, Elt); - return this->begin() + InsertElt; - } - - assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds."); - - // Ensure there is enough space, and get the (maybe updated) address of - // Elt. - const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumToInsert); - - // Uninvalidate the iterator. - I = this->begin() + InsertElt; - - // If there are more elements between the insertion point and the end of the - // range than there are being inserted, we can use a simple approach to - // insertion. Since we already reserved space, we know that this won't - // reallocate the vector. - if (size_t(this->end() - I) >= NumToInsert) { - T* OldEnd = this->end(); - append(std::move_iterator(this->end() - NumToInsert), - std::move_iterator(this->end())); - - // Copy the existing elements that get replaced. - std::move_backward(I, OldEnd - NumToInsert, OldEnd); - - // If we just moved the element we're inserting, be sure to update - // the reference (never happens if TakesParamByValue). - if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end()) - EltPtr += NumToInsert; - - std::fill_n(I, NumToInsert, *EltPtr); - return I; - } - - // Otherwise, we're inserting more elements than exist already, and we're - // not inserting at the end. - - // Move over the elements that we're about to overwrite. - T* OldEnd = this->end(); - this->set_size(this->size() + NumToInsert); - size_t NumOverwritten = OldEnd - I; - this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten); - - // If we just moved the element we're inserting, be sure to update - // the reference (never happens if TakesParamByValue). - if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end()) - EltPtr += NumToInsert; - - // Replace the overwritten part. - std::fill_n(I, NumOverwritten, *EltPtr); - - // Insert the non-overwritten middle part. - std::uninitialized_fill_n(OldEnd, NumToInsert - NumOverwritten, *EltPtr); - return I; - } - - template ::iterator_category, - std::input_iterator_tag>::value>> - iterator insert(iterator I, ItTy From, ItTy To) { - // Convert iterator to elt# to avoid invalidating iterator when we reserve() - size_t InsertElt = I - this->begin(); - - if (I == this->end()) { // Important special case for empty vector. - append(From, To); - return this->begin() + InsertElt; - } - - assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds."); - - // Check that the reserve that follows doesn't invalidate the iterators. - this->assertSafeToAddRange(From, To); - - size_t NumToInsert = std::distance(From, To); - - // Ensure there is enough space. - reserve(this->size() + NumToInsert); - - // Uninvalidate the iterator. - I = this->begin() + InsertElt; - - // If there are more elements between the insertion point and the end of the - // range than there are being inserted, we can use a simple approach to - // insertion. Since we already reserved space, we know that this won't - // reallocate the vector. - if (size_t(this->end() - I) >= NumToInsert) { - T* OldEnd = this->end(); - append(std::move_iterator(this->end() - NumToInsert), - std::move_iterator(this->end())); - - // Copy the existing elements that get replaced. - std::move_backward(I, OldEnd - NumToInsert, OldEnd); - - std::copy(From, To, I); - return I; - } - - // Otherwise, we're inserting more elements than exist already, and we're - // not inserting at the end. - - // Move over the elements that we're about to overwrite. - T* OldEnd = this->end(); - this->set_size(this->size() + NumToInsert); - size_t NumOverwritten = OldEnd - I; - this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten); - - // Replace the overwritten part. - for (T* J = I; NumOverwritten > 0; --NumOverwritten) { - *J = *From; - ++J; - ++From; - } - - // Insert the non-overwritten middle part. - this->uninitialized_copy(From, To, OldEnd); - return I; - } - - void insert(iterator I, std::initializer_list IL) { - insert(I, IL.begin(), IL.end()); - } - - template - reference emplace_back(ArgTypes&&... Args) { - if (LLVM_UNLIKELY(this->size() >= this->capacity())) - return this->growAndEmplaceBack(std::forward(Args)...); - - ::new ((void*)this->end()) T(std::forward(Args)...); - this->set_size(this->size() + 1); - return this->back(); - } - - SmallVectorImpl& operator=(const SmallVectorImpl& RHS); - - SmallVectorImpl& operator=(SmallVectorImpl&& RHS); - - bool operator==(const SmallVectorImpl& RHS) const { - if (this->size() != RHS.size()) return false; - return std::equal(this->begin(), this->end(), RHS.begin()); - } - bool operator!=(const SmallVectorImpl& RHS) const { - return !(*this == RHS); - } - - bool operator<(const SmallVectorImpl& RHS) const { - return std::lexicographical_compare(this->begin(), this->end(), RHS.begin(), RHS.end()); - } -}; - -template -void SmallVectorImpl::swap(SmallVectorImpl& RHS) { - if (this == &RHS) return; - - // We can only avoid copying elements if neither vector is small. - if (!this->isSmall() && !RHS.isSmall()) { - std::swap(this->BeginX, RHS.BeginX); - std::swap(this->Size, RHS.Size); - std::swap(this->Capacity, RHS.Capacity); - return; - } - this->reserve(RHS.size()); - RHS.reserve(this->size()); - - // Swap the shared elements. - size_t NumShared = this->size(); - if (NumShared > RHS.size()) NumShared = RHS.size(); - for (size_type i = 0; i != NumShared; ++i) - std::swap((*this)[i], RHS[i]); - - // Copy over the extra elts. - if (this->size() > RHS.size()) { - size_t EltDiff = this->size() - RHS.size(); - this->uninitialized_copy(this->begin() + NumShared, this->end(), RHS.end()); - RHS.set_size(RHS.size() + EltDiff); - this->destroy_range(this->begin() + NumShared, this->end()); - this->set_size(NumShared); - } else if (RHS.size() > this->size()) { - size_t EltDiff = RHS.size() - this->size(); - this->uninitialized_copy(RHS.begin() + NumShared, RHS.end(), this->end()); - this->set_size(this->size() + EltDiff); - this->destroy_range(RHS.begin() + NumShared, RHS.end()); - RHS.set_size(NumShared); - } -} - -template -SmallVectorImpl& SmallVectorImpl:: -operator=(const SmallVectorImpl& RHS) { - // Avoid self-assignment. - if (this == &RHS) return *this; - - // If we already have sufficient space, assign the common elements, then - // destroy any excess. - size_t RHSSize = RHS.size(); - size_t CurSize = this->size(); - if (CurSize >= RHSSize) { - // Assign common elements. - iterator NewEnd; - if (RHSSize) - NewEnd = std::copy(RHS.begin(), RHS.begin() + RHSSize, this->begin()); - else - NewEnd = this->begin(); - - // Destroy excess elements. - this->destroy_range(NewEnd, this->end()); - - // Trim. - this->set_size(RHSSize); - return *this; - } - - // If we have to grow to have enough elements, destroy the current elements. - // This allows us to avoid copying them during the grow. - // FIXME: don't do this if they're efficiently moveable. - if (this->capacity() < RHSSize) { - // Destroy current elements. - this->clear(); - CurSize = 0; - this->grow(RHSSize); - } else if (CurSize) { - // Otherwise, use assignment for the already-constructed elements. - std::copy(RHS.begin(), RHS.begin() + CurSize, this->begin()); - } - - // Copy construct the new elements in place. - this->uninitialized_copy(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize); - - // Set end. - this->set_size(RHSSize); - return *this; -} - -template -SmallVectorImpl& SmallVectorImpl::operator=(SmallVectorImpl&& RHS) { - // Avoid self-assignment. - if (this == &RHS) return *this; - - // If the RHS isn't small, clear this vector and then steal its buffer. - if (!RHS.isSmall()) { - this->assignRemote(std::move(RHS)); - return *this; - } - - // If we already have sufficient space, assign the common elements, then - // destroy any excess. - size_t RHSSize = RHS.size(); - size_t CurSize = this->size(); - if (CurSize >= RHSSize) { - // Assign common elements. - iterator NewEnd = this->begin(); - if (RHSSize) - NewEnd = std::move(RHS.begin(), RHS.end(), NewEnd); - - // Destroy excess elements and trim the bounds. - this->destroy_range(NewEnd, this->end()); - this->set_size(RHSSize); - - // Clear the RHS. - RHS.clear(); - - return *this; - } - - // If we have to grow to have enough elements, destroy the current elements. - // This allows us to avoid copying them during the grow. - // FIXME: this may not actually make any sense if we can efficiently move - // elements. - if (this->capacity() < RHSSize) { - // Destroy current elements. - this->clear(); - CurSize = 0; - this->grow(RHSSize); - } else if (CurSize) { - // Otherwise, use assignment for the already-constructed elements. - std::move(RHS.begin(), RHS.begin() + CurSize, this->begin()); - } - - // Move-construct the new elements in place. - this->uninitialized_move(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize); - - // Set end. - this->set_size(RHSSize); - - RHS.clear(); - return *this; -} - -/// Storage for the SmallVector elements. This is specialized for the N=0 case -/// to avoid allocating unnecessary storage. -template -struct SmallVectorStorage { - alignas(T) char InlineElts[N * sizeof(T)]; -}; - -/// We need the storage to be properly aligned even for small-size of 0 so that -/// the pointer math in \a SmallVectorTemplateCommon::getFirstEl() is -/// well-defined. -template -struct alignas(T) SmallVectorStorage {}; - -/// Forward declaration of SmallVector so that -/// calculateSmallVectorDefaultInlinedElements can reference -/// `sizeof(SmallVector)`. -template -class SmallVector; - -/// Helper class for calculating the default number of inline elements for -/// `SmallVector`. -/// -/// This should be migrated to a constexpr function when our minimum -/// compiler support is enough for multi-statement constexpr functions. -template -struct CalculateSmallVectorDefaultInlinedElements { - // Parameter controlling the default number of inlined elements - // for `SmallVector`. - // - // The default number of inlined elements ensures that - // 1. There is at least one inlined element. - // 2. `sizeof(SmallVector) <= kPreferredSmallVectorSizeof` unless - // it contradicts 1. - static constexpr size_t kPreferredSmallVectorSizeof = 64; - - // static_assert that sizeof(T) is not "too big". - // - // Because our policy guarantees at least one inlined element, it is possible - // for an arbitrarily large inlined element to allocate an arbitrarily large - // amount of inline storage. We generally consider it an antipattern for a - // SmallVector to allocate an excessive amount of inline storage, so we want - // to call attention to these cases and make sure that users are making an - // intentional decision if they request a lot of inline storage. - // - // We want this assertion to trigger in pathological cases, but otherwise - // not be too easy to hit. To accomplish that, the cutoff is actually somewhat - // larger than kPreferredSmallVectorSizeof (otherwise, - // `SmallVector>` would be one easy way to trip it, and that - // pattern seems useful in practice). - // - // One wrinkle is that this assertion is in theory non-portable, since - // sizeof(T) is in general platform-dependent. However, we don't expect this - // to be much of an issue, because most LLVM development happens on 64-bit - // hosts, and therefore sizeof(T) is expected to *decrease* when compiled for - // 32-bit hosts, dodging the issue. The reverse situation, where development - // happens on a 32-bit host and then fails due to sizeof(T) *increasing* on a - // 64-bit host, is expected to be very rare. - static_assert( - sizeof(T) <= 256, - "You are trying to use a default number of inlined elements for " - "`SmallVector` but `sizeof(T)` is really big! Please use an " - "explicit number of inlined elements with `SmallVector` to make " - "sure you really want that much inline storage."); - - // Discount the size of the header itself when calculating the maximum inline - // bytes. - static constexpr size_t PreferredInlineBytes = - kPreferredSmallVectorSizeof - sizeof(SmallVector); - static constexpr size_t NumElementsThatFit = PreferredInlineBytes / sizeof(T); - static constexpr size_t value = - NumElementsThatFit == 0 ? 1 : NumElementsThatFit; -}; - -/// This is a 'vector' (really, a variable-sized array), optimized -/// for the case when the array is small. It contains some number of elements -/// in-place, which allows it to avoid heap allocation when the actual number of -/// elements is below that threshold. This allows normal "small" cases to be -/// fast without losing generality for large inputs. -/// -/// \note -/// In the absence of a well-motivated choice for the number of inlined -/// elements \p N, it is recommended to use \c SmallVector (that is, -/// omitting the \p N). This will choose a default number of inlined elements -/// reasonable for allocation on the stack (for example, trying to keep \c -/// sizeof(SmallVector) around 64 bytes). -/// -/// \warning This does not attempt to be exception safe. -/// -/// \see https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h -template ::value> -class SmallVector : public SmallVectorImpl, - SmallVectorStorage { -public: - SmallVector() - : SmallVectorImpl(N) {} - - ~SmallVector() { - // Destroy the constructed elements in the vector. - this->destroy_range(this->begin(), this->end()); - } - - explicit SmallVector(size_t Size, const T& Value = T()) - : SmallVectorImpl(N) { - this->assign(Size, Value); - } - - template ::iterator_category, - std::input_iterator_tag>::value>> - SmallVector(ItTy S, ItTy E) - : SmallVectorImpl(N) { - this->append(S, E); - } - - template - explicit SmallVector(const iterator_range& R) - : SmallVectorImpl(N) { - this->append(R.begin(), R.end()); - } - - SmallVector(std::initializer_list IL) - : SmallVectorImpl(N) { - this->assign(IL); - } - - SmallVector(const SmallVector& RHS) - : SmallVectorImpl(N) { - if (!RHS.empty()) - SmallVectorImpl::operator=(RHS); - } - - SmallVector& operator=(const SmallVector& RHS) { - SmallVectorImpl::operator=(RHS); - return *this; - } - - SmallVector(SmallVector&& RHS) - : SmallVectorImpl(N) { - if (!RHS.empty()) - SmallVectorImpl::operator=(::std::move(RHS)); - } - - SmallVector(SmallVectorImpl&& RHS) - : SmallVectorImpl(N) { - if (!RHS.empty()) - SmallVectorImpl::operator=(::std::move(RHS)); - } - - SmallVector& operator=(SmallVector&& RHS) { - if (N) { - SmallVectorImpl::operator=(::std::move(RHS)); - return *this; - } - // SmallVectorImpl::operator= does not leverage N==0. Optimize the - // case. - if (this == &RHS) - return *this; - if (RHS.empty()) { - this->destroy_range(this->begin(), this->end()); - this->Size = 0; - } else { - this->assignRemote(std::move(RHS)); - } - return *this; - } - - SmallVector& operator=(SmallVectorImpl&& RHS) { - SmallVectorImpl::operator=(::std::move(RHS)); - return *this; - } - - SmallVector& operator=(std::initializer_list IL) { - this->assign(IL); - return *this; - } -}; - -template -inline size_t capacity_in_bytes(const SmallVector& X) { - return X.capacity_in_bytes(); -} - -template -using ValueTypeFromRangeType = - typename std::remove_const()))>::type>::type; - -/// Given a range of type R, iterate the entire range and return a -/// SmallVector with elements of the vector. This is useful, for example, -/// when you want to iterate a range and then sort the results. -template -SmallVector, Size> to_vector(R&& Range) { - return { std::begin(Range), std::end(Range) }; -} -template -SmallVector, - CalculateSmallVectorDefaultInlinedElements< - ValueTypeFromRangeType>::value> -to_vector(R&& Range) { - return { std::begin(Range), std::end(Range) }; -} - -namespace std { - -/// Implement std::swap in terms of SmallVector swap. -template -inline void swap(SmallVectorImpl& LHS, SmallVectorImpl& RHS) { - LHS.swap(RHS); -} - -/// Implement std::swap in terms of SmallVector swap. -template -inline void swap(SmallVector& LHS, SmallVector& RHS) { - LHS.swap(RHS); -} - -} // namespace std - -#ifdef _MSC_VER -# pragma warning(pop) -#endif diff --git a/source/10-common/StbImplementations.c b/source/10-common/StbImplementations.c deleted file mode 100644 index 73bbc2a..0000000 --- a/source/10-common/StbImplementations.c +++ /dev/null @@ -1,14 +0,0 @@ -#define STB_RECT_PACK_IMPLEMENTATION -#include - -#define STB_TRUETYPE_IMPLEMENTATION -#include - -#define STB_IMAGE_IMPLEMENTATION -#include - -#define STB_SPRINTF_IMPLEMENTATION -#include - -#define STB_C_LEXER_IMPLEMENTATION -#include diff --git a/source/10-common/Type2ObjectMap.hpp b/source/10-common/Type2ObjectMap.hpp deleted file mode 100644 index 24c45f3..0000000 --- a/source/10-common/Type2ObjectMap.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "TypeTraits.hpp" - -#include - -template -class Type2ObjectMap { -public: - template - TType& Insert(TType&& value) { - // TODO - } - - template - TType& InsertOrAssign(TType& value) { - // TODO - } - - template - TType Remove() { - // TODO - } - - template - const TValue* Find() const { - // TODO - } - - template - TValue* Find() { - return const_cast(const_cast(this)->Find()); - } - - size_t size() const { - // TODO - } -}; diff --git a/source/10-common/TypeTraits.hpp b/source/10-common/TypeTraits.hpp deleted file mode 100644 index 73a56f9..0000000 --- a/source/10-common/TypeTraits.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -/// This template will be instanciated for each unique type, and the char variable will be ODR-used which gives it an unique address. -template -struct TypeIdentifier { - static const char obj = 0; -}; - -template -struct DefaultDeleter { - void operator()(T* ptr) const { - delete ptr; - } -}; - -template -struct RemoveMemberPtrImpl {}; - -template -struct RemoveMemberPtrImpl { - using Type = U; -}; - -template -using RemoveMemberPtr = typename RemoveMemberPtrImpl::Type; diff --git a/source/10-common/Uid.cpp b/source/10-common/Uid.cpp deleted file mode 100644 index 1930cd8..0000000 --- a/source/10-common/Uid.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "Uid.hpp" - -#include "RapidJsonHelper.hpp" - -#include -#include -#include - -Uid Uid::Create() { - std::random_device rd; - std::mt19937_64 gen(rd()); - std::uniform_int_distribution dist( - std::numeric_limits::min(), - std::numeric_limits::max()); - - Uid uid; - uid.upper = dist(gen); - uid.lower = dist(gen); - return uid; -} - -bool Uid::IsNull() const { - return upper == 0 && lower == 0; -} - -void Uid::ReadString(std::string_view str) { - sscanf(str.data(), BRUSSEL_Uid_SCAN_STR, &upper, &lower); -} - -std::string Uid::WriteString() { - char buf[256]; - snprintf(buf, sizeof(buf), BRUSSEL_Uid_FORMAT_STR, upper, lower); - return std::string(buf); -} - -void Uid::Read(const rapidjson::Value& value) { - assert(value.IsArray()); - assert(value.Size() == 2); - auto& upper = value[0]; - assert(upper.IsUint64()); - auto& lower = value[1]; - assert(lower.IsUint64()); - - this->upper = upper.GetUint64(); - this->lower = lower.GetUint64(); -} - -void Uid::WriteInto(rapidjson::Value& value, rapidjson::Document& root) { - value.Reserve(2, root.GetAllocator()); - value.PushBack((uint64_t)upper, root.GetAllocator()); - value.PushBack((uint64_t)lower, root.GetAllocator()); -} - -rapidjson::Value Uid::Write(rapidjson::Document& root) { - rapidjson::Value result(rapidjson::kArrayType); - WriteInto(result, root); - return result; -} diff --git a/source/10-common/Uid.hpp b/source/10-common/Uid.hpp deleted file mode 100644 index f58129c..0000000 --- a/source/10-common/Uid.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "Utils.hpp" - -#include -#include -#include -#include -#include - -#define BRUSSEL_Uid_SCAN_STR "%" PRIx64 "-%" PRIx64 -#define BRUSSEL_Uid_SCAN_EXPAND(uid) &((uid).upper), &((uid).upper) -#define BRUSSEL_Uid_FORMAT_STR "%016" PRIx64 "-%016" PRIx64 -#define BRUSSEL_Uid_FORMAT_EXPAND(uid) (uid).upper, (uid).lower - -struct Uid { - uint64_t upper = 0; - uint64_t lower = 0; - - static Uid Create(); - - bool IsNull() const; - - void ReadString(std::string_view str); - std::string WriteString(); - - void Read(const rapidjson::Value& value); - void WriteInto(rapidjson::Value& value, rapidjson::Document& root); - rapidjson::Value Write(rapidjson::Document& root); - - auto operator<=>(const Uid&) const = default; -}; - -template <> -struct std::hash { - size_t operator()(const Uid& uid) const { - size_t hash = 0; - Utils::HashCombine(hash, uid.upper); - Utils::HashCombine(hash, uid.lower); - return hash; - } -}; diff --git a/source/10-common/Utils.cpp b/source/10-common/Utils.cpp deleted file mode 100644 index dc76b0a..0000000 --- a/source/10-common/Utils.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "Utils.hpp" - -#include "Macros.hpp" -#include "ScopeGuard.hpp" - -#ifdef _WIN32 -# include -#endif - -namespace fs = std::filesystem; - -#ifdef _WIN32 -# define BRUSSEL_MODE_STRING(string) L##string -#else -# define BRUSSEL_MODE_STRING(string) string -#endif - -#if _WIN32 -using FopenModeString = const wchar_t*; -#else -using FopenModeString = const char*; -#endif - -static FopenModeString GetModeString(Utils::IoMode mode, bool binary) { - using namespace Utils; - if (binary) { - switch (mode) { - case Read: return BRUSSEL_MODE_STRING("rb"); - case WriteTruncate: return BRUSSEL_MODE_STRING("wb"); - case WriteAppend: return BRUSSEL_MODE_STRING("ab"); - } - } else { - switch (mode) { - case Read: return BRUSSEL_MODE_STRING("r"); - case WriteTruncate: return BRUSSEL_MODE_STRING("w"); - case WriteAppend: return BRUSSEL_MODE_STRING("a"); - } - } - return nullptr; -} - -FILE* Utils::OpenCstdioFile(const fs::path& path, IoMode mode, bool binary) { -#ifdef _WIN32 - // fs::path::c_str() returns `const wchar_t*` under Windows, because NT uses UTF-16 natively - // NOTE: _wfopen() only affects the type of path parameter, otherwise the file stream created is identical to the one by fopen() - return _wfopen(path.c_str(), ::GetModeString(mode, binary)); -#else - return fopen(path.c_str(), ::GetModeString(mode, binary)); -#endif -} - -FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) { -#ifdef _WIN32 - // On Windows, fopen() accepts ANSI codepage encoded path, convert our UTF-8 string to UTF-16 to ensure that no matter what the locale is, the path continues to work - WCHAR platformPath[MAX_PATH]; - if (MultiByteToWideChar(CP_UTF8, 0, path, -1, platformPath, MAX_PATH) == 0) { - return nullptr; - } - return _wfopen(platformPath, ::GetModeString(mode, binary)); -#else - return fopen(path, ::GetModeString(mode, binary)); -#endif -} - -std::string Utils::ReadFileAsString(const fs::path& path) { - auto file = Utils::OpenCstdioFile(path, Utils::Read); - if (!file) throw std::runtime_error("Failed to open source file."); - DEFER { fclose(file); }; - - fseek(file, 0, SEEK_END); - auto fileSize = ftell(file); - rewind(file); - - std::string result(fileSize, '\0'); - fread(result.data(), fileSize, 1, file); - - return result; -} - -bool Utils::InRangeInclusive(int n, int lower, int upper) { - if (lower > upper) { - std::swap(lower, upper); - } - return n >= lower && n <= upper; -} - -bool Utils::LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate) { - bool verticalLine = p1.x == p2.x && InRangeInclusive(candidate.x, p1.x, p2.x); - bool horizontalLine = p1.y == p2.y && InRangeInclusive(candidate.y, p1.y, p2.y); - return verticalLine && horizontalLine; -} - -bool Utils::IsColinear(glm::ivec2 p1, glm::ivec2 p2) { - return p1.x == p2.x || p1.y == p2.y; -} - -std::string Utils::MakeRandomNumberedName(const char* tag) { - int n = std::rand(); -#define RNG_NAME_PATTERN "Unnamed %s #%d", tag, n - // NOTE: does not include null-terminator - int size = snprintf(nullptr, 0, RNG_NAME_PATTERN); - std::string result; - result.resize(size); // std::string::resize handles storage for null-terminator alreaedy - snprintf(result.data(), size, RNG_NAME_PATTERN); -#undef RNG_NAME_PATTERN - return result; -} diff --git a/source/10-common/Utils.hpp b/source/10-common/Utils.hpp deleted file mode 100644 index 9560b57..0000000 --- a/source/10-common/Utils.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace Utils { - -enum IoMode { - Read, - WriteTruncate, - WriteAppend, -}; - -FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary = false); -FILE* OpenCstdioFile(const char* path, IoMode mode, bool binary = false); - -std::string ReadFileAsString(const std::filesystem::path& path); - -constexpr float Abs(float v) noexcept { - return v < 0.0f ? -v : v; -} - -bool InRangeInclusive(int n, int lower, int upper); -bool LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate); - -bool IsColinear(glm::ivec2 p1, glm::ivec2 p2); - -template -void HashCombine(std::size_t& seed, const T& v) { - seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); -} - -std::string MakeRandomNumberedName(const char* tag); - -} // namespace Utils - -struct StringHash { - using is_transparent = void; - - std::size_t operator()(const std::string& key) const { return robin_hood::hash_bytes(key.c_str(), key.size()); } - std::size_t operator()(std::string_view key) const { return robin_hood::hash_bytes(key.data(), key.size()); } - std::size_t operator()(const char* key) const { return robin_hood::hash_bytes(key, std::strlen(key)); } -}; - -struct StringEqual { - using is_transparent = int; - - bool operator()(std::string_view lhs, const std::string& rhs) const { - const std::string_view view = rhs; - return lhs == view; - } - - bool operator()(const char* lhs, const std::string& rhs) const { - return std::strcmp(lhs, rhs.c_str()) == 0; - } - - bool operator()(const std::string& lhs, const std::string& rhs) const { - return lhs == rhs; - } -}; diff --git a/source/10-common/YCombinator.hpp b/source/10-common/YCombinator.hpp deleted file mode 100644 index b1d2350..0000000 --- a/source/10-common/YCombinator.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -template -struct YCombinator { - // NOTE: implicit constructor allows initializing this - Func func; - - template - decltype(auto) operator()(Ts&&... args) const { - // NOTE: static_cast(args)... is equivalent to std::forward(args)... - // written this way so that we don't have to include , as well as reducing template instanciations to help compile time - return func(*this, static_cast(args)...); - } -}; diff --git a/source/10-common/buildfile b/source/10-common/buildfile deleted file mode 100644 index 8d1c8b6..0000000 --- a/source/10-common/buildfile +++ /dev/null @@ -1 +0,0 @@ - diff --git a/source/20-codegen-compiler/CodegenConfig.hpp b/source/20-codegen-compiler/CodegenConfig.hpp deleted file mode 100644 index b9dc56c..0000000 --- a/source/20-codegen-compiler/CodegenConfig.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#ifndef CODEGEN_DEBUG_PRINT -# define CODEGEN_DEBUG_PRINT 0 -#endif - -#if CODEGEN_DEBUG_PRINT -# define DEBUG_PRINTF(...) printf(__VA_ARGS__) -#else -# define DEBUG_PRINTF(...) -#endif diff --git a/source/20-codegen-compiler/CodegenDecl.cpp b/source/20-codegen-compiler/CodegenDecl.cpp deleted file mode 100644 index 7cf21ce..0000000 --- a/source/20-codegen-compiler/CodegenDecl.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "CodegenDecl.hpp" - -#include - -static EnumValuePattern NextPattern(EnumValuePattern val) { - return (EnumValuePattern)(val + 1); -} - -EnumValuePattern DeclEnum::CalcPattern() const { - if (elements.empty()) return EVP_Continuous; - - auto pattern = EVP_Continuous; -restart: - auto lastVal = elements[0].value; - for (size_t i = 1; i < elements.size(); ++i) { - auto currVal = elements[i].value; - switch (pattern) { - case EVP_Continuous: { - bool satisfy = lastVal + 1 == currVal; - if (!satisfy) { - pattern = NextPattern(pattern); - goto restart; - } - } break; - - case EVP_Bits: { - bool satisfy = (lastVal << 1) == currVal; - if (!satisfy) { - pattern = NextPattern(pattern); - goto restart; - } - } break; - - // A random pattern can match anything - case EVP_Random: - case EVP_COUNT: break; - } - lastVal = currVal; - } - - return pattern; -} - -EnumValuePattern DeclEnum::GetPattern() const { - if (pattern == EVP_COUNT) { - pattern = CalcPattern(); - } - return pattern; -} diff --git a/source/20-codegen-compiler/CodegenDecl.hpp b/source/20-codegen-compiler/CodegenDecl.hpp deleted file mode 100644 index 0728c08..0000000 --- a/source/20-codegen-compiler/CodegenDecl.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include -#include - -// TODO replace std::string name with std::string_view into the token storage? - -struct DeclNamespace { - DeclNamespace* container = nullptr; - std::string name; - std::string_view fullname; // View into storage map key -}; - -struct DeclStruct; -struct DeclMemberVariable { - DeclStruct* containerStruct = nullptr; - std::string name; - std::string type; - std::string getterName; - std::string setterName; -}; -struct DeclMemberFunction { - DeclStruct* containerStruct = nullptr; - // TODO -}; - -// Structs or classes -struct DeclStruct { - DeclNamespace* container = nullptr; - std::vector baseClasses; - std::vector memberVariables; - std::vector generatedVariables; - std::vector memberFunctions; - std::vector generatedFunctions; - std::string name; - std::string_view fullname; - - // Scanned generation options - bool generating : 1 = false; - bool generatingInheritanceHiearchy : 1 = false; -}; - -enum EnumUnderlyingType { - EUT_Int8, - EUT_Int16, - EUT_Int32, - EUT_Int64, - EUT_Uint8, - EUT_Uint16, - EUT_Uint32, - EUT_Uint64, - EUT_COUNT, -}; - -enum EnumValuePattern { - // The numbers cover n..m with no gaps - EVP_Continuous, - // The numbers cover for i in n..m, 1 << i - // e.g. [0] = 1 << 0, - // [1] = 1 << 1. - // [2] = 1 << 2. etc. - EVP_Bits, - // The numbesr don't have a particular pattern - EVP_Random, - EVP_COUNT, -}; - -struct DeclEnumElement { - std::string name; - // TODO support int64_t, etc. enum underlying types - uint64_t value; -}; - -struct DeclEnum { - DeclNamespace* container = nullptr; - std::string name; - std::string_view fullname; - std::vector elements; - EnumUnderlyingType underlyingType; - // Start with invalid value, calculate on demand - mutable EnumValuePattern pattern = EVP_COUNT; - - EnumValuePattern CalcPattern() const; - EnumValuePattern GetPattern() const; -}; - -struct DeclFunctionArgument { - std::string type; - std::string name; -}; - -struct DeclFunction { - DeclNamespace* container = nullptr; - // Things like extern, static, etc. that gets written before the function return type - std::string prefix; - std::string name; - std::string_view fullname; - std::string returnType; - std::vector arguments; - std::string body; -}; diff --git a/source/20-codegen-compiler/CodegenInput.cpp b/source/20-codegen-compiler/CodegenInput.cpp deleted file mode 100644 index 0dced0e..0000000 --- a/source/20-codegen-compiler/CodegenInput.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "CodegenInput.hpp" - -#include -#include - -#include -#include - -struct SomeDecl { - std::variant v; -}; - -class CodegenInput::Private { -public: - // We want address stability for everything - robin_hood::unordered_node_map decls; - robin_hood::unordered_node_map namespaces; -}; - -CodegenInput::CodegenInput() - : m{ new Private() } // -{ -} - -CodegenInput::~CodegenInput() { - delete m; -} - -#define STORE_DECL_OF_TYPE(DeclType, fullname, decl) \ - auto [iter, success] = m->decls.try_emplace(std::move(fullname), SomeDecl{ .v = std::move(decl) }); \ - auto& key = iter->first; \ - auto& val = iter->second; \ - auto& declRef = std::get(val.v); \ - declRef.fullname = key; \ - return &declRef - -DeclEnum* CodegenInput::AddEnum(std::string fullname, DeclEnum decl) { -#if CODEGEN_DEBUG_PRINT - printf("Committed enum '%s'\n", decl.name.c_str()); - for (auto& elm : decl.elements) { - printf(" - element %s = %" PRId64 "\n", elm.name.c_str(), elm.value); - } -#endif - - STORE_DECL_OF_TYPE(DeclEnum, fullname, decl); -} - -DeclStruct* CodegenInput::AddStruct(std::string fullname, DeclStruct decl) { -#if CODEGEN_DEBUG_PRINT - printf("Committed struct '%s'\n", decl.name.c_str()); - printf(" Base classes:\n"); - for (auto& base : decl.baseClasses) { - printf(" - %.*s\n", PRINTF_STRING_VIEW(base->name)); - } -#endif - - STORE_DECL_OF_TYPE(DeclStruct, fullname, decl); -} - -#define FIND_DECL_OF_TYPE(DeclType) \ - auto iter = m->decls.find(name); \ - if (iter != m->decls.end()) { \ - auto& some = iter->second.v; \ - if (auto decl = std::get_if(&some)) { \ - return decl; \ - } \ - } \ - return nullptr - -const DeclEnum* CodegenInput::FindEnum(std::string_view name) const { - FIND_DECL_OF_TYPE(DeclEnum); -} - -const DeclStruct* CodegenInput::FindStruct(std::string_view name) const { - FIND_DECL_OF_TYPE(DeclStruct); -} - -DeclNamespace* CodegenInput::AddNamespace(DeclNamespace ns) { - auto path = Utils::MakeFullName(""sv, &ns); - auto [iter, success] = m->namespaces.try_emplace(std::move(path), std::move(ns)); - auto& nsRef = iter->second; - if (success) { - nsRef.fullname = iter->first; - } - return &nsRef; -} - -const DeclNamespace* CodegenInput::FindNamespace(std::string_view fullname) const { - auto iter = m->namespaces.find(fullname); - if (iter != m->namespaces.end()) { - return &iter->second; - } else { - return nullptr; - } -} - -DeclNamespace* CodegenInput::FindNamespace(std::string_view name) { - return const_cast(const_cast(this)->FindNamespace(name)); -} diff --git a/source/20-codegen-compiler/CodegenInput.hpp b/source/20-codegen-compiler/CodegenInput.hpp deleted file mode 100644 index 63c2673..0000000 --- a/source/20-codegen-compiler/CodegenInput.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "CodegenConfig.hpp" -#include "CodegenDecl.hpp" -#include "CodegenUtils.hpp" - -#include -#include -#include - -using namespace std::literals; - -class CodegenInput { -private: - class Private; - Private* m; - -public: - CodegenInput(); - ~CodegenInput(); - - DeclEnum* AddEnum(std::string fullname, DeclEnum decl); - DeclStruct* AddStruct(std::string fullname, DeclStruct decl); - - const DeclEnum* FindEnum(std::string_view name) const; - const DeclStruct* FindStruct(std::string_view name) const; - - DeclNamespace* AddNamespace(DeclNamespace ns); - - const DeclNamespace* FindNamespace(std::string_view fullname) const; - DeclNamespace* FindNamespace(std::string_view name); -}; diff --git a/source/20-codegen-compiler/CodegenLexer.cpp b/source/20-codegen-compiler/CodegenLexer.cpp deleted file mode 100644 index dab6aea..0000000 --- a/source/20-codegen-compiler/CodegenLexer.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "CodegenLexer.hpp" - -#include - -bool StbTokenIsSingleChar(int lexerToken) { - return lexerToken >= 0 && lexerToken < 256; -} - -bool StbTokenIsMultiChar(int lexerToken) { - return !StbTokenIsMultiChar(lexerToken); -} - -std::string CombineTokens(std::span tokens) { - size_t length = 0; - for (auto& token : tokens) { - length += token.text.size(); - } - std::string result; - result.reserve(length); - for (auto& token : tokens) { - result += token.text; - } - return result; -} - -const StbLexerToken& CodegenLexer::Current() const { - assert(idx < tokens.size()); - return tokens[idx]; -} - -void CodegenLexer::InitializeFrom(std::string_view source) { - this->tokens = {}; - this->idx = 0; - - stb_lexer lexer; - char stringStorage[65536]; - const char* srcBegin = source.data(); - const char* srcEnd = srcBegin + source.length(); - stb_c_lexer_init(&lexer, srcBegin, srcEnd, stringStorage, sizeof(stringStorage)); - - struct TokenCombiningPattern { - StbLexerToken result; - char matchChars[16]; - }; - - const TokenCombiningPattern kDoubleColon = { - .result = { - .text = "::", - .type = CLEX_ext_double_colon, - }, - .matchChars = { ':', ':', '\0' }, - }; - const TokenCombiningPattern kDotDotDot = { - .result = { - .text = "...", - .type = CLEX_ext_dot_dot_dot, - }, - .matchChars = { '.', '.', '.', '\0' }, - }; - - const TokenCombiningPattern* currentState = nullptr; - int currentStateCharIdx = 0; - - while (true) { - // See stb_c_lexer.h's comments, here are a few additinos that aren't made clear in the file: - // - `lexer->token` (noted as "token" below) after calling stb_c_lexer_get_token() contains either: - // 1. 0 <= token < 256: an ASCII character (more precisely a single char that the lexer ate; technically can be an incomplete code unit) - // 2. token < 0: an unknown token - // 3. One of the `CLEX_*` enums: a special, recognized token such as an operator - - int stbToken = stb_c_lexer_get_token(&lexer); - if (stbToken == 0) { - // EOF - break; - } - - if (lexer.token == CLEX_parse_error) { - printf("[ERROR] stb_c_lexer countered a parse error.\n"); - // TODO how to handle? - continue; - } - - StbLexerToken token; - if (StbTokenIsSingleChar(lexer.token)) { - char c = lexer.token; - - token.type = CLEX_ext_single_char; - token.text = std::string(1, c); - - if (!currentState) { -#define TRY_START_MATCH(states) \ - if (states.matchChars[0] == c) { \ - currentState = &states; \ - currentStateCharIdx = 1; \ - } - TRY_START_MATCH(kDoubleColon); - TRY_START_MATCH(kDotDotDot); -#undef TRY_START_MATCH - } else { - if (currentState->matchChars[currentStateCharIdx] == c) { - // Match success - ++currentStateCharIdx; - - // If we matched all of the chars... - if (currentState->matchChars[currentStateCharIdx] == '\0') { - // We matched (currentStateCharIdx) tokens though this one is pushed into the vector, leaving (currentStateCharIdx - 1) tokens to be removed - for (int i = 0, count = currentStateCharIdx - 1; i < count; ++i) { - tokens.pop_back(); - } - - // Set the current token to desired result - token = currentState->result; - - currentState = nullptr; - currentStateCharIdx = 0; - } - } else { - // Match fail, reset - - currentState = nullptr; - currentStateCharIdx = 0; - } - } - } else { - token.type = lexer.token; - // WORKAROUND: use null terminated string, stb_c_lexer doens't set string_len properly when parsing identifiers - token.text = std::string(lexer.string); - - switch (token.type) { - case CLEX_intlit: - token.lexerIntNumber = lexer.int_number; - break; - - case CLEX_floatlit: - token.lexerRealNumber = lexer.real_number; - break; - } - } - tokens.push_back(std::move(token)); - token = {}; - } -} - -const StbLexerToken* CodegenLexer::TryConsumeToken(int type) { - auto& token = tokens[idx]; - if (token.type == type) { - ++idx; - return &token; - } - return nullptr; -} - -const StbLexerToken* CodegenLexer::TryConsumeSingleCharToken(char c) { - auto& token = tokens[idx]; - if (token.type == CLEX_ext_single_char && - token.text[0] == c) - { - ++idx; - return &token; - } - return nullptr; -} - -void CodegenLexer::SkipUntilToken(int type) { - while (idx < tokens.size()) { - if (Current().type == type) { - break; - } - ++idx; - } -} - -void CodegenLexer::SkipUntilTokenSingleChar(char c) { - while (idx < tokens.size()) { - auto& curr = Current(); - if (curr.type == CLEX_ext_single_char && - curr.text[0] == c) - { - break; - } - ++idx; - } -} diff --git a/source/20-codegen-compiler/CodegenLexer.hpp b/source/20-codegen-compiler/CodegenLexer.hpp deleted file mode 100644 index 76adce6..0000000 --- a/source/20-codegen-compiler/CodegenLexer.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -enum { - CLEX_ext_single_char = CLEX_first_unused_token, - CLEX_ext_double_colon, - CLEX_ext_dot_dot_dot, - CLEX_ext_COUNT, -}; - -struct StbLexerToken { - std::string text; - - union { - double lexerRealNumber; - long lexerIntNumber; - }; - - // Can either be CLEX_* or CLEX_ext_* values - int type; -}; - -bool StbTokenIsSingleChar(int lexerToken); -bool StbTokenIsMultiChar(int lexerToken); -std::string CombineTokens(std::span tokens); - -struct CodegenLexer { - std::vector tokens; - size_t idx = 0; - - void InitializeFrom(std::string_view source); - - const StbLexerToken& Current() const; - - const StbLexerToken* TryConsumeToken(int type); - const StbLexerToken* TryConsumeSingleCharToken(char c); - - void SkipUntilToken(int type); - void SkipUntilTokenSingleChar(char c); -}; diff --git a/source/20-codegen-compiler/CodegenOutput.cpp b/source/20-codegen-compiler/CodegenOutput.cpp deleted file mode 100644 index ccd163c..0000000 --- a/source/20-codegen-compiler/CodegenOutput.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "CodegenOutput.hpp" - -#include "CodegenUtils.hpp" - -void CodegenOutput::AddRequestInclude(std::string_view include) { - if (!mRequestIncludes.contains(include)) { - mRequestIncludes.insert(std::string(include)); - } -} - -void CodegenOutput::AddOutputThing(CodegenOutputThing thing) { - mOutThings.push_back(std::move(thing)); -} - -void CodegenOutput::MergeContents(CodegenOutput other) { - std::move(other.mOutThings.begin(), other.mOutThings.end(), std::back_inserter(this->mOutThings)); - std::move(other.mOutStructs.begin(), other.mOutStructs.end(), std::back_inserter(this->mOutStructs)); - std::move(other.mOutEnums.begin(), other.mOutEnums.end(), std::back_inserter(this->mOutEnums)); - std::move(other.mOutFunctions.begin(), other.mOutFunctions.end(), std::back_inserter(this->mOutFunctions)); -} - -void CodegenOutput::Write(FILE* file) const { - for (auto& include : mRequestIncludes) { - // TODO how to resolve to the correct include paths? - WRITE_FMT_LN(file, "#include <%s>", include.c_str()); - } - - for (auto& thing : mOutThings) { - fwrite(thing.text.c_str(), sizeof(char), thing.text.size(), file); - WRITE_LIT(file, "\n"); - } - - for (auto& declStruct : mOutStructs) { - WRITE_FMT_LN(file, "struct %s {", declStruct.name.c_str()); - // TODO - WRITE_LIT_LN(file, "};"); - } - - for (auto& declEnum : mOutEnums) { - // TODO - } - - for (auto& declFunc : mOutFunctions) { - // TODO - } -} diff --git a/source/20-codegen-compiler/CodegenOutput.hpp b/source/20-codegen-compiler/CodegenOutput.hpp deleted file mode 100644 index aa28715..0000000 --- a/source/20-codegen-compiler/CodegenOutput.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "CodegenDecl.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -// A generic "thing" (could be anything, comments, string-concated functionsm, etc.) to spit into the output file -struct CodegenOutputThing { - std::string text; -}; - -class CodegenOutput { -private: - robin_hood::unordered_set mRequestIncludes; - std::vector mOutThings; - std::vector mOutStructs; - std::vector mOutEnums; - std::vector mOutFunctions; - -public: - std::string optionOutPrefix; - // Whether to add prefixes mOutPrefix to all global names or not - bool optionAutoAddPrefix : 1 = false; - -public: - void AddRequestInclude(std::string_view include); - void AddOutputThing(CodegenOutputThing thing); - - void MergeContents(CodegenOutput other); - - void Write(FILE* file) const; -}; diff --git a/source/20-codegen-compiler/CodegenUtils.cpp b/source/20-codegen-compiler/CodegenUtils.cpp deleted file mode 100644 index 0c70cb6..0000000 --- a/source/20-codegen-compiler/CodegenUtils.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "CodegenUtils.hpp" - -#include -#include -#include - -#include -#include - -bool Utils::WriteOutputFile(const CodegenOutput& output, const char* path) { - auto outputFile = Utils::OpenCstdioFile(path, Utils::WriteTruncate); - if (!outputFile) { - printf("[ERROR] unable to open output file %s\n", path); - return false; - } - DEFER { fclose(outputFile); }; - - DEBUG_PRINTF("Writing output %s\n", path); - output.Write(outputFile); - - return true; -} - -std::string Utils::MakeFullName(std::string_view name, DeclNamespace* ns) { - size_t length = 0; - std::vector components; - if (!name.empty()) { - components.push_back(name); - length += name.length(); - } - - DeclNamespace* currentNamespace = ns; - while (currentNamespace) { - components.push_back(currentNamespace->name); - length += currentNamespace->name.size() + /*::*/ 2; - currentNamespace = currentNamespace->container; - } - - std::string fullname; - fullname.reserve(length); - for (auto it = components.rbegin(); it != components.rend(); ++it) { - fullname += *it; - fullname += "::"; - } - // Get rid of the last "::" - fullname.pop_back(); - fullname.pop_back(); - - return fullname; -} - -// NOTE: assuming we are only dealing with ASCII characters -static bool IsLowerCase(char c) { - return c >= 'a' && c <= 'z'; -} -static bool IsUpperCase(char c) { - return c >= 'A' && c <= 'Z'; -} -static bool IsAlphabetic(char c) { - return IsLowerCase(c) || IsUpperCase(c); -} -static char MakeUpperCase(char c) { - if (IsAlphabetic(c)) { - return IsUpperCase(c) - ? c - : ('A' + (c - 'a')); - } - return c; -} - -std::vector Utils::SplitIdentifier(std::string_view name) { - // TODO handle SCREAMING_CASE - - size_t chunkStart = 0; - size_t chunkEnd = 0; - std::vector result; - auto PushChunk = [&]() { result.push_back(std::string_view(name.begin() + chunkStart, name.begin() + chunkEnd)); }; - while (chunkEnd < name.size()) { - char c = name[chunkEnd]; - if (IsUpperCase(c)) { - // Start of next chunk, using camelCase or PascalCase - PushChunk(); - chunkStart = chunkEnd; - chunkEnd = chunkStart + 1; - continue; - } else if (c == '_') { - // End of this chunk, using snake_case - PushChunk(); - chunkStart = chunkEnd + 1; - chunkEnd = chunkStart + 1; - continue; - } else if (c == '-') { - // End of this chunk, using kebab-case - PushChunk(); - chunkStart = chunkEnd + 1; - chunkEnd = chunkStart + 1; - continue; - } - ++chunkEnd; - } - - if ((chunkEnd - chunkStart) >= 1) { - PushChunk(); - } - - return result; -} - -std::string Utils::MakePascalCase(std::string_view name) { - std::string result; - for (auto part : SplitIdentifier(name)) { - result += MakeUpperCase(part[0]); - result += part.substr(1); - } - return result; -} - -void Utils::ProduceGeneratedHeader(const char* headerFilename, CodegenOutput& header, const char* sourceFilename, CodegenOutput& source) { - CodegenOutputThing headerOut; - headerOut.text += &R"""( -// This file is generated. Any changes will be overidden when building. -#pragma once -#include -#include -#include -)"""[1]; - - CodegenOutputThing sourceOut; - APPEND_LIT_LN(sourceOut.text, "// This file is generated. Any changes will be overidden when building."); - APPEND_FMT_LN(sourceOut.text, "#include \"%s\"", headerFilename); - sourceOut.text += &R"""( -#include -#include -#include -using namespace std::literals; -)"""[1]; - - header.AddOutputThing(std::move(headerOut)); - source.AddOutputThing(std::move(sourceOut)); -} - -void Utils::ProduceClassTypeInfo(CodegenOutput& source, std::string_view className, const DeclNamespace* ns) { - CodegenOutputThing thing; - - source.AddOutputThing(std::move(thing)); -} diff --git a/source/20-codegen-compiler/CodegenUtils.hpp b/source/20-codegen-compiler/CodegenUtils.hpp deleted file mode 100644 index 62d5400..0000000 --- a/source/20-codegen-compiler/CodegenUtils.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "CodegenConfig.hpp" -#include "CodegenDecl.hpp" -#include "CodegenOutput.hpp" - -#include -#include - -// I give up, hopefully nothing overflows this buffer -// TODO handle buffer sizing properly - -#define INPLACE_FMT(varName, format, ...) \ - char varName[2048]; \ - snprintf(varName, sizeof(varName), format, __VA_ARGS__); - -#define APPEND_LIT(out, str) \ - out += str - -#define APPEND_FMT(out, format, ...) \ - { \ - char buffer[65536]; \ - snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ - out += buffer; \ - } - -#define WRITE_LIT(file, str) \ - fwrite(str, sizeof(char), sizeof(str) - 1, file) - -// NOTE: snprintf() returns the size written (given an infinite buffer) not including \0 -#define WRITE_FMT(file, format, ...) \ - { \ - char buffer[65536]; \ - int size = snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ - fwrite(buffer, sizeof(char), std::min(size, sizeof(buffer)), file); \ - } - -#define APPEND_LIT_LN(out, str) APPEND_LIT(out, (str "\n")) -#define APPEND_FMT_LN(out, format, ...) APPEND_FMT(out, (format "\n"), __VA_ARGS__) -#define WRITE_LIT_LN(out, str) WRITE_LIT(out, (str "\n")) -#define WRITE_FMT_LN(out, format, ...) WRITE_FMT(out, (format "\n"), __VA_ARGS__) - -namespace Utils { - -bool WriteOutputFile(const CodegenOutput& output, const char* path); - -std::string MakeFullName(std::string_view name, DeclNamespace* ns = nullptr); -std::vector SplitIdentifier(std::string_view name); -std::string MakePascalCase(std::string_view name); - -void ProduceGeneratedHeader(const char* headerFilename, CodegenOutput& header, const char* sourceFilename, CodegenOutput& source); -void ProduceClassTypeInfo(CodegenOutput& source, std::string_view className, const DeclNamespace* ns = nullptr); - -} // namespace Utils diff --git a/source/20-codegen-compiler/buildfile b/source/20-codegen-compiler/buildfile deleted file mode 100644 index 8d1c8b6..0000000 --- a/source/20-codegen-compiler/buildfile +++ /dev/null @@ -1 +0,0 @@ - diff --git a/source/20-codegen-compiler/main.cpp b/source/20-codegen-compiler/main.cpp deleted file mode 100644 index 5e052a3..0000000 --- a/source/20-codegen-compiler/main.cpp +++ /dev/null @@ -1,1112 +0,0 @@ -#include "CodegenConfig.hpp" -#include "CodegenDecl.hpp" -#include "CodegenInput.hpp" -#include "CodegenLexer.hpp" -#include "CodegenOutput.hpp" -#include "CodegenUtils.hpp" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::literals; -namespace fs = std::filesystem; - -// TODO support codegen target in .cpp files - -struct AppState { - std::string_view outputDir; -}; - -FSTR_LUT_DECL(ClexNames, CLEX_eof, CLEX_ext_COUNT) { - FSTR_LUT_MAP_FOR(ClexNames); - FSTR_LUT_MAP_ENUM(CLEX_intlit); - FSTR_LUT_MAP_ENUM(CLEX_floatlit); - FSTR_LUT_MAP_ENUM(CLEX_id); - FSTR_LUT_MAP_ENUM(CLEX_dqstring); - FSTR_LUT_MAP_ENUM(CLEX_sqstring); - FSTR_LUT_MAP_ENUM(CLEX_charlit); - FSTR_LUT_MAP_ENUM(CLEX_eq); - FSTR_LUT_MAP_ENUM(CLEX_noteq); - FSTR_LUT_MAP_ENUM(CLEX_lesseq); - FSTR_LUT_MAP_ENUM(CLEX_greatereq); - FSTR_LUT_MAP_ENUM(CLEX_andand); - FSTR_LUT_MAP_ENUM(CLEX_oror); - FSTR_LUT_MAP_ENUM(CLEX_shl); - FSTR_LUT_MAP_ENUM(CLEX_shr); - FSTR_LUT_MAP_ENUM(CLEX_plusplus); - FSTR_LUT_MAP_ENUM(CLEX_minusminus); - FSTR_LUT_MAP_ENUM(CLEX_pluseq); - FSTR_LUT_MAP_ENUM(CLEX_minuseq); - FSTR_LUT_MAP_ENUM(CLEX_muleq); - FSTR_LUT_MAP_ENUM(CLEX_diveq); - FSTR_LUT_MAP_ENUM(CLEX_modeq); - FSTR_LUT_MAP_ENUM(CLEX_andeq); - FSTR_LUT_MAP_ENUM(CLEX_oreq); - FSTR_LUT_MAP_ENUM(CLEX_xoreq); - FSTR_LUT_MAP_ENUM(CLEX_arrow); - FSTR_LUT_MAP_ENUM(CLEX_eqarrow); - FSTR_LUT_MAP_ENUM(CLEX_shleq); - FSTR_LUT_MAP_ENUM(CLEX_shreq); - FSTR_LUT_MAP_ENUM(CLEX_ext_single_char); - FSTR_LUT_MAP_ENUM(CLEX_ext_double_colon); - FSTR_LUT_MAP_ENUM(CLEX_ext_dot_dot_dot); -} - -FSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { - FSTR_LUT_MAP_FOR(EnumUnderlyingType); - FSTR_LUT_MAP(EUT_Int8, "int8_t"); - FSTR_LUT_MAP(EUT_Int16, "int16_t"); - FSTR_LUT_MAP(EUT_Int32, "int32_t"); - FSTR_LUT_MAP(EUT_Int64, "int64_t"); - FSTR_LUT_MAP(EUT_Uint8, "uint8_t"); - FSTR_LUT_MAP(EUT_Uint16, "uint16_t"); - FSTR_LUT_MAP(EUT_Uint32, "uint32_t"); - FSTR_LUT_MAP(EUT_Uint64, "uint64_t"); -} - -RSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { - RSTR_LUT_MAP_FOR(EnumUnderlyingType); - - // Platform-dependent types - // TODO all of these can be suffixde with "int" - RSTR_LUT_MAP(EUT_Int16, "short"); - RSTR_LUT_MAP(EUT_Uint16, "unsigned short"); - RSTR_LUT_MAP(EUT_Int32, "int"); - RSTR_LUT_MAP(EUT_Uint32, "unsigned"); - RSTR_LUT_MAP(EUT_Uint32, "unsigned int"); -#ifdef _WIN32 - RSTR_LUT_MAP(EUT_Int32, "long"); - RSTR_LUT_MAP(EUT_Uint32, "unsigned long"); -#else - RSTR_LUT_MAP(EUT_Int64, "long"); - RSTR_LUT_MAP(EUT_Uint64, "unsigned long"); -#endif - RSTR_LUT_MAP(EUT_Int64, "long long"); - RSTR_LUT_MAP(EUT_Uint64, "unsigned long long"); - - // Sized types - RSTR_LUT_MAP(EUT_Int8, "int8_t"); - RSTR_LUT_MAP(EUT_Int16, "int16_t"); - RSTR_LUT_MAP(EUT_Int32, "int32_t"); - RSTR_LUT_MAP(EUT_Int64, "int64_t"); - RSTR_LUT_MAP(EUT_Uint8, "uint8_t"); - RSTR_LUT_MAP(EUT_Uint16, "uint16_t"); - RSTR_LUT_MAP(EUT_Uint32, "uint32_t"); - RSTR_LUT_MAP(EUT_Uint64, "uint64_t"); -} - -FSTR_LUT_DECL(EnumValuePattern, 0, EVP_COUNT) { - FSTR_LUT_MAP_FOR(EnumValuePattern); - FSTR_LUT_MAP_ENUM(EVP_Continuous); - FSTR_LUT_MAP_ENUM(EVP_Bits); - FSTR_LUT_MAP_ENUM(EVP_Random); -} - -enum CppKeyword { - CKw_Namespace, - CKw_Struct, - CKw_Class, - CKw_Enum, - CKw_Public, - CKw_Protected, - CKw_Private, - CKw_Virtual, - CKw_COUNT, -}; - -RSTR_LUT_DECL(CppKeyword, 0, CKw_COUNT) { - RSTR_LUT_MAP_FOR(CppKeyword); - RSTR_LUT_MAP(CKw_Namespace, "namespace"); - RSTR_LUT_MAP(CKw_Struct, "struct"); - RSTR_LUT_MAP(CKw_Class, "class"); - RSTR_LUT_MAP(CKw_Enum, "enum"); - RSTR_LUT_MAP(CKw_Public, "public"); - RSTR_LUT_MAP(CKw_Protected, "protected"); - RSTR_LUT_MAP(CKw_Private, "private"); - RSTR_LUT_MAP(CKw_Virtual, "virtual"); -} - -enum CodegenDirective { - CD_Class, - CD_ClassProperty, - CD_ClassMethod, - CD_Enum, - CD_COUNT, -}; - -RSTR_LUT_DECL(CodegenDirective, 0, CD_COUNT) { - RSTR_LUT_MAP_FOR(CodegenDirective); - RSTR_LUT_MAP(CD_Class, "BRUSSEL_CLASS"); - RSTR_LUT_MAP(CD_ClassProperty, "BRUSSEL_PROPERTY"); - RSTR_LUT_MAP(CD_ClassMethod, "BRUSSEL_METHOD"); - RSTR_LUT_MAP(CD_Enum, "BRUSSEL_ENUM"); -} - -std::vector> -TryConsumeDirectiveArgumentList(CodegenLexer& lexer) { - std::vector> result; - decltype(result)::value_type currentArg; - - size_t i = lexer.idx; - int parenDepth = 0; - for (; i < lexer.tokens.size(); ++i) { - auto& token = lexer.tokens[i]; - if (token.text[0] == '(') { - if (parenDepth > 0) { - currentArg.push_back(&token); - } - ++parenDepth; - } else if (token.text[0] == ')') { - --parenDepth; - if (parenDepth == 0) { - // End of argument list - ++i; // Consume the ')' token - break; - } - } else if (parenDepth > 0) { - // Parse these only if we are inside the argument list - if (token.text[0] == ',') { - result.push_back(std::move(currentArg)); - currentArg = {}; - } else { - currentArg.push_back(&token); - } - } - } - - if (!currentArg.empty()) { - result.push_back(std::move(currentArg)); - } - - lexer.idx = i; - return result; -} - -std::vector* -GetDirectiveArgument(std::vector>& list, size_t idx, const char* errMsg = nullptr) { - if (idx < list.size()) { - if (errMsg) { - printf("%s", errMsg); - } - return &list[idx]; - } - return nullptr; -} - -bool TryConsumeKeyword(CodegenLexer& lexer, CppKeyword keyword) { - auto& token = lexer.Current(); - if (token.type == CLEX_id) { - auto iter = RSTR_LUT(CppKeyword).find(token.text); - if (iter != RSTR_LUT(CppKeyword).end()) { - ++lexer.idx; - return true; - } - } - return false; -} - -bool TryConsumeAnyKeyword(CodegenLexer& lexer) { - auto& token = lexer.Current(); - if (token.type == CLEX_id && - RSTR_LUT(CppKeyword).contains(token.text)) - { - ++lexer.idx; - return true; - } - return false; -} - -std::optional -TryConsumeMemberVariable(CodegenLexer& lexer) { - // The identifier/name will always be one single token, right before the 1st '=' (if has initializer) or ';' (no initializer) - // NOTE: we assume there is no (a == b) stuff in the templates - - auto& tokens = lexer.tokens; - auto& idx = lexer.idx; - - size_t idenTokIdx; - size_t typeStart = idx; - size_t typeEnd; - for (; idx < tokens.size(); ++idx) { - auto& token = tokens[idx]; - if (token.type == CLEX_ext_single_char) { - if (token.text[0] == '=') { - typeEnd = idx - 1; - idenTokIdx = idx - 1; - lexer.SkipUntilTokenSingleChar(';'); - goto found; - } else if (token.text[0] == ';') { - typeEnd = idx - 1; - idenTokIdx = idx - 1; - goto found; - } - } - } - // We reached end of input but still no end of statement - return {}; - -found: - if (tokens[idenTokIdx].type != CLEX_id) { - // Expected identifier, found something else - return {}; - } - - DeclMemberVariable result; - result.name = tokens[idenTokIdx].text; - result.type = CombineTokens(std::span(&tokens[typeStart], &tokens[typeEnd])); - - // Consume the '=' or ';' token - ++idx; - - return result; -} - -enum StructMetaGenOptions { - // TODO how tf do we implement this one: needs full source scanning - SMGO_InheritanceHiearchy, - SMGO_COUNT, -}; - -RSTR_LUT_DECL(StructMetaGenOptions, 0, SMGO_COUNT) { - RSTR_LUT_MAP_FOR(StructMetaGenOptions); - RSTR_LUT_MAP(SMGO_InheritanceHiearchy, "InheritanceHiearchy"); -} - -enum StructPropertyOptions { - SPO_Getter, - SPO_Setter, - SPO_COUNT, -}; - -RSTR_LUT_DECL(StructPropertyOptions, 0, SPO_COUNT) { - RSTR_LUT_MAP_FOR(StructPropertyOptions); - RSTR_LUT_MAP(SPO_Getter, "GETTER"); - RSTR_LUT_MAP(SPO_Setter, "SETTER"); -} - -enum EnumMetaGenOptions { - EMGO_ToString, - EMGO_FromString, - EMGO_ExcludeUseHeuristics, - EMGO_COUNT, -}; - -RSTR_LUT_DECL(EnumMetaGenOptions, 0, EMGO_COUNT) { - RSTR_LUT_MAP_FOR(EnumMetaGenOptions); - RSTR_LUT_MAP(EMGO_ToString, "ToString"); - RSTR_LUT_MAP(EMGO_FromString, "FromString"); - RSTR_LUT_MAP(EMGO_ExcludeUseHeuristics, "ExcludeHeuristics"); -} - -void GenerateEnumStringArray(CodegenOutput& out, const DeclEnum& decl, const char* arrayName, const std::vector& filteredElements) { - CodegenOutputThing thing; - APPEND_FMT_LN(thing.text, "const char* %s[] = {", arrayName); - for (auto& elm : filteredElements) { - APPEND_FMT_LN(thing.text, "\"%s\",", elm.name.c_str()); - } - APPEND_LIT_LN(thing.text, "};"); - out.AddOutputThing(std::move(thing)); -} - -void GenerateEnumStringMap(CodegenOutput& out, const DeclEnum& decl, const char* mapName, const std::vector& filteredElements) { - CodegenOutputThing thing; - // TODO - out.AddOutputThing(std::move(thing)); -} - -void GenerateForEnum(CodegenOutput& headerOut, CodegenOutput& sourceOut, const DeclEnum& decl, EnumFlags options) { - char enumName[2048]; - if (decl.container) { - snprintf(enumName, sizeof(enumName), "%.*s::%s", PRINTF_STRING_VIEW(decl.container->fullname), decl.name.c_str()); - } else { - strncpy(enumName, decl.name.c_str(), sizeof(enumName)); - } - - // TODO mangle to prevent name conflicts of enum in different namespaces - auto& declIdName = decl.name; - - auto useExcludeHeuristics = options.IsSet(EMGO_ExcludeUseHeuristics); - auto filteredElements = [&]() { - if (useExcludeHeuristics) { - decltype(decl.elements) result; - for (auto& elm : decl.elements) { - if (elm.name.ends_with("COUNT")) continue; - - result.push_back(elm); - } - return result; - } else { - return decl.elements; - } - }(); - - if (options.IsSet(EMGO_ToString)) { - // Generate value -> string lookup table and function - INPLACE_FMT(val2StrName, "gCG_%s_Val2Str", declIdName.c_str()); - - switch (decl.GetPattern()) { - case EVP_Continuous: { - GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); - int minVal = filteredElements.empty() ? 0 : filteredElements.front().value; - int maxVal = filteredElements.empty() ? 0 : filteredElements.back().value; - - CodegenOutputThing lookupFunctionDecl; - { - auto& o = lookupFunctionDecl.text; - APPEND_LIT_LN(o, "template <>"); - APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value);", enumName, enumName); - } - - CodegenOutputThing lookupFunctionDef; - { - auto& o = lookupFunctionDef.text; - APPEND_LIT_LN(o, "template <>"); - APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value) {", enumName, enumName); - APPEND_FMT_LN(o, " auto intVal = (%s)value;", FSTR_LUT_LOOKUP(EnumUnderlyingType, decl.underlyingType)); - APPEND_FMT_LN(o, " if (intVal < %d || intVal > %d) return {};", minVal, maxVal); - APPEND_FMT_LN(o, " return %s[intVal - %d];", val2StrName, minVal); - APPEND_LIT_LN(o, "}"); - } - - headerOut.AddOutputThing(std::move(lookupFunctionDecl)); - sourceOut.AddOutputThing(std::move(lookupFunctionDef)); - } break; - - case EVP_Bits: { - GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); - // TODO - } break; - - case EVP_Random: { - GenerateEnumStringMap(sourceOut, decl, val2StrName, filteredElements); - // TODO - } break; - - case EVP_COUNT: break; - } - } - - if (options.IsSet(EMGO_FromString)) { - // Generate string -> value lookup table - INPLACE_FMT(str2ValName, "gCG_%s_Str2Val", declIdName.c_str()); - - CodegenOutputThing lookupTable; - { - auto& o = lookupTable.text; - // TODO use correct underlying type - APPEND_FMT_LN(o, "constinit frozen::unordered_map %s = {", filteredElements.size(), str2ValName); - for (auto& elm : filteredElements) { - APPEND_FMT_LN(o, "{\"%s\", %" PRId64 "},", elm.name.c_str(), elm.value); - } - APPEND_LIT_LN(o, "};"); - } - - // Generate lookup function - CodegenOutputThing lookupFunctionDecl; - { - auto& o = lookupFunctionDecl.text; - APPEND_LIT_LN(o, "template <>"); - APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value);", enumName, enumName); - } - - CodegenOutputThing lookupFunctionDef; - { - auto& o = lookupFunctionDef.text; - APPEND_LIT_LN(o, "template <>"); - APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value) {", enumName, enumName); - APPEND_FMT_LN(o, " auto iter = %s.find(value);", str2ValName); - APPEND_FMT_LN(o, " if (iter != %s.end()) {", str2ValName); - APPEND_FMT_LN(o, " return (%s)iter->second;", enumName); - APPEND_LIT_LN(o, " } else {"); - APPEND_LIT_LN(o, " return {};"); - APPEND_LIT_LN(o, " }"); - APPEND_LIT_LN(o, "}"); - } - - sourceOut.AddOutputThing(std::move(lookupTable)); - headerOut.AddOutputThing(std::move(lookupFunctionDecl)); - sourceOut.AddOutputThing(std::move(lookupFunctionDef)); - } -} - -void GenerateClassProperty(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { - // TODO -} - -void GenerateClassFunction(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { - // TODO -} - -void GenerateForClassMetadata( - CodegenOutput& headerOutput, - CodegenOutput& sourceOutput, - const DeclStruct& decl) // -{ - // TODO mangle - auto declIdName = decl.name.c_str(); - - CodegenOutputThing data; - // TODO generate type id, this needs global scanning - APPEND_FMT_LN(data.text, "const TypeInfo* const gCGtype_%s_BaseClasses[] = {", declIdName); - for (auto& baseClass : decl.baseClasses) { - // TODO get ptr to TypeInfo, this needs global scanning for non-file local classes - } - APPEND_LIT_LN(data.text, "};"); - APPEND_FMT_LN(data.text, "const TypePropertyInfo gCGtype_%s_Properties[] = {", declIdName); - for (auto& property : decl.memberVariables) { - APPEND_FMT_LN(data.text, "{.name=\"%s\"sv, .getterName=\"%s\"sv, .setterName=\"%s\"sv},", property.name.c_str(), property.getterName.c_str(), property.setterName.c_str()); - } - APPEND_LIT_LN(data.text, "};"); - APPEND_FMT_LN(data.text, "const TypeInfo gCGtype_%s_TypeInfo = {", declIdName); - APPEND_FMT_LN(data.text, ".name = \"%s\"sv,", declIdName); - APPEND_FMT_LN(data.text, ".parents = gCGtype_%s_BaseClasses,", declIdName); - APPEND_FMT_LN(data.text, ".properties = gCGtype_%s_Properties};", declIdName); - - CodegenOutputThing queryFunc; - APPEND_FMT(queryFunc.text, - "template <>\n" - "const TypeInfo* Metadata::GetTypeInfo<%.*s>() {\n" - " return &gCGtype_%s_TypeInfo;\n" - "}\n", - PRINTF_STRING_VIEW(decl.fullname), - declIdName); - - sourceOutput.AddOutputThing(std::move(data)); - sourceOutput.AddOutputThing(std::move(queryFunc)); -} - -void HandleInputFile(AppState& state, std::string_view filenameStem, std::string_view source) { - CodegenLexer lexer; - lexer.InitializeFrom(source); - -#if CODEGEN_DEBUG_PRINT - printf("BEGIN tokens\n"); - for (auto& token : lexer.tokens) { - switch (token.type) { - case CLEX_intlit: { - printf(" token %-32s = %ld\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerIntNumber); - } break; - - case CLEX_floatlit: { - printf(" token %-32s = %f\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerRealNumber); - } break; - - default: { - printf(" token %-32s '%s'\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.text.c_str()); - } break; - } - } - printf("END tokens\n"); -#endif - - CodegenInput cgInput; - CodegenOutput cgHeaderOutput; - CodegenOutput cgSourceOutput; - { - INPLACE_FMT(hpp, "%.*s.gh.inl", PRINTF_STRING_VIEW(filenameStem)); - INPLACE_FMT(cpp, "%.*s.gs.inl", PRINTF_STRING_VIEW(filenameStem)); - Utils::ProduceGeneratedHeader(hpp, cgHeaderOutput, cpp, cgSourceOutput); - } - CodegenOutput cgStandaloneSourceOutput; - - int currentBraceDepth = 0; - // The current effective namespace, see example - DeclNamespace* currentNamespace = nullptr; - DeclStruct* currentStruct = nullptr; - int currentStructBraceDepth = 0; - - struct NamespaceStackframe { - // The current namespace that owns the brace level, see example - DeclNamespace* ns = nullptr; - // Brace depth `ns` was created at (e.g. [std::details].depth == 0) - int depth = 0; - }; - std::vector nsStack; - - // Example: - // namespace std::details { - // /* [stack top].ns = std::details */ - // /* [stack top].depth = std */ - // } - // namespace foo { - // /* [stack top].ns = foo */ - // /* [stack top].depth = foo */ - // namespace details { - // /* [stack top].ns = foo::details */ - // /* [stack top].depth = foo::details */ - // } - // } - - auto& tokens = lexer.tokens; - auto& idx = lexer.idx; - while (lexer.idx < lexer.tokens.size()) { - auto& token = lexer.Current(); - - bool incrementTokenIdx = true; - - // Reamalgamate token type and single char tokens; - int tokenKey; - if (token.type == CLEX_ext_single_char) { - tokenKey = token.text[0]; - } else { - tokenKey = token.type; - } - - switch (tokenKey) { - case CLEX_id: { - CppKeyword keyword; - { - auto& map = RSTR_LUT(CppKeyword); - auto iter = map.find(token.text); - if (iter != map.end()) { - keyword = iter->second; - } else { - keyword = CKw_COUNT; // Skip keyword section - } - } - switch (keyword) { - case CKw_Namespace: { - ++idx; - incrementTokenIdx = false; - - int nestingCount = 0; - while (true) { - if (tokens[idx].type != CLEX_id) { - // TODO better error recovery - // TODO handle annoymous namespaces - printf("[ERROR] invalid syntax for namespace\n"); - break; - } - - currentNamespace = cgInput.AddNamespace(DeclNamespace{ - .container = currentNamespace, - .name = tokens[idx].text, - }); - - // Consume the identifier token - ++idx; - - if (tokens[idx].type == CLEX_ext_double_colon) { - // Consume the "::" token - ++idx; - } else { - break; - } - } - - nsStack.push_back(NamespaceStackframe{ - .ns = currentNamespace, - .depth = currentBraceDepth, - }); - - goto endCaseCLEX_id; - } - - case CKw_Struct: - case CKw_Class: { - // Consume the 'class' or 'struct' keyword - ++idx; - incrementTokenIdx = false; - - auto& idenTok = tokens[idx]; - if (idenTok.type != CLEX_id) { - printf("[ERROR] invalid syntax for struct or class\n"); - break; - } - - DEBUG_PRINTF("[DEBUG] found struct named %s\n", idenTok.text.c_str()); - - auto& name = idenTok.text; - auto fullname = Utils::MakeFullName(name, currentNamespace); - DeclStruct structDecl; - structDecl.container = currentNamespace; - structDecl.name = name; - - // Consume the identifier token - ++idx; - - if (lexer.TryConsumeSingleCharToken(':')) { - while (true) { - // Public, protected, etc. - TryConsumeAnyKeyword(lexer); - - auto& idenTok = tokens[idx]; - if (idenTok.type != CLEX_id) { - printf("[ERROR] invalid syntax for class inheritance list\n"); - goto endCase; - } - - // TODO support namespace qualified names - auto baseClassFullname = Utils::MakeFullName(idenTok.text, currentNamespace); - auto baseClassDecl = cgInput.FindStruct(baseClassFullname); - if (baseClassDecl) { - // We silently ignore a non-existent base class, because they may reside in a file that we didn't scan - structDecl.baseClasses.push_back(baseClassDecl); - } - - // Consume the identifier token - ++idx; - - if (lexer.TryConsumeSingleCharToken('{')) { - // End of base class list - --idx; // Give the '{' token back to the main loop - break; - } else if (!lexer.TryConsumeSingleCharToken(',')) { - // If the list didn't end, we expect a comma (then followed by more entries) - printf("[ERROR] invalid syntax for class inheritance list\n"); - goto endCase; - } - - // NOTE: we currently only scan one base class to workaround some code inherits from template classes after their initial base class - // TODO remove this hack - break; - } - } - - { - // Get a pointer to the decl inside CodegenInput's storage - auto decl = cgInput.AddStruct(std::move(fullname), std::move(structDecl)); - currentStruct = decl; - currentStructBraceDepth = currentBraceDepth; - } - - endCase: - goto endCaseCLEX_id; - } - - case CKw_Enum: { - // Consume the "enum" keyword - ++idx; - incrementTokenIdx = false; - - StbLexerToken* idenTok; - if (tokens[idx].text == "class") { - // Consume the "class" keyword - ++idx; - idenTok = &tokens[idx]; - DEBUG_PRINTF("[DEBUG] found enum class named %s\n", idenTok->text.c_str()); - } else { - idenTok = &tokens[idx]; - DEBUG_PRINTF("[DEBUG] found enum named %s\n", idenTok->text.c_str()); - } - - DeclEnum enumDecl; - enumDecl.container = currentNamespace; - enumDecl.underlyingType = EUT_Int32; // TODO - enumDecl.name = tokens[idx].text; - - // Consume the enum name identifier - ++idx; - - int enumClosingBraceCount = 0; - int enumBraceDepth = 0; - while (enumClosingBraceCount == 0 && idx < tokens.size()) { - auto& token = tokens[idx]; - switch (token.type) { - case CLEX_id: { - auto& vec = enumDecl.elements; - // Set to the previous enum element's value + 1, or starting from 0 if this is the first - // Also overridden in the CLEX_intlit branch - auto value = vec.empty() ? 0 : vec.back().value + 1; - vec.push_back(DeclEnumElement{ - .name = token.text, - .value = value, - }); - } break; - - case CLEX_intlit: { - auto& vec = enumDecl.elements; - if (!vec.empty()) { - auto& lastElm = vec.back(); - lastElm.value = token.lexerIntNumber; - } - } break; - - case CLEX_ext_single_char: { - switch (token.text[0]) { - case '{': { - ++enumBraceDepth; - } break; - - case '}': { - --enumBraceDepth; - ++enumClosingBraceCount; - } break; - } - } break; - } - - ++idx; - } - - auto fullname = Utils::MakeFullName(enumDecl.name, currentNamespace); - cgInput.AddEnum(std::move(fullname), std::move(enumDecl)); - goto endCaseCLEX_id; - } - - // We don't care about these keywords - case CKw_Public: - case CKw_Protected: - case CKw_Private: - case CKw_Virtual: - case CKw_COUNT: break; - } - - CodegenDirective directive; - { - auto& map = RSTR_LUT(CodegenDirective); - auto iter = map.find(token.text); - if (iter != map.end()) { - directive = iter->second; - } else { - directive = CD_COUNT; // Skip directive section - } - } - switch (directive) { - case CD_Class: { - // Consume the directive - ++idx; - incrementTokenIdx = false; - - if (!currentStruct) { - printf("[ERROR] BRUSSEL_CLASS must be used within a class or struct\n"); - break; - } - - // Always-on option - currentStruct->generating = true; - - auto argList = TryConsumeDirectiveArgumentList(lexer); - auto& lut = RSTR_LUT(StructMetaGenOptions); - for (auto& arg : argList) { - if (arg.empty()) { - printf("[ERROR] empty argument is invalid in BRUSSEL_CLASS\n"); - continue; - } - - auto& optionDirective = arg[0]->text; - auto iter = lut.find(optionDirective); - if (iter == lut.end()) continue; - switch (iter->second) { - case SMGO_InheritanceHiearchy: currentStruct->generatingInheritanceHiearchy = true; break; - case SMGO_COUNT: break; - } - } - - goto endCaseCLEX_id; - } - - case CD_ClassProperty: { - // Consume the directive - ++idx; - incrementTokenIdx = false; - - if (!currentStruct || - !currentStruct->generating) - { - printf("[ERROR] BRUSSEL_PROPERTY must be used within a class or struct, that has the BRUSSEL_CLASS directive\n"); - break; - } - - auto argList = TryConsumeDirectiveArgumentList(lexer); - auto declOpt = TryConsumeMemberVariable(lexer); - if (!declOpt.has_value()) { - printf("[ERROR] a member variable must immediately follow a BRUSSEL_PROPERTY\n"); - break; - } - auto& decl = declOpt.value(); - - // Different option's common logic - std::string pascalCaseName; - auto GetPascalCasedName = [&]() -> const std::string& { - if (pascalCaseName.empty()) { - pascalCaseName = Utils::MakePascalCase(decl.name); - } - return pascalCaseName; - }; - - auto& lut = RSTR_LUT(StructPropertyOptions); - for (auto& arg : argList) { - if (arg.empty()) { - printf("[ERROR] empty argument is invalid in BRUSSEL_PROPERTY\n"); - continue; - } - - auto& optionDirective = arg[0]->text; - auto iter = lut.find(optionDirective); - if (iter == lut.end()) continue; - switch (iter->second) { - case SPO_Getter: { - // TODO I'm too lazy to write error checks, just let the codegen crash - auto& getterName = arg.at(1)->text; - if (getterName == "auto") { - // NOTE: intentionally shadowing - INPLACE_FMT(getterName, "Get%s", GetPascalCasedName().c_str()); - - // TODO generate getter function - - decl.getterName = getterName; - } else { - decl.getterName = getterName; - } - } break; - - case SPO_Setter: { - // TODO - auto& setterName = arg.at(1)->text; - if (setterName == "auto") { - // NOTE: intentionally shadowing - INPLACE_FMT(setterName, "Set%s", GetPascalCasedName().c_str()); - - // TODO generate setter function - - decl.setterName = setterName; - } else { - decl.setterName = setterName; - } - } break; - - case SPO_COUNT: break; - } - } - - currentStruct->memberVariables.push_back(std::move(decl)); - - goto endCaseCLEX_id; - } - - case CD_ClassMethod: { - // Consume the directive - ++idx; - incrementTokenIdx = false; - - goto endCaseCLEX_id; - } - - case CD_Enum: { - // Consume the directive - ++idx; - incrementTokenIdx = false; - - auto& optionsStrMap = RSTR_LUT(EnumMetaGenOptions); - auto argList = TryConsumeDirectiveArgumentList(lexer); - - if (argList.size() < 1) { - printf("[ERROR] invalid syntax for BRUSSEL_ENUM\n"); - break; - } - - auto& enumName = argList[0][0]->text; - auto enumDecl = cgInput.FindEnum(Utils::MakeFullName(enumName, currentNamespace)); - if (!enumDecl) { - printf("[ERROR] BRUSSEL_ENUM: referring to non-existent enum '%s'\n", enumName.c_str()); - break; - } - - auto& directiveOptions = argList[1]; - EnumFlags options; - for (auto optionTok : directiveOptions) { - auto iter = optionsStrMap.find(optionTok->text); - if (iter != optionsStrMap.end()) { - options |= iter->second; - } else { - printf("[ERROR] BRUSSEL_ENUM: invalid option '%s'\n", optionTok->text.c_str()); - } - } - - GenerateForEnum(cgHeaderOutput, cgSourceOutput, *enumDecl, options); - - goto endCaseCLEX_id; - } - - case CD_COUNT: break; - } - - endCaseCLEX_id:; - } break; - - case '{': { - currentBraceDepth++; - if (currentBraceDepth < 0) { - printf("[WARNING] unbalanced brace\n"); - } - } break; - - case '}': { - currentBraceDepth--; - if (currentBraceDepth < 0) { - printf("[WARNING] unbalanced brace\n"); - } - - if (!nsStack.empty()) { - auto& ns = nsStack.back(); - if (ns.depth == currentBraceDepth) { - nsStack.pop_back(); - - if (!nsStack.empty()) { - currentNamespace = nsStack.back().ns; - } else { - currentNamespace = nullptr; - } - } - } - - if (currentStruct && - currentBraceDepth == currentStructBraceDepth) - { - // Exit struct - - if (currentStruct->generating) { - GenerateForClassMetadata(cgHeaderOutput, cgSourceOutput, *currentStruct); - } - if (currentStruct->generatingInheritanceHiearchy) { - // NOTE: this option is transitive to all child classes (as long as they have the basic annotation) - // TODO - } - - currentStruct = nullptr; - currentStructBraceDepth = 0; - } - } break; - } - - if (incrementTokenIdx) { - ++idx; - } - } - - if (currentBraceDepth != 0) { - printf("[WARNING] unbalanced brace at end of file."); - } - - INPLACE_FMT(generatedHeaderInlName, "%.*s/%.*s.gh.inl", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem)); - Utils::WriteOutputFile(cgHeaderOutput, generatedHeaderInlName); - INPLACE_FMT(generatedSourceInlName, "%.*s/%.*s.gs.inl", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem)); - Utils::WriteOutputFile(cgSourceOutput, generatedSourceInlName); - INPLACE_FMT(generatedCppName, "%.*s/%.*s.g.cpp", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem)); - Utils::WriteOutputFile(cgStandaloneSourceOutput, generatedCppName); -} - -enum InputOpcode { - IOP_ProcessSingleFile, - IOP_ProcessRecursively, - IOP_COUNT, -}; - -void HandleArgument(AppState& state, InputOpcode opcode, std::string_view operand) { - switch (opcode) { - case IOP_ProcessSingleFile: { - DEBUG_PRINTF("Processing single file %.*s\n", PRINTF_STRING_VIEW(operand)); - - fs::path path(operand); - auto filenameStem = path.stem().string(); - auto source = Utils::ReadFileAsString(path); - HandleInputFile(state, filenameStem, source); - } break; - - case IOP_ProcessRecursively: { - DEBUG_PRINTF("Recursively processing folder %.*s\n", PRINTF_STRING_VIEW(operand)); - - fs::path startPath(operand); - for (auto& item : fs::recursive_directory_iterator(startPath)) { - if (!item.is_regular_file()) { - continue; - } - - auto& path = item.path(); - auto pathExt = path.extension(); - auto pathStem = path.stem(); - if (pathExt != ".h" && - pathExt != ".hpp") - { - continue; - } - - DEBUG_PRINTF("Processing subfile %s\n", path.string().c_str()); - - auto filenameStem = pathStem.string(); - auto source = Utils::ReadFileAsString(path); - HandleInputFile(state, filenameStem, source); - } - } break; - - case IOP_COUNT: break; - } -} - -InputOpcode ParseInputOpcode(std::string_view text) { - if (text == "single"sv) { - return IOP_ProcessSingleFile; - } else if (text == "rec"sv) { - return IOP_ProcessRecursively; - } else { - DEBUG_PRINTF("Unknown input opcode %s\n", text.data()); - throw std::runtime_error("Unknown input opcode"); - } -} - -int main(int argc, char* argv[]) { - FSTR_LUT_INIT(ClexNames); - FSTR_LUT_INIT(EnumUnderlyingType); - RSTR_LUT_INIT(EnumUnderlyingType); - FSTR_LUT_INIT(EnumValuePattern); - RSTR_LUT_INIT(CppKeyword); - RSTR_LUT_INIT(CodegenDirective); - RSTR_LUT_INIT(StructMetaGenOptions); - RSTR_LUT_INIT(StructPropertyOptions); - RSTR_LUT_INIT(EnumMetaGenOptions); - - // TODO better arg parser - // option 1: use cxxopts and positional arguments - // option 2: take one argument only, being a json objecet - - AppState state; - - // If no cli is provided (argv[0] conventionally but not mandatorily the cli), this will do thing - // Otherwise, start with the 2nd element in the array, which is the 1st actual argument - if (argc < 2) { - // NOTE: keep in sync with various enum options and parser code - printf(&R"""( -USAGE: codegen.exe [:]... -where : the directory to write generated contents to. This will NOT automatically create the directory. - is one of: - "single" process this file only - "rec" starting at the given directory , recursively process all .h .hpp files -)"""[1]); - return -1; - } - - state.outputDir = std::string_view(argv[1]); - DEBUG_PRINTF("Outputting to directory %.*s.\n", PRINTF_STRING_VIEW(state.outputDir)); - - for (int i = 2; i < argc; ++i) { - const char* argRaw = argv[i]; - std::string_view arg(argRaw); - DEBUG_PRINTF("Processing input command %s\n", argRaw); - - auto separatorLoc = arg.find(':'); - if (separatorLoc != std::string_view::npos) { - auto opcodeString = arg.substr(0, separatorLoc); - auto opcode = ParseInputOpcode(opcodeString); - auto operand = arg.substr(separatorLoc + 1); - - HandleArgument(state, opcode, operand); - } - } - - return 0; -} diff --git a/source/20-codegen-compiler/test/examples/TestClass.hpp.txt b/source/20-codegen-compiler/test/examples/TestClass.hpp.txt deleted file mode 100644 index 3eed8db..0000000 --- a/source/20-codegen-compiler/test/examples/TestClass.hpp.txt +++ /dev/null @@ -1,38 +0,0 @@ -#include - -class MyClass { - BRUSSEL_CLASS() - -public: - BRUSSEL_PROPERTY(GETTER GetName, SETTER SetName) - std::string name; - - BRUSSEL_PROPERTY(GETTER auto, SETTER auto) - std::string tag; - - BRUSSEL_PROPERTY() - int foo; - - BRUSSEL_PROPERTY() - int bar; - -public: - const std::string& GetName() const { return name; } - void SetName(std::string name) { this->name = std::move(name); } -}; - -namespace MyNamespace { -struct Base { - BRUSSEL_CLASS(InheritanceHiearchy) -}; - -struct DerviedFoo : public Base { - BRUSSEL_CLASS() -}; - -struct DerviedBar : Base { - BRUSSEL_CLASS() -}; -} - -#include diff --git a/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt b/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt deleted file mode 100644 index 30c36c0..0000000 --- a/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt +++ /dev/null @@ -1,44 +0,0 @@ -enum MyEnum { - EnumElement1, - EnumElement2, - EnumElement3, -}; -BRUSSEL_ENUM(MyEnum, ToString FromString); - -// Let's also test enum class -enum class CountedEnumAll { - CEA_Foo, - CEA_Bar, - CEA_COUNT, -}; -BRUSSEL_ENUM(CountedEnumAll, ToString FromString); - -enum CountedEnum { - CE_Foo, - CE_Bar, - CE_FooBar, - CE_COUNT, -}; -BRUSSEL_ENUM(CountedEnum, ToString FromString ExcludeHeuristics); - -namespace MyNamespace { -enum class MyNamespacedEnum { - MNE_Foo, - MNE_Bar, -}; -BRUSSEL_ENUM(MyNamespacedEnum, ToString FromString ExcludeHeuristics); - -namespace details { - enum MyNamespacedEnum { - MNE_Foo, - MNE_Bar, - }; - BRUSSEL_ENUM(MyNamespacedEnum, ToString FromString ExcludeHeuristics); -} -} - -namespace foo::details { -enum Enum { -}; -BRUSSEL_ENUM(Enum, ToString FromString ExcludeHeuristics); -} diff --git a/source/20-codegen-runtime/MacrosCodegen.hpp b/source/20-codegen-runtime/MacrosCodegen.hpp deleted file mode 100644 index 956123c..0000000 --- a/source/20-codegen-runtime/MacrosCodegen.hpp +++ /dev/null @@ -1,10 +0,0 @@ -// NOTE: Contents of this file is coupled with the codegen compiler. -// When updating, change both sides at the same time. - -#pragma once - -#define BRUSSEL_CLASS(...) -#define BRUSSEL_PROPERTY(...) -#define BRUSSEL_METHOD(...) - -#define BRUSSEL_ENUM(name, options) diff --git a/source/20-codegen-runtime/Metadata.cpp b/source/20-codegen-runtime/Metadata.cpp deleted file mode 100644 index 0d640da..0000000 --- a/source/20-codegen-runtime/Metadata.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "Metadata.hpp" - -auto Metadata::TypeInfoList::Iterator::operator*() const -> const TypeInfo& { - // TODO -} - -auto Metadata::TypeInfoList::Iterator::operator++() -> Iterator& { - // TODO -} - -auto Metadata::TypeInfoList::Iterator::operator++(int) -> Iterator { - auto copy = *this; - ++copy; - return copy; -} - -bool Metadata::TypeInfoList::Iterator::operator==(const Iterator& that) const { - return this->data == that.data; -} - -bool Metadata::TypeInfoList::Iterator::operator==(const Sentinel&) const { - // TODO -} - -auto Metadata::TypeInfoList::begin() const -> Iterator { - return Iterator(); -} - -auto Metadata::TypeInfoList::end() const -> Sentinel { - return Sentinel(); -} - -auto Metadata::GetTypeInfoList() -> const TypeInfoList& { - // TODO -} - -auto Metadata::QueryTypeInfo(TypeId id) -> const TypeInfo* { - // TODO - return nullptr; -} - -auto Metadata::QueryTypeInfo(std::string_view id) -> const TypeInfo* { - // TODO - return nullptr; -} diff --git a/source/20-codegen-runtime/Metadata.hpp b/source/20-codegen-runtime/Metadata.hpp deleted file mode 100644 index e89fd8f..0000000 --- a/source/20-codegen-runtime/Metadata.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "MacrosCodegen.hpp" -#include "MetadataBase.hpp" - -namespace Metadata { - -struct TypeInfoList { - struct Sentinel { - }; - - struct Iterator { - void* data; - - const TypeInfo& operator*() const; - Iterator& operator++(); - Iterator operator++(int); - - bool operator==(const Iterator&) const; - bool operator==(const Sentinel&) const; - }; - - Iterator begin() const; - Sentinel end() const; -}; - -/// Get a list of all type infos present. -const TypeInfoList& GetTypeInfoList(); - -const TypeInfo* QueryTypeInfo(TypeId id); -const TypeInfo* QueryTypeInfo(std::string_view name); - -} // namespace Metadata diff --git a/source/20-codegen-runtime/MetadataBase.cpp b/source/20-codegen-runtime/MetadataBase.cpp deleted file mode 100644 index 2f2ef94..0000000 --- a/source/20-codegen-runtime/MetadataBase.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "MetadataBase.hpp" - -bool Metadata::TypePropertyInfo::IsDirectAccess() const { - return getterName.empty() && setterName.empty(); -} diff --git a/source/20-codegen-runtime/MetadataBase.hpp b/source/20-codegen-runtime/MetadataBase.hpp deleted file mode 100644 index c1ad894..0000000 --- a/source/20-codegen-runtime/MetadataBase.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace Metadata { - -struct TypeId { - size_t id; -}; - -struct TypeInfo; - -struct TypePropertyInfo { - std::string_view name; - const TypeInfo* type; - std::string_view getterName; - std::string_view setterName; - - bool IsDirectAccess() const; -}; - -struct TypeMethodInfo { - std::string_view name; - // TODO -}; - -struct TypeInfo { - TypeId typeId; - std::string_view name; - std::span parents; - std::span properties; - std::span methods; - - /// Whether this object is registered at runtime or statically compiled - bool dynamic = false; -}; - -// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase -template -const TypeInfo* GetTypeInfo(); - -// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase -template -std::string_view EnumToString(TEnum value); - -// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase -template -std::optional EnumFromString(std::string_view str); - -} // namespace Metadata diff --git a/source/20-codegen-runtime/MetadataDetails.hpp b/source/20-codegen-runtime/MetadataDetails.hpp deleted file mode 100644 index 09b71ff..0000000 --- a/source/20-codegen-runtime/MetadataDetails.hpp +++ /dev/null @@ -1,7 +0,0 @@ -// This file contains implementation details used by the outputs of the code generaetor. Consumers do not use. -#pragma once - -#include "MetadataBase.hpp" - -namespace Metadata::Details { -} // namespace Metadata::Details diff --git a/source/20-codegen-runtime/buildfile b/source/20-codegen-runtime/buildfile deleted file mode 100644 index 8d1c8b6..0000000 --- a/source/20-codegen-runtime/buildfile +++ /dev/null @@ -1 +0,0 @@ - diff --git a/source/30-game/App.cpp b/source/30-game/App.cpp deleted file mode 100644 index 45a7545..0000000 --- a/source/30-game/App.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "App.hpp" - -#include -#include - -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); -} - -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 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 - -#include -#include -#include -#include - -using KeyCaptureCallback = std::function; - -class App { -private: - std::deque mKeyCaptureCallbacks; - PodVector mPlayers; - std::unique_ptr 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 -#include -#include - -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 - -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 -#include - -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 786274e..0000000 --- a/source/30-game/CommonVertexIndex.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "CommonVertexIndex.hpp" - -template -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 -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 -static void AssignPositions(TVertex vertices[4], const Rect& 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 -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 -static void AssignDepths(TVertex vertices[4], float z) { - for (int i = 0; i < 4; ++i) { - auto& vert = vertices[i]; - vert.z = z; - } -} - -template -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 -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(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(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(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(indices, topLeft, topRight, bottomRight, bottomLeft); -} - -void Vertex_PC::Assign(Vertex_PC vertices[4], const Rect& 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& 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 adb81b6..0000000 --- a/source/30-game/CommonVertexIndex.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include "Color.hpp" -#include "RcPtr.hpp" -#include "Rect.hpp" -#include "Texture.hpp" -#include "VertexIndex.hpp" - -#include - -// Initialized in main() -inline RcPtr gVformatStandard{}; -inline RcPtr gVformatStandardSplit{}; - -// 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& 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& 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 08d08ec..0000000 --- a/source/30-game/EditorAccessories.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "EditorAccessories.hpp" - -#include - -void EditorSettings::Show() { -} diff --git a/source/30-game/EditorAccessories.hpp b/source/30-game/EditorAccessories.hpp deleted file mode 100644 index 687b509..0000000 --- a/source/30-game/EditorAccessories.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -class EditorSettings { -public: - void Show(); -}; 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 - -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 - -EditorAttachment::EditorAttachment() { -} - -std::unique_ptr 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(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 - -class EaGameObject : public EditorAttachment { -public: - // NOTE: in degrees - glm::vec3 eulerAnglesRotation; - -public: - static std::unique_ptr Create(GameObject* object); -}; - -class EaPlayer : public EaGameObject { -public: - RcPtr confSprite; - RcPtr 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 -#include -#include -#include -#include -#include - -#define IMGUI_DEFINE_MATH_OPERATORS -#include - -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 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(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::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::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 -#include -#include -#include -#include - -class EditorCommandExecuteContext; -class EditorCommand { -public: - std::string name; - std::function callback; - std::function subsequentCallback; - std::function terminate; -}; - -class EditorCommandExecuteContext { - friend class EditorCommandPalette; - -private: - const EditorCommand* mCommand = nullptr; - std::vector mCurrentOptions; - int mDepth = 0; - -public: - bool IsInitiated() const; - const EditorCommand* GetCurrentCommand() const; - void Initiate(const EditorCommand& command); - - void Prompt(std::vector 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 mCommands; - std::vector mItems; - std::vector 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 - -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 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 43857a8..0000000 --- a/source/30-game/EditorCorePrivate.cpp +++ /dev/null @@ -1,1113 +0,0 @@ -#include "EditorCorePrivate.hpp" - -#include "App.hpp" -#include "AppConfig.hpp" -#include "EditorAccessories.hpp" -#include "EditorAttachmentImpl.hpp" -#include "EditorCommandPalette.hpp" -#include "EditorNotification.hpp" -#include "EditorUtils.hpp" -#include "GameObject.hpp" -#include "Mesh.hpp" -#include "Player.hpp" -#include "SceneThings.hpp" -#include "VertexIndex.hpp" - -#include -#include -#include -#include - -#define GLFW_INCLUDE_NONE -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::literals; - -namespace ProjectBrussel_UNITY_ID { -// TODO handle internal state internally and move this to EditorUtils.hpp -enum RenamableSelectableAction { - RSA_None, - RSA_Selected, - RSA_RenameCommitted, - RSA_RenameCancelled, -}; -RenamableSelectableAction RenamableSelectable(const char* displayName, bool selected, bool& renaming, std::string& renamingScratchBuffer) // -{ - RenamableSelectableAction result = RSA_None; - - ImGuiSelectableFlags flags = 0; - // When renaming, disable all other entries that is not the one being renamed - if (renaming && !selected) { - flags |= ImGuiSelectableFlags_Disabled; - } - - if (renaming && selected) { - // State: being renamed - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); - ImGui::SetKeyboardFocusHere(); - if (ImGui::InputText("##Rename", &renamingScratchBuffer, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) { - // Confirm - renaming = false; - result = RSA_RenameCommitted; - } - ImGui::PopStyleVar(2); - - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { - // Cancel - renaming = false; - result = RSA_RenameCancelled; - } - } else { - // State: normal - - if (ImGui::Selectable(displayName, selected, flags)) { - result = RSA_Selected; - } - } - - return result; -} -} // namespace ProjectBrussel_UNITY_ID - -void EditorInspector::SelectTarget(TargetType type, void* object) { - selectedItt = type; - selectedItPtr = object; - renaming = false; - renamingScratchBuffer.clear(); -} - -EditorContentBrowser::EditorContentBrowser(EditorInspector* inspector) - : mInspector{ inspector } { -} - -EditorContentBrowser::~EditorContentBrowser() { -} - -void EditorContentBrowser::Show(bool* open) { - using namespace ProjectBrussel_UNITY_ID; - - ImGuiWindowFlags windowFlags; - if (mDocked) { - // Center window horizontally, align bottom vertically - auto& viewportSize = ImGui::GetMainViewport()->Size; - ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2, viewportSize.y), ImGuiCond_Always, ImVec2(0.5f, 1.0f)); - ImGui::SetNextWindowSizeRelScreen(0.8f, mBrowserHeight); - windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; - } else { - windowFlags = 0; - } - ImGui::Begin("Content Browser", open, windowFlags); - - ImGui::Splitter(true, kSplitterThickness, &mSplitterLeft, &mSplitterRight, kLeftPaneMinWidth, kRightPaneMinWidth); - - ImGui::BeginChild("LeftPane", ImVec2(mSplitterLeft - kPadding, 0.0f)); - { - if (ImGui::Selectable("Settings", mPane == P_Settings)) { - mPane = P_Settings; - } - if (ImGui::Selectable("Ires", mPane == P_Ires)) { - mPane = P_Ires; - } - if (ImGui::Selectable("Levels", mPane == P_Level)) { - mPane = P_Level; - } - } - ImGui::EndChild(); - - ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding); - ImGui::BeginChild("RightPane"); // Fill remaining space - auto origItt = mInspector->selectedItt; - auto origItPtr = mInspector->selectedItPtr; - switch (mPane) { - case P_Settings: { - ImGui::Checkbox("Docked", &mDocked); - ImGui::SliderFloat("Height", &mBrowserHeight, 0.1f, 1.0f); - } break; - - case P_Ires: { - bool isIttIres = origItt == EditorInspector::ITT_Ires; - - if (ImGui::Button("New")) { - ImGui::OpenPopup("New Ires"); - } - if (ImGui::BeginPopup("New Ires")) { - for (int i = 0; i < (int)IresObject::KD_COUNT; ++i) { - auto kind = static_cast(i); - if (ImGui::MenuItem(Metadata::EnumToString(kind).data())) { - auto ires = IresObject::Create(kind); - auto [DISCARD, success] = IresManager::instance->Add(ires.get()); - if (success) { - (void)ires.release(); - } - } - } - ImGui::EndPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button("Refresh list") || - ImGui::IsKeyPressed(ImGuiKey_F5)) - { - // TODO - } - - ImGui::SameLine(); - if (ImGui::Button("Save", !isIttIres)) { - auto ires = static_cast(origItPtr); - IresManager::instance->Save(ires); - } - - ImGui::SameLine(); - if (ImGui::Button("Reload", !isIttIres)) { - auto ires = static_cast(origItPtr); - IresManager::instance->Reload(ires); - } - - ImGui::SameLine(); - if (ImGui::Button("Rename", !isIttIres) || - (isIttIres && ImGui::IsKeyPressed(ImGuiKey_F2, false))) - { - auto ires = static_cast(origItPtr); - mInspector->renaming = true; - mInspector->renamingScratchBuffer = ires->GetName(); - } - - ImGui::SameLine(); - if (ImGui::Button("Delete", !isIttIres) || - (isIttIres && ImGui::IsKeyPressed(ImGuiKey_Delete, false))) - { - ImGui::OpenPopup("Delete Ires"); - } - bool openedDummy = true; - if (ImGui::BeginPopupModal("Delete Ires", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { - if (ImGui::Button("Confirm")) { - auto ires = static_cast(origItPtr); - IresManager::instance->Delete(ires); - } - ImGui::SameLine(); - if (ImGui::Button("Cancel")) { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - - auto& objects = IresManager::instance->GetObjects(); - for (auto it = objects.begin(); it != objects.end(); ++it) { - auto ires = it->second.Get(); - auto& name = ires->GetName(); - - bool selected = origItPtr == ires; - - switch (RenamableSelectable(name.c_str(), selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { - case RSA_Selected: { - mInspector->SelectTarget(EditorInspector::ITT_Ires, ires); - } break; - - case RSA_RenameCommitted: { - ires->SetName(std::move(mInspector->renamingScratchBuffer)); - } break; - - // Do nothing - case RSA_RenameCancelled: - case RSA_None: break; - } - if (!mInspector->renaming) { - if (ImGui::BeginDragDropSource()) { - auto kindName = Metadata::EnumToString(ires->GetKind()); - // Reason: intentionally using pointer as payload - ImGui::SetDragDropPayload(kindName.data(), &ires, sizeof(ires)); // NOLINT(bugprone-sizeof-expression) - ImGui::Text("%s '%s'", kindName.data(), name.c_str()); - ImGui::EndDragDropSource(); - } - } - } - } break; - - case P_Level: { - bool isIttLevel = origItt == EditorInspector::ITT_Level; - - if (ImGui::Button("New")) { - auto uid = Uid::Create(); - auto& ldObj = LevelManager::instance->AddLevel(uid); - mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); - mInspector->renaming = true; - mInspector->renamingScratchBuffer = ldObj.name; - } - - if (ImGui::Button("Save", !isIttLevel)) { - auto ldObj = static_cast(origItPtr); - LevelManager::instance->SaveLevel(ldObj->level->GetUid()); - } - - auto& objects = LevelManager::instance->mObjByUid; - for (auto it = objects.begin(); it != objects.end(); ++it) { - auto& uid = it->first; - auto& ldObj = it->second; - auto* level = ldObj.level.Get(); - bool selected = origItPtr == &ldObj; - const char* displayName = ldObj.name.c_str(); - if (strcmp(displayName, "") == 0) { - displayName = ""; - } - - switch (RenamableSelectable(displayName, selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { - case RSA_Selected: { - mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); - } break; - - case RSA_RenameCommitted: { - ldObj.name = std::move(mInspector->renamingScratchBuffer); - } break; - - // Do nothing - case RSA_RenameCancelled: - case RSA_None: break; - } - if (!mInspector->renaming) { - if (ImGui::BeginDragDropSource()) { - // Reason: intentionally using pointer as payload - ImGui::SetDragDropPayload(BRUSSEL_TAG_Level, &ldObj, sizeof(ldObj)); // NOLINT(bugprone-sizeof-expression) - ImGui::Text(BRUSSEL_Uid_FORMAT_STR, BRUSSEL_Uid_FORMAT_EXPAND(uid)); - ImGui::TextUnformatted(ldObj.name.c_str()); - ImGui::EndDragDropSource(); - } - } - } - } break; - } - ImGui::EndChild(); - - ImGui::End(); -} - -namespace ProjectBrussel_UNITY_ID { -glm::quat CalcQuaternionFromDegreesEulerAngle(glm::vec3 eulerAngleDegrees) { - glm::vec3 eulerAngleRadians; - eulerAngleRadians.x = eulerAngleDegrees.x / 180 * M_PI; - eulerAngleRadians.y = eulerAngleDegrees.y / 180 * M_PI; - eulerAngleRadians.z = eulerAngleDegrees.z / 180 * M_PI; - return glm::quat(eulerAngleRadians); -} - -void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) { - app->PushKeyCaptureCallback([=](int key, int action) { - // Allow the user to cancel by pressing Esc - if (key == GLFW_KEY_ESCAPE) { - return true; - } - - if (action == GLFW_PRESS) { - *writeKey = key; - *writeKeyStatus = writeKeyStatus; - return true; - } - return false; - }); -} - -struct GobjTreeNodeShowInfo { - EditorInstance* in_editor; - GameObject* out_openPopup = nullptr; -}; - -void GobjTreeNode(GobjTreeNodeShowInfo& showInfo, GameObject* object) { - auto& inspector = showInfo.in_editor->GetInspector(); - - auto attachment = object->GetEditorAttachment(); - if (!attachment) { - attachment = EaGameObject::Create(object).release(); - object->SetEditorAttachment(attachment); // NOTE: takes ownership - } - - ImGuiTreeNodeFlags flags = - ImGuiTreeNodeFlags_DefaultOpen | - ImGuiTreeNodeFlags_OpenOnDoubleClick | - ImGuiTreeNodeFlags_OpenOnArrow | - ImGuiTreeNodeFlags_SpanAvailWidth | - ImGuiTreeNodeFlags_NoTreePushOnOpen; - if (inspector.selectedItPtr == object) { - flags |= ImGuiTreeNodeFlags_Selected; - } - - ImGui::PushID(reinterpret_cast(object)); - // BEGIN tree node - - bool opened = ImGui::TreeNodeEx(attachment->name.c_str(), flags); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - inspector.SelectTarget(EditorInspector::ITT_GameObject, object); - } - if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - { - showInfo.out_openPopup = object; - } - - if (opened) { - ImGui::Indent(); - for (auto& child : object->GetChildren()) { - GobjTreeNode(showInfo, child); - } - ImGui::Unindent(); - } - - // END tree node - ImGui::PopID(); -}; - -#define GAMEOBJECT_CONSTRUCTOR(ClassName) [](GameWorld* world) -> GameObject* { return new ClassName(world); } -struct CreatableGameObject { - GameObject* (*factory)(GameWorld*); - const char* name; - GameObject::Kind kind; -} creatableGameObjects[] = { - { - .factory = GAMEOBJECT_CONSTRUCTOR(GameObject), - .name = "GameObject", - .kind = GameObject::KD_Generic, - }, - { - .factory = GAMEOBJECT_CONSTRUCTOR(SimpleGeometryObject), - .name = "Simple Geometry", - .kind = GameObject::KD_SimpleGeometry, - }, - { - .factory = GAMEOBJECT_CONSTRUCTOR(BuildingObject), - .name = "Building", - .kind = GameObject::KD_Building, - }, - { - .factory = GAMEOBJECT_CONSTRUCTOR(LevelWrapperObject), - .name = "Level Wrapper", - .kind = GameObject::KD_LevelWrapper, - }, -}; -#undef GAMEOBJECT_CONSTRUCTOR -} // namespace ProjectBrussel_UNITY_ID - -std::unique_ptr IEditor::CreateInstance(App* app) { - return std::make_unique(app); -} - -EditorInstance::EditorInstance(App* app) - : mApp{ app } - , mEdContentBrowser(&mEdInspector) { - mEditorCamera.name = "Editor Camera"s; - mEditorCamera.SetEyePos(glm::vec3(0, 0, 1)); - mEditorCamera.SetTargetDirection(glm::vec3(0, 0, -1)); - app->BindActiveCamera(&mEditorCamera); -} - -EditorInstance::~EditorInstance() { -} - -void EditorInstance::OnGameStateChanged(bool running) { - if (running) { - mApp->UnbindActiveCamera(); - } else { - mApp->BindActiveCamera(&mEditorCamera); - } -} - -void EditorInstance::Show() { - using namespace ProjectBrussel_UNITY_ID; - using namespace Tags; - - auto world = mApp->GetWorld(); - auto& io = ImGui::GetIO(); - - ImGui::BeginMainMenuBar(); - if (ImGui::BeginMenu("View")) { - ImGui::MenuItem("ImGui Demo", nullptr, &mWindowVisible_ImGuiDemo); - ImGui::MenuItem("Command Palette", "Ctrl+Shift+P", &mWindowVisible_CommandPalette); - ImGui::MenuItem("Inspector", nullptr, &mWindowVisible_Inspector); - ImGui::MenuItem("Content Browser", "Ctrl+Space", &mWindowVisible_ContentBrowser); - ImGui::MenuItem("World Structure", nullptr, &mWindowVisible_WorldStructure); - ImGui::MenuItem("World Properties", nullptr, &mWindowVisible_WorldProperties); - ImGui::EndMenu(); - } - ImGui::EndMainMenuBar(); - - if (mWindowVisible_ImGuiDemo) { - ImGui::ShowDemoWindow(&mWindowVisible_ImGuiDemo); - } - - if (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(GLFW_KEY_P, false)) { - mWindowVisible_CommandPalette = !mWindowVisible_CommandPalette; - } - if (mWindowVisible_CommandPalette) { - mEdCommandPalette.Show(&mWindowVisible_CommandPalette); - } - - if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) { - mWindowVisible_ContentBrowser = !mWindowVisible_ContentBrowser; - } - if (mWindowVisible_ContentBrowser) { - mEdContentBrowser.Show(&mWindowVisible_ContentBrowser); - } - - auto& camera = *mApp->GetActiveCamera(); - ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); - ImGuizmo::SetDrawlist(ImGui::GetBackgroundDrawList()); - - if (IsCurrentCameraEditor() && mEcm == ECM_Side3D) { - float viewManipulateRight = io.DisplaySize.x; - float viewManipulateTop = 0; - - // TODO get rid of this massive hack: how to manage const better for intuitively read-only, but write doesn't-care data? - auto& lastFrameInfo = const_cast(mApp->GetWorldRenderer()->GetLastFrameInfo()); - auto& view = lastFrameInfo.matrixView; - auto& proj = lastFrameInfo.matrixProj; - -#if 0 - ImGuizmo::ViewManipulate( - glm::value_ptr(view), - 200.0f, // TODO - ImVec2(viewManipulateRight - 128, viewManipulateTop), - ImVec2(128, 128), - 0x10101010); - // Extract eye and target position from view matrix - // - View matrix transforms world space to view space - // - Inverse view matrix should transform view space into world space - // - In view space, camera's pos is (0,0,0) and the look/forward vector should be (0,0,-1) - auto invView = glm::inverse(view); - camera.eye = invView * glm::vec4(0, 0, 0, 1); - camera.target = camera.eye + glm::vec3(invView * glm::vec4(0, 0, -1, 1)); -#endif - - // TODO draw this as a part of the world so it doesn't block objects -#if 0 - glm::mat4 identity(1.00f); - ImGuizmo::DrawGrid( - glm::value_ptr(view), - glm::value_ptr(proj), - glm::value_ptr(identity), - 100.f); -#endif - - { // Camera controls - auto cameraPos = camera.eye; - auto cameraForward = glm::normalize(camera.target - camera.eye); - // Always move on the horzontal flat plane - cameraForward.y = 0.0f; - - if (mMoveCamKeyboard) { - if (ImGui::IsKeyDown(ImGuiKey_W)) { - cameraPos += mMoveCamSlideSpeed * cameraForward; - } - if (ImGui::IsKeyDown(ImGuiKey_S)) { - auto cameraBack = glm::normalize(-cameraForward); - cameraPos += mMoveCamSlideSpeed * cameraBack; - } - if (ImGui::IsKeyDown(ImGuiKey_A)) { - auto cameraRight = glm::normalize(glm::cross(cameraForward, glm::vec3(0, 1, 0))); - auto cameraLeft = -cameraRight; - cameraPos += mMoveCamSlideSpeed * cameraLeft; - } - if (ImGui::IsKeyDown(ImGuiKey_D)) { - auto cameraRight = glm::normalize(glm::cross(cameraForward, glm::vec3(0, 1, 0))); - cameraPos += mMoveCamSlideSpeed * cameraRight; - } - if (ImGui::IsKeyDown(ImGuiKey_Space)) { - cameraPos.y += mMoveCamSlideSpeed; - } - if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { - cameraPos.y -= mMoveCamSlideSpeed; - } - } - - if (mMoveCamScrollWheel) { - cameraPos += cameraForward * io.MouseWheel * mMoveCamScrollSpeed; - } - - camera.SetEyePos(cameraPos); - } - } else { - { // Camera controls - auto cameraPos = camera.eye; - - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !io.WantCaptureMouse && !mDragCam_Happening) { - mDragCam_CamInitial = camera.eye; - mDragCam_CursorInitial = ImGui::GetMousePos(); - mDragCam_Happening = true; - } - if (mDragCam_Happening) { - auto newPos = ImGui::GetMousePos(); - // NOTE: we are emulating as if the mouse is dragging the "canvas", through moving the camera in the opposite direction of the natural position delta - float deltaX = mDragCam_CursorInitial.x - newPos.x; - cameraPos.x = mDragCam_CamInitial.x + deltaX / camera.pixelsPerMeter; - float deltaY = -(mDragCam_CursorInitial.y - newPos.y); // Invert Y delta because ImGui uses top-left origin (mouse moving down translates to positive value, but in our coordinate system down is negative) - cameraPos.y = mDragCam_CamInitial.y + deltaY / camera.pixelsPerMeter; - } - if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { - mDragCam_Happening = false; - } - - if (mMoveCamKeyboard) { - if (ImGui::IsKeyDown(ImGuiKey_W)) { - cameraPos.y += mMoveCamSlideSpeed; - } - if (ImGui::IsKeyDown(ImGuiKey_S)) { - cameraPos.y -= mMoveCamSlideSpeed; - } - if (ImGui::IsKeyDown(ImGuiKey_A)) { - cameraPos.x -= mMoveCamSlideSpeed; - } - if (ImGui::IsKeyDown(ImGuiKey_D)) { - cameraPos.x += mMoveCamSlideSpeed; - } - } - - if (mMoveCamScrollWheel) { - cameraPos.z = std::clamp(cameraPos.z + io.MouseWheel, 0.1f, 100.0f); - } - - camera.SetEyePos(cameraPos); - } - } - - if (mWindowVisible_Inspector) { - ImGui::Begin("Inspector"); - switch (mEdInspector.selectedItt) { - case EditorInspector::ITT_GameObject: { - auto object = static_cast(mEdInspector.selectedItPtr); - ShowInspector(object); - } break; - - case EditorInspector::ITT_Ires: { - auto ires = static_cast(mEdInspector.selectedItPtr); - ShowInspector(ires); - } break; - - case EditorInspector::ITT_Level: { - auto ldObj = static_cast(mEdInspector.selectedItPtr); - ShowInspector(ldObj); - } break; - - case EditorInspector::ITT_None: break; - } - ImGui::End(); - } - - if (mWindowVisible_WorldProperties) { - ImGui::Begin("World properties"); - ShowWorldProperties(); - ImGui::End(); - } - - if (mWindowVisible_WorldStructure) { - ImGui::Begin("World structure"); - GobjTreeNodeShowInfo showInfo{ - .in_editor = this, - }; - GobjTreeNode(showInfo, &world->GetRoot()); - - if (showInfo.out_openPopup) { - mPopupCurrent_GameObject = showInfo.out_openPopup; - - ImGui::OpenPopup("GameObject Popup"); - ImGui::SetNextWindowPos(ImGui::GetMousePos()); - } - if (ImGui::BeginPopup("GameObject Popup")) { - // Target no longer selected during popup open - if (!mPopupCurrent_GameObject) { - ImGui::CloseCurrentPopup(); - } - - if (ImGui::BeginMenu("Add child")) { - for (size_t i = 0; i < std::size(creatableGameObjects); ++i) { - auto& info = creatableGameObjects[i]; - if (ImGui::MenuItem(info.name)) { - auto object = info.factory(world); - mPopupCurrent_GameObject->AddChild(object); - } - } - ImGui::EndMenu(); - } - ImGui::Separator(); - if (ImGui::MenuItem("Remove")) { - ImGui::DialogConfirmation("Are you sure you want to delete this GameObject?", [object = mPopupCurrent_GameObject](bool yes) { - object->RemoveSelfFromParent(); - delete object; - }); - } - ImGui::EndPopup(); - } - ImGui::End(); - } - - ShowSpriteViewer(); - - ImGui::ShowDialogs(); - ImGui::ShowNotifications(); -} - -void EditorInstance::ShowWorldProperties() { - if (mApp->IsGameRunning()) { - if (ImGui::Button("Pause")) { - mApp->SetGameRunning(false); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("The game is currently running. Click to pause."); - } - } else { - if (ImGui::Button("Play")) { - mApp->SetGameRunning(true); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("The game is currently paused. Click to run."); - } - } - - auto& camera = *mApp->GetActiveCamera(); - - // vvv Camera settings (per instance) - ImGui::TextUnformatted("Active camera:"); - ImGui::Indent(); - - ImGui::TextUnformatted(camera.name.c_str()); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Object at <%p>", &camera); - } - - ImGui::InputFloat3("Eye", glm::value_ptr(camera.eye)); - if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { - ImGui::OpenPopup("##CTXMENU"); - } - ImGui::InputFloat3("Target", glm::value_ptr(camera.target)); - if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { - ImGui::OpenPopup("##CTXMENU"); - } - if (ImGui::BeginPopup("##CTXMENU", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) { - if (ImGui::MenuItem("Reset to origin")) { - camera.SetEyePos(glm::vec3(1.0f, 1.0f, 1.0f)); - } - if (ImGui::MenuItem("Reset completely")) { - camera.eye = glm::vec3(0, 0, 1); - camera.target = glm::vec3(0, 0, 0); - } - ImGui::EndPopup(); - } - - if (IsCurrentCameraEditor()) { - const char* preview; - switch (mEcm) { - case ECM_2D: preview = "2D view"; break; - case ECM_Side3D: preview = "Side 3D view"; break; - } - if (ImGui::BeginCombo("Mode", preview)) { - if (ImGui::Selectable("2D view", mEcm == ECM_2D)) { - if (mEcm != ECM_2D) { - mEcm = ECM_2D; - - // TODO project eye to world plane - - camera.SetHasPerspective(false); - } - } - if (ImGui::Selectable("Side 3D view", mEcm == ECM_Side3D, ImGuiSelectableFlags_None)) { - if (mEcm != ECM_Side3D) { - mEcm = ECM_Side3D; - - auto origEye = camera.eye; - auto origTarget = camera.target; - - // New setup: focus on the point of world plane that we were originally "hovering above" - camera.target = origEye; - camera.target.z = 0.0f; - - // New setup: move the eye back at an angle - camera.eye = camera.target; - camera.eye.x += 4.0f * std::cos(60.0f); - camera.eye.y += 0.0f; - camera.eye.z += 4.0f * std::sin(60.0f); - - camera.SetHasPerspective(true); - } - } - ImGui::EndCombo(); - } - } - - ImGui::Checkbox("Perspective", &camera.perspective); - if (camera.perspective) { - float fovDegress = camera.fov / M_PI * 180.0f; - if (ImGui::SliderFloat("FOV", &fovDegress, 1.0f, 180.0f)) { - camera.fov = fovDegress / 180.0f * M_PI; - } - } else { - if (ImGui::InputFloat("Pixels per meter", &camera.pixelsPerMeter)) { - camera.pixelsPerMeter = std::max(camera.pixelsPerMeter, 0.0f); - } - } - - ImGui::Unindent(); - // ^^^ Camera settings (per instance) - // vvv Camera control settings - ImGui::TextUnformatted("Camera controls:"); - ImGui::Indent(); - - ImGui::Checkbox("Move camera with WASD", &mMoveCamKeyboard); - ImGui::SliderFloat("Slide speed", &mMoveCamSlideSpeed, 0.1f, 10.0f); - - ImGui::Checkbox("Move camera with scoll wheel", &mMoveCamScrollWheel); - ImGui::SliderFloat("Scroll speed", &mMoveCamScrollSpeed, 0.01, 10.0f); - - ImGui::Unindent(); - // ^^^ Camera control settings -} - -// TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism - -void EditorInstance::ShowInspector(IresObject* ires) { - ires->ShowEditor(*this); -} - -void EditorInstance::ShowInspector(LevelManager::LoadableObject* ldObj) { - using namespace Tags; - using namespace ProjectBrussel_UNITY_ID; - - ImGui::InputText("Name", &ldObj->name); - ImGui::InputTextMultiline("Desciption", &ldObj->description); - - if (ImGui::CollapsingHeader("Instanciation Entries")) { - ldObj->level->ShowInstanciationEntries(*this); - } -} - -void EditorInstance::ShowInspector(GameObject* object) { - using namespace Tags; - using namespace ProjectBrussel_UNITY_ID; - - auto& io = ImGui::GetIO(); - - auto objectEa = static_cast(object->GetEditorAttachment()); - - auto ShowInspector = [&](/*array[6]*/ float* bounds = nullptr) { - glm::mat4 identityMatrix(1.00f); - - if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_T)) - mGuizmo.currOperation = ImGuizmo::TRANSLATE; - if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_R)) - mGuizmo.currOperation = ImGuizmo::ROTATE; - if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_S)) - mGuizmo.currOperation = ImGuizmo::SCALE; - if (ImGui::RadioButton("Translate", mGuizmo.currOperation == ImGuizmo::TRANSLATE)) - mGuizmo.currOperation = ImGuizmo::TRANSLATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Rotate", mGuizmo.currOperation == ImGuizmo::ROTATE)) - mGuizmo.currOperation = ImGuizmo::ROTATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Scale", mGuizmo.currOperation == ImGuizmo::SCALE)) - mGuizmo.currOperation = ImGuizmo::SCALE; - ImGui::SameLine(); - if (ImGui::RadioButton("Universal", mGuizmo.currOperation == ImGuizmo::UNIVERSAL)) - mGuizmo.currOperation = ImGuizmo::UNIVERSAL; - - if (mGuizmo.currOperation != ImGuizmo::SCALE) { - if (ImGui::RadioButton("Local", mGuizmo.currMode == ImGuizmo::LOCAL)) - mGuizmo.currMode = ImGuizmo::LOCAL; - ImGui::SameLine(); - if (ImGui::RadioButton("World", mGuizmo.currMode == ImGuizmo::WORLD)) - mGuizmo.currMode = ImGuizmo::WORLD; - } - - ImGui::Checkbox("##UseSnap", &mGuizmo.useSnap); - ImGui::SameLine(); - switch (mGuizmo.currOperation) { - case ImGuizmo::TRANSLATE: - ImGui::InputFloat3("Pos Snap", &mGuizmo.snap[0]); - break; - case ImGuizmo::ROTATE: - ImGui::InputFloat("Angle Snap", &mGuizmo.snap[0]); - break; - case ImGuizmo::SCALE: - ImGui::InputFloat("Scale Snap", &mGuizmo.snap[0]); - break; - default: break; - } - - if (bounds) { - ImGui::Checkbox("Bound Sizing", &mGuizmo.boundSizing); - if (mGuizmo.boundSizing) { - ImGui::PushID(3); - ImGui::Checkbox("##BoundSizing", &mGuizmo.boundSizingSnap); - ImGui::SameLine(); - ImGui::InputFloat3("Bound Snap", mGuizmo.boundsSnap); - ImGui::PopID(); - } - } - - ImGui::Separator(); - - auto objectPos = object->GetPos(); - if (ImGui::InputFloat3("Translation", &objectPos.x)) { - object->SetPos(objectPos); - } - - auto objectRot = object->GetRotation(); - if (ImGui::InputFloat3("Rotation", &objectEa->eulerAnglesRotation.x)) { - objectRot = CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation); - object->SetRotation(objectRot); - } - - auto objectScale = object->GetScale(); - if (ImGui::InputFloat3("Scale", &objectScale.x)) { - object->SetScale(objectScale); - } - }; - - auto ShowGuizmo = [&](/*array[6]*/ float* bounds = nullptr) { - glm::mat4 identityMatrix(1.00f); - - auto& lastFrameInfo = mApp->GetWorldRenderer()->GetLastFrameInfo(); - auto& cameraViewMat = lastFrameInfo.matrixView; - const float* cameraView = &cameraViewMat[0][0]; - auto& cameraProjMat = lastFrameInfo.matrixProj; - const float* cameraProj = &cameraProjMat[0][0]; - - auto objectPos = object->GetPos(); - auto objectScale = object->GetScale(); - - float matrix[16]; - ImGuizmo::RecomposeMatrixFromComponents(&objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x, matrix); - - bool edited = ImGuizmo::Manipulate( - cameraView, - cameraProj, - mGuizmo.currOperation, - mGuizmo.currMode, - matrix, - nullptr, - mGuizmo.useSnap ? mGuizmo.snap : nullptr, - mGuizmo.boundSizing ? bounds : nullptr, - (bounds && mGuizmo.boundSizingSnap) ? mGuizmo.boundsSnap : nullptr); - - if (edited) { - ImGuizmo::DecomposeMatrixToComponents(matrix, &objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x); - object->SetPos(objectPos); - object->SetRotation(CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation)); - object->SetScale(objectScale); - } - }; - - auto type = object->GetKind(); - switch (type) { - case GameObject::KD_Player: { - ShowInspector(); - ShowGuizmo(); - ImGui::Separator(); - - auto player = static_cast(object); - auto ea = static_cast(objectEa); - auto& kb = player->keybinds; - - ImGui::Text("Player #%d", player->GetId()); - - ImGui::TextUnformatted("Spritesheet: "); - ImGui::SameLine(); - IresObject::ShowReferenceSafe(*this, ea->confSprite.Get()); - if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Spritesheet).data())) { - auto spritesheet = *static_cast(payload->Data); - ea->confSprite.Attach(spritesheet); - auto def = spritesheet->GetInstance(); - player->sprite.SetDefinition(def); - player->renderObject.autofill_TextureAtlas.Attach(def->GetAtlas()); - } - ImGui::EndDragDropTarget(); - } - - ImGui::TextUnformatted("Material: "); - ImGui::SameLine(); - IresObject::ShowReferenceSafe(*this, ea->confMaterial.Get()); - if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Material).data())) { - auto material = *static_cast(payload->Data); - ea->confMaterial.Attach(material); - player->SetMaterial(material->GetInstance()); - } - ImGui::EndDragDropTarget(); - } - - if (ImGui::Button("Load config")) { - bool success = player->LoadFromFile(); - if (success) { - ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully loaded player config")); - } - } - ImGui::SameLine(); - if (ImGui::Button("Save config")) { - bool success = player->SaveToFile(); - if (success) { - ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully saved player config")); - } - } - - ImGui::Text("Move left (%s)", ImGui::GetKeyNameGlfw(kb.keyLeft)); - ImGui::SameLine(); - if (ImGui::Button("Change##Move left")) { - PushKeyCodeRecorder(mApp, &kb.keyLeft, &kb.pressedLeft); - } - - ImGui::Text("Move right (%s)", ImGui::GetKeyNameGlfw(kb.keyRight)); - ImGui::SameLine(); - if (ImGui::Button("Change##Move right")) { - PushKeyCodeRecorder(mApp, &kb.keyRight, &kb.pressedRight); - } - - ImGui::Text("Jump (%s)", ImGui::GetKeyNameGlfw(kb.keyJump)); - ImGui::SameLine(); - if (ImGui::Button("Change##Jump")) { - PushKeyCodeRecorder(mApp, &kb.keyJump, &kb.pressedJump); - } - - ImGui::Text("Attack (%s)", ImGui::GetKeyNameGlfw(kb.keyAttack)); - ImGui::SameLine(); - if (ImGui::Button("Change##Attack")) { - PushKeyCodeRecorder(mApp, &kb.keyAttack, &kb.pressedAttack); - } - } break; - - case GameObject::KD_SimpleGeometry: { - auto sg = static_cast(object); - - float bounds[6] = { - /*x1*/ -sg->GetSize().x / 2.0f, - /*y1*/ -sg->GetSize().y / 2.0f, - /*z1*/ -sg->GetSize().z / 2.0f, - /*x2*/ +sg->GetSize().x / 2.0f, - /*y2*/ +sg->GetSize().y / 2.0f, - /*z2*/ +sg->GetSize().z / 2.0f, - }; - - ShowInspector(bounds); - ShowGuizmo(bounds); - ImGui::Separator(); - - sg->SetSize(glm::vec3(bounds[3] - bounds[0], bounds[4] - bounds[1], bounds[5] - bounds[2])); - - auto size = sg->GetSize(); - if (ImGui::InputFloat3("Size", &size.x)) { - sg->SetSize(size); - } - - auto xFaceColor = sg->GetXFaceColor(); - if (ImGui::ColorEdit4("X color", &xFaceColor)) { - sg->SetXFaceColor(xFaceColor); - } - auto yFaceColor = sg->GetYFaceColor(); - if (ImGui::ColorEdit4("Y color", &yFaceColor)) { - sg->SetYFaceColor(yFaceColor); - } - auto zFaceColor = sg->GetZFaceColor(); - if (ImGui::ColorEdit4("Z color", &zFaceColor)) { - sg->SetZFaceColor(zFaceColor); - } - - if (ImGui::Button("Sync from X color")) { - sg->SetYFaceColor(xFaceColor); - sg->SetZFaceColor(xFaceColor); - } - ImGui::SameLine(); - if (ImGui::Button("Reset")) { - sg->SetXFaceColor(kXAxisColor); - sg->SetYFaceColor(kYAxisColor); - sg->SetZFaceColor(kZAxisColor); - } - } break; - - case GameObject::KD_Building: { - ShowInspector(); - ShowGuizmo(); - ImGui::Separator(); - - auto b = static_cast(object); - // TODO - } break; - - case GameObject::KD_LevelWrapper: { - ShowInspector(); - ShowGuizmo(); - ImGui::Separator(); - - auto lwo = static_cast(object); - // TODO - } break; - - default: { - ShowInspector(); - ShowGuizmo(); - } break; - } -} - -void EditorInstance::OpenSpriteViewer(SpriteDefinition* sprite) { - mSpriteView_Instance.Attach(sprite); - mSpriteView_Frame = 0; - mSpriteView_OpenNextFrame = true; -} - -void EditorInstance::ShowSpriteViewer() { - if (mSpriteView_Instance == nullptr) return; - if (!mSpriteView_Instance->IsValid()) return; - - if (mSpriteView_OpenNextFrame) { - mSpriteView_OpenNextFrame = false; - ImGui::OpenPopup("Sprite Viewer"); - } - - bool windowOpen = true; - if (ImGui::BeginPopupModal("Sprite Viewer", &windowOpen)) { - auto atlas = mSpriteView_Instance->GetAtlas(); - auto atlasSize = atlas->GetInfo().size; - auto& frames = mSpriteView_Instance->GetFrames(); - - int frameCount = mSpriteView_Instance->GetFrames().size(); - if (ImGui::Button("<")) { - --mSpriteView_Frame; - } - ImGui::SameLine(); - ImGui::Text("%d/%d", mSpriteView_Frame + 1, frameCount); - ImGui::SameLine(); - if (ImGui::Button(">")) { - ++mSpriteView_Frame; - } - // Scrolling down (negative value) should advance, so invert the sign - mSpriteView_Frame += -std::round(ImGui::GetIO().MouseWheel); - // Clamp mSpriteView_Frame to range [0, frameCount) - if (mSpriteView_Frame < 0) { - mSpriteView_Frame = frameCount - 1; - } else if (mSpriteView_Frame >= frameCount) { - mSpriteView_Frame = 0; - } - - auto& currFrame = frames[mSpriteView_Frame]; - auto boundingBox = mSpriteView_Instance->GetBoundingBox(); - ImGui::Text("Frame size: (%d, %d)", boundingBox.x, boundingBox.y); - ImGui::Text("Frame location: (%f, %f) to (%f, %f)", currFrame.u0, currFrame.v0, currFrame.u1, currFrame.v1); - ImGui::Image( - (ImTextureID)(uintptr_t)atlas->GetHandle(), - Utils::FitImage(atlasSize), - ImVec2(currFrame.u0, currFrame.v0), - ImVec2(currFrame.u1, currFrame.v1)); - - ImGui::EndPopup(); - } -} diff --git a/source/30-game/EditorCorePrivate.hpp b/source/30-game/EditorCorePrivate.hpp deleted file mode 100644 index 4fbfb72..0000000 --- a/source/30-game/EditorCorePrivate.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once - -#include "EditorCore.hpp" - -#include "App.hpp" -#include "EditorAttachment.hpp" -#include "EditorCommandPalette.hpp" -#include "EditorUtils.hpp" -#include "GameObject.hpp" -#include "Ires.hpp" -#include "Level.hpp" -#include "RcPtr.hpp" -#include "Sprite.hpp" -#include "World.hpp" - -#include -#include - -// 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; - - // - 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 mSpriteView_Instance; - EditorCommandPalette mEdCommandPalette; - EditorInspector mEdInspector; - EditorContentBrowser mEdContentBrowser; - 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 mDragCam_Happening = false; - bool mMoveCamKeyboard = false; - bool mMoveCamScrollWheel = 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/EditorGuizmo.cpp b/source/30-game/EditorGuizmo.cpp deleted file mode 100644 index 3e4f890..0000000 --- a/source/30-game/EditorGuizmo.cpp +++ /dev/null @@ -1,2897 +0,0 @@ -// https://github.com/CedricGuillemet/ImGuizmo -// v 1.84 WIP -// -// The MIT License(MIT) -// -// Copyright(c) 2021 Cedric Guillemet -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -#ifndef IMGUI_DEFINE_MATH_OPERATORS -# define IMGUI_DEFINE_MATH_OPERATORS -#endif -#include "EditorGuizmo.hpp" -#include "imgui_internal.h" - -#if defined(_MSC_VER) || defined(__MINGW32__) -# include -#endif -#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) -# define _malloca(x) alloca(x) -# define _freea(x) -#endif - -// includes patches for multiview from -// https://github.com/CedricGuillemet/ImGuizmo/issues/15 - -namespace IMGUIZMO_NAMESPACE { -static const float ZPI = 3.14159265358979323846f; -static const float RAD2DEG = (180.f / ZPI); -static const float DEG2RAD = (ZPI / 180.f); -const float screenRotateSize = 0.06f; -// scale a bit so translate axis do not touch when in universal -const float rotationDisplayFactor = 1.2f; - -static OPERATION operator&(OPERATION lhs, OPERATION rhs) { - return static_cast(static_cast(lhs) & static_cast(rhs)); -} - -static bool operator!=(OPERATION lhs, int rhs) { - return static_cast(lhs) != rhs; -} - -static bool Intersects(OPERATION lhs, OPERATION rhs) { - return (lhs & rhs) != 0; -} - -// True if lhs contains rhs -static bool Contains(OPERATION lhs, OPERATION rhs) { - return (lhs & rhs) == rhs; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// utility and math - -void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) { - r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; - r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; - r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; - r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; - - r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; - r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; - r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; - r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; - - r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; - r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; - r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; - r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; - - r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; - r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; - r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; - r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; -} - -void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) { - float temp, temp2, temp3, temp4; - temp = 2.0f * znear; - temp2 = right - left; - temp3 = top - bottom; - temp4 = zfar - znear; - m16[0] = temp / temp2; - m16[1] = 0.0; - m16[2] = 0.0; - m16[3] = 0.0; - m16[4] = 0.0; - m16[5] = temp / temp3; - m16[6] = 0.0; - m16[7] = 0.0; - m16[8] = (right + left) / temp2; - m16[9] = (top + bottom) / temp3; - m16[10] = (-zfar - znear) / temp4; - m16[11] = -1.0f; - m16[12] = 0.0; - m16[13] = 0.0; - m16[14] = (-temp * zfar) / temp4; - m16[15] = 0.0; -} - -void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) { - float ymax, xmax; - ymax = znear * tanf(fovyInDegrees * DEG2RAD); - xmax = ymax * aspectRatio; - Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); -} - -void Cross(const float* a, const float* b, float* r) { - r[0] = a[1] * b[2] - a[2] * b[1]; - r[1] = a[2] * b[0] - a[0] * b[2]; - r[2] = a[0] * b[1] - a[1] * b[0]; -} - -float Dot(const float* a, const float* b) { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; -} - -void Normalize(const float* a, float* r) { - float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); - r[0] = a[0] * il; - r[1] = a[1] * il; - r[2] = a[2] * il; -} - -void LookAt(const float* eye, const float* at, const float* up, float* m16) { - float X[3], Y[3], Z[3], tmp[3]; - - tmp[0] = eye[0] - at[0]; - tmp[1] = eye[1] - at[1]; - tmp[2] = eye[2] - at[2]; - Normalize(tmp, Z); - Normalize(up, Y); - Cross(Y, Z, tmp); - Normalize(tmp, X); - Cross(Z, X, tmp); - Normalize(tmp, Y); - - m16[0] = X[0]; - m16[1] = Y[0]; - m16[2] = Z[0]; - m16[3] = 0.0f; - m16[4] = X[1]; - m16[5] = Y[1]; - m16[6] = Z[1]; - m16[7] = 0.0f; - m16[8] = X[2]; - m16[9] = Y[2]; - m16[10] = Z[2]; - m16[11] = 0.0f; - m16[12] = -Dot(X, eye); - m16[13] = -Dot(Y, eye); - m16[14] = -Dot(Z, eye); - m16[15] = 1.0f; -} - -template -T Clamp(T x, T y, T z) { - return ((x < y) ? y : ((x > z) ? z : x)); -} -template -T max(T x, T y) { - return (x > y) ? x : y; -} -template -T min(T x, T y) { - return (x < y) ? x : y; -} -template -bool IsWithin(T x, T y, T z) { - return (x >= y) && (x <= z); -} - -struct matrix_t; -struct vec_t { -public: - float x, y, z, w; - - void Lerp(const vec_t& v, float t) { - x += (v.x - x) * t; - y += (v.y - y) * t; - z += (v.z - z) * t; - w += (v.w - w) * t; - } - - void Set(float v) { x = y = z = w = v; } - void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { - x = _x; - y = _y; - z = _z; - w = _w; - } - - vec_t& operator-=(const vec_t& v) { - x -= v.x; - y -= v.y; - z -= v.z; - w -= v.w; - return *this; - } - vec_t& operator+=(const vec_t& v) { - x += v.x; - y += v.y; - z += v.z; - w += v.w; - return *this; - } - vec_t& operator*=(const vec_t& v) { - x *= v.x; - y *= v.y; - z *= v.z; - w *= v.w; - return *this; - } - vec_t& operator*=(float v) { - x *= v; - y *= v; - z *= v; - w *= v; - return *this; - } - - vec_t operator*(float f) const; - vec_t operator-() const; - vec_t operator-(const vec_t& v) const; - vec_t operator+(const vec_t& v) const; - vec_t operator*(const vec_t& v) const; - - const vec_t& operator+() const { return (*this); } - float Length() const { return sqrtf(x * x + y * y + z * z); }; - float LengthSq() const { return (x * x + y * y + z * z); }; - vec_t Normalize() { - (*this) *= (1.f / (Length() > FLT_EPSILON ? Length() : FLT_EPSILON)); - return (*this); - } - vec_t Normalize(const vec_t& v) { - this->Set(v.x, v.y, v.z, v.w); - this->Normalize(); - return (*this); - } - vec_t Abs() const; - - void Cross(const vec_t& v) { - vec_t res; - res.x = y * v.z - z * v.y; - res.y = z * v.x - x * v.z; - res.z = x * v.y - y * v.x; - - x = res.x; - y = res.y; - z = res.z; - w = 0.f; - } - - void Cross(const vec_t& v1, const vec_t& v2) { - x = v1.y * v2.z - v1.z * v2.y; - y = v1.z * v2.x - v1.x * v2.z; - z = v1.x * v2.y - v1.y * v2.x; - w = 0.f; - } - - float Dot(const vec_t& v) const { - return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); - } - - float Dot3(const vec_t& v) const { - return (x * v.x) + (y * v.y) + (z * v.z); - } - - void Transform(const matrix_t& matrix); - void Transform(const vec_t& s, const matrix_t& matrix); - - void TransformVector(const matrix_t& matrix); - void TransformPoint(const matrix_t& matrix); - void TransformVector(const vec_t& v, const matrix_t& matrix) { - (*this) = v; - this->TransformVector(matrix); - } - void TransformPoint(const vec_t& v, const matrix_t& matrix) { - (*this) = v; - this->TransformPoint(matrix); - } - - float& operator[](size_t index) { return ((float*)&x)[index]; } - const float& operator[](size_t index) const { return ((float*)&x)[index]; } - bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)); } -}; - -vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { - vec_t res; - res.x = _x; - res.y = _y; - res.z = _z; - res.w = _w; - return res; -} -vec_t makeVect(ImVec2 v) { - vec_t res; - res.x = v.x; - res.y = v.y; - res.z = 0.f; - res.w = 0.f; - return res; -} -vec_t vec_t::operator*(float f) const { - return makeVect(x * f, y * f, z * f, w * f); -} -vec_t vec_t::operator-() const { - return makeVect(-x, -y, -z, -w); -} -vec_t vec_t::operator-(const vec_t& v) const { - return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); -} -vec_t vec_t::operator+(const vec_t& v) const { - return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); -} -vec_t vec_t::operator*(const vec_t& v) const { - return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); -} -vec_t vec_t::Abs() const { - return makeVect(fabsf(x), fabsf(y), fabsf(z)); -} - -vec_t Normalized(const vec_t& v) { - vec_t res; - res = v; - res.Normalize(); - return res; -} -vec_t Cross(const vec_t& v1, const vec_t& v2) { - vec_t res; - res.x = v1.y * v2.z - v1.z * v2.y; - res.y = v1.z * v2.x - v1.x * v2.z; - res.z = v1.x * v2.y - v1.y * v2.x; - res.w = 0.f; - return res; -} - -float Dot(const vec_t& v1, const vec_t& v2) { - return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); -} - -vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) { - vec_t normal, res; - normal.Normalize(p_normal); - res.w = normal.Dot(p_point1); - res.x = normal.x; - res.y = normal.y; - res.z = normal.z; - return res; -} - -struct matrix_t { -public: - union { - float m[4][4]; - float m16[16]; - struct - { - vec_t right, up, dir, position; - } v; - vec_t component[4]; - }; - - operator float*() { return m16; } - operator const float*() const { return m16; } - void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } - - void Translation(const vec_t& vt) { - v.right.Set(1.f, 0.f, 0.f, 0.f); - v.up.Set(0.f, 1.f, 0.f, 0.f); - v.dir.Set(0.f, 0.f, 1.f, 0.f); - v.position.Set(vt.x, vt.y, vt.z, 1.f); - } - - void Scale(float _x, float _y, float _z) { - v.right.Set(_x, 0.f, 0.f, 0.f); - v.up.Set(0.f, _y, 0.f, 0.f); - v.dir.Set(0.f, 0.f, _z, 0.f); - v.position.Set(0.f, 0.f, 0.f, 1.f); - } - void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } - - matrix_t& operator*=(const matrix_t& mat) { - matrix_t tmpMat; - tmpMat = *this; - tmpMat.Multiply(mat); - *this = tmpMat; - return *this; - } - matrix_t operator*(const matrix_t& mat) const { - matrix_t matT; - matT.Multiply(*this, mat); - return matT; - } - - void Multiply(const matrix_t& matrix) { - matrix_t tmp; - tmp = *this; - - FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); - } - - void Multiply(const matrix_t& m1, const matrix_t& m2) { - FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); - } - - float GetDeterminant() const { - return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - - m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; - } - - float Inverse(const matrix_t& srcMatrix, bool affine = false); - void SetToIdentity() { - v.right.Set(1.f, 0.f, 0.f, 0.f); - v.up.Set(0.f, 1.f, 0.f, 0.f); - v.dir.Set(0.f, 0.f, 1.f, 0.f); - v.position.Set(0.f, 0.f, 0.f, 1.f); - } - void Transpose() { - matrix_t tmpm; - for (int l = 0; l < 4; l++) - { - for (int c = 0; c < 4; c++) - { - tmpm.m[l][c] = m[c][l]; - } - } - (*this) = tmpm; - } - - void RotationAxis(const vec_t& axis, float angle); - - void OrthoNormalize() { - v.right.Normalize(); - v.up.Normalize(); - v.dir.Normalize(); - } -}; - -void vec_t::Transform(const matrix_t& matrix) { - vec_t out; - - out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; - out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; - out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; - out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; - - x = out.x; - y = out.y; - z = out.z; - w = out.w; -} - -void vec_t::Transform(const vec_t& s, const matrix_t& matrix) { - *this = s; - Transform(matrix); -} - -void vec_t::TransformPoint(const matrix_t& matrix) { - vec_t out; - - out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; - out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; - out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; - out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; - - x = out.x; - y = out.y; - z = out.z; - w = out.w; -} - -void vec_t::TransformVector(const matrix_t& matrix) { - vec_t out; - - out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; - out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; - out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; - out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; - - x = out.x; - y = out.y; - z = out.z; - w = out.w; -} - -float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) { - float det = 0; - - if (affine) - { - det = GetDeterminant(); - float s = 1 / det; - m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; - m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; - m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; - m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; - m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; - m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; - m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; - m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; - m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; - m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); - m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); - m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); - } else - { - // transpose matrix - float src[16]; - for (int i = 0; i < 4; ++i) - { - src[i] = srcMatrix.m16[i * 4]; - src[i + 4] = srcMatrix.m16[i * 4 + 1]; - src[i + 8] = srcMatrix.m16[i * 4 + 2]; - src[i + 12] = srcMatrix.m16[i * 4 + 3]; - } - - // calculate pairs for first 8 elements (cofactors) - float tmp[12]; // temp array for pairs - tmp[0] = src[10] * src[15]; - tmp[1] = src[11] * src[14]; - tmp[2] = src[9] * src[15]; - tmp[3] = src[11] * src[13]; - tmp[4] = src[9] * src[14]; - tmp[5] = src[10] * src[13]; - tmp[6] = src[8] * src[15]; - tmp[7] = src[11] * src[12]; - tmp[8] = src[8] * src[14]; - tmp[9] = src[10] * src[12]; - tmp[10] = src[8] * src[13]; - tmp[11] = src[9] * src[12]; - - // calculate first 8 elements (cofactors) - m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); - m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); - m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); - m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); - m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); - m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); - m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); - m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); - - // calculate pairs for second 8 elements (cofactors) - tmp[0] = src[2] * src[7]; - tmp[1] = src[3] * src[6]; - tmp[2] = src[1] * src[7]; - tmp[3] = src[3] * src[5]; - tmp[4] = src[1] * src[6]; - tmp[5] = src[2] * src[5]; - tmp[6] = src[0] * src[7]; - tmp[7] = src[3] * src[4]; - tmp[8] = src[0] * src[6]; - tmp[9] = src[2] * src[4]; - tmp[10] = src[0] * src[5]; - tmp[11] = src[1] * src[4]; - - // calculate second 8 elements (cofactors) - m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); - m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); - m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); - m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); - m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); - m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); - m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); - m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); - - // calculate determinant - det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; - - // calculate matrix inverse - float invdet = 1 / det; - for (int j = 0; j < 16; ++j) - { - m16[j] *= invdet; - } - } - - return det; -} - -void matrix_t::RotationAxis(const vec_t& axis, float angle) { - float length2 = axis.LengthSq(); - if (length2 < FLT_EPSILON) - { - SetToIdentity(); - return; - } - - vec_t n = axis * (1.f / sqrtf(length2)); - float s = sinf(angle); - float c = cosf(angle); - float k = 1.f - c; - - float xx = n.x * n.x * k + c; - float yy = n.y * n.y * k + c; - float zz = n.z * n.z * k + c; - float xy = n.x * n.y * k; - float yz = n.y * n.z * k; - float zx = n.z * n.x * k; - float xs = n.x * s; - float ys = n.y * s; - float zs = n.z * s; - - m[0][0] = xx; - m[0][1] = xy + zs; - m[0][2] = zx - ys; - m[0][3] = 0.f; - m[1][0] = xy - zs; - m[1][1] = yy; - m[1][2] = yz + xs; - m[1][3] = 0.f; - m[2][0] = zx + ys; - m[2][1] = yz - xs; - m[2][2] = zz; - m[2][3] = 0.f; - m[3][0] = 0.f; - m[3][1] = 0.f; - m[3][2] = 0.f; - m[3][3] = 1.f; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// - -enum MOVETYPE { - MT_NONE, - MT_MOVE_X, - MT_MOVE_Y, - MT_MOVE_Z, - MT_MOVE_YZ, - MT_MOVE_ZX, - MT_MOVE_XY, - MT_MOVE_SCREEN, - MT_ROTATE_X, - MT_ROTATE_Y, - MT_ROTATE_Z, - MT_ROTATE_SCREEN, - MT_SCALE_X, - MT_SCALE_Y, - MT_SCALE_Z, - MT_SCALE_XYZ -}; - -static bool IsTranslateType(int type) { - return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; -} - -static bool IsRotateType(int type) { - return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; -} - -static bool IsScaleType(int type) { - return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; -} - -// Matches MT_MOVE_AB order -static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; - -struct Context { - Context() - : mbUsing(false), mbEnable(true), mbUsingBounds(false) { - } - - ImDrawList* mDrawList; - - MODE mMode; - matrix_t mViewMat; - matrix_t mProjectionMat; - matrix_t mModel; - matrix_t mModelLocal; // orthonormalized model - matrix_t mModelInverse; - matrix_t mModelSource; - matrix_t mModelSourceInverse; - matrix_t mMVP; - matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition - matrix_t mViewProjection; - - vec_t mModelScaleOrigin; - vec_t mCameraEye; - vec_t mCameraRight; - vec_t mCameraDir; - vec_t mCameraUp; - vec_t mRayOrigin; - vec_t mRayVector; - - float mRadiusSquareCenter; - ImVec2 mScreenSquareCenter; - ImVec2 mScreenSquareMin; - ImVec2 mScreenSquareMax; - - float mScreenFactor; - vec_t mRelativeOrigin; - - bool mbUsing; - bool mbEnable; - bool mbMouseOver; - bool mReversed; // reversed projection matrix - - // translation - vec_t mTranslationPlan; - vec_t mTranslationPlanOrigin; - vec_t mMatrixOrigin; - vec_t mTranslationLastDelta; - - // rotation - vec_t mRotationVectorSource; - float mRotationAngle; - float mRotationAngleOrigin; - // vec_t mWorldToLocalAxis; - - // scale - vec_t mScale; - vec_t mScaleValueOrigin; - vec_t mScaleLast; - float mSaveMousePosx; - - // save axis factor when using gizmo - bool mBelowAxisLimit[3]; - bool mBelowPlaneLimit[3]; - float mAxisFactor[3]; - - // bounds stretching - // NOTE: these variable only lives during the duration of a drag - /// Position in world space, of the knob on the opposite side of the knob being dragged. - /// This is the point that needs to space regardless of where anchor is placed. - vec_t mBoundsPivot; - /// Position in world space, of the knob begin dragged. - /// This is the point that's being moved. - vec_t mBoundsAnchor; - vec_t mBoundsPlan; - /// Position in local space, of the knob on the opposite side of the knob being dragged - vec_t mBoundsLocalPivot; - int mBoundsBestAxis; - /// The axes that are being modified by the current operation. May contain 1 or 2 elements. - /// Unused elements are filled with -1 during the operation. - int mBoundsAxis[2]; - /// The index of the corner that pivot data is fetched from (opposite side from anchor). - int mBoundsPivotCornerIndex; - bool mbUsingBounds; - bool mbIsUsingBigAnchor; - /// Model matrix passed into ImGuizmo::Manipulate() - matrix_t mBoundsMatrix; - - // - int mCurrentOperation; - - float mX = 0.f; - float mY = 0.f; - float mWidth = 0.f; - float mHeight = 0.f; - float mXMax = 0.f; - float mYMax = 0.f; - float mDisplayRatio = 1.f; - - bool mIsOrthographic = false; - - int mActualID = -1; - int mEditingID = -1; - OPERATION mOperation = OPERATION(-1); - - bool mAllowAxisFlip = true; - float mGizmoSizeClipSpace = 0.1f; -}; - -static Context gContext; - -static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; -static const ImU32 directionColor[3] = { IM_COL32(0xAA, 0, 0, 0xFF), IM_COL32(0, 0xAA, 0, 0xFF), IM_COL32(0, 0, 0xAA, 0XFF) }; - -// Alpha: 100%: FF, 87%: DE, 70%: B3, 54%: 8A, 50%: 80, 38%: 61, 12%: 1F -static const ImU32 planeColor[3] = { IM_COL32(0xAA, 0, 0, 0x61), IM_COL32(0, 0xAA, 0, 0x61), IM_COL32(0, 0, 0xAA, 0x61) }; -static const ImU32 selectionColor = IM_COL32(0xFF, 0x80, 0x10, 0x8A); -static const ImU32 inactiveColor = IM_COL32(0x99, 0x99, 0x99, 0x99); -static const ImU32 translationLineColor = IM_COL32(0xAA, 0xAA, 0xAA, 0xAA); -static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", "X : %5.3f Y : %5.3f Z : %5.3f" }; -static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; -static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; -static const int translationInfoIndex[] = { 0, 0, 0, 1, 0, 0, 2, 0, 0, 1, 2, 0, 0, 2, 0, 0, 1, 0, 0, 1, 2 }; -static const float quadMin = 0.5f; -static const float quadMax = 0.8f; -static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; -static const int halfCircleSegmentCount = 64; -static const float snapTension = 0.5f; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); -static int GetRotateType(OPERATION op); -static int GetScaleType(OPERATION op); - -static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) { - vec_t trans; - trans.TransformPoint(worldPos, mat); - trans *= 0.5f / trans.w; - trans += makeVect(0.5f, 0.5f); - trans.y = 1.f - trans.y; - trans.x *= size.x; - trans.y *= size.y; - trans.x += position.x; - trans.y += position.y; - return ImVec2(trans.x, trans.y); -} - -static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) { - ImGuiIO& io = ImGui::GetIO(); - - matrix_t mViewProjInverse; - mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); - - const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; - const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; - - const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; - const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); - - rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); - rayOrigin *= 1.f / rayOrigin.w; - vec_t rayEnd; - rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); - rayEnd *= 1.f / rayEnd.w; - rayDir = Normalized(rayEnd - rayOrigin); -} - -static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) { - vec_t startOfSegment = start; - const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; - startOfSegment.TransformPoint(mvp); - if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction - { - startOfSegment *= 1.f / startOfSegment.w; - } - - vec_t endOfSegment = end; - endOfSegment.TransformPoint(mvp); - if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction - { - endOfSegment *= 1.f / endOfSegment.w; - } - - vec_t clipSpaceAxis = endOfSegment - startOfSegment; - clipSpaceAxis.y /= gContext.mDisplayRatio; - float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); - return segmentLengthInClipSpace; -} - -static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) { - vec_t pts[] = { ptO, ptA, ptB }; - for (unsigned int i = 0; i < 3; i++) - { - pts[i].TransformPoint(gContext.mMVP); - if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction - { - pts[i] *= 1.f / pts[i].w; - } - } - vec_t segA = pts[1] - pts[0]; - vec_t segB = pts[2] - pts[0]; - segA.y /= gContext.mDisplayRatio; - segB.y /= gContext.mDisplayRatio; - vec_t segAOrtho = makeVect(-segA.y, segA.x); - segAOrtho.Normalize(); - float dt = segAOrtho.Dot3(segB); - float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); - return surface; -} - -inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) { - vec_t c = point - vertPos1; - vec_t V; - - V.Normalize(vertPos2 - vertPos1); - float d = (vertPos2 - vertPos1).Length(); - float t = V.Dot3(c); - - if (t < 0.f) - { - return vertPos1; - } - - if (t > d) - { - return vertPos2; - } - - return vertPos1 + V * t; -} - -static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) { - const float numer = plan.Dot3(rOrigin) - plan.w; - const float denom = plan.Dot3(rVector); - - if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect - { - return -1.0f; - } - - return -(numer / denom); -} - -static float DistanceToPlane(const vec_t& point, const vec_t& plan) { - return plan.Dot3(point) + plan.w; -} - -static bool IsInContextRect(ImVec2 p) { - return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); -} - -static bool IsHoveringWindow() { - ImGuiContext& g = *ImGui::GetCurrentContext(); - ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName); - if (g.HoveredWindow == window) // Mouse hovering drawlist window - return true; - if (g.HoveredWindow != NULL) // Any other window is hovered - return false; - if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) - return true; - return false; -} - -void SetRect(float x, float y, float width, float height) { - gContext.mX = x; - gContext.mY = y; - gContext.mWidth = width; - gContext.mHeight = height; - gContext.mXMax = gContext.mX + gContext.mWidth; - gContext.mYMax = gContext.mY + gContext.mXMax; - gContext.mDisplayRatio = width / height; -} - -void SetOrthographic(bool isOrthographic) { - gContext.mIsOrthographic = isOrthographic; -} - -void SetDrawlist(ImDrawList* drawlist) { - gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); -} - -void SetImGuiContext(ImGuiContext* ctx) { - ImGui::SetCurrentContext(ctx); -} - -void BeginFrame() { - const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; - -#ifdef IMGUI_HAS_VIEWPORT - ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); -#else - ImGuiIO& io = ImGui::GetIO(); - ImGui::SetNextWindowSize(io.DisplaySize); - ImGui::SetNextWindowPos(ImVec2(0, 0)); -#endif - - ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); - ImGui::PushStyleColor(ImGuiCol_Border, 0); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - - ImGui::Begin("gizmo", NULL, flags); - gContext.mDrawList = ImGui::GetWindowDrawList(); - ImGui::End(); - ImGui::PopStyleVar(); - ImGui::PopStyleColor(2); -} - -bool IsUsing() { - return gContext.mbUsing || gContext.mbUsingBounds; -} - -bool IsOver() { - return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || - (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || - (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); -} - -bool IsOver(OPERATION op) { - if (IsUsing()) - { - return true; - } - if (Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) - { - return true; - } - if (Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) - { - return true; - } - if (Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) - { - return true; - } - return false; -} - -void Enable(bool enable) { - gContext.mbEnable = enable; - if (!enable) - { - gContext.mbUsing = false; - gContext.mbUsingBounds = false; - } -} - -static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) { - gContext.mMode = mode; - gContext.mViewMat = *(matrix_t*)view; - gContext.mProjectionMat = *(matrix_t*)projection; - gContext.mbMouseOver = IsHoveringWindow(); - - gContext.mModelLocal = *(matrix_t*)matrix; - gContext.mModelLocal.OrthoNormalize(); - - if (mode == LOCAL) - { - gContext.mModel = gContext.mModelLocal; - } else - { - gContext.mModel.Translation(((matrix_t*)matrix)->v.position); - } - gContext.mModelSource = *(matrix_t*)matrix; - gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); - - gContext.mModelInverse.Inverse(gContext.mModel); - gContext.mModelSourceInverse.Inverse(gContext.mModelSource); - gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; - gContext.mMVP = gContext.mModel * gContext.mViewProjection; - gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; - - matrix_t viewInverse; - viewInverse.Inverse(gContext.mViewMat); - gContext.mCameraDir = viewInverse.v.dir; - gContext.mCameraEye = viewInverse.v.position; - gContext.mCameraRight = viewInverse.v.right; - gContext.mCameraUp = viewInverse.v.up; - - // projection reverse - vec_t nearPos, farPos; - nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); - farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); - - gContext.mReversed = (nearPos.z / nearPos.w) > (farPos.z / farPos.w); - - // compute scale from the size of camera right vector projected on screen at the matrix position - vec_t pointRight = viewInverse.v.right; - pointRight.TransformPoint(gContext.mViewProjection); - gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); - - vec_t rightViewInverse = viewInverse.v.right; - rightViewInverse.TransformVector(gContext.mModelInverse); - float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); - gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; - - ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); - gContext.mScreenSquareCenter = centerSSpace; - gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); - gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); - - ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); -} - -static void ComputeColors(ImU32* colors, int type, OPERATION operation) { - if (gContext.mbEnable) - { - switch (operation) - { - case TRANSLATE: - colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; - for (int i = 0; i < 3; i++) - { - colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : directionColor[i]; - colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : planeColor[i]; - colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; - } - break; - case ROTATE: - colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; - for (int i = 0; i < 3; i++) - { - colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : directionColor[i]; - } - break; - case SCALEU: - case SCALE: - colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; - for (int i = 0; i < 3; i++) - { - colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : directionColor[i]; - } - break; - // note: this internal function is only called with three possible values for operation - default: - break; - } - } else - { - for (int i = 0; i < 7; i++) - { - colors[i] = inactiveColor; - } - } -} - -static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) { - dirAxis = directionUnary[axisIndex]; - dirPlaneX = directionUnary[(axisIndex + 1) % 3]; - dirPlaneY = directionUnary[(axisIndex + 2) % 3]; - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) - { - // when using, use stored factors so the gizmo doesn't flip when we translate - belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; - belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; - - dirAxis *= gContext.mAxisFactor[axisIndex]; - dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; - dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; - } else - { - // new method - float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis, localCoordinates); - float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis, localCoordinates); - - float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX, localCoordinates); - float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX, localCoordinates); - - float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY, localCoordinates); - float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY, localCoordinates); - - // For readability - bool& allowFlip = gContext.mAllowAxisFlip; - float mulAxis = (allowFlip && lenDir < lenDirMinus && fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; - float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX && fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; - float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY && fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; - dirAxis *= mulAxis; - dirPlaneX *= mulAxisX; - dirPlaneY *= mulAxisY; - - // for axis - float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); - - float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); - belowPlaneLimit = (paraSurf > 0.0025f); - belowAxisLimit = (axisLengthInClipSpace > 0.02f); - - // and store values - gContext.mAxisFactor[axisIndex] = mulAxis; - gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; - gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; - gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; - gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; - } -} - -static void ComputeSnap(float* value, float snap) { - if (snap <= FLT_EPSILON) - { - return; - } - - float modulo = fmodf(*value, snap); - float moduloRatio = fabsf(modulo) / snap; - if (moduloRatio < snapTension) - { - *value -= modulo; - } else if (moduloRatio > (1.f - snapTension)) - { - *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); - } -} -static void ComputeSnap(vec_t& value, const float* snap) { - for (int i = 0; i < 3; i++) - { - ComputeSnap(&value[i], snap[i]); - } -} - -static float ComputeAngleOnPlan() { - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); - vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); - - vec_t perpendicularVector; - perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); - perpendicularVector.Normalize(); - float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); - float angle = acosf(acosAngle); - angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; - return angle; -} - -static void DrawRotationGizmo(OPERATION op, int type) { - if (!Intersects(op, ROTATE)) - { - return; - } - ImDrawList* drawList = gContext.mDrawList; - - // colors - ImU32 colors[7]; - ComputeColors(colors, type, ROTATE); - - vec_t cameraToModelNormalized; - if (gContext.mIsOrthographic) - { - matrix_t viewInverse; - viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); - cameraToModelNormalized = viewInverse.v.dir; - } else - { - cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); - } - - cameraToModelNormalized.TransformVector(gContext.mModelInverse); - - gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; - - bool hasRSC = Intersects(op, ROTATE_SCREEN); - for (int axis = 0; axis < 3; axis++) - { - if (!Intersects(op, static_cast(ROTATE_Z >> axis))) - { - continue; - } - const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); - const int circleMul = (hasRSC && !usingAxis) ? 1 : 2; - - ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); - - float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; - - for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) - { - float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)halfCircleSegmentCount); - vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); - vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; - circlePos[i] = worldToPos(pos, gContext.mMVP); - } - if (!gContext.mbUsing || usingAxis) - { - drawList->AddPolyline(circlePos, circleMul * halfCircleSegmentCount + 1, colors[3 - axis], false, 2); - } - - float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); - if (radiusAxis > gContext.mRadiusSquareCenter) - { - gContext.mRadiusSquareCenter = radiusAxis; - } - } - if (hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN)) - { - drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, 3.f); - } - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) - { - ImVec2 circlePos[halfCircleSegmentCount + 1]; - - circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); - for (unsigned int i = 1; i < halfCircleSegmentCount; i++) - { - float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); - matrix_t rotateVectorMatrix; - rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); - vec_t pos; - pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); - pos *= gContext.mScreenFactor * rotationDisplayFactor; - circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); - } - drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0x80)); - drawList->AddPolyline(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0xFF), true, 2); - - ImVec2 destinationPosOnScreen = circlePos[1]; - char tmps[512]; - ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); - } -} - -static void DrawHatchedAxis(const vec_t& axis) { - for (int j = 1; j < 10; j++) - { - ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); - ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); - gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, IM_COL32(0, 0, 0, 0x80), 6.f); - } -} - -static void DrawScaleGizmo(OPERATION op, int type) { - ImDrawList* drawList = gContext.mDrawList; - - if (!Intersects(op, SCALE)) - { - return; - } - - // colors - ImU32 colors[7]; - ComputeColors(colors, type, SCALE); - - // draw - vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) - { - scaleDisplay = gContext.mScale; - } - - for (int i = 0; i < 3; i++) - { - if (!Intersects(op, static_cast(SCALE_X << i))) - { - continue; - } - const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); - if (!gContext.mbUsing || usingAxis) - { - vec_t dirPlaneX, dirPlaneY, dirAxis; - bool belowAxisLimit, belowPlaneLimit; - ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); - - // draw axis - if (belowAxisLimit) - { - bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); - float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; - ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); - ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); - ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) - { - drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); - drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); - } - - if (!hasTranslateOnAxis || gContext.mbUsing) - { - drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); - } - drawList->AddCircleFilled(worldDirSSpace, 6.f, colors[i + 1]); - - if (gContext.mAxisFactor[i] < 0.f) - { - DrawHatchedAxis(dirAxis * scaleDisplay[i]); - } - } - } - } - - // draw screen cirle - drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) - { - // ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); - ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); - /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); - dif.Normalize(); - dif *= 5.f; - drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); - drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); - drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); - */ - char tmps[512]; - // vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; - int componentInfoIndex = (type - MT_SCALE_X) * 3; - ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); - } -} - -static void DrawScaleUniveralGizmo(OPERATION op, int type) { - ImDrawList* drawList = gContext.mDrawList; - - if (!Intersects(op, SCALEU)) - { - return; - } - - // colors - ImU32 colors[7]; - ComputeColors(colors, type, SCALEU); - - // draw - vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) - { - scaleDisplay = gContext.mScale; - } - - for (int i = 0; i < 3; i++) - { - if (!Intersects(op, static_cast(SCALE_XU << i))) - { - continue; - } - const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); - if (!gContext.mbUsing || usingAxis) - { - vec_t dirPlaneX, dirPlaneY, dirAxis; - bool belowAxisLimit, belowPlaneLimit; - ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); - - // draw axis - if (belowAxisLimit) - { - bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); - float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; - // ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); - // ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); - ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); - -#if 0 - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) - { - drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); - drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); - } - /* - if (!hasTranslateOnAxis || gContext.mbUsing) - { - drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); - } - */ -#endif - drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); - } - } - } - - // draw screen cirle - drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, 3.f); - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) - { - // ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); - ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); - /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); - dif.Normalize(); - dif *= 5.f; - drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); - drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); - drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); - */ - char tmps[512]; - // vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; - int componentInfoIndex = (type - MT_SCALE_X) * 3; - ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); - } -} - -static void DrawTranslationGizmo(OPERATION op, int type) { - ImDrawList* drawList = gContext.mDrawList; - if (!drawList) - { - return; - } - - if (!Intersects(op, TRANSLATE)) - { - return; - } - - // colors - ImU32 colors[7]; - ComputeColors(colors, type, TRANSLATE); - - const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); - - // draw - bool belowAxisLimit = false; - bool belowPlaneLimit = false; - for (int i = 0; i < 3; ++i) - { - vec_t dirPlaneX, dirPlaneY, dirAxis; - ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); - - if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) - { - // draw axis - if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) - { - ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); - ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); - - drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); - - // Arrow head begin - ImVec2 dir(origin - worldDirSSpace); - - float d = sqrtf(ImLengthSqr(dir)); - dir /= d; // Normalize - dir *= 6.0f; - - ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector - ImVec2 a(worldDirSSpace + dir); - drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); - // Arrow head end - - if (gContext.mAxisFactor[i] < 0.f) - { - DrawHatchedAxis(dirAxis); - } - } - } - // draw plane - if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) - { - if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) - { - ImVec2 screenQuadPts[4]; - for (int j = 0; j < 4; ++j) - { - vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; - screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); - } - drawList->AddPolyline(screenQuadPts, 4, directionColor[i], true, 1.0f); - drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); - } - } - } - - drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); - - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) - { - ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); - ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); - vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; - dif.Normalize(); - dif *= 5.f; - drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); - drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); - drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); - - char tmps[512]; - vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; - int componentInfoIndex = (type - MT_MOVE_X) * 3; - ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); - } -} - -static bool CanActivate() { - if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) - { - return true; - } - return false; -} - -static bool HandleAndDrawLocalBounds(float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) { - ImGuiIO& io = ImGui::GetIO(); - ImDrawList* drawList = gContext.mDrawList; - - // compute best projection axis - vec_t axesWorldDirections[3]; - vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; - int axes[3]; - unsigned int numAxes = 1; - axes[0] = gContext.mBoundsBestAxis; - int bestAxis = axes[0]; - if (!gContext.mbUsingBounds) - { - numAxes = 0; - float bestDot = 0.f; - for (int i = 0; i < 3; i++) - { - vec_t dirPlaneNormalWorld; - dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); - dirPlaneNormalWorld.Normalize(); - - float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); - if (dt >= bestDot) - { - bestDot = dt; - bestAxis = i; - bestAxisWorldDirection = dirPlaneNormalWorld; - } - - if (dt >= 0.1f) - { - axes[numAxes] = i; - axesWorldDirections[numAxes] = dirPlaneNormalWorld; - ++numAxes; - } - } - } - - if (numAxes == 0) - { - axes[0] = bestAxis; - axesWorldDirections[0] = bestAxisWorldDirection; - numAxes = 1; - } - - else if (bestAxis != axes[0]) - { - unsigned int bestIndex = 0; - for (unsigned int i = 0; i < numAxes; i++) - { - if (axes[i] == bestAxis) - { - bestIndex = i; - break; - } - } - int tempAxis = axes[0]; - axes[0] = axes[bestIndex]; - axes[bestIndex] = tempAxis; - vec_t tempDirection = axesWorldDirections[0]; - axesWorldDirections[0] = axesWorldDirections[bestIndex]; - axesWorldDirections[bestIndex] = tempDirection; - } - - matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; - for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) - { - bestAxis = axes[axisIndex]; - bestAxisWorldDirection = axesWorldDirections[axisIndex]; - - // Corners of the plane (rectangle) containing bestAxis - vec_t corners[4]; - - int secondAxis = (bestAxis + 1) % 3; - int thirdAxis = (bestAxis + 2) % 3; - // ImU32 col[] = { IM_COL32(255, 0, 0, 255), IM_COL32(0, 255, 0, 255), IM_COL32(0, 0, 255, 255) }; - for (int i = 0; i < 4; i++) { - corners[i].w = 0.0f; - corners[i][bestAxis] = 0.0f; - corners[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; - corners[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; - - // ImVec2 pos = worldToPos(corners[i], boundsMVP); - // drawList->AddCircleFilled(pos, 10.0f, col[axisIndex]); - } - - // draw bounds - unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); - - for (int i = 0; i < 4; i++) - { - ImVec2 worldBound1 = worldToPos(corners[i], boundsMVP); - ImVec2 worldBound2 = worldToPos(corners[(i + 1) % 4], boundsMVP); - if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) - { - continue; - } - float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); - int stepCount = (int)(boundDistance / 10.f); - stepCount = min(stepCount, 1000); - float stepLength = 1.f / (float)stepCount; - for (int j = 0; j < stepCount; j++) - { - float t1 = (float)j * stepLength; - float t2 = (float)j * stepLength + stepLength * 0.5f; - ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); - ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); - // drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); - drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); - } - vec_t midPoint = (corners[i] + corners[(i + 1) % 4]) * 0.5f; - ImVec2 midBound = worldToPos(midPoint, boundsMVP); - static const float AnchorBigRadius = 8.f; - static const float AnchorSmallRadius = 6.f; - bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); - bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); - - int type = MT_NONE; - vec_t gizmoHitProportion; - - if (Intersects(operation, TRANSLATE)) - { - type = GetMoveType(operation, &gizmoHitProportion); - } - if (Intersects(operation, ROTATE) && type == MT_NONE) - { - type = GetRotateType(operation); - } - if (Intersects(operation, SCALE) && type == MT_NONE) - { - type = GetScaleType(operation); - } - - if (type != MT_NONE) - { - overBigAnchor = false; - overSmallAnchor = false; - } - - unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); - unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); - - drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); - drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); - - drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); - drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); - int oppositeIndex = (i + 2) % 4; - // big anchor on corners - if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) - { - gContext.mBoundsPivot.TransformPoint(corners[(i + 2) % 4], gContext.mModelSource); - gContext.mBoundsAnchor.TransformPoint(corners[i], gContext.mModelSource); - gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); - gContext.mBoundsBestAxis = bestAxis; - gContext.mBoundsAxis[0] = secondAxis; - gContext.mBoundsAxis[1] = thirdAxis; - - gContext.mBoundsLocalPivot.Set(0.f); - gContext.mBoundsLocalPivot[secondAxis] = corners[oppositeIndex][secondAxis]; - gContext.mBoundsLocalPivot[thirdAxis] = corners[oppositeIndex][thirdAxis]; - gContext.mBoundsPivotCornerIndex = oppositeIndex; - - gContext.mbUsingBounds = true; - gContext.mEditingID = gContext.mActualID; - gContext.mBoundsMatrix = gContext.mModelSource; - - gContext.mbIsUsingBigAnchor = true; - } - // small anchor on middle of segment - if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) - { - vec_t midPointOpposite = (corners[(i + 2) % 4] + corners[(i + 3) % 4]) * 0.5f; - gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); - gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); - gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); - gContext.mBoundsBestAxis = bestAxis; - int indices[] = { secondAxis, thirdAxis }; - gContext.mBoundsAxis[0] = indices[i % 2]; - gContext.mBoundsAxis[1] = -1; - - int localPivotComponentIdx = gContext.mBoundsAxis[0]; - gContext.mBoundsLocalPivot.Set(0.f); - gContext.mBoundsLocalPivot[localPivotComponentIdx] = corners[oppositeIndex][localPivotComponentIdx]; // bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); - gContext.mBoundsPivotCornerIndex = oppositeIndex; - - gContext.mbUsingBounds = true; - gContext.mEditingID = gContext.mActualID; - gContext.mBoundsMatrix = gContext.mModelSource; - - gContext.mbIsUsingBigAnchor = false; - } - } - - ImGui::Text("bounds pivot: %.2f, %.2f, %.2f", gContext.mBoundsPivot.x, gContext.mBoundsPivot.y, gContext.mBoundsPivot.z); - ImGui::Text("bounds anchor: %.2f, %.2f, %.2f", gContext.mBoundsAnchor.x, gContext.mBoundsAnchor.y, gContext.mBoundsAnchor.z); - ImGui::Text("bounds plan: %.2f, %.2f, %.2f", gContext.mBoundsPlan.x, gContext.mBoundsPlan.y, gContext.mBoundsPlan.z); - ImGui::Text("bounds local pivot: %.2f, %.2f, %.2f", gContext.mBoundsLocalPivot.x, gContext.mBoundsLocalPivot.y, gContext.mBoundsLocalPivot.z); - if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) - { - // compute projected mouse position on plan - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); - vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; - - // compute a reference and delta vectors base on mouse move - vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); - vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); - - ImGui::Text("Delta: %.2f, %.2f, %.2f", deltaVector.x, deltaVector.y, deltaVector.z); - ImGui::Text("Ref: %.2f, %.2f, %.2f", referenceVector.x, referenceVector.y, referenceVector.z); - ImGui::Separator(); - - // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length - for (int axisIndex1 : gContext.mBoundsAxis) { - if (axisIndex1 == -1) { - continue; - } - - vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); - // ImGui::Text("Axisdir: %.2f, %.2f, %.2f", axisDir.x, axisDir.y, axisDir.z); - - float refAxisComp = axisDir.Dot(referenceVector); - float deltaAxisComp = axisDir.Dot(deltaVector); - // ImGui::Text("refAxisComp: %.2f", refAxisComp); - - float length = deltaAxisComp; - if (snapValues) { - ComputeSnap(&length, snapValues[axisIndex1]); - } - - // ImGui::Text("axis idx %d", axisIndex1); - // TODO(hnosm): logic that mapps mouse pos to bound seems to account for translation fixup already? - bounds[axisIndex1] = -length / 2; - bounds[axisIndex1 + 3] = +length / 2; - } - - // Update corner positions, translation fixup code needs them - for (int i = 0; i < 4; i++) { - corners[i].w = 0.0f; - corners[i][bestAxis] = 0.0f; - corners[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; - corners[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; - } - - // Translation (object center) fixup - make sure pivot stays in place - // TODO(hnosm): is there a better way to write this that doesn't involve transferring a bunch of extra state from begin drag frame? - vec_t newLocalPivot; - if (gContext.mbIsUsingBigAnchor) { - newLocalPivot.Set(0.0f); - newLocalPivot[secondAxis] = corners[gContext.mBoundsPivotCornerIndex][secondAxis]; - newLocalPivot[thirdAxis] = corners[gContext.mBoundsPivotCornerIndex][thirdAxis]; - } else { - newLocalPivot.Set(0.0f); - int localPivotComponentIdx = gContext.mBoundsAxis[0]; - newLocalPivot[localPivotComponentIdx] = corners[gContext.mBoundsPivotCornerIndex][localPivotComponentIdx]; - } - - vec_t delta = gContext.mBoundsLocalPivot - newLocalPivot; - vec_t oldTranslation = gContext.mBoundsMatrix.component[3]; - matrix->component[3] = oldTranslation + delta; - - // info text - char tmps[512]; - ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); - ImFormatString(tmps, sizeof(tmps), - // Size of the bounds in each axis direction - "X: %.2f Y: %.2f Z:%.2f", - (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length(), - (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length(), - (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length()); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); - drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); - } - - if (!io.MouseDown[0]) { - gContext.mbUsingBounds = false; - gContext.mEditingID = -1; - } - if (gContext.mbUsingBounds) - { - break; - } - } - - return gContext.mbUsingBounds; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// - -static int GetScaleType(OPERATION op) { - if (gContext.mbUsing) - { - return MT_NONE; - } - ImGuiIO& io = ImGui::GetIO(); - int type = MT_NONE; - - // screen - if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && - io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && - Contains(op, SCALE)) - { - type = MT_SCALE_XYZ; - } - - // compute - for (int i = 0; i < 3 && type == MT_NONE; i++) - { - if (!Intersects(op, static_cast(SCALE_X << i))) - { - continue; - } - vec_t dirPlaneX, dirPlaneY, dirAxis; - bool belowAxisLimit, belowPlaneLimit; - ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); - dirAxis.TransformVector(gContext.mModelLocal); - dirPlaneX.TransformVector(gContext.mModelLocal); - dirPlaneY.TransformVector(gContext.mModelLocal); - - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); - vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; - - const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; - const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; - const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); - const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); - const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); - - vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); - - if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size - { - type = MT_SCALE_X + i; - } - } - - // universal - - vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; - float dist = deltaScreen.Length(); - if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) - { - type = MT_SCALE_XYZ; - } - - for (int i = 0; i < 3 && type == MT_NONE; i++) - { - if (!Intersects(op, static_cast(SCALE_XU << i))) - { - continue; - } - - vec_t dirPlaneX, dirPlaneY, dirAxis; - bool belowAxisLimit, belowPlaneLimit; - ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); - - // draw axis - if (belowAxisLimit) - { - bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); - float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; - // ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); - // ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); - ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); - - float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos)); - if (distance < 12.f) - { - type = MT_SCALE_X + i; - } - } - } - return type; -} - -static int GetRotateType(OPERATION op) { - if (gContext.mbUsing) - { - return MT_NONE; - } - ImGuiIO& io = ImGui::GetIO(); - int type = MT_NONE; - - vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; - float dist = deltaScreen.Length(); - if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) - { - type = MT_ROTATE_SCREEN; - } - - const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; - - vec_t modelViewPos; - modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); - - for (int i = 0; i < 3 && type == MT_NONE; i++) - { - if (!Intersects(op, static_cast(ROTATE_X << i))) - { - continue; - } - // pickup plan - vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); - - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); - const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; - vec_t intersectViewPos; - intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); - - if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) - { - continue; - } - - const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; - vec_t idealPosOnCircle = Normalized(localPos); - idealPosOnCircle.TransformVector(gContext.mModelInverse); - const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); - - // gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); - const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; - - const float distance = makeVect(distanceOnScreen).Length(); - if (distance < 8.f) // pixel size - { - type = MT_ROTATE_X + i; - } - } - - return type; -} - -static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) { - if (!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) - { - return MT_NONE; - } - ImGuiIO& io = ImGui::GetIO(); - int type = MT_NONE; - - // screen - if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && - io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && - Contains(op, TRANSLATE)) - { - type = MT_MOVE_SCREEN; - } - - const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); - - // compute - for (int i = 0; i < 3 && type == MT_NONE; i++) - { - vec_t dirPlaneX, dirPlaneY, dirAxis; - bool belowAxisLimit, belowPlaneLimit; - ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); - dirAxis.TransformVector(gContext.mModel); - dirPlaneX.TransformVector(gContext.mModel); - dirPlaneY.TransformVector(gContext.mModel); - - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); - vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; - - const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); - const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); - - vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); - if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size - { - type = MT_MOVE_X + i; - } - - const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); - const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); - if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) - { - type = MT_MOVE_YZ + i; - } - - if (gizmoHitProportion) - { - *gizmoHitProportion = makeVect(dx, dy, 0.f); - } - } - return type; -} - -static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) { - if (!Intersects(op, TRANSLATE) || type != MT_NONE) - { - return false; - } - const ImGuiIO& io = ImGui::GetIO(); - const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; - bool modified = false; - - // move - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) - { - ImGui::CaptureMouseFromApp(); - const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); - const float len = fabsf(signedLength); // near plan - const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; - - // compute delta - const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; - vec_t delta = newOrigin - gContext.mModel.v.position; - - // 1 axis constraint - if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) - { - const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; - const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; - const float lengthOnAxis = Dot(axisValue, delta); - delta = axisValue * lengthOnAxis; - } - - // snap - if (snap) - { - vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; - if (applyRotationLocaly) - { - matrix_t modelSourceNormalized = gContext.mModelSource; - modelSourceNormalized.OrthoNormalize(); - matrix_t modelSourceNormalizedInverse; - modelSourceNormalizedInverse.Inverse(modelSourceNormalized); - cumulativeDelta.TransformVector(modelSourceNormalizedInverse); - ComputeSnap(cumulativeDelta, snap); - cumulativeDelta.TransformVector(modelSourceNormalized); - } else - { - ComputeSnap(cumulativeDelta, snap); - } - delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; - } - - if (delta != gContext.mTranslationLastDelta) - { - modified = true; - } - gContext.mTranslationLastDelta = delta; - - // compute matrix & delta - matrix_t deltaMatrixTranslation; - deltaMatrixTranslation.Translation(delta); - if (deltaMatrix) - { - memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); - } - - const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; - *(matrix_t*)matrix = res; - - if (!io.MouseDown[0]) - { - gContext.mbUsing = false; - } - - type = gContext.mCurrentOperation; - } else - { - // find new possible way to move - vec_t gizmoHitProportion; - type = GetMoveType(op, &gizmoHitProportion); - if (type != MT_NONE) - { - ImGui::CaptureMouseFromApp(); - } - if (CanActivate() && type != MT_NONE) - { - gContext.mbUsing = true; - gContext.mEditingID = gContext.mActualID; - gContext.mCurrentOperation = type; - vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; - - vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); - for (unsigned int i = 0; i < 3; i++) - { - vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); - movePlanNormal[i].Cross(orthoVector); - movePlanNormal[i].Normalize(); - } - // pickup plan - gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); - gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; - gContext.mMatrixOrigin = gContext.mModel.v.position; - - gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); - } - } - return modified; -} - -static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) { - if ((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) - { - return false; - } - ImGuiIO& io = ImGui::GetIO(); - bool modified = false; - - if (!gContext.mbUsing) - { - // find new possible way to scale - type = GetScaleType(op); - if (type != MT_NONE) - { - ImGui::CaptureMouseFromApp(); - } - if (CanActivate() && type != MT_NONE) - { - gContext.mbUsing = true; - gContext.mEditingID = gContext.mActualID; - gContext.mCurrentOperation = type; - const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; - // pickup plan - - gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); - gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; - gContext.mMatrixOrigin = gContext.mModel.v.position; - gContext.mScale.Set(1.f, 1.f, 1.f); - gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); - gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); - gContext.mSaveMousePosx = io.MousePos.x; - } - } - // scale - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) - { - ImGui::CaptureMouseFromApp(); - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); - vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; - vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; - vec_t delta = newOrigin - gContext.mModelLocal.v.position; - - // 1 axis constraint - if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) - { - int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; - const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; - float lengthOnAxis = Dot(axisValue, delta); - delta = axisValue * lengthOnAxis; - - vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; - float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); - - gContext.mScale[axisIndex] = max(ratio, 0.001f); - } else - { - float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; - gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); - } - - // snap - if (snap) - { - float scaleSnap[] = { snap[0], snap[0], snap[0] }; - ComputeSnap(gContext.mScale, scaleSnap); - } - - // no 0 allowed - for (int i = 0; i < 3; i++) - gContext.mScale[i] = max(gContext.mScale[i], 0.001f); - - if (gContext.mScaleLast != gContext.mScale) - { - modified = true; - } - gContext.mScaleLast = gContext.mScale; - - // compute matrix & delta - matrix_t deltaMatrixScale; - deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); - - matrix_t res = deltaMatrixScale * gContext.mModelLocal; - *(matrix_t*)matrix = res; - - if (deltaMatrix) - { - vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; - - vec_t originalScaleDivider; - originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; - originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; - originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; - - deltaScale = deltaScale * originalScaleDivider; - - deltaMatrixScale.Scale(deltaScale); - memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); - } - - if (!io.MouseDown[0]) - { - gContext.mbUsing = false; - gContext.mScale.Set(1.f, 1.f, 1.f); - } - - type = gContext.mCurrentOperation; - } - return modified; -} - -static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) { - if (!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) - { - return false; - } - ImGuiIO& io = ImGui::GetIO(); - bool applyRotationLocaly = gContext.mMode == LOCAL; - bool modified = false; - - if (!gContext.mbUsing) - { - type = GetRotateType(op); - - if (type != MT_NONE) - { - ImGui::CaptureMouseFromApp(); - } - - if (type == MT_ROTATE_SCREEN) - { - applyRotationLocaly = true; - } - - if (CanActivate() && type != MT_NONE) - { - gContext.mbUsing = true; - gContext.mEditingID = gContext.mActualID; - gContext.mCurrentOperation = type; - const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; - // pickup plan - if (applyRotationLocaly) - { - gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); - } else - { - gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); - } - - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); - vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; - gContext.mRotationVectorSource = Normalized(localPos); - gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); - } - } - - // rotation - if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) - { - ImGui::CaptureMouseFromApp(); - gContext.mRotationAngle = ComputeAngleOnPlan(); - if (snap) - { - float snapInRadian = snap[0] * DEG2RAD; - ComputeSnap(&gContext.mRotationAngle, snapInRadian); - } - vec_t rotationAxisLocalSpace; - - rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); - rotationAxisLocalSpace.Normalize(); - - matrix_t deltaRotation; - deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); - if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) - { - modified = true; - } - gContext.mRotationAngleOrigin = gContext.mRotationAngle; - - matrix_t scaleOrigin; - scaleOrigin.Scale(gContext.mModelScaleOrigin); - - if (applyRotationLocaly) - { - *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; - } else - { - matrix_t res = gContext.mModelSource; - res.v.position.Set(0.f); - - *(matrix_t*)matrix = res * deltaRotation; - ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; - } - - if (deltaMatrix) - { - *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; - } - - if (!io.MouseDown[0]) - { - gContext.mbUsing = false; - gContext.mEditingID = -1; - } - type = gContext.mCurrentOperation; - } - return modified; -} - -void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) { - matrix_t mat = *(matrix_t*)matrix; - - scale[0] = mat.v.right.Length(); - scale[1] = mat.v.up.Length(); - scale[2] = mat.v.dir.Length(); - - mat.OrthoNormalize(); - - rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); - rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); - rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); - - translation[0] = mat.v.position.x; - translation[1] = mat.v.position.y; - translation[2] = mat.v.position.z; -} - -void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) { - matrix_t& mat = *(matrix_t*)matrix; - - matrix_t rot[3]; - for (int i = 0; i < 3; i++) - { - rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); - } - - mat = rot[0] * rot[1] * rot[2]; - - float validScale[3]; - for (int i = 0; i < 3; i++) - { - if (fabsf(scale[i]) < FLT_EPSILON) - { - validScale[i] = 0.001f; - } else - { - validScale[i] = scale[i]; - } - } - mat.v.right *= validScale[0]; - mat.v.up *= validScale[1]; - mat.v.dir *= validScale[2]; - mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); -} - -void SetID(int id) { - gContext.mActualID = id; -} - -void AllowAxisFlip(bool value) { - gContext.mAllowAxisFlip = value; -} - -bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, float* localBounds, const float* boundsSnap) { - // Scale is always local or matrix will be skewed when applying world scale or oriented matrix - ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); - - // set delta to identity - if (deltaMatrix) - { - ((matrix_t*)deltaMatrix)->SetToIdentity(); - } - - // behind camera - vec_t camSpacePosition; - camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); - if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f) - { - return false; - } - - // -- - int type = MT_NONE; - bool manipulated = false; - if (gContext.mbEnable) - { - if (!gContext.mbUsingBounds) - { - manipulated |= HandleTranslation(matrix, deltaMatrix, operation, type, snap) || - HandleScale(matrix, deltaMatrix, operation, type, snap) || - HandleRotation(matrix, deltaMatrix, operation, type, snap); - } - } - if (localBounds && !gContext.mbUsing) - { - manipulated |= HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); - } - - gContext.mOperation = operation; - if (!gContext.mbUsingBounds) - { - DrawRotationGizmo(operation, type); - DrawTranslationGizmo(operation, type); - DrawScaleGizmo(operation, type); - DrawScaleUniveralGizmo(operation, type); - } - return manipulated; -} - -void SetGizmoSizeClipSpace(float value) { - gContext.mGizmoSizeClipSpace = value; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -void ComputeFrustumPlanes(vec_t* frustum, const float* clip) { - frustum[0].x = clip[3] - clip[0]; - frustum[0].y = clip[7] - clip[4]; - frustum[0].z = clip[11] - clip[8]; - frustum[0].w = clip[15] - clip[12]; - - frustum[1].x = clip[3] + clip[0]; - frustum[1].y = clip[7] + clip[4]; - frustum[1].z = clip[11] + clip[8]; - frustum[1].w = clip[15] + clip[12]; - - frustum[2].x = clip[3] + clip[1]; - frustum[2].y = clip[7] + clip[5]; - frustum[2].z = clip[11] + clip[9]; - frustum[2].w = clip[15] + clip[13]; - - frustum[3].x = clip[3] - clip[1]; - frustum[3].y = clip[7] - clip[5]; - frustum[3].z = clip[11] - clip[9]; - frustum[3].w = clip[15] - clip[13]; - - frustum[4].x = clip[3] - clip[2]; - frustum[4].y = clip[7] - clip[6]; - frustum[4].z = clip[11] - clip[10]; - frustum[4].w = clip[15] - clip[14]; - - frustum[5].x = clip[3] + clip[2]; - frustum[5].y = clip[7] + clip[6]; - frustum[5].z = clip[11] + clip[10]; - frustum[5].w = clip[15] + clip[14]; - - for (int i = 0; i < 6; i++) - { - frustum[i].Normalize(); - } -} - -void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) { - matrix_t viewInverse; - viewInverse.Inverse(*(matrix_t*)view); - - struct CubeFace { - float z; - ImVec2 faceCoordsScreen[4]; - ImU32 color; - }; - CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); - - if (!faces) - { - return; - } - - vec_t frustum[6]; - matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; - ComputeFrustumPlanes(frustum, viewProjection.m16); - - int cubeFaceCount = 0; - for (int cube = 0; cube < matrixCount; cube++) - { - const float* matrix = &matrices[cube * 16]; - - matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; - - for (int iFace = 0; iFace < 6; iFace++) - { - const int normalIndex = (iFace % 3); - const int perpXIndex = (normalIndex + 1) % 3; - const int perpYIndex = (normalIndex + 2) % 3; - const float invert = (iFace > 2) ? -1.f : 1.f; - - const vec_t faceCoords[4] = { - directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], - directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], - directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], - directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], - }; - - // clipping - /* - bool skipFace = false; - for (unsigned int iCoord = 0; iCoord < 4; iCoord++) - { - vec_t camSpacePosition; - camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); - if (camSpacePosition.z < 0.001f) - { - skipFace = true; - break; - } - } - if (skipFace) - { - continue; - } - */ - vec_t centerPosition, centerPositionVP; - centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); - centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); - - bool inFrustum = true; - for (int iFrustum = 0; iFrustum < 6; iFrustum++) - { - float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); - if (dist < 0.f) - { - inFrustum = false; - break; - } - } - - if (!inFrustum) - { - continue; - } - CubeFace& cubeFace = faces[cubeFaceCount]; - - // 3D->2D - // ImVec2 faceCoordsScreen[4]; - for (unsigned int iCoord = 0; iCoord < 4; iCoord++) - { - cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); - } - cubeFace.color = directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0); - - cubeFace.z = centerPositionVP.z / centerPositionVP.w; - cubeFaceCount++; - } - } - qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { - CubeFace* a = (CubeFace*)_a; - CubeFace* b = (CubeFace*)_b; - if (a->z < b->z) - { - return 1; - } - return -1; - }); - // draw face with lighter color - for (int iFace = 0; iFace < cubeFaceCount; iFace++) - { - const CubeFace& cubeFace = faces[iFace]; - gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); - } - - _freea(faces); -} - -void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) { - matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; - vec_t frustum[6]; - ComputeFrustumPlanes(frustum, viewProjection.m16); - matrix_t res = *(matrix_t*)matrix * viewProjection; - - for (float f = -gridSize; f <= gridSize; f += 1.f) - { - for (int dir = 0; dir < 2; dir++) - { - vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); - vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); - bool visible = true; - for (int i = 0; i < 6; i++) - { - float dA = DistanceToPlane(ptA, frustum[i]); - float dB = DistanceToPlane(ptB, frustum[i]); - if (dA < 0.f && dB < 0.f) - { - visible = false; - break; - } - if (dA > 0.f && dB > 0.f) - { - continue; - } - if (dA < 0.f) - { - float len = fabsf(dA - dB); - float t = fabsf(dA) / len; - ptA.Lerp(ptB, t); - } - if (dB < 0.f) - { - float len = fabsf(dB - dA); - float t = fabsf(dB) / len; - ptB.Lerp(ptA, t); - } - } - if (visible) - { - ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); - col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; - col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF) : col; - - float thickness = 1.f; - thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; - thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; - - gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); - } - } - } -} - -void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) { - static bool isDraging = false; - static bool isClicking = false; - static bool isInside = false; - static vec_t interpolationUp; - static vec_t interpolationDir; - static int interpolationFrames = 0; - const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); - - matrix_t svgView, svgProjection; - svgView = gContext.mViewMat; - svgProjection = gContext.mProjectionMat; - - ImGuiIO& io = ImGui::GetIO(); - gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); - matrix_t viewInverse; - viewInverse.Inverse(*(matrix_t*)view); - - const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; - - // view/projection matrices - const float distance = 3.f; - matrix_t cubeProjection, cubeView; - float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; - Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); - - vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); - vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); - vec_t eye = dir * distance; - vec_t zero = makeVect(0.f, 0.f); - LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); - - // set context - gContext.mViewMat = cubeView; - gContext.mProjectionMat = cubeProjection; - ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); - - const matrix_t res = cubeView * cubeProjection; - - // panels - static const ImVec2 panelPosition[9] = { ImVec2(0.75f, 0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; - - static const ImVec2 panelSize[9] = { ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; - - // tag faces - bool boxes[27]{}; - for (int iPass = 0; iPass < 2; iPass++) - { - for (int iFace = 0; iFace < 6; iFace++) - { - const int normalIndex = (iFace % 3); - const int perpXIndex = (normalIndex + 1) % 3; - const int perpYIndex = (normalIndex + 2) % 3; - const float invert = (iFace > 2) ? -1.f : 1.f; - const vec_t indexVectorX = directionUnary[perpXIndex] * invert; - const vec_t indexVectorY = directionUnary[perpYIndex] * invert; - const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; - - // plan local space - const vec_t n = directionUnary[normalIndex] * invert; - vec_t viewSpaceNormal = n; - vec_t viewSpacePoint = n * 0.5f; - viewSpaceNormal.TransformVector(cubeView); - viewSpaceNormal.Normalize(); - viewSpacePoint.TransformPoint(cubeView); - const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); - - // back face culling - if (viewSpaceFacePlan.w > 0.f) - { - continue; - } - - const vec_t facePlan = BuildPlan(n * 0.5f, n); - - const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); - vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); - - float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; - float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; - - // panels - const vec_t dx = directionUnary[perpXIndex]; - const vec_t dy = directionUnary[perpYIndex]; - const vec_t origin = directionUnary[normalIndex] - dx - dy; - for (int iPanel = 0; iPanel < 9; iPanel++) - { - vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); - const ImVec2 p = panelPosition[iPanel] * 2.f; - const ImVec2 s = panelSize[iPanel] * 2.f; - ImVec2 faceCoordsScreen[4]; - vec_t panelPos[4] = { dx * p.x + dy * p.y, - dx * p.x + dy * (p.y + s.y), - dx * (p.x + s.x) + dy * (p.y + s.y), - dx * (p.x + s.x) + dy * p.y }; - - for (unsigned int iCoord = 0; iCoord < 4; iCoord++) - { - faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); - } - - const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; - bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; - int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); - IM_ASSERT(boxCoordInt < 27); - boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; - - // draw face with lighter color - if (iPass) - { - gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); - if (boxes[boxCoordInt]) - { - gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); - - if (!io.MouseDown[0] && !isDraging && isClicking) - { - // apply new view direction - int cx = boxCoordInt / 9; - int cy = (boxCoordInt - cx * 9) / 3; - int cz = boxCoordInt % 3; - interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); - interpolationDir.Normalize(); - - if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) - { - vec_t right = viewInverse.v.right; - if (fabsf(right.x) > fabsf(right.z)) - { - right.z = 0.f; - } else - { - right.x = 0.f; - } - right.Normalize(); - interpolationUp = Cross(interpolationDir, right); - interpolationUp.Normalize(); - } else - { - interpolationUp = referenceUp; - } - interpolationFrames = 40; - isClicking = false; - } - if (io.MouseClicked[0] && !isDraging) - { - isClicking = true; - } - } - } - } - } - } - if (interpolationFrames) - { - interpolationFrames--; - vec_t newDir = viewInverse.v.dir; - newDir.Lerp(interpolationDir, 0.2f); - newDir.Normalize(); - - vec_t newUp = viewInverse.v.up; - newUp.Lerp(interpolationUp, 0.3f); - newUp.Normalize(); - newUp = interpolationUp; - vec_t newEye = camTarget + newDir * length; - LookAt(&newEye.x, &camTarget.x, &newUp.x, view); - } - isInside = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos); - - // drag view - if (!isDraging && io.MouseClicked[0] && isInside) - { - isDraging = true; - isClicking = false; - } else if (isDraging && !io.MouseDown[0]) - { - isDraging = false; - } - - if (isDraging) - { - matrix_t rx, ry, roll; - - rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); - ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); - - roll = rx * ry; - - vec_t newDir = viewInverse.v.dir; - newDir.TransformVector(roll); - newDir.Normalize(); - - // clamp - vec_t planDir = Cross(viewInverse.v.right, referenceUp); - planDir.y = 0.f; - planDir.Normalize(); - float dt = Dot(planDir, newDir); - if (dt < 0.0f) - { - newDir += planDir * dt; - newDir.Normalize(); - } - - vec_t newEye = camTarget + newDir * length; - LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); - } - - // restore view/projection because it was used to compute ray - ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); -} -}; // namespace IMGUIZMO_NAMESPACE diff --git a/source/30-game/EditorGuizmo.hpp b/source/30-game/EditorGuizmo.hpp deleted file mode 100644 index 0560050..0000000 --- a/source/30-game/EditorGuizmo.hpp +++ /dev/null @@ -1,232 +0,0 @@ -// https://github.com/CedricGuillemet/ImGuizmo -// v 1.84 WIP -// -// The MIT License(MIT) -// -// Copyright(c) 2021 Cedric Guillemet -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// ------------------------------------------------------------------------------------------- -// History : -// 2019/11/03 View gizmo -// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. -// 2016/09/09 Hatched negative axis. Snapping. Documentation update. -// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved -// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. -// 2016/08/31 First version -// -// ------------------------------------------------------------------------------------------- -// Future (no order): -// -// - Multi view -// - display rotation/translation/scale infos in local/world space and not only local -// - finish local/world matrix application -// - OPERATION as bitmask -// -// ------------------------------------------------------------------------------------------- -// Example -#if 0 -void EditTransform(const Camera& camera, matrix_t& matrix) -{ - static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); - static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); - if (ImGui::IsKeyPressed(90)) - mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - if (ImGui::IsKeyPressed(69)) - mCurrentGizmoOperation = ImGuizmo::ROTATE; - if (ImGui::IsKeyPressed(82)) // r Key - mCurrentGizmoOperation = ImGuizmo::SCALE; - if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) - mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) - mCurrentGizmoOperation = ImGuizmo::ROTATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) - mCurrentGizmoOperation = ImGuizmo::SCALE; - float matrixTranslation[3], matrixRotation[3], matrixScale[3]; - ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); - ImGui::InputFloat3("Tr", matrixTranslation, 3); - ImGui::InputFloat3("Rt", matrixRotation, 3); - ImGui::InputFloat3("Sc", matrixScale, 3); - ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); - - if (mCurrentGizmoOperation != ImGuizmo::SCALE) - { - if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) - mCurrentGizmoMode = ImGuizmo::LOCAL; - ImGui::SameLine(); - if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) - mCurrentGizmoMode = ImGuizmo::WORLD; - } - static bool useSnap(false); - if (ImGui::IsKeyPressed(83)) - useSnap = !useSnap; - ImGui::Checkbox("", &useSnap); - ImGui::SameLine(); - vec_t snap; - switch (mCurrentGizmoOperation) - { - case ImGuizmo::TRANSLATE: - snap = config.mSnapTranslation; - ImGui::InputFloat3("Snap", &snap.x); - break; - case ImGuizmo::ROTATE: - snap = config.mSnapRotation; - ImGui::InputFloat("Angle Snap", &snap.x); - break; - case ImGuizmo::SCALE: - snap = config.mSnapScale; - ImGui::InputFloat("Scale Snap", &snap.x); - break; - } - ImGuiIO& io = ImGui::GetIO(); - ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); - ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); -} -#endif -#pragma once - -#ifdef USE_IMGUI_API -# include "imconfig.h" -#endif -#ifndef IMGUI_API -# define IMGUI_API -#endif - -// NOTE(hnosm): added so that we don't have to force #include after everything else -#include - -#ifndef IMGUIZMO_NAMESPACE -# define IMGUIZMO_NAMESPACE ImGuizmo -#endif - -namespace IMGUIZMO_NAMESPACE { -// call inside your own window and before Manipulate() in order to draw gizmo to that window. -// Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). -IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); - -// call BeginFrame right after ImGui_XXXX_NewFrame(); -IMGUI_API void BeginFrame(); - -// this is necessary because when imguizmo is compiled into a dll, and imgui into another -// globals are not shared between them. -// More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam -// expose method to set imgui context -IMGUI_API void SetImGuiContext(ImGuiContext* ctx); - -// return true if mouse cursor is over any gizmo control (axis, plan or screen component) -IMGUI_API bool IsOver(); - -// return true if mouse IsOver or if the gizmo is in moving state -IMGUI_API bool IsUsing(); - -// enable/disable the gizmo. Stay in the state until next call to Enable. -// gizmo is rendered with gray half transparent color when disabled -IMGUI_API void Enable(bool enable); - -// helper functions for manualy editing translation/rotation/scale with an input float -// translation, rotation and scale float points to 3 floats each -// Angles are in degrees (more suitable for human editing) -// example: -// float matrixTranslation[3], matrixRotation[3], matrixScale[3]; -// ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); -// ImGui::InputFloat3("Tr", matrixTranslation, 3); -// ImGui::InputFloat3("Rt", matrixRotation, 3); -// ImGui::InputFloat3("Sc", matrixScale, 3); -// ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); -// -// These functions have some numerical stability issues for now. Use with caution. -IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); -IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); - -IMGUI_API void SetRect(float x, float y, float width, float height); -// default is false -IMGUI_API void SetOrthographic(bool isOrthographic); - -// Render a cube with face color corresponding to face normal. Usefull for debug/tests -IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); -IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); - -// call it when you want a gizmo -// Needs view and projection matrices. -// matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional -// translation is applied in world space -enum OPERATION { - TRANSLATE_X = (1u << 0), - TRANSLATE_Y = (1u << 1), - TRANSLATE_Z = (1u << 2), - ROTATE_X = (1u << 3), - ROTATE_Y = (1u << 4), - ROTATE_Z = (1u << 5), - ROTATE_SCREEN = (1u << 6), - SCALE_X = (1u << 7), - SCALE_Y = (1u << 8), - SCALE_Z = (1u << 9), - BOUNDS = (1u << 10), - SCALE_XU = (1u << 11), - SCALE_YU = (1u << 12), - SCALE_ZU = (1u << 13), - - TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, - ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, - SCALE = SCALE_X | SCALE_Y | SCALE_Z, - SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal - UNIVERSAL = TRANSLATE | ROTATE | SCALEU -}; - -inline OPERATION operator|(OPERATION lhs, OPERATION rhs) { - return static_cast(static_cast(lhs) | static_cast(rhs)); -} - -enum MODE { - LOCAL, - WORLD -}; - -IMGUI_API bool Manipulate( - const float* view, - const float* projection, - OPERATION operation, - MODE mode, - float* matrix, - float* deltaMatrix = NULL, - const float* snap = NULL, - float* localBounds = NULL, - const float* boundsSnap = NULL); - -// -// Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en -// It seems to be a defensive patent in the US. I don't think it will bring troubles using it as -// other software are using the same mechanics. But just in case, you are now warned! -// -IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); - -IMGUI_API void SetID(int id); - -// return true if the cursor is over the operation's gizmo -IMGUI_API bool IsOver(OPERATION op); -IMGUI_API void SetGizmoSizeClipSpace(float value); - -// Allow axis to flip -// When true (default), the guizmo axis flip for better visibility -// When false, they always stay along the positive world/local axis -IMGUI_API void AllowAxisFlip(bool value); -} // namespace IMGUIZMO_NAMESPACE diff --git a/source/30-game/EditorNotification.cpp b/source/30-game/EditorNotification.cpp deleted file mode 100644 index e4a869e..0000000 --- a/source/30-game/EditorNotification.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// Adapted from https://github.com/patrickcjk/imgui-notify -#include "EditorNotification.hpp" - -#include "Macros.hpp" - -#define IMGUI_DEFINE_MATH_OPERATORS -#include - -#include -#include -#include -#include -#include - -ImGuiToast::ImGuiToast(ImGuiToastType type, int dismissTime) { - IM_ASSERT(type < ImGuiToastType_COUNT); - - mType = type; - mDismissTime = dismissTime; - - using namespace std::chrono; - auto timeStamp = system_clock::now().time_since_epoch(); - mCreationTime = duration_cast(timeStamp).count(); - - memset(mTitle, 0, sizeof(mTitle)); - memset(mContent, 0, sizeof(mContent)); -} - -ImGuiToast::ImGuiToast(ImGuiToastType type, const char* format, ...) - : ImGuiToast(type) { - if (format) { - va_list args; - va_start(args, format); - SetContent(format, args); - va_end(args); - } -} - -ImGuiToast::ImGuiToast(ImGuiToastType type, int dismissTime, const char* format, ...) - : ImGuiToast(type, dismissTime) { - if (format) { - va_list args; - va_start(args, format); - SetContent(format, args); - va_end(args); - } -} - -void ImGuiToast::SetTitle(const char* format, ...) { - if (format) { - va_list args; - va_start(args, format); - SetTitle(format, args); - va_end(args); - } -} - -void ImGuiToast::SetContent(const char* format, ...) { - if (format) { - va_list args; - va_start(args, format); - SetContent(format, args); - va_end(args); - } -} - -void ImGuiToast::SetType(const ImGuiToastType& type) { - IM_ASSERT(type < ImGuiToastType_COUNT); - mType = type; -} - -const char* ImGuiToast::GetTitle() { - return mTitle; -} - -const char* ImGuiToast::GetDefaultTitle() { - if (!strlen(mTitle)) { - switch (mType) { - case ImGuiToastType_None: return nullptr; - case ImGuiToastType_Success: return "Success"; - case ImGuiToastType_Warning: return "Warning"; - case ImGuiToastType_Error: return "Error"; - case ImGuiToastType_Info: return "Info"; - case ImGuiToastType_COUNT: UNREACHABLE; - } - } - - return mTitle; -} - -ImGuiToastType ImGuiToast::GetType() { - return mType; -} - -ImVec4 ImGuiToast::GetColor() { - switch (mType) { - case ImGuiToastType_None: return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White - case ImGuiToastType_Success: return ImVec4(0, 1.0f, 0, 1.0f); // Green - case ImGuiToastType_Warning: return ImVec4(1.0f, 1.0f, 0, 1.0f); // Yellow - case ImGuiToastType_Error: return ImVec4(1.0f, 0, 0, 1.0f); // Red - case ImGuiToastType_Info: return ImVec4(0, 0.616, 1.0f, 1.0f); // Blue - case ImGuiToastType_COUNT: UNREACHABLE; - } - return ImVec4(); -} - -const char* ImGuiToast::GetIcon() { - switch (mType) { - case ImGuiToastType_None: return nullptr; -#if 1 - // TODO add IconFontHeaders and replace with proper icons - case ImGuiToastType_Success: return nullptr; - case ImGuiToastType_Warning: return nullptr; - case ImGuiToastType_Error: return nullptr; - case ImGuiToastType_Info: return nullptr; -#else - case ImGuiToastType_Success: return ICON_FA_CHECK_CIRCLE; - case ImGuiToastType_Warning: return ICON_FA_EXCLAMATION_TRIANGLE; - case ImGuiToastType_Error: return ICON_FA_TIMES_CIRCLE; - case ImGuiToastType_Info: return ICON_FA_INFO_CIRCLE; -#endif - case ImGuiToastType_COUNT: UNREACHABLE; - } - return nullptr; -} - -const char* ImGuiToast::GetContent() { - return this->mContent; -} - -uint64_t ImGuiToast::GetElapsedTime() { - using namespace std::chrono; - auto timeStamp = system_clock::now().time_since_epoch(); - auto timeStampI = duration_cast(timeStamp).count(); - return timeStampI - mCreationTime; -} - -ImGuiToastPhase ImGuiToast::GetPhase() { - const auto elapsed = GetElapsedTime(); - - if (elapsed > kNotifyFadeInOutTime + mDismissTime + kNotifyFadeInOutTime) { - return ImGuiToastPhase_Expired; - } else if (elapsed > kNotifyFadeInOutTime + mDismissTime) { - return ImGuiToastPhase_FadeOut; - } else if (elapsed > kNotifyFadeInOutTime) { - return ImGuiToastPhase_Wait; - } else { - return ImGuiToastPhase_FadeIn; - } -} - -float ImGuiToast::GetFadePercent() { - const auto phase = GetPhase(); - const auto elapsed = GetElapsedTime(); - - if (phase == ImGuiToastPhase_FadeIn) - { - return ((float)elapsed / (float)kNotifyFadeInOutTime) * kNotifyOpacity; - } else if (phase == ImGuiToastPhase_FadeOut) - { - return (1.0f - (((float)elapsed - (float)kNotifyFadeInOutTime - (float)mDismissTime) / (float)kNotifyFadeInOutTime)) * kNotifyOpacity; - } - - return 1.0f * kNotifyOpacity; -} - -void ImGuiToast::SetTitle(const char* format, va_list args) { - vsnprintf(mTitle, sizeof(mTitle), format, args); -} - -void ImGuiToast::SetContent(const char* format, va_list args) { - vsnprintf(mContent, sizeof(mContent), format, args); -} - -namespace ImGui { -static std::vector notifications; -} - -static bool IsNullOrEmpty(const char* str) { - return !str || !strlen(str); -} - -void ImGui::AddNotification(ImGuiToast toast) { - notifications.push_back(std::move(toast)); -} - -void ImGui::RemoveNotification(int index) { - notifications.erase(notifications.begin() + index); -} - -void ImGui::ShowNotifications() { - auto vpSize = GetMainViewport()->Size; - - float height = 0.0f; - for (auto i = 0; i < notifications.size(); i++) { - auto* currentToast = ¬ifications[i]; - - // Remove toast if expired - if (currentToast->GetPhase() == ImGuiToastPhase_Expired) { - RemoveNotification(i); - continue; - } - - // Get icon, title and other data - const auto icon = currentToast->GetIcon(); - const auto title = currentToast->GetTitle(); - const auto content = currentToast->GetContent(); - const auto defaultTitle = currentToast->GetDefaultTitle(); - const auto opacity = currentToast->GetFadePercent(); // Get opacity based of the current phase - - // Window rendering - auto textColor = currentToast->GetColor(); - textColor.w = opacity; - - // Generate new unique name for this toast - char windowName[50]; - snprintf(windowName, std::size(windowName), "##TOAST%d", i); - - SetNextWindowBgAlpha(opacity); - SetNextWindowPos(ImVec2(vpSize.x - kNotifyPaddingX, vpSize.y - kNotifyPaddingY - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f)); - Begin(windowName, nullptr, kNotifyToastFlags); - BringWindowToDisplayFront(GetCurrentWindow()); - - // Here we render the toast content - { - PushTextWrapPos(vpSize.x / 3.0f); // We want to support multi-line text, this will wrap the text after 1/3 of the screen width - - bool wasTitleRendered = false; - - // If an icon is set - if (!::IsNullOrEmpty(icon)) { - // Render icon text - PushStyleColor(ImGuiCol_Text, textColor); - TextUnformatted(icon); - PopStyleColor(); - wasTitleRendered = true; - } - - // If a title is set - if (!::IsNullOrEmpty(title)) { - // If a title and an icon is set, we want to render on same line - if (!::IsNullOrEmpty(icon)) - SameLine(); - - TextUnformatted(title); // Render title text - wasTitleRendered = true; - } else if (!::IsNullOrEmpty(defaultTitle)) { - if (!::IsNullOrEmpty(icon)) - SameLine(); - - TextUnformatted(defaultTitle); // Render default title text (ImGuiToastType_Success -> "Success", etc...) - wasTitleRendered = true; - } - - // In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically - if (wasTitleRendered && !::IsNullOrEmpty(content)) { - SetCursorPosY(GetCursorPosY() + 5.0f); // Must be a better way to do this!!!! - } - - // If a content is set - if (!::IsNullOrEmpty(content)) { - if (wasTitleRendered) { - Separator(); - } - - TextUnformatted(content); // Render content text - } - - PopTextWrapPos(); - } - - // Save height for next toasts - height += GetWindowHeight() + kNotifyPaddingMessageY; - - End(); - } -} diff --git a/source/30-game/EditorNotification.hpp b/source/30-game/EditorNotification.hpp deleted file mode 100644 index 3af8c2d..0000000 --- a/source/30-game/EditorNotification.hpp +++ /dev/null @@ -1,81 +0,0 @@ -// Adapted from https://github.com/patrickcjk/imgui-notify -#pragma once - -#include -#include - -enum ImGuiToastType { - ImGuiToastType_None, - ImGuiToastType_Success, - ImGuiToastType_Warning, - ImGuiToastType_Error, - ImGuiToastType_Info, - ImGuiToastType_COUNT -}; - -enum ImGuiToastPhase { - ImGuiToastPhase_FadeIn, - ImGuiToastPhase_Wait, - ImGuiToastPhase_FadeOut, - ImGuiToastPhase_Expired, - ImGuiToastPhase_COUNT -}; - -enum ImGuiToastPos { - ImGuiToastPos_TopLeft, - ImGuiToastPos_TopCenter, - ImGuiToastPos_TopRight, - ImGuiToastPos_BottomLeft, - ImGuiToastPos_BottomCenter, - ImGuiToastPos_BottomRight, - ImGuiToastPos_Center, - ImGuiToastPos_COUNT -}; - -constexpr int kNotifyMaxMsgLength = 4096; // Max message content length -constexpr float kNotifyPaddingX = 20.0f; // Bottom-left X padding -constexpr float kNotifyPaddingY = 20.0f; // Bottom-left Y padding -constexpr float kNotifyPaddingMessageY = 10.0f; // Padding Y between each message -constexpr uint64_t kNotifyFadeInOutTime = 150; // Fade in and out duration -constexpr uint64_t kNotifyDefaultDismiss = 3000; // Auto dismiss after X ms (default, applied only of no data provided in constructors) -constexpr float kNotifyOpacity = 1.0f; // 0-1 Toast opacity -constexpr ImGuiWindowFlags kNotifyToastFlags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing; - -class ImGuiToast { -private: - ImGuiToastType mType = ImGuiToastType_None; - char mTitle[kNotifyMaxMsgLength] = {}; - char mContent[kNotifyMaxMsgLength] = {}; - int mDismissTime = kNotifyDefaultDismiss; - uint64_t mCreationTime = 0; - -public: - ImGuiToast(ImGuiToastType type, int dismissTime = kNotifyDefaultDismiss); - ImGuiToast(ImGuiToastType type, const char* format, ...); - ImGuiToast(ImGuiToastType type, int dismissTime, const char* format, ...); - - void SetTitle(const char* format, ...); - void SetContent(const char* format, ...); - void SetType(const ImGuiToastType& type); - - const char* GetTitle(); - const char* GetDefaultTitle(); - ImGuiToastType GetType(); - ImVec4 GetColor(); - const char* GetIcon(); - const char* GetContent(); - - uint64_t GetElapsedTime(); - ImGuiToastPhase GetPhase(); - float GetFadePercent(); - -private: - void SetTitle(const char* format, va_list args); - void SetContent(const char* format, va_list args); -}; - -namespace ImGui { -void AddNotification(ImGuiToast toast); -void RemoveNotification(int index); -void ShowNotifications(); -} // namespace ImGui 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 - -#include -#include -#include -#include -#include - -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(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(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 callback; -}; - -static DialogObject gConfirmationDialog{}; - -void ImGui::DialogConfirmation(std::string message, std::function 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 99c522b..0000000 --- a/source/30-game/EditorUtils.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include "Color.hpp" -#include "EditorGuizmo.hpp" - -#include -#include - -// 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 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); - -} // namespace Utils 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 -#include - -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 - -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 -#include - -#include -#include -#include -#include - -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::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::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, 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 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::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(rapidjson::AsStringView(*rvType)); - assert(kind.has_value()); - auto obj = std::unique_ptr(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 diff --git a/source/30-game/GameObject.hpp b/source/30-game/GameObject.hpp deleted file mode 100644 index f975803..0000000 --- a/source/30-game/GameObject.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#include "EditorAttachment.hpp" -#include "Material.hpp" -#include "Renderer.hpp" -#include "VertexIndex.hpp" - -#include -#include - -#include -#include -#include -#include -#include - -namespace Tags { -enum class GameObjectKind { - KD_Generic, - KD_Player, - KD_SimpleGeometry, - KD_Building, - KD_LevelWrapper, - KD_COUNT, -}; -BRUSSEL_ENUM(GameObjectKind, ToString FromString ExcludeHeuristics); -} // namespace Tags - -class GameWorld; -class GameObject { -public: - using Kind = Tags::GameObjectKind; - using enum Tags::GameObjectKind; - -private: - std::unique_ptr mEditorAttachment; - GameWorld* mWorld; - GameObject* mParent; - PodVector 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& GetChildren() const; - void AddChild(GameObject* child); - GameObject* RemoveChild(int index); - GameObject* RemoveChild(GameObject* child); - void RemoveSelfFromParent(); - PodVector 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 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 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 diff --git a/source/30-game/GraphicsTags.cpp b/source/30-game/GraphicsTags.cpp deleted file mode 100644 index eb9a079..0000000 --- a/source/30-game/GraphicsTags.cpp +++ /dev/null @@ -1,273 +0,0 @@ -#include "GraphicsTags.hpp" - -#include -#include -#include - -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 GL_NONE; -} - -namespace ProjectBrussel_UNITY_ID { -struct GLTypeInfo { - robin_hood::unordered_flat_map enum2Name; - robin_hood::unordered_flat_map 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 GL_NONE; - } -} - -#include diff --git a/source/30-game/GraphicsTags.hpp b/source/30-game/GraphicsTags.hpp deleted file mode 100644 index cdf79eb..0000000 --- a/source/30-game/GraphicsTags.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -namespace Tags { -/// Vertex element semantics, used to identify the meaning of vertex buffer contents -enum VertexElementSemantic { - /// 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, -}; -BRUSSEL_ENUM(VertexElementSemantic, ToString FromString ExcludeHeuristics); - -enum VertexElementType { - 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; -BRUSSEL_ENUM(VertexElementType, ToString FromString ExcludeHeuristics); - -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 { - TF_Linear, - TF_Nearest, -}; -BRUSSEL_ENUM(TexFilter, ToString FromString ExcludeHeuristics); - -std::string_view GLTypeToString(GLenum); -GLenum GLTypeFromString(std::string_view name); - -constexpr auto kInvalidLocation = std::numeric_limits::max(); -} // namespace Tags - -#include 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 -#include -#include - -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 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 pixels, glm::ivec2 dimensions, int channels) { - mData = std::make_unique(pixels.size()); - std::memcpy(mData.get(), pixels.data(), pixels.size()); - mSize = dimensions; - mChannels = channels; - return true; -} - -bool Image::InitFromPixels(std::unique_ptr 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 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 -#include -#include -#include - -/// 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 mData; - glm::ivec2 mSize; - int mChannels; - -public: - Image(); - - bool InitFromImageFile(const char* filePath, int desiredChannels = 0); - bool InitFromImageData(std::span data, int desiredChannels = 0); - bool InitFromPixels(std::span pixels, glm::ivec2 dimensions, int channels); - bool InitFromPixels(std::unique_ptr 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 GetData() const; - glm::ivec2 GetSize() const; - int GetChannels() const; - bool IsEmpty() const; -}; diff --git a/source/30-game/Ires.cpp b/source/30-game/Ires.cpp deleted file mode 100644 index bfa4cdf..0000000 --- a/source/30-game/Ires.cpp +++ /dev/null @@ -1,409 +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 -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; -using namespace std::literals; - -IresObject::IresObject(Kind kind) - : mKind{ kind } { -} - -std::unique_ptr IresObject::Create(Kind kind) { - switch (kind) { - case KD_Texture: return std::make_unique(); - case KD_Shader: return std::make_unique(); - case KD_Material: return std::make_unique(); - case KD_SpriteFiles: return std::make_unique(); - case KD_Spritesheet: return std::make_unique(); - 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(""); -} - -void IresObject::ShowName() const { - if (IsAnnoymous()) { - ImGui::Text("", (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(""); - 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("", (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: ", (void*)this); - } else { - ImGui::Text("Name: %s", mName.c_str()); - } - - if (mUid.IsNull()) { - ImGui::TextUnformatted("Uid: "); - } 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::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::ReadBasic(const rapidjson::Value& value) { - auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); - if (!rvType) return nullptr; - auto kind = Metadata::EnumFromString(rapidjson::AsStringView(*rvType)); - assert(kind.has_value()); - auto ires = Create(kind.value()); - if (!ires) return nullptr; - - auto rvUid = rapidjson::GetProperty(value, rapidjson::kArrayType, "Uid"sv); - if (!rvUid) return nullptr; - ires->mUid.Read(*rvUid); - - 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 ires; - }; - - class IresLoadTimeContext final : public IresLoadingContext { - public: - // NOTE: pointer stability required - robin_hood::unordered_node_map candidates; - std::vector> candidatesByKind; - - IresLoadTimeContext() { - candidatesByKind.resize((int)IresObject::KD_COUNT); - } - - std::vector& GetByKind(IresObject::Kind kind) { - int i = static_cast(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.", 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(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.", cand->path.c_str()); - continue; - } - - ires->mMan = this; - mObjByUid.try_emplace(ires->GetUid(), ires); - } - } -} - -std::pair 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 writer(stream); - writer.SetFormatOptions(rapidjson::PrettyFormatOptions::kFormatSingleLineArray); - root.Accept(writer); -} - -IresObject* IresManager::FindIres(const Uid& uid) const { - auto iter = mObjByUid.find(uid); - if (iter != mObjByUid.end()) { - return iter->second.Get(); - } else { - return nullptr; - } -} - -#include diff --git a/source/30-game/Ires.hpp b/source/30-game/Ires.hpp deleted file mode 100644 index b6420f3..0000000 --- a/source/30-game/Ires.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once - -#include "EditorAttachment.hpp" -#include "EditorCore.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -// Forward declarations -class IresManager; -class IresWritingContext; -class IresLoadingContext; - -namespace Tags { -enum class IresObjectKind { - KD_Texture, - KD_Shader, - KD_Material, - KD_SpriteFiles, - KD_Spritesheet, - KD_COUNT, -}; -BRUSSEL_ENUM(IresObjectKind, ToString FromString ExcludeHeuristics); -} // 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 mEditorAttachment; // Transient - IresManager* mMan = nullptr; // Transient - Kind mKind; // Serialized in full mode - -public: - IresObject(Kind kind); - virtual ~IresObject() = default; - - static std::unique_ptr 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 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 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> mObjByUid; - -public: - void DiscoverFilesDesignatedLocation(); - void DiscoverFiles(const std::filesystem::path& dir); - - std::pair 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); - - const auto& GetObjects() const { return mObjByUid; } - virtual IresObject* FindIres(const Uid& uid) const override; -}; - -#include 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 -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace std::literals; -namespace fs = std::filesystem; - -constexpr auto kParentToRootObject = std::numeric_limits::max(); -constexpr auto kInvalidEntryId = std::numeric_limits::max(); - -struct Level::InstanciationEntry { - // If set to std::numeric_limits::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(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 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 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 9114a64..0000000 --- a/source/30-game/Level.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include "EditorCore.hpp" -#include "GameObject.hpp" - -#include -#include - -#include -#include -#include - -// 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 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 field in release mode? - struct LoadableObject { - RcPtr 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 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 { -private: - RcPtr 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 9b0c42d..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 -#include -#include -#include - -#include -#include -#include -#include -#include - -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 -TUniform& ObtainUniform(Shader* shader, const char* name, std::vector& 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 -void Material::SetVector(const char* name, const glm::vec& 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 -void Material::SetMatrix(const char* name, const glm::mat& 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 Material::GetVectors() const { - return mBoundVectors; -} - -std::span Material::GetMatrices() const { - return mBoundMatrices; -} - -std::span 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(*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(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(*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::max()); break; - default: ImGui::TextUnformatted("Unsupported scalar type"); break; - } - ImGui::Unindent(); - } - for (auto& field : mInstance->mBoundVectors) { - auto& decl = static_cast(*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(*shaderInfo.uniforms[field.infoUniformIndex]); - decl.ShowInfo(); - - // TODO - } - for (auto& field : mInstance->mBoundTextures) { - auto& decl = static_cast(*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(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 -#include -#include -#include -#include -#include -#include -#include -#include - -// 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 value; - /* Transient */ int infoUniformIndex; - /* Transient */ GLint location; - }; - - IresMaterial* mIres = nullptr; - RcPtr mShader; - std::vector mBoundScalars; - std::vector mBoundVectors; - std::vector mBoundMatrices; - std::vector 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 - void SetVector(const char* name, const glm::vec& vec); - - /// Instanciated for sizes (2,2) (3,3) (4,4) (2,3) (3,2) (2,4) (4,2) (3,4) (4,3) - template - void SetMatrix(const char* name, const glm::mat& mat); - - void SetTexture(const char* name, Texture* texture); - - std::span GetVectors() const; - std::span GetMatrices() const; - std::span 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 gDefaultMaterial; - -class IresMaterial : public IresObject { -private: - RcPtr 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 - -// 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 -#include -#include -#include - -struct StandardVertexExtra { - float u, v; - uint8_t r, g, b, a; -}; - -class StandardCpuMeshData { -public: - PodVector vertPositions; - PodVector vertExtra; - PodVector index; - size_t vertexCount; - size_t triangleCount; -}; - -class StandardCpuMesh { -// private: -// StandardCpuMeshData* mData = nullptr; -// RcPtr 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 -#include - -// 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 PlayerKeyBinds::GetKeyArray() { - return { &keyLeft, kPlayerKeyBindCount }; -} -std::span 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{ 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 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 d003a25..0000000 --- a/source/30-game/Player.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "GameObject.hpp" -#include "Material.hpp" -#include "RcPtr.hpp" -#include "Sprite.hpp" - -#define GLFW_INCLUDE_NONE -#include - -#include -#include - -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 GetKeyArray(); - std::span GetKeyStatusArray(); -}; - -class Player : public GameObject { -public: - std::vector 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 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 3497449..0000000 --- a/source/30-game/Renderer.cpp +++ /dev/null @@ -1,170 +0,0 @@ -#include "Renderer.hpp" - -#include "GameObject.hpp" - -#include -#include -#include -#include - -RenderObject::RenderObject() - : mVao{ GL_NONE } { -} - -RenderObject::~RenderObject() { - DeleteGLObjects(); -} - -GLuint RenderObject::GetGLVao() const { - return mVao; -} - -void RenderObject::RebuildIfNecessary() { - if (mVao != GL_NONE) { - 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(GL_NONE); -} - -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 != GL_NONE) { - glDeleteVertexArrays(1, &mVao); - mVao = GL_NONE; - } -} - -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); - - auto vpMatrix = mFrame.matrixProj * mFrame.matrixView; - - // 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) { - glm::mat4 objectMatrix(1.0f); - objectMatrix = glm::translate(objectMatrix, gameObject->GetPos()); - objectMatrix *= glm::toMat4(gameObject->GetRotation()); - objectMatrix = glm::scale(objectMatrix, gameObject->GetScale()); - glm::mat4 transform = vpMatrix * objectMatrix; - - glUniformMatrix4fv(shader->autofill_Transform, 1, GL_FALSE, &transform[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); - } -} diff --git a/source/30-game/Renderer.hpp b/source/30-game/Renderer.hpp deleted file mode 100644 index 98a9f28..0000000 --- a/source/30-game/Renderer.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include "Camera.hpp" -#include "Material.hpp" -#include "RcPtr.hpp" -#include "VertexIndex.hpp" - -#include -#include -#include - -// TODO add optional support for OpenGL separate attrib binding & only depend on vertex format - -class GameObject; - -class RenderObject { -public: - RcPtr autofill_TextureAtlas; - -private: - RcPtr mMaterial; - RcPtr mIndexBuf; - RcPtr 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 { -private: - RendererFrameInfo mFrame; - bool mInsideFrame = false; - -public: - 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(); -}; 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 - -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 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 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 c261fbb..0000000 --- a/source/30-game/SceneThings.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "Color.hpp" -#include "GameObject.hpp" -#include "Renderer.hpp" - -#include -#include - -class SimpleGeometryObject : public GameObject { -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 GetRenderObjects() const override; -}; - -class BuildingObject : public GameObject { -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 GetRenderObjects() const override; -}; diff --git a/source/30-game/Shader.cpp b/source/30-game/Shader.cpp deleted file mode 100644 index 4a58635..0000000 --- a/source/30-game/Shader.cpp +++ /dev/null @@ -1,773 +0,0 @@ -#include "Shader.hpp" - -#include "AppConfig.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -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))); -} - -bool ShaderThingId::IsValid() const { - return kind == KD_Invalid; -} - -namespace ProjectBrussel_UNITY_ID { -GLuint FindLocation(const std::vector& 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); -} - -ShaderVariable* ShaderInfo::FindVariable(const ShaderThingId& thing) { - switch (thing.kind) { - case ShaderThingId::KD_Input: return &inputs[thing.index]; - case ShaderThingId::KD_Output: return &outputs[thing.index]; - case ShaderThingId::KD_Uniform: return uniforms[thing.index].get(); - case ShaderThingId::KD_Invalid: break; - } - return nullptr; -} - -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(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; - } - - 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; - } - - return false; -} - -std::unique_ptr CreateVariable(GLenum type, GLuint loc) { - GLenum scalarType; - int width; - int height; - if (QueryMathInfo(type, scalarType, width, height)) { - auto res = std::make_unique(); - res->location = loc; - res->scalarType = type; - res->width = width; - res->height = height; - return res; - } - - if (QuerySamplerInfo(type)) { - auto res = std::make_unique(); - res->location = loc; - res->type = type; - return res; - } - - return nullptr; -} -} // 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& 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; -} - -namespace ProjectBrussel_UNITY_ID { -void WriteShaderVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderVariable& var) { - value.AddMember("Name", var.name, root.GetAllocator()); - value.AddMember("Semantic", rapidjson::StringRef(Metadata::EnumToString(var.semantic)), root.GetAllocator()); - value.AddMember("OpenGLLocation", var.location, root.GetAllocator()); -} - -bool ReadShaderVariable(const rapidjson::Value& value, ShaderVariable& var) { - using namespace Tags; - - BRUSSEL_JSON_GET(value, "Name", std::string, var.name, return false); - { // Semantic - auto rvSemantic = rapidjson::GetProperty(value, rapidjson::kStringType, "Semantic"sv); - if (!rvSemantic) { - var.semantic = VES_Generic; - } else { - auto str = rapidjson::AsStringView(*rvSemantic); - auto lookup = Metadata::EnumFromString(str); - var.semantic = lookup.value_or(VES_Generic); - } - } - BRUSSEL_JSON_GET_DEFAULT(value, "OpenGLLocation", int, var.location, 0); - return true; -} - -void WriteShaderMathVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderMathVariable& var) { - WriteShaderVariable(value, root, var); - value.AddMember("ScalarType", rapidjson::StringRef(Tags::GLTypeToString(var.scalarType)), root.GetAllocator()); - value.AddMember("Width", var.width, root.GetAllocator()); - value.AddMember("Height", var.height, root.GetAllocator()); - value.AddMember("ArrayLength", var.arrayLength, root.GetAllocator()); -} - -bool ReadShaderMathVariable(const rapidjson::Value& value, ShaderMathVariable& var) { - if (!ReadShaderVariable(value, var)) return false; - { - auto rvScalar = rapidjson::GetProperty(value, rapidjson::kStringType, "ScalarType"sv); - if (!rvScalar) return false; - var.scalarType = Tags::GLTypeFromString(rapidjson::AsStringView(*rvScalar)); - } - BRUSSEL_JSON_GET(value, "Width", int, var.width, return false); - BRUSSEL_JSON_GET(value, "Height", int, var.height, return false); - BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1); - return true; -} - -void WriteShaderSamplerVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderSamplerVariable& var) { - WriteShaderVariable(value, root, var); - // TODO -} - -bool ReadShaderSamplerVariable(const rapidjson::Value& value, ShaderSamplerVariable& var) { - if (!ReadShaderVariable(value, var)) return false; - BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1); - return true; -} -} // namespace ProjectBrussel_UNITY_ID - -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) { - uniform->ShowInfo(); - } - 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); - - auto& shaderInfo = mInstance->mInfo; - - value.AddMember("SourceFile", mSourceFile, root.GetAllocator()); - - auto SaveMathVars = [&](const char* name, const std::vector& vars) { - rapidjson::Value rvThings(rapidjson::kArrayType); - for (auto& thing : vars) { - rapidjson::Value rvThing(rapidjson::kObjectType); - WriteShaderMathVariable(rvThing, root, thing); - - rvThings.PushBack(rvThing, root.GetAllocator()); - } - value.AddMember(rapidjson::StringRef(name), rvThings, root.GetAllocator()); - }; - SaveMathVars("Inputs", shaderInfo.inputs); - SaveMathVars("Outputs", shaderInfo.outputs); - - // TODO uniforms -} - -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(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& 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 { - if (type == "Math"sv) { - auto uniform = std::make_unique(); - ReadShaderMathVariable(*rvValue, *uniform); - - return uniform; - } else if (type == "Sampler"sv) { - auto uniform = std::make_unique(); - 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 707e6cc..0000000 --- a/source/30-game/Shader.hpp +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once - -#include "GraphicsTags.hpp" -#include "Ires.hpp" -#include "RcPtr.hpp" -#include "Utils.hpp" - -#include -#include -#include -#include -#include - -// TODO move to variable after pattern matching is in the language - -// Forward declarations -class Shader; -class IresShader; - -struct ShaderVariable { - enum Kind { - KD_Math, - KD_Sampler, - }; - - std::string name; - Kind kind; - GLuint location; - Tags::VertexElementSemantic semantic = Tags::VES_Generic; - - virtual void ShowInfo() const = 0; - -protected: - ShaderVariable(Kind kind) - : kind{ kind } {} -}; - -struct ShaderMathVariable : public ShaderVariable { - GLenum scalarType; - int arrayLength; - int width; - int height; - - ShaderMathVariable() - : ShaderVariable(KD_Math) {} - - virtual void ShowInfo() const override; -}; - -struct ShaderSamplerVariable : public ShaderVariable { - GLenum type; - int arrayLength; - - ShaderSamplerVariable() - : ShaderVariable(KD_Sampler) {} - - virtual void ShowInfo() const override; -}; - -struct ShaderThingId { - enum Kind { - KD_Input, - KD_Output, - KD_Uniform, - KD_Invalid, - }; - - Kind kind = KD_Invalid; - int index = 0; - - bool IsValid() const; -}; - -struct ShaderInfo { - robin_hood::unordered_map things; - std::vector inputs; - std::vector outputs; - std::vector> uniforms; - - GLuint FindInputLocation(Tags::VertexElementSemantic semantic); - GLuint FindOutputLocation(Tags::VertexElementSemantic semantic); - ShaderVariable* FindVariable(const ShaderThingId& thing); -}; - -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 gDefaultShader; - -class IresShader : public IresObject { -private: - RcPtr 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 -#include -#include -#include - -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 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(); - if (atlas->InitAtlas(atlasIn, &atlasOut) != Texture::EC_Success) { - return nullptr; - } - - auto sprite = std::make_unique(); - 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(*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::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(); - if (atlas->InitFromFile(path) != Texture::EC_Success) { - return nullptr; - } - - auto sprite = std::make_unique(); - 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 -#include -#include -#include -#include - -class SpriteDefinition : public RefCounted { - friend class IresSpriteFiles; - friend class IresSpritesheet; - -private: - RcPtr mAtlas; - glm::ivec2 mBoundingBox; - std::vector 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 mInstance; - std::vector 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 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 mSprites; -}; - -class Sprite { -private: - RcPtr 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 -#include -#include -#include -#include - -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 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(size.x); - rect.h = static_cast(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 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 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(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(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 -#include -#include -#include - -// 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 sources; - PackingMode packingMode; - }; - struct AtlasOutput { - std::vector 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 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 - -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 -#include -#include -#include - -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, 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 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 d4a8344..0000000 --- a/source/30-game/World.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "World.hpp" - -#include "GameObject.hpp" -#include "PodVector.hpp" - -#include - -namespace ProjectBrussel_UNITY_ID { -template -void CallGameObjectRecursive(GameObject* start, TFunction&& func) { - PodVector 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); - } -} - -struct DrawCall { - GLuint vao; - GLuint vbo; -}; -} // 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() { - if (mAwakened) return; - - ProjectBrussel_UNITY_ID::CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Awaken(); }); - mAwakened = true; -} - -void GameWorld::Resleep() { - if (!mAwakened) return; - - ProjectBrussel_UNITY_ID::CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Resleep(); }); - mAwakened = false; -} - -void GameWorld::Update() { - 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/buildfile b/source/30-game/buildfile deleted file mode 100644 index e69de29..0000000 diff --git a/source/30-game/main.cpp b/source/30-game/main.cpp deleted file mode 100644 index c49fc0b..0000000 --- a/source/30-game/main.cpp +++ /dev/null @@ -1,461 +0,0 @@ -#include "App.hpp" - -#include "AppConfig.hpp" -#include "CommonVertexIndex.hpp" -#include "EditorGuizmo.hpp" -#include "Ires.hpp" -#include "Level.hpp" -#include "Material.hpp" -#include "Shader.hpp" - -#define GLFW_INCLUDE_NONE -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -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 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(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(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(glfwGetWindowUserPointer(window)); - auto app = userData->app; - app->HandleKey(keyboard, key, action); - } -} - -// https://stackoverflow.com/questions/54499256/how-to-find-the-saved-games-folder-programmatically-in-c-c -#if defined(_WIN32) -# if defined(__MINGW32__) -# include -# else -# include -# endif -# include -# 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; - - constexpr auto kImGuiBackend = "imgui-backend"; - constexpr auto kGameDataDir = "game-data-directory"; - constexpr auto kGameAssetDir = "game-asset-directory"; - - cxxopts::Options options(std::string(AppConfig::kAppName), ""); - // clang-format off - options.add_options() - (kImGuiBackend, "ImGui backend. Options: opengl2, opengl3. Leave empty to default.", cxxopts::value()) - (kGameAssetDir, "Directory in which assets are looked up from. Can be relative paths to the executable.", cxxopts::value()->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()) - ; - // clang-format on - auto args = options.parse(argc, argv); - - bool imguiUseOpenGL3; - if (args.count(kImGuiBackend) > 0) { - auto imguiBackend = args[kImGuiBackend].as(); - 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(); - - 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 { -#if defined(_WIN32) - fs::path dataDir; - - PWSTR path = nullptr; - HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, nullptr, &path); - if (SUCCEEDED(hr)) { - dataDir = fs::path(path) / AppConfig::kAppName; - CoTaskMemFree(path); - - fs::create_directories(dataDir); - } 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 dataDir = fs::path("~/Library/Application Support/") / AppConfig::kAppName; - fs::create_directories(dataDir); -#elif defined(__linux__) - auto dataDir = GetEnvVar("XDG_DATA_HOME", "~/.local/share") / AppConfig::kAppName; - fs::create_directories(dataDir); -#endif - } - - if (args.count(kGameDataDir) > 0) { - auto dataDir = args[kGameDataDir].as(); - - fs::path dataDirPath(dataDir); - fs::create_directories(dataDir); - - AppConfig::dataDir = std::move(dataDir); - AppConfig::dataDirPath = std::move(dataDirPath); - } else { - // TODO platform default path - AppConfig::dataDir = "."; - AppConfig::dataDirPath = fs::path("."); - } - - if (!glfwInit()) { - return -1; - } - - glfwSetErrorCallback(&GlfwErrorCallback); - - 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, "ImGui Command Palette Example", 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); - - // TODO setup opengl debug context - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - return -3; - } - - 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(); - } - - 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, - }); - - // 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; -} diff --git a/source/CodegenCompiler/CodegenConfig.hpp b/source/CodegenCompiler/CodegenConfig.hpp new file mode 100644 index 0000000..b9dc56c --- /dev/null +++ b/source/CodegenCompiler/CodegenConfig.hpp @@ -0,0 +1,11 @@ +#pragma once + +#ifndef CODEGEN_DEBUG_PRINT +# define CODEGEN_DEBUG_PRINT 0 +#endif + +#if CODEGEN_DEBUG_PRINT +# define DEBUG_PRINTF(...) printf(__VA_ARGS__) +#else +# define DEBUG_PRINTF(...) +#endif diff --git a/source/CodegenCompiler/CodegenDecl.cpp b/source/CodegenCompiler/CodegenDecl.cpp new file mode 100644 index 0000000..7cf21ce --- /dev/null +++ b/source/CodegenCompiler/CodegenDecl.cpp @@ -0,0 +1,49 @@ +#include "CodegenDecl.hpp" + +#include + +static EnumValuePattern NextPattern(EnumValuePattern val) { + return (EnumValuePattern)(val + 1); +} + +EnumValuePattern DeclEnum::CalcPattern() const { + if (elements.empty()) return EVP_Continuous; + + auto pattern = EVP_Continuous; +restart: + auto lastVal = elements[0].value; + for (size_t i = 1; i < elements.size(); ++i) { + auto currVal = elements[i].value; + switch (pattern) { + case EVP_Continuous: { + bool satisfy = lastVal + 1 == currVal; + if (!satisfy) { + pattern = NextPattern(pattern); + goto restart; + } + } break; + + case EVP_Bits: { + bool satisfy = (lastVal << 1) == currVal; + if (!satisfy) { + pattern = NextPattern(pattern); + goto restart; + } + } break; + + // A random pattern can match anything + case EVP_Random: + case EVP_COUNT: break; + } + lastVal = currVal; + } + + return pattern; +} + +EnumValuePattern DeclEnum::GetPattern() const { + if (pattern == EVP_COUNT) { + pattern = CalcPattern(); + } + return pattern; +} diff --git a/source/CodegenCompiler/CodegenDecl.hpp b/source/CodegenCompiler/CodegenDecl.hpp new file mode 100644 index 0000000..0728c08 --- /dev/null +++ b/source/CodegenCompiler/CodegenDecl.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include +#include + +// TODO replace std::string name with std::string_view into the token storage? + +struct DeclNamespace { + DeclNamespace* container = nullptr; + std::string name; + std::string_view fullname; // View into storage map key +}; + +struct DeclStruct; +struct DeclMemberVariable { + DeclStruct* containerStruct = nullptr; + std::string name; + std::string type; + std::string getterName; + std::string setterName; +}; +struct DeclMemberFunction { + DeclStruct* containerStruct = nullptr; + // TODO +}; + +// Structs or classes +struct DeclStruct { + DeclNamespace* container = nullptr; + std::vector baseClasses; + std::vector memberVariables; + std::vector generatedVariables; + std::vector memberFunctions; + std::vector generatedFunctions; + std::string name; + std::string_view fullname; + + // Scanned generation options + bool generating : 1 = false; + bool generatingInheritanceHiearchy : 1 = false; +}; + +enum EnumUnderlyingType { + EUT_Int8, + EUT_Int16, + EUT_Int32, + EUT_Int64, + EUT_Uint8, + EUT_Uint16, + EUT_Uint32, + EUT_Uint64, + EUT_COUNT, +}; + +enum EnumValuePattern { + // The numbers cover n..m with no gaps + EVP_Continuous, + // The numbers cover for i in n..m, 1 << i + // e.g. [0] = 1 << 0, + // [1] = 1 << 1. + // [2] = 1 << 2. etc. + EVP_Bits, + // The numbesr don't have a particular pattern + EVP_Random, + EVP_COUNT, +}; + +struct DeclEnumElement { + std::string name; + // TODO support int64_t, etc. enum underlying types + uint64_t value; +}; + +struct DeclEnum { + DeclNamespace* container = nullptr; + std::string name; + std::string_view fullname; + std::vector elements; + EnumUnderlyingType underlyingType; + // Start with invalid value, calculate on demand + mutable EnumValuePattern pattern = EVP_COUNT; + + EnumValuePattern CalcPattern() const; + EnumValuePattern GetPattern() const; +}; + +struct DeclFunctionArgument { + std::string type; + std::string name; +}; + +struct DeclFunction { + DeclNamespace* container = nullptr; + // Things like extern, static, etc. that gets written before the function return type + std::string prefix; + std::string name; + std::string_view fullname; + std::string returnType; + std::vector arguments; + std::string body; +}; diff --git a/source/CodegenCompiler/CodegenInput.cpp b/source/CodegenCompiler/CodegenInput.cpp new file mode 100644 index 0000000..0dced0e --- /dev/null +++ b/source/CodegenCompiler/CodegenInput.cpp @@ -0,0 +1,99 @@ +#include "CodegenInput.hpp" + +#include +#include + +#include +#include + +struct SomeDecl { + std::variant v; +}; + +class CodegenInput::Private { +public: + // We want address stability for everything + robin_hood::unordered_node_map decls; + robin_hood::unordered_node_map namespaces; +}; + +CodegenInput::CodegenInput() + : m{ new Private() } // +{ +} + +CodegenInput::~CodegenInput() { + delete m; +} + +#define STORE_DECL_OF_TYPE(DeclType, fullname, decl) \ + auto [iter, success] = m->decls.try_emplace(std::move(fullname), SomeDecl{ .v = std::move(decl) }); \ + auto& key = iter->first; \ + auto& val = iter->second; \ + auto& declRef = std::get(val.v); \ + declRef.fullname = key; \ + return &declRef + +DeclEnum* CodegenInput::AddEnum(std::string fullname, DeclEnum decl) { +#if CODEGEN_DEBUG_PRINT + printf("Committed enum '%s'\n", decl.name.c_str()); + for (auto& elm : decl.elements) { + printf(" - element %s = %" PRId64 "\n", elm.name.c_str(), elm.value); + } +#endif + + STORE_DECL_OF_TYPE(DeclEnum, fullname, decl); +} + +DeclStruct* CodegenInput::AddStruct(std::string fullname, DeclStruct decl) { +#if CODEGEN_DEBUG_PRINT + printf("Committed struct '%s'\n", decl.name.c_str()); + printf(" Base classes:\n"); + for (auto& base : decl.baseClasses) { + printf(" - %.*s\n", PRINTF_STRING_VIEW(base->name)); + } +#endif + + STORE_DECL_OF_TYPE(DeclStruct, fullname, decl); +} + +#define FIND_DECL_OF_TYPE(DeclType) \ + auto iter = m->decls.find(name); \ + if (iter != m->decls.end()) { \ + auto& some = iter->second.v; \ + if (auto decl = std::get_if(&some)) { \ + return decl; \ + } \ + } \ + return nullptr + +const DeclEnum* CodegenInput::FindEnum(std::string_view name) const { + FIND_DECL_OF_TYPE(DeclEnum); +} + +const DeclStruct* CodegenInput::FindStruct(std::string_view name) const { + FIND_DECL_OF_TYPE(DeclStruct); +} + +DeclNamespace* CodegenInput::AddNamespace(DeclNamespace ns) { + auto path = Utils::MakeFullName(""sv, &ns); + auto [iter, success] = m->namespaces.try_emplace(std::move(path), std::move(ns)); + auto& nsRef = iter->second; + if (success) { + nsRef.fullname = iter->first; + } + return &nsRef; +} + +const DeclNamespace* CodegenInput::FindNamespace(std::string_view fullname) const { + auto iter = m->namespaces.find(fullname); + if (iter != m->namespaces.end()) { + return &iter->second; + } else { + return nullptr; + } +} + +DeclNamespace* CodegenInput::FindNamespace(std::string_view name) { + return const_cast(const_cast(this)->FindNamespace(name)); +} diff --git a/source/CodegenCompiler/CodegenInput.hpp b/source/CodegenCompiler/CodegenInput.hpp new file mode 100644 index 0000000..63c2673 --- /dev/null +++ b/source/CodegenCompiler/CodegenInput.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" +#include "CodegenUtils.hpp" + +#include +#include +#include + +using namespace std::literals; + +class CodegenInput { +private: + class Private; + Private* m; + +public: + CodegenInput(); + ~CodegenInput(); + + DeclEnum* AddEnum(std::string fullname, DeclEnum decl); + DeclStruct* AddStruct(std::string fullname, DeclStruct decl); + + const DeclEnum* FindEnum(std::string_view name) const; + const DeclStruct* FindStruct(std::string_view name) const; + + DeclNamespace* AddNamespace(DeclNamespace ns); + + const DeclNamespace* FindNamespace(std::string_view fullname) const; + DeclNamespace* FindNamespace(std::string_view name); +}; diff --git a/source/CodegenCompiler/CodegenLexer.cpp b/source/CodegenCompiler/CodegenLexer.cpp new file mode 100644 index 0000000..dab6aea --- /dev/null +++ b/source/CodegenCompiler/CodegenLexer.cpp @@ -0,0 +1,183 @@ +#include "CodegenLexer.hpp" + +#include + +bool StbTokenIsSingleChar(int lexerToken) { + return lexerToken >= 0 && lexerToken < 256; +} + +bool StbTokenIsMultiChar(int lexerToken) { + return !StbTokenIsMultiChar(lexerToken); +} + +std::string CombineTokens(std::span tokens) { + size_t length = 0; + for (auto& token : tokens) { + length += token.text.size(); + } + std::string result; + result.reserve(length); + for (auto& token : tokens) { + result += token.text; + } + return result; +} + +const StbLexerToken& CodegenLexer::Current() const { + assert(idx < tokens.size()); + return tokens[idx]; +} + +void CodegenLexer::InitializeFrom(std::string_view source) { + this->tokens = {}; + this->idx = 0; + + stb_lexer lexer; + char stringStorage[65536]; + const char* srcBegin = source.data(); + const char* srcEnd = srcBegin + source.length(); + stb_c_lexer_init(&lexer, srcBegin, srcEnd, stringStorage, sizeof(stringStorage)); + + struct TokenCombiningPattern { + StbLexerToken result; + char matchChars[16]; + }; + + const TokenCombiningPattern kDoubleColon = { + .result = { + .text = "::", + .type = CLEX_ext_double_colon, + }, + .matchChars = { ':', ':', '\0' }, + }; + const TokenCombiningPattern kDotDotDot = { + .result = { + .text = "...", + .type = CLEX_ext_dot_dot_dot, + }, + .matchChars = { '.', '.', '.', '\0' }, + }; + + const TokenCombiningPattern* currentState = nullptr; + int currentStateCharIdx = 0; + + while (true) { + // See stb_c_lexer.h's comments, here are a few additinos that aren't made clear in the file: + // - `lexer->token` (noted as "token" below) after calling stb_c_lexer_get_token() contains either: + // 1. 0 <= token < 256: an ASCII character (more precisely a single char that the lexer ate; technically can be an incomplete code unit) + // 2. token < 0: an unknown token + // 3. One of the `CLEX_*` enums: a special, recognized token such as an operator + + int stbToken = stb_c_lexer_get_token(&lexer); + if (stbToken == 0) { + // EOF + break; + } + + if (lexer.token == CLEX_parse_error) { + printf("[ERROR] stb_c_lexer countered a parse error.\n"); + // TODO how to handle? + continue; + } + + StbLexerToken token; + if (StbTokenIsSingleChar(lexer.token)) { + char c = lexer.token; + + token.type = CLEX_ext_single_char; + token.text = std::string(1, c); + + if (!currentState) { +#define TRY_START_MATCH(states) \ + if (states.matchChars[0] == c) { \ + currentState = &states; \ + currentStateCharIdx = 1; \ + } + TRY_START_MATCH(kDoubleColon); + TRY_START_MATCH(kDotDotDot); +#undef TRY_START_MATCH + } else { + if (currentState->matchChars[currentStateCharIdx] == c) { + // Match success + ++currentStateCharIdx; + + // If we matched all of the chars... + if (currentState->matchChars[currentStateCharIdx] == '\0') { + // We matched (currentStateCharIdx) tokens though this one is pushed into the vector, leaving (currentStateCharIdx - 1) tokens to be removed + for (int i = 0, count = currentStateCharIdx - 1; i < count; ++i) { + tokens.pop_back(); + } + + // Set the current token to desired result + token = currentState->result; + + currentState = nullptr; + currentStateCharIdx = 0; + } + } else { + // Match fail, reset + + currentState = nullptr; + currentStateCharIdx = 0; + } + } + } else { + token.type = lexer.token; + // WORKAROUND: use null terminated string, stb_c_lexer doens't set string_len properly when parsing identifiers + token.text = std::string(lexer.string); + + switch (token.type) { + case CLEX_intlit: + token.lexerIntNumber = lexer.int_number; + break; + + case CLEX_floatlit: + token.lexerRealNumber = lexer.real_number; + break; + } + } + tokens.push_back(std::move(token)); + token = {}; + } +} + +const StbLexerToken* CodegenLexer::TryConsumeToken(int type) { + auto& token = tokens[idx]; + if (token.type == type) { + ++idx; + return &token; + } + return nullptr; +} + +const StbLexerToken* CodegenLexer::TryConsumeSingleCharToken(char c) { + auto& token = tokens[idx]; + if (token.type == CLEX_ext_single_char && + token.text[0] == c) + { + ++idx; + return &token; + } + return nullptr; +} + +void CodegenLexer::SkipUntilToken(int type) { + while (idx < tokens.size()) { + if (Current().type == type) { + break; + } + ++idx; + } +} + +void CodegenLexer::SkipUntilTokenSingleChar(char c) { + while (idx < tokens.size()) { + auto& curr = Current(); + if (curr.type == CLEX_ext_single_char && + curr.text[0] == c) + { + break; + } + ++idx; + } +} diff --git a/source/CodegenCompiler/CodegenLexer.hpp b/source/CodegenCompiler/CodegenLexer.hpp new file mode 100644 index 0000000..76adce6 --- /dev/null +++ b/source/CodegenCompiler/CodegenLexer.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +enum { + CLEX_ext_single_char = CLEX_first_unused_token, + CLEX_ext_double_colon, + CLEX_ext_dot_dot_dot, + CLEX_ext_COUNT, +}; + +struct StbLexerToken { + std::string text; + + union { + double lexerRealNumber; + long lexerIntNumber; + }; + + // Can either be CLEX_* or CLEX_ext_* values + int type; +}; + +bool StbTokenIsSingleChar(int lexerToken); +bool StbTokenIsMultiChar(int lexerToken); +std::string CombineTokens(std::span tokens); + +struct CodegenLexer { + std::vector tokens; + size_t idx = 0; + + void InitializeFrom(std::string_view source); + + const StbLexerToken& Current() const; + + const StbLexerToken* TryConsumeToken(int type); + const StbLexerToken* TryConsumeSingleCharToken(char c); + + void SkipUntilToken(int type); + void SkipUntilTokenSingleChar(char c); +}; diff --git a/source/CodegenCompiler/CodegenOutput.cpp b/source/CodegenCompiler/CodegenOutput.cpp new file mode 100644 index 0000000..ccd163c --- /dev/null +++ b/source/CodegenCompiler/CodegenOutput.cpp @@ -0,0 +1,46 @@ +#include "CodegenOutput.hpp" + +#include "CodegenUtils.hpp" + +void CodegenOutput::AddRequestInclude(std::string_view include) { + if (!mRequestIncludes.contains(include)) { + mRequestIncludes.insert(std::string(include)); + } +} + +void CodegenOutput::AddOutputThing(CodegenOutputThing thing) { + mOutThings.push_back(std::move(thing)); +} + +void CodegenOutput::MergeContents(CodegenOutput other) { + std::move(other.mOutThings.begin(), other.mOutThings.end(), std::back_inserter(this->mOutThings)); + std::move(other.mOutStructs.begin(), other.mOutStructs.end(), std::back_inserter(this->mOutStructs)); + std::move(other.mOutEnums.begin(), other.mOutEnums.end(), std::back_inserter(this->mOutEnums)); + std::move(other.mOutFunctions.begin(), other.mOutFunctions.end(), std::back_inserter(this->mOutFunctions)); +} + +void CodegenOutput::Write(FILE* file) const { + for (auto& include : mRequestIncludes) { + // TODO how to resolve to the correct include paths? + WRITE_FMT_LN(file, "#include <%s>", include.c_str()); + } + + for (auto& thing : mOutThings) { + fwrite(thing.text.c_str(), sizeof(char), thing.text.size(), file); + WRITE_LIT(file, "\n"); + } + + for (auto& declStruct : mOutStructs) { + WRITE_FMT_LN(file, "struct %s {", declStruct.name.c_str()); + // TODO + WRITE_LIT_LN(file, "};"); + } + + for (auto& declEnum : mOutEnums) { + // TODO + } + + for (auto& declFunc : mOutFunctions) { + // TODO + } +} diff --git a/source/CodegenCompiler/CodegenOutput.hpp b/source/CodegenCompiler/CodegenOutput.hpp new file mode 100644 index 0000000..aa28715 --- /dev/null +++ b/source/CodegenCompiler/CodegenOutput.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "CodegenDecl.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +// A generic "thing" (could be anything, comments, string-concated functionsm, etc.) to spit into the output file +struct CodegenOutputThing { + std::string text; +}; + +class CodegenOutput { +private: + robin_hood::unordered_set mRequestIncludes; + std::vector mOutThings; + std::vector mOutStructs; + std::vector mOutEnums; + std::vector mOutFunctions; + +public: + std::string optionOutPrefix; + // Whether to add prefixes mOutPrefix to all global names or not + bool optionAutoAddPrefix : 1 = false; + +public: + void AddRequestInclude(std::string_view include); + void AddOutputThing(CodegenOutputThing thing); + + void MergeContents(CodegenOutput other); + + void Write(FILE* file) const; +}; diff --git a/source/CodegenCompiler/CodegenUtils.cpp b/source/CodegenCompiler/CodegenUtils.cpp new file mode 100644 index 0000000..0c70cb6 --- /dev/null +++ b/source/CodegenCompiler/CodegenUtils.cpp @@ -0,0 +1,146 @@ +#include "CodegenUtils.hpp" + +#include +#include +#include + +#include +#include + +bool Utils::WriteOutputFile(const CodegenOutput& output, const char* path) { + auto outputFile = Utils::OpenCstdioFile(path, Utils::WriteTruncate); + if (!outputFile) { + printf("[ERROR] unable to open output file %s\n", path); + return false; + } + DEFER { fclose(outputFile); }; + + DEBUG_PRINTF("Writing output %s\n", path); + output.Write(outputFile); + + return true; +} + +std::string Utils::MakeFullName(std::string_view name, DeclNamespace* ns) { + size_t length = 0; + std::vector components; + if (!name.empty()) { + components.push_back(name); + length += name.length(); + } + + DeclNamespace* currentNamespace = ns; + while (currentNamespace) { + components.push_back(currentNamespace->name); + length += currentNamespace->name.size() + /*::*/ 2; + currentNamespace = currentNamespace->container; + } + + std::string fullname; + fullname.reserve(length); + for (auto it = components.rbegin(); it != components.rend(); ++it) { + fullname += *it; + fullname += "::"; + } + // Get rid of the last "::" + fullname.pop_back(); + fullname.pop_back(); + + return fullname; +} + +// NOTE: assuming we are only dealing with ASCII characters +static bool IsLowerCase(char c) { + return c >= 'a' && c <= 'z'; +} +static bool IsUpperCase(char c) { + return c >= 'A' && c <= 'Z'; +} +static bool IsAlphabetic(char c) { + return IsLowerCase(c) || IsUpperCase(c); +} +static char MakeUpperCase(char c) { + if (IsAlphabetic(c)) { + return IsUpperCase(c) + ? c + : ('A' + (c - 'a')); + } + return c; +} + +std::vector Utils::SplitIdentifier(std::string_view name) { + // TODO handle SCREAMING_CASE + + size_t chunkStart = 0; + size_t chunkEnd = 0; + std::vector result; + auto PushChunk = [&]() { result.push_back(std::string_view(name.begin() + chunkStart, name.begin() + chunkEnd)); }; + while (chunkEnd < name.size()) { + char c = name[chunkEnd]; + if (IsUpperCase(c)) { + // Start of next chunk, using camelCase or PascalCase + PushChunk(); + chunkStart = chunkEnd; + chunkEnd = chunkStart + 1; + continue; + } else if (c == '_') { + // End of this chunk, using snake_case + PushChunk(); + chunkStart = chunkEnd + 1; + chunkEnd = chunkStart + 1; + continue; + } else if (c == '-') { + // End of this chunk, using kebab-case + PushChunk(); + chunkStart = chunkEnd + 1; + chunkEnd = chunkStart + 1; + continue; + } + ++chunkEnd; + } + + if ((chunkEnd - chunkStart) >= 1) { + PushChunk(); + } + + return result; +} + +std::string Utils::MakePascalCase(std::string_view name) { + std::string result; + for (auto part : SplitIdentifier(name)) { + result += MakeUpperCase(part[0]); + result += part.substr(1); + } + return result; +} + +void Utils::ProduceGeneratedHeader(const char* headerFilename, CodegenOutput& header, const char* sourceFilename, CodegenOutput& source) { + CodegenOutputThing headerOut; + headerOut.text += &R"""( +// This file is generated. Any changes will be overidden when building. +#pragma once +#include +#include +#include +)"""[1]; + + CodegenOutputThing sourceOut; + APPEND_LIT_LN(sourceOut.text, "// This file is generated. Any changes will be overidden when building."); + APPEND_FMT_LN(sourceOut.text, "#include \"%s\"", headerFilename); + sourceOut.text += &R"""( +#include +#include +#include +using namespace std::literals; +)"""[1]; + + header.AddOutputThing(std::move(headerOut)); + source.AddOutputThing(std::move(sourceOut)); +} + +void Utils::ProduceClassTypeInfo(CodegenOutput& source, std::string_view className, const DeclNamespace* ns) { + CodegenOutputThing thing; + + source.AddOutputThing(std::move(thing)); +} diff --git a/source/CodegenCompiler/CodegenUtils.hpp b/source/CodegenCompiler/CodegenUtils.hpp new file mode 100644 index 0000000..62d5400 --- /dev/null +++ b/source/CodegenCompiler/CodegenUtils.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" +#include "CodegenOutput.hpp" + +#include +#include + +// I give up, hopefully nothing overflows this buffer +// TODO handle buffer sizing properly + +#define INPLACE_FMT(varName, format, ...) \ + char varName[2048]; \ + snprintf(varName, sizeof(varName), format, __VA_ARGS__); + +#define APPEND_LIT(out, str) \ + out += str + +#define APPEND_FMT(out, format, ...) \ + { \ + char buffer[65536]; \ + snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ + out += buffer; \ + } + +#define WRITE_LIT(file, str) \ + fwrite(str, sizeof(char), sizeof(str) - 1, file) + +// NOTE: snprintf() returns the size written (given an infinite buffer) not including \0 +#define WRITE_FMT(file, format, ...) \ + { \ + char buffer[65536]; \ + int size = snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ + fwrite(buffer, sizeof(char), std::min(size, sizeof(buffer)), file); \ + } + +#define APPEND_LIT_LN(out, str) APPEND_LIT(out, (str "\n")) +#define APPEND_FMT_LN(out, format, ...) APPEND_FMT(out, (format "\n"), __VA_ARGS__) +#define WRITE_LIT_LN(out, str) WRITE_LIT(out, (str "\n")) +#define WRITE_FMT_LN(out, format, ...) WRITE_FMT(out, (format "\n"), __VA_ARGS__) + +namespace Utils { + +bool WriteOutputFile(const CodegenOutput& output, const char* path); + +std::string MakeFullName(std::string_view name, DeclNamespace* ns = nullptr); +std::vector SplitIdentifier(std::string_view name); +std::string MakePascalCase(std::string_view name); + +void ProduceGeneratedHeader(const char* headerFilename, CodegenOutput& header, const char* sourceFilename, CodegenOutput& source); +void ProduceClassTypeInfo(CodegenOutput& source, std::string_view className, const DeclNamespace* ns = nullptr); + +} // namespace Utils diff --git a/source/CodegenCompiler/buildfile b/source/CodegenCompiler/buildfile new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/source/CodegenCompiler/buildfile @@ -0,0 +1 @@ + diff --git a/source/CodegenCompiler/main.cpp b/source/CodegenCompiler/main.cpp new file mode 100644 index 0000000..5e052a3 --- /dev/null +++ b/source/CodegenCompiler/main.cpp @@ -0,0 +1,1112 @@ +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" +#include "CodegenInput.hpp" +#include "CodegenLexer.hpp" +#include "CodegenOutput.hpp" +#include "CodegenUtils.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; +namespace fs = std::filesystem; + +// TODO support codegen target in .cpp files + +struct AppState { + std::string_view outputDir; +}; + +FSTR_LUT_DECL(ClexNames, CLEX_eof, CLEX_ext_COUNT) { + FSTR_LUT_MAP_FOR(ClexNames); + FSTR_LUT_MAP_ENUM(CLEX_intlit); + FSTR_LUT_MAP_ENUM(CLEX_floatlit); + FSTR_LUT_MAP_ENUM(CLEX_id); + FSTR_LUT_MAP_ENUM(CLEX_dqstring); + FSTR_LUT_MAP_ENUM(CLEX_sqstring); + FSTR_LUT_MAP_ENUM(CLEX_charlit); + FSTR_LUT_MAP_ENUM(CLEX_eq); + FSTR_LUT_MAP_ENUM(CLEX_noteq); + FSTR_LUT_MAP_ENUM(CLEX_lesseq); + FSTR_LUT_MAP_ENUM(CLEX_greatereq); + FSTR_LUT_MAP_ENUM(CLEX_andand); + FSTR_LUT_MAP_ENUM(CLEX_oror); + FSTR_LUT_MAP_ENUM(CLEX_shl); + FSTR_LUT_MAP_ENUM(CLEX_shr); + FSTR_LUT_MAP_ENUM(CLEX_plusplus); + FSTR_LUT_MAP_ENUM(CLEX_minusminus); + FSTR_LUT_MAP_ENUM(CLEX_pluseq); + FSTR_LUT_MAP_ENUM(CLEX_minuseq); + FSTR_LUT_MAP_ENUM(CLEX_muleq); + FSTR_LUT_MAP_ENUM(CLEX_diveq); + FSTR_LUT_MAP_ENUM(CLEX_modeq); + FSTR_LUT_MAP_ENUM(CLEX_andeq); + FSTR_LUT_MAP_ENUM(CLEX_oreq); + FSTR_LUT_MAP_ENUM(CLEX_xoreq); + FSTR_LUT_MAP_ENUM(CLEX_arrow); + FSTR_LUT_MAP_ENUM(CLEX_eqarrow); + FSTR_LUT_MAP_ENUM(CLEX_shleq); + FSTR_LUT_MAP_ENUM(CLEX_shreq); + FSTR_LUT_MAP_ENUM(CLEX_ext_single_char); + FSTR_LUT_MAP_ENUM(CLEX_ext_double_colon); + FSTR_LUT_MAP_ENUM(CLEX_ext_dot_dot_dot); +} + +FSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { + FSTR_LUT_MAP_FOR(EnumUnderlyingType); + FSTR_LUT_MAP(EUT_Int8, "int8_t"); + FSTR_LUT_MAP(EUT_Int16, "int16_t"); + FSTR_LUT_MAP(EUT_Int32, "int32_t"); + FSTR_LUT_MAP(EUT_Int64, "int64_t"); + FSTR_LUT_MAP(EUT_Uint8, "uint8_t"); + FSTR_LUT_MAP(EUT_Uint16, "uint16_t"); + FSTR_LUT_MAP(EUT_Uint32, "uint32_t"); + FSTR_LUT_MAP(EUT_Uint64, "uint64_t"); +} + +RSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { + RSTR_LUT_MAP_FOR(EnumUnderlyingType); + + // Platform-dependent types + // TODO all of these can be suffixde with "int" + RSTR_LUT_MAP(EUT_Int16, "short"); + RSTR_LUT_MAP(EUT_Uint16, "unsigned short"); + RSTR_LUT_MAP(EUT_Int32, "int"); + RSTR_LUT_MAP(EUT_Uint32, "unsigned"); + RSTR_LUT_MAP(EUT_Uint32, "unsigned int"); +#ifdef _WIN32 + RSTR_LUT_MAP(EUT_Int32, "long"); + RSTR_LUT_MAP(EUT_Uint32, "unsigned long"); +#else + RSTR_LUT_MAP(EUT_Int64, "long"); + RSTR_LUT_MAP(EUT_Uint64, "unsigned long"); +#endif + RSTR_LUT_MAP(EUT_Int64, "long long"); + RSTR_LUT_MAP(EUT_Uint64, "unsigned long long"); + + // Sized types + RSTR_LUT_MAP(EUT_Int8, "int8_t"); + RSTR_LUT_MAP(EUT_Int16, "int16_t"); + RSTR_LUT_MAP(EUT_Int32, "int32_t"); + RSTR_LUT_MAP(EUT_Int64, "int64_t"); + RSTR_LUT_MAP(EUT_Uint8, "uint8_t"); + RSTR_LUT_MAP(EUT_Uint16, "uint16_t"); + RSTR_LUT_MAP(EUT_Uint32, "uint32_t"); + RSTR_LUT_MAP(EUT_Uint64, "uint64_t"); +} + +FSTR_LUT_DECL(EnumValuePattern, 0, EVP_COUNT) { + FSTR_LUT_MAP_FOR(EnumValuePattern); + FSTR_LUT_MAP_ENUM(EVP_Continuous); + FSTR_LUT_MAP_ENUM(EVP_Bits); + FSTR_LUT_MAP_ENUM(EVP_Random); +} + +enum CppKeyword { + CKw_Namespace, + CKw_Struct, + CKw_Class, + CKw_Enum, + CKw_Public, + CKw_Protected, + CKw_Private, + CKw_Virtual, + CKw_COUNT, +}; + +RSTR_LUT_DECL(CppKeyword, 0, CKw_COUNT) { + RSTR_LUT_MAP_FOR(CppKeyword); + RSTR_LUT_MAP(CKw_Namespace, "namespace"); + RSTR_LUT_MAP(CKw_Struct, "struct"); + RSTR_LUT_MAP(CKw_Class, "class"); + RSTR_LUT_MAP(CKw_Enum, "enum"); + RSTR_LUT_MAP(CKw_Public, "public"); + RSTR_LUT_MAP(CKw_Protected, "protected"); + RSTR_LUT_MAP(CKw_Private, "private"); + RSTR_LUT_MAP(CKw_Virtual, "virtual"); +} + +enum CodegenDirective { + CD_Class, + CD_ClassProperty, + CD_ClassMethod, + CD_Enum, + CD_COUNT, +}; + +RSTR_LUT_DECL(CodegenDirective, 0, CD_COUNT) { + RSTR_LUT_MAP_FOR(CodegenDirective); + RSTR_LUT_MAP(CD_Class, "BRUSSEL_CLASS"); + RSTR_LUT_MAP(CD_ClassProperty, "BRUSSEL_PROPERTY"); + RSTR_LUT_MAP(CD_ClassMethod, "BRUSSEL_METHOD"); + RSTR_LUT_MAP(CD_Enum, "BRUSSEL_ENUM"); +} + +std::vector> +TryConsumeDirectiveArgumentList(CodegenLexer& lexer) { + std::vector> result; + decltype(result)::value_type currentArg; + + size_t i = lexer.idx; + int parenDepth = 0; + for (; i < lexer.tokens.size(); ++i) { + auto& token = lexer.tokens[i]; + if (token.text[0] == '(') { + if (parenDepth > 0) { + currentArg.push_back(&token); + } + ++parenDepth; + } else if (token.text[0] == ')') { + --parenDepth; + if (parenDepth == 0) { + // End of argument list + ++i; // Consume the ')' token + break; + } + } else if (parenDepth > 0) { + // Parse these only if we are inside the argument list + if (token.text[0] == ',') { + result.push_back(std::move(currentArg)); + currentArg = {}; + } else { + currentArg.push_back(&token); + } + } + } + + if (!currentArg.empty()) { + result.push_back(std::move(currentArg)); + } + + lexer.idx = i; + return result; +} + +std::vector* +GetDirectiveArgument(std::vector>& list, size_t idx, const char* errMsg = nullptr) { + if (idx < list.size()) { + if (errMsg) { + printf("%s", errMsg); + } + return &list[idx]; + } + return nullptr; +} + +bool TryConsumeKeyword(CodegenLexer& lexer, CppKeyword keyword) { + auto& token = lexer.Current(); + if (token.type == CLEX_id) { + auto iter = RSTR_LUT(CppKeyword).find(token.text); + if (iter != RSTR_LUT(CppKeyword).end()) { + ++lexer.idx; + return true; + } + } + return false; +} + +bool TryConsumeAnyKeyword(CodegenLexer& lexer) { + auto& token = lexer.Current(); + if (token.type == CLEX_id && + RSTR_LUT(CppKeyword).contains(token.text)) + { + ++lexer.idx; + return true; + } + return false; +} + +std::optional +TryConsumeMemberVariable(CodegenLexer& lexer) { + // The identifier/name will always be one single token, right before the 1st '=' (if has initializer) or ';' (no initializer) + // NOTE: we assume there is no (a == b) stuff in the templates + + auto& tokens = lexer.tokens; + auto& idx = lexer.idx; + + size_t idenTokIdx; + size_t typeStart = idx; + size_t typeEnd; + for (; idx < tokens.size(); ++idx) { + auto& token = tokens[idx]; + if (token.type == CLEX_ext_single_char) { + if (token.text[0] == '=') { + typeEnd = idx - 1; + idenTokIdx = idx - 1; + lexer.SkipUntilTokenSingleChar(';'); + goto found; + } else if (token.text[0] == ';') { + typeEnd = idx - 1; + idenTokIdx = idx - 1; + goto found; + } + } + } + // We reached end of input but still no end of statement + return {}; + +found: + if (tokens[idenTokIdx].type != CLEX_id) { + // Expected identifier, found something else + return {}; + } + + DeclMemberVariable result; + result.name = tokens[idenTokIdx].text; + result.type = CombineTokens(std::span(&tokens[typeStart], &tokens[typeEnd])); + + // Consume the '=' or ';' token + ++idx; + + return result; +} + +enum StructMetaGenOptions { + // TODO how tf do we implement this one: needs full source scanning + SMGO_InheritanceHiearchy, + SMGO_COUNT, +}; + +RSTR_LUT_DECL(StructMetaGenOptions, 0, SMGO_COUNT) { + RSTR_LUT_MAP_FOR(StructMetaGenOptions); + RSTR_LUT_MAP(SMGO_InheritanceHiearchy, "InheritanceHiearchy"); +} + +enum StructPropertyOptions { + SPO_Getter, + SPO_Setter, + SPO_COUNT, +}; + +RSTR_LUT_DECL(StructPropertyOptions, 0, SPO_COUNT) { + RSTR_LUT_MAP_FOR(StructPropertyOptions); + RSTR_LUT_MAP(SPO_Getter, "GETTER"); + RSTR_LUT_MAP(SPO_Setter, "SETTER"); +} + +enum EnumMetaGenOptions { + EMGO_ToString, + EMGO_FromString, + EMGO_ExcludeUseHeuristics, + EMGO_COUNT, +}; + +RSTR_LUT_DECL(EnumMetaGenOptions, 0, EMGO_COUNT) { + RSTR_LUT_MAP_FOR(EnumMetaGenOptions); + RSTR_LUT_MAP(EMGO_ToString, "ToString"); + RSTR_LUT_MAP(EMGO_FromString, "FromString"); + RSTR_LUT_MAP(EMGO_ExcludeUseHeuristics, "ExcludeHeuristics"); +} + +void GenerateEnumStringArray(CodegenOutput& out, const DeclEnum& decl, const char* arrayName, const std::vector& filteredElements) { + CodegenOutputThing thing; + APPEND_FMT_LN(thing.text, "const char* %s[] = {", arrayName); + for (auto& elm : filteredElements) { + APPEND_FMT_LN(thing.text, "\"%s\",", elm.name.c_str()); + } + APPEND_LIT_LN(thing.text, "};"); + out.AddOutputThing(std::move(thing)); +} + +void GenerateEnumStringMap(CodegenOutput& out, const DeclEnum& decl, const char* mapName, const std::vector& filteredElements) { + CodegenOutputThing thing; + // TODO + out.AddOutputThing(std::move(thing)); +} + +void GenerateForEnum(CodegenOutput& headerOut, CodegenOutput& sourceOut, const DeclEnum& decl, EnumFlags options) { + char enumName[2048]; + if (decl.container) { + snprintf(enumName, sizeof(enumName), "%.*s::%s", PRINTF_STRING_VIEW(decl.container->fullname), decl.name.c_str()); + } else { + strncpy(enumName, decl.name.c_str(), sizeof(enumName)); + } + + // TODO mangle to prevent name conflicts of enum in different namespaces + auto& declIdName = decl.name; + + auto useExcludeHeuristics = options.IsSet(EMGO_ExcludeUseHeuristics); + auto filteredElements = [&]() { + if (useExcludeHeuristics) { + decltype(decl.elements) result; + for (auto& elm : decl.elements) { + if (elm.name.ends_with("COUNT")) continue; + + result.push_back(elm); + } + return result; + } else { + return decl.elements; + } + }(); + + if (options.IsSet(EMGO_ToString)) { + // Generate value -> string lookup table and function + INPLACE_FMT(val2StrName, "gCG_%s_Val2Str", declIdName.c_str()); + + switch (decl.GetPattern()) { + case EVP_Continuous: { + GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); + int minVal = filteredElements.empty() ? 0 : filteredElements.front().value; + int maxVal = filteredElements.empty() ? 0 : filteredElements.back().value; + + CodegenOutputThing lookupFunctionDecl; + { + auto& o = lookupFunctionDecl.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value);", enumName, enumName); + } + + CodegenOutputThing lookupFunctionDef; + { + auto& o = lookupFunctionDef.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value) {", enumName, enumName); + APPEND_FMT_LN(o, " auto intVal = (%s)value;", FSTR_LUT_LOOKUP(EnumUnderlyingType, decl.underlyingType)); + APPEND_FMT_LN(o, " if (intVal < %d || intVal > %d) return {};", minVal, maxVal); + APPEND_FMT_LN(o, " return %s[intVal - %d];", val2StrName, minVal); + APPEND_LIT_LN(o, "}"); + } + + headerOut.AddOutputThing(std::move(lookupFunctionDecl)); + sourceOut.AddOutputThing(std::move(lookupFunctionDef)); + } break; + + case EVP_Bits: { + GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); + // TODO + } break; + + case EVP_Random: { + GenerateEnumStringMap(sourceOut, decl, val2StrName, filteredElements); + // TODO + } break; + + case EVP_COUNT: break; + } + } + + if (options.IsSet(EMGO_FromString)) { + // Generate string -> value lookup table + INPLACE_FMT(str2ValName, "gCG_%s_Str2Val", declIdName.c_str()); + + CodegenOutputThing lookupTable; + { + auto& o = lookupTable.text; + // TODO use correct underlying type + APPEND_FMT_LN(o, "constinit frozen::unordered_map %s = {", filteredElements.size(), str2ValName); + for (auto& elm : filteredElements) { + APPEND_FMT_LN(o, "{\"%s\", %" PRId64 "},", elm.name.c_str(), elm.value); + } + APPEND_LIT_LN(o, "};"); + } + + // Generate lookup function + CodegenOutputThing lookupFunctionDecl; + { + auto& o = lookupFunctionDecl.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value);", enumName, enumName); + } + + CodegenOutputThing lookupFunctionDef; + { + auto& o = lookupFunctionDef.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value) {", enumName, enumName); + APPEND_FMT_LN(o, " auto iter = %s.find(value);", str2ValName); + APPEND_FMT_LN(o, " if (iter != %s.end()) {", str2ValName); + APPEND_FMT_LN(o, " return (%s)iter->second;", enumName); + APPEND_LIT_LN(o, " } else {"); + APPEND_LIT_LN(o, " return {};"); + APPEND_LIT_LN(o, " }"); + APPEND_LIT_LN(o, "}"); + } + + sourceOut.AddOutputThing(std::move(lookupTable)); + headerOut.AddOutputThing(std::move(lookupFunctionDecl)); + sourceOut.AddOutputThing(std::move(lookupFunctionDef)); + } +} + +void GenerateClassProperty(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { + // TODO +} + +void GenerateClassFunction(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { + // TODO +} + +void GenerateForClassMetadata( + CodegenOutput& headerOutput, + CodegenOutput& sourceOutput, + const DeclStruct& decl) // +{ + // TODO mangle + auto declIdName = decl.name.c_str(); + + CodegenOutputThing data; + // TODO generate type id, this needs global scanning + APPEND_FMT_LN(data.text, "const TypeInfo* const gCGtype_%s_BaseClasses[] = {", declIdName); + for (auto& baseClass : decl.baseClasses) { + // TODO get ptr to TypeInfo, this needs global scanning for non-file local classes + } + APPEND_LIT_LN(data.text, "};"); + APPEND_FMT_LN(data.text, "const TypePropertyInfo gCGtype_%s_Properties[] = {", declIdName); + for (auto& property : decl.memberVariables) { + APPEND_FMT_LN(data.text, "{.name=\"%s\"sv, .getterName=\"%s\"sv, .setterName=\"%s\"sv},", property.name.c_str(), property.getterName.c_str(), property.setterName.c_str()); + } + APPEND_LIT_LN(data.text, "};"); + APPEND_FMT_LN(data.text, "const TypeInfo gCGtype_%s_TypeInfo = {", declIdName); + APPEND_FMT_LN(data.text, ".name = \"%s\"sv,", declIdName); + APPEND_FMT_LN(data.text, ".parents = gCGtype_%s_BaseClasses,", declIdName); + APPEND_FMT_LN(data.text, ".properties = gCGtype_%s_Properties};", declIdName); + + CodegenOutputThing queryFunc; + APPEND_FMT(queryFunc.text, + "template <>\n" + "const TypeInfo* Metadata::GetTypeInfo<%.*s>() {\n" + " return &gCGtype_%s_TypeInfo;\n" + "}\n", + PRINTF_STRING_VIEW(decl.fullname), + declIdName); + + sourceOutput.AddOutputThing(std::move(data)); + sourceOutput.AddOutputThing(std::move(queryFunc)); +} + +void HandleInputFile(AppState& state, std::string_view filenameStem, std::string_view source) { + CodegenLexer lexer; + lexer.InitializeFrom(source); + +#if CODEGEN_DEBUG_PRINT + printf("BEGIN tokens\n"); + for (auto& token : lexer.tokens) { + switch (token.type) { + case CLEX_intlit: { + printf(" token %-32s = %ld\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerIntNumber); + } break; + + case CLEX_floatlit: { + printf(" token %-32s = %f\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerRealNumber); + } break; + + default: { + printf(" token %-32s '%s'\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.text.c_str()); + } break; + } + } + printf("END tokens\n"); +#endif + + CodegenInput cgInput; + CodegenOutput cgHeaderOutput; + CodegenOutput cgSourceOutput; + { + INPLACE_FMT(hpp, "%.*s.gh.inl", PRINTF_STRING_VIEW(filenameStem)); + INPLACE_FMT(cpp, "%.*s.gs.inl", PRINTF_STRING_VIEW(filenameStem)); + Utils::ProduceGeneratedHeader(hpp, cgHeaderOutput, cpp, cgSourceOutput); + } + CodegenOutput cgStandaloneSourceOutput; + + int currentBraceDepth = 0; + // The current effective namespace, see example + DeclNamespace* currentNamespace = nullptr; + DeclStruct* currentStruct = nullptr; + int currentStructBraceDepth = 0; + + struct NamespaceStackframe { + // The current namespace that owns the brace level, see example + DeclNamespace* ns = nullptr; + // Brace depth `ns` was created at (e.g. [std::details].depth == 0) + int depth = 0; + }; + std::vector nsStack; + + // Example: + // namespace std::details { + // /* [stack top].ns = std::details */ + // /* [stack top].depth = std */ + // } + // namespace foo { + // /* [stack top].ns = foo */ + // /* [stack top].depth = foo */ + // namespace details { + // /* [stack top].ns = foo::details */ + // /* [stack top].depth = foo::details */ + // } + // } + + auto& tokens = lexer.tokens; + auto& idx = lexer.idx; + while (lexer.idx < lexer.tokens.size()) { + auto& token = lexer.Current(); + + bool incrementTokenIdx = true; + + // Reamalgamate token type and single char tokens; + int tokenKey; + if (token.type == CLEX_ext_single_char) { + tokenKey = token.text[0]; + } else { + tokenKey = token.type; + } + + switch (tokenKey) { + case CLEX_id: { + CppKeyword keyword; + { + auto& map = RSTR_LUT(CppKeyword); + auto iter = map.find(token.text); + if (iter != map.end()) { + keyword = iter->second; + } else { + keyword = CKw_COUNT; // Skip keyword section + } + } + switch (keyword) { + case CKw_Namespace: { + ++idx; + incrementTokenIdx = false; + + int nestingCount = 0; + while (true) { + if (tokens[idx].type != CLEX_id) { + // TODO better error recovery + // TODO handle annoymous namespaces + printf("[ERROR] invalid syntax for namespace\n"); + break; + } + + currentNamespace = cgInput.AddNamespace(DeclNamespace{ + .container = currentNamespace, + .name = tokens[idx].text, + }); + + // Consume the identifier token + ++idx; + + if (tokens[idx].type == CLEX_ext_double_colon) { + // Consume the "::" token + ++idx; + } else { + break; + } + } + + nsStack.push_back(NamespaceStackframe{ + .ns = currentNamespace, + .depth = currentBraceDepth, + }); + + goto endCaseCLEX_id; + } + + case CKw_Struct: + case CKw_Class: { + // Consume the 'class' or 'struct' keyword + ++idx; + incrementTokenIdx = false; + + auto& idenTok = tokens[idx]; + if (idenTok.type != CLEX_id) { + printf("[ERROR] invalid syntax for struct or class\n"); + break; + } + + DEBUG_PRINTF("[DEBUG] found struct named %s\n", idenTok.text.c_str()); + + auto& name = idenTok.text; + auto fullname = Utils::MakeFullName(name, currentNamespace); + DeclStruct structDecl; + structDecl.container = currentNamespace; + structDecl.name = name; + + // Consume the identifier token + ++idx; + + if (lexer.TryConsumeSingleCharToken(':')) { + while (true) { + // Public, protected, etc. + TryConsumeAnyKeyword(lexer); + + auto& idenTok = tokens[idx]; + if (idenTok.type != CLEX_id) { + printf("[ERROR] invalid syntax for class inheritance list\n"); + goto endCase; + } + + // TODO support namespace qualified names + auto baseClassFullname = Utils::MakeFullName(idenTok.text, currentNamespace); + auto baseClassDecl = cgInput.FindStruct(baseClassFullname); + if (baseClassDecl) { + // We silently ignore a non-existent base class, because they may reside in a file that we didn't scan + structDecl.baseClasses.push_back(baseClassDecl); + } + + // Consume the identifier token + ++idx; + + if (lexer.TryConsumeSingleCharToken('{')) { + // End of base class list + --idx; // Give the '{' token back to the main loop + break; + } else if (!lexer.TryConsumeSingleCharToken(',')) { + // If the list didn't end, we expect a comma (then followed by more entries) + printf("[ERROR] invalid syntax for class inheritance list\n"); + goto endCase; + } + + // NOTE: we currently only scan one base class to workaround some code inherits from template classes after their initial base class + // TODO remove this hack + break; + } + } + + { + // Get a pointer to the decl inside CodegenInput's storage + auto decl = cgInput.AddStruct(std::move(fullname), std::move(structDecl)); + currentStruct = decl; + currentStructBraceDepth = currentBraceDepth; + } + + endCase: + goto endCaseCLEX_id; + } + + case CKw_Enum: { + // Consume the "enum" keyword + ++idx; + incrementTokenIdx = false; + + StbLexerToken* idenTok; + if (tokens[idx].text == "class") { + // Consume the "class" keyword + ++idx; + idenTok = &tokens[idx]; + DEBUG_PRINTF("[DEBUG] found enum class named %s\n", idenTok->text.c_str()); + } else { + idenTok = &tokens[idx]; + DEBUG_PRINTF("[DEBUG] found enum named %s\n", idenTok->text.c_str()); + } + + DeclEnum enumDecl; + enumDecl.container = currentNamespace; + enumDecl.underlyingType = EUT_Int32; // TODO + enumDecl.name = tokens[idx].text; + + // Consume the enum name identifier + ++idx; + + int enumClosingBraceCount = 0; + int enumBraceDepth = 0; + while (enumClosingBraceCount == 0 && idx < tokens.size()) { + auto& token = tokens[idx]; + switch (token.type) { + case CLEX_id: { + auto& vec = enumDecl.elements; + // Set to the previous enum element's value + 1, or starting from 0 if this is the first + // Also overridden in the CLEX_intlit branch + auto value = vec.empty() ? 0 : vec.back().value + 1; + vec.push_back(DeclEnumElement{ + .name = token.text, + .value = value, + }); + } break; + + case CLEX_intlit: { + auto& vec = enumDecl.elements; + if (!vec.empty()) { + auto& lastElm = vec.back(); + lastElm.value = token.lexerIntNumber; + } + } break; + + case CLEX_ext_single_char: { + switch (token.text[0]) { + case '{': { + ++enumBraceDepth; + } break; + + case '}': { + --enumBraceDepth; + ++enumClosingBraceCount; + } break; + } + } break; + } + + ++idx; + } + + auto fullname = Utils::MakeFullName(enumDecl.name, currentNamespace); + cgInput.AddEnum(std::move(fullname), std::move(enumDecl)); + goto endCaseCLEX_id; + } + + // We don't care about these keywords + case CKw_Public: + case CKw_Protected: + case CKw_Private: + case CKw_Virtual: + case CKw_COUNT: break; + } + + CodegenDirective directive; + { + auto& map = RSTR_LUT(CodegenDirective); + auto iter = map.find(token.text); + if (iter != map.end()) { + directive = iter->second; + } else { + directive = CD_COUNT; // Skip directive section + } + } + switch (directive) { + case CD_Class: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + if (!currentStruct) { + printf("[ERROR] BRUSSEL_CLASS must be used within a class or struct\n"); + break; + } + + // Always-on option + currentStruct->generating = true; + + auto argList = TryConsumeDirectiveArgumentList(lexer); + auto& lut = RSTR_LUT(StructMetaGenOptions); + for (auto& arg : argList) { + if (arg.empty()) { + printf("[ERROR] empty argument is invalid in BRUSSEL_CLASS\n"); + continue; + } + + auto& optionDirective = arg[0]->text; + auto iter = lut.find(optionDirective); + if (iter == lut.end()) continue; + switch (iter->second) { + case SMGO_InheritanceHiearchy: currentStruct->generatingInheritanceHiearchy = true; break; + case SMGO_COUNT: break; + } + } + + goto endCaseCLEX_id; + } + + case CD_ClassProperty: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + if (!currentStruct || + !currentStruct->generating) + { + printf("[ERROR] BRUSSEL_PROPERTY must be used within a class or struct, that has the BRUSSEL_CLASS directive\n"); + break; + } + + auto argList = TryConsumeDirectiveArgumentList(lexer); + auto declOpt = TryConsumeMemberVariable(lexer); + if (!declOpt.has_value()) { + printf("[ERROR] a member variable must immediately follow a BRUSSEL_PROPERTY\n"); + break; + } + auto& decl = declOpt.value(); + + // Different option's common logic + std::string pascalCaseName; + auto GetPascalCasedName = [&]() -> const std::string& { + if (pascalCaseName.empty()) { + pascalCaseName = Utils::MakePascalCase(decl.name); + } + return pascalCaseName; + }; + + auto& lut = RSTR_LUT(StructPropertyOptions); + for (auto& arg : argList) { + if (arg.empty()) { + printf("[ERROR] empty argument is invalid in BRUSSEL_PROPERTY\n"); + continue; + } + + auto& optionDirective = arg[0]->text; + auto iter = lut.find(optionDirective); + if (iter == lut.end()) continue; + switch (iter->second) { + case SPO_Getter: { + // TODO I'm too lazy to write error checks, just let the codegen crash + auto& getterName = arg.at(1)->text; + if (getterName == "auto") { + // NOTE: intentionally shadowing + INPLACE_FMT(getterName, "Get%s", GetPascalCasedName().c_str()); + + // TODO generate getter function + + decl.getterName = getterName; + } else { + decl.getterName = getterName; + } + } break; + + case SPO_Setter: { + // TODO + auto& setterName = arg.at(1)->text; + if (setterName == "auto") { + // NOTE: intentionally shadowing + INPLACE_FMT(setterName, "Set%s", GetPascalCasedName().c_str()); + + // TODO generate setter function + + decl.setterName = setterName; + } else { + decl.setterName = setterName; + } + } break; + + case SPO_COUNT: break; + } + } + + currentStruct->memberVariables.push_back(std::move(decl)); + + goto endCaseCLEX_id; + } + + case CD_ClassMethod: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + goto endCaseCLEX_id; + } + + case CD_Enum: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + auto& optionsStrMap = RSTR_LUT(EnumMetaGenOptions); + auto argList = TryConsumeDirectiveArgumentList(lexer); + + if (argList.size() < 1) { + printf("[ERROR] invalid syntax for BRUSSEL_ENUM\n"); + break; + } + + auto& enumName = argList[0][0]->text; + auto enumDecl = cgInput.FindEnum(Utils::MakeFullName(enumName, currentNamespace)); + if (!enumDecl) { + printf("[ERROR] BRUSSEL_ENUM: referring to non-existent enum '%s'\n", enumName.c_str()); + break; + } + + auto& directiveOptions = argList[1]; + EnumFlags options; + for (auto optionTok : directiveOptions) { + auto iter = optionsStrMap.find(optionTok->text); + if (iter != optionsStrMap.end()) { + options |= iter->second; + } else { + printf("[ERROR] BRUSSEL_ENUM: invalid option '%s'\n", optionTok->text.c_str()); + } + } + + GenerateForEnum(cgHeaderOutput, cgSourceOutput, *enumDecl, options); + + goto endCaseCLEX_id; + } + + case CD_COUNT: break; + } + + endCaseCLEX_id:; + } break; + + case '{': { + currentBraceDepth++; + if (currentBraceDepth < 0) { + printf("[WARNING] unbalanced brace\n"); + } + } break; + + case '}': { + currentBraceDepth--; + if (currentBraceDepth < 0) { + printf("[WARNING] unbalanced brace\n"); + } + + if (!nsStack.empty()) { + auto& ns = nsStack.back(); + if (ns.depth == currentBraceDepth) { + nsStack.pop_back(); + + if (!nsStack.empty()) { + currentNamespace = nsStack.back().ns; + } else { + currentNamespace = nullptr; + } + } + } + + if (currentStruct && + currentBraceDepth == currentStructBraceDepth) + { + // Exit struct + + if (currentStruct->generating) { + GenerateForClassMetadata(cgHeaderOutput, cgSourceOutput, *currentStruct); + } + if (currentStruct->generatingInheritanceHiearchy) { + // NOTE: this option is transitive to all child classes (as long as they have the basic annotation) + // TODO + } + + currentStruct = nullptr; + currentStructBraceDepth = 0; + } + } break; + } + + if (incrementTokenIdx) { + ++idx; + } + } + + if (currentBraceDepth != 0) { + printf("[WARNING] unbalanced brace at end of file."); + } + + INPLACE_FMT(generatedHeaderInlName, "%.*s/%.*s.gh.inl", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem)); + Utils::WriteOutputFile(cgHeaderOutput, generatedHeaderInlName); + INPLACE_FMT(generatedSourceInlName, "%.*s/%.*s.gs.inl", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem)); + Utils::WriteOutputFile(cgSourceOutput, generatedSourceInlName); + INPLACE_FMT(generatedCppName, "%.*s/%.*s.g.cpp", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem)); + Utils::WriteOutputFile(cgStandaloneSourceOutput, generatedCppName); +} + +enum InputOpcode { + IOP_ProcessSingleFile, + IOP_ProcessRecursively, + IOP_COUNT, +}; + +void HandleArgument(AppState& state, InputOpcode opcode, std::string_view operand) { + switch (opcode) { + case IOP_ProcessSingleFile: { + DEBUG_PRINTF("Processing single file %.*s\n", PRINTF_STRING_VIEW(operand)); + + fs::path path(operand); + auto filenameStem = path.stem().string(); + auto source = Utils::ReadFileAsString(path); + HandleInputFile(state, filenameStem, source); + } break; + + case IOP_ProcessRecursively: { + DEBUG_PRINTF("Recursively processing folder %.*s\n", PRINTF_STRING_VIEW(operand)); + + fs::path startPath(operand); + for (auto& item : fs::recursive_directory_iterator(startPath)) { + if (!item.is_regular_file()) { + continue; + } + + auto& path = item.path(); + auto pathExt = path.extension(); + auto pathStem = path.stem(); + if (pathExt != ".h" && + pathExt != ".hpp") + { + continue; + } + + DEBUG_PRINTF("Processing subfile %s\n", path.string().c_str()); + + auto filenameStem = pathStem.string(); + auto source = Utils::ReadFileAsString(path); + HandleInputFile(state, filenameStem, source); + } + } break; + + case IOP_COUNT: break; + } +} + +InputOpcode ParseInputOpcode(std::string_view text) { + if (text == "single"sv) { + return IOP_ProcessSingleFile; + } else if (text == "rec"sv) { + return IOP_ProcessRecursively; + } else { + DEBUG_PRINTF("Unknown input opcode %s\n", text.data()); + throw std::runtime_error("Unknown input opcode"); + } +} + +int main(int argc, char* argv[]) { + FSTR_LUT_INIT(ClexNames); + FSTR_LUT_INIT(EnumUnderlyingType); + RSTR_LUT_INIT(EnumUnderlyingType); + FSTR_LUT_INIT(EnumValuePattern); + RSTR_LUT_INIT(CppKeyword); + RSTR_LUT_INIT(CodegenDirective); + RSTR_LUT_INIT(StructMetaGenOptions); + RSTR_LUT_INIT(StructPropertyOptions); + RSTR_LUT_INIT(EnumMetaGenOptions); + + // TODO better arg parser + // option 1: use cxxopts and positional arguments + // option 2: take one argument only, being a json objecet + + AppState state; + + // If no cli is provided (argv[0] conventionally but not mandatorily the cli), this will do thing + // Otherwise, start with the 2nd element in the array, which is the 1st actual argument + if (argc < 2) { + // NOTE: keep in sync with various enum options and parser code + printf(&R"""( +USAGE: codegen.exe [:]... +where : the directory to write generated contents to. This will NOT automatically create the directory. + is one of: + "single" process this file only + "rec" starting at the given directory , recursively process all .h .hpp files +)"""[1]); + return -1; + } + + state.outputDir = std::string_view(argv[1]); + DEBUG_PRINTF("Outputting to directory %.*s.\n", PRINTF_STRING_VIEW(state.outputDir)); + + for (int i = 2; i < argc; ++i) { + const char* argRaw = argv[i]; + std::string_view arg(argRaw); + DEBUG_PRINTF("Processing input command %s\n", argRaw); + + auto separatorLoc = arg.find(':'); + if (separatorLoc != std::string_view::npos) { + auto opcodeString = arg.substr(0, separatorLoc); + auto opcode = ParseInputOpcode(opcodeString); + auto operand = arg.substr(separatorLoc + 1); + + HandleArgument(state, opcode, operand); + } + } + + return 0; +} diff --git a/source/CodegenCompiler/test/examples/TestClass.hpp.txt b/source/CodegenCompiler/test/examples/TestClass.hpp.txt new file mode 100644 index 0000000..3eed8db --- /dev/null +++ b/source/CodegenCompiler/test/examples/TestClass.hpp.txt @@ -0,0 +1,38 @@ +#include + +class MyClass { + BRUSSEL_CLASS() + +public: + BRUSSEL_PROPERTY(GETTER GetName, SETTER SetName) + std::string name; + + BRUSSEL_PROPERTY(GETTER auto, SETTER auto) + std::string tag; + + BRUSSEL_PROPERTY() + int foo; + + BRUSSEL_PROPERTY() + int bar; + +public: + const std::string& GetName() const { return name; } + void SetName(std::string name) { this->name = std::move(name); } +}; + +namespace MyNamespace { +struct Base { + BRUSSEL_CLASS(InheritanceHiearchy) +}; + +struct DerviedFoo : public Base { + BRUSSEL_CLASS() +}; + +struct DerviedBar : Base { + BRUSSEL_CLASS() +}; +} + +#include diff --git a/source/CodegenCompiler/test/examples/TestEnum.hpp.txt b/source/CodegenCompiler/test/examples/TestEnum.hpp.txt new file mode 100644 index 0000000..30c36c0 --- /dev/null +++ b/source/CodegenCompiler/test/examples/TestEnum.hpp.txt @@ -0,0 +1,44 @@ +enum MyEnum { + EnumElement1, + EnumElement2, + EnumElement3, +}; +BRUSSEL_ENUM(MyEnum, ToString FromString); + +// Let's also test enum class +enum class CountedEnumAll { + CEA_Foo, + CEA_Bar, + CEA_COUNT, +}; +BRUSSEL_ENUM(CountedEnumAll, ToString FromString); + +enum CountedEnum { + CE_Foo, + CE_Bar, + CE_FooBar, + CE_COUNT, +}; +BRUSSEL_ENUM(CountedEnum, ToString FromString ExcludeHeuristics); + +namespace MyNamespace { +enum class MyNamespacedEnum { + MNE_Foo, + MNE_Bar, +}; +BRUSSEL_ENUM(MyNamespacedEnum, ToString FromString ExcludeHeuristics); + +namespace details { + enum MyNamespacedEnum { + MNE_Foo, + MNE_Bar, + }; + BRUSSEL_ENUM(MyNamespacedEnum, ToString FromString ExcludeHeuristics); +} +} + +namespace foo::details { +enum Enum { +}; +BRUSSEL_ENUM(Enum, ToString FromString ExcludeHeuristics); +} diff --git a/source/CodegenRuntime/MacrosCodegen.hpp b/source/CodegenRuntime/MacrosCodegen.hpp new file mode 100644 index 0000000..956123c --- /dev/null +++ b/source/CodegenRuntime/MacrosCodegen.hpp @@ -0,0 +1,10 @@ +// NOTE: Contents of this file is coupled with the codegen compiler. +// When updating, change both sides at the same time. + +#pragma once + +#define BRUSSEL_CLASS(...) +#define BRUSSEL_PROPERTY(...) +#define BRUSSEL_METHOD(...) + +#define BRUSSEL_ENUM(name, options) diff --git a/source/CodegenRuntime/Metadata.cpp b/source/CodegenRuntime/Metadata.cpp new file mode 100644 index 0000000..0d640da --- /dev/null +++ b/source/CodegenRuntime/Metadata.cpp @@ -0,0 +1,45 @@ +#include "Metadata.hpp" + +auto Metadata::TypeInfoList::Iterator::operator*() const -> const TypeInfo& { + // TODO +} + +auto Metadata::TypeInfoList::Iterator::operator++() -> Iterator& { + // TODO +} + +auto Metadata::TypeInfoList::Iterator::operator++(int) -> Iterator { + auto copy = *this; + ++copy; + return copy; +} + +bool Metadata::TypeInfoList::Iterator::operator==(const Iterator& that) const { + return this->data == that.data; +} + +bool Metadata::TypeInfoList::Iterator::operator==(const Sentinel&) const { + // TODO +} + +auto Metadata::TypeInfoList::begin() const -> Iterator { + return Iterator(); +} + +auto Metadata::TypeInfoList::end() const -> Sentinel { + return Sentinel(); +} + +auto Metadata::GetTypeInfoList() -> const TypeInfoList& { + // TODO +} + +auto Metadata::QueryTypeInfo(TypeId id) -> const TypeInfo* { + // TODO + return nullptr; +} + +auto Metadata::QueryTypeInfo(std::string_view id) -> const TypeInfo* { + // TODO + return nullptr; +} diff --git a/source/CodegenRuntime/Metadata.hpp b/source/CodegenRuntime/Metadata.hpp new file mode 100644 index 0000000..e89fd8f --- /dev/null +++ b/source/CodegenRuntime/Metadata.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "MacrosCodegen.hpp" +#include "MetadataBase.hpp" + +namespace Metadata { + +struct TypeInfoList { + struct Sentinel { + }; + + struct Iterator { + void* data; + + const TypeInfo& operator*() const; + Iterator& operator++(); + Iterator operator++(int); + + bool operator==(const Iterator&) const; + bool operator==(const Sentinel&) const; + }; + + Iterator begin() const; + Sentinel end() const; +}; + +/// Get a list of all type infos present. +const TypeInfoList& GetTypeInfoList(); + +const TypeInfo* QueryTypeInfo(TypeId id); +const TypeInfo* QueryTypeInfo(std::string_view name); + +} // namespace Metadata diff --git a/source/CodegenRuntime/MetadataBase.cpp b/source/CodegenRuntime/MetadataBase.cpp new file mode 100644 index 0000000..2f2ef94 --- /dev/null +++ b/source/CodegenRuntime/MetadataBase.cpp @@ -0,0 +1,5 @@ +#include "MetadataBase.hpp" + +bool Metadata::TypePropertyInfo::IsDirectAccess() const { + return getterName.empty() && setterName.empty(); +} diff --git a/source/CodegenRuntime/MetadataBase.hpp b/source/CodegenRuntime/MetadataBase.hpp new file mode 100644 index 0000000..c1ad894 --- /dev/null +++ b/source/CodegenRuntime/MetadataBase.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include + +namespace Metadata { + +struct TypeId { + size_t id; +}; + +struct TypeInfo; + +struct TypePropertyInfo { + std::string_view name; + const TypeInfo* type; + std::string_view getterName; + std::string_view setterName; + + bool IsDirectAccess() const; +}; + +struct TypeMethodInfo { + std::string_view name; + // TODO +}; + +struct TypeInfo { + TypeId typeId; + std::string_view name; + std::span parents; + std::span properties; + std::span methods; + + /// Whether this object is registered at runtime or statically compiled + bool dynamic = false; +}; + +// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase +template +const TypeInfo* GetTypeInfo(); + +// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase +template +std::string_view EnumToString(TEnum value); + +// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase +template +std::optional EnumFromString(std::string_view str); + +} // namespace Metadata diff --git a/source/CodegenRuntime/MetadataDetails.hpp b/source/CodegenRuntime/MetadataDetails.hpp new file mode 100644 index 0000000..09b71ff --- /dev/null +++ b/source/CodegenRuntime/MetadataDetails.hpp @@ -0,0 +1,7 @@ +// This file contains implementation details used by the outputs of the code generaetor. Consumers do not use. +#pragma once + +#include "MetadataBase.hpp" + +namespace Metadata::Details { +} // namespace Metadata::Details diff --git a/source/CodegenRuntime/buildfile b/source/CodegenRuntime/buildfile new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/source/CodegenRuntime/buildfile @@ -0,0 +1 @@ + diff --git a/source/Common/Color.hpp b/source/Common/Color.hpp new file mode 100644 index 0000000..ef0c5a9 --- /dev/null +++ b/source/Common/Color.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include "Utils.hpp" + +#include +#include +#include +#include + +class HsvColor; +class RgbaColor { +public: + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + +public: + constexpr RgbaColor() noexcept + : r{ 255 } + , g{ 255 } + , b{ 255 } + , a{ 255 } { + } + + constexpr RgbaColor(float r, float g, float b, float a = 1.0f) noexcept + : r{ static_cast(r * 255.0f) } + , g{ static_cast(g * 255.0f) } + , b{ static_cast(b * 255.0f) } + , a{ static_cast(a * 255.0f) } { + } + + constexpr RgbaColor(int r, int g, int b, int a = 255) noexcept + : r{ static_cast(r & 0xFF) } + , g{ static_cast(g & 0xFF) } + , b{ static_cast(b & 0xFF) } + , a{ static_cast(a & 0xFF) } { + } + + constexpr RgbaColor(uint32_t rgba) noexcept + : r{ static_cast((rgba >> 0) & 0xFF) } + , g{ static_cast((rgba >> 8) & 0xFF) } + , b{ static_cast((rgba >> 16) & 0xFF) } + , a{ static_cast((rgba >> 24) & 0xFF) } { + } + + constexpr uint32_t GetScalar() const noexcept { + uint32_t res = 0; + res |= r << 0; + res |= g << 8; + res |= b << 16; + res |= a << 24; + return res; + } + + constexpr void SetScalar(uint32_t scalar) noexcept { + r = (scalar >> 0) & 0xFF; + g = (scalar >> 8) & 0xFF; + b = (scalar >> 16) & 0xFF; + a = (scalar >> 24) & 0xFF; + } + + constexpr float GetNormalizedRed() const noexcept { + return r / 255.0f; + } + + constexpr float GetNormalizedGreen() const noexcept { + return g / 255.0f; + } + + constexpr float GetNormalizedBlue() const noexcept { + return b / 255.0f; + } + + constexpr float GetNormalizedAlpha() const noexcept { + return a / 255.0f; + } + + constexpr glm::ivec4 ToIVec() const noexcept { + return { r, g, b, a }; + } + + constexpr glm::vec4 ToVec() const noexcept { + return { GetNormalizedRed(), GetNormalizedGreen(), GetNormalizedBlue(), GetNormalizedAlpha() }; + } + + // Forward declaring because cyclic reference between RgbaColor and HsvColor + constexpr HsvColor ToHsv() const noexcept; + + friend constexpr bool operator==(const RgbaColor&, const RgbaColor&) noexcept = default; +}; + +constexpr RgbaColor kXAxisColor(0xFF, 0x80, 0x80, 0xFF); +constexpr RgbaColor kYAxisColor(0x80, 0xFF, 0x80, 0xFF); +constexpr RgbaColor kZAxisColor(0x80, 0x80, 0xFF, 0xFF); + +class HsvColor { +public: + float h; + float s; + float v; + float a; + +public: + constexpr HsvColor() noexcept + : h{ 0.0f } + , s{ 0.0f } + , v{ 1.0f } + , a{ 1.0f } { + } + + constexpr HsvColor(float h, float s, float v, float a) noexcept + : h{ h } + , s{ s } + , v{ v } + , a{ a } { + } + + // Forward declaring because cyclic reference between RgbaColor and HsvColor + constexpr RgbaColor ToRgba() const noexcept; +}; + +constexpr HsvColor RgbaColor::ToHsv() const noexcept { + float r = GetNormalizedRed(); + float g = GetNormalizedBlue(); + float b = GetNormalizedGreen(); + float a = GetNormalizedAlpha(); + + auto p = g < b ? glm::vec4(b, g, -1, 2.0f / 3.0f) : glm::vec4(g, b, 0, -1.0f / 3.0f); + auto q = r < p.x ? glm::vec4(p.x, p.y, p.w, r) : glm::vec4(r, p.y, p.z, p.x); + float c = q.x - std::min(q.w, q.y); + float h = Utils::Abs((q.w - q.y) / (6 * c + std::numeric_limits::epsilon()) + q.z); + + glm::vec3 hcv{ h, c, q.x }; + float s = hcv.y / (hcv.z + std::numeric_limits::epsilon()); + return HsvColor(hcv.x, s, hcv.z, a); +} + +constexpr RgbaColor HsvColor::ToRgba() const noexcept { + float r = Utils::Abs(h * 6 - 3) - 1; + float g = 2 - Utils::Abs(h * 6 - 2); + float b = 2 - Utils::Abs(h * 6 - 4); + + auto rgb = glm::vec3{ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }; + auto vc = (rgb - glm::vec3{ 0, 0, 0 }) * s + glm::vec3{ 1, 1, 1 } * v; + + return RgbaColor(vc.x, vc.y, vc.z, a); +} diff --git a/source/Common/Enum.hpp b/source/Common/Enum.hpp new file mode 100644 index 0000000..8ad75ba --- /dev/null +++ b/source/Common/Enum.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +template +class EnumFlags { +public: + using Enum = TEnum; + using Underlying = std::underlying_type_t; + +private: + Underlying mValue; + +public: + EnumFlags() + : mValue{ 0 } { + } + + EnumFlags(TEnum e) + : mValue{ static_cast(1) << static_cast(e) } { + } + + bool IsSet(EnumFlags mask) const { + return (mValue & mask.mValue) == mask.mValue; + } + + bool IsSet(std::initializer_list enums) { + EnumFlags flags; + for (auto& e : enums) { + flags.mValue |= static_cast(e); + } + return IsSet(flags); + } + + bool IsSetExclusive(EnumFlags mask) const { + return mValue == mask.mValue; + } + + bool IsSetExclusive(std::initializer_list enums) { + EnumFlags flags; + for (auto& e : enums) { + flags.mValue |= static_cast(e); + } + return IsSetExclusive(flags); + } + + void SetOn(EnumFlags mask) { + mValue |= mask.mValue; + } + + void SetOff(EnumFlags mask) { + mValue &= ~mask.mValue; + } + + void Set(EnumFlags mask, bool enabled) { + if (enabled) { + SetOn(mask); + } else { + SetOff(mask); + } + } + + EnumFlags& operator|=(EnumFlags that) { + mValue |= that.mValue; + return *this; + } + + EnumFlags& operator&=(EnumFlags that) { + mValue &= that.mValue; + return *this; + } + + EnumFlags& operator^=(EnumFlags that) { + mValue ^= that.mValue; + return *this; + } + + EnumFlags& operator|=(TEnum e) { + mValue |= 1 << static_cast(e); + return *this; + } + + EnumFlags& operator&=(TEnum e) { + mValue &= 1 << static_cast(e); + return *this; + } + + EnumFlags& operator^=(TEnum e) { + mValue ^= 1 << static_cast(e); + return *this; + } + + EnumFlags operator|(EnumFlags that) const { return EnumFlags(mValue | that.mValue); } + EnumFlags operator&(EnumFlags that) const { return EnumFlags(mValue & that.mValue); } + EnumFlags operator^(EnumFlags that) const { return EnumFlags(mValue ^ that.mValue); } + + EnumFlags operator|(TEnum e) const { return EnumFlags(mValue | 1 << static_cast(e)); } + EnumFlags operator&(TEnum e) const { return EnumFlags(mValue & 1 << static_cast(e)); } + EnumFlags operator^(TEnum e) const { return EnumFlags(mValue ^ 1 << static_cast(e)); } + + EnumFlags operator~() const { return EnumFlags(~mValue); } +}; diff --git a/source/Common/LookupTable.hpp b/source/Common/LookupTable.hpp new file mode 100644 index 0000000..54548f2 --- /dev/null +++ b/source/Common/LookupTable.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +// BIDI stands for bi-directional +#define BIDI_LUT_DECL(name, aType, aCount, bType, bCount) \ + int gLutBidi_##name##_A2B[aCount]; \ + int gLutBidi_##name##_B2A[bCount]; \ + using name##AType = aType; \ + using name##BType = bType; \ + void InitializeLutBidi##name() +#define BIDI_LUT_MAP_FOR(name) \ + int* lutMappingA2B = gLutBidi_##name##_A2B; \ + int* lutMappingB2A = gLutBidi_##name##_B2A +#define BIDI_LUT_MAP(from, to) \ + lutMappingA2B[from] = to; \ + lutMappingB2A[to] = from +#define BIDI_LUT_INIT(name) InitializeLutBidi##name() +#define BIDI_LUT_A2B_LOOKUP(name, from) (name##BType)(gLutBidi_##name##_A2B[from]) +#define BIDI_LUT_B2A_LOOKUP(name, to) (name##AType)(gLutBidi_##name##_B2A[to]) + +// Forward string lookup +#define FSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + constexpr int kLutFwMinVal_##name = enumMinValue; \ + const char* gLutFw_##name[(int)enumMaxValue - (int)enumMinValue]; \ + void InitializeLutFw##name() +#define FSTR_LUT_MAP_FOR(name) \ + const char** lutMapping = gLutFw_##name; \ + int lutMappingMinValue = kLutFwMinVal_##name +#define FSTR_LUT_MAP(value, text) lutMapping[value - lutMappingMinValue] = text +#define FSTR_LUT_MAP_ENUM(enumValue) FSTR_LUT_MAP(enumValue, #enumValue) +#define FSTR_LUT_LOOKUP(name, enumValue) gLutFw_##name[enumValue - kLutFwMinVal_##name] +#define FSTR_LUT_INIT(name) InitializeLutFw##name() + +// RSTR stands for reverse-string lookup +#define RSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + robin_hood::unordered_flat_map gLutRv_##name; \ + void InitializeLutRv##name() +#define RSTR_LUT_MAP_FOR(name) auto& lutMapping = gLutRv_##name; +#define RSTR_LUT_MAP(value, text) lutMapping.insert_or_assign(std::string_view(text), value); +#define RSTR_LUT(name) gLutRv_##name +#define BSTR_LUT_LOOKUP(name, string) gLutRv_##name.find(std::string_view(text))->second +#define RSTR_LUT_INIT(name) InitializeLutRv##name() + +// BSTR stands for bi-directional string lookup +#define BSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + constexpr int kLutBstrMinVal_##name = enumMinValue; \ + const char* gLutBstr_##name##_V2S[(int)enumMaxValue - (int)enumMinValue]; \ + robin_hood::unordered_flat_map gLutBstr_##name##_S2V; \ + void InitializeLutBstr##name() +#define BSTR_LUT_MAP_FOR(name) \ + const char** lutMappingV2S = gLutBstr_##name##_V2S; \ + auto& lutMappingS2V = gLutBstr_##name##_S2V; \ + int lutMappingMinValue = kLutBstrMinVal_##name +#define BSTR_LUT_MAP(value, text) \ + lutMappingV2S[value - lutMappingMinValue] = text; \ + lutMappingS2V.insert_or_assign(std::string_view(text), value); +#define BSTR_LUT_MAP_ENUM(enumValue) BSTR_LUT_MAP(enumValue, #enumValue) +#define BSTR_LUT_V2S(name) gLutBstr_##name##_V2S +#define BSTR_LUT_S2V(name) gLutBstr_##name##_S2V +#define BSTR_LUT_V2S_LOOKUP(name, enumValue) gLutBstr_##name##_V2S[enumValue - kLutBstrMinVal_##name] +#define BSTR_LUT_S2V_LOOKUP(name, string) gLutBstr_##name##_S2V.find(std::string_view(text))->second +#define BSTR_LUT_INIT(name) InitializeLutBstr##name() diff --git a/source/Common/Macros.hpp b/source/Common/Macros.hpp new file mode 100644 index 0000000..a255ada --- /dev/null +++ b/source/Common/Macros.hpp @@ -0,0 +1,31 @@ +#pragma once + +#define STRINGIFY_IMPL(text) #text +#define STRINGIFY(text) STRINGIFY_IMPL(text) + +#define CONCAT_IMPL(a, b) a##b +#define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c)) +#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d)) + +#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__) +#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__) +#define DISCARD UNIQUE_NAME(_discard) + +#define UNUSED(x) (void)x; + +#define PRINTF_STRING_VIEW(s) (int)s.size(), s.data() + +#if defined(_MSC_VER) +# define UNREACHABLE __assume(0) +#elif defined(__GNUC__) || defined(__clang__) +# define UNREACHABLE __builtin_unreachable() +#else +# define UNREACHABLE +#endif + +#if _WIN32 +# define PLATFORM_PATH_STR "%ls" +#else +# define PLATFORM_PATH_STR "%s" +#endif diff --git a/source/Common/PodVector.hpp b/source/Common/PodVector.hpp new file mode 100644 index 0000000..74e99d6 --- /dev/null +++ b/source/Common/PodVector.hpp @@ -0,0 +1,297 @@ +// File adapted from dear-imgui's ImVector, implemented in https://github.com/ocornut/imgUI/blob/master/imgui.h +#pragma once + +#include +#include +#include +#include +#include +#include + +template +class PodVector { +public: + using value_type = T; + using iterator = value_type*; + using const_iterator = const value_type*; + +private: + int mSize; + int mCapacity; + T* mData; + +public: + PodVector() { + mSize = mCapacity = 0; + mData = nullptr; + } + + PodVector(const PodVector& src) { + mSize = mCapacity = 0; + mData = nullptr; + operator=(src); + } + + PodVector& operator=(const PodVector& src) { + clear(); + resize(src.mSize); + std::memcpy(mData, src.mData, (size_t)mSize * sizeof(T)); + return *this; + } + + PodVector(PodVector&& src) { + mSize = src.mSize; + mCapacity = src.mCapacity; + mData = src.mData; + + src.mSize = src.mCapacity = 0; + src.mData = nullptr; + } + + PodVector& operator=(PodVector&& src) { + if (this != &src) { + std::free(mData); + + mSize = src.mSize; + mCapacity = src.mCapacity; + mData = src.mData; + + src.mSize = src.mCapacity = 0; + src.mData = nullptr; + } + return *this; + } + + ~PodVector() { + std::free(mData); + } + + bool empty() const { return mSize == 0; } + int size() const { return mSize; } + int size_in_bytes() const { return mSize * (int)sizeof(T); } + int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); } + int capacity() const { return mCapacity; } + + T& operator[](int i) { + assert(i >= 0 && i < mSize); + return mData[i]; + } + + const T& operator[](int i) const { + assert(i >= 0 && i < mSize); + return mData[i]; + } + + void clear() { + if (mData) { + mSize = mCapacity = 0; + std::free(mData); + mData = nullptr; + } + } + + T* begin() { return mData; } + const T* begin() const { return mData; } + T* end() { return mData + mSize; } + const T* end() const { return mData + mSize; } + + T* data() { return mData; } + + T& front() { + assert(mSize > 0); + return mData[0]; + } + + const T& front() const { + assert(mSize > 0); + return mData[0]; + } + + T& back() { + assert(mSize > 0); + return mData[mSize - 1]; + } + + const T& back() const { + assert(mSize > 0); + return mData[mSize - 1]; + } + + void swap(PodVector& rhs) { + int rhs_size = rhs.mSize; + rhs.mSize = mSize; + mSize = rhs_size; + int rhs_cap = rhs.mCapacity; + rhs.mCapacity = mCapacity; + mCapacity = rhs_cap; + T* rhs_mDataTmp = rhs.mData; + rhs.mData = mData; + mData = rhs_mDataTmp; + } + + int grow_capacity(int sz) const { + int newCapacity = mCapacity ? (mCapacity + mCapacity / 2) : 8; + return newCapacity > sz ? newCapacity : sz; + } + + void resize(int new_size) { + if (new_size > mCapacity) reserve(grow_capacity(new_size)); + mSize = new_size; + } + + void resize_more(int size) { + resize(mSize + size); + } + + void resize(int new_size, const T& v) { + if (new_size > mCapacity) reserve(grow_capacity(new_size)); + if (new_size > mSize) { + for (int n = mSize; n < new_size; n++) { + std::memcpy(&mData[n], &v, sizeof(v)); + } + } + mSize = new_size; + } + + void resize_more(int size, const T& v) { + resize(mSize + size, v); + } + + void shrink(int new_size) { + assert(new_size <= mSize); + mSize = new_size; + } + + /// Resize a vector to a smaller mSize, guaranteed not to cause a reallocation + void reserve(int newCapacity) { + if (newCapacity <= mCapacity) return; + auto tmp = (T*)std::malloc((size_t)newCapacity * sizeof(T)); + if (mData) { + std::memcpy(tmp, mData, (size_t)mSize * sizeof(T)); + std::free(mData); + } + mData = tmp; + mCapacity = newCapacity; + } + + void reserve_more(int size) { + reserve(mSize + size); + } + + /// NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the PodVector data itself! e.g. v.push_back(v[10]) is forbidden. + void push_back(const T& v) { + if (mSize == mCapacity) reserve(grow_capacity(mSize + 1)); + std::memcpy(&mData[mSize], &v, sizeof(v)); + mSize++; + } + + void pop_back() { + assert(mSize > 0); + mSize--; + } + + void push_front(const T& v) { + if (mSize == 0) { + push_back(v); + } else { + insert(mData, v); + } + } + + T* erase(const T* it) { + assert(it >= mData && it < mData + mSize); + const ptrdiff_t off = it - mData; + std::memmove(mData + off, mData + off + 1, ((size_t)mSize - (size_t)off - 1) * sizeof(T)); + mSize--; + return mData + off; + } + + T* erase(const T* it, const T* it_last) { + assert(it >= mData && it < mData + mSize && it_last > it && it_last <= mData + mSize); + const ptrdiff_t count = it_last - it; + const ptrdiff_t off = it - mData; + std::memmove(mData + off, mData + off + count, ((size_t)mSize - (size_t)off - count) * sizeof(T)); + mSize -= (int)count; + return mData + off; + } + + T* erase_unsorted(const T* it) { + assert(it >= mData && it < mData + mSize); + const ptrdiff_t off = it - mData; + if (it < mData + mSize - 1) std::memcpy(mData + off, mData + mSize - 1, sizeof(T)); + mSize--; + return mData + off; + } + + T* insert(const T* it, const T& v) { + assert(it >= mData && it <= mData + mSize); + const ptrdiff_t off = it - mData; + if (mSize == mCapacity) reserve(grow_capacity(mSize + 1)); + if (off < (int)mSize) std::memmove(mData + off + 1, mData + off, ((size_t)mSize - (size_t)off) * sizeof(T)); + std::memcpy(&mData[off], &v, sizeof(v)); + mSize++; + return mData + off; + } + + bool contains(const T& v) const { + const T* data = mData; + const T* dataEnd = mData + mSize; + while (data < dataEnd) { + if (*data++ == v) return true; + } + return false; + } + + T* find(const T& v) { + T* data = mData; + const T* dataEnd = mData + mSize; + while (data < dataEnd) + if (*data == v) + break; + else + ++data; + return data; + } + + const T* find(const T& v) const { + const T* data = mData; + const T* dataEnd = mData + mSize; + while (data < dataEnd) + if (*data == v) + break; + else + ++data; + return data; + } + + bool find_erase(const T& v) { + const T* it = find(v); + if (it < mData + mSize) { + erase(it); + return true; + } + return false; + } + + bool find_erase_unsorted(const T& v) { + const T* it = find(v); + if (it < mData + mSize) { + erase_unsorted(it); + return true; + } + return false; + } + + int index_from_ptr(const T* it) const { + assert(it >= mData && it < mData + mSize); + const ptrdiff_t off = it - mData; + return (int)off; + } + + // Custom utility functions + + std::span as_span() { return { mData, (size_t)mSize }; } + std::span as_data_span() { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; } + std::span as_span() const { return { mData, (size_t)mSize }; } + std::span as_data_span() const { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; } +}; diff --git a/source/Common/RTTI.hpp b/source/Common/RTTI.hpp new file mode 100644 index 0000000..bc0d289 --- /dev/null +++ b/source/Common/RTTI.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +template +bool is_a(TBase* t) { + assert(t != nullptr); + return T::IsInstance(t); +} + +template +bool is_a_nullable(TBase* t) { + if (t) { + return is_a(t); + } else { + return false; + } +} + +template +T* dyn_cast(TBase* t) { + assert(t != nullptr); + if (T::IsInstance(t)) { + return static_cast(t); + } else { + return nullptr; + } +} + +template +const T* dyn_cast(const TBase* t) { + assert(t != nullptr); + if (T::IsInstance(t)) { + return static_cast(t); + } else { + return nullptr; + } +} + +template +T* dyn_cast_nullable(TBase* t) { + if (!t) return nullptr; + return dyn_cast(t); +} diff --git a/source/Common/RapidJsonHelper.hpp b/source/Common/RapidJsonHelper.hpp new file mode 100644 index 0000000..75cd93a --- /dev/null +++ b/source/Common/RapidJsonHelper.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include + +#define BRUSSEL_JSON_GET(object, name, type, out, failAction) \ + { \ + auto it = (object).FindMember(name); \ + if (it == (object).MemberEnd()) failAction; \ + auto& value = it->value; \ + if (!value.Is()) failAction; \ + (out) = value.Get(); \ + } + +#define BRUSSEL_JSON_GET_DEFAULT(object, name, type, out, theDefault) \ + do { \ + auto it = (object).FindMember(name); \ + if (it == (object).MemberEnd()) { \ + (out) = theDefault; \ + break; \ + } \ + auto& value = it->value; \ + if (!value.Is()) { \ + (out) = theDefault; \ + break; \ + } \ + (out) = value.Get(); \ + } while (0); + +namespace rapidjson { + +inline const Value* GetProperty(const Value& value, std::string_view name) { + for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { + if (it->name.GetStringLength() != name.size()) continue; + if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue; + + return &it->value; + } + return nullptr; +} + +inline const Value* GetProperty(const Value& value, Type type, std::string_view name) { + for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { + if (it->name.GetStringLength() != name.size()) continue; + if (it->value.GetType() != type) continue; + if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue; + + return &it->value; + } + return nullptr; +} + +inline std::string_view AsStringView(const Value& value) { + return std::string_view(value.GetString(), value.GetStringLength()); +} + +inline std::string_view AsStringView(const GenericStringRef& strRef) { + return std::string_view(strRef.s, strRef.length); +} + +inline std::string AsString(const Value& value) { + return std::string(value.GetString(), value.GetStringLength()); +} + +inline std::string AsString(const GenericStringRef& strRef) { + return std::string(strRef.s, strRef.length); +} + +// RapidJson itself already provides std::string and const char* overloads +inline GenericStringRef StringRef(std::string_view str) { + return GenericStringRef( + str.data() ? str.data() : "", + str.size()); +} + +template +rapidjson::Value WriteVectorPrimitives(rapidjson::Document& root, TIter begin, TSentienl end) { + using TElement = typename TIter::value_type; + + rapidjson::Value list; + while (begin != end) { + if constexpr (std::is_same_v) { + auto& elm = *begin; + list.PushBack(rapidjson::Value(elm.c_str(), elm.size()), root.GetAllocator()); + } else { + list.PushBack(*begin, root.GetAllocator()); + } + ++begin; + } + return list; +} + +template +bool ReadVectorPrimitives(const rapidjson::Value& value, TContainer& list) { + using TElement = typename TContainer::value_type; + + if (!value.IsArray()) return false; + + list.reserve(value.Size()); + for (auto& elm : value.GetArray()) { + if (!elm.Is()) return {}; + list.push_back(elm.Get()); + } + + return true; +} + +} // namespace rapidjson diff --git a/source/Common/RcPtr.hpp b/source/Common/RcPtr.hpp new file mode 100644 index 0000000..130b2b2 --- /dev/null +++ b/source/Common/RcPtr.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include "Macros.hpp" +#include "TypeTraits.hpp" + +#include +#include +#include +#include + +class RefCounted { +public: + // DO NOT MODIFY this field, unless explicitly documented the use + uint32_t refCount = 0; + uint32_t weakCount = 0; // TODO implement +}; + +template > +class RcPtr : TDeleter { +private: + static_assert(std::is_base_of_v); + T* mPtr; + +public: + RcPtr() + : mPtr{ nullptr } { + } + + explicit RcPtr(T* ptr) + : mPtr{ ptr } { + if (ptr) { + ++ptr->RefCounted::refCount; + } + } + + ~RcPtr() { + CleanUp(); + } + + void Attach(T* ptr) { + CleanUp(); + mPtr = ptr; + if (ptr) { + ++ptr->RefCounted::refCount; + } + } + + void Detatch() { + CleanUp(); + mPtr = nullptr; + } + + RcPtr(const RcPtr& that) + : mPtr{ that.mPtr } { + if (mPtr) { + ++mPtr->RefCounted::refCount; + } + } + + RcPtr& operator=(const RcPtr& that) { + CleanUp(); + mPtr = that.mPtr; + if (mPtr) { + ++mPtr->RefCounted::refCount; + } + return *this; + } + + RcPtr(RcPtr&& that) + : mPtr{ that.mPtr } { + that.mPtr = nullptr; + } + + RcPtr& operator=(RcPtr&& that) { + CleanUp(); + mPtr = that.mPtr; + that.mPtr = nullptr; + return *this; + } + + template + requires std::is_base_of_v + operator RcPtr() const { + return RcPtr(mPtr); + } + + bool operator==(std::nullptr_t ptr) const { + return mPtr == nullptr; + } + + bool operator==(const T* ptr) const { + return mPtr == ptr; + } + + bool operator==(T* ptr) const { + return mPtr == ptr; + } + + template + bool operator==(const RcPtr& ptr) const { + return mPtr == ptr.Get(); + } + + T* Get() const { + return mPtr; + } + + T& operator*() const { return *mPtr; } + T* operator->() const { return mPtr; } + +private: + void CleanUp() { + if (mPtr) { + --mPtr->RefCounted::refCount; + if (mPtr->RefCounted::refCount == 0) { + TDeleter::operator()(mPtr); + } + } + } +}; diff --git a/source/Common/Rect.hpp b/source/Common/Rect.hpp new file mode 100644 index 0000000..89d9b01 --- /dev/null +++ b/source/Common/Rect.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include + +/// Rect is a rectangle representation based on a point and a dimensions, in television coordinate space +/// (x increases from left to right, y increases from top to bottom). +template +class Rect { +public: + using ScalarType = T; + using VectorType = glm::vec<2, T>; + +public: + T x; + T y; + T width; + T height; + +public: + Rect() + : x{ 0 }, y{ 0 }, width{ 0 }, height{ 0 } { + } + + Rect(T x, T y, T width, T height) + : x{ x }, y{ y }, width{ width }, height{ height } { + } + + Rect(VectorType pos, VectorType size) + : x{ pos.x } + , y{ pos.y } + , width{ size.x } + , height{ size.y } { + } + + T x0() const { return x; } + T y0() const { return y; } + T x1() const { return x + width; } + T y1() const { return y + height; } + + VectorType TopLeft() const { + return VectorType{ x, y }; + } + + VectorType TopRight() const { + return VectorType{ x + width, y }; + } + + VectorType BottomLeft() const { + return VectorType{ x, y + height }; + } + + VectorType BottomRight() const { + return VectorType{ x + width, y + height }; + } + + VectorType Center() const { + return TopLeft() + VectorType{ width / 2, height / 2 }; + } + + VectorType Dimensions() const { + return VectorType{ width, height }; + } + + VectorType Extents() const { + return VectorType{ width / 2, height / 2 }; + } + + /// Assumes `bySize * 2` is smaller than both `width` and `height` (does not produce a negative-dimension rectangle). + Rect Shrink(T bySize) const { + T two = bySize * 2; + return Rect{ x + bySize, y + bySize, width - two, height - two }; + } + + Rect Shrink(T left, T top, T right, T bottom) const { + return Rect{ + x + left, + y + top, + width - left - right, + height - top - bottom, + }; + } + + Rect Expand(T bySize) const { + T two = bySize * 2; + return Rect{ x - bySize, y - bySize, width + two, height + two }; + } + + Rect Expand(T left, T top, T right, T bottom) const { + return Rect{ + x - left, + y - top, + width + left + right, + height + top + bottom, + }; + } + + bool Contains(VectorType point) const { + return point.x >= x && + point.y >= y && + point.x < x + width && + point.y < y + height; + } + + bool Intersects(const Rect& that) const { + bool xBetween = x > that.x0() && x < that.x1(); + bool yBetween = y > that.y0() && y < that.y1(); + return xBetween && yBetween; + } + + // Write min()/max() tenary by hand so that we don't have to include + // This file is practically going to be included in every file in this project + + static Rect Intersection(const Rect& a, const Rect& b) { + auto x0 = a.x0() > b.x0() ? a.x0() : b.x0(); // Max + auto y0 = a.y0() > b.y0() ? a.y0() : b.y0(); // Max + auto x1 = a.x1() < b.x1() ? a.x1() : b.x1(); // Min + auto y1 = a.y1() < b.y1() ? a.y1() : b.y1(); // Min + auto width = x1 - x0; + auto height = y1 - x0; + return Rect{ x0, y0, width, height }; + } + + static Rect Union(const Rect& a, const Rect& b) { + auto x0 = a.x0() < b.x0() ? a.x0() : b.x0(); // Min + auto y0 = a.y0() < b.y0() ? a.y0() : b.y0(); // Min + auto x1 = a.x1() > b.x1() ? a.x1() : b.x1(); // Max + auto y1 = a.y1() > b.y1() ? a.y1() : b.y1(); // Max + auto width = x1 - x0; + auto height = y1 - x0; + return Rect{ x0, y0, width, height }; + } + + friend bool operator==(const Rect&, const Rect&) = default; + + Rect operator+(glm::vec<2, T> offset) const { + return { x + offset.x, y + offset.y, width, height }; + } + + Rect operator-(glm::vec<2, T> offset) const { + return { x - offset.x, y - offset.y, width, height }; + } + + Rect& operator+=(glm::vec<2, T> offset) { + x += offset.x; + y += offset.y; + return *this; + } + + Rect& operator-=(glm::vec<2, T> offset) { + x -= offset.x; + y -= offset.y; + return *this; + } + + template + Rect Cast() const { + return { + static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height), + }; + } +}; diff --git a/source/Common/ScopeGuard.hpp b/source/Common/ScopeGuard.hpp new file mode 100644 index 0000000..28f3385 --- /dev/null +++ b/source/Common/ScopeGuard.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "Macros.hpp" + +#include + +template +class ScopeGuard { +private: + TCleanupFunc mFunc; + bool mDismissed = false; + +public: + /// Specifically left this implicit so that constructs like + /// \code + /// ScopeGuard sg = [&]() { res.Cleanup(); }; + /// \endcode + /// would work. It is highly discourage and unlikely that one would want to use ScopeGuard as a function + /// parameter, so the normal argument that implicit conversion are harmful doesn't really apply here. + // Deliberately not explicit to allow usages like: ScopeGuard var = lambda; + ScopeGuard(TCleanupFunc&& function) noexcept + : mFunc{ std::move(function) } { + } + + ~ScopeGuard() noexcept { + if (!mDismissed) { + mFunc(); + } + } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + + ScopeGuard(ScopeGuard&& that) noexcept + : mFunc{ std::move(that.mFunc) } { + that.Cancel(); + } + + ScopeGuard& operator=(ScopeGuard&& that) noexcept { + if (!mDismissed) { + mFunc(); + } + this->mFunc = std::move(that.mFunc); + this->cancelled = std::exchange(that.cancelled, true); + } + + void Dismiss() noexcept { + mDismissed = true; + } +}; + +template +auto GuardDeletion(T* ptr) { + return ScopeGuard([ptr]() { + delete ptr; + }); +} + +#define SCOPE_GUARD(name) ScopeGuard name = [&]() +#define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]() diff --git a/source/Common/SmallVector.cpp b/source/Common/SmallVector.cpp new file mode 100644 index 0000000..c38e8a7 --- /dev/null +++ b/source/Common/SmallVector.cpp @@ -0,0 +1,145 @@ +// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/lib/Support/SmallVector.cpp +// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c +// adapted in code style and utilities to fix this project + +//===- llvm/ADT/SmallVector.cpp - 'Normally small' vectors ----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the SmallVector class. +// +//===----------------------------------------------------------------------===// + +#include "SmallVector.hpp" + +#include +#include +#include + +// Check that no bytes are wasted and everything is well-aligned. +namespace { +// These structures may cause binary compat warnings on AIX. Suppress the +// warning since we are only using these types for the static assertions below. +#if defined(_AIX) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Waix-compat" +#endif +struct Struct16B { + alignas(16) void* X; +}; +struct Struct32B { + alignas(32) void* X; +}; +#if defined(_AIX) +# pragma GCC diagnostic pop +#endif +} // namespace +static_assert(sizeof(SmallVector) == + sizeof(unsigned) * 2 + sizeof(void*), + "wasted space in SmallVector size 0"); +static_assert(alignof(SmallVector) >= alignof(Struct16B), + "wrong alignment for 16-byte aligned T"); +static_assert(alignof(SmallVector) >= alignof(Struct32B), + "wrong alignment for 32-byte aligned T"); +static_assert(sizeof(SmallVector) >= alignof(Struct16B), + "missing padding for 16-byte aligned T"); +static_assert(sizeof(SmallVector) >= alignof(Struct32B), + "missing padding for 32-byte aligned T"); +static_assert(sizeof(SmallVector) == + sizeof(unsigned) * 2 + sizeof(void*) * 2, + "wasted space in SmallVector size 1"); + +static_assert(sizeof(SmallVector) == + sizeof(void*) * 2 + sizeof(void*), + "1 byte elements have word-sized type for size and capacity"); + +/// Report that MinSize doesn't fit into this vector's size type. Throws +/// std::length_error or calls report_fatal_error. +[[noreturn]] static void report_size_overflow(size_t MinSize, size_t MaxSize); +static void report_size_overflow(size_t MinSize, size_t MaxSize) { + std::string Reason = "SmallVector unable to grow. Requested capacity (" + + std::to_string(MinSize) + + ") is larger than maximum value for size type (" + + std::to_string(MaxSize) + ")"; + throw std::length_error(Reason); +} + +/// Report that this vector is already at maximum capacity. Throws +/// std::length_error or calls report_fatal_error. +[[noreturn]] static void report_at_maximum_capacity(size_t MaxSize); +static void report_at_maximum_capacity(size_t MaxSize) { + std::string Reason = + "SmallVector capacity unable to grow. Already at maximum size " + + std::to_string(MaxSize); + throw std::length_error(Reason); +} + +// Note: Moving this function into the header may cause performance regression. +template +static size_t getNewCapacity(size_t MinSize, size_t TSize, size_t OldCapacity) { + constexpr size_t MaxSize = std::numeric_limits::max(); + + // Ensure we can fit the new capacity. + // This is only going to be applicable when the capacity is 32 bit. + if (MinSize > MaxSize) + report_size_overflow(MinSize, MaxSize); + + // Ensure we can meet the guarantee of space for at least one more element. + // The above check alone will not catch the case where grow is called with a + // default MinSize of 0, but the current capacity cannot be increased. + // This is only going to be applicable when the capacity is 32 bit. + if (OldCapacity == MaxSize) + report_at_maximum_capacity(MaxSize); + + // In theory 2*capacity can overflow if the capacity is 64 bit, but the + // original capacity would never be large enough for this to be a problem. + size_t NewCapacity = 2 * OldCapacity + 1; // Always grow. + return std::min(std::max(NewCapacity, MinSize), MaxSize); +} + +// Note: Moving this function into the header may cause performance regression. +template +void* SmallVectorBase::mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity) { + NewCapacity = getNewCapacity(MinSize, TSize, this->capacity()); + return malloc(NewCapacity * TSize); +} + +// Note: Moving this function into the header may cause performance regression. +template +void SmallVectorBase::grow_pod(void* FirstEl, size_t MinSize, size_t TSize) { + size_t NewCapacity = getNewCapacity(MinSize, TSize, this->capacity()); + void* NewElts; + if (BeginX == FirstEl) { + NewElts = malloc(NewCapacity * TSize); + + // Copy the elements over. No need to run dtors on PODs. + memcpy(NewElts, this->BeginX, size() * TSize); + } else { + // If this wasn't grown from the inline copy, grow the allocated space. + NewElts = realloc(this->BeginX, NewCapacity * TSize); + } + + this->BeginX = NewElts; + this->Capacity = NewCapacity; +} + +template class SmallVectorBase; + +// Disable the uint64_t instantiation for 32-bit builds. +// Both uint32_t and uint64_t instantiations are needed for 64-bit builds. +// This instantiation will never be used in 32-bit builds, and will cause +// warnings when sizeof(Size_T) > sizeof(size_t). +#if SIZE_MAX > UINT32_MAX +template class SmallVectorBase; + +// Assertions to ensure this #if stays in sync with SmallVectorSizeType. +static_assert(sizeof(SmallVectorSizeType) == sizeof(uint64_t), + "Expected SmallVectorBase variant to be in use."); +#else +static_assert(sizeof(SmallVectorSizeType) == sizeof(uint32_t), + "Expected SmallVectorBase variant to be in use."); +#endif diff --git a/source/Common/SmallVector.hpp b/source/Common/SmallVector.hpp new file mode 100644 index 0000000..e33a25d --- /dev/null +++ b/source/Common/SmallVector.hpp @@ -0,0 +1,1332 @@ +// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/ADT/SmallVector.h +// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c +// adapted in code style and utilities to fix this project + +//===- llvm/ADT/SmallVector.h - 'Normally small' vectors --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines the SmallVector class. +/// +//===----------------------------------------------------------------------===// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4267) // The compiler detected a conversion from size_t to a smaller type. +#endif + +#if __has_builtin(__builtin_expect) || defined(__GNUC__) +# define LLVM_LIKELY(EXPR) __builtin_expect((bool)(EXPR), true) +# define LLVM_UNLIKELY(EXPR) __builtin_expect((bool)(EXPR), false) +#else +# define LLVM_LIKELY(EXPR) (EXPR) +# define LLVM_UNLIKELY(EXPR) (EXPR) +#endif + +template +class iterator_range; + +/// This is all the stuff common to all SmallVectors. +/// +/// The template parameter specifies the type which should be used to hold the +/// Size and Capacity of the SmallVector, so it can be adjusted. +/// Using 32 bit size is desirable to shrink the size of the SmallVector. +/// Using 64 bit size is desirable for cases like SmallVector, where a +/// 32 bit size would limit the vector to ~4GB. SmallVectors are used for +/// buffering bitcode output - which can exceed 4GB. +template +class SmallVectorBase { +protected: + void* BeginX; + Size_T Size = 0, Capacity; + + /// The maximum value of the Size_T used. + static constexpr size_t SizeTypeMax() { + return std::numeric_limits::max(); + } + + SmallVectorBase() = delete; + SmallVectorBase(void* FirstEl, size_t TotalCapacity) + : BeginX(FirstEl), Capacity(TotalCapacity) {} + + /// This is a helper for \a grow() that's out of line to reduce code + /// duplication. This function will report a fatal error if it can't grow at + /// least to \p MinSize. + void* mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity); + + /// This is an implementation of the grow() method which only works + /// on POD-like data types and is out of line to reduce code duplication. + /// This function will report a fatal error if it cannot increase capacity. + void grow_pod(void* FirstEl, size_t MinSize, size_t TSize); + +public: + size_t size() const { return Size; } + size_t capacity() const { return Capacity; } + + [[nodiscard]] bool empty() const { return !Size; } + +protected: + /// Set the array size to \p N, which the current array must have enough + /// capacity for. + /// + /// This does not construct or destroy any elements in the vector. + void set_size(size_t N) { + assert(N <= capacity()); + Size = N; + } +}; + +template +using SmallVectorSizeType = + typename std::conditional= 8, uint64_t, uint32_t>::type; + +/// Figure out the offset of the first element. +template +struct SmallVectorAlignmentAndSize { + alignas(SmallVectorBase>) char Base[sizeof( + SmallVectorBase>)]; + alignas(T) char FirstEl[sizeof(T)]; +}; + +/// This is the part of SmallVectorTemplateBase which does not depend on whether +/// the type T is a POD. The extra dummy template argument is used by ArrayRef +/// to avoid unnecessarily requiring T to be complete. +template +class SmallVectorTemplateCommon + : public SmallVectorBase> { + using Base = SmallVectorBase>; + + /// Find the address of the first element. For this pointer math to be valid + /// with small-size of 0 for T with lots of alignment, it's important that + /// SmallVectorStorage is properly-aligned even for small-size of 0. + void* getFirstEl() const { + return const_cast(reinterpret_cast( + reinterpret_cast(this) + + offsetof(SmallVectorAlignmentAndSize, FirstEl))); + } + // Space after 'FirstEl' is clobbered, do not add any instance vars after it. + +protected: + SmallVectorTemplateCommon(size_t Size) + : Base(getFirstEl(), Size) {} + + void grow_pod(size_t MinSize, size_t TSize) { + Base::grow_pod(getFirstEl(), MinSize, TSize); + } + + /// Return true if this is a smallvector which has not had dynamic + /// memory allocated for it. + bool isSmall() const { return this->BeginX == getFirstEl(); } + + /// Put this vector in a state of being small. + void resetToSmall() { + this->BeginX = getFirstEl(); + this->Size = this->Capacity = 0; // FIXME: Setting Capacity to 0 is suspect. + } + + /// Return true if V is an internal reference to the given range. + bool isReferenceToRange(const void* V, const void* First, const void* Last) const { + // Use std::less to avoid UB. + std::less<> LessThan; + return !LessThan(V, First) && LessThan(V, Last); + } + + /// Return true if V is an internal reference to this vector. + bool isReferenceToStorage(const void* V) const { + return isReferenceToRange(V, this->begin(), this->end()); + } + + /// Return true if First and Last form a valid (possibly empty) range in this + /// vector's storage. + bool isRangeInStorage(const void* First, const void* Last) const { + // Use std::less to avoid UB. + std::less<> LessThan; + return !LessThan(First, this->begin()) && !LessThan(Last, First) && + !LessThan(this->end(), Last); + } + + /// Return true unless Elt will be invalidated by resizing the vector to + /// NewSize. + bool isSafeToReferenceAfterResize(const void* Elt, size_t NewSize) { + // Past the end. + if (LLVM_LIKELY(!isReferenceToStorage(Elt))) + return true; + + // Return false if Elt will be destroyed by shrinking. + if (NewSize <= this->size()) + return Elt < this->begin() + NewSize; + + // Return false if we need to grow. + return NewSize <= this->capacity(); + } + + /// Check whether Elt will be invalidated by resizing the vector to NewSize. + void assertSafeToReferenceAfterResize(const void* Elt, size_t NewSize) { + assert(isSafeToReferenceAfterResize(Elt, NewSize) && + "Attempting to reference an element of the vector in an operation " + "that invalidates it"); + } + + /// Check whether Elt will be invalidated by increasing the size of the + /// vector by N. + void assertSafeToAdd(const void* Elt, size_t N = 1) { + this->assertSafeToReferenceAfterResize(Elt, this->size() + N); + } + + /// Check whether any part of the range will be invalidated by clearing. + void assertSafeToReferenceAfterClear(const T* From, const T* To) { + if (From == To) + return; + this->assertSafeToReferenceAfterResize(From, 0); + this->assertSafeToReferenceAfterResize(To - 1, 0); + } + template < + class ItTy, + std::enable_if_t, T*>::value, + bool> = false> + void assertSafeToReferenceAfterClear(ItTy, ItTy) {} + + /// Check whether any part of the range will be invalidated by growing. + void assertSafeToAddRange(const T* From, const T* To) { + if (From == To) + return; + this->assertSafeToAdd(From, To - From); + this->assertSafeToAdd(To - 1, To - From); + } + template < + class ItTy, + std::enable_if_t, T*>::value, + bool> = false> + void assertSafeToAddRange(ItTy, ItTy) {} + + /// Reserve enough space to add one element, and return the updated element + /// pointer in case it was a reference to the storage. + template + static const T* reserveForParamAndGetAddressImpl(U* This, const T& Elt, size_t N) { + size_t NewSize = This->size() + N; + if (LLVM_LIKELY(NewSize <= This->capacity())) + return &Elt; + + bool ReferencesStorage = false; + int64_t Index = -1; + if (!U::TakesParamByValue) { + if (LLVM_UNLIKELY(This->isReferenceToStorage(&Elt))) { + ReferencesStorage = true; + Index = &Elt - This->begin(); + } + } + This->grow(NewSize); + return ReferencesStorage ? This->begin() + Index : &Elt; + } + +public: + using size_type = size_t; + using difference_type = ptrdiff_t; + using value_type = T; + using iterator = T*; + using const_iterator = const T*; + + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = std::reverse_iterator; + + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + + using Base::capacity; + using Base::empty; + using Base::size; + + // forward iterator creation methods. + iterator begin() { return (iterator)this->BeginX; } + const_iterator begin() const { return (const_iterator)this->BeginX; } + iterator end() { return begin() + size(); } + const_iterator end() const { return begin() + size(); } + + // reverse iterator creation methods. + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + size_type size_in_bytes() const { return size() * sizeof(T); } + size_type max_size() const { + return std::min(this->SizeTypeMax(), size_type(-1) / sizeof(T)); + } + + size_t capacity_in_bytes() const { return capacity() * sizeof(T); } + + /// Return a pointer to the vector's buffer, even if empty(). + pointer data() { return pointer(begin()); } + /// Return a pointer to the vector's buffer, even if empty(). + const_pointer data() const { return const_pointer(begin()); } + + reference operator[](size_type idx) { + assert(idx < size()); + return begin()[idx]; + } + const_reference operator[](size_type idx) const { + assert(idx < size()); + return begin()[idx]; + } + + reference front() { + assert(!empty()); + return begin()[0]; + } + const_reference front() const { + assert(!empty()); + return begin()[0]; + } + + reference back() { + assert(!empty()); + return end()[-1]; + } + const_reference back() const { + assert(!empty()); + return end()[-1]; + } +}; + +/// SmallVectorTemplateBase - This is where we put +/// method implementations that are designed to work with non-trivial T's. +/// +/// We approximate is_trivially_copyable with trivial move/copy construction and +/// trivial destruction. While the standard doesn't specify that you're allowed +/// copy these types with memcpy, there is no way for the type to observe this. +/// This catches the important case of std::pair, which is not +/// trivially assignable. +template ::value) && (std::is_trivially_move_constructible::value) && std::is_trivially_destructible::value> +class SmallVectorTemplateBase : public SmallVectorTemplateCommon { + friend class SmallVectorTemplateCommon; + +protected: + static constexpr bool TakesParamByValue = false; + using ValueParamT = const T&; + + SmallVectorTemplateBase(size_t Size) + : SmallVectorTemplateCommon(Size) {} + + static void destroy_range(T* S, T* E) { + while (S != E) { + --E; + E->~T(); + } + } + + /// Move the range [I, E) into the uninitialized memory starting with "Dest", + /// constructing elements as needed. + template + static void uninitialized_move(It1 I, It1 E, It2 Dest) { + std::uninitialized_copy(std::make_move_iterator(I), + std::make_move_iterator(E), + Dest); + } + + /// Copy the range [I, E) onto the uninitialized memory starting with "Dest", + /// constructing elements as needed. + template + static void uninitialized_copy(It1 I, It1 E, It2 Dest) { + std::uninitialized_copy(I, E, Dest); + } + + /// Grow the allocated memory (without initializing new elements), doubling + /// the size of the allocated memory. Guarantees space for at least one more + /// element, or MinSize more elements if specified. + void grow(size_t MinSize = 0); + + /// Create a new allocation big enough for \p MinSize and pass back its size + /// in \p NewCapacity. This is the first section of \a grow(). + T* mallocForGrow(size_t MinSize, size_t& NewCapacity) { + return static_cast( + SmallVectorBase>::mallocForGrow( + MinSize, sizeof(T), NewCapacity)); + } + + /// Move existing elements over to the new allocation \p NewElts, the middle + /// section of \a grow(). + void moveElementsForGrow(T* NewElts); + + /// Transfer ownership of the allocation, finishing up \a grow(). + void takeAllocationForGrow(T* NewElts, size_t NewCapacity); + + /// Reserve enough space to add one element, and return the updated element + /// pointer in case it was a reference to the storage. + const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) { + return this->reserveForParamAndGetAddressImpl(this, Elt, N); + } + + /// Reserve enough space to add one element, and return the updated element + /// pointer in case it was a reference to the storage. + T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) { + return const_cast( + this->reserveForParamAndGetAddressImpl(this, Elt, N)); + } + + static T&& forward_value_param(T&& V) { return std::move(V); } + static const T& forward_value_param(const T& V) { return V; } + + void growAndAssign(size_t NumElts, const T& Elt) { + // Grow manually in case Elt is an internal reference. + size_t NewCapacity; + T* NewElts = mallocForGrow(NumElts, NewCapacity); + std::uninitialized_fill_n(NewElts, NumElts, Elt); + this->destroy_range(this->begin(), this->end()); + takeAllocationForGrow(NewElts, NewCapacity); + this->set_size(NumElts); + } + + template + T& growAndEmplaceBack(ArgTypes&&... Args) { + // Grow manually in case one of Args is an internal reference. + size_t NewCapacity; + T* NewElts = mallocForGrow(0, NewCapacity); + ::new ((void*)(NewElts + this->size())) T(std::forward(Args)...); + moveElementsForGrow(NewElts); + takeAllocationForGrow(NewElts, NewCapacity); + this->set_size(this->size() + 1); + return this->back(); + } + +public: + void push_back(const T& Elt) { + const T* EltPtr = reserveForParamAndGetAddress(Elt); + ::new ((void*)this->end()) T(*EltPtr); + this->set_size(this->size() + 1); + } + + void push_back(T&& Elt) { + T* EltPtr = reserveForParamAndGetAddress(Elt); + ::new ((void*)this->end()) T(::std::move(*EltPtr)); + this->set_size(this->size() + 1); + } + + void pop_back() { + this->set_size(this->size() - 1); + this->end()->~T(); + } +}; + +// Define this out-of-line to dissuade the C++ compiler from inlining it. +template +void SmallVectorTemplateBase::grow(size_t MinSize) { + size_t NewCapacity; + T* NewElts = mallocForGrow(MinSize, NewCapacity); + moveElementsForGrow(NewElts); + takeAllocationForGrow(NewElts, NewCapacity); +} + +// Define this out-of-line to dissuade the C++ compiler from inlining it. +template +void SmallVectorTemplateBase::moveElementsForGrow( + T* NewElts) { + // Move the elements over. + this->uninitialized_move(this->begin(), this->end(), NewElts); + + // Destroy the original elements. + destroy_range(this->begin(), this->end()); +} + +// Define this out-of-line to dissuade the C++ compiler from inlining it. +template +void SmallVectorTemplateBase::takeAllocationForGrow( + T* NewElts, size_t NewCapacity) { + // If this wasn't grown from the inline copy, deallocate the old space. + if (!this->isSmall()) + free(this->begin()); + + this->BeginX = NewElts; + this->Capacity = NewCapacity; +} + +/// SmallVectorTemplateBase - This is where we put +/// method implementations that are designed to work with trivially copyable +/// T's. This allows using memcpy in place of copy/move construction and +/// skipping destruction. +template +class SmallVectorTemplateBase : public SmallVectorTemplateCommon { + friend class SmallVectorTemplateCommon; + +protected: + /// True if it's cheap enough to take parameters by value. Doing so avoids + /// overhead related to mitigations for reference invalidation. + static constexpr bool TakesParamByValue = sizeof(T) <= 2 * sizeof(void*); + + /// Either const T& or T, depending on whether it's cheap enough to take + /// parameters by value. + using ValueParamT = + typename std::conditional::type; + + SmallVectorTemplateBase(size_t Size) + : SmallVectorTemplateCommon(Size) {} + + // No need to do a destroy loop for POD's. + static void destroy_range(T*, T*) {} + + /// Move the range [I, E) onto the uninitialized memory + /// starting with "Dest", constructing elements into it as needed. + template + static void uninitialized_move(It1 I, It1 E, It2 Dest) { + // Just do a copy. + uninitialized_copy(I, E, Dest); + } + + /// Copy the range [I, E) onto the uninitialized memory + /// starting with "Dest", constructing elements into it as needed. + template + static void uninitialized_copy(It1 I, It1 E, It2 Dest) { + // Arbitrary iterator types; just use the basic implementation. + std::uninitialized_copy(I, E, Dest); + } + + /// Copy the range [I, E) onto the uninitialized memory + /// starting with "Dest", constructing elements into it as needed. + template + static void uninitialized_copy( + T1* I, T1* E, T2* Dest, std::enable_if_t::type, T2>::value>* = nullptr) { + // Use memcpy for PODs iterated by pointers (which includes SmallVector + // iterators): std::uninitialized_copy optimizes to memmove, but we can + // use memcpy here. Note that I and E are iterators and thus might be + // invalid for memcpy if they are equal. + if (I != E) + memcpy(reinterpret_cast(Dest), I, (E - I) * sizeof(T)); + } + + /// Double the size of the allocated memory, guaranteeing space for at + /// least one more element or MinSize if specified. + void grow(size_t MinSize = 0) { this->grow_pod(MinSize, sizeof(T)); } + + /// Reserve enough space to add one element, and return the updated element + /// pointer in case it was a reference to the storage. + const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) { + return this->reserveForParamAndGetAddressImpl(this, Elt, N); + } + + /// Reserve enough space to add one element, and return the updated element + /// pointer in case it was a reference to the storage. + T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) { + return const_cast( + this->reserveForParamAndGetAddressImpl(this, Elt, N)); + } + + /// Copy \p V or return a reference, depending on \a ValueParamT. + static ValueParamT forward_value_param(ValueParamT V) { return V; } + + void growAndAssign(size_t NumElts, T Elt) { + // Elt has been copied in case it's an internal reference, side-stepping + // reference invalidation problems without losing the realloc optimization. + this->set_size(0); + this->grow(NumElts); + std::uninitialized_fill_n(this->begin(), NumElts, Elt); + this->set_size(NumElts); + } + + template + T& growAndEmplaceBack(ArgTypes&&... Args) { + // Use push_back with a copy in case Args has an internal reference, + // side-stepping reference invalidation problems without losing the realloc + // optimization. + push_back(T(std::forward(Args)...)); + return this->back(); + } + +public: + void push_back(ValueParamT Elt) { + const T* EltPtr = reserveForParamAndGetAddress(Elt); + memcpy(reinterpret_cast(this->end()), EltPtr, sizeof(T)); + this->set_size(this->size() + 1); + } + + void pop_back() { this->set_size(this->size() - 1); } +}; + +/// This class consists of common code factored out of the SmallVector class to +/// reduce code duplication based on the SmallVector 'N' template parameter. +template +class SmallVectorImpl : public SmallVectorTemplateBase { + using SuperClass = SmallVectorTemplateBase; + +public: + using iterator = typename SuperClass::iterator; + using const_iterator = typename SuperClass::const_iterator; + using reference = typename SuperClass::reference; + using size_type = typename SuperClass::size_type; + +protected: + using SmallVectorTemplateBase::TakesParamByValue; + using ValueParamT = typename SuperClass::ValueParamT; + + // Default ctor - Initialize to empty. + explicit SmallVectorImpl(unsigned N) + : SmallVectorTemplateBase(N) {} + + void assignRemote(SmallVectorImpl&& RHS) { + this->destroy_range(this->begin(), this->end()); + if (!this->isSmall()) + free(this->begin()); + this->BeginX = RHS.BeginX; + this->Size = RHS.Size; + this->Capacity = RHS.Capacity; + RHS.resetToSmall(); + } + +public: + SmallVectorImpl(const SmallVectorImpl&) = delete; + + ~SmallVectorImpl() { + // Subclass has already destructed this vector's elements. + // If this wasn't grown from the inline copy, deallocate the old space. + if (!this->isSmall()) + free(this->begin()); + } + + void clear() { + this->destroy_range(this->begin(), this->end()); + this->Size = 0; + } + +private: + // Make set_size() private to avoid misuse in subclasses. + using SuperClass::set_size; + + template + void resizeImpl(size_type N) { + if (N == this->size()) + return; + + if (N < this->size()) { + this->truncate(N); + return; + } + + this->reserve(N); + for (auto I = this->end(), E = this->begin() + N; I != E; ++I) + if (ForOverwrite) + new (&*I) T; + else + new (&*I) T(); + this->set_size(N); + } + +public: + void resize(size_type N) { resizeImpl(N); } + + /// Like resize, but \ref T is POD, the new values won't be initialized. + void resize_for_overwrite(size_type N) { resizeImpl(N); } + + /// Like resize, but requires that \p N is less than \a size(). + void truncate(size_type N) { + assert(this->size() >= N && "Cannot increase size with truncate"); + this->destroy_range(this->begin() + N, this->end()); + this->set_size(N); + } + + void resize(size_type N, ValueParamT NV) { + if (N == this->size()) + return; + + if (N < this->size()) { + this->truncate(N); + return; + } + + // N > this->size(). Defer to append. + this->append(N - this->size(), NV); + } + + void reserve(size_type N) { + if (this->capacity() < N) + this->grow(N); + } + + void pop_back_n(size_type NumItems) { + assert(this->size() >= NumItems); + truncate(this->size() - NumItems); + } + + [[nodiscard]] T pop_back_val() { + T Result = ::std::move(this->back()); + this->pop_back(); + return Result; + } + + void swap(SmallVectorImpl& RHS); + + /// Add the specified range to the end of the SmallVector. + template ::iterator_category, + std::input_iterator_tag>::value>> + void append(in_iter in_start, in_iter in_end) { + this->assertSafeToAddRange(in_start, in_end); + size_type NumInputs = std::distance(in_start, in_end); + this->reserve(this->size() + NumInputs); + this->uninitialized_copy(in_start, in_end, this->end()); + this->set_size(this->size() + NumInputs); + } + + /// Append \p NumInputs copies of \p Elt to the end. + void append(size_type NumInputs, ValueParamT Elt) { + const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumInputs); + std::uninitialized_fill_n(this->end(), NumInputs, *EltPtr); + this->set_size(this->size() + NumInputs); + } + + void append(std::initializer_list IL) { + append(IL.begin(), IL.end()); + } + + void append(const SmallVectorImpl& RHS) { append(RHS.begin(), RHS.end()); } + + void assign(size_type NumElts, ValueParamT Elt) { + // Note that Elt could be an internal reference. + if (NumElts > this->capacity()) { + this->growAndAssign(NumElts, Elt); + return; + } + + // Assign over existing elements. + std::fill_n(this->begin(), std::min(NumElts, this->size()), Elt); + if (NumElts > this->size()) + std::uninitialized_fill_n(this->end(), NumElts - this->size(), Elt); + else if (NumElts < this->size()) + this->destroy_range(this->begin() + NumElts, this->end()); + this->set_size(NumElts); + } + + // FIXME: Consider assigning over existing elements, rather than clearing & + // re-initializing them - for all assign(...) variants. + + template ::iterator_category, + std::input_iterator_tag>::value>> + void assign(in_iter in_start, in_iter in_end) { + this->assertSafeToReferenceAfterClear(in_start, in_end); + clear(); + append(in_start, in_end); + } + + void assign(std::initializer_list IL) { + clear(); + append(IL); + } + + void assign(const SmallVectorImpl& RHS) { assign(RHS.begin(), RHS.end()); } + + iterator erase(const_iterator CI) { + // Just cast away constness because this is a non-const member function. + iterator I = const_cast(CI); + + assert(this->isReferenceToStorage(CI) && "Iterator to erase is out of bounds."); + + iterator N = I; + // Shift all elts down one. + std::move(I + 1, this->end(), I); + // Drop the last elt. + this->pop_back(); + return (N); + } + + iterator erase(const_iterator CS, const_iterator CE) { + // Just cast away constness because this is a non-const member function. + iterator S = const_cast(CS); + iterator E = const_cast(CE); + + assert(this->isRangeInStorage(S, E) && "Range to erase is out of bounds."); + + iterator N = S; + // Shift all elts down. + iterator I = std::move(E, this->end(), S); + // Drop the last elts. + this->destroy_range(I, this->end()); + this->set_size(I - this->begin()); + return (N); + } + +private: + template + iterator insert_one_impl(iterator I, ArgType&& Elt) { + // Callers ensure that ArgType is derived from T. + static_assert( + std::is_same>, + T>::value, + "ArgType must be derived from T!"); + + if (I == this->end()) { // Important special case for empty vector. + this->push_back(::std::forward(Elt)); + return this->end() - 1; + } + + assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds."); + + // Grow if necessary. + size_t Index = I - this->begin(); + std::remove_reference_t* EltPtr = + this->reserveForParamAndGetAddress(Elt); + I = this->begin() + Index; + + ::new ((void*)this->end()) T(::std::move(this->back())); + // Push everything else over. + std::move_backward(I, this->end() - 1, this->end()); + this->set_size(this->size() + 1); + + // If we just moved the element we're inserting, be sure to update + // the reference (never happens if TakesParamByValue). + static_assert(!TakesParamByValue || std::is_same::value, + "ArgType must be 'T' when taking by value!"); + if (!TakesParamByValue && this->isReferenceToRange(EltPtr, I, this->end())) + ++EltPtr; + + *I = ::std::forward(*EltPtr); + return I; + } + +public: + iterator insert(iterator I, T&& Elt) { + return insert_one_impl(I, this->forward_value_param(std::move(Elt))); + } + + iterator insert(iterator I, const T& Elt) { + return insert_one_impl(I, this->forward_value_param(Elt)); + } + + iterator insert(iterator I, size_type NumToInsert, ValueParamT Elt) { + // Convert iterator to elt# to avoid invalidating iterator when we reserve() + size_t InsertElt = I - this->begin(); + + if (I == this->end()) { // Important special case for empty vector. + append(NumToInsert, Elt); + return this->begin() + InsertElt; + } + + assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds."); + + // Ensure there is enough space, and get the (maybe updated) address of + // Elt. + const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumToInsert); + + // Uninvalidate the iterator. + I = this->begin() + InsertElt; + + // If there are more elements between the insertion point and the end of the + // range than there are being inserted, we can use a simple approach to + // insertion. Since we already reserved space, we know that this won't + // reallocate the vector. + if (size_t(this->end() - I) >= NumToInsert) { + T* OldEnd = this->end(); + append(std::move_iterator(this->end() - NumToInsert), + std::move_iterator(this->end())); + + // Copy the existing elements that get replaced. + std::move_backward(I, OldEnd - NumToInsert, OldEnd); + + // If we just moved the element we're inserting, be sure to update + // the reference (never happens if TakesParamByValue). + if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end()) + EltPtr += NumToInsert; + + std::fill_n(I, NumToInsert, *EltPtr); + return I; + } + + // Otherwise, we're inserting more elements than exist already, and we're + // not inserting at the end. + + // Move over the elements that we're about to overwrite. + T* OldEnd = this->end(); + this->set_size(this->size() + NumToInsert); + size_t NumOverwritten = OldEnd - I; + this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten); + + // If we just moved the element we're inserting, be sure to update + // the reference (never happens if TakesParamByValue). + if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end()) + EltPtr += NumToInsert; + + // Replace the overwritten part. + std::fill_n(I, NumOverwritten, *EltPtr); + + // Insert the non-overwritten middle part. + std::uninitialized_fill_n(OldEnd, NumToInsert - NumOverwritten, *EltPtr); + return I; + } + + template ::iterator_category, + std::input_iterator_tag>::value>> + iterator insert(iterator I, ItTy From, ItTy To) { + // Convert iterator to elt# to avoid invalidating iterator when we reserve() + size_t InsertElt = I - this->begin(); + + if (I == this->end()) { // Important special case for empty vector. + append(From, To); + return this->begin() + InsertElt; + } + + assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds."); + + // Check that the reserve that follows doesn't invalidate the iterators. + this->assertSafeToAddRange(From, To); + + size_t NumToInsert = std::distance(From, To); + + // Ensure there is enough space. + reserve(this->size() + NumToInsert); + + // Uninvalidate the iterator. + I = this->begin() + InsertElt; + + // If there are more elements between the insertion point and the end of the + // range than there are being inserted, we can use a simple approach to + // insertion. Since we already reserved space, we know that this won't + // reallocate the vector. + if (size_t(this->end() - I) >= NumToInsert) { + T* OldEnd = this->end(); + append(std::move_iterator(this->end() - NumToInsert), + std::move_iterator(this->end())); + + // Copy the existing elements that get replaced. + std::move_backward(I, OldEnd - NumToInsert, OldEnd); + + std::copy(From, To, I); + return I; + } + + // Otherwise, we're inserting more elements than exist already, and we're + // not inserting at the end. + + // Move over the elements that we're about to overwrite. + T* OldEnd = this->end(); + this->set_size(this->size() + NumToInsert); + size_t NumOverwritten = OldEnd - I; + this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten); + + // Replace the overwritten part. + for (T* J = I; NumOverwritten > 0; --NumOverwritten) { + *J = *From; + ++J; + ++From; + } + + // Insert the non-overwritten middle part. + this->uninitialized_copy(From, To, OldEnd); + return I; + } + + void insert(iterator I, std::initializer_list IL) { + insert(I, IL.begin(), IL.end()); + } + + template + reference emplace_back(ArgTypes&&... Args) { + if (LLVM_UNLIKELY(this->size() >= this->capacity())) + return this->growAndEmplaceBack(std::forward(Args)...); + + ::new ((void*)this->end()) T(std::forward(Args)...); + this->set_size(this->size() + 1); + return this->back(); + } + + SmallVectorImpl& operator=(const SmallVectorImpl& RHS); + + SmallVectorImpl& operator=(SmallVectorImpl&& RHS); + + bool operator==(const SmallVectorImpl& RHS) const { + if (this->size() != RHS.size()) return false; + return std::equal(this->begin(), this->end(), RHS.begin()); + } + bool operator!=(const SmallVectorImpl& RHS) const { + return !(*this == RHS); + } + + bool operator<(const SmallVectorImpl& RHS) const { + return std::lexicographical_compare(this->begin(), this->end(), RHS.begin(), RHS.end()); + } +}; + +template +void SmallVectorImpl::swap(SmallVectorImpl& RHS) { + if (this == &RHS) return; + + // We can only avoid copying elements if neither vector is small. + if (!this->isSmall() && !RHS.isSmall()) { + std::swap(this->BeginX, RHS.BeginX); + std::swap(this->Size, RHS.Size); + std::swap(this->Capacity, RHS.Capacity); + return; + } + this->reserve(RHS.size()); + RHS.reserve(this->size()); + + // Swap the shared elements. + size_t NumShared = this->size(); + if (NumShared > RHS.size()) NumShared = RHS.size(); + for (size_type i = 0; i != NumShared; ++i) + std::swap((*this)[i], RHS[i]); + + // Copy over the extra elts. + if (this->size() > RHS.size()) { + size_t EltDiff = this->size() - RHS.size(); + this->uninitialized_copy(this->begin() + NumShared, this->end(), RHS.end()); + RHS.set_size(RHS.size() + EltDiff); + this->destroy_range(this->begin() + NumShared, this->end()); + this->set_size(NumShared); + } else if (RHS.size() > this->size()) { + size_t EltDiff = RHS.size() - this->size(); + this->uninitialized_copy(RHS.begin() + NumShared, RHS.end(), this->end()); + this->set_size(this->size() + EltDiff); + this->destroy_range(RHS.begin() + NumShared, RHS.end()); + RHS.set_size(NumShared); + } +} + +template +SmallVectorImpl& SmallVectorImpl:: +operator=(const SmallVectorImpl& RHS) { + // Avoid self-assignment. + if (this == &RHS) return *this; + + // If we already have sufficient space, assign the common elements, then + // destroy any excess. + size_t RHSSize = RHS.size(); + size_t CurSize = this->size(); + if (CurSize >= RHSSize) { + // Assign common elements. + iterator NewEnd; + if (RHSSize) + NewEnd = std::copy(RHS.begin(), RHS.begin() + RHSSize, this->begin()); + else + NewEnd = this->begin(); + + // Destroy excess elements. + this->destroy_range(NewEnd, this->end()); + + // Trim. + this->set_size(RHSSize); + return *this; + } + + // If we have to grow to have enough elements, destroy the current elements. + // This allows us to avoid copying them during the grow. + // FIXME: don't do this if they're efficiently moveable. + if (this->capacity() < RHSSize) { + // Destroy current elements. + this->clear(); + CurSize = 0; + this->grow(RHSSize); + } else if (CurSize) { + // Otherwise, use assignment for the already-constructed elements. + std::copy(RHS.begin(), RHS.begin() + CurSize, this->begin()); + } + + // Copy construct the new elements in place. + this->uninitialized_copy(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize); + + // Set end. + this->set_size(RHSSize); + return *this; +} + +template +SmallVectorImpl& SmallVectorImpl::operator=(SmallVectorImpl&& RHS) { + // Avoid self-assignment. + if (this == &RHS) return *this; + + // If the RHS isn't small, clear this vector and then steal its buffer. + if (!RHS.isSmall()) { + this->assignRemote(std::move(RHS)); + return *this; + } + + // If we already have sufficient space, assign the common elements, then + // destroy any excess. + size_t RHSSize = RHS.size(); + size_t CurSize = this->size(); + if (CurSize >= RHSSize) { + // Assign common elements. + iterator NewEnd = this->begin(); + if (RHSSize) + NewEnd = std::move(RHS.begin(), RHS.end(), NewEnd); + + // Destroy excess elements and trim the bounds. + this->destroy_range(NewEnd, this->end()); + this->set_size(RHSSize); + + // Clear the RHS. + RHS.clear(); + + return *this; + } + + // If we have to grow to have enough elements, destroy the current elements. + // This allows us to avoid copying them during the grow. + // FIXME: this may not actually make any sense if we can efficiently move + // elements. + if (this->capacity() < RHSSize) { + // Destroy current elements. + this->clear(); + CurSize = 0; + this->grow(RHSSize); + } else if (CurSize) { + // Otherwise, use assignment for the already-constructed elements. + std::move(RHS.begin(), RHS.begin() + CurSize, this->begin()); + } + + // Move-construct the new elements in place. + this->uninitialized_move(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize); + + // Set end. + this->set_size(RHSSize); + + RHS.clear(); + return *this; +} + +/// Storage for the SmallVector elements. This is specialized for the N=0 case +/// to avoid allocating unnecessary storage. +template +struct SmallVectorStorage { + alignas(T) char InlineElts[N * sizeof(T)]; +}; + +/// We need the storage to be properly aligned even for small-size of 0 so that +/// the pointer math in \a SmallVectorTemplateCommon::getFirstEl() is +/// well-defined. +template +struct alignas(T) SmallVectorStorage {}; + +/// Forward declaration of SmallVector so that +/// calculateSmallVectorDefaultInlinedElements can reference +/// `sizeof(SmallVector)`. +template +class SmallVector; + +/// Helper class for calculating the default number of inline elements for +/// `SmallVector`. +/// +/// This should be migrated to a constexpr function when our minimum +/// compiler support is enough for multi-statement constexpr functions. +template +struct CalculateSmallVectorDefaultInlinedElements { + // Parameter controlling the default number of inlined elements + // for `SmallVector`. + // + // The default number of inlined elements ensures that + // 1. There is at least one inlined element. + // 2. `sizeof(SmallVector) <= kPreferredSmallVectorSizeof` unless + // it contradicts 1. + static constexpr size_t kPreferredSmallVectorSizeof = 64; + + // static_assert that sizeof(T) is not "too big". + // + // Because our policy guarantees at least one inlined element, it is possible + // for an arbitrarily large inlined element to allocate an arbitrarily large + // amount of inline storage. We generally consider it an antipattern for a + // SmallVector to allocate an excessive amount of inline storage, so we want + // to call attention to these cases and make sure that users are making an + // intentional decision if they request a lot of inline storage. + // + // We want this assertion to trigger in pathological cases, but otherwise + // not be too easy to hit. To accomplish that, the cutoff is actually somewhat + // larger than kPreferredSmallVectorSizeof (otherwise, + // `SmallVector>` would be one easy way to trip it, and that + // pattern seems useful in practice). + // + // One wrinkle is that this assertion is in theory non-portable, since + // sizeof(T) is in general platform-dependent. However, we don't expect this + // to be much of an issue, because most LLVM development happens on 64-bit + // hosts, and therefore sizeof(T) is expected to *decrease* when compiled for + // 32-bit hosts, dodging the issue. The reverse situation, where development + // happens on a 32-bit host and then fails due to sizeof(T) *increasing* on a + // 64-bit host, is expected to be very rare. + static_assert( + sizeof(T) <= 256, + "You are trying to use a default number of inlined elements for " + "`SmallVector` but `sizeof(T)` is really big! Please use an " + "explicit number of inlined elements with `SmallVector` to make " + "sure you really want that much inline storage."); + + // Discount the size of the header itself when calculating the maximum inline + // bytes. + static constexpr size_t PreferredInlineBytes = + kPreferredSmallVectorSizeof - sizeof(SmallVector); + static constexpr size_t NumElementsThatFit = PreferredInlineBytes / sizeof(T); + static constexpr size_t value = + NumElementsThatFit == 0 ? 1 : NumElementsThatFit; +}; + +/// This is a 'vector' (really, a variable-sized array), optimized +/// for the case when the array is small. It contains some number of elements +/// in-place, which allows it to avoid heap allocation when the actual number of +/// elements is below that threshold. This allows normal "small" cases to be +/// fast without losing generality for large inputs. +/// +/// \note +/// In the absence of a well-motivated choice for the number of inlined +/// elements \p N, it is recommended to use \c SmallVector (that is, +/// omitting the \p N). This will choose a default number of inlined elements +/// reasonable for allocation on the stack (for example, trying to keep \c +/// sizeof(SmallVector) around 64 bytes). +/// +/// \warning This does not attempt to be exception safe. +/// +/// \see https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h +template ::value> +class SmallVector : public SmallVectorImpl, + SmallVectorStorage { +public: + SmallVector() + : SmallVectorImpl(N) {} + + ~SmallVector() { + // Destroy the constructed elements in the vector. + this->destroy_range(this->begin(), this->end()); + } + + explicit SmallVector(size_t Size, const T& Value = T()) + : SmallVectorImpl(N) { + this->assign(Size, Value); + } + + template ::iterator_category, + std::input_iterator_tag>::value>> + SmallVector(ItTy S, ItTy E) + : SmallVectorImpl(N) { + this->append(S, E); + } + + template + explicit SmallVector(const iterator_range& R) + : SmallVectorImpl(N) { + this->append(R.begin(), R.end()); + } + + SmallVector(std::initializer_list IL) + : SmallVectorImpl(N) { + this->assign(IL); + } + + SmallVector(const SmallVector& RHS) + : SmallVectorImpl(N) { + if (!RHS.empty()) + SmallVectorImpl::operator=(RHS); + } + + SmallVector& operator=(const SmallVector& RHS) { + SmallVectorImpl::operator=(RHS); + return *this; + } + + SmallVector(SmallVector&& RHS) + : SmallVectorImpl(N) { + if (!RHS.empty()) + SmallVectorImpl::operator=(::std::move(RHS)); + } + + SmallVector(SmallVectorImpl&& RHS) + : SmallVectorImpl(N) { + if (!RHS.empty()) + SmallVectorImpl::operator=(::std::move(RHS)); + } + + SmallVector& operator=(SmallVector&& RHS) { + if (N) { + SmallVectorImpl::operator=(::std::move(RHS)); + return *this; + } + // SmallVectorImpl::operator= does not leverage N==0. Optimize the + // case. + if (this == &RHS) + return *this; + if (RHS.empty()) { + this->destroy_range(this->begin(), this->end()); + this->Size = 0; + } else { + this->assignRemote(std::move(RHS)); + } + return *this; + } + + SmallVector& operator=(SmallVectorImpl&& RHS) { + SmallVectorImpl::operator=(::std::move(RHS)); + return *this; + } + + SmallVector& operator=(std::initializer_list IL) { + this->assign(IL); + return *this; + } +}; + +template +inline size_t capacity_in_bytes(const SmallVector& X) { + return X.capacity_in_bytes(); +} + +template +using ValueTypeFromRangeType = + typename std::remove_const()))>::type>::type; + +/// Given a range of type R, iterate the entire range and return a +/// SmallVector with elements of the vector. This is useful, for example, +/// when you want to iterate a range and then sort the results. +template +SmallVector, Size> to_vector(R&& Range) { + return { std::begin(Range), std::end(Range) }; +} +template +SmallVector, + CalculateSmallVectorDefaultInlinedElements< + ValueTypeFromRangeType>::value> +to_vector(R&& Range) { + return { std::begin(Range), std::end(Range) }; +} + +namespace std { + +/// Implement std::swap in terms of SmallVector swap. +template +inline void swap(SmallVectorImpl& LHS, SmallVectorImpl& RHS) { + LHS.swap(RHS); +} + +/// Implement std::swap in terms of SmallVector swap. +template +inline void swap(SmallVector& LHS, SmallVector& RHS) { + LHS.swap(RHS); +} + +} // namespace std + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/source/Common/StbImplementations.c b/source/Common/StbImplementations.c new file mode 100644 index 0000000..73bbc2a --- /dev/null +++ b/source/Common/StbImplementations.c @@ -0,0 +1,14 @@ +#define STB_RECT_PACK_IMPLEMENTATION +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define STB_SPRINTF_IMPLEMENTATION +#include + +#define STB_C_LEXER_IMPLEMENTATION +#include diff --git a/source/Common/Type2ObjectMap.hpp b/source/Common/Type2ObjectMap.hpp new file mode 100644 index 0000000..24c45f3 --- /dev/null +++ b/source/Common/Type2ObjectMap.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "TypeTraits.hpp" + +#include + +template +class Type2ObjectMap { +public: + template + TType& Insert(TType&& value) { + // TODO + } + + template + TType& InsertOrAssign(TType& value) { + // TODO + } + + template + TType Remove() { + // TODO + } + + template + const TValue* Find() const { + // TODO + } + + template + TValue* Find() { + return const_cast(const_cast(this)->Find()); + } + + size_t size() const { + // TODO + } +}; diff --git a/source/Common/TypeTraits.hpp b/source/Common/TypeTraits.hpp new file mode 100644 index 0000000..73a56f9 --- /dev/null +++ b/source/Common/TypeTraits.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +/// This template will be instanciated for each unique type, and the char variable will be ODR-used which gives it an unique address. +template +struct TypeIdentifier { + static const char obj = 0; +}; + +template +struct DefaultDeleter { + void operator()(T* ptr) const { + delete ptr; + } +}; + +template +struct RemoveMemberPtrImpl {}; + +template +struct RemoveMemberPtrImpl { + using Type = U; +}; + +template +using RemoveMemberPtr = typename RemoveMemberPtrImpl::Type; diff --git a/source/Common/Uid.cpp b/source/Common/Uid.cpp new file mode 100644 index 0000000..1930cd8 --- /dev/null +++ b/source/Common/Uid.cpp @@ -0,0 +1,58 @@ +#include "Uid.hpp" + +#include "RapidJsonHelper.hpp" + +#include +#include +#include + +Uid Uid::Create() { + std::random_device rd; + std::mt19937_64 gen(rd()); + std::uniform_int_distribution dist( + std::numeric_limits::min(), + std::numeric_limits::max()); + + Uid uid; + uid.upper = dist(gen); + uid.lower = dist(gen); + return uid; +} + +bool Uid::IsNull() const { + return upper == 0 && lower == 0; +} + +void Uid::ReadString(std::string_view str) { + sscanf(str.data(), BRUSSEL_Uid_SCAN_STR, &upper, &lower); +} + +std::string Uid::WriteString() { + char buf[256]; + snprintf(buf, sizeof(buf), BRUSSEL_Uid_FORMAT_STR, upper, lower); + return std::string(buf); +} + +void Uid::Read(const rapidjson::Value& value) { + assert(value.IsArray()); + assert(value.Size() == 2); + auto& upper = value[0]; + assert(upper.IsUint64()); + auto& lower = value[1]; + assert(lower.IsUint64()); + + this->upper = upper.GetUint64(); + this->lower = lower.GetUint64(); +} + +void Uid::WriteInto(rapidjson::Value& value, rapidjson::Document& root) { + value.Reserve(2, root.GetAllocator()); + value.PushBack((uint64_t)upper, root.GetAllocator()); + value.PushBack((uint64_t)lower, root.GetAllocator()); +} + +rapidjson::Value Uid::Write(rapidjson::Document& root) { + rapidjson::Value result(rapidjson::kArrayType); + WriteInto(result, root); + return result; +} diff --git a/source/Common/Uid.hpp b/source/Common/Uid.hpp new file mode 100644 index 0000000..f58129c --- /dev/null +++ b/source/Common/Uid.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "Utils.hpp" + +#include +#include +#include +#include +#include + +#define BRUSSEL_Uid_SCAN_STR "%" PRIx64 "-%" PRIx64 +#define BRUSSEL_Uid_SCAN_EXPAND(uid) &((uid).upper), &((uid).upper) +#define BRUSSEL_Uid_FORMAT_STR "%016" PRIx64 "-%016" PRIx64 +#define BRUSSEL_Uid_FORMAT_EXPAND(uid) (uid).upper, (uid).lower + +struct Uid { + uint64_t upper = 0; + uint64_t lower = 0; + + static Uid Create(); + + bool IsNull() const; + + void ReadString(std::string_view str); + std::string WriteString(); + + void Read(const rapidjson::Value& value); + void WriteInto(rapidjson::Value& value, rapidjson::Document& root); + rapidjson::Value Write(rapidjson::Document& root); + + auto operator<=>(const Uid&) const = default; +}; + +template <> +struct std::hash { + size_t operator()(const Uid& uid) const { + size_t hash = 0; + Utils::HashCombine(hash, uid.upper); + Utils::HashCombine(hash, uid.lower); + return hash; + } +}; diff --git a/source/Common/Utils.cpp b/source/Common/Utils.cpp new file mode 100644 index 0000000..dc76b0a --- /dev/null +++ b/source/Common/Utils.cpp @@ -0,0 +1,107 @@ +#include "Utils.hpp" + +#include "Macros.hpp" +#include "ScopeGuard.hpp" + +#ifdef _WIN32 +# include +#endif + +namespace fs = std::filesystem; + +#ifdef _WIN32 +# define BRUSSEL_MODE_STRING(string) L##string +#else +# define BRUSSEL_MODE_STRING(string) string +#endif + +#if _WIN32 +using FopenModeString = const wchar_t*; +#else +using FopenModeString = const char*; +#endif + +static FopenModeString GetModeString(Utils::IoMode mode, bool binary) { + using namespace Utils; + if (binary) { + switch (mode) { + case Read: return BRUSSEL_MODE_STRING("rb"); + case WriteTruncate: return BRUSSEL_MODE_STRING("wb"); + case WriteAppend: return BRUSSEL_MODE_STRING("ab"); + } + } else { + switch (mode) { + case Read: return BRUSSEL_MODE_STRING("r"); + case WriteTruncate: return BRUSSEL_MODE_STRING("w"); + case WriteAppend: return BRUSSEL_MODE_STRING("a"); + } + } + return nullptr; +} + +FILE* Utils::OpenCstdioFile(const fs::path& path, IoMode mode, bool binary) { +#ifdef _WIN32 + // fs::path::c_str() returns `const wchar_t*` under Windows, because NT uses UTF-16 natively + // NOTE: _wfopen() only affects the type of path parameter, otherwise the file stream created is identical to the one by fopen() + return _wfopen(path.c_str(), ::GetModeString(mode, binary)); +#else + return fopen(path.c_str(), ::GetModeString(mode, binary)); +#endif +} + +FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) { +#ifdef _WIN32 + // On Windows, fopen() accepts ANSI codepage encoded path, convert our UTF-8 string to UTF-16 to ensure that no matter what the locale is, the path continues to work + WCHAR platformPath[MAX_PATH]; + if (MultiByteToWideChar(CP_UTF8, 0, path, -1, platformPath, MAX_PATH) == 0) { + return nullptr; + } + return _wfopen(platformPath, ::GetModeString(mode, binary)); +#else + return fopen(path, ::GetModeString(mode, binary)); +#endif +} + +std::string Utils::ReadFileAsString(const fs::path& path) { + auto file = Utils::OpenCstdioFile(path, Utils::Read); + if (!file) throw std::runtime_error("Failed to open source file."); + DEFER { fclose(file); }; + + fseek(file, 0, SEEK_END); + auto fileSize = ftell(file); + rewind(file); + + std::string result(fileSize, '\0'); + fread(result.data(), fileSize, 1, file); + + return result; +} + +bool Utils::InRangeInclusive(int n, int lower, int upper) { + if (lower > upper) { + std::swap(lower, upper); + } + return n >= lower && n <= upper; +} + +bool Utils::LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate) { + bool verticalLine = p1.x == p2.x && InRangeInclusive(candidate.x, p1.x, p2.x); + bool horizontalLine = p1.y == p2.y && InRangeInclusive(candidate.y, p1.y, p2.y); + return verticalLine && horizontalLine; +} + +bool Utils::IsColinear(glm::ivec2 p1, glm::ivec2 p2) { + return p1.x == p2.x || p1.y == p2.y; +} + +std::string Utils::MakeRandomNumberedName(const char* tag) { + int n = std::rand(); +#define RNG_NAME_PATTERN "Unnamed %s #%d", tag, n + // NOTE: does not include null-terminator + int size = snprintf(nullptr, 0, RNG_NAME_PATTERN); + std::string result; + result.resize(size); // std::string::resize handles storage for null-terminator alreaedy + snprintf(result.data(), size, RNG_NAME_PATTERN); +#undef RNG_NAME_PATTERN + return result; +} diff --git a/source/Common/Utils.hpp b/source/Common/Utils.hpp new file mode 100644 index 0000000..9560b57 --- /dev/null +++ b/source/Common/Utils.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Utils { + +enum IoMode { + Read, + WriteTruncate, + WriteAppend, +}; + +FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary = false); +FILE* OpenCstdioFile(const char* path, IoMode mode, bool binary = false); + +std::string ReadFileAsString(const std::filesystem::path& path); + +constexpr float Abs(float v) noexcept { + return v < 0.0f ? -v : v; +} + +bool InRangeInclusive(int n, int lower, int upper); +bool LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate); + +bool IsColinear(glm::ivec2 p1, glm::ivec2 p2); + +template +void HashCombine(std::size_t& seed, const T& v) { + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +std::string MakeRandomNumberedName(const char* tag); + +} // namespace Utils + +struct StringHash { + using is_transparent = void; + + std::size_t operator()(const std::string& key) const { return robin_hood::hash_bytes(key.c_str(), key.size()); } + std::size_t operator()(std::string_view key) const { return robin_hood::hash_bytes(key.data(), key.size()); } + std::size_t operator()(const char* key) const { return robin_hood::hash_bytes(key, std::strlen(key)); } +}; + +struct StringEqual { + using is_transparent = int; + + bool operator()(std::string_view lhs, const std::string& rhs) const { + const std::string_view view = rhs; + return lhs == view; + } + + bool operator()(const char* lhs, const std::string& rhs) const { + return std::strcmp(lhs, rhs.c_str()) == 0; + } + + bool operator()(const std::string& lhs, const std::string& rhs) const { + return lhs == rhs; + } +}; diff --git a/source/Common/YCombinator.hpp b/source/Common/YCombinator.hpp new file mode 100644 index 0000000..b1d2350 --- /dev/null +++ b/source/Common/YCombinator.hpp @@ -0,0 +1,14 @@ +#pragma once + +template +struct YCombinator { + // NOTE: implicit constructor allows initializing this + Func func; + + template + decltype(auto) operator()(Ts&&... args) const { + // NOTE: static_cast(args)... is equivalent to std::forward(args)... + // written this way so that we don't have to include , as well as reducing template instanciations to help compile time + return func(*this, static_cast(args)...); + } +}; diff --git a/source/Common/buildfile b/source/Common/buildfile new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/source/Common/buildfile @@ -0,0 +1 @@ + diff --git a/source/Game/App.cpp b/source/Game/App.cpp new file mode 100644 index 0000000..45a7545 --- /dev/null +++ b/source/Game/App.cpp @@ -0,0 +1,149 @@ +#include "App.hpp" + +#include +#include + +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); +} + +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 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/Game/App.hpp b/source/Game/App.hpp new file mode 100644 index 0000000..c73c5a1 --- /dev/null +++ b/source/Game/App.hpp @@ -0,0 +1,62 @@ +#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 + +#include +#include +#include +#include + +using KeyCaptureCallback = std::function; + +class App { +private: + std::deque mKeyCaptureCallbacks; + PodVector mPlayers; + std::unique_ptr 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/Game/AppConfig.hpp b/source/Game/AppConfig.hpp new file mode 100644 index 0000000..794bee5 --- /dev/null +++ b/source/Game/AppConfig.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +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/Game/Camera.cpp b/source/Game/Camera.cpp new file mode 100644 index 0000000..39f0369 --- /dev/null +++ b/source/Game/Camera.cpp @@ -0,0 +1,46 @@ +#include "Camera.hpp" + +#include "AppConfig.hpp" + +#include + +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/Game/Camera.hpp b/source/Game/Camera.hpp new file mode 100644 index 0000000..7bf0a6c --- /dev/null +++ b/source/Game/Camera.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +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/Game/CommonVertexIndex.cpp b/source/Game/CommonVertexIndex.cpp new file mode 100644 index 0000000..786274e --- /dev/null +++ b/source/Game/CommonVertexIndex.cpp @@ -0,0 +1,152 @@ +#include "CommonVertexIndex.hpp" + +template +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 +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 +static void AssignPositions(TVertex vertices[4], const Rect& 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 +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 +static void AssignDepths(TVertex vertices[4], float z) { + for (int i = 0; i < 4; ++i) { + auto& vert = vertices[i]; + vert.z = z; + } +} + +template +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 +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(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(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(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(indices, topLeft, topRight, bottomRight, bottomLeft); +} + +void Vertex_PC::Assign(Vertex_PC vertices[4], const Rect& 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& 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/Game/CommonVertexIndex.hpp b/source/Game/CommonVertexIndex.hpp new file mode 100644 index 0000000..adb81b6 --- /dev/null +++ b/source/Game/CommonVertexIndex.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "Color.hpp" +#include "RcPtr.hpp" +#include "Rect.hpp" +#include "Texture.hpp" +#include "VertexIndex.hpp" + +#include + +// Initialized in main() +inline RcPtr gVformatStandard{}; +inline RcPtr gVformatStandardSplit{}; + +// 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& 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& 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/Game/EditorAccessories.cpp b/source/Game/EditorAccessories.cpp new file mode 100644 index 0000000..08d08ec --- /dev/null +++ b/source/Game/EditorAccessories.cpp @@ -0,0 +1,6 @@ +#include "EditorAccessories.hpp" + +#include + +void EditorSettings::Show() { +} diff --git a/source/Game/EditorAccessories.hpp b/source/Game/EditorAccessories.hpp new file mode 100644 index 0000000..687b509 --- /dev/null +++ b/source/Game/EditorAccessories.hpp @@ -0,0 +1,6 @@ +#pragma once + +class EditorSettings { +public: + void Show(); +}; diff --git a/source/Game/EditorAttachment.hpp b/source/Game/EditorAttachment.hpp new file mode 100644 index 0000000..61b824b --- /dev/null +++ b/source/Game/EditorAttachment.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +class EditorAttachment { +public: + std::string name; + +public: + EditorAttachment(); + virtual ~EditorAttachment() = default; +}; diff --git a/source/Game/EditorAttachmentImpl.cpp b/source/Game/EditorAttachmentImpl.cpp new file mode 100644 index 0000000..b09c133 --- /dev/null +++ b/source/Game/EditorAttachmentImpl.cpp @@ -0,0 +1,23 @@ +#include "EditorAttachmentImpl.hpp" +#include "EditorAttachment.hpp" + +#include + +EditorAttachment::EditorAttachment() { +} + +std::unique_ptr 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(result); +} diff --git a/source/Game/EditorAttachmentImpl.hpp b/source/Game/EditorAttachmentImpl.hpp new file mode 100644 index 0000000..53bcd37 --- /dev/null +++ b/source/Game/EditorAttachmentImpl.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "EditorAttachment.hpp" +#include "GameObject.hpp" +#include "Material.hpp" +#include "Player.hpp" +#include "Sprite.hpp" + +#include + +class EaGameObject : public EditorAttachment { +public: + // NOTE: in degrees + glm::vec3 eulerAnglesRotation; + +public: + static std::unique_ptr Create(GameObject* object); +}; + +class EaPlayer : public EaGameObject { +public: + RcPtr confSprite; + RcPtr confMaterial; +}; + +class EaLevelWrapper : public EaGameObject { +public: +}; + +class EaIresObject : public EditorAttachment { +public: + std::string nameEditingScratch; + bool isEditingName = false; +}; diff --git a/source/Game/EditorCommandPalette.cpp b/source/Game/EditorCommandPalette.cpp new file mode 100644 index 0000000..0e7b894 --- /dev/null +++ b/source/Game/EditorCommandPalette.cpp @@ -0,0 +1,406 @@ +#include "EditorCommandPalette.hpp" + +#include "AppConfig.hpp" +#include "EditorUtils.hpp" +#include "FuzzyMatch.hpp" +#include "Utils.hpp" + +#include +#include +#include +#include +#include +#include + +#define IMGUI_DEFINE_MATH_OPERATORS +#include + +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 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(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::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::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/Game/EditorCommandPalette.hpp b/source/Game/EditorCommandPalette.hpp new file mode 100644 index 0000000..101344d --- /dev/null +++ b/source/Game/EditorCommandPalette.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include +#include + +class EditorCommandExecuteContext; +class EditorCommand { +public: + std::string name; + std::function callback; + std::function subsequentCallback; + std::function terminate; +}; + +class EditorCommandExecuteContext { + friend class EditorCommandPalette; + +private: + const EditorCommand* mCommand = nullptr; + std::vector mCurrentOptions; + int mDepth = 0; + +public: + bool IsInitiated() const; + const EditorCommand* GetCurrentCommand() const; + void Initiate(const EditorCommand& command); + + void Prompt(std::vector 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 mCommands; + std::vector mItems; + std::vector 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/Game/EditorCore.hpp b/source/Game/EditorCore.hpp new file mode 100644 index 0000000..726f43e --- /dev/null +++ b/source/Game/EditorCore.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +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 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/Game/EditorCorePrivate.cpp b/source/Game/EditorCorePrivate.cpp new file mode 100644 index 0000000..43857a8 --- /dev/null +++ b/source/Game/EditorCorePrivate.cpp @@ -0,0 +1,1113 @@ +#include "EditorCorePrivate.hpp" + +#include "App.hpp" +#include "AppConfig.hpp" +#include "EditorAccessories.hpp" +#include "EditorAttachmentImpl.hpp" +#include "EditorCommandPalette.hpp" +#include "EditorNotification.hpp" +#include "EditorUtils.hpp" +#include "GameObject.hpp" +#include "Mesh.hpp" +#include "Player.hpp" +#include "SceneThings.hpp" +#include "VertexIndex.hpp" + +#include +#include +#include +#include + +#define GLFW_INCLUDE_NONE +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace ProjectBrussel_UNITY_ID { +// TODO handle internal state internally and move this to EditorUtils.hpp +enum RenamableSelectableAction { + RSA_None, + RSA_Selected, + RSA_RenameCommitted, + RSA_RenameCancelled, +}; +RenamableSelectableAction RenamableSelectable(const char* displayName, bool selected, bool& renaming, std::string& renamingScratchBuffer) // +{ + RenamableSelectableAction result = RSA_None; + + ImGuiSelectableFlags flags = 0; + // When renaming, disable all other entries that is not the one being renamed + if (renaming && !selected) { + flags |= ImGuiSelectableFlags_Disabled; + } + + if (renaming && selected) { + // State: being renamed + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); + ImGui::SetKeyboardFocusHere(); + if (ImGui::InputText("##Rename", &renamingScratchBuffer, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) { + // Confirm + renaming = false; + result = RSA_RenameCommitted; + } + ImGui::PopStyleVar(2); + + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + // Cancel + renaming = false; + result = RSA_RenameCancelled; + } + } else { + // State: normal + + if (ImGui::Selectable(displayName, selected, flags)) { + result = RSA_Selected; + } + } + + return result; +} +} // namespace ProjectBrussel_UNITY_ID + +void EditorInspector::SelectTarget(TargetType type, void* object) { + selectedItt = type; + selectedItPtr = object; + renaming = false; + renamingScratchBuffer.clear(); +} + +EditorContentBrowser::EditorContentBrowser(EditorInspector* inspector) + : mInspector{ inspector } { +} + +EditorContentBrowser::~EditorContentBrowser() { +} + +void EditorContentBrowser::Show(bool* open) { + using namespace ProjectBrussel_UNITY_ID; + + ImGuiWindowFlags windowFlags; + if (mDocked) { + // Center window horizontally, align bottom vertically + auto& viewportSize = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2, viewportSize.y), ImGuiCond_Always, ImVec2(0.5f, 1.0f)); + ImGui::SetNextWindowSizeRelScreen(0.8f, mBrowserHeight); + windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; + } else { + windowFlags = 0; + } + ImGui::Begin("Content Browser", open, windowFlags); + + ImGui::Splitter(true, kSplitterThickness, &mSplitterLeft, &mSplitterRight, kLeftPaneMinWidth, kRightPaneMinWidth); + + ImGui::BeginChild("LeftPane", ImVec2(mSplitterLeft - kPadding, 0.0f)); + { + if (ImGui::Selectable("Settings", mPane == P_Settings)) { + mPane = P_Settings; + } + if (ImGui::Selectable("Ires", mPane == P_Ires)) { + mPane = P_Ires; + } + if (ImGui::Selectable("Levels", mPane == P_Level)) { + mPane = P_Level; + } + } + ImGui::EndChild(); + + ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding); + ImGui::BeginChild("RightPane"); // Fill remaining space + auto origItt = mInspector->selectedItt; + auto origItPtr = mInspector->selectedItPtr; + switch (mPane) { + case P_Settings: { + ImGui::Checkbox("Docked", &mDocked); + ImGui::SliderFloat("Height", &mBrowserHeight, 0.1f, 1.0f); + } break; + + case P_Ires: { + bool isIttIres = origItt == EditorInspector::ITT_Ires; + + if (ImGui::Button("New")) { + ImGui::OpenPopup("New Ires"); + } + if (ImGui::BeginPopup("New Ires")) { + for (int i = 0; i < (int)IresObject::KD_COUNT; ++i) { + auto kind = static_cast(i); + if (ImGui::MenuItem(Metadata::EnumToString(kind).data())) { + auto ires = IresObject::Create(kind); + auto [DISCARD, success] = IresManager::instance->Add(ires.get()); + if (success) { + (void)ires.release(); + } + } + } + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Refresh list") || + ImGui::IsKeyPressed(ImGuiKey_F5)) + { + // TODO + } + + ImGui::SameLine(); + if (ImGui::Button("Save", !isIttIres)) { + auto ires = static_cast(origItPtr); + IresManager::instance->Save(ires); + } + + ImGui::SameLine(); + if (ImGui::Button("Reload", !isIttIres)) { + auto ires = static_cast(origItPtr); + IresManager::instance->Reload(ires); + } + + ImGui::SameLine(); + if (ImGui::Button("Rename", !isIttIres) || + (isIttIres && ImGui::IsKeyPressed(ImGuiKey_F2, false))) + { + auto ires = static_cast(origItPtr); + mInspector->renaming = true; + mInspector->renamingScratchBuffer = ires->GetName(); + } + + ImGui::SameLine(); + if (ImGui::Button("Delete", !isIttIres) || + (isIttIres && ImGui::IsKeyPressed(ImGuiKey_Delete, false))) + { + ImGui::OpenPopup("Delete Ires"); + } + bool openedDummy = true; + if (ImGui::BeginPopupModal("Delete Ires", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { + if (ImGui::Button("Confirm")) { + auto ires = static_cast(origItPtr); + IresManager::instance->Delete(ires); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + auto& objects = IresManager::instance->GetObjects(); + for (auto it = objects.begin(); it != objects.end(); ++it) { + auto ires = it->second.Get(); + auto& name = ires->GetName(); + + bool selected = origItPtr == ires; + + switch (RenamableSelectable(name.c_str(), selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { + case RSA_Selected: { + mInspector->SelectTarget(EditorInspector::ITT_Ires, ires); + } break; + + case RSA_RenameCommitted: { + ires->SetName(std::move(mInspector->renamingScratchBuffer)); + } break; + + // Do nothing + case RSA_RenameCancelled: + case RSA_None: break; + } + if (!mInspector->renaming) { + if (ImGui::BeginDragDropSource()) { + auto kindName = Metadata::EnumToString(ires->GetKind()); + // Reason: intentionally using pointer as payload + ImGui::SetDragDropPayload(kindName.data(), &ires, sizeof(ires)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text("%s '%s'", kindName.data(), name.c_str()); + ImGui::EndDragDropSource(); + } + } + } + } break; + + case P_Level: { + bool isIttLevel = origItt == EditorInspector::ITT_Level; + + if (ImGui::Button("New")) { + auto uid = Uid::Create(); + auto& ldObj = LevelManager::instance->AddLevel(uid); + mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); + mInspector->renaming = true; + mInspector->renamingScratchBuffer = ldObj.name; + } + + if (ImGui::Button("Save", !isIttLevel)) { + auto ldObj = static_cast(origItPtr); + LevelManager::instance->SaveLevel(ldObj->level->GetUid()); + } + + auto& objects = LevelManager::instance->mObjByUid; + for (auto it = objects.begin(); it != objects.end(); ++it) { + auto& uid = it->first; + auto& ldObj = it->second; + auto* level = ldObj.level.Get(); + bool selected = origItPtr == &ldObj; + const char* displayName = ldObj.name.c_str(); + if (strcmp(displayName, "") == 0) { + displayName = ""; + } + + switch (RenamableSelectable(displayName, selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { + case RSA_Selected: { + mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); + } break; + + case RSA_RenameCommitted: { + ldObj.name = std::move(mInspector->renamingScratchBuffer); + } break; + + // Do nothing + case RSA_RenameCancelled: + case RSA_None: break; + } + if (!mInspector->renaming) { + if (ImGui::BeginDragDropSource()) { + // Reason: intentionally using pointer as payload + ImGui::SetDragDropPayload(BRUSSEL_TAG_Level, &ldObj, sizeof(ldObj)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text(BRUSSEL_Uid_FORMAT_STR, BRUSSEL_Uid_FORMAT_EXPAND(uid)); + ImGui::TextUnformatted(ldObj.name.c_str()); + ImGui::EndDragDropSource(); + } + } + } + } break; + } + ImGui::EndChild(); + + ImGui::End(); +} + +namespace ProjectBrussel_UNITY_ID { +glm::quat CalcQuaternionFromDegreesEulerAngle(glm::vec3 eulerAngleDegrees) { + glm::vec3 eulerAngleRadians; + eulerAngleRadians.x = eulerAngleDegrees.x / 180 * M_PI; + eulerAngleRadians.y = eulerAngleDegrees.y / 180 * M_PI; + eulerAngleRadians.z = eulerAngleDegrees.z / 180 * M_PI; + return glm::quat(eulerAngleRadians); +} + +void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) { + app->PushKeyCaptureCallback([=](int key, int action) { + // Allow the user to cancel by pressing Esc + if (key == GLFW_KEY_ESCAPE) { + return true; + } + + if (action == GLFW_PRESS) { + *writeKey = key; + *writeKeyStatus = writeKeyStatus; + return true; + } + return false; + }); +} + +struct GobjTreeNodeShowInfo { + EditorInstance* in_editor; + GameObject* out_openPopup = nullptr; +}; + +void GobjTreeNode(GobjTreeNodeShowInfo& showInfo, GameObject* object) { + auto& inspector = showInfo.in_editor->GetInspector(); + + auto attachment = object->GetEditorAttachment(); + if (!attachment) { + attachment = EaGameObject::Create(object).release(); + object->SetEditorAttachment(attachment); // NOTE: takes ownership + } + + ImGuiTreeNodeFlags flags = + ImGuiTreeNodeFlags_DefaultOpen | + ImGuiTreeNodeFlags_OpenOnDoubleClick | + ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_SpanAvailWidth | + ImGuiTreeNodeFlags_NoTreePushOnOpen; + if (inspector.selectedItPtr == object) { + flags |= ImGuiTreeNodeFlags_Selected; + } + + ImGui::PushID(reinterpret_cast(object)); + // BEGIN tree node + + bool opened = ImGui::TreeNodeEx(attachment->name.c_str(), flags); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + inspector.SelectTarget(EditorInspector::ITT_GameObject, object); + } + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + showInfo.out_openPopup = object; + } + + if (opened) { + ImGui::Indent(); + for (auto& child : object->GetChildren()) { + GobjTreeNode(showInfo, child); + } + ImGui::Unindent(); + } + + // END tree node + ImGui::PopID(); +}; + +#define GAMEOBJECT_CONSTRUCTOR(ClassName) [](GameWorld* world) -> GameObject* { return new ClassName(world); } +struct CreatableGameObject { + GameObject* (*factory)(GameWorld*); + const char* name; + GameObject::Kind kind; +} creatableGameObjects[] = { + { + .factory = GAMEOBJECT_CONSTRUCTOR(GameObject), + .name = "GameObject", + .kind = GameObject::KD_Generic, + }, + { + .factory = GAMEOBJECT_CONSTRUCTOR(SimpleGeometryObject), + .name = "Simple Geometry", + .kind = GameObject::KD_SimpleGeometry, + }, + { + .factory = GAMEOBJECT_CONSTRUCTOR(BuildingObject), + .name = "Building", + .kind = GameObject::KD_Building, + }, + { + .factory = GAMEOBJECT_CONSTRUCTOR(LevelWrapperObject), + .name = "Level Wrapper", + .kind = GameObject::KD_LevelWrapper, + }, +}; +#undef GAMEOBJECT_CONSTRUCTOR +} // namespace ProjectBrussel_UNITY_ID + +std::unique_ptr IEditor::CreateInstance(App* app) { + return std::make_unique(app); +} + +EditorInstance::EditorInstance(App* app) + : mApp{ app } + , mEdContentBrowser(&mEdInspector) { + mEditorCamera.name = "Editor Camera"s; + mEditorCamera.SetEyePos(glm::vec3(0, 0, 1)); + mEditorCamera.SetTargetDirection(glm::vec3(0, 0, -1)); + app->BindActiveCamera(&mEditorCamera); +} + +EditorInstance::~EditorInstance() { +} + +void EditorInstance::OnGameStateChanged(bool running) { + if (running) { + mApp->UnbindActiveCamera(); + } else { + mApp->BindActiveCamera(&mEditorCamera); + } +} + +void EditorInstance::Show() { + using namespace ProjectBrussel_UNITY_ID; + using namespace Tags; + + auto world = mApp->GetWorld(); + auto& io = ImGui::GetIO(); + + ImGui::BeginMainMenuBar(); + if (ImGui::BeginMenu("View")) { + ImGui::MenuItem("ImGui Demo", nullptr, &mWindowVisible_ImGuiDemo); + ImGui::MenuItem("Command Palette", "Ctrl+Shift+P", &mWindowVisible_CommandPalette); + ImGui::MenuItem("Inspector", nullptr, &mWindowVisible_Inspector); + ImGui::MenuItem("Content Browser", "Ctrl+Space", &mWindowVisible_ContentBrowser); + ImGui::MenuItem("World Structure", nullptr, &mWindowVisible_WorldStructure); + ImGui::MenuItem("World Properties", nullptr, &mWindowVisible_WorldProperties); + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + + if (mWindowVisible_ImGuiDemo) { + ImGui::ShowDemoWindow(&mWindowVisible_ImGuiDemo); + } + + if (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(GLFW_KEY_P, false)) { + mWindowVisible_CommandPalette = !mWindowVisible_CommandPalette; + } + if (mWindowVisible_CommandPalette) { + mEdCommandPalette.Show(&mWindowVisible_CommandPalette); + } + + if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) { + mWindowVisible_ContentBrowser = !mWindowVisible_ContentBrowser; + } + if (mWindowVisible_ContentBrowser) { + mEdContentBrowser.Show(&mWindowVisible_ContentBrowser); + } + + auto& camera = *mApp->GetActiveCamera(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::SetDrawlist(ImGui::GetBackgroundDrawList()); + + if (IsCurrentCameraEditor() && mEcm == ECM_Side3D) { + float viewManipulateRight = io.DisplaySize.x; + float viewManipulateTop = 0; + + // TODO get rid of this massive hack: how to manage const better for intuitively read-only, but write doesn't-care data? + auto& lastFrameInfo = const_cast(mApp->GetWorldRenderer()->GetLastFrameInfo()); + auto& view = lastFrameInfo.matrixView; + auto& proj = lastFrameInfo.matrixProj; + +#if 0 + ImGuizmo::ViewManipulate( + glm::value_ptr(view), + 200.0f, // TODO + ImVec2(viewManipulateRight - 128, viewManipulateTop), + ImVec2(128, 128), + 0x10101010); + // Extract eye and target position from view matrix + // - View matrix transforms world space to view space + // - Inverse view matrix should transform view space into world space + // - In view space, camera's pos is (0,0,0) and the look/forward vector should be (0,0,-1) + auto invView = glm::inverse(view); + camera.eye = invView * glm::vec4(0, 0, 0, 1); + camera.target = camera.eye + glm::vec3(invView * glm::vec4(0, 0, -1, 1)); +#endif + + // TODO draw this as a part of the world so it doesn't block objects +#if 0 + glm::mat4 identity(1.00f); + ImGuizmo::DrawGrid( + glm::value_ptr(view), + glm::value_ptr(proj), + glm::value_ptr(identity), + 100.f); +#endif + + { // Camera controls + auto cameraPos = camera.eye; + auto cameraForward = glm::normalize(camera.target - camera.eye); + // Always move on the horzontal flat plane + cameraForward.y = 0.0f; + + if (mMoveCamKeyboard) { + if (ImGui::IsKeyDown(ImGuiKey_W)) { + cameraPos += mMoveCamSlideSpeed * cameraForward; + } + if (ImGui::IsKeyDown(ImGuiKey_S)) { + auto cameraBack = glm::normalize(-cameraForward); + cameraPos += mMoveCamSlideSpeed * cameraBack; + } + if (ImGui::IsKeyDown(ImGuiKey_A)) { + auto cameraRight = glm::normalize(glm::cross(cameraForward, glm::vec3(0, 1, 0))); + auto cameraLeft = -cameraRight; + cameraPos += mMoveCamSlideSpeed * cameraLeft; + } + if (ImGui::IsKeyDown(ImGuiKey_D)) { + auto cameraRight = glm::normalize(glm::cross(cameraForward, glm::vec3(0, 1, 0))); + cameraPos += mMoveCamSlideSpeed * cameraRight; + } + if (ImGui::IsKeyDown(ImGuiKey_Space)) { + cameraPos.y += mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { + cameraPos.y -= mMoveCamSlideSpeed; + } + } + + if (mMoveCamScrollWheel) { + cameraPos += cameraForward * io.MouseWheel * mMoveCamScrollSpeed; + } + + camera.SetEyePos(cameraPos); + } + } else { + { // Camera controls + auto cameraPos = camera.eye; + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !io.WantCaptureMouse && !mDragCam_Happening) { + mDragCam_CamInitial = camera.eye; + mDragCam_CursorInitial = ImGui::GetMousePos(); + mDragCam_Happening = true; + } + if (mDragCam_Happening) { + auto newPos = ImGui::GetMousePos(); + // NOTE: we are emulating as if the mouse is dragging the "canvas", through moving the camera in the opposite direction of the natural position delta + float deltaX = mDragCam_CursorInitial.x - newPos.x; + cameraPos.x = mDragCam_CamInitial.x + deltaX / camera.pixelsPerMeter; + float deltaY = -(mDragCam_CursorInitial.y - newPos.y); // Invert Y delta because ImGui uses top-left origin (mouse moving down translates to positive value, but in our coordinate system down is negative) + cameraPos.y = mDragCam_CamInitial.y + deltaY / camera.pixelsPerMeter; + } + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { + mDragCam_Happening = false; + } + + if (mMoveCamKeyboard) { + if (ImGui::IsKeyDown(ImGuiKey_W)) { + cameraPos.y += mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_S)) { + cameraPos.y -= mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_A)) { + cameraPos.x -= mMoveCamSlideSpeed; + } + if (ImGui::IsKeyDown(ImGuiKey_D)) { + cameraPos.x += mMoveCamSlideSpeed; + } + } + + if (mMoveCamScrollWheel) { + cameraPos.z = std::clamp(cameraPos.z + io.MouseWheel, 0.1f, 100.0f); + } + + camera.SetEyePos(cameraPos); + } + } + + if (mWindowVisible_Inspector) { + ImGui::Begin("Inspector"); + switch (mEdInspector.selectedItt) { + case EditorInspector::ITT_GameObject: { + auto object = static_cast(mEdInspector.selectedItPtr); + ShowInspector(object); + } break; + + case EditorInspector::ITT_Ires: { + auto ires = static_cast(mEdInspector.selectedItPtr); + ShowInspector(ires); + } break; + + case EditorInspector::ITT_Level: { + auto ldObj = static_cast(mEdInspector.selectedItPtr); + ShowInspector(ldObj); + } break; + + case EditorInspector::ITT_None: break; + } + ImGui::End(); + } + + if (mWindowVisible_WorldProperties) { + ImGui::Begin("World properties"); + ShowWorldProperties(); + ImGui::End(); + } + + if (mWindowVisible_WorldStructure) { + ImGui::Begin("World structure"); + GobjTreeNodeShowInfo showInfo{ + .in_editor = this, + }; + GobjTreeNode(showInfo, &world->GetRoot()); + + if (showInfo.out_openPopup) { + mPopupCurrent_GameObject = showInfo.out_openPopup; + + ImGui::OpenPopup("GameObject Popup"); + ImGui::SetNextWindowPos(ImGui::GetMousePos()); + } + if (ImGui::BeginPopup("GameObject Popup")) { + // Target no longer selected during popup open + if (!mPopupCurrent_GameObject) { + ImGui::CloseCurrentPopup(); + } + + if (ImGui::BeginMenu("Add child")) { + for (size_t i = 0; i < std::size(creatableGameObjects); ++i) { + auto& info = creatableGameObjects[i]; + if (ImGui::MenuItem(info.name)) { + auto object = info.factory(world); + mPopupCurrent_GameObject->AddChild(object); + } + } + ImGui::EndMenu(); + } + ImGui::Separator(); + if (ImGui::MenuItem("Remove")) { + ImGui::DialogConfirmation("Are you sure you want to delete this GameObject?", [object = mPopupCurrent_GameObject](bool yes) { + object->RemoveSelfFromParent(); + delete object; + }); + } + ImGui::EndPopup(); + } + ImGui::End(); + } + + ShowSpriteViewer(); + + ImGui::ShowDialogs(); + ImGui::ShowNotifications(); +} + +void EditorInstance::ShowWorldProperties() { + if (mApp->IsGameRunning()) { + if (ImGui::Button("Pause")) { + mApp->SetGameRunning(false); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("The game is currently running. Click to pause."); + } + } else { + if (ImGui::Button("Play")) { + mApp->SetGameRunning(true); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("The game is currently paused. Click to run."); + } + } + + auto& camera = *mApp->GetActiveCamera(); + + // vvv Camera settings (per instance) + ImGui::TextUnformatted("Active camera:"); + ImGui::Indent(); + + ImGui::TextUnformatted(camera.name.c_str()); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Object at <%p>", &camera); + } + + ImGui::InputFloat3("Eye", glm::value_ptr(camera.eye)); + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { + ImGui::OpenPopup("##CTXMENU"); + } + ImGui::InputFloat3("Target", glm::value_ptr(camera.target)); + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { + ImGui::OpenPopup("##CTXMENU"); + } + if (ImGui::BeginPopup("##CTXMENU", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) { + if (ImGui::MenuItem("Reset to origin")) { + camera.SetEyePos(glm::vec3(1.0f, 1.0f, 1.0f)); + } + if (ImGui::MenuItem("Reset completely")) { + camera.eye = glm::vec3(0, 0, 1); + camera.target = glm::vec3(0, 0, 0); + } + ImGui::EndPopup(); + } + + if (IsCurrentCameraEditor()) { + const char* preview; + switch (mEcm) { + case ECM_2D: preview = "2D view"; break; + case ECM_Side3D: preview = "Side 3D view"; break; + } + if (ImGui::BeginCombo("Mode", preview)) { + if (ImGui::Selectable("2D view", mEcm == ECM_2D)) { + if (mEcm != ECM_2D) { + mEcm = ECM_2D; + + // TODO project eye to world plane + + camera.SetHasPerspective(false); + } + } + if (ImGui::Selectable("Side 3D view", mEcm == ECM_Side3D, ImGuiSelectableFlags_None)) { + if (mEcm != ECM_Side3D) { + mEcm = ECM_Side3D; + + auto origEye = camera.eye; + auto origTarget = camera.target; + + // New setup: focus on the point of world plane that we were originally "hovering above" + camera.target = origEye; + camera.target.z = 0.0f; + + // New setup: move the eye back at an angle + camera.eye = camera.target; + camera.eye.x += 4.0f * std::cos(60.0f); + camera.eye.y += 0.0f; + camera.eye.z += 4.0f * std::sin(60.0f); + + camera.SetHasPerspective(true); + } + } + ImGui::EndCombo(); + } + } + + ImGui::Checkbox("Perspective", &camera.perspective); + if (camera.perspective) { + float fovDegress = camera.fov / M_PI * 180.0f; + if (ImGui::SliderFloat("FOV", &fovDegress, 1.0f, 180.0f)) { + camera.fov = fovDegress / 180.0f * M_PI; + } + } else { + if (ImGui::InputFloat("Pixels per meter", &camera.pixelsPerMeter)) { + camera.pixelsPerMeter = std::max(camera.pixelsPerMeter, 0.0f); + } + } + + ImGui::Unindent(); + // ^^^ Camera settings (per instance) + // vvv Camera control settings + ImGui::TextUnformatted("Camera controls:"); + ImGui::Indent(); + + ImGui::Checkbox("Move camera with WASD", &mMoveCamKeyboard); + ImGui::SliderFloat("Slide speed", &mMoveCamSlideSpeed, 0.1f, 10.0f); + + ImGui::Checkbox("Move camera with scoll wheel", &mMoveCamScrollWheel); + ImGui::SliderFloat("Scroll speed", &mMoveCamScrollSpeed, 0.01, 10.0f); + + ImGui::Unindent(); + // ^^^ Camera control settings +} + +// TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism + +void EditorInstance::ShowInspector(IresObject* ires) { + ires->ShowEditor(*this); +} + +void EditorInstance::ShowInspector(LevelManager::LoadableObject* ldObj) { + using namespace Tags; + using namespace ProjectBrussel_UNITY_ID; + + ImGui::InputText("Name", &ldObj->name); + ImGui::InputTextMultiline("Desciption", &ldObj->description); + + if (ImGui::CollapsingHeader("Instanciation Entries")) { + ldObj->level->ShowInstanciationEntries(*this); + } +} + +void EditorInstance::ShowInspector(GameObject* object) { + using namespace Tags; + using namespace ProjectBrussel_UNITY_ID; + + auto& io = ImGui::GetIO(); + + auto objectEa = static_cast(object->GetEditorAttachment()); + + auto ShowInspector = [&](/*array[6]*/ float* bounds = nullptr) { + glm::mat4 identityMatrix(1.00f); + + if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_T)) + mGuizmo.currOperation = ImGuizmo::TRANSLATE; + if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_R)) + mGuizmo.currOperation = ImGuizmo::ROTATE; + if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_S)) + mGuizmo.currOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mGuizmo.currOperation == ImGuizmo::TRANSLATE)) + mGuizmo.currOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mGuizmo.currOperation == ImGuizmo::ROTATE)) + mGuizmo.currOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mGuizmo.currOperation == ImGuizmo::SCALE)) + mGuizmo.currOperation = ImGuizmo::SCALE; + ImGui::SameLine(); + if (ImGui::RadioButton("Universal", mGuizmo.currOperation == ImGuizmo::UNIVERSAL)) + mGuizmo.currOperation = ImGuizmo::UNIVERSAL; + + if (mGuizmo.currOperation != ImGuizmo::SCALE) { + if (ImGui::RadioButton("Local", mGuizmo.currMode == ImGuizmo::LOCAL)) + mGuizmo.currMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mGuizmo.currMode == ImGuizmo::WORLD)) + mGuizmo.currMode = ImGuizmo::WORLD; + } + + ImGui::Checkbox("##UseSnap", &mGuizmo.useSnap); + ImGui::SameLine(); + switch (mGuizmo.currOperation) { + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Pos Snap", &mGuizmo.snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &mGuizmo.snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &mGuizmo.snap[0]); + break; + default: break; + } + + if (bounds) { + ImGui::Checkbox("Bound Sizing", &mGuizmo.boundSizing); + if (mGuizmo.boundSizing) { + ImGui::PushID(3); + ImGui::Checkbox("##BoundSizing", &mGuizmo.boundSizingSnap); + ImGui::SameLine(); + ImGui::InputFloat3("Bound Snap", mGuizmo.boundsSnap); + ImGui::PopID(); + } + } + + ImGui::Separator(); + + auto objectPos = object->GetPos(); + if (ImGui::InputFloat3("Translation", &objectPos.x)) { + object->SetPos(objectPos); + } + + auto objectRot = object->GetRotation(); + if (ImGui::InputFloat3("Rotation", &objectEa->eulerAnglesRotation.x)) { + objectRot = CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation); + object->SetRotation(objectRot); + } + + auto objectScale = object->GetScale(); + if (ImGui::InputFloat3("Scale", &objectScale.x)) { + object->SetScale(objectScale); + } + }; + + auto ShowGuizmo = [&](/*array[6]*/ float* bounds = nullptr) { + glm::mat4 identityMatrix(1.00f); + + auto& lastFrameInfo = mApp->GetWorldRenderer()->GetLastFrameInfo(); + auto& cameraViewMat = lastFrameInfo.matrixView; + const float* cameraView = &cameraViewMat[0][0]; + auto& cameraProjMat = lastFrameInfo.matrixProj; + const float* cameraProj = &cameraProjMat[0][0]; + + auto objectPos = object->GetPos(); + auto objectScale = object->GetScale(); + + float matrix[16]; + ImGuizmo::RecomposeMatrixFromComponents(&objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x, matrix); + + bool edited = ImGuizmo::Manipulate( + cameraView, + cameraProj, + mGuizmo.currOperation, + mGuizmo.currMode, + matrix, + nullptr, + mGuizmo.useSnap ? mGuizmo.snap : nullptr, + mGuizmo.boundSizing ? bounds : nullptr, + (bounds && mGuizmo.boundSizingSnap) ? mGuizmo.boundsSnap : nullptr); + + if (edited) { + ImGuizmo::DecomposeMatrixToComponents(matrix, &objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x); + object->SetPos(objectPos); + object->SetRotation(CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation)); + object->SetScale(objectScale); + } + }; + + auto type = object->GetKind(); + switch (type) { + case GameObject::KD_Player: { + ShowInspector(); + ShowGuizmo(); + ImGui::Separator(); + + auto player = static_cast(object); + auto ea = static_cast(objectEa); + auto& kb = player->keybinds; + + ImGui::Text("Player #%d", player->GetId()); + + ImGui::TextUnformatted("Spritesheet: "); + ImGui::SameLine(); + IresObject::ShowReferenceSafe(*this, ea->confSprite.Get()); + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Spritesheet).data())) { + auto spritesheet = *static_cast(payload->Data); + ea->confSprite.Attach(spritesheet); + auto def = spritesheet->GetInstance(); + player->sprite.SetDefinition(def); + player->renderObject.autofill_TextureAtlas.Attach(def->GetAtlas()); + } + ImGui::EndDragDropTarget(); + } + + ImGui::TextUnformatted("Material: "); + ImGui::SameLine(); + IresObject::ShowReferenceSafe(*this, ea->confMaterial.Get()); + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Material).data())) { + auto material = *static_cast(payload->Data); + ea->confMaterial.Attach(material); + player->SetMaterial(material->GetInstance()); + } + ImGui::EndDragDropTarget(); + } + + if (ImGui::Button("Load config")) { + bool success = player->LoadFromFile(); + if (success) { + ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully loaded player config")); + } + } + ImGui::SameLine(); + if (ImGui::Button("Save config")) { + bool success = player->SaveToFile(); + if (success) { + ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully saved player config")); + } + } + + ImGui::Text("Move left (%s)", ImGui::GetKeyNameGlfw(kb.keyLeft)); + ImGui::SameLine(); + if (ImGui::Button("Change##Move left")) { + PushKeyCodeRecorder(mApp, &kb.keyLeft, &kb.pressedLeft); + } + + ImGui::Text("Move right (%s)", ImGui::GetKeyNameGlfw(kb.keyRight)); + ImGui::SameLine(); + if (ImGui::Button("Change##Move right")) { + PushKeyCodeRecorder(mApp, &kb.keyRight, &kb.pressedRight); + } + + ImGui::Text("Jump (%s)", ImGui::GetKeyNameGlfw(kb.keyJump)); + ImGui::SameLine(); + if (ImGui::Button("Change##Jump")) { + PushKeyCodeRecorder(mApp, &kb.keyJump, &kb.pressedJump); + } + + ImGui::Text("Attack (%s)", ImGui::GetKeyNameGlfw(kb.keyAttack)); + ImGui::SameLine(); + if (ImGui::Button("Change##Attack")) { + PushKeyCodeRecorder(mApp, &kb.keyAttack, &kb.pressedAttack); + } + } break; + + case GameObject::KD_SimpleGeometry: { + auto sg = static_cast(object); + + float bounds[6] = { + /*x1*/ -sg->GetSize().x / 2.0f, + /*y1*/ -sg->GetSize().y / 2.0f, + /*z1*/ -sg->GetSize().z / 2.0f, + /*x2*/ +sg->GetSize().x / 2.0f, + /*y2*/ +sg->GetSize().y / 2.0f, + /*z2*/ +sg->GetSize().z / 2.0f, + }; + + ShowInspector(bounds); + ShowGuizmo(bounds); + ImGui::Separator(); + + sg->SetSize(glm::vec3(bounds[3] - bounds[0], bounds[4] - bounds[1], bounds[5] - bounds[2])); + + auto size = sg->GetSize(); + if (ImGui::InputFloat3("Size", &size.x)) { + sg->SetSize(size); + } + + auto xFaceColor = sg->GetXFaceColor(); + if (ImGui::ColorEdit4("X color", &xFaceColor)) { + sg->SetXFaceColor(xFaceColor); + } + auto yFaceColor = sg->GetYFaceColor(); + if (ImGui::ColorEdit4("Y color", &yFaceColor)) { + sg->SetYFaceColor(yFaceColor); + } + auto zFaceColor = sg->GetZFaceColor(); + if (ImGui::ColorEdit4("Z color", &zFaceColor)) { + sg->SetZFaceColor(zFaceColor); + } + + if (ImGui::Button("Sync from X color")) { + sg->SetYFaceColor(xFaceColor); + sg->SetZFaceColor(xFaceColor); + } + ImGui::SameLine(); + if (ImGui::Button("Reset")) { + sg->SetXFaceColor(kXAxisColor); + sg->SetYFaceColor(kYAxisColor); + sg->SetZFaceColor(kZAxisColor); + } + } break; + + case GameObject::KD_Building: { + ShowInspector(); + ShowGuizmo(); + ImGui::Separator(); + + auto b = static_cast(object); + // TODO + } break; + + case GameObject::KD_LevelWrapper: { + ShowInspector(); + ShowGuizmo(); + ImGui::Separator(); + + auto lwo = static_cast(object); + // TODO + } break; + + default: { + ShowInspector(); + ShowGuizmo(); + } break; + } +} + +void EditorInstance::OpenSpriteViewer(SpriteDefinition* sprite) { + mSpriteView_Instance.Attach(sprite); + mSpriteView_Frame = 0; + mSpriteView_OpenNextFrame = true; +} + +void EditorInstance::ShowSpriteViewer() { + if (mSpriteView_Instance == nullptr) return; + if (!mSpriteView_Instance->IsValid()) return; + + if (mSpriteView_OpenNextFrame) { + mSpriteView_OpenNextFrame = false; + ImGui::OpenPopup("Sprite Viewer"); + } + + bool windowOpen = true; + if (ImGui::BeginPopupModal("Sprite Viewer", &windowOpen)) { + auto atlas = mSpriteView_Instance->GetAtlas(); + auto atlasSize = atlas->GetInfo().size; + auto& frames = mSpriteView_Instance->GetFrames(); + + int frameCount = mSpriteView_Instance->GetFrames().size(); + if (ImGui::Button("<")) { + --mSpriteView_Frame; + } + ImGui::SameLine(); + ImGui::Text("%d/%d", mSpriteView_Frame + 1, frameCount); + ImGui::SameLine(); + if (ImGui::Button(">")) { + ++mSpriteView_Frame; + } + // Scrolling down (negative value) should advance, so invert the sign + mSpriteView_Frame += -std::round(ImGui::GetIO().MouseWheel); + // Clamp mSpriteView_Frame to range [0, frameCount) + if (mSpriteView_Frame < 0) { + mSpriteView_Frame = frameCount - 1; + } else if (mSpriteView_Frame >= frameCount) { + mSpriteView_Frame = 0; + } + + auto& currFrame = frames[mSpriteView_Frame]; + auto boundingBox = mSpriteView_Instance->GetBoundingBox(); + ImGui::Text("Frame size: (%d, %d)", boundingBox.x, boundingBox.y); + ImGui::Text("Frame location: (%f, %f) to (%f, %f)", currFrame.u0, currFrame.v0, currFrame.u1, currFrame.v1); + ImGui::Image( + (ImTextureID)(uintptr_t)atlas->GetHandle(), + Utils::FitImage(atlasSize), + ImVec2(currFrame.u0, currFrame.v0), + ImVec2(currFrame.u1, currFrame.v1)); + + ImGui::EndPopup(); + } +} diff --git a/source/Game/EditorCorePrivate.hpp b/source/Game/EditorCorePrivate.hpp new file mode 100644 index 0000000..4fbfb72 --- /dev/null +++ b/source/Game/EditorCorePrivate.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include "EditorCore.hpp" + +#include "App.hpp" +#include "EditorAttachment.hpp" +#include "EditorCommandPalette.hpp" +#include "EditorUtils.hpp" +#include "GameObject.hpp" +#include "Ires.hpp" +#include "Level.hpp" +#include "RcPtr.hpp" +#include "Sprite.hpp" +#include "World.hpp" + +#include +#include + +// 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; + + // + 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 mSpriteView_Instance; + EditorCommandPalette mEdCommandPalette; + EditorInspector mEdInspector; + EditorContentBrowser mEdContentBrowser; + 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 mDragCam_Happening = false; + bool mMoveCamKeyboard = false; + bool mMoveCamScrollWheel = 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/Game/EditorGuizmo.cpp b/source/Game/EditorGuizmo.cpp new file mode 100644 index 0000000..3e4f890 --- /dev/null +++ b/source/Game/EditorGuizmo.cpp @@ -0,0 +1,2897 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.84 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "EditorGuizmo.hpp" +#include "imgui_internal.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +# include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +# define _malloca(x) alloca(x) +# define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace IMGUIZMO_NAMESPACE { +static const float ZPI = 3.14159265358979323846f; +static const float RAD2DEG = (180.f / ZPI); +static const float DEG2RAD = (ZPI / 180.f); +const float screenRotateSize = 0.06f; +// scale a bit so translate axis do not touch when in universal +const float rotationDisplayFactor = 1.2f; + +static OPERATION operator&(OPERATION lhs, OPERATION rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +static bool operator!=(OPERATION lhs, int rhs) { + return static_cast(lhs) != rhs; +} + +static bool Intersects(OPERATION lhs, OPERATION rhs) { + return (lhs & rhs) != 0; +} + +// True if lhs contains rhs +static bool Contains(OPERATION lhs, OPERATION rhs) { + return (lhs & rhs) == rhs; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// utility and math + +void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; +} + +void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; +} + +void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); +} + +void Cross(const float* a, const float* b, float* r) { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; +} + +float Dot(const float* a, const float* b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +void Normalize(const float* a, float* r) { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; +} + +void LookAt(const float* eye, const float* at, const float* up, float* m16) { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; +} + +template +T Clamp(T x, T y, T z) { + return ((x < y) ? y : ((x > z) ? z : x)); +} +template +T max(T x, T y) { + return (x > y) ? x : y; +} +template +T min(T x, T y) { + return (x < y) ? x : y; +} +template +bool IsWithin(T x, T y, T z) { + return (x >= y) && (x <= z); +} + +struct matrix_t; +struct vec_t { +public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { + x = _x; + y = _y; + z = _z; + w = _w; + } + + vec_t& operator-=(const vec_t& v) { + x -= v.x; + y -= v.y; + z -= v.z; + w -= v.w; + return *this; + } + vec_t& operator+=(const vec_t& v) { + x += v.x; + y += v.y; + z += v.z; + w += v.w; + return *this; + } + vec_t& operator*=(const vec_t& v) { + x *= v.x; + y *= v.y; + z *= v.z; + w *= v.w; + return *this; + } + vec_t& operator*=(float v) { + x *= v; + y *= v; + z *= v; + w *= v; + return *this; + } + + vec_t operator*(float f) const; + vec_t operator-() const; + vec_t operator-(const vec_t& v) const; + vec_t operator+(const vec_t& v) const; + vec_t operator*(const vec_t& v) const; + + const vec_t& operator+() const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { + (*this) *= (1.f / (Length() > FLT_EPSILON ? Length() : FLT_EPSILON)); + return (*this); + } + vec_t Normalize(const vec_t& v) { + this->Set(v.x, v.y, v.z, v.w); + this->Normalize(); + return (*this); + } + vec_t Abs() const; + + void Cross(const vec_t& v) { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { + (*this) = v; + this->TransformVector(matrix); + } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { + (*this) = v; + this->TransformPoint(matrix); + } + + float& operator[](size_t index) { return ((float*)&x)[index]; } + const float& operator[](size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)); } +}; + +vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { + vec_t res; + res.x = _x; + res.y = _y; + res.z = _z; + res.w = _w; + return res; +} +vec_t makeVect(ImVec2 v) { + vec_t res; + res.x = v.x; + res.y = v.y; + res.z = 0.f; + res.w = 0.f; + return res; +} +vec_t vec_t::operator*(float f) const { + return makeVect(x * f, y * f, z * f, w * f); +} +vec_t vec_t::operator-() const { + return makeVect(-x, -y, -z, -w); +} +vec_t vec_t::operator-(const vec_t& v) const { + return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); +} +vec_t vec_t::operator+(const vec_t& v) const { + return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); +} +vec_t vec_t::operator*(const vec_t& v) const { + return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); +} +vec_t vec_t::Abs() const { + return makeVect(fabsf(x), fabsf(y), fabsf(z)); +} + +vec_t Normalized(const vec_t& v) { + vec_t res; + res = v; + res.Normalize(); + return res; +} +vec_t Cross(const vec_t& v1, const vec_t& v2) { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; +} + +float Dot(const vec_t& v1, const vec_t& v2) { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); +} + +vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; +} + +struct matrix_t { +public: + union { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + operator float*() { return m16; } + operator const float*() const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator*=(const matrix_t& mat) { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator*(const matrix_t& mat) const { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } +}; + +void vec_t::Transform(const matrix_t& matrix) { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; +} + +void vec_t::Transform(const vec_t& s, const matrix_t& matrix) { + *this = s; + Transform(matrix); +} + +void vec_t::TransformPoint(const matrix_t& matrix) { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; +} + +void vec_t::TransformVector(const matrix_t& matrix) { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; +} + +float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; +} + +void matrix_t::RotationAxis(const vec_t& axis, float angle) { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// + +enum MOVETYPE { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ +}; + +static bool IsTranslateType(int type) { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; +} + +static bool IsRotateType(int type) { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; +} + +static bool IsScaleType(int type) { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; +} + +// Matches MT_MOVE_AB order +static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + +struct Context { + Context() + : mbUsing(false), mbEnable(true), mbUsingBounds(false) { + } + + ImDrawList* mDrawList; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelLocal; // orthonormalized model + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + bool mbMouseOver; + bool mReversed; // reversed projection matrix + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + // vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + // bounds stretching + // NOTE: these variable only lives during the duration of a drag + /// Position in world space, of the knob on the opposite side of the knob being dragged. + /// This is the point that needs to space regardless of where anchor is placed. + vec_t mBoundsPivot; + /// Position in world space, of the knob begin dragged. + /// This is the point that's being moved. + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + /// Position in local space, of the knob on the opposite side of the knob being dragged + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + /// The axes that are being modified by the current operation. May contain 1 or 2 elements. + /// Unused elements are filled with -1 during the operation. + int mBoundsAxis[2]; + /// The index of the corner that pivot data is fetched from (opposite side from anchor). + int mBoundsPivotCornerIndex; + bool mbUsingBounds; + bool mbIsUsingBigAnchor; + /// Model matrix passed into ImGuizmo::Manipulate() + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + + int mActualID = -1; + int mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; +}; + +static Context gContext; + +static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; +static const ImU32 directionColor[3] = { IM_COL32(0xAA, 0, 0, 0xFF), IM_COL32(0, 0xAA, 0, 0xFF), IM_COL32(0, 0, 0xAA, 0XFF) }; + +// Alpha: 100%: FF, 87%: DE, 70%: B3, 54%: 8A, 50%: 80, 38%: 61, 12%: 1F +static const ImU32 planeColor[3] = { IM_COL32(0xAA, 0, 0, 0x61), IM_COL32(0, 0xAA, 0, 0x61), IM_COL32(0, 0, 0xAA, 0x61) }; +static const ImU32 selectionColor = IM_COL32(0xFF, 0x80, 0x10, 0x8A); +static const ImU32 inactiveColor = IM_COL32(0x99, 0x99, 0x99, 0x99); +static const ImU32 translationLineColor = IM_COL32(0xAA, 0xAA, 0xAA, 0xAA); +static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", "X : %5.3f Y : %5.3f Z : %5.3f" }; +static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; +static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; +static const int translationInfoIndex[] = { 0, 0, 0, 1, 0, 0, 2, 0, 0, 1, 2, 0, 0, 2, 0, 0, 1, 0, 0, 1, 2 }; +static const float quadMin = 0.5f; +static const float quadMax = 0.8f; +static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; +static const int halfCircleSegmentCount = 64; +static const float snapTension = 0.5f; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); +static int GetRotateType(OPERATION op); +static int GetScaleType(OPERATION op); + +static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); +} + +static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); +} + +static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) { + vec_t startOfSegment = start; + const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; + startOfSegment.TransformPoint(mvp); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(mvp); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; +} + +static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; +} + +inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; +} + +static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) { + const float numer = plan.Dot3(rOrigin) - plan.w; + const float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); +} + +static float DistanceToPlane(const vec_t& point, const vec_t& plan) { + return plan.Dot3(point) + plan.w; +} + +static bool IsInContextRect(ImVec2 p) { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); +} + +static bool IsHoveringWindow() { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName); + if (g.HoveredWindow == window) // Mouse hovering drawlist window + return true; + if (g.HoveredWindow != NULL) // Any other window is hovered + return false; + if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) + return true; + return false; +} + +void SetRect(float x, float y, float width, float height) { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; +} + +void SetOrthographic(bool isOrthographic) { + gContext.mIsOrthographic = isOrthographic; +} + +void SetDrawlist(ImDrawList* drawlist) { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); +} + +void SetImGuiContext(ImGuiContext* ctx) { + ImGui::SetCurrentContext(ctx); +} + +void BeginFrame() { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); +} + +bool IsUsing() { + return gContext.mbUsing || gContext.mbUsingBounds; +} + +bool IsOver() { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); +} + +bool IsOver(OPERATION op) { + if (IsUsing()) + { + return true; + } + if (Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if (Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if (Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; +} + +void Enable(bool enable) { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } +} + +static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + gContext.mbMouseOver = IsHoveringWindow(); + + gContext.mModelLocal = *(matrix_t*)matrix; + gContext.mModelLocal.OrthoNormalize(); + + if (mode == LOCAL) + { + gContext.mModel = gContext.mModelLocal; + } else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z / nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); +} + +static void ComputeColors(ImU32* colors, int type, OPERATION operation) { + if (gContext.mbEnable) + { + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : directionColor[i]; + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : planeColor[i]; + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : directionColor[i]; + } + break; + case SCALEU: + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : directionColor[i]; + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } else + { + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } +} + +static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirAxis *= gContext.mAxisFactor[axisIndex]; + dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; + } else + { + // new method + float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis, localCoordinates); + float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis, localCoordinates); + + float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX, localCoordinates); + float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX, localCoordinates); + + float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY, localCoordinates); + float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY, localCoordinates); + + // For readability + bool& allowFlip = gContext.mAllowAxisFlip; + float mulAxis = (allowFlip && lenDir < lenDirMinus && fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX && fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY && fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + belowPlaneLimit = (paraSurf > 0.0025f); + belowAxisLimit = (axisLengthInClipSpace > 0.02f); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxis; + gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; + gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } +} + +static void ComputeSnap(float* value, float snap) { + if (snap <= FLT_EPSILON) + { + return; + } + + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + { + *value -= modulo; + } else if (moduloRatio > (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } +} +static void ComputeSnap(vec_t& value, const float* snap) { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } +} + +static float ComputeAngleOnPlan() { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; +} + +static void DrawRotationGizmo(OPERATION op, int type) { + if (!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + cameraToModelNormalized = viewInverse.v.dir; + } else + { + cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + } + + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + for (int axis = 0; axis < 3; axis++) + { + if (!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); + const int circleMul = (hasRSC && !usingAxis) ? 1 : 2; + + ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)halfCircleSegmentCount); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + if (!gContext.mbUsing || usingAxis) + { + drawList->AddPolyline(circlePos, circleMul * halfCircleSegmentCount + 1, colors[3 - axis], false, 2); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + } + if (hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN)) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, 3.f); + } + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0x80)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0xFF), true, 2); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } +} + +static void DrawHatchedAxis(const vec_t& axis) { + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, IM_COL32(0, 0, 0, 0x80), 6.f); + } +} + +static void DrawScaleGizmo(OPERATION op, int type) { + ImDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + drawList->AddCircleFilled(worldDirSSpace, 6.f, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + // ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + // vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } +} + +static void DrawScaleUniveralGizmo(OPERATION op, int type) { + ImDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALEU)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALEU); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + // ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + // ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); + +#if 0 + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + /* + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + */ +#endif + drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); + } + } + } + + // draw screen cirle + drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, 3.f); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + // ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + // vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } +} + +static void DrawTranslationGizmo(OPERATION op, int type) { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if (!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) + { + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= 6.0f; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + } + // draw plane + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) + { + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, directionColor[i], true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) + { + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } +} + +static bool CanActivate() { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; +} + +static bool HandleAndDrawLocalBounds(float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // Corners of the plane (rectangle) containing bestAxis + vec_t corners[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + // ImU32 col[] = { IM_COL32(255, 0, 0, 255), IM_COL32(0, 255, 0, 255), IM_COL32(0, 0, 255, 255) }; + for (int i = 0; i < 4; i++) { + corners[i].w = 0.0f; + corners[i][bestAxis] = 0.0f; + corners[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + corners[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + + // ImVec2 pos = worldToPos(corners[i], boundsMVP); + // drawList->AddCircleFilled(pos, 10.0f, col[axisIndex]); + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(corners[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(corners[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + float stepLength = 1.f / (float)stepCount; + for (int j = 0; j < stepCount; j++) + { + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + // drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (corners[i] + corners[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if (Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if (Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if (Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(corners[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(corners[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = corners[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = corners[oppositeIndex][thirdAxis]; + gContext.mBoundsPivotCornerIndex = oppositeIndex; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + + gContext.mbIsUsingBigAnchor = true; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (corners[(i + 2) % 4] + corners[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis, thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + int localPivotComponentIdx = gContext.mBoundsAxis[0]; + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[localPivotComponentIdx] = corners[oppositeIndex][localPivotComponentIdx]; // bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + gContext.mBoundsPivotCornerIndex = oppositeIndex; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + + gContext.mbIsUsingBigAnchor = false; + } + } + + ImGui::Text("bounds pivot: %.2f, %.2f, %.2f", gContext.mBoundsPivot.x, gContext.mBoundsPivot.y, gContext.mBoundsPivot.z); + ImGui::Text("bounds anchor: %.2f, %.2f, %.2f", gContext.mBoundsAnchor.x, gContext.mBoundsAnchor.y, gContext.mBoundsAnchor.z); + ImGui::Text("bounds plan: %.2f, %.2f, %.2f", gContext.mBoundsPlan.x, gContext.mBoundsPlan.y, gContext.mBoundsPlan.z); + ImGui::Text("bounds local pivot: %.2f, %.2f, %.2f", gContext.mBoundsLocalPivot.x, gContext.mBoundsLocalPivot.y, gContext.mBoundsLocalPivot.z); + if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + ImGui::Text("Delta: %.2f, %.2f, %.2f", deltaVector.x, deltaVector.y, deltaVector.z); + ImGui::Text("Ref: %.2f, %.2f, %.2f", referenceVector.x, referenceVector.y, referenceVector.z); + ImGui::Separator(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int axisIndex1 : gContext.mBoundsAxis) { + if (axisIndex1 == -1) { + continue; + } + + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + // ImGui::Text("Axisdir: %.2f, %.2f, %.2f", axisDir.x, axisDir.y, axisDir.z); + + float refAxisComp = axisDir.Dot(referenceVector); + float deltaAxisComp = axisDir.Dot(deltaVector); + // ImGui::Text("refAxisComp: %.2f", refAxisComp); + + float length = deltaAxisComp; + if (snapValues) { + ComputeSnap(&length, snapValues[axisIndex1]); + } + + // ImGui::Text("axis idx %d", axisIndex1); + // TODO(hnosm): logic that mapps mouse pos to bound seems to account for translation fixup already? + bounds[axisIndex1] = -length / 2; + bounds[axisIndex1 + 3] = +length / 2; + } + + // Update corner positions, translation fixup code needs them + for (int i = 0; i < 4; i++) { + corners[i].w = 0.0f; + corners[i][bestAxis] = 0.0f; + corners[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + corners[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // Translation (object center) fixup - make sure pivot stays in place + // TODO(hnosm): is there a better way to write this that doesn't involve transferring a bunch of extra state from begin drag frame? + vec_t newLocalPivot; + if (gContext.mbIsUsingBigAnchor) { + newLocalPivot.Set(0.0f); + newLocalPivot[secondAxis] = corners[gContext.mBoundsPivotCornerIndex][secondAxis]; + newLocalPivot[thirdAxis] = corners[gContext.mBoundsPivotCornerIndex][thirdAxis]; + } else { + newLocalPivot.Set(0.0f); + int localPivotComponentIdx = gContext.mBoundsAxis[0]; + newLocalPivot[localPivotComponentIdx] = corners[gContext.mBoundsPivotCornerIndex][localPivotComponentIdx]; + } + + vec_t delta = gContext.mBoundsLocalPivot - newLocalPivot; + vec_t oldTranslation = gContext.mBoundsMatrix.component[3]; + matrix->component[3] = oldTranslation + delta; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), + // Size of the bounds in each axis direction + "X: %.2f Y: %.2f Z:%.2f", + (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length(), + (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length(), + (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length()); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + + return gContext.mbUsingBounds; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// + +static int GetScaleType(OPERATION op) { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + dirAxis.TransformVector(gContext.mModelLocal); + dirPlaneX.TransformVector(gContext.mModelLocal); + dirPlaneY.TransformVector(gContext.mModelLocal); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + type = MT_SCALE_X + i; + } + } + + // universal + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) + { + type = MT_SCALE_XYZ; + } + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + // ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + // ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); + + float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos)); + if (distance < 12.f) + { + type = MT_SCALE_X + i; + } + } + } + return type; +} + +static int GetRotateType(OPERATION op) { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) + { + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); + + // gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + type = MT_ROTATE_X + i; + } + } + + return type; +} + +static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) { + if (!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; +} + +static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) { + if (!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + const ImGuiIO& io = ImGui::GetIO(); + const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + const float len = fabsf(signedLength); // near plan + const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + const float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(op, &gizmoHitProportion); + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; +} + +static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) { + if ((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(op); + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModelLocal.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModelLocal; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; +} + +static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) { + if (!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = GetRotateType(op); + + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; + } else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; +} + +void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; +} + +void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); +} + +void SetID(int id) { + gContext.mActualID = id; +} + +void AllowAxisFlip(bool value) { + gContext.mAllowAxisFlip = value; +} + +bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, float* localBounds, const float* boundsSnap) { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated |= HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + if (localBounds && !gContext.mbUsing) + { + manipulated |= HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + DrawScaleUniveralGizmo(operation, type); + } + return manipulated; +} + +void SetGizmoSizeClipSpace(float value) { + gContext.mGizmoSizeClipSpace = value; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +void ComputeFrustumPlanes(vec_t* frustum, const float* clip) { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } +} + +void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { + directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + // ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + cubeFace.color = directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); +} + +void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF) : col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } +} + +void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) { + static bool isDraging = false; + static bool isClicking = false; + static bool isInside = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; + Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f, 0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.5f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + IM_ASSERT(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; + + // draw face with lighter color + if (iPass) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); + + if (!io.MouseDown[0] && !isDraging && isClicking) + { + // apply new view direction + int cx = boxCoordInt / 9; + int cy = (boxCoordInt - cx * 9) / 3; + int cz = boxCoordInt % 3; + interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + isClicking = false; + } + if (io.MouseClicked[0] && !isDraging) + { + isClicking = true; + } + } + } + } + } + } + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + } + isInside = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos); + + // drag view + if (!isDraging && io.MouseClicked[0] && isInside) + { + isDraging = true; + isClicking = false; + } else if (isDraging && !io.MouseDown[0]) + { + isDraging = false; + } + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); +} +}; // namespace IMGUIZMO_NAMESPACE diff --git a/source/Game/EditorGuizmo.hpp b/source/Game/EditorGuizmo.hpp new file mode 100644 index 0000000..0560050 --- /dev/null +++ b/source/Game/EditorGuizmo.hpp @@ -0,0 +1,232 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.84 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +# include "imconfig.h" +#endif +#ifndef IMGUI_API +# define IMGUI_API +#endif + +// NOTE(hnosm): added so that we don't have to force #include after everything else +#include + +#ifndef IMGUIZMO_NAMESPACE +# define IMGUIZMO_NAMESPACE ImGuizmo +#endif + +namespace IMGUIZMO_NAMESPACE { +// call inside your own window and before Manipulate() in order to draw gizmo to that window. +// Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). +IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + +// call BeginFrame right after ImGui_XXXX_NewFrame(); +IMGUI_API void BeginFrame(); + +// this is necessary because when imguizmo is compiled into a dll, and imgui into another +// globals are not shared between them. +// More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam +// expose method to set imgui context +IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + +// return true if mouse cursor is over any gizmo control (axis, plan or screen component) +IMGUI_API bool IsOver(); + +// return true if mouse IsOver or if the gizmo is in moving state +IMGUI_API bool IsUsing(); + +// enable/disable the gizmo. Stay in the state until next call to Enable. +// gizmo is rendered with gray half transparent color when disabled +IMGUI_API void Enable(bool enable); + +// helper functions for manualy editing translation/rotation/scale with an input float +// translation, rotation and scale float points to 3 floats each +// Angles are in degrees (more suitable for human editing) +// example: +// float matrixTranslation[3], matrixRotation[3], matrixScale[3]; +// ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); +// ImGui::InputFloat3("Tr", matrixTranslation, 3); +// ImGui::InputFloat3("Rt", matrixRotation, 3); +// ImGui::InputFloat3("Sc", matrixScale, 3); +// ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); +// +// These functions have some numerical stability issues for now. Use with caution. +IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); +IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + +IMGUI_API void SetRect(float x, float y, float width, float height); +// default is false +IMGUI_API void SetOrthographic(bool isOrthographic); + +// Render a cube with face color corresponding to face normal. Usefull for debug/tests +IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); +IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + +// call it when you want a gizmo +// Needs view and projection matrices. +// matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional +// translation is applied in world space +enum OPERATION { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal + UNIVERSAL = TRANSLATE | ROTATE | SCALEU +}; + +inline OPERATION operator|(OPERATION lhs, OPERATION rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +enum MODE { + LOCAL, + WORLD +}; + +IMGUI_API bool Manipulate( + const float* view, + const float* projection, + OPERATION operation, + MODE mode, + float* matrix, + float* deltaMatrix = NULL, + const float* snap = NULL, + float* localBounds = NULL, + const float* boundsSnap = NULL); + +// +// Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en +// It seems to be a defensive patent in the US. I don't think it will bring troubles using it as +// other software are using the same mechanics. But just in case, you are now warned! +// +IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + +IMGUI_API void SetID(int id); + +// return true if the cursor is over the operation's gizmo +IMGUI_API bool IsOver(OPERATION op); +IMGUI_API void SetGizmoSizeClipSpace(float value); + +// Allow axis to flip +// When true (default), the guizmo axis flip for better visibility +// When false, they always stay along the positive world/local axis +IMGUI_API void AllowAxisFlip(bool value); +} // namespace IMGUIZMO_NAMESPACE diff --git a/source/Game/EditorNotification.cpp b/source/Game/EditorNotification.cpp new file mode 100644 index 0000000..e4a869e --- /dev/null +++ b/source/Game/EditorNotification.cpp @@ -0,0 +1,277 @@ +// Adapted from https://github.com/patrickcjk/imgui-notify +#include "EditorNotification.hpp" + +#include "Macros.hpp" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include + +#include +#include +#include +#include +#include + +ImGuiToast::ImGuiToast(ImGuiToastType type, int dismissTime) { + IM_ASSERT(type < ImGuiToastType_COUNT); + + mType = type; + mDismissTime = dismissTime; + + using namespace std::chrono; + auto timeStamp = system_clock::now().time_since_epoch(); + mCreationTime = duration_cast(timeStamp).count(); + + memset(mTitle, 0, sizeof(mTitle)); + memset(mContent, 0, sizeof(mContent)); +} + +ImGuiToast::ImGuiToast(ImGuiToastType type, const char* format, ...) + : ImGuiToast(type) { + if (format) { + va_list args; + va_start(args, format); + SetContent(format, args); + va_end(args); + } +} + +ImGuiToast::ImGuiToast(ImGuiToastType type, int dismissTime, const char* format, ...) + : ImGuiToast(type, dismissTime) { + if (format) { + va_list args; + va_start(args, format); + SetContent(format, args); + va_end(args); + } +} + +void ImGuiToast::SetTitle(const char* format, ...) { + if (format) { + va_list args; + va_start(args, format); + SetTitle(format, args); + va_end(args); + } +} + +void ImGuiToast::SetContent(const char* format, ...) { + if (format) { + va_list args; + va_start(args, format); + SetContent(format, args); + va_end(args); + } +} + +void ImGuiToast::SetType(const ImGuiToastType& type) { + IM_ASSERT(type < ImGuiToastType_COUNT); + mType = type; +} + +const char* ImGuiToast::GetTitle() { + return mTitle; +} + +const char* ImGuiToast::GetDefaultTitle() { + if (!strlen(mTitle)) { + switch (mType) { + case ImGuiToastType_None: return nullptr; + case ImGuiToastType_Success: return "Success"; + case ImGuiToastType_Warning: return "Warning"; + case ImGuiToastType_Error: return "Error"; + case ImGuiToastType_Info: return "Info"; + case ImGuiToastType_COUNT: UNREACHABLE; + } + } + + return mTitle; +} + +ImGuiToastType ImGuiToast::GetType() { + return mType; +} + +ImVec4 ImGuiToast::GetColor() { + switch (mType) { + case ImGuiToastType_None: return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White + case ImGuiToastType_Success: return ImVec4(0, 1.0f, 0, 1.0f); // Green + case ImGuiToastType_Warning: return ImVec4(1.0f, 1.0f, 0, 1.0f); // Yellow + case ImGuiToastType_Error: return ImVec4(1.0f, 0, 0, 1.0f); // Red + case ImGuiToastType_Info: return ImVec4(0, 0.616, 1.0f, 1.0f); // Blue + case ImGuiToastType_COUNT: UNREACHABLE; + } + return ImVec4(); +} + +const char* ImGuiToast::GetIcon() { + switch (mType) { + case ImGuiToastType_None: return nullptr; +#if 1 + // TODO add IconFontHeaders and replace with proper icons + case ImGuiToastType_Success: return nullptr; + case ImGuiToastType_Warning: return nullptr; + case ImGuiToastType_Error: return nullptr; + case ImGuiToastType_Info: return nullptr; +#else + case ImGuiToastType_Success: return ICON_FA_CHECK_CIRCLE; + case ImGuiToastType_Warning: return ICON_FA_EXCLAMATION_TRIANGLE; + case ImGuiToastType_Error: return ICON_FA_TIMES_CIRCLE; + case ImGuiToastType_Info: return ICON_FA_INFO_CIRCLE; +#endif + case ImGuiToastType_COUNT: UNREACHABLE; + } + return nullptr; +} + +const char* ImGuiToast::GetContent() { + return this->mContent; +} + +uint64_t ImGuiToast::GetElapsedTime() { + using namespace std::chrono; + auto timeStamp = system_clock::now().time_since_epoch(); + auto timeStampI = duration_cast(timeStamp).count(); + return timeStampI - mCreationTime; +} + +ImGuiToastPhase ImGuiToast::GetPhase() { + const auto elapsed = GetElapsedTime(); + + if (elapsed > kNotifyFadeInOutTime + mDismissTime + kNotifyFadeInOutTime) { + return ImGuiToastPhase_Expired; + } else if (elapsed > kNotifyFadeInOutTime + mDismissTime) { + return ImGuiToastPhase_FadeOut; + } else if (elapsed > kNotifyFadeInOutTime) { + return ImGuiToastPhase_Wait; + } else { + return ImGuiToastPhase_FadeIn; + } +} + +float ImGuiToast::GetFadePercent() { + const auto phase = GetPhase(); + const auto elapsed = GetElapsedTime(); + + if (phase == ImGuiToastPhase_FadeIn) + { + return ((float)elapsed / (float)kNotifyFadeInOutTime) * kNotifyOpacity; + } else if (phase == ImGuiToastPhase_FadeOut) + { + return (1.0f - (((float)elapsed - (float)kNotifyFadeInOutTime - (float)mDismissTime) / (float)kNotifyFadeInOutTime)) * kNotifyOpacity; + } + + return 1.0f * kNotifyOpacity; +} + +void ImGuiToast::SetTitle(const char* format, va_list args) { + vsnprintf(mTitle, sizeof(mTitle), format, args); +} + +void ImGuiToast::SetContent(const char* format, va_list args) { + vsnprintf(mContent, sizeof(mContent), format, args); +} + +namespace ImGui { +static std::vector notifications; +} + +static bool IsNullOrEmpty(const char* str) { + return !str || !strlen(str); +} + +void ImGui::AddNotification(ImGuiToast toast) { + notifications.push_back(std::move(toast)); +} + +void ImGui::RemoveNotification(int index) { + notifications.erase(notifications.begin() + index); +} + +void ImGui::ShowNotifications() { + auto vpSize = GetMainViewport()->Size; + + float height = 0.0f; + for (auto i = 0; i < notifications.size(); i++) { + auto* currentToast = ¬ifications[i]; + + // Remove toast if expired + if (currentToast->GetPhase() == ImGuiToastPhase_Expired) { + RemoveNotification(i); + continue; + } + + // Get icon, title and other data + const auto icon = currentToast->GetIcon(); + const auto title = currentToast->GetTitle(); + const auto content = currentToast->GetContent(); + const auto defaultTitle = currentToast->GetDefaultTitle(); + const auto opacity = currentToast->GetFadePercent(); // Get opacity based of the current phase + + // Window rendering + auto textColor = currentToast->GetColor(); + textColor.w = opacity; + + // Generate new unique name for this toast + char windowName[50]; + snprintf(windowName, std::size(windowName), "##TOAST%d", i); + + SetNextWindowBgAlpha(opacity); + SetNextWindowPos(ImVec2(vpSize.x - kNotifyPaddingX, vpSize.y - kNotifyPaddingY - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f)); + Begin(windowName, nullptr, kNotifyToastFlags); + BringWindowToDisplayFront(GetCurrentWindow()); + + // Here we render the toast content + { + PushTextWrapPos(vpSize.x / 3.0f); // We want to support multi-line text, this will wrap the text after 1/3 of the screen width + + bool wasTitleRendered = false; + + // If an icon is set + if (!::IsNullOrEmpty(icon)) { + // Render icon text + PushStyleColor(ImGuiCol_Text, textColor); + TextUnformatted(icon); + PopStyleColor(); + wasTitleRendered = true; + } + + // If a title is set + if (!::IsNullOrEmpty(title)) { + // If a title and an icon is set, we want to render on same line + if (!::IsNullOrEmpty(icon)) + SameLine(); + + TextUnformatted(title); // Render title text + wasTitleRendered = true; + } else if (!::IsNullOrEmpty(defaultTitle)) { + if (!::IsNullOrEmpty(icon)) + SameLine(); + + TextUnformatted(defaultTitle); // Render default title text (ImGuiToastType_Success -> "Success", etc...) + wasTitleRendered = true; + } + + // In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically + if (wasTitleRendered && !::IsNullOrEmpty(content)) { + SetCursorPosY(GetCursorPosY() + 5.0f); // Must be a better way to do this!!!! + } + + // If a content is set + if (!::IsNullOrEmpty(content)) { + if (wasTitleRendered) { + Separator(); + } + + TextUnformatted(content); // Render content text + } + + PopTextWrapPos(); + } + + // Save height for next toasts + height += GetWindowHeight() + kNotifyPaddingMessageY; + + End(); + } +} diff --git a/source/Game/EditorNotification.hpp b/source/Game/EditorNotification.hpp new file mode 100644 index 0000000..3af8c2d --- /dev/null +++ b/source/Game/EditorNotification.hpp @@ -0,0 +1,81 @@ +// Adapted from https://github.com/patrickcjk/imgui-notify +#pragma once + +#include +#include + +enum ImGuiToastType { + ImGuiToastType_None, + ImGuiToastType_Success, + ImGuiToastType_Warning, + ImGuiToastType_Error, + ImGuiToastType_Info, + ImGuiToastType_COUNT +}; + +enum ImGuiToastPhase { + ImGuiToastPhase_FadeIn, + ImGuiToastPhase_Wait, + ImGuiToastPhase_FadeOut, + ImGuiToastPhase_Expired, + ImGuiToastPhase_COUNT +}; + +enum ImGuiToastPos { + ImGuiToastPos_TopLeft, + ImGuiToastPos_TopCenter, + ImGuiToastPos_TopRight, + ImGuiToastPos_BottomLeft, + ImGuiToastPos_BottomCenter, + ImGuiToastPos_BottomRight, + ImGuiToastPos_Center, + ImGuiToastPos_COUNT +}; + +constexpr int kNotifyMaxMsgLength = 4096; // Max message content length +constexpr float kNotifyPaddingX = 20.0f; // Bottom-left X padding +constexpr float kNotifyPaddingY = 20.0f; // Bottom-left Y padding +constexpr float kNotifyPaddingMessageY = 10.0f; // Padding Y between each message +constexpr uint64_t kNotifyFadeInOutTime = 150; // Fade in and out duration +constexpr uint64_t kNotifyDefaultDismiss = 3000; // Auto dismiss after X ms (default, applied only of no data provided in constructors) +constexpr float kNotifyOpacity = 1.0f; // 0-1 Toast opacity +constexpr ImGuiWindowFlags kNotifyToastFlags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing; + +class ImGuiToast { +private: + ImGuiToastType mType = ImGuiToastType_None; + char mTitle[kNotifyMaxMsgLength] = {}; + char mContent[kNotifyMaxMsgLength] = {}; + int mDismissTime = kNotifyDefaultDismiss; + uint64_t mCreationTime = 0; + +public: + ImGuiToast(ImGuiToastType type, int dismissTime = kNotifyDefaultDismiss); + ImGuiToast(ImGuiToastType type, const char* format, ...); + ImGuiToast(ImGuiToastType type, int dismissTime, const char* format, ...); + + void SetTitle(const char* format, ...); + void SetContent(const char* format, ...); + void SetType(const ImGuiToastType& type); + + const char* GetTitle(); + const char* GetDefaultTitle(); + ImGuiToastType GetType(); + ImVec4 GetColor(); + const char* GetIcon(); + const char* GetContent(); + + uint64_t GetElapsedTime(); + ImGuiToastPhase GetPhase(); + float GetFadePercent(); + +private: + void SetTitle(const char* format, va_list args); + void SetContent(const char* format, va_list args); +}; + +namespace ImGui { +void AddNotification(ImGuiToast toast); +void RemoveNotification(int index); +void ShowNotifications(); +} // namespace ImGui diff --git a/source/Game/EditorUtils.cpp b/source/Game/EditorUtils.cpp new file mode 100644 index 0000000..20caef7 --- /dev/null +++ b/source/Game/EditorUtils.cpp @@ -0,0 +1,447 @@ +#include "EditorUtils.hpp" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include + +#include +#include +#include +#include +#include + +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(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(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 callback; +}; + +static DialogObject gConfirmationDialog{}; + +void ImGui::DialogConfirmation(std::string message, std::function 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/Game/EditorUtils.hpp b/source/Game/EditorUtils.hpp new file mode 100644 index 0000000..99c522b --- /dev/null +++ b/source/Game/EditorUtils.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "Color.hpp" +#include "EditorGuizmo.hpp" + +#include +#include + +// 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 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); + +} // namespace Utils diff --git a/source/Game/FuzzyMatch.cpp b/source/Game/FuzzyMatch.cpp new file mode 100644 index 0000000..0ab604d --- /dev/null +++ b/source/Game/FuzzyMatch.cpp @@ -0,0 +1,174 @@ +// Adapted from https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.h +#include "FuzzyMatch.hpp" + +#include +#include + +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/Game/FuzzyMatch.hpp b/source/Game/FuzzyMatch.hpp new file mode 100644 index 0000000..7a26b7e --- /dev/null +++ b/source/Game/FuzzyMatch.hpp @@ -0,0 +1,10 @@ +// Adapted from https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.h +#pragma once + +#include + +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/Game/GameObject.cpp b/source/Game/GameObject.cpp new file mode 100644 index 0000000..3b15111 --- /dev/null +++ b/source/Game/GameObject.cpp @@ -0,0 +1,230 @@ +#include "GameObject.hpp" + +#include "Level.hpp" +#include "Player.hpp" +#include "SceneThings.hpp" +#include "World.hpp" + +#include +#include + +#include +#include +#include +#include + +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::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::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, 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 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::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(rapidjson::AsStringView(*rvType)); + assert(kind.has_value()); + auto obj = std::unique_ptr(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 diff --git a/source/Game/GameObject.hpp b/source/Game/GameObject.hpp new file mode 100644 index 0000000..f975803 --- /dev/null +++ b/source/Game/GameObject.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include "EditorAttachment.hpp" +#include "Material.hpp" +#include "Renderer.hpp" +#include "VertexIndex.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace Tags { +enum class GameObjectKind { + KD_Generic, + KD_Player, + KD_SimpleGeometry, + KD_Building, + KD_LevelWrapper, + KD_COUNT, +}; +BRUSSEL_ENUM(GameObjectKind, ToString FromString ExcludeHeuristics); +} // namespace Tags + +class GameWorld; +class GameObject { +public: + using Kind = Tags::GameObjectKind; + using enum Tags::GameObjectKind; + +private: + std::unique_ptr mEditorAttachment; + GameWorld* mWorld; + GameObject* mParent; + PodVector 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& GetChildren() const; + void AddChild(GameObject* child); + GameObject* RemoveChild(int index); + GameObject* RemoveChild(GameObject* child); + void RemoveSelfFromParent(); + PodVector 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 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 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 diff --git a/source/Game/GraphicsTags.cpp b/source/Game/GraphicsTags.cpp new file mode 100644 index 0000000..eb9a079 --- /dev/null +++ b/source/Game/GraphicsTags.cpp @@ -0,0 +1,273 @@ +#include "GraphicsTags.hpp" + +#include +#include +#include + +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 GL_NONE; +} + +namespace ProjectBrussel_UNITY_ID { +struct GLTypeInfo { + robin_hood::unordered_flat_map enum2Name; + robin_hood::unordered_flat_map 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 GL_NONE; + } +} + +#include diff --git a/source/Game/GraphicsTags.hpp b/source/Game/GraphicsTags.hpp new file mode 100644 index 0000000..cdf79eb --- /dev/null +++ b/source/Game/GraphicsTags.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace Tags { +/// Vertex element semantics, used to identify the meaning of vertex buffer contents +enum VertexElementSemantic { + /// 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, +}; +BRUSSEL_ENUM(VertexElementSemantic, ToString FromString ExcludeHeuristics); + +enum VertexElementType { + 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; +BRUSSEL_ENUM(VertexElementType, ToString FromString ExcludeHeuristics); + +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 { + TF_Linear, + TF_Nearest, +}; +BRUSSEL_ENUM(TexFilter, ToString FromString ExcludeHeuristics); + +std::string_view GLTypeToString(GLenum); +GLenum GLTypeFromString(std::string_view name); + +constexpr auto kInvalidLocation = std::numeric_limits::max(); +} // namespace Tags + +#include diff --git a/source/Game/Image.cpp b/source/Game/Image.cpp new file mode 100644 index 0000000..3673acc --- /dev/null +++ b/source/Game/Image.cpp @@ -0,0 +1,101 @@ +#include "Image.hpp" + +#include +#include +#include + +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 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 pixels, glm::ivec2 dimensions, int channels) { + mData = std::make_unique(pixels.size()); + std::memcpy(mData.get(), pixels.data(), pixels.size()); + mSize = dimensions; + mChannels = channels; + return true; +} + +bool Image::InitFromPixels(std::unique_ptr 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 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/Game/Image.hpp b/source/Game/Image.hpp new file mode 100644 index 0000000..c577c24 --- /dev/null +++ b/source/Game/Image.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "Color.hpp" +#include "RcPtr.hpp" + +#include +#include +#include +#include + +/// 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 mData; + glm::ivec2 mSize; + int mChannels; + +public: + Image(); + + bool InitFromImageFile(const char* filePath, int desiredChannels = 0); + bool InitFromImageData(std::span data, int desiredChannels = 0); + bool InitFromPixels(std::span pixels, glm::ivec2 dimensions, int channels); + bool InitFromPixels(std::unique_ptr 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 GetData() const; + glm::ivec2 GetSize() const; + int GetChannels() const; + bool IsEmpty() const; +}; diff --git a/source/Game/Ires.cpp b/source/Game/Ires.cpp new file mode 100644 index 0000000..bfa4cdf --- /dev/null +++ b/source/Game/Ires.cpp @@ -0,0 +1,409 @@ +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; +using namespace std::literals; + +IresObject::IresObject(Kind kind) + : mKind{ kind } { +} + +std::unique_ptr IresObject::Create(Kind kind) { + switch (kind) { + case KD_Texture: return std::make_unique(); + case KD_Shader: return std::make_unique(); + case KD_Material: return std::make_unique(); + case KD_SpriteFiles: return std::make_unique(); + case KD_Spritesheet: return std::make_unique(); + 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(""); +} + +void IresObject::ShowName() const { + if (IsAnnoymous()) { + ImGui::Text("", (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(""); + 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("", (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: ", (void*)this); + } else { + ImGui::Text("Name: %s", mName.c_str()); + } + + if (mUid.IsNull()) { + ImGui::TextUnformatted("Uid: "); + } 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::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::ReadBasic(const rapidjson::Value& value) { + auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); + if (!rvType) return nullptr; + auto kind = Metadata::EnumFromString(rapidjson::AsStringView(*rvType)); + assert(kind.has_value()); + auto ires = Create(kind.value()); + if (!ires) return nullptr; + + auto rvUid = rapidjson::GetProperty(value, rapidjson::kArrayType, "Uid"sv); + if (!rvUid) return nullptr; + ires->mUid.Read(*rvUid); + + 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 ires; + }; + + class IresLoadTimeContext final : public IresLoadingContext { + public: + // NOTE: pointer stability required + robin_hood::unordered_node_map candidates; + std::vector> candidatesByKind; + + IresLoadTimeContext() { + candidatesByKind.resize((int)IresObject::KD_COUNT); + } + + std::vector& GetByKind(IresObject::Kind kind) { + int i = static_cast(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.", 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(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.", cand->path.c_str()); + continue; + } + + ires->mMan = this; + mObjByUid.try_emplace(ires->GetUid(), ires); + } + } +} + +std::pair 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 writer(stream); + writer.SetFormatOptions(rapidjson::PrettyFormatOptions::kFormatSingleLineArray); + root.Accept(writer); +} + +IresObject* IresManager::FindIres(const Uid& uid) const { + auto iter = mObjByUid.find(uid); + if (iter != mObjByUid.end()) { + return iter->second.Get(); + } else { + return nullptr; + } +} + +#include diff --git a/source/Game/Ires.hpp b/source/Game/Ires.hpp new file mode 100644 index 0000000..b6420f3 --- /dev/null +++ b/source/Game/Ires.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include "EditorAttachment.hpp" +#include "EditorCore.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// Forward declarations +class IresManager; +class IresWritingContext; +class IresLoadingContext; + +namespace Tags { +enum class IresObjectKind { + KD_Texture, + KD_Shader, + KD_Material, + KD_SpriteFiles, + KD_Spritesheet, + KD_COUNT, +}; +BRUSSEL_ENUM(IresObjectKind, ToString FromString ExcludeHeuristics); +} // 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 mEditorAttachment; // Transient + IresManager* mMan = nullptr; // Transient + Kind mKind; // Serialized in full mode + +public: + IresObject(Kind kind); + virtual ~IresObject() = default; + + static std::unique_ptr 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 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 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> mObjByUid; + +public: + void DiscoverFilesDesignatedLocation(); + void DiscoverFiles(const std::filesystem::path& dir); + + std::pair 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); + + const auto& GetObjects() const { return mObjByUid; } + virtual IresObject* FindIres(const Uid& uid) const override; +}; + +#include diff --git a/source/Game/Level.cpp b/source/Game/Level.cpp new file mode 100644 index 0000000..076e5d5 --- /dev/null +++ b/source/Game/Level.cpp @@ -0,0 +1,228 @@ +#include "Level.hpp" + +#include "AppConfig.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; +namespace fs = std::filesystem; + +constexpr auto kParentToRootObject = std::numeric_limits::max(); +constexpr auto kInvalidEntryId = std::numeric_limits::max(); + +struct Level::InstanciationEntry { + // If set to std::numeric_limits::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(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 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 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/Game/Level.hpp b/source/Game/Level.hpp new file mode 100644 index 0000000..9114a64 --- /dev/null +++ b/source/Game/Level.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "EditorCore.hpp" +#include "GameObject.hpp" + +#include +#include + +#include +#include +#include + +// 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 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 field in release mode? + struct LoadableObject { + RcPtr 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 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 { +private: + RcPtr mLevel; + +public: + LevelWrapperObject(GameWorld* world); + ~LevelWrapperObject() override; + + Level* GetBoundLevel() const; + void SetBoundLevel(Level* level); +}; diff --git a/source/Game/Material.cpp b/source/Game/Material.cpp new file mode 100644 index 0000000..9b0c42d --- /dev/null +++ b/source/Game/Material.cpp @@ -0,0 +1,526 @@ +#include "Material.hpp" + +#include "AppConfig.hpp" +#include "EditorCore.hpp" +#include "EditorUtils.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 +TUniform& ObtainUniform(Shader* shader, const char* name, std::vector& 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 +void Material::SetVector(const char* name, const glm::vec& 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 +void Material::SetMatrix(const char* name, const glm::mat& 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 Material::GetVectors() const { + return mBoundVectors; +} + +std::span Material::GetMatrices() const { + return mBoundMatrices; +} + +std::span 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(*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(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(*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::max()); break; + default: ImGui::TextUnformatted("Unsupported scalar type"); break; + } + ImGui::Unindent(); + } + for (auto& field : mInstance->mBoundVectors) { + auto& decl = static_cast(*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(*shaderInfo.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + + // TODO + } + for (auto& field : mInstance->mBoundTextures) { + auto& decl = static_cast(*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(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/Game/Material.hpp b/source/Game/Material.hpp new file mode 100644 index 0000000..f1cd7dd --- /dev/null +++ b/source/Game/Material.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include "Ires.hpp" +#include "RcPtr.hpp" +#include "Shader.hpp" +#include "Texture.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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 value; + /* Transient */ int infoUniformIndex; + /* Transient */ GLint location; + }; + + IresMaterial* mIres = nullptr; + RcPtr mShader; + std::vector mBoundScalars; + std::vector mBoundVectors; + std::vector mBoundMatrices; + std::vector 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 + void SetVector(const char* name, const glm::vec& vec); + + /// Instanciated for sizes (2,2) (3,3) (4,4) (2,3) (3,2) (2,4) (4,2) (3,4) (4,3) + template + void SetMatrix(const char* name, const glm::mat& mat); + + void SetTexture(const char* name, Texture* texture); + + std::span GetVectors() const; + std::span GetMatrices() const; + std::span 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 gDefaultMaterial; + +class IresMaterial : public IresObject { +private: + RcPtr 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/Game/Mesh.cpp b/source/Game/Mesh.cpp new file mode 100644 index 0000000..244e2e3 --- /dev/null +++ b/source/Game/Mesh.cpp @@ -0,0 +1,54 @@ +#include "Mesh.hpp" + +#include + +// 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/Game/Mesh.hpp b/source/Game/Mesh.hpp new file mode 100644 index 0000000..f86fd55 --- /dev/null +++ b/source/Game/Mesh.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "Color.hpp" +#include "VertexIndex.hpp" +#include "PodVector.hpp" +#include "RcPtr.hpp" + +#include +#include +#include +#include + +struct StandardVertexExtra { + float u, v; + uint8_t r, g, b, a; +}; + +class StandardCpuMeshData { +public: + PodVector vertPositions; + PodVector vertExtra; + PodVector index; + size_t vertexCount; + size_t triangleCount; +}; + +class StandardCpuMesh { +// private: +// StandardCpuMeshData* mData = nullptr; +// RcPtr 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/Game/Player.cpp b/source/Game/Player.cpp new file mode 100644 index 0000000..34c4549 --- /dev/null +++ b/source/Game/Player.cpp @@ -0,0 +1,139 @@ +#include "Player.hpp" + +#include "AppConfig.hpp" +#include "CommonVertexIndex.hpp" +#include "ScopeGuard.hpp" +#include "Utils.hpp" + +#include +#include + +// 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 PlayerKeyBinds::GetKeyArray() { + return { &keyLeft, kPlayerKeyBindCount }; +} +std::span 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{ 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 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/Game/Player.hpp b/source/Game/Player.hpp new file mode 100644 index 0000000..d003a25 --- /dev/null +++ b/source/Game/Player.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "GameObject.hpp" +#include "Material.hpp" +#include "RcPtr.hpp" +#include "Sprite.hpp" + +#define GLFW_INCLUDE_NONE +#include + +#include +#include + +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 GetKeyArray(); + std::span GetKeyStatusArray(); +}; + +class Player : public GameObject { +public: + std::vector 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 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/Game/Renderer.cpp b/source/Game/Renderer.cpp new file mode 100644 index 0000000..3497449 --- /dev/null +++ b/source/Game/Renderer.cpp @@ -0,0 +1,170 @@ +#include "Renderer.hpp" + +#include "GameObject.hpp" + +#include +#include +#include +#include + +RenderObject::RenderObject() + : mVao{ GL_NONE } { +} + +RenderObject::~RenderObject() { + DeleteGLObjects(); +} + +GLuint RenderObject::GetGLVao() const { + return mVao; +} + +void RenderObject::RebuildIfNecessary() { + if (mVao != GL_NONE) { + 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(GL_NONE); +} + +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 != GL_NONE) { + glDeleteVertexArrays(1, &mVao); + mVao = GL_NONE; + } +} + +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); + + auto vpMatrix = mFrame.matrixProj * mFrame.matrixView; + + // 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) { + glm::mat4 objectMatrix(1.0f); + objectMatrix = glm::translate(objectMatrix, gameObject->GetPos()); + objectMatrix *= glm::toMat4(gameObject->GetRotation()); + objectMatrix = glm::scale(objectMatrix, gameObject->GetScale()); + glm::mat4 transform = vpMatrix * objectMatrix; + + glUniformMatrix4fv(shader->autofill_Transform, 1, GL_FALSE, &transform[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); + } +} diff --git a/source/Game/Renderer.hpp b/source/Game/Renderer.hpp new file mode 100644 index 0000000..98a9f28 --- /dev/null +++ b/source/Game/Renderer.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "Camera.hpp" +#include "Material.hpp" +#include "RcPtr.hpp" +#include "VertexIndex.hpp" + +#include +#include +#include + +// TODO add optional support for OpenGL separate attrib binding & only depend on vertex format + +class GameObject; + +class RenderObject { +public: + RcPtr autofill_TextureAtlas; + +private: + RcPtr mMaterial; + RcPtr mIndexBuf; + RcPtr 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 { +private: + RendererFrameInfo mFrame; + bool mInsideFrame = false; + +public: + 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(); +}; diff --git a/source/Game/SceneThings.cpp b/source/Game/SceneThings.cpp new file mode 100644 index 0000000..3fa0436 --- /dev/null +++ b/source/Game/SceneThings.cpp @@ -0,0 +1,142 @@ +#include "SceneThings.hpp" + +#include "CommonVertexIndex.hpp" +#include "Rect.hpp" + +#include + +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 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 BuildingObject::GetRenderObjects() const { + return { &mRenderObject, 1 }; +} diff --git a/source/Game/SceneThings.hpp b/source/Game/SceneThings.hpp new file mode 100644 index 0000000..c261fbb --- /dev/null +++ b/source/Game/SceneThings.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "Color.hpp" +#include "GameObject.hpp" +#include "Renderer.hpp" + +#include +#include + +class SimpleGeometryObject : public GameObject { +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 GetRenderObjects() const override; +}; + +class BuildingObject : public GameObject { +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 GetRenderObjects() const override; +}; diff --git a/source/Game/Shader.cpp b/source/Game/Shader.cpp new file mode 100644 index 0000000..4a58635 --- /dev/null +++ b/source/Game/Shader.cpp @@ -0,0 +1,773 @@ +#include "Shader.hpp" + +#include "AppConfig.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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))); +} + +bool ShaderThingId::IsValid() const { + return kind == KD_Invalid; +} + +namespace ProjectBrussel_UNITY_ID { +GLuint FindLocation(const std::vector& 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); +} + +ShaderVariable* ShaderInfo::FindVariable(const ShaderThingId& thing) { + switch (thing.kind) { + case ShaderThingId::KD_Input: return &inputs[thing.index]; + case ShaderThingId::KD_Output: return &outputs[thing.index]; + case ShaderThingId::KD_Uniform: return uniforms[thing.index].get(); + case ShaderThingId::KD_Invalid: break; + } + return nullptr; +} + +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(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; + } + + 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; + } + + return false; +} + +std::unique_ptr CreateVariable(GLenum type, GLuint loc) { + GLenum scalarType; + int width; + int height; + if (QueryMathInfo(type, scalarType, width, height)) { + auto res = std::make_unique(); + res->location = loc; + res->scalarType = type; + res->width = width; + res->height = height; + return res; + } + + if (QuerySamplerInfo(type)) { + auto res = std::make_unique(); + res->location = loc; + res->type = type; + return res; + } + + return nullptr; +} +} // 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& 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; +} + +namespace ProjectBrussel_UNITY_ID { +void WriteShaderVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderVariable& var) { + value.AddMember("Name", var.name, root.GetAllocator()); + value.AddMember("Semantic", rapidjson::StringRef(Metadata::EnumToString(var.semantic)), root.GetAllocator()); + value.AddMember("OpenGLLocation", var.location, root.GetAllocator()); +} + +bool ReadShaderVariable(const rapidjson::Value& value, ShaderVariable& var) { + using namespace Tags; + + BRUSSEL_JSON_GET(value, "Name", std::string, var.name, return false); + { // Semantic + auto rvSemantic = rapidjson::GetProperty(value, rapidjson::kStringType, "Semantic"sv); + if (!rvSemantic) { + var.semantic = VES_Generic; + } else { + auto str = rapidjson::AsStringView(*rvSemantic); + auto lookup = Metadata::EnumFromString(str); + var.semantic = lookup.value_or(VES_Generic); + } + } + BRUSSEL_JSON_GET_DEFAULT(value, "OpenGLLocation", int, var.location, 0); + return true; +} + +void WriteShaderMathVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderMathVariable& var) { + WriteShaderVariable(value, root, var); + value.AddMember("ScalarType", rapidjson::StringRef(Tags::GLTypeToString(var.scalarType)), root.GetAllocator()); + value.AddMember("Width", var.width, root.GetAllocator()); + value.AddMember("Height", var.height, root.GetAllocator()); + value.AddMember("ArrayLength", var.arrayLength, root.GetAllocator()); +} + +bool ReadShaderMathVariable(const rapidjson::Value& value, ShaderMathVariable& var) { + if (!ReadShaderVariable(value, var)) return false; + { + auto rvScalar = rapidjson::GetProperty(value, rapidjson::kStringType, "ScalarType"sv); + if (!rvScalar) return false; + var.scalarType = Tags::GLTypeFromString(rapidjson::AsStringView(*rvScalar)); + } + BRUSSEL_JSON_GET(value, "Width", int, var.width, return false); + BRUSSEL_JSON_GET(value, "Height", int, var.height, return false); + BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1); + return true; +} + +void WriteShaderSamplerVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderSamplerVariable& var) { + WriteShaderVariable(value, root, var); + // TODO +} + +bool ReadShaderSamplerVariable(const rapidjson::Value& value, ShaderSamplerVariable& var) { + if (!ReadShaderVariable(value, var)) return false; + BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1); + return true; +} +} // namespace ProjectBrussel_UNITY_ID + +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) { + uniform->ShowInfo(); + } + 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); + + auto& shaderInfo = mInstance->mInfo; + + value.AddMember("SourceFile", mSourceFile, root.GetAllocator()); + + auto SaveMathVars = [&](const char* name, const std::vector& vars) { + rapidjson::Value rvThings(rapidjson::kArrayType); + for (auto& thing : vars) { + rapidjson::Value rvThing(rapidjson::kObjectType); + WriteShaderMathVariable(rvThing, root, thing); + + rvThings.PushBack(rvThing, root.GetAllocator()); + } + value.AddMember(rapidjson::StringRef(name), rvThings, root.GetAllocator()); + }; + SaveMathVars("Inputs", shaderInfo.inputs); + SaveMathVars("Outputs", shaderInfo.outputs); + + // TODO uniforms +} + +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(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& 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 { + if (type == "Math"sv) { + auto uniform = std::make_unique(); + ReadShaderMathVariable(*rvValue, *uniform); + + return uniform; + } else if (type == "Sampler"sv) { + auto uniform = std::make_unique(); + 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/Game/Shader.hpp b/source/Game/Shader.hpp new file mode 100644 index 0000000..707e6cc --- /dev/null +++ b/source/Game/Shader.hpp @@ -0,0 +1,173 @@ +#pragma once + +#include "GraphicsTags.hpp" +#include "Ires.hpp" +#include "RcPtr.hpp" +#include "Utils.hpp" + +#include +#include +#include +#include +#include + +// TODO move to variable after pattern matching is in the language + +// Forward declarations +class Shader; +class IresShader; + +struct ShaderVariable { + enum Kind { + KD_Math, + KD_Sampler, + }; + + std::string name; + Kind kind; + GLuint location; + Tags::VertexElementSemantic semantic = Tags::VES_Generic; + + virtual void ShowInfo() const = 0; + +protected: + ShaderVariable(Kind kind) + : kind{ kind } {} +}; + +struct ShaderMathVariable : public ShaderVariable { + GLenum scalarType; + int arrayLength; + int width; + int height; + + ShaderMathVariable() + : ShaderVariable(KD_Math) {} + + virtual void ShowInfo() const override; +}; + +struct ShaderSamplerVariable : public ShaderVariable { + GLenum type; + int arrayLength; + + ShaderSamplerVariable() + : ShaderVariable(KD_Sampler) {} + + virtual void ShowInfo() const override; +}; + +struct ShaderThingId { + enum Kind { + KD_Input, + KD_Output, + KD_Uniform, + KD_Invalid, + }; + + Kind kind = KD_Invalid; + int index = 0; + + bool IsValid() const; +}; + +struct ShaderInfo { + robin_hood::unordered_map things; + std::vector inputs; + std::vector outputs; + std::vector> uniforms; + + GLuint FindInputLocation(Tags::VertexElementSemantic semantic); + GLuint FindOutputLocation(Tags::VertexElementSemantic semantic); + ShaderVariable* FindVariable(const ShaderThingId& thing); +}; + +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 gDefaultShader; + +class IresShader : public IresObject { +private: + RcPtr 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/Game/Sprite.cpp b/source/Game/Sprite.cpp new file mode 100644 index 0000000..2b4923c --- /dev/null +++ b/source/Game/Sprite.cpp @@ -0,0 +1,328 @@ +#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 +#include +#include +#include + +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 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(); + if (atlas->InitAtlas(atlasIn, &atlasOut) != Texture::EC_Success) { + return nullptr; + } + + auto sprite = std::make_unique(); + 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(*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::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(); + if (atlas->InitFromFile(path) != Texture::EC_Success) { + return nullptr; + } + + auto sprite = std::make_unique(); + 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/Game/Sprite.hpp b/source/Game/Sprite.hpp new file mode 100644 index 0000000..e163a01 --- /dev/null +++ b/source/Game/Sprite.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include "Ires.hpp" +#include "PodVector.hpp" +#include "RcPtr.hpp" +#include "Renderer.hpp" +#include "Texture.hpp" + +#include +#include +#include +#include +#include + +class SpriteDefinition : public RefCounted { + friend class IresSpriteFiles; + friend class IresSpritesheet; + +private: + RcPtr mAtlas; + glm::ivec2 mBoundingBox; + std::vector 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 mInstance; + std::vector 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 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 mSprites; +}; + +class Sprite { +private: + RcPtr 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/Game/Texture.cpp b/source/Game/Texture.cpp new file mode 100644 index 0000000..6fa7c8a --- /dev/null +++ b/source/Game/Texture.cpp @@ -0,0 +1,250 @@ +#include "Texture.hpp" + +#include "Macros.hpp" +#include "PodVector.hpp" +#include "ScopeGuard.hpp" + +#include +#include +#include +#include +#include + +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 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(size.x); + rect.h = static_cast(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 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 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(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(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/Game/Texture.hpp b/source/Game/Texture.hpp new file mode 100644 index 0000000..108dfa7 --- /dev/null +++ b/source/Game/Texture.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include "GraphicsTags.hpp" +#include "Image.hpp" +#include "Ires.hpp" +#include "RcPtr.hpp" + +#include +#include +#include +#include + +// 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 sources; + PackingMode packingMode; + }; + struct AtlasOutput { + std::vector 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 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/Game/VertexIndex.cpp b/source/Game/VertexIndex.cpp new file mode 100644 index 0000000..ac68289 --- /dev/null +++ b/source/Game/VertexIndex.cpp @@ -0,0 +1,84 @@ +#include "VertexIndex.hpp" + +#include + +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/Game/VertexIndex.hpp b/source/Game/VertexIndex.hpp new file mode 100644 index 0000000..2d65617 --- /dev/null +++ b/source/Game/VertexIndex.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "GraphicsTags.hpp" +#include "RcPtr.hpp" +#include "SmallVector.hpp" + +#include +#include +#include +#include + +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, 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 elements; + int vertexSize = 0; + + const decltype(elements)& GetElements() const { return elements; } + void AddElement(VertexElementFormat element); + void RemoveElement(int index); +}; diff --git a/source/Game/World.cpp b/source/Game/World.cpp new file mode 100644 index 0000000..d4a8344 --- /dev/null +++ b/source/Game/World.cpp @@ -0,0 +1,74 @@ +#include "World.hpp" + +#include "GameObject.hpp" +#include "PodVector.hpp" + +#include + +namespace ProjectBrussel_UNITY_ID { +template +void CallGameObjectRecursive(GameObject* start, TFunction&& func) { + PodVector 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); + } +} + +struct DrawCall { + GLuint vao; + GLuint vbo; +}; +} // 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() { + if (mAwakened) return; + + ProjectBrussel_UNITY_ID::CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Awaken(); }); + mAwakened = true; +} + +void GameWorld::Resleep() { + if (!mAwakened) return; + + ProjectBrussel_UNITY_ID::CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Resleep(); }); + mAwakened = false; +} + +void GameWorld::Update() { + 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/Game/World.hpp b/source/Game/World.hpp new file mode 100644 index 0000000..288142e --- /dev/null +++ b/source/Game/World.hpp @@ -0,0 +1,25 @@ +#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/Game/buildfile b/source/Game/buildfile new file mode 100644 index 0000000..e69de29 diff --git a/source/Game/main.cpp b/source/Game/main.cpp new file mode 100644 index 0000000..c49fc0b --- /dev/null +++ b/source/Game/main.cpp @@ -0,0 +1,461 @@ +#include "App.hpp" + +#include "AppConfig.hpp" +#include "CommonVertexIndex.hpp" +#include "EditorGuizmo.hpp" +#include "Ires.hpp" +#include "Level.hpp" +#include "Material.hpp" +#include "Shader.hpp" + +#define GLFW_INCLUDE_NONE +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 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(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(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(glfwGetWindowUserPointer(window)); + auto app = userData->app; + app->HandleKey(keyboard, key, action); + } +} + +// https://stackoverflow.com/questions/54499256/how-to-find-the-saved-games-folder-programmatically-in-c-c +#if defined(_WIN32) +# if defined(__MINGW32__) +# include +# else +# include +# endif +# include +# 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; + + constexpr auto kImGuiBackend = "imgui-backend"; + constexpr auto kGameDataDir = "game-data-directory"; + constexpr auto kGameAssetDir = "game-asset-directory"; + + cxxopts::Options options(std::string(AppConfig::kAppName), ""); + // clang-format off + options.add_options() + (kImGuiBackend, "ImGui backend. Options: opengl2, opengl3. Leave empty to default.", cxxopts::value()) + (kGameAssetDir, "Directory in which assets are looked up from. Can be relative paths to the executable.", cxxopts::value()->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()) + ; + // clang-format on + auto args = options.parse(argc, argv); + + bool imguiUseOpenGL3; + if (args.count(kImGuiBackend) > 0) { + auto imguiBackend = args[kImGuiBackend].as(); + 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(); + + 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 { +#if defined(_WIN32) + fs::path dataDir; + + PWSTR path = nullptr; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, nullptr, &path); + if (SUCCEEDED(hr)) { + dataDir = fs::path(path) / AppConfig::kAppName; + CoTaskMemFree(path); + + fs::create_directories(dataDir); + } 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 dataDir = fs::path("~/Library/Application Support/") / AppConfig::kAppName; + fs::create_directories(dataDir); +#elif defined(__linux__) + auto dataDir = GetEnvVar("XDG_DATA_HOME", "~/.local/share") / AppConfig::kAppName; + fs::create_directories(dataDir); +#endif + } + + if (args.count(kGameDataDir) > 0) { + auto dataDir = args[kGameDataDir].as(); + + fs::path dataDirPath(dataDir); + fs::create_directories(dataDir); + + AppConfig::dataDir = std::move(dataDir); + AppConfig::dataDirPath = std::move(dataDirPath); + } else { + // TODO platform default path + AppConfig::dataDir = "."; + AppConfig::dataDirPath = fs::path("."); + } + + if (!glfwInit()) { + return -1; + } + + glfwSetErrorCallback(&GlfwErrorCallback); + + 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, "ImGui Command Palette Example", 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); + + // TODO setup opengl debug context + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + return -3; + } + + 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(); + } + + 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, + }); + + // 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; +} -- cgit v1.2.3-70-g09d2