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