Aussenplan bekommt Daecher, Oeffnungen und die Farbgebung der Renderings

Der Zeichner konnte bisher nur Klotz mit Satteldach. Jetzt:

  * Pultdaecher mit Fallrichtung. Die Richtung kommt aus der Aussennormalen
    der genannten Kante und nicht aus einem festen Nord-Sued-Vektor - das
    Grundstueck steht zehn Grad schief, und eine Traufe soll parallel zum
    Gebaeude laufen und nicht fast parallel.
  * Offene und halb offene Seiten. Genannt werden Himmelsrichtungen, die
    Kanten sagen selbst, welche gemeint ist. Bei einer halb offenen Seite
    bleibt die Haelfte stehen, die von der Oeffnung weiter weg liegt.
  * Ein Tor, mittig in seiner Wand.
  * Module, die plan auf der Dachflaeche liegen.
  * Rueckflaechen fallen weg. Bei positivem Umlauf der Grundflaeche haben die
    zugewandten Seiten eine positive projizierte Flaeche - damit verschwinden
    die Kanten, die man sonst durch einen Koerper hindurch gesehen haette,
    ohne sie einzeln benennen zu muessen.
  * Der First laeuft nicht mehr an festen Eckennummern entlang, sondern
    zwischen den Mitten zweier benannter Kanten. "achse" ueberschreibt das -
    bei der fast quadratischen Garage entschiede sonst die zweite
    Nachkommastelle, welche Seite die laengere ist.

Damit steht am Grundstueck: das Haus bekommt seine Wiederkehr auf der
Suedseite (als eigener Koerper auf derselben Grundflaeche - sie liegt
suedlich und damit weiter vorn, deckt das Hauptdach also von selbst richtig
ab; Breite und Tiefe sind vom Luftbild geschaetzt). Die Garage hat ein
begruentes Dach, ihr First liegt quer und ihr Tor nach Sueden. Der OG-Carport
ist nach Sueden offen und faellt zur Strasse hin ab, der EG-Carport ist nach
Westen offen, seine Nord- und Suedwand stehen nur hinten, und er faellt nach
Sueden ab. Auf beiden liegen die Module.

Die Farben folgen den Innenansichten: Weiss und Grau, dazu ein blaeulicher
Grauton fuer die Boeden von Veranda und Terrasse, OG etwas heller als EG.
Rot war ohnehin falsch, seit das Garagendach begruent ist.

Drei Fehler kamen beim Zeichnen heraus:

  * Die Dachflaechen nahmen die Firstenden in fester Reihenfolge. Eine der
    beiden wurde damit zur Schleife und zeichnete einen Zacken quer ueber das
    Haus. Jetzt bekommt jedes Traufende den Firstpunkt, der bei ihm liegt.
  * Die Wiederkehr zog ihre Ost- und Westwand mitten durch das Haus. Sie hat
    jetzt gar keine Waende: sie sitzt als reines Dach auf sechs Metern.
  * Wo eine Seite offen ist, schaute man durch den Koerper auf den schwarzen
    Hintergrund - ein Loch, kein Unterstand. Solche Koerper bekommen jetzt
    einen Boden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 20:40:05 +02:00
co-authored by Claude Opus 5
parent ff74ec3c68
commit d30f4ac0ab
+365 -87
View File
@@ -102,39 +102,73 @@ const PLAN_RAND = 12;
function aussenFlaechen()
{
return [
["raum" => null, "z" => 0, "hoehe" => 6, "dach" => 3,
"farbe" => "#c8cdd6",
["raum" => "Garten", "z" => 0, "hoehe" => 0, "farbe" => "#4a6b4e",
"ecken" => [[8.6, 40.6], [7.9, 45.0], [49.0, 38.4], [50.7, 12.9], [23.6, 16.7],
[24.3, 19.5], [36.8, 17.2], [39.2, 31.2], [24.2, 33.8], [22.7, 24.2],
[19.9, 25.6], [20.2, 28.7], [19.0, 29.8], [19.3, 39.1]]],
["raum" => null, "z" => 0, "hoehe" => 0, "farbe" => "#6e7278", // Hof
"ecken" => [[9.3, 26.1], [8.9, 36.4], [11.0, 36.0], [18.9, 34.7], [19.0, 29.8],
[20.4, 28.7], [19.8, 25.6], [19.6, 24.3]]],
// Das Haus. First ueber die laengere Achse, also Ost-West - so liegt
// er auch auf dem Luftbild.
["raum" => null, "z" => 0, "hoehe" => 6, "farbe" => "#d5d9df",
"dachfarbe" => "#9aa3ad", "dach" => ["art" => "sattel", "hoehe" => 3],
"ecken" => [[36.8, 17.2], [39.2, 31.2], [24.2, 33.8], [21.8, 19.8]]],
["raum" => null, "z" => 0, "hoehe" => 0, "dach" => 0,
"farbe" => "#57585a",
"ecken" => [[9.3, 26.1], [8.9, 36.4], [11.0, 36.0], [18.9, 34.7], [19.0, 29.8], [20.4, 28.7], [19.8, 25.6], [19.6, 24.3]]],
["raum" => "Carport EG", "z" => 0, "hoehe" => 2.2, "dach" => 0,
"farbe" => "#3f4a63",
"ecken" => [[18.9, 34.7], [19.6, 38.9], [10.7, 40.5], [10.0, 36.2]]],
["raum" => "Carport OG", "z" => 0, "hoehe" => 2.5, "dach" => 0,
"farbe" => "#6f7680",
"ecken" => [[12.2, 19.1], [13.2, 25.4], [9.3, 26.1], [8.2, 19.8]]],
["raum" => "Veranda EG", "z" => 0, "hoehe" => 0, "dach" => 0,
"farbe" => "#9a7b52",
"ecken" => [[24.1, 19.4], [26.1, 30.8], [23.7, 31.2], [21.8, 19.8]]],
["raum" => "Veranda OG", "z" => 3, "hoehe" => 0, "dach" => 0,
"farbe" => "#c9a978",
"ecken" => [[24.1, 19.4], [26.1, 30.8], [23.7, 31.2], [21.8, 19.8]]],
["raum" => "Terrasse EG", "z" => 0, "hoehe" => 0, "dach" => 0,
"farbe" => "#9a7b52",
"ecken" => [[23.7, 31.2], [28.6, 30.3], [29.1, 32.9], [24.2, 33.8]]],
["raum" => "Terrasse OG", "z" => 3, "hoehe" => 0, "dach" => 0,
"farbe" => "#c9a978",
"ecken" => [[23.7, 31.2], [28.6, 30.3], [29.1, 32.9], [24.2, 33.8]]],
["raum" => "Pergola", "z" => 0, "hoehe" => 0, "dach" => 0,
"farbe" => "#7a6a8f",
// Wiederkehr auf der Suedseite: ein Kreuzgiebel quer zum Hauptfirst.
// Als eigener Koerper auf derselben Grundflaeche gezeichnet - er liegt
// suedlich und damit weiter vorn, deckt das Hauptdach also von selbst
// richtig ab. Die Firsthoehe folgt der Dachneigung des Hauses: 3 m auf
// 7,1 m halbe Spannweite sind knapp 23 Grad, bei 3 m halber
// Spannweite also rund 1,3 m.
//
// Breite und Tiefe sind vom Luftbild geschaetzt - wenn es genauer sein
// soll, klick die Flaeche in tools/lageplan.php ab.
["raum" => null, "z" => 6, "hoehe" => 0, "farbe" => "#d5d9df",
"dachfarbe" => "#9aa3ad",
"dach" => ["art" => "sattel", "hoehe" => 1.3, "achse" => "NS"],
"ecken" => [[34.7, 32.0], [28.7, 33.0], [27.5, 25.6], [33.4, 24.6]]],
// Beide Carports sind Holzkonstruktionen mit Pultdach, die Module
// liegen plan darauf. Der OG-Carport faellt zur Strasse ab, also nach
// Westen, und ist nach Sueden offen.
["raum" => "Carport OG", "z" => 0, "hoehe" => 2.5, "farbe" => "#b8b3aa",
"dachfarbe" => "#a8a29a", "pv" => true,
"dach" => ["art" => "pult", "hoehe" => 0.8, "tief" => "W"],
"offen" => ["S"],
"ecken" => [[12.2, 19.2], [13.3, 25.4], [9.3, 26.1], [8.2, 19.9]]],
// Der EG-Carport faellt nach Sueden ab, ist nach Westen offen, und
// seine Nord- und Suedwand stehen nur in der hinteren Haelfte.
["raum" => "Carport EG", "z" => 0, "hoehe" => 2.2, "farbe" => "#b8b3aa",
"dachfarbe" => "#a8a29a", "pv" => true,
"dach" => ["art" => "pult", "hoehe" => 0.7, "tief" => "S"],
"offen" => ["W"], "halb" => ["N", "S"],
"ecken" => [[19.0, 34.6], [19.3, 39.1], [10.7, 40.3], [10.4, 35.8]]],
// Die Garage hat ein begruentes Dach und ihr Tor nach Sueden. Der
// First laeuft quer zu dem, was die laengere Achse nahelegen wuerde -
// bei einem fast quadratischen Grundriss entscheidet das sonst der
// Zufall der zweiten Nachkommastelle.
["raum" => "Garage", "z" => 0, "hoehe" => 2.5, "farbe" => "#c9ced5",
"dachfarbe" => "#7d9470", "tor" => "S",
"dach" => ["art" => "sattel", "hoehe" => 1.5, "achse" => "OW"],
"ecken" => [[12.2, 19.2], [13.3, 25.4], [19.6, 24.3], [18.5, 18.0]]],
["raum" => "Veranda EG", "z" => 0, "hoehe" => 0, "farbe" => "#aeb8c4",
"ecken" => [[24.3, 19.5], [26.2, 30.8], [23.8, 31.1], [22.0, 19.9]]],
["raum" => "Veranda OG", "z" => 3, "hoehe" => 0, "farbe" => "#c3cbd5",
"ecken" => [[24.3, 19.5], [26.2, 30.8], [23.8, 31.1], [22.0, 19.9]]],
["raum" => "Terrasse EG", "z" => 0, "hoehe" => 0, "farbe" => "#aeb8c4",
"ecken" => [[23.8, 31.1], [28.3, 30.4], [29.0, 33.0], [24.2, 33.8]]],
["raum" => "Terrasse OG", "z" => 3, "hoehe" => 0, "farbe" => "#c3cbd5",
"ecken" => [[23.8, 31.1], [28.3, 30.4], [29.0, 33.0], [24.2, 33.8]]],
["raum" => "Pergola", "z" => 0, "hoehe" => 0, "farbe" => "#9aa3ad",
"ecken" => [[48.0, 36.5], [43.8, 36.4], [46.0, 31.5], [49.4, 32.4]]],
["raum" => "Garage", "z" => 0, "hoehe" => 2.5, "dach" => 1.5,
"farbe" => "#8a9099",
"ecken" => [[12.2, 19.1], [13.2, 25.4], [19.6, 24.3], [18.5, 18.0]]],
["raum" => "Garten", "z" => 0, "hoehe" => 0, "dach" => 0,
"farbe" => "#2e5c33",
"ecken" => [[8.6, 40.8], [7.9, 45.2], [49.0, 38.2], [50.6, 12.5], [23.7, 17.1], [24.1, 19.4], [36.8, 17.2], [39.2, 31.2], [24.2, 33.8], [22.5, 23.8], [19.8, 25.6], [20.4, 28.7], [19.0, 29.8], [19.6, 38.9]]],
];
}
@@ -171,7 +205,8 @@ function aussenEinpassung($flaechen)
$xs = [];
$ys = [];
foreach ($flaechen as $f) {
$oben = ($f["z"] ?? 0) + ($f["hoehe"] ?? 0) + ($f["dach"] ?? 0);
// "dach" ist eine Beschreibung, keine Zahl - die Hoehe steckt darin.
$oben = ($f["z"] ?? 0) + ($f["hoehe"] ?? 0) + ($f["dach"]["hoehe"] ?? 0);
foreach ($f["ecken"] as $e) {
foreach ([$f["z"] ?? 0, $oben] as $z) {
list($X, $Y) = $P($e[0], $e[1], $z);
@@ -232,17 +267,118 @@ function aussenSchatten($hex, $faktor)
(int) round(($n & 255) * $faktor));
}
/**
* Himmelsrichtung, in die eine Aussennormale zeigt.
*
* Das Grundstueck steht rund zehn Grad schief im Raster; die groessere der
* beiden Komponenten ist deshalb immer eindeutig.
*/
function aussenRichtung($nx, $ny)
{
if (abs($nx) > abs($ny)) {
return $nx > 0 ? "O" : "W";
}
return $ny > 0 ? "S" : "N";
}
/**
* Ein Vieleck mit positivem Umlaufsinn.
*
* Daran haengt zweierlei: die Aussennormale einer Kante ist nur bei bekanntem
* Umlauf berechenbar, und das Wegwerfen der Rueckseiten prueft das Vorzeichen
* der projizierten Flaeche. Beides ginge schief, wenn eine Flaeche andersherum
* abgeklickt wurde als die naechste.
*/
function aussenUmlauf($ecken)
{
$summe = 0.0;
$n = count($ecken);
for ($i = 0; $i < $n; $i++) {
$j = ($i + 1) % $n;
$summe += $ecken[$i][0] * $ecken[$j][1] - $ecken[$j][0] * $ecken[$i][1];
}
return $summe < 0 ? array_reverse($ecken) : $ecken;
}
/** Je Kante ihre Himmelsrichtung, Aussennormale und Laenge. */
function aussenKanten($ecken)
{
$kanten = [];
$n = count($ecken);
for ($i = 0; $i < $n; $i++) {
$a = $ecken[$i];
$b = $ecken[($i + 1) % $n];
$dx = $b[0] - $a[0];
$dy = $b[1] - $a[1];
$laenge = hypot($dx, $dy);
// Aussennormale einer positiv umlaufenden Flaeche in Bildkoordinaten.
$nx = $laenge > 0 ? $dy / $laenge : 0;
$ny = $laenge > 0 ? -$dx / $laenge : 0;
$kanten[] = ["i" => $i, "a" => $a, "b" => $b, "laenge" => $laenge,
"nx" => $nx, "ny" => $ny,
"richtung" => aussenRichtung($nx, $ny),
"mitte" => [($a[0] + $b[0]) / 2, ($a[1] + $b[1]) / 2]];
}
return $kanten;
}
/** Die Kante, deren Aussennormale in diese Richtung zeigt. */
function aussenKante($kanten, $richtung)
{
foreach ($kanten as $k) {
if ($k["richtung"] === $richtung) {
return $k;
}
}
return null;
}
/**
* Zeigt eine projizierte Flaeche zum Betrachter?
*
* Bei positivem Umlauf der Grundflaeche haben die zugewandten Seiten eine
* positive projizierte Flaeche, die abgewandten eine negative. Damit fallen
* die Rueckseiten weg, ohne sie einzeln benennen zu muessen - und mit ihnen
* die Kanten, die man durch einen Koerper hindurch gesehen haette.
*/
function aussenSichtbar($punkte)
{
$summe = 0.0;
$n = count($punkte);
for ($i = 0; $i < $n; $i++) {
$j = ($i + 1) % $n;
$summe += $punkte[$i][0] * $punkte[$j][1] - $punkte[$j][0] * $punkte[$i][1];
}
return $summe > 0;
}
/** Ein Vieleck zu seinem Schwerpunkt hin einziehen, um `abstand` Meter. */
function aussenEinzug($ecken, $abstand)
{
$m = aussenMitte($ecken);
$neu = [];
foreach ($ecken as $p) {
$dx = $p[0] - $m[0];
$dy = $p[1] - $m[1];
$d = hypot($dx, $dy);
$f = $d > $abstand ? ($d - $abstand) / $d : 0.0;
$neu[] = [$m[0] + $dx * $f, $m[1] + $dy * $f];
}
return $neu;
}
/**
* Die Zeichnung als Liste von Polygonen, von hinten nach vorn sortiert.
*
* Sortiert wird nach dem hintersten Punkt der Grundfläche (Malerreihenfolge).
* Das reicht hier: die Körper stehen nebeneinander auf dem Boden und
* durchdringen sich nicht. Bei ineinander verschachtelten Körpern bräuchte es
* mehr, aber ein Grundstück ist keine Baugruppe.
* Sortiert wird nach dem hintersten Punkt der Grundflaeche (Malerreihenfolge).
* Das reicht hier: die Koerper stehen nebeneinander auf dem Boden. Die
* Wiederkehr durchdringt zwar das Hausdach, liegt aber suedlich davon und
* damit ohnehin weiter vorn - genau so faelscht man einen Kreuzgiebel, ohne
* Flaechen verschneiden zu muessen.
*
* Returns:
* ["polygone" => [["punkte" => [[x,y],...], "farbe" => "#rrggbb"], ...],
* "anker" => [Raumname => [x, y], ...]] Mitte der Deckfläche
* "anker" => [Raumname => [x, y], ...]] Mitte der Deckflaeche
*/
function aussenZeichnung()
{
@@ -254,71 +390,213 @@ function aussenZeichnung()
$stuecke = [];
foreach ($flaechen as $f) {
$z = $f["z"] ?? 0;
$hoehe = $f["hoehe"] ?? 0;
$dach = $f["dach"] ?? 0;
$farbe = $f["farbe"] ?? "#8a9099";
$ecken = aussenUmlauf($f["ecken"]);
$kanten = aussenKanten($ecken);
$z = $f["z"] ?? 0;
$wand = $f["hoehe"] ?? 0;
$dach = $f["dach"] ?? null;
$farbe = $f["farbe"] ?? "#8a9099";
$offen = $f["offen"] ?? [];
$halb = $f["halb"] ?? [];
$tiefe = INF;
foreach ($f["ecken"] as $e) {
$p = aussenPunkt($einpassung, $e[0], $e[1], 0);
$tiefe = min($tiefe, $p[1]);
// --- Hoehe der Oberkante je Ecke ---------------------------------
//
// Beim Pultdach faellt sie zu einer Seite ab. Die Fallrichtung kommt
// aus der Aussennormalen der genannten Kante und nicht aus einem
// festen Nord-Sued-Vektor: das Grundstueck steht schief, und ein
// First soll parallel zur Traufe laufen und nicht fast parallel.
$zOben = array_fill(0, count($ecken), $z + $wand);
if ($dach && $dach["art"] === "pult") {
$tief = aussenKante($kanten, $dach["tief"]);
if ($tief) {
$werte = [];
foreach ($ecken as $p) {
$werte[] = $p[0] * $tief["nx"] + $p[1] * $tief["ny"];
}
$lo = min($werte);
$hi = max($werte);
foreach ($ecken as $i => $p) {
$anteil = $hi > $lo ? ($hi - $werte[$i]) / ($hi - $lo) : 0;
$zOben[$i] = $z + $wand + $dach["hoehe"] * $anteil;
}
}
}
$unten = [];
$oben = [];
foreach ($f["ecken"] as $e) {
$unten[] = aussenPunkt($einpassung, $e[0], $e[1], $z);
$oben[] = aussenPunkt($einpassung, $e[0], $e[1], $z + $hoehe);
foreach ($ecken as $i => $p) {
$unten[] = aussenPunkt($einpassung, $p[0], $p[1], $z);
$oben[] = aussenPunkt($einpassung, $p[0], $p[1], $zOben[$i]);
}
$polygone = [];
if ($hoehe > 0) {
$anzahl = count($f["ecken"]);
for ($i = 0; $i < $anzahl; $i++) {
$j = ($i + 1) % $anzahl;
$polygone[] = ["punkte" => [$unten[$i], $unten[$j], $oben[$j], $oben[$i]],
"farbe" => aussenSchatten($farbe, 0.62)];
$tiefe = INF;
foreach ($ecken as $p) {
$tiefe = min($tiefe, aussenPunkt($einpassung, $p[0], $p[1], 0)[1]);
}
// --- Boden -------------------------------------------------------
//
// Nur wo eine Seite offen ist. Bei einem geschlossenen Koerper sieht
// man ihn nie, bei einem offenen dagegen sofort - ohne ihn schaut man
// durch den Carport auf den schwarzen Hintergrund, und das liest sich
// als Loch und nicht als Unterstand.
if ($wand > 0 && ($offen || $halb)) {
$boden = [];
foreach ($ecken as $pkt) {
$boden[] = aussenPunkt($einpassung, $pkt[0], $pkt[1], $z);
}
$polygone[] = ["punkte" => $boden, "farbe" => aussenSchatten($farbe, 0.55)];
}
// --- Waende ------------------------------------------------------
if ($wand > 0) {
foreach ($kanten as $k) {
if (in_array($k["richtung"], $offen, true)) {
continue;
}
$i = $k["i"];
$j = ($i + 1) % count($ecken);
$von = $ecken[$i];
$bis = $ecken[$j];
$zVon = $zOben[$i];
$zBis = $zOben[$j];
if (in_array($k["richtung"], $halb, true)) {
// Halb offene Seite: stehen bleibt die Haelfte, die von
// der offenen Seite weiter weg liegt - vorn ist offen.
$offeneKante = $offen ? aussenKante($kanten, $offen[0]) : null;
$mitteKante = [($von[0] + $bis[0]) / 2, ($von[1] + $bis[1]) / 2];
$zMitte = ($zVon + $zBis) / 2;
if ($offeneKante) {
$wertVon = $von[0] * $offeneKante["nx"] + $von[1] * $offeneKante["ny"];
$wertBis = $bis[0] * $offeneKante["nx"] + $bis[1] * $offeneKante["ny"];
if ($wertVon > $wertBis) {
$bis = $mitteKante;
$zBis = $zMitte;
} else {
$von = $mitteKante;
$zVon = $zMitte;
}
}
}
$wandflaeche = [
aussenPunkt($einpassung, $von[0], $von[1], $z),
aussenPunkt($einpassung, $bis[0], $bis[1], $z),
aussenPunkt($einpassung, $bis[0], $bis[1], $zBis),
aussenPunkt($einpassung, $von[0], $von[1], $zVon),
];
if (!aussenSichtbar($wandflaeche)) {
continue;
}
$polygone[] = ["punkte" => $wandflaeche, "farbe" => aussenSchatten($farbe, 0.72)];
// Das Tor sitzt mittig in seiner Wand und reicht nicht ganz
// bis unter die Traufe.
if (($f["tor"] ?? null) === $k["richtung"]) {
$torHoch = min(2.2, $z + min($zVon, $zBis) - $z);
$a = 0.2;
$b = 0.8;
$pa = [$von[0] + ($bis[0] - $von[0]) * $a, $von[1] + ($bis[1] - $von[1]) * $a];
$pb = [$von[0] + ($bis[0] - $von[0]) * $b, $von[1] + ($bis[1] - $von[1]) * $b];
$polygone[] = ["punkte" => [
aussenPunkt($einpassung, $pa[0], $pa[1], $z),
aussenPunkt($einpassung, $pb[0], $pb[1], $z),
aussenPunkt($einpassung, $pb[0], $pb[1], $z + $torHoch),
aussenPunkt($einpassung, $pa[0], $pa[1], $z + $torHoch),
], "farbe" => aussenSchatten($farbe, 0.52)];
}
}
}
$polygone[] = ["punkte" => $oben, "farbe" => $farbe];
if ($dach > 0 && count($f["ecken"]) === 4) {
$e = $f["ecken"];
// Der First laeuft ueber die laengere Achse - er verbindet die
// Mitten der beiden kuerzeren Seiten. Fest verdrahtet waere das
// bei einem quer stehenden Gebaeude falsch herum.
$laenge = function ($a, $b) {
return hypot($a[0] - $b[0], $a[1] - $b[1]);
};
$langsSeite01 = $laenge($e[0], $e[1]) >= $laenge($e[1], $e[2]);
$paar = $langsSeite01 ? [[0, 3], [1, 2]] : [[0, 1], [2, 3]];
// --- Dach --------------------------------------------------------
if ($dach && $dach["art"] === "sattel" && count($ecken) === 4) {
// Der First laeuft entlang der laengeren Gebaeudeachse, wenn nichts
// anderes dasteht. Fest an Eckennummern haengt das nicht mehr: die
// Kanten sagen selbst, wo Norden ist.
$achse = $dach["achse"] ?? null;
if (!$achse) {
$nord = aussenKante($kanten, "N");
$ost = aussenKante($kanten, "O");
$achse = ($nord && $ost && $nord["laenge"] >= $ost["laenge"]) ? "OW" : "NS";
}
$enden = $achse === "OW" ? ["O", "W"] : ["N", "S"];
$seiten = $achse === "OW" ? ["N", "S"] : ["O", "W"];
$firstHoch = $z + $wand + $dach["hoehe"];
// Die Firstenden erst in Weltkoordinaten: gebraucht werden sie
// gleich zum Vergleichen von Abstaenden, und das geht nur vor der
// Projektion sinnvoll.
$firstWelt = [];
$first = [];
foreach ($paar as $pp) {
$first[] = aussenPunkt($einpassung,
($e[$pp[0]][0] + $e[$pp[1]][0]) / 2,
($e[$pp[0]][1] + $e[$pp[1]][1]) / 2,
$z + $hoehe + $dach);
foreach ($enden as $r) {
$k = aussenKante($kanten, $r);
$firstWelt[$r] = $k ? $k["mitte"] : null;
$first[$r] = $k
? aussenPunkt($einpassung, $k["mitte"][0], $k["mitte"][1], $firstHoch)
: null;
}
$dachfarbe = $f["dachfarbe"] ?? "#b5563a";
// Erst die beiden Giebeldreiecke, dann die Dachflaechen darueber:
// ohne sie klaffte an den Schmalseiten ein Loch, durch das die
// Wand darunter zu sehen war.
foreach ($paar as $i => $pp) {
$polygone[] = ["punkte" => [$oben[$pp[0]], $oben[$pp[1]], $first[$i]],
"farbe" => aussenSchatten($farbe, 0.88)];
// Giebeldreiecke schliessen die Schmalseiten. Ohne sie schiene an
// den Enden die Wand durch das Dach.
foreach ($enden as $r) {
$k = aussenKante($kanten, $r);
if (!$k || !$first[$r]) {
continue;
}
$giebel = [
aussenPunkt($einpassung, $k["a"][0], $k["a"][1], $z + $wand),
aussenPunkt($einpassung, $k["b"][0], $k["b"][1], $z + $wand),
$first[$r],
];
if (aussenSichtbar($giebel)) {
$polygone[] = ["punkte" => $giebel, "farbe" => aussenSchatten($farbe, 0.86)];
}
}
if ($langsSeite01) {
$polygone[] = ["punkte" => [$oben[0], $oben[1], $first[1], $first[0]],
"farbe" => $dachfarbe];
$polygone[] = ["punkte" => [$oben[3], $oben[2], $first[1], $first[0]],
"farbe" => aussenSchatten($dachfarbe, 0.78)];
} else {
$polygone[] = ["punkte" => [$oben[1], $oben[2], $first[1], $first[0]],
"farbe" => $dachfarbe];
$polygone[] = ["punkte" => [$oben[0], $oben[3], $first[1], $first[0]],
"farbe" => aussenSchatten($dachfarbe, 0.78)];
foreach ($seiten as $r) {
$k = aussenKante($kanten, $r);
if (!$k || !$firstWelt[$enden[0]] || !$firstWelt[$enden[1]]) {
continue;
}
// Jedes Traufende bekommt den Firstpunkt, der bei ihm liegt.
// In fester Reihenfolge genommen wird eine der beiden
// Dachflaechen zur Schleife und zeichnet einen Zacken quer
// ueber das Haus - genau das war zu sehen.
$nahB = $enden[0];
$nahA = $enden[1];
if (hypot($k["b"][0] - $firstWelt[$enden[0]][0], $k["b"][1] - $firstWelt[$enden[0]][1])
> hypot($k["b"][0] - $firstWelt[$enden[1]][0], $k["b"][1] - $firstWelt[$enden[1]][1])) {
$nahB = $enden[1];
$nahA = $enden[0];
}
$schraege = [
aussenPunkt($einpassung, $k["a"][0], $k["a"][1], $z + $wand),
aussenPunkt($einpassung, $k["b"][0], $k["b"][1], $z + $wand),
$first[$nahB],
$first[$nahA],
];
if (aussenSichtbar($schraege)) {
$polygone[] = ["punkte" => $schraege,
"farbe" => aussenSchatten($f["dachfarbe"] ?? "#9aa3ad",
$r === "S" || $r === "W" ? 1.0 : 0.82)];
}
}
$deck = null;
} else {
// Flach, Pult oder gar kein Dach: die Oberseite ist die Deckflaeche.
$polygone[] = ["punkte" => $oben, "farbe" => $f["dachfarbe"] ?? $farbe];
$deck = $oben;
}
// Die Module liegen plan auf der Dachflaeche - ein Stueck eingezogen,
// damit der Rand des Daches sichtbar bleibt.
if (!empty($f["pv"]) && $deck) {
$modulEcken = aussenEinzug($ecken, 0.4);
$modul = [];
foreach ($modulEcken as $i => $p) {
$modul[] = aussenPunkt($einpassung, $p[0], $p[1], $zOben[$i] + 0.05);
}
$polygone[] = ["punkte" => $modul, "farbe" => "#2f3a49"];
}
$stuecke[] = ["tiefe" => $tiefe, "polygone" => $polygone,