Rueckmeldung Punkt fuer Punkt: Weg rund ums Haus - Der Ostweg ist jetzt durchgehend gepflastert von der Verbundstein- Anpassung (x 2.86) bis zur Grenze (x 4.45) - vorher 0.83 m breit mit Rasenstreifen daneben. Der Weg an der Suedgrenze reicht von der Grenze bis an die Nachbarmauer (z 4.49 bis 5.97). - Winkelelemente Ost auf die Grenzlinie x 4.45; die Linie bei z 4.96 im Sueden war keine eigene Stuetzwand, das ist die Nachbarmauer selbst. Hoehen und Gefaelle - LEVELS koennen jetzt auch eine geneigte Ebene vorgeben. Die Wege sind Rampen: Ostweg rund 12%, Suedweg rund 3%, Feuerwehrzufahrt 13.6% aus den Plankoten -2.08 und -1.08. Der Architekturplan nennt an denselben Stellen "Steigung 9.9%" und "Steigung 13.0%". - Ausserhalb der Zonen wird die Interpolation der naechsten Zone fortgesetzt statt ihres konstanten Ersatzwerts - die Feuerwehrzufahrt stand vorher als schwebende Platte ueber einer Gelaendekante. - Gelaende oestlich der Grenze korrigiert: dort lag ein Huegel ueber dem Weg, obwohl der Plan -0.72 und -1.57 angibt. Obere Ebene - Der Rasen ist eben auf -0.02. Die Koten 0.09 bis 0.26 liegen schon im angehobenen Kiesbett des Sichtschutzes, nicht mehr im Rasen; ohne feste Vorgabe zog die Interpolation den Rasen huegelig hoch. Stufen - Beim gedeckten Sitzplatz stimmen jetzt beide schwebenden Platten: der Plan zeichnet zwei versetzte Rechtecke mit den Koten -0.02 und -0.17, bisher lagen sie auf -0.17 und -0.31 - die untere verschwand im Belag. - Das Gartentor steht auf dem oberen Niveau (-0.13), von dort zwei Stufen hinunter auf den Hauptsitzplatz. - Westtreppe ueber den ganzen Treppenblock (5 Stufen statt 4), die Gelaenderpfosten reichen bis ins Gelaende statt in der Luft zu enden. Rundgang - Treppen und schwebende Stufen sind begehbar: ihre Trittflaechen gehen in die Hoehenabfrage ein, die Stufen sind kein Hindernis mehr. - Springen mit der Leertaste, rund 55 cm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,33 @@ als auskragende Platten mit Schattenfuge, der Flammengrill als anthrazitfarbener
|
||||
Stahlkubus mit Holzfach und runder Feueröffnung. Die Bilder lassen sich mit
|
||||
`node tools/plan-bilder.mjs` aus dem PDF ziehen.
|
||||
|
||||
### Ebene Ebenen, geneigte Wege
|
||||
|
||||
`LEVELS` in `tools/authoring.mjs` gibt einzelnen Bereichen ihre Höhe fest vor,
|
||||
statt sie aus streuenden Koten zu schätzen. Zwei Formen:
|
||||
|
||||
- **`level` + `flatten`** – waagrecht und zwingend. Die obere Ebene mit Rasen
|
||||
und Kiesfeld liegt eben auf −0.02. Ohne diese Vorgabe zieht die Interpolation
|
||||
den Rasen hügelig hoch, weil die Koten 0.09 bis 0.26 schon im angehobenen
|
||||
Kiesbett des Sichtschutzes liegen und nicht mehr im Rasen.
|
||||
- **`plane`** – eine geneigte Ebene, für die Wege. Sie sind Rampen, keine
|
||||
waagrechten Felder: Ostweg rund 12 %, Weg an der Südgrenze rund 3 %,
|
||||
Feuerwehrzufahrt rund 13.6 % (Plankoten −2.08 und −1.08). Der
|
||||
Architekturplan (`Erdgeschoss mit Umgebung 1:100`) beschriftet dieselben
|
||||
Gefälle mit „Steigung 9.9 %“ und „Steigung 13.0 %“.
|
||||
|
||||
Ausserhalb aller Zonen wird die Interpolation der nächstgelegenen Zone
|
||||
fortgesetzt, nicht ihr konstanter Ersatzwert – sonst steht der Belag als
|
||||
schwebende Platte über einer Geländekante.
|
||||
|
||||
### Treppen begehen und springen
|
||||
|
||||
Stufen und Treppen stecken nicht im Höhenraster, sondern sind eigene Körper.
|
||||
Damit man sie begehen kann, rechnet `main.ts` ihre Trittflächen in die
|
||||
Höhenabfrage des Rundgangs ein (`treppeTritte` in `src/scene/structures.ts`);
|
||||
die schwebenden Stufen sind entsprechend **kein** Hindernis mehr. Dazu kommt
|
||||
der Sprung mit der Leertaste – rund 55 cm, genug für jede Mauerkrone.
|
||||
|
||||
### Warum Zonen statt eines einzigen Geländes
|
||||
|
||||
Das Grundstück fällt über rund 3.5 m ab, die Sprünge liegen aber nicht im
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
<div id="crosshair"></div>
|
||||
|
||||
<div id="hint">
|
||||
<span class="desktop">Klicken zum Umsehen · <kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> gehen · <kbd>Shift</kbd> schneller · <kbd>Esc</kbd> Maus freigeben</span>
|
||||
<span class="desktop">Klicken zum Umsehen · <kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> gehen · <kbd>Shift</kbd> schneller · <kbd>Leertaste</kbd> springen · <kbd>Esc</kbd> Maus freigeben</span>
|
||||
<span class="touch">Links ziehen zum Gehen · rechts wischen zum Umsehen</span>
|
||||
</div>
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+20
-6
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Begehen des Gartens: Maus/Tastatur am Desktop, Joystick und Wischen am Handy.
|
||||
* Der Spieler folgt dem Gelaende, kann Stufen bis 45 cm ersteigen und wird
|
||||
* von Mauern, Gebaeuden und Objekten aufgehalten.
|
||||
* Der Spieler folgt dem Gelaende, kann Stufen bis 45 cm ersteigen, mit der
|
||||
* Leertaste springen und wird von Mauern, Gebaeuden und Objekten aufgehalten.
|
||||
*/
|
||||
import { Euler, PerspectiveCamera, Vector3 } from 'three';
|
||||
|
||||
@@ -28,6 +28,9 @@ export interface WalkerOptions {
|
||||
|
||||
const SPEED_WALK = 1.65;
|
||||
const SPEED_RUN = 3.6;
|
||||
/** Absprunggeschwindigkeit: reicht fuer rund 55 cm Sprunghoehe. */
|
||||
const JUMP = 3.3;
|
||||
const GRAVITY = 9.81;
|
||||
|
||||
export class Walker {
|
||||
readonly camera: PerspectiveCamera;
|
||||
@@ -155,13 +158,24 @@ export class Walker {
|
||||
|
||||
this.move(dx, dz);
|
||||
|
||||
// Springen: nur vom Boden weg, danach uebernimmt die Schwerkraft.
|
||||
if (this.keys.has('Space') && this.grounded) {
|
||||
this.velocityY = JUMP;
|
||||
this.grounded = false;
|
||||
}
|
||||
|
||||
// Vertikal: dem Gelaende folgen, Absaetze weich abfangen
|
||||
const ground = this.opts.height(this.pos.x, this.pos.z) + this.opts.eyeHeight;
|
||||
if (this.pos.y > ground + 0.02) {
|
||||
this.velocityY -= 9.81 * dt;
|
||||
if (!this.grounded || this.pos.y > ground + 0.02) {
|
||||
this.velocityY -= GRAVITY * dt;
|
||||
this.pos.y += this.velocityY * dt;
|
||||
if (this.pos.y <= ground) { this.pos.y = ground; this.velocityY = 0; this.grounded = true; }
|
||||
else this.grounded = false;
|
||||
if (this.velocityY <= 0 && this.pos.y <= ground) {
|
||||
this.pos.y = ground;
|
||||
this.velocityY = 0;
|
||||
this.grounded = true;
|
||||
} else {
|
||||
this.grounded = false;
|
||||
}
|
||||
} else {
|
||||
// sanftes Nachziehen beim Aufsteigen
|
||||
this.pos.y += (ground - this.pos.y) * Math.min(1, dt * 12);
|
||||
|
||||
+33
-3
@@ -13,7 +13,7 @@ import './style.css';
|
||||
import { loadSite, makeHeightSampler, type SiteData } from './site';
|
||||
import { buildTerrain } from './scene/terrain';
|
||||
import { buildSurfaces } from './scene/surfaces';
|
||||
import { buildStelen, buildProps, buildLamps, buildLinear } from './scene/structures';
|
||||
import { buildStelen, buildProps, buildLamps, buildLinear, treppeTritte } from './scene/structures';
|
||||
import { buildWalls } from './scene/walls';
|
||||
import { buildVegetation } from './scene/vegetation';
|
||||
import { buildBuilding, buildNeighbourBuildings } from './scene/building';
|
||||
@@ -80,6 +80,35 @@ async function main() {
|
||||
});
|
||||
scene.add(env.group);
|
||||
|
||||
// -- Begehbare Bauteile --------------------------------------------------
|
||||
// Stufen und Treppen stecken nicht im Hoehenraster, sondern sind eigene
|
||||
// Koerper. Damit man sie wirklich begehen kann, wird ihre Trittflaeche in
|
||||
// die Hoehenabfrage des Rundgangs eingerechnet - sonst blieben sie blosse
|
||||
// Hindernisse, um die man herumlaufen muss.
|
||||
interface Tritt { cx: number; cz: number; hw: number; hd: number; rot: number; top: number }
|
||||
const tritte: Tritt[] = [];
|
||||
for (const p of site.props) {
|
||||
if (p.type !== 'stufe' || p.top === undefined) continue;
|
||||
tritte.push({
|
||||
cx: p.x, cz: p.z, hw: p.w / 2, hd: p.d / 2,
|
||||
rot: (p.rot * Math.PI) / 180, top: p.top,
|
||||
});
|
||||
}
|
||||
for (const f of site.linear) {
|
||||
if (f.kind === 'treppe') tritte.push(...treppeTritte(f));
|
||||
}
|
||||
const onTritt = (t: Tritt, x: number, z: number) => {
|
||||
const dx = x - t.cx, dz = z - t.cz;
|
||||
const c = Math.cos(-t.rot), s = Math.sin(-t.rot);
|
||||
return Math.abs(dx * c - dz * s) <= t.hw && Math.abs(dx * s + dz * c) <= t.hd;
|
||||
};
|
||||
/** Hoehe unter den Fuessen: Gelaende oder die hoechste Trittflaeche darauf. */
|
||||
const walkHeight = (x: number, z: number) => {
|
||||
let y = height(x, z);
|
||||
for (const t of tritte) if (onTritt(t, x, z) && t.top > y) y = t.top;
|
||||
return y;
|
||||
};
|
||||
|
||||
// -- Hindernisse fuer den Rundgang --------------------------------------
|
||||
const obstacles: Obstacle[] = [];
|
||||
const b = site.building;
|
||||
@@ -113,7 +142,8 @@ async function main() {
|
||||
}
|
||||
}
|
||||
for (const p of site.props) {
|
||||
if (p.type === 'schacht' || p.type === 'wasserhahn') continue;
|
||||
// Stufen sind begehbar, kein Hindernis - sie stehen in `tritte`.
|
||||
if (p.type === 'schacht' || p.type === 'wasserhahn' || p.type === 'stufe') continue;
|
||||
obstacles.push({ cx: p.x, cz: p.z, hw: p.w / 2, hd: p.d / 2, rot: (p.rot * Math.PI) / 180 });
|
||||
}
|
||||
for (const pl of site.plants) {
|
||||
@@ -137,7 +167,7 @@ async function main() {
|
||||
obstacles.push({ cx: p.x, cz: p.z, hw: 0.26, hd: 0.26, rot: 0 });
|
||||
}
|
||||
|
||||
const walker = new Walker(camera, { height, obstacles, bounds: BOUNDS });
|
||||
const walker = new Walker(camera, { height: walkHeight, obstacles, bounds: BOUNDS });
|
||||
walker.attach(canvas);
|
||||
const start = site.startViews[0];
|
||||
walker.teleport(start.x, start.z, start.yaw);
|
||||
|
||||
+36
-4
@@ -342,7 +342,7 @@ export function buildProps(props: Prop[], height: (x: number, z: number) => numb
|
||||
const group = new Group();
|
||||
group.name = 'Objekte';
|
||||
for (const p of props) {
|
||||
const y = height(p.x, p.z);
|
||||
const y = p.base ?? height(p.x, p.z);
|
||||
let obj: Group | Mesh;
|
||||
switch (p.type) {
|
||||
case 'gartenhaus': obj = gartenhaus(p, y); break;
|
||||
@@ -469,7 +469,7 @@ function blockTreppe(f: Linear): Group {
|
||||
* Gelaender aus Stahlrohr: Pfosten alle rund 1.1 m, Handlauf und Knieholm
|
||||
* folgen dem Gefaelle des Laufs.
|
||||
*/
|
||||
function gelaender(f: Linear): Group {
|
||||
function gelaender(f: Linear, height: (x: number, z: number) => number): Group {
|
||||
const g = new Group();
|
||||
const mat = new MeshStandardMaterial({ color: 0x4c4f52, roughness: 0.42, metalness: 0.65 });
|
||||
const [[x0, z0], [x1, z1]] = [f.path[0], f.path[f.path.length - 1]];
|
||||
@@ -483,7 +483,11 @@ function gelaender(f: Linear): Group {
|
||||
for (let i = 0; i < n; i++) {
|
||||
const t = i / (n - 1);
|
||||
const y = fromY + (toY - fromY) * t;
|
||||
parts.push(boxGeometry(x0 + dx * t, z0 + dz * t, 0.05, 0.05, y - 0.35, y + h, rot, 1));
|
||||
const px = x0 + dx * t, pz = z0 + dz * t;
|
||||
// Der Pfosten muss bis ins Gelaende reichen, sonst schwebt er neben der
|
||||
// Treppe in der Luft.
|
||||
const fuss = Math.min(y, height(px, pz)) - 0.3;
|
||||
parts.push(boxGeometry(px, pz, 0.06, 0.06, fuss, y + h, rot, 1));
|
||||
}
|
||||
// Holme: als flache Bloecke ueber die ganze Laenge, um die Neigung gelegt.
|
||||
for (const [level, thick] of [[h, 0.055], [h * 0.52, 0.04]] as const) {
|
||||
@@ -535,6 +539,34 @@ function rinne(f: Linear, height: (x: number, z: number) => number): Group {
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trittflaechen einer Blockstufentreppe. Der Rundgang braucht sie, damit man
|
||||
* die Treppen wirklich hinauf- und hinuntergehen kann - sonst waere die Treppe
|
||||
* fuer den Spieler nur ein Hindernis im Gelaende.
|
||||
*/
|
||||
export function treppeTritte(f: Linear): { cx: number; cz: number; hw: number; hd: number; rot: number; top: number }[] {
|
||||
const [[x0, z0], [x1, z1]] = [f.path[0], f.path[f.path.length - 1]];
|
||||
const dx = x1 - x0, dz = z1 - z0;
|
||||
const len = Math.hypot(dx, dz) || 1;
|
||||
const steps = f.steps ?? 4;
|
||||
const width = f.width ?? 1.2;
|
||||
const fromY = f.fromY ?? 0, toY = f.toY ?? -1;
|
||||
const riser = (fromY - toY) / steps;
|
||||
const tread = len / steps;
|
||||
const out = [];
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const s = (i + 0.5) / steps;
|
||||
out.push({
|
||||
cx: x0 + dx * s, cz: z0 + dz * s,
|
||||
// Wie in obstacleFromSegment: hw laeuft laengs der Achse, hd quer dazu.
|
||||
hw: tread / 2, hd: width / 2,
|
||||
rot: Math.atan2(dz, dx),
|
||||
top: fromY - (i + 1) * riser,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Baut Treppen, Gelaender und Rinnen aus dem Plan. */
|
||||
export function buildLinear(features: Linear[], height: (x: number, z: number) => number): Group {
|
||||
const group = new Group();
|
||||
@@ -543,7 +575,7 @@ export function buildLinear(features: Linear[], height: (x: number, z: number) =
|
||||
let obj: Group;
|
||||
switch (f.kind) {
|
||||
case 'treppe': obj = blockTreppe(f); break;
|
||||
case 'gelaender': obj = gelaender(f); break;
|
||||
case 'gelaender': obj = gelaender(f, height); break;
|
||||
case 'rinne': obj = rinne(f, height); break;
|
||||
default: continue;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ export interface Prop {
|
||||
rot: number;
|
||||
/** Absolute Oberkante, sofern das Bauteil nicht auf dem Gelaende aufsitzt. */
|
||||
top?: number;
|
||||
/** Absolute Unterkante, sofern das Bauteil nicht auf dem Gelaende steht. */
|
||||
base?: number;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -151,7 +151,10 @@ export function setupHud(opts: HudOptions): Hud {
|
||||
|
||||
<h3>Bedienung</h3>
|
||||
<ul>
|
||||
<li>Rundgang: Klicken, dann <kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd>, <kbd>Shift</kbd> zum Rennen</li>
|
||||
<li>Rundgang: Klicken, dann <kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd>, <kbd>Shift</kbd> zum Rennen,
|
||||
<kbd>Leertaste</kbd> zum Springen</li>
|
||||
<li>Treppen und die schwebenden Stufen sind begehbar – hinauf geht es zu Fuss
|
||||
oder mit einem Sprung</li>
|
||||
<li>Am Handy: links der Joystick, rechts wischen zum Umsehen</li>
|
||||
<li>Übersicht: ziehen zum Drehen, Mausrad zum Zoomen</li>
|
||||
<li>Die Sonne steht für Abtwil SG astronomisch korrekt</li>
|
||||
|
||||
+72
-29
@@ -84,6 +84,42 @@ export const TREES = [
|
||||
* bleibt die Ausgleichsebene aus den Koten stehen.
|
||||
*/
|
||||
export const LEVELS = [
|
||||
{
|
||||
// Die Feuerwehrzufahrt ist eine Rampe: der Plan gibt -2.08 bei z -7.21
|
||||
// und -1.08 bei z 0.12 an, also rund 13.6 % Steigung. Ohne feste Vorgabe
|
||||
// legt sich der Belag als schwebende Platte ueber das Gelaende.
|
||||
id: 'feuerwehrzufahrt', plane: [0, 0.1364, -1.096], types: ['tegula'],
|
||||
note: 'Feuerwehrzufahrt als Rampe',
|
||||
polygon: [[-25.90, -13.80], [-21.90, -13.80], [-21.90, 6.60], [-25.90, 6.60]],
|
||||
},
|
||||
{
|
||||
// Der Ostweg steigt von der Treppenkrone (-0.37 bei z -4.18) bis zur
|
||||
// Suedostecke (0.63 bei z 4.31) gleichmaessig an - im Architekturplan
|
||||
// steht dort "Steigung 9.9%".
|
||||
id: 'weg-ost', plane: [0, 0.1178, 0.1224], types: ['tegula'],
|
||||
note: 'Ostweg als Rampe',
|
||||
polygon: [[2.80, -4.30], [4.55, -4.30], [4.55, 5.05], [2.80, 5.05]],
|
||||
},
|
||||
{
|
||||
id: 'weg-ost-unten', level: -1.57, types: ['tegula'],
|
||||
note: 'Podest am Fuss der Osttreppe',
|
||||
polygon: [[2.80, -7.70], [4.55, -7.70], [4.55, -6.45], [2.80, -6.45]],
|
||||
},
|
||||
{
|
||||
// Der Weg an der Suedgrenze folgt dem Anstieg der Grenzkoten von -0.04
|
||||
// im Westen auf 0.63 im Osten.
|
||||
id: 'weg-sued', plane: [0.0305, 0, 0.5634], types: ['tegula'],
|
||||
note: 'Weg an der Suedgrenze als Rampe',
|
||||
polygon: [[-21.90, 4.45], [4.55, 4.45], [4.55, 6.05], [-21.90, 6.05]],
|
||||
},
|
||||
{
|
||||
// Der Rasen der oberen Ebene ist eben. Die Koten 0.09 bis 0.26 liegen
|
||||
// schon im angehobenen Kiesbett des Sichtschutzes, nicht mehr im Rasen -
|
||||
// ohne feste Vorgabe zieht die Interpolation den Rasen huegelig hoch.
|
||||
id: 'oben', level: -0.02, flatten: true,
|
||||
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]],
|
||||
},
|
||||
{
|
||||
id: 'unten',
|
||||
level: -0.33,
|
||||
@@ -142,16 +178,8 @@ export const WALLS = [
|
||||
// sie stuetzen den Garten gegen den tiefer liegenden Fussweg ab.
|
||||
id: 'winkelelemente-ost', type: 'beton',
|
||||
note: 'Winkelelemente aus Beton an der Ostgrenze, gegen den Fussweg',
|
||||
path: [[3.735, -6.90], [3.735, 4.90]],
|
||||
t: 0.14, top: null, base: null, retains: 'ost',
|
||||
},
|
||||
{
|
||||
// Der Plan beschriftet die Linie bei z 4.96 mit "Winkelelemente": sie
|
||||
// fasst das hoeher liegende Nachbargrundstueck hinter dem Sichtschutz.
|
||||
id: 'winkelelemente-sued', type: 'beton',
|
||||
note: 'Winkelelemente aus Beton entlang der Suedgrenze',
|
||||
path: [[-21.20, 4.96], [1.99, 4.96]],
|
||||
t: 0.14, top: null, base: null, retains: 'sued',
|
||||
path: [[4.45, -6.90], [4.45, 4.90]],
|
||||
t: 0.16, top: null, base: null, retains: 'ost',
|
||||
},
|
||||
{
|
||||
id: 'betonmauer-nachbar', type: 'beton',
|
||||
@@ -209,14 +237,14 @@ export const PROPS = [
|
||||
note: 'Schlaufschacht D = 30 cm' },
|
||||
// Der Torfluegel liegt im Plan bei x -21.53 und laeuft ueber z 2.56 bis 3.56.
|
||||
{ id: 'gartentor', type: 'tor', x: -21.53, z: 3.06, w: 1.00, d: 0.06, h: 1.20, rot: 90,
|
||||
note: 'Gartentor 100 cm, Stabgitter aus Stahl' },
|
||||
base: -0.13, note: 'Gartentor 100 cm, Stabgitter aus Stahl, auf dem oberen Niveau' },
|
||||
// Zwei schwebende Travertinplatten fuehren vom gedeckten Sitzplatz (-0.02)
|
||||
// auf den Hauptsitzplatz (-0.33). Lage und Format stehen im Plan, die
|
||||
// Ausbildung als auskragende Platte zeigt das Referenzbild des Planers.
|
||||
{ id: 'stufe-oben', type: 'stufe', x: -14.335, z: -1.065, w: 0.81, d: 3.25, h: 0.08, rot: 0,
|
||||
top: -0.17, note: 'Schwebende Travertinstufe, Tritthoehe -0.17' },
|
||||
top: -0.02, note: 'Schwebende Travertinstufe, Tritthoehe -0.02 (Kote im Plan)' },
|
||||
{ id: 'stufe-unten', type: 'stufe', x: -14.880, z: -1.480, w: 0.80, d: 3.20, h: 0.08, rot: 0,
|
||||
top: -0.31, note: 'Schwebende Travertinstufe, Tritthoehe -0.31' },
|
||||
top: -0.17, note: 'Schwebende Travertinstufe, Tritthoehe -0.17 (Kote im Plan)' },
|
||||
// Beim Gartentor liegen ebenfalls zwei schwebende Travertinstufen: der Plan
|
||||
// zeichnet ein Feld 0.80 x 1.60 m mit den Koten -0.13 (Podest am Tor) und
|
||||
// -0.32 (Anschluss an den Hauptsitzplatz).
|
||||
@@ -245,15 +273,20 @@ export const EXTRA_SURFACES = [
|
||||
// Der Plan zeigt oestlich der Grenze einen gepflasterten Weg, erst dahinter
|
||||
// beginnt die Wiese. Zwei Leuchten stehen daran.
|
||||
type: 'tegula',
|
||||
note: 'Fussweg an der Ostseite zwischen Verbundstein-Anpassung und Winkelelementen',
|
||||
polygon: [[2.86, -7.00], [3.69, -7.00], [3.69, 4.90], [2.86, 4.90]],
|
||||
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]],
|
||||
},
|
||||
{
|
||||
type: 'tegula',
|
||||
note: 'Weg an der Ostseite, Podest unterhalb der Treppe auf -1.57',
|
||||
polygon: [[2.86, -7.60], [4.45, -7.60], [4.45, -6.52], [2.86, -6.52]],
|
||||
},
|
||||
{
|
||||
// Schmaler gepflasterter Streifen jenseits der Suedgrenze, zwischen
|
||||
// Grenze (z 4.49) und den Winkelelementen des Nachbarn (z 4.96).
|
||||
type: 'tegula',
|
||||
note: 'Tegula-Streifen des Suednachbarn entlang der Grenze',
|
||||
polygon: [[-21.74, 4.49], [1.99, 4.49], [1.99, 4.96], [-21.74, 4.96]],
|
||||
note: 'Weg entlang der Suedgrenze: von der Grenze bis an die Nachbarmauer gepflastert',
|
||||
polygon: [[-21.74, 4.49], [4.45, 4.49], [4.45, 5.97], [-21.74, 5.97]],
|
||||
},
|
||||
{
|
||||
type: 'tegula',
|
||||
@@ -295,12 +328,12 @@ export const LINEAR = [
|
||||
// x -23.34; Koten -0.28 oben und -1.08 auf der Feuerwehrzufahrt.
|
||||
id: 'treppe-west', kind: 'treppe',
|
||||
note: 'Treppe und Rampe von der Feuerwehrzufahrt zum Gartentor (-0.28 auf -1.08)',
|
||||
path: [[-21.84, 1.37], [-22.64, 1.37]], width: 1.70, fromY: -0.28, toY: -1.08, steps: 4,
|
||||
path: [[-21.80, 1.37], [-23.34, 1.37]], width: 1.70, fromY: -0.28, toY: -1.08, steps: 5,
|
||||
},
|
||||
{
|
||||
id: 'gelaender-west', kind: 'gelaender',
|
||||
note: 'Gelaender an der Treppe zur Feuerwehrzufahrt',
|
||||
path: [[-21.84, 0.52], [-22.64, 0.52]], fromY: -0.28, toY: -1.08, height: 0.95,
|
||||
path: [[-21.80, 0.52], [-23.34, 0.52]], fromY: -0.28, toY: -1.08, height: 0.95,
|
||||
},
|
||||
{
|
||||
id: 'rinne-gartentor', kind: 'rinne',
|
||||
@@ -397,7 +430,7 @@ export const NEIGHBOUR_BUILDINGS = [
|
||||
export const TERRAIN_ZONES = [
|
||||
{
|
||||
id: 'garten', label: 'Garten Kellenberger',
|
||||
polygon: [[-20.90, -6.60], [3.74, -6.60], [3.74, 4.96], [-20.90, 4.96]],
|
||||
polygon: [[-20.90, -6.60], [4.55, -6.60], [4.55, 5.97], [-20.90, 5.97]],
|
||||
fallback: -0.05,
|
||||
},
|
||||
{
|
||||
@@ -415,12 +448,12 @@ export const TERRAIN_ZONES = [
|
||||
// faengt der Nachbar sein hoeher liegendes Terrain, dahinter steht die
|
||||
// bestehende Betonmauer.
|
||||
id: 'sued', label: 'Nachbargrundstück Süd',
|
||||
polygon: [[-22.00, 4.96], [6.00, 4.96], [6.00, 12.00], [-22.00, 12.00]],
|
||||
polygon: [[-22.00, 5.97], [6.00, 5.97], [6.00, 12.00], [-22.00, 12.00]],
|
||||
fallback: 0.45,
|
||||
},
|
||||
{
|
||||
id: 'ost', label: 'Wiese Ost',
|
||||
polygon: [[3.74, -6.60], [9.00, -6.60], [9.00, 4.96], [3.74, 4.96]],
|
||||
polygon: [[4.55, -6.60], [9.00, -6.60], [9.00, 5.97], [4.55, 5.97]],
|
||||
fallback: 0.55,
|
||||
},
|
||||
];
|
||||
@@ -430,10 +463,14 @@ export const TERRAIN_ZONES = [
|
||||
* plausibles Gelaende aber noetig sind (Auslauf gegen die Grundstuecksraender).
|
||||
*/
|
||||
export const EXTRA_SPOTS = [
|
||||
{ x: -24.5, z: -8.0, y: -2.60, zone: 'zufahrt' },
|
||||
{ x: -24.5, z: 0.0, y: -1.60, zone: 'zufahrt' },
|
||||
{ x: -24.5, z: 5.0, y: -0.60, zone: 'zufahrt' },
|
||||
{ x: -22.6, z: 5.5, y: -0.30, zone: 'zufahrt' },
|
||||
// Auf der Zufahrt selbst gelten die Plankoten -2.08 (z -7.21) und -1.08
|
||||
// (z 0.12); quer dazu ist sie eben, sonst entsteht eine Gelaendekante.
|
||||
{ x: -25.0, z: -12.0, y: -2.75, zone: 'zufahrt' },
|
||||
{ x: -25.0, z: -7.2, y: -2.08, zone: 'zufahrt' },
|
||||
{ x: -25.0, z: 0.1, y: -1.08, zone: 'zufahrt' },
|
||||
{ x: -25.0, z: 6.0, y: -0.28, zone: 'zufahrt' },
|
||||
{ x: -22.4, z: -7.2, y: -2.08, zone: 'zufahrt' },
|
||||
{ x: -22.4, z: 6.0, y: -0.28, zone: 'zufahrt' },
|
||||
{ x: -20.0, z: -12.5, y: -1.75, zone: 'zollet' },
|
||||
{ x: -8.0, z: -12.5, y: -1.70, zone: 'zollet' },
|
||||
{ x: 2.0, z: -11.5, y: -1.65, zone: 'zollet' },
|
||||
@@ -446,9 +483,15 @@ export const EXTRA_SPOTS = [
|
||||
{ x: -18.0, z: 9.0, y: 0.25, zone: 'sued' },
|
||||
{ x: -6.0, z: 9.0, y: 0.60, zone: 'sued' },
|
||||
{ x: 4.0, z: 9.0, y: 1.00, zone: 'sued' },
|
||||
{ x: 7.5, z: -5.0, y: 0.10, zone: 'ost' },
|
||||
{ x: 7.5, z: 0.0, y: 0.45, zone: 'ost' },
|
||||
{ x: 7.5, z: 5.0, y: 0.80, zone: 'ost' },
|
||||
// Oestlich der Grenze faellt das Gelaende nach Norden zum Stellplatz des
|
||||
// Nachbarn ab - die Plankoten dort lauten -0.72 und -1.57. Ohne diese
|
||||
// Werte legt sich ein Huegel neben den Weg und deckt ihn zu.
|
||||
{ x: 7.5, z: -6.4, y: -1.25, zone: 'ost' },
|
||||
{ x: 7.5, z: -3.0, y: -0.35, zone: 'ost' },
|
||||
{ x: 7.5, z: 0.0, y: 0.20, zone: 'ost' },
|
||||
{ x: 7.5, z: 5.0, y: 0.75, zone: 'ost' },
|
||||
{ x: 5.0, z: -6.4, y: -1.35, zone: 'ost' },
|
||||
{ x: 3.0, z: -8.5, y: -1.55, zone: 'zollet' },
|
||||
];
|
||||
|
||||
/** Startposition und Blickrichtung beim Betreten der Szene. yaw = Grad ab Nord. */
|
||||
|
||||
+19
-6
@@ -293,8 +293,12 @@ for (const s of surfaces) {
|
||||
let plane, ys;
|
||||
|
||||
if (fixed) {
|
||||
plane = { a: 0, b: 0, c: fixed.level };
|
||||
ys = [fixed.level];
|
||||
// Eine Ebene darf auch geneigt vorgegeben sein - Wege sind Rampen.
|
||||
plane = fixed.plane
|
||||
? { a: fixed.plane[0], b: fixed.plane[1], c: fixed.plane[2] }
|
||||
: { a: 0, b: 0, c: fixed.level };
|
||||
const corners = s.polygon.map(([x, z]) => plane.a * x + plane.b * z + plane.c);
|
||||
ys = [Math.min(...corners), Math.max(...corners)];
|
||||
s.level = fixed.id;
|
||||
} else {
|
||||
const inside = terrainSpots.filter((sp) => owner.get(sp) === s);
|
||||
@@ -363,16 +367,25 @@ for (let j = 0; j < nz; j++) {
|
||||
const zone = zoneOf(x, z);
|
||||
let y;
|
||||
if (!zone) {
|
||||
// Ausserhalb aller Zonen: naechstgelegene Zone weiterziehen
|
||||
// Ausserhalb aller Zonen: naechstgelegene Zone weiterziehen. Dabei wird
|
||||
// deren Interpolation fortgesetzt, nicht ihr konstanter Ersatzwert -
|
||||
// sonst entsteht am Zonenrand eine Gelaendekante.
|
||||
let best = null, bd = Infinity;
|
||||
for (const t of TERRAIN_ZONES) {
|
||||
const [cx, cz] = centroid(t.polygon);
|
||||
const d = Math.hypot(cx - x, cz - z);
|
||||
const d = distToPolygon(x, z, t.polygon);
|
||||
if (d < bd) { bd = d; best = t; }
|
||||
}
|
||||
y = best.fallback;
|
||||
y = interpolate(x, z, best.id);
|
||||
} else {
|
||||
y = interpolate(x, z, zone);
|
||||
// Ausdruecklich flach vorgegebene Ebenen: der Rasen der oberen Ebene
|
||||
// ist eben, ohne Vorgabe zieht ihn die Interpolation huegelig hoch.
|
||||
for (const lv of LEVELS) {
|
||||
if (!lv.flatten || !pointInPolygon(x, z, lv.polygon)) continue;
|
||||
y = lv.level;
|
||||
locked[j * nx + i] = 1;
|
||||
break;
|
||||
}
|
||||
for (const f of flatSurfaces) {
|
||||
if (!pointInPolygon(x, z, f.polygon)) continue;
|
||||
y = Math.max(f.min, Math.min(f.max, f.plane.a * x + f.plane.b * z + f.plane.c));
|
||||
|
||||
Reference in New Issue
Block a user