@@ -62,6 +62,7 @@ interface CelestrakResponse {
6262 MEAN_MOTION_DOT : number ;
6363 MEAN_MOTION_DDOT : number ;
6464 fetchTime : Date ;
65+ group : string ;
6566}
6667
6768const Globe : React . FC = ( ) => {
@@ -73,6 +74,7 @@ const Globe: React.FC = () => {
7374 const [ controls , setControls ] = useState < OrbitControls | null > ( null ) ;
7475 const [ tooltip , setTooltip ] = useState < TooltipState > ( { visible : false , text : '' , x : 0 , y : 0 } ) ;
7576 const [ popup , setPopup ] = useState < PopupState > ( { visible : false , data : null , x : 0 , y : 0 } ) ;
77+ const [ isPopupVisible , setIsPopupVisible ] = useState ( false ) ;
7678 const [ satellites , setSatellites ] = useState < SatelliteData [ ] > ( [ ] ) ;
7779 const [ activeGroup , setActiveGroup ] = useState < string > ( 'stations' ) ;
7880 const [ selectedSatellite , setSelectedSatellite ] = useState < SatelliteData | null > ( null ) ;
@@ -276,6 +278,7 @@ const Globe: React.FC = () => {
276278 orbitLinesRef . current . forEach ( line => {
277279 if ( line . material instanceof THREE . MeshBasicMaterial ) {
278280 line . material . opacity = 0 ;
281+ line . material . color . setHex ( 0xFAC515 ) ;
279282 }
280283 } ) ;
281284
@@ -284,6 +287,7 @@ const Globe: React.FC = () => {
284287 if ( clickedIndex !== - 1 && orbitLinesRef . current [ clickedIndex ] ) {
285288 const lineMaterial = orbitLinesRef . current [ clickedIndex ] . material as THREE . MeshBasicMaterial ;
286289 lineMaterial . opacity = 0.8 ;
290+ lineMaterial . color . setHex ( 0xFAC515 ) ;
287291 }
288292
289293 // Get the satellite's position for camera movement
@@ -405,15 +409,19 @@ const Globe: React.FC = () => {
405409 y = rect . top + padding ;
406410 }
407411
408- // Add a small delay before showing the popup for smoother animation
412+ // First hide the current popup
413+ setIsPopupVisible ( false ) ;
414+
415+ // After a short delay, show the new popup
409416 setTimeout ( ( ) => {
410417 setPopup ( {
411418 visible : true ,
412419 data : satelliteData . data ,
413420 x,
414421 y
415422 } ) ;
416- } , 100 ) ;
423+ setIsPopupVisible ( true ) ;
424+ } , 300 ) ; // Match this with the transition duration
417425 }
418426 } ;
419427
@@ -422,11 +430,14 @@ const Globe: React.FC = () => {
422430 }
423431 } else {
424432 // If clicking outside of a satellite, hide the popup and orbit line
425- setPopup ( { visible : false , data : null , x : 0 , y : 0 } ) ;
426- if ( activeOrbit && scene ) {
427- scene . remove ( activeOrbit ) ;
428- setActiveOrbit ( null ) ;
429- }
433+ setIsPopupVisible ( false ) ;
434+ setTimeout ( ( ) => {
435+ setPopup ( { visible : false , data : null , x : 0 , y : 0 } ) ;
436+ if ( activeOrbit && scene ) {
437+ scene . remove ( activeOrbit ) ;
438+ setActiveOrbit ( null ) ;
439+ }
440+ } , 300 ) ; // Match this with the transition duration
430441 }
431442 } ;
432443
@@ -541,6 +552,12 @@ const Globe: React.FC = () => {
541552
542553 setActiveGroup ( group ) ;
543554
555+ // Hide the popup immediately
556+ setIsPopupVisible ( false ) ;
557+ setTimeout ( ( ) => {
558+ setPopup ( { visible : false , data : null , x : 0 , y : 0 } ) ;
559+ } , 300 ) ; // Match the transition duration
560+
544561 // Clear existing orbit lines
545562 if ( scene ) {
546563 orbitLinesRef . current . forEach ( line => {
@@ -763,70 +780,39 @@ const Globe: React.FC = () => {
763780 // Add first point again to close the loop
764781 points . push ( points [ 0 ] . clone ( ) ) ;
765782
766- // Create line geometry
767- const lineGeometry = new THREE . BufferGeometry ( ) . setFromPoints ( points ) ;
768-
769- // Create a wider line using a mesh
770- const lineWidth = 0.05 ; // Base width of the line
771- const positions = new Float32Array ( points . length * 6 * 3 ) ; // 6 vertices per segment (2 triangles)
772- const indices = new Uint16Array ( ( points . length - 1 ) * 6 ) ; // 6 indices per segment (2 triangles)
773-
774- // Create vertices and indices for the line mesh
775- for ( let i = 0 ; i < points . length - 1 ; i ++ ) {
776- const p1 = points [ i ] ;
777- const p2 = points [ i + 1 ] ;
778-
779- // Calculate direction vector
780- const direction = p2 . clone ( ) . sub ( p1 ) . normalize ( ) ;
781-
782- // Calculate perpendicular vector in the plane
783- const perpendicular = new THREE . Vector3 ( ) . crossVectors ( direction , p1 . clone ( ) . normalize ( ) ) . normalize ( ) ;
784-
785- // Create vertices for the segment
786- const v1 = p1 . clone ( ) . add ( perpendicular . clone ( ) . multiplyScalar ( lineWidth ) ) ;
787- const v2 = p1 . clone ( ) . sub ( perpendicular . clone ( ) . multiplyScalar ( lineWidth ) ) ;
788- const v3 = p2 . clone ( ) . add ( perpendicular . clone ( ) . multiplyScalar ( lineWidth ) ) ;
789- const v4 = p2 . clone ( ) . sub ( perpendicular . clone ( ) . multiplyScalar ( lineWidth ) ) ;
790-
791- // Add vertices to positions array
792- const vertices = [ v1 , v2 , v3 , v4 ] ;
793- vertices . forEach ( ( v , j ) => {
794- positions [ i * 12 + j * 3 ] = v . x ;
795- positions [ i * 12 + j * 3 + 1 ] = v . y ;
796- positions [ i * 12 + j * 3 + 2 ] = v . z ;
797- } ) ;
798-
799- // Add indices for two triangles
800- const baseIndex = i * 4 ;
801- indices [ i * 6 ] = baseIndex ;
802- indices [ i * 6 + 1 ] = baseIndex + 1 ;
803- indices [ i * 6 + 2 ] = baseIndex + 2 ;
804- indices [ i * 6 + 3 ] = baseIndex + 1 ;
805- indices [ i * 6 + 4 ] = baseIndex + 3 ;
806- indices [ i * 6 + 5 ] = baseIndex + 2 ;
807- }
783+ // Create a curve from the points
784+ const curve = new THREE . CatmullRomCurve3 ( points ) ;
808785
809- const meshGeometry = new THREE . BufferGeometry ( ) ;
810- meshGeometry . setAttribute ( 'position' , new THREE . BufferAttribute ( positions , 3 ) ) ;
811- meshGeometry . setIndex ( new THREE . BufferAttribute ( indices , 1 ) ) ;
786+ // Create tube geometry
787+ const tubeGeometry = new THREE . TubeGeometry (
788+ curve ,
789+ segments ,
790+ 0.02 , // tube radius
791+ 8 , // radial segments
792+ false // closed
793+ ) ;
812794
813- // Create material that will always face the camera
795+ // Create material for the tube
814796 const material = new THREE . MeshBasicMaterial ( {
815- color : 0xffffff ,
797+ color : 0xFAC515 , // Golden yellow color
816798 transparent : true ,
817799 opacity : 0 , // Start invisible
818800 side : THREE . DoubleSide ,
819801 depthTest : false
820802 } ) ;
821803
822- const lineMesh = new THREE . Mesh ( meshGeometry , material ) ;
823- lineMesh . renderOrder = 1 ;
804+ const tubeMesh = new THREE . Mesh ( tubeGeometry , material ) ;
805+ tubeMesh . renderOrder = 1 ;
824806
825- return lineMesh ;
807+ return tubeMesh ;
826808 } ;
827809
828810 const handleSatelliteClick = ( satellite : SatelliteData ) => {
829811 setSelectedSatellite ( satellite ) ;
812+
813+ // Hide current popup immediately
814+ setIsPopupVisible ( false ) ;
815+
830816 // Find the satellite mesh
831817 const satelliteMesh = satelliteMeshesRef . current . find (
832818 sat => sat . data . noradId === satellite . noradId
@@ -837,6 +823,7 @@ const Globe: React.FC = () => {
837823 orbitLinesRef . current . forEach ( line => {
838824 if ( line . material instanceof THREE . MeshBasicMaterial ) {
839825 line . material . opacity = 0 ;
826+ line . material . color . setHex ( 0xFAC515 ) ;
840827 }
841828 } ) ;
842829
@@ -845,6 +832,7 @@ const Globe: React.FC = () => {
845832 if ( clickedIndex !== - 1 && orbitLinesRef . current [ clickedIndex ] ) {
846833 const lineMaterial = orbitLinesRef . current [ clickedIndex ] . material as THREE . MeshBasicMaterial ;
847834 lineMaterial . opacity = 0.8 ;
835+ lineMaterial . color . setHex ( 0xFAC515 ) ;
848836 }
849837
850838 // Get the satellite's position
@@ -887,6 +875,57 @@ const Globe: React.FC = () => {
887875 } else {
888876 // Re-enable controls after animation
889877 controls . enabled = true ;
878+
879+ // Show popup after camera animation
880+ const screenPosition = satellitePosition . clone ( ) . project ( camera ) ;
881+
882+ // Check if satellite is behind the globe (z > 1)
883+ if ( screenPosition . z > 1 ) {
884+ return ;
885+ }
886+
887+ const rect = renderer ?. domElement . getBoundingClientRect ( ) ;
888+ if ( ! rect ) return ;
889+
890+ // Position popup at bottom right of satellite dot with 1px spacing
891+ const dotSize = SATELLITE_SIZE * 100 ; // Convert to pixels
892+ let x = ( ( screenPosition . x * 0.5 + 0.5 ) * rect . width ) + rect . left ;
893+ let y = ( - ( screenPosition . y * 0.5 - 0.5 ) * rect . height ) + rect . top ;
894+
895+ // Ensure popup stays within viewport
896+ const popupWidth = 305 ;
897+ const popupHeight = 174 ;
898+ const padding = 10 ;
899+
900+ // Adjust x position if popup would go off the right edge
901+ if ( x + popupWidth > rect . right - padding ) {
902+ x = x - popupWidth - dotSize - 1 ; // Position to the left of the dot
903+ } else {
904+ x = x + dotSize + 1 ; // Position to the right of the dot
905+ }
906+
907+ // Adjust x position if popup would go off the left edge
908+ if ( x < rect . left + padding ) {
909+ x = rect . left + padding ;
910+ }
911+
912+ // Adjust y position if popup would go off the bottom edge
913+ if ( y + popupHeight > rect . bottom - padding ) {
914+ y = rect . bottom - popupHeight - padding ;
915+ }
916+ // Adjust y position if popup would go off the top edge
917+ if ( y < rect . top + padding ) {
918+ y = rect . top + padding ;
919+ }
920+
921+ // Update popup position and show it
922+ setPopup ( {
923+ visible : true ,
924+ data : satellite ,
925+ x,
926+ y
927+ } ) ;
928+ setIsPopupVisible ( true ) ;
890929 }
891930 } ;
892931
@@ -989,6 +1028,7 @@ const Globe: React.FC = () => {
9891028 data = { popup . data }
9901029 x = { popup . x }
9911030 y = { popup . y }
1031+ isVisible = { isPopupVisible }
9921032 />
9931033 ) }
9941034 { /* <SidePanel satellites={satellites} /> */ }
0 commit comments