use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
use rayon::prelude::*;
use crate::physics::BallColor;
const BRAILLE_BASE: u32 = 0x2800;
const BRAILLE_DOT_BITS: [u8; 8] = [
0b0000_0001, 0b0000_0010, 0b0000_0100, 0b0100_0000, 0b0000_1000, 0b0001_0000, 0b0010_0000, 0b1000_0000, ];
pub const NUM_BALL_COLORS: usize = 7;
#[derive(Debug, Clone, Copy, Default)]
pub struct CellData {
pub dot_bits: u8,
pub ball_count: u16,
pub color_counts: [u16; NUM_BALL_COLORS],
}
impl CellData {
pub fn dominant_color(&self) -> BallColor {
let mut max_count = 0u16;
let mut max_index = 0usize;
for (i, &count) in self.color_counts.iter().enumerate() {
if i == 0 {
if count > max_count {
max_count = count;
max_index = i;
}
} else if count >= max_count && count > 0 {
max_count = count;
max_index = i;
}
}
BallColor::from_index(max_index)
}
}
struct AtomicCellData {
dot_bits: AtomicU8,
ball_count: AtomicU16,
color_counts: [AtomicU16; NUM_BALL_COLORS],
}
impl Default for AtomicCellData {
fn default() -> Self {
Self {
dot_bits: AtomicU8::new(0),
ball_count: AtomicU16::new(0),
color_counts: [
AtomicU16::new(0),
AtomicU16::new(0),
AtomicU16::new(0),
AtomicU16::new(0),
AtomicU16::new(0),
AtomicU16::new(0),
AtomicU16::new(0),
],
}
}
}
pub struct BrailleCanvas {
width: u16,
height: u16,
cells: Vec<CellData>,
atomic_cells: Vec<AtomicCellData>,
}
impl BrailleCanvas {
pub fn new(width: u16, height: u16) -> Self {
let size = (width as usize) * (height as usize);
Self {
width,
height,
cells: vec![CellData::default(); size],
atomic_cells: (0..size).map(|_| AtomicCellData::default()).collect(),
}
}
pub fn clear(&mut self) {
for cell in &mut self.cells {
cell.dot_bits = 0;
cell.ball_count = 0;
cell.color_counts = [0; NUM_BALL_COLORS];
}
for cell in &self.atomic_cells {
cell.dot_bits.store(0, Ordering::Relaxed);
cell.ball_count.store(0, Ordering::Relaxed);
for color_count in &cell.color_counts {
color_count.store(0, Ordering::Relaxed);
}
}
}
pub fn plot(&mut self, sub_x: u32, sub_y: u32) {
self.plot_with_color(sub_x, sub_y, BallColor::White);
}
pub fn plot_with_color(&mut self, sub_x: u32, sub_y: u32, color: BallColor) {
let cell_x = (sub_x / 2) as u16;
let cell_y = (sub_y / 4) as u16;
if cell_x >= self.width || cell_y >= self.height {
return;
}
let local_x = (sub_x % 2) as usize;
let local_y = (sub_y % 4) as usize;
let bit_index = local_y * 2 + local_x;
let dot_bit = BRAILLE_DOT_BITS[bit_index];
let idx = (cell_y as usize) * (self.width as usize) + (cell_x as usize);
self.cells[idx].dot_bits |= dot_bit;
self.cells[idx].ball_count += 1;
self.cells[idx].color_counts[color.index()] += 1;
}
pub fn plot_batch_parallel(&self, positions: &[(u32, u32)]) {
let width = self.width as u32;
let height = self.height as u32;
let stride = self.width as usize;
positions.par_iter().for_each(|&(sub_x, sub_y)| {
let cell_x = sub_x / 2;
let cell_y = sub_y / 4;
if cell_x >= width || cell_y >= height {
return;
}
let local_x = (sub_x % 2) as usize;
let local_y = (sub_y % 4) as usize;
let bit_index = local_y * 2 + local_x;
let dot_bit = BRAILLE_DOT_BITS[bit_index];
let idx = (cell_y as usize) * stride + (cell_x as usize);
self.atomic_cells[idx]
.dot_bits
.fetch_or(dot_bit, Ordering::Relaxed);
self.atomic_cells[idx]
.ball_count
.fetch_add(1, Ordering::Relaxed);
self.atomic_cells[idx].color_counts[0].fetch_add(1, Ordering::Relaxed);
});
}
pub fn plot_batch_parallel_with_colors(&self, positions: &[(u32, u32, BallColor)]) {
let width = self.width as u32;
let height = self.height as u32;
let stride = self.width as usize;
positions.par_iter().for_each(|&(sub_x, sub_y, color)| {
let cell_x = sub_x / 2;
let cell_y = sub_y / 4;
if cell_x >= width || cell_y >= height {
return;
}
let local_x = (sub_x % 2) as usize;
let local_y = (sub_y % 4) as usize;
let bit_index = local_y * 2 + local_x;
let dot_bit = BRAILLE_DOT_BITS[bit_index];
let idx = (cell_y as usize) * stride + (cell_x as usize);
self.atomic_cells[idx]
.dot_bits
.fetch_or(dot_bit, Ordering::Relaxed);
self.atomic_cells[idx]
.ball_count
.fetch_add(1, Ordering::Relaxed);
self.atomic_cells[idx].color_counts[color.index()].fetch_add(1, Ordering::Relaxed);
});
}
pub fn sync_from_atomic(&mut self) {
for (cell, atomic) in self.cells.iter_mut().zip(self.atomic_cells.iter()) {
cell.dot_bits = atomic.dot_bits.load(Ordering::Relaxed);
cell.ball_count = atomic.ball_count.load(Ordering::Relaxed);
for (i, count) in cell.color_counts.iter_mut().enumerate() {
*count = atomic.color_counts[i].load(Ordering::Relaxed);
}
}
}
pub fn get_char(&self, col: u16, row: u16) -> char {
if col >= self.width || row >= self.height {
return char::from_u32(BRAILLE_BASE).unwrap_or(' ');
}
let idx = (row as usize) * (self.width as usize) + (col as usize);
let dot_bits = self.cells[idx].dot_bits;
char::from_u32(BRAILLE_BASE + u32::from(dot_bits)).unwrap_or(' ')
}
pub fn get_ball_count(&self, col: u16, row: u16) -> u16 {
if col >= self.width || row >= self.height {
return 0;
}
let idx = (row as usize) * (self.width as usize) + (col as usize);
self.cells[idx].ball_count
}
pub fn get_dominant_color(&self, col: u16, row: u16) -> BallColor {
if col >= self.width || row >= self.height {
return BallColor::White;
}
let idx = (row as usize) * (self.width as usize) + (col as usize);
self.cells[idx].dominant_color()
}
pub fn get_cell(&self, col: u16, row: u16) -> Option<&CellData> {
if col >= self.width || row >= self.height {
return None;
}
let idx = (row as usize) * (self.width as usize) + (col as usize);
self.cells.get(idx)
}
pub fn resize(&mut self, width: u16, height: u16) {
let new_size = (width as usize) * (height as usize);
self.cells.resize(new_size, CellData::default());
self.atomic_cells
.resize_with(new_size, AtomicCellData::default);
self.width = width;
self.height = height;
self.clear();
}
pub fn dimensions(&self) -> (u16, u16) {
(self.width, self.height)
}
pub fn subpixel_dimensions(&self) -> (u32, u32) {
((self.width as u32) * 2, (self.height as u32) * 4)
}
}
pub fn physics_to_subpixel(
physics_x: f32,
physics_y: f32,
world_width: f32,
world_height: f32,
canvas_width: u16,
canvas_height: u16,
) -> (u32, u32) {
let sub_width = (canvas_width as u32) * 2;
let sub_height = (canvas_height as u32) * 4;
let norm_x = physics_x / world_width;
let norm_y = 1.0 - (physics_y / world_height);
let sub_x = (norm_x * sub_width as f32).clamp(0.0, (sub_width.saturating_sub(1)) as f32) as u32;
let sub_y =
(norm_y * sub_height as f32).clamp(0.0, (sub_height.saturating_sub(1)) as f32) as u32;
(sub_x, sub_y)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_braille_dot_mapping() {
assert_eq!(BRAILLE_DOT_BITS[0], 0b0000_0001); assert_eq!(BRAILLE_DOT_BITS[4], 0b0000_1000); assert_eq!(BRAILLE_DOT_BITS[3], 0b0100_0000); assert_eq!(BRAILLE_DOT_BITS[7], 0b1000_0000); }
#[test]
fn test_braille_character_generation() {
let mut canvas = BrailleCanvas::new(10, 10);
canvas.plot(0, 0);
let ch = canvas.get_char(0, 0);
assert_eq!(ch, '\u{2801}');
}
#[test]
fn test_full_braille_character() {
let mut canvas = BrailleCanvas::new(10, 10);
for sub_x in 0..2 {
for sub_y in 0..4 {
canvas.plot(sub_x, sub_y);
}
}
let ch = canvas.get_char(0, 0);
assert_eq!(ch, '\u{28FF}');
}
#[test]
fn test_physics_to_subpixel_origin() {
let (sub_x, sub_y) = physics_to_subpixel(0.0, 0.0, 10.0, 10.0, 10, 10);
assert_eq!(sub_x, 0);
assert_eq!(sub_y, 39); }
#[test]
fn test_physics_to_subpixel_top() {
let (sub_x, sub_y) = physics_to_subpixel(0.0, 10.0, 10.0, 10.0, 10, 10);
assert_eq!(sub_x, 0);
assert_eq!(sub_y, 0);
}
#[test]
fn test_ball_count() {
let mut canvas = BrailleCanvas::new(10, 10);
canvas.plot(0, 0);
canvas.plot(0, 0);
canvas.plot(0, 0);
assert_eq!(canvas.get_ball_count(0, 0), 3);
}
}