fix(world): Bordstein verschwindet beim flachen Blick nicht mehr

Gemeldet: Der Bordstein ist zu sehen, wenn man davor steht und nach unten
schaut, verschwindet aber beim Blick nach oben und aus der Entfernung.

Ursache ist der Tiefenversatz der Beläge. glPolygonOffset rechnet
offset = factor * Tiefensteigung + units * r, und die Tiefensteigung geht
beim streifenden Blick über eine Bodenfläche gegen unendlich. Die Rangfolge
der Beläge steckte im Faktor (Gehweg -9), also zog der Gehwegbelag beim
flachen Blick um ein Vielfaches seines nominellen Versatzes nach vorn und
deckte die 18 cm hohe Bordsteinwand daneben (Versatz 0) vollständig zu. Im
Browser nachgemessen: aus 18 m war die Wand weg, aus zwei Metern und von
oben war sie da; mit Faktor 0 oder -1 kam sie zurück.

Genau davor warnt der Kommentar über POLYGON_OFFSET_ORDER seit Teil 1 ("wächst
die Liste weiter, ist die Stufenhöhe zu entkoppeln"). Das passiert jetzt: Die
Rangfolge tragen allein die Units - sie zählen in der kleinsten auflösbaren
Tiefendifferenz, hängen nicht vom Blickwinkel ab und trennen koplanare Beläge
auf jede Entfernung. Der Faktor ist für alle Beläge derselbe kleine Wert und
hält sie über der Bodenplatte, ohne Geometrie daneben zu überdecken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5 committed 2026-09-18 12:41:54 +02:00
1 parent 4a91e84bfc
commit 50eee9281a
2 files changed
+45 -14

No files matched your search

+23 -6
View File
@@ -17,10 +17,9 @@ export const TEXTURES = {
carriageway: "asphalt_02", carriageway: "asphalt_02",
lots: "asphalt_02", // Parkplätze und Vorplätze: derselbe Asphalt wie die Fahrbahn lots: "asphalt_02", // Parkplätze und Vorplätze: derselbe Asphalt wie die Fahrbahn
curb: "curb", // modular_sidewalk_curb_kit (Just8, CC BY 4.0), s. tools/assets/textures.mjs curb: "curb", // modular_sidewalk_curb_kit (Just8, CC BY 4.0), s. tools/assets/textures.mjs
// Korrektur: sidewalk_Diffuse.jpg aus modular_sidewalk_curb_kit ist keine Betontextur, sondern // Derselbe Wert wie TEXTURES.paths: helle Betonplatten in echter Größe. Die Kit-Textur schied
// das Platzhalterbild des Kits (roter Schriftzug "Tru-planar Sidewalk texture" auf dunklem // aus, weil sidewalk_Diffuse.jpg kein Betonbild war, sondern das Platzhalterbild des Kits
// Grund, im Browser geprüft) - das Kit taugt deshalb nur für den Bordstein. Derselbe Wert wie // (roter Schriftzug "Tru-planar Sidewalk texture", im Browser geprüft).
// TEXTURES.paths, s. Entwurf Abschnitt "Die beiden Kits".
sidewalk: "concrete_pavement", sidewalk: "concrete_pavement",
// Ruling 56: eine Brücke ist in den Daten dasselbe Straßenstück, nur über Wasser — dieselbe // Ruling 56: eine Brücke ist in den Daten dasselbe Straßenstück, nur über Wasser — dieselbe
// Textur wie die Fahrbahn, sonst zeigt jeder Straße-Brücke-Übergang eine Farbnaht. // Textur wie die Fahrbahn, sonst zeigt jeder Straße-Brücke-Übergang eine Farbnaht.
@@ -180,14 +179,32 @@ export async function loadSet(loader, slug, fallback, { tint, maxAnisotropy } =
export const POLYGON_OFFSET_ORDER = ["ground", "urban", "park", "sand", "field", "court", "pool", export const POLYGON_OFFSET_ORDER = ["ground", "urban", "park", "sand", "field", "court", "pool",
"parking", "lots", "sidewalk", "paths"]; "parking", "lots", "sidewalk", "paths"];
// Nachtrag Teil 2, im Browser gemessen: Der Faktor ist der gefährliche Teil der Formel. Er wird mit
// der Tiefensteigung multipliziert, und die geht beim streifenden Blick über eine Bodenfläche gegen
// unendlich — ein Belag wird dann um ein Vielfaches seines nominellen Versatzes nach vorn gezogen.
// Mit einem Faktor je Rangstufe (Gehweg: -9) deckte der Gehwegbelag den 18 cm hohen Bordstein neben
// sich aus 18 m vollständig zu; von oben und aus zwei Metern war er zu sehen, beim flachen Blick die
// Straße entlang verschwand er. Genau der Fall, vor dem der Kommentar oben gewarnt hat.
//
// Deshalb ist die Stufenhöhe jetzt entkoppelt: Die Rangfolge steckt allein in den Units. Sie zählen
// in der kleinsten auflösbaren Tiefendifferenz, hängen nicht vom Blickwinkel ab und trennen
// koplanare Beläge auf jede Entfernung. Der Faktor ist für alle Beläge derselbe kleine Wert; er hält
// sie über der Bodenplatte (Faktor 0), ohne sie vor Geometrie zu ziehen, die daneben steht.
const DECAL_FACTOR = -1;
const DECAL_UNITS_STEP = 2; // zwei Einheiten je Stufe: Luft gegen Interpolationsrauschen
export const POLYGON_OFFSETS = { export const POLYGON_OFFSETS = {
...Object.fromEntries( ...Object.fromEntries(
POLYGON_OFFSET_ORDER.map((key, i) => [ POLYGON_OFFSET_ORDER.map((key, i) => [
key, key,
{ polygonOffset: true, polygonOffsetFactor: -i, polygonOffsetUnits: -i }, {
polygonOffset: true,
polygonOffsetFactor: i === 0 ? 0 : DECAL_FACTOR,
polygonOffsetUnits: i === 0 ? 0 : -i * DECAL_UNITS_STEP,
},
]), ]),
), ),
markings: { polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -1 }, markings: { polygonOffset: true, polygonOffsetFactor: DECAL_FACTOR, polygonOffsetUnits: -DECAL_UNITS_STEP },
}; };
/** Trägt POLYGON_OFFSETS in die passenden Materialien ein. Nur die benannten Schichten — /** Trägt POLYGON_OFFSETS in die passenden Materialien ein. Nur die benannten Schichten —
+22 -8
View File
@@ -66,16 +66,27 @@ describe("Tiefenpuffer-Versatz je Schicht (gegen Flackern zwischen Boden/Stadt/P
expect(new Set(sigs).size).toBe(sigs.length); expect(new Set(sigs).size).toBe(sigs.length);
}); });
it("Betrag von Faktor und Units steigt streng von Boden zu Straße (Richtung Kamera)", () => { // Im Browser gemessen (Bordstein-Befund): Der Faktor wird mit der Tiefensteigung multipliziert,
const factors = POLYGON_OFFSET_ORDER.map((key) => POLYGON_OFFSETS[key].polygonOffsetFactor); // und die geht beim streifenden Blick über eine Bodenfläche gegen unendlich. Mit einem Faktor je
// Rangstufe zog der Gehwegbelag (-9) den 18 cm hohen Bordstein daneben aus 18 m vollständig zu;
// von oben und aus der Nähe war er da. Deshalb trägt die Rangfolge jetzt allein die Units.
it("trägt die Rangfolge in den Units, nicht im Faktor", () => {
const units = POLYGON_OFFSET_ORDER.map((key) => POLYGON_OFFSETS[key].polygonOffsetUnits); const units = POLYGON_OFFSET_ORDER.map((key) => POLYGON_OFFSETS[key].polygonOffsetUnits);
for (let i = 1; i < factors.length; i++) { for (let i = 1; i < units.length; i++) {
// Zunehmend negativ = zunehmend Richtung Kamera gezogen, s. Kommentar in materials.js. // Zunehmend negativ = zunehmend Richtung Kamera gezogen, s. Kommentar in materials.js.
expect(factors[i]).toBeLessThan(factors[i - 1]); expect(units[i], POLYGON_OFFSET_ORDER[i]).toBeLessThan(units[i - 1]);
expect(units[i]).toBeLessThan(units[i - 1]);
} }
}); });
it("gibt allen Belägen denselben kleinen Faktor, nur die Bodenplatte bleibt bei null", () => {
const [boden, ...belaege] = POLYGON_OFFSET_ORDER;
expect(POLYGON_OFFSETS[boden].polygonOffsetFactor).toBe(0);
const faktoren = new Set(belaege.map((key) => POLYGON_OFFSETS[key].polygonOffsetFactor));
expect([...faktoren]).toEqual([-1]);
// Die Markierung liegt außerhalb der Rangfolge, folgt aber derselben Regel.
expect(POLYGON_OFFSETS.markings.polygonOffsetFactor).toBe(-1);
});
it("trägt den Versatz nur in Boden/Stadtfläche/Park/Strand/Straße ein — Brücken, Gebäude, " it("trägt den Versatz nur in Boden/Stadtfläche/Park/Strand/Straße ein — Brücken, Gebäude, "
+ "Wasser und Meeresboden bleiben unberührt (eigene Materialobjekte, s. Auftrag)", () => { + "Wasser und Meeresboden bleiben unberührt (eigene Materialobjekte, s. Auftrag)", () => {
const fake = Object.fromEntries( const fake = Object.fromEntries(
@@ -105,11 +116,11 @@ describe("Schichten aus Teil 2", () => {
expect(MATERIAL_KEYS).not.toContain("roads"); expect(MATERIAL_KEYS).not.toContain("roads");
}); });
it("nimmt den Bordstein aus dem Kit, den Gehweg aus den Betonplatten von Poly Haven", () => { it("nimmt Bordstein und Gehweg von Poly Haven, nicht mehr aus dem Kit", () => {
// Korrektur: sidewalk_Diffuse.jpg aus dem Kit ist ein Platzhalterbild (roter Schriftzug), // Korrektur: sidewalk_Diffuse.jpg aus dem Kit ist ein Platzhalterbild (roter Schriftzug),
// keine Betontextur (im Browser geprüft) - der Gehweg benutzt deshalb denselben Belag wie // keine Betontextur (im Browser geprüft) - der Gehweg benutzt deshalb denselben Belag wie
// die Parkwege, nicht den Kit-Slug. // die Parkwege, nicht den Kit-Slug.
expect(TEXTURES.curb).toBe("curb"); expect(TEXTURES.curb).toBe("brushed_concrete_04");
expect(TEXTURES.sidewalk).toBe("concrete_pavement"); expect(TEXTURES.sidewalk).toBe("concrete_pavement");
expect(TEXTURES.sidewalk).toBe(TEXTURES.paths); expect(TEXTURES.sidewalk).toBe(TEXTURES.paths);
expect(TEXTURES.carriageway).toBe("asphalt_02"); expect(TEXTURES.carriageway).toBe("asphalt_02");
@@ -125,7 +136,10 @@ describe("Schichten aus Teil 2", () => {
// Ihr einziger Nachbar ist die Fahrbahn 1 cm darunter, und die hat keinen Versatz. Die oberste // Ihr einziger Nachbar ist die Fahrbahn 1 cm darunter, und die hat keinen Versatz. Die oberste
// Stufe zöge sie beim streifenden Blick am weitesten nach vorn, ohne dass es etwas nützte. // Stufe zöge sie beim streifenden Blick am weitesten nach vorn, ohne dass es etwas nützte.
expect(POLYGON_OFFSET_ORDER).not.toContain("markings"); expect(POLYGON_OFFSET_ORDER).not.toContain("markings");
expect(POLYGON_OFFSETS.markings).toEqual({ polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -1 }); expect(POLYGON_OFFSETS.markings).toEqual({ polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -2 });
// Eine Stufe wie der unterste Belag, nicht die oberste: Ihr Nachbar ist die Fahrbahn ohne Versatz.
expect(POLYGON_OFFSETS.markings.polygonOffsetUnits)
.toBe(POLYGON_OFFSETS[POLYGON_OFFSET_ORDER[1]].polygonOffsetUnits);
// Fahrbahn und Bordstein liegen 15 cm tiefer und sind echte Geometrie — kein Versatz nötig. // Fahrbahn und Bordstein liegen 15 cm tiefer und sind echte Geometrie — kein Versatz nötig.
expect(POLYGON_OFFSETS.carriageway).toBeUndefined(); expect(POLYGON_OFFSETS.carriageway).toBeUndefined();
expect(POLYGON_OFFSETS.curb).toBeUndefined(); expect(POLYGON_OFFSETS.curb).toBeUndefined();