Add five minute panoramic sky rotation

This commit is contained in:
vini 2026-07-19 14:54:42 -03:00
parent 3416cac155
commit 02697dae1f
2 changed files with 79 additions and 10 deletions

View file

@ -1254,9 +1254,11 @@ if (typeof CONSTELLATIONS !== 'undefined') {
constellationArtImages.set(constellation.id, entry);
}
}
const VIEW_AZ = 90; // centro da tela olha para o LESTE — a tela física do
// usuário está voltada p/ leste, então o wallpaper age
// como janela: estrelas nascem subindo dentro da cena
const CAMERA_HOME_AZ = 90; // posição inicial: leste
const CAMERA_TURN_MS = 5 * 60 * 1000;
let viewAz = CAMERA_HOME_AZ;
let cameraRotating = false;
let cameraRotationStartMs = Date.now();
const FOV_AZ = 140; // largura da tela em graus de azimute
const ALT_TOP = 75; // altitude no topo da tela
const HORIZON_F = 0.585; // fração da altura da tela até o horizonte
@ -1267,6 +1269,23 @@ let skyTimeScale = 1;
let skyClockRealMs = Date.now();
let skyClockSimMs = skyClockRealMs;
function currentCameraAz() {
if (!cameraRotating) return CAMERA_HOME_AZ;
const phase = ((Date.now() - cameraRotationStartMs) % CAMERA_TURN_MS) /
CAMERA_TURN_MS;
return (CAMERA_HOME_AZ + phase * 360) % 360;
}
function setCameraRotating(on) {
on = !!on;
if (on === cameraRotating) return;
cameraRotating = on;
cameraRotationStartMs = Date.now();
viewAz = CAMERA_HOME_AZ;
lastProjMs = -Infinity;
projectSky();
}
function skyNowMs() {
const realNow = Date.now();
return skyClockSimMs + (realNow - skyClockRealMs) * skyTimeScale +
@ -1310,7 +1329,7 @@ function altAz(raDeg, decDeg, lstDeg) {
/* Projeção cilíndrica: azimute -> x, altitude -> y. null = fora da tela */
function skyToScreen(alt, az, W, horizonPx) {
const daz = ((az - VIEW_AZ) % 360 + 540) % 360 - 180;
const daz = ((az - viewAz) % 360 + 540) % 360 - 180;
if (Math.abs(daz) > FOV_AZ / 2 || alt < -3 || alt > ALT_TOP) return null;
return { x: (0.5 + daz / FOV_AZ) * W, y: horizonPx * (1 - alt / ALT_TOP) };
}
@ -1380,7 +1399,7 @@ function buildMwGrid() {
const ca = Math.cos(alt * DEG), sa = Math.sin(alt * DEG);
for (let gx = 0; gx < gw; gx++, i++) {
const sx = (gx + 0.5) * MW_SAMPLE_SCALE;
const az = (VIEW_AZ + (sx / W - 0.5) * FOV_AZ) * DEG;
const az = (CAMERA_HOME_AZ + (sx / W - 0.5) * FOV_AZ) * DEG;
const north = ca * Math.cos(az), east = ca * Math.sin(az);
wx[i] = -sl * north + cl * sa;
wy[i] = -east;
@ -1408,13 +1427,36 @@ function renderMilkyWay(lstDeg, glareOn) {
const m20 = E2G[2][0] * cL + E2G[2][1] * sL;
const m21 = E2G[2][0] * sL - E2G[2][1] * cL;
const m22 = E2G[2][2];
/* Gira o viewport em torno do zênite sem reconstruir a grade de milhões
de pixels. A matriz horizontal é incorporada à equatorial-galáctica. */
const cameraDelta = (viewAz - CAMERA_HOME_AZ) * DEG;
const cr = Math.cos(cameraDelta), sr = Math.sin(cameraDelta);
const clat = Math.cos(obsLat * DEG), slat = Math.sin(obsLat * DEG);
const r00 = cr * slat * slat + clat * clat;
const r01 = -slat * sr;
const r02 = clat * slat * (1 - cr);
const r10 = slat * sr;
const r11 = cr;
const r12 = -clat * sr;
const r20 = r02;
const r21 = clat * sr;
const r22 = cr * clat * clat + slat * slat;
const n00 = m00 * r00 + m01 * r10 + m02 * r20;
const n01 = m00 * r01 + m01 * r11 + m02 * r21;
const n02 = m00 * r02 + m01 * r12 + m02 * r22;
const n10 = m10 * r00 + m11 * r10 + m12 * r20;
const n11 = m10 * r01 + m11 * r11 + m12 * r21;
const n12 = m10 * r02 + m11 * r12 + m12 * r22;
const n20 = m20 * r00 + m21 * r10 + m22 * r20;
const n21 = m20 * r01 + m21 * r11 + m22 * r21;
const n22 = m20 * r02 + m21 * r12 + m22 * r22;
const tw = mwTex.w, th = mwTex.h, tex = mwTex.data, px = img.data;
const inv2pi = 1 / (2 * Math.PI), invpi = 1 / Math.PI;
for (let i = 0, j = 0; i < wx.length; i++, j += 4) {
const gx = m00 * wx[i] + m01 * wy[i] + m02 * wz[i];
const gy = m10 * wx[i] + m11 * wy[i] + m12 * wz[i];
const gz = m20 * wx[i] + m21 * wy[i] + m22 * wz[i];
const gx = n00 * wx[i] + n01 * wy[i] + n02 * wz[i];
const gy = n10 * wx[i] + n11 * wy[i] + n12 * wz[i];
const gz = n20 * wx[i] + n21 * wy[i] + n22 * wz[i];
const l = Math.atan2(gy, gx);
const b = Math.asin(Math.max(-1, Math.min(1, gz)));
const absB = Math.abs(b / DEG);
@ -1512,7 +1554,7 @@ function drawConstellationArt(c, constellation, lst, W, horizonPx, previewProjec
return { x: sc.x, y: sc.y, alt: ALT_TOP / 2 };
}
const aa = altAz(anchor.sky[0], anchor.sky[1], lst);
const daz = ((aa.az - VIEW_AZ) % 360 + 540) % 360 - 180;
const daz = ((aa.az - viewAz) % 360 + 540) % 360 - 180;
return {
x: (0.5 + daz / FOV_AZ) * W,
y: horizonPx * (1 - aa.alt / ALT_TOP),
@ -1738,6 +1780,7 @@ function projectSky() {
}
const bctx = renderCanvas.getContext('2d');
bctx.clearRect(0, 0, W, bufferH);
viewAz = currentCameraAz();
const lst = localSiderealDeg(skyNowMs());
const glareOn = !document.body.classList.contains('balada') &&
!document.body.classList.contains('chuva');
@ -1824,7 +1867,7 @@ function resizeCanvas() {
}
function drawStars(ts) {
const projectionInterval = skyTimeScale > 1 ? 4000 : 30000;
const projectionInterval = (skyTimeScale > 1 || cameraRotating) ? 4000 : 30000;
if (ts - lastProjMs > projectionInterval) { lastProjMs = ts; projectSky(); }
ctx.clearRect(0, 0, canvas.width, canvas.height);
const transition = Math.max(0, Math.min(1,
@ -1871,6 +1914,21 @@ let skyTimeScaleFileVal = null;
.finally(() => setTimeout(pollSkyTimeScaleFile, 800));
})();
/* Giro panorâmico controlado por toggle-camera-rotation.sh. */
let cameraRotationFileVal = null;
(function pollCameraRotationFile() {
fetch('file:///tmp/skeledance-camera-rotation?t=' + Date.now())
.then(r => r.text())
.then(t => {
const v = t.trim();
if (v === cameraRotationFileVal) return;
cameraRotationFileVal = v;
setCameraRotating(v === '1');
})
.catch(() => {})
.finally(() => setTimeout(pollCameraRotationFile, 800));
})();
/* ============================================================
FLOOR CANVAS — perspective grid with true vanishing point
============================================================ */