Add lava to caves, which sets you on fire if you swim in it. Currently requires uncommenting #define LAVA in the shaders, and only looks good with cheap fluid mode.

This commit is contained in:
Avi Weinstock 2021-06-16 17:40:18 -04:00
parent e7f54d6306
commit 2226a4c6a9
9 changed files with 143 additions and 44 deletions

View File

@ -37,6 +37,8 @@ layout(location = 1) flat in uint f_pos_norm;
// ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192];
// };
//#define LAVA
layout(std140, set = 2, binding = 0)
uniform u_locals {
vec3 model_offs;
@ -78,7 +80,11 @@ void main() {
// vec3 view_dir = normalize(-vec3(vert_pos4)/* / vert_pos4.w*/);
vec3 view_dir = -cam_to_frag;
// vec3 surf_color = /*srgb_to_linear*/(vec3(0.4, 0.7, 2.0));
#ifdef LAVA
vec3 water_color = (1.0 - MU_LAVA);
#else
/*const */vec3 water_color = (1.0 - MU_WATER) * MU_SCATTER;//srgb_to_linear(vec3(0.2, 0.5, 1.0));
#endif
// /*const */vec3 water_color = srgb_to_linear(vec3(0.0, 0.25, 0.5));
/* vec3 sun_dir = get_sun_dir(time_of_day.x);
@ -118,7 +124,11 @@ void main() {
// Water is transparent so both normals are valid.
vec3 cam_norm = faceforward(f_norm, f_norm, cam_to_frag);
#ifdef LAVA
vec3 mu = MU_LAVA;
#else
vec3 mu = MU_WATER;
#endif
// NOTE: Default intersection point is camera position, meaning if we fail to intersect we assume the whole camera is in water.
vec3 cam_attenuation = vec3(1.0);//compute_attenuation_point(f_pos, -view_dir, mu, fluid_alt, cam_pos.xyz);
@ -177,7 +187,14 @@ void main() {
// float reflected_light_point = /*length*/(diffuse_light_point.r) + f_light * point_shadow;
// reflected_light += k_d * (diffuse_light_point + f_light * point_shadow * shade_frac) + specular_light_point;
float passthrough = clamp(dot(cam_norm, -cam_to_frag) * 1.0 - 0.2, 0, 1);
#ifdef LAVA
float opacity = 0.9;
emitted_light = vec3(0.1, 0, 0);
#else
float opacity = 0.2;
#endif
float passthrough = clamp(dot(cam_norm, -cam_to_frag) * 1.0 - opacity, 0, 1);
float min_refl = min(emitted_light.r, min(emitted_light.g, emitted_light.b));
vec3 surf_color = illuminate(max_light, view_dir, water_color * /* fog_color * */emitted_light, /*surf_color * */water_color * reflected_light);

View File

@ -88,6 +88,8 @@ float wave_height(vec3 pos) {
return pow(abs(height), 0.5) * sign(height) * 15.0;
}
//#define LAVA
void main() {
// First 3 normals are negative, next 3 are positive
vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1));
@ -148,7 +150,12 @@ void main() {
vec3 norm = vec3(0, 0, 1) * nmap.z + b_norm * nmap.x + c_norm * nmap.y;
// vec3 norm = f_norm;
#ifdef LAVA
vec3 water_color = (1.0 - MU_LAVA);
#else
vec3 water_color = (1.0 - MU_WATER) * MU_SCATTER;
#endif
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP || FLUID_MODE == FLUID_MODE_SHINY)
float f_alt = alt_at(f_pos.xy);
#elif (SHADOW_MODE == SHADOW_MODE_NONE || FLUID_MODE == FLUID_MODE_CHEAP)
@ -230,7 +237,12 @@ void main() {
// vec3 water_color_direct = exp(-MU_WATER);//exp(-MU_WATER);//vec3(1.0);
// vec3 water_color_direct = exp(-water_attenuation * (water_depth_to_light + water_depth_to_camera));
// vec3 water_color_ambient = exp(-water_attenuation * (water_depth_to_vertical + water_depth_to_camera));
#ifdef LAVA
vec3 mu = MU_LAVA;
#else
vec3 mu = MU_WATER;
#endif
// NOTE: Default intersection point is camera position, meaning if we fail to intersect we assume the whole camera is in water.
vec3 cam_attenuation = compute_attenuation_point(f_pos, -view_dir, mu, fluid_alt, cam_pos.xyz);
// float water_depth_to_vertical = max(/*f_alt - f_pos.z*/f_light, 0.0);
@ -290,7 +302,13 @@ void main() {
// reflected_light += point_light;
// vec3 surf_color = srgb_to_linear(vec3(0.2, 0.5, 1.0)) * light * diffuse_light * ambient_light;
const float REFLECTANCE = 0.5;
#ifdef LAVA
//vec3 surf_color = illuminate(max_light, view_dir, water_color * emitted_light, reflect_color * REFLECTANCE + water_color * reflected_light);
vec3 surf_color = vec3(1, 0.25, 0);
#else
vec3 surf_color = illuminate(max_light, view_dir, water_color * emitted_light/* * log(1.0 - MU_WATER)*/, /*cam_attenuation * *//*water_color * */reflect_color * REFLECTANCE + water_color * reflected_light/* * log(1.0 - MU_WATER)*/);
#endif
//vec3 surf_color = illuminate(max_light, view_dir, water_color * emitted_light/* * log(1.0 - MU_WATER)*/, /*cam_attenuation * *//*water_color * */reflect_color * REFLECTANCE + water_color * reflected_light/* * log(1.0 - MU_WATER)*/);
// passthrough = pow(passthrough, 1.0 / (1.0 + water_depth_to_camera));
/* surf_color = cam_attenuation.g < 0.5 ?

View File

@ -2,7 +2,9 @@
#define SRGB_GLSL
// Linear RGB, attenuation coefficients for water at roughly R, G, B wavelengths.
// See https://en.wikipedia.org/wiki/Electromagnetic_absorption_by_water
const vec3 MU_WATER = vec3(0.6, 0.04, 0.01);
const vec3 MU_LAVA = vec3(0.1, 0.75, 1.0);
// // NOTE: Automatic in v4.0
// float

View File

@ -557,7 +557,7 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<AVERAGE_
} else {
use BlockKind::*;
match kind {
Air | Water => Rgb { r: 0, g: 0, b: 0 },
Air | Water | Lava => Rgb { r: 0, g: 0, b: 0 },
Rock => Rgb {
r: 93,
g: 110,

View File

@ -9,11 +9,38 @@ use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum LiquidKind {
Water,
Lava,
}
impl LiquidKind {
/// If an entity is in multiple overlapping liquid blocks, which one takes
/// precedence? (should be a rare edge case, since checkerboard patterns of
/// water and lava shouldn't show up in worldgen)
pub fn merge(self, other: LiquidKind) -> LiquidKind {
use LiquidKind::{Lava, Water};
match (self, other) {
(Water, Water) => Water,
(Water, Lava) => Lava,
(Lava, _) => Lava,
}
}
}
/// Fluid medium in which the entity exists
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Fluid {
Air { vel: Vel, elevation: f32 },
Water { vel: Vel, depth: f32 },
Air {
vel: Vel,
elevation: f32,
},
Liquid {
kind: LiquidKind,
vel: Vel,
depth: f32,
},
}
impl Fluid {
@ -21,7 +48,14 @@ impl Fluid {
pub fn density(&self) -> Density {
match self {
Self::Air { .. } => Density(AIR_DENSITY),
Self::Water { .. } => Density(WATER_DENSITY),
Self::Liquid {
kind: LiquidKind::Water,
..
} => Density(WATER_DENSITY),
Self::Liquid {
kind: LiquidKind::Lava,
..
} => Density(WATER_DENSITY),
}
}
@ -53,14 +87,14 @@ impl Fluid {
pub fn flow_vel(&self) -> Vel {
match self {
Self::Air { vel, .. } => *vel,
Self::Water { vel, .. } => *vel,
Self::Liquid { vel, .. } => *vel,
}
}
// Very simple but useful in reducing mental overhead
pub fn relative_flow(&self, vel: &Vel) -> Vel { Vel(self.flow_vel().0 - vel.0) }
pub fn is_liquid(&self) -> bool { matches!(self, Fluid::Water { .. }) }
pub fn is_liquid(&self) -> bool { matches!(self, Fluid::Liquid { .. }) }
pub fn elevation(&self) -> Option<f32> {
match self {
@ -71,7 +105,7 @@ impl Fluid {
pub fn depth(&self) -> Option<f32> {
match self {
Fluid::Water { depth, .. } => Some(*depth),
Fluid::Liquid { depth, .. } => Some(*depth),
_ => None,
}
}

View File

@ -1,5 +1,8 @@
use super::SpriteKind;
use crate::{comp::tool::ToolKind, make_case_elim};
use crate::{
comp::{fluid_dynamics::LiquidKind, tool::ToolKind},
make_case_elim,
};
use enum_iterator::IntoEnumIterator;
use hashbrown::HashMap;
use lazy_static::lazy_static;
@ -27,6 +30,7 @@ make_case_elim!(
pub enum BlockKind {
Air = 0x00, // Air counts as a fluid
Water = 0x01,
Lava = 0x02,
// 0x02 <= x < 0x10 are reserved for other fluids. These are 2^n aligned to allow bitwise
// checking of common conditions. For example, `is_fluid` is just `block_kind &
// 0x0F == 0` (this is a very common operation used in meshing that could do with
@ -63,6 +67,15 @@ impl BlockKind {
#[inline]
pub const fn is_liquid(&self) -> bool { self.is_fluid() && !self.is_air() }
#[inline]
pub const fn liquid_kind(&self) -> Option<LiquidKind> {
Some(match self {
BlockKind::Water => LiquidKind::Water,
BlockKind::Lava => LiquidKind::Lava,
_ => return None,
})
}
/// Determine whether the block is filled (i.e: fully solid). Right now,
/// this is the opposite of being a fluid.
#[inline]

View File

@ -1,8 +1,8 @@
use common::{
comp::{
fluid_dynamics::Fluid, Buff, BuffCategory, BuffChange, BuffEffect, BuffId, BuffKind,
BuffSource, Buffs, Energy, Health, HealthChange, HealthSource, Inventory, ModifierKind,
PhysicsState, Stats,
fluid_dynamics::{LiquidKind, Fluid}, Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId,
BuffKind, BuffSource, Buffs, Energy, Health, HealthChange, HealthSource, Inventory,
ModifierKind, PhysicsState, Stats,
},
event::{EventBus, ServerEvent},
resources::DeltaTime,
@ -45,34 +45,39 @@ impl<'a> System<'a> for Sys {
// Set to false to avoid spamming server
buffs.set_event_emission(false);
stats.set_event_emission(false);
for (entity, mut buff_comp, energy, mut stat, health) in (
for (entity, mut buff_comp, energy, mut stat, health, physics_state) in (
&read_data.entities,
&mut buffs,
&read_data.energies,
&mut stats,
&read_data.healths,
read_data.physics_states.maybe(),
)
.join()
{
let in_fluid = physics_state.and_then(|p| p.in_fluid);
if matches!(in_fluid, Some(Fluid::Liquid { kind: LiquidKind::Lava, .. })) {
if !buff_comp.contains(BuffKind::Burning) {
buff_comp.insert(Buff::new(
BuffKind::Burning,
BuffData::new(200.0, None),
vec![BuffCategory::Natural],
BuffSource::World,
));
}
}
let (buff_comp_kinds, buff_comp_buffs): (
&HashMap<BuffKind, Vec<BuffId>>,
&mut HashMap<BuffId, Buff>,
) = buff_comp.parts();
let mut expired_buffs = Vec::<BuffId>::new();
// For each buff kind present on entity, if the buff kind queues, only ticks
// duration of strongest buff of that kind, else it ticks durations of all buffs
// of that kind. Any buffs whose durations expire are marked expired.
for (kind, ids) in buff_comp_kinds.iter() {
// Only get the physics state component if the entity has the burning buff, as
// we don't need it for any other conditions yet
let in_fluid = if matches!(kind, BuffKind::Burning) {
read_data
.physics_states
.get(entity)
.and_then(|p| p.in_fluid)
} else {
None
};
if kind.queues() {
if let Some((Some(buff), id)) =
ids.get(0).map(|id| (buff_comp_buffs.get_mut(id), id))
@ -257,7 +262,7 @@ fn tick_buff(
}
if let Some(remaining_time) = &mut buff.time {
// Extinguish Burning buff when in water
if matches!(buff.kind, BuffKind::Burning) && matches!(in_fluid, Some(Fluid::Water { .. })) {
if matches!(buff.kind, BuffKind::Burning) && matches!(in_fluid, Some(Fluid::Liquid { kind: LiquidKind::Water, .. })) {
*remaining_time = Duration::default();
}

View File

@ -1,7 +1,7 @@
use common::{
comp::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, Wings},
fluid_dynamics::{Fluid, LiquidKind, Wings},
BeamSegment, Body, CharacterState, Collider, Density, Mass, Mounting, Ori, PhysicsState,
Pos, PosVelDefer, PreviousPhysCache, Projectile, Scale, Shockwave, Stats, Sticky, Vel,
},
@ -905,13 +905,15 @@ impl<'a> PhysicsData<'a> {
.terrain
.get(pos.0.map(|e| e.floor() as i32))
.ok()
.and_then(|vox| vox.is_liquid().then_some(1.0))
.map(|depth| Fluid::Water {
depth,
vel: Vel::zero(),
.and_then(|vox| {
vox.liquid_kind().map(|kind| Fluid::Liquid {
kind,
depth: 1.0,
vel: Vel::zero(),
})
})
.or_else(|| match physics_state.in_fluid {
Some(Fluid::Water { .. }) | None => Some(Fluid::Air {
Some(Fluid::Liquid { .. }) | None => Some(Fluid::Air {
elevation: pos.0.z,
vel: Vel::default(),
}),
@ -1541,24 +1543,26 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
});
// Find liquid immersion and wall collision all in one round of iteration
let mut max_liquid_z = None::<f32>;
let mut liquid = None::<(LiquidKind, f32)>;
let mut wall_dir_collisions = [false; 4];
near_iter.for_each(|(i, j, k)| {
let block_pos = player_voxel_pos + Vec3::new(i, j, k);
if let Some(block) = terrain.get(block_pos).ok().copied() {
// Check for liquid blocks
if block.is_liquid() {
if let Some(block_liquid) = block.liquid_kind() {
let liquid_aabb = Aabb {
min: block_pos.map(|e| e as f32),
// The liquid part of a liquid block always extends 1 block high.
max: block_pos.map(|e| e as f32) + Vec3::one(),
};
if player_aabb.collides_with_aabb(liquid_aabb) {
max_liquid_z = Some(match max_liquid_z {
Some(z) => z.max(liquid_aabb.max.z),
None => liquid_aabb.max.z,
});
liquid = match liquid {
Some((kind, max_liquid_z)) => {
Some((kind.merge(block_liquid), max_liquid_z.max(liquid_aabb.max.z)))
},
None => Some((block_liquid, liquid_aabb.max.z)),
};
}
}
// Check for walls
@ -1594,23 +1598,24 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
physics_state.ground_vel = ground_vel;
}
physics_state.in_fluid = max_liquid_z
.map(|max_z| max_z - pos.0.z) // NOTE: assumes min_z == 0.0
.map(|depth| {
physics_state
physics_state.in_fluid = liquid
.map(|(kind, max_z)| (kind, max_z - pos.0.z)) // NOTE: assumes min_z == 0.0
.map(|(kind, depth)| {
(kind, physics_state
.in_liquid()
// This is suboptimal because it doesn't check for true depth,
// so it can cause problems for situations like swimming down
// a river and spawning or teleporting in(/to) water
.map(|old_depth| (old_depth + old_pos.z - pos.0.z).max(depth))
.unwrap_or(depth)
.unwrap_or(depth))
})
.map(|depth| Fluid::Water {
.map(|(kind, depth)| Fluid::Liquid {
kind,
depth,
vel: Vel::zero(),
})
.or_else(|| match physics_state.in_fluid {
Some(Fluid::Water { .. }) | None => Some(Fluid::Air {
Some(Fluid::Liquid { .. }) | None => Some(Fluid::Air {
elevation: pos.0.z,
vel: Vel::default(),
}),

View File

@ -210,10 +210,15 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
//make pits
for z in cave_base - pit_depth..cave_base {
if pit_condition && (cave_roof - cave_base) > 10 {
let kind = if z < (cave_base - pit_depth) + (3 * pit_depth / 4) {
BlockKind::Lava
} else {
BlockKind::Air
};
canvas.set(
Vec3::new(wpos2d.x, wpos2d.y, z),
Block::new(
BlockKind::Air,
kind,
noisy_color(info.index().colors.layer.scaffold.into(), 8),
),
);