From ba614edd29c27c6e3648c42c8e1c4e8626e56693 Mon Sep 17 00:00:00 2001 From: Sara Date: Tue, 10 Sep 2024 22:09:46 +0200 Subject: [PATCH] feat: defined various core engine concepts - Scene Tree - Engine loop - Draw list - Renderables - Resources --- include/resource_dir.h | 95 ---------------------------------- src/core/camera_node.h | 18 +++++++ src/core/engine_loop.c | 35 +++++++++++++ src/core/engine_loop.h | 14 ++++++ src/core/render.c | 35 +++++++++++++ src/core/render.h | 24 +++++++++ src/core/renderable.h | 23 +++++++++ src/core/resources.c | 64 +++++++++++++++++++++++ src/core/resources.h | 11 ++++ src/core/scene.c | 98 ++++++++++++++++++++++++++++++++++++ src/core/scene.h | 27 ++++++++++ src/core/scene_node_entity.h | 47 +++++++++++++++++ src/core/transformable.c | 19 +++++++ src/core/transformable.h | 31 ++++++++++++ 14 files changed, 446 insertions(+), 95 deletions(-) delete mode 100644 include/resource_dir.h create mode 100644 src/core/camera_node.h create mode 100644 src/core/engine_loop.c create mode 100644 src/core/engine_loop.h create mode 100644 src/core/render.c create mode 100644 src/core/render.h create mode 100644 src/core/renderable.h create mode 100644 src/core/resources.c create mode 100644 src/core/resources.h create mode 100644 src/core/scene.c create mode 100644 src/core/scene.h create mode 100644 src/core/scene_node_entity.h create mode 100644 src/core/transformable.c create mode 100644 src/core/transformable.h 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 };\ +} +