terrain_forge/algorithms/
bsp.rs1use crate::{Algorithm, Grid, Rng, Tile};
2
3#[derive(Debug, Clone)]
4pub struct BspConfig {
5 pub min_room_size: usize,
6 pub max_depth: usize,
7 pub room_padding: usize,
8}
9
10impl Default for BspConfig {
11 fn default() -> Self {
12 Self { min_room_size: 5, max_depth: 4, room_padding: 1 }
13 }
14}
15
16pub struct Bsp {
17 config: BspConfig,
18}
19
20impl Bsp {
21 pub fn new(config: BspConfig) -> Self { Self { config } }
22}
23
24impl Default for Bsp {
25 fn default() -> Self { Self::new(BspConfig::default()) }
26}
27
28struct BspNode {
29 x: usize, y: usize, w: usize, h: usize,
30 left: Option<Box<BspNode>>,
31 right: Option<Box<BspNode>>,
32 room: Option<(usize, usize, usize, usize)>,
33}
34
35impl BspNode {
36 fn new(x: usize, y: usize, w: usize, h: usize) -> Self {
37 Self { x, y, w, h, left: None, right: None, room: None }
38 }
39
40 fn split(&mut self, rng: &mut Rng, min_size: usize, depth: usize, max_depth: usize) {
41 if depth >= max_depth { return; }
42
43 let can_h = self.h >= min_size * 2;
44 let can_v = self.w >= min_size * 2;
45 if !can_h && !can_v { return; }
46
47 let split_h = if can_h && can_v { rng.chance(0.5) } else { can_h };
48
49 if split_h {
50 let split = rng.range_usize(min_size, self.h - min_size + 1);
51 self.left = Some(Box::new(BspNode::new(self.x, self.y, self.w, split)));
52 self.right = Some(Box::new(BspNode::new(self.x, self.y + split, self.w, self.h - split)));
53 } else {
54 let split = rng.range_usize(min_size, self.w - min_size + 1);
55 self.left = Some(Box::new(BspNode::new(self.x, self.y, split, self.h)));
56 self.right = Some(Box::new(BspNode::new(self.x + split, self.y, self.w - split, self.h)));
57 }
58
59 if let Some(ref mut l) = self.left { l.split(rng, min_size, depth + 1, max_depth); }
60 if let Some(ref mut r) = self.right { r.split(rng, min_size, depth + 1, max_depth); }
61 }
62
63 fn create_rooms(&mut self, rng: &mut Rng, padding: usize) {
64 if self.left.is_some() || self.right.is_some() {
65 if let Some(ref mut l) = self.left { l.create_rooms(rng, padding); }
66 if let Some(ref mut r) = self.right { r.create_rooms(rng, padding); }
67 } else {
68 let min_w = 3.min(self.w.saturating_sub(padding * 2));
69 let min_h = 3.min(self.h.saturating_sub(padding * 2));
70 if min_w < 3 || min_h < 3 { return; }
71
72 let max_w = self.w.saturating_sub(padding * 2);
73 let max_h = self.h.saturating_sub(padding * 2);
74 let w = rng.range_usize(min_w, max_w + 1);
75 let h = rng.range_usize(min_h, max_h + 1);
76 let x = self.x + padding + rng.range_usize(0, max_w - w + 1);
77 let y = self.y + padding + rng.range_usize(0, max_h - h + 1);
78 self.room = Some((x, y, w, h));
79 }
80 }
81
82 fn get_center(&self) -> Option<(usize, usize)> {
83 if let Some((x, y, w, h)) = self.room {
84 return Some((x + w / 2, y + h / 2));
85 }
86 self.left.as_ref().and_then(|n| n.get_center())
87 .or_else(|| self.right.as_ref().and_then(|n| n.get_center()))
88 }
89
90 fn carve(&self, grid: &mut Grid<Tile>) {
91 if let Some((x, y, w, h)) = self.room {
92 grid.fill_rect(x as i32, y as i32, w, h, Tile::Floor);
93 }
94 if let (Some(ref left), Some(ref right)) = (&self.left, &self.right) {
95 left.carve(grid);
96 right.carve(grid);
97 if let (Some((lx, ly)), Some((rx, ry))) = (left.get_center(), right.get_center()) {
98 for x in lx.min(rx)..=lx.max(rx) { grid.set(x as i32, ly as i32, Tile::Floor); }
99 for y in ly.min(ry)..=ly.max(ry) { grid.set(rx as i32, y as i32, Tile::Floor); }
100 }
101 }
102 }
103}
104
105impl Algorithm<Tile> for Bsp {
106 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
107 let mut rng = Rng::new(seed);
108 let mut root = BspNode::new(1, 1, grid.width() - 2, grid.height() - 2);
109 root.split(&mut rng, self.config.min_room_size, 0, self.config.max_depth);
110 root.create_rooms(&mut rng, self.config.room_padding);
111 root.carve(grid);
112 }
113
114 fn name(&self) -> &'static str { "BSP" }
115}