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); } }