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