Dach/Wand: Giebel-Endfüllung (dünne Einzelfläche) nicht mehr emittiert — lag
deckungsgleich vor der ohnehin bis zur Dach-Unterkante hochgeklemmten Giebelwand und wirkte als sichtbare Doppel-Schicht. Zusätzlich werden aufeinanderfolgende Wandstücke gleicher geklemmter Oberkante (z. B. am First) wieder zu einem Body verschmolzen statt in viele 0.15-m-Fragmente zu zerfallen.
This commit is contained in:
@@ -1550,8 +1550,10 @@ describe("Dach-Wand-Verschneidung (clipPieceToRoofs) — Wand-Top folgt der Dach
|
||||
expect(seg.height).toBeLessThan(3.0 - 0.05);
|
||||
expect(seg.height).toBeGreaterThan(2.8); // nicht mehr als die Dachdicke gekappt
|
||||
}
|
||||
// Am First (y nahe 2): unverändert volle Höhe (Dachunterkante liegt weit darüber).
|
||||
const nearRidge = walls.filter((s) => s.start[1] > 1.8 && s.start[1] < 2.2);
|
||||
// Am First (y=2): unverändert volle Höhe. Die ungeклippten Achsenstücke gleicher
|
||||
// Oberkante werden zu EINEM Body verschmolzen, daher hier das Segment suchen, das
|
||||
// y=2 ÜBERSPANNT (nicht mehr ein an y≈2 beginnendes 0.15-m-Stück).
|
||||
const nearRidge = walls.filter((s) => Math.min(s.start[1], s.end[1]) <= 2 && Math.max(s.start[1], s.end[1]) >= 2);
|
||||
expect(nearRidge.length).toBeGreaterThan(0);
|
||||
for (const seg of nearRidge) {
|
||||
expect(seg.height).toBeCloseTo(3.0, 6);
|
||||
@@ -1886,13 +1888,14 @@ describe("emitRoofs (Dachflächen + Giebel als Meshes)", () => {
|
||||
thickness: 0.3,
|
||||
};
|
||||
|
||||
it("Satteldach: Flächen + Giebel als EIN Roof-Mesh, First-Höhe passt zur Traufhöhe + ridgeHeight", () => {
|
||||
it("Satteldach: nur die Dachflächen als Roof-Mesh (keine Giebel-Endfüllung), First-Höhe passt zur Traufhöhe + ridgeHeight", () => {
|
||||
const project: Project = { ...sampleProject, roofs: [baseRoof] };
|
||||
const { meshes } = projectToModel3d(project);
|
||||
const roofMeshes = meshes.filter(isRoofMesh);
|
||||
// Einschichtiges Dach (thickness-Fallback): 1 Schicht-Slab-Mesh (Flächen) +
|
||||
// 1 Giebel-Mesh (Endfüllung) = 2 Roof-Meshes (beide ROOF_RGB).
|
||||
expect(roofMeshes.length).toBe(2);
|
||||
// Einschichtiges Dach (thickness-Fallback): NUR das Schicht-Slab-Mesh der
|
||||
// geneigten Flächen. Die Giebel-Endfüllung wird bewusst nicht mehr emittiert
|
||||
// (der Giebel entsteht aus der Wand darunter) -> 1 Roof-Mesh.
|
||||
expect(roofMeshes.length).toBe(1);
|
||||
for (const m of roofMeshes) {
|
||||
expect(m.positions.every((v) => Number.isFinite(v))).toBe(true);
|
||||
expect(m.indices.length % 3).toBe(0);
|
||||
@@ -2024,9 +2027,9 @@ describe("emitRoofs (Dachflächen + Giebel als Meshes)", () => {
|
||||
const noField: Project = { ...sampleProject };
|
||||
delete (noField as { roofs?: unknown }).roofs;
|
||||
expect(projectToModel3d(noField).meshes.filter(isRoofMesh).length).toBe(0);
|
||||
// sampleProject enthält ein Demo-Satteldach (RF1, einschichtig) -> 2 Roof-
|
||||
// Meshes: Schicht-Slab (Flächen) + Giebel-Endfüllung.
|
||||
expect(projectToModel3d(sampleProject).meshes.filter(isRoofMesh).length).toBe(2);
|
||||
// sampleProject enthält ein Demo-Satteldach (RF1, einschichtig) -> 1 Roof-
|
||||
// Mesh: nur der Schicht-Slab der Flächen (keine Giebel-Endfüllung mehr).
|
||||
expect(projectToModel3d(sampleProject).meshes.filter(isRoofMesh).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+25
-17
@@ -1099,18 +1099,32 @@ function clipPieceToRoofs(
|
||||
const span = bb - a;
|
||||
const steps = Math.max(1, Math.ceil(span / ROOF_CLIP_STEP));
|
||||
const stepLen = span / steps;
|
||||
// Je Stufe die geklemmte Oberkante bestimmen …
|
||||
const tops: number[] = [];
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const sa = a + i * stepLen;
|
||||
const sb = a + (i + 1) * stepLen;
|
||||
const mid = (sa + sb) / 2;
|
||||
const mid = a + (i + 0.5) * stepLen;
|
||||
const p = add(add(wall.start, scale(u, mid)), scale(n, bandOffset));
|
||||
let clip = zt;
|
||||
for (const { geo, total } of roofData) {
|
||||
const under = roofUndersideAt(geo, total, p.x, p.y);
|
||||
if (under !== null && under < clip) clip = under;
|
||||
}
|
||||
const zte = Math.max(zb, clip);
|
||||
tops.push(Math.max(zb, clip));
|
||||
}
|
||||
// … und AUFEINANDERFOLGENDE Stufen gleicher Oberkante zu EINEM Body verschmelzen.
|
||||
// Wo das Dach das Stück gar nicht klippt (Oberkante == zt über die ganze Länge,
|
||||
// z. B. nahe dem First oder wenn kein Dach wirklich überdeckt), entsteht so wieder
|
||||
// EIN durchgehender Body statt vieler identischer 0.15-m-Fragmente — nur der echt
|
||||
// geneigte Bereich bleibt gestuft. Das entlastet auch den Live-Schnitt, der genau
|
||||
// diese Wandkörper cuttet (weniger, saubere Schnitt-Kappen).
|
||||
let runStart = 0;
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
if (i < steps && Math.abs(tops[i] - tops[runStart]) <= EPS) continue;
|
||||
const sa = a + runStart * stepLen;
|
||||
const sb = a + i * stepLen;
|
||||
const zte = tops[runStart];
|
||||
if (zte - zb > EPS) emit(sa, sb, zb, zte);
|
||||
runStart = i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1932,19 +1946,13 @@ function emitRoofs(project: Project): RMesh[] {
|
||||
if (indices.length > 0) out.push({ positions, indices, kind: "extrusion", color, materialIndex });
|
||||
return offsetAbove + t;
|
||||
}, 0);
|
||||
// Giebel (Endfüllung) als dünne Fläche, äusserste Schichtfarbe/Override.
|
||||
if (g.gables.length > 0) {
|
||||
const gp: number[] = [];
|
||||
const gi: number[] = [];
|
||||
for (const gable of g.gables) pushFanTriangles(gp, gi, gable);
|
||||
if (gi.length > 0) {
|
||||
const first = layers[0];
|
||||
const gc =
|
||||
overrideColor ??
|
||||
(first?.componentId ? hexToRgb(getComponent(project, first.componentId).color, ROOF_RGB) : ROOF_RGB);
|
||||
out.push({ positions: gp, indices: gi, kind: "extrusion", color: gc });
|
||||
}
|
||||
}
|
||||
// Giebel-ENDFÜLLUNG bewusst NICHT mehr emittieren: die dreieckige Giebelfläche
|
||||
// wurde früher als papierdünne Einzelfläche gebaut ("komischer Abschluss" an
|
||||
// den Sattel-/Mansard-Enden) und lag zudem deckungsgleich vor der Giebelwand,
|
||||
// die ohnehin bis an die Dach-Unterkante hochgeklemmt wird (s.
|
||||
// {@link clipPieceToRoofs}) — also doppelt und optisch als dünne 2D-Schicht
|
||||
// sichtbar. Es wird nur noch das Dach SELBST (die geneigten Slab-Flächen)
|
||||
// konstruiert; der Giebel entsteht aus der Wand darunter.
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user