import { PAINT_BG, PAINT_BG_PBR, PAINT_BG_NOISE } from './paint_state.mjs'; const MAX_HISTORY = 64; // Ensure key→value is in map/palette; return index. function _intern(map, palette, key, val) { if (!map.has(key)) { map.set(key, palette.length); palette.push(val); } return map.get(key); } // Brush/tool settings at snapshot time. class Tool_Snapshot { constructor(active_brush_side, paint) { this.active_brush_side = active_brush_side; this.enabled = paint.enabled; this.tool = paint.tool; this.color_left = [...paint.color_left]; this.color_right = [...paint.color_right]; this.metallic_left = paint.metallic_left; this.metallic_right = paint.metallic_right; this.roughness_left = paint.roughness_left; this.roughness_right = paint.roughness_right; this.noise_left = [...paint.noise_left]; this.noise_right = [...paint.noise_right]; } } // Compressed face-paint snapshot. // face_mat[fi] = 0 means default (unpainted); >0 = index into mat_palette. // mat_palette entries = [color_idx, pbr_idx, noise_idx]. class Paint_Snapshot { constructor(paint, face_count, tool) { this.tool = tool; const color_map = new Map(); const pbr_map = new Map(); const noise_map = new Map(); const mat_map = new Map(); this.color_palette = []; this.pbr_palette = []; this.noise_palette = []; this.mat_palette = []; // Index 0 reserved for default background material. color_map.set(PAINT_BG.join(','), 0); this.color_palette.push([...PAINT_BG]); pbr_map.set(PAINT_BG_PBR.join(','), 0); this.pbr_palette.push([...PAINT_BG_PBR]); noise_map.set(PAINT_BG_NOISE.join(','), 0); this.noise_palette.push([...PAINT_BG_NOISE]); mat_map.set('0|0|0', 0); this.mat_palette.push([0, 0, 0]); this.face_mat = new Uint32Array(face_count); // default 0 for (let fi = 0; fi < face_count; fi++) { const c = paint.face_colors.get(fi); const pbr = paint.face_pbr.get(fi); const noise = paint.face_noise.get(fi); if (c === undefined && pbr === undefined && noise === undefined) { continue; } const cv = c ?? PAINT_BG; const pv = pbr ?? PAINT_BG_PBR; const nv = noise ?? PAINT_BG_NOISE; const c_idx = _intern(color_map, this.color_palette, cv.join(','), [...cv]); const pbr_idx = _intern(pbr_map, this.pbr_palette, pv.join(','), [...pv]); const noi_idx = _intern(noise_map, this.noise_palette, nv.join(','), [...nv]); const mat_key = `${c_idx}|${pbr_idx}|${noi_idx}`; const mat_idx = _intern(mat_map, this.mat_palette, mat_key, [c_idx, pbr_idx, noi_idx]); // If the material happens to hash to default (all three indices = 0), leave face_mat = 0. if (mat_idx !== 0) { this.face_mat[fi] = mat_idx; } } } // Restore paint Maps from this snapshot. Returns the saved active_brush_side. restore(paint) { paint.face_colors.clear(); paint.face_pbr.clear(); paint.face_noise.clear(); paint.face_key.clear(); for (let fi = 0; fi < this.face_mat.length; fi++) { const mat_idx = this.face_mat[fi]; if (mat_idx === 0) { continue; } const [c_idx, pbr_idx, noi_idx] = this.mat_palette[mat_idx]; const c = this.color_palette[c_idx]; const pbr = this.pbr_palette[pbr_idx]; const noise = this.noise_palette[noi_idx]; paint.face_colors.set(fi, [...c]); paint.face_pbr.set(fi, [...pbr]); paint.face_noise.set(fi, [...noise]); paint.face_key.set(fi, paint._make_key(c, pbr, noise)); } const t = this.tool; paint.enabled = t.enabled; paint.tool = t.tool; paint.color_left = [...t.color_left]; paint.color_right = [...t.color_right]; paint.metallic_left = t.metallic_left; paint.metallic_right = t.metallic_right; paint.roughness_left = t.roughness_left; paint.roughness_right = t.roughness_right; paint.noise_left = [...t.noise_left]; paint.noise_right = [...t.noise_right]; return t.active_brush_side; } byte_size() { return this.face_mat.byteLength; } } export class Undo_State { constructor() { this.history = []; this.pos = -1; } // Push a new snapshot; truncates redo history. push(paint, face_count, active_brush_side) { this.history.splice(this.pos + 1); this.history.push(new Paint_Snapshot(paint, face_count, new Tool_Snapshot(active_brush_side, paint))); if (this.history.length > MAX_HISTORY) { this.history.shift(); } else { this.pos++; } } can_undo() { return this.pos > 0; } can_redo() { return this.pos < this.history.length - 1; } // Restore previous snapshot; returns active_brush_side or null if nothing to undo. undo(paint) { if (!this.can_undo()) { return null; } this.pos--; return this.history[this.pos].restore(paint); } // Restore next snapshot; returns active_brush_side or null if nothing to redo. redo(paint) { if (!this.can_redo()) { return null; } this.pos++; return this.history[this.pos].restore(paint); } // Drop all history (e.g. when mesh topology changes). invalidate() { this.history = []; this.pos = -1; } }