v0.4.7: lampen niet meer door muren, buiten overdag gedimd, binnenspots feller

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-09-30 12:47:08 +02:00
co-authored by Claude Opus 5.5
parent 5ecf3e0c2a
commit 448c2b1289
4 changed files with 60 additions and 21 deletions
+6
View File
@@ -173,6 +173,12 @@ 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.
- **Lichtbronnen (v0.4.7).** Geen echte schaduw per lamp (één schaduwkaart
per lamp overschrijdt de browserlimiet); licht door muren wordt geometrisch
voorkomen: buitenlampen en boeideel-spots zijn half hun bundelhoek naar
buiten gekanteld (boeideel: normaal van de dakrand, op hoeken diagonaal),
binnenspots reiken hard tot net voorbij de vloer. Buitenlampen overdag op
35% (LightingModel.DAY_OUTDOOR_FACTOR), 's nachts 100%; binnenspots feller.
- **Mijlpaal v0.4 — gevelopbouw.** Samenvatting van de v0.3.11–0.3.19-iteraties: - **Mijlpaal v0.4 — gevelopbouw.** Samenvatting van de v0.3.11–0.3.19-iteraties:
kruislat-latwerk op vochtdoek (richting gevelbekleding globaal), lampen op de kruislat-latwerk op vochtdoek (richting gevelbekleding globaal), lampen op de
afgewerkte gevel (+52 mm), logo-watermerk op het doek, trasraam (cement- afgewerkte gevel (+52 mm), logo-watermerk op het doek, trasraam (cement-
+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.6'; const APP_VERSION = '0.4.7';
// 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 = [];
+3 -1
View File
@@ -336,7 +336,9 @@ function mountHouseEditor(container, callbacks) {
if (parts && parts.lightingGroup) parts.lightingGroup.traverse(o => { if (o.userData.beam) o.visible = !lampsOn; }); if (parts && parts.lightingGroup) parts.lightingGroup.traverse(o => { if (o.userData.beam) o.visible = !lampsOn; });
if (lampsOn && parts && lampenVisible) { if (lampsOn && parts && lampenVisible) {
const edge = dakVisible ? parts.roofEdge : null; const edge = dakVisible ? parts.roofEdge : null;
lightsGroup = LightingModel.buildLights3D(parts.lighting.lamps, edge ? edge.spots : [], edge ? edge.top - edge.fasciaHeight : 0, parts.lighting.indoor); lightsGroup = LightingModel.buildLights3D(parts.lighting.lamps, edge ? edge.spots : [], edge ? edge.top - edge.fasciaHeight : 0, parts.lighting.indoor, {
outdoorFactor: skyMode === 'night' ? 1 : LightingModel.DAY_OUTDOOR_FACTOR
});
scene.add(lightsGroup); scene.add(lightsGroup);
} }
} }
+50 -19
View File
@@ -188,9 +188,20 @@ window.LightingModel = (() => {
const spots = []; const spots = [];
if (s.mode === 'spots') { if (s.mode === 'spots') {
const rs = RoofModel.normalizeSettings(roofSettings); const rs = RoofModel.normalizeSettings(roofSettings);
edge.segments.filter(sg => sg.fascia).forEach(sg => spotsBetween(sg.spotA, sg.spotB, rs.spotMin, rs.spotMax, spots)); edge.segments.filter(sg => sg.fascia).forEach(sg => {
const from = spots.length;
spotsBetween(sg.spotA, sg.spotB, rs.spotMin, rs.spotMax, spots);
for (let k = from; k < spots.length; k++) spots[k].normal = { x: sg.normal.x, z: sg.normal.z };
});
} }
edge.spots = spots.filter((p, i) => spots.findIndex(q => Math.hypot(q.x - p.x, q.z - p.z) < 5) === i); // Dubbele spots (hoeken) samenvoegen; de buitenrichting wordt dan de
// som van beide gevels (diagonaal naar buiten).
edge.spots = [];
spots.forEach(p => {
const q = edge.spots.find(e => Math.hypot(e.x - p.x, e.z - p.z) < 5);
if (!q) edge.spots.push({ x: p.x, z: p.z, normal: p.normal });
else if (p.normal && q.normal) q.normal = { x: q.normal.x + p.normal.x, z: q.normal.z + p.normal.z };
});
} }
return { lamps }; return { lamps };
} }
@@ -212,7 +223,7 @@ window.LightingModel = (() => {
// Binnenspots per unit. units = HouseModel-parts.units (rect, walls, roof.base), // Binnenspots per unit. units = HouseModel-parts.units (rect, walls, roof.base),
// interior = parts.interior (binnenwanden met place). Geeft // interior = parts.interior (binnenwanden met place). Geeft
// [{ unitId, x, z, y }] (mm, y = onderkant plafond). // [{ unitId, x, z, y, floorY }] (mm, y = onderkant plafond, floorY = loopvlak).
function computeIndoor(settings, units, interior) { function computeIndoor(settings, units, interior) {
const s = normalizeSettings(settings); const s = normalizeSettings(settings);
if (!s.indoor) return []; if (!s.indoor) return [];
@@ -229,7 +240,7 @@ window.LightingModel = (() => {
const a = { x: iw.place.cx - half * c, z: iw.place.cz + half * sn }, b = { x: iw.place.cx + half * c, z: iw.place.cz - half * sn }; const a = { x: iw.place.cx - half * c, z: iw.place.cz + half * sn }, b = { x: iw.place.cx + half * c, z: iw.place.cz - half * sn };
return distToSegment({ x, z }, a, b) < iw.place.thickness / 2 + off; return distToSegment({ x, z }, a, b) < iw.place.thickness / 2 + off;
}); });
if (!tooClose) out.push({ unitId: u.id, x, z, y: u.roof.base }); if (!tooClose) out.push({ unitId: u.id, x, z, y: u.roof.base, floorY: u.layout.floorTop });
})); }));
}); });
return out; return out;
@@ -277,31 +288,51 @@ window.LightingModel = (() => {
return group; return group;
} }
// Volle render: elke gevellamp (per lichtrichting) en elke boeideel-spot // Render: elke gevellamp (per lichtrichting), boeideel-spot en binnenspot
// wordt een echte SpotLight. Gevellampen staan net buiten het gevelvlak en // wordt een echte SpotLight. Echte schaduw per lamp is te zwaar (één
// schijnen langs de gevel omhoog/omlaag; spots schijnen recht naar beneden. // schaduwkaart per lamp; de browser kan er maar ~16), dus licht door muren
// spots = [{ x, z }] (mm), spotY = hoogte onderkant boeideel (mm); // wordt geometrisch voorkomen:
// indoor = binnenspots [{ x, z, y }] (mm). Buitenlampen 15% minder fel dan // - buitenlampen en boeideel-spots zijn half hun bundelhoek naar buiten
// eerst (was overdag te heftig). // gekanteld: de binnenrand van de bundel loopt verticaal langs de gevel;
function buildLights3D(lamps, spots, spotY, indoor) { // - binnenspots hebben een harde reikwijdte net voorbij de vloer
// (INDOOR_REACH), zodat ze niet tot de grond buiten reiken.
// spots = [{ x, z }] (mm, boeideel), spotY = onderkant boeideel (mm),
// indoor = [{ x, z, y, floorY }] (mm). o.outdoorFactor schaalt de buiten-
// lampen (overdag lager: dan vallen ze nauwelijks op).
const OUTDOOR = { lamp: 2.0, spot: 1.6, angle: 0.5 };
const INDOOR = { intensity: 4.0, angle: 1.0, reach: 1.12, decay: 0.6 };
const DAY_OUTDOOR_FACTOR = 0.35;
function buildLights3D(lamps, spots, spotY, indoor, o) {
const toM = 0.001; const toM = 0.001;
const fo = (o && o.outdoorFactor !== undefined) ? o.outdoorFactor : 1;
const group = new THREE.Group(); const group = new THREE.Group();
const add = (x, y, z, dirY, intensity, angle, distance) => { // Spot op (x, y, z) die naar (x + tx, y + ty, z + tz) schijnt.
const light = new THREE.SpotLight(0xffd9a0, intensity, distance, angle, 0.6, 1.5); const add = (x, y, z, tx, ty, tz, intensity, angle, distance, decay) => {
const light = new THREE.SpotLight(0xffd9a0, intensity, distance, angle, 0.6, decay);
light.position.set(x, y, z); light.position.set(x, y, z);
light.target.position.set(x, y + dirY * 2, z); light.target.position.set(x + tx, y + ty, z + tz);
group.add(light); group.add(light);
group.add(light.target); group.add(light.target);
}; };
const tilt = Math.tan(OUTDOOR.angle); // bundelas half de bundelhoek naar buiten
lamps.forEach(l => { lamps.forEach(l => {
const x = l.x * toM + l.normal.x * 0.12, z = l.z * toM + l.normal.z * 0.12, y = l.y * toM; const x = l.x * toM + l.normal.x * 0.12, z = l.z * toM + l.normal.z * 0.12, y = l.y * toM;
if (l.direction !== 'up') add(x, y - 0.09, z, -1, 2.55, 0.55, 4.5); const nx = l.normal.x * tilt, nz = l.normal.z * tilt;
if (l.direction !== 'down') add(x, y + 0.09, z, 1, 2.55, 0.55, 4.5); if (l.direction !== 'up') add(x, y - 0.09, z, nx, -1, nz, OUTDOOR.lamp * fo, OUTDOOR.angle, 4.5, 1.5);
if (l.direction !== 'down') add(x, y + 0.09, z, nx, 1, nz, OUTDOOR.lamp * fo, OUTDOOR.angle, 4.5, 1.5);
});
// Boeideel-spots: naar buiten gekanteld (normaal van de dakrand; op een
// hoek diagonaal).
(spots || []).forEach(p => {
const n = p.normal || { x: 0, z: 0 }, len = Math.hypot(n.x, n.z) || 1;
add(p.x * toM, spotY * toM - 0.02, p.z * toM, n.x / len * tilt, -1, n.z / len * tilt, OUTDOOR.spot * fo, OUTDOOR.angle, 5.5, 1.5);
});
(indoor || []).forEach(p => {
const drop = (p.y - p.floorY) * toM; // plafond -> loopvlak
add(p.x * toM, p.y * toM - 0.02, p.z * toM, 0, -1, 0, INDOOR.intensity, INDOOR.angle, drop * INDOOR.reach, INDOOR.decay);
}); });
(spots || []).forEach(p => add(p.x * toM, spotY * toM - 0.02, p.z * toM, -1, 2.1, 0.6, 5.5));
(indoor || []).forEach(p => add(p.x * toM, p.y * toM - 0.02, p.z * toM, -1, 1.2, 0.7, 4));
return group; return group;
} }
return { DEFAULT_SETTINGS, MODES, DIRECTIONS, WALL_OVERRIDES, createDefaultUnitLighting, normalizeSettings, compute, computeIndoor, createMaterials, build3D, buildLights3D }; return { DEFAULT_SETTINGS, MODES, DIRECTIONS, WALL_OVERRIDES, createDefaultUnitLighting, DAY_OUTDOOR_FACTOR, normalizeSettings, compute, computeIndoor, createMaterials, build3D, buildLights3D };
})(); })();