use crate::coord::{Coord, RuVec2};
#[derive(Clone, Copy, Debug)]
pub struct Viewport {
pub width_px: u32,
pub height_px: u32,
pub span: Coord,
pub perimeter: Coord,
pub diagonal_sq: Coord,
pub ru: Coord,
half_w: Coord,
half_h: Coord,
}
impl Viewport {
pub fn new(width_px: u32, height_px: u32) -> Self {
let w = width_px as Coord;
let h = height_px as Coord;
let perimeter = w + h;
let span = (2.0 * w * h) / perimeter;
let diagonal_sq = w * w + h * h;
Self {
width_px,
height_px,
span,
perimeter,
diagonal_sq,
ru: 1.0,
half_w: w * 0.5,
half_h: h * 0.5,
}
}
pub fn with_ru(mut self, ru: Coord) -> Self {
self.ru = ru;
self
}
#[inline]
pub fn ru_to_px_x(&self, x_ru: Coord) -> isize {
(self.half_w + x_ru * self.span * self.ru) as isize
}
#[inline]
pub fn ru_to_px_y(&self, y_ru: Coord) -> isize {
(self.half_h + y_ru * self.span * self.ru) as isize
}
#[inline]
pub fn ru_to_px_d(&self, d_ru: Coord) -> isize {
(d_ru * self.span * self.ru) as isize
}
#[inline]
pub fn ru_to_px(&self, p: RuVec2) -> (isize, isize) {
(self.ru_to_px_x(p.x), self.ru_to_px_y(p.y))
}
#[inline]
pub fn px_to_ru(&self, px: i32, py: i32) -> RuVec2 {
let span_ru = self.span * self.ru;
RuVec2 {
x: (px as Coord - self.half_w) / span_ru,
y: (py as Coord - self.half_h) / span_ru,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn span_is_harmonic_mean() {
let vp = Viewport::new(1920, 1080);
assert!((vp.span - 1382.4).abs() < 0.01, "span = {}, expected ~1382.4", vp.span);
}
#[test]
fn center_origin_round_trip() {
let vp = Viewport::new(800, 600);
let (px, py) = vp.ru_to_px(RuVec2::ZERO);
assert_eq!((px, py), (400, 300));
}
#[test]
fn px_to_ru_inverse() {
let vp = Viewport::new(1024, 768);
let original = RuVec2::new(0.25, -0.125);
let (px, py) = vp.ru_to_px(original);
let recovered = vp.px_to_ru(px as i32, py as i32);
let dx = (recovered.x - original.x).abs();
let dy = (recovered.y - original.y).abs();
let one_px_ru = (vp.span * vp.ru).recip();
assert!(dx <= one_px_ru, "dx={} > 1px_ru={}", dx, one_px_ru);
assert!(dy <= one_px_ru, "dy={} > 1px_ru={}", dy, one_px_ru);
}
#[test]
fn perimeter_and_diagonal_sq() {
let vp = Viewport::new(3, 4);
assert!((vp.perimeter - 7.0).abs() < 1e-6);
assert!((vp.diagonal_sq - 25.0).abs() < 1e-6);
}
}