Better surface swimming, no underwater sprites

This commit is contained in:
Joshua Barretto 2020-08-11 12:13:18 +01:00
parent c1b07465cf
commit 6992194ad4
17 changed files with 119 additions and 101 deletions

View File

@ -396,7 +396,7 @@ impl Client {
}
pub fn pick_up(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.read_component_copied(entity) {
if let Some(uid) = self.state.read_component_cloned(entity) {
self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Pickup(uid),
@ -520,7 +520,7 @@ impl Client {
}
pub fn mount(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.read_component_copied(entity) {
if let Some(uid) = self.state.read_component_cloned(entity) {
self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::Mount(uid)))
.unwrap();
@ -1294,7 +1294,7 @@ impl Client {
pub fn entity(&self) -> EcsEntity { self.entity }
/// Get the player's Uid.
pub fn uid(&self) -> Option<Uid> { self.state.read_component_copied(self.entity) }
pub fn uid(&self) -> Option<Uid> { self.state.read_component_cloned(self.entity) }
/// Get the client state
pub fn get_client_state(&self) -> ClientState { self.client_state }
@ -1347,7 +1347,7 @@ impl Client {
pub fn is_admin(&self) -> bool {
let client_uid = self
.state
.read_component_copied::<Uid>(self.entity)
.read_component_cloned::<Uid>(self.entity)
.expect("Client doesn't have a Uid!!!");
self.player_list

View File

@ -1,5 +1,4 @@
use crate::{path::Chaser, sync::Uid, comp::Body};
use serde::{Deserialize, Serialize};
use specs::{Component, Entity as EcsEntity};
use specs_idvs::IdvStorage;
use vek::*;

View File

@ -80,7 +80,7 @@ pub struct PhysicsState {
pub on_ceiling: bool,
pub on_wall: Option<Vec3<f32>>,
pub touch_entity: Option<Uid>,
pub in_fluid: bool,
pub in_fluid: Option<f32>, // Depth
}
impl PhysicsState {

View File

@ -199,8 +199,8 @@ impl State {
}
/// Read a component attributed to a particular entity.
pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
self.ecs.read_storage().get(entity).copied()
pub fn read_component_cloned<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
self.ecs.read_storage().get(entity).cloned()
}
/// Get a read-only reference to the storage of a particular component type.

View File

@ -24,7 +24,7 @@ impl CharacterBehavior for Data {
update.character = CharacterState::GlideWield;
return update;
}
if data.physics.in_fluid {
if data.physics.in_fluid.map(|depth| depth > 0.5).unwrap_or(false) {
update.character = CharacterState::Idle;
}
// If there is a wall in front of character and they are trying to climb go to

View File

@ -19,7 +19,7 @@ impl CharacterBehavior for Data {
if !data.physics.on_ground {
update.character = CharacterState::Glide;
}
if data.physics.in_fluid {
if data.physics.in_fluid.map(|depth| depth > 0.5).unwrap_or(false) {
update.character = CharacterState::Idle;
}

View File

@ -8,7 +8,7 @@ use crate::{
sys::{character_behavior::JoinData, phys::GRAVITY},
util::Dir,
};
use vek::vec::Vec2;
use vek::*;
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
const BASE_HUMANOID_AIR_ACCEL: f32 = 8.0;
@ -67,8 +67,8 @@ impl Body {
/// Handles updating `Components` to move player based on state of `JoinData`
pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
if data.physics.in_fluid {
swim_move(data, update, efficiency);
if let Some(depth) = data.physics.in_fluid {
swim_move(data, update, efficiency, depth);
} else {
basic_move(data, update, efficiency);
}
@ -104,7 +104,7 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, rate: f32)
}
/// Updates components to move player as if theyre swimming
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: f32) {
// Update velocity
update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir
@ -120,7 +120,7 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
// Swim
if data.inputs.swimup.is_pressed() {
update.vel.0.z =
(update.vel.0.z + data.dt.0 * GRAVITY * 4.0).min(BASE_HUMANOID_WATER_SPEED);
(update.vel.0.z + data.dt.0 * GRAVITY * 4.0 * depth.clamped(0.0, 1.0).powf(3.0)).min(BASE_HUMANOID_WATER_SPEED);
}
// Swim
if data.inputs.swimdown.is_pressed() {
@ -192,14 +192,14 @@ pub fn attempt_swap_loadout(data: &JoinData, update: &mut StateUpdate) {
/// Checks that player can wield the glider and updates `CharacterState` if so
pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) {
if data.physics.on_ground && !data.physics.in_fluid && data.body.is_humanoid() {
if data.physics.on_ground && !data.physics.in_fluid.map(|depth| depth > 1.0).unwrap_or(false) && data.body.is_humanoid() {
update.character = CharacterState::GlideWield;
}
}
/// Checks that player can jump and sends jump event if so
pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.jump.is_pressed() && data.physics.on_ground && !data.physics.in_fluid {
if data.inputs.jump.is_pressed() && data.physics.on_ground && !data.physics.in_fluid.map(|depth| depth > 1.0).unwrap_or(false) {
update
.local_events
.push_front(LocalEvent::Jump(data.entity));

View File

@ -241,7 +241,8 @@ impl<'a> System<'a> for Sys {
bearing.xy().try_normalized().unwrap_or(Vec2::zero())
* speed.min(0.2 + (dist - AVG_FOLLOW_DIST) / 8.0);
inputs.jump.set_state(bearing.z > 1.5);
inputs.swim.set_state(bearing.z > 0.5);
inputs.swimup.set_state(bearing.z > 0.5);
inputs.swimdown.set_state(bearing.z < 0.5);
}
} else {
do_idle = true;
@ -331,7 +332,8 @@ impl<'a> System<'a> for Sys {
.unwrap_or(Vec2::zero())
* speed;
inputs.jump.set_state(bearing.z > 1.5);
inputs.swim.set_state(bearing.z > 0.5);
inputs.swimup.set_state(bearing.z > 0.5);
inputs.swimdown.set_state(bearing.z < 0.5);
}
} else {
do_idle = true;
@ -399,7 +401,8 @@ impl<'a> System<'a> for Sys {
.unwrap_or(Vec2::zero())
* speed;
inputs.jump.set_state(bearing.z > 1.5);
inputs.swim.set_state(bearing.z > 0.5);
inputs.swimup.set_state(bearing.z > 0.5);
inputs.swimdown.set_state(bearing.z < 0.5);
}
if dist_sqrd < 16.0f32.powf(2.0)

View File

@ -13,6 +13,7 @@ use specs::{
saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
};
use vek::*;
use std::ops::Range;
pub const GRAVITY: f32 = 9.81 * 5.0;
const BOUYANCY: f32 = 1.0;
@ -123,7 +124,7 @@ impl<'a> System<'a> for Sys {
} else {
0.0
})
.max(if physics_state.in_fluid {
.max(if physics_state.in_fluid.is_some() {
FRIC_FLUID
} else {
0.0
@ -133,7 +134,7 @@ impl<'a> System<'a> for Sys {
.is_some();
let downward_force = if !in_loaded_chunk {
0.0 // No gravity in unloaded chunks
} else if physics_state.in_fluid {
} else if physics_state.in_fluid.map(|depth| depth > 0.75).unwrap_or(false) {
(1.0 - BOUYANCY) * GRAVITY
} else {
GRAVITY
@ -175,31 +176,47 @@ impl<'a> System<'a> for Sys {
.flatten()
.flatten();
// Function for determining whether the player at a specific position collides
// with the ground
let collision_with = |pos: Vec3<f32>,
hit: &dyn Fn(&Block) -> bool,
near_iter| {
for (i, j, k) in near_iter {
let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k);
// Function for iterating over the blocks the player at a specific position collides
// with
fn collision_iter<'a>(
pos: Vec3<f32>,
terrain: &'a TerrainGrid,
hit: &'a dyn Fn(&Block) -> bool,
near_iter: impl Iterator<Item=(i32, i32, i32)> + 'a,
radius: f32,
z_range: Range<f32>,
) -> impl Iterator<Item=Aabb<f32>> + 'a {
near_iter
.filter_map(move |(i, j, k)| {
let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k);
if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) {
let player_aabb = Aabb {
min: pos + Vec3::new(-radius, -radius, z_min),
max: pos + Vec3::new(radius, radius, z_max),
};
let block_aabb = Aabb {
min: block_pos.map(|e| e as f32),
max: block_pos.map(|e| e as f32)
+ Vec3::new(1.0, 1.0, block.get_height()),
};
if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) {
let player_aabb = Aabb {
min: pos + Vec3::new(-radius, -radius, z_range.start),
max: pos + Vec3::new(radius, radius, z_range.end),
};
let block_aabb = Aabb {
min: block_pos.map(|e| e as f32),
max: block_pos.map(|e| e as f32)
+ Vec3::new(1.0, 1.0, block.get_height()),
};
if player_aabb.collides_with_aabb(block_aabb) {
return true;
if player_aabb.collides_with_aabb(block_aabb) {
return Some(block_aabb);
}
}
}
}
false
None
})
};
// Function for determining whether the player at a specific position collides
// with blocks with the given criteria
let collision_with = |pos: Vec3<f32>,
hit: &dyn Fn(&Block) -> bool,
near_iter|
{
collision_iter(pos, &terrain, hit, near_iter, radius, z_min..z_max).count() > 0
};
let was_on_ground = physics_state.on_ground;
@ -400,8 +417,9 @@ impl<'a> System<'a> for Sys {
}
// Figure out if we're in water
physics_state.in_fluid =
collision_with(pos.0, &|block| block.is_fluid(), near_iter.clone());
physics_state.in_fluid = collision_iter(pos.0, &terrain, &|block| block.is_fluid(), near_iter.clone(), radius, z_min..z_max)
.max_by_key(|block_aabb| (block_aabb.max.z * 100.0) as i32)
.map(|block_aabb| block_aabb.max.z - pos.0.z);
},
Collider::Point => {
let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta).ignore_error().cast();

View File

@ -262,7 +262,7 @@ fn handle_jump(
action: &ChatCommand,
) {
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
match server.state.read_component_copied::<comp::Pos>(target) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(current_pos) => {
server
.state
@ -287,7 +287,7 @@ fn handle_goto(
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
if server
.state
.read_component_copied::<comp::Pos>(target)
.read_component_cloned::<comp::Pos>(target)
.is_some()
{
server
@ -498,9 +498,9 @@ fn handle_tp(
);
return;
};
if let Some(_pos) = server.state.read_component_copied::<comp::Pos>(target) {
if let Some(_pos) = server.state.read_component_cloned::<comp::Pos>(target) {
if let Some(player) = opt_player {
if let Some(pos) = server.state.read_component_copied::<comp::Pos>(player) {
if let Some(pos) = server.state.read_component_cloned::<comp::Pos>(player) {
server.state.write_component(target, pos);
server.state.write_component(target, comp::ForceUpdate);
} else {
@ -545,7 +545,7 @@ fn handle_spawn(
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => {
let uid = server
.state
.read_component_copied(target)
.read_component_cloned(target)
.expect("Expected player to have a UID");
if let Some(alignment) = parse_alignment(uid, &opt_align) {
let amount = opt_amount
@ -556,7 +556,7 @@ fn handle_spawn(
let ai = opt_ai.unwrap_or_else(|| "true".to_string());
match server.state.read_component_copied::<comp::Pos>(target) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => {
let agent =
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
@ -666,7 +666,7 @@ fn handle_spawn_training_dummy(
_args: String,
_action: &ChatCommand,
) {
match server.state.read_component_copied::<comp::Pos>(target) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => {
let vel = Vec3::new(
rand::thread_rng().gen_range(-2.0, 3.0),
@ -707,7 +707,7 @@ fn handle_spawn_campfire(
_args: String,
_action: &ChatCommand,
) {
match server.state.read_component_copied::<comp::Pos>(target) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => {
server
.state
@ -1066,7 +1066,7 @@ fn handle_explosion(
let ecs = server.state.ecs();
match server.state.read_component_copied::<comp::Pos>(target) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => {
ecs.read_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::Explosion {
@ -1090,7 +1090,7 @@ fn handle_waypoint(
_args: String,
_action: &ChatCommand,
) {
match server.state.read_component_copied::<comp::Pos>(target) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => {
let time = server.state.ecs().read_resource();
let _ = server
@ -1126,7 +1126,7 @@ fn handle_adminify(
Some(player) => {
let is_admin = if server
.state
.read_component_copied::<comp::Admin>(player)
.read_component_cloned::<comp::Admin>(player)
.is_some()
{
ecs.write_storage::<comp::Admin>().remove(player);
@ -1670,7 +1670,7 @@ fn handle_remove_lights(
action: &ChatCommand,
) {
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
let opt_player_pos = server.state.read_component_copied::<comp::Pos>(target);
let opt_player_pos = server.state.read_component_cloned::<comp::Pos>(target);
let mut to_delete = vec![];
match opt_player_pos {

View File

@ -251,7 +251,7 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
.is_some()
{
let respawn_point = state
.read_component_copied::<comp::Waypoint>(entity)
.read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);

View File

@ -167,10 +167,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
thrown_items.push((
*pos,
state
.read_component_copied::<comp::Vel>(entity)
.read_component_cloned::<comp::Vel>(entity)
.unwrap_or_default(),
state
.read_component_copied::<comp::Ori>(entity)
.read_component_cloned::<comp::Ori>(entity)
.unwrap_or_default(),
*kind,
));
@ -185,7 +185,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
state.read_storage::<comp::Pos>().get(entity)
{
let uid = state
.read_component_copied(entity)
.read_component_cloned(entity)
.expect("Expected player to have a UID");
if (
&state.read_storage::<comp::Alignment>(),
@ -341,7 +341,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
dropped_items.push((
*pos,
state
.read_component_copied::<comp::Ori>(entity)
.read_component_cloned::<comp::Ori>(entity)
.unwrap_or_default(),
item,
));
@ -373,10 +373,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
for _ in 0..amount {
dropped_items.push((
state
.read_component_copied::<comp::Pos>(entity)
.read_component_cloned::<comp::Pos>(entity)
.unwrap_or_default(),
state
.read_component_copied::<comp::Ori>(entity)
.read_component_cloned::<comp::Ori>(entity)
.unwrap_or_default(),
item.clone(),
));
@ -407,7 +407,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
+ Vec3::unit_z() * 15.0
+ Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0;
let uid = state.read_component_copied::<Uid>(entity);
let uid = state.read_component_cloned::<Uid>(entity);
let mut new_entity = state
.create_object(Default::default(), match kind {

View File

@ -20,7 +20,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
// Note: If other `ServerEvent`s are referring to this entity they will be
// disrupted
let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
let maybe_uid = state.read_component_copied::<Uid>(entity);
let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
let maybe_group = state
.ecs()

View File

@ -173,7 +173,7 @@ impl StateExt for State {
self.write_component(entity, comp::CharacterState::default());
self.write_component(
entity,
comp::Alignment::Owned(self.read_component_copied(entity).unwrap()),
comp::Alignment::Owned(self.read_component_cloned(entity).unwrap()),
);
// Set the character id for the player
@ -213,7 +213,7 @@ impl StateExt for State {
// Notify clients of a player list update
let client_uid = self
.read_component_copied::<Uid>(entity)
.read_component_cloned::<Uid>(entity)
.map(|u| u)
.expect("Client doesn't have a Uid!!!");

View File

@ -505,7 +505,7 @@ impl<'a> Widget for Social<'a> {
})
.or_else(|| {
self.selected_entity
.and_then(|s| self.client.state().read_component_copied(s.0))
.and_then(|s| self.client.state().read_component_cloned(s.0))
})
.filter(|selected| {
// Prevent inviting entities already in the same group
@ -564,7 +564,7 @@ impl<'a> Widget for Social<'a> {
});
}
}
} // End of Online Tab
} // End of Online Tab
events
}

View File

@ -581,7 +581,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::character::StandAnimation::update_skeleton(
@ -823,7 +823,7 @@ impl FigureMgr {
)
},
CharacterState::Wielding { .. } => {
if physics.in_fluid {
if physics.in_fluid.is_some() {
anim::character::SwimWieldAnimation::update_skeleton(
&target_base,
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
@ -949,7 +949,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => {
@ -1047,7 +1047,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => {
@ -1143,7 +1143,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => {
@ -1237,7 +1237,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton(
@ -1329,7 +1329,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::fish_medium::IdleAnimation::update_skeleton(
@ -1404,7 +1404,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::dragon::IdleAnimation::update_skeleton(
@ -1478,7 +1478,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::critter::IdleAnimation::update_skeleton(
@ -1553,7 +1553,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::bird_small::IdleAnimation::update_skeleton(
@ -1628,7 +1628,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::fish_small::IdleAnimation::update_skeleton(
@ -1703,7 +1703,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::biped_large::IdleAnimation::update_skeleton(
@ -1795,7 +1795,7 @@ impl FigureMgr {
let target_base = match (
physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water
physics.in_fluid.is_some(), // In water
) {
// Standing
(true, false, false) => anim::golem::IdleAnimation::update_skeleton(

View File

@ -31,49 +31,44 @@ pub fn apply_scatter_to<'a>(
chunk: &SimChunk,
) {
use BlockKind::*;
let scatter: &[(_, fn(&SimChunk) -> (f32, Option<(f32, f32)>))] = &[
let scatter: &[(_, bool, fn(&SimChunk) -> (f32, Option<(f32, f32)>))] = &[
// (density, Option<(wavelen, threshold)>)
(BlueFlower, |c| {
(BlueFlower, false, |c| {
(
close(c.temp, -0.3, 0.7).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.6)),
)
}),
(PinkFlower, |c| {
(PinkFlower, false, |c| {
(
close(c.temp, 0.15, 0.5).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.6)),
)
}),
(DeadBush, |c| {
(DeadBush, false, |c| {
(
close(c.temp, 0.8, 0.3).min(close(c.humidity, 0.0, 0.4)) * 0.015,
None,
)
}),
(Twigs, |c| ((c.tree_density - 0.5).max(0.0) * 0.0025, None)),
(Stones, |c| ((c.rockiness - 0.5).max(0.0) * 0.005, None)),
(ShortGrass, |c| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.4)),
)
}),
(MediumGrass, |c| {
(Twigs, false, |c| ((c.tree_density - 0.5).max(0.0) * 0.0025, None)),
(Stones, false, |c| ((c.rockiness - 0.5).max(0.0) * 0.005, None)),
(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, |c| {
(LongGrass, false, |c| {
(
close(c.temp, 0.4, 0.4).min(close(c.humidity, 0.8, 0.2)) * 0.05,
Some((48.0, 0.1)),
)
}),
(GrassSnow, |c| {
(GrassSnow, false, |c| {
(
close(c.temp, -0.4, 0.4).min(close(c.rockiness, 0.0, 0.5)),
close(c.temp, -0.4, 0.4).min(close(c.rockiness, 0.0, 0.5)) * 0.1,
Some((48.0, 0.6)),
)
}),
@ -92,7 +87,9 @@ pub fn apply_scatter_to<'a>(
continue;
};
let bk = scatter.iter().enumerate().find_map(|(i, (bk, f))| {
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);
if density <= 0.0
|| patch
@ -105,6 +102,7 @@ pub fn apply_scatter_to<'a>(
})
.unwrap_or(false)
|| !RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
|| underwater != *is_underwater
{
None
} else {