Handle oceans and steep rivers gracefully, update changelog

This commit is contained in:
Joshua Barretto 2021-09-21 18:35:43 +01:00
parent 2ae7bca9c0
commit 96e23ae2d4
4 changed files with 97 additions and 61 deletions

View File

@ -13,12 +13,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a crafting station icon to the crafting menu sidebar for items that could be crafted at a crafting station
- Added a setting to disable the hotkey hints
- Added a credits screen in the main menu which shows attributions for assets
- Shrubs, a system for spawning smaller tree-like plants into the world.
- Waterfalls
- Sailing boat (currently requires spawning in)
### Changed
- Made dungeon tiers 3, 4, and 5 more common
- Put date at the begining of the log file instead of the end to allow MIME type recognition
- Tweaked CR and exp calculation formula
- Sprite spawn rates
### Removed
@ -27,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The menu map now properly handles dragging the map, zooming, and setting the waypoint when hovering icons
- Falling through an airship in flight should no longer be possible (although many issues with airship physics remain)
- Avoided black hexagons when bloom is enabled by suppressing NaN/Inf pixels during the first bloom blur pass
- Many know water generation problems
## [0.11.0] - 2021-09-11

View File

@ -106,7 +106,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let gradient = sim.get_gradient_approx(chunk_pos);
let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 6.0;
let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 5.0;
let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| {
let kind = match river.river_kind {
Some(kind) => kind,
@ -210,7 +210,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// Lakes get a special distance function to avoid cookie-cutter edges
if matches!(
downhill_chunk.river.river_kind,
Some(RiverKind::Lake { .. })
Some(RiverKind::Lake { .. } | RiverKind::Ocean)
) {
let water_chunk = posj.map(|e| e as f64);
let lake_width_noise = sim
@ -262,18 +262,23 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
}
},
RiverKind::Ocean => {
let ndist = wposf.distance_squared(neighbor_wpos);
let (closest_pos, closest_dist, closest_t) = (neighbor_wpos, ndist, 0.0);
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 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,
};
let pos = water_aabr.projected_point(wposf);
(
direction,
coeffs,
sim.get(closest_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
e as i32 / sz as i32
}))
.expect("Must already work"),
closest_t,
closest_pos,
closest_dist.sqrt(),
sim.get(posj).expect("Must already work"),
0.5,
pos,
pos.distance(wposf),
)
},
};
@ -378,8 +383,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
default
};
let res = self.min.map_or(res, |m| m.min(res));
let res = self.max.map_or(res, |m| m.max(res));
res
self.max.map_or(res, |m| m.max(res))
}
}
@ -413,7 +417,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Lerp::lerp(a, b, t)
}
let actual_sea_level = CONFIG.sea_level + 2.0; // TODO: Don't add 2.0, why is this required?
// Use this to temporarily alter the sea level
let base_sea_level = CONFIG.sea_level + 0.01;
// What's going on here?
//
@ -437,12 +442,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// 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),
WeightedSum::default().with_max(base_sea_level),
false,
WeightedSum::default().with_max(actual_sea_level),
WeightedSum::default().with_max(base_sea_level),
10000.0f32,
None,
WeightedSum::default().with_max(actual_sea_level),
WeightedSum::default().with_max(base_sea_level),
),
|(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) {
(
@ -479,15 +484,19 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
}
},
// Slightly wider threshold is chosen in case the lake bounds are a bit wrong
RiverKind::Lake { .. } => {
let lake_water_alt = river_water_alt(
river_chunk.alt.max(river_chunk.water_alt),
downhill_chunk.alt.max(downhill_chunk.water_alt),
river_t as f32,
is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
);
RiverKind::Lake { .. } | RiverKind::Ocean => {
let lake_water_alt = if matches!(kind, RiverKind::Ocean) {
base_sea_level
} else {
river_water_alt(
river_chunk.alt.max(river_chunk.water_alt),
downhill_chunk.alt.max(downhill_chunk.water_alt),
river_t as f32,
is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
)
};
if river_edge_dist > 0.0 && river_width > lake_width * 0.99 /* !matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. }))*/ {
if river_edge_dist > 0.0 && river_width > lake_width * 0.99 {
let unbounded_water_alt = lake_water_alt - ((river_edge_dist - 8.0).max(0.0) / 5.0).powf(2.0);
unbounded_water_level = unbounded_water_level
.with(unbounded_water_alt, 1.0 / (1.0 + river_edge_dist * 5.0))
@ -506,7 +515,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.with(lake_water_alt, near_center + 0.1 / (1.0 + river_edge_dist));
}
},
RiverKind::Ocean => {},
};
let river_edge_dist_unclamped = (river_dist - river_width * 0.5) as f32;
@ -517,7 +525,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
(_, _) => (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level),
},
);
let unbounded_water_level = unbounded_water_level.eval_or(actual_sea_level);
let unbounded_water_level = unbounded_water_level.eval_or(base_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.
@ -528,11 +536,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.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(base_sea_level).max(unbounded_water_level),
}
.max(base_sea_level);
let riverless_alt = alt;
@ -586,7 +592,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
);
Some((river_water_alt, cross_section.y as f32, None))
},
RiverKind::Lake { .. } => {
RiverKind::Lake { .. } | RiverKind::Ocean => {
let lake_water_alt = river_water_alt(
river_chunk.alt.max(river_chunk.water_alt),
downhill_chunk.alt.max(downhill_chunk.water_alt),
@ -604,8 +610,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
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,
((river_dist / (river_width * 0.5) - 0.5) * 2.0).clamped(0.0, 1.0)
as f32,
);
Some((
@ -618,7 +624,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(min_alt),
))
},
RiverKind::Ocean => None,
};
const BANK_STRENGTH: f32 = 100.0;
@ -631,7 +636,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
// Waterfalls 'boost' the depth of the river to prevent artifacts. This
// is also necessary when rivers become very
// steep without explicitly being waterfalls.
// TODO: Come up with a more principled way of doing this without
// guessing magic numbers
let waterfall_boost =
@ -640,7 +647,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
* (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5)
/ 20.0
} else {
0.0
// Handle very steep rivers gracefully
(river_chunk.alt - downhill_chunk.alt).max(0.0) * 2.0
/ TerrainChunkSize::RECT_SIZE.x as f32
};
let riverbed_depth =
near_centre * water_depth + MIN_DEPTH + waterfall_boost;
@ -666,16 +675,28 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
+ (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH
/ BANK_SCALE),
0.0,
(river_edge_dist / BANK_SCALE).clamped(0.0, 1.0),
power((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64, 2.0)
as f32,
);
let alt = alt.with(water_alt + GORGE, weight);
let alt = if lake_dist > 0.0 && water_level < unbounded_water_level {
alt.with_max(unbounded_water_level)
let alt = if matches!(kind, RiverKind::Ocean) {
alt
} else if (0.0..1.5).contains(&river_edge_dist)
&& water_dist.map_or(false, |d| d >= 0.0)
{
alt.with_max(water_alt + GORGE)
} else {
alt
};
alt
if matches!(kind, RiverKind::Ocean) {
alt
} else if lake_dist > 0.0 && water_level < unbounded_water_level {
alt.with_max(unbounded_water_level)
} else {
alt
}
}
} else {
alt
@ -684,7 +705,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
(_, _) => alt,
},
);
let alt = alt.eval_or(riverless_alt);
let alt = alt
.eval_or(riverless_alt)
.max(if water_dist.map_or(true, |d| d > 0.0) {
// Terrain below sea level breaks things, so force it to never happen
base_sea_level + 0.5
} else {
f32::MIN
});
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(),
@ -1024,7 +1052,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
// Land
ground,
((alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0),
((alt - base_sea_level) / 12.0).clamped(0.0, 1.0),
),
surface_veg,
),

View File

@ -393,7 +393,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, CONFIG.temperate_temp, 0.8)
* MUSH_FACT
* 300.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 18.0
{
1.0
@ -408,7 +408,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
(
MUSH_FACT
* 600.0
* if col.water_level < CONFIG.sea_level && (col.water_level - col.alt) < 3.0 {
* if col.water_level <= CONFIG.sea_level && (col.water_level - col.alt) < 3.0 {
1.0
} else {
0.0
@ -422,7 +422,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, CONFIG.temperate_temp, 0.8)
* MUSH_FACT
* 50.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 11.0
{
1.0
@ -438,7 +438,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, 1.0, 0.95)
* MUSH_FACT
* 50.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 11.0
{
1.0
@ -453,7 +453,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
(
MUSH_FACT
* 250.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
{
1.0
@ -468,7 +468,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
(
MUSH_FACT
* 250.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
{
1.0
@ -484,7 +484,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, 1.0, 0.95)
* MUSH_FACT
* 500.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
{
1.0
@ -500,7 +500,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, CONFIG.temperate_temp, 0.8)
* MUSH_FACT
* 125.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM - 9.0
{
1.0
@ -516,7 +516,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, CONFIG.temperate_temp, 0.8)
* MUSH_FACT
* 220.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM - 9.0
{
1.0
@ -532,7 +532,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, CONFIG.temperate_temp, 0.7)
* MUSH_FACT
* 300.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 3.0
{
1.0
@ -548,7 +548,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, 1.0, 0.9)
* MUSH_FACT
* 160.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
{
1.0
@ -564,7 +564,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
close(col.temp, 1.0, 0.9)
* MUSH_FACT
* 120.0
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
{
1.0
@ -579,7 +579,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
(
(c.rockiness - 0.5).max(0.0)
* 1.0e-3
* if col.water_level < CONFIG.sea_level
* if col.water_level <= CONFIG.sea_level
&& col.alt < col.water_level - DEPTH_WATER_NORM + 20.0
{
1.0
@ -604,7 +604,6 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
];
canvas.foreach_col(|canvas, wpos2d, col| {
// TODO: Why do we need to add 1.0 here? Idk...
let underwater = col.water_level.floor() > col.alt;
let kind = scatter

View File

@ -2324,11 +2324,15 @@ impl SimChunk {
const SOIL_SCALE: f32 = 16.0;
let soil = soil_nz * SOIL_SCALE * tree_density.sqrt() * humidity.sqrt();
// Prevent dunes pushing the altitude underwater
if alt + dune + soil < water_alt {
let warp_factor = ((alt - CONFIG.sea_level) / 16.0).clamped(0.0, 1.0);
let warp = (dune + soil) * warp_factor;
// Prevent warping pushing the altitude underwater
if alt + warp < water_alt {
alt
} else {
alt + dune + soil
alt + warp
}
};