Merge branch 'zesterer/shrubs' into 'master'

Shrubs (and water fixes)

See merge request veloren/veloren!2796
This commit is contained in:
Joshua Barretto 2021-10-04 23:24:56 +00:00
commit ecc7fda80d
27 changed files with 1164 additions and 882 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

@ -47,4 +47,26 @@
central: ("air_balloon.rudder"),
),
),
SailBoat: (
bone0: (
offset: (-6.5, -15.5, 0.0),
phys_offset: (0.0, 0.0, 0.0),
central: ("sail_boat.structure"),
),
bone1: (
offset: (0.0, 0.0, 0.0),
phys_offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
bone2: (
offset: (0.0, 0.0, 0.0),
phys_offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
bone3: (
offset: (-1.5, -6.0, -5.0),
phys_offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
),
})

Binary file not shown.

View File

@ -204,7 +204,7 @@ void main() {
const float R_s2s1 = pow((1.0 - 1.3325) / (1.0 + 1.3325), 2);
const float R_s1s2 = pow((1.3325 - 1.0) / (1.3325 + 1.0), 2);
// float faces_fluid = faces_fluid && f_pos.z <= floor(f_alt);
float fluid_alt = max(f_pos.z + 1, floor(f_alt));
float fluid_alt = max(f_pos.z + 1, floor(f_alt + 1));
float R_s = /*(f_pos.z < f_alt)*/faces_fluid /*&& f_pos.z <= fluid_alt*/ ? mix(R_s2s1 * R_s1s0, R_s1s0, medium.x) : mix(R_s2s0, R_s1s2 * R_s2s0, medium.x);
// vec3 surf_color = /*srgb_to_linear*/(f_col);

View File

@ -0,0 +1,32 @@
#![enable(unwrap_newtypes)]
[
(
specifier: "world.shrub.1",
center: (6, 6, 2),
),
(
specifier: "world.shrub.jungle.bush-0",
center: (5, 5, 3),
),
(
specifier: "world.shrub.jungle.bush-1",
center: (5, 5, 2),
),
(
specifier: "world.shrub.jungle.bush-2",
center: (5, 5, 3),
),
(
specifier: "world.shrub.jungle.bush-3",
center: (5, 5, 3),
),
(
specifier: "world.shrub.jungle.bush-4",
center: (5, 5, 4),
),
(
specifier: "world.shrub.jungle.bush-5",
center: (5, 5, 5),
),
]

BIN
assets/world/shrub/1.vox Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -854,6 +854,7 @@ impl Body {
Body::Ship(ship) => match ship {
ship::Body::DefaultAirship => [0.0, 0.0, 10.0],
ship::Body::AirBalloon => [0.0, 0.0, 5.0],
ship::Body::SailBoat => [-2.0, -5.0, 4.0],
},
_ => [0.0, 0.0, 0.0],
}

View File

@ -1,13 +1,13 @@
use crate::{
comp::{Density, Mass},
consts::AIR_DENSITY,
consts::{AIR_DENSITY, WATER_DENSITY},
make_case_elim,
};
use rand::prelude::SliceRandom;
use serde::{Deserialize, Serialize};
use vek::Vec3;
pub const ALL_BODIES: [Body; 2] = [Body::DefaultAirship, Body::AirBalloon];
pub const ALL_BODIES: [Body; 3] = [Body::DefaultAirship, Body::AirBalloon, Body::SailBoat];
make_case_elim!(
body,
@ -16,6 +16,7 @@ make_case_elim!(
pub enum Body {
DefaultAirship = 0,
AirBalloon = 1,
SailBoat = 2,
}
);
@ -35,6 +36,7 @@ impl Body {
match self {
Body::DefaultAirship => "airship_human.structure",
Body::AirBalloon => "air_balloon.structure",
Body::SailBoat => "sail_boat.structure",
}
}
@ -42,15 +44,21 @@ impl Body {
match self {
Body::DefaultAirship => Vec3::new(25.0, 50.0, 40.0),
Body::AirBalloon => Vec3::new(25.0, 50.0, 40.0),
Body::SailBoat => Vec3::new(13.0, 31.0, 3.0),
}
}
fn balloon_vol(&self) -> f32 {
let spheroid_vol = |equat_d: f32, polar_d: f32| -> f32 {
(std::f32::consts::PI / 6.0) * equat_d.powi(2) * polar_d
};
let dim = self.dimensions();
spheroid_vol(dim.z, dim.y)
match self {
Body::DefaultAirship | Body::AirBalloon => {
let spheroid_vol = |equat_d: f32, polar_d: f32| -> f32 {
(std::f32::consts::PI / 6.0) * equat_d.powi(2) * polar_d
};
let dim = self.dimensions();
spheroid_vol(dim.z, dim.y)
},
_ => 0.0,
}
}
fn hull_vol(&self) -> f32 {
@ -66,14 +74,19 @@ impl Body {
Density(ratio * oak_density + (1.0 - ratio) * AIR_DENSITY)
}
pub fn density(&self) -> Density { Density(AIR_DENSITY) }
pub fn density(&self) -> Density {
match self {
Body::DefaultAirship | Body::AirBalloon => Density(AIR_DENSITY),
_ => Density(AIR_DENSITY * 0.8 + WATER_DENSITY * 0.2), // Most boats should be buoyant
}
}
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,
}
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
}
}

View File

@ -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<f32> {
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,6 +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(1500.0 * self.mass().0),
Body::Ship(_) => None,
}
}

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)
{
quadratic_nearest_point(
&coeffs,
wposf,
Vec2::new(neighbor_wpos, downhill_wpos),
) {
(t, pt, dist)
} else {
let ndist = wposf.distance_squared(neighbor_wpos);

View File

@ -293,7 +293,53 @@ pub fn river_spline_coeffs(
pub fn quadratic_nearest_point(
spline: &Vec3<Vec2<f64>>,
point: Vec2<f64>,
_line: Vec2<Vec2<f64>>, // Used for alternative distance functions below
) -> Option<(f64, Vec2<f64>, f64)> {
//let eval_at = |t: f64| spline.x * t * t + spline.y * t + spline.z;
// Linear
// let line = LineSegment2 {
// start: line.x,
// 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)));
// Quadratic
// let curve = QuadraticBezier2 {
// start: line.x,
// ctrl: eval_at(0.5),
// end: line.y,
// };
// let (t, pos) = curve.binary_search_point_by_steps(point, 16, 0.001);
// let t = t.clamped(0.0, 1.0);
// let pos = curve.evaluate(t);
// return Some((t, pos, pos.distance_squared(point)));
// Cubic
// let ctrl_at = |t: f64, end: f64| {
// let a = eval_at(end);
// let b = eval_at(Lerp::lerp(end, t, 0.1));
// let dir = (b - a).normalized();
// let exact = eval_at(t);
// a + dir * exact.distance(a)
// };
// let curve = CubicBezier2 {
// start: line.x,
// ctrl0: ctrl_at(0.33, 0.0),
// ctrl1: ctrl_at(0.66, 1.0),
// end: line.y,
// };
// let (t, pos) = curve.binary_search_point_by_steps(point, 12, 0.01);
// let t = t.clamped(0.0, 1.0);
// let pos = curve.evaluate(t);
// return Some((t, pos, pos.distance_squared(point)));
let a = spline.z.x;
let b = spline.y.x;
let c = spline.x.x;
@ -328,18 +374,14 @@ pub fn quadratic_nearest_point(
let min_root = roots
.iter()
.copied()
.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))
} 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))
(root, river_point)
} else {
None
let root = root.clamped(0.0, 1.0);
let river_point = spline.x * root * root + spline.y * root + spline.z;
(root, river_point)
}
})
.map(|(root, river_point)| {

View File

@ -31,7 +31,9 @@ impl Skeleton for ShipSkeleton {
buf: &mut [FigureBoneData; super::MAX_BONE_COUNT],
body: Self::Body,
) -> Offsets {
let bone0_mat = base_mat * Mat4::scaling_3d(1.0 / 11.0) * Mat4::<f32>::from(self.bone0);
let scale_mat = Mat4::scaling_3d(1.0 / 11.0);
let bone0_mat = base_mat * scale_mat * Mat4::<f32>::from(self.bone0);
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
make_bone(bone0_mat),
@ -43,10 +45,12 @@ impl Skeleton for ShipSkeleton {
lantern: None,
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::Ship(body)
.mountee_offset()
.into_tuple()
.into(),
position: (base_mat * scale_mat).mul_point(
common::comp::Body::Ship(body)
.mountee_offset()
.into_tuple()
.into(),
),
..Default::default()
},
}
@ -89,18 +93,22 @@ impl<'a> From<&'a Body> for SkeletonAttr {
bone0: match body {
DefaultAirship => (0.0, 0.0, 0.0),
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
},
bone1: match body {
DefaultAirship => (-13.0, -25.0, 10.0),
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
},
bone2: match body {
DefaultAirship => (13.0, -25.0, 10.0),
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
},
bone3: match body {
DefaultAirship => (0.0, -27.5, 8.5),
AirBalloon => (0.0, -9.0, 8.0),
SailBoat => (0.0, 0.0, 0.0),
},
}
}

View File

@ -35,7 +35,7 @@ impl ForestKind {
ForestKind::Cedar => 0.275..1.45,
ForestKind::Pine => 0.2..1.4,
ForestKind::Birch => 0.0..0.6,
ForestKind::Mangrove => 0.65..1.3,
ForestKind::Mangrove => 0.5..1.3,
ForestKind::Swamp => 0.5..1.1,
_ => 0.0..0.0,
}
@ -51,7 +51,7 @@ impl ForestKind {
ForestKind::Cedar => -0.65..0.15,
ForestKind::Pine => -1.8..-0.2,
ForestKind::Birch => -0.7..0.25,
ForestKind::Mangrove => 0.4..1.6,
ForestKind::Mangrove => 0.35..1.6,
ForestKind::Swamp => -0.6..0.8,
_ => 0.0..0.0,
}

View File

@ -147,23 +147,27 @@ impl<'a> BlockGen<'a> {
} else {
Some(Block::new(BlockKind::Earth, col))
}
} else if (wposf.z as f32) < height {
} else if wposf.z as i32 <= height as i32 {
let grass_factor = (wposf.z as f32 - (height - grass_depth))
.div(grass_depth)
.sqrt();
let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor);
// Surface
Some(Block::new(
if snow_cover {
Some(if water_level > height.ceil() {
Block::new(
BlockKind::Sand,
sub_surface_color.map(|e| (e * 255.0) as u8),
)
} else {
let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor);
if grass_factor < 0.7 {
Block::new(BlockKind::Earth, col.map(|e| (e * 255.0) as u8))
} else if snow_cover {
//if temp < CONFIG.snow_temp + 0.031 {
BlockKind::Snow
} else if grass_factor > 0.7 {
BlockKind::Grass
Block::new(BlockKind::Snow, col.map(|e| (e * 255.0) as u8))
} else {
BlockKind::Earth
},
col.map(|e| (e * 255.0) as u8),
))
Block::new(BlockKind::Grass, col.map(|e| (e * 255.0) as u8))
}
})
} else {
None
}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,12 @@
pub mod scatter;
pub mod shrub;
pub mod spot;
pub mod tree;
pub mod wildlife;
pub use self::{scatter::apply_scatter_to, spot::apply_spots_to, tree::apply_trees_to};
pub use self::{
scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to, tree::apply_trees_to,
};
use crate::{
column::ColumnSample,

View File

@ -9,7 +9,8 @@ fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.125)
}
const MUSH_FACT: f32 = 1.0e-4; // To balance everything around the mushroom spawning rate
const MUSH_FACT: f32 = 1.0e-4; // To balance things around the mushroom spawning rate
const GRASS_FACT: f32 = 1.0e-3; // To balance things around the grass spawning rate
const DEPTH_WATER_NORM: f32 = 15.0; // Water depth at which regular underwater sprites start spawning
pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
use SpriteKind::*;
@ -18,132 +19,132 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
let scatter: &[(
_,
bool,
fn(&SimChunk, &ColumnSample) -> (f32, Option<(f32, f32)>),
fn(&SimChunk, &ColumnSample) -> (f32, Option<(f32, f32, f32)>),
)] = &[
// (density, Option<(wavelen, threshold)>)
// (density, Option<(base_density_proportion, wavelen, threshold)>)
// Flowers
(BlueFlower, false, |c, col| {
(BlueFlower, false, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.7).min(close(
c.humidity,
close(col.temp, CONFIG.temperate_temp, 0.7).min(close(
col.humidity,
CONFIG.jungle_hum,
0.4,
)) * col.tree_density
* MUSH_FACT
* 256.0,
Some((256.0, 0.25)),
Some((0.0, 256.0, 0.25)),
)
}),
(PinkFlower, false, |c, col| {
(PinkFlower, false, |_, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
Some((0.0, 100.0, 0.1)),
)
}),
(PurpleFlower, false, |c, col| {
(PurpleFlower, false, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.7).min(close(
c.humidity,
close(col.temp, CONFIG.temperate_temp, 0.7).min(close(
col.humidity,
CONFIG.jungle_hum,
0.4,
)) * col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
Some((0.0, 100.0, 0.1)),
)
}),
(RedFlower, false, |c, col| {
(RedFlower, false, |_, col| {
(
close(c.temp, CONFIG.tropical_temp, 0.7).min(close(
c.humidity,
close(col.temp, CONFIG.tropical_temp, 0.7).min(close(
col.humidity,
CONFIG.jungle_hum,
0.4,
)) * col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
Some((0.0, 100.0, 0.1)),
)
}),
(WhiteFlower, false, |c, col| {
(WhiteFlower, false, |_, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
Some((0.0, 100.0, 0.1)),
)
}),
(YellowFlower, false, |c, col| {
(YellowFlower, false, |_, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
Some((0.0, 100.0, 0.1)),
)
}),
(Cotton, false, |c, col| {
(Cotton, false, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.7).min(close(
c.humidity,
close(col.temp, CONFIG.temperate_temp, 0.7).min(close(
col.humidity,
CONFIG.jungle_hum,
0.4,
)) * col.tree_density
* MUSH_FACT
* 75.0,
Some((256.0, 0.25)),
Some((0.0, 256.0, 0.25)),
)
}),
(Sunflower, false, |c, col| {
(Sunflower, false, |_, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.15)),
Some((0.0, 100.0, 0.15)),
)
}),
(WildFlax, false, |c, col| {
(WildFlax, false, |_, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 600.0,
Some((100.0, 0.15)),
Some((0.0, 100.0, 0.15)),
)
}),
// Herbs and Spices
(LingonBerry, false, |c, _| {
(LingonBerry, false, |_, col| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.jungle_hum, 0.5))
close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.5))
* MUSH_FACT
* 2.5,
None,
)
}),
(LeafyPlant, false, |c, _| {
(LeafyPlant, false, |_, col| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.jungle_hum, 0.3))
* MUSH_FACT
close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.3))
* GRASS_FACT
* 4.0,
None,
)
}),
(Fern, false, |c, _| {
(Fern, false, |_, col| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.5))
* MUSH_FACT
close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.forest_hum, 0.5))
* GRASS_FACT
* 0.25,
Some((64.0, 0.2)),
Some((0.0, 64.0, 0.2)),
)
}),
(Blueberry, false, |c, _| {
(Blueberry, false, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.5).min(close(
c.humidity,
close(col.temp, CONFIG.temperate_temp, 0.5).min(close(
col.humidity,
CONFIG.forest_hum,
0.5,
)) * MUSH_FACT
@ -151,191 +152,200 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
None,
)
}),
(Pumpkin, false, |c, _| {
(Pumpkin, false, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.5).min(close(
c.humidity,
close(col.temp, CONFIG.temperate_temp, 0.5).min(close(
col.humidity,
CONFIG.forest_hum,
0.5,
)) * MUSH_FACT
* 500.0,
Some((512.0, 0.05)),
Some((0.0, 512.0, 0.05)),
)
}),
// Collectable Objects
// Only spawn twigs in temperate forests
(Twigs, false, |c, _| {
(Twigs, false, |_, col| {
(
(c.tree_density * 1.25 - 0.25).powf(0.5).max(0.0) * 0.75e-3,
(col.tree_density * 1.25 - 0.25).powf(0.5).max(0.0) * 0.75e-3,
None,
)
}),
(Stones, false, |c, _| {
((c.rockiness - 0.5).max(0.025) * 1.0e-3, None)
(Stones, false, |chunk, _| {
((chunk.rockiness - 0.5).max(0.025) * 1.0e-3, None)
}),
(Copper, false, |c, _| {
((c.rockiness - 0.5).max(0.0) * 1.5e-3, None)
(Copper, false, |chunk, _| {
((chunk.rockiness - 0.5).max(0.0) * 1.5e-3, None)
}),
(Tin, false, |c, _| {
((c.rockiness - 0.5).max(0.0) * 1.5e-3, None)
(Tin, false, |chunk, _| {
((chunk.rockiness - 0.5).max(0.0) * 1.5e-3, None)
}),
// Don't spawn Mushrooms in snowy regions
(Mushroom, false, |c, _| {
(Mushroom, false, |_, col| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.35)) * MUSH_FACT,
close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT,
None,
)
}),
// Grass
(ShortGrass, false, |c, _| {
(ShortGrass, false, |_, col| {
(
close(c.temp, 0.2, 0.65).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) * 0.015,
None,
close(col.temp, 0.2, 0.75).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
* GRASS_FACT
* 150.0,
Some((0.3, 64.0, 0.3)),
)
}),
(MediumGrass, false, |c, _| {
(MediumGrass, false, |_, col| {
(
close(c.temp, 0.2, 0.6).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) * 0.012,
None,
close(col.temp, 0.2, 0.6).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
* GRASS_FACT
* 120.0,
Some((0.3, 64.0, 0.3)),
)
}),
(LongGrass, false, |c, _| {
(LongGrass, false, |_, col| {
(
close(c.temp, 0.3, 0.35).min(close(c.humidity, CONFIG.jungle_hum, 0.3)) * 0.15,
Some((48.0, 0.2)),
close(col.temp, 0.3, 0.35).min(close(col.humidity, CONFIG.jungle_hum, 0.3))
* GRASS_FACT
* 150.0,
Some((0.1, 48.0, 0.3)),
)
}),
// Jungle Sprites
// (LongGrass, false, |c, col| {
// (
// close(c.temp, CONFIG.tropical_temp, 0.4).min(close(
// c.humidity,
// close(col.temp, CONFIG.tropical_temp, 0.4).min(close(
// col.humidity,
// CONFIG.jungle_hum,
// 0.6,
// )) * 0.08,
// Some((60.0, 5.0)),
// Some((0.0, 60.0, 5.0)),
// )
// }),
/*(WheatGreen, false, |c, col| {
(
close(c.temp, 0.4, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.1))
close(col.temp, 0.4, 0.2).min(close(col.humidity, CONFIG.forest_hum, 0.1))
* MUSH_FACT
* 0.001,
None,
)
}),*/
(GrassSnow, false, |c, _| {
(GrassSnow, false, |_, col| {
(
close(c.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
c.humidity,
close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
col.humidity,
CONFIG.forest_hum,
0.5,
)) * 0.01,
Some((48.0, 0.2)),
)) * GRASS_FACT
* 100.0,
Some((0.0, 48.0, 0.2)),
)
}),
(Moonbell, false, |c, _| {
(Moonbell, false, |_, col| {
(
close(c.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
c.humidity,
close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
col.humidity,
CONFIG.forest_hum,
0.5,
)) * 0.003,
Some((48.0, 0.2)),
Some((0.0, 48.0, 0.2)),
)
}),
// Savanna Plants
(SavannaGrass, false, |c, _| {
(SavannaGrass, false, |_, col| {
(
{
let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25));
let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1));
(savanna - desert * 5.0).max(0.0)
let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
(savanna - desert * 5.0).max(0.0) * GRASS_FACT * 250.0
},
Some((0.5, 0.2)),
Some((0.15, 64.0, 0.2)),
)
}),
(TallSavannaGrass, false, |c, _| {
(TallSavannaGrass, false, |_, col| {
(
{
let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25));
let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1));
(savanna - desert * 5.0).max(0.0)
let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
(savanna - desert * 5.0).max(0.0) * GRASS_FACT * 150.0
},
Some((12.5, 0.25)),
Some((0.1, 48.0, 0.2)),
)
}),
(RedSavannaGrass, false, |c, _| {
(RedSavannaGrass, false, |_, col| {
(
{
let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25));
let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1));
(savanna - desert * 5.0).max(0.0)
let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
(savanna - desert * 5.0).max(0.0) * GRASS_FACT * 120.0
},
Some((0.1, 0.1)),
Some((0.15, 48.0, 0.25)),
)
}),
(SavannaBush, false, |c, _| {
(SavannaBush, false, |_, col| {
(
{
let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25));
let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1));
(savanna - desert * 5.0).max(0.0)
let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
(savanna - desert * 5.0).max(0.0) * GRASS_FACT * 40.0
},
Some((0.08, 0.05)),
Some((0.1, 96.0, 0.15)),
)
}),
// Desert Plants
(DeadBush, false, |c, _| {
(DeadBush, false, |_, col| {
(
close(c.temp, 1.0, 0.95).min(close(c.humidity, 0.0, 0.3)) * MUSH_FACT * 7.5,
close(col.temp, 1.0, 0.95).min(close(col.humidity, 0.0, 0.3)) * MUSH_FACT * 7.5,
None,
)
}),
(Pyrebloom, false, |c, _| {
(Pyrebloom, false, |_, col| {
(
close(c.temp, 1.0, 0.95).min(close(c.humidity, 0.0, 0.3)) * MUSH_FACT * 0.35,
close(col.temp, 1.0, 0.95).min(close(col.humidity, 0.0, 0.3)) * MUSH_FACT * 0.35,
None,
)
}),
(LargeCactus, false, |c, _| {
(LargeCactus, false, |_, col| {
(
close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 3.5,
close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 1.5,
None,
)
}),
(RoundCactus, false, |c, _| {
(RoundCactus, false, |_, col| {
(
close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5,
close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5,
None,
)
}),
(ShortCactus, false, |c, _| {
(ShortCactus, false, |_, col| {
(
close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5,
close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5,
None,
)
}),
(MedFlatCactus, false, |c, _| {
(MedFlatCactus, false, |_, col| {
(
close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5,
close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5,
None,
)
}),
(ShortFlatCactus, false, |c, _| {
(ShortFlatCactus, false, |_, col| {
(
close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5,
close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5,
None,
)
}),
(Reed, false, |c, col| {
(Reed, false, |_, col| {
(
close(c.humidity, CONFIG.jungle_hum, 0.7)
close(col.humidity, CONFIG.jungle_hum, 0.9)
* col
.water_dist
.map(|wd| Lerp::lerp(0.2, 0.0, (wd / 8.0).clamped(0.0, 1.0)))
.unwrap_or(0.0),
Some((128.0, 0.5)),
.unwrap_or(0.0)
* ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0),
Some((0.2, 128.0, 0.5)),
)
}),
// Underwater chests
@ -374,23 +384,23 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
} else {
0.0
},
Some((100.0, 0.15)),
Some((0.0, 100.0, 0.15)),
)
}),
// seagrass
(Seagrass, true, |c, col| {
(Seagrass, true, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.8)
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
} else {
0.0
},
Some((150.0, 0.3)),
Some((0.0, 150.0, 0.3)),
)
}),
// seagrass, coastal patches
@ -398,44 +408,44 @@ 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
},
Some((150.0, 0.4)),
Some((0.0, 150.0, 0.4)),
)
}),
// scattered seaweed (temperate species)
(SeaweedTemperate, true, |c, col| {
(SeaweedTemperate, true, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.8)
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
} else {
0.0
},
Some((500.0, 0.75)),
Some((0.0, 500.0, 0.75)),
)
}),
// scattered seaweed (tropical species)
(SeaweedTropical, true, |c, col| {
(SeaweedTropical, true, |_, col| {
(
close(c.temp, 1.0, 0.95)
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
} else {
0.0
},
Some((500.0, 0.75)),
Some((0.0, 500.0, 0.75)),
)
}),
// Caulerpa lentillifera algae patch
@ -443,14 +453,14 @@ 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
} else {
0.0
},
Some((100.0, 0.15)),
Some((0.0, 100.0, 0.15)),
)
}),
// Caulerpa prolifera algae patch
@ -458,110 +468,110 @@ 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
} else {
0.0
},
Some((100.0, 0.15)),
Some((0.0, 100.0, 0.15)),
)
}),
// Mermaids' fan algae patch
(MermaidsFan, true, |c, col| {
(MermaidsFan, true, |_, col| {
(
close(c.temp, 1.0, 0.95)
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
} else {
0.0
},
Some((50.0, 0.10)),
Some((0.0, 50.0, 0.10)),
)
}),
// Sea anemones
(SeaAnemone, true, |c, col| {
(SeaAnemone, true, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.8)
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
} else {
0.0
},
Some((100.0, 0.3)),
Some((0.0, 100.0, 0.3)),
)
}),
// Giant Kelp
(GiantKelp, true, |c, col| {
(GiantKelp, true, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.8)
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
} else {
0.0
},
Some((200.0, 0.4)),
Some((0.0, 200.0, 0.4)),
)
}),
// Bull Kelp
(BullKelp, true, |c, col| {
(BullKelp, true, |_, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.7)
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
} else {
0.0
},
Some((75.0, 0.3)),
Some((0.0, 75.0, 0.3)),
)
}),
// Stony Corals
(StonyCoral, true, |c, col| {
(StonyCoral, true, |_, col| {
(
close(c.temp, 1.0, 0.9)
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
} else {
0.0
},
Some((120.0, 0.4)),
Some((0.0, 120.0, 0.4)),
)
}),
// Soft Corals
(SoftCoral, true, |c, col| {
(SoftCoral, true, |_, col| {
(
close(c.temp, 1.0, 0.9)
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
} else {
0.0
},
Some((120.0, 0.4)),
Some((0.0, 120.0, 0.4)),
)
}),
// Seashells
@ -569,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
@ -594,16 +604,16 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
];
canvas.foreach_col(|canvas, wpos2d, col| {
let underwater = col.water_level > col.alt;
let underwater = col.water_level.floor() > col.alt;
let kind = scatter
.iter()
.enumerate()
.find_map(|(i, (kind, is_underwater, f))| {
let (density, patch) = f(canvas.chunk(), col);
let is_patch = patch
.map(|(wavelen, threshold)| {
canvas
let density = patch
.map(|(base_density_prop, wavelen, threshold)| {
if canvas
.index()
.noise
.scatter_nz
@ -614,10 +624,14 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
)
.abs()
> 1.0 - threshold as f64
{
density
} else {
density * base_density_prop
}
})
.unwrap_or(true);
.unwrap_or(density);
if density > 0.0
&& is_patch
&& rng.gen::<f32>() < density //RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
&& underwater == *is_underwater
{

76
world/src/layer/shrub.rs Normal file
View File

@ -0,0 +1,76 @@
use crate::{
all::ForestKind,
util::{seed_expan, Sampler, StructureGen2d, UnitChooser},
Canvas,
};
use common::{
assets::AssetHandle,
terrain::structure::{Structure, StructuresGroup},
};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use vek::*;
lazy_static! {
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, _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(|_, 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)?;
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
&& col.path.map_or(true, |(d, _, _, _)| d > 6.0)
{
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
}
});
}
});
for shrub in shrub_cache.values().filter_map(|s| s.as_ref()) {
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(shrub.seed));
let units = UnitChooser::new(shrub.seed).get(shrub.seed).into();
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

@ -360,7 +360,7 @@ impl TreeConfig {
}
pub fn jungle(rng: &mut impl Rng, scale: f32) -> Self {
let scale = scale * (0.9 + rng.gen::<f32>().powi(4) * 1.0);
let scale = scale * (0.8 + rng.gen::<f32>() * 0.5);
let log_scale = 1.0 + scale.log2().max(0.0);
Self {
@ -369,14 +369,14 @@ impl TreeConfig {
branch_child_len: 0.35,
branch_child_radius: 0.5,
branch_child_radius_lerp: true,
leaf_radius: 4.0 * log_scale..4.5 * log_scale,
leaf_radius_scaled: 0.0,
leaf_radius: 10.0 * log_scale..11.5 * log_scale,
leaf_radius_scaled: -8.0 * log_scale,
straightness: 0.2,
max_depth: 2,
splits: 7.5..8.5,
split_range: 0.3..1.25,
split_range: 0.2..1.25,
branch_len_bias: 0.5,
leaf_vertical_scale: 0.4,
leaf_vertical_scale: 0.35,
proportionality: 0.8,
inhabited: false,
hanging_sprites: &[(0.00007, SpriteKind::Beehive), (0.015, SpriteKind::Liana)],
@ -467,16 +467,16 @@ impl TreeConfig {
trunk_len: 13.0 * scale,
trunk_radius: 1.65 * scale,
branch_child_len: 0.75,
branch_child_radius: 0.75,
branch_child_radius: 0.6,
branch_child_radius_lerp: true,
leaf_radius: 2.0 * log_scale..2.1 * log_scale,
leaf_radius: 1.5 * log_scale..2.0 * log_scale,
leaf_radius_scaled: 0.0,
straightness: 0.3,
max_depth: 5,
splits: 3.5..4.25,
split_range: 0.5..1.25,
branch_len_bias: 0.0,
leaf_vertical_scale: 0.5,
leaf_vertical_scale: 0.65,
proportionality: 0.5,
inhabited: false,
hanging_sprites: &[(0.00007, SpriteKind::Beehive)],

View File

@ -347,6 +347,7 @@ impl World {
};
layer::apply_caves_to(&mut canvas, &mut dynamic_rng);
layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng);
layer::apply_trees_to(&mut canvas, &mut dynamic_rng);
layer::apply_scatter_to(&mut canvas, &mut dynamic_rng);
layer::apply_paths_to(&mut canvas);

View File

@ -1563,8 +1563,10 @@ impl WorldSim {
let dist = rpos.map(|e| e as f32).magnitude();
if let Some(c) = self.get_mut(cliff + rpos) {
let warp = 1.0 / (1.0 + dist);
c.tree_density *= 1.0 - warp;
c.cliff_height = Lerp::lerp(44.0, 0.0, -1.0 + dist / 3.5);
if !c.river.near_water() {
c.tree_density *= 1.0 - warp;
c.cliff_height = Lerp::lerp(44.0, 0.0, -1.0 + dist / 3.5);
}
}
});
}
@ -2054,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,
})
});
@ -2219,7 +2219,8 @@ impl SimChunk {
} else {
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)
+ TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2),
)
};
@ -2295,10 +2296,10 @@ impl SimChunk {
.min(1.0);
// Add geologically short timescale undulation to the world for various reasons
let alt = alt
let alt =
// Don't add undulation to rivers, mainly because this could accidentally result in rivers flowing uphill
+ if river.near_water() {
0.0
if river.near_water() {
alt
} else {
// Sand dunes (formed over a short period of time, so we don't care about erosion sim)
let warp = Vec2::new(
@ -2323,7 +2324,16 @@ impl SimChunk {
const SOIL_SCALE: f32 = 16.0;
let soil = soil_nz * SOIL_SCALE * tree_density.sqrt() * humidity.sqrt();
dune + soil
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 + warp
}
};
Self {
@ -2415,4 +2425,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
},
}
}
}