v0.4.2: iconen gerepareerd, lucht-achtergrond (skybox) met toggle

- bootstrap-icons: @font-face correct ingebed (selector .bi::before was stuk)
- core/skybox.js + assets/skybox-data.js (lazy), knop Lucht in alle 3D-weergaven
- woning: met lucht aan raster en sandwichpanelen onzichtbaar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-09-30 11:58:07 +02:00
co-authored by Claude Opus 5.5
parent 3591fad220
commit 3a4b9608c0
10 changed files with 121 additions and 6 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.1';
const APP_VERSION = '0.4.2';
// 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang.
let tabs = [];
+79
View File
@@ -0,0 +1,79 @@
/*!
* Tectum
* Copyright © 2026 Machiel Heinen.
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
* Zie LICENTIE.md.
*/
// ============================================================
// core/skybox.js — lucht-achtergrond (equirectangulair panorama) voor de
// 3D-weergaven, met een "Lucht"-knop in de 3D-werkbalk. De afbeelding
// (assets/skybox-data.js, gegenereerd uit assets/skybox.webp) wordt pas bij
// de eerste keer aanzetten geladen en daarna gedeeld. De keuze aan/uit wordt
// onthouden (localStorage, alleen gemak).
// Plain script (geen ES-module).
// ============================================================
window.Skybox = (() => {
const PREF_KEY = 'tectum.skybox';
let texturePromise = null;
function load() {
if (texturePromise) return texturePromise;
texturePromise = new Promise((resolve, reject) => {
const fromData = () => {
const img = new Image();
img.onload = () => {
const tex = new THREE.Texture(img);
tex.mapping = THREE.EquirectangularReflectionMapping;
tex.needsUpdate = true;
resolve(tex);
};
img.onerror = reject;
img.src = window.TECTUM_SKYBOX;
};
if (window.TECTUM_SKYBOX) return fromData();
const s = document.createElement('script');
s.src = 'assets/skybox-data.js';
s.onload = fromData;
s.onerror = reject;
document.head.appendChild(s);
});
texturePromise.catch(() => { texturePromise = null; });
return texturePromise;
}
function isOn() {
try { return localStorage.getItem(PREF_KEY) === '1'; } catch (e) { return false; }
}
function remember(on) {
try { localStorage.setItem(PREF_KEY, on ? '1' : '0'); } catch (e) { /* geen opslag: alleen deze sessie */ }
}
// Zet de achtergrond van `scene`: lucht als on, anders fallbackColor (hex).
// wanted() wordt na het laden nog eens gevraagd (knop intussen uitgezet?).
function apply(scene, on, fallbackColor, wanted) {
if (!on) { scene.background = new THREE.Color(fallbackColor); return; }
load().then(tex => { if (!wanted || wanted()) scene.background = tex; })
.catch(() => { scene.background = new THREE.Color(fallbackColor); });
}
// "Lucht"-knop in een 3D-werkbalk. onChange(on) bij elke klik (en niet bij
// aanmaken: de aanroeper leest de begintoestand met isOn()).
function createToggle(toolbar, onChange) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn-3d-toggle' + (isOn() ? ' active' : '');
btn.title = 'Lucht als achtergrond';
btn.innerHTML = '<i class="bi bi-cloud-sun"></i>Lucht';
btn.addEventListener('click', () => {
const on = !btn.classList.contains('active');
btn.classList.toggle('active', on);
remember(on);
onChange(on);
});
toolbar.appendChild(btn);
return btn;
}
return { load, isOn, apply, createToggle };
})();
+7 -1
View File
@@ -706,7 +706,8 @@ function mountFloorEditor(container, callbacks) {
dirLight.castShadow = true;
scene.add(dirLight);
scene.add(new THREE.GridHelper(10, 20, 0x444444, 0x222222));
const grid = new THREE.GridHelper(10, 20, 0x444444, 0x222222);
scene.add(grid);
const floorMaterials = roofMode ? RoofModel.createMaterials() : FloorModel.createMaterials();
@@ -726,6 +727,11 @@ function mountFloorEditor(container, callbacks) {
isolatieVisible = v.isolatie;
update3DScene();
});
// Lucht-achtergrond (core/skybox.js).
let skyOn = Skybox.isOn();
const applySky = () => { grid.visible = !skyOn; Skybox.apply(scene, skyOn, 0x1a1a1a, () => skyOn); };
Skybox.createToggle(document.getElementById('floor-toolbar-3d'), (on) => { skyOn = on; applySky(); });
applySky();
function update3DScene() {
if (floorGroup3D) scene.remove(floorGroup3D);
+9 -2
View File
@@ -301,6 +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();
// Weergave-filter (core/view-filter.js). Beplating = wandplaten, vloer-
// en plafondplaten, dakleer en boeideel/daktrim.
@@ -319,12 +320,17 @@ function mountHouseEditor(container, callbacks) {
// Volle render: avondscène, lampen en spots als echte lichtbronnen.
let lightsGroup = null;
// Lucht (core/skybox.js): alleen in de gewone weergave (de volle render is
// een avondscène). De sandwichpanelen zitten in de grond: met lucht aan
// zijn ze niet zichtbaar.
function applyRenderMode() {
scene.background = new THREE.Color(fullRender ? 0x0b0f18 : 0x1a1a1a);
if (fullRender) scene.background = new THREE.Color(0x0b0f18);
else Skybox.apply(scene, skyOn, 0x1a1a1a, () => skyOn && !fullRender);
if (parts && parts.sandwichGroup) parts.sandwichGroup.visible = beplatingVisible && !(skyOn && !fullRender);
ambient.intensity = fullRender ? 0.1 : 0.6;
dirLight.intensity = fullRender ? 0.12 : 0.8;
ground.visible = fullRender;
grid.visible = !fullRender;
grid.visible = !fullRender && !skyOn;
if (lightsGroup) { scene.remove(lightsGroup); lightsGroup = null; }
// De nep-lichtbundels (kegels) alleen in de snelle weergave; in de
// volle render schijnt er echt licht.
@@ -335,6 +341,7 @@ function mountHouseEditor(container, callbacks) {
scene.add(lightsGroup);
}
}
Skybox.createToggle(document.getElementById('ho-toolbar-3d'), (on) => { skyOn = on; applyRenderMode(); });
document.getElementById('ho-btn-render').addEventListener('click', (e) => {
fullRender = !fullRender;
e.currentTarget.classList.toggle('active', fullRender);
+7 -1
View File
@@ -1203,7 +1203,8 @@ function mountWallEditor(container, callbacks) {
dirLight.castShadow = true;
scene.add(dirLight);
scene.add(new THREE.GridHelper(10, 20, 0x444444, 0x222222));
const grid = new THREE.GridHelper(10, 20, 0x444444, 0x222222);
scene.add(grid);
const wallMaterials = WallModel.createMaterials();
@@ -1241,6 +1242,11 @@ function mountWallEditor(container, callbacks) {
isolatieVisible = v.isolatie;
update3DScene();
});
// Lucht-achtergrond (core/skybox.js).
let skyOn = Skybox.isOn();
const applySky = () => { grid.visible = !skyOn; Skybox.apply(scene, skyOn, 0x1a1a1a, () => skyOn); };
Skybox.createToggle(document.getElementById('wall-toolbar-3d'), (on) => { skyOn = on; applySky(); });
applySky();
// 3D-opbouw + volume-boekhouding komen uit WallModel.build3D (gedeeld
// met de unit-editor).