Release v0.1.4 2026-07-12
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { mkdir, stat } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { NodeIO } from "@gltf-transform/core";
|
||||
import { EXTMeshoptCompression, EXTTextureWebP, KHRMeshQuantization } from "@gltf-transform/extensions";
|
||||
import { MeshoptDecoder, MeshoptEncoder } from "meshoptimizer";
|
||||
|
||||
const rootDirectory = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const playerDirectory = path.join(rootDirectory, "game_assets/models/claudecraft/chars/players");
|
||||
const outputDirectory = path.join(rootDirectory, ".tmp/pruned-party-models");
|
||||
const writeInPlace = process.argv.includes("--write");
|
||||
|
||||
const REQUIRED_CLIPS = {
|
||||
druid: ["Death_A", "Hit_A", "Idle", "Running_A", "Walking_A", "Spellcasting", "2H_Melee_Attack_Chop"],
|
||||
knight: ["Death_A", "Hit_A", "Idle", "Running_A", "Walking_A", "Spellcasting", "1H_Melee_Attack_Chop"],
|
||||
ranger: ["Death_A", "Hit_A", "Idle", "Running_A", "Walking_A", "Spellcasting", "2H_Ranged_Shoot"],
|
||||
mage: ["Death_A", "Hit_A", "Idle", "Running_A", "Walking_A", "Spellcasting", "Spellcast_Shoot"],
|
||||
rogue: ["Death_A", "Hit_A", "Idle", "Running_A", "Walking_A", "Spellcasting", "Dualwield_Melee_Attack_Chop"],
|
||||
};
|
||||
|
||||
await MeshoptEncoder.ready;
|
||||
const io = new NodeIO()
|
||||
.registerExtensions([EXTMeshoptCompression, EXTTextureWebP, KHRMeshQuantization])
|
||||
.registerDependencies({
|
||||
"meshopt.decoder": MeshoptDecoder,
|
||||
"meshopt.encoder": MeshoptEncoder,
|
||||
});
|
||||
|
||||
if (!writeInPlace) await mkdir(outputDirectory, { recursive: true });
|
||||
|
||||
for (const [modelName, requiredClips] of Object.entries(REQUIRED_CLIPS)) {
|
||||
const inputPath = path.join(playerDirectory, `${modelName}.glb`);
|
||||
const document = await io.read(inputPath);
|
||||
const animations = document.getRoot().listAnimations();
|
||||
const available = new Set(animations.map((animation) => animation.getName()));
|
||||
const missing = requiredClips.filter((clip) => !available.has(clip));
|
||||
if (missing.length) throw new Error(`${modelName}.glb is missing required clips: ${missing.join(", ")}`);
|
||||
|
||||
for (const animation of animations) {
|
||||
if (!requiredClips.includes(animation.getName())) animation.dispose();
|
||||
}
|
||||
const outputPath = writeInPlace ? inputPath : path.join(outputDirectory, `${modelName}.glb`);
|
||||
await io.write(outputPath, document);
|
||||
const sourceSize = (await stat(inputPath)).size;
|
||||
const outputSize = (await stat(outputPath)).size;
|
||||
console.log(`${path.basename(outputPath)}: ${sourceSize} → ${outputSize} bytes (${requiredClips.length} clips)`);
|
||||
}
|
||||
Reference in New Issue
Block a user