Files
KuppelSimulation/JS/public/visualization.js

322 lines
9.8 KiB
JavaScript

class Visualization {
constructor() {
this.canvas = document.getElementById('simCanvas');
this.ctx = this.canvas.getContext('2d');
this.width = 700;
this.height = 700;
this.center = { x: this.width / 2, y: this.height / 2 };
this.radius = 260;
this.tagCount = 36;
// Aktuelle Positionen
this.telescopeAngle = 0;
this.domeAngle = 0;
this.slotWidth = 20;
this.telescopeData = null;
this.domeData = null;
// Lokale Teleskop-Interpolation für flüssige Bewegung
this.localTelescopeAngle = 0;
this.telescopeSpeed = 6; // 6°/sec = 1 U/min
this.lastUpdateTime = Date.now();
// Lokale Kuppel-Interpolation für flüssige Bewegung
this.localDomeAngle = 0;
this.domeSpeed = 720; // 720°/sec = 2 U/sec wie in der Kuppel-Logik
this.lastDomeUpdateTime = Date.now();
this.socket = io('http://localhost:3005');
this.setupSocketListeners();
this.startAnimation();
this.updateStatus();
}
setupSocketListeners() {
this.socket.on('connect', () => {
console.log('Visualization connected to server');
this.updateSystemStatus('WebSocket: Connected');
});
this.socket.on('disconnect', () => {
this.updateSystemStatus('WebSocket: Disconnected');
});
this.socket.on('telescope-position', (data) => {
console.log(`[Visualization] Received telescope position: ${data.angle.toFixed(1)}°`);
this.telescopeData = data;
this.telescopeAngle = data.angle;
// Weiche Synchronisation: nur korrigieren wenn Differenz groß ist
const diff = Math.abs(data.angle - this.localTelescopeAngle);
const adjustedDiff = Math.min(diff, 360 - diff); // Berücksichtige Überlauf bei 360°
if (adjustedDiff > 3) {
// Große Abweichung (z.B. durch Button-Klick) -> sofort korrigieren
console.log(`[Visualization] Large sync correction: ${adjustedDiff.toFixed(1)}°`);
this.localTelescopeAngle = data.angle;
} else {
// Kleine Abweichung -> langsam angleichen um Blitzen zu vermeiden
const correctionFactor = 0.1; // 10% Korrektur pro Update
let targetAngle = data.angle;
// Handle 360° wrap-around
if (Math.abs(targetAngle - this.localTelescopeAngle) > 180) {
if (targetAngle > this.localTelescopeAngle) {
targetAngle -= 360;
} else {
targetAngle += 360;
}
}
this.localTelescopeAngle += (targetAngle - this.localTelescopeAngle) * correctionFactor;
this.localTelescopeAngle = (this.localTelescopeAngle + 360) % 360;
}
this.lastUpdateTime = Date.now();
this.updateTelescopeStatus();
});
this.socket.on('dome-position', (data) => {
this.domeData = data;
this.domeAngle = data.angle;
this.slotWidth = data.slotWidth;
console.log(`[Visualization] Received dome position: ${data.angle.toFixed(1)}°`);
// SOFORTIGE Synchronisation für die Kuppel (keine weiche Angleichung)
// Die Kuppel soll genau das anzeigen, was sie wirklich ist
this.localDomeAngle = data.angle;
this.lastDomeUpdateTime = Date.now();
this.updateDomeStatus();
});
}
startAnimation() {
// 60 FPS Animation
setInterval(() => {
this.draw();
}, 1000 / 60);
}
draw() {
// Lokale Teleskop-Interpolation aktualisieren (für flüssige Bewegung)
const now = Date.now();
const deltaTime = (now - this.lastUpdateTime) / 1000; // in Sekunden
this.localTelescopeAngle = (this.localTelescopeAngle + this.telescopeSpeed * deltaTime) % 360;
this.lastUpdateTime = now;
// KUPPEL: Keine lokale Interpolation! Zeige nur echte Position
// Die Kuppel bewegt sich nur diskret, wenn das Teleskop den Spalt verlässt
// Canvas löschen
this.ctx.fillStyle = '#2a2a2a';
this.ctx.fillRect(0, 0, this.width, this.height);
// NFC-Tags zeichnen
this.drawTags();
// Kuppel-Spalt zeichnen (verwendet echte Position, keine Interpolation)
this.drawDomeSlot();
// Teleskop zeichnen (verwendet lokale Interpolation)
this.drawTelescope();
// Zentrum
this.drawCenter();
// Debug-Info aktualisieren
this.updateDebugInfo();
}
drawTags() {
this.ctx.fillStyle = '#666';
for (let i = 0; i < this.tagCount; i++) {
const angle = 2 * Math.PI * i / this.tagCount; // Positive für Uhrzeigersinn
const x = this.center.x + Math.cos(angle) * this.radius;
const y = this.center.y + Math.sin(angle) * this.radius;
this.ctx.beginPath();
this.ctx.arc(x, y, 4, 0, 2 * Math.PI);
this.ctx.fill();
}
}
drawDomeSlot() {
const midAngle = this.localDomeAngle * Math.PI / 180; // Verwendet sofortigen Sync ohne Interpolation
const halfSlot = this.slotWidth * Math.PI / 360;
this.ctx.strokeStyle = '#4a9eff';
this.ctx.lineWidth = 12;
this.ctx.beginPath();
this.ctx.arc(
this.center.x,
this.center.y,
this.radius,
midAngle - halfSlot,
midAngle + halfSlot
);
this.ctx.stroke();
}
drawTelescope() {
const angle = this.localTelescopeAngle * Math.PI / 180; // Verwende lokale Interpolation
const length = this.radius - 60;
const endX = this.center.x + Math.cos(angle) * length;
const endY = this.center.y + Math.sin(angle) * length;
// Linie
this.ctx.strokeStyle = '#ff4444';
this.ctx.lineWidth = 6;
this.ctx.beginPath();
this.ctx.moveTo(this.center.x, this.center.y);
this.ctx.lineTo(endX, endY);
this.ctx.stroke();
// Endpunkt
this.ctx.fillStyle = '#ff4444';
this.ctx.beginPath();
this.ctx.arc(endX, endY, 8, 0, 2 * Math.PI);
this.ctx.fill();
}
drawCenter() {
this.ctx.fillStyle = '#333';
this.ctx.beginPath();
this.ctx.arc(this.center.x, this.center.y, 6, 0, 2 * Math.PI);
this.ctx.fill();
}
updateTelescopeStatus() {
if (this.telescopeData) {
const statusEl = document.getElementById('telescope-status');
statusEl.textContent = `Position: ${this.telescopeAngle.toFixed(1)}° | Speed: 1 U/min | Running`;
}
}
updateDomeStatus() {
if (this.domeData) {
const statusEl = document.getElementById('dome-status');
const diff = this.domeData.diff || 0;
const isTracking = Math.abs(diff) > this.slotWidth / 2;
statusEl.textContent = `Position: ${this.domeAngle.toFixed(1)}° | ${isTracking ? 'Tracking' : 'Standby'} | Diff: ${diff.toFixed(1)}°`;
}
}
updateSystemStatus(message) {
const statusEl = document.getElementById('system-status');
statusEl.textContent = message;
}
updateDebugInfo() {
const debugEl = document.getElementById('debug');
const now = new Date().toLocaleTimeString();
let info = `=== Observatory Debug Info [${now}] ===\n`;
if (this.telescopeData) {
info += `🔭 TELESCOPE:\n`;
info += ` Position: ${this.telescopeAngle.toFixed(1)}°\n`;
info += ` Last Update: ${new Date(this.telescopeData.timestamp).toLocaleTimeString()}\n\n`;
}
if (this.domeData) {
info += `🏠 DOME:\n`;
info += ` Position: ${this.domeAngle.toFixed(1)}°\n`;
info += ` Slot Width: ${this.slotWidth}°\n`;
info += ` Difference: ${(this.domeData.diff || 0).toFixed(1)}°\n`;
info += ` Status: ${Math.abs(this.domeData.diff || 0) > this.slotWidth / 2 ? 'TRACKING' : 'STANDBY'}\n`;
info += ` Last Update: ${new Date(this.domeData.timestamp).toLocaleTimeString()}\n\n`;
}
info += `📊 SYSTEM:\n`;
info += ` FPS: 60\n`;
info += ` Tags: ${this.tagCount}\n`;
info += ` WebSocket: ${this.socket.connected ? 'Connected' : 'Disconnected'}`;
debugEl.textContent = info;
}
updateStatus() {
// Status alle 100ms aktualisieren
setInterval(() => {
this.updateDebugInfo();
}, 100);
}
}
// Telescope Control Funktionen für Buttons
function adjustTelescope(degrees) {
if (window.telescope && window.telescope.socket) {
window.telescope.socket.emit('telescope-control', {
command: 'adjust',
degrees: degrees
});
}
}
// Keyboard Controls
document.addEventListener('keydown', (event) => {
switch(event.code) {
case 'ArrowLeft':
adjustTelescope(-15);
break;
case 'ArrowRight':
adjustTelescope(15);
break;
case 'KeyQ':
adjustTelescope(-45);
break;
case 'KeyE':
adjustTelescope(45);
break;
}
});
// Global flag to prevent multiple initializations
window.observatoryInitialized = window.observatoryInitialized || false;
// Initialize visualization when page loads
document.addEventListener('DOMContentLoaded', () => {
// Prevent multiple initializations
if (window.observatoryInitialized) {
console.log('=== Observatory System Already Running - Skipping ===');
return;
}
console.log('=== Observatory System Starting ===');
// Stoppe und entferne eventuelle bereits laufende Instanzen
if (window.telescope) {
console.log('Stopping existing telescope...');
window.telescope.stop();
delete window.telescope;
}
if (window.dome) {
console.log('Stopping existing dome...');
window.dome.stop();
delete window.dome;
}
if (window.visualization) {
console.log('Stopping existing visualization...');
delete window.visualization;
}
// Warte kurz, dann erstelle neue Instanzen
setTimeout(() => {
console.log('Creating telescope...');
window.telescope = new Telescope('http://localhost:3005');
console.log('Creating dome...');
window.dome = new Dome('http://localhost:3005');
console.log('Creating visualization...');
window.visualization = new Visualization();
// Markiere als initialisiert
window.observatoryInitialized = true;
console.log('=== Observatory System Ready ===');
}, 100);
});