use ndarray::ArrayView2;
use crate::base::Tile;
use crate::colors::Color;
pub fn draw_rect(
frame: &mut [u8],
x_min: usize,
x_max: usize,
y_min: usize,
y_max: usize,
color: [u8; 4],
frame_width: usize,
) {
for py in y_min..y_max {
for px in x_min..x_max {
let idx = (py * frame_width + px) * 4;
if idx + 4 <= frame.len() {
frame[idx..idx + 4].copy_from_slice(&color);
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TileStyle {
pub tri_colors: [Color; 4],
}
pub struct SpriteSquare {
pub size: usize,
pub pixels: Box<[u8]>,
}
impl TileStyle {
#[inline(always)]
pub fn north_color(&self) -> Color {
self.tri_colors[0]
}
#[inline(always)]
pub fn east_color(&self) -> Color {
self.tri_colors[1]
}
#[inline(always)]
pub fn south_color(&self) -> Color {
self.tri_colors[2]
}
#[inline(always)]
pub fn west_color(&self) -> Color {
self.tri_colors[3]
}
pub fn as_sprite(&self, size: usize) -> SpriteSquare {
let mut pixels = vec![0; size * size * 4];
for row in 0..size {
for col in 0..size {
let ne = col > row;
let se = row > size - col - 1;
let color = match (ne, se) {
(true, true) => self.east_color(),
(true, false) => self.north_color(),
(false, true) => self.south_color(),
(false, false) => self.west_color(),
};
let idx = 4 * (row * size + col);
pixels[idx..idx + 4].copy_from_slice(color.as_slice());
}
}
SpriteSquare {
size,
pixels: pixels.into_boxed_slice(),
}
}
}
pub fn blit_sprite(
frame: &mut [u8],
sprite: &SpriteSquare,
grid_x: usize,
grid_y: usize,
frame_width_px: usize,
) {
let tile_size = sprite.size;
let tile_width_bytes = tile_size * 4;
let frame_stride = frame_width_px * 4;
let start = grid_y * tile_size * frame_stride + grid_x * tile_size * 4;
for (e, pixel_row) in sprite.pixels.chunks(tile_width_bytes).enumerate() {
let from = start + e * frame_stride;
frame[from..from + tile_width_bytes].copy_from_slice(pixel_row);
}
}
pub fn render_tiles(
frame: &mut [u8],
tiles: ArrayView2<Tile>,
sprites: &[SpriteSquare],
frame_width_px: usize,
) {
for ((y, x), &tileid) in tiles.indexed_iter() {
if let Some(sprite) = sprites.get(tileid as usize) {
blit_sprite(frame, sprite, x, y, frame_width_px);
}
}
}
pub fn render_outlines(
frame: &mut [u8],
tiles: ArrayView2<Tile>,
scale: usize,
frame_width_px: usize,
) {
let outline_color = [0u8, 0, 0, 255];
for ((y, x), &tileid) in tiles.indexed_iter() {
if tileid == 0 {
continue;
}
let tx = x * scale;
let ty = y * scale;
draw_rect(frame, tx, tx + scale, ty, ty + 1, outline_color, frame_width_px);
draw_rect(frame, tx, tx + scale, ty + scale - 1, ty + scale, outline_color, frame_width_px);
draw_rect(frame, tx, tx + 1, ty, ty + scale, outline_color, frame_width_px);
draw_rect(frame, tx + scale - 1, tx + scale, ty, ty + scale, outline_color, frame_width_px);
}
}
pub fn render_blockers(
frame: &mut [u8],
tiles: ArrayView2<Tile>,
blocker_masks: &[u8],
scale: usize,
frame_width_px: usize,
frame_height_px: usize,
) {
let depth = (scale / 3).max(2);
let half_len = (scale / 3).max(2);
let blocker_color = [140, 140, 140, 255];
for ((y, x), &tileid) in tiles.indexed_iter() {
let mask = blocker_masks.get(tileid as usize).copied().unwrap_or(0);
if mask == 0 {
continue;
}
let tile_x = x * scale;
let tile_y = y * scale;
let mid_x = tile_x + scale / 2;
let mid_y = tile_y + scale / 2;
if mask & 0b0001 != 0 {
draw_rect(
frame,
mid_x.saturating_sub(half_len),
mid_x + half_len,
tile_y.saturating_sub(depth),
tile_y,
blocker_color,
frame_width_px,
);
}
if mask & 0b0010 != 0 {
let right = tile_x + scale;
draw_rect(
frame,
right,
(right + depth).min(frame_width_px),
mid_y.saturating_sub(half_len),
mid_y + half_len,
blocker_color,
frame_width_px,
);
}
if mask & 0b0100 != 0 {
let bottom = tile_y + scale;
draw_rect(
frame,
mid_x.saturating_sub(half_len),
mid_x + half_len,
bottom,
(bottom + depth).min(frame_height_px),
blocker_color,
frame_width_px,
);
}
if mask & 0b1000 != 0 {
draw_rect(
frame,
tile_x.saturating_sub(depth),
tile_x,
mid_y.saturating_sub(half_len),
mid_y + half_len,
blocker_color,
frame_width_px,
);
}
}
}
pub fn render_mismatches(
frame: &mut [u8],
mismatch_locs: &ArrayView2<usize>,
scale: usize,
frame_width_px: usize,
) {
let thick = (scale / 4).max(1);
let long = (scale / 3).max(1);
let color = [255, 0, 0, 255];
for ((y, x), &mm) in mismatch_locs.indexed_iter() {
if mm == 0 {
continue;
}
if mm & 0b0010 != 0 {
let edge_y = y * scale + scale;
let mid_x = x * scale + scale / 2;
draw_rect(
frame,
mid_x.saturating_sub(long),
mid_x + long,
edge_y.saturating_sub(thick),
edge_y + thick,
color,
frame_width_px,
);
}
if mm & 0b0001 != 0 {
let edge_x = x * scale;
let mid_y = y * scale + scale / 2;
draw_rect(
frame,
edge_x.saturating_sub(thick),
edge_x + thick,
mid_y.saturating_sub(long),
mid_y + long,
color,
frame_width_px,
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array2;
fn pixel_at(frame: &[u8], x: usize, y: usize, width: usize) -> [u8; 4] {
let idx = (y * width + x) * 4;
[frame[idx], frame[idx + 1], frame[idx + 2], frame[idx + 3]]
}
#[test]
fn draw_rect_writes_correct_pixels() {
let (w, h) = (4, 4);
let mut frame = vec![0u8; w * h * 4];
let color = [10, 20, 30, 255];
draw_rect(&mut frame, 1, 3, 1, 3, color, w);
assert_eq!(pixel_at(&frame, 1, 1, w), color);
assert_eq!(pixel_at(&frame, 2, 2, w), color);
assert_eq!(pixel_at(&frame, 0, 0, w), [0, 0, 0, 0]);
assert_eq!(pixel_at(&frame, 3, 3, w), [0, 0, 0, 0]);
}
#[test]
fn draw_rect_zero_size_is_noop() {
let mut frame = vec![0u8; 16];
draw_rect(&mut frame, 2, 2, 0, 1, [255; 4], 2);
assert!(frame.iter().all(|&b| b == 0));
}
#[test]
fn draw_rect_out_of_bounds_safe() {
let mut frame = vec![0u8; 4 * 4 * 4];
draw_rect(&mut frame, 2, 10, 2, 10, [255; 4], 4);
assert_eq!(pixel_at(&frame, 3, 3, 4), [255; 4]);
}
#[test]
fn as_sprite_triangle_regions() {
let n = [10, 0, 0, 255];
let e = [0, 10, 0, 255];
let s = [0, 0, 10, 255];
let w = [0, 0, 0, 255];
let style = TileStyle {
tri_colors: [n, e, s, w],
};
let size = 8;
let sprite = style.as_sprite(size);
assert_eq!(sprite.size, size);
let px = |col: usize, row: usize| -> [u8; 4] {
let idx = 4 * (row * size + col);
[
sprite.pixels[idx],
sprite.pixels[idx + 1],
sprite.pixels[idx + 2],
sprite.pixels[idx + 3],
]
};
assert_eq!(px(size - 1, 0), n);
assert_eq!(px(size - 1, size - 1), s);
assert_eq!(px(0, size - 1), w);
assert_eq!(px(size - 1, size / 2), e);
}
#[test]
fn blit_sprite_places_pixels_correctly() {
let tile_size = 2;
let grid_w = 2;
let frame_w = grid_w * tile_size; let frame_h = 2 * tile_size; let mut frame = vec![0u8; frame_w * frame_h * 4];
let red = [255, 0, 0, 255];
let sprite = SpriteSquare {
size: tile_size,
pixels: vec![
red[0], red[1], red[2], red[3], red[0], red[1], red[2], red[3], red[0], red[1], red[2], red[3], red[0], red[1], red[2], red[3], ]
.into_boxed_slice(),
};
blit_sprite(&mut frame, &sprite, 1, 0, frame_w);
assert_eq!(pixel_at(&frame, 2, 0, frame_w), red);
assert_eq!(pixel_at(&frame, 3, 1, frame_w), red);
assert_eq!(pixel_at(&frame, 0, 0, frame_w), [0, 0, 0, 0]);
}
#[test]
fn render_tiles_blits_all() {
let scale = 2;
let tiles = Array2::from_shape_vec((1, 2), vec![0u32, 1u32]).unwrap();
let frame_w = 2 * scale;
let mut frame = vec![0u8; frame_w * scale * 4];
let blue = [0, 0, 200, 255];
let empty_sprite = SpriteSquare {
size: scale,
pixels: vec![0u8; scale * scale * 4].into_boxed_slice(),
};
let blue_sprite = SpriteSquare {
size: scale,
pixels: vec![blue[0], blue[1], blue[2], blue[3]]
.repeat(scale * scale)
.into_boxed_slice(),
};
let sprites = vec![empty_sprite, blue_sprite];
render_tiles(&mut frame, tiles.view(), &sprites, frame_w);
assert_eq!(pixel_at(&frame, 2, 0, frame_w), blue);
assert_eq!(pixel_at(&frame, 0, 0, frame_w), [0, 0, 0, 0]);
}
#[test]
fn render_outlines_draws_borders_for_nonempty() {
let scale = 12;
let tiles = Array2::from_shape_vec((1, 2), vec![0u32, 1u32]).unwrap();
let frame_w = 2 * scale;
let frame_h = scale;
let mut frame = vec![0u8; frame_w * frame_h * 4];
render_outlines(&mut frame, tiles.view(), scale, frame_w);
let black = [0u8, 0, 0, 255];
assert_eq!(pixel_at(&frame, scale, 0, frame_w), black);
assert_eq!(pixel_at(&frame, 2 * scale - 1, scale - 1, frame_w), black);
assert_eq!(pixel_at(&frame, 0, 0, frame_w), [0, 0, 0, 0]);
assert_eq!(pixel_at(&frame, scale - 1, scale - 1, frame_w), [0, 0, 0, 0]);
}
#[test]
fn render_blockers_north_bit() {
let scale = 12;
let tiles = Array2::from_shape_vec((2, 1), vec![0u32, 1u32]).unwrap();
let frame_w = scale;
let frame_h = 2 * scale;
let mut frame = vec![0u8; frame_w * frame_h * 4];
let masks: Vec<u8> = vec![0, 0b0001];
render_blockers(&mut frame, tiles.view(), &masks, scale, frame_w, frame_h);
let blocker = [140, 140, 140, 255];
let depth = (scale / 3).max(2);
let check_y = scale - 1; let check_x = scale / 2;
assert!(check_y >= scale.saturating_sub(depth));
assert_eq!(pixel_at(&frame, check_x, check_y, frame_w), blocker);
}
#[test]
fn render_blockers_each_direction() {
let scale = 12;
let mut tiles = Array2::zeros((3, 3));
tiles[[1, 1]] = 1u32;
let frame_w = 3 * scale;
let frame_h = 3 * scale;
let mut frame = vec![0u8; frame_w * frame_h * 4];
let masks: Vec<u8> = vec![0, 0b1111]; render_blockers(&mut frame, tiles.view(), &masks, scale, frame_w, frame_h);
let blocker = [140, 140, 140, 255];
let mid_x = scale + scale / 2; let mid_y = scale + scale / 2;
assert_eq!(pixel_at(&frame, mid_x, scale - 1, frame_w), blocker);
assert_eq!(pixel_at(&frame, mid_x, 2 * scale, frame_w), blocker);
assert_eq!(pixel_at(&frame, 2 * scale, mid_y, frame_w), blocker);
assert_eq!(pixel_at(&frame, scale - 1, mid_y, frame_w), blocker);
}
#[test]
fn render_mismatches_south_bit() {
let scale = 12;
let mut locs = Array2::<usize>::zeros((2, 1));
locs[[0, 0]] = 0b0010; let frame_w = scale;
let frame_h = 2 * scale;
let mut frame = vec![0u8; frame_w * frame_h * 4];
render_mismatches(&mut frame, &locs.view(), scale, frame_w);
let red = [255, 0, 0, 255];
let mid_x = scale / 2;
assert_eq!(pixel_at(&frame, mid_x, scale, frame_w), red);
}
#[test]
fn render_mismatches_west_bit() {
let scale = 12;
let mut locs = Array2::<usize>::zeros((1, 2));
locs[[0, 1]] = 0b0001; let frame_w = 2 * scale;
let frame_h = scale;
let mut frame = vec![0u8; frame_w * frame_h * 4];
render_mismatches(&mut frame, &locs.view(), scale, frame_w);
let red = [255, 0, 0, 255];
let mid_y = scale / 2;
assert_eq!(pixel_at(&frame, scale, mid_y, frame_w), red);
}
#[test]
fn render_mismatches_zero_is_noop() {
let scale = 12;
let locs = Array2::<usize>::zeros((2, 2));
let frame_w = 2 * scale;
let frame_h = 2 * scale;
let mut frame = vec![0u8; frame_w * frame_h * 4];
render_mismatches(&mut frame, &locs.view(), scale, frame_w);
assert!(frame.iter().all(|&b| b == 0));
}
}