tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
span.h
1#pragma once
2
3#include <tess/core/assert.h>
4#include <tess/core/shape.h>
5
6#include <algorithm>
7#include <cmath>
8#include <cstdint>
9#include <functional>
10#include <limits>
11
12namespace tess {
13
15struct TileSpan {
16 Coord3 origin{};
17 std::uint32_t x_count = 0;
18
19 friend constexpr bool operator==(TileSpan lhs,
20 TileSpan rhs) noexcept = default;
21};
22
25 std::uint64_t spans = 0;
26 std::uint64_t tiles = 0;
27};
28
29template <typename Shape>
31[[nodiscard]] constexpr auto shape_bounds() noexcept -> Box3 {
32 return Box3{{0, 0, 0}, ShapeTraits<Shape>::size};
33}
34
35namespace detail {
36
37struct ClippedAxis {
38 std::int64_t origin = 0;
39 std::uint64_t count = 0;
40};
41
42[[nodiscard]] constexpr auto clip_axis(std::int64_t origin,
43 std::uint64_t extent,
44 std::uint64_t limit) noexcept
45 -> ClippedAxis {
46 if (extent == 0 || limit == 0) {
47 return {};
48 }
49 const auto first = std::max<std::int64_t>(origin, 0);
50 const auto skipped = tess::detail::axis_delta(origin, first);
51 const auto first_unsigned = static_cast<std::uint64_t>(first);
52 if (skipped >= extent || first_unsigned >= limit) {
53 return {};
54 }
55 return ClippedAxis{
56 first,
57 std::min(extent - skipped, limit - first_unsigned),
58 };
59}
60
61[[nodiscard]] constexpr auto lower_radius_bound(std::int64_t center,
62 std::uint32_t radius) noexcept
63 -> std::int64_t {
64 constexpr auto min = std::numeric_limits<std::int64_t>::min();
65 const auto distance = static_cast<std::uint64_t>(radius);
66 if (center < 0 && tess::detail::magnitude(center) >
67 tess::detail::magnitude(min) - distance) {
68 return min;
69 }
70 return center - static_cast<std::int64_t>(radius);
71}
72
73[[nodiscard]] inline auto integer_sqrt(std::uint64_t value) noexcept
74 -> std::uint32_t {
75 auto root =
76 static_cast<std::uint64_t>(std::sqrt(static_cast<long double>(value)));
77 while (root != 0 && root > value / root) {
78 --root;
79 }
80 while (root < std::numeric_limits<std::uint32_t>::max() &&
81 root + 1 <= value / (root + 1)) {
82 ++root;
83 }
84 return static_cast<std::uint32_t>(root);
85}
86
87constexpr void add_tiles(SpanQueryStats& stats, std::uint64_t count) noexcept {
88 stats.tiles = tess::detail::saturating_add(stats.tiles, count);
89}
90
91template <typename Fn>
92void emit_x_runs(std::int64_t x, std::uint64_t count, std::int64_t y,
93 std::int64_t z, SpanQueryStats& stats, Fn& fn) {
94 constexpr auto max = std::numeric_limits<std::uint32_t>::max();
95 auto emitted = std::uint64_t{0};
96 while (emitted < count) {
97 const auto run = static_cast<std::uint32_t>(
98 std::min<std::uint64_t>(count - emitted, max));
99 std::invoke(fn,
100 TileSpan{{x + static_cast<std::int64_t>(emitted), y, z}, run});
101 stats.spans = tess::detail::saturating_add(stats.spans, 1);
102 add_tiles(stats, run);
103 emitted += run;
104 }
105}
106
107} // namespace detail
108
109template <typename Shape, typename Fn>
111auto for_each_box_span(Box3 box, Fn&& fn) -> SpanQueryStats {
112 const auto size = ShapeTraits<Shape>::size;
113 const auto x = detail::clip_axis(box.origin.x, box.extent.x, size.x);
114 const auto y = detail::clip_axis(box.origin.y, box.extent.y, size.y);
115 const auto z = detail::clip_axis(box.origin.z, box.extent.z, size.z);
116 auto stats = SpanQueryStats{};
117 if (x.count == 0 || y.count == 0 || z.count == 0) {
118 return stats;
119 }
120 auto&& callback = fn;
121 for (std::uint64_t z_offset = 0; z_offset < z.count; ++z_offset) {
122 for (std::uint64_t y_offset = 0; y_offset < y.count; ++y_offset) {
123 detail::emit_x_runs(
124 x.origin, x.count, y.origin + static_cast<std::int64_t>(y_offset),
125 z.origin + static_cast<std::int64_t>(z_offset), stats, callback);
126 }
127 }
128 return stats;
129}
130
131template <typename Shape, typename Fn>
133auto for_each_radius_span(Coord3 center, std::uint32_t radius, Fn&& fn)
134 -> SpanQueryStats {
135 const auto diameter = static_cast<std::uint64_t>(radius) * 2 + 1;
136 const auto broad = Box3{
137 {
138 detail::lower_radius_bound(center.x, radius),
139 detail::lower_radius_bound(center.y, radius),
140 detail::lower_radius_bound(center.z, radius),
141 },
142 {diameter, diameter, diameter},
143 };
144 const auto size = ShapeTraits<Shape>::size;
145 const auto y = detail::clip_axis(broad.origin.y, broad.extent.y, size.y);
146 const auto z = detail::clip_axis(broad.origin.z, broad.extent.z, size.z);
147 const auto radius_squared = static_cast<std::uint64_t>(radius) * radius;
148 auto stats = SpanQueryStats{};
149 auto&& callback = fn;
150
151 for (std::uint64_t z_offset = 0; z_offset < z.count; ++z_offset) {
152 const auto world_z = z.origin + static_cast<std::int64_t>(z_offset);
153 const auto dz = tess::detail::abs_delta(center.z, world_z);
154 if (dz > radius) {
155 continue;
156 }
157 const auto dz_squared = dz * dz;
158 for (std::uint64_t y_offset = 0; y_offset < y.count; ++y_offset) {
159 const auto world_y = y.origin + static_cast<std::int64_t>(y_offset);
160 const auto dy = tess::detail::abs_delta(center.y, world_y);
161 if (dy > radius) {
162 continue;
163 }
164 const auto dy_squared = dy * dy;
165 if (dy_squared > radius_squared - dz_squared) {
166 continue;
167 }
168 const auto half_width =
169 detail::integer_sqrt(radius_squared - dz_squared - dy_squared);
170 const auto x = detail::clip_axis(
171 detail::lower_radius_bound(center.x, half_width),
172 static_cast<std::uint64_t>(half_width) * 2 + 1, size.x);
173 detail::emit_x_runs(x.origin, x.count, world_y, world_z, stats, callback);
174 }
175 }
176 return stats;
177}
178
179template <typename Shape, typename Fn>
181auto for_each_chunk_span(ChunkKey key, Box3 local_box, Fn&& fn)
182 -> SpanQueryStats {
183 using Traits = ShapeTraits<Shape>;
184 TESS_ASSERT(key.value < Traits::chunk_count);
185 if (key.value >= Traits::chunk_count) {
186 return {};
187 }
188 const auto chunk = chunk_coord<Shape>(key);
189 const auto base = Coord3{
190 static_cast<std::int64_t>(chunk.x * Traits::chunk.x),
191 static_cast<std::int64_t>(chunk.y * Traits::chunk.y),
192 static_cast<std::int64_t>(chunk.z * Traits::chunk.z),
193 };
194 const auto x = detail::clip_axis(local_box.origin.x, local_box.extent.x,
195 Traits::chunk.x);
196 const auto y = detail::clip_axis(local_box.origin.y, local_box.extent.y,
197 Traits::chunk.y);
198 const auto z = detail::clip_axis(local_box.origin.z, local_box.extent.z,
199 Traits::chunk.z);
200 auto stats = SpanQueryStats{};
201 if (x.count == 0 || y.count == 0 || z.count == 0) {
202 return stats;
203 }
204 auto&& callback = fn;
205 for (std::uint64_t z_offset = 0; z_offset < z.count; ++z_offset) {
206 for (std::uint64_t y_offset = 0; y_offset < y.count; ++y_offset) {
207 detail::emit_x_runs(
208 base.x + x.origin, x.count,
209 base.y + y.origin + static_cast<std::int64_t>(y_offset),
210 base.z + z.origin + static_cast<std::int64_t>(z_offset), stats,
211 callback);
212 }
213 }
214 return stats;
215}
216
217} // namespace tess
Definition shape.h:94
Definition shape.h:86
Definition shape.h:46
Definition shape.h:320
Counts exact canonical tiles and emitted x-axis runs for one query.
Definition span.h:24
One contiguous canonical x-axis run at a fixed world y and z coordinate.
Definition span.h:15