// scripts/optimize-models.mjs // Nén .glb: backup bản gốc rồi tối ưu (meshopt + nén texture webp). // Chạy: node scripts/optimize-models.mjs (an toàn chạy lại — luôn nén từ backup gốc) import { promises as fs } from "node:fs"; import path from "node:path"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; const run = promisify(execFile); const ROOT = process.cwd(); const MODEL_DIR = path.join(ROOT, "public/antiquity"); const BACKUP_DIR = path.join(ROOT, "model-originals"); const fmt = (b) => (b / 1024 / 1024).toFixed(2) + "MB"; const exists = (p) => fs.access(p).then(() => true).catch(() => false); async function optimizeModel(file) { const dest = path.join(MODEL_DIR, file); const backup = path.join(BACKUP_DIR, file); await fs.mkdir(BACKUP_DIR, { recursive: true }); if (!(await exists(backup))) { await fs.copyFile(dest, backup); } const before = (await fs.stat(dest)).size; // Luôn tối ưu TỪ backup gốc; ghi đè ra dest. const isWin = process.platform === "win32"; const bin = path.join(ROOT, "node_modules/.bin", isWin ? "gltf-transform.cmd" : "gltf-transform"); const q = (s) => (isWin ? `"${s}"` : s); await run( q(bin), [ "optimize", q(backup), q(dest), "--compress", "meshopt", "--texture-compress", "webp", ], // Trên Windows phải chạy .cmd qua shell (Node >=18 ném EINVAL nếu spawn .cmd trực tiếp). { shell: isWin } ); const after = (await fs.stat(dest)).size; console.log(`${file}: ${fmt(before)} -> ${fmt(after)} (-${Math.round((1 - after / before) * 100)}%)`); } try { const files = (await fs.readdir(MODEL_DIR)).filter((f) => /\.glb$/i.test(f)).sort(); for (const f of files) { await optimizeModel(f); } console.log("Done."); } catch (err) { console.error("optimize-models failed:", err); process.exit(1); }