# HG changeset patch # User Mike Becker # Date 1763330531 -3600 # Node ID 8796f03aac26b38e3c558f5f82220a0f1a6f1fa5 # Parent 359eaf2a8bd2f46f7c12d8822c2a07dbdc0f1618 make the camera follow the player + add limited vision diff -r 359eaf2a8bd2 -r 8796f03aac26 demo/snake/snake.c --- a/demo/snake/snake.c Sun Nov 16 22:00:13 2025 +0100 +++ b/demo/snake/snake.c Sun Nov 16 23:02:11 2025 +0100 @@ -58,10 +58,15 @@ static asc_vec2i directions[4]; typedef struct { + AscSceneNode *node; asc_color color; enum MoveDirection direction; enum MoveDirection target_direction; /** + * The number of tiles this player can look. + */ + float view_distance; + /** * The speed in tiles per second. */ float speed; @@ -473,6 +478,8 @@ ); asc_scene_add_node(MAIN_SCENE, node); Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); + player->node = node; + player->view_distance = 7.f; // start with 7 tiles player->speed = 3.f; // start with 3 tiles/sec player->number = 1; player->health = 100; @@ -491,8 +498,7 @@ return player; } -static asc_rect main_scene_viewport_update(asc_vec2u window_size) { - +static void main_scene_viewport_update(AscCamera *camera, asc_vec2u window_size) { // margins const unsigned margin = 16; @@ -505,7 +511,8 @@ // check if there is still a viewport left and chicken out when not if (window_size.width < rw || window_size.height < rh) { - return ASC_RECT(0, 0, 0, 0); + camera->viewport = ASC_RECT(0, 0, 0, 0); + return; } window_size.width -= rw; window_size.height -= rh; @@ -525,7 +532,30 @@ offset_y += margin; // Set the viewport to the scaled and centered region - return ASC_RECT(offset_x, offset_y, viewport_size, viewport_size); + camera->viewport = ASC_RECT(offset_x, offset_y, viewport_size, viewport_size); +} + +static void main_scene_camera_update(AscCamera *camera) { + // follow the player + // TODO: for hot seat / split screen updates we need to attach custom data to the camera + // s.t. the camera knows which player to follow + const Player *player = game.players[0]; + + const AscSceneNode *player_node = player->node; + const float view_distance = player->view_distance; + const float cam_view_distance = (1+view_distance) * GAME_FIELD_TILE_SIZE; + const float proj_size = 2 * cam_view_distance; + + // TODO: create camera API to avoid direct access to the matrices + + // update the projection matrix + asc_mat4f_ortho_update_size(camera->projection, proj_size, proj_size); + + // update the view matrix + asc_vec2f pos = asc_scene_node_get_position2f(player_node); + pos.x = -pos.x + cam_view_distance; + pos.y = -pos.y + cam_view_distance; + asc_transform_translate2f(camera->view, pos); } int main(void) { @@ -553,23 +583,19 @@ AscCamera backdrop_camera; asc_camera_init(&backdrop_camera, .type = ASC_CAMERA_ORTHO, - .projection_update_func = asc_camera_ortho_update_size + .viewport_update_func = asc_camera_ortho_update_size ); asc_scene_init(BACKDROP_SCENE, "backdrop", &backdrop_camera); backdrop_create(); // Initialize the main scene - const unsigned initial_view_distance = 10; AscCamera main_camera; asc_camera_init(&main_camera, .type = ASC_CAMERA_ORTHO, - .ortho.rect = ASC_RECT(0, 0, - initial_view_distance*2*GAME_FIELD_TILE_SIZE, - initial_view_distance*2*GAME_FIELD_TILE_SIZE - ), .viewport_clear = true, .clear_color = ASC_RGBi(0, 32, 16), - .viewport_update_func = main_scene_viewport_update + .viewport_update_func = main_scene_viewport_update, + .update_func = main_scene_camera_update ); asc_scene_init(MAIN_SCENE, "main", &main_camera); diff -r 359eaf2a8bd2 -r 8796f03aac26 src/ascension/camera.h --- a/src/ascension/camera.h Sun Nov 16 22:00:13 2025 +0100 +++ b/src/ascension/camera.h Sun Nov 16 23:02:11 2025 +0100 @@ -32,8 +32,8 @@ typedef struct asc_camera_s AscCamera; -typedef asc_rect(*asc_camera_viewport_update_func)(asc_vec2u window_size); -typedef void(*asc_camera_projection_update_func)(AscCamera*, asc_vec2u window_size); +typedef void(*asc_camera_viewport_update_func)(AscCamera*, asc_vec2u window_size); +typedef void(*asc_camera_update_func)(AscCamera*); struct asc_camera_s { asc_mat4f projection; @@ -43,13 +43,8 @@ * Will be updated every frame. */ asc_rect viewport; - /** - * Function that gets invoked whenever the window sized changed. - * Calculates the new drawing viewport. - * If @c NULL, the entire window will be used. - */ asc_camera_viewport_update_func viewport_update_func; - asc_camera_projection_update_func projection_update_func; + asc_camera_update_func update_func; asc_color clear_color; bool viewport_clear; }; @@ -70,8 +65,17 @@ TODO: implement } perspective;*/ }; + /** + * Function that only gets invoked when the window size changed. + * Implementations should update the @c viewport rectangle + * and may update the projection matrix as well. + */ asc_camera_viewport_update_func viewport_update_func; - asc_camera_projection_update_func projection_update_func; + /** + * Function that is invoked every frame. + * Example use: update the position of the camera to follow an object. + */ + asc_camera_update_func update_func; /** * Indicates whether the viewport for this camera shall be cleared before rendering. * The active drawing color will be used as the clear color (can be changed in the camera struct, afterward). @@ -88,26 +92,16 @@ /** - * Updates the camera with the new window viewport. - * - * This is automatically called when a window resizes. - * It should not be necessary to call this function manually. - * - * @param camera the camera - * @param window_size the new window size - */ -void asc_camera_update_viewport(AscCamera *camera, asc_vec2u window_size); - -/** * Shorter version of updating an orthographic camera which assumes the top right corner at (0,0). * * @attention The camera MUST have been initialized with asc_camera_ortho() at position (0,0). * - * @note This function can be used as an asc_camera_projection_update_func to keep the orthographic projection + * @note This function can be used as an asc_camera_viewport_update_func to keep the orthographic projection * aligned with the window size (useful for UIs or backdrops). * * @param camera the camera * @param size the new size + * @see asc_camera_ortho() */ void asc_camera_ortho_update_size(AscCamera *camera, asc_vec2u size); diff -r 359eaf2a8bd2 -r 8796f03aac26 src/ascension/scene_node.h --- a/src/ascension/scene_node.h Sun Nov 16 22:00:13 2025 +0100 +++ b/src/ascension/scene_node.h Sun Nov 16 23:02:11 2025 +0100 @@ -246,6 +246,12 @@ asc_scene_node_update_transform(node); } +static inline void asc_scene_node_set_position2f(AscSceneNode *node, asc_vec2f position) { + node->position.x = position.x; + node->position.y = position.y; + asc_scene_node_update_transform(node); +} + static inline void asc_scene_node_move(AscSceneNode *node, asc_vec3f offset) { node->position.x += offset.x; node->position.y += offset.y; @@ -253,6 +259,20 @@ asc_scene_node_update_transform(node); } +static inline void asc_scene_node_move2f(AscSceneNode *node, asc_vec2f offset) { + node->position.x += offset.x; + node->position.y += offset.y; + asc_scene_node_update_transform(node); +} + +static inline asc_vec3f asc_scene_node_get_position(const AscSceneNode *node) { + return node->position; +} + +static inline asc_vec2f asc_scene_node_get_position2f(const AscSceneNode *node) { + return ASC_VEC2F(node->position.x, node->position.y); +} + static inline void asc_scene_node_set_scale(AscSceneNode *node, asc_vec3f scale) { node->scale = scale; asc_scene_node_update_transform(node); @@ -263,12 +283,20 @@ asc_scene_node_update_transform(node); } -static inline void asc_scene_node_set_position2f(AscSceneNode *node, asc_vec2f position) { - node->position.x = position.x; - node->position.y = position.y; +static inline void asc_scene_node_set_origin2f(AscSceneNode *node, asc_vec2f origin) { + node->origin.x = origin.x; + node->origin.y = origin.y; asc_scene_node_update_transform(node); } +static inline asc_vec3f asc_scene_node_get_origin(const AscSceneNode *node) { + return node->origin; +} + +static inline asc_vec2f asc_scene_node_get_origin2f(const AscSceneNode *node) { + return ASC_VEC2F(node->origin.x, node->origin.y); +} + /** * Sets the z-index of a 2D scene node. * @@ -283,24 +311,12 @@ asc_scene_node_update_transform(node); } -static inline void asc_scene_node_move2f(AscSceneNode *node, asc_vec2f offset) { - node->position.x += offset.x; - node->position.y += offset.y; - asc_scene_node_update_transform(node); -} - static inline void asc_scene_node_set_scale2f(AscSceneNode *node, asc_vec2f scale) { node->scale.width = scale.width; node->scale.height = scale.height; asc_scene_node_update_transform(node); } -static inline void asc_scene_node_set_origin2f(AscSceneNode *node, asc_vec2f origin) { - node->origin.x = origin.x; - node->origin.y = origin.y; - asc_scene_node_update_transform(node); -} - static inline void asc_scene_node_set_rotation(AscSceneNode *node, asc_transform rotation) { memcpy(node->rotation, rotation, ASC_TRANSFORM_SIZE); asc_scene_node_update_transform(node); diff -r 359eaf2a8bd2 -r 8796f03aac26 src/camera.c --- a/src/camera.c Sun Nov 16 22:00:13 2025 +0100 +++ b/src/camera.c Sun Nov 16 23:02:11 2025 +0100 @@ -45,7 +45,7 @@ asc_error("Illegal argument for asc_camera_init(): type = %d", args.type); } camera->viewport_update_func = args.viewport_update_func; - camera->projection_update_func = args.projection_update_func; + camera->update_func = args.update_func; camera->viewport_clear = args.viewport_clear; camera->clear_color = args.clear_color; } @@ -60,17 +60,7 @@ } void asc_camera_ortho_update_size(AscCamera *camera, asc_vec2u size) { + camera->viewport.size = size; asc_mat4f_ortho_update_size(camera->projection, (float)size.width, (float)size.height); } -void asc_camera_update_viewport(AscCamera *camera, asc_vec2u window_size) { - if (camera->viewport_update_func == NULL) { - // this assumes the viewport was initialized with zeros! - camera->viewport.size = window_size; - } else { - camera->viewport = camera->viewport_update_func(window_size); - } - if (camera->projection_update_func != NULL) { - camera->projection_update_func(camera, window_size); - } -} diff -r 359eaf2a8bd2 -r 8796f03aac26 src/window.c --- a/src/window.c Sun Nov 16 22:00:13 2025 +0100 +++ b/src/window.c Sun Nov 16 23:02:11 2025 +0100 @@ -69,7 +69,7 @@ snprintf(ui_scene_name, sizeof(ui_scene_name), "Window %u UI", index); asc_camera_init(&window->ui_camera, .type = ASC_CAMERA_ORTHO, .ortho.rect = ASC_RECT(0, 0, window->dimensions.width, window->dimensions.height), - .projection_update_func = asc_camera_ortho_update_size); + .viewport_update_func = asc_camera_ortho_update_size); asc_scene_init(&window->ui, ui_scene_name, &window->ui_camera); asc_dprintf("Window %u initialized at index %u", window->id, index); asc_context.active_window = index; @@ -121,6 +121,21 @@ memset(window, 0, sizeof(AscWindow)); } +static void asc_window_update_viewport(AscCamera *camera, asc_vec2u window_size) { + if (camera->viewport_update_func == NULL) { + // this assumes the viewport was initialized with zeros! + camera->viewport.size = window_size; + } else { + camera->viewport_update_func(camera, window_size); + } +} + +static void asc_window_update_camera(AscCamera *camera) { + if (camera->update_func != NULL) { + camera->update_func(camera); + } +} + void asc_window_sync(unsigned int index) { if (index > ASC_MAX_WINDOWS) { asc_error("Index for syncing window out of bounds (%u/%u).", index, ASC_MAX_WINDOWS); @@ -146,12 +161,20 @@ if (window->resized) { for (unsigned int i = 0; i < ASC_MAX_SCENES; i++) { if (asc_scene_active(&window->scenes[i])) { - asc_camera_update_viewport(window->scenes[i].camera, window->dimensions); + asc_window_update_viewport(window->scenes[i].camera, window->dimensions); } } - asc_camera_update_viewport(window->ui.camera, window->dimensions); + asc_window_update_viewport(window->ui.camera, window->dimensions); } + // Execute camera updates + for (unsigned int i = 0; i < ASC_MAX_SCENES; i++) { + if (asc_scene_active(&window->scenes[i])) { + asc_window_update_camera(window->scenes[i].camera); + } + } + asc_window_update_camera(window->ui.camera); + // Execute all behaviors // TODO: this can eventually be parallelized for (unsigned int i = 0; i < ASC_MAX_SCENES; i++) {