Skip to main content

orthogonal_2d_wfc/
orthogonal_2d_wfc.rs

1//! # 正交2D WFC系统示例
2//!
3//! 这个示例实现了一个具体的WFC系统,对应C++版本的basicWFCManager。
4//!
5//! ## 系统特性
6//!
7//! - **网格**:10x10的正交2D网格
8//! - **瓷砖**:每个瓷砖有4条边,每条边有2种类型(0或1)
9//! - **可视化**:生成ASCII艺术来显示WFC状态
10//!
11//! ## 瓷砖类型
12//!
13//! - ALL0: 全0瓷砖 [0,0,0,0]
14//! - ALL1: 全1瓷砖 [1,1,1,1]
15//! - 通道瓷砖: [1,1,0,0], [0,0,1,1]
16//! - 三岔路口: [0,1,1,1], [1,0,1,1], [1,1,0,1], [1,1,1,0]
17
18use rlwfc::{
19    Cell, CellState, DefaultInitializer, GridBuilder, GridError,
20    GridSystem, Tile, TileId, TileSet, TileSetVirtual, WfcError, WfcManager,
21};
22
23// =============================================================================
24// 正交2D网格构建器
25// =============================================================================
26
27/// 正交2D网格构建器,对应C++的Orthogonal2DGrid
28struct Orthogonal2DGridBuilder {
29    width: usize,
30    height: usize,
31}
32
33impl Orthogonal2DGridBuilder {
34    fn new(width: usize, height: usize) -> Self {
35        Self { width, height }
36    }
37}
38
39impl GridBuilder for Orthogonal2DGridBuilder {
40    fn build_grid_system(&mut self, grid: &mut GridSystem) -> Result<(), GridError> {
41        println!("构建 {}x{} 正交2D网格...", self.width, self.height);
42
43        // Step 1: 创建所有单元格
44        let mut cells = vec![vec![]; self.height];
45        for y in 0..self.height {
46            cells[y] = Vec::with_capacity(self.width);
47            for x in 0..self.width {
48                let cell_id = grid.add_cell_with_name(
49                    Cell::with_id((y * self.width + x) as u32),
50                    format!("cell_{}_{}", x, y),
51                );
52                cells[y].push(cell_id);
53            }
54        }
55
56        // Step 2: 按照WFC库的要求创建边:东、南、西、北顺序
57        // 这样neighbors()会返回:[北, 西, 南, 东] (petgraph逆序)
58        for y in 0..self.height {
59            for x in 0..self.width {
60                let current = cells[y][x];
61
62                // 1. 东边 (向右)
63                if x < self.width - 1 {
64                    grid.create_edge(current, Some(cells[y][x + 1]))?;
65                } else {
66                    grid.create_edge(current, None)?;
67                }
68
69                // 2. 南边 (向下)
70                if y < self.height - 1 {
71                    grid.create_edge(current, Some(cells[y + 1][x]))?;
72                } else {
73                    grid.create_edge(current, None)?;
74                }
75
76                // 3. 西边 (向左)
77                if x > 0 {
78                    grid.create_edge(current, Some(cells[y][x - 1]))?;
79                } else {
80                    grid.create_edge(current, None)?;
81                }
82
83                // 4. 北边 (向上)
84                if y > 0 {
85                    grid.create_edge(current, Some(cells[y - 1][x]))?;
86                } else {
87                    grid.create_edge(current, None)?;
88                }
89            }
90        }
91
92        println!(
93            "网格构建完成:{} 个单元格,{} 条边",
94            grid.get_cells_count(),
95            grid.get_edges_count()
96        );
97        Ok(())
98    }
99
100    fn get_dimensions(&self) -> Vec<usize> {
101        vec![self.width, self.height]
102    }
103
104    fn get_grid_type_name(&self) -> &'static str {
105        "Orthogonal2DGrid"
106    }
107}
108
109// =============================================================================
110// 方形瓷砖集,对应C++的SquareTileSet
111// =============================================================================
112
113/// 方形瓷砖集,对应C++的SquareTileSet
114struct SquareTileSet {
115    tiles: TileSet<i32>,
116}
117
118impl SquareTileSet {
119    fn new() -> Self {
120        Self {
121            tiles: TileSet::new(),
122        }
123    }
124
125}
126
127impl TileSetVirtual<i32> for SquareTileSet {
128    fn build_tile_set(&mut self) -> Result<(), GridError> {
129        self.tiles.clear();
130
131        // 添加基础瓷砖,用数字表示连接类型
132        // 0 = 空白,1 = 路径
133
134        // 全空白瓷砖
135        self.tiles.add_tile(vec![0, 0, 0, 0], 1); // [北, 西, 南, 东]
136
137        // 直线路径瓷砖
138        self.tiles.add_tile(vec![1, 0, 1, 0], 1); // 垂直路径
139        self.tiles.add_tile(vec![0, 1, 0, 1], 1); // 水平路径
140
141        // 端点
142        // self.tiles.add_tile(vec![1, 0, 0, 0], 1);
143        // self.tiles.add_tile(vec![0, 1, 0, 0], 1);
144        // self.tiles.add_tile(vec![0, 0, 1, 0], 1);
145        // self.tiles.add_tile(vec![0, 0, 0, 1], 1);
146
147        // 转角路径瓷砖
148        // self.tiles.add_tile(vec![1, 1, 0, 0], 1);  // 左上角
149        // self.tiles.add_tile(vec![1, 0, 0, 1], 1);  // 右上角
150        // self.tiles.add_tile(vec![0, 1, 1, 0], 1);  // 左下角
151        // self.tiles.add_tile(vec![0, 0, 1, 1], 1);  // 右下角
152
153        // T型路径瓷砖
154        self.tiles.add_tile(vec![1, 1, 1, 0], 1); // 向右开口的T
155        self.tiles.add_tile(vec![1, 0, 1, 1], 1); // 向左开口的T
156        self.tiles.add_tile(vec![0, 1, 1, 1], 1); // 向上开口的T
157        self.tiles.add_tile(vec![1, 1, 0, 1], 1); // 向下开口的T
158
159        // 十字路口瓷砖
160        self.tiles.add_tile(vec![1, 1, 1, 1], 1); // 全连通
161
162        println!("瓷砖集构建完成:{} 种瓷砖", self.tiles.get_tile_count());
163        Ok(())
164    }
165
166    fn judge_possibility(&self, neighbor_possibilities: &[Vec<TileId>], candidate: TileId) -> bool {
167        let candidate_tile = match self.tiles.get_tile(candidate) {
168            Some(tile) => tile,
169            None => return false,
170        };
171
172        // 检查候选瓷砖与所有邻居的兼容性
173        // neighbor_possibilities的索引对应:[北, 西, 南, 东]
174        for (direction_index, neighbor_tiles) in neighbor_possibilities.iter().enumerate() {
175            if neighbor_tiles.is_empty() {
176                continue;
177            }
178
179            let mut edge_compatible = false;
180            let candidate_edge = candidate_tile.edges[direction_index];
181
182            for &neighbor_tile_id in neighbor_tiles {
183                if let Some(neighbor_tile) = self.tiles.get_tile(neighbor_tile_id) {
184                    // 计算邻居瓷砖对应方向的边索引
185                    // 边数据顺序:[北, 西, 南, 东]
186                    let neighbor_edge_index = match direction_index {
187                        0 => 2, // 北边 -> 邻居的南边
188                        1 => 3, // 西边 -> 邻居的东边
189                        2 => 0, // 南边 -> 邻居的北边
190                        3 => 1, // 东边 -> 邻居的西边
191                        _ => continue,
192                    };
193
194                    let neighbor_edge = neighbor_tile.edges[neighbor_edge_index];
195
196                    // 相邻边必须相等才兼容
197                    if candidate_edge == neighbor_edge {
198                        edge_compatible = true;
199                        break;
200                    }
201                }
202            }
203
204            if !edge_compatible {
205                return false;
206            }
207        }
208
209        true
210    }
211
212    fn get_tile(&self, tile_id: TileId) -> Option<&Tile<i32>> {
213        self.tiles.get_tile(tile_id)
214    }
215
216    fn get_tile_count(&self) -> usize {
217        self.tiles.get_tile_count()
218    }
219
220    fn get_all_tile_ids(&self) -> Vec<TileId> {
221        self.tiles.get_all_tile_ids()
222    }
223}
224
225// =============================================================================
226// 主要演示逻辑
227// =============================================================================
228
229fn main() -> Result<(), Box<dyn std::error::Error>> {
230    println!("正交2D WFC系统演示");
231    println!("对应C++版本的basicWFCManager实现");
232    println!();
233
234    // 创建小一点的网格以便于观察
235    let width = 10;
236    let height = 10;
237
238    // 1. 创建网格系统
239    let grid_builder = Orthogonal2DGridBuilder::new(width, height);
240    let grid = GridSystem::from_builder(grid_builder)?;
241
242    // 2. 创建瓷砖集
243    let tile_set = Box::new(SquareTileSet::new());
244
245    // 3. 创建WFC管理器
246    let mut wfc_manager = WfcManager::new(grid, tile_set)?;
247
248    // 4. 初始化系统
249    let mut initializer = DefaultInitializer;
250    wfc_manager.initialize_with(&mut initializer)?;
251
252    println!("WFC系统初始化完成\n");
253
254    // 5. 显示初始状态
255    println!("初始状态:");
256    print_statistics(&wfc_manager);
257    print_ascii_grid(&wfc_manager, width, height);
258
259    // 6. 运行WFC算法
260    println!("开始WFC算法执行...\n");
261
262    let mut step_count = 0;
263    let max_steps = 500000;
264
265    loop {
266        step_count += 1;
267
268        match wfc_manager.run_step() {
269            Ok(rlwfc::StepResult::Collapsed) => {
270                if step_count <= 5 {
271                    println!("步骤 {}: 成功坍塌一个单元", step_count);
272                }
273            }
274            Ok(rlwfc::StepResult::ConflictsResolved) => {
275                println!("步骤 {}: 解决了冲突", step_count);
276            }
277            Ok(rlwfc::StepResult::Complete) => {
278                println!("步骤 {}: WFC算法完成!", step_count);
279                break;
280            }
281            Ok(rlwfc::StepResult::ConflictResolutionFailed) => {
282                println!("步骤 {}: 冲突解决失败", step_count);
283                break;
284            }
285            Err(e) => {
286                println!("步骤 {}: 错误 - {:?}", step_count, e);
287                break;
288            }
289        }
290
291        // 每20步显示一次状态
292        if step_count % 20 == 0 {
293            print_ascii_grid(&wfc_manager, width, height);
294        }
295
296        if step_count >= max_steps {
297            println!("达到最大步数限制 ({})", max_steps);
298            break;
299        }
300    }
301
302    // 7. 显示最终结果
303    println!("\n最终结果:");
304    print_statistics(&wfc_manager);
305    print_ascii_grid(&wfc_manager, width, height);
306
307    // 8. 演示瓷砖信息
308    demonstrate_tiles(&wfc_manager)?;
309
310    Ok(())
311}
312
313/// 打印系统统计信息
314fn print_statistics(manager: &WfcManager<i32>) {
315    let grid = manager.get_grid();
316    let total_cells = grid.get_cells_count();
317
318    let mut uncollapsed_count = 0;
319    let mut collapsed_count = 0;
320    let mut conflict_count = 0;
321
322    for cell_id in grid.get_all_cells() {
323        match manager.get_cell_state(cell_id) {
324            Ok(rlwfc::CellState::Uncollapsed) => uncollapsed_count += 1,
325            Ok(rlwfc::CellState::Collapsed) => collapsed_count += 1,
326            Ok(rlwfc::CellState::Conflict) => conflict_count += 1,
327            Err(_) => {}
328        }
329    }
330
331    println!("系统统计:");
332    println!("  总单元格数: {}", total_cells);
333    println!("  已坍塌: {}", collapsed_count);
334    println!("  未坍塌: {}", uncollapsed_count);
335    println!("  冲突: {}", conflict_count);
336    println!(
337        "  完成率: {:.1}%",
338        (collapsed_count as f64 / total_cells as f64) * 100.0
339    );
340}
341
342/// 打印ASCII网格
343fn print_ascii_grid(manager: &WfcManager<i32>, width: usize, height: usize) {
344    println!("\nWFC 系统状态可视化:");
345
346    // 打印顶部边框
347    print!("┏");
348    for _ in 0..width * 2 {
349        print!("━");
350    }
351    println!("┓");
352
353    for y in 0..height {
354        print!("┃");
355        for x in 0..width {
356            let cell_name = format!("cell_{}_{}", x, y);
357            let cell_id = manager.get_grid().get_cell_by_name(&cell_name).unwrap();
358
359            let symbol = match manager.get_cell_state(cell_id) {
360                Ok(CellState::Collapsed) => {
361                    let tile_id = manager.get_collapsed_cell_tile(cell_id).unwrap();
362                    let tile = manager.get_tile(tile_id).unwrap();
363                    tile_to_symbol(tile)
364                }
365                Ok(CellState::Uncollapsed) => "?",
366                Ok(CellState::Conflict) => "X",
367                Err(_) => "E",
368            };
369
370            print!("{} ", symbol);
371        }
372        println!("┃");
373    }
374
375    // 打印底部边框
376    print!("┗");
377    for _ in 0..width * 2 {
378        print!("━");
379    }
380    println!("┛");
381
382    println!("图例: ? = 未坍塌, X = 冲突,   = 空地, ┼ = 四通");
383    println!("      ─ │ = 直通道, ┌┐└┘ = 拐角, ├┤┬┴ = 三通, ╵╴╷╶ = 端点");
384}
385
386/// 将瓷砖转换为显示符号
387fn tile_to_symbol(tile: &Tile<i32>) -> &'static str {
388    // 边的顺序是 [北, 西, 南, 东]
389    match tile.edges.as_slice() {
390        [0, 0, 0, 0] => " ", // 全0 - 空地
391        [1, 1, 1, 1] => "┼", // 全1 - 四通
392
393        // 直通道
394        [1, 0, 1, 0] => "│", // 北南通道 - 垂直
395        [0, 1, 0, 1] => "─", // 西东通道 - 水平
396
397        // 拐角 (两个相邻方向的连接)
398        [1, 0, 0, 1] => "└", // 北东拐角
399        [0, 0, 1, 1] => "┌", // 南东拐角
400        [0, 1, 1, 0] => "┐", // 西南拐角
401        [1, 1, 0, 0] => "┘", // 北西拐角
402
403        [1, 0, 0, 0] => "↑",
404        [0, 1, 0, 0] => "←",
405        [0, 0, 1, 0] => "↓",
406        [0, 0, 0, 1] => "→",
407
408        // 三通 (三个方向的连接)
409        [0, 1, 1, 1] => "┬", // 西南东三通 (右侧T)
410        [1, 0, 1, 1] => "├", // 北南东三通 (左侧T)
411        [1, 1, 0, 1] => "┴", // 北西东三通 (顶部T)
412        [1, 1, 1, 0] => "┤", // 北西南三通 (底部T)
413
414        // 其他未定义的组合
415        _ => "?",
416    }
417}
418
419/// 演示瓷砖信息
420fn demonstrate_tiles(manager: &WfcManager<i32>) -> Result<(), WfcError> {
421    println!("\n瓷砖信息:");
422    println!("{}", "─".repeat(40));
423
424    let tile_ids = manager.get_all_tile_ids();
425
426    for (i, &tile_id) in tile_ids.iter().enumerate() {
427        if let Some(tile) = manager.get_tile(tile_id) {
428            let symbol = tile_to_symbol(tile);
429            println!("瓷砖 {}: 边配置 {:?}, 符号: '{}'", i, tile.edges, symbol);
430        }
431    }
432
433    Ok(())
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439
440    #[test]
441    fn test_orthogonal_2d_grid_builder() {
442        let mut builder = Orthogonal2DGridBuilder::new(3, 3);
443        let mut grid = GridSystem::new();
444
445        builder.build_grid_system(&mut grid).unwrap();
446
447        assert_eq!(grid.get_cells_count(), 9);
448
449        // 测试命名查找
450        let cell_1_1 = grid.get_cell_by_name("cell_1_1").unwrap();
451        assert!(grid.contains_cell(cell_1_1));
452    }
453
454    #[test]
455    fn test_square_tile_set() {
456        let mut tile_set = SquareTileSet::new();
457        tile_set.build_tile_set().unwrap();
458
459        assert_eq!(tile_set.get_tile_count(), 8); // 2 + 2 + 4 = 8个瓷砖
460
461        // 测试ALL0瓷砖
462        let all0_tile = tile_set.get_tile(0).unwrap();
463        assert_eq!(all0_tile.edges, vec![0, 0, 0, 0]);
464
465        // 测试ALL1瓷砖
466        let all1_tile = tile_set.get_tile(1).unwrap();
467        assert_eq!(all1_tile.edges, vec![1, 1, 1, 1]);
468    }
469
470    #[test]
471    fn test_tile_compatibility() {
472        let mut tile_set = SquareTileSet::new();
473        tile_set.build_tile_set().unwrap();
474
475        // 测试ALL0瓷砖与自身的兼容性
476        let neighbor_constraints = vec![
477            vec![0], // 上邻居是ALL0
478            vec![],  // 其他方向无约束
479            vec![],
480            vec![],
481        ];
482
483        assert!(tile_set.judge_possibility(&neighbor_constraints, 0)); // ALL0应该兼容
484    }
485}