import fs from "fs"; import path from "path"; import { NodeIO } from "@gltf-transform/core"; import { ALL_EXTENSIONS } from "@gltf-transform/extensions"; import { dedup, prune, resample, weld, quantize } from "@gltf-transform/functions"; import { BACKUP_SUFFIX, needsConversion } from "../../utils/gltf.js"; import { convert } from "../convert/service.js"; const io = new NodeIO().registerExtensions(ALL_EXTENSIONS); const QUANTIZE_PRESETS = { high: { position: 16, normal: 12, texcoord: 14, color: 10, generic: 12 }, balanced: { position: 14, normal: 10, texcoord: 12, color: 8, generic: 12 }, aggressive: { position: 12, normal: 8, texcoord: 10, color: 8, generic: 10 }, light: { position: 10, normal: 8, texcoord: 10, color: 8, generic: 10 } }; export const buildTransforms = (config) => { const transforms = []; const selected = new Set(config.commands); if (selected.has("dedup")) transforms.push(dedup()); if (selected.has("prune")) transforms.push(prune()); if (selected.has("resample")) transforms.push(resample()); if (selected.has("weld")) transforms.push(weld()); if (selected.has("quantize")) { const preset = QUANTIZE_PRESETS[config.quantizePreset] || QUANTIZE_PRESETS.balanced; transforms.push(quantize({ quantizePosition: preset.position, quantizeNormal: preset.normal, quantizeTexcoord: preset.texcoord, quantizeColor: preset.color, quantizeGeneric: preset.generic })); } return transforms; }; const resolveOutput = (file, config) => { const cwd = process.cwd(); const originalExt = path.extname(file).toLowerCase() || ".gltf"; const baseName = originalExt ? file.slice(0, -originalExt.length) : file; const isConvertible = needsConversion(file); const targetExt = config.outputFormat === "gltf" ? ".gltf" : (config.outputFormat === "glb" || isConvertible) ? ".glb" : originalExt; const wantsCopy = config.outputOptions.includes("copy"); const wantsOverwrite = config.outputOptions.includes("overwrite"); const mode = wantsCopy ? "copy" : (wantsOverwrite ? "overwrite" : "copy"); const backup = config.outputOptions.includes("backup") && mode === "overwrite"; const targetName = mode === "copy" ? `${baseName}_compressed${targetExt}` : `${baseName}${targetExt}`; return { cwd, mode, backup, sourcePath: path.join(cwd, file), sourceExt: originalExt, targetExt, targetPath: path.join(cwd, targetName), baseName, needsConversion: isConvertible }; }; export const processFile = async (file, config, transforms) => { const output = resolveOutput(file, config); if (!fs.existsSync(output.sourcePath)) { return { ok: false, file, reason: "missing" }; } if (!transforms.length) { return { ok: false, file, reason: "no-transform" }; } if (output.backup) { const backupName = `${output.baseName}${BACKUP_SUFFIX}${output.sourceExt}`; const backupPath = path.join(output.cwd, backupName); fs.copyFileSync(output.sourcePath, backupPath); } let inputPath = output.sourcePath; if (output.needsConversion) { const tempPath = path.join(output.cwd, `${output.baseName}_temp.glb`); await convert(output.sourcePath, tempPath, { binary: true }); inputPath = tempPath; } const document = io.read(inputPath); await document.transform(...transforms); io.write(output.targetPath, document); if (output.needsConversion) { fs.rmSync(inputPath, { force: true }); } if (output.mode === "overwrite" && output.targetPath !== output.sourcePath) { fs.rmSync(output.sourcePath, { force: true }); } return { ok: true, file, output: output.targetPath }; };