#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LedLayout<const N: usize, const W: usize, const H: usize> {
map: [(u16, u16); N],
}
impl<const N: usize, const W: usize, const H: usize> LedLayout<N, W, H> {
#[must_use]
pub const fn index_to_xy(&self) -> &[(u16, u16); N] {
&self.map
}
#[must_use]
pub const fn width(&self) -> usize {
W
}
#[must_use]
pub const fn height(&self) -> usize {
H
}
#[must_use]
pub const fn len(&self) -> usize {
N
}
#[must_use]
pub const fn xy_to_index(&self) -> [u16; N] {
assert!(
N <= u16::MAX as usize,
"total LEDs must fit in u16 for xy_to_index"
);
let mut mapping = [None; N];
let mut led_index = 0;
while led_index < N {
let (col, row) = self.map[led_index];
let col = col as usize;
let row = row as usize;
assert!(col < W, "column out of bounds in xy_to_index");
assert!(row < H, "row out of bounds in xy_to_index");
let target_index = row * W + col;
let slot = &mut mapping[target_index];
assert!(
slot.is_none(),
"duplicate (col,row) in xy_to_index inversion"
);
*slot = Some(led_index as u16);
led_index += 1;
}
let mut finalized = [0u16; N];
let mut i = 0;
while i < N {
finalized[i] = mapping[i].expect("xy_to_index requires every (col,row) to be covered");
i += 1;
}
finalized
}
#[must_use]
pub const fn equals(&self, other: &Self) -> bool {
let mut i = 0;
while i < N {
if self.map[i].0 != other.map[i].0 || self.map[i].1 != other.map[i].1 {
return false;
}
i += 1;
}
true
}
#[must_use]
pub const fn new(map: [(u16, u16); N]) -> Self {
assert!(W > 0 && H > 0, "W and H must be positive");
assert!(W * H == N, "W*H must equal N");
let mut seen = [false; N];
let mut i = 0;
while i < N {
let (c, r) = map[i];
let c = c as usize;
let r = r as usize;
assert!(c < W, "column out of bounds");
assert!(r < H, "row out of bounds");
let cell = r * W + c;
assert!(!seen[cell], "duplicate (col,row) in mapping");
seen[cell] = true;
i += 1;
}
let mut k = 0;
while k < N {
assert!(seen[k], "mapping does not cover every cell");
k += 1;
}
Self { map }
}
#[must_use]
pub const fn linear_h() -> Self {
assert!(H == 1, "linear_h requires H == 1");
assert!(W == N, "linear_h requires W == N");
let mut mapping = [(0_u16, 0_u16); N];
let mut x_index = 0;
while x_index < W {
mapping[x_index] = (x_index as u16, 0);
x_index += 1;
}
Self::new(mapping)
}
#[must_use]
pub const fn linear_v() -> Self {
assert!(W == 1, "linear_v requires W == 1");
assert!(H == N, "linear_v requires H == N");
let mut mapping = [(0_u16, 0_u16); N];
let mut y_index = 0;
while y_index < H {
mapping[y_index] = (0, y_index as u16);
y_index += 1;
}
Self::new(mapping)
}
#[must_use]
pub const fn serpentine_column_major() -> Self {
assert!(W > 0 && H > 0, "W and H must be positive");
assert!(W * H == N, "W*H must equal N");
let mut mapping = [(0_u16, 0_u16); N];
let mut y_index = 0;
while y_index < H {
let mut x_index = 0;
while x_index < W {
let led_index = if x_index % 2 == 0 {
x_index * H + y_index
} else {
x_index * H + (H - 1 - y_index)
};
mapping[led_index] = (x_index as u16, y_index as u16);
x_index += 1;
}
y_index += 1;
}
Self::new(mapping)
}
#[must_use]
pub const fn serpentine_row_major() -> Self {
assert!(W > 0 && H > 0, "W and H must be positive");
assert!(W * H == N, "W*H must equal N");
let mut mapping = [(0_u16, 0_u16); N];
let mut y_index = 0;
while y_index < H {
let mut x_index = 0;
while x_index < W {
let led_index = if y_index % 2 == 0 {
y_index * W + x_index
} else {
y_index * W + (W - 1 - x_index)
};
mapping[led_index] = (x_index as u16, y_index as u16);
x_index += 1;
}
y_index += 1;
}
Self::new(mapping)
}
#[must_use]
pub const fn rotate_cw(self) -> LedLayout<N, H, W> {
let mut out = [(0u16, 0u16); N];
let mut i = 0;
while i < N {
let (c, r) = self.map[i];
let c = c as usize;
let r = r as usize;
out[i] = ((H - 1 - r) as u16, c as u16);
i += 1;
}
LedLayout::<N, H, W>::new(out)
}
#[must_use]
pub const fn flip_h(self) -> Self {
let mut out = [(0u16, 0u16); N];
let mut i = 0;
while i < N {
let (c, r) = self.map[i];
let c = c as usize;
out[i] = ((W - 1 - c) as u16, r);
i += 1;
}
Self::new(out)
}
#[must_use]
pub const fn rotate_180(self) -> Self {
self.rotate_cw().rotate_cw()
}
#[must_use]
pub const fn rotate_ccw(self) -> LedLayout<N, H, W> {
self.rotate_cw().rotate_cw().rotate_cw()
}
#[must_use]
pub const fn flip_v(self) -> Self {
self.rotate_cw().flip_h().rotate_ccw()
}
#[must_use]
pub const fn combine_h<
const N2: usize,
const OUT_N: usize,
const W2: usize,
const OUT_W: usize,
>(
self,
right: LedLayout<N2, W2, H>,
) -> LedLayout<OUT_N, OUT_W, H> {
assert!(OUT_N == N + N2, "OUT_N must equal LEFT + RIGHT");
assert!(OUT_W == W + W2, "OUT_W must equal W + W2");
let mut out = [(0u16, 0u16); OUT_N];
let mut i = 0;
while i < N {
out[i] = self.map[i];
i += 1;
}
let mut j = 0;
while j < N2 {
let (c, r) = right.map[j];
out[N + j] = ((c as usize + W) as u16, r);
j += 1;
}
LedLayout::<OUT_N, OUT_W, H>::new(out)
}
#[must_use]
pub const fn combine_v<
const N2: usize,
const OUT_N: usize,
const H2: usize,
const OUT_H: usize,
>(
self,
bottom: LedLayout<N2, W, H2>,
) -> LedLayout<OUT_N, W, OUT_H> {
assert!(OUT_N == N + N2, "OUT_N must equal TOP + BOTTOM");
assert!(OUT_H == H + H2, "OUT_H must equal H + H2");
let top_t = self.rotate_cw().flip_h(); let bot_t = bottom.rotate_cw().flip_h();
let combined_t: LedLayout<OUT_N, OUT_H, W> = top_t.combine_h::<N2, OUT_N, H2, OUT_H>(bot_t);
combined_t.rotate_cw().flip_h() }
}