Various changes to crystal generation and WIP flowers

This commit is contained in:
Syniis 2024-02-09 03:53:40 +01:00
parent ec310efb3c
commit 148eef4805

View File

@ -11,7 +11,7 @@ use common::{
SpriteKind, SpriteKind,
}, },
}; };
use inline_tweak::{tweak, tweak_fn}; use inline_tweak::tweak_fn;
use noise::NoiseFn; use noise::NoiseFn;
use rand::prelude::*; use rand::prelude::*;
use std::{ use std::{
@ -124,7 +124,7 @@ impl Tunnel {
.clamped(0.0, 1.0) .clamped(0.0, 1.0)
.powf(3.0), .powf(3.0),
) )
.clamped(radius.start + 4.0, radius.end); .clamped(radius.start + 0.0, radius.end);
let height_here = (1.0 - dist / horizontal).max(0.0).powf(0.3) * vertical; let height_here = (1.0 - dist / horizontal).max(0.0).powf(0.3) * vertical;
if height_here > 0.0 { if height_here > 0.0 {
@ -215,10 +215,10 @@ impl Tunnel {
close(humidity, 0.0, 0.2) * close(temp, -0.4, 0.5) * close(mineral, 0.0, 0.6); close(humidity, 0.0, 0.2) * close(temp, -0.4, 0.5) * close(mineral, 0.0, 0.6);
let icy = close(temp, -1.0, 0.3); let icy = close(temp, -1.0, 0.3);
let crystal = underground let crystal = underground
* (close(humidity, 0.0, 0.5) + close(humidity, 1.0, 0.5)) * (close(humidity, 0.0, 0.5).max(close(humidity, 1.0, 0.5)))
* (close(temp, 1.5, 0.8) + close(temp, -1.0, 0.3)) * (close(temp, 1.6, 0.9).max(close(temp, -1.0, 0.3)))
* close(depth, 1.0, 0.6) * close(depth, 1.0, 0.6)
* close(mineral, 2.5, 1.8); * close(mineral, 2.5, 2.0);
let sandy = close(humidity, 0.1, 0.2) * close(temp, 0.0, 0.4) * close(depth, 0.4, 0.4); let sandy = close(humidity, 0.1, 0.2) * close(temp, 0.0, 0.4) * close(depth, 0.4, 0.4);
let biomes = [barren, mushroom, fire, leafy, dusty, icy, crystal, sandy]; let biomes = [barren, mushroom, fire, leafy, dusty, icy, crystal, sandy];
@ -350,6 +350,7 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
let info = canvas.info(); let info = canvas.info();
let mut mushroom_cache = HashMap::new(); let mut mushroom_cache = HashMap::new();
let mut crystal_cache = HashMap::new(); let mut crystal_cache = HashMap::new();
let mut flower_cache = HashMap::new();
canvas.foreach_col(|canvas, wpos2d, col| { canvas.foreach_col(|canvas, wpos2d, col| {
let land = info.land(); let land = info.land();
@ -372,6 +373,7 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
tunnel, tunnel,
&mut mushroom_cache, &mut mushroom_cache,
&mut crystal_cache, &mut crystal_cache,
&mut flower_cache,
rng, rng,
); );
} }
@ -400,16 +402,23 @@ struct Mushroom {
} }
struct Crystal { struct Crystal {
dir: Vec3<f64>, dir: Vec3<f32>,
length: f64, length: f32,
radius: f64, radius: f32,
} }
struct CrystalCluster { struct CrystalCluster {
pos: Vec3<i32>, pos: Vec3<i32>,
crystals: Vec<Crystal>, crystals: Vec<Crystal>,
color: Rgb<u8>, color: Rgb<u8>,
} }
struct Flower {
pos: Vec3<i32>,
stalk: f32,
petals: usize,
}
#[inline_tweak::tweak_fn] #[inline_tweak::tweak_fn]
fn write_column<R: Rng>( fn write_column<R: Rng>(
canvas: &mut Canvas, canvas: &mut Canvas,
@ -420,6 +429,7 @@ fn write_column<R: Rng>(
tunnel: Tunnel, tunnel: Tunnel,
mushroom_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<Mushroom>>, mushroom_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<Mushroom>>,
crystal_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<CrystalCluster>>, crystal_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<CrystalCluster>>,
flower_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<Flower>>,
rng: &mut R, rng: &mut R,
) { ) {
mushroom_cache.clear(); mushroom_cache.clear();
@ -526,7 +536,7 @@ fn write_column<R: Rng>(
let (z_range, radius) = let (z_range, radius) =
tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?; tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?;
let pos = wpos2d.with_z(z_range.start); let pos = wpos2d.with_z(z_range.start);
if rng.gen_bool(0.5 * close(radius as f32, 64.0, 48.0) as f64) if rng.gen_bool(0.5* close(radius as f32, 64.0, 48.0) as f64)
&& tunnel.biome_at(pos, &info).mushroom > 0.5 && tunnel.biome_at(pos, &info).mushroom > 0.5
// Ensure that we're not placing the mushroom over a void // Ensure that we're not placing the mushroom over a void
&& !tunnel_bounds_at(pos.xy(), &info, &info.land()) && !tunnel_bounds_at(pos.xy(), &info, &info.land())
@ -632,7 +642,7 @@ fn write_column<R: Rng>(
Rgb::new(243, 204, 255), Rgb::new(243, 204, 255),
Rgb::new(231, 154, 255), Rgb::new(231, 154, 255),
]; ];
for (wpos2d, seed) in StructureGen2d::new(112358, 32, 6).get(wpos.xy()) { for (wpos2d, seed) in StructureGen2d::new(112358, 20, 8).get(wpos.xy()) {
let cluster = if let Some(crystal) = crystal_cache let cluster = if let Some(crystal) = crystal_cache
.entry((tunnel.a.wpos.with_z(tunnel.a.depth), wpos2d)) .entry((tunnel.a.wpos.with_z(tunnel.a.depth), wpos2d))
.or_insert_with(|| { .or_insert_with(|| {
@ -647,23 +657,24 @@ fn write_column<R: Rng>(
z_range.end z_range.end
}); });
if rng if rng.gen_bool(
.gen_bool(0.8 * close(tunnel.biome_at(pos, &info).crystal, 1.0, 0.5) as f64) 0.65 * close(tunnel.biome_at(pos, &info).crystal, 1.0, 0.7) as f64,
&& !tunnel_bounds_at(pos.xy(), &info, &info.land()) ) && !tunnel_bounds_at(pos.xy(), &info, &info.land())
.any(|(_, z_range, _, _)| z_range.contains(&(z_range.start - 1))) .any(|(_, z_range, _, _)| z_range.contains(&(z_range.start - 1)))
{ {
let mut crystals: Vec<Crystal> = Vec::new(); let mut crystals: Vec<Crystal> = Vec::new();
let max_length = 32.0; let max_length = (32.0 * close(radius as f32, 64.0, 52.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( let main_radius = Lerp::lerp_unclamped(
2.0, 2.0,
4.5, 4.5,
main_length / max_length + rng.gen_range(-0.2..0.2), main_length / max_length + rng.gen_range(-0.1..0.1),
); );
let main_dir = Vec3::new( let main_dir = Vec3::new(
rng.gen_range(tweak!(-3.0)..tweak!(3.0)), rng.gen_range(-3.0..3.0),
rng.gen_range(tweak!(-3.0)..tweak!(3.0)), rng.gen_range(-3.0..3.0),
rng.gen_range(0.0..10.0) * if on_ground { 1.0 } else { -1.0 }, rng.gen_range(0.0..10.0) * if on_ground { 1.0 } else { -1.0 },
) )
.normalized(); .normalized();
@ -675,19 +686,15 @@ fn write_column<R: Rng>(
}; };
crystals.push(main_crystal); crystals.push(main_crystal);
(0..12).for_each(|_| { (0..4).for_each(|_| {
let side_radius = main_radius * rng.gen_range(tweak!(0.5)..tweak!(0.8)); let side_radius = (main_radius * rng.gen_range(0.5..0.8)).max(1.0);
let side_length = main_length * rng.gen_range(tweak!(0.3)..tweak!(0.8)); let side_length = main_length * rng.gen_range(0.3..0.8);
if side_radius < 1.5 {
return;
}
let side_crystal = Crystal { let side_crystal = Crystal {
dir: Vec3::new( dir: Vec3::new(
rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0),
rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0),
main_dir.z + rng.gen_range(-0.2..0.2), (main_dir.z + rng.gen_range(-0.2..0.2)).clamped(0.0, 1.0),
) ),
.normalized(),
length: side_length, length: side_length,
radius: side_radius, radius: side_radius,
}; };
@ -713,50 +720,136 @@ fn write_column<R: Rng>(
} else { } else {
continue; continue;
}; };
let wposf = wpos.map(|e| e as f64); let wposf = wpos.map(|e| e as f32);
let cluster_pos = cluster.pos.map(|e| e as f64); let cluster_pos = cluster.pos.map(|e| e as f32);
for crystal in &cluster.crystals { for crystal in &cluster.crystals {
let rpos = wposf - cluster_pos;
let line = LineSegment3 { let line = LineSegment3 {
start: cluster_pos, start: cluster_pos,
end: cluster_pos + crystal.dir * crystal.length, end: cluster_pos + crystal.dir * crystal.length,
}; };
let line_length = line.start.distance_squared(line.end);
let taper = if line_length < 0.001 {
0.0
} else {
rpos.dot(line.end - line.start) / line_length
};
let projected = line.projected_point(wposf); let projected = line.projected_point(wposf);
let dist_sq = projected.distance_squared(wposf); let dist_sq = projected.distance_squared(wposf);
if dist_sq < crystal.radius.powi(2) {
let rpos = wposf - cluster_pos;
let line_length = line.start.distance_squared(line.end);
let taper = if line_length < 0.001 {
0.0
} else {
rpos.dot(line.end - line.start) / line_length
};
let extreme_taper_cutoff = 0.8; let peak_cutoff = 0.8;
let taper_factor = 0.8; let taper_factor = 0.7;
let extreme_taper_factor = 0.4; let peak_taper = 0.3;
let crystal_radius = if taper > extreme_taper_cutoff { let crystal_radius = if taper > peak_cutoff {
let taper = let taper = (taper - peak_cutoff) * 5.0;
(taper - extreme_taper_cutoff) * (1.0 - extreme_taper_cutoff).recip(); Lerp::lerp(
Lerp::lerp( crystal.radius * taper_factor,
crystal.radius * taper_factor, crystal.radius * peak_taper,
crystal.radius * extreme_taper_factor, taper,
taper, )
) } else {
} else { let taper = taper * 1.25;
let taper = taper * extreme_taper_cutoff.recip(); Lerp::lerp(crystal.radius, crystal.radius * taper_factor, taper)
Lerp::lerp(crystal.radius, crystal.radius * taper_factor, taper) };
};
if dist_sq < crystal_radius.powi(2) { if dist_sq < crystal_radius.powi(2) {
return Some(Block::new(BlockKind::GlowingRock, cluster.color)); return Some(Block::new(BlockKind::GlowingRock, cluster.color));
}
} }
} }
} }
None None
}; };
let mut get_flower = |wpos: Vec3<i32>| {
for (wpos2d, seed) in StructureGen2d::new(1602, 32, 8).get(wpos.xy()) {
let flower = if let Some(flower) = flower_cache
.entry((tunnel.a.wpos.with_z(tunnel.a.depth), wpos2d))
.or_insert_with(|| {
let mut rng = RandomPerm::new(seed);
let (z_range, radius) =
tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?;
let pos = wpos2d.with_z(z_range.start);
if rng.gen_bool(0.5 * close(radius as f32, 64.0, 48.0) as f64)
&& tunnel.biome_at(pos, &info).leafy > 0.9
&& !tunnel_bounds_at(pos.xy(), &info, &info.land())
.any(|(_, z_range, _, _)| z_range.contains(&(z_range.start - 1)))
{
Some(Flower {
pos,
stalk: 8.0
+ rng.gen::<f32>().powf(2.0)
* (z_range.end - z_range.start - 8) as f32
* 0.75,
petals: rng.gen_range(1..5) * 2 + 1,
})
} else {
None
}
}) {
flower
} else {
continue;
};
let wposf = wpos.map(|e| e as f64);
let warp_freq = 1.0 / 32.0;
let warp_amp = Vec3::new(3.0, 3.0, 3.0);
let wposf_warped = wposf.map(|e| e as f32)
+ Vec3::new(
FastNoise::new(seed).get(wposf * warp_freq),
FastNoise::new(seed + 1).get(wposf * warp_freq),
FastNoise::new(seed + 2).get(wposf * warp_freq),
) * warp_amp
* (wposf.z as f32 - flower.pos.z as f32)
.mul(0.1)
.clamped(0.0, 1.0);
let rpos = wposf_warped - flower.pos.map(|e| e as f32);
let stalk_radius = 2.5f32;
let petal_radius = 12.0f32;
let petal_height = 8.0;
let petal_thickness = 3.0;
let dist = rpos.xy().magnitude_squared();
let petal_radius = petal_radius.powi(2);
if dist < petal_radius {
let petal_height_at = (dist / petal_radius).powf(0.5) * petal_height;
if rpos.z > flower.stalk + petal_height_at
&& rpos.z <= flower.stalk + petal_height_at + petal_thickness
{
let away = dist / petal_radius;
if away > 0.2 {
let near = (rpos.x.atan2(rpos.y))
.rem_euclid(std::f32::consts::TAU / flower.petals as f32);
let inset = close(near, -1.0, 0.85).max(close(near, 1.0, 0.85));
if dist / petal_radius < inset {
return Some(Block::new(BlockKind::Wood, Rgb::new(240, 50, 50)));
}
} else {
return Some(Block::new(BlockKind::Wood, Rgb::new(240, 50, 50)));
}
}
if rpos.z <= flower.stalk + 3.0
&& dist < (stalk_radius * Lerp::lerp(1.5, 0.75, rpos.z / flower.stalk)).powi(2)
{
if flower.stalk - rpos.z - 0.0 + 0.0 >= 0.0 {
return Some(Block::new(BlockKind::Wood, Rgb::new(40, 244, 150)));
}
// Stalk
return Some(Block::new(BlockKind::Wood, Rgb::new(150, 244, 40)));
}
}
}
None
};
for z in bedrock..z_range.end { for z in bedrock..z_range.end {
let wpos = wpos2d.with_z(z); let wpos = wpos2d.with_z(z);
let mut try_spawn_entity = false; let mut try_spawn_entity = false;
@ -841,7 +934,7 @@ fn write_column<R: Rng>(
Lerp::lerp( Lerp::lerp(
Rgb::new(105, 25, 131), Rgb::new(105, 25, 131),
Rgb::new(251, 238, 255), Rgb::new(251, 238, 255),
col.marble_mid, col.marble_small,
), ),
biome.crystal, biome.crystal,
), ),
@ -1039,6 +1132,8 @@ fn write_column<R: Rng>(
Block::air(sprite) Block::air(sprite)
} else if let Some(block) = is_crystal(wpos) { } else if let Some(block) = is_crystal(wpos) {
block block
} else if let Some(block) = get_flower(wpos) {
block
} else { } else {
get_mushroom(wpos, rng).unwrap_or(Block::air(SpriteKind::Empty)) get_mushroom(wpos, rng).unwrap_or(Block::air(SpriteKind::Empty))
} }