cosmo/frontend/src/components/OrbitRenderer.tsx

117 lines
3.2 KiB
TypeScript

/**
* 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';
import { request } from '../utils/request';
interface OrbitData {
bodyId: string;
bodyName: string;
bodyNameZh: string | null;
points: THREE.Vector3[];
color: string;
numPoints: number;
periodDays: number | null;
}
interface OrbitRendererProps {
visible?: boolean;
}
export function OrbitRenderer({ visible = true }: OrbitRendererProps) {
const [orbits, setOrbits] = useState<OrbitData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchOrbits = async () => {
try {
// Fetch precomputed orbits from backend
const response = await request.get('/celestial/orbits');
const data = response.data;
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;
}
// 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());
}
}
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);
} catch (err) {
console.error('❌ Failed to load orbits:', err);
setError(err instanceof Error ? err.message : 'Unknown error');
setLoading(false);
}
};
fetchOrbits();
}, []);
if (loading) {
return null;
}
if (error) {
console.error('⚠️ Orbit rendering error:', error);
return null;
}
if (orbits.length === 0) {
return null;
}
return (
<group visible={visible}>
{orbits.map((orbit) => (
<Line
key={orbit.bodyId}
points={orbit.points}
color={orbit.color}
lineWidth={1.5}
opacity={0.4}
transparent
/>
))}
</group>
);
}