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:
parent
04036b6e4a
commit
5693191b57
8 changed files with 144 additions and 32 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -463,6 +463,7 @@ dependencies = [
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
"bevy_time",
|
"bevy_time",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue