#!/usr/bin/env node import { spawn } from "node:child_process"; import { mkdtemp, readFile, rm, stat } from "node:fs/promises"; import { createHash } from "node:crypto"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { projectRoot, workRoot } from "./lib.mjs"; const slug = "utgarde-pinnacle"; function runNodeScript(script, args) { return new Promise((resolve, reject) => { const child = spawn(process.execPath, [script, ...args], { cwd: projectRoot, stdio: "inherit", windowsHide: true, }); child.once("error", reject); child.once("exit", (code) => code === 0 ? resolve() : reject(new Error(`${path.basename(script)} exited with code ${code}.`))); }); } async function buildCandidates() { const source = path.join(workRoot, slug, "staging", "environment", `${slug}-visual.glb`); const artifacts = path.join(projectRoot, "artifacts/dungeon-pipeline"); const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), `${slug}-uv-candidates-`)); const spatialOnly = process.argv.includes("--spatial-only"); const prepared = path.join(temporaryDirectory, `${slug}-prepared.glb`); const cli = path.join(projectRoot, "node_modules/@gltf-transform/cli/bin/cli.js"); const encoder = path.join(projectRoot, "scripts/asset-pipeline/compress-ktx2.mjs"); const outputs = [ { name: "p14-u12", command: "meshopt", args: ["--quantize-position", "14", "--quantize-texcoord", "12"] }, { name: "p16-u12", command: "meshopt", args: ["--quantize-position", "16", "--quantize-texcoord", "12"] }, { name: "p16-u16", command: "meshopt", args: ["--quantize-position", "16", "--quantize-texcoord", "16"] }, { name: "repeat-optimize-p14-u12", command: "optimize", args: [ "--compress", "meshopt", "--meshopt-level", "high", "--instance", "true", "--instance-min", "3", "--simplify", "false", "--texture-compress", "false", ] }, ]; try { const results = []; if (!spatialOnly) { await runNodeScript(cli, [ "optimize", source, prepared, "--compress", "false", "--meshopt-level", "high", "--instance", "true", "--instance-min", "3", "--simplify", "false", "--texture-compress", "false", ]); await runNodeScript(encoder, [prepared, "--concurrency", "1", "--jobs", "2"]); for (const output of outputs) { const candidate = path.join(artifacts, `${slug}-uv-${output.name}.glb`); const args = output.command === "meshopt" ? ["meshopt", prepared, candidate, "--level", "high", ...output.args] : ["optimize", prepared, candidate, ...output.args]; await runNodeScript(cli, args); const bytes = await stat(candidate); const hash = createHash("sha256"); hash.update(await readFile(candidate)); results.push({ candidate: path.relative(projectRoot, candidate).replaceAll("\\", "/"), byteLength: bytes.size, sha256: hash.digest("hex") }); } } const spatialPrepared = path.join(temporaryDirectory, `${slug}-spatial-prepared.glb`); await runNodeScript(cli, [ "optimize", source, spatialPrepared, "--compress", "false", "--instance", "false", "--flatten", "false", "--join", "false", "--weld", "false", "--simplify", "false", "--texture-compress", "false", ]); await runNodeScript(encoder, [spatialPrepared, "--concurrency", "1", "--jobs", "2"]); const spatialCandidate = path.join(artifacts, `${slug}-uv-spatial-p16-u16.glb`); await runNodeScript(cli, [ "meshopt", spatialPrepared, spatialCandidate, "--level", "high", "--quantize-position", "16", "--quantize-texcoord", "16", ]); const spatialBytes = await stat(spatialCandidate); const spatialHash = createHash("sha256").update(await readFile(spatialCandidate)).digest("hex"); results.push({ candidate: path.relative(projectRoot, spatialCandidate).replaceAll("\\", "/"), byteLength: spatialBytes.size, sha256: spatialHash, geometryPacking: "no instance, flatten, join, or weld; one 16-bit position/UV meshopt pass", }); console.log(JSON.stringify({ slug, source: path.relative(projectRoot, source).replaceAll("\\", "/"), candidates: results }, null, 2)); } finally { await rm(temporaryDirectory, { recursive: true, force: true }); } } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { await buildCandidates(); } export { buildCandidates };