diff --git a/src/main.js b/src/main.js index b266e4f..f6f07b8 100644 --- a/src/main.js +++ b/src/main.js @@ -28,7 +28,7 @@ scene.add(sun); // Ein Material je Schicht aus buildChunkGeometry() (Task 7), jetzt mit echten Poly-Haven- // Texturen statt Flächenfarben (Task 9, src/world/materials.js). buildings trägt zusätzlich // die dort erzeugten Vertexfarben (Pastellton pro Gebäude) über der Stuckputz-Textur. -const materials = await createMaterials(); +const materials = await createMaterials(renderer.capabilities.getMaxAnisotropy()); // Himmel und Nebel über der Szene; sunPosition zeigt in dieselbe Richtung wie das Sonnenlicht. const sky = new Sky(); diff --git a/src/world/materials.js b/src/world/materials.js index 50751e1..4a1d72c 100644 --- a/src/world/materials.js +++ b/src/world/materials.js @@ -7,13 +7,22 @@ export const TEXTURES = { park: "leafy_grass", // Grünflächen urban: "concrete_pavement_03", // versiegelte Stadtfläche roads: "asphalt_02", - bridges: "concrete_pavement_03", + // Ruling 56: eine Brücke ist in den Daten dasselbe Straßenstück, nur über Wasser + // (geometry.js trennt sie einzig über roads.filter(r => r.bridge)) — dieselbe + // Textur wie roads, sonst zeigt jeder Straße-Brücke-Übergang eine Farbnaht. + bridges: "asphalt_02", buildings: "white_stucco", }; const FALLBACK_COLORS = { ground: 0xa8b878, sand: 0xe8d888, park: 0x8bc34a, urban: 0xd8d8d8, roads: 0x505050, bridges: 0xb0b0b0, buildings: 0xe8e4dc, }; +// Ruling 55: asphalt_02 misst roh heller (Ø 89.5) als concrete_pavement_03 (Ø 65.9) — +// Stadtflächen wirkten dadurch wie dunkle Flecken auf der Straße, das Straßennetz +// verschwand optisch. material.color kann eine Textur nur abdunkeln, nie aufhellen, +// also wird hier die hellere Straße (und die jetzt identisch texturierte Brücke) +// abgetönt statt die Stadtfläche zu verändern. +const ROAD_TINT = 0x999999; /** Kachelbare Wellen-Normalmap, weil Poly Haven keine Wassertextur hat. */ export function waterNormalData(size = 128) { @@ -34,7 +43,7 @@ export function waterNormalData(size = 128) { return data; } -async function loadSet(loader, slug, fallback) { +async function loadSet(loader, slug, fallback, { tint, maxAnisotropy } = {}) { let map, normalMap, arm; try { [map, normalMap, arm] = await Promise.all( @@ -46,16 +55,29 @@ async function loadSet(loader, slug, fallback) { } for (const t of [map, normalMap, arm]) { t.wrapS = t.wrapT = RepeatWrapping; + // Ruling 58: Straßen laufen flach in die Tiefe — dort flimmert Standard- + // filterung zuerst. createMaterials() kennt den Renderer nicht selbst, daher + // als optionaler Parameter von außen; ohne ihn bleibt three beim Standard (1). + if (maxAnisotropy) t.anisotropy = maxAnisotropy; } map.colorSpace = SRGBColorSpace; - // arm packt Ambient Occlusion, Rauheit und Metallgrad in R, G und B. - return new MeshStandardMaterial({ map, normalMap, aoMap: arm, roughnessMap: arm, metalnessMap: arm, metalness: 0 }); + // arm packt Ambient Occlusion und Rauheit in R und G. metalnessMap fehlt bewusst + // (Ruling 57): metalness steht fest auf 0, three multipliziert texelMetalness nur + // noch dazu (metalnessmap_fragment.glsl.js) und könnte das Ergebnis nie von 0 + // wegbewegen — reine Kosten (Texturbindung, Shader-Permutation) ohne Wirkung. + const material = new MeshStandardMaterial({ map, normalMap, aoMap: arm, roughnessMap: arm, metalness: 0 }); + // Ruling 55/56: nur der geladene Erfolgsfall bekommt die Tönung — der einfarbige + // Ersatz oben hat schon seinen eigenen, passenden Grundton. + if (tint) material.color.set(tint); + return material; } -export async function createMaterials() { +export async function createMaterials(maxAnisotropy) { const loader = new TextureLoader(); + const tints = { roads: ROAD_TINT, bridges: ROAD_TINT }; const entries = await Promise.all( - MATERIAL_KEYS.map(async (key) => [key, await loadSet(loader, TEXTURES[key], FALLBACK_COLORS[key])]), + MATERIAL_KEYS.map(async (key) => + [key, await loadSet(loader, TEXTURES[key], FALLBACK_COLORS[key], { tint: tints[key], maxAnisotropy })]), ); const materials = Object.fromEntries(entries); materials.buildings.vertexColors = true; // pastellige Tönung je Gebäude diff --git a/tests/materials.test.js b/tests/materials.test.js index e996f9a..013210c 100644 --- a/tests/materials.test.js +++ b/tests/materials.test.js @@ -17,6 +17,12 @@ describe("materials", () => { expect(MATERIAL_KEYS).toContain("park"); }); + it("gibt Brücken dieselbe Textur wie Straßen, damit an der Übergangsnaht kein Farbsprung entsteht (Ruling 56)", () => { + // Brücken sind in den Daten dasselbe Straßenstück, nur über Wasser + // (geometry.js trennt sie einzig über roads.filter(r => r.bridge)). + expect(TEXTURES.bridges).toBe(TEXTURES.roads); + }); + it("erzeugt eine Wellen-Normalmap mit neutraler Grundfarbe", () => { const data = waterNormalData(32); expect(data.length).toBe(32 * 32 * 4);