138 lines
4.0 KiB
TypeScript
138 lines
4.0 KiB
TypeScript
/**
|
|
* 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<Star[]>([]);
|
|
|
|
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 (
|
|
<group>
|
|
{starData.map((star) => (
|
|
<group key={star.name}>
|
|
{/* Star sphere */}
|
|
<mesh position={star.position} geometry={sphereGeometry} scale={[star.size, star.size, star.size]}>
|
|
<meshBasicMaterial
|
|
color={star.color}
|
|
transparent
|
|
opacity={0.9}
|
|
blending={THREE.AdditiveBlending}
|
|
/>
|
|
</mesh>
|
|
|
|
{/* Star glow */}
|
|
<mesh position={star.position} geometry={sphereGeometry} scale={[star.size * 2, star.size * 2, star.size * 2]}>
|
|
<meshBasicMaterial
|
|
color={star.color}
|
|
transparent
|
|
opacity={0.2}
|
|
blending={THREE.AdditiveBlending}
|
|
/>
|
|
</mesh>
|
|
|
|
{/* Star name label - positioned radially outward from star */}
|
|
<Billboard position={star.position.clone().multiplyScalar(1.05)}>
|
|
<Text
|
|
fontSize={40} // Increased font size
|
|
color="#FFFFFF"
|
|
anchorX="center"
|
|
anchorY="middle"
|
|
outlineWidth={2}
|
|
outlineColor="#000000"
|
|
>
|
|
{star.name_zh}
|
|
</Text>
|
|
</Billboard>
|
|
</group>
|
|
))}
|
|
</group>
|
|
);
|
|
}
|