/** * Galaxies component - renders distant galaxies as billboards */ import { useEffect, useState, useMemo } from 'react'; import { Billboard, Text, useTexture } from '@react-three/drei'; import * as THREE from 'three'; import { fetchStaticData } from '../utils/api'; interface Galaxy { name: string; name_zh: string; type: string; distance_mly: number; // Distance in millions of light years ra: number; // Right Ascension in degrees dec: number; // Declination in degrees magnitude: number; diameter_kly: number; // Diameter in thousands of light years color: string; } /** * Create a procedural galaxy texture */ function createGalaxyTexture(color: string, type: string): THREE.Texture { const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256; const ctx = canvas.getContext('2d')!; const centerX = canvas.width / 2; const centerY = canvas.height / 2; const radius = canvas.width / 2; // Create radial gradient for galaxy glow const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius); // Parse color const tempColor = new THREE.Color(color); const r = Math.floor(tempColor.r * 255); const g = Math.floor(tempColor.g * 255); const b = Math.floor(tempColor.b * 255); if (type === 'spiral') { // Spiral galaxy: bright core with arms gradient.addColorStop(0, `rgba(255, 255, 255, 1.0)`); gradient.addColorStop(0.1, `rgba(${r}, ${g}, ${b}, 0.9)`); gradient.addColorStop(0.3, `rgba(${r}, ${g}, ${b}, 0.6)`); gradient.addColorStop(0.6, `rgba(${r}, ${g}, ${b}, 0.3)`); gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`); } else if (type === 'irregular') { // Irregular galaxy: more diffuse gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.8)`); gradient.addColorStop(0.4, `rgba(${r}, ${g}, ${b}, 0.5)`); gradient.addColorStop(0.8, `rgba(${r}, ${g}, ${b}, 0.2)`); gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`); } else { // Galactic center or elliptical: bright concentrated core gradient.addColorStop(0, `rgba(255, 255, 220, 1.0)`); gradient.addColorStop(0.2, `rgba(${r}, ${g}, ${b}, 0.9)`); gradient.addColorStop(0.5, `rgba(${r}, ${g}, ${b}, 0.5)`); gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`); } ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); // Add some star-like points for detail (only for spiral galaxies) if (type === 'spiral') { ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; for (let i = 0; i < 50; i++) { const angle = Math.random() * Math.PI * 2; const dist = Math.random() * radius * 0.7; const x = centerX + Math.cos(angle) * dist; const y = centerY + Math.sin(angle) * dist; const size = Math.random() * 1.5; ctx.fillRect(x, y, size, size); } } const texture = new THREE.CanvasTexture(canvas); texture.needsUpdate = true; return texture; } /** * Convert RA/Dec to Cartesian coordinates for distant objects */ function raDecToCartesian(ra: number, dec: number, distance: number) { 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); } /** * Calculate visual size based on actual diameter and distance */ function calculateAngularSize(diameterKly: number, distanceMly: number): number { // Angular diameter in radians const angularDiameter = diameterKly / (distanceMly * 1000); // Significantly increased multiplier for larger distance return Math.max(20, angularDiameter * 8000); } export function Galaxies() { const [galaxies, setGalaxies] = useState([]); useEffect(() => { // Load galaxy data from API fetchStaticData('galaxy') .then((response) => { // Convert API response to Galaxy format const galaxyData = response.items.map((item) => ({ name: item.name, name_zh: item.name_zh, type: item.data.type, distance_mly: item.data.distance_mly, ra: item.data.ra, dec: item.data.dec, magnitude: item.data.magnitude, diameter_kly: item.data.diameter_kly, color: item.data.color, })); setGalaxies(galaxyData); }) .catch((err) => console.error('Failed to load galaxies:', err)); }, []); const galaxyData = useMemo(() => { return galaxies.map((galaxy) => { // Place galaxies on celestial sphere at fixed distance for visualization const visualDistance = 5000; // Fixed distance for celestial sphere (was 200) const position = raDecToCartesian(galaxy.ra, galaxy.dec, visualDistance); // Calculate visual size based on actual properties const size = galaxy.type === 'galactic_center' ? 80 // Much larger for Milky Way center : calculateAngularSize(galaxy.diameter_kly, galaxy.distance_mly); // Create procedural texture for this galaxy const texture = createGalaxyTexture(galaxy.color, galaxy.type); return { ...galaxy, position, size, texture, }; }); }, [galaxies]); if (galaxyData.length === 0) { return null; } return ( {galaxyData.map((galaxy) => ( {/* Galaxy texture */} {/* Galaxy name label - positioned slightly outward from galaxy */} {galaxy.name_zh} ))} ); }