feat(world): Materialrollen der Tribüne, Asset-Test je Ort
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>
This commit is contained in:
1 parent
e33c1817ec
commit
ae0f6106bb
5 files changed
+94
-21
No files matched your search
+4
-1
@@ -34,7 +34,7 @@ export const PLACES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Materialien, die kollidieren. Alles andere — Gras, Blätter, Büsche, Kleinteile — läuft man durch. */
|
/** Materialien, die kollidieren. Alles andere — Gras, Blätter, Büsche, Sitzschalen, Kleinteile — läuft man durch. */
|
||||||
export const SOLID_MATERIALS = [
|
export const SOLID_MATERIALS = [
|
||||||
// Boden
|
// Boden
|
||||||
"gravel", "asphalt", "curbAsphalt", "padAsphalt", "puddleAsphalt", "cobble", "grass", "concrete",
|
"gravel", "asphalt", "curbAsphalt", "padAsphalt", "puddleAsphalt", "cobble", "grass", "concrete",
|
||||||
@@ -49,6 +49,9 @@ export const SOLID_MATERIALS = [
|
|||||||
"modular_electricity_poles", "barrel_03", "plastic_container",
|
"modular_electricity_poles", "barrel_03", "plastic_container",
|
||||||
// Bäume
|
// Bäume
|
||||||
"quiver_tree_01_trunk", "quiver_tree_02",
|
"quiver_tree_01_trunk", "quiver_tree_02",
|
||||||
|
// Tribüne: Betonkörper und Tunnel, Verkleidung, Stahl (Geländer, Dachtragwerk, Rinne), Dach, Kabinenboden,
|
||||||
|
// Glas der Pressekabine, Anschlusskästen am Boden
|
||||||
|
"Sichtbeton", "Estrich", "Verkleidung", "Stahl", "Trapezblech", "Riffelblech", "Verglasung", "utility_box_01",
|
||||||
];
|
];
|
||||||
const SOLID = new Set(SOLID_MATERIALS);
|
const SOLID = new Set(SOLID_MATERIALS);
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,19 @@ describe("isSolidMaterial", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bruch: Die Tribüne wäre an Beton, Stahl, Dach, Glas oder den Anschlusskästen durchlässig, oder man
|
||||||
|
// bliebe an Sitzschalen, Kabeln und Wandleuchten hängen (zwischen den Sitzreihen sind nur 38 cm).
|
||||||
|
it("lässt Beton, Stahl, Dach, Glas und Anschlusskästen der Tribüne kollidieren, Sitze, Kabel und Leuchten nicht", () => {
|
||||||
|
for (const name of ["Sichtbeton", "Estrich", "Verkleidung", "Stahl", "Trapezblech", "Riffelblech", "Verglasung",
|
||||||
|
"utility_box_01"]) {
|
||||||
|
expect(isSolidMaterial(name)).toBe(true);
|
||||||
|
}
|
||||||
|
for (const name of ["Sitzschale", "modular_electric_cables", "modular_electric_cables_boxes", "industrial_wall_lamp",
|
||||||
|
"industrial_wall_lamp_glass", "power_box_01"]) {
|
||||||
|
expect(isSolidMaterial(name)).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("kennt jedes Material der Liste", () => {
|
it("kennt jedes Material der Liste", () => {
|
||||||
expect(SOLID_MATERIALS.every(isSolidMaterial)).toBe(true);
|
expect(SOLID_MATERIALS.every(isSolidMaterial)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
+44
-15
@@ -8,11 +8,34 @@ import { MeshoptDecoder } from "meshoptimizer";
|
|||||||
import { PLACES, SOLID_MATERIALS } from "../src/world/places.js";
|
import { PLACES, SOLID_MATERIALS } from "../src/world/places.js";
|
||||||
import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../tools/assets/bumpExtension.mjs";
|
import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../tools/assets/bumpExtension.mjs";
|
||||||
|
|
||||||
const place = PLACES.find((entry) => entry.id === "hood-yard");
|
// Feste Materialien je Ort. SOLID_MATERIALS gilt für alle Orte zusammen, aber keine Datei enthält alle
|
||||||
const file = fileURLToPath(new URL(`../public/${place.url.slice(2)}`, import.meta.url));
|
// Namen — geprüft wird deshalb jede Datei gegen ihre eigene Zeile.
|
||||||
const CONTAINERS = ["cont_a_side", "cont_a_end", "cont_b_side", "cont_b_end", "cont_c_side", "cont_c_end"];
|
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"],
|
||||||
|
};
|
||||||
|
|
||||||
describe("hood-yard.glb", () => {
|
// 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;
|
let document;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await MeshoptDecoder.ready;
|
await MeshoptDecoder.ready;
|
||||||
@@ -22,17 +45,16 @@ describe("hood-yard.glb", () => {
|
|||||||
document = await io.read(file);
|
document = await io.read(file);
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
// Bruch: Aufbereitung übersprungen oder falsch eingestellt — die Quelle hatte 653 MB.
|
// Bruch: Aufbereitung übersprungen oder falsch eingestellt — die Quelle des Hood Yard hatte 653 MB.
|
||||||
it("ist kleiner als 30 MB", () => {
|
it("ist kleiner als 30 MB", () => {
|
||||||
expect(statSync(file).size).toBeLessThan(30e6);
|
expect(statSync(file).size).toBeLessThan(30e6);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bruch: das Wellblech ist weg, die Geometrie unkomprimiert, oder GPU-Instanzen, deren Matrizen die
|
// Bruch: Geometrie unkomprimiert oder GPU-Instanzen, deren Matrizen die Kollision des Controllers nicht liest.
|
||||||
// Kollision des Controllers nicht liest.
|
it("nutzt meshopt und WebP und hat keine Instanzen", () => {
|
||||||
it("nutzt meshopt und WebP, behält EXT_materials_bump und hat keine Instanzen", () => {
|
|
||||||
const used = document.getRoot().listExtensionsUsed().map((extension) => extension.extensionName);
|
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).toEqual(expect.arrayContaining(["EXT_meshopt_compression", "EXT_texture_webp"]));
|
||||||
expect(used).not.toContain("EXT_mesh_gpu_instancing");
|
expect(used).not.toContain("EXT_mesh_gpu_instancing");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,18 +65,25 @@ describe("hood-yard.glb", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gibt allen sechs Container-Materialien ihre Bump-Textur", () => {
|
// 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]));
|
const materials = new Map(document.getRoot().listMaterials().map((material) => [material.getName(), material]));
|
||||||
for (const name of CONTAINERS) {
|
|
||||||
|
expect(used).toContain(EXT_MATERIALS_BUMP);
|
||||||
|
for (const name of bump) {
|
||||||
expect(materials.get(name)?.getExtension(EXT_MATERIALS_BUMP)?.getBumpTexture()).toBeTruthy();
|
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
|
// 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.
|
// 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 der Kollisionsliste", () => {
|
it("enthält jedes feste Material seines Orts", () => {
|
||||||
const names = document.getRoot().listMaterials().map((material) => material.getName());
|
const names = document.getRoot().listMaterials().map((material) => material.getName());
|
||||||
expect(SOLID_MATERIALS.filter((name) => !names.includes(name))).toEqual([]);
|
|
||||||
|
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.
|
// Bruch: bounds in PLACES passt nicht mehr zur Datei — der Datentest prüfte dann die falsche Fläche.
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { beforeAll, describe, expect, it } from "vitest";
|
|||||||
import { Document } from "@gltf-transform/core";
|
import { Document } from "@gltf-transform/core";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../tools/assets/bumpExtension.mjs";
|
import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../tools/assets/bumpExtension.mjs";
|
||||||
import { FAR_TEXTURE_SIZE, NEAR_TEXTURE_SIZE, optimizePlace, placeTarget } from "../tools/assets/places.mjs";
|
import { FAR_TEXTURE_SIZE, NEAR_MATERIALS, NEAR_TEXTURE_SIZE, SIMPLIFY_RATIOS, optimizePlace, placeTarget }
|
||||||
|
from "../tools/assets/places.mjs";
|
||||||
|
|
||||||
/** 1100 px große Streifen. seed macht die Bilder verschieden, sonst legt dedup() sie zusammen. */
|
/** 1100 px große Streifen. seed macht die Bilder verschieden, sonst legt dedup() sie zusammen. */
|
||||||
function stripes(seed) {
|
function stripes(seed) {
|
||||||
@@ -114,3 +115,25 @@ describe("placeTarget", () => {
|
|||||||
expect(() => placeTarget("Hood Yard")).toThrow();
|
expect(() => placeTarget("Hood Yard")).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Materiallisten der Tribüne", () => {
|
||||||
|
// Bruch: Beton, Estrich, Verkleidung, Stahl oder Sitze fielen auf 512 px, obwohl der Spieler direkt
|
||||||
|
// davorsteht — oder Dach und Kabinenboden, die man nur von weitem sieht, behielten 768 px.
|
||||||
|
it("führt Beton, Estrich, Verkleidung, Stahl und Sitze als Nahflächen, Dach und Kabinenboden nicht", () => {
|
||||||
|
for (const name of ["Sichtbeton", "Estrich", "Verkleidung", "Stahl", "Sitzschale"]) expect(NEAR_MATERIALS.has(name)).toBe(true);
|
||||||
|
for (const name of ["Trapezblech", "Riffelblech"]) expect(NEAR_MATERIALS.has(name)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bruch: Kabel, Kästen und Leuchten blieben bei 64 000 Dreiecken, oder die Vereinfachung träfe Körper,
|
||||||
|
// Dach, Geländer oder Sitze (Löcher in Wänden, schiefe Stufenkanten).
|
||||||
|
it("vereinfacht nur die Kleinteile der Tribüne, auf 0,25", () => {
|
||||||
|
for (const name of ["modular_electric_cables", "modular_electric_cables_boxes", "industrial_wall_lamp",
|
||||||
|
"industrial_wall_lamp_glass", "power_box_01", "utility_box_01"]) {
|
||||||
|
expect(SIMPLIFY_RATIOS.get(name)).toBe(0.25);
|
||||||
|
}
|
||||||
|
for (const name of ["Sichtbeton", "Estrich", "Verkleidung", "Stahl", "Trapezblech", "Riffelblech", "Verglasung",
|
||||||
|
"Sitzschale"]) {
|
||||||
|
expect(SIMPLIFY_RATIOS.has(name)).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -29,19 +29,24 @@ export const NEAR_MATERIALS = new Set([
|
|||||||
"cont_a_side", "cont_a_end", "cont_b_side", "cont_b_end", "cont_c_side", "cont_c_end", "contFrame", "contHardware",
|
"cont_a_side", "cont_a_end", "cont_b_side", "cont_b_end", "cont_c_side", "cont_c_end", "contFrame", "contHardware",
|
||||||
"ironPlain", "ironTeal", "ironBlue", "osb", "osbB", "rollershutter_window_01", "rollershutter_window_01_graffiti",
|
"ironPlain", "ironTeal", "ironBlue", "osb", "osbB", "rollershutter_window_01", "rollershutter_window_01_graffiti",
|
||||||
"chainlink_post", "chainlink_wire",
|
"chainlink_post", "chainlink_wire",
|
||||||
|
// Tribüne: die Quelle hat höchstens 768 px, compressTexture vergrößert nicht
|
||||||
|
"Sichtbeton", "Estrich", "Verkleidung", "Stahl", "Sitzschale",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anteil der Dreiecke, der je Material bleibt; Container, Schuppen, Zaun und Boden fehlen hier und
|
* Anteil der Dreiecke, der je Material bleibt; Gebäude, Boden und Zäune (Container, Schuppen, Tribüne mit
|
||||||
* bleiben unverändert. Gemessen am Hood Yard nach flatten/join: die vier Köcherbäume haben zusammen
|
* Sitzen) fehlen hier und bleiben unverändert. Gemessen am Hood Yard nach flatten/join: die vier Köcherbäume
|
||||||
* 2,2 Mio. Dreiecke (quiver_tree_01_leaf 1,13 Mio., quiver_tree_02 739 000, quiver_tree_01_trunk
|
* haben zusammen 2,2 Mio. Dreiecke (quiver_tree_01_leaf 1,13 Mio., quiver_tree_02 739 000,
|
||||||
* 370 000), Gras je 60 000–150 000, die Mast-Kleinteile 92 000.
|
* quiver_tree_01_trunk 370 000), Gras je 60 000–150 000, die Mast-Kleinteile 92 000. An der Tribüne haben
|
||||||
|
* Kabel, Kabeldosen, Wandleuchten und Kästen zusammen 64 298.
|
||||||
*/
|
*/
|
||||||
export const SIMPLIFY_RATIOS = new Map([
|
export const SIMPLIFY_RATIOS = new Map([
|
||||||
["quiver_tree_01_leaf", 0.15], ["quiver_tree_01_trunk", 0.15], ["quiver_tree_02", 0.15],
|
["quiver_tree_01_leaf", 0.15], ["quiver_tree_01_trunk", 0.15], ["quiver_tree_02", 0.15],
|
||||||
["searsia_lucida", 0.5], ["searsia_lucida_leaves", 0.5], ["searsia_lucida_twigs", 0.5],
|
["searsia_lucida", 0.5], ["searsia_lucida_leaves", 0.5], ["searsia_lucida_twigs", 0.5],
|
||||||
["grass_medium_01", 0.5], ["grass_medium_01_dry", 0.5], ["grass_medium_02", 0.5],
|
["grass_medium_01", 0.5], ["grass_medium_01_dry", 0.5], ["grass_medium_02", 0.5],
|
||||||
["modular_electricity_poles_pieces", 0.25], ["security_camera_01", 0.25],
|
["modular_electricity_poles_pieces", 0.25], ["security_camera_01", 0.25],
|
||||||
|
["modular_electric_cables", 0.25], ["modular_electric_cables_boxes", 0.25], ["industrial_wall_lamp", 0.25],
|
||||||
|
["industrial_wall_lamp_glass", 0.25], ["power_box_01", 0.25], ["utility_box_01", 0.25],
|
||||||
]);
|
]);
|
||||||
// Anteil am Mesh-Radius. Nach join umspannt ein Pflanzenmaterial den ganzen Hof (~17 m Radius), die
|
// Anteil am Mesh-Radius. Nach join umspannt ein Pflanzenmaterial den ganzen Hof (~17 m Radius), die
|
||||||
// Grenze liegt dann bei ~17 cm und greift kaum — die Anteile oben steuern (am Modell gemessen: 0,002
|
// Grenze liegt dann bei ~17 cm und greift kaum — die Anteile oben steuern (am Modell gemessen: 0,002
|
||||||
|
|||||||
Reference in new issue
Block a user