steel_core/worldgen/carver/
cave.rs1use std::f32::consts::{FRAC_PI_2, PI, TAU};
10
11use steel_math::trig;
12use steel_registry::carver::CaveCarverConfiguration;
13use steel_utils::random::{Random, legacy_random::LegacyRandom};
14use steel_utils::{BlockPos, ChunkPos};
15use steel_worldgen::density::DimensionNoises;
16
17use crate::worldgen::carver::{
18 CarveParams, CarveRun, CarveSkipChecker, CarverStyle, cached_replaceable_states, can_reach,
19 horizontal_tunnel_radius,
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum CaveKind {
25 Overworld,
27 Nether,
29}
30
31impl CaveKind {
32 const fn cave_bound(self) -> i32 {
35 match self {
36 Self::Overworld => 15,
37 Self::Nether => 10,
38 }
39 }
40
41 const fn y_scale(self) -> f64 {
44 match self {
45 Self::Overworld => 1.0,
46 Self::Nether => 5.0,
47 }
48 }
49
50 const fn style(self) -> CarverStyle {
51 match self {
52 Self::Overworld => CarverStyle::Overworld,
53 Self::Nether => CarverStyle::Nether,
54 }
55 }
56
57 fn thickness(self, random: &mut impl Random) -> f32 {
60 match self {
61 Self::Overworld => {
62 let mut thickness = random.next_f32() * 2.0 + random.next_f32();
66 if random.next_i32_bounded(10) == 0 {
67 thickness *= random.next_f32() * random.next_f32() * 3.0 + 1.0;
68 }
69 thickness
70 }
71 Self::Nether => {
72 (random.next_f32() * 2.0 + random.next_f32()) * 2.0
75 }
76 }
77 }
78}
79
80const CARVER_RANGE: i32 = 4;
82const MAX_TUNNEL_DISTANCE: i32 = (CARVER_RANGE * 2 - 1) * 16;
84
85#[derive(Debug, Clone, Copy)]
87struct TunnelState {
88 x: f64,
89 y: f64,
90 z: f64,
91 horizontal_rotation: f32,
93 vertical_rotation: f32,
95}
96
97#[derive(Debug, Clone, Copy)]
100struct TunnelParams {
101 tunnel_seed: i64,
102 horizontal_radius_multiplier: f64,
103 vertical_radius_multiplier: f64,
104 thickness: f32,
105 step: i32,
106 dist: i32,
107 y_scale: f64,
108}
109
110impl<N, F> CarveRun<'_, '_, N, F>
111where
112 N: DimensionNoises,
113 F: FnMut(BlockPos) -> u16,
114{
115 pub fn carve_cave(
123 &mut self,
124 config: &CaveCarverConfiguration,
125 kind: CaveKind,
126 source_pos: ChunkPos,
127 random: &mut LegacyRandom,
128 ) {
129 let bound = kind.cave_bound();
133 let inner = random.next_i32_bounded(bound);
134 let mid = random.next_i32_bounded(inner + 1);
135 let cave_count = random.next_i32_bounded(mid + 1);
136
137 let source_min_x = source_pos.0.x * 16;
138 let source_min_z = source_pos.0.y * 16;
139
140 let lava_level_y = config
141 .base
142 .lava_level
143 .resolve_y(self.ctx.min_y, self.ctx.gen_depth);
144 let params = CarveParams {
145 replaceable_tag: &config.base.replaceable_tag,
146 replaceable_states: cached_replaceable_states(&config.base.replaceable_tag),
147 lava_level_y,
148 style: kind.style(),
149 };
150
151 for _ in 0..cave_count {
152 let x = f64::from(source_min_x + random.next_i32_bounded(16));
153 let y = f64::from(
154 config
155 .base
156 .y
157 .sample(random, self.ctx.min_y, self.ctx.gen_depth),
158 );
159 let z = f64::from(source_min_z + random.next_i32_bounded(16));
160
161 let horizontal_radius_multiplier =
162 f64::from(config.horizontal_radius_multiplier.sample(random));
163 let vertical_radius_multiplier =
164 f64::from(config.vertical_radius_multiplier.sample(random));
165 let floor_level = f64::from(config.floor_level.sample(random));
166
167 let skip_checker = move |xd: f64, yd: f64, zd: f64, _world_y: i32| {
172 yd <= floor_level || xd * xd + yd * yd + zd * zd >= 1.0
173 };
174
175 let mut tunnels = 1i32;
176 if random.next_i32_bounded(4) == 0 {
177 let y_scale = f64::from(config.base.y_scale.sample(random));
178 let thickness = 1.0 + random.next_f32() * 6.0;
179 self.create_room(¶ms, x, y, z, thickness, y_scale, &skip_checker);
180 tunnels += random.next_i32_bounded(4);
181 }
182
183 for _ in 0..tunnels {
184 let state = TunnelState {
185 x,
186 y,
187 z,
188 horizontal_rotation: random.next_f32() * TAU,
189 vertical_rotation: (random.next_f32() - 0.5) / 4.0,
190 };
191 let tunnel = TunnelParams {
192 tunnel_seed: 0, horizontal_radius_multiplier,
194 vertical_radius_multiplier,
195 thickness: kind.thickness(random),
196 step: 0,
197 dist: MAX_TUNNEL_DISTANCE - random.next_i32_bounded(MAX_TUNNEL_DISTANCE / 4),
198 y_scale: kind.y_scale(),
199 };
200 let tunnel = TunnelParams {
203 tunnel_seed: random.next_i64(),
204 ..tunnel
205 };
206 self.create_tunnel(¶ms, state, tunnel, skip_checker);
207 }
208 }
209 }
210
211 #[expect(
214 clippy::too_many_arguments,
215 reason = "mirrors vanilla CaveWorldCarver.createRoom"
216 )]
217 fn create_room<S: CarveSkipChecker>(
218 &mut self,
219 params: &CarveParams<'_>,
220 x: f64,
221 y: f64,
222 z: f64,
223 thickness: f32,
224 y_scale: f64,
225 skip_checker: S,
226 ) {
227 let horizontal_radius =
232 1.5 + f64::from(trig::sin(f64::from(FRAC_PI_2))) * f64::from(thickness);
233 let vertical_radius = horizontal_radius * y_scale;
234 self.carve_ellipsoid(
235 params,
236 x + 1.0,
237 y,
238 z,
239 horizontal_radius,
240 vertical_radius,
241 skip_checker,
242 );
243 }
244
245 fn create_tunnel<S>(
248 &mut self,
249 params: &CarveParams<'_>,
250 mut state: TunnelState,
251 tunnel: TunnelParams,
252 skip_checker: S,
253 ) where
254 S: CarveSkipChecker + Copy,
255 {
256 let mut random = LegacyRandom::from_seed(tunnel.tunnel_seed as u64);
257 let split_point = random.next_i32_bounded(tunnel.dist / 2) + tunnel.dist / 4;
258 let steep = random.next_i32_bounded(6) == 0;
259 let mut y_rota: f32 = 0.0;
260 let mut x_rota: f32 = 0.0;
261
262 for current_step in tunnel.step..tunnel.dist {
263 let progress_arg = PI * current_step as f32 / tunnel.dist as f32;
268 let horizontal_radius = horizontal_tunnel_radius(progress_arg, tunnel.thickness);
269 let vertical_radius = horizontal_radius * tunnel.y_scale;
270 let cos_x = trig::cos(f64::from(state.vertical_rotation));
271 state.x += f64::from(trig::cos(f64::from(state.horizontal_rotation)) * cos_x);
272 state.y += f64::from(trig::sin(f64::from(state.vertical_rotation)));
273 state.z += f64::from(trig::sin(f64::from(state.horizontal_rotation)) * cos_x);
274 state.vertical_rotation *= if steep { 0.92 } else { 0.7 };
275 state.vertical_rotation += x_rota * 0.1;
276 state.horizontal_rotation += y_rota * 0.1;
277 x_rota *= 0.9;
278 y_rota *= 0.75;
279 x_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 2.0;
280 y_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 4.0;
281
282 if current_step == split_point && tunnel.thickness > 1.0 {
283 let sub_seed_a = random.next_i64();
287 let sub_thickness_a = random.next_f32() * 0.5 + 0.5;
288 let sub_state_a = TunnelState {
289 horizontal_rotation: state.horizontal_rotation - FRAC_PI_2,
290 vertical_rotation: state.vertical_rotation / 3.0,
291 ..state
292 };
293 let sub_seed_b = random.next_i64();
294 let sub_thickness_b = random.next_f32() * 0.5 + 0.5;
295 let sub_state_b = TunnelState {
296 horizontal_rotation: state.horizontal_rotation + FRAC_PI_2,
297 vertical_rotation: state.vertical_rotation / 3.0,
298 ..state
299 };
300 let sub_tunnel_a = TunnelParams {
301 tunnel_seed: sub_seed_a,
302 thickness: sub_thickness_a,
303 step: current_step,
304 y_scale: 1.0,
305 ..tunnel
306 };
307 let sub_tunnel_b = TunnelParams {
308 tunnel_seed: sub_seed_b,
309 thickness: sub_thickness_b,
310 step: current_step,
311 y_scale: 1.0,
312 ..tunnel
313 };
314 self.create_tunnel(params, sub_state_a, sub_tunnel_a, skip_checker);
315 self.create_tunnel(params, sub_state_b, sub_tunnel_b, skip_checker);
316 return;
317 }
318
319 if random.next_i32_bounded(4) == 0 {
320 continue;
321 }
322
323 if !can_reach(
324 self.chunk_min_x,
325 self.chunk_min_z,
326 state.x,
327 state.z,
328 current_step,
329 tunnel.dist,
330 tunnel.thickness,
331 ) {
332 return;
333 }
334
335 self.carve_ellipsoid(
336 params,
337 state.x,
338 state.y,
339 state.z,
340 horizontal_radius * tunnel.horizontal_radius_multiplier,
341 vertical_radius * tunnel.vertical_radius_multiplier,
342 skip_checker,
343 );
344 }
345 }
346}