use retroglyph_core::grid::Pos;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CellGeometry {
pub glyph_w: u8,
pub glyph_h: u8,
pub scale: u16,
}
impl CellGeometry {
#[must_use]
pub const fn new(glyph_w: u8, glyph_h: u8, scale: u16) -> Self {
Self {
glyph_w,
glyph_h,
scale,
}
}
#[must_use]
pub const fn cell_size(&self) -> (u32, u32) {
(
self.glyph_w as u32 * self.scale as u32,
self.glyph_h as u32 * self.scale as u32,
)
}
#[must_use]
pub const fn surface_size(&self, cols: u16, rows: u16) -> (u32, u32) {
let (cell_w, cell_h) = self.cell_size();
(cols as u32 * cell_w, rows as u32 * cell_h)
}
#[must_use]
pub fn pixel_to_cell(&self, x: f64, y: f64) -> Pos {
let (cell_w, cell_h) = self.cell_size();
Pos {
x: pixel_to_cell_axis(x, cell_w),
y: pixel_to_cell_axis(y, cell_h),
}
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub(crate) fn pixel_to_cell_axis(px: f64, cell: u32) -> u16 {
let index =
u32::checked_div(px.max(0.0) as u32, cell).map_or(0, |v| v.min(u32::from(u16::MAX)));
u16::try_from(index).unwrap_or(u16::MAX)
}
#[cfg(test)]
mod tests {
use super::CellGeometry;
use retroglyph_core::grid::Pos;
#[test]
fn cell_size_is_glyph_times_scale() {
assert_eq!(CellGeometry::new(8, 16, 1).cell_size(), (8, 16));
assert_eq!(CellGeometry::new(8, 16, 2).cell_size(), (16, 32));
assert_eq!(CellGeometry::new(6, 12, 3).cell_size(), (18, 36));
}
#[test]
fn surface_size_is_grid_times_cell() {
assert_eq!(CellGeometry::new(8, 16, 1).surface_size(80, 25), (640, 400));
assert_eq!(
CellGeometry::new(8, 16, 2).surface_size(80, 25),
(1280, 800)
);
}
#[test]
fn zero_grid_is_zero_surface() {
assert_eq!(CellGeometry::new(8, 16, 2).surface_size(0, 0), (0, 0));
}
#[test]
fn pixel_to_cell_basic() {
let geometry = CellGeometry::new(8, 16, 1);
assert_eq!(geometry.pixel_to_cell(20.0, 48.0), Pos { x: 2, y: 3 });
}
#[test]
fn pixel_to_cell_origin() {
let geometry = CellGeometry::new(8, 16, 1);
assert_eq!(geometry.pixel_to_cell(0.0, 0.0), Pos { x: 0, y: 0 });
}
#[test]
fn pixel_to_cell_negative_coords_clamp_to_zero() {
let geometry = CellGeometry::new(8, 16, 1);
assert_eq!(geometry.pixel_to_cell(-5.0, -10.0), Pos { x: 0, y: 0 });
}
#[test]
fn pixel_to_cell_zero_cell_size_returns_origin() {
let geometry = CellGeometry::new(0, 0, 1);
assert_eq!(geometry.pixel_to_cell(100.0, 200.0), Pos { x: 0, y: 0 });
}
#[test]
fn pixel_to_cell_accounts_for_scale() {
let geometry = CellGeometry::new(8, 16, 2);
assert_eq!(geometry.pixel_to_cell(20.0, 48.0), Pos { x: 1, y: 1 });
}
#[test]
fn pixel_to_cell_clamps_to_u16_max() {
let geometry = CellGeometry::new(1, 1, 1);
assert_eq!(
geometry.pixel_to_cell(f64::from(u32::MAX), f64::from(u32::MAX)),
Pos {
x: u16::MAX,
y: u16::MAX
}
);
}
}