// Dashboard HUD Overlay - WiFi DensePose 3D Visualization // Connection status, FPS counter, detection confidence, person count, sensing mode export class DashboardHUD { constructor(container) { this.container = typeof container === 'string' ? document.getElementById(container) : container; // State this.state = { connectionStatus: 'disconnected', // connected, disconnected, connecting, error isRealData: false, fps: 0, confidence: 0, personCount: 0, sensingMode: 'Mock', // CSI, RSSI, Mock latency: 0, messageCount: 0, uptime: 0 }; this._fpsFrames = []; this._lastFpsUpdate = 0; this._build(); } _build() { // Create HUD overlay container this.hudElement = document.createElement('div'); this.hudElement.id = 'viz-hud'; this.hudElement.innerHTML = `
MOCK DATA
Disconnected
Latency -- ms
Messages 0
Uptime 0s
-- FPS
Frame -- ms
Persons 0
Confidence 0%
MOCK
WiFi DensePose
Drag to orbit | Scroll to zoom | Right-click to pan
`; this.container.style.position = 'relative'; this.container.appendChild(this.hudElement); // Cache DOM references this._els = { banner: this.hudElement.querySelector('#hud-banner'), statusDot: this.hudElement.querySelector('#hud-status-dot'), connStatus: this.hudElement.querySelector('#hud-conn-status'), latency: this.hudElement.querySelector('#hud-latency'), msgCount: this.hudElement.querySelector('#hud-msg-count'), uptime: this.hudElement.querySelector('#hud-uptime'), fps: this.hudElement.querySelector('#hud-fps'), frameTime: this.hudElement.querySelector('#hud-frame-time'), personCount: this.hudElement.querySelector('#hud-person-count'), confidence: this.hudElement.querySelector('#hud-confidence'), confidenceFill: this.hudElement.querySelector('#hud-confidence-fill'), modeBadge: this.hudElement.querySelector('#hud-mode-badge') }; } // Update state from external data updateState(newState) { Object.assign(this.state, newState); this._render(); } // Track FPS - call each frame tickFPS() { const now = performance.now(); this._fpsFrames.push(now); // Keep only last second of frames while (this._fpsFrames.length > 0 && this._fpsFrames[0] < now - 1000) { this._fpsFrames.shift(); } // Update FPS display at most 4 times per second if (now - this._lastFpsUpdate > 250) { this.state.fps = this._fpsFrames.length; const frameTime = this._fpsFrames.length > 1 ? (now - this._fpsFrames[0]) / (this._fpsFrames.length - 1) : 0; this._lastFpsUpdate = now; // Update FPS elements this._els.fps.textContent = `${this.state.fps} FPS`; this._els.fps.className = 'hud-fps ' + ( this.state.fps >= 50 ? 'high' : this.state.fps >= 25 ? 'mid' : 'low' ); this._els.frameTime.textContent = `${frameTime.toFixed(1)} ms`; } } _render() { const { state } = this; // Banner if (state.isRealData) { this._els.banner.textContent = 'REAL DATA - LIVE STREAM'; this._els.banner.className = 'hud-banner real'; } else { this._els.banner.textContent = 'MOCK DATA - DEMO MODE'; this._els.banner.className = 'hud-banner mock'; } // Connection status this._els.statusDot.className = `hud-status-dot ${state.connectionStatus}`; const statusText = { connected: 'Connected', disconnected: 'Disconnected', connecting: 'Connecting...', error: 'Error' }; this._els.connStatus.textContent = statusText[state.connectionStatus] || 'Unknown'; // Latency this._els.latency.textContent = state.latency > 0 ? `${state.latency.toFixed(0)} ms` : '-- ms'; // Messages this._els.msgCount.textContent = state.messageCount.toLocaleString(); // Uptime const uptimeSec = Math.floor(state.uptime); if (uptimeSec < 60) { this._els.uptime.textContent = `${uptimeSec}s`; } else if (uptimeSec < 3600) { this._els.uptime.textContent = `${Math.floor(uptimeSec / 60)}m ${uptimeSec % 60}s`; } else { const h = Math.floor(uptimeSec / 3600); const m = Math.floor((uptimeSec % 3600) / 60); this._els.uptime.textContent = `${h}h ${m}m`; } // Person count this._els.personCount.textContent = state.personCount; this._els.personCount.style.color = state.personCount > 0 ? '#00ff88' : '#556677'; // Confidence const confPct = (state.confidence * 100).toFixed(1); this._els.confidence.textContent = `${confPct}%`; this._els.confidenceFill.style.width = `${state.confidence * 100}%`; // Color temperature: red (low) -> yellow (mid) -> green (high) const confHue = state.confidence * 120; // 0=red, 60=yellow, 120=green this._els.confidenceFill.style.background = `hsl(${confHue}, 100%, 45%)`; // Sensing mode const modeLower = (state.sensingMode || 'Mock').toLowerCase(); this._els.modeBadge.textContent = state.sensingMode.toUpperCase(); this._els.modeBadge.className = `hud-mode-badge ${modeLower}`; } dispose() { if (this.hudElement && this.hudElement.parentNode) { this.hudElement.parentNode.removeChild(this.hudElement); } } }