steel_core/worldgen/carver/
canyon.rs1use std::f32::consts::{PI, TAU};
8
9use steel_math::trig;
10use steel_registry::carver::CanyonCarverConfiguration;
11use steel_utils::random::{Random, legacy_random::LegacyRandom};
12use steel_utils::{BlockPos, ChunkPos};
13use steel_worldgen::density::DimensionNoises;
14
15use crate::worldgen::carver::{
16 CarveParams, CarveRun, CarverStyle, cached_replaceable_states, can_reach,
17 horizontal_tunnel_radius,
18};
19
20const CARVER_RANGE: i32 = 4;
23const MAX_TUNNEL_DISTANCE: i32 = (CARVER_RANGE * 2 - 1) * 16;
25
26#[derive(Debug, Clone, Copy)]
28struct CanyonState {
29 x: f64,
30 y: f64,
31 z: f64,
32 horizontal_rotation: f32,
34 vertical_rotation: f32,
36}
37
38#[derive(Debug, Clone, Copy)]
40struct CanyonTunnel {
41 tunnel_seed: i64,
42 thickness: f32,
43 distance: 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_canyon(
60 &mut self,
61 config: &CanyonCarverConfiguration,
62 source_pos: ChunkPos,
63 random: &mut LegacyRandom,
64 ) {
65 let source_min_x = source_pos.0.x * 16;
66 let source_min_z = source_pos.0.y * 16;
67
68 let lava_level_y = config
69 .base
70 .lava_level
71 .resolve_y(self.ctx.min_y, self.ctx.gen_depth);
72 let params = CarveParams {
73 replaceable_tag: &config.base.replaceable_tag,
74 replaceable_states: cached_replaceable_states(&config.base.replaceable_tag),
75 lava_level_y,
76 style: CarverStyle::Overworld,
77 };
78
79 let state = CanyonState {
80 x: f64::from(source_min_x + random.next_i32_bounded(16)),
81 y: f64::from(
82 config
83 .base
84 .y
85 .sample(random, self.ctx.min_y, self.ctx.gen_depth),
86 ),
87 z: f64::from(source_min_z + random.next_i32_bounded(16)),
88 horizontal_rotation: random.next_f32() * TAU,
89 vertical_rotation: config.vertical_rotation.sample(random),
90 };
91
92 let y_scale = f64::from(config.base.y_scale.sample(random));
94 let thickness = config.shape.thickness.sample(random);
95 let distance =
96 (MAX_TUNNEL_DISTANCE as f32 * config.shape.distance_factor.sample(random)) as i32;
97 let tunnel_seed = random.next_i64();
98
99 let tunnel = CanyonTunnel {
100 tunnel_seed,
101 thickness,
102 distance,
103 y_scale,
104 };
105
106 self.do_carve_canyon(¶ms, config, state, tunnel);
107 }
108
109 fn do_carve_canyon(
111 &mut self,
112 params: &CarveParams<'_>,
113 config: &CanyonCarverConfiguration,
114 mut state: CanyonState,
115 tunnel: CanyonTunnel,
116 ) {
117 let mut random = LegacyRandom::from_seed(tunnel.tunnel_seed as u64);
118 let width_factors = config
119 .shape
120 .init_width_factors(self.ctx.gen_depth, &mut random);
121 let mut y_rota: f32 = 0.0;
122 let mut x_rota: f32 = 0.0;
123
124 for current_step in 0..tunnel.distance {
125 let progress = PI * current_step as f32 / tunnel.distance as f32;
126 let mut horizontal_radius = horizontal_tunnel_radius(progress, tunnel.thickness);
127 let mut vertical_radius = horizontal_radius * tunnel.y_scale;
128 horizontal_radius *=
129 f64::from(config.shape.horizontal_radius_factor.sample(&mut random));
130 vertical_radius = config.shape.update_vertical_radius(
131 &mut random,
132 vertical_radius,
133 tunnel.distance as f32,
134 current_step as f32,
135 );
136
137 let xc = trig::cos(f64::from(state.vertical_rotation));
138 let xs = trig::sin(f64::from(state.vertical_rotation));
139 state.x += f64::from(trig::cos(f64::from(state.horizontal_rotation)) * xc);
140 state.y += f64::from(xs);
141 state.z += f64::from(trig::sin(f64::from(state.horizontal_rotation)) * xc);
142 state.vertical_rotation *= 0.7;
143 state.vertical_rotation += x_rota * 0.05;
144 state.horizontal_rotation += y_rota * 0.05;
145 x_rota *= 0.8;
146 y_rota *= 0.5;
147 x_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 2.0;
148 y_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 4.0;
149
150 if random.next_i32_bounded(4) == 0 {
151 continue;
152 }
153
154 if !can_reach(
155 self.chunk_min_x,
156 self.chunk_min_z,
157 state.x,
158 state.z,
159 current_step,
160 tunnel.distance,
161 tunnel.thickness,
162 ) {
163 return;
164 }
165
166 let min_y = self.ctx.min_y;
167 let skip_checker = |xd: f64, yd: f64, zd: f64, world_y: i32| {
168 should_skip_canyon(&width_factors, min_y, xd, yd, zd, world_y)
169 };
170
171 self.carve_ellipsoid(
172 params,
173 state.x,
174 state.y,
175 state.z,
176 horizontal_radius,
177 vertical_radius,
178 skip_checker,
179 );
180 }
181 }
182}
183
184fn should_skip_canyon(
189 width_factors: &[f32],
190 min_y: i32,
191 xd: f64,
192 yd: f64,
193 zd: f64,
194 world_y: i32,
195) -> bool {
196 let y_index = (world_y - min_y - 1) as usize;
197 let factor = width_factors[y_index];
198 (xd * xd + zd * zd) * f64::from(factor) + yd * yd / 6.0 >= 1.0
199}