/** * Stars component - renders nearby stars in 3D space */ import { useEffect, useState, useMemo } from 'react'; import { Billboard, Html } from '@react-three/drei'; import * as THREE from 'three'; import { request } from '../utils/request'; import { createLabelTexture } from '../utils/labelTexture'; interface Star { name: string; name_zh: string; distance_ly: number; ra: number; // Right Ascension in degrees dec: number; // Declination in degrees magnitude: number; color: string; position: THREE.Vector3; size: number; rawData?: any; // Add optional rawData field } /** * Convert RA/Dec to Cartesian coordinates * RA: Right Ascension (0-360 degrees) * Dec: Declination (-90 to 90 degrees) * Distance: fixed distance for celestial sphere */ function raDecToCartesian(ra: number, dec: number, distance: number = 5000) { // Convert to radians const raRad = (ra * Math.PI) / 180; const decRad = (dec * Math.PI) / 180; // Convert to Cartesian coordinates const x = distance * Math.cos(decRad) * Math.cos(raRad); const y = distance * Math.cos(decRad) * Math.sin(raRad); const z = distance * Math.sin(decRad); return new THREE.Vector3(x, y, z); } /** * Scale star brightness based on magnitude * Lower magnitude = brighter star */ function magnitudeToSize(magnitude: number): number { // Brighter stars (lower magnitude) should be slightly larger // But all stars should be very small compared to planets const normalized = Math.max(-2, Math.min(12, magnitude)); return Math.max(5, 20 - normalized * 1.2); } // Sub-component for individual star to handle label texture efficiently function StarObject({ star, geometry, mode, onStarClick }: { star: Star; geometry: THREE.SphereGeometry; mode: 'sky' | 'galaxy'; onStarClick?: (star: Star) => void; }) { const [hovered, setHovered] = useState(false); // Generate label texture const labelTexture = useMemo(() => { // Use Chinese name if available, otherwise use English name const displayName = star.name_zh || star.name; return createLabelTexture(displayName, null, "", "#FFFFFF"); }, [star.name_zh, star.name]); // Adjust visual parameters based on mode const baseSize = mode === 'galaxy' ? star.size * 8 : star.size; // Make stars larger in galaxy mode const visualSize = hovered ? baseSize * 1.5 : baseSize; // Scale up on hover const labelScale: [number, number, number] = mode === 'galaxy' ? [50, 25, 1] : [200, 100, 1]; // Smaller labels in galaxy mode const labelOffset = mode === 'galaxy' ? 4 : 1.05; // Offset factor // Handle cursor useEffect(() => { document.body.style.cursor = hovered ? 'pointer' : 'auto'; return () => { document.body.style.cursor = 'auto'; }; }, [hovered]); return ( { console.log('Star clicked:', star.name); e.stopPropagation(); onStarClick?.(star); }} onPointerOver={(e) => { console.log('Pointer over:', star.name); e.stopPropagation(); setHovered(true); }} onPointerOut={(e) => { e.stopPropagation(); setHovered(false); }} > {/* Star sphere */} {/* Star glow */} {/* Star name label */} {labelTexture && ( )} {/* Hover Tooltip in Galaxy Mode */} {mode === 'galaxy' && hovered && star.rawData && (
{star.name_zh || star.name}
距离: {star.rawData.distance_pc?.toFixed(2) ?? 'N/A'} pc (~{star.distance_ly?.toFixed(2) ?? 'N/A'} ly)
{star.rawData.spectral_type && (
光谱类型: {star.rawData.spectral_type}
)} {star.rawData.radius_solar && (
半径: {star.rawData.radius_solar.toFixed(2)} R☉
)} {star.rawData.mass_solar && (
质量: {star.rawData.mass_solar.toFixed(2)} M☉
)} {star.rawData.temperature_k && (
温度: {star.rawData.temperature_k.toFixed(0)} K
)} {star.rawData.star_count && (
恒星数: {star.rawData.star_count.toFixed(0)} 颗
)}
点击查看详细信息
)}
); } export function Stars({ mode = 'sky', onStarClick }: { mode?: 'sky' | 'galaxy'; onStarClick?: (star: Star) => void }) { const [stars, setStars] = useState([]); useEffect(() => { if (mode === 'galaxy') { // Galaxy Mode: Load from new star-systems API request.get('/star-systems', { params: { limit: 1000, exclude_solar: false } }) .then((res) => { const { systems } = res.data; // Process star systems data const starData = systems .filter((system: any) => system.position_x != null && system.position_y != null && system.position_z != null) .map((system: any) => { // Galaxy Mode: Use 3D coordinates from database (Parsecs) // Scale factor to make the visualization comfortable const SCALE = 100; const position = new THREE.Vector3( system.position_x * SCALE, system.position_y * SCALE, system.position_z * SCALE ); // Calculate size based on radius_solar if available const radius = system.radius_solar || 1.0; const size = Math.max(0.5, Math.log(radius + 1) * 2); // Calculate distance_ly if not provided const distance_ly = system.distance_ly || (system.distance_pc ? system.distance_pc * 3.26 : null); return { name: system.name, name_zh: system.name_zh, distance_ly, ra: system.ra, dec: system.dec, magnitude: system.magnitude, color: system.color || '#ffffff', position, size, // Pass complete system data for details view and hover tooltip rawData: system }; }); setStars(starData); }) .catch((err) => console.error('Failed to load star systems:', err)); } else { // Sky Mode: Load from static data API request.get('/celestial/static/star') .then((res) => { const data = res.data; const starData = data.items.map((item: any) => { // Sky Mode: Project to Celestial Sphere const position = raDecToCartesian(item.data.ra, item.data.dec, 5000); const size = magnitudeToSize(item.data.magnitude); return { name: item.name, name_zh: item.name_zh, distance_ly: item.data.distance_ly || (item.data.distance_pc ? item.data.distance_pc * 3.26 : null), ra: item.data.ra, dec: item.data.dec, magnitude: item.data.magnitude, color: item.data.color, position, size, rawData: item.data }; }); setStars(starData); }) .catch((err) => console.error('Failed to load stars:', err)); } }, [mode]); // Reuse geometry for all stars to improve performance const sphereGeometry = useMemo(() => new THREE.SphereGeometry(1, 16, 16), []); if (stars.length === 0) { return null; } return ( {stars.map((star) => ( ))} ); }