use crate::bounding_volume::{BoundingSphere, BoundingVolume};
use crate::math::{Pose, Real, Vector, DIM, TWO_DIM};
use crate::shape::{Cuboid, SupportMap};
use crate::utils::PoseOps;
use arrayvec::ArrayVec;
use num::Bounded;
use crate::query::{Ray, RayCast};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(C)]
pub struct Aabb {
pub mins: Vector,
pub maxs: Vector,
}
impl Aabb {
#[cfg(feature = "dim3")]
pub const EDGES_VERTEX_IDS: [(usize, usize); 12] = [
(0, 1),
(1, 2),
(3, 2),
(0, 3),
(4, 5),
(5, 6),
(7, 6),
(4, 7),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
];
#[cfg(feature = "dim3")]
pub const FACES_VERTEX_IDS: [(usize, usize, usize, usize); 6] = [
(1, 2, 6, 5),
(0, 3, 7, 4),
(2, 3, 7, 6),
(1, 0, 4, 5),
(4, 5, 6, 7),
(0, 1, 2, 3),
];
#[cfg(feature = "dim2")]
pub const FACES_VERTEX_IDS: [(usize, usize); 4] = [
(1, 2),
(3, 0),
(2, 3),
(0, 1),
];
#[inline]
pub fn new(mins: Vector, maxs: Vector) -> Aabb {
Aabb { mins, maxs }
}
#[inline]
pub fn new_invalid() -> Self {
Self::new(
Vector::splat(Real::max_value()),
Vector::splat(-Real::max_value()),
)
}
#[inline]
pub fn from_half_extents(center: Vector, half_extents: Vector) -> Self {
Self::new(center - half_extents, center + half_extents)
}
pub fn from_points_ref<'a, I>(pts: I) -> Self
where
I: IntoIterator<Item = &'a Vector>,
{
super::aabb_utils::local_point_cloud_aabb(pts.into_iter().copied())
}
pub fn from_points<I>(pts: I) -> Self
where
I: IntoIterator<Item = Vector>,
{
super::aabb_utils::local_point_cloud_aabb(pts)
}
#[inline]
pub fn center(&self) -> Vector {
self.mins.midpoint(self.maxs)
}
#[inline]
pub fn half_extents(&self) -> Vector {
let half: Real = 0.5;
(self.maxs - self.mins) * half
}
#[inline]
pub fn volume(&self) -> Real {
let extents = self.extents();
#[cfg(feature = "dim2")]
return extents.x * extents.y;
#[cfg(feature = "dim3")]
return extents.x * extents.y * extents.z;
}
pub fn half_area_or_perimeter(&self) -> Real {
#[cfg(feature = "dim2")]
return self.half_perimeter();
#[cfg(feature = "dim3")]
return self.half_area();
}
#[cfg(feature = "dim2")]
pub fn half_perimeter(&self) -> Real {
let extents = self.extents();
extents.x + extents.y
}
#[cfg(feature = "dim3")]
pub fn half_area(&self) -> Real {
let extents = self.extents();
extents.x * (extents.y + extents.z) + extents.y * extents.z
}
#[inline]
pub fn extents(&self) -> Vector {
self.maxs - self.mins
}
pub fn take_point(&mut self, pt: Vector) {
self.mins = self.mins.min(pt);
self.maxs = self.maxs.max(pt);
}
#[inline]
pub fn transform_by(&self, m: &Pose) -> Self {
let ls_center = self.center();
let center = m * ls_center;
let ws_half_extents = m.absolute_transform_vector(self.half_extents());
Aabb::new(center + (-ws_half_extents), center + ws_half_extents)
}
#[inline]
pub fn translated(mut self, translation: Vector) -> Self {
self.mins += translation;
self.maxs += translation;
self
}
#[inline]
pub fn scaled(self, scale: Vector) -> Self {
let a = self.mins * scale;
let b = self.maxs * scale;
Self {
mins: a.min(b),
maxs: a.max(b),
}
}
#[inline]
#[must_use]
pub fn scaled_wrt_center(self, scale: Vector) -> Self {
let center = self.center();
let half_extents = self.half_extents() * scale.abs();
Self::from_half_extents(center, half_extents)
}
#[inline]
pub fn bounding_sphere(&self) -> BoundingSphere {
let center = self.center();
let radius = self.mins.distance(self.maxs) * 0.5;
BoundingSphere::new(center, radius)
}
#[inline]
pub fn contains_local_point(&self, point: Vector) -> bool {
for i in 0..DIM {
if point[i] < self.mins[i] || point[i] > self.maxs[i] {
return false;
}
}
true
}
pub fn distance_to_origin(&self) -> Real {
self.mins.max(-self.maxs).max(Vector::ZERO).length()
}
#[inline]
pub fn intersects_moving_aabb(&self, aabb2: &Self, vel12: Vector) -> bool {
let msum = Aabb {
mins: self.mins - aabb2.maxs,
maxs: self.maxs - aabb2.mins,
};
let ray = Ray::new(Vector::ZERO, vel12);
msum.intersects_local_ray(&ray, 1.0)
}
pub fn intersection(&self, other: &Aabb) -> Option<Aabb> {
let result = Aabb {
mins: self.mins.max(other.mins),
maxs: self.maxs.min(other.maxs),
};
for i in 0..DIM {
if result.mins[i] > result.maxs[i] {
return None;
}
}
Some(result)
}
pub fn aligned_intersections(&self, pos12: &Pose, aabb2: &Self) -> Option<(Aabb, Aabb)> {
let pos21 = pos12.inverse();
let aabb2_1 = aabb2.transform_by(pos12);
let inter1_1 = self.intersection(&aabb2_1)?;
let inter1_2 = inter1_1.transform_by(&pos21);
let aabb1_2 = self.transform_by(&pos21);
let inter2_2 = aabb2.intersection(&aabb1_2)?;
let inter2_1 = inter2_2.transform_by(pos12);
Some((
inter1_1.intersection(&inter2_1)?,
inter1_2.intersection(&inter2_2)?,
))
}
pub fn difference(&self, rhs: &Aabb) -> ArrayVec<Self, TWO_DIM> {
self.difference_with_cut_sequence(rhs).0
}
pub fn difference_with_cut_sequence(
&self,
rhs: &Aabb,
) -> (ArrayVec<Self, TWO_DIM>, ArrayVec<(i8, Real), TWO_DIM>) {
let mut result = ArrayVec::new();
let mut cut_sequence = ArrayVec::new();
for i in 0..DIM {
if self.mins[i] >= rhs.maxs[i] || self.maxs[i] <= rhs.mins[i] {
result.push(*self);
return (result, cut_sequence);
}
}
let mut rest = *self;
for i in 0..DIM {
if rhs.mins[i] > rest.mins[i] {
let mut fragment = rest;
fragment.maxs[i] = rhs.mins[i];
rest.mins[i] = rhs.mins[i];
result.push(fragment);
cut_sequence.push((i as i8 + 1, rhs.mins[i]));
}
if rhs.maxs[i] < rest.maxs[i] {
let mut fragment = rest;
fragment.mins[i] = rhs.maxs[i];
rest.maxs[i] = rhs.maxs[i];
result.push(fragment);
cut_sequence.push((-(i as i8 + 1), -rhs.maxs[i]));
}
}
(result, cut_sequence)
}
#[inline]
#[cfg(feature = "dim2")]
pub fn vertices(&self) -> [Vector; 4] {
[
Vector::new(self.mins.x, self.mins.y),
Vector::new(self.maxs.x, self.mins.y),
Vector::new(self.maxs.x, self.maxs.y),
Vector::new(self.mins.x, self.maxs.y),
]
}
#[inline]
#[cfg(feature = "dim3")]
pub fn vertices(&self) -> [Vector; 8] {
[
Vector::new(self.mins.x, self.mins.y, self.mins.z),
Vector::new(self.maxs.x, self.mins.y, self.mins.z),
Vector::new(self.maxs.x, self.maxs.y, self.mins.z),
Vector::new(self.mins.x, self.maxs.y, self.mins.z),
Vector::new(self.mins.x, self.mins.y, self.maxs.z),
Vector::new(self.maxs.x, self.mins.y, self.maxs.z),
Vector::new(self.maxs.x, self.maxs.y, self.maxs.z),
Vector::new(self.mins.x, self.maxs.y, self.maxs.z),
]
}
#[inline]
#[cfg(feature = "dim2")]
pub fn split_at_center(&self) -> [Aabb; 4] {
let center = self.center();
[
Aabb::new(self.mins, center),
Aabb::new(
Vector::new(center.x, self.mins.y),
Vector::new(self.maxs.x, center.y),
),
Aabb::new(center, self.maxs),
Aabb::new(
Vector::new(self.mins.x, center.y),
Vector::new(center.x, self.maxs.y),
),
]
}
#[inline]
#[cfg(feature = "dim3")]
pub fn split_at_center(&self) -> [Aabb; 8] {
let center = self.center();
[
Aabb::new(
Vector::new(self.mins.x, self.mins.y, self.mins.z),
Vector::new(center.x, center.y, center.z),
),
Aabb::new(
Vector::new(center.x, self.mins.y, self.mins.z),
Vector::new(self.maxs.x, center.y, center.z),
),
Aabb::new(
Vector::new(center.x, center.y, self.mins.z),
Vector::new(self.maxs.x, self.maxs.y, center.z),
),
Aabb::new(
Vector::new(self.mins.x, center.y, self.mins.z),
Vector::new(center.x, self.maxs.y, center.z),
),
Aabb::new(
Vector::new(self.mins.x, self.mins.y, center.z),
Vector::new(center.x, center.y, self.maxs.z),
),
Aabb::new(
Vector::new(center.x, self.mins.y, center.z),
Vector::new(self.maxs.x, center.y, self.maxs.z),
),
Aabb::new(
Vector::new(center.x, center.y, center.z),
Vector::new(self.maxs.x, self.maxs.y, self.maxs.z),
),
Aabb::new(
Vector::new(self.mins.x, center.y, center.z),
Vector::new(center.x, self.maxs.y, self.maxs.z),
),
]
}
#[must_use]
pub fn add_half_extents(&self, half_extents: Vector) -> Self {
Self {
mins: self.mins - half_extents,
maxs: self.maxs + half_extents,
}
}
pub fn project_on_axis(&self, axis: Vector) -> (Real, Real) {
let cuboid = Cuboid::new(self.half_extents());
let shift = cuboid.local_support_point_toward(axis).dot(axis).abs();
let center = self.center().dot(axis);
(center - shift, center + shift)
}
#[cfg(feature = "dim3")]
#[cfg(feature = "alloc")]
pub fn intersects_spiral(
&self,
point: Vector,
spiral_center: Vector,
axis: Vector,
linvel: Vector,
angvel: Real,
) -> bool {
use crate::math::{Matrix2, Vector2};
use crate::utils::WBasis;
use crate::utils::{Interval, IntervalFunction};
use alloc::vec;
struct SpiralPlaneDistance {
spiral_center: Vector,
tangents: [Vector; 2],
linvel: Vector,
angvel: Real,
point: Vector2,
plane: Vector,
bias: Real,
}
impl SpiralPlaneDistance {
fn spiral_pt_at(&self, t: Real) -> Vector {
let angle = t * self.angvel;
let (sin, cos) = <Real as simba::scalar::ComplexField>::sin_cos(angle);
let rotmat = Matrix2::from_cols(Vector2::new(cos, sin), Vector2::new(-sin, cos));
let rotated_pt = rotmat * self.point;
let shift = self.tangents[0] * rotated_pt.x + self.tangents[1] * rotated_pt.y;
self.spiral_center + self.linvel * t + shift
}
}
impl IntervalFunction<Real> for SpiralPlaneDistance {
fn eval(&self, t: Real) -> Real {
let point_pos = self.spiral_pt_at(t);
point_pos.dot(self.plane) - self.bias
}
fn eval_interval(&self, t: Interval<Real>) -> Interval<Real> {
let angle = t * self.angvel;
let (sin, cos) = angle.sin_cos();
let rotated_pt_x =
cos * Interval::splat(self.point.x) - sin * Interval::splat(self.point.y);
let rotated_pt_y =
sin * Interval::splat(self.point.x) + cos * Interval::splat(self.point.y);
let shift_x = Interval::splat(self.tangents[0].x) * rotated_pt_x
+ Interval::splat(self.tangents[1].x) * rotated_pt_y;
let shift_y = Interval::splat(self.tangents[0].y) * rotated_pt_x
+ Interval::splat(self.tangents[1].y) * rotated_pt_y;
let shift_z = Interval::splat(self.tangents[0].z) * rotated_pt_x
+ Interval::splat(self.tangents[1].z) * rotated_pt_y;
let point_pos_x = Interval::splat(self.spiral_center.x)
+ Interval::splat(self.linvel.x) * t
+ shift_x;
let point_pos_y = Interval::splat(self.spiral_center.y)
+ Interval::splat(self.linvel.y) * t
+ shift_y;
let point_pos_z = Interval::splat(self.spiral_center.z)
+ Interval::splat(self.linvel.z) * t
+ shift_z;
point_pos_x * Interval::splat(self.plane.x)
+ point_pos_y * Interval::splat(self.plane.y)
+ point_pos_z * Interval::splat(self.plane.z)
- Interval::splat(self.bias)
}
fn eval_interval_gradient(&self, t: Interval<Real>) -> Interval<Real> {
let angle = t * self.angvel;
let (sin, cos) = angle.sin_cos();
let angvel_interval = Interval::splat(self.angvel);
let rotated_pt_x = (-sin * angvel_interval) * Interval::splat(self.point.x)
- (cos * angvel_interval) * Interval::splat(self.point.y);
let rotated_pt_y = (cos * angvel_interval) * Interval::splat(self.point.x)
+ (-sin * angvel_interval) * Interval::splat(self.point.y);
let shift_x = Interval::splat(self.tangents[0].x) * rotated_pt_x
+ Interval::splat(self.tangents[1].x) * rotated_pt_y;
let shift_y = Interval::splat(self.tangents[0].y) * rotated_pt_x
+ Interval::splat(self.tangents[1].y) * rotated_pt_y;
let shift_z = Interval::splat(self.tangents[0].z) * rotated_pt_x
+ Interval::splat(self.tangents[1].z) * rotated_pt_y;
let point_vel_x = shift_x + Interval::splat(self.linvel.x);
let point_vel_y = shift_y + Interval::splat(self.linvel.y);
let point_vel_z = shift_z + Interval::splat(self.linvel.z);
point_vel_x * Interval::splat(self.plane.x)
+ point_vel_y * Interval::splat(self.plane.y)
+ point_vel_z * Interval::splat(self.plane.z)
}
}
let tangents = axis.orthonormal_basis();
let dpos = point - spiral_center;
#[cfg(feature = "f32")]
let spiral_point = glamx::Vec2::new(dpos.dot(tangents[0]), dpos.dot(tangents[1]));
#[cfg(feature = "f64")]
let spiral_point = glamx::DVec2::new(dpos.dot(tangents[0]), dpos.dot(tangents[1]));
let mut distance_fn = SpiralPlaneDistance {
spiral_center,
tangents,
linvel,
angvel,
point: spiral_point,
plane: Vector::X,
bias: 0.0,
};
let mut roots = vec![];
let mut candidates = vec![];
let planes = [
(-self.mins[0], -Vector::X, 0),
(self.maxs[0], Vector::X, 0),
(-self.mins[1], -Vector::Y, 1),
(self.maxs[1], Vector::Y, 1),
(-self.mins[2], -Vector::Z, 2),
(self.maxs[2], Vector::Z, 2),
];
let range = self.project_on_axis(axis);
let range_bias = spiral_center.dot(axis);
let interval = Interval::sort(range.0, range.1) - range_bias;
for (bias, axis, i) in &planes {
distance_fn.plane = *axis;
distance_fn.bias = *bias;
crate::utils::find_root_intervals_to(
&distance_fn,
interval,
1.0e-5,
1.0e-5,
100,
&mut roots,
&mut candidates,
);
for root in roots.drain(..) {
let point = distance_fn.spiral_pt_at(root.midpoint());
let (j, k) = ((i + 1) % 3, (i + 2) % 3);
if point[j] >= self.mins[j]
&& point[j] <= self.maxs[j]
&& point[k] >= self.mins[k]
&& point[k] <= self.maxs[k]
{
return true;
}
}
}
false
}
}
impl BoundingVolume for Aabb {
#[inline]
fn center(&self) -> Vector {
self.center()
}
#[inline]
fn intersects(&self, other: &Aabb) -> bool {
(self.mins.cmple(other.maxs) & self.maxs.cmpge(other.mins)).all()
}
#[inline]
fn contains(&self, other: &Aabb) -> bool {
(self.mins.cmple(other.mins) & self.maxs.cmpge(other.maxs)).all()
}
#[inline]
fn merge(&mut self, other: &Aabb) {
self.mins = self.mins.min(other.mins);
self.maxs = self.maxs.max(other.maxs);
}
#[inline]
fn merged(&self, other: &Aabb) -> Aabb {
Aabb {
mins: self.mins.min(other.mins),
maxs: self.maxs.max(other.maxs),
}
}
#[inline]
fn loosen(&mut self, amount: Real) {
assert!(amount >= 0.0, "The loosening margin must be positive.");
self.mins += Vector::splat(-amount);
self.maxs += Vector::splat(amount);
}
#[inline]
fn loosened(&self, amount: Real) -> Aabb {
assert!(amount >= 0.0, "The loosening margin must be positive.");
Aabb {
mins: self.mins + Vector::splat(-amount),
maxs: self.maxs + Vector::splat(amount),
}
}
#[inline]
fn tighten(&mut self, amount: Real) {
assert!(amount >= 0.0, "The tightening margin must be positive.");
self.mins += Vector::splat(amount);
self.maxs += Vector::splat(-amount);
assert!(
self.mins.cmple(self.maxs).all(),
"The tightening margin is to large."
);
}
#[inline]
fn tightened(&self, amount: Real) -> Aabb {
assert!(amount >= 0.0, "The tightening margin must be positive.");
Aabb::new(
self.mins + Vector::splat(amount),
self.maxs + Vector::splat(-amount),
)
}
}