diff --git a/README.md b/README.md index a33215c..1c063ac 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,12 @@ assets/ logo (preloader) + bestemmingsmap voor toekomstige unitrand bij een naad); spots te dicht bij een binnenwand vervallen. Woning-instellingen › Verlichting › Binnenspots. In 3D (filter Lampen), 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: kruislat-latwerk op vochtdoek (richting gevelbekleding globaal), lampen op de afgewerkte gevel (+52 mm), logo-watermerk op het doek, trasraam (cement- diff --git a/js/app.js b/js/app.js index 9486465..0d4eeb5 100644 --- a/js/app.js +++ b/js/app.js @@ -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 = []; diff --git a/js/house/house.js b/js/house/house.js index adb2786..4e87727 100644 --- a/js/house/house.js +++ b/js/house/house.js @@ -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); } } diff --git a/js/lighting/lighting-model.js b/js/lighting/lighting-model.js index 9bacec5..daac5c8 100644 --- a/js/lighting/lighting-model.js +++ b/js/lighting/lighting-model.js @@ -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 }; })();