wang_corner_carpet/
wang_corner_carpet.rs

1// An example which shows that exhaustive tile sets aren't so much desired by WFC
2use {
3    simple_tiled_wfc::grid_generation::{WfcModule, WfcContext, WfcContextBuilder},
4    macroquad::prelude::{scene::{Node, RefMut}, *},
5    macroquad::miniquad::{TextureParams, TextureFormat, TextureWrap},
6    std::{ sync::mpsc::channel }
7};
8
9pub const fn get_north_west(kind_code: u8) -> u8 { kind_code & 0b11 }
10pub const fn get_north_east(kind_code: u8) -> u8 { (kind_code / 0b100) & 0b11 }
11pub const fn get_south_east(kind_code: u8) -> u8 { (kind_code / 0b10000) & 0b11 }
12pub const fn get_south_west(kind_code: u8) -> u8 { (kind_code / 0b1000000) & 0b11 }
13
14pub struct SubRect {
15    pub x: i32,
16    pub y: i32,
17    pub width: i32,
18    pub height: i32
19}
20
21//noinspection DuplicatedCode
22fn draw_subrect(tex: Texture2D, subrect: &SubRect, x: f32, y: f32, scale: f32) {
23    let InternalGlContext {
24        quad_context: ctx, ..
25    } = unsafe { get_internal_gl() };
26    draw_texture_ex(
27        tex,
28        x * ctx.dpi_scale(), y * ctx.dpi_scale(),
29        WHITE,
30        DrawTextureParams {
31            source: Some(Rect::new(
32                subrect.x as f32, subrect.y as f32,
33                subrect.width as f32, subrect.height as f32
34            )),
35            dest_size: Some([
36                subrect.width as f32 * ctx.dpi_scale() * scale,
37                subrect.height as f32 * ctx.dpi_scale() * scale
38            ].into()),
39            ..Default::default()
40        },
41    );
42}
43
44fn window_conf() -> Conf {
45    Conf {
46        window_title: "Wang corner carpet".to_owned(),
47        fullscreen: false,
48        window_width: 1280,
49        window_height: 800,
50        high_dpi: true,
51        ..Default::default()
52    }
53}
54
55type CustomBitSet = [u8; 32];
56
57struct WangTilemap {
58    w: usize,
59    h: usize,
60    tile_width: usize,
61    tile_height: usize,
62    tiles: Vec<(u8, SubRect)>,
63    modules: Vec<WfcModule<CustomBitSet>>,
64    texture: Texture2D,
65    map_data: Vec<usize>
66}
67
68impl WangTilemap {
69    pub async fn new(w: usize, h: usize) -> Self {
70        let InternalGlContext {
71            quad_context: ctx, ..
72        } = unsafe { get_internal_gl() };
73
74        let texture_bytes = load_file("assets/wang_4_corner.png").await.unwrap();
75
76        let img = image::load_from_memory(&texture_bytes[..])
77            .unwrap_or_else(|e| panic!("{}", e))
78            .to_rgba8();
79
80        let img_w = img.width();
81        let img_h = img.height();
82
83        let texture = Texture2D::from_miniquad_texture(
84            miniquad::Texture::from_data_and_format(
85                ctx,
86                &img.into_raw(),
87                TextureParams {
88                    format: TextureFormat::RGBA8,
89                    wrap: TextureWrap::Clamp,
90                    filter: FilterMode::Nearest,
91                    width: img_w,
92                    height: img_h
93                }
94            )
95        );
96
97        let tiles = (0..=255)
98            .map(|idx| {
99                let width = 32;
100                let height = 32;
101                (
102                    idx as u8,
103                    SubRect {
104                        x: (idx % 16) * width,
105                        y: (idx / 16) * height,
106                        width,
107                        height
108                    }
109                )
110            })
111            .collect::<Vec<_>>();
112
113        let modules = tiles
114            .iter()
115            .map(|tile| {
116                let mut module: WfcModule<CustomBitSet> = WfcModule::new();
117                for i in 0..tiles.len() {
118                    let other_tile = &tiles[i];
119                    if get_north_west(tile.0) == get_south_west(other_tile.0) &&
120                        get_north_east(tile.0) == get_south_east(other_tile.0) {
121                        module.add_north_neighbour(i);
122                    }
123                    if get_south_west(tile.0) == get_north_west(other_tile.0) &&
124                        get_south_east(tile.0) == get_north_east(other_tile.0) {
125                        module.add_south_neighbour(i);
126                    }
127                    if get_north_west(tile.0) == get_north_east(other_tile.0) &&
128                        get_south_west(tile.0) == get_south_east(other_tile.0) {
129                        module.add_west_neighbour(i);
130                    }
131                    if get_north_east(tile.0) == get_north_west(other_tile.0) &&
132                        get_south_east(tile.0) == get_south_west(other_tile.0) {
133                        module.add_east_neighbour(i);
134                    }
135                }
136                module
137            })
138            .collect::<Vec<_>>();
139
140        Self {
141            w,
142            h,
143            tile_width: 32,
144            tile_height: 32,
145            tiles,
146            modules,
147            texture,
148            map_data: vec![0; w*h]
149        }
150    }
151
152    pub fn generate_new_map(&mut self) {
153        let mut wfc_context: WfcContext<CustomBitSet> = WfcContextBuilder
154        ::new(&self.modules, self.w, self.h)
155            .build();
156
157        let (tx, rc) = channel();
158
159        wfc_context.collapse(100, tx.clone());
160
161        let results = rc.recv()
162            .unwrap()
163            .unwrap_or_else(|_| vec![0; self.w * self.h]);
164
165        self.map_data.clear();
166        self.map_data.extend_from_slice(&results[..]);
167    }
168}
169
170impl Node for WangTilemap {
171    fn update(mut node: RefMut<Self>) {
172        if is_key_pressed(KeyCode::Space) {
173            node.generate_new_map();
174        }
175    }
176
177    fn draw(node: RefMut<Self>) {
178        let InternalGlContext {
179            quad_context: ctx, ..
180        } = unsafe { get_internal_gl() };
181
182        const SCALE_UP: f32 = 0.5;
183        let start_x = screen_width() / ctx.dpi_scale();
184        let start_x = (start_x - (node.w * node.tile_width) as f32 * SCALE_UP) / 2.0;
185
186        let start_y = screen_height() / ctx.dpi_scale();
187        let start_y = (start_y - (node.h * node.tile_height) as f32 * SCALE_UP) / 2.0;
188
189        for j in 0..node.h {
190            for i in 0..node.w {
191                let idx = node.w * j + i;
192                let x = start_x + (node.tile_width * i) as f32 * SCALE_UP;
193                let y = start_y + (node.tile_height * j) as f32 * SCALE_UP;
194                let idx = node.map_data[idx];
195                draw_subrect(node.texture, &(node.tiles[idx].1), x, y, SCALE_UP);
196            }
197        }
198    }
199}
200
201#[macroquad::main(window_conf)]
202async fn main() {
203    scene::add_node({
204        let mut tilemap = WangTilemap::new(48, 48).await;
205        tilemap.generate_new_map();
206        tilemap
207    });
208    loop {
209        if is_key_pressed(KeyCode::Escape) {
210            break;
211        }
212        clear_background(Color::new(0.12, 0.1, 0.15, 1.00));
213        next_frame().await;
214    }
215}