fix: update dimensions and descriptions in README and improve model generation logic in klabava.js
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
+95
-76
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user