singe/util/neonCoastCar.sh
2026-10-05 22:12:53 -05:00

60 lines
2.6 KiB
Bash
Executable file

#!/bin/bash
# Renders the Neon Coast car with Singe and makes demos/neonCoast/art/car.png from it: one frame per
# steering angle, side by side, each with true alpha from a black and a white render of the same
# view. Runs headless; nothing appears on screen.
#
# util/neonCoastCar.sh
set -euo pipefail
REPO=$(cd "$(dirname "$0")/.." && pwd)
BIN=$(ls "$REPO"/.builddir/Singe-v*-Linux-x86_64 | head -1)
SCRATCH=${SCRATCH:-/tmp/neonCoastCar}
rm -rf "$SCRATCH"
mkdir -p "$SCRATCH/run/car"
cp "$REPO/util/neonCoastCar.singe" "$SCRATCH/run/car/"
# With "traffic" it makes art/traffic.png instead: the car straight from behind in other paints.
SHEET=car
if [ "${1:-}" = "traffic" ]; then
SHEET=traffic
printf 'CAR_TRAFFIC = true\ndofile(singeGetScriptPath():match("(.*[/\\\\])") .. "neonCoastCar.singe")\n' > "$SCRATCH/run/car/traffic.singe"
fi
SCRIPT=car/neonCoastCar.singe
if [ "$SHEET" = "traffic" ]; then
SCRIPT=car/traffic.singe
fi
cd "$SCRATCH/run"
SDL_VIDEODRIVER=offscreen SDL_AUDIO_DRIVER=dummy timeout -k 10 120 "$BIN" -k -s -w -C 1024x768 -x 1024 -y 768 --deterministic -d "$SCRATCH/data" "$SCRIPT" > "$SCRATCH/run.log" 2>&1 || true
python3 - "$SCRATCH/data" "$REPO/demos/neonCoast/art/$SHEET.png" <<'PY'
import glob, os, sys
import numpy as np
from PIL import Image
data, out = sys.argv[1], sys.argv[2]
shots = {os.path.basename(p)[:-4]: p for p in glob.glob(data + "/**/shot*.png", recursive=True)}
takes = sorted({int(k[4:-1]) for k in shots})
frames = []
for a in takes:
b = np.asarray(Image.open(shots["shot%02db" % a]).convert("RGB")).astype(np.float64)
w = np.asarray(Image.open(shots["shot%02dw" % a]).convert("RGB")).astype(np.float64)
# Over black a pixel is c * alpha; over white it is c * alpha + (1 - alpha) * white, white being
# whatever the background actually came out as.
white = (w[:4, :4] - b[:4, :4]).mean()
alpha = np.clip(1 - (w - b).mean(axis=2) / white, 0, 1)
colour = np.where(alpha[..., None] > 0.004, b / np.maximum(alpha[..., None], 0.004), 0)
frames.append(np.dstack([np.clip(colour, 0, 255), alpha * 255]))
# One box round every frame, so the car does not jump when the frame changes.
mask = np.zeros(frames[0].shape[:2], bool)
for f in frames:
mask |= f[..., 3] > 8
ys, xs = np.where(mask)
x0, x1, y0, y1 = xs.min() - 2, xs.max() + 3, ys.min() - 2, ys.max() + 3
width = 288
height = int((y1 - y0) * width / (x1 - x0))
sheet = Image.new("RGBA", (width * len(frames), height), (0, 0, 0, 0))
for i, f in enumerate(frames):
im = Image.fromarray(f[y0:y1, x0:x1].astype(np.uint8), "RGBA").resize((width, height), Image.LANCZOS)
sheet.paste(im, (i * width, 0))
sheet.save(out)
print(os.path.basename(out), sheet.size, "frames", len(takes))
PY