#pragma once // ----------------------------------------------------------------------------- // Слой отрисовки 2D: камера, список отрисовки, сортировка по глубине. // // Раньше файл назывался render/iso.h и считал изометрию прямо здесь. Теперь её // считает engine/projection.h, а тут остаётся то, что от вида не зависит: // собрать список, отсортировать, вылить в фреймбуфер. // // Мир везде декартов (тайл = 1.0 юнита). Симуляция, ИИ и коллизии об этом файле // не знают. // ----------------------------------------------------------------------------- #include #include #include "engine/config.h" #include "engine/framebuffer.h" #include "engine/math.h" #include "engine/projection.h" namespace v2d { inline Vec2 WorldToScreen(Vec2 w) { return g_proj.WorldToScreen(w); } inline Vec2 ScreenToWorld(Vec2 s) { return g_proj.ScreenToWorld(s); } // Ключ сортировки по глубине: чем больше, тем «ближе к камере». inline float Depth(Vec2 w) { return g_proj.Depth(w); } // Направление в ЭКРАННЫХ осях -> направление в мире (WASD, спек 5). inline Vec2 ScreenDirToWorld(Vec2 screenDir) { return Normalized(ScreenToWorld(screenDir)); } // Обратное: направление в МИРЕ -> направление в экранных осях. Нужно тому, кто // водит отряд не рукой, а кодом (автопилот балансового прогона): ввод игры // экранный, и подсовывать в него мировой вектор значило бы проверять не тот // путь, которым ходит живой игрок. inline Vec2 WorldDirToScreen(Vec2 worldDir) { const Vec2 a = g_proj.WorldToScreen(Vec2{0.0f, 0.0f}); const Vec2 b = g_proj.WorldToScreen(worldDir); return Normalized(Vec2{b.x - a.x, b.y - a.y}); } // Камера следует за якорем со сглаживанием; смещение округляется до пикселя. struct Camera { Vec2 pos{}; bool snapped = false; void SnapTo(Vec2 target) { pos = target; snapped = true; } void Follow(Vec2 target, float dt, float smooth) { if (!snapped) { SnapTo(target); return; } const float k = 1.0f - std::exp(-smooth * dt); pos = Lerp(pos, target, k); } void ScreenOffset(int& ox, int& oy) const { const Vec2 c = WorldToScreen(pos); ox = int(std::lround(INTERNAL_W * 0.5f - c.x)); oy = int(std::lround(INTERNAL_H * 0.5f - c.y)); } }; // Один элемент списка отрисовки: и стены, и сущности идут в общий список. struct DrawItem { float depth = 0.0f; const Sprite* spr = nullptr; int x = 0; int y = 0; uint32_t tint = 0; bool hasTint = false; // явный флаг: RGBA(0,0,0,0) — легальный цвет bool shaded = false; // рисовать через световую рампу ShadeQuad quad{}; }; inline void SortByDepth(std::vector& items) { std::stable_sort(items.begin(), items.end(), [](const DrawItem& a, const DrawItem& b) { return a.depth < b.depth; }); } inline void FlushDrawList(Framebuffer& fb, const std::vector& items) { for (const DrawItem& it : items) { if (!it.spr) continue; if (it.hasTint) fb.BlitTinted(*it.spr, it.x, it.y, it.tint); else if (it.shaded) fb.BlitShaded(*it.spr, it.x, it.y, it.quad); else fb.Blit(*it.spr, it.x, it.y); } } } // namespace v2d