aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/App.cpp30
-rw-r--r--source/App.hpp22
-rw-r--r--source/CMakeLists.txt11
-rw-r--r--source/GameObject.cpp136
-rw-r--r--source/GameObject.hpp51
-rw-r--r--source/GameObjectTypeTag.hpp16
-rw-r--r--source/Level.cpp7
-rw-r--r--source/Level.hpp16
-rw-r--r--source/Macros.hpp29
-rw-r--r--source/Material.cpp169
-rw-r--r--source/Material.hpp76
-rw-r--r--source/Mesh.cpp1
-rw-r--r--source/Mesh.hpp17
-rw-r--r--source/Player.cpp50
-rw-r--r--source/Player.hpp42
-rw-r--r--source/PodVector.hpp295
-rw-r--r--source/RcPtr.hpp113
-rw-r--r--source/SceneThings.cpp19
-rw-r--r--source/SceneThings.hpp21
-rw-r--r--source/ScopeGuard.hpp53
-rw-r--r--source/Shader.cpp327
-rw-r--r--source/Shader.hpp81
-rw-r--r--source/Texture.cpp99
-rw-r--r--source/Texture.hpp66
-rw-r--r--source/TypeTraits.hpp19
-rw-r--r--source/World.cpp68
-rw-r--r--source/World.hpp31
-rw-r--r--source/main.cpp19
28 files changed, 1879 insertions, 5 deletions
diff --git a/source/App.cpp b/source/App.cpp
index 35fb5f7..c85dd9e 100644
--- a/source/App.cpp
+++ b/source/App.cpp
@@ -1,7 +1,35 @@
#include "App.hpp"
#include <imgui.h>
+#include <memory>
+
+void App::Init() {
+ mCurrentWorld = std::make_unique<GameWorld>();
+ auto worldRoot = mCurrentWorld->GetRoot();
+
+ constexpr int kPlayerCount = 2;
+ for (int i = 0; i < kPlayerCount; ++i) {
+ auto player = new Player(mCurrentWorld.get());
+ worldRoot.AddChild(player);
+ mPlayers.push_back(player);
+ }
+}
+
+void App::Shutdown() {
+ mCurrentWorld = nullptr;
+ mPlayers.clear();
+}
void App::Show() {
- // Application goes here
+ mCurrentWorld->Draw();
+}
+
+void App::HandleKey(GLFWkeyboard* keyboard, int key, int action) {
+ for (auto& player : mPlayers) {
+ for (auto playerKeyboard : player->boundKeyboards) {
+ if (playerKeyboard == keyboard) {
+ player->HandleKeyInput(key, action);
+ }
+ }
+ }
}
diff --git a/source/App.hpp b/source/App.hpp
index bc99354..5a701d0 100644
--- a/source/App.hpp
+++ b/source/App.hpp
@@ -1,6 +1,24 @@
#pragma once
+#include "Player.hpp"
+#include "PodVector.hpp"
+#include "World.hpp"
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <memory>
+#include <vector>
+
class App {
+private:
+ PodVector<Player*> mPlayers;
+ std::unique_ptr<GameWorld> mCurrentWorld;
+
public:
- void Show();
-}; \ No newline at end of file
+ void Init();
+ void Shutdown();
+
+ void Show();
+ void HandleKey(GLFWkeyboard* keyboard, int key, int action);
+};
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index a76475b..9a67cb5 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -1,4 +1,13 @@
target_sources(${PROJECT_NAME}
PRIVATE
- App.hpp App.cpp
+ App.cpp
+ GameObject.cpp
+ Level.cpp
+ Material.cpp
+ Mesh.cpp
+ Player.cpp
+ SceneThings.cpp
+ Shader.cpp
+ Texture.cpp
+ World.cpp
)
diff --git a/source/GameObject.cpp b/source/GameObject.cpp
new file mode 100644
index 0000000..bbddb20
--- /dev/null
+++ b/source/GameObject.cpp
@@ -0,0 +1,136 @@
+#include "GameObject.hpp"
+
+#include "World.hpp"
+
+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) {
+ for (auto child : obj->GetChildren()) {
+ FreeRecursive(obj);
+ }
+ }
+ if (freeSelf) {
+ delete obj;
+ }
+}
+
+GameObject::GameObject(GameWorld* world)
+ : mWorld{ world } {
+}
+
+GameObject::~GameObject() {
+ RemoveAllChildren();
+ if (mParent) {
+ mParent->RemoveChild(this);
+ // NOTE: from this point on, mParent will be nullptr
+ }
+}
+
+GameWorld* GameObject::GetWorld() const {
+ return mWorld;
+}
+
+GameObject* GameObject::GetParent() const {
+ return mParent;
+}
+
+const PodVector<GameObject*>& GameObject::GetChildren() const {
+ return mChildren;
+}
+
+namespace ProjectBrussel_UNITY_ID {
+bool ValidateGameObjectChild(GameObject* parent, GameObject* child) {
+ return parent->GetWorld() == child->GetWorld();
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+void GameObject::AddChild(GameObject* child) {
+ if (child->mParent) {
+ return;
+ }
+ if (!ProjectBrussel_UNITY_ID::ValidateGameObjectChild(this, child)) {
+ return;
+ }
+
+ mChildren.push_back(child);
+ child->SetParent(this);
+}
+
+GameObject* GameObject::RemoveChild(int index) {
+ if (index < 0 || index >= mChildren.size()) {
+ return nullptr;
+ }
+
+ auto it = mChildren.begin() + index;
+ auto child = *it;
+
+ // cancelUpdate(ret);
+
+ std::swap(*it, mChildren.back());
+ mChildren.pop_back();
+ child->SetParent(nullptr);
+ return child;
+}
+
+GameObject* GameObject::RemoveChild(GameObject* child) {
+ if (child) {
+ for (auto it = mChildren.begin(); it != mChildren.end(); ++it) {
+ if (*it == child) {
+ // cancelUpdate(child);
+
+ std::swap(*it, mChildren.back());
+ mChildren.pop_back();
+ child->SetParent(nullptr);
+ return child;
+ }
+ }
+ }
+ return nullptr;
+}
+
+PodVector<GameObject*> GameObject::RemoveAllChildren() {
+ for (auto& child : mChildren) {
+ child->SetParent(nullptr);
+ }
+
+ auto result = std::move(mChildren);
+ // Moving from STL object leaves it in a valid but _unspecified_ state, call std::vector::clear() to guarantee it's empty
+ // NOTE: even though we have the source code of PodVector<T>, we still do this to follow convention
+ mChildren.clear();
+ return result;
+}
+
+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;
+}
+
+const Mesh* GameObject::GetMesh() const {
+ return nullptr;
+}
+
+void GameObject::Awaken() {
+}
+
+void GameObject::Resleep() {
+}
+
+void GameObject::Update() {
+}
+
+void GameObject::SetParent(GameObject* parent) {
+ if (mParent != parent) {
+ mParent = parent;
+ // needUpdate();
+ }
+}
diff --git a/source/GameObject.hpp b/source/GameObject.hpp
new file mode 100644
index 0000000..9567edd
--- /dev/null
+++ b/source/GameObject.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "GameObjectTypeTag.hpp"
+#include "Material.hpp"
+#include "Mesh.hpp"
+#include "PodVector.hpp"
+
+#include <glm/glm.hpp>
+#include <glm/gtc/quaternion.hpp>
+#include <vector>
+
+class GameWorld;
+class GameObject {
+private:
+ GameWorld* mWorld;
+ GameObject* mParent;
+ PodVector<GameObject*> mChildren;
+ glm::quat mRot;
+ glm::vec3 mPos;
+
+public:
+ static void FreeRecursive(GameObject* object);
+
+ // TODO allow moving between worlds
+ explicit GameObject(GameWorld* world);
+ virtual ~GameObject();
+
+ GameWorld* GetWorld() const;
+ GameObject* GetParent() const;
+ const PodVector<GameObject*>& GetChildren() const;
+ void AddChild(GameObject* child);
+ GameObject* RemoveChild(int index);
+ GameObject* RemoveChild(GameObject* child);
+ PodVector<GameObject*> RemoveAllChildren();
+
+ // Tag
+ virtual Tags::GameObjectMemoryManagement GetMemoryManagement() const;
+ virtual Tags::GameObjectType GetTypeTag() const;
+
+ // Visuals
+ virtual const Material* GetMeshMaterial() const;
+ virtual const Mesh* GetMesh() const;
+
+ // Lifetime hooks
+ virtual void Awaken();
+ virtual void Resleep();
+ virtual void Update();
+
+protected:
+ void SetParent(GameObject* parent);
+};
diff --git a/source/GameObjectTypeTag.hpp b/source/GameObjectTypeTag.hpp
new file mode 100644
index 0000000..6455507
--- /dev/null
+++ b/source/GameObjectTypeTag.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+namespace Tags {
+enum GameObjectMemoryManagement : int {
+ GOMM_None,
+ GOMM_AllChildren,
+ GOMM_SelfAndAllChildren,
+};
+
+enum GameObjectType : int {
+ GOT_Generic, ///< All uncategorized game objects.
+ GOT_Player,
+ GOT_Building,
+ GOT_LevelWrapper,
+};
+} // namespace Tags
diff --git a/source/Level.cpp b/source/Level.cpp
new file mode 100644
index 0000000..71a7473
--- /dev/null
+++ b/source/Level.cpp
@@ -0,0 +1,7 @@
+#include "Level.hpp"
+
+LevelWrapperObject::~LevelWrapperObject() {
+ for (auto child : GetChildren()) {
+ FreeRecursive(child);
+ }
+}
diff --git a/source/Level.hpp b/source/Level.hpp
new file mode 100644
index 0000000..2359c1d
--- /dev/null
+++ b/source/Level.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "GameObject.hpp"
+
+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; }
+};
+
+/// Represents a seralized GameObject tree.
+class Level {
+};
diff --git a/source/Macros.hpp b/source/Macros.hpp
new file mode 100644
index 0000000..71a438a
--- /dev/null
+++ b/source/Macros.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#define STRINGIFY_IMPL(text) #text
+#define STRINGIFY(text) STRINGIFY_IMPL(text)
+
+#define CONCAT_IMPL(a, b) a##b
+#define CONCAT(a, b) CONCAT_IMPL(a, b)
+#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c))
+#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d))
+
+#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__)
+#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__)
+#define DISCARD UNIQUE_NAME(_discard)
+
+#define UNUSED(x) (void)x;
+
+#if defined(_MSC_VER)
+# define UNREACHABLE __assume(0)
+#elif defined(__GNUC__) || defined(__clang__)
+# define UNREACHABLE __builtin_unreachable()
+#else
+# define UNREACHABLE
+#endif
+
+#if defined(DOCTEST_CONFIG_DISABLE)
+# define TESTED_MEMEBERS_VISBILITY private
+#else
+# define TESTED_MEMEBERS_VISBILITY public
+#endif
diff --git a/source/Material.cpp b/source/Material.cpp
new file mode 100644
index 0000000..138434c
--- /dev/null
+++ b/source/Material.cpp
@@ -0,0 +1,169 @@
+#include "Material.hpp"
+
+#include <cstdlib>
+#include <cstring>
+
+Material::Material(Shader* shader)
+ : mShader(shader) {
+}
+
+namespace ProjectBrussel_UNITY_ID {
+template <class TUniform>
+TUniform& ObtainUniform(std::vector<TUniform>& uniforms, GLint location) {
+ for (auto& uniform : uniforms) {
+ if (uniform.location == location) {
+ return uniform;
+ }
+ }
+
+ auto& uniform = uniforms.emplace_back();
+ uniform.location = location;
+ return uniform;
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+void Material::SetFloat(const char* name, float value) {
+ GLint location = glGetUniformLocation(mShader->GetProgram(), name);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location);
+ uniform.floatValue = value;
+ uniform.actualType = GL_FLOAT;
+}
+
+void Material::SetInt(const char* name, int32_t value) {
+ GLint location = glGetUniformLocation(mShader->GetProgram(), name);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location);
+ uniform.intValue = value;
+ uniform.actualType = GL_INT;
+}
+
+void Material::SetUInt(const char* name, uint32_t value) {
+ GLint location = glGetUniformLocation(mShader->GetProgram(), name);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location);
+ uniform.uintValue = value;
+ uniform.actualType = GL_UNSIGNED_INT;
+}
+
+template <int length>
+void Material::SetVector(const char* name, const glm::vec<length, float>& vec) {
+ static_assert(length >= 1 && length <= 4);
+
+ GLint location = glGetUniformLocation(mShader->GetProgram(), name);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundVectors, location);
+ uniform.actualLength = length;
+ std::memset(uniform.value, 0, sizeof(uniform.value));
+ std::memcpy(uniform.value, &vec[0], length * sizeof(float));
+}
+
+template void Material::SetVector<1>(const char*, const glm::vec<1, float>&);
+template void Material::SetVector<2>(const char*, const glm::vec<2, float>&);
+template void Material::SetVector<3>(const char*, const glm::vec<3, float>&);
+template void Material::SetVector<4>(const char*, const glm::vec<4, float>&);
+
+template <int width, int height>
+void Material::SetMatrix(const char* name, const glm::mat<width, height, float>& mat) {
+ static_assert(width >= 1 && width <= 4);
+ static_assert(height >= 1 && height <= 4);
+
+ GLint location = glGetUniformLocation(mShader->GetProgram(), name);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundMatrices, location);
+ uniform.actualWidth = width;
+ uniform.actualHeight = height;
+ std::memset(uniform.value, 0, sizeof(uniform.value));
+ std::memcpy(uniform.value, &mat[0][0], width * height * sizeof(float));
+}
+
+template void Material::SetMatrix<2, 2>(const char*, const glm::mat<2, 2, float>&);
+template void Material::SetMatrix<3, 3>(const char*, const glm::mat<3, 3, float>&);
+template void Material::SetMatrix<4, 4>(const char*, const glm::mat<4, 4, float>&);
+
+template void Material::SetMatrix<2, 3>(const char*, const glm::mat<2, 3, float>&);
+template void Material::SetMatrix<3, 2>(const char*, const glm::mat<3, 2, float>&);
+
+template void Material::SetMatrix<2, 4>(const char*, const glm::mat<2, 4, float>&);
+template void Material::SetMatrix<4, 2>(const char*, const glm::mat<4, 2, float>&);
+
+template void Material::SetMatrix<3, 4>(const char*, const glm::mat<3, 4, float>&);
+template void Material::SetMatrix<4, 3>(const char*, const glm::mat<4, 3, float>&);
+
+void Material::SetTexture(const char* name, Texture* texture) {
+ GLint location = glGetUniformLocation(mShader->GetProgram(), name);
+
+ for (auto& uniform : mBoundTextures) {
+ if (uniform.location == location) {
+ uniform.value.Attach(texture);
+ return;
+ }
+ }
+
+ auto& uniform = mBoundTextures.emplace_back();
+ uniform.value.Attach(texture);
+ uniform.location = location;
+}
+
+std::span<const Material::VectorUniform> Material::GetVectors() const {
+ return mBoundVectors;
+}
+
+std::span<const Material::MatrixUniform> Material::GetMatrices() const {
+ return mBoundMatrices;
+}
+
+std::span<const Material::TextureUniform> Material::GetTextures() const {
+ return mBoundTextures;
+}
+
+const Shader& Material::GetShader() const {
+ return *mShader;
+}
+
+static constexpr int IdentifyMatrixSize(int width, int height) {
+ return width * 10 + height;
+}
+
+void Material::UseUniforms() const {
+ for (auto& uniform : mBoundScalars) {
+ switch (uniform.actualType) {
+ case GL_FLOAT: glUniform1f(uniform.location, uniform.intValue); break;
+ case GL_INT: glUniform1i(uniform.location, uniform.intValue); break;
+ case GL_UNSIGNED_INT: glUniform1ui(uniform.location, uniform.intValue); break;
+ default: break;
+ }
+ }
+
+ for (auto& uniform : mBoundVectors) {
+ switch (uniform.actualLength) {
+ case 1: glUniform1fv(uniform.location, 1, &uniform.value[0]); break;
+ case 2: glUniform2fv(uniform.location, 1, &uniform.value[0]); break;
+ case 3: glUniform3fv(uniform.location, 1, &uniform.value[0]); break;
+ case 4: glUniform4fv(uniform.location, 1, &uniform.value[0]); break;
+ default: break;
+ }
+ }
+
+ for (auto& uniform : mBoundMatrices) {
+ switch (IdentifyMatrixSize(uniform.actualWidth, uniform.actualHeight)) {
+ case IdentifyMatrixSize(2, 2): glUniformMatrix2fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+ case IdentifyMatrixSize(3, 3): glUniformMatrix3fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+ case IdentifyMatrixSize(4, 4): glUniformMatrix4fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+
+ case IdentifyMatrixSize(2, 3): glUniformMatrix2x3fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+ case IdentifyMatrixSize(3, 2): glUniformMatrix3x2fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+
+ case IdentifyMatrixSize(2, 4): glUniformMatrix2x4fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+ case IdentifyMatrixSize(4, 2): glUniformMatrix4x2fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+
+ case IdentifyMatrixSize(3, 4): glUniformMatrix3x4fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+ case IdentifyMatrixSize(4, 3): glUniformMatrix4x3fv(uniform.location, 1, GL_FALSE, uniform.value); break;
+
+ default: break;
+ }
+ }
+
+ int i = 0;
+ for (auto& uniform : mBoundTextures) {
+ glActiveTexture(GL_TEXTURE0 + i);
+ glBindTexture(GL_TEXTURE_2D, uniform.value->GetHandle());
+ glUniform1i(uniform.location, i);
+ ++i;
+ }
+}
diff --git a/source/Material.hpp b/source/Material.hpp
new file mode 100644
index 0000000..6290a25
--- /dev/null
+++ b/source/Material.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include "RcPtr.hpp"
+#include "Shader.hpp"
+#include "Texture.hpp"
+
+#include <glad/glad.h>
+#include <cstddef>
+#include <cstdint>
+#include <glm/glm.hpp>
+#include <memory>
+#include <span>
+#include <string_view>
+#include <vector>
+
+// TODO support multiple sizes of vectors and matrices
+class Material : public RefCounted {
+public:
+ struct ScalarUniform {
+ union {
+ float floatValue;
+ int32_t intValue;
+ uint32_t uintValue;
+ };
+ GLenum actualType;
+ GLint location;
+ };
+
+ struct VectorUniform {
+ float value[4];
+ int actualLength;
+ GLint location;
+ };
+
+ struct MatrixUniform {
+ float value[16];
+ int actualWidth;
+ int actualHeight;
+ GLint location;
+ };
+
+ struct TextureUniform {
+ RcPtr<Texture> value;
+ GLint location;
+ };
+
+ RcPtr<Shader> mShader;
+ std::vector<ScalarUniform> mBoundScalars;
+ std::vector<VectorUniform> mBoundVectors;
+ std::vector<MatrixUniform> mBoundMatrices;
+ std::vector<TextureUniform> mBoundTextures;
+
+public:
+ Material(Shader* shader);
+
+ void SetFloat(const char* name, float value);
+ void SetInt(const char* name, int32_t value);
+ void SetUInt(const char* name, uint32_t value);
+
+ /// Instanciated for length == 1, 2, 3, 4
+ template <int length>
+ void SetVector(const char* name, const glm::vec<length, float>& vec);
+
+ /// Instanciated for sizes (2,2) (3,3) (4,4) (2,3) (3,2) (2,4) (4,2) (3,4) (4,3)
+ template <int width, int height>
+ void SetMatrix(const char* name, const glm::mat<width, height, float>& mat);
+
+ void SetTexture(const char* name, Texture* texture);
+
+ std::span<const VectorUniform> GetVectors() const;
+ std::span<const MatrixUniform> GetMatrices() const;
+ std::span<const TextureUniform> GetTextures() const;
+ const Shader& GetShader() const;
+
+ void UseUniforms() const;
+};
diff --git a/source/Mesh.cpp b/source/Mesh.cpp
new file mode 100644
index 0000000..3b0ee1f
--- /dev/null
+++ b/source/Mesh.cpp
@@ -0,0 +1 @@
+#include "Mesh.hpp"
diff --git a/source/Mesh.hpp b/source/Mesh.hpp
new file mode 100644
index 0000000..d383087
--- /dev/null
+++ b/source/Mesh.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "PodVector.hpp"
+#include "RcPtr.hpp"
+
+#include <cstdint>
+
+struct MeshVertex {
+ float x, y, z;
+ float u, v;
+ uint8_t r, g, b, a;
+};
+
+class Mesh : public RefCounted {
+private:
+ PodVector<MeshVertex> mVertex;
+};
diff --git a/source/Player.cpp b/source/Player.cpp
new file mode 100644
index 0000000..6575820
--- /dev/null
+++ b/source/Player.cpp
@@ -0,0 +1,50 @@
+#include "Player.hpp"
+
+// Keep the same number as # of fields in `struct {}` in PlayerKeyBinds
+constexpr int kPlayerKeyBindCount = 4;
+
+// Here be dragons: this treats consecutive fiels as an array, technically UB
+std::span<int> PlayerKeyBinds::GetKeyArray() {
+ return { &keyLeft, kPlayerKeyBindCount };
+}
+std::span<bool> PlayerKeyBinds::GetKeyStatusArray() {
+ return { &pressedLeft, kPlayerKeyBindCount };
+}
+
+void Player::Awaken() {
+}
+
+void Player::Resleep() {
+}
+
+void Player::Update() {
+ if (keybinds.pressedLeft) {
+ }
+ if (keybinds.pressedRight) {
+ }
+
+ // TODO jump controller
+
+ // TODO attack controller
+}
+
+void Player::HandleKeyInput(int key, int action) {
+ bool pressed;
+ if (action == GLFW_PRESS) {
+ pressed = true;
+ } else if (action == GLFW_REPEAT) {
+ return;
+ } else /* action == GLFW_RELEASE */ {
+ pressed = false;
+ }
+
+ for (int i = 0; i < kPlayerKeyBindCount; ++i) {
+ int kbKey = keybinds.GetKeyArray()[i];
+ bool& kbStatus = keybinds.GetKeyStatusArray()[i];
+
+ if (kbKey == key) {
+ kbStatus = pressed;
+ break;
+ }
+ }
+}
diff --git a/source/Player.hpp b/source/Player.hpp
new file mode 100644
index 0000000..1ed30cd
--- /dev/null
+++ b/source/Player.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "GameObject.hpp"
+#include "GameObjectTypeTag.hpp"
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <span>
+#include <vector>
+
+struct PlayerKeyBinds {
+ int keyLeft = GLFW_KEY_A;
+ int keyRight = GLFW_KEY_D;
+ int keyJump = GLFW_KEY_SPACE;
+ int keyAttack = GLFW_KEY_J;
+
+ bool pressedLeft = 0;
+ bool pressedRight = 0;
+ bool pressedJump = 0;
+ bool pressedAttack = 0;
+
+ std::span<int> GetKeyArray();
+ std::span<bool> GetKeyStatusArray();
+};
+
+class Player : public GameObject {
+public:
+ std::vector<GLFWkeyboard*> boundKeyboards;
+ PlayerKeyBinds keybinds;
+
+public:
+ using GameObject::GameObject;
+
+ virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_Player; }
+
+ virtual void Awaken() override;
+ virtual void Resleep() override;
+ virtual void Update() override;
+
+ void HandleKeyInput(int key, int action);
+};
diff --git a/source/PodVector.hpp b/source/PodVector.hpp
new file mode 100644
index 0000000..8f0281b
--- /dev/null
+++ b/source/PodVector.hpp
@@ -0,0 +1,295 @@
+// File adapted from dear-imgui's ImVector, implemented in https://github.com/ocornut/imgUI/blob/master/imgui.h
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <span>
+
+template <class T>
+class PodVector {
+public:
+ using value_type = T;
+ using iterator = value_type*;
+ using const_iterator = const value_type*;
+
+private:
+ int mSize;
+ int mCapacity;
+ T* mData;
+
+public:
+ PodVector() {
+ mSize = mCapacity = 0;
+ mData = nullptr;
+ }
+
+ PodVector(const PodVector<T>& src) {
+ mSize = mCapacity = 0;
+ mData = nullptr;
+ operator=(src);
+ }
+
+ PodVector<T>& operator=(const PodVector<T>& src) {
+ clear();
+ resize(src.mSize);
+ std::memcpy(mData, src.mData, (size_t)mSize * sizeof(T));
+ return *this;
+ }
+
+ PodVector(PodVector&& src) {
+ mSize = src.mSize;
+ mCapacity = src.mCapacity;
+ mData = src.mData;
+
+ src.mSize = src.mCapacity = 0;
+ src.mData = nullptr;
+ }
+
+ PodVector& operator=(PodVector&& src) {
+ if (this != &src) {
+ std::free(mData);
+
+ mSize = src.mSize;
+ mCapacity = src.mCapacity;
+ mData = src.mData;
+
+ src.mSize = src.mCapacity = 0;
+ src.mData = nullptr;
+ }
+ return *this;
+ }
+
+ ~PodVector() {
+ std::free(mData);
+ }
+
+ bool empty() const { return mSize == 0; }
+ int size() const { return mSize; }
+ int size_in_bytes() const { return mSize * (int)sizeof(T); }
+ int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); }
+ int capacity() const { return mCapacity; }
+
+ T& operator[](int i) {
+ assert(i >= 0 && i < mSize);
+ return mData[i];
+ }
+
+ const T& operator[](int i) const {
+ assert(i >= 0 && i < mSize);
+ return mData[i];
+ }
+
+ void clear() {
+ if (mData) {
+ mSize = mCapacity = 0;
+ std::free(mData);
+ mData = nullptr;
+ }
+ }
+
+ T* begin() { return mData; }
+ const T* begin() const { return mData; }
+ T* end() { return mData + mSize; }
+ const T* end() const { return mData + mSize; }
+
+ T& front() {
+ assert(mSize > 0);
+ return mData[0];
+ }
+
+ const T& front() const {
+ assert(mSize > 0);
+ return mData[0];
+ }
+
+ T& back() {
+ assert(mSize > 0);
+ return mData[mSize - 1];
+ }
+
+ const T& back() const {
+ assert(mSize > 0);
+ return mData[mSize - 1];
+ }
+
+ void swap(PodVector<T>& rhs) {
+ int rhs_size = rhs.mSize;
+ rhs.mSize = mSize;
+ mSize = rhs_size;
+ int rhs_cap = rhs.mCapacity;
+ rhs.mCapacity = mCapacity;
+ mCapacity = rhs_cap;
+ T* rhs_mDataTmp = rhs.mData;
+ rhs.mData = mData;
+ mData = rhs_mDataTmp;
+ }
+
+ int grow_capacity(int sz) const {
+ int newCapacity = mCapacity ? (mCapacity + mCapacity / 2) : 8;
+ return newCapacity > sz ? newCapacity : sz;
+ }
+
+ void resize(int new_size) {
+ if (new_size > mCapacity) reserve(grow_capacity(new_size));
+ mSize = new_size;
+ }
+
+ void resize_more(int size) {
+ resize(mSize + size);
+ }
+
+ void resize(int new_size, const T& v) {
+ if (new_size > mCapacity) reserve(grow_capacity(new_size));
+ if (new_size > mSize) {
+ for (int n = mSize; n < new_size; n++) {
+ std::memcpy(&mData[n], &v, sizeof(v));
+ }
+ }
+ mSize = new_size;
+ }
+
+ void resize_more(int size, const T& v) {
+ resize(mSize + size, v);
+ }
+
+ void shrink(int new_size) {
+ assert(new_size <= mSize);
+ mSize = new_size;
+ }
+
+ /// Resize a vector to a smaller mSize, guaranteed not to cause a reallocation
+ void reserve(int newCapacity) {
+ if (newCapacity <= mCapacity) return;
+ auto tmp = (T*)std::malloc((size_t)newCapacity * sizeof(T));
+ if (mData) {
+ std::memcpy(tmp, mData, (size_t)mSize * sizeof(T));
+ std::free(mData);
+ }
+ mData = tmp;
+ mCapacity = newCapacity;
+ }
+
+ void reserve_more(int size) {
+ reserve(mSize + size);
+ }
+
+ /// NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the PodVector data itself! e.g. v.push_back(v[10]) is forbidden.
+ void push_back(const T& v) {
+ if (mSize == mCapacity) reserve(grow_capacity(mSize + 1));
+ std::memcpy(&mData[mSize], &v, sizeof(v));
+ mSize++;
+ }
+
+ void pop_back() {
+ assert(mSize > 0);
+ mSize--;
+ }
+
+ void push_front(const T& v) {
+ if (mSize == 0) {
+ push_back(v);
+ } else {
+ insert(mData, v);
+ }
+ }
+
+ T* erase(const T* it) {
+ assert(it >= mData && it < mData + mSize);
+ const ptrdiff_t off = it - mData;
+ std::memmove(mData + off, mData + off + 1, ((size_t)mSize - (size_t)off - 1) * sizeof(T));
+ mSize--;
+ return mData + off;
+ }
+
+ T* erase(const T* it, const T* it_last) {
+ assert(it >= mData && it < mData + mSize && it_last > it && it_last <= mData + mSize);
+ const ptrdiff_t count = it_last - it;
+ const ptrdiff_t off = it - mData;
+ std::memmove(mData + off, mData + off + count, ((size_t)mSize - (size_t)off - count) * sizeof(T));
+ mSize -= (int)count;
+ return mData + off;
+ }
+
+ T* erase_unsorted(const T* it) {
+ assert(it >= mData && it < mData + mSize);
+ const ptrdiff_t off = it - mData;
+ if (it < mData + mSize - 1) std::memcpy(mData + off, mData + mSize - 1, sizeof(T));
+ mSize--;
+ return mData + off;
+ }
+
+ T* insert(const T* it, const T& v) {
+ assert(it >= mData && it <= mData + mSize);
+ const ptrdiff_t off = it - mData;
+ if (mSize == mCapacity) reserve(grow_capacity(mSize + 1));
+ if (off < (int)mSize) std::memmove(mData + off + 1, mData + off, ((size_t)mSize - (size_t)off) * sizeof(T));
+ std::memcpy(&mData[off], &v, sizeof(v));
+ mSize++;
+ return mData + off;
+ }
+
+ bool contains(const T& v) const {
+ const T* data = mData;
+ const T* dataEnd = mData + mSize;
+ while (data < dataEnd) {
+ if (*data++ == v) return true;
+ }
+ return false;
+ }
+
+ T* find(const T& v) {
+ T* data = mData;
+ const T* dataEnd = mData + mSize;
+ while (data < dataEnd)
+ if (*data == v)
+ break;
+ else
+ ++data;
+ return data;
+ }
+
+ const T* find(const T& v) const {
+ const T* data = mData;
+ const T* dataEnd = mData + mSize;
+ while (data < dataEnd)
+ if (*data == v)
+ break;
+ else
+ ++data;
+ return data;
+ }
+
+ bool find_erase(const T& v) {
+ const T* it = find(v);
+ if (it < mData + mSize) {
+ erase(it);
+ return true;
+ }
+ return false;
+ }
+
+ bool find_erase_unsorted(const T& v) {
+ const T* it = find(v);
+ if (it < mData + mSize) {
+ erase_unsorted(it);
+ return true;
+ }
+ return false;
+ }
+
+ int index_from_ptr(const T* it) const {
+ assert(it >= mData && it < mData + mSize);
+ const ptrdiff_t off = it - mData;
+ return (int)off;
+ }
+
+ // Custom utility functions
+
+ std::span<T> as_span() { return { mData, (size_t)mSize }; }
+ std::span<uint8_t> as_data_span() { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; }
+ std::span<const T> as_span() const { return { mData, (size_t)mSize }; }
+ std::span<const uint8_t> as_data_span() const { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; }
+};
diff --git a/source/RcPtr.hpp b/source/RcPtr.hpp
new file mode 100644
index 0000000..90097e1
--- /dev/null
+++ b/source/RcPtr.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include "Macros.hpp"
+#include "TypeTraits.hpp"
+
+#include <cstddef>
+#include <optional>
+#include <type_traits>
+
+class RefCounted {
+public:
+ // DO NOT MODIFY this field, unless explicitly documented the use
+ size_t refCount = 0;
+};
+
+template <class T, class TDeleter = DefaultDeleter<T>>
+class RcPtr : TDeleter {
+private:
+ static_assert(std::is_base_of_v<RefCounted, T>);
+ T* mPtr;
+
+public:
+ RcPtr()
+ : mPtr{ nullptr } {
+ }
+
+ explicit RcPtr(T* ptr)
+ : mPtr{ ptr } {
+ if (ptr) {
+ ++ptr->refCount;
+ }
+ }
+
+ ~RcPtr() {
+ CleanUp();
+ }
+
+ void Attach(T* ptr) {
+ CleanUp();
+ mPtr = ptr;
+ if (ptr) {
+ ++ptr->refCount;
+ }
+ }
+
+ void Detatch() {
+ CleanUp();
+ mPtr = nullptr;
+ }
+
+ RcPtr(const RcPtr& that)
+ : mPtr{ that.mPtr } {
+ if (mPtr) {
+ ++mPtr->refCount;
+ }
+ }
+
+ RcPtr& operator=(const RcPtr& that) {
+ CleanUp();
+ mPtr = that.mPtr;
+ if (mPtr) {
+ ++mPtr->refCount;
+ }
+ return *this;
+ }
+
+ RcPtr(RcPtr&& that)
+ : mPtr{ that.mPtr } {
+ that.mPtr = nullptr;
+ }
+
+ RcPtr& operator=(RcPtr&& that) {
+ CleanUp();
+ mPtr = that.mPtr;
+ that.mPtr = nullptr;
+ return *this;
+ }
+
+ template <class TBase>
+ requires std::is_base_of_v<TBase, T>
+ operator RcPtr<TBase>() const {
+ return RcPtr<TBase>(mPtr);
+ }
+
+ bool operator==(std::nullptr_t ptr) const {
+ return mPtr == nullptr;
+ }
+
+ bool operator==(const T* ptr) const {
+ return mPtr == ptr;
+ }
+
+ bool operator==(T* ptr) const {
+ return mPtr == ptr;
+ }
+
+ T* Get() const {
+ return mPtr;
+ }
+
+ T& operator*() const { return *mPtr; }
+ T* operator->() const { return mPtr; }
+
+private:
+ void CleanUp() {
+ if (mPtr) {
+ --mPtr->refCount;
+ if (mPtr->refCount == 0) {
+ TDeleter::operator()(mPtr);
+ }
+ }
+ }
+};
diff --git a/source/SceneThings.cpp b/source/SceneThings.cpp
new file mode 100644
index 0000000..ec7a82f
--- /dev/null
+++ b/source/SceneThings.cpp
@@ -0,0 +1,19 @@
+#include "SceneThings.hpp"
+
+void BuildingObject::SetMeshMaterial(Material* material) {
+ mMaterial.Attach(material);
+ // TODO update render
+}
+
+const Material* BuildingObject::GetMeshMaterial() const {
+ return mMaterial.Get();
+}
+
+void BuildingObject::SetMesh(Mesh* mesh) {
+ mMesh.Attach(mesh);
+ // TODO update render
+}
+
+const Mesh* BuildingObject::GetMesh() const {
+ return mMesh.Get();
+}
diff --git a/source/SceneThings.hpp b/source/SceneThings.hpp
new file mode 100644
index 0000000..0e213a6
--- /dev/null
+++ b/source/SceneThings.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "GameObject.hpp"
+
+#include <vector>
+
+class BuildingObject : public GameObject {
+private:
+ RcPtr<Mesh> mMesh;
+ RcPtr<Material> mMaterial;
+
+public:
+ using GameObject::GameObject;
+
+ virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_Building; }
+
+ void SetMeshMaterial(Material* material);
+ virtual const Material* GetMeshMaterial() const override;
+ void SetMesh(Mesh* mesh);
+ virtual const Mesh* GetMesh() const override;
+};
diff --git a/source/ScopeGuard.hpp b/source/ScopeGuard.hpp
new file mode 100644
index 0000000..b4a1749
--- /dev/null
+++ b/source/ScopeGuard.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "Macros.hpp"
+
+#include <utility>
+
+template <class TCleanupFunc>
+class ScopeGuard {
+private:
+ TCleanupFunc mFunc;
+ bool mDismissed = false;
+
+public:
+ /// Specifically left this implicit so that constructs like
+ /// \code
+ /// ScopeGuard sg = [&]() { res.Cleanup(); };
+ /// \endcode
+ /// would work. It is highly discourage and unlikely that one would want to use ScopeGuard as a function
+ /// parameter, so the normal argument that implicit conversion are harmful doesn't really apply here.
+ // Deliberately not explicit to allow usages like: ScopeGuard var = lambda;
+ ScopeGuard(TCleanupFunc&& function) noexcept
+ : mFunc{ std::move(function) } {
+ }
+
+ ~ScopeGuard() noexcept {
+ if (!mDismissed) {
+ mFunc();
+ }
+ }
+
+ ScopeGuard(const ScopeGuard&) = delete;
+ ScopeGuard& operator=(const ScopeGuard&) = delete;
+
+ ScopeGuard(ScopeGuard&& that) noexcept
+ : mFunc{ std::move(that.mFunc) } {
+ that.Cancel();
+ }
+
+ ScopeGuard& operator=(ScopeGuard&& that) noexcept {
+ if (!mDismissed) {
+ mFunc();
+ }
+ this->mFunc = std::move(that.mFunc);
+ this->cancelled = std::exchange(that.cancelled, true);
+ }
+
+ void Dismiss() noexcept {
+ mDismissed = true;
+ }
+};
+
+#define SCOPE_GUARD(name) ScopeGuard name = [&]()
+#define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]()
diff --git a/source/Shader.cpp b/source/Shader.cpp
new file mode 100644
index 0000000..458491a
--- /dev/null
+++ b/source/Shader.cpp
@@ -0,0 +1,327 @@
+#include "Shader.hpp"
+
+#include "ScopeGuard.hpp"
+
+#include <cstddef>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <utility>
+
+using namespace std::literals::string_literals;
+using namespace std::literals::string_view_literals;
+
+Shader::~Shader() {
+ glDeleteProgram(mHandle);
+}
+
+namespace ProjectBrussel_UNITY_ID {
+Shader::ErrorCode LoadFile(std::string& out, const char* filePath) {
+ std::ifstream ifs(filePath);
+ if (!ifs) {
+ return Shader::FileIOFailed;
+ }
+
+ std::stringstream buf;
+ buf << ifs.rdbuf();
+ out = buf.str();
+
+ return Shader::Success;
+}
+
+// Grabs section [begin, end)
+Shader::ErrorCode CreateShader(GLuint& out, const char* src, int beginIdx, int endIdx, GLenum type) {
+ out = glCreateShader(type);
+
+ const GLchar* begin = &src[beginIdx];
+ const GLint len = endIdx - beginIdx;
+ glShaderSource(out, 1, &begin, &len);
+
+ glCompileShader(out);
+ GLint compileStatus;
+ glGetShaderiv(out, GL_COMPILE_STATUS, &compileStatus);
+ if (compileStatus == GL_FALSE) {
+ GLint len;
+ glGetShaderiv(out, GL_INFO_LOG_LENGTH, &len);
+
+ std::string log(len, '\0');
+ glGetShaderInfoLog(out, len, nullptr, log.data());
+
+ return Shader ::CompilationFailed;
+ }
+
+ return Shader::Success;
+}
+
+Shader::ErrorCode CreateShader(GLuint& out, std::string_view str, GLenum type) {
+ return CreateShader(out, str.data(), 0, str.size(), type);
+}
+
+Shader::ErrorCode LinkShaderProgram(GLuint program) {
+ glLinkProgram(program);
+ GLint linkStatus;
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus == GL_FALSE) {
+ GLint len;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
+
+ std::string log(len, '\0');
+ glGetProgramInfoLog(program, len, nullptr, log.data());
+
+ return Shader::LinkingFailed;
+ }
+
+ return Shader::Success;
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+#define CATCH_ERROR_IMPL(x, name) \
+ auto name = x; \
+ if (name != Shader::Success) { \
+ return name; \
+ }
+#define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result))
+
+Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) {
+ if (IsValid()) {
+ return ShaderAlreadyCreated;
+ }
+
+ GLuint program = glCreateProgram();
+ ScopeGuard sg = [&]() { glDeleteProgram(program); };
+
+ GLuint vertex = 0;
+ DEFER { glDeleteShader(vertex); };
+ if (!sources.vertex.empty()) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, sources.vertex, GL_VERTEX_SHADER));
+ glAttachShader(program, vertex);
+ }
+
+ GLuint geometry = 0;
+ DEFER { glDeleteShader(geometry); };
+ if (!sources.geometry.empty()) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, sources.geometry, GL_GEOMETRY_SHADER));
+ glAttachShader(program, geometry);
+ }
+
+ GLuint tessControl = 0;
+ DEFER { glDeleteShader(tessControl); };
+ if (!sources.tessControl.empty()) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, sources.tessControl, GL_TESS_CONTROL_SHADER));
+ glAttachShader(program, tessControl);
+ }
+
+ GLuint tessEval = 0;
+ DEFER { glDeleteShader(tessEval); };
+ if (!sources.tessEval.empty()) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, sources.tessEval, GL_TESS_EVALUATION_SHADER));
+ glAttachShader(program, tessEval);
+ }
+
+ GLuint fragment = 0;
+ DEFER { glDeleteShader(fragment); };
+ if (!sources.fragment.empty()) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, sources.fragment, GL_FRAGMENT_SHADER));
+ glAttachShader(program, fragment);
+ }
+
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program));
+
+ sg.Dismiss();
+ mHandle = program;
+
+ return Success;
+}
+
+Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
+ if (IsValid()) {
+ return ShaderAlreadyCreated;
+ }
+
+ GLuint program = glCreateProgram();
+ ScopeGuard sg = [&]() { glDeleteProgram(program); };
+
+ GLuint vertex = 0;
+ DEFER { glDeleteShader(vertex); };
+ if (files.vertex) {
+ std::string src;
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.vertex));
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, src, GL_VERTEX_SHADER));
+ glAttachShader(program, vertex);
+ }
+
+ GLuint geometry = 0;
+ DEFER { glDeleteShader(geometry); };
+ if (files.geometry) {
+ std::string src;
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.geometry));
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, src, GL_GEOMETRY_SHADER));
+ glAttachShader(program, geometry);
+ }
+
+ GLuint tessControl = 0;
+ DEFER { glDeleteShader(tessControl); };
+ if (files.tessControl) {
+ std::string src;
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessControl));
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, src, GL_TESS_CONTROL_SHADER));
+ glAttachShader(program, tessControl);
+ }
+
+ GLuint tessEval = 0;
+ DEFER { glDeleteShader(tessEval); };
+ if (files.tessEval) {
+ std::string src;
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessEval));
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, src, GL_TESS_EVALUATION_SHADER));
+ glAttachShader(program, tessEval);
+ }
+
+ GLuint fragment = 0;
+ DEFER { glDeleteShader(fragment); };
+ if (files.fragment) {
+ std::string src;
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.fragment));
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, src, GL_FRAGMENT_SHADER));
+ glAttachShader(program, fragment);
+ }
+
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program));
+
+ sg.Dismiss();
+ mHandle = program;
+
+ return Success;
+}
+
+Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
+ GLuint vertex = 0;
+ DEFER { glDeleteShader(vertex); };
+
+ GLuint geometry = 0;
+ DEFER { glDeleteShader(geometry); };
+
+ GLuint tessControl = 0;
+ DEFER { glDeleteShader(tessControl); };
+
+ GLuint tessEval = 0;
+ DEFER { glDeleteShader(tessEval); };
+
+ GLuint fragment = 0;
+ DEFER { glDeleteShader(fragment); };
+
+ int prevBegin = -1; // Excluding #type marker
+ int prevEnd = -1; // [begin, end)
+ std::string prevShaderVariant;
+
+ auto CommitSection = [&]() -> ErrorCode {
+ if (prevBegin == -1 || prevEnd == -1) {
+ // Not actually "succeeding" here, but we just want to skip this call and continue
+ return Success;
+ }
+
+ if (prevShaderVariant == "vertex" && !vertex) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, source.data(), prevBegin, prevEnd, GL_VERTEX_SHADER));
+ } else if (prevShaderVariant == "geometry" && !geometry) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, source.data(), prevBegin, prevEnd, GL_GEOMETRY_SHADER));
+ } else if (prevShaderVariant == "tessellation_control" && !tessControl) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, source.data(), prevBegin, prevEnd, GL_TESS_CONTROL_SHADER));
+ } else if (prevShaderVariant == "tessellation_evaluation" && !tessEval) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, source.data(), prevBegin, prevEnd, GL_TESS_EVALUATION_SHADER));
+ } else if (prevShaderVariant == "fragment" && !fragment) {
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER));
+ } else {
+ return InvalidShaderVariant;
+ }
+
+ prevBegin = -1;
+ prevEnd = -1;
+ prevShaderVariant.clear();
+
+ return Success;
+ };
+
+ constexpr const char* kMarker = "#type ";
+ bool matchingDirective = true; // If true, we are matching marker pattern; if false, we are accumulating shader variant identifier
+ int matchIndex = 0; // Current index of the pattern trying to match
+ std::string shaderVariant;
+
+ // Don't use utf8 iterator, shader sources are expected to be ASCII only
+ for (size_t i = 0; i < source.size(); ++i) {
+ char c = source[i];
+
+ if (matchingDirective) {
+ if (c == kMarker[matchIndex]) {
+ // Matched the expected character, go to next char in pattern
+ matchIndex++;
+
+ // If we are at the end of the marker pattern...
+ if (kMarker[matchIndex] == '\0') {
+ matchingDirective = false;
+ matchIndex = 0;
+ continue;
+ }
+
+ // This might be a shader variant directive -> might be end of a section
+ if (c == '#') {
+ prevEnd = i;
+ continue;
+ }
+ } else {
+ // Unexpected character, rollback to beginning
+ matchIndex = 0;
+ }
+ } else {
+ if (c == '\n') {
+ // Found complete shader variant directive
+
+ CATCH_ERROR(CommitSection()); // Try commit section, for the first apparent of #type this should do nothing, as `prevEnd` will still be -1
+ prevBegin = i + 1; // +1 to skip new line (technically not needed)
+ prevShaderVariant = std::move(shaderVariant);
+
+ matchingDirective = true;
+ shaderVariant.clear();
+ } else {
+ // Simply accumulate to shader variant buffer
+ shaderVariant += c;
+ }
+ }
+ }
+
+ // Commit the last section
+ prevEnd = static_cast<int>(source.size());
+ CATCH_ERROR(CommitSection());
+
+ GLuint program = glCreateProgram();
+ ScopeGuard sg = [&]() { glDeleteProgram(program); };
+
+ if (vertex) glAttachShader(program, vertex);
+ if (geometry) glAttachShader(program, geometry);
+ if (tessControl) glAttachShader(program, tessControl);
+ if (tessEval) glAttachShader(program, tessEval);
+ if (fragment) glAttachShader(program, fragment);
+
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program));
+
+ sg.Dismiss();
+ mHandle = program;
+
+ return Success;
+}
+
+Shader::ErrorCode Shader::InitFromFile(const char* filePath) {
+ std::string src;
+ CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, filePath));
+
+ return InitFromSource(src);
+}
+
+#undef CATCH_ERROR
+
+GLuint Shader::GetProgram() const {
+ return mHandle;
+}
+
+bool Shader::IsValid() const {
+ return mHandle != 0;
+}
diff --git a/source/Shader.hpp b/source/Shader.hpp
new file mode 100644
index 0000000..b481bd6
--- /dev/null
+++ b/source/Shader.hpp
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "RcPtr.hpp"
+
+#include <glad/glad.h>
+#include <memory>
+#include <string_view>
+
+class ShaderDetails : public RefCounted {
+public:
+};
+
+class Shader : public RefCounted {
+private:
+ GLuint mHandle = 0;
+
+public:
+ Shader() = default;
+ ~Shader();
+ Shader(const Shader&) = delete;
+ Shader& operator=(const Shader&) = delete;
+ Shader(Shader&&) = default;
+ Shader& operator=(Shader&&) = default;
+
+ enum ErrorCode {
+ Success,
+ /// Generated when Init*() functions are called on an already initialized Shader object.
+ ShaderAlreadyCreated,
+ /// Generated when the one-source-file text contains invalid or duplicate shader variants.
+ InvalidShaderVariant,
+ FileIOFailed,
+ CompilationFailed,
+ LinkingFailed,
+ };
+
+ struct ShaderSources {
+ std::string_view vertex;
+ std::string_view geometry;
+ std::string_view tessControl;
+ std::string_view tessEval;
+ std::string_view fragment;
+ };
+
+ /// Create shader by compiling each shader source file separately and then combining them together
+ /// into a Shader object.
+ ErrorCode InitFromSources(const ShaderSources& sources);
+
+ struct ShaderFilePaths {
+ const char* vertex = nullptr;
+ const char* geometry = nullptr;
+ const char* tessControl = nullptr;
+ const char* tessEval = nullptr;
+ const char* fragment = nullptr;
+ };
+
+ /// Create shader by loading each specified file and combining them together to form a shader object.
+ /// Use the mental model that this function simply loads the files from disk (synchronously) and then
+ /// passing them to `FromSource(const ShaderSources& source)`.
+ ErrorCode InitFromFiles(const ShaderFilePaths& files);
+
+ /// The given text will be split into different shader sections according to #type directives,
+ /// and combined to form a Shader object.
+ /// For OpenGL, this process involves compililng each section separately and then linking them
+ /// together.
+ ///
+ /// There are a directive for each shader type
+ /// - `#type vertex`: Vertex shader
+ /// - `#type geometry`: Geometry shader
+ /// - `#type tessellation_control`: Tessellation control shader
+ /// - `#type tessellation_evaluation`: Tessellation evaluation shader
+ /// - `#type fragment`: Fragment shader
+ ErrorCode InitFromSource(std::string_view source);
+
+ /// Create shader by loading the file at the `file`, and then giving the contents to
+ /// `FromSource(std::string_view source)`.
+ ErrorCode InitFromFile(const char* file);
+
+ GLuint GetProgram() const;
+
+ bool IsValid() const;
+};
diff --git a/source/Texture.cpp b/source/Texture.cpp
new file mode 100644
index 0000000..968c4bc
--- /dev/null
+++ b/source/Texture.cpp
@@ -0,0 +1,99 @@
+#include "Texture.hpp"
+
+#include "PodVector.hpp"
+
+#include <stb_image.h>
+#include <stb_rect_pack.h>
+#include <cstdint>
+#include <cstring>
+#include <deque>
+#include <filesystem>
+#include <fstream>
+#include <stdexcept>
+#include <utility>
+
+Texture::~Texture() {
+ glDeleteTextures(1, &mHandle);
+}
+
+static GLenum MapTextureFilteringToGL(Texture::Filtering option) {
+ switch (option) {
+ case Texture::LinearFilter: return GL_LINEAR;
+ case Texture::NearestFilter: return GL_NEAREST;
+ }
+ return 0;
+}
+
+bool Texture::InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically) {
+ int width, height;
+ int channels;
+
+ stbi_set_flip_vertically_on_load(flipVertically);
+ auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, 4);
+ if (!result) {
+ return false;
+ }
+
+ glDeleteTextures(1, &mHandle); // In case the caller gave us
+ glGenTextures(1, &mHandle);
+ glBindTexture(GL_TEXTURE_2D, mHandle);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter));
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result);
+
+ mInfo.size = { width, height };
+ mInfo.isAtlas = false;
+ return true;
+}
+
+// bool Texture::InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically) {
+// GLenum sourceFormat;
+// switch (image.GetChannels()) {
+// case 1: sourceFormat = GL_RED; break;
+// case 2: sourceFormat = GL_RG; break;
+// case 3: sourceFormat = GL_RGB; break;
+// case 4: sourceFormat = GL_RGBA; break;
+// default: return false;
+// }
+
+// auto size = image.GetSize();
+
+// std::unique_ptr<uint8_t[]> dataStorage;
+// uint8_t* dataPtr;
+// if (flipVertically) {
+// dataStorage = std::make_unique<uint8_t[]>(image.GetDataLength());
+// dataPtr = dataStorage.get();
+
+// size_t rowStride = size.width * image.GetChannels() * sizeof(uint8_t);
+// for (size_t y = 0; y < size.height; ++y) {
+// size_t invY = (size.height - 1) - y;
+// std::memcpy(dataPtr + invY * rowStride, image.GetDataPtr() + y * rowStride, rowStride);
+// }
+// } else {
+// // dataStorage is unused, we read pixels directly from `image`
+// dataPtr = image.GetDataPtr();
+// }
+
+// glDeleteTextures(1, &mHandle);
+// glGenTextures(1, &mHandle);
+// glBindTexture(GL_TEXTURE_2D, mHandle);
+// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter));
+// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter));
+// glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.width, size.height, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr);
+
+// mInfo.size = size;
+// mInfo.isAtlas = false;
+// return true;
+// }
+
+const TextureInfo& Texture::GetInfo() const {
+ return mInfo;
+}
+
+GLuint Texture::GetHandle() const {
+ return mHandle;
+}
+
+bool Texture::IsValid() const {
+ return mHandle != 0;
+}
diff --git a/source/Texture.hpp b/source/Texture.hpp
new file mode 100644
index 0000000..c372998
--- /dev/null
+++ b/source/Texture.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "RcPtr.hpp"
+
+#include <glad/glad.h>
+#include <glm/glm.hpp>
+#include <memory>
+
+// TODO abstract texture traits such as component sizes from OpenGL
+
+class TextureInfo {
+public:
+ glm::ivec2 size;
+ bool isAtlas = false;
+};
+
+class Texture : public RefCounted {
+ friend class TextureStitcher;
+
+private:
+ TextureInfo mInfo;
+ GLuint mHandle = 0;
+
+public:
+ Texture() = default;
+ ~Texture();
+
+ Texture(const Texture&) = delete;
+ Texture& operator=(const Texture&) = delete;
+ Texture(Texture&&) = default;
+ Texture& operator=(Texture&&) = default;
+
+ enum Filtering {
+ LinearFilter,
+ NearestFilter,
+ };
+
+ struct TextureProperties {
+ Filtering minifyingFilter = LinearFilter;
+ Filtering magnifyingFilter = LinearFilter;
+ };
+
+ bool InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically = false);
+ // bool InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically = false);
+
+ const TextureInfo& GetInfo() const;
+ GLuint GetHandle() const;
+
+ bool IsValid() const;
+};
+
+/// A pure numerical subregion of a texture. u0/v0 are the UV coordinates of bottom left
+/// corner, and u1/v1 are the top left corner.
+struct Subregion {
+ /// Bottom left corner
+ float u0 = 0.0f;
+ float v0 = 0.0f;
+ /// Top right corner
+ float u1 = 0.0f;
+ float v1 = 0.0f;
+};
+
+/// A subregion of a specific texture.
+struct TextureSubregion : public Subregion {
+ RcPtr<Texture> atlasTexture;
+};
diff --git a/source/TypeTraits.hpp b/source/TypeTraits.hpp
new file mode 100644
index 0000000..cca9a1f
--- /dev/null
+++ b/source/TypeTraits.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+template <class T>
+struct DefaultDeleter {
+ void operator()(T* ptr) const {
+ delete ptr;
+ }
+};
+
+template <class>
+struct RemoveMemberPtrImpl {};
+
+template <class T, class U>
+struct RemoveMemberPtrImpl<U T::*> {
+ using Type = U;
+};
+
+template <class T>
+using RemoveMemberPtr = typename RemoveMemberPtrImpl<T>::Type;
diff --git a/source/World.cpp b/source/World.cpp
new file mode 100644
index 0000000..907f056
--- /dev/null
+++ b/source/World.cpp
@@ -0,0 +1,68 @@
+#include "World.hpp"
+
+#include <glad/glad.h>
+
+namespace ProjectBrussel_UNITY_ID {
+template <class TFunction>
+void CallGameObjectRecursive(GameObject* start, TFunction&& func) {
+ PodVector<GameObject*> stack;
+ stack.push_back(start);
+
+ while (!stack.empty()) {
+ auto obj = stack.back();
+ stack.pop_back();
+
+ for (auto child : obj->GetChildren()) {
+ stack.push_back(child);
+ }
+
+ func(obj);
+ }
+}
+
+struct DrawCall {
+ GLuint vao;
+ GLuint vbo;
+};
+} // namespace ProjectBrussel_UNITY_ID
+
+struct GameWorld::RenderData {
+ void SubmitDrawCalls() {
+ // TODO
+ }
+};
+
+GameWorld::GameWorld()
+ : mRender{ new RenderData() }
+ , mRoot(this) {
+}
+
+GameWorld::~GameWorld() {
+ delete mRender;
+ for (auto child : mRoot.GetChildren()) {
+ GameObject::FreeRecursive(child);
+ }
+}
+
+void GameWorld::Awaken() {
+ if (mAwakened) return;
+
+ ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [](GameObject* obj) { obj->Awaken(); });
+ mAwakened = true;
+}
+
+void GameWorld::Resleep() {
+ if (!mAwakened) return;
+
+ ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [](GameObject* obj) { obj->Resleep(); });
+ mAwakened = false;
+}
+
+void GameWorld::Update() {
+ ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [this](GameObject* obj) {
+ obj->Update();
+ });
+}
+
+void GameWorld::Draw() {
+}
diff --git a/source/World.hpp b/source/World.hpp
new file mode 100644
index 0000000..ab33ecb
--- /dev/null
+++ b/source/World.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "GameObject.hpp"
+
+class GameWorld {
+private:
+ class RenderData;
+ RenderData* mRender;
+
+ GameObject mRoot;
+ bool mAwakened = false;
+
+public:
+ GameWorld();
+ ~GameWorld();
+
+ GameWorld(const GameWorld&) = delete;
+ GameWorld& operator=(const GameWorld&) = delete;
+ GameWorld(GameWorld&&) = default;
+ GameWorld& operator=(GameWorld&&) = default;
+
+ bool IsAwake() const { return mAwakened; }
+ void Awaken();
+ void Resleep();
+ void Update();
+
+ void Draw();
+
+ const GameObject& GetRoot() const { return mRoot; }
+ GameObject& GetRoot() { return mRoot; }
+};
diff --git a/source/main.cpp b/source/main.cpp
index b98d62b..8f61696 100644
--- a/source/main.cpp
+++ b/source/main.cpp
@@ -18,6 +18,14 @@ static void GlfwErrorCallback(int error, const char* description) {
fprintf(stderr, "Glfw Error %d: %s\n", error, description);
}
+static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+ GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard();
+ if (keyboard) {
+ App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
+ app->HandleKey(keyboard, key, action);
+ }
+}
+
int main() {
if (!glfwInit()) {
return -1;
@@ -46,10 +54,18 @@ int main() {
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
+ App app;
+
GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr);
if (window == nullptr) {
return -2;
}
+
+ glfwSetWindowUserPointer(window, &app);
+
+ // Window callbacks are retained by ImGui GLFW backend
+ glfwSetKeyCallback(window, &GlfwKeyCallback);
+
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
@@ -63,7 +79,7 @@ int main() {
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
- App app;
+ app.Init();
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
@@ -84,6 +100,7 @@ int main() {
glfwSwapBuffers(window);
}
+ app.Shutdown();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();