tess 0.4.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
shape.h
1#pragma once
2
3#include <tess/core/assert.h>
4#include <tess/core/uint128.h>
5
6#include <cstdint>
7#include <limits>
8#include <type_traits>
9
10namespace tess {
11
13struct Extent3 {
14 std::uint64_t x = 0;
15 std::uint64_t y = 0;
16 std::uint64_t z = 1;
17
18 friend constexpr bool operator==(Extent3 lhs, Extent3 rhs) noexcept = default;
19};
20
22struct Coord2 {
23 std::int64_t x = 0;
24 std::int64_t y = 0;
25
26 friend constexpr bool operator==(Coord2 lhs, Coord2 rhs) noexcept = default;
27};
28
30struct Coord3 {
31 std::int64_t x = 0;
32 std::int64_t y = 0;
33 std::int64_t z = 0;
34
35 friend constexpr bool operator==(Coord3 lhs, Coord3 rhs) noexcept = default;
36};
37
40 std::uint64_t x = 0;
41 std::uint64_t y = 0;
42 std::uint64_t z = 0;
43
44 friend constexpr bool operator==(ChunkCoord3 lhs,
45 ChunkCoord3 rhs) noexcept = default;
46};
47
50 std::uint64_t x = 0;
51 std::uint64_t y = 0;
52 std::uint64_t z = 0;
53
54 friend constexpr bool operator==(LocalCoord3 lhs,
55 LocalCoord3 rhs) noexcept = default;
56};
57
60 std::uint64_t value = 0;
61
62 friend constexpr bool operator==(LocalTileId lhs,
63 LocalTileId rhs) noexcept = default;
64};
65
67struct ChunkKey {
68 std::uint64_t value = 0;
69
70 friend constexpr bool operator==(ChunkKey lhs,
71 ChunkKey rhs) noexcept = default;
72};
73
75struct Box3 {
76 Coord3 origin{};
77 Extent3 extent{};
78
79 friend constexpr bool operator==(Box3 lhs, Box3 rhs) noexcept = default;
80};
81
82template <typename Shape>
84struct TileKey;
85
86template <typename Shape>
89 ChunkKey chunk_key{};
90 LocalTileId local_tile_id{};
91
92 friend constexpr bool operator==(ResolvedTile lhs,
93 ResolvedTile rhs) noexcept = default;
94};
95
97[[nodiscard]] constexpr Coord3 to_coord3(Coord2 coord) noexcept {
98 return Coord3{coord.x, coord.y, 0};
99}
100
101namespace detail {
102
103[[nodiscard]] constexpr bool is_power_of_two(std::uint64_t value) noexcept {
104 return value != 0 && (value & (value - 1)) == 0;
105}
106
107[[nodiscard]] constexpr bool is_valid_extent(Extent3 extent) noexcept {
108 return extent.x > 0 && extent.y > 0 && extent.z > 0;
109}
110
111[[nodiscard]] constexpr bool is_divisible_by(Extent3 size,
112 Extent3 chunk) noexcept {
113 return size.x % chunk.x == 0 && size.y % chunk.y == 0 &&
114 size.z % chunk.z == 0;
115}
116
117// Wrap-proof "x * y * z fits uint64" for nonzero axes: UInt128 arithmetic
118// wraps mod 2^128, so a triple product of uint64 axes can wrap and satisfy a
119// fits-uint64 comparison it should fail. Division sidesteps that; purely
120// compile-time (used only in ShapeTraits static_asserts).
121[[nodiscard]] constexpr bool product_fits_uint64(std::uint64_t x,
122 std::uint64_t y,
123 std::uint64_t z) noexcept {
124 constexpr auto max = std::numeric_limits<std::uint64_t>::max();
125 if (x > max / y) {
126 return false;
127 }
128 return x * y <= max / z;
129}
130
131[[nodiscard]] constexpr UInt128 product(Extent3 extent) noexcept {
132 return static_cast<UInt128>(extent.x) * static_cast<UInt128>(extent.y) *
133 static_cast<UInt128>(extent.z);
134}
135
136[[nodiscard]] constexpr UInt128 chunk_count(Extent3 size,
137 Extent3 chunk) noexcept {
138 return static_cast<UInt128>(size.x / chunk.x) *
139 static_cast<UInt128>(size.y / chunk.y) *
140 static_cast<UInt128>(size.z / chunk.z);
141}
142
143[[nodiscard]] constexpr std::uint32_t bit_width(UInt128 value) noexcept {
144 std::uint32_t bits = 0;
145 while (value != 0) {
146 ++bits;
147 value >>= 1;
148 }
149 return bits;
150}
151
152[[nodiscard]] constexpr std::uint32_t bits_for_count(UInt128 count) noexcept {
153 return count <= 1 ? 0 : bit_width(count - 1);
154}
155
156template <std::uint32_t Bits>
157using KeyStorage = std::conditional_t<Bits <= 64, std::uint64_t, UInt128>;
158
159[[nodiscard]] constexpr std::uint64_t magnitude(std::int64_t value) noexcept {
160 return static_cast<std::uint64_t>(-(value + 1)) + 1;
161}
162
163[[nodiscard]] constexpr std::uint64_t axis_delta(std::int64_t origin,
164 std::int64_t coord) noexcept {
165 if (origin >= 0) {
166 return static_cast<std::uint64_t>(coord - origin);
167 }
168
169 const auto origin_magnitude = magnitude(origin);
170 if (coord < 0) {
171 return origin_magnitude - magnitude(coord);
172 }
173
174 return origin_magnitude + static_cast<std::uint64_t>(coord);
175}
176
177[[nodiscard]] constexpr bool axis_contains(std::int64_t origin,
178 std::uint64_t extent,
179 std::int64_t coord) noexcept {
180 if (coord < origin) {
181 return false;
182 }
183
184 return axis_delta(origin, coord) < extent;
185}
186
187// Magnitude of the difference between two signed coordinates without the
188// signed-overflow UB of `lhs - rhs` at the int64 extremes.
189[[nodiscard]] constexpr std::uint64_t abs_delta(std::int64_t lhs,
190 std::int64_t rhs) noexcept {
191 return lhs < rhs
192 ? static_cast<std::uint64_t>(rhs) - static_cast<std::uint64_t>(lhs)
193 : static_cast<std::uint64_t>(lhs) -
194 static_cast<std::uint64_t>(rhs);
195}
196
197[[nodiscard]] constexpr std::uint64_t saturating_add(
198 std::uint64_t lhs, std::uint64_t rhs) noexcept {
199 constexpr auto max = std::numeric_limits<std::uint64_t>::max();
200 return lhs > max - rhs ? max : lhs + rhs;
201}
202
203} // namespace detail
204
206[[nodiscard]] constexpr bool contains(Box3 box, Coord3 coord) noexcept {
207 return detail::axis_contains(box.origin.x, box.extent.x, coord.x) &&
208 detail::axis_contains(box.origin.y, box.extent.y, coord.y) &&
209 detail::axis_contains(box.origin.z, box.extent.z, coord.z);
210}
211
215[[nodiscard]] constexpr auto manhattan_distance(Coord3 lhs, Coord3 rhs) noexcept
216 -> std::uint64_t {
217 return detail::saturating_add(
218 detail::abs_delta(lhs.x, rhs.x),
219 detail::saturating_add(detail::abs_delta(lhs.y, rhs.y),
220 detail::abs_delta(lhs.z, rhs.z)));
221}
222
223template <Extent3 Size, Extent3 Chunk>
225struct Shape {
226 static constexpr Extent3 size = Size;
227 static constexpr Extent3 chunk = Chunk;
228
229 static_assert(detail::is_valid_extent(Size),
230 "Shape size dimensions must be greater than zero.");
231 static_assert(detail::is_valid_extent(Chunk),
232 "Shape chunk dimensions must be greater than zero.");
233 static_assert(detail::is_power_of_two(Chunk.x) &&
234 detail::is_power_of_two(Chunk.y) &&
235 detail::is_power_of_two(Chunk.z),
236 "Shape chunk dimensions must be powers of two.");
237 static_assert(detail::is_divisible_by(Size, Chunk),
238 "Shape size dimensions must be multiples of chunk "
239 "dimensions.");
240};
241
242template <typename Shape>
245 static constexpr Extent3 size = Shape::size;
246 static constexpr Extent3 chunk = Shape::chunk;
247
248 static constexpr std::uint64_t chunk_count_x = size.x / chunk.x;
249 static constexpr std::uint64_t chunk_count_y = size.y / chunk.y;
250 static constexpr std::uint64_t chunk_count_z = size.z / chunk.z;
251
252 static constexpr auto precise_chunk_count = detail::chunk_count(size, chunk);
253 static constexpr auto precise_local_tile_count = detail::product(chunk);
254
255 // The precise_* UInt128 products wrap mod 2^128 for extreme axes, which
256 // would defeat the fits-uint64 asserts below; establish wrap-proof
257 // (division-based) bounds first. Also require each size axis to fit int64
258 // so coord() cannot wrap a Coord3 component. Compile-time only.
259 static_assert(detail::product_fits_uint64(chunk_count_x, chunk_count_y,
260 chunk_count_z),
261 "Shape chunk count must fit std::uint64_t without wrapping.");
262 static_assert(detail::product_fits_uint64(chunk.x, chunk.y, chunk.z),
263 "Shape local tile count must fit std::uint64_t without "
264 "wrapping.");
265 static_assert(
266 size.x <= static_cast<std::uint64_t>(
267 std::numeric_limits<std::int64_t>::max()) &&
268 size.y <= static_cast<std::uint64_t>(
269 std::numeric_limits<std::int64_t>::max()) &&
270 size.z <= static_cast<std::uint64_t>(
271 std::numeric_limits<std::int64_t>::max()),
272 "Shape size axes must fit std::int64_t so coordinates cannot wrap.");
273
274 static_assert(precise_chunk_count <=
275 static_cast<detail::UInt128>(
276 std::numeric_limits<std::uint64_t>::max()),
277 "Shape chunk count must fit std::uint64_t.");
278 static_assert(precise_local_tile_count <=
279 static_cast<detail::UInt128>(
280 std::numeric_limits<std::uint64_t>::max()),
281 "Shape local tile count must fit LocalTileId.");
282
283 static constexpr std::uint64_t chunk_count =
284 static_cast<std::uint64_t>(precise_chunk_count);
285 static constexpr std::uint64_t local_tile_count =
286 static_cast<std::uint64_t>(precise_local_tile_count);
287
288 static constexpr std::uint32_t local_bits =
289 detail::bits_for_count(precise_local_tile_count);
290 static constexpr std::uint32_t chunk_bits =
291 detail::bits_for_count(precise_chunk_count);
292 static constexpr std::uint32_t tile_key_bits = local_bits + chunk_bits;
293
294 static_assert(chunk_bits <= 64, "ChunkKey must fit std::uint64_t.");
295 static_assert(tile_key_bits <= 128, "TileKey must fit u64 or u128.");
296
297 using TileKeyStorage = detail::KeyStorage<tile_key_bits>;
298
299 static constexpr bool single_chunk = chunk_count == 1;
300 static constexpr bool degenerate_x = size.x == 1;
301 static constexpr bool degenerate_y = size.y == 1;
302 static constexpr bool degenerate_z = size.z == 1;
303};
304
305template <typename Shape>
307struct TileKey {
308 ShapeTraits<Shape>::TileKeyStorage value{};
309
310 friend constexpr bool operator==(TileKey lhs, TileKey rhs) noexcept = default;
311};
312
313template <typename Shape>
315[[nodiscard]] constexpr bool contains(Coord3 coord) noexcept {
316 return contains(Box3{Coord3{0, 0, 0}, ShapeTraits<Shape>::size}, coord);
317}
318
319template <typename Shape>
321[[nodiscard]] constexpr ChunkCoord3 chunk_coord(Coord3 coord) noexcept {
322 const auto chunk = ShapeTraits<Shape>::chunk;
323 return ChunkCoord3{
324 static_cast<std::uint64_t>(coord.x) / chunk.x,
325 static_cast<std::uint64_t>(coord.y) / chunk.y,
326 static_cast<std::uint64_t>(coord.z) / chunk.z,
327 };
328}
329
330template <typename Shape>
332[[nodiscard]] constexpr LocalCoord3 local_coord(Coord3 coord) noexcept {
333 const auto chunk = ShapeTraits<Shape>::chunk;
334 return LocalCoord3{
335 static_cast<std::uint64_t>(coord.x) % chunk.x,
336 static_cast<std::uint64_t>(coord.y) % chunk.y,
337 static_cast<std::uint64_t>(coord.z) % chunk.z,
338 };
339}
340
341template <typename Shape>
343[[nodiscard]] constexpr LocalTileId local_tile_id(LocalCoord3 coord) noexcept {
344 const auto chunk = ShapeTraits<Shape>::chunk;
345 return LocalTileId{coord.x + coord.y * chunk.x + coord.z * chunk.x * chunk.y};
346}
347
348template <typename Shape>
350[[nodiscard]] constexpr Coord3 coord(ChunkCoord3 chunk_coord,
351 LocalTileId local_tile_id) noexcept {
352 const auto chunk = ShapeTraits<Shape>::chunk;
353 const auto local_xy = chunk.x * chunk.y;
354 const auto local_z = local_tile_id.value / local_xy;
355 const auto remainder = local_tile_id.value % local_xy;
356 const auto local_y = remainder / chunk.x;
357 const auto local_x = remainder % chunk.x;
358
359 return Coord3{
360 static_cast<std::int64_t>(chunk_coord.x * chunk.x + local_x),
361 static_cast<std::int64_t>(chunk_coord.y * chunk.y + local_y),
362 static_cast<std::int64_t>(chunk_coord.z * chunk.z + local_z),
363 };
364}
365
366template <typename Shape>
368[[nodiscard]] constexpr ChunkKey chunk_key(ChunkCoord3 coord) noexcept {
369 using Traits = ShapeTraits<Shape>;
370 return ChunkKey{coord.x + coord.y * Traits::chunk_count_x +
371 coord.z * Traits::chunk_count_x * Traits::chunk_count_y};
372}
373
374template <typename Shape>
376[[nodiscard]] constexpr ChunkCoord3 chunk_coord(ChunkKey key) noexcept {
377 using Traits = ShapeTraits<Shape>;
378 const auto chunk_xy = Traits::chunk_count_x * Traits::chunk_count_y;
379 const auto z = key.value / chunk_xy;
380 const auto remainder = key.value % chunk_xy;
381 const auto y = remainder / Traits::chunk_count_x;
382 const auto x = remainder % Traits::chunk_count_x;
383
384 return ChunkCoord3{x, y, z};
385}
386
387template <typename Shape>
389[[nodiscard]] constexpr TileKey<Shape> tile_key(Coord3 coord) noexcept {
390 using Traits = ShapeTraits<Shape>;
391 TESS_ASSERT(contains<Shape>(coord));
392 const auto local = local_tile_id<Shape>(local_coord<Shape>(coord));
393 using Storage = Traits::TileKeyStorage;
394
395 if constexpr (Traits::chunk_bits == 0) {
396 // Single-chunk shapes: the key is the local id. Shifting the chunk key
397 // into place would shift by local_bits, which may equal the storage
398 // width and would be undefined behavior.
399 return TileKey<Shape>{static_cast<Storage>(local.value)};
400 } else {
401 const auto chunk = chunk_key<Shape>(chunk_coord<Shape>(coord));
402 return TileKey<Shape>{
403 (static_cast<Storage>(chunk.value) << Traits::local_bits) |
404 static_cast<Storage>(local.value),
405 };
406 }
407}
408
409template <typename Shape>
411[[nodiscard]] constexpr ChunkKey chunk_key(TileKey<Shape> key) noexcept {
412 using Traits = ShapeTraits<Shape>;
413 if constexpr (Traits::chunk_bits == 0) {
414 // Single-chunk shapes store only the local id; shifting right by
415 // local_bits may equal the storage width and would be undefined
416 // behavior.
417 static_cast<void>(key);
418 return ChunkKey{0};
419 } else {
420 return ChunkKey{
421 static_cast<std::uint64_t>(key.value >> Traits::local_bits),
422 };
423 }
424}
425
426template <typename Shape>
428[[nodiscard]] constexpr LocalTileId local_tile_id(TileKey<Shape> key) noexcept {
429 using Traits = ShapeTraits<Shape>;
430 using Storage = Traits::TileKeyStorage;
431 Storage mask = 0;
432 if constexpr (Traits::local_bits == 64) {
433 mask = static_cast<Storage>(std::numeric_limits<std::uint64_t>::max());
434 } else {
435 mask = (static_cast<Storage>(1) << Traits::local_bits) - 1;
436 }
437
438 return LocalTileId{static_cast<std::uint64_t>(key.value & mask)};
439}
440
441template <typename Shape>
443[[nodiscard]] constexpr Coord3 coord(TileKey<Shape> key) noexcept {
444 return coord<Shape>(chunk_coord<Shape>(chunk_key<Shape>(key)),
445 local_tile_id<Shape>(key));
446}
447
448} // namespace tess
Definition shape.h:75
Definition shape.h:39
Definition shape.h:67
Definition shape.h:22
Definition shape.h:30
Definition shape.h:13
Definition shape.h:49
Definition shape.h:59
Definition shape.h:88
Definition shape.h:244
Definition shape.h:225
Definition shape.h:307