Skip to content

Commit ad46da0

Browse files
committed
make path finding and field of vision generic
1 parent 245b0d6 commit ad46da0

File tree

6 files changed

+548
-469
lines changed

6 files changed

+548
-469
lines changed

include/gf2/core/FieldOfVision.h

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// SPDX-License-Identifier: Zlib
2+
// Copyright (c) 2023-2025 Julien Bernard
3+
#ifndef GF_FIELD_OF_VISION_H
4+
#define GF_FIELD_OF_VISION_H
5+
6+
#include <cassert>
7+
#include <queue>
8+
9+
#include "Array2D.h"
10+
#include "Direction.h"
11+
#include "Rational.h"
12+
#include "Vec2.h"
13+
14+
namespace gf {
15+
16+
/*
17+
* Symmetric Shadowcasting
18+
* based on https://github.com/370417/symmetric-shadowcasting
19+
* License: CC0-1.0
20+
*/
21+
22+
namespace details {
23+
24+
template<typename T>
25+
T round_ties_up(const Rational<T>& rat)
26+
{
27+
T q = rat.numerator() / rat.denominator();
28+
T r = rat.numerator() % rat.denominator();
29+
30+
while (r < 0) {
31+
r += rat.denominator();
32+
--q;
33+
}
34+
35+
if (2 * r >= rat.denominator()) {
36+
return q + 1;
37+
}
38+
39+
return q;
40+
}
41+
42+
template<typename T>
43+
T round_ties_down(const Rational<T>& rat)
44+
{
45+
T q = rat.numerator() / rat.denominator();
46+
T r = rat.numerator() % rat.denominator();
47+
48+
while (r < 0) {
49+
r += rat.denominator();
50+
--q;
51+
}
52+
53+
if (2 * r > rat.denominator()) {
54+
return q + 1;
55+
}
56+
57+
return q;
58+
}
59+
60+
struct Quadrant {
61+
Direction direction;
62+
Vec2I origin;
63+
64+
Vec2I transform(Vec2I position) const
65+
{
66+
switch (direction) {
67+
case Direction::Up:
68+
return { origin.x + position.x, origin.y - position.y };
69+
case Direction::Down:
70+
return { origin.x + position.x, origin.y + position.y };
71+
case Direction::Right:
72+
return { origin.x + position.y, origin.y + position.x };
73+
case Direction::Left:
74+
return { origin.x - position.y, origin.y + position.x };
75+
76+
default:
77+
break;
78+
}
79+
80+
assert(false);
81+
return { 0, 0 };
82+
}
83+
};
84+
85+
struct Row {
86+
int32_t depth = 0;
87+
Rational<int32_t> start_slope;
88+
Rational<int32_t> end_slope;
89+
90+
PositionRange tiles() const
91+
{
92+
auto min_x = round_ties_down(depth * start_slope);
93+
auto max_x = round_ties_up(depth * end_slope);
94+
return rectangle_range(RectI::from_position_size({ min_x, depth }, { max_x - min_x + 1, 1 }));
95+
}
96+
97+
Row next() const
98+
{
99+
return { depth + 1, start_slope, end_slope };
100+
}
101+
};
102+
103+
inline bool is_symmetric(const Row& row, Vec2I position)
104+
{
105+
return row.depth * row.start_slope <= position.x && position.x <= row.depth * row.end_slope;
106+
}
107+
108+
inline Rational<int32_t> slope(Vec2I position)
109+
{
110+
return { (2 * position.x) - 1, 2 * position.y };
111+
}
112+
113+
template<typename InputCell, typename OutputCell, typename Function>
114+
void compute_visibility_in_quadrant(const Array2D<InputCell>& input_cells, Array2D<OutputCell>& output_cells, Vec2I origin, int range_limit, Quadrant quadrant, Function set_visible) // NOLINT(readability-function-cognitive-complexity)
115+
{
116+
const int square_range_limit = square(range_limit);
117+
const Row first_row = { 1, -1, 1 };
118+
119+
std::queue<Row> rows;
120+
rows.push(first_row);
121+
122+
while (!rows.empty()) {
123+
Row row = rows.front();
124+
rows.pop();
125+
126+
std::optional<Vec2I> prev_absolute;
127+
128+
const PositionRange range = row.tiles();
129+
130+
for (const Vec2I position : range) {
131+
const Vec2I absolute = quadrant.transform(position);
132+
133+
if (square_distance(absolute, origin) > square_range_limit) {
134+
continue;
135+
}
136+
137+
if (!input_cells.valid(absolute)) {
138+
continue;
139+
}
140+
141+
const InputCell& input_cell = input_cells(absolute);
142+
OutputCell& output_cell = output_cells(absolute);
143+
144+
if (!input_cell.transparent() || is_symmetric(row, position)) {
145+
set_visible(output_cell);
146+
}
147+
148+
if (prev_absolute) {
149+
const InputCell& prev_input_cell = input_cells(*prev_absolute);
150+
151+
if (!prev_input_cell.transparent() && input_cell.transparent()) {
152+
row.start_slope = slope(position);
153+
}
154+
155+
if (prev_input_cell.transparent() && !input_cell.transparent()) {
156+
Row next_row = row.next();
157+
next_row.end_slope = slope(position);
158+
rows.push(next_row);
159+
}
160+
}
161+
162+
prev_absolute = absolute;
163+
}
164+
165+
if (prev_absolute && input_cells(*prev_absolute).transparent()) {
166+
rows.push(row.next());
167+
}
168+
}
169+
}
170+
171+
}
172+
173+
template<typename InputCell, typename OutputCell, typename Function>
174+
void compute_symmetric_shadowcasting(const Array2D<InputCell>& input_cells, Array2D<OutputCell>& output_cells, Vec2I origin, int range_limit, Function set_visible)
175+
{
176+
assert(input_cells.size() == output_cells.size());
177+
assert(output_cells.valid(origin));
178+
179+
OutputCell& cell = output_cells(origin);
180+
set_visible(cell);
181+
182+
for (auto direction : { Direction::Up, Direction::Left, Direction::Down, Direction::Right }) {
183+
const details::Quadrant quadrant = { direction, origin };
184+
details::compute_visibility_in_quadrant(input_cells, output_cells, origin, range_limit, quadrant, set_visible);
185+
}
186+
}
187+
188+
}
189+
190+
#endif // GF_FIELD_OF_VISION_H

include/gf2/core/GridMap.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include "Array2D.h"
1111
#include "CoreApi.h"
1212
#include "Flags.h"
13+
#include "GridTypes.h"
1314
#include "Math.h"
15+
#include "Vec2.h"
1416

1517
namespace gf {
1618

@@ -99,7 +101,21 @@ namespace gf {
99101

100102
void raw_compute_field_of_vision(Vec2I origin, int range_limit, Flags<CellProperty> properties);
101103

102-
Array2D<Flags<CellProperty>> m_cells;
104+
struct Cell {
105+
Flags<CellProperty> flags;
106+
107+
bool walkable() const
108+
{
109+
return flags.test(CellProperty::Walkable);
110+
}
111+
112+
bool transparent() const
113+
{
114+
return flags.test(CellProperty::Transparent);
115+
}
116+
};
117+
118+
Array2D<Cell> m_cells;
103119
Array2D<uint32_t> m_tags;
104120
AnyGrid m_grid;
105121
};

0 commit comments

Comments
 (0)