101 lines
3.0 KiB
C++
101 lines
3.0 KiB
C++
|
|
#include "engine/projection.h"
|
|||
|
|
|
|||
|
|
#include <algorithm>
|
|||
|
|
#include <cstring>
|
|||
|
|
|
|||
|
|
namespace
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
struct ModeInfo
|
|||
|
|
{
|
|||
|
|
const char* name;
|
|||
|
|
int tileW, tileH, wallH;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Размеры подобраны так, чтобы плита занимала на экране сопоставимую площадь:
|
|||
|
|
// иначе смена вида меняла бы ещё и масштаб, и сравнить два вида было бы нельзя.
|
|||
|
|
const ModeInfo MODES[VIEW_MODE_COUNT] = {
|
|||
|
|
{"iso", TILE_W, TILE_H, WALL_H},
|
|||
|
|
{"topdown", TILE_MAX_W, TILE_MAX_H, TILE_MAX_H},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
} // namespace
|
|||
|
|
|
|||
|
|
const char* ViewModeName(ViewMode m)
|
|||
|
|
{
|
|||
|
|
const int i = int(m);
|
|||
|
|
return (i >= 0 && i < VIEW_MODE_COUNT) ? MODES[i].name : "?";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool ParseViewMode(const char* name, ViewMode& out)
|
|||
|
|
{
|
|||
|
|
if (!name) return false;
|
|||
|
|
for (int i = 0; i < VIEW_MODE_COUNT; ++i)
|
|||
|
|
if (std::strcmp(name, MODES[i].name) == 0)
|
|||
|
|
{
|
|||
|
|
out = ViewMode(i);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Projection Projection::For(ViewMode m)
|
|||
|
|
{
|
|||
|
|
const ModeInfo& info = MODES[int(m) < VIEW_MODE_COUNT ? int(m) : 0];
|
|||
|
|
Projection p;
|
|||
|
|
p.mode = m;
|
|||
|
|
p.tileW = info.tileW;
|
|||
|
|
p.tileH = info.tileH;
|
|||
|
|
p.wallH = info.wallH;
|
|||
|
|
return p;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Vec2 Projection::WorldToScreen(Vec2 w) const
|
|||
|
|
{
|
|||
|
|
if (mode == ViewMode::TOP_DOWN) return {w.x * float(tileW), w.y * float(tileH)};
|
|||
|
|
|
|||
|
|
// sx = (wx - wy) * tileW/2
|
|||
|
|
// sy = (wx + wy) * tileH/2
|
|||
|
|
return {(w.x - w.y) * float(tileW) * 0.5f, (w.x + w.y) * float(tileH) * 0.5f};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Vec2 Projection::ScreenToWorld(Vec2 s) const
|
|||
|
|
{
|
|||
|
|
if (mode == ViewMode::TOP_DOWN) return {s.x / float(tileW), s.y / float(tileH)};
|
|||
|
|
|
|||
|
|
const float a = s.x / float(tileW); // = (wx - wy) / 2
|
|||
|
|
const float b = s.y / float(tileH); // = (wx + wy) / 2
|
|||
|
|
return {b + a, b - a};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
float Projection::Depth(Vec2 w) const
|
|||
|
|
{
|
|||
|
|
// В изометрии «дальше от камеры» — это меньшая сумма координат; при виде
|
|||
|
|
// сверху глубины как таковой нет, и порядок задаёт только строка экрана.
|
|||
|
|
return (mode == ViewMode::TOP_DOWN) ? w.y : (w.x + w.y);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Projection::TileRow(int y, int& x0, int& x1) const
|
|||
|
|
{
|
|||
|
|
if (mode == ViewMode::TOP_DOWN)
|
|||
|
|
{
|
|||
|
|
x0 = 0;
|
|||
|
|
x1 = tileW;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Ступенька ромба — tileW/(tileH/2), то есть ровно наклон 2:1.
|
|||
|
|
const int half = std::max(1, tileH / 2);
|
|||
|
|
const int k = (y < half) ? y : (tileH - 1 - y);
|
|||
|
|
const int width = std::max(1, (tileW / half) * (k + 1));
|
|||
|
|
x0 = (tileW - width) / 2;
|
|||
|
|
x1 = x0 + width;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int Projection::WallAnchorY() const
|
|||
|
|
{
|
|||
|
|
// Изометрия: низ блока садится на ромб пола, поэтому крыша поднята над
|
|||
|
|
// центром плиты на высоту тайла. Вид сверху: подниматься некуда.
|
|||
|
|
return (mode == ViewMode::TOP_DOWN) ? tileH / 2 : tileH / 2 + tileH;
|
|||
|
|
}
|