1use std::{cmp, num::NonZeroU32, sync::LazyLock};
2
3use azalea_core::{
4 direction::{Axis, AxisCycle, Direction},
5 hit_result::BlockHitResult,
6 math::{EPSILON, binary_search},
7 position::{BlockPos, Vec3, Vec3i},
8};
9
10use super::mergers::IndexMerger;
11use crate::collision::{Aabb, BitSetDiscreteVoxelShape, DiscreteVoxelShape};
12
13pub struct Shapes;
14
15pub static BLOCK_SHAPE: LazyLock<VoxelShape> = LazyLock::new(|| {
16 let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1);
17 shape.fill(0, 0, 0);
18 VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(shape)))
19});
20
21pub fn box_shape(
22 min_x: f64,
23 min_y: f64,
24 min_z: f64,
25 max_x: f64,
26 max_y: f64,
27 max_z: f64,
28) -> VoxelShape {
29 if max_x - min_x < EPSILON && max_y - min_y < EPSILON && max_z - min_z < EPSILON {
33 return EMPTY_SHAPE.clone();
34 }
35
36 let x_bits = find_bits(min_x, max_x);
37 let y_bits = find_bits(min_y, max_y);
38 let z_bits = find_bits(min_z, max_z);
39
40 if x_bits < 0 || y_bits < 0 || z_bits < 0 {
41 return VoxelShape::Array(ArrayVoxelShape::new(
42 BLOCK_SHAPE.shape().to_owned(),
43 [min_x, max_x].into(),
44 [min_y, max_y].into(),
45 [min_z, max_z].into(),
46 ));
47 }
48 if x_bits == 0 && y_bits == 0 && z_bits == 0 {
49 return BLOCK_SHAPE.clone();
50 }
51
52 let x_bits = 1 << x_bits;
53 let y_bits = 1 << y_bits;
54 let z_bits = 1 << z_bits;
55 let shape = BitSetDiscreteVoxelShape::with_filled_bounds(
56 x_bits,
57 y_bits,
58 z_bits,
59 Vec3i {
60 x: (min_x * x_bits as f64).round() as i32,
61 y: (min_y * y_bits as f64).round() as i32,
62 z: (min_z * z_bits as f64).round() as i32,
63 },
64 Vec3i {
65 x: (max_x * x_bits as f64).round() as i32,
66 y: (max_y * y_bits as f64).round() as i32,
67 z: (max_z * z_bits as f64).round() as i32,
68 },
69 );
70 VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(shape)))
71}
72
73pub static EMPTY_SHAPE: LazyLock<VoxelShape> = LazyLock::new(|| {
74 VoxelShape::Array(ArrayVoxelShape::new(
75 DiscreteVoxelShape::BitSet(BitSetDiscreteVoxelShape::new(0, 0, 0)),
76 [0.].into(),
77 [0.].into(),
78 [0.].into(),
79 ))
80});
81
82fn find_bits(min: f64, max: f64) -> i32 {
83 if min < -EPSILON || max > 1. + EPSILON {
84 return -1;
85 }
86 for bits in 0..=3 {
87 let shifted_bits = 1 << bits;
88 let min = min * shifted_bits as f64;
89 let max = max * shifted_bits as f64;
90 let min_ok = (min - min.round()).abs() < EPSILON * shifted_bits as f64;
91 let max_ok = (max - max.round()).abs() < EPSILON * shifted_bits as f64;
92 if min_ok && max_ok {
93 return bits;
94 }
95 }
96 -1
97}
98
99impl Shapes {
100 pub fn or(a: VoxelShape, b: VoxelShape) -> VoxelShape {
101 Self::join(a, b, |a, b| a || b)
102 }
103
104 pub fn collide(
105 axis: Axis,
106 entity_box: &Aabb,
107 collision_boxes: &[VoxelShape],
108 mut movement: f64,
109 ) -> f64 {
110 for shape in collision_boxes {
111 if movement.abs() < EPSILON {
112 return 0.;
113 }
114 movement = shape.collide(axis, entity_box, movement);
115 }
116 movement
117 }
118
119 pub fn join(a: VoxelShape, b: VoxelShape, op: fn(bool, bool) -> bool) -> VoxelShape {
120 Self::join_unoptimized(a, b, op).optimize()
121 }
122
123 pub fn join_unoptimized(
124 a: VoxelShape,
125 b: VoxelShape,
126 op: fn(bool, bool) -> bool,
127 ) -> VoxelShape {
128 if op(false, false) {
129 panic!("Illegal operation");
130 };
131 let op_true_false = op(true, false);
135 let op_false_true = op(false, true);
136 if a.is_empty() {
137 return if op_false_true {
138 b
139 } else {
140 EMPTY_SHAPE.clone()
141 };
142 }
143 if b.is_empty() {
144 return if op_true_false {
145 a
146 } else {
147 EMPTY_SHAPE.clone()
148 };
149 }
150 let var5 = Self::create_index_merger(
162 1,
163 a.get_coords(Axis::X),
164 b.get_coords(Axis::X),
165 op_true_false,
166 op_false_true,
167 );
168 let var6 = Self::create_index_merger(
169 (var5.size() - 1).try_into().unwrap(),
170 a.get_coords(Axis::Y),
171 b.get_coords(Axis::Y),
172 op_true_false,
173 op_false_true,
174 );
175 let var7 = Self::create_index_merger(
176 ((var5.size() - 1) * (var6.size() - 1)).try_into().unwrap(),
177 a.get_coords(Axis::Z),
178 b.get_coords(Axis::Z),
179 op_true_false,
180 op_false_true,
181 );
182 let var8 = BitSetDiscreteVoxelShape::join(a.shape(), b.shape(), &var5, &var6, &var7, op);
183 if matches!(var5, IndexMerger::DiscreteCube { .. })
186 && matches!(var6, IndexMerger::DiscreteCube { .. })
187 && matches!(var7, IndexMerger::DiscreteCube { .. })
188 {
189 VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(var8)))
190 } else {
191 VoxelShape::Array(ArrayVoxelShape::new(
192 DiscreteVoxelShape::BitSet(var8),
193 var5.get_list().into(),
194 var6.get_list().into(),
195 var7.get_list().into(),
196 ))
197 }
198 }
199
200 pub fn matches_anywhere(
203 a: &VoxelShape,
204 b: &VoxelShape,
205 op: impl Fn(bool, bool) -> bool,
206 ) -> bool {
207 debug_assert!(!op(false, false));
208 let a_is_empty = a.is_empty();
209 let b_is_empty = b.is_empty();
210 if a_is_empty || b_is_empty {
211 return op(!a_is_empty, !b_is_empty);
212 }
213 if a == b {
214 return op(true, true);
215 }
216
217 let op_true_false = op(true, false);
218 let op_false_true = op(false, true);
219
220 for axis in [Axis::X, Axis::Y, Axis::Z] {
221 if a.max(axis) < b.min(axis) - EPSILON {
222 return op_true_false || op_false_true;
223 }
224 if b.max(axis) < a.min(axis) - EPSILON {
225 return op_true_false || op_false_true;
226 }
227 }
228
229 let x_merger = Self::create_index_merger(
230 1,
231 a.get_coords(Axis::X),
232 b.get_coords(Axis::X),
233 op_true_false,
234 op_false_true,
235 );
236 let y_merger = Self::create_index_merger(
237 (x_merger.size() - 1) as i32,
238 a.get_coords(Axis::Y),
239 b.get_coords(Axis::Y),
240 op_true_false,
241 op_false_true,
242 );
243 let z_merger = Self::create_index_merger(
244 ((x_merger.size() - 1) * (y_merger.size() - 1)) as i32,
245 a.get_coords(Axis::Z),
246 b.get_coords(Axis::Z),
247 op_true_false,
248 op_false_true,
249 );
250
251 Self::matches_anywhere_with_mergers(
252 x_merger,
253 y_merger,
254 z_merger,
255 a.shape().to_owned(),
256 b.shape().to_owned(),
257 op,
258 )
259 }
260
261 pub fn matches_anywhere_with_mergers(
262 merged_x: IndexMerger,
263 merged_y: IndexMerger,
264 merged_z: IndexMerger,
265 shape1: DiscreteVoxelShape,
266 shape2: DiscreteVoxelShape,
267 op: impl Fn(bool, bool) -> bool,
268 ) -> bool {
269 !merged_x.for_merged_indexes(|x, x2, _x3| {
270 merged_y.for_merged_indexes(|y, y2, _y3| {
271 merged_z.for_merged_indexes(|z, z2, _z3| {
272 !op(
273 shape1.is_full_wide(Vec3i::new(x, y, z)),
274 shape2.is_full_wide(Vec3i::new(x2, y2, z2)),
275 )
276 })
277 })
278 })
279 }
280
281 pub fn create_index_merger(
282 _var0: i32,
283 coords1: &[f64],
284 coords2: &[f64],
285 var3: bool,
286 var4: bool,
287 ) -> IndexMerger {
288 let var5 = coords1.len() - 1;
289 let var6 = coords2.len() - 1;
290 if coords1[var5] < coords2[0] - EPSILON {
301 IndexMerger::NonOverlapping {
302 lower: coords1.into(),
303 upper: coords2.into(),
304 swap: false,
305 }
306 } else if coords2[var6] < coords1[0] - EPSILON {
307 IndexMerger::NonOverlapping {
308 lower: coords2.into(),
309 upper: coords1.into(),
310 swap: true,
311 }
312 } else if var5 == var6 && coords1 == coords2 {
313 IndexMerger::Identical {
314 coords: coords1.into(),
315 }
316 } else {
317 IndexMerger::new_indirect(coords1, coords2, var3, var4)
318 }
319 }
320}
321
322#[derive(Clone, Debug, PartialEq)]
323pub enum VoxelShape {
324 Array(ArrayVoxelShape),
325 Cube(CubeVoxelShape),
326}
327
328impl VoxelShape {
329 fn min(&self, axis: Axis) -> f64 {
330 let first_full = self.shape().first_full(axis);
331 if first_full >= self.shape().size(axis) as i32 {
332 f64::INFINITY
333 } else {
334 self.get(axis, first_full.try_into().unwrap())
335 }
336 }
337 fn max(&self, axis: Axis) -> f64 {
338 let last_full = self.shape().last_full(axis);
339 if last_full <= 0 {
340 f64::NEG_INFINITY
341 } else {
342 self.get(axis, last_full.try_into().unwrap())
343 }
344 }
345
346 pub fn shape(&self) -> &DiscreteVoxelShape {
347 match self {
348 VoxelShape::Array(s) => s.shape(),
349 VoxelShape::Cube(s) => s.shape(),
350 }
351 }
352
353 #[inline]
354 pub fn get_coords(&self, axis: Axis) -> &[f64] {
355 match self {
356 VoxelShape::Array(s) => s.get_coords(axis),
357 VoxelShape::Cube(s) => s.get_coords(axis),
358 }
359 }
360
361 pub fn is_empty(&self) -> bool {
362 self.shape().is_empty()
363 }
364
365 #[must_use]
366 pub fn move_relative(&self, delta: Vec3) -> VoxelShape {
367 if self.shape().is_empty() {
368 return EMPTY_SHAPE.clone();
369 }
370
371 VoxelShape::Array(ArrayVoxelShape::new(
372 self.shape().to_owned(),
373 self.get_coords(Axis::X)
374 .iter()
375 .map(|c| c + delta.x)
376 .collect(),
377 self.get_coords(Axis::Y)
378 .iter()
379 .map(|c| c + delta.y)
380 .collect(),
381 self.get_coords(Axis::Z)
382 .iter()
383 .map(|c| c + delta.z)
384 .collect(),
385 ))
386 }
387
388 #[inline]
389 pub fn get(&self, axis: Axis, index: usize) -> f64 {
390 match self {
392 VoxelShape::Array(s) => s.get_coords(axis)[index],
393 VoxelShape::Cube(s) => s.get_coords(axis)[index],
394 }
396 }
397
398 pub fn find_index(&self, axis: Axis, coord: f64) -> i32 {
399 match self {
400 VoxelShape::Cube(s) => s.find_index(axis, coord),
401 _ => {
402 let upper_limit = (self.shape().size(axis) + 1) as i32;
403 binary_search(0, upper_limit, |t| coord < self.get(axis, t as usize)) - 1
404 }
405 }
406 }
407
408 pub fn clip(&self, from: Vec3, to: Vec3, block_pos: BlockPos) -> Option<BlockHitResult> {
409 if self.is_empty() {
410 return None;
411 }
412 let vector = to - from;
413 if vector.length_squared() < EPSILON {
414 return None;
415 }
416 let right_after_start = from + (vector * 0.001);
417
418 if self.shape().is_full_wide(Vec3i::new(
419 self.find_index(Axis::X, right_after_start.x - block_pos.x as f64),
420 self.find_index(Axis::Y, right_after_start.y - block_pos.y as f64),
421 self.find_index(Axis::Z, right_after_start.z - block_pos.z as f64),
422 )) {
423 Some(BlockHitResult {
424 block_pos,
425 direction: Direction::nearest(vector).opposite(),
426 location: right_after_start,
427 inside: true,
428 miss: false,
429 world_border: false,
430 })
431 } else {
432 Aabb::clip_iterable(&self.to_aabbs(), from, to, block_pos)
433 }
434 }
435
436 pub fn collide(&self, axis: Axis, entity_box: &Aabb, movement: f64) -> f64 {
437 self.collide_x(AxisCycle::between(axis, Axis::X), entity_box, movement)
438 }
439 pub fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &Aabb, mut movement: f64) -> f64 {
440 if self.shape().is_empty() {
441 return movement;
442 }
443 if movement.abs() < EPSILON {
444 return 0.;
445 }
446
447 let inverse_axis_cycle = axis_cycle.inverse();
448
449 let x_axis = inverse_axis_cycle.cycle(Axis::X);
450 let y_axis = inverse_axis_cycle.cycle(Axis::Y);
451 let z_axis = inverse_axis_cycle.cycle(Axis::Z);
452
453 let max_x = entity_box.max(&x_axis);
454 let min_x = entity_box.min(&x_axis);
455
456 let x_min_index = self.find_index(x_axis, min_x + EPSILON);
457 let x_max_index = self.find_index(x_axis, max_x - EPSILON);
458
459 let y_min_index = cmp::max(
460 0,
461 self.find_index(y_axis, entity_box.min(&y_axis) + EPSILON),
462 );
463 let y_max_index = cmp::min(
464 self.shape().size(y_axis) as i32,
465 self.find_index(y_axis, entity_box.max(&y_axis) - EPSILON) + 1,
466 );
467
468 let z_min_index = cmp::max(
469 0,
470 self.find_index(z_axis, entity_box.min(&z_axis) + EPSILON),
471 );
472 let z_max_index = cmp::min(
473 self.shape().size(z_axis) as i32,
474 self.find_index(z_axis, entity_box.max(&z_axis) - EPSILON) + 1,
475 );
476
477 if movement > 0. {
478 for x in x_max_index + 1..(self.shape().size(x_axis) as i32) {
479 for y in y_min_index..y_max_index {
480 for z in z_min_index..z_max_index {
481 if self
482 .shape()
483 .is_full_wide_axis_cycle(inverse_axis_cycle, Vec3i { x, y, z })
484 {
485 let var23 = self.get(x_axis, x as usize) - max_x;
486 if var23 >= -EPSILON {
487 movement = f64::min(movement, var23);
488 }
489 return movement;
490 }
491 }
492 }
493 }
494 } else if movement < 0. && x_min_index > 0 {
495 for x in (0..x_min_index).rev() {
496 for y in y_min_index..y_max_index {
497 for z in z_min_index..z_max_index {
498 if self
499 .shape()
500 .is_full_wide_axis_cycle(inverse_axis_cycle, Vec3i { x, y, z })
501 {
502 let var23 = self.get(x_axis, (x + 1) as usize) - min_x;
503 if var23 <= EPSILON {
504 movement = f64::max(movement, var23);
505 }
506 return movement;
507 }
508 }
509 }
510 }
511 }
512
513 movement
514 }
515
516 fn optimize(&self) -> VoxelShape {
517 let mut shape = EMPTY_SHAPE.clone();
518 self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| {
519 shape = Shapes::join_unoptimized(
520 shape.clone(),
521 box_shape(var1x, var3, var5, var7, var9, var11),
522 |a, b| a || b,
523 );
524 });
525 shape
526 }
527
528 pub fn for_all_boxes(&self, mut consumer: impl FnMut(f64, f64, f64, f64, f64, f64))
529 where
530 Self: Sized,
531 {
532 let x_coords = self.get_coords(Axis::X);
533 let y_coords = self.get_coords(Axis::Y);
534 let z_coords = self.get_coords(Axis::Z);
535 self.shape().for_all_boxes(
536 |min_x, min_y, min_z, max_x, max_y, max_z| {
537 consumer(
538 x_coords[min_x as usize],
539 y_coords[min_y as usize],
540 z_coords[min_z as usize],
541 x_coords[max_x as usize],
542 y_coords[max_y as usize],
543 z_coords[max_z as usize],
544 );
545 },
546 true,
547 );
548 }
549
550 pub fn to_aabbs(&self) -> Vec<Aabb> {
551 let mut aabbs = Vec::new();
552 self.for_all_boxes(|min_x, min_y, min_z, max_x, max_y, max_z| {
553 aabbs.push(Aabb {
554 min: Vec3::new(min_x, min_y, min_z),
555 max: Vec3::new(max_x, max_y, max_z),
556 });
557 });
558 aabbs
559 }
560
561 pub fn bounds(&self) -> Aabb {
562 assert!(!self.is_empty(), "Can't get bounds for empty shape");
563 Aabb {
564 min: Vec3::new(self.min(Axis::X), self.min(Axis::Y), self.min(Axis::Z)),
565 max: Vec3::new(self.max(Axis::X), self.max(Axis::Y), self.max(Axis::Z)),
566 }
567 }
568}
569
570impl From<&Aabb> for VoxelShape {
571 fn from(aabb: &Aabb) -> Self {
572 box_shape(
573 aabb.min.x, aabb.min.y, aabb.min.z, aabb.max.x, aabb.max.y, aabb.max.z,
574 )
575 }
576}
577impl From<Aabb> for VoxelShape {
578 fn from(aabb: Aabb) -> Self {
579 VoxelShape::from(&aabb)
580 }
581}
582
583#[derive(Clone, Debug, PartialEq)]
584pub struct ArrayVoxelShape {
585 shape: DiscreteVoxelShape,
586 #[allow(dead_code)]
588 faces: Option<Box<[VoxelShape]>>,
589
590 xs: CompactArray<f64>,
591 ys: CompactArray<f64>,
592 zs: CompactArray<f64>,
593}
594
595#[derive(Clone, Debug, PartialEq)]
596pub struct CubeVoxelShape {
597 shape: DiscreteVoxelShape,
598 #[allow(dead_code)]
600 faces: Option<Vec<VoxelShape>>,
601
602 x_coords: CompactArray<f64>,
603 y_coords: CompactArray<f64>,
604 z_coords: CompactArray<f64>,
605}
606
607impl ArrayVoxelShape {
608 pub fn new(
609 shape: DiscreteVoxelShape,
610 xs: CompactArray<f64>,
611 ys: CompactArray<f64>,
612 zs: CompactArray<f64>,
613 ) -> Self {
614 let x_size = shape.size(Axis::X) + 1;
615 let y_size = shape.size(Axis::Y) + 1;
616 let z_size = shape.size(Axis::Z) + 1;
617
618 debug_assert_eq!(x_size, xs.len() as u32);
620 debug_assert_eq!(y_size, ys.len() as u32);
621 debug_assert_eq!(z_size, zs.len() as u32);
622
623 Self {
624 faces: None,
625 shape,
626 xs,
627 ys,
628 zs,
629 }
630 }
631
632 fn shape(&self) -> &DiscreteVoxelShape {
633 &self.shape
634 }
635
636 #[inline]
637 fn get_coords(&self, axis: Axis) -> &[f64] {
638 axis.choose(&self.xs, &self.ys, &self.zs).as_slice()
639 }
640}
641
642impl CubeVoxelShape {
643 pub fn new(shape: DiscreteVoxelShape) -> Self {
644 let x_coords = Self::calculate_coords(&shape, Axis::X);
645 let y_coords = Self::calculate_coords(&shape, Axis::Y);
646 let z_coords = Self::calculate_coords(&shape, Axis::Z);
647
648 Self {
649 shape,
650 faces: None,
651 x_coords,
652 y_coords,
653 z_coords,
654 }
655 }
656}
657
658impl CubeVoxelShape {
659 fn shape(&self) -> &DiscreteVoxelShape {
660 &self.shape
661 }
662
663 fn calculate_coords(shape: &DiscreteVoxelShape, axis: Axis) -> CompactArray<f64> {
664 let size = shape.size(axis);
665
666 (0..=size).map(|i| i as f64 / size as f64).collect()
667 }
668
669 #[inline]
670 fn get_coords(&self, axis: Axis) -> &[f64] {
671 axis.choose(&self.x_coords, &self.y_coords, &self.z_coords)
672 .as_slice()
673 }
674
675 fn find_index(&self, axis: Axis, coord: f64) -> i32 {
676 let n = self.shape().size(axis);
677 f64::floor(f64::clamp(coord * (n as f64), -1f64, n as f64)) as i32
678 }
679}
680
681#[derive(Debug)]
682pub struct CubePointRange {
683 pub parts: NonZeroU32,
685}
686impl CubePointRange {
687 pub fn get_double(&self, index: u32) -> f64 {
688 index as f64 / self.parts.get() as f64
689 }
690
691 pub fn size(&self) -> u32 {
692 self.parts.get() + 1
693 }
694
695 pub fn iter(&self) -> Box<[f64]> {
696 (0..=self.parts.get()).map(|i| self.get_double(i)).collect()
697 }
698}
699
700#[derive(Clone, Debug, PartialEq)]
701pub enum CompactArray<T: Clone + Copy> {
702 Box(Box<[T]>),
703 Inline([T; 1]),
704}
705impl<T: Clone + Copy> CompactArray<T> {
706 fn as_slice(&self) -> &[T] {
707 match self {
708 CompactArray::Box(slice) => slice,
709 CompactArray::Inline(arr) => arr,
710 }
711 }
712 fn len(&self) -> usize {
713 match self {
714 CompactArray::Box(slice) => slice.len(),
715 CompactArray::Inline(arr) => arr.len(),
716 }
717 }
718}
719impl<T: Clone + Copy> From<Box<[T]>> for CompactArray<T> {
720 fn from(value: Box<[T]>) -> Self {
721 if value.len() == 1 {
722 Self::Inline([value[0]])
723 } else {
724 Self::Box(value)
725 }
726 }
727}
728impl<T: Clone + Copy, const N: usize> From<[T; N]> for CompactArray<T> {
729 fn from(value: [T; N]) -> Self {
730 if value.len() == 1 {
731 Self::Inline([value[0]])
732 } else {
733 Self::Box(Box::new(value))
734 }
735 }
736}
737impl<T: Clone + Copy> FromIterator<T> for CompactArray<T> {
738 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
739 Self::from(Box::from_iter(iter))
740 }
741}
742
743#[cfg(test)]
744mod tests {
745 use super::*;
746
747 #[test]
748 fn test_block_shape() {
749 let shape = &*BLOCK_SHAPE;
750 assert_eq!(shape.shape().size(Axis::X), 1);
751 assert_eq!(shape.shape().size(Axis::Y), 1);
752 assert_eq!(shape.shape().size(Axis::Z), 1);
753
754 assert_eq!(shape.get_coords(Axis::X).len(), 2);
755 assert_eq!(shape.get_coords(Axis::Y).len(), 2);
756 assert_eq!(shape.get_coords(Axis::Z).len(), 2);
757 }
758
759 #[test]
760 fn test_box_shape() {
761 let shape = box_shape(0., 0., 0., 1., 1., 1.);
762 assert_eq!(shape.shape().size(Axis::X), 1);
763 assert_eq!(shape.shape().size(Axis::Y), 1);
764 assert_eq!(shape.shape().size(Axis::Z), 1);
765
766 assert_eq!(shape.get_coords(Axis::X).len(), 2);
767 assert_eq!(shape.get_coords(Axis::Y).len(), 2);
768 assert_eq!(shape.get_coords(Axis::Z).len(), 2);
769 }
770
771 #[test]
772 fn test_top_slab_shape() {
773 let shape = box_shape(0., 0.5, 0., 1., 1., 1.);
774 assert_eq!(shape.shape().size(Axis::X), 1);
775 assert_eq!(shape.shape().size(Axis::Y), 2);
776 assert_eq!(shape.shape().size(Axis::Z), 1);
777
778 assert_eq!(shape.get_coords(Axis::X).len(), 2);
779 assert_eq!(shape.get_coords(Axis::Y).len(), 3);
780 assert_eq!(shape.get_coords(Axis::Z).len(), 2);
781 }
782
783 #[test]
784 fn test_join_is_not_empty() {
785 let shape = box_shape(0., 0., 0., 1., 1., 1.);
786 let shape2 = box_shape(0., 0.5, 0., 1., 1., 1.);
787 let joined = Shapes::matches_anywhere(&shape, &shape2, |a, b| a && b);
789 assert!(joined, "Shapes should intersect");
790 }
791
792 #[test]
793 fn clip_in_front_of_block() {
794 let block_shape = &*BLOCK_SHAPE;
795 let block_hit_result = block_shape
796 .clip(
797 Vec3::new(-0.3, 0.5, 0.),
798 Vec3::new(5.3, 0.5, 0.),
799 BlockPos::new(0, 0, 0),
800 )
801 .unwrap();
802
803 assert_eq!(
804 block_hit_result,
805 BlockHitResult {
806 location: Vec3 {
807 x: 0.0,
808 y: 0.5,
809 z: 0.0
810 },
811 direction: Direction::West,
812 block_pos: BlockPos { x: 0, y: 0, z: 0 },
813 inside: false,
814 world_border: false,
815 miss: false
816 }
817 );
818 }
819}