in preparation of more scenes, bring back AscScene struct default tip

Thu, 24 Apr 2025 19:53:40 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 24 Apr 2025 19:53:40 +0200
changeset 95
622887f7e954
parent 94
24bd333be668

in preparation of more scenes, bring back AscScene struct

src/Makefile file | annotate | diff | comparison | revisions
src/ascension/context.h file | annotate | diff | comparison | revisions
src/ascension/core.h file | annotate | diff | comparison | revisions
src/ascension/error.h file | annotate | diff | comparison | revisions
src/ascension/scene.h file | annotate | diff | comparison | revisions
src/ascension/scene_node.h file | annotate | diff | comparison | revisions
src/ascension/ui.h file | annotate | diff | comparison | revisions
src/ascension/ui/text.h file | annotate | diff | comparison | revisions
src/ascension/utils.h file | annotate | diff | comparison | revisions
src/ascension/window.h file | annotate | diff | comparison | revisions
src/context.c file | annotate | diff | comparison | revisions
src/error.c file | annotate | diff | comparison | revisions
src/font.c file | annotate | diff | comparison | revisions
src/glcontext.c file | annotate | diff | comparison | revisions
src/mesh.c file | annotate | diff | comparison | revisions
src/primitives.c file | annotate | diff | comparison | revisions
src/scene.c file | annotate | diff | comparison | revisions
src/scene_node.c file | annotate | diff | comparison | revisions
src/shader.c file | annotate | diff | comparison | revisions
src/text.c file | annotate | diff | comparison | revisions
src/texture.c file | annotate | diff | comparison | revisions
src/window.c file | annotate | diff | comparison | revisions
test/snake/Makefile file | annotate | diff | comparison | revisions
--- a/src/Makefile	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/Makefile	Thu Apr 24 19:53:40 2025 +0200
@@ -27,11 +27,11 @@
 
 BUILD_DIR=../build/lib
 
-SRC = context.c glcontext.c error.c filesystem.c window.c \
+SRC = context.c glcontext.c filesystem.c window.c \
 	  shader.c \
 	  mesh.c primitives.c texture.c \
 	  sprite.c \
-	  camera.c scene.c \
+	  camera.c scene.c scene_node.c \
 	  font.c text.c
 
 OBJ = $(SRC:%.c=$(BUILD_DIR)/%.o)
@@ -54,16 +54,7 @@
  ascension/primitives.h ascension/mesh.h ascension/shader.h \
  ascension/texture.h ascension/scene.h ascension/scene_node.h \
  ascension/transform.h ascension/camera.h ascension/input.h \
- ascension/ui/font.h ascension/error.h ascension/utils.h
-	@echo "Compiling $<"
-	$(CC) -o $@ $(CFLAGS) -c $<
-
-$(BUILD_DIR)/error.o: error.c ascension/context.h ascension/datatypes.h \
- ascension/window.h ascension/glcontext.h ascension/primitives.h \
- ascension/mesh.h ascension/shader.h ascension/texture.h \
- ascension/scene.h ascension/scene_node.h ascension/transform.h \
- ascension/camera.h ascension/input.h ascension/ui/font.h \
- ascension/error.h ascension/utils.h
+ ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -76,26 +67,34 @@
  ascension/mesh.h ascension/shader.h ascension/texture.h \
  ascension/scene.h ascension/scene_node.h ascension/transform.h \
  ascension/camera.h ascension/input.h ascension/ui/font.h \
- ascension/error.h ascension/filesystem.h ascension/ui/font.h
+ ascension/filesystem.h ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/glcontext.o: glcontext.c ascension/glcontext.h \
  ascension/primitives.h ascension/mesh.h ascension/shader.h \
- ascension/texture.h ascension/error.h
+ ascension/texture.h ascension/context.h ascension/datatypes.h \
+ ascension/window.h ascension/glcontext.h ascension/scene.h \
+ ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/input.h ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
-$(BUILD_DIR)/mesh.o: mesh.c ascension/mesh.h ascension/error.h
+$(BUILD_DIR)/mesh.o: mesh.c ascension/mesh.h ascension/context.h \
+ ascension/datatypes.h ascension/window.h ascension/glcontext.h \
+ ascension/primitives.h ascension/mesh.h ascension/shader.h \
+ ascension/texture.h ascension/scene.h ascension/scene_node.h \
+ ascension/transform.h ascension/camera.h ascension/input.h \
+ ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/primitives.o: primitives.c ascension/primitives.h \
- ascension/mesh.h ascension/error.h ascension/context.h \
- ascension/datatypes.h ascension/window.h ascension/glcontext.h \
- ascension/primitives.h ascension/shader.h ascension/texture.h \
- ascension/scene.h ascension/scene_node.h ascension/transform.h \
- ascension/camera.h ascension/input.h ascension/ui/font.h
+ ascension/mesh.h ascension/context.h ascension/datatypes.h \
+ ascension/window.h ascension/glcontext.h ascension/primitives.h \
+ ascension/shader.h ascension/texture.h ascension/scene.h \
+ ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/input.h ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -104,9 +103,17 @@
  ascension/context.h ascension/window.h ascension/glcontext.h \
  ascension/primitives.h ascension/mesh.h ascension/shader.h \
  ascension/texture.h ascension/scene.h ascension/input.h \
- ascension/ui/font.h ascension/utils.h ascension/2d.h \
- ascension/2d/sprite.h ascension/2d/../scene_node.h \
- ascension/2d/../texture.h
+ ascension/ui/font.h ascension/2d.h ascension/2d/sprite.h \
+ ascension/2d/../scene_node.h ascension/2d/../texture.h
+	@echo "Compiling $<"
+	$(CC) -o $@ $(CFLAGS) -c $<
+
+$(BUILD_DIR)/scene_node.o: scene_node.c ascension/scene_node.h \
+ ascension/datatypes.h ascension/transform.h ascension/context.h \
+ ascension/window.h ascension/glcontext.h ascension/primitives.h \
+ ascension/mesh.h ascension/shader.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
+ ascension/input.h ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -115,7 +122,7 @@
  ascension/mesh.h ascension/shader.h ascension/texture.h \
  ascension/scene.h ascension/scene_node.h ascension/transform.h \
  ascension/camera.h ascension/input.h ascension/ui/font.h \
- ascension/shader.h ascension/error.h ascension/filesystem.h
+ ascension/shader.h ascension/filesystem.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -135,9 +142,8 @@
  ascension/mesh.h ascension/shader.h ascension/texture.h \
  ascension/scene.h ascension/scene_node.h ascension/transform.h \
  ascension/camera.h ascension/input.h ascension/ui/font.h \
- ascension/error.h ascension/ui/text.h ascension/ui/font.h \
- ascension/ui/../2d/sprite.h ascension/ui/../2d/../scene_node.h \
- ascension/ui/../2d/../texture.h ascension/ui/../utils.h
+ ascension/ui/text.h ascension/ui/font.h ascension/ui/../2d/sprite.h \
+ ascension/ui/../2d/../scene_node.h ascension/ui/../2d/../texture.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -146,8 +152,7 @@
  ascension/primitives.h ascension/mesh.h ascension/shader.h \
  ascension/texture.h ascension/scene.h ascension/scene_node.h \
  ascension/transform.h ascension/camera.h ascension/input.h \
- ascension/ui/font.h ascension/texture.h ascension/error.h \
- ascension/filesystem.h
+ ascension/ui/font.h ascension/texture.h ascension/filesystem.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -156,7 +161,7 @@
  ascension/shader.h ascension/texture.h ascension/scene.h \
  ascension/scene_node.h ascension/transform.h ascension/camera.h \
  ascension/context.h ascension/window.h ascension/input.h \
- ascension/ui/font.h ascension/error.h ascension/utils.h
+ ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
--- a/src/ascension/context.h	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/ascension/context.h	Thu Apr 24 19:53:40 2025 +0200
@@ -36,6 +36,8 @@
 #include <cx/buffer.h>
 #include <cx/string.h>
 
+#include <stdio.h>
+
 /** The flag for the overall initialized state. */
 #define ASC_FLAG_INITILIZED  0x01u
 
@@ -45,6 +47,34 @@
 /** Flag is set when SDL wants to quit the application. */
 #define ASC_FLAG_QUIT 0x80000000u
 
+
+#define asc_test_flag(reg, flag) ((reg & flag) == flag)
+#define asc_test_flag_masked(reg, mask, flag) ((reg & mask) == flag)
+#define asc_clear_flag(reg, flag) (reg &= ~(flag))
+#define asc_set_flag(reg, flag) (reg |= flag)
+#define asc_set_flag_masked(reg, mask, flag) (reg = (reg & ~(mask)) | flag)
+
+
+
+void asc_error_impl(const char* file, unsigned line, char const* fmt, ...);
+#define asc_error(...) asc_error_impl(__FILE__, __LINE__, __VA_ARGS__)
+
+void asc_error_gl(unsigned code, const char *message);
+
+int asc_error_catch_all_gl(void);
+
+bool asc_has_error(void);
+char const* asc_get_error(void);
+void asc_clear_error(void);
+
+#define asc_wprintf(...) printf("[WARNING %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n')
+#ifdef NDEBUG
+#define asc_dprintf(...)
+#else
+#define asc_dprintf(...) printf("[DEBUG %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n')
+#endif
+
+
 /**
  * The global ascension context.
  */
--- a/src/ascension/core.h	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/ascension/core.h	Thu Apr 24 19:53:40 2025 +0200
@@ -28,7 +28,6 @@
 #ifndef ASCENSION_CORE_H
 #define ASCENSION_CORE_H
 
-#include "error.h"
 #include "context.h"
 #include "shader.h"
 #include "scene.h"
--- a/src/ascension/error.h	Thu Apr 24 18:41:42 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- * Copyright 2023 Mike Becker. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef ASCENSION_ERROR_H
-#define ASCENSION_ERROR_H
-
-#include <cx/string.h>
-#include <stdio.h>
-
-void asc_error_impl(const char* file, unsigned line, char const* fmt, ...);
-#define asc_error(...) asc_error_impl(__FILE__, __LINE__, __VA_ARGS__)
-
-void asc_error_gl(unsigned code, cxstring message);
-
-int asc_error_catch_all_gl(void);
-
-bool asc_has_error(void);
-char const* asc_get_error(void);
-void asc_clear_error(void);
-
-#define asc_wprintf(...) printf("[WARNING %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n')
-#ifdef NDEBUG
-#define asc_dprintf(...)
-#else
-#define asc_dprintf(...) printf("[DEBUG %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n')
-#endif
-
-#endif /* ASCENSION_ERROR_H */
-
--- a/src/ascension/scene.h	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/ascension/scene.h	Thu Apr 24 19:53:40 2025 +0200
@@ -32,136 +32,41 @@
 #include "scene_node.h"
 #include "camera.h"
 
+typedef struct {
+    // TODO: should a scene always have an own camera?
+    // yes: that would enable efficient frustum culling that does not need to be recalculated
+    // no: if the same scene needs to be drawn from different cameras, we must duplicate the scene graph
+    AscCamera camera;
+    AscSceneNode *root;
+} AscScene;
+
+/**
+ * Initializes a scene graph.
+ *
+ * @param scene the scene graph
+ */
+__attribute__((__nonnull__))
+void asc_scene_init(AscScene *scene);
+
+/**
+ * Destroys a scene graph.
+ *
+ * @param scene the scene graph
+ */
+__attribute__((__nonnull__))
+void asc_scene_destroy(AscScene *scene);
+
 /**
  * Draws the scene with the specified root node.
  *
- * @param root the root node of the scene graph
+ * @param scene 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_draw(AscSceneNode *root, asc_recti viewport, const AscCamera *camera);
-
-/**
- * Creates an empty node that may serve as a container for other nodes.
- *
- * The free_func of this node will be a simple free().
- *
- * @return the new node
- */
-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__))
-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(
-        AscSceneNode *node,
-        asc_scene_update_func behavior
-);
-
-__attribute__((__nonnull__))
-void asc_node_update(AscSceneNode *node);
+void asc_scene_draw(AscScene *scene, asc_recti viewport);
 
 __attribute__((__nonnull__))
-void asc_node_update_transform(AscSceneNode *node);
-
-
-__attribute__((__nonnull__)) static inline
-void asc_set_position(AscSceneNode *node, float x, float y, float z) {
-    node->position.x = x;
-    node->position.y = y;
-    node->position.z = z;
-    asc_node_update_transform(node);
-}
-
-__attribute__((__nonnull__)) static inline
-void asc_set_position2d(AscSceneNode *node, int x, int y) {
-    node->position.x = (float)x;
-    node->position.y = (float)y;
-    node->position.z = 0.f;
-    asc_node_update_transform(node);
-}
-
-__attribute__((__nonnull__)) static inline
-asc_vec2i asc_get_position2d(AscSceneNode *node) {
-    return (asc_vec2i) {(int) node->position.x, (int) node->position.y};
-}
-
-__attribute__((__nonnull__)) static inline
-void asc_set_scale(AscSceneNode *node, float width, float height, float depth) {
-    node->scale.width = width;
-    node->scale.height = height;
-    node->scale.depth = depth;
-    asc_node_update_transform(node);
-}
-
-__attribute__((__nonnull__)) static inline
-void asc_set_scale2d(AscSceneNode *node, int width, int height) {
-    node->scale.width = (float)width;
-    node->scale.height = (float)height;
-    node->scale.depth = 1.f;
-    asc_node_update_transform(node);
-}
-
-__attribute__((__nonnull__)) static inline
-asc_vec2i asc_get_scale2d(AscSceneNode *node) {
-    return (asc_vec2i) {(int) node->scale.width, (int) node->scale.height};
-}
+void asc_scene_add_node(AscScene *scene, AscSceneNode *node);
 
 #endif // ASCENSION_SCENE_H
 
--- a/src/ascension/scene_node.h	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/ascension/scene_node.h	Thu Apr 24 19:53:40 2025 +0200
@@ -39,6 +39,7 @@
 typedef void(*asc_scene_update_func)(AscSceneNode*);
 
 enum AscRenderGroup {
+    ASC_RENDER_GROUP_NONE = -1,
     ASC_RENDER_GROUP_SPRITE_OPAQUE,
     ASC_RENDER_GROUP_SPRITE_BLEND,
     ASC_RENDER_GROUP_COUNT
@@ -94,4 +95,126 @@
 #define ASC_SCENE_NODE_HIDDEN                 0x80000000
 
 
+
+/**
+ * Creates an empty node that may serve as a container for other nodes.
+ *
+ * The free_func of this node will be a simple free().
+ *
+ * @return the new node
+ */
+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__))
+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(
+        AscSceneNode *node,
+        asc_scene_update_func behavior
+);
+
+__attribute__((__nonnull__))
+void asc_node_update(AscSceneNode *node);
+
+__attribute__((__nonnull__))
+void asc_node_update_transform(AscSceneNode *node);
+
+
+__attribute__((__nonnull__)) static inline
+void asc_set_position(AscSceneNode *node, float x, float y, float z) {
+    node->position.x = x;
+    node->position.y = y;
+    node->position.z = z;
+    asc_node_update_transform(node);
+}
+
+__attribute__((__nonnull__)) static inline
+void asc_set_position2d(AscSceneNode *node, int x, int y) {
+    node->position.x = (float)x;
+    node->position.y = (float)y;
+    node->position.z = 0.f;
+    asc_node_update_transform(node);
+}
+
+__attribute__((__nonnull__)) static inline
+asc_vec2i asc_get_position2d(AscSceneNode *node) {
+    return (asc_vec2i) {(int) node->position.x, (int) node->position.y};
+}
+
+__attribute__((__nonnull__)) static inline
+void asc_set_scale(AscSceneNode *node, float width, float height, float depth) {
+    node->scale.width = width;
+    node->scale.height = height;
+    node->scale.depth = depth;
+    asc_node_update_transform(node);
+}
+
+__attribute__((__nonnull__)) static inline
+void asc_set_scale2d(AscSceneNode *node, int width, int height) {
+    node->scale.width = (float)width;
+    node->scale.height = (float)height;
+    node->scale.depth = 1.f;
+    asc_node_update_transform(node);
+}
+
+__attribute__((__nonnull__)) static inline
+asc_vec2i asc_get_scale2d(AscSceneNode *node) {
+    return (asc_vec2i) {(int) node->scale.width, (int) node->scale.height};
+}
+
 #endif
--- a/src/ascension/ui.h	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/ascension/ui.h	Thu Apr 24 19:53:40 2025 +0200
@@ -30,8 +30,5 @@
 
 #include "ui/text.h"
 
-#define asc_add_ui_node(node) \
-    asc_scene_node_link(asc_active_window->ui, node)
-
 #endif /* ASCENSION_UI_H */
 
--- a/src/ascension/ui/text.h	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/ascension/ui/text.h	Thu Apr 24 19:53:40 2025 +0200
@@ -30,7 +30,6 @@
 
 #include "font.h"
 #include "../2d/sprite.h"
-#include "../utils.h"
 
 #include <cx/string.h>
 
--- a/src/ascension/utils.h	Thu Apr 24 18:41:42 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- * Copyright 2023 Mike Becker. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef ASCENSION_UTILS_H
-#define ASCENSION_UTILS_H
-
-#include <stdbool.h>
-
-#define asc_test_flag(reg, flag) ((reg & flag) == flag)
-#define asc_test_flag_masked(reg, mask, flag) ((reg & mask) == flag)
-#define asc_clear_flag(reg, flag) (reg &= ~(flag))
-#define asc_set_flag(reg, flag) (reg |= flag)
-#define asc_set_flag_masked(reg, mask, flag) (reg = (reg & ~(mask)) | flag)
-
-#endif /* ASCENSION_UTILS_H */
-
--- a/src/ascension/window.h	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/ascension/window.h	Thu Apr 24 19:53:40 2025 +0200
@@ -53,7 +53,7 @@
     SDL_Window* window;
     asc_vec2i dimensions;
     AscGLContext glctx;
-    AscSceneNode *ui;
+    AscScene ui;
 } AscWindow;
 
 /**
@@ -122,5 +122,9 @@
  */
 unsigned int asc_window_index(Uint32 id);
 
+
+__attribute__((__nonnull__))
+void asc_add_ui_node(AscSceneNode *node);
+
 #endif /* ASCENSION_WINDOW_H */
 
--- a/src/context.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/context.c	Thu Apr 24 19:53:40 2025 +0200
@@ -26,17 +26,96 @@
  */
 
 #include "ascension/context.h"
-#include "ascension/error.h"
-#include "ascension/utils.h"
 
 #include <SDL2/SDL.h>
 #include <SDL2/SDL_ttf.h>
 #include <SDL2/SDL_image.h>
 
+#include <GL/gl.h>
+
+#include <cx/printf.h>
+
 #include <time.h>
 
 AscContext asc_context;
 
+void asc_error_impl(const char* file, unsigned line, char const* fmt, ...) {
+    asc_set_flag(asc_context.flags, ASC_FLAG_HAS_ERROR);
+
+    // write to error buffer
+    va_list args;
+    va_start(args, fmt);
+    CxBuffer* buf = &asc_context.error_buffer;
+    size_t bufpos = buf->pos;
+    int written = cx_vfprintf(buf, cxBufferWriteFunc, fmt, args);
+    cxBufferPut(buf, '\n');
+    va_end(args);
+
+    // also print to stderr
+    // avoid double-formatting, get it directly from the buffer
+    fprintf(stderr, "[ERROR %s %u] %.*s\n",
+        file, line, written, buf->space+bufpos);
+}
+
+bool asc_has_error(void) {
+    return asc_test_flag(asc_context.flags, ASC_FLAG_HAS_ERROR);
+}
+
+char const* asc_get_error(void) {
+    // we zero-terminate the current buffer contents before providing them
+    cxBufferPut(&asc_context.error_buffer, 0);
+    --asc_context.error_buffer.pos;
+    --asc_context.error_buffer.size;
+    return asc_context.error_buffer.space;
+}
+
+void asc_clear_error(void) {
+    cxBufferClear(&asc_context.error_buffer);
+    asc_clear_flag(asc_context.flags, ASC_FLAG_HAS_ERROR);
+}
+
+void asc_error_gl(unsigned code, const char *message) {
+    const char *glerr;
+    switch(code) {
+        case GL_NO_ERROR:
+            return;
+        case GL_INVALID_ENUM:
+            glerr = "invalid enum";
+            break;
+        case GL_INVALID_VALUE:
+            glerr = "invalid value";
+            break;
+        case GL_INVALID_OPERATION:
+            glerr = "invalid operation";
+            break;
+        case GL_INVALID_FRAMEBUFFER_OPERATION:
+            glerr = "invalid framebuffer operation";
+            break;
+        case GL_OUT_OF_MEMORY:
+            glerr = "out of memory";
+            break;
+        case GL_STACK_UNDERFLOW:
+            glerr = "stack underflow";
+            break;
+        case GL_STACK_OVERFLOW:
+            glerr = "stack overflow";
+            break;
+        default:
+            glerr = "unknown GL error";
+    }
+    asc_error("%s\nGL Error: %s", message, glerr);
+}
+
+int asc_error_catch_all_gl(void) {
+    GLenum error;
+    int ret = 0;
+    while ((error = glGetError()) != GL_NO_ERROR) {
+        asc_error_gl(error, "Uncaught");
+        ret = 1;
+    }
+    return ret;
+}
+
 static uint64_t asc_nanos(void) {
     struct timespec ts;
     clock_gettime(CLOCK_MONOTONIC, &ts);
--- a/src/error.c	Thu Apr 24 18:41:42 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- * Copyright 2023 Mike Becker. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "ascension/context.h"
-#include "ascension/error.h"
-#include "ascension/utils.h"
-
-#include <cx/buffer.h>
-#include <cx/printf.h>
-#include <GL/gl.h>
-
-void asc_error_impl(const char* file, unsigned line, char const* fmt, ...) {
-    asc_set_flag(asc_context.flags, ASC_FLAG_HAS_ERROR);
-
-    // write to error buffer
-    va_list args;
-    va_start(args, fmt);
-    CxBuffer* buf = &asc_context.error_buffer;
-    size_t bufpos = buf->pos;
-    int written = cx_vfprintf(buf, cxBufferWriteFunc, fmt, args);
-    cxBufferPut(buf, '\n');
-    va_end(args);
-
-    // also print to stderr
-    // avoid double-formatting, get it directly from the buffer
-    fprintf(stderr, "[ERROR %s %u] %.*s\n",
-        file, line, written, buf->space+bufpos);
-}
-
-bool asc_has_error(void) {
-    return asc_test_flag(asc_context.flags, ASC_FLAG_HAS_ERROR);
-}
-
-char const* asc_get_error(void) {
-    // we zero-terminate the current buffer contents before providing them
-    cxBufferPut(&asc_context.error_buffer, 0);
-    --asc_context.error_buffer.pos;
-    --asc_context.error_buffer.size;
-    return asc_context.error_buffer.space;
-}
-
-void asc_clear_error(void) {
-    cxBufferClear(&asc_context.error_buffer);
-    asc_clear_flag(asc_context.flags, ASC_FLAG_HAS_ERROR);
-}
-
-void asc_error_gl(unsigned code, cxstring message) {
-    const char *glerr;
-    switch(code) {
-        case GL_NO_ERROR:
-            return;
-        case GL_INVALID_ENUM:
-            glerr = "invalid enum";
-            break;
-        case GL_INVALID_VALUE:
-            glerr = "invalid value";
-            break;
-        case GL_INVALID_OPERATION:
-            glerr = "invalid operation";
-            break;
-        case GL_INVALID_FRAMEBUFFER_OPERATION:
-            glerr = "invalid framebuffer operation";
-            break;
-        case GL_OUT_OF_MEMORY:
-            glerr = "out of memory";
-            break;
-        case GL_STACK_UNDERFLOW:
-            glerr = "stack underflow";
-            break;
-        case GL_STACK_OVERFLOW:
-            glerr = "stack overflow";
-            break;
-        default:
-            glerr = "unknown GL error";
-    }
-    asc_error("%s\nGL Error: %s", message, glerr);
-}
-
-int asc_error_catch_all_gl(void) {
-    GLenum error;
-    int ret = 0;
-    while ((error = glGetError()) != GL_NO_ERROR) {
-        asc_error_gl(error, CX_STR("Uncaught"));
-        ret = 1;
-    }
-    return ret;
-}
--- a/src/font.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/font.c	Thu Apr 24 19:53:40 2025 +0200
@@ -26,7 +26,6 @@
  */
 
 #include "ascension/context.h"
-#include "ascension/error.h"
 #include "ascension/filesystem.h"
 #include "ascension/ui/font.h"
 
--- a/src/glcontext.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/glcontext.c	Thu Apr 24 19:53:40 2025 +0200
@@ -26,7 +26,7 @@
  */
 
 #include "ascension/glcontext.h"
-#include "ascension/error.h"
+#include "ascension/context.h"
 
 #include <cx/printf.h>
 
--- a/src/mesh.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/mesh.c	Thu Apr 24 19:53:40 2025 +0200
@@ -26,7 +26,7 @@
  */
 
 #include "ascension/mesh.h"
-#include "ascension/error.h"
+#include "ascension/context.h"
 
 #include <GL/glew.h>
 #include <assert.h>
--- a/src/primitives.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/primitives.c	Thu Apr 24 19:53:40 2025 +0200
@@ -26,7 +26,6 @@
  */
 
 #include "ascension/primitives.h"
-#include "ascension/error.h"
 #include "ascension/context.h"
 
 #include <GL/glew.h>
--- a/src/scene.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/scene.c	Thu Apr 24 19:53:40 2025 +0200
@@ -28,46 +28,41 @@
 #include "ascension/scene.h"
 
 #include "ascension/context.h"
-#include "ascension/utils.h"
 
 #include "ascension/2d.h"
 
+#include <cx/tree.h>
 #include <cx/linked_list.h>
 #include <cx/array_list.h>
-#include <cx/tree.h>
 #include <cx/utils.h>
 
 #include <GL/glew.h>
 
 #include <assert.h>
 
-static CxTreeIterator asc_scene_node_iterator(
-        AscSceneNode *node,
-        bool visit_on_exit
-) {
-    return cx_tree_iterator(
-            node, visit_on_exit,
-            offsetof(AscSceneNode, children),
-            offsetof(AscSceneNode, next)
-    );
+void asc_scene_init(AscScene *scene) {
+    asc_dprintf("Initialized scene %"PRIxPTR, (uintptr_t) scene);
+    // TODO: how should we initialize the camera?
+    scene->root = asc_scene_node_empty();
 }
 
-static CxTreeVisitor asc_scene_node_visitor(AscSceneNode *node) {
-    return cx_tree_visitor(node,
-            offsetof(AscSceneNode, children),
-            offsetof(AscSceneNode, next)
-    );
+void asc_scene_destroy(AscScene *scene) {
+    asc_dprintf("Destroyed scene %"PRIxPTR, (uintptr_t) scene);
+    asc_scene_node_free(scene->root);
 }
 
-void asc_scene_draw(AscSceneNode *root, asc_recti viewport, const AscCamera *camera) {
+void asc_scene_draw(AscScene *scene, asc_recti viewport) {
     // create render groups
     CxList *render_group[ASC_RENDER_GROUP_COUNT];
     cx_for_n(i, ASC_RENDER_GROUP_COUNT) {
         render_group[i] = cxArrayListCreateSimple(CX_STORE_POINTERS, 32);
     }
 
-    // skip the root node deliberately, we know it's just the container
-    CxTreeVisitor iter = asc_scene_node_visitor(root);
+    // skip the root node deliberately; we know it's just the container
+    CxTreeVisitor iter = cx_tree_visitor(scene->root,
+            offsetof(AscSceneNode, children),
+            offsetof(AscSceneNode, next)
+    );
     cxIteratorNext(iter);
 
     // update the children and add them to the render groups
@@ -116,7 +111,9 @@
         }
 
         // add to render group
-        cxListAdd(render_group[node->render_group], node);
+        if (node->render_group >= 0) {
+            cxListAdd(render_group[node->render_group], node);
+        }
     }
 
     // set the viewport (in OpenGL we need to invert the Y axis)
@@ -145,9 +142,9 @@
         AscShaderProgram *shader = &ASC_SHADER_SPRITE->program;
         glUseProgram(shader->id);
         glUniformMatrix4fv(shader->projection, 1,
-                           GL_FALSE, camera->projection);
+                           GL_FALSE, scene->camera.projection);
         glUniformMatrix4fv(shader->view, 1,
-                               GL_FALSE, camera->view);
+                               GL_FALSE, scene->camera.view);
 
         // render opaque sprites from front to back
         glDisable(GL_BLEND);
@@ -171,92 +168,10 @@
     }
 }
 
-AscSceneNode *asc_scene_node_empty(void) {
-    AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
-    node->free_func = (asc_scene_free_func) free;
-    node->scale.x = node->scale.y = node->scale.z = 1;
-    asc_transform_identity(node->transform);
-    asc_transform_identity(node->world_transform);
-    return node;
-}
-
-void asc_scene_node_free(AscSceneNode *node) {
-    if (node == NULL) return;
-
-    // remove this node from its parent
-    asc_scene_node_unlink(node);
-
-    // free the entire subtree
-    CxTreeIterator iter = asc_scene_node_iterator(node, true);
-    cx_foreach(AscSceneNode*, child, iter) {
-        if (!iter.exiting) continue;
-        if (child->behaviors != NULL) {
-            cxListFree(child->behaviors);
-        }
-        if (child->free_func != NULL) {
-            child->free_func(child);
-        } else {
-            free(child);
-        }
-    }
-}
-
-void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) {
-    cx_tree_link(
-            parent, node,
-            offsetof(AscSceneNode, parent),
-            offsetof(AscSceneNode, children),
-            offsetof(AscSceneNode, last_child),
-            offsetof(AscSceneNode, prev),
-            offsetof(AscSceneNode, next)
-    );
+void asc_scene_add_node(AscScene *scene, AscSceneNode *node) {
+    asc_scene_node_link(scene->root, node);
 }
 
-void asc_scene_node_unlink(AscSceneNode *node) {
-    cx_tree_unlink(
-            node,
-            offsetof(AscSceneNode, parent),
-            offsetof(AscSceneNode, children),
-            offsetof(AscSceneNode, last_child),
-            offsetof(AscSceneNode, prev),
-            offsetof(AscSceneNode, next)
-    );
-}
-
-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_node(AscSceneNode *node) {
+    asc_scene_node_unlink(node);
 }
-
-void asc_scene_remove_behavior(
-        AscSceneNode *node,
-        asc_scene_update_func behavior
-) {
-    if (node->behaviors != NULL) {
-        cxListFindRemove(node->behaviors, behavior);
-    }
-}
-
-void asc_node_update(AscSceneNode *node) {
-    asc_set_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS);
-}
-
-void asc_node_update_transform(AscSceneNode *node) {
-    // fast skip if node is already marked
-    if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
-        return;
-    }
-
-    CxTreeIterator iter = asc_scene_node_iterator(node, false);
-    cx_foreach(AscSceneNode*, n, iter) {
-        if (asc_test_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
-            cxTreeIteratorContinue(iter);
-        }
-        asc_set_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/scene_node.c	Thu Apr 24 19:53:40 2025 +0200
@@ -0,0 +1,134 @@
+/*
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * Copyright 2023 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ascension/scene_node.h"
+#include "ascension/context.h"
+
+#include <cx/tree.h>
+#include <cx/linked_list.h>
+
+static CxTreeIterator asc_scene_node_iterator(
+        AscSceneNode *node,
+        bool visit_on_exit
+) {
+    return cx_tree_iterator(
+            node, visit_on_exit,
+            offsetof(AscSceneNode, children),
+            offsetof(AscSceneNode, next)
+    );
+}
+
+AscSceneNode *asc_scene_node_empty(void) {
+    AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
+    node->render_group = ASC_RENDER_GROUP_NONE;
+    node->free_func = (asc_scene_free_func) free;
+    node->scale.x = node->scale.y = node->scale.z = 1;
+    asc_transform_identity(node->transform);
+    asc_transform_identity(node->world_transform);
+    return node;
+}
+
+void asc_scene_node_free(AscSceneNode *node) {
+    if (node == NULL) return;
+
+    // remove this node from its parent
+    asc_scene_node_unlink(node);
+
+    // free the entire subtree
+    CxTreeIterator iter = asc_scene_node_iterator(node, true);
+    cx_foreach(AscSceneNode*, child, iter) {
+        if (!iter.exiting) continue;
+        if (child->behaviors != NULL) {
+            cxListFree(child->behaviors);
+        }
+        if (child->free_func != NULL) {
+            child->free_func(child);
+        } else {
+            free(child);
+        }
+    }
+}
+
+void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) {
+    cx_tree_link(
+            parent, node,
+            offsetof(AscSceneNode, parent),
+            offsetof(AscSceneNode, children),
+            offsetof(AscSceneNode, last_child),
+            offsetof(AscSceneNode, prev),
+            offsetof(AscSceneNode, next)
+    );
+}
+
+void asc_scene_node_unlink(AscSceneNode *node) {
+    cx_tree_unlink(
+            node,
+            offsetof(AscSceneNode, parent),
+            offsetof(AscSceneNode, children),
+            offsetof(AscSceneNode, last_child),
+            offsetof(AscSceneNode, prev),
+            offsetof(AscSceneNode, next)
+    );
+}
+
+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(
+        AscSceneNode *node,
+        asc_scene_update_func behavior
+) {
+    if (node->behaviors != NULL) {
+        cxListFindRemove(node->behaviors, behavior);
+    }
+}
+
+void asc_node_update(AscSceneNode *node) {
+    asc_set_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS);
+}
+
+void asc_node_update_transform(AscSceneNode *node) {
+    // fast skip if node is already marked
+    if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
+        return;
+    }
+
+    CxTreeIterator iter = asc_scene_node_iterator(node, false);
+    cx_foreach(AscSceneNode*, n, iter) {
+        if (asc_test_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
+            cxTreeIteratorContinue(iter);
+        }
+        asc_set_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM);
+    }
+}
--- a/src/shader.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/shader.c	Thu Apr 24 19:53:40 2025 +0200
@@ -27,7 +27,6 @@
 
 #include "ascension/context.h"
 #include "ascension/shader.h"
-#include "ascension/error.h"
 #include "ascension/filesystem.h"
 
 #include <GL/glew.h>
--- a/src/text.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/text.c	Thu Apr 24 19:53:40 2025 +0200
@@ -26,7 +26,6 @@
  */
 
 #include "ascension/context.h"
-#include "ascension/error.h"
 #include "ascension/ui/text.h"
 
 #include <assert.h>
--- a/src/texture.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/texture.c	Thu Apr 24 19:53:40 2025 +0200
@@ -27,7 +27,6 @@
 
 #include "ascension/context.h"
 #include "ascension/texture.h"
-#include "ascension/error.h"
 #include "ascension/filesystem.h"
 
 #include <assert.h>
--- a/src/window.c	Thu Apr 24 18:41:42 2025 +0200
+++ b/src/window.c	Thu Apr 24 19:53:40 2025 +0200
@@ -27,8 +27,6 @@
 
 #include "ascension/window.h"
 #include "ascension/context.h"
-#include "ascension/error.h"
-#include "ascension/utils.h"
 
 #include <GL/glew.h>
 
@@ -53,10 +51,6 @@
         asc_error("Cannot create window - slot %u already occupied.", index);
         return;
     }
-    if (window->ui != NULL) {
-        asc_wprintf("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;
@@ -82,7 +76,7 @@
     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_scene_init(&window->ui);
         asc_dprintf("Window %u initialized", window->id);
         asc_context.active_window = index;
     } else {
@@ -110,8 +104,7 @@
     asc_gl_context_activate(&window->glctx);
 
     // destroy all scenes
-    asc_scene_node_free(window->ui);
-    window->ui = NULL;
+    asc_scene_destroy(&window->ui);
 
     // release context related data
     asc_gl_context_destroy(&window->glctx);
@@ -146,12 +139,12 @@
     int window_height = asc_active_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
-    AscCamera ui_camera;
-    asc_camera_ortho(&ui_camera, viewport);
-    asc_scene_draw(asc_active_window->ui, viewport, &ui_camera);
+    AscScene *ui = &asc_active_window->ui;
+    asc_recti viewport = {0, 0, window_width, window_height};
+    asc_camera_ortho(&ui->camera, viewport);
+    asc_scene_draw(ui, viewport);
 
     // Swap Buffers
     SDL_GL_SwapWindow(asc_active_window->window);
@@ -176,3 +169,7 @@
     }
     return i;
 }
+
+void asc_add_ui_node(AscSceneNode *node) {
+    asc_scene_add_node(&asc_active_window->ui, node);
+}
--- a/test/snake/Makefile	Thu Apr 24 18:41:42 2025 +0200
+++ b/test/snake/Makefile	Thu Apr 24 19:53:40 2025 +0200
@@ -45,19 +45,17 @@
 FORCE:
 
 $(BUILD_DIR)/snake.o: snake.c ../../src/ascension/core.h \
- ../../src/ascension/error.h ../../src/ascension/context.h \
- ../../src/ascension/datatypes.h ../../src/ascension/window.h \
- ../../src/ascension/glcontext.h ../../src/ascension/primitives.h \
- ../../src/ascension/mesh.h ../../src/ascension/shader.h \
- ../../src/ascension/texture.h ../../src/ascension/scene.h \
- ../../src/ascension/scene_node.h ../../src/ascension/transform.h \
- ../../src/ascension/camera.h ../../src/ascension/input.h \
- ../../src/ascension/ui/font.h ../../src/ascension/ui.h \
- ../../src/ascension/ui/text.h ../../src/ascension/ui/font.h \
- ../../src/ascension/ui/../2d/sprite.h \
+ ../../src/ascension/context.h ../../src/ascension/datatypes.h \
+ ../../src/ascension/window.h ../../src/ascension/glcontext.h \
+ ../../src/ascension/primitives.h ../../src/ascension/mesh.h \
+ ../../src/ascension/shader.h ../../src/ascension/texture.h \
+ ../../src/ascension/scene.h ../../src/ascension/scene_node.h \
+ ../../src/ascension/transform.h ../../src/ascension/camera.h \
+ ../../src/ascension/input.h ../../src/ascension/ui/font.h \
+ ../../src/ascension/ui.h ../../src/ascension/ui/text.h \
+ ../../src/ascension/ui/font.h ../../src/ascension/ui/../2d/sprite.h \
  ../../src/ascension/ui/../2d/../scene_node.h \
- ../../src/ascension/ui/../2d/../texture.h \
- ../../src/ascension/ui/../utils.h
+ ../../src/ascension/ui/../2d/../texture.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 

mercurial