/** * Stars component - renders nearby stars in 3D space */ import { useEffect, useState, useMemo } from 'react'; import { Billboard } 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; } /** * 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 }: { star: Star; geometry: THREE.SphereGeometry }) { // Generate label texture const labelTexture = useMemo(() => { return createLabelTexture(star.name_zh, null, "", "#FFFFFF"); }, [star.name_zh]); return ( {/* Star sphere */} {/* Star glow */} {/* Star name label - positioned radially outward from star */} {labelTexture && ( )} ); } 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) => { // Place all stars on a celestial sphere at fixed distance (5000 units) 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, ra: item.data.ra, dec: item.data.dec, magnitude: item.data.magnitude, color: item.data.color, position, size }; }); setStars(starData); }) .catch((err) => console.error('Failed to load stars:', err)); }, []); // 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) => ( ))} ); }