1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 23:44:38 +00:00
azalea/azalea-physics/src/collision/shape.rs
2022-09-19 20:38:08 -05:00

254 lines
7.8 KiB
Rust

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<dyn VoxelShape> {
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<dyn VoxelShape> {
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<Box<dyn VoxelShape>>,
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<dyn DiscreteVoxelShape>;
fn get_coords(&self, axis: Axis) -> Vec<f64>;
// 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> {
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<dyn DiscreteVoxelShape>,
// TODO: check where faces is used in minecraft
#[allow(dead_code)]
faces: Option<Vec<Box<dyn VoxelShape>>>,
pub xs: Vec<f64>,
pub ys: Vec<f64>,
pub zs: Vec<f64>,
}
pub struct CubeVoxelShape {
shape: Box<dyn DiscreteVoxelShape>,
// TODO: check where faces is used in minecraft
#[allow(dead_code)]
faces: Option<Vec<Box<dyn VoxelShape>>>,
}
impl ArrayVoxelShape {
pub fn new(
shape: Box<dyn 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;
// 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<dyn DiscreteVoxelShape>) -> Self {
Self { shape, faces: None }
}
}
impl VoxelShape for ArrayVoxelShape {
fn shape(&self) -> Box<dyn DiscreteVoxelShape> {
self.shape.clone()
}
fn get_coords(&self, axis: Axis) -> Vec<f64> {
axis.choose(self.xs.clone(), self.ys.clone(), self.zs.clone())
}
}
impl VoxelShape for CubeVoxelShape {
fn shape(&self) -> Box<dyn DiscreteVoxelShape> {
self.shape.clone()
}
fn get_coords(&self, axis: Axis) -> Vec<f64> {
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);
}
}