steel_core/worldgen/carver/
cave.rs1use std::f32::consts::{FRAC_PI_2, PI, TAU};
8
9use steel_math::trig;
10use steel_registry::carver::CaveCarverConfiguration;
11use steel_utils::random::{Random, legacy_random::LegacyRandom};
12use steel_utils::{BlockPos, ChunkPos};
13use steel_worldgen::density::DimensionNoises;
14
15use crate::worldgen::carver::{CarveRun, CarveSkipChecker, can_reach, horizontal_tunnel_radius};
16
17const CARVER_RANGE: i32 = 4;
19const MAX_TUNNEL_DISTANCE: i32 = (CARVER_RANGE * 2 - 1) * 16;
21
22#[derive(Debug, Clone, Copy)]
24struct TunnelState {
25 x: f64,
26 y: f64,
27 z: f64,
28 horizontal_rotation: f32,
30 vertical_rotation: f32,
32}
33
34#[derive(Debug, Clone, Copy)]
37struct TunnelParams {
38 tunnel_seed: i64,
39 horizontal_radius_multiplier: f64,
40 vertical_radius_multiplier: f64,
41 thickness: f32,
42 step: i32,
43 dist: i32,
44 y_scale: f64,
45}
46
47impl<N, F> CarveRun<'_, '_, N, F>
48where
49 N: DimensionNoises,
50 F: FnMut(BlockPos) -> u16,
51{
52 pub fn carve_cave(
59 &mut self,
60 config: &CaveCarverConfiguration,
61 source_pos: ChunkPos,
62 random: &mut LegacyRandom,
63 ) {
64 let cave_count = config.count.sample(random);
65
66 let source_min_x = source_pos.0.x * 16;
67 let source_min_z = source_pos.0.y * 16;
68
69 for _ in 0..cave_count {
70 let x = f64::from(source_min_x + random.next_i32_bounded(16));
71 let y = f64::from(config.base.y.sample(
72 random,
73 self.ctx.min_y,
74 self.ctx.gen_depth,
75 self.ctx.sea_level,
76 ));
77 let z = f64::from(source_min_z + random.next_i32_bounded(16));
78
79 let horizontal_radius_multiplier =
80 f64::from(config.horizontal_radius_multiplier.sample(random));
81 let vertical_radius_multiplier =
82 f64::from(config.vertical_radius_multiplier.sample(random));
83 let start_vertical_radius_multiplier =
84 f64::from(config.start_vertical_radius_multiplier.sample(random));
85 let floor_level = f64::from(config.floor_level.sample(random));
86
87 let skip_checker = move |xd: f64, yd: f64, zd: f64, _world_y: i32| {
92 yd <= floor_level || xd * xd + yd * yd + zd * zd >= 1.0
93 };
94
95 let mut tunnels = 1i32;
96 if random.next_i32_bounded(4) == 0 {
97 let y_scale = f64::from(config.room_vertical_radius_multiplier.sample(random));
98 let thickness = 1.0 + random.next_f32() * 6.0;
99 self.create_room(x, y, z, thickness, y_scale, &skip_checker);
100 tunnels += random.next_i32_bounded(4);
101 }
102
103 for _ in 0..tunnels {
104 let state = TunnelState {
105 x,
106 y,
107 z,
108 horizontal_rotation: random.next_f32() * TAU,
109 vertical_rotation: (random.next_f32() - 0.5) / 4.0,
110 };
111 let tunnel = TunnelParams {
112 tunnel_seed: 0, horizontal_radius_multiplier,
114 vertical_radius_multiplier,
115 thickness: get_thickness(config, random),
116 step: 0,
117 dist: MAX_TUNNEL_DISTANCE - random.next_i32_bounded(MAX_TUNNEL_DISTANCE / 4),
118 y_scale: start_vertical_radius_multiplier,
119 };
120 let tunnel = TunnelParams {
123 tunnel_seed: random.next_i64(),
124 ..tunnel
125 };
126 self.create_tunnel(state, tunnel, skip_checker);
127 }
128 }
129 }
130
131 fn create_room<S: CarveSkipChecker>(
134 &mut self,
135 x: f64,
136 y: f64,
137 z: f64,
138 thickness: f32,
139 y_scale: f64,
140 skip_checker: S,
141 ) {
142 let horizontal_radius = 1.5 + f64::from(trig::sin(FRAC_PI_2)) * f64::from(thickness);
147 let vertical_radius = horizontal_radius * y_scale;
148 self.carve_ellipsoid(
149 x + 1.0,
150 y,
151 z,
152 horizontal_radius,
153 vertical_radius,
154 skip_checker,
155 );
156 }
157
158 fn create_tunnel<S>(&mut self, mut state: TunnelState, tunnel: TunnelParams, skip_checker: S)
161 where
162 S: CarveSkipChecker + Copy,
163 {
164 let mut random = LegacyRandom::from_seed(tunnel.tunnel_seed as u64);
165 let split_point = random.next_i32_bounded(tunnel.dist / 2) + tunnel.dist / 4;
166 let steep = random.next_i32_bounded(6) == 0;
167 let mut y_rota: f32 = 0.0;
168 let mut x_rota: f32 = 0.0;
169
170 for current_step in tunnel.step..tunnel.dist {
171 let progress_arg = PI * current_step as f32 / tunnel.dist as f32;
176 let horizontal_radius = horizontal_tunnel_radius(progress_arg, tunnel.thickness);
177 let vertical_radius = horizontal_radius * tunnel.y_scale;
178 let cos_x = trig::cos(state.vertical_rotation);
179 state.x += f64::from(trig::cos(state.horizontal_rotation) * cos_x);
180 state.y += f64::from(trig::sin(state.vertical_rotation));
181 state.z += f64::from(trig::sin(state.horizontal_rotation) * cos_x);
182 state.vertical_rotation *= if steep { 0.92 } else { 0.7 };
183 state.vertical_rotation += x_rota * 0.1;
184 state.horizontal_rotation += y_rota * 0.1;
185 x_rota *= 0.9;
186 y_rota *= 0.75;
187 x_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 2.0;
188 y_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 4.0;
189
190 if current_step == split_point && tunnel.thickness > 1.0 {
191 let sub_seed_a = random.next_i64();
195 let sub_thickness_a = random.next_f32() * 0.5 + 0.5;
196 let sub_state_a = TunnelState {
197 horizontal_rotation: state.horizontal_rotation - FRAC_PI_2,
198 vertical_rotation: state.vertical_rotation / 3.0,
199 ..state
200 };
201 let sub_seed_b = random.next_i64();
202 let sub_thickness_b = random.next_f32() * 0.5 + 0.5;
203 let sub_state_b = TunnelState {
204 horizontal_rotation: state.horizontal_rotation + FRAC_PI_2,
205 vertical_rotation: state.vertical_rotation / 3.0,
206 ..state
207 };
208 let sub_tunnel_a = TunnelParams {
209 tunnel_seed: sub_seed_a,
210 thickness: sub_thickness_a,
211 step: current_step,
212 y_scale: 1.0,
213 ..tunnel
214 };
215 let sub_tunnel_b = TunnelParams {
216 tunnel_seed: sub_seed_b,
217 thickness: sub_thickness_b,
218 step: current_step,
219 y_scale: 1.0,
220 ..tunnel
221 };
222 self.create_tunnel(sub_state_a, sub_tunnel_a, skip_checker);
223 self.create_tunnel(sub_state_b, sub_tunnel_b, skip_checker);
224 return;
225 }
226
227 if random.next_i32_bounded(4) == 0 {
228 continue;
229 }
230
231 if !can_reach(
232 self.chunk_min_x,
233 self.chunk_min_z,
234 state.x,
235 state.z,
236 current_step,
237 tunnel.dist,
238 tunnel.thickness,
239 ) {
240 return;
241 }
242
243 self.carve_ellipsoid(
244 state.x,
245 state.y,
246 state.z,
247 horizontal_radius * tunnel.horizontal_radius_multiplier,
248 vertical_radius * tunnel.vertical_radius_multiplier,
249 skip_checker,
250 );
251 }
252 }
253}
254
255fn get_thickness(config: &CaveCarverConfiguration, random: &mut LegacyRandom) -> f32 {
257 let mut thickness = config.thickness.sample(random);
258 if config.weird_thickness_bias && random.next_i32_bounded(10) == 0 {
259 thickness *= random.next_f32() * random.next_f32() * 3.0 + 1.0;
260 }
261 thickness
262}