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
+1 -1
View File
@@ -4,7 +4,7 @@
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
* 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.
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 (lampsOn && parts && lampenVisible) {
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);
}
}
+50 -19
View File
@@ -188,9 +188,20 @@ window.LightingModel = (() => {
const spots = [];
if (s.mode === 'spots') {
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 };
}
@@ -212,7 +223,7 @@ window.LightingModel = (() => {
// Binnenspots per unit. units = HouseModel-parts.units (rect, walls, roof.base),
// 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) {
const s = normalizeSettings(settings);
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 };
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;
@@ -277,31 +288,51 @@ window.LightingModel = (() => {
return group;
}
// Volle render: elke gevellamp (per lichtrichting) en elke boeideel-spot
// wordt een echte SpotLight. Gevellampen staan net buiten het gevelvlak en
// schijnen langs de gevel omhoog/omlaag; spots schijnen recht naar beneden.
// spots = [{ x, z }] (mm), spotY = hoogte onderkant boeideel (mm);
// indoor = binnenspots [{ x, z, y }] (mm). Buitenlampen 15% minder fel dan
// eerst (was overdag te heftig).
function buildLights3D(lamps, spots, spotY, indoor) {
// Render: elke gevellamp (per lichtrichting), boeideel-spot en binnenspot
// wordt een echte SpotLight. Echte schaduw per lamp is te zwaar (één
// schaduwkaart per lamp; de browser kan er maar ~16), dus licht door muren
// wordt geometrisch voorkomen:
// - buitenlampen en boeideel-spots zijn half hun bundelhoek naar buiten
// gekanteld: de binnenrand van de bundel loopt verticaal langs de gevel;
// - 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 fo = (o && o.outdoorFactor !== undefined) ? o.outdoorFactor : 1;
const group = new THREE.Group();
const add = (x, y, z, dirY, intensity, angle, distance) => {
const light = new THREE.SpotLight(0xffd9a0, intensity, distance, angle, 0.6, 1.5);
// Spot op (x, y, z) die naar (x + tx, y + ty, z + tz) schijnt.
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.target.position.set(x, y + dirY * 2, z);
light.target.position.set(x + tx, y + ty, z + tz);
group.add(light);
group.add(light.target);
};
const tilt = Math.tan(OUTDOOR.angle); // bundelas half de bundelhoek naar buiten
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;
if (l.direction !== 'up') add(x, y - 0.09, z, -1, 2.55, 0.55, 4.5);
if (l.direction !== 'down') 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 !== '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 { 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 };
})();