1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use crate::*;

use image::{GenericImage, GenericImageView, RgbaImage};
use std::fmt::Debug;

#[derive(Debug)]
pub struct CanvasBlock {
    pub image: DynamicImage,
    pub handle: TextureHandle,
    pub modified: bool,
}

pub const CANVAS_BLOCK_SIZE: i32 = 1024;
const BLOCK_SIZE: i32 = CANVAS_BLOCK_SIZE;
const PIXELS_PER_WORLD_UNIT: i32 = 16;

pub const fn blood_block_world_size() -> i32 {
    BLOCK_SIZE / PIXELS_PER_WORLD_UNIT
}

#[derive(Debug)]
pub struct BloodCanvas {
    pub creator: Arc<AtomicRefCell<dyn TextureCreator + Send + Sync + 'static>>,
    pub blocks: HashMap<IVec2, CanvasBlock>,
}

impl BloodCanvas {
    pub fn new(creator: Arc<AtomicRefCell<dyn TextureCreator + Send + Sync + 'static>>) -> Self {
        Self {
            creator,
            blocks: HashMap::default(),
        }
    }

    pub fn set_pixel(&mut self, position: Vec2, color: Color) {
        let position = position * PIXELS_PER_WORLD_UNIT as f32;

        self.set_pixel_internal(position.x as i32, position.y as i32, color)
    }

    fn get_block(&mut self, x: i32, y: i32) -> &mut CanvasBlock {
        let key = ivec2(x, y);

        self.blocks.entry(key).or_insert_with(|| {
            let image =
                DynamicImage::ImageRgba8(RgbaImage::new(BLOCK_SIZE as u32, BLOCK_SIZE as u32));

            let name = format!("blood-canvas-{}-{}", x, y);

            let handle = self.creator.borrow_mut().handle_from_image(&name, &image);

            CanvasBlock {
                handle,
                image,
                modified: false,
            }
        })
    }

    pub fn circle_at_internal(
        &mut self,
        position: Vec2,
        radius: i32,
        pixel_prob: f32,
        color: fn() -> Color,
    ) {
        let position = position * PIXELS_PER_WORLD_UNIT as f32;

        let x = position.x as i32;
        let y = position.y as i32;

        for dx in -radius..radius {
            for dy in -radius..radius {
                if dx * dx + dy * dy < radius * radius && flip_coin(pixel_prob) {
                    self.set_pixel_internal(x + dx, y + dy, color());
                }
            }
        }
    }

    fn set_pixel_internal(&mut self, x: i32, y: i32, color: Color) {
        let bx = (x as f32 / BLOCK_SIZE as f32).floor() as i32;
        let by = (y as f32 / BLOCK_SIZE as f32).floor() as i32;

        let block = self.get_block(bx, by);

        block.modified = true;
        block.image.put_pixel(
            (x - bx * BLOCK_SIZE) as u32,
            (y - by * BLOCK_SIZE) as u32,
            image::Rgba([
                (color.r * 255.0) as u8,
                (color.g * 255.0) as u8,
                (color.b * 255.0) as u8,
                (color.a * 255.0) as u8,
            ]),
        );
    }

    pub fn blit_at(
        &mut self,
        texture: TextureHandle,
        position: Vec2,
        source_rect: Option<IRect>,
        tint: Color,
    ) {
        let assets = ASSETS.borrow_mut();
        let image_map = assets.texture_image_map.lock();

        if let Some(image) = image_map.get(&texture) {
            let rect = source_rect.unwrap_or(IRect::new(
                ivec2(0, 0),
                ivec2(image.width() as i32, image.height() as i32),
            ));

            let size_offset = rect.size.as_vec2() / 2.0;

            for x in 0..rect.size.x {
                for y in 0..rect.size.y {
                    let px =
                        image.get_pixel((x + rect.offset.x) as u32, (y + rect.offset.y) as u32);

                    if px.0[3] > 0 {
                        self.set_pixel(
                            position + vec2(x as f32, y as f32) / 16.0 - size_offset / 16.0,
                            // position,
                            Into::<Color>::into(px) * tint,
                            // RED,
                        );
                    }
                }
            }
        }
    }
}

pub trait TextureCreator: Debug {
    fn handle_from_image(&self, name: &str, image: &DynamicImage) -> TextureHandle;
    fn update_texture(&self, image: &DynamicImage, texture: TextureHandle);
}