use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB}; use azalea_core::{binary_search, Axis, AxisCycle, EPSILON}; use std::cmp; pub struct Shapes {} pub fn block_shape() -> Box { let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1); shape.fill(0, 0, 0); Box::new(CubeVoxelShape::new(Box::new(shape))) } pub fn empty_shape() -> Box { Box::new(ArrayVoxelShape::new( Box::new(BitSetDiscreteVoxelShape::new(0, 0, 0)), vec![0.], vec![0.], vec![0.], )) } impl Shapes { pub fn collide( axis: &Axis, entity_box: &AABB, collision_boxes: &Vec>, mut movement: f64, ) -> f64 { for shape in collision_boxes { if movement.abs() < EPSILON { return 0.; } movement = shape.collide(axis, entity_box, movement); } movement } } pub trait VoxelShape { fn shape(&self) -> Box; fn get_coords(&self, axis: Axis) -> Vec; // 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 { if self.shape().is_empty() { return empty_shape(); } Box::new(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(), self.get_coords(Axis::Z).iter().map(|c| c + z).collect(), )) } fn get(&self, axis: Axis, index: usize) -> f64 { 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 } 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 { if self.shape().is_empty() { return movement; } if movement.abs() < EPSILON { return 0.; } 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); 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 var15 = cmp::max( 0, self.find_index(y_axis, entity_box.min(&y_axis) + EPSILON), ); let var16 = 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( 0, self.find_index(z_axis, entity_box.min(&z_axis) + EPSILON), ); let var18 = 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; if var23 >= -EPSILON { movement = f64::min(movement, var23); } return movement; } } } } } 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; } } } } } } movement } } pub struct ArrayVoxelShape { shape: Box, // TODO: check where faces is used in minecraft #[allow(dead_code)] faces: Option>>, pub xs: Vec, pub ys: Vec, pub zs: Vec, } pub struct CubeVoxelShape { shape: Box, // TODO: check where faces is used in minecraft #[allow(dead_code)] faces: Option>>, } impl ArrayVoxelShape { pub fn new( shape: Box, xs: Vec, ys: Vec, zs: Vec, ) -> 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; // Lengths of point arrays must be consistent with the size of the VoxelShape. assert_eq!(x_size, xs.len() as u32); assert_eq!(y_size, ys.len() as u32); assert_eq!(z_size, zs.len() as u32); Self { faces: None, shape, xs, ys, zs, } } } impl CubeVoxelShape { pub fn new(shape: Box) -> Self { Self { shape, faces: None } } } impl VoxelShape for ArrayVoxelShape { fn shape(&self) -> Box { self.shape.clone() } fn get_coords(&self, axis: Axis) -> Vec { axis.choose(self.xs.clone(), self.ys.clone(), self.zs.clone()) } } impl VoxelShape for CubeVoxelShape { fn shape(&self) -> Box { self.shape.clone() } fn get_coords(&self, axis: Axis) -> Vec { let size = self.shape.size(axis); let mut parts = Vec::with_capacity(size as usize); for i in 0..=size { parts.push(i as f64 / size as f64); } parts } fn find_index(&self, axis: Axis, coord: f64) -> i32 { let n = self.shape().size(axis); (f64::clamp(coord * (n as f64), -1f64, n as f64)) as i32 } } #[cfg(test)] mod tests { use super::*; #[test] fn test_block_shape() { let shape = block_shape(); 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); } }