From 1b864887e775c21cb78d0e4dd4c60c03dd6fc2f9 Mon Sep 17 00:00:00 2001 From: Joshua Yanovski Date: Thu, 7 Nov 2019 21:25:30 +0100 Subject: [PATCH] Reverting changes except to humidity and temperature noise. --- world/examples/water.rs | 57 +++++++++++++-- world/src/column/mod.rs | 68 ++++++++++++------ world/src/config.rs | 2 +- world/src/sim/mod.rs | 149 ++++++++++++++++++++++++++-------------- world/src/sim/util.rs | 18 ++--- 5 files changed, 204 insertions(+), 90 deletions(-) diff --git a/world/examples/water.rs b/world/examples/water.rs index f768f2d1ee..a6ae8266d3 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -21,21 +21,50 @@ fn main() { while win.is_open() { let mut buf = vec![0; W * H]; + const QUADRANTS : usize = 4; + let mut quads = [[0u32; QUADRANTS]; QUADRANTS]; + let mut rivers = 0u32; + let mut lakes = 0u32; + let mut oceans = 0u32; for i in 0..W { for j in 0..H { let pos = focus + Vec2::new(i as i32, j as i32) * scale; - let (alt, water_alt, river_kind) = sampler + let (alt, water_alt, humidity, temperature, river_kind) = sampler .get(pos) - .map(|sample| (sample.alt, sample.water_alt, sample.river.river_kind)) - .unwrap_or((CONFIG.sea_level, CONFIG.sea_level, None)); - let alt = ((alt - CONFIG.sea_level) / CONFIG.mountain_scale) + .map(|sample| (sample.alt, sample.water_alt, sample.humidity, sample.temp, sample.river.river_kind)) + .unwrap_or((CONFIG.sea_level, CONFIG.sea_level, 0.0, 0.0, None)); + let humidity = humidity .min(1.0) .max(0.0); + let temperature = temperature + .min(1.0) + .max(-1.0) + * 0.5 + 0.5; let water_alt = ((alt.max(water_alt) - CONFIG.sea_level) / CONFIG.mountain_scale) .min(1.0) .max(0.0); + let alt = ((alt - CONFIG.sea_level) / CONFIG.mountain_scale) + .min(1.0) + .max(0.0); + let quad = |x: f32| ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1); + if river_kind.is_none() || humidity != 0.0 { + quads[quad(humidity)][quad(temperature)] += 1; + } + match river_kind { + Some(RiverKind::River { .. }) => { + rivers += 1; + }, + Some(RiverKind::Lake { .. }) => { + lakes += 1; + }, + Some(RiverKind::Ocean { .. }) => { + oceans += 1; + }, + None => {}, + } + buf[j * W + i] = match river_kind { Some(RiverKind::Ocean) => u32::from_le_bytes([64, 32, 0, 255]), Some(RiverKind::Lake { .. }) => u32::from_le_bytes([ @@ -50,12 +79,30 @@ fn main() { 0, 255, ]), - None => u32::from_le_bytes([0, (alt * 255.0) as u8, 0, 255]), + None => u32::from_le_bytes([ + (/*alt * *//*(1.0 - humidity)*/(alt * humidity).sqrt()/*temperature*/ * 255.0) as u8, + (/*alt*//*alt*//* * humidity*//*alt * 255.0*//*humidity*/alt * 255.0) as u8, + (/*alt*//*alt * *//*(1.0 - humidity)*/(alt * temperature).sqrt() * 255.0) as u8, + 255, + ]), }; } } let spd = 32; + if win.is_key_down(minifb::Key::P) { + println!("\ + Land(adjacent): (X = temp, Y = humidity): {:?}\n\ + Rivers: {:?}\n\ + Lakes: {:?}\n\ + Oceans: {:?}\n\ + Total water: {:?}\n\ + Total land(adjacent): {:?}", + quads, rivers, lakes, oceans, + rivers + lakes + oceans, + quads.iter().map( |x| x.iter().sum::() ).sum::() + ); + } if win.is_key_down(minifb::Key::W) { focus.y -= spd * scale; } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index f2cf610d65..d389199b84 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -829,21 +829,36 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { //let humidity = humidity.add((marble - 0.5) * 0.10); // Colours - let cold_grass = Rgb::new(0.1, 0.5, 0.1); - let warm_grass = Rgb::new(0.1, 0.9, 0.2); - let dark_grass = Rgb::new(0.1, 0.3, 0.2); - let wet_grass = Rgb::new(0.1, 0.5, 0.5); - let cold_stone = Rgb::new(0.5, 0.5, 0.5); - //let warm_stone = Rgb::new(0.6, 0.6, 0.5); - let warm_stone = Rgb::new(0.6, 0.5, 0.1); + let cold_grass = Rgb::new(0.0, 0.5, 0.25); + // let cold_grass = Rgb::new(0.1, 0.5, 0.1); + let warm_grass = Rgb::new(0.4, 0.8, 0.0); + // let warm_grass = Rgb::new(0.1, 0.9, 0.2); + let dark_grass = Rgb::new(0.15, 0.4, 0.1); + // let dark_grass = Rgb::new(0.1, 0.3, 0.2); + let wet_grass = Rgb::new(0.1, 0.8, 0.2); + // let wet_grass = Rgb::new(0.1, 0.5, 0.5); + let cold_stone = Rgb::new(0.57, 0.67, 0.8); + // let cold_stone = Rgb::new(0.5, 0.5, 0.5); + let warm_stone = Rgb::new(0.77, 0.77, 0.64); + // //let warm_stone = Rgb::new(0.6, 0.6, 0.5); + // let warm_stone = Rgb::new(0.6, 0.5, 0.1); let beach_sand = Rgb::new(0.9, 0.82, 0.6); - let desert_sand = Rgb::new(0.7, 0.7, 0.4); - let snow = Rgb::new(0.0, 0.0, 0.1); - let stone_col = Rgb::new(152, 98, 16); - let dirt = Lerp::lerp( + let desert_sand = Rgb::new(0.95, 0.75, 0.5); + // let desert_sand = Rgb::new(0.7, 0.7, 0.4); + let snow = Rgb::new(0.8, 0.85, 1.0); + // let snow = Rgb::new(0.0, 0.0, 0.1); + + // let stone_col = Rgb::new(152, 98, 16); + let stone_col = Rgb::new(195, 187, 201); + /*let dirt = Lerp::lerp( Rgb::new(0.4, 0.4, 0.4), Rgb::new(0.4, 0.4, 0.4), marble, + );*/ + let dirt = Lerp::lerp( + Rgb::new(0.075, 0.07, 0.3), + Rgb::new(0.75, 0.55, 0.1), + marble, ); let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6); let dead_tundra = Lerp::lerp(warm_stone, Rgb::new(0.3, 0.12, 0.2), marble); @@ -881,7 +896,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { sand, temp.sub(CONFIG.snow_temp) .div(CONFIG.desert_temp.sub(CONFIG.snow_temp)) - .mul(4.5), + .mul(/*4.5*/0.5), ), cliff, alt.sub(CONFIG.mountain_scale * 0.25) @@ -902,7 +917,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .div(CONFIG.snow_temp.neg()) /*.sub((marble - 0.5) * 0.05) .mul(256.0)*/ - .mul(2.0), + .mul(1.0), + // .mul(2.0), ), // 0 to tropical_temp grass, @@ -912,18 +928,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { moss, temp.sub(CONFIG.tropical_temp) .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp)) - .mul(2.0), + .mul(1.0), + // .mul(2.0), ), // above desert_temp sand, temp.sub(CONFIG.desert_temp) .div(1.0 - CONFIG.desert_temp) - .mul(2.0), + .mul(4.0), + // .mul(2.0), ), humidity .sub(CONFIG.desert_hum) .div(CONFIG.forest_hum.sub(CONFIG.desert_hum)) - .mul(2.0), + .mul(1.0), + // .mul(2.0), ); // From forest to jungle humidity, we go from snow to dark grass to grass to tropics to sand // depending on temperature. @@ -941,18 +960,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { tropical, temp.sub(CONFIG.tropical_temp) .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp)) - .mul(2.0), + .mul(1.0), + // .mul(2.0), ), // above desert_temp sand, temp.sub(CONFIG.desert_temp) .div(1.0 - CONFIG.desert_temp) - .mul(2.0), + .mul(4.0), + // .mul(2.0), ), humidity .sub(CONFIG.forest_hum) .div(CONFIG.jungle_hum.sub(CONFIG.forest_hum)) - .mul(2.0), + .mul(1.0), + // .mul(2.0), ); // From jungle humidity upwards, we go from snow to grass to rainforest to tropics to sand. let ground = Rgb::lerp( @@ -969,13 +991,15 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { tropical, temp.sub(CONFIG.tropical_temp) .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp)) - .mul(2.0), + .mul(4.0), + // .mul(2.0), ), // above desert_temp sand, temp.sub(CONFIG.desert_temp) .div(1.0 - CONFIG.desert_temp) - .mul(2.0), + .mul(4.0), + // .mul(2.0), ), humidity.sub(CONFIG.jungle_hum).mul(1.0), ); @@ -1046,7 +1070,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // Beach ((ocean_level - 1.0) / 2.0).max(0.0), ), - sub_surface_color: warm_grass, + sub_surface_color: /*warm_grass*/dirt, tree_density, forest_kind: sim_chunk.forest_kind, close_structures: self.gen_close_structures(wpos), diff --git a/world/src/config.rs b/world/src/config.rs index 3a6bf149e4..1e5a372409 100644 --- a/world/src/config.rs +++ b/world/src/config.rs @@ -48,7 +48,7 @@ pub const CONFIG: Config = Config { mountain_scale: 2048.0, snow_temp: -0.6, tropical_temp: 0.2, - desert_temp: 0.9, + desert_temp: 0.6, desert_hum: 0.15, forest_hum: 0.5, jungle_hum: 0.85, diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 0006b7bdb5..8cf43400b2 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -112,26 +112,30 @@ impl WorldSim { let gen_ctx = GenCtx { turb_x_nz: SuperSimplex::new().set_seed(rng.gen()), turb_y_nz: SuperSimplex::new().set_seed(rng.gen()), - chaos_nz: RidgedMulti::new().set_octaves(2).set_seed(rng.gen()), + chaos_nz: RidgedMulti::new().set_octaves(7).set_seed(rng.gen()), hill_nz: SuperSimplex::new().set_seed(rng.gen()), alt_nz: HybridMulti::new() - .set_octaves(2) + .set_octaves(8) .set_persistence(0.1) .set_seed(rng.gen()), //temp_nz: SuperSimplex::new().set_seed(rng.gen()), temp_nz: Fbm::new() - .set_octaves(8) - .set_persistence(0.9) + .set_octaves(6) + .set_persistence(0.5) + .set_frequency(/*4.0 / /*(1024.0 * 4.0/* * 8.0*/)*//*32.0*/((1 << 6) * (WORLD_SIZE.x)) as f64*/1.0 / (((1 << 6) * 64) as f64)) + // .set_frequency(1.0 / 1024.0) + // .set_frequency(1.0 / (1024.0 * 8.0)) + .set_lacunarity(2.0) .set_seed(rng.gen()), - small_nz: BasicMulti::new().set_octaves(1).set_seed(rng.gen()), + small_nz: BasicMulti::new().set_octaves(2).set_seed(rng.gen()), rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(rng.gen()), - cliff_nz: HybridMulti::new().set_persistence(0.1).set_seed(rng.gen()), - warp_nz: FastNoise::new(rng.gen()), //BasicMulti::new().set_octaves(1).set_seed(gen_seed()), + cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(rng.gen()), + warp_nz: FastNoise::new(rng.gen()), //BasicMulti::new().set_octaves(3).set_seed(gen_seed()), tree_nz: BasicMulti::new() - .set_octaves(1) - .set_persistence(0.1) + .set_octaves(12) + .set_persistence(0.75) .set_seed(rng.gen()), cave_0_nz: SuperSimplex::new().set_seed(rng.gen()), cave_1_nz: SuperSimplex::new().set_seed(rng.gen()), @@ -141,7 +145,7 @@ impl WorldSim { cliff_gen: StructureGen2d::new(rng.gen(), 80, 56), humid_nz: Billow::new() .set_octaves(9) - .set_persistence(0.7) + .set_persistence(0.4) .set_frequency(0.2) // .set_octaves(6) // .set_persistence(0.5) @@ -159,6 +163,7 @@ impl WorldSim { .set_persistence(0.9) .set_seed(rng.gen()); + // No NaNs in these uniform vectors, since the original noise value always returns Some. let ((alt_base, _), (chaos, _)) = rayon::join( || { uniform_noise(|_, wposf| { @@ -192,7 +197,7 @@ impl WorldSim { .get((wposf.div(400.0)).into_array()) .min(1.0) .max(-1.0) - .mul(0.9)) + .mul(0.3)) .add(0.3) .max(0.0); @@ -232,6 +237,8 @@ impl WorldSim { // We ignore sea level because we actually want to be relative to sea level here and want // things in CONFIG.mountain_scale units, but otherwise this is a correct altitude // calculation. Note that this is using the "unadjusted" temperature. + // + // No NaNs in these uniform vectors, since the original noise value always returns Some. let (alt_old, alt_old_inverse) = uniform_noise(|posi, wposf| { // This is the extension upwards from the base added to some extra noise from -1 to // 1. @@ -289,7 +296,7 @@ impl WorldSim { // = [-.3675, .3325] + ([-0.5785, 0.7345]) // = [-0.946, 1.067] Some( - ((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2))) + ((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2)/*0.25*/)) .mul(map_edge_factor(posi) as f64) .add( (CONFIG.sea_level as f64) @@ -301,9 +308,28 @@ impl WorldSim { ) }); + // Calculate oceans. + let old_height = |posi: usize| alt_old[posi].1; + let is_ocean = get_oceans(old_height); + let is_ocean_fn = |posi: usize| is_ocean[posi]; + + /* // Recalculate altitudes without oceans. + // NaNs in these uniform vectors wherever pure_water() returns true. + let (alt_old_no_ocean, /*alt_old_inverse*/_) = uniform_noise(|posi, _| { + if is_ocean_fn(posi) { + None + } else { + Some(old_height(posi)/*.abs()*/) + } + }); + + let old_height_uniform = |posi: usize| alt_old_no_ocean[posi].0; + let alt_old_min_uniform = 0.0; + let alt_old_max_uniform = 1.0; */ // Find the minimum and maximum original altitudes. // NOTE: Will panic if there is no land, and will not work properly if the minimum and // maximum land altitude are identical (will most likely panic later). + let old_height_uniform = |posi: usize| alt_old[posi].0; let (alt_old_min_index, _alt_old_min) = alt_old_inverse .iter() .copied() @@ -313,12 +339,6 @@ impl WorldSim { let alt_old_min_uniform = alt_old[alt_old_min_index].0; let alt_old_max_uniform = alt_old[alt_old_max_index].0; - // Calculate oceans. - let old_height = |posi: usize| alt_old[posi].1; - let old_height_uniform = |posi: usize| alt_old[posi].0; - let is_ocean = get_oceans(old_height); - let is_ocean_fn = |posi: usize| is_ocean[posi]; - // Perform some erosion. let max_erosion_per_delta_t = 32.0 / CONFIG.mountain_scale as f64; @@ -349,9 +369,12 @@ impl WorldSim { |posi| { let height = ((old_height_uniform(posi) - alt_old_min_uniform) as f64 / (alt_old_max_uniform - alt_old_min_uniform) as f64) + /*.mul(1.0 - f64::EPSILON * 0.5) + .add(f64::EPSILON * 0.5);*/ .max(1e-7 / CONFIG.mountain_scale as f64) .min(1.0f64 - 1e-7); let height = erosion_factor(height); + assert!(height >= 0.0); let height = height .mul(max_erosion_per_delta_t * 7.0 / 8.0) .add(max_erosion_per_delta_t / 8.0); @@ -473,25 +496,31 @@ impl WorldSim { true }; - let (pure_flux, _) = uniform_noise(|posi, _| { - if pure_water(posi) { - None - } else { - Some(flux_old[posi]) - } - }); - - let ((alt_no_water, _), ((temp_base, _), (humid_base, _))) = rayon::join( + // NaNs in these uniform vectors wherever pure_water() returns true. + let (((alt_no_water, _), (pure_flux, _)), ((temp_base, _), (humid_base, _))) = rayon::join( || { - uniform_noise(|posi, _| { - if pure_water(posi) { - None - } else { - // A version of alt that is uniform over *non-water* (or land-adjacent water) - // chunks. - Some(alt[posi]) - } - }) + rayon::join( + || { + uniform_noise(|posi, _| { + if pure_water(posi) { + None + } else { + // A version of alt that is uniform over *non-water* (or land-adjacent water) + // chunks. + Some(alt[posi]) + } + }) + }, + || { + uniform_noise(|posi, _| { + if pure_water(posi) { + None + } else { + Some(flux_old[posi]) + } + }) + }, + ) }, || { rayon::join( @@ -501,7 +530,7 @@ impl WorldSim { None } else { // -1 to 1. - Some(gen_ctx.temp_nz.get((wposf.div(12000.0)).into_array()) as f32) + Some(gen_ctx.temp_nz.get((wposf/*.div(12000.0)*/).into_array()) as f32) } }) }, @@ -1007,15 +1036,18 @@ impl SimChunk { let _map_edge_factor = map_edge_factor(posi); let (_, chaos) = gen_cdf.chaos[posi]; - let (humid_uniform, _) = gen_cdf.humid_base[posi]; let alt_pre = gen_cdf.alt[posi]; let water_alt_pre = gen_cdf.water_alt[posi]; let downhill_pre = gen_cdf.dh[posi]; - let (flux_uniform, _) = gen_cdf.pure_flux[posi]; let flux = gen_cdf.flux[posi]; + let river = gen_cdf.rivers[posi].clone(); + + // Can have NaNs in non-uniform part where pure_water returned true. We just test one of + // the four in order to find out whether this is the case. + let (flux_uniform, /*flux_non_uniform*/_) = gen_cdf.pure_flux[posi]; let (alt_uniform, _) = gen_cdf.alt_no_water[posi]; let (temp_uniform, _) = gen_cdf.temp_base[posi]; - let river = gen_cdf.rivers[posi].clone(); + let (humid_uniform, _) = gen_cdf.humid_base[posi]; /* // Vertical difference from the equator (NOTE: "uniform" with much lower granularity than // other uniform quantities, but hopefully this doesn't matter *too* much--if it does, we @@ -1030,25 +1062,36 @@ impl SimChunk { // Take the weighted average of our randomly generated base humidity, the scaled // negative altitude, and the calculated water flux over this point in order to compute // humidity. - const HUMID_WEIGHTS: [f32; 3] = [4.0, 0.1, 0.1]; - let humidity = cdf_irwin_hall( - &HUMID_WEIGHTS, - [humid_uniform, flux_uniform, 1.0 - alt_uniform], - ); + const HUMID_WEIGHTS: [f32; /*3*/2] = [4.0, 1.0/*, 1.0*/]; + let humidity = /*if flux_non_uniform.is_nan() { + 0.0 + } else */{ + cdf_irwin_hall( + &HUMID_WEIGHTS, + [humid_uniform, flux_uniform/*, 1.0 - alt_uniform*/], + ) + }; // We also correlate temperature negatively with altitude and absolute latitude, using // different weighting than we use for humidity. - const TEMP_WEIGHTS: [f32; 2] = [2.0, 16.0 /*, 1.0*/]; - let temp = cdf_irwin_hall( - &TEMP_WEIGHTS, - [ - temp_uniform, - 1.0 - alt_uniform, /* 1.0 - abs_lat_uniform*/ - ], - ) + const TEMP_WEIGHTS: [f32; 2] = [/*1.5, */1.0, 2.0]; + let temp = /*if flux_non_uniform.is_nan() { + 0.0 + } else */{ + cdf_irwin_hall( + &TEMP_WEIGHTS, + [ + temp_uniform, + 1.0 - alt_uniform, /* 1.0 - abs_lat_uniform*/ + ], + ) + } // Convert to [-1, 1] .sub(0.5) .mul(2.0); + /* if (temp - (1.0 - alt_uniform).sub(0.5).mul(2.0)).abs() >= 1e-7 { + panic!("Halp!"); + } */ let mut alt = CONFIG.sea_level.add(alt_pre.mul(CONFIG.mountain_scale)); let water_alt = CONFIG diff --git a/world/src/sim/util.rs b/world/src/sim/util.rs index c88cdcdc1e..d51c9d2b5b 100644 --- a/world/src/sim/util.rs +++ b/world/src/sim/util.rs @@ -74,13 +74,13 @@ pub fn cdf_irwin_hall(weights: &[f32; N], samples: [f32; N]) -> // We should be able to iterate through the whole power set // instead, and figure out K by calling count_ones(), so we can compute the result in O(2^N) // iterations. - let x: f32 = weights + let x: f64 = weights .iter() .zip(samples.iter()) - .map(|(weight, sample)| weight * sample) + .map(|(&weight, &sample)| weight as f64 * sample as f64) .sum(); - let mut y = 0.0f32; + let mut y = 0.0f64; for subset in 0u32..(1 << N) { // Number of set elements let k = subset.count_ones(); @@ -89,8 +89,8 @@ pub fn cdf_irwin_hall(weights: &[f32; N], samples: [f32; N]) -> .iter() .enumerate() .filter(|(i, _)| subset & (1 << i) as u32 != 0) - .map(|(_, k)| k) - .sum::(); + .map(|(_, &k)| k as f64) + .sum::(); // Compute max(0, x - B_subset)^N let z = (x - z).max(0.0).powi(N as i32); // The parity of k determines whether the sum is negated. @@ -98,10 +98,10 @@ pub fn cdf_irwin_hall(weights: &[f32; N], samples: [f32; N]) -> } // Divide by the product of the weights. - y /= weights.iter().product::(); + y /= weights.iter().map(|&k| k as f64).product::(); // Remember to multiply by 1 / N! at the end. - y / (1..(N as i32) + 1).product::() as f32 + (y / (1..(N as i32) + 1).product::() as f64) as f32 } /// First component of each element of the vector is the computed CDF of the noise function at this @@ -148,7 +148,7 @@ pub fn vec2_as_uniform_idx(idx: Vec2) -> usize { /// vector returned by uniform_noise, and (for convenience) the float-translated version of those /// coordinates. /// f should return a value with no NaNs. If there is a NaN, it will panic. There are no other -/// conditions on f. If f returns None, the value will be set to 0.0, and will be ignored for the +/// conditions on f. If f returns None, the value will be set to NaN, and will be ignored for the /// purposes of computing the uniform range. /// /// Returns a vec of (f32, f32) pairs consisting of the percentage of chunks with a value lower than @@ -179,7 +179,7 @@ pub fn uniform_noise( // position of the noise in the sorted vector (divided by the vector length). // This guarantees a uniform distribution among the samples (excluding those that returned // None, which will remain at zero). - let mut uniform_noise = vec![(0.0, F::zero()); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice(); + let mut uniform_noise = vec![(0.0, F::nan()/*zero()*/); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice(); // NOTE: Consider using try_into here and elsewhere in this function, since i32::MAX // technically doesn't fit in an f32 (even if we should never reach that limit). let total = noise.len() as f32;