use crate::geometry::geo_enums::{GeoPosition, GeoRelation};
use crate::geometry::geo_traits::{
AlmostCollidesWith, CollidesWith, DistanceTo, SeparationDistance,
};
use crate::geometry::primitives::Edge;
use crate::geometry::primitives::Point;
use crate::util::FPA;
use anyhow::Result;
use anyhow::ensure;
use ordered_float::OrderedFloat;
#[derive(Clone, Debug, PartialEq, Copy)]
pub struct Rect {
pub x_min: f32,
pub y_min: f32,
pub x_max: f32,
pub y_max: f32,
}
impl Rect {
pub fn try_new(x_min: f32, y_min: f32, x_max: f32, y_max: f32) -> Result<Self> {
ensure!(
x_min < x_max && y_min < y_max,
"invalid rectangle, x_min: {x_min}, x_max: {x_max}, y_min: {y_min}, y_max: {y_max}"
);
Ok(Rect {
x_min,
y_min,
x_max,
y_max,
})
}
pub fn from_diagonal_corners(c1: Point, c2: Point) -> Result<Self> {
let x_min = f32::min(c1.x(), c2.x());
let y_min = f32::min(c1.y(), c2.y());
let x_max = f32::max(c1.x(), c2.x());
let y_max = f32::max(c1.y(), c2.y());
Rect::try_new(x_min, y_min, x_max, y_max)
}
#[inline(always)]
pub fn relation_to(&self, other: Rect) -> GeoRelation {
if !self.collides_with(&other) {
return GeoRelation::Disjoint;
}
if self.x_min <= other.x_min
&& self.y_min <= other.y_min
&& self.x_max >= other.x_max
&& self.y_max >= other.y_max
{
return GeoRelation::Surrounding;
}
if self.x_min >= other.x_min
&& self.y_min >= other.y_min
&& self.x_max <= other.x_max
&& self.y_max <= other.y_max
{
return GeoRelation::Enclosed;
}
GeoRelation::Intersecting
}
#[inline(always)]
pub fn almost_relation_to(&self, other: Rect) -> GeoRelation {
if !self.almost_collides_with(&other) {
return GeoRelation::Disjoint;
}
if FPA::from(self.x_min) <= FPA::from(other.x_min)
&& FPA::from(self.y_min) <= FPA::from(other.y_min)
&& FPA::from(self.x_max) >= FPA::from(other.x_max)
&& FPA::from(self.y_max) >= FPA::from(other.y_max)
{
return GeoRelation::Surrounding;
}
if FPA::from(self.x_min) >= FPA::from(other.x_min)
&& FPA::from(self.y_min) >= FPA::from(other.y_min)
&& FPA::from(self.x_max) <= FPA::from(other.x_max)
&& FPA::from(self.y_max) <= FPA::from(other.y_max)
{
return GeoRelation::Enclosed;
}
GeoRelation::Intersecting
}
pub fn inflate_to_square(&self) -> Rect {
let width = self.x_max - self.x_min;
let height = self.y_max - self.y_min;
let mut dx = 0.0;
let mut dy = 0.0;
if height < width {
dy = (width - height) / 2.0;
} else if width < height {
dx = (height - width) / 2.0;
}
Rect {
x_min: self.x_min - dx,
y_min: self.y_min - dy,
x_max: self.x_max + dx,
y_max: self.y_max + dy,
}
}
pub fn scale(self, factor: f32) -> Self {
let dx = (self.x_max - self.x_min) * (factor - 1.0) / 2.0;
let dy = (self.y_max - self.y_min) * (factor - 1.0) / 2.0;
self.resize_by(dx, dy)
.expect("scaling should not lead to invalid rectangle")
}
pub fn resize_by(mut self, dx: f32, dy: f32) -> Option<Self> {
self.x_min -= dx;
self.y_min -= dy;
self.x_max += dx;
self.y_max += dy;
if self.x_min < self.x_max && self.y_min < self.y_max {
Some(self)
} else {
None
}
}
pub const QUADRANT_NEIGHBOR_LAYOUT: [[usize; 2]; 4] = [[1, 3], [0, 2], [1, 3], [0, 2]];
pub fn quadrants(&self) -> [Self; 4] {
let mid = self.centroid();
let corners = self.corners();
let q1 = Rect::from_diagonal_corners(corners[0], mid).unwrap();
let q2 = Rect::from_diagonal_corners(corners[1], mid).unwrap();
let q3 = Rect::from_diagonal_corners(corners[2], mid).unwrap();
let q4 = Rect::from_diagonal_corners(corners[3], mid).unwrap();
[q1, q2, q3, q4]
}
pub fn corners(&self) -> [Point; 4] {
[
Point(self.x_max, self.y_max),
Point(self.x_min, self.y_max),
Point(self.x_min, self.y_min),
Point(self.x_max, self.y_min),
]
}
pub fn edges(&self) -> [Edge; 4] {
let c = self.corners();
[
Edge {
start: c[0],
end: c[1],
},
Edge {
start: c[1],
end: c[2],
},
Edge {
start: c[2],
end: c[3],
},
Edge {
start: c[3],
end: c[0],
},
]
}
pub fn width(&self) -> f32 {
self.x_max - self.x_min
}
pub fn height(&self) -> f32 {
self.y_max - self.y_min
}
pub fn intersection(a: Rect, b: Rect) -> Option<Rect> {
let x_min = f32::max(a.x_min, b.x_min);
let y_min = f32::max(a.y_min, b.y_min);
let x_max = f32::min(a.x_max, b.x_max);
let y_max = f32::min(a.y_max, b.y_max);
if x_min < x_max && y_min < y_max {
Some(Rect {
x_min,
y_min,
x_max,
y_max,
})
} else {
None
}
}
pub fn bounding_rect(a: Rect, b: Rect) -> Rect {
let x_min = f32::min(a.x_min, b.x_min);
let y_min = f32::min(a.y_min, b.y_min);
let x_max = f32::max(a.x_max, b.x_max);
let y_max = f32::max(a.y_max, b.y_max);
Rect {
x_min,
y_min,
x_max,
y_max,
}
}
pub fn centroid(&self) -> Point {
Point(
(self.x_min + self.x_max) / 2.0,
(self.y_min + self.y_max) / 2.0,
)
}
pub fn area(&self) -> f32 {
(self.x_max - self.x_min) * (self.y_max - self.y_min)
}
pub fn diameter(&self) -> f32 {
let dx = self.x_max - self.x_min;
let dy = self.y_max - self.y_min;
(dx.powi(2) + dy.powi(2)).sqrt()
}
}
impl CollidesWith<Rect> for Rect {
#[inline(always)]
fn collides_with(&self, other: &Rect) -> bool {
f32::max(self.x_min, other.x_min) <= f32::min(self.x_max, other.x_max)
&& f32::max(self.y_min, other.y_min) <= f32::min(self.y_max, other.y_max)
}
}
impl AlmostCollidesWith<Rect> for Rect {
#[inline(always)]
fn almost_collides_with(&self, other: &Rect) -> bool {
FPA(f32::max(self.x_min, other.x_min)) <= FPA(f32::min(self.x_max, other.x_max))
&& FPA(f32::max(self.y_min, other.y_min)) <= FPA(f32::min(self.y_max, other.y_max))
}
}
impl CollidesWith<Point> for Rect {
#[inline(always)]
fn collides_with(&self, point: &Point) -> bool {
let Point(x, y) = *point;
x >= self.x_min && x <= self.x_max && y >= self.y_min && y <= self.y_max
}
}
impl AlmostCollidesWith<Point> for Rect {
#[inline(always)]
fn almost_collides_with(&self, point: &Point) -> bool {
let (x, y) = (*point).into();
FPA(x) >= FPA(self.x_min)
&& FPA(x) <= FPA(self.x_max)
&& FPA(y) >= FPA(self.y_min)
&& FPA(y) <= FPA(self.y_max)
}
}
impl CollidesWith<Edge> for Rect {
#[inline(always)]
fn collides_with(&self, edge: &Edge) -> bool {
let e_x_min = edge.x_min();
let e_x_max = edge.x_max();
let e_y_min = edge.y_min();
let e_y_max = edge.y_max();
let x_no_overlap = e_x_min.max(self.x_min) > e_x_max.min(self.x_max);
let y_no_overlap = e_y_min.max(self.y_min) > e_y_max.min(self.y_max);
if x_no_overlap || y_no_overlap {
return false; }
if self.collides_with(&edge.start) || self.collides_with(&edge.end) {
return true;
}
let corner_sides = self.corners().map(|corner| {
let Point(p_x, p_y) = corner;
let Point(s_x, s_y) = edge.start;
let Point(e_x, e_y) = edge.end;
(p_x - s_x) * (e_y - s_y) - (p_y - s_y) * (e_x - s_x)
});
if corner_sides.iter().all(|v| *v > 0.0) || corner_sides.iter().all(|v| *v < 0.0) {
return false;
}
self.edges()
.iter()
.any(|rect_edge| edge.collides_with(rect_edge))
}
}
impl DistanceTo<Point> for Rect {
#[inline(always)]
fn distance_to(&self, point: &Point) -> f32 {
self.sq_distance_to(point).sqrt()
}
#[inline(always)]
fn sq_distance_to(&self, point: &Point) -> f32 {
let Point(x, y) = *point;
let mut distance: f32 = 0.0;
if x < self.x_min {
distance += (x - self.x_min).powi(2);
} else if x > self.x_max {
distance += (x - self.x_max).powi(2);
}
if y < self.y_min {
distance += (y - self.y_min).powi(2);
} else if y > self.y_max {
distance += (y - self.y_max).powi(2);
}
distance.abs()
}
}
impl SeparationDistance<Point> for Rect {
#[inline(always)]
fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
let (position, sq_distance) = self.sq_separation_distance(point);
(position, sq_distance.sqrt())
}
#[inline(always)]
fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
match self.collides_with(point) {
false => (GeoPosition::Exterior, self.sq_distance_to(point)),
true => {
let Point(x, y) = *point;
let min_distance = [
(x - self.x_min).abs(),
(x - self.x_max).abs(),
(y - self.y_min).abs(),
(y - self.y_max).abs(),
]
.into_iter()
.min_by_key(|&d| OrderedFloat(d))
.unwrap();
(GeoPosition::Interior, min_distance.powi(2))
}
}
}
}