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 {
13 min_room_size: 5,
14 max_depth: 4,
15 room_padding: 1,
16 }
17 }
18}
19
20pub struct Bsp {
21 config: BspConfig,
22}
23
24impl Bsp {
25 pub fn new(config: BspConfig) -> Self {
26 Self { config }
27 }
28}
29
30impl Default for Bsp {
31 fn default() -> Self {
32 Self::new(BspConfig::default())
33 }
34}
35
36struct BspNode {
37 x: usize,
38 y: usize,
39 w: usize,
40 h: usize,
41 left: Option<Box<BspNode>>,
42 right: Option<Box<BspNode>>,
43 room: Option<(usize, usize, usize, usize)>,
44}
45
46impl BspNode {
47 fn new(x: usize, y: usize, w: usize, h: usize) -> Self {
48 Self {
49 x,
50 y,
51 w,
52 h,
53 left: None,
54 right: None,
55 room: None,
56 }
57 }
58
59 fn split(&mut self, rng: &mut Rng, min_size: usize, depth: usize, max_depth: usize) {
60 if depth >= max_depth {
61 return;
62 }
63
64 let can_h = self.h >= min_size * 2;
65 let can_v = self.w >= min_size * 2;
66 if !can_h && !can_v {
67 return;
68 }
69
70 let split_h = if can_h && can_v {
71 rng.chance(0.5)
72 } else {
73 can_h
74 };
75
76 if split_h {
77 let split = rng.range_usize(min_size, self.h - min_size + 1);
78 self.left = Some(Box::new(BspNode::new(self.x, self.y, self.w, split)));
79 self.right = Some(Box::new(BspNode::new(
80 self.x,
81 self.y + split,
82 self.w,
83 self.h - split,
84 )));
85 } else {
86 let split = rng.range_usize(min_size, self.w - min_size + 1);
87 self.left = Some(Box::new(BspNode::new(self.x, self.y, split, self.h)));
88 self.right = Some(Box::new(BspNode::new(
89 self.x + split,
90 self.y,
91 self.w - split,
92 self.h,
93 )));
94 }
95
96 if let Some(ref mut l) = self.left {
97 l.split(rng, min_size, depth + 1, max_depth);
98 }
99 if let Some(ref mut r) = self.right {
100 r.split(rng, min_size, depth + 1, max_depth);
101 }
102 }
103
104 fn create_rooms(&mut self, rng: &mut Rng, padding: usize) {
105 if self.left.is_some() || self.right.is_some() {
106 if let Some(ref mut l) = self.left {
107 l.create_rooms(rng, padding);
108 }
109 if let Some(ref mut r) = self.right {
110 r.create_rooms(rng, padding);
111 }
112 } else {
113 let min_w = 3.min(self.w.saturating_sub(padding * 2));
114 let min_h = 3.min(self.h.saturating_sub(padding * 2));
115 if min_w < 3 || min_h < 3 {
116 return;
117 }
118
119 let max_w = self.w.saturating_sub(padding * 2);
120 let max_h = self.h.saturating_sub(padding * 2);
121 let w = rng.range_usize(min_w, max_w + 1);
122 let h = rng.range_usize(min_h, max_h + 1);
123 let x = self.x + padding + rng.range_usize(0, max_w - w + 1);
124 let y = self.y + padding + rng.range_usize(0, max_h - h + 1);
125 self.room = Some((x, y, w, h));
126 }
127 }
128
129 fn get_center(&self) -> Option<(usize, usize)> {
130 if let Some((x, y, w, h)) = self.room {
131 return Some((x + w / 2, y + h / 2));
132 }
133 self.left
134 .as_ref()
135 .and_then(|n| n.get_center())
136 .or_else(|| self.right.as_ref().and_then(|n| n.get_center()))
137 }
138
139 fn carve(&self, grid: &mut Grid<Tile>) {
140 if let Some((x, y, w, h)) = self.room {
141 grid.fill_rect(x as i32, y as i32, w, h, Tile::Floor);
142 }
143 if let (Some(ref left), Some(ref right)) = (&self.left, &self.right) {
144 left.carve(grid);
145 right.carve(grid);
146 if let (Some((lx, ly)), Some((rx, ry))) = (left.get_center(), right.get_center()) {
147 for x in lx.min(rx)..=lx.max(rx) {
148 grid.set(x as i32, ly as i32, Tile::Floor);
149 }
150 for y in ly.min(ry)..=ly.max(ry) {
151 grid.set(rx as i32, y as i32, Tile::Floor);
152 }
153 }
154 }
155 }
156}
157
158impl Algorithm<Tile> for Bsp {
159 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
160 let mut rng = Rng::new(seed);
161 let mut root = BspNode::new(1, 1, grid.width() - 2, grid.height() - 2);
162 root.split(
163 &mut rng,
164 self.config.min_room_size,
165 0,
166 self.config.max_depth,
167 );
168 root.create_rooms(&mut rng, self.config.room_padding);
169 root.carve(grid);
170 }
171
172 fn name(&self) -> &'static str {
173 "BSP"
174 }
175}