/**
* CelestialBody component - renders a planet or probe with textures
*/
import { useRef, useMemo, useState, useEffect } from 'react';
import { Mesh, DoubleSide } from 'three';
import { useFrame } from '@react-three/fiber';
import { useTexture, Html } from '@react-three/drei';
import type { CelestialBody as CelestialBodyType } from '../types';
import { calculateRenderPosition, getOffsetDescription } from '../utils/renderPosition';
import { fetchBodyResources } from '../utils/api';
interface CelestialBodyProps {
body: CelestialBodyType;
allBodies: CelestialBodyType[];
}
// Saturn Rings component - multiple rings for band effect
function SaturnRings() {
return (
{/* Inner bright ring */}
{/* Middle darker band */}
{/* Outer bright ring */}
{/* Cassini Division (gap) */}
{/* A Ring (outer) */}
);
}
// Planet component with texture
function Planet({ body, size, emissive, emissiveIntensity, allBodies }: {
body: CelestialBodyType;
size: number;
emissive: string;
emissiveIntensity: number;
allBodies: CelestialBodyType[];
}) {
const meshRef = useRef(null);
const position = body.positions[0];
const [texturePath, setTexturePath] = useState(undefined);
// Use smart render position calculation
const renderPosition = useMemo(() => {
return calculateRenderPosition(body, allBodies);
}, [position.x, position.y, position.z, body, allBodies]);
const scaledPos = { x: renderPosition.x, y: renderPosition.y, z: renderPosition.z };
// Fetch texture from backend API
useEffect(() => {
fetchBodyResources(body.id, 'texture')
.then((response) => {
// Find the main texture (not atmosphere or night layers)
const mainTexture = response.resources.find(
(r) => !r.file_path.includes('atmosphere') && !r.file_path.includes('night')
);
if (mainTexture) {
// Construct full URL from file_path
// file_path is like "texture/2k_sun.jpg", need to add "upload/" prefix
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = import.meta.env.VITE_API_BASE_URL ? '' : ':8000';
setTexturePath(`${protocol}//${hostname}${port}/upload/${mainTexture.file_path}`);
} else {
setTexturePath(null);
}
})
.catch((err) => {
console.error(`Failed to load texture for ${body.name}:`, err);
setTexturePath(null);
});
}, [body.id, body.name]);
// Show nothing while loading
if (texturePath === undefined) {
return null;
}
return ;
}
// Separate component to handle texture loading
function PlanetMesh({ body, size, emissive, emissiveIntensity, scaledPos, texturePath, position, meshRef, hasOffset, allBodies }: {
body: CelestialBodyType;
size: number;
emissive: string;
emissiveIntensity: number;
scaledPos: { x: number; y: number; z: number };
texturePath: string | null;
position: { x: number; y: number; z: number };
meshRef: React.RefObject;
hasOffset: boolean;
allBodies: CelestialBodyType[];
}) {
// Load texture if path is provided
const texture = texturePath ? useTexture(texturePath) : null;
// Slow rotation for visual effect
useFrame((_, delta) => {
if (meshRef.current) {
meshRef.current.rotation.y += delta * 0.1;
}
});
// Calculate ACTUAL distance from Sun for display (not scaled)
const distance = Math.sqrt(position.x ** 2 + position.y ** 2 + position.z ** 2);
// Get offset description if this body has one
const offsetDesc = hasOffset ? getOffsetDescription(body, allBodies) : null;
return (
{texture ? (
) : (
)}
{/* Saturn Rings */}
{body.name === 'Saturn' && }
{/* Sun glow effect */}
{body.type === 'star' && (
<>
>
)}
{/* Name label */}
{body.name_zh || body.name}
{offsetDesc && (
<>
{offsetDesc}
>
)}
{distance.toFixed(2)} AU
);
}
export function CelestialBody({ body, allBodies }: CelestialBodyProps) {
// Get the current position (use the first position for now)
const position = body.positions[0];
if (!position) return null;
// Skip probes - they will use 3D models
if (body.type === 'probe') {
return null;
}
// Determine size based on body type
const appearance = useMemo(() => {
if (body.type === 'star') {
return {
size: 0.4, // Slightly larger sun for better visibility
emissive: '#FDB813',
emissiveIntensity: 1.5,
};
}
// Satellite (natural moons) - small size with slight glow for visibility
if (body.type === 'satellite') {
const satelliteSizes: Record = {
Moon: 0.15, // Small but visible
// Add other satellites here as needed
};
return {
size: satelliteSizes[body.name] || 0.12,
emissive: '#888888', // Slight glow to make it visible
emissiveIntensity: 0.4,
};
}
// Planet sizes - balanced for visibility with smaller probes
const planetSizes: Record = {
Mercury: 0.35, // Slightly larger for visibility
Venus: 0.55, // Slightly larger for visibility
Earth: 0.6, // Slightly larger for visibility
Mars: 0.45, // Slightly larger for visibility
Jupiter: 1.4, // Larger gas giant
Saturn: 1.2, // Larger gas giant
Uranus: 0.8, // Medium outer planet
Neptune: 0.8, // Medium outer planet
Pluto: 0.2, // Dwarf planet, smaller than Moon
};
return {
size: planetSizes[body.name] || 0.5,
emissive: '#000000',
emissiveIntensity: 0,
};
}, [body.name, body.type]);
return (
);
}