wang_corner_carpet/
wang_corner_carpet.rs1use {
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
21fn 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}