use std::hash::Hash;
use glam::Vec2;
use ordered_float::OrderedFloat;
use crate::{color::Color, handle::Handle};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Rect {
pub min: Vec2,
pub max: Vec2,
}
impl Rect {
pub fn new(center: Vec2, radius: f32) -> Self {
Self {
min: center - Vec2::splat(radius),
max: center + Vec2::splat(radius),
}
}
pub fn is_touching(&self, other: &Self) -> bool {
!(self.min.x > other.max.x
|| self.max.x < other.min.x
|| self.min.y > other.max.y
|| self.max.y < other.min.y)
}
pub fn contains_pos(&self, pos: Vec2) -> bool {
let Rect { min, max } = self;
pos.x >= min.x && pos.y >= min.y && pos.x <= max.x && pos.y <= max.y
}
pub fn center(&self) -> Vec2 {
(self.min + self.max) * 0.5
}
pub fn size(&self) -> Vec2 {
self.max - self.min
}
pub fn overlap(&self, other: &Self) -> Vec2 {
(self.max - other.min).min(other.max - self.min)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinePattern {
Solid,
Dashed { dash_length: f32, gap_length: f32 },
Dotted { spacing: f32 },
}
#[derive(Debug, Clone)]
pub struct Line {
pub start: Vec2,
pub end: Vec2,
pub thickness: f32,
pub color: Color,
}
#[derive(Debug, Clone)]
pub(crate) enum DrawContent {
Texture {
handle: Handle,
color: Color,
},
Lines(Vec<Line>),
Circle {
radius: f32,
color: Color,
},
Polygon {
vertices: Vec<Vec2>,
color: Color,
},
PolygonTextured {
vertices: Vec<Vec2>,
uvs: Vec<Vec2>,
color: Color,
handle: Handle,
},
}
impl DrawContent {
pub fn texture_handle(&self) -> Option<&Handle> {
if let Self::Texture { handle, .. } = self {
Some(handle)
} else if let Self::PolygonTextured { handle, .. } = self {
Some(handle)
} else {
None
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct LineCacheKey {
start: [OrderedFloat<f32>; 2],
end: [OrderedFloat<f32>; 2],
thickness: OrderedFloat<f32>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum DrawContentCacheKey {
Texture {
handle: u64,
},
Lines(Vec<LineCacheKey>),
Circle {
radius: OrderedFloat<f32>,
},
Polygon(Vec<[OrderedFloat<f32>; 2]>),
PolygonTextured {
handle: u64,
vertices: Vec<[OrderedFloat<f32>; 2]>,
uvs: Vec<[OrderedFloat<f32>; 2]>,
},
}
impl From<DrawContent> for DrawContentCacheKey {
fn from(value: DrawContent) -> Self {
match value {
DrawContent::Texture { handle, color: _ } => Self::Texture {
handle: handle.id(),
},
DrawContent::Lines(lines) => Self::Lines(
lines
.into_iter()
.map(
|Line {
start,
end,
thickness,
color: _,
}| {
LineCacheKey {
start: [start.x.into(), start.y.into()],
end: [end.x.into(), end.y.into()],
thickness: thickness.into(),
}
},
)
.collect(),
),
DrawContent::Circle { radius, color: _ } => Self::Circle {
radius: radius.into(),
},
DrawContent::Polygon { vertices, color: _ } => Self::Polygon(
vertices
.into_iter()
.map(|Vec2 { x, y }| [OrderedFloat(x), OrderedFloat(y)])
.collect(),
),
DrawContent::PolygonTextured {
vertices,
uvs,
handle,
color: _,
} => Self::PolygonTextured {
handle: handle.id(),
vertices: vertices
.into_iter()
.map(|Vec2 { x, y }| [OrderedFloat(x), OrderedFloat(y)])
.collect(),
uvs: uvs
.into_iter()
.map(|Vec2 { x, y }| [OrderedFloat(x), OrderedFloat(y)])
.collect(),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rect_new() {
let r = Rect::new(Vec2::new(5.0, 5.0), 3.0);
assert_eq!(r.min, Vec2::new(2.0, 2.0));
assert_eq!(r.max, Vec2::new(8.0, 8.0));
}
#[test]
fn test_rect_center() {
let r = Rect {
min: Vec2::new(0.0, 0.0),
max: Vec2::new(10.0, 10.0),
};
assert_eq!(r.center(), Vec2::new(5.0, 5.0));
}
#[test]
fn test_rect_size() {
let r = Rect {
min: Vec2::new(2.0, 3.0),
max: Vec2::new(7.0, 9.0),
};
assert_eq!(r.size(), Vec2::new(5.0, 6.0));
}
#[test]
fn test_rect_contains_pos_inside() {
let r = Rect {
min: Vec2::ZERO,
max: Vec2::splat(10.0),
};
assert!(r.contains_pos(Vec2::splat(5.0)));
}
#[test]
fn test_rect_contains_pos_outside() {
let r = Rect {
min: Vec2::ZERO,
max: Vec2::splat(10.0),
};
assert!(!r.contains_pos(Vec2::splat(11.0)));
assert!(!r.contains_pos(Vec2::new(-1.0, 5.0)));
}
#[test]
fn test_rect_contains_pos_boundary() {
let r = Rect {
min: Vec2::ZERO,
max: Vec2::splat(10.0),
};
assert!(r.contains_pos(Vec2::ZERO)); assert!(r.contains_pos(Vec2::splat(10.0))); assert!(r.contains_pos(Vec2::new(10.0, 0.0))); }
#[test]
fn test_rect_is_touching_overlapping() {
let a = Rect {
min: Vec2::ZERO,
max: Vec2::splat(10.0),
};
let b = Rect {
min: Vec2::splat(5.0),
max: Vec2::splat(15.0),
};
assert!(a.is_touching(&b));
assert!(b.is_touching(&a));
}
#[test]
fn test_rect_is_touching_separate() {
let a = Rect {
min: Vec2::ZERO,
max: Vec2::splat(5.0),
};
let b = Rect {
min: Vec2::splat(10.0),
max: Vec2::splat(15.0),
};
assert!(!a.is_touching(&b));
assert!(!b.is_touching(&a));
}
#[test]
fn test_rect_is_touching_edge() {
let a = Rect {
min: Vec2::ZERO,
max: Vec2::splat(5.0),
};
let b = Rect {
min: Vec2::new(5.0, 0.0),
max: Vec2::new(10.0, 5.0),
};
assert!(a.is_touching(&b)); }
#[test]
fn test_rect_overlap() {
let a = Rect {
min: Vec2::ZERO,
max: Vec2::splat(10.0),
};
let b = Rect {
min: Vec2::splat(5.0),
max: Vec2::splat(15.0),
};
let overlap = a.overlap(&b);
assert_eq!(overlap, Vec2::splat(5.0));
}
#[test]
fn test_rect_overlap_no_intersection() {
let a = Rect {
min: Vec2::ZERO,
max: Vec2::splat(5.0),
};
let b = Rect {
min: Vec2::splat(10.0),
max: Vec2::splat(15.0),
};
let overlap = a.overlap(&b);
assert!(overlap.x < 0.0);
assert!(overlap.y < 0.0);
}
}