/**
* Сборка спрайт-атласа (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 };
}