diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2022-10-02 12:29:47 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-02 12:29:47 -0500 |
| commit | c9b4dccd7eaeed68ce96cf5167916417d0baa6a7 (patch) | |
| tree | 0b381ee72a1486ccb22fe22158b5d7d3edaf3f99 /azalea-physics/src/collision/shape.rs | |
| parent | aa78491ee09ec0c6879e6edde349ca67cf809daf (diff) | |
| download | azalea-drasl-c9b4dccd7eaeed68ce96cf5167916417d0baa6a7.tar.xz | |
All block shapes & collisions (#22)
* start adding shapes
* add more collision stuff
* DiscreteCubeMerger
* more mergers
* start adding BitSetDiscreteVoxelShape::join
* i love rust :smiley: :smiley: :smiley:
* r
* IT COMPILES????
* fix warning
* fix error
* fix more clippy issues
* add box_shape
* more shape stuff
* make DiscreteVoxelShape an enum
* Update shape.rs
* also make VoxelShape an enum
* implement BitSet::clear
* add more missing things
* it compiles
W
* start block shape codegen
* optimize shape codegen
* make az-block/blocks.rs look better (broken)
* almost new block macro
* make the codegen not generate 'type'
* try to fix
* work more on the blocks macro
* wait it compiles
* fix clippy issues
* shapes codegen works
* well it's almost working
* simplify some shape codegen
* enum type names are correct
* W it compiles
* cargo check no longer warns
* fix some clippy issues
* start making it so the shape impl is on BlockStates
* insane code
* new impl compiles
* fix wrong find_bits + TESTS PASS!
* add a test for slab collision
* fix clippy issues
* ok rust
* fix error that happens when on stairs
* add test for top slabs
* start adding join_is_not_empty
* add more to join_is_not_empty
* top slabs still don't work!!
* x..=0 doesn't work in rust :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley:
* remove comment since i added more useful names
* remove some printlns
* fix walls in some configurations erroring
* fix some warnings
* change comment to \`\`\`ignore instead of \`\`\`no_run
* players are .6 wide not .8
* fix clippy's complaints
* i missed one clippy warning
Diffstat (limited to 'azalea-physics/src/collision/shape.rs')
| -rw-r--r-- | azalea-physics/src/collision/shape.rs | 585 |
1 files changed, 512 insertions, 73 deletions
diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs index cfd05d2e..0157724f 100644 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -1,28 +1,114 @@ +use super::mergers::IndexMerger; use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB}; use azalea_core::{binary_search, Axis, AxisCycle, EPSILON}; -use std::cmp; +use std::{cmp, num::NonZeroU32}; pub struct Shapes {} -pub fn block_shape() -> Box<dyn VoxelShape> { +pub fn block_shape() -> VoxelShape { let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1); shape.fill(0, 0, 0); - Box::new(CubeVoxelShape::new(Box::new(shape))) + VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(shape))) } -pub fn empty_shape() -> Box<dyn VoxelShape> { - Box::new(ArrayVoxelShape::new( - Box::new(BitSetDiscreteVoxelShape::new(0, 0, 0)), + +pub fn box_shape( + min_x: f64, + min_y: f64, + min_z: f64, + max_x: f64, + max_y: f64, + max_z: f64, +) -> VoxelShape { + assert!(min_x >= 0., "min_x must be >= 0 but was {}", min_x); + assert!(min_y >= 0.); + assert!(min_z >= 0.); + assert!(max_x >= 0.); + assert!(max_y >= 0.); + assert!(max_z >= 0.); + + box_shape_unchecked(min_x, min_y, min_z, max_x, max_y, max_z) +} + +pub fn box_shape_unchecked( + min_x: f64, + min_y: f64, + min_z: f64, + max_x: f64, + max_y: f64, + max_z: f64, +) -> VoxelShape { + if max_x - min_x < EPSILON && max_y - min_y < EPSILON && max_z - min_z < EPSILON { + return empty_shape(); + } + + let x_bits = find_bits(min_x, max_x); + let y_bits = find_bits(min_y, max_y); + let z_bits = find_bits(min_z, max_z); + + if x_bits < 0 || y_bits < 0 || z_bits < 0 { + return VoxelShape::Array(ArrayVoxelShape::new( + block_shape().shape(), + vec![min_x, max_x], + vec![min_y, max_y], + vec![min_z, max_z], + )); + } + if x_bits == 0 && y_bits == 0 && z_bits == 0 { + return block_shape(); + } + + let x_bits = 1 << x_bits; + let y_bits = 1 << y_bits; + let z_bits = 1 << z_bits; + let shape = BitSetDiscreteVoxelShape::with_filled_bounds( + x_bits, + y_bits, + z_bits, + (min_x * x_bits as f64).round() as i32, + (min_y * y_bits as f64).round() as i32, + (min_z * z_bits as f64).round() as i32, + (max_x * x_bits as f64).round() as i32, + (max_y * y_bits as f64).round() as i32, + (max_z * z_bits as f64).round() as i32, + ); + VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(shape))) +} + +pub fn empty_shape() -> VoxelShape { + VoxelShape::Array(ArrayVoxelShape::new( + DiscreteVoxelShape::BitSet(BitSetDiscreteVoxelShape::new(0, 0, 0)), vec![0.], vec![0.], vec![0.], )) } +fn find_bits(min: f64, max: f64) -> i32 { + if min < -EPSILON || max > 1. + EPSILON { + return -1; + } + for bits in 0..=3 { + let shifted_bits = 1 << bits; + let min = min * shifted_bits as f64; + let max = max * shifted_bits as f64; + let min_ok = (min - min.round()).abs() < EPSILON * shifted_bits as f64; + let max_ok = (max - max.round()).abs() < EPSILON * shifted_bits as f64; + if min_ok && max_ok { + return bits; + } + } + -1 +} + impl Shapes { + pub fn or(a: VoxelShape, b: VoxelShape) -> VoxelShape { + Self::join(a, b, |a, b| a || b) + } + pub fn collide( axis: &Axis, entity_box: &AABB, - collision_boxes: &Vec<Box<dyn VoxelShape>>, + collision_boxes: &Vec<VoxelShape>, mut movement: f64, ) -> f64 { for shape in collision_boxes { @@ -33,21 +119,239 @@ impl Shapes { } movement } + + pub fn join(a: VoxelShape, b: VoxelShape, op: fn(bool, bool) -> bool) -> VoxelShape { + Self::join_unoptimized(a, b, op).optimize() + } + + pub fn join_unoptimized( + a: VoxelShape, + b: VoxelShape, + op: fn(bool, bool) -> bool, + ) -> VoxelShape { + if op(false, false) { + panic!("Illegal operation"); + }; + // if (a == b) { + // return if op(true, true) { a } else { empty_shape() }; + // } + let op_true_false = op(true, false); + let op_false_true = op(false, true); + if a.is_empty() { + return if op_false_true { b } else { empty_shape() }; + } + if b.is_empty() { + return if op_true_false { a } else { empty_shape() }; + } + // IndexMerger var5 = createIndexMerger(1, a.getCoords(Direction.Axis.X), b.getCoords(Direction.Axis.X), var3, var4); + // IndexMerger var6 = createIndexMerger(var5.size() - 1, a.getCoords(Direction.Axis.Y), b.getCoords(Direction.Axis.Y), var3, var4); + // IndexMerger var7 = createIndexMerger((var5.size() - 1) * (var6.size() - 1), a.getCoords(Direction.Axis.Z), b.getCoords(Direction.Axis.Z), var3, var4); + // BitSetDiscreteVoxelShape var8 = BitSetDiscreteVoxelShape.join(a.shape, b.shape, var5, var6, var7, op); + // return (VoxelShape)(var5 instanceof DiscreteCubeMerger && var6 instanceof DiscreteCubeMerger && var7 instanceof DiscreteCubeMerger ? new CubeVoxelShape(var8) : new ArrayVoxelShape(var8, var5.getList(), var6.getList(), var7.getList())); + let var5 = Self::create_index_merger( + 1, + a.get_coords(Axis::X), + b.get_coords(Axis::X), + op_true_false, + op_false_true, + ); + let var6 = Self::create_index_merger( + (var5.size() - 1).try_into().unwrap(), + a.get_coords(Axis::Y), + b.get_coords(Axis::Y), + op_true_false, + op_false_true, + ); + let var7 = Self::create_index_merger( + ((var5.size() - 1) * (var6.size() - 1)).try_into().unwrap(), + a.get_coords(Axis::Z), + b.get_coords(Axis::Z), + op_true_false, + op_false_true, + ); + let var8 = BitSetDiscreteVoxelShape::join(&a.shape(), &b.shape(), &var5, &var6, &var7, op); + // if var5.is_discrete_cube_merger() + if let IndexMerger::DiscreteCube { .. } = var5 + && let IndexMerger::DiscreteCube { .. } = var6 + && let IndexMerger::DiscreteCube { .. } = var7 + { + VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(var8))) + } else { + VoxelShape::Array(ArrayVoxelShape::new( + DiscreteVoxelShape::BitSet(var8), + var5.get_list(), + var6.get_list(), + var7.get_list(), + )) + } + } + + /// Check if the op is true anywhere when joining the two shapes + /// vanilla calls this joinIsNotEmpty + pub fn matches_anywhere(a: &VoxelShape, b: &VoxelShape, op: fn(bool, bool) -> bool) -> bool { + assert!(!op(false, false)); + let a_is_empty = a.is_empty(); + let b_is_empty = b.is_empty(); + if a_is_empty || b_is_empty { + return op(!a_is_empty, !b_is_empty); + } + if a == b { + return op(true, true); + } + + let op_true_false = op(true, false); + let op_false_true = op(false, true); + + for axis in [Axis::X, Axis::Y, Axis::Z] { + if a.max(axis) < b.min(axis) - EPSILON { + return op_true_false || op_false_true; + } + if b.max(axis) < a.min(axis) - EPSILON { + return op_true_false || op_false_true; + } + } + + let x_merger = Self::create_index_merger( + 1, + a.get_coords(Axis::X), + b.get_coords(Axis::X), + op_true_false, + op_false_true, + ); + let y_merger = Self::create_index_merger( + (x_merger.size() - 1) as i32, + a.get_coords(Axis::Y), + b.get_coords(Axis::Y), + op_true_false, + op_false_true, + ); + let z_merger = Self::create_index_merger( + ((x_merger.size() - 1) * (y_merger.size() - 1)) as i32, + a.get_coords(Axis::Z), + b.get_coords(Axis::Z), + op_true_false, + op_false_true, + ); + + Self::matches_anywhere_with_mergers(x_merger, y_merger, z_merger, a.shape(), b.shape(), op) + } + + pub fn matches_anywhere_with_mergers( + merged_x: IndexMerger, + merged_y: IndexMerger, + merged_z: IndexMerger, + shape1: DiscreteVoxelShape, + shape2: DiscreteVoxelShape, + op: fn(bool, bool) -> bool, + ) -> bool { + !merged_x.for_merged_indexes(|var5x, var6, _var7| { + merged_y.for_merged_indexes(|var6x, var7x, _var8| { + merged_z.for_merged_indexes(|var7, var8x, _var9| { + !op( + shape1.is_full_wide(var5x, var6x, var7), + shape2.is_full_wide(var6, var7x, var8x), + ) + }) + }) + }) + } + + pub fn create_index_merger( + _var0: i32, + var1: Vec<f64>, + var2: Vec<f64>, + var3: bool, + var4: bool, + ) -> IndexMerger { + let var5 = var1.len() - 1; + let var6 = var2.len() - 1; + // if (&var1 as &dyn Any).is::<CubePointRange>() && (&var2 as &dyn Any).is::<CubePointRange>() + // { + // return new DiscreteCubeMerger(var0, var5, var6, var3, var4); + // let var7: i64 = lcm(var5 as u32, var6 as u32).try_into().unwrap(); + // // if ((long)var0 * var7 <= 256L) { + // if var0 as i64 * var7 <= 256 { + // return IndexMerger::new_discrete_cube(var5 as u32, var6 as u32); + // } + // } + + if var1[var5] < var2[0] - EPSILON { + IndexMerger::NonOverlapping { + lower: var1, + upper: var2, + swap: false, + } + } else if var2[var6] < var1[0] - EPSILON { + IndexMerger::NonOverlapping { + lower: var2, + upper: var1, + swap: true, + } + } else if var5 == var6 && var1 == var2 { + IndexMerger::Identical { coords: var1 } + } else { + IndexMerger::new_indirect(&var1, &var2, var3, var4) + } + } } -pub trait VoxelShape { - fn shape(&self) -> Box<dyn DiscreteVoxelShape>; +#[derive(Clone, PartialEq, Debug)] +pub enum VoxelShape { + Array(ArrayVoxelShape), + Cube(CubeVoxelShape), +} - fn get_coords(&self, axis: Axis) -> Vec<f64>; +impl VoxelShape { + // public double min(Direction.Axis var1) { + // int var2 = this.shape.firstFull(var1); + // return var2 >= this.shape.getSize(var1) ? 1.0D / 0.0 : this.get(var1, var2); + // } + // public double max(Direction.Axis var1) { + // int var2 = this.shape.lastFull(var1); + // return var2 <= 0 ? -1.0D / 0.0 : this.get(var1, var2); + // } + fn min(&self, axis: Axis) -> f64 { + let first_full = self.shape().first_full(axis); + if first_full >= self.shape().size(axis) as i32 { + f64::INFINITY + } else { + self.get(axis, first_full.try_into().unwrap()) + } + } + fn max(&self, axis: Axis) -> f64 { + let last_full = self.shape().last_full(axis); + if last_full <= 0 { + f64::NEG_INFINITY + } else { + self.get(axis, last_full.try_into().unwrap()) + } + } - // TODO: optimization: should this be changed to return ArrayVoxelShape? - // i might change the implementation of empty_shape in the future so not 100% sure - fn move_relative(&self, x: f64, y: f64, z: f64) -> Box<dyn VoxelShape> { + pub fn shape(&self) -> DiscreteVoxelShape { + match self { + VoxelShape::Array(s) => s.shape(), + VoxelShape::Cube(s) => s.shape(), + } + } + + pub fn get_coords(&self, axis: Axis) -> Vec<f64> { + match self { + VoxelShape::Array(s) => s.get_coords(axis), + VoxelShape::Cube(s) => s.get_coords(axis), + } + } + + pub fn is_empty(&self) -> bool { + self.shape().is_empty() + } + + #[must_use] + pub fn move_relative(&self, x: f64, y: f64, z: f64) -> VoxelShape { if self.shape().is_empty() { return empty_shape(); } - Box::new(ArrayVoxelShape::new( + VoxelShape::Array(ArrayVoxelShape::new( self.shape(), self.get_coords(Axis::X).iter().map(|c| c + x).collect(), self.get_coords(Axis::Y).iter().map(|c| c + y).collect(), @@ -55,21 +359,34 @@ pub trait VoxelShape { )) } - fn get(&self, axis: Axis, index: usize) -> f64 { - self.get_coords(axis)[index] + pub fn get(&self, axis: Axis, index: usize) -> f64 { + // self.get_coords(axis)[index] + match self { + VoxelShape::Array(s) => s.get_coords(axis)[index], + VoxelShape::Cube(s) => s.get_coords(axis)[index], + // _ => self.get_coords(axis)[index], + } } - fn find_index(&self, axis: Axis, coord: f64) -> i32 { - let r = binary_search(0, (self.shape().size(axis) + 1) as i32, &|t| { - coord < self.get(axis, t as usize) - }) - 1; - r + pub fn find_index(&self, axis: Axis, coord: f64) -> i32 { + // let r = binary_search(0, (self.shape().size(axis) + 1) as i32, &|t| { + // coord < self.get(axis, t as usize) + // }) - 1; + // r + match self { + VoxelShape::Cube(s) => s.find_index(axis, coord), + _ => { + binary_search(0, (self.shape().size(axis) + 1) as i32, &|t| { + coord < self.get(axis, t as usize) + }) - 1 + } + } } - fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 { + pub fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 { self.collide_x(AxisCycle::between(*axis, Axis::X), entity_box, movement) } - fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &AABB, mut movement: f64) -> f64 { + pub fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &AABB, mut movement: f64) -> f64 { if self.shape().is_empty() { return movement; } @@ -79,7 +396,6 @@ pub trait VoxelShape { let inverse_axis_cycle = axis_cycle.inverse(); - // probably not good names but idk what this does let x_axis = inverse_axis_cycle.cycle(Axis::X); let y_axis = inverse_axis_cycle.cycle(Axis::Y); let z_axis = inverse_axis_cycle.cycle(Axis::Z); @@ -87,40 +403,36 @@ pub trait VoxelShape { let max_x = entity_box.max(&x_axis); let min_x = entity_box.min(&x_axis); - // i gave up on names at this point (these are the obfuscated names from fernflower) - let var13 = self.find_index(x_axis, min_x + EPSILON); - let var14 = self.find_index(x_axis, max_x - EPSILON); + let x_min_index = self.find_index(x_axis, min_x + EPSILON); + let x_max_index = self.find_index(x_axis, max_x - EPSILON); - let var15 = cmp::max( + let y_min_index = cmp::max( 0, self.find_index(y_axis, entity_box.min(&y_axis) + EPSILON), ); - let var16 = cmp::min( + let y_max_index = cmp::min( self.shape().size(y_axis) as i32, self.find_index(y_axis, entity_box.max(&y_axis) - EPSILON) + 1, ); - let var17 = cmp::max( + let z_min_index = cmp::max( 0, self.find_index(z_axis, entity_box.min(&z_axis) + EPSILON), ); - let var18 = cmp::min( + let z_max_index = cmp::min( self.shape().size(z_axis) as i32, self.find_index(z_axis, entity_box.max(&z_axis) - EPSILON) + 1, ); - let var19 = self.shape().size(x_axis); if movement > 0. { - for var20 in var14 + 1..(var19 as i32) { - for var21 in var15..var16 { - for var22 in var17..var18 { - if self.shape().is_full_wide_axis_cycle( - inverse_axis_cycle, - var20.try_into().unwrap(), - var21.try_into().unwrap(), - var22.try_into().unwrap(), - ) { - let var23 = self.get(x_axis, var20 as usize) - max_x; + for x in x_max_index + 1..(self.shape().size(x_axis) as i32) { + for y in y_min_index..y_max_index { + for z in z_min_index..z_max_index { + if self + .shape() + .is_full_wide_axis_cycle(inverse_axis_cycle, x, y, z) + { + let var23 = self.get(x_axis, x as usize) - max_x; if var23 >= -EPSILON { movement = f64::min(movement, var23); } @@ -129,23 +441,19 @@ pub trait VoxelShape { } } } - } else if movement < 0. { - if var13 > 0 { - for var20 in (var13 - 1)..=0 { - for var21 in var15..var16 { - for var22 in var17..var18 { - if self.shape().is_full_wide_axis_cycle( - inverse_axis_cycle, - var20.try_into().unwrap(), - var21.try_into().unwrap(), - var22.try_into().unwrap(), - ) { - let var23 = self.get(x_axis, (var20 + 1) as usize) - min_x; - if var23 <= EPSILON { - movement = f64::max(movement, var23); - } - return movement; + } else if movement < 0. && x_min_index > 0 { + for x in (0..x_min_index).rev() { + for y in y_min_index..y_max_index { + for z in z_min_index..z_max_index { + if self + .shape() + .is_full_wide_axis_cycle(inverse_axis_cycle, x, y, z) + { + let var23 = self.get(x_axis, (x + 1) as usize) - min_x; + if var23 <= EPSILON { + movement = f64::max(movement, var23); } + return movement; } } } @@ -154,33 +462,112 @@ pub trait VoxelShape { movement } + + // public VoxelShape optimize() { + // VoxelShape[] var1 = new VoxelShape[]{Shapes.empty()}; + // this.forAllBoxes((var1x, var3, var5, var7, var9, var11) -> { + // var1[0] = Shapes.joinUnoptimized(var1[0], Shapes.box(var1x, var3, var5, var7, var9, var11), BooleanOp.OR); + // }); + // return var1[0]; + // } + fn optimize(&self) -> VoxelShape { + // let mut var1 = empty_shape(); + // self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| { + // var1 = Shapes::join_unoptimized( + // var1, + // box_shape(var1x, var3, var5, var7, var9, var11), + // |a, b| a || b, + // ); + // }); + // var1 + let mut var1 = empty_shape(); + self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| { + var1 = Shapes::join_unoptimized( + var1.clone(), + box_shape(var1x, var3, var5, var7, var9, var11), + |a, b| a || b, + ); + }); + var1 + } + + // public void forAllBoxes(Shapes.DoubleLineConsumer var1) { + // DoubleList var2 = this.getCoords(Direction.Axis.X); + // DoubleList var3 = this.getCoords(Direction.Axis.Y); + // DoubleList var4 = this.getCoords(Direction.Axis.Z); + // this.shape.forAllBoxes((var4x, var5, var6, var7, var8, var9) -> { + // var1.consume(var2.getDouble(var4x), var3.getDouble(var5), var4.getDouble(var6), var2.getDouble(var7), var3.getDouble(var8), var4.getDouble(var9)); + // }, true); + // } + pub fn for_all_boxes(&self, mut consumer: impl FnMut(f64, f64, f64, f64, f64, f64)) + where + Self: Sized, + { + // let x_coords = self.get_coords(Axis::X); + // let y_coords = self.get_coords(Axis::Y); + // let z_coords = self.get_coords(Axis::Z); + // self.shape().for_all_boxes( + // |var4x, var5, var6, var7, var8, var9| { + // consumer( + // x_coords[var4x as usize], + // y_coords[var5 as usize], + // z_coords[var6 as usize], + // x_coords[var7 as usize], + // y_coords[var8 as usize], + // z_coords[var9 as usize], + // ) + // }, + // true, + // ); + let x_coords = self.get_coords(Axis::X); + let y_coords = self.get_coords(Axis::Y); + let z_coords = self.get_coords(Axis::Z); + self.shape().for_all_boxes( + |var4x, var5, var6, var7, var8, var9| { + consumer( + x_coords[var4x as usize], + y_coords[var5 as usize], + z_coords[var6 as usize], + x_coords[var7 as usize], + y_coords[var8 as usize], + z_coords[var9 as usize], + ) + }, + true, + ); + } } +impl From<AABB> for VoxelShape { + fn from(aabb: AABB) -> Self { + box_shape_unchecked( + aabb.min_x, aabb.min_y, aabb.min_z, aabb.max_x, aabb.max_y, aabb.max_z, + ) + } +} + +#[derive(Clone, PartialEq, Debug)] pub struct ArrayVoxelShape { - shape: Box<dyn DiscreteVoxelShape>, + shape: DiscreteVoxelShape, // TODO: check where faces is used in minecraft #[allow(dead_code)] - faces: Option<Vec<Box<dyn VoxelShape>>>, + faces: Option<Vec<VoxelShape>>, pub xs: Vec<f64>, pub ys: Vec<f64>, pub zs: Vec<f64>, } +#[derive(Clone, PartialEq, Debug)] pub struct CubeVoxelShape { - shape: Box<dyn DiscreteVoxelShape>, + shape: DiscreteVoxelShape, // TODO: check where faces is used in minecraft #[allow(dead_code)] - faces: Option<Vec<Box<dyn VoxelShape>>>, + faces: Option<Vec<VoxelShape>>, } impl ArrayVoxelShape { - pub fn new( - shape: Box<dyn DiscreteVoxelShape>, - xs: Vec<f64>, - ys: Vec<f64>, - zs: Vec<f64>, - ) -> Self { + pub fn new(shape: DiscreteVoxelShape, xs: Vec<f64>, ys: Vec<f64>, zs: Vec<f64>) -> Self { let x_size = shape.size(Axis::X) + 1; let y_size = shape.size(Axis::Y) + 1; let z_size = shape.size(Axis::Z) + 1; @@ -201,13 +588,13 @@ impl ArrayVoxelShape { } impl CubeVoxelShape { - pub fn new(shape: Box<dyn DiscreteVoxelShape>) -> Self { + pub fn new(shape: DiscreteVoxelShape) -> Self { Self { shape, faces: None } } } -impl VoxelShape for ArrayVoxelShape { - fn shape(&self) -> Box<dyn DiscreteVoxelShape> { +impl ArrayVoxelShape { + fn shape(&self) -> DiscreteVoxelShape { self.shape.clone() } @@ -216,8 +603,8 @@ impl VoxelShape for ArrayVoxelShape { } } -impl VoxelShape for CubeVoxelShape { - fn shape(&self) -> Box<dyn DiscreteVoxelShape> { +impl CubeVoxelShape { + fn shape(&self) -> DiscreteVoxelShape { self.shape.clone() } @@ -236,6 +623,25 @@ impl VoxelShape for CubeVoxelShape { } } +#[derive(Debug)] +pub struct CubePointRange { + /// Needs at least 1 part + pub parts: NonZeroU32, +} +impl CubePointRange { + pub fn get_double(&self, index: u32) -> f64 { + index as f64 / self.parts.get() as f64 + } + + pub fn size(&self) -> u32 { + self.parts.get() + 1 + } + + pub fn iter(&self) -> Vec<f64> { + (0..=self.parts.get()).map(|i| self.get_double(i)).collect() + } +} + #[cfg(test)] mod tests { use super::*; @@ -251,4 +657,37 @@ mod tests { assert_eq!(shape.get_coords(Axis::Y).len(), 2); assert_eq!(shape.get_coords(Axis::Z).len(), 2); } + + #[test] + fn test_box_shape() { + let shape = box_shape(0., 0., 0., 1., 1., 1.); + assert_eq!(shape.shape().size(Axis::X), 1); + assert_eq!(shape.shape().size(Axis::Y), 1); + assert_eq!(shape.shape().size(Axis::Z), 1); + + assert_eq!(shape.get_coords(Axis::X).len(), 2); + assert_eq!(shape.get_coords(Axis::Y).len(), 2); + assert_eq!(shape.get_coords(Axis::Z).len(), 2); + } + + #[test] + fn test_top_slab_shape() { + let shape = box_shape(0., 0.5, 0., 1., 1., 1.); + assert_eq!(shape.shape().size(Axis::X), 1); + assert_eq!(shape.shape().size(Axis::Y), 2); + assert_eq!(shape.shape().size(Axis::Z), 1); + + assert_eq!(shape.get_coords(Axis::X).len(), 2); + assert_eq!(shape.get_coords(Axis::Y).len(), 3); + assert_eq!(shape.get_coords(Axis::Z).len(), 2); + } + + #[test] + fn test_join_is_not_empty() { + let shape = box_shape(0., 0., 0., 1., 1., 1.); + let shape2 = box_shape(0., 0.5, 0., 1., 1., 1.); + // detect if the shapes intersect at all + let joined = Shapes::matches_anywhere(&shape, &shape2, |a, b| a && b); + assert!(joined, "Shapes should intersect"); + } } |
