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