/** * Probe component - renders space probes with 3D models */ import { useRef, useMemo } from 'react'; import { Group } from 'three'; import { useGLTF, Html } from '@react-three/drei'; import { useFrame } from '@react-three/fiber'; import type { CelestialBody } from '../types'; import { scalePosition } from '../utils/scaleDistance'; interface ProbeProps { body: CelestialBody; } // Separate component for each probe type to properly use hooks function ProbeModel({ body, modelPath }: { body: CelestialBody; modelPath: string }) { const groupRef = useRef(null); const position = body.positions[0]; // Apply non-linear distance scaling const scaledPos = useMemo(() => { const baseScaled = scalePosition(position.x, position.y, position.z); const distance = Math.sqrt(position.x ** 2 + position.y ** 2 + position.z ** 2); // Special handling for probes very close to planets (< 10 AU from Sun) // These probes need visual offset to avoid overlapping with planets if (distance < 10) { // Add a radial offset to push the probe away from the Sun (and nearby planets) // This makes probes like Juno visible next to Jupiter const angle = Math.atan2(position.y, position.x); const offsetAmount = 3.0; // Visual offset in scaled units return { x: baseScaled.x + Math.cos(angle) * offsetAmount, y: baseScaled.y + Math.sin(angle) * offsetAmount, z: baseScaled.z, }; } return baseScaled; }, [position.x, position.y, position.z]); // Load 3D model - must be at top level const { scene } = useGLTF(modelPath); // Configure model materials for proper rendering const configuredScene = useMemo(() => { const clonedScene = scene.clone(); clonedScene.traverse((child: any) => { if (child.isMesh) { // Force proper depth testing and high render order child.renderOrder = 10000; if (child.material) { // Clone material to avoid modifying shared materials if (Array.isArray(child.material)) { child.material = child.material.map((mat: any) => { const clonedMat = mat.clone(); clonedMat.depthTest = true; clonedMat.depthWrite = true; clonedMat.transparent = false; clonedMat.opacity = 1.0; clonedMat.alphaTest = 0; clonedMat.needsUpdate = true; return clonedMat; }); } else { child.material = child.material.clone(); child.material.depthTest = true; child.material.depthWrite = true; child.material.transparent = false; child.material.opacity = 1.0; child.material.alphaTest = 0; child.material.needsUpdate = true; } } } }); return clonedScene; }, [scene]); // Slow rotation for visual effect useFrame((_, delta) => { if (groupRef.current) { groupRef.current.rotation.y += delta * 0.2; } }); // Calculate ACTUAL distance from Sun (not scaled) const distance = Math.sqrt(position.x ** 2 + position.y ** 2 + position.z ** 2); return ( {/* Removed the semi-transparent sphere to avoid rendering conflicts */} {/* Name label */} 🛰️ {body.name}
{distance.toFixed(2)} AU
); } // Fallback component when model is not available function ProbeFallback({ body }: { body: CelestialBody }) { const position = body.positions[0]; // Apply non-linear distance scaling const scaledPos = useMemo(() => { const baseScaled = scalePosition(position.x, position.y, position.z); const distance = Math.sqrt(position.x ** 2 + position.y ** 2 + position.z ** 2); // Special handling for probes very close to planets (< 10 AU from Sun) if (distance < 10) { const angle = Math.atan2(position.y, position.x); const offsetAmount = 3.0; // Visual offset in scaled units return { x: baseScaled.x + Math.cos(angle) * offsetAmount, y: baseScaled.y + Math.sin(angle) * offsetAmount, z: baseScaled.z, }; } return baseScaled; }, [position.x, position.y, position.z]); // Calculate ACTUAL distance from Sun (not scaled) const distance = Math.sqrt(position.x ** 2 + position.y ** 2 + position.z ** 2); return ( {/* Name label */} 🛰️ {body.name}
{distance.toFixed(2)} AU
); } export function Probe({ body }: ProbeProps) { const position = body.positions[0]; if (!position) return null; // Model mapping for probes - match actual filenames const modelMap: Record = { 'Voyager 1': '/models/voyager_1.glb', 'Voyager 2': '/models/voyager_2.glb', 'Juno': '/models/juno.glb', 'Cassini': '/models/cassini.glb', 'New Horizons': null, // No model yet 'Parker Solar Probe': '/models/parker_solar_probe.glb', 'Perseverance': null, // No model yet }; const modelPath = modelMap[body.name]; // Use model if available, otherwise use fallback if (modelPath) { return ; } return ; } // Preload available models const modelsToPreload = [ '/models/voyager_1.glb', '/models/voyager_2.glb', '/models/juno.glb', '/models/cassini.glb', '/models/parker_solar_probe.glb', ]; modelsToPreload.forEach((path) => { useGLTF.preload(path); });