Adopt cave performance improvements from zoomy-worldgen

This commit is contained in:
Syniis 2024-02-19 03:15:58 +01:00
parent 5d9202337e
commit 53cbb90b37

View File

@ -1,22 +1,25 @@
use super::scatter::close; use super::scatter::close;
use crate::{ use crate::{
util::{sampler::Sampler, FastNoise2d, RandomField, RandomPerm, StructureGen2d, LOCALITY}, util::{
sampler::Sampler, FastNoise2d, RandomField, RandomPerm, StructureGen2d, LOCALITY, SQUARE_4,
},
Canvas, CanvasInfo, ColumnSample, Land, Canvas, CanvasInfo, ColumnSample, Land,
}; };
use common::{ use common::{
generation::EntityInfo, generation::EntityInfo,
terrain::{ terrain::{
quadratic_nearest_point, river_spline_coeffs, Block, BlockKind, CoordinateConversions, quadratic_nearest_point, river_spline_coeffs, Block, BlockKind, CoordinateConversions,
SpriteKind, SpriteKind, TerrainChunkSize,
}, },
vol::RectVolSize,
}; };
use hashbrown::HashMap;
use inline_tweak::tweak_fn; use inline_tweak::tweak_fn;
use noise::NoiseFn; use noise::NoiseFn;
use rand::prelude::*; use rand::prelude::*;
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashMap,
f64::consts::PI, f64::consts::PI,
ops::{Add, Mul, Range, Sub}, ops::{Add, Mul, Range, Sub},
}; };
@ -39,6 +42,7 @@ fn to_wpos(cell: Vec2<i32>, level: u32) -> Vec2<i32> {
const AVG_LEVEL_DEPTH: i32 = 120; const AVG_LEVEL_DEPTH: i32 = 120;
const LAYERS: u32 = 4; const LAYERS: u32 = 4;
const MAX_RADIUS: f32 = 64.0;
fn node_at(cell: Vec2<i32>, level: u32, land: &Land) -> Option<Node> { fn node_at(cell: Vec2<i32>, level: u32, land: &Land) -> Option<Node> {
let rand = RandomField::new(37 + level); let rand = RandomField::new(37 + level);
@ -76,6 +80,7 @@ pub fn surface_entrances<'a>(land: &'a Land) -> impl Iterator<Item = Vec2<i32>>
.filter_map(|cell| Some(tunnel_below_from_cell(cell, 0, land)?.a.wpos)) .filter_map(|cell| Some(tunnel_below_from_cell(cell, 0, land)?.a.wpos))
} }
#[derive(Copy, Clone)]
pub struct Tunnel { pub struct Tunnel {
a: Node, a: Node,
b: Node, b: Node,
@ -83,6 +88,8 @@ pub struct Tunnel {
} }
impl Tunnel { impl Tunnel {
const RADIUS_RANGE: Range<f64> = 8.0..MAX_RADIUS as f64;
fn ctrl_offset(&self) -> Vec2<f32> { fn ctrl_offset(&self) -> Vec2<f32> {
let start = self.a.wpos.map(|e| e as f64 + 0.5); let start = self.a.wpos.map(|e| e as f64 + 0.5);
let end = self.b.wpos.map(|e| e as f64 + 0.5); let end = self.b.wpos.map(|e| e as f64 + 0.5);
@ -91,70 +98,77 @@ impl Tunnel {
.map(|e| e as f32) .map(|e| e as f32)
} }
#[tweak_fn] fn possibly_near(&self, wposf: Vec2<f64>, threshold: f64) -> Option<(f64, Vec2<f64>, f64)> {
fn z_range_at(
&self,
wposf: Vec2<f64>,
info: CanvasInfo,
) -> Option<(Range<i32>, f64, f64, f64)> {
let start = self.a.wpos.map(|e| e as f64 + 0.5); let start = self.a.wpos.map(|e| e as f64 + 0.5);
let end = self.b.wpos.map(|e| e as f64 + 0.5); let end = self.b.wpos.map(|e| e as f64 + 0.5);
if let Some((t, closest, _)) = quadratic_nearest_point( if let Some((t, closest, _)) = quadratic_nearest_point(
&river_spline_coeffs(start, self.ctrl_offset(), end), &river_spline_coeffs(start, self.ctrl_offset(), end),
wposf, wposf,
Vec2::new(start, end), Vec2::new(start, end),
) { ) {
let dist = closest.distance(wposf); let dist2 = closest.distance_squared(wposf);
let radius = 8.0..64.0; if dist2 < (Self::RADIUS_RANGE.end + threshold).powi(2) {
if dist < radius.end + 1.0 { Some((t, closest, dist2.sqrt()))
let horizontal = Lerp::lerp( } else {
radius.start, None
radius.end, }
(info.index().noise.cave_fbm_nz.get( } else {
(closest.with_z(info.land().get_alt_approx(self.a.wpos) as f64) / 256.0) None
.into_array(), }
) + 0.5) }
.clamped(0.0, 1.0)
.powf(3.0),
);
let vertical = Lerp::lerp(
radius.start,
radius.end,
(info.index().noise.cave_fbm_nz.get(
(closest.with_z(info.land().get_alt_approx(self.b.wpos) as f64) / 256.0)
.into_array(),
) + 0.5)
.clamped(0.0, 1.0)
.powf(3.0),
)
.clamped(radius.start + 0.0, radius.end);
let height_here = (1.0 - dist / horizontal).max(0.0).powf(0.3) * vertical;
if height_here > 0.0 { #[tweak_fn]
let z_offs = info fn z_range_at(
.index() &self,
.noise wposf: Vec2<f64>,
.cave_fbm_nz info: CanvasInfo,
.get((wposf / 512.0).into_array()) ) -> Option<(Range<i32>, f32, f32, f32)> {
* 96.0 let _start = self.a.wpos.map(|e| e as f64 + 0.5);
* ((1.0 - (t - 0.5).abs() * 2.0) * 8.0).min(1.0); let _end = self.b.wpos.map(|e| e as f64 + 0.5);
let alt_here = info.land().get_alt_approx(closest.map(|e| e as i32)); if let Some((t, closest, dist)) = self.possibly_near(wposf, 1.0) {
let base = (Lerp::lerp( let horizontal = Lerp::lerp(
alt_here as f64 - self.a.depth as f64, Self::RADIUS_RANGE.start,
alt_here as f64 - self.b.depth as f64, Self::RADIUS_RANGE.end,
t, (info.index().noise.cave_fbm_nz.get(
) + z_offs) (closest.with_z(info.land().get_alt_approx(self.a.wpos) as f64) / 256.0)
.min(alt_here as f64); .into_array(),
Some(( ) + 0.5)
(base - height_here * 0.3) as i32..(base + height_here * 1.35) as i32, .clamped(0.0, 1.0)
horizontal, .powf(3.0),
vertical, );
dist, let vertical = Lerp::lerp(
)) Self::RADIUS_RANGE.start,
} else { Self::RADIUS_RANGE.end,
None (info.index().noise.cave_fbm_nz.get(
} (closest.with_z(info.land().get_alt_approx(self.b.wpos) as f64) / 256.0)
.into_array(),
) + 0.5)
.clamped(0.0, 1.0)
.powf(3.0),
);
let height_here = (1.0 - dist / horizontal).max(0.0).powf(0.3) * vertical;
if height_here > 0.0 {
let z_offs = info
.index()
.noise
.cave_fbm_nz
.get((wposf / 512.0).into_array())
* 96.0
* ((1.0 - (t - 0.5).abs() * 2.0) * 8.0).min(1.0);
let alt_here = info.land().get_alt_approx(closest.map(|e| e as i32));
let base = (Lerp::lerp(
alt_here as f64 - self.a.depth as f64,
alt_here as f64 - self.b.depth as f64,
t,
) + z_offs)
.min(alt_here as f64);
Some((
(base - height_here * 0.3) as i32..(base + height_here * 1.35) as i32,
horizontal as f32,
vertical as f32,
dist as f32,
))
} else { } else {
None None
} }
@ -178,39 +192,34 @@ impl Tunnel {
// effectively increase biome size // effectively increase biome size
let humidity = Lerp::lerp( let humidity = Lerp::lerp(
col.humidity, col.humidity,
info.index() FastNoise2d::new(41)
.noise .get(wpos.xy().map(|e| e as f64 / 768.0))
.cave_nz .mul(1.2),
.get(wpos.xy().map(|e| e as f64 / 1024.0).into_array()) as f32,
below, below,
); );
let temp = Lerp::lerp( let temp = Lerp::lerp(
col.temp, col.temp,
info.index() FastNoise2d::new(42)
.noise .get(wpos.xy().map(|e| e as f64 / 1536.0))
.cave_nz .mul(1.15)
.get(wpos.xy().map(|e| e as f64 / 2048.0).into_array())
.mul(2.0) .mul(2.0)
.sub(1.0) .sub(1.0)
.add( .add(
((col.alt as f64 - wpos.z as f64) ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.5))
/ (AVG_LEVEL_DEPTH as f64 * LAYERS as f64 * 0.5))
.clamped(0.0, 2.5), .clamped(0.0, 2.5),
) as f32, ),
below, below,
); );
let mineral = info let mineral = FastNoise2d::new(43)
.index() .get(wpos.xy().map(|e| e as f64 / 320.0))
.noise .mul(1.15)
.cave_nz
.get(wpos.xy().map(|e| e as f64 / 512.0).into_array())
.mul(0.5) .mul(0.5)
.add( .add(
((col.alt as f64 - wpos.z as f64) / (AVG_LEVEL_DEPTH as f64 * LAYERS as f64)) ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32))
.clamped(0.0, 1.5), .clamped(0.0, 1.5),
) as f32; );
let [ let [
barren, barren,
@ -228,35 +237,37 @@ impl Tunnel {
// Mushrooms grow underground and thrive in a humid environment with moderate // Mushrooms grow underground and thrive in a humid environment with moderate
// temperatures // temperatures
let mushroom = underground let mushroom = underground
* close(humidity, 1.0, 0.70) * close(humidity, 1.0, 0.7)
* close(temp, 1.2, 1.0) * close(temp, 1.5, 0.9)
* close(depth, 1.0, 0.7); * close(depth, 1.0, 0.6);
// Extremely hot and dry areas deep underground // Extremely hot and dry areas deep underground
let fire = underground let fire = underground
* close(humidity, 0.0, 0.6) * close(humidity, 0.0, 0.6)
* close(temp, 2.5, 1.5) * close(temp, 2.5, 1.4)
* close(depth, 1.0, 0.55); * close(depth, 1.0, 0.5);
// Overgrown with plants that need a moderate climate to survive // Overgrown with plants that need a moderate climate to survive
let leafy = underground let leafy = underground
* close(humidity, 0.5, 0.7) * close(humidity, 0.8, 0.8)
* close(temp, 0.8, 0.7) * close(temp, 1.0, 0.8)
* close(depth, 0.0, 0.75); * close(depth, 0.0, 0.6);
// Cool temperature, dry and devoid of value // Cool temperature, dry and devoid of value
let dusty = let dusty = close(humidity, 0.0, 0.5) * close(temp, -0.1, 0.6);
close(humidity, 0.0, 0.5) * close(temp, -0.1, 0.6) * close(mineral, -1.0, 2.0);
// Deep underground and freezing cold // Deep underground and freezing cold
let icy = underground * close(temp, -1.0, 0.6) * close(depth, 1.0, 0.5); let icy = underground
* close(temp, -1.0, 0.6)
* close(depth, 1.0, 0.5)
* close(humidity, 1.0, 0.7);
// Rocky cold cave that appear near the surface // Rocky cold cave that appear near the surface
let snowy = close(temp, -0.5, 0.3) * close(depth, 0.0, 0.5); let snowy = close(temp, -0.5, 0.3) * close(depth, 0.0, 0.4);
// Crystals grow deep underground in areas rich with minerals. They are present // Crystals grow deep underground in areas rich with minerals. They are present
// in areas with colder temperatures and low humidity // in areas with colder temperatures and low humidity
let crystal = underground let crystal = underground
* close(humidity, 0.0, 0.65) * close(humidity, 0.0, 0.7)
* close(temp, -0.3, 0.9) * close(temp, -0.5, 0.8)
* close(depth, 1.0, 0.6) * close(depth, 1.0, 0.55)
* close(mineral, 1.5, 1.5); * close(mineral, 1.5, 1.0);
// Hot, dry and shallow // Hot, dry and shallow
let sandy = close(humidity, 0.0, 0.2) * close(temp, 0.7, 0.8) * close(depth, 0.1, 0.5); let sandy = close(humidity, 0.0, 0.3) * close(temp, 0.7, 0.9) * close(depth, 0.0, 0.6);
let biomes = [ let biomes = [
barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy, barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy,
@ -352,69 +363,109 @@ fn tunnels_down_from<'a>(
.filter_map(move |rpos| tunnel_below_from_cell(col_cell + rpos, level, land)) .filter_map(move |rpos| tunnel_below_from_cell(col_cell + rpos, level, land))
} }
fn all_tunnels_at<'a>(
wpos2d: Vec2<i32>,
_info: &'a CanvasInfo,
land: &'a Land,
) -> impl Iterator<Item = (u32, Tunnel)> + 'a {
(1..LAYERS + 1).flat_map(move |level| {
tunnels_at(wpos2d, level, land)
.chain(tunnels_down_from(wpos2d, level - 1, land))
.map(move |tunnel| (level, tunnel))
})
}
fn tunnel_bounds_at_from<'a>(
wpos2d: Vec2<i32>,
info: &'a CanvasInfo,
_land: &'a Land,
tunnels: impl Iterator<Item = (u32, Tunnel)> + 'a,
) -> impl Iterator<Item = (u32, Range<i32>, f32, f32, f32, Tunnel)> + 'a {
let wposf = wpos2d.map(|e| e as f64 + 0.5);
info.col_or_gen(wpos2d)
.map(move |col| {
let col_alt = col.alt;
let col_water_dist = col.water_dist;
tunnels.filter_map(move |(level, tunnel)| {
let (z_range, horizontal, vertical, dist) = tunnel.z_range_at(wposf, *info)?;
// Avoid cave entrances intersecting water
let z_range = Lerp::lerp(
z_range.end,
z_range.start,
1.0 - (1.0
- ((col_water_dist.unwrap_or(1000.0) - 4.0).max(0.0) / 32.0)
.clamped(0.0, 1.0))
* (1.0 - ((col_alt - z_range.end as f32 - 4.0) / 8.0).clamped(0.0, 1.0)),
)..z_range.end;
if z_range.end - z_range.start > 0 {
Some((level, z_range, horizontal, vertical, dist, tunnel))
} else {
None
}
})
})
.into_iter()
.flatten()
}
pub fn tunnel_bounds_at<'a>( pub fn tunnel_bounds_at<'a>(
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
info: &'a CanvasInfo, info: &'a CanvasInfo,
land: &'a Land, land: &'a Land,
) -> impl Iterator<Item = (u32, Range<i32>, f64, f64, f64, Tunnel)> + 'a { ) -> impl Iterator<Item = (u32, Range<i32>, f32, f32, f32, Tunnel)> + 'a {
let wposf = wpos2d.map(|e| e as f64 + 0.5); tunnel_bounds_at_from(wpos2d, info, land, all_tunnels_at(wpos2d, info, land))
info.col_or_gen(wpos2d).into_iter().flat_map(move |col| {
let col_alt = col.alt;
let col_water_dist = col.water_dist;
(1..LAYERS + 1).flat_map(move |level| {
tunnels_at(wpos2d, level, land)
.chain(tunnels_down_from(wpos2d, level - 1, land))
.filter_map(move |tunnel| {
let (z_range, horizontal, vertical, dist) = tunnel.z_range_at(wposf, *info)?;
// Avoid cave entrances intersecting water
let z_range = Lerp::lerp(
z_range.end,
z_range.start,
1.0 - (1.0
- ((col_water_dist.unwrap_or(1000.0) - 4.0).max(0.0) / 32.0)
.clamped(0.0, 1.0))
* (1.0
- ((col_alt - z_range.end as f32 - 4.0) / 8.0).clamped(0.0, 1.0)),
)..z_range.end;
if z_range.end - z_range.start > 0 {
Some((level, z_range, horizontal, vertical, dist, tunnel))
} else {
None
}
})
})
})
} }
pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
let info = canvas.info(); let info = canvas.info();
let mut structure_cache = HashMap::new(); let land = info.land();
canvas.foreach_col(|canvas, wpos2d, col| {
let land = info.land();
let tunnel_bounds = tunnel_bounds_at(wpos2d, &info, &land).collect::<Vec<_>>(); let diagonal = (TerrainChunkSize::RECT_SIZE.map(|e| e * e).sum() as f32).sqrt() as f64;
let tunnels = all_tunnels_at(
info.wpos() + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2,
&info,
&land,
)
.filter(|(_, tunnel)| {
SQUARE_4
.into_iter()
.map(|rpos| info.wpos() + rpos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
.any(|wpos| {
tunnel
.possibly_near(wpos.map(|e| e as f64), diagonal + 1.0)
.is_some()
})
})
.collect::<Vec<_>>();
if !tunnels.is_empty() {
let mut structure_cache = HashMap::new();
canvas.foreach_col(|canvas, wpos2d, col| {
let tunnel_bounds =
tunnel_bounds_at_from(wpos2d, &info, &land, tunnels.iter().copied())
.collect::<Vec<_>>();
// First, clear out tunnels // First, clear out tunnels
for (_, z_range, _, _, _, _) in &tunnel_bounds { for (_, z_range, _, _, _, _) in &tunnel_bounds {
for z in z_range.start..z_range.end.min(col.alt as i32 + 1) { for z in z_range.start..z_range.end.min(col.alt as i32 + 1) {
canvas.set(wpos2d.with_z(z), Block::air(SpriteKind::Empty)); canvas.set(wpos2d.with_z(z), Block::empty());
}
} }
}
for (level, z_range, horizontal, vertical, dist, tunnel) in tunnel_bounds { for (level, z_range, horizontal, vertical, dist, tunnel) in tunnel_bounds {
write_column( write_column(
canvas, canvas,
col, col,
level, level,
wpos2d, wpos2d,
z_range.clone(), z_range.clone(),
tunnel, tunnel,
(horizontal, vertical, dist), (horizontal, vertical, dist),
&mut structure_cache, &mut structure_cache,
rng, rng,
); );
} }
}); });
}
} }
#[derive(Default)] #[derive(Default)]
@ -479,7 +530,7 @@ fn write_column<R: Rng>(
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
z_range: Range<i32>, z_range: Range<i32>,
tunnel: Tunnel, tunnel: Tunnel,
dimensions: (f64, f64, f64), dimensions: (f32, f32, f32),
structure_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<CaveStructure>>, structure_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<CaveStructure>>,
rng: &mut R, rng: &mut R,
) { ) {
@ -490,23 +541,21 @@ fn write_column<R: Rng>(
let void_below = !canvas.get(wpos2d.with_z(z_range.start - 1)).is_filled(); let void_below = !canvas.get(wpos2d.with_z(z_range.start - 1)).is_filled();
// Exposed to the sky // Exposed to the sky
let sky_above = z_range.end as f32 > col.alt; let sky_above = z_range.end as f32 > col.alt;
let cavern_height = (z_range.end - z_range.start) as f64; let cavern_height = (z_range.end - z_range.start) as f32;
let (cave_width, max_height, dist_cave_center) = dimensions; let (cave_width, max_height, dist_cave_center) = dimensions;
let biome = tunnel.biome_at(wpos2d.with_z(z_range.start), &info); let biome = tunnel.biome_at(wpos2d.with_z(z_range.start), &info);
let stalactite = { let stalactite = {
info FastNoise2d::new(35)
.index() .get(wpos2d.map(|e| e as f64 / 8.0))
.noise .mul(1.0)
.cave_nz .sub(0.5 + (biome.leafy - 1.0).max(0.0))
.get(wpos2d.map(|e| e as f64 / 16.0).into_array())
.sub(0.5 + (biome.leafy - 1.0).max(0.0) as f64)
.max(0.0) .max(0.0)
.mul(2.0) .mul(2.0)
.add((biome.leafy - 1.0).max(0.0) as f64) .add((biome.leafy - 1.0).max(0.0))
// No stalactites near entrances // No stalactites near entrances
.mul(((col.alt as f64 - z_range.end as f64) / 8.0).clamped(0.0, 1.0)) .mul(((col.alt - z_range.end as f32) / 32.0).clamped(0.0, 1.0))
.mul(8.0 + cavern_height * (0.4 + (biome.sandy as f64 - 0.5).max(0.0))) .mul(4.0 + cavern_height * (0.4 + (biome.sandy - 0.5).max(0.0)))
}; };
let ceiling_cover = if (biome.leafy - 0.3) let ceiling_cover = if (biome.leafy - 0.3)
@ -516,8 +565,8 @@ fn write_column<R: Rng>(
.max(biome.fire - 0.5) .max(biome.fire - 0.5)
> 0.0 > 0.0
{ {
1.0.mul(((col.alt as f64 - z_range.end as f64) / 8.0).clamped(0.0, 1.0)) 1.0.mul(((col.alt - z_range.end as f32) / 32.0).clamped(0.0, 1.0))
.mul(cavern_height * (dist_cave_center / (cave_width)).powf(3.33)) .mul(cavern_height * (dist_cave_center / cave_width).powf(3.33))
.max(1.0) .max(1.0)
.sub( .sub(
if col.marble_mid if col.marble_mid
@ -528,7 +577,7 @@ fn write_column<R: Rng>(
.max(biome.leafy - 0.4) .max(biome.leafy - 0.4)
.max(biome.mushroom - 0.4) .max(biome.mushroom - 0.4)
{ {
cavern_height * col.marble_mid as f64 cavern_height * col.marble_mid
} else { } else {
0.0 0.0
}, },
@ -539,29 +588,26 @@ fn write_column<R: Rng>(
}; };
let basalt = if biome.fire > 0.0 { let basalt = if biome.fire > 0.0 {
info.index() FastNoise2d::new(36)
.noise .get(wpos2d.map(|e| e as f64 / 32.0))
.cave_nz .mul(1.25)
.get(wpos2d.map(|e| e as f64 / 48.0).into_array())
.sub(0.5) .sub(0.5)
.max(0.0) .max(0.0)
.mul(6.0 + cavern_height * 0.5) .mul(6.0 + cavern_height * 0.5)
.mul(biome.fire as f64) .mul(biome.fire)
} else { } else {
0.0 0.0
}; };
let lava = if biome.fire > 0.0 { let lava = if biome.fire > 0.0 {
info.index() FastNoise2d::new(37)
.noise .get(wpos2d.map(|e| e as f64 / 32.0))
.cave_nz .mul(0.5)
.get(wpos2d.map(|e| e as f64 / 64.0).into_array())
.sub(0.5)
.abs() .abs()
.sub(0.2) .sub(0.2)
.min(0.0) .min(0.0)
// .mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.0)) // .mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.0))
.mul((biome.fire as f64 - 0.5).mul(30.0).clamped(0.0, 1.0)) .mul((biome.fire - 0.5).mul(30.0).clamped(0.0, 1.0))
.mul(64.0) .mul(64.0)
.max(-32.0) .max(-32.0)
} else { } else {
@ -570,11 +616,9 @@ fn write_column<R: Rng>(
let height_factor = (max_height / 32.0).clamped(0.0, 1.0).powf(2.0); let height_factor = (max_height / 32.0).clamped(0.0, 1.0).powf(2.0);
let width_factor = (cave_width / 32.0).clamped(0.0, 1.0).powf(2.0); let width_factor = (cave_width / 32.0).clamped(0.0, 1.0).powf(2.0);
let ridge = info let ridge = FastNoise2d::new(38)
.index() .get(wpos2d.map(|e| e as f64 / 512.0))
.noise .mul(1.0)
.cave_nz
.get(wpos2d.map(|e| e as f64 / 512.0).into_array())
.sub(0.25) .sub(0.25)
.max(0.0) .max(0.0)
.mul(1.3) .mul(1.3)
@ -582,28 +626,22 @@ fn write_column<R: Rng>(
.mul(width_factor) .mul(width_factor)
.mul( .mul(
(0.75 * dist_cave_center) (0.75 * dist_cave_center)
+ max_height + max_height * (close(dist_cave_center, cave_width, cave_width * 0.7)),
* (close( )
dist_cave_center as f32, .mul(((col.alt - z_range.end as f32) / 64.0).clamped(0.0, 1.0));
cave_width as f32,
cave_width as f32 * 0.7,
)) as f64,
);
let bump = info let bump = FastNoise2d::new(39)
.index() .get(wpos2d.map(|e| e as f64 / 8.0))
.noise .mul(1.0)
.cave_nz
.get(wpos2d.map(|e| e as f64 / 8.0).into_array())
.add(1.0) .add(1.0)
.mul(0.5) .mul(0.5)
.mul(((col.alt as f64 - z_range.end as f64) / 16.0).clamped(0.0, 1.0)) .mul(((col.alt - z_range.end as f32) / 16.0).clamped(0.0, 1.0))
.mul({ .mul({
let (val, total) = [ let (val, total) = [
(biome.sandy as f64 - 0.3, 0.9), (biome.sandy - 0.3, 0.9),
(biome.dusty as f64 - 0.2, 0.5), (biome.dusty - 0.2, 0.5),
(biome.leafy as f64 - 0.5, 0.6), (biome.leafy - 0.5, 0.6),
(biome.barren as f64 - 0.1, 0.6), (biome.barren - 0.1, 0.6),
] ]
.into_iter() .into_iter()
.fold((0.0, 0.0), |a, x| (a.0 + x.0.max(0.0) * x.1, a.1 + x.1)); .fold((0.0, 0.0), |a, x| (a.0 + x.0.max(0.0) * x.1, a.1 + x.1));
@ -642,7 +680,7 @@ fn write_column<R: Rng>(
if biome.mushroom > 0.7 if biome.mushroom > 0.7
&& rng.gen_bool( && rng.gen_bool(
0.5 * close(vertical as f32, 64.0, 48.0) as f64 0.5 * close(vertical, MAX_RADIUS, 48.0) as f64
* close(biome.mushroom, 1.0, 0.7) as f64, * close(biome.mushroom, 1.0, 0.7) as f64,
) )
{ {
@ -660,7 +698,7 @@ fn write_column<R: Rng>(
), ),
})) }))
} else if biome.crystal > 0.5 } else if biome.crystal > 0.5
&& rng.gen_bool(0.6 * close(biome.crystal, 1.0, 0.7) as f64) && rng.gen_bool(0.4 * close(biome.crystal, 1.0, 0.7) as f64)
{ {
let colors = [ let colors = [
Rgb::new(209, 106, 255), Rgb::new(209, 106, 255),
@ -677,7 +715,7 @@ fn write_column<R: Rng>(
let mut crystals: Vec<Crystal> = Vec::new(); let mut crystals: Vec<Crystal> = Vec::new();
let max_length = (48.0 * close(vertical as f32, 64.0, 42.0)).max(12.0); let max_length = (48.0 * close(vertical, MAX_RADIUS, 42.0)).max(12.0);
let main_length = rng.gen_range(8.0..max_length); let main_length = rng.gen_range(8.0..max_length);
let main_radius = Lerp::lerp_unclamped( let main_radius = Lerp::lerp_unclamped(
2.0, 2.0,
@ -715,7 +753,7 @@ fn write_column<R: Rng>(
}); });
let mut color: Rgb<u8> = *(colors.choose(&mut rng).unwrap()); let mut color: Rgb<u8> = *(colors.choose(&mut rng).unwrap());
if tunnel.biome_at(pos, &info).icy > 0.125 { if tunnel.biome_at(pos, &info).icy > 0.5 {
color.r = color.r.saturating_sub(150u8); color.r = color.r.saturating_sub(150u8);
color.g = color.g.saturating_sub(40u8); color.g = color.g.saturating_sub(40u8);
color.b = color.b.saturating_add(0u8); color.b = color.b.saturating_add(0u8);
@ -727,19 +765,21 @@ fn write_column<R: Rng>(
})) }))
} else if biome.leafy > 0.8 } else if biome.leafy > 0.8
&& rng.gen_bool( && rng.gen_bool(
0.2 * close(vertical as f32, 64.0, 52.0) as f64 0.2 * (close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0)
* close(biome.leafy, 1.0, 0.2) as f64, * close(horizontal, MAX_RADIUS, MAX_RADIUS - 12.0)
* close(biome.leafy, 1.0, 0.2))
as f64,
) )
{ {
Some(CaveStructure::Flower(Flower { Some(CaveStructure::Flower(Flower {
pos, pos,
stalk: 8.0 stalk: 4.0
+ rng.gen::<f32>().powf(2.0) + rng.gen::<f32>().powf(2.0)
* (z_range.end - z_range.start - 8) as f32 * (z_range.end - z_range.start - 8) as f32
* 0.8, * 0.75,
petals: rng.gen_range(1..5) * 2 + 1, petals: rng.gen_range(1..5) * 2 + 1,
petal_height: rng.gen_range(4.0..8.0), petal_height: rng.gen_range(4.0..16.0),
petal_radius: rng.gen_range(10.0..18.0), petal_radius: rng.gen_range(8.0..16.0),
rotation: (Mat3::rotation_x( rotation: (Mat3::rotation_x(
-(rng.gen_bool(1.0) as u32 as f32) * std::f32::consts::PI -(rng.gen_bool(1.0) as u32 as f32) * std::f32::consts::PI
/ rng.gen_range(3.0..16.0), / rng.gen_range(3.0..16.0),
@ -748,15 +788,15 @@ fn write_column<R: Rng>(
)) ))
.transposed(), .transposed(),
})) }))
} else if biome.leafy > 0.5 } else if biome.leafy > 0.8
&& rng.gen_bool(0.6 * close(biome.leafy, 1.0, 0.5) as f64) && rng.gen_bool(0.4 * close(biome.leafy, 1.0, 0.4) as f64)
{ {
Some(CaveStructure::GiantRoot { Some(CaveStructure::GiantRoot {
pos, pos,
radius: rng.gen_range( radius: rng.gen_range(
3.0..(6.0 2.0..(6.0
+ close(vertical as f32, 64.0, 32.0) * 2.0 + close(vertical, MAX_RADIUS, MAX_RADIUS / 2.0) * 2.0
+ close(horizontal as f32, 64.0, 32.0) * 2.0), + close(horizontal, MAX_RADIUS, MAX_RADIUS / 2.0) * 2.0),
), ),
height: (z_range.end - z_range.start) as f32, height: (z_range.end - z_range.start) as f32,
}) })
@ -1018,7 +1058,8 @@ fn write_column<R: Rng>(
let mold_pos = mold_posf.map(|e| e.floor() as i32); let mold_pos = mold_posf.map(|e| e.floor() as i32);
let mut rng = RandomPerm::new(((mold_pos.x << 16) | mold_pos.y) as u32); let mut rng = RandomPerm::new(((mold_pos.x << 16) | mold_pos.y) as u32);
if biome.mushroom > 0.7 if !void_above
&& biome.mushroom > 0.7
&& ceiling_cover > 0.0 && ceiling_cover > 0.0
&& rng.gen_bool(0.025 * close(biome.mushroom, 1.0, 0.3) as f64) && rng.gen_bool(0.025 * close(biome.mushroom, 1.0, 0.3) as f64)
{ {
@ -1092,9 +1133,7 @@ fn write_column<R: Rng>(
biome.icy, biome.icy,
); );
Block::new( Block::new(
if rand.chance(wpos, biome.mushroom * 0.01) { if rand.chance(wpos, (biome.mushroom * 0.01).max(biome.icy)) {
BlockKind::GlowingWeakRock
} else if rand.chance(wpos, biome.icy) {
BlockKind::GlowingWeakRock BlockKind::GlowingWeakRock
} else if rand.chance(wpos, biome.sandy) { } else if rand.chance(wpos, biome.sandy) {
BlockKind::Sand BlockKind::Sand
@ -1382,10 +1421,10 @@ fn write_column<R: Rng>(
.flatten() .flatten()
{ {
Block::air(sprite) Block::air(sprite)
} else if let Some(vine) = } else if let Some(mold) =
ceiling_mold(wposf).or_else(|| ceiling_mold(wposf.xy().yx().with_z(wposf.z))) ceiling_mold(wposf).or_else(|| ceiling_mold(wposf.xy().yx().with_z(wposf.z)))
{ {
vine mold
} else if let Some(structure_block) = get_structure(wpos, rng) { } else if let Some(structure_block) = get_structure(wpos, rng) {
structure_block structure_block
} else { } else {