#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LedLayout<const N: usize, const W: usize, const H: usize> {
index_to_xy: [(u16, u16); N],
xy_to_index: [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.index_to_xy
}
#[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 is_empty(&self) -> bool {
N == 0
}
#[must_use]
pub const fn xy_to_index(&self) -> &[u16; N] {
&self.xy_to_index
}
#[must_use]
pub const fn equals(&self, other: &Self) -> bool {
let mut i = 0;
while i < N {
if self.index_to_xy[i].0 != other.index_to_xy[i].0
|| self.index_to_xy[i].1 != other.index_to_xy[i].1
{
return false;
}
i += 1;
}
true
}
#[must_use]
pub const fn new(index_to_xy: [(u16, u16); N]) -> Self {
assert!(W > 0 && H > 0, "W and H must be positive");
assert!(W * H == N, "W*H must equal N");
assert!(N <= u16::MAX as usize, "total LEDs must fit in u16");
let mut seen = [false; N];
let mut xy_to_index = [0_u16; N];
let mut led_index = 0;
while led_index < N {
let (x, y) = index_to_xy[led_index];
let x = x as usize;
let y = y as usize;
assert!(x < W, "column out of bounds");
assert!(y < H, "row out of bounds");
let cell_index = y * W + x;
assert!(!seen[cell_index], "duplicate (col,row) in mapping");
seen[cell_index] = true;
xy_to_index[cell_index] = led_index as u16;
led_index += 1;
}
let mut k = 0;
while k < N {
assert!(seen[k], "mapping does not cover every cell");
k += 1;
}
Self {
index_to_xy,
xy_to_index,
}
}
#[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.index_to_xy[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.index_to_xy[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.index_to_xy[i];
i += 1;
}
let mut j = 0;
while j < N2 {
let (c, r) = right.index_to_xy[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() }
}