3#include <tess/core/assert.h>
4#include <tess/core/uint128.h>
18 friend constexpr bool operator==(
Extent3 lhs,
Extent3 rhs)
noexcept =
default;
26 friend constexpr bool operator==(
Coord2 lhs,
Coord2 rhs)
noexcept =
default;
35 friend constexpr bool operator==(
Coord3 lhs,
Coord3 rhs)
noexcept =
default;
60 std::uint64_t value = 0;
68 std::uint64_t value = 0;
70 friend constexpr bool operator==(
ChunkKey lhs,
79 friend constexpr bool operator==(
Box3 lhs,
Box3 rhs)
noexcept =
default;
82template <
typename Shape>
86template <
typename Shape>
97[[nodiscard]]
constexpr Coord3 to_coord3(
Coord2 coord)
noexcept {
98 return Coord3{coord.x, coord.y, 0};
103[[nodiscard]]
constexpr bool is_power_of_two(std::uint64_t value)
noexcept {
104 return value != 0 && (value & (value - 1)) == 0;
107[[nodiscard]]
constexpr bool is_valid_extent(Extent3 extent)
noexcept {
108 return extent.x > 0 && extent.y > 0 && extent.z > 0;
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;
121[[nodiscard]]
constexpr bool product_fits_uint64(std::uint64_t x,
123 std::uint64_t z)
noexcept {
124 constexpr auto max = std::numeric_limits<std::uint64_t>::max();
128 return x * y <= max / z;
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);
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);
143[[nodiscard]]
constexpr std::uint32_t bit_width(UInt128 value)
noexcept {
144 std::uint32_t bits = 0;
152[[nodiscard]]
constexpr std::uint32_t bits_for_count(UInt128 count)
noexcept {
153 return count <= 1 ? 0 : bit_width(count - 1);
156template <std::u
int32_t Bits>
157using KeyStorage = std::conditional_t<Bits <= 64, std::uint64_t, UInt128>;
159[[nodiscard]]
constexpr std::uint64_t magnitude(std::int64_t value)
noexcept {
160 return static_cast<std::uint64_t
>(-(value + 1)) + 1;
163[[nodiscard]]
constexpr std::uint64_t axis_delta(std::int64_t origin,
164 std::int64_t coord)
noexcept {
166 return static_cast<std::uint64_t
>(coord - origin);
169 const auto origin_magnitude = magnitude(origin);
171 return origin_magnitude - magnitude(coord);
174 return origin_magnitude +
static_cast<std::uint64_t
>(coord);
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) {
184 return axis_delta(origin, coord) < extent;
189[[nodiscard]]
constexpr std::uint64_t abs_delta(std::int64_t lhs,
190 std::int64_t rhs)
noexcept {
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);
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;
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);
215[[nodiscard]]
constexpr auto manhattan_distance(
Coord3 lhs,
Coord3 rhs)
noexcept
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)));
223template <Extent3 Size, Extent3 Chunk>
226 static constexpr Extent3 size = Size;
227 static constexpr Extent3 chunk = Chunk;
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 "
242template <
typename Shape>
245 static constexpr Extent3 size = Shape::size;
246 static constexpr Extent3 chunk = Shape::chunk;
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;
252 static constexpr auto precise_chunk_count = detail::chunk_count(size, chunk);
253 static constexpr auto precise_local_tile_count = detail::product(chunk);
259 static_assert(detail::product_fits_uint64(chunk_count_x, chunk_count_y,
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 "
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.");
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.");
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);
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;
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.");
297 using TileKeyStorage = detail::KeyStorage<tile_key_bits>;
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;
305template <
typename Shape>
308 ShapeTraits<Shape>::TileKeyStorage value{};
310 friend constexpr bool operator==(
TileKey lhs,
TileKey rhs)
noexcept =
default;
313template <
typename Shape>
315[[nodiscard]]
constexpr bool contains(
Coord3 coord)
noexcept {
316 return contains(
Box3{
Coord3{0, 0, 0}, ShapeTraits<Shape>::size}, coord);
319template <
typename Shape>
322 const auto chunk = ShapeTraits<Shape>::chunk;
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,
330template <
typename Shape>
333 const auto chunk = ShapeTraits<Shape>::chunk;
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,
341template <
typename Shape>
344 const auto chunk = ShapeTraits<Shape>::chunk;
345 return LocalTileId{coord.x + coord.y * chunk.x + coord.z * chunk.x * chunk.y};
348template <
typename Shape>
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;
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),
366template <
typename Shape>
370 return ChunkKey{coord.x + coord.y * Traits::chunk_count_x +
371 coord.z * Traits::chunk_count_x * Traits::chunk_count_y};
374template <
typename 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;
387template <
typename Shape>
391 TESS_ASSERT(contains<Shape>(coord));
392 const auto local = local_tile_id<Shape>(local_coord<Shape>(coord));
393 using Storage = Traits::TileKeyStorage;
395 if constexpr (Traits::chunk_bits == 0) {
401 const auto chunk = chunk_key<Shape>(chunk_coord<Shape>(coord));
403 (
static_cast<Storage
>(chunk.value) << Traits::local_bits) |
404 static_cast<Storage
>(local.value),
409template <
typename Shape>
413 if constexpr (Traits::chunk_bits == 0) {
417 static_cast<void>(key);
421 static_cast<std::uint64_t
>(key.value >> Traits::local_bits),
426template <
typename Shape>
430 using Storage = Traits::TileKeyStorage;
432 if constexpr (Traits::local_bits == 64) {
433 mask =
static_cast<Storage
>(std::numeric_limits<std::uint64_t>::max());
435 mask = (
static_cast<Storage
>(1) << Traits::local_bits) - 1;
438 return LocalTileId{
static_cast<std::uint64_t
>(key.value & mask)};
441template <
typename Shape>
444 return coord<Shape>(chunk_coord<Shape>(chunk_key<Shape>(key)),
445 local_tile_id<Shape>(key));