94 lines
3.0 KiB
C
94 lines
3.0 KiB
C
|
|
#pragma once
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Изометрия существует ТОЛЬКО здесь и в рендере. Симуляция, ИИ и коллизии
|
|||
|
|
// работают в декартовом мире (top-down) и об этом файле не знают.
|
|||
|
|
//
|
|||
|
|
// sx = (wx - wy) * 16
|
|||
|
|
// sy = (wx + wy) * 8
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
#include <algorithm>
|
|||
|
|
#include <vector>
|
|||
|
|
|
|||
|
|
#include "core/math.h"
|
|||
|
|
#include "render/framebuffer.h"
|
|||
|
|
#include "tuning.h"
|
|||
|
|
|
|||
|
|
namespace iso
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
inline Vec2 WorldToScreen(Vec2 w)
|
|||
|
|
{
|
|||
|
|
return {(w.x - w.y) * float(TILE_HALF_W), (w.x + w.y) * float(TILE_HALF_H)};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
inline Vec2 ScreenToWorld(Vec2 s)
|
|||
|
|
{
|
|||
|
|
const float a = s.x / float(TILE_W); // = (wx - wy) / 2
|
|||
|
|
const float b = s.y / float(TILE_H); // = (wx + wy) / 2
|
|||
|
|
return {b + a, b - a};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Ключ сортировки по глубине: чем больше, тем «ближе к камере».
|
|||
|
|
inline float Depth(Vec2 w) { return w.x + w.y; }
|
|||
|
|
|
|||
|
|
// Направление в ЭКРАННЫХ осях -> направление в мире (WASD, спек 5).
|
|||
|
|
inline Vec2 ScreenDirToWorld(Vec2 screenDir)
|
|||
|
|
{
|
|||
|
|
return Normalized(ScreenToWorld(screenDir));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Камера следует за якорем со сглаживанием; смещение округляется до пикселя.
|
|||
|
|
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<DrawItem>& 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<DrawItem>& 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 iso
|