diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 375645db08..2165d416cf 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -83,12 +83,7 @@ impl Body { pub fn mass(&self) -> Mass { Mass((self.hull_vol() + self.balloon_vol()) * self.density().0) } - pub fn can_fly(&self) -> bool { - match self { - Body::DefaultAirship | Body::AirBalloon => true, - _ => false, - } - } + pub fn can_fly(&self) -> bool { matches!(self, Body::DefaultAirship | Body::AirBalloon) } pub fn has_water_thrust(&self) -> bool { !self.can_fly() // TODO: Differentiate this more carefully diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 5f7b20790f..655f6b3e8f 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -173,6 +173,7 @@ impl Body { quadruped_low::Species::Lavadrake => 1.7, _ => 2.0, }, + Body::Ship(ship) if ship.has_water_thrust() => 0.1, Body::Ship(_) => 0.035, } } @@ -180,7 +181,7 @@ impl Body { /// Returns thrust force if the body type can swim, otherwise None pub fn swim_thrust(&self) -> Option { match self { - Body::Object(_) | Body::Ship(_) => None, + Body::Object(_) => None, Body::BipedLarge(_) | Body::Golem(_) => Some(200.0 * self.mass().0), Body::BipedSmall(_) => Some(100.0 * self.mass().0), Body::BirdMedium(_) => Some(50.0 * self.mass().0), @@ -200,8 +201,8 @@ impl Body { Body::QuadrupedLow(_) => Some(300.0 * self.mass().0), Body::QuadrupedMedium(_) => Some(300.0 * self.mass().0), Body::QuadrupedSmall(_) => Some(300.0 * self.mass().0), - Body::Ship(ship) if ship.has_water_thrust() => Some(500.0 * self.mass().0), - _ => None, + Body::Ship(ship) if ship.has_water_thrust() => Some(750.0 * self.mass().0), + Body::Ship(_) => None, } } diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 719df4d3a3..d5d8d87042 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -293,9 +293,9 @@ pub fn river_spline_coeffs( pub fn quadratic_nearest_point( spline: &Vec3>, point: Vec2, - line: Vec2>, + _line: Vec2>, // Used for alternative distance functions below ) -> Option<(f64, Vec2, f64)> { - let eval_at = |t: f64| spline.x * t * t + spline.y * t + spline.z; + //let eval_at = |t: f64| spline.x * t * t + spline.y * t + spline.z; // Linear @@ -374,25 +374,15 @@ pub fn quadratic_nearest_point( let min_root = roots .iter() .copied() - // .chain((0..30).map(|i| i as f64 / 30.0)) - .filter_map(|root| { + .map(|root| { let river_point = spline.x * root * root + spline.y * root + spline.z; - let river_zero = spline.z; - let river_one = spline.x + spline.y + spline.z; if root > 0.0 && root < 1.0 { - Some((root, river_point)) + (root, river_point) } else { let root = root.clamped(0.0, 1.0); let river_point = spline.x * root * root + spline.y * root + spline.z; - Some((root, river_point)) + (root, river_point) } - // } else if river_point.distance_squared(river_zero) < 0.5 { - // Some((root, /*river_point*/ river_zero)) - // } else if river_point.distance_squared(river_one) < 0.5 { - // Some((root, /*river_point*/ river_one)) - // } else { - // None - // } }) .map(|(root, river_point)| { let river_distance = river_point.distance_squared(point); @@ -406,9 +396,4 @@ pub fn quadratic_nearest_point( .unwrap() }); min_root - // .map(|(t, pt, dist)| { - // let t = t.clamped(0.0, 1.0); - // let pos = spline.x * t * t + spline.y * t + spline.z; - // (t, pos, pos.distance_squared(point)) - // }) } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b4320967b6..375e4ccc08 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2704,9 +2704,6 @@ temp {:?} humidity {:?} rockiness {:?} tree_density {:?} -in_river {:?} -in_lake {:?} -unbounded_water_level {:?} spawn_rate {:?} "#, wpos, alt, @@ -2723,9 +2720,6 @@ spawn_rate {:?} "#, humidity, rockiness, tree_density, - col.in_river, - col.in_lake, - col.unbounded_water_level, spawn_rate )) }; diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 4cc7171c88..e6f54d7793 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -14,7 +14,6 @@ use common::{ use noise::NoiseFn; use serde::Deserialize; use std::{ - cmp::Reverse, f32, ops::{Add, Div, Mul, Sub}, }; @@ -51,6 +50,7 @@ pub struct Colors { pub tropical_high: (f32, f32, f32), } +/// Generalised power function, pushes values in the range 0-1 to extremes. fn power(x: f64, t: f64) -> f64 { if x < 0.5 { (2.0 * x).powf(t) / 2.0 @@ -59,23 +59,6 @@ fn power(x: f64, t: f64) -> f64 { } } -fn cubic(x: f64) -> f64 { - power(x, 3.0) - // if x < 0.5 { - // (2.0 * x).powf(3.0) / 2.0 - // } else { - // 1.0 - (-2.0 * x + 2.0).powf(3.0) / 2.0 - // } -} - -fn revcubic(x: f64) -> f64 { - if x < 0.5 { - (x / 4.0).powf(1.0 / 3.0) - } else { - 1.0 - (2.0 - 2.0 * x).powf(1.0 / 3.0) / 2.0 - } -} - impl<'a> ColumnGen<'a> { pub fn new(sim: &'a WorldSim) -> Self { Self { sim } } } @@ -146,8 +129,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } }; let downhill_wpos = downhill_pos.map(|e| e as f64); - let downhill_pos = - downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e.div_euclid(sz as i32)); + let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { + e.div_euclid(sz as i32) + }); let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5; let direction = neighbor_wpos - downhill_wpos; let river_width_min = if let RiverKind::River { cross_section } = kind { @@ -159,17 +143,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let coeffs = river_spline_coeffs(neighbor_wpos, chunkj.river.spline_derivative, downhill_wpos); let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind { - RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { - if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, downhill_wpos)) /*{ - let curve = CubicBezier2 { - start: neighbor_wpos, - ctrl0: neighbor_wpos * 0.75 + downhill_wpos * 0.25 + chunkj.river.spline_derivative.map(|e| e as f64).normalized() * 0.0, - ctrl1: downhill_wpos * 0.75 + neighbor_wpos * 0.25 - downhill_chunk.river.spline_derivative.map(|e| e as f64).normalized() * 0.0, - end: downhill_wpos, - }; - let (t, pos) = curve.binary_search_point_by_steps(wposf, 16, 0.001); - Some((t, pos, curve.evaluate(t).distance_squared(wposf))) - }*/ { + RiverKind::River { .. } => { + if let Some((t, pt, dist)) = quadratic_nearest_point( + &coeffs, + wposf, + Vec2::new(neighbor_wpos, downhill_wpos), + ) { let (t, pt, dist) = if dist > wposf.distance_squared(neighbor_wpos) { (0.0, neighbor_wpos, wposf.distance_squared(neighbor_wpos)) } else if dist > wposf.distance_squared(downhill_wpos) { @@ -220,16 +199,24 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if pass_dist > 1 { return (posj, chunkj, river, None); } - let neighbor_pass_wpos = neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; + let neighbor_pass_wpos = + neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; let neighbor_pass_pos = neighbor_pass_pos .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let coeffs = river_spline_coeffs(neighbor_wpos, spline_derivative, neighbor_pass_wpos); let direction = neighbor_wpos - neighbor_pass_wpos; - if matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. })) { + // Lakes get a special distance function to avoid cookie-cutter edges + if matches!( + downhill_chunk.river.river_kind, + Some(RiverKind::Lake { .. }) + ) { let water_chunk = posj.map(|e| e as f64); - let lake_width_noise = sim.gen_ctx.small_nz.get((wposf.map(|e| e as f64).div(32.0)).into_array()); + let lake_width_noise = sim + .gen_ctx + .small_nz + .get((wposf.map(|e| e as f64).div(32.0)).into_array()); let water_aabr = Aabr { min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0, max: (water_chunk + 1.0) * neighbor_coef - 4.0 + lake_width_noise * 8.0, @@ -243,7 +230,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { pos, pos.distance(wposf), ) - } else if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, neighbor_pass_wpos)) { + } else if let Some((t, pt, dist)) = quadratic_nearest_point( + &coeffs, + wposf, + Vec2::new(neighbor_wpos, neighbor_pass_wpos), + ) { ( direction, coeffs, @@ -288,10 +279,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let river_width_max = if let Some(RiverKind::River { cross_section }) = downhill_chunk.river.river_kind { - (cross_section.x as f64).min(river_width_min * 1.75) // Hack + // Harmless hack that prevents a river growing wildly outside its bounds to + // create water walls + (cross_section.x as f64).min(river_width_min * 1.75) } else if let Some(RiverKind::River { cross_section }) = chunkj.river.river_kind { Lerp::lerp(cross_section.x as f64, lake_width, 0.5) } else { + // 0.5 prevents rivers pooling into lakes having extremely wide bounds, creating + // water walls lake_width * 0.5 }; let river_width_noise = (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array())) @@ -302,11 +297,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let river_width = Lerp::lerp( river_width_min, river_width_max, - // if matches!(chunkj.river.river_kind, Some(RiverKind::Lake { .. })) { - // (1.0 - (river_t.clamped(0.0, 1.0) * 2.0 - 1.0).powi(2)).sqrt() * 0.5 - // } else { - cubic(river_t.clamped(0.0, 1.0)) - // }, + river_t.clamped(0.0, 1.0).powf(3.0), ); let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3); @@ -330,19 +321,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ) }); - let downhill = sim_chunk.downhill; - let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos)); debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level); - let downhill_water_alt = downhill_pos - .map(|downhill_chunk| { - downhill_chunk - .water_alt - .min(sim_chunk.water_alt) - .max(sim_chunk.alt.min(sim_chunk.water_alt)) - }) - .unwrap_or(CONFIG.sea_level); - + /// A type that makes managing surface altitude weighting much simpler. #[derive(Default)] struct WeightedSum { sum: T, @@ -351,6 +332,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { max: Option, } impl WeightedSum { + /// Add a weight to the sum. fn with(self, value: f32, weight: f32) -> Self { Self { sum: self.sum + value * weight, @@ -359,7 +341,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } - /// With an upper bound + /// Add an upper bound to the result. fn with_min(self, min: f32) -> Self { Self { min: Some(self.min.unwrap_or(min).min(min)), @@ -367,7 +349,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } - /// With a lower bound + /// Add a lower bound to the result. fn with_max(self, max: f32) -> Self { Self { max: Some(self.max.unwrap_or(max).max(max)), @@ -375,6 +357,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + /// Evaluate the weighted sum, if weightings were applied. fn eval(&self) -> Option { if self.weight > 0.0 { let res = self.sum / self.weight; @@ -386,6 +369,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + /// Evaluate the weighted sum, or use a default value if no + /// weightings were applied. fn eval_or(&self, default: f32) -> f32 { let res = if self.weight > 0.0 { self.sum / self.weight @@ -398,11 +383,16 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + /// Determine whether a river should become a waterfall fn is_waterfall( chunk_pos: Vec2, river_chunk: &SimChunk, downhill_chunk: &SimChunk, ) -> bool { + // Waterfalls are rare, so use some hacky RNG seeded with the position to + // reflect that. Additionally, the river must experience a rapid + // change in elevation. Pooling into a lake produces a rapid. + // TODO: Find a better way to produce rapids along the course of a river? (chunk_pos.sum() as u32 % 19 < 2 || matches!( downhill_chunk.river.river_kind, @@ -411,28 +401,40 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { && (river_chunk.water_alt > downhill_chunk.water_alt + 8.0) } + /// Determine the altitude of a river based on the altitude of the + /// spline ends and a tweening factor. fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 { - Lerp::lerp( - a, - b, - // if t < 0.5 { - // 0.0 - // } else { - // let t = (t - 0.5) * 2.0; - // t.powf(1.0 + (a - b).max(0.0) / 1.5) - // }, - // t.powf(1.0 / (1.0 + (a - b).max(0.0) / 3.0)), - if is_waterfall { - power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32 - } else { - // cubic(t as f64) as f32 - t - }, - ) + let t = if is_waterfall { + // Waterfalls bias the water altitude toward extremes + power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32 + } else { + t + }; + Lerp::lerp(a, b, t) } let actual_sea_level = CONFIG.sea_level + 2.0; // TODO: Don't add 2.0, why is this required? + // What's going on here? + // + // We're iterating over nearby bodies of water and calculating a weighted sum + // for the river water level, the lake water level, and the 'unbounded + // water level' (the maximum water body altitude, which we use later to + // prevent water walls). In doing so, we also apply various clamping strategies + // to catch lots of nasty edge cases, as well as calculating the + // distance to the nearest body of water. + // + // The clamping strategies employed prevent very specific, annoying artifacts + // such as 'water walls' (vertical columns of water that are physically + // implausible) and 'backflows' (regions where a body of water appears to + // flow upstream due to irregular humps along its course). + // + // It is incredibly difficult to explain exactly what every part of this code is + // doing without visual examples. Needless to say, any changes to this + // code *at all* should be very ruggedly tested to ensure that + // they do not result in artifacts, even in edge cases. The exact configuration + // of this code is the product of hundreds of hours of testing and + // refinement and I ask that you do not take that effort lightly. let (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level) = neighbor_river_data.clone().fold( ( WeightedSum::default().with_max(actual_sea_level), @@ -444,20 +446,24 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ), |(mut river_water_level, mut in_river, mut lake_water_level, mut lake_dist, water_dist, mut unbounded_water_level), (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { ( - Some(kind/*RiverKind::River { cross_section }*/), - Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), + Some(kind), + Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))), ) => { // Distance from river center let river_dist = river_pos.distance(wposf); // Distance from edge of river let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; // 0.0 = not near river, 1.0 = in middle of river - let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); - let near_center = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); + let near_center = ((river_dist / (river_width * 0.5)) as f32) + .min(1.0) + .mul(f32::consts::PI) + .cos() + .add(1.0) + .mul(0.5); match kind { - RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { - // Alt of river water *is* the alt of land + RiverKind::River { .. } => { + // Alt of river water *is* the alt of land (ignoring gorge, which gets applied later) let river_water_alt = river_water_alt( river_chunk.alt.max(river_chunk.water_alt), downhill_chunk.alt.max(downhill_chunk.water_alt), @@ -465,14 +471,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { is_waterfall(river_chunk_idx, river_chunk, downhill_chunk), ); - // if river_edge_dist > 0.0 { - // unbounded_water_level = unbounded_water_level - // .with(river_water_alt - (river_edge_dist - 2.0).max(0.0), 1.0 / (1.0 + river_edge_dist * 5.0)) - // .with_max(river_water_alt - (river_edge_dist - 2.0).max(0.0)); - // } - river_water_level = river_water_level - // .with_max(river_water_alt) .with(river_water_alt, near_center); if river_edge_dist <= 0.0 { @@ -499,17 +498,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .with(lake_water_alt, near_center); lake_dist = lake_dist.min(river_edge_dist); - // if river_edge_dist <= 0.0 { - // in_lake = true; - // } // Lake border prevents a lake failing to propagate its altitude to nearby rivers - let border = (river_width / lake_width) as f32 * 14.0; - if river_edge_dist <= 0.0/*border*/ { + if river_edge_dist <= 0.0 { lake_water_level = lake_water_level // Make sure the closest lake is prioritised .with(lake_water_alt, near_center + 0.1 / (1.0 + river_edge_dist)); - // .with_max(lake_water_alt); } }, RiverKind::Ocean => {}, @@ -524,6 +518,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }, ); let unbounded_water_level = unbounded_water_level.eval_or(actual_sea_level); + // Calculate a final, canonical altitude for the water in this column by + // combining and clamping the attributes we found while iterating over + // nearby bodies of water. let water_level = match ( river_water_level.eval(), lake_water_level @@ -538,6 +535,31 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let riverless_alt = alt; + + // What's going on here? + // + // Now that we've figured out the altitude of the water in this column, we can + // determine the altitude of the river banks. This initially appears + // somewhat backward (surely the river basin determines the water level?) + // but it is necessary to prevent backflows. Here, the surface of the water is + // king because we require global information to determine it without + // backflows. The river banks simply reflect the will of the water. We care + // much less about a river bank that's slightly rugged and irregular than we do + // about the surface of the water itself being rugged and irregular (and + // hence physically implausible). From that perspective, it makes sense + // that we determine river banks after the water level because it is the one + // that we are most at liberty to screw up. + // + // Similar to the iteration above, we perform a fold over nearby bodies of water + // and use the distance to the water to come up wight a weighted sum for + // the altitude. The way we determine this altitude differs somewhat + // between rivers, lakes, and the ocean and also whether we are *inside* said + // bodies of water or simply near their edge. + // + // As with the previous iteration, a lot of this code is extremely delicate and + // has been carefully designed to handle innumeral edge cases. Please + // test any changes to this code extremely well to avoid regressions: some + // edge cases are very rare indeed! let alt = neighbor_river_data.clone().fold( WeightedSum::default().with(riverless_alt, 1.0), |alt, (river_chunk_idx, river_chunk, river, dist_info)| match ( @@ -545,20 +567,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { dist_info, ) { ( - Some(kind /* RiverKind::River { cross_section } */), - Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), + Some(kind), + Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))), ) => { // Distance from river center let river_dist = river_pos.distance(wposf); // Distance from edge of river let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; - // 0.0 = not near river, 1.0 = in middle of river - let near_river = ((river_dist / river_width) as f32) - .min(1.0) - .mul(f32::consts::PI) - .cos() - .add(1.0) - .mul(0.5); let water_alt = match kind { RiverKind::River { cross_section } => { @@ -595,7 +610,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(( lake_water_alt, - /* river_width as f32 * 0.15 */ depth, + // TODO: The depth given to us by the erosion code is technically + // correct, but it also + // looks terrible. Come up with a good solution to this. + /* river_width as f32 * 0.15 */ + depth, Some(min_alt), )) }, @@ -612,6 +631,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .cos() .add(1.0) .mul(0.5); + // Waterfalls 'boost' the depth of the river to prevent artifacts + // TODO: Come up with a more principled way of doing this without + // guessing magic numbers let waterfall_boost = if is_waterfall(river_chunk_idx, river_chunk, downhill_chunk) { (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0) @@ -620,8 +642,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { 0.0 }; - // let river_depth = cross_section.y as f32 /*river_width as f32 * - // 0.15*/; let riverbed_depth = near_centre * water_depth + MIN_DEPTH + waterfall_boost; // Handle rivers debouching into the ocean nicely by 'flattening' their @@ -646,8 +666,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { + (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), 0.0, - // cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) - // as f32, (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), ); let alt = alt.with(water_alt + GORGE, weight); @@ -657,11 +675,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { alt }; - // let alt = if !in_river && lake_dist > 0.0 { - // alt.with_max(water_alt + GORGE - river_edge_dist.powf(2.0) / - // 10.0) } else { - // alt - // }; alt } } else { @@ -673,169 +686,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ); let alt = alt.eval_or(riverless_alt); - // Find the average distance to each neighboring body of water. - let mut river_count = 0.0f64; - let mut overlap_count = 0.0f64; - let mut river_distance_product = 1.0f64; - let mut river_overlap_distance_product = 0.0f64; - let mut max_river = None; - let mut max_key = None; - let mut max_dip = 0.0f32; - // IDEA: - // For every "nearby" chunk, check whether it is a river. If so, find the - // closest point on the river segment to wposf (if two point are - // equidistant, choose the earlier one), calling this point river_pos - // and the length (from 0 to 1) along the river segment for the nearby - // chunk river_t. Let river_dist be the distance from river_pos to wposf. - // - // Let river_alt be the interpolated river height at this point - // (from the alt/water altitude at the river, to the alt/water_altitude of the - // downhill river, increasing with river_t). - // - // Now, if river_dist is <= river_width * 0.5, then we don't care what altitude - // we use, and mark that we are on a river (we decide what river to use - // using a heuristic, and set the solely according to the computed - // river_alt for that point). - // - // Otherwise, we let dist = river_dist - river_width * 0.5. - // - // If dist >= TerrainChunkSize::RECT_SIZE.x, we don't include this river in the - // calculation of the correct altitude for this point. - // - // Otherwise (i.e. dist < TerrainChunkSize::RECT_SIZE.x), we want to bias the - // altitude of this point towards the altitude of the river. - // Specifically, as the dist goes from TerrainChunkSize::RECT_SIZE.x to - // 0, the weighted altitude of this point should go from - // alt to river_alt. - neighbor_river_data.for_each(|(river_chunk_idx, river_chunk, river, dist)| { - return; // TODO: Not this - - match river.river_kind { - Some(kind) => { - if kind.is_river() && dist.is_none() { - // Ostensibly near a river segment, but not "usefully" so (there is no - // closest point between t = 0.0 and t = 1.0). - return; - } else { - let river_dist = dist.map(|(_, dist, river_width, (river_t, (river_pos, _), downhill_river))| { - let downhill_height = if kind.is_river() { - // Lerp::lerp( - // river_chunk.alt.max(river_chunk.water_alt), - // downhill_river.alt.max(downhill_river.water_alt), - // river_t as f32, - // ) as f64 - river_width - (river_pos - wposf).magnitude() - } else { - let neighbor_pos = - river_chunk_idx.map(|e| e as f64) * neighbor_coef; - river_width - (wposf - neighbor_pos).magnitude() - }; - (Reverse((dist.x, dist.y /*- if kind.is_river() { 0.01 } else { 0.0 }*/)), downhill_height) - }); - let river_dist = river_dist.or_else(|| { - if !kind.is_river() { - let neighbor_pos = - river_chunk_idx.map(|e| e as f64) * neighbor_coef; - let dist = (wposf - neighbor_pos).magnitude(); - let dist_upon = - (dist - TerrainChunkSize::RECT_SIZE.x as f64 * 0.5).max(0.0); - let dist_ = if dist == 0.0 { f64::INFINITY } else { -dist }; - Some((Reverse((0.0, dist_upon)), dist_)) - } else { - None - } - }); - let river_key = (river_dist, Reverse(kind)); - if max_key < Some(river_key) { - max_river = Some((river_chunk_idx, river_chunk, river, dist)); - max_key = Some(river_key); - } - } - - // NOTE: we scale by the distance to the river divided by the difference - // between the edge of the river that we intersect, and the remaining distance - // until the nearest point in "this" chunk (i.e. the one whose top-left corner - // is chunk_pos) that is at least 2 chunks away from the river source. - if let Some((_, dist, river_width, (river_t, (river_pos, _), downhill_river_chunk))) = dist { - let max_distance = if !river.is_river() { - /*(*/ - TerrainChunkSize::RECT_SIZE.x as f64 /* * (1.0 - (2.0f64.sqrt() / 2.0))) + 4.0*/ - lake_width * 0.5 - } else { - TerrainChunkSize::RECT_SIZE.x as f64 - }; - let scale_factor = max_distance; - let river_dist = if kind.is_river() { - // dist.y - ((river_pos - wposf).magnitude() - river_width * 0.5).max(0.0) - } else { - let water_chunk = river_chunk_idx.map(|e| e as f64); - let water_aabr = Aabr { - min: water_chunk * neighbor_coef, - max: (water_chunk + 1.0) * neighbor_coef, - }; - /*water_aabr.distance_to_point(wposf)*/ - (river_pos - wposf).magnitude() / (TerrainChunkSize::RECT_SIZE.x as f64 * 0.1) - }; - - if !(dist.x == 0.0 && river_dist < scale_factor) { - return; - } - // We basically want to project outwards from river_pos, along the current - // tangent line, to chunks <= river_width * 1.0 away from this - // point. We *don't* want to deal with closer chunks because they - - // NOTE: river_width <= 2 * max terrain chunk size width, so this should not - // lead to division by zero. - // NOTE: If distance = 0.0 this goes to zero, which is desired since it - // means points that actually intersect with rivers will not be interpolated - // with the "normal" height of this point. - // NOTE: We keep the maximum at 1.0 so we don't undo work from another river - // just by being far away. - let river_scale = river_dist / scale_factor; - let river_alt = - Lerp::lerp(river_chunk.alt, downhill_river_chunk.alt, river_t as f32); - let river_alt = Lerp::lerp(river_alt, alt, river_scale as f32); - let river_alt_diff = river_alt - alt; - let river_alt_inv = river_alt_diff as f64; - river_overlap_distance_product += (1.0 - river_scale) * river_alt_inv; - overlap_count += 1.0 - river_scale; - river_count += 1.0; - river_distance_product *= river_scale; - max_dip = max_dip.max(-river_alt_diff + if let RiverKind::River { cross_section } = kind { - let river_depth = river_width as f32 * 0.5; - - (cross_section.y * river_depth).max(0.0) + 1.5 - } else { - 0.0 - }); - } - } - None => {} - } - }); - - let river_scale_factor = if river_count == 0.0 { - 1.0 - } else { - let river_scale_factor = river_distance_product; - if river_scale_factor == 0.0 { - 0.0 - } else { - river_scale_factor.powf(if river_count == 0.0 { - 1.0 - } else { - 1.0 / river_count - }) - } - }; - - let alt_for_river = alt; - // + if overlap_count == 0.0 { - // 0.0 - // } else { - // river_overlap_distance_product / overlap_count - // } as f32; - let riverless_alt_delta = (sim.gen_ctx.small_nz.get( (wposf_turb.div(200.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64))).into_array(), ) as f32) @@ -873,240 +723,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let cliff_offset = cliff * cliff_height; let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height; - let river_gouge = 0.5; - let (_in_water, old_water_dist, alt_, old_water_level, _riverless_alt, warp_factor) = - if let Some(( - max_border_river_pos, - river_chunk, - max_border_river, - max_border_river_dist, - )) = max_river - { - // This is flowing into a lake, or a lake, or is at least a non-ocean tile. - // - // If we are <= water_alt, we are in the lake; otherwise, we are flowing into - // it. - match max_border_river.river_kind { - Some(RiverKind::River { cross_section }) => 'block: { - if max_border_river_dist.map(|(_, dist, _, _)| dist) != Some(Vec2::zero()) { - let (_, _, river_width, (_, (river_pos, _), _)) = - max_border_river_dist.unwrap(); - let river_dist = wposf.distance(river_pos); + let warp_factor = water_dist.map_or(1.0, |d| d.max(0.0) / 64.0); - // FIXME: Make water altitude accurate. - break 'block ( - river_scale_factor <= 1.0, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - river_scale_factor as f32, - ); - } - let (_, _, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = - max_border_river_dist.unwrap(); - let river_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ); - let new_alt = river_alt - river_gouge; - let river_dist = wposf.distance(river_pos); - let river_height_factor = river_dist / (river_width * 0.5); - - // This is not a good way to determine river depth, but we do it - // anyway. TODO: Make the erosion - // sim output better river depths through `cross_section`. - let river_depth = river_width as f32 * 0.5; - - let valley_alt = Lerp::lerp( - new_alt - (cross_section.y * river_depth).max(1.0), - new_alt - 1.0, - (river_height_factor * river_height_factor) as f32, - ); - - ( - true, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip, //valley_alt.min(alt), - new_alt.max(valley_alt.min(alt) + 1.0), - alt, //river_alt + cross_section.y.max(1.0), - river_scale_factor as f32, //0.0, - ) - }, - Some(RiverKind::Ocean) => 'block: { - let (_, dist, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = - if let Some(dist) = max_border_river_dist { - dist - } else { - error!( - ?max_border_river, - ?chunk_pos, - ?max_border_river_pos, - "downhill error details" - ); - panic!("Oceans should definitely have a downhill! ...Right?"); - }; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ) + 1.0; - - if dist == Vec2::zero() { - let river_dist = wposf.distance(river_pos); - let _river_height_factor = river_dist / (river_width * 0.5); - break 'block ( - true, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip, //alt_for_river, - // .min(lake_water_alt - 1.0 - river_gouge), - lake_water_alt, - alt, //alt_for_river.max(lake_water_alt), - 0.0, - ); - } - - ( - river_scale_factor <= 1.0, - Some((wposf.distance(river_pos) - river_width * 0.5) as f32), - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt + 1.0, - alt, //alt_for_river, - river_scale_factor as f32, - ) - }, - Some(RiverKind::Lake { .. }) => 'block: { - let lake_chunk = max_border_river_pos.map(|e| e as f64); - let lake_aabr = Aabr { - min: lake_chunk * neighbor_coef, - max: (lake_chunk + 1.0) * neighbor_coef, - }; - let lake_dist = lake_aabr.distance_to_point(wposf); - let downhill_river_chunk = max_border_river_pos; - let lake_id_dist = downhill_river_chunk - chunk_pos; - let in_bounds = lake_id_dist.x >= -1 - && lake_id_dist.y >= -1 - && lake_id_dist.x <= 1 - && lake_id_dist.y <= 1; - let in_bounds = in_bounds && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); - let (_, dist, _, (river_t, _, downhill_river_chunk)) = - if let Some(dist) = max_border_river_dist { - dist - } else if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = 0.0; - break 'block ( - in_bounds - || downhill_water_alt.max(river_chunk.water_alt) - > alt_for_river, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - (downhill_water_alt.max(river_chunk.water_alt) - river_gouge), - alt_for_river, - river_scale_factor as f32 * (1.0 - gouge_factor), - ); - } else { - break 'block ( - false, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ); - }; - - let lake_dist = dist.y; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ); - // if dist == Vec2::zero() { - // return ( - // true, - // Some(lake_dist as f32), - // alt_for_river, - // // .min(lake_water_alt - 1.0 - river_gouge), - // lake_water_alt - river_gouge, - // alt_for_river.max(lake_water_alt), - // 0.0, - // ); - // } - if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 || in_bounds { - let gouge_factor = if in_bounds && lake_dist <= 1.0 { - 1.0 - } else { - 0.0 - }; - let in_bounds_ = - lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; - if gouge_factor == 1.0 { - break 'block ( - true, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - // .min(lake_water_alt - 1.0 - river_gouge), - downhill_water_alt.max(lake_water_alt) - river_gouge, - alt.max(lake_water_alt), - 0.0, // river_scale_factor as f32* (1.0 - gouge_factor), - ); - } else { - break 'block ( - true, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - if in_bounds_ { - downhill_water_alt.max(lake_water_alt) - } else { - downhill_water_alt //.max(lake_water_alt) - } - .max(river_chunk.water_alt) - - river_gouge, - alt_for_river, - river_scale_factor as f32 * (1.0 - gouge_factor), - ); - } - } - ( - river_scale_factor <= 1.0, - Some(lake_dist as f32), - alt_for_river - max_dip, /* alt_for_river.max(lake_water_alt). - * max(river_chunk.water_alt), */ - downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ) - }, - None => ( - false, - None, - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - 1.0, - ), - } - } else { - ( - false, - None, - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - 1.0, - ) - }; // NOTE: To disable warp, uncomment this line. // let warp_factor = 0.0; - let warp_factor = warp_factor.min(water_dist.map_or(1.0, |d| d.max(0.0) / 64.0)); let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); - // let riverless_alt = alt + riverless_alt_delta; //riverless_alt + - // riverless_alt_delta; - let alt = alt/*alt_*/ + riverless_alt_delta; + let alt = alt + riverless_alt_delta; let basement = alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; @@ -1433,9 +1056,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { snow_cover, cliff_offset, cliff_height, - in_river, - in_lake: lake_dist <= 0.0, - unbounded_water_level, chunk: sim_chunk, }) @@ -1468,9 +1088,6 @@ pub struct ColumnSample<'a> { pub snow_cover: bool, pub cliff_offset: f32, pub cliff_height: f32, - pub in_river: bool, - pub in_lake: bool, - pub unbounded_water_level: f32, pub chunk: &'a SimChunk, } diff --git a/world/src/layer/shrub.rs b/world/src/layer/shrub.rs index da51b5ade9..6f054354a2 100644 --- a/world/src/layer/shrub.rs +++ b/world/src/layer/shrub.rs @@ -1,12 +1,11 @@ use crate::{ all::ForestKind, - util::{seed_expan, RandomPerm, Sampler, StructureGen2d, UnitChooser}, + util::{seed_expan, Sampler, StructureGen2d, UnitChooser}, Canvas, }; use common::{ assets::AssetHandle, terrain::structure::{Structure, StructuresGroup}, - vol::ReadVol, }; use hashbrown::HashMap; use lazy_static::lazy_static; @@ -24,13 +23,13 @@ struct Shrub { kind: ForestKind, } -pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { +pub fn apply_shrubs_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) { let mut shrub_cache = HashMap::new(); let shrub_gen = StructureGen2d::new(canvas.index().seed, 8, 4); let info = canvas.info(); - canvas.foreach_col(|canvas, wpos2d, col| { + canvas.foreach_col(|_, wpos2d, _| { for (wpos, seed) in std::array::IntoIter::new(shrub_gen.get(wpos2d)) { shrub_cache.entry(wpos).or_insert_with(|| { let col = info.col_or_gen(wpos)?;