diff --git a/klabava/README_CZ.md b/klabava/README_CZ.md index 15d494a..492bc97 100644 --- a/klabava/README_CZ.md +++ b/klabava/README_CZ.md @@ -24,7 +24,7 @@ Potom otevřete . Prohlížeč potřebuje WebGL a připoj Z půdorysu a pohledů vychází obrys domu 17,40 × 13,05 m, zastavěná plocha 178,75 m², užitná plocha 153,74 m² a úrovně hřebenů +5,275 a +5,832 m. Řez uvádí sklon střechy 23°. Hmota je modelována ve dvou navazujících křídlech; vyšší střecha je omezena na hlubší křídlo a její rozpon vychází z řezu a pohledů. Situace uvádí parcelu 66/17 o výměře 1 037 m² a 206,54 m² včetně krytého vstupu a terasy. Dvě parkovací stání jsou u komunikace vpravo od domu; plocha je zakreslená přibližně 6,625 × 5,0 m. Ve výškopisu jsou zaměřené hodnoty přibližně 349,5 m n. m. u komunikace a 347,2 m n. m. u zadní části parcely. -Objem domu a terén jsou zjednodušené z PDF. Rozmístění otvorů a střešní napojení jsou schematické. Hranice parcely je převzatá graficky ze situačního PDF, nikoli z vytyčovacích souřadnic. Výšky terénu jsou interpolované mezi zaměřenými body. Příjezd, parkovací plocha a materiály slouží k prostorové orientaci. Model není BIM ani podklad pro stavbu, zemní práce nebo vytyčení. +Objem domu, polohy a rozměry hlavních otvorů a výšky střech vycházejí z kót půdorysu, pohledů a řezu. Sklon střechy odpovídá řezu 23°; vyšší a nižší valbová hmota jsou v místě napojení zjednodušené a komín je usazen na šikmé rovině střechy. Rámy otvorů, terén, příjezd a parkovací plocha slouží k prostorové orientaci. Hranice parcely je převzatá graficky ze situačního PDF, nikoli z vytyčovacích souřadnic; výšky terénu jsou interpolované mezi zaměřenými body. Model není BIM ani podklad pro stavbu, zemní práce nebo vytyčení. Soubor `data/model.json` uchovává hlavní údaje projektu. Dodané PDF jsou zkopírované do `references/` a dostupné z panelu Projekt. Barvy fasády, střešní krytiny, oken a vybavení nejsou zatím vybrané. diff --git a/klabava/index.html b/klabava/index.html index b4032dc..116a8e5 100644 --- a/klabava/index.html +++ b/klabava/index.html @@ -6,9 +6,9 @@ Klabava | Rodinný dům - - - + + + @@ -59,8 +59,8 @@
Rozsah modelu a přesnost -

Půdorysná hmota, hlavní otvory a výšky vycházejí z dodaných výkresů. Střecha je schematicky složená z valbových ploch. Terén navazuje na zaměřené body přibližně od 349,5 m n. m. u komunikace k 347,2 m n. m. v zadní části parcely; mezi body je interpolovaný.

-

Hranice parcely je převzatá graficky z PDF situace, ne ze souřadnic pro vytyčení. Příjezd, parkovací plocha, okna a materiály jsou zjednodušené pro prostorovou orientaci. Model slouží k prezentaci a studii oslunění, nikoli jako BIM, geodetický nebo stavební podklad.

+

Půdorysná hmota, poloha a rozměry hlavních otvorů a výšky střech vycházejí z půdorysu, pohledů a řezu. Střecha je složená ze dvou valbových hmot se sklony podle řezu; komín dosedá na šikmou rovinu. Terén navazuje na zaměřené body přibližně od 349,5 m n. m. u komunikace k 347,2 m n. m. v zadní části parcely; mezi body je interpolovaný.

+

Hranice parcely je převzatá graficky z PDF situace, ne ze souřadnic pro vytyčení. Napojení střešních hmot, rámy oken a dveří, příjezd, parkovací plocha a materiály jsou zjednodušené pro prostorovou orientaci. Model slouží k prezentaci a studii oslunění, nikoli jako BIM, geodetický nebo stavební podklad.

Pracovní vizualizace · standardy doplníme po jejich výběru
@@ -120,6 +120,6 @@
Pracovní model · Klabava 66/17
- + diff --git a/klabava/js/klabava.js b/klabava/js/klabava.js index fc08ddd..7224be5 100644 --- a/klabava/js/klabava.js +++ b/klabava/js/klabava.js @@ -138,6 +138,25 @@ function poly(parent, name, points, material) { geometry.computeVertexNormals(); return mesh(geometry, material, parent, name); } +function slopedBox(parent, name, x, z, sx, sz, lowerAt, upperAt, material) { + const x0 = x - sx / 2, x1 = x + sx / 2, z0 = z - sz / 2, z1 = z + sz / 2; + const vertices = [ + [x0, lowerAt(x0, z0), z0], [x1, lowerAt(x1, z0), z0], [x1, lowerAt(x1, z1), z1], [x0, lowerAt(x0, z1), z1], + [x0, upperAt(x0, z0), z0], [x1, upperAt(x1, z0), z0], [x1, upperAt(x1, z1), z1], [x0, upperAt(x0, z1), z1] + ]; + const faces = [[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]; + const positions = [], indices = []; + for (const face of faces) { + const base = positions.length / 3; + for (const index of face) positions.push(...vertices[index]); + for (let i = 1; i < face.length - 1; i++) indices.push(base, base + i, base + i + 1); + } + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); + geometry.setIndex(indices); + geometry.computeVertexNormals(); + return mesh(geometry, material, parent, name); +} function line(parent, name, points, color = 0x657361, opacity = 0.85) { const geometry = new THREE.BufferGeometry().setFromPoints(points.map(p => new THREE.Vector3(...p))); const material = new THREE.LineBasicMaterial({ color, transparent: opacity < 1, opacity }); @@ -154,7 +173,7 @@ function groundHeight(x, z) { const dx = Math.max(Math.abs(x) - 9.5, 0); const dz = z > 15.8 ? z - 15.8 : z < 0.4 ? 0.4 - z : 0; const blend = smooth(0, 4.2, Math.hypot(dx, dz)); - return -0.13 * (1 - blend) + rawGround(x, z) * blend; + return -0.30 * (1 - blend) + rawGround(x, z) * blend; } function buildGround() { @@ -252,7 +271,7 @@ function makeWallEdge(p1, p2, openings = []) { const dx = p2[0] - p1[0], dz = p2[1] - p1[1]; const length = Math.hypot(dx, dz); const angle = Math.atan2(-dz, dx); - const thickness = 0.22, height = 3.046; + const thickness = 0.295, height = 3.046, wallBottom = -0.30; const positionAt = (distance) => [p1[0] + dx * distance / length, p1[1] + dz * distance / length]; const wallSegment = (from, to, bottom, top, name = 'Obvodová stěna') => { if (to - from < 0.02 || top - bottom < 0.02) return; @@ -265,113 +284,113 @@ function makeWallEdge(p1, p2, openings = []) { const left = clamp(opening.center - opening.width / 2, 0.05, length - 0.05); const right = clamp(opening.center + opening.width / 2, 0.05, length - 0.05); if (left <= cursor || right <= left) continue; - wallSegment(cursor, left, 0, height); - if (opening.bottom > 0) wallSegment(left, right, 0, opening.bottom, 'Parapet pod oknem'); + wallSegment(cursor, left, wallBottom, height); + if (opening.bottom > wallBottom) wallSegment(left, right, wallBottom, opening.bottom, 'Parapet pod oknem'); const top = Math.min(height, opening.bottom + opening.height); wallSegment(left, right, top, height, 'Nadpraží otvoru'); const [x, z] = positionAt((left + right) / 2); const centerY = opening.bottom + (top - opening.bottom) / 2; const paneMaterial = opening.kind === 'door' ? mats.door : mats.glass; - box(houseGroup, opening.kind === 'door' ? 'Vstupní dveře – zjednodušené' : 'Zasklení – zjednodušené', x, centerY, z, right - left - 0.10, Math.max(0.12, top - opening.bottom - 0.10), 0.055, paneMaterial, angle); + const paneName = opening.kind === 'door' ? 'Vstupní dveře – zjednodušené' : opening.kind === 'glazedDoor' ? 'Prosklené dveře – zjednodušené' : 'Okno – zjednodušené'; + box(houseGroup, paneName, x, centerY, z, right - left - 0.10, Math.max(0.12, top - opening.bottom - 0.10), 0.055, paneMaterial, angle); const frameWidth = 0.055; const jambHeight = top - opening.bottom; for (const offset of [-1, 1]) { const [jx, jz] = positionAt((left + right) / 2 + offset * (right - left - frameWidth) / 2); box(houseGroup, 'Rám otvoru', jx, opening.bottom + jambHeight / 2, jz, frameWidth, jambHeight, 0.09, mats.frame, angle); } + for (let pane = 1; pane < (opening.panes || 1); pane++) { + const offset = (pane / opening.panes - 0.5) * (right - left); + const mullionX = x + dx / length * offset; + const mullionZ = z + dz / length * offset; + box(houseGroup, 'Svislý rám okna', mullionX, opening.bottom + jambHeight / 2, mullionZ, frameWidth, jambHeight, 0.09, mats.frame, angle); + } box(houseGroup, 'Horní rám otvoru', x, top - frameWidth / 2, z, right - left, frameWidth, 0.09, mats.frame, angle); if (opening.bottom > 0) box(houseGroup, 'Parapet okna', x, opening.bottom + frameWidth / 2, z, right - left, frameWidth, 0.09, mats.frame, angle); cursor = right; } - wallSegment(cursor, length, 0, height); + wallSegment(cursor, length, wallBottom, height); } -function hipRoof(x0, x1, z0, z1, name, ridgeHeight) { - const overhang = 0.38, eave = 3.046; - x0 -= overhang; x1 += overhang; z0 -= overhang; z1 += overhang; +function hipPyramid(x0, x1, z0, z1, name, ridgeHeight, options = {}) { + const eave = 3.046; + const west = options.overhang?.west ?? 0.25, east = options.overhang?.east ?? 0.25; + const south = options.overhang?.south ?? 0.25, north = options.overhang?.north ?? 0.25; + x0 -= west; x1 += east; z0 -= south; z1 += north; const xm = (x0 + x1) / 2, zm = (z0 + z1) / 2; - const width = x1 - x0, depth = z1 - z0; const v = (x, y, z) => [x, y, z]; - if (width >= depth) { - const inset = depth / 2; - const a = v(x0 + inset, ridgeHeight, zm), b = v(x1 - inset, ridgeHeight, zm); - poly(houseGroup, `${name} – severní střešní rovina`, [v(x0, eave, z1), v(x1, eave, z1), b, a], mats.roof); - poly(houseGroup, `${name} – jižní střešní rovina`, [v(x1, eave, z0), v(x0, eave, z0), a, b], mats.roof); - poly(houseGroup, `${name} – západní valba`, [v(x0, eave, z1), v(x0, eave, z0), a], mats.roof); - poly(houseGroup, `${name} – východní valba`, [v(x1, eave, z0), v(x1, eave, z1), b], mats.roof); - box(houseGroup, `${name} – hřeben`, (a[0] + b[0]) / 2, ridgeHeight, zm, Math.max(0.18, b[0] - a[0]), 0.12, 0.12, mats.roof); - } else { - const inset = width / 2; - const a = v(xm, ridgeHeight, z0 + inset), b = v(xm, ridgeHeight, z1 - inset); - poly(houseGroup, `${name} – východní střešní rovina`, [v(x1, eave, z0), v(x1, eave, z1), b, a], mats.roof); - poly(houseGroup, `${name} – západní střešní rovina`, [v(x0, eave, z1), v(x0, eave, z0), a, b], mats.roof); - poly(houseGroup, `${name} – severní valba`, [v(x0, eave, z1), v(x1, eave, z1), b], mats.roof); - poly(houseGroup, `${name} – jižní valba`, [v(x1, eave, z0), v(x0, eave, z0), a], mats.roof); - box(houseGroup, `${name} – hřeben`, xm, ridgeHeight, (a[2] + b[2]) / 2, 0.12, 0.12, Math.max(0.18, b[2] - a[2]), mats.roof); - } - // Eaves and downpipes make the roof silhouette readable at presentation scale. - box(houseGroup, `${name} – okap sever`, xm, eave - 0.04, z1, width, 0.12, 0.12, mats.frame); - box(houseGroup, `${name} – okap jih`, xm, eave - 0.04, z0, width, 0.12, 0.12, mats.frame); - box(houseGroup, `${name} – okap západ`, x0, eave - 0.04, zm, 0.12, 0.12, depth, mats.frame); - box(houseGroup, `${name} – okap východ`, x1, eave - 0.04, zm, 0.12, 0.12, depth, mats.frame); + const apex = v(xm, ridgeHeight, zm); + poly(houseGroup, `${name} – severní valba`, [v(x0, eave, z1), v(x1, eave, z1), apex], mats.roof); + poly(houseGroup, `${name} – jižní valba`, [v(x1, eave, z0), v(x0, eave, z0), apex], mats.roof); + poly(houseGroup, `${name} – západní valba`, [v(x0, eave, z0), v(x0, eave, z1), apex], mats.roof); + poly(houseGroup, `${name} – východní valba`, [v(x1, eave, z1), v(x1, eave, z0), apex], mats.roof); + const width = x1 - x0, depth = z1 - z0; + const edges = options.eaveEdges ?? { west: true, east: true, south: true, north: true }; + if (edges.north) box(houseGroup, `${name} – okap sever`, xm, eave - 0.04, z1, width, 0.12, 0.12, mats.frame); + if (edges.south) box(houseGroup, `${name} – okap jih`, xm, eave - 0.04, z0, width, 0.12, 0.12, mats.frame); + if (edges.west) box(houseGroup, `${name} – okap západ`, x0, eave - 0.04, zm, 0.12, 0.12, depth, mats.frame); + if (edges.east) box(houseGroup, `${name} – okap východ`, x1, eave - 0.04, zm, 0.12, 0.12, depth, mats.frame); + return { x0, x1, z0, z1, ridgeX: xm, ridgeZ: zm, eave, ridgeHeight }; } -// The higher roof crosses the deeper wing. Its 13.05 m roof span and +5.832 m -// ridge follow the 23° section/elevation; the wing walls themselves are 11.67 m deep. -function hipRoofAcrossDepth(x0, x1, z0, z1, name, ridgeHeight) { - const eave = 3.046, overhangX = 0.38, overhangZ = 0.69; - x0 -= overhangX; x1 += overhangX; z0 -= overhangZ; z1 += overhangZ; - const zm = (z0 + z1) / 2; - const v = (x, y, z) => [x, y, z]; - poly(houseGroup, `${name} – severní střešní rovina`, [ - v(x0, eave, z1), v(x1, eave, z1), v(x1, ridgeHeight, zm), v(x0, ridgeHeight, zm) - ], mats.roof); - poly(houseGroup, `${name} – jižní střešní rovina`, [ - v(x1, eave, z0), v(x0, eave, z0), v(x0, ridgeHeight, zm), v(x1, ridgeHeight, zm) - ], mats.roof); - poly(houseGroup, `${name} – západní valba`, [ - v(x0, eave, z0), v(x0, eave, z1), v(x0, ridgeHeight, zm) - ], mats.roof); - poly(houseGroup, `${name} – východní valba`, [ - v(x1, eave, z1), v(x1, eave, z0), v(x1, ridgeHeight, zm) - ], mats.roof); - box(houseGroup, `${name} – hřeben`, (x0 + x1) / 2, ridgeHeight, zm, x1 - x0, 0.12, 0.12, mats.roof); - box(houseGroup, `${name} – okap sever`, (x0 + x1) / 2, eave - 0.04, z1, x1 - x0, 0.12, 0.12, mats.frame); - box(houseGroup, `${name} – okap jih`, (x0 + x1) / 2, eave - 0.04, z0, x1 - x0, 0.12, 0.12, mats.frame); - box(houseGroup, `${name} – okap západ`, x0, eave - 0.04, zm, 0.12, 0.12, z1 - z0, mats.frame); - box(houseGroup, `${name} – okap východ`, x1, eave - 0.04, zm, 0.12, 0.12, z1 - z0, mats.frame); +function hipRoofHeight(roof, x, z) { + const fall = Math.max( + Math.abs(x - roof.ridgeX) / ((roof.x1 - roof.x0) / 2), + Math.abs(z - roof.ridgeZ) / ((roof.z1 - roof.z0) / 2) + ); + return roof.eave + (roof.ridgeHeight - roof.eave) * (1 - fall); } function buildHouse() { makeFootprintSlab(); const openings = [ - [{ center: 2.0, width: 1.0, bottom: 0, height: 2.18, kind: 'door' }, { center: 6.2, width: 2.05, bottom: 0.85, height: 1.38 }], - [{ center: 2.5, width: 1.35, bottom: 0.9, height: 1.25 }, { center: 6.5, width: 1.1, bottom: 0.9, height: 1.25 }], - [{ center: 2.5, width: 1.65, bottom: 0.9, height: 1.35 }, { center: 7.4, width: 3.25, bottom: 0, height: 2.22, kind: 'door' }], - [{ center: 1.7, width: 1.55, bottom: 0.85, height: 1.4 }], - [{ center: 3.25, width: 3.4, bottom: 0, height: 2.2, kind: 'door' }], - [{ center: 2.2, width: 1.4, bottom: 0.9, height: 1.2 }, { center: 8.4, width: 1.7, bottom: 0.9, height: 1.2 }], - [{ center: 2.3, width: 1.45, bottom: 0.9, height: 1.25 }, { center: 5.4, width: 1.25, bottom: 0.9, height: 1.25 }], + // North long façade: three windows, positioned from the dimension chain in the floor plan. + [{ center: 1.66, width: 0.8, bottom: 1.3, height: 1.3 }, { center: 4.805, width: 1.3, bottom: 1.3, height: 1.3, panes: 2 }, { center: 7.525, width: 1.8, bottom: 1.3, height: 1.3, panes: 2 }], + // East end façade: two window groups. + [{ center: 1.8, width: 1.8, bottom: 1.3, height: 1.3, panes: 2 }, { center: 6.9, width: 1.8, bottom: 1.3, height: 1.3, panes: 2 }], + // South long façade: 1.8 m glazed door, 1.1 m door, and 1.8 m window from the dimension chain. + [{ center: 2.575, width: 1.8, bottom: 1.3, height: 1.3, panes: 2 }, { center: 4.925, width: 1.1, bottom: 0, height: 2.3, kind: 'door' }, { center: 8.475, width: 1.8, bottom: 0, height: 2.3, kind: 'glazedDoor', panes: 2 }], + [{ center: 1.8, width: 1.3, bottom: 1.3, height: 1.3 }], + // The garden-end door is 1.1 m wide in the plan. + [{ center: 1.125, width: 1.1, bottom: 0, height: 2.3, kind: 'door' }], + // West end: the entry door and two larger window groups shown in the elevations. + [{ center: 2.315, width: 1.8, bottom: 1.3, height: 1.3, panes: 2 }, { center: 6.221, width: 1.8, bottom: 1.3, height: 1.3, panes: 2 }, { center: 9.07, width: 1.1, bottom: 0, height: 2.3, kind: 'door' }], + [], [] ]; for (let i = 0; i < footprint.length; i++) makeWallEdge(footprint[i], footprint[(i + 1) % footprint.length], openings[i]); - // The section shows the 23° lower roof ridge (+5.275 m); the elevations show the deeper wing ridge (+5.832 m). - hipRoof(-1.575, 8.7, -2.775, 6.525, 'Nižší valbová střecha', 5.275); - hipRoofAcrossDepth(-8.7, -1.575, -6.525, 5.145, 'Vyšší valbová střecha', 5.832); + // The section's 23° pitch and both elevations' ridge levels define two separate hipped masses. + // Their inner eaves meet at the common wall; neither roof projects through the other. + hipPyramid(-1.575, 8.7, -2.775, 6.525, 'Nižší valbová střecha', 5.275, { + overhang: { west: 0, east: 0.25, south: 0.38, north: 0.38 }, + eaveEdges: { west: false, east: true, south: true, north: true } + }); + const highRoof = hipPyramid(-8.7, -1.575, -6.525, 5.145, 'Vyšší valbová střecha', 5.832, { + overhang: { west: 0.25, east: 0, south: 0.69, north: 0.69 }, + eaveEdges: { west: true, east: false, south: true, north: true } + }); - // Chimney shown in the elevations, with a simple cap. - box(houseGroup, 'Komín', 5.7, 5.78, 1.8, 0.64, 1.25, 0.64, mats.foundation); - box(houseGroup, 'Hlavice komína', 5.7, 6.42, 1.8, 0.82, 0.12, 0.82, mats.frame); + // The elevations set the chimney top at +6.500 m and show 1.255 m above the roof plane. + // The base is cut to the sloping roof plane so the masonry cannot float above or pass through it. + const chimneyX = -5.14, chimneyZ = 0.69; + const chimneyRoofY = (x, z) => hipRoofHeight(highRoof, x, z); + slopedBox(houseGroup, 'Oplechování komína', chimneyX, chimneyZ, 0.82, 0.82, + chimneyRoofY, (x, z) => chimneyRoofY(x, z) + 0.08, mats.metal); + slopedBox(houseGroup, 'Komín', chimneyX, chimneyZ, 0.64, 0.64, + (x, z) => chimneyRoofY(x, z) + 0.05, () => 6.38, mats.foundation); + box(houseGroup, 'Hlavice komína', -5.14, 6.44, 0.69, 0.82, 0.12, 0.82, mats.frame); - // Covered terrace is included in the site sheets; its posts and canopy are deliberately simple. + // The covered terrace is 4.585 × 3.533 m and sits directly outside the south glazed door. const deck = group(modelRoot, 'Krytá terasa – schematicky'); - box(deck, 'Podlaha terasy', -4.0, -0.02, 0.05, 5.1, 0.18, 3.15, mats.terrace); - for (const [x, z] of [[-6.35, -1.38], [-1.65, -1.38], [-6.35, 1.48], [-1.65, 1.48]]) { - const ground = groundHeight(x, z); - box(deck, 'Sloupek kryté terasy', x, (2.92 + ground) / 2, z, 0.14, 2.92 - ground, 0.14, mats.wall); + deck.position.set(0.225, 0, houseGroup.position.z - 4.542); + box(deck, 'Podlaha terasy', 0, -0.15, 0, 4.585, 0.15, 3.533, mats.terrace); + for (const [x, z] of [[-2.12, -1.55], [2.12, -1.55], [-2.12, 1.55], [2.12, 1.55]]) { + const ground = groundHeight(deck.position.x + x, deck.position.z + z); + const postTop = 3.02; + box(deck, 'Sloupek kryté terasy', x, (postTop + ground) / 2, z, 0.14, postTop - ground, 0.14, mats.wall); } - box(deck, 'Stříška terasy', -4.0, 3.02, 0.05, 5.35, 0.16, 3.35, mats.terrace); - box(deck, 'Schod z terasy', -4.0, -0.19, -1.7, 2.5, 0.18, 0.45, mats.path); + box(deck, 'Stříška terasy', 0, 3.1, 0, 5.05, 0.16, 4.0, mats.terrace); + box(deck, 'Schod z terasy', 0, -0.25, -2.0, 2.5, 0.18, 0.45, mats.path); } buildGround();