3#include <tess/core/assert.h>
4#include <tess/core/lattice.h>
5#include <tess/core/uint128.h>
19 friend constexpr bool operator==(
Extent3 lhs,
Extent3 rhs)
noexcept =
default;
31 [[nodiscard]]
constexpr operator Coord3()
const noexcept;
33 friend constexpr bool operator==(
Coord2 lhs,
Coord2 rhs)
noexcept =
default;
41 friend constexpr bool operator==(
HexCoord lhs,
51 friend constexpr bool operator==(
Coord3 lhs,
Coord3 rhs)
noexcept =
default;
55constexpr Coord2::operator
Coord3() const noexcept {
return Coord3{x, y, 0}; }
79 std::uint64_t value = 0;
87 std::uint64_t value = 0;
89 friend constexpr bool operator==(
ChunkKey lhs,
98 friend constexpr bool operator==(
Box3 lhs,
Box3 rhs)
noexcept =
default;
101template <
typename Shape>
105template <
typename Shape>
116[[nodiscard]]
constexpr Coord3 to_coord3(
Coord2 coord)
noexcept {
121[[nodiscard]]
constexpr Coord3 to_coord3(HexCoord coord)
noexcept {
122 return Coord3{coord.q, coord.r, 0};
126[[nodiscard]]
constexpr HexCoord to_hex_coord(
Coord3 coord)
noexcept {
127 TESS_ASSERT(coord.z == 0);
133[[nodiscard]]
constexpr bool is_power_of_two(std::uint64_t value)
noexcept {
134 return value != 0 && (value & (value - 1)) == 0;
137[[nodiscard]]
constexpr bool is_valid_extent(Extent3 extent)
noexcept {
138 return extent.x > 0 && extent.y > 0 && extent.z > 0;
141[[nodiscard]]
constexpr bool is_divisible_by(Extent3 size,
142 Extent3 chunk)
noexcept {
143 return size.x % chunk.x == 0 && size.y % chunk.y == 0 &&
144 size.z % chunk.z == 0;
151[[nodiscard]]
constexpr bool product_fits_uint64(std::uint64_t x,
153 std::uint64_t z)
noexcept {
154 constexpr auto max = std::numeric_limits<std::uint64_t>::max();
158 return x * y <= max / z;
161[[nodiscard]]
constexpr UInt128 product(Extent3 extent)
noexcept {
162 return static_cast<UInt128
>(extent.x) *
static_cast<UInt128
>(extent.y) *
163 static_cast<UInt128
>(extent.z);
166[[nodiscard]]
constexpr UInt128 chunk_count(Extent3 size,
167 Extent3 chunk)
noexcept {
168 return static_cast<UInt128
>(size.x / chunk.x) *
169 static_cast<UInt128
>(size.y / chunk.y) *
170 static_cast<UInt128
>(size.z / chunk.z);
173[[nodiscard]]
constexpr std::uint32_t bit_width(UInt128 value)
noexcept {
174 std::uint32_t bits = 0;
182[[nodiscard]]
constexpr std::uint32_t bits_for_count(UInt128 count)
noexcept {
183 return count <= 1 ? 0 : bit_width(count - 1);
186template <std::u
int32_t Bits>
187using KeyStorage = std::conditional_t<Bits <= 64, std::uint64_t, UInt128>;
189[[nodiscard]]
constexpr std::uint64_t magnitude(std::int64_t value)
noexcept {
190 return static_cast<std::uint64_t
>(-(value + 1)) + 1;
193[[nodiscard]]
constexpr std::uint64_t axis_delta(std::int64_t origin,
194 std::int64_t coord)
noexcept {
196 return static_cast<std::uint64_t
>(coord - origin);
199 const auto origin_magnitude = magnitude(origin);
201 return origin_magnitude - magnitude(coord);
204 return origin_magnitude +
static_cast<std::uint64_t
>(coord);
207[[nodiscard]]
constexpr bool axis_contains(std::int64_t origin,
208 std::uint64_t extent,
209 std::int64_t coord)
noexcept {
210 if (coord < origin) {
214 return axis_delta(origin, coord) < extent;
219[[nodiscard]]
constexpr std::uint64_t abs_delta(std::int64_t lhs,
220 std::int64_t rhs)
noexcept {
222 ?
static_cast<std::uint64_t
>(rhs) -
static_cast<std::uint64_t
>(lhs)
223 : static_cast<std::uint64_t>(lhs) -
224 static_cast<std::uint64_t>(rhs);
227[[nodiscard]]
constexpr std::uint64_t saturating_add(
228 std::uint64_t lhs, std::uint64_t rhs)
noexcept {
229 constexpr auto max = std::numeric_limits<std::uint64_t>::max();
230 return lhs > max - rhs ? max : lhs + rhs;
233[[nodiscard]]
constexpr UInt128 add(UInt128 lhs, UInt128 rhs)
noexcept {
234 const auto low = lhs.lo + rhs.lo;
235 const auto carry = low < lhs.lo ? std::uint64_t{1} : std::uint64_t{0};
236 return UInt128::from_parts(lhs.hi + rhs.hi + carry, low);
239struct SignedMagnitude {
240 bool negative =
false;
241 std::uint64_t magnitude = 0;
244[[nodiscard]]
constexpr SignedMagnitude signed_delta(
245 std::int64_t lhs, std::int64_t rhs)
noexcept {
246 return SignedMagnitude{lhs < rhs, abs_delta(lhs, rhs)};
249[[nodiscard]]
constexpr UInt128 signed_sum_magnitude(
250 SignedMagnitude lhs, SignedMagnitude rhs)
noexcept {
251 if (lhs.negative == rhs.negative) {
252 return add(UInt128{lhs.magnitude}, UInt128{rhs.magnitude});
254 return lhs.magnitude < rhs.magnitude ? UInt128{rhs.magnitude - lhs.magnitude}
255 : UInt128{lhs.magnitude - rhs.magnitude};
261[[nodiscard]]
constexpr bool contains(
Box3 box,
Coord3 coord)
noexcept {
262 return detail::axis_contains(box.origin.x, box.extent.x, coord.x) &&
263 detail::axis_contains(box.origin.y, box.extent.y, coord.y) &&
264 detail::axis_contains(box.origin.z, box.extent.z, coord.z);
270[[nodiscard]]
constexpr auto manhattan_distance(
Coord3 lhs,
Coord3 rhs)
noexcept
272 return detail::saturating_add(
273 detail::abs_delta(lhs.x, rhs.x),
274 detail::saturating_add(detail::abs_delta(lhs.y, rhs.y),
275 detail::abs_delta(lhs.z, rhs.z)));
279[[nodiscard]]
constexpr auto hex_distance(
HexCoord lhs,
HexCoord rhs)
noexcept
281 const auto dq = detail::signed_delta(lhs.q, rhs.q);
282 const auto dr = detail::signed_delta(lhs.r, rhs.r);
283 auto twice_distance =
286 detail::add(twice_distance, detail::signed_sum_magnitude(dq, dr));
287 const auto distance = twice_distance >> 1U;
288 if (distance.hi != 0) {
289 return std::numeric_limits<std::uint64_t>::max();
294template <Extent3 Size, Extent3 Chunk,
typename Lattice = lattice::Orthogonal>
297 static constexpr Extent3 size = Size;
298 static constexpr Extent3 chunk = Chunk;
299 using lattice_type = Lattice;
301 static_assert(detail::is_valid_extent(Size),
302 "Shape size dimensions must be greater than zero.");
303 static_assert(detail::is_valid_extent(Chunk),
304 "Shape chunk dimensions must be greater than zero.");
305 static_assert(detail::is_power_of_two(Chunk.x) &&
306 detail::is_power_of_two(Chunk.y) &&
307 detail::is_power_of_two(Chunk.z),
308 "Shape chunk dimensions must be powers of two.");
309 static_assert(detail::is_divisible_by(Size, Chunk),
310 "Shape size dimensions must be multiples of chunk "
313 "Shape lattice must satisfy lattice::LatticeType.");
314 static_assert(Lattice::template valid_shape<Size, Chunk>,
315 "Shape dimensions are invalid for the selected lattice.");
318template <
typename Shape>
321 static constexpr Extent3 size = Shape::size;
322 static constexpr Extent3 chunk = Shape::chunk;
323 using lattice_type =
typename Shape::lattice_type;
324 static constexpr auto lattice_identity = lattice_type::identity;
325 static constexpr auto lattice_version = lattice_type::version;
327 static constexpr std::uint64_t chunk_count_x = size.x / chunk.x;
328 static constexpr std::uint64_t chunk_count_y = size.y / chunk.y;
329 static constexpr std::uint64_t chunk_count_z = size.z / chunk.z;
331 static constexpr auto precise_chunk_count = detail::chunk_count(size, chunk);
332 static constexpr auto precise_local_tile_count = detail::product(chunk);
338 static_assert(detail::product_fits_uint64(chunk_count_x, chunk_count_y,
340 "Shape chunk count must fit std::uint64_t without wrapping.");
341 static_assert(detail::product_fits_uint64(chunk.x, chunk.y, chunk.z),
342 "Shape local tile count must fit std::uint64_t without "
345 size.x <=
static_cast<std::uint64_t
>(
346 std::numeric_limits<std::int64_t>::max()) &&
347 size.y <=
static_cast<std::uint64_t
>(
348 std::numeric_limits<std::int64_t>::max()) &&
349 size.z <=
static_cast<std::uint64_t
>(
350 std::numeric_limits<std::int64_t>::max()),
351 "Shape size axes must fit std::int64_t so coordinates cannot wrap.");
354 precise_chunk_count <=
355 static_cast<UInt128>(std::numeric_limits<std::uint64_t>::max()),
356 "Shape chunk count must fit std::uint64_t.");
358 precise_local_tile_count <=
359 static_cast<UInt128>(std::numeric_limits<std::uint64_t>::max()),
360 "Shape local tile count must fit LocalTileId.");
362 static constexpr std::uint64_t chunk_count =
363 static_cast<std::uint64_t
>(precise_chunk_count);
364 static constexpr std::uint64_t local_tile_count =
365 static_cast<std::uint64_t
>(precise_local_tile_count);
367 static constexpr std::uint32_t local_bits =
368 detail::bits_for_count(precise_local_tile_count);
369 static constexpr std::uint32_t chunk_bits =
370 detail::bits_for_count(precise_chunk_count);
371 static constexpr std::uint32_t tile_key_bits = local_bits + chunk_bits;
373 static_assert(chunk_bits <= 64,
"ChunkKey must fit std::uint64_t.");
374 static_assert(tile_key_bits <= 128,
"TileKey must fit u64 or u128.");
376 using TileKeyStorage = detail::KeyStorage<tile_key_bits>;
378 static constexpr bool single_chunk = chunk_count == 1;
379 static constexpr bool degenerate_x = size.x == 1;
380 static constexpr bool degenerate_y = size.y == 1;
381 static constexpr bool degenerate_z = size.z == 1;
384template <
typename Shape>
387 ShapeTraits<Shape>::TileKeyStorage value{};
389 friend constexpr bool operator==(
TileKey lhs,
TileKey rhs)
noexcept =
default;
392template <
typename Shape>
394[[nodiscard]]
constexpr bool contains(
Coord3 coord)
noexcept {
395 return contains(
Box3{
Coord3{0, 0, 0}, ShapeTraits<Shape>::size}, coord);
398template <
typename Shape>
401 const auto chunk = ShapeTraits<Shape>::chunk;
403 static_cast<std::uint64_t
>(coord.x) / chunk.x,
404 static_cast<std::uint64_t
>(coord.y) / chunk.y,
405 static_cast<std::uint64_t
>(coord.z) / chunk.z,
409template <
typename Shape>
412 const auto chunk = ShapeTraits<Shape>::chunk;
414 static_cast<std::uint64_t
>(coord.x) % chunk.x,
415 static_cast<std::uint64_t
>(coord.y) % chunk.y,
416 static_cast<std::uint64_t
>(coord.z) % chunk.z,
420template <
typename Shape>
423 const auto chunk = ShapeTraits<Shape>::chunk;
424 return LocalTileId{coord.x + coord.y * chunk.x + coord.z * chunk.x * chunk.y};
427template <
typename Shape>
431 const auto chunk = ShapeTraits<Shape>::chunk;
432 const auto local_xy = chunk.x * chunk.y;
433 const auto local_z = local_tile_id.value / local_xy;
434 const auto remainder = local_tile_id.value % local_xy;
435 const auto local_y = remainder / chunk.x;
436 const auto local_x = remainder % chunk.x;
439 static_cast<std::int64_t
>(chunk_coord.x * chunk.x + local_x),
440 static_cast<std::int64_t
>(chunk_coord.y * chunk.y + local_y),
441 static_cast<std::int64_t
>(chunk_coord.z * chunk.z + local_z),
445template <
typename Shape>
449 return ChunkKey{coord.x + coord.y * Traits::chunk_count_x +
450 coord.z * Traits::chunk_count_x * Traits::chunk_count_y};
453template <
typename Shape>
457 const auto chunk_xy = Traits::chunk_count_x * Traits::chunk_count_y;
458 const auto z = key.value / chunk_xy;
459 const auto remainder = key.value % chunk_xy;
460 const auto y = remainder / Traits::chunk_count_x;
461 const auto x = remainder % Traits::chunk_count_x;
466template <
typename Shape>
470 TESS_ASSERT(contains<Shape>(coord));
471 const auto local = local_tile_id<Shape>(local_coord<Shape>(coord));
472 using Storage = Traits::TileKeyStorage;
474 if constexpr (Traits::chunk_bits == 0) {
480 const auto chunk = chunk_key<Shape>(chunk_coord<Shape>(coord));
482 (
static_cast<Storage
>(chunk.value) << Traits::local_bits) |
483 static_cast<Storage
>(local.value),
488template <
typename Shape>
492 if constexpr (Traits::chunk_bits == 0) {
496 static_cast<void>(key);
500 static_cast<std::uint64_t
>(key.value >> Traits::local_bits),
505template <
typename Shape>
509 using Storage = Traits::TileKeyStorage;
511 if constexpr (Traits::local_bits == 64) {
512 mask =
static_cast<Storage
>(std::numeric_limits<std::uint64_t>::max());
514 mask = (
static_cast<Storage
>(1) << Traits::local_bits) - 1;
517 return LocalTileId{
static_cast<std::uint64_t
>(key.value & mask)};
520template <
typename Shape>
523 return coord<Shape>(chunk_coord<Shape>(chunk_key<Shape>(key)),
524 local_tile_id<Shape>(key));
Packed 128-bit key storage with a deliberately partial operator set.
Definition uint128.h:34