131 lines
3.8 KiB
TypeScript
131 lines
3.8 KiB
TypeScript
/**
|
|
* Constellations component - renders major constellations with connecting lines
|
|
*/
|
|
import { useEffect, useState, useMemo } from 'react';
|
|
import { Line, Text, Billboard } from '@react-three/drei';
|
|
import * as THREE from 'three';
|
|
import { fetchStaticData } from '../utils/api';
|
|
|
|
interface ConstellationStar {
|
|
name: string;
|
|
ra: number; // Right Ascension in degrees
|
|
dec: number; // Declination in degrees
|
|
}
|
|
|
|
interface Constellation {
|
|
name: string;
|
|
name_zh: string;
|
|
stars: ConstellationStar[];
|
|
lines: [number, number][]; // Indices of stars to connect
|
|
}
|
|
|
|
/**
|
|
* Convert RA/Dec to Cartesian coordinates
|
|
* Use fixed distance for constellation stars to create celestial sphere effect
|
|
*/
|
|
function raDecToCartesian(ra: number, dec: number, distance: number = 100) {
|
|
const raRad = (ra * Math.PI) / 180;
|
|
const decRad = (dec * Math.PI) / 180;
|
|
|
|
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);
|
|
}
|
|
|
|
export function Constellations() {
|
|
const [constellations, setConstellations] = useState<Constellation[]>([]);
|
|
|
|
useEffect(() => {
|
|
// Load constellation data from API
|
|
fetchStaticData('constellation')
|
|
.then((response) => {
|
|
// Convert API response to Constellation format
|
|
const constellationData = response.items.map((item) => ({
|
|
name: item.name,
|
|
name_zh: item.name_zh,
|
|
stars: item.data.stars,
|
|
lines: item.data.lines,
|
|
}));
|
|
setConstellations(constellationData);
|
|
})
|
|
.catch((err) => console.error('Failed to load constellations:', err));
|
|
}, []);
|
|
|
|
const constellationLines = useMemo(() => {
|
|
return constellations.map((constellation) => {
|
|
// Convert all stars to 3D positions
|
|
const starPositions = constellation.stars.map((star) =>
|
|
raDecToCartesian(star.ra, star.dec)
|
|
);
|
|
|
|
// Create line segments based on connection indices
|
|
const lineSegments = constellation.lines.map(([startIdx, endIdx]) => ({
|
|
start: starPositions[startIdx],
|
|
end: starPositions[endIdx],
|
|
}));
|
|
|
|
// Calculate center position for label (average of all stars)
|
|
const center = starPositions.reduce(
|
|
(acc, pos) => acc.add(pos),
|
|
new THREE.Vector3()
|
|
).divideScalar(starPositions.length);
|
|
|
|
return {
|
|
name: constellation.name,
|
|
nameZh: constellation.name_zh,
|
|
starPositions,
|
|
lineSegments,
|
|
center,
|
|
};
|
|
});
|
|
}, [constellations]);
|
|
|
|
if (constellationLines.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<group>
|
|
{constellationLines.map((constellation) => (
|
|
<group key={constellation.name}>
|
|
{/* Render constellation stars */}
|
|
{constellation.starPositions.map((pos, idx) => (
|
|
<mesh key={`${constellation.name}-star-${idx}`} position={pos}>
|
|
<sphereGeometry args={[0.3, 8, 8]} />
|
|
<meshBasicMaterial color="#FFFFFF" transparent opacity={0.8} />
|
|
</mesh>
|
|
))}
|
|
|
|
{/* Render connecting lines */}
|
|
{constellation.lineSegments.map((segment, idx) => (
|
|
<Line
|
|
key={`${constellation.name}-line-${idx}`}
|
|
points={[segment.start, segment.end]}
|
|
color="#4488FF"
|
|
lineWidth={1}
|
|
transparent
|
|
opacity={0.5}
|
|
/>
|
|
))}
|
|
|
|
{/* Constellation name label */}
|
|
<Billboard position={constellation.center}>
|
|
<Text
|
|
fontSize={2}
|
|
color="#88AAFF"
|
|
anchorX="center"
|
|
anchorY="middle"
|
|
outlineWidth={0.1}
|
|
outlineColor="#000000"
|
|
>
|
|
{constellation.nameZh}
|
|
</Text>
|
|
</Billboard>
|
|
</group>
|
|
))}
|
|
</group>
|
|
);
|
|
}
|