azalea_physics/collision/
shape.rs

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    // we don't check for the numbers being out of bounds because some blocks are
30    // weird and are outside 0-1
31
32    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        // if (a == b) {
132        //     return if op(true, true) { a } else { empty_shape() };
133        // }
134        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        // IndexMerger var5 = createIndexMerger(1, a.getCoords(Direction.Axis.X),
151        // b.getCoords(Direction.Axis.X), var3, var4); IndexMerger var6 =
152        // createIndexMerger(var5.size() - 1, a.getCoords(Direction.Axis.Y),
153        // b.getCoords(Direction.Axis.Y), var3, var4); IndexMerger var7 =
154        // createIndexMerger((var5.size() - 1) * (var6.size() - 1),
155        // a.getCoords(Direction.Axis.Z), b.getCoords(Direction.Axis.Z), var3, var4);
156        // BitSetDiscreteVoxelShape var8 = BitSetDiscreteVoxelShape.join(a.shape,
157        // b.shape, var5, var6, var7, op); return (VoxelShape)(var5 instanceof
158        // DiscreteCubeMerger && var6 instanceof DiscreteCubeMerger && var7 instanceof
159        // DiscreteCubeMerger ? new CubeVoxelShape(var8) : new ArrayVoxelShape(var8,
160        // var5.getList(), var6.getList(), var7.getList()));
161        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 var5.is_discrete_cube_merger()
184
185        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    /// Check if the op is true anywhere when joining the two shapes
201    /// vanilla calls this joinIsNotEmpty (join_is_not_empty).
202    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 (&var1 as &dyn Any).is::<CubePointRange>() && (&var2 as &dyn
291        // Any).is::<CubePointRange>() {
292        // return new DiscreteCubeMerger(var0, var5, var6, var3, var4);
293        // let var7: i64 = lcm(var5 as u32, var6 as u32).try_into().unwrap();
294        // //    if ((long)var0 * var7 <= 256L) {
295        // if var0 as i64 * var7 <= 256 {
296        //     return IndexMerger::new_discrete_cube(var5 as u32, var6 as u32);
297        // }
298        // }
299
300        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    // public double min(Direction.Axis var1) {
330    //     int var2 = this.shape.firstFull(var1);
331    //     return var2 >= this.shape.getSize(var1) ? 1.0D / 0.0 : this.get(var1,
332    // var2); }
333    // public double max(Direction.Axis var1) {
334    //     int var2 = this.shape.lastFull(var1);
335    //     return var2 <= 0 ? -1.0D / 0.0 : this.get(var1, var2);
336    // }
337    fn min(&self, axis: Axis) -> f64 {
338        let first_full = self.shape().first_full(axis);
339        if first_full >= self.shape().size(axis) as i32 {
340            f64::INFINITY
341        } else {
342            self.get(axis, first_full.try_into().unwrap())
343        }
344    }
345    fn max(&self, axis: Axis) -> f64 {
346        let last_full = self.shape().last_full(axis);
347        if last_full <= 0 {
348            f64::NEG_INFINITY
349        } else {
350            self.get(axis, last_full.try_into().unwrap())
351        }
352    }
353
354    pub fn shape(&self) -> &DiscreteVoxelShape {
355        match self {
356            VoxelShape::Array(s) => s.shape(),
357            VoxelShape::Cube(s) => s.shape(),
358        }
359    }
360
361    #[inline]
362    pub fn get_coords(&self, axis: Axis) -> &[f64] {
363        match self {
364            VoxelShape::Array(s) => s.get_coords(axis),
365            VoxelShape::Cube(s) => s.get_coords(axis),
366        }
367    }
368
369    pub fn is_empty(&self) -> bool {
370        self.shape().is_empty()
371    }
372
373    #[must_use]
374    pub fn move_relative(&self, delta: Vec3) -> VoxelShape {
375        if self.shape().is_empty() {
376            return EMPTY_SHAPE.clone();
377        }
378
379        VoxelShape::Array(ArrayVoxelShape::new(
380            self.shape().to_owned(),
381            self.get_coords(Axis::X)
382                .iter()
383                .map(|c| c + delta.x)
384                .collect(),
385            self.get_coords(Axis::Y)
386                .iter()
387                .map(|c| c + delta.y)
388                .collect(),
389            self.get_coords(Axis::Z)
390                .iter()
391                .map(|c| c + delta.z)
392                .collect(),
393        ))
394    }
395
396    #[inline]
397    pub fn get(&self, axis: Axis, index: usize) -> f64 {
398        // self.get_coords(axis)[index]
399        match self {
400            VoxelShape::Array(s) => s.get_coords(axis)[index],
401            VoxelShape::Cube(s) => s.get_coords(axis)[index],
402            // _ => self.get_coords(axis)[index],
403        }
404    }
405
406    pub fn find_index(&self, axis: Axis, coord: f64) -> i32 {
407        match self {
408            VoxelShape::Cube(s) => s.find_index(axis, coord),
409            _ => {
410                let upper_limit = (self.shape().size(axis) + 1) as i32;
411                binary_search(0, upper_limit, |t| coord < self.get(axis, t as usize)) - 1
412            }
413        }
414    }
415
416    pub fn clip(&self, from: Vec3, to: Vec3, block_pos: BlockPos) -> Option<BlockHitResult> {
417        if self.is_empty() {
418            return None;
419        }
420        let vector = to - from;
421        if vector.length_squared() < EPSILON {
422            return None;
423        }
424        let right_after_start = from + (vector * 0.001);
425
426        if self.shape().is_full_wide(Vec3i::new(
427            self.find_index(Axis::X, right_after_start.x - block_pos.x as f64),
428            self.find_index(Axis::Y, right_after_start.y - block_pos.y as f64),
429            self.find_index(Axis::Z, right_after_start.z - block_pos.z as f64),
430        )) {
431            Some(BlockHitResult {
432                block_pos,
433                direction: Direction::nearest(vector).opposite(),
434                location: right_after_start,
435                inside: true,
436                miss: false,
437                world_border: false,
438            })
439        } else {
440            Aabb::clip_iterable(&self.to_aabbs(), from, to, block_pos)
441        }
442    }
443
444    pub fn collide(&self, axis: Axis, entity_box: &Aabb, movement: f64) -> f64 {
445        self.collide_x(AxisCycle::between(axis, Axis::X), entity_box, movement)
446    }
447    pub fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &Aabb, mut movement: f64) -> f64 {
448        if self.shape().is_empty() {
449            return movement;
450        }
451        if movement.abs() < EPSILON {
452            return 0.;
453        }
454
455        let inverse_axis_cycle = axis_cycle.inverse();
456
457        let x_axis = inverse_axis_cycle.cycle(Axis::X);
458        let y_axis = inverse_axis_cycle.cycle(Axis::Y);
459        let z_axis = inverse_axis_cycle.cycle(Axis::Z);
460
461        let max_x = entity_box.max(&x_axis);
462        let min_x = entity_box.min(&x_axis);
463
464        let x_min_index = self.find_index(x_axis, min_x + EPSILON);
465        let x_max_index = self.find_index(x_axis, max_x - EPSILON);
466
467        let y_min_index = cmp::max(
468            0,
469            self.find_index(y_axis, entity_box.min(&y_axis) + EPSILON),
470        );
471        let y_max_index = cmp::min(
472            self.shape().size(y_axis) as i32,
473            self.find_index(y_axis, entity_box.max(&y_axis) - EPSILON) + 1,
474        );
475
476        let z_min_index = cmp::max(
477            0,
478            self.find_index(z_axis, entity_box.min(&z_axis) + EPSILON),
479        );
480        let z_max_index = cmp::min(
481            self.shape().size(z_axis) as i32,
482            self.find_index(z_axis, entity_box.max(&z_axis) - EPSILON) + 1,
483        );
484
485        if movement > 0. {
486            for x in x_max_index + 1..(self.shape().size(x_axis) as i32) {
487                for y in y_min_index..y_max_index {
488                    for z in z_min_index..z_max_index {
489                        if self
490                            .shape()
491                            .is_full_wide_axis_cycle(inverse_axis_cycle, Vec3i { x, y, z })
492                        {
493                            let var23 = self.get(x_axis, x as usize) - max_x;
494                            if var23 >= -EPSILON {
495                                movement = f64::min(movement, var23);
496                            }
497                            return movement;
498                        }
499                    }
500                }
501            }
502        } else if movement < 0. && x_min_index > 0 {
503            for x in (0..x_min_index).rev() {
504                for y in y_min_index..y_max_index {
505                    for z in z_min_index..z_max_index {
506                        if self
507                            .shape()
508                            .is_full_wide_axis_cycle(inverse_axis_cycle, Vec3i { x, y, z })
509                        {
510                            let var23 = self.get(x_axis, (x + 1) as usize) - min_x;
511                            if var23 <= EPSILON {
512                                movement = f64::max(movement, var23);
513                            }
514                            return movement;
515                        }
516                    }
517                }
518            }
519        }
520
521        movement
522    }
523
524    fn optimize(&self) -> VoxelShape {
525        let mut shape = EMPTY_SHAPE.clone();
526        self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| {
527            shape = Shapes::join_unoptimized(
528                shape.clone(),
529                box_shape(var1x, var3, var5, var7, var9, var11),
530                |a, b| a || b,
531            );
532        });
533        shape
534    }
535
536    pub fn for_all_boxes(&self, mut consumer: impl FnMut(f64, f64, f64, f64, f64, f64))
537    where
538        Self: Sized,
539    {
540        let x_coords = self.get_coords(Axis::X);
541        let y_coords = self.get_coords(Axis::Y);
542        let z_coords = self.get_coords(Axis::Z);
543        self.shape().for_all_boxes(
544            |min_x, min_y, min_z, max_x, max_y, max_z| {
545                consumer(
546                    x_coords[min_x as usize],
547                    y_coords[min_y as usize],
548                    z_coords[min_z as usize],
549                    x_coords[max_x as usize],
550                    y_coords[max_y as usize],
551                    z_coords[max_z as usize],
552                );
553            },
554            true,
555        );
556    }
557
558    pub fn to_aabbs(&self) -> Vec<Aabb> {
559        let mut aabbs = Vec::new();
560        self.for_all_boxes(|min_x, min_y, min_z, max_x, max_y, max_z| {
561            aabbs.push(Aabb {
562                min: Vec3::new(min_x, min_y, min_z),
563                max: Vec3::new(max_x, max_y, max_z),
564            });
565        });
566        aabbs
567    }
568
569    pub fn bounds(&self) -> Aabb {
570        assert!(!self.is_empty(), "Can't get bounds for empty shape");
571        Aabb {
572            min: Vec3::new(self.min(Axis::X), self.min(Axis::Y), self.min(Axis::Z)),
573            max: Vec3::new(self.max(Axis::X), self.max(Axis::Y), self.max(Axis::Z)),
574        }
575    }
576}
577
578impl From<&Aabb> for VoxelShape {
579    fn from(aabb: &Aabb) -> Self {
580        box_shape(
581            aabb.min.x, aabb.min.y, aabb.min.z, aabb.max.x, aabb.max.y, aabb.max.z,
582        )
583    }
584}
585impl From<Aabb> for VoxelShape {
586    fn from(aabb: Aabb) -> Self {
587        VoxelShape::from(&aabb)
588    }
589}
590
591#[derive(Clone, Debug, PartialEq)]
592pub struct ArrayVoxelShape {
593    shape: DiscreteVoxelShape,
594    // TODO: check where faces is used in minecraft
595    #[allow(dead_code)]
596    faces: Option<Box<[VoxelShape]>>,
597
598    xs: CompactArray<f64>,
599    ys: CompactArray<f64>,
600    zs: CompactArray<f64>,
601}
602
603#[derive(Clone, Debug, PartialEq)]
604pub struct CubeVoxelShape {
605    shape: DiscreteVoxelShape,
606    // TODO: check where faces is used in minecraft
607    #[allow(dead_code)]
608    faces: Option<Vec<VoxelShape>>,
609
610    x_coords: CompactArray<f64>,
611    y_coords: CompactArray<f64>,
612    z_coords: CompactArray<f64>,
613}
614
615impl ArrayVoxelShape {
616    pub fn new(
617        shape: DiscreteVoxelShape,
618        xs: CompactArray<f64>,
619        ys: CompactArray<f64>,
620        zs: CompactArray<f64>,
621    ) -> Self {
622        let x_size = shape.size(Axis::X) + 1;
623        let y_size = shape.size(Axis::Y) + 1;
624        let z_size = shape.size(Axis::Z) + 1;
625
626        // Lengths of point arrays must be consistent with the size of the VoxelShape.
627        debug_assert_eq!(x_size, xs.len() as u32);
628        debug_assert_eq!(y_size, ys.len() as u32);
629        debug_assert_eq!(z_size, zs.len() as u32);
630
631        Self {
632            faces: None,
633            shape,
634            xs,
635            ys,
636            zs,
637        }
638    }
639
640    fn shape(&self) -> &DiscreteVoxelShape {
641        &self.shape
642    }
643
644    #[inline]
645    fn get_coords(&self, axis: Axis) -> &[f64] {
646        axis.choose(&self.xs, &self.ys, &self.zs).as_slice()
647    }
648}
649
650impl CubeVoxelShape {
651    pub fn new(shape: DiscreteVoxelShape) -> Self {
652        let x_coords = Self::calculate_coords(&shape, Axis::X);
653        let y_coords = Self::calculate_coords(&shape, Axis::Y);
654        let z_coords = Self::calculate_coords(&shape, Axis::Z);
655
656        Self {
657            shape,
658            faces: None,
659            x_coords,
660            y_coords,
661            z_coords,
662        }
663    }
664}
665
666impl CubeVoxelShape {
667    fn shape(&self) -> &DiscreteVoxelShape {
668        &self.shape
669    }
670
671    fn calculate_coords(shape: &DiscreteVoxelShape, axis: Axis) -> CompactArray<f64> {
672        let size = shape.size(axis);
673
674        (0..=size).map(|i| i as f64 / size as f64).collect()
675    }
676
677    #[inline]
678    fn get_coords(&self, axis: Axis) -> &[f64] {
679        axis.choose(&self.x_coords, &self.y_coords, &self.z_coords)
680            .as_slice()
681    }
682
683    fn find_index(&self, axis: Axis, coord: f64) -> i32 {
684        let n = self.shape().size(axis);
685        f64::floor(f64::clamp(coord * (n as f64), -1f64, n as f64)) as i32
686    }
687}
688
689#[derive(Debug)]
690pub struct CubePointRange {
691    /// Needs at least 1 part
692    pub parts: NonZeroU32,
693}
694impl CubePointRange {
695    pub fn get_double(&self, index: u32) -> f64 {
696        index as f64 / self.parts.get() as f64
697    }
698
699    pub fn size(&self) -> u32 {
700        self.parts.get() + 1
701    }
702
703    pub fn iter(&self) -> Box<[f64]> {
704        (0..=self.parts.get()).map(|i| self.get_double(i)).collect()
705    }
706}
707
708#[derive(Clone, Debug, PartialEq)]
709pub enum CompactArray<T: Clone + Copy> {
710    Box(Box<[T]>),
711    Inline([T; 1]),
712}
713impl<T: Clone + Copy> CompactArray<T> {
714    fn as_slice(&self) -> &[T] {
715        match self {
716            CompactArray::Box(slice) => slice,
717            CompactArray::Inline(arr) => arr,
718        }
719    }
720    fn len(&self) -> usize {
721        match self {
722            CompactArray::Box(slice) => slice.len(),
723            CompactArray::Inline(arr) => arr.len(),
724        }
725    }
726}
727impl<T: Clone + Copy> From<Box<[T]>> for CompactArray<T> {
728    fn from(value: Box<[T]>) -> Self {
729        if value.len() == 1 {
730            Self::Inline([value[0]])
731        } else {
732            Self::Box(value)
733        }
734    }
735}
736impl<T: Clone + Copy, const N: usize> From<[T; N]> for CompactArray<T> {
737    fn from(value: [T; N]) -> Self {
738        if value.len() == 1 {
739            Self::Inline([value[0]])
740        } else {
741            Self::Box(Box::new(value))
742        }
743    }
744}
745impl<T: Clone + Copy> FromIterator<T> for CompactArray<T> {
746    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
747        Self::from(Box::from_iter(iter))
748    }
749}
750
751#[cfg(test)]
752mod tests {
753    use super::*;
754
755    #[test]
756    fn test_block_shape() {
757        let shape = &*BLOCK_SHAPE;
758        assert_eq!(shape.shape().size(Axis::X), 1);
759        assert_eq!(shape.shape().size(Axis::Y), 1);
760        assert_eq!(shape.shape().size(Axis::Z), 1);
761
762        assert_eq!(shape.get_coords(Axis::X).len(), 2);
763        assert_eq!(shape.get_coords(Axis::Y).len(), 2);
764        assert_eq!(shape.get_coords(Axis::Z).len(), 2);
765    }
766
767    #[test]
768    fn test_box_shape() {
769        let shape = box_shape(0., 0., 0., 1., 1., 1.);
770        assert_eq!(shape.shape().size(Axis::X), 1);
771        assert_eq!(shape.shape().size(Axis::Y), 1);
772        assert_eq!(shape.shape().size(Axis::Z), 1);
773
774        assert_eq!(shape.get_coords(Axis::X).len(), 2);
775        assert_eq!(shape.get_coords(Axis::Y).len(), 2);
776        assert_eq!(shape.get_coords(Axis::Z).len(), 2);
777    }
778
779    #[test]
780    fn test_top_slab_shape() {
781        let shape = box_shape(0., 0.5, 0., 1., 1., 1.);
782        assert_eq!(shape.shape().size(Axis::X), 1);
783        assert_eq!(shape.shape().size(Axis::Y), 2);
784        assert_eq!(shape.shape().size(Axis::Z), 1);
785
786        assert_eq!(shape.get_coords(Axis::X).len(), 2);
787        assert_eq!(shape.get_coords(Axis::Y).len(), 3);
788        assert_eq!(shape.get_coords(Axis::Z).len(), 2);
789    }
790
791    #[test]
792    fn test_join_is_not_empty() {
793        let shape = box_shape(0., 0., 0., 1., 1., 1.);
794        let shape2 = box_shape(0., 0.5, 0., 1., 1., 1.);
795        // detect if the shapes intersect at all
796        let joined = Shapes::matches_anywhere(&shape, &shape2, |a, b| a && b);
797        assert!(joined, "Shapes should intersect");
798    }
799
800    #[test]
801    fn clip_in_front_of_block() {
802        let block_shape = &*BLOCK_SHAPE;
803        let block_hit_result = block_shape
804            .clip(
805                Vec3::new(-0.3, 0.5, 0.),
806                Vec3::new(5.3, 0.5, 0.),
807                BlockPos::new(0, 0, 0),
808            )
809            .unwrap();
810
811        assert_eq!(
812            block_hit_result,
813            BlockHitResult {
814                location: Vec3 {
815                    x: 0.0,
816                    y: 0.5,
817                    z: 0.0
818                },
819                direction: Direction::West,
820                block_pos: BlockPos { x: 0, y: 0, z: 0 },
821                inside: false,
822                world_border: false,
823                miss: false
824            }
825        );
826    }
827}