remove unnecessary scene container

Mon, 01 Apr 2024 18:54:19 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 01 Apr 2024 18:54:19 +0200
changeset 47
44457f6cb0a2
parent 46
d3285aed65b3
child 48
6e5b5ba2752c

remove unnecessary scene container

src/ascension/camera.h file | annotate | diff | comparison | revisions
src/ascension/scene.h file | annotate | diff | comparison | revisions
src/ascension/window.h file | annotate | diff | comparison | revisions
src/camera.c file | annotate | diff | comparison | revisions
src/context.c file | annotate | diff | comparison | revisions
src/scene.c file | annotate | diff | comparison | revisions
src/window.c file | annotate | diff | comparison | revisions
test/snake.c file | annotate | diff | comparison | revisions
--- a/src/ascension/camera.h	Fri Mar 29 00:03:25 2024 +0100
+++ b/src/ascension/camera.h	Mon Apr 01 18:54:19 2024 +0200
@@ -30,30 +30,14 @@
 
 #include "datatypes.h"
 
-enum AscCameraType {
-    ASC_CAMERA_DISABLED,
-    ASC_CAMERA_ORTHO,
-    ASC_CAMERA_PERSPECTIVE,
-};
-
 typedef struct AscCamera AscCamera;
-typedef void(*asc_camera_update_func)(AscCamera*);
 
 struct AscCamera {
-    asc_camera_update_func update;
     asc_mat4f projection;
     asc_mat4f view;
-    asc_vec3f right;
-    asc_vec3f up;
-    asc_vec3f direction;
-    asc_recti rect;
-    // TODO: fov?
 };
 
 __attribute__((__nonnull__))
 void asc_camera_ortho(AscCamera *camera, asc_recti rect);
 
-__attribute__((__nonnull__))
-void asc_camera_disable(AscCamera *camera);
-
 #endif //ASCENSION_CAMERA_H
--- a/src/ascension/scene.h	Fri Mar 29 00:03:25 2024 +0100
+++ b/src/ascension/scene.h	Mon Apr 01 18:54:19 2024 +0200
@@ -32,25 +32,18 @@
 #include "transform.h"
 #include "camera.h"
 
-#include <cx/array_list.h>
+#include <cx/list.h>
 
 typedef struct AscSceneNode AscSceneNode;
-typedef struct AscBehaviorNode AscBehaviorNode;
 
 typedef void(*asc_scene_free_func)(AscSceneNode*);
 typedef void(*asc_scene_update_func)(AscSceneNode*);
 typedef void(*asc_scene_draw_func)(AscSceneNode const*);
 
-struct AscBehaviorNode {
-    AscSceneNode *parent;
-    AscBehaviorNode *prev;
-    AscBehaviorNode *next;
-    asc_scene_update_func func;
-};
-
 enum AscRenderGroup {
     ASC_RENDER_GROUP_SPRITE_OPAQUE,
-    ASC_RENDER_GROUP_SPRITE_BLEND
+    ASC_RENDER_GROUP_SPRITE_BLEND,
+    ASC_RENDER_GROUP_COUNT
 };
 
 struct AscSceneNode {
@@ -58,7 +51,7 @@
     AscSceneNode *prev;
     AscSceneNode *next;
     AscSceneNode *children;
-    AscBehaviorNode *behaviors;
+    CxList *behaviors;
     asc_scene_free_func free_func;
     asc_scene_update_func update_func;
     asc_scene_draw_func draw_func;
@@ -74,42 +67,19 @@
 };
 
 /**
- * Place this as first member of a structure that shall be usable as a scene node.
+ * Place this as first member of a structure that shall be used as a scene node.
  */
 #define extend_asc_scene_node AscSceneNode base
 
-struct asc_render_group_entry {
-    asc_scene_draw_func draw;
-    AscSceneNode const *node;
-};
-
-#define ASC_SCENE_CAMERAS_MAX 4
-
-typedef struct AscScene {
-    AscSceneNode *root;
-    asc_recti viewport;
-    AscCamera cameras[ASC_SCENE_CAMERAS_MAX];
-    CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_sprites_opaque);
-    CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_sprites_blended);
-} AscScene;
-
 /**
- * Initializes the scene using the active window as reference.
+ * Draws the scene with the specified root node.
  *
- * @param scene the scene to initialize
- * @param type determines the type of camera to use
+ * @param root the root node of the scene graph
+ * @param viewport the window viewport the scene shall be drawn to
+ * @param camera the camera to obtain the view and projection matrix from
  */
 __attribute__((__nonnull__))
-void asc_scene_init(AscScene *scene);
-
-__attribute__((__nonnull__))
-void asc_scene_destroy(AscScene *scene);
-
-__attribute__((__nonnull__))
-void asc_scene_draw(AscScene *scene);
-
-__attribute__((__nonnull__))
-void asc_scene_add(AscScene *scene, AscSceneNode *node);
+void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera);
 
 /**
  * Creates an empty node that may serve as a container for other nodes.
@@ -120,25 +90,68 @@
  */
 AscSceneNode *asc_scene_node_empty(void);
 
+/**
+ * Unlinks the node from its parent and frees the entire subtree.
+ *
+ * The free_func of this node and all child nodes is called, starting
+ * with the leaf nodes and terminating with \p node.
+ *
+ * @param node the node to unlink
+ */
 void asc_scene_node_free(AscSceneNode *node);
 
+/**
+ * Links a node to a (new) parent.
+ *
+ * @param parent the (new) parent
+ * @param node the node to link
+ */
 __attribute__((__nonnull__))
 void asc_scene_node_link(
         AscSceneNode *restrict parent,
         AscSceneNode *restrict node
 );
 
+/**
+ * Unlinks a node from its parent.
+ *
+ * This might be useful to temporarily remove a subtree from a scene.
+ * To permanently remove the node use asc_scene_node_free().
+ *
+ * @param node the node to unlink
+ */
 __attribute__((__nonnull__))
 void asc_scene_node_unlink(AscSceneNode *node);
 
+/**
+ * Adds a behavior function to the node.
+ *
+ * A behavior function MUST NOT be added more than once to the same node.
+ * This will not be checked.
+ *
+ * @param node the node
+ * @param behavior the behavior function
+ */
 __attribute__((__nonnull__))
-AscBehaviorNode *asc_scene_add_behavior(
+void asc_scene_add_behavior(
         AscSceneNode *node,
         asc_scene_update_func behavior
 );
 
+/**
+ * Removes a behavior function from the node.
+ *
+ * If the behavior function is not attached to this node, this function
+ * does nothing.
+ *
+ * @param node the node
+ * @param behavior the behavior function
+ */
 __attribute__((__nonnull__))
-void asc_scene_remove_behavior(AscBehaviorNode *node);
+void asc_scene_remove_behavior(
+        AscSceneNode *node,
+        asc_scene_update_func behavior
+);
 
 #define asc_node_update(node) \
     ((AscSceneNode*)node)->need_graphics_update = true
--- a/src/ascension/window.h	Fri Mar 29 00:03:25 2024 +0100
+++ b/src/ascension/window.h	Mon Apr 01 18:54:19 2024 +0200
@@ -52,7 +52,7 @@
     asc_vec2i dimensions;
     bool resized;
     AscGLContext glctx;
-    AscScene ui;
+    AscSceneNode *ui;
 } AscWindow;
 
 #define asc_window_active asc_context.active_window
--- a/src/camera.c	Fri Mar 29 00:03:25 2024 +0100
+++ b/src/camera.c	Mon Apr 01 18:54:19 2024 +0200
@@ -27,20 +27,11 @@
 
 #include "ascension/camera.h"
 
-static void asc_camera_update_ortho(AscCamera *camera) {
-    float left = (float) camera->rect.pos.x;
-    float right = left + (float) camera->rect.size.width;
-    float top = (float) camera->rect.pos.y;
-    float bottom = top + (float) camera->rect.size.height;
+void asc_camera_ortho(AscCamera *camera, asc_recti rect) {
+    asc_mat4f_unit(camera->view);
+    float left = (float) rect.pos.x;
+    float right = left + (float) rect.size.width;
+    float top = (float) rect.pos.y;
+    float bottom = top + (float) rect.size.height;
     asc_mat4f_ortho(camera->projection, left, right, bottom, top, -1, 1);
 }
-
-void asc_camera_ortho(AscCamera *camera, asc_recti rect) {
-    asc_mat4f_unit(camera->view);
-    camera->update = asc_camera_update_ortho;
-    camera->rect = rect;
-}
-
-void asc_camera_disable(AscCamera *camera) {
-    camera->update = NULL;
-}
--- a/src/context.c	Fri Mar 29 00:03:25 2024 +0100
+++ b/src/context.c	Mon Apr 01 18:54:19 2024 +0200
@@ -94,8 +94,6 @@
             asc_vec2i dimensions = (asc_vec2i) {width, height};
             asc_context.windows[i].resized = true;
             asc_context.windows[i].dimensions = dimensions;
-            asc_context.windows[i].ui.viewport.size = dimensions;
-            asc_context.windows[i].ui.cameras[0].rect.size = dimensions;
             return;
         }
     }
--- a/src/scene.c	Fri Mar 29 00:03:25 2024 +0100
+++ b/src/scene.c	Mon Apr 01 18:54:19 2024 +0200
@@ -30,6 +30,8 @@
 
 #include "ascension/context.h"
 
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
 #include <cx/tree.h>
 #include <cx/utils.h>
 
@@ -56,53 +58,26 @@
     );
 }
 
-void asc_scene_init(AscScene *scene) {
-    if (scene->root != NULL) {
-        asc_error("Scene is already initialized.");
-        return;
+struct asc_render_group_entry {
+    asc_scene_draw_func draw;
+    AscSceneNode const *node;
+};
+
+#define asc_draw_render_group(iter) \
+    cx_foreach(struct asc_render_group_entry*, entry, iter) { \
+        entry->draw(entry->node); \
     }
 
-    // zero everything, first
-    memset(scene, 0, sizeof(AscScene));
-
-    // default viewport is the entire viewport of the active window
-    scene->viewport.size = asc_context.active_window->dimensions;
-
-    // create the root node
-    scene->root = asc_scene_node_empty();
-
-    // initialize the render groups
-    cx_array_initialize(scene->rg_sprites_opaque, 8);
-    cx_array_initialize(scene->rg_sprites_blended, 8);
-}
-
-void asc_scene_destroy(AscScene *scene) {
-    asc_scene_node_free(scene->root);
-}
-
-void asc_scene_add(AscScene *scene, AscSceneNode *node) {
-    asc_scene_node_link(scene->root, node);
-    asc_node_update(node);
-}
-
-#define asc_scene_draw_render_group(rg) \
-    cx_for_n(i, rg##_size) { \
-        rg[i].draw(rg[i].node); \
-    } (void)0
-
-#define asc_scene_draw_render_group_reversed(rg) \
-    for(size_t i = rg##_size ; i > 0 ; i--) { \
-        rg[i-1].draw(rg[i-1].node); \
-    } (void)0
-
-void asc_scene_draw(AscScene *scene) {
-    // reset render groups
-    // TODO: avoid recalculating the groups, if possible
-    scene->rg_sprites_opaque_size = 0;
-    scene->rg_sprites_blended_size = 0;
+void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera) {
+    // create render groups
+    CxList *render_group[ASC_RENDER_GROUP_COUNT];
+    cx_for_n(i, ASC_RENDER_GROUP_COUNT) {
+        render_group[i] = cxArrayListCreateSimple(
+                sizeof(struct asc_render_group_entry), 32);
+    }
 
     // skip the root node deliberately, we know it's just the container
-    CxTreeVisitor iter = asc_scene_node_visitor(scene->root);
+    CxTreeVisitor iter = asc_scene_node_visitor(root);
     cxIteratorNext(iter);
 
     // update the children and add them to the render groups
@@ -110,12 +85,16 @@
         node->depth = iter.depth;
 
         // execute behaviors, first
-        AscBehaviorNode *behavior = node->behaviors;
-        while (behavior) {
-            behavior->func(node);
-            behavior = behavior->next;
+        if (node->behaviors != NULL) {
+            CxIterator behavior_iter = cxListIterator(node->behaviors);
+            cx_foreach(asc_scene_update_func, behavior, behavior_iter) {
+                behavior(node);
+            }
         }
 
+        // TODO: implement culling
+        // TODO: implement a hidden flag (requires UCX tree-continue function)
+
         // check if geometry needs update
         if (node->need_graphics_update) {
             assert(node->update_func != NULL);
@@ -142,56 +121,51 @@
             struct asc_render_group_entry entry = {
                     node->draw_func, node
             };
-            switch (node->render_group) {
-                case ASC_RENDER_GROUP_SPRITE_OPAQUE:
-                    cx_array_simple_add(scene->rg_sprites_opaque, entry);
-                    break;
-                case ASC_RENDER_GROUP_SPRITE_BLEND:
-                    cx_array_simple_add(scene->rg_sprites_blended, entry);
-                    break;
-            }
+            cxListAdd(render_group[node->render_group], &entry);
         }
     }
 
     // set the viewport (in OpenGL we need to invert the Y axis)
     glViewport(
-            scene->viewport.pos.x,
-            -scene->viewport.pos.y,
-            scene->viewport.size.width,
-            scene->viewport.size.height
+            viewport.pos.x,
+            -viewport.pos.y,
+            viewport.size.width,
+            viewport.size.height
     );
-    glClear(GL_COLOR_BUFFER_BIT);
 
-    // -----------------------------------------
-    // process the render groups for each camera
-    // -----------------------------------------
+    // -------------------------
+    // process the render groups
+    // -------------------------
     AscShaderProgram *shader;
-    cx_for_n(cam_id, ASC_SCENE_CAMERAS_MAX) {
-        // update camera parameters, first
-        AscCamera *camera = &scene->cameras[cam_id];
-        if (camera->update == NULL) continue;
-        camera->update(camera);
+    CxIterator render_iter;
+
+    // 2D Elements
+    // ===========
+    glEnable(GL_DEPTH_TEST);
+    glClear(GL_DEPTH_BUFFER_BIT);
 
-        // 2D Elements
-        // ===========
-        glEnable(GL_DEPTH_TEST);
-        glClear(GL_DEPTH_BUFFER_BIT);
+    // Sprites
+    // -------
+    // TODO: implement view matrix for 2D worlds
+    shader = &asc_context.active_window->glctx.shader.sprite.base;
+    glUseProgram(shader->id);
+    glUniformMatrix4fv(shader->projection, 1,
+                       GL_FALSE, camera->projection);
 
-        // Sprites
-        // -------
-        // TODO: see if we can really always ignore the view matrix
-        shader = &asc_context.active_window->glctx.shader.sprite.base;
-        glUseProgram(shader->id);
-        glUniformMatrix4fv(shader->projection, 1,
-                           GL_FALSE, camera->projection);
+    // render opaque sprites from front to back
+    glDisable(GL_BLEND);
+    render_iter = cxListBackwardsIterator(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE]);
+    asc_draw_render_group(render_iter);
 
-        // render opaque sprites from front to back
-        glDisable(GL_BLEND);
-        asc_scene_draw_render_group_reversed(scene->rg_sprites_opaque);
-        // render sprites with alpha value from back to front
-        glEnable(GL_BLEND);
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        asc_scene_draw_render_group(scene->rg_sprites_blended);
+    // render sprites with alpha value from back to front
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    render_iter = cxListIterator(render_group[ASC_RENDER_GROUP_SPRITE_BLEND]);
+    asc_draw_render_group(render_iter);
+
+    // destroy render groups
+    cx_for_n(i, ASC_RENDER_GROUP_COUNT) {
+        cxListDestroy(render_group[i]);
     }
 }
 
@@ -213,6 +187,9 @@
     CxTreeIterator iter = asc_scene_node_iterator(node, true);
     cx_foreach(AscSceneNode*, child, iter) {
         if (!iter.exiting) continue;
+        if (child->behaviors != NULL) {
+            cxListDestroy(child->behaviors);
+        }
         if (child->free_func != NULL) {
             child->free_func(child);
         } else {
@@ -241,28 +218,23 @@
     );
 }
 
-AscBehaviorNode *asc_scene_add_behavior(AscSceneNode *node, asc_scene_update_func behavior) {
-    AscBehaviorNode *behavior_node = calloc(1, sizeof(AscBehaviorNode));
-    behavior_node->func = behavior;
-    cx_tree_link(
-            node,
-            behavior_node,
-            offsetof(AscBehaviorNode, parent),
-            offsetof(AscSceneNode, behaviors),
-            offsetof(AscBehaviorNode, prev),
-            offsetof(AscBehaviorNode, next)
-    );
-    return behavior_node;
+void asc_scene_add_behavior(
+        AscSceneNode *node,
+        asc_scene_update_func behavior
+) {
+    if (node->behaviors == NULL) {
+        node->behaviors = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    }
+    cxListAdd(node->behaviors, behavior);
 }
 
-void asc_scene_remove_behavior(AscBehaviorNode *node) {
-    cx_tree_unlink(
-            node,
-            offsetof(AscBehaviorNode, parent),
-            offsetof(AscSceneNode, behaviors),
-            offsetof(AscBehaviorNode, prev),
-            offsetof(AscBehaviorNode, next)
-    );
+void asc_scene_remove_behavior(
+        AscSceneNode *node,
+        asc_scene_update_func behavior
+) {
+    if (node->behaviors != NULL) {
+        cxListFindRemove(node->behaviors, behavior);
+    }
 }
 
 void asc_update_transform(AscSceneNode *node) {
--- a/src/window.c	Fri Mar 29 00:03:25 2024 +0100
+++ b/src/window.c	Mon Apr 01 18:54:19 2024 +0200
@@ -43,13 +43,6 @@
     settings->title = "Ascended Window";
 }
 
-static void asc_window_init_scenes(AscWindow *window) {
-    asc_scene_init(&window->ui);
-    asc_camera_ortho(&window->ui.cameras[0], (asc_recti){
-        0, 0, window->dimensions
-    });
-}
-
 AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const *settings) {
     if (index >= ASC_MAX_WINDOWS) {
         asc_error("Maximum number of windows exceeded.");
@@ -58,9 +51,13 @@
     AscWindow *window = &asc_context.windows[index];
     if (window->id > 0) {
         asc_error("Cannot create window - slot already occupied.");
-        asc_dprintf("Tried to create window with index %u", index);
+        asc_dprintf("Tried to create window with index %u twice", index);
         return NULL;
     }
+    if (window->ui != NULL) {
+        asc_dprintf("Window with index %u has a dangling UI pointer", index);
+        asc_scene_node_free(window->ui);
+    }
 
     Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
     flags |= settings->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_RESIZABLE;
@@ -86,9 +83,9 @@
     window->resized = true; // count initial sizing as resize
 
     if (asc_gl_context_initialize(&window->glctx, window->window, &settings->glsettings)) {
+        window->ui = asc_scene_node_empty();
         asc_dprintf("Window %u initialized", window->id);
         asc_context.active_window = window;
-        asc_window_init_scenes(window);
         return window;
     } else {
         asc_dprintf("Creating GL context failed for window %u", window->id);
@@ -110,7 +107,8 @@
     }
 
     // destroy all scenes
-    asc_scene_destroy(&window->ui);
+    asc_scene_node_free(window->ui);
+    window->ui = NULL;
 
     // release context related data
     asc_gl_context_destroy(&window->glctx);
@@ -136,8 +134,17 @@
         asc_window_activate(window);
     }
 
+    // Clear the color buffer for the window frame
+    int window_width = window->dimensions.width;
+    int window_height = window->dimensions.height;
+    glViewport(0, 0, window_width, window_height);
+    glClear(GL_COLOR_BUFFER_BIT);
+    asc_recti viewport = {0, 0, window_width, window_height};
+
     // Draw the UI
-    asc_scene_draw(&window->ui);
+    AscCamera ui_camera;
+    asc_camera_ortho(&ui_camera, viewport);
+    asc_scene_draw(window->ui, viewport, &ui_camera);
 
     // Swap Buffers
     SDL_GL_SwapWindow(window->window);
--- a/test/snake.c	Fri Mar 29 00:03:25 2024 +0100
+++ b/test/snake.c	Mon Apr 01 18:54:19 2024 +0200
@@ -50,7 +50,7 @@
     asc_ink_rgb(255, 0, 0);
     AscSceneNode* node = asc_text(10, 10, "XXXXXXX FPS");
     asc_scene_add_behavior(node, update_fps_counter);
-    asc_scene_add(&asc_window_active->ui, node);
+    asc_scene_node_link(asc_window_active->ui, node);
 }
 
 static void update_score_counter(AscSceneNode *node) {
@@ -71,7 +71,7 @@
     asc_ink_rgb(0, 255, 0);
     AscSceneNode* node = asc_text(0, 0, "Score: 0");
     asc_scene_add_behavior(node, update_score_counter);
-    asc_scene_add(&asc_window_active->ui, node);
+    asc_scene_node_link(asc_window_active->ui, node);
 }
 
 int main(int argc, char** argv) {

mercurial