use crate::trans_affine::TransAffine;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspectRatio {
Stretch,
Meet,
Slice,
}
pub struct TransViewport {
world_x1: f64,
world_y1: f64,
world_x2: f64,
world_y2: f64,
device_x1: f64,
device_y1: f64,
device_x2: f64,
device_y2: f64,
aspect: AspectRatio,
is_valid: bool,
align_x: f64,
align_y: f64,
wx1: f64,
wy1: f64,
wx2: f64,
wy2: f64,
dx1: f64,
dy1: f64,
kx: f64,
ky: f64,
}
impl Default for TransViewport {
fn default() -> Self {
Self::new()
}
}
impl TransViewport {
pub fn new() -> Self {
Self {
world_x1: 0.0,
world_y1: 0.0,
world_x2: 1.0,
world_y2: 1.0,
device_x1: 0.0,
device_y1: 0.0,
device_x2: 1.0,
device_y2: 1.0,
aspect: AspectRatio::Stretch,
is_valid: true,
align_x: 0.5,
align_y: 0.5,
wx1: 0.0,
wy1: 0.0,
wx2: 1.0,
wy2: 1.0,
dx1: 0.0,
dy1: 0.0,
kx: 1.0,
ky: 1.0,
}
}
pub fn preserve_aspect_ratio(&mut self, align_x: f64, align_y: f64, aspect: AspectRatio) {
self.align_x = align_x;
self.align_y = align_y;
self.aspect = aspect;
self.update();
}
pub fn set_device_viewport(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
self.device_x1 = x1;
self.device_y1 = y1;
self.device_x2 = x2;
self.device_y2 = y2;
self.update();
}
pub fn set_world_viewport(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
self.world_x1 = x1;
self.world_y1 = y1;
self.world_x2 = x2;
self.world_y2 = y2;
self.update();
}
pub fn device_viewport(&self) -> (f64, f64, f64, f64) {
(
self.device_x1,
self.device_y1,
self.device_x2,
self.device_y2,
)
}
pub fn world_viewport(&self) -> (f64, f64, f64, f64) {
(self.world_x1, self.world_y1, self.world_x2, self.world_y2)
}
pub fn world_viewport_actual(&self) -> (f64, f64, f64, f64) {
(self.wx1, self.wy1, self.wx2, self.wy2)
}
pub fn is_valid(&self) -> bool {
self.is_valid
}
pub fn align_x(&self) -> f64 {
self.align_x
}
pub fn align_y(&self) -> f64 {
self.align_y
}
pub fn aspect_ratio(&self) -> AspectRatio {
self.aspect
}
pub fn transform(&self, x: &mut f64, y: &mut f64) {
*x = (*x - self.wx1) * self.kx + self.dx1;
*y = (*y - self.wy1) * self.ky + self.dy1;
}
pub fn transform_scale_only(&self, x: &mut f64, y: &mut f64) {
*x *= self.kx;
*y *= self.ky;
}
pub fn inverse_transform(&self, x: &mut f64, y: &mut f64) {
*x = (*x - self.dx1) / self.kx + self.wx1;
*y = (*y - self.dy1) / self.ky + self.wy1;
}
pub fn inverse_transform_scale_only(&self, x: &mut f64, y: &mut f64) {
*x /= self.kx;
*y /= self.ky;
}
pub fn device_dx(&self) -> f64 {
self.dx1 - self.wx1 * self.kx
}
pub fn device_dy(&self) -> f64 {
self.dy1 - self.wy1 * self.ky
}
pub fn scale_x(&self) -> f64 {
self.kx
}
pub fn scale_y(&self) -> f64 {
self.ky
}
pub fn scale(&self) -> f64 {
(self.kx + self.ky) * 0.5
}
pub fn to_affine(&self) -> TransAffine {
let mut mtx = TransAffine::new_translation(-self.wx1, -self.wy1);
mtx.multiply(&TransAffine::new_scaling(self.kx, self.ky));
mtx.multiply(&TransAffine::new_translation(self.dx1, self.dy1));
mtx
}
pub fn to_affine_scale_only(&self) -> TransAffine {
TransAffine::new_scaling(self.kx, self.ky)
}
fn update(&mut self) {
const EPSILON: f64 = 1e-30;
if (self.world_x1 - self.world_x2).abs() < EPSILON
|| (self.world_y1 - self.world_y2).abs() < EPSILON
|| (self.device_x1 - self.device_x2).abs() < EPSILON
|| (self.device_y1 - self.device_y2).abs() < EPSILON
{
self.wx1 = self.world_x1;
self.wy1 = self.world_y1;
self.wx2 = self.world_x1 + 1.0;
self.wy2 = self.world_y2 + 1.0;
self.dx1 = self.device_x1;
self.dy1 = self.device_y1;
self.kx = 1.0;
self.ky = 1.0;
self.is_valid = false;
return;
}
let mut world_x1 = self.world_x1;
let mut world_y1 = self.world_y1;
let mut world_x2 = self.world_x2;
let mut world_y2 = self.world_y2;
let device_x1 = self.device_x1;
let device_y1 = self.device_y1;
let device_x2 = self.device_x2;
let device_y2 = self.device_y2;
if self.aspect != AspectRatio::Stretch {
self.kx = (device_x2 - device_x1) / (world_x2 - world_x1);
self.ky = (device_y2 - device_y1) / (world_y2 - world_y1);
if (self.aspect == AspectRatio::Meet) == (self.kx < self.ky) {
let d = (world_y2 - world_y1) * self.ky / self.kx;
world_y1 += (world_y2 - world_y1 - d) * self.align_y;
world_y2 = world_y1 + d;
} else {
let d = (world_x2 - world_x1) * self.kx / self.ky;
world_x1 += (world_x2 - world_x1 - d) * self.align_x;
world_x2 = world_x1 + d;
}
}
self.wx1 = world_x1;
self.wy1 = world_y1;
self.wx2 = world_x2;
self.wy2 = world_y2;
self.dx1 = device_x1;
self.dy1 = device_y1;
self.kx = (device_x2 - device_x1) / (world_x2 - world_x1);
self.ky = (device_y2 - device_y1) / (world_y2 - world_y1);
self.is_valid = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_identity() {
let vp = TransViewport::new();
assert!(vp.is_valid());
assert_eq!(vp.scale_x(), 1.0);
assert_eq!(vp.scale_y(), 1.0);
}
#[test]
fn test_stretch_scaling() {
let mut vp = TransViewport::new();
vp.set_world_viewport(0.0, 0.0, 100.0, 100.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 400.0);
assert_eq!(vp.scale_x(), 2.0);
assert_eq!(vp.scale_y(), 4.0);
}
#[test]
fn test_transform() {
let mut vp = TransViewport::new();
vp.set_world_viewport(0.0, 0.0, 100.0, 100.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 200.0);
let mut x = 50.0;
let mut y = 50.0;
vp.transform(&mut x, &mut y);
assert_eq!(x, 100.0);
assert_eq!(y, 100.0);
}
#[test]
fn test_inverse_transform() {
let mut vp = TransViewport::new();
vp.set_world_viewport(0.0, 0.0, 100.0, 100.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 200.0);
let mut x = 100.0;
let mut y = 100.0;
vp.inverse_transform(&mut x, &mut y);
assert_eq!(x, 50.0);
assert_eq!(y, 50.0);
}
#[test]
fn test_meet_aspect_ratio() {
let mut vp = TransViewport::new();
vp.set_world_viewport(0.0, 0.0, 100.0, 100.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 400.0);
vp.preserve_aspect_ratio(0.5, 0.5, AspectRatio::Meet);
assert_eq!(vp.scale_x(), 2.0);
assert_eq!(vp.scale_y(), 2.0);
}
#[test]
fn test_slice_aspect_ratio() {
let mut vp = TransViewport::new();
vp.set_world_viewport(0.0, 0.0, 100.0, 100.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 400.0);
vp.preserve_aspect_ratio(0.5, 0.5, AspectRatio::Slice);
assert_eq!(vp.scale_x(), 4.0);
assert_eq!(vp.scale_y(), 4.0);
}
#[test]
fn test_to_affine() {
let mut vp = TransViewport::new();
vp.set_world_viewport(0.0, 0.0, 100.0, 100.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 200.0);
let mtx = vp.to_affine();
let mut x = 50.0;
let mut y = 50.0;
mtx.transform(&mut x, &mut y);
assert!((x - 100.0).abs() < 1e-10);
assert!((y - 100.0).abs() < 1e-10);
}
#[test]
fn test_invalid_zero_size() {
let mut vp = TransViewport::new();
vp.set_world_viewport(50.0, 50.0, 50.0, 50.0); assert!(!vp.is_valid());
}
#[test]
fn test_device_dx_dy() {
let mut vp = TransViewport::new();
vp.set_world_viewport(10.0, 20.0, 110.0, 120.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 200.0);
assert_eq!(vp.device_dx(), -20.0);
}
#[test]
fn test_scale() {
let mut vp = TransViewport::new();
vp.set_world_viewport(0.0, 0.0, 100.0, 100.0);
vp.set_device_viewport(0.0, 0.0, 200.0, 400.0);
assert_eq!(vp.scale(), 3.0); }
}