diff --git a/public/models/places/hood-yard.glb b/public/models/places/hood-yard.glb new file mode 100644 index 0000000..238797b Binary files /dev/null and b/public/models/places/hood-yard.glb differ diff --git a/tests/placesAsset.test.js b/tests/placesAsset.test.js new file mode 100644 index 0000000..8df9023 --- /dev/null +++ b/tests/placesAsset.test.js @@ -0,0 +1,70 @@ +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"; + +const place = PLACES.find((entry) => entry.id === "hood-yard"); +const file = fileURLToPath(new URL(`../public/${place.url.slice(2)}`, import.meta.url)); +const CONTAINERS = ["cont_a_side", "cont_a_end", "cont_b_side", "cont_b_end", "cont_c_side", "cont_c_end"]; + +describe("hood-yard.glb", () => { + 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 hatte 653 MB. + it("ist kleiner als 30 MB", () => { + expect(statSync(file).size).toBeLessThan(30e6); + }); + + // Bruch: das Wellblech ist weg, die Geometrie unkomprimiert, oder GPU-Instanzen, deren Matrizen die + // Kollision des Controllers nicht liest. + it("nutzt meshopt und WebP, behält EXT_materials_bump und hat keine Instanzen", () => { + const used = document.getRoot().listExtensionsUsed().map((extension) => extension.extensionName); + + expect(used).toEqual(expect.arrayContaining(["EXT_meshopt_compression", "EXT_texture_webp", EXT_MATERIALS_BUMP])); + 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); + } + }); + + it("gibt allen sechs Container-Materialien ihre Bump-Textur", () => { + const materials = new Map(document.getRoot().listMaterials().map((material) => [material.getName(), material])); + for (const name of CONTAINERS) { + expect(materials.get(name)?.getExtension(EXT_MATERIALS_BUMP)?.getBumpTexture()).toBeTruthy(); + } + }); + + // Bruch: dedup oder ein neues Werkzeug benennt ein Material um — der Hof wäre dort ohne Warnung + // durchlässig, weil die Kollision nach Namen auswählt. + it("enthält jedes feste Material der Kollisionsliste", () => { + const names = document.getRoot().listMaterials().map((material) => material.getName()); + expect(SOLID_MATERIALS.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); + }); +});