// Aspekt-korrekte Orthografie + Papier-mm-Strichbreiten-Formel. // 1:1-Port von `computeOrthoMatrix` / dem strokeScale-Pfad aus // `src/plan/glPlan/glPlanRender.ts`. // // WICHTIG (Konventionsunterschied WebGL vs. wgpu): // - WebGL-Clip-Z liegt in [-1, 1]. // - wgpu/WebGPU-Clip-Z liegt in [0, 1]. // Fuer den reinen 2D-Fall (z=0) spielt das keine Rolle: wir setzen z konstant auf // 0.0 im Shader; die XY-Abbildung ist identisch. Die Matrix hier ist daher exakt // die WebGL-Ortho (Spalten-Major), unveraendert uebernehmbar. use crate::tessellate::PX_PER_M; use crate::types::ViewBox; /// 4x4-Matrix, Spalten-Major (WebGL/wgpu-Konvention: `m[col*4 + row]`). pub type Mat4 = [f32; 16]; /// Einheitsmatrix. pub fn identity() -> Mat4 { let mut m = [0.0f32; 16]; m[0] = 1.0; m[5] = 1.0; m[10] = 1.0; m[15] = 1.0; m } /// Orthografische Projektion [left,right] x [bottom,top] -> Clip. Spalten-Major. /// Die Z-Zeile bildet z=0 auf 0 ab (fuer wgpu unschaedlich, siehe Modul-Kopf). fn ortho(left: f32, right: f32, bottom: f32, top: f32) -> Mat4 { let mut m = identity(); let w = right - left; let h = top - bottom; m[0] = 2.0 / w; m[5] = 2.0 / h; m[10] = -1.0; m[12] = -(right + left) / w; m[13] = -(top + bottom) / h; m } /// Projektionsmatrix fuer einen viewBox, aspekt-korrigiert wie SVG /// `preserveAspectRatio="xMidYMid meet"`: der viewBox wird auf das /// Canvas-Seitenverhaeltnis gedehnt (zentriert), damit die Skalierung in X und Y /// gleich ist (keine Verzerrung, Perpendikel bleiben senkrecht). /// /// Eingabe-Positionen sind bereits im BILDSCHIRM-Raum (`to_screen`), daher keine /// zusaetzliche Meter-Skalierung. Bildschirm-Y zeigt nach unten -> top = vb.y, /// bottom = vb.y + vb.h (kleineres Y oben -> Clip +1). pub fn compute_ortho_matrix(view_box: ViewBox, canvas_w: f32, canvas_h: f32) -> Mat4 { let ViewBox { x, y, w, h } = corrected_view_box(view_box, canvas_w, canvas_h); // top = y (Bildschirm-Y oben, kleiner), bottom = y + h. ortho(x, x + w, y + h, y) } /// Der aufs Canvas-Seitenverhaeltnis gedehnte (zentrierte) viewBox — exakt die /// Korrektur, die auch in der Ortho-Matrix steckt. Mit ihm laesst sich ein /// Bildschirm-Raum-Punkt direkt in Fenster-Pixel projizieren: /// `px = (s - corrected.xy) * meet_scale(corrected, ...)` (der Textpass braucht /// genau das fuer die Glyphen-Anker). pub fn corrected_view_box(view_box: ViewBox, canvas_w: f32, canvas_h: f32) -> ViewBox { let ViewBox { mut x, mut y, mut w, mut h, } = view_box; if canvas_w > 0.0 && canvas_h > 0.0 && w > 0.0 && h > 0.0 { let c_aspect = canvas_w / canvas_h; let v_aspect = w / h; if c_aspect > v_aspect { // Canvas breiter -> viewBox in der Breite dehnen, X zentrieren. let nw = h * c_aspect; x -= (nw - w) / 2.0; w = nw; } else { // Canvas hoeher -> viewBox in der Hoehe dehnen, Y zentrieren. let nh = w / c_aspect; y -= (nh - h) / 2.0; h = nh; } } ViewBox::new(x, y, w, h) } /// meet-Skala: Geraete-px je viewBox-Einheit (SVG `xMidYMid meet`). pub fn meet_scale(view_box: ViewBox, viewport_w: f32, viewport_h: f32) -> f32 { (viewport_w / view_box.w).min(viewport_h / view_box.h) } /// Umrechnung Papier-mm -> Geraete-px, EXAKT wie der SVG-`printStrokeVb`-Pfad: /// Breite_vb = mm * N/1000 * PX_PER_M (viewBox-Einheiten, skaliert mit Zoom) /// Breite_px = Breite_vb * meetSkala (Geraete-px je viewBox-Einheit) /// -> mm * (N/1000 * PX_PER_M * meet). So gilt: 0.35 mm bei 1:100 = echte 0.35 mm /// Papier (deckungsgleich zum SVG-Renderer). pub fn mm_to_device_px(view_box: ViewBox, viewport_w: f32, viewport_h: f32, paper_scale_n: f32) -> f32 { let meet = meet_scale(view_box, viewport_w, viewport_h); (paper_scale_n / 1000.0) * PX_PER_M * meet }