Nordgrenze
- Sitzmauer und Travertinmauer waren vertauscht. Der Plan zeichnet aussen,
auf der Terrasse der Nachbarn, die "Sitzmauer" (0.55 tief, rund 40 cm
hoch: Terrasse -1.77, Krone -1.35) und direkt dahinter die
"Travertinmauer", die jetzt bis auf das Niveau der Nachbarn hinunter-
reicht - sie sehen Naturstein statt Beton.
- Terrasse der Nachbarn fest auf -1.77.
- Schotterwiese (Flaechen 4 und 5) eben: quer kein Hang mehr, laengs folgt
sie der Feuerwehrzufahrt. Unter der Ligusterhecke fassen Winkelelemente
den Garten der Nachbarn, so wie der Plan sie dort beschriftet. Wiesen mit
fester Ebene werden jetzt plan gerendert statt dem Raster zu folgen.
- Eingang fuer die Nachbarn: Travertinstufe in der Luecke zwischen Hecke
und Mauer ("Stufe" im Plan), darueber ein Torbogen aus Cortenstahl mit
Kletterrose. Kein Tor.
Rundgang
- Klick auf den Boden laeuft dorthin, Ziehen dreht den Blick. Kein
Mausfang mehr.
- Weiches Anfahren und Bremsen, gedaempftes Nachfuehren bei Stufen statt
Fall-und-Aufprall, Rutschen an Hindernissen.
- Travertinmauern mit fester Krone sind begehbar; man steigt ueber die
Sitzmauer vom Sitzplatz auf den Rasen, statt springen zu muessen.
- Gartentor oeffnet sich beim Naehertreten und schliesst hinter einem, E
uebersteuert. Schwelle unter dem Tor schliesst die Luecke zum Podest.
- Blickrichtung blieb beim Wechsel Uebersicht/Rundgang nicht erhalten
(Vorzeichenfehler in yawDegrees).
- mergeGeometries loest indizierte Geometrien vorher auf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+125
-19
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* Freistehende Bauteile: Travertinstelen, Gartenhaus, Spielhaus,
|
||||
* Flammengrill, Baenkli, Maehroboter-Garage, Gartentor und die Leuchten.
|
||||
* Flammengrill, Baenkli, Maehroboter-Garage, Gartentor, Torbogen und die Leuchten.
|
||||
* Die Mauern stehen in walls.ts.
|
||||
*/
|
||||
import {
|
||||
BoxGeometry, BufferGeometry, CylinderGeometry, Group, Mesh, MeshStandardMaterial,
|
||||
PointLight, SpotLight, type MeshStandardMaterialParameters,
|
||||
BoxGeometry, BufferGeometry, CatmullRomCurve3, CylinderGeometry, Group, IcosahedronGeometry,
|
||||
Mesh, MeshStandardMaterial, PointLight, SpotLight, TubeGeometry, Vector3,
|
||||
type MeshStandardMaterialParameters,
|
||||
} from 'three';
|
||||
import type { Lamp, Linear, Prop, Stele } from '../site';
|
||||
import { boxGeometry, mergeGeometries } from './geometry';
|
||||
@@ -346,35 +347,139 @@ function schachtdeckel(p: Prop, y: number): Group {
|
||||
* Flachstahl und senkrechte Staebe im Abstand von rund 11 cm - das Tor bleibt
|
||||
* durchsichtig, es ist kein geschlossenes Blatt.
|
||||
*/
|
||||
/**
|
||||
* Gartentor mit drehbarem Fluegel. Die Pfosten stehen fest, der Fluegel haengt
|
||||
* in einer eigenen Gruppe am Scharnier - so kann der Rundgang ihn oeffnen und
|
||||
* schliessen. Er schlaegt nach aussen auf das Podest auf, weil innen gleich
|
||||
* die beiden Stufen liegen.
|
||||
*/
|
||||
function gartentor(p: Prop, y: number): Group {
|
||||
const g = new Group();
|
||||
const mat = new MeshStandardMaterial({ color: 0x43464a, roughness: 0.45, metalness: 0.55 });
|
||||
const a = (p.rot * Math.PI) / 180;
|
||||
// Laengsrichtung des Fluegels in der Ebene, quer dazu die Blattdicke.
|
||||
const ux = Math.cos(a), uz = Math.sin(a);
|
||||
const at = (t: number): [number, number] => [p.x + ux * t, p.z + uz * t];
|
||||
const parts: BufferGeometry[] = [];
|
||||
const half = p.w / 2;
|
||||
const top = y + p.h;
|
||||
|
||||
// Pfosten links und rechts, etwas hoeher als der Fluegel
|
||||
for (const t of [-half - 0.05, half + 0.05]) {
|
||||
const [x, z] = at(t);
|
||||
parts.push(boxGeometry(x, z, 0.08, 0.08, y - 0.35, top + 0.06, p.rot, 1));
|
||||
const pfosten: BufferGeometry[] = [];
|
||||
for (const s of [-half - 0.05, half + 0.05]) {
|
||||
pfosten.push(boxGeometry(p.x + ux * s, p.z + uz * s, 0.08, 0.08, y - 0.35, top + 0.06, p.rot, 1));
|
||||
}
|
||||
// Rahmen: unterer und oberer Riegel
|
||||
for (const [y0, y1] of [[y + 0.08, y + 0.14], [top - 0.08, top]] as const) {
|
||||
parts.push(boxGeometry(p.x, p.z, p.w, 0.035, y0, y1, p.rot, 1));
|
||||
const posts = new Mesh(mergeGeometries(pfosten), mat);
|
||||
posts.castShadow = true;
|
||||
g.add(posts);
|
||||
|
||||
// Fluegel in Scharnier-Koordinaten: er laeuft vom Scharnier aus in lokal -X.
|
||||
const teile: BufferGeometry[] = [];
|
||||
for (const [y0, y1] of [[0.08, 0.14], [p.h - 0.08, p.h]] as const) {
|
||||
teile.push(boxGeometry(-half, 0, p.w - 0.02, 0.035, y0, y1, 0, 1));
|
||||
}
|
||||
// Senkrechte Staebe
|
||||
for (const x of [-0.03, -p.w + 0.03]) teile.push(boxGeometry(x, 0, 0.04, 0.04, 0.08, p.h, 0, 1));
|
||||
const n = Math.max(4, Math.round(p.w / 0.11));
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const [x, z] = at(-half + (i * p.w) / n);
|
||||
parts.push(boxGeometry(x, z, 0.016, 0.016, y + 0.08, top, p.rot, 1));
|
||||
for (let i = 1; i < n; i++) {
|
||||
teile.push(boxGeometry(-(i * p.w) / n, 0, 0.016, 0.016, 0.08, p.h, 0, 1));
|
||||
}
|
||||
const m = new Mesh(mergeGeometries(parts), mat);
|
||||
m.castShadow = true;
|
||||
g.add(m);
|
||||
const leaf = new Mesh(mergeGeometries(teile), mat);
|
||||
leaf.castShadow = true;
|
||||
|
||||
// Scharnier am +u-Ende. Lokal -X zeigt im geschlossenen Zustand nach -u:
|
||||
// (-cos th, sin th) = (-ux, -uz) => th = atan2(-uz, ux).
|
||||
const pivot = new Group();
|
||||
pivot.name = 'Torfluegel';
|
||||
pivot.position.set(p.x + ux * half, y, p.z + uz * half);
|
||||
const zu = Math.atan2(-uz, ux);
|
||||
pivot.rotation.y = zu;
|
||||
pivot.add(leaf);
|
||||
g.add(pivot);
|
||||
g.userData.fluegel = pivot;
|
||||
g.userData.zu = zu;
|
||||
g.userData.offen = zu + 1.65;
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Torbogen fuer den Eingang der Nachbarn: zwei Baender aus Cortenstahl, die
|
||||
* als Pfosten aufsteigen und oben einen Halbkreis schlagen, dazwischen
|
||||
* Querstaebe. An einer Seite rankt eine Kletterrose hoch. Kein Tor - der
|
||||
* Durchgang bleibt offen.
|
||||
*/
|
||||
function torbogen(p: Prop, y: number): Group {
|
||||
const g = new Group();
|
||||
const corten = new MeshStandardMaterial({ color: 0x8b4a27, roughness: 0.78, metalness: 0.25 });
|
||||
const a = (p.rot * Math.PI) / 180;
|
||||
// u: quer zum Durchgang (Spannweite), v: laengs des Durchgangs (Tiefe)
|
||||
const ux = Math.cos(a), uz = Math.sin(a);
|
||||
const vx = -Math.sin(a), vz = Math.cos(a);
|
||||
const r = p.w / 2;
|
||||
const P = (u: number, yy: number, v: number) =>
|
||||
new Vector3(p.x + ux * u + vx * v, yy, p.z + uz * u + vz * v);
|
||||
|
||||
const baender: BufferGeometry[] = [];
|
||||
for (const v of [-p.d / 2, p.d / 2]) {
|
||||
const pts: Vector3[] = [P(-r, y - 0.2, v), P(-r, y + p.h * 0.5, v), P(-r, y + p.h, v)];
|
||||
for (let i = 1; i < 16; i++) {
|
||||
const w = Math.PI - (i / 16) * Math.PI;
|
||||
pts.push(P(Math.cos(w) * r, y + p.h + Math.sin(w) * r, v));
|
||||
}
|
||||
pts.push(P(r, y + p.h, v), P(r, y + p.h * 0.5, v), P(r, y - 0.2, v));
|
||||
const kurve = new CatmullRomCurve3(pts, false, 'centripetal');
|
||||
baender.push(new TubeGeometry(kurve, 96, 0.026, 6, false));
|
||||
}
|
||||
// Querstaebe laengs des Bogens
|
||||
for (let i = 0; i <= 8; i++) {
|
||||
const w = Math.PI - (i / 8) * Math.PI;
|
||||
const u = Math.cos(w) * r, yy = y + p.h + Math.sin(w) * r;
|
||||
const kurve = new CatmullRomCurve3([P(u, yy, -p.d / 2), P(u, yy, p.d / 2)]);
|
||||
baender.push(new TubeGeometry(kurve, 2, 0.012, 5, false));
|
||||
}
|
||||
for (const u of [-r, r]) {
|
||||
for (const yy of [y + 0.45, y + 0.95, y + 1.45]) {
|
||||
const kurve = new CatmullRomCurve3([P(u, yy, -p.d / 2), P(u, yy, p.d / 2)]);
|
||||
baender.push(new TubeGeometry(kurve, 2, 0.012, 5, false));
|
||||
}
|
||||
}
|
||||
const bogen = new Mesh(mergeGeometries(baender), corten);
|
||||
bogen.castShadow = true;
|
||||
g.add(bogen);
|
||||
|
||||
// Kletterrose an einer Seite: viele kleine, unregelmaessige Laubbueschel den
|
||||
// Pfosten hinauf und ein Stueck ueber den Bogen, dazwischen einzelne Blueten.
|
||||
const laub = new MeshStandardMaterial({ color: 0x35592a, roughness: 0.92, flatShading: true });
|
||||
const bluete = new MeshStandardMaterial({ color: 0xd47a8e, roughness: 0.7 });
|
||||
const laubGeo = new IcosahedronGeometry(1, 0);
|
||||
let seed = 7;
|
||||
const rnd = () => ((seed = (seed * 16807) % 2147483647) / 2147483647);
|
||||
const ranke: [number, number][] = [];
|
||||
for (let i = 0; i < 14; i++) ranke.push([-r, y + 0.05 + (i / 13) * p.h]);
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
const w = Math.PI - (i / 16) * Math.PI;
|
||||
ranke.push([Math.cos(w) * r, y + p.h + Math.sin(w) * r]);
|
||||
}
|
||||
const blaetter: BufferGeometry[] = [];
|
||||
const blueten: BufferGeometry[] = [];
|
||||
for (const [u, yy] of ranke) {
|
||||
const n = 3 + Math.floor(rnd() * 3);
|
||||
for (let k = 0; k < n; k++) {
|
||||
const geo = laubGeo.clone();
|
||||
geo.scale(0.05 + rnd() * 0.05, 0.04 + rnd() * 0.04, 0.05 + rnd() * 0.05);
|
||||
geo.rotateY(rnd() * 6.28);
|
||||
geo.rotateX((rnd() - 0.5) * 0.8);
|
||||
const q = P(u + (rnd() - 0.5) * 0.16, yy + (rnd() - 0.5) * 0.14, (rnd() - 0.5) * p.d * 1.1);
|
||||
geo.translate(q.x, q.y, q.z);
|
||||
blaetter.push(geo);
|
||||
if (rnd() < 0.22) {
|
||||
const bg = laubGeo.clone();
|
||||
bg.scale(0.028, 0.024, 0.028);
|
||||
bg.translate(q.x + (rnd() - 0.5) * 0.06, q.y + 0.04, q.z + (rnd() - 0.5) * 0.06);
|
||||
blueten.push(bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
const rose = new Mesh(mergeGeometries(blaetter), laub);
|
||||
rose.castShadow = true;
|
||||
g.add(rose);
|
||||
if (blueten.length) g.add(new Mesh(mergeGeometries(blueten), bluete));
|
||||
return g;
|
||||
}
|
||||
|
||||
@@ -390,6 +495,7 @@ export function buildProps(props: Prop[], height: (x: number, z: number) => numb
|
||||
case 'flammengrill': obj = flammengrill(p, y); break;
|
||||
case 'bank': obj = bank(p, y); break;
|
||||
case 'tor': obj = gartentor(p, y); break;
|
||||
case 'torbogen': obj = torbogen(p, y); break;
|
||||
case 'stufe': obj = schwebendeStufe(p, y); break;
|
||||
case 'roboter': obj = roboterGarage(p, y); break;
|
||||
case 'wasserhahn': obj = wasserhahn(p, y); break;
|
||||
|
||||
Reference in New Issue
Block a user