Klima 3D V2: Session-Abschluss — Strandleben komplett + Berichte

- Strandleben: 22 Stellen (vorher 12), fünf Typen statt drei:
  Figur, Sonnenschirm, Handtuch, Liegestuhl (neu), Hund (neu).
  Positionen über gleichmäßiges Winkel-Raster mit Jitter, damit die
  Elemente rundum verteilt sitzen statt zufällig zusammen zu klumpen.
- makeBeachChair: angeschrägte Rückenlehne + Sitz + vier Beine in
  wechselnder Stoff-Farbe.
- makeBeachDog: Körper + Kopf + Schnauze + Schwanz + vier Beinchen +
  Ohren in zufälliger Fell-Farbe.

- _status.md: auf finalen V2-Stand überschrieben
- _inbox/zentrale/2026-04-19-2130-klima-3d-v2-abschluss.md: Bericht an
  Atlas mit vollständiger Feature-Liste
- _inbox/zentrale/2026-04-19-2135-klima-3d-v2-lehrplan-nachtrag.md:
  Nachtrag an Lehrplan (Modul-Detailseite um 2D/3D-Launcher erweitern)
- _inbox/glossar/2026-04-19-2130-klima-3d-v2-info-topics.md: Info an
  Glossar (17 view-lokale INFO_TOPICS, keine DB-Änderung nötig)

3D V2 ist einsatzbereit — Thomas schließt heute ab, strukturierter
Browser-Regression-Test folgt in späterer Session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 14:49:04 +02:00
parent 59d45e419d
commit ce0164264f
5 changed files with 433 additions and 81 deletions
+82 -11
View File
@@ -4230,27 +4230,98 @@ function makeBeachTowel() {
towel.position.y = 0.002; g.add(towel);
return g;
}
// Liegestuhl: angeschrägte Rückenlehne + Sitzfläche + zwei Beine
function makeBeachChair() {
const g = new THREE.Group();
const colors = [0xe04040, 0x3070c0, 0xe0a040, 0x40a06a, 0xb040c0];
const fabricMat = new THREE.MeshStandardMaterial({
color: colors[Math.floor(Math.random() * colors.length)], roughness: 1
});
const frameMat = new THREE.MeshStandardMaterial({ color: 0x6a4a28, roughness: 1 });
// Sitzfläche
const seat = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.01, 0.08), fabricMat);
seat.position.set(0, 0.06, 0); g.add(seat);
// Rückenlehne (schräg)
const back = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.13, 0.01), fabricMat);
back.rotation.x = 0.4;
back.position.set(0, 0.11, -0.05); g.add(back);
// Beine
for (const sx of [-0.05, 0.05]) {
for (const sz of [-0.03, 0.03]) {
const leg = new THREE.Mesh(new THREE.CylinderGeometry(0.005, 0.005, 0.06, 4), frameMat);
leg.position.set(sx, 0.03, sz); g.add(leg);
}
}
return g;
}
// Kleiner Strand-Hund: dicker Körper + Kopf + Schwanz + vier Beinchen
function makeBeachDog() {
const g = new THREE.Group();
const furColors = [0xd8a870, 0x6a4a28, 0xe0e0e0, 0x2a2a2a, 0xa07040];
const mat = new THREE.MeshStandardMaterial({
color: furColors[Math.floor(Math.random() * furColors.length)], roughness: 1
});
// Körper (horizontaler Zylinder)
const body = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.035, 0.12, 8), mat);
body.rotation.z = Math.PI / 2;
body.position.y = 0.05; g.add(body);
// Kopf
const head = new THREE.Mesh(new THREE.SphereGeometry(0.035, 6, 5), mat);
head.position.set(0.07, 0.07, 0); g.add(head);
// Schnauze (leicht dunkler)
const snout = new THREE.Mesh(
new THREE.BoxGeometry(0.035, 0.025, 0.025),
new THREE.MeshStandardMaterial({ color: 0x2a2018, roughness: 1 })
);
snout.position.set(0.10, 0.06, 0); g.add(snout);
// Schwanz
const tail = new THREE.Mesh(new THREE.CylinderGeometry(0.007, 0.012, 0.05, 5), mat);
tail.rotation.z = -Math.PI / 3;
tail.position.set(-0.08, 0.07, 0); g.add(tail);
// Vier Beine
for (const bx of [0.045, -0.045]) {
for (const bz of [0.025, -0.025]) {
const leg = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.008, 0.04, 5), mat);
leg.position.set(bx, 0.02, bz); g.add(leg);
}
}
// Ohren (zwei kleine Dreiecke)
for (const ey of [-0.02, 0.02]) {
const ear = new THREE.Mesh(new THREE.ConeGeometry(0.012, 0.025, 4), mat);
ear.position.set(0.06, 0.10, ey); g.add(ear);
}
return g;
}
function buildBeachLife() {
while (beachLifeGroup.children.length) beachLifeGroup.remove(beachLifeGroup.children[0]);
// 12 Stellen am Küstenband, zufällig auf Figuren/Schirme/Handtücher verteilt
for (let i = 0; i < 12; i++) {
// 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++) {
let x = 0, z = 0, y = 0;
for (let tries = 0; tries < 20; tries++) {
const a = Math.random() * Math.PI * 2;
const rScale = 0.82 + Math.random() * 0.08;
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 a = angleBase + (Math.random() - 0.5) * 0.5;
const rScale = 0.80 + Math.random() * 0.10;
x = Math.cos(a) * ISLAND_HALF_WIDTH * rScale;
z = Math.sin(a) * ISLAND_HALF_LENGTH * rScale;
y = terrainHeight(x, z);
if (y >= 0.2 && y <= 0.55) break;
if (y >= 0.18 && y <= 0.58) break;
}
if (y < 0.2) continue;
if (y < 0.18) continue;
const roll = Math.random();
const piece = roll < 0.45 ? makeBeachFigure()
: roll < 0.80 ? makeBeachUmbrella()
: makeBeachTowel();
let piece;
if (roll < 0.28) piece = makeBeachFigure();
else if (roll < 0.50) piece = makeBeachUmbrella();
else if (roll < 0.68) piece = makeBeachTowel();
else if (roll < 0.86) piece = makeBeachChair();
else piece = makeBeachDog();
piece.position.set(x, y, z);
piece.rotation.y = Math.random() * Math.PI * 2;
piece.scale.setScalar(0.55 + Math.random() * 0.35); // winzig
piece.scale.setScalar(0.55 + Math.random() * 0.40); // winzig
beachLifeGroup.add(piece);
}
}