diff --git a/include/resource_dir.h b/include/resource_dir.h
deleted file mode 100644
index 65096f0..0000000
--- a/include/resource_dir.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/**********************************************************************************************
-*
-* raylibExtras * Utilities and Shared Components for Raylib
-*
-* Resource Dir * function to help find resource dir in common locations
-*
-* LICENSE: MIT
-*
-* Copyright (c) 2022 Jeffery Myers
-*
-* Permission is hereby granted, free of charge, to any person obtaining a copy
-* of this software and associated documentation files (the "Software"), to deal
-* in the Software without restriction, including without limitation the rights
-* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-* copies of the Software, and to permit persons to whom the Software is
-* furnished to do so, subject to the following conditions:
-*
-* The above copyright notice and this permission notice shall be included in all
-* copies or substantial portions of the Software.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-* SOFTWARE.
-*
-**********************************************************************************************/
-
-#pragma once
-
-#include "raylib.h"
-
-#if defined(__cplusplus)
-extern "C" { // Prevents name mangling of functions
-#endif
- ///
- /// Looks for the specified resource dir in several common locations
- /// The working dir
- /// The app dir
- /// Up to 3 levels above the app dir
- /// When found the dir will be set as the working dir so that assets can be loaded relative to that.
- ///
- /// The name of the resources dir to look for
- /// True if a dir with the name was found, false if no change was made to the working dir
- inline static bool SearchAndSetResourceDir(const char* folderName)
- {
- // check the working dir
- if (DirectoryExists(folderName))
- {
- ChangeDirectory(TextFormat("%s/%s", GetWorkingDirectory(), folderName));
- return true;
- }
-
- const char* appDir = GetApplicationDirectory();
-
- // check the applicationDir
- const char* dir = TextFormat("%s%s", appDir, folderName);
- if (DirectoryExists(dir))
- {
- ChangeDirectory(dir);
- return true;
- }
-
- // check one up from the app dir
- dir = TextFormat("%s../%s", appDir, folderName);
- if (DirectoryExists(dir))
- {
- ChangeDirectory(dir);
- return true;
- }
-
- // check two up from the app dir
- dir = TextFormat("%s../../%s", appDir, folderName);
- if (DirectoryExists(dir))
- {
- ChangeDirectory(dir);
- return true;
- }
-
- // check three up from the app dir
- dir = TextFormat("%s../../../%s", appDir, folderName);
- if (DirectoryExists(dir))
- {
- ChangeDirectory(dir);
- return true;
- }
-
- return false;
- }
-
-#if defined(__cplusplus)
-}
-#endif
\ No newline at end of file
diff --git a/src/core/camera_node.h b/src/core/camera_node.h
new file mode 100644
index 0000000..4f3f4ee
--- /dev/null
+++ b/src/core/camera_node.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "scene_node_entity.h"
+#include "utils/drop.h"
+#include "raylib.h"
+
+typedef struct CameraNode {
+ SceneNode *node;
+} CameraNode;
+
+void CameraNodeEnterTree(CameraNode *self);
+void CameraNodeExitTree(CameraNode *self);
+void CameraNodeTick(CameraNode *self, double delta);
+Camera3D CameraNodeGetCamera(CameraNode *self);
+
+impl_default_Drop_for(CameraNode);
+impl_SceneNodeEntity_for(CameraNode, CameraNodeEnterTree, CameraNodeExitTree, CameraNodeTick);
+
diff --git a/src/core/engine_loop.c b/src/core/engine_loop.c
new file mode 100644
index 0000000..930cf6e
--- /dev/null
+++ b/src/core/engine_loop.c
@@ -0,0 +1,35 @@
+#include "engine_loop.h"
+#include "resources.h"
+#include "render.h"
+#include "stdlib.h"
+#include "raylib.h"
+
+static
+void InitializeRaylibContext() {
+ // initialize fullscreen game window
+ SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
+ InitWindow(1280, 800, "Hello Raylib");
+ if(!IsWindowFullscreen())
+ ToggleFullscreen();
+}
+
+void RunGame() {
+ while (!WindowShouldClose()) {
+ RenderNextFrame();
+ }
+ ShutDown();
+}
+
+void InitializeEngine() {
+ // initialize context
+ InitializeRaylibContext();
+ // initialize engine subsystems
+ InitializeResourceSubsystem();
+ InitializeRenderingSubsystem();
+}
+
+void ShutDown() {
+ CleanResourceSubsystem();
+ CloseWindow();
+ exit(0);
+}
diff --git a/src/core/engine_loop.h b/src/core/engine_loop.h
new file mode 100644
index 0000000..5159d8a
--- /dev/null
+++ b/src/core/engine_loop.h
@@ -0,0 +1,14 @@
+#pragma once
+
+typedef struct GameContext {
+} GameContext;
+
+// IMPORTANT: should be implemented by game code
+extern void InitializeGame(GameContext *settings);
+
+//! initialize window and application context
+extern void InitializeEngine();
+//! run the actual game, requires InitializeEngine to be called first
+extern void RunGame();
+// shut down game entirely
+extern void ShutDown();
diff --git a/src/core/render.c b/src/core/render.c
new file mode 100644
index 0000000..b06b1bc
--- /dev/null
+++ b/src/core/render.c
@@ -0,0 +1,35 @@
+#include "render.h"
+#include "utils/debug.h"
+#include "utils/list.h"
+
+static List g_render_objects = {}; //!< List of all registered rendering objects
+CameraNode *camera = NULL; //!< Reference to current main camera
+
+void InitializeRenderingSubsystem() {
+ g_render_objects = list_from_type(Renderable);
+}
+
+void CleanupRenderingSubsystem() {
+ list_empty(&g_render_objects);
+}
+
+void AddRenderable(Renderable renderable) {
+ ASSERT_RETURN(list_find(&g_render_objects, &renderable) == g_render_objects.len,, "AddRenderable: Argument is already registered, cannot register twice");
+ list_add(&g_render_objects, &renderable);
+}
+
+void RemoveRenderable(Renderable renderable) {
+ size_t idx = list_find(&g_render_objects, &renderable);
+ ASSERT_RETURN(idx != g_render_objects.len,, "RemoveRenderable: Argument is not registered, cannot unregister.");
+ list_erase(&g_render_objects, idx);
+}
+
+void RenderNextFrame() {
+ BeginDrawing();
+ ClearBackground(BLACK);
+ BeginMode3D(CameraNodeGetCamera(camera));
+ list_foreach(Renderable *,object, &g_render_objects)
+ object->tc->draw(object->data);
+ EndMode3D();
+ EndDrawing();
+}
diff --git a/src/core/render.h b/src/core/render.h
new file mode 100644
index 0000000..5ca062c
--- /dev/null
+++ b/src/core/render.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "camera_node.h"
+#include "renderable.h"
+#include "raylib.h"
+
+//! Initialize the rendering subsystem.
+extern void InitializeRenderingSubsystem();
+//! Cleanup after rendering system,
+//! assumes you won't want to re-initialize after.
+extern void CleanupRenderingSubsystem();
+
+//! Register a renderable object through a typeclass.
+extern void AddRenderable(Renderable renderable);
+//! Remove a renderable object from draw list
+extern void RemoveRenderable(Renderable renderable);
+
+//! Set a camera as the main rendering camera.
+extern void SetMainCamera(CameraNode *camera);
+//! Get the current main rendering camera.
+extern void GetMainCamera(CameraNode *camera);
+
+//! Draw a frame to the screen based on the current state of the game.
+extern void RenderNextFrame();
diff --git a/src/core/renderable.h b/src/core/renderable.h
new file mode 100644
index 0000000..f458001
--- /dev/null
+++ b/src/core/renderable.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "scene_node_entity.h"
+#include "utils/typeclass_helpers.h"
+
+typedef struct IRenderable {
+ void (*const draw)(void *self);
+} IRenderable;
+
+typedef struct Renderable {
+ IRenderable const *tc;
+ void *data;
+ ISceneNodeEntity const *scene_node_entity;
+} Renderable;
+
+#define impl_Renderable_for(T, draw_f)\
+Renderable T##_as_Renderable(T *x) {\
+ TC_FN_TYPECHECK(void, draw_f, T*);\
+ static IRenderable const tc = {\
+ .draw = (void(*const)(void*)) draw_f\
+ };\
+ return (Renderable){ .tc = &tc, .data = x, .scene_node_entity = T##_as_SceneNodeEntity(x).tc };\
+}
diff --git a/src/core/resources.c b/src/core/resources.c
new file mode 100644
index 0000000..98e6b64
--- /dev/null
+++ b/src/core/resources.c
@@ -0,0 +1,64 @@
+#include "resources.h"
+#include "stdint.h"
+#include "utils/dictionary.h"
+#include "utils/debug.h"
+
+char const *RESOURCE_DIRECTORY = "resources";
+
+typedef enum ResourceType {
+ MODEL_RESOURCE = 0, TEXTURE_RESOURCE
+} ResourceType;
+
+typedef struct ResourceContainer {
+ uint32_t rid;
+ char const *resource_name;
+ ResourceType type;
+ union {
+ Model model;
+ Texture texture;
+ };
+} ResourceContainer;
+
+static Dictionary g_resources = {};
+
+static inline
+void Internal_CleanResourceContainer(ResourceContainer *container) {
+ switch(container->type) {
+ case MODEL_RESOURCE:
+ UnloadModel(container->model);
+ return;
+ case TEXTURE_RESOURCE:
+ UnloadTexture(container->texture);
+ return;
+ }
+ UNREACHABLE("CleanResourceContainer: Cleanup function not defined for %u", container->type);
+}
+
+static inline
+void InitializeResourceDirectory() {
+ // if there is a resource directory in the working directory, prioritize that for debugging
+ if(DirectoryExists(RESOURCE_DIRECTORY)) {
+ ChangeDirectory(TextFormat("%s/%s", GetWorkingDirectory(), RESOURCE_DIRECTORY));
+ LOG_WARNING("Using working dir resources, this is possible for debug purposes.");
+ }
+ // check application installation directory for a resource directory
+ // this is the default running environment
+ char const *installation_resource_dir = TextFormat(GetApplicationDirectory(), RESOURCE_DIRECTORY);
+ if(DirectoryExists(installation_resource_dir)) {
+ ChangeDirectory(installation_resource_dir);
+ return;
+ }
+ UNREACHABLE("Failed to find resource directory");
+}
+
+void InitializeResourceSubsystem() {
+ InitializeResourceDirectory();
+ g_resources = dictionary_from_type(ResourceContainer);
+}
+
+void CleanResourceSubsystem() {
+ list_foreach(ResourceContainer *,container, &g_resources.list)
+ Internal_CleanResourceContainer(container);
+ dictionary_empty(&g_resources);
+}
+
diff --git a/src/core/resources.h b/src/core/resources.h
new file mode 100644
index 0000000..bfe1866
--- /dev/null
+++ b/src/core/resources.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "raylib.h"
+
+extern void InitializeResourceSubsystem();
+extern void CleanResourceSubsystem();
+
+//! Load a model resource from disk, path is relative to the resource folder
+extern Model *ResourcesLoadModel(char const *res_path);
+//! Load a texture resource from disk, path is relative to the resource folder
+extern Texture *ResourcesLoadTexture(char const *res_path);
diff --git a/src/core/scene.c b/src/core/scene.c
new file mode 100644
index 0000000..5e37e2d
--- /dev/null
+++ b/src/core/scene.c
@@ -0,0 +1,98 @@
+#include "scene.h"
+#include "scene_node_entity.h"
+#include "utils/debug.h"
+#include "utils/list.h"
+#include "utils/drop.h"
+#include "utils/mirror.h"
+
+struct SceneNode {
+ SceneNode *parent;
+ Scene *scene;
+ List children; //!< list of child SceneNodes
+ SceneNodeEntity entity; //!< scene node entity that adds functionality to the node
+};
+
+struct Scene {
+ SceneNode *root;
+ List nodes;
+};
+
+static
+void Internal_SceneRemoveNode(Scene *self, SceneNode *node) {
+ size_t idx = list_find(&self->nodes, node);
+ list_erase(&self->nodes, idx);
+}
+
+static
+void Internal_SceneAddNode(Scene *self, SceneNode node) {
+ ASSERT_RETURN(list_find(&self->nodes, &node) == self->nodes.len,, "Attempting to add node that is already in this scene");
+ ASSERT_RETURN(node.scene != NULL,, "Attempting to add node that is already in a scene, remove it from that scene first");
+ list_add(&self->nodes, &node);
+}
+
+SceneNode *CreateSceneNode() {
+ SceneNode *node = new(SceneNode);
+ ASSERT_RETURN(node != NULL, NULL, "CreateSceneNode: Failed to instantiate new SceneNode");
+ node->children = list_from_type(SceneNode*);
+ return node;
+}
+
+void DestroySceneNode(SceneNode *self) {
+ // remove all children from scene as well
+ list_foreach(SceneNode **, child, &self->children)
+ DestroySceneNode(*child);
+ // inform entity of exit tree event
+ self->entity.tc->exit_tree(self->entity.data);
+ // remove node from scene if it is part of one
+ if(self->scene != NULL)
+ Internal_SceneRemoveNode(self->scene, self);
+ if(self->entity.data != NULL) {
+ self->entity.drop->drop(self->entity.data);
+ }
+ free(self);
+}
+
+SceneNode *SceneNodeGetChild(SceneNode *self, size_t idx) {
+ return *list_at_as(SceneNode*, &self->children, idx);
+}
+
+void SceneNodeAddChild(SceneNode *self, SceneNode *child) {
+ // catch logic error of trying to attach a child that already has a parent
+ ASSERT_RETURN(child->parent != NULL,, "SceneNodeAddChild: New child node already has a parent");
+ // register parent-child relationship
+ list_add(&self->children, &child);
+ child->parent = self;
+ // make sure scene matches
+ child->scene = self->scene;
+ // notify child of scene entrance
+ child->entity.tc->enter_tree(child->entity.data);
+}
+
+void SceneNodeRemoveChild(SceneNode *self, SceneNode *child) {
+ // catch error of attempting to remove non-child node
+ ASSERT_RETURN(child->parent == self,, "SceneNodeRemoveChild: Cannot remove node that is not a child of this node.");
+ // get the index and use it to ensure that the child is actually registered with the parent
+ size_t const idx = list_find(&self->children, child);
+ ASSERT_RETURN(idx == self->children.len,, "IMPORTANT: SceneNodeRemoveChild: child is not registered with parent.");
+ // we now know that the child is actually a child of self.
+ // notify child that it is being removed from the tree.
+ child->entity.tc->exit_tree(child->entity.data);
+ // use the index to erase the object from the list
+ list_erase(&self->children, idx);
+ Internal_SceneRemoveNode(child->scene, child);
+ child->scene = NULL;
+
+}
+
+void Internal_SceneNodeAttachEntity(SceneNodeEntity object) {
+ object.tc->get_node(object.data)->entity = object;
+}
+
+
+Scene *CreateScene(SceneNode const *root) {
+ Scene *scene = new(Scene);
+ scene->nodes = list_from_type(SceneNode);
+ Internal_SceneAddNode(scene, *root);
+ root->entity.tc->enter_tree(root->entity.data);
+ return scene;
+}
diff --git a/src/core/scene.h b/src/core/scene.h
new file mode 100644
index 0000000..9f60899
--- /dev/null
+++ b/src/core/scene.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "stddef.h"
+#include "utils/typeclass_helpers.h"
+typedef struct SceneNodeEntity SceneNodeEntity;
+
+typedef struct SceneNode SceneNode;
+typedef struct Scene Scene;
+
+//! Instantiate a new scene node.
+extern SceneNode *CreateSceneNode();
+//! Free a scene node.
+extern void DestroySceneNode(SceneNode *self);
+//! Get a child from a scene node.
+extern SceneNode *SceneNodeGetChild(SceneNode *self, size_t idx);
+//! Attach a child to a node
+extern void SceneNodeAddChild(SceneNode *self, SceneNode *child);
+//! Detach a child from a node, removing it from the scene
+extern void SceneNodeRemoveChild(SceneNode *self, SceneNode *child);
+
+//! INTERNAL FUNCTION, used to attach a scene node entity typeclass to an object.
+extern void Internal_SceneNodeAttachEntity(SceneNodeEntity object);
+
+//! Instantiate a new scene with a root node.
+extern Scene *CreateScene(SceneNode const *root);
+//! Destroy a node and it's scene tree.
+extern void DestroyScene(Scene *scene);
diff --git a/src/core/scene_node_entity.h b/src/core/scene_node_entity.h
new file mode 100644
index 0000000..512cbe6
--- /dev/null
+++ b/src/core/scene_node_entity.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "scene.h"
+#include "utils/mirror.h"
+#include "utils/drop.h"
+
+struct SceneNode;
+
+typedef struct ISceneNodeEntity {
+ void(*const enter_tree)(void *self);
+ void(*const exit_tree)(void *self);
+ void(*const tick)(void *self, double delta);
+ struct SceneNode *(*const get_node)(void *self);
+ void (*const set_node)(void *self, struct SceneNode *node);
+} ISceneNodeEntity;
+
+typedef struct SceneNodeEntity {
+ ISceneNodeEntity const *tc;
+ void *data;
+ IMirror const *mirror;
+ IDrop const *drop;
+} SceneNodeEntity;
+
+//! Implement SceneNodeEntity for a struct.
+//! IMPORTANT: requires Mirror and Drop to be implemented,
+//! as well as a `SceneNode *node` member on T.
+//! Generates functions called T##_get_node_GEN_ and T##_set_node_GEN_ as getter and setter for that member.
+#define impl_SceneNodeEntity_for(T, enter_tree_f, exit_tree_f, tick_f)\
+static struct SceneNode *T##_get_node_GEN_(T *self) { return self->node; }\
+static void T##_set_node_GEN_(T *self, struct SceneNode *node) { self->node = node; }\
+SceneNodeEntity T##AttachToSceneNode(T* x, SceneNode *node) {\
+ TC_FN_TYPECHECK(void, enter_tree_f, T*);\
+ TC_FN_TYPECHECK(void, exit_tree_f, T*);\
+ TC_FN_TYPECHECK(void, tick_f, T*, double);\
+ static ISceneNodeEntity const tc = {\
+ .enter_tree = (void(*const)(void*)) enter_tree_f,\
+ .exit_tree = (void(*const)(void*)) exit_tree_f,\
+ .tick = (void(*const)(void*,double)) tick_f,\
+ .get_node = (struct SceneNode *(*const)(void*)) T##_get_node_GEN_,\
+ .set_node = (void (*const)(void*, struct SceneNode*)) T##_set_node_GEN_\
+ };\
+ Internal_AttachToSceneNode(object);\
+ x->node = node;\
+ SceneNodeEntity e = { .tc = &tc, .data = x, .mirror = T##_as_Mirror(x).tc, .drop = T##_as_Drop(x).tc };\
+ Internal_SceneNodeAttachEntity(e);\
+ return e;\
+}
diff --git a/src/core/transformable.c b/src/core/transformable.c
new file mode 100644
index 0000000..536db0d
--- /dev/null
+++ b/src/core/transformable.c
@@ -0,0 +1,19 @@
+#include "transformable.h"
+
+Matrix TransformGetMatrix(Transform const *self) {
+ Matrix mat = MatrixScale(self->scale.x, self->scale.y, self->scale.z);
+ mat = MatrixMultiply(mat, QuaternionToMatrix(self->rotation));
+ mat.m12 = self->translation.x;
+ mat.m13 = self->translation.y;
+ mat.m14 = self->translation.z;
+ return mat;
+}
+
+void UpdateTransformable(Transformable *self, Transformable *parent) {
+ Transform *parent_global = self->tc->get_global_transform(self->data);
+ Transform *global = self->tc->get_global_transform(self->data);
+ Transform *local = self->tc->get_transform(self->data);
+ global->translation = Vector3Add(parent_global->translation, local->translation);
+ global->rotation = QuaternionMultiply(parent_global->rotation, local->rotation);
+ global->scale = Vector3Multiply(parent_global->scale, local->scale);
+}
diff --git a/src/core/transformable.h b/src/core/transformable.h
new file mode 100644
index 0000000..e8ef415
--- /dev/null
+++ b/src/core/transformable.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "utils/mirror.h"
+#include "utils/typeclass_helpers.h"
+#include "raylib.h"
+#include "raymath.h"
+
+typedef struct ITransformable {
+ Transform *(*const get_transform)(void*);
+ Transform *(*const get_global_transform)(void*);
+} ITransformable;
+
+typedef struct Transformable {
+ ITransformable const *tc;
+ void *data;
+ IMirror const *mirror;
+} Transformable;
+
+extern Matrix TransformGetMatrix(Transform const *self);
+extern void UpdateTransformable(Transformable *self, Transformable *parent);
+
+#define impl_Transformable_for(T, get_transform_f)\
+Transformable T##_as_Transformable(T *x) {\
+ TC_FN_TYPECHECK(Transform, get_transform_f, T*);\
+ static ITransformable const tc = {\
+ .get_transform = (Transform *(*const)(void*)) get_transform_f,\
+ .get_global_transform = (Transform *(*const)(void*)) get_global_transform_f\
+ };\
+ return { .tc = &tc, data = x, .mirror = T##_as_Mirror(x).tc };\
+}
+