11#include <unordered_set>
18constexpr float kPi = 3.14159265358979323846f;
19constexpr float kExploredMask = 0.35f;
21enum class Algorithm : uint8_t { Shadowcast, Raycast, Permissive,
Rectangle };
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:
123 return adx * adx + ady * ady <= radius * radius;
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}};
224 RadiusMetric
metric = RadiusMetric::Euclidean;
259 return std::max(0, r.
radius + bonus);
263 if (
topology == Topology::Hex)
return hexDistance(ox, oy,
x,
y) <= radius;
264 return inRadiusOrtho(
metric,
x - ox,
y - oy, radius);
267 bool inRadius3(
int ox,
int oy,
int oz,
int x,
int y,
int z,
int radius)
const {
269 return hexDistance(ox, oy,
x,
y) + std::abs(
z - oz) <= radius;
271 return inRadius3Ortho(
metric,
x - ox,
y - oy,
z - oz, radius);
276 const auto o =
layer->config()->orientation;
287 state.assign(
n, CellState::Unknown);
296 auto cfg =
layer->config();
297 resize(cfg->mapW, cfg->mapH, 1);
298 if (
mode == Mode::Volume)
mode = Mode::Grid2D;
305 auto cfg =
layer->config();
308 resize(cfg->mapW, cfg->mapH, 1);
312 for (
int i = 0; i <
n; ++i) {
313 const uint32_t gid = (i < int(
tiles->gids.size())) ?
tileGid(
tiles->gids[
size_t(i)]) : 0
u;
343 if (idx < 0 || idx >=
int(
state.size()))
return;
344 if (
state[
size_t(
idx)] != CellState::Visible) {
345 state[size_t(
idx)] = CellState::Visible;
363 state[size_t(
idx)] = CellState::Explored;
370 std::fill(
state.begin(),
state.end(), CellState::Unknown);
377 if (r.id ==
id)
return &r;
384 if (r.id ==
id)
return &r;
389 bool losBresenham2(
int x0,
int y0,
int x1,
int y1,
int zSlice,
bool applyHeight)
const {
390 int dx = std::abs(x1 - x0);
391 int dy = std::abs(y1 - y0);
392 const int sx = x0 < x1 ? 1 : -1;
393 const int sy = y0 < y1 ? 1 : -1;
400 if (
x == x1 &&
y == y1)
return true;
401 const int e2 = 2 * err;
412 const bool atEnd = (nx == x1 && ny == y1);
414 if (applyHeight &&
mode == Mode::Heightmap && !atEnd) {
427 bool losHexCube(
int x0,
int y0,
int x1,
int y1,
int zSlice,
bool applyHeight)
const {
428 int q0, r0, s0, q1, r1, s1;
429 offsetToCube(x0, y0, q0, r0, s0);
430 offsetToCube(x1, y1, q1, r1, s1);
431 const int n = cubeDistance(q0, r0, s0, q1, r1, s1);
432 if (
n == 0)
return true;
434 for (
int i = 1; i <=
n; ++i) {
435 const float t = float(i) / float(
n);
436 const float qf = float(q0) + (float(q1) - float(q0)) * t;
437 const float rf = float(r0) + (float(r1) - float(r0)) * t;
438 const float sf = float(s0) + (float(s1) - float(s0)) * t;
440 int rq = int(std::lround(qf));
441 int rr = int(std::lround(rf));
442 int rs = int(std::lround(sf));
443 const float qdiff = std::fabs(rq - qf);
444 const float rdiff = std::fabs(rr - rf);
445 const float sdiff = std::fabs(rs - sf);
446 if (qdiff > rdiff && qdiff > sdiff) rq = -rr - rs;
447 else if (rdiff > sdiff) rr = -rq - rs;
451 cubeToOffset(rq, rr,
x,
y);
452 const bool atEnd = (i ==
n);
454 if (applyHeight &&
mode == Mode::Heightmap && !atEnd) {
461 bool los2(
int x0,
int y0,
int x1,
int y1,
int zSlice,
bool applyHeight)
const {
462 if (
topology == Topology::Hex)
return losHexCube(x0, y0, x1, y1, zSlice, applyHeight);
467 if (
mode != Mode::Heightmap)
return true;
468 return los2(ox, oy, tx, ty, 0,
true);
471 void tryMark2(
int ox,
int oy,
int x,
int y,
int zSlice,
bool useCone,
float facingDeg,
472 float halfAngleDeg,
int radius) {
475 if (!inCone(ox, oy,
x,
y, useCone, facingDeg, halfAngleDeg))
return;
481 void castLight(
int ox,
int oy,
int radius,
int row,
float startSlope,
float endSlope,
int xx,
482 int xy,
int yx,
int yy,
int zSlice,
bool useCone,
float facingDeg,
483 float halfAngleDeg) {
484 if (startSlope < endSlope)
return;
485 float newStart = 0.f;
486 for (
int j = row; j <= radius; ++j) {
489 bool blocked =
false;
492 const int mapX = ox + dx * xx + dy * xy;
493 const int mapY = oy + dx * yx + dy * yy;
494 const float leftSlope = (float(dx) - 0.5f) / (
float(dy) + 0.5f);
495 const float rightSlope = (float(dx) + 0.5f) / (
float(dy) - 0.5f);
496 if (startSlope < rightSlope)
continue;
497 if (endSlope > leftSlope)
break;
498 tryMark2(ox, oy, mapX, mapY, zSlice, useCone, facingDeg, halfAngleDeg, radius);
501 newStart = rightSlope;
505 startSlope = newStart;
508 castLight(ox, oy, radius, j + 1, startSlope, leftSlope, xx, xy, yx, yy, zSlice,
509 useCone, facingDeg, halfAngleDeg);
510 newStart = rightSlope;
519 if (radius == 0)
return;
520 for (
int oct = 0; oct < 8; ++oct) {
521 castLight(r.
x, r.
y, radius, 1, 1.f, 0.f, kMultXX[oct], kMultXY[oct], kMultYX[oct],
527 const float px = float(
x) + ((
y & 1) ? 0.5f : 0.f);
528 const float py = float(
y) * 0.86602540378f;
529 const float opx = float(ox) + ((oy & 1) ? 0.5f : 0.f);
530 const float opy = float(oy) * 0.86602540378f;
531 return std::atan2(py - opy,
px - opx);
536 float mid = normalizeAngle(0.5f * (a0 + a1));
538 if (std::fabs(a1 - a0) > kPi) mid = normalizeAngle(mid + kPi);
539 for (
const auto &sh : shadows) {
540 float s = sh.start, e = sh.end;
542 if (mid >=
s && mid <= e)
return true;
544 if (mid >=
s || mid <= e)
return true;
550 void addAngleShadow(std::vector<AngleShadow> &shadows,
float a0,
float a1)
const {
551 a0 = normalizeAngle(a0);
552 a1 = normalizeAngle(a1);
557 float diff = normalizeAngle(end - start);
558 if (diff < 0.f) std::swap(start, end);
559 shadows.push_back(
AngleShadow{normalizeAngle(start), normalizeAngle(end)});
564 if (radius == 0)
return;
565 std::vector<AngleShadow> shadows;
569 offsetToCube(r.
x, r.
y, cq, cr, cs);
571 for (
int ring = 1; ring <= radius; ++ring) {
573 int q = cq + kCubeDirs[4][0] * ring;
574 int rr = cr + kCubeDirs[4][1] * ring;
575 int s = cs + kCubeDirs[4][2] * ring;
576 for (
int side = 0; side < 6; ++side) {
579 cubeToOffset(q, rr,
x,
y);
584 (ring <= 0) ? kPi : (0.55f / float(ring));
585 const float a0 = normalizeAngle(ang - half);
586 const float a1 = normalizeAngle(ang + half);
589 tryMark2(r.
x, r.
y,
x,
y, zSlice,
false, 0.f, 180.f, radius);
595 q += kCubeDirs[side][0];
596 rr += kCubeDirs[side][1];
597 s += kCubeDirs[side][2];
609 void castRayLine(
int ox,
int oy,
int tx,
int ty,
int zSlice,
bool useCone,
float facingDeg,
610 float halfAngleDeg,
int radius) {
613 int q0, r0, s0, q1, r1, s1;
614 offsetToCube(ox, oy, q0, r0, s0);
615 offsetToCube(tx, ty, q1, r1, s1);
616 const int n = std::max(1, cubeDistance(q0, r0, s0, q1, r1, s1));
617 for (
int i = 0; i <=
n; ++i) {
618 const float t = float(i) / float(
n);
619 int rq = int(std::lround(
float(q0) + (
float(q1) -
float(q0)) * t));
620 int rr = int(std::lround(
float(r0) + (
float(r1) -
float(r0)) * t));
624 cubeToOffset(rq, rr,
x,
y);
625 tryMark2(ox, oy,
x,
y, zSlice, useCone, facingDeg, halfAngleDeg, radius);
630 int dx = std::abs(tx - ox);
631 int dy = std::abs(ty - oy);
632 const int sx = ox < tx ? 1 : -1;
633 const int sy = oy < ty ? 1 : -1;
638 tryMark2(ox, oy,
x,
y, zSlice, useCone, facingDeg, halfAngleDeg, radius);
639 if (
x == tx &&
y == ty)
break;
641 const int e2 = 2 * err;
655 if (radius == 0)
return;
658 offsetToCube(r.
x, r.
y, cq, cr, cs);
659 for (
int ring = 1; ring <= radius; ++ring) {
660 int q = cq + kCubeDirs[4][0] * ring;
661 int rr = cr + kCubeDirs[4][1] * ring;
662 for (
int side = 0; side < 6; ++side) {
665 cubeToOffset(q, rr,
x,
y);
668 q += kCubeDirs[side][0];
669 rr += kCubeDirs[side][1];
675 for (
int i = -radius; i <= radius; ++i) {
689 if (radius == 0)
return;
690 for (
int dy = -radius; dy <= radius; ++dy) {
691 for (
int dx = -radius; dx <= radius; ++dx) {
692 if (dx == 0 && dy == 0)
continue;
693 const int tx = r.
x + dx;
694 const int ty = r.
y + dy;
696 if (!
inRadius2(r.
x, r.
y, tx, ty, radius))
continue;
698 bool see =
los2(r.
x, r.
y, tx, ty, zSlice,
mode == Mode::Heightmap);
700 const int sx = (dx > 0) ? -1 : (dx < 0 ? 1 : 0);
701 const int sy = (dy > 0) ? -1 : (dy < 0 ? 1 : 0);
702 const int cands[3][2] = {{tx + sx, ty}, {tx, ty + sy}, {tx + sx, ty + sy}};
703 for (
auto &
c : cands) {
706 if (ax == tx && ay == ty)
continue;
709 if (!
inRadius2(r.
x, r.
y, ax, ay, radius))
continue;
710 if (
los2(r.
x, r.
y, ax, ay, zSlice,
mode == Mode::Heightmap)) {
716 if (see)
tryMark2(r.
x, r.
y, tx, ty, zSlice,
false, 0.f, 180.f, radius);
722 const int xMin = std::max(0, ox - radius);
723 const int xMax = std::min(
width - 1, ox + radius);
724 const int yMin = std::max(0, oy - radius);
725 const int yMax = std::min(
height - 1, oy + radius);
726 const int bw = xMax - xMin + 1;
727 const int bh = yMax - yMin + 1;
728 std::vector<uint8_t> used(
size_t(bw * bh), 0);
729 auto usedAt = [&](
int x,
int y) -> uint8_t & {
730 return used[size_t((
y - yMin) * bw + (
x - xMin))];
733 std::vector<Rect> rects;
734 for (
int y = yMin;
y <= yMax; ++
y) {
735 for (
int x = xMin;
x <= xMax; ++
x) {
736 if (usedAt(
x,
y))
continue;
746 while (grow && y1 + 1 <= yMax) {
747 for (
int xx =
x; xx <= x1; ++xx) {
749 !
inRadius2(ox, oy, xx, y1 + 1, radius)) {
756 for (
int yy =
y; yy <= y1; ++yy)
757 for (
int xx =
x; xx <= x1; ++xx) usedAt(xx, yy) = 1;
758 rects.push_back(
Rect{
x,
y, x1, y1});
766 std::vector<uint8_t> lit(
size_t(
width *
height), 0);
767 auto litAt = [&](
int x,
int y) -> uint8_t & {
return lit[size_t(
index2(
x,
y))]; };
769 for (
int y = std::max(0, r.
y - radius);
y <= std::min(
height - 1, r.
y + radius); ++
y) {
770 for (
int x = std::max(0, r.
x - radius);
x <= std::min(
width - 1, r.
x + radius); ++
x) {
779 const float ox = float(r.
x) + 0.5f;
780 const float oy = float(r.
y) + 0.5f;
781 std::sort(rects.begin(), rects.end(), [&](
const Rect &
a,
const Rect &
b) {
782 const int acx = (a.x0 + a.x1) / 2;
783 const int acy = (a.y0 + a.y1) / 2;
784 const int bcx = (b.x0 + b.x1) / 2;
785 const int bcy = (b.y0 + b.y1) / 2;
786 return inRadius2(r.x, r.y, acx, acy, radius) &&
787 (std::abs(acx - r.x) + std::abs(acy - r.y)) <
788 (std::abs(bcx - r.x) + std::abs(bcy - r.y));
791 for (
const auto &rc : rects) {
793 const float corners[4][2] = {
794 {float(rc.x0), float(rc.y0)},
795 {float(rc.x1) + 1.f, float(rc.y0)},
796 {float(rc.x0), float(rc.y1) + 1.f},
797 {float(rc.x1) + 1.f, float(rc.y1) + 1.f},
800 for (
int i = 0; i < 4; ++i) {
801 angles[i] = std::atan2(corners[i][1] - oy, corners[i][0] - ox);
803 float aMin = angles[0], aMax = angles[0];
804 for (
int i = 1; i < 4; ++i) {
805 const float dMin = normalizeAngle(angles[i] - aMin);
806 const float dMax = normalizeAngle(angles[i] - aMax);
807 if (dMin < 0.f) aMin = angles[i];
808 if (dMax > 0.f) aMax = angles[i];
810 const float nearest2 =
811 float(std::min({(rc.x0 - r.
x) * (rc.x0 - r.
x) + (rc.y0 - r.
y) * (rc.y0 - r.
y),
812 (rc.x1 - r.
x) * (rc.x1 - r.
x) + (rc.y0 - r.
y) * (rc.y0 - r.
y),
813 (rc.x0 - r.
x) * (rc.x0 - r.
x) + (rc.y1 - r.
y) * (rc.y1 - r.
y),
814 (rc.x1 - r.
x) * (rc.x1 - r.
x) + (rc.y1 - r.
y) * (rc.y1 - r.
y)}));
816 for (
int y = std::max(0, r.
y - radius);
y <= std::min(
height - 1, r.
y + radius); ++
y) {
817 for (
int x = std::max(0, r.
x - radius);
x <= std::min(
width - 1, r.
x + radius); ++
x) {
818 if (!litAt(
x,
y))
continue;
820 if (
x >= rc.x0 && x <= rc.x1 && y >= rc.y0 &&
y <= rc.y1)
continue;
821 const float d2 = float((
x - r.
x) * (
x - r.
x) + (
y - r.
y) * (
y - r.
y));
822 if (d2 <= nearest2 + 0.01f)
continue;
823 const float ang = std::atan2(
float(
y) + 0.5f - oy,
float(
x) + 0.5f - ox);
825 if (aMin <= aMax) inside = (ang >= aMin && ang <= aMax);
826 else inside = (ang >= aMin || ang <= aMax);
828 if (normalizeAngle(aMax - aMin) < 0.f) {
829 inside = normalizeAngle(ang - aMin) >= 0.f ||
830 normalizeAngle(aMax - ang) >= 0.f;
832 const float da = normalizeAngle(ang - aMin);
833 const float span = normalizeAngle(aMax - aMin);
834 inside = da >= 0.f && da <= span;
836 if (inside) litAt(
x,
y) = 0;
843 if (litAt(
x,
y))
tryMark2(r.
x, r.
y,
x,
y, zSlice,
false, 0.f, 180.f, radius);
849 if (
mode != Mode::Volume)
return;
851 for (
int y = std::max(0, r.
y - radius);
y <= std::min(
height - 1, r.
y + radius); ++
y) {
852 for (
int x = std::max(0, r.
x - radius);
x <= std::min(
width - 1, r.
x + radius); ++
x) {
854 if (
state[
size_t(
idx)] != CellState::Visible)
continue;
858 if (z < 0 || z >=
depth)
break;
872 if (
mode == Mode::Volume) {
879 case Algorithm::Raycast:
882 case Algorithm::Permissive:
885 case Algorithm::Rectangle:
888 case Algorithm::Shadowcast:
910Fov &
Fov::operator=(
Fov &&) noexcept = default;
918 impl_->layer =
nullptr;
919 if (impl_->mode == Mode::Volume) impl_->mode = Mode::Grid2D;
924 impl_->layer =
nullptr;
925 impl_->mode = Mode::Volume;
934 const Mode next = parseMode(
name, impl_->mode);
936 if (next != Mode::Volume && impl_->depth != 1) {
937 impl_->resize(impl_->width, impl_->height, 1);
945 impl_->algorithm = parseAlgorithm(
name, impl_->algorithm);
952 impl_->metric = parseMetric(
name, impl_->metric);
959 if (
name ==
"auto") {
960 impl_->topologyManual =
false;
961 impl_->applyAutoTopologyFromLayer();
962 if (!impl_->layer) impl_->topology = Topology::Ortho;
963 }
else if (
name ==
"hex" ||
name ==
"hexagonal" ||
name ==
"staggered") {
964 impl_->topologyManual =
true;
965 impl_->topology = Topology::Hex;
966 }
else if (
name ==
"ortho" ||
name ==
"orthogonal") {
967 impl_->topologyManual =
true;
968 impl_->topology = Topology::Ortho;
976 impl_->cornerPeek = enable;
983 impl_->opaqueGids.insert(uint32_t(gid));
984 if (impl_->layer) impl_->syncFromLayer();
985 else impl_->dirty =
true;
990 impl_->opaqueGids.erase(uint32_t(gid));
991 if (impl_->layer) impl_->syncFromLayer();
992 else impl_->dirty =
true;
996 impl_->opaqueGids.clear();
997 if (impl_->layer) impl_->syncFromLayer();
998 else impl_->dirty =
true;
1002 impl_->blockEmpty = enable;
1003 if (impl_->layer) impl_->syncFromLayer();
1004 else impl_->dirty =
true;
1010 if (!impl_->inBounds2(
x,
y))
return;
1011 if (impl_->mode == Mode::Volume) {
1012 if (!impl_->inBounds3(
x,
y, 0))
return;
1013 impl_->opaque[size_t(impl_->index3(
x,
y, 0))] = opaque ? 1u : 0
u;
1015 impl_->opaque[size_t(impl_->index2(
x,
y))] = opaque ? 1u : 0
u;
1017 impl_->dirty =
true;
1021 if (impl_->mode == Mode::Volume)
return impl_->cellOpaque3(
x,
y, 0);
1022 return impl_->cellOpaque2(
x,
y);
1026 if (!impl_->inBounds3(
x,
y,
z))
return;
1027 impl_->opaque[size_t(impl_->index3(
x,
y,
z))] = opaque ? 1u : 0
u;
1028 impl_->dirty =
true;
1036 if (!impl_->inBounds2(
x,
y))
return;
1037 impl_->elevation[size_t(impl_->index2(
x,
y))] = elev;
1038 impl_->dirty =
true;
1044 impl_->cliffBlock = delta;
1045 impl_->dirty =
true;
1050 impl_->eyeOffset = offset;
1051 impl_->dirty =
true;
1056 impl_->verticalRange = range;
1057 impl_->dirty =
true;
1065 r.
id = impl_->nextRevealerId++;
1070 impl_->revealers.push_back(r);
1071 impl_->dirty =
true;
1076 auto &
v = impl_->revealers;
1077 v.erase(std::remove_if(
v.begin(),
v.end(), [
id](
const Impl::Revealer &r) { return r.id == id; }),
1079 impl_->dirty =
true;
1083 impl_->revealers.clear();
1084 impl_->dirty =
true;
1092 if (
auto *r = impl_->findRevealer(
id)) {
1096 impl_->dirty =
true;
1101 if (
auto *r = impl_->findRevealer(
id)) {
1103 impl_->dirty =
true;
1108 if (
auto *r = impl_->findRevealer(
id)) {
1110 r->facingDeg = facingDeg;
1111 r->halfAngleDeg = std::max(0.f, halfAngleDeg);
1112 impl_->dirty =
true;
1117 if (
auto *r = impl_->findRevealer(
id)) {
1119 r->halfAngleDeg = 180.f;
1120 impl_->dirty =
true;
1125 if (
auto *r = impl_->findRevealer(
id)) {
1127 impl_->dirty =
true;
1134 if (
auto *r = impl_->findRevealer(
id)) {
1135 r->perception = perception;
1136 impl_->dirty =
true;
1141 if (
const auto *r = impl_->findRevealerConst(
id))
return r->perception;
1146 impl_->perceptionRadiusScale =
scale;
1147 impl_->dirty =
true;
1155 if (
const auto *r = impl_->findRevealerConst(
id))
return impl_->effectiveRadiusOf(*r);
1160 if (impl_->mode == Mode::Volume)
return canDetect3(revealerId,
x,
y, 0, targetStealth);
1161 const auto *r = impl_->findRevealerConst(revealerId);
1162 if (!r || !r->enabled)
return false;
1164 return (r->perception + impl_->detectionMargin) >= targetStealth;
1168 const auto *r = impl_->findRevealerConst(revealerId);
1169 if (!r || !r->enabled)
return false;
1171 return (r->perception + impl_->detectionMargin) >= targetStealth;
1179 if (impl_->mode == Mode::Volume)
return isVisible3(
x,
y, 0);
1180 if (!impl_->inBounds2(
x,
y))
return false;
1181 return impl_->state[size_t(impl_->index2(
x,
y))] == CellState::Visible;
1185 if (impl_->mode == Mode::Volume)
return isExplored3(
x,
y, 0);
1186 if (!impl_->inBounds2(
x,
y))
return false;
1187 const auto s = impl_->state[size_t(impl_->index2(
x,
y))];
1188 return s == CellState::Explored ||
s == CellState::Visible;
1192 if (!impl_->inBounds3(
x,
y,
z))
return false;
1193 return impl_->state[size_t(impl_->index3(
x,
y,
z))] == CellState::Visible;
1197 if (!impl_->inBounds3(
x,
y,
z))
return false;
1198 const auto s = impl_->state[size_t(impl_->index3(
x,
y,
z))];
1199 return s == CellState::Explored ||
s == CellState::Visible;
1203 if (impl_->mode == Mode::Volume)
return getState3(
x,
y, 0);
1204 if (!impl_->inBounds2(
x,
y))
return "unknown";
1205 switch (impl_->state[
size_t(impl_->index2(
x,
y))]) {
1206 case CellState::Visible:
1208 case CellState::Explored:
1216 if (!impl_->inBounds3(
x,
y,
z))
return "unknown";
1217 switch (impl_->state[
size_t(impl_->index3(
x,
y,
z))]) {
1218 case CellState::Visible:
1220 case CellState::Explored:
1230 impl_->resetVisibleKeepExplored();
1231 impl_->dirty =
true;
1236 if (!impl_->inBounds2(
x,
y))
return 0.f;
1237 return stateToMaskValue(impl_->state[
size_t(impl_->index2(
x,
y))]);
1242 if (!impl_->inBounds2(
x,
y))
return 0;
1243 return int(stateToMaskByte(impl_->state[
size_t(impl_->index2(
x,
y))]));
1247 if (!impl_->inBounds3(
x,
y,
z))
return 0.f;
1248 return stateToMaskValue(impl_->state[
size_t(impl_->index3(
x,
y,
z))]);
1252 if (!impl_->inBounds3(
x,
y,
z))
return 0;
1253 return int(stateToMaskByte(impl_->state[
size_t(impl_->index3(
x,
y,
z))]));
1259 if (impl_->width <= 0 || impl_->height <= 0) {
1263 if (impl_->mode == Mode::Volume) {
1264 if (sliceZ < 0 || sliceZ >= impl_->depth)
return false;
1268 out.resize(
size_t(impl_->width * impl_->height));
1269 for (
int y = 0;
y < impl_->height; ++
y) {
1270 for (
int x = 0;
x < impl_->width; ++
x) {
1271 CellState
s = (impl_->mode == Mode::Volume)
1272 ? impl_->state[
size_t(impl_->index3(
x,
y, sliceZ))]
1273 : impl_->state[size_t(impl_->index2(
x,
y))];
1274 out[size_t(impl_->index2(
x,
y))] = stateToMaskByte(
s);
1285 if (!gfx)
return nullptr;
1286 std::vector<uint8_t> r8;
1288 std::vector<uint8_t> rgba(
size_t(impl_->width * impl_->height * 4));
1289 for (
size_t i = 0; i < r8.size(); ++i) {
1290 const uint8_t
v = r8[i];
1291 rgba[i * 4 + 0] =
v;
1292 rgba[i * 4 + 1] =
v;
1293 rgba[i * 4 + 2] =
v;
1294 rgba[i * 4 + 3] =
v;
1296 return gfx->
newTexture(impl_->width, impl_->height, rgba.data());
std::vector< WfcTile > tiles
virtual Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false)=0
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
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)
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