Files
EVEngine/src/modules/grid/GridProjection.cpp
T

200 lines
6.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "grid/GridProjection.h"
#include <algorithm>
#include <cmath>
#include <utility>
namespace eve::grid {
namespace {
bool staggerDoShift(const GridConfig &cfg, int cx, int cy) {
const int major = (cfg.staggerAxis == StaggerAxis::Y) ? cy : cx;
const bool odd = (major & 1) != 0;
return cfg.staggerIndex == StaggerIndex::Odd ? odd : !odd;
}
// odd-r offset(staggerAxis=Y, odd)pointy-top hex <-> cube。
void offsetToCube(int col, int row, int &x, int &y, int &z) {
x = col - (row - (row & 1)) / 2;
y = row;
z = -x - y;
}
void cubeToOffset(int x, int y, int &col, int &row) {
row = y;
col = x + (row - (row & 1)) / 2;
}
} // namespace
void cellToWorld(const GridConfig &cfg, int cx, int cy, float &px, float &py) {
const float tw = cellPitchX(cfg);
const float th = cellPitchY(cfg);
switch (cfg.layout) {
case GridLayout::Rectangle:
px = cfg.originX + float(cx) * tw;
py = cfg.originY + float(cy) * th;
break;
case GridLayout::Isometric:
case GridLayout::IsometricZAsY:
// 菱形投影(IsometricZAsY 在 v1 与 Isometric 同构,Z-as-Y 由平面轴承担)。
px = cfg.originX + float(cx - cy) * tw * 0.5f;
py = cfg.originY + float(cx + cy) * th * 0.5f;
break;
case GridLayout::Staggered:
case GridLayout::Hexagon: {
const bool hex = cfg.layout == GridLayout::Hexagon && cfg.hexSideLength > 0.f;
if (cfg.staggerAxis == StaggerAxis::Y) {
const float pitchY = hex ? (th + cfg.hexSideLength) * 0.5f : th * 0.5f;
px = cfg.originX + float(cx) * tw +
(staggerDoShift(cfg, cx, cy) ? tw * 0.5f : 0.f);
py = cfg.originY + float(cy) * pitchY;
} else {
const float pitchX = hex ? (tw + cfg.hexSideLength) * 0.5f : tw * 0.5f;
px = cfg.originX + float(cx) * pitchX;
py = cfg.originY + float(cy) * th +
(staggerDoShift(cfg, cx, cy) ? th * 0.5f : 0.f);
}
break;
}
}
}
float cellToDepthY(const GridConfig &cfg, int cx, int cy) {
float px = 0.f, py = 0.f;
cellToWorld(cfg, cx, cy, px, py);
return py + cfg.cellH;
}
void worldToCell(const GridConfig &cfg, float px, float py, int &cx, int &cy, int mapW,
int mapH) {
switch (cfg.layout) {
case GridLayout::Rectangle: {
const float lx = px - cfg.originX;
const float ly = py - cfg.originY;
const float tw = cfg.cellW > 0.f ? cellPitchX(cfg) : 1.f;
const float th = cfg.cellH > 0.f ? cellPitchY(cfg) : 1.f;
cx = int(std::floor(lx / tw));
cy = int(std::floor(ly / th));
break;
}
case GridLayout::Isometric:
case GridLayout::IsometricZAsY: {
const float lx = px - cfg.originX;
const float ly = py - cfg.originY;
const float tw = cfg.cellW > 0.f ? cellPitchX(cfg) * 0.5f : 1.f;
const float th = cfg.cellH > 0.f ? cellPitchY(cfg) * 0.5f : 1.f;
const float a = lx / tw;
const float b = ly / th;
cx = int(std::floor((b + a) * 0.5f));
cy = int(std::floor((b - a) * 0.5f));
break;
}
default: {
// 最近格中心(staggered / hex)。
cx = 0;
cy = 0;
float best = 1e30f;
const int w = std::max(1, mapW);
const int h = std::max(1, mapH);
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
float wx = 0.f, wy = 0.f;
cellToWorld(cfg, x, y, wx, wy);
wx += cfg.cellW * 0.5f;
wy += cfg.cellH * 0.5f;
const float dx = wx - px;
const float dy = wy - py;
const float d = dx * dx + dy * dy;
if (d < best) {
best = d;
cx = x;
cy = y;
}
}
}
break;
}
}
}
void foreachRotatedFootprint(int w, int h, const std::vector<uint8_t> &mask, int steps,
bool hexMode, const std::function<void(int lx, int ly)> &fn) {
if (!fn) return;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
// 1. 收集实心局部格。
std::vector<std::pair<int, int>> solid;
for (int ly = 0; ly < h; ++ly) {
for (int lx = 0; lx < w; ++lx) {
bool solidCell = true;
if (!mask.empty()) {
const size_t idx = size_t(ly) * size_t(w) + size_t(lx);
solidCell = idx < mask.size() && mask[idx] != 0;
}
if (solidCell) solid.emplace_back(lx, ly);
}
}
if (solid.empty()) return;
// 2. 旋转到归一化局部坐标。
std::vector<std::pair<int, int>> rotated;
rotated.reserve(solid.size());
int minX = 1 << 30, minY = 1 << 30;
for (const auto &c : solid) {
int rx = c.first;
int ry = c.second;
if (hexMode) {
int x = 0, y = 0, z = 0;
offsetToCube(c.first, c.second, x, y, z);
const int s = ((steps % 6) + 6) % 6;
for (int i = 0; i < s; ++i) {
const int nx = -z;
const int ny = -x;
const int nz = -y;
x = nx;
y = ny;
z = nz;
}
cubeToOffset(x, y, rx, ry);
} else {
const int q = ((steps % 4) + 4) % 4;
switch (q) {
case 1: // 90° CCW
rx = c.second;
ry = w - 1 - c.first;
break;
case 2: // 180°
rx = w - 1 - c.first;
ry = h - 1 - c.second;
break;
case 3: // 270° CCW / 90° CW
rx = h - 1 - c.second;
ry = c.first;
break;
default:
break;
}
}
rotated.emplace_back(rx, ry);
minX = std::min(minX, rx);
minY = std::min(minY, ry);
}
for (const auto &c : rotated) fn(c.first - minX, c.second - minY);
}
void rotatedFootprintSize(int w, int h, const std::vector<uint8_t> &mask, int steps,
bool hexMode, int &outW, int &outH) {
int maxX = -1;
int maxY = -1;
foreachRotatedFootprint(w, h, mask, steps, hexMode, [&](int lx, int ly) {
maxX = std::max(maxX, lx);
maxY = std::max(maxY, ly);
});
outW = maxX + 1;
outH = maxY + 1;
}
} // namespace eve::grid