载入中...
搜索中...
未找到
LineOfSight.cpp
浏览该文件的文档.
1
7
8#include "common/Diagnostic.h"
9
10#include <algorithm>
11#include <cstdlib>
12#include <utility>
13
14namespace eve::tactics {
15namespace {
16
18bool blocksSight(const BoardState& board, Cell cell) {
19 const auto state = board.cell(cell);
20 if (!state) return true;
21 return std::find(state.value().tags.begin(), state.value().tags.end(), kSightBlockerTag) !=
22 state.value().tags.end();
23}
24
26struct Hex {
27 int q = 0;
28 int r = 0;
29};
30
31Hex toHex(Cell cell) { return Hex{cell.x, cell.y}; }
32
34Cell fromCube(double x, double y, double z) {
35 std::int64_t rx = static_cast<std::int64_t>(x < 0 ? x - 0.5 : x + 0.5);
36 std::int64_t ry = static_cast<std::int64_t>(y < 0 ? y - 0.5 : y + 0.5);
37 std::int64_t rz = static_cast<std::int64_t>(z < 0 ? z - 0.5 : z + 0.5);
38 const std::int64_t dx = std::llabs(rx - static_cast<std::int64_t>(x));
39 const std::int64_t dy = std::llabs(ry - static_cast<std::int64_t>(y));
40 const std::int64_t dz = std::llabs(rz - static_cast<std::int64_t>(z));
41 if (dx > dy && dx > dz) {
42 rx = -ry - rz;
43 } else if (dy > dz) {
44 ry = -rx - rz;
45 } else {
46 rz = -rx - ry;
47 }
48 return Cell{static_cast<int>(rx), static_cast<int>(ry), 0};
49}
50
58std::vector<Cell> traceInterior(const BoardState& board, Cell from, Cell to, bool& reverse) {
59 std::vector<Cell> interior;
60 if (from.layer != to.layer) return interior;
61 const int layer = from.layer;
62
63 Cell start = from;
64 Cell end = to;
65 reverse = (to < from);
66 if (reverse) std::swap(start, end);
67
69 // Cube-space interpolation: the standard hex line, deduplicated so a trace that clips
70 // two hexes in one step does not repeat them.
71 const Hex a = toHex(start);
72 const Hex b = toHex(end);
73 const int dx = b.q - a.q;
74 const int dy = b.r - a.r;
75 const int dz = -dx - dy;
76 const int steps = std::max({std::abs(dx), std::abs(dy), std::abs(dz)});
78 for (int step = 1; step < steps; ++step) {
79 const double t = static_cast<double>(step) / static_cast<double>(steps);
80 Cell cell = fromCube(static_cast<double>(a.q) + static_cast<double>(dx) * t,
81 static_cast<double>(a.r) + static_cast<double>(dy) * t,
82 static_cast<double>(dz) * t);
84 if (cell == previous) continue;
85 interior.push_back(cell);
86 previous = cell;
87 }
88 return interior;
89 }
90
91 // Square topologies: a supercover walk. When both error conditions hold in one step the
92 // trace takes a corner, so a diagonal that squeezes between two cells is blocked if
93 // either of them blocks: a shot does not pass through a keyhole.
94 const int dx = end.x - start.x;
95 const int dy = end.y - start.y;
96 const int stepX = (dx > 0) - (dx < 0);
97 const int stepY = (dy > 0) - (dy < 0);
98 const int absX = std::abs(dx);
99 const int absY = std::abs(dy);
100 int x = start.x;
101 int y = start.y;
102 int error = absX - absY;
103 while (x != end.x || y != end.y) {
104 const int doubled = 2 * error;
105 if (doubled > -absY) {
106 error -= absY;
107 x += stepX;
108 }
109 if (doubled < absX) {
110 error += absX;
111 y += stepY;
112 }
113 if (x == end.x && y == end.y) break;
114 Cell cell{x, y, layer};
115 interior.push_back(cell);
116 }
117 return interior;
118}
119
120} // namespace
121
122std::string_view coverLevelName(CoverLevel level) noexcept {
123 switch (level) {
124 case CoverLevel::None: return "none";
125 case CoverLevel::Half: return "half";
126 case CoverLevel::Full: return "full";
127 }
128 return "none";
129}
130
132 if (board.topology() == BoardTopology::ExplicitGraph)
134 "graph sight requires an explicit spatial policy", "topology"));
135 if (from.layer != to.layer)
137 Diagnostic::error(DiagnosticCode::Unsupported, "line of sight does not cross layers", "from.layer"));
138 if (!board.contains(from))
140 Diagnostic::error(DiagnosticCode::NotFound, "line of sight origin is not on the board", "from"));
141 if (!board.contains(to))
143 Diagnostic::error(DiagnosticCode::NotFound, "line of sight target is not on the board", "to"));
144 if (from == to) return Result<bool>::success(true);
145
146 bool reverse = false;
147 auto interior = traceInterior(board, from, to, reverse);
148 for (const Cell cell : interior) {
149 if (blocksSight(board, cell)) return Result<bool>::success(false);
150 }
151 return Result<bool>::success(true);
152}
153
155 if (board.topology() == BoardTopology::ExplicitGraph)
157 DiagnosticCode::Unsupported, "graph cover requires an explicit spatial policy", "topology"));
158 if (attacker.layer != target.layer)
160 Diagnostic::error(DiagnosticCode::Unsupported, "cover does not cross layers", "attacker.layer"));
161 if (!board.contains(target))
163 Diagnostic::error(DiagnosticCode::NotFound, "cover target is not on the board", "target"));
164 if (!board.contains(attacker))
166 Diagnostic::error(DiagnosticCode::NotFound, "cover attacker is not on the board", "attacker"));
167
168 // Full cover first: something sits on the incoming line.
169 bool reverse = false;
170 for (const Cell cell : traceInterior(board, attacker, target, reverse)) {
171 if (blocksSight(board, cell)) return Result<CoverLevel>::success(CoverLevel::Full);
172 }
173 // Half cover: a blocker next to the target, but not between attacker and target.
174 for (const Cell neighbour : board.neighbours(target)) {
175 // A neighbour the board does not contain is not cover a target can use.
176 if (board.contains(neighbour) && blocksSight(board, neighbour))
178 }
180}
181
182std::shared_ptr<ILineOfSightPolicy> gridLineOfSightPolicy() {
183 static const std::shared_ptr<GridLineOfSightPolicy> policy = std::make_shared<GridLineOfSightPolicy>();
184 return policy;
185}
186
187std::shared_ptr<ICoverPolicy> gridCoverPolicy() {
188 static const std::shared_ptr<GridCoverPolicy> policy = std::make_shared<GridCoverPolicy>();
189 return policy;
190}
191
193 Cell origin, int minimum, int maximum, CellRangeMetric metric) {
194 auto inRange = PathQuery::cellsInRange(board, origin, minimum, maximum, metric);
195 if (!inRange) return Result<std::vector<Cell>>::failure(inRange.status());
196 const std::vector<Cell> candidates = std::move(inRange).takeValue();
197
198 std::vector<Cell> visible;
199 visible.reserve(candidates.size());
200 for (const Cell cell : candidates) {
201 auto sight = policy.visible(board, origin, cell);
202 if (!sight) return Result<std::vector<Cell>>::failure(sight.status());
203 if (sight.value()) visible.push_back(cell);
204 }
205 return Result<std::vector<Cell>>::success(std::move(visible));
206}
207
208} // namespace eve::tactics
LogicalId target
Duration start
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string from
Stable, structured diagnostics shared by engine modules.
float maximum[3]
float minimum[3]
std::array< double, 10 > q
double r
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
Injectable line-of-sight and cover policies over the tactical board.
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int level
graphics::Canvas * previous
std::string error
Definition Package.cpp:60
TileLayer * layer
int steps
float t
V3 origin
Definition RoadBake.cpp:138
float dz
float dy
float dx
Cell cell
BoardState board
bool visible
float step
Definition TreeMesh.cpp:314
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Authoritative board facts and occupancy indexes for one battle.
Result< CellState > cell(Cell cell) const
Return an owning copy of a cell fact, or NotFound.
BoardTopology topology() const noexcept
Return the board topology.
Result< CoverLevel > cover(const BoardState &board, Cell attacker, Cell target) const override
Cover.
Result< bool > visible(const BoardState &board, Cell from, Cell to) const override
Visible.
Answers "can @p from see @p to" for one board.
Definition LineOfSight.h:61
virtual Result< bool > visible(const BoardState &board, Cell from, Cell to) const =0
Report whether from can see to.
static Result< std::vector< Cell > > cellsInRange(const BoardState &board, Cell origin, int minimum, int maximum, CellRangeMetric metric)
Enumerate existing cells whose logical distance is within an inclusive range.
@ Cell
A cell was removed; the out-parameter holds it.
std::string_view coverLevelName(CoverLevel level) noexcept
Stable protocol spelling of a cover level.
Result< std::vector< Cell > > visibleCellsInRange(const BoardState &board, const ILineOfSightPolicy &policy, Cell origin, int minimum, int maximum, CellRangeMetric metric)
Cells within a metric range of origin that policy can actually see.
std::shared_ptr< ILineOfSightPolicy > gridLineOfSightPolicy()
The process-wide built-in line-of-sight policy.
CellRangeMetric
Deterministic logical distance used by cellsInRange.
Definition TacticsPath.h:15
constexpr std::string_view kSightBlockerTag
Cell tag that makes a cell block line of sight.
Definition LineOfSight.h:39
CoverLevel
How much protection a cell gives against an attacker.
Definition LineOfSight.h:42
@ None
No protection.
@ Full
Full protection: a blocker sits between the attacker and the target.
@ Half
Partial protection: an adjacent blocker, but not on the incoming line.
std::shared_ptr< ICoverPolicy > gridCoverPolicy()
The process-wide built-in cover policy (same ownership remarks as above).
Logical board coordinate independent of rendering projection.