/**
* 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) => (
))}
);
}