76 lines
4.4 KiB
C
76 lines
4.4 KiB
C
|
|
#pragma once
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Проекция: как мир кладётся на экран.
|
|||
|
|
//
|
|||
|
|
// Это ЕДИНСТВЕННОЕ место, где движок знает про изометрию. Мир везде декартов
|
|||
|
|
// (тайл = 1.0 юнита), симуляция, ИИ и коллизии об этом файле не знают вообще —
|
|||
|
|
// правило «src/ai/ не знает про изометрию» этим и держится.
|
|||
|
|
//
|
|||
|
|
// Проекция задаёт четыре вещи, и больше ничего:
|
|||
|
|
// * куда мировая точка попадает на экран и обратно;
|
|||
|
|
// * чем сортируется список отрисовки (кто «ближе к камере»);
|
|||
|
|
// * какой формы плита пола — ромб или квадрат;
|
|||
|
|
// * насколько крыша стены поднята над своей плитой.
|
|||
|
|
//
|
|||
|
|
// Всё остальное — спрайты, свет, кровь, эффекты — считается ЧЕРЕЗ эти четыре
|
|||
|
|
// ответа и потому переживает смену вида. Абстракция с одной реализацией была бы
|
|||
|
|
// самообманом, поэтому режима сразу два: изометрия 2:1 и вид сверху.
|
|||
|
|
//
|
|||
|
|
// Вид выбирается ОДИН раз при старте (проектом или ключом --view) и дальше не
|
|||
|
|
// меняется: тайлсет рисуется под форму плиты, и смена вида на ходу означала бы
|
|||
|
|
// перерисовку всех спрайтов посреди кадра.
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
#include <cstdint>
|
|||
|
|
|
|||
|
|
#include "engine/config.h"
|
|||
|
|
#include "engine/math.h"
|
|||
|
|
|
|||
|
|
enum class ViewMode : uint8_t
|
|||
|
|
{
|
|||
|
|
ISO_2_1 = 0, // ромб 32x16, наклон 2:1 — классическая изометрия
|
|||
|
|
TOP_DOWN, // квадрат 32x32, вид строго сверху
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
constexpr int VIEW_MODE_COUNT = 2;
|
|||
|
|
|
|||
|
|
const char* ViewModeName(ViewMode m);
|
|||
|
|
// Разбор имени из project.txt или из ключа --view. false — имя не опознано,
|
|||
|
|
// out не тронут (звать с уже осмысленным значением по умолчанию).
|
|||
|
|
bool ParseViewMode(const char* name, ViewMode& out);
|
|||
|
|
|
|||
|
|
struct Projection
|
|||
|
|
{
|
|||
|
|
ViewMode mode = ViewMode::ISO_2_1;
|
|||
|
|
|
|||
|
|
int tileW = TILE_W; // размер плиты пола на экране
|
|||
|
|
int tileH = TILE_H;
|
|||
|
|
int wallH = WALL_H; // высота спрайта стены целиком
|
|||
|
|
|
|||
|
|
static Projection For(ViewMode m);
|
|||
|
|
|
|||
|
|
// Мир -> экран и обратно. Обратная строго обратна прямой: по ней считается
|
|||
|
|
// направление WASD, и рассогласование увело бы отряд не туда, куда жмут.
|
|||
|
|
Vec2 WorldToScreen(Vec2 w) const;
|
|||
|
|
Vec2 ScreenToWorld(Vec2 s) const;
|
|||
|
|
|
|||
|
|
// Ключ сортировки: чем больше, тем ближе к камере.
|
|||
|
|
float Depth(Vec2 w) const;
|
|||
|
|
|
|||
|
|
// Занятая часть строки y у плиты пола: ромб или вся строка.
|
|||
|
|
void TileRow(int y, int& x0, int& x1) const;
|
|||
|
|
|
|||
|
|
// Куда внутри спрайта стены попадает центр её плиты. В изометрии крыша
|
|||
|
|
// поднята над полом на высоту тайла, при виде сверху подниматься некуда.
|
|||
|
|
int WallAnchorY() const;
|
|||
|
|
|
|||
|
|
// Есть ли у стены видимые вертикальные грани. При виде сверху их нет, и
|
|||
|
|
// рисовать их значило бы показывать стену в двух проекциях сразу.
|
|||
|
|
bool WallHasSides() const { return mode == ViewMode::ISO_2_1; }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Активная проекция. Глобальная по той же причине, что и g_tune: её читают
|
|||
|
|
// рендер, редактор и генерация тайлов, и протаскивать её параметром через
|
|||
|
|
// каждый блит значило бы переписать половину рендера ради значения, которое
|
|||
|
|
// за весь запуск не меняется ни разу.
|
|||
|
|
inline Projection g_proj;
|