Sun, 13 Jul 2025 15:09:04 +0200
rework how transformations work to allow rotations around a point of origin
src/2d.c | file | annotate | diff | comparison | revisions | |
src/ascension/datatypes.h | file | annotate | diff | comparison | revisions | |
src/ascension/scene_node.h | file | annotate | diff | comparison | revisions | |
src/ascension/sprite.h | file | annotate | diff | comparison | revisions | |
src/ascension/text.h | file | annotate | diff | comparison | revisions | |
src/ascension/transform.h | file | annotate | diff | comparison | revisions | |
src/scene.c | file | annotate | diff | comparison | revisions | |
src/scene_node.c | file | annotate | diff | comparison | revisions | |
src/sprite.c | file | annotate | diff | comparison | revisions | |
src/text.c | file | annotate | diff | comparison | revisions | |
test/snake/snake.c | file | annotate | diff | comparison | revisions |
--- a/src/2d.c Sun Jul 13 14:22:40 2025 +0200 +++ b/src/2d.c Sun Jul 13 15:09:04 2025 +0200 @@ -34,6 +34,8 @@ #include <assert.h> +// TODO: add "origin" arguments to 2D primitives + typedef struct asc_rectangle_shader_s { AscShaderProgram program; asc_uniform_loc color; @@ -188,9 +190,9 @@ } AscSceneNode *node = &rectangle->node; - asc_transform_identity(node->transform); - asc_transform_translate3f(node->transform, - ASC_VEC3F(pos_x, pos_y, ASC_SCENE_2D_DEPTH_OFFSET)); + node->position = ASC_VEC3F(pos_x, pos_y, ASC_SCENE_2D_DEPTH_OFFSET); + node->scale = ASC_VEC3F_1; + asc_mat4f_unit(node->rotation); node->render_group = asc_context.ink.alpha < 255 ? ASC_RENDER_GROUP_2D_BLEND : ASC_RENDER_GROUP_2D_OPAQUE; @@ -344,9 +346,9 @@ } AscSceneNode *node = &ellipsis->node; - asc_transform_identity(node->transform); - asc_transform_translate3f(node->transform, - ASC_VEC3F(pos_x, pos_y, ASC_SCENE_2D_DEPTH_OFFSET)); + node->position = ASC_VEC3F(pos_x, pos_y, ASC_SCENE_2D_DEPTH_OFFSET); + node->scale = ASC_VEC3F_1; + asc_mat4f_unit(node->rotation); node->render_group = asc_context.ink.alpha < 255 ? ASC_RENDER_GROUP_2D_BLEND : ASC_RENDER_GROUP_2D_OPAQUE;
--- a/src/ascension/datatypes.h Sun Jul 13 14:22:40 2025 +0200 +++ b/src/ascension/datatypes.h Sun Jul 13 15:09:04 2025 +0200 @@ -107,7 +107,6 @@ typedef union asc_vec3f { struct { float x, y, z; }; struct { float width, height, depth; }; - struct { float pitch, yaw, roll; }; float data[3]; } asc_vec3f; #define ASC_VEC3F(x, y, z) (asc_vec3f){{(float)x, (float)y, (float)(z)}} @@ -307,6 +306,14 @@ return ASC_VEC3F(v.x*s, v.y*s, v.z*s); } +static inline asc_vec2f asc_vec2f_neg(asc_vec2f v) { + return ASC_VEC2F(-v.x, -v.y); +} + +static inline asc_vec3f asc_vec3f_neg(asc_vec3f v) { + return ASC_VEC3F(-v.x, -v.y, -v.z); +} + static inline unsigned asc_vec2u_sqrlen(asc_vec2u v) { return v.x*v.x + v.y*v.y; }
--- a/src/ascension/scene_node.h Sun Jul 13 14:22:40 2025 +0200 +++ b/src/ascension/scene_node.h Sun Jul 13 15:09:04 2025 +0200 @@ -66,6 +66,10 @@ asc_scene_node_draw_func draw_func; asc_transform transform; asc_transform world_transform; + asc_vec3f position; + asc_vec3f scale; + asc_vec3f origin; + asc_transform rotation; enum AscRenderGroup render_group; /** * Custom flags for this node. @@ -100,6 +104,7 @@ */ #define ASC_SCENE_NODE_HIDDEN 0x80000000 +// TODO: some functions are prefixed asc_scene_node_ and others just asc_node_ /** * Creates an empty node that may serve as a container for other nodes. @@ -123,6 +128,17 @@ void asc_scene_node_free(AscSceneNode *node); /** + * Calculates the transformation matrix from components. + * + * Used internally, usually you never need to call this. + * Use asc_node_update_transform() to trigger a recalculation. + * + * @param node the node + * @see asc_node_update_transform() + */ +void asc_scene_node_calculate_transform(AscSceneNode *node); + +/** * Sets the name of a node. * * @param node the node @@ -172,39 +188,50 @@ */ #define ASC_SCENE_2D_DEPTH_OFFSET 0.0078125f -/** - * Applies an affine transformation to a scene node. - * - * @param node the node to modify - * @param matrix the matrix to multiply with the current transformation matrix - */ -ASC_TRANFORM_FUNC void asc_node_transform_apply(AscSceneNode *node, const asc_transform matrix) { - asc_mat4f_mul(node->transform, node->transform, matrix); +static inline void asc_node_set_position(AscSceneNode *node, asc_vec3f position) { + node->position = position; + asc_node_update_transform(node); +} + +static inline void asc_node_set_scale(AscSceneNode *node, asc_vec3f scale) { + node->scale = scale; + asc_node_update_transform(node); +} + +static inline void asc_node_set_origin(AscSceneNode *node, asc_vec3f origin) { + node->origin = origin; + asc_node_update_transform(node); +} + +static inline void asc_node_set_position2f(AscSceneNode *node, asc_vec2f position) { + node->position.x = position.x; + node->position.y = position.y; asc_node_update_transform(node); } -/** - * Overwrites the current local transformation matrix. - * - * @param node the node to modify - * @param matrix the matrix to set as the new local transformation matrix - */ -ASC_TRANFORM_FUNC void asc_node_transform_set(AscSceneNode *node, const asc_transform matrix) { - asc_transform_copy(node->transform, matrix); +static inline void asc_node_set_scale2f(AscSceneNode *node, asc_vec2f scale) { + node->scale.width = scale.width; + node->scale.height = scale.height; + asc_node_update_transform(node); +} + +static inline void asc_node_set_origin2f(AscSceneNode *node, asc_vec2f origin) { + node->origin.x = origin.x; + node->origin.y = origin.y; asc_node_update_transform(node); } -/** - * Resets the node to use an identity transformation matrix. - * - * This is, for example, useful when you want to recalculate a chain of transformations from scratch. - * - * @param node the node for which to reset the local transformation - */ -ASC_TRANFORM_FUNC void asc_node_transform_reset(AscSceneNode *node) { - asc_transform_identity(node->transform); +static inline void asc_node_set_rotation(AscSceneNode *node, asc_transform rotation) { + memcpy(node->rotation, rotation, ASC_TRANSFORM_SIZE); asc_node_update_transform(node); } +static inline void asc_node_roll_deg(AscSceneNode *node, float angle) { + asc_transform r, d; + asc_transform_roll(r, asc_rad(angle)); + asc_transform_apply(d, r, node->rotation); + asc_transform_copy(node->rotation, d); + asc_node_update_transform(node); +} #endif
--- a/src/ascension/sprite.h Sun Jul 13 14:22:40 2025 +0200 +++ b/src/ascension/sprite.h Sun Jul 13 15:09:04 2025 +0200 @@ -48,6 +48,8 @@ AscTexture *texture; int x; int y; + int origin_x; + int origin_y; /** * Optional width for re-scaling. * If zero, the texture width will be used.
--- a/src/ascension/text.h Sun Jul 13 14:22:40 2025 +0200 +++ b/src/ascension/text.h Sun Jul 13 15:09:04 2025 +0200 @@ -69,6 +69,7 @@ const char *text; enum asc_text_alignment alignment; unsigned short max_width; + bool centered; }; /**
--- a/src/ascension/transform.h Sun Jul 13 14:22:40 2025 +0200 +++ b/src/ascension/transform.h Sun Jul 13 15:09:04 2025 +0200 @@ -40,148 +40,138 @@ #define ASC_TRANFORM_FUNC static inline #endif +ASC_TRANFORM_FUNC void asc_transform_copy(asc_transform dest, const asc_transform src) { + memcpy(dest, src, ASC_TRANSFORM_SIZE); +} + +ASC_TRANFORM_FUNC void asc_transform_apply(asc_transform dest, const asc_transform left, const asc_transform right) { + asc_mat4f_mul(dest, left, right); +} ASC_TRANFORM_FUNC void asc_transform_identity(asc_transform transform) { asc_mat4f_unit(transform); } -ASC_TRANFORM_FUNC void asc_transform_copy(asc_transform dest, const asc_transform src) { - memcpy(dest, src, ASC_TRANSFORM_SIZE); -} - +/** + * Makes the transformation matrix a translation matrix. + * + * @param transform the matrix to initialize + * @param vec the translation vector + */ ASC_TRANFORM_FUNC void asc_transform_translate3f( asc_transform transform, asc_vec3f vec ) { - transform[asc_mat4_index(3, 0)] += vec.x; - transform[asc_mat4_index(3, 1)] += vec.y; - transform[asc_mat4_index(3, 2)] += vec.z; + asc_mat4f_unit(transform); + transform[asc_mat4_index(3, 0)] = vec.x; + transform[asc_mat4_index(3, 1)] = vec.y; + transform[asc_mat4_index(3, 2)] = vec.z; } +/** + * Makes the transformation matrix a scale matrix. + * + * @param transform the matrix to initialize + * @param vec the scale vector + */ ASC_TRANFORM_FUNC void asc_transform_scale3f( asc_transform transform, asc_vec3f vec ) { - for (unsigned i = 0 ; i < 3 ; i++) { - transform[asc_mat4_index(0, i)] *= vec.width; - transform[asc_mat4_index(1, i)] *= vec.height; - transform[asc_mat4_index(2, i)] *= vec.depth; - } + memset(transform, 0, ASC_TRANSFORM_SIZE); + transform[asc_mat4_index(0, 0)] = vec.width; + transform[asc_mat4_index(1, 1)] = vec.height; + transform[asc_mat4_index(2, 2)] = vec.depth; + transform[asc_mat4_index(3, 3)] = 1.f; } +/** + * Makes the transformation matrix a translation matrix. + * + * @param transform the matrix to initialize + * @param vec the translation vector + */ ASC_TRANFORM_FUNC void asc_transform_translate2f( asc_transform transform, asc_vec2f vec ) { - transform[asc_mat4_index(3, 0)] += vec.x; - transform[asc_mat4_index(3, 1)] += vec.y; + asc_transform_translate3f(transform, ASC_VEC3F(vec.x, vec.y, 0.f)); } +/** + * Makes the transformation matrix a scale matrix. + * + * @param transform the matrix to initialize + * @param vec the scale vector + */ ASC_TRANFORM_FUNC void asc_transform_scale2f( asc_transform transform, asc_vec2f vec ) { - for (unsigned i = 0 ; i < 3 ; i++) { - transform[asc_mat4_index(0, i)] *= vec.width; - transform[asc_mat4_index(1, i)] *= vec.height; - } + asc_transform_scale3f(transform, ASC_VEC3F(vec.width, vec.height, 1.f)); } +/** + * Makes the transformation matrix a rotation matrix around the y-axis. + * + * @param transform the matrix to initialize + * @param angle the angle in radians + */ ASC_TRANFORM_FUNC void asc_transform_yaw( asc_transform transform, float angle ) { - float s = sinf(angle); - float c = cosf(angle); - - float m00 = transform[asc_mat4_index(0, 0)]; - float m02 = transform[asc_mat4_index(0, 2)]; - float m10 = transform[asc_mat4_index(1, 0)]; - float m12 = transform[asc_mat4_index(1, 2)]; - float m20 = transform[asc_mat4_index(2, 0)]; - float m22 = transform[asc_mat4_index(2, 2)]; - - transform[asc_mat4_index(0, 0)] = m00 * c + m02 * s; - transform[asc_mat4_index(0, 2)] = -m00 * s + m02 * c; - transform[asc_mat4_index(1, 0)] = m10 * c + m12 * s; - transform[asc_mat4_index(1, 2)] = -m10 * s + m12 * c; - transform[asc_mat4_index(2, 0)] = m20 * c + m22 * s; - transform[asc_mat4_index(2, 2)] = -m20 * s + m22 * c; + memset(transform, 0, ASC_TRANSFORM_SIZE); + const float s = asc_sin(angle); + const float c = asc_cos(angle); + transform[asc_mat4_index(0, 0)] = c; + transform[asc_mat4_index(0, 2)] = -s; + transform[asc_mat4_index(1, 1)] = 1; + transform[asc_mat4_index(2, 0)] = s; + transform[asc_mat4_index(2, 2)] = c; + transform[asc_mat4_index(3, 3)] = 1; } +/** + * Makes the transformation matrix a rotation matrix around the x-axis. + * + * @param transform the matrix to initialize + * @param angle the angle in radians + */ ASC_TRANFORM_FUNC void asc_transform_pitch( asc_transform transform, float angle ) { - float s = sinf(angle); - float c = cosf(angle); - - float m01 = transform[asc_mat4_index(0, 1)]; - float m02 = transform[asc_mat4_index(0, 2)]; - float m11 = transform[asc_mat4_index(1, 1)]; - float m12 = transform[asc_mat4_index(1, 2)]; - float m21 = transform[asc_mat4_index(2, 1)]; - float m22 = transform[asc_mat4_index(2, 2)]; - - transform[asc_mat4_index(0, 1)] = m01 * c - m02 * s; - transform[asc_mat4_index(0, 2)] = m01 * s + m02 * c; - transform[asc_mat4_index(1, 1)] = m11 * c - m12 * s; - transform[asc_mat4_index(1, 2)] = m11 * s + m12 * c; - transform[asc_mat4_index(2, 1)] = m21 * c - m22 * s; - transform[asc_mat4_index(2, 2)] = m21 * s + m22 * c; + memset(transform, 0, ASC_TRANSFORM_SIZE); + const float s = asc_sin(angle); + const float c = asc_cos(angle); + transform[asc_mat4_index(0, 0)] = 1; + transform[asc_mat4_index(1, 1)] = c; + transform[asc_mat4_index(1, 2)] = s; + transform[asc_mat4_index(2, 1)] = -s; + transform[asc_mat4_index(2, 2)] = c; + transform[asc_mat4_index(3, 3)] = 1; } +/** + * Makes the transformation matrix a rotation matrix around the z-axis. + * + * @param transform the matrix to initialize + * @param angle the angle in radians + */ ASC_TRANFORM_FUNC void asc_transform_roll( asc_transform transform, float angle ) { - float s = sinf(angle); - float c = cosf(angle); - - float m00 = transform[asc_mat4_index(0, 0)]; - float m01 = transform[asc_mat4_index(0, 1)]; - float m10 = transform[asc_mat4_index(1, 0)]; - float m11 = transform[asc_mat4_index(1, 1)]; - float m20 = transform[asc_mat4_index(2, 0)]; - float m21 = transform[asc_mat4_index(2, 1)]; - - transform[asc_mat4_index(0, 0)] = m00 * c - m01 * s; - transform[asc_mat4_index(0, 1)] = m00 * s + m01 * c; - transform[asc_mat4_index(1, 0)] = m10 * c - m11 * s; - transform[asc_mat4_index(1, 1)] = m10 * s + m11 * c; - transform[asc_mat4_index(2, 0)] = m20 * c - m21 * s; - transform[asc_mat4_index(2, 1)] = m20 * s + m21 * c; -} - -ASC_TRANFORM_FUNC void asc_transform_roll_origin( - asc_transform transform, - float angle, - asc_vec3f point_of_origin -) { - // TODO: this is somehow still broken - find out why - asc_transform translate; - asc_transform_identity(translate); - translate[asc_mat4_index(3, 0)] = -point_of_origin.x; - translate[asc_mat4_index(3, 1)] = -point_of_origin.y; - translate[asc_mat4_index(3, 2)] = -point_of_origin.z; - - asc_transform rotate; - asc_transform_identity(rotate); - float s = sinf(angle); - float c = cosf(angle); - rotate[asc_mat4_index(0, 0)] = c; - rotate[asc_mat4_index(0, 1)] = s; - rotate[asc_mat4_index(1, 0)] = -s; - rotate[asc_mat4_index(1, 1)] = c; - - asc_transform result; - asc_mat4f_mul(result, transform, translate); - asc_mat4f_mul(transform, result, rotate); - translate[asc_mat4_index(3, 0)] *= -1.f; - translate[asc_mat4_index(3, 1)] *= -1.f; - translate[asc_mat4_index(3, 2)] *= -1.f; - asc_mat4f_mul(result, transform, translate); - asc_transform_copy(transform, result); + memset(transform, 0, ASC_TRANSFORM_SIZE); + const float s = asc_sin(angle); + const float c = asc_cos(angle); + transform[asc_mat4_index(0, 0)] = c; + transform[asc_mat4_index(0, 1)] = s; + transform[asc_mat4_index(1, 0)] = -s; + transform[asc_mat4_index(1, 1)] = c; + transform[asc_mat4_index(2, 2)] = 1; + transform[asc_mat4_index(3, 3)] = 1; }
--- a/src/scene.c Sun Jul 13 14:22:40 2025 +0200 +++ b/src/scene.c Sun Jul 13 15:09:04 2025 +0200 @@ -164,6 +164,9 @@ if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) { asc_set_flag(node->flags, ASC_SCENE_NODE_TRANSFORM_UPDATED); asc_clear_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM); + + asc_scene_node_calculate_transform(node); + if (node->parent == scene->root) { // skip unnecessary multiplication with unity matrix asc_transform_copy(node->world_transform, node->transform);
--- a/src/scene_node.c Sun Jul 13 14:22:40 2025 +0200 +++ b/src/scene_node.c Sun Jul 13 15:09:04 2025 +0200 @@ -79,6 +79,24 @@ } } +void asc_scene_node_calculate_transform(AscSceneNode *node) { + asc_transform temp, temp2; + + // move the point of origin + asc_transform_translate3f(temp, asc_vec3f_neg(node->origin)); + + // apply the rotation + asc_transform_apply(node->transform, node->rotation, temp); + + // apply the scale + asc_transform_scale3f(temp, node->scale); + asc_transform_apply(temp2, temp, node->transform); + + // apply the translation + asc_transform_translate3f(temp, node->position); + asc_transform_apply(node->transform, temp, temp2); +} + void asc_scene_node_name(AscSceneNode *node, const char *name) { cx_strfree(&node->name); if (name == NULL) {
--- a/src/sprite.c Sun Jul 13 14:22:40 2025 +0200 +++ b/src/sprite.c Sun Jul 13 15:09:04 2025 +0200 @@ -133,9 +133,10 @@ node->destroy_func = asc_sprite_destroy; node->draw_func = asc_sprite_draw; - asc_transform_identity(node->transform); - asc_transform_translate3f(node->transform, - ASC_VEC3F(args.x, args.y, ASC_SCENE_2D_DEPTH_OFFSET)); + node->position = ASC_VEC3F(args.x, args.y, ASC_SCENE_2D_DEPTH_OFFSET); + node->origin = ASC_VEC3F(args.origin_x, args.origin_y, 0); + node->scale = ASC_VEC3F_1; + asc_mat4f_unit(node->rotation); asc_node_update(node); return node;
--- a/src/text.c Sun Jul 13 14:22:40 2025 +0200 +++ b/src/text.c Sun Jul 13 15:09:04 2025 +0200 @@ -86,9 +86,7 @@ return; } if (asc_test_flag(text->base.flags, ASC_TEXT_CENTERED_FLAG)) { - unsigned short newoffx = surface->w / 2; - asc_transform_translate2f(node->transform, ASC_VEC2F(text->offx - newoffx, 0)); - text->offx = newoffx; + asc_node_set_origin(node, ASC_VEC3F(surface->w / 2, 0, 0)); } // Transfer Image Data @@ -151,9 +149,9 @@ node->destroy_func = asc_text_destroy; node->update_func = asc_text_update; node->draw_func = asc_text_draw; - asc_transform_identity(node->transform); - asc_transform_translate3f(node->transform, - ASC_VEC3F(args.x, args.y, ASC_SCENE_2D_DEPTH_OFFSET)); + node->position = ASC_VEC3F(args.x, args.y, ASC_SCENE_2D_DEPTH_OFFSET); + node->scale = ASC_VEC3F_1; + asc_mat4f_unit(node->rotation); // text properties node->flags = args.alignment; // use flags variable to save some space @@ -165,6 +163,9 @@ } else { text->text = cx_mutstr(strdup(args.text)); } + if (args.centered) { + asc_set_flag(node->flags, ASC_TEXT_CENTERED_FLAG); + } // initialize texture // mesh will be created in the update func
--- a/test/snake/snake.c Sun Jul 13 14:22:40 2025 +0200 +++ b/test/snake/snake.c Sun Jul 13 15:09:04 2025 +0200 @@ -70,13 +70,10 @@ if (asc_test_flag(node->flags, ASC_SCENE_NODE_GRAPHICS_UPDATED) || asc_active_window->resized) { asc_vec2u bottom_right = asc_active_window->dimensions; asc_vec2u text_size = ((AscText*)node)->dimension; - asc_transform new_transform; - asc_transform_identity(new_transform); - asc_transform_translate2f(new_transform, ASC_VEC2F( + asc_node_set_position2f(node, ASC_VEC2F( (int) bottom_right.x - (int) text_size.width - 10, (int) bottom_right.y - (int) text_size.height - 10 )); - asc_node_transform_set(node, new_transform); } } @@ -125,7 +122,9 @@ .x = 250, .y = 300, .width = 64, - .height = 64 + .height = 64, + .origin_x = 32, + .origin_y = 32, ); asc_scene_add_node(MAIN_SCENE, sprite); return sprite; @@ -208,7 +207,7 @@ // player rotation if (asc_key_pressed(ASC_KEY(LEFT))) { - asc_transform_roll_origin(spaceship->transform, asc_rad(-90), ASC_VEC3F(32, 32, 0)); + asc_node_roll_deg(spaceship, -90); asc_node_update_transform(spaceship); }