diff --git a/.gitignore b/.gitignore index 5cb53ad..4bebd89 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ examples/ # Lokale exports *.pdf .DS_Store +# Oude grastextuur (niet meer gebruikt), lokaal bewaard +assets/grass*.jpg diff --git a/README.md b/README.md index 894586f..b08c2cf 100644 --- a/README.md +++ b/README.md @@ -157,15 +157,16 @@ assets/ logo (preloader) + bestemmingsmap voor toekomstige Koppeling, Dak). Labels: zelfstandig naamwoord, geen dubbele punt, uitleg in een hint (i-icoon) i.p.v. tussen haakjes. Sneltoets T wisselt in drie stappen: standaard -> 3D groot -> alleen de render -> standaard. -- **Lucht en gras (v0.4.3).** Knop "Lucht" in elke 3D-werkbalk (core/skybox.js, - keuze onthouden). Het panorama (assets/skybox.webp) wordt op de grond - geprojecteerd (port van three.js GroundedSkybox), met scherp gras - (assets/grass.jpg, v0.4.4: kleurgecorrigeerd naar het gemiddelde grasgroen van het panorama onder de camera, belichting gecompenseerd) in een cirkel van 12 m - dat daarna overvloeit in het panorama. De zon werpt schaduw op het gras en - de camera kan niet onder de grond. Woning: het gras ligt op het maaiveld - (woning-instelling, standaard 350 mm onder bovenkant vloer), zodat het - ingegraven deel van vloer en sandwichpanelen eronder verdwijnt. Wand/vloer: - gras op de onderkant van het element. De volle render blijft een avondscène. +- **Omgeving en render (v0.4.5).** Knop "Lucht" in elke 3D-werkbalk wisselt + geen -> dag -> nacht (core/skybox.js, keuze onthouden). Het panorama + (assets/skybox.webp / skybox-night.webp) wordt op de grond geprojecteerd + (port van three.js GroundedSkybox); onder het object ligt een ronde + tegelvloer (assets/tiles.jpg), groot genoeg voor het hele object, met een + witte opstaande rand van 100 x 25 mm. Vloer en rand vangen licht en + schaduw. Woning: de tegelvloer ligt op het maaiveld (woning-instelling, + standaard 350 mm onder bovenkant vloer). Knop "Render" (camera): lampen en + spots als echte lichtbronnen in het gewone daglicht; Nacht: omgevingslicht + 15%, lampen altijd aan (100%). - **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/assets/README.txt b/assets/README.txt index f6b3c64..36b2dc1 100644 --- a/assets/README.txt +++ b/assets/README.txt @@ -1,4 +1,5 @@ Assets voor de 3D-weergave en de PDF: - logo.svg / logo-animated.svg logo (splash, about); PDF-logo staat als data in js/core/logo-data.js -- skybox.webp equirectangulair panorama (6000x3000) voor "Lucht" (core/skybox.js) -- grass.jpg grastextuur, kleurgecorrigeerd naar het panorama-gras (bron: "grass (kopie).jpg", niet in git) +- skybox.webp equirectangulair panorama dag (4096x2048), "Lucht: Dag" (core/skybox.js) +- skybox-night.webp equirectangulair panorama nacht (4096x2048), "Lucht: Nacht" +- tiles.jpg tegeltextuur (1024x1024) voor de ronde vloer onder het object diff --git a/assets/grass.jpg b/assets/grass.jpg deleted file mode 100644 index 460d4ba..0000000 Binary files a/assets/grass.jpg and /dev/null differ diff --git a/assets/skybox-night.webp b/assets/skybox-night.webp new file mode 100644 index 0000000..b12dd4c Binary files /dev/null and b/assets/skybox-night.webp differ diff --git a/assets/skybox.webp b/assets/skybox.webp index bbbebdd..5372f5a 100644 Binary files a/assets/skybox.webp and b/assets/skybox.webp differ diff --git a/assets/tiles.jpg b/assets/tiles.jpg new file mode 100644 index 0000000..1708499 Binary files /dev/null and b/assets/tiles.jpg differ diff --git a/js/app.js b/js/app.js index ce8e62a..84bb2b5 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.4'; +const APP_VERSION = '0.4.5'; // 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang. let tabs = []; diff --git a/js/core/skybox.js b/js/core/skybox.js index e086bed..0f0c42f 100644 --- a/js/core/skybox.js +++ b/js/core/skybox.js @@ -5,11 +5,12 @@ * Zie LICENTIE.md. */ // ============================================================ -// core/skybox.js — omgeving voor de 3D-weergaven ("Lucht"-knop): een -// panorama (assets/skybox.webp) dat op de grond geprojecteerd wordt, zodat -// het object echt op het gras staat, plus scherp gras (assets/grass.jpg) in -// een cirkel rond het object dat naar buiten toe overvloeit in het gras van -// het panorama. Het gras vangt ook de schaduw op. +// core/skybox.js — omgeving voor de 3D-weergaven ("Lucht": geen/dag/nacht). +// Een panorama (assets/skybox.webp of skybox-night.webp) wordt op de grond +// geprojecteerd, zodat het object op de grond staat; onder het object ligt +// een ronde tegelvloer (assets/tiles.jpg) met een witte opstaande rand, groot +// genoeg voor het hele object. Vloer en rand reageren op het licht en vangen +// de schaduw op. Nacht: omgevingslicht op NIGHT_FACTOR (lampen blijven 100%). // // Grondprojectie: port van three.js GroundedSkybox (r157+, MIT; wij draaien // r128): een binnenstebuiten bol waarvan de onderhelft plat gedrukt wordt op @@ -19,33 +20,25 @@ // ============================================================ window.Skybox = (() => { - const PREF_KEY = 'tectum.skybox'; - const SKY_URL = 'assets/skybox.webp'; - const GRASS_URL = 'assets/grass.jpg'; - const CAMERA_HEIGHT_M = 1.7; // hoogte van de panoramacamera boven het gras + const PREF_KEY = 'tectum.sky'; + const MODES = ['none', 'day', 'night']; + const SKY_URLS = { day: 'assets/skybox.webp', night: 'assets/skybox-night.webp' }; + const TILES_URL = 'assets/tiles.jpg'; + const CAMERA_HEIGHT_M = 1.7; // hoogte van de panoramacamera boven de grond const RADIUS_M = 60; // straal van de omgevingsbol - const GRASS_RADIUS_M = 12; // tot hier is het eigen gras volledig zichtbaar - const GRASS_FADE_M = 6; // daarna vloeit het over in het panorama - const GRASS_TILE_M = 2.0; // breedte van één grastegel (hoogte volgt de beeldverhouding) - // assets/grass.jpg is kleurgecorrigeerd naar het gemiddelde grasgroen van het - // panorama recht onder de camera (bron: "grass (kopie).jpg"). Het panorama - // is onbelicht; het gras wordt belicht door ambient (0.6) + zon (0.8, vanuit - // SUN_OFFSET). Deze factor maakt belicht gras gelijk aan de textuurkleur. + const TILE_M = 2.4; // één tegeltextuur (1024 px) beslaat zoveel meter + const MARGIN_M = 1.5; // vrije rand tussen object en opstaande rand + const RIM_HEIGHT_M = 0.10, RIM_THICK_M = 0.025; + const NIGHT_FACTOR = 0.15; // omgevingslicht 's nachts const SUN_OFFSET = { x: 8, y: 14, z: 6 }; - const GRASS_SHADE = 1 / (0.6 + 0.8 * SUN_OFFSET.y / Math.hypot(SUN_OFFSET.x, SUN_OFFSET.y, SUN_OFFSET.z)); - let loading = null; - function loadTextures() { - if (loading) return loading; - const loader = new THREE.TextureLoader(); - const load = (url) => new Promise((resolve, reject) => loader.load(url, resolve, undefined, reject)); - loading = Promise.all([load(SKY_URL), load(GRASS_URL)]).then(([sky, grass]) => { - grass.wrapS = grass.wrapT = THREE.RepeatWrapping; - grass.anisotropy = 8; - return { sky, grass }; - }); - loading.catch(() => { loading = null; }); - return loading; + const cache = {}; + function loadTexture(url) { + if (!cache[url]) { + cache[url] = new Promise((resolve, reject) => new THREE.TextureLoader().load(url, resolve, undefined, reject)); + cache[url].catch(() => { delete cache[url]; }); + } + return cache[url]; } function buildSky(map) { @@ -65,98 +58,145 @@ window.Skybox = (() => { } pos.needsUpdate = true; const mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ map, depthWrite: false })); - mesh.position.y = h - 0.01; // vlakke vloer net onder het eigen gras + mesh.position.y = h - 0.01; // vlakke vloer net onder de tegelvloer mesh.renderOrder = -1; return mesh; } - function buildGrass(map) { - const R = GRASS_RADIUS_M + GRASS_FADE_M; + // Ronde tegelvloer (straal r) + witte opstaande rand erlangs. + function buildTerrace(map, r) { + const group = new THREE.Group(); const tex = map.clone(); tex.needsUpdate = true; - const aspect = (map.image && map.image.width) ? map.image.height / map.image.width : 1; - tex.repeat.set(2 * R / GRASS_TILE_M, 2 * R / (GRASS_TILE_M * aspect)); - const material = new THREE.MeshLambertMaterial({ - map: tex, color: new THREE.Color(GRASS_SHADE, GRASS_SHADE, GRASS_SHADE), transparent: true, depthWrite: false - }); - // Randvervaging op afstand tot het midden (eigen varying: de UV's zijn - // al herhaald voor de textuur). - material.onBeforeCompile = (shader) => { - shader.vertexShader = 'varying float vGrassR;\n' + shader.vertexShader.replace( - '#include ', '#include \nvGrassR = length(transformed.xy);'); - shader.fragmentShader = 'varying float vGrassR;\n' + shader.fragmentShader.replace( - '#include ', - `#include \ngl_FragColor.a *= 1.0 - smoothstep(${GRASS_RADIUS_M.toFixed(1)}, ${R.toFixed(1)}, vGrassR);`); - }; - const mesh = new THREE.Mesh(new THREE.CircleGeometry(R, 96), material); - mesh.rotation.x = -Math.PI / 2; - mesh.receiveShadow = true; - return mesh; + tex.wrapS = tex.wrapT = THREE.RepeatWrapping; + tex.anisotropy = 8; + tex.repeat.set(2 * r / TILE_M, 2 * r / TILE_M); + const floor = new THREE.Mesh(new THREE.CircleGeometry(r, 128), new THREE.MeshStandardMaterial({ map: tex, roughness: 0.85 })); + floor.rotation.x = -Math.PI / 2; + floor.receiveShadow = true; + group.add(floor); + + const ring = new THREE.Shape(); + ring.absarc(0, 0, r + RIM_THICK_M, 0, Math.PI * 2, false); + const hole = new THREE.Path(); + hole.absarc(0, 0, r, 0, Math.PI * 2, true); + ring.holes.push(hole); + const rim = new THREE.Mesh( + new THREE.ExtrudeGeometry(ring, { depth: RIM_HEIGHT_M, bevelEnabled: false, curveSegments: 128 }), + new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.5 }) + ); + rim.rotation.x = -Math.PI / 2; // extrusie omhoog + rim.castShadow = rim.receiveShadow = true; + group.add(rim); + return group; } - // Omgeving voor één scene. Geeft { group, setPlacement(x, groundY, z) }: - // het midden (m) waar het object staat en de hoogte van het maaiveld (m). + // Omgeving voor één scene: { group, setMode(mode), fitTo(object3D, groundY) }. function createEnvironment(scene) { const group = new THREE.Group(); group.visible = false; scene.add(group); - loadTextures().then(({ sky, grass }) => { - group.add(buildSky(sky)); - group.add(buildGrass(grass)); - }).catch(() => console.warn('Tectum: lucht/gras niet geladen (draait de app via een webserver?)')); + const skies = {}; + let terrace = null, terraceR = 0, tilesTex = null, mode = 'none'; + + function showSky() { + Object.keys(skies).forEach(k => { skies[k].visible = k === mode; }); + if (mode === 'none' || skies[mode]) return; + const wanted = mode; + loadTexture(SKY_URLS[wanted]).then(tex => { + if (!skies[wanted]) { skies[wanted] = buildSky(tex); group.add(skies[wanted]); } + Object.keys(skies).forEach(k => { skies[k].visible = k === mode; }); + }).catch(() => console.warn('Tectum: lucht niet geladen (draait de app via een webserver?)')); + } + function setTerrace(r) { + if (terrace && Math.abs(r - terraceR) < 0.05) return; + terraceR = r; + const build = () => { + if (terrace) { group.remove(terrace); terrace.traverse(o => { if (o.geometry) o.geometry.dispose(); }); } + terrace = buildTerrace(tilesTex, terraceR); + group.add(terrace); + }; + if (tilesTex) build(); + else loadTexture(TILES_URL).then(t => { tilesTex = t; build(); }).catch(() => {}); + } + return { group, - setPlacement(x, groundY, z) { group.position.set(x, groundY, z); } + setMode(m) { mode = m; group.visible = m !== 'none'; showSky(); }, + // Midden en straal uit de omhullende van het object (horizontaal), vloer op groundY (m). + fitTo(object3D, groundY) { + if (!object3D) return; + const box = new THREE.Box3().setFromObject(object3D); + if (box.isEmpty()) return; + const c = box.getCenter(new THREE.Vector3()); + group.position.set(c.x, groundY, c.z); + setTerrace(Math.hypot(box.max.x - box.min.x, box.max.z - box.min.z) / 2 + MARGIN_M); + } }; } - function isOn() { - try { return localStorage.getItem(PREF_KEY) === '1'; } catch (e) { return false; } + function getMode() { + try { + const v = localStorage.getItem(PREF_KEY); + if (MODES.includes(v)) return v; + return localStorage.getItem('tectum.skybox') === '1' ? 'day' : 'none'; // oude aan/uit-voorkeur + } catch (e) { return 'none'; } } - function remember(on) { - try { localStorage.setItem(PREF_KEY, on ? '1' : '0'); } catch (e) { /* geen opslag: alleen deze sessie */ } + function remember(m) { + try { localStorage.setItem(PREF_KEY, m); } catch (e) { /* geen opslag: alleen deze sessie */ } } - // Lucht aan/uit voor een weergave: omgeving tonen, raster verbergen, camera - // niet onder de grond, zon met schaduw op het gras. - // o: { env, grid, controls, sun, shadowRoot (Object3D die schaduw werpt) } - function apply(on, o) { - o.env.group.visible = on; + const lightFactor = (m) => m === 'night' ? NIGHT_FACTOR : 1; + + // Omgeving toepassen: lucht/vloer, raster, camera niet onder de grond, zon + // met schaduw, en het licht (nacht: NIGHT_FACTOR). + // o: { env, grid, controls, sun, ambient, base: { ambient, sun }, shadowRoot } + function apply(m, o) { + const on = m !== 'none'; + o.env.setMode(m); if (o.grid) o.grid.visible = !on; if (o.controls) o.controls.maxPolarAngle = on ? Math.PI / 2 - 0.03 : Math.PI - 0.001; + const f = lightFactor(m); + if (o.ambient && o.base) o.ambient.intensity = o.base.ambient * f; + if (o.sun && o.base) o.sun.intensity = o.base.sun * f; if (o.sun) { o.sun.castShadow = on; if (on) { - const cam = o.sun.shadow.camera; - cam.left = cam.bottom = -GRASS_RADIUS_M; cam.right = cam.top = GRASS_RADIUS_M; - cam.near = 0.5; cam.far = 60; + const p = o.env.group.position, cam = o.sun.shadow.camera; + cam.left = cam.bottom = -20; cam.right = cam.top = 20; + cam.near = 0.5; cam.far = 80; cam.updateProjectionMatrix(); o.sun.shadow.mapSize.set(2048, 2048); o.sun.shadow.bias = -0.0005; - o.sun.position.set(o.env.group.position.x + SUN_OFFSET.x, o.env.group.position.y + SUN_OFFSET.y, o.env.group.position.z + SUN_OFFSET.z); - o.sun.target.position.copy(o.env.group.position); + o.sun.position.set(p.x + SUN_OFFSET.x, p.y + SUN_OFFSET.y, p.z + SUN_OFFSET.z); + o.sun.target.position.copy(p); o.sun.target.updateMatrixWorld(); } } - if (o.shadowRoot) o.shadowRoot.traverse(m => { if (m.isMesh) m.castShadow = on; }); + if (o.shadowRoot) o.shadowRoot.traverse(obj => { if (obj.isMesh) obj.castShadow = on; }); } - // "Lucht"-knop in een 3D-werkbalk. onChange(on) bij elke klik. + // Knop in een 3D-werkbalk die door geen -> dag -> nacht wisselt. + const LABELS = { none: ['cloud', 'Lucht'], day: ['sun', 'Dag'], night: ['moon-stars', 'Nacht'] }; function createToggle(toolbar, onChange) { const btn = document.createElement('button'); btn.type = 'button'; - btn.className = 'btn-3d-toggle' + (isOn() ? ' active' : ''); - btn.title = 'Lucht en gras als omgeving'; - btn.innerHTML = 'Lucht'; + btn.title = 'Omgeving: geen / dag / nacht'; + let m = getMode(); + const sync = () => { + btn.className = 'btn-3d-toggle' + (m !== 'none' ? ' active' : ''); + btn.innerHTML = `${LABELS[m][1]}`; + }; btn.addEventListener('click', () => { - const on = !btn.classList.contains('active'); - btn.classList.toggle('active', on); - remember(on); - onChange(on); + m = MODES[(MODES.indexOf(m) + 1) % MODES.length]; + remember(m); + sync(); + onChange(m); }); + sync(); toolbar.appendChild(btn); return btn; } - return { CAMERA_HEIGHT_M, createEnvironment, apply, isOn, createToggle }; + return { CAMERA_HEIGHT_M, NIGHT_FACTOR, createEnvironment, apply, getMode, lightFactor, createToggle }; })(); diff --git a/js/floor/floor.js b/js/floor/floor.js index 9e6ecd0..f929f99 100644 --- a/js/floor/floor.js +++ b/js/floor/floor.js @@ -700,7 +700,8 @@ function mountFloorEditor(container, callbacks) { controls.update(); } - scene.add(new THREE.AmbientLight(0xffffff, 0.6)); + const floorAmbient = new THREE.AmbientLight(0xffffff, 0.6); + scene.add(floorAmbient); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(5, 8, 5); dirLight.castShadow = true; @@ -727,11 +728,14 @@ function mountFloorEditor(container, callbacks) { isolatieVisible = v.isolatie; update3DScene(); }); - // Lucht en gras (core/skybox.js); de vloer staat met zijn onderkant op het gras. - let skyOn = Skybox.isOn(); + // Omgeving (core/skybox.js: geen/dag/nacht); de vloer staat op de tegelvloer. + let skyMode = Skybox.getMode(); const skyEnv = Skybox.createEnvironment(scene); - const applySky = () => Skybox.apply(skyOn, { env: skyEnv, grid, controls, sun: dirLight, shadowRoot: floorGroup3D }); - Skybox.createToggle(document.getElementById('floor-toolbar-3d'), (on) => { skyOn = on; applySky(); }); + const applySky = () => { + skyEnv.fitTo(floorGroup3D, 0); + Skybox.apply(skyMode, { env: skyEnv, grid, controls, sun: dirLight, ambient: floorAmbient, base: { ambient: 0.6, sun: 0.8 }, shadowRoot: floorGroup3D }); + }; + Skybox.createToggle(document.getElementById('floor-toolbar-3d'), (m) => { skyMode = m; applySky(); }); function update3DScene() { if (floorGroup3D) scene.remove(floorGroup3D); diff --git a/js/house/house.js b/js/house/house.js index b585cd8..aa1cb0d 100644 --- a/js/house/house.js +++ b/js/house/house.js @@ -84,7 +84,7 @@ const HOUSE_OVERVIEW_TEMPLATE = `
3D
- +
@@ -301,7 +301,7 @@ function mountHouseEditor(container, callbacks) { let cameraFitted = CameraMemory.restore(overviewCamKey, camera, controls); let beplatingVisible = true, isolatieVisible = true, lampenVisible = true; let fullRender = false; - let skyOn = Skybox.isOn(); + let skyMode = Skybox.getMode(); const skyEnv = Skybox.createEnvironment(scene); // Weergave-filter (core/view-filter.js). Beplating = wandplaten, vloer- @@ -319,30 +319,28 @@ function mountHouseEditor(container, callbacks) { refresh({ keepProps: true }); }); - // Volle render: avondscène, lampen en spots als echte lichtbronnen. + // Omgeving (core/skybox.js: geen/dag/nacht) en Render. Render = lampen en + // spots als echte lichtbronnen, in het licht van de gekozen omgeving; + // Nacht = omgevingslicht op 15% en de lampen altijd aan (100%). De + // tegelvloer ligt op het maaiveld, dus het ingegraven deel van vloer en + // sandwichpanelen verdwijnt eronder. let lightsGroup = null; - // Lucht en gras (core/skybox.js): alleen in de gewone weergave (de volle - // render is een avondscène). Het gras ligt op het maaiveld, dus het - // ingegraven deel van vloer en sandwichpanelen verdwijnt eronder. function applyRenderMode() { - const sky = skyOn && !fullRender; - scene.background = new THREE.Color(fullRender ? 0x0b0f18 : 0x1a1a1a); - Skybox.apply(sky, { env: skyEnv, controls, sun: dirLight, shadowRoot: houseGroup }); - ambient.intensity = fullRender ? 0.1 : 0.6; - dirLight.intensity = fullRender ? 0.12 : 0.8; - ground.visible = fullRender; - grid.visible = !fullRender && !sky; + const lampsOn = fullRender || skyMode === 'night'; + scene.background = new THREE.Color(0x1a1a1a); + Skybox.apply(skyMode, { env: skyEnv, controls, sun: dirLight, ambient, base: { ambient: 0.6, sun: 0.8 }, shadowRoot: houseGroup }); + ground.visible = fullRender && skyMode === 'none'; // zonder omgeving: vlak waarop het lamplicht valt + grid.visible = skyMode === 'none' && !fullRender; if (lightsGroup) { scene.remove(lightsGroup); lightsGroup = null; } - // De nep-lichtbundels (kegels) alleen in de snelle weergave; in de - // volle render schijnt er echt licht. - if (parts && parts.lightingGroup) parts.lightingGroup.traverse(o => { if (o.userData.beam) o.visible = !fullRender; }); - if (fullRender && parts && lampenVisible) { + // De nep-lichtbundels (kegels) alleen zonder echte lampen. + 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); scene.add(lightsGroup); } } - Skybox.createToggle(document.getElementById('ho-toolbar-3d'), (on) => { skyOn = on; applyRenderMode(); }); + Skybox.createToggle(document.getElementById('ho-toolbar-3d'), (m) => { skyMode = m; applyRenderMode(); }); document.getElementById('ho-btn-render').addEventListener('click', (e) => { fullRender = !fullRender; e.currentTarget.classList.toggle('active', fullRender); @@ -873,7 +871,7 @@ function mountHouseEditor(container, callbacks) { 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; - skyEnv.setPlacement(cx, groundY, cz); + skyEnv.fitTo(houseGroup, groundY); ground.position.set(cx, groundY - 0.002, cz); applyRenderMode(); if (!cameraFitted) { diff --git a/js/wall/wall.js b/js/wall/wall.js index 189381d..a77bb0f 100644 --- a/js/wall/wall.js +++ b/js/wall/wall.js @@ -1197,7 +1197,8 @@ function mountWallEditor(container, callbacks) { controls.update(); } - scene.add(new THREE.AmbientLight(0xffffff, 0.6)); + const ambient = new THREE.AmbientLight(0xffffff, 0.6); + scene.add(ambient); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(5, 8, 5); dirLight.castShadow = true; @@ -1242,11 +1243,14 @@ function mountWallEditor(container, callbacks) { isolatieVisible = v.isolatie; update3DScene(); }); - // Lucht en gras (core/skybox.js); de wand staat met zijn onderkant op het gras. - let skyOn = Skybox.isOn(); + // Omgeving (core/skybox.js: geen/dag/nacht); de wand staat op de tegelvloer. + let skyMode = Skybox.getMode(); const skyEnv = Skybox.createEnvironment(scene); - const applySky = () => Skybox.apply(skyOn, { env: skyEnv, grid, controls, sun: dirLight, shadowRoot: wallGroup3D }); - Skybox.createToggle(document.getElementById('wall-toolbar-3d'), (on) => { skyOn = on; applySky(); }); + const applySky = () => { + skyEnv.fitTo(wallGroup3D, 0); + Skybox.apply(skyMode, { env: skyEnv, grid, controls, sun: dirLight, ambient, base: { ambient: 0.6, sun: 0.8 }, shadowRoot: wallGroup3D }); + }; + Skybox.createToggle(document.getElementById('wall-toolbar-3d'), (m) => { skyMode = m; applySky(); }); // 3D-opbouw + volume-boekhouding komen uit WallModel.build3D (gedeeld // met de unit-editor).