use nalgebra_glm::{Vec2, Vec4, Mat4};
use serde::{Deserialize, Serialize};
use crate::render_traits::{MarginParams, ViewParams, AspectRatioMode, AspectRatioAlignmentMode, UnitsMode};
use crate::positioning::{get_point_position, get_scale_mat, get_translate_mat, get_aspect_ratio_mat};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ScreenCoord {
pub x: f32,
pub y: f32,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum DataCoord {
TwoD { x: f32, y: f32 },
ThreeD { x: f32, y: f32, z: f32 },
}
#[derive(Debug, Clone, Copy)]
pub struct DataBounds {
pub x_min: f32,
pub x_max: f32,
pub y_min: f32,
pub y_max: f32,
}
pub fn project(view_params: &ViewParams, layer_bounds: Option<MarginParams>, coord: DataCoord) -> ScreenCoord {
let (pos_x, pos_y) = match coord {
DataCoord::TwoD { x, y } => (x, y),
DataCoord::ThreeD { x, y, .. } => {
panic!("3D coordinates not supported in project function yet");
}
};
let camera_view = view_params.camera_view.unwrap_or([
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]);
let bounds = if layer_bounds.is_none() {
&view_params.margins
} else {
&layer_bounds
};
let (x_px, y_px) = get_point_position(
pos_x,
pos_y,
view_params.width as f32,
view_params.height as f32,
&camera_view,
UnitsMode::Data,
UnitsMode::Data,
view_params.aspect_ratio_mode,
view_params.aspect_ratio_alignment_mode,
None,
);
return ScreenCoord {
x: x_px,
y: y_px,
};
}
pub fn unproject(view_params: &ViewParams, layer_bounds: Option<MarginParams>, coord: ScreenCoord) -> Option<DataCoord> {
let camera_view_raw = view_params.camera_view.unwrap_or([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
let camera_view = Mat4::from_column_slice(&camera_view_raw);
let bounds = if layer_bounds.is_none() {
&view_params.margins
} else {
&layer_bounds
};
let margin_top = bounds.as_ref().and_then(|m| m.margin_top).unwrap_or(0.0);
let margin_left = bounds.as_ref().and_then(|m| m.margin_left).unwrap_or(0.0);
let margin_right = bounds.as_ref().and_then(|m| m.margin_right).unwrap_or(0.0);
let margin_bottom = bounds.as_ref().and_then(|m| m.margin_bottom).unwrap_or(0.0);
let layer_screen_coord = ScreenCoord {
x: coord.x - margin_left,
y: coord.y - margin_bottom,
};
let layer_w = view_params.width as f32 - margin_left - margin_right;
let layer_h = view_params.height as f32 - margin_top - margin_bottom;
if layer_screen_coord.x < 0.0 || layer_screen_coord.x > layer_w || layer_screen_coord.y < 0.0 || layer_screen_coord.y > layer_h {
return None;
}
let norm_x = layer_screen_coord.x / layer_w;
let norm_y = layer_screen_coord.y / layer_h;
let layer_aspect_ratio = layer_w / layer_h;
let ASPECT_RATIO_MAT = get_aspect_ratio_mat(
layer_aspect_ratio,
view_params.aspect_ratio_mode,
view_params.aspect_ratio_alignment_mode
);
let NORM_TO_NDC_MAT = get_translate_mat(-1.0, -1.0, 0.0) * get_scale_mat(2.0, 2.0, 1.0);
let NDC_TO_NORM_MAT = get_translate_mat(0.5, 0.5, 0.0) * get_scale_mat(0.5, 0.5, 1.0);
let model_view_projection = ASPECT_RATIO_MAT * camera_view;
let forward_mat = (NDC_TO_NORM_MAT * model_view_projection * NORM_TO_NDC_MAT);
let inverse_mat = forward_mat.try_inverse().expect("Forward projection matrix is not invertible");
let data_pos = inverse_mat * Vec4::new(norm_x, norm_y, 0.0, 1.0);
return Some(DataCoord::TwoD {
x: data_pos.x,
y: data_pos.y
});
}
pub fn camera_matrix_to_zoom_and_translation(camera_view: Option<[f32; 16]>) -> (f32, f32, f32, f32) {
let camera_view = camera_view.unwrap_or([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
let zoom_x = camera_view[0];
let zoom_y = camera_view[5];
let translate_x = camera_view[12];
let translate_y = camera_view[13];
(zoom_x, zoom_y, translate_x, translate_y)
}
pub fn get_bounds(view_params: &ViewParams) -> DataBounds {
let (zoom_x, zoom_y, translate_x, translate_y) = camera_matrix_to_zoom_and_translation(view_params.camera_view);
let aspect_ratio_mode = view_params.aspect_ratio_mode;
let aspect_ratio_alignment_mode = view_params.aspect_ratio_alignment_mode;
let bounds = &view_params.margins;
let margin_top = bounds.as_ref().and_then(|m| m.margin_top).unwrap_or(0.0) as f64;
let margin_right = bounds.as_ref().and_then(|m| m.margin_right).unwrap_or(0.0) as f64;
let margin_bottom = bounds.as_ref().and_then(|m| m.margin_bottom).unwrap_or(0.0) as f64;
let margin_left = bounds.as_ref().and_then(|m| m.margin_left).unwrap_or(0.0) as f64;
let viewport_w = view_params.width as f32;
let viewport_h = view_params.height as f32;
let layer_w = viewport_w - (margin_left + margin_right) as f32;
let layer_h = viewport_h - (margin_top + margin_bottom) as f32;
let layer_aspect_ratio = layer_w / layer_h;
let mut x_scale_for_aspect_ratio_mode = 1.0_f32;
let mut y_scale_for_aspect_ratio_mode = 1.0_f32;
match aspect_ratio_mode {
AspectRatioMode::Ignore => {}
AspectRatioMode::Contain => {
if layer_aspect_ratio > 1.0 {
x_scale_for_aspect_ratio_mode = layer_aspect_ratio;
} else if layer_aspect_ratio < 1.0 {
y_scale_for_aspect_ratio_mode = 1.0 / layer_aspect_ratio;
}
}
AspectRatioMode::Cover => {
if layer_aspect_ratio > 1.0 {
y_scale_for_aspect_ratio_mode = 1.0 / layer_aspect_ratio;
} else if layer_aspect_ratio < 1.0 {
x_scale_for_aspect_ratio_mode = layer_aspect_ratio;
}
}
}
let mut x_translation_for_aspect_ratio_alignment_mode = 0.0_f32;
let mut y_translation_for_aspect_ratio_alignment_mode = 0.0_f32;
match aspect_ratio_alignment_mode {
AspectRatioAlignmentMode::Center => {}
AspectRatioAlignmentMode::Start => {
x_translation_for_aspect_ratio_alignment_mode = x_scale_for_aspect_ratio_mode - 1.0;
y_translation_for_aspect_ratio_alignment_mode = y_scale_for_aspect_ratio_mode - 1.0;
}
AspectRatioAlignmentMode::End => {
x_translation_for_aspect_ratio_alignment_mode = 1.0 - x_scale_for_aspect_ratio_mode;
y_translation_for_aspect_ratio_alignment_mode = 1.0 - y_scale_for_aspect_ratio_mode;
}
}
let x_adjustment = x_scale_for_aspect_ratio_mode - 1.0;
let y_adjustment = y_scale_for_aspect_ratio_mode - 1.0;
let min_x = (((-translate_x - 1.0 - x_adjustment + x_translation_for_aspect_ratio_alignment_mode) / zoom_x) + 1.0) / 2.0;
let max_x = (((-translate_x + 1.0 + x_adjustment + x_translation_for_aspect_ratio_alignment_mode) / zoom_x) + 1.0) / 2.0;
let min_y = (((-translate_y - 1.0 - y_adjustment + y_translation_for_aspect_ratio_alignment_mode) / zoom_y) + 1.0) / 2.0;
let max_y = (((-translate_y + 1.0 + y_adjustment + y_translation_for_aspect_ratio_alignment_mode) / zoom_y) + 1.0) / 2.0;
DataBounds {
x_min: min_x,
x_max: max_x,
y_min: min_y,
y_max: max_y,
}
}
pub fn get_camera_matrix_from_bounds(view_params: &ViewParams, data_bounds: &DataBounds) -> [f32; 16] {
let aspect_ratio_mode = view_params.aspect_ratio_mode;
let aspect_ratio_alignment_mode = view_params.aspect_ratio_alignment_mode;
let bounds = &view_params.margins;
let margin_top = bounds.as_ref().and_then(|m| m.margin_top).unwrap_or(0.0);
let margin_right = bounds.as_ref().and_then(|m| m.margin_right).unwrap_or(0.0);
let margin_bottom = bounds.as_ref().and_then(|m| m.margin_bottom).unwrap_or(0.0);
let margin_left = bounds.as_ref().and_then(|m| m.margin_left).unwrap_or(0.0);
let viewport_w = view_params.width as f32;
let viewport_h = view_params.height as f32;
let layer_w = viewport_w - margin_left - margin_right;
let layer_h = viewport_h - margin_top - margin_bottom;
let layer_aspect_ratio = layer_w / layer_h;
let mut x_scale_for_aspect_ratio_mode = 1.0_f32;
let mut y_scale_for_aspect_ratio_mode = 1.0_f32;
match aspect_ratio_mode {
AspectRatioMode::Ignore => {}
AspectRatioMode::Contain => {
if layer_aspect_ratio > 1.0 {
x_scale_for_aspect_ratio_mode = layer_aspect_ratio;
} else if layer_aspect_ratio < 1.0 {
y_scale_for_aspect_ratio_mode = 1.0 / layer_aspect_ratio;
}
}
AspectRatioMode::Cover => {
if layer_aspect_ratio > 1.0 {
y_scale_for_aspect_ratio_mode = 1.0 / layer_aspect_ratio;
} else if layer_aspect_ratio < 1.0 {
x_scale_for_aspect_ratio_mode = layer_aspect_ratio;
}
}
}
let mut x_translation_for_aspect_ratio_alignment_mode = 0.0_f32;
let mut y_translation_for_aspect_ratio_alignment_mode = 0.0_f32;
match aspect_ratio_alignment_mode {
AspectRatioAlignmentMode::Center => {}
AspectRatioAlignmentMode::Start => {
x_translation_for_aspect_ratio_alignment_mode = x_scale_for_aspect_ratio_mode - 1.0;
y_translation_for_aspect_ratio_alignment_mode = y_scale_for_aspect_ratio_mode - 1.0;
}
AspectRatioAlignmentMode::End => {
x_translation_for_aspect_ratio_alignment_mode = 1.0 - x_scale_for_aspect_ratio_mode;
y_translation_for_aspect_ratio_alignment_mode = 1.0 - y_scale_for_aspect_ratio_mode;
}
}
let x_adjustment = x_scale_for_aspect_ratio_mode - 1.0;
let y_adjustment = y_scale_for_aspect_ratio_mode - 1.0;
let x_range = data_bounds.x_max - data_bounds.x_min;
let y_range = data_bounds.y_max - data_bounds.y_min;
let mut zoom_x = (1.0 + x_adjustment) / x_range;
let mut zoom_y = (1.0 + y_adjustment) / y_range;
if aspect_ratio_mode != AspectRatioMode::Ignore {
let zoom = zoom_x.min(zoom_y);
zoom_x = zoom;
zoom_y = zoom;
}
let translate_x = x_translation_for_aspect_ratio_alignment_mode - zoom_x * ((data_bounds.x_min + data_bounds.x_max) - 1.0);
let translate_y = y_translation_for_aspect_ratio_alignment_mode - zoom_y * ((data_bounds.y_min + data_bounds.y_max) - 1.0);
[
zoom_x, 0.0, 0.0, 0.0,
0.0, zoom_y, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
translate_x, translate_y, 0.0, 1.0,
]
}