use crate::{Hex, HexOrientation, OffsetHexMode, storage::HexStore};
use glam::{IVec2, UVec2};
#[cfg(feature = "rayon")]
use rayon::prelude::*;
use std::fmt::Debug;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(
feature = "bevy_ecs",
derive(bevy_ecs::resource::Resource, bevy_ecs::component::Component)
)]
#[derive(Clone)]
pub struct RectMap<T> {
inner: Vec<T>,
meta: RectMetadata,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(
feature = "bevy_ecs",
derive(bevy_ecs::resource::Resource, bevy_ecs::component::Component)
)]
pub struct RectMetadata {
orientation: HexOrientation,
offset_mode: OffsetHexMode,
start: IVec2,
dim: UVec2,
wrap_strategies: [WrapStrategy; 2],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub enum WrapStrategy {
Clamp,
Cycle,
}
impl RectMetadata {
#[must_use]
pub fn from_half_size(half_size: impl Into<UVec2>) -> Self {
let half_size = half_size.into();
Self {
start: -half_size.as_ivec2(),
dim: 2 * half_size,
orientation: HexOrientation::Pointy,
offset_mode: OffsetHexMode::Odd,
wrap_strategies: [WrapStrategy::Cycle, WrapStrategy::Clamp],
}
}
#[must_use]
pub fn from_start_end(start: IVec2, end: IVec2) -> Self {
Self {
start,
dim: (end.max(start) - start).as_uvec2(),
orientation: HexOrientation::Pointy,
offset_mode: OffsetHexMode::Odd,
wrap_strategies: [WrapStrategy::Cycle, WrapStrategy::Clamp],
}
}
#[must_use]
pub const fn from_start_dim(start: IVec2, dim: UVec2) -> Self {
Self {
start,
dim,
orientation: HexOrientation::Pointy,
offset_mode: OffsetHexMode::Odd,
wrap_strategies: [WrapStrategy::Cycle, WrapStrategy::Clamp],
}
}
#[must_use]
pub fn with_half_size(mut self, half_size: IVec2) -> Self {
self.start = -half_size.abs();
self.dim = (2 * half_size.abs()).as_uvec2();
self
}
#[must_use]
pub fn with_start_end(mut self, start: IVec2, end: IVec2) -> Self {
self.start = start;
self.dim = (end.max(start) - start).as_uvec2();
self
}
#[must_use]
pub const fn with_start_dim(mut self, start: IVec2, dim: UVec2) -> Self {
self.start = start;
self.dim = dim;
self
}
#[must_use]
pub const fn with_orientation(mut self, orientation: HexOrientation) -> Self {
self.orientation = orientation;
self
}
#[must_use]
pub const fn with_offset_mode(mut self, offset_mode: OffsetHexMode) -> Self {
self.offset_mode = offset_mode;
self
}
#[must_use]
pub const fn with_wrap_strategies(mut self, wrap_strategies: [WrapStrategy; 2]) -> Self {
self.wrap_strategies = wrap_strategies;
self
}
#[inline]
pub fn build<T>(self, values: impl FnMut(Hex) -> T) -> RectMap<T> {
RectMap::new(self, values)
}
#[inline]
#[cfg(feature = "rayon")]
pub fn build_parallel<T, F>(self, values: F) -> RectMap<T>
where
F: Fn(Hex) -> T + Send + Sync,
T: Send,
{
RectMap::new_parallel(self, values)
}
#[must_use]
pub fn build_default<T: Default>(self) -> RectMap<T> {
RectMap::default_values(self)
}
#[cfg(feature = "rayon")]
#[must_use]
pub fn build_default_parralel<T: Default + Send>(self) -> RectMap<T> {
RectMap::default_values_parallel(self)
}
#[must_use]
#[inline]
pub const fn orientation(&self) -> HexOrientation {
self.orientation
}
#[must_use]
#[inline]
pub const fn offset_mode(&self) -> OffsetHexMode {
self.offset_mode
}
#[must_use]
#[inline]
pub const fn dim(&self) -> UVec2 {
self.dim
}
#[must_use]
#[inline]
pub const fn wrap_strategies(&self) -> [WrapStrategy; 2] {
self.wrap_strategies
}
fn idx_to_hex(&self, idx: usize) -> Hex {
let ij = self.idx_to_ij(idx);
self.ij_to_hex(ij)
}
#[expect(clippy::cast_possible_truncation)]
fn idx_to_ij(&self, idx: usize) -> IVec2 {
let idx = idx as u32;
let rc = UVec2::new(idx % self.dim.x, idx / self.dim.x);
rc.as_ivec2() + self.start
}
const fn ij_to_hex(&self, ij: IVec2) -> Hex {
Hex::from_offset_coordinates(ij.to_array(), self.offset_mode, self.orientation)
}
fn hex_to_idx(&self, hex: Hex) -> Option<usize> {
let ij = self.hex_to_offset(hex);
self.contains_offset(ij).then(|| self.ij_to_idx(ij))
}
fn ij_to_idx(&self, ij: IVec2) -> usize {
let rc = (ij - self.start).as_uvec2();
(rc.x + rc.y * self.dim.x) as usize
}
#[must_use]
pub fn hex_to_offset(&self, hex: Hex) -> IVec2 {
hex.to_offset_coordinates(self.offset_mode, self.orientation)
.into()
}
fn wrapped_hex_to_idx(&self, hex: Hex) -> usize {
let ij = self.hex_to_offset(hex);
let ij = self.wrap_offset(ij);
self.ij_to_idx(ij)
}
#[must_use]
pub fn wrap_offset(&self, offset: impl Into<IVec2>) -> IVec2 {
let dim = self.dim.as_ivec2();
let end = self.start + dim;
let mut offset = offset.into();
if self.wrap_strategies[0] == WrapStrategy::Cycle {
while offset.x < self.start.x {
offset.x += dim.x;
}
while offset.x >= end.x {
offset.x -= dim.x;
}
} else {
offset.x = offset.x.clamp(self.start.x, end.x - 1);
}
if self.wrap_strategies[1] == WrapStrategy::Cycle {
while offset.y < self.start.y {
offset.y += dim.y;
}
while offset.y >= end.y {
offset.y -= dim.y;
}
} else {
offset.y = offset.y.clamp(self.start.y, end.y - 1);
}
offset
}
#[must_use]
pub fn wrap_hex(&self, hex: Hex) -> Hex {
let ij = self.hex_to_offset(hex);
let ij = self.wrap_offset(ij);
self.ij_to_hex(ij)
}
#[must_use]
pub fn contains_hex(&self, hex: Hex) -> bool {
self.contains_offset(self.hex_to_offset(hex))
}
#[must_use]
pub fn contains_offset(&self, offset_coord: impl Into<IVec2>) -> bool {
let offset_coord = offset_coord.into();
offset_coord
== offset_coord.clamp(self.start, self.start + self.dim.as_ivec2() - IVec2::ONE)
&& !self.is_empty()
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.dim.element_product() as usize
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn iter_hex(&self) -> impl ExactSizeIterator<Item = Hex> {
(0..self.len()).map(|idx| self.idx_to_hex(idx))
}
#[cfg(feature = "rayon")]
#[must_use]
pub fn iter_par_hex(&self) -> impl IndexedParallelIterator<Item = Hex> {
(0..self.len())
.into_par_iter()
.map(|idx| self.idx_to_hex(idx))
}
#[must_use]
pub fn iter_offset(&self) -> impl ExactSizeIterator<Item = IVec2> {
(0..self.len()).map(|idx| self.idx_to_ij(idx))
}
#[cfg(feature = "rayon")]
#[must_use]
pub fn iter_par_offset(&self) -> impl IndexedParallelIterator<Item = IVec2> {
(0..self.len())
.into_par_iter()
.map(|idx| self.idx_to_ij(idx))
}
}
impl<T> RectMap<T> {
#[must_use]
pub fn new(meta: RectMetadata, values: impl FnMut(Hex) -> T) -> Self {
let inner = meta.iter_hex().map(values).collect();
Self { inner, meta }
}
#[cfg(feature = "rayon")]
pub fn new_parallel<F>(meta: RectMetadata, values: F) -> Self
where
F: Fn(Hex) -> T + Send + Sync,
T: Send,
{
let mut inner = Vec::with_capacity(meta.len());
meta.iter_par_hex().map(values).collect_into_vec(&mut inner);
Self { inner, meta }
}
#[must_use]
#[inline]
pub const fn meta(&self) -> &RectMetadata {
&self.meta
}
#[must_use]
#[inline]
pub fn default_values(meta: RectMetadata) -> Self
where
T: Default,
{
Self::new(meta, |_| Default::default())
}
#[cfg(feature = "rayon")]
#[must_use]
#[inline]
pub fn default_values_parallel(meta: RectMetadata) -> Self
where
T: Default + Send,
{
Self::new_parallel(meta, |_| Default::default())
}
#[must_use]
pub fn get_by_offset(&self, offset_coord: impl Into<IVec2>) -> Option<&T> {
let offset_coord = offset_coord.into();
if self.contains_offset(offset_coord) {
let idx = self.ij_to_idx(offset_coord);
Some(&self.inner[idx])
} else {
None
}
}
#[must_use]
pub fn get_mut_by_offset(&mut self, offset_coord: impl Into<IVec2>) -> Option<&mut T> {
let offset_coord = offset_coord.into();
if self.contains_offset(offset_coord) {
let idx = self.ij_to_idx(offset_coord);
Some(&mut self.inner[idx])
} else {
None
}
}
#[must_use]
pub fn wrapped_get(&self, hex: Hex) -> &T {
let idx = self.wrapped_hex_to_idx(hex);
&self.inner[idx]
}
#[must_use]
pub fn wrapped_get_mut(&mut self, hex: Hex) -> &mut T {
let idx = self.wrapped_hex_to_idx(hex);
&mut self.inner[idx]
}
#[must_use]
pub fn wrapped_get_by_offset(&self, offset_coord: impl Into<IVec2>) -> &T {
let wrapped = self.wrap_offset(offset_coord);
let idx = self.ij_to_idx(wrapped);
&self.inner[idx]
}
#[must_use]
pub fn wrapped_get_mut_by_offset(&mut self, offset_coord: impl Into<IVec2>) -> &mut T {
let wrapped = self.wrap_offset(offset_coord);
let idx = self.ij_to_idx(wrapped);
&mut self.inner[idx]
}
}
impl<T> std::ops::Deref for RectMap<T> {
type Target = RectMetadata;
#[inline]
fn deref(&self) -> &Self::Target {
self.meta()
}
}
impl<T> HexStore<T> for RectMap<T> {
fn get(&self, hex: crate::Hex) -> Option<&T> {
let idx = self.meta.hex_to_idx(hex)?;
self.inner.get(idx)
}
fn get_mut(&mut self, hex: crate::Hex) -> Option<&mut T> {
let idx = self.meta.hex_to_idx(hex)?;
self.inner.get_mut(idx)
}
#[inline]
fn values<'s>(&'s self) -> impl ExactSizeIterator<Item = &'s T>
where
T: 's,
{
self.inner.iter()
}
#[inline]
fn values_mut<'s>(&'s mut self) -> impl ExactSizeIterator<Item = &'s mut T>
where
T: 's,
{
self.inner.iter_mut()
}
#[inline]
fn iter<'s>(&'s self) -> impl ExactSizeIterator<Item = (crate::Hex, &'s T)>
where
T: 's,
{
self.values().enumerate().map(|(i, value)| {
let hex = self.meta.idx_to_hex(i);
(hex, value)
})
}
#[inline]
fn iter_mut<'s>(&'s mut self) -> impl ExactSizeIterator<Item = (crate::Hex, &'s mut T)>
where
T: 's,
{
let meta = self.meta.clone();
self.values_mut().enumerate().map(move |(i, value)| {
let hex = meta.idx_to_hex(i);
(hex, value)
})
}
}
impl<T: Debug> Debug for RectMap<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RectMap")
.field("inner", &self.inner)
.field("meta", &self.meta)
.finish()
}
}
#[cfg(test)]
mod half_size_test {
use super::*;
const HALF_SIZES: &[[u32; 2]] = &[
[0, 0],
[0, 1],
[1, 1],
[2, 2],
[8, 4],
[16, 8],
[28, 8],
[64, 128],
[127, 201],
[256, 512],
];
const WRAP_STRATEGIES: &[[WrapStrategy; 2]] = &[
[WrapStrategy::Clamp, WrapStrategy::Clamp],
[WrapStrategy::Clamp, WrapStrategy::Cycle],
[WrapStrategy::Cycle, WrapStrategy::Clamp],
[WrapStrategy::Cycle, WrapStrategy::Cycle],
];
#[test]
fn idx_hex_test() {
for dim in HALF_SIZES {
let rect_map = RectMetadata::from_half_size(UVec2::from_array(*dim))
.with_orientation(HexOrientation::Pointy)
.with_offset_mode(OffsetHexMode::Odd)
.with_wrap_strategies([WrapStrategy::Cycle, WrapStrategy::Clamp])
.build_default::<i32>();
for idx in 0..rect_map.len() {
let hex = rect_map.idx_to_hex(idx);
assert!(rect_map.contains_hex(hex));
let _idx = rect_map.hex_to_idx(hex);
assert_eq!(Some(idx), _idx);
}
assert_eq!(rect_map.iter().count(), rect_map.len());
}
}
#[test]
fn contains_test() {
for dim in HALF_SIZES {
let rect_map = RectMetadata::from_half_size(UVec2::from_array(*dim))
.with_orientation(HexOrientation::Pointy)
.with_offset_mode(OffsetHexMode::Odd)
.with_wrap_strategies([WrapStrategy::Cycle, WrapStrategy::Clamp])
.build_default::<i32>();
let dim = [dim[0] as i32, dim[1] as i32];
for a in -2..2 {
for b in -2..2 {
for i in (2 * a * dim[0])..((2 * a + 1) * dim[0]) {
for j in (2 * b * dim[1])..((2 * b + 1) * dim[1]) {
assert_eq!(
a == 0 && b == 0,
rect_map.contains_offset(IVec2::new(i, j))
);
assert_eq!(
a == 0 && b == 0,
rect_map.get_by_offset(IVec2::new(i, j)).is_some()
);
}
}
}
}
}
}
#[test]
fn wrap_test() {
for dim in HALF_SIZES {
for wrap_strategies in WRAP_STRATEGIES {
let rect_map = RectMetadata::from_half_size(UVec2::from_array(*dim))
.with_orientation(HexOrientation::Pointy)
.with_offset_mode(OffsetHexMode::Odd)
.with_wrap_strategies(*wrap_strategies)
.build_default::<i32>();
let dim = [dim[0] as i32, dim[1] as i32];
for a in -2..2 {
for b in -2..2 {
let i_iter_a = (2 * a * dim[0] - dim[0])..((2 * a) * dim[0] + dim[0]);
let i_iter_b: Box<dyn Iterator<Item = i32>> =
if wrap_strategies[0] == WrapStrategy::Cycle {
Box::new(-dim[0]..dim[0])
} else {
Box::new(i_iter_a.clone().map(|i| i.clamp(-dim[0], dim[0] - 1)))
};
for (ia, ib) in i_iter_a.zip(i_iter_b) {
let j_iter_a = (2 * b * dim[1] - dim[1])..((2 * b) * dim[1] + dim[1]);
let j_iter_b: Box<dyn Iterator<Item = i32>> =
if wrap_strategies[1] == WrapStrategy::Cycle {
Box::new(-dim[1]..dim[1])
} else {
Box::new(j_iter_a.clone().map(|j| j.clamp(-dim[1], dim[1] - 1)))
};
for (ja, jb) in j_iter_a.zip(j_iter_b) {
let ij_a = IVec2::new(ia, ja);
let ij_b = IVec2::new(ib, jb);
let wij_a = rect_map.wrap_offset(ij_a);
let wij_b = rect_map.wrap_offset(ij_b);
assert_eq!(wij_a, wij_b);
}
}
}
}
}
}
}
#[test]
fn construction_test() {
for dim in HALF_SIZES {
let rect_map =
RectMetadata::from_half_size(UVec2::from_array(*dim)).build(|h| h.x * 1000 + h.y);
for (h, v) in rect_map.iter() {
assert_eq!(h.x * 1000 + h.y, *v);
}
}
}
}
#[cfg(test)]
mod start_end_test {
use super::*;
const START_END: &[([i32; 2], [i32; 2])] = &[
([0, 0], [0, 0]),
([0, 0], [1, 1]),
([-1, -1], [1, 1]),
([-10, -10], [10, 10]),
([-10, -10], [15, 15]),
([0, 0], [15, 15]),
([0, 0], [-30, -30]),
([-17, -13], [31, 41]),
([-64, -64], [64, 64]),
];
const WRAP_STRATEGIES: &[[WrapStrategy; 2]] = &[
[WrapStrategy::Clamp, WrapStrategy::Clamp],
[WrapStrategy::Clamp, WrapStrategy::Cycle],
[WrapStrategy::Cycle, WrapStrategy::Clamp],
[WrapStrategy::Cycle, WrapStrategy::Cycle],
];
#[test]
fn idx_hex_test() {
for (start, end) in START_END {
let rect_map =
RectMetadata::from_start_end((*start).into(), (*end).into()).build_default::<i32>();
for idx in 0..rect_map.len() {
let hex = rect_map.idx_to_hex(idx);
assert!(rect_map.contains_hex(hex));
let ij = rect_map.hex_to_offset(hex);
assert!(rect_map.contains_offset(ij));
assert_eq!(Some(idx), rect_map.hex_to_idx(hex));
}
assert_eq!(rect_map.iter().count(), rect_map.len());
}
}
#[test]
fn contains_test() {
for (start, end) in START_END {
let rect_map =
RectMetadata::from_start_end((*start).into(), (*end).into()).build_default::<i32>();
for i in (start[0] - 10)..(end[0] + 10) {
for j in (start[1] - 10)..(end[1] + 10) {
let contain = (i >= start[0] && i < end[0]) && (j >= start[1] && j < end[1]);
println!(
"{:?} || {:?} - {:?} | {}, {} | {}",
rect_map.dim, start, end, i, j, contain
);
assert_eq!(rect_map.contains_offset([i, j]), contain);
assert_eq!(rect_map.get_by_offset([i, j]).is_some(), contain);
}
}
}
}
#[test]
fn wrap_test() {
for (start, end) in START_END {
for wrap_strategies in WRAP_STRATEGIES {
let rect_map = RectMetadata::from_start_end((*start).into(), (*end).into())
.with_wrap_strategies(*wrap_strategies)
.build_default::<i32>();
let dim = [(end[0] - start[0]).max(0), (end[1] - start[1]).max(0)];
for a in -2..2 {
for b in -2..2 {
let i_iter_a = (2 * a * dim[0] - dim[0])..((2 * a) * dim[0] + dim[0]);
let i_iter_b: Box<dyn Iterator<Item = i32>> =
if wrap_strategies[0] == WrapStrategy::Cycle {
Box::new(-dim[0]..dim[0])
} else {
Box::new(i_iter_a.clone().map(|i| i.clamp(-dim[0], dim[0] - 1)))
};
for (ia, ib) in i_iter_a.zip(i_iter_b) {
let j_iter_a = (2 * b * dim[1] - dim[1])..((2 * b) * dim[1] + dim[1]);
let j_iter_b: Box<dyn Iterator<Item = i32>> =
if wrap_strategies[1] == WrapStrategy::Cycle {
Box::new(-dim[1]..dim[1])
} else {
Box::new(j_iter_a.clone().map(|j| j.clamp(-dim[1], dim[1] - 1)))
};
for (ja, jb) in j_iter_a.zip(j_iter_b) {
let ij_a = IVec2::new(ia, ja);
let ij_b = IVec2::new(ib, jb);
let wij_a = rect_map.wrap_offset(ij_a);
let wij_b = rect_map.wrap_offset(ij_b);
assert_eq!(wij_a, wij_b);
}
}
}
}
}
}
}
#[test]
fn construction_test() {
for (start, end) in START_END {
let rect_map = RectMetadata::from_start_end((*start).into(), (*end).into())
.build(|h| h.x * 1000 + h.y);
for (h, v) in rect_map.iter() {
assert_eq!(h.x * 1000 + h.y, *v);
}
}
}
}
#[cfg(test)]
mod start_dim_test {
use super::*;
const START_END: &[([i32; 2], [u32; 2])] = &[
([0, 0], [0, 0]),
([0, 0], [1, 1]),
([-1, -1], [1, 1]),
([-10, -10], [10, 10]),
([-10, -10], [15, 15]),
([0, 0], [15, 15]),
([0, 0], [30, 30]),
([-17, -13], [31, 41]),
([-64, -64], [64, 64]),
];
const WRAP_STRATEGIES: &[[WrapStrategy; 2]] = &[
[WrapStrategy::Clamp, WrapStrategy::Clamp],
[WrapStrategy::Clamp, WrapStrategy::Cycle],
[WrapStrategy::Cycle, WrapStrategy::Clamp],
[WrapStrategy::Cycle, WrapStrategy::Cycle],
];
#[test]
fn idx_hex_test() {
for (start, dim) in START_END {
let rect_map =
RectMetadata::from_start_dim((*start).into(), (*dim).into()).build_default::<i32>();
for idx in 0..rect_map.len() {
let hex = rect_map.idx_to_hex(idx);
assert!(rect_map.contains_hex(hex));
let ij = rect_map.hex_to_offset(hex);
assert!(rect_map.contains_offset(ij));
assert_eq!(Some(idx), rect_map.hex_to_idx(hex));
}
assert_eq!(rect_map.iter().count(), rect_map.len());
}
}
#[test]
fn contains_test() {
for (start, dim) in START_END {
let rect_map =
RectMetadata::from_start_dim((*start).into(), (*dim).into()).build_default::<i32>();
for i in (start[0] - 10)..(start[0] + dim[0] as i32 + 10) {
for j in (start[1] - 10)..(start[1] + dim[1] as i32 + 10) {
let contain = (i >= start[0] && i < start[0] + dim[0] as i32)
&& (j >= start[1] && j < start[1] + dim[1] as i32);
assert_eq!(rect_map.contains_offset([i, j]), contain);
assert_eq!(rect_map.get_by_offset([i, j]).is_some(), contain);
}
}
}
}
#[test]
fn wrap_test() {
for (start, dim) in START_END {
for wrap_strategies in WRAP_STRATEGIES {
let rect_map = RectMetadata::from_start_dim((*start).into(), (*dim).into())
.with_wrap_strategies(*wrap_strategies)
.build_default::<i32>();
let dim = [dim[0] as i32, dim[1] as i32];
for a in -2..2 {
for b in -2..2 {
let i_iter_a = (2 * a * dim[0] - dim[0])..((2 * a) * dim[0] + dim[0]);
let i_iter_b: Box<dyn Iterator<Item = i32>> =
if wrap_strategies[0] == WrapStrategy::Cycle {
Box::new(-dim[0]..dim[0])
} else {
Box::new(i_iter_a.clone().map(|i| i.clamp(-dim[0], dim[0] - 1)))
};
for (ia, ib) in i_iter_a.zip(i_iter_b) {
let j_iter_a = (2 * b * dim[1] - dim[1])..((2 * b) * dim[1] + dim[1]);
let j_iter_b: Box<dyn Iterator<Item = i32>> =
if wrap_strategies[1] == WrapStrategy::Cycle {
Box::new(-dim[1]..dim[1])
} else {
Box::new(j_iter_a.clone().map(|j| j.clamp(-dim[1], dim[1] - 1)))
};
for (ja, jb) in j_iter_a.zip(j_iter_b) {
let ij_a = IVec2::new(ia, ja);
let ij_b = IVec2::new(ib, jb);
let wij_a = rect_map.wrap_offset(ij_a);
let wij_b = rect_map.wrap_offset(ij_b);
assert_eq!(wij_a, wij_b);
}
}
}
}
}
}
}
#[test]
fn construction_test() {
for (start, dim) in START_END {
let rect_map = RectMetadata::from_start_dim((*start).into(), (*dim).into())
.build(|h| h.x * 1000 + h.y);
for (h, v) in rect_map.iter() {
assert_eq!(h.x * 1000 + h.y, *v);
}
}
}
}