pub const MATRIX_ROWS: u32 = 12; pub const MATRIX_COLS: u32 = 7; const BASE_MATRIX_SPAN: f32 = 24.0; const MATRIX_SPAN_GROWTH: f32 = 0.6; const MIN_MATRIX_SPAN: f32 = 24.0; const MAX_MATRIX_SPAN: f32 = 58.0; const MIN_CELL_SPACING: f32 = 0.52; const MAX_CELL_SPACING: f32 = 3.8; const MIN_BOARD_PADDING: f32 = 2.6; const MAX_BOARD_PADDING: f32 = 6.8; const MATRIX_OFFSET_Y: f32 = -2.4; const MATRIX_OFFSET_Z: f32 = 12.0; const CAMERA_FOV: f32 = 36.0; const CAMERA_DISTANCE_MIN: f32 = 30.0; const CAMERA_DISTANCE_MAX: f32 = 122.0; const CAMERA_FIT_PADDING: f32 = 1.04; const CAMERA_ELEVATION_DEG: f32 = 64.0; const CAMERA_TARGET_X: f32 = 0.0; const CAMERA_TARGET_Y: f32 = MATRIX_OFFSET_Y + 0.2; const CAMERA_TARGET_Z: f32 = MATRIX_OFFSET_Z - 0.4; pub struct MatrixLayout { pub cell_spacing: f32, pub board_width: f32, pub board_depth: f32, pub board_padding: f32, pub label_float_offset: f32, } impl MatrixLayout { pub fn new(rows: u32, cols: u32) -> Self { let longest_edge = rows.max(cols).max(1) as f32; let edge_span = (longest_edge - 1.0).max(1.0); let target_span = (BASE_MATRIX_SPAN + edge_span * MATRIX_SPAN_GROWTH) .clamp(MIN_MATRIX_SPAN, MAX_MATRIX_SPAN); let cell_spacing = (target_span / edge_span).clamp(MIN_CELL_SPACING, MAX_CELL_SPACING); let board_width = cols.max(1) as f32 * cell_spacing; let board_depth = rows.max(1) as f32 * cell_spacing; let board_padding = (cell_spacing * 1.62).clamp(MIN_BOARD_PADDING, MAX_BOARD_PADDING); let label_float_offset = (cell_spacing * 0.42).clamp(0.36, 1.12); Self { cell_spacing, board_width, board_depth, board_padding, label_float_offset, } } } pub fn build_view_projection(aspect: f32, layout: &MatrixLayout) -> [[f32; 4]; 4] { let camera_distance = fit_camera_distance( layout.board_width, layout.board_depth, layout.board_padding, aspect, ); let elevation = CAMERA_ELEVATION_DEG.to_radians(); let target = glam::Vec3::new(CAMERA_TARGET_X, CAMERA_TARGET_Y, CAMERA_TARGET_Z); let eye = glam::Vec3::new( CAMERA_TARGET_X, CAMERA_TARGET_Y + elevation.sin() * camera_distance, CAMERA_TARGET_Z + elevation.cos() * camera_distance, ); let view = glam::Mat4::look_at_rh(eye, target, glam::Vec3::Y); let projection = glam::Mat4::perspective_rh(CAMERA_FOV.to_radians(), aspect, 0.1, 500.0); let open_gl_to_wgpu = glam::Mat4::from_cols_array(&[ 1.0, 0.0, 0.0, 0.0, // 0.0, 1.0, 0.0, 0.0, // 0.0, 0.0, 0.5, 0.0, // 0.0, 0.0, 0.5, 1.0, ]); (open_gl_to_wgpu * projection * view).to_cols_array_2d() } pub fn glyph_world_position( row: u32, col: u32, rows: u32, cols: u32, layout: &MatrixLayout, pressure: f32, ) -> ([f32; 4], f32) { let normalized = pressure.clamp(0.0, 1.0); let x = (col as f32 - cols as f32 / 2.0 + 0.5) * layout.cell_spacing; let z = (row as f32 - rows as f32 / 2.0 + 0.5) * layout.cell_spacing; ( [ x, MATRIX_OFFSET_Y + layout.label_float_offset, MATRIX_OFFSET_Z + z, 1.0, ], normalized, ) } fn fit_camera_distance(board_width: f32, board_depth: f32, board_padding: f32, aspect: f32) -> f32 { let padded_width = board_width + board_padding * 2.0; let padded_depth = board_depth + board_padding * 2.0; let safe_aspect = aspect.max(0.5); let effective_half_span = (padded_depth * 0.5).max((padded_width * 0.5) / safe_aspect); let fov_radians = (CAMERA_FOV * 0.5).to_radians(); let fit_distance = (effective_half_span / fov_radians.tan()) * CAMERA_FIT_PADDING; fit_distance.clamp(CAMERA_DISTANCE_MIN, CAMERA_DISTANCE_MAX) }