1use crate::density::traits::ColumnCache;
2use crate::density::traits::NoiseSettings;
3use crate::{
4 density::DimensionNoises,
5 noise::{Aquifer, AquiferResult, preliminary_surface_level},
6};
7use steel_math::lerp;
8
9pub(crate) fn column_base_height<N: DimensionNoises>(
16 cache: &mut N::ColumnCache,
17 noises: &N,
18 aquifer: &mut Aquifer<N>,
19 x: i32,
20 z: i32,
21 ocean_floor: bool,
22) -> i32 {
23 let estimate = preliminary_surface_level::<N>(noises, cache, x, z);
24 let max_y = (estimate + 16).min(N::Settings::MIN_Y + N::Settings::HEIGHT - 1);
25 iterate_noise_column_capped::<N>(cache, noises, aquifer, x, z, max_y, ocean_floor)
26}
27pub(crate) fn column_interpolated_density<N: DimensionNoises>(
29 cache: &mut N::ColumnCache,
30 noises: &N,
31 x: i32,
32 y: i32,
33 z: i32,
34 cell_w: i32,
35 cell_h: i32,
36) -> f64 {
37 interpolated_density::<N>(cache, noises, x, y, z, cell_w, cell_h)
38}
39#[expect(
45 clippy::too_many_lines,
46 reason = "keeps the vanilla density interpolation flow in one readable pass"
47)]
48pub(crate) fn find_solid_block_below_air<N: DimensionNoises>(
49 cache: &mut N::ColumnCache,
50 noises: &N,
51 aquifer: &mut Aquifer<N>,
52 block_x: i32,
53 block_z: i32,
54 start_y: i32,
55 min_solid_y: i32,
56) -> Option<i32> {
57 const MAX_INTERP: usize = 16;
58
59 if start_y <= min_solid_y {
60 return None;
61 }
62
63 let cell_w = N::Settings::CELL_WIDTH;
64 let cell_h = N::Settings::CELL_HEIGHT;
65 let min_y = N::Settings::MIN_Y;
66 let height = N::Settings::HEIGHT;
67 let cell_min_y = min_y.div_euclid(cell_h);
68 let cell_count_y = height.div_euclid(cell_h);
69
70 let cell_x = block_x.div_euclid(cell_w);
71 let cell_z = block_z.div_euclid(cell_w);
72 let factor_x = f64::from(block_x.rem_euclid(cell_w)) / f64::from(cell_w);
73 let factor_z = f64::from(block_z.rem_euclid(cell_w)) / f64::from(cell_w);
74 let x0 = cell_x * cell_w;
75 let x1 = x0 + cell_w;
76 let z0 = cell_z * cell_w;
77 let z1 = z0 + cell_w;
78
79 let interp_count = N::interpolated_count();
80
81 let mut c000 = [0.0f64; MAX_INTERP];
82 let mut c100 = [0.0f64; MAX_INTERP];
83 let mut c010 = [0.0f64; MAX_INTERP];
84 let mut c110 = [0.0f64; MAX_INTERP];
85 let mut c001 = [0.0f64; MAX_INTERP];
86 let mut c101 = [0.0f64; MAX_INTERP];
87 let mut c011 = [0.0f64; MAX_INTERP];
88 let mut c111 = [0.0f64; MAX_INTERP];
89 let mut interpolated = [0.0f64; MAX_INTERP];
90
91 macro_rules! fill {
92 ($out:expr, $ex:expr, $ey:expr, $ez:expr, $blended:expr) => {{
93 cache.ensure($ex, $ez, noises);
94 noises.fill_cell_corner_densities(
95 &mut *cache,
96 $ex,
97 $ey,
98 $ez,
99 $blended,
100 &mut $out[..interp_count],
101 );
102 }};
103 }
104
105 let max_cell_y_idx = {
106 let raw = start_y.div_euclid(cell_h) - cell_min_y;
107 raw.clamp(0, cell_count_y - 1)
108 };
109 let min_cell_y_idx = {
110 let raw = min_solid_y.div_euclid(cell_h) - cell_min_y;
111 raw.clamp(0, cell_count_y - 1)
112 };
113
114 let mut above_is_air = false;
115 let mut have_above = false;
116 let mut blended_scratch = [0.0_f64; 2];
117
118 for cell_y_idx in (min_cell_y_idx..=max_cell_y_idx).rev() {
119 let y0 = (cell_min_y + cell_y_idx) * cell_h;
120 let y1 = y0 + cell_h;
121 let ys = [y0, y1];
122
123 noises.compute_noise_column(x0, &ys, z0, &mut blended_scratch);
124 let b000 = blended_scratch[0];
125 let b010 = blended_scratch[1];
126 noises.compute_noise_column(x1, &ys, z0, &mut blended_scratch);
127 let b100 = blended_scratch[0];
128 let b110 = blended_scratch[1];
129 noises.compute_noise_column(x0, &ys, z1, &mut blended_scratch);
130 let b001 = blended_scratch[0];
131 let b011 = blended_scratch[1];
132 noises.compute_noise_column(x1, &ys, z1, &mut blended_scratch);
133 let b101 = blended_scratch[0];
134 let b111 = blended_scratch[1];
135
136 fill!(c000, x0, y0, z0, b000);
137 fill!(c100, x1, y0, z0, b100);
138 fill!(c010, x0, y1, z0, b010);
139 fill!(c110, x1, y1, z0, b110);
140 fill!(c001, x0, y0, z1, b001);
141 fill!(c101, x1, y0, z1, b101);
142 fill!(c011, x0, y1, z1, b011);
143 fill!(c111, x1, y1, z1, b111);
144
145 let top_y_in_cell = if cell_y_idx == max_cell_y_idx {
146 (start_y - y0).clamp(0, cell_h - 1)
147 } else {
148 cell_h - 1
149 };
150 let bottom_y_in_cell = if cell_y_idx == min_cell_y_idx {
151 (min_solid_y - y0).clamp(0, cell_h - 1)
152 } else {
153 0
154 };
155
156 for y_in_cell in (bottom_y_in_cell..=top_y_in_cell).rev() {
157 let pos_y = y0 + y_in_cell;
158 let factor_y = f64::from(y_in_cell) / f64::from(cell_h);
159
160 for ch in 0..interp_count {
161 let d00 = lerp(factor_y, c000[ch], c010[ch]);
162 let d10 = lerp(factor_y, c100[ch], c110[ch]);
163 let d01 = lerp(factor_y, c001[ch], c011[ch]);
164 let d11 = lerp(factor_y, c101[ch], c111[ch]);
165 let d0 = lerp(factor_x, d00, d10);
166 let d1 = lerp(factor_x, d01, d11);
167 interpolated[ch] = lerp(factor_z, d0, d1);
168 }
169
170 let density = noises.combine_interpolated(
171 &mut *cache,
172 &interpolated[..interp_count],
173 0,
174 pos_y,
175 0,
176 );
177 let state = aquifer.compute_substance(noises, block_x, pos_y, block_z, density);
178 let is_air = matches!(state, AquiferResult::Air);
179 let is_solid = matches!(state, AquiferResult::Solid);
180
181 if have_above && above_is_air && is_solid {
182 return Some(pos_y);
183 }
184
185 above_is_air = is_air;
186 have_above = true;
187 }
188 }
189
190 None
191}
192pub(crate) fn iterate_noise_column_with_aquifer<N: DimensionNoises>(
199 cache: &mut N::ColumnCache,
200 noises: &N,
201 aquifer: &mut Aquifer<N>,
202 block_x: i32,
203 block_z: i32,
204 ocean_floor: bool,
205) -> i32 {
206 let max_y = N::Settings::MIN_Y + N::Settings::HEIGHT - 1;
207 iterate_noise_column_capped::<N>(cache, noises, aquifer, block_x, block_z, max_y, ocean_floor)
208}
209
210#[expect(
215 clippy::too_many_lines,
216 reason = "inlines 8-corner density buffers + interpolation to match vanilla's iterateNoiseColumn fast path"
217)]
218fn iterate_noise_column_capped<N: DimensionNoises>(
219 cache: &mut N::ColumnCache,
220 noises: &N,
221 aquifer: &mut Aquifer<N>,
222 block_x: i32,
223 block_z: i32,
224 max_y_inclusive: i32,
225 ocean_floor: bool,
226) -> i32 {
227 const MAX_INTERP: usize = 16;
229
230 let cell_w = N::Settings::CELL_WIDTH;
231 let cell_h = N::Settings::CELL_HEIGHT;
232 let min_y = N::Settings::MIN_Y;
233 let height = N::Settings::HEIGHT;
234 let cell_min_y = min_y.div_euclid(cell_h);
235 let cell_count_y = height.div_euclid(cell_h);
236
237 let cell_x = block_x.div_euclid(cell_w);
238 let cell_z = block_z.div_euclid(cell_w);
239 let factor_x = f64::from(block_x.rem_euclid(cell_w)) / f64::from(cell_w);
240 let factor_z = f64::from(block_z.rem_euclid(cell_w)) / f64::from(cell_w);
241 let x0 = cell_x * cell_w;
242 let x1 = x0 + cell_w;
243 let z0 = cell_z * cell_w;
244 let z1 = z0 + cell_w;
245
246 let interp_count = N::interpolated_count();
247
248 let mut c000 = [0.0f64; MAX_INTERP];
249 let mut c100 = [0.0f64; MAX_INTERP];
250 let mut c010 = [0.0f64; MAX_INTERP];
251 let mut c110 = [0.0f64; MAX_INTERP];
252 let mut c001 = [0.0f64; MAX_INTERP];
253 let mut c101 = [0.0f64; MAX_INTERP];
254 let mut c011 = [0.0f64; MAX_INTERP];
255 let mut c111 = [0.0f64; MAX_INTERP];
256 let mut interpolated = [0.0f64; MAX_INTERP];
257
258 macro_rules! fill {
259 ($out:expr, $ex:expr, $ey:expr, $ez:expr, $blended:expr) => {{
260 cache.ensure($ex, $ez, noises);
261 noises.fill_cell_corner_densities(
262 &mut *cache,
263 $ex,
264 $ey,
265 $ez,
266 $blended,
267 &mut $out[..interp_count],
268 );
269 }};
270 }
271
272 let max_cell_y_idx = {
274 let raw = max_y_inclusive.div_euclid(cell_h) - cell_min_y;
275 raw.clamp(0, cell_count_y - 1)
276 };
277 let top_cell_top_y_in_cell =
279 (max_y_inclusive - (cell_min_y + max_cell_y_idx) * cell_h).clamp(0, cell_h - 1);
280
281 let mut blended_scratch = [0.0_f64; 2];
283 for cell_y_idx in (0..=max_cell_y_idx).rev() {
284 let y0 = (cell_min_y + cell_y_idx) * cell_h;
285 let y1 = y0 + cell_h;
286 let ys = [y0, y1];
287
288 noises.compute_noise_column(x0, &ys, z0, &mut blended_scratch);
291 let b000 = blended_scratch[0];
292 let b010 = blended_scratch[1];
293 noises.compute_noise_column(x1, &ys, z0, &mut blended_scratch);
294 let b100 = blended_scratch[0];
295 let b110 = blended_scratch[1];
296 noises.compute_noise_column(x0, &ys, z1, &mut blended_scratch);
297 let b001 = blended_scratch[0];
298 let b011 = blended_scratch[1];
299 noises.compute_noise_column(x1, &ys, z1, &mut blended_scratch);
300 let b101 = blended_scratch[0];
301 let b111 = blended_scratch[1];
302
303 fill!(c000, x0, y0, z0, b000);
305 fill!(c100, x1, y0, z0, b100);
306 fill!(c010, x0, y1, z0, b010);
307 fill!(c110, x1, y1, z0, b110);
308 fill!(c001, x0, y0, z1, b001);
309 fill!(c101, x1, y0, z1, b101);
310 fill!(c011, x0, y1, z1, b011);
311 fill!(c111, x1, y1, z1, b111);
312
313 let top_y_in_cell = if cell_y_idx == max_cell_y_idx {
316 top_cell_top_y_in_cell
317 } else {
318 cell_h - 1
319 };
320
321 for y_in_cell in (0..=top_y_in_cell).rev() {
323 let pos_y = (cell_min_y + cell_y_idx) * cell_h + y_in_cell;
324 let factor_y = f64::from(y_in_cell) / f64::from(cell_h);
325
326 for ch in 0..interp_count {
328 let d00 = lerp(factor_y, c000[ch], c010[ch]);
329 let d10 = lerp(factor_y, c100[ch], c110[ch]);
330 let d01 = lerp(factor_y, c001[ch], c011[ch]);
331 let d11 = lerp(factor_y, c101[ch], c111[ch]);
332 let d0 = lerp(factor_x, d00, d10);
333 let d1 = lerp(factor_x, d01, d11);
334 interpolated[ch] = lerp(factor_z, d0, d1);
335 }
336
337 let density = noises.combine_interpolated(
339 &mut *cache,
340 &interpolated[..interp_count],
341 0,
342 pos_y,
343 0,
344 );
345
346 let opaque = match aquifer.compute_substance(noises, block_x, pos_y, block_z, density) {
348 AquiferResult::Solid => true,
349 AquiferResult::Fluid(_) => !ocean_floor,
350 AquiferResult::Air => false,
351 };
352
353 if opaque {
354 return pos_y + 1;
355 }
356 }
357 }
358 min_y
359}
360
361fn interpolated_density<N: DimensionNoises>(
365 cache: &mut N::ColumnCache,
366 noises: &N,
367 x: i32,
368 y: i32,
369 z: i32,
370 cell_w: i32,
371 cell_h: i32,
372) -> f64 {
373 const MAX_INTERP: usize = 16;
374
375 let cx = x.div_euclid(cell_w);
376 let cy = y.div_euclid(cell_h);
377 let cz = z.div_euclid(cell_w);
378 let fx = f64::from(x.rem_euclid(cell_w)) / f64::from(cell_w);
379 let fy = f64::from(y.rem_euclid(cell_h)) / f64::from(cell_h);
380 let fz = f64::from(z.rem_euclid(cell_w)) / f64::from(cell_w);
381
382 let x0 = cx * cell_w;
383 let x1 = x0 + cell_w;
384 let y0 = cy * cell_h;
385 let y1 = y0 + cell_h;
386 let z0 = cz * cell_w;
387 let z1 = z0 + cell_w;
388
389 let interp_count = N::interpolated_count();
390
391 let mut c000 = [0.0f64; MAX_INTERP];
392 let mut c100 = [0.0f64; MAX_INTERP];
393 let mut c010 = [0.0f64; MAX_INTERP];
394 let mut c110 = [0.0f64; MAX_INTERP];
395 let mut c001 = [0.0f64; MAX_INTERP];
396 let mut c101 = [0.0f64; MAX_INTERP];
397 let mut c011 = [0.0f64; MAX_INTERP];
398 let mut c111 = [0.0f64; MAX_INTERP];
399 let mut interpolated = [0.0f64; MAX_INTERP];
400
401 macro_rules! fill {
402 ($out:expr, $ex:expr, $ey:expr, $ez:expr, $blended:expr) => {{
403 cache.ensure($ex, $ez, noises);
404 noises.fill_cell_corner_densities(
405 &mut *cache,
406 $ex,
407 $ey,
408 $ez,
409 $blended,
410 &mut $out[..interp_count],
411 );
412 }};
413 }
414
415 let ys = [y0, y1];
417 let mut blended_scratch = [0.0_f64; 2];
418 noises.compute_noise_column(x0, &ys, z0, &mut blended_scratch);
419 let (b000, b010) = (blended_scratch[0], blended_scratch[1]);
420 noises.compute_noise_column(x1, &ys, z0, &mut blended_scratch);
421 let (b100, b110) = (blended_scratch[0], blended_scratch[1]);
422 noises.compute_noise_column(x0, &ys, z1, &mut blended_scratch);
423 let (b001, b011) = (blended_scratch[0], blended_scratch[1]);
424 noises.compute_noise_column(x1, &ys, z1, &mut blended_scratch);
425 let (b101, b111) = (blended_scratch[0], blended_scratch[1]);
426
427 fill!(c000, x0, y0, z0, b000);
428 fill!(c100, x1, y0, z0, b100);
429 fill!(c010, x0, y1, z0, b010);
430 fill!(c110, x1, y1, z0, b110);
431 fill!(c001, x0, y0, z1, b001);
432 fill!(c101, x1, y0, z1, b101);
433 fill!(c011, x0, y1, z1, b011);
434 fill!(c111, x1, y1, z1, b111);
435
436 for ch in 0..interp_count {
437 let d00 = lerp(fy, c000[ch], c010[ch]);
438 let d10 = lerp(fy, c100[ch], c110[ch]);
439 let d01 = lerp(fy, c001[ch], c011[ch]);
440 let d11 = lerp(fy, c101[ch], c111[ch]);
441 let d0 = lerp(fx, d00, d10);
442 let d1 = lerp(fx, d01, d11);
443 interpolated[ch] = lerp(fz, d0, d1);
444 }
445
446 noises.combine_interpolated(&mut *cache, &interpolated[..interp_count], 0, y, 0)
447}