v0.4.15: gevelbekleding per woning (rabat/steen, override per wand), stenen trasraam (110 mm, 181 texturen), riet per unit, vloerafwerking, verfkleur binnen, boeideelkleur, gevel tot onder dakrand, binnenhoek-fixes; testontwerp als standaard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7d91416004
commit
12c2579471
+129
-19
@@ -60,12 +60,23 @@ window.HouseModel = (() => {
|
||||
function createDefaultSettings() {
|
||||
return {
|
||||
spacing: { exterior: { min: 240, max: 480 }, interior: { min: 300, max: 600 } },
|
||||
cladding: 'horizontaal', // richting gevelbekleding, voorlopig voor alle buitenwanden
|
||||
// Gevelbekleding van alle buitenwanden (override per wand: unit.facade):
|
||||
// rabat = vochtdoek + kruislat + rabat (richting: cladding);
|
||||
// steen = cement-gebonden plaat + steenblok (stone = steencode, WallModel.STONE).
|
||||
// Het trasraam (eigen afwerking/steen) komt onder beide.
|
||||
facade: { type: 'rabat', stone: 'K490' },
|
||||
cladding: 'horizontaal', // richting rabat, voorlopig voor alle buitenwanden
|
||||
// Trasraam: cement-gebonden plaat onderaan alle vochtdoek-gevels, hoogte
|
||||
// vanaf het beloopbare vloervlak (WallModel.trasraamFor).
|
||||
trasraam: { enabled: true, height: WallModel.TRASRAAM_DEFAULT_MM },
|
||||
// finish: 'cementplaat' of een steencode (WallModel.TRASRAAM_STONES).
|
||||
trasraam: { enabled: true, height: WallModel.TRASRAAM_DEFAULT_MM, finish: 'cementplaat' },
|
||||
// Binnenzijde van alle wanden (voorlopig globaal): geschilderd in
|
||||
// interiorColor (egaal, zonder plaatnaden) of kale spaanplaat.
|
||||
interiorPaint: true,
|
||||
interiorColor: '#f2efe9',
|
||||
sandwich: true, // sandwichpanelen rondom (SandwichModel), vanaf loopvlak 600 mm de grond in
|
||||
groundLevel: 350, // maaiveld: zoveel mm onder het loopvlak (bovenkant vloer); voor de 3D-omgeving
|
||||
groundLevel: 0, // maaiveld: zoveel mm onder het loopvlak (bovenkant vloer); voor de 3D-omgeving
|
||||
floorFinish: 'visgraat_001', // vloerafwerking (FloorModel.FINISHES), voorlopig globaal
|
||||
roof: Object.assign({}, RoofModel.DEFAULT_SETTINGS),
|
||||
lighting: Object.assign({}, LightingModel.DEFAULT_SETTINGS)
|
||||
};
|
||||
@@ -73,10 +84,35 @@ window.HouseModel = (() => {
|
||||
|
||||
function createUnitEntry(id, attach, unitOpts) {
|
||||
const unit = UnitModel.createDefaultState(unitOpts);
|
||||
return { id, attach, unit, roof: RoofModel.createDefaultRoof(unit.floor), lighting: LightingModel.createDefaultUnitLighting() };
|
||||
return { id, attach, unit, roof: RoofModel.createDefaultRoof(unit.floor), lighting: LightingModel.createDefaultUnitLighting(), facade: { walls: {} } };
|
||||
}
|
||||
|
||||
// Gevelbekleding: keuze voor de woning, override per wand.
|
||||
const FACADE_TYPES = { rabat: 'Rabat', steen: 'Steen' };
|
||||
// { type, stone } voor één wand van een unit (u = entry uit state.units).
|
||||
function facadeFor(state, u, side) {
|
||||
const f = state.settings.facade;
|
||||
const o = (u.facade && u.facade.walls && u.facade.walls[side]) || null;
|
||||
const type = o && FACADE_TYPES[o.type] ? o.type : f.type;
|
||||
const stone = o && o.type === 'steen' && WallModel.isStoneFinish(o.stone) ? o.stone : f.stone;
|
||||
return { type, stone, override: !!o };
|
||||
}
|
||||
|
||||
// TIJDELIJK (testen): een nieuwe woning is standaard het testontwerp:
|
||||
// L-vorm, unit A lessenaar (noord 2600, zuid 3200) met riet. Op false
|
||||
// zetten om terug te gaan naar de gewone standaard (één unit).
|
||||
const TEST_DEFAULT = true;
|
||||
|
||||
function createDefaultState(template) {
|
||||
if (TEST_DEFAULT && (!template || template === 'single')) {
|
||||
const st = createDefaultState('L');
|
||||
const A = st.units[0], W = A.unit.walls;
|
||||
const h = (k, l, r) => Object.assign(W[k].wall, { heightAll: Math.min(l, r), heightLeft: l, heightRight: r, heightMid: null });
|
||||
h('achter', 3200, 3200); h('links', 2600, 3200); h('rechts', 3200, 2600);
|
||||
A.roof.covering = 'riet_001';
|
||||
computeParts(st, { skip3D: true });
|
||||
return st;
|
||||
}
|
||||
const state = { project: createEmptyProject(), settings: createDefaultSettings(), units: [], interiorWalls: [] };
|
||||
state.units.push(createUnitEntry('A', null, { width: 6000, depth: 3000 }));
|
||||
if (template === 'L') {
|
||||
@@ -282,11 +318,14 @@ window.HouseModel = (() => {
|
||||
const sp = state.settings.spacing;
|
||||
const tr = state.settings.trasraam;
|
||||
state.units.forEach(u => {
|
||||
const trasraam = tr.enabled ? WallModel.trasraamFor(tr.height) : null;
|
||||
Object.values(u.unit.walls).forEach(ws => {
|
||||
const trasraam = tr.enabled ? WallModel.trasraamFor(tr.height, tr.finish) : null;
|
||||
Object.entries(u.unit.walls).forEach(([side, ws]) => {
|
||||
ws.wall.minSpacing = sp.exterior.min;
|
||||
ws.wall.maxSpacing = sp.exterior.max;
|
||||
ws.materials = Object.assign({}, ws.materials, { cladding: state.settings.cladding, trasraam });
|
||||
const fc = facadeFor(state, u, side);
|
||||
ws.materials = Object.assign({}, ws.materials, fc.type === 'steen'
|
||||
? { exterior: 'cementplaat', stone: fc.stone, cladding: state.settings.cladding, trasraam }
|
||||
: { exterior: 'vochtdoek', stone: null, cladding: state.settings.cladding, trasraam });
|
||||
});
|
||||
if (u.roof.followFloor) {
|
||||
u.roof.deck.floor.width = u.unit.floor.floor.width;
|
||||
@@ -310,14 +349,22 @@ window.HouseModel = (() => {
|
||||
const W = p.place.width, T = p.place.thickness;
|
||||
const normal = { x: p.place.normal.x, z: p.place.normal.y };
|
||||
const axis = Math.abs(normal.z) > 0.5 ? 'x' : 'z', perp = axis === 'x' ? 'z' : 'x';
|
||||
const q0 = UnitModel.localToWorld(p.place, -W / 2, T / 2), q1 = UnitModel.localToWorld(p.place, W / 2, T / 2);
|
||||
segs.push({ piece: p, axis, normal, line: q0[perp], sAt0: q0[axis], sAtW: q1[axis], s0: Math.min(q0[axis], q1[axis]), s1: Math.max(q0[axis], q1[axis]) });
|
||||
// Binnenhoek-doorloop (negatieve skinExt, zie trimTop): zit dat stuk
|
||||
// over de hele hoogte in de buur-unit, dan begint de gevel pas daarna.
|
||||
// Loopt de gevel erboven door (lager dak van de buur), dan telt het
|
||||
// hele stuk mee, zodat de gevel één geheel blijft met de rest.
|
||||
const e = p.skinExt || {}, tt = p.trimTop || {};
|
||||
const maxY = WallModel.topLine(p.config).maxY + (p.skinAbove || 0);
|
||||
const trim = (k) => e[k] < 0 && !(tt[k] > 0 && tt[k] < maxY - 1) ? -e[k] : 0;
|
||||
const tL = trim('left'), tR = trim('right');
|
||||
const q0 = UnitModel.localToWorld(p.place, -W / 2 + tL, T / 2), q1 = UnitModel.localToWorld(p.place, W / 2 - tR, T / 2);
|
||||
segs.push({ piece: p, trimL: tL, axis, normal, line: q0[perp], sAt0: q0[axis], sAtW: q1[axis], s0: Math.min(q0[axis], q1[axis]), s1: Math.max(q0[axis], q1[axis]) });
|
||||
});
|
||||
groupFacadeRuns(segs).forEach(run => {
|
||||
const dir = Math.sign(run.segs[0].sAtW - run.segs[0].sAt0) || 1;
|
||||
const sLeft = dir > 0 ? run.s0 : run.s1, sRight = dir > 0 ? run.s1 : run.s0;
|
||||
const first = run.segs.reduce((best, sg) => Math.abs(sg.sAt0 - sLeft) < Math.abs(best.sAt0 - sLeft) ? sg : best);
|
||||
const extLeft = (first.piece.skinExt && first.piece.skinExt.left) || 0;
|
||||
const extLeft = Math.max(0, (first.piece.skinExt && first.piece.skinExt.left) || 0);
|
||||
// Horizontaal latten-raster: vanaf het trasraam van een gewoon stuk
|
||||
// (absolute hoogte), zodat opbouw/naadwand-stukken erop aansluiten.
|
||||
const ref = run.segs.find(sg => !sg.piece.place.baseY && sg.piece.skinFrom === undefined);
|
||||
@@ -326,7 +373,7 @@ window.HouseModel = (() => {
|
||||
run.segs.forEach(sg => {
|
||||
const p = sg.piece;
|
||||
sg.piece.facade = {
|
||||
u0: Math.abs(sg.sAt0 - sLeft) + extLeft, extStart: extLeft > 0, isEnd: Math.abs(sg.sAtW - sRight) < 5,
|
||||
u0: Math.abs(sg.sAt0 - sLeft) + extLeft - sg.trimL, extStart: extLeft > 0, isEnd: Math.abs(sg.sAtW - sRight) < 5,
|
||||
gridY0: gridAbs === undefined ? undefined : gridAbs - p.place.floorTop,
|
||||
yOff: floorAbs === undefined ? 0 : p.place.floorTop - floorAbs
|
||||
};
|
||||
@@ -344,10 +391,11 @@ window.HouseModel = (() => {
|
||||
const wallThickness = (id, side) => WallModel.thicknessOf(WallModel.configFromState(state.units.find(u => u.id === id).unit.walls[side]));
|
||||
const { cuts, seams } = computeSeams(rects, wallThickness);
|
||||
|
||||
function buildWall(config, frame, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow) {
|
||||
function buildWall(config, frame, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop) {
|
||||
if (!build) return { group: null, stats: null, specs: null };
|
||||
const built = WallModel.build3D(config, frame, {
|
||||
materials: o.wallMaterials, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow,
|
||||
materials: o.wallMaterials, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop,
|
||||
painted: state.settings.interiorPaint,
|
||||
spaanplaatVisible: o.beplatingVisible !== false, buitenVisible: o.beplatingVisible !== false
|
||||
});
|
||||
return { group: built.group, stats: built.stats, specs: WallModel.computeSpecs(config, built.stats, buitenMateriaal) };
|
||||
@@ -360,7 +408,8 @@ window.HouseModel = (() => {
|
||||
const ws = u.unit.walls[wl.side];
|
||||
const buitenMateriaal = (ws.materials || {}).exterior || 'vochtdoek';
|
||||
const cladding = (ws.materials || {}).cladding || 'horizontaal';
|
||||
const trasraam = buitenMateriaal === 'vochtdoek' ? ((ws.materials || {}).trasraam || null) : null;
|
||||
const stone = buitenMateriaal === 'cementplaat' ? ((ws.materials || {}).stone || null) : null;
|
||||
const trasraam = buitenMateriaal === 'vochtdoek' || stone ? ((ws.materials || {}).trasraam || null) : null;
|
||||
const wp = UnitModel.wallPieces(wl, ws, raised);
|
||||
const pieces = wp.pieces.map(p => {
|
||||
const c = UnitModel.localToWorld(wl, p.lxCenter, 0);
|
||||
@@ -368,7 +417,7 @@ window.HouseModel = (() => {
|
||||
return Object.assign(p, {
|
||||
element: `${u.id}-W${wl.nr}${p.suffix}`,
|
||||
label: `Unit ${u.id} ${wl.label.toLowerCase()}` + (base ? ` opbouw (vanaf ${base} mm)` : p.suffix ? ` (${p.suffix})` : ''),
|
||||
buitenMateriaal, cladding, trasraam: base ? null : trasraam,
|
||||
buitenMateriaal, cladding, trasraam: base ? null : trasraam, stone,
|
||||
place: { cx: rect.cx + c.x, cz: rect.cz + c.z, rotY: wl.rotY, width: p.config.wallWidth, thickness: wl.thickness, normal: wl.normal, floorTop: layout.floorTop + base, baseY: base }
|
||||
}); // 3D volgt na assignFacades (latwerk/doek per gevel)
|
||||
});
|
||||
@@ -394,7 +443,8 @@ window.HouseModel = (() => {
|
||||
const floorData = FloorModel.computeFromState(u.unit.floor, rims);
|
||||
const floorSpecs = FloorModel.computeSpecs(floorData.calcData, floorData.resolvedOpenings);
|
||||
const floorGroup = build ? FloorModel.build3D(floorData.calcData, floorData.resolvedOpenings, floorData.chassisData, {
|
||||
materials: o.floorMaterials, osbVisible: o.beplatingVisible !== false
|
||||
materials: o.floorMaterials, osbVisible: o.beplatingVisible !== false,
|
||||
finish: { key: state.settings.floorFinish, originX: rect.cx * 0.001, originZ: rect.cz * 0.001 }
|
||||
}) : null;
|
||||
|
||||
const walls = layout.walls.map(wl => wallOf(u, rect, layout, wl));
|
||||
@@ -643,9 +693,35 @@ window.HouseModel = (() => {
|
||||
// over het blootliggende deel van een naadwand. Daarna pas bouwen.
|
||||
const extPieces = [];
|
||||
units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => extPieces.push(p))));
|
||||
// Gevel tot onder de dakrand: tussen wandbovenkant en onderkant boeideel
|
||||
// resp. daktrim zit het dakpakket (dek + bedekking, verschilt per dak).
|
||||
// De buitenlagen van de wand lopen daar door (skinAbove, mm), zodat er
|
||||
// geen dakrand bloot ligt, bij elke daksoort.
|
||||
const roofSt = RoofModel.normalizeSettings(state.settings.roof);
|
||||
const flatTopsPre = units.filter(u => u.roof.shape.type === 'plat').map(u => u.roof.top);
|
||||
const flatTopPre = flatTopsPre.length ? Math.max(...flatTopsPre) : 0;
|
||||
units.forEach(u => {
|
||||
const su = state.units.find(x => x.id === u.id);
|
||||
const below = su.roof.fascia !== false ? roofSt.fasciaHeight - RoofModel.EDGE_ABOVE_MM : roofSt.trimHeight;
|
||||
u.walls.forEach(w => w.pieces.forEach(p => {
|
||||
const m = UnitModel.localToWorld(p.place, 0, p.place.thickness / 2);
|
||||
const top = u.roof.shape.type === 'plat' ? flatTopPre : u.roof.topAt(m.x, m.z);
|
||||
p.skinAbove = Math.max(0, Math.round(top - u.roof.ceilingAt(m.x, m.z) - below));
|
||||
// Binnenhoek-doorloop (negatieve skinExt): tot de bovenkant van het
|
||||
// dak van de buur-unit die daar staat geen buitenlagen (trimTop).
|
||||
const e = p.skinExt || {}, W = p.place.width;
|
||||
p.trimTop = {};
|
||||
[['left', -1], ['right', 1]].forEach(([k, d]) => {
|
||||
if (!(e[k] < 0)) return;
|
||||
const q = UnitModel.localToWorld(p.place, d * (W / 2 + e[k] / 2), p.place.thickness / 2 + 20);
|
||||
const nb = units.find(o => o.id !== u.id && q.x > o.rect.x0 && q.x < o.rect.x1 && q.z > o.rect.z0 && q.z < o.rect.z1);
|
||||
p.trimTop[k] = nb ? Math.round(nb.roof.topAt(q.x, q.z) - p.place.floorTop) : 0;
|
||||
});
|
||||
}));
|
||||
});
|
||||
assignFacades(extPieces.concat(interior.filter(p => p.skinFrom !== undefined)));
|
||||
extPieces.concat(interior).forEach(p => {
|
||||
const built = buildWall(p.config, p.frame, p.buitenMateriaal, p.cladding, p.trasraam, p.skinExt, p.facade, p.skinFrom, p.spaanplaatBelow);
|
||||
const built = buildWall(p.config, p.frame, p.buitenMateriaal, p.cladding, p.trasraam, p.skinExt, p.facade, p.skinFrom, p.spaanplaatBelow, p.stone, p.skinAbove, p.trimTop);
|
||||
if (built.group && p.shiftZ) {
|
||||
const outer = new THREE.Group();
|
||||
built.group.position.z = p.shiftZ * 0.001;
|
||||
@@ -725,6 +801,9 @@ window.HouseModel = (() => {
|
||||
const purchase = WallModel.planPurchase(wallBom);
|
||||
|
||||
const sumP = (fn) => pieces.reduce((acc, p) => acc + fn(p), 0);
|
||||
const stonePiece = pieces.find(p => p.trasraam && WallModel.isStoneFinish(p.trasraam.finish));
|
||||
const stoneCode = stonePiece ? stonePiece.trasraam.finish : null;
|
||||
const stoneCodes = [...new Set(pieces.filter(p => p.stone).map(p => p.stone))].join(', ');
|
||||
const sumU = (fn) => parts.units.reduce((acc, u) => acc + fn(u), 0);
|
||||
const byMat = (mat) => pieces.filter(p => p.buitenMateriaal === mat);
|
||||
|
||||
@@ -762,10 +841,24 @@ window.HouseModel = (() => {
|
||||
},
|
||||
{ naam: 'OSB-plaat (vloeren + plafonds dak)', m2: sumU(u => u.floorSpecs.osbAreaM2 + u.roof.specs.osbAreaM2), m3: sumU(u => u.floorSpecs.osbVolM3 + u.roof.specs.osbVolM3), kg: sumU(u => u.floorSpecs.osbKg + u.roof.specs.osbKg) },
|
||||
{ naam: 'Dakleer (EPDM)', m2: sumU(u => u.roof.specs.dakleerM2), m3: null, kg: sumU(u => u.roof.specs.dakleerKg) },
|
||||
{ naam: `Riet (${RoofModel.RIET.thickMM} mm)`, m2: sumU(u => u.roof.specs.rietM2 || 0), m3: null, kg: sumU(u => u.roof.specs.rietKg || 0) },
|
||||
{
|
||||
naam: 'Cementplaat (buitenzijde)', m2: byMat('cementplaat').reduce((a, p) => a + p.stats.cementplaatAreaM2, 0),
|
||||
m3: byMat('cementplaat').reduce((a, p) => a + p.stats.cementplaatVolumeM3, 0), kg: byMat('cementplaat').reduce((a, p) => a + p.specs.buitenKg, 0)
|
||||
},
|
||||
{
|
||||
naam: `Steenstrips gevel${stoneCodes ? ' (' + stoneCodes + ')' : ''}`,
|
||||
m2: sumP(p => p.stats.stoneAreaM2 || 0), m3: null, kg: 0 // gewicht volgt
|
||||
},
|
||||
{
|
||||
naam: `Steenstrips trasraam${stoneCode ? ' (' + stoneCode + ')' : ''}`,
|
||||
m2: sumP(p => p.stats.trasraamStoneAreaM2 || 0), m3: null, kg: 0 // gewicht volgt
|
||||
},
|
||||
{
|
||||
naam: `Isolatieblok steen (${WallModel.STONE.THICK_MM - WallModel.STONE.STRIP_MM} mm)`,
|
||||
m2: sumP(p => (p.stats.trasraamIsolatieM3 || 0) + (p.stats.stoneIsolatieM3 || 0)) / ((WallModel.STONE.THICK_MM - WallModel.STONE.STRIP_MM) / 1000),
|
||||
m3: sumP(p => (p.stats.trasraamIsolatieM3 || 0) + (p.stats.stoneIsolatieM3 || 0)), kg: 0
|
||||
},
|
||||
{
|
||||
naam: 'Cementplaat (trasraam)', m2: sumP(p => p.stats.trasraamAreaM2 || 0),
|
||||
m3: sumP(p => p.stats.trasraamVolumeM3 || 0), kg: sumP(p => p.specs.trasraamKg || 0)
|
||||
@@ -784,6 +877,7 @@ window.HouseModel = (() => {
|
||||
roofEdge: parts.roofEdge ? {
|
||||
fasciaLengthM: parts.roofEdge.fasciaLengthMM / 1000,
|
||||
fasciaHeight: parts.roofEdge.fasciaHeight,
|
||||
fasciaColor: (RoofModel.FASCIA_COLORS[parts.roofEdge.fasciaColor] || {}).label || '',
|
||||
overhang: parts.roofEdge.overhang,
|
||||
soffitM2: parts.roofEdge.soffitM2,
|
||||
trimLengthM: parts.roofEdge.trimLengthMM / 1000,
|
||||
@@ -837,11 +931,22 @@ window.HouseModel = (() => {
|
||||
state.settings = state.settings || {};
|
||||
state.settings.spacing = Object.assign(def.spacing, state.settings.spacing || {});
|
||||
if (!WallModel.CLADDING_DIRECTIONS[state.settings.cladding]) state.settings.cladding = def.cladding;
|
||||
// v0.4.15: gevelbekleding per woning; een wand met cementplaat wordt een
|
||||
// steen-override (de rest volgt de woning, standaard rabat).
|
||||
const hadFacade = !!state.settings.facade;
|
||||
state.settings.facade = Object.assign(def.facade, state.settings.facade || {});
|
||||
if (!FACADE_TYPES[state.settings.facade.type]) state.settings.facade.type = 'rabat';
|
||||
if (!WallModel.isStoneFinish(state.settings.facade.stone)) state.settings.facade.stone = def.facade.stone;
|
||||
state.settings.trasraam = Object.assign(def.trasraam, state.settings.trasraam || {});
|
||||
state.settings.trasraam.enabled = state.settings.trasraam.enabled !== false;
|
||||
if (!(state.settings.trasraam.height > 0)) state.settings.trasraam.height = WallModel.TRASRAAM_DEFAULT_MM;
|
||||
if (!WallModel.isStoneFinish(state.settings.trasraam.finish)) state.settings.trasraam.finish = 'cementplaat';
|
||||
state.settings.sandwich = state.settings.sandwich !== false;
|
||||
if (!(state.settings.groundLevel >= 0)) state.settings.groundLevel = def.groundLevel;
|
||||
if (!/^#[0-9a-f]{6}$/i.test(state.settings.interiorColor || '')) state.settings.interiorColor = def.interiorColor;
|
||||
state.settings.interiorPaint = state.settings.interiorPaint !== false;
|
||||
// v0.4.15: maaiveld standaard gelijk met de bovenkant vloer (was 350 mm eronder).
|
||||
if (!(state.settings.groundLevel >= 0) || (!state.settings.floorFinish && state.settings.groundLevel === 350)) state.settings.groundLevel = def.groundLevel;
|
||||
if (!FloorModel.FINISHES[state.settings.floorFinish]) state.settings.floorFinish = def.floorFinish;
|
||||
const oldRoof = state.settings.roof || {}; // v0.3.0: edge/spots waren woning-breed
|
||||
state.settings.roof = RoofModel.normalizeSettings(oldRoof);
|
||||
// v0.3.1: type/richting van de verlichting stonden per unit; nu woning-breed.
|
||||
@@ -854,13 +959,18 @@ window.HouseModel = (() => {
|
||||
if (!u.unit.layout) u.unit.layout = { corners: 'voor-achter' };
|
||||
if (!u.roof) { u.roof = RoofModel.createDefaultRoof(u.unit.floor); delete u.roof.fascia; }
|
||||
if (u.roof.fascia === undefined) u.roof.fascia = oldRoof.edge !== 'daktrim';
|
||||
if (!RoofModel.COVERINGS[u.roof.covering]) u.roof.covering = 'dakleer';
|
||||
u.lighting = { walls: (u.lighting && u.lighting.walls) || {} };
|
||||
u.facade = { walls: (u.facade && u.facade.walls) || {} };
|
||||
if (!hadFacade) Object.entries(u.unit.walls).forEach(([side, ws]) => {
|
||||
if ((ws.materials || {}).exterior === 'cementplaat' && !u.facade.walls[side]) u.facade.walls[side] = { type: 'steen' };
|
||||
});
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
ALIGN_LABELS, SIDE_LABELS, DEFAULT_INTERIOR_THICKNESS,
|
||||
ALIGN_LABELS, SIDE_LABELS, DEFAULT_INTERIOR_THICKNESS, FACADE_TYPES, facadeFor,
|
||||
clone, createEmptyProject, createDefaultSettings, createUnitEntry, applyGlobalSettings, nextUnitId, createDefaultState, unitRects, computeSeams, overlappingPairs,
|
||||
interiorWallDefaults, setThickness, thicknessOf, interiorLayout, ensureSeamWalls,
|
||||
computeParts, allWallPieces, computeTotals, validateState, fromUnitDesign, normalize
|
||||
|
||||
@@ -218,7 +218,7 @@ window.HousePdf = (() => {
|
||||
const e = totals.roofEdge, L = totals.lamps;
|
||||
const rows = [];
|
||||
if (e.fasciaLengthM > 0) {
|
||||
rows.push(['Boeidelen', `${fmt(e.fasciaLengthM, 2)} m, ${e.fasciaHeight} mm hoog, overstek ${e.overhang} mm`]);
|
||||
rows.push(['Boeidelen', `${fmt(e.fasciaLengthM, 2)} m, ${e.fasciaHeight} mm hoog, overstek ${e.overhang} mm${e.fasciaColor ? ', ' + e.fasciaColor.toLowerCase() : ''}`]);
|
||||
rows.push(['Onderplaat boeideel', fmt(e.soffitM2, 2) + ' m2']);
|
||||
}
|
||||
if (e.trimLengthM > 0) rows.push(['Daktrim', `${fmt(e.trimLengthM, 2)} m, ${e.trimHeight} mm hoog`]);
|
||||
|
||||
+109
-7
@@ -169,6 +169,7 @@ function mountHouseEditor(container, callbacks) {
|
||||
lockWidth: 'Binnen een woning volgt de wandbreedte uit de vloer van de unit.',
|
||||
lockSpacing: SPACING_LOCK_MSG,
|
||||
lockCladding: 'De richting van de gevelbekleding is (voorlopig) globaal: Overzicht, niets geselecteerd.',
|
||||
lockExterior: 'De gevelbekleding volgt de woning (Overzicht › Gevel); per wand aan te passen via de wand in de plattegrond.',
|
||||
seamCuts: wl.cutsU,
|
||||
lamps: [].concat(...wl.pieces.map(p => p.lamps.map(l => ({ u: p.u0 + l.u, y: l.y }))))
|
||||
});
|
||||
@@ -313,6 +314,7 @@ function mountHouseEditor(container, callbacks) {
|
||||
ground.visible = false;
|
||||
scene.add(ground);
|
||||
const wallMaterials = WallModel.createMaterials();
|
||||
const SPAANPLAAT_COLOR = wallMaterials.spaanplaatMaterial.color.getHex(); // kaal (niet geschilderd)
|
||||
const floorMaterials = FloorModel.createMaterials();
|
||||
const roofMaterials = RoofModel.createMaterials();
|
||||
const lightingMaterials = LightingModel.createMaterials();
|
||||
@@ -328,6 +330,46 @@ function mountHouseEditor(container, callbacks) {
|
||||
let vloerplan = false;
|
||||
const cutPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0);
|
||||
renderer.localClippingEnabled = true;
|
||||
const capMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
|
||||
// Egaal witte afdekking van de snede: per wandstuk een vlak op de
|
||||
// snijhoogte over de volle dikte (incl. doek/latwerk of steen aan de
|
||||
// buitenzijde en de hoekdoorloop), met de openingen die door de snede
|
||||
// lopen uitgespaard. cutAbs = snijhoogte (mm, absoluut).
|
||||
function buildPlanCaps(cutAbs) {
|
||||
const toM = 0.001, g = new THREE.Group();
|
||||
const S = WallModel.STONE;
|
||||
HouseModel.allWallPieces(parts).forEach(p => {
|
||||
const y = cutAbs - p.place.floorTop; // snijhoogte in het stuk
|
||||
if (y <= 0 || y >= WallModel.topLine(p.config).maxY) return;
|
||||
const W = p.place.width, T = p.place.thickness;
|
||||
// Buitenlagen op de snijhoogte (alleen buitenwanden: skinExt/normal).
|
||||
let out = 0;
|
||||
if (p.place.normal && p.buitenMateriaal !== 'geen') {
|
||||
const tr = p.trasraam;
|
||||
out = tr && y < tr.top ? (WallModel.isStoneFinish(tr.finish) ? S.THICK_MM : 0) : WallModel.claddingOffset(p.buitenMateriaal, p.stone);
|
||||
}
|
||||
const e = p.skinExt || {};
|
||||
const xL = -W / 2 - (e.left > 0 ? e.left + out : 0), xR = W / 2 + (e.right > 0 ? e.right + out : 0);
|
||||
const gaps = p.config.openings.filter(op => op.y < y && op.y + op.h > y)
|
||||
.map(op => [-W / 2 + op.x, -W / 2 + op.x + op.w]).sort((a, b) => a[0] - b[0]);
|
||||
const spans = [];
|
||||
let cur = xL;
|
||||
gaps.forEach(([a, b]) => { if (a > cur) spans.push([cur, Math.min(a, xR)]); cur = Math.max(cur, b); });
|
||||
if (xR > cur) spans.push([cur, xR]);
|
||||
const z0 = -T / 2, z1 = T / 2 + out;
|
||||
spans.filter(([a, b]) => b - a > 1).forEach(([a, b]) => {
|
||||
const m = new THREE.Mesh(new THREE.PlaneGeometry((b - a) * toM, (z1 - z0) * toM), capMaterial);
|
||||
m.rotation.x = -Math.PI / 2;
|
||||
m.position.set((a + b) / 2 * toM, (y - 0.5) * toM, (z0 + z1) / 2 * toM);
|
||||
const holder = new THREE.Group();
|
||||
holder.rotation.y = p.place.rotY;
|
||||
holder.position.set(p.place.cx * toM, p.place.floorTop * toM, p.place.cz * toM);
|
||||
holder.add(m);
|
||||
g.add(holder);
|
||||
});
|
||||
});
|
||||
return g;
|
||||
}
|
||||
let fullRender = false;
|
||||
let skyMode = Skybox.getMode();
|
||||
const skyEnv = Skybox.createEnvironment(scene);
|
||||
@@ -690,13 +732,23 @@ function mountHouseEditor(container, callbacks) {
|
||||
sel('hp-att-side', 'Zijde', HouseModel.SIDE_LABELS, a.side) +
|
||||
sel('hp-att-align', 'Uitlijning', HouseModel.ALIGN_LABELS[axis], a.align) +
|
||||
num('hp-att-offset', 'Verschuiving', a.offset || 0);
|
||||
// Dakbedekking per unit; riet alleen bij een hellend dak.
|
||||
const pu = parts && parts.units.find(x => x.id === u.id);
|
||||
const shapeType = pu ? pu.roof.shape.type : 'plat';
|
||||
const covOpts = Object.fromEntries(Object.entries(RoofModel.COVERINGS)
|
||||
.filter(([, c]) => !c.sloped || shapeType !== 'plat').map(([k, c]) => [k, c.label]));
|
||||
const covInfo = (k) => { const c = RoofModel.COVERINGS[k]; return `${c.thickMM} mm, ${String(c.kgM2).replace('.', ',')} kg/m²`; };
|
||||
const bedekking = sel('hp-roof-cover', 'Bedekking', covOpts, RoofModel.coveringFor(u.roof.covering, shapeType), {
|
||||
hint: shapeType === 'plat' ? 'Riet kan alleen bij een hellend dak (lessenaar/zadel)' : covInfo(RoofModel.coveringFor(u.roof.covering, shapeType)) + '; boeideel schuift mee omhoog'
|
||||
});
|
||||
const dak = toggle('hp-roof-follow', 'Maat volgt vloer', u.roof.followFloor) +
|
||||
(u.roof.followFloor ? '' :
|
||||
num('hp-roof-w', 'Breedte', u.roof.deck.floor.width, { hint: 'West–oost' }) +
|
||||
num('hp-roof-d', 'Diepte', u.roof.deck.floor.depth, { hint: 'Noord–zuid' }) +
|
||||
num('hp-roof-ox', 'Verschuiving oost', u.roof.offsetX || 0) +
|
||||
num('hp-roof-oz', 'Verschuiving zuid', u.roof.offsetZ || 0)) +
|
||||
toggle('hp-fascia', 'Boeidelen', u.roof.fascia !== false, { hint: 'Uit = daktrim van 80 mm' });
|
||||
toggle('hp-fascia', 'Boeidelen', u.roof.fascia !== false, { hint: 'Uit = daktrim van 80 mm' }) +
|
||||
bedekking;
|
||||
|
||||
propsEl.innerHTML = warn + head('box', `Unit ${u.id}`, `${f.width} × ${f.depth} mm`) +
|
||||
UI.tabs('unit', [
|
||||
@@ -720,6 +772,7 @@ function mountHouseEditor(container, callbacks) {
|
||||
UI.onSel('hp-att-align', v => { u.attach.align = v; }, rerender);
|
||||
UI.onToggle('hp-roof-follow', v => { u.roof.followFloor = v; }, rerender);
|
||||
UI.onToggle('hp-fascia', v => { u.roof.fascia = v; }, rerender);
|
||||
UI.onSel('hp-roof-cover', v => { u.roof.covering = v; }, rerender);
|
||||
UI.onNum('hp-roof-w', v => { if (v >= 500) u.roof.deck.floor.width = v; }, after);
|
||||
UI.onNum('hp-roof-d', v => { if (v >= 500) u.roof.deck.floor.depth = v; }, after);
|
||||
UI.onNum('hp-roof-ox', v => { u.roof.offsetX = v; }, after);
|
||||
@@ -744,11 +797,25 @@ function mountHouseEditor(container, callbacks) {
|
||||
{ hint: `Automatisch: ${lt.endOffset} mm uit de hoek, alleen op vrije gevel van min. ${lt.minWallWidth} mm. Naast openingen: ${lt.openingOffset} mm naast elke opening.` }) +
|
||||
UI.specCard('Op dit wandstuk', 'lightbulb', [['Gevellampen', piece.lamps.length, 'st.']])
|
||||
: info('Gevellampen staan uit. Kies bij Woning › Verlichting het type "Gevellampen".');
|
||||
const su = unitById(pu.id);
|
||||
const fc = HouseModel.facadeFor(state, su, wl.side);
|
||||
const ov = su.facade.walls[wl.side] || null;
|
||||
const f0 = state.settings.facade;
|
||||
const woningLabel = `Woning (${HouseModel.FACADE_TYPES[f0.type]}${f0.type === 'steen' ? ' ' + f0.stone : ''})`;
|
||||
const gevel = sel('hw-facade', 'Type', Object.assign({ woning: woningLabel }, HouseModel.FACADE_TYPES), ov ? ov.type : 'woning',
|
||||
{ hint: 'Geldt voor de hele zijde van deze unit' }) +
|
||||
(ov && ov.type === 'steen' ? stonePicker('hw-facade-stone', 'Steen', fc.stone, `Steenstrip op isolatieblok, samen ${WallModel.STONE.THICK_MM} mm dik`) : '');
|
||||
propsEl.innerHTML = warn + head('bricks', piece.element, piece.label) +
|
||||
cols(UI.specCard('Wandstuk', 'rulers', rows), section('Gevellampen', 'lightbulb', lampen)) +
|
||||
cols(UI.specCard('Wandstuk', 'rulers', rows), section('Gevelbekleding', 'grip-horizontal', gevel)) +
|
||||
section('Gevellampen', 'lightbulb', lampen) +
|
||||
(wl.dropped.length ? UI.warn(`${wl.dropped.length} opening(en) van deze zijde vallen in een naad en vervallen.`) : '') +
|
||||
actions(button('Wand bewerken', 'pencil-square', `data-go="wall:${pu.id}:${wl.side}"`));
|
||||
UI.onSel('hw-light', v => { unitById(pu.id).lighting.walls[wl.side] = v; }, rerender);
|
||||
UI.onSel('hw-facade', v => {
|
||||
if (v === 'woning') delete su.facade.walls[wl.side];
|
||||
else su.facade.walls[wl.side] = v === 'steen' ? { type: v, stone: fc.stone } : { type: v };
|
||||
}, rerender);
|
||||
UI.onSel('hw-facade-stone', v => { su.facade.walls[wl.side].stone = v; }, rerender);
|
||||
}
|
||||
|
||||
function renderIwProps(iw, warn) {
|
||||
@@ -799,6 +866,13 @@ function mountHouseEditor(container, callbacks) {
|
||||
|
||||
// Woning-instellingen (niets geselecteerd): alles wat voor de hele woning
|
||||
// geldt, in tabbladen.
|
||||
// Keuzes voor de vormgeving van het trasraam: plaat of een steen.
|
||||
const stoneOptions = Object.assign({}, ...WallModel.TRASRAAM_STONES.map(c => ({ [c]: `Steen ${c}` })));
|
||||
const trasraamFinishes = Object.assign({ cementplaat: 'Cementplaat' }, stoneOptions);
|
||||
// Steenkeuze met voorvertoning.
|
||||
const stonePicker = (id, label, value, hint) => sel(id, label, stoneOptions, value, { hint }) +
|
||||
`<img class="hs-stone-preview" src="${WallModel.stoneThumbUrl(value, 128)}" alt="${value}">`;
|
||||
|
||||
function renderHouseProps(warn) {
|
||||
const st = state.settings, sp = st.spacing, r = st.roof, lt = st.lighting;
|
||||
const pos = { positive: true };
|
||||
@@ -822,17 +896,34 @@ function mountHouseEditor(container, callbacks) {
|
||||
actions(button('Unit toevoegen', 'plus-lg', 'data-action="add-unit"'), button('Binnenwand tekenen', 'pencil', 'data-action="draw-iw"'),
|
||||
button('Projectgegevens', 'card-text', 'data-action="project"')) },
|
||||
{ key: 'gevel', label: 'Gevel', icon: 'bricks', html: cols(
|
||||
section('Gevelbekleding', 'grip-horizontal', sel('hs-cladding', 'Richting', WallModel.CLADDING_DIRECTIONS, st.cladding, { hint: 'WEO/Novalit, voorlopig voor alle buitenwanden' })) +
|
||||
section('Trasraam', 'bricks', toggle('hs-trasraam', 'Trasraam', st.trasraam.enabled, { hint: 'Cement-gebonden plaat onderaan de gevel' }) +
|
||||
(st.trasraam.enabled ? num('hs-trasraam-h', 'Hoogte', st.trasraam.height, { step: 10, hint: 'Vanaf bovenkant vloer' }) : '')),
|
||||
section('Gevelbekleding', 'grip-horizontal',
|
||||
sel('hs-facade', 'Type', HouseModel.FACADE_TYPES, st.facade.type, { hint: 'Voor alle buitenwanden; per wand aan te passen (klik een wand in de plattegrond)' }) +
|
||||
(st.facade.type === 'steen'
|
||||
? stonePicker('hs-facade-stone', 'Steen', st.facade.stone, `Steenstrip op isolatieblok, samen ${WallModel.STONE.THICK_MM} mm dik`)
|
||||
: sel('hs-cladding', 'Richting', WallModel.CLADDING_DIRECTIONS, st.cladding, { hint: 'Rabat op vochtdoek en kruislat' }))) +
|
||||
section('Trasraam', 'bricks', toggle('hs-trasraam', 'Trasraam', st.trasraam.enabled, { hint: 'Strook onderaan de gevel, onder rabat én steen (eigen steen mogelijk)' }) +
|
||||
(st.trasraam.enabled ? num('hs-trasraam-h', 'Hoogte', st.trasraam.height, { step: 10, hint: 'Vanaf bovenkant vloer' }) +
|
||||
sel('hs-trasraam-finish', 'Vormgeving', trasraamFinishes, st.trasraam.finish, {
|
||||
hint: WallModel.isStoneFinish(st.trasraam.finish)
|
||||
? `Steenstrip op isolatieblok, samen ${WallModel.STONE.THICK_MM} mm dik`
|
||||
: 'Cement-gebonden plaat'
|
||||
}) +
|
||||
(WallModel.isStoneFinish(st.trasraam.finish)
|
||||
? `<img class="hs-stone-preview" src="${WallModel.stoneThumbUrl(st.trasraam.finish, 128)}" alt="${st.trasraam.finish}">` : '') : '')),
|
||||
section('Sandwichpanelen', 'bounding-box', toggle('hs-sandwich', 'Rondom woning', st.sandwich,
|
||||
{ hint: `${SandwichModel.PANEL_WIDTH_MM} × ${SandwichModel.PANEL_LENGTH_MM} × ${SandwichModel.THICK_MM} mm, vanaf bovenkant vloer ${SandwichModel.DEPTH_MM} mm naar beneden` })) +
|
||||
section('Maaiveld', 'tree', num('hs-ground', 'Onder bovenkant vloer', st.groundLevel, { step: 10, hint: 'Hoogte van de tegelvloer in de 3D-weergave bij Dag/Nacht' }))) },
|
||||
{ key: 'binnen', label: 'Binnen', icon: 'palette', html: cols(
|
||||
section('Wanden', 'palette', toggle('hs-int-paint', 'Geschilderd', st.interiorPaint, { hint: 'Alle wanden aan de kamerzijde; egaal, zonder plaatnaden' }) +
|
||||
(st.interiorPaint ? UI.color('hs-int-color', 'Verfkleur', st.interiorColor, { hint: 'Voorlopig één kleur voor de hele woning' }) : '')),
|
||||
section('Vloer', 'grid-3x3', sel('hs-floor-finish', 'Afwerking', Object.fromEntries(Object.entries(FloorModel.FINISHES).map(([k, f]) => [k, f.label])), st.floorFinish, { hint: 'Alle vloeren (voorlopig één keuze)' }) +
|
||||
info('Wanden uit: kale spaanplaat met plaatnaden.'))) },
|
||||
{ key: 'constructie', label: 'Regelwerk', icon: 'columns-gap', html: cols(
|
||||
section('Buitenwanden', 'bricks', num('hs-ext-min', 'Afstand min.', sp.exterior.min, { step: 10, hint: 'Hart-op-hart stijlen' }) + num('hs-ext-max', 'Afstand max.', sp.exterior.max, { step: 10, hint: 'Hart-op-hart stijlen' })),
|
||||
section('Binnenwanden', 'distribute-horizontal', num('hs-int-min', 'Afstand min.', sp.interior.min, { step: 10, hint: 'Hart-op-hart stijlen' }) + num('hs-int-max', 'Afstand max.', sp.interior.max, { step: 10, hint: 'Hart-op-hart stijlen' }))) },
|
||||
{ key: 'dak', label: 'Dakrand', icon: 'house', html: cols(
|
||||
section('Boeideel', 'border-outer', num('hs-overhang', 'Overstek', r.overhang, { step: 10, hint: `${RoofModel.OVERHANG_MIN} – ${RoofModel.OVERHANG_MAX} mm` }) + num('hs-fascia-h', 'Hoogte', r.fasciaHeight, { step: 10 })),
|
||||
section('Boeideel', 'border-outer', num('hs-overhang', 'Overstek', r.overhang, { step: 10, hint: `${RoofModel.OVERHANG_MIN} – ${RoofModel.OVERHANG_MAX} mm` }) + num('hs-fascia-h', 'Hoogte', r.fasciaHeight, { step: 10 }) +
|
||||
sel('hs-fascia-color', 'Kleur', Object.fromEntries(Object.entries(RoofModel.FASCIA_COLORS).map(([k, c]) => [k, c.label])), r.fasciaColor, { hint: 'Egale kleur' })),
|
||||
section('Daktrim', 'border-top', num('hs-trim-h', 'Hoogte', r.trimHeight, { step: 5, hint: 'Bij units zonder boeidelen' }))) },
|
||||
{ key: 'verlichting', label: 'Verlichting', icon: 'lightbulb', html:
|
||||
cols(section('Buiten', 'sun', sel('hs-light-mode', 'Type', LightingModel.MODES, lt.mode)),
|
||||
@@ -844,9 +935,15 @@ function mountHouseEditor(container, callbacks) {
|
||||
]);
|
||||
|
||||
UI.onSel('hs-cladding', v => { st.cladding = v; }, rerender);
|
||||
UI.onSel('hs-facade', v => { st.facade.type = v; }, rerender);
|
||||
UI.onColor('hs-int-color', v => { st.interiorColor = v; }, after);
|
||||
UI.onToggle('hs-int-paint', v => { st.interiorPaint = v; }, rerender);
|
||||
UI.onSel('hs-floor-finish', v => { st.floorFinish = v; }, rerender);
|
||||
UI.onSel('hs-facade-stone', v => { st.facade.stone = v; }, rerender);
|
||||
UI.onToggle('hs-trasraam', v => { st.trasraam.enabled = v; }, rerender);
|
||||
UI.onToggle('hs-sandwich', v => { st.sandwich = v; }, rerender);
|
||||
UI.onNum('hs-trasraam-h', v => { st.trasraam.height = v; }, after, pos);
|
||||
UI.onSel('hs-trasraam-finish', v => { st.trasraam.finish = v; }, rerender);
|
||||
UI.onNum('hs-ground', v => { if (v >= 0) st.groundLevel = v; }, after);
|
||||
UI.onNum('hs-ext-min', v => { sp.exterior.min = v; }, after, pos);
|
||||
UI.onNum('hs-ext-max', v => { sp.exterior.max = v; }, after, pos);
|
||||
@@ -854,6 +951,7 @@ function mountHouseEditor(container, callbacks) {
|
||||
UI.onNum('hs-int-max', v => { sp.interior.max = v; }, after, pos);
|
||||
UI.onNum('hs-overhang', v => { r.overhang = Math.min(RoofModel.OVERHANG_MAX, Math.max(RoofModel.OVERHANG_MIN, v)); }, after, pos);
|
||||
UI.onNum('hs-fascia-h', v => { r.fasciaHeight = v; }, after, pos);
|
||||
UI.onSel('hs-fascia-color', v => { r.fasciaColor = v; }, rerender);
|
||||
UI.onNum('hs-trim-h', v => { r.trimHeight = v; }, after, pos);
|
||||
UI.onNum('hs-spot-min', v => { r.spotMin = v; }, after, pos);
|
||||
UI.onNum('hs-spot-max', v => { r.spotMax = v; }, after, pos);
|
||||
@@ -917,6 +1015,8 @@ function mountHouseEditor(container, callbacks) {
|
||||
|
||||
// --- Alles opnieuw doorrekenen ---
|
||||
function refresh(opts) {
|
||||
// Binnenzijde geschilderd: de (gedeelde) spaanplaat krijgt de verfkleur.
|
||||
wallMaterials.spaanplaatMaterial.color.set(state.settings.interiorPaint ? state.settings.interiorColor : SPAANPLAAT_COLOR);
|
||||
parts = HouseModel.computeParts(state, { wallMaterials, floorMaterials, roofMaterials, lightingMaterials, sandwichMaterials, beplatingVisible, lampenVisible });
|
||||
const totals = HouseModel.computeTotals(parts);
|
||||
|
||||
@@ -944,6 +1044,7 @@ function mountHouseEditor(container, callbacks) {
|
||||
});
|
||||
houseGroup.add(p.group);
|
||||
});
|
||||
if (vloerplan) houseGroup.add(buildPlanCaps(cutPlane.constant / toM));
|
||||
if (dakVisible && !vloerplan) {
|
||||
parts.units.forEach(u => {
|
||||
u.roof.group.position.set(u.roof.rect.cx * toM, u.roof.base * toM, u.roof.rect.cz * toM);
|
||||
@@ -960,7 +1061,8 @@ function mountHouseEditor(container, callbacks) {
|
||||
const cx = (bx0 + bx1) / 2 * toM, cz = (bz0 + bz1) / 2 * toM;
|
||||
grid.position.set(cx, 0, cz);
|
||||
// Maaiveld: settings.groundLevel onder het loopvlak (bovenkant vloer).
|
||||
const groundY = (Math.max(...parts.units.map(u => u.layout.floorTop)) - state.settings.groundLevel) * toM;
|
||||
// 2 mm extra lager: bij maaiveld 0 niet in hetzelfde vlak als de OSB (flikkeren).
|
||||
const groundY = (Math.max(...parts.units.map(u => u.layout.floorTop)) - state.settings.groundLevel - 2) * toM;
|
||||
skyEnv.fitTo(houseGroup, groundY);
|
||||
ground.position.set(cx, groundY - 0.002, cz);
|
||||
applyRenderMode();
|
||||
|
||||
Reference in New Issue
Block a user