aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-04-25 20:22:07 -0700
committerrtk0c <[email protected]>2022-04-25 20:22:07 -0700
commit855da86feae1a5cc14dc2d486ccf115f484dbc2e (patch)
tree8284c6a6bdfb1a919eb1a22f466f4180a329c7f3 /source
parentd78a55de5003dbb040f1d1c369409e63a2c806d8 (diff)
Changeset: 16 Initial work on rendering sprites to screen
Diffstat (limited to 'source')
-rw-r--r--source/App.cpp103
-rw-r--r--source/App.hpp27
-rw-r--r--source/AppConfig.hpp4
-rw-r--r--source/CMakeLists.txt6
-rw-r--r--source/Camera.cpp33
-rw-r--r--source/Camera.hpp20
-rw-r--r--source/CommonVertexIndex.cpp152
-rw-r--r--source/CommonVertexIndex.hpp70
-rw-r--r--source/CpuMesh.cpp54
-rw-r--r--source/CpuMesh.hpp51
-rw-r--r--source/EditorAttachmentImpl.cpp10
-rw-r--r--source/EditorAttachmentImpl.hpp15
-rw-r--r--source/EditorCommandPalette.cpp704
-rw-r--r--source/EditorCommandPalette.hpp49
-rw-r--r--source/EditorCore.cpp289
-rw-r--r--source/EditorCore.hpp15
-rw-r--r--source/EditorNotification.cpp341
-rw-r--r--source/EditorNotification.hpp84
-rw-r--r--source/FuzzyMatch.cpp174
-rw-r--r--source/FuzzyMatch.hpp10
-rw-r--r--source/GameObject.cpp87
-rw-r--r--source/GameObject.hpp36
-rw-r--r--source/GameObjectTags.hpp22
-rw-r--r--source/Ires.cpp20
-rw-r--r--source/Ires.hpp10
-rw-r--r--source/Level.cpp5
-rw-r--r--source/Level.hpp7
-rw-r--r--source/Material.cpp2
-rw-r--r--source/Material.hpp3
-rw-r--r--source/Mesh.cpp130
-rw-r--r--source/Mesh.hpp98
-rw-r--r--source/Player.cpp39
-rw-r--r--source/Player.hpp17
-rw-r--r--source/Renderer.cpp130
-rw-r--r--source/Renderer.hpp47
-rw-r--r--source/SceneThings.cpp72
-rw-r--r--source/SceneThings.hpp39
-rw-r--r--source/Shader.hpp3
-rw-r--r--source/SmallVector.hpp8
-rw-r--r--source/Sprite.cpp63
-rw-r--r--source/Sprite.hpp50
-rw-r--r--source/VertexIndex.cpp84
-rw-r--r--source/VertexIndex.hpp71
-rw-r--r--source/World.cpp13
-rw-r--r--source/World.hpp8
-rw-r--r--source/YCombinator.hpp14
-rw-r--r--source/main.cpp304
47 files changed, 2753 insertions, 840 deletions
diff --git a/source/App.cpp b/source/App.cpp
index ac5b319..dc38342 100644
--- a/source/App.cpp
+++ b/source/App.cpp
@@ -2,44 +2,102 @@
#include <utility>
-void App::Init() {
- if (mInitialized) return;
- mInitialized = true;
-
- mCurrentWorld = std::make_unique<GameWorld>();
- auto& worldRoot = mCurrentWorld->GetRoot();
+App::App() {
+ auto& worldRoot = mWorld.GetRoot();
constexpr int kPlayerCount = 2;
for (int i = 0; i < kPlayerCount; ++i) {
- auto player = new Player(mCurrentWorld.get(), 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
+
+ // TODO this renders nothing, fix
+ mGameCamera.Move(glm::vec3(0, 0, 1));
+ mGameCamera.LookAt(glm::vec3(0, 0, 0));
+ mGameCamera.SetHasPerspective(false);
+ mEditorCamera.Move(glm::vec3(0, 0, 1));
+ mEditorCamera.LookAt(glm::vec3(0, 0, 0));
+ mGameCamera.SetHasPerspective(false);
+}
+
+App::~App() {
+}
+
+bool App::IsGameRunning() const {
+ return mGameRunning;
+}
+
+void App::SetGameRunning(bool running) {
+ if (mGameRunning != running) {
+ mGameRunning = running;
+ if (running) {
+ mWorld.Awaken();
+ } else {
+ mWorld.Resleep();
+ }
}
+}
- mCurrentWorld->Awaken();
- mEditor = std::make_unique<EditorInstance>(this, mCurrentWorld.get());
+bool App::IsEditorVisible() const {
+ return mEditorVisible;
}
-void App::Shutdown() {
- if (!mInitialized) return;
- mInitialized = false;
- mEditor = nullptr;
- mCurrentWorld->Resleep();
- mCurrentWorld = nullptr;
- mPlayers.clear();
+void App::SetEditorVisible(bool visible) {
+ if (mEditorVisible != visible) {
+ mEditorVisible = visible;
+ if (visible) {
+ if (mEditor == nullptr) {
+ mEditor = std::make_unique<EditorInstance>(this, &mWorld);
+ }
+ }
+ }
}
void App::Show() {
- if (mEditorShown) {
+ if (mEditorVisible) {
mEditor->Show();
}
}
void App::Update() {
+ if (IsGameRunning()) {
+ mWorld.Update();
+ }
}
-void App::Draw() {
- mCurrentWorld->Draw();
+void App::Draw(float currentTime, float deltaTime) {
+ Camera* camera;
+ if (IsGameRunning()) {
+ camera = &mGameCamera;
+ } else {
+ camera = &mEditorCamera;
+ }
+ mRenderer.BeginFrame(*camera, currentTime, deltaTime);
+
+ PodVector<GameObject*> stack;
+ stack.push_back(&mWorld.GetRoot());
+
+ while (!stack.empty()) {
+ auto obj = stack.back();
+ stack.pop_back();
+
+ for (auto child : obj->GetChildren()) {
+ stack.push_back(child);
+ }
+
+ auto renderObjects = obj->GetRenderObjects();
+ mRenderer.Draw(renderObjects.data(), renderObjects.size());
+ }
+
+ mRenderer.EndFrame();
}
void App::HandleMouse(int button, int action) {
@@ -60,15 +118,10 @@ void App::HandleKey(GLFWkeyboard* keyboard, int key, int action) {
switch (key) {
case GLFW_KEY_F3: {
if (action == GLFW_PRESS) {
- mEditorShown = !mEditorShown;
+ SetEditorVisible(!IsEditorVisible());
}
return;
}
-
- case GLFW_KEY_F11: {
- // TODO fullscreen
- return;
- }
}
for (auto& player : mPlayers) {
diff --git a/source/App.hpp b/source/App.hpp
index fbdbd43..26857db 100644
--- a/source/App.hpp
+++ b/source/App.hpp
@@ -1,8 +1,10 @@
#pragma once
+#include "Camera.hpp"
#include "EditorCore.hpp"
#include "Player.hpp"
#include "PodVector.hpp"
+#include "Renderer.hpp"
#include "World.hpp"
#define GLFW_INCLUDE_NONE
@@ -20,19 +22,32 @@ private:
std::deque<KeyCaptureCallback> mKeyCaptureCallbacks;
PodVector<Player*> mPlayers;
std::unique_ptr<EditorInstance> mEditor;
- std::unique_ptr<GameWorld> mCurrentWorld;
- bool mEditorShown = true;
- bool mInitialized = false;
+ GameWorld mWorld;
+ Camera mGameCamera;
+ Camera mEditorCamera;
+ Renderer mRenderer;
+ // NOTE: should only be true when mEditor != nullptr
+ bool mEditorVisible = false;
+ bool mGameRunning = false;
public:
- void Init();
- void Shutdown();
+ App();
+ ~App();
+
+ EditorInstance* GetEditor() { return mEditor.get(); }
+ GameWorld* GetWorld() { return &mWorld; }
+
+ 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();
+ void Draw(float currentTime, float deltaTime);
void HandleMouse(int button, int action);
void HandleMouseMotion(double xOff, double yOff);
diff --git a/source/AppConfig.hpp b/source/AppConfig.hpp
index 8d69fad..b6c1fab 100644
--- a/source/AppConfig.hpp
+++ b/source/AppConfig.hpp
@@ -8,6 +8,10 @@ constexpr std::string_view kAppName = "ProjectBrussel";
// Since kAppName is initialized by a C string literal, we know it's null termianted
constexpr const char* kAppNameC = kAppName.data();
+inline float mainWidnowWidth;
+inline float mainWindowHeight;
+inline float mainWindowAspectRatio;
+
// 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;
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index f37d4a9..b4b6e3b 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -1,12 +1,15 @@
target_sources(${PROJECT_NAME}
PRIVATE
App.cpp
- CpuMesh.cpp
+ Camera.cpp
+ CommonVertexIndex.cpp
EditorAccessories.cpp
EditorAttachmentImpl.cpp
+ EditorCommandPalette.cpp
EditorCore.cpp
EditorNotification.cpp
EditorUtils.cpp
+ FuzzyMatch.cpp
GameObject.cpp
GraphicsTags.cpp
Image.cpp
@@ -22,6 +25,7 @@ PRIVATE
Sprite.cpp
Texture.cpp
Uid.cpp
+ VertexIndex.cpp
World.cpp
)
diff --git a/source/Camera.cpp b/source/Camera.cpp
new file mode 100644
index 0000000..90acd8f
--- /dev/null
+++ b/source/Camera.cpp
@@ -0,0 +1,33 @@
+#include "Camera.hpp"
+
+#include "AppConfig.hpp"
+
+#include <glm/gtc/matrix_transform.hpp>
+
+void Camera::Move(glm::vec3 pos) {
+ this->pos = pos;
+}
+
+void Camera::LookAt(glm::vec3 pos) {
+ this->lookAt = pos;
+}
+
+void Camera::SetHasPerspective(bool perspective) {
+ this->perspective = perspective;
+}
+
+glm::mat4 Camera::CalcViewMatrix() const {
+ return glm::lookAt(pos, lookAt, glm::vec3(0, 1, 0));
+}
+
+glm::mat4 Camera::CalcProjectionMatrix() const {
+ if (perspective) {
+ return glm::perspective(90.0f, AppConfig::mainWindowAspectRatio, 0.1f, 1000.0f);
+ } else {
+ return glm::ortho(
+ pos.x - AppConfig::mainWidnowWidth / 2,
+ pos.x + AppConfig::mainWidnowWidth / 2,
+ pos.y - AppConfig::mainWindowHeight / 2,
+ pos.y + AppConfig::mainWindowHeight / 2);
+ }
+}
diff --git a/source/Camera.hpp b/source/Camera.hpp
new file mode 100644
index 0000000..d203622
--- /dev/null
+++ b/source/Camera.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <glm/glm.hpp>
+
+class Camera {
+public:
+ glm::vec3 pos;
+ glm::vec3 lookAt;
+ bool perspective = false;
+
+public:
+ void Move(glm::vec3 pos);
+ void LookAt(glm::vec3 pos);
+
+ bool HasPerspective() const { return perspective; }
+ void SetHasPerspective(bool perspective);
+
+ glm::mat4 CalcViewMatrix() const;
+ glm::mat4 CalcProjectionMatrix() const;
+};
diff --git a/source/CommonVertexIndex.cpp b/source/CommonVertexIndex.cpp
new file mode 100644
index 0000000..786274e
--- /dev/null
+++ b/source/CommonVertexIndex.cpp
@@ -0,0 +1,152 @@
+#include "CommonVertexIndex.hpp"
+
+template <class TNumber>
+static void AssignIndices(TNumber indices[6], TNumber startIdx) {
+ // Triangle #1
+ indices[0] = startIdx + 1; // Top right
+ indices[1] = startIdx + 0; // Top left
+ indices[2] = startIdx + 3; // Bottom left
+ // Triangle #2
+ indices[3] = startIdx + 1; // Top right
+ indices[4] = startIdx + 3; // Bottom left
+ indices[5] = startIdx + 2; // Bottom right
+}
+
+template <class TNumber>
+static void AssignIndices(TNumber indices[6], TNumber topLeft, TNumber topRight, TNumber bottomRight, TNumber bottomLeft) {
+ // Triangle #1
+ indices[0] = topRight;
+ indices[1] = topLeft;
+ indices[2] = bottomLeft;
+ // Triangle #2
+ indices[3] = topRight;
+ indices[4] = bottomLeft;
+ indices[5] = bottomRight;
+}
+
+template <class TVertex>
+static void AssignPositions(TVertex vertices[4], const Rect<float>& rect) {
+ // Top left
+ vertices[0].x = rect.x0();
+ vertices[0].y = rect.y0();
+ // Top right
+ vertices[1].x = rect.x1();
+ vertices[1].y = rect.y0();
+ // Bottom right
+ vertices[2].x = rect.x1();
+ vertices[2].y = rect.y1();
+ // Bottom left
+ vertices[3].x = rect.x0();
+ vertices[3].y = rect.y1();
+}
+
+template <class TVertex>
+static void AssignPositions(TVertex vertices[4], glm::vec2 bl, glm::vec2 tr) {
+ // Top left
+ vertices[0].x = bl.x;
+ vertices[0].y = tr.y;
+ // Top right
+ vertices[1].x = tr.x;
+ vertices[1].y = tr.y;
+ // Bottom right
+ vertices[2].x = tr.x;
+ vertices[2].y = bl.y;
+ // Bottom left
+ vertices[3].x = bl.x;
+ vertices[3].y = bl.y;
+}
+
+template <class TVertex>
+static void AssignDepths(TVertex vertices[4], float z) {
+ for (int i = 0; i < 4; ++i) {
+ auto& vert = vertices[i];
+ vert.z = z;
+ }
+}
+
+template <class TVertex>
+static void AssignTexCoords(TVertex vertices[4], const Subregion& texcoords) {
+ // Top left
+ vertices[0].u = texcoords.u0;
+ vertices[0].v = texcoords.v1;
+ // Top right
+ vertices[1].u = texcoords.u1;
+ vertices[1].v = texcoords.v1;
+ // Bottom right
+ vertices[2].u = texcoords.u1;
+ vertices[2].v = texcoords.v0;
+ // Bottom left
+ vertices[3].u = texcoords.u0;
+ vertices[3].v = texcoords.v0;
+}
+
+template <class TVertex>
+static void AssignColors(TVertex vertices[4], RgbaColor color) {
+ for (int i = 0; i < 4; ++i) {
+ auto& vert = vertices[i];
+ vert.r = color.r;
+ vert.g = color.g;
+ vert.b = color.b;
+ vert.a = color.a;
+ }
+}
+
+void Index_U16::Assign(uint16_t indices[6], uint16_t startIdx) {
+ ::AssignIndices(indices, startIdx);
+}
+
+void Index_U16::Assign(uint16_t indices[6], uint16_t startIdx, uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft) {
+ ::AssignIndices<uint16_t>(indices, startIdx + topLeft, startIdx + topRight, startIdx + bottomRight, startIdx + bottomLeft);
+}
+
+void Index_U16::Assign(uint16_t indices[6], uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft) {
+ ::AssignIndices<uint16_t>(indices, topLeft, topRight, bottomRight, bottomLeft);
+}
+
+void Index_U32::Assign(uint32_t indices[6], uint32_t startIdx) {
+ ::AssignIndices(indices, startIdx);
+}
+
+void Index_U32::Assign(uint32_t indices[6], uint32_t startIdx, uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft) {
+ ::AssignIndices<uint32_t>(indices, startIdx + topLeft, startIdx + topRight, startIdx + bottomRight, startIdx + bottomLeft);
+}
+
+void Index_U32::Assign(uint32_t indices[6], uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft) {
+ ::AssignIndices<uint32_t>(indices, topLeft, topRight, bottomRight, bottomLeft);
+}
+
+void Vertex_PC::Assign(Vertex_PC vertices[4], const Rect<float>& rect) {
+ ::AssignPositions(vertices, rect);
+}
+
+void Vertex_PC::Assign(Vertex_PC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight) {
+ ::AssignPositions(vertices, bottomLeft, topRight);
+}
+
+void Vertex_PC::Assign(Vertex_PC vertices[4], float z) {
+ ::AssignDepths(vertices, z);
+}
+
+void Vertex_PC::Assign(Vertex_PC vertices[4], RgbaColor color) {
+ ::AssignColors(vertices, color);
+}
+
+void Vertex_PTC::Assign(Vertex_PTC vertices[4], const Rect<float>& rect) {
+ ::AssignPositions(vertices, rect);
+}
+
+void Vertex_PTC::Assign(Vertex_PTC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight) {
+ ::AssignPositions(vertices, bottomLeft, topRight);
+}
+
+void Vertex_PTC::Assign(Vertex_PTC vertices[4], float z) {
+ ::AssignDepths(vertices, z);
+}
+
+void Vertex_PTC::Assign(Vertex_PTC vertices[4], const Subregion& texcoords) {
+ ::AssignTexCoords(vertices, texcoords);
+}
+
+void Vertex_PTC::Assign(Vertex_PTC vertices[4], RgbaColor color) {
+ ::AssignColors(vertices, color);
+}
diff --git a/source/CommonVertexIndex.hpp b/source/CommonVertexIndex.hpp
new file mode 100644
index 0000000..c15e658
--- /dev/null
+++ b/source/CommonVertexIndex.hpp
@@ -0,0 +1,70 @@
+#pragma once
+
+#include "Color.hpp"
+#include "Rect.hpp"
+#include "Texture.hpp"
+
+#include <cstdint>
+
+// Suffixes:
+// - _P_osition
+// - _T_exture coordiantes
+// - _C_olor
+// - _N_ormal
+// When an number is attached to some suffix, it means there are N number of this element
+
+struct Index_U16 {
+ uint16_t value;
+
+ static void Assign(uint16_t indices[6], uint16_t startIdx);
+ static void Assign(uint16_t indices[6], uint16_t startIdx, uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft);
+ static void Assign(uint16_t indices[6], uint16_t topLeft, uint16_t topRight, uint16_t bottomRight, uint16_t bottomLeft);
+};
+
+struct Index_U32 {
+ uint32_t value;
+
+ static void Assign(uint32_t indices[6], uint32_t startIdx);
+ static void Assign(uint32_t indices[6], uint32_t startIdx, uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft);
+ static void Assign(uint32_t indices[6], uint32_t topLeft, uint32_t topRight, uint32_t bottomRight, uint32_t bottomLeft);
+};
+
+struct Vertex_PC {
+ float x, y, z;
+ uint8_t r, g, b, a;
+
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PC vertices[4], const Rect<float>& rect);
+ /// Assign position in regular cartesian coordinate space (x increases from left to right, y increases from top to bottom).
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight);
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PC vertices[4], float z);
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PC vertices[4], RgbaColor color);
+};
+
+struct Vertex_PTC {
+ float x, y, z;
+ float u, v;
+ uint8_t r, g, b, a;
+
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PTC vertices[4], const Rect<float>& rect);
+ /// Assign position in regular cartesian coordinate space (x increases from left to right, y increases from top to bottom).
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PTC vertices[4], glm::vec2 bottomLeft, glm::vec2 topRight);
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PTC vertices[4], float z);
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PTC vertices[4], const Subregion& uvs);
+ /// Assumes the 4 vertices come in TL, TR, BR, BL order.
+ static void Assign(Vertex_PTC vertices[4], RgbaColor color);
+};
+
+struct Vertex_PTNC {
+ float x, y, z;
+ float nx, ny, nz;
+ float u, v;
+ uint8_t r, g, b, a;
+};
diff --git a/source/CpuMesh.cpp b/source/CpuMesh.cpp
deleted file mode 100644
index 15b0f54..0000000
--- a/source/CpuMesh.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-#include "CpuMesh.hpp"
-
-#include <cstring>
-
-StandardCpuMesh::StandardCpuMesh()
- : mGpuMesh(new GpuMesh()) {
- mGpuMesh->vertFormat = gVformatStandard;
- mGpuMesh->vertBufBindings.SetBinding(0, new GpuVertexBuffer());
- mGpuMesh->vertBufBindings.SetBinding(1, new GpuVertexBuffer());
- mGpuMesh->indexBuf.Attach(new GpuIndexBuffer());
-}
-
-StandardCpuMesh::~StandardCpuMesh() {
- delete mData;
-}
-
-void StandardCpuMesh::CreateCpuData() {
- if (!mData) {
- mData = new StandardCpuMeshData();
- }
-}
-
-GpuVertexBuffer* StandardCpuMesh::GetPosBuffer() const {
- return mGpuMesh->vertBufBindings.bindings[0].Get();
-}
-
-GpuVertexBuffer* StandardCpuMesh::GetExtraBuffer() const {
- return mGpuMesh->vertBufBindings.bindings[1].Get();
-}
-
-bool StandardCpuMesh::UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex) {
- if (mData) {
- std::memcpy(&mData->vertPositions[startVertIndex], pos, count * sizeof(glm::vec3));
- }
- auto posBuf = GetPosBuffer();
- glBindBuffer(GL_ARRAY_BUFFER, posBuf->handle);
- glBufferSubData(GL_ARRAY_BUFFER, startVertIndex * mGpuMesh->vertFormat->vertexSize, count * sizeof(glm::vec3), pos);
- return true;
-}
-
-bool StandardCpuMesh::UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex) {
- if (!mData) return false;
- // TODO
-}
-
-bool StandardCpuMesh::UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex) {
- if (!mData) return false;
- // TODO
-}
-
-bool StandardCpuMesh::UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex) {
- if (!mData) return false;
- // TODO
-}
diff --git a/source/CpuMesh.hpp b/source/CpuMesh.hpp
deleted file mode 100644
index 9fcb00c..0000000
--- a/source/CpuMesh.hpp
+++ /dev/null
@@ -1,51 +0,0 @@
-#pragma once
-
-#include "Color.hpp"
-#include "Mesh.hpp"
-#include "PodVector.hpp"
-#include "RcPtr.hpp"
-
-#include <cstddef>
-#include <cstdint>
-#include <glm/glm.hpp>
-#include <memory>
-
-struct StandardVertex {
- float x, y, z;
- float u, v;
- uint8_t r, g, b, a;
-};
-
-struct StandardVertexExtra {
- float u, v;
- uint8_t r, g, b, a;
-};
-
-class StandardCpuMeshData {
-public:
- PodVector<glm::vec3> vertPositions;
- PodVector<StandardVertexExtra> vertExtra;
- PodVector<uint32_t> index;
- size_t vertexCount;
- size_t triangleCount;
-};
-
-class StandardCpuMesh {
-private:
- StandardCpuMeshData* mData = nullptr;
- RcPtr<GpuMesh> mGpuMesh;
-
-public:
- StandardCpuMesh();
- ~StandardCpuMesh();
-
- GpuVertexBuffer* GetPosBuffer() const;
- GpuVertexBuffer* GetExtraBuffer() const;
- GpuMesh* GetGpuMesh() const { return mGpuMesh.Get(); }
-
- void CreateCpuData();
- bool UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex);
- bool UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex);
- bool UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex);
- bool UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex);
-};
diff --git a/source/EditorAttachmentImpl.cpp b/source/EditorAttachmentImpl.cpp
index 75d93cb..5193f84 100644
--- a/source/EditorAttachmentImpl.cpp
+++ b/source/EditorAttachmentImpl.cpp
@@ -7,14 +7,14 @@ EditorAttachment::EditorAttachment() {
std::unique_ptr<EditorAttachment> EaGameObject::Create(GameObject* object) {
EditorAttachment* result;
- using namespace Tags;
- switch (object->GetTypeTag()) {
- case GOT_Player: result = new EaPlayer(); break;
- case GOT_LevelWrapper: result = new EaLevelWrapper(); break;
+ 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 EditorAttachment(); break;
}
- result->name = NameOf(object->GetTypeTag());
+ result->name = GameObject::ToString(kind);
return std::unique_ptr<EditorAttachment>(result);
}
diff --git a/source/EditorAttachmentImpl.hpp b/source/EditorAttachmentImpl.hpp
index 258e987..e9dbe6f 100644
--- a/source/EditorAttachmentImpl.hpp
+++ b/source/EditorAttachmentImpl.hpp
@@ -2,7 +2,9 @@
#include "EditorAttachment.hpp"
#include "GameObject.hpp"
+#include "Material.hpp"
#include "Player.hpp"
+#include "Sprite.hpp"
#include <memory>
@@ -13,6 +15,8 @@ public:
class EaPlayer : public EditorAttachment {
public:
+ RcPtr<IresSpritesheet> confSprite;
+ RcPtr<IresMaterial> confMaterial;
};
class EaLevelWrapper : public EditorAttachment {
@@ -24,14 +28,3 @@ public:
std::string nameEditingScratch;
bool isEditingName = false;
};
-
-class EaShader : public EditorAttachment {
-public:
- Shader* shader;
-};
-
-class EaMaterial : public EditorAttachment {
-public:
- std::string editingScratch;
- bool isEditingName = false;
-};
diff --git a/source/EditorCommandPalette.cpp b/source/EditorCommandPalette.cpp
new file mode 100644
index 0000000..02ff65a
--- /dev/null
+++ b/source/EditorCommandPalette.cpp
@@ -0,0 +1,704 @@
+#include "EditorCommandPalette.hpp"
+
+#include "FuzzyMatch.hpp"
+
+#include <imgui.h>
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <utility>
+
+#define IMGUI_DEFINE_MATH_OPERATORS
+#include <imgui_internal.h>
+
+namespace ImCmd {
+// =================================================================
+// Private forward decls
+// =================================================================
+
+struct StackFrame;
+class ExecutionManager;
+
+struct SearchResult;
+class SearchManager;
+
+struct CommandOperationRegister;
+struct CommandOperationUnregister;
+struct CommandOperation;
+struct Context;
+
+struct ItemExtraData;
+struct Instance;
+
+// =================================================================
+// Private interface
+// =================================================================
+
+struct StackFrame {
+ std::vector<std::string> Options;
+ int SelectedOption = -1;
+};
+
+class ExecutionManager {
+private:
+ Instance* m_Instance;
+ Command* m_ExecutingCommand = nullptr;
+ std::vector<StackFrame> m_CallStack;
+
+public:
+ ExecutionManager(Instance& instance)
+ : m_Instance{ &instance } {}
+
+ int GetItemCount() const;
+ const char* GetItem(int idx) const;
+ void SelectItem(int idx);
+
+ void PushOptions(std::vector<std::string> options);
+};
+
+struct SearchResult {
+ int ItemIndex;
+ int Score;
+ int MatchCount;
+ uint8_t Matches[32];
+};
+
+class SearchManager {
+private:
+ Instance* m_Instance;
+
+public:
+ std::vector<SearchResult> SearchResults;
+ char SearchText[std::numeric_limits<uint8_t>::max() + 1];
+
+public:
+ SearchManager(Instance& instance)
+ : m_Instance{ &instance } {
+ std::memset(SearchText, 0, sizeof(SearchText));
+ }
+
+ int GetItemCount() const;
+ const char* GetItem(int idx) const;
+
+ bool IsActive() const;
+
+ void SetSearchText(const char* text);
+ void ClearSearchText();
+ void RefreshSearchResults();
+};
+
+struct CommandOperationRegister {
+ Command Candidate;
+};
+
+struct CommandOperationUnregister {
+ const char* Name;
+};
+
+struct CommandOperation {
+ enum OpType {
+ OpType_Register,
+ OpType_Unregister,
+ };
+
+ OpType Type;
+ int Index;
+};
+
+struct Context {
+ ImGuiStorage Instances;
+ Instance* CurrentCommandPalette = nullptr;
+ std::vector<Command> Commands;
+ std::vector<CommandOperationRegister> PendingRegisterOps;
+ std::vector<CommandOperationUnregister> PendingUnregisterOps;
+ std::vector<CommandOperation> PendingOps;
+ ImFont* Fonts[ImCmdTextType_COUNT] = {};
+ ImU32 FontColors[ImCmdTextType_COUNT] = {};
+ int CommandStorageLocks = 0;
+ bool HasFontColorOverride[ImCmdTextType_COUNT] = {};
+ bool IsExecuting = false;
+ bool IsTerminating = false;
+
+ struct
+ {
+ bool ItemSelected = false;
+ } LastCommandPaletteStatus;
+
+ struct
+ {
+ const char* NewSearchText = nullptr;
+ bool FocusSearchBox = false;
+ } NextCommandPaletteActions;
+
+ void RegisterCommand(Command command) {
+ auto location = std::lower_bound(
+ Commands.begin(),
+ Commands.end(),
+ command,
+ [](const Command& a, const Command& b) -> bool {
+ return strcmp(a.Name, b.Name) < 0;
+ });
+ Commands.insert(location, std::move(command));
+ }
+
+ bool UnregisterCommand(const char* name) {
+ struct Comparator {
+ bool operator()(const Command& command, const char* str) const {
+ return strcmp(command.Name, str) < 0;
+ }
+
+ bool operator()(const char* str, const Command& command) const {
+ return strcmp(str, command.Name) < 0;
+ }
+ };
+
+ auto range = std::equal_range(Commands.begin(), Commands.end(), name, Comparator{});
+ Commands.erase(range.first, range.second);
+
+ return range.first != range.second;
+ }
+
+ bool CommitOps() {
+ if (IsCommandStorageLocked()) {
+ return false;
+ }
+
+ for (auto& operation : PendingOps) {
+ switch (operation.Type) {
+ case CommandOperation::OpType_Register: {
+ auto& op = PendingRegisterOps[operation.Index];
+ RegisterCommand(std::move(op.Candidate));
+ } break;
+
+ case CommandOperation::OpType_Unregister: {
+ auto& op = PendingUnregisterOps[operation.Index];
+ UnregisterCommand(op.Name);
+ } break;
+ }
+ }
+
+ bool had_action = !PendingOps.empty();
+ PendingRegisterOps.clear();
+ PendingUnregisterOps.clear();
+ PendingOps.clear();
+
+ return had_action;
+ }
+
+ bool IsCommandStorageLocked() const {
+ return CommandStorageLocks > 0;
+ }
+};
+
+struct ItemExtraData {
+ bool Hovered = false;
+ bool Held = false;
+};
+
+struct Instance {
+ ExecutionManager Session;
+ SearchManager Search;
+ std::vector<ItemExtraData> ExtraData;
+
+ int CurrentSelectedItem = 0;
+
+ struct
+ {
+ bool RefreshSearch = false;
+ bool ClearSearch = false;
+ } PendingActions;
+
+ Instance()
+ : Session(*this)
+ , Search(*this) {}
+};
+
+static Context gContext;
+
+// =================================================================
+// Private implementation
+// =================================================================
+
+int ExecutionManager::GetItemCount() const {
+ if (m_ExecutingCommand) {
+ return static_cast<int>(m_CallStack.back().Options.size());
+ } else {
+ return static_cast<int>(gContext.Commands.size());
+ }
+}
+
+const char* ExecutionManager::GetItem(int idx) const {
+ if (m_ExecutingCommand) {
+ return m_CallStack.back().Options[idx].c_str();
+ } else {
+ return gContext.Commands[idx].Name;
+ }
+}
+
+template <class... Ts>
+static void InvokeSafe(const std::function<void(Ts...)>& func, Ts... args) {
+ if (func) {
+ func(std::forward<Ts>(args)...);
+ }
+}
+
+void ExecutionManager::SelectItem(int idx) {
+ auto cmd = m_ExecutingCommand;
+ size_t initial_call_stack_height = m_CallStack.size();
+ if (cmd == nullptr) {
+ cmd = m_ExecutingCommand = &gContext.Commands[idx];
+ ++gContext.CommandStorageLocks;
+
+ gContext.IsExecuting = true;
+ InvokeSafe(m_ExecutingCommand->InitialCallback); // Calls ::Prompt()
+ gContext.IsExecuting = false;
+ } else {
+ m_CallStack.back().SelectedOption = idx;
+
+ gContext.IsExecuting = true;
+ InvokeSafe(cmd->SubsequentCallback, idx); // Calls ::Prompt()
+ gContext.IsExecuting = false;
+ }
+
+ size_t final_call_stack_height = m_CallStack.size();
+ if (initial_call_stack_height == final_call_stack_height) {
+
+ gContext.IsTerminating = true;
+ InvokeSafe(m_ExecutingCommand->TerminatingCallback); // Shouldn't call ::Prompt()
+ gContext.IsTerminating = false;
+
+ m_ExecutingCommand = nullptr;
+ m_CallStack.clear();
+ --gContext.CommandStorageLocks;
+
+ // If the executed command involved subcommands...
+ if (final_call_stack_height > 0) {
+ m_Instance->PendingActions.ClearSearch = true;
+ m_Instance->CurrentSelectedItem = 0;
+ }
+
+ gContext.LastCommandPaletteStatus.ItemSelected = true;
+ } else {
+ // Something new is prompted
+ // It doesn't make sense for "current selected item" to persists through completely different set of options
+ m_Instance->PendingActions.ClearSearch = true;
+ m_Instance->CurrentSelectedItem = 0;
+ }
+}
+
+void ExecutionManager::PushOptions(std::vector<std::string> options) {
+ m_CallStack.push_back({});
+ auto& frame = m_CallStack.back();
+
+ frame.Options = std::move(options);
+
+ m_Instance->PendingActions.ClearSearch = true;
+}
+
+int SearchManager::GetItemCount() const {
+ return static_cast<int>(SearchResults.size());
+}
+
+const char* SearchManager::GetItem(int idx) const {
+ int actualIdx = SearchResults[idx].ItemIndex;
+ return m_Instance->Session.GetItem(actualIdx);
+}
+
+bool SearchManager::IsActive() const {
+ return SearchText[0] != '\0';
+}
+
+void SearchManager::SetSearchText(const char* text) {
+ // Note: must detect clang first because clang-cl.exe defines both _MSC_VER and __clang__, but only accepts #pragma clang
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+# pragma warning(push)
+# pragma warning(disable : 4996)
+#endif
+ // Copy at most IM_ARRAYSIZE(SearchText) chars from `text` to `SearchText`
+ std::strncpy(SearchText, text, IM_ARRAYSIZE(SearchText));
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#elif defined(__clang__)
+# pragma clang diagnostic pop
+#elif defined(_MSC_VER)
+# pragma warning(pop)
+#endif
+ RefreshSearchResults();
+}
+
+void SearchManager::ClearSearchText() {
+ std::memset(SearchText, 0, IM_ARRAYSIZE(SearchText));
+ SearchResults.clear();
+}
+
+void SearchManager::RefreshSearchResults() {
+ m_Instance->CurrentSelectedItem = 0;
+ SearchResults.clear();
+
+ int item_count = m_Instance->Session.GetItemCount();
+ for (int i = 0; i < item_count; ++i) {
+ const char* text = m_Instance->Session.GetItem(i);
+ SearchResult result;
+ if (FuzzyMatch::Search(SearchText, text, result.Score, result.Matches, IM_ARRAYSIZE(result.Matches), result.MatchCount)) {
+ result.ItemIndex = i;
+ SearchResults.push_back(result);
+ }
+ }
+
+ std::sort(
+ SearchResults.begin(),
+ SearchResults.end(),
+ [](const SearchResult& a, const SearchResult& b) -> bool {
+ // We want the biggest element first
+ return a.Score > b.Score;
+ });
+}
+
+// =================================================================
+// API implementation
+// =================================================================
+
+void AddCommand(Command command) {
+ if (gContext.IsCommandStorageLocked()) {
+ gContext.PendingRegisterOps.push_back(CommandOperationRegister{ std::move(command) });
+ CommandOperation op;
+ op.Type = CommandOperation::OpType_Register;
+ op.Index = static_cast<int>(gContext.PendingRegisterOps.size()) - 1;
+ gContext.PendingOps.push_back(op);
+ } else {
+ gContext.RegisterCommand(std::move(command));
+ }
+
+ if (auto current = gContext.CurrentCommandPalette) {
+ current->PendingActions.RefreshSearch = true;
+ }
+}
+
+void RemoveCommand(const char* name) {
+ if (gContext.IsCommandStorageLocked()) {
+ gContext.PendingUnregisterOps.push_back(CommandOperationUnregister{ name });
+ CommandOperation op;
+ op.Type = CommandOperation::OpType_Unregister;
+ op.Index = static_cast<int>(gContext.PendingUnregisterOps.size()) - 1;
+ gContext.PendingOps.push_back(op);
+ } else {
+ gContext.UnregisterCommand(name);
+ }
+
+ if (auto current = gContext.CurrentCommandPalette) {
+ current->PendingActions.RefreshSearch = true;
+ }
+}
+
+void SetStyleFont(ImCmdTextType type, ImFont* font) {
+ gContext.Fonts[type] = font;
+}
+
+void SetStyleColor(ImCmdTextType type, ImU32 color) {
+ gContext.FontColors[type] = color;
+ gContext.HasFontColorOverride[type] = true;
+}
+
+void ClearStyleColor(ImCmdTextType type) {
+ gContext.HasFontColorOverride[type] = false;
+}
+
+void SetNextCommandPaletteSearch(const char* text) {
+ IM_ASSERT(text != nullptr);
+ gContext.NextCommandPaletteActions.NewSearchText = text;
+}
+
+void SetNextCommandPaletteSearchBoxFocused() {
+ gContext.NextCommandPaletteActions.FocusSearchBox = true;
+}
+
+void ShowCommandPalette(const char* name) {
+ auto& gi = *[&]() {
+ auto id = ImHashStr(name);
+ if (auto ptr = gContext.Instances.GetVoidPtr(id)) {
+ return reinterpret_cast<Instance*>(ptr);
+ } else {
+ auto instance = new Instance();
+ gContext.Instances.SetVoidPtr(id, instance);
+ return instance;
+ }
+ }();
+
+ float width = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x;
+ float search_result_window_height = 400.0f; // TODO config
+
+ // BEGIN this command palette
+ gContext.CurrentCommandPalette = &gi;
+ ImGui::PushID(name);
+
+ gContext.LastCommandPaletteStatus = {};
+
+ // BEGIN processing PendingActions
+ bool refresh_search = gi.PendingActions.RefreshSearch;
+ refresh_search |= gContext.CommitOps();
+
+ if (auto text = gContext.NextCommandPaletteActions.NewSearchText) {
+ refresh_search = false;
+ if (text[0] == '\0') {
+ gi.Search.ClearSearchText();
+ } else {
+ gi.Search.SetSearchText(text);
+ }
+ } else if (gi.PendingActions.ClearSearch) {
+ refresh_search = false;
+ gi.Search.ClearSearchText();
+ }
+
+ if (refresh_search) {
+ gi.Search.RefreshSearchResults();
+ }
+
+ gi.PendingActions = {};
+ // END procesisng PendingActions
+
+ if (gContext.NextCommandPaletteActions.FocusSearchBox) {
+ // 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("##SearchBox", gi.Search.SearchText, IM_ARRAYSIZE(gi.Search.SearchText))) {
+ // Search string updated, update search results
+ gi.Search.RefreshSearchResults();
+ }
+
+ ImGui::BeginChild("SearchResults", ImVec2(width, search_result_window_height));
+
+ auto window = ImGui::GetCurrentWindow();
+ auto draw_list = window->DrawList;
+
+ auto font_regular = gContext.Fonts[ImCmdTextType_Regular];
+ if (!font_regular) {
+ font_regular = ImGui::GetDrawListSharedData()->Font;
+ }
+ auto font_highlight = gContext.Fonts[ImCmdTextType_Highlight];
+ if (!font_highlight) {
+ font_highlight = ImGui::GetDrawListSharedData()->Font;
+ }
+
+ ImU32 text_color_regular;
+ ImU32 text_color_highlight;
+ if (gContext.HasFontColorOverride[ImCmdTextType_Regular]) {
+ text_color_regular = gContext.FontColors[ImCmdTextType_Regular];
+ } else {
+ text_color_regular = ImGui::GetColorU32(ImGuiCol_Text);
+ }
+ if (gContext.HasFontColorOverride[ImCmdTextType_Highlight]) {
+ text_color_highlight = gContext.FontColors[ImCmdTextType_Highlight];
+ } else {
+ text_color_highlight = ImGui::GetColorU32(ImGuiCol_Text);
+ }
+
+ auto item_hovered_color = ImGui::GetColorU32(ImGuiCol_HeaderHovered);
+ auto item_active_color = ImGui::GetColorU32(ImGuiCol_HeaderActive);
+ auto item_selected_color = ImGui::GetColorU32(ImGuiCol_Header);
+
+ int item_count;
+ if (gi.Search.IsActive()) {
+ item_count = gi.Search.GetItemCount();
+ } else {
+ item_count = gi.Session.GetItemCount();
+ }
+
+ if (gi.ExtraData.size() < item_count) {
+ gi.ExtraData.resize(item_count);
+ }
+
+ // Flag used to delay item selection until after the loop ends
+ bool select_focused_item = false;
+ for (int i = 0; i < item_count; ++i) {
+ auto id = window->GetID(static_cast<int>(i));
+
+ ImVec2 size{
+ ImGui::GetContentRegionAvail().x,
+ ImMax(font_regular->FontSize, font_highlight->FontSize),
+ };
+ ImRect rect{
+ window->DC.CursorPos,
+ window->DC.CursorPos + ImGui::CalcItemSize(size, 0.0f, 0.0f),
+ };
+
+ bool& hovered = gi.ExtraData[i].Hovered;
+ bool& held = gi.ExtraData[i].Held;
+ if (held && hovered) {
+ draw_list->AddRectFilled(rect.Min, rect.Max, item_active_color);
+ } else if (hovered) {
+ draw_list->AddRectFilled(rect.Min, rect.Max, item_hovered_color);
+ } else if (gi.CurrentSelectedItem == i) {
+ draw_list->AddRectFilled(rect.Min, rect.Max, item_selected_color);
+ }
+
+ if (gi.Search.IsActive()) {
+ // Iterating search results: draw text with highlights at matched chars
+
+ auto& search_result = gi.Search.SearchResults[i];
+ auto text = gi.Search.GetItem(i);
+
+ auto text_pos = window->DC.CursorPos;
+ int range_begin;
+ int range_end;
+ int last_range_end = 0;
+
+ auto DrawCurrentRange = [&]() {
+ if (range_begin != last_range_end) {
+ // Draw normal text between last highlighted range end and current highlighted range start
+ auto begin = text + last_range_end;
+ auto end = text + range_begin;
+ draw_list->AddText(text_pos, text_color_regular, begin, end);
+
+ auto segment_size = font_regular->CalcTextSizeA(font_regular->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end);
+ text_pos.x += segment_size.x;
+ }
+
+ auto begin = text + range_begin;
+ auto end = text + range_end;
+ draw_list->AddText(font_highlight, font_highlight->FontSize, text_pos, text_color_highlight, begin, end);
+
+ auto segment_size = font_highlight->CalcTextSizeA(font_highlight->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end);
+ text_pos.x += segment_size.x;
+ };
+
+ IM_ASSERT(search_result.MatchCount >= 1);
+ range_begin = search_result.Matches[0];
+ range_end = range_begin;
+
+ int last_char_idx = -1;
+ for (int j = 0; j < search_result.MatchCount; ++j) {
+ int char_idx = search_result.Matches[j];
+
+ if (char_idx == last_char_idx + 1) {
+ // These 2 indices are equal, extend our current range by 1
+ ++range_end;
+ } else {
+ DrawCurrentRange();
+ last_range_end = range_end;
+ range_begin = char_idx;
+ range_end = char_idx + 1;
+ }
+
+ last_char_idx = char_idx;
+ }
+
+ // Draw the remaining range (if any)
+ if (range_begin != range_end) {
+ DrawCurrentRange();
+ }
+
+ // Draw the text after the last range (if any)
+ draw_list->AddText(text_pos, text_color_regular, text + range_end); // Draw until \0
+ } else {
+ // Iterating everything else: draw text as-is, there is no highlights
+
+ auto text = gi.Session.GetItem(i);
+ auto text_pos = window->DC.CursorPos;
+ draw_list->AddText(text_pos, text_color_regular, text);
+ }
+
+ ImGui::ItemSize(rect);
+ if (!ImGui::ItemAdd(rect, id)) {
+ continue;
+ }
+ if (ImGui::ButtonBehavior(rect, id, &hovered, &held)) {
+ gi.CurrentSelectedItem = i;
+ select_focused_item = true;
+ }
+ }
+
+ if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) {
+ gi.CurrentSelectedItem = ImMax(gi.CurrentSelectedItem - 1, 0);
+ } else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) {
+ gi.CurrentSelectedItem = ImMin(gi.CurrentSelectedItem + 1, item_count - 1);
+ }
+ if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) || select_focused_item) {
+ if (gi.Search.IsActive() && !gi.Search.SearchResults.empty()) {
+ auto idx = gi.Search.SearchResults[gi.CurrentSelectedItem].ItemIndex;
+ gi.Session.SelectItem(idx);
+ } else {
+ gi.Session.SelectItem(gi.CurrentSelectedItem);
+ }
+ }
+
+ ImGui::EndChild();
+
+ gContext.NextCommandPaletteActions = {};
+
+ ImGui::PopID();
+ gContext.CurrentCommandPalette = nullptr;
+ // END this command palette
+}
+
+bool IsAnyItemSelected() {
+ return gContext.LastCommandPaletteStatus.ItemSelected;
+}
+
+void RemoveCache(const char* name) {
+ auto& instances = gContext.Instances;
+ auto id = ImHashStr(name);
+ if (auto ptr = instances.GetVoidPtr(id)) {
+ auto instance = reinterpret_cast<Instance*>(ptr);
+ instances.SetVoidPtr(id, nullptr);
+ delete instance;
+ }
+}
+
+void RemoveAllCaches() {
+ auto& instances = gContext.Instances;
+ for (auto& entry : instances.Data) {
+ auto instance = reinterpret_cast<Instance*>(entry.val_p);
+ entry.val_p = nullptr;
+ delete instance;
+ }
+ instances = {};
+}
+
+void SetNextWindowAffixedTop(ImGuiCond cond) {
+ auto viewport = ImGui::GetMainViewport()->Size;
+
+ // Center window horizontally, align top vertically
+ ImGui::SetNextWindowPos(ImVec2(viewport.x / 2, 0), cond, ImVec2(0.5f, 0.0f));
+}
+
+void ShowCommandPaletteWindow(const char* name, bool* p_open) {
+ auto viewport = ImGui::GetMainViewport()->Size;
+
+ SetNextWindowAffixedTop();
+ ImGui::SetNextWindowSize(ImVec2(viewport.x * 0.3f, 0.0f));
+ ImGui::Begin(name, nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar);
+
+ if (ImGui::IsWindowAppearing()) {
+ SetNextCommandPaletteSearchBoxFocused();
+ }
+
+ ShowCommandPalette(name);
+
+ if (IsAnyItemSelected()) {
+ *p_open = false;
+ }
+ if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
+ // Close popup when user unfocused the command palette window (clicking elsewhere)
+ *p_open = false;
+ }
+
+ ImGui::End();
+}
+
+void Prompt(std::vector<std::string> options) {
+ IM_ASSERT(gContext.CurrentCommandPalette != nullptr);
+ IM_ASSERT(gContext.IsExecuting);
+ IM_ASSERT(!gContext.IsTerminating);
+
+ auto& gi = *gContext.CurrentCommandPalette;
+ gi.Session.PushOptions(std::move(options));
+}
+} // namespace ImCmd
diff --git a/source/EditorCommandPalette.hpp b/source/EditorCommandPalette.hpp
new file mode 100644
index 0000000..1603f41
--- /dev/null
+++ b/source/EditorCommandPalette.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <imgui.h>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <vector>
+
+enum ImCmdTextType {
+ ImCmdTextType_Regular,
+ ImCmdTextType_Highlight,
+ ImCmdTextType_COUNT,
+};
+
+namespace ImCmd {
+struct Command {
+ const char* Name;
+ std::function<void()> InitialCallback;
+ std::function<void(int selected_option)> SubsequentCallback;
+ std::function<void()> TerminatingCallback;
+};
+
+// Command management
+void AddCommand(Command command);
+void RemoveCommand(const char* name);
+
+// Styling
+void SetStyleFont(ImCmdTextType type, ImFont* font);
+void SetStyleColor(ImCmdTextType type, ImU32 color);
+void ClearStyleColor(ImCmdTextType type); //< Clear the style color for the given type, defaulting to ImGuiCol_Text
+
+// Command palette widget
+void SetNextCommandPaletteSearch(const char* text);
+void SetNextCommandPaletteSearchBoxFocused();
+void ShowCommandPalette(const char* name);
+bool IsAnyItemSelected();
+
+void RemoveCache(const char* name);
+void RemoveAllCaches();
+
+// Command palette widget in a window helper
+void SetNextWindowAffixedTop(ImGuiCond cond = 0);
+void ShowCommandPaletteWindow(const char* name, bool* p_open);
+
+// Command responses, only call these in command callbacks (except TerminatingCallback)
+void Prompt(std::vector<std::string> options);
+
+} // namespace ImCmd
diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp
index 59da2b3..601a850 100644
--- a/source/EditorCore.cpp
+++ b/source/EditorCore.cpp
@@ -2,17 +2,20 @@
#include "App.hpp"
#include "AppConfig.hpp"
-#include "CpuMesh.hpp"
#include "EditorAccessories.hpp"
#include "EditorAttachmentImpl.hpp"
+#include "EditorCommandPalette.hpp"
#include "EditorNotification.hpp"
#include "EditorUtils.hpp"
-#include "GameObjectTags.hpp"
+#include "GameObject.hpp"
#include "Level.hpp"
#include "Macros.hpp"
#include "Mesh.hpp"
#include "Player.hpp"
+#include "SceneThings.hpp"
#include "ScopeGuard.hpp"
+#include "VertexIndex.hpp"
+#include "YCombinator.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
@@ -217,6 +220,84 @@ void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) {
return false;
});
}
+
+struct GobjTreeNodeShowInfo {
+ EditorInstance* in_editor;
+ GameObject* out_openPopup = nullptr;
+};
+
+void GobjTreeNode(GobjTreeNodeShowInfo& showInfo, GameObject* object) {
+ auto& inspector = showInfo.in_editor->GetInspector();
+
+ auto attachment = object->GetEditorAttachment();
+ if (!attachment) {
+ attachment = EaGameObject::Create(object).release();
+ object->SetEditorAttachment(attachment); // NOTE: takes ownership
+ }
+
+ ImGuiTreeNodeFlags flags =
+ ImGuiTreeNodeFlags_DefaultOpen |
+ ImGuiTreeNodeFlags_OpenOnDoubleClick |
+ ImGuiTreeNodeFlags_OpenOnArrow |
+ ImGuiTreeNodeFlags_SpanAvailWidth |
+ ImGuiTreeNodeFlags_NoTreePushOnOpen;
+ if (inspector.selectedItPtr == object) {
+ flags |= ImGuiTreeNodeFlags_Selected;
+ }
+
+ ImGui::PushID(reinterpret_cast<uintptr_t>(object));
+ // BEGIN tree node
+
+ bool opened = ImGui::TreeNodeEx(attachment->name.c_str(), flags);
+ if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
+ inspector.SelectTarget(EditorInspector::ITT_GameObject, object);
+ }
+ if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) &&
+ ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
+ {
+ showInfo.out_openPopup = object;
+ }
+
+ if (opened) {
+ ImGui::Indent();
+ for (auto& child : object->GetChildren()) {
+ GobjTreeNode(showInfo, child);
+ }
+ ImGui::Unindent();
+ }
+
+ // END tree node
+ ImGui::PopID();
+};
+
+#define GAMEOBJECT_CONSTRUCTOR(ClassName) [](GameWorld* world) -> GameObject* { return new ClassName(world); }
+struct CreatableGameObject {
+ GameObject* (*factory)(GameWorld*);
+ const char* name;
+ GameObject::Kind kind;
+} creatableGameObjects[] = {
+ {
+ .factory = GAMEOBJECT_CONSTRUCTOR(GameObject),
+ .name = "GameObject",
+ .kind = GameObject::KD_Generic,
+ },
+ {
+ .factory = GAMEOBJECT_CONSTRUCTOR(SimpleGeometryObject),
+ .name = "Simple Geometry",
+ .kind = GameObject::KD_SimpleGeometry,
+ },
+ {
+ .factory = GAMEOBJECT_CONSTRUCTOR(BuildingObject),
+ .name = "Building",
+ .kind = GameObject::KD_Building,
+ },
+ {
+ .factory = GAMEOBJECT_CONSTRUCTOR(LevelWrapperObject),
+ .name = "Level Wrapper",
+ .kind = GameObject::KD_LevelWrapper,
+ },
+};
+#undef GAMEOBJECT_CONSTRUCTOR
} // namespace ProjectBrussel_UNITY_ID
EditorInstance::EditorInstance(App* app, GameWorld* world)
@@ -228,44 +309,129 @@ EditorInstance::~EditorInstance() {
}
void EditorInstance::Show() {
- if (!mWorld) return;
+ using namespace ProjectBrussel_UNITY_ID;
+ using namespace Tags;
+
+ if (!mWorld) {
+ return;
+ }
auto& io = ImGui::GetIO();
- if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) {
- mEdContentBrowserVisible = !mEdContentBrowserVisible;
+
+ 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();
- ImGui::Begin("World properties");
- ShowWorldProperties();
- ImGui::End();
+ if (mWindowVisible_ImGuiDemo) {
+ ImGui::ShowDemoWindow(&mWindowVisible_ImGuiDemo);
+ }
- ImGui::Begin("World structure");
- ShowGameObjectInTree(&mWorld->GetRoot());
- ImGui::End();
+ if (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(GLFW_KEY_P, false)) {
+ mWindowVisible_CommandPalette = !mWindowVisible_CommandPalette;
+ }
+ if (mWindowVisible_CommandPalette) {
+ ImCmd::ShowCommandPaletteWindow("Command Palette", &mWindowVisible_CommandPalette);
+ }
- ImGui::Begin("Inspector");
- switch (mEdInspector.selectedItt) {
- case EditorInspector::ITT_GameObject: {
- ShowInspector(static_cast<GameObject*>(mEdInspector.selectedItPtr));
- } break;
+ if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) {
+ mWindowVisible_ContentBrowser = !mWindowVisible_ContentBrowser;
+ }
+ if (mWindowVisible_ContentBrowser) {
+ mEdContentBrowser.Show(&mWindowVisible_ContentBrowser);
+ }
- case EditorInspector::ITT_Ires: {
- auto ires = static_cast<IresObject*>(mEdInspector.selectedItPtr);
- ShowInspector(ires);
- } break;
+ if (mWindowVisible_Inspector) {
+ ImGui::Begin("Inspector");
+ switch (mEdInspector.selectedItt) {
+ case EditorInspector::ITT_GameObject: {
+ ShowInspector(static_cast<GameObject*>(mEdInspector.selectedItPtr));
+ } break;
- case EditorInspector::ITT_None: break;
+ case EditorInspector::ITT_Ires: {
+ auto ires = static_cast<IresObject*>(mEdInspector.selectedItPtr);
+ ShowInspector(ires);
+ } break;
+
+ case EditorInspector::ITT_None: break;
+ }
+ ImGui::End();
}
- ImGui::End();
- if (mEdContentBrowserVisible) {
- mEdContentBrowser.Show(&mEdContentBrowserVisible);
+ if (mWindowVisible_WorldProperties) {
+ ImGui::Begin("World properties");
+ ShowWorldProperties();
+ ImGui::End();
+ }
+
+ if (mWindowVisible_WorldStructure) {
+ ImGui::Begin("World structure");
+ {
+ GobjTreeNodeShowInfo showInfo{
+ .in_editor = this,
+ };
+ GobjTreeNode(showInfo, &mWorld->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(mWorld);
+ mPopupCurrent_GameObject->AddChild(object);
+ }
+ }
+ ImGui::EndMenu();
+ }
+ ImGui::Separator();
+ if (ImGui::MenuItem("Remove")) {
+ // TODO
+ }
+ ImGui::EndPopup();
+ }
+ }
+ ImGui::End();
}
ShowSpriteViewer();
+
+ 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.");
+ }
+ }
}
// TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism
@@ -290,17 +456,42 @@ void EditorInstance::ShowInspector(GameObject* object) {
}
};
- auto type = object->GetTypeTag();
+ auto type = object->GetKind();
switch (type) {
- case Tags::GOT_Player: {
+ case GameObject::KD_Player: {
ShowFields();
ImGui::Separator();
auto player = static_cast<Player*>(object);
+ auto ea = static_cast<EaPlayer*>(player->GetEditorAttachment());
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(IresObject::ToString(IresObject::KD_Spritesheet).data())) {
+ auto spritesheet = *static_cast<IresSpritesheet* const*>(payload->Data);
+ ea->confSprite.Attach(spritesheet);
+ player->sprite.SetDefinition(spritesheet->GetInstance());
+ }
+ ImGui::EndDragDropTarget();
+ }
+
+ ImGui::TextUnformatted("Material: ");
+ ImGui::SameLine();
+ IresObject::ShowReferenceSafe(*this, ea->confMaterial.Get());
+ if (ImGui::BeginDragDropTarget()) {
+ if (auto payload = ImGui::AcceptDragDropPayload(IresObject::ToString(IresObject::KD_Material).data())) {
+ auto material = *static_cast<IresMaterial* const*>(payload->Data);
+ ea->confMaterial.Attach(material);
+ player->SetMaterial(material->GetInstance());
+ }
+ ImGui::EndDragDropTarget();
+ }
+
if (ImGui::Button("Load config")) {
bool success = player->LoadFromFile();
if (success) {
@@ -340,49 +531,37 @@ void EditorInstance::ShowInspector(GameObject* object) {
}
} break;
- case Tags::GOT_LevelWrapper: {
+ case GameObject::KD_SimpleGeometry: {
ShowFields();
ImGui::Separator();
- auto lwo = static_cast<LevelWrapperObject*>(object);
+ auto sg = static_cast<SimpleGeometryObject*>(object);
// TODO
} break;
- default: {
+ case GameObject::KD_Building: {
ShowFields();
- } break;
- }
-}
+ ImGui::Separator();
-void EditorInstance::ShowGameObjectInTree(GameObject* object) {
- auto attachment = object->GetEditorAttachment();
- if (!attachment) {
- attachment = EaGameObject::Create(object).release();
- object->SetEditorAttachment(attachment); // NOTE: takes ownership
- }
+ auto b = static_cast<BuildingObject*>(object);
+ // TODO
+ } break;
- ImGuiTreeNodeFlags flags = 0;
- flags |= ImGuiTreeNodeFlags_DefaultOpen;
- flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick;
- flags |= ImGuiTreeNodeFlags_OpenOnArrow;
- flags |= ImGuiTreeNodeFlags_SpanAvailWidth;
- if (mEdInspector.selectedItPtr == object) {
- flags |= ImGuiTreeNodeFlags_Selected;
- }
+ case GameObject::KD_LevelWrapper: {
+ ShowFields();
+ ImGui::Separator();
- if (ImGui::TreeNodeEx(attachment->name.c_str(), flags)) {
- if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
- mEdInspector.SelectTarget(EditorInspector::ITT_GameObject, object);
- }
+ auto lwo = static_cast<LevelWrapperObject*>(object);
+ // TODO
+ } break;
- for (auto& child : object->GetChildren()) {
- ShowGameObjectInTree(child);
- }
- ImGui::TreePop();
+ default: {
+ ShowFields();
+ } break;
}
}
-void EditorInstance::OpenSpriteViewer(Sprite* sprite) {
+void EditorInstance::OpenSpriteViewer(SpriteDefinition* sprite) {
mSpriteView_Instance.Attach(sprite);
mSpriteView_Frame = 0;
mSpriteView_OpenNextFrame = true;
diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp
index 13dc2d0..b473b2e 100644
--- a/source/EditorCore.hpp
+++ b/source/EditorCore.hpp
@@ -10,6 +10,7 @@
#include <memory>
#include <string>
+// TODO move inspector drawing to this class
struct EditorInspector {
enum TargetType {
ITT_GameObject,
@@ -58,12 +59,18 @@ class EditorInstance {
private:
App* mApp;
GameWorld* mWorld;
- RcPtr<Sprite> mSpriteView_Instance;
+ GameObject* mPopupCurrent_GameObject = nullptr;
+ RcPtr<SpriteDefinition> mSpriteView_Instance;
EditorInspector mEdInspector;
EditorContentBrowser mEdContentBrowser;
int mSpriteView_Frame;
bool mSpriteView_OpenNextFrame = false;
- bool mEdContentBrowserVisible = 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;
public:
EditorInstance(App* app, GameWorld* world);
@@ -74,7 +81,7 @@ public:
EditorInspector& GetInspector() { return mEdInspector; }
EditorContentBrowser& GetContentBrowser() { return mEdContentBrowser; }
- void OpenSpriteViewer(Sprite* sprite);
+ void OpenSpriteViewer(SpriteDefinition* sprite);
private:
void ShowWorldProperties();
@@ -82,7 +89,5 @@ private:
void ShowInspector(IresObject* ires);
void ShowInspector(GameObject* object);
- void ShowGameObjectInTree(GameObject* object);
-
void ShowSpriteViewer();
};
diff --git a/source/EditorNotification.cpp b/source/EditorNotification.cpp
index 1fdd93a..e4a869e 100644
--- a/source/EditorNotification.cpp
+++ b/source/EditorNotification.cpp
@@ -3,7 +3,9 @@
#include "Macros.hpp"
-// #include <IconsFontAwesome.h>
+#define IMGUI_DEFINE_MATH_OPERATORS
+#include <imgui_internal.h>
+
#include <chrono>
#include <cstdarg>
#include <cstdio>
@@ -11,163 +13,163 @@
#include <vector>
ImGuiToast::ImGuiToast(ImGuiToastType type, int dismissTime) {
- IM_ASSERT(type < ImGuiToastType_COUNT);
+ IM_ASSERT(type < ImGuiToastType_COUNT);
- mType = type;
- mDismissTime = dismissTime;
+ mType = type;
+ mDismissTime = dismissTime;
- using namespace std::chrono;
- auto timeStamp = system_clock::now().time_since_epoch();
- mCreationTime = duration_cast<milliseconds>(timeStamp).count();
+ using namespace std::chrono;
+ auto timeStamp = system_clock::now().time_since_epoch();
+ mCreationTime = duration_cast<milliseconds>(timeStamp).count();
- memset(mTitle, 0, sizeof(mTitle));
- memset(mContent, 0, sizeof(mContent));
+ 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(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);
- }
+ : 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);
- }
+ 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);
- }
+ 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;
+ IM_ASSERT(type < ImGuiToastType_COUNT);
+ mType = type;
}
const char* ImGuiToast::GetTitle() {
- return mTitle;
+ 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;
- }
- }
+ 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;
+ return mTitle;
}
ImGuiToastType ImGuiToast::GetType() {
- return mType;
+ 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();
+ 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;
+ 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;
+ // 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;
+ 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;
+ case ImGuiToastType_COUNT: UNREACHABLE;
+ }
+ return nullptr;
}
const char* ImGuiToast::GetContent() {
- return this->mContent;
+ return this->mContent;
}
uint64_t ImGuiToast::GetElapsedTime() {
- using namespace std::chrono;
- auto timeStamp = system_clock::now().time_since_epoch();
- auto timeStampI = duration_cast<milliseconds>(timeStamp).count();
- return timeStampI - mCreationTime;
+ using namespace std::chrono;
+ auto timeStamp = system_clock::now().time_since_epoch();
+ auto timeStampI = duration_cast<milliseconds>(timeStamp).count();
+ return timeStampI - mCreationTime;
}
ImGuiToastPhase ImGuiToast::GetPhase() {
- const auto elapsed = GetElapsedTime();
+ 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;
- }
+ 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();
+ 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;
- }
+ 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;
+ return 1.0f * kNotifyOpacity;
}
void ImGuiToast::SetTitle(const char* format, va_list args) {
- vsnprintf(mTitle, sizeof(mTitle), format, args);
+ vsnprintf(mTitle, sizeof(mTitle), format, args);
}
void ImGuiToast::SetContent(const char* format, va_list args) {
- vsnprintf(mContent, sizeof(mContent), format, args);
+ vsnprintf(mContent, sizeof(mContent), format, args);
}
namespace ImGui {
@@ -175,100 +177,101 @@ static std::vector<ImGuiToast> notifications;
}
static bool IsNullOrEmpty(const char* str) {
- return !str || !strlen(str);
+ return !str || !strlen(str);
}
void ImGui::AddNotification(ImGuiToast toast) {
- notifications.push_back(std::move(toast));
+ notifications.push_back(std::move(toast));
}
void ImGui::RemoveNotification(int index) {
- notifications.erase(notifications.begin() + index);
+ notifications.erase(notifications.begin() + index);
}
void ImGui::ShowNotifications() {
- auto vpSize = GetMainViewport()->Size;
+ auto vpSize = GetMainViewport()->Size;
- float height = 0.0f;
- for (auto i = 0; i < notifications.size(); i++) {
- auto* currentToast = &notifications[i];
+ float height = 0.0f;
+ for (auto i = 0; i < notifications.size(); i++) {
+ auto* currentToast = &notifications[i];
- // Remove toast if expired
- if (currentToast->GetPhase() == ImGuiToastPhase_Expired) {
- RemoveNotification(i);
- continue;
- }
+ // 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
+ // 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;
+ // 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);
+ // 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);
+ 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
+ // 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;
+ bool wasTitleRendered = false;
- // If an icon is set
- if (!::IsNullOrEmpty(icon)) {
- // Render icon text
- PushStyleColor(ImGuiCol_Text, textColor);
- TextUnformatted(icon);
- PopStyleColor();
- wasTitleRendered = true;
- }
+ // 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();
+ // 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(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;
- }
+ 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!!!!
- }
+ // 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();
- }
+ // If a content is set
+ if (!::IsNullOrEmpty(content)) {
+ if (wasTitleRendered) {
+ Separator();
+ }
- TextUnformatted(content); // Render content text
- }
+ TextUnformatted(content); // Render content text
+ }
- PopTextWrapPos();
- }
+ PopTextWrapPos();
+ }
- // Save height for next toasts
- height += GetWindowHeight() + kNotifyPaddingMessageY;
+ // Save height for next toasts
+ height += GetWindowHeight() + kNotifyPaddingMessageY;
- End();
- }
+ End();
+ }
}
diff --git a/source/EditorNotification.hpp b/source/EditorNotification.hpp
index 01350f0..7bb6dab 100644
--- a/source/EditorNotification.hpp
+++ b/source/EditorNotification.hpp
@@ -5,31 +5,31 @@
#include <cstdint>
enum ImGuiToastType {
- ImGuiToastType_None,
- ImGuiToastType_Success,
- ImGuiToastType_Warning,
- ImGuiToastType_Error,
- ImGuiToastType_Info,
- ImGuiToastType_COUNT
+ ImGuiToastType_None,
+ ImGuiToastType_Success,
+ ImGuiToastType_Warning,
+ ImGuiToastType_Error,
+ ImGuiToastType_Info,
+ ImGuiToastType_COUNT
};
enum ImGuiToastPhase {
- ImGuiToastPhase_FadeIn,
- ImGuiToastPhase_Wait,
- ImGuiToastPhase_FadeOut,
- ImGuiToastPhase_Expired,
- ImGuiToastPhase_COUNT
+ 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
+ ImGuiToastPos_TopLeft,
+ ImGuiToastPos_TopCenter,
+ ImGuiToastPos_TopRight,
+ ImGuiToastPos_BottomLeft,
+ ImGuiToastPos_BottomCenter,
+ ImGuiToastPos_BottomRight,
+ ImGuiToastPos_Center,
+ ImGuiToastPos_COUNT
};
constexpr int kNotifyMaxMsgLength = 4096; // Max message content length
@@ -43,35 +43,35 @@ constexpr ImGuiWindowFlags kNotifyToastFlags = ImGuiWindowFlags_AlwaysAutoResize
class ImGuiToast {
private:
- ImGuiToastType mType = ImGuiToastType_None;
- char mTitle[kNotifyMaxMsgLength] = {};
- char mContent[kNotifyMaxMsgLength] = {};
- int mDismissTime = kNotifyDefaultDismiss;
- uint64_t mCreationTime = 0;
+ 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, ...);
+ 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);
+ 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();
+ 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);
+ void SetTitle(const char* format, va_list args);
+ void SetContent(const char* format, va_list args);
};
namespace ImGui {
diff --git a/source/FuzzyMatch.cpp b/source/FuzzyMatch.cpp
new file mode 100644
index 0000000..0ab604d
--- /dev/null
+++ b/source/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 <cctype>
+#include <cstring>
+
+namespace FuzzyMatch {
+
+namespace P6503_UNITY_ID {
+ bool SearchRecursive(const char* pattern, const char* src, int& outScore, const char* strBegin, const uint8_t srcMatches[], uint8_t newMatches[], int maxMatches, int& nextMatch, int& recursionCount, int recursionLimit);
+} // namespace P6503_UNITY_ID
+
+bool SearchSimple(char const* pattern, char const* haystack) {
+ while (*pattern != '\0' && *haystack != '\0') {
+ if (tolower(*pattern) == tolower(*haystack)) {
+ ++pattern;
+ }
+ ++haystack;
+ }
+
+ return *pattern == '\0';
+}
+
+bool Search(char const* pattern, char const* haystack, int& outScore) {
+ uint8_t matches[256];
+ int matchCount = 0;
+ return Search(pattern, haystack, outScore, matches, sizeof(matches), matchCount);
+}
+
+bool Search(char const* pattern, char const* haystack, int& outScore, uint8_t matches[], int maxMatches, int& outMatches) {
+ int recursionCount = 0;
+ int recursionLimit = 10;
+ int newMatches = 0;
+ bool result = P6503_UNITY_ID::SearchRecursive(pattern, haystack, outScore, haystack, nullptr, matches, maxMatches, newMatches, recursionCount, recursionLimit);
+ outMatches = newMatches;
+ return result;
+}
+
+namespace P6503_UNITY_ID {
+ bool SearchRecursive(const char* pattern, const char* src, int& outScore, const char* strBegin, const uint8_t srcMatches[], uint8_t newMatches[], int maxMatches, int& nextMatch, int& recursionCount, int recursionLimit) {
+ // Count recursions
+ ++recursionCount;
+ if (recursionCount >= recursionLimit) {
+ return false;
+ }
+
+ // Detect end of strings
+ if (*pattern == '\0' || *src == '\0') {
+ return false;
+ }
+
+ // Recursion params
+ bool recursiveMatch = false;
+ uint8_t bestRecursiveMatches[256];
+ int bestRecursiveScore = 0;
+
+ // Loop through pattern and str looking for a match
+ bool firstMatch = true;
+ while (*pattern != '\0' && *src != '\0') {
+ // Found match
+ if (tolower(*pattern) == tolower(*src)) {
+ // Supplied matches buffer was too short
+ if (nextMatch >= maxMatches) {
+ return false;
+ }
+
+ // "Copy-on-Write" srcMatches into matches
+ if (firstMatch && srcMatches) {
+ memcpy(newMatches, srcMatches, nextMatch);
+ firstMatch = false;
+ }
+
+ // Recursive call that "skips" this match
+ uint8_t recursiveMatches[256];
+ int recursiveScore;
+ int recursiveNextMatch = nextMatch;
+ if (SearchRecursive(pattern, src + 1, recursiveScore, strBegin, newMatches, recursiveMatches, sizeof(recursiveMatches), recursiveNextMatch, recursionCount, recursionLimit)) {
+ // Pick the best recursive score
+ if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
+ memcpy(bestRecursiveMatches, recursiveMatches, 256);
+ bestRecursiveScore = recursiveScore;
+ }
+ recursiveMatch = true;
+ }
+
+ // Advance
+ newMatches[nextMatch++] = (uint8_t)(src - strBegin);
+ ++pattern;
+ }
+ ++src;
+ }
+
+ // Determine if full pattern was matched
+ bool matched = *pattern == '\0';
+
+ // Calculate score
+ if (matched) {
+ const int sequentialBonus = 15; // bonus for adjacent matches
+ const int separatorBonus = 30; // bonus if match occurs after a separator
+ const int camelBonus = 30; // bonus if match is uppercase and prev is lower
+ const int firstLetterBonus = 15; // bonus if the first letter is matched
+
+ const int leadingLetterPenalty = -5; // penalty applied for every letter in str before the first match
+ const int maxLeadingLetterPenalty = -15; // maximum penalty for leading letters
+ const int unmatchedLetterPenalty = -1; // penalty for every letter that doesn't matter
+
+ // Iterate str to end
+ while (*src != '\0') {
+ ++src;
+ }
+
+ // Initialize score
+ outScore = 100;
+
+ // Apply leading letter penalty
+ int penalty = leadingLetterPenalty * newMatches[0];
+ if (penalty < maxLeadingLetterPenalty) {
+ penalty = maxLeadingLetterPenalty;
+ }
+ outScore += penalty;
+
+ // Apply unmatched penalty
+ int unmatched = (int)(src - strBegin) - nextMatch;
+ outScore += unmatchedLetterPenalty * unmatched;
+
+ // Apply ordering bonuses
+ for (int i = 0; i < nextMatch; ++i) {
+ uint8_t currIdx = newMatches[i];
+
+ if (i > 0) {
+ uint8_t prevIdx = newMatches[i - 1];
+
+ // Sequential
+ if (currIdx == (prevIdx + 1))
+ outScore += sequentialBonus;
+ }
+
+ // Check for bonuses based on neighbor character value
+ if (currIdx > 0) {
+ // Camel case
+ char neighbor = strBegin[currIdx - 1];
+ char curr = strBegin[currIdx];
+ if (::islower(neighbor) && ::isupper(curr)) {
+ outScore += camelBonus;
+ }
+
+ // Separator
+ bool neighborSeparator = neighbor == '_' || neighbor == ' ';
+ if (neighborSeparator) {
+ outScore += separatorBonus;
+ }
+ } else {
+ // First letter
+ outScore += firstLetterBonus;
+ }
+ }
+ }
+
+ // Return best result
+ if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
+ // Recursive score is better than "this"
+ memcpy(newMatches, bestRecursiveMatches, maxMatches);
+ outScore = bestRecursiveScore;
+ return true;
+ } else if (matched) {
+ // "this" score is better than recursive
+ return true;
+ } else {
+ // no match
+ return false;
+ }
+ }
+} // namespace P6503_UNITY_ID
+} // namespace FuzzyMatch
diff --git a/source/FuzzyMatch.hpp b/source/FuzzyMatch.hpp
new file mode 100644
index 0000000..7a26b7e
--- /dev/null
+++ b/source/FuzzyMatch.hpp
@@ -0,0 +1,10 @@
+// Adapted from https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.h
+#pragma once
+
+#include <cstdint>
+
+namespace FuzzyMatch {
+bool SearchSimple(char const* pattern, char const* haystack);
+bool Search(char const* pattern, char const* haystack, int& outScore);
+bool Search(char const* pattern, char const* haystack, int& outScore, uint8_t matches[], int maxMatches, int& outMatches);
+} // namespace FuzzyMatch
diff --git a/source/GameObject.cpp b/source/GameObject.cpp
index 0a063e8..bb86acc 100644
--- a/source/GameObject.cpp
+++ b/source/GameObject.cpp
@@ -1,55 +1,36 @@
#include "GameObject.hpp"
-#include "GameObjectTags.hpp"
+#include "Player.hpp"
+#include "SceneThings.hpp"
#include "World.hpp"
#include <string_view>
#include <utility>
-namespace ProjectBrussel_UNITY_ID {
-const char* kNamesGOMM[] = {
- "None" /* GOMM_None */,
- "AllChildren" /* GOMM_AllChildren */,
- "SelfAndAllChildren" /* GOMM_SelfAndAllChildren */,
-
-};
-const char* kNamesGOT[] = {
- "GameObject" /* GOT_Generic */,
- "Player" /* GOT_Player */,
- "Building" /* GOT_Building */,
- "LevelWrapper" /* GOT_LevelWrapper */,
-};
+using namespace std::literals;
+namespace ProjectBrussel_UNITY_ID {
bool ValidateGameObjectChild(GameObject* parent, GameObject* child) {
return parent->GetWorld() == child->GetWorld();
}
} // namespace ProjectBrussel_UNITY_ID
-const char* Tags::NameOf(GameObjectMemoryManagement value) {
- return ProjectBrussel_UNITY_ID::kNamesGOMM[value];
-}
-
-const char* Tags::NameOf(GameObjectType value) {
- return ProjectBrussel_UNITY_ID::kNamesGOT[value];
-}
-
void GameObject::FreeRecursive(GameObject* obj) {
- auto gomm = obj->GetMemoryManagement();
- bool freeSelf = gomm != Tags::GOMM_SelfAndAllChildren;
- bool freeChildren = gomm != Tags::GOMM_SelfAndAllChildren && gomm != Tags::GOMM_AllChildren;
-
- if (freeChildren) {
+ if (!obj->mStopFreePropagation) {
for (auto child : obj->GetChildren()) {
FreeRecursive(obj);
}
}
- if (freeSelf) {
- delete obj;
- }
+ delete obj;
}
GameObject::GameObject(GameWorld* world)
- : mWorld{ world } {
+ : GameObject(KD_Generic, world) {
+}
+
+GameObject::GameObject(Kind kind, GameWorld* world)
+ : mWorld{ world }
+ , mKind{ kind } {
}
GameObject::~GameObject() {
@@ -60,6 +41,31 @@ GameObject::~GameObject() {
}
}
+std::string_view GameObject::ToString(Kind kind) {
+ switch (kind) {
+ case KD_Generic: return "GameObject"sv;
+ case KD_Player: return "Player"sv;
+ case KD_SimpleGeometry: return "SimpleGeometry"sv;
+ case KD_Building: return "Building"sv;
+ case KD_LevelWrapper: return "LevelWrapper"sv;
+ case KD_COUNT: break;
+ }
+ return std::string_view();
+}
+
+GameObject::Kind GameObject::FromString(std::string_view name) {
+ if (name == "GameObject"sv) return KD_Generic;
+ if (name == "Player"sv) return KD_Player;
+ if (name == "SimpleGeometry"sv) return KD_SimpleGeometry;
+ if (name == "Building"sv) return KD_Building;
+ if (name == "LevelWrapper"sv) return KD_LevelWrapper;
+ return KD_COUNT;
+}
+
+GameObject::Kind GameObject::GetKind() const {
+ return mKind;
+}
+
GameWorld* GameObject::GetWorld() const {
return mWorld;
}
@@ -73,10 +79,12 @@ const PodVector<GameObject*>& GameObject::GetChildren() const {
}
void GameObject::AddChild(GameObject* child) {
+ using namespace ProjectBrussel_UNITY_ID;
+
if (child->mParent) {
return;
}
- if (!ProjectBrussel_UNITY_ID::ValidateGameObjectChild(this, child)) {
+ if (!ValidateGameObjectChild(this, child)) {
return;
}
@@ -144,20 +152,11 @@ void GameObject::SetRotation(const glm::quat& rotation) {
mRot = rotation;
}
-Tags::GameObjectMemoryManagement GameObject::GetMemoryManagement() const {
- return Tags::GOMM_None;
-};
-
-Tags::GameObjectType GameObject::GetTypeTag() const {
- return Tags::GOT_Generic;
-}
-
-const Material* GameObject::GetMeshMaterial() const {
- return nullptr;
+std::span<const RenderObject> GameObject::GetRenderObjects() const {
+ return {};
}
-const GpuMesh* GameObject::GetMesh() const {
- return nullptr;
+void GameObject::OnInitialized() {
}
void GameObject::Awaken() {
diff --git a/source/GameObject.hpp b/source/GameObject.hpp
index e1e6dd1..be53ca0 100644
--- a/source/GameObject.hpp
+++ b/source/GameObject.hpp
@@ -1,30 +1,46 @@
#pragma once
#include "EditorAttachment.hpp"
-#include "GameObjectTags.hpp"
#include "Material.hpp"
-#include "Mesh.hpp"
#include "PodVector.hpp"
+#include "Renderer.hpp"
+#include "VertexIndex.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
+#include <span>
#include <vector>
class GameWorld;
class GameObject {
-public: // NOTE: public for editors
+public:
+ enum Kind {
+ KD_Generic,
+ KD_Player,
+ KD_SimpleGeometry,
+ KD_Building,
+ KD_LevelWrapper,
+ KD_COUNT,
+ };
+
+private:
std::unique_ptr<EditorAttachment> mEditorAttachment = nullptr;
GameWorld* mWorld = nullptr;
GameObject* mParent = nullptr;
PodVector<GameObject*> mChildren;
glm::quat mRot{};
glm::vec3 mPos{};
+ Kind mKind;
+
+protected:
+ bool mStopFreePropagation : 1 = true;
public:
static void FreeRecursive(GameObject* object);
// TODO allow moving between worlds
- explicit GameObject(GameWorld* world);
+ GameObject(GameWorld* world);
+ GameObject(Kind kind, GameWorld* world);
virtual ~GameObject();
GameObject(const GameObject&) = delete;
@@ -32,6 +48,10 @@ public:
GameObject(GameObject&&) = default;
GameObject& operator=(GameObject&&) = default;
+ static std::string_view ToString(Kind kind);
+ static Kind FromString(std::string_view name);
+ Kind GetKind() const;
+
GameWorld* GetWorld() const;
GameObject* GetParent() const;
const PodVector<GameObject*>& GetChildren() const;
@@ -49,15 +69,11 @@ public:
const glm::quat& GetRotation() const;
void SetRotation(const glm::quat& rotation);
- // Tag
- virtual Tags::GameObjectMemoryManagement GetMemoryManagement() const;
- virtual Tags::GameObjectType GetTypeTag() const;
-
// Visuals
- virtual const Material* GetMeshMaterial() const;
- virtual const GpuMesh* GetMesh() const;
+ virtual std::span<const RenderObject> GetRenderObjects() const;
// Lifetime hooks
+ virtual void OnInitialized();
virtual void Awaken();
virtual void Resleep();
virtual void Update();
diff --git a/source/GameObjectTags.hpp b/source/GameObjectTags.hpp
deleted file mode 100644
index 01a0ca4..0000000
--- a/source/GameObjectTags.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-namespace Tags {
-enum GameObjectMemoryManagement {
- GOMM_None,
- GOMM_AllChildren,
- GOMM_SelfAndAllChildren,
- GOMM_COUNT,
-};
-
-const char* NameOf(GameObjectMemoryManagement value);
-
-enum GameObjectType {
- GOT_Generic, ///< All uncategorized game objects.
- GOT_Player,
- GOT_Building,
- GOT_LevelWrapper,
- GOT_COUNT,
-};
-
-const char* NameOf(GameObjectType value);
-} // namespace Tags
diff --git a/source/Ires.cpp b/source/Ires.cpp
index f60fcb7..346e6e6 100644
--- a/source/Ires.cpp
+++ b/source/Ires.cpp
@@ -74,7 +74,15 @@ void IresObject::SetName(std::string name) {
}
}
-void IresObject::ShowNullName(EditorInstance& editor, Kind kind) {
+void IresObject::ShowNameSafe(IresObject* ires) {
+ if (ires) {
+ ires->ShowName();
+ } else {
+ ShowNameNull();
+ }
+}
+
+void IresObject::ShowNameNull() {
ImGui::Text("<null>");
}
@@ -86,15 +94,15 @@ void IresObject::ShowName() const {
}
}
-void IresObject::ShowFullName() const {
- if (IsAnnoymous()) {
- ImGui::Text("<annoymous %p> (%lx-%lx)", (void*)this, mUid.upper, mUid.lower);
+void IresObject::ShowReferenceSafe(EditorInstance& editor, IresObject* ires) {
+ if (ires) {
+ ires->ShowReference(editor);
} else {
- ImGui::Text("%s (%lx-%lx)", mName.c_str(), mUid.upper, mUid.lower);
+ ShowReferenceNull(editor);
}
}
-void IresObject::ShowNullReference(EditorInstance& editor, Kind kind) {
+void IresObject::ShowReferenceNull(EditorInstance& editor) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]);
ImGui::Text("<null>");
ImGui::PopStyleColor();
diff --git a/source/Ires.hpp b/source/Ires.hpp
index 5875264..a4867da 100644
--- a/source/Ires.hpp
+++ b/source/Ires.hpp
@@ -52,12 +52,14 @@ public:
void SetName(std::string name);
const Uid& GetUid() const { return mUid; }
- static void ShowNullName(EditorInstance& editor, Kind kind);
+ static void ShowNameSafe(IresObject* ires);
+ static void ShowNameNull();
void ShowName() const;
- void ShowFullName() const;
- static void ShowNullReference(EditorInstance& editor, Kind kind);
- virtual void ShowReference(EditorInstance& editor);
+ static void ShowReferenceSafe(EditorInstance& editor, IresObject* ires);
+ static void ShowReferenceNull(EditorInstance& editor);
+ void ShowReference(EditorInstance& editor);
+
virtual void ShowEditor(EditorInstance& editor);
EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); }
diff --git a/source/Level.cpp b/source/Level.cpp
index 71a7473..eec7b85 100644
--- a/source/Level.cpp
+++ b/source/Level.cpp
@@ -1,5 +1,10 @@
#include "Level.hpp"
+LevelWrapperObject::LevelWrapperObject(GameWorld* world)
+ : GameObject(KD_LevelWrapper, world) {
+ mStopFreePropagation = true;
+}
+
LevelWrapperObject::~LevelWrapperObject() {
for (auto child : GetChildren()) {
FreeRecursive(child);
diff --git a/source/Level.hpp b/source/Level.hpp
index 2359c1d..5c92333 100644
--- a/source/Level.hpp
+++ b/source/Level.hpp
@@ -4,11 +4,8 @@
class LevelWrapperObject : public GameObject {
public:
- using GameObject::GameObject;
- ~LevelWrapperObject() override;
-
- virtual Tags::GameObjectMemoryManagement GetMemoryManagement() const override { return Tags::GOMM_AllChildren; }
- virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_LevelWrapper; }
+ LevelWrapperObject(GameWorld* world);
+ ~LevelWrapperObject() override;
};
/// Represents a seralized GameObject tree.
diff --git a/source/Material.cpp b/source/Material.cpp
index abb2f57..3d227f6 100644
--- a/source/Material.cpp
+++ b/source/Material.cpp
@@ -346,7 +346,7 @@ void IresMaterial::ShowEditor(EditorInstance& editor) {
if (shader) {
shader->GetIres()->ShowReference(editor);
} else {
- IresObject::ShowNullReference(editor, KD_Shader);
+ IresObject::ShowReferenceNull(editor);
}
if (ImGui::BeginDragDropTarget()) {
if (auto payload = ImGui::AcceptDragDropPayload(ToString(KD_Shader).data())) {
diff --git a/source/Material.hpp b/source/Material.hpp
index 623a022..eb05a73 100644
--- a/source/Material.hpp
+++ b/source/Material.hpp
@@ -105,6 +105,9 @@ public:
void UseUniforms() const;
};
+// Initialized in main()
+inline RcPtr<Material> gDefaultMaterial;
+
class IresMaterial : public IresObject {
private:
RcPtr<Material> mInstance;
diff --git a/source/Mesh.cpp b/source/Mesh.cpp
index 5b4f708..244e2e3 100644
--- a/source/Mesh.cpp
+++ b/source/Mesh.cpp
@@ -1,98 +1,54 @@
#include "Mesh.hpp"
-#include <algorithm>
+#include <cstring>
-GpuVertexBuffer::GpuVertexBuffer() {
- glGenBuffers(1, &handle);
-}
+// 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());
+// }
-GpuVertexBuffer::~GpuVertexBuffer() {
- glDeleteBuffers(1, &handle);
-}
+// StandardCpuMesh::~StandardCpuMesh() {
+// delete mData;
+// }
-void GpuVertexBuffer::Upload(const std::byte* data, size_t sizeInBytes) {
- glBindBuffer(GL_ARRAY_BUFFER, handle);
- glBufferData(GL_ARRAY_BUFFER, sizeInBytes, data, GL_DYNAMIC_DRAW);
-}
+// void StandardCpuMesh::CreateCpuData() {
+// if (!mData) {
+// mData = new StandardCpuMeshData();
+// }
+// }
-GpuIndexBuffer::GpuIndexBuffer() {
- glGenBuffers(1, &handle);
-}
+// GpuVertexBuffer* StandardCpuMesh::GetPosBuffer() const {
+// return mGpuMesh->vertBufBindings.bindings[0].Get();
+// }
-GpuIndexBuffer::~GpuIndexBuffer() {
- glDeleteBuffers(1, &handle);
-}
+// GpuVertexBuffer* StandardCpuMesh::GetExtraBuffer() const {
+// return mGpuMesh->vertBufBindings.bindings[1].Get();
+// }
-void GpuIndexBuffer::Upload(const std::byte* data, size_t count) {
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * Tags::SizeOf(indexType), data, GL_DYNAMIC_DRAW);
-}
+// 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;
+// }
-void GpuIndexBuffer::Upload(const std::byte* data, Tags::IndexType type, size_t count) {
- indexType = type;
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * Tags::SizeOf(type), data, GL_DYNAMIC_DRAW);
-}
+// bool StandardCpuMesh::UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex) {
+// if (!mData) return false;
+// // TODO
+// }
-int BufferBindings::GetMaxBindingIndex() const {
- return bindings.size() - 1;
-}
+// bool StandardCpuMesh::UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex) {
+// if (!mData) return false;
+// // TODO
+// }
-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);
-}
-
-GpuMesh::GpuMesh() {
-}
-
-GpuMesh::~GpuMesh() {
-}
-
-bool GpuMesh::IsEmpty() const {
- return vertFormat == nullptr || indexBuf == nullptr;
-}
+// bool StandardCpuMesh::UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex) {
+// if (!mData) return false;
+// // TODO
+// }
diff --git a/source/Mesh.hpp b/source/Mesh.hpp
index bc755a3..f86fd55 100644
--- a/source/Mesh.hpp
+++ b/source/Mesh.hpp
@@ -1,83 +1,45 @@
#pragma once
-#include "GraphicsTags.hpp"
+#include "Color.hpp"
+#include "VertexIndex.hpp"
+#include "PodVector.hpp"
#include "RcPtr.hpp"
-#include "SmallVector.hpp"
-#include <glad/glad.h>
#include <cstddef>
#include <cstdint>
-#include <vector>
+#include <glm/glm.hpp>
+#include <memory>
-struct GpuVertexBuffer : public RefCounted {
- GLuint handle;
- int sizeInBytes;
-
- GpuVertexBuffer();
- ~GpuVertexBuffer();
-
- void Upload(const std::byte* data, size_t sizeInBytes);
+struct StandardVertexExtra {
+ float u, v;
+ uint8_t r, g, b, a;
};
-struct GpuIndexBuffer : public RefCounted {
- GLuint handle;
- Tags::IndexType indexType;
- int sizeInBytes;
-
- GpuIndexBuffer();
- ~GpuIndexBuffer();
-
- void Upload(const std::byte* data, size_t count);
- void Upload(const std::byte* data, Tags::IndexType type, size_t count);
-};
-
-struct BufferBindings {
- SmallVector<RcPtr<GpuVertexBuffer>, 4> bindings;
-
- int GetMaxBindingIndex() const;
-
- /// Safe. Returns nullptr if the index is not bound to any buffers.
- GpuVertexBuffer* GetBinding(int index) const;
- /// Adds or updates a buffer binding. Setting a binding to nullptr effectively removes the binding.
- void SetBinding(int index, GpuVertexBuffer* buffer);
- void Clear();
-};
-
-struct VertexElementFormat {
- /// NOTE:
- /// "Automatic" means it will be set inside VertexFormat::AddElement()
- /// "Parameter" means it must be set by the user
- /* Automatic */ int offset;
- /* Parameter */ int bindingIndex;
- /* Parameter */ Tags::VertexElementType type;
- /* Parameter */ Tags::VertexElementSemantic semantic;
-
- int GetStride() const;
-};
-
-struct VertexFormat : public RefCounted {
- std::vector<VertexElementFormat> elements;
- int vertexSize = 0;
-
- const std::vector<VertexElementFormat>& GetElements() { return elements; }
- void AddElement(VertexElementFormat element);
- void RemoveElement(int index);
+class StandardCpuMeshData {
+public:
+ PodVector<glm::vec3> vertPositions;
+ PodVector<StandardVertexExtra> vertExtra;
+ PodVector<uint32_t> index;
+ size_t vertexCount;
+ size_t triangleCount;
};
-// Initialized in main()
-inline RcPtr<VertexFormat> gVformatStandard{};
-inline RcPtr<VertexFormat> gVformatStandardPacked{};
+class StandardCpuMesh {
+// private:
+// StandardCpuMeshData* mData = nullptr;
+// RcPtr<GpuMesh> mGpuMesh;
-// TODO handle immutability
-class GpuMesh : public RefCounted {
-public:
- RcPtr<VertexFormat> vertFormat;
- BufferBindings vertBufBindings;
- RcPtr<GpuIndexBuffer> indexBuf;
+// public:
+// StandardCpuMesh();
+// ~StandardCpuMesh();
-public:
- GpuMesh();
- ~GpuMesh();
+// GpuVertexBuffer* GetPosBuffer() const;
+// GpuVertexBuffer* GetExtraBuffer() const;
+// GpuMesh* GetGpuMesh() const { return mGpuMesh.Get(); }
- bool IsEmpty() const;
+// 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/Player.cpp b/source/Player.cpp
index f56d6ea..f97793e 100644
--- a/source/Player.cpp
+++ b/source/Player.cpp
@@ -19,8 +19,11 @@ std::span<bool> PlayerKeyBinds::GetKeyStatusArray() {
}
Player::Player(GameWorld* world, int id)
- : GameObject(world)
+ : GameObject(KD_Player, world)
, mId{ id } {
+ renderObject.SetMaterial(gDefaultMaterial.Get());
+ renderObject.SetFormat(gVformatStandardPacked.Get(), Tags::IT_16Bit);
+ renderObject.RebuildIfNecessary();
}
void Player::Awaken() {
@@ -32,6 +35,8 @@ void Player::Resleep() {
}
void Player::Update() {
+ using namespace Tags;
+
if (keybinds.pressedLeft) {
}
if (keybinds.pressedRight) {
@@ -40,6 +45,38 @@ void Player::Update() {
// TODO jump controller
// TODO attack controller
+
+ // TODO set default sprite to get rid of this check
+ if (sprite.GetDefinition()) {
+ int prevFrame = sprite.GetFrame();
+ sprite.PlayFrame();
+ int currFrame = sprite.GetFrame();
+ if (prevFrame != currFrame) {
+ uint16_t indices[6];
+ Index_U16::Assign(indices, 0);
+ renderObject.GetIndexBuffer()->Upload((const std::byte*)indices, IT_16Bit, std::size(indices));
+
+ Vertex_PTC vertices[4];
+ Vertex_PTC::Assign(vertices, Rect<float>{ GetPos(), sprite.GetDefinition()->GetBoundingBox() });
+ Vertex_PTC::Assign(vertices, 0.0f);
+ Vertex_PTC::Assign(vertices, RgbaColor(255, 255, 255));
+ Vertex_PTC::Assign(vertices, sprite.GetFrameSubregion());
+ renderObject.GetVertexBufferBindings().bindings[0]->Upload((const std::byte*)vertices, sizeof(vertices));
+ }
+ }
+}
+
+Material* Player::GetMaterial() const {
+ return renderObject.GetMaterial();
+}
+
+void Player::SetMaterial(Material* material) {
+ renderObject.SetMaterial(material);
+ renderObject.RebuildIfNecessary();
+}
+
+std::span<const RenderObject> Player::GetRenderObjects() const {
+ return { &renderObject, 1 };
}
void Player::HandleKeyInput(int key, int action) {
diff --git a/source/Player.hpp b/source/Player.hpp
index 4ae4f80..d003a25 100644
--- a/source/Player.hpp
+++ b/source/Player.hpp
@@ -1,7 +1,9 @@
#pragma once
#include "GameObject.hpp"
-#include "GameObjectTags.hpp"
+#include "Material.hpp"
+#include "RcPtr.hpp"
+#include "Sprite.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
@@ -24,26 +26,25 @@ struct PlayerKeyBinds {
std::span<bool> GetKeyStatusArray();
};
-class PlayerVisual {
-public:
-};
-
class Player : public GameObject {
public:
std::vector<GLFWkeyboard*> boundKeyboards;
PlayerKeyBinds keybinds;
- PlayerVisual visuals;
+ Sprite sprite;
+ RenderObject renderObject;
int mId;
public:
Player(GameWorld* world, int id);
- virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_Player; }
-
virtual void Awaken() override;
virtual void Resleep() override;
virtual void Update() override;
+ Material* GetMaterial() const;
+ void SetMaterial(Material* material);
+ virtual std::span<const RenderObject> GetRenderObjects() const override;
+
int GetId() const { return mId; }
void HandleKeyInput(int key, int action);
diff --git a/source/Renderer.cpp b/source/Renderer.cpp
index dec24ea..f96c3a0 100644
--- a/source/Renderer.cpp
+++ b/source/Renderer.cpp
@@ -1,19 +1,43 @@
#include "Renderer.hpp"
-RenderObject::RenderObject(GpuMesh* mesh, Material* material) {
+#include <cassert>
+
+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& vf = mesh->vertFormat;
- auto& vBindings = mesh->vertBufBindings.bindings;
- auto& shaderInfo = material->GetShader()->GetInfo();
+ auto& vBindings = mVertexBufBinding.bindings;
+ auto& shaderInfo = mMaterial->GetShader()->GetInfo();
// Setup vertex buffers
- for (auto& elm : vf->elements) {
+ for (auto& elm : mVertexFormat->elements) {
assert(elm.bindingIndex < vBindings.size());
auto& buffer = vBindings[elm.bindingIndex];
- int index = material->GetShader()->GetInfo().FindInputLocation(elm.semantic);
+ int index = shaderInfo.FindInputLocation(elm.semantic);
+ if (index == -1) {
+ continue;
+ }
+
glBindBuffer(GL_ARRAY_BUFFER, buffer->handle);
glEnableVertexAttribArray(index);
glVertexAttribPointer(
@@ -21,23 +45,101 @@ RenderObject::RenderObject(GpuMesh* mesh, Material* material) {
Tags::VectorLenOf(elm.type),
Tags::FindGLType(elm.type),
Tags::IsNormalized(elm.type),
- vf->vertexSize,
+ mVertexFormat->vertexSize,
(void*)(uintptr_t)elm.offset);
}
// Setup index buffer
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->indexBuf->handle);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuf->handle);
glBindVertexArray(GL_NONE);
}
-RenderObject::~RenderObject() {
- glDeleteVertexArrays(1, &mVao);
+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_Cam = &camera;
+ mFrame_Transform = camera.CalcProjectionMatrix() * camera.CalcViewMatrix();
+ mFrame_Time = currentTime;
+ mFrame_DeltaTime = deltaTime;
+}
+
+void Renderer::EndFrame() {
+ assert(mInsideFrame == true);
+ mInsideFrame = false;
}
-void Renderer::Draw(RenderObject& object) {
- auto indexType = object.GetMesh()->indexBuf->indexType;
+void Renderer::Draw(const RenderObject* objects, size_t count) {
+ using namespace Tags;
+
+ assert(mInsideFrame);
+
+ // 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();
- glBindVertexArray(object.GetGLVao());
- glDrawElements(GL_TRIANGLES, 0, Tags::FindGLType(indexType), 0);
+ glUseProgram(shader->GetProgram());
+
+ // Autofill uniforms
+ if (shader->autofillLoc_Transform != kInvalidLocation) {
+ glUniformMatrix4fv(shader->autofillLoc_Transform, 1, GL_FALSE, &mFrame_Transform[0][0]);
+ }
+ if (shader->autofillLoc_Time != kInvalidLocation) {
+ glUniform1f(shader->autofillLoc_Time, mFrame_Time);
+ }
+ if (shader->autofillLoc_DeltaTime != kInvalidLocation) {
+ glUniform1f(shader->autofillLoc_DeltaTime, mFrame_DeltaTime);
+ }
+
+ // Material uniforms
+ mat->UseUniforms();
+
+ glBindVertexArray(object.GetGLVao());
+ glDrawElements(GL_TRIANGLES, indexBuffer->count, indexBuffer->GetIndexTypeGL(), 0);
+ }
}
diff --git a/source/Renderer.hpp b/source/Renderer.hpp
index 18a1e6e..b4ca735 100644
--- a/source/Renderer.hpp
+++ b/source/Renderer.hpp
@@ -1,46 +1,61 @@
#pragma once
+#include "Camera.hpp"
#include "Material.hpp"
-#include "Mesh.hpp"
#include "RcPtr.hpp"
+#include "VertexIndex.hpp"
#include <glad/glad.h>
+#include <cstddef>
#include <glm/glm.hpp>
+// TODO add optional support for OpenGL separate attrib binding & only depend on vertex format
+
+// By default, RenderObject sets a default material but not data buffers.
class RenderObject {
public:
glm::mat4 worldMatrix;
private:
RcPtr<Material> mMaterial;
- RcPtr<GpuMesh> mMesh;
+ RcPtr<GpuIndexBuffer> mIndexBuf;
+ RcPtr<VertexFormat> mVertexFormat;
+ BufferBindings mVertexBufBinding;
GLuint mVao;
public:
- RenderObject(GpuMesh* mesh, Material* material);
+ RenderObject();
~RenderObject();
- GLuint GetGLVao() const { return mVao; }
+ GLuint GetGLVao() const;
+ void RebuildIfNecessary();
+
Material* GetMaterial() const { return mMaterial.Get(); }
- GpuMesh* GetMesh() const { return mMesh.Get(); }
-};
+ void SetMaterial(Material* material);
-class Camera {
-public:
- glm::mat4 viewMatrix;
- glm::mat4 projectionMatrix;
+ 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);
-public:
- void Move(glm::vec3 pos);
- void LookAt(glm::vec3 pos);
+private:
+ void DeleteGLObjects();
};
class Renderer {
private:
- Camera* mCam;
+ Camera* mFrame_Cam;
+ glm::mat4 mFrame_Transform;
+ float mFrame_Time;
+ float mFrame_DeltaTime;
+ bool mInsideFrame = false;
public:
- void BeginFrame();
+ void BeginFrame(Camera& camera, float currentTime, float deltaTime);
+ void Draw(const RenderObject* objects, size_t count);
void EndFrame();
- void Draw(RenderObject& object);
};
diff --git a/source/SceneThings.cpp b/source/SceneThings.cpp
index 894ea58..2a25fb6 100644
--- a/source/SceneThings.cpp
+++ b/source/SceneThings.cpp
@@ -1,19 +1,71 @@
#include "SceneThings.hpp"
-void BuildingObject::SetMeshMaterial(Material* material) {
- mMaterial.Attach(material);
- // TODO update render
+#include "CommonVertexIndex.hpp"
+#include "Rect.hpp"
+
+#include <utility>
+
+SimpleGeometryObject::SimpleGeometryObject(GameWorld* world)
+ : GameObject(KD_SimpleGeometry, world)
+ , mRenderObject()
+ , mSize{ 20.0f, 20.0f }
+ , mColor(60, 60, 60) {
+ mRenderObject.SetMaterial(gDefaultMaterial.Get());
+ mRenderObject.SetFormat(gVformatStandardPacked.Get(), Tags::IT_16Bit);
+ mRenderObject.RebuildIfNecessary();
+}
+
+void SimpleGeometryObject::SetSize(glm::vec2 size) {
+ mSize = size;
+}
+
+void SimpleGeometryObject::SetColor(RgbaColor color) {
+ mColor = color;
+}
+
+std::span<const RenderObject> SimpleGeometryObject::GetRenderObjects() const {
+ return { &mRenderObject, 1 };
}
-const Material* BuildingObject::GetMeshMaterial() const {
- return mMaterial.Get();
+void SimpleGeometryObject::UpdateRenderObject() {
+ using namespace Tags;
+
+ uint16_t indices[6];
+ Index_U16::Assign(indices, 0);
+ mRenderObject.GetIndexBuffer()->Upload((const std::byte*)indices, IT_16Bit, std::size(indices));
+
+ Vertex_PTC vertices[4];
+ Vertex_PTC::Assign(vertices, Rect<float>{ GetPos(), mSize });
+ Vertex_PTC::Assign(vertices, 0.0f);
+ Vertex_PTC::Assign(vertices, mColor);
+ mRenderObject.GetVertexBufferBindings().bindings[0]->Upload((const std::byte*)vertices, sizeof(vertices));
}
-void BuildingObject::SetMesh(GpuMesh* mesh) {
- mMesh.Attach(mesh);
- // TODO update render
+BuildingObject::BuildingObject(GameWorld* world)
+ : GameObject(KD_Building, world) {
+ mRenderObject.SetMaterial(gDefaultMaterial.Get());
+ mRenderObject.SetFormat(gVformatStandardPacked.Get(), Tags::IT_32Bit);
+ mRenderObject.RebuildIfNecessary();
}
-const GpuMesh* BuildingObject::GetMesh() const {
- return mMesh.Get();
+// void BuildingObject::SetMeshMaterial(Material* material) {
+// mMaterial.Attach(material);
+// // TODO update render
+// }
+
+// const Material* BuildingObject::GetMeshMaterial() const {
+// return mMaterial.Get();
+// }
+
+// void BuildingObject::SetMesh(GpuMesh* mesh) {
+// mMesh.Attach(mesh);
+// // TODO update render
+// }
+
+// const GpuMesh* BuildingObject::GetMesh() const {
+// return mMesh.Get();
+// }
+
+std::span<const RenderObject> BuildingObject::GetRenderObjects() const {
+ return { &mRenderObject, 1 };
}
diff --git a/source/SceneThings.hpp b/source/SceneThings.hpp
index f8ad000..da551c0 100644
--- a/source/SceneThings.hpp
+++ b/source/SceneThings.hpp
@@ -1,21 +1,42 @@
#pragma once
+#include "Color.hpp"
#include "GameObject.hpp"
+#include "Renderer.hpp"
+#include <glm/glm.hpp>
#include <vector>
-class BuildingObject : public GameObject {
+class SimpleGeometryObject : public GameObject {
private:
- RcPtr<GpuMesh> mMesh;
- RcPtr<Material> mMaterial;
+ RenderObject mRenderObject;
+ glm::vec2 mSize;
+ RgbaColor mColor;
public:
- using GameObject::GameObject;
+ SimpleGeometryObject(GameWorld* world);
+
+ glm::vec2 GetSize() const { return mSize; }
+ void SetSize(glm::vec2 size);
+ RgbaColor GetColor() const { return mColor; }
+ void SetColor(RgbaColor color);
+ virtual std::span<const RenderObject> GetRenderObjects() const override;
+
+private:
+ void UpdateRenderObject();
+};
- virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_Building; }
+class BuildingObject : public GameObject {
+private:
+ RenderObject mRenderObject;
+
+public:
+ BuildingObject(GameWorld* world);
- void SetMeshMaterial(Material* material);
- virtual const Material* GetMeshMaterial() const override;
- void SetMesh(GpuMesh* mesh);
- virtual const GpuMesh* GetMesh() const override;
+ // TODO
+ // void SetMeshMaterial(Material* material);
+ // virtual const Material* GetMeshMaterial() const override;
+ // void SetMesh(GpuMesh* mesh);
+ // virtual const GpuMesh* GetMesh() const override;
+ virtual std::span<const RenderObject> GetRenderObjects() const override;
};
diff --git a/source/Shader.hpp b/source/Shader.hpp
index fefa67c..15290e2 100644
--- a/source/Shader.hpp
+++ b/source/Shader.hpp
@@ -151,6 +151,9 @@ public:
bool IsValid() const;
};
+// Initialized in main()
+inline RcPtr<Shader> gDefaultShader;
+
class IresShader : public IresObject {
private:
RcPtr<Shader> mInstance;
diff --git a/source/SmallVector.hpp b/source/SmallVector.hpp
index 9461800..e33a25d 100644
--- a/source/SmallVector.hpp
+++ b/source/SmallVector.hpp
@@ -36,6 +36,14 @@
# 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 <typename IteratorT>
class iterator_range;
diff --git a/source/Sprite.cpp b/source/Sprite.cpp
index 876b75a..6cff1d3 100644
--- a/source/Sprite.cpp
+++ b/source/Sprite.cpp
@@ -5,6 +5,7 @@
#include "EditorUtils.hpp"
#include "Image.hpp"
#include "RapidJsonHelper.hpp"
+#include "Rect.hpp"
#include <imgui.h>
#include <misc/cpp/imgui_stdlib.h>
@@ -13,7 +14,7 @@
using namespace std::literals;
-bool Sprite::IsValid() const {
+bool SpriteDefinition::IsValid() const {
return mAtlas != nullptr;
}
@@ -21,7 +22,7 @@ bool IresSpriteFiles::IsValid() const {
return !spriteFiles.empty();
}
-Sprite* IresSpriteFiles::CreateInstance() const {
+SpriteDefinition* IresSpriteFiles::CreateInstance() const {
if (IsValid()) {
return nullptr;
}
@@ -42,7 +43,7 @@ Sprite* IresSpriteFiles::CreateInstance() const {
return nullptr;
}
- auto sprite = std::make_unique<Sprite>();
+ auto sprite = std::make_unique<SpriteDefinition>();
sprite->mAtlas.Attach(atlas.release());
sprite->mBoundingBox = atlasOut.elements[0].subregionSize;
sprite->mFrames.reserve(atlasOut.elements.size());
@@ -58,7 +59,7 @@ Sprite* IresSpriteFiles::CreateInstance() const {
return sprite.release();
}
-Sprite* IresSpriteFiles::GetInstance() {
+SpriteDefinition* IresSpriteFiles::GetInstance() {
if (mInstance == nullptr) {
mInstance.Attach(CreateInstance());
}
@@ -91,11 +92,11 @@ bool IresSpritesheet::IsValid() const {
sheetHSplit != 0;
}
-void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, const IresSpritesheet* conf) {
+void IresSpritesheet::ResplitSpritesheet(SpriteDefinition* sprite, const IresSpritesheet* conf) {
ResplitSpritesheet(sprite, conf->sheetWSplit, conf->sheetHSplit, conf->frameCountOverride);
}
-void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit, int frameCount) {
+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;
@@ -131,26 +132,26 @@ void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit,
}
}
-Sprite* IresSpritesheet::CreateInstance() const {
+SpriteDefinition* IresSpritesheet::CreateInstance() const {
if (!IsValid()) {
return nullptr;
}
char path[256];
- snprintf(path, sizeof(path), "%s/Ires/%s", AppConfig::assetDir.c_str(), spritesheetFile.c_str());
+ snprintf(path, sizeof(path), "%s/%s", AppConfig::assetDir.c_str(), spritesheetFile.c_str());
auto atlas = std::make_unique<Texture>();
if (atlas->InitFromFile(path) != Texture::EC_Success) {
return nullptr;
}
- auto sprite = std::make_unique<Sprite>();
+ auto sprite = std::make_unique<SpriteDefinition>();
sprite->mAtlas.Attach(atlas.release());
ResplitSpritesheet(sprite.get(), this);
return sprite.release();
}
-Sprite* IresSpritesheet::GetInstance() {
+SpriteDefinition* IresSpritesheet::GetInstance() {
if (mInstance == nullptr) {
mInstance.Attach(CreateInstance());
}
@@ -281,18 +282,46 @@ void IresSpritesheet::Read(IresLoadingContext& ctx, const rapidjson::Value& valu
BRUSSEL_JSON_GET_DEFAULT(value, "FrameCount", int, frameCountOverride, 0);
}
-SpriteMesh::SpriteMesh(Sprite* sprite)
- : mSprite(sprite) {
+Sprite::Sprite()
+ : mDefinition(nullptr) {
}
-void SpriteMesh::SetFrame(int frame) {
- // TODO
+bool Sprite::IsValid() const {
+ return mDefinition != nullptr;
}
-void SpriteMesh::PlayFrame() {
- // TODO
+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 SpriteMesh::SetPlaybackSpeed(int speed) {
+void Sprite::SetPlaybackSpeed(int speed) {
// TODO
}
diff --git a/source/Sprite.hpp b/source/Sprite.hpp
index b7d357f..de2077f 100644
--- a/source/Sprite.hpp
+++ b/source/Sprite.hpp
@@ -1,11 +1,12 @@
#pragma once
-#include "CpuMesh.hpp"
+#include "CommonVertexIndex.hpp"
#include "Ires.hpp"
-#include "Mesh.hpp"
#include "PodVector.hpp"
#include "RcPtr.hpp"
+#include "Renderer.hpp"
#include "Texture.hpp"
+#include "VertexIndex.hpp"
#include <rapidjson/fwd.h>
#include <glm/glm.hpp>
@@ -13,7 +14,7 @@
#include <string_view>
#include <vector>
-class Sprite : public RefCounted {
+class SpriteDefinition : public RefCounted {
friend class IresSpriteFiles;
friend class IresSpritesheet;
@@ -31,7 +32,7 @@ public:
class IresSpriteFiles : public IresObject {
public:
- RcPtr<Sprite> mInstance;
+ RcPtr<SpriteDefinition> mInstance;
std::vector<std::string> spriteFiles;
public:
@@ -41,8 +42,8 @@ public:
// NOTE: does not check whether all specified files have the same dimensions
bool IsValid() const;
- Sprite* CreateInstance() const;
- Sprite* GetInstance();
+ SpriteDefinition* CreateInstance() const;
+ SpriteDefinition* GetInstance();
void InvalidateInstance();
void Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const override;
@@ -51,7 +52,7 @@ public:
class IresSpritesheet : public IresObject {
public:
- RcPtr<Sprite> mInstance;
+ RcPtr<SpriteDefinition> mInstance;
std::string spritesheetFile;
int sheetWSplit = 1;
int sheetHSplit = 1;
@@ -63,11 +64,11 @@ public:
bool IsValid() const;
- static void ResplitSpritesheet(Sprite* sprite, const IresSpritesheet* conf);
- static void ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit, int frameCountOverride = -1);
+ static void ResplitSpritesheet(SpriteDefinition* sprite, const IresSpritesheet* conf);
+ static void ResplitSpritesheet(SpriteDefinition* sprite, int wSplit, int hSplit, int frameCountOverride = -1);
- Sprite* CreateInstance() const;
- Sprite* GetInstance();
+ SpriteDefinition* CreateInstance() const;
+ SpriteDefinition* GetInstance();
void InvalidateInstance();
bool IsFrameCountOverriden() const;
@@ -79,27 +80,34 @@ public:
void Read(IresLoadingContext& ctx, const rapidjson::Value& value) override;
};
-class SpriteMesh {
+// TODO
+class SpriteCollection {
private:
- RcPtr<GpuMesh> mMesh;
- RcPtr<Sprite> mSprite;
- PodVector<StandardVertex> mVertices;
- PodVector<uint16_t> mIndices;
+ std::vector<SpriteDefinition> mSprites;
+};
+
+class Sprite {
+private:
+ RcPtr<SpriteDefinition> mDefinition;
int mCurrentFrame = 0;
+ int mTimeElapsed = 0;
// # of frames per second
int mPlaybackSpeed = 5;
public:
- SpriteMesh(Sprite* sprite);
+ Sprite();
+
+ bool IsValid() const;
- Sprite* GetSprite() const { return mSprite.Get(); }
- GpuMesh* GetGpuMesh() const { return mMesh.Get(); }
+ SpriteDefinition* GetDefinition() const { return mDefinition.Get(); }
+ void SetDefinition(SpriteDefinition* definition);
- int GetFrame() const { return mCurrentFrame; }
+ int GetFrame() const;
+ const Subregion& GetFrameSubregion() const;
void SetFrame(int frame);
// Update as if a render frame has passed
void PlayFrame();
- int GetPlaybackSpeed() const { return mPlaybackSpeed; }
+ int GetPlaybackSpeed() const;
void SetPlaybackSpeed(int speed);
};
diff --git a/source/VertexIndex.cpp b/source/VertexIndex.cpp
new file mode 100644
index 0000000..ac68289
--- /dev/null
+++ b/source/VertexIndex.cpp
@@ -0,0 +1,84 @@
+#include "VertexIndex.hpp"
+
+#include <algorithm>
+
+GpuVertexBuffer::GpuVertexBuffer() {
+ glGenBuffers(1, &handle);
+}
+
+GpuVertexBuffer::~GpuVertexBuffer() {
+ glDeleteBuffers(1, &handle);
+}
+
+void GpuVertexBuffer::Upload(const std::byte* data, size_t sizeInBytes) {
+ glBindBuffer(GL_ARRAY_BUFFER, handle);
+ glBufferData(GL_ARRAY_BUFFER, sizeInBytes, data, GL_DYNAMIC_DRAW);
+}
+
+GpuIndexBuffer::GpuIndexBuffer() {
+ glGenBuffers(1, &handle);
+}
+
+GpuIndexBuffer::~GpuIndexBuffer() {
+ glDeleteBuffers(1, &handle);
+}
+
+void GpuIndexBuffer::Upload(const std::byte* data, Tags::IndexType type, size_t count) {
+ this->indexType = type;
+ this->count = count;
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * Tags::SizeOf(type), data, GL_DYNAMIC_DRAW);
+}
+
+int BufferBindings::GetMaxBindingIndex() const {
+ return bindings.size() - 1;
+}
+
+GpuVertexBuffer* BufferBindings::GetBinding(int index) const {
+ if (index >= 0 && index < bindings.size()) {
+ return bindings[index].Get();
+ } else {
+ return nullptr;
+ }
+}
+
+void BufferBindings::SetBinding(int index, GpuVertexBuffer* buffer) {
+ int maxBindingIndex = GetMaxBindingIndex();
+ if (index > maxBindingIndex) {
+ int countDelta = index - maxBindingIndex;
+ bindings.resize(bindings.size() + countDelta);
+ }
+
+ bindings[index].Attach(buffer);
+ if (index == maxBindingIndex && buffer == nullptr) {
+ bindings.pop_back();
+ }
+}
+
+void BufferBindings::Clear() {
+ bindings.clear();
+}
+
+int VertexElementFormat::GetStride() const {
+ return Tags::SizeOf(type);
+}
+
+void VertexFormat::AddElement(VertexElementFormat element) {
+ vertexSize += element.GetStride();
+
+ int lastIdx = (int)elements.size() - 1;
+ if (lastIdx >= 0) {
+ auto& last = elements[lastIdx];
+ element.offset = last.offset + last.GetStride();
+ } else {
+ element.offset = 0;
+ }
+
+ elements.push_back(std::move(element));
+}
+
+void VertexFormat::RemoveElement(int index) {
+ auto& element = elements[index];
+ vertexSize -= element.GetStride();
+ elements.erase(elements.begin() + index);
+}
diff --git a/source/VertexIndex.hpp b/source/VertexIndex.hpp
new file mode 100644
index 0000000..cc5aeca
--- /dev/null
+++ b/source/VertexIndex.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "GraphicsTags.hpp"
+#include "RcPtr.hpp"
+#include "SmallVector.hpp"
+
+#include <glad/glad.h>
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+struct GpuVertexBuffer : public RefCounted {
+ GLuint handle;
+ int sizeInBytes;
+
+ GpuVertexBuffer();
+ ~GpuVertexBuffer();
+
+ void Upload(const std::byte* data, size_t sizeInBytes);
+};
+
+struct GpuIndexBuffer : public RefCounted {
+ GLuint handle;
+ Tags::IndexType indexType;
+ int count;
+
+ GpuIndexBuffer();
+ ~GpuIndexBuffer();
+
+ Tags::IndexType GetIndexType() const { return indexType; }
+ GLenum GetIndexTypeGL() const { return Tags::FindGLType(indexType); }
+
+ void Upload(const std::byte* data, Tags::IndexType type, size_t count);
+};
+
+struct BufferBindings {
+ SmallVector<RcPtr<GpuVertexBuffer>, 4> bindings;
+
+ int GetMaxBindingIndex() const;
+
+ /// Safe. Returns nullptr if the index is not bound to any buffers.
+ GpuVertexBuffer* GetBinding(int index) const;
+ /// Adds or updates a buffer binding. Setting a binding to nullptr effectively removes the binding.
+ void SetBinding(int index, GpuVertexBuffer* buffer);
+ void Clear();
+};
+
+struct VertexElementFormat {
+ /// NOTE:
+ /// "Automatic" means it will be set inside VertexFormat::AddElement()
+ /// "Parameter" means it must be set by the user
+ /* Automatic */ int offset;
+ /* Parameter */ int bindingIndex;
+ /* Parameter */ Tags::VertexElementType type;
+ /* Parameter */ Tags::VertexElementSemantic semantic;
+
+ int GetStride() const;
+};
+
+struct VertexFormat : public RefCounted {
+ SmallVector<VertexElementFormat, 4> elements;
+ int vertexSize = 0;
+
+ const decltype(elements)& GetElements() const { return elements; }
+ void AddElement(VertexElementFormat element);
+ void RemoveElement(int index);
+};
+
+// Initialized in main()
+inline RcPtr<VertexFormat> gVformatStandard{};
+inline RcPtr<VertexFormat> gVformatStandardPacked{};
diff --git a/source/World.cpp b/source/World.cpp
index 49aa58f..d4a8344 100644
--- a/source/World.cpp
+++ b/source/World.cpp
@@ -29,15 +29,8 @@ struct DrawCall {
};
} // namespace ProjectBrussel_UNITY_ID
-struct GameWorld::RenderData {
- void SubmitDrawCalls() {
- // TODO
- }
-};
-
GameWorld::GameWorld()
- : mRender{ new RenderData() }
- , mRoot{ new GameObject(this) } {
+ : mRoot{ new GameObject(this) } {
}
GameWorld::~GameWorld() {
@@ -45,7 +38,6 @@ GameWorld::~GameWorld() {
Resleep();
}
- delete mRender;
delete mRoot;
}
@@ -73,9 +65,6 @@ void GameWorld::Update() {
});
}
-void GameWorld::Draw() {
-}
-
GameObject& GameWorld::GetRoot() {
return *mRoot;
}
diff --git a/source/World.hpp b/source/World.hpp
index b12e5ad..288142e 100644
--- a/source/World.hpp
+++ b/source/World.hpp
@@ -1,16 +1,12 @@
#pragma once
-
class GameObject;
class GameWorld {
private:
- class RenderData;
- RenderData* mRender;
-
GameObject* mRoot;
bool mAwakened = false;
-public:
+public:
GameWorld();
~GameWorld();
@@ -24,8 +20,6 @@ public:
void Resleep();
void Update();
- void Draw();
-
const GameObject& GetRoot() const;
GameObject& GetRoot();
};
diff --git a/source/YCombinator.hpp b/source/YCombinator.hpp
new file mode 100644
index 0000000..b1d2350
--- /dev/null
+++ b/source/YCombinator.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+template <class Func>
+struct YCombinator {
+ // NOTE: implicit constructor allows initializing this
+ Func func;
+
+ template <class... Ts>
+ decltype(auto) operator()(Ts&&... args) const {
+ // NOTE: static_cast<Ts>(args)... is equivalent to std::forward<Ts>(args)...
+ // written this way so that we don't have to include <utility>, as well as reducing template instanciations to help compile time
+ return func(*this, static_cast<Ts>(args)...);
+ }
+};
diff --git a/source/main.cpp b/source/main.cpp
index b166754..99a11c0 100644
--- a/source/main.cpp
+++ b/source/main.cpp
@@ -1,9 +1,10 @@
#include "App.hpp"
#include "AppConfig.hpp"
-#include "EditorNotification.hpp"
#include "Ires.hpp"
-#include "Mesh.hpp"
+#include "Material.hpp"
+#include "Shader.hpp"
+#include "VertexIndex.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
@@ -13,47 +14,86 @@
#include <backends/imgui_impl_opengl3.h>
#include <glad/glad.h>
#include <imgui.h>
+#include <imgui_internal.h>
#include <cstdlib>
#include <cxxopts.hpp>
#include <filesystem>
#include <string>
namespace fs = std::filesystem;
+using namespace std::literals;
-static void GlfwErrorCallback(int error, const char* description) {
- fprintf(stderr, "Glfw Error %d: %s\n", error, description);
+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::mainWidnowWidth = width;
+ AppConfig::mainWindowHeight = height;
+ AppConfig::mainWindowAspectRatio = (float)width / height;
}
-static void GlfwMouseCallback(GLFWwindow* window, int button, int action, int mods) {
+void GlfwMouseCallback(GLFWwindow* window, int button, int action, int mods) {
if (ImGui::GetIO().WantCaptureMouse) {
return;
}
- App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
+ auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window));
+ auto app = userData->app;
app->HandleMouse(button, action);
}
-static void GlfwMouseMotionCallback(GLFWwindow* window, double xOff, double yOff) {
+void GlfwMouseMotionCallback(GLFWwindow* window, double xOff, double yOff) {
if (ImGui::GetIO().WantCaptureMouse) {
return;
}
- App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
+ auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window));
+ auto app = userData->app;
app->HandleMouseMotion(xOff, yOff);
}
-static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (ImGui::GetIO().WantCaptureKeyboard) {
return;
}
GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard();
if (keyboard) {
- App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
+ auto userData = static_cast<GlfwUserData*>(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 <ShlObj.h>
+# else
+# include <ShlObj_core.h>
+# endif
+# include <objbase.h>
+# pragma comment(lib, "shell32.lib")
+# pragma comment(lib, "ole32.lib")
+#elif defined(__linux__)
+fs::path GetEnvVar(const char* name, const char* backup) {
+ if (const char* path = std::getenv(name)) {
+ fs::path dataDir(path);
+ fs::create_directories(dataDir);
+ return dataDir;
+ } else {
+ fs::path dataDir(backup);
+ fs::create_directories(dataDir);
+ return dataDir;
+ }
+}
+#endif
+
int main(int argc, char* argv[]) {
using namespace Tags;
@@ -72,7 +112,7 @@ int main(int argc, char* argv[]) {
auto args = options.parse(argc, argv);
bool imguiUseOpenGL3;
- {
+ if (args.count(kImGuiBackend) > 0) {
auto imguiBackend = args[kImGuiBackend].as<std::string>();
if (imguiBackend == "opengl2") {
imguiUseOpenGL3 = false;
@@ -80,11 +120,13 @@ int main(int argc, char* argv[]) {
imguiUseOpenGL3 = true;
} else {
// TODO support more backends?
- imguiUseOpenGL3 = false;
+ imguiUseOpenGL3 = true;
}
+ } else {
+ imguiUseOpenGL3 = true;
}
- {
+ if (args.count(kGameAssetDir) > 0) {
auto assetDir = args[kGameAssetDir].as<std::string>();
fs::path assetDirPath(assetDir);
@@ -95,6 +137,31 @@ int main(int argc, char* argv[]) {
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) {
@@ -118,13 +185,7 @@ int main(int argc, char* argv[]) {
glfwSetErrorCallback(&GlfwErrorCallback);
// Decide GL+GLSL versions
-#if defined(IMGUI_IMPL_OPENGL_ES2)
- // GL ES 2.0 + GLSL 100
- const char* imguiGlslVersion = "#version 100";
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
- glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
-#elif defined(__APPLE__)
+#if defined(__APPLE__)
// GL 3.2 + GLSL 150
const char* imguiGlslVersion = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
@@ -132,29 +193,37 @@ int main(int argc, char* argv[]) {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#else
- // GL 3.0 + GLSL 130
+ // GL 3.3 + GLSL 130
const char* imguiGlslVersion = "#version 130";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
#endif
- App app;
+ GlfwUserData glfwUserData;
GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr);
if (window == nullptr) {
return -2;
}
- glfwSetWindowUserPointer(window, &app);
+ 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;
}
@@ -169,6 +238,9 @@ int main(int argc, char* argv[]) {
ImGui_ImplOpenGL2_Init();
}
+ auto& io = ImGui::GetIO();
+ auto& ctx = *ImGui::GetCurrentContext();
+
IresManager::instance = new IresManager();
IresManager::instance->DiscoverFilesDesignatedLocation();
@@ -206,45 +278,169 @@ int main(int argc, char* argv[]) {
.semantic = VES_Color1,
});
- app.Init();
- while (!glfwWindowShouldClose(window)) {
- glfwPollEvents();
+ // Matches gVformatStandardPacked
+ gDefaultShader.Attach(new Shader());
+ gDefaultShader->InitFromSources(Shader::ShaderSources{
+ .vertex = R"""(
+#version 330 core
+layout(location = 0) in vec3 pos;
+layout(location = 1) in vec2 texcoord;
+layout(location = 2) in vec4 color;
+out vec4 v2fColor;
+uniform mat4 transformation;
+void main() {
+ gl_Position = transformation * 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 vec2 texcoord;
+ ShaderMathVariable var;
+ var.scalarType = GL_FLOAT;
+ var.width = 1;
+ var.height = 2;
+ var.arrayLength = 1;
+ var.semantic = VES_TexCoords1;
+ var.location = 1;
+ gDefaultShader->GetInfo().inputs.push_back(std::move(var));
+ gDefaultShader->GetInfo().things.try_emplace(
+ "texcoord"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 = 2;
+ 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
- int fbWidth, fbHeight;
- 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);
+ gDefaultMaterial.Attach(new Material());
+ gDefaultMaterial->SetShader(gDefaultShader.Get());
- { // Regular draw
- app.Update();
- app.Draw();
- }
+ { // Main loop
+ App app;
+ glfwUserData.app = &app;
+
+ // 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)) {
+ glfwPollEvents();
+
+ double currTime = glfwGetTime();
+ double deltaTime = prevTime - currTime;
- { // ImGui stuff
- if (imguiUseOpenGL3) {
- ImGui_ImplOpenGL3_NewFrame();
- } else {
- ImGui_ImplOpenGL2_NewFrame();
+ // 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();
+ {
+ 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;
+ }
}
- ImGui_ImplGlfw_NewFrame();
- ImGui::NewFrame();
- app.Show();
- ImGui::ShowNotifications();
+ int fbWidth = AppConfig::mainWidnowWidth;
+ 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);
- ImGui::Render();
- if (imguiUseOpenGL3) {
- ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
- } else {
- ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
+ { // Regular draw
+ app.Draw(currTime, deltaTime);
}
- }
- glfwSwapBuffers(window);
+ { // ImGui draw
+ 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);
+ }
}
- app.Shutdown();
if (imguiUseOpenGL3) {
ImGui_ImplOpenGL3_Shutdown();