use std::marker::PhantomData;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Plane;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Window;
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "", deserialize = ""))]
pub struct Point<S> {
pub x: i32,
pub y: i32,
#[serde(skip)]
_space: PhantomData<S>,
}
impl<S> Point<S> {
pub const fn new(x: i32, y: i32) -> Self {
Point {
x,
y,
_space: PhantomData,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(bound(serialize = "", deserialize = ""))]
pub struct Rect<S> {
pub origin: Point<S>,
pub width: u32,
pub height: u32,
}
impl<S: Copy> Rect<S> {
pub const fn new(origin: Point<S>, width: u32, height: u32) -> Self {
Rect {
origin,
width,
height,
}
}
pub const fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Self {
Rect {
origin: Point::new(x, y),
width,
height,
}
}
pub fn x0(&self) -> i32 {
self.origin.x
}
pub fn y0(&self) -> i32 {
self.origin.y
}
pub fn x1(&self) -> i32 {
self.origin.x + self.width as i32
}
pub fn y1(&self) -> i32 {
self.origin.y + self.height as i32
}
pub fn is_empty(&self) -> bool {
self.width == 0 || self.height == 0
}
pub fn round_outward(self, chunk: u32) -> Rect<S> {
if self.is_empty() {
return self;
}
let chunk = chunk as i32;
let x0 = self.x0().div_euclid(chunk) * chunk;
let y0 = self.y0().div_euclid(chunk) * chunk;
let x1 = (self.x1() - 1).div_euclid(chunk) * chunk + chunk;
let y1 = (self.y1() - 1).div_euclid(chunk) * chunk + chunk;
Rect::from_xywh(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32)
}
pub fn contains(&self, other: Rect<S>) -> bool {
if other.is_empty() {
return true;
}
if self.is_empty() {
return false;
}
other.x0() >= self.x0()
&& other.y0() >= self.y0()
&& other.x1() <= self.x1()
&& other.y1() <= self.y1()
}
pub fn union(self, other: Rect<S>) -> Rect<S> {
if self.is_empty() {
return other;
}
if other.is_empty() {
return self;
}
let x0 = self.x0().min(other.x0());
let y0 = self.y0().min(other.y0());
let x1 = self.x1().max(other.x1());
let y1 = self.y1().max(other.y1());
Rect::from_xywh(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32)
}
pub fn intersect(self, other: Rect<S>) -> Option<Rect<S>> {
let x0 = self.x0().max(other.x0());
let y0 = self.y0().max(other.y0());
let x1 = self.x1().min(other.x1());
let y1 = self.y1().min(other.y1());
if x1 > x0 && y1 > y0 {
Some(Rect::from_xywh(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32))
} else {
None
}
}
pub fn from_corners(x0: i32, y0: i32, x1: i32, y1: i32) -> Rect<S> {
Rect::from_xywh(
x0,
y0,
x1.saturating_sub(x0).max(0) as u32,
y1.saturating_sub(y0).max(0) as u32,
)
}
pub fn clamp_f32(self, x0: f32, y0: f32, x1: f32, y1: f32) -> Option<Rect<S>> {
Rect::from_corners(
x0.floor() as i32,
y0.floor() as i32,
x1.ceil() as i32,
y1.ceil() as i32,
)
.intersect(self)
}
pub fn subtract(self, other: Option<Rect<S>>) -> Vec<Rect<S>> {
if self.is_empty() {
return Vec::new();
}
let other = match other.and_then(|o| self.intersect(o)) {
Some(o) => o,
None => return vec![self],
};
let mut out = Vec::with_capacity(4);
if other.y0() > self.y0() {
out.push(Rect::from_xywh(
self.x0(),
self.y0(),
self.width,
(other.y0() - self.y0()) as u32,
));
}
if other.y1() < self.y1() {
out.push(Rect::from_xywh(
self.x0(),
other.y1(),
self.width,
(self.y1() - other.y1()) as u32,
));
}
if other.x0() > self.x0() {
out.push(Rect::from_xywh(
self.x0(),
other.y0(),
(other.x0() - self.x0()) as u32,
other.height,
));
}
if other.x1() < self.x1() {
out.push(Rect::from_xywh(
other.x1(),
other.y0(),
(self.x1() - other.x1()) as u32,
other.height,
));
}
out
}
}
impl WindowPoint {
pub fn to_canvas(self, canvas_origin: CanvasPoint) -> CanvasPoint {
CanvasPoint::new(self.x + canvas_origin.x, self.y + canvas_origin.y)
}
}
impl CanvasPoint {
pub fn to_window(self, canvas_origin: CanvasPoint) -> WindowPoint {
WindowPoint::new(self.x - canvas_origin.x, self.y - canvas_origin.y)
}
}
impl WindowRect {
pub fn to_canvas(self, canvas_origin: CanvasPoint) -> CanvasRect {
CanvasRect::new(
self.origin.to_canvas(canvas_origin),
self.width,
self.height,
)
}
}
impl CanvasRect {
pub fn to_window(self, canvas_origin: CanvasPoint) -> WindowRect {
WindowRect::new(
self.origin.to_window(canvas_origin),
self.width,
self.height,
)
}
}
pub type CanvasPoint = Point<Plane>;
pub type CanvasRect = Rect<Plane>;
pub type WindowPoint = Point<Window>;
pub type WindowRect = Rect<Window>;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LayerPoint {
pub x: u32,
pub y: u32,
}
impl LayerPoint {
pub const fn new(x: u32, y: u32) -> Self {
LayerPoint { x, y }
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LayerRect {
pub origin: LayerPoint,
pub width: u32,
pub height: u32,
}
impl LayerRect {
pub const fn new(origin: LayerPoint, width: u32, height: u32) -> Self {
LayerRect {
origin,
width,
height,
}
}
pub const fn from_xywh(x: u32, y: u32, width: u32, height: u32) -> Self {
LayerRect {
origin: LayerPoint::new(x, y),
width,
height,
}
}
pub fn x0(&self) -> u32 {
self.origin.x
}
pub fn y0(&self) -> u32 {
self.origin.y
}
pub fn x1(&self) -> u32 {
self.origin.x + self.width
}
pub fn y1(&self) -> u32 {
self.origin.y + self.height
}
pub fn is_empty(&self) -> bool {
self.width == 0 || self.height == 0
}
pub fn contains(&self, other: LayerRect) -> bool {
if other.is_empty() {
return true;
}
if self.is_empty() {
return false;
}
other.x0() >= self.x0()
&& other.y0() >= self.y0()
&& other.x1() <= self.x1()
&& other.y1() <= self.y1()
}
pub fn intersect(self, other: LayerRect) -> Option<LayerRect> {
let x0 = self.x0().max(other.x0());
let y0 = self.y0().max(other.y0());
let x1 = self.x1().min(other.x1());
let y1 = self.y1().min(other.y1());
if x1 > x0 && y1 > y0 {
Some(LayerRect::from_xywh(x0, y0, x1 - x0, y1 - y0))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(x: i32, y: i32, w: u32, h: u32) -> CanvasRect {
CanvasRect::from_xywh(x, y, w, h)
}
#[test]
fn union_with_empty_is_identity() {
let a = r(10, 10, 20, 20);
let empty = r(0, 0, 0, 0);
assert_eq!(a.union(empty), a);
assert_eq!(empty.union(a), a);
}
#[test]
fn union_disjoint_extends() {
let a = r(0, 0, 10, 10);
let b = r(20, 20, 10, 10);
assert_eq!(a.union(b), r(0, 0, 30, 30));
}
#[test]
fn intersect_disjoint_is_none() {
assert_eq!(r(0, 0, 10, 10).intersect(r(20, 20, 10, 10)), None);
}
#[test]
fn intersect_touching_is_none() {
assert_eq!(r(0, 0, 10, 10).intersect(r(10, 0, 10, 10)), None);
}
#[test]
fn intersect_overlap() {
let i = r(0, 0, 10, 10).intersect(r(5, 5, 10, 10)).unwrap();
assert_eq!(i, r(5, 5, 5, 5));
}
#[test]
fn intersect_contained() {
let i = r(0, 0, 100, 100).intersect(r(20, 30, 5, 5)).unwrap();
assert_eq!(i, r(20, 30, 5, 5));
}
#[test]
fn from_corners_normal() {
assert_eq!(CanvasRect::from_corners(5, 10, 15, 30), r(5, 10, 10, 20));
}
#[test]
fn from_corners_reversed_is_empty() {
let rect = CanvasRect::from_corners(15, 30, 5, 10);
assert!(rect.is_empty());
assert_eq!(rect, r(15, 30, 0, 0));
}
#[test]
fn from_corners_extreme_i32_no_overflow() {
let rect = CanvasRect::from_corners(i32::MAX, 0, i32::MIN, 10);
assert!(rect.is_empty());
}
#[test]
fn clamp_f32_disjoint_is_none() {
let extent = r(0, 0, 100, 100);
assert_eq!(extent.clamp_f32(500.0, 500.0, 550.0, 550.0), None);
assert_eq!(extent.clamp_f32(-50.0, -50.0, -10.0, -10.0), None);
}
#[test]
fn clamp_f32_partial_clamps_to_extent() {
let extent = r(0, 0, 100, 100);
let i = extent.clamp_f32(80.5, 80.5, 130.0, 130.0).unwrap();
assert_eq!(i, r(80, 80, 20, 20));
}
#[test]
fn clamp_f32_inside_rounds_outward() {
let extent = r(0, 0, 100, 100);
let i = extent.clamp_f32(10.2, 10.8, 20.1, 20.9).unwrap();
assert_eq!(i, r(10, 10, 11, 11));
}
#[test]
fn subtract_none_returns_self() {
let a = r(0, 0, 10, 10);
assert_eq!(a.subtract(None), vec![a]);
}
#[test]
fn subtract_disjoint_returns_self() {
let a = r(0, 0, 10, 10);
assert_eq!(a.subtract(Some(r(20, 20, 5, 5))), vec![a]);
}
#[test]
fn subtract_identical_returns_empty() {
let a = r(0, 0, 10, 10);
assert!(a.subtract(Some(a)).is_empty());
}
#[test]
fn subtract_contained_returns_four_strips() {
let a = r(0, 0, 100, 100);
let b = r(40, 40, 20, 20);
let parts = a.subtract(Some(b));
assert_eq!(parts.len(), 4);
let total_area: u64 = parts.iter().map(|r| r.width as u64 * r.height as u64).sum();
let expected = a.width as u64 * a.height as u64 - b.width as u64 * b.height as u64;
assert_eq!(total_area, expected);
}
#[test]
fn subtract_corner_overlap() {
let a = r(0, 0, 10, 10);
let parts = a.subtract(Some(r(-5, -5, 10, 10)));
assert_eq!(parts.len(), 2);
}
#[test]
fn contains_empty_other_is_true() {
assert!(r(10, 10, 5, 5).contains(r(0, 0, 0, 0)));
}
#[test]
fn contains_self_in_self_is_true() {
let a = r(0, 0, 10, 10);
assert!(a.contains(a));
}
#[test]
fn contains_partial_overlap_is_false() {
assert!(!r(0, 0, 10, 10).contains(r(5, 5, 10, 10)));
}
#[test]
fn negative_offset_round_trip() {
let a = r(-256, -256, 512, 512);
assert_eq!(a.x0(), -256);
assert_eq!(a.x1(), 256);
assert_eq!(a.union(a), a);
}
#[test]
fn round_outward_already_aligned_is_identity() {
let a = r(0, 0, 256, 256);
assert_eq!(a.round_outward(256), a);
}
#[test]
fn round_outward_grows_far_edge_to_chunk() {
let a = r(0, 0, 257, 1);
assert_eq!(a.round_outward(256), r(0, 0, 512, 256));
}
#[test]
fn bounds_align_to_256_handles_negative_canvas_coords() {
let a = r(-1, -1, 1, 1);
let r256 = a.round_outward(256);
assert_eq!(r256.x0(), -256);
assert_eq!(r256.y0(), -256);
assert_eq!(r256.width, 256);
assert_eq!(r256.height, 256);
}
#[test]
fn round_outward_negative_origin_just_inside_alignment() {
let a = r(-257, -257, 256, 256);
let r256 = a.round_outward(256);
assert_eq!(r256.x0(), -512);
assert_eq!(r256.y0(), -512);
assert_eq!(r256.x1(), 0);
assert_eq!(r256.y1(), 0);
}
#[test]
fn round_outward_preserves_empty() {
let a = r(10, 10, 0, 5);
assert_eq!(a.round_outward(256), a);
}
fn lr(x: u32, y: u32, w: u32, h: u32) -> LayerRect {
LayerRect::from_xywh(x, y, w, h)
}
#[test]
fn layer_rect_edges() {
let a = lr(10, 20, 30, 40);
assert_eq!(a.x0(), 10);
assert_eq!(a.y0(), 20);
assert_eq!(a.x1(), 40);
assert_eq!(a.y1(), 60);
}
#[test]
fn layer_rect_contains_self_and_empty() {
let a = lr(0, 0, 10, 10);
assert!(a.contains(a));
assert!(a.contains(lr(0, 0, 0, 0)));
}
#[test]
fn layer_rect_contains_subrect() {
assert!(lr(0, 0, 100, 100).contains(lr(40, 40, 20, 20)));
}
#[test]
fn layer_rect_contains_rejects_partial_overlap() {
assert!(!lr(0, 0, 10, 10).contains(lr(5, 5, 10, 10)));
}
#[test]
fn layer_rect_contains_rejects_disjoint() {
assert!(!lr(0, 0, 10, 10).contains(lr(20, 20, 5, 5)));
}
#[test]
fn layer_rect_intersect_disjoint_is_none() {
assert_eq!(lr(0, 0, 10, 10).intersect(lr(20, 20, 5, 5)), None);
}
#[test]
fn layer_rect_intersect_touching_is_none() {
assert_eq!(lr(0, 0, 10, 10).intersect(lr(10, 0, 10, 10)), None);
}
#[test]
fn layer_rect_intersect_overlap() {
let i = lr(0, 0, 10, 10).intersect(lr(5, 5, 10, 10)).unwrap();
assert_eq!(i, lr(5, 5, 5, 5));
}
#[test]
fn window_to_canvas_round_trips() {
let origin = CanvasPoint::new(40, 15);
let w = WindowRect::from_xywh(3, 7, 20, 12);
let plane = w.to_canvas(origin);
assert_eq!(plane, CanvasRect::from_xywh(43, 22, 20, 12));
assert_eq!(plane.to_window(origin), w);
}
#[test]
fn window_to_canvas_zero_origin_is_identity() {
let origin = CanvasPoint::new(0, 0);
let w = WindowRect::from_xywh(5, 9, 11, 13);
assert_eq!(w.to_canvas(origin), CanvasRect::from_xywh(5, 9, 11, 13));
}
#[test]
fn window_point_to_canvas_shifts_by_origin() {
let origin = CanvasPoint::new(-12, 30);
let p = WindowPoint::new(8, 4);
assert_eq!(p.to_canvas(origin), CanvasPoint::new(-4, 34));
}
#[test]
fn window_rect_intersect_and_union() {
let a = WindowRect::from_xywh(0, 0, 10, 10);
let b = WindowRect::from_xywh(5, 5, 10, 10);
assert_eq!(a.intersect(b), Some(WindowRect::from_xywh(5, 5, 5, 5)));
assert_eq!(a.union(b), WindowRect::from_xywh(0, 0, 15, 15));
}
#[test]
fn canvas_point_to_window_is_inverse_of_to_canvas() {
let origin = CanvasPoint::new(40, -15);
let p = CanvasPoint::new(43, -8);
let w = p.to_window(origin);
assert_eq!(w, WindowPoint::new(3, 7));
assert_eq!(w.to_canvas(origin), p);
}
#[test]
fn window_rect_shares_generic_algebra() {
let outer = WindowRect::from_xywh(0, 0, 100, 100);
assert!(outer.contains(WindowRect::from_xywh(10, 10, 5, 5)));
assert_eq!(
WindowRect::from_xywh(0, 0, 257, 1).round_outward(256),
WindowRect::from_xywh(0, 0, 512, 256)
);
}
}