/**
* Перекладка слотов модели на палитру сцены. Нужна после импорта .vox
* (там слоты 1..N без отношения к сцене): цвет модели замещается цветом
* сцены — точным совпадением hex или ближайшим по RGB-расстоянию
* (детерминированно, при равенстве — меньший слот).
*/
import { hexToRgb } from '../voxel/grid';
import { decodeModel, encodeModel } from './format';
import type { VoxelPalette } from '../voxel/grid';
import type { VoxelModel } from './format';
export function remapSlots(model: VoxelModel, palette: VoxelPalette): VoxelModel {
const scene = Object.entries(palette.colors).map(([slot, hex]) => ({
slot: Number(slot),
rgb: hexToRgb(hex),
}));
// слот модели → слот сцены
const mapping = new Map<number, number>();
for (const [slot, hex] of Object.entries(model.palette)) {
const rgb = hexToRgb(hex);
let best = scene[0], bestD = Infinity;
for (const s of scene) {
const d = (s.rgb[0] - rgb[0]) ** 2 + (s.rgb[1] - rgb[1]) ** 2 + (s.rgb[2] - rgb[2]) ** 2;
if (d < bestD || (best && d === bestD && s.slot < best.slot)) { bestD = d; best = s; }
}
if (best) mapping.set(Number(slot), best.slot); // пустая палитра — слот не перекладывается
}
const grid = decodeModel(model);
for (let i = 0; i < grid.data.length; i++) {
const b = grid.data[i];
if (b !== 0) grid.data[i] = mapping.get(b) ?? b;
}
// палитра результата — занятые слоты сцены с их hex
const used = new Set(grid.data.filter((b) => b !== 0));
const out = Object.fromEntries(
[...used].map((slot) => [slot, palette.colors[slot]]),
);
return encodeModel(grid, out);
}