From fb5937af2f0fcffdf30caa3c2696ad9a80827efb Mon Sep 17 00:00:00 2001 From: jedarden Date: Sat, 25 Apr 2026 01:42:56 -0400 Subject: [PATCH] feat(dashboard): add Fresnel zone debug overlay with shared geometry module Fresnel zone ellipsoids render for all active links when the debug layer is toggled on. Uses shared fresnel.js helper for geometry computation, with hover tooltips showing link details and click-to-select. viz3d.js refactored to use the shared module instead of duplicating calculations. Co-Authored-By: Claude Opus 4.7 --- dashboard/js/app.js | 7 ++ dashboard/js/viz3d.js | 150 +++++++++++++++++++++--------------------- 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/dashboard/js/app.js b/dashboard/js/app.js index 7bdf565..4401863 100644 --- a/dashboard/js/app.js +++ b/dashboard/js/app.js @@ -2290,6 +2290,13 @@ } }; + // Expose for viz3d.js to call when links rebuild + window.rebuildFresnelDebugEllipsoids = function() { + if (state.fresnelDebugVisible) { + rebuildFresnelDebugEllipsoids(); + } + }; + // ============================================ // Fresnel Zone Debug Overlay // ============================================ diff --git a/dashboard/js/viz3d.js b/dashboard/js/viz3d.js index 3ad6cb5..8afacfe 100644 --- a/dashboard/js/viz3d.js +++ b/dashboard/js/viz3d.js @@ -417,9 +417,9 @@ const Viz3D = (function () { _linkLines.set(id, line); }); - // Update Fresnel zones if visible - if (_fresnelZonesVisible) { - rebuildActiveFresnelZones(); + // Notify app.js to rebuild Fresnel debug overlay if visible + if (_fresnelZonesVisible && window.rebuildFresnelDebugEllipsoids) { + rebuildFresnelDebugEllipsoids(); } } @@ -2890,45 +2890,46 @@ const Viz3D = (function () { }; let _fresnelZones = []; // Array of THREE.Mesh for explainability Fresnel zones - let _fresnelActiveZones = []; // Array of THREE.Line for active link Fresnel zones (wireframe) + let _fresnelActiveZones = []; // Array of ellipsoid objects from shared Fresnel module let _fresnelZonesVisible = false; // Toggle state for active link Fresnel zones /** * Calculate Fresnel zone ellipsoid geometry for a link. + * Uses the shared Fresnel module from fresnel.js when available. + * Falls back to a local calculation for backward compatibility. * @param {THREE.Vector3} tx - Transmitter position * @param {THREE.Vector3} rx - Receiver position * @param {number} zoneNumber - Fresnel zone number (1-based) * @returns {Object} Ellipsoid parameters: { center, semiAxes, rotation } */ function _calculateFresnelZone(tx, rx, zoneNumber) { - // WiFi wavelength and Fresnel zone constants - const lambda = FRESNEL_CONFIG.wavelength; // ~0.123 m for 2.4 GHz + // For zone 1, use the shared Fresnel module if available + if (window.Fresnel && zoneNumber === 1) { + var channel = 6; // default 2.4 GHz + var params = Fresnel.calculateFresnelEllipsoid(tx, rx, channel); + return { + center: params.center, + semiAxes: params.semiAxes, + rotation: params.rotation, + zoneNumber: 1 + }; + } + + // Fallback for multi-zone or when Fresnel module is unavailable + const lambda = FRESNEL_CONFIG.wavelength; const n = zoneNumber; - - // Direct distance between TX and RX const d = tx.distanceTo(rx); - - // Fresnel zone path difference: n * lambda / 2 const deltaL = n * lambda / 2; - - // Ellipsoid semi-axes calculation - // For a prolate spheroid with foci at tx and rx: - // Semi-major axis (a) = (d + deltaL) / 2 - // Semi-minor axis (b) = sqrt(deltaL * (2*d + deltaL)) / 2 const a = (d + deltaL) / 2; const b = Math.sqrt(Math.max(0, deltaL * (2 * d + deltaL))) / 2; - - // Center of ellipsoid (midpoint between TX and RX) const center = new THREE.Vector3().addVectors(tx, rx).multiplyScalar(0.5); - - // Rotation: align with TX-RX axis const direction = new THREE.Vector3().subVectors(rx, tx).normalize(); - const up = new THREE.Vector3(0, 1, 0); - const quaternion = new THREE.Quaternion().setFromUnitVectors(up, direction); + const xAxis = new THREE.Vector3(1, 0, 0); + const quaternion = new THREE.Quaternion().setFromUnitVectors(xAxis, direction); return { center: center, - semiAxes: new THREE.Vector3(b, b, a), // X, Y, Z semi-axes (Z is along link axis) + semiAxes: new THREE.Vector3(a, b, b), rotation: quaternion, zoneNumber: n }; @@ -3004,51 +3005,43 @@ const Viz3D = (function () { /** * Create a wireframe Fresnel zone ellipsoid for an active link. + * Uses the shared Fresnel module from fresnel.js for zone 1. * @param {THREE.Vector3} tx - Transmitter position * @param {THREE.Vector3} rx - Receiver position * @param {number} zoneNumber - Fresnel zone number (1-5) * @param {number} color - Color hex value - * @returns {THREE.LineSegments|null} The created wireframe mesh + * @returns {Object|null} Ellipsoid object { wireframe, fill, data } or null */ function _createWireframeFresnelZone(tx, rx, zoneNumber, color) { if (!_scene) return null; - // Calculate Fresnel zone geometry + // Use the shared Fresnel module for zone 1 (first Fresnel zone) + if (window.Fresnel && zoneNumber === 1) { + var channel = 6; // default 2.4 GHz + var ellipsoid = Fresnel.addFresnelEllipsoid(tx, rx, channel, color, { + wireframeOpacity: 0.4, + fillOpacity: 0.06 + }); + return ellipsoid; + } + + // Fallback: torus-based wireframe for higher zones var zone = _calculateFresnelZone(tx, rx, zoneNumber); if (!zone) return null; - // Create wireframe ellipsoid using TorusGeometry (thin tube) - // Torus with tube radius ~0.005m, following the ellipsoid path - var tubeRadius = 0.008; // 8mm tube thickness for visibility + var tubeRadius = 0.008; var tubularSegments = 64; var radialSegments = 8; var geometry = new THREE.TorusGeometry( - zone.semiAxes.z, // major radius (distance from center to ellipsoid surface along Z axis) + zone.semiAxes.x, tubeRadius, tubularSegments, radialSegments ); - // Apply scaling to create ellipsoid instead of torus - // Scale X and Y by semi-minor / semi-major ratio - var scaleRatio = zone.semiAxes.x / zone.semiAxes.z; - geometry.scale(scaleRatio, scaleRatio, 1.0); + var scaleRatio = zone.semiAxes.y / zone.semiAxes.x; + geometry.scale(1, scaleRatio, scaleRatio); - // Position and rotate - var mesh = new THREE.Mesh(geometry); - - // Rotate to align with link direction - mesh.position.copy(zone.center); - mesh.quaternion.copy(zone.rotation); - - // Orient the torus: rotate 90 degrees so tube lies in correct plane - var orientQuat = new THREE.Quaternion().setFromAxisAngle( - new THREE.Vector3(1, 0, 0), - Math.PI / 2 - ); - mesh.quaternion.multiply(orientQuat); - - // Create wireframe material var material = new THREE.LineBasicMaterial({ color: color || FRESNEL_CONFIG.color, transparent: true, @@ -3056,16 +3049,19 @@ const Viz3D = (function () { depthTest: false }); - // Convert mesh to wireframe var wireframe = new THREE.LineSegments( new THREE.WireframeGeometry(geometry), material ); - wireframe.position.copy(mesh.position); - wireframe.quaternion.copy(mesh.quaternion); + wireframe.position.copy(zone.center); + wireframe.quaternion.copy(zone.rotation); - // Clean up temporary mesh - mesh.geometry.dispose(); + // Orient torus to correct plane + var orientQuat = new THREE.Quaternion().setFromAxisAngle( + new THREE.Vector3(1, 0, 0), + Math.PI / 2 + ); + wireframe.quaternion.multiply(orientQuat); _scene.add(wireframe); return wireframe; @@ -3073,15 +3069,13 @@ const Viz3D = (function () { /** * Rebuild Fresnel zone visualization for all active links. - * Creates wireframe ellipsoids for the first 3 Fresnel zones of each active link. + * Uses the shared Fresnel module for first Fresnel zone ellipsoids. */ function rebuildActiveFresnelZones() { - // Clear existing Fresnel zones clearActiveFresnelZones(); if (!_fresnelZonesVisible) return; - // Get active links _activeLinks.forEach(function(link, linkID) { var txMesh = _nodeMeshes.get(link.node_mac); var rxMesh = _nodeMeshes.get(link.peer_mac); @@ -3090,50 +3084,54 @@ const Viz3D = (function () { var tx = txMesh.position; var rx = rxMesh.position; - // Determine color based on link health var healthData = _linkHealth.get(linkID); var healthScore = healthData ? healthData.score : 0.5; var zoneColor = _getHealthColor(healthScore); - // Create Fresnel zones for first 3 zones - for (var n = 1; n <= 3; n++) { - var wireframe = _createWireframeFresnelZone(tx, rx, n, zoneColor); - if (wireframe) { - _fresnelActiveZones.push(wireframe); - wireframe.userData = { - linkID: linkID, - zoneNumber: n - }; + // Create first Fresnel zone using shared module + var ellipsoid = _createWireframeFresnelZone(tx, rx, 1, zoneColor); + if (ellipsoid) { + _fresnelActiveZones.push(ellipsoid); + // Store link metadata for interactions + if (ellipsoid.wireframe) { + ellipsoid.wireframe.userData.linkID = linkID; + ellipsoid.wireframe.userData.healthScore = healthScore; + } + if (ellipsoid.fill) { + ellipsoid.fill.userData.linkID = linkID; + ellipsoid.fill.userData.healthScore = healthScore; } } }); } /** - * Clear all active Fresnel zone wireframes. + * Clear all active Fresnel zone ellipsoids. */ function clearActiveFresnelZones() { - _fresnelActiveZones.forEach(function(wireframe) { - if (_scene) { - _scene.remove(wireframe); + _fresnelActiveZones.forEach(function(item) { + if (window.Fresnel && item && item.wireframe) { + // Shared Fresnel module ellipsoid + Fresnel.removeFresnelEllipsoid(item); + } else if (item && item.geometry) { + // Legacy wireframe object + if (_scene) _scene.remove(item); + item.geometry.dispose(); + item.material.dispose(); } - wireframe.geometry.dispose(); - wireframe.material.dispose(); }); _fresnelActiveZones = []; } /** * Toggle visibility of Fresnel zone overlays for active links. + * Delegates to the app.js Fresnel debug overlay system. * @param {boolean} visible - Whether to show Fresnel zones */ function toggleFresnelZones(visible) { _fresnelZonesVisible = visible; - - if (visible) { - rebuildActiveFresnelZones(); - } else { - clearActiveFresnelZones(); + if (window.toggleFresnelDebugOverlay) { + toggleFresnelDebugOverlay(visible); } }