feat(world): Materialien, Kollision und Laternen für die neuen Straßen

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-17 21:26:18 +02:00
1 parent ca4f7161af
commit 25248b3929
8 files changed
+205 -110

No files matched your search

+48 -33
View File
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { STREET_LAMP_HEIGHT, lampFit, lampPoints, palmTransforms, pointInPolygon, vergeLampPoints } from "../src/world/props.js";
import { STREET_LAMP_HEIGHT, chunkLampPoints, lampFit, lampPoints, palmTransforms, pointInPolygon,
sidewalkLampPoints } from "../src/world/props.js";
describe("props", () => {
it("setzt Laternen im gewünschten Abstand entlang der Linie", () => {
@@ -66,43 +67,57 @@ describe("pointInPolygon", () => {
});
});
describe("vergeLampPoints", () => {
it("versetzt eine Laterne von der Fahrbahn zum ersten freien Abstand, senkrecht zur Linie", () => {
// angle 0 -> Blickrichtung (1, 0), Normale (0, 1). Straße 10 m breit (Halbbreite 5 m):
// der erste Versatz aus LAMP_VERGE (6 m) reicht bereits über den Fahrbahnrand hinaus.
const points = [{ x: 0, z: 0, angle: 0 }];
const roads = [{ poly: [[[-50, -5], [50, -5], [50, 5], [-50, 5]]], bridge: false }];
const out = vergeLampPoints(points, roads);
describe("Laternen auf dem Gehweg", () => {
// Straße entlang der x-Achse, angle 0: Die Normale ist (0, 1). Der Gehweg liegt nur auf der Seite
// z > 0, 3,5 bis 6 m neben der Mittellinie — wie bei einer Wohnstraße mit 7 m Fahrbahn.
const gehweg = [{ poly: [[[-20, 3.5], [20, 3.5], [20, 6], [-20, 6]]] }];
it("stellt die Laterne auf den Gehweg, so dicht an die Bordsteinkante wie möglich", () => {
const out = sidewalkLampPoints([{ x: 0, z: 0, angle: 0 }], gehweg);
expect(out).toHaveLength(1);
expect(out[0].x).toBeCloseTo(0);
expect(out[0].z).toBeCloseTo(6);
expect(pointInPolygon(out[0].x, out[0].z, roads[0].poly)).toBe(false);
expect(out[0].z).toBeCloseTo(4); // der erste Abstand aus LAMP_VERGE, der auf dem Gehweg liegt
});
it("lässt eine Laterne entfallen, wenn keiner der sechs Abstände die Fahrbahn verlässt", () => {
// Halbbreite 20 m > größter Versatz (16 m) -> jede Probe bleibt auf der Fahrbahn.
const points = [{ x: 0, z: 0, angle: 0 }];
const roads = [{ poly: [[[-50, -20], [50, -20], [50, 20], [-50, 20]]], bridge: false }];
expect(vergeLampPoints(points, roads)).toHaveLength(0);
});
it("wechselt die Versatzseite von Punkt zu Punkt ab", () => {
// Ohne Straßen gewinnt sofort der erste (kleinste) Versatz bei jedem Punkt.
const points = [{ x: 0, z: 0, angle: 0 }, { x: 10, z: 0, angle: 0 }];
const out = vergeLampPoints(points, []);
expect(out[0].z).toBeCloseTo(6);
expect(out[1].z).toBeCloseTo(-6);
});
// Fix-Runde 2, Befund B / Ruling 63: siehe Kommentar bei pointInPolygon — derselbe
// ungeprüfte Fall (offene statt geschlossene Ringe) steckte auch hier in jeder Vorrichtung.
it("versetzt eine Laterne auch dann, wenn der Straßenring geschlossen ist", () => {
const points = [{ x: 0, z: 0, angle: 0 }];
const ring = [[-50, -5], [50, -5], [50, 5], [-50, 5]];
const roads = [{ poly: [[...ring, ring[0]]], bridge: false }];
const out = vergeLampPoints(points, roads);
it("sucht auch auf der anderen Straßenseite", () => {
const gegenueber = [{ poly: [[[-20, -6], [20, -6], [20, -3.5], [-20, -3.5]]] }];
const out = sidewalkLampPoints([{ x: 0, z: 0, angle: 0 }], gegenueber);
expect(out).toHaveLength(1);
expect(out[0].z).toBeCloseTo(6);
expect(out[0].z).toBeCloseTo(-4);
});
it("lässt eine Laterne weg, für die es keinen Gehweg gibt", () => {
expect(sidewalkLampPoints([{ x: 0, z: 0, angle: 0 }], [])).toEqual([]);
});
it("hängt nicht an der Listenposition", () => {
// Die alte Fassung wählte die Seite nach dem Index: Der zweite Punkt hätte auf der Seite ohne
// Gehweg gesucht und wäre entfallen.
const punkte = [{ x: -10, z: 0, angle: 0 }, { x: 10, z: 0, angle: 0 }];
const vorwaerts = sidewalkLampPoints(punkte, gehweg);
const rueckwaerts = sidewalkLampPoints([...punkte].reverse(), gehweg);
expect(vorwaerts).toHaveLength(2);
const xs = (out) => out.map((p) => p.x).sort((a, b) => a - b);
expect(xs(vorwaerts)).toEqual(xs(rueckwaerts));
});
it("findet den Gehweg auch, wenn sein Ring geschlossen ist (so liegen die Chunk-Dateien vor)", () => {
const ring = [[-20, 3.5], [20, 3.5], [20, 6], [-20, 6]];
expect(sidewalkLampPoints([{ x: 0, z: 0, angle: 0 }], [{ poly: [[...ring, ring[0]]] }])).toHaveLength(1);
});
it("setzt aus einem Chunk im Format von Teil 2 Laternen auf den Gehweg", () => {
// Wohnstraße entlang z = 50, Gehweg auf beiden Seiten; lampPoints() setzt auf 100 m drei Punkte.
const chunk = {
chunk: [0, 0], origin: [0, 0], land: [], surfaces: [], buildings: [], palms: [],
carriageway: [], curb: [], markings: [], lots: [], bridges: [],
edges: [{ line: [[0, 50], [100, 50]], klasse: "wohnstrasse", breite: 12, fahrbahn: 7 }],
sidewalk: [{ poly: [[[0, 53.5], [100, 53.5], [100, 56], [0, 56]]] },
{ poly: [[[0, 44], [100, 44], [100, 46.5], [0, 46.5]]] }],
};
const out = chunkLampPoints(chunk);
expect(out).toHaveLength(3);
for (const p of out) expect(chunk.sidewalk.some((s) => pointInPolygon(p.x, p.z, s.poly))).toBe(true);
});
});