/** * Stars component - renders nearby stars in 3D space */ import { useEffect, useState, useMemo } from 'react'; import { Text, Billboard } from '@react-three/drei'; import * as THREE from 'three'; import { request } from '../utils/request'; 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; } /** * 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); } export function Stars() { const [stars, setStars] = useState([]); useEffect(() => { // Load star data from API request.get('/celestial/static/star') .then((res) => { const data = res.data; // API returns { category, items: [{ id, name, name_zh, data: {...} }] } const starData = data.items.map((item: any) => ({ name: item.name, name_zh: item.name_zh, distance_ly: item.data.distance_ly, ra: item.data.ra, dec: item.data.dec, magnitude: item.data.magnitude, color: item.data.color, })); setStars(starData); }) .catch((err) => console.error('Failed to load stars:', err)); }, []); const starData = useMemo(() => { return stars.map((star) => { // Place all stars on a celestial sphere at fixed distance (5000 units) // This way they appear as background objects, similar to constellations const position = raDecToCartesian(star.ra, star.dec, 5000); // Size based on brightness (magnitude) const size = magnitudeToSize(star.magnitude); return { ...star, position, size, }; }); }, [stars]); // Reuse geometry for all stars to improve performance const sphereGeometry = useMemo(() => new THREE.SphereGeometry(1, 16, 16), []); if (starData.length === 0) { return null; } return ( {starData.map((star) => ( {/* Star sphere */} {/* Star glow */} {/* Star name label - positioned radially outward from star */} {star.name_zh} ))} ); }