Cleaned up shrub implementation

This commit is contained in:
Joshua Barretto 2021-09-17 13:36:05 +01:00
parent c399fe9a39
commit e8021fab2c
12 changed files with 406 additions and 326 deletions

View File

@ -6,27 +6,27 @@
center: (6, 6, 2),
),
(
specifier: "world.shrub.jungle-bush-0",
specifier: "world.shrub.jungle.bush-0",
center: (5, 5, 3),
),
(
specifier: "world.shrub.jungle-bush-1",
specifier: "world.shrub.jungle.bush-1",
center: (5, 5, 2),
),
(
specifier: "world.shrub.jungle-bush-2",
specifier: "world.shrub.jungle.bush-2",
center: (5, 5, 3),
),
(
specifier: "world.shrub.jungle-bush-3",
specifier: "world.shrub.jungle.bush-3",
center: (5, 5, 3),
),
(
specifier: "world.shrub.jungle-bush-4",
specifier: "world.shrub.jungle.bush-4",
center: (5, 5, 4),
),
(
specifier: "world.shrub.jungle-bush-5",
specifier: "world.shrub.jungle.bush-5",
center: (5, 5, 5),
),
]

View File

@ -551,8 +551,11 @@ impl<'a> MapConfig<'a> {
downhill_wpos,
);
let (_t, _pt, dist) = if let Some((t, pt, dist)) =
quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, downhill_wpos))
{
quadratic_nearest_point(
&coeffs,
wposf,
Vec2::new(neighbor_wpos, downhill_wpos),
) {
(t, pt, dist)
} else {
let ndist = wposf.distance_squared(neighbor_wpos);

View File

@ -304,9 +304,9 @@ pub fn quadratic_nearest_point(
// end: line.y,
// };
// let len_sq = line.start.distance_squared(line.end);
// let t = ((point - line.start).dot(line.end - line.start) / len_sq).clamped(0.0, 1.0);
// let pos = line.start + (line.end - line.start) * t;
// return Some((t, pos, pos.distance_squared(point)));
// let t = ((point - line.start).dot(line.end - line.start) /
// len_sq).clamped(0.0, 1.0); let pos = line.start + (line.end - line.start)
// * t; return Some((t, pos, pos.distance_squared(point)));
// Quadratic
@ -406,9 +406,9 @@ 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))
// })
// .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))
// })
}

View File

@ -309,7 +309,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// },
);
let river_width = river_width.max(2.0f64.sqrt() + 0.1) * (1.0 + river_width_noise * 0.3);
let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3);
// To find the distance, we just evaluate the quadratic equation at river_t and
// see if it's within width (but we should be able to use it for a
// lot more, and this probably isn't the very best approach anyway
@ -344,7 +344,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.unwrap_or(CONFIG.sea_level);
#[derive(Default)]
struct WeightedSum<T> { sum: T, weight: T, min: Option<T>, max: Option<T> }
struct WeightedSum<T> {
sum: T,
weight: T,
min: Option<T>,
max: Option<T>,
}
impl WeightedSum<f32> {
fn with(self, value: f32, weight: f32) -> Self {
Self {
@ -353,14 +358,23 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
..self
}
}
/// With an upper bound
fn with_min(self, min: f32) -> Self {
Self { min: Some(self.min.unwrap_or(min).min(min)), ..self }
Self {
min: Some(self.min.unwrap_or(min).min(min)),
..self
}
}
/// With a lower bound
fn with_max(self, max: f32) -> Self {
Self { max: Some(self.max.unwrap_or(max).max(max)), ..self }
Self {
max: Some(self.max.unwrap_or(max).max(max)),
..self
}
}
fn eval(&self) -> Option<f32> {
if self.weight > 0.0 {
let res = self.sum / self.weight;
@ -370,8 +384,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
} else {
None
}
}
fn eval_or(&self, default: f32) -> f32 {
let res = if self.weight > 0.0 {
self.sum / self.weight
@ -384,8 +398,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
}
}
fn is_waterfall(chunk_pos: Vec2<i32>, river_chunk: &SimChunk, downhill_chunk: &SimChunk) -> bool {
(chunk_pos.sum() as u32 % 19 < 2 || matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. }))) && (river_chunk.water_alt > downhill_chunk.water_alt + 8.0)
fn is_waterfall(
chunk_pos: Vec2<i32>,
river_chunk: &SimChunk,
downhill_chunk: &SimChunk,
) -> bool {
(chunk_pos.sum() as u32 % 19 < 2
|| matches!(
downhill_chunk.river.river_kind,
Some(RiverKind::Lake { .. })
))
&& (river_chunk.water_alt > downhill_chunk.water_alt + 8.0)
}
fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 {
@ -501,17 +524,28 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
},
);
let unbounded_water_level = unbounded_water_level.eval_or(actual_sea_level);
let water_level = match (river_water_level.eval(), lake_water_level.eval().filter(|_| lake_dist <= 0.0 || in_river)) {
let water_level = match (
river_water_level.eval(),
lake_water_level
.eval()
.filter(|_| lake_dist <= 0.0 || in_river),
) {
(Some(r), Some(l)) => r.max(l),
(r, l) => r.or(l).unwrap_or(actual_sea_level).max(unbounded_water_level),
(r, l) => r
.or(l)
.unwrap_or(actual_sea_level)
.max(unbounded_water_level),
};
let riverless_alt = alt;
let alt = neighbor_river_data.clone().fold(
WeightedSum::default().with(riverless_alt, 1.0),
|alt, (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) {
|alt, (river_chunk_idx, river_chunk, river, dist_info)| match (
river.river_kind,
dist_info,
) {
(
Some(kind/*RiverKind::River { cross_section }*/),
Some(kind /* RiverKind::River { cross_section } */),
Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))),
) => {
// Distance from river center
@ -519,7 +553,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// 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_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 } => {
@ -540,11 +579,25 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
);
let depth = water_level - Lerp::lerp(riverless_alt.min(water_level), water_level - 4.0, 0.5);
let depth = water_level
- Lerp::lerp(
riverless_alt.min(water_level),
water_level - 4.0,
0.5,
);
let min_alt = Lerp::lerp(riverless_alt, lake_water_alt, ((river_dist - 8.5) / (river_width * 0.5 - 8.5).max(0.01)).clamped(0.0, 1.0) as f32);
let min_alt = Lerp::lerp(
riverless_alt,
lake_water_alt,
((river_dist - 8.5) / (river_width * 0.5 - 8.5).max(0.01))
.clamped(0.0, 1.0) as f32,
);
Some((lake_water_alt, /*river_width as f32 * 0.15*/ depth, Some(min_alt)))
Some((
lake_water_alt,
/* river_width as f32 * 0.15 */ depth,
Some(min_alt),
))
},
RiverKind::Ocean => None,
};
@ -553,29 +606,48 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
if let Some((water_alt, water_depth, min_alt)) = water_alt {
if river_edge_dist <= 0.0 {
const MIN_DEPTH: f32 = 1.0;
let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);
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) * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5) / 20.0
} 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 bottom
let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH));
alt
.with(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt), near_centre * BANK_STRENGTH)
.with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt))
let near_centre = ((river_dist / (river_width * 0.5)) as f32)
.min(1.0)
.mul(f32::consts::PI)
.cos()
.add(1.0)
.mul(0.5);
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)
* (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5)
/ 20.0
} 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
// bottom
let riverbed_alt = (water_alt - riverbed_depth)
.max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH));
alt.with(
min_alt.unwrap_or(riverbed_alt).min(riverbed_alt),
near_centre * BANK_STRENGTH,
)
.with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt))
} else {
const GORGE: f32 = 0.5;
const BANK_SCALE: f32 = 24.0;
// Weighting of this riverbank on nearby terrain (higher when closer to the river). This
// 'pulls' the riverbank toward the river's altitude to make sure that we get a smooth
// Weighting of this riverbank on nearby terrain (higher when closer to
// the river). This 'pulls' the riverbank
// toward the river's altitude to make sure that we get a smooth
// transition from normal terrain to the water.
let weight = Lerp::lerp(
BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH / BANK_SCALE),
BANK_STRENGTH
/ (1.0
+ (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,
// 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);
@ -586,8 +658,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
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.with_max(water_alt + GORGE - river_edge_dist.powf(2.0) /
// 10.0) } else {
// alt
// };
alt
@ -758,11 +830,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
};
let alt_for_river = alt;
// + if overlap_count == 0.0 {
// 0.0
// } else {
// river_overlap_distance_product / overlap_count
// } as f32;
// + 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(),
@ -802,261 +874,238 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
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 (_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);
// 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;
// FIXME: Make water altitude accurate.
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,
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_for_river,
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);
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 {
// 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(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),
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(lake_dist as f32),
alt_for_river - max_dip,//alt_for_river.max(lake_water_alt).max(river_chunk.water_alt),
(
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_for_river,
river_scale_factor as f32,
)
},
None => (
alt, //alt_for_river,
1.0,
),
}
} else {
(
false,
None,
alt_for_river - max_dip,//alt_for_river,
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 riverless_alt = alt + riverless_alt_delta; //riverless_alt +
// riverless_alt_delta;
let alt = alt/*alt_*/ + riverless_alt_delta;
let basement =
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;

View File

@ -1,4 +1,5 @@
use crate::{
all::ForestKind,
util::{seed_expan, RandomPerm, Sampler, StructureGen2d, UnitChooser},
Canvas,
};
@ -14,12 +15,13 @@ use rand_chacha::ChaChaRng;
use vek::*;
lazy_static! {
static ref SHRUBS: AssetHandle<StructuresGroup> = Structure::load_group("shrubs");
static ref JUNGLE_SHRUBS: AssetHandle<StructuresGroup> = Structure::load_group("shrubs.jungle");
}
struct Shrub {
wpos: Vec3<i32>,
seed: u32,
kind: ForestKind,
}
pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) {
@ -33,7 +35,10 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) {
shrub_cache.entry(wpos).or_insert_with(|| {
let col = info.col_or_gen(wpos)?;
if RandomPerm::new(seed).chance(37, col.tree_density * 0.3)
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
const BASE_SHRUB_DENSITY: f64 = 0.15;
if rng.gen_bool((BASE_SHRUB_DENSITY * col.tree_density as f64).clamped(0.0, 1.0))
&& col.water_dist.map_or(true, |d| d > 8.0)
&& col.alt > col.water_level
&& col.spawn_rate > 0.9
@ -42,6 +47,11 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) {
Some(Shrub {
wpos: wpos.with_z(col.alt as i32),
seed,
kind: *info
.chunks()
.make_forest_lottery(wpos)
.choose_seeded(seed)
.as_ref()?,
})
} else {
None
@ -55,7 +65,12 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) {
let units = UnitChooser::new(shrub.seed).get(shrub.seed).into();
let shrubs = SHRUBS.read();
let shrubs = match shrub.kind {
ForestKind::Mangrove => &JUNGLE_SHRUBS,
_ => continue, // TODO: Add more shrub varieties
}
.read();
let structure = shrubs.choose(&mut rng).unwrap();
canvas.blit_structure(shrub.wpos, structure, shrub.seed, units, true);
}

View File

@ -2056,47 +2056,45 @@ impl WorldSim {
self.get_nearest_way(wpos, |chunk| Some(chunk.cave))
}
/// Create a [`Lottery<Option<ForestKind>>`] that generates [`ForestKind`]s
/// according to the conditions at the given position. If no or fewer
/// trees are appropriate for the conditions, `None` may be generated.
pub fn make_forest_lottery(&self, wpos: Vec2<i32>) -> Lottery<Option<ForestKind>> {
let chunk = if let Some(chunk) = self.get_wpos(wpos) {
chunk
} else {
return Lottery::from(vec![(1.0, None)]);
};
let env = chunk.get_environment();
Lottery::from(
ForestKind::into_enum_iter()
.enumerate()
.map(|(i, fk)| {
const CLUSTER_SIZE: f64 = 48.0;
let nz = (FastNoise2d::new(i as u32 * 37)
.get(wpos.map(|e| e as f64) / CLUSTER_SIZE)
+ 1.0)
/ 2.0;
(fk.proclivity(&env) * nz, Some(fk))
})
.chain(std::iter::once((0.001, None)))
.collect::<Vec<_>>(),
)
}
/// Return an iterator over candidate tree positions (note that only some of
/// these will become trees since environmental parameters may forbid
/// them spawning).
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
// Deterministic based on wpos
let normal_trees = std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos))
.filter_map(move |(pos, seed)| {
let chunk = self.get_wpos(pos)?;
let env = Environment {
humid: chunk.humidity,
temp: chunk.temp,
near_water: if chunk.river.is_lake()
|| chunk.river.near_river()
|| chunk.alt < CONFIG.sea_level + 6.0
// Close to sea in altitude
{
1.0
} else {
0.0
},
};
.filter_map(move |(wpos, seed)| {
let lottery = self.make_forest_lottery(wpos);
Some(TreeAttr {
pos,
pos: wpos,
seed,
scale: 1.0,
forest_kind: *Lottery::from(
ForestKind::into_enum_iter()
.enumerate()
.map(|(i, fk)| {
const CLUSTER_SIZE: f64 = 48.0;
let nz = (FastNoise2d::new(i as u32 * 37)
.get(pos.map(|e| e as f64) / CLUSTER_SIZE)
+ 1.0)
/ 2.0;
(fk.proclivity(&env) * nz, Some(fk))
})
.chain(std::iter::once((0.001, None)))
.collect::<Vec<_>>(),
)
.choose_seeded(seed)
.as_ref()?,
forest_kind: *lottery.choose_seeded(seed).as_ref()?,
inhabited: false,
})
});
@ -2222,8 +2220,7 @@ impl SimChunk {
Some(
uniform_idx_as_vec2(map_size_lg, downhill_pre as usize)
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32)
+ TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2)
,
+ TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2),
)
};
@ -2424,4 +2421,20 @@ impl SimChunk {
}
pub fn near_cliffs(&self) -> bool { self.cliff_height > 0.0 }
pub fn get_environment(&self) -> Environment {
Environment {
humid: self.humidity,
temp: self.temp,
near_water: if self.river.is_lake()
|| self.river.near_river()
|| self.alt < CONFIG.sea_level + 6.0
// Close to sea in altitude
{
1.0
} else {
0.0
},
}
}
}