11#include <unordered_set>
18constexpr float kPi = 3.14159265358979323846f;
19constexpr float kExploredMask = 0.35f;
22enum class RadiusMetric : uint8_t { Euclidean, Chebyshev, Manhattan };
23enum class Mode : uint8_t { Grid2D, Heightmap, Volume };
24enum class Topology : uint8_t { Ortho, Hex };
25enum class CellState : uint8_t {
Unknown = 0, Explored = 1, Visible = 2 };
27Algorithm parseAlgorithm(
const std::string &
name, Algorithm fallback) {
28 if (
name ==
"shadowcast")
return Algorithm::Shadowcast;
29 if (
name ==
"raycast")
return Algorithm::Raycast;
30 if (
name ==
"permissive")
return Algorithm::Permissive;
31 if (
name ==
"rectangle")
return Algorithm::Rectangle;
35std::string algorithmName(Algorithm
a) {
37 case Algorithm::Raycast:
39 case Algorithm::Permissive:
41 case Algorithm::Rectangle:
43 case Algorithm::Shadowcast:
49Mode parseMode(
const std::string &
name, Mode fallback) {
50 if (
name ==
"grid2d")
return Mode::Grid2D;
51 if (
name ==
"heightmap")
return Mode::Heightmap;
52 if (
name ==
"volume")
return Mode::Volume;
56std::string modeName(Mode
m) {
68RadiusMetric parseMetric(
const std::string &
name, RadiusMetric fallback) {
69 if (
name ==
"euclidean")
return RadiusMetric::Euclidean;
70 if (
name ==
"chebyshev")
return RadiusMetric::Chebyshev;
71 if (
name ==
"manhattan")
return RadiusMetric::Manhattan;
75std::string metricName(RadiusMetric
m) {
77 case RadiusMetric::Euclidean:
79 case RadiusMetric::Chebyshev:
81 case RadiusMetric::Manhattan:
87std::string topologyName(Topology
t) {
return t == Topology::Hex ?
"hex" :
"ortho"; }
89void offsetToCube(
int x,
int y,
int &
q,
int &
r,
int &
s) {
91 q =
x - (
y - (
y & 1)) / 2;
96void cubeToOffset(
int q,
int r,
int &
x,
int &
y) {
98 x =
q + (
r - (
r & 1)) / 2;
101int cubeDistance(
int q0,
int r0,
int s0,
int q1,
int r1,
int s1) {
102 return std::max({std::abs(q0 - q1), std::abs(r0 - r1), std::abs(s0 - s1)});
105int hexDistance(
int x0,
int y0,
int x1,
int y1) {
106 int q0, r0, s0, q1, r1, s1;
107 offsetToCube(x0, y0, q0, r0, s0);
108 offsetToCube(x1, y1, q1, r1, s1);
109 return cubeDistance(q0, r0, s0, q1, r1, s1);
112bool inRadiusOrtho(RadiusMetric metric,
int dx,
int dy,
int radius) {
113 if (
radius < 0)
return false;
114 const int adx = std::abs(
dx);
115 const int ady = std::abs(
dy);
117 case RadiusMetric::Chebyshev:
118 return std::max(adx, ady) <=
radius;
119 case RadiusMetric::Manhattan:
120 return adx + ady <=
radius;
121 case RadiusMetric::Euclidean:
127bool inRadius3Ortho(RadiusMetric metric,
int dx,
int dy,
int dz,
int radius) {
128 if (
radius < 0)
return false;
129 const int adx = std::abs(
dx);
130 const int ady = std::abs(
dy);
131 const int adz = std::abs(
dz);
133 case RadiusMetric::Chebyshev:
134 return std::max({adx, ady, adz}) <=
radius;
135 case RadiusMetric::Manhattan:
136 return adx + ady + adz <=
radius;
137 case RadiusMetric::Euclidean:
139 return adx * adx + ady * ady + adz * adz <=
radius *
radius;
143float angleDiffDeg(
float a,
float b) {
145 while (
d > 180.f)
d -= 360.f;
146 while (
d < -180.f)
d += 360.f;
150bool inCone(
int ox,
int oy,
int x,
int y,
bool useCone,
float facingDeg,
float halfAngleDeg) {
151 if (!useCone || halfAngleDeg >= 180.f)
return true;
152 if (
x ==
ox &&
y ==
oy)
return true;
153 const float ang = std::atan2(
float(
y -
oy),
float(
x -
ox)) * (180.f / kPi);
154 return std::fabs(angleDiffDeg(
ang, facingDeg)) <= halfAngleDeg;
157uint8_t stateToMaskByte(CellState
s) {
159 case CellState::Visible:
161 case CellState::Explored:
162 return uint8_t(kExploredMask * 255.f + 0.5f);
163 case CellState::Unknown:
169float stateToMaskValue(CellState
s) {
171 case CellState::Visible:
173 case CellState::Explored:
174 return kExploredMask;
175 case CellState::Unknown:
181float normalizeAngle(
float a) {
182 while (
a <= -kPi)
a += 2.f * kPi;
183 while (
a > kPi)
a -= 2.f * kPi;
187constexpr int kMultXX[8] = {1, 0, 0, -1, -1, 0, 0, 1};
188constexpr int kMultXY[8] = {0, 1, -1, 0, 0, -1, 1, 0};
189constexpr int kMultYX[8] = {0, 1, 1, 0, 0, -1, -1, 0};
190constexpr int kMultYY[8] = {1, 0, 0, 1, -1, 0, 0, -1};
192constexpr int kCubeDirs[6][3] = {{1, -1, 0}, {1, 0, -1}, {0, 1, -1}, {-1, 1, 0}, {-1, 0, 1}, {0, -1, 1}};
225 RadiusMetric
metric = RadiusMetric::Euclidean;
260 return std::max(0,
r.radius + bonus);
270 return hexDistance(
ox,
oy,
x,
y) + std::abs(
z - oz) <=
radius;
277 const auto o =
layer->config()->orientation;
288 state.assign(
n, CellState::Unknown);
297 auto cfg =
layer->config();
298 resize(cfg->mapW, cfg->mapH, 1);
299 if (
mode == Mode::Volume)
mode = Mode::Grid2D;
306 auto cfg =
layer->config();
308 auto tileset =
layer->tileset();
310 resize(cfg->mapW, cfg->mapH, 1);
314 for (
int i = 0; i <
n; ++i) {
315 const uint32_t gid = (i < int(
tiles->gids.size())) ?
tileGid(
tiles->gids[
size_t(i)]) : 0
u;
320 std::find_if(tileset->visuals.begin(), tileset->visuals.end(),
322 if (visual != tileset->visuals.end())
isOpaque =
isOpaque || visual->opaque;
350 if (idx < 0 || idx >=
int(
state.size()))
return;
351 if (
state[
size_t(
idx)] != CellState::Visible) {
352 state[size_t(
idx)] = CellState::Visible;
370 state[size_t(
idx)] = CellState::Explored;
377 std::fill(
state.begin(),
state.end(), CellState::Unknown);
384 if (
r.id ==
id)
return &
r;
391 if (
r.id ==
id)
return &
r;
396 bool losBresenham2(
int x0,
int y0,
int x1,
int y1,
int zSlice,
bool applyHeight)
const {
397 int dx = std::abs(x1 - x0);
398 int dy = std::abs(y1 - y0);
399 const int sx = x0 < x1 ? 1 : -1;
400 const int sy = y0 < y1 ? 1 : -1;
407 if (
x == x1 &&
y == y1)
return true;
408 const int e2 = 2 * err;
419 const bool atEnd = (
nx == x1 &&
ny == y1);
421 if (applyHeight &&
mode == Mode::Heightmap && !atEnd) {
434 bool losHexCube(
int x0,
int y0,
int x1,
int y1,
int zSlice,
bool applyHeight)
const {
435 int q0, r0, s0, q1, r1, s1;
436 offsetToCube(x0, y0, q0, r0, s0);
437 offsetToCube(x1, y1, q1, r1, s1);
438 const int n = cubeDistance(q0, r0, s0, q1, r1, s1);
439 if (
n == 0)
return true;
441 for (
int i = 1; i <=
n; ++i) {
442 const float t = float(i) / float(
n);
443 const float qf = float(q0) + (float(q1) - float(q0)) *
t;
444 const float rf = float(r0) + (float(r1) - float(r0)) *
t;
445 const float sf = float(s0) + (float(s1) - float(s0)) *
t;
447 int rq = int(std::lround(qf));
448 int rr = int(std::lround(rf));
449 int rs = int(std::lround(sf));
450 const float qdiff = std::fabs(rq - qf);
451 const float rdiff = std::fabs(rr - rf);
452 const float sdiff = std::fabs(rs - sf);
453 if (qdiff > rdiff && qdiff > sdiff) rq = -rr - rs;
454 else if (rdiff > sdiff) rr = -rq - rs;
458 cubeToOffset(rq, rr,
x,
y);
459 const bool atEnd = (i ==
n);
461 if (applyHeight &&
mode == Mode::Heightmap && !atEnd) {
468 bool los2(
int x0,
int y0,
int x1,
int y1,
int zSlice,
bool applyHeight)
const {
469 if (
topology == Topology::Hex)
return losHexCube(x0, y0, x1, y1, zSlice, applyHeight);
474 if (
mode != Mode::Heightmap)
return true;
475 return los2(
ox,
oy, tx, ty, 0,
true);
478 void tryMark2(
int ox,
int oy,
int x,
int y,
int zSlice,
bool useCone,
float facingDeg,
479 float halfAngleDeg,
int radius) {
482 if (!inCone(
ox,
oy,
x,
y, useCone, facingDeg, halfAngleDeg))
return;
489 int xy,
int yx,
int yy,
int zSlice,
bool useCone,
float facingDeg,
490 float halfAngleDeg) {
491 if (startSlope < endSlope)
return;
492 float newStart = 0.f;
493 for (
int j = row; j <=
radius; ++j) {
496 bool blocked =
false;
499 const int mapX =
ox +
dx * xx +
dy * xy;
500 const int mapY =
oy +
dx * yx +
dy * yy;
501 const float leftSlope = (float(
dx) - 0.5f) / (
float(
dy) + 0.5f);
502 const float rightSlope = (float(
dx) + 0.5f) / (
float(
dy) - 0.5f);
503 if (startSlope < rightSlope)
continue;
504 if (endSlope > leftSlope)
break;
508 newStart = rightSlope;
512 startSlope = newStart;
515 castLight(
ox,
oy,
radius, j + 1, startSlope, leftSlope, xx, xy, yx, yy, zSlice,
516 useCone, facingDeg, halfAngleDeg);
517 newStart = rightSlope;
527 for (
int oct = 0; oct < 8; ++oct) {
528 castLight(
r.x,
r.y,
radius, 1, 1.f, 0.f, kMultXX[oct], kMultXY[oct], kMultYX[oct],
529 kMultYY[oct], zSlice,
r.useCone,
r.facingDeg,
r.halfAngleDeg);
534 const float px = float(
x) + ((
y & 1) ? 0.5f : 0.f);
535 const float py = float(
y) * 0.86602540378f;
536 const float opx = float(
ox) + ((
oy & 1) ? 0.5f : 0.f);
537 const float opy = float(
oy) * 0.86602540378f;
538 return std::atan2(
py - opy,
px - opx);
543 float mid = normalizeAngle(0.5f * (a0 + a1));
545 if (std::fabs(a1 - a0) > kPi)
mid = normalizeAngle(
mid + kPi);
546 for (
const auto &sh : shadows) {
547 float s = sh.start, e = sh.end;
549 if (
mid >=
s &&
mid <= e)
return true;
551 if (
mid >=
s ||
mid <= e)
return true;
557 void addAngleShadow(std::vector<AngleShadow> &shadows,
float a0,
float a1)
const {
558 a0 = normalizeAngle(a0);
559 a1 = normalizeAngle(a1);
564 float diff = normalizeAngle(
end -
start);
565 if (diff < 0.f) std::swap(
start,
end);
572 std::vector<AngleShadow> shadows;
576 offsetToCube(
r.x,
r.y, cq, cr, cs);
578 for (
int ring = 1; ring <=
radius; ++ring) {
580 int q = cq + kCubeDirs[4][0] * ring;
581 int rr = cr + kCubeDirs[4][1] * ring;
582 int s = cs + kCubeDirs[4][2] * ring;
586 cubeToOffset(
q, rr,
x,
y);
588 inCone(
r.x,
r.y,
x,
y,
r.useCone,
r.facingDeg,
r.halfAngleDeg)) {
591 (ring <= 0) ? kPi : (0.55f / float(ring));
592 const float a0 = normalizeAngle(
ang - half);
593 const float a1 = normalizeAngle(
ang + half);
602 q += kCubeDirs[
side][0];
603 rr += kCubeDirs[
side][1];
604 s += kCubeDirs[
side][2];
616 void castRayLine(
int ox,
int oy,
int tx,
int ty,
int zSlice,
bool useCone,
float facingDeg,
617 float halfAngleDeg,
int radius) {
620 int q0, r0, s0, q1, r1, s1;
621 offsetToCube(
ox,
oy, q0, r0, s0);
622 offsetToCube(tx, ty, q1, r1, s1);
623 const int n = std::max(1, cubeDistance(q0, r0, s0, q1, r1, s1));
624 for (
int i = 0; i <=
n; ++i) {
625 const float t = float(i) / float(
n);
626 int rq = int(std::lround(
float(q0) + (
float(q1) -
float(q0)) *
t));
627 int rr = int(std::lround(
float(r0) + (
float(r1) -
float(r0)) *
t));
631 cubeToOffset(rq, rr,
x,
y);
637 int dx = std::abs(tx -
ox);
638 int dy = std::abs(ty -
oy);
639 const int sx =
ox < tx ? 1 : -1;
640 const int sy =
oy < ty ? 1 : -1;
646 if (
x == tx &&
y == ty)
break;
648 const int e2 = 2 * err;
665 offsetToCube(
r.x,
r.y, cq, cr, cs);
666 for (
int ring = 1; ring <=
radius; ++ring) {
667 int q = cq + kCubeDirs[4][0] * ring;
668 int rr = cr + kCubeDirs[4][1] * ring;
672 cubeToOffset(
q, rr,
x,
y);
675 q += kCubeDirs[
side][0];
676 rr += kCubeDirs[
side][1];
699 if (
dx == 0 &&
dy == 0)
continue;
700 const int tx =
r.x +
dx;
701 const int ty =
r.y +
dy;
704 if (!inCone(
r.x,
r.y, tx, ty,
r.useCone,
r.facingDeg,
r.halfAngleDeg))
continue;
705 bool see =
los2(
r.x,
r.y, tx, ty, zSlice,
mode == Mode::Heightmap);
707 const int sx = (
dx > 0) ? -1 : (
dx < 0 ? 1 : 0);
708 const int sy = (
dy > 0) ? -1 : (
dy < 0 ? 1 : 0);
709 const int cands[3][2] = {{tx +
sx, ty}, {tx, ty +
sy}, {tx +
sx, ty +
sy}};
710 for (
auto &
c : cands) {
713 if (
ax == tx &&
ay == ty)
continue;
731 const int yMin = std::max(0,
oy -
radius);
734 const int bh = yMax - yMin + 1;
735 std::vector<uint8_t> used(
size_t(bw * bh), 0);
736 auto usedAt = [&](
int x,
int y) -> uint8_t & {
737 return used[size_t((
y - yMin) * bw + (
x -
xMin))];
740 std::vector<Rect> rects;
741 for (
int y = yMin;
y <= yMax; ++
y) {
743 if (usedAt(
x,
y))
continue;
753 while (grow && y1 + 1 <= yMax) {
754 for (
int xx =
x; xx <= x1; ++xx) {
763 for (
int yy =
y; yy <= y1; ++yy)
764 for (
int xx =
x; xx <= x1; ++xx) usedAt(xx, yy) = 1;
765 rects.push_back(
Rect{
x,
y, x1, y1});
773 std::vector<uint8_t> lit(
size_t(
width *
height), 0);
774 auto litAt = [&](
int x,
int y) -> uint8_t & {
return lit[size_t(
index2(
x,
y))]; };
779 if (!inCone(
r.x,
r.y,
x,
y,
r.useCone,
r.facingDeg,
r.halfAngleDeg))
continue;
786 const float ox = float(
r.x) + 0.5f;
787 const float oy = float(
r.y) + 0.5f;
788 std::sort(rects.begin(), rects.end(), [&](
const Rect &
a,
const Rect &
b) {
789 const int acx = (a.x0 + a.x1) / 2;
790 const int acy = (a.y0 + a.y1) / 2;
791 const int bcx = (b.x0 + b.x1) / 2;
792 const int bcy = (b.y0 + b.y1) / 2;
793 return inRadius2(r.x, r.y, acx, acy, radius) &&
794 (std::abs(acx - r.x) + std::abs(acy - r.y)) <
795 (std::abs(bcx - r.x) + std::abs(bcy - r.y));
798 for (
const auto &rc : rects) {
800 const float corners[4][2] = {
801 {float(rc.x0), float(rc.y0)},
802 {float(rc.x1) + 1.f, float(rc.y0)},
803 {float(rc.x0), float(rc.y1) + 1.f},
804 {float(rc.x1) + 1.f, float(rc.y1) + 1.f},
807 for (
int i = 0; i < 4; ++i) {
808 angles[i] = std::atan2(corners[i][1] -
oy, corners[i][0] -
ox);
810 float aMin = angles[0], aMax = angles[0];
811 for (
int i = 1; i < 4; ++i) {
812 const float dMin = normalizeAngle(angles[i] - aMin);
813 const float dMax = normalizeAngle(angles[i] - aMax);
814 if (dMin < 0.f) aMin = angles[i];
815 if (dMax > 0.f) aMax = angles[i];
817 const float nearest2 =
818 float(std::min({(rc.x0 -
r.x) * (rc.x0 -
r.x) + (rc.y0 -
r.y) * (rc.y0 -
r.y),
819 (rc.x1 -
r.x) * (rc.x1 -
r.x) + (rc.y0 -
r.y) * (rc.y0 -
r.y),
820 (rc.x0 -
r.x) * (rc.x0 -
r.x) + (rc.y1 -
r.y) * (rc.y1 -
r.y),
821 (rc.x1 -
r.x) * (rc.x1 -
r.x) + (rc.y1 -
r.y) * (rc.y1 -
r.y)}));
825 if (!litAt(
x,
y))
continue;
827 if (
x >= rc.x0 && x <= rc.x1 && y >= rc.y0 &&
y <= rc.y1)
continue;
828 const float d2 = float((
x -
r.x) * (
x -
r.x) + (
y -
r.y) * (
y -
r.y));
829 if (d2 <= nearest2 + 0.01f)
continue;
830 const float ang = std::atan2(
float(
y) + 0.5f -
oy,
float(
x) + 0.5f -
ox);
832 if (aMin <= aMax)
inside = (
ang >= aMin &&
ang <= aMax);
835 if (normalizeAngle(aMax - aMin) < 0.f) {
836 inside = normalizeAngle(
ang - aMin) >= 0.f ||
837 normalizeAngle(aMax -
ang) >= 0.f;
839 const float da = normalizeAngle(
ang - aMin);
840 const float span = normalizeAngle(aMax - aMin);
841 inside = da >= 0.f && da <= span;
856 if (
mode != Mode::Volume)
return;
861 if (
state[
size_t(
idx)] != CellState::Visible)
continue;
865 if (z < 0 || z >=
depth)
break;
876 if (!
r.enabled)
return;
879 if (
mode == Mode::Volume) {
886 case Algorithm::Raycast:
889 case Algorithm::Permissive:
892 case Algorithm::Rectangle:
895 case Algorithm::Shadowcast:
918Fov &
Fov::operator=(
Fov &&) noexcept = default;
926 impl_->layer =
nullptr;
927 if (impl_->mode == Mode::Volume) impl_->mode = Mode::Grid2D;
932 impl_->layer =
nullptr;
933 impl_->mode = Mode::Volume;
942 const Mode next = parseMode(
name, impl_->mode);
944 if (next != Mode::Volume && impl_->depth != 1) {
945 impl_->resize(impl_->width, impl_->height, 1);
953 impl_->algorithm = parseAlgorithm(
name, impl_->algorithm);
960 impl_->metric = parseMetric(
name, impl_->metric);
967 if (
name ==
"auto") {
968 impl_->topologyManual =
false;
969 impl_->applyAutoTopologyFromLayer();
970 if (!impl_->layer) impl_->topology = Topology::Ortho;
971 }
else if (
name ==
"hex" ||
name ==
"hexagonal" ||
name ==
"staggered") {
972 impl_->topologyManual =
true;
973 impl_->topology = Topology::Hex;
974 }
else if (
name ==
"ortho" ||
name ==
"orthogonal") {
975 impl_->topologyManual =
true;
976 impl_->topology = Topology::Ortho;
984 impl_->cornerPeek = enable;
991 impl_->opaqueGids.insert(uint32_t(gid));
992 if (impl_->layer) impl_->syncFromLayer();
993 else impl_->dirty =
true;
998 impl_->opaqueGids.erase(uint32_t(gid));
999 if (impl_->layer) impl_->syncFromLayer();
1000 else impl_->dirty =
true;
1004 impl_->opaqueGids.clear();
1005 if (impl_->layer) impl_->syncFromLayer();
1006 else impl_->dirty =
true;
1010 impl_->blockEmpty = enable;
1011 if (impl_->layer) impl_->syncFromLayer();
1012 else impl_->dirty =
true;
1018 if (!impl_->inBounds2(
x,
y))
return;
1019 if (impl_->mode == Mode::Volume) {
1020 if (!impl_->inBounds3(
x,
y, 0))
return;
1021 impl_->opaque[size_t(impl_->index3(
x,
y, 0))] = opaque ? 1u : 0
u;
1023 impl_->opaque[size_t(impl_->index2(
x,
y))] = opaque ? 1u : 0
u;
1025 impl_->dirty =
true;
1029 if (impl_->mode == Mode::Volume)
return impl_->cellOpaque3(
x,
y, 0);
1030 return impl_->cellOpaque2(
x,
y);
1034 if (!impl_->inBounds3(
x,
y,
z))
return;
1035 impl_->opaque[size_t(impl_->index3(
x,
y,
z))] = opaque ? 1u : 0
u;
1036 impl_->dirty =
true;
1044 if (!impl_->inBounds2(
x,
y))
return;
1045 impl_->elevation[size_t(impl_->index2(
x,
y))] = elev;
1046 impl_->dirty =
true;
1052 impl_->cliffBlock = delta;
1053 impl_->dirty =
true;
1058 impl_->eyeOffset =
offset;
1059 impl_->dirty =
true;
1064 impl_->verticalRange =
range;
1065 impl_->dirty =
true;
1073 r.
id = impl_->nextRevealerId++;
1078 impl_->revealers.push_back(
r);
1079 impl_->dirty =
true;
1084 auto &
v = impl_->revealers;
1085 v.erase(std::remove_if(
v.begin(),
v.end(), [
id](
const Impl::Revealer &
r) { return r.id == id; }),
1087 impl_->dirty =
true;
1091 impl_->revealers.clear();
1092 impl_->dirty =
true;
1100 if (
auto *
r = impl_->findRevealer(
id)) {
1104 impl_->dirty =
true;
1109 if (
auto *
r = impl_->findRevealer(
id)) {
1111 impl_->dirty =
true;
1116 if (
auto *
r = impl_->findRevealer(
id)) {
1118 r->facingDeg = facingDeg;
1119 r->halfAngleDeg = std::max(0.f, halfAngleDeg);
1120 impl_->dirty =
true;
1125 if (
auto *
r = impl_->findRevealer(
id)) {
1127 r->halfAngleDeg = 180.f;
1128 impl_->dirty =
true;
1133 if (
auto *
r = impl_->findRevealer(
id)) {
1135 impl_->dirty =
true;
1142 if (
auto *
r = impl_->findRevealer(
id)) {
1143 r->perception = perception;
1144 impl_->dirty =
true;
1149 if (
const auto *
r = impl_->findRevealerConst(
id))
return r->perception;
1154 impl_->perceptionRadiusScale =
scale;
1155 impl_->dirty =
true;
1163 if (
const auto *
r = impl_->findRevealerConst(
id))
return impl_->effectiveRadiusOf(*
r);
1168 if (impl_->mode == Mode::Volume)
return canDetect3(revealerId,
x,
y, 0, targetStealth);
1169 const auto *
r = impl_->findRevealerConst(revealerId);
1170 if (!
r || !
r->enabled)
return false;
1172 return (
r->perception + impl_->detectionMargin) >= targetStealth;
1176 const auto *
r = impl_->findRevealerConst(revealerId);
1177 if (!
r || !
r->enabled)
return false;
1179 return (
r->perception + impl_->detectionMargin) >= targetStealth;
1187 if (impl_->mode == Mode::Volume)
return isVisible3(
x,
y, 0);
1188 if (!impl_->inBounds2(
x,
y))
return false;
1189 return impl_->state[size_t(impl_->index2(
x,
y))] == CellState::Visible;
1193 if (impl_->mode == Mode::Volume)
return isExplored3(
x,
y, 0);
1194 if (!impl_->inBounds2(
x,
y))
return false;
1195 const auto s = impl_->state[size_t(impl_->index2(
x,
y))];
1196 return s == CellState::Explored ||
s == CellState::Visible;
1200 if (!impl_->inBounds3(
x,
y,
z))
return false;
1201 return impl_->state[size_t(impl_->index3(
x,
y,
z))] == CellState::Visible;
1205 if (!impl_->inBounds3(
x,
y,
z))
return false;
1206 const auto s = impl_->state[size_t(impl_->index3(
x,
y,
z))];
1207 return s == CellState::Explored ||
s == CellState::Visible;
1211 if (impl_->mode == Mode::Volume)
return getState3(
x,
y, 0);
1212 if (!impl_->inBounds2(
x,
y))
return "unknown";
1213 switch (impl_->state[
size_t(impl_->index2(
x,
y))]) {
1214 case CellState::Visible:
1216 case CellState::Explored:
1224 if (!impl_->inBounds3(
x,
y,
z))
return "unknown";
1225 switch (impl_->state[
size_t(impl_->index3(
x,
y,
z))]) {
1226 case CellState::Visible:
1228 case CellState::Explored:
1238 impl_->resetVisibleKeepExplored();
1239 impl_->dirty =
true;
1244 result.
width = impl_->width;
1245 result.
height = impl_->height;
1246 result.
depth = impl_->depth;
1247 result.
states.reserve(impl_->state.size());
1248 for (
const auto state : impl_->state) result.
states.push_back(stateToMaskByte(
state));
1253 if (snapshotValue.
width != impl_->width || snapshotValue.
height != impl_->height ||
1254 snapshotValue.
depth != impl_->depth || snapshotValue.
states.size() != impl_->state.size())
1259 impl_->state[
index] =
value == 0 ? CellState::Unknown :
1260 value >= 192 ? CellState::Visible : CellState::Explored;
1262 impl_->visibleList.clear();
1264 if (impl_->state[
index] == CellState::Visible) impl_->visibleList.push_back(
static_cast<int>(
index));
1265 impl_->dirty =
true;
1271 if (!impl_->inBounds2(
x,
y))
return 0.f;
1272 return stateToMaskValue(impl_->state[
size_t(impl_->index2(
x,
y))]);
1277 if (!impl_->inBounds2(
x,
y))
return 0;
1278 return int(stateToMaskByte(impl_->state[
size_t(impl_->index2(
x,
y))]));
1282 if (!impl_->inBounds3(
x,
y,
z))
return 0.f;
1283 return stateToMaskValue(impl_->state[
size_t(impl_->index3(
x,
y,
z))]);
1287 if (!impl_->inBounds3(
x,
y,
z))
return 0;
1288 return int(stateToMaskByte(impl_->state[
size_t(impl_->index3(
x,
y,
z))]));
1294 if (impl_->width <= 0 || impl_->height <= 0) {
1298 if (impl_->mode == Mode::Volume) {
1299 if (sliceZ < 0 || sliceZ >= impl_->depth)
return false;
1303 out.resize(
size_t(impl_->width * impl_->height));
1304 for (
int y = 0;
y < impl_->height; ++
y) {
1305 for (
int x = 0;
x < impl_->width; ++
x) {
1306 CellState
s = (impl_->mode == Mode::Volume)
1307 ? impl_->state[
size_t(impl_->index3(
x,
y, sliceZ))]
1308 : impl_->state[size_t(impl_->index2(
x,
y))];
1309 out[size_t(impl_->index2(
x,
y))] = stateToMaskByte(
s);
1320 if (!gfx)
return nullptr;
1321 std::vector<uint8_t> r8;
1323 std::vector<uint8_t> rgba(
size_t(impl_->width * impl_->height * 4));
1324 for (
size_t i = 0; i < r8.size(); ++i) {
1325 const uint8_t
v = r8[i];
1326 rgba[i * 4 + 0] =
v;
1327 rgba[i * 4 + 1] =
v;
1328 rgba[i * 4 + 2] =
v;
1329 rgba[i * 4 + 3] =
v;
1331 return gfx->
newTexture(impl_->width, impl_->height, rgba.data());
std::array< double, 10 > q
std::array< float, 3 > scale
std::vector< char > inside
std::vector< WfcTile > tiles
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
virtual Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false)=0
Creates a texture. @ownership Caller deletes unless documented otherwise.
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Dynamic field-of-view / fog-of-war facade. Phase A: 2D shadowcast + multi-revealer + explored memory....
void blockOpaqueGid(int gid)
bool canDetect(int revealerId, int x, int y, float targetStealth) const
float getElevation(int x, int y) const
bool isExplored(int x, int y) const
bool isOpaque(int x, int y) const
int getVerticalRange() const
void bindLayer(TileLayer *layer)
std::string getRadiusMetric() const
void setAlgorithm(const std::string &name)
"shadowcast" | "raycast" | "permissive" | "rectangle"
float getDetectionMargin() const
float getEyeOffset() const
void setRadiusMetric(const std::string &name)
"euclidean" | "chebyshev" | "manhattan" — ignored when topology is hex (cube distance).
float getCliffBlock() const
void setRevealerEnabled(int id, bool enabled)
void setOpaque3(int x, int y, int z, bool opaque)
bool isVisible3(int x, int y, int z) const
void setDetectionMargin(float margin)
void setEyeOffset(float offset)
bool isVisible(int x, int y) const
int getEffectiveRadius(int id) const
void setSize(int width, int height)
bool fillMaskR8(std::vector< uint8_t > &out) const
Result< void > restore(const Snapshot &snapshot)
Restore exact cell states atomically.
void setRevealerPosition(int id, int x, int y)
float getRevealerPerception(int id) const
float getMaskValue3(int x, int y, int z) const
void setBlockEmpty(bool enable)
std::string getMode() const
int addRevealer(int x, int y, int radius)
std::string getState3(int x, int y, int z) const
float getMaskValue(int x, int y) const
bool isExplored3(int x, int y, int z) const
int getRevealerCount() const
graphics::Texture * buildMaskTextureSlice(graphics::Graphics *gfx, int sliceZ) const
void setCliffBlock(float delta)
void setElevation(int x, int y, float elev)
Snapshot snapshot() const
Capture exact unknown/explored/visible cell states without revealers.
std::string getTopology() const
bool fillMaskR8Slice(std::vector< uint8_t > &out, int sliceZ) const
int addRevealer3(int x, int y, int z, int radius)
std::string getAlgorithm() const
void setRevealerRadius(int id, int radius)
void setRevealerPosition3(int id, int x, int y, int z)
void setRevealerPerception(int id, float perception)
Soft RPG hooks (no hard dependency on rpg module). effectiveRadius = radius + floor(perception * perc...
void setRevealerFacing(int id, float facingDeg, float halfAngleDeg)
void removeRevealer(int id)
bool getCornerPeek() const
void clearRevealerFacing(int id)
void setPerceptionRadiusScale(float scale)
void setMode(const std::string &name)
"grid2d" (default) | "heightmap" | "volume"
void unblockOpaqueGid(int gid)
int getMaskByte(int x, int y) const
void setVerticalRange(int range)
bool canDetect3(int revealerId, int x, int y, int z, float targetStealth) const
float getPerceptionRadiusScale() const
bool isOpaque3(int x, int y, int z) const
bool getBlockEmpty() const
std::string getState(int x, int y) const
void setCornerPeek(bool enable)
void setOpaque(int x, int y, bool opaque)
graphics::Texture * buildMaskTexture(graphics::Graphics *gfx) const
Upload current 2D / slice mask as RGBA8 Texture (R=G=B=A=mask byte). Caller owns the returned Texture...
void setTopology(const std::string &name)
"ortho" (default) | "hex" | "auto" (hex if bound layer is hex/staggered).
void setVolumeSize(int width, int height, int depth)
int getMaskByte3(int x, int y, int z) const
ECS tile layer entity. Script mutates tile GIDs / tileset / draw; TileRenderSystem batch-draws atlas ...
放置世界:格子占用(多通道)+ 地形语义 + 已放置建筑实例。 行为由 PlacementSystem 提供;本类暴露便于脚本绑定的薄封装方法。 坐标换算统一走 eve::grid(支持 rectang...
uint32_t tileGid(uint32_t raw)
Strip Tiled flip / rotate flags; keep low 28 bits.
float hexCellAngle(int ox, int oy, int x, int y) const
void computeShadowcast(const Revealer &r, int zSlice, int radius)
void computeRaycast(const Revealer &r, int zSlice, int radius)
std::vector< uint8_t > opaque
void computePermissive(const Revealer &r, int zSlice, int radius)
void computeOrthoShadowcast(const Revealer &r, int zSlice, int radius)
void computeRectangle(const Revealer &r, int zSlice, int radius)
const Revealer * findRevealerConst(int id) const
void markVisibleIdx(int idx)
std::vector< CellState > state
void markVisible2(int x, int y)
int index2(int x, int y) const
void castRayLine(int ox, int oy, int tx, int ty, int zSlice, bool useCone, float facingDeg, float halfAngleDeg, int radius)
bool inBounds3(int x, int y, int z) const
bool passesHeightToTarget(int ox, int oy, int tx, int ty) const
bool cellOpaque3(int x, int y, int z) const
void markVisible3(int x, int y, int z)
int effectiveVerticalRange() const
void resetVisibleKeepExplored()
std::vector< float > elevation
float perceptionRadiusScale
void castLight(int ox, int oy, int radius, int row, float startSlope, float endSlope, int xx, int xy, int yx, int yy, int zSlice, bool useCone, float facingDeg, float halfAngleDeg)
std::vector< Revealer > revealers
bool cellOpaque2(int x, int y) const
std::vector< Rect > buildOpaqueRects(int ox, int oy, int radius, int zSlice) const
bool inRadius3(int ox, int oy, int oz, int x, int y, int z, int radius) const
void resize(int w, int h, int d)
bool angleFullyCovered(float a0, float a1, const std::vector< AngleShadow > &shadows) const
void computeHexShadowcast(const Revealer &r, int zSlice, int radius)
void applyAutoTopologyFromLayer()
void addAngleShadow(std::vector< AngleShadow > &shadows, float a0, float a1) const
Revealer * findRevealer(int id)
std::unordered_set< uint32_t > opaqueGids
bool los2(int x0, int y0, int x1, int y1, int zSlice, bool applyHeight) const
void bindLayer(TileLayer *l)
bool losBresenham2(int x0, int y0, int x1, int y1, int zSlice, bool applyHeight) const
std::vector< int > visibleList
bool losHexCube(int x0, int y0, int x1, int y1, int zSlice, bool applyHeight) const
float elevAt(int x, int y) const
void tryMark2(int ox, int oy, int x, int y, int zSlice, bool useCone, float facingDeg, float halfAngleDeg, int radius)
int index3(int x, int y, int z) const
bool inBounds2(int x, int y) const
int effectiveRadiusOf(const Revealer &r) const
bool inRadius2(int ox, int oy, int x, int y, int radius) const
void computeRevealer(const Revealer &r)
void extendVertical(const Revealer &r, int zSlice, int radius)
bool cellOpaqueOnSlice(int x, int y, int zSlice) const
Owning visibility-memory snapshot for the current grid/volume dimensions.
std::vector< uint8_t > states
Per-GID visual metadata emitted by a project-defined asset pipeline.