Add five minute panoramic sky rotation
This commit is contained in:
parent
3416cac155
commit
02697dae1f
2 changed files with 79 additions and 10 deletions
|
|
@ -1254,9 +1254,11 @@ if (typeof CONSTELLATIONS !== 'undefined') {
|
||||||
constellationArtImages.set(constellation.id, entry);
|
constellationArtImages.set(constellation.id, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const VIEW_AZ = 90; // centro da tela olha para o LESTE — a tela física do
|
const CAMERA_HOME_AZ = 90; // posição inicial: leste
|
||||||
// usuário está voltada p/ leste, então o wallpaper age
|
const CAMERA_TURN_MS = 5 * 60 * 1000;
|
||||||
// como janela: estrelas nascem subindo dentro da cena
|
let viewAz = CAMERA_HOME_AZ;
|
||||||
|
let cameraRotating = false;
|
||||||
|
let cameraRotationStartMs = Date.now();
|
||||||
const FOV_AZ = 140; // largura da tela em graus de azimute
|
const FOV_AZ = 140; // largura da tela em graus de azimute
|
||||||
const ALT_TOP = 75; // altitude no topo da tela
|
const ALT_TOP = 75; // altitude no topo da tela
|
||||||
const HORIZON_F = 0.585; // fração da altura da tela até o horizonte
|
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 skyClockRealMs = Date.now();
|
||||||
let skyClockSimMs = skyClockRealMs;
|
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() {
|
function skyNowMs() {
|
||||||
const realNow = Date.now();
|
const realNow = Date.now();
|
||||||
return skyClockSimMs + (realNow - skyClockRealMs) * skyTimeScale +
|
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 */
|
/* Projeção cilíndrica: azimute -> x, altitude -> y. null = fora da tela */
|
||||||
function skyToScreen(alt, az, W, horizonPx) {
|
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;
|
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) };
|
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);
|
const ca = Math.cos(alt * DEG), sa = Math.sin(alt * DEG);
|
||||||
for (let gx = 0; gx < gw; gx++, i++) {
|
for (let gx = 0; gx < gw; gx++, i++) {
|
||||||
const sx = (gx + 0.5) * MW_SAMPLE_SCALE;
|
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);
|
const north = ca * Math.cos(az), east = ca * Math.sin(az);
|
||||||
wx[i] = -sl * north + cl * sa;
|
wx[i] = -sl * north + cl * sa;
|
||||||
wy[i] = -east;
|
wy[i] = -east;
|
||||||
|
|
@ -1408,13 +1427,36 @@ function renderMilkyWay(lstDeg, glareOn) {
|
||||||
const m20 = E2G[2][0] * cL + E2G[2][1] * sL;
|
const m20 = E2G[2][0] * cL + E2G[2][1] * sL;
|
||||||
const m21 = E2G[2][0] * sL - E2G[2][1] * cL;
|
const m21 = E2G[2][0] * sL - E2G[2][1] * cL;
|
||||||
const m22 = E2G[2][2];
|
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 tw = mwTex.w, th = mwTex.h, tex = mwTex.data, px = img.data;
|
||||||
const inv2pi = 1 / (2 * Math.PI), invpi = 1 / Math.PI;
|
const inv2pi = 1 / (2 * Math.PI), invpi = 1 / Math.PI;
|
||||||
|
|
||||||
for (let i = 0, j = 0; i < wx.length; i++, j += 4) {
|
for (let i = 0, j = 0; i < wx.length; i++, j += 4) {
|
||||||
const gx = m00 * wx[i] + m01 * wy[i] + m02 * wz[i];
|
const gx = n00 * wx[i] + n01 * wy[i] + n02 * wz[i];
|
||||||
const gy = m10 * wx[i] + m11 * wy[i] + m12 * wz[i];
|
const gy = n10 * wx[i] + n11 * wy[i] + n12 * wz[i];
|
||||||
const gz = m20 * wx[i] + m21 * wy[i] + m22 * wz[i];
|
const gz = n20 * wx[i] + n21 * wy[i] + n22 * wz[i];
|
||||||
const l = Math.atan2(gy, gx);
|
const l = Math.atan2(gy, gx);
|
||||||
const b = Math.asin(Math.max(-1, Math.min(1, gz)));
|
const b = Math.asin(Math.max(-1, Math.min(1, gz)));
|
||||||
const absB = Math.abs(b / DEG);
|
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 };
|
return { x: sc.x, y: sc.y, alt: ALT_TOP / 2 };
|
||||||
}
|
}
|
||||||
const aa = altAz(anchor.sky[0], anchor.sky[1], lst);
|
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 {
|
return {
|
||||||
x: (0.5 + daz / FOV_AZ) * W,
|
x: (0.5 + daz / FOV_AZ) * W,
|
||||||
y: horizonPx * (1 - aa.alt / ALT_TOP),
|
y: horizonPx * (1 - aa.alt / ALT_TOP),
|
||||||
|
|
@ -1738,6 +1780,7 @@ function projectSky() {
|
||||||
}
|
}
|
||||||
const bctx = renderCanvas.getContext('2d');
|
const bctx = renderCanvas.getContext('2d');
|
||||||
bctx.clearRect(0, 0, W, bufferH);
|
bctx.clearRect(0, 0, W, bufferH);
|
||||||
|
viewAz = currentCameraAz();
|
||||||
const lst = localSiderealDeg(skyNowMs());
|
const lst = localSiderealDeg(skyNowMs());
|
||||||
const glareOn = !document.body.classList.contains('balada') &&
|
const glareOn = !document.body.classList.contains('balada') &&
|
||||||
!document.body.classList.contains('chuva');
|
!document.body.classList.contains('chuva');
|
||||||
|
|
@ -1824,7 +1867,7 @@ function resizeCanvas() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawStars(ts) {
|
function drawStars(ts) {
|
||||||
const projectionInterval = skyTimeScale > 1 ? 4000 : 30000;
|
const projectionInterval = (skyTimeScale > 1 || cameraRotating) ? 4000 : 30000;
|
||||||
if (ts - lastProjMs > projectionInterval) { lastProjMs = ts; projectSky(); }
|
if (ts - lastProjMs > projectionInterval) { lastProjMs = ts; projectSky(); }
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
const transition = Math.max(0, Math.min(1,
|
const transition = Math.max(0, Math.min(1,
|
||||||
|
|
@ -1871,6 +1914,21 @@ let skyTimeScaleFileVal = null;
|
||||||
.finally(() => setTimeout(pollSkyTimeScaleFile, 800));
|
.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
|
FLOOR CANVAS — perspective grid with true vanishing point
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
|
||||||
11
toggle-camera-rotation.sh
Executable file
11
toggle-camera-rotation.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
STATE_FILE=/tmp/skeledance-camera-rotation
|
||||||
|
|
||||||
|
if [ "$(cat "$STATE_FILE" 2>/dev/null)" = "1" ]; then
|
||||||
|
printf '0\n' > "$STATE_FILE"
|
||||||
|
printf 'Câmera fixa olhando para leste\n'
|
||||||
|
else
|
||||||
|
printf '1\n' > "$STATE_FILE"
|
||||||
|
printf 'Rotação panorâmica ativa: 360° em 5 minutos\n'
|
||||||
|
fi
|
||||||
Loading…
Add table
Add a link
Reference in a new issue