use crate::geo::Point;
use geo::Rect;
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BoundingBox2D {
pub rect: Rect,
}
impl BoundingBox2D {
pub fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
Self {
rect: Rect::new(
geo::coord! { x: min_x, y: min_y },
geo::coord! { x: max_x, y: max_y },
),
}
}
pub fn from_rect(rect: Rect) -> Self {
Self { rect }
}
pub fn min_x(&self) -> f64 {
self.rect.min().x
}
pub fn min_y(&self) -> f64 {
self.rect.min().y
}
pub fn max_x(&self) -> f64 {
self.rect.max().x
}
pub fn max_y(&self) -> f64 {
self.rect.max().y
}
pub fn center(&self) -> Point {
Point::new(
(self.min_x() + self.max_x()) / 2.0,
(self.min_y() + self.max_y()) / 2.0,
)
}
pub fn width(&self) -> f64 {
self.max_x() - self.min_x()
}
pub fn height(&self) -> f64 {
self.max_y() - self.min_y()
}
pub fn contains_point(&self, point: &Point) -> bool {
point.x() >= self.min_x()
&& point.x() <= self.max_x()
&& point.y() >= self.min_y()
&& point.y() <= self.max_y()
}
pub fn intersects(&self, other: &BoundingBox2D) -> bool {
!(self.max_x() < other.min_x()
|| self.min_x() > other.max_x()
|| self.max_y() < other.min_y()
|| self.min_y() > other.max_y())
}
pub fn expand(&self, amount: f64) -> Self {
Self::new(
self.min_x() - amount,
self.min_y() - amount,
self.max_x() + amount,
self.max_y() + amount,
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BoundingBox3D {
pub min_x: f64,
pub min_y: f64,
pub min_z: f64,
pub max_x: f64,
pub max_y: f64,
pub max_z: f64,
}
impl BoundingBox3D {
#[must_use]
pub fn new(min_x: f64, min_y: f64, min_z: f64, max_x: f64, max_y: f64, max_z: f64) -> Self {
let (min_x, max_x) = if min_x <= max_x {
(min_x, max_x)
} else {
(max_x, min_x)
};
let (min_y, max_y) = if min_y <= max_y {
(min_y, max_y)
} else {
(max_y, min_y)
};
let (min_z, max_z) = if min_z <= max_z {
(min_z, max_z)
} else {
(max_z, min_z)
};
Self {
min_x,
min_y,
min_z,
max_x,
max_y,
max_z,
}
}
#[inline]
#[must_use]
pub fn center(&self) -> (f64, f64, f64) {
(
(self.min_x + self.max_x) / 2.0,
(self.min_y + self.max_y) / 2.0,
(self.min_z + self.max_z) / 2.0,
)
}
#[inline]
#[must_use]
pub fn width(&self) -> f64 {
self.max_x - self.min_x
}
#[inline]
#[must_use]
pub fn height(&self) -> f64 {
self.max_y - self.min_y
}
#[inline]
#[must_use]
pub fn depth(&self) -> f64 {
self.max_z - self.min_z
}
#[inline]
#[must_use]
pub fn volume(&self) -> f64 {
self.width() * self.height() * self.depth()
}
#[inline]
#[must_use]
pub fn contains_point(&self, x: f64, y: f64, z: f64) -> bool {
x >= self.min_x
&& x <= self.max_x
&& y >= self.min_y
&& y <= self.max_y
&& z >= self.min_z
&& z <= self.max_z
}
#[inline]
#[must_use]
pub fn intersects(&self, other: &BoundingBox3D) -> bool {
!(self.max_x < other.min_x
|| self.min_x > other.max_x
|| self.max_y < other.min_y
|| self.min_y > other.max_y
|| self.max_z < other.min_z
|| self.min_z > other.max_z)
}
#[must_use]
pub fn expand(&self, amount: f64) -> Self {
Self::new(
self.min_x - amount,
self.min_y - amount,
self.min_z - amount,
self.max_x + amount,
self.max_y + amount,
self.max_z + amount,
)
}
#[inline]
#[must_use]
pub fn to_2d(&self) -> BoundingBox2D {
BoundingBox2D::new(self.min_x, self.min_y, self.max_x, self.max_y)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemporalBoundingBox2D {
pub bbox: BoundingBox2D,
pub timestamp: SystemTime,
}
impl TemporalBoundingBox2D {
pub fn new(bbox: BoundingBox2D, timestamp: SystemTime) -> Self {
Self { bbox, timestamp }
}
pub fn bbox(&self) -> &BoundingBox2D {
&self.bbox
}
pub fn timestamp(&self) -> &SystemTime {
&self.timestamp
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemporalBoundingBox3D {
pub bbox: BoundingBox3D,
pub timestamp: SystemTime,
}
impl TemporalBoundingBox3D {
pub fn new(bbox: BoundingBox3D, timestamp: SystemTime) -> Self {
Self { bbox, timestamp }
}
pub fn bbox(&self) -> &BoundingBox3D {
&self.bbox
}
pub fn timestamp(&self) -> &SystemTime {
&self.timestamp
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bbox3d_new_normalizes_swapped_corners() {
let bbox = BoundingBox3D::new(10.0, 20.0, 30.0, 0.0, 5.0, 15.0);
assert_eq!((bbox.min_x, bbox.max_x), (0.0, 10.0));
assert_eq!((bbox.min_y, bbox.max_y), (5.0, 20.0));
assert_eq!((bbox.min_z, bbox.max_z), (15.0, 30.0));
assert!(bbox.width() >= 0.0 && bbox.height() >= 0.0 && bbox.depth() >= 0.0);
assert!(bbox.contains_point(5.0, 10.0, 20.0));
}
#[test]
fn test_bbox2d_creation() {
let bbox = BoundingBox2D::new(-74.0, 40.7, -73.9, 40.8);
assert_eq!(bbox.min_x(), -74.0);
assert_eq!(bbox.min_y(), 40.7);
assert_eq!(bbox.max_x(), -73.9);
assert_eq!(bbox.max_y(), 40.8);
}
#[test]
fn test_bbox2d_dimensions() {
let bbox = BoundingBox2D::new(0.0, 0.0, 10.0, 5.0);
assert_eq!(bbox.width(), 10.0);
assert_eq!(bbox.height(), 5.0);
}
#[test]
fn test_bbox2d_center() {
let bbox = BoundingBox2D::new(0.0, 0.0, 10.0, 10.0);
let center = bbox.center();
assert_eq!(center.x(), 5.0);
assert_eq!(center.y(), 5.0);
}
#[test]
fn test_bbox2d_contains() {
let bbox = BoundingBox2D::new(0.0, 0.0, 10.0, 10.0);
assert!(bbox.contains_point(&Point::new(5.0, 5.0)));
assert!(bbox.contains_point(&Point::new(0.0, 0.0)));
assert!(bbox.contains_point(&Point::new(10.0, 10.0)));
assert!(!bbox.contains_point(&Point::new(-1.0, 5.0)));
assert!(!bbox.contains_point(&Point::new(11.0, 5.0)));
}
#[test]
fn test_bbox2d_intersects() {
let bbox1 = BoundingBox2D::new(0.0, 0.0, 10.0, 10.0);
let bbox2 = BoundingBox2D::new(5.0, 5.0, 15.0, 15.0);
let bbox3 = BoundingBox2D::new(20.0, 20.0, 30.0, 30.0);
assert!(bbox1.intersects(&bbox2));
assert!(bbox2.intersects(&bbox1));
assert!(!bbox1.intersects(&bbox3));
assert!(!bbox3.intersects(&bbox1));
}
#[test]
fn test_bbox2d_expand() {
let bbox = BoundingBox2D::new(0.0, 0.0, 10.0, 10.0);
let expanded = bbox.expand(5.0);
assert_eq!(expanded.min_x(), -5.0);
assert_eq!(expanded.min_y(), -5.0);
assert_eq!(expanded.max_x(), 15.0);
assert_eq!(expanded.max_y(), 15.0);
}
#[test]
fn test_bbox3d_creation() {
let bbox = BoundingBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
assert_eq!(bbox.min_x, 0.0);
assert_eq!(bbox.min_y, 0.0);
assert_eq!(bbox.min_z, 0.0);
assert_eq!(bbox.max_x, 10.0);
assert_eq!(bbox.max_y, 10.0);
assert_eq!(bbox.max_z, 10.0);
}
#[test]
fn test_bbox3d_dimensions() {
let bbox = BoundingBox3D::new(0.0, 0.0, 0.0, 10.0, 5.0, 3.0);
assert_eq!(bbox.width(), 10.0);
assert_eq!(bbox.height(), 5.0);
assert_eq!(bbox.depth(), 3.0);
assert_eq!(bbox.volume(), 150.0);
}
#[test]
fn test_bbox3d_center() {
let bbox = BoundingBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
let (x, y, z) = bbox.center();
assert_eq!(x, 5.0);
assert_eq!(y, 5.0);
assert_eq!(z, 5.0);
}
#[test]
fn test_bbox3d_contains() {
let bbox = BoundingBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
assert!(bbox.contains_point(5.0, 5.0, 5.0));
assert!(bbox.contains_point(0.0, 0.0, 0.0));
assert!(bbox.contains_point(10.0, 10.0, 10.0));
assert!(!bbox.contains_point(-1.0, 5.0, 5.0));
assert!(!bbox.contains_point(5.0, 5.0, 11.0));
}
#[test]
fn test_bbox3d_intersects() {
let bbox1 = BoundingBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
let bbox2 = BoundingBox3D::new(5.0, 5.0, 5.0, 15.0, 15.0, 15.0);
let bbox3 = BoundingBox3D::new(20.0, 20.0, 20.0, 30.0, 30.0, 30.0);
assert!(bbox1.intersects(&bbox2));
assert!(bbox2.intersects(&bbox1));
assert!(!bbox1.intersects(&bbox3));
assert!(!bbox3.intersects(&bbox1));
}
#[test]
fn test_bbox3d_to_2d() {
let bbox3d = BoundingBox3D::new(0.0, 0.0, 5.0, 10.0, 10.0, 15.0);
let bbox2d = bbox3d.to_2d();
assert_eq!(bbox2d.min_x(), 0.0);
assert_eq!(bbox2d.min_y(), 0.0);
assert_eq!(bbox2d.max_x(), 10.0);
assert_eq!(bbox2d.max_y(), 10.0);
}
#[test]
fn test_temporal_bbox2d() {
let bbox = BoundingBox2D::new(0.0, 0.0, 10.0, 10.0);
let timestamp = SystemTime::now();
let temporal_bbox = TemporalBoundingBox2D::new(bbox.clone(), timestamp);
assert_eq!(temporal_bbox.bbox(), &bbox);
assert_eq!(temporal_bbox.timestamp(), ×tamp);
}
#[test]
fn test_temporal_bbox3d() {
let bbox = BoundingBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
let timestamp = SystemTime::now();
let temporal_bbox = TemporalBoundingBox3D::new(bbox.clone(), timestamp);
assert_eq!(temporal_bbox.bbox(), &bbox);
assert_eq!(temporal_bbox.timestamp(), ×tamp);
}
}