65 lines
3 KiB
Bash
Executable file
65 lines
3 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}
|
|
# The sheet's pixels to a design unit of the demo (demos/kit.lua, K.sprite): each frame is 288 design
|
|
# units wide, rendered from a view this many times 1024 by 768.
|
|
DENSITY=2.25
|
|
RENDER_W=$(python3 -c "print(round(1024 * $DENSITY))")
|
|
RENDER_H=$(python3 -c "print(round(768 * $DENSITY))")
|
|
FRAME_W=$(python3 -c "print(round(288 * $DENSITY))")
|
|
|
|
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 -C ${RENDER_W}x$RENDER_H -x $RENDER_W -y $RENDER_H --deterministic -d "$SCRATCH/data" "$SCRIPT" > "$SCRATCH/run.log" 2>&1 || true
|
|
python3 - "$SCRATCH/data" "$REPO/demos/neonCoast/art/$SHEET.png" "$FRAME_W" <<'PY'
|
|
import glob, os, sys
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
data, out, width = sys.argv[1], sys.argv[2], int(sys.argv[3])
|
|
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
|
|
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
|