76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
import { closeSync, openSync, readSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { MeshStandardMaterial } from "three";
|
|
import { describe, expect, it } from "vitest";
|
|
import { DUNGEON_DEFINITIONS } from "../game/dungeonRegistry";
|
|
import { BITE_SIZED_STOCKADES_DUNGEON_ID } from "../game/dungeonContentSource";
|
|
import { prepareDungeonMaterial } from "./productionDungeonMaterials";
|
|
|
|
interface GltfMaterial {
|
|
name?: string;
|
|
alphaMode?: string;
|
|
alphaCutoff?: number;
|
|
pbrMetallicRoughness?: { baseColorFactor?: number[] };
|
|
}
|
|
|
|
function shippedMaterials(file: string): GltfMaterial[] {
|
|
const descriptor = openSync(file, "r");
|
|
try {
|
|
const header = Buffer.alloc(20);
|
|
expect(readSync(descriptor, header, 0, 20, 0)).toBe(20);
|
|
expect(header.toString("utf8", 0, 4)).toBe("glTF");
|
|
const json = Buffer.alloc(header.readUInt32LE(12));
|
|
expect(readSync(descriptor, json, 0, json.length, 20)).toBe(json.length);
|
|
return (JSON.parse(json.toString("utf8")) as { materials: GltfMaterial[] }).materials;
|
|
} finally {
|
|
closeSync(descriptor);
|
|
}
|
|
}
|
|
|
|
const wowDungeons = DUNGEON_DEFINITIONS.filter((definition) => (
|
|
definition.expansion !== "runewaker" && definition.id !== BITE_SIZED_STOCKADES_DUNGEON_ID
|
|
));
|
|
|
|
describe("shipped WoW dungeon material policies", () => {
|
|
it("covers all 54 campaign dungeons", () => {
|
|
expect(wowDungeons).toHaveLength(54);
|
|
});
|
|
|
|
it.each(wowDungeons)("retains authored opacity and effect roles in $title", (definition) => {
|
|
let opaqueCount = 0;
|
|
const roles = definition.presentation.materials;
|
|
const matches = (name: string, patterns: readonly string[], names: readonly string[]) => (
|
|
names.some((candidate) => candidate.toLowerCase() === name.toLowerCase())
|
|
|| patterns.some((pattern) => new RegExp(pattern, "i").test(name))
|
|
);
|
|
for (const chunk of definition.assets.visual) {
|
|
const file = chunk.url
|
|
? path.join(process.cwd(), "public", chunk.url)
|
|
: path.join(process.cwd(), "src/assets/game/dungeons", definition.id, chunk.fileName);
|
|
for (const raw of shippedMaterials(file)) {
|
|
const name = raw.name ?? "";
|
|
const source = new MeshStandardMaterial({
|
|
name,
|
|
transparent: raw.alphaMode === "BLEND",
|
|
alphaTest: raw.alphaMode === "MASK" ? raw.alphaCutoff ?? 0.5 : 0,
|
|
opacity: raw.pbrMetallicRoughness?.baseColorFactor?.[3] ?? 1,
|
|
});
|
|
const prepared = prepareDungeonMaterial(source, roles, definition.environmentMode);
|
|
const cutout = matches(name, roles.cutoutPatterns, roles.cutoutNames);
|
|
const blended = !cutout && (matches(name, roles.blendPatterns, roles.blendNames)
|
|
|| matches(name, roles.additivePatterns, roles.additiveNames));
|
|
expect(prepared.transparent, name).toBe(blended);
|
|
expect(prepared.depthWrite, name).toBe(!blended);
|
|
if (!cutout && !blended) {
|
|
opaqueCount += 1;
|
|
expect(prepared.opacity, name).toBe(1);
|
|
expect(prepared.alphaTest, name).toBe(0);
|
|
}
|
|
prepared.dispose();
|
|
source.dispose();
|
|
}
|
|
}
|
|
expect(opaqueCount).toBeGreaterThan(0);
|
|
});
|
|
});
|