add camera and render groups

Fri, 15 Mar 2024 00:06:59 +0100

author
Mike Becker <universe@uap-core.de>
date
Fri, 15 Mar 2024 00:06:59 +0100
changeset 37
8a8cc6725b48
parent 36
e26b4ac1661c
child 38
6e5629ea4c5c

add camera and render groups

src/Makefile file | annotate | diff | comparison | revisions
src/ascension/camera.h file | annotate | diff | comparison | revisions
src/ascension/datatypes.h file | annotate | diff | comparison | revisions
src/ascension/scene.h file | annotate | diff | comparison | revisions
src/ascension/text.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/text.c file | annotate | diff | comparison | revisions
src/window.c file | annotate | diff | comparison | revisions
test/Makefile file | annotate | diff | comparison | revisions
test/snake.c file | annotate | diff | comparison | revisions
--- a/src/Makefile	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/Makefile	Fri Mar 15 00:06:59 2024 +0100
@@ -27,8 +27,8 @@
 
 BUILD_DIR=../build/lib
 
-SRC = context.c error.c window.c files.c shader.c font.c text.c scene.c \
-	  primitives.c
+SRC = context.c error.c window.c files.c shader.c font.c text.c \
+      scene.c camera.c primitives.c
 
 OBJ = $(SRC:%.c=$(BUILD_DIR)/%.o)
 
@@ -41,17 +41,22 @@
 
 FORCE:
 
+$(BUILD_DIR)/camera.o: camera.c ascension/camera.h ascension/datatypes.h
+	@echo "Compiling $<"
+	$(CC) -o $@ $(CFLAGS) -c $<
+
 $(BUILD_DIR)/context.o: context.c ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/primitives.h \
  ascension/mesh.h ascension/scene.h ascension/transform.h \
- ascension/font.h ascension/error.h ascension/utils.h ascension/shader.h
+ ascension/camera.h ascension/font.h ascension/error.h ascension/utils.h \
+ ascension/shader.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/error.o: error.c ascension/context.h ascension/datatypes.h \
  ascension/window.h ascension/primitives.h ascension/mesh.h \
- ascension/scene.h ascension/transform.h ascension/font.h \
- ascension/error.h ascension/utils.h
+ ascension/scene.h ascension/transform.h ascension/camera.h \
+ ascension/font.h ascension/error.h ascension/utils.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -62,19 +67,22 @@
 $(BUILD_DIR)/font.o: font.c ascension/font.h ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/primitives.h \
  ascension/mesh.h ascension/scene.h ascension/transform.h \
- ascension/font.h ascension/error.h
+ ascension/camera.h ascension/font.h ascension/error.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/primitives.h \
- ascension/scene.h ascension/transform.h ascension/font.h
+ ascension/scene.h ascension/transform.h ascension/camera.h \
+ ascension/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
-$(BUILD_DIR)/scene.o: scene.c ascension/scene.h ascension/transform.h \
- ascension/datatypes.h ascension/error.h
+$(BUILD_DIR)/scene.o: scene.c ascension/scene.h ascension/datatypes.h \
+ ascension/transform.h ascension/camera.h ascension/error.h \
+ ascension/context.h ascension/window.h ascension/primitives.h \
+ ascension/mesh.h ascension/scene.h ascension/font.h ascension/shader.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -84,16 +92,17 @@
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/text.o: text.c ascension/text.h ascension/font.h \
- ascension/scene.h ascension/transform.h ascension/datatypes.h \
- ascension/context.h ascension/window.h ascension/primitives.h \
- ascension/mesh.h ascension/error.h ascension/shader.h
+ ascension/scene.h ascension/datatypes.h ascension/transform.h \
+ ascension/camera.h ascension/context.h ascension/window.h \
+ ascension/primitives.h ascension/mesh.h ascension/error.h \
+ ascension/shader.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/window.o: window.c ascension/window.h ascension/datatypes.h \
  ascension/primitives.h ascension/mesh.h ascension/scene.h \
- ascension/transform.h ascension/context.h ascension/window.h \
- ascension/font.h ascension/error.h ascension/utils.h
+ ascension/transform.h ascension/camera.h ascension/context.h \
+ ascension/window.h ascension/font.h ascension/error.h ascension/utils.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ascension/camera.h	Fri Mar 15 00:06:59 2024 +0100
@@ -0,0 +1,59 @@
+/*
+ * 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_CAMERA_H
+#define ASCENSION_CAMERA_H
+
+#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/datatypes.h	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/ascension/datatypes.h	Fri Mar 15 00:06:59 2024 +0100
@@ -44,11 +44,22 @@
 typedef signed char asc_sbyte;
 
 typedef union asc_vec2i {
-    int data[2];
     struct { int x, y; };
     struct { int width, height; };
+    int data[2];
 } asc_vec2i;
 
+typedef struct asc_recti {
+    asc_vec2i pos;
+    asc_vec2i size;
+} asc_recti;
+
+typedef union asc_vec3f {
+    struct { float x, y, z; };
+    struct { float width, height, depth; };
+    float data[3];
+} asc_vec3f;
+
 typedef struct asc_col4i {
     asc_ubyte red, green, blue, alpha;
 } asc_col4i;
@@ -114,6 +125,14 @@
  */
 #define asc_mat4_index(col, row) asc_mat_index(col, row, 4)
 
+static inline void asc_mat4f_unit(asc_mat4f mat) {
+    memset(mat, 0, sizeof(float) * 16);
+    mat[asc_mat4_index(0,0)] = 1;
+    mat[asc_mat4_index(1,1)] = 1;
+    mat[asc_mat4_index(2,2)] = 1;
+    mat[asc_mat4_index(3,3)] = 1;
+}
+
 static inline void asc_mat4f_ortho(
         asc_mat4f mat,
         float left,
--- a/src/ascension/scene.h	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/ascension/scene.h	Fri Mar 15 00:06:59 2024 +0100
@@ -28,7 +28,11 @@
 #ifndef ASCENSION_SCENE_H
 #define ASCENSION_SCENE_H
 
+#include "datatypes.h"
 #include "transform.h"
+#include "camera.h"
+
+#include <cx/array_list.h>
 
 typedef struct AscSceneNode AscSceneNode;
 typedef struct AscBehaviorNode AscBehaviorNode;
@@ -44,6 +48,11 @@
     asc_scene_update_func func;
 };
 
+enum AscRenderGroup {
+    ASC_RENDER_GROUP_NONE,
+    ASC_RENDER_GROUP_FONTS
+};
+
 struct AscSceneNode {
     AscSceneNode *parent;
     AscSceneNode *prev;
@@ -53,9 +62,12 @@
     void *data;
     asc_scene_free_func free_func;
     asc_scene_update_func update_func;
+    asc_scene_update_func transform_update_func;
     asc_scene_draw_func draw_func;
     asc_transform transform;
-    bool need_update;
+    enum AscRenderGroup render_group;
+    bool need_full_update;
+    bool need_transform_update;
 };
 
 /**
@@ -65,13 +77,32 @@
 
 #define asc_node(obj) ((AscSceneNode*)obj)
 
-#define asc_node_update(node) ((AscSceneNode*)node)->need_update = true
+#define asc_node_update(node) \
+    ((AscSceneNode*)node)->need_full_update = true
+#define asc_node_update_transform(node) \
+    ((AscSceneNode*)node)->need_transform_update = true
+
+struct asc_render_group_entry {
+    asc_scene_draw_func draw;
+    AscSceneNode const *node;
+};
+
+#define ASC_SCENE_CAMERAS_MAX 4
 
 typedef struct AscScene {
     AscSceneNode *root;
-    // TODO: add render groups for batching
+    asc_recti viewport;
+    AscCamera cameras[ASC_SCENE_CAMERAS_MAX];
+    CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_none);
+    CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_fonts);
 } AscScene;
 
+/**
+ * Initializes the scene using the active window as reference.
+ *
+ * @param scene the scene to initialize
+ * @param type determines the type of camera to use
+ */
 __attribute__((__nonnull__))
 void asc_scene_init(AscScene *scene);
 
@@ -79,7 +110,7 @@
 void asc_scene_destroy(AscScene *scene);
 
 __attribute__((__nonnull__))
-void asc_scene_draw(AscScene const *scene);
+void asc_scene_draw(AscScene *scene);
 
 __attribute__((__nonnull__))
 void asc_scene_add(AscScene *scene, AscSceneNode *node);
--- a/src/ascension/text.h	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/ascension/text.h	Fri Mar 15 00:06:59 2024 +0100
@@ -40,9 +40,8 @@
     unsigned max_width;
     bool hidden;
     bool centered;
-    struct {
-        unsigned tex_id;
-    } internal;
+    asc_vec2i dimension;
+    unsigned tex_id;
 } AscText;
 
 
--- a/src/ascension/window.h	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/ascension/window.h	Fri Mar 15 00:06:59 2024 +0100
@@ -55,11 +55,11 @@
     SDL_GLContext glctx;
     AscPrimitives primitives;
     asc_vec2i dimensions;
-    asc_mat4f projection;
+    bool resized;
     AscScene ui;
 } AscWindow;
 
-#define asc_window_active_ui &(asc_context.active_window->ui)
+#define asc_window_active asc_context.active_window
 
 /**
  * Initializes the settings structure with default values.
@@ -102,7 +102,7 @@
  *
  * @param window the window
  */
-void asc_window_sync(AscWindow const *window);
+void asc_window_sync(AscWindow *window);
 
 /**
  * Switches the active window.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/camera.c	Fri Mar 15 00:06:59 2024 +0100
@@ -0,0 +1,46 @@
+/*
+ * 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/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;
+    asc_mat4f_ortho(camera->projection, left, right, bottom, top);
+}
+
+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	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/context.c	Fri Mar 15 00:06:59 2024 +0100
@@ -83,9 +83,11 @@
 static void asc_event_window_resized(Uint32 id, Sint32 width, Sint32 height) {
     for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) {
         if (asc_context.windows[i].id == id) {
-            asc_context.windows[i].dimensions.width = width;
-            asc_context.windows[i].dimensions.height = height;
-            asc_mat4f_ortho(asc_context.windows[i].projection, 0, (float) width, (float) height, 0);
+            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	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/scene.c	Fri Mar 15 00:06:59 2024 +0100
@@ -28,19 +28,34 @@
 #include "ascension/scene.h"
 #include "ascension/error.h"
 
+#include "ascension/context.h"
+
 #include <cx/tree.h>
+#include <cx/utils.h>
+
+#include "ascension/shader.h"
+#include <GL/glew.h>
 
 #include <assert.h>
 
-#define child_list_off_ \
-    offsetof(AscSceneNode, children), offsetof(AscSceneNode, next)
-
 void asc_scene_init(AscScene *scene) {
     if (scene->root != NULL) {
         asc_error("Scene is already initialized.");
         return;
     }
+
+    // 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_none, 8);
+    cx_array_initialize(scene->rg_fonts, 8);
 }
 
 void asc_scene_destroy(AscScene *scene) {
@@ -52,13 +67,25 @@
     asc_node_update(node);
 }
 
-void asc_scene_draw(AscScene const *scene) {
-    CxTreeIterator iter = cx_tree_iterator(scene->root, false, child_list_off_);
+#define asc_scene_draw_render_group(rg) \
+    cx_for_n(i, rg##_size) { \
+        rg[i].draw(rg[i].node); \
+    } (void)0
+
+void asc_scene_draw(AscScene *scene) {
+    // reset render groups
+    scene->rg_none_size = 0;
+    scene->rg_fonts_size = 0;
 
     // skip the root node deliberately, we know it's just the container
+    CxTreeIterator iter = cx_tree_iterator(
+            scene->root, false,
+            offsetof(AscSceneNode, children),
+            offsetof(AscSceneNode, next)
+    );
     cxIteratorNext(iter);
 
-    // update the children
+    // update the children and add them to the render groups
     cx_foreach(AscSceneNode*, node, iter) {
         // execute behaviors, first
         AscBehaviorNode *behavior = node->behaviors;
@@ -68,22 +95,65 @@
         }
 
         // check if geometry needs update
-        if (node->need_update && node->update_func != NULL) {
-            node->need_update = false;
-            asc_transform_copy(node->transform, node->parent->transform);
+        if (node->need_full_update) {
+            assert(node->update_func != NULL);
+            node->need_full_update = false;
             node->update_func(node);
         }
+        if (node->need_transform_update) {
+            assert(node->transform_update_func != NULL);
+            node->need_transform_update = false;
+            asc_transform_identity(node->transform);
+            node->transform_update_func(node);
+        }
 
-        // TODO: don't visit the tree for drawing, visit the render groups
+        // add to render group
         if (node->draw_func != NULL) {
-            node->draw_func(node);
+            struct asc_render_group_entry entry = {
+                    node->draw_func, node
+            };
+            switch (node->render_group) {
+                case ASC_RENDER_GROUP_NONE:
+                    cx_array_simple_add(scene->rg_none, entry);
+                    break;
+                case ASC_RENDER_GROUP_FONTS:
+                    cx_array_simple_add(scene->rg_fonts, entry);
+                    break;
+            }
         }
     }
+
+    // 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
+    );
+
+    // -----------------------------------------
+    // process the render groups for each camera
+    // -----------------------------------------
+    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);
+
+        // for the NONE group, the draw func is expected to do everything
+        asc_scene_draw_render_group(scene->rg_none);
+
+        // draw the FONTS group
+        // TODO: see if we can really always ignore the view matrix
+        glUseProgram(ASC_SHADER_FONT.base.id);
+        glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1,
+                           GL_FALSE, camera->projection);
+        asc_scene_draw_render_group(scene->rg_fonts);
+    }
 }
 
 AscSceneNode *asc_scene_node_empty(void) {
     AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
-    assert(node != NULL);
     node->free_func = (asc_scene_free_func) free;
     asc_transform_identity(node->transform);
     return node;
@@ -96,7 +166,11 @@
     asc_scene_node_unlink(node);
 
     // free the entire subtree
-    CxTreeIterator iter = cx_tree_iterator(node, true, child_list_off_);
+    CxTreeIterator iter = cx_tree_iterator(
+            node, true,
+            offsetof(AscSceneNode, children),
+            offsetof(AscSceneNode, next)
+    );
     cx_foreach(AscSceneNode*, child, iter) {
         if (!iter.exiting) continue;
         if (child->free_func != NULL) {
--- a/src/text.c	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/text.c	Fri Mar 15 00:06:59 2024 +0100
@@ -33,29 +33,28 @@
 #include <GL/glew.h>
 
 static void asc_text_draw(AscText const *node) {
-    if (node->color.alpha == 0 || node->hidden || node->internal.tex_id == 0) {
+    if (node->color.alpha == 0 || node->hidden || node->tex_id == 0) {
         return;
     }
 
-    // TODO: when we group draw calls, we don't need to activate shader here
-    glUseProgram(ASC_SHADER_FONT.base.id);
-
-    // TODO: when we group UI draw calls, we don't need to upload matrices here
-    // Upload matrices
-    glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1,
-                       GL_FALSE, asc_context.active_window->projection);
+    // Upload model matrix
     glUniformMatrix4fv(ASC_SHADER_FONT.base.model, 1,
                        GL_FALSE, node->base.transform);
 
     // Upload surface
     glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
+    glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id);
     glUniform1i(ASC_SHADER_FONT.surface, 0);
 
     // Draw mesh
     asc_primitives_draw_plane();
 }
 
+static void asc_text_update_transform(AscText *node) {
+    asc_transform_scale(node->base.transform, (float) node->dimension.width, (float) node->dimension.height, 0);
+    asc_transform_translate2i(node->base.transform, node->position);
+}
+
 static void asc_text_update(AscText *node) {
     // short circuit if fully transparent or hidden, we don't need anything
     if (node->color.alpha == 0 || node->hidden) {
@@ -63,12 +62,12 @@
     }
 
     // Generate new texture, if required
-    if (node->internal.tex_id == 0) {
-        glGenTextures(1, &node->internal.tex_id);
-        glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
+    if (node->tex_id == 0) {
+        glGenTextures(1, &node->tex_id);
+        glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id);
         glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
         glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-        asc_dprintf("Generated new texture for text node: %u", node->internal.tex_id);
+        asc_dprintf("Generated new texture for text node: %u", node->tex_id);
     }
 
     // Render text onto a surface
@@ -82,14 +81,13 @@
         asc_error(SDL_GetError());
         return;
     }
-
-    // Transform
-    asc_transform_scale(node->base.transform, (float) surface->w, (float) surface->h, 0);
-    asc_transform_translate2i(node->base.transform, node->position);
+    node->dimension.width = surface->w;
+    node->dimension.height = surface->h;
+    asc_node_update_transform(node);
 
     // Transfer Image Data
     // TODO: move the image data transfer to a separate function - we will need it more often
-    glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
+    glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id);
     glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / surface->format->BytesPerPixel);
     glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA,
                  surface->w, surface->h,
@@ -106,8 +104,10 @@
         return NULL;
     }
 
+    node->base.render_group = ASC_RENDER_GROUP_FONTS;
     node->base.free_func = (asc_scene_free_func) asc_text_free;
     node->base.update_func = (asc_scene_update_func) asc_text_update;
+    node->base.transform_update_func = (asc_scene_update_func) asc_text_update_transform;
     node->base.draw_func = (asc_scene_draw_func) asc_text_draw;
 
     node->position.x = x;
@@ -118,12 +118,16 @@
         node->text = strdup(text);
     }
 
+    // initialize
+    asc_text_update(node);
+    asc_text_update_transform(node);
+
     return &node->base;
 }
 
 void asc_text_free(AscText *node) {
-    asc_dprintf("Release text node texture: %u", node->internal.tex_id);
-    glDeleteTextures(1, &node->internal.tex_id);
+    asc_dprintf("Release text node texture: %u", node->tex_id);
+    glDeleteTextures(1, &node->tex_id);
     free(node->text);
     free(node);
 }
\ No newline at end of file
--- a/src/window.c	Wed Mar 06 23:38:17 2024 +0100
+++ b/src/window.c	Fri Mar 15 00:06:59 2024 +0100
@@ -63,6 +63,9 @@
 
 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) {
@@ -98,13 +101,7 @@
             &window->dimensions.width,
             &window->dimensions.height
     );
-    asc_mat4f_ortho(
-            window->projection,
-            0,
-            (float) window->dimensions.width,
-            (float) window->dimensions.height,
-            0
-    );
+    window->resized = true; // count initial sizing as resize
 
     SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, settings->gl_major_version);
@@ -127,8 +124,8 @@
 
             asc_dprintf("Window %u initialized", window->id);
             if (asc_primitives_init(&window->primitives)) {
+                asc_context.active_window = window;
                 asc_window_init_scenes(window);
-                asc_context.active_window = window;
                 return window;
             } else {
                 asc_dprintf("!!! Creating primitives for window %u failed !!!", window->id);
@@ -183,14 +180,13 @@
     memset(window, 0, sizeof(AscWindow));
 }
 
-void asc_window_sync(AscWindow const* window) {
+void asc_window_sync(AscWindow* window) {
     AscWindow const *active_window = asc_context.active_window;
     if (window != active_window) {
         asc_window_activate(window);
     }
 
-    // Clear viewport for new frame
-    glViewport(0, 0, window->dimensions.width, window->dimensions.height);
+    // Clear for new frame
     glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
@@ -200,6 +196,9 @@
     // Swap Buffers
     SDL_GL_SwapWindow(window->window);
 
+    // Clear Flags
+    window->resized = false;
+
     if (window != active_window) {
         asc_window_activate(active_window);
     }
--- a/test/Makefile	Wed Mar 06 23:38:17 2024 +0100
+++ b/test/Makefile	Fri Mar 15 00:06:59 2024 +0100
@@ -46,8 +46,8 @@
  ../src/ascension/datatypes.h ../src/ascension/window.h \
  ../src/ascension/primitives.h ../src/ascension/mesh.h \
  ../src/ascension/scene.h ../src/ascension/transform.h \
- ../src/ascension/font.h ../src/ascension/shader.h \
- ../src/ascension/text.h
+ ../src/ascension/camera.h ../src/ascension/font.h \
+ ../src/ascension/shader.h ../src/ascension/text.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
--- a/test/snake.c	Wed Mar 06 23:38:17 2024 +0100
+++ b/test/snake.c	Fri Mar 15 00:06:59 2024 +0100
@@ -30,7 +30,7 @@
 
 static void update_fps_counter(AscSceneNode *node) {
     // addition and multiplication is more efficient testing for zero
-    // at an unnoticible cost of imprecision
+    // at an unnoticeable cost of imprecision
     static unsigned last_fps = 0u;
     static unsigned debounce = 999u;
     unsigned fps = 1000u;
@@ -51,7 +51,27 @@
     asc_ink_rgb(255, 0, 0);
     AscSceneNode* node = asc_text(10, 10, "XXXXX FPS");
     asc_scene_add_behavior(node, update_fps_counter);
-    asc_scene_add(asc_window_active_ui, node);
+    asc_scene_add(&asc_window_active->ui, node);
+}
+
+static void update_score_counter(AscSceneNode *node) {
+    AscText *text = asc_text_data(node);
+
+    // tie to bottom left of the screen
+    if (asc_window_active->resized) {
+        asc_vec2i bottom_left = asc_window_active->dimensions;
+        text->position.x = bottom_left.x - text->dimension.width - 10;
+        text->position.y = bottom_left.y - text->dimension.height - 10;
+        asc_node_update_transform(text);
+    }
+}
+
+static void create_score_counter(void) {
+    asc_set_font(asc_font(ASC_FONT_BOLD, 14));
+    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);
 }
 
 int main(int argc, char** argv) {
@@ -69,8 +89,10 @@
     AscWindow *window = asc_window_initialize(0, &settings);
     asc_shader_initialize_predefined();
 
-    // create fps counter
+    // create UI elements
     create_fps_counter();
+    create_score_counter();
+
 
     // Main Loop
     do {

mercurial