1use crate::density::traits::ColumnCache;
2use crate::density::traits::NoiseSettings;
3use crate::{
4 density::DimensionNoises,
5 noise::{Aquifer, AquiferResult, preliminary_surface_level},
6};
7
8#[inline]
9#[expect(
10 clippy::similar_names,
11 reason = "the names mirror Vanilla's interpolation corners"
12)]
13#[expect(
14 clippy::too_many_arguments,
15 reason = "the scalar interpolation helper mirrors Vanilla's eight corners"
16)]
17fn interpolate_cell_value(
18 factor_x: f32,
19 factor_z: f32,
20 y_in_cell: i32,
21 cell_height: i32,
22 v000: f32,
23 v100: f32,
24 v010: f32,
25 v110: f32,
26 v001: f32,
27 v101: f32,
28 v011: f32,
29 v111: f32,
30) -> f32 {
31 let v00 = v000 + factor_z * (v001 - v000);
32 let v01 = v010 + factor_z * (v011 - v010);
33 let v10 = v100 + factor_z * (v101 - v100);
34 let v11 = v110 + factor_z * (v111 - v110);
35 let v0 = v00 + factor_x * (v10 - v00);
36 let v1 = v01 + factor_x * (v11 - v01);
37 let step = (v1 - v0) * (1.0_f32 / cell_height as f32);
38 let mut value = v0;
39 for _ in 0..y_in_cell {
40 value += step;
41 }
42 value
43}
44
45pub(crate) fn column_base_height<N: DimensionNoises>(
52 cache: &mut N::ColumnCache,
53 noises: &N,
54 aquifer: &mut Aquifer<N>,
55 x: i32,
56 z: i32,
57 ocean_floor: bool,
58) -> i32 {
59 let estimate = preliminary_surface_level::<N>(noises, cache, x, z);
60 let max_y = (estimate + 16).min(N::Settings::MIN_Y + N::Settings::HEIGHT - 1);
61 iterate_noise_column_capped::<N>(cache, noises, aquifer, x, z, max_y, ocean_floor)
62}
63pub(crate) fn column_interpolated_density<N: DimensionNoises>(
65 cache: &mut N::ColumnCache,
66 noises: &N,
67 x: i32,
68 y: i32,
69 z: i32,
70 cell_w: i32,
71 cell_h: i32,
72) -> f32 {
73 interpolated_density::<N>(cache, noises, x, y, z, cell_w, cell_h)
74}
75#[expect(
81 clippy::too_many_lines,
82 reason = "keeps the vanilla density interpolation flow in one readable pass"
83)]
84pub(crate) fn find_solid_block_below_air<N: DimensionNoises>(
85 cache: &mut N::ColumnCache,
86 noises: &N,
87 aquifer: &mut Aquifer<N>,
88 block_x: i32,
89 block_z: i32,
90 start_y: i32,
91 min_solid_y: i32,
92) -> Option<i32> {
93 const MAX_INTERP: usize = 16;
94
95 if start_y <= min_solid_y {
96 return None;
97 }
98
99 let cell_w = N::Settings::CELL_WIDTH;
100 let cell_h = N::Settings::CELL_HEIGHT;
101 let min_y = N::Settings::MIN_Y;
102 let height = N::Settings::HEIGHT;
103 let cell_min_y = min_y.div_euclid(cell_h);
104 let cell_count_y = height.div_euclid(cell_h);
105
106 let cell_x = block_x.div_euclid(cell_w);
107 let cell_z = block_z.div_euclid(cell_w);
108 let factor_x = block_x.rem_euclid(cell_w) as f32 / cell_w as f32;
109 let factor_z = block_z.rem_euclid(cell_w) as f32 / cell_w as f32;
110 let x0 = cell_x * cell_w;
111 let x1 = x0 + cell_w;
112 let z0 = cell_z * cell_w;
113 let z1 = z0 + cell_w;
114
115 let interp_count = N::interpolated_count();
116
117 let mut c000 = [0.0_f32; MAX_INTERP];
118 let mut c100 = [0.0_f32; MAX_INTERP];
119 let mut c010 = [0.0_f32; MAX_INTERP];
120 let mut c110 = [0.0_f32; MAX_INTERP];
121 let mut c001 = [0.0_f32; MAX_INTERP];
122 let mut c101 = [0.0_f32; MAX_INTERP];
123 let mut c011 = [0.0_f32; MAX_INTERP];
124 let mut c111 = [0.0_f32; MAX_INTERP];
125 let mut interpolated = [0.0_f32; MAX_INTERP];
126
127 macro_rules! fill {
128 ($out:expr, $ex:expr, $ey:expr, $ez:expr, $blended:expr) => {{
129 cache.ensure($ex, $ez, noises);
130 noises.fill_cell_corner_densities(
131 &mut *cache,
132 $ex,
133 $ey,
134 $ez,
135 $blended,
136 &mut $out[..interp_count],
137 );
138 }};
139 }
140
141 let max_cell_y_idx = {
142 let raw = start_y.div_euclid(cell_h) - cell_min_y;
143 raw.clamp(0, cell_count_y - 1)
144 };
145 let min_cell_y_idx = {
146 let raw = min_solid_y.div_euclid(cell_h) - cell_min_y;
147 raw.clamp(0, cell_count_y - 1)
148 };
149
150 let mut above_is_air = false;
151 let mut have_above = false;
152 let mut blended_scratch = [0.0_f32; 2];
153
154 for cell_y_idx in (min_cell_y_idx..=max_cell_y_idx).rev() {
155 let y0 = (cell_min_y + cell_y_idx) * cell_h;
156 let y1 = y0 + cell_h;
157 let ys = [y0, y1];
158
159 noises.compute_noise_column(x0, &ys, z0, &mut blended_scratch);
160 let b000 = blended_scratch[0];
161 let b010 = blended_scratch[1];
162 noises.compute_noise_column(x1, &ys, z0, &mut blended_scratch);
163 let b100 = blended_scratch[0];
164 let b110 = blended_scratch[1];
165 noises.compute_noise_column(x0, &ys, z1, &mut blended_scratch);
166 let b001 = blended_scratch[0];
167 let b011 = blended_scratch[1];
168 noises.compute_noise_column(x1, &ys, z1, &mut blended_scratch);
169 let b101 = blended_scratch[0];
170 let b111 = blended_scratch[1];
171
172 fill!(c000, x0, y0, z0, b000);
173 fill!(c100, x1, y0, z0, b100);
174 fill!(c010, x0, y1, z0, b010);
175 fill!(c110, x1, y1, z0, b110);
176 fill!(c001, x0, y0, z1, b001);
177 fill!(c101, x1, y0, z1, b101);
178 fill!(c011, x0, y1, z1, b011);
179 fill!(c111, x1, y1, z1, b111);
180
181 let top_y_in_cell = if cell_y_idx == max_cell_y_idx {
182 (start_y - y0).clamp(0, cell_h - 1)
183 } else {
184 cell_h - 1
185 };
186 let bottom_y_in_cell = if cell_y_idx == min_cell_y_idx {
187 (min_solid_y - y0).clamp(0, cell_h - 1)
188 } else {
189 0
190 };
191
192 for y_in_cell in (bottom_y_in_cell..=top_y_in_cell).rev() {
193 let pos_y = y0 + y_in_cell;
194 for ch in 0..interp_count {
195 interpolated[ch] = interpolate_cell_value(
196 factor_x, factor_z, y_in_cell, cell_h, c000[ch], c100[ch], c010[ch], c110[ch],
197 c001[ch], c101[ch], c011[ch], c111[ch],
198 );
199 }
200
201 let density = noises.combine_interpolated(
202 &mut *cache,
203 &interpolated[..interp_count],
204 0,
205 pos_y,
206 0,
207 );
208 let state =
209 aquifer.compute_substance(noises, block_x, pos_y, block_z, f64::from(density));
210 let is_air = matches!(state, AquiferResult::Air);
211 let is_solid = matches!(state, AquiferResult::Solid);
212
213 if have_above && above_is_air && is_solid {
214 return Some(pos_y);
215 }
216
217 above_is_air = is_air;
218 have_above = true;
219 }
220 }
221
222 None
223}
224pub(crate) fn iterate_noise_column_with_aquifer<N: DimensionNoises>(
231 cache: &mut N::ColumnCache,
232 noises: &N,
233 aquifer: &mut Aquifer<N>,
234 block_x: i32,
235 block_z: i32,
236 ocean_floor: bool,
237) -> i32 {
238 let max_y = N::Settings::MIN_Y + N::Settings::HEIGHT - 1;
239 iterate_noise_column_capped::<N>(cache, noises, aquifer, block_x, block_z, max_y, ocean_floor)
240}
241
242#[expect(
247 clippy::too_many_lines,
248 reason = "inlines 8-corner density buffers + interpolation to match vanilla's iterateNoiseColumn fast path"
249)]
250fn iterate_noise_column_capped<N: DimensionNoises>(
251 cache: &mut N::ColumnCache,
252 noises: &N,
253 aquifer: &mut Aquifer<N>,
254 block_x: i32,
255 block_z: i32,
256 max_y_inclusive: i32,
257 ocean_floor: bool,
258) -> i32 {
259 const MAX_INTERP: usize = 16;
261
262 let cell_w = N::Settings::CELL_WIDTH;
263 let cell_h = N::Settings::CELL_HEIGHT;
264 let min_y = N::Settings::MIN_Y;
265 let height = N::Settings::HEIGHT;
266 let cell_min_y = min_y.div_euclid(cell_h);
267 let cell_count_y = height.div_euclid(cell_h);
268
269 let cell_x = block_x.div_euclid(cell_w);
270 let cell_z = block_z.div_euclid(cell_w);
271 let factor_x = block_x.rem_euclid(cell_w) as f32 / cell_w as f32;
272 let factor_z = block_z.rem_euclid(cell_w) as f32 / cell_w as f32;
273 let x0 = cell_x * cell_w;
274 let x1 = x0 + cell_w;
275 let z0 = cell_z * cell_w;
276 let z1 = z0 + cell_w;
277
278 let interp_count = N::interpolated_count();
279
280 let mut c000 = [0.0_f32; MAX_INTERP];
281 let mut c100 = [0.0_f32; MAX_INTERP];
282 let mut c010 = [0.0_f32; MAX_INTERP];
283 let mut c110 = [0.0_f32; MAX_INTERP];
284 let mut c001 = [0.0_f32; MAX_INTERP];
285 let mut c101 = [0.0_f32; MAX_INTERP];
286 let mut c011 = [0.0_f32; MAX_INTERP];
287 let mut c111 = [0.0_f32; MAX_INTERP];
288 let mut interpolated = [0.0_f32; MAX_INTERP];
289
290 macro_rules! fill {
291 ($out:expr, $ex:expr, $ey:expr, $ez:expr, $blended:expr) => {{
292 cache.ensure($ex, $ez, noises);
293 noises.fill_cell_corner_densities(
294 &mut *cache,
295 $ex,
296 $ey,
297 $ez,
298 $blended,
299 &mut $out[..interp_count],
300 );
301 }};
302 }
303
304 let max_cell_y_idx = {
306 let raw = max_y_inclusive.div_euclid(cell_h) - cell_min_y;
307 raw.clamp(0, cell_count_y - 1)
308 };
309 let top_cell_top_y_in_cell =
311 (max_y_inclusive - (cell_min_y + max_cell_y_idx) * cell_h).clamp(0, cell_h - 1);
312
313 let mut blended_scratch = [0.0_f32; 2];
315 for cell_y_idx in (0..=max_cell_y_idx).rev() {
316 let y0 = (cell_min_y + cell_y_idx) * cell_h;
317 let y1 = y0 + cell_h;
318 let ys = [y0, y1];
319
320 noises.compute_noise_column(x0, &ys, z0, &mut blended_scratch);
323 let b000 = blended_scratch[0];
324 let b010 = blended_scratch[1];
325 noises.compute_noise_column(x1, &ys, z0, &mut blended_scratch);
326 let b100 = blended_scratch[0];
327 let b110 = blended_scratch[1];
328 noises.compute_noise_column(x0, &ys, z1, &mut blended_scratch);
329 let b001 = blended_scratch[0];
330 let b011 = blended_scratch[1];
331 noises.compute_noise_column(x1, &ys, z1, &mut blended_scratch);
332 let b101 = blended_scratch[0];
333 let b111 = blended_scratch[1];
334
335 fill!(c000, x0, y0, z0, b000);
337 fill!(c100, x1, y0, z0, b100);
338 fill!(c010, x0, y1, z0, b010);
339 fill!(c110, x1, y1, z0, b110);
340 fill!(c001, x0, y0, z1, b001);
341 fill!(c101, x1, y0, z1, b101);
342 fill!(c011, x0, y1, z1, b011);
343 fill!(c111, x1, y1, z1, b111);
344
345 let top_y_in_cell = if cell_y_idx == max_cell_y_idx {
348 top_cell_top_y_in_cell
349 } else {
350 cell_h - 1
351 };
352
353 for y_in_cell in (0..=top_y_in_cell).rev() {
355 let pos_y = (cell_min_y + cell_y_idx) * cell_h + y_in_cell;
356 for ch in 0..interp_count {
358 interpolated[ch] = interpolate_cell_value(
359 factor_x, factor_z, y_in_cell, cell_h, c000[ch], c100[ch], c010[ch], c110[ch],
360 c001[ch], c101[ch], c011[ch], c111[ch],
361 );
362 }
363
364 let density = noises.combine_interpolated(
366 &mut *cache,
367 &interpolated[..interp_count],
368 0,
369 pos_y,
370 0,
371 );
372
373 let opaque = match aquifer.compute_substance(
375 noises,
376 block_x,
377 pos_y,
378 block_z,
379 f64::from(density),
380 ) {
381 AquiferResult::Solid => true,
382 AquiferResult::Fluid(_) => !ocean_floor,
383 AquiferResult::Air => false,
384 };
385
386 if opaque {
387 return pos_y + 1;
388 }
389 }
390 }
391 min_y
392}
393
394fn interpolated_density<N: DimensionNoises>(
398 cache: &mut N::ColumnCache,
399 noises: &N,
400 x: i32,
401 y: i32,
402 z: i32,
403 cell_w: i32,
404 cell_h: i32,
405) -> f32 {
406 const MAX_INTERP: usize = 16;
407
408 let cx = x.div_euclid(cell_w);
409 let cy = y.div_euclid(cell_h);
410 let cz = z.div_euclid(cell_w);
411 let fx = x.rem_euclid(cell_w) as f32 / cell_w as f32;
412 let fz = z.rem_euclid(cell_w) as f32 / cell_w as f32;
413
414 let x0 = cx * cell_w;
415 let x1 = x0 + cell_w;
416 let y0 = cy * cell_h;
417 let y1 = y0 + cell_h;
418 let z0 = cz * cell_w;
419 let z1 = z0 + cell_w;
420
421 let interp_count = N::interpolated_count();
422
423 let mut c000 = [0.0_f32; MAX_INTERP];
424 let mut c100 = [0.0_f32; MAX_INTERP];
425 let mut c010 = [0.0_f32; MAX_INTERP];
426 let mut c110 = [0.0_f32; MAX_INTERP];
427 let mut c001 = [0.0_f32; MAX_INTERP];
428 let mut c101 = [0.0_f32; MAX_INTERP];
429 let mut c011 = [0.0_f32; MAX_INTERP];
430 let mut c111 = [0.0_f32; MAX_INTERP];
431 let mut interpolated = [0.0_f32; MAX_INTERP];
432
433 macro_rules! fill {
434 ($out:expr, $ex:expr, $ey:expr, $ez:expr, $blended:expr) => {{
435 cache.ensure($ex, $ez, noises);
436 noises.fill_cell_corner_densities(
437 &mut *cache,
438 $ex,
439 $ey,
440 $ez,
441 $blended,
442 &mut $out[..interp_count],
443 );
444 }};
445 }
446
447 let ys = [y0, y1];
449 let mut blended_scratch = [0.0_f32; 2];
450 noises.compute_noise_column(x0, &ys, z0, &mut blended_scratch);
451 let (b000, b010) = (blended_scratch[0], blended_scratch[1]);
452 noises.compute_noise_column(x1, &ys, z0, &mut blended_scratch);
453 let (b100, b110) = (blended_scratch[0], blended_scratch[1]);
454 noises.compute_noise_column(x0, &ys, z1, &mut blended_scratch);
455 let (b001, b011) = (blended_scratch[0], blended_scratch[1]);
456 noises.compute_noise_column(x1, &ys, z1, &mut blended_scratch);
457 let (b101, b111) = (blended_scratch[0], blended_scratch[1]);
458
459 fill!(c000, x0, y0, z0, b000);
460 fill!(c100, x1, y0, z0, b100);
461 fill!(c010, x0, y1, z0, b010);
462 fill!(c110, x1, y1, z0, b110);
463 fill!(c001, x0, y0, z1, b001);
464 fill!(c101, x1, y0, z1, b101);
465 fill!(c011, x0, y1, z1, b011);
466 fill!(c111, x1, y1, z1, b111);
467
468 for ch in 0..interp_count {
469 interpolated[ch] = interpolate_cell_value(
470 fx,
471 fz,
472 y.rem_euclid(cell_h),
473 cell_h,
474 c000[ch],
475 c100[ch],
476 c010[ch],
477 c110[ch],
478 c001[ch],
479 c101[ch],
480 c011[ch],
481 c111[ch],
482 );
483 }
484
485 noises.combine_interpolated(&mut *cache, &interpolated[..interp_count], 0, y, 0)
486}