jengine/renderer/utils.rs
1// ── Letterbox viewport math ───────────────────────────────────────────────────
2//
3// Computes the largest axis-uniform scaled rectangle that fits the logical
4// (game) resolution inside the physical (window) resolution, centred on both
5// axes. The resulting `Viewport` describes the scissor / viewport rectangle
6// to pass to the GPU.
7
8use crate::window::WindowConfig;
9
10// ── Viewport ──────────────────────────────────────────────────────────────────
11
12/// Axis-aligned rectangle in physical pixels that centres the game view while
13/// preserving its aspect ratio (letterbox / pillarbox).
14///
15/// Use `x`, `y` as the top-left origin and `width`, `height` as the extent.
16/// All values are in physical pixels, ready for a GPU scissor or viewport call.
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct Viewport {
19 /// Horizontal offset from the left edge of the window in physical pixels.
20 pub x: f32,
21 /// Vertical offset from the top edge of the window in physical pixels.
22 pub y: f32,
23 /// Width of the game view in physical pixels.
24 pub width: f32,
25 /// Height of the game view in physical pixels.
26 pub height: f32,
27}
28
29// ── letterbox_viewport ────────────────────────────────────────────────────────
30
31/// Calculate the letterbox `Viewport` for `config`.
32///
33/// The uniform scale factor is:
34/// ```text
35/// scale = min(physical_width / logical_width,
36/// physical_height / logical_height)
37/// ```
38/// The centring offsets are:
39/// ```text
40/// x = (physical_width - logical_width * scale) / 2
41/// y = (physical_height - logical_height * scale) / 2
42/// ```
43///
44/// Returns a zero-sized `Viewport` at the origin when either logical dimension
45/// is zero (avoids a division-by-zero and produces a safe no-op rectangle).
46pub fn letterbox_viewport(config: &WindowConfig) -> Viewport {
47 if config.logical_width == 0 || config.logical_height == 0 {
48 return Viewport { x: 0.0, y: 0.0, width: 0.0, height: 0.0 };
49 }
50
51 let pw = config.physical_width as f32;
52 let ph = config.physical_height as f32;
53 let lw = config.logical_width as f32;
54 let lh = config.logical_height as f32;
55
56 let scale = (pw / lw).min(ph / lh);
57
58 let width = lw * scale;
59 let height = lh * scale;
60 let x = (pw - width) / 2.0;
61 let y = (ph - height) / 2.0;
62
63 Viewport { x, y, width, height }
64}
65
66// ── Tests ──────────────────────────────────────────────────────────────────────
67