summaryrefslogtreecommitdiff
path: root/src/gui/crt.zig
blob: 9cf8159369baaa2c88ba14cbd53d07508264808d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//! The Crt builtin's screen geometry, shared between the gui shell's input
//! mapping and its shader: crt.frag.glsl shows the scene point
//! uv' = cc*(1 + barrel*r2) at screen point cc, so a physical mouse position
//! must pass through the SAME forward transform before it becomes a cell.
//! Pure std, so the unit-test step can pin it against the shader formula.

const std = @import("std");

/// MUST match BARREL in shaders/crt.frag.glsl
pub const barrel: f32 = 0.012;

pub const Point = struct { x: f32, y: f32 };

/// physical window pixels -> the scene pixels the shader displays there,
/// clamped to the window. The shader's phosphor quantization and chromatic
/// offset displace at most a few pixels — well under a cell — and are
/// ignored here.
pub fn map(x: f32, y: f32, w: f32, h: f32) Point {
    const ccx = x / w * 2.0 - 1.0;
    const ccy = y / h * 2.0 - 1.0;
    const s = 1.0 + barrel * (ccx * ccx + ccy * ccy);
    return .{
        .x = std.math.clamp((ccx * s + 1.0) * 0.5 * w, 0, w - 1),
        .y = std.math.clamp((ccy * s + 1.0) * 0.5 * h, 0, h - 1),
    };
}

test "map matches the shader's barrel formula" {
    // center is a fixed point
    try std.testing.expectApproxEqAbs(@as(f32, 500), map(500, 250, 1000, 500).x, 0.001);
    try std.testing.expectApproxEqAbs(@as(f32, 250), map(500, 250, 1000, 500).y, 0.001);
    // corners: cc=(±1,±1), r2=2, scale 1.024 pushes past the tube -> clamped
    try std.testing.expectEqual(@as(f32, 0), map(0, 0, 1000, 500).x);
    try std.testing.expectEqual(@as(f32, 0), map(0, 0, 1000, 500).y);
    try std.testing.expectEqual(@as(f32, 999), map(1000, 500, 1000, 500).x);
    try std.testing.expectEqual(@as(f32, 499), map(1000, 500, 1000, 500).y);
    // (100,100) of 1000x500: cc=(-0.8,-0.6), r2=1.0, s=1.012 ->
    // cc'=(-0.8096,-0.6072) -> (95.2, 98.2)
    try std.testing.expectApproxEqAbs(@as(f32, 95.2), map(100, 100, 1000, 500).x, 0.01);
    try std.testing.expectApproxEqAbs(@as(f32, 98.2), map(100, 100, 1000, 500).y, 0.01);
    // (900,250): cc=(0.8,0), r2=0.64, s=1.00768 -> x'=903.072
    try std.testing.expectApproxEqAbs(@as(f32, 903.072), map(900, 250, 1000, 500).x, 0.01);
}