Skip to content

Commit 5693191

Browse files
committed
implement fluid_shape
1 parent 04036b6 commit 5693191

File tree

8 files changed

+145
-33
lines changed

8 files changed

+145
-33
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

azalea-block/src/lib.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ mod range;
88
use core::fmt::Debug;
99
use std::{
1010
any::Any,
11-
io::{Cursor, Write},
11+
fmt,
12+
io::{self, Cursor, Write},
1213
};
1314

1415
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
@@ -115,13 +116,13 @@ impl AzaleaRead for BlockState {
115116
}
116117
}
117118
impl AzaleaWrite for BlockState {
118-
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
119+
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
119120
u32::azalea_write_var(&(self.id as u32), buf)
120121
}
121122
}
122123

123-
impl std::fmt::Debug for BlockState {
124-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124+
impl Debug for BlockState {
125+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125126
write!(
126127
f,
127128
"BlockState(id: {}, {:?})",
@@ -134,65 +135,84 @@ impl std::fmt::Debug for BlockState {
134135
#[derive(Clone, Debug)]
135136
pub struct FluidState {
136137
pub fluid: azalea_registry::Fluid,
137-
pub height: u8,
138+
/// 0 = empty, 8 = full, 9 = max.
139+
///
140+
/// 9 is meant to be used when there's another fluid block of the same type
141+
/// above it, but it's usually unused by this struct.
142+
pub amount: u8,
143+
}
144+
impl FluidState {
145+
/// A floating point number in between 0 and 1 representing the height (as a
146+
/// percentage of a full block) of the fluid.
147+
pub fn height(&self) -> f32 {
148+
self.amount as f32 / 9.
149+
}
138150
}
139151

140152
impl Default for FluidState {
141153
fn default() -> Self {
142154
Self {
143155
fluid: azalea_registry::Fluid::Empty,
144-
height: 0,
156+
amount: 0,
145157
}
146158
}
147159
}
148160

149161
impl From<BlockState> for FluidState {
150162
fn from(state: BlockState) -> Self {
163+
// note that 8 here might be treated as 9 in some cases if there's another fluid
164+
// block of the same type above it
165+
151166
if state
152167
.property::<crate::properties::Waterlogged>()
153168
.unwrap_or_default()
154169
{
155170
Self {
156171
fluid: azalea_registry::Fluid::Water,
157-
height: 15,
172+
amount: 8,
158173
}
159174
} else {
160175
let block = Box::<dyn Block>::from(state);
161176
if let Some(water) = block.downcast_ref::<crate::blocks::Water>() {
162177
Self {
163178
fluid: azalea_registry::Fluid::Water,
164-
height: water.level as u8,
179+
amount: to_or_from_legacy_fluid_level(water.level as u8),
165180
}
166181
} else if let Some(lava) = block.downcast_ref::<crate::blocks::Lava>() {
167182
Self {
168183
fluid: azalea_registry::Fluid::Lava,
169-
height: lava.level as u8,
184+
amount: to_or_from_legacy_fluid_level(lava.level as u8),
170185
}
171186
} else {
172187
Self {
173188
fluid: azalea_registry::Fluid::Empty,
174-
height: 0,
189+
amount: 0,
175190
}
176191
}
177192
}
178193
}
179194
}
180195

196+
// see FlowingFluid.getLegacyLevel
197+
fn to_or_from_legacy_fluid_level(level: u8) -> u8 {
198+
8_u8.saturating_sub(level)
199+
}
200+
181201
impl From<FluidState> for BlockState {
182202
fn from(state: FluidState) -> Self {
183203
match state.fluid {
184204
azalea_registry::Fluid::Empty => BlockState::AIR,
185205
azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater => {
186206
BlockState::from(crate::blocks::Water {
187207
level: crate::properties::WaterLevel::from(
188-
state.height as BlockStateIntegerRepr,
208+
state.amount as BlockStateIntegerRepr,
189209
),
190210
})
191211
}
192212
azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava => {
193213
BlockState::from(crate::blocks::Lava {
194214
level: crate::properties::LavaLevel::from(
195-
state.height as BlockStateIntegerRepr,
215+
state.amount as BlockStateIntegerRepr,
196216
),
197217
})
198218
}

azalea-entity/src/plugin/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ pub fn update_fluid_on_eyes(
104104
.read()
105105
.get_fluid_state(&eye_block_pos)
106106
.unwrap_or_default();
107-
let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.height as f64 / 16f64);
107+
let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.amount as f64 / 16f64);
108108
if fluid_cutoff_y > adjusted_eye_y {
109109
**fluid_on_eyes = fluid_at_eye.fluid;
110110
} else {

azalea-physics/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ azalea-registry = { path = "../azalea-registry", version = "0.11.0" }
1919
azalea-world = { path = "../azalea-world", version = "0.11.0" }
2020
bevy_app = { workspace = true }
2121
bevy_ecs = { workspace = true }
22+
tracing = { workspace = true }
2223
parking_lot = { workspace = true }

azalea-physics/src/clip.rs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@ pub struct ClipContext {
2020
// pub collision_context: EntityCollisionContext,
2121
}
2222
impl ClipContext {
23-
// minecraft passes in the world and blockpos here... but it doesn't actually
24-
// seem necessary?
25-
2623
/// Get the shape of given block, using the type of shape set in
2724
/// [`Self::block_shape_type`].
2825
pub fn block_shape(&self, block_state: BlockState) -> &VoxelShape {
26+
// minecraft passes in the world and blockpos to this function but it's not
27+
// actually necessary. it is for fluid_shape though
2928
match self.block_shape_type {
3029
BlockShapeType::Collider => block_state.collision_shape(),
3130
BlockShapeType::Outline => block_state.outline_shape(),
@@ -41,6 +40,19 @@ impl ClipContext {
4140
}
4241
}
4342
}
43+
44+
pub fn fluid_shape(
45+
&self,
46+
fluid_state: FluidState,
47+
world: &ChunkStorage,
48+
pos: &BlockPos,
49+
) -> &VoxelShape {
50+
if self.fluid_pick_type.can_pick(&fluid_state) {
51+
crate::collision::fluid_shape(&fluid_state, world, pos)
52+
} else {
53+
&EMPTY_SHAPE
54+
}
55+
}
4456
}
4557

4658
#[derive(Debug, Copy, Clone)]
@@ -63,6 +75,17 @@ pub enum FluidPickType {
6375
Any,
6476
Water,
6577
}
78+
impl FluidPickType {
79+
pub fn can_pick(&self, fluid_state: &FluidState) -> bool {
80+
match self {
81+
Self::None => false,
82+
Self::SourceOnly => fluid_state.amount == 8,
83+
Self::Any => fluid_state.fluid != azalea_registry::Fluid::Empty,
84+
Self::Water => fluid_state.fluid == azalea_registry::Fluid::Water,
85+
}
86+
}
87+
}
88+
6689
#[derive(Debug, Clone)]
6790
pub struct EntityCollisionContext {
6891
pub descending: bool,
@@ -81,15 +104,29 @@ pub fn clip(chunk_storage: &ChunkStorage, context: ClipContext) -> BlockHitResul
81104
let block_state = chunk_storage.get_block_state(block_pos).unwrap_or_default();
82105
let fluid_state = FluidState::from(block_state);
83106

84-
// TODO: add fluid stuff to this (see getFluidState in vanilla source)
85107
let block_shape = ctx.block_shape(block_state);
108+
let interaction_clip = clip_with_interaction_override(
109+
&ctx.from,
110+
&ctx.to,
111+
block_pos,
112+
block_shape,
113+
&block_state,
114+
);
115+
let fluid_shape = ctx.fluid_shape(fluid_state, chunk_storage, block_pos);
116+
let fluid_clip = fluid_shape.clip(&ctx.from, &ctx.to, block_pos);
86117

87-
clip_with_interaction_override(&ctx.from, &ctx.to, block_pos, block_shape, &block_state)
88-
// let block_distance = if let Some(block_hit_result) =
89-
// block_hit_result { context.from.distance_squared_to(&
90-
// block_hit_result.location) } else {
91-
// f64::INFINITY
92-
// };
118+
let distance_to_interaction = interaction_clip
119+
.map(|hit| ctx.from.distance_squared_to(&hit.location))
120+
.unwrap_or(f64::MAX);
121+
let distance_to_fluid = fluid_clip
122+
.map(|hit| ctx.from.distance_squared_to(&hit.location))
123+
.unwrap_or(f64::MAX);
124+
125+
if distance_to_interaction <= distance_to_fluid {
126+
interaction_clip
127+
} else {
128+
fluid_clip
129+
}
93130
},
94131
|context| {
95132
let vec = context.from - context.to;
@@ -107,9 +144,10 @@ fn clip_with_interaction_override(
107144
to: &Vec3,
108145
block_pos: &BlockPos,
109146
block_shape: &VoxelShape,
110-
block_state: &BlockState,
147+
_block_state: &BlockState,
111148
) -> Option<BlockHitResult> {
112149
let block_hit_result = block_shape.clip(from, to, block_pos);
150+
113151
if let Some(block_hit_result) = block_hit_result {
114152
// TODO: minecraft calls .getInteractionShape here
115153
// getInteractionShape is empty for almost every shape except cauldons,
@@ -123,9 +161,10 @@ fn clip_with_interaction_override(
123161
return Some(block_hit_result.with_direction(interaction_hit_result.direction));
124162
}
125163
}
164+
126165
Some(block_hit_result)
127166
} else {
128-
block_hit_result
167+
None
129168
}
130169
}
131170

azalea-physics/src/collision/mod.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@ mod mergers;
44
mod shape;
55
mod world_collisions;
66

7-
use std::ops::Add;
8-
9-
use azalea_core::{aabb::AABB, direction::Axis, math::EPSILON, position::Vec3};
10-
use azalea_world::{Instance, MoveEntityError};
7+
use std::{ops::Add, sync::LazyLock};
8+
9+
use azalea_block::FluidState;
10+
use azalea_core::{
11+
aabb::AABB,
12+
direction::Axis,
13+
math::EPSILON,
14+
position::{BlockPos, Vec3},
15+
};
16+
use azalea_world::{ChunkStorage, Instance, MoveEntityError};
1117
use bevy_ecs::world::Mut;
1218
pub use blocks::BlockWithShape;
1319
pub use discrete_voxel_shape::*;
1420
pub use shape::*;
21+
use tracing::warn;
1522

1623
use self::world_collisions::get_block_collisions;
1724

@@ -333,3 +340,48 @@ fn collide_with_shapes(
333340
z: z_movement,
334341
}
335342
}
343+
344+
/// Get the [`VoxelShape`] for the given fluid state.
345+
///
346+
/// The instance and position are required so it can check if the block above is
347+
/// also the same fluid type.
348+
pub fn fluid_shape(
349+
fluid: &FluidState,
350+
world: &ChunkStorage,
351+
pos: &BlockPos,
352+
) -> &'static VoxelShape {
353+
if fluid.amount == 9 {
354+
let fluid_state_above = world.get_fluid_state(&pos.up(1)).unwrap_or_default();
355+
if fluid_state_above.fluid == fluid.fluid {
356+
return &BLOCK_SHAPE;
357+
}
358+
}
359+
360+
// pre-calculate these in a LazyLock so this function can return a
361+
// reference instead
362+
363+
static FLUID_SHAPES: LazyLock<[VoxelShape; 10]> = LazyLock::new(|| {
364+
[
365+
calculate_shape_for_fluid(0),
366+
calculate_shape_for_fluid(1),
367+
calculate_shape_for_fluid(2),
368+
calculate_shape_for_fluid(3),
369+
calculate_shape_for_fluid(4),
370+
calculate_shape_for_fluid(5),
371+
calculate_shape_for_fluid(6),
372+
calculate_shape_for_fluid(7),
373+
calculate_shape_for_fluid(8),
374+
calculate_shape_for_fluid(9),
375+
]
376+
});
377+
378+
if fluid.amount > 9 {
379+
warn!("Tried to calculate shape for fluid with height > 9: {fluid:?} at {pos}");
380+
return &EMPTY_SHAPE;
381+
}
382+
383+
&FLUID_SHAPES[fluid.amount as usize]
384+
}
385+
fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
386+
box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
387+
}

azalea-physics/src/collision/shape.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ impl VoxelShape {
413413
VoxelShape::Cube(s) => s.find_index(axis, coord),
414414
_ => {
415415
let upper_limit = (self.shape().size(axis) + 1) as i32;
416-
binary_search(0, upper_limit, &|t| coord < self.get(axis, t as usize)) - 1
416+
binary_search(0, upper_limit, |t| coord < self.get(axis, t as usize)) - 1
417417
}
418418
}
419419
}

azalea/src/pathfinder/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,6 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
340340
call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos)
341341
};
342342

343-
let path;
344-
345343
let start_time = Instant::now();
346344

347345
let astar::Path {
@@ -375,7 +373,7 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
375373
debug!(" {}", movement.target.apply(origin));
376374
}
377375

378-
path = movements.into_iter().collect::<VecDeque<_>>();
376+
let path = movements.into_iter().collect::<VecDeque<_>>();
379377

380378
let goto_id_now = opts.goto_id_atomic.load(atomic::Ordering::SeqCst);
381379
if goto_id != goto_id_now {
@@ -520,6 +518,7 @@ pub fn path_found_listener(
520518
}
521519
}
522520

521+
#[allow(clippy::type_complexity)]
523522
pub fn timeout_movement(
524523
mut query: Query<(
525524
Entity,

0 commit comments

Comments
 (0)