1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

implement fluid_shape

This commit is contained in:
mat 2024-12-27 12:30:24 +00:00
parent 04036b6e4a
commit 5693191b57
8 changed files with 144 additions and 32 deletions

1
Cargo.lock generated
View file

@ -463,6 +463,7 @@ dependencies = [
"bevy_ecs", "bevy_ecs",
"bevy_time", "bevy_time",
"parking_lot", "parking_lot",
"tracing",
"uuid", "uuid",
] ]

View file

@ -8,7 +8,8 @@ mod range;
use core::fmt::Debug; use core::fmt::Debug;
use std::{ use std::{
any::Any, any::Any,
io::{Cursor, Write}, fmt,
io::{self, Cursor, Write},
}; };
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
@ -115,13 +116,13 @@ impl AzaleaRead for BlockState {
} }
} }
impl AzaleaWrite for BlockState { impl AzaleaWrite for BlockState {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
u32::azalea_write_var(&(self.id as u32), buf) u32::azalea_write_var(&(self.id as u32), buf)
} }
} }
impl std::fmt::Debug for BlockState { impl Debug for BlockState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"BlockState(id: {}, {:?})", "BlockState(id: {}, {:?})",
@ -134,50 +135,69 @@ impl std::fmt::Debug for BlockState {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FluidState { pub struct FluidState {
pub fluid: azalea_registry::Fluid, pub fluid: azalea_registry::Fluid,
pub height: u8, /// 0 = empty, 8 = full, 9 = max.
///
/// 9 is meant to be used when there's another fluid block of the same type
/// above it, but it's usually unused by this struct.
pub amount: u8,
}
impl FluidState {
/// A floating point number in between 0 and 1 representing the height (as a
/// percentage of a full block) of the fluid.
pub fn height(&self) -> f32 {
self.amount as f32 / 9.
}
} }
impl Default for FluidState { impl Default for FluidState {
fn default() -> Self { fn default() -> Self {
Self { Self {
fluid: azalea_registry::Fluid::Empty, fluid: azalea_registry::Fluid::Empty,
height: 0, amount: 0,
} }
} }
} }
impl From<BlockState> for FluidState { impl From<BlockState> for FluidState {
fn from(state: BlockState) -> Self { fn from(state: BlockState) -> Self {
// note that 8 here might be treated as 9 in some cases if there's another fluid
// block of the same type above it
if state if state
.property::<crate::properties::Waterlogged>() .property::<crate::properties::Waterlogged>()
.unwrap_or_default() .unwrap_or_default()
{ {
Self { Self {
fluid: azalea_registry::Fluid::Water, fluid: azalea_registry::Fluid::Water,
height: 15, amount: 8,
} }
} else { } else {
let block = Box::<dyn Block>::from(state); let block = Box::<dyn Block>::from(state);
if let Some(water) = block.downcast_ref::<crate::blocks::Water>() { if let Some(water) = block.downcast_ref::<crate::blocks::Water>() {
Self { Self {
fluid: azalea_registry::Fluid::Water, fluid: azalea_registry::Fluid::Water,
height: water.level as u8, amount: to_or_from_legacy_fluid_level(water.level as u8),
} }
} else if let Some(lava) = block.downcast_ref::<crate::blocks::Lava>() { } else if let Some(lava) = block.downcast_ref::<crate::blocks::Lava>() {
Self { Self {
fluid: azalea_registry::Fluid::Lava, fluid: azalea_registry::Fluid::Lava,
height: lava.level as u8, amount: to_or_from_legacy_fluid_level(lava.level as u8),
} }
} else { } else {
Self { Self {
fluid: azalea_registry::Fluid::Empty, fluid: azalea_registry::Fluid::Empty,
height: 0, amount: 0,
} }
} }
} }
} }
} }
// see FlowingFluid.getLegacyLevel
fn to_or_from_legacy_fluid_level(level: u8) -> u8 {
8_u8.saturating_sub(level)
}
impl From<FluidState> for BlockState { impl From<FluidState> for BlockState {
fn from(state: FluidState) -> Self { fn from(state: FluidState) -> Self {
match state.fluid { match state.fluid {
@ -185,14 +205,14 @@ impl From<FluidState> for BlockState {
azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater => { azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater => {
BlockState::from(crate::blocks::Water { BlockState::from(crate::blocks::Water {
level: crate::properties::WaterLevel::from( level: crate::properties::WaterLevel::from(
state.height as BlockStateIntegerRepr, state.amount as BlockStateIntegerRepr,
), ),
}) })
} }
azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava => { azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava => {
BlockState::from(crate::blocks::Lava { BlockState::from(crate::blocks::Lava {
level: crate::properties::LavaLevel::from( level: crate::properties::LavaLevel::from(
state.height as BlockStateIntegerRepr, state.amount as BlockStateIntegerRepr,
), ),
}) })
} }

View file

@ -104,7 +104,7 @@ pub fn update_fluid_on_eyes(
.read() .read()
.get_fluid_state(&eye_block_pos) .get_fluid_state(&eye_block_pos)
.unwrap_or_default(); .unwrap_or_default();
let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.height as f64 / 16f64); let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.amount as f64 / 16f64);
if fluid_cutoff_y > adjusted_eye_y { if fluid_cutoff_y > adjusted_eye_y {
**fluid_on_eyes = fluid_at_eye.fluid; **fluid_on_eyes = fluid_at_eye.fluid;
} else { } else {

View file

@ -19,4 +19,5 @@ azalea-registry = { path = "../azalea-registry", version = "0.11.0" }
azalea-world = { path = "../azalea-world", version = "0.11.0" } azalea-world = { path = "../azalea-world", version = "0.11.0" }
bevy_app = { workspace = true } bevy_app = { workspace = true }
bevy_ecs = { workspace = true } bevy_ecs = { workspace = true }
tracing = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -20,12 +20,11 @@ pub struct ClipContext {
// pub collision_context: EntityCollisionContext, // pub collision_context: EntityCollisionContext,
} }
impl ClipContext { impl ClipContext {
// minecraft passes in the world and blockpos here... but it doesn't actually
// seem necessary?
/// Get the shape of given block, using the type of shape set in /// Get the shape of given block, using the type of shape set in
/// [`Self::block_shape_type`]. /// [`Self::block_shape_type`].
pub fn block_shape(&self, block_state: BlockState) -> &VoxelShape { pub fn block_shape(&self, block_state: BlockState) -> &VoxelShape {
// minecraft passes in the world and blockpos to this function but it's not
// actually necessary. it is for fluid_shape though
match self.block_shape_type { match self.block_shape_type {
BlockShapeType::Collider => block_state.collision_shape(), BlockShapeType::Collider => block_state.collision_shape(),
BlockShapeType::Outline => block_state.outline_shape(), BlockShapeType::Outline => block_state.outline_shape(),
@ -41,6 +40,19 @@ impl ClipContext {
} }
} }
} }
pub fn fluid_shape(
&self,
fluid_state: FluidState,
world: &ChunkStorage,
pos: &BlockPos,
) -> &VoxelShape {
if self.fluid_pick_type.can_pick(&fluid_state) {
crate::collision::fluid_shape(&fluid_state, world, pos)
} else {
&EMPTY_SHAPE
}
}
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -63,6 +75,17 @@ pub enum FluidPickType {
Any, Any,
Water, Water,
} }
impl FluidPickType {
pub fn can_pick(&self, fluid_state: &FluidState) -> bool {
match self {
Self::None => false,
Self::SourceOnly => fluid_state.amount == 8,
Self::Any => fluid_state.fluid != azalea_registry::Fluid::Empty,
Self::Water => fluid_state.fluid == azalea_registry::Fluid::Water,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EntityCollisionContext { pub struct EntityCollisionContext {
pub descending: bool, pub descending: bool,
@ -81,15 +104,29 @@ pub fn clip(chunk_storage: &ChunkStorage, context: ClipContext) -> BlockHitResul
let block_state = chunk_storage.get_block_state(block_pos).unwrap_or_default(); let block_state = chunk_storage.get_block_state(block_pos).unwrap_or_default();
let fluid_state = FluidState::from(block_state); let fluid_state = FluidState::from(block_state);
// TODO: add fluid stuff to this (see getFluidState in vanilla source)
let block_shape = ctx.block_shape(block_state); let block_shape = ctx.block_shape(block_state);
let interaction_clip = clip_with_interaction_override(
&ctx.from,
&ctx.to,
block_pos,
block_shape,
&block_state,
);
let fluid_shape = ctx.fluid_shape(fluid_state, chunk_storage, block_pos);
let fluid_clip = fluid_shape.clip(&ctx.from, &ctx.to, block_pos);
clip_with_interaction_override(&ctx.from, &ctx.to, block_pos, block_shape, &block_state) let distance_to_interaction = interaction_clip
// let block_distance = if let Some(block_hit_result) = .map(|hit| ctx.from.distance_squared_to(&hit.location))
// block_hit_result { context.from.distance_squared_to(& .unwrap_or(f64::MAX);
// block_hit_result.location) } else { let distance_to_fluid = fluid_clip
// f64::INFINITY .map(|hit| ctx.from.distance_squared_to(&hit.location))
// }; .unwrap_or(f64::MAX);
if distance_to_interaction <= distance_to_fluid {
interaction_clip
} else {
fluid_clip
}
}, },
|context| { |context| {
let vec = context.from - context.to; let vec = context.from - context.to;
@ -107,9 +144,10 @@ fn clip_with_interaction_override(
to: &Vec3, to: &Vec3,
block_pos: &BlockPos, block_pos: &BlockPos,
block_shape: &VoxelShape, block_shape: &VoxelShape,
block_state: &BlockState, _block_state: &BlockState,
) -> Option<BlockHitResult> { ) -> Option<BlockHitResult> {
let block_hit_result = block_shape.clip(from, to, block_pos); let block_hit_result = block_shape.clip(from, to, block_pos);
if let Some(block_hit_result) = block_hit_result { if let Some(block_hit_result) = block_hit_result {
// TODO: minecraft calls .getInteractionShape here // TODO: minecraft calls .getInteractionShape here
// getInteractionShape is empty for almost every shape except cauldons, // getInteractionShape is empty for almost every shape except cauldons,
@ -123,9 +161,10 @@ fn clip_with_interaction_override(
return Some(block_hit_result.with_direction(interaction_hit_result.direction)); return Some(block_hit_result.with_direction(interaction_hit_result.direction));
} }
} }
Some(block_hit_result) Some(block_hit_result)
} else { } else {
block_hit_result None
} }
} }

View file

@ -4,14 +4,21 @@ mod mergers;
mod shape; mod shape;
mod world_collisions; mod world_collisions;
use std::ops::Add; use std::{ops::Add, sync::LazyLock};
use azalea_core::{aabb::AABB, direction::Axis, math::EPSILON, position::Vec3}; use azalea_block::FluidState;
use azalea_world::{Instance, MoveEntityError}; use azalea_core::{
aabb::AABB,
direction::Axis,
math::EPSILON,
position::{BlockPos, Vec3},
};
use azalea_world::{ChunkStorage, Instance, MoveEntityError};
use bevy_ecs::world::Mut; use bevy_ecs::world::Mut;
pub use blocks::BlockWithShape; pub use blocks::BlockWithShape;
pub use discrete_voxel_shape::*; pub use discrete_voxel_shape::*;
pub use shape::*; pub use shape::*;
use tracing::warn;
use self::world_collisions::get_block_collisions; use self::world_collisions::get_block_collisions;
@ -333,3 +340,48 @@ fn collide_with_shapes(
z: z_movement, z: z_movement,
} }
} }
/// Get the [`VoxelShape`] for the given fluid state.
///
/// The instance and position are required so it can check if the block above is
/// also the same fluid type.
pub fn fluid_shape(
fluid: &FluidState,
world: &ChunkStorage,
pos: &BlockPos,
) -> &'static VoxelShape {
if fluid.amount == 9 {
let fluid_state_above = world.get_fluid_state(&pos.up(1)).unwrap_or_default();
if fluid_state_above.fluid == fluid.fluid {
return &BLOCK_SHAPE;
}
}
// pre-calculate these in a LazyLock so this function can return a
// reference instead
static FLUID_SHAPES: LazyLock<[VoxelShape; 10]> = LazyLock::new(|| {
[
calculate_shape_for_fluid(0),
calculate_shape_for_fluid(1),
calculate_shape_for_fluid(2),
calculate_shape_for_fluid(3),
calculate_shape_for_fluid(4),
calculate_shape_for_fluid(5),
calculate_shape_for_fluid(6),
calculate_shape_for_fluid(7),
calculate_shape_for_fluid(8),
calculate_shape_for_fluid(9),
]
});
if fluid.amount > 9 {
warn!("Tried to calculate shape for fluid with height > 9: {fluid:?} at {pos}");
return &EMPTY_SHAPE;
}
&FLUID_SHAPES[fluid.amount as usize]
}
fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
}

View file

@ -413,7 +413,7 @@ impl VoxelShape {
VoxelShape::Cube(s) => s.find_index(axis, coord), VoxelShape::Cube(s) => s.find_index(axis, coord),
_ => { _ => {
let upper_limit = (self.shape().size(axis) + 1) as i32; let upper_limit = (self.shape().size(axis) + 1) as i32;
binary_search(0, upper_limit, &|t| coord < self.get(axis, t as usize)) - 1 binary_search(0, upper_limit, |t| coord < self.get(axis, t as usize)) - 1
} }
} }
} }

View file

@ -340,8 +340,6 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos) call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos)
}; };
let path;
let start_time = Instant::now(); let start_time = Instant::now();
let astar::Path { let astar::Path {
@ -375,7 +373,7 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
debug!(" {}", movement.target.apply(origin)); debug!(" {}", movement.target.apply(origin));
} }
path = movements.into_iter().collect::<VecDeque<_>>(); let path = movements.into_iter().collect::<VecDeque<_>>();
let goto_id_now = opts.goto_id_atomic.load(atomic::Ordering::SeqCst); let goto_id_now = opts.goto_id_atomic.load(atomic::Ordering::SeqCst);
if goto_id != goto_id_now { if goto_id != goto_id_now {
@ -520,6 +518,7 @@ pub fn path_found_listener(
} }
} }
#[allow(clippy::type_complexity)]
pub fn timeout_movement( pub fn timeout_movement(
mut query: Query<( mut query: Query<(
Entity, Entity,