Skip to main content

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