phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

render.c (10522B)


      1 #include "engine/render.h"
      2 
      3 #include <math.h>
      4 #include <stdio.h>
      5 
      6 #include "config.h"
      7 
      8 void
      9 ph_camera_follow_player(PhCamera *camera, const PhWorld *world)
     10 {
     11 	const PhEntity *player = ph_world_player(world);
     12 	float max_x = fmaxf(0.0f, world->area.width * PH_TILE_SIZE - camera->viewport_w);
     13 	float max_y = fmaxf(0.0f, world->area.height * PH_TILE_SIZE - camera->viewport_h);
     14 
     15 	camera->pos.x = player ? fminf(max_x,
     16 		fmaxf(0.0f, player->pos.x - camera->viewport_w * 0.5f)) : 0.0f;
     17 	camera->pos.y = player ? fminf(max_y,
     18 		fmaxf(0.0f, player->pos.y - camera->viewport_h * 0.5f)) : 0.0f;
     19 }
     20 
     21 static float
     22 ph_world_pixel(float position, float camera)
     23 {
     24 	return floorf(position + 0.5f) - floorf(camera + 0.5f);
     25 }
     26 
     27 void
     28 ph_render_sprite(PhRenderer *renderer, int asset_id,
     29 	int sprite_tile_x, int sprite_tile_y, PhRect dst, PhFlip flip)
     30 {
     31 	PhRect src = {
     32 		.x = (float)(sprite_tile_x * PH_TILE_SIZE),
     33 		.y = (float)(sprite_tile_y * PH_TILE_SIZE),
     34 		.w = (float)PH_TILE_SIZE,
     35 		.h = (float)PH_TILE_SIZE,
     36 	};
     37 
     38 	ph_render_texture(renderer, asset_id, src, dst, flip);
     39 }
     40 
     41 void
     42 ph_render_entity_frame(const PhEntityDef *def, const PhEntity *entity,
     43 	int *sprite_tile_x, int *sprite_tile_y, PhFlip *flip)
     44 {
     45 	int direction = entity->motion.facing_y > 0 ? PH_SPRITE_DOWN :
     46 		entity->motion.facing_y < 0 ? PH_SPRITE_UP : PH_SPRITE_SIDE;
     47 	int frame = ph_entity_animation_frame(def, entity);
     48 
     49 	*sprite_tile_x = def->sprite_tiles[direction][frame][0];
     50 	*sprite_tile_y = def->sprite_tiles[direction][frame][1];
     51 	*flip = direction == PH_SPRITE_SIDE && entity->motion.facing_x > 0 ?
     52 		PH_FLIP_HORIZONTAL : PH_FLIP_NONE;
     53 }
     54 
     55 static void
     56 ph_draw_area(PhRenderer *renderer, const PhWorld *world, const PhCamera *camera)
     57 {
     58 	int tx;
     59 	int ty;
     60 
     61 	for (ty = (int)(camera->pos.y / PH_TILE_SIZE);
     62 			ty < world->area.height && (float)(ty * PH_TILE_SIZE) <
     63 			camera->pos.y + camera->viewport_h; ++ty) {
     64 		for (tx = (int)(camera->pos.x / PH_TILE_SIZE);
     65 				tx < world->area.width && (float)(tx * PH_TILE_SIZE) <
     66 				camera->pos.x + camera->viewport_w; ++tx) {
     67 			const PhTileDef *def = ph_area_tile_def(&world->area, tx, ty);
     68 			int asset_id;
     69 			PhRect rect = {
     70 				.x = ph_world_pixel((float)(tx * PH_TILE_SIZE), camera->pos.x),
     71 				.y = ph_world_pixel((float)(ty * PH_TILE_SIZE), camera->pos.y),
     72 				.w = (float)PH_TILE_SIZE,
     73 				.h = (float)PH_TILE_SIZE,
     74 			};
     75 			PhRect src;
     76 			int sprite_tile_x;
     77 			int sprite_tile_y;
     78 
     79 			if (!def) continue;
     80 			if (def->background_asset_id) {
     81 				int background = def->background_asset_id;
     82 				PhRect background_src = {
     83 					.x = (float)(def->background_sprite_tile_x * PH_TILE_SIZE),
     84 					.y = (float)(def->background_sprite_tile_y * PH_TILE_SIZE),
     85 					.w = (float)PH_TILE_SIZE,
     86 					.h = (float)PH_TILE_SIZE,
     87 				};
     88 
     89 				if (ph_render_has_asset(renderer, background))
     90 					ph_render_texture(renderer, background, background_src, rect, PH_FLIP_NONE);
     91 			}
     92 			asset_id = def->asset_id;
     93 			if (!ph_render_has_asset(renderer, asset_id)) continue;
     94 			sprite_tile_x = def->sprite_tile_x;
     95 			sprite_tile_y = def->sprite_tile_y;
     96 			if (def->autotile == PH_TILE_AUTOTILE_3X3) {
     97 				const PhTileDef *north = ph_area_tile_def(&world->area, tx, ty - 1);
     98 				const PhTileDef *east = ph_area_tile_def(&world->area, tx + 1, ty);
     99 				const PhTileDef *south = ph_area_tile_def(&world->area, tx, ty + 1);
    100 				const PhTileDef *west = ph_area_tile_def(&world->area, tx - 1, ty);
    101 				int joins_north = north && north->symbol == def->symbol;
    102 				int joins_east = east && east->symbol == def->symbol;
    103 				int joins_south = south && south->symbol == def->symbol;
    104 				int joins_west = west && west->symbol == def->symbol;
    105 
    106 				sprite_tile_x += joins_west == joins_east ? 1 : joins_west ? 2 : 0;
    107 				sprite_tile_y += joins_north == joins_south ? 1 : joins_north ? 2 : 0;
    108 			} else if (def->autotile == PH_TILE_AUTOTILE_FIXED &&
    109 					def->autotile_width > 0 && def->autotile_height > 0) {
    110 				const PhTileDef *neighbor;
    111 				int offset_x = 0;
    112 				int offset_y = 0;
    113 
    114 				while (offset_x < def->autotile_width - 1 &&
    115 						(neighbor = ph_area_tile_def(&world->area,
    116 						tx - offset_x - 1, ty)) &&
    117 						neighbor->autotile == def->autotile &&
    118 						neighbor->asset_id == def->asset_id &&
    119 						neighbor->sprite_tile_x == def->sprite_tile_x &&
    120 						neighbor->sprite_tile_y == def->sprite_tile_y &&
    121 						neighbor->autotile_width == def->autotile_width &&
    122 						neighbor->autotile_height == def->autotile_height) ++offset_x;
    123 				while (offset_y < def->autotile_height - 1 &&
    124 						(neighbor = ph_area_tile_def(&world->area,
    125 						tx, ty - offset_y - 1)) &&
    126 						neighbor->autotile == def->autotile &&
    127 						neighbor->asset_id == def->asset_id &&
    128 						neighbor->sprite_tile_x == def->sprite_tile_x &&
    129 						neighbor->sprite_tile_y == def->sprite_tile_y &&
    130 						neighbor->autotile_width == def->autotile_width &&
    131 						neighbor->autotile_height == def->autotile_height) ++offset_y;
    132 				sprite_tile_x += offset_x;
    133 				sprite_tile_y += offset_y;
    134 			}
    135 			src = (PhRect){
    136 				.x = (float)(sprite_tile_x * PH_TILE_SIZE),
    137 				.y = (float)(sprite_tile_y * PH_TILE_SIZE),
    138 				.w = (float)PH_TILE_SIZE,
    139 				.h = (float)PH_TILE_SIZE,
    140 			};
    141 
    142 			ph_render_texture(renderer, asset_id, src, rect, PH_FLIP_NONE);
    143 		}
    144 	}
    145 }
    146 
    147 static void
    148 ph_draw_items(PhRenderer *renderer, const PhWorld *world, const PhCamera *camera)
    149 {
    150 	int i;
    151 
    152 	for (i = 0; i < world->ground_item_count; ++i) {
    153 		const PhGroundItem *drop = &world->ground_items[i];
    154 		const PhItemDef *def = ph_world_item_def(world, drop->item_id);
    155 		int asset_id = -1;
    156 		PhRect rect;
    157 
    158 		if (!drop->active || drop->area_id != world->area_id) {
    159 			continue;
    160 		}
    161 
    162 		rect.x = ph_world_pixel(drop->pos.x, camera->pos.x) - 3.0f;
    163 		rect.y = ph_world_pixel(drop->pos.y, camera->pos.y) - 3.0f;
    164 		rect.w = 6.0f;
    165 		rect.h = 6.0f;
    166 
    167 		if (def) asset_id = def->asset_id;
    168 		if (ph_render_has_asset(renderer, asset_id) &&
    169 				def->sprite_tile_x >= 0 && def->sprite_tile_y >= 0) {
    170 			rect.x = ph_world_pixel(drop->pos.x, camera->pos.x) -
    171 				PH_TILE_SIZE * 0.5f;
    172 			rect.y = ph_world_pixel(drop->pos.y, camera->pos.y) -
    173 				PH_TILE_SIZE * 0.5f;
    174 			rect.w = PH_TILE_SIZE;
    175 			rect.h = PH_TILE_SIZE;
    176 			ph_render_sprite(renderer, asset_id,
    177 				def->sprite_tile_x, def->sprite_tile_y, rect, PH_FLIP_NONE);
    178 		} else {
    179 			ph_render_color(renderer, (PhColor){ PH_ITEM_COLOR });
    180 			ph_render_fill(renderer, rect);
    181 		}
    182 	}
    183 }
    184 
    185 static void
    186 ph_draw_entities(PhRenderer *renderer, const PhWorld *world, const PhCamera *camera)
    187 {
    188 	int i;
    189 
    190 	for (i = 0; i < world->entity_count; ++i) {
    191 		const PhEntity *entity = &world->entities[i];
    192 		const PhEntityDef *def = ph_world_entity_def(world, entity->type_id);
    193 		int asset_id = -1;
    194 		PhRect rect;
    195 		int sprite_tile_x;
    196 		int sprite_tile_y;
    197 		PhFlip flip;
    198 
    199 		if (!entity->active || entity->area_id != world->area_id || !def) {
    200 			continue;
    201 		}
    202 
    203 		asset_id = def->asset_id;
    204 		ph_render_entity_frame(def, entity, &sprite_tile_x, &sprite_tile_y, &flip);
    205 		if (ph_render_has_asset(renderer, asset_id) &&
    206 				sprite_tile_x >= 0 && sprite_tile_y >= 0) {
    207 
    208 			rect.x = ph_world_pixel(entity->pos.x, camera->pos.x) -
    209 				PH_TILE_SIZE * 0.5f;
    210 			rect.y = ph_world_pixel(entity->pos.y, camera->pos.y) -
    211 				PH_TILE_SIZE * 0.75f;
    212 			rect.w = PH_TILE_SIZE;
    213 			rect.h = PH_TILE_SIZE;
    214 
    215 			ph_render_sprite(renderer, asset_id, sprite_tile_x, sprite_tile_y, rect, flip);
    216 		} else {
    217 			rect.x = ph_world_pixel(entity->pos.x, camera->pos.x) - 7.0f;
    218 			rect.y = ph_world_pixel(entity->pos.y, camera->pos.y) - 12.0f;
    219 			rect.w = 14.0f;
    220 			rect.h = 18.0f;
    221 
    222 			if (def->kind == PH_ENTITY_MONSTER) {
    223 				ph_render_color(renderer, (PhColor){ PH_MONSTER_COLOR });
    224 			} else {
    225 				ph_render_color(renderer, (PhColor){ PH_NPC_COLOR });
    226 			}
    227 			ph_render_fill(renderer, rect);
    228 		}
    229 	}
    230 }
    231 
    232 void
    233 ph_render_world(PhRenderer *renderer,
    234 	const PhWorld *world, const PhCamera *camera)
    235 {
    236 	ph_draw_area(renderer, world, camera);
    237 	ph_draw_items(renderer, world, camera);
    238 	ph_draw_entities(renderer, world, camera);
    239 }
    240 
    241 static void
    242 ph_debug_radius(PhRenderer *renderer, PhVec2 pos, const PhCamera *camera)
    243 {
    244 	float x = ph_world_pixel(pos.x, camera->pos.x);
    245 	float y = ph_world_pixel(pos.y, camera->pos.y);
    246 	int radius = PH_ENTITY_BLOCK_RADIUS;
    247 	int offset;
    248 
    249 	if (x + radius < 0 || y + radius < 0 ||
    250 			x - radius >= camera->viewport_w || y - radius >= camera->viewport_h) return;
    251 	/* The collision test uses distance between centers, not sprite rectangles. */
    252 	for (offset = -radius; offset <= radius; ++offset) {
    253 		float edge = floorf(sqrtf((float)(radius * radius - offset * offset)));
    254 
    255 		ph_render_fill(renderer, (PhRect){ x - edge, y + offset, 1, 1 });
    256 		ph_render_fill(renderer, (PhRect){ x + edge, y + offset, 1, 1 });
    257 		ph_render_fill(renderer, (PhRect){ x + offset, y - edge, 1, 1 });
    258 		ph_render_fill(renderer, (PhRect){ x + offset, y + edge, 1, 1 });
    259 	}
    260 }
    261 
    262 void
    263 ph_render_world_debug(PhRenderer *renderer, const PhWorld *world,
    264 	const PhCamera *camera)
    265 {
    266 	int x, y, i;
    267 
    268 	ph_render_color(renderer, (PhColor){ PH_DEBUG_BLOCK_COLOR });
    269 	for (y = (int)(camera->pos.y / PH_TILE_SIZE); y < world->area.height &&
    270 			y * PH_TILE_SIZE < camera->pos.y + camera->viewport_h; ++y)
    271 		for (x = (int)(camera->pos.x / PH_TILE_SIZE); x < world->area.width &&
    272 				x * PH_TILE_SIZE < camera->pos.x + camera->viewport_w; ++x)
    273 			if (ph_area_tile_blocked(&world->area, x, y))
    274 				ph_render_outline(renderer, (PhRect){
    275 					ph_world_pixel((float)(x * PH_TILE_SIZE), camera->pos.x),
    276 					ph_world_pixel((float)(y * PH_TILE_SIZE), camera->pos.y),
    277 					PH_TILE_SIZE, PH_TILE_SIZE });
    278 	for (i = 0; i < world->entity_count; ++i) {
    279 		const PhEntity *entity = &world->entities[i];
    280 		const PhEntityDef *def = ph_world_entity_def(world, entity->type_id);
    281 		int blocks;
    282 
    283 		if (!entity->active || entity->area_id != world->area_id || !def) continue;
    284 		blocks = def->blocks_movement || i == world->player_index;
    285 		if (blocks) {
    286 			ph_render_color(renderer, (PhColor){ PH_DEBUG_ACTOR_COLOR });
    287 			ph_debug_radius(renderer, entity->pos, camera);
    288 		}
    289 		ph_render_color(renderer, (PhColor){ PH_DEBUG_TARGET_COLOR });
    290 		if (blocks && entity->motion.moving) ph_debug_radius(renderer, entity->motion.target, camera);
    291 		if (i != world->player_index) {
    292 			float tx = ph_world_pixel(entity->motion.target.x, camera->pos.x);
    293 			float ty = ph_world_pixel(entity->motion.target.y, camera->pos.y);
    294 			char label[16];
    295 
    296 			if (tx < 0 || ty < 0 || tx >= camera->viewport_w || ty >= camera->viewport_h) continue;
    297 			ph_render_outline(renderer, (PhRect){ tx - 2, ty - 2, 5, 5 });
    298 			snprintf(label, sizeof(label), "%d", i);
    299 			ph_render_text(renderer, tx + 4, ty - 4, label);
    300 		}
    301 	}
    302 }