#include "render/scene.h" namespace { // Запас за краями кадра, чтобы не отсекать спрайты, торчащие внутрь. Считается // по САМОМУ БОЛЬШОМУ тайлу из возможных, а не по текущей проекции: это верхняя // оценка, и запас, взятый с избытком, стоит нескольких лишних блитов по краю — // заниженный стоит мигающих спрайтов на кромке кадра. constexpr int CULL_MARGIN_X = TILE_MAX_W; constexpr int CULL_MARGIN_Y = WALL_H + TILE_MAX_H; inline bool OffScreen(int sx, int sy) { return sx < -CULL_MARGIN_X || sx > INTERNAL_W + CULL_MARGIN_X || sy < -CULL_MARGIN_Y || sy > INTERNAL_H + CULL_MARGIN_Y; } } // namespace void Scene::Begin(const v2d::Camera& cam) { items.clear(); cam.ScreenOffset(ox, oy); } void Scene::Add(const Sprite* spr, Vec2 world, int pixelLift, float depthBias) { const Vec2 s = v2d::WorldToScreen(world); const int sx = int(std::lround(s.x)) + ox; const int sy = int(std::lround(s.y)) + oy - pixelLift; if (OffScreen(sx, sy)) return; v2d::DrawItem it; it.depth = v2d::Depth(world) + depthBias; it.spr = spr; it.x = sx; it.y = sy; items.push_back(it); } void Scene::AddShaded(const Sprite* spr, Vec2 world, const ShadeQuad& quad, int pixelLift, float depthBias) { const Vec2 s = v2d::WorldToScreen(world); const int sx = int(std::lround(s.x)) + ox; const int sy = int(std::lround(s.y)) + oy - pixelLift; if (OffScreen(sx, sy)) return; v2d::DrawItem it; it.depth = v2d::Depth(world) + depthBias; it.spr = spr; it.x = sx; it.y = sy; it.shaded = true; it.quad = quad; items.push_back(it); } void Scene::Flush(Framebuffer& fb) { v2d::SortByDepth(items); v2d::FlushDrawList(fb, items); } // Ниже этого уровня рампа даёт ступень 0, то есть чистый чёрный. static const float DARK_CUTOFF = 0.5f / float(RAMP_LEVELS - 1); void DrawFloor(Framebuffer& fb, const SpriteSet& sprites, const Tilemap& map, int ox, int oy, const Lighting& light, const BloodField* blood) { for (int ty = 0; ty < MAP_H; ++ty) { for (int tx = 0; tx < MAP_W; ++tx) { if (map.IsWall(tx, ty)) continue; // под стеной пол не виден if (light.MaxCornerOf(tx, ty) < DARK_CUTOFF) continue; const Vec2 c = v2d::WorldToScreen({tx + 0.5f, ty + 0.5f}); const int sx = int(std::lround(c.x)) + ox; const int sy = int(std::lround(c.y)) + oy; if (OffScreen(sx, sy)) continue; const Sprite* stained = blood ? blood->At(tx, ty) : nullptr; const Sprite& s = stained ? *stained : sprites.FloorTile(tx, ty, map.ThemeAt(tx, ty)); fb.BlitShaded(s, sx, sy, light.QuadFor(tx, ty, g_proj.tileH - 1)); } } } void CollectWalls(Scene& scene, const SpriteSet& sprites, const Tilemap& map, const Lighting& light) { for (int ty = 0; ty < MAP_H; ++ty) for (int tx = 0; tx < MAP_W; ++tx) { if (!map.IsWall(tx, ty)) continue; if (light.MaxCornerOf(tx, ty) < DARK_CUTOFF) continue; // clampRow = TILE_H-1: вертикальные грани светятся как нижняя кромка // крыши, а не как экстраполяция патча на 32 строки вниз. scene.AddShaded(&sprites.WallTile(tx, ty, map.ThemeAt(tx, ty)), {tx + 0.5f, ty + 0.5f}, light.QuadFor(tx, ty, g_proj.tileH - 1)); } }