diff --git a/locenice/dum_3D_slunce.html b/locenice/dum_3D_slunce.html index f703f51..380cbc5 100644 --- a/locenice/dum_3D_slunce.html +++ b/locenice/dum_3D_slunce.html @@ -1350,7 +1350,7 @@ } } - /* Optional surroundings: independent switches, all off on every fresh open. */ + /* Optional surroundings: independent switches, all on when the page opens. */ .site-controls { display: flex; align-items: center; @@ -1555,7 +1555,7 @@ - +
@@ -1568,7 +1568,7 @@
23°Střecha
-
-
+
+ id="siteRoad" autocomplete="off" checked>Cesta a přístup + id="siteParcel" autocomplete="off" checked>Celý pozemek + type="checkbox" id="siteParking" autocomplete="off" checked>Parkování
SLUNCE NAD DOMEM
21. 6. 2026 · 10:00
-
+
STUDIE OSVĚTLENÍ
@@ -1787,6 +1787,20 @@ let target = [.8, 1.15, 0], theta = 2.25, phi = .30, distance = 35.0, aspect = 1, vp, lightVP, eye = [0, 0, 0]; let animation = null, pointerMap = new Map(), pinch = null, wheelTimer = 0; const initialCamera = { theta: 2.25, phi: .30, distance: 35.0, target: [.8, 1.15, 0], ortho: false }; + // Opening composition follows the supplied reference; house geometry is unchanged. + // Keep this separate from the existing Entry / Terrace / Top view presets. + const startupCamera = { theta: .63, phi: .30, distance: 41.35, target: [-2.8, 2.3, 0], ortho: false }; + function applyStartupView() { + document.querySelectorAll('[data-preset]').forEach(b => b.classList.remove('active')); + $('#elevation').value = ''; + $('#panel').classList.remove('open'); + $('#infoButton').classList.remove('active'); + // Match the existing portrait resize rule, without fitting the entire long parcel. + setCamera({ ...startupCamera, distance: startupCamera.distance * Math.max(1, 1 / aspect) }, 'Voln\u00fd pohled', true); + autoRotate = true; + $('#rotateButton').classList.add('active'); + dirty = true; + } const originalColors = {}; const vadd = (a, b) => a.map((x, i) => x + b[i]), vsub = (a, b) => a.map((x, i) => x - b[i]), scale = (a, s) => a.map(x => x * s), dot = (a, b) => a.reduce((r, x, i) => r + x * b[i], 0), norm = a => scale(a, 1 / Math.hypot(...a)), cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; function mul(a, b) { const c = new Float32Array(16); for (let j = 0; j < 4; j++)for (let i = 0; i < 4; i++)for (let k = 0; k < 4; k++)c[j * 4 + i] += a[k * 4 + i] * b[j * 4 + k]; return c; } @@ -1887,7 +1901,7 @@ precision highp float;void main(){}`; $('#savePng').onclick = () => { render(); canvas.toBlob(b => saveBlob(b, 'dum_pohled.png'), 'image/png'); }; window.addEventListener('resize', resize); window.addEventListener('keydown', e => { if (e.key === 'Escape') { $('#panel').classList.remove('open'); $('#infoButton').classList.remove('active'); } if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return; if (e.key === '1') setPreset('entry'); if (e.key === '2') setPreset('terrace'); if (e.key === '3') setPreset('top'); if (e.key === 'r') setPreset('terrace'); }); - async function start() { try { await new Promise(r => setTimeout(r, 40)); const s = atob(b64); originalBytes = new Uint8Array(s.length); for (let i = 0; i < s.length; i++)originalBytes[i] = s.charCodeAt(i); const dv = new DataView(originalBytes.buffer); if (dv.getUint32(0, true) !== 0x46546c67) throw Error('Invalid GLB'); let off = 12; while (off < originalBytes.length) { const len = dv.getUint32(off, true), type = dv.getUint32(off + 4, true); if (type === 0x4e4f534a) gltf = JSON.parse(new TextDecoder().decode(originalBytes.subarray(off + 8, off + 8 + len))); if (type === 0x004e4942) binary = originalBytes.subarray(off + 8, off + 8 + len); off += 8 + len; } initGL(); initSolar(); initSiteControls(); distance = Math.max(41, 38 / aspect); initialCamera.distance = distance; presetDefs.entry.distance = Math.max(39, 38 / aspect); presetDefs.top.distance = Math.max(46, 25 / aspect); if (aspect < 1) presetDefs.top.theta = Math.PI / 2; $('#loading').style.display = 'none'; requestAnimationFrame(animate); if ('ontouchstart' in window) $('.help').innerHTML = 'Jedn\u00edm prstem ot\u00e1\u010dej  ·  Dv\u011bma prsty p\u0159ibli\u017e a posu\u0148'; } catch (e) { console.error(e); $('#loading').className = 'error'; $('#loading').innerHTML = '
' + String(e.message).replace(/'; $('#fallbackSave').onclick = () => saveBlob(new Blob([originalBytes], { type: 'model/gltf-binary' }), 'dum_3D_pozemek.glb'); } } + async function start() { try { await new Promise(r => setTimeout(r, 40)); const s = atob(b64); originalBytes = new Uint8Array(s.length); for (let i = 0; i < s.length; i++)originalBytes[i] = s.charCodeAt(i); const dv = new DataView(originalBytes.buffer); if (dv.getUint32(0, true) !== 0x46546c67) throw Error('Invalid GLB'); let off = 12; while (off < originalBytes.length) { const len = dv.getUint32(off, true), type = dv.getUint32(off + 4, true); if (type === 0x4e4f534a) gltf = JSON.parse(new TextDecoder().decode(originalBytes.subarray(off + 8, off + 8 + len))); if (type === 0x004e4942) binary = originalBytes.subarray(off + 8, off + 8 + len); off += 8 + len; } initGL(); initSolar(); distance = Math.max(41, 38 / aspect); initialCamera.distance = distance; presetDefs.entry.distance = Math.max(39, 38 / aspect); presetDefs.top.distance = Math.max(46, 25 / aspect); if (aspect < 1) presetDefs.top.theta = Math.PI / 2; initSiteControls(); applyStartupView(); $('#loading').style.display = 'none'; requestAnimationFrame(animate); if ('ontouchstart' in window) $('.help').innerHTML = 'Jedn\u00edm prstem ot\u00e1\u010dej  ·  Dv\u011bma prsty p\u0159ibli\u017e a posu\u0148'; } catch (e) { console.error(e); $('#loading').className = 'error'; $('#loading').innerHTML = '
' + String(e.message).replace(/'; $('#fallbackSave').onclick = () => saveBlob(new Blob([originalBytes], { type: 'model/gltf-binary' }), 'dum_3D_pozemek.glb'); } } // Solar geometry: Meeus / NOAA. UTC is the only time basis for the ephemeris. // Model axes: +X = south gable, +Z = entrance (west) side, +Y = up. const RAD = Math.PI / 180, DAY_MS = 86400000; @@ -2042,12 +2056,12 @@ precision highp float;void main(){}`; window.addEventListener('keydown', e => { if (['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON', 'SUMMARY'].includes(e.target.tagName)) return; if (e.code === 'Space') { e.preventDefault(); setSolarPlaying(!solar.playing); } if (e.key.toLowerCase() === 's') toggleSunPanel(); }); document.addEventListener('visibilitychange', () => { if (document.hidden) setSolarPlaying(false); }); const cp = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); cp.id = 'compassSun'; cp.setAttribute('r', '3'); cp.setAttribute('fill', '#c89439'); cp.setAttribute('stroke', '#fff7d7'); cp.setAttribute('stroke-width', '1'); $('#compassGroup').appendChild(cp); - toggleSunPanel(true); rebuildSolarDay(false); + toggleSunPanel(false); rebuildSolarDay(false); // Read-only diagnostics and small public control API for verification and reuse. window.SUN_SIM = { position: solarPosition, getState: () => ({ date: solar.date, time: clockText(solar.clock), utc: new Date(solar.clock).toISOString(), dayMinutes: solar.length, bearing: solar.bearing, lat: solar.lat, lon: solar.lon, azimuth: solar.pos.azimuth, elevation: solar.pos.elevation, light: solar.light.slice(), rise: clockText(solar.rise), set: clockText(solar.set), noon: clockText(solar.noon), shadows: solar.shadows, playing: solar.playing }), setDate: d => { solar.date = d; rebuildSolarDay(); }, setTime: t => { solar.minute = (timeForClock(t) - solar.start) / 60000; updateSolar(); }, setBearing: b => { solar.bearing = mod(b, 360); $('#houseBearing').value = solar.bearing; updateSolar(); }, pause: () => setSolarPlaying(false) }; } - // Independent optional site layers. State is deliberately never persisted. + // Independent site layers: initSiteControls enables all three on each fresh open. const site = { road: false, parcel: false, parking: false, savedCamera: null, lastFocus: null }; const siteActive = () => site.road || site.parcel || site.parking; function meshVisible(m) { @@ -2135,7 +2149,8 @@ precision highp float;void main(){}`; } function initSiteControls() { for (const [id, key] of [['siteRoad', 'road'], ['siteParcel', 'parcel'], ['siteParking', 'parking']]) { - const input = $('#' + id); input.checked = false; input.onchange = () => setSiteLayer(key, input.checked); + const input = $('#' + id); input.onchange = () => setSiteLayer(key, input.checked); + setSiteLayer(key, true, false); } $('#siteFit').onclick = () => focusSite(); window.SITE_SIM = {