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:
parent
c6aab821a3
commit
51a74ac85e
1 changed files with 165 additions and 10 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue