use embedded_graphics::drawable::{Drawable, Pixel};
use embedded_graphics::fonts::{Font, Text};
use embedded_graphics::geometry::{Dimensions, Point, Size};
use embedded_graphics::pixelcolor::{Rgb888, RgbColor};
use embedded_graphics::style::TextStyleBuilder;
use embedded_graphics::transform::Transform;
use embedded_graphics::DrawTarget;
use std::convert::{Infallible, TryInto};
use crate::block::{Block, BlockAttributes};
use crate::blockgen::BlockGen;
use crate::math::{GridCoordinate, GridPoint, RGB, RGBA};
use crate::space::{Grid, Space};
pub fn draw_text<F>(
space: &mut Space,
color: Rgb888,
origin: GridPoint,
font: F,
text: impl AsRef<str>,
) where
F: Font + Copy,
{
let style = TextStyleBuilder::new(font).text_color(color).build();
Text::new(text.as_ref(), Point::new(origin.x as i32, origin.y as i32))
.into_styled(style)
.draw(&mut VoxelDisplayAdapter::new(space, origin.z))
.unwrap();
}
pub fn draw_to_blocks<D>(ctx: &mut BlockGen, object: D) -> Space
where
for<'a> &'a D: Drawable<Rgb888>,
D: Dimensions + Transform,
{
let block_size: i32 = ctx.size;
let top_left_2d = object.top_left();
let bottom_right_2d = object.bottom_right();
let low_block = GridPoint::new(
floor_divide(top_left_2d.x, block_size),
floor_divide(-bottom_right_2d.y, block_size),
0,
);
let high_block = GridPoint::new(
ceil_divide(bottom_right_2d.x, block_size),
ceil_divide(-top_left_2d.y, block_size),
1,
);
let block_grid = Grid::new(low_block, high_block - low_block);
let mut output_space = Space::empty(block_grid);
for cube in block_grid.interior_iter() {
let mut block_space = ctx.new_block_space();
if false {
block_space.set(
(0, 0, 0),
&Block::Atom(BlockAttributes::default(), RGBA::new(1.0, 0.0, 0.0, 1.0)),
);
}
let offset = Point::new(-cube.x as i32 * block_size, cube.y as i32 * block_size);
object
.translate(offset)
.draw(&mut VoxelDisplayAdapter::new(
&mut block_space,
ctx.size / 2,
))
.unwrap();
output_space.set(
cube,
&Block::Recur(
BlockAttributes::default(),
ctx.universe.insert_anonymous(block_space),
),
);
}
output_space
}
struct VoxelDisplayAdapter<'a> {
space: &'a mut Space,
z: GridCoordinate,
}
impl<'a> VoxelDisplayAdapter<'a> {
fn new(space: &'a mut Space, z: GridCoordinate) -> Self {
Self { space, z }
}
}
impl DrawTarget<Rgb888> for VoxelDisplayAdapter<'_> {
type Error = Infallible;
fn draw_pixel(&mut self, pixel: Pixel<Rgb888>) -> Result<(), Self::Error> {
let Pixel(Point { x, y }, color) = pixel;
self.space.set(
(x as GridCoordinate, -y as GridCoordinate, self.z),
&Block::Atom(BlockAttributes::default(), RGB::from(color).with_alpha(1.0)),
);
Ok(())
}
fn size(&self) -> Size {
let size = self.space.grid().size();
Size {
width: size.x.try_into().unwrap_or(u32::MAX),
height: size.y.try_into().unwrap_or(u32::MAX),
}
}
}
impl From<Rgb888> for RGB {
fn from(color: Rgb888) -> RGB {
RGB::new(
f32::from(color.r()) / 255.0,
f32::from(color.g()) / 255.0,
f32::from(color.b()) / 255.0,
)
}
}
fn ceil_divide(a: i32, b: i32) -> i32 {
assert!(b > 0);
if a < 0 {
a / b
} else {
(a + b - 1) / b
}
}
fn floor_divide(a: i32, b: i32) -> i32 {
assert!(b > 0);
if a > 0 {
a / b
} else {
(a - (b - 1)) / b
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::RGBA;
use crate::universe::Universe;
use embedded_graphics::primitives::{Primitive, Rectangle};
use embedded_graphics::style::{PrimitiveStyle, PrimitiveStyleBuilder};
#[test]
fn drawing_adapter_works() -> Result<(), Infallible> {
let mut space = Space::empty_positive(100, 100, 100);
Pixel(Point::new(2, -3), Rgb888::new(0, 127, 255))
.draw(&mut VoxelDisplayAdapter::new(&mut space, 4))?;
assert_eq!(
space[(2, 3, 4)].color(),
RGBA::new(0.0, 127.0 / 255.0, 1.0, 1.0)
);
Ok(())
}
fn a_primitive_style() -> PrimitiveStyle<Rgb888> {
PrimitiveStyleBuilder::new()
.fill_color(Rgb888::new(0, 127, 255))
.build()
}
fn a_primitive_color() -> RGBA {
RGBA::new(0.0, 127.0 / 255.0, 1.0, 1.0)
}
#[test]
fn draw_to_blocks_bounds_one_block() {
let mut universe = Universe::new();
let mut ctx: BlockGen = BlockGen::new(&mut universe, 16);
let drawable =
Rectangle::new(Point::new(0, 0), Point::new(2, 3)).into_styled(a_primitive_style());
let space = draw_to_blocks(&mut ctx, drawable);
assert_eq!(*space.grid(), Grid::new((0, -1, 0), (1, 1, 1)));
let ref block_space_ref = space[(0, -1, 0)].space().expect("not a recursive block");
assert_eq!(
block_space_ref.borrow()[(0, 15, 8)].color(),
a_primitive_color()
);
}
#[test]
fn draw_to_blocks_bounds_negative_coords_one_block() {
let mut universe = Universe::new();
let mut ctx: BlockGen = BlockGen::new(&mut universe, 16);
let drawable =
Rectangle::new(Point::new(-3, -2), Point::new(0, 0)).into_styled(a_primitive_style());
let space = draw_to_blocks(&mut ctx, drawable);
assert_eq!(*space.grid(), Grid::new((-1, 0, 0), (1, 1, 1)));
let ref block_space_ref = space[(-1, 0, 0)].space().expect("not a recursive block");
assert_eq!(
block_space_ref.borrow()[(15, 0, 8)].color(),
a_primitive_color()
);
}
}