Qualitaetssprung: ebene Belaege, echte Bauteile, Sichtschutz
Deploy App / deploy (push) Successful in 49s

Nach der Plankontrolle ueberarbeitet:

- Belaege sind eben. Je Belag wird aus den Hoehenkoten eine Ebene bestimmt
  (Gefaelle <= 5 %, am Kotenschwerpunkt aufgehaengt, nur Koten derselben
  Gelaendezone). Das Raster wird darauf gezwungen und dort nicht geglaettet.
  Jeder Belag bekommt eine senkrechte Sichtkante zum Gelaende.
- Mauern sind Bauteile: Travertin-Schichtmauerwerk mit versetzten Stossfugen
  und Abdeckplatte, einzelne Winkelelemente mit Fussplatte, Ortbeton in
  Schaltafelfeldern.
- Sichtschutz: die Eibenreihen des Plans werden zu geschnittenen Hecken
  verkettet, ebenso die Ligusterhecke. Lamellenwand im Planrhythmus.
- Der Plan zeichnet die Travertinplatten 40x60 einzeln im Dekorkies - jetzt
  30 erhabene Platten an den Planpositionen statt geschlossenem Belag.
- Normalmaps aus dem Fugenbild fuer Platten, Pflaster, Holzschalung und Laub.
- Nachbarhaus Zollet richtig platziert (stand auf dem Nachbargarten) und
  wie Haus 9a ausgebildet.

Behobene Fehler: boxGeometry erzeugte Seiten und Deckel mit umgekehrtem
Wickelsinn - saemtliche Boxen waren innen nach aussen gekehrt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 07:47:47 +02:00
co-authored by Claude Opus 5
parent f2b7b1b236
commit 8979775895
24 changed files with 1452 additions and 319 deletions
+120 -5
View File
@@ -218,9 +218,11 @@ export function boxGeometry(
[cx + u * ca - v * sa, y, cz + u * sa + v * ca];
const hw = w / 2, hd = d / 2;
const s = 1 / tile;
const faces: [Vec2, Vec2, number][] = [
[[-hw, -hd], [hw, -hd], 1], [[hw, -hd], [hw, hd], 1],
[[hw, hd], [-hw, hd], 1], [[-hw, hd], [-hw, -hd], 1],
// Umlauf im Uhrzeigersinn in (u, v). Damit zeigen die Flaechennormalen nach
// aussen - andersherum sieht man in die Box hinein.
const faces: [Vec2, Vec2][] = [
[[-hw, -hd], [-hw, hd]], [[-hw, hd], [hw, hd]],
[[hw, hd], [hw, -hd]], [[hw, -hd], [-hw, -hd]],
];
let uRun = 0;
for (const [p0, p1] of faces) {
@@ -232,9 +234,13 @@ export function boxGeometry(
uv.push(uRun * s, y0 * s, (uRun + len) * s, y1 * s, uRun * s, y1 * s);
uRun += len;
}
// Deckel
// Deckel, ebenfalls nach aussen (oben) gewickelt
const t0 = P(-hw, -hd, y1), t1 = P(hw, -hd, y1), t2 = P(hw, hd, y1), t3 = P(-hw, hd, y1);
pos.push(...t0, ...t1, ...t2, ...t0, ...t2, ...t3);
pos.push(...t0, ...t3, ...t2, ...t0, ...t2, ...t1);
uv.push(0, 0, 0, d * s, w * s, d * s, 0, 0, w * s, d * s, w * s, 0);
// Boden, damit die Box auch von unten geschlossen ist
const b0 = P(-hw, -hd, y0), b1 = P(hw, -hd, y0), b2 = P(hw, hd, y0), b3 = P(-hw, hd, y0);
pos.push(...b0, ...b1, ...b2, ...b0, ...b2, ...b3);
uv.push(0, 0, w * s, 0, w * s, d * s, 0, 0, w * s, d * s, 0, d * s);
const g = new BufferGeometry();
g.setAttribute('position', new BufferAttribute(new Float32Array(pos), 3));
@@ -242,3 +248,112 @@ export function boxGeometry(
g.computeVertexNormals();
return g;
}
/**
* Ebener Belag: das Polygon liegt exakt in der Ebene a*x + b*z + c.
* Gebaute Flaechen sind eben - sie duerfen dem Gelaende nicht folgen.
*/
export function planarSurfaceGeometry(
polygon: Vec2[],
plane: [number, number, number],
tile: number,
lift = 0,
): BufferGeometry {
const [a, b, c] = plane;
const tris = refine(triangulate(polygon), 1.6);
const pos = new Float32Array(tris.length * 9);
const uv = new Float32Array(tris.length * 6);
let p = 0, u = 0;
for (const t of tris) {
for (const [x, z] of t) {
pos[p++] = x;
pos[p++] = a * x + b * z + c + lift;
pos[p++] = z;
uv[u++] = x / tile;
uv[u++] = -z / tile;
}
}
const g = new BufferGeometry();
g.setAttribute('position', new BufferAttribute(pos, 3));
g.setAttribute('uv', new BufferAttribute(uv, 2));
g.computeVertexNormals();
return g;
}
/**
* Sichtkante eines Belags: senkrechtes Band von der Belagsoberkante hinunter
* bis unter das Gelaende. Erst damit wirkt eine Terrasse wie gebaut und nicht
* wie aufgemalt.
*/
export function surfaceEdgeGeometry(
polygon: Vec2[],
topAt: (x: number, z: number) => number,
bottomAt: (x: number, z: number) => number,
tile: number,
step = 0.4,
): BufferGeometry {
const pos: number[] = [];
const uv: number[] = [];
let run = 0;
const s = 1 / tile;
const n = polygon.length;
for (let i = 0; i < n; i++) {
const [ax, az] = polygon[i];
const [bx, bz] = polygon[(i + 1) % n];
const len = Math.hypot(bx - ax, bz - az);
const parts = Math.max(1, Math.round(len / step));
for (let k = 0; k < parts; k++) {
const t0 = k / parts, t1 = (k + 1) / parts;
const x0 = ax + (bx - ax) * t0, z0 = az + (bz - az) * t0;
const x1 = ax + (bx - ax) * t1, z1 = az + (bz - az) * t1;
const ty0 = topAt(x0, z0), ty1 = topAt(x1, z1);
const by0 = Math.min(bottomAt(x0, z0), ty0 - 0.012);
const by1 = Math.min(bottomAt(x1, z1), ty1 - 0.012);
const u0 = run, u1 = run + len / parts;
// Aussenseite: Polygon laeuft im Gegenuhrzeigersinn, also liegt die
// Sichtseite rechts der Laufrichtung.
pos.push(x0, ty0, z0, x0, by0, z0, x1, by1, z1);
uv.push(u0 * s, ty0 * s, u0 * s, by0 * s, u1 * s, by1 * s);
pos.push(x0, ty0, z0, x1, by1, z1, x1, ty1, z1);
uv.push(u0 * s, ty0 * s, u1 * s, by1 * s, u1 * s, ty1 * s);
run = u1;
}
}
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;
}
/**
* Metallband: schmales Stahlband entlang eines Polygonrandes, wie es der Plan
* zwischen Kies, Rasen und Beeten zeigt.
*/
export function edgingGeometry(
polygon: Vec2[],
topAt: (x: number, z: number) => number,
thickness = 0.005,
proud = 0.022,
depth = 0.12,
): BufferGeometry {
const parts: BufferGeometry[] = [];
const n = polygon.length;
for (let i = 0; i < n; i++) {
const [ax, az] = polygon[i];
const [bx, bz] = polygon[(i + 1) % n];
const len = Math.hypot(bx - ax, bz - az);
if (len < 0.12) continue;
const steps = Math.max(1, Math.round(len / 1.2));
for (let k = 0; k < steps; k++) {
const t0 = k / steps, t1 = (k + 1) / steps;
const x0 = ax + (bx - ax) * t0, z0 = az + (bz - az) * t0;
const x1 = ax + (bx - ax) * t1, z1 = az + (bz - az) * t1;
const cx = (x0 + x1) / 2, cz = (z0 + z1) / 2;
const y = topAt(cx, cz);
const ang = (Math.atan2(z1 - z0, x1 - x0) * 180) / Math.PI;
parts.push(boxGeometry(cx, cz, len / steps + 0.004, thickness, y + proud - depth, y + proud, ang, 1));
}
}
return parts.length ? mergeGeometries(parts) : new BufferGeometry();
}