pass humidity to meta, move smoke cycle to separate file

This commit is contained in:
Christof Petig 2022-05-25 20:49:11 +02:00
parent f12f79439f
commit 59d876a654
6 changed files with 261 additions and 86 deletions

View File

@ -88,6 +88,7 @@ pub struct TerrainChunkMeta {
contains_river: bool,
river_velocity: Vec3<f32>,
temp: f32,
humidity: f32,
contains_settlement: bool,
contains_dungeon: bool,
}
@ -102,6 +103,7 @@ impl TerrainChunkMeta {
contains_river: bool,
river_velocity: Vec3<f32>,
temp: f32,
humidity: f32,
contains_settlement: bool,
contains_dungeon: bool,
) -> Self {
@ -114,6 +116,7 @@ impl TerrainChunkMeta {
contains_river,
river_velocity,
temp,
humidity,
contains_settlement,
contains_dungeon,
}
@ -129,6 +132,7 @@ impl TerrainChunkMeta {
contains_river: false,
river_velocity: Vec3::zero(),
temp: 0.0,
humidity: 0.0,
contains_settlement: false,
contains_dungeon: false,
}
@ -153,6 +157,8 @@ impl TerrainChunkMeta {
pub fn contains_dungeon(&self) -> bool { self.contains_dungeon }
pub fn temp(&self) -> f32 { self.temp }
pub fn humidity(&self) -> f32 { self.humidity }
}
// Terrain type aliases

View File

@ -5,6 +5,7 @@ pub mod lod;
pub mod math;
pub mod particle;
pub mod simple;
pub mod smoke_cycle;
pub mod terrain;
pub mod trail;

View File

@ -24,6 +24,7 @@ use common::{
use common_base::span;
use hashbrown::HashMap;
use rand::prelude::*;
//use rand_chacha::ChaCha8Rng;
use specs::{saveload::MarkerAllocator, Join, WorldExt};
use std::{
f32::consts::{PI, TAU},
@ -1249,9 +1250,35 @@ impl ParticleMgr {
}
// smoke is more complex as it comes with varying rate and color
{
// fn create_smoke(
// position: Vec3<i32>,
// temperature: f32,
// humidity: f32,
// time_of_day: f32,
// ) -> FireplaceProperties {
// let mut rng2 = ChaCha8Rng::from_seed(seed_from_pos(pos));
// let strength_mod = (0.5_f32 - temperature).max(0.0); // -0.5 (desert) to
// 1.5 (ice) let strength =
// rng2.gen_range((5.0 * strength_mod)..(100.0 * strength_mod).max(1.0))
// as u8; let dryness = (biome_dryness(chunk.meta().biome()) +
// rng2.gen_range(-20..20)) .min(255)
// .max(0) as u8;
// // tracing::trace!(?pos, ?strength, ?dryness);
// FireplaceProperties::new(pos, dryness, strength)
// }
struct FirePlaceProperties {
position: Vec3<i32>,
strength: f32,
dry_chance: f32,
}
let range = 8_usize;
let rate = 3.0 / 128.0;
let lifetime = 40.0;
let time_of_day = scene_data
.state
.get_time_of_day()
.rem_euclid(24.0 * 60.0 * 60.0) as f32;
// mode: ParticleMode::CampfireSmoke,
for offset in Spiral2d::new().take((range * 2 + 1).pow(2)) {
@ -1259,32 +1286,49 @@ impl ParticleMgr {
terrain.get(chunk_pos).map(|chunk_data| {
let blocks = &chunk_data.blocks_of_interest.smokers;
let sum = blocks
.iter()
.fold(0u32, |sum, smoker| sum + smoker.strength as u32);
let avg_particles = dt * sum as f32 * rate;
let mut smoke_properties: Vec<FirePlaceProperties> = Vec::new();
let block_pos =
Vec3::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
let mut sum = 0.0_f32;
for smoker in blocks.iter() {
let position = block_pos + smoker.position;
let prop = crate::scene::smoke_cycle::smoke_at_time(
position,
smoker.temperature,
smoker.humidity,
time_of_day,
);
sum += prop.0;
smoke_properties.push(FirePlaceProperties {
position,
strength: prop.0,
dry_chance: 0.5,
});
}
// let sum = blocks
// .iter()
// .fold(0u32, |sum, smoker| sum + smoker.strength as u32);
let avg_particles = dt * sum as f32 * rate;
let particle_count = avg_particles.trunc() as usize
+ (rng.gen::<f32>() < avg_particles.fract()) as usize;
let chosen =
blocks.choose_multiple_weighted(&mut rng, particle_count, |smoker| {
smoker.strength as u32
});
let chosen = smoke_properties.choose_multiple_weighted(
&mut rng,
particle_count,
|smoker| smoker.strength,
);
if let Ok(chosen) = chosen {
let mut smoke_particles: Vec<Particle> = chosen
.map(|smoker| {
Particle::new(
Duration::from_secs_f32(lifetime),
time,
if rng.gen::<u8>() > smoker.dryness {
if rng.gen::<f32>() > smoker.dry_chance {
ParticleMode::BlackSmoke
} else {
ParticleMode::CampfireSmoke
},
(block_pos + smoker.position)
.map(|e: i32| e as f32 + rng.gen::<f32>()),
smoker.position.map(|e: i32| e as f32 + rng.gen::<f32>()),
)
})
.collect();

View File

@ -0,0 +1,183 @@
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use vek::*;
// create pseudorandom from position
fn seed_from_pos(pos: Vec3<i32>) -> [u8; 32] {
[
pos.x as u8,
(pos.x >> 8) as u8,
(pos.x >> 16) as u8,
(pos.x >> 24) as u8,
0,
0,
0,
0,
pos.y as u8,
(pos.y >> 8) as u8,
(pos.y >> 16) as u8,
(pos.y >> 24) as u8,
0,
0,
0,
0,
pos.z as u8,
(pos.z >> 8) as u8,
(pos.z >> 16) as u8,
(pos.z >> 24) as u8,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
}
struct FireplaceTiming {
// all this assumes sunrise at 6am, sunset at 6pm
breakfast: f32, // 5am to 7am
dinner: f32, // 5pm to 7pm
daily_cycle: f32, // 30min to 2hours
}
const SMOKE_BREAKFAST_STRENGTH: f32 = 96.0;
const SMOKE_BREAKFAST_HALF_DURATION: f32 = 45.0 * 60.0;
const SMOKE_BREAKFAST_START: f32 = 5.0 * 60.0 * 60.0;
const SMOKE_BREAKFAST_RANGE: f32 = 2.0 * 60.0 * 60.0;
const SMOKE_DINNER_STRENGTH: f32 = 128.0;
const SMOKE_DINNER_HALF_DURATION: f32 = 60.0 * 60.0;
const SMOKE_DINNER_START: f32 = 17.0 * 60.0 * 60.0;
const SMOKE_DINNER_RANGE: f32 = 2.0 * 60.0 * 60.0;
const SMOKE_DAILY_CYCLE_MIN: f32 = 30.0 * 60.0;
const SMOKE_DAILY_CYCLE_MAX: f32 = 120.0 * 60.0;
const SMOKE_MAX_TEMPERATURE: f32 = 0.0; // temperature for nominal smoke (0..daily_var)
const SMOKE_MAX_TEMP_VALUE: f32 = 1.0;
const SMOKE_TEMP_MULTIPLIER: f32 = 96.0;
const SMOKE_DAILY_VARIATION: f32 = 32.0;
struct FireplaceClimate {
daily_strength: f32, // can be negative (offset)
day_start: f32, // seconds since breakfast for daily cycle
day_end: f32, // seconds before dinner on daily cycle
}
fn create_timing(rng: &mut ChaCha8Rng) -> FireplaceTiming {
let breakfast: f32 = SMOKE_BREAKFAST_START + rng.gen::<f32>() * SMOKE_BREAKFAST_RANGE;
let dinner: f32 = SMOKE_DINNER_START + rng.gen::<f32>() * SMOKE_DINNER_RANGE;
let daily_cycle: f32 =
SMOKE_DAILY_CYCLE_MIN + rng.gen::<f32>() * (SMOKE_DAILY_CYCLE_MAX - SMOKE_DAILY_CYCLE_MIN);
FireplaceTiming {
breakfast,
dinner,
daily_cycle,
}
}
fn create_climate(temperature: f32, _humidity: f32) -> FireplaceClimate {
// temp -1…1, humidity 0…1
let daily_strength =
(SMOKE_MAX_TEMPERATURE - temperature).min(SMOKE_MAX_TEMP_VALUE) * SMOKE_TEMP_MULTIPLIER;
// when is breakfast down to daily strength
// daily_strength ==
// SMOKE_BREAKFAST_STRENGTH*(1.0-(t-breakfast)/SMOKE_BREAKFAST_HALF_DURATION)
// (t-breakfast) = (1.0 -
// daily_strength/SMOKE_BREAKFAST_STRENGTH)*SMOKE_BREAKFAST_HALF_DURATION
let day_start = (SMOKE_BREAKFAST_STRENGTH - daily_strength.max(0.0))
* (SMOKE_BREAKFAST_HALF_DURATION / SMOKE_BREAKFAST_STRENGTH);
let day_end = (SMOKE_DINNER_STRENGTH - daily_strength.max(0.0))
* (SMOKE_DINNER_HALF_DURATION / SMOKE_DINNER_STRENGTH);
FireplaceClimate {
daily_strength,
day_start,
day_end,
}
}
type Increasing = bool;
pub fn smoke_at_time(
position: Vec3<i32>,
temperature: f32,
humidity: f32,
time_of_day: f32,
) -> (f32, Increasing) {
let mut pseudorandom = ChaCha8Rng::from_seed(seed_from_pos(position));
let timing = create_timing(&mut pseudorandom);
let climate = create_climate(temperature, humidity);
let after_breakfast = time_of_day - timing.breakfast;
//let after_dinner = time_of_day-timing.dinner;
if after_breakfast < -SMOKE_BREAKFAST_HALF_DURATION {
/* night */
(0.0, false)
} else if after_breakfast < 0.0 {
/* cooking breakfast */
(
(SMOKE_BREAKFAST_HALF_DURATION + after_breakfast)
* (SMOKE_BREAKFAST_STRENGTH / SMOKE_BREAKFAST_HALF_DURATION),
true,
)
} else if time_of_day < climate.day_start {
/* cooling */
(
(SMOKE_BREAKFAST_HALF_DURATION - after_breakfast)
* (SMOKE_BREAKFAST_STRENGTH / SMOKE_BREAKFAST_HALF_DURATION),
false,
)
} else if time_of_day < climate.day_end {
/* day cycle */
let day_phase = ((time_of_day - climate.day_start) / timing.daily_cycle).fract();
if day_phase < 0.5 {
(
(climate.daily_strength + day_phase * (2.0 * SMOKE_DAILY_VARIATION)).max(0.0),
true,
)
} else {
(
(climate.daily_strength + (1.0 - day_phase) * (2.0 * SMOKE_DAILY_VARIATION))
.max(0.0),
false,
)
}
} else if time_of_day < timing.dinner {
/* cooking dinner */
(
(SMOKE_BREAKFAST_HALF_DURATION + time_of_day - timing.dinner)
* (SMOKE_BREAKFAST_STRENGTH / SMOKE_BREAKFAST_HALF_DURATION),
true,
)
} else {
/* cooling + night */
(
(SMOKE_BREAKFAST_HALF_DURATION - time_of_day + timing.dinner).max(0.0)
* (SMOKE_BREAKFAST_STRENGTH / SMOKE_BREAKFAST_HALF_DURATION),
false,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_smoke() {
let position = Vec3::new(22_i32, 11, 33);
let temperature = -0.5;
let humidity = 0.5;
for i in (0..24 * 4) {
let time_of_day = 15.0 * 60.0 * (i as f32);
println!(
"{:.2} {}",
time_of_day / (60.0 * 60.0),
smoke_at_time(position, temperature, humidity, time_of_day)
);
}
}
}

View File

@ -1,5 +1,5 @@
use crate::hud::CraftingTab;
use common::terrain::{BiomeKind, BlockKind, SpriteKind, TerrainChunk};
use common::terrain::{BlockKind, SpriteKind, TerrainChunk};
use common_base::span;
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
@ -12,18 +12,18 @@ pub enum Interaction {
Mine,
}
pub struct SmokeProperties {
pub struct FireplaceProperties {
pub position: Vec3<i32>,
pub dryness: u8, // 0 = black smoke, 255 = white
pub strength: u8, // 0 = thin, 128 = normal, 255 = very strong
pub humidity: f32,
pub temperature: f32,
}
impl SmokeProperties {
fn new(position: Vec3<i32>, dryness: u8, strength: u8) -> Self {
impl FireplaceProperties {
fn new(position: Vec3<i32>, humidity: f32, temperature: f32) -> Self {
Self {
position,
dryness,
strength,
humidity,
temperature,
}
}
}
@ -36,7 +36,7 @@ pub struct BlocksOfInterest {
pub slow_river: Vec<Vec3<i32>>,
pub fast_river: Vec<Vec3<i32>>,
pub fires: Vec<Vec3<i32>>,
pub smokers: Vec<SmokeProperties>,
pub smokers: Vec<FireplaceProperties>,
pub beehives: Vec<Vec3<i32>>,
pub reeds: Vec<Vec3<i32>>,
pub fireflies: Vec<Vec3<i32>>,
@ -54,60 +54,6 @@ pub struct BlocksOfInterest {
pub lights: Vec<(Vec3<i32>, u8)>,
}
fn biome_dryness(biome: BiomeKind) -> i32 {
match biome {
BiomeKind::Void => 0,
BiomeKind::Lake => 0,
BiomeKind::Ocean => 0,
BiomeKind::Swamp => 10,
BiomeKind::Jungle => 60,
BiomeKind::Snowland => 60,
BiomeKind::Desert => 100, // dry but dung
BiomeKind::Mountain => 160,
BiomeKind::Forest => 180,
BiomeKind::Taiga => 180,
BiomeKind::Grassland => 200,
BiomeKind::Savannah => 240,
}
}
fn seed_from_pos(pos: Vec3<i32>) -> [u8; 32] {
[
pos.x as u8,
(pos.x >> 8) as u8,
(pos.x >> 16) as u8,
(pos.x >> 24) as u8,
0,
0,
0,
0,
pos.y as u8,
(pos.y >> 8) as u8,
(pos.y >> 16) as u8,
(pos.y >> 24) as u8,
0,
0,
0,
0,
pos.z as u8,
(pos.z >> 8) as u8,
(pos.z >> 16) as u8,
(pos.z >> 24) as u8,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
}
impl BlocksOfInterest {
pub fn from_chunk(chunk: &TerrainChunk) -> Self {
span!(_guard, "from_chunk", "BlocksOfInterest::from_chunk");
@ -158,18 +104,12 @@ impl BlocksOfInterest {
BlockKind::Snow | BlockKind::Ice if rng.gen_range(0..16) == 0 => snow.push(pos),
_ => match block.get_sprite() {
Some(SpriteKind::Ember) => {
let mut rng2 = ChaCha8Rng::from_seed(seed_from_pos(pos));
let strength_mod = (0.5_f32 - chunk.meta().temp()).max(0.0); // -0.5 (desert) to 1.5 (ice)
let strength = rng2
.gen_range((5.0 * strength_mod)..(100.0 * strength_mod).max(1.0))
as u8;
let dryness = (biome_dryness(chunk.meta().biome())
+ rng2.gen_range(-20..20))
.min(255)
.max(0) as u8;
// tracing::trace!(?pos, ?strength, ?dryness);
fires.push(pos);
smokers.push(SmokeProperties::new(pos, dryness, strength));
smokers.push(FireplaceProperties::new(
pos,
chunk.meta().humidity(),
chunk.meta().temp(),
));
},
// Offset positions to account for block height.
// TODO: Is this a good idea?
@ -197,7 +137,7 @@ impl BlocksOfInterest {
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
},
Some(SpriteKind::SmokeDummy) => {
smokers.push(SmokeProperties::new(pos, 255, 128));
smokers.push(FireplaceProperties::new(pos, 0.0, -1.0));
},
Some(SpriteKind::Forge) => interactables
.push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))),

View File

@ -294,6 +294,7 @@ impl World {
sim_chunk.river.is_river(),
sim_chunk.river.velocity,
sim_chunk.temp,
sim_chunk.humidity,
sim_chunk
.sites
.iter()