cosmo/frontend/src/components/OrbitRenderer.tsx

126 lines
3.6 KiB
TypeScript
Raw Normal View History

2025-11-29 15:10:00 +00:00
/**
* OrbitRenderer - Unified orbit rendering component
* Renders precomputed orbital paths for all celestial bodies (planets and dwarf planets)
*/
import { useEffect, useState } from 'react';
import { Line } from '@react-three/drei';
import * as THREE from 'three';
import { scalePosition } from '../utils/scaleDistance';
interface OrbitData {
bodyId: string;
bodyName: string;
bodyNameZh: string | null;
points: THREE.Vector3[];
color: string;
numPoints: number;
periodDays: number | null;
}
export function OrbitRenderer() {
const [orbits, setOrbits] = useState<OrbitData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchOrbits = async () => {
console.log('🌌 Fetching orbital data from backend...');
try {
// Fetch precomputed orbits from backend
const response = await fetch('http://localhost:8000/api/celestial/orbits');
if (!response.ok) {
throw new Error(`Failed to fetch orbits: ${response.statusText}`);
}
const data = await response.json();
if (!data.orbits || data.orbits.length === 0) {
console.warn('⚠️ No orbital data found in database');
setLoading(false);
setError('No orbital data available. Please generate orbits first.');
return;
}
console.log(`📊 Processing ${data.orbits.length} orbits...`);
// Convert to Three.js format
const orbitData: OrbitData[] = data.orbits.map((orbit: any) => {
// Convert position points to Vector3 with scaling
const points = orbit.points.map((p: any) => {
const scaled = scalePosition(p.x, p.y, p.z);
// Convert to Three.js coordinate system (x, z, y)
return new THREE.Vector3(scaled.x, scaled.z, scaled.y);
});
// Close the orbit loop if first and last points are close
if (points.length > 1) {
const firstPoint = points[0];
const lastPoint = points[points.length - 1];
const distance = firstPoint.distanceTo(lastPoint);
// If endpoints are close (within 5 units), close the loop
if (distance < 5) {
points.push(firstPoint.clone());
}
}
console.log(`${orbit.body_name_zh || orbit.body_name}: ${points.length} points`);
return {
bodyId: orbit.body_id,
bodyName: orbit.body_name,
bodyNameZh: orbit.body_name_zh,
points,
color: orbit.color || '#CCCCCC',
numPoints: orbit.num_points,
periodDays: orbit.period_days,
};
});
setOrbits(orbitData);
setLoading(false);
console.log(`🎉 Loaded ${orbitData.length} orbits successfully`);
} catch (err) {
console.error('❌ Failed to load orbits:', err);
setError(err instanceof Error ? err.message : 'Unknown error');
setLoading(false);
}
};
fetchOrbits();
}, []);
if (loading) {
console.log('⏳ Loading orbits...');
return null;
}
if (error) {
console.error('⚠️ Orbit rendering error:', error);
return null;
}
if (orbits.length === 0) {
console.warn('⚠️ No orbits to render');
return null;
}
return (
<group>
{orbits.map((orbit) => (
<Line
key={orbit.bodyId}
points={orbit.points}
color={orbit.color}
lineWidth={1.5}
opacity={0.4}
transparent
/>
))}
</group>
);
}