diff --git a/assets/static/js/app.js b/assets/static/js/app.js index 8134dd2..0d780fb 100644 --- a/assets/static/js/app.js +++ b/assets/static/js/app.js @@ -801,8 +801,7 @@ class SkyView { if (!this.radar3d.aircraftMeshes.has(key)) { // Create new aircraft mesh only if within range if (withinRange) { - const geometry = new THREE.ConeGeometry(0.5, 2, 6); - const material = new THREE.MeshLambertMaterial({ color: 0x00ff00 }); + const { geometry, material } = this.create3DAircraftGeometry(aircraft); const mesh = new THREE.Mesh(geometry, material); this.radar3d.aircraftMeshes.set(key, mesh); this.radar3d.scene.add(mesh); @@ -838,10 +837,8 @@ class SkyView { mesh.position.set(x, y, z); - // Orient mesh based on track - if (aircraft.Track !== undefined) { - mesh.rotation.y = -aircraft.Track * Math.PI / 180; - } + // Update aircraft visual indicators (direction, climb/descent) + this.update3DAircraftVisuals(mesh, aircraft); // Update trail if trails are enabled if (this.radar3d.showTrails && aircraft.position_history) { @@ -1056,11 +1053,15 @@ class SkyView { const icao = this.radar3d.selectedAircraft; - // Reset mesh appearance + // Reset mesh appearance to original aircraft color const mesh = this.radar3d.aircraftMeshes.get(icao); - if (mesh) { - mesh.material.color.setHex(0x00ff00); // Back to green - mesh.material.emissive.setHex(0x000000); // Remove glow + const aircraft = this.aircraftManager.aircraftData.get(icao); + if (mesh && aircraft) { + const visualType = this.getAircraftVisualType(aircraft); + const originalColor = this.getAircraftColor(aircraft, visualType); + mesh.material.color.setHex(originalColor); + mesh.material.emissive.setHex(originalColor); + mesh.material.emissiveIntensity = 0.1; // Restore original glow } // Reset label appearance @@ -1184,6 +1185,160 @@ class SkyView { console.log(`3D trails ${this.radar3d.showTrails ? 'enabled' : 'disabled'}`); } } + + // Aircraft visual type determination based on category and other data + getAircraftVisualType(aircraft) { + const category = aircraft.Category || ''; + + // Helicopter detection + if (category.toLowerCase().includes('helicopter') || + category.toLowerCase().includes('rotorcraft')) { + return 'helicopter'; + } + + // Weight-based categories + if (category.includes('Heavy')) return 'heavy'; + if (category.includes('Medium')) return 'medium'; + if (category.includes('Light')) return 'light'; + + // Size-based categories (fallback) + if (category.includes('Large')) return 'heavy'; + if (category.includes('Small')) return 'light'; + + // Default to medium commercial aircraft + return 'medium'; + } + + // Get aircraft color based on type and status + getAircraftColor(aircraft, visualType) { + // Emergency override + if (aircraft.Emergency && aircraft.Emergency !== 'None') { + return 0xff0000; // Red for emergencies + } + + // Special squawk codes + if (aircraft.Squawk) { + if (aircraft.Squawk === '7500' || aircraft.Squawk === '7600' || aircraft.Squawk === '7700') { + return 0xff0000; // Red for emergency squawks + } + } + + // Type-based colors + switch (visualType) { + case 'helicopter': + return 0x00ffff; // Cyan for helicopters + case 'heavy': + return 0x0088ff; // Blue for heavy aircraft + case 'medium': + return 0x00ff00; // Green for medium aircraft (default) + case 'light': + return 0xffff00; // Yellow for light aircraft + default: + return 0x00ff00; // Default green + } + } + + // Create appropriate 3D geometry and material for aircraft type + create3DAircraftGeometry(aircraft) { + const visualType = this.getAircraftVisualType(aircraft); + const color = this.getAircraftColor(aircraft, visualType); + + let geometry, scale = 1; + + switch (visualType) { + case 'helicopter': + // Helicopter: Wider, flatter shape with rotor disk + geometry = new THREE.CylinderGeometry(0.8, 0.4, 0.6, 8); + scale = 1.0; + break; + + case 'heavy': + // Heavy aircraft: Large, wide fuselage + geometry = new THREE.CylinderGeometry(0.4, 0.8, 3.0, 8); + scale = 1.3; + break; + + case 'light': + // Light aircraft: Small, simple shape + geometry = new THREE.ConeGeometry(0.3, 1.5, 6); + scale = 0.7; + break; + + case 'medium': + default: + // Medium/default: Standard cone shape + geometry = new THREE.ConeGeometry(0.5, 2, 6); + scale = 1.0; + break; + } + + const material = new THREE.MeshLambertMaterial({ + color: color, + emissive: color, + emissiveIntensity: 0.1 + }); + + // Scale geometry if needed + if (scale !== 1.0) { + geometry.scale(scale, scale, scale); + } + + return { geometry, material }; + } + + // Update aircraft visual indicators (direction, climb/descent) + update3DAircraftVisuals(mesh, aircraft) { + // Set aircraft direction based on track + if (aircraft.Track !== undefined && aircraft.Track !== 0) { + mesh.rotation.y = -aircraft.Track * Math.PI / 180; + } + + // Add climb/descent indicator + this.update3DClimbDescentIndicator(mesh, aircraft); + } + + // Add or update climb/descent visual indicator + update3DClimbDescentIndicator(mesh, aircraft) { + const verticalRate = aircraft.VerticalRate || 0; + const threshold = 500; // feet per minute + + // Remove existing indicator + const existingIndicator = mesh.getObjectByName('climbIndicator'); + if (existingIndicator) { + mesh.remove(existingIndicator); + } + + // Add new indicator if significant vertical movement + if (Math.abs(verticalRate) > threshold) { + let indicatorGeometry, indicatorMaterial; + + if (verticalRate > threshold) { + // Climbing - green upward arrow + indicatorGeometry = new THREE.ConeGeometry(0.2, 0.8, 4); + indicatorMaterial = new THREE.MeshBasicMaterial({ + color: 0x00ff00, + transparent: true, + opacity: 0.8 + }); + } else if (verticalRate < -threshold) { + // Descending - red downward arrow + indicatorGeometry = new THREE.ConeGeometry(0.2, 0.8, 4); + indicatorMaterial = new THREE.MeshBasicMaterial({ + color: 0xff0000, + transparent: true, + opacity: 0.8 + }); + indicatorGeometry.rotateX(Math.PI); // Flip for downward arrow + } + + if (indicatorGeometry && indicatorMaterial) { + const indicator = new THREE.Mesh(indicatorGeometry, indicatorMaterial); + indicator.name = 'climbIndicator'; + indicator.position.set(0, 2, 0); // Position above aircraft + mesh.add(indicator); + } + } + } initialize3DControls() { // Enable and initialize the Reset View button