steel_core/worldgen/generator/
flat.rs1use glam::IVec3;
2use rustc_hash::FxHashMap;
3use steel_registry::biome::BiomeRef;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::template_pool::{TemplateData, TemplatePoolData};
6use steel_registry::{REGISTRY, RegistryExt, vanilla_biomes};
7use steel_utils::random::RandomSource;
8use steel_utils::{BlockStateId, ChunkPos, Identifier};
9
10use crate::chunk::Chunk;
11use crate::worldgen::generator::{
12 ChunkGenerator, GenerationChunk, TerrainPhase, xoroshiro_worldgen_region_random,
13};
14use crate::worldgen::region::WorldGenRegion;
15use crate::worldgen::structure::{StructureGenerator, create_structures};
16use steel_worldgen::noise::Beardifier;
17use steel_worldgen::structure::{ColumnBlock, StructureGenerationContext};
18
19pub struct FlatChunkGenerator {
24 pub layers: Vec<BlockStateId>,
26 biome_id: u16,
28 seed: i64,
30 sea_level: i32,
32 structure_generator: Option<StructureGenerator>,
34}
35
36impl FlatChunkGenerator {
37 pub(crate) const MIN_Y: i32 = 0;
38 pub(crate) const HEIGHT: i32 = 384;
39 pub(crate) const SEA_LEVEL: i32 = -63;
40
41 #[must_use]
43 pub fn new(bedrock: BlockStateId, dirt: BlockStateId, grass: BlockStateId) -> Self {
44 Self::new_layers(vec![bedrock, dirt, dirt, grass])
45 }
46
47 #[must_use]
49 pub fn new_layers(layers: Vec<BlockStateId>) -> Self {
50 Self::new_layers_with_structures(layers, 0, Self::SEA_LEVEL, None)
51 }
52
53 #[must_use]
55 pub(crate) fn new_layers_with_structures(
56 layers: Vec<BlockStateId>,
57 seed: i64,
58 sea_level: i32,
59 structure_generator: Option<StructureGenerator>,
60 ) -> Self {
61 let biome_id = REGISTRY
62 .biomes
63 .id_from_key(&Identifier::vanilla("plains".to_string()))
64 .unwrap_or(0) as u16;
65
66 Self {
67 layers,
68 biome_id,
69 seed,
70 sea_level,
71 structure_generator,
72 }
73 }
74}
75
76struct FlatGenerationContext<'a> {
77 seed: i64,
78 chunk_x: i32,
79 chunk_z: i32,
80 chunk_min_x: i32,
81 chunk_min_z: i32,
82 center_block_x: i32,
83 center_block_z: i32,
84 sea_level: i32,
85 min_y: i32,
86 height: i32,
87 layers: &'a [BlockStateId],
88 biome: BiomeRef,
89 template_pools: &'a FxHashMap<Identifier, TemplatePoolData>,
90 templates: &'a FxHashMap<Identifier, TemplateData>,
91 surface_y_cache: Option<i32>,
92}
93
94impl FlatGenerationContext<'_> {
95 fn state_at_y(&self, y: i32) -> Option<BlockStateId> {
96 let relative_y = y.checked_sub(self.min_y)? as usize;
97 self.layers.get(relative_y).copied()
98 }
99
100 fn is_opaque_at_y(&self, y: i32, ocean_floor: bool) -> bool {
101 let Some(state) = self.state_at_y(y) else {
102 return false;
103 };
104 if ocean_floor {
105 state.is_solid()
106 } else {
107 state.is_solid() || state.has_fluid()
108 }
109 }
110
111 fn base_height_flat(&self, ocean_floor: bool) -> i32 {
112 for y in (self.min_y..self.min_y + self.height).rev() {
113 if self.is_opaque_at_y(y, ocean_floor) {
114 return y + 1;
115 }
116 }
117 self.min_y
118 }
119}
120
121impl StructureGenerationContext for FlatGenerationContext<'_> {
122 fn seed(&self) -> i64 {
123 self.seed
124 }
125
126 fn chunk_x(&self) -> i32 {
127 self.chunk_x
128 }
129
130 fn chunk_z(&self) -> i32 {
131 self.chunk_z
132 }
133
134 fn chunk_min_x(&self) -> i32 {
135 self.chunk_min_x
136 }
137
138 fn chunk_min_z(&self) -> i32 {
139 self.chunk_min_z
140 }
141
142 fn center_block_x(&self) -> i32 {
143 self.center_block_x
144 }
145
146 fn center_block_z(&self) -> i32 {
147 self.center_block_z
148 }
149
150 fn sea_level(&self) -> i32 {
151 self.sea_level
152 }
153
154 fn min_y(&self) -> i32 {
155 self.min_y
156 }
157
158 fn height(&self) -> i32 {
159 self.height
160 }
161
162 fn template_pools(&self) -> &FxHashMap<Identifier, TemplatePoolData> {
163 self.template_pools
164 }
165
166 fn templates(&self) -> &FxHashMap<Identifier, TemplateData> {
167 self.templates
168 }
169
170 fn base_height(&mut self, _x: i32, _z: i32, ocean_floor: bool) -> i32 {
171 self.base_height_flat(ocean_floor)
172 }
173
174 fn base_height_full(&mut self, _x: i32, _z: i32, ocean_floor: bool) -> i32 {
175 self.base_height_flat(ocean_floor)
176 }
177
178 fn biome_at(&mut self, _block_x: i32, _block_y: i32, _block_z: i32) -> BiomeRef {
179 self.biome
180 }
181
182 fn column_state(&mut self, _x: i32, y: i32, _z: i32) -> ColumnBlock {
183 let Some(state) = self.state_at_y(y) else {
184 return ColumnBlock::Air;
185 };
186 if state.is_solid() {
187 ColumnBlock::Solid
188 } else if state.has_fluid() {
189 ColumnBlock::Fluid
190 } else {
191 ColumnBlock::Air
192 }
193 }
194
195 fn surface_y(&mut self) -> i32 {
196 if let Some(y) = self.surface_y_cache {
197 return y;
198 }
199 let y = self.base_height_flat(false) - 1;
200 self.surface_y_cache = Some(y);
201 y
202 }
203
204 fn terrain_surface_height(&self, _x: i32, _z: i32, ocean_floor: bool) -> i32 {
205 self.base_height_flat(ocean_floor)
206 }
207
208 fn terrain_is_opaque(&self, _x: i32, y: i32, _z: i32, ocean_floor: bool) -> bool {
209 self.is_opaque_at_y(y, ocean_floor)
210 }
211}
212
213impl ChunkGenerator for FlatChunkGenerator {
214 fn min_y(&self) -> i32 {
215 Self::MIN_Y
216 }
217
218 fn gen_depth(&self) -> i32 {
219 Self::HEIGHT
220 }
221
222 fn noise_biome(&self, _quart_x: i32, _quart_y: i32, _quart_z: i32) -> BiomeRef {
223 &vanilla_biomes::PLAINS
224 }
225
226 fn spawn_height(&self, min_y: i32, height: i32) -> i32 {
227 min_y + height.min(self.layers.len() as i32)
228 }
229
230 fn structure_generator(&self) -> Option<&StructureGenerator> {
231 self.structure_generator.as_ref()
232 }
233
234 fn create_structures(&self, chunk: &Chunk) {
235 let Some(structure_generator) = &self.structure_generator else {
236 return;
237 };
238
239 let pos = chunk.pos();
240 let chunk_x = pos.0.x;
241 let chunk_z = pos.0.y;
242 let chunk_min_x = chunk_x * 16;
243 let chunk_min_z = chunk_z * 16;
244 let mut ctx = FlatGenerationContext {
245 seed: self.seed,
246 chunk_x,
247 chunk_z,
248 chunk_min_x,
249 chunk_min_z,
250 center_block_x: chunk_min_x + 8,
251 center_block_z: chunk_min_z + 8,
252 sea_level: self.sea_level,
253 min_y: chunk.min_y(),
254 height: (chunk.sections().sections.len() * 16) as i32,
255 layers: &self.layers,
256 biome: &vanilla_biomes::PLAINS,
257 template_pools: structure_generator.template_pools(),
258 templates: structure_generator.templates(),
259 surface_y_cache: None,
260 };
261 create_structures(structure_generator, chunk, &mut ctx);
262 }
263
264 fn create_biomes(&self, chunk: &Chunk) {
265 let section_count = chunk.sections().sections.len();
266
267 for section_index in 0..section_count {
268 let section = &chunk.sections().sections[section_index];
269 let mut section_guard = section.write();
270
271 for local_quart_x in 0..4usize {
272 for local_quart_y in 0..4usize {
273 for local_quart_z in 0..4usize {
274 section_guard.biomes.set(
275 local_quart_x,
276 local_quart_y,
277 local_quart_z,
278 self.biome_id,
279 );
280 }
281 }
282 }
283 drop(section_guard);
284 }
285
286 chunk.mark_dirty();
287 }
288
289 fn fill_from_noise(
290 &self,
291 chunk: GenerationChunk<'_, TerrainPhase>,
292 _beardifier: Option<&Beardifier>,
293 ) {
294 let max_relative_y = chunk.section_count() * 16;
295
296 for x in 0..16 {
297 for z in 0..16 {
298 for (relative_y, block) in self.layers.iter().enumerate().take(max_relative_y) {
299 chunk.set_relative_block(x, relative_y, z, *block);
300 }
301 }
302 }
303 }
304
305 fn build_surface(
306 &self,
307 _chunk: GenerationChunk<'_, TerrainPhase>,
308 _neighbor_biomes: &dyn Fn(IVec3) -> u16,
309 ) {
310 }
311
312 fn apply_carvers(&self, _chunk: GenerationChunk<'_, TerrainPhase>) {}
313
314 fn create_worldgen_region_random(&self, world_seed: i64, center: ChunkPos) -> RandomSource {
315 xoroshiro_worldgen_region_random(world_seed, center)
316 }
317
318 fn apply_biome_decorations(&self, _region: &mut WorldGenRegion<'_>) {}
319}