feat(world): Parkwege, Felder, Plätze, Pool und Parkplatz als eigene Schichten

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-16 08:34:43 +02:00
1 parent e675e62a16
commit b736afb7ff
10 files changed
+96 -17

No files matched your search

+5 -2
View File
@@ -2,7 +2,7 @@ import { BufferGeometry, Float32BufferAttribute, ShapeUtils, Vector2 } from "thr
const GROUND_THICKNESS = 1.0;
const SURFACE_OFFSET = 0.02; // Sand, Park und Stadtfläche liegen knapp über dem Boden
const ROAD_OFFSET = 0.03; // Straßen wiederum knapp über den Flächen
const ROAD_OFFSET = 0.03; // Straßen und Parkwege wiederum knapp über den Flächen
const BRIDGE_THICKNESS = 0.6;
// Brückendeck lag bisher exakt bei y=0, identisch mit der Bodenoberkante (top weiter unten) —
// garantiertes Z-Fighting überall dort, wo eine Brücke an Land endet, unabhängig von Kamera-
@@ -12,7 +12,7 @@ const BRIDGE_THICKNESS = 0.6;
// klein genug, dass die Brücke weiterhin flach und ohne spürbare Stufe befahrbar bleibt — wie
// die von SURFACE_OFFSET bereits unangetastet gebliebene Kollision auf den Bodenflächen.
const BRIDGE_OFFSET = SURFACE_OFFSET;
export const SURFACE_KINDS = ["sand", "park", "urban"];
export const SURFACE_KINDS = ["sand", "park", "urban", "field", "court", "pool", "parking"];
/** Pastellton je Gebäude, reproduzierbar aus seiner Lage. */
function buildingTint(poly) {
@@ -132,6 +132,9 @@ export function buildChunkGeometry(chunk) {
roads: flatPolygons(roads, { y: ROAD_OFFSET, uvScale: 8 }),
bridges: extrudePolygons(bridges, { base: -BRIDGE_THICKNESS, top: BRIDGE_OFFSET, uvScale: 8 }),
buildings: buildBuildings(chunk.buildings),
// Parkwege (docs/superpowers/specs/2026-09-15-flamingo-park-design.md): flach wie Straßen, Textur in
// echter Größe — Poly Haven misst concrete_pavement mit 1,8 × 1,8 m.
paths: flatPolygons((chunk.paths ?? []).map((path) => path.poly), { y: ROAD_OFFSET, uvScale: 1.8 }),
};
for (const kind of SURFACE_KINDS) {
const rings = (chunk.surfaces ?? []).filter((s) => s.kind === kind).map((s) => s.poly);
+28 -5
View File
@@ -1,12 +1,18 @@
import { DataTexture, MeshStandardMaterial, RepeatWrapping, RGBAFormat, SRGBColorSpace, TextureLoader,
Vector2 } from "three";
export const MATERIAL_KEYS = ["ground", "sand", "park", "urban", "roads", "bridges", "buildings"];
export const MATERIAL_KEYS = ["ground", "sand", "park", "urban", "field", "court", "pool", "parking", "paths",
"roads", "bridges", "buildings"];
export const TEXTURES = {
ground: "grass_path_2", // unbebautes Land
sand: "sand_01", // Strand
park: "leafy_grass", // Grünflächen
urban: "concrete_pavement_03", // versiegelte Stadtfläche
// Flamingo Park (docs/superpowers/specs/2026-09-15-flamingo-park-design.md): Felder im Gras des Parks, der
// Parkplatz im Asphalt der Straßen. Beide lädt createMaterials nicht neu, sondern klont park und roads.
field: "leafy_grass",
parking: "asphalt_02",
paths: "concrete_pavement", // helle Betonplatten, auch für die Gehwege aus Teil 2 gedacht
roads: "asphalt_02",
// 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
@@ -14,8 +20,10 @@ export const TEXTURES = {
bridges: "asphalt_02",
buildings: "white_stucco",
};
// Plätze und Pool sind einfarbig: Sandplätze, Hartplätze und Poolwasser hat Poly Haven nicht.
export const SURFACE_COLORS = { court: 0x6f8c79, pool: 0x3f9fd8 };
const FALLBACK_COLORS = {
ground: 0xa8b878, sand: 0xe8d888, park: 0x8bc34a, urban: 0xd8d8d8,
ground: 0xa8b878, sand: 0xe8d888, park: 0x8bc34a, urban: 0xd8d8d8, paths: 0xd8d2c4,
roads: 0x505050, bridges: 0xb0b0b0, buildings: 0xe8e4dc,
};
// Ruling 55: asphalt_02 misst roh heller (Ø 89.5) als concrete_pavement_03 (Ø 65.9) —
@@ -24,6 +32,11 @@ const FALLBACK_COLORS = {
// 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;
// Felder heben sich vom Rasen ab. material.color kann nur abdunkeln, also Rot und Blau stärker als Grün;
// abgestimmt wird im Browser.
const FIELD_TINT = 0xc8f0b0;
// Klone statt zweiter Ladevorgang: dieselben Texturen, eigene Tönung und eigener polygonOffset.
const CLONES = { field: { from: "park", tint: FIELD_TINT }, parking: { from: "roads" } };
/** Kachelbare Wellen-Normalmap, weil Poly Haven keine Wassertextur hat. */
export function waterNormalData(size = 128) {
@@ -102,7 +115,9 @@ async function loadSet(loader, slug, fallback, { tint, maxAnisotropy } = {}) {
// Fläche), units den senkrechten Blick von oben (Steigung ~ 0) — der Spieler sieht im Spiel
// beides, je nach Kamerawinkel (Vogelperspektive in fly mode wie im Befund, aber auch normale
// Bodenperspektive).
export const POLYGON_OFFSET_ORDER = ["ground", "urban", "park", "sand", "roads"];
// Flamingo Park: Felder, Plätze, Pool und Parkplatz liegen über dem Rasen, die Wege über allen Flächen
// und unter den Straßen, die sie an der Parkkante berühren.
export const POLYGON_OFFSET_ORDER = ["ground", "urban", "park", "sand", "field", "court", "pool", "parking", "paths", "roads"];
export const POLYGON_OFFSETS = Object.fromEntries(
POLYGON_OFFSET_ORDER.map((key, i) => [
@@ -111,7 +126,7 @@ export const POLYGON_OFFSETS = Object.fromEntries(
]),
);
/** Trägt POLYGON_OFFSETS in die passenden Materialien ein. Nur die fünf benannten Schichten —
/** Trägt POLYGON_OFFSETS in die passenden Materialien ein. Nur die benannten Schichten —
* Brücken (eigene, geometrische Lösung, s. geometry.js BRIDGE-Kommentar), Gebäude, Wasser und
* Meeresboden sind eigene Materialobjekte ohne Coplanar-Risiko und bleiben unberührt. */
export function applyPolygonOffsets(materials) {
@@ -124,11 +139,19 @@ export function applyPolygonOffsets(materials) {
export async function createMaterials(maxAnisotropy) {
const loader = new TextureLoader();
const tints = { roads: ROAD_TINT, bridges: ROAD_TINT };
const textured = MATERIAL_KEYS.filter((key) => !CLONES[key] && !SURFACE_COLORS[key]);
const entries = await Promise.all(
MATERIAL_KEYS.map(async (key) =>
textured.map(async (key) =>
[key, await loadSet(loader, TEXTURES[key], FALLBACK_COLORS[key], { tint: tints[key], maxAnisotropy })]),
);
const materials = Object.fromEntries(entries);
for (const [key, { from, tint }] of Object.entries(CLONES)) {
materials[key] = materials[from].clone();
if (tint) materials[key].color.set(tint);
}
for (const [key, color] of Object.entries(SURFACE_COLORS)) {
materials[key] = new MeshStandardMaterial({ color, roughness: key === "pool" ? 0.25 : 0.9, metalness: 0 });
}
materials.buildings.vertexColors = true; // pastellige Tönung je Gebäude
applyPolygonOffsets(materials);
+1 -1
View File
@@ -4,7 +4,7 @@ import { buildChunkGeometry } from "./geometry.js";
import { pointInPolygon } from "./props.js";
const key = (i, j) => `${i}_${j}`;
const DECALS = new Set(["roads", "sand", "park", "urban"]);
const DECALS = new Set(["roads", "sand", "park", "urban", "field", "court", "pool", "parking", "paths"]);
/** Mittelpunkt eines Chunks in three-Koordinaten. */
function chunkCenter(i, j) {