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 }