v0.4.8: gevellampen tellen vanaf de zichtbare afgewerkte hoek

- bedekte gevelstukken (wandstuk in de naad, binnenhoek L) afgetrokken via voetafdruk
- uiteinden verlengd met afwerking (+ skinExt bij tussenpassende wanden)
- groupFacadeRuns accepteert overlap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-09-30 12:54:41 +02:00
co-authored by Claude Opus 5.5
parent 448c2b1289
commit f163f4deb1
4 changed files with 35 additions and 4 deletions
+7
View File
@@ -173,6 +173,13 @@ assets/ logo (preloader) + bestemmingsmap voor toekomstige
unitrand bij een naad); spots te dicht bij een binnenwand vervallen. unitrand bij een naad); spots te dicht bij een binnenwand vervallen.
Woning-instellingen › Verlichting › Binnenspots. In 3D (filter Lampen), Woning-instellingen › Verlichting › Binnenspots. In 3D (filter Lampen),
plattegrond (open cirkeltje), render en totalen. plattegrond (open cirkeltje), render en totalen.
- **Startpunt gevellampen (v0.4.8).** Een gevel telt vanaf de zichtbare,
afgewerkte hoek: elk uiteinde + afwerking van de haakse wand (52 mm bij
vochtdoek), plus de bedekte kopse kant (skinExt) bij tussenpassende wanden.
Delen van een gevel achter een andere wand (bijv. een wandstuk dat de naad
in loopt om een binnenhoek te sluiten, zoals A-W2 bij een L) worden
afgetrokken via de voetafdruk van die wand; de gevel begint dan bij diens
afgewerkte buitenkant.
- **Lichtbronnen (v0.4.7).** Geen echte schaduw per lamp (één schaduwkaart - **Lichtbronnen (v0.4.7).** Geen echte schaduw per lamp (één schaduwkaart
per lamp overschrijdt de browserlimiet); licht door muren wordt geometrisch per lamp overschrijdt de browserlimiet); licht door muren wordt geometrisch
voorkomen: buitenlampen en boeideel-spots zijn half hun bundelhoek naar voorkomen: buitenlampen en boeideel-spots zijn half hun bundelhoek naar
+1 -1
View File
@@ -4,7 +4,7 @@
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
* Zie LICENTIE.md. * Zie LICENTIE.md.
*/ */
const APP_VERSION = '0.4.7'; const APP_VERSION = '0.4.8';
// 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang. // 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang.
let tabs = []; let tabs = [];
+2 -2
View File
@@ -329,7 +329,7 @@ function offsetRectilinearLoop(pts, d) {
} }
// Gevels: groepeert lijnstukken in de plattegrond tot doorlopende gevels // Gevels: groepeert lijnstukken in de plattegrond tot doorlopende gevels
// (zelfde as + normaal + gevelvlak, aansluitend). segs: [{ axis ('x'|'z'), // (zelfde as + normaal + gevelvlak, aansluitend of overlappend). segs: [{ axis ('x'|'z'),
// normal {x, z}, line (positie op de andere as), s0, s1 (s0 < s1) }, ...]. // normal {x, z}, line (positie op de andere as), s0, s1 (s0 < s1) }, ...].
// Geeft [{ axis, normal, line, s0, s1, segs (op s0 gesorteerd) }]. // Geeft [{ axis, normal, line, s0, s1, segs (op s0 gesorteerd) }].
// Gebruikt door de woning (latwerk/doek-patroon) en de buitenverlichting. // Gebruikt door de woning (latwerk/doek-patroon) en de buitenverlichting.
@@ -338,7 +338,7 @@ function groupFacadeRuns(segs, opts) {
const runs = []; const runs = [];
segs.slice().sort((a, b) => a.s0 - b.s0).forEach(sg => { segs.slice().sort((a, b) => a.s0 - b.s0).forEach(sg => {
const run = runs.find(r => r.axis === sg.axis && Math.abs(r.normal.x - sg.normal.x) < 1e-6 && Math.abs(r.normal.z - sg.normal.z) < 1e-6 && const run = runs.find(r => r.axis === sg.axis && Math.abs(r.normal.x - sg.normal.x) < 1e-6 && Math.abs(r.normal.z - sg.normal.z) < 1e-6 &&
Math.abs(r.line - sg.line) < lineTol && Math.abs(r.s1 - sg.s0) < gapTol); Math.abs(r.line - sg.line) < lineTol && sg.s0 <= r.s1 + gapTol); // aansluitend of overlappend
if (run) { run.segs.push(sg); run.s1 = Math.max(run.s1, sg.s1); } if (run) { run.segs.push(sg); run.s1 = Math.max(run.s1, sg.s1); }
else runs.push(Object.assign({}, sg, { s0: sg.s0, s1: sg.s1, segs: [sg] })); else runs.push(Object.assign({}, sg, { s0: sg.s0, s1: sg.s1, segs: [sg] }));
}); });
+25 -1
View File
@@ -126,9 +126,23 @@ window.LightingModel = (() => {
const axis = Math.abs(normal.z) > 0.5 ? 'x' : 'z'; const axis = Math.abs(normal.z) > 0.5 ? 'x' : 'z';
const perp = axis === 'x' ? 'z' : 'x'; const perp = axis === 'x' ? 'z' : 'x';
const q0 = UnitModel.localToWorld(p.place, -W / 2, T / 2), q1 = UnitModel.localToWorld(p.place, W / 2, T / 2); const q0 = UnitModel.localToWorld(p.place, -W / 2, T / 2), q1 = UnitModel.localToWorld(p.place, W / 2, T / 2);
// Gevel loopt tot de afgewerkte hoek, zodat endOffset altijd vanaf de
// zichtbare hoek telt: elk uiteinde + afwerking (latwerk + bekleding van
// de haakse wand die eroverheen loopt), en waar dit stuk de kopse kant
// van een doorlopende wand bedekt (skinExt) ook nog die wanddikte.
// Binnenhoeken en naden: zie de voetafdrukken in 3 en de overlap in 2.
const c = WallModel.claddingOffset(p.buitenMateriaal), e = p.skinExt || {};
const extL = (e.left > 0 ? e.left : 0) + c, extR = (e.right > 0 ? e.right : 0) + c;
const up = q1[axis] >= q0[axis];
// Voetafdruk van dit stuk incl. afwerking (om bedekte delen van andere
// gevels af te trekken, zie 3).
const corners = [[-W / 2, -p.place.thickness / 2], [W / 2, -p.place.thickness / 2], [-W / 2, T / 2], [W / 2, T / 2]]
.map(([lx, lz]) => UnitModel.localToWorld(p.place, lx, lz));
segs.push({ segs.push({
piece: p, normal, axis, perp, line: q0[perp], piece: p, normal, axis, perp, line: q0[perp],
sAt0: q0[axis], sAtW: q1[axis], s0: Math.min(q0[axis], q1[axis]), s1: Math.max(q0[axis], q1[axis]), sAt0: q0[axis], sAtW: q1[axis],
s0: up ? q0[axis] - extL : q1[axis] - extR, s1: up ? q1[axis] + extR : q0[axis] + extL,
foot: { x0: Math.min(...corners.map(q => q.x)), x1: Math.max(...corners.map(q => q.x)), z0: Math.min(...corners.map(q => q.z)), z1: Math.max(...corners.map(q => q.z)) },
override: overrides[u.id][w.side] || 'auto' override: overrides[u.id][w.side] || 'auto'
}); });
}))); })));
@@ -153,6 +167,16 @@ window.LightingModel = (() => {
runs.forEach(run => { runs.forEach(run => {
const blocked = []; const blocked = [];
const pos = []; // lampposities langs deze gevel (wereld-mm) const pos = []; // lampposities langs deze gevel (wereld-mm)
// Delen van deze gevel die achter een andere wand liggen (bijv. het
// stuk van een wand dat de naad in loopt om een binnenhoek te sluiten)
// zijn geen zichtbare gevel: de gevel begint pas bij de afgewerkte
// buitenkant van die andere wand.
segs.filter(o => !run.segs.includes(o)).forEach(o => {
const f = o.foot;
const pMin = run.axis === 'x' ? f.z0 : f.x0, pMax = run.axis === 'x' ? f.z1 : f.x1;
const aMin = run.axis === 'x' ? f.x0 : f.z0, aMax = run.axis === 'x' ? f.x1 : f.z1;
if (run.line > pMin + 1 && run.line < pMax - 1 && aMax > run.s0 && aMin < run.s1) blocked.push([Math.max(run.s0, aMin), Math.min(run.s1, aMax)]);
});
run.segs.forEach(sg => { run.segs.forEach(sg => {
const cfg = sg.piece.config, sW = cfg.studWidth; const cfg = sg.piece.config, sW = cfg.studWidth;
const low = Math.min(cfg.wallHeightLeft, cfg.wallHeightRight) < H + 100; const low = Math.min(cfg.wallHeightLeft, cfg.wallHeightRight) < H + 100;