Merge branch 'isse/splish-splosh' into 'master'

Water splashes

See merge request veloren/veloren!4640
This commit is contained in:
Isse
2024-11-08 11:29:42 +00:00
19 changed files with 246 additions and 11 deletions

View File

@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- IP Bans
- Npcs can catch you stealing.
- Button to show unknown recipes.
- Water splashes and river particles.
### Changed

View File

@ -141,6 +141,27 @@
threshold: 7.0,
subtitle: "subtitle-lavapool",
),
SplashSmall: (
files: [
"voxygen.audio.sfx.footsteps.splash_small",
],
threshold: 0.0,
subtitle: "subtitle-splash"
),
SplashMedium: (
files: [
"voxygen.audio.sfx.footsteps.splash_medium",
],
threshold: 0.0,
subtitle: "subtitle-splash"
),
SplashBig: (
files: [
"voxygen.audio.sfx.footsteps.splash_big",
],
threshold: 0.0,
subtitle: "subtitle-splash"
),
//
// Character States
//

BIN
assets/voxygen/audio/sfx/footsteps/splash_big.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/splash_medium.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/footsteps/splash_small.ogg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -23,6 +23,7 @@ subtitle-swim = Swimming
subtitle-climb = Climbing
subtitle-damage = Damage
subtitle-death = Death
subtitle-splash = Splash
subtitle-wield_bow = Bow equipped
subtitle-unwield_bow = Bow unequipped

View File

@ -24,6 +24,7 @@ layout(location = 0) in vec3 f_pos;
layout(location = 1) flat in vec3 f_norm;
layout(location = 2) in vec4 f_col;
layout(location = 3) in float f_reflect;
layout(location = 4) flat in int f_mode;
layout(location = 0) out vec4 tgt_color;
layout(location = 1) out uvec4 tgt_mat;
@ -79,8 +80,10 @@ void main() {
// CPU) we need to some how find an approximation of how much the sun is blocked. We do this by fading out the sun
// as the particle moves underground. This isn't perfect, but it does at least mean that particles don't look like
// they're exposed to the sun when in dungeons
const float SUN_FADEOUT_DIST = 20.0;
sun_info.block *= clamp((f_pos.z - f_alt) / SUN_FADEOUT_DIST + 1, 0, 1);
const float LIGHT_FADEOUT_OFFSET = 50.0;
const float LIGHT_FADEOUT_DIST = 20.0;
sun_info.block *= clamp((f_pos.z - f_alt + LIGHT_FADEOUT_OFFSET) / LIGHT_FADEOUT_DIST + 1, 0, 1);
moon_info.block *= clamp((f_pos.z - f_alt + LIGHT_FADEOUT_OFFSET) / LIGHT_FADEOUT_DIST + 1, 0, 1);
// To account for prior saturation.
float max_light = 0.0;
@ -110,5 +113,14 @@ void main() {
// Temporarily disable particle transparency to avoid artifacts
tgt_color = vec4(surf_color, 1.0 /*f_col.a*/);
tgt_mat = uvec4(uvec3((f_norm + 1.0) * 127.0), MAT_BLOCK);
uint material = MAT_BLOCK;
const int WATER_FOAM = 64;
if (f_mode == WATER_FOAM) {
material = MAT_FLUID;
}
tgt_mat = uvec4(uvec3((f_norm + 1.0) * 127.0), material);
}

View File

@ -33,6 +33,7 @@ layout(location = 2) out vec4 f_col;
//layout(location = x) out float f_ao;
//layout(location = x) out float f_light;
layout(location = 3) out float f_reflect;
layout(location = 4) flat out int f_mode;
const float SCALE = 1.0 / 11.0;
@ -100,6 +101,7 @@ const int SPORE = 60;
const int SURPRISE_EGG = 61;
const int FLAME_TORNADO = 62;
const int POISON = 63;
const int WATER_FOAM = 64;
// meters per second squared (acceleration)
const float earth_gravity = 9.807;
@ -1058,6 +1060,15 @@ void main() {
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case WATER_FOAM:
f_reflect = 0.1;
attr = Attr(
inst_dir * pow(percent(), 0.5) * 0.5 + percent() * percent() * vec3(0, 0, -50),
vec3((1.5 * (1 - slow_start(0.2)))),
vec4(1.0, 1.0, 1.0, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
default:
attr = Attr(
linear_motion(
@ -1097,6 +1108,8 @@ void main() {
//vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0;
f_col = vec4(attr.col.rgb, attr.col.a);
f_mode = inst_mode;
gl_Position =
all_mat *
vec4(f_pos, 1);

View File

@ -51,7 +51,9 @@ impl Component for PosVelOriDefer {
/// no need to send it via network
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct PreviousPhysCache {
pub velocity: Vec3<f32>,
pub velocity_dt: Vec3<f32>,
pub in_fluid: Option<Fluid>,
/// Center of bounding sphere that encompasses the entity along its path for
/// this tick
pub center: Vec3<f32>,

View File

@ -186,6 +186,12 @@ pub enum Outcome {
uid: Uid,
head: usize,
},
Splash {
vel: Vec3<f32>,
pos: Vec3<f32>,
mass: f32,
kind: comp::fluid_dynamics::LiquidKind,
},
}
impl Outcome {
@ -225,7 +231,8 @@ impl Outcome {
| Outcome::TeleportedByPortal { pos}
| Outcome::FromTheAshes { pos }
| Outcome::ClayGolemDash { pos }
| Outcome::Glider { pos, .. } => Some(*pos),
| Outcome::Glider { pos, .. }
| Outcome::Splash { pos, .. } => Some(*pos),
Outcome::BreakBlock { pos, .. }
| Outcome::DamagedBlock { pos, .. }
| Outcome::SpriteUnlocked { pos }

View File

@ -10,6 +10,7 @@ mod interpolation;
pub mod melee;
mod mount;
pub mod phys;
mod phys_events;
pub mod projectile;
mod shockwave;
mod stats;
@ -34,6 +35,7 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
&mount::Sys::sys_name(),
&stats::Sys::sys_name(),
]);
dispatch::<phys_events::Sys>(dispatch_builder, &[&phys::Sys::sys_name()]);
dispatch::<projectile::Sys>(dispatch_builder, &[&phys::Sys::sys_name()]);
dispatch::<shockwave::Sys>(dispatch_builder, &[&phys::Sys::sys_name()]);
dispatch::<beam::Sys>(dispatch_builder, &[&phys::Sys::sys_name()]);

View File

@ -365,7 +365,9 @@ impl<'a> PhysicsData<'a> {
.write
.previous_phys_cache
.insert(entity, PreviousPhysCache {
velocity: Vec3::zero(),
velocity_dt: Vec3::zero(),
in_fluid: None,
center: Vec3::zero(),
collision_boundary: 0.0,
scale: 0.0,
@ -378,11 +380,12 @@ impl<'a> PhysicsData<'a> {
}
// Update PreviousPhysCache
for (_, vel, position, ori, phys_cache, collider, scale, cs) in (
for (_, vel, position, ori, phys_state, phys_cache, collider, scale, cs) in (
&self.read.entities,
&self.write.velocities,
&self.write.positions,
&self.write.orientations,
&self.write.physics_states,
&mut self.write.previous_phys_cache,
&self.read.colliders,
self.read.scales.maybe(),
@ -397,6 +400,8 @@ impl<'a> PhysicsData<'a> {
let half_height = (z_max - z_min) / 2.0;
phys_cache.velocity_dt = vel.0 * self.read.dt.0;
phys_cache.velocity = vel.0;
phys_cache.in_fluid = phys_state.in_fluid;
let entity_center = position.0 + Vec3::new(0.0, 0.0, z_min + half_height);
let flat_radius = collider.bounding_radius() * scale;
let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt();

View File

@ -0,0 +1,57 @@
use common::{
comp::{Fluid, Mass, PhysicsState, Pos, PreviousPhysCache, Vel},
event::EventBus,
outcome::Outcome,
};
use common_ecs::System;
use specs::{Join, Read, ReadStorage};
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, EventBus<Outcome>>,
ReadStorage<'a, PhysicsState>,
ReadStorage<'a, PreviousPhysCache>,
ReadStorage<'a, Vel>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Mass>,
);
const NAME: &'static str = "phys_events";
const ORIGIN: common_ecs::Origin = common_ecs::Origin::Common;
const PHASE: common_ecs::Phase = common_ecs::Phase::Create;
fn run(
_job: &mut common_ecs::Job<Self>,
(outcomes, physics_states, previous_phys_cache, velocities, positions, masses): Self::SystemData,
) {
let mut outcomes = outcomes.emitter();
for (physics_state, prev, vel, pos, mass) in (
&physics_states,
&previous_phys_cache,
&velocities,
&positions,
&masses,
)
.join()
{
// Only splash when going from air into a liquid
if let (Some(Fluid::Liquid { kind, .. }), Some(Fluid::Air { .. })) =
(physics_state.in_fluid, prev.in_fluid)
{
outcomes.emit(Outcome::Splash {
pos: pos.0,
vel: if vel.0.magnitude_squared() > prev.velocity.magnitude_squared() {
vel.0
} else {
prev.velocity
},
mass: mass.0,
kind,
});
}
}
}
}

View File

@ -130,6 +130,9 @@ pub enum SfxEvent {
Lavapool,
Idle,
Swim,
SplashSmall,
SplashMedium,
SplashBig,
Run(BlockKind),
QuadRun(BlockKind),
Roll,
@ -897,6 +900,22 @@ impl SfxMgr {
error!("Couldn't get position of entity that lost head");
}
},
Outcome::Splash { vel, pos, mass, .. } => {
let magnitude = (-vel.z).max(0.0);
let energy = mass * magnitude;
if energy > 0.0 {
let (sfx, volume) = if energy < 10.0 {
(SfxEvent::SplashSmall, energy / 20.0)
} else if energy < 100.0 {
(SfxEvent::SplashMedium, (energy - 10.0) / 90.0 + 0.5)
} else {
(SfxEvent::SplashBig, (energy / 100.0).sqrt() + 0.5)
};
let sfx_trigger_item = triggers.get_key_value(&sfx);
audio.emit_sfx(sfx_trigger_item, *pos, Some(volume.max(10.0)));
}
},
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => {},
}
}

View File

@ -115,6 +115,7 @@ pub enum ParticleMode {
SurpriseEgg = 61,
FlameTornado = 62,
Poison = 63,
WaterFoam = 64,
}
impl ParticleMode {

View File

@ -857,7 +857,7 @@ where
);
instances
},
blocks_of_interest: BlocksOfInterest::from_blocks(block_iter, 0.0, 10.0, 0.0, dyna),
blocks_of_interest: BlocksOfInterest::from_blocks(block_iter, Vec3::zero(), 10.0, 0.0, dyna),
blocks_offset: *offset,
}));
});

View File

@ -506,6 +506,51 @@ impl ParticleMgr {
}
};
},
Outcome::Splash {
vel,
pos,
mass,
kind,
} => {
let mode = match kind {
comp::fluid_dynamics::LiquidKind::Water => ParticleMode::WaterFoam,
comp::fluid_dynamics::LiquidKind::Lava => ParticleMode::CampfireFire,
};
let magnitude = (-vel.z).max(0.0);
let energy = mass * magnitude;
if energy > 0.0 {
let count = ((0.6 * energy.sqrt()).ceil() as usize).min(500);
let mut i = 0;
let r = 0.5 / count as f32;
self.particles
.resize_with(self.particles.len() + count, || {
let t = i as f32 / count as f32 + rng.gen_range(-r..=r);
i += 1;
let angle = t * TAU;
let s = angle.sin();
let c = angle.cos();
let energy = energy
* f32::abs(rng.gen_range(0.0..1.0) + rng.gen_range(0.0..1.0) - 0.5);
let axis = -Vec3::unit_z();
let plane = Vec3::new(c, s, 0.0);
let pos = *pos + plane * rng.gen_range(0.0..0.5);
let energy = energy.sqrt() * 0.5;
let dir = plane * (1.0 + energy) - axis * energy;
Particle::new_directed(
Duration::from_millis(4000),
time,
mode,
pos,
pos + dir,
)
});
}
},
Outcome::ProjectileShot { .. }
| Outcome::Beam { .. }
| Outcome::ExpChange { .. }
@ -2447,6 +2492,14 @@ impl ParticleMgr {
mode: ParticleMode::Spore,
cond: |_| true,
},
BlockParticles {
blocks: |boi| BlockParticleSlice::PositionsAndDirs(&boi.waterfall),
range: 2,
rate: 4.0,
lifetime: 5.0,
mode: ParticleMode::WaterFoam,
cond: |_| true,
},
];
let ecs = scene_data.state.ecs();

View File

@ -255,7 +255,7 @@ fn mesh_worker(
span!(_guard, "mesh_worker");
let blocks_of_interest = BlocksOfInterest::from_blocks(
chunk.iter_changed().map(|(pos, block)| (pos, *block)),
chunk.meta().river_velocity().magnitude_squared(),
chunk.meta().river_velocity(),
chunk.meta().temp(),
chunk.meta().humidity(),
&*chunk,

View File

@ -40,6 +40,7 @@ pub struct BlocksOfInterest {
pub grass: Vec<Vec3<i32>>,
pub slow_river: Vec<Vec3<i32>>,
pub fast_river: Vec<Vec3<i32>>,
pub waterfall: Vec<(Vec3<i32>, Vec3<f32>)>,
pub lavapool: Vec<Vec3<i32>>,
pub fires: Vec<Vec3<i32>>,
pub smokers: Vec<SmokerProperties>,
@ -68,7 +69,7 @@ pub struct BlocksOfInterest {
impl BlocksOfInterest {
pub fn from_blocks(
blocks: impl Iterator<Item = (Vec3<i32>, Block)>,
river_speed_sq: f32,
river_velocity: Vec3<f32>,
temperature: f32,
humidity: f32,
chunk: &impl ReadVol<Vox = Block>,
@ -79,6 +80,7 @@ impl BlocksOfInterest {
let mut grass = Vec::new();
let mut slow_river = Vec::new();
let mut fast_river = Vec::new();
let mut waterfall = Vec::new();
let mut lavapool = Vec::new();
let mut fires = Vec::new();
let mut smokers = Vec::new();
@ -124,9 +126,38 @@ impl BlocksOfInterest {
_ => {},
}
},
// Assign a river speed to water blocks depending on river velocity
BlockKind::Water if river_speed_sq > 0.9_f32.powi(2) => fast_river.push(pos),
BlockKind::Water if river_speed_sq > 0.3_f32.powi(2) => slow_river.push(pos),
BlockKind::Water => {
let is_waterfall = chunk
.get(pos + vek::Vec3::unit_z())
.is_ok_and(|b| b.is_air())
&& [
vek::Vec2::new(0, 1),
vek::Vec2::new(1, 0),
vek::Vec2::new(0, -1),
vek::Vec2::new(-1, 0),
]
.iter()
.map(|p| {
(1..=2)
.take_while(|i| {
chunk.get(pos + p.with_z(*i)).is_ok_and(|b| b.is_liquid())
})
.count()
})
.any(|s| s >= 2);
if is_waterfall {
waterfall.push((pos, river_velocity));
}
let river_speed_sq = river_velocity.magnitude_squared();
// Assign a river speed to water blocks depending on river velocity
if is_waterfall || river_speed_sq > 0.9_f32.powi(2) {
fast_river.push(pos)
} else if river_speed_sq > 0.3_f32.powi(2) {
slow_river.push(pos)
}
},
BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos),
BlockKind::Lava
if chunk
@ -267,6 +298,7 @@ impl BlocksOfInterest {
grass,
slow_river,
fast_river,
waterfall,
lavapool,
fires,
smokers,