1use super::*;
2
3#[derive(Debug)]
7pub enum RaytraceAction {
8 Pass,
10 CheckShape,
12 ImmediateHit,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ClipBlockShape {
19 Collider,
21 Outline,
23 Visual,
25 FallDamageResetting {
27 entity_is_player: bool,
29 },
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ClipFluid {
35 None,
37 SourceOnly,
39 Any,
41 Water,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq)]
47pub struct ClipHitResult {
48 pub location: DVec3,
50 pub direction: Direction,
52 pub block_pos: BlockPos,
54 pub miss: bool,
56 pub inside: bool,
58 pub world_border_hit: bool,
60}
61
62impl ClipHitResult {
63 #[must_use]
65 pub const fn is_miss(self) -> bool {
66 self.miss
67 }
68}
69
70impl World {
71 pub fn ray_outline_check(
73 &self,
74 block_pos: BlockPos,
75 from: DVec3,
76 to: DVec3,
77 ) -> (bool, Option<Direction>) {
78 let state = self.get_block_state(block_pos);
79 let shape = state.get_outline_shape_at(block_pos);
80
81 match Self::clip_shape(block_pos, from, to, shape) {
82 Some(hit) => (true, Some(hit.direction)),
83 None => (false, None),
84 }
85 }
86
87 #[must_use]
89 pub fn clip(
90 &self,
91 start_pos: DVec3,
92 end_pos: DVec3,
93 block_shape: ClipBlockShape,
94 fluid: ClipFluid,
95 ) -> ClipHitResult {
96 if start_pos == end_pos {
97 return Self::clip_miss(start_pos, end_pos);
98 }
99
100 let adjust = -1.0e-7f64;
101 let to = end_pos.lerp(start_pos, adjust);
102 let from = start_pos.lerp(end_pos, adjust);
103
104 let mut block = BlockPos::new(
105 from.x.floor() as i32,
106 from.y.floor() as i32,
107 from.z.floor() as i32,
108 );
109
110 if let Some(hit) = self.clip_block_and_fluid(block, start_pos, end_pos, block_shape, fluid)
111 {
112 return hit;
113 }
114
115 let difference = to - from;
116
117 let step = difference.signum().as_ivec3();
118
119 let delta = DVec3::new(
120 if step.x == 0 {
121 f64::MAX
122 } else {
123 (f64::from(step.x)) / difference.x
124 },
125 if step.y == 0 {
126 f64::MAX
127 } else {
128 (f64::from(step.y)) / difference.y
129 },
130 if step.z == 0 {
131 f64::MAX
132 } else {
133 (f64::from(step.z)) / difference.z
134 },
135 );
136
137 let mut next = DVec3::new(
138 delta.x
139 * (if step.x > 0 {
140 1.0 - (from.x - from.x.floor())
141 } else {
142 from.x - from.x.floor()
143 }),
144 delta.y
145 * (if step.y > 0 {
146 1.0 - (from.y - from.y.floor())
147 } else {
148 from.y - from.y.floor()
149 }),
150 delta.z
151 * (if step.z > 0 {
152 1.0 - (from.z - from.z.floor())
153 } else {
154 from.z - from.z.floor()
155 }),
156 );
157
158 while next.x <= 1.0 || next.y <= 1.0 || next.z <= 1.0 {
159 if next.x < next.y && next.x < next.z {
160 block.0.x += step.x;
161 next.x += delta.x;
162 } else if next.y < next.z {
163 block.0.y += step.y;
164 next.y += delta.y;
165 } else {
166 block.0.z += step.z;
167 next.z += delta.z;
168 }
169
170 if let Some(hit) =
171 self.clip_block_and_fluid(block, start_pos, end_pos, block_shape, fluid)
172 {
173 return hit;
174 }
175 }
176
177 Self::clip_miss(start_pos, end_pos)
178 }
179
180 #[must_use]
182 pub fn clip_including_border(
183 &self,
184 start_pos: DVec3,
185 end_pos: DVec3,
186 block_shape: ClipBlockShape,
187 fluid: ClipFluid,
188 ) -> ClipHitResult {
189 let hit = self.clip(start_pos, end_pos, block_shape, fluid);
190 let border = self.world_border_snapshot();
191 if border.is_within_bounds_with_margin(start_pos.x, start_pos.z, 0.0)
192 && !border.is_within_bounds_with_margin(hit.location.x, hit.location.z, 0.0)
193 {
194 let delta = hit.location - start_pos;
195 let location = border.clamp_vec3_to_bound(hit.location);
196 return ClipHitResult {
197 location,
198 direction: Self::approximate_nearest_direction(delta),
199 block_pos: BlockPos::from(location),
200 miss: false,
201 inside: false,
202 world_border_hit: true,
203 };
204 }
205 hit
206 }
207
208 pub(super) fn clip_block_and_fluid(
209 &self,
210 pos: BlockPos,
211 from: DVec3,
212 to: DVec3,
213 block_shape: ClipBlockShape,
214 fluid: ClipFluid,
215 ) -> Option<ClipHitResult> {
216 let state = self.get_block_state(pos);
217 let block_result = Self::clip_shape(
218 pos,
219 from,
220 to,
221 self.clip_block_shape(state, pos, block_shape),
222 )
223 .map(|hit| Self::clip_with_interaction_override(pos, from, to, state, hit));
224 let fluid_result = self.clip_fluid_shape(pos, from, to, state, fluid);
225
226 match (block_result, fluid_result) {
227 (Some(block_hit), Some(fluid_hit)) => {
228 let block_distance = from.distance_squared(block_hit.location);
229 let fluid_distance = from.distance_squared(fluid_hit.location);
230 if block_distance <= fluid_distance {
231 Some(block_hit)
232 } else {
233 Some(fluid_hit)
234 }
235 }
236 (Some(hit), None) | (None, Some(hit)) => Some(hit),
237 (None, None) => None,
238 }
239 }
240
241 pub(super) fn clip_with_interaction_override(
242 pos: BlockPos,
243 from: DVec3,
244 to: DVec3,
245 state: BlockStateId,
246 block_hit: ClipHitResult,
247 ) -> ClipHitResult {
248 let Some(override_hit) =
249 Self::clip_shape(pos, from, to, state.get_interaction_shape_at(pos))
250 else {
251 return block_hit;
252 };
253
254 if from.distance_squared(override_hit.location) < from.distance_squared(block_hit.location)
255 {
256 ClipHitResult {
257 direction: override_hit.direction,
258 ..block_hit
259 }
260 } else {
261 block_hit
262 }
263 }
264
265 pub(super) fn clip_block_shape(
266 &self,
267 state: BlockStateId,
268 pos: BlockPos,
269 shape: ClipBlockShape,
270 ) -> OffsetVoxelShape {
271 match shape {
272 ClipBlockShape::Collider => state.get_collision_shape_at(pos),
273 ClipBlockShape::Outline => state.get_outline_shape_at(pos),
274 ClipBlockShape::Visual => state.get_visual_shape_at(pos),
275 ClipBlockShape::FallDamageResetting { entity_is_player } => {
276 OffsetVoxelShape::without_offset(
277 self.fall_damage_resetting_shape(state, entity_is_player),
278 )
279 }
280 }
281 }
282
283 pub(super) fn fall_damage_resetting_shape(
284 &self,
285 state: BlockStateId,
286 entity_is_player: bool,
287 ) -> VoxelShape {
288 let block = state.get_block();
289 if block.has_tag(&BlockTag::FALL_DAMAGE_RESETTING) {
290 return VoxelShape::FULL_BLOCK;
291 }
292
293 if !entity_is_player {
294 return VoxelShape::EMPTY;
295 }
296
297 if block == &vanilla_blocks::END_GATEWAY || block == &vanilla_blocks::END_PORTAL {
298 return VoxelShape::FULL_BLOCK;
299 }
300
301 if block == &vanilla_blocks::NETHER_PORTAL
302 && self.get_game_rule(&PLAYERS_NETHER_PORTAL_DEFAULT_DELAY) == 0
303 {
304 return VoxelShape::FULL_BLOCK;
305 }
306
307 VoxelShape::EMPTY
308 }
309
310 pub(super) fn clip_fluid_shape(
311 &self,
312 pos: BlockPos,
313 from: DVec3,
314 to: DVec3,
315 state: BlockStateId,
316 fluid: ClipFluid,
317 ) -> Option<ClipHitResult> {
318 let fluid_state = state.get_fluid_state();
319 let can_pick = match fluid {
320 ClipFluid::None => false,
321 ClipFluid::SourceOnly => fluid_state.is_source(),
322 ClipFluid::Any => !fluid_state.is_empty(),
323 ClipFluid::Water => fluid_state.is_water(),
324 };
325 if !can_pick {
326 return None;
327 }
328
329 let height = self.fluid_clip_height(pos, fluid_state);
330 Self::clip_local_aabb(
331 pos,
332 from,
333 to,
334 BlockLocalAabb::new(0.0, 0.0, 0.0, 1.0, height, 1.0),
335 )
336 }
337
338 pub(super) fn fluid_clip_height(&self, pos: BlockPos, fluid_state: FluidState) -> f64 {
339 let above_fluid = self.get_block_state(pos.above()).get_fluid_state();
340 Self::fluid_clip_height_from_above(fluid_state, above_fluid)
341 }
342
343 pub(super) fn fluid_clip_height_from_above(
344 fluid_state: FluidState,
345 above_fluid: FluidState,
346 ) -> f64 {
347 if FLUID_BEHAVIORS
348 .get_behavior(fluid_state.fluid_id)
349 .is_same(above_fluid.fluid_id)
350 {
351 1.0
352 } else {
353 f64::from(fluid_state.own_height())
354 }
355 }
356
357 pub(super) fn clip_shape(
358 block_pos: BlockPos,
359 from: DVec3,
360 to: DVec3,
361 shape: OffsetVoxelShape,
362 ) -> Option<ClipHitResult> {
363 if shape.is_empty() {
364 return None;
365 }
366
367 if (to - from).length_squared() < 1.0e-7 {
368 return None;
369 }
370
371 let block_vec = DVec3::new(
372 f64::from(block_pos.x()),
373 f64::from(block_pos.y()),
374 f64::from(block_pos.z()),
375 );
376 let inside_test_point = from + (to - from) * 0.001;
377 if Self::shape_contains_world_point(shape, block_vec, inside_test_point) {
378 return Some(ClipHitResult {
379 location: inside_test_point,
380 direction: Self::approximate_nearest_direction(to - from).opposite(),
381 block_pos,
382 miss: false,
383 inside: true,
384 world_border_hit: false,
385 });
386 }
387
388 let mut closest: Option<(f64, Direction)> = None;
389
390 for shape in shape.iter() {
391 let world_min = DVec3::new(shape.min_x(), shape.min_y(), shape.min_z()) + block_vec;
392 let world_max = DVec3::new(shape.max_x(), shape.max_y(), shape.max_z()) + block_vec;
393
394 if let Some(hit) = Self::intersects_aabb_with_t(from, to, world_min, world_max)
395 && hit.0 > 0.0
396 && hit.0 < 1.0
397 && closest.is_none_or(|(best_t, _)| hit.0 < best_t)
398 {
399 closest = Some(hit);
400 }
401 }
402
403 closest.map(|(t, direction)| ClipHitResult {
404 location: from + (to - from) * t,
405 direction,
406 block_pos,
407 miss: false,
408 inside: false,
409 world_border_hit: false,
410 })
411 }
412
413 pub(super) fn clip_local_aabb(
414 block_pos: BlockPos,
415 from: DVec3,
416 to: DVec3,
417 aabb: BlockLocalAabb,
418 ) -> Option<ClipHitResult> {
419 if aabb.is_empty() {
420 return None;
421 }
422
423 if (to - from).length_squared() < 1.0e-7 {
424 return None;
425 }
426
427 let block_vec = DVec3::new(
428 f64::from(block_pos.x()),
429 f64::from(block_pos.y()),
430 f64::from(block_pos.z()),
431 );
432 let inside_test_point = from + (to - from) * 0.001;
433 if Self::local_aabb_contains_world_point(aabb, block_vec, inside_test_point) {
434 return Some(ClipHitResult {
435 location: inside_test_point,
436 direction: Self::approximate_nearest_direction(to - from).opposite(),
437 block_pos,
438 miss: false,
439 inside: true,
440 world_border_hit: false,
441 });
442 }
443
444 let world_min = DVec3::new(aabb.min_x(), aabb.min_y(), aabb.min_z()) + block_vec;
445 let world_max = DVec3::new(aabb.max_x(), aabb.max_y(), aabb.max_z()) + block_vec;
446 Self::intersects_aabb_with_t(from, to, world_min, world_max).and_then(|(t, direction)| {
447 if t > 0.0 && t < 1.0 {
448 Some(ClipHitResult {
449 location: from + (to - from) * t,
450 direction,
451 block_pos,
452 miss: false,
453 inside: false,
454 world_border_hit: false,
455 })
456 } else {
457 None
458 }
459 })
460 }
461
462 pub(super) fn shape_contains_world_point(
463 shape: OffsetVoxelShape,
464 block_vec: DVec3,
465 point: DVec3,
466 ) -> bool {
467 shape
468 .iter()
469 .any(|aabb| Self::local_aabb_contains_world_point(aabb, block_vec, point))
470 }
471
472 pub(super) fn local_aabb_contains_world_point(
473 aabb: BlockLocalAabb,
474 block_vec: DVec3,
475 point: DVec3,
476 ) -> bool {
477 let local = point - block_vec;
478 !aabb.is_empty()
479 && local.x >= aabb.min_x()
480 && local.x <= aabb.max_x()
481 && local.y >= aabb.min_y()
482 && local.y <= aabb.max_y()
483 && local.z >= aabb.min_z()
484 && local.z <= aabb.max_z()
485 }
486
487 pub(super) fn clip_miss(from: DVec3, to: DVec3) -> ClipHitResult {
488 ClipHitResult {
489 location: to,
490 direction: Self::approximate_nearest_direction(from - to),
491 block_pos: BlockPos::from(to),
492 miss: true,
493 inside: false,
494 world_border_hit: false,
495 }
496 }
497
498 pub(super) fn approximate_nearest_direction(vector: DVec3) -> Direction {
499 let mut result = Direction::North;
500 let mut highest_dot = 0.0;
501 for direction in [
502 Direction::Down,
503 Direction::Up,
504 Direction::North,
505 Direction::South,
506 Direction::West,
507 Direction::East,
508 ] {
509 let dot = vector.dot(direction.offset_vec().as_dvec3());
510 if dot > highest_dot {
511 highest_dot = dot;
512 result = direction;
513 }
514 }
515 result
516 }
517
518 pub(super) fn intersects_aabb_with_t(
527 start: DVec3,
528 end: DVec3,
529 min: DVec3,
530 max: DVec3,
531 ) -> Option<(f64, Direction)> {
532 let dir = end - start;
533
534 let mut tmin = f64::NEG_INFINITY;
535 let mut tmax = f64::INFINITY;
536 let mut hit_dir = None;
537
538 macro_rules! slab {
539 ($start:expr, $dir:expr, $min:expr, $max:expr, $neg:expr, $pos:expr) => {{
540 if $dir.abs() < 1e-8 {
541 if $start < $min || $start > $max {
542 return None;
543 }
544 } else {
545 let inv = 1.0 / $dir;
546 let mut t1 = ($min - $start) * inv;
547 let mut t2 = ($max - $start) * inv;
548
549 let dir_hit = if t1 > t2 {
550 std::mem::swap(&mut t1, &mut t2);
551 $pos
552 } else {
553 $neg
554 };
555
556 if t1 > tmin {
557 tmin = t1;
558 hit_dir = Some(dir_hit);
559 }
560
561 tmax = tmax.min(t2);
562 if tmin > tmax {
563 return None;
564 }
565 }
566 }};
567 }
568
569 slab!(
570 start.x,
571 dir.x,
572 min.x,
573 max.x,
574 Direction::West,
575 Direction::East
576 );
577 slab!(start.y, dir.y, min.y, max.y, Direction::Down, Direction::Up);
578 slab!(
579 start.z,
580 dir.z,
581 min.z,
582 max.z,
583 Direction::North,
584 Direction::South
585 );
586
587 if tmax < 0.0 {
588 None
589 } else {
590 hit_dir.map(|d| (tmin, d))
591 }
592 }
593
594 pub fn raytrace<F>(
598 &self,
599 start_pos: DVec3,
600 end_pos: DVec3,
601 hit_check: F,
602 ) -> (Option<BlockPos>, Option<Direction>)
603 where
604 F: Fn(BlockPos, &Self) -> RaytraceAction,
605 {
606 if start_pos == end_pos {
607 return (None, None);
608 }
609
610 let adjust = -1.0e-7f64;
611 let to = end_pos.lerp(start_pos, adjust);
612 let from = start_pos.lerp(end_pos, adjust);
613
614 let mut block = BlockPos::new(
615 from.x.floor() as i32,
616 from.y.floor() as i32,
617 from.z.floor() as i32,
618 );
619
620 match hit_check(block, self) {
621 RaytraceAction::ImmediateHit => return (Some(block), None),
622 RaytraceAction::CheckShape => {
623 let (hit, face) = self.ray_outline_check(block, start_pos, end_pos);
624 if hit {
625 return (Some(block), face);
626 }
627 }
628 RaytraceAction::Pass => {}
629 }
630
631 let difference = to - from;
632
633 let step = difference.signum().as_ivec3();
634
635 let delta = DVec3::new(
636 if step.x == 0 {
637 f64::MAX
638 } else {
639 (f64::from(step.x)) / difference.x
640 },
641 if step.y == 0 {
642 f64::MAX
643 } else {
644 (f64::from(step.y)) / difference.y
645 },
646 if step.z == 0 {
647 f64::MAX
648 } else {
649 (f64::from(step.z)) / difference.z
650 },
651 );
652
653 let mut next = DVec3::new(
654 delta.x
655 * (if step.x > 0 {
656 1.0 - (from.x - from.x.floor())
657 } else {
658 from.x - from.x.floor()
659 }),
660 delta.y
661 * (if step.y > 0 {
662 1.0 - (from.y - from.y.floor())
663 } else {
664 from.y - from.y.floor()
665 }),
666 delta.z
667 * (if step.z > 0 {
668 1.0 - (from.z - from.z.floor())
669 } else {
670 from.z - from.z.floor()
671 }),
672 );
673
674 while next.x <= 1.0 || next.y <= 1.0 || next.z <= 1.0 {
675 let block_direction = if next.x < next.y && next.x < next.z {
680 block.0.x += step.x;
681 next.x += delta.x;
682 if step.x > 0 {
683 Direction::West
684 } else {
685 Direction::East
686 }
687 } else if next.y < next.x && next.y < next.z {
688 block.0.y += step.y;
689 next.y += delta.y;
690 if step.y > 0 {
691 Direction::Down
692 } else {
693 Direction::Up
694 }
695 } else {
696 block.0.z += step.z;
697 next.z += delta.z;
698 if step.z > 0 {
699 Direction::North
700 } else {
701 Direction::South
702 }
703 };
704
705 match hit_check(block, self) {
706 RaytraceAction::ImmediateHit => {
707 return (Some(block), Some(block_direction));
708 }
709 RaytraceAction::CheckShape => {
710 let (hit, face) = self.ray_outline_check(block, start_pos, end_pos);
711 if hit {
712 return (Some(block), face);
713 }
714 }
715 RaytraceAction::Pass => {}
716 }
717 }
718
719 (None, None)
720 }
721}