#include "render.h" #include //--- // Camera management //--- void camera_init(struct camera *c, struct map *m) { c->zoom = 1; c->limits.x_min = fix(-CAMERA_BORDER); c->limits.x_max = fix(m->width + CAMERA_BORDER); c->limits.y_min = fix(-CAMERA_BORDER); c->limits.y_max = fix(m->height + CAMERA_BORDER); /* Fullscreen */ c->viewport.x_min = 0; c->viewport.x_max = DWIDTH; c->viewport.y_min = 0; c->viewport.y_max = DHEIGHT; c->width = fix(c->viewport.x_max - c->viewport.x_min) / TILE_WIDTH; c->height = fix(c->viewport.y_max - c->viewport.y_min) / TILE_HEIGHT; c->x = (c->limits.x_min + c->limits.x_max) / 2; c->y = (c->limits.y_min + c->limits.y_max) / 2; } ipoint_t camera_map2screen(struct camera *c, fpoint_t p) { return (ipoint_t){ .x = DWIDTH / 2 + fround((p.x - c->x) * TILE_WIDTH * c->zoom), .y = DHEIGHT / 2 + fround((p.y - c->y) * TILE_HEIGHT * c->zoom), }; } /* Translate screen coordinates to map coordinates */ fpoint_t camera_screen2map(struct camera *c, ipoint_t p) { return (fpoint_t){ .x = c->x + fix(p.x - DWIDTH / 2) / TILE_WIDTH / c->zoom, .y = c->y + fix(p.y - DHEIGHT / 2) / TILE_HEIGHT / c->zoom, }; } /* Lock the camera at the center if set by settings. */ static bool camera_lock(struct camera *c) { if(c->zoom == 1 && CAMERA_LOCK_AT_x1) { c->x = (c->limits.x_min + c->limits.x_max) / 2; c->y = (c->limits.y_min + c->limits.y_max) / 2; return true; } return false; } /* Bound the camera to the map limits */ static void camera_bound(struct camera *c) { /* Project top left and bottom-right corners of viewport onto the map */ ipoint_t v_tl = { c->viewport.x_min, c->viewport.y_min }; ipoint_t v_br = { c->viewport.x_max, c->viewport.y_max }; fpoint_t m_tl = camera_screen2map(c, v_tl); fpoint_t m_br = camera_screen2map(c, v_br); /* Bound viewport to map limits */ if(m_tl.x < c->limits.x_min) c->x += (c->limits.x_min - m_tl.x); else if(m_br.x > c->limits.x_max) c->x += (c->limits.x_max - m_br.x); if(m_tl.y < c->limits.y_min) c->y += (c->limits.y_min - m_tl.y); else if(m_br.y > c->limits.y_max) c->y += (c->limits.y_max - m_br.y); } void camera_move(struct camera *c, map_coord_t dx, map_coord_t dy) { if(camera_lock(c)) return; c->x += dx; c->y += dy; camera_bound(c); } void camera_zoom(struct camera *c, int zoom) { if(zoom < ZOOM_MIN) zoom = ZOOM_MIN; if(zoom > ZOOM_MAX) zoom = ZOOM_MAX; c->zoom = zoom; if(camera_lock(c)) return; camera_bound(c); } fixed_t camera_ppu(struct camera *c) { /* Since this assumes isotropic space we can use TILE_WIDTH or TILE_HEIGHT indifferently (they're assumed equal) */ return fix(1) * c->zoom * TILE_WIDTH; } //--- // Rendering //--- void render_map(struct map *m, struct camera *c) { extern bopti_image_t img_tileset_base; extern bopti_image_t img_tileset_decor; /* Render floor and walls */ for(int row = 0; row < m->height; row++) for(int col = 0; col < m->width; col++) { struct tile *t = map_tile(m, col, row); if(!t) continue; fpoint_t tile_pos = { fix(col), fix(row) }; ipoint_t p = camera_map2screen(c, tile_pos); /* Floor/wall layer */ dsubimage(p.x, p.y, &img_tileset_base, TILE_WIDTH * (t->base % 16), TILE_HEIGHT * (t->base / 16), TILE_WIDTH, TILE_HEIGHT, DIMAGE_NONE); /* Decoration layer */ dsubimage(p.x, p.y, &img_tileset_decor, TILE_WIDTH * (t->decor % 16), TILE_HEIGHT * (t->decor / 16), TILE_WIDTH, TILE_HEIGHT, DIMAGE_NONE); } /* Render entities */ for(int i = 0; i < m->entity_count; i++) { struct entity *e = m->entities[i]; ipoint_t p = camera_map2screen(c, (fpoint_t){ e->x, e->y }); /* Translate entity hitbox to the map coordinate system */ shape_t s = e->hitbox; shape_scale(&s, camera_ppu(c)); shape_translate(&s, point_i2f(p)); shape_draw(&s); /* Show entity center */ dline(p.x-1, p.y, p.x+1, p.y, s.color); dline(p.x, p.y-1, p.x, p.y+1, s.color); } }