fix(world): Gehweg lädt seine Diffuse-Textur auch ohne Normal- und ARM-Karte
loadSet() lud Diffuse-, Normal- und ARM-Karte in einem gemeinsamen Promise.all und fiel schon beim Fehlen einer einzigen Datei auf den einfarbigen Ersatz zurück. modular_sidewalk_curb_kit liefert für den Gehweg nur sidewalk_Diffuse.jpg (keine Normal-Map, und die metallicRoughness-Karte ist laut Entwurf eine 605-Byte-Einfarbfläche, die bewusst nicht übernommen wird) — Folge: 0,298 km² rendern einfarbig #d8d2c4, bei jedem Laden "Textur sidewalk fehlt", die committete Kachel blieb ungenutzt. Nur die Diffuse-Karte ist jetzt Pflicht; Normal- und ARM-Karte werden einzeln geladen und fallen bei einem Fehler je für sich auf null zurück, ohne die anderen beiden mitzureißen. Ohne ARM-Karte bleibt roughness auf 0.9 (derselbe Wert wie der bisherige einfarbige Ersatz). loadSet() ist jetzt exportiert, damit tests/materials.test.js sie mit einem Attrappenloader ohne echte Dateien prüfen kann: Diffuse allein, Diffuse+Normal ohne ARM, der volle Satz, und weiterhin der einfarbige Ersatz, wenn die Diffuse-Karte selbst fehlt.
This commit is contained in:
1 parent
736dcbc1ad
commit
3f4fa7f1ed
2 files changed
+85
-9
No files matched your search
+28
-6
@@ -67,17 +67,36 @@ export function waterNormalData(size = 128) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSet(loader, slug, fallback, { tint, maxAnisotropy } = {}) {
|
/**
|
||||||
let map, normalMap, arm;
|
* Ein Materialsatz aus den Kacheln von `./textures/<slug>_*.jpg`.
|
||||||
|
*
|
||||||
|
* Nur die Diffuse-Karte ist Pflicht; Normal- und ARM-Karte sind freiwillig. Grund: Nicht jede
|
||||||
|
* Quelle liefert alle drei. Der Gehweg aus modular_sidewalk_curb_kit hat nur `sidewalk_Diffuse.jpg`
|
||||||
|
* — das Kit bringt gar keine Normal-Map dafür, und `sidewalk_metallicRoughness.png` ist eine 605
|
||||||
|
* Byte große Einfarbfläche, die der Entwurf bewusst nicht übernimmt (Abschnitt "Die beiden Kits").
|
||||||
|
* Solange alle drei Pflicht waren, fiel der Gehweg deshalb auf den einfarbigen Ersatz zurück: 0,298
|
||||||
|
* km² in `#d8d2c4`, bei jedem Laden die Warnung "Textur sidewalk fehlt", und die committete
|
||||||
|
* Diffuse-Kachel lag ungenutzt im Repository.
|
||||||
|
*
|
||||||
|
* Ohne ARM-Karte bleibt die Rauheit auf demselben Wert wie beim einfarbigen Ersatz (0,9): matt,
|
||||||
|
* wie eine Betonfläche. Ohne Normal-Map ist die Fläche flach — das ist der ehrliche Zustand der
|
||||||
|
* Quelle und immer noch näher an ihr als ein Einfarbton.
|
||||||
|
*/
|
||||||
|
export async function loadSet(loader, slug, fallback, { tint, maxAnisotropy } = {}) {
|
||||||
|
let map;
|
||||||
try {
|
try {
|
||||||
[map, normalMap, arm] = await Promise.all(
|
map = await loader.loadAsync(`./textures/${slug}_Diffuse.jpg`);
|
||||||
["Diffuse", "nor_gl", "arm"].map((m) => loader.loadAsync(`./textures/${slug}_${m}.jpg`)),
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`Textur ${slug} fehlt, einfarbiger Ersatz:`, err);
|
console.warn(`Textur ${slug} fehlt, einfarbiger Ersatz:`, err);
|
||||||
return new MeshStandardMaterial({ color: fallback, roughness: 0.9 });
|
return new MeshStandardMaterial({ color: fallback, roughness: 0.9 });
|
||||||
}
|
}
|
||||||
|
// Die beiden Beiwerke einzeln und je für sich: eine fehlende Datei nimmt nur ihre eigene
|
||||||
|
// Wirkung weg, nicht die der anderen und schon gar nicht die der Diffuse-Karte.
|
||||||
|
const [normalMap, arm] = await Promise.all(
|
||||||
|
["nor_gl", "arm"].map((m) => loader.loadAsync(`./textures/${slug}_${m}.jpg`).then((t) => t, () => null)),
|
||||||
|
);
|
||||||
for (const t of [map, normalMap, arm]) {
|
for (const t of [map, normalMap, arm]) {
|
||||||
|
if (!t) continue;
|
||||||
t.wrapS = t.wrapT = RepeatWrapping;
|
t.wrapS = t.wrapT = RepeatWrapping;
|
||||||
// Ruling 58: Straßen laufen flach in die Tiefe — dort flimmert Standard-
|
// Ruling 58: Straßen laufen flach in die Tiefe — dort flimmert Standard-
|
||||||
// filterung zuerst. createMaterials() kennt den Renderer nicht selbst, daher
|
// filterung zuerst. createMaterials() kennt den Renderer nicht selbst, daher
|
||||||
@@ -89,7 +108,10 @@ async function loadSet(loader, slug, fallback, { tint, maxAnisotropy } = {}) {
|
|||||||
// (Ruling 57): metalness steht fest auf 0, three multipliziert texelMetalness nur
|
// (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
|
// noch dazu (metalnessmap_fragment.glsl.js) und könnte das Ergebnis nie von 0
|
||||||
// wegbewegen — reine Kosten (Texturbindung, Shader-Permutation) ohne Wirkung.
|
// wegbewegen — reine Kosten (Texturbindung, Shader-Permutation) ohne Wirkung.
|
||||||
const material = new MeshStandardMaterial({ map, normalMap, aoMap: arm, roughnessMap: arm, metalness: 0 });
|
const material = new MeshStandardMaterial({
|
||||||
|
map, normalMap, aoMap: arm, roughnessMap: arm, metalness: 0,
|
||||||
|
...(arm ? {} : { roughness: 0.9 }),
|
||||||
|
});
|
||||||
// Ruling 55/56: nur der geladene Erfolgsfall bekommt die Tönung — der einfarbige
|
// Ruling 55/56: nur der geladene Erfolgsfall bekommt die Tönung — der einfarbige
|
||||||
// Ersatz oben hat schon seinen eigenen, passenden Grundton.
|
// Ersatz oben hat schon seinen eigenen, passenden Grundton.
|
||||||
if (tint) material.color.set(tint);
|
if (tint) material.color.set(tint);
|
||||||
|
|||||||
+57
-3
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { applyPolygonOffsets, MATERIAL_KEYS, POLYGON_OFFSETS, POLYGON_OFFSET_ORDER, SURFACE_COLORS, TEXTURES,
|
import { applyPolygonOffsets, loadSet, MATERIAL_KEYS, POLYGON_OFFSETS, POLYGON_OFFSET_ORDER, SURFACE_COLORS,
|
||||||
waterNormalData } from "../src/world/materials.js";
|
TEXTURES, waterNormalData } from "../src/world/materials.js";
|
||||||
import { buildChunkGeometry } from "../src/world/geometry.js";
|
import { buildChunkGeometry } from "../src/world/geometry.js";
|
||||||
|
|
||||||
describe("materials", () => {
|
describe("materials", () => {
|
||||||
@@ -127,3 +127,57 @@ describe("Schichten aus Teil 2", () => {
|
|||||||
expect(POLYGON_OFFSETS.curb).toBeUndefined();
|
expect(POLYGON_OFFSETS.curb).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Befund der Gesamtprüfung: loadSet() lud Diffuse-, Normal- und ARM-Karte in einem einzigen
|
||||||
|
// Promise.all und fiel auf den einfarbigen Ersatz zurück, sobald eine davon fehlte. Für den Gehweg
|
||||||
|
// liefert modular_sidewalk_curb_kit nur `sidewalk_Diffuse.jpg` (der Entwurf sagt das selbst,
|
||||||
|
// Abschnitt "Die beiden Kits") — 0,298 km² rendern deshalb einfarbig, und die committete Kachel
|
||||||
|
// lag ungenutzt im Repository. Die Tests laufen ohne echte Dateien: loadSet() bekommt seinen Loader
|
||||||
|
// von außen, hier ein Attrappenloader, der je nach Dateiname auflöst oder abweist.
|
||||||
|
describe("Texturen laden: Diffuse ist Pflicht, Normal und ARM sind freiwillig", () => {
|
||||||
|
/** Loader-Attrappe: löst für jede Kachel auf, deren Kürzel in `vorhanden` steht. */
|
||||||
|
const fakeLoader = (vorhanden) => ({
|
||||||
|
loadAsync: (url) => {
|
||||||
|
// Über die Endung vergleichen, nicht über den letzten Unterstrich: "nor_gl" trägt selbst einen.
|
||||||
|
const teil = vorhanden.find((t) => url.endsWith(`_${t}.jpg`));
|
||||||
|
return teil ? Promise.resolve({ name: teil }) : Promise.reject(new Error(`404 ${url}`));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => { vi.restoreAllMocks(); });
|
||||||
|
|
||||||
|
it("nimmt eine Diffuse-Karte ohne Normal- und ARM-Karte an, statt einfarbig zu werden", async () => {
|
||||||
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
const material = await loadSet(fakeLoader(["Diffuse"]), "sidewalk", 0xd8d2c4);
|
||||||
|
expect(material.map).toMatchObject({ name: "Diffuse" });
|
||||||
|
expect(material.normalMap).toBeNull();
|
||||||
|
expect(material.aoMap).toBeNull();
|
||||||
|
expect(material.roughnessMap).toBeNull();
|
||||||
|
// Ohne ARM-Karte kommt die Rauheit aus dem Material selbst, nicht aus der Textur.
|
||||||
|
expect(material.roughness).toBe(0.9);
|
||||||
|
expect(warn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("nimmt auch eine fehlende ARM-Karte allein hin, ohne die Normal-Map mitzureißen", async () => {
|
||||||
|
const material = await loadSet(fakeLoader(["Diffuse", "nor_gl"]), "sidewalk", 0xd8d2c4);
|
||||||
|
expect(material.normalMap).toMatchObject({ name: "nor_gl" });
|
||||||
|
expect(material.roughnessMap).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hängt den vollen Satz weiterhin vollständig ein", async () => {
|
||||||
|
const material = await loadSet(fakeLoader(["Diffuse", "nor_gl", "arm"]), "asphalt_02", 0x505050,
|
||||||
|
{ maxAnisotropy: 8 });
|
||||||
|
expect(material.map).toMatchObject({ name: "Diffuse", anisotropy: 8 });
|
||||||
|
expect(material.normalMap).toMatchObject({ name: "nor_gl", anisotropy: 8 });
|
||||||
|
expect(material.aoMap).toMatchObject({ name: "arm" });
|
||||||
|
expect(material.roughnessMap).toBe(material.aoMap);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fällt nur dann auf den einfarbigen Ersatz zurück, wenn die Diffuse-Karte fehlt", async () => {
|
||||||
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
const material = await loadSet(fakeLoader(["nor_gl", "arm"]), "fehlt", 0x123456);
|
||||||
|
expect(material.map).toBeNull();
|
||||||
|
expect(material.color.getHex()).toBe(0x123456);
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user