81 lines
3.3 KiB
JavaScript
81 lines
3.3 KiB
JavaScript
import { access } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import {
|
|
EXTMeshoptCompression,
|
|
} from "@gltf-transform/extensions";
|
|
import { dedup, mergeDocuments, prune, unpartition } from "@gltf-transform/functions";
|
|
import { createGameAssetIO, convertDocumentTexturesToKtx2 } from "./lib/ktx2.mjs";
|
|
|
|
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const sourceDirectory = path.join(
|
|
repositoryRoot,
|
|
"game_assets",
|
|
"models",
|
|
"original",
|
|
"environment",
|
|
"kaykit-dungeon",
|
|
);
|
|
const outputPath = path.join(sourceDirectory, "dungeon-kit-uastc.glb");
|
|
const sources = ["pillar-decorated.glb", "wall-pillar.glb", "torch-lit.glb"];
|
|
const expectedMeshNames = ["pillar_decorated", "wall_pillar", "torch_lit"];
|
|
await Promise.all(sources.map((source) => access(path.join(sourceDirectory, source))));
|
|
const io = await createGameAssetIO();
|
|
|
|
const documents = await Promise.all(sources.map((source) => io.read(path.join(sourceDirectory, source))));
|
|
const document = documents[0];
|
|
const destinationScene = document.getRoot().listScenes()[0].setName("dungeon_kit");
|
|
|
|
for (const sourceDocument of documents.slice(1)) {
|
|
const sourceScene = sourceDocument.getRoot().listScenes()[0];
|
|
const propertyMap = mergeDocuments(document, sourceDocument);
|
|
const copiedScene = propertyMap.get(sourceScene);
|
|
if (!copiedScene) throw new Error(`Could not merge scene from ${sourceScene.getName() || "unnamed source"}.`);
|
|
for (const child of [...copiedScene.listChildren()]) destinationScene.addChild(child);
|
|
copiedScene.dispose();
|
|
}
|
|
|
|
await document.transform(
|
|
dedup({ keepUniqueNames: false }),
|
|
unpartition(),
|
|
prune({ keepSolidTextures: true }),
|
|
);
|
|
|
|
const root = document.getRoot();
|
|
const meshNames = root.listMeshes().map((mesh) => mesh.getName()).sort();
|
|
const expectedSorted = [...expectedMeshNames].sort();
|
|
if (JSON.stringify(meshNames) !== JSON.stringify(expectedSorted)) {
|
|
throw new Error(`Dungeon kit mesh names changed: expected ${expectedSorted.join(", ")}; received ${meshNames.join(", ")}.`);
|
|
}
|
|
if (root.listTextures().length !== 1) {
|
|
throw new Error(`Dungeon kit must contain exactly one shared texture; received ${root.listTextures().length}.`);
|
|
}
|
|
if (root.listMaterials().length !== 1) {
|
|
throw new Error(`Dungeon kit must contain exactly one shared material; received ${root.listMaterials().length}.`);
|
|
}
|
|
|
|
await convertDocumentTexturesToKtx2(document, "iwt-dungeon-kit-");
|
|
const meshoptExtension = root.listExtensionsUsed()
|
|
.find((extension) => extension.extensionName === EXTMeshoptCompression.EXTENSION_NAME)
|
|
?? document.createExtension(EXTMeshoptCompression);
|
|
meshoptExtension
|
|
.setRequired(true)
|
|
.setEncoderOptions({ method: EXTMeshoptCompression.EncoderMethod.QUANTIZE });
|
|
|
|
await io.write(outputPath, document);
|
|
|
|
const outputDocument = await io.read(outputPath);
|
|
const outputRoot = outputDocument.getRoot();
|
|
const outputTexture = outputRoot.listTextures()[0];
|
|
if (
|
|
outputRoot.listMeshes().length !== 3
|
|
|| outputRoot.listMaterials().length !== 1
|
|
|| outputRoot.listTextures().length !== 1
|
|
|| outputTexture.getMimeType() !== "image/ktx2"
|
|
) {
|
|
throw new Error("Generated dungeon kit failed structural validation.");
|
|
}
|
|
|
|
console.log(`Built ${path.relative(repositoryRoot, outputPath)}`);
|
|
console.log("Meshes: pillar_decorated, wall_pillar, torch_lit; shared textures: 1; encoding: KTX2/UASTC.");
|