ASW Lib
A.D.S. Games SDL Wrapper Library. A library targeted at Allegro4 users who want to switch to SDL3 and use modern c++.
Loading...
Searching...
No Matches
geometry.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6#include <limits>
7#include <numbers>
8#include <utility>
9
10namespace {
11constexpr float TAU = std::numbers::pi_v<float> * 2.0F;
12
13// Small turn either side of each corner, so rays go past it
14constexpr float CORNER_NUDGE = 0.0001F;
15
16using asw::Vec2f;
17
18struct Ray {
19 // Angle relative to the view direction, used to sort the rays
20 float angle;
22};
23
24struct Segment {
27
28 // Distance from the view point to the closest point of the segment. No
29 // ray can hit the segment closer than this.
30 float distance;
31};
32
33Vec2f rotate(const Vec2f& v, float cos_a, float sin_a)
34{
35 return { (v.x * cos_a) - (v.y * sin_a), (v.x * sin_a) + (v.y * cos_a) };
36}
37
38float distance_to_segment(const Vec2f& p, const Vec2f& a, const Vec2f& b)
39{
40 const Vec2f edge = b - a;
41 const float length_sq = edge.dot(edge);
42 const float t = length_sq > 0.0F ? std::clamp((p - a).dot(edge) / length_sq, 0.0F, 1.0F) : 0.0F;
43 return (p - (a + (edge * t))).magnitude();
44}
45
46// Clip a segment to an axis aligned box (Liang-Barsky). Returns false when
47// no part of it is inside.
48bool clip_segment(Vec2f& a, Vec2f& b, const Vec2f& min, const Vec2f& max)
49{
50 const Vec2f d = b - a;
51 float t0 = 0.0F;
52 float t1 = 1.0F;
53
54 const std::array<std::pair<float, float>, 4> edges { {
55 { -d.x, a.x - min.x },
56 { d.x, max.x - a.x },
57 { -d.y, a.y - min.y },
58 { d.y, max.y - a.y },
59 } };
60 for (const auto& [p, q] : edges) {
61 if (p == 0.0F) {
62 if (q < 0.0F) {
63 return false;
64 }
65 continue;
66 }
67
68 const float r = q / p;
69 if (p < 0.0F) {
70 t0 = std::max(t0, r);
71 } else {
72 t1 = std::min(t1, r);
73 }
74 if (t0 > t1) {
75 return false;
76 }
77 }
78
79 const Vec2f start = a;
80 a = start + (d * t0);
81 b = start + (d * t1);
82 return true;
83}
84} // namespace
85
86std::optional<float> asw::geometry::ray_hit(
87 const Vec2f& origin, const Vec2f& direction, const Vec2f& a, const Vec2f& b)
88{
89 const Vec2f edge = b - a;
90 const float denom = direction.cross(edge);
91 if (std::abs(denom) < 1e-8F) {
92 return std::nullopt;
93 }
94
95 const Vec2f to_a = a - origin;
96 const float t = to_a.cross(edge) / denom;
97 const float u = to_a.cross(direction) / denom;
98 if (t < 0.0F || u < 0.0F || u > 1.0F) {
99 return std::nullopt;
100 }
101 return t;
102}
103
105 const std::vector<Polygonf>& occluders, float direction, float cone)
106{
107 Polygonf result;
108 visibility(result, from, radius, occluders, direction, cone);
109 return result;
110}
111
112void asw::geometry::visibility(Polygonf& result, const Vec2f& from, float radius,
113 const std::vector<Polygonf>& occluders, float direction, float cone)
114{
115 thread_local std::vector<Quadf> occluder_bounds;
116 occluder_bounds.clear();
117 for (const auto& polygon : occluders) {
118 occluder_bounds.push_back(bounds(polygon));
119 }
120 visibility(result, from, radius, occluders, occluder_bounds, direction, cone);
121}
122
123void asw::geometry::visibility(Polygonf& result, const Vec2f& from, float radius,
124 const std::vector<Polygonf>& occluders, const std::vector<Quadf>& occluder_bounds,
125 float direction, float cone)
126{
127 result.clear();
128 if (!(radius > 0.0F)) {
129 return;
130 }
131
132 thread_local std::vector<Segment> segments;
133 thread_local std::vector<Vec2f> corners;
134 thread_local std::vector<Ray> rays;
135 segments.clear();
136 corners.clear();
137 rays.clear();
138
139 // The edge of the square the view reaches
140 const std::array<Vec2f, 4> square { {
141 { from.x - radius, from.y - radius },
142 { from.x + radius, from.y - radius },
143 { from.x + radius, from.y + radius },
144 { from.x - radius, from.y + radius },
145 } };
146 for (std::size_t i = 0; i < square.size(); ++i) {
147 const auto& a = square[i];
148 const auto& b = square[(i + 1) % square.size()];
149 segments.push_back({ a, b, distance_to_segment(from, a, b) });
150 corners.push_back(a);
151 }
152
153 // Edges are clipped to the square. Where an edge crosses the square is a
154 // corner of the visible area too, so it gets rays like any other corner.
155 const asw::Quadf reach(square[0], Vec2f(radius * 2.0F, radius * 2.0F));
156 for (std::size_t p = 0; p < occluders.size(); ++p) {
157 const auto& polygon = occluders[p];
158 if (polygon.size() < 2 || p >= occluder_bounds.size()
159 || !occluder_bounds[p].collides(reach)) {
160 continue;
161 }
162 for (std::size_t i = 0; i < polygon.size(); ++i) {
163 Vec2f a = polygon[i];
164 Vec2f b = polygon[(i + 1) % polygon.size()];
165 const Vec2f original_b = b;
166 if (!clip_segment(a, b, square[0], square[2])) {
167 continue;
168 }
169
170 segments.push_back({ a, b, distance_to_segment(from, a, b) });
171
172 // Each corner inside the square starts exactly one edge, so the
173 // first end of each edge gives it once. A clipped second end
174 // starts no edge, so add it here.
175 corners.push_back(a);
176 if (b != original_b) {
177 corners.push_back(b);
178 }
179 }
180 }
181
182 // Nearest edges first, so each ray can stop once the rest are too far
183 std::ranges::sort(segments, { }, &Segment::distance);
184
185 const bool spot = cone > 0.0F && cone < TAU;
186 const float half_cone = cone / 2.0F;
187 auto add_ray = [&](float angle, const Vec2f& dir) {
188 if (!spot || std::abs(angle) <= half_cone) {
189 rays.push_back({ angle, dir });
190 }
191 };
192
193 // Aim at each corner and just either side of it
194 const float nudge_cos = std::cos(CORNER_NUDGE);
195 const float nudge_sin = std::sin(CORNER_NUDGE);
196 for (const auto& corner : corners) {
197 const Vec2f to = corner - from;
198 const float length = to.magnitude();
199 if (length <= 0.0F) {
200 continue;
201 }
202
203 const Vec2f dir = to / length;
204 const float angle = std::remainder(std::atan2(dir.y, dir.x) - direction, TAU);
205 add_ray(angle - CORNER_NUDGE, rotate(dir, nudge_cos, -nudge_sin));
206 add_ray(angle, dir);
207 add_ray(angle + CORNER_NUDGE, rotate(dir, nudge_cos, nudge_sin));
208 }
209
210 if (spot) {
211 for (const float edge : { -half_cone, half_cone }) {
212 add_ray(edge, Vec2f(std::cos(direction + edge), std::sin(direction + edge)));
213 }
214 }
215
216 std::ranges::sort(rays, { }, &Ray::angle);
217
218 result.reserve(rays.size());
219 for (const auto& ray : rays) {
220 // Ray directions have length 1, so a hit's t is its distance
221 float nearest = std::numeric_limits<float>::max();
222 for (const auto& segment : segments) {
223 if (segment.distance >= nearest) {
224 break;
225 }
226 if (const auto t = ray_hit(from, ray.dir, segment.a, segment.b)) {
227 nearest = std::min(nearest, *t);
228 }
229 }
230
231 if (nearest < std::numeric_limits<float>::max()) {
232 result.push_back(from + (ray.dir * nearest));
233 }
234 }
235}
T cross(const Vec2 &other) const
Calculate the cross product of two vectors.
Definition geometry.h:129
T y
The y component of the vector.
Definition geometry.h:280
T x
The x component of the vector.
Definition geometry.h:277
Real magnitude() const
Calculate the magnitude of the vector.
Definition geometry.h:138
T dot(const Vec2 &other) const
Calculate the dot product of two vectors.
Definition geometry.h:119
Common geometry types.
bool clip_segment(Vec2f &a, Vec2f &b, const Vec2f &min, const Vec2f &max)
Definition geometry.cpp:48
Vec2f rotate(const Vec2f &v, float cos_a, float sin_a)
Definition geometry.cpp:33
float distance_to_segment(const Vec2f &p, const Vec2f &a, const Vec2f &b)
Definition geometry.cpp:38
Polygonf visibility(const Vec2f &from, float radius, const std::vector< Polygonf > &occluders, float direction=0.0F, float cone=0.0F)
Find the area that can be seen from a point.
Definition geometry.cpp:104
std::optional< float > ray_hit(const Vec2f &origin, const Vec2f &direction, const Vec2f &a, const Vec2f &b)
Find where a ray hits a line segment.
Definition geometry.cpp:86
Quad< T > bounds(const Polygon< T > &polygon)
Get the smallest rectangle that holds every corner of a polygon.
Definition geometry.h:766
Polygon< float > Polygonf
Definition geometry.h:741