aboutsummaryrefslogtreecommitdiff
path: root/src/crepe
diff options
context:
space:
mode:
authorLoek Le Blansch <loek@pipeframe.xyz>2024-10-31 18:41:30 +0100
committerLoek Le Blansch <loek@pipeframe.xyz>2024-10-31 18:41:30 +0100
commit8e3367b186e60eb1e33bf58a066823cb00a7566e (patch)
treec4038a31993767276efec5fa1b1a37dff3b79465 /src/crepe
parentb7df77d6cc26cb9ee46891d7108f01734b3104dd (diff)
parent35ef3ba91ce9e00466508f2388f4c1dd2321b505 (diff)
Merge branch 'master' into poc/audio-miniaudio
Diffstat (limited to 'src/crepe')
-rw-r--r--src/crepe/Asset.cpp16
-rw-r--r--src/crepe/Asset.h33
-rw-r--r--src/crepe/CMakeLists.txt39
-rw-r--r--src/crepe/Collider.cpp5
-rw-r--r--src/crepe/Collider.h14
-rw-r--r--src/crepe/CollisionSystem.cpp7
-rw-r--r--src/crepe/CollisionSystem.h11
-rw-r--r--src/crepe/Component.cpp5
-rw-r--r--src/crepe/Component.h22
-rw-r--r--src/crepe/ComponentManager.cpp29
-rw-r--r--src/crepe/ComponentManager.h62
-rw-r--r--src/crepe/ComponentManager.hpp147
-rw-r--r--src/crepe/Particle.cpp20
-rw-r--r--src/crepe/Particle.h22
-rw-r--r--src/crepe/ParticleSystem.cpp62
-rw-r--r--src/crepe/ParticleSystem.h18
-rw-r--r--src/crepe/PhysicsSystem.cpp62
-rw-r--r--src/crepe/PhysicsSystem.h11
-rw-r--r--src/crepe/Position.h10
-rw-r--r--src/crepe/RenderSystem.cpp42
-rw-r--r--src/crepe/RenderSystem.h17
-rw-r--r--src/crepe/SDLApp.cpp71
-rw-r--r--src/crepe/SDLApp.h26
-rw-r--r--src/crepe/SDLContext.cpp158
-rw-r--r--src/crepe/SDLContext.h52
-rw-r--r--src/crepe/ScriptSystem.cpp47
-rw-r--r--src/crepe/ScriptSystem.h26
-rw-r--r--src/crepe/Sound.cpp60
-rw-r--r--src/crepe/Sound.h81
-rw-r--r--src/crepe/SoundContext.cpp20
-rw-r--r--src/crepe/SoundContext.h26
-rw-r--r--src/crepe/System.h22
-rw-r--r--src/crepe/api/AssetManager.cpp17
-rw-r--r--src/crepe/api/AssetManager.h35
-rw-r--r--src/crepe/api/AssetManager.hpp24
-rw-r--r--src/crepe/api/AudioSource.cpp23
-rw-r--r--src/crepe/api/AudioSource.h41
-rw-r--r--src/crepe/api/BehaviorScript.cpp0
-rw-r--r--src/crepe/api/BehaviorScript.h35
-rw-r--r--src/crepe/api/BehaviorScript.hpp22
-rw-r--r--src/crepe/api/CMakeLists.txt31
-rw-r--r--src/crepe/api/CircleCollider.h13
-rw-r--r--src/crepe/api/Color.cpp34
-rw-r--r--src/crepe/api/Color.h37
-rw-r--r--src/crepe/api/Config.h40
-rw-r--r--src/crepe/api/Force.cpp21
-rw-r--r--src/crepe/api/Force.h17
-rw-r--r--src/crepe/api/GameObject.cpp7
-rw-r--r--src/crepe/api/GameObject.h24
-rw-r--r--src/crepe/api/GameObject.hpp15
-rw-r--r--src/crepe/api/ParticleEmitter.cpp37
-rw-r--r--src/crepe/api/ParticleEmitter.h42
-rw-r--r--src/crepe/api/Point.h11
-rw-r--r--src/crepe/api/Rigidbody.cpp8
-rw-r--r--src/crepe/api/Rigidbody.h30
-rw-r--r--src/crepe/api/Script.cpp3
-rw-r--r--src/crepe/api/Script.h38
-rw-r--r--src/crepe/api/Script.hpp25
-rw-r--r--src/crepe/api/Sprite.cpp20
-rw-r--r--src/crepe/api/Sprite.h32
-rw-r--r--src/crepe/api/Texture.cpp32
-rw-r--r--src/crepe/api/Texture.h33
-rw-r--r--src/crepe/api/Transform.cpp15
-rw-r--r--src/crepe/api/Transform.h27
-rw-r--r--src/crepe/main.cpp3
-rw-r--r--src/crepe/util/CMakeLists.txt12
-rw-r--r--src/crepe/util/color.cpp91
-rw-r--r--src/crepe/util/color.h51
-rw-r--r--src/crepe/util/fmt.cpp33
-rw-r--r--src/crepe/util/fmt.h10
-rw-r--r--src/crepe/util/log.cpp53
-rw-r--r--src/crepe/util/log.h40
72 files changed, 2320 insertions, 5 deletions
diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp
new file mode 100644
index 0000000..8a2a11c
--- /dev/null
+++ b/src/crepe/Asset.cpp
@@ -0,0 +1,16 @@
+#include <filesystem>
+
+#include "Asset.h"
+
+using namespace crepe;
+
+Asset::Asset(const std::string & src) {
+ // FIXME: restore this
+ // this->src = std::filesystem::canonical(src);
+ this->src = src;
+ this->file = std::ifstream(this->src, std::ios::in | std::ios::binary);
+}
+
+const std::istream & Asset::read() { return this->file; }
+
+const char * Asset::canonical() { return this->src.c_str(); }
diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h
new file mode 100644
index 0000000..0cb5834
--- /dev/null
+++ b/src/crepe/Asset.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <fstream>
+#include <iostream>
+#include <string>
+
+namespace crepe {
+
+/**
+ * \brief Asset location helper
+ *
+ * This class is used to locate and canonicalize paths to game asset files, and
+ * should *always* be used when retrieving files from disk.
+ */
+class Asset {
+public:
+ /**
+ * \param src Unique identifier to asset
+ */
+ Asset(const std::string & src);
+
+public:
+ //! Get an input stream to the contents of this resource
+ const std::istream & read();
+ //! Get the canonical path to this resource
+ const char * canonical();
+
+private:
+ std::string src;
+ std::ifstream file;
+};
+
+} // namespace crepe
diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt
index 392b7d7..d938eb8 100644
--- a/src/crepe/CMakeLists.txt
+++ b/src/crepe/CMakeLists.txt
@@ -1,3 +1,38 @@
-target_sources(main PUBLIC
- main.cpp
+target_sources(crepe PUBLIC
+ Asset.cpp
+ Sound.cpp
+ SoundContext.cpp
+ Particle.cpp
+ ParticleSystem.cpp
+ SDLApp.cpp
+ ComponentManager.cpp
+ Component.cpp
+ ScriptSystem.cpp
+ PhysicsSystem.cpp
+ CollisionSystem.cpp
+ Collider.cpp
+ SDLContext.cpp
+
+ RenderSystem.cpp
)
+
+target_sources(crepe PUBLIC FILE_SET HEADERS FILES
+ Asset.h
+ Sound.h
+ SoundContext.h
+ SDLContext.h
+ ComponentManager.h
+ ComponentManager.hpp
+ Component.h
+ System.h
+ ScriptSystem.h
+ PhysicsSystem.h
+ CollisionSystem.h
+ Collider.h
+ SDLContext.h
+ RenderSystem.h
+)
+
+add_subdirectory(api)
+add_subdirectory(util)
+
diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp
new file mode 100644
index 0000000..13a3f33
--- /dev/null
+++ b/src/crepe/Collider.cpp
@@ -0,0 +1,5 @@
+#include "Collider.h"
+
+using namespace crepe;
+
+Collider::Collider(uint32_t gameObjectId) : Component(gameObjectId) {}
diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h
new file mode 100644
index 0000000..68a7d1d
--- /dev/null
+++ b/src/crepe/Collider.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "Component.h"
+
+namespace crepe {
+
+class Collider : public Component {
+public:
+ Collider(uint32_t game_object_id);
+
+ int size;
+};
+
+} // namespace crepe
diff --git a/src/crepe/CollisionSystem.cpp b/src/crepe/CollisionSystem.cpp
new file mode 100644
index 0000000..55e0fdc
--- /dev/null
+++ b/src/crepe/CollisionSystem.cpp
@@ -0,0 +1,7 @@
+#include "CollisionSystem.h"
+
+using namespace crepe;
+
+CollisionSystem::CollisionSystem() {}
+
+void CollisionSystem::update() {}
diff --git a/src/crepe/CollisionSystem.h b/src/crepe/CollisionSystem.h
new file mode 100644
index 0000000..1e9f1aa
--- /dev/null
+++ b/src/crepe/CollisionSystem.h
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace crepe {
+
+class CollisionSystem {
+public:
+ CollisionSystem();
+ void update();
+};
+
+} // namespace crepe
diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp
new file mode 100644
index 0000000..358ce31
--- /dev/null
+++ b/src/crepe/Component.cpp
@@ -0,0 +1,5 @@
+#include "Component.h"
+
+using namespace crepe;
+
+Component::Component(uint32_t id) : game_object_id(id), active(true) {}
diff --git a/src/crepe/Component.h b/src/crepe/Component.h
new file mode 100644
index 0000000..bc44865
--- /dev/null
+++ b/src/crepe/Component.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <cstdint>
+
+namespace crepe {
+
+class ComponentManager;
+
+class Component {
+protected:
+ friend class crepe::ComponentManager;
+ Component(uint32_t id);
+
+public:
+ virtual ~Component() = default;
+
+public:
+ uint32_t game_object_id;
+ bool active;
+};
+
+} // namespace crepe
diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/ComponentManager.cpp
new file mode 100644
index 0000000..8bde33a
--- /dev/null
+++ b/src/crepe/ComponentManager.cpp
@@ -0,0 +1,29 @@
+#include "ComponentManager.h"
+#include "util/log.h"
+
+using namespace crepe;
+
+ComponentManager & ComponentManager::get_instance() {
+ static ComponentManager instance;
+ return instance;
+}
+
+void ComponentManager::delete_all_components_of_id(uint32_t id) {
+ // Loop through all the types (in the unordered_map<>)
+ for (auto & [type, componentArray] : components) {
+ // Make sure that the id (that we are looking for) is within the boundaries of the vector<>
+ if (id < componentArray.size()) {
+ // Clear the components at this specific id
+ componentArray[id].clear();
+ }
+ }
+}
+
+void ComponentManager::delete_all_components() {
+ // Clear the whole unordered_map<>
+ this->components.clear();
+}
+
+ComponentManager::ComponentManager() { dbg_trace(); }
+
+ComponentManager::~ComponentManager() { dbg_trace(); }
diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h
new file mode 100644
index 0000000..2b5e1df
--- /dev/null
+++ b/src/crepe/ComponentManager.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <typeindex>
+#include <unordered_map>
+#include <vector>
+
+#include "Component.h"
+
+namespace crepe {
+
+class ComponentManager {
+public:
+ // Singleton
+ static ComponentManager & get_instance();
+ ComponentManager(const ComponentManager &) = delete;
+ ComponentManager(ComponentManager &&) = delete;
+ ComponentManager & operator=(const ComponentManager &) = delete;
+ ComponentManager & operator=(ComponentManager &&) = delete;
+
+public:
+ //! Add a component of a specific type
+ template <typename T, typename... Args>
+ T & add_component(uint32_t id, Args &&... args);
+ //! Deletes all components of a specific type and id
+ template <typename T>
+ void delete_components_by_id(uint32_t id);
+ //! Deletes all components of a specific type
+ template <typename T>
+ void delete_components();
+ //! Deletes all components of a specific id
+ void delete_all_components_of_id(uint32_t id);
+ //! Deletes all components
+ void delete_all_components();
+
+ //! Get a vector<> of all components at specific type and id
+ template <typename T>
+ std::vector<std::reference_wrapper<T>>
+ get_components_by_id(uint32_t id) const;
+ //! Get a vector<> of all components of a specific type
+ template <typename T>
+ std::vector<std::reference_wrapper<T>> get_components_by_type() const;
+
+private:
+ ComponentManager();
+ virtual ~ComponentManager();
+
+ /*
+ * The std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> below might seem a bit strange, let me explain this structure:
+ * The std::unordered_map<> has a key and value. The key is a std::type_index and the value is a std::vector. So, a new std::vector will be created for each new std::type_index.
+ * The first std::vector<> stores another vector<>. This first vector<> is to bind the entity's id to a component.
+ * The second std::vector<> stores unique_ptrs. Each component can be gathered via an unique_ptr. This second vector<> allows multiple components of the same std::type_index for one entity (id).
+ */
+ std::unordered_map<std::type_index,
+ std::vector<std::vector<std::unique_ptr<Component>>>>
+ components;
+};
+
+} // namespace crepe
+
+#include "ComponentManager.hpp"
diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/ComponentManager.hpp
new file mode 100644
index 0000000..9b07f13
--- /dev/null
+++ b/src/crepe/ComponentManager.hpp
@@ -0,0 +1,147 @@
+#pragma once
+
+#include <type_traits>
+
+#include "ComponentManager.h"
+
+namespace crepe {
+
+template <class T, typename... Args>
+T & ComponentManager::add_component(uint32_t id, Args &&... args) {
+ using namespace std;
+
+ static_assert(is_base_of<Component, T>::value,
+ "add_component must recieve a derivative class of Component");
+
+ // Determine the type of T (this is used as the key of the unordered_map<>)
+ type_index type = typeid(T);
+
+ // Check if this component type is already in the unordered_map<>
+ if (components.find(type) == components.end()) {
+ //If not, create a new (empty) vector<> of vector<unique_ptr<Component>>
+ components[type] = vector<vector<unique_ptr<Component>>>();
+ }
+
+ // Resize the vector<> if the id is greater than the current size
+ if (id >= components[type].size()) {
+ // Initialize new slots to nullptr (resize does automatically init to nullptr)
+ components[type].resize(id + 1);
+ }
+
+ // Create a new component of type T (arguments directly forwarded). The
+ // constructor must be called by ComponentManager.
+ T * instance = new T(id, forward<Args>(args)...);
+ // store its unique_ptr in the vector<>
+ components[type][id].push_back(unique_ptr<T>(instance));
+
+ return *instance;
+}
+
+template <typename T>
+void ComponentManager::delete_components_by_id(uint32_t id) {
+ using namespace std;
+
+ // Determine the type of T (this is used as the key of the unordered_map<>)
+ type_index type = typeid(T);
+
+ // Find the type (in the unordered_map<>)
+ if (components.find(type) != components.end()) {
+ // Get the correct vector<>
+ vector<vector<unique_ptr<Component>>> & component_array
+ = components[type];
+
+ // Make sure that the id (that we are looking for) is within the boundaries of the vector<>
+ if (id < component_array.size()) {
+ // Clear the whole vector<> of this specific type and id
+ component_array[id].clear();
+ }
+ }
+}
+
+template <typename T>
+void ComponentManager::delete_components() {
+ // Determine the type of T (this is used as the key of the unordered_map<>)
+ std::type_index type = typeid(T);
+
+ if (components.find(type) == components.end()) return;
+
+ components[type].clear();
+}
+
+template <typename T>
+std::vector<std::reference_wrapper<T>>
+ComponentManager::get_components_by_id(uint32_t id) const {
+ using namespace std;
+
+ // Determine the type of T (this is used as the key of the unordered_map<>)
+ type_index type = typeid(T);
+
+ // Create an empty vector<>
+ vector<reference_wrapper<T>> component_vector;
+
+ if (components.find(type) == components.end()) return component_vector;
+
+ // Get the correct vector<>
+ const vector<vector<unique_ptr<Component>>> & component_array
+ = components.at(type);
+
+ // Make sure that the id (that we are looking for) is within the boundaries of the vector<>
+ if (id >= component_array.size()) return component_vector;
+
+ // Loop trough the whole vector<>
+ for (const unique_ptr<Component> & component_ptr : component_array[id]) {
+ // Cast the unique_ptr to a raw pointer
+ T * casted_component = static_cast<T *>(component_ptr.get());
+
+ if (casted_component == nullptr) continue;
+
+ // Add the dereferenced raw pointer to the vector<>
+ component_vector.push_back(*casted_component);
+ }
+
+ return component_vector;
+}
+
+template <typename T>
+std::vector<std::reference_wrapper<T>>
+ComponentManager::get_components_by_type() const {
+ using namespace std;
+
+ // Determine the type of T (this is used as the key of the unordered_map<>)
+ type_index type = typeid(T);
+
+ // Create an empty vector<>
+ vector<reference_wrapper<T>> component_vector;
+ // Set the id to 0 (the id will also be stored in the returned vector<>)
+ // uint32_t id = 0;
+
+ // Find the type (in the unordered_map<>)
+ if (components.find(type) == components.end()) return component_vector;
+
+ // Get the correct vector<>
+ const vector<vector<unique_ptr<Component>>> & component_array
+ = components.at(type);
+
+ // Loop through the whole vector<>
+ for (const vector<unique_ptr<Component>> & component : component_array) {
+ // Loop trough the whole vector<>
+ for (const unique_ptr<Component> & component_ptr : component) {
+ // Cast the unique_ptr to a raw pointer
+ T * casted_component = static_cast<T *>(component_ptr.get());
+
+ // Ensure that the cast was successful
+ if (casted_component == nullptr) continue;
+
+ // Pair the dereferenced raw pointer and the id and add it to the vector<>
+ component_vector.emplace_back(ref(*casted_component));
+ }
+
+ // Increase the id (the id will also be stored in the returned vector<>)
+ //++id;
+ }
+
+ // Return the vector<>
+ return component_vector;
+}
+
+} // namespace crepe
diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp
new file mode 100644
index 0000000..4810e80
--- /dev/null
+++ b/src/crepe/Particle.cpp
@@ -0,0 +1,20 @@
+#include "Particle.h"
+
+using namespace crepe;
+
+Particle::Particle() { this->active = false; }
+
+void Particle::reset(float lifespan, Position position, Position velocity) {
+ this->time_in_life = 0;
+ this->lifespan = lifespan;
+ this->position = position;
+ this->velocity = velocity;
+ this->active = true;
+}
+
+void Particle::update(float deltaTime) {
+ time_in_life += deltaTime;
+ position.x += velocity.x * deltaTime;
+ position.y += velocity.y * deltaTime;
+ if (time_in_life >= lifespan) this->active = false;
+}
diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h
new file mode 100644
index 0000000..21e691d
--- /dev/null
+++ b/src/crepe/Particle.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "Position.h"
+
+namespace crepe {
+
+class Particle {
+public:
+ Position position;
+ // FIXME: `Position` is an awkward name for a 2D vector. See FIXME comment in
+ // api/Transform.h for fix proposal.
+ Position velocity;
+ float lifespan;
+ bool active;
+
+ Particle();
+ void reset(float lifespan, Position position, Position velocity);
+ void update(float deltaTime);
+ float time_in_life;
+};
+
+} // namespace crepe
diff --git a/src/crepe/ParticleSystem.cpp b/src/crepe/ParticleSystem.cpp
new file mode 100644
index 0000000..af6c550
--- /dev/null
+++ b/src/crepe/ParticleSystem.cpp
@@ -0,0 +1,62 @@
+#include <cmath>
+#include <ctime>
+
+#include "api/ParticleEmitter.h"
+
+#include "ComponentManager.h"
+#include "ParticleSystem.h"
+
+using namespace crepe;
+
+ParticleSystem::ParticleSystem() : elapsed_time(0.0f) {}
+
+void ParticleSystem::update() {
+ ComponentManager & mgr = ComponentManager::get_instance();
+ std::vector<std::reference_wrapper<ParticleEmitter>> emitters
+ = mgr.get_components_by_type<ParticleEmitter>();
+ float delta_time = 0.10;
+ for (ParticleEmitter & emitter : emitters) {
+ float update_amount = 1 / static_cast<float>(emitter.emission_rate);
+ for (float i = 0; i < delta_time; i += update_amount) {
+ emit_particle(emitter);
+ }
+ for (size_t j = 0; j < emitter.particles.size(); j++) {
+ if (emitter.particles[j].active) {
+ emitter.particles[j].update(delta_time);
+ }
+ }
+ }
+}
+
+void ParticleSystem::emit_particle(ParticleEmitter & emitter) {
+ Position initial_position = {emitter.position.x, emitter.position.y};
+ float random_angle = 0.0f;
+ if (emitter.max_angle < emitter.min_angle) {
+ random_angle = ((emitter.min_angle
+ + (std::rand()
+ % (static_cast<uint32_t>(emitter.max_angle + 360
+ - emitter.min_angle + 1))))
+ % 360);
+ } else {
+ random_angle = emitter.min_angle
+ + (std::rand()
+ % (static_cast<uint32_t>(emitter.max_angle
+ - emitter.min_angle + 1)));
+ }
+ float angle_in_radians = random_angle * (M_PI / 180.0f);
+ float random_speed_offset = (static_cast<float>(std::rand()) / RAND_MAX)
+ * (2 * emitter.speed_offset)
+ - emitter.speed_offset;
+ float velocity_x
+ = (emitter.speed + random_speed_offset) * std::cos(angle_in_radians);
+ float velocity_y
+ = (emitter.speed + random_speed_offset) * std::sin(angle_in_radians);
+ Position initial_velocity = {velocity_x, velocity_y};
+ for (size_t i = 0; i < emitter.particles.size(); i++) {
+ if (!emitter.particles[i].active) {
+ emitter.particles[i].reset(emitter.end_lifespan, initial_position,
+ initial_velocity);
+ break;
+ }
+ }
+}
diff --git a/src/crepe/ParticleSystem.h b/src/crepe/ParticleSystem.h
new file mode 100644
index 0000000..ad96eb0
--- /dev/null
+++ b/src/crepe/ParticleSystem.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "api/ParticleEmitter.h"
+
+namespace crepe {
+
+class ParticleSystem {
+public:
+ ParticleSystem();
+ void update();
+
+private:
+ void emit_particle(ParticleEmitter & emitter); //emits a new particle
+
+ float elapsed_time; //elapsed time since the last emission
+};
+
+} // namespace crepe
diff --git a/src/crepe/PhysicsSystem.cpp b/src/crepe/PhysicsSystem.cpp
new file mode 100644
index 0000000..16f4c10
--- /dev/null
+++ b/src/crepe/PhysicsSystem.cpp
@@ -0,0 +1,62 @@
+#include <iostream>
+
+#include "api/Force.h"
+#include "api/Rigidbody.h"
+#include "api/Transform.h"
+
+#include "ComponentManager.h"
+#include "PhysicsSystem.h"
+
+using namespace crepe;
+using namespace crepe::api;
+
+PhysicsSystem::PhysicsSystem() {}
+
+void PhysicsSystem::update() {
+ ComponentManager & mgr = ComponentManager::get_instance();
+ std::vector<std::reference_wrapper<Rigidbody>> rigidbodies
+ = mgr.get_components_by_type<Rigidbody>();
+ std::vector<std::reference_wrapper<Transform>> transforms
+ = mgr.get_components_by_type<Transform>();
+
+ for (Rigidbody & rigidbody : rigidbodies) {
+
+ switch (rigidbody.body_type) {
+ case BodyType::DYNAMIC:
+ for (Transform & transform : transforms) {
+ if (transform.game_object_id == rigidbody.game_object_id) {
+ rigidbody.velocity_x = 0;
+ rigidbody.velocity_y = 0;
+ std::vector<std::reference_wrapper<Force>> forces
+ = mgr.get_components_by_id<Force>(
+ rigidbody.game_object_id);
+ rigidbody.velocity_y
+ += rigidbody.gravity_scale * 1 * rigidbody.mass;
+
+ for (Force & force : forces) {
+ rigidbody.velocity_x += force.force_x;
+ rigidbody.velocity_y += force.force_y;
+ }
+
+ std::cout << "before transform.postion.x "
+ << transform.position.x << std::endl;
+ std::cout << "before transform.postion.y "
+ << transform.position.y << std::endl;
+ transform.position.x += rigidbody.velocity_x;
+ transform.position.y += rigidbody.velocity_y;
+ std::cout << "after transform.postion.x "
+ << transform.position.x << std::endl;
+ std::cout << "after transform.postion.y "
+ << transform.position.y << std::endl;
+ }
+ }
+ break;
+ case BodyType::KINEMATIC:
+ break; //(scripts)
+ case BodyType::STATIC:
+ break; //(unmoveable objects)
+ default:
+ break;
+ }
+ }
+}
diff --git a/src/crepe/PhysicsSystem.h b/src/crepe/PhysicsSystem.h
new file mode 100644
index 0000000..33b4072
--- /dev/null
+++ b/src/crepe/PhysicsSystem.h
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace crepe {
+
+class PhysicsSystem {
+public:
+ PhysicsSystem();
+ void update();
+};
+
+} // namespace crepe
diff --git a/src/crepe/Position.h b/src/crepe/Position.h
new file mode 100644
index 0000000..f84b63d
--- /dev/null
+++ b/src/crepe/Position.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+
+struct Position {
+ float x = 0.0;
+ float y = 0.0;
+};
+
+} // namespace crepe
diff --git a/src/crepe/RenderSystem.cpp b/src/crepe/RenderSystem.cpp
new file mode 100644
index 0000000..fae93f0
--- /dev/null
+++ b/src/crepe/RenderSystem.cpp
@@ -0,0 +1,42 @@
+#include <functional>
+#include <vector>
+
+#include "api/Sprite.h"
+#include "api/Transform.h"
+#include "util/log.h"
+
+#include "ComponentManager.h"
+#include "RenderSystem.h"
+#include "SDLContext.h"
+
+using namespace crepe;
+using namespace crepe::api;
+
+RenderSystem::RenderSystem() { dbg_trace(); }
+
+RenderSystem::~RenderSystem() { dbg_trace(); }
+
+RenderSystem & RenderSystem::get_instance() {
+ static RenderSystem instance;
+ return instance;
+}
+
+void RenderSystem::update() {
+
+ ComponentManager & mgr = ComponentManager::get_instance();
+
+ std::vector<std::reference_wrapper<Sprite>> sprites
+ = mgr.get_components_by_type<Sprite>();
+
+ SDLContext & render = SDLContext::get_instance();
+ render.clear_screen();
+
+ for (const Sprite & sprite : sprites) {
+ std::vector<std::reference_wrapper<Transform>> transforms
+ = mgr.get_components_by_id<Transform>(sprite.game_object_id);
+ for (const Transform & transform : transforms) {
+ render.draw(sprite, transform);
+ }
+ }
+ render.present_screen();
+}
diff --git a/src/crepe/RenderSystem.h b/src/crepe/RenderSystem.h
new file mode 100644
index 0000000..4b910a4
--- /dev/null
+++ b/src/crepe/RenderSystem.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "System.h"
+
+namespace crepe {
+
+class RenderSystem : public System {
+
+public:
+ static RenderSystem & get_instance();
+ void update();
+
+private:
+ RenderSystem();
+ ~RenderSystem();
+};
+} // namespace crepe
diff --git a/src/crepe/SDLApp.cpp b/src/crepe/SDLApp.cpp
new file mode 100644
index 0000000..c6ddeaa
--- /dev/null
+++ b/src/crepe/SDLApp.cpp
@@ -0,0 +1,71 @@
+#include <iostream>
+
+#include "SDLApp.h"
+
+SDLApp::SDLApp(int window_width, int window_height)
+ : window_width(window_width), window_height(window_height), window(nullptr),
+ renderer(nullptr) {}
+
+// FIXME: why is there clean_up and ~SDLApp?
+SDLApp::~SDLApp() { clean_up(); }
+
+bool SDLApp::initialize() {
+ if (SDL_Init(SDL_INIT_VIDEO) != 0) {
+ // FIXME: throw exception
+ std::cerr << "SDL Initialization Error: " << SDL_GetError()
+ << std::endl;
+ return false;
+ }
+
+ window = SDL_CreateWindow("Particle System", SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, window_width,
+ window_height, SDL_WINDOW_SHOWN);
+ if (!window) {
+ // FIXME: throw exception
+ std::cerr << "Window Creation Error: " << SDL_GetError() << std::endl;
+ return false;
+ }
+
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+ if (!renderer) {
+ // FIXME: throw exception
+ std::cerr << "Renderer Creation Error: " << SDL_GetError() << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+void SDLApp::handle_events(bool & running) {
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ if (event.type == SDL_QUIT) {
+ running = false;
+ }
+ }
+}
+
+void SDLApp::clear_screen() {
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderClear(renderer);
+}
+
+void SDLApp::present_screen() { SDL_RenderPresent(renderer); }
+
+void SDLApp::draw_square(int x, int y, int size) {
+ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+ SDL_Rect rect = {x, y, size, size};
+ SDL_RenderFillRect(renderer, &rect);
+}
+
+SDL_Texture * square_texture = nullptr; // Load this with an image or create it
+
+void SDLApp::clean_up() {
+ if (renderer) {
+ SDL_DestroyRenderer(renderer);
+ }
+ if (window) {
+ SDL_DestroyWindow(window);
+ }
+ SDL_Quit();
+}
diff --git a/src/crepe/SDLApp.h b/src/crepe/SDLApp.h
new file mode 100644
index 0000000..e67947b
--- /dev/null
+++ b/src/crepe/SDLApp.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <SDL2/SDL.h>
+
+#include "api/ParticleEmitter.h"
+
+class SDLApp {
+public:
+ SDLApp(int window_width, int window_height);
+ ~SDLApp();
+
+ bool initialize();
+ void handle_events(bool & running);
+ void clear_screen();
+ void present_screen();
+ void draw_square(int x, int y, int size);
+ void clean_up();
+ void draw_particles(const std::vector<crepe::ParticleEmitter> & emitters);
+ void draw_multiple_squares(const std::vector<SDL_Rect> & squares);
+
+private:
+ int window_width;
+ int window_height;
+ SDL_Window * window;
+ SDL_Renderer * renderer;
+};
diff --git a/src/crepe/SDLContext.cpp b/src/crepe/SDLContext.cpp
new file mode 100644
index 0000000..8bc5bc6
--- /dev/null
+++ b/src/crepe/SDLContext.cpp
@@ -0,0 +1,158 @@
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_surface.h>
+#include <SDL2/SDL_video.h>
+#include <cmath>
+#include <cstddef>
+#include <iostream>
+
+#include "api/Sprite.h"
+#include "api/Texture.h"
+#include "api/Transform.h"
+#include "util/log.h"
+
+#include "SDLContext.h"
+
+using namespace crepe;
+
+SDLContext & SDLContext::get_instance() {
+ static SDLContext instance;
+ return instance;
+}
+
+void SDLContext::handle_events(bool & running) {
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ if (event.type == SDL_QUIT) {
+ running = false;
+ }
+ }
+}
+
+SDLContext::~SDLContext() {
+ dbg_trace();
+
+ if (this->game_renderer != nullptr)
+ SDL_DestroyRenderer(this->game_renderer);
+
+ if (this->game_window != nullptr) {
+ SDL_DestroyWindow(this->game_window);
+ }
+
+ // TODO: how are we going to ensure that these are called from the same
+ // thread that SDL_Init() was called on? This has caused problems for me
+ // before.
+ IMG_Quit();
+ SDL_Quit();
+}
+
+void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer); }
+
+SDLContext::SDLContext() {
+ dbg_trace();
+ // FIXME: read window defaults from config manager
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ // FIXME: throw exception
+ std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError()
+ << std::endl;
+ return;
+ }
+
+ this->game_window = SDL_CreateWindow(
+ "Crepe Game Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ 1920, 1080, SDL_WINDOW_SHOWN);
+ if (!this->game_window) {
+ // FIXME: throw exception
+ std::cerr << "Window could not be created! SDL_Error: "
+ << SDL_GetError() << std::endl;
+ }
+
+ this->game_renderer
+ = SDL_CreateRenderer(this->game_window, -1, SDL_RENDERER_ACCELERATED);
+ if (!this->game_renderer) {
+ // FIXME: throw exception
+ std::cerr << "Renderer could not be created! SDL_Error: "
+ << SDL_GetError() << std::endl;
+ SDL_DestroyWindow(this->game_window);
+ return;
+ }
+
+ int img_flags = IMG_INIT_PNG;
+ if (!(IMG_Init(img_flags) & img_flags)) {
+ // FIXME: throw exception
+ std::cout << "SDL_image could not initialize! SDL_image Error: "
+ << IMG_GetError() << std::endl;
+ }
+}
+
+void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer); }
+
+void SDLContext::draw(const api::Sprite & sprite,
+ const api::Transform & transform) {
+
+ static SDL_RendererFlip render_flip
+ = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x)
+ | (SDL_FLIP_VERTICAL * sprite.flip.flip_y));
+
+ int w, h;
+ SDL_QueryTexture(sprite.sprite_image->texture, NULL, NULL, &w, &h);
+ // needs maybe camera for position
+ SDL_Rect dstrect = {
+ .x = static_cast<int>(transform.position.x),
+ .y = static_cast<int>(transform.position.y),
+ .w = static_cast<int>(w * transform.scale),
+ .h = static_cast<int>(h * transform.scale),
+ };
+
+ double degrees = transform.rotation * 180 / M_PI;
+ SDL_RenderCopyEx(this->game_renderer, sprite.sprite_image->texture, NULL,
+ &dstrect, degrees, NULL, render_flip);
+}
+
+/*
+SDL_Texture * SDLContext::setTextureFromPath(const char * path, SDL_Rect & clip,
+ const int row, const int col) {
+ dbg_trace();
+
+ SDL_Surface * tmp = IMG_Load(path);
+ if (!tmp) {
+ std::cerr << "Error surface " << IMG_GetError << std::endl;
+ }
+
+ clip.
+ w = tmp->w / col;
+ clip.h = tmp->h / row;
+
+ SDL_Texture * CreatedTexture
+ = SDL_CreateTextureFromSurface(this->game_renderer, tmp);
+
+ if (!CreatedTexture) {
+ std::cerr << "Error could not create texture " << IMG_GetError
+ << std::endl;
+ }
+ SDL_FreeSurface(tmp);
+
+ return CreatedTexture;
+}
+*/
+
+SDL_Texture * SDLContext::texture_from_path(const char * path) {
+ dbg_trace();
+
+ SDL_Surface * tmp = IMG_Load(path);
+ if (!tmp) {
+ std::cerr << "Error surface " << IMG_GetError << std::endl;
+ }
+ SDL_Texture * created_texture
+ = SDL_CreateTextureFromSurface(this->game_renderer, tmp);
+
+ if (!created_texture) {
+ std::cerr << "Error could not create texture " << IMG_GetError
+ << std::endl;
+ }
+ SDL_FreeSurface(tmp);
+
+ return created_texture;
+}
diff --git a/src/crepe/SDLContext.h b/src/crepe/SDLContext.h
new file mode 100644
index 0000000..4d9c1bc
--- /dev/null
+++ b/src/crepe/SDLContext.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_video.h>
+
+#include "api/Sprite.h"
+#include "api/Transform.h"
+
+#include "RenderSystem.h"
+
+namespace crepe::api {
+class Texture;
+}
+
+namespace crepe {
+
+class SDLContext {
+
+public:
+ // singleton
+ static SDLContext & get_instance();
+ SDLContext(const SDLContext &) = delete;
+ SDLContext(SDLContext &&) = delete;
+ SDLContext & operator=(const SDLContext &) = delete;
+ SDLContext & operator=(SDLContext &&) = delete;
+
+ //TODO decide events wouter?
+
+private:
+ void handle_events(bool & running);
+
+private:
+ SDLContext();
+ virtual ~SDLContext();
+
+private:
+ friend class api::Texture;
+ SDL_Texture * texture_from_path(const char *);
+ //SDL_Texture* setTextureFromPath(const char*, SDL_Rect& clip, const int row, const int col);
+
+private:
+ friend class RenderSystem;
+ void draw(const api::Sprite &, const api::Transform &);
+ void clear_screen();
+ void present_screen();
+
+private:
+ SDL_Window * game_window = nullptr;
+ SDL_Renderer * game_renderer = nullptr;
+};
+
+} // namespace crepe
diff --git a/src/crepe/ScriptSystem.cpp b/src/crepe/ScriptSystem.cpp
new file mode 100644
index 0000000..171b490
--- /dev/null
+++ b/src/crepe/ScriptSystem.cpp
@@ -0,0 +1,47 @@
+#include <forward_list>
+#include <functional>
+#include <vector>
+
+#include "api/BehaviorScript.h"
+#include "api/Script.h"
+#include "util/log.h"
+
+#include "ComponentManager.h"
+#include "ScriptSystem.h"
+
+using namespace std;
+using namespace crepe;
+using namespace crepe::api;
+
+ScriptSystem::ScriptSystem() { dbg_trace(); }
+ScriptSystem::~ScriptSystem() { dbg_trace(); }
+
+ScriptSystem & ScriptSystem::get_instance() {
+ static ScriptSystem instance;
+ return instance;
+}
+
+void ScriptSystem::update() {
+ using namespace std;
+ dbg_trace();
+
+ forward_list<Script *> scripts = this->get_scripts();
+ for (Script * script : scripts) script->update();
+}
+
+forward_list<Script *> ScriptSystem::get_scripts() {
+ forward_list<Script *> scripts = {};
+ ComponentManager & mgr = ComponentManager::get_instance();
+ vector<reference_wrapper<BehaviorScript>> behavior_scripts
+ = mgr.get_components_by_type<BehaviorScript>();
+
+ for (auto behavior_script_ref : behavior_scripts) {
+ BehaviorScript & behavior_script = behavior_script_ref.get();
+ if (!behavior_script.active) continue;
+ Script * script = behavior_script.script.get();
+ if (script == nullptr) continue;
+ scripts.push_front(script);
+ }
+
+ return scripts;
+}
diff --git a/src/crepe/ScriptSystem.h b/src/crepe/ScriptSystem.h
new file mode 100644
index 0000000..1f472a0
--- /dev/null
+++ b/src/crepe/ScriptSystem.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <forward_list>
+
+#include "System.h"
+
+namespace crepe::api {
+class Script;
+}
+
+namespace crepe {
+
+class ScriptSystem : public System {
+public:
+ static ScriptSystem & get_instance();
+ void update();
+
+private:
+ ScriptSystem();
+ ~ScriptSystem();
+
+private:
+ std::forward_list<api::Script *> get_scripts();
+};
+
+} // namespace crepe
diff --git a/src/crepe/Sound.cpp b/src/crepe/Sound.cpp
new file mode 100644
index 0000000..64fa281
--- /dev/null
+++ b/src/crepe/Sound.cpp
@@ -0,0 +1,60 @@
+#include "util/log.h"
+
+#include "Sound.h"
+#include "SoundContext.h"
+
+using namespace crepe;
+
+Sound::Sound(std::unique_ptr<Asset> res) {
+ dbg_trace();
+ this->load(std::move(res));
+}
+
+Sound::Sound(const char * src) {
+ dbg_trace();
+ this->load(std::make_unique<Asset>(src));
+}
+
+void Sound::load(std::unique_ptr<Asset> res) {
+ this->sample.load(res->canonical());
+}
+
+void Sound::play() {
+ SoundContext & ctx = SoundContext::get_instance();
+ if (ctx.engine.getPause(this->handle)) {
+ // resume if paused
+ ctx.engine.setPause(this->handle, false);
+ } else {
+ // or start new sound
+ this->handle = ctx.engine.play(this->sample, this->volume);
+ ctx.engine.setLooping(this->handle, this->looping);
+ }
+}
+
+void Sound::pause() {
+ SoundContext & ctx = SoundContext::get_instance();
+ if (ctx.engine.getPause(this->handle)) return;
+ ctx.engine.setPause(this->handle, true);
+}
+
+void Sound::rewind() {
+ SoundContext & ctx = SoundContext::get_instance();
+ if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
+ ctx.engine.seek(this->handle, 0);
+}
+
+void Sound::set_volume(float volume) {
+ this->volume = volume;
+
+ SoundContext & ctx = SoundContext::get_instance();
+ if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
+ ctx.engine.setVolume(this->handle, this->volume);
+}
+
+void Sound::set_looping(bool looping) {
+ this->looping = looping;
+
+ SoundContext & ctx = SoundContext::get_instance();
+ if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
+ ctx.engine.setLooping(this->handle, this->looping);
+}
diff --git a/src/crepe/Sound.h b/src/crepe/Sound.h
new file mode 100644
index 0000000..917b57e
--- /dev/null
+++ b/src/crepe/Sound.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <memory>
+#include <soloud/soloud.h>
+#include <soloud/soloud_wav.h>
+
+#include "Asset.h"
+
+namespace crepe {
+
+class Sound {
+public:
+ /**
+ * \brief Pause this sample
+ *
+ * Pauses this sound if it is playing, or does nothing if it is already
+ * paused. The playhead position is saved, such that calling \c play() after
+ * this function makes the sound resume.
+ */
+ void pause();
+ /**
+ * \brief Play this sample
+ *
+ * Resume playback if this sound is paused, or start from the beginning of
+ * the sample.
+ *
+ * \note This class only saves a reference to the most recent 'voice' of this
+ * sound. Calling \c play() while the sound is already playing causes
+ * multiple instances of the sample to play simultaniously. The sample
+ * started last is the one that is controlled afterwards.
+ */
+ void play();
+ /**
+ * \brief Reset playhead position
+ *
+ * Resets the playhead position so that calling \c play() after this function
+ * makes it play from the start of the sample. If the sound is not paused
+ * before calling this function, this function will stop playback.
+ */
+ void rewind();
+ /**
+ * \brief Set playback volume / gain
+ *
+ * \param volume Volume (0 = muted, 1 = full volume)
+ */
+ void set_volume(float volume);
+ /**
+ * \brief Get playback volume / gain
+ *
+ * \return Volume
+ */
+ float get_volume() const { return this->volume; }
+ /**
+ * \brief Set looping behavior for this sample
+ *
+ * \param looping Looping behavior (false = one-shot, true = loop)
+ */
+ void set_looping(bool looping);
+ /**
+ * \brief Get looping behavior
+ *
+ * \return true if looping, false if one-shot
+ */
+ bool get_looping() const { return this->looping; }
+
+public:
+ Sound(const char * src);
+ Sound(std::unique_ptr<Asset> res);
+
+private:
+ void load(std::unique_ptr<Asset> res);
+
+private:
+ SoLoud::Wav sample;
+ SoLoud::handle handle;
+
+ float volume = 1.0f;
+ bool looping = false;
+};
+
+} // namespace crepe
diff --git a/src/crepe/SoundContext.cpp b/src/crepe/SoundContext.cpp
new file mode 100644
index 0000000..72047d2
--- /dev/null
+++ b/src/crepe/SoundContext.cpp
@@ -0,0 +1,20 @@
+#include "util/log.h"
+
+#include "SoundContext.h"
+
+using namespace crepe;
+
+SoundContext & SoundContext::get_instance() {
+ static SoundContext instance;
+ return instance;
+}
+
+SoundContext::SoundContext() {
+ dbg_trace();
+ engine.init();
+}
+
+SoundContext::~SoundContext() {
+ dbg_trace();
+ engine.deinit();
+}
diff --git a/src/crepe/SoundContext.h b/src/crepe/SoundContext.h
new file mode 100644
index 0000000..d3123d2
--- /dev/null
+++ b/src/crepe/SoundContext.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <soloud/soloud.h>
+
+#include "Sound.h"
+
+namespace crepe {
+
+class SoundContext {
+private:
+ SoundContext();
+ virtual ~SoundContext();
+
+ // singleton
+ static SoundContext & get_instance();
+ SoundContext(const SoundContext &) = delete;
+ SoundContext(SoundContext &&) = delete;
+ SoundContext & operator=(const SoundContext &) = delete;
+ SoundContext & operator=(SoundContext &&) = delete;
+
+private:
+ SoLoud::Soloud engine;
+ friend class Sound;
+};
+
+} // namespace crepe
diff --git a/src/crepe/System.h b/src/crepe/System.h
new file mode 100644
index 0000000..ecbb7f5
--- /dev/null
+++ b/src/crepe/System.h
@@ -0,0 +1,22 @@
+#pragma once
+
+namespace crepe {
+
+class System {
+public:
+ static System & get_instance();
+ virtual void update() = 0;
+
+protected:
+ System() {};
+ virtual ~System() {};
+
+private:
+ // singleton
+ System(const System &) = delete;
+ System(System &&) = delete;
+ System & operator=(const System &) = delete;
+ System & operator=(System &&) = delete;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp
new file mode 100644
index 0000000..560df6c
--- /dev/null
+++ b/src/crepe/api/AssetManager.cpp
@@ -0,0 +1,17 @@
+#include "util/log.h"
+
+#include "AssetManager.h"
+
+using namespace crepe::api;
+
+AssetManager & AssetManager::get_instance() {
+ static AssetManager instance;
+ return instance;
+}
+
+AssetManager::~AssetManager() {
+ dbg_trace();
+ this->asset_cache.clear();
+}
+
+AssetManager::AssetManager() { dbg_trace(); }
diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h
new file mode 100644
index 0000000..3e72a49
--- /dev/null
+++ b/src/crepe/api/AssetManager.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <any>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+namespace crepe::api {
+
+class AssetManager {
+
+private:
+ std::unordered_map<std::string, std::any> asset_cache;
+
+private:
+ AssetManager();
+ virtual ~AssetManager();
+
+public:
+ AssetManager(const AssetManager &) = delete;
+ AssetManager(AssetManager &&) = delete;
+ AssetManager & operator=(const AssetManager &) = delete;
+ AssetManager & operator=(AssetManager &&) = delete;
+
+ static AssetManager & get_instance();
+
+public:
+ template <typename asset>
+ std::shared_ptr<asset> cache(const std::string & file_path,
+ bool reload = false);
+};
+
+} // namespace crepe::api
+
+#include "AssetManager.hpp"
diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp
new file mode 100644
index 0000000..468724c
--- /dev/null
+++ b/src/crepe/api/AssetManager.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "AssetManager.h"
+
+namespace crepe::api {
+
+template <typename asset>
+std::shared_ptr<asset> AssetManager::cache(const std::string & file_path,
+ bool reload) {
+ auto it = asset_cache.find(file_path);
+
+ if (!reload && it != asset_cache.end()) {
+ return std::any_cast<std::shared_ptr<asset>>(it->second);
+ }
+
+ std::shared_ptr<asset> new_asset
+ = std::make_shared<asset>(file_path.c_str());
+
+ asset_cache[file_path] = new_asset;
+
+ return new_asset;
+}
+
+} // namespace crepe::api
diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
new file mode 100644
index 0000000..35b8d83
--- /dev/null
+++ b/src/crepe/api/AudioSource.cpp
@@ -0,0 +1,23 @@
+#include <memory>
+
+#include "../Sound.h"
+
+#include "AudioSource.h"
+
+using namespace crepe::api;
+
+AudioSource::AudioSource(std::unique_ptr<Asset> audio_clip) {
+ this->sound = std::make_unique<crepe::Sound>(std::move(audio_clip));
+}
+
+void AudioSource::play() { return this->play(false); }
+
+void AudioSource::play(bool looping) {
+ this->sound->set_looping(looping);
+ this->sound->play();
+}
+
+void AudioSource::stop() {
+ this->sound->pause();
+ this->sound->rewind();
+}
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
new file mode 100644
index 0000000..7980212
--- /dev/null
+++ b/src/crepe/api/AudioSource.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <memory>
+
+#include "../Asset.h"
+#include "../Component.h"
+
+namespace crepe {
+class Sound;
+}
+
+namespace crepe::api {
+
+//! Audio source component
+class AudioSource : Component {
+public:
+ AudioSource(std::unique_ptr<Asset> audio_clip);
+ virtual ~AudioSource() = default;
+
+public:
+ //! Start or resume this audio source
+ void play();
+ void play(bool looping);
+ //! Stop this audio source
+ void stop();
+
+public:
+ //! Sample file location
+ std::unique_ptr<Asset> audio_clip;
+ //! TODO: ?????
+ bool play_on_awake;
+ //! Repeat the current audio clip during playback
+ bool loop;
+ //! Normalized volume (0.0 - 1.0)
+ float volume;
+
+private:
+ std::unique_ptr<crepe::Sound> sound;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/crepe/api/BehaviorScript.cpp
diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h
new file mode 100644
index 0000000..6133cc8
--- /dev/null
+++ b/src/crepe/api/BehaviorScript.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <memory>
+
+#include "../Component.h"
+
+namespace crepe {
+class ScriptSystem;
+class ComponentManager;
+} // namespace crepe
+
+namespace crepe::api {
+
+class Script;
+
+class BehaviorScript : public Component {
+protected:
+ friend class crepe::ComponentManager;
+ using Component::Component;
+
+public:
+ virtual ~BehaviorScript() = default;
+
+public:
+ template <class T>
+ BehaviorScript & set_script();
+
+protected:
+ friend class crepe::ScriptSystem;
+ std::unique_ptr<Script> script = nullptr;
+};
+
+} // namespace crepe::api
+
+#include "BehaviorScript.hpp"
diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp
new file mode 100644
index 0000000..2a3502f
--- /dev/null
+++ b/src/crepe/api/BehaviorScript.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <type_traits>
+
+#include "../util/log.h"
+
+#include "BehaviorScript.h"
+#include "Script.h"
+
+namespace crepe::api {
+
+template <class T>
+BehaviorScript & BehaviorScript::set_script() {
+ static_assert(std::is_base_of<Script, T>::value);
+ dbg_trace();
+ Script * s = new T();
+ s->parent = this;
+ this->script = std::unique_ptr<Script>(s);
+ return *this;
+}
+
+} // namespace crepe::api
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
new file mode 100644
index 0000000..0bb1263
--- /dev/null
+++ b/src/crepe/api/CMakeLists.txt
@@ -0,0 +1,31 @@
+target_sources(crepe PUBLIC
+ # AudioSource.cpp
+ BehaviorScript.cpp
+ Script.cpp
+ GameObject.cpp
+ Rigidbody.cpp
+ Force.cpp
+ ParticleEmitter.cpp
+ Transform.cpp
+ Color.cpp
+ Texture.cpp
+ AssetManager.cpp
+ Sprite.cpp
+)
+
+target_sources(crepe PUBLIC FILE_SET HEADERS FILES
+ # AudioSource.h
+ BehaviorScript.h
+ Config.h
+ Script.h
+ Script.hpp
+ GameObject.h
+ GameObject.hpp
+ Rigidbody.h
+ Sprite.h
+ Point.h
+ Color.h
+ Texture.h
+ AssetManager.h
+ AssetManager.hpp
+)
diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h
new file mode 100644
index 0000000..762574b
--- /dev/null
+++ b/src/crepe/api/CircleCollider.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "../Collider.h"
+
+namespace crepe::api {
+
+class CircleCollider : public Collider {
+public:
+ CircleCollider(uint32_t game_object_id, int radius)
+ : Collider(game_object_id), radius(radius) {}
+ int radius;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp
new file mode 100644
index 0000000..fb5bd1a
--- /dev/null
+++ b/src/crepe/api/Color.cpp
@@ -0,0 +1,34 @@
+#include "Color.h"
+
+using namespace crepe::api;
+
+Color Color::white = Color(255, 255, 255, 0);
+Color Color::red = Color(255, 0, 0, 0);
+Color Color::green = Color(0, 255, 0, 0);
+Color Color::blue = Color(0, 0, 255, 0);
+Color Color::black = Color(0, 0, 0, 0);
+Color Color::cyan = Color(0, 255, 255, 0);
+Color Color::yellow = Color(255, 255, 0, 0);
+Color Color::magenta = Color(255, 0, 255, 0);
+
+// FIXME: do we really need double precision for color values?
+Color::Color(double red, double green, double blue, double alpha) {
+ this->a = alpha;
+ this->r = red;
+ this->g = green;
+ this->b = blue;
+};
+
+const Color & Color::get_white() { return Color::white; };
+
+const Color & Color::get_red() { return Color::red; };
+const Color & Color::get_green() { return Color::green; };
+const Color & Color::get_blue() { return Color::blue; };
+
+const Color & Color::get_black() { return Color::black; };
+
+const Color & Color::get_cyan() { return Color::cyan; };
+
+const Color & Color::get_yellow() { return Color::yellow; };
+
+const Color & Color::get_magenta() { return Color::magenta; };
diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h
new file mode 100644
index 0000000..e818de4
--- /dev/null
+++ b/src/crepe/api/Color.h
@@ -0,0 +1,37 @@
+#pragma once
+
+namespace crepe::api {
+
+class Color {
+
+ // FIXME: can't these colors be defined as a `static constexpr const Color`
+ // instead?
+
+public:
+ Color(double red, double green, double blue, double alpha);
+ static const Color & get_white();
+ static const Color & get_red();
+ static const Color & get_green();
+ static const Color & get_blue();
+ static const Color & get_cyan();
+ static const Color & get_magenta();
+ static const Color & get_yellow();
+ static const Color & get_black();
+
+private:
+ double r;
+ double g;
+ double b;
+ double a;
+
+ static Color white;
+ static Color red;
+ static Color green;
+ static Color blue;
+ static Color cyan;
+ static Color magenta;
+ static Color yellow;
+ static Color black;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
new file mode 100644
index 0000000..8a7f268
--- /dev/null
+++ b/src/crepe/api/Config.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "../util/log.h"
+
+namespace crepe::api {
+
+class Config {
+private:
+ Config() = default;
+
+public:
+ ~Config() = default;
+
+public:
+ //! Retrieve handle to global Config instance
+ static Config & get_instance() {
+ static Config instance;
+ return instance;
+ }
+
+public:
+ //! Logging-related settings
+ struct {
+ /**
+ * \brief Log level
+ *
+ * Only messages with equal or higher priority than this value will be
+ * logged.
+ */
+ util::LogLevel level = util::LogLevel::INFO;
+ /**
+ * \brief Colored log output
+ *
+ * Enables log coloring using ANSI escape codes.
+ */
+ bool color = true;
+ } log;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Force.cpp b/src/crepe/api/Force.cpp
new file mode 100644
index 0000000..e359adc
--- /dev/null
+++ b/src/crepe/api/Force.cpp
@@ -0,0 +1,21 @@
+#include <cmath>
+
+#include "Force.h"
+
+namespace crepe::api {
+
+Force::Force(uint32_t game_object_id, uint32_t magnitude, uint32_t direction)
+ : Component(game_object_id) {
+ // TODO: A standard angle unit should be established for the entire engine
+ // and assumed to be the default everywhere. Only conversion functions should
+ // explicitly contain the unit (i.e. `deg_to_rad()` & `rad_to_deg()`)
+
+ // Convert direction from degrees to radians
+ float radian_direction = static_cast<float>(direction) * (M_PI / 180.0f);
+ force_x = static_cast<int32_t>(
+ std::round(magnitude * std::cos(radian_direction)));
+ force_y = static_cast<int32_t>(
+ std::round(magnitude * std::sin(radian_direction)));
+}
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Force.h b/src/crepe/api/Force.h
new file mode 100644
index 0000000..8da9a00
--- /dev/null
+++ b/src/crepe/api/Force.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <cstdint>
+
+#include "../Component.h"
+
+namespace crepe::api {
+
+class Force : public Component {
+public:
+ Force(uint32_t game_object_id, uint32_t magnitude, uint32_t direction);
+
+ int32_t force_x;
+ int32_t force_y;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp
new file mode 100644
index 0000000..b167187
--- /dev/null
+++ b/src/crepe/api/GameObject.cpp
@@ -0,0 +1,7 @@
+#include "GameObject.h"
+
+using namespace crepe::api;
+using namespace std;
+
+GameObject::GameObject(uint32_t id, string name, string tag, int layer)
+ : id(id), name(name), tag(tag), active(true), layer(layer) {}
diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h
new file mode 100644
index 0000000..57508c5
--- /dev/null
+++ b/src/crepe/api/GameObject.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+namespace crepe::api {
+
+class GameObject {
+public:
+ GameObject(uint32_t id, std::string name, std::string tag, int layer);
+
+ template <typename T, typename... Args>
+ T & add_component(Args &&... args);
+
+ uint32_t id;
+ std::string name;
+ std::string tag;
+ bool active;
+ int layer;
+};
+
+} // namespace crepe::api
+
+#include "GameObject.hpp"
diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp
new file mode 100644
index 0000000..3c7e867
--- /dev/null
+++ b/src/crepe/api/GameObject.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "../ComponentManager.h"
+
+#include "GameObject.h"
+
+namespace crepe::api {
+
+template <typename T, typename... Args>
+T & GameObject::add_component(Args &&... args) {
+ auto & mgr = ComponentManager::get_instance();
+ return mgr.add_component<T>(this->id, std::forward<Args>(args)...);
+}
+
+} // namespace crepe::api
diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp
new file mode 100644
index 0000000..0b3a9ee
--- /dev/null
+++ b/src/crepe/api/ParticleEmitter.cpp
@@ -0,0 +1,37 @@
+#include <ctime>
+#include <iostream>
+
+#include "Particle.h"
+#include "ParticleEmitter.h"
+
+using namespace crepe;
+
+ParticleEmitter::ParticleEmitter(uint32_t game_object_id,
+ uint32_t max_particles, uint32_t emission_rate,
+ uint32_t speed, uint32_t speed_offset,
+ uint32_t angle, uint32_t angleOffset,
+ float begin_lifespan, float end_lifespan)
+ : Component(game_object_id), max_particles(max_particles),
+ emission_rate(emission_rate), speed(speed), speed_offset(speed_offset),
+ position{0, 0}, begin_lifespan(begin_lifespan),
+ end_lifespan(end_lifespan) {
+ std::srand(
+ static_cast<uint32_t>(std::time(nullptr))); // initialize random seed
+ std::cout << "Create emitter" << std::endl;
+ // FIXME: Why do these expressions start with `360 +`, only to be `% 360`'d
+ // right after? This does not make any sense to me.
+ min_angle = (360 + angle - (angleOffset % 360)) % 360;
+ max_angle = (360 + angle + (angleOffset % 360)) % 360;
+ position.x = 400; // FIXME: what are these magic values?
+ position.y = 400;
+ for (size_t i = 0; i < max_particles; i++) {
+ this->particles.emplace_back();
+ }
+}
+
+ParticleEmitter::~ParticleEmitter() {
+ std::vector<Particle>::iterator it = this->particles.begin();
+ while (it != this->particles.end()) {
+ it = this->particles.erase(it);
+ }
+}
diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h
new file mode 100644
index 0000000..2e2e95b
--- /dev/null
+++ b/src/crepe/api/ParticleEmitter.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+#include "Component.h"
+#include "Particle.h"
+
+namespace crepe {
+
+class ParticleEmitter : public Component {
+public:
+ ParticleEmitter(uint32_t game_object_id, uint32_t max_particles,
+ uint32_t emission_rate, uint32_t speed,
+ uint32_t speed_offset, uint32_t angle, uint32_t angleOffset,
+ float begin_lifespan, float end_lifespan);
+ ~ParticleEmitter();
+
+ //! position of the emitter
+ Position position;
+ //! maximum number of particles
+ uint32_t max_particles;
+ //! rate of particle emission
+ uint32_t emission_rate;
+ //! base speed of the particles
+ uint32_t speed;
+ //! offset for random speed variation
+ uint32_t speed_offset;
+ //! min angle of particle emission
+ uint32_t min_angle;
+ //! max angle of particle emission
+ uint32_t max_angle;
+ //! begin Lifespan of particle (only visual)
+ float begin_lifespan;
+ //! begin Lifespan of particle
+ float end_lifespan;
+
+ //! collection of particles
+ std::vector<Particle> particles;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/Point.h b/src/crepe/api/Point.h
new file mode 100644
index 0000000..b47b7e6
--- /dev/null
+++ b/src/crepe/api/Point.h
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace crepe::api {
+
+class Point {
+public:
+ double x;
+ double y;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp
new file mode 100644
index 0000000..ebf9fb9
--- /dev/null
+++ b/src/crepe/api/Rigidbody.cpp
@@ -0,0 +1,8 @@
+#include "Rigidbody.h"
+
+using namespace crepe::api;
+
+Rigidbody::Rigidbody(uint32_t game_object_id, int mass, int gravity_scale,
+ BodyType bodyType)
+ : Component(game_object_id), mass(mass), gravity_scale(gravity_scale),
+ body_type(bodyType) {}
diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h
new file mode 100644
index 0000000..6079a76
--- /dev/null
+++ b/src/crepe/api/Rigidbody.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <cstdint>
+
+#include "../Component.h"
+
+namespace crepe::api {
+
+// FIXME: can't this enum be defined inside the class declaration of Rigidbody?
+enum class BodyType {
+ //! Does not move (e.g. walls, ground ...)
+ STATIC,
+ //! Moves and responds to forces (e.g. player, physics objects ...)
+ DYNAMIC,
+ //! Moves but does not respond to forces (e.g. moving platforms ...)
+ KINEMATIC,
+};
+
+class Rigidbody : public Component {
+public:
+ Rigidbody(uint32_t game_object_id, int mass, int gravity_scale,
+ BodyType body_type);
+ int32_t velocity_x;
+ int32_t velocity_y;
+ int mass;
+ int gravity_scale;
+ BodyType body_type;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp
new file mode 100644
index 0000000..5016ed0
--- /dev/null
+++ b/src/crepe/api/Script.cpp
@@ -0,0 +1,3 @@
+#include "Script.h"
+
+using namespace crepe::api;
diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h
new file mode 100644
index 0000000..59e6ec0
--- /dev/null
+++ b/src/crepe/api/Script.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <vector>
+
+namespace crepe {
+class ScriptSystem;
+}
+
+namespace crepe::api {
+
+class BehaviorScript;
+
+class Script {
+ friend class crepe::ScriptSystem;
+
+protected:
+ virtual void init() {}
+ virtual void update() {}
+ // NOTE: additional *events* (like unity's OnDisable and OnEnable) should be
+ // implemented as member methods in derivative user script classes and
+ // registered in init(), otherwise this class will balloon in size with each
+ // added event.
+
+protected:
+ template <typename T>
+ T & get_component();
+
+ template <typename T>
+ std::vector<std::reference_wrapper<T>> get_components();
+
+private:
+ friend class crepe::api::BehaviorScript;
+ BehaviorScript * parent = nullptr;
+};
+
+} // namespace crepe::api
+
+#include "Script.hpp"
diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp
new file mode 100644
index 0000000..8004fe3
--- /dev/null
+++ b/src/crepe/api/Script.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "../ComponentManager.h"
+
+#include "BehaviorScript.h"
+#include "Script.h"
+
+namespace crepe::api {
+
+template <typename T>
+T & Script::get_component() {
+ std::vector<std::reference_wrapper<T>> all_components
+ = this->get_components<T>();
+ if (all_components.size() < 1) throw nullptr; // TODO
+
+ return all_components.back().get();
+}
+
+template <typename T>
+std::vector<std::reference_wrapper<T>> Script::get_components() {
+ ComponentManager & mgr = ComponentManager::get_instance();
+ return mgr.get_components_by_id<T>(this->parent->game_object_id);
+}
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp
new file mode 100644
index 0000000..806f147
--- /dev/null
+++ b/src/crepe/api/Sprite.cpp
@@ -0,0 +1,20 @@
+#include <cstdint>
+#include <memory>
+
+#include "api/Texture.h"
+#include "util/log.h"
+
+#include "Component.h"
+#include "Sprite.h"
+
+using namespace std;
+using namespace crepe;
+using namespace crepe::api;
+
+Sprite::Sprite(uint32_t id, shared_ptr<Texture> image, const Color & color,
+ const FlipSettings & flip)
+ : Component(id), color(color), flip(flip), sprite_image(image) {
+ dbg_trace();
+}
+
+Sprite::~Sprite() { dbg_trace(); }
diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h
new file mode 100644
index 0000000..b06125e
--- /dev/null
+++ b/src/crepe/api/Sprite.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <SDL2/SDL_rect.h>
+#include <cstdint>
+#include <memory>
+
+#include "api/Color.h"
+#include "api/Texture.h"
+
+#include "Component.h"
+
+namespace crepe::api {
+
+struct FlipSettings {
+ bool flip_x = 1;
+ bool flip_y = 1;
+};
+
+class Sprite : public Component {
+
+public:
+ Sprite(uint32_t game_id, std::shared_ptr<Texture> image,
+ const Color & color, const FlipSettings & flip);
+ ~Sprite();
+ std::shared_ptr<Texture> sprite_image;
+ Color color;
+ FlipSettings flip;
+ uint8_t sorting_in_layer;
+ uint8_t order_in_layer;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp
new file mode 100644
index 0000000..481ef7c
--- /dev/null
+++ b/src/crepe/api/Texture.cpp
@@ -0,0 +1,32 @@
+#include <SDL2/SDL_render.h>
+
+#include "util/log.h"
+
+#include "Asset.h"
+#include "SDLContext.h"
+#include "Texture.h"
+
+using namespace crepe::api;
+using namespace std;
+
+Texture::Texture(unique_ptr<Asset> res) {
+ dbg_trace();
+ this->load(std::move(res));
+}
+
+Texture::Texture(const char * src) {
+ dbg_trace();
+ this->load(make_unique<Asset>(src));
+}
+
+Texture::~Texture() {
+ dbg_trace();
+ if (this->texture != nullptr) {
+ SDL_DestroyTexture(this->texture);
+ }
+}
+
+void Texture::load(unique_ptr<Asset> res) {
+ SDLContext & ctx = SDLContext::get_instance();
+ this->texture = ctx.texture_from_path(res->canonical());
+}
diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h
new file mode 100644
index 0000000..f8481e3
--- /dev/null
+++ b/src/crepe/api/Texture.h
@@ -0,0 +1,33 @@
+#pragma once
+
+// FIXME: this header can't be included because this is an API header, and SDL2
+// development headers won't be bundled with crepe. Why is this facade in the
+// API namespace?
+#include <SDL2/SDL_render.h>
+#include <memory>
+
+#include "Asset.h"
+
+namespace crepe {
+class SDLContext;
+}
+
+namespace crepe::api {
+
+class Texture {
+
+public:
+ Texture(const char * src);
+ Texture(std::unique_ptr<Asset> res);
+ ~Texture();
+
+private:
+ void load(std::unique_ptr<Asset> res);
+
+private:
+ SDL_Texture * texture = nullptr;
+
+ friend class crepe::SDLContext;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp
new file mode 100644
index 0000000..3b218bc
--- /dev/null
+++ b/src/crepe/api/Transform.cpp
@@ -0,0 +1,15 @@
+#include <cstdint>
+
+#include "api/Point.h"
+#include "util/log.h"
+
+#include "Component.h"
+#include "Transform.h"
+
+using namespace crepe::api;
+
+Transform::Transform(uint32_t game_id, const Point & point, double rot, double scale)
+ : Component(game_id), position(point), rotation(rot), scale(scale) {
+ dbg_trace();
+}
+Transform::~Transform() { dbg_trace(); }
diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h
new file mode 100644
index 0000000..c451c16
--- /dev/null
+++ b/src/crepe/api/Transform.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <cstdint>
+
+#include "api/Point.h"
+
+#include "Component.h"
+
+namespace crepe::api {
+
+class Transform : public Component {
+ // FIXME: What's the difference between the `Point` and `Position`
+ // classes/structs? How about we replace both with a universal `Vec2` that
+ // works similar (or the same) as those found in GLSL?
+
+public:
+ Transform(uint32_t id, const Point &, double, double);
+ ~Transform();
+ //! Translation (shift)
+ Point position;
+ //! Rotation, in radians
+ double rotation;
+ //! Multiplication factor
+ double scale;
+};
+
+} // namespace crepe::api
diff --git a/src/crepe/main.cpp b/src/crepe/main.cpp
deleted file mode 100644
index 8e9a184..0000000
--- a/src/crepe/main.cpp
+++ /dev/null
@@ -1,3 +0,0 @@
-#include <stdio.h>
-
-int main() { printf("Hello World!\n"); }
diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt
new file mode 100644
index 0000000..bbeaad9
--- /dev/null
+++ b/src/crepe/util/CMakeLists.txt
@@ -0,0 +1,12 @@
+target_sources(crepe PUBLIC
+ color.cpp
+ log.cpp
+ fmt.cpp
+)
+
+target_sources(crepe PUBLIC FILE_SET HEADERS FILES
+ color.h
+ log.h
+ fmt.h
+)
+
diff --git a/src/crepe/util/color.cpp b/src/crepe/util/color.cpp
new file mode 100644
index 0000000..a7bbc81
--- /dev/null
+++ b/src/crepe/util/color.cpp
@@ -0,0 +1,91 @@
+#include <cstdarg>
+
+#include "../api/Config.h"
+#include "color.h"
+#include "fmt.h"
+
+using namespace crepe::util;
+using namespace std;
+
+static constexpr const char * RESET_CODE = "\e[0m";
+
+const string LogColor::str(const string & content) {
+ auto & cfg = api::Config::get_instance();
+ string out = content;
+ if (cfg.log.color) out = this->code + out;
+ if (content.size() == 0) return out;
+ if (cfg.log.color) out = out + RESET_CODE;
+ return out;
+}
+
+const char * LogColor::c_str(const char * content) {
+ this->final = this->str(content == NULL ? "" : content);
+ return this->final.c_str();
+}
+
+const char * LogColor::fmt(const char * fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ string content = va_stringf(args, fmt);
+ va_end(args);
+ return this->c_str(content.c_str());
+}
+
+LogColor & LogColor::add_code(unsigned int code) {
+ this->code += stringf("\e[%dm", code);
+ return *this;
+}
+
+LogColor & LogColor::reset() {
+ this->code = RESET_CODE;
+ return *this;
+}
+
+LogColor & LogColor::fg_black(bool bright) {
+ return this->add_code(bright ? 90 : 30);
+}
+LogColor & LogColor::fg_red(bool bright) {
+ return this->add_code(bright ? 91 : 31);
+}
+LogColor & LogColor::fg_green(bool bright) {
+ return this->add_code(bright ? 92 : 32);
+}
+LogColor & LogColor::fg_yellow(bool bright) {
+ return this->add_code(bright ? 93 : 33);
+}
+LogColor & LogColor::fg_blue(bool bright) {
+ return this->add_code(bright ? 94 : 34);
+}
+LogColor & LogColor::fg_magenta(bool bright) {
+ return this->add_code(bright ? 95 : 35);
+}
+LogColor & LogColor::fg_cyan(bool bright) {
+ return this->add_code(bright ? 96 : 36);
+}
+LogColor & LogColor::fg_white(bool bright) {
+ return this->add_code(bright ? 97 : 37);
+}
+LogColor & LogColor::bg_black(bool bright) {
+ return this->add_code(bright ? 100 : 40);
+}
+LogColor & LogColor::bg_red(bool bright) {
+ return this->add_code(bright ? 101 : 41);
+}
+LogColor & LogColor::bg_green(bool bright) {
+ return this->add_code(bright ? 102 : 42);
+}
+LogColor & LogColor::bg_yellow(bool bright) {
+ return this->add_code(bright ? 103 : 43);
+}
+LogColor & LogColor::bg_blue(bool bright) {
+ return this->add_code(bright ? 104 : 44);
+}
+LogColor & LogColor::bg_magenta(bool bright) {
+ return this->add_code(bright ? 105 : 45);
+}
+LogColor & LogColor::bg_cyan(bool bright) {
+ return this->add_code(bright ? 106 : 46);
+}
+LogColor & LogColor::bg_white(bool bright) {
+ return this->add_code(bright ? 107 : 47);
+}
diff --git a/src/crepe/util/color.h b/src/crepe/util/color.h
new file mode 100644
index 0000000..91e1abe
--- /dev/null
+++ b/src/crepe/util/color.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <string>
+
+namespace crepe::util {
+
+class LogColor {
+public:
+ LogColor() = default;
+
+public:
+ //! get color code as c-style string (or color content string)
+ const char * c_str(const char * content = NULL);
+ //! color printf-style format string
+ const char * fmt(const char * fmt, ...);
+ //! get color code as stl string (or color content string)
+ const std::string str(const std::string & content = "");
+
+public:
+ //! reset color to default foreground and background color
+ LogColor & reset();
+
+public:
+ LogColor & fg_black(bool bright = false);
+ LogColor & fg_red(bool bright = false);
+ LogColor & fg_green(bool bright = false);
+ LogColor & fg_yellow(bool bright = false);
+ LogColor & fg_blue(bool bright = false);
+ LogColor & fg_magenta(bool bright = false);
+ LogColor & fg_cyan(bool bright = false);
+ LogColor & fg_white(bool bright = false);
+
+public:
+ LogColor & bg_black(bool bright = false);
+ LogColor & bg_red(bool bright = false);
+ LogColor & bg_green(bool bright = false);
+ LogColor & bg_yellow(bool bright = false);
+ LogColor & bg_blue(bool bright = false);
+ LogColor & bg_magenta(bool bright = false);
+ LogColor & bg_cyan(bool bright = false);
+ LogColor & bg_white(bool bright = false);
+
+private:
+ LogColor & add_code(unsigned int code);
+
+private:
+ std::string code = "";
+ std::string final = "";
+};
+
+} // namespace crepe::util
diff --git a/src/crepe/util/fmt.cpp b/src/crepe/util/fmt.cpp
new file mode 100644
index 0000000..8ef1164
--- /dev/null
+++ b/src/crepe/util/fmt.cpp
@@ -0,0 +1,33 @@
+#include <cstdarg>
+#include <cstdio>
+#include <string>
+
+#include "fmt.h"
+
+using namespace std;
+
+string crepe::util::va_stringf(va_list args, const char * fmt) {
+ va_list args_copy;
+ va_copy(args_copy, args);
+
+ size_t sz = vsnprintf(NULL, 0, fmt, args_copy) + 1;
+ char * msg = (char *) malloc(sz);
+ va_end(args_copy);
+
+ vsnprintf(msg, sz, fmt, args);
+
+ string out = msg;
+ free(msg);
+
+ va_end(args);
+
+ return out;
+}
+
+string crepe::util::stringf(const char * fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ string out = va_stringf(args, fmt);
+ va_end(args);
+ return out;
+}
diff --git a/src/crepe/util/fmt.h b/src/crepe/util/fmt.h
new file mode 100644
index 0000000..44c426f
--- /dev/null
+++ b/src/crepe/util/fmt.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <string>
+
+namespace crepe::util {
+
+std::string va_stringf(va_list args, const char * fmt);
+std::string stringf(const char * fmt, ...);
+
+} // namespace crepe::util
diff --git a/src/crepe/util/log.cpp b/src/crepe/util/log.cpp
new file mode 100644
index 0000000..6bcc4ae
--- /dev/null
+++ b/src/crepe/util/log.cpp
@@ -0,0 +1,53 @@
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include "../api/Config.h"
+#include "fmt.h"
+#include "log.h"
+
+using namespace crepe::util;
+using namespace std;
+
+string log_prefix(LogLevel level) {
+ switch (level) {
+ case LogLevel::TRACE:
+ return LogColor().fg_white().str("[TRACE]") + " ";
+ case LogLevel::DEBUG:
+ return LogColor().fg_magenta().str("[DEBUG]") + " ";
+ case LogLevel::INFO:
+ return LogColor().fg_blue().str("[INFO]") + " ";
+ case LogLevel::WARNING:
+ return LogColor().fg_yellow().str("[WARN]") + " ";
+ case LogLevel::ERROR:
+ return LogColor().fg_red().str("[ERROR]") + " ";
+ }
+ return "";
+}
+
+static void log(LogLevel level, const string msg) {
+ auto & cfg = crepe::api::Config::get_instance();
+ if (level < cfg.log.level) return;
+
+ string out = log_prefix(level) + msg;
+ if (!out.ends_with("\n")) out += "\n";
+
+ // TODO: also log to file or smth
+ printf("%s", out.c_str());
+ fflush(stdout);
+}
+
+void crepe::util::logf(const char * fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ log(LogLevel::DEBUG, va_stringf(args, fmt));
+ va_end(args);
+}
+
+void crepe::util::logf(LogLevel level, const char * fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ log(level, va_stringf(args, fmt));
+ va_end(args);
+}
diff --git a/src/crepe/util/log.h b/src/crepe/util/log.h
new file mode 100644
index 0000000..308ba96
--- /dev/null
+++ b/src/crepe/util/log.h
@@ -0,0 +1,40 @@
+#pragma once
+
+// allow user to disable debug macros
+#ifndef CREPE_DISABLE_MACROS
+
+#include "color.h"
+
+// utility macros
+#define _crepe_logf_here(level, format, ...) \
+ crepe::util::logf( \
+ level, "%s" format, \
+ crepe::util::LogColor().fg_white(false).fmt( \
+ "%s (%s:%d)", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__), \
+ __VA_ARGS__)
+
+// very illegal global function-style macros
+// NOLINTBEGIN
+#define dbg_logf(fmt, ...) \
+ _crepe_logf_here(crepe::util::LogLevel::DEBUG, ": " fmt, __VA_ARGS__)
+#define dbg_log(str) \
+ _crepe_logf_here(crepe::util::LogLevel::DEBUG, "%s: " str, "")
+#define dbg_trace() _crepe_logf_here(crepe::util::LogLevel::TRACE, "%s", "")
+// NOLINTEND
+
+#endif
+
+namespace crepe::util {
+
+enum LogLevel {
+ TRACE,
+ DEBUG,
+ INFO,
+ WARNING,
+ ERROR,
+};
+
+void logf(const char * fmt, ...);
+void logf(enum LogLevel level, const char * fmt, ...);
+
+} // namespace crepe::util