Memo vom 11.09.2026 umgesetzt
Deploy App / deploy (push) Successful in 28s

Haus
- Oeffnungen aus den Fassadenplaenen von EHLE abgegriffen. Zum Garten steht
  im Erdgeschoss eine einzige, mittige Fensterfront (x -8.82 bis -4.67),
  daneben das Lamellenband vor dem gedeckten Sitzplatz; im Obergeschoss zwei
  Fenster mit zwei schmalen Lamellen dazwischen.
- Der gedeckte Sitzplatz ist nur noch gegen Westen offen, so wie es die
  Suedwestfassade als "Aussensitzplatz" zeigt. Gegen den Garten steht dort
  eine geschlossene Wand - bisher schaute man durch die Fassade hinein.
- Hauszugang an der Nordostfassade statt an der Nordwestfassade: Tuerfluegel
  und feststehende Verglasung, zusammen 3.15 m, darueber das Vordach. Das
  Vordach kennt jetzt seine Fassadenseite.
- Die Fassadennamen im Datenmodell waren vertauscht; jetzt mit Tabelle im
  README festgehalten.

Garten
- Sichtschutz: die gruenen Punkte im Kiesbett sind Ziergraeser, keine
  Polster. Neue Halmgeometrie - schmales, sich verjuengendes Band, das nach
  oben ueberhaengt, rund 1.10 m. Die Bodenleuchten sind jetzt Strahler und
  leuchten Stelen und Graeser von unten an.
- Baenkli: die Fuesse standen quer zur Sitzplatte, weil der Versatz in
  Welt-Z statt entlang der gedrehten Achse gerechnet wurde.
- Obere Ebene bis an das Kraeuterbeet eben auf -0.02. Zwischen Grillfeld und
  Kraeuterbeet stieg der Rasen um gut 30 cm an.
- Unter dem Spielhaus steht der Betonklotz aus dem Plan (graues Rechteck
  1.01 x 2.70 m); die offene Plattform daneben bleibt auf Stuetzen.
- Aus dem Gartenhaus wird ein Gartenschrank: 1.80 x 0.50 m, 2.00 m hoch, mit
  dem Ruecken in der Ecke und den Doppeltueren zum Garten.
- Neben dem Gelaender der Osttreppe lag Rasen; dort liegt jetzt derselbe
  Belag wie auf dem Weg, bis an den Treppenkopf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 08:01:39 +02:00
co-authored by Claude Opus 5
parent fad23a34c3
commit fefa8e8c9a
9 changed files with 329 additions and 69 deletions
+91
View File
@@ -0,0 +1,91 @@
Sprecher 1 00:00:03
So, ich mache jetzt hier ein bisschen sein Memo.
Bezüglich paar Sachen, die wir noch nicht so gut gefallen.
Ich bin gerade am umherlaufen.
Hier in der 3 d Visualisierung.
Mir schon mal auffällt ist.
Die vom Haus.
Bei der unteren Fassade, also beim unteren Stock an der Fassade, bei den langen Seiten.
Dort ist das Fenster noch falsch, was im unteren Stock ist das Fenster eigentlich ziemlich zentral.
Und auch dort vorne, bei dem beim Überdeckten Sitzplatz.
Hat es eigentlich dort an dieser langen Fassade oder langen an der Länge quasi beim Haus.
Jetzt tut es so.
So, guck spät, also du so spät in der Fassade, wo man direkt in einen gedeckten Sitzplatz reinschauen kann.
Da müsste man definitiv nur anpassen.
Denn.
Auf falle ich das.
Beim Sichtschutz dort, bei den Travertinelementen, da hat es im Boden ja immer so einen kleinen grünen Punkt auf dem Plan gehabt, wo ich auf ein kleines Gewächs hinhäute.
Jetzt hast du mir dort einfach.
So kleine grüne Pille hin gemacht.
Dann kannst du mir irgendetwas quasi so ein bisschen.
Gewechselt in die Höhe wächst also ich so ein bisschen.
Lange Grüne, wirklich schön, voller Travertin.
Ein bisschen hochwachsen quasi, und dann eben von der Lampe von unten her beleuchtet werden.
Dann was mir auch aufgefallen ist, ist.
Im Garten ein Paar, wenn man reinkommt.
Dort hat es einen *******.
Und der Enkelin.
Ich liebe dich komisch Power und Sekretär studieren.
Stütze des Bänkli also den Fuß vom Bänkle.
Der ist wie an einem anderen Ort, ein Bänkli selber.
Dann tu bitte mal einfach nochmal den Plan anschauen und die nächsten Debisualisieren.
Da kann man sich noch etwas besser machen.
Genau.
Dann.
Wenn wir hinten am Haus an der Seite hinten durchläuft.
Also etwa bei dieser langen Seite von der Fassade nach.
Dann kommt ja zuerst der Bereich mit dem Grill.
Und dann kommt eigentlich ein Kräuterbett.
Also die Heimat dort eingezeichnet hast. Die ist noch ein bisschen komisch. Also du hast irgendwie so eine.
Die Wiese geht wie nochmals rauf und das ist auch nicht der Fall. Also die Wiese ist wirklich echt.
Überall eben. Aber die ganze Fläche.
An dieser Seite noch beschrieben.
Und dann können sie sogar direkt am Kies kommen. Das ist nachher das Kräuterbett.
Das wäre noch etwas besser, schöner einzuzeichnen und wir sollen sie auch.
Der Sandkasten kann sie nachher wieder gut.
Und dann?
Das Spielhaus finde ich auch ganz cool, das ist auch gut gemacht.
Was sicher noch fehlt, ist unter dem Spielhaus.
Hat sie noch so einen großen grauen Betonklotz?
Ich bin mir nicht ganz sicher, ob der auf den Plänen eingezeichnet ist. Aber komm doch mal mit Pläne mal reinschauen, ob du etwas ähnliches finden tust.
Es hat wirklich so einen.
Das Nahtbett und klopft es eigentlich unter dem Spielhaus?
Und das ist eben im Bett, durchläuft. Also heute schon gibt.
Wir wollen eben genau irgendwie verticken und darum haben wir eigentlich das Spielhaus. Wieso ein bisschen also?
Ich komme obendrauf.
Planen kann.
Denn.
Bei einem Haustiereingang sind fehlt mir noch ein bisschen der der Hauseingang selber. Also.
Dort schaut man ja auch einfach Pläne nochmals im Detail an den Hauseingang.
Einfach bitte so machen wie auf den Plänen drauf.
Die Fenster könntest mir nur.
Machen an der Aussenfassade.
Das ist noch im unteren Stock.
Fenster gemacht.
Nova stockt es zwar auch noch 1.
Und dann hast du mal hier für den für das Gartenhäuschen.
Quasi ist mir einfach ein riesiger Klotz gebaut.
Und.
Türe ist quasi wirklich hinten raus vom Garten aus, das Macht keinen Sinn.
Tu bitte das Gartenhaus.
Wiesen fürs Gartenschrank machen also.
Quasi einfach ganz hinten in die Ecke, so wie du es auch jetzt schon hast.
Das muss wieso eine Art Gartenschrank rein?
Auto 1 80.
Hochsee ungefähr 10 oder 2 Meter hoch vielleicht.
Etwa 1 80 länge.
Aber eben nur etwa so.
50 Zentimeter tiefe.
Ja, ich glaube, das wäre es für das erste Mal.
Kannst du mir diese Sachen weiterbauen?
Und einfach noch ein letzter Punkt zusammen noch.
Wenn man auch wieder beim Hauseingang dran sind.
Dann hast du mir den Weg jetzt eigentlich schön eingezeichnet mit dem Flaschenstrahl.
Und dann dort, wo das Geländer anfängt, also runter zu den Nachbarn runter, weil dort eben die Treppe noch runter zu den Nachbauern.
Also dort, wo das Gelände schon. Du hast schon die Länder gemacht.
Aber neben den Geländern gibt es einfach eine Visa.
Das ist natürlich falsch. Also es ist nicht eine Wiese dort, sondern halt wirklich.
Den gleichen Look. Eigentlich lade ich hier einfach.
Die Flaschensteine hören dann wieder vorne auf, dort, wo das Gelände anfängt und dann wieder die Treppe runter zu den Pflastersteinen.
Aber für den Nachbarn?
Ja.
+20
View File
@@ -187,6 +187,26 @@ Ausserhalb aller Zonen wird die Interpolation der nächstgelegenen Zone
fortgesetzt, nicht ihr konstanter Ersatzwert – sonst steht der Belag als fortgesetzt, nicht ihr konstanter Ersatzwert – sonst steht der Belag als
schwebende Platte über einer Geländekante. schwebende Platte über einer Geländekante.
### Das Haus nach den Fassadenplänen
Die Öffnungen sind aus den Fassadenplänen von EHLE Architekten (1:100)
abgegriffen. Die Pläne benennen die Fassaden nach Himmelsrichtung, das
Datenmodell nach Achsrichtung:
| Plan | Site | Lage |
| --- | --- | --- |
| Südostfassade | `south`, z +0.96 | lang, zum Garten |
| Nordostfassade | `east`, x +0.28 | kurz, Hauszugang |
| Südwestfassade | `west`, x −13.82 | kurz, zur Terrasse |
| Nordwestfassade | `north`, z −6.87 | lang, zum Nachbarn – im Plansatz nicht gezeichnet, bleibt geschlossen |
Zum Garten steht im Erdgeschoss **eine** mittige Fensterfront, daneben das
Lamellenband vor dem gedeckten Sitzplatz; im Obergeschoss zwei Fenster mit zwei
schmalen Lamellen dazwischen. Der gedeckte Sitzplatz ist **nur gegen Westen
offen** („Aussensitzplatz“ in der Südwestfassade) – gegen den Garten steht dort
eine geschlossene Wand. Ohne sie schaut man von aussen durch die Fassade in den
Sitzplatz hinein.
### Treppen begehen und springen ### Treppen begehen und springen
Stufen und Treppen stecken nicht im Höhenraster, sondern sind eigene Körper. Stufen und Treppen stecken nicht im Höhenraster, sondern sind eigene Körper.
File diff suppressed because one or more lines are too long
+25 -8
View File
@@ -118,7 +118,10 @@ export function buildBuilding(b: Building): Group {
const list: Rect[] = b.openings const list: Rect[] = b.openings
.filter((o) => o.face === face) .filter((o) => o.face === face)
.map((o) => ({ u0: Math.min(o.a, o.b), u1: Math.max(o.a, o.b), y0: o.sill, y1: o.head })); .map((o) => ({ u0: Math.min(o.a, o.b), u1: Math.max(o.a, o.b), y0: o.sill, y1: o.head }));
if (face === 'south') list.push({ u0: b.loggia.x0, u1: b.loggia.x1, y0: b.floorLevel - 0.4, y1: b.loggia.ceiling }); // Der gedeckte Sitzplatz ist nur gegen Westen offen - so zeigt es die
// Suedwestfassade ("Aussensitzplatz"). Gegen den Garten steht dort eine
// geschlossene Wand mit dem Lamellenband davor; ohne sie schaut man von
// aussen durch die Fassade in den Sitzplatz.
if (face === 'west') list.push({ u0: b.loggia.z0, u1: b.loggia.z1, y0: b.floorLevel - 0.4, y1: b.loggia.ceiling }); if (face === 'west') list.push({ u0: b.loggia.z0, u1: b.loggia.z1, y0: b.floorLevel - 0.4, y1: b.loggia.ceiling });
return list; return list;
}; };
@@ -159,6 +162,11 @@ export function buildBuilding(b: Building): Group {
holzMat, holzMat,
); );
loggia.add(back); loggia.add(back);
const front = new Mesh(
boxGeometry((lg.x0 + lg.x1) / 2, lg.z1 - 0.08, lg.x1 - lg.x0, 0.16, b.floorLevel - 0.4, lg.ceiling, 0, holz.tile),
holzMat,
);
loggia.add(front);
const ceil = new PlaneGeometry(lg.x1 - lg.x0, lg.z1 - lg.z0); const ceil = new PlaneGeometry(lg.x1 - lg.x0, lg.z1 - lg.z0);
ceil.rotateX(Math.PI / 2); ceil.rotateX(Math.PI / 2);
const ceilMesh = new Mesh(ceil, new MeshStandardMaterial({ color: 0xd8d4cc, roughness: 0.85 })); const ceilMesh = new Mesh(ceil, new MeshStandardMaterial({ color: 0xd8d4cc, roughness: 0.85 }));
@@ -232,14 +240,23 @@ export function buildBuilding(b: Building): Group {
group.add(roof); group.add(roof);
// --- Vordach ueber dem Eingang ----------------------------------------- // --- Vordach ueber dem Eingang -----------------------------------------
// --- Vordach ueber dem Hauszugang --------------------------------------
// Es kragt senkrecht zu seiner Fassade aus; a und b laufen in der
// Fassadenrichtung (Site-X bei Nord/Sued, Site-Z bei Ost/West).
const cp = b.canopy; const cp = b.canopy;
const canopy = new Mesh( const dachMat = new MeshStandardMaterial({ color: 0x5a5148, roughness: 0.6 });
boxGeometry((cp.a + cp.b) / 2, z0 - cp.depth / 2, cp.b - cp.a, cp.depth, cp.y, cp.y + cp.thickness, 0, 2), const mitte = (cp.a + cp.b) / 2, laenge = Math.abs(cp.b - cp.a);
new MeshStandardMaterial({ color: 0x5a5148, roughness: 0.6 }), let dach: Mesh;
); if (cp.face === 'east' || cp.face === 'west') {
canopy.name = 'Vordach Eingang'; const xf = cp.face === 'east' ? x1 + cp.depth / 2 : x0 - cp.depth / 2;
canopy.castShadow = true; dach = new Mesh(boxGeometry(xf, mitte, cp.depth, laenge, cp.y, cp.y + cp.thickness, 0, 2), dachMat);
group.add(canopy); } else {
const zf = cp.face === 'south' ? z1 + cp.depth / 2 : z0 - cp.depth / 2;
dach = new Mesh(boxGeometry(mitte, zf, laenge, cp.depth, cp.y, cp.y + cp.thickness, 0, 2), dachMat);
}
dach.name = 'Vordach Hauszugang';
dach.castShadow = true;
group.add(dach);
return group; return group;
} }
+75 -22
View File
@@ -5,7 +5,7 @@
*/ */
import { import {
BoxGeometry, BufferGeometry, CylinderGeometry, Group, Mesh, MeshStandardMaterial, BoxGeometry, BufferGeometry, CylinderGeometry, Group, Mesh, MeshStandardMaterial,
PointLight, type MeshStandardMaterialParameters, PointLight, SpotLight, type MeshStandardMaterialParameters,
} from 'three'; } from 'three';
import type { Lamp, Linear, Prop, Stele } from '../site'; import type { Lamp, Linear, Prop, Stele } from '../site';
import { boxGeometry, mergeGeometries } from './geometry'; import { boxGeometry, mergeGeometries } from './geometry';
@@ -80,35 +80,61 @@ function schwebendeStufe(p: Prop, y: number): Group {
// Freistehende Objekte // Freistehende Objekte
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/**
* Gartenschrank: schmaler Schrank mit Doppeltuer und leicht geneigtem Deckel.
* Der Plan reserviert an dieser Stelle 1.8 x 1.8 m fuer ein Gartenhaus; gebaut
* wird ein Schrank von 1.80 x 0.50 m, rund 2.00 m hoch, mit dem Ruecken in der
* Ecke und den Tueren zum Garten.
*/
function gartenhaus(p: Prop, y: number): Group { function gartenhaus(p: Prop, y: number): Group {
const g = new Group(); const g = new Group();
const holz = material(tex.fassadeHolz(), { roughness: 0.8 }); const holz = material(tex.fassadeHolz(), { roughness: 0.8 });
const dunkel = new MeshStandardMaterial({ color: 0x3b3934, roughness: 0.62 });
const metall = new MeshStandardMaterial({ color: 0x55565a, roughness: 0.45, metalness: 0.6 });
const a = (p.rot * Math.PI) / 180;
// u laeuft entlang p.w, v entlang p.d - so wie in boxGeometry.
const ux = Math.cos(a), uz = Math.sin(a);
const vx = -Math.sin(a), vz = Math.cos(a);
const body = new Mesh(boxGeometry(p.x, p.z, p.w, p.d, y, y + p.h, p.rot, 1.2), holz); const body = new Mesh(boxGeometry(p.x, p.z, p.w, p.d, y, y + p.h, p.rot, 1.2), holz);
body.castShadow = true; body.receiveShadow = true; body.castShadow = true; body.receiveShadow = true;
g.add(body); g.add(body);
// Deckel mit leichtem Ueberstand
const roof = new Mesh( const roof = new Mesh(
boxGeometry(p.x, p.z, p.w + 0.16, p.d + 0.16, y + p.h, y + p.h + 0.09, p.rot, 2), boxGeometry(p.x + ux * 0.03, p.z + uz * 0.03, p.w + 0.12, p.d + 0.14, y + p.h, y + p.h + 0.06, p.rot, 2),
material(tex.beton(), { color: 0x6a6a66, roughness: 0.9 }), material(tex.beton(), { color: 0x6a6a66, roughness: 0.9 }),
); );
roof.castShadow = true; roof.castShadow = true;
g.add(roof); g.add(roof);
// Tuer
const door = new Mesh( // Zwei Tuerfluegel auf der Laengsseite, leicht vor der Schrankfront
new BoxGeometry(0.78, 1.95, 0.04), const front = p.w / 2 + 0.015;
new MeshStandardMaterial({ color: 0x3b3934, roughness: 0.6 }), const parts: BufferGeometry[] = [];
); for (const s of [-1, 1]) {
door.position.set(p.x, y + 0.98, p.z - p.d / 2 - 0.02); const off = s * (p.d / 4);
g.add(door); parts.push(boxGeometry(
p.x - ux * front + vx * off, p.z - uz * front + vz * off,
0.03, p.d / 2 - 0.03, y + 0.06, y + p.h - 0.06, p.rot, 1.2,
));
}
const doors = new Mesh(mergeGeometries(parts), dunkel);
doors.castShadow = true;
g.add(doors);
// Zwei schlanke Griffe
const griffe: BufferGeometry[] = [];
for (const s of [-1, 1]) {
const off = s * 0.06;
griffe.push(boxGeometry(
p.x - ux * (front + 0.035) + vx * off, p.z - uz * (front + 0.035) + vz * off,
0.025, 0.025, y + p.h * 0.45, y + p.h * 0.62, p.rot, 1,
));
}
g.add(new Mesh(mergeGeometries(griffe), metall));
return g; return g;
} }
/** Spielhaus durch Zimmermann: Podest, Bretterwaende, Gelaender, Pultdach. */
/**
* Spielhaus des Zimmermanns: ein Stelzenhaus. Der Plan zeigt ein Podest von
* 1.81 x 2.70 m mit der Kote 1.47 - also rund 0.9 m ueber dem Gelaende - und
* darauf an der Ostseite eine Huette von 1.01 x 2.70 m. Der westliche Teil
* des Podests bleibt offene Plattform mit Bruestung und Leiter.
*/
function spielhaus(p: Prop, y: number): Group { function spielhaus(p: Prop, y: number): Group {
const g = new Group(); const g = new Group();
const holz = material(tex.fassadeHolz(), { color: 0xd8b98c, roughness: 0.85 }); const holz = material(tex.fassadeHolz(), { color: 0xd8b98c, roughness: 0.85 });
@@ -122,12 +148,20 @@ function spielhaus(p: Prop, y: number): Group {
const plattW = p.w - hutW; // offene Plattform im Westen const plattW = p.w - hutW; // offene Plattform im Westen
const plattX = p.x - hw + plattW / 2; const plattX = p.x - hw + plattW / 2;
// Unter der Huette steht ein Betonklotz - im Plan das graue Rechteck von
// 1.01 x 2.70 m unter dem Podest. Die offene Plattform daneben steht auf
// zwei schlanken Stuetzen.
const klotz = new Mesh(
boxGeometry(hutX, p.z, hutW, p.d, y - 0.45, deck - 0.06, p.rot, 2),
material(tex.beton(), { color: 0x93918c, roughness: 0.92 }),
);
klotz.name = 'Betonsockel Spielhaus';
klotz.castShadow = true; klotz.receiveShadow = true;
g.add(klotz);
const trag: BufferGeometry[] = []; const trag: BufferGeometry[] = [];
// Vier Stuetzen, etwas eingerueckt for (const dz of [-hd + 0.14, hd - 0.14]) {
for (const dx of [-hw + 0.14, hw - 0.14]) { trag.push(boxGeometry(p.x - hw + 0.14, p.z + dz, 0.11, 0.11, y - 0.35, deck - 0.06, 0, 1));
for (const dz of [-hd + 0.14, hd - 0.14]) {
trag.push(boxGeometry(p.x + dx, p.z + dz, 0.11, 0.11, y - 0.35, deck - 0.06, 0, 1));
}
} }
// Podestboden // Podestboden
trag.push(boxGeometry(p.x, p.z, p.w, p.d, deck - 0.06, deck, p.rot, 1.2)); trag.push(boxGeometry(p.x, p.z, p.w, p.d, deck - 0.06, deck, p.rot, 1.2));
@@ -242,9 +276,14 @@ function bank(p: Prop, y: number): Group {
const seat = new Mesh(boxGeometry(p.x, p.z, p.w, p.d, y + p.h - 0.09, y + p.h, p.rot, belag.tile), stein); const seat = new Mesh(boxGeometry(p.x, p.z, p.w, p.d, y + p.h - 0.09, y + p.h, p.rot, belag.tile), stein);
seat.castShadow = true; seat.receiveShadow = true; seat.castShadow = true; seat.receiveShadow = true;
g.add(seat); g.add(seat);
// Die Fuesse sitzen unter den Enden der Sitzplatte. Der Versatz muss der
// gedrehten v-Achse folgen - sonst stehen sie quer daneben.
const a = (p.rot * Math.PI) / 180;
const vx = -Math.sin(a), vz = Math.cos(a);
for (const s of [-1, 1]) { for (const s of [-1, 1]) {
const off = s * (p.d / 2 - 0.22);
const m = new Mesh( const m = new Mesh(
boxGeometry(p.x, p.z + (s * (p.d / 2 - 0.22)), p.w - 0.08, 0.14, y, y + p.h - 0.09, p.rot, belag.tile), boxGeometry(p.x + vx * off, p.z + vz * off, p.w - 0.08, 0.14, y, y + p.h - 0.09, p.rot, belag.tile),
stein, stein,
); );
m.castShadow = true; m.castShadow = true;
@@ -381,6 +420,7 @@ export function buildLamps(lamps: Lamp[], height: (x: number, z: number) => numb
color: 0xffd9a0, emissive: 0xffc26e, emissiveIntensity: 0, roughness: 0.4, color: 0xffd9a0, emissive: 0xffc26e, emissiveIntensity: 0, roughness: 0.4,
}); });
const pointLights: PointLight[] = []; const pointLights: PointLight[] = [];
const spots: SpotLight[] = [];
for (const l of lamps) { for (const l of lamps) {
const y = height(l.x, l.z); const y = height(l.x, l.z);
@@ -405,6 +445,18 @@ export function buildLamps(lamps: Lamp[], height: (x: number, z: number) => numb
pl.position.set(l.x, y + (l.kind === 'poller' ? 0.6 : 0.1), l.z); pl.position.set(l.x, y + (l.kind === 'poller' ? 0.6 : 0.1), l.z);
pointLights.push(pl); pointLights.push(pl);
g.add(pl); g.add(pl);
if (l.kind === 'boden') {
// Bodenleuchten sind Strahler: sie leuchten die Travertinstelen und die
// Ziergraeser darueber von unten an. Ohne Schatten, sonst wird die
// Abendszene mit 11 Strahlern zu teuer.
const sp = new SpotLight(0xffd6a2, 0, 5.5, 0.55, 0.55, 1.4);
sp.position.set(l.x, y + 0.04, l.z);
sp.target.position.set(l.x, y + 3.0, l.z);
g.add(sp);
g.add(sp.target);
spots.push(sp);
}
group.add(g); group.add(g);
} }
@@ -413,6 +465,7 @@ export function buildLamps(lamps: Lamp[], height: (x: number, z: number) => numb
setOn(on: boolean) { setOn(on: boolean) {
glowMat.emissiveIntensity = on ? 1.6 : 0; glowMat.emissiveIntensity = on ? 1.6 : 0;
for (const pl of pointLights) pl.intensity = on ? 2.2 : 0; for (const pl of pointLights) pl.intensity = on ? 2.2 : 0;
for (const sp of spots) sp.intensity = on ? 5.0 : 0;
}, },
}; };
} }
+47 -13
View File
@@ -6,7 +6,7 @@
* statt Blattgeometrie - das bleibt auch auf dem Handy fluessig. * statt Blattgeometrie - das bleibt auch auf dem Handy fluessig.
*/ */
import { import {
BoxGeometry, BufferAttribute, BufferGeometry, Color, CylinderGeometry, DoubleSide, BufferAttribute, BufferGeometry, Color, CylinderGeometry, DoubleSide,
Group, IcosahedronGeometry, InstancedMesh, Matrix4, Mesh, MeshStandardMaterial, Group, IcosahedronGeometry, InstancedMesh, Matrix4, Mesh, MeshStandardMaterial,
Object3D, Quaternion, Vector3, Object3D, Quaternion, Vector3,
} from 'three'; } from 'three';
@@ -67,12 +67,43 @@ function blob(
return g; return g;
} }
/**
* Ein Grashalm: schmales Band, das sich nach oben verjuengt und ueberhaengt.
* Fusspunkt im Ursprung, Hoehe rund 1.10 m - Lage und Neigung kommen aus der
* Instanzmatrix, damit die Normalen nicht verzerrt werden.
*/
function halmGeometry(): BufferGeometry {
const SEG = 6, H = 1.10, W = 0.026;
const pos: number[] = [];
const uv: number[] = [];
const pt = (i: number) => {
const f = i / SEG;
const y = H * f;
// Ueberhang: der Halm biegt sich mit dem Quadrat der Hoehe nach vorn.
const x = 0.42 * H * f * f;
const w = W * (1 - f * 0.92);
return { x, y, w };
};
for (let i = 0; i < SEG; i++) {
const a = pt(i), b2 = pt(i + 1);
pos.push(a.x, a.y, -a.w, a.x, a.y, a.w, b2.x, b2.y, b2.w);
pos.push(a.x, a.y, -a.w, b2.x, b2.y, b2.w, b2.x, b2.y, -b2.w);
const v0 = i / SEG, v1 = (i + 1) / SEG;
uv.push(0, v0, 1, v0, 1, v1, 0, v0, 1, v1, 0, v1);
}
const g = new BufferGeometry();
g.setAttribute('position', new BufferAttribute(new Float32Array(pos), 3));
g.setAttribute('uv', new BufferAttribute(new Float32Array(uv), 2));
g.computeVertexNormals();
return g;
}
const LAUB = { const LAUB = {
parrotia: 0x76944f, parrotia: 0x76944f,
apfel: 0x7e9c52, apfel: 0x7e9c52,
eibe: 0x466b3a, eibe: 0x466b3a,
strauch: 0x6b8f45, strauch: 0x6b8f45,
gras: 0xa3b45c, gras: 0x9aad5b,
kraut: 0x8ba455, kraut: 0x8ba455,
liguster: 0x557a37, liguster: 0x557a37,
}; };
@@ -437,24 +468,27 @@ export function buildVegetation(
}), 'Beerensträucher'); }), 'Beerensträucher');
if (strauchMesh) group.add(strauchMesh); if (strauchMesh) group.add(strauchMesh);
// --- Graeser: Buendel schmaler, leicht geneigter Halme ------------------- // --- Ziergraeser --------------------------------------------------------
// Im Plan stehen zwischen den Travertinstelen kleine gruene Punkte. Das
// sind keine Polster, sondern Graeser, die an den Stelen hochwachsen und
// abends von den Bodenleuchten angestrahlt werden. Ein Halm ist ein
// schmales, sich verjuengendes Band, das nach oben ueberhaengt.
const graeser = plants.filter((p) => p.type === 'gras' && p.x !== undefined); const graeser = plants.filter((p) => p.type === 'gras' && p.x !== undefined);
if (graeser.length) { if (graeser.length) {
// Ein Halm als Einheitsgeometrie mit Fusspunkt im Ursprung; Neigung und const halm = halmGeometry();
// Position kommen aus der Instanzmatrix, nicht aus einer Mesh-Rotation. const mesh = new InstancedMesh(halm, foliage(LAUB.gras, 0.96, false), graeser.length * 16);
const blade = new BoxGeometry(0.022, 1, 0.022); (mesh.material as MeshStandardMaterial).side = DoubleSide;
blade.translate(0, 0.5, 0);
const mesh = new InstancedMesh(blade, foliage(LAUB.gras, 0.98), graeser.length * 9);
const dummy = new Object3D(); const dummy = new Object3D();
let k = 0; let k = 0;
for (const p of graeser) { for (const p of graeser) {
const y = height(p.x!, p.z!); const y = height(p.x!, p.z!);
for (let i = 0; i < 9; i++) { for (let i = 0; i < 16; i++) {
const a = r() * Math.PI * 2; const a = r() * Math.PI * 2;
const rad = (p.r ?? 0.12) * r() * 0.9; const rad = (p.r ?? 0.12) * r() * 0.8;
dummy.position.set(p.x! + Math.cos(a) * rad, y, p.z! + Math.sin(a) * rad); dummy.position.set(p.x! + Math.cos(a) * rad, y, p.z! + Math.sin(a) * rad);
dummy.rotation.set((r() - 0.5) * 0.45, r() * Math.PI, (r() - 0.5) * 0.45); dummy.rotation.set(0, a + (r() - 0.5) * 1.2, 0);
dummy.scale.set(1, 0.42 + r() * 0.4, 1); const s = 0.85 + r() * 0.55;
dummy.scale.set(0.75 + r() * 0.5, s, 1);
dummy.updateMatrix(); dummy.updateMatrix();
mesh.setMatrixAt(k++, dummy.matrix); mesh.setMatrixAt(k++, dummy.matrix);
} }
@@ -462,7 +496,7 @@ export function buildVegetation(
mesh.count = k; mesh.count = k;
mesh.instanceMatrix.needsUpdate = true; mesh.instanceMatrix.needsUpdate = true;
mesh.castShadow = true; mesh.castShadow = true;
mesh.name = 'Gräser'; mesh.name = 'Ziergräser';
group.add(mesh); group.add(mesh);
} }
+1 -1
View File
@@ -147,7 +147,7 @@ export interface Building {
parapet: number; parapet: number;
outline: Vec2[]; outline: Vec2[];
loggia: { x0: number; z0: number; x1: number; z1: number; ceiling: number }; loggia: { x0: number; z0: number; x1: number; z1: number; ceiling: number };
canopy: { a: number; b: number; depth: number; y: number; thickness: number }; canopy: { face: 'north' | 'south' | 'east' | 'west'; a: number; b: number; depth: number; y: number; thickness: number };
openings: Opening[]; openings: Opening[];
} }
+57 -23
View File
@@ -98,7 +98,12 @@ export const LEVELS = [
// steht dort "Steigung 9.9%". // steht dort "Steigung 9.9%".
id: 'weg-ost', plane: [0, 0.1178, 0.1224], types: ['tegula'], id: 'weg-ost', plane: [0, 0.1178, 0.1224], types: ['tegula'],
note: 'Ostweg als Rampe', note: 'Ostweg als Rampe',
polygon: [[2.80, -4.30], [4.55, -4.30], [4.55, 5.05], [2.80, 5.05]], polygon: [[2.05, -4.30], [4.55, -4.30], [4.55, 5.05], [2.05, 5.05]],
},
{
id: 'weg-ost-gelaender', level: -0.45, types: ['tegula'],
note: 'Belagstreifen neben dem Gelaender der Osttreppe',
polygon: [[2.05, -6.60], [2.92, -6.60], [2.92, -4.15], [2.05, -4.15]],
}, },
{ {
id: 'weg-ost-unten', level: -1.57, types: ['tegula'], id: 'weg-ost-unten', level: -1.57, types: ['tegula'],
@@ -118,7 +123,7 @@ export const LEVELS = [
// ohne feste Vorgabe zieht die Interpolation den Rasen huegelig hoch. // ohne feste Vorgabe zieht die Interpolation den Rasen huegelig hoch.
id: 'oben', level: -0.02, flatten: true, id: 'oben', level: -0.02, flatten: true,
note: 'Obere Ebene: Rasen und Kiesfeld, eben auf -0.02', note: 'Obere Ebene: Rasen und Kiesfeld, eben auf -0.02',
polygon: [[-18.30, 0.90], [-8.60, 0.90], [-8.60, 3.90], [-18.30, 3.90]], polygon: [[-18.30, 0.90], [-4.70, 0.90], [-4.70, 4.00], [-18.30, 4.00]],
}, },
{ {
id: 'unten', id: 'unten',
@@ -218,8 +223,11 @@ export const WALLS = [
/** Freistehende Bauteile und Moeblierung. rot = Drehung um Y in Grad. */ /** Freistehende Bauteile und Moeblierung. rot = Drehung um Y in Grad. */
export const PROPS = [ export const PROPS = [
{ id: 'gartenhaus', type: 'gartenhaus', x: 1.20, z: -3.60, w: 1.8, d: 1.8, h: 2.15, rot: 0, // Der Plan reserviert 1.8 x 1.8 m fuer das Gartenhaus. Gebaut wird dort ein
note: 'Standort Gartenhaus 1,8 x 1,8 m' }, // schmaler Gartenschrank: 1.80 m breit, nur 0.50 m tief, rund 2.00 m hoch,
// mit dem Ruecken in der Ecke und den Tueren zum Garten.
{ id: 'gartenschrank', type: 'gartenhaus', x: 1.87, z: -3.58, w: 0.50, d: 1.80, h: 2.00, rot: 0,
note: 'Gartenschrank 1.80 x 0.50 m, 2.00 m hoch' },
// Der Plan zeichnet zwei Rechtecke: das Podest 1.81 x 2.70 m (x -0.95 bis // Der Plan zeichnet zwei Rechtecke: das Podest 1.81 x 2.70 m (x -0.95 bis
// 0.86) und darauf die Huette 1.01 x 2.70 m an der Ostseite (x -0.15 bis // 0.86) und darauf die Huette 1.01 x 2.70 m an der Ostseite (x -0.15 bis
// 0.86). Die Kote 1.47 im Podest ist die Hoehe des Bodens - ein Stelzenhaus, // 0.86). Die Kote 1.47 im Podest ist die Hoehe des Bodens - ein Stelzenhaus,
@@ -274,7 +282,12 @@ export const EXTRA_SURFACES = [
// beginnt die Wiese. Zwei Leuchten stehen daran. // beginnt die Wiese. Zwei Leuchten stehen daran.
type: 'tegula', type: 'tegula',
note: 'Weg an der Ostseite, Rampe oberhalb der Treppe (Steigung rund 12%)', note: 'Weg an der Ostseite, Rampe oberhalb der Treppe (Steigung rund 12%)',
polygon: [[2.86, -4.18], [4.45, -4.18], [4.45, 4.96], [2.86, 4.96]], polygon: [[2.12, -4.18], [4.45, -4.18], [4.45, 4.96], [2.12, 4.96]],
},
{
type: 'tegula',
note: 'Belagstreifen neben dem Gelaender, bis an den Kopf der Osttreppe',
polygon: [[2.12, -6.55], [2.86, -6.55], [2.86, -4.18], [2.12, -4.18]],
}, },
{ {
type: 'tegula', type: 'tegula',
@@ -386,25 +399,46 @@ export const BUILDING = {
* der Suedwestecke, oben vom Obergeschoss ueberdeckt. * der Suedwestecke, oben vom Obergeschoss ueberdeckt.
*/ */
loggia: { x0: -13.82, z0: -3.06, x1: -9.06, z1: 0.96, ceiling: 2.75 }, loggia: { x0: -13.82, z0: -3.06, x1: -9.06, z1: 0.96, ceiling: 2.75 },
/** Vordach ueber dem Hauseingang an der Nordostfassade (siehe Foto). */ /**
canopy: { a: -3.20, b: 0.28, depth: 1.35, y: 2.60, thickness: 0.16 }, * Vordach ueber dem Hauszugang an der Nordostfassade (x = +0.28). Der
* Situationsplan zeigt ein Eingangspodest von 1.05 + 1.13 m Tiefe, darueber
* das "Vordach (Holzbauer)".
*/
canopy: { face: 'east', a: -2.95, b: 0.75, depth: 2.20, y: 2.28, thickness: 0.16 },
/**
* Oeffnungen, abgegriffen aus den Fassadenplaenen von EHLE Architekten
* (1:100, 27.04.2021). Die Fassaden heissen dort nach Himmelsrichtung:
* Suedostfassade = z +0.96 (lang, zum Garten) -> face 'south'
* Nordostfassade = x +0.28 (kurz, Hauszugang) -> face 'east'
* Suedwestfassade = x -13.82 (kurz, zur Terrasse) -> face 'west'
* Nordwestfassade = z -6.87 (lang, zum Nachbarn) -> face 'north'
* Die Nordwestfassade ist im Plansatz nicht gezeichnet; sie steht am
* Nachbarbau und bleibt geschlossen.
*/
openings: [ openings: [
// Suedwestfassade (z = 0.96 / 0.51) - grosse Fensterfronten zum Garten // -- Suedostfassade (z = +0.96), zum Garten ---------------------------
{ face: 'south', a: -4.40, b: -0.20, sill: 0.0, head: 2.45, kind: 'glass' }, // Erdgeschoss: das Lamellenband vor dem gedeckten Sitzplatz und die
{ face: 'south', a: -12.90, b: -9.40, sill: 0.0, head: 2.45, kind: 'glass' }, // grosse, mittig sitzende Fensterfront.
{ face: 'south', a: -3.90, b: -0.60, sill: 3.35, head: 5.00, kind: 'glass', upper: true }, { face: 'south', a: -12.44, b: -9.32, sill: 0.15, head: 2.20, kind: 'slats' },
{ face: 'south', a: -12.60, b: -9.70, sill: 3.35, head: 5.00, kind: 'glass', upper: true }, { face: 'south', a: -8.82, b: -4.67, sill: 0.0, head: 2.19, kind: 'glass' },
// Westfassade (x = -13.82) - Fenster // Obergeschoss: zwei Fenster aussen, dazwischen zwei schmale Lamellen.
{ face: 'west', a: -5.84, b: -4.24, sill: 0.85, head: 2.35, kind: 'window' }, { face: 'south', a: -12.44, b: -9.98, sill: 3.08, head: 4.90, kind: 'window', upper: true },
{ face: 'west', a: -5.60, b: -4.50, sill: 3.75, head: 5.00, kind: 'window', upper: true }, { face: 'south', a: -8.90, b: -7.67, sill: 3.30, head: 4.90, kind: 'slats', upper: true },
// Nordostfassade (z = -6.87) - Eingang und Lamellenfenster { face: 'south', a: -6.55, b: -5.40, sill: 3.30, head: 4.90, kind: 'slats', upper: true },
{ face: 'north', a: -1.90, b: -0.90, sill: 0.0, head: 2.35, kind: 'door' }, { face: 'south', a: -3.60, b: -1.10, sill: 3.08, head: 4.90, kind: 'window', upper: true },
{ face: 'north', a: -5.60, b: -3.90, sill: 0.90, head: 2.35, kind: 'window' },
{ face: 'north', a: -11.60, b: -10.20, sill: 0.90, head: 2.35, kind: 'slats' }, // -- Nordostfassade (x = +0.28), Hauszugang ---------------------------
{ face: 'north', a: -5.40, b: -4.10, sill: 3.75, head: 5.10, kind: 'window', upper: true }, // Der Zugang ist ein Element aus Tuerfluegel und feststehender
{ face: 'north', a: -11.40, b: -10.40, sill: 3.75, head: 5.10, kind: 'slats', upper: true }, // Verglasung, zusammen rund 3.15 m breit.
// Ostfassade (x = 0.28) { face: 'east', a: -0.65, b: 0.48, sill: 0.0, head: 2.20, kind: 'door' },
{ face: 'east', a: -5.60, b: -4.30, sill: 0.90, head: 2.35, kind: 'window' }, { face: 'east', a: -2.67, b: -0.71, sill: 0.0, head: 2.20, kind: 'glass' },
{ face: 'east', a: -6.40, b: -5.54, sill: 1.19, head: 2.15, kind: 'slats' },
{ face: 'east', a: -5.93, b: -3.36, sill: 3.18, head: 4.94, kind: 'window', upper: true },
// -- Suedwestfassade (x = -13.82), zur Terrasse -----------------------
// Der grosse Ausschnitt daneben ist der gedeckte Sitzplatz selbst.
{ face: 'west', a: -5.60, b: -3.91, sill: 0.42, head: 2.01, kind: 'slats' },
{ face: 'west', a: -5.71, b: -3.07, sill: 3.17, head: 4.97, kind: 'window', upper: true },
], ],
}; };
+12 -1
View File
@@ -173,6 +173,15 @@ for (const st of stelen) {
} }
stelen.sort((a, b) => a.z - b.z || a.x - b.x); stelen.sort((a, b) => a.z - b.z || a.x - b.x);
/**
* Die gruenen Punkte, die im Plan zwischen den Travertinstelen im Kiesbett
* stehen, sind keine Polster, sondern Ziergraeser - sie wachsen an den
* Stelen hoch und werden von den Bodenleuchten angestrahlt.
*/
function imKiesbett(x, z) {
return stelen.some((s) => Math.hypot(s.x - x, s.z - z) < 1.1);
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// 2. Hecken und Pflanzen aus den Plankreisen // 2. Hecken und Pflanzen aus den Plankreisen
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -187,7 +196,9 @@ for (const s of raw.shapes) {
const { cx, cz, r } = s.circle; const { cx, cz, r } = s.circle;
if (r < 0.06) continue; // Lampensymbole und Punktraster if (r < 0.06) continue; // Lampensymbole und Punktraster
if (treeAt(cx, cz)) continue; // Solitaere kommen aus dem Authoring if (treeAt(cx, cz)) continue; // Solitaere kommen aus dem Authoring
plants.push({ type, x: round(cx), z: round(cz), r: round(r, 2) }); // Kraeuter im Kiesbett des Sichtschutzes sind Ziergraeser.
const art = (type === 'kraut' || type === 'gras') && imKiesbett(cx, cz) ? 'gras' : type;
plants.push({ type: art, x: round(cx), z: round(cz), r: round(r, 2) });
} }
// Der Ligusterhecken-Streifen ist im Plan eine Flaeche, keine Kreise. // Der Ligusterhecken-Streifen ist im Plan eine Flaeche, keine Kreise.
for (const s of raw.shapes) { for (const s of raw.shapes) {