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 = {