improve snap-to-grid-movement

Mon, 21 Jul 2025 21:28:34 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 21 Jul 2025 21:28:34 +0200
changeset 217
4b3c974eab44
parent 216
943980fa37b5
child 218
2ead0699ce77

improve snap-to-grid-movement

src/ascension/datatypes.h file | annotate | diff | comparison | revisions
test/snake/snake.c file | annotate | diff | comparison | revisions
--- a/src/ascension/datatypes.h	Sun Jul 20 23:31:40 2025 +0200
+++ b/src/ascension/datatypes.h	Mon Jul 21 21:28:34 2025 +0200
@@ -233,6 +233,14 @@
     }
 }
 
+static inline int asc_sgn(int x) {
+    return x < 0 ? -1 : 1;
+}
+
+static inline int asc_sgnf(float x) {
+    return asc_fround_zero(x) < 0 ? -1 : 1;
+}
+
 /**
  * Returns the cosine of x.
  *
@@ -298,6 +306,22 @@
 //   Vector Functions
 // --------------------------------------------------------------------------
 
+static inline asc_vec2i asc_vec2_ftoi(asc_vec2f v) {
+    return ASC_VEC2I(v.x, v.y);
+}
+
+static inline asc_vec2f asc_vec2_itof(asc_vec2i v) {
+    return ASC_VEC2F(v.x, v.y);
+}
+
+static inline asc_vec2u asc_vec2_ftou(asc_vec2f v) {
+    return ASC_VEC2U(v.x, v.y);
+}
+
+static inline asc_vec2f asc_vec2_utof(asc_vec2u v) {
+    return ASC_VEC2F(v.x, v.y);
+}
+
 static inline asc_vec2f asc_vec2f_scale(asc_vec2f v, float s) {
     return ASC_VEC2F(v.x*s, v.y*s);
 }
--- a/test/snake/snake.c	Sun Jul 20 23:31:40 2025 +0200
+++ b/test/snake/snake.c	Mon Jul 21 21:28:34 2025 +0200
@@ -52,7 +52,7 @@
 };
 
 static asc_transform rotations[4];
-static asc_vec2f directions[4];
+static asc_vec2i directions[4];
 
 typedef struct {
     enum MoveDirection direction;
@@ -63,7 +63,7 @@
     float speed;
 } Spaceship;
 
-static const unsigned game_field_size = 512;
+static const unsigned game_field_size = 16;
 static const unsigned game_field_tile_size = 32;
 
 static void init_globals(void) {
@@ -71,10 +71,10 @@
     asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90));
     asc_transform_roll(rotations[MOVE_RIGHT], asc_rad(90));
     asc_transform_roll(rotations[MOVE_DOWN], asc_rad(180));
-    directions[MOVE_UP] = ASC_VEC2F(0, -1);
-    directions[MOVE_LEFT] = ASC_VEC2F(-1, 0);
-    directions[MOVE_DOWN] = ASC_VEC2F(0, 1);
-    directions[MOVE_RIGHT] = ASC_VEC2F(1, 0);
+    directions[MOVE_UP] = ASC_VEC2I(0, -1);
+    directions[MOVE_LEFT] = ASC_VEC2I(-1, 0);
+    directions[MOVE_DOWN] = ASC_VEC2I(0, 1);
+    directions[MOVE_RIGHT] = ASC_VEC2I(1, 0);
 }
 
 static void destroy_textures(void) {
@@ -153,29 +153,39 @@
 static void move_spaceship(AscBehavior *behavior) {
     AscSceneNode *node = behavior->node;
     Spaceship *spaceship = node->user_data;
-    const float ts = (float) game_field_tile_size;
-    const float speed = ts * spaceship->speed * asc_context.frame_factor;
-    const asc_vec2f dvec = directions[spaceship->direction];
-    const asc_vec2f movement = asc_vec2f_scale(dvec, speed);
+    const int ts = (int) game_field_tile_size;
+    const float fts = (float) ts;
+    const float speed = fts * spaceship->speed * asc_context.frame_factor;
+    const asc_vec2i dir = directions[spaceship->direction];
+    const asc_vec2f movement = asc_vec2f_scale(asc_vec2_itof(dir), speed);
+    // check if we are supposed to change the direction
     if (spaceship->direction == spaceship->target_direction) {
-        // no change of direction - keep moving
+        // move without changing the direction
         asc_scene_node_move2f(node, movement);
     } else {
-        // identify the tile we are currently in
-        int x = (int) (node->position.x / ts);
-        int y = (int) (node->position.y / ts);
-        const asc_vec2f tcenter = ASC_VEC2F(x*ts, y*ts);
-        // calculate the (squared) distance to the tile's center
-        float cdist = asc_vec2f_sqrlen(asc_vec2f_sub(tcenter, ASC_VEC2F(node->position.x, node->position.y)));
-        // if it is less than squared length of the movement vector, snap to the center and rotate
-        // TODO: think about if we should allow changing direction only when we haven't passed the center yet
-        if (cdist < asc_vec2f_sqrlen(dvec)) {
-            asc_scene_node_set_position2f(node, tcenter);
+        // determine axis
+        // and check if we are about to cross the center
+        // this relies on positive positions!
+        bool rotate = false;
+        if (movement.x == 0) {
+            // vertical movement
+            const int y0 = (int)node->position.y / ts;
+            const int y1 = (int)(node->position.y+movement.y) / ts;
+            rotate = y0 != y1 && asc_sgn(y1-y0) == dir.y;
+        } else {
+            // horizontal movement
+            const int x0 = (int)node->position.x / ts;
+            const int x1 = (int)(node->position.x+movement.x) / ts;
+            rotate = x0 != x1 && asc_sgn(x1-x0) == dir.x;
+        }
+        if (rotate) {
+            // snap position to the center of the tile
+            node->position.x = roundf(node->position.x / fts) * fts;
+            node->position.y = roundf(node->position.y / fts) * fts;
             spaceship->direction = spaceship->target_direction;
             asc_scene_node_set_rotation(node, rotations[spaceship->direction]);
-            // TODO: this is losing some speed - improve the calculation and add partial movement after rotation
         } else {
-            // too far away from center, continue normal movement
+            // changing the direction not permitted, yet, continue movement
             asc_scene_node_move2f(node, movement);
         }
     }
@@ -203,10 +213,10 @@
 static void create_gamefield() {
     // TODO: create a proper data structure and a more interesting map than just a basic grid
     AscSceneNode *gamefield = asc_scene_node_empty();
-    for (unsigned x = 0; x < game_field_size; x+=game_field_tile_size) {
-        for (unsigned y = 0; y < game_field_size; y+=game_field_tile_size) {
+    for (unsigned x = 1; x <= game_field_size; x++) {
+        for (unsigned y = 1; y <= game_field_size; y++) {
             AscSceneNode *tile = asc_rectangle(
-                .x = x, .y = y, .filled = true, .thickness = 1,
+                .x = x*game_field_tile_size, .y = y*game_field_tile_size, .filled = true, .thickness = 1,
                 .origin_x = game_field_tile_size / 2, .origin_y = game_field_tile_size / 2,
                 .width = game_field_tile_size, .height = game_field_tile_size,
                 .color = ASC_RGB(0, 128, 255),
@@ -270,11 +280,9 @@
     );
     asc_scene_init(MAIN_SCENE,
         .type = ASC_CAMERA_ORTHO,
-        .ortho.rect = ASC_RECT(
-            -game_field_tile_size,
-            -game_field_tile_size,
-            game_field_size+game_field_tile_size,
-            game_field_size+game_field_tile_size
+        .ortho.rect = ASC_RECT(0, 0,
+            (game_field_size+1)*game_field_tile_size,
+            (game_field_size+1)*game_field_tile_size
         ),
         .viewport_clear = true,
         .clear_color = ASC_RGB(0, 128, 90),
@@ -306,6 +314,7 @@
         }
 
         // player rotation
+        // TODO: queue up to two movement commands (for sharp 90° turns)
         if (asc_key_pressed(ASC_KEY(LEFT))) {
             if (spaceship->direction != MOVE_RIGHT) {
                 spaceship->target_direction = MOVE_LEFT;

mercurial