Newer
Older
rpg / packages / engine / tools / atlas.mjs
/**
 * Сборка спрайт-атласа (Pixi Spritesheet): кадры в одну строку + JSON-описание.
 * Модуль ничего не знает о контенте: кадры — массив Uint8Array w×h×4 (RGBA),
 * имена и размеры задаёт вызывающий.
 */
import { encodePng } from './png.mjs';

/**
 * Кадры в строку атласа шириной frames.length × frameW.
 * @param frames  массив Uint8Array (frameW×frameH×4, RGBA)
 * @param names   имена кадров (ключи JSON.frames)
 * @param opts    { frameW, frameH, image, animations? } — image: имя PNG рядом
 *                с JSON; animations: Record<имя, number[]> (индексы в names) —
 *                попадает в JSON.animations списками имён кадров (формат Pixi)
 * @returns { png: Buffer, json: object }
 */
export function buildAtlas(frames, names, { frameW, frameH, image, animations }) {
    if (frames.length !== names.length) {
        throw new Error(`кадры и имена разной длины: ${frames.length} != ${names.length}`);
    }
    const sheetW = frameW * frames.length;
    const sheet = new Uint8Array(sheetW * frameH * 4);
    const framesJson = {};
    frames.forEach((src, i) => {
        if (src.length !== frameW * frameH * 4) {
            throw new Error(`кадр ${names[i]}: ожидается ${frameW}×${frameH}×4 байт, пришло ${src.length}`);
        }
        for (let y = 0; y < frameH; y++) {
            for (let x = 0; x < frameW; x++) {
                sheet.set(src.subarray((y * frameW + x) * 4, (y * frameW + x) * 4 + 4), ((y * sheetW) + i * frameW + x) * 4);
            }
        }
        framesJson[names[i]] = {
            frame: { x: i * frameW, y: 0, w: frameW, h: frameH },
            rotated: false,
            trimmed: false,
            sourceSize: { w: frameW, h: frameH }
        };
    });
    const json = {
        frames: framesJson,
        meta: { image, size: { w: sheetW, h: frameH }, scale: 1 }
    };
    if (animations) {
        // Формат Pixi Spritesheet: имя анимации → список имён кадров.
        json.animations = Object.fromEntries(
            Object.entries(animations).map(([name, idx]) => [
                name,
                idx.map((i) => {
                    if (i < 0 || i >= names.length) {
                        throw new Error(`анимация ${name}: индекс ${i} вне кадров (${names.length})`);
                    }
                    return names[i];
                })
            ])
        );
    }
    return { png: encodePng(sheetW, frameH, Buffer.from(sheet)), json };
}