Files
Kravickas--PS4-apps/tools/make_clock_light_norm.py
2026-10-16 08:31:15 +02:00

60 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Clock mode's internal-light normaliser: the 90th percentile of the light
field at least 30 px inside the slab's rim (at the 1/4-resolution grid, rim samples every 3 px), per
2-degree step of the light's screen direction - exactly what the approved preview used, which a
single GPU pass cannot compute. Writes src/clock_light_norm.h (180 floats, index = angle / 2 deg,
angle = atan2(l2y, l2x) in [0, 360))."""
import os, math
import numpy as np
HERE = os.path.dirname(os.path.abspath(__file__)); ROOT = os.path.dirname(HERE)
W, H, CX, CY, PW, PH, RC = 1920, 1080, 960.0, 540.0, 1152.0, 648.0, 56.0
LS, S0, SPREAD, STEP = 170.0, 3.0, 0.07, 3.0
def rrect_sdf(x, y):
qx = np.abs(x - CX) - PW / 2 + RC; qy = np.abs(y - CY) - PH / 2 + RC
return np.hypot(np.maximum(qx, 0), np.maximum(qy, 0)) + np.minimum(np.maximum(qx, qy), 0) - RC
def rim_points():
hx, hy = PW / 2 - RC, PH / 2 - RC; pts = []; nrm = []
for (cx, cy, a0) in ((hx, -hy, -90), (hx, hy, 0), (-hx, hy, 90), (-hx, -hy, 180)):
n = int(math.ceil(math.pi / 2 * RC / STEP))
for i in range(n):
a = math.radians(a0 + 90 * (i + 0.5) / n)
pts.append((CX + cx + RC * math.cos(a), CY + cy + RC * math.sin(a))); nrm.append((math.cos(a), math.sin(a)))
for (x0, y0, x1, y1, nx, ny) in ((-hx, -PH / 2, hx, -PH / 2, 0, -1), (PW / 2, -hy, PW / 2, hy, 1, 0),
(hx, PH / 2, -hx, PH / 2, 0, 1), (-PW / 2, hy, -PW / 2, -hy, -1, 0)):
L = math.hypot(x1 - x0, y1 - y0); n = int(L / STEP)
for i in range(n):
t = (i + 0.5) / n; pts.append((CX + x0 + (x1 - x0) * t, CY + y0 + (y1 - y0) * t)); nrm.append((nx, ny))
return np.array(pts), np.array(nrm)
def main():
P0, N0 = rim_points()
gy, gx = np.mgrid[0:H // 4, 0:W // 4]; X = (gx + 0.5) * 4; Y = (gy + 0.5) * 4
d = rrect_sdf(X, Y); sel = d < -30.0; X = X[sel]; Y = Y[sel] # every point >= 30 px inside
out = []
for k in range(180):
a = math.radians(2 * k); l2 = np.array([math.cos(a), math.sin(a)])
cosi = -(N0 @ l2); ok = cosi > 0; P, N, c = P0[ok], N0[ok], cosi[ok]
eta = 1 / 1.5; kk = 1 - eta * eta * (1 - c ** 2)
t = eta * l2[None, :] + (eta * c - np.sqrt(kk))[:, None] * N; t /= np.linalg.norm(t, axis=1, keepdims=True)
w = c * (1 - (0.04 + 0.96 * (1 - c) ** 5)) * STEP
I = np.zeros(X.size)
for b in range(0, len(P), 128):
vx = X[:, None] - P[None, b:b + 128, 0]; vy = Y[:, None] - P[None, b:b + 128, 1]
al = vx * t[None, b:b + 128, 0] + vy * t[None, b:b + 128, 1]; pe = np.abs(vx * t[None, b:b + 128, 1] - vy * t[None, b:b + 128, 0])
sig = S0 + SPREAD * np.maximum(al, 0)
I += np.where(al > 0, w[None, b:b + 128] * np.exp(-np.sqrt(vx * vx + vy * vy) / LS) * np.exp(-0.5 * (pe / sig) ** 2) / (sig * 2.5066), 0).sum(1)
out.append(np.percentile(I, 90))
with open(os.path.join(ROOT, "src", "clock_light_norm.h"), "w") as f:
f.write("/* Generated by tools/make_clock_light_norm.py: the internal light's normaliser per 2 degrees of\n"
" the light's screen direction (atan2(l2y, l2x), 0 .. 358). */\n#pragma once\n")
f.write("static const float clock_light_norm[180] = {\n")
for i in range(0, 180, 6):
f.write(" " + " ".join("%.6ff," % v for v in out[i:i + 6]) + "\n")
f.write("};\n")
print("normaliser: min %.4f max %.4f" % (min(out), max(out)))
if __name__ == "__main__":
main()