use crate::Viewport;
use bytemuck::{Pod, Zeroable};
#[derive(Debug, Clone, Copy)]
pub struct DataRangeParams {
pub plot_x: f32,
pub plot_y: f32,
pub plot_width: f32,
pub plot_height: f32,
pub data_x_min: f64,
pub data_x_max: f64,
pub data_y_min: f64,
pub data_y_max: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct DataTransform {
uniform: TransformUniform,
}
impl DataTransform {
pub fn identity(viewport: Viewport) -> Self {
let logical = viewport.to_logical();
Self {
uniform: TransformUniform::identity(logical.width, logical.height),
}
}
pub fn from_data_range(viewport: Viewport, params: DataRangeParams) -> Self {
let logical = viewport.to_logical();
Self {
uniform: TransformUniform::for_data_range(
logical.width,
logical.height,
params.plot_x,
params.plot_y,
params.plot_width,
params.plot_height,
params.data_x_min as f32,
params.data_x_max as f32,
params.data_y_min as f32,
params.data_y_max as f32,
),
}
}
pub(crate) fn uniform(&self) -> &TransformUniform {
&self.uniform
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, PartialEq)]
pub(crate) struct TransformUniform {
pub(crate) projection: [[f32; 4]; 4],
pub(crate) scale: [f32; 2],
pub(crate) offset: [f32; 2],
}
impl TransformUniform {
pub(crate) fn identity(viewport_width: f32, viewport_height: f32) -> Self {
Self {
projection: Self::ortho_matrix(viewport_width, viewport_height),
scale: [1.0, 1.0],
offset: [0.0, 0.0],
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn for_data_range(
viewport_width: f32,
viewport_height: f32,
plot_x: f32,
plot_y: f32,
plot_width: f32,
plot_height: f32,
data_x_min: f32,
data_x_max: f32,
data_y_min: f32,
data_y_max: f32,
) -> Self {
let scale_x = plot_width / (data_x_max - data_x_min);
let scale_y = -plot_height / (data_y_max - data_y_min);
let offset_x = plot_x - data_x_min * scale_x;
let offset_y = plot_y + plot_height - data_y_min * scale_y;
Self {
projection: Self::ortho_matrix(viewport_width, viewport_height),
scale: [scale_x, scale_y],
offset: [offset_x, offset_y],
}
}
pub(crate) fn ortho_matrix(width: f32, height: f32) -> [[f32; 4]; 4] {
[
[2.0 / width, 0.0, 0.0, 0.0],
[0.0, -2.0 / height, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[-1.0, 1.0, 0.0, 1.0],
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use astrelis_core::geometry::{PhysicalPosition, PhysicalSize, ScaleFactor};
fn test_viewport() -> Viewport {
Viewport {
position: PhysicalPosition::new(0.0, 0.0),
size: PhysicalSize::new(800.0, 600.0),
scale_factor: ScaleFactor(1.0),
}
}
#[test]
fn test_identity_transform() {
let transform = DataTransform::identity(test_viewport());
let u = transform.uniform();
assert_eq!(u.scale, [1.0, 1.0]);
assert_eq!(u.offset, [0.0, 0.0]);
}
#[test]
fn test_data_range_transform() {
let params = DataRangeParams {
plot_x: 100.0,
plot_y: 50.0,
plot_width: 600.0,
plot_height: 400.0,
data_x_min: 0.0,
data_x_max: 10.0,
data_y_min: 0.0,
data_y_max: 100.0,
};
let transform = DataTransform::from_data_range(test_viewport(), params);
let u = transform.uniform();
assert!((u.scale[0] - 60.0).abs() < 0.001);
assert!((u.scale[1] - (-4.0)).abs() < 0.001);
}
#[test]
fn test_ortho_matrix_dimensions() {
let matrix = TransformUniform::ortho_matrix(800.0, 600.0);
assert!((matrix[0][0] - 2.0 / 800.0).abs() < 0.0001);
assert!((matrix[1][1] - (-2.0 / 600.0)).abs() < 0.0001);
assert!((matrix[2][2] - 1.0).abs() < 0.0001);
}
#[test]
fn test_transform_uniform_size() {
assert_eq!(std::mem::size_of::<TransformUniform>(), 80);
}
}