use crate::{
primitives::{
Arc2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, CircularSector, CircularSegment,
Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon, Rhombus,
Segment2d, Triangle2d,
},
Dir2, Mat2, Rot2, Vec2,
};
use std::f32::consts::{FRAC_PI_2, PI, TAU};
use smallvec::SmallVec;
use super::{Aabb2d, Bounded2d, BoundingCircle};
impl Bounded2d for Circle {
fn aabb_2d(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::new(translation, Vec2::splat(self.radius))
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.radius)
}
}
#[inline]
fn arc_bounding_points(arc: Arc2d, rotation: impl Into<Rot2>) -> SmallVec<[Vec2; 7]> {
let mut bounds = SmallVec::<[Vec2; 7]>::new();
let rotation = rotation.into();
bounds.push(rotation * arc.left_endpoint());
bounds.push(rotation * arc.right_endpoint());
let left_angle = (FRAC_PI_2 + arc.half_angle + rotation.as_radians()).rem_euclid(TAU);
let right_angle = (FRAC_PI_2 - arc.half_angle + rotation.as_radians()).rem_euclid(TAU);
let inverted = left_angle < right_angle;
for extremum in [Vec2::X, Vec2::Y, Vec2::NEG_X, Vec2::NEG_Y] {
let angle = extremum.to_angle().rem_euclid(TAU);
#[allow(clippy::nonminimal_bool)]
if !inverted && angle >= right_angle && angle <= left_angle
|| inverted && (angle >= right_angle || angle <= left_angle)
{
bounds.push(extremum * arc.radius);
}
}
bounds
}
impl Bounded2d for Arc2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
if self.half_angle >= PI {
return Circle::new(self.radius).aabb_2d(translation, rotation);
}
Aabb2d::from_point_cloud(translation, 0.0, &arc_bounding_points(*self, rotation))
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
if self.is_major() {
BoundingCircle::new(translation, self.radius)
} else {
let center = rotation.into() * self.chord_midpoint();
BoundingCircle::new(center + translation, self.half_chord_length())
}
}
}
impl Bounded2d for CircularSector {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
if self.half_angle() >= PI {
return Circle::new(self.radius()).aabb_2d(translation, rotation);
}
let mut bounds = arc_bounding_points(self.arc, rotation);
bounds.push(Vec2::ZERO);
Aabb2d::from_point_cloud(translation, 0.0, &bounds)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
if self.arc.is_major() {
BoundingCircle::new(translation, self.arc.radius)
} else {
Triangle2d::new(
Vec2::ZERO,
self.arc.left_endpoint(),
self.arc.right_endpoint(),
)
.bounding_circle(translation, rotation)
}
}
}
impl Bounded2d for CircularSegment {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
self.arc.aabb_2d(translation, rotation)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
self.arc.bounding_circle(translation, rotation)
}
}
impl Bounded2d for Ellipse {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let (hw, hh) = (self.half_size.x, self.half_size.y);
let (alpha_sin, alpha_cos) = rotation.sin_cos();
let (beta_sin, beta_cos) = (alpha_cos, -alpha_sin);
let (ux, uy) = (hw * alpha_cos, hw * alpha_sin);
let (vx, vy) = (hh * beta_cos, hh * beta_sin);
let half_size = Vec2::new(ux.hypot(vx), uy.hypot(vy));
Aabb2d::new(translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.semi_major())
}
}
impl Bounded2d for Rhombus {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation_mat = rotation.into();
let [rotated_x_half_diagonal, rotated_y_half_diagonal] = [
rotation_mat * Vec2::new(self.half_diagonals.x, 0.0),
rotation_mat * Vec2::new(0.0, self.half_diagonals.y),
];
let aabb_half_extent = rotated_x_half_diagonal
.abs()
.max(rotated_y_half_diagonal.abs());
Aabb2d {
min: -aabb_half_extent + translation,
max: aabb_half_extent + translation,
}
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.circumradius())
}
}
impl Bounded2d for Plane2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let normal = rotation * *self.normal;
let facing_x = normal == Vec2::X || normal == Vec2::NEG_X;
let facing_y = normal == Vec2::Y || normal == Vec2::NEG_Y;
let half_width = if facing_x { 0.0 } else { f32::MAX / 2.0 };
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
let half_size = Vec2::new(half_width, half_height);
Aabb2d::new(translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, f32::MAX / 2.0)
}
}
impl Bounded2d for Line2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let direction = rotation * *self.direction;
let max = f32::MAX / 2.0;
let half_width = if direction.x == 0.0 { 0.0 } else { max };
let half_height = if direction.y == 0.0 { 0.0 } else { max };
let half_size = Vec2::new(half_width, half_height);
Aabb2d::new(translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, f32::MAX / 2.0)
}
}
impl Bounded2d for Segment2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let direction = rotation * *self.direction;
let half_size = (self.half_length * direction).abs();
Aabb2d::new(translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.half_length)
}
}
impl<const N: usize> Bounded2d for Polyline2d<N> {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
}
}
impl Bounded2d for BoxedPolyline2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
}
}
impl Bounded2d for Triangle2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let [a, b, c] = self.vertices.map(|vtx| rotation * vtx);
let min = Vec2::new(a.x.min(b.x).min(c.x), a.y.min(b.y).min(c.y));
let max = Vec2::new(a.x.max(b.x).max(c.x), a.y.max(b.y).max(c.y));
Aabb2d {
min: min + translation,
max: max + translation,
}
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
let rotation: Rot2 = rotation.into();
let [a, b, c] = self.vertices;
let side_opposite_to_non_acute = if (b - a).dot(c - a) <= 0.0 {
Some((b, c))
} else if (c - b).dot(a - b) <= 0.0 {
Some((c, a))
} else if (a - c).dot(b - c) <= 0.0 {
Some((a, b))
} else {
None
};
if let Some((point1, point2)) = side_opposite_to_non_acute {
let (segment, center) = Segment2d::from_points(point1, point2);
segment.bounding_circle(rotation * center + translation, rotation)
} else {
let (Circle { radius }, circumcenter) = self.circumcircle();
BoundingCircle::new(rotation * circumcenter + translation, radius)
}
}
}
impl Bounded2d for Rectangle {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let (sin, cos) = rotation.sin_cos();
let abs_rot_mat = Mat2::from_cols_array(&[cos.abs(), sin.abs(), sin.abs(), cos.abs()]);
let half_size = abs_rot_mat * self.half_size;
Aabb2d::new(translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
let radius = self.half_size.length();
BoundingCircle::new(translation, radius)
}
}
impl<const N: usize> Bounded2d for Polygon<N> {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
}
}
impl Bounded2d for BoxedPolygon {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
}
}
impl Bounded2d for RegularPolygon {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let mut min = Vec2::ZERO;
let mut max = Vec2::ZERO;
for vertex in self.vertices(rotation.as_radians()) {
min = min.min(vertex);
max = max.max(vertex);
}
Aabb2d {
min: min + translation,
max: max + translation,
}
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.circumcircle.radius)
}
}
impl Bounded2d for Capsule2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let segment = Segment2d {
direction: rotation * Dir2::Y,
half_length: self.half_length,
};
let (a, b) = (segment.point1(), segment.point2());
let min = a.min(b) - Vec2::splat(self.radius);
let max = a.max(b) + Vec2::splat(self.radius);
Aabb2d {
min: min + translation,
max: max + translation,
}
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.radius + self.half_length)
}
}
#[cfg(test)]
mod tests {
use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_4, FRAC_PI_6, TAU};
use approx::assert_abs_diff_eq;
use glam::Vec2;
use crate::{
bounding::Bounded2d,
primitives::{
Arc2d, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Line2d, Plane2d,
Polygon, Polyline2d, Rectangle, RegularPolygon, Rhombus, Segment2d, Triangle2d,
},
Dir2,
};
#[test]
fn circle() {
let circle = Circle { radius: 1.0 };
let translation = Vec2::new(2.0, 1.0);
let aabb = circle.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
let bounding_circle = circle.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
}
#[test]
fn arc_and_segment() {
struct TestCase {
name: &'static str,
arc: Arc2d,
translation: Vec2,
rotation: f32,
aabb_min: Vec2,
aabb_max: Vec2,
bounding_circle_center: Vec2,
bounding_circle_radius: f32,
}
let apothem = f32::sqrt(3.0) / 2.0;
let tests = [
TestCase {
name: "1/6th circle untransformed",
arc: Arc2d::from_radians(1.0, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-0.5, apothem),
aabb_max: Vec2::new(0.5, 1.0),
bounding_circle_center: Vec2::new(0.0, apothem),
bounding_circle_radius: 0.5,
},
TestCase {
name: "1/6th circle with radius 0.5",
arc: Arc2d::from_radians(0.5, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-0.25, apothem / 2.0),
aabb_max: Vec2::new(0.25, 0.5),
bounding_circle_center: Vec2::new(0.0, apothem / 2.0),
bounding_circle_radius: 0.25,
},
TestCase {
name: "1/6th circle with radius 2.0",
arc: Arc2d::from_radians(2.0, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-1.0, 2.0 * apothem),
aabb_max: Vec2::new(1.0, 2.0),
bounding_circle_center: Vec2::new(0.0, 2.0 * apothem),
bounding_circle_radius: 1.0,
},
TestCase {
name: "1/6th circle translated",
arc: Arc2d::from_radians(1.0, FRAC_PI_3),
translation: Vec2::new(2.0, 3.0),
rotation: 0.0,
aabb_min: Vec2::new(1.5, 3.0 + apothem),
aabb_max: Vec2::new(2.5, 4.0),
bounding_circle_center: Vec2::new(2.0, 3.0 + apothem),
bounding_circle_radius: 0.5,
},
TestCase {
name: "1/6th circle rotated",
arc: Arc2d::from_radians(1.0, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: FRAC_PI_6,
aabb_min: Vec2::new(-apothem, 0.5),
aabb_max: Vec2::new(0.0, 1.0),
bounding_circle_center: Vec2::new(-apothem / 2.0, apothem.powi(2)),
bounding_circle_radius: 0.5,
},
TestCase {
name: "1/4er circle rotated to be axis-aligned",
arc: Arc2d::from_radians(1.0, FRAC_PI_2),
translation: Vec2::ZERO,
rotation: -FRAC_PI_4,
aabb_min: Vec2::ZERO,
aabb_max: Vec2::splat(1.0),
bounding_circle_center: Vec2::splat(0.5),
bounding_circle_radius: f32::sqrt(2.0) / 2.0,
},
TestCase {
name: "5/6th circle untransformed",
arc: Arc2d::from_radians(1.0, 5.0 * FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-1.0, -apothem),
aabb_max: Vec2::new(1.0, 1.0),
bounding_circle_center: Vec2::ZERO,
bounding_circle_radius: 1.0,
},
TestCase {
name: "5/6th circle translated",
arc: Arc2d::from_radians(1.0, 5.0 * FRAC_PI_3),
translation: Vec2::new(2.0, 3.0),
rotation: 0.0,
aabb_min: Vec2::new(1.0, 3.0 - apothem),
aabb_max: Vec2::new(3.0, 4.0),
bounding_circle_center: Vec2::new(2.0, 3.0),
bounding_circle_radius: 1.0,
},
TestCase {
name: "5/6th circle rotated",
arc: Arc2d::from_radians(1.0, 5.0 * FRAC_PI_3),
translation: Vec2::ZERO,
rotation: FRAC_PI_6,
aabb_min: Vec2::new(-1.0, -1.0),
aabb_max: Vec2::new(1.0, 1.0),
bounding_circle_center: Vec2::ZERO,
bounding_circle_radius: 1.0,
},
];
for test in tests {
println!("subtest case: {}", test.name);
let segment: CircularSegment = test.arc.into();
let arc_aabb = test.arc.aabb_2d(test.translation, test.rotation);
assert_abs_diff_eq!(test.aabb_min, arc_aabb.min);
assert_abs_diff_eq!(test.aabb_max, arc_aabb.max);
let segment_aabb = segment.aabb_2d(test.translation, test.rotation);
assert_abs_diff_eq!(test.aabb_min, segment_aabb.min);
assert_abs_diff_eq!(test.aabb_max, segment_aabb.max);
let arc_bounding_circle = test.arc.bounding_circle(test.translation, test.rotation);
assert_abs_diff_eq!(test.bounding_circle_center, arc_bounding_circle.center);
assert_abs_diff_eq!(test.bounding_circle_radius, arc_bounding_circle.radius());
let segment_bounding_circle = segment.bounding_circle(test.translation, test.rotation);
assert_abs_diff_eq!(test.bounding_circle_center, segment_bounding_circle.center);
assert_abs_diff_eq!(
test.bounding_circle_radius,
segment_bounding_circle.radius()
);
}
}
#[test]
fn circular_sector() {
struct TestCase {
name: &'static str,
arc: Arc2d,
translation: Vec2,
rotation: f32,
aabb_min: Vec2,
aabb_max: Vec2,
bounding_circle_center: Vec2,
bounding_circle_radius: f32,
}
let apothem = f32::sqrt(3.0) / 2.0;
let inv_sqrt_3 = f32::sqrt(3.0).recip();
let tests = [
TestCase {
name: "1/3rd circle",
arc: Arc2d::from_radians(1.0, TAU / 3.0),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-apothem, 0.0),
aabb_max: Vec2::new(apothem, 1.0),
bounding_circle_center: Vec2::new(0.0, 0.5),
bounding_circle_radius: apothem,
},
TestCase {
name: "1/6th circle untransformed",
arc: Arc2d::from_radians(1.0, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-0.5, 0.0),
aabb_max: Vec2::new(0.5, 1.0),
bounding_circle_center: Vec2::new(0.0, inv_sqrt_3),
bounding_circle_radius: inv_sqrt_3,
},
TestCase {
name: "1/6th circle with radius 0.5",
arc: Arc2d::from_radians(0.5, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-0.25, 0.0),
aabb_max: Vec2::new(0.25, 0.5),
bounding_circle_center: Vec2::new(0.0, inv_sqrt_3 / 2.0),
bounding_circle_radius: inv_sqrt_3 / 2.0,
},
TestCase {
name: "1/6th circle with radius 2.0",
arc: Arc2d::from_radians(2.0, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-1.0, 0.0),
aabb_max: Vec2::new(1.0, 2.0),
bounding_circle_center: Vec2::new(0.0, 2.0 * inv_sqrt_3),
bounding_circle_radius: 2.0 * inv_sqrt_3,
},
TestCase {
name: "1/6th circle translated",
arc: Arc2d::from_radians(1.0, FRAC_PI_3),
translation: Vec2::new(2.0, 3.0),
rotation: 0.0,
aabb_min: Vec2::new(1.5, 3.0),
aabb_max: Vec2::new(2.5, 4.0),
bounding_circle_center: Vec2::new(2.0, 3.0 + inv_sqrt_3),
bounding_circle_radius: inv_sqrt_3,
},
TestCase {
name: "1/6th circle rotated",
arc: Arc2d::from_radians(1.0, FRAC_PI_3),
translation: Vec2::ZERO,
rotation: FRAC_PI_6,
aabb_min: Vec2::new(-apothem, 0.0),
aabb_max: Vec2::new(0.0, 1.0),
bounding_circle_center: Vec2::new(-inv_sqrt_3 / 2.0, 0.5),
bounding_circle_radius: inv_sqrt_3,
},
TestCase {
name: "1/4er circle rotated to be axis-aligned",
arc: Arc2d::from_radians(1.0, FRAC_PI_2),
translation: Vec2::ZERO,
rotation: -FRAC_PI_4,
aabb_min: Vec2::ZERO,
aabb_max: Vec2::splat(1.0),
bounding_circle_center: Vec2::splat(0.5),
bounding_circle_radius: f32::sqrt(2.0) / 2.0,
},
TestCase {
name: "5/6th circle untransformed",
arc: Arc2d::from_radians(1.0, 5.0 * FRAC_PI_3),
translation: Vec2::ZERO,
rotation: 0.0,
aabb_min: Vec2::new(-1.0, -apothem),
aabb_max: Vec2::new(1.0, 1.0),
bounding_circle_center: Vec2::ZERO,
bounding_circle_radius: 1.0,
},
TestCase {
name: "5/6th circle translated",
arc: Arc2d::from_radians(1.0, 5.0 * FRAC_PI_3),
translation: Vec2::new(2.0, 3.0),
rotation: 0.0,
aabb_min: Vec2::new(1.0, 3.0 - apothem),
aabb_max: Vec2::new(3.0, 4.0),
bounding_circle_center: Vec2::new(2.0, 3.0),
bounding_circle_radius: 1.0,
},
TestCase {
name: "5/6th circle rotated",
arc: Arc2d::from_radians(1.0, 5.0 * FRAC_PI_3),
translation: Vec2::ZERO,
rotation: FRAC_PI_6,
aabb_min: Vec2::new(-1.0, -1.0),
aabb_max: Vec2::new(1.0, 1.0),
bounding_circle_center: Vec2::ZERO,
bounding_circle_radius: 1.0,
},
];
for test in tests {
println!("subtest case: {}", test.name);
let sector: CircularSector = test.arc.into();
let aabb = sector.aabb_2d(test.translation, test.rotation);
assert_abs_diff_eq!(test.aabb_min, aabb.min);
assert_abs_diff_eq!(test.aabb_max, aabb.max);
let bounding_circle = sector.bounding_circle(test.translation, test.rotation);
assert_abs_diff_eq!(test.bounding_circle_center, bounding_circle.center);
assert_abs_diff_eq!(test.bounding_circle_radius, bounding_circle.radius());
}
}
#[test]
fn ellipse() {
let ellipse = Ellipse::new(1.0, 0.5);
let translation = Vec2::new(2.0, 1.0);
let aabb = ellipse.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, Vec2::new(1.0, 0.5));
assert_eq!(aabb.max, Vec2::new(3.0, 1.5));
let bounding_circle = ellipse.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
}
#[test]
fn rhombus() {
let rhombus = Rhombus::new(2.0, 1.0);
let translation = Vec2::new(2.0, 1.0);
let aabb = rhombus.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
assert_eq!(aabb.min, Vec2::new(1.2928932, 0.29289323));
assert_eq!(aabb.max, Vec2::new(2.7071068, 1.7071068));
let bounding_circle = rhombus.bounding_circle(translation, std::f32::consts::FRAC_PI_4);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
let rhombus = Rhombus::new(0.0, 0.0);
let translation = Vec2::new(0.0, 0.0);
let aabb = rhombus.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
assert_eq!(aabb.min, Vec2::new(0.0, 0.0));
assert_eq!(aabb.max, Vec2::new(0.0, 0.0));
let bounding_circle = rhombus.bounding_circle(translation, std::f32::consts::FRAC_PI_4);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 0.0);
}
#[test]
fn plane() {
let translation = Vec2::new(2.0, 1.0);
let aabb1 = Plane2d::new(Vec2::X).aabb_2d(translation, 0.0);
assert_eq!(aabb1.min, Vec2::new(2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec2::new(2.0, f32::MAX / 2.0));
let aabb2 = Plane2d::new(Vec2::Y).aabb_2d(translation, 0.0);
assert_eq!(aabb2.min, Vec2::new(-f32::MAX / 2.0, 1.0));
assert_eq!(aabb2.max, Vec2::new(f32::MAX / 2.0, 1.0));
let aabb3 = Plane2d::new(Vec2::ONE).aabb_2d(translation, 0.0);
assert_eq!(aabb3.min, Vec2::new(-f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec2::new(f32::MAX / 2.0, f32::MAX / 2.0));
let bounding_circle = Plane2d::new(Vec2::Y).bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), f32::MAX / 2.0);
}
#[test]
fn line() {
let translation = Vec2::new(2.0, 1.0);
let aabb1 = Line2d { direction: Dir2::Y }.aabb_2d(translation, 0.0);
assert_eq!(aabb1.min, Vec2::new(2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec2::new(2.0, f32::MAX / 2.0));
let aabb2 = Line2d { direction: Dir2::X }.aabb_2d(translation, 0.0);
assert_eq!(aabb2.min, Vec2::new(-f32::MAX / 2.0, 1.0));
assert_eq!(aabb2.max, Vec2::new(f32::MAX / 2.0, 1.0));
let aabb3 = Line2d {
direction: Dir2::from_xy(1.0, 1.0).unwrap(),
}
.aabb_2d(translation, 0.0);
assert_eq!(aabb3.min, Vec2::new(-f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec2::new(f32::MAX / 2.0, f32::MAX / 2.0));
let bounding_circle = Line2d { direction: Dir2::Y }.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), f32::MAX / 2.0);
}
#[test]
fn segment() {
let translation = Vec2::new(2.0, 1.0);
let segment = Segment2d::from_points(Vec2::new(-1.0, -0.5), Vec2::new(1.0, 0.5)).0;
let aabb = segment.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, Vec2::new(1.0, 0.5));
assert_eq!(aabb.max, Vec2::new(3.0, 1.5));
let bounding_circle = segment.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0_f32.hypot(0.5));
}
#[test]
fn polyline() {
let polyline = Polyline2d::<4>::new([
Vec2::ONE,
Vec2::new(-1.0, 1.0),
Vec2::NEG_ONE,
Vec2::new(1.0, -1.0),
]);
let translation = Vec2::new(2.0, 1.0);
let aabb = polyline.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
let bounding_circle = polyline.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), std::f32::consts::SQRT_2);
}
#[test]
fn acute_triangle() {
let acute_triangle =
Triangle2d::new(Vec2::new(0.0, 1.0), Vec2::NEG_ONE, Vec2::new(1.0, -1.0));
let translation = Vec2::new(2.0, 1.0);
let aabb = acute_triangle.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
let (Circle { radius }, circumcenter) = acute_triangle.circumcircle();
let bounding_circle = acute_triangle.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, circumcenter + translation);
assert_eq!(bounding_circle.radius(), radius);
}
#[test]
fn obtuse_triangle() {
let obtuse_triangle = Triangle2d::new(
Vec2::new(0.0, 1.0),
Vec2::new(-10.0, -1.0),
Vec2::new(10.0, -1.0),
);
let translation = Vec2::new(2.0, 1.0);
let aabb = obtuse_triangle.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, Vec2::new(-8.0, 0.0));
assert_eq!(aabb.max, Vec2::new(12.0, 2.0));
let bounding_circle = obtuse_triangle.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation - Vec2::Y);
assert_eq!(bounding_circle.radius(), 10.0);
}
#[test]
fn rectangle() {
let rectangle = Rectangle::new(2.0, 1.0);
let translation = Vec2::new(2.0, 1.0);
let aabb = rectangle.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
let expected_half_size = Vec2::splat(1.0606601);
assert_eq!(aabb.min, translation - expected_half_size);
assert_eq!(aabb.max, translation + expected_half_size);
let bounding_circle = rectangle.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0_f32.hypot(0.5));
}
#[test]
fn polygon() {
let polygon = Polygon::<4>::new([
Vec2::ONE,
Vec2::new(-1.0, 1.0),
Vec2::NEG_ONE,
Vec2::new(1.0, -1.0),
]);
let translation = Vec2::new(2.0, 1.0);
let aabb = polygon.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
let bounding_circle = polygon.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), std::f32::consts::SQRT_2);
}
#[test]
fn regular_polygon() {
let regular_polygon = RegularPolygon::new(1.0, 5);
let translation = Vec2::new(2.0, 1.0);
let aabb = regular_polygon.aabb_2d(translation, 0.0);
assert!((aabb.min - (translation - Vec2::new(0.9510565, 0.8090169))).length() < 1e-6);
assert!((aabb.max - (translation + Vec2::new(0.9510565, 1.0))).length() < 1e-6);
let bounding_circle = regular_polygon.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
}
#[test]
fn capsule() {
let capsule = Capsule2d::new(0.5, 2.0);
let translation = Vec2::new(2.0, 1.0);
let aabb = capsule.aabb_2d(translation, 0.0);
assert_eq!(aabb.min, translation - Vec2::new(0.5, 1.5));
assert_eq!(aabb.max, translation + Vec2::new(0.5, 1.5));
let bounding_circle = capsule.bounding_circle(translation, 0.0);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.5);
}
}