summaryrefslogtreecommitdiff
path: root/src/gui/crt.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/crt.zig')
-rw-r--r--src/gui/crt.zig101
1 files changed, 0 insertions, 101 deletions
diff --git a/src/gui/crt.zig b/src/gui/crt.zig
deleted file mode 100644
index 2a5ea7e4..00000000
--- a/src/gui/crt.zig
+++ /dev/null
@@ -1,101 +0,0 @@
-//! 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.016;
-
-pub const Point = struct { x: f32, y: f32 };
-pub const Effects = struct { crt: bool = false, ripple: bool = false, glitch: bool = false };
-pub const Frame = struct { effects: Effects = .{}, time_seconds: f32 = 0 };
-
-fn fract(x: f32) f32 {
- return x - @floor(x);
-}
-
-fn smoothstep(lo: f32, hi: f32, value: f32) f32 {
- const t = std.math.clamp((value - lo) / (hi - lo), 0, 1);
- return t * t * (3 - 2 * t);
-}
-
-/// Match `sceneUv` in crt.frag.glsl. This is a forward screen→sample mapping:
-/// a physical pointer is sent to the logical scene pixel displayed under it.
-pub fn mapScene(x: f32, y: f32, w: f32, h: f32, effects: Effects, time_seconds: f32) ?Point {
- const safe_w = @max(1, w);
- const safe_h = @max(1, h);
- const ccx = x / safe_w * 2.0 - 1.0;
- const ccy = y / safe_h * 2.0 - 1.0;
- const s = 1.0 + barrel * (ccx * ccx + ccy * ccy) * @as(f32, @floatFromInt(@intFromBool(effects.crt)));
- var u = ccx * s * 0.5 + 0.5;
- var v = ccy * s * 0.5 + 0.5;
- // The CRT shader paints no scene when this barrel-mapped coordinate lies
- // on or beyond the tube edge. There is no logical edge cell under those
- // black pixels, so input must be inert there rather than clamped.
- if (effects.crt and (u <= 0 or u >= 1 or v <= 0 or v >= 1)) return null;
- if (effects.ripple) {
- const cx = u - 0.5;
- const cy = v - 0.5;
- const radius = @sqrt(cx * cx + cy * cy);
- const envelope = 1.0 - smoothstep(0.04, 0.72, radius);
- const wave = @sin(radius * 42.0 - time_seconds * 5.2);
- const len = @max(radius, 0.00001);
- u += cx / len * wave * envelope * 0.0032;
- v += cy / len * wave * envelope * 0.0032;
- }
- if (effects.glitch) {
- const band = @floor(v * 96.0);
- const tick = @floor(time_seconds * 13.0);
- const seed = noiseAt(band, tick);
- const tear = smoothstep(0.955, 1.0, seed);
- u += (noiseAt(seed, band) - 0.5) * tear * 0.020;
- }
- return .{
- .x = std.math.clamp(u * safe_w, 0, safe_w - 1),
- .y = std.math.clamp(v * safe_h, 0, safe_h - 1),
- };
-}
-
-fn noiseAt(x: f32, y: f32) f32 {
- var qx = fract(x * 0.1031);
- var qy = fract(y * 0.1031);
- var qz = fract(x * 0.1031);
- const d = qx * (qy + 33.33) + qy * (qz + 33.33) + qz * (qx + 33.33);
- qx += d;
- qy += d;
- qz += d;
- return fract((qx + qy) * qz);
-}
-
-test "map matches the shader's barrel formula" {
- // center is a fixed point
- const center = mapScene(500, 250, 1000, 500, .{ .crt = true }, 0).?;
- try std.testing.expectApproxEqAbs(@as(f32, 500), center.x, 0.001);
- try std.testing.expectApproxEqAbs(@as(f32, 250), center.y, 0.001);
- // Corners barrel beyond the tube and are black/inert in the shader.
- try std.testing.expect(mapScene(0, 0, 1000, 500, .{ .crt = true }, 0) == null);
- try std.testing.expect(mapScene(1000, 500, 1000, 500, .{ .crt = true }, 0) == null);
- // (100,100) of 1000x500: cc=(-0.8,-0.6), r2=1.0, s=1.016 ->
- // cc'=(-0.8128,-0.6096) -> (93.6, 97.6)
- const near_edge = mapScene(100, 100, 1000, 500, .{ .crt = true }, 0).?;
- try std.testing.expectApproxEqAbs(@as(f32, 93.6), near_edge.x, 0.01);
- try std.testing.expectApproxEqAbs(@as(f32, 97.6), near_edge.y, 0.01);
- // (900,250): cc=(0.8,0), r2=0.64, s=1.01024 -> x'=904.096
- const side = mapScene(900, 250, 1000, 500, .{ .crt = true }, 0).?;
- try std.testing.expectApproxEqAbs(@as(f32, 904.096), side.x, 0.01);
-}
-
-test "scene mapping composes ripple and deterministic glitch" {
- const plain = mapScene(300, 200, 800, 600, .{}, 2.5).?;
- try std.testing.expectApproxEqAbs(@as(f32, 300), plain.x, 0.001);
- try std.testing.expectApproxEqAbs(@as(f32, 200), plain.y, 0.001);
- const ripple = mapScene(300, 200, 800, 600, .{ .ripple = true }, 2.5).?;
- try std.testing.expect(ripple.x != plain.x or ripple.y != plain.y);
- try std.testing.expectEqual(
- mapScene(300, 200, 800, 600, .{ .glitch = true }, 2.5).?,
- mapScene(300, 200, 800, 600, .{ .glitch = true }, 2.5).?,
- );
-}