Klima 3D: Strandleben tourismus-reaktiv + Radweg-Dashes durchgehend
- beachLifeCount(): base 8 + 14 wenn state.tourismMode + 4 pro Airport, gedeckelt bei 40. Skaliert die Anzahl der Strandleben-Elemente an die Tourismus-Aktivität (mehr Tourismus = mehr Menschen am Strand). syncBeachLife() wird pro Frame gerufen, rebuildt aber nur, wenn sich die Zahl wirklich geändert hat (O(1) Check bei Stabilität). - Radwege: zurück zu gestrichelter Mittellinie (5 Striche × Periode 0.28). BIKE_SPACING auf exakt BIKE_LENGTH (1.4) gesetzt (vorher 0.92·BIKE_LENGTH), damit die Dashes nahtlos über Segment-Grenzen weiterlaufen — Strich des nächsten Segments landet genau dort, wo die Dash-Periode des vorherigen weitergeht. - Ausrichtung bleibt via coastRotation: Radwege folgen weiter der gleichen Ellipsen-Tangente wie das grüne Zone-Overlay beim Platzieren, sind also parallel zur Küstenkurve. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+49
-17
@@ -2953,21 +2953,24 @@ function makeCloudSeeder() {
|
||||
|
||||
function makeBikePath() {
|
||||
const g = new THREE.Group();
|
||||
// Asphalt bleibt als Grundplatte
|
||||
// Asphalt als Grundplatte
|
||||
const asphalt = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(1.4, 0.04, 0.45),
|
||||
new THREE.MeshStandardMaterial({ color: 0x3a3a3a, roughness: 1 })
|
||||
);
|
||||
asphalt.position.y = 0.02; g.add(asphalt);
|
||||
// Durchgehende weiße Mittellinie: eine einzige Box über die volle
|
||||
// Asphalt-Länge. Bei snap-spacing 1.288 (Überlappung 8 %) greifen
|
||||
// benachbarte Segmente ineinander → visuell eine durchlaufende Linie.
|
||||
const centerline = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(1.4, 0.006, 0.05),
|
||||
new THREE.MeshStandardMaterial({ color: 0xf8f8f8, roughness: 1 })
|
||||
);
|
||||
centerline.position.y = 0.045; g.add(centerline);
|
||||
// Zwei Rad-Pictogramm-Stelen am Rand für Deko
|
||||
// Gestrichelte Mittellinie: 5 gleichlange Striche mit Periode 0.28
|
||||
// (Strich 0.18, Lücke 0.10). Wird zusammen mit BIKE_SPACING = 1.4
|
||||
// (= Bike-Länge) ausgelegt, damit das Muster nahtlos über Segment-
|
||||
// Grenzen weiterläuft, wenn mehrere Radwege aneinander gesnappt
|
||||
// werden.
|
||||
const stripeMat = new THREE.MeshStandardMaterial({ color: 0xf8f8f8, roughness: 1 });
|
||||
for (let i = -2; i <= 2; i++) {
|
||||
const stripe = new THREE.Mesh(new THREE.BoxGeometry(0.18, 0.006, 0.05), stripeMat);
|
||||
stripe.position.set(i * 0.28, 0.045, 0);
|
||||
g.add(stripe);
|
||||
}
|
||||
// Zwei Rad-Pictogramm-Stelen am Rand
|
||||
for (const sx of [-0.55, 0.55]) {
|
||||
const post = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(0.02, 0.025, 0.22, 5),
|
||||
@@ -3209,9 +3212,11 @@ function snapBikesPlacement(rawX, rawZ) {
|
||||
const relX = rawX - best.pos.x;
|
||||
const relZ = rawZ - best.pos.z;
|
||||
const dir = (relX * fx + relZ * fz) >= 0 ? 1 : -1;
|
||||
// Leicht überlappender Abstand (~92 % der Mesh-Länge), damit die Segmente
|
||||
// visuell ineinander greifen statt Lücken zu lassen.
|
||||
const SPACING = BIKE_LENGTH * 0.92;
|
||||
// Exakte Bike-Länge als Spacing: Dashes der Mittellinie (Periode 0.28,
|
||||
// 5 Striche pro Segment) laufen dadurch nahtlos über Segment-Grenzen
|
||||
// — ein Strich des nächsten Segments liegt genau dort, wo die
|
||||
// Dash-Periode des vorherigen weitergehen würde.
|
||||
const SPACING = BIKE_LENGTH;
|
||||
const snapX = best.pos.x + dir * SPACING * fx;
|
||||
const snapZ = best.pos.z + dir * SPACING * fz;
|
||||
const snapY = Math.max(terrainHeight(snapX, snapZ), 0);
|
||||
@@ -4294,16 +4299,37 @@ function makeBeachDog() {
|
||||
}
|
||||
return g;
|
||||
}
|
||||
// Dynamische Anzahl Strandleben abhängig von Tourismus-Aktivität:
|
||||
// Base 8 (Einheimische) + 14 wenn tourismMode aktiv + 4 pro Flughafen.
|
||||
// Gedeckelt bei 40, damit die Szene nicht platzt.
|
||||
function beachLifeCount() {
|
||||
const base = 8;
|
||||
const tourismBonus = state && state.tourismMode ? 14 : 0;
|
||||
const airports = (state && state.ownedMeasures && state.ownedMeasures.airport
|
||||
&& state.ownedMeasures.airport.count) || 0;
|
||||
return Math.min(40, base + tourismBonus + airports * 4);
|
||||
}
|
||||
let _lastBeachCount = -1;
|
||||
function syncBeachLife() {
|
||||
const c = beachLifeCount();
|
||||
if (c !== _lastBeachCount) {
|
||||
_lastBeachCount = c;
|
||||
buildBeachLife();
|
||||
}
|
||||
}
|
||||
|
||||
function buildBeachLife() {
|
||||
while (beachLifeGroup.children.length) beachLifeGroup.remove(beachLifeGroup.children[0]);
|
||||
// 22 Stellen rund um die Küste, gleichmäßig verteilt — Figuren,
|
||||
// Schirme, Handtücher, Liegestühle und Hunde in wechselnder Mischung.
|
||||
for (let i = 0; i < 22; i++) {
|
||||
// Anzahl dynamisch: mehr Tourismus = mehr Strandleben (bis 40).
|
||||
// Gleichmäßig auf Winkel-Raster mit Jitter verteilt, damit die Elemente
|
||||
// rundum verteilt sitzen statt an einer Stelle zu klumpen.
|
||||
const count = beachLifeCount();
|
||||
for (let i = 0; i < count; i++) {
|
||||
let x = 0, z = 0, y = 0;
|
||||
for (let tries = 0; tries < 24; tries++) {
|
||||
// Winkel aus gleichmäßigem Raster (mit Jitter), damit nicht alle
|
||||
// Elemente zufällig an derselben Seite der Insel hocken.
|
||||
const angleBase = (i / 22) * Math.PI * 2;
|
||||
const angleBase = (i / count) * Math.PI * 2;
|
||||
const a = angleBase + (Math.random() - 0.5) * 0.5;
|
||||
const rScale = 0.80 + Math.random() * 0.10;
|
||||
x = Math.cos(a) * ISLAND_HALF_WIDTH * rScale;
|
||||
@@ -4694,6 +4720,10 @@ function updateAmbient(tNow, dt) {
|
||||
updateVillage(tNow, dt);
|
||||
updateClouds(tNow);
|
||||
updateFallenTrees(dt);
|
||||
// Strandleben-Anzahl reaktiv an Tourismus-Zustand + Airport-Anzahl.
|
||||
// syncBeachLife ist billig (nur Count-Vergleich), rebuildt nur bei
|
||||
// Änderung.
|
||||
syncBeachLife();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -4915,7 +4945,9 @@ function startGame(levelId) {
|
||||
// Start-Dorf aufstellen + Vulkan-Schutzwall zurücksetzen (Engine hatte
|
||||
// hasMountainShield in createGame nicht gesetzt → false/undefined).
|
||||
buildVillage();
|
||||
_lastBeachCount = -1; // Strandleben-Zahl beim nächsten sync neu auswerten
|
||||
buildBeachLife();
|
||||
_lastBeachCount = beachLifeCount();
|
||||
syncMountainShield();
|
||||
syncCitizenBeach();
|
||||
syncMeasureMeshes(); // die drei Start-Bäume sichtbar machen
|
||||
|
||||
Reference in New Issue
Block a user