use glam::Vec2;
use super::Support;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum ShapeData {
Circle(Circle),
Rectangle(Rectangle),
Segment(Segment),
}
impl Support<Vec2> for ShapeData {
fn support(&self, direction: Vec2) -> Vec2 {
match self {
ShapeData::Circle(circle) => circle.support(direction),
ShapeData::Rectangle(rect) => rect.support(direction),
ShapeData::Segment(segment) => segment.support(direction),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Circle {
radius: f32,
}
impl Circle {
#[inline]
#[must_use]
pub fn new(radius: f32) -> Self {
Self { radius }
}
#[must_use]
pub fn radius(&self) -> f32 {
self.radius
}
}
impl From<Circle> for ShapeData {
#[inline]
fn from(circle: Circle) -> Self {
Self::Circle(circle)
}
}
impl Support<Vec2> for Circle {
fn support(&self, direction: Vec2) -> Vec2 {
let point = direction.clamp_length(self.radius, self.radius);
if point.is_nan() {
Vec2::new(self.radius, 0.0)
} else {
point
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Rectangle {
half_extents: Vec2,
}
impl Rectangle {
#[inline]
#[must_use]
pub fn new(width: f32, height: f32) -> Self {
Self {
half_extents: Vec2::new(width * 0.5, height * 0.5).abs(),
}
}
#[must_use]
pub fn half_extents(&self) -> [f32; 2] {
self.half_extents.into()
}
}
impl From<Rectangle> for ShapeData {
#[inline]
fn from(rect: Rectangle) -> Self {
Self::Rectangle(rect)
}
}
impl Support<Vec2> for Rectangle {
fn support(&self, direction: Vec2) -> Vec2 {
let mut support = self.half_extents;
if direction.x < 0.0 {
support.x = -support.x;
}
if direction.y < 0.0 {
support.y = -support.y;
}
support
}
}
#[derive(Debug, Clone)]
pub struct Segment {
p1: Vec2,
p2: Vec2,
}
impl Segment {
pub fn new(p1: impl Into<[f32; 2]>, p2: impl Into<[f32; 2]>) -> Self {
Self {
p1: p1.into().into(),
p2: p2.into().into(),
}
}
}
impl From<Segment> for ShapeData {
fn from(segment: Segment) -> Self {
Self::Segment(segment)
}
}
impl Support<Vec2> for Segment {
fn support(&self, direction: Vec2) -> Vec2 {
if self.p1.dot(direction) > self.p2.dot(direction) {
self.p1
} else {
self.p2
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_ulps_eq;
#[test]
fn circle() {
assert_eq!(Circle::new(2.0).support(Vec2::X), Vec2::X * 2.0);
assert_eq!(Circle::new(3.0).support(Vec2::Y), Vec2::Y * 3.0);
assert_eq!(Circle::new(0.0).support(Vec2::X), Vec2::ZERO);
}
#[test]
fn circle_with_invalid_direction() {
assert_ulps_eq!(
Circle::new(1.)
.support(Vec2::splat(f32::NAN))
.length_squared(),
1.0
);
}
#[test]
fn rectangle() {
let rectangle = Rectangle::new(6.0, 4.0);
assert_eq!(rectangle.support(Vec2::new(1., 1.)), Vec2::new(3., 2.));
assert_eq!(rectangle.support(Vec2::new(-1., 1.)), Vec2::new(-3., 2.));
assert_eq!(rectangle.support(Vec2::new(1., -1.)), Vec2::new(3., -2.));
assert_eq!(rectangle.support(Vec2::new(-1., -1.)), Vec2::new(-3., -2.));
}
#[test]
fn rectangle_with_invalid_direction() {
assert_ulps_eq!(
Rectangle::new(2., 2.)
.support(Vec2::splat(f32::NAN))
.length_squared(),
2.0
);
}
#[test]
fn line() {
let segment = Segment::new(Vec2::ZERO, Vec2::X * 2.0);
assert_eq!(segment.support(Vec2::X), Vec2::X * 2.0);
assert_eq!(segment.support(Vec2::X + Vec2::X), Vec2::X * 2.0);
assert_eq!(segment.support(-Vec2::X), Vec2::ZERO);
assert_eq!(segment.support(-Vec2::X - Vec2::X), Vec2::ZERO);
}
}