feat: Implement aircraft type differentiation and movement indicators

Adds comprehensive visual differentiation system for 3D radar aircraft:

Aircraft Type Differentiation:
- Different 3D geometries for aircraft categories (helicopter, heavy, medium, light)
- Color coding: cyan=helicopter, blue=heavy, green=medium, yellow=light
- Size scaling based on aircraft weight class
- Emergency override: red color for emergency squawks and alerts

Movement Indicators:
- Aircraft orientation based on track/heading data
- Climb/descent indicators with 500 fpm threshold
- Green upward arrows for climbing aircraft
- Red downward arrows for descending aircraft
- Dynamic indicator management (add/remove based on vertical rate)

Technical Implementation:
- Modular aircraft geometry creation system
- Visual type detection based on Category field
- Enhanced material system with emissive properties
- Proper cleanup of climb/descent indicators
- Updated selection system to restore original aircraft colors

Addresses core visual enhancement requirements from issue #42.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-09-01 21:22:14 +02:00
commit 51a74ac85e

View file

@ -801,8 +801,7 @@ class SkyView {
if (!this.radar3d.aircraftMeshes.has(key)) { if (!this.radar3d.aircraftMeshes.has(key)) {
// Create new aircraft mesh only if within range // Create new aircraft mesh only if within range
if (withinRange) { if (withinRange) {
const geometry = new THREE.ConeGeometry(0.5, 2, 6); const { geometry, material } = this.create3DAircraftGeometry(aircraft);
const material = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material); const mesh = new THREE.Mesh(geometry, material);
this.radar3d.aircraftMeshes.set(key, mesh); this.radar3d.aircraftMeshes.set(key, mesh);
this.radar3d.scene.add(mesh); this.radar3d.scene.add(mesh);
@ -838,10 +837,8 @@ class SkyView {
mesh.position.set(x, y, z); mesh.position.set(x, y, z);
// Orient mesh based on track // Update aircraft visual indicators (direction, climb/descent)
if (aircraft.Track !== undefined) { this.update3DAircraftVisuals(mesh, aircraft);
mesh.rotation.y = -aircraft.Track * Math.PI / 180;
}
// Update trail if trails are enabled // Update trail if trails are enabled
if (this.radar3d.showTrails && aircraft.position_history) { if (this.radar3d.showTrails && aircraft.position_history) {
@ -1056,11 +1053,15 @@ class SkyView {
const icao = this.radar3d.selectedAircraft; const icao = this.radar3d.selectedAircraft;
// Reset mesh appearance // Reset mesh appearance to original aircraft color
const mesh = this.radar3d.aircraftMeshes.get(icao); const mesh = this.radar3d.aircraftMeshes.get(icao);
if (mesh) { const aircraft = this.aircraftManager.aircraftData.get(icao);
mesh.material.color.setHex(0x00ff00); // Back to green if (mesh && aircraft) {
mesh.material.emissive.setHex(0x000000); // Remove glow 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 // Reset label appearance
@ -1184,6 +1185,160 @@ class SkyView {
console.log(`3D trails ${this.radar3d.showTrails ? 'enabled' : 'disabled'}`); 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() { initialize3DControls() {
// Enable and initialize the Reset View button // Enable and initialize the Reset View button