Fest: Sichtbeton, Estrich, Verkleidung, Stahl, Trapezblech, Riffelblech, Verglasung, utility_box_01. Nahflächen: Sichtbeton, Estrich, Verkleidung, Stahl, Sitzschale. Kabel, Kabeldosen, Wandleuchten und Kästen auf 0,25. Der Asset-Test prüft jede Datei aus PLACES gegen ihre eigene Zeile fester Materialien statt gegen die ganze globale Liste, Bump nur beim Hood Yard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
100 lines
5.0 KiB
JavaScript
100 lines
5.0 KiB
JavaScript
import { statSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
import { NodeIO } from "@gltf-transform/core";
|
|
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
|
import { getBounds } from "@gltf-transform/functions";
|
|
import { MeshoptDecoder } from "meshoptimizer";
|
|
import { PLACES, SOLID_MATERIALS } from "../src/world/places.js";
|
|
import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../tools/assets/bumpExtension.mjs";
|
|
|
|
// Feste Materialien je Ort. SOLID_MATERIALS gilt für alle Orte zusammen, aber keine Datei enthält alle
|
|
// Namen — geprüft wird deshalb jede Datei gegen ihre eigene Zeile.
|
|
const SOLID_BY_PLACE = {
|
|
"hood-yard": [
|
|
"gravel", "asphalt", "curbAsphalt", "padAsphalt", "puddleAsphalt", "cobble", "grass", "concrete",
|
|
"cont_a_side", "cont_a_end", "cont_b_side", "cont_b_end", "cont_c_side", "cont_c_end", "contFrame",
|
|
"ironPlain", "ironTeal", "ironBlue", "galv", "rust", "darkTrim", "osb", "osbB",
|
|
"rollershutter_window_01", "rollershutter_window_01_graffiti",
|
|
"chainlink_post", "chainlink_wire",
|
|
"modular_electricity_poles", "barrel_03", "plastic_container",
|
|
"quiver_tree_01_trunk", "quiver_tree_02",
|
|
],
|
|
tribuene: ["Sichtbeton", "Estrich", "Verkleidung", "Stahl", "Trapezblech", "Riffelblech", "Verglasung", "utility_box_01"],
|
|
};
|
|
|
|
// Materialien, deren Relief nur aus EXT_materials_bump kommt. Die Tribüne hat keine Bump-Maps.
|
|
const BUMP_BY_PLACE = {
|
|
"hood-yard": ["cont_a_side", "cont_a_end", "cont_b_side", "cont_b_end", "cont_c_side", "cont_c_end"],
|
|
};
|
|
|
|
// Bruch: ein festes Material steht in keiner Zeile — die Datei seines Orts würde nie darauf geprüft.
|
|
it("verteilt SOLID_MATERIALS vollständig auf die Orte", () => {
|
|
expect(Object.values(SOLID_BY_PLACE).flat().sort()).toEqual([...SOLID_MATERIALS].sort());
|
|
});
|
|
|
|
describe.each(PLACES.map((place) => [place.id, place]))("%s.glb", (id, place) => {
|
|
const file = fileURLToPath(new URL(`../public/${place.url.slice(2)}`, import.meta.url));
|
|
const bump = BUMP_BY_PLACE[id] ?? [];
|
|
let document;
|
|
beforeAll(async () => {
|
|
await MeshoptDecoder.ready;
|
|
const io = new NodeIO()
|
|
.registerExtensions([...ALL_EXTENSIONS, EXTMaterialsBump])
|
|
.registerDependencies({ "meshopt.decoder": MeshoptDecoder });
|
|
document = await io.read(file);
|
|
}, 30000);
|
|
|
|
// Bruch: Aufbereitung übersprungen oder falsch eingestellt — die Quelle des Hood Yard hatte 653 MB.
|
|
it("ist kleiner als 30 MB", () => {
|
|
expect(statSync(file).size).toBeLessThan(30e6);
|
|
});
|
|
|
|
// Bruch: Geometrie unkomprimiert oder GPU-Instanzen, deren Matrizen die Kollision des Controllers nicht liest.
|
|
it("nutzt meshopt und WebP und hat keine Instanzen", () => {
|
|
const used = document.getRoot().listExtensionsUsed().map((extension) => extension.extensionName);
|
|
|
|
expect(used).toEqual(expect.arrayContaining(["EXT_meshopt_compression", "EXT_texture_webp"]));
|
|
expect(used).not.toContain("EXT_mesh_gpu_instancing");
|
|
});
|
|
|
|
it("hat nur WebP-Texturen bis 1024 px", () => {
|
|
for (const texture of document.getRoot().listTextures()) {
|
|
expect(texture.getMimeType()).toBe("image/webp");
|
|
expect(Math.max(...texture.getSize())).toBeLessThanOrEqual(1024);
|
|
}
|
|
});
|
|
|
|
// Bruch: die Aufbereitung verwirft EXT_materials_bump (wie die gltf-transform-CLI) — das Wellblech der
|
|
// Container wäre flach.
|
|
it.runIf(bump.length > 0)("gibt den Bump-Materialien ihre Bump-Textur", () => {
|
|
const used = document.getRoot().listExtensionsUsed().map((extension) => extension.extensionName);
|
|
const materials = new Map(document.getRoot().listMaterials().map((material) => [material.getName(), material]));
|
|
|
|
expect(used).toContain(EXT_MATERIALS_BUMP);
|
|
for (const name of bump) {
|
|
expect(materials.get(name)?.getExtension(EXT_MATERIALS_BUMP)?.getBumpTexture()).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
// Bruch: dedup oder ein neues Werkzeug benennt ein Material um — der Ort wäre dort ohne Warnung
|
|
// durchlässig, weil die Kollision nach Namen auswählt. Oder der Ort hat keine Zeile in SOLID_BY_PLACE.
|
|
it("enthält jedes feste Material seines Orts", () => {
|
|
const names = document.getRoot().listMaterials().map((material) => material.getName());
|
|
|
|
expect(SOLID_BY_PLACE[id]).toBeDefined();
|
|
expect(SOLID_BY_PLACE[id].filter((name) => !names.includes(name))).toEqual([]);
|
|
});
|
|
|
|
// Bruch: bounds in PLACES passt nicht mehr zur Datei — der Datentest prüfte dann die falsche Fläche.
|
|
it("passt auf 5 cm zu den Außenmaßen in PLACES", () => {
|
|
const { min, max } = getBounds(document.getRoot().listScenes()[0]);
|
|
const { minX, maxX, minZ, maxZ } = place.bounds;
|
|
|
|
expect(Math.abs(min[0] - minX)).toBeLessThan(0.05);
|
|
expect(Math.abs(max[0] - maxX)).toBeLessThan(0.05);
|
|
expect(Math.abs(min[2] - minZ)).toBeLessThan(0.05);
|
|
expect(Math.abs(max[2] - maxZ)).toBeLessThan(0.05);
|
|
});
|
|
});
|