1use std::collections::HashSet;
2
3use azalea_block::{
4 fluid_state::{FluidKind, FluidState},
5 BlockState,
6};
7use azalea_core::{
8 aabb::AABB,
9 block_hit_result::BlockHitResult,
10 direction::{Axis, Direction},
11 math::{self, lerp, EPSILON},
12 position::{BlockPos, Vec3},
13};
14use azalea_inventory::ItemStack;
15use azalea_world::ChunkStorage;
16use bevy_ecs::entity::Entity;
17
18use crate::collision::{BlockWithShape, VoxelShape, EMPTY_SHAPE};
19
20#[derive(Debug, Clone)]
21pub struct ClipContext {
22 pub from: Vec3,
23 pub to: Vec3,
24 pub block_shape_type: BlockShapeType,
25 pub fluid_pick_type: FluidPickType,
26 }
28impl ClipContext {
29 pub fn block_shape(&self, block_state: BlockState) -> &VoxelShape {
32 match self.block_shape_type {
35 BlockShapeType::Collider => block_state.collision_shape(),
36 BlockShapeType::Outline => block_state.outline_shape(),
37 BlockShapeType::Visual => block_state.collision_shape(),
38 BlockShapeType::FallDamageResetting => {
39 if azalea_registry::tags::blocks::FALL_DAMAGE_RESETTING
40 .contains(&azalea_registry::Block::from(block_state))
41 {
42 block_state.collision_shape()
43 } else {
44 &EMPTY_SHAPE
45 }
46 }
47 }
48 }
49
50 pub fn fluid_shape(
51 &self,
52 fluid_state: FluidState,
53 world: &ChunkStorage,
54 pos: &BlockPos,
55 ) -> &VoxelShape {
56 if self.fluid_pick_type.can_pick(&fluid_state) {
57 crate::collision::fluid_shape(&fluid_state, world, pos)
58 } else {
59 &EMPTY_SHAPE
60 }
61 }
62}
63
64#[derive(Debug, Copy, Clone)]
65pub enum BlockShapeType {
66 Collider,
68 Outline,
70 Visual,
75 FallDamageResetting,
76}
77#[derive(Debug, Copy, Clone)]
78pub enum FluidPickType {
79 None,
80 SourceOnly,
81 Any,
82 Water,
83}
84impl FluidPickType {
85 pub fn can_pick(&self, fluid_state: &FluidState) -> bool {
86 match self {
87 Self::None => false,
88 Self::SourceOnly => fluid_state.amount == 8,
89 Self::Any => fluid_state.kind != FluidKind::Empty,
90 Self::Water => fluid_state.kind == FluidKind::Water,
91 }
92 }
93}
94
95#[derive(Debug, Clone)]
96pub struct EntityCollisionContext {
97 pub descending: bool,
98 pub entity_bottom: f64,
99 pub held_item: ItemStack,
100 pub entity: Entity,
102}
103
104pub fn clip(chunk_storage: &ChunkStorage, context: ClipContext) -> BlockHitResult {
105 traverse_blocks(
106 context.from,
107 context.to,
108 context,
109 |ctx, block_pos| {
110 let block_state = chunk_storage.get_block_state(block_pos).unwrap_or_default();
111 let fluid_state = FluidState::from(block_state);
112
113 let block_shape = ctx.block_shape(block_state);
114 let interaction_clip = clip_with_interaction_override(
115 &ctx.from,
116 &ctx.to,
117 block_pos,
118 block_shape,
119 &block_state,
120 );
121 let fluid_shape = ctx.fluid_shape(fluid_state, chunk_storage, block_pos);
122 let fluid_clip = fluid_shape.clip(&ctx.from, &ctx.to, block_pos);
123
124 let distance_to_interaction = interaction_clip
125 .map(|hit| ctx.from.distance_squared_to(&hit.location))
126 .unwrap_or(f64::MAX);
127 let distance_to_fluid = fluid_clip
128 .map(|hit| ctx.from.distance_squared_to(&hit.location))
129 .unwrap_or(f64::MAX);
130
131 if distance_to_interaction <= distance_to_fluid {
132 interaction_clip
133 } else {
134 fluid_clip
135 }
136 },
137 |context| {
138 let vec = context.from - context.to;
139 BlockHitResult::miss(
140 context.to,
141 Direction::nearest(vec),
142 BlockPos::from(context.to),
143 )
144 },
145 )
146}
147
148fn clip_with_interaction_override(
149 from: &Vec3,
150 to: &Vec3,
151 block_pos: &BlockPos,
152 block_shape: &VoxelShape,
153 _block_state: &BlockState,
154) -> Option<BlockHitResult> {
155 let block_hit_result = block_shape.clip(from, to, block_pos);
156
157 if let Some(block_hit_result) = block_hit_result {
158 let interaction_shape = &*EMPTY_SHAPE;
162 let interaction_hit_result = interaction_shape.clip(from, to, block_pos);
163 if let Some(interaction_hit_result) = interaction_hit_result {
164 if interaction_hit_result.location.distance_squared_to(from)
165 < block_hit_result.location.distance_squared_to(from)
166 {
167 return Some(block_hit_result.with_direction(interaction_hit_result.direction));
168 }
169 }
170
171 Some(block_hit_result)
172 } else {
173 None
174 }
175}
176
177pub fn traverse_blocks<C, T>(
178 from: Vec3,
179 to: Vec3,
180 context: C,
181 get_hit_result: impl Fn(&C, &BlockPos) -> Option<T>,
182 get_miss_result: impl Fn(&C) -> T,
183) -> T {
184 if from == to {
185 return get_miss_result(&context);
186 }
187
188 let right_after_end = Vec3 {
189 x: lerp(-EPSILON, to.x, from.x),
190 y: lerp(-EPSILON, to.y, from.y),
191 z: lerp(-EPSILON, to.z, from.z),
192 };
193
194 let right_before_start = Vec3 {
195 x: lerp(-EPSILON, from.x, to.x),
196 y: lerp(-EPSILON, from.y, to.y),
197 z: lerp(-EPSILON, from.z, to.z),
198 };
199
200 let mut current_block = BlockPos::from(right_before_start);
201 if let Some(data) = get_hit_result(&context, ¤t_block) {
202 return data;
203 }
204
205 let vec = right_after_end - right_before_start;
206
207 let vec_sign = Vec3 {
208 x: math::sign(vec.x),
209 y: math::sign(vec.y),
210 z: math::sign(vec.z),
211 };
212
213 #[rustfmt::skip]
214 let percentage_step = Vec3 {
215 x: if vec_sign.x == 0. { f64::MAX } else { vec_sign.x / vec.x },
216 y: if vec_sign.y == 0. { f64::MAX } else { vec_sign.y / vec.y },
217 z: if vec_sign.z == 0. { f64::MAX } else { vec_sign.z / vec.z },
218 };
219
220 let mut percentage = Vec3 {
221 x: percentage_step.x
222 * if vec_sign.x > 0. {
223 1. - math::fract(right_before_start.x)
224 } else {
225 math::fract(right_before_start.x)
226 },
227 y: percentage_step.y
228 * if vec_sign.y > 0. {
229 1. - math::fract(right_before_start.y)
230 } else {
231 math::fract(right_before_start.y)
232 },
233 z: percentage_step.z
234 * if vec_sign.z > 0. {
235 1. - math::fract(right_before_start.z)
236 } else {
237 math::fract(right_before_start.z)
238 },
239 };
240
241 loop {
242 if percentage.x > 1. && percentage.y > 1. && percentage.z > 1. {
243 return get_miss_result(&context);
244 }
245
246 if percentage.x < percentage.y {
247 if percentage.x < percentage.z {
248 current_block.x += vec_sign.x as i32;
249 percentage.x += percentage_step.x;
250 } else {
251 current_block.z += vec_sign.z as i32;
252 percentage.z += percentage_step.z;
253 }
254 } else if percentage.y < percentage.z {
255 current_block.y += vec_sign.y as i32;
256 percentage.y += percentage_step.y;
257 } else {
258 current_block.z += vec_sign.z as i32;
259 percentage.z += percentage_step.z;
260 }
261
262 if let Some(data) = get_hit_result(&context, ¤t_block) {
263 return data;
264 }
265 }
266}
267
268pub fn box_traverse_blocks(from: &Vec3, to: &Vec3, aabb: &AABB) -> HashSet<BlockPos> {
269 let delta = to - from;
270 let traversed_blocks = BlockPos::between_closed_aabb(aabb);
271 if delta.length_squared() < (0.99999_f32 * 0.99999) as f64 {
272 return traversed_blocks.into_iter().collect();
273 }
274
275 let mut traversed_and_collided_blocks = HashSet::new();
276 let target_min_pos = aabb.min;
277 let from_min_pos = target_min_pos - delta;
278 add_collisions_along_travel(
279 &mut traversed_and_collided_blocks,
280 from_min_pos,
281 target_min_pos,
282 *aabb,
283 );
284 traversed_and_collided_blocks.extend(traversed_blocks);
285 traversed_and_collided_blocks
286}
287
288pub fn add_collisions_along_travel(
289 collisions: &mut HashSet<BlockPos>,
290 from: Vec3,
291 to: Vec3,
292 aabb: AABB,
293) {
294 let delta = to - from;
295 let mut min_x = from.x.floor() as i32;
296 let mut min_y = from.y.floor() as i32;
297 let mut min_z = from.z.floor() as i32;
298 let direction_x = math::sign_as_int(delta.x);
299 let direction_y = math::sign_as_int(delta.y);
300 let direction_z = math::sign_as_int(delta.z);
301 let step_x = if direction_x == 0 {
302 f64::MAX
303 } else {
304 direction_x as f64 / delta.x
305 };
306 let step_y = if direction_y == 0 {
307 f64::MAX
308 } else {
309 direction_y as f64 / delta.y
310 };
311 let step_z = if direction_z == 0 {
312 f64::MAX
313 } else {
314 direction_z as f64 / delta.z
315 };
316 let mut cur_x = step_x
317 * if direction_x > 0 {
318 1. - math::fract(from.x)
319 } else {
320 math::fract(from.x)
321 };
322 let mut cur_y = step_y
323 * if direction_y > 0 {
324 1. - math::fract(from.y)
325 } else {
326 math::fract(from.y)
327 };
328 let mut cur_z = step_z
329 * if direction_z > 0 {
330 1. - math::fract(from.z)
331 } else {
332 math::fract(from.z)
333 };
334 let mut step_count = 0;
335
336 while cur_x <= 1. || cur_y <= 1. || cur_z <= 1. {
337 if cur_x < cur_y {
338 if cur_x < cur_z {
339 min_x += direction_x;
340 cur_x += step_x;
341 } else {
342 min_z += direction_z;
343 cur_z += step_z;
344 }
345 } else if cur_y < cur_z {
346 min_y += direction_y;
347 cur_y += step_y;
348 } else {
349 min_z += direction_z;
350 cur_z += step_z;
351 }
352
353 if step_count > 16 {
354 break;
355 }
356 step_count += 1;
357
358 let Some(clip_location) = AABB::clip_with_from_and_to(
359 &Vec3::new(min_x as f64, min_y as f64, min_z as f64),
360 &Vec3::new((min_x + 1) as f64, (min_y + 1) as f64, (min_z + 1) as f64),
361 &from,
362 &to,
363 ) else {
364 continue;
365 };
366
367 let initial_max_x = clip_location
368 .x
369 .clamp(min_x as f64 + 1.0E-5, min_x as f64 + 1.0 - 1.0E-5);
370 let initial_max_y = clip_location
371 .y
372 .clamp(min_y as f64 + 1.0E-5, min_y as f64 + 1.0 - 1.0E-5);
373 let initial_max_z = clip_location
374 .z
375 .clamp(min_z as f64 + 1.0E-5, min_z as f64 + 1.0 - 1.0E-5);
376 let max_x = (initial_max_x + aabb.get_size(Axis::X)).floor() as i32;
377 let max_y = (initial_max_y + aabb.get_size(Axis::Y)).floor() as i32;
378 let max_z = (initial_max_z + aabb.get_size(Axis::Z)).floor() as i32;
379
380 for x in min_x..=max_x {
381 for y in min_y..=max_y {
382 for z in min_z..=max_z {
383 collisions.insert(BlockPos::new(x, y, z));
384 }
385 }
386 }
387 }
388}