RECORDANDRONCO/tools/mbfacings.py

89 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Top-down vehicle renders -> 16-facing rotation sheets.
flux renders each vehicle facing NORTH from directly above; PIL rotates into 16
frames (OpenRA facing order: north first, counter-clockwise), packed into one
horizontal strip with FrameSize metadata. Geometrically correct, unlike rotating
a 3/4-view sprite. Also: terrain tile + wax crystal density sheet.
"""
import os, sys
sys.path.insert(0, os.path.dirname(__file__))
from mbgen import run, upload # noqa: E402
from PIL import Image # noqa: E402
from PIL.PngImagePlugin import PngInfo # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), '..')
RENDERS = os.path.join(ROOT, 'assets_src', 'renders_topdown')
ASSETS = os.path.join(ROOT, 'mods', 'vinylgod', 'sequences', 'assets')
os.makedirs(RENDERS, exist_ok=True)
MRP = 'rusty junkyard robot aesthetic, rust metal with hot pink trim, '
STR = 'glossy seamless white tech aesthetic, soft blue ring lights, '
SUF = ', viewed from directly above, top-down aerial view, vehicle pointing straight up toward the top of the image, single object centered, video game sprite, plain flat grey background, no text, no shadow'
VEHICLES = [
('digger', 26, MRP + 'forklift harvester truck carrying crates of vinyl records'),
('popupvan', 26, MRP + 'delivery truck covered in band stickers with roof loudspeaker'),
('spinner', 20, MRP + 'small round robot riding a single vinyl record wheel with blade arms'),
('stacktank', 30, MRP + 'heavy tracked tank made of stacked speaker cabinets with subwoofer turret'),
('basscannon', 30, MRP + 'long six-wheeled flatbed truck with one colossal subwoofer cannon'),
('streamvan', 26, STR + 'sleek white autonomous delivery van with blue play-button roof logo'),
('scraper', 24, STR + 'white autonomous street sweeper vehicle with glowing blue front intake'),
('drmwalker', 30, STR + 'white quad-legged mech with one blue ring light, seen from above'),
]
def sheet16(im, size, out):
im = im.crop(im.getbbox() or (0, 0, im.width, im.height))
im.thumbnail((size * 4, size * 4), Image.LANCZOS) # rotate at 4x, downscale after
strip = Image.new('RGBA', (size * 16, size), (0, 0, 0, 0))
for i in range(16):
fr = im.rotate(i * 22.5, expand=False, resample=Image.BICUBIC)
fr.thumbnail((size, size), Image.LANCZOS)
strip.paste(fr, (i * size + (size - fr.width) // 2, (size - fr.height) // 2), fr)
meta = PngInfo()
meta.add_text('FrameSize', f'{size},{size}')
strip.save(out, pnginfo=meta)
print(f'[facings] {out}', flush=True)
for name, size, prompt in VEHICLES:
raw = os.path.join(RENDERS, f'{name}_raw.png')
cut = os.path.join(RENDERS, f'{name}.png')
if not os.path.exists(cut):
run('flux_local', {'prompt': prompt + SUF}, raw)
run('bg_remove_local', {}, cut, asset_id=upload(raw))
sheet16(Image.open(cut).convert('RGBA'), size, os.path.join(ASSETS, f'{name}.png'))
# terrain tile: seamless ground texture -> 32x32
traw = os.path.join(RENDERS, 'terrain_raw.png')
if not os.path.exists(traw):
run('flux_local', {'prompt': 'seamless tileable texture, cracked dark asphalt with faint dust and tiny speckles, muted grey brown, flat even lighting, top down, no objects'}, traw)
t = Image.open(traw).convert('RGBA').resize((64, 64), Image.LANCZOS).crop((16, 16, 48, 48))
t.save(os.path.join(ROOT, 'mods', 'vinylgod', 'tilesets', 'vinylgod', 'vinylgod.png'))
print('[terrain] tile written', flush=True)
# wax crystals: one cluster render -> 12 density frames
wraw = os.path.join(RENDERS, 'wax_raw.png')
wcut = os.path.join(RENDERS, 'wax.png')
if not os.path.exists(wcut):
run('flux_local', {'prompt': 'cluster of jagged black vinyl crystal shards glowing hot pink from the cracks, viewed from directly above, top down, video game resource sprite, single cluster centered, plain flat grey background'}, wraw)
run('bg_remove_local', {}, wcut, asset_id=upload(wraw))
w = Image.open(wcut).convert('RGBA')
w = w.crop(w.getbbox() or (0, 0, w.width, w.height))
strip = Image.new('RGBA', (32 * 12, 32), (0, 0, 0, 0))
spots = [(16, 16), (7, 9), (24, 10), (9, 24), (24, 24), (16, 6), (5, 17), (27, 18), (16, 26), (11, 13), (21, 15), (16, 20)]
for f in range(12):
for i in range(f + 1):
s = 10 + (6 if i == 0 else 0)
c = w.copy()
c.thumbnail((s, s), Image.LANCZOS)
c = c.rotate(i * 47, expand=False)
x, y = spots[i]
strip.paste(c, (f * 32 + x - c.width // 2, y - c.height // 2), c)
meta = PngInfo()
meta.add_text('FrameSize', '32,32')
strip.save(os.path.join(ASSETS, 'wax.png'), pnginfo=meta)
print('[wax] density sheet written', flush=True)
print('ALL DONE')