1use std::collections::HashSet;
2
3use azalea_block::{
4 BlockState,
5 fluid_state::{FluidKind, FluidState},
6};
7use azalea_core::{
8 aabb::AABB,
9 block_hit_result::BlockHitResult,
10 direction::{Axis, Direction},
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 .map(|hit| ctx.from.distance_squared_to(&hit.location))
115 .unwrap_or(f64::MAX);
116 let distance_to_fluid = fluid_clip
117 .map(|hit| ctx.from.distance_squared_to(&hit.location))
118 .unwrap_or(f64::MAX);
119
120 if distance_to_interaction <= distance_to_fluid {
121 interaction_clip
122 } else {
123 fluid_clip
124 }
125 },
126 |context| {
127 let vec = context.from - context.to;
128 BlockHitResult::miss(
129 context.to,
130 Direction::nearest(vec),
131 BlockPos::from(context.to),
132 )
133 },
134 )
135}
136
137fn clip_with_interaction_override(
138 from: &Vec3,
139 to: &Vec3,
140 block_pos: &BlockPos,
141 block_shape: &VoxelShape,
142 _block_state: &BlockState,
143) -> Option<BlockHitResult> {
144 let block_hit_result = block_shape.clip(from, to, block_pos);
145
146 if let Some(block_hit_result) = block_hit_result {
147 let interaction_shape = &*EMPTY_SHAPE;
151 let interaction_hit_result = interaction_shape.clip(from, to, block_pos);
152 if let Some(interaction_hit_result) = interaction_hit_result {
153 if interaction_hit_result.location.distance_squared_to(from)
154 < block_hit_result.location.distance_squared_to(from)
155 {
156 return Some(block_hit_result.with_direction(interaction_hit_result.direction));
157 }
158 }
159
160 Some(block_hit_result)
161 } else {
162 None
163 }
164}
165
166pub fn traverse_blocks<C, T>(
167 from: Vec3,
168 to: Vec3,
169 context: C,
170 get_hit_result: impl Fn(&C, &BlockPos) -> Option<T>,
171 get_miss_result: impl Fn(&C) -> T,
172) -> T {
173 if from == to {
174 return get_miss_result(&context);
175 }
176
177 let right_after_end = Vec3 {
178 x: lerp(-EPSILON, to.x, from.x),
179 y: lerp(-EPSILON, to.y, from.y),
180 z: lerp(-EPSILON, to.z, from.z),
181 };
182
183 let right_before_start = Vec3 {
184 x: lerp(-EPSILON, from.x, to.x),
185 y: lerp(-EPSILON, from.y, to.y),
186 z: lerp(-EPSILON, from.z, to.z),
187 };
188
189 let mut current_block = BlockPos::from(right_before_start);
190 if let Some(data) = get_hit_result(&context, ¤t_block) {
191 return data;
192 }
193
194 let vec = right_after_end - right_before_start;
195
196 let vec_sign = Vec3 {
197 x: math::sign(vec.x),
198 y: math::sign(vec.y),
199 z: math::sign(vec.z),
200 };
201
202 #[rustfmt::skip]
203 let percentage_step = Vec3 {
204 x: if vec_sign.x == 0. { f64::MAX } else { vec_sign.x / vec.x },
205 y: if vec_sign.y == 0. { f64::MAX } else { vec_sign.y / vec.y },
206 z: if vec_sign.z == 0. { f64::MAX } else { vec_sign.z / vec.z },
207 };
208
209 let mut percentage = Vec3 {
210 x: percentage_step.x
211 * if vec_sign.x > 0. {
212 1. - math::fract(right_before_start.x)
213 } else {
214 math::fract(right_before_start.x)
215 },
216 y: percentage_step.y
217 * if vec_sign.y > 0. {
218 1. - math::fract(right_before_start.y)
219 } else {
220 math::fract(right_before_start.y)
221 },
222 z: percentage_step.z
223 * if vec_sign.z > 0. {
224 1. - math::fract(right_before_start.z)
225 } else {
226 math::fract(right_before_start.z)
227 },
228 };
229
230 loop {
231 if percentage.x > 1. && percentage.y > 1. && percentage.z > 1. {
232 return get_miss_result(&context);
233 }
234
235 if percentage.x < percentage.y {
236 if percentage.x < percentage.z {
237 current_block.x += vec_sign.x as i32;
238 percentage.x += percentage_step.x;
239 } else {
240 current_block.z += vec_sign.z as i32;
241 percentage.z += percentage_step.z;
242 }
243 } else if percentage.y < percentage.z {
244 current_block.y += vec_sign.y as i32;
245 percentage.y += percentage_step.y;
246 } else {
247 current_block.z += vec_sign.z as i32;
248 percentage.z += percentage_step.z;
249 }
250
251 if let Some(data) = get_hit_result(&context, ¤t_block) {
252 return data;
253 }
254 }
255}
256
257pub fn box_traverse_blocks(from: &Vec3, to: &Vec3, aabb: &AABB) -> HashSet<BlockPos> {
258 let delta = to - from;
259 let traversed_blocks = BlockPos::between_closed_aabb(aabb);
260 if delta.length_squared() < (0.99999_f32 * 0.99999) as f64 {
261 return traversed_blocks.into_iter().collect();
262 }
263
264 let mut traversed_and_collided_blocks = HashSet::new();
265 let target_min_pos = aabb.min;
266 let from_min_pos = target_min_pos - delta;
267 add_collisions_along_travel(
268 &mut traversed_and_collided_blocks,
269 from_min_pos,
270 target_min_pos,
271 *aabb,
272 );
273 traversed_and_collided_blocks.extend(traversed_blocks);
274 traversed_and_collided_blocks
275}
276
277pub fn add_collisions_along_travel(
278 collisions: &mut HashSet<BlockPos>,
279 from: Vec3,
280 to: Vec3,
281 aabb: AABB,
282) {
283 let delta = to - from;
284 let mut min_x = from.x.floor() as i32;
285 let mut min_y = from.y.floor() as i32;
286 let mut min_z = from.z.floor() as i32;
287 let direction_x = math::sign_as_int(delta.x);
288 let direction_y = math::sign_as_int(delta.y);
289 let direction_z = math::sign_as_int(delta.z);
290 let step_x = if direction_x == 0 {
291 f64::MAX
292 } else {
293 direction_x as f64 / delta.x
294 };
295 let step_y = if direction_y == 0 {
296 f64::MAX
297 } else {
298 direction_y as f64 / delta.y
299 };
300 let step_z = if direction_z == 0 {
301 f64::MAX
302 } else {
303 direction_z as f64 / delta.z
304 };
305 let mut cur_x = step_x
306 * if direction_x > 0 {
307 1. - math::fract(from.x)
308 } else {
309 math::fract(from.x)
310 };
311 let mut cur_y = step_y
312 * if direction_y > 0 {
313 1. - math::fract(from.y)
314 } else {
315 math::fract(from.y)
316 };
317 let mut cur_z = step_z
318 * if direction_z > 0 {
319 1. - math::fract(from.z)
320 } else {
321 math::fract(from.z)
322 };
323 let mut step_count = 0;
324
325 while cur_x <= 1. || cur_y <= 1. || cur_z <= 1. {
326 if cur_x < cur_y {
327 if cur_x < cur_z {
328 min_x += direction_x;
329 cur_x += step_x;
330 } else {
331 min_z += direction_z;
332 cur_z += step_z;
333 }
334 } else if cur_y < cur_z {
335 min_y += direction_y;
336 cur_y += step_y;
337 } else {
338 min_z += direction_z;
339 cur_z += step_z;
340 }
341
342 if step_count > 16 {
343 break;
344 }
345 step_count += 1;
346
347 let Some(clip_location) = AABB::clip_with_from_and_to(
348 &Vec3::new(min_x as f64, min_y as f64, min_z as f64),
349 &Vec3::new((min_x + 1) as f64, (min_y + 1) as f64, (min_z + 1) as f64),
350 &from,
351 &to,
352 ) else {
353 continue;
354 };
355
356 let initial_max_x = clip_location
357 .x
358 .clamp(min_x as f64 + 1.0E-5, min_x as f64 + 1.0 - 1.0E-5);
359 let initial_max_y = clip_location
360 .y
361 .clamp(min_y as f64 + 1.0E-5, min_y as f64 + 1.0 - 1.0E-5);
362 let initial_max_z = clip_location
363 .z
364 .clamp(min_z as f64 + 1.0E-5, min_z as f64 + 1.0 - 1.0E-5);
365 let max_x = (initial_max_x + aabb.get_size(Axis::X)).floor() as i32;
366 let max_y = (initial_max_y + aabb.get_size(Axis::Y)).floor() as i32;
367 let max_z = (initial_max_z + aabb.get_size(Axis::Z)).floor() as i32;
368
369 for x in min_x..=max_x {
370 for y in min_y..=max_y {
371 for z in min_z..=max_z {
372 collisions.insert(BlockPos::new(x, y, z));
373 }
374 }
375 }
376 }
377}