Merge branch 'zesterer/rtsim' into 'master'

Beehives, Fireflies, and minor worldsim/lighting adjustments

See merge request veloren/veloren!1357
This commit is contained in:
Joshua Barretto 2020-09-05 17:16:08 +00:00
commit 49c713a071
37 changed files with 1056 additions and 725 deletions

View File

@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Point and directional lights now cast realistic shadows, using shadow mapping.
- Added leaf and chimney particles
- Some more combat sound effects
- Beehives and bees
- Fireflies
### Changed

738
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,8 @@ Item(
(
kind: "Black0",
color: (r: 255, g: 128, b: 26),
strength_thousandths: 3000,
flicker_thousandths: 300,
strength_thousandths: 5000,
flicker_thousandths: 150,
),
),
)

View File

@ -5,7 +5,7 @@ Item(
(
kind: "Blue0",
color: (r: 255, g: 128, b: 26),
strength_thousandths: 4000,
strength_thousandths: 6000,
flicker_thousandths: 250,
),
),

View File

@ -5,8 +5,8 @@ Item(
(
kind: "Green0",
color: (r: 255, g: 128, b: 26),
strength_thousandths: 4000,
flicker_thousandths: 500,
strength_thousandths: 6000,
flicker_thousandths: 250,
),
),
)

View File

@ -5,8 +5,8 @@ Item(
(
kind: "Red0",
color: (r: 255, g: 128, b: 26),
strength_thousandths: 3500,
flicker_thousandths: 1000,
strength_thousandths: 5000,
flicker_thousandths: 250,
),
),
)

View File

@ -140,9 +140,8 @@ float lights_at(vec3 wpos, vec3 wnorm, vec3 /*cam_to_frag*/view_dir, vec3 mu, ve
float distance_2 = dot(difference, difference);
// float strength = attenuation_strength(difference);// pow(attenuation_strength(difference), 0.6);
// // NOTE: This normalizes strength to 1.0 at the center of the point source.
// float strength = 1.0 / (1.0 + distance_2);
float strength = 1.0 / distance_2;
// NOTE: This normalizes strength to 0.25 at the center of the point source.
float strength = 1.0 / (4 + distance_2);
// Multiply the vec3 only once
const float PI = 3.1415926535897932384626433832795;

View File

@ -71,6 +71,10 @@ void main() {
max_light += lights_at(f_pos, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light);
// Allow particles to glow at night
// TODO: Not this
emitted_light += max(f_col.rgb - 1.0, vec3(0));
surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light);
#if (CLOUD_MODE == CLOUD_MODE_REGULAR)

View File

@ -45,6 +45,8 @@ const int FIREWORK_PURPLE = 6;
const int FIREWORK_RED = 7;
const int FIREWORK_YELLOW = 8;
const int LEAF = 9;
const int FIREFLY = 10;
const int BEE = 11;
// meters per second squared (acceleration)
const float earth_gravity = 9.807;
@ -214,6 +216,30 @@ void main() {
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.5 + rand6 * 0.5) * 0.6, 0), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else if (inst_mode == FIREFLY) {
float raise = pow(sin(3.1416 * lifetime / inst_lifespan), 0.2);
attr = Attr(
vec3(0, 0, raise * 5.0) + vec3(
sin(lifetime * 1.0 + rand0) + sin(lifetime * 7.0 + rand3) * 0.3,
sin(lifetime * 3.0 + rand1) + sin(lifetime * 8.0 + rand4) * 0.3,
sin(lifetime * 2.0 + rand2) + sin(lifetime * 9.0 + rand5) * 0.3
),
raise,
vec4(vec3(5, 5, 1.1), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else if (inst_mode == BEE) {
float lower = pow(sin(3.1416 * lifetime / inst_lifespan), 0.2);
attr = Attr(
vec3(0, 0, lower * -0.5) + vec3(
sin(lifetime * 2.0 + rand0) + sin(lifetime * 9.0 + rand3) * 0.3,
sin(lifetime * 3.0 + rand1) + sin(lifetime * 10.0 + rand4) * 0.3,
sin(lifetime * 4.0 + rand2) + sin(lifetime * 11.0 + rand5) * 0.3
) * 0.5,
lower,
vec4(vec3(1, 0.7, 0), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else {
attr = Attr(
linear_motion(

BIN
assets/voxygen/voxel/sprite/beehive/beehive.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/reed/reed-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/reed/reed-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/reed/reed-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/reed/reed-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/reed/reed-4.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1947,4 +1947,48 @@ GrassSnow: Some((
],
wind_sway: 0.2,
)),
// Reed
Reed: Some((
variations: [
(
model: "voxygen.voxel.sprite.reed.reed-0",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
(
model: "voxygen.voxel.sprite.reed.reed-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
(
model: "voxygen.voxel.sprite.reed.reed-2",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
(
model: "voxygen.voxel.sprite.reed.reed-3",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
(
model: "voxygen.voxel.sprite.reed.reed-4",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
],
wind_sway: 0.3,
)),
// Beehive
Beehive: Some((
variations: [
(
model: "voxygen.voxel.sprite.beehive.beehive",
offset: (-5.5, -5.5, 0.0),
lod_axes: (1.0, 1.0, 1.0),
),
],
wind_sway: 0.1,
)),
)

View File

@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage;
use std::{fs::File, io::BufReader};
use vek::*;
make_case_elim!(
body,
@ -505,6 +506,14 @@ impl Body {
Body::QuadrupedLow(_) => 4.5,
}
}
pub fn default_light_offset(&self) -> Vec3<f32> {
// TODO: Make this a manifest
match self {
Body::Object(_) => Vec3::unit_z() * 0.5,
_ => Vec3::unit_z(),
}
}
}
impl Component for Body {

View File

@ -42,6 +42,7 @@ pub mod store;
pub mod sync;
pub mod sys;
pub mod terrain;
pub mod time;
pub mod typed;
pub mod util;
pub mod vol;

View File

@ -5,6 +5,7 @@ use crate::{
sync::WorldSyncExt,
sys,
terrain::{Block, TerrainChunk, TerrainGrid},
time::DayPeriod,
vol::WriteVol,
};
use hashbrown::{HashMap, HashSet};
@ -230,6 +231,22 @@ impl State {
/// localised timings.
pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
/// Get the current in-game day period (period of the day/night cycle)
pub fn get_day_period(&self) -> DayPeriod {
let tod = self.get_time_of_day().rem_euclid(60.0 * 60.0 * 24.0);
if tod < 60.0 * 60.0 * 4.0 {
DayPeriod::Night
} else if tod < 60.0 * 60.0 * 10.0 {
DayPeriod::Morning
} else if tod < 60.0 * 60.0 * 16.0 {
DayPeriod::Noon
} else if tod < 60.0 * 60.0 * 20.0 {
DayPeriod::Evening
} else {
DayPeriod::Night
}
}
/// Get the current in-game time.
///
/// Note that this does not correspond to the time of day.

View File

@ -156,9 +156,7 @@ impl<'a> System<'a> for Sys {
_ => {},
}
}
}
if let Some(dir) = velocities
} else if let Some(dir) = velocities
.get(entity)
.and_then(|vel| vel.0.try_normalized())
{

View File

@ -93,6 +93,8 @@ make_case_elim!(
DropGate = 0x50,
DropGateBottom = 0x51,
GrassSnow = 0x52,
Reed = 0x53,
Beehive = 0x54,
}
);
@ -200,6 +202,8 @@ impl BlockKind {
BlockKind::DropGate => false,
BlockKind::DropGateBottom => false,
BlockKind::GrassSnow => true,
BlockKind::Reed => true,
BlockKind::Beehive => true,
_ => false,
}
}
@ -296,6 +300,8 @@ impl BlockKind {
BlockKind::DropGate => false,
BlockKind::DropGateBottom => false,
BlockKind::GrassSnow => false,
BlockKind::Reed => false,
BlockKind::Beehive => false,
_ => true,
}
}
@ -373,6 +379,7 @@ impl BlockKind {
BlockKind::DropGate => true,
BlockKind::DropGateBottom => false,
BlockKind::GrassSnow => false,
BlockKind::Reed => false,
_ => true,
}
}
@ -502,7 +509,8 @@ impl Block {
| BlockKind::Chest
| BlockKind::DropGate
| BlockKind::DropGateBottom
| BlockKind::Door => Some(self.color[0] & 0b111),
| BlockKind::Door
| BlockKind::Beehive => Some(self.color[0] & 0b111),
_ => None,
}
}

13
common/src/time.rs Normal file
View File

@ -0,0 +1,13 @@
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum DayPeriod {
Night,
Morning,
Noon,
Evening,
}
impl DayPeriod {
pub fn is_dark(&self) -> bool { *self == DayPeriod::Night }
pub fn is_light(&self) -> bool { !self.is_dark() }
}

View File

@ -1,8 +1,8 @@
use crate::{sys, Server, StateExt};
use common::{
comp::{
self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightAnimation, LightEmitter,
Loadout, Pos, Projectile, Scale, Stats, Vel, WaypointArea,
self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
Projectile, Scale, Stats, Vel, WaypointArea,
},
outcome::Outcome,
util::Dir,
@ -119,16 +119,12 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
.state
.create_object(Pos(pos), comp::object::Body::CampfireLit)
.with(LightEmitter {
col: Rgb::new(1.0, 0.8, 0.0),
strength: 8.0,
col: Rgb::new(1.0, 0.3, 0.1),
strength: 5.0,
flicker: 1.0,
animated: true,
})
.with(LightAnimation {
offset: Vec3::new(0.0, 0.0, 2.0),
col: Rgb::new(1.0, 0.8, 0.0),
strength: 8.0,
})
.with(WaypointArea::default())
.with(comp::Mass(100000.0))
.build();
}

View File

@ -41,7 +41,7 @@ pub fn handle_lantern(server: &mut Server, entity: EcsEntity) {
.insert(entity, comp::LightEmitter {
col: lantern.color(),
strength: lantern.strength(),
flicker: 1.0,
flicker: 0.35,
animated: true,
});
}

View File

@ -358,6 +358,9 @@ impl Server {
/// Get a reference to the server's world.
pub fn world(&self) -> &World { &self.world }
/// Get a reference to the server's world map.
pub fn map(&self) -> &WorldMapMsg { &self.map }
/// Execute a single server tick, handle input and update the game state by
/// the given duration.
pub fn tick(&mut self, _input: Input, dt: Duration) -> Result<Vec<Event>, Error> {

View File

@ -103,6 +103,8 @@ pub enum ParticleMode {
FireworkRed = 7,
FireworkYellow = 8,
Leaf = 9,
Firefly = 10,
Bee = 11,
}
impl ParticleMode {

View File

@ -43,6 +43,7 @@ use guillotiere::AtlasAllocator;
use hashbrown::HashMap;
use specs::{Entity as EcsEntity, Join, LazyUpdate, WorldExt};
use treeculler::{BVol, BoundingSphere};
use vek::*;
const DAMAGE_FADE_COEFFICIENT: f64 = 5.0;
const MOVING_THRESHOLD: f32 = 0.7;
@ -363,13 +364,20 @@ impl FigureMgr {
// TODO: Pending review in #587
pub fn update_lighting(&mut self, scene_data: &SceneData) {
let ecs = scene_data.state.ecs();
for (entity, light_emitter) in (&ecs.entities(), &ecs.read_storage::<LightEmitter>()).join()
for (entity, body, light_emitter) in (
&ecs.entities(),
ecs.read_storage::<common::comp::Body>().maybe(),
&ecs.read_storage::<LightEmitter>(),
)
.join()
{
// Add LightAnimation for objects with a LightEmitter
let mut anim_storage = ecs.write_storage::<LightAnimation>();
if let None = anim_storage.get_mut(entity) {
let anim = LightAnimation {
offset: vek::Vec3::zero(),
offset: body
.map(|b| b.default_light_offset())
.unwrap_or_else(Vec3::zero),
col: light_emitter.col,
strength: 0.0,
};

View File

@ -282,32 +282,85 @@ impl ParticleMgr {
(e.floor() as i32).div_euclid(sz as i32)
});
type BoiFn<'a> = fn(&'a BlocksOfInterest) -> &'a [Vec3<i32>];
// blocks, chunk range, emission density, lifetime, particle mode
//
// - blocks: the function to select the blocks of interest that we should emit
// from
// - chunk range: the range, in chunks, that the particles should be generated
// in from the player
// - emission density: the density, per block per second, of the generated
// particles
// - lifetime: the number of seconds that each particle should live for
// - particle mode: the visual mode of the generated particle
let particles: &[(BoiFn, usize, f32, f32, ParticleMode)] = &[
(|boi| &boi.leaves, 4, 0.001, 30.0, ParticleMode::Leaf),
(|boi| &boi.embers, 2, 20.0, 0.25, ParticleMode::CampfireFire),
(|boi| &boi.embers, 8, 3.0, 40.0, ParticleMode::CampfireSmoke),
struct BlockParticles<'a> {
// The function to select the blocks of interest that we should emit from
blocks: fn(&'a BlocksOfInterest) -> &'a [Vec3<i32>],
// The range, in chunks, that the particles should be generated in from the player
range: usize,
// The emission rate, per block per second, of the generated particles
rate: f32,
// The number of seconds that each particle should live for
lifetime: f32,
// The visual mode of the generated particle
mode: ParticleMode,
// Condition that must be true
cond: fn(&SceneData) -> bool,
}
let particles: &[BlockParticles] = &[
BlockParticles {
blocks: |boi| &boi.leaves,
range: 4,
rate: 0.001,
lifetime: 30.0,
mode: ParticleMode::Leaf,
cond: |_| true,
},
BlockParticles {
blocks: |boi| &boi.embers,
range: 2,
rate: 20.0,
lifetime: 0.25,
mode: ParticleMode::CampfireFire,
cond: |_| true,
},
BlockParticles {
blocks: |boi| &boi.embers,
range: 8,
rate: 3.0,
lifetime: 40.0,
mode: ParticleMode::CampfireSmoke,
cond: |_| true,
},
BlockParticles {
blocks: |boi| &boi.reeds,
range: 6,
rate: 0.004,
lifetime: 40.0,
mode: ParticleMode::Firefly,
cond: |sd| sd.state.get_day_period().is_dark(),
},
BlockParticles {
blocks: |boi| &boi.flowers,
range: 5,
rate: 0.002,
lifetime: 40.0,
mode: ParticleMode::Firefly,
cond: |sd| sd.state.get_day_period().is_dark(),
},
BlockParticles {
blocks: |boi| &boi.beehives,
range: 3,
rate: 0.5,
lifetime: 30.0,
mode: ParticleMode::Bee,
cond: |sd| sd.state.get_day_period().is_light(),
},
];
let mut rng = thread_rng();
for (get_blocks, range, rate, dur, mode) in particles.iter() {
for offset in Spiral2d::new().take((*range * 2 + 1).pow(2)) {
for particles in particles.iter() {
if !(particles.cond)(scene_data) {
continue;
}
for offset in Spiral2d::new().take((particles.range * 2 + 1).pow(2)) {
let chunk_pos = player_chunk + offset;
terrain.get(chunk_pos).map(|chunk_data| {
let blocks = get_blocks(&chunk_data.blocks_of_interest);
let blocks = (particles.blocks)(&chunk_data.blocks_of_interest);
let avg_particles = dt * blocks.len() as f32 * *rate;
let avg_particles = dt * blocks.len() as f32 * particles.rate;
let particle_count = avg_particles.trunc() as usize
+ (rng.gen::<f32>() < avg_particles.fract()) as usize;
@ -318,9 +371,9 @@ impl ParticleMgr {
+ blocks.choose(&mut rng).copied().unwrap(); // Can't fail
Particle::new(
Duration::from_secs_f32(*dur),
Duration::from_secs_f32(particles.lifetime),
time,
*mode,
particles.mode,
block_pos.map(|e: i32| e as f32 + rng.gen::<f32>()),
)
})

View File

@ -7,13 +7,21 @@ use vek::*;
pub struct BlocksOfInterest {
pub leaves: Vec<Vec3<i32>>,
pub grass: Vec<Vec3<i32>>,
pub embers: Vec<Vec3<i32>>,
pub beehives: Vec<Vec3<i32>>,
pub reeds: Vec<Vec3<i32>>,
pub flowers: Vec<Vec3<i32>>,
}
impl BlocksOfInterest {
pub fn from_chunk(chunk: &TerrainChunk) -> Self {
let mut leaves = Vec::new();
let mut grass = Vec::new();
let mut embers = Vec::new();
let mut beehives = Vec::new();
let mut reeds = Vec::new();
let mut flowers = Vec::new();
chunk
.vol_iter(
@ -24,14 +32,36 @@ impl BlocksOfInterest {
chunk.get_max_z(),
),
)
.for_each(|(pos, block)| {
if block.kind() == BlockKind::Leaves && thread_rng().gen_range(0, 16) == 0 {
leaves.push(pos);
} else if block.kind() == BlockKind::Ember {
embers.push(pos);
}
.for_each(|(pos, block)| match block.kind() {
BlockKind::Leaves => {
if thread_rng().gen_range(0, 16) == 0 {
leaves.push(pos)
}
},
BlockKind::Grass => {
if thread_rng().gen_range(0, 16) == 0 {
grass.push(pos)
}
},
BlockKind::Ember => embers.push(pos),
BlockKind::Beehive => beehives.push(pos),
BlockKind::Reed => reeds.push(pos),
BlockKind::PinkFlower => flowers.push(pos),
BlockKind::PurpleFlower => flowers.push(pos),
BlockKind::RedFlower => flowers.push(pos),
BlockKind::WhiteFlower => flowers.push(pos),
BlockKind::YellowFlower => flowers.push(pos),
BlockKind::Sunflower => flowers.push(pos),
_ => {},
});
Self { leaves, embers }
Self {
leaves,
grass,
embers,
beehives,
reeds,
flowers,
}
}
}

View File

@ -574,10 +574,12 @@ pub fn block_from_structure(
// None of these BlockKinds has an orientation, so we just use zero for the other color
// bits.
StructureBlock::Liana => Some(Block::new(BlockKind::Liana, Rgb::zero())),
StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 3 > 0 {
Block::empty()
} else {
StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 24 == 0 {
Block::new(BlockKind::Beehive, Rgb::zero())
} else if field.get(pos + structure_pos + 1) % 3 == 0 {
Block::new(BlockKind::Apple, Rgb::zero())
} else {
Block::empty()
}),
StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 {
Block::empty()

View File

@ -774,6 +774,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.div(100.0)
.into_array(),
) as f32)
//.mul(water_dist.map(|wd| (wd / 2.0).clamped(0.0, 1.0).powf(0.5)).unwrap_or(1.0))
.mul(rockiness)
.sub(0.4)
.max(0.0)
@ -996,6 +997,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
(alt, ground, sub_surface_color)
};
// Make river banks not have grass
let ground = water_dist
.map(|wd| Lerp::lerp(sub_surface_color, ground, (wd / 3.0).clamped(0.0, 1.0)))
.unwrap_or(ground);
let near_ocean = max_river.and_then(|(_, _, river_data, _)| {
if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean))
&& ((alt <= water_level.max(CONFIG.sea_level + 5.0) && !is_cliffs) || !near_cliffs)

View File

@ -1,8 +1,11 @@
pub mod scatter;
pub use self::scatter::apply_scatter_to;
use crate::{
column::ColumnSample,
sim::SimChunk,
util::{RandomField, Sampler},
IndexRef, CONFIG,
IndexRef,
};
use common::{
assets::Asset,
@ -27,320 +30,6 @@ pub struct Colors {
pub stalagtite: (u8, u8, u8),
}
fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.5)
}
const MUSH_FACT: f32 = 1.0e-4; // To balance everything around the mushroom spawning rate
pub fn apply_scatter_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: IndexRef,
chunk: &SimChunk,
) {
use BlockKind::*;
#[allow(clippy::type_complexity)]
// TODO: Add back all sprites we had before
let scatter: &[(_, bool, fn(&SimChunk) -> (f32, Option<(f32, f32)>))] = &[
// (density, Option<(wavelen, threshold)>)
// Flowers
(BlueFlower, false, |c| {
(
close(c.temp, 0.1, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.01,
Some((48.0, 0.2)),
)
}),
(PinkFlower, false, |c| {
(
close(c.temp, 0.2, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.01,
Some((48.0, 0.2)),
)
}),
(PurpleFlower, false, |c| {
(
close(c.temp, 0.3, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.01,
Some((48.0, 0.2)),
)
}),
(RedFlower, false, |c| {
(
close(c.temp, 0.5, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.01,
Some((48.0, 0.2)),
)
}),
(WhiteFlower, false, |c| {
(
close(c.temp, 0.0, 0.3).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.01,
Some((48.0, 0.2)),
)
}),
(YellowFlower, false, |c| {
(
close(c.temp, 0.3, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.01,
Some((48.0, 0.2)),
)
}),
// Herbs and Spices
(LingonBerry, false, |c| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.5,
None,
)
}),
(LeafyPlant, false, |c| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.5,
None,
)
}),
(Fern, false, |c| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.35))
* MUSH_FACT
* 0.5,
Some((48.0, 0.4)),
)
}),
(Blueberry, false, |c| {
(
close(c.temp, CONFIG.temperate_temp, 0.5).min(close(
c.humidity,
CONFIG.forest_hum,
0.35,
)) * MUSH_FACT
* 0.3,
None,
)
}),
// Collectable Objects
// Only spawn twigs in temperate forests
(Twigs, false, |c| {
((c.tree_density - 0.5).max(0.0) * 1.0e-3, None)
}),
(Stones, false, |c| {
((c.rockiness - 0.5).max(0.0) * 1.0e-3, None)
}),
// Don't spawn Mushrooms in snowy regions
(Mushroom, false, |c| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.35)) * MUSH_FACT,
None,
)
}),
// Grass
(ShortGrass, false, |c| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.4)),
)
}),
(MediumGrass, false, |c| {
(
close(c.temp, 0.0, 0.6).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.2)),
)
}),
(LongGrass, false, |c| {
(
close(c.temp, 0.4, 0.4).min(close(c.humidity, 0.8, 0.2)) * 0.08,
Some((48.0, 0.1)),
)
}),
// Jungle Sprites
(LongGrass, false, |c| {
(
close(c.temp, CONFIG.tropical_temp, 0.4).min(close(
c.humidity,
CONFIG.jungle_hum,
0.6,
)) * 0.08,
Some((60.0, 5.0)),
)
}),
/*(WheatGreen, false, |c| {
(
close(c.temp, 0.4, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.1))
* MUSH_FACT
* 0.001,
None,
)
}),*/
(GrassSnow, false, |c| {
(
close(c.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
c.humidity,
CONFIG.forest_hum,
0.5,
)) * 0.01,
Some((48.0, 0.2)),
)
}),
// Desert Plants
(DeadBush, false, |c| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.3,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(LargeCactus, false, |c| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
/*(BarrelCactus, false, |c| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(RoundCactus, false, |c| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(ShortCactus, false, |c| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(MedFlatCactus, false, |c| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(ShortFlatCactus, false, |c| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),*/
];
for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 {
let offs = Vec2::new(x, y);
let wpos2d = wpos2d + offs;
// Sample terrain
let col_sample = if let Some(col_sample) = get_column(offs) {
col_sample
} else {
continue;
};
let underwater = col_sample.water_level > col_sample.alt;
let bk = scatter
.iter()
.enumerate()
.find_map(|(i, (bk, is_underwater, f))| {
let (density, patch) = f(chunk);
let is_patch = patch
.map(|(wavelen, threshold)| {
index.noise.scatter_nz.get(
wpos2d
.map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0)
.into_array(),
) < threshold as f64
})
.unwrap_or(false);
if density <= 0.0
|| is_patch
|| !RandomField::new(i as u32)
.chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
|| underwater != *is_underwater
{
None
} else {
Some(*bk)
}
});
if let Some(bk) = bk {
let alt = col_sample.alt as i32;
// Find the intersection between ground and air, if there is one near the
// surface
if let Some(solid_end) = (-4..8)
.find(|z| {
vol.get(Vec3::new(offs.x, offs.y, alt + z))
.map(|b| b.is_solid())
.unwrap_or(false)
})
.and_then(|solid_start| {
(1..8).map(|z| solid_start + z).find(|z| {
vol.get(Vec3::new(offs.x, offs.y, alt + z))
.map(|b| !b.is_solid())
.unwrap_or(true)
})
})
{
let _ = vol.set(
Vec3::new(offs.x, offs.y, alt + solid_end),
Block::new(bk, Rgb::broadcast(0)),
);
}
}
}
}
}
pub fn apply_paths_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,

360
world/src/layer/scatter.rs Normal file
View File

@ -0,0 +1,360 @@
use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, IndexRef, CONFIG};
use common::{
terrain::{Block, BlockKind},
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
};
use noise::NoiseFn;
use std::f32;
use vek::*;
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
pub fn apply_scatter_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: IndexRef,
chunk: &SimChunk,
) {
use BlockKind::*;
#[allow(clippy::type_complexity)]
// TODO: Add back all sprites we had before
let scatter: &[(
_,
bool,
fn(&SimChunk, &ColumnSample) -> (f32, Option<(f32, f32)>),
)] = &[
// (density, Option<(wavelen, threshold)>)
// Flowers
(BlueFlower, false, |c, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.7).min(close(
c.humidity,
CONFIG.jungle_hum,
0.4,
)) * col.tree_density
* MUSH_FACT
* 256.0,
Some((256.0, 0.25)),
)
}),
(PinkFlower, false, |c, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
)
}),
(PurpleFlower, false, |c, col| {
(
close(c.temp, CONFIG.temperate_temp, 0.7).min(close(
c.humidity,
CONFIG.jungle_hum,
0.4,
)) * col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
)
}),
(RedFlower, false, |c, col| {
(
close(c.temp, CONFIG.tropical_temp, 0.6).min(close(
c.humidity,
CONFIG.jungle_hum,
0.3,
)) * col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.05)),
)
}),
(WhiteFlower, false, |c, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
)
}),
(YellowFlower, false, |c, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.1)),
)
}),
(Sunflower, false, |c, col| {
(
close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4))
* col.tree_density
* MUSH_FACT
* 350.0,
Some((100.0, 0.15)),
)
}),
// Herbs and Spices
(LingonBerry, false, |c, _| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.jungle_hum, 0.5))
* MUSH_FACT
* 2.5,
None,
)
}),
(LeafyPlant, false, |c, _| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.jungle_hum, 0.3))
* MUSH_FACT
* 4.0,
None,
)
}),
(Fern, false, |c, _| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.5))
* MUSH_FACT
* 0.25,
Some((64.0, 0.2)),
)
}),
(Blueberry, false, |c, _| {
(
close(c.temp, CONFIG.temperate_temp, 0.5).min(close(
c.humidity,
CONFIG.forest_hum,
0.5,
)) * MUSH_FACT
* 0.3,
None,
)
}),
// Collectable Objects
// Only spawn twigs in temperate forests
(Twigs, false, |c, _| {
((c.tree_density - 0.5).max(0.0) * 1.0e-3, None)
}),
(Stones, false, |c, _| {
((c.rockiness - 0.5).max(0.0) * 1.0e-3, None)
}),
// Don't spawn Mushrooms in snowy regions
(Mushroom, false, |c, _| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.35)) * MUSH_FACT,
None,
)
}),
// Grass
(ShortGrass, false, |c, _| {
(
close(c.temp, 0.2, 0.65).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) * 0.015,
None,
)
}),
(MediumGrass, false, |c, _| {
(
close(c.temp, 0.2, 0.6).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) * 0.012,
None,
)
}),
(LongGrass, false, |c, _| {
(
close(c.temp, 0.3, 0.35).min(close(c.humidity, CONFIG.jungle_hum, 0.3)) * 0.15,
Some((48.0, 0.2)),
)
}),
// Jungle Sprites
// (LongGrass, false, |c, col| {
// (
// close(c.temp, CONFIG.tropical_temp, 0.4).min(close(
// c.humidity,
// CONFIG.jungle_hum,
// 0.6,
// )) * 0.08,
// Some((60.0, 5.0)),
// )
// }),
/*(WheatGreen, false, |c, col| {
(
close(c.temp, 0.4, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.1))
* MUSH_FACT
* 0.001,
None,
)
}),*/
(GrassSnow, false, |c, _| {
(
close(c.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
c.humidity,
CONFIG.forest_hum,
0.5,
)) * 0.01,
Some((48.0, 0.2)),
)
}),
// Desert Plants
(DeadBush, false, |c, _| {
(
close(c.temp, 1.0, 0.95).min(close(c.humidity, 0.0, 0.3)) * MUSH_FACT * 15.0,
None,
)
}),
(LargeCactus, false, |c, _| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
/*(BarrelCactus, false, |c, col| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(RoundCactus, false, |c, col| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(ShortCactus, false, |c, col| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(MedFlatCactus, false, |c, col| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),
(ShortFlatCactus, false, |c, col| {
(
close(c.temp, CONFIG.desert_temp + 0.2, 0.3).min(close(
c.humidity,
CONFIG.desert_hum,
0.2,
)) * MUSH_FACT
* 0.1,
None,
)
}),*/
(Reed, false, |c, col| {
(
close(c.humidity, CONFIG.jungle_hum, 0.7)
* 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)),
)
}),
];
for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 {
let offs = Vec2::new(x, y);
let wpos2d = wpos2d + offs;
// Sample terrain
let col_sample = if let Some(col_sample) = get_column(offs) {
col_sample
} else {
continue;
};
let underwater = col_sample.water_level > col_sample.alt;
let bk = scatter
.iter()
.enumerate()
.find_map(|(i, (bk, is_underwater, f))| {
let (density, patch) = f(chunk, col_sample);
let is_patch = patch
.map(|(wavelen, threshold)| {
index
.noise
.scatter_nz
.get(
wpos2d
.map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0)
.into_array(),
)
.abs()
> 1.0 - threshold as f64
})
.unwrap_or(true);
if density > 0.0
&& is_patch
&& RandomField::new(i as u32)
.chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
&& underwater == *is_underwater
{
Some(*bk)
} else {
None
}
});
if let Some(bk) = bk {
let alt = col_sample.alt as i32;
// Find the intersection between ground and air, if there is one near the
// surface
if let Some(solid_end) = (-4..8)
.find(|z| {
vol.get(Vec3::new(offs.x, offs.y, alt + z))
.map(|b| b.is_solid())
.unwrap_or(false)
})
.and_then(|solid_start| {
(1..8).map(|z| solid_start + z).find(|z| {
vol.get(Vec3::new(offs.x, offs.y, alt + z))
.map(|b| !b.is_solid())
.unwrap_or(true)
})
})
{
let _ = vol.set(
Vec3::new(offs.x, offs.y, alt + solid_end),
Block::new(bk, Rgb::broadcast(0)),
);
}
}
}
}
}

View File

@ -17,6 +17,7 @@ mod column;
pub mod config;
pub mod index;
pub mod layer;
pub mod rtsim;
pub mod sim;
pub mod sim2;
pub mod site;

1
world/src/rtsim/mod.rs Normal file
View File

@ -0,0 +1 @@

View File

@ -1657,7 +1657,7 @@ impl WorldSim {
let new_chunk = this.get(downhill_pos)?;
const SLIDE_THRESHOLD: f32 = 5.0;
if new_chunk.is_underwater() || new_chunk.alt + SLIDE_THRESHOLD < chunk.alt {
if new_chunk.river.near_water() || new_chunk.alt + SLIDE_THRESHOLD < chunk.alt {
break;
} else {
chunk = new_chunk;
@ -2184,6 +2184,7 @@ impl SimChunk {
humidity,
rockiness: if true {
(gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
//.add(if river.near_river() { 20.0 } else { 0.0 })
.sub(0.1)
.mul(1.3)
.max(0.0)

View File

@ -421,9 +421,7 @@ impl Floor {
let offs = Vec2::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0))
.try_normalized()
.unwrap_or_else(Vec2::unit_y)
* FLOOR_SIZE.x as f32
/ 2.0
- 8.0;
* (TILE_SIZE as f32 / 2.0 - 4.0);
if !self.final_level {
supplement.add_entity(
EntityInfo::at((origin + stair_rcenter).map(|e| e as f32) + Vec3::from(offs))