mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 17:19:15 -10:00
camera fluid: lava fog starts when a corner of the near plane dips under the lava
This commit is contained in:
1 parent
e5a4324ca3
commit
30715a97d9
6 files changed
+105
-27
No files matched your search
@@ -419,6 +419,31 @@ pub enum CameraFluid {
|
||||
PowderSnow,
|
||||
}
|
||||
|
||||
/// `Camera.NearPlane`: the near plane's centre and its half extents toward
|
||||
/// the camera's left and up, relative to the camera.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct NearPlane {
|
||||
pub forward: [f64; 3],
|
||||
pub left: [f64; 3],
|
||||
pub up: [f64; 3],
|
||||
}
|
||||
impl NearPlane {
|
||||
/// `forward`, `getTopLeft`, `getTopRight`, `getBottomLeft`, `getBottomRight`:
|
||||
/// the points `Camera.getFluidInCamera` looks for lava and powder snow at.
|
||||
pub fn points(&self) -> [[f64; 3]; 5] {
|
||||
let corner = |up: f64, left: f64| -> [f64; 3] {
|
||||
std::array::from_fn(|i| self.forward[i] + self.up[i] * up + self.left[i] * left)
|
||||
};
|
||||
[
|
||||
self.forward,
|
||||
corner(1.0, 1.0),
|
||||
corner(1.0, -1.0),
|
||||
corner(-1.0, 1.0),
|
||||
corner(-1.0, -1.0),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// `Camera`: the eye-height and field-of-view easing and the view placement.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Camera {
|
||||
@@ -481,6 +506,19 @@ impl Camera {
|
||||
self.up = self.rotation.transform([0.0, 1.0, 0.0]);
|
||||
self.left = self.rotation.transform([-1.0, 0.0, 0.0]);
|
||||
}
|
||||
/// `Camera.getNearPlane(fov)` for a `width` x `height` window, at the
|
||||
/// projection's `zNear` of 0.05.
|
||||
pub fn near_plane(&self, fov: f32, width: f32, height: f32) -> NearPlane {
|
||||
let z_near = f64::from(0.05f32);
|
||||
let aspect = f64::from(width) / f64::from(height);
|
||||
let plane_height = horizon_core::jmath::tan(f64::from(fov * DEG_TO_RAD) / 2.0) * z_near;
|
||||
let scale = |v: [f32; 3], by: f64| v.map(|c| f64::from(c) * by);
|
||||
NearPlane {
|
||||
forward: scale(self.forwards, z_near),
|
||||
left: scale(self.left, plane_height * aspect),
|
||||
up: scale(self.up, plane_height),
|
||||
}
|
||||
}
|
||||
/// `Camera.move(forwards, up, right)`.
|
||||
pub fn move_by(&mut self, forwards: f32, up: f32, right: f32) {
|
||||
let offset = self.rotation.transform([right, up, -forwards]);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use crate::{
|
||||
biome::Biomes,
|
||||
blocks::{BlockFacts, Fluid},
|
||||
camera::NearPlane,
|
||||
environment::Sky,
|
||||
world::World,
|
||||
};
|
||||
@@ -135,9 +136,24 @@ fn camera_fluid_height(world: &World, facts: &BlockFacts, b: [i32; 3]) -> Option
|
||||
};
|
||||
Some((s.fluid, height))
|
||||
}
|
||||
/// `Camera.getFluidInCamera` (near-plane points reduced to the camera and
|
||||
/// the point just in front of it).
|
||||
pub fn fog_type(world: &World, facts: &BlockFacts, camera: [f64; 3], forward: [f64; 3]) -> FogType {
|
||||
/// `Camera.getFluidInCamera`: water at the camera, then lava or powder snow
|
||||
/// at the near plane's centre and corners.
|
||||
pub fn fog_type(world: &World, facts: &BlockFacts, camera: [f64; 3], near: &NearPlane) -> FogType {
|
||||
camera_fog_type(
|
||||
camera,
|
||||
near,
|
||||
|b| camera_fluid_height(world, facts, b),
|
||||
|b| &*facts.get(world.block(b[0], b[1], b[2])).name == "minecraft:powder_snow",
|
||||
)
|
||||
}
|
||||
/// `fog_type` over a block lookup: the fluid and its camera height, and
|
||||
/// whether the block is powder snow.
|
||||
fn camera_fog_type(
|
||||
camera: [f64; 3],
|
||||
near: &NearPlane,
|
||||
fluid: impl Fn([i32; 3]) -> Option<(Fluid, f64)>,
|
||||
powder_snow: impl Fn([i32; 3]) -> bool,
|
||||
) -> FogType {
|
||||
let block = |p: [f64; 3]| {
|
||||
[
|
||||
p[0].floor() as i32,
|
||||
@@ -146,28 +162,19 @@ pub fn fog_type(world: &World, facts: &BlockFacts, camera: [f64; 3], forward: [f
|
||||
]
|
||||
};
|
||||
let b = block(camera);
|
||||
if let Some((Fluid::Water, h)) = camera_fluid_height(world, facts, b)
|
||||
if let Some((Fluid::Water, h)) = fluid(b)
|
||||
&& camera[1] < f64::from(b[1]) + h
|
||||
{
|
||||
return FogType::Water;
|
||||
}
|
||||
for p in [
|
||||
[
|
||||
camera[0] + forward[0] * 0.05,
|
||||
camera[1] + forward[1] * 0.05,
|
||||
camera[2] + forward[2] * 0.05,
|
||||
],
|
||||
camera,
|
||||
] {
|
||||
for point in near.points() {
|
||||
let p: [f64; 3] = std::array::from_fn(|i| camera[i] + point[i]);
|
||||
let b = block(p);
|
||||
match camera_fluid_height(world, facts, b) {
|
||||
match fluid(b) {
|
||||
Some((Fluid::Lava, h)) if p[1] <= f64::from(b[1]) + h => return FogType::Lava,
|
||||
Some((Fluid::Lava, _)) => {}
|
||||
_ => {
|
||||
if &*facts.get(world.block(b[0], b[1], b[2])).name == "minecraft:powder_snow" {
|
||||
return FogType::PowderSnow;
|
||||
}
|
||||
}
|
||||
_ if powder_snow(b) => return FogType::PowderSnow,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
FogType::Atmospheric
|
||||
@@ -448,6 +455,23 @@ mod tests {
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn lava_fog_starts_when_a_near_plane_corner_dips_under_the_surface() {
|
||||
// Flowing lava (amount 4) on y 64: the camera sees its surface at 64 + 4/9.
|
||||
let surface = 64.0 + 4.0 / 9.0;
|
||||
let fluid = |b: [i32; 3]| (b[1] == 64).then_some((Fluid::Lava, 4.0 / 9.0));
|
||||
let fog = |y: f64, pitch: f32| {
|
||||
let mut camera = crate::camera::Camera::default();
|
||||
camera.set_rotation(0.0, pitch);
|
||||
let near = camera.near_plane(70.0, 1600.0, 900.0);
|
||||
camera_fog_type([0.5, y, 0.5], &near, fluid, |_| false)
|
||||
};
|
||||
// Looking level, the near plane reaches tan(35 degrees) * 0.05 = 0.035 below the eye.
|
||||
assert_eq!(fog(surface + 0.02, 0.0), FogType::Lava);
|
||||
assert_eq!(fog(surface + 0.05, 0.0), FogType::Atmospheric);
|
||||
// The eye itself is not tested: looking up, the whole plane is above it.
|
||||
assert_eq!(fog(surface - 0.01, -90.0), FogType::Atmospheric);
|
||||
}
|
||||
#[test]
|
||||
fn boss_world_fog_closes_in() {
|
||||
// AtmosphericFogEnvironment.setupFog under a createWorldFog boss bar.
|
||||
let sky = Sky {
|
||||
|
||||
@@ -15290,7 +15290,7 @@ impl Game<'_> {
|
||||
&self.world,
|
||||
self.res.facts(),
|
||||
[camera.x, camera.y, camera.z],
|
||||
self.forward(),
|
||||
&self.near_plane(),
|
||||
),
|
||||
horizon_client::fog::FogType::Lava | horizon_client::fog::FogType::PowderSnow
|
||||
),
|
||||
|
||||
@@ -107,7 +107,7 @@ impl Game<'_> {
|
||||
}
|
||||
/// The fluid at the camera, as `Camera.getFluidInCamera` reports it.
|
||||
pub(super) fn camera_fluid(&self, camera: [f64; 3]) -> CameraFluid {
|
||||
match fog_type(&self.world, self.res.facts(), camera, self.forward()) {
|
||||
match fog_type(&self.world, self.res.facts(), camera, &self.near_plane()) {
|
||||
FogType::Water => CameraFluid::Water,
|
||||
FogType::Lava => CameraFluid::Lava,
|
||||
FogType::PowderSnow => CameraFluid::PowderSnow,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! campfire smoke and rain splashes (`ClientLevel.tickWeatherEffects`).
|
||||
use super::{Dim, Game};
|
||||
use horizon_client::assets::Properties;
|
||||
use horizon_client::camera::NearPlane;
|
||||
use horizon_client::fog::{FogData, FogInputs, FogType, fog_type, update_rain_fog};
|
||||
use horizon_client::{
|
||||
animate_tick::{AnimateContext, animate_tick, campfire_particles, marker_particle_target},
|
||||
@@ -85,6 +86,18 @@ impl Game<'_> {
|
||||
yaw.cos() * pitch.cos(),
|
||||
]
|
||||
}
|
||||
/// `Camera.getNearPlane` at the FOV option, as `getFluidInCamera` asks for it.
|
||||
pub(super) fn near_plane(&self) -> NearPlane {
|
||||
let [width, height] = self
|
||||
.renderer
|
||||
.as_ref()
|
||||
.map_or([1, 1], |r| [r.config.width, r.config.height]);
|
||||
self.view.near_plane(
|
||||
self.settings.fov as i32 as f32,
|
||||
width as f32,
|
||||
height.max(1) as f32,
|
||||
)
|
||||
}
|
||||
/// `FogRenderer.setupFog` for the frame's camera, and Horizon's Fog option.
|
||||
pub(super) fn frame_fog(
|
||||
&self,
|
||||
@@ -95,7 +108,7 @@ impl Game<'_> {
|
||||
) -> FogData {
|
||||
let forward = self.forward();
|
||||
let at = [camera.x, camera.y, camera.z];
|
||||
let fog_type = fog_type(&self.world, self.res.facts(), at, forward);
|
||||
let fog_type = fog_type(&self.world, self.res.facts(), at, &self.near_plane());
|
||||
let (rain, thunder) = if self.dim == Dim::Overworld {
|
||||
self.world.weather.levels(self.clock.fraction() as f32)
|
||||
} else {
|
||||
@@ -239,7 +252,7 @@ impl Game<'_> {
|
||||
&self.world,
|
||||
self.res.facts(),
|
||||
[camera.x, camera.y, camera.z],
|
||||
self.forward(),
|
||||
&self.near_plane(),
|
||||
) != FogType::Water
|
||||
}
|
||||
/// `ScreenEffectRenderer` + `Hud` camera overlays for this frame.
|
||||
@@ -312,7 +325,7 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
let in_water =
|
||||
fog_type(&self.world, self.res.facts(), eye, self.forward()) == FogType::Water;
|
||||
fog_type(&self.world, self.res.facts(), eye, &self.near_plane()) == FogType::Water;
|
||||
let water =
|
||||
(in_water && !spectator).then(|| (self.eye_brightness(eye), self.yaw, self.pitch));
|
||||
// Hud.extractCameraOverlays; the spyglass scope (and its easing) is
|
||||
@@ -383,7 +396,7 @@ impl Game<'_> {
|
||||
self.screen_fx.tick_spinning(nausea);
|
||||
// LocalPlayer water vision and AtmosphericFogEnvironment rain fog.
|
||||
let in_water =
|
||||
fog_type(&self.world, self.res.facts(), e, self.forward()) == FogType::Water;
|
||||
fog_type(&self.world, self.res.facts(), e, &self.near_plane()) == FogType::Water;
|
||||
self.fog.tick(in_water);
|
||||
let b = [
|
||||
e[0].floor() as i32,
|
||||
|
||||
@@ -1251,9 +1251,12 @@ impl Game<'_> {
|
||||
if self.potions.effects.get("conduit_power").is_some()
|
||||
&& let Some(eye) = self.eye_position()
|
||||
{
|
||||
let in_water =
|
||||
horizon_client::fog::fog_type(&self.world, self.res.facts(), eye, self.forward())
|
||||
== FogType::Water;
|
||||
let in_water = horizon_client::fog::fog_type(
|
||||
&self.world,
|
||||
self.res.facts(),
|
||||
eye,
|
||||
&self.near_plane(),
|
||||
) == FogType::Water;
|
||||
return self.fog.water_vision(in_water);
|
||||
}
|
||||
0.0
|
||||
|
||||
Reference in new issue
Block a user