RogueLife/src/render.c

193 lines
5.5 KiB
C
Raw Normal View History

#include "render.h"
2021-06-04 15:14:12 +02:00
#include "game.h"
2021-06-09 20:47:39 +02:00
#include "anim.h"
2021-06-04 15:14:12 +02:00
#include <gint/display.h>
//---
// Camera management
//---
2021-06-04 15:14:12 +02:00
void camera_init(camera_t *c, map_t const *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;
}
2021-06-04 15:14:12 +02:00
ipoint_t camera_map2screen(camera_t const *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 */
2021-06-04 15:14:12 +02:00
fpoint_t camera_screen2map(camera_t const *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. */
2021-06-04 15:14:12 +02:00
static bool camera_lock(camera_t *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 */
2021-06-04 15:14:12 +02:00
static void camera_bound(camera_t *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);
}
2021-06-04 15:14:12 +02:00
void camera_move(camera_t *c, map_coord_t dx, map_coord_t dy)
{
if(camera_lock(c)) return;
c->x += dx;
c->y += dy;
camera_bound(c);
}
2021-06-04 15:14:12 +02:00
void camera_zoom(camera_t *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);
}
2021-06-04 15:14:12 +02:00
fixed_t camera_ppu(camera_t const *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
//---
2021-06-02 16:45:02 +02:00
/* TODO: render_map(): Split into render_game() which takes the game
structure as input (including the player) */
2021-06-04 15:14:12 +02:00
void render_map(map_t const *m, camera_t const *c)
{
2021-06-02 09:38:59 +02:00
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);
2021-06-02 09:38:59 +02:00
/* Floor/wall layer */
2021-06-02 15:40:32 +02:00
if(t->base) dsubimage(p.x, p.y, &img_tileset_base,
TILE_WIDTH * (t->base % 16), TILE_HEIGHT * (t->base / 16),
TILE_WIDTH, TILE_HEIGHT, DIMAGE_NONE);
2021-06-02 09:38:59 +02:00
/* Decoration layer */
2021-06-02 15:40:32 +02:00
if(t->decor) dsubimage(p.x, p.y, &img_tileset_decor,
TILE_WIDTH * (t->decor % 16), TILE_HEIGHT * (t->decor / 16),
TILE_WIDTH, TILE_HEIGHT, DIMAGE_NONE);
}
2021-06-04 15:14:12 +02:00
}
void render_game(game_t const *g)
{
camera_t const *c = &g->camera;
render_map(&g->map, &g->camera);
/* Render entities */
2021-06-04 15:14:12 +02:00
for(int i = 0; i < g->entity_count; i++) {
entity_t *e = g->entities[i];
2021-06-04 15:14:12 +02:00
ipoint_t p = camera_map2screen(c, entity_pos(e));
2021-06-09 20:47:39 +02:00
/* Show entity sprite */
if(e->anim.frame) {
anim_frame_render(p.x, p.y, e->anim.frame);
}
/* Show entity hitbox in the map coordinate system */
if(!e->anim.frame || OVERLAY_HITBOXES) {
frect_t r = e->hitbox;
r = rect_scale(r, camera_ppu(c));
r = rect_translate(r, point_i2f(p));
rect_draw(r, e->color);
r = e->sprite;
r = rect_scale(r, camera_ppu(c));
r = rect_translate(r, point_i2f(p));
rect_draw(r, C_RGB(12, 12, 12));
2021-06-09 20:47:39 +02:00
/* Show entity center */
dline(p.x-1, p.y, p.x+1, p.y, e->color);
dline(p.x, p.y-1, p.x, p.y+1, e->color);
}
}
/* Render effect areas */
for(int i = 0; i < g->effect_area_count; i++) {
effect_area_t *ea = g->effect_areas[i];
ipoint_t anchor = camera_map2screen(c, ea->anchor);
if(ea->anim.frame) {
fpoint_t top_left = {
ea->anchor.x + ea->sprite.l,
ea->anchor.y + ea->sprite.t };
ipoint_t p = camera_map2screen(c, top_left);
anim_frame_render(p.x, p.y, ea->anim.frame);
}
if(!ea->anim.frame || OVERLAY_HITBOXES) {
frect_t r = ea->sprite;
r = rect_scale(r, camera_ppu(c));
r = rect_translate(r, point_i2f(anchor));
rect_draw(r, C_RGB(31, 31, 0));
2021-06-09 20:47:39 +02:00
}
}
}