@zendir/ui 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/CHANGELOG.md +419 -386
  2. package/README.md +640 -631
  3. package/dist/_virtual/_rolldown/runtime.js +13 -0
  4. package/dist/index.js +17 -221
  5. package/dist/react/3d/CesiumCaptureSource.d.ts +0 -1
  6. package/dist/react/3d/CesiumCaptureSource.js +364 -283
  7. package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
  8. package/dist/react/3d/ZenSpace3D.d.ts +2 -3
  9. package/dist/react/3d/ZenSpace3D.js +1681 -1237
  10. package/dist/react/3d/ZenSpace3D.js.map +1 -1
  11. package/dist/react/3d/ZenSpace3DCesium.d.ts +0 -1
  12. package/dist/react/3d/ZenSpace3DCesium.js +513 -560
  13. package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
  14. package/dist/react/3d/ZenSpace3DShaders.js +61 -66
  15. package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
  16. package/dist/react/3d/ZenSpace3DTypes.d.ts +19 -20
  17. package/dist/react/3d/ZenSpace3DUtils.d.ts +0 -1
  18. package/dist/react/3d/ZenSpace3DUtils.js +37 -24
  19. package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
  20. package/dist/react/3d/capturePngAnalysis.js +42 -36
  21. package/dist/react/3d/capturePngAnalysis.js.map +1 -1
  22. package/dist/react/3d/index.d.ts +3 -3
  23. package/dist/react/3d/index.js +5 -53
  24. package/dist/react/3d/threeLoader.d.ts +1 -1
  25. package/dist/react/3d/threeLoader.js +23 -18
  26. package/dist/react/3d/threeLoader.js.map +1 -1
  27. package/dist/react/astro/ClassificationBanner.d.ts +2 -3
  28. package/dist/react/astro/ClassificationBanner.js +95 -79
  29. package/dist/react/astro/ClassificationBanner.js.map +1 -1
  30. package/dist/react/astro/GlobalStatusBar.d.ts +0 -1
  31. package/dist/react/astro/GlobalStatusBar.js +157 -163
  32. package/dist/react/astro/GlobalStatusBar.js.map +1 -1
  33. package/dist/react/astro/MissionClock.d.ts +7 -8
  34. package/dist/react/astro/MissionClock.js +377 -400
  35. package/dist/react/astro/MissionClock.js.map +1 -1
  36. package/dist/react/astro/MonitoringIcon.d.ts +1 -2
  37. package/dist/react/astro/MonitoringIcon.js +422 -358
  38. package/dist/react/astro/MonitoringIcon.js.map +1 -1
  39. package/dist/react/astro/Notification.d.ts +1 -2
  40. package/dist/react/astro/Notification.js +229 -126
  41. package/dist/react/astro/Notification.js.map +1 -1
  42. package/dist/react/astro/Progress.d.ts +0 -1
  43. package/dist/react/astro/Progress.js +199 -141
  44. package/dist/react/astro/Progress.js.map +1 -1
  45. package/dist/react/astro/SimulationControls.d.ts +3 -4
  46. package/dist/react/astro/SimulationControls.js +596 -650
  47. package/dist/react/astro/SimulationControls.js.map +1 -1
  48. package/dist/react/astro/StatusIndicator.d.ts +0 -1
  49. package/dist/react/astro/StatusIndicator.js +236 -170
  50. package/dist/react/astro/StatusIndicator.js.map +1 -1
  51. package/dist/react/astro/UnifiedTimeline.d.ts +9 -10
  52. package/dist/react/astro/UnifiedTimeline.js +2345 -2392
  53. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  54. package/dist/react/astro/index.d.ts +5 -5
  55. package/dist/react/astro/index.js +1 -25
  56. package/dist/react/cards/AccessCard.d.ts +0 -1
  57. package/dist/react/cards/AccessCard.js +471 -405
  58. package/dist/react/cards/AccessCard.js.map +1 -1
  59. package/dist/react/cards/OrbitCard.d.ts +0 -1
  60. package/dist/react/cards/OrbitCard.js +487 -364
  61. package/dist/react/cards/OrbitCard.js.map +1 -1
  62. package/dist/react/cards/SpacecraftCard.d.ts +0 -1
  63. package/dist/react/cards/SpacecraftCard.js +813 -925
  64. package/dist/react/cards/SpacecraftCard.js.map +1 -1
  65. package/dist/react/cards/TelemetryCard.d.ts +0 -1
  66. package/dist/react/cards/TelemetryCard.js +1055 -732
  67. package/dist/react/cards/TelemetryCard.js.map +1 -1
  68. package/dist/react/cards/TelemetryStreamCard.d.ts +0 -1
  69. package/dist/react/cards/TelemetryStreamCard.js +323 -297
  70. package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
  71. package/dist/react/cards/index.d.ts +1 -1
  72. package/dist/react/cards/index.js +1 -8
  73. package/dist/react/charts/GroundTrackMap.d.ts +6 -7
  74. package/dist/react/charts/GroundTrackMap.js +1377 -1256
  75. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  76. package/dist/react/charts/GroundTrackMapLeaflet.d.ts +1 -2
  77. package/dist/react/charts/GroundTrackMapLeaflet.js +1065 -987
  78. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  79. package/dist/react/charts/groundTrackMapLeafletTiles.js +34 -25
  80. package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
  81. package/dist/react/charts/groundTrackMapLeafletUtils.js +70 -61
  82. package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
  83. package/dist/react/charts/index.d.ts +1 -1
  84. package/dist/react/charts/index.js +7 -100
  85. package/dist/react/charts/unified/AstroChart.d.ts +1 -2
  86. package/dist/react/charts/unified/AstroChart.js +1331 -1411
  87. package/dist/react/charts/unified/AstroChart.js.map +1 -1
  88. package/dist/react/charts/unified/PowerOverviewChart.d.ts +3 -3
  89. package/dist/react/charts/unified/PowerOverviewChart.js +495 -478
  90. package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
  91. package/dist/react/charts/unified/domain.d.ts +12 -13
  92. package/dist/react/charts/unified/domain.js +3999 -3145
  93. package/dist/react/charts/unified/domain.js.map +1 -1
  94. package/dist/react/charts/unified/generators.d.ts +3 -4
  95. package/dist/react/charts/unified/generators.js +561 -498
  96. package/dist/react/charts/unified/generators.js.map +1 -1
  97. package/dist/react/charts/unified/index.d.ts +1 -1
  98. package/dist/react/charts/unified/presets.d.ts +14 -15
  99. package/dist/react/charts/unified/presets.js +857 -994
  100. package/dist/react/charts/unified/presets.js.map +1 -1
  101. package/dist/react/charts/unified/sync.d.ts +2 -3
  102. package/dist/react/charts/unified/sync.js +229 -195
  103. package/dist/react/charts/unified/sync.js.map +1 -1
  104. package/dist/react/charts/unified/theme.d.ts +11 -12
  105. package/dist/react/charts/unified/theme.js +606 -642
  106. package/dist/react/charts/unified/theme.js.map +1 -1
  107. package/dist/react/charts/unified/types.d.ts +32 -33
  108. package/dist/react/charts/unified/useChartStream.d.ts +1 -2
  109. package/dist/react/charts/unified/useChartStream.js +203 -216
  110. package/dist/react/charts/unified/useChartStream.js.map +1 -1
  111. package/dist/react/chatgpt/AppCard.d.ts +1 -2
  112. package/dist/react/chatgpt/index.d.ts +7 -7
  113. package/dist/react/chatgpt/index.js +160 -139
  114. package/dist/react/chatgpt/index.js.map +1 -1
  115. package/dist/react/context/CategoryContext.d.ts +0 -1
  116. package/dist/react/context/CategoryContext.js +78 -29
  117. package/dist/react/context/CategoryContext.js.map +1 -1
  118. package/dist/react/context/DisplaySettingsContext.d.ts +1 -2
  119. package/dist/react/context/DisplaySettingsContext.js +205 -156
  120. package/dist/react/context/DisplaySettingsContext.js.map +1 -1
  121. package/dist/react/context/SpatialSelectionContext.d.ts +0 -1
  122. package/dist/react/context/SpatialSelectionContext.js +36 -7
  123. package/dist/react/context/SpatialSelectionContext.js.map +1 -1
  124. package/dist/react/core/data/DataTable.d.ts +3 -4
  125. package/dist/react/core/data/DataTable.js +473 -440
  126. package/dist/react/core/data/DataTable.js.map +1 -1
  127. package/dist/react/core/data/DataValue.d.ts +4 -5
  128. package/dist/react/core/data/DataValue.js +605 -571
  129. package/dist/react/core/data/DataValue.js.map +1 -1
  130. package/dist/react/core/data/propertyConfig.d.ts +1 -2
  131. package/dist/react/core/data/propertyConfig.js +446 -382
  132. package/dist/react/core/data/propertyConfig.js.map +1 -1
  133. package/dist/react/core/display/AstroIcon.d.ts +4 -5
  134. package/dist/react/core/display/AstroIcon.js +339 -429
  135. package/dist/react/core/display/AstroIcon.js.map +1 -1
  136. package/dist/react/core/display/Badge.d.ts +2 -3
  137. package/dist/react/core/display/Badge.js +131 -131
  138. package/dist/react/core/display/Badge.js.map +1 -1
  139. package/dist/react/core/display/CardHeader.d.ts +0 -1
  140. package/dist/react/core/display/CardHeader.js +398 -311
  141. package/dist/react/core/display/CardHeader.js.map +1 -1
  142. package/dist/react/core/display/Container.d.ts +2 -3
  143. package/dist/react/core/display/Container.js +640 -668
  144. package/dist/react/core/display/Container.js.map +1 -1
  145. package/dist/react/core/display/CopyButton.d.ts +1 -2
  146. package/dist/react/core/display/CopyButton.js +158 -101
  147. package/dist/react/core/display/CopyButton.js.map +1 -1
  148. package/dist/react/core/display/GlassCard.d.ts +3 -4
  149. package/dist/react/core/display/GlassCard.js +416 -369
  150. package/dist/react/core/display/GlassCard.js.map +1 -1
  151. package/dist/react/core/display/HeaderIconWithStatus.d.ts +0 -1
  152. package/dist/react/core/display/HeaderIconWithStatus.js +152 -152
  153. package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
  154. package/dist/react/core/display/Icon.d.ts +2 -3
  155. package/dist/react/core/display/Icon.js +172 -143
  156. package/dist/react/core/display/Icon.js.map +1 -1
  157. package/dist/react/core/display/Typography.d.ts +24 -25
  158. package/dist/react/core/display/Typography.js +360 -178
  159. package/dist/react/core/display/Typography.js.map +1 -1
  160. package/dist/react/core/feedback/ConfirmDialog.d.ts +1 -2
  161. package/dist/react/core/feedback/ConfirmDialog.js +343 -304
  162. package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
  163. package/dist/react/core/feedback/Dialog.d.ts +2 -3
  164. package/dist/react/core/feedback/Dialog.js +189 -197
  165. package/dist/react/core/feedback/Dialog.js.map +1 -1
  166. package/dist/react/core/feedback/Toast.d.ts +2 -3
  167. package/dist/react/core/feedback/Toast.js +346 -207
  168. package/dist/react/core/feedback/Toast.js.map +1 -1
  169. package/dist/react/core/index.d.ts +26 -26
  170. package/dist/react/core/index.js +7 -119
  171. package/dist/react/core/inputs/Button.d.ts +2 -3
  172. package/dist/react/core/inputs/Button.js +281 -304
  173. package/dist/react/core/inputs/Button.js.map +1 -1
  174. package/dist/react/core/inputs/Checkbox.d.ts +2 -3
  175. package/dist/react/core/inputs/Checkbox.js +139 -127
  176. package/dist/react/core/inputs/Checkbox.js.map +1 -1
  177. package/dist/react/core/inputs/Input.d.ts +3 -4
  178. package/dist/react/core/inputs/Input.js +277 -285
  179. package/dist/react/core/inputs/Input.js.map +1 -1
  180. package/dist/react/core/inputs/LimitsBar.d.ts +1 -2
  181. package/dist/react/core/inputs/LimitsBar.js +271 -196
  182. package/dist/react/core/inputs/LimitsBar.js.map +1 -1
  183. package/dist/react/core/inputs/NumberInput.d.ts +2 -3
  184. package/dist/react/core/inputs/NumberInput.js +531 -503
  185. package/dist/react/core/inputs/NumberInput.js.map +1 -1
  186. package/dist/react/core/inputs/PinInput.d.ts +2 -3
  187. package/dist/react/core/inputs/PinInput.js +224 -207
  188. package/dist/react/core/inputs/PinInput.js.map +1 -1
  189. package/dist/react/core/inputs/Select.d.ts +1 -2
  190. package/dist/react/core/inputs/Select.js +298 -299
  191. package/dist/react/core/inputs/Select.js.map +1 -1
  192. package/dist/react/core/inputs/Toggle.d.ts +2 -3
  193. package/dist/react/core/inputs/Toggle.js +142 -148
  194. package/dist/react/core/inputs/Toggle.js.map +1 -1
  195. package/dist/react/core/layout/Box.d.ts +3 -4
  196. package/dist/react/core/layout/Box.js +118 -122
  197. package/dist/react/core/layout/Box.js.map +1 -1
  198. package/dist/react/core/layout/Center.d.ts +0 -1
  199. package/dist/react/core/layout/Center.js +45 -32
  200. package/dist/react/core/layout/Center.js.map +1 -1
  201. package/dist/react/core/layout/Divider.d.ts +1 -2
  202. package/dist/react/core/layout/Divider.js +112 -106
  203. package/dist/react/core/layout/Divider.js.map +1 -1
  204. package/dist/react/core/layout/Flex.d.ts +4 -5
  205. package/dist/react/core/layout/Flex.js +120 -124
  206. package/dist/react/core/layout/Flex.js.map +1 -1
  207. package/dist/react/core/layout/Grid.d.ts +5 -6
  208. package/dist/react/core/layout/Grid.js +131 -138
  209. package/dist/react/core/layout/Grid.js.map +1 -1
  210. package/dist/react/core/layout/Spacer.d.ts +0 -1
  211. package/dist/react/core/layout/Spacer.js +36 -29
  212. package/dist/react/core/layout/Spacer.js.map +1 -1
  213. package/dist/react/core/layout/Stack.d.ts +4 -5
  214. package/dist/react/core/layout/Stack.js +127 -70
  215. package/dist/react/core/layout/Stack.js.map +1 -1
  216. package/dist/react/core/layout/responsive.d.ts +1 -2
  217. package/dist/react/core/layout/responsive.js +25 -19
  218. package/dist/react/core/layout/responsive.js.map +1 -1
  219. package/dist/react/core/layout/useBreakpoint.d.ts +1 -1
  220. package/dist/react/core/layout/useBreakpoint.js +119 -66
  221. package/dist/react/core/layout/useBreakpoint.js.map +1 -1
  222. package/dist/react/core/navigation/AppBar.d.ts +1 -2
  223. package/dist/react/core/navigation/AppBar.js +827 -805
  224. package/dist/react/core/navigation/AppBar.js.map +1 -1
  225. package/dist/react/core/navigation/Pagination.d.ts +2 -3
  226. package/dist/react/core/navigation/Pagination.js +207 -187
  227. package/dist/react/core/navigation/Pagination.js.map +1 -1
  228. package/dist/react/core/navigation/SideNav.d.ts +5 -6
  229. package/dist/react/core/navigation/SideNav.js +855 -629
  230. package/dist/react/core/navigation/SideNav.js.map +1 -1
  231. package/dist/react/core/navigation/Tabs.d.ts +0 -1
  232. package/dist/react/core/navigation/Tabs.js +113 -119
  233. package/dist/react/core/navigation/Tabs.js.map +1 -1
  234. package/dist/react/core/overlays/Popover.d.ts +2 -3
  235. package/dist/react/core/overlays/Popover.js +303 -282
  236. package/dist/react/core/overlays/Popover.js.map +1 -1
  237. package/dist/react/core/overlays/SidePanel.d.ts +1 -2
  238. package/dist/react/core/overlays/SidePanel.js +217 -212
  239. package/dist/react/core/overlays/SidePanel.js.map +1 -1
  240. package/dist/react/core/overlays/Tooltip.d.ts +1 -2
  241. package/dist/react/core/overlays/Tooltip.js +170 -176
  242. package/dist/react/core/overlays/Tooltip.js.map +1 -1
  243. package/dist/react/core/widgets/ActivityPlanner.d.ts +0 -1
  244. package/dist/react/core/widgets/ActivityPlanner.js +855 -517
  245. package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
  246. package/dist/react/core/widgets/Capture.d.ts +2 -3
  247. package/dist/react/core/widgets/Capture.js +965 -779
  248. package/dist/react/core/widgets/Capture.js.map +1 -1
  249. package/dist/react/core/widgets/ChatPanel.d.ts +16 -17
  250. package/dist/react/core/widgets/ChatPanel.js +1734 -1034
  251. package/dist/react/core/widgets/ChatPanel.js.map +1 -1
  252. package/dist/react/core/widgets/ColorPickerPanel.d.ts +0 -1
  253. package/dist/react/core/widgets/ColorPickerPanel.js +298 -287
  254. package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
  255. package/dist/react/core/widgets/CommandBuilder.d.ts +2 -3
  256. package/dist/react/core/widgets/CommandBuilder.js +624 -516
  257. package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
  258. package/dist/react/core/widgets/ConnectionForm.d.ts +2 -3
  259. package/dist/react/core/widgets/ConnectionForm.js +528 -492
  260. package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
  261. package/dist/react/core/widgets/FileExplorer.d.ts +3 -4
  262. package/dist/react/core/widgets/FileExplorer.js +617 -505
  263. package/dist/react/core/widgets/FileExplorer.js.map +1 -1
  264. package/dist/react/core/widgets/HexViewer.d.ts +2 -3
  265. package/dist/react/core/widgets/HexViewer.js +1353 -1091
  266. package/dist/react/core/widgets/HexViewer.js.map +1 -1
  267. package/dist/react/core/widgets/ImageFrame.d.ts +1 -2
  268. package/dist/react/core/widgets/ImageFrame.js +230 -193
  269. package/dist/react/core/widgets/ImageFrame.js.map +1 -1
  270. package/dist/react/core/widgets/ImageGallery.d.ts +2 -1
  271. package/dist/react/core/widgets/ImageGallery.js +380 -314
  272. package/dist/react/core/widgets/ImageGallery.js.map +1 -1
  273. package/dist/react/core/widgets/ImagePanel.d.ts +0 -1
  274. package/dist/react/core/widgets/ImagePanel.js +110 -101
  275. package/dist/react/core/widgets/ImagePanel.js.map +1 -1
  276. package/dist/react/core/widgets/LogViewer.d.ts +2 -3
  277. package/dist/react/core/widgets/LogViewer.js +711 -607
  278. package/dist/react/core/widgets/LogViewer.js.map +1 -1
  279. package/dist/react/core/widgets/MessageStream.d.ts +3 -4
  280. package/dist/react/core/widgets/MessageStream.js +514 -444
  281. package/dist/react/core/widgets/MessageStream.js.map +1 -1
  282. package/dist/react/core/widgets/MissionCalendar.d.ts +3 -4
  283. package/dist/react/core/widgets/MissionCalendar.js +1488 -1020
  284. package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
  285. package/dist/react/core/widgets/PacketViewer.d.ts +2 -3
  286. package/dist/react/core/widgets/PacketViewer.js +550 -421
  287. package/dist/react/core/widgets/PacketViewer.js.map +1 -1
  288. package/dist/react/core/widgets/capture-placeholder.png.js +6 -5
  289. package/dist/react/core/widgets/capture-placeholder.png.js.map +1 -1
  290. package/dist/react/hooks/index.d.ts +5 -5
  291. package/dist/react/hooks/useAccessWindows.d.ts +0 -1
  292. package/dist/react/hooks/useCompactMode.d.ts +1 -1
  293. package/dist/react/hooks/useCompactMode.js +104 -58
  294. package/dist/react/hooks/useCompactMode.js.map +1 -1
  295. package/dist/react/hooks/useGroundTrackHistory.d.ts +0 -1
  296. package/dist/react/hooks/useSimulationScene.d.ts +1 -2
  297. package/dist/react/hooks/useSimulationScene.js +481 -374
  298. package/dist/react/hooks/useSimulationScene.js.map +1 -1
  299. package/dist/react/index.d.ts +3 -3
  300. package/dist/react/panels/LayerControlPanel.d.ts +1 -2
  301. package/dist/react/panels/LayerControlPanel.js +223 -180
  302. package/dist/react/panels/LayerControlPanel.js.map +1 -1
  303. package/dist/react/panels/ObjectInventoryPanel.d.ts +2 -3
  304. package/dist/react/panels/ObjectInventoryPanel.js +315 -250
  305. package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
  306. package/dist/react/panels/index.d.ts +1 -1
  307. package/dist/react/panels/index.js +1 -6
  308. package/dist/react/shared/ErrorBoundary.d.ts +1 -2
  309. package/dist/react/shared/ErrorBoundary.js +170 -139
  310. package/dist/react/shared/ErrorBoundary.js.map +1 -1
  311. package/dist/react/shared/Skeleton.d.ts +1 -2
  312. package/dist/react/shared/Skeleton.js +300 -301
  313. package/dist/react/shared/Skeleton.js.map +1 -1
  314. package/dist/react/theme/ThemeProvider.d.ts +2 -321
  315. package/dist/react/theme/ThemeProvider.js +313 -939
  316. package/dist/react/theme/ThemeProvider.js.map +1 -1
  317. package/dist/react/theme/cardAccent.d.ts +1 -2
  318. package/dist/react/theme/cardAccent.js +196 -126
  319. package/dist/react/theme/cardAccent.js.map +1 -1
  320. package/dist/react/theme/config.d.ts +0 -1
  321. package/dist/react/theme/config.js +47 -25
  322. package/dist/react/theme/config.js.map +1 -1
  323. package/dist/react/theme/index.d.ts +2 -2
  324. package/dist/react/theme/index.js +1 -16
  325. package/dist/react/theme/types.d.ts +332 -0
  326. package/dist/react/theme/types.js +39 -0
  327. package/dist/react/theme/types.js.map +1 -0
  328. package/dist/react/types.d.ts +8 -8
  329. package/dist/react/types.js +147 -47
  330. package/dist/react/types.js.map +1 -1
  331. package/dist/react/utils/categoryPalette.d.ts +0 -1
  332. package/dist/react/utils/categoryPalette.js +91 -102
  333. package/dist/react/utils/categoryPalette.js.map +1 -1
  334. package/dist/react/utils/index.d.ts +5 -5
  335. package/dist/react/utils/index.js +457 -371
  336. package/dist/react/utils/index.js.map +1 -1
  337. package/dist/react/visualizations/EclipseTimerCard.d.ts +0 -1
  338. package/dist/react/visualizations/LinkBudgetCard.d.ts +0 -1
  339. package/dist/react/visualizations/NavBallCard.d.ts +0 -1
  340. package/dist/react/visualizations/PropulsionCard.d.ts +1 -2
  341. package/dist/react/visualizations/SensorFootprintCard.d.ts +1 -2
  342. package/dist/react/visualizations/ThermalHeatmapCard.d.ts +0 -1
  343. package/dist/react/visualizations/index.d.ts +3 -3
  344. package/dist/react.d.ts +2 -1
  345. package/dist/react.js +17 -221
  346. package/dist/shaders/atmosphere.frag.js +6 -5
  347. package/dist/shaders/atmosphere.frag.js.map +1 -1
  348. package/dist/shaders/atmosphere.vert.js +6 -5
  349. package/dist/shaders/atmosphere.vert.js.map +1 -1
  350. package/dist/shaders/stars.frag.js +6 -5
  351. package/dist/shaders/stars.frag.js.map +1 -1
  352. package/dist/shaders/stars.vert.js +6 -5
  353. package/dist/shaders/stars.vert.js.map +1 -1
  354. package/dist/style.css +26 -3
  355. package/dist/tokens/core.d.ts +159 -0
  356. package/dist/tokens/core.js +144 -0
  357. package/dist/tokens/core.js.map +1 -0
  358. package/dist/tokens/css-vars.d.ts +213 -2
  359. package/dist/tokens/css-vars.js +590 -227
  360. package/dist/tokens/css-vars.js.map +1 -1
  361. package/dist/tokens/index.d.ts +31 -228
  362. package/dist/tokens/index.js +281 -366
  363. package/dist/tokens/index.js.map +1 -1
  364. package/dist/tokens/palettes/astro.d.ts +129 -0
  365. package/dist/tokens/palettes/astro.js +112 -0
  366. package/dist/tokens/palettes/astro.js.map +1 -0
  367. package/dist/tokens/palettes/index.d.ts +288 -0
  368. package/dist/tokens/palettes/index.js +35 -0
  369. package/dist/tokens/palettes/index.js.map +1 -0
  370. package/dist/tokens/palettes/tailwind.d.ts +324 -0
  371. package/dist/tokens/palettes/tailwind.js +201 -0
  372. package/dist/tokens/palettes/tailwind.js.map +1 -0
  373. package/dist/tokens/palettes/zendir.d.ts +126 -0
  374. package/dist/tokens/palettes/zendir.js +92 -0
  375. package/dist/tokens/palettes/zendir.js.map +1 -0
  376. package/dist/tokens/themes.d.ts +30 -0
  377. package/dist/tokens/themes.js +704 -0
  378. package/dist/tokens/themes.js.map +1 -0
  379. package/dist/tokens/tokens.css +533 -155
  380. package/dist/types/index.d.ts +2 -2
  381. package/package.json +310 -313
  382. package/dist/index.js.map +0 -1
  383. package/dist/react/3d/index.js.map +0 -1
  384. package/dist/react/astro/index.js.map +0 -1
  385. package/dist/react/cards/index.js.map +0 -1
  386. package/dist/react/charts/index.js.map +0 -1
  387. package/dist/react/core/index.js.map +0 -1
  388. package/dist/react/panels/index.js.map +0 -1
  389. package/dist/react/theme/astro-tokens.d.ts +0 -153
  390. package/dist/react/theme/index.js.map +0 -1
  391. package/dist/react.js.map +0 -1
@@ -1,1254 +1,1698 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import React, { forwardRef, useState, useEffect, useRef, useMemo, useCallback, useImperativeHandle } from "react";
3
1
  import { useTheme } from "../theme/ThemeProvider.js";
4
2
  import { AstroIcon } from "../core/display/AstroIcon.js";
5
- import { SCENE_EARTH_RADIUS, latLonAltToCartesian, KM_TO_SCENE, easeOutCubic } from "./ZenSpace3DUtils.js";
6
- import atmosphereVertexShader from "../../shaders/atmosphere.vert.js";
7
- import atmosphereFragmentShader from "../../shaders/atmosphere.frag.js";
8
- import starsVertexShader from "../../shaders/stars.vert.js";
9
- import starsFragmentShader from "../../shaders/stars.frag.js";
3
+ import { KM_TO_SCENE, SCENE_EARTH_RADIUS, easeOutCubic, latLonAltToCartesian } from "./ZenSpace3DUtils.js";
4
+ import atmosphere_default from "../../shaders/atmosphere.vert.js";
5
+ import atmosphere_default$1 from "../../shaders/atmosphere.frag.js";
6
+ import stars_default from "../../shaders/stars.vert.js";
7
+ import stars_default$1 from "../../shaders/stars.frag.js";
10
8
  import { loadThree } from "./threeLoader.js";
11
9
  import { ZenSpace3DCesium } from "./ZenSpace3DCesium.js";
12
- const DEFAULT_LAYERS = {
13
- planets: true,
14
- moons: true,
15
- satellites: true,
16
- debris: false,
17
- stations: true,
18
- asteroids: false,
19
- groundStations: true,
20
- orbits: true,
21
- labels: true,
22
- constellations: false,
23
- coverage: true,
24
- atmosphere: true,
25
- stars: true,
26
- grid: false,
27
- terminator: true
10
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ //#region src/react/3d/ZenSpace3D.tsx
13
+ /**
14
+ * @zendir/ui - ZenSpace3D Component (Optimized)
15
+ *
16
+ * Professional 3D space visualization with smooth interactions.
17
+ * Optimized for performance with settings from EarthViewer.
18
+ */
19
+ var DEFAULT_LAYERS = {
20
+ planets: true,
21
+ moons: true,
22
+ satellites: true,
23
+ debris: false,
24
+ stations: true,
25
+ asteroids: false,
26
+ groundStations: true,
27
+ orbits: true,
28
+ labels: true,
29
+ constellations: false,
30
+ coverage: true,
31
+ atmosphere: true,
32
+ stars: true,
33
+ grid: false,
34
+ terminator: true
28
35
  };
29
- const CAMERA_TRANSITION_DURATION = 2e3;
30
- const STARS_COUNT = 5e3;
31
- const EARTH_SEGMENTS = 64;
32
- const EARTH_SEGMENTS_V = 32;
33
- const SPHERICAL_SUBDIVISIONS = 16;
36
+ var CAMERA_TRANSITION_DURATION = 2e3;
37
+ var STARS_COUNT = 5e3;
38
+ var EARTH_SEGMENTS = 64;
39
+ var EARTH_SEGMENTS_V = 32;
40
+ var SPHERICAL_SUBDIVISIONS = 16;
34
41
  function checkWebGLCapabilities() {
35
- try {
36
- const canvas = document.createElement("canvas");
37
- const gl2 = canvas.getContext("webgl2");
38
- if (gl2) return { available: true, version: 2 };
39
- const gl = canvas.getContext("webgl");
40
- if (gl) return { available: true, version: 1 };
41
- } catch {
42
- }
43
- return { available: false, version: 0 };
42
+ try {
43
+ const canvas = document.createElement("canvas");
44
+ if (canvas.getContext("webgl2")) return {
45
+ available: true,
46
+ version: 2
47
+ };
48
+ if (canvas.getContext("webgl")) return {
49
+ available: true,
50
+ version: 1
51
+ };
52
+ } catch {}
53
+ return {
54
+ available: false,
55
+ version: 0
56
+ };
44
57
  }
58
+ /**
59
+ * Spherical linear interpolation (SLERP) between two unit vectors
60
+ * Creates smooth arc along the sphere surface
61
+ */
45
62
  function slerp(v1, v2, t) {
46
- const len1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z);
47
- const len2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z);
48
- if (len1 < 1e-4 || len2 < 1e-4) {
49
- return { x: v1.x, y: v1.y, z: v1.z };
50
- }
51
- const n1 = { x: v1.x / len1, y: v1.y / len1, z: v1.z / len1 };
52
- const n2 = { x: v2.x / len2, y: v2.y / len2, z: v2.z / len2 };
53
- let dot = n1.x * n2.x + n1.y * n2.y + n1.z * n2.z;
54
- dot = Math.max(-1, Math.min(1, dot));
55
- const theta = Math.acos(dot);
56
- if (theta < 1e-3) {
57
- return {
58
- x: n1.x + t * (n2.x - n1.x),
59
- y: n1.y + t * (n2.y - n1.y),
60
- z: n1.z + t * (n2.z - n1.z)
61
- };
62
- }
63
- const sinTheta = Math.sin(theta);
64
- if (Math.abs(sinTheta) < 1e-4) {
65
- return {
66
- x: n1.x + t * (n2.x - n1.x),
67
- y: n1.y + t * (n2.y - n1.y),
68
- z: n1.z + t * (n2.z - n1.z)
69
- };
70
- }
71
- const a = Math.sin((1 - t) * theta) / sinTheta;
72
- const b = Math.sin(t * theta) / sinTheta;
73
- return {
74
- x: a * n1.x + b * n2.x,
75
- y: a * n1.y + b * n2.y,
76
- z: a * n1.z + b * n2.z
77
- };
63
+ const len1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z);
64
+ const len2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z);
65
+ if (len1 < 1e-4 || len2 < 1e-4) return {
66
+ x: v1.x,
67
+ y: v1.y,
68
+ z: v1.z
69
+ };
70
+ const n1 = {
71
+ x: v1.x / len1,
72
+ y: v1.y / len1,
73
+ z: v1.z / len1
74
+ };
75
+ const n2 = {
76
+ x: v2.x / len2,
77
+ y: v2.y / len2,
78
+ z: v2.z / len2
79
+ };
80
+ let dot = n1.x * n2.x + n1.y * n2.y + n1.z * n2.z;
81
+ dot = Math.max(-1, Math.min(1, dot));
82
+ const theta = Math.acos(dot);
83
+ if (theta < .001) return {
84
+ x: n1.x + t * (n2.x - n1.x),
85
+ y: n1.y + t * (n2.y - n1.y),
86
+ z: n1.z + t * (n2.z - n1.z)
87
+ };
88
+ const sinTheta = Math.sin(theta);
89
+ if (Math.abs(sinTheta) < 1e-4) return {
90
+ x: n1.x + t * (n2.x - n1.x),
91
+ y: n1.y + t * (n2.y - n1.y),
92
+ z: n1.z + t * (n2.z - n1.z)
93
+ };
94
+ const a = Math.sin((1 - t) * theta) / sinTheta;
95
+ const b = Math.sin(t * theta) / sinTheta;
96
+ return {
97
+ x: a * n1.x + b * n2.x,
98
+ y: a * n1.y + b * n2.y,
99
+ z: a * n1.z + b * n2.z
100
+ };
78
101
  }
102
+ /**
103
+ * Subdivide polygon edges along great circles for spherical rendering
104
+ * Takes corner points and returns a densely sampled spherical polygon
105
+ */
79
106
  function subdivideSphericalPolygon(corners, subdivisions, radius) {
80
- if (corners.length < 3 || subdivisions < 1 || radius <= 0) {
81
- return corners;
82
- }
83
- const result = [];
84
- for (let i = 0; i < corners.length; i++) {
85
- const p1 = corners[i];
86
- const p2 = corners[(i + 1) % corners.length];
87
- if (!isFinite(p1.x) || !isFinite(p1.y) || !isFinite(p1.z) || !isFinite(p2.x) || !isFinite(p2.y) || !isFinite(p2.z)) {
88
- continue;
89
- }
90
- for (let j = 0; j < subdivisions; j++) {
91
- const t = j / subdivisions;
92
- const interpolated = slerp(p1, p2, t);
93
- if (!isFinite(interpolated.x) || !isFinite(interpolated.y) || !isFinite(interpolated.z)) {
94
- continue;
95
- }
96
- const len = Math.sqrt(interpolated.x ** 2 + interpolated.y ** 2 + interpolated.z ** 2);
97
- if (len < 1e-4) continue;
98
- result.push({
99
- x: interpolated.x / len * radius,
100
- y: interpolated.y / len * radius,
101
- z: interpolated.z / len * radius
102
- });
103
- }
104
- }
105
- return result.length > 0 ? result : corners;
107
+ if (corners.length < 3 || subdivisions < 1 || radius <= 0) return corners;
108
+ const result = [];
109
+ for (let i = 0; i < corners.length; i++) {
110
+ const p1 = corners[i];
111
+ const p2 = corners[(i + 1) % corners.length];
112
+ if (!isFinite(p1.x) || !isFinite(p1.y) || !isFinite(p1.z) || !isFinite(p2.x) || !isFinite(p2.y) || !isFinite(p2.z)) continue;
113
+ for (let j = 0; j < subdivisions; j++) {
114
+ const interpolated = slerp(p1, p2, j / subdivisions);
115
+ if (!isFinite(interpolated.x) || !isFinite(interpolated.y) || !isFinite(interpolated.z)) continue;
116
+ const len = Math.sqrt(interpolated.x ** 2 + interpolated.y ** 2 + interpolated.z ** 2);
117
+ if (len < 1e-4) continue;
118
+ result.push({
119
+ x: interpolated.x / len * radius,
120
+ y: interpolated.y / len * radius,
121
+ z: interpolated.z / len * radius
122
+ });
123
+ }
124
+ }
125
+ return result.length > 0 ? result : corners;
106
126
  }
107
127
  function createSatelliteShape(THREE, category, size) {
108
- const group = new THREE.Group();
109
- if (category === "station") {
110
- const geo = new THREE.BoxGeometry(size * 1.2, size * 0.6, size * 0.6);
111
- const mat = new THREE.MeshBasicMaterial({ color: 13421772 });
112
- group.add(new THREE.Mesh(geo, mat));
113
- const panelGeo = new THREE.BoxGeometry(size * 2.5, size * 0.03, size * 0.6);
114
- const panelMat = new THREE.MeshBasicMaterial({ color: 1723002 });
115
- const leftPanel = new THREE.Mesh(panelGeo, panelMat);
116
- leftPanel.position.x = -size * 1.5;
117
- group.add(leftPanel);
118
- const rightPanel = new THREE.Mesh(panelGeo, panelMat);
119
- rightPanel.position.x = size * 1.5;
120
- group.add(rightPanel);
121
- } else if (category === "satellite") {
122
- const geo = new THREE.BoxGeometry(size * 0.5, size * 0.5, size * 0.6);
123
- const mat = new THREE.MeshBasicMaterial({ color: 11184810 });
124
- group.add(new THREE.Mesh(geo, mat));
125
- const panelGeo = new THREE.BoxGeometry(size * 1.2, size * 0.02, size * 0.4);
126
- const panelMat = new THREE.MeshBasicMaterial({ color: 1718890 });
127
- const leftPanel = new THREE.Mesh(panelGeo, panelMat);
128
- leftPanel.position.x = -size * 0.7;
129
- group.add(leftPanel);
130
- const rightPanel = new THREE.Mesh(panelGeo, panelMat);
131
- rightPanel.position.x = size * 0.7;
132
- group.add(rightPanel);
133
- } else if (category === "debris") {
134
- const geo = new THREE.IcosahedronGeometry(size * 0.3, 0);
135
- const mat = new THREE.MeshBasicMaterial({ color: 6710886 });
136
- group.add(new THREE.Mesh(geo, mat));
137
- } else {
138
- const geo = new THREE.SphereGeometry(size * 0.4, 8, 6);
139
- const mat = new THREE.MeshBasicMaterial({ color: 8947848 });
140
- group.add(new THREE.Mesh(geo, mat));
141
- }
142
- return group;
128
+ const group = new THREE.Group();
129
+ if (category === "station") {
130
+ const geo = new THREE.BoxGeometry(size * 1.2, size * .6, size * .6);
131
+ const mat = new THREE.MeshBasicMaterial({ color: 13421772 });
132
+ group.add(new THREE.Mesh(geo, mat));
133
+ const panelGeo = new THREE.BoxGeometry(size * 2.5, size * .03, size * .6);
134
+ const panelMat = new THREE.MeshBasicMaterial({ color: 1723002 });
135
+ const leftPanel = new THREE.Mesh(panelGeo, panelMat);
136
+ leftPanel.position.x = -size * 1.5;
137
+ group.add(leftPanel);
138
+ const rightPanel = new THREE.Mesh(panelGeo, panelMat);
139
+ rightPanel.position.x = size * 1.5;
140
+ group.add(rightPanel);
141
+ } else if (category === "satellite") {
142
+ const geo = new THREE.BoxGeometry(size * .5, size * .5, size * .6);
143
+ const mat = new THREE.MeshBasicMaterial({ color: 11184810 });
144
+ group.add(new THREE.Mesh(geo, mat));
145
+ const panelGeo = new THREE.BoxGeometry(size * 1.2, size * .02, size * .4);
146
+ const panelMat = new THREE.MeshBasicMaterial({ color: 1718890 });
147
+ const leftPanel = new THREE.Mesh(panelGeo, panelMat);
148
+ leftPanel.position.x = -size * .7;
149
+ group.add(leftPanel);
150
+ const rightPanel = new THREE.Mesh(panelGeo, panelMat);
151
+ rightPanel.position.x = size * .7;
152
+ group.add(rightPanel);
153
+ } else if (category === "debris") {
154
+ const geo = new THREE.IcosahedronGeometry(size * .3, 0);
155
+ const mat = new THREE.MeshBasicMaterial({ color: 6710886 });
156
+ group.add(new THREE.Mesh(geo, mat));
157
+ } else {
158
+ const geo = new THREE.SphereGeometry(size * .4, 8, 6);
159
+ const mat = new THREE.MeshBasicMaterial({ color: 8947848 });
160
+ group.add(new THREE.Mesh(geo, mat));
161
+ }
162
+ return group;
143
163
  }
144
- const ZenSpace3D = forwardRef(
145
- function ZenSpace3D2(props, ref) {
146
- const [cesiumModule, setCesiumModule] = useState(null);
147
- useEffect(() => {
148
- import("cesium").then((m) => setCesiumModule(m)).catch(() => setCesiumModule(null));
149
- }, []);
150
- if (cesiumModule) {
151
- return /* @__PURE__ */ jsx(ZenSpace3DCesium, { ref, cesium: cesiumModule, ...props });
152
- }
153
- return /* @__PURE__ */ jsx(ZenSpace3DThree, { ref, ...props });
154
- }
155
- );
156
- const ZenSpace3DThree = forwardRef(
157
- function ZenSpace3DThree2(props, ref) {
158
- const {
159
- satellites = [],
160
- debris = [],
161
- stations = [],
162
- groundStations = [],
163
- customObjects = [],
164
- view = "earth-orbit",
165
- focusedObjectId,
166
- initialCamera,
167
- sunDirection,
168
- planetPositions,
169
- visibilityCones = [],
170
- satelliteCoverages = [],
171
- overpasses: _overpasses = [],
172
- orbitPaths = [],
173
- orbitDesignArcs: _orbitDesignArcs = [],
174
- communicationLinks = [],
175
- showAxisHelper = false,
176
- layers: layerOverrides,
177
- showTools = true,
178
- showInfoPanel = true,
179
- width = "100%",
180
- height = 600,
181
- callbacks,
182
- maxObjects = 1e4,
183
- className = "",
184
- style
185
- } = props;
186
- const { tokens: _tokens } = useTheme();
187
- const [webglCapabilities] = useState(() => checkWebGLCapabilities());
188
- const containerRef = useRef(null);
189
- const rendererRef = useRef(null);
190
- const sceneRef = useRef(null);
191
- const cameraRef = useRef(null);
192
- const controlsRef = useRef(null);
193
- const animationRef = useRef(null);
194
- const raycasterRef = useRef(null);
195
- const mouseRef = useRef(null);
196
- const earthRef = useRef(null);
197
- const starsMaterialRef = useRef(null);
198
- const atmosphereMaterialRef = useRef(null);
199
- const objectsGroupRef = useRef(null);
200
- const linksGroupRef = useRef(null);
201
- const conesGroupRef = useRef(null);
202
- const orbitsGroupRef = useRef(null);
203
- const planetsGroupRef = useRef(null);
204
- const timeRef = useRef(0);
205
- const focusTransitionRef = useRef(null);
206
- const isUserInteractingRef = useRef(false);
207
- const objectMeshMapRef = useRef(/* @__PURE__ */ new Map());
208
- const isAnimatingRef = useRef(false);
209
- useRef(false);
210
- const [THREE, setTHREE] = useState(null);
211
- const threeRef = useRef(null);
212
- threeRef.current = THREE;
213
- const [OrbitControlsClass, setOrbitControlsClass] = useState(null);
214
- const [isLoading, setIsLoading] = useState(true);
215
- const [error, setError] = useState(null);
216
- const [layers, setLayers] = useState({ ...DEFAULT_LAYERS, ...layerOverrides });
217
- const [selection, setSelection] = useState({ selectedId: null, selectedObject: null, hoveredId: null });
218
- const [hoverTooltip, setHoverTooltip] = useState({ visible: false, x: 0, y: 0, object: null });
219
- const [currentView, setCurrentView] = useState(view);
220
- const currentViewRef = useRef(view);
221
- const effectiveLayers = useMemo(() => ({ ...layers, ...layerOverrides }), [layers, layerOverrides]);
222
- useEffect(() => {
223
- setCurrentView(view);
224
- currentViewRef.current = view;
225
- }, [view]);
226
- useEffect(() => {
227
- if (!webglCapabilities.available) {
228
- setIsLoading(false);
229
- return;
230
- }
231
- loadThree().then(({ THREE: threeModule, OrbitControls: controls }) => {
232
- setTHREE(threeModule);
233
- setOrbitControlsClass(() => controls);
234
- }).catch((err) => {
235
- setError("Failed to load 3D library");
236
- setIsLoading(false);
237
- });
238
- }, [webglCapabilities.available]);
239
- const initScene = useCallback(() => {
240
- var _a, _b;
241
- if (!THREE || !containerRef.current || !OrbitControlsClass) return;
242
- if (rendererRef.current) {
243
- isAnimatingRef.current = false;
244
- if (animationRef.current) {
245
- cancelAnimationFrame(animationRef.current);
246
- animationRef.current = null;
247
- }
248
- (_a = controlsRef.current) == null ? void 0 : _a.dispose();
249
- rendererRef.current.dispose();
250
- if ((_b = containerRef.current) == null ? void 0 : _b.contains(rendererRef.current.domElement)) {
251
- containerRef.current.removeChild(rendererRef.current.domElement);
252
- }
253
- }
254
- const container = containerRef.current;
255
- const rect = container.getBoundingClientRect();
256
- const w = rect.width;
257
- const h = typeof height === "number" ? height : rect.height;
258
- const scene = new THREE.Scene();
259
- sceneRef.current = scene;
260
- const viewMode = currentViewRef.current;
261
- const farClip = viewMode === "solar-system" ? 1e6 : 1e5;
262
- const camera = new THREE.PerspectiveCamera(45, w / h, 1, farClip);
263
- const initialDistance = viewMode === "solar-system" ? 5e3 : 1e4;
264
- camera.position.set(0, initialDistance * 0.3, initialDistance);
265
- cameraRef.current = camera;
266
- if (initialCamera == null ? void 0 : initialCamera.position) {
267
- camera.position.set(initialCamera.position.x, initialCamera.position.y, initialCamera.position.z);
268
- }
269
- const renderer = new THREE.WebGLRenderer({
270
- antialias: true,
271
- alpha: true,
272
- powerPreference: "high-performance",
273
- preserveDrawingBuffer: true
274
- // Prevents flickering from buffer swaps
275
- });
276
- renderer.setSize(w, h);
277
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
278
- container.appendChild(renderer.domElement);
279
- rendererRef.current = renderer;
280
- const controls = new OrbitControlsClass(camera, renderer.domElement);
281
- controls.enableDamping = true;
282
- controls.dampingFactor = 0.05;
283
- controls.rotateSpeed = 0.5;
284
- controls.minDistance = viewMode === "solar-system" ? 500 : 3050;
285
- controls.maxDistance = viewMode === "solar-system" ? 1e5 : 25e3;
286
- controls.enablePan = viewMode === "solar-system";
287
- let interactionTimer;
288
- const handleControlStart = () => {
289
- isUserInteractingRef.current = true;
290
- };
291
- const handleControlEnd = () => {
292
- clearTimeout(interactionTimer);
293
- interactionTimer = setTimeout(() => {
294
- isUserInteractingRef.current = false;
295
- }, 3e3);
296
- };
297
- controls.addEventListener("start", handleControlStart);
298
- controls.addEventListener("end", handleControlEnd);
299
- controlsRef.current = controls;
300
- raycasterRef.current = new THREE.Raycaster();
301
- mouseRef.current = new THREE.Vector2();
302
- objectsGroupRef.current = new THREE.Group();
303
- objectsGroupRef.current.name = "objects";
304
- scene.add(objectsGroupRef.current);
305
- linksGroupRef.current = new THREE.Group();
306
- linksGroupRef.current.name = "links";
307
- scene.add(linksGroupRef.current);
308
- conesGroupRef.current = new THREE.Group();
309
- conesGroupRef.current.name = "cones";
310
- scene.add(conesGroupRef.current);
311
- orbitsGroupRef.current = new THREE.Group();
312
- orbitsGroupRef.current.name = "orbits";
313
- scene.add(orbitsGroupRef.current);
314
- planetsGroupRef.current = new THREE.Group();
315
- planetsGroupRef.current.name = "planets";
316
- scene.add(planetsGroupRef.current);
317
- if (viewMode === "solar-system") {
318
- createSolarSystem(THREE, scene);
319
- } else {
320
- createEarth(THREE, scene);
321
- createAtmosphere(THREE, scene);
322
- }
323
- createStars(THREE, scene);
324
- createLighting(THREE, scene);
325
- if (showAxisHelper && viewMode !== "solar-system") {
326
- createAxisHelper(THREE, scene);
327
- }
328
- setIsLoading(false);
329
- return () => {
330
- clearTimeout(interactionTimer);
331
- if (animationRef.current) cancelAnimationFrame(animationRef.current);
332
- controls.removeEventListener("start", handleControlStart);
333
- controls.removeEventListener("end", handleControlEnd);
334
- controls.dispose();
335
- renderer.dispose();
336
- if (container.contains(renderer.domElement)) container.removeChild(renderer.domElement);
337
- };
338
- }, [THREE, OrbitControlsClass, height, initialCamera, showAxisHelper]);
339
- const createSolarSystem = useCallback((THREE2, _scene) => {
340
- const planetsGroup = planetsGroupRef.current;
341
- if (!planetsGroup) return;
342
- const DISTANCE_SCALE = 5e-6;
343
- const SIZE_SCALE = 0.01;
344
- const sunRadius = 696340 * SIZE_SCALE;
345
- const sunGeo = new THREE2.SphereGeometry(sunRadius, 32, 24);
346
- const sunMat = new THREE2.MeshBasicMaterial({ color: 16768324 });
347
- const sunMesh = new THREE2.Mesh(sunGeo, sunMat);
348
- sunMesh.name = "sun";
349
- sunMesh.userData = { objectId: "sun", planetId: "sun", isPlanet: true, isSelectable: true };
350
- planetsGroup.add(sunMesh);
351
- const glowGeo = new THREE2.SphereGeometry(sunRadius * 1.3, 24, 16);
352
- const glowMat = new THREE2.MeshBasicMaterial({ color: 16755200, transparent: true, opacity: 0.2 });
353
- planetsGroup.add(new THREE2.Mesh(glowGeo, glowMat));
354
- const configs = [
355
- { id: "mercury", color: 9205843, radius: 2439.7, distance: 579e5 },
356
- { id: "venus", color: 16762441, radius: 6051.8, distance: 1082e5 },
357
- { id: "earth", color: 7050198, radius: 6371, distance: 1496e5 },
358
- { id: "mars", color: 12665870, radius: 3389.5, distance: 2279e5 },
359
- { id: "jupiter", color: 14207645, radius: 69911, distance: 7785e5 },
360
- { id: "saturn", color: 16045470, radius: 58232, distance: 1434e6 },
361
- { id: "uranus", color: 13756391, radius: 25362, distance: 2871e6 },
362
- { id: "neptune", color: 5987807, radius: 24622, distance: 4495e6 }
363
- ];
364
- configs.forEach((c) => {
365
- const visualScale = c.distance < 3e8 ? 15 : 5;
366
- const planetRadius = c.radius * SIZE_SCALE * visualScale;
367
- const geo = new THREE2.SphereGeometry(planetRadius, 24, 16);
368
- const mat = new THREE2.MeshPhongMaterial({ color: c.color, shininess: 30 });
369
- const mesh = new THREE2.Mesh(geo, mat);
370
- const orbitDist = c.distance * DISTANCE_SCALE;
371
- mesh.position.x = orbitDist;
372
- mesh.name = c.id;
373
- mesh.userData = { objectId: c.id, planetId: c.id, isPlanet: true, isSelectable: true, data: { id: c.id, name: c.id.charAt(0).toUpperCase() + c.id.slice(1), category: "planet" } };
374
- planetsGroup.add(mesh);
375
- objectMeshMapRef.current.set(c.id, mesh);
376
- if (c.id === "saturn") {
377
- const ringGeo = new THREE2.RingGeometry(planetRadius * 1.4, planetRadius * 2.2, 32);
378
- const ringMat = new THREE2.MeshBasicMaterial({ color: 13219990, transparent: true, opacity: 0.6, side: THREE2.DoubleSide });
379
- const ring = new THREE2.Mesh(ringGeo, ringMat);
380
- ring.rotation.x = Math.PI / 2.5;
381
- ring.position.copy(mesh.position);
382
- planetsGroup.add(ring);
383
- }
384
- const points = [];
385
- for (let i = 0; i <= 64; i++) {
386
- const angle = i / 64 * Math.PI * 2;
387
- points.push(new THREE2.Vector3(orbitDist * Math.cos(angle), 0, orbitDist * Math.sin(angle)));
388
- }
389
- const orbitGeo = new THREE2.BufferGeometry().setFromPoints(points);
390
- const orbitMat = new THREE2.LineBasicMaterial({ color: 4473958, transparent: true, opacity: 0.3 });
391
- planetsGroup.add(new THREE2.Line(orbitGeo, orbitMat));
392
- });
393
- const sunLight = new THREE2.PointLight(16777198, 2, 1e5);
394
- sunLight.position.set(0, 0, 0);
395
- planetsGroup.add(sunLight);
396
- }, []);
397
- const createEarth = useCallback((THREE2, scene) => {
398
- const earthGeometry = new THREE2.SphereGeometry(SCENE_EARTH_RADIUS, EARTH_SEGMENTS, EARTH_SEGMENTS_V);
399
- const earthMaterial = new THREE2.MeshPhongMaterial({ color: 2245802, shininess: 10 });
400
- const textureLoader = new THREE2.TextureLoader();
401
- textureLoader.load("/world.topo.jpg", (texture) => {
402
- texture.wrapS = THREE2.RepeatWrapping;
403
- texture.wrapT = THREE2.ClampToEdgeWrapping;
404
- texture.minFilter = THREE2.LinearFilter;
405
- texture.magFilter = THREE2.LinearFilter;
406
- earthMaterial.map = texture;
407
- earthMaterial.needsUpdate = true;
408
- }, void 0, () => {
409
- });
410
- const earth = new THREE2.Mesh(earthGeometry, earthMaterial);
411
- earth.rotation.y = -Math.PI / 2 + Math.PI;
412
- earth.name = "earth";
413
- scene.add(earth);
414
- earthRef.current = earth;
415
- }, []);
416
- const createAtmosphere = useCallback((THREE2, scene) => {
417
- if (!effectiveLayers.atmosphere) return;
418
- const defaultSunDir = new THREE2.Vector3(1, 0.3, 0.5).normalize();
419
- const atmosphereGeometry = new THREE2.SphereGeometry(SCENE_EARTH_RADIUS * 1.15, 32, 16);
420
- const atmosphereMaterial = new THREE2.ShaderMaterial({
421
- uniforms: {
422
- uSunDirection: { value: defaultSunDir }
423
- },
424
- vertexShader: atmosphereVertexShader,
425
- fragmentShader: atmosphereFragmentShader,
426
- blending: THREE2.AdditiveBlending,
427
- side: THREE2.BackSide,
428
- transparent: true,
429
- depthWrite: false
430
- });
431
- const atmosphere = new THREE2.Mesh(atmosphereGeometry, atmosphereMaterial);
432
- atmosphere.rotation.y = -Math.PI / 2;
433
- atmosphere.name = "atmosphere";
434
- scene.add(atmosphere);
435
- atmosphereMaterialRef.current = atmosphereMaterial;
436
- }, [effectiveLayers.atmosphere]);
437
- const createStars = useCallback((THREE2, scene) => {
438
- const starsGeometry = new THREE2.BufferGeometry();
439
- const starsDist = currentView === "solar-system" ? 5e10 : 5e4;
440
- const positions = new Float32Array(STARS_COUNT * 3);
441
- const opacities = new Float32Array(STARS_COUNT);
442
- for (let i = 0; i < STARS_COUNT; i++) {
443
- const radius = starsDist + Math.random() * starsDist;
444
- const theta = Math.random() * Math.PI * 2;
445
- const phi = Math.acos(2 * Math.random() - 1);
446
- positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
447
- positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
448
- positions[i * 3 + 2] = radius * Math.cos(phi);
449
- opacities[i] = Math.random();
450
- }
451
- starsGeometry.setAttribute("position", new THREE2.BufferAttribute(positions, 3));
452
- starsGeometry.setAttribute("opacity", new THREE2.BufferAttribute(opacities, 1));
453
- const starsMaterial = new THREE2.ShaderMaterial({
454
- uniforms: { time: { value: 0 } },
455
- vertexShader: starsVertexShader,
456
- fragmentShader: starsFragmentShader,
457
- transparent: true,
458
- depthWrite: false,
459
- blending: THREE2.AdditiveBlending
460
- });
461
- scene.add(new THREE2.Points(starsGeometry, starsMaterial));
462
- starsMaterialRef.current = starsMaterial;
463
- }, [currentView]);
464
- const createLighting = useCallback((THREE2, scene) => {
465
- scene.add(new THREE2.AmbientLight(4210752, 0.6));
466
- const sunLight = new THREE2.DirectionalLight(16777215, 1.2);
467
- sunLight.position.set(1e4, 5e3, 1e4);
468
- scene.add(sunLight);
469
- }, []);
470
- const createAxisHelper = useCallback((THREE2, scene) => {
471
- const len = SCENE_EARTH_RADIUS * 1.5;
472
- const group = new THREE2.Group();
473
- [
474
- { color: 16724787, dir: [1, 0, 0] },
475
- { color: 3407667, dir: [0, 1, 0] },
476
- { color: 3355647, dir: [0, 0, 1] }
477
- ].forEach(({ color, dir }) => {
478
- const points = [new THREE2.Vector3(0, 0, 0), new THREE2.Vector3(dir[0] * len, dir[1] * len, dir[2] * len)];
479
- const geo = new THREE2.BufferGeometry().setFromPoints(points);
480
- const mat = new THREE2.LineBasicMaterial({ color });
481
- group.add(new THREE2.Line(geo, mat));
482
- });
483
- scene.add(group);
484
- }, []);
485
- const updateObjects = useCallback(() => {
486
- if (!THREE || !objectsGroupRef.current || currentView === "solar-system") return;
487
- const group = objectsGroupRef.current;
488
- const meshMap = objectMeshMapRef.current;
489
- while (group.children.length > 0) {
490
- const child = group.children[0];
491
- group.remove(child);
492
- }
493
- meshMap.clear();
494
- const allObjects = [...satellites, ...debris, ...stations, ...customObjects];
495
- allObjects.slice(0, maxObjects).forEach((obj) => {
496
- const categoryLayer = obj.category === "satellite" ? "satellites" : obj.category === "debris" ? "debris" : obj.category === "station" ? "stations" : "satellites";
497
- if (!effectiveLayers[categoryLayer]) return;
498
- let size = 50;
499
- if (obj.category === "station") size = 100;
500
- else if (obj.category === "debris") size = 25;
501
- const satelliteGroup = createSatelliteShape(THREE, obj.category, size);
502
- satelliteGroup.position.set(obj.position.x, obj.position.y, obj.position.z);
503
- satelliteGroup.lookAt(0, 0, 0);
504
- satelliteGroup.userData = { objectId: obj.id, category: obj.category, data: obj, isSelectable: true };
505
- satelliteGroup.name = obj.id;
506
- group.add(satelliteGroup);
507
- meshMap.set(obj.id, satelliteGroup);
508
- });
509
- }, [THREE, satellites, debris, stations, customObjects, maxObjects, effectiveLayers, currentView]);
510
- const updateGroundStations = useCallback(() => {
511
- if (!THREE || !objectsGroupRef.current || currentView === "solar-system") return;
512
- if (!effectiveLayers.groundStations) return;
513
- groundStations.forEach((gs) => {
514
- const pos = latLonAltToCartesian(gs.latitude, gs.longitude, 0);
515
- const geo = new THREE.SphereGeometry(35, 8, 6);
516
- const mat = new THREE.MeshBasicMaterial({ color: 65535 });
517
- const marker = new THREE.Mesh(geo, mat);
518
- marker.position.set(pos.x, pos.y, pos.z);
519
- marker.userData = { objectId: gs.id, category: "groundStation", data: gs, isSelectable: true };
520
- objectsGroupRef.current.add(marker);
521
- objectMeshMapRef.current.set(gs.id, marker);
522
- const ringGeo = new THREE.RingGeometry(45, 65, 16);
523
- const ringMat = new THREE.MeshBasicMaterial({ color: 65535, transparent: true, opacity: 0.4, side: THREE.DoubleSide });
524
- const ring = new THREE.Mesh(ringGeo, ringMat);
525
- ring.position.set(pos.x, pos.y, pos.z);
526
- ring.lookAt(0, 0, 0);
527
- objectsGroupRef.current.add(ring);
528
- });
529
- }, [THREE, groundStations, effectiveLayers.groundStations, currentView]);
530
- const updateCoverage = useCallback(() => {
531
- const T = threeRef.current;
532
- if (!T || !conesGroupRef.current || currentView === "solar-system") return;
533
- while (conesGroupRef.current.children.length > 0) {
534
- conesGroupRef.current.remove(conesGroupRef.current.children[0]);
535
- }
536
- if (!effectiveLayers.coverage) return;
537
- satelliteCoverages.forEach((coverage) => {
538
- let satPos;
539
- if (coverage.satellitePosition) {
540
- satPos = new T.Vector3(coverage.satellitePosition.x, coverage.satellitePosition.y, coverage.satellitePosition.z);
541
- } else {
542
- const satMesh = objectMeshMapRef.current.get(coverage.satelliteId);
543
- if (!satMesh) return;
544
- satPos = satMesh.position.clone();
545
- }
546
- const satDistance = satPos.length();
547
- const coneColor = new T.Color(coverage.color || "#22d3ee");
548
- const highlightColor = new T.Color(coverage.color || "#22d3ee").multiplyScalar(1.3);
549
- if (coverage.footprintPolygon && coverage.footprintPolygon.length >= 3 && coverage.showFootprint !== false) {
550
- const cornerPoints = coverage.footprintPolygon.map((pt) => latLonAltToCartesian(pt.latitude, pt.longitude, 0));
551
- const borderPts = subdivideSphericalPolygon(cornerPoints, SPHERICAL_SUBDIVISIONS, SCENE_EARTH_RADIUS * 1.003).map((pt) => new T.Vector3(pt.x, pt.y, pt.z));
552
- borderPts.push(borderPts[0].clone());
553
- const borderGeo = new T.BufferGeometry().setFromPoints(borderPts);
554
- const borderMat = new T.LineBasicMaterial({ color: highlightColor, transparent: true, opacity: 0.95, linewidth: 2 });
555
- conesGroupRef.current.add(new T.Line(borderGeo, borderMat));
556
- const glowPts = subdivideSphericalPolygon(cornerPoints, SPHERICAL_SUBDIVISIONS, SCENE_EARTH_RADIUS * 1.005).map((pt) => new T.Vector3(pt.x, pt.y, pt.z));
557
- glowPts.push(glowPts[0].clone());
558
- const glowGeo = new T.BufferGeometry().setFromPoints(glowPts);
559
- const glowMat = new T.LineBasicMaterial({ color: coneColor, transparent: true, opacity: 0.4 });
560
- conesGroupRef.current.add(new T.Line(glowGeo, glowMat));
561
- if (coverage.showCone !== false) {
562
- for (const corner of cornerPoints) {
563
- const edgePt = new T.Vector3(corner.x, corner.y, corner.z).normalize().multiplyScalar(SCENE_EARTH_RADIUS);
564
- const lineGeo = new T.BufferGeometry().setFromPoints([satPos, edgePt]);
565
- const lineMat = new T.LineBasicMaterial({ color: coneColor, transparent: true, opacity: 0.4 });
566
- conesGroupRef.current.add(new T.Line(lineGeo, lineMat));
567
- }
568
- }
569
- } else if (coverage.halfAngle && coverage.showCone !== false) {
570
- const halfAngleRad = coverage.halfAngle * (Math.PI / 180);
571
- const coneHeight = satDistance - SCENE_EARTH_RADIUS * 0.98;
572
- const coneRadius = coneHeight * Math.tan(halfAngleRad);
573
- const coneGeo = new T.ConeGeometry(coneRadius, coneHeight, 48, 1, true);
574
- const coneMat = new T.MeshBasicMaterial({
575
- color: coneColor,
576
- transparent: true,
577
- opacity: 0.12,
578
- side: T.DoubleSide,
579
- depthWrite: false
580
- });
581
- const cone = new T.Mesh(coneGeo, coneMat);
582
- cone.position.copy(satPos);
583
- cone.lookAt(0, 0, 0);
584
- cone.rotateX(Math.PI / 2);
585
- cone.translateY(-coneHeight / 2);
586
- conesGroupRef.current.add(cone);
587
- if (coverage.showFootprint !== false) {
588
- const nadirDir = satPos.clone().normalize();
589
- const footprintPos = nadirDir.clone().multiplyScalar(SCENE_EARTH_RADIUS * 1.003);
590
- const footprintRadius = coneRadius * (SCENE_EARTH_RADIUS / satDistance);
591
- const footGeo = new T.CircleGeometry(footprintRadius, 64);
592
- const footMat = new T.MeshBasicMaterial({
593
- color: coneColor,
594
- transparent: true,
595
- opacity: 0.3,
596
- side: T.DoubleSide,
597
- depthWrite: false
598
- });
599
- const footprint = new T.Mesh(footGeo, footMat);
600
- footprint.position.copy(footprintPos);
601
- footprint.lookAt(satPos);
602
- conesGroupRef.current.add(footprint);
603
- const ringGeo = new T.RingGeometry(footprintRadius * 0.97, footprintRadius, 64);
604
- const ringMat = new T.MeshBasicMaterial({
605
- color: highlightColor,
606
- transparent: true,
607
- opacity: 0.7,
608
- side: T.DoubleSide
609
- });
610
- const ring = new T.Mesh(ringGeo, ringMat);
611
- ring.position.copy(footprintPos);
612
- ring.lookAt(satPos);
613
- conesGroupRef.current.add(ring);
614
- }
615
- }
616
- if (coverage.showNadirLine !== false) {
617
- let nadirGroundPoint;
618
- if (coverage.nadirPoint) {
619
- const np = latLonAltToCartesian(coverage.nadirPoint.latitude, coverage.nadirPoint.longitude, 0);
620
- nadirGroundPoint = new T.Vector3(np.x, np.y, np.z);
621
- } else {
622
- nadirGroundPoint = satPos.clone().normalize().multiplyScalar(SCENE_EARTH_RADIUS);
623
- }
624
- const lineGeo = new T.BufferGeometry().setFromPoints([satPos, nadirGroundPoint]);
625
- const lineMat = new T.LineBasicMaterial({ color: highlightColor, transparent: true, opacity: 0.7 });
626
- conesGroupRef.current.add(new T.Line(lineGeo, lineMat));
627
- }
628
- });
629
- visibilityCones.forEach((cone) => {
630
- const pos = latLonAltToCartesian(cone.center.latitude, cone.center.longitude, 0);
631
- const coneHeight = 1e3 * KM_TO_SCENE;
632
- const minElev = cone.minElevation || 10;
633
- const coneRadius = coneHeight * Math.tan((90 - minElev) * (Math.PI / 180) * 0.4);
634
- const coneColor = new T.Color(cone.color || "#22d3ee");
635
- const coneGeo = new T.ConeGeometry(coneRadius, coneHeight, 24, 1, true);
636
- const coneMat = new T.MeshBasicMaterial({
637
- color: coneColor,
638
- transparent: true,
639
- opacity: cone.opacity || 0.12,
640
- side: T.DoubleSide,
641
- depthWrite: false
642
- });
643
- const coneMesh = new T.Mesh(coneGeo, coneMat);
644
- coneMesh.position.set(pos.x, pos.y, pos.z);
645
- coneMesh.lookAt(0, 0, 0);
646
- coneMesh.rotateX(-Math.PI / 2);
647
- coneMesh.translateY(coneHeight / 2);
648
- conesGroupRef.current.add(coneMesh);
649
- const footGeo = new T.RingGeometry(coneRadius * 0.7, coneRadius * 0.9, 32);
650
- const footMat = new T.MeshBasicMaterial({
651
- color: coneColor,
652
- transparent: true,
653
- opacity: 0.3,
654
- side: T.DoubleSide
655
- });
656
- const foot = new T.Mesh(footGeo, footMat);
657
- foot.position.set(pos.x * 1.002, pos.y * 1.002, pos.z * 1.002);
658
- foot.lookAt(0, 0, 0);
659
- conesGroupRef.current.add(foot);
660
- });
661
- }, [visibilityCones, satelliteCoverages, effectiveLayers.coverage, currentView]);
662
- const updateLinks = useCallback(() => {
663
- if (!THREE || !linksGroupRef.current || currentView === "solar-system") return;
664
- while (linksGroupRef.current.children.length > 0) {
665
- linksGroupRef.current.remove(linksGroupRef.current.children[0]);
666
- }
667
- communicationLinks.forEach((link) => {
668
- const fromMesh = objectMeshMapRef.current.get(link.fromId);
669
- const toMesh = objectMeshMapRef.current.get(link.toId);
670
- if (!fromMesh || !toMesh) return;
671
- const color = link.color || (link.status === "active" ? "#00ff88" : "#666666");
672
- const geo = new THREE.BufferGeometry().setFromPoints([fromMesh.position.clone(), toMesh.position.clone()]);
673
- const mat = new THREE.LineDashedMaterial({
674
- color: new THREE.Color(color),
675
- transparent: true,
676
- opacity: link.status === "active" ? 0.8 : 0.4,
677
- dashSize: 30,
678
- gapSize: 20
679
- });
680
- const line = new THREE.Line(geo, mat);
681
- line.computeLineDistances();
682
- line.userData = { isAnimatedLink: true };
683
- linksGroupRef.current.add(line);
684
- });
685
- }, [THREE, communicationLinks, currentView]);
686
- const updateOrbits = useCallback(() => {
687
- if (!THREE || !orbitsGroupRef.current || currentView === "solar-system") return;
688
- while (orbitsGroupRef.current.children.length > 0) {
689
- orbitsGroupRef.current.remove(orbitsGroupRef.current.children[0]);
690
- }
691
- if (!effectiveLayers.orbits) return;
692
- orbitPaths.forEach((path) => {
693
- if (path.points.length < 2) return;
694
- const points = path.points.map((p) => new THREE.Vector3(p.x, p.y, p.z));
695
- const geo = new THREE.BufferGeometry().setFromPoints(points);
696
- const mat = new THREE.LineBasicMaterial({ color: new THREE.Color(path.color || "#888888"), transparent: true, opacity: 0.5 });
697
- orbitsGroupRef.current.add(new THREE.Line(geo, mat));
698
- });
699
- }, [THREE, orbitPaths, effectiveLayers.orbits, currentView]);
700
- const handleMouseMove = useCallback((event) => {
701
- var _a, _b;
702
- if (!containerRef.current || !raycasterRef.current || !cameraRef.current) return;
703
- const rect = containerRef.current.getBoundingClientRect();
704
- mouseRef.current.set(
705
- (event.clientX - rect.left) / rect.width * 2 - 1,
706
- -((event.clientY - rect.top) / rect.height) * 2 + 1
707
- );
708
- raycasterRef.current.setFromCamera(mouseRef.current, cameraRef.current);
709
- const selectables = [];
710
- (_a = objectsGroupRef.current) == null ? void 0 : _a.traverse((obj) => {
711
- var _a2;
712
- if ((_a2 = obj.userData) == null ? void 0 : _a2.isSelectable) selectables.push(obj);
713
- });
714
- const intersects = raycasterRef.current.intersectObjects(selectables, true);
715
- if (intersects.length > 0) {
716
- let hit = intersects[0].object;
717
- while (hit && !((_b = hit.userData) == null ? void 0 : _b.objectId)) hit = hit.parent;
718
- if (hit == null ? void 0 : hit.userData) {
719
- setHoverTooltip({ visible: true, x: event.clientX - rect.left, y: event.clientY - rect.top, object: hit.userData.data });
720
- if (containerRef.current) containerRef.current.style.cursor = "pointer";
721
- return;
722
- }
723
- }
724
- setHoverTooltip({ visible: false, x: 0, y: 0, object: null });
725
- if (containerRef.current) containerRef.current.style.cursor = "grab";
726
- }, []);
727
- const handleClick = useCallback((event) => {
728
- var _a, _b, _c, _d, _e, _f;
729
- if (!containerRef.current || !raycasterRef.current || !cameraRef.current) return;
730
- const rect = containerRef.current.getBoundingClientRect();
731
- mouseRef.current.set(
732
- (event.clientX - rect.left) / rect.width * 2 - 1,
733
- -((event.clientY - rect.top) / rect.height) * 2 + 1
734
- );
735
- raycasterRef.current.setFromCamera(mouseRef.current, cameraRef.current);
736
- const selectables = [];
737
- (_a = objectsGroupRef.current) == null ? void 0 : _a.traverse((obj) => {
738
- var _a2;
739
- if ((_a2 = obj.userData) == null ? void 0 : _a2.isSelectable) selectables.push(obj);
740
- });
741
- (_b = planetsGroupRef.current) == null ? void 0 : _b.traverse((obj) => {
742
- var _a2;
743
- if ((_a2 = obj.userData) == null ? void 0 : _a2.isSelectable) selectables.push(obj);
744
- });
745
- const intersects = raycasterRef.current.intersectObjects(selectables, true);
746
- if (intersects.length > 0) {
747
- let hit = intersects[0].object;
748
- while (hit && !((_c = hit.userData) == null ? void 0 : _c.objectId)) hit = hit.parent;
749
- if ((_d = hit == null ? void 0 : hit.userData) == null ? void 0 : _d.objectId) {
750
- const objectId = hit.userData.objectId;
751
- setSelection({ selectedId: objectId, selectedObject: hit.userData.data, hoveredId: null });
752
- (_e = callbacks == null ? void 0 : callbacks.onSelect) == null ? void 0 : _e.call(callbacks, hit.userData.data);
753
- flyTo(objectId, { animate: true });
754
- return;
755
- }
756
- }
757
- setSelection({ selectedId: null, selectedObject: null, hoveredId: null });
758
- (_f = callbacks == null ? void 0 : callbacks.onSelect) == null ? void 0 : _f.call(callbacks, null);
759
- }, [callbacks]);
760
- const flyTo = useCallback((objectId, options) => {
761
- var _a, _b, _c, _d, _e, _f;
762
- if (!THREE || !cameraRef.current || !controlsRef.current) return;
763
- const mesh = objectMeshMapRef.current.get(objectId);
764
- if (!mesh) {
765
- return;
766
- }
767
- const targetPos = mesh.position.clone();
768
- const camera = cameraRef.current;
769
- const controls = controlsRef.current;
770
- const category = (_a = mesh.userData) == null ? void 0 : _a.category;
771
- const isGroundStation = category === "groundStation";
772
- let viewDistance;
773
- if (options == null ? void 0 : options.distance) {
774
- viewDistance = options.distance;
775
- } else {
776
- const isPlanet = (_b = mesh.userData) == null ? void 0 : _b.isPlanet;
777
- const targetDist = targetPos.length();
778
- if (isPlanet) {
779
- const planetRadius = ((_d = (_c = mesh.geometry) == null ? void 0 : _c.parameters) == null ? void 0 : _d.radius) || 100;
780
- viewDistance = planetRadius * 5;
781
- } else if (isGroundStation) {
782
- viewDistance = 200;
783
- } else if (category === "station") {
784
- viewDistance = 80;
785
- } else if (category === "satellite") {
786
- viewDistance = 60;
787
- } else if (targetDist > SCENE_EARTH_RADIUS * 2) {
788
- viewDistance = 150;
789
- } else {
790
- viewDistance = 100 * ((options == null ? void 0 : options.zoom) || 1);
791
- }
792
- }
793
- let cameraTargetPos;
794
- if (options == null ? void 0 : options.offset) {
795
- cameraTargetPos = targetPos.clone().add(new THREE.Vector3(options.offset.x, options.offset.y, options.offset.z));
796
- } else if (isGroundStation) {
797
- const upDir = targetPos.clone().normalize();
798
- cameraTargetPos = targetPos.clone().add(upDir.multiplyScalar(viewDistance));
799
- } else {
800
- const dir = targetPos.clone().normalize();
801
- const side = new THREE.Vector3(-dir.z, 0.2, dir.x).normalize();
802
- cameraTargetPos = targetPos.clone().add(side.multiplyScalar(viewDistance));
803
- }
804
- if ((options == null ? void 0 : options.animate) !== false) {
805
- focusTransitionRef.current = {
806
- startTime: Date.now(),
807
- startPos: { x: camera.position.x, y: camera.position.y, z: camera.position.z },
808
- targetPos: { x: cameraTargetPos.x, y: cameraTargetPos.y, z: cameraTargetPos.z },
809
- startTarget: { x: controls.target.x, y: controls.target.y, z: controls.target.z },
810
- targetTarget: { x: targetPos.x, y: targetPos.y, z: targetPos.z },
811
- duration: CAMERA_TRANSITION_DURATION,
812
- isActive: true
813
- };
814
- } else {
815
- camera.position.copy(cameraTargetPos);
816
- controls.target.copy(targetPos);
817
- controls.update();
818
- }
819
- (_f = callbacks == null ? void 0 : callbacks.onFocus) == null ? void 0 : _f.call(callbacks, ((_e = mesh.userData) == null ? void 0 : _e.data) || { id: objectId });
820
- }, [THREE, callbacks]);
821
- const focusOn = flyTo;
822
- const animate = useCallback(() => {
823
- var _a;
824
- if (!isAnimatingRef.current) return;
825
- if (!rendererRef.current || !sceneRef.current || !cameraRef.current) return;
826
- const camera = cameraRef.current;
827
- const controls = controlsRef.current;
828
- if ((_a = focusTransitionRef.current) == null ? void 0 : _a.isActive) {
829
- const t = focusTransitionRef.current;
830
- const progress = Math.min((Date.now() - t.startTime) / t.duration, 1);
831
- const e = easeOutCubic(progress);
832
- camera.position.x = t.startPos.x + (t.targetPos.x - t.startPos.x) * e;
833
- camera.position.y = t.startPos.y + (t.targetPos.y - t.startPos.y) * e;
834
- camera.position.z = t.startPos.z + (t.targetPos.z - t.startPos.z) * e;
835
- controls.target.x = t.startTarget.x + (t.targetTarget.x - t.startTarget.x) * e;
836
- controls.target.y = t.startTarget.y + (t.targetTarget.y - t.startTarget.y) * e;
837
- controls.target.z = t.startTarget.z + (t.targetTarget.z - t.startTarget.z) * e;
838
- if (progress >= 1) t.isActive = false;
839
- }
840
- if (controls) controls.update();
841
- timeRef.current += 0.016;
842
- if (starsMaterialRef.current) starsMaterialRef.current.uniforms.time.value = timeRef.current;
843
- if (linksGroupRef.current) {
844
- linksGroupRef.current.children.forEach((child) => {
845
- var _a2;
846
- if (((_a2 = child.userData) == null ? void 0 : _a2.isAnimatedLink) && child.material) {
847
- child.material.dashOffset -= 1.5;
848
- }
849
- });
850
- }
851
- rendererRef.current.render(sceneRef.current, camera);
852
- animationRef.current = requestAnimationFrame(animate);
853
- }, []);
854
- useImperativeHandle(ref, () => ({
855
- focusOn,
856
- flyTo,
857
- setView: (mode) => setCurrentView(mode),
858
- setTime: () => {
859
- },
860
- setTimeScale: () => {
861
- },
862
- togglePlay: () => {
863
- },
864
- setLayerVisibility: (layer, visible) => setLayers((prev) => ({ ...prev, [layer]: visible })),
865
- getCameraState: () => {
866
- var _a, _b, _c;
867
- if (!cameraRef.current) return { position: { x: 0, y: 0, z: 1e4 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 1, z: 0 }, fov: 45, near: 0.1, far: 1e5 };
868
- const c = cameraRef.current;
869
- return { position: { x: c.position.x, y: c.position.y, z: c.position.z }, target: { x: ((_a = controlsRef.current) == null ? void 0 : _a.target.x) || 0, y: ((_b = controlsRef.current) == null ? void 0 : _b.target.y) || 0, z: ((_c = controlsRef.current) == null ? void 0 : _c.target.z) || 0 }, up: { x: c.up.x, y: c.up.y, z: c.up.z }, fov: c.fov, near: c.near, far: c.far };
870
- },
871
- setCameraState: (state) => {
872
- if (!cameraRef.current) return;
873
- if (state.position) cameraRef.current.position.set(state.position.x, state.position.y, state.position.z);
874
- if (state.target && controlsRef.current) controlsRef.current.target.set(state.target.x, state.target.y, state.target.z);
875
- },
876
- exportImage: (format = "png") => {
877
- var _a;
878
- return ((_a = rendererRef.current) == null ? void 0 : _a.domElement.toDataURL(`image/${format}`)) || "";
879
- },
880
- exportScene: () => new Blob(),
881
- getVisibleObjects: () => [...satellites, ...debris, ...stations, ...customObjects],
882
- findObjects: (q) => [...satellites, ...debris, ...stations, ...customObjects].filter((obj) => obj.name.toLowerCase().includes(q.toLowerCase()) || obj.id.toLowerCase().includes(q.toLowerCase())),
883
- addObject: () => {
884
- },
885
- removeObject: () => {
886
- },
887
- updateObject: () => {
888
- },
889
- measureDistance: (_from, _to) => 0
890
- }), [satellites, debris, stations, customObjects, focusOn]);
891
- useEffect(() => {
892
- if (THREE && OrbitControlsClass) return initScene();
893
- }, [THREE, OrbitControlsClass, initScene]);
894
- useEffect(() => {
895
- if (!isLoading && THREE) {
896
- if (animationRef.current) {
897
- cancelAnimationFrame(animationRef.current);
898
- animationRef.current = null;
899
- }
900
- isAnimatingRef.current = true;
901
- animate();
902
- return () => {
903
- isAnimatingRef.current = false;
904
- if (animationRef.current) {
905
- cancelAnimationFrame(animationRef.current);
906
- animationRef.current = null;
907
- }
908
- };
909
- }
910
- }, [isLoading, THREE, animate]);
911
- useEffect(() => {
912
- if (THREE && !isLoading) {
913
- updateObjects();
914
- updateGroundStations();
915
- updateCoverage();
916
- updateOrbits();
917
- updateLinks();
918
- }
919
- }, [THREE, isLoading, updateObjects, updateGroundStations, updateCoverage, updateOrbits, updateLinks]);
920
- useEffect(() => {
921
- const container = containerRef.current;
922
- if (!container || isLoading) return;
923
- container.addEventListener("mousemove", handleMouseMove);
924
- container.addEventListener("click", handleClick);
925
- return () => {
926
- container.removeEventListener("mousemove", handleMouseMove);
927
- container.removeEventListener("click", handleClick);
928
- };
929
- }, [isLoading, handleMouseMove, handleClick]);
930
- useEffect(() => {
931
- const handleResize = () => {
932
- if (!containerRef.current || !rendererRef.current || !cameraRef.current) return;
933
- const rect = containerRef.current.getBoundingClientRect();
934
- const h = typeof height === "number" ? height : rect.height;
935
- cameraRef.current.aspect = rect.width / h;
936
- cameraRef.current.updateProjectionMatrix();
937
- rendererRef.current.setSize(rect.width, h);
938
- };
939
- window.addEventListener("resize", handleResize);
940
- return () => window.removeEventListener("resize", handleResize);
941
- }, [height]);
942
- useEffect(() => {
943
- if (focusedObjectId) focusOn(focusedObjectId, { animate: true });
944
- }, [focusedObjectId, focusOn]);
945
- useEffect(() => {
946
- if (THREE && atmosphereMaterialRef.current && sunDirection) {
947
- const dir = new THREE.Vector3(sunDirection.x, sunDirection.y, sunDirection.z).normalize();
948
- atmosphereMaterialRef.current.uniforms.uSunDirection.value = dir;
949
- }
950
- }, [THREE, sunDirection]);
951
- useEffect(() => {
952
- if (!THREE || !planetPositions || !planetsGroupRef.current) return;
953
- planetsGroupRef.current.traverse((child) => {
954
- var _a;
955
- if (((_a = child.userData) == null ? void 0 : _a.planetId) && planetPositions[child.userData.planetId]) {
956
- const pos = planetPositions[child.userData.planetId];
957
- child.position.set(pos.x, pos.y, pos.z);
958
- }
959
- });
960
- }, [THREE, planetPositions]);
961
- if (!webglCapabilities.available) {
962
- return /* @__PURE__ */ jsxs("div", { className: `zenspace3d-fallback ${className}`, style: { width, height: typeof height === "number" ? height : 600, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", backgroundColor: "#030508", borderRadius: 8, fontFamily: "ui-monospace, monospace", ...style }, children: [
963
- /* @__PURE__ */ jsx("div", { style: { marginBottom: 16 }, children: /* @__PURE__ */ jsx(AstroIcon, { name: "public", size: "large", label: "Globe", style: { color: "#22d3ee", opacity: 0.5 } }) }),
964
- /* @__PURE__ */ jsx("div", { style: { color: "#b8bcc8", fontSize: 14 }, children: "WebGL is not available" })
965
- ] });
966
- }
967
- if (error) return /* @__PURE__ */ jsx("div", { style: { width, height, display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "#0a0a0f", color: "#ff4444" }, children: /* @__PURE__ */ jsxs("p", { children: [
968
- "Error: ",
969
- error
970
- ] }) });
971
- return /* @__PURE__ */ jsxs("div", { className: `zenspace3d ${className}`, style: { width, height: typeof height === "number" ? height : "100%", position: "relative", backgroundColor: "#000", borderRadius: 8, overflow: "hidden", cursor: "grab", ...style }, children: [
972
- /* @__PURE__ */ jsx("div", { ref: containerRef, style: { width: "100%", height: "100%" } }),
973
- isLoading && /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "rgba(0,0,0,0.9)" }, children: /* @__PURE__ */ jsx("div", { style: { color: "#22d3ee", fontSize: 14 }, children: "Loading..." }) }),
974
- showTools && !isLoading && /* @__PURE__ */ jsxs("div", { style: { position: "absolute", top: 12, left: 12, display: "flex", flexDirection: "column", gap: 4 }, children: [
975
- /* @__PURE__ */ jsx(LayerBtn, { icon: "satellite", label: "Satellites", active: layers.satellites, onClick: () => setLayers((l) => ({ ...l, satellites: !l.satellites })) }),
976
- /* @__PURE__ */ jsx(LayerBtn, { icon: "home", label: "Stations", active: layers.stations, onClick: () => setLayers((l) => ({ ...l, stations: !l.stations })) }),
977
- /* @__PURE__ */ jsx(LayerBtn, { icon: "antenna", label: "Ground", active: layers.groundStations, onClick: () => setLayers((l) => ({ ...l, groundStations: !l.groundStations })) }),
978
- /* @__PURE__ */ jsx(LayerBtn, { icon: "timeline", label: "Orbits", active: layers.orbits, onClick: () => setLayers((l) => ({ ...l, orbits: !l.orbits })) }),
979
- /* @__PURE__ */ jsx(LayerBtn, { icon: "signal-cellular-4-bar", label: "Coverage", active: layers.coverage, onClick: () => setLayers((l) => ({ ...l, coverage: !l.coverage })) })
980
- ] }),
981
- !isLoading && /* @__PURE__ */ jsx(
982
- ObjectLegend,
983
- {
984
- currentView,
985
- satellites,
986
- stations,
987
- groundStations,
988
- onObjectClick: (id, objectData) => {
989
- var _a;
990
- setSelection({ selectedId: id, selectedObject: objectData, hoveredId: null });
991
- (_a = callbacks == null ? void 0 : callbacks.onSelect) == null ? void 0 : _a.call(callbacks, objectData);
992
- flyTo(id, { animate: true });
993
- }
994
- }
995
- ),
996
- hoverTooltip.visible && hoverTooltip.object && /* @__PURE__ */ jsxs("div", { style: {
997
- position: "absolute",
998
- left: hoverTooltip.x + 15,
999
- top: hoverTooltip.y - 10,
1000
- backgroundColor: "rgba(20, 25, 35, 0.95)",
1001
- color: "#e4e4e7",
1002
- padding: "10px 14px",
1003
- borderRadius: 4,
1004
- fontSize: 12,
1005
- fontFamily: "system-ui, -apple-system, sans-serif",
1006
- boxShadow: "0 4px 20px rgba(0,0,0,0.5)",
1007
- pointerEvents: "none",
1008
- zIndex: 100,
1009
- minWidth: 140
1010
- }, children: [
1011
- /* @__PURE__ */ jsx("div", { style: { color: "#ffffff", fontWeight: 500, marginBottom: 4 }, children: hoverTooltip.object.name || hoverTooltip.object.targetName || "Unknown" }),
1012
- hoverTooltip.object.category && /* @__PURE__ */ jsx("div", { style: { color: "#9ca3af", fontSize: 11 }, children: hoverTooltip.object.category }),
1013
- hoverTooltip.object.overpassTime && /* @__PURE__ */ jsxs("div", { style: { color: "#9ca3af", fontSize: 11, marginTop: 4 }, children: [
1014
- "Overpass time: ",
1015
- hoverTooltip.object.overpassTime
1016
- ] })
1017
- ] }),
1018
- showInfoPanel && selection.selectedObject && /* @__PURE__ */ jsxs("div", { style: { position: "absolute", bottom: 16, right: 12, backgroundColor: "rgba(0,0,0,0.9)", color: "#e4e4e7", padding: "14px 18px", borderRadius: 10, fontSize: 12, fontFamily: "ui-monospace, monospace", border: "1px solid rgba(34,211,238,0.3)", minWidth: 180 }, children: [
1019
- /* @__PURE__ */ jsx("div", { style: { color: "#22d3ee", fontWeight: 500, marginBottom: 8 }, children: selection.selectedObject.name }),
1020
- /* @__PURE__ */ jsx("div", { style: { color: "#b8bcc8", fontSize: 10 }, children: selection.selectedObject.category }),
1021
- /* @__PURE__ */ jsx("button", { onClick: () => setSelection({ selectedId: null, selectedObject: null, hoveredId: null }), style: { marginTop: 10, padding: "5px 10px", backgroundColor: "rgba(255,255,255,0.1)", border: "none", borderRadius: 4, color: "#a1a1aa", cursor: "pointer", fontSize: 10, width: "100%" }, children: "Close" })
1022
- ] }),
1023
- !isLoading && /* @__PURE__ */ jsx("div", { style: { position: "absolute", bottom: 12, left: 12, backgroundColor: "rgba(0,0,0,0.5)", color: "#b8bcc8", padding: "5px 10px", borderRadius: 6, fontSize: 10 }, children: "Drag to rotate • Scroll to zoom" })
1024
- ] });
1025
- }
1026
- );
164
+ var ZenSpace3D = forwardRef(function ZenSpace3D(props, ref) {
165
+ const [cesiumModule, setCesiumModule] = useState(null);
166
+ useEffect(() => {
167
+ import("cesium").then((m) => setCesiumModule(m)).catch(() => setCesiumModule(null));
168
+ }, []);
169
+ if (cesiumModule) return /* @__PURE__ */ jsx(ZenSpace3DCesium, {
170
+ ref,
171
+ cesium: cesiumModule,
172
+ ...props
173
+ });
174
+ return /* @__PURE__ */ jsx(ZenSpace3DThree, {
175
+ ref,
176
+ ...props
177
+ });
178
+ });
179
+ var ZenSpace3DThree = forwardRef(function ZenSpace3DThree(props, ref) {
180
+ const { satellites = [], debris = [], stations = [], groundStations = [], customObjects = [], view = "earth-orbit", focusedObjectId, initialCamera, sunDirection, planetPositions, visibilityCones = [], satelliteCoverages = [], overpasses: _overpasses = [], orbitPaths = [], orbitDesignArcs: _orbitDesignArcs = [], communicationLinks = [], showAxisHelper = false, layers: layerOverrides, showTools = true, showInfoPanel = true, width = "100%", height = 600, callbacks, maxObjects = 1e4, className = "", style } = props;
181
+ const { tokens: _tokens } = useTheme();
182
+ const [webglCapabilities] = useState(() => checkWebGLCapabilities());
183
+ const containerRef = useRef(null);
184
+ const rendererRef = useRef(null);
185
+ const sceneRef = useRef(null);
186
+ const cameraRef = useRef(null);
187
+ const controlsRef = useRef(null);
188
+ const animationRef = useRef(null);
189
+ const raycasterRef = useRef(null);
190
+ const mouseRef = useRef(null);
191
+ const earthRef = useRef(null);
192
+ const starsMaterialRef = useRef(null);
193
+ const atmosphereMaterialRef = useRef(null);
194
+ const objectsGroupRef = useRef(null);
195
+ const linksGroupRef = useRef(null);
196
+ const conesGroupRef = useRef(null);
197
+ const orbitsGroupRef = useRef(null);
198
+ const planetsGroupRef = useRef(null);
199
+ const timeRef = useRef(0);
200
+ const focusTransitionRef = useRef(null);
201
+ const isUserInteractingRef = useRef(false);
202
+ const objectMeshMapRef = useRef(/* @__PURE__ */ new Map());
203
+ const isAnimatingRef = useRef(false);
204
+ useRef(false);
205
+ const [THREE, setTHREE] = useState(null);
206
+ const threeRef = useRef(null);
207
+ threeRef.current = THREE;
208
+ const [OrbitControlsClass, setOrbitControlsClass] = useState(null);
209
+ const [isLoading, setIsLoading] = useState(true);
210
+ const [error, setError] = useState(null);
211
+ const [layers, setLayers] = useState({
212
+ ...DEFAULT_LAYERS,
213
+ ...layerOverrides
214
+ });
215
+ const [selection, setSelection] = useState({
216
+ selectedId: null,
217
+ selectedObject: null,
218
+ hoveredId: null
219
+ });
220
+ const [hoverTooltip, setHoverTooltip] = useState({
221
+ visible: false,
222
+ x: 0,
223
+ y: 0,
224
+ object: null
225
+ });
226
+ const [currentView, setCurrentView] = useState(view);
227
+ const currentViewRef = useRef(view);
228
+ const effectiveLayers = useMemo(() => ({
229
+ ...layers,
230
+ ...layerOverrides
231
+ }), [layers, layerOverrides]);
232
+ useEffect(() => {
233
+ setCurrentView(view);
234
+ currentViewRef.current = view;
235
+ }, [view]);
236
+ useEffect(() => {
237
+ if (!webglCapabilities.available) {
238
+ setIsLoading(false);
239
+ return;
240
+ }
241
+ loadThree().then(({ THREE: threeModule, OrbitControls: controls }) => {
242
+ setTHREE(threeModule);
243
+ setOrbitControlsClass(() => controls);
244
+ }).catch((err) => {
245
+ setError("Failed to load 3D library");
246
+ setIsLoading(false);
247
+ });
248
+ }, [webglCapabilities.available]);
249
+ const initScene = useCallback(() => {
250
+ if (!THREE || !containerRef.current || !OrbitControlsClass) return;
251
+ if (rendererRef.current) {
252
+ isAnimatingRef.current = false;
253
+ if (animationRef.current) {
254
+ cancelAnimationFrame(animationRef.current);
255
+ animationRef.current = null;
256
+ }
257
+ controlsRef.current?.dispose();
258
+ rendererRef.current.dispose();
259
+ if (containerRef.current?.contains(rendererRef.current.domElement)) containerRef.current.removeChild(rendererRef.current.domElement);
260
+ }
261
+ const container = containerRef.current;
262
+ const rect = container.getBoundingClientRect();
263
+ const w = rect.width;
264
+ const h = typeof height === "number" ? height : rect.height;
265
+ const scene = new THREE.Scene();
266
+ sceneRef.current = scene;
267
+ const viewMode = currentViewRef.current;
268
+ const farClip = viewMode === "solar-system" ? 1e6 : 1e5;
269
+ const camera = new THREE.PerspectiveCamera(45, w / h, 1, farClip);
270
+ const initialDistance = viewMode === "solar-system" ? 5e3 : 1e4;
271
+ camera.position.set(0, initialDistance * .3, initialDistance);
272
+ cameraRef.current = camera;
273
+ if (initialCamera?.position) camera.position.set(initialCamera.position.x, initialCamera.position.y, initialCamera.position.z);
274
+ const renderer = new THREE.WebGLRenderer({
275
+ antialias: true,
276
+ alpha: true,
277
+ powerPreference: "high-performance",
278
+ preserveDrawingBuffer: true
279
+ });
280
+ renderer.setSize(w, h);
281
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
282
+ container.appendChild(renderer.domElement);
283
+ rendererRef.current = renderer;
284
+ const controls = new OrbitControlsClass(camera, renderer.domElement);
285
+ controls.enableDamping = true;
286
+ controls.dampingFactor = .05;
287
+ controls.rotateSpeed = .5;
288
+ controls.minDistance = viewMode === "solar-system" ? 500 : 3050;
289
+ controls.maxDistance = viewMode === "solar-system" ? 1e5 : 25e3;
290
+ controls.enablePan = viewMode === "solar-system";
291
+ let interactionTimer;
292
+ const handleControlStart = () => {
293
+ isUserInteractingRef.current = true;
294
+ };
295
+ const handleControlEnd = () => {
296
+ clearTimeout(interactionTimer);
297
+ interactionTimer = setTimeout(() => {
298
+ isUserInteractingRef.current = false;
299
+ }, 3e3);
300
+ };
301
+ controls.addEventListener("start", handleControlStart);
302
+ controls.addEventListener("end", handleControlEnd);
303
+ controlsRef.current = controls;
304
+ raycasterRef.current = new THREE.Raycaster();
305
+ mouseRef.current = new THREE.Vector2();
306
+ objectsGroupRef.current = new THREE.Group();
307
+ objectsGroupRef.current.name = "objects";
308
+ scene.add(objectsGroupRef.current);
309
+ linksGroupRef.current = new THREE.Group();
310
+ linksGroupRef.current.name = "links";
311
+ scene.add(linksGroupRef.current);
312
+ conesGroupRef.current = new THREE.Group();
313
+ conesGroupRef.current.name = "cones";
314
+ scene.add(conesGroupRef.current);
315
+ orbitsGroupRef.current = new THREE.Group();
316
+ orbitsGroupRef.current.name = "orbits";
317
+ scene.add(orbitsGroupRef.current);
318
+ planetsGroupRef.current = new THREE.Group();
319
+ planetsGroupRef.current.name = "planets";
320
+ scene.add(planetsGroupRef.current);
321
+ if (viewMode === "solar-system") createSolarSystem(THREE, scene);
322
+ else {
323
+ createEarth(THREE, scene);
324
+ createAtmosphere(THREE, scene);
325
+ }
326
+ createStars(THREE, scene);
327
+ createLighting(THREE, scene);
328
+ if (showAxisHelper && viewMode !== "solar-system") createAxisHelper(THREE, scene);
329
+ setIsLoading(false);
330
+ return () => {
331
+ clearTimeout(interactionTimer);
332
+ if (animationRef.current) cancelAnimationFrame(animationRef.current);
333
+ controls.removeEventListener("start", handleControlStart);
334
+ controls.removeEventListener("end", handleControlEnd);
335
+ controls.dispose();
336
+ renderer.dispose();
337
+ if (container.contains(renderer.domElement)) container.removeChild(renderer.domElement);
338
+ };
339
+ }, [
340
+ THREE,
341
+ OrbitControlsClass,
342
+ height,
343
+ initialCamera,
344
+ showAxisHelper
345
+ ]);
346
+ const createSolarSystem = useCallback((THREE, _scene) => {
347
+ const planetsGroup = planetsGroupRef.current;
348
+ if (!planetsGroup) return;
349
+ const DISTANCE_SCALE = 5e-6;
350
+ const SIZE_SCALE = .01;
351
+ const sunRadius = 696340 * SIZE_SCALE;
352
+ const sunGeo = new THREE.SphereGeometry(sunRadius, 32, 24);
353
+ const sunMat = new THREE.MeshBasicMaterial({ color: 16768324 });
354
+ const sunMesh = new THREE.Mesh(sunGeo, sunMat);
355
+ sunMesh.name = "sun";
356
+ sunMesh.userData = {
357
+ objectId: "sun",
358
+ planetId: "sun",
359
+ isPlanet: true,
360
+ isSelectable: true
361
+ };
362
+ planetsGroup.add(sunMesh);
363
+ const glowGeo = new THREE.SphereGeometry(sunRadius * 1.3, 24, 16);
364
+ const glowMat = new THREE.MeshBasicMaterial({
365
+ color: 16755200,
366
+ transparent: true,
367
+ opacity: .2
368
+ });
369
+ planetsGroup.add(new THREE.Mesh(glowGeo, glowMat));
370
+ [
371
+ {
372
+ id: "mercury",
373
+ color: 9205843,
374
+ radius: 2439.7,
375
+ distance: 579e5
376
+ },
377
+ {
378
+ id: "venus",
379
+ color: 16762441,
380
+ radius: 6051.8,
381
+ distance: 1082e5
382
+ },
383
+ {
384
+ id: "earth",
385
+ color: 7050198,
386
+ radius: 6371,
387
+ distance: 1496e5
388
+ },
389
+ {
390
+ id: "mars",
391
+ color: 12665870,
392
+ radius: 3389.5,
393
+ distance: 2279e5
394
+ },
395
+ {
396
+ id: "jupiter",
397
+ color: 14207645,
398
+ radius: 69911,
399
+ distance: 7785e5
400
+ },
401
+ {
402
+ id: "saturn",
403
+ color: 16045470,
404
+ radius: 58232,
405
+ distance: 1434e6
406
+ },
407
+ {
408
+ id: "uranus",
409
+ color: 13756391,
410
+ radius: 25362,
411
+ distance: 2871e6
412
+ },
413
+ {
414
+ id: "neptune",
415
+ color: 5987807,
416
+ radius: 24622,
417
+ distance: 4495e6
418
+ }
419
+ ].forEach((c) => {
420
+ const visualScale = c.distance < 3e8 ? 15 : 5;
421
+ const planetRadius = c.radius * SIZE_SCALE * visualScale;
422
+ const geo = new THREE.SphereGeometry(planetRadius, 24, 16);
423
+ const mat = new THREE.MeshPhongMaterial({
424
+ color: c.color,
425
+ shininess: 30
426
+ });
427
+ const mesh = new THREE.Mesh(geo, mat);
428
+ const orbitDist = c.distance * DISTANCE_SCALE;
429
+ mesh.position.x = orbitDist;
430
+ mesh.name = c.id;
431
+ mesh.userData = {
432
+ objectId: c.id,
433
+ planetId: c.id,
434
+ isPlanet: true,
435
+ isSelectable: true,
436
+ data: {
437
+ id: c.id,
438
+ name: c.id.charAt(0).toUpperCase() + c.id.slice(1),
439
+ category: "planet"
440
+ }
441
+ };
442
+ planetsGroup.add(mesh);
443
+ objectMeshMapRef.current.set(c.id, mesh);
444
+ if (c.id === "saturn") {
445
+ const ringGeo = new THREE.RingGeometry(planetRadius * 1.4, planetRadius * 2.2, 32);
446
+ const ringMat = new THREE.MeshBasicMaterial({
447
+ color: 13219990,
448
+ transparent: true,
449
+ opacity: .6,
450
+ side: THREE.DoubleSide
451
+ });
452
+ const ring = new THREE.Mesh(ringGeo, ringMat);
453
+ ring.rotation.x = Math.PI / 2.5;
454
+ ring.position.copy(mesh.position);
455
+ planetsGroup.add(ring);
456
+ }
457
+ const points = [];
458
+ for (let i = 0; i <= 64; i++) {
459
+ const angle = i / 64 * Math.PI * 2;
460
+ points.push(new THREE.Vector3(orbitDist * Math.cos(angle), 0, orbitDist * Math.sin(angle)));
461
+ }
462
+ const orbitGeo = new THREE.BufferGeometry().setFromPoints(points);
463
+ const orbitMat = new THREE.LineBasicMaterial({
464
+ color: 4473958,
465
+ transparent: true,
466
+ opacity: .3
467
+ });
468
+ planetsGroup.add(new THREE.Line(orbitGeo, orbitMat));
469
+ });
470
+ const sunLight = new THREE.PointLight(16777198, 2, 1e5);
471
+ sunLight.position.set(0, 0, 0);
472
+ planetsGroup.add(sunLight);
473
+ }, []);
474
+ const createEarth = useCallback((THREE, scene) => {
475
+ const earthGeometry = new THREE.SphereGeometry(SCENE_EARTH_RADIUS, EARTH_SEGMENTS, EARTH_SEGMENTS_V);
476
+ const earthMaterial = new THREE.MeshPhongMaterial({
477
+ color: 2245802,
478
+ shininess: 10
479
+ });
480
+ new THREE.TextureLoader().load("/world.topo.jpg", (texture) => {
481
+ texture.wrapS = THREE.RepeatWrapping;
482
+ texture.wrapT = THREE.ClampToEdgeWrapping;
483
+ texture.minFilter = THREE.LinearFilter;
484
+ texture.magFilter = THREE.LinearFilter;
485
+ earthMaterial.map = texture;
486
+ earthMaterial.needsUpdate = true;
487
+ }, void 0, () => {});
488
+ const earth = new THREE.Mesh(earthGeometry, earthMaterial);
489
+ earth.rotation.y = -Math.PI / 2 + Math.PI;
490
+ earth.name = "earth";
491
+ scene.add(earth);
492
+ earthRef.current = earth;
493
+ }, []);
494
+ const createAtmosphere = useCallback((THREE, scene) => {
495
+ if (!effectiveLayers.atmosphere) return;
496
+ const defaultSunDir = new THREE.Vector3(1, .3, .5).normalize();
497
+ const atmosphereGeometry = new THREE.SphereGeometry(SCENE_EARTH_RADIUS * 1.15, 32, 16);
498
+ const atmosphereMaterial = new THREE.ShaderMaterial({
499
+ uniforms: { uSunDirection: { value: defaultSunDir } },
500
+ vertexShader: atmosphere_default,
501
+ fragmentShader: atmosphere_default$1,
502
+ blending: THREE.AdditiveBlending,
503
+ side: THREE.BackSide,
504
+ transparent: true,
505
+ depthWrite: false
506
+ });
507
+ const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
508
+ atmosphere.rotation.y = -Math.PI / 2;
509
+ atmosphere.name = "atmosphere";
510
+ scene.add(atmosphere);
511
+ atmosphereMaterialRef.current = atmosphereMaterial;
512
+ }, [effectiveLayers.atmosphere]);
513
+ const createStars = useCallback((THREE, scene) => {
514
+ const starsGeometry = new THREE.BufferGeometry();
515
+ const starsDist = currentView === "solar-system" ? 5e10 : 5e4;
516
+ const positions = new Float32Array(STARS_COUNT * 3);
517
+ const opacities = new Float32Array(STARS_COUNT);
518
+ for (let i = 0; i < STARS_COUNT; i++) {
519
+ const radius = starsDist + Math.random() * starsDist;
520
+ const theta = Math.random() * Math.PI * 2;
521
+ const phi = Math.acos(2 * Math.random() - 1);
522
+ positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
523
+ positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
524
+ positions[i * 3 + 2] = radius * Math.cos(phi);
525
+ opacities[i] = Math.random();
526
+ }
527
+ starsGeometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
528
+ starsGeometry.setAttribute("opacity", new THREE.BufferAttribute(opacities, 1));
529
+ const starsMaterial = new THREE.ShaderMaterial({
530
+ uniforms: { time: { value: 0 } },
531
+ vertexShader: stars_default,
532
+ fragmentShader: stars_default$1,
533
+ transparent: true,
534
+ depthWrite: false,
535
+ blending: THREE.AdditiveBlending
536
+ });
537
+ scene.add(new THREE.Points(starsGeometry, starsMaterial));
538
+ starsMaterialRef.current = starsMaterial;
539
+ }, [currentView]);
540
+ const createLighting = useCallback((THREE, scene) => {
541
+ scene.add(new THREE.AmbientLight(4210752, .6));
542
+ const sunLight = new THREE.DirectionalLight(16777215, 1.2);
543
+ sunLight.position.set(1e4, 5e3, 1e4);
544
+ scene.add(sunLight);
545
+ }, []);
546
+ const createAxisHelper = useCallback((THREE, scene) => {
547
+ const len = SCENE_EARTH_RADIUS * 1.5;
548
+ const group = new THREE.Group();
549
+ [
550
+ {
551
+ color: 16724787,
552
+ dir: [
553
+ 1,
554
+ 0,
555
+ 0
556
+ ]
557
+ },
558
+ {
559
+ color: 3407667,
560
+ dir: [
561
+ 0,
562
+ 1,
563
+ 0
564
+ ]
565
+ },
566
+ {
567
+ color: 3355647,
568
+ dir: [
569
+ 0,
570
+ 0,
571
+ 1
572
+ ]
573
+ }
574
+ ].forEach(({ color, dir }) => {
575
+ const points = [new THREE.Vector3(0, 0, 0), new THREE.Vector3(dir[0] * len, dir[1] * len, dir[2] * len)];
576
+ const geo = new THREE.BufferGeometry().setFromPoints(points);
577
+ const mat = new THREE.LineBasicMaterial({ color });
578
+ group.add(new THREE.Line(geo, mat));
579
+ });
580
+ scene.add(group);
581
+ }, []);
582
+ const updateObjects = useCallback(() => {
583
+ if (!THREE || !objectsGroupRef.current || currentView === "solar-system") return;
584
+ const group = objectsGroupRef.current;
585
+ const meshMap = objectMeshMapRef.current;
586
+ while (group.children.length > 0) {
587
+ const child = group.children[0];
588
+ group.remove(child);
589
+ }
590
+ meshMap.clear();
591
+ [
592
+ ...satellites,
593
+ ...debris,
594
+ ...stations,
595
+ ...customObjects
596
+ ].slice(0, maxObjects).forEach((obj) => {
597
+ const categoryLayer = obj.category === "satellite" ? "satellites" : obj.category === "debris" ? "debris" : obj.category === "station" ? "stations" : "satellites";
598
+ if (!effectiveLayers[categoryLayer]) return;
599
+ let size = 50;
600
+ if (obj.category === "station") size = 100;
601
+ else if (obj.category === "debris") size = 25;
602
+ const satelliteGroup = createSatelliteShape(THREE, obj.category, size);
603
+ satelliteGroup.position.set(obj.position.x, obj.position.y, obj.position.z);
604
+ satelliteGroup.lookAt(0, 0, 0);
605
+ satelliteGroup.userData = {
606
+ objectId: obj.id,
607
+ category: obj.category,
608
+ data: obj,
609
+ isSelectable: true
610
+ };
611
+ satelliteGroup.name = obj.id;
612
+ group.add(satelliteGroup);
613
+ meshMap.set(obj.id, satelliteGroup);
614
+ });
615
+ }, [
616
+ THREE,
617
+ satellites,
618
+ debris,
619
+ stations,
620
+ customObjects,
621
+ maxObjects,
622
+ effectiveLayers,
623
+ currentView
624
+ ]);
625
+ const updateGroundStations = useCallback(() => {
626
+ if (!THREE || !objectsGroupRef.current || currentView === "solar-system") return;
627
+ if (!effectiveLayers.groundStations) return;
628
+ groundStations.forEach((gs) => {
629
+ const pos = latLonAltToCartesian(gs.latitude, gs.longitude, 0);
630
+ const geo = new THREE.SphereGeometry(35, 8, 6);
631
+ const mat = new THREE.MeshBasicMaterial({ color: 65535 });
632
+ const marker = new THREE.Mesh(geo, mat);
633
+ marker.position.set(pos.x, pos.y, pos.z);
634
+ marker.userData = {
635
+ objectId: gs.id,
636
+ category: "groundStation",
637
+ data: gs,
638
+ isSelectable: true
639
+ };
640
+ objectsGroupRef.current.add(marker);
641
+ objectMeshMapRef.current.set(gs.id, marker);
642
+ const ringGeo = new THREE.RingGeometry(45, 65, 16);
643
+ const ringMat = new THREE.MeshBasicMaterial({
644
+ color: 65535,
645
+ transparent: true,
646
+ opacity: .4,
647
+ side: THREE.DoubleSide
648
+ });
649
+ const ring = new THREE.Mesh(ringGeo, ringMat);
650
+ ring.position.set(pos.x, pos.y, pos.z);
651
+ ring.lookAt(0, 0, 0);
652
+ objectsGroupRef.current.add(ring);
653
+ });
654
+ }, [
655
+ THREE,
656
+ groundStations,
657
+ effectiveLayers.groundStations,
658
+ currentView
659
+ ]);
660
+ const updateCoverage = useCallback(() => {
661
+ const T = threeRef.current;
662
+ if (!T || !conesGroupRef.current || currentView === "solar-system") return;
663
+ while (conesGroupRef.current.children.length > 0) conesGroupRef.current.remove(conesGroupRef.current.children[0]);
664
+ if (!effectiveLayers.coverage) return;
665
+ satelliteCoverages.forEach((coverage) => {
666
+ let satPos;
667
+ if (coverage.satellitePosition) satPos = new T.Vector3(coverage.satellitePosition.x, coverage.satellitePosition.y, coverage.satellitePosition.z);
668
+ else {
669
+ const satMesh = objectMeshMapRef.current.get(coverage.satelliteId);
670
+ if (!satMesh) return;
671
+ satPos = satMesh.position.clone();
672
+ }
673
+ const satDistance = satPos.length();
674
+ const coneColor = new T.Color(coverage.color || "#22d3ee");
675
+ const highlightColor = new T.Color(coverage.color || "#22d3ee").multiplyScalar(1.3);
676
+ if (coverage.footprintPolygon && coverage.footprintPolygon.length >= 3 && coverage.showFootprint !== false) {
677
+ const cornerPoints = coverage.footprintPolygon.map((pt) => latLonAltToCartesian(pt.latitude, pt.longitude, 0));
678
+ const borderPts = subdivideSphericalPolygon(cornerPoints, SPHERICAL_SUBDIVISIONS, SCENE_EARTH_RADIUS * 1.003).map((pt) => new T.Vector3(pt.x, pt.y, pt.z));
679
+ borderPts.push(borderPts[0].clone());
680
+ const borderGeo = new T.BufferGeometry().setFromPoints(borderPts);
681
+ const borderMat = new T.LineBasicMaterial({
682
+ color: highlightColor,
683
+ transparent: true,
684
+ opacity: .95,
685
+ linewidth: 2
686
+ });
687
+ conesGroupRef.current.add(new T.Line(borderGeo, borderMat));
688
+ const glowPts = subdivideSphericalPolygon(cornerPoints, SPHERICAL_SUBDIVISIONS, SCENE_EARTH_RADIUS * 1.005).map((pt) => new T.Vector3(pt.x, pt.y, pt.z));
689
+ glowPts.push(glowPts[0].clone());
690
+ const glowGeo = new T.BufferGeometry().setFromPoints(glowPts);
691
+ const glowMat = new T.LineBasicMaterial({
692
+ color: coneColor,
693
+ transparent: true,
694
+ opacity: .4
695
+ });
696
+ conesGroupRef.current.add(new T.Line(glowGeo, glowMat));
697
+ if (coverage.showCone !== false) for (const corner of cornerPoints) {
698
+ const edgePt = new T.Vector3(corner.x, corner.y, corner.z).normalize().multiplyScalar(SCENE_EARTH_RADIUS);
699
+ const lineGeo = new T.BufferGeometry().setFromPoints([satPos, edgePt]);
700
+ const lineMat = new T.LineBasicMaterial({
701
+ color: coneColor,
702
+ transparent: true,
703
+ opacity: .4
704
+ });
705
+ conesGroupRef.current.add(new T.Line(lineGeo, lineMat));
706
+ }
707
+ } else if (coverage.halfAngle && coverage.showCone !== false) {
708
+ const halfAngleRad = coverage.halfAngle * (Math.PI / 180);
709
+ const coneHeight = satDistance - SCENE_EARTH_RADIUS * .98;
710
+ const coneRadius = coneHeight * Math.tan(halfAngleRad);
711
+ const coneGeo = new T.ConeGeometry(coneRadius, coneHeight, 48, 1, true);
712
+ const coneMat = new T.MeshBasicMaterial({
713
+ color: coneColor,
714
+ transparent: true,
715
+ opacity: .12,
716
+ side: T.DoubleSide,
717
+ depthWrite: false
718
+ });
719
+ const cone = new T.Mesh(coneGeo, coneMat);
720
+ cone.position.copy(satPos);
721
+ cone.lookAt(0, 0, 0);
722
+ cone.rotateX(Math.PI / 2);
723
+ cone.translateY(-coneHeight / 2);
724
+ conesGroupRef.current.add(cone);
725
+ if (coverage.showFootprint !== false) {
726
+ const footprintPos = satPos.clone().normalize().clone().multiplyScalar(SCENE_EARTH_RADIUS * 1.003);
727
+ const footprintRadius = coneRadius * (SCENE_EARTH_RADIUS / satDistance);
728
+ const footGeo = new T.CircleGeometry(footprintRadius, 64);
729
+ const footMat = new T.MeshBasicMaterial({
730
+ color: coneColor,
731
+ transparent: true,
732
+ opacity: .3,
733
+ side: T.DoubleSide,
734
+ depthWrite: false
735
+ });
736
+ const footprint = new T.Mesh(footGeo, footMat);
737
+ footprint.position.copy(footprintPos);
738
+ footprint.lookAt(satPos);
739
+ conesGroupRef.current.add(footprint);
740
+ const ringGeo = new T.RingGeometry(footprintRadius * .97, footprintRadius, 64);
741
+ const ringMat = new T.MeshBasicMaterial({
742
+ color: highlightColor,
743
+ transparent: true,
744
+ opacity: .7,
745
+ side: T.DoubleSide
746
+ });
747
+ const ring = new T.Mesh(ringGeo, ringMat);
748
+ ring.position.copy(footprintPos);
749
+ ring.lookAt(satPos);
750
+ conesGroupRef.current.add(ring);
751
+ }
752
+ }
753
+ if (coverage.showNadirLine !== false) {
754
+ let nadirGroundPoint;
755
+ if (coverage.nadirPoint) {
756
+ const np = latLonAltToCartesian(coverage.nadirPoint.latitude, coverage.nadirPoint.longitude, 0);
757
+ nadirGroundPoint = new T.Vector3(np.x, np.y, np.z);
758
+ } else nadirGroundPoint = satPos.clone().normalize().multiplyScalar(SCENE_EARTH_RADIUS);
759
+ const lineGeo = new T.BufferGeometry().setFromPoints([satPos, nadirGroundPoint]);
760
+ const lineMat = new T.LineBasicMaterial({
761
+ color: highlightColor,
762
+ transparent: true,
763
+ opacity: .7
764
+ });
765
+ conesGroupRef.current.add(new T.Line(lineGeo, lineMat));
766
+ }
767
+ });
768
+ visibilityCones.forEach((cone) => {
769
+ const pos = latLonAltToCartesian(cone.center.latitude, cone.center.longitude, 0);
770
+ const coneHeight = 1e3 * KM_TO_SCENE;
771
+ const minElev = cone.minElevation || 10;
772
+ const coneRadius = coneHeight * Math.tan((90 - minElev) * (Math.PI / 180) * .4);
773
+ const coneColor = new T.Color(cone.color || "#22d3ee");
774
+ const coneGeo = new T.ConeGeometry(coneRadius, coneHeight, 24, 1, true);
775
+ const coneMat = new T.MeshBasicMaterial({
776
+ color: coneColor,
777
+ transparent: true,
778
+ opacity: cone.opacity || .12,
779
+ side: T.DoubleSide,
780
+ depthWrite: false
781
+ });
782
+ const coneMesh = new T.Mesh(coneGeo, coneMat);
783
+ coneMesh.position.set(pos.x, pos.y, pos.z);
784
+ coneMesh.lookAt(0, 0, 0);
785
+ coneMesh.rotateX(-Math.PI / 2);
786
+ coneMesh.translateY(coneHeight / 2);
787
+ conesGroupRef.current.add(coneMesh);
788
+ const footGeo = new T.RingGeometry(coneRadius * .7, coneRadius * .9, 32);
789
+ const footMat = new T.MeshBasicMaterial({
790
+ color: coneColor,
791
+ transparent: true,
792
+ opacity: .3,
793
+ side: T.DoubleSide
794
+ });
795
+ const foot = new T.Mesh(footGeo, footMat);
796
+ foot.position.set(pos.x * 1.002, pos.y * 1.002, pos.z * 1.002);
797
+ foot.lookAt(0, 0, 0);
798
+ conesGroupRef.current.add(foot);
799
+ });
800
+ }, [
801
+ visibilityCones,
802
+ satelliteCoverages,
803
+ effectiveLayers.coverage,
804
+ currentView
805
+ ]);
806
+ const updateLinks = useCallback(() => {
807
+ if (!THREE || !linksGroupRef.current || currentView === "solar-system") return;
808
+ while (linksGroupRef.current.children.length > 0) linksGroupRef.current.remove(linksGroupRef.current.children[0]);
809
+ communicationLinks.forEach((link) => {
810
+ const fromMesh = objectMeshMapRef.current.get(link.fromId);
811
+ const toMesh = objectMeshMapRef.current.get(link.toId);
812
+ if (!fromMesh || !toMesh) return;
813
+ const color = link.color || (link.status === "active" ? "#00ff88" : "#666666");
814
+ const geo = new THREE.BufferGeometry().setFromPoints([fromMesh.position.clone(), toMesh.position.clone()]);
815
+ const mat = new THREE.LineDashedMaterial({
816
+ color: new THREE.Color(color),
817
+ transparent: true,
818
+ opacity: link.status === "active" ? .8 : .4,
819
+ dashSize: 30,
820
+ gapSize: 20
821
+ });
822
+ const line = new THREE.Line(geo, mat);
823
+ line.computeLineDistances();
824
+ line.userData = { isAnimatedLink: true };
825
+ linksGroupRef.current.add(line);
826
+ });
827
+ }, [
828
+ THREE,
829
+ communicationLinks,
830
+ currentView
831
+ ]);
832
+ const updateOrbits = useCallback(() => {
833
+ if (!THREE || !orbitsGroupRef.current || currentView === "solar-system") return;
834
+ while (orbitsGroupRef.current.children.length > 0) orbitsGroupRef.current.remove(orbitsGroupRef.current.children[0]);
835
+ if (!effectiveLayers.orbits) return;
836
+ orbitPaths.forEach((path) => {
837
+ if (path.points.length < 2) return;
838
+ const points = path.points.map((p) => new THREE.Vector3(p.x, p.y, p.z));
839
+ const geo = new THREE.BufferGeometry().setFromPoints(points);
840
+ const mat = new THREE.LineBasicMaterial({
841
+ color: new THREE.Color(path.color || "#888888"),
842
+ transparent: true,
843
+ opacity: .5
844
+ });
845
+ orbitsGroupRef.current.add(new THREE.Line(geo, mat));
846
+ });
847
+ }, [
848
+ THREE,
849
+ orbitPaths,
850
+ effectiveLayers.orbits,
851
+ currentView
852
+ ]);
853
+ const handleMouseMove = useCallback((event) => {
854
+ if (!containerRef.current || !raycasterRef.current || !cameraRef.current) return;
855
+ const rect = containerRef.current.getBoundingClientRect();
856
+ mouseRef.current.set((event.clientX - rect.left) / rect.width * 2 - 1, -((event.clientY - rect.top) / rect.height) * 2 + 1);
857
+ raycasterRef.current.setFromCamera(mouseRef.current, cameraRef.current);
858
+ const selectables = [];
859
+ objectsGroupRef.current?.traverse((obj) => {
860
+ if (obj.userData?.isSelectable) selectables.push(obj);
861
+ });
862
+ const intersects = raycasterRef.current.intersectObjects(selectables, true);
863
+ if (intersects.length > 0) {
864
+ let hit = intersects[0].object;
865
+ while (hit && !hit.userData?.objectId) hit = hit.parent;
866
+ if (hit?.userData) {
867
+ setHoverTooltip({
868
+ visible: true,
869
+ x: event.clientX - rect.left,
870
+ y: event.clientY - rect.top,
871
+ object: hit.userData.data
872
+ });
873
+ if (containerRef.current) containerRef.current.style.cursor = "pointer";
874
+ return;
875
+ }
876
+ }
877
+ setHoverTooltip({
878
+ visible: false,
879
+ x: 0,
880
+ y: 0,
881
+ object: null
882
+ });
883
+ if (containerRef.current) containerRef.current.style.cursor = "grab";
884
+ }, []);
885
+ const handleClick = useCallback((event) => {
886
+ if (!containerRef.current || !raycasterRef.current || !cameraRef.current) return;
887
+ const rect = containerRef.current.getBoundingClientRect();
888
+ mouseRef.current.set((event.clientX - rect.left) / rect.width * 2 - 1, -((event.clientY - rect.top) / rect.height) * 2 + 1);
889
+ raycasterRef.current.setFromCamera(mouseRef.current, cameraRef.current);
890
+ const selectables = [];
891
+ objectsGroupRef.current?.traverse((obj) => {
892
+ if (obj.userData?.isSelectable) selectables.push(obj);
893
+ });
894
+ planetsGroupRef.current?.traverse((obj) => {
895
+ if (obj.userData?.isSelectable) selectables.push(obj);
896
+ });
897
+ const intersects = raycasterRef.current.intersectObjects(selectables, true);
898
+ if (intersects.length > 0) {
899
+ let hit = intersects[0].object;
900
+ while (hit && !hit.userData?.objectId) hit = hit.parent;
901
+ if (hit?.userData?.objectId) {
902
+ const objectId = hit.userData.objectId;
903
+ setSelection({
904
+ selectedId: objectId,
905
+ selectedObject: hit.userData.data,
906
+ hoveredId: null
907
+ });
908
+ callbacks?.onSelect?.(hit.userData.data);
909
+ flyTo(objectId, { animate: true });
910
+ return;
911
+ }
912
+ }
913
+ setSelection({
914
+ selectedId: null,
915
+ selectedObject: null,
916
+ hoveredId: null
917
+ });
918
+ callbacks?.onSelect?.(null);
919
+ }, [callbacks]);
920
+ const flyTo = useCallback((objectId, options) => {
921
+ if (!THREE || !cameraRef.current || !controlsRef.current) return;
922
+ const mesh = objectMeshMapRef.current.get(objectId);
923
+ if (!mesh) return;
924
+ const targetPos = mesh.position.clone();
925
+ const camera = cameraRef.current;
926
+ const controls = controlsRef.current;
927
+ const category = mesh.userData?.category;
928
+ const isGroundStation = category === "groundStation";
929
+ let viewDistance;
930
+ if (options?.distance) viewDistance = options.distance;
931
+ else {
932
+ const isPlanet = mesh.userData?.isPlanet;
933
+ const targetDist = targetPos.length();
934
+ if (isPlanet) viewDistance = (mesh.geometry?.parameters?.radius || 100) * 5;
935
+ else if (isGroundStation) viewDistance = 200;
936
+ else if (category === "station") viewDistance = 80;
937
+ else if (category === "satellite") viewDistance = 60;
938
+ else if (targetDist > 3e3 * 2) viewDistance = 150;
939
+ else viewDistance = 100 * (options?.zoom || 1);
940
+ }
941
+ let cameraTargetPos;
942
+ if (options?.offset) cameraTargetPos = targetPos.clone().add(new THREE.Vector3(options.offset.x, options.offset.y, options.offset.z));
943
+ else if (isGroundStation) {
944
+ const upDir = targetPos.clone().normalize();
945
+ cameraTargetPos = targetPos.clone().add(upDir.multiplyScalar(viewDistance));
946
+ } else {
947
+ const dir = targetPos.clone().normalize();
948
+ const side = new THREE.Vector3(-dir.z, .2, dir.x).normalize();
949
+ cameraTargetPos = targetPos.clone().add(side.multiplyScalar(viewDistance));
950
+ }
951
+ if (options?.animate !== false) focusTransitionRef.current = {
952
+ startTime: Date.now(),
953
+ startPos: {
954
+ x: camera.position.x,
955
+ y: camera.position.y,
956
+ z: camera.position.z
957
+ },
958
+ targetPos: {
959
+ x: cameraTargetPos.x,
960
+ y: cameraTargetPos.y,
961
+ z: cameraTargetPos.z
962
+ },
963
+ startTarget: {
964
+ x: controls.target.x,
965
+ y: controls.target.y,
966
+ z: controls.target.z
967
+ },
968
+ targetTarget: {
969
+ x: targetPos.x,
970
+ y: targetPos.y,
971
+ z: targetPos.z
972
+ },
973
+ duration: CAMERA_TRANSITION_DURATION,
974
+ isActive: true
975
+ };
976
+ else {
977
+ camera.position.copy(cameraTargetPos);
978
+ controls.target.copy(targetPos);
979
+ controls.update();
980
+ }
981
+ callbacks?.onFocus?.(mesh.userData?.data || { id: objectId });
982
+ }, [THREE, callbacks]);
983
+ const focusOn = flyTo;
984
+ const animate = useCallback(() => {
985
+ if (!isAnimatingRef.current) return;
986
+ if (!rendererRef.current || !sceneRef.current || !cameraRef.current) return;
987
+ const camera = cameraRef.current;
988
+ const controls = controlsRef.current;
989
+ if (focusTransitionRef.current?.isActive) {
990
+ const t = focusTransitionRef.current;
991
+ const progress = Math.min((Date.now() - t.startTime) / t.duration, 1);
992
+ const e = easeOutCubic(progress);
993
+ camera.position.x = t.startPos.x + (t.targetPos.x - t.startPos.x) * e;
994
+ camera.position.y = t.startPos.y + (t.targetPos.y - t.startPos.y) * e;
995
+ camera.position.z = t.startPos.z + (t.targetPos.z - t.startPos.z) * e;
996
+ controls.target.x = t.startTarget.x + (t.targetTarget.x - t.startTarget.x) * e;
997
+ controls.target.y = t.startTarget.y + (t.targetTarget.y - t.startTarget.y) * e;
998
+ controls.target.z = t.startTarget.z + (t.targetTarget.z - t.startTarget.z) * e;
999
+ if (progress >= 1) t.isActive = false;
1000
+ }
1001
+ if (controls) controls.update();
1002
+ timeRef.current += .016;
1003
+ if (starsMaterialRef.current) starsMaterialRef.current.uniforms.time.value = timeRef.current;
1004
+ if (linksGroupRef.current) linksGroupRef.current.children.forEach((child) => {
1005
+ if (child.userData?.isAnimatedLink && child.material) child.material.dashOffset -= 1.5;
1006
+ });
1007
+ rendererRef.current.render(sceneRef.current, camera);
1008
+ animationRef.current = requestAnimationFrame(animate);
1009
+ }, []);
1010
+ useImperativeHandle(ref, () => ({
1011
+ focusOn,
1012
+ flyTo,
1013
+ setView: (mode) => setCurrentView(mode),
1014
+ setTime: () => {},
1015
+ setTimeScale: () => {},
1016
+ togglePlay: () => {},
1017
+ setLayerVisibility: (layer, visible) => setLayers((prev) => ({
1018
+ ...prev,
1019
+ [layer]: visible
1020
+ })),
1021
+ getCameraState: () => {
1022
+ if (!cameraRef.current) return {
1023
+ position: {
1024
+ x: 0,
1025
+ y: 0,
1026
+ z: 1e4
1027
+ },
1028
+ target: {
1029
+ x: 0,
1030
+ y: 0,
1031
+ z: 0
1032
+ },
1033
+ up: {
1034
+ x: 0,
1035
+ y: 1,
1036
+ z: 0
1037
+ },
1038
+ fov: 45,
1039
+ near: .1,
1040
+ far: 1e5
1041
+ };
1042
+ const c = cameraRef.current;
1043
+ return {
1044
+ position: {
1045
+ x: c.position.x,
1046
+ y: c.position.y,
1047
+ z: c.position.z
1048
+ },
1049
+ target: {
1050
+ x: controlsRef.current?.target.x || 0,
1051
+ y: controlsRef.current?.target.y || 0,
1052
+ z: controlsRef.current?.target.z || 0
1053
+ },
1054
+ up: {
1055
+ x: c.up.x,
1056
+ y: c.up.y,
1057
+ z: c.up.z
1058
+ },
1059
+ fov: c.fov,
1060
+ near: c.near,
1061
+ far: c.far
1062
+ };
1063
+ },
1064
+ setCameraState: (state) => {
1065
+ if (!cameraRef.current) return;
1066
+ if (state.position) cameraRef.current.position.set(state.position.x, state.position.y, state.position.z);
1067
+ if (state.target && controlsRef.current) controlsRef.current.target.set(state.target.x, state.target.y, state.target.z);
1068
+ },
1069
+ exportImage: (format = "png") => rendererRef.current?.domElement.toDataURL(`image/${format}`) || "",
1070
+ exportScene: () => new Blob(),
1071
+ getVisibleObjects: () => [
1072
+ ...satellites,
1073
+ ...debris,
1074
+ ...stations,
1075
+ ...customObjects
1076
+ ],
1077
+ findObjects: (q) => [
1078
+ ...satellites,
1079
+ ...debris,
1080
+ ...stations,
1081
+ ...customObjects
1082
+ ].filter((obj) => obj.name.toLowerCase().includes(q.toLowerCase()) || obj.id.toLowerCase().includes(q.toLowerCase())),
1083
+ addObject: () => {},
1084
+ removeObject: () => {},
1085
+ updateObject: () => {},
1086
+ measureDistance: (_from, _to) => 0
1087
+ }), [
1088
+ satellites,
1089
+ debris,
1090
+ stations,
1091
+ customObjects,
1092
+ focusOn
1093
+ ]);
1094
+ useEffect(() => {
1095
+ if (THREE && OrbitControlsClass) return initScene();
1096
+ }, [
1097
+ THREE,
1098
+ OrbitControlsClass,
1099
+ initScene
1100
+ ]);
1101
+ useEffect(() => {
1102
+ if (!isLoading && THREE) {
1103
+ if (animationRef.current) {
1104
+ cancelAnimationFrame(animationRef.current);
1105
+ animationRef.current = null;
1106
+ }
1107
+ isAnimatingRef.current = true;
1108
+ animate();
1109
+ return () => {
1110
+ isAnimatingRef.current = false;
1111
+ if (animationRef.current) {
1112
+ cancelAnimationFrame(animationRef.current);
1113
+ animationRef.current = null;
1114
+ }
1115
+ };
1116
+ }
1117
+ }, [
1118
+ isLoading,
1119
+ THREE,
1120
+ animate
1121
+ ]);
1122
+ useEffect(() => {
1123
+ if (THREE && !isLoading) {
1124
+ updateObjects();
1125
+ updateGroundStations();
1126
+ updateCoverage();
1127
+ updateOrbits();
1128
+ updateLinks();
1129
+ }
1130
+ }, [
1131
+ THREE,
1132
+ isLoading,
1133
+ updateObjects,
1134
+ updateGroundStations,
1135
+ updateCoverage,
1136
+ updateOrbits,
1137
+ updateLinks
1138
+ ]);
1139
+ useEffect(() => {
1140
+ const container = containerRef.current;
1141
+ if (!container || isLoading) return;
1142
+ container.addEventListener("mousemove", handleMouseMove);
1143
+ container.addEventListener("click", handleClick);
1144
+ return () => {
1145
+ container.removeEventListener("mousemove", handleMouseMove);
1146
+ container.removeEventListener("click", handleClick);
1147
+ };
1148
+ }, [
1149
+ isLoading,
1150
+ handleMouseMove,
1151
+ handleClick
1152
+ ]);
1153
+ useEffect(() => {
1154
+ const handleResize = () => {
1155
+ if (!containerRef.current || !rendererRef.current || !cameraRef.current) return;
1156
+ const rect = containerRef.current.getBoundingClientRect();
1157
+ const h = typeof height === "number" ? height : rect.height;
1158
+ cameraRef.current.aspect = rect.width / h;
1159
+ cameraRef.current.updateProjectionMatrix();
1160
+ rendererRef.current.setSize(rect.width, h);
1161
+ };
1162
+ window.addEventListener("resize", handleResize);
1163
+ return () => window.removeEventListener("resize", handleResize);
1164
+ }, [height]);
1165
+ useEffect(() => {
1166
+ if (focusedObjectId) focusOn(focusedObjectId, { animate: true });
1167
+ }, [focusedObjectId, focusOn]);
1168
+ useEffect(() => {
1169
+ if (THREE && atmosphereMaterialRef.current && sunDirection) {
1170
+ const dir = new THREE.Vector3(sunDirection.x, sunDirection.y, sunDirection.z).normalize();
1171
+ atmosphereMaterialRef.current.uniforms.uSunDirection.value = dir;
1172
+ }
1173
+ }, [THREE, sunDirection]);
1174
+ useEffect(() => {
1175
+ if (!THREE || !planetPositions || !planetsGroupRef.current) return;
1176
+ planetsGroupRef.current.traverse((child) => {
1177
+ if (child.userData?.planetId && planetPositions[child.userData.planetId]) {
1178
+ const pos = planetPositions[child.userData.planetId];
1179
+ child.position.set(pos.x, pos.y, pos.z);
1180
+ }
1181
+ });
1182
+ }, [THREE, planetPositions]);
1183
+ if (!webglCapabilities.available) return /* @__PURE__ */ jsxs("div", {
1184
+ className: `zenspace3d-fallback ${className}`,
1185
+ style: {
1186
+ width,
1187
+ height: typeof height === "number" ? height : 600,
1188
+ display: "flex",
1189
+ flexDirection: "column",
1190
+ alignItems: "center",
1191
+ justifyContent: "center",
1192
+ backgroundColor: "#030508",
1193
+ borderRadius: 8,
1194
+ fontFamily: "ui-monospace, monospace",
1195
+ ...style
1196
+ },
1197
+ children: [/* @__PURE__ */ jsx("div", {
1198
+ style: { marginBottom: 16 },
1199
+ children: /* @__PURE__ */ jsx(AstroIcon, {
1200
+ name: "public",
1201
+ size: "large",
1202
+ label: "Globe",
1203
+ style: {
1204
+ color: "#22d3ee",
1205
+ opacity: .5
1206
+ }
1207
+ })
1208
+ }), /* @__PURE__ */ jsx("div", {
1209
+ style: {
1210
+ color: "#b8bcc8",
1211
+ fontSize: 14
1212
+ },
1213
+ children: "WebGL is not available"
1214
+ })]
1215
+ });
1216
+ if (error) return /* @__PURE__ */ jsx("div", {
1217
+ style: {
1218
+ width,
1219
+ height,
1220
+ display: "flex",
1221
+ alignItems: "center",
1222
+ justifyContent: "center",
1223
+ backgroundColor: "#0a0a0f",
1224
+ color: "#ff4444"
1225
+ },
1226
+ children: /* @__PURE__ */ jsxs("p", { children: ["Error: ", error] })
1227
+ });
1228
+ return /* @__PURE__ */ jsxs("div", {
1229
+ className: `zenspace3d ${className}`,
1230
+ style: {
1231
+ width,
1232
+ height: typeof height === "number" ? height : "100%",
1233
+ position: "relative",
1234
+ backgroundColor: "#000",
1235
+ borderRadius: 8,
1236
+ overflow: "hidden",
1237
+ cursor: "grab",
1238
+ ...style
1239
+ },
1240
+ children: [
1241
+ /* @__PURE__ */ jsx("div", {
1242
+ ref: containerRef,
1243
+ style: {
1244
+ width: "100%",
1245
+ height: "100%"
1246
+ }
1247
+ }),
1248
+ isLoading && /* @__PURE__ */ jsx("div", {
1249
+ style: {
1250
+ position: "absolute",
1251
+ top: 0,
1252
+ left: 0,
1253
+ right: 0,
1254
+ bottom: 0,
1255
+ display: "flex",
1256
+ alignItems: "center",
1257
+ justifyContent: "center",
1258
+ backgroundColor: "rgba(0,0,0,0.9)"
1259
+ },
1260
+ children: /* @__PURE__ */ jsx("div", {
1261
+ style: {
1262
+ color: "#22d3ee",
1263
+ fontSize: 14
1264
+ },
1265
+ children: "Loading..."
1266
+ })
1267
+ }),
1268
+ showTools && !isLoading && /* @__PURE__ */ jsxs("div", {
1269
+ style: {
1270
+ position: "absolute",
1271
+ top: 12,
1272
+ left: 12,
1273
+ display: "flex",
1274
+ flexDirection: "column",
1275
+ gap: 4
1276
+ },
1277
+ children: [
1278
+ /* @__PURE__ */ jsx(LayerBtn, {
1279
+ icon: "satellite",
1280
+ label: "Satellites",
1281
+ active: layers.satellites,
1282
+ onClick: () => setLayers((l) => ({
1283
+ ...l,
1284
+ satellites: !l.satellites
1285
+ }))
1286
+ }),
1287
+ /* @__PURE__ */ jsx(LayerBtn, {
1288
+ icon: "home",
1289
+ label: "Stations",
1290
+ active: layers.stations,
1291
+ onClick: () => setLayers((l) => ({
1292
+ ...l,
1293
+ stations: !l.stations
1294
+ }))
1295
+ }),
1296
+ /* @__PURE__ */ jsx(LayerBtn, {
1297
+ icon: "antenna",
1298
+ label: "Ground",
1299
+ active: layers.groundStations,
1300
+ onClick: () => setLayers((l) => ({
1301
+ ...l,
1302
+ groundStations: !l.groundStations
1303
+ }))
1304
+ }),
1305
+ /* @__PURE__ */ jsx(LayerBtn, {
1306
+ icon: "timeline",
1307
+ label: "Orbits",
1308
+ active: layers.orbits,
1309
+ onClick: () => setLayers((l) => ({
1310
+ ...l,
1311
+ orbits: !l.orbits
1312
+ }))
1313
+ }),
1314
+ /* @__PURE__ */ jsx(LayerBtn, {
1315
+ icon: "signal-cellular-4-bar",
1316
+ label: "Coverage",
1317
+ active: layers.coverage,
1318
+ onClick: () => setLayers((l) => ({
1319
+ ...l,
1320
+ coverage: !l.coverage
1321
+ }))
1322
+ })
1323
+ ]
1324
+ }),
1325
+ !isLoading && /* @__PURE__ */ jsx(ObjectLegend, {
1326
+ currentView,
1327
+ satellites,
1328
+ stations,
1329
+ groundStations,
1330
+ onObjectClick: (id, objectData) => {
1331
+ setSelection({
1332
+ selectedId: id,
1333
+ selectedObject: objectData,
1334
+ hoveredId: null
1335
+ });
1336
+ callbacks?.onSelect?.(objectData);
1337
+ flyTo(id, { animate: true });
1338
+ }
1339
+ }),
1340
+ hoverTooltip.visible && hoverTooltip.object && /* @__PURE__ */ jsxs("div", {
1341
+ style: {
1342
+ position: "absolute",
1343
+ left: hoverTooltip.x + 15,
1344
+ top: hoverTooltip.y - 10,
1345
+ backgroundColor: "rgba(20, 25, 35, 0.95)",
1346
+ color: "#e4e4e7",
1347
+ padding: "10px 14px",
1348
+ borderRadius: 4,
1349
+ fontSize: 12,
1350
+ fontFamily: "system-ui, -apple-system, sans-serif",
1351
+ boxShadow: "0 4px 20px rgba(0,0,0,0.5)",
1352
+ pointerEvents: "none",
1353
+ zIndex: 100,
1354
+ minWidth: 140
1355
+ },
1356
+ children: [
1357
+ /* @__PURE__ */ jsx("div", {
1358
+ style: {
1359
+ color: "#ffffff",
1360
+ fontWeight: 500,
1361
+ marginBottom: 4
1362
+ },
1363
+ children: hoverTooltip.object.name || hoverTooltip.object.targetName || "Unknown"
1364
+ }),
1365
+ hoverTooltip.object.category && /* @__PURE__ */ jsx("div", {
1366
+ style: {
1367
+ color: "#9ca3af",
1368
+ fontSize: 11
1369
+ },
1370
+ children: hoverTooltip.object.category
1371
+ }),
1372
+ hoverTooltip.object.overpassTime && /* @__PURE__ */ jsxs("div", {
1373
+ style: {
1374
+ color: "#9ca3af",
1375
+ fontSize: 11,
1376
+ marginTop: 4
1377
+ },
1378
+ children: ["Overpass time: ", hoverTooltip.object.overpassTime]
1379
+ })
1380
+ ]
1381
+ }),
1382
+ showInfoPanel && selection.selectedObject && /* @__PURE__ */ jsxs("div", {
1383
+ style: {
1384
+ position: "absolute",
1385
+ bottom: 16,
1386
+ right: 12,
1387
+ backgroundColor: "rgba(0,0,0,0.9)",
1388
+ color: "#e4e4e7",
1389
+ padding: "14px 18px",
1390
+ borderRadius: 10,
1391
+ fontSize: 12,
1392
+ fontFamily: "ui-monospace, monospace",
1393
+ border: "1px solid rgba(34,211,238,0.3)",
1394
+ minWidth: 180
1395
+ },
1396
+ children: [
1397
+ /* @__PURE__ */ jsx("div", {
1398
+ style: {
1399
+ color: "#22d3ee",
1400
+ fontWeight: 500,
1401
+ marginBottom: 8
1402
+ },
1403
+ children: selection.selectedObject.name
1404
+ }),
1405
+ /* @__PURE__ */ jsx("div", {
1406
+ style: {
1407
+ color: "#b8bcc8",
1408
+ fontSize: 10
1409
+ },
1410
+ children: selection.selectedObject.category
1411
+ }),
1412
+ /* @__PURE__ */ jsx("button", {
1413
+ onClick: () => setSelection({
1414
+ selectedId: null,
1415
+ selectedObject: null,
1416
+ hoveredId: null
1417
+ }),
1418
+ style: {
1419
+ marginTop: 10,
1420
+ padding: "5px 10px",
1421
+ backgroundColor: "rgba(255,255,255,0.1)",
1422
+ border: "none",
1423
+ borderRadius: 4,
1424
+ color: "#a1a1aa",
1425
+ cursor: "pointer",
1426
+ fontSize: 10,
1427
+ width: "100%"
1428
+ },
1429
+ children: "Close"
1430
+ })
1431
+ ]
1432
+ }),
1433
+ !isLoading && /* @__PURE__ */ jsx("div", {
1434
+ style: {
1435
+ position: "absolute",
1436
+ bottom: 12,
1437
+ left: 12,
1438
+ backgroundColor: "rgba(0,0,0,0.5)",
1439
+ color: "#b8bcc8",
1440
+ padding: "5px 10px",
1441
+ borderRadius: 6,
1442
+ fontSize: 10
1443
+ },
1444
+ children: "Drag to rotate • Scroll to zoom"
1445
+ })
1446
+ ]
1447
+ });
1448
+ });
1027
1449
  function LayerBtn({ icon, label, active, onClick }) {
1028
- return /* @__PURE__ */ jsxs(
1029
- "button",
1030
- {
1031
- onClick,
1032
- "aria-label": `Toggle ${label} layer`,
1033
- "aria-pressed": active,
1034
- style: {
1035
- display: "flex",
1036
- alignItems: "center",
1037
- gap: 6,
1038
- padding: "6px 10px",
1039
- backgroundColor: active ? "rgba(34,211,238,0.15)" : "rgba(0,0,0,0.7)",
1040
- border: active ? "1px solid rgba(34,211,238,0.4)" : "1px solid rgba(255,255,255,0.1)",
1041
- borderRadius: 6,
1042
- cursor: "pointer",
1043
- fontSize: "0.6875rem",
1044
- // 11px in rem
1045
- color: active ? "#22d3ee" : "#b8bcc8",
1046
- // WCAG: improved contrast from #71717a
1047
- minWidth: 110
1048
- },
1049
- children: [
1050
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx(AstroIcon, { name: icon, size: "extra-small", label: "" }) }),
1051
- /* @__PURE__ */ jsx("span", { children: label })
1052
- ]
1053
- }
1054
- );
1450
+ return /* @__PURE__ */ jsxs("button", {
1451
+ onClick,
1452
+ "aria-label": `Toggle ${label} layer`,
1453
+ "aria-pressed": active,
1454
+ style: {
1455
+ display: "flex",
1456
+ alignItems: "center",
1457
+ gap: 6,
1458
+ padding: "6px 10px",
1459
+ backgroundColor: active ? "rgba(34,211,238,0.15)" : "rgba(0,0,0,0.7)",
1460
+ border: active ? "1px solid rgba(34,211,238,0.4)" : "1px solid rgba(255,255,255,0.1)",
1461
+ borderRadius: 6,
1462
+ cursor: "pointer",
1463
+ fontSize: "0.6875rem",
1464
+ color: active ? "#22d3ee" : "#b8bcc8",
1465
+ minWidth: 110
1466
+ },
1467
+ children: [/* @__PURE__ */ jsx("span", {
1468
+ "aria-hidden": "true",
1469
+ children: /* @__PURE__ */ jsx(AstroIcon, {
1470
+ name: icon,
1471
+ size: "extra-small",
1472
+ label: ""
1473
+ })
1474
+ }), /* @__PURE__ */ jsx("span", { children: label })]
1475
+ });
1055
1476
  }
1056
1477
  function ObjectLegend({ currentView, satellites, stations, groundStations, onObjectClick }) {
1057
- const [hoveredSection, setHoveredSection] = React.useState(null);
1058
- const [pinnedSection, setPinnedSection] = React.useState(null);
1059
- const isExpanded = (section) => hoveredSection === section || pinnedSection === section;
1060
- const togglePin = (section) => {
1061
- setPinnedSection((prev) => prev === section ? null : section);
1062
- };
1063
- const sectionStyle = {
1064
- cursor: "pointer",
1065
- padding: "4px 0",
1066
- display: "flex",
1067
- alignItems: "center",
1068
- justifyContent: "space-between",
1069
- transition: "background-color 0.15s"
1070
- };
1071
- const itemStyle = {
1072
- padding: "3px 8px",
1073
- marginLeft: 12,
1074
- cursor: "pointer",
1075
- borderRadius: 4,
1076
- fontSize: 10,
1077
- color: "#a1a1aa",
1078
- transition: "all 0.15s"
1079
- };
1080
- return /* @__PURE__ */ jsxs("div", { style: {
1081
- position: "absolute",
1082
- top: 12,
1083
- right: 12,
1084
- backgroundColor: "rgba(0,0,0,0.85)",
1085
- color: "#e4e4e7",
1086
- padding: "10px 14px",
1087
- borderRadius: 8,
1088
- fontSize: 11,
1089
- fontFamily: "ui-monospace, monospace",
1090
- minWidth: 160,
1091
- maxHeight: 400,
1092
- overflowY: "auto"
1093
- }, children: [
1094
- /* @__PURE__ */ jsx("div", { style: { color: "#b8bcc8", fontSize: 10, marginBottom: 8 }, children: currentView === "solar-system" ? "Solar System" : "Earth Orbit" }),
1095
- satellites.length > 0 && /* @__PURE__ */ jsxs(
1096
- "div",
1097
- {
1098
- onMouseEnter: () => setHoveredSection("satellites"),
1099
- onMouseLeave: () => setHoveredSection(null),
1100
- children: [
1101
- /* @__PURE__ */ jsxs(
1102
- "div",
1103
- {
1104
- style: {
1105
- ...sectionStyle,
1106
- backgroundColor: isExpanded("satellites") ? "rgba(255,255,255,0.05)" : "transparent"
1107
- },
1108
- onClick: () => togglePin("satellites"),
1109
- children: [
1110
- /* @__PURE__ */ jsxs("span", { children: [
1111
- /* @__PURE__ */ jsx("span", { style: { color: "#00ff88" }, children: "●" }),
1112
- " ",
1113
- satellites.length,
1114
- " Satellites"
1115
- ] }),
1116
- /* @__PURE__ */ jsxs("span", { style: { color: pinnedSection === "satellites" ? "#00ff88" : "#666", fontSize: 9 }, children: [
1117
- isExpanded("satellites") ? "▼" : "▶",
1118
- pinnedSection === "satellites" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AstroIcon, { name: "bookmark", size: "extra-small", label: "", style: { marginLeft: 4, verticalAlign: "middle" } }) }) : null
1119
- ] })
1120
- ]
1121
- }
1122
- ),
1123
- isExpanded("satellites") && /* @__PURE__ */ jsx("div", { style: { marginBottom: 4, paddingBottom: 4 }, children: satellites.map((sat) => /* @__PURE__ */ jsx(
1124
- "div",
1125
- {
1126
- style: itemStyle,
1127
- onClick: (e) => {
1128
- e.stopPropagation();
1129
- onObjectClick(sat.id, sat);
1130
- },
1131
- onMouseEnter: (e) => {
1132
- e.currentTarget.style.backgroundColor = "rgba(0,255,136,0.15)";
1133
- e.currentTarget.style.color = "#00ff88";
1134
- },
1135
- onMouseLeave: (e) => {
1136
- e.currentTarget.style.backgroundColor = "transparent";
1137
- e.currentTarget.style.color = "#a1a1aa";
1138
- },
1139
- children: sat.name || sat.id
1140
- },
1141
- sat.id
1142
- )) })
1143
- ]
1144
- }
1145
- ),
1146
- stations.length > 0 && /* @__PURE__ */ jsxs(
1147
- "div",
1148
- {
1149
- onMouseEnter: () => setHoveredSection("stations"),
1150
- onMouseLeave: () => setHoveredSection(null),
1151
- children: [
1152
- /* @__PURE__ */ jsxs(
1153
- "div",
1154
- {
1155
- style: {
1156
- ...sectionStyle,
1157
- backgroundColor: isExpanded("stations") ? "rgba(255,255,255,0.05)" : "transparent"
1158
- },
1159
- onClick: () => togglePin("stations"),
1160
- children: [
1161
- /* @__PURE__ */ jsxs("span", { children: [
1162
- /* @__PURE__ */ jsx("span", { style: { color: "#ffd700" }, children: "●" }),
1163
- " ",
1164
- stations.length,
1165
- " Stations"
1166
- ] }),
1167
- /* @__PURE__ */ jsxs("span", { style: { color: pinnedSection === "stations" ? "#ffd700" : "#666", fontSize: 9 }, children: [
1168
- isExpanded("stations") ? "" : "▶",
1169
- pinnedSection === "stations" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AstroIcon, { name: "bookmark", size: "extra-small", label: "", style: { marginLeft: 4, verticalAlign: "middle" } }) }) : null
1170
- ] })
1171
- ]
1172
- }
1173
- ),
1174
- isExpanded("stations") && /* @__PURE__ */ jsx("div", { style: { marginBottom: 4, paddingBottom: 4 }, children: stations.map((st) => /* @__PURE__ */ jsx(
1175
- "div",
1176
- {
1177
- style: itemStyle,
1178
- onClick: (e) => {
1179
- e.stopPropagation();
1180
- onObjectClick(st.id, st);
1181
- },
1182
- onMouseEnter: (e) => {
1183
- e.currentTarget.style.backgroundColor = "rgba(255,215,0,0.15)";
1184
- e.currentTarget.style.color = "#ffd700";
1185
- },
1186
- onMouseLeave: (e) => {
1187
- e.currentTarget.style.backgroundColor = "transparent";
1188
- e.currentTarget.style.color = "#a1a1aa";
1189
- },
1190
- children: st.name || st.id
1191
- },
1192
- st.id
1193
- )) })
1194
- ]
1195
- }
1196
- ),
1197
- groundStations.length > 0 && /* @__PURE__ */ jsxs(
1198
- "div",
1199
- {
1200
- onMouseEnter: () => setHoveredSection("groundStations"),
1201
- onMouseLeave: () => setHoveredSection(null),
1202
- children: [
1203
- /* @__PURE__ */ jsxs(
1204
- "div",
1205
- {
1206
- style: {
1207
- ...sectionStyle,
1208
- backgroundColor: isExpanded("groundStations") ? "rgba(255,255,255,0.05)" : "transparent"
1209
- },
1210
- onClick: () => togglePin("groundStations"),
1211
- children: [
1212
- /* @__PURE__ */ jsxs("span", { children: [
1213
- /* @__PURE__ */ jsx("span", { style: { color: "#00ffff" }, children: "▲" }),
1214
- " ",
1215
- groundStations.length,
1216
- " Ground Stations"
1217
- ] }),
1218
- /* @__PURE__ */ jsxs("span", { style: { color: pinnedSection === "groundStations" ? "#00ffff" : "#666", fontSize: 9 }, children: [
1219
- isExpanded("groundStations") ? "▼" : "",
1220
- pinnedSection === "groundStations" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AstroIcon, { name: "bookmark", size: "extra-small", label: "", style: { marginLeft: 4, verticalAlign: "middle" } }) }) : null
1221
- ] })
1222
- ]
1223
- }
1224
- ),
1225
- isExpanded("groundStations") && /* @__PURE__ */ jsx("div", { style: { marginBottom: 4, paddingBottom: 4 }, children: groundStations.map((gs) => /* @__PURE__ */ jsx(
1226
- "div",
1227
- {
1228
- style: itemStyle,
1229
- onClick: (e) => {
1230
- e.stopPropagation();
1231
- onObjectClick(gs.id, gs);
1232
- },
1233
- onMouseEnter: (e) => {
1234
- e.currentTarget.style.backgroundColor = "rgba(0,255,255,0.15)";
1235
- e.currentTarget.style.color = "#00ffff";
1236
- },
1237
- onMouseLeave: (e) => {
1238
- e.currentTarget.style.backgroundColor = "transparent";
1239
- e.currentTarget.style.color = "#a1a1aa";
1240
- },
1241
- children: gs.name || gs.id
1242
- },
1243
- gs.id
1244
- )) })
1245
- ]
1246
- }
1247
- )
1248
- ] });
1478
+ const [hoveredSection, setHoveredSection] = React.useState(null);
1479
+ const [pinnedSection, setPinnedSection] = React.useState(null);
1480
+ const isExpanded = (section) => hoveredSection === section || pinnedSection === section;
1481
+ const togglePin = (section) => {
1482
+ setPinnedSection((prev) => prev === section ? null : section);
1483
+ };
1484
+ const sectionStyle = {
1485
+ cursor: "pointer",
1486
+ padding: "4px 0",
1487
+ display: "flex",
1488
+ alignItems: "center",
1489
+ justifyContent: "space-between",
1490
+ transition: "background-color 0.15s"
1491
+ };
1492
+ const itemStyle = {
1493
+ padding: "3px 8px",
1494
+ marginLeft: 12,
1495
+ cursor: "pointer",
1496
+ borderRadius: 4,
1497
+ fontSize: 10,
1498
+ color: "#a1a1aa",
1499
+ transition: "all 0.15s"
1500
+ };
1501
+ return /* @__PURE__ */ jsxs("div", {
1502
+ style: {
1503
+ position: "absolute",
1504
+ top: 12,
1505
+ right: 12,
1506
+ backgroundColor: "rgba(0,0,0,0.85)",
1507
+ color: "#e4e4e7",
1508
+ padding: "10px 14px",
1509
+ borderRadius: 8,
1510
+ fontSize: 11,
1511
+ fontFamily: "ui-monospace, monospace",
1512
+ minWidth: 160,
1513
+ maxHeight: 400,
1514
+ overflowY: "auto"
1515
+ },
1516
+ children: [
1517
+ /* @__PURE__ */ jsx("div", {
1518
+ style: {
1519
+ color: "#b8bcc8",
1520
+ fontSize: 10,
1521
+ marginBottom: 8
1522
+ },
1523
+ children: currentView === "solar-system" ? "Solar System" : "Earth Orbit"
1524
+ }),
1525
+ satellites.length > 0 && /* @__PURE__ */ jsxs("div", {
1526
+ onMouseEnter: () => setHoveredSection("satellites"),
1527
+ onMouseLeave: () => setHoveredSection(null),
1528
+ children: [/* @__PURE__ */ jsxs("div", {
1529
+ style: {
1530
+ ...sectionStyle,
1531
+ backgroundColor: isExpanded("satellites") ? "rgba(255,255,255,0.05)" : "transparent"
1532
+ },
1533
+ onClick: () => togglePin("satellites"),
1534
+ children: [/* @__PURE__ */ jsxs("span", { children: [
1535
+ /* @__PURE__ */ jsx("span", {
1536
+ style: { color: "#00ff88" },
1537
+ children: ""
1538
+ }),
1539
+ " ",
1540
+ satellites.length,
1541
+ " ",
1542
+ "Satellites"
1543
+ ] }), /* @__PURE__ */ jsxs("span", {
1544
+ style: {
1545
+ color: pinnedSection === "satellites" ? "#00ff88" : "#666",
1546
+ fontSize: 9
1547
+ },
1548
+ children: [isExpanded("satellites") ? "▼" : "▶", pinnedSection === "satellites" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AstroIcon, {
1549
+ name: "bookmark",
1550
+ size: "extra-small",
1551
+ label: "",
1552
+ style: {
1553
+ marginLeft: 4,
1554
+ verticalAlign: "middle"
1555
+ }
1556
+ }) }) : null]
1557
+ })]
1558
+ }), isExpanded("satellites") && /* @__PURE__ */ jsx("div", {
1559
+ style: {
1560
+ marginBottom: 4,
1561
+ paddingBottom: 4
1562
+ },
1563
+ children: satellites.map((sat) => /* @__PURE__ */ jsx("div", {
1564
+ style: itemStyle,
1565
+ onClick: (e) => {
1566
+ e.stopPropagation();
1567
+ onObjectClick(sat.id, sat);
1568
+ },
1569
+ onMouseEnter: (e) => {
1570
+ e.currentTarget.style.backgroundColor = "rgba(0,255,136,0.15)";
1571
+ e.currentTarget.style.color = "#00ff88";
1572
+ },
1573
+ onMouseLeave: (e) => {
1574
+ e.currentTarget.style.backgroundColor = "transparent";
1575
+ e.currentTarget.style.color = "#a1a1aa";
1576
+ },
1577
+ children: sat.name || sat.id
1578
+ }, sat.id))
1579
+ })]
1580
+ }),
1581
+ stations.length > 0 && /* @__PURE__ */ jsxs("div", {
1582
+ onMouseEnter: () => setHoveredSection("stations"),
1583
+ onMouseLeave: () => setHoveredSection(null),
1584
+ children: [/* @__PURE__ */ jsxs("div", {
1585
+ style: {
1586
+ ...sectionStyle,
1587
+ backgroundColor: isExpanded("stations") ? "rgba(255,255,255,0.05)" : "transparent"
1588
+ },
1589
+ onClick: () => togglePin("stations"),
1590
+ children: [/* @__PURE__ */ jsxs("span", { children: [
1591
+ /* @__PURE__ */ jsx("span", {
1592
+ style: { color: "#ffd700" },
1593
+ children: "●"
1594
+ }),
1595
+ " ",
1596
+ stations.length,
1597
+ " ",
1598
+ "Stations"
1599
+ ] }), /* @__PURE__ */ jsxs("span", {
1600
+ style: {
1601
+ color: pinnedSection === "stations" ? "#ffd700" : "#666",
1602
+ fontSize: 9
1603
+ },
1604
+ children: [isExpanded("stations") ? "▼" : "▶", pinnedSection === "stations" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AstroIcon, {
1605
+ name: "bookmark",
1606
+ size: "extra-small",
1607
+ label: "",
1608
+ style: {
1609
+ marginLeft: 4,
1610
+ verticalAlign: "middle"
1611
+ }
1612
+ }) }) : null]
1613
+ })]
1614
+ }), isExpanded("stations") && /* @__PURE__ */ jsx("div", {
1615
+ style: {
1616
+ marginBottom: 4,
1617
+ paddingBottom: 4
1618
+ },
1619
+ children: stations.map((st) => /* @__PURE__ */ jsx("div", {
1620
+ style: itemStyle,
1621
+ onClick: (e) => {
1622
+ e.stopPropagation();
1623
+ onObjectClick(st.id, st);
1624
+ },
1625
+ onMouseEnter: (e) => {
1626
+ e.currentTarget.style.backgroundColor = "rgba(255,215,0,0.15)";
1627
+ e.currentTarget.style.color = "#ffd700";
1628
+ },
1629
+ onMouseLeave: (e) => {
1630
+ e.currentTarget.style.backgroundColor = "transparent";
1631
+ e.currentTarget.style.color = "#a1a1aa";
1632
+ },
1633
+ children: st.name || st.id
1634
+ }, st.id))
1635
+ })]
1636
+ }),
1637
+ groundStations.length > 0 && /* @__PURE__ */ jsxs("div", {
1638
+ onMouseEnter: () => setHoveredSection("groundStations"),
1639
+ onMouseLeave: () => setHoveredSection(null),
1640
+ children: [/* @__PURE__ */ jsxs("div", {
1641
+ style: {
1642
+ ...sectionStyle,
1643
+ backgroundColor: isExpanded("groundStations") ? "rgba(255,255,255,0.05)" : "transparent"
1644
+ },
1645
+ onClick: () => togglePin("groundStations"),
1646
+ children: [/* @__PURE__ */ jsxs("span", { children: [
1647
+ /* @__PURE__ */ jsx("span", {
1648
+ style: { color: "#00ffff" },
1649
+ children: "▲"
1650
+ }),
1651
+ " ",
1652
+ groundStations.length,
1653
+ " Ground Stations"
1654
+ ] }), /* @__PURE__ */ jsxs("span", {
1655
+ style: {
1656
+ color: pinnedSection === "groundStations" ? "#00ffff" : "#666",
1657
+ fontSize: 9
1658
+ },
1659
+ children: [isExpanded("groundStations") ? "" : "▶", pinnedSection === "groundStations" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AstroIcon, {
1660
+ name: "bookmark",
1661
+ size: "extra-small",
1662
+ label: "",
1663
+ style: {
1664
+ marginLeft: 4,
1665
+ verticalAlign: "middle"
1666
+ }
1667
+ }) }) : null]
1668
+ })]
1669
+ }), isExpanded("groundStations") && /* @__PURE__ */ jsx("div", {
1670
+ style: {
1671
+ marginBottom: 4,
1672
+ paddingBottom: 4
1673
+ },
1674
+ children: groundStations.map((gs) => /* @__PURE__ */ jsx("div", {
1675
+ style: itemStyle,
1676
+ onClick: (e) => {
1677
+ e.stopPropagation();
1678
+ onObjectClick(gs.id, gs);
1679
+ },
1680
+ onMouseEnter: (e) => {
1681
+ e.currentTarget.style.backgroundColor = "rgba(0,255,255,0.15)";
1682
+ e.currentTarget.style.color = "#00ffff";
1683
+ },
1684
+ onMouseLeave: (e) => {
1685
+ e.currentTarget.style.backgroundColor = "transparent";
1686
+ e.currentTarget.style.color = "#a1a1aa";
1687
+ },
1688
+ children: gs.name || gs.id
1689
+ }, gs.id))
1690
+ })]
1691
+ })
1692
+ ]
1693
+ });
1249
1694
  }
1250
- export {
1251
- ZenSpace3D,
1252
- ZenSpace3D as default
1253
- };
1254
- //# sourceMappingURL=ZenSpace3D.js.map
1695
+ //#endregion
1696
+ export { ZenSpace3D, ZenSpace3D as default };
1697
+
1698
+ //# sourceMappingURL=ZenSpace3D.js.map