diff --git a/plugin/contents/ui/dancer.html b/plugin/contents/ui/dancer.html
index daa52fd..83819e7 100644
--- a/plugin/contents/ui/dancer.html
+++ b/plugin/contents/ui/dancer.html
@@ -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
============================================================ */
diff --git a/toggle-camera-rotation.sh b/toggle-camera-rotation.sh
new file mode 100755
index 0000000..59bfdec
--- /dev/null
+++ b/toggle-camera-rotation.sh
@@ -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