fix(world): Laternen vom Fahrbahnrand versetzt, ungenutzte Requisiten entfernt

Ruling 60: vergeLampPoints sucht von 6 bis 16 m nach außen und verwirft eine
Laterne ganz, wenn keiner der sechs Abstände die Straßenpolygone des Chunks
verlässt (Punkt-in-Polygon mit Löchern, pointInPolygon). Ruling 61: die vier
nie platzierten Requisiten (Bank, Mülleimer, Hydrant, Absperrung) fliegen aus
props.mjs und aus dem Repository, ~18 MB auf 1,9 MB im Arbeitsverzeichnis.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-13 01:28:43 +02:00
1 parent 9173400caa
commit d6a2f6d269
7 files changed
+120 -23

No files matched your search

+59 -19
View File
@@ -6,6 +6,7 @@ import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
const PALM_MODEL_HEIGHT = 439.74; // Einheiten im OBJ, gemessen
const TRUNK_RADIUS = 0.22;
const LAMP_VERGE = [6, 8, 10, 12, 14, 16]; // Meter, von innen nach außen (Ruling 60)
/** Kleiner, wiederholbarer Zufall aus einem Schlüssel. */
function hashUnit(seed, key) {
@@ -39,6 +40,58 @@ export function lampPoints(lines, spacing = 35) {
return out;
}
/** Halbstrahl-Verfahren (Ray Casting): liegt (x, z) im Ring? Ring offen oder geschlossen. */
function inRing(x, z, ring) {
let inside = false;
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
const [xi, zi] = ring[i], [xj, zj] = ring[j];
if ((zi > z) !== (zj > z) && x < (xj - xi) * (z - zi) / (zj - zi) + xi) inside = !inside;
}
return inside;
}
/**
* Liegt (x, z) auf der Fläche, die dieses Ringpaket beschreibt — Außenring zuerst, danach
* Löcher (wie chunk.roads[i].poly, Task 6)? Auf der Fläche heißt: im Außenring und in
* keinem der Löcher.
*/
export function pointInPolygon(x, z, rings) {
return rings.length > 0 && inRing(x, z, rings[0]) && !rings.slice(1).some((hole) => inRing(x, z, hole));
}
/**
* Versetzt lampPoints()-Punkte seitlich von der Fahrbahn (Ruling 60). lampPoints kennt nur
* die Mittellinie und setzt jeden Punkt genau darauf — ohne diesen Schritt stünde jede
* Laterne mitten auf der Fahrbahn. Für jeden Punkt wird senkrecht zur Blickrichtung
* (Normale zu (cos angle, sin angle) ist (-sin angle, cos angle)) von innen nach außen
* durch LAMP_VERGE probiert; die Seite wechselt von Punkt zu Punkt (gerader Index links,
* ungerader rechts), das halbiert die Zahl je Straßenseite und wirkt weniger wie eine
* durchgehende Reihe. Der erste Abstand, dessen Punkt außerhalb aller Straßenpolygone des
* Chunks liegt, gewinnt; findet keiner der sechs einen freien Punkt, entfällt die Laterne
* ganz — wie bei den Palmen (Ruling 22) kein Sonderfall, sondern die vorgesehene Reaktion
* auf eine Straße, die breiter ist als der Suchbereich.
*
* Bewusst nicht in der Python-Pipeline (Ruling 60): chunk.roads kennt nur die Straßen
* dieses einen Chunks, nicht die des Nachbarn — nahe einer Chunk-Grenze kann eine Laterne
* deshalb auf der Fahrbahn stehen bleiben, obwohl eine Straße im Nachbarchunk sie deckt.
* Bekannter, hingenommener Rest (siehe Bericht für Zahlen an echten Daten).
*/
export function vergeLampPoints(points, roads) {
const out = [];
points.forEach((p, i) => {
const side = i % 2 === 0 ? 1 : -1;
const nx = -Math.sin(p.angle) * side, nz = Math.cos(p.angle) * side;
for (const verge of LAMP_VERGE) {
const x = p.x + nx * verge, z = p.z + nz * verge;
if (!roads.some((r) => pointInPolygon(x, z, r.poly))) {
out.push({ x, z, angle: p.angle });
return;
}
}
});
return out;
}
export function palmTransforms(points, seed = 1) {
return points.map(([x, z]) => {
const key = `${x.toFixed(1)},${z.toFixed(1)}`;
@@ -126,27 +179,14 @@ export class PropLayer {
}
/**
* Laternen entlang der Mittellinien (lampPoints, oben).
*
* Ohne diese Methode wäre lampPoints() eine getestete, aber nirgends aufgerufene Funktion,
* und this.lamp ein geladenes, nie benutztes Modell — im gesamten Umsetzungsplan ruft keine
* andere Aufgabe lampPoints() auf. Die Verdrahtung hier verwendet ausschließlich bereits
* spezifizierte, getestete Bausteine (lampPoints exakt wie oben, this.lamp aus load()).
*
* Platzierung exakt auf der Mittellinie, ohne seitlichen Versatz zur Fahrbahnkante: anders
* als bei den Palmen (Python-seitige Suche vom Fahrbahnrand nach außen gegen Straßen- und
* Gebäudepolygone, Ruling 22) kennen die Chunk-Daten keine separate Gehwegspur — nur
* Fahrbahn-, Sand-, Park- und Stadtfläche — und lampPoints(lines, spacing) bekommt bewusst
* keine Straßen- oder Gebäudepolygone übergeben. Ein erfundener fester Versatz ließe sich
* an keiner echten Geometrie prüfen und wäre nach Ruling 22 vermutlich ebenso falsch wie der
* ursprüngliche 4-m-Versatz der Palmen (60 % auf der Fahrbahn). Ruling 18 nimmt "mitten auf
* der Fahrbahn" für Laternen bereits ausdrücklich hin ("an einer Kreuzung ... stehen vier
* Laternen im selben Punkt, mitten auf der Fahrbahn") und behebt dort nur das Stapeln an
* Kreuzungen — genau dafür ist lampPoints() da. Das Modell ist bei 1,7 m laut Poly-Haven-
* Angabe bereits maßstäblich, anders als die Palme also keine Zufallsskalierung.
* Laternen entlang der Mittellinien (lampPoints), seitlich zur Fahrbahnkante versetzt
* (vergeLampPoints, Ruling 60 — Fix-Runde 1; siehe dort für die Begründung). Das Modell
* ist bei 1,7 m laut Poly-Haven-Angabe bereits maßstäblich, anders als die Palme also
* keine Zufallsskalierung.
*/
#buildLamps(chunk, visual) {
const points = lampPoints(chunk.centerlines ?? []);
const centerlinePoints = lampPoints(chunk.centerlines ?? []);
const points = vergeLampPoints(centerlinePoints, chunk.roads ?? []);
if (!points.length || !this.lampParts?.length) return;
const dummy = new Object3D();
const local = new Matrix4();