Attribute VB_Name = "Sys"
Option Explicit

'__API functions_______________________________________________________________

Declare Function CallWindowProc Lib "User32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function ClientToScreen Lib "User32" (ByVal hWnd As Long, lpPoint As PointApi) As Long
Declare Function ClientToScreenEx Lib "User32" Alias "ClientToScreen" (ByVal hWnd As Long, lpPoint As Rect) As Long
'Declare Function ClipCursor Lib "User32" (lpRect As Any) As Long
Declare Function CreateWindowEx Lib "User32" Alias "CreateWindowExA" (ByVal dwExStyle As Long, ByVal lpClassName As String, ByVal lpWindowName As String, ByVal dwStyle As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As Long, ByVal hMenu As Long, ByVal hInstance As Long, lpParam As Any) As Long
Declare Function DefWindowProc Lib "User32" Alias "DefWindowProcA" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function DestroyIcon Lib "User32" (ByVal hIcon As Long) As Long
Declare Function DestroyWindow Lib "User32" (ByVal hWnd As Long) As Long
Declare Function DrawIconEx Lib "User32" (ByVal hDC As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, Optional ByVal cxWidth As Long, Optional ByVal cyHeight As Long, Optional ByVal istepIfAniCur As Long, Optional ByVal hbrFlickerFreeDraw As Long, Optional ByVal diFlags As Long) As Boolean
Declare Function DrawText Lib "User32" Alias "DrawTextA" (ByVal hDC As Long, ByVal lpStr As String, ByVal nCount As Long, lpRect As Rect, ByVal wFormat As Long) As Long
Declare Function FindWindow Lib "User32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'Declare Function GetAsyncKeyState Lib "User32" (ByVal vKey As Long) As Integer
Declare Function GetCapture Lib "User32" () As Long
Declare Function GetClientRect Lib "User32" (ByVal hWnd As Long, lpRect As Rect) As Long
Declare Function GetCursor Lib "User32" () As Long
Declare Function GetCursorInfo Lib "User32" (pCI As CursorInfo) As Long
Declare Function GetCursorPos Lib "User32" (lpPoint As PointApi) As Long
Declare Function GetDC Lib "User32" (ByVal hWnd As Long) As Long
Declare Function GetDesktopWindow Lib "User32" () As Long
Declare Function GetForegroundWindow Lib "User32" () As Long
Declare Function GetFocus Lib "User32" () As Long
Declare Function GetIconInfo Lib "User32" (ByVal hIcon As Long, pIconInfo As IconInfo) As Long
Declare Function GetKeyState Lib "User32" (ByVal vKey As Long) As Integer
Declare Function GetParent Lib "User32" (ByVal hWnd As Long) As Long
Declare Function GetWindow Lib "User32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
'GW_HWNDFIRST=0 GW_HWNDLAST=1 GW_HWNDNEXT=2 GW_HWNDPREV=3 GW_OWNER=4 GW_CHILD=5 GW_ENABLEDPOPUP=6
Declare Function GetWindowDC Lib "User32" (ByVal hWnd As Long) As Long
Declare Function GetWindowLong Lib "User32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Declare Function GetWindowPlacement Lib "User32" (ByVal hWnd As Long, lpWndPl As WindowPlacement) As Long
Declare Function GetWindowRect Lib "User32" (ByVal hWnd As Long, lpRect As Any) As Long
Declare Function GetSysColor Lib "User32" (ByVal nIndex As Long) As Long
Declare Function GetSystemMetrics Lib "User32" (ByVal nIndex As Long) As Long
'SM_CXSCREEN=0 SM_CYSCREEN=1 SM_XVIRTUALSCREEN=76 SM_YVIRTUALSCREEN=77 SM_CXVIRTUALSCREEN=78 SM_CYVIRTUALSCREEN=79
Declare Function IsWindow Lib "User32" (ByVal hWnd As Long) As Long
'Declare Function IsWindowVisible Lib "User32" (ByVal hWnd As Long) As Long
Declare Function LoadCursor Lib "User32" Alias "LoadCursorA" (ByVal hInstance As Long, ByVal lpCursorName As Long) As Long
Declare Function LoadImageX Lib "User32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpszName As String, ByVal uType As Long, Optional ByVal cxDesired As Long, Optional ByVal cyDesired As Long, Optional ByVal fuLoad As Long) As Long
'Declare Function MessageBox Lib "User32" Alias "MessageBoxA" (Optional ByVal hWnd As Long, Optional ByVal lpText As String = vbNullString, Optional ByVal lpCaption As String = vbNullString, Optional ByVal wType As VbMsgBoxStyle) As VbMsgBoxResult
Declare Sub mouse_event Lib "User32" (ByVal dwFlags As Long, Optional ByVal DX As Long, Optional ByVal DY As Long, Optional ByVal dwData As Long, Optional ByVal dwExtraInfo As Long)
'MOUSEEVENTF_MOVE=1 MOUSEEVENTF_LEFTDOWN=2 MOUSEEVENTF_LEFTUP=4 MOUSEEVENTF_RIGHTDOWN=8 MOUSEEVENTF_RIGHTUP=&H10
Declare Function PostMessage Lib "User32" Alias "PostMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, Optional ByVal wParam As Long, Optional ByVal lParam As Long) As Long
Declare Sub PostQuitMessage Lib "User32" (ByVal nExitCode As Long)
Declare Function RedrawWindow Lib "User32" (ByVal hWnd As Long, Optional ByVal lprcUpdate As Long, Optional ByVal hrgnUpdate As Long, Optional ByVal fuRedraw As Long) As Long
'RDW_INVALIDATE=1 RDW_INTERNALPAINT=2 RDW_ERASE=4 RDW_VALIDATE=8 RDW_NOINTERNALPAINT=&H10 RDW_NOERASE=&H20 RDW_NOCHILDREN=&H40 RDW_ALLCHILDREN=&H80 RDW_UPDATENOW=&H100 RDW_ERASENOW=&H200 RDW_FRAME=&H400 RDW_NOFRAME=&H800
Declare Function RegisterClass Lib "User32" Alias "RegisterClassA" (Class As WndClass) As Long
Declare Function ReleaseCapture Lib "User32" () As Long
Declare Function ReleaseDC Lib "User32" (ByVal hWnd As Long, ByVal hDC As Long) As Long
Declare Function SendMessage Lib "User32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, Optional ByVal wParam As Long, Optional lParam As Any = 0) As Long
Declare Function SetCapture Lib "User32" (ByVal hWnd As Long) As Long
Declare Function SetCursor Lib "User32" (ByVal hCursor As Long) As Long
Declare Function SetCursorPos Lib "User32" (ByVal X As Long, ByVal Y As Long) As Long
Declare Function SetFocusX Lib "User32" Alias "SetFocus" (ByVal hWnd As Long) As Long
Declare Function SetLayeredWindowAttributes Lib "User32" (ByVal hWnd As Long, Optional ByVal crKey As Long, Optional ByVal bAlpha As Byte, Optional ByVal dwFlags As Long) As Long
Declare Function SetParent Lib "User32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
Declare Function SetScrollInfo Lib "User32" (ByVal hWnd As Long, ByVal fnBar As Long, lpcScrollInfo As ScrollInfo, ByVal fRedraw As Long) As Long
'SB_HORZ=0 SB_VERT=1 SB_BOTH=3
Declare Function SetWindowLong Lib "User32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
'GWL_WNDPROC=-4 GWL_HINSTANCE=-6 GWL_HWNDPARENT=-8 GWL_ID=-12 GWL_STYLE=-16 GWL_EXSTYLE=-20 GWL_USERDATA=-21
Declare Function SetWindowPos Lib "User32" (ByVal hWnd As Long, Optional ByVal hWndInsertAfter As Long, Optional ByVal X As Long, Optional ByVal Y As Long, Optional ByVal CX As Long, Optional ByVal CY As Long, Optional ByVal wFlags As Long) As Long
'1=NoSize 2=NoMove 4=NoZOrd 8=NoRedraw &H10=NoAct &H20=FrameChanged &H40=Show &H80=Hide   NoTopMost=-2 TopMost=-1 Top=0 Bottom=1
Declare Function SetWindowText Lib "User32" Alias "SetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String) As Long
Declare Function ShowWindow Lib "User32" (ByVal hWnd As Long, Optional ByVal nCmdShow As Long = vbNormalFocus) As Long
'Declare Function ShowWindowAsync Lib "User32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
'SW_HIDE=0 SW_SHOWNORMAL=1 SW_SHOWMINIMIZED=2 SW_SHOWMAXIMIZED=3 SW_SHOWNOACTIVATE=4 SW_SHOW=5 SW_MINIMIZE=6 SW_SHOWMINNOACTIVE=7 SW_SHOWNA=8 SW_RESTORE=9 SW_SHOWDEFAULT=10
Declare Function ShowScrollBar Lib "User32" (ByVal hWnd As Long, Optional ByVal wBar As Long = 3, Optional ByVal bShow As Long) As Long
'SB_HORZ=0 SB_VERT=1 SB_BOTH=3
Declare Function SystemParametersInfo Lib "User32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, lpvParam As Any, ByVal fuWinIni As Long) As Long
Declare Function UnregisterClass Lib "User32" Alias "UnregisterClassA" (ByVal lpClassName As String, ByVal hInstance As Long) As Long
Declare Function WindowFromPoint Lib "User32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long

Declare Function BitBlt Lib "Gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Declare Function CreateCompatibleBitmap Lib "Gdi32" (ByVal hDC As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Declare Function CreateCompatibleDC Lib "Gdi32" (ByVal hDC As Long) As Long
Declare Function CreateDC Lib "Gdi32" Alias "CreateDCA" (Optional ByVal lpDriverName As String = "DISPLAY", Optional ByVal lpDeviceName As String = vbNullString, Optional ByVal lpOutput As String = vbNullString, Optional ByVal lpInitData As Long) As Long
Declare Function CreateDIBSection Lib "Gdi32" (ByVal hDC As Long, pBitmapInfo As BitmapInfo, ByVal iUsage As Long, ppvBits As Long, ByVal hSection As Long, ByVal dwOffset As Long) As Long
Declare Function DeleteDC Lib "Gdi32" (ByVal hDC As Long) As Long
Declare Function DeleteObject Lib "Gdi32" (ByVal hObject As Long) As Long
Declare Function GetCurrentObject Lib "Gdi32" (ByVal hDC As Long, ByVal uObjectType As Long) As Long
'OBJ_PEN=1 OBJ_BRUSH=2 OBJ_PAL=5 OBJ_FONT=6 OBJ_BITMAP=7
Declare Function GetDeviceCaps Lib "Gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Declare Function GetDIBits Lib "Gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BitmapInfo, ByVal wUsage As Long) As Long
Declare Function GetObject Lib "Gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
Declare Function Polygon Lib "Gdi32" (ByVal hDC As Long, lpPoint As PointApi, ByVal nCount As Long) As Long
Declare Function SelectObject Lib "Gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
Declare Function SetBrushOrgEx Lib "Gdi32" (ByVal hDC As Long, ByVal nXOrg As Long, ByVal nYOrg As Long, ByVal lppt As Long) As Long
'Declare Function SetDIBits Lib "Gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long,  lpBits As Any, lpBI As BitmapInfo, ByVal wUsage As Long) As Long
'Declare Function SetPolyFillMode Lib "Gdi32" (ByVal hDC As Long, ByVal nPolyFillMode As Long) As Long
Declare Function SetStretchBltMode Lib "Gdi32" (ByVal hDC As Long, ByVal nStretchMode As Long) As Long
Declare Function StretchBlt Lib "Gdi32" (ByVal hDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, ByVal dwRop As Long) As Long
Declare Function StretchDIBits Lib "Gdi32" (ByVal hDC As Long, ByVal DestX As Long, ByVal DestY As Long, ByVal nDestWidth As Long, ByVal nDestHeight As Long, ByVal SrcX As Long, ByVal SrcY As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, lpBits As Any, lpBitsInfo As BitmapInfo, ByVal wUsage As Long, ByVal dwRop As Long) As Long
Declare Function TextOut Lib "Gdi32" Alias "TextOutA" (ByVal hDC As Long, ByVal X As Long, ByVal Y As Long, ByVal lpString As String, ByVal nCount As Long) As Long

Declare Sub GetLocalTime Lib "Kernel32" (lpSystemTime As SystemTime)
Declare Sub GetSystemTime Lib "Kernel32" (lpSystemTime As SystemTime)
Declare Sub GetSystemTimeAsFileTime Lib "Kernel32" (lpSystemTimeAsFileTime As Long64)
Declare Function GetVersion Lib "Kernel32" () As Long
Declare Sub GlobalMemoryStatus Lib "Kernel32" (lpBuffer As MemoryStatus)
Declare Function FileTimeToLocalFileTime Lib "Kernel32" (lpFileTime As Long64, lpLocalFileTime As Long64) As Long
Declare Function lstrcpy Lib "Kernel32" (Dst As Any, Src As Any) As Long
Declare Function lstrcpyn Lib "Kernel32" (Dst As Any, Src As Any, ByVal Length As Long) As Long
Declare Function lstrlen Lib "Kernel32" (ByVal Str As String) As Long
Declare Function MultiByteToWideChar Lib "Kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As String, ByVal cchMultiByte As Long, ByVal lpWideCharStr As String, ByVal cchWideChar As Long) As Long
Declare Function QueryDosDevice Lib "Kernel32" Alias "QueryDosDeviceA" (ByVal lpDeviceName As String, ByVal lpTargetPath As String, ByVal ucchMax As Long) As Long
Declare Function QueryPerformanceCounter Lib "Kernel32" (lpPerformanceCount As Long64) As Long
Declare Function QueryPerformanceFrequency Lib "Kernel32" (lpFrequency As Long64) As Long
Declare Sub RtlFillMemory Lib "Kernel32" (pDest As Any, ByVal Length As Long, ByVal Fill As Byte)
Declare Sub RtlMoveMemory Lib "Kernel32" (pDest As Any, pSource As Any, Optional ByVal ByteLen As Long = 4)
'Declare Sub RtlMoveMemoryX Lib "Kernel32" Alias "RtlMoveMemory" (ByVal pDest As String, ByVal pSource As Long, ByVal ByteLen As Long)
Declare Sub Sleep Lib "Kernel32" (ByVal dwMilliseconds As Long)

Type PointApi: X As Long: Y As Long: End Type
Type Size: CX As Long: CY As Long: End Type
Type Bitmap: bmType As Long: bmWidth As Long: bmHeight As Long: bmWidthBytes As Long: bmPlanes As Integer: bmBitsPixel As Integer: bmBits As Long: End Type
Type BitmapInfoHeader: biSize As Long: biWidth As Long: biHeight As Long: biPlanes As Integer: biBitCount As Integer: biCompression As Long: biSizeImage As Long: biXPelsPerMeter As Long: biYPelsPerMeter As Long: biClrUsed As Long: biClrImportant As Long: End Type
Type BitmapInfo: Bh As BitmapInfoHeader: bmiColors As Long: End Type
Type CursorInfo: cbSize As Long: Flags As Long: hCursor As Long: ptScreenPos As PointApi: End Type
Type IconInfo: fIcon As Long: xHotspot As Long: yHotspot As Long: hbmMask As Long: hbmColor As Long: End Type
Type Long64: L As Long: H As Long: End Type
Type MemoryStatus: Length As Long: MemoryLoad As Long: TotalPhys As Long: AvailPhys As Long: TotalPageFile As Long: AvailPageFile As Long: TotalVirtual As Long: AvailVirtual As Long: End Type
Type MinMaxInfo: ptReserved As PointApi: ptMaxSize As PointApi: ptMaxPosition As PointApi: ptMinTrackSize As PointApi: ptMaxTrackSize As PointApi: End Type
Type Rect: Left As Long: Top As Long: Right As Long: Bottom As Long: End Type
Type RectEx: Left As Long: Top As Long: Right As Long: Bottom As Long: Width As Long: Height As Long: End Type
Type ScrollInfo: cbSize As Long: fMask As Long: nMin As Long: nMax As Long: nPage As Long: nPos As Long: nTrackPos As Long: End Type
Type SystemTime: Year As Integer: Month As Integer: DayOfWeek As Integer: Day As Integer: Hour As Integer: Minute As Integer: Second As Integer: Milliseconds As Integer: End Type
Type WindowPlacement: Length As Long: Flags As Long: ShowCmd As Long: MinX As Long: MinY As Long: MaxX As Long: MaxY As Long: Left As Long: Top As Long: Right As Long: Bottom As Long: End Type
Type WndClass: Style As Long: lpFnWndProc As Long: cbClsExtra As Long: cbWndExtra2 As Long: hInstance As Long: hIcon As Long: hCursor As Long: hbrBackground As Long: lpszMenuName As String: lpszClassName As String: End Type

'__File functions______________________________________________________________

Declare Function CloseHandle Lib "Kernel32" (ByVal hObject As Long) As Long
Declare Function CopyFileEx Lib "Kernel32" Alias "CopyFileExA" (ByVal lpExistingFileName As String, ByVal lpNewFileName As String, Optional ByVal lpProgressRoutine As Long, Optional ByVal lpData As Long, Optional ByVal pbCancel As Long, Optional ByVal dwCopyFlags As Long) As Long
Declare Function CreateFile Lib "Kernel32" Alias "CreateFileA" (ByVal FileName As String, Optional ByVal DesiredAccess As Long, Optional ByVal ShareMode As Long, Optional ByVal SecurityAttributes As Long, Optional ByVal CreationDisposition As Long, Optional ByVal FlagsAndAttributes As Long, Optional ByVal TemplateFile As Long) As Long
'GENERIC_READ=&H80000000 GENERIC_WRITE=&H40000000 GENERIC_EXECUTE=&H20000000 GENERIC_ALL=&H10000000
'CREATE_NEW=1 CREATE_ALWAYS=2 OPEN_EXISTING=3 OPEN_ALWAYS=4 TRUNCATE_EXISTING=5 .. FILE_SHARE_READ=1 FILE_SHARE_WRITE=2
Declare Function FindClose Lib "Kernel32" (ByVal FindFile As Long) As Long
Declare Function FindFirst Lib "Kernel32" Alias "FindFirstFileA" (ByVal FileName As String, FindFileData As Win32_Find_Data) As Long
Declare Function FindNext Lib "Kernel32" Alias "FindNextFileA" (ByVal FindFile As Long, FindFileData As Win32_Find_Data) As Long
'Declare Function FlushFileBuffers Lib "Kernel32" (ByVal File As Long) As Long
Declare Function ReadFile Lib "Kernel32" (ByVal File As Long, ByVal Buffer As String, ByVal NumberOfBytesToRead As Long, NumberOfBytesRead As Long, Optional ByVal Overlapped As Long) As Long
Declare Function ReadFileX Lib "Kernel32" Alias "ReadFile" (ByVal File As Long, ByVal Buffer As Long, ByVal NumberOfBytesToRead As Long, NumberOfBytesRead As Long, Optional ByVal Overlapped As Long) As Long
Declare Function WriteFile Lib "Kernel32" (ByVal File As Long, ByVal Buffer As String, ByVal NumberOfBytesToWrite As Long, Optional NumberOfBytesWritten As Long, Optional ByVal Overlapped As Long) As Long
Declare Function WriteFileX Lib "Kernel32" Alias "WriteFile" (ByVal File As Long, ByVal Buffer As Long, ByVal NumberOfBytesToWrite As Long, Optional NumberOfBytesWritten As Long, Optional ByVal Overlapped As Long) As Long
Declare Function GetFileAttributes Lib "Kernel32" Alias "GetFileAttributesA" (ByVal FileName As String) As Long
Declare Function CreateDirectory Lib "Kernel32" Alias "CreateDirectoryA" (ByVal PathName As String, Optional ByVal SecurityAttributes As Long) As Long
Declare Function SetCurrentDirectory Lib "Kernel32" Alias "SetCurrentDirectoryA" (ByVal PathName As String) As Long

Type FileTime: L As Long: H As Long: End Type
Type Win32_Find_Data: Attr As Long: Creation As FileTime: LastAccess As FileTime: LastWrite As FileTime: SizeH As Long: Size As Long: Reserved As Long: Reserved2 As Long: Name As String * 260: Alternate As String * 14: End Type

'__Comm functions______________________________________________________________

Declare Function GetCommState Lib "Kernel32" (ByVal nCid As Long, lpDCB As Dcb) As Long
Declare Function SetCommState Lib "Kernel32" (ByVal hCommDev As Long, lpDCB As Dcb) As Long
Declare Function GetCommTimeouts Lib "Kernel32" (ByVal hFile As Long, lpCommTimeouts As COMMTimeouts) As Long
Declare Function SetCommTimeouts Lib "Kernel32" (ByVal hFile As Long, lpCommTimeouts As COMMTimeouts) As Long

Type COMMTimeouts: ReadIntervalTimeOut As Long: ReadTotalTimeOutMultiplier As Long: ReadTotalTimeOutConstant As Long: WriteTotalTimeOutMultiplier As Long: WriteTotalTimeOutConstant As Long: End Type
Type Dcb: DCBlength As Long: BaudRate As Long: BitFields As Long: Reserved As Integer: XonLim As Integer: XoffLim As Integer: ByteSize As Byte: Parity As Byte: StopBits As Byte: XonChar As Byte: XoffChar As Byte: ErrorChar As Byte: EofChar As Byte: EvtChar As Byte: Reserved1 As Integer: End Type

'__Network functions___________________________________________________________

Declare Function WSAStartup Lib "WSock32" (ByVal VersionRequired As Long, Data As WSAData) As Long
Declare Function WSACleanup Lib "WSock32" () As Long
'Declare Function WSAGetLastError Lib "WSock32" () As Long
Declare Function socket Lib "WSock32" (ByVal AddressFamily As Long, ByVal SocketType As Long, ByVal Protocol As Long) As Long
'PF_INET=2 .. SOCK_STREAM=1 SOCK_DGRAM=2 .. IPPROTO_IP=0 IPPROTO_TCP=6 IPPROTO_UDP=17
Declare Function closesocket Lib "WSock32" (ByVal socket As Long) As Long
Declare Function bind Lib "WSock32" (ByVal socket As Long, Name As SockAddr, ByVal NameLen As Long) As Long
'Declare Function listen Lib "WSock32" (ByVal socket As Long, ByVal BackLog As Long) As Long
'Declare Function accept Lib "WSock32" (ByVal socket As Long, Addr As SockAddr, AddrLen As Long) As Long
Declare Function connect Lib "WSock32" (ByVal socket As Long, Name As SockAddr, ByVal NameLen As Long) As Long
Declare Function recv Lib "WSock32" (ByVal socket As Long, Buffer As Any, ByVal BufferLen As Long, ByVal Flags As Long) As Long
Declare Function recvfrom Lib "WSock32" (ByVal socket As Long, Buffer As Any, ByVal BufferLen As Long, ByVal Flags As Long, FromAddr As SockAddr, FromLen As Long) As Long
Declare Function send Lib "WSock32" (ByVal socket As Long, Buffer As Any, ByVal BufferLen As Long, ByVal Flags As Long) As Long
Declare Function sendto Lib "WSock32" (ByVal socket As Long, Buffer As Any, ByVal BufferLen As Long, ByVal Flags As Long, ToAddr As SockAddr, ByVal ToLen As Long) As Long
'Declare Function selectx Lib "WSock32" Alias "select" (ByVal NFds As Long, ReadFds As Any, WriteFds As Any, ExceptFds As Any, TimeOut As TimeVal) As Integer
Declare Function ioctlsocket Lib "WSock32" (ByVal socket As Long, ByVal Cmd As Long, ArgP As Long) As Long
'ioctlsocket socket, FIONBIO, X 'FIONBIO=&H8004667E 0=Block(default) 1=NonBlock .. FIONREAD=&H4004667F
Declare Function setsockopt Lib "WSock32" (ByVal socket As Long, ByVal Level As Long, ByVal OptName As Long, Optional OptVal As Long, Optional ByVal OptLen As Long = 4) As Long
'Declare Function getsockopt Lib "WSock32" (ByVal Socket As Long, ByVal Level As Long, ByVal OptName As Long, Optional OptVal As Long, Optional OptLen As Long = 4) As Long
'SOL_SOCKET=&HFFFF SO_REUSEADDR=4 SO_SNDBUF=&H1001 SO_RCVBUF=&H1002 SO_SNDTIMEO=&H1005 SO_RCVTIMEO=&H1006
Declare Function gethostbyname Lib "WSock32" (ByVal HostName As String) As Long
'Declare Function gethostbyaddr Lib "WSock32" (Addr As Long, ByVal AddrLen As Long, ByVal AddrType As Long) As Long
Declare Function inet_addr Lib "WSock32" (ByVal IP As String) As Long
'Declare Function inet_ntoa Lib "WSock32" (ByVal Add As Long) As Long
Declare Function htonl Lib "WSock32" (ByVal HostLong As Long) As Long
'Declare Function ntohl Lib "WSock32" (ByVal NetLong As Long) As Long
Declare Function htons Lib "WSock32" (ByVal HostShort As Integer) As Integer
'Declare Function ntohs Lib "WSock32" (ByVal NetShort As Integer) As Integer

Type WSAData: Version As Integer: HighVersion As Integer: Description(256 - 1) As Byte: SystemStatus(128 - 1) As Byte: MaxSockets As Integer: MaxUdpDg As Integer: VendorInfo As Long: End Type
Type SockAddr: Family As Integer: Port As Integer: Addr As Long: Zero(8 - 1) As Byte: End Type
'Type FDSet: Count As Long: ArrayX(64 - 1) As Long: End Type
'Type TimeVal: Sec As Long: uSec As Long: End Type

'__Common Dialogs functions____________________________________________________

Declare Function ChooseColor Lib "ComDlg32" Alias "ChooseColorA" (pChoosecolor As Choose_Color) As Long
Declare Function ChooseFont Lib "ComDlg32" Alias "ChooseFontA" (pChooseFont As Choose_Font) As Long
Declare Function GetOpenFileName Lib "ComDlg32" Alias "GetOpenFileNameA" (pOpenFileName As OpenFileName) As Long
Declare Function GetSaveFileName Lib "ComDlg32" Alias "GetSaveFileNameA" (pOpenFileName As OpenFileName) As Long
'OFN_OVERWRITEPROMPT=2 OFN_HIDEREADONLY=4 OFN_NOCHANGEDIR=8 OFN_ALLOWMULTISELECT=&H200
'OFN_PATHMUSTEXIST=&H800 OFN_FILEMUSTEXIST=&H1000
'OFN_CREATEPROMPT=&H2000 OFN_EXPLORER=&H80000 OFN_NODEREFERENCELINKS=&H100000 OFN_LONGNAMES=&H200000

Type Choose_Color: lStructSize As Long: hwndOwner As Long: hInstance As Long: rgbResult As Long: lpCustColors As Long: Flags As Long: lCustData As Long: lpfnHook As Long: lpTemplateName As String: End Type
Type Log_Font: Height As Long: Width As Long: Escapement As Long: Orientation As Long: Weight As Long: Italic As Byte: Underline As Byte: StrikeOut As Byte: CharSet As Byte: OutPrecision As Byte: ClipPrecision As Byte: Quality As Byte: PitchAndFamily As Byte: FaceName(32 - 1) As Byte: End Type
Type Choose_Font: StructSize As Long: hwndOwner As Long: hDC As Long: LogFont As Long: PointSize As Long: Flags As Long: rgbColors As Long: CustData As Long: Hook As Long: TemplateName As String: hInstance As Long: Style As String: FontType As Integer: Missing_Alignment As Integer: SizeMin As Long: SizeMax As Long: End Type
Type OpenFileName: Size As Long: hWnd As Long: hInstance As Long: Filter As String: CustomFilter As String: MaxCustFilter As Long: FilterIndex As Long: File As String: MaxFile As Long: FileTitle As String: MaxFileTitle As Long: InitialDir As String: Title As String: Flags As Long: FileOffset As Integer: FileExtension As Integer: DefExt As String: CustData As Long: Hook As Long: TemplateName As String: End Type

'__Menu functions______________________________________________________________

Declare Function AppendMenu Lib "User32" Alias "AppendMenuA" (ByVal hMenu As Long, Optional ByVal uFlags As Long, Optional ByVal uIDNewItem As Long, Optional ByVal lpNewItem As String = vbNullString) As Long
Declare Function CreatePopupMenu Lib "User32" () As Long
Declare Function DestroyMenu Lib "User32" (ByVal hMenu As Long) As Long
Declare Function GetMenu Lib "User32" (ByVal hWnd As Long) As Long
Declare Function GetMenuItemInfo Lib "User32" Alias "GetMenuItemInfoA" (ByVal hMenu As Long, ByVal uItem As Long, ByVal fByPosition As Long, lpMenuItemInfo As MenuItemInfo) As Long
Declare Function GetSystemMenu Lib "User32" (ByVal hWnd As Long, ByVal bRevert As Long) As Long
Declare Function SetMenuInfo Lib "User32" (ByVal hMenu As Long, lpMenuInfo As MenuInfo) As Long
'MIM_STYLE=&H10 MNS_MODELESS=&H40000000 MIM_APPLYTOSUBMENUS=&H80000000
Declare Function TrackPopupMenu Lib "User32" (ByVal hMenu As Long, Optional ByVal uFlags As Long, Optional ByVal X As Long, Optional ByVal Y As Long, Optional ByVal nReserved As Long, Optional ByVal hWnd As Long, Optional ByVal lpRc As Long) As Long

Type MenuInfo: cbSize As Long: fMask As Long: dwStyle As Long: cyMax As Long: hbrBack As Long: dwContextHelpID As Long: dwMenuData As Long: End Type
Type MenuItemInfo: cbSize As Long: fMask As Long: fType As Long: fState As Long: wID As Long: hSubMenu As Long: hbmpChecked As Long: hbmpUnchecked As Long: dwItemData As Long: dwTypeData As String: CCH As Long: End Type

'__Display functions___________________________________________________________

Declare Function EnumDisplayDevices Lib "User32" Alias "EnumDisplayDevicesA" (ByVal lpDevice As String, ByVal iDevNum As Long, lpDisplayDevice As DisplayDevice, Optional ByVal dwFlags As Long) As Long
Declare Function EnumDisplaySettings Lib "User32" Alias "EnumDisplaySettingsA" (ByVal lpszDeviceName As String, ByVal iModeNum As Long, lpDevMode As DevMode) As Long
'ENUM_CURRENT_SETTINGS=-1 ENUM_REGISTRY_SETTINGS=-2
Declare Function GetMonitorInfo Lib "User32" Alias "GetMonitorInfoA" (ByVal hMonitor As Long, lpMi As MonitorInfo) As Long
Declare Function MonitorFromWindow Lib "User32" (ByVal hWnd As Long, Optional ByVal dwFlags As Long) As Long
'MONITOR_DEFAULTTONULL=0 MONITOR_DEFAULTTOPRIMARY=1 MONITOR_DEFAULTTONEAREST=2

Type MonitorInfo: Size As Long: Monitor As Rect: Work As Rect: Flags As Long: End Type
Type DisplayDevice: Size As Long: DeviceName As String * 32: DeviceString As String * 128: StateFlags As Long: DeviceID As String * 128: DeviceKey As String * 128: End Type
Type DevMode: DeviceName As String * 32: SpecVersion As Integer: DriverVersion As Integer: Size As Integer: DriverExtra As Integer: Fields As Long: PositionX As Long: PositionY As Long: DisplayOrientation As Long: DisplayFixedOutput As Long: Color As Integer: Duplex As Integer: YResolution As Integer: TTOption As Integer: Collate As Integer: FormName As String * 32: LogPixels As Integer: BitsPerPel As Long: PelsWidth As Long: PelsHeight As Long: DisplayFlags As Long: DisplayFrequency As Long: End Type

'__GDI+ functions______________________________________________________________

'Declare Function GetCurrentObject Lib "Gdi32" (ByVal hDC As Long, ByVal uObjectType As Long) As Long
''OBJ_PEN=1 OBJ_BRUSH=2 OBJ_PAL=5 OBJ_FONT=6 OBJ_BITMAP=7
'Declare Function GetDIBits Lib "Gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BitmapInfo, ByVal wUsage As Long) As Long
'Declare Function GetObject Lib "Gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
''Declare Function SetDIBits Lib "Gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long,  lpBits As Any, lpBI As BitmapInfo, ByVal wUsage As Long) As Long
'Declare Function StretchDIBits Lib "Gdi32" (ByVal hDC As Long, ByVal DestX As Long, ByVal DestY As Long, ByVal nDestWidth As Long, ByVal nDestHeight As Long, ByVal SrcX As Long, ByVal SrcY As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, lpBits As Any, lpBitsInfo As BitmapInfo, ByVal wUsage As Long, ByVal dwRop As Long) As Long

'Type RGBTriple: rgbBlue As Byte: rgbGreen As Byte: rgbRed As Byte: End Type
'Type Bitmap: bmType As Long: bmWidth As Long: bmHeight As Long: bmWidthBytes As Long: bmPlanes As Integer: bmBitsPixel As Integer: bmBits As Long: End Type
'Type BitmapInfoHeader: bmSize As Long: bmWidth As Long: bmHeight As Long: bmPlanes As Integer: bmBitCount As Integer: bmCompression As Long: bmSizeImage As Long: bmXPelsPerMeter As Long: bmYPelsPerMeter As Long: bmClrUsed As Long: bmClrImportant As Long: End Type
'Type BitmapInfo: bmHeader As BitmapInfoHeader: bmColors(255) As RGBTriple: End Type

Declare Function CLSIDFromString Lib "Ole32" (ByVal Str As String, ID As CLSID) As Long
Declare Function OleCreatePictureIndirect Lib "OlePro32" (pPictDesc As PictDesc, riid As CLSID, ByVal fOwn As Long, ppvObj As StdPicture) As Long
'Declare Sub OleCreatePictureIndirect Lib "OleAut32" (lpPictDesc As PictDesc, riid As CLSID, ByVal fOwn As Boolean, lplpvObj As StdPicture)
Declare Function GdiplusStartup Lib "GdiPlus" (Token As Long, InputBuf As GdiPlusStartupInput, Optional ByVal OutputBuf As Long) As Long
Declare Sub GdiplusShutdown Lib "GdiPlus" (ByVal Token As Long)
Declare Function GdipCreateBitmapFromHBITMAP Lib "GdiPlus" (ByVal hBmp As Long, ByVal hPal As Long, Bitmap As Long) As Long
'Declare Function GdipCreateHBITMAPFromBitmap Lib "GdiPlus" (ByVal BITMAP As Long, hBmp As Long, Optional ByVal Background As Long) As Long
Declare Function GdipCreateFromHDC Lib "GdiPlus" (ByVal hDC As Long, Graphics As Long) As Long
Declare Function GdipCreateHBITMAPFromBitmap Lib "GdiPlus" (ByVal Bitmap As Long, hBmp As Long, Optional ByVal Background As Long) As Long
Declare Function GdipDeleteGraphics Lib "GdiPlus" (ByVal Graphics As Long) As Long
Declare Function GdipDisposeImage Lib "GdiPlus" (ByVal Image As Long) As Long
Declare Function GdipDrawImageRectI Lib "GdiPlus" (ByVal Graphics As Long, ByVal Image As Long, ByVal X As Long, ByVal Y As Long, ByVal Width As Long, ByVal Height As Long) As Long
'Declare Function GdipGetImageEncodersSize Lib "GdiPlus" (NumEncoders As Long, Size As Long) As Long
'Declare Function GdipGetImageEncoders Lib "GdiPlus" (ByVal NumEncoders As Long, ByVal Size As Long, Encoders As Any) As Long
Declare Function GdipGetImageWidth Lib "GdiPlus" (ByVal Image As Long, Width As Long) As Long
Declare Function GdipGetImageHeight Lib "GdiPlus" (ByVal Image As Long, Height As Long) As Long
'Declare Function GdipGetImageThumbnail Lib "GdiPlus" (ByVal Image As Long, ByVal ThumbWidth As Long, ByVal ThumbHeight As Long, ThumbImage As Long, Optional ByVal Callback As Long = 0, Optional ByVal CallbackData As Long = 0) As Long
Declare Function GdipLoadImageFromFile Lib "GdiPlus" (ByVal FileName As String, Image As Long) As Long
Declare Function GdipSaveImageToFile Lib "GdiPlus" (ByVal Image As Long, ByVal FileName As String, CLSIDEncoder As CLSID, EncoderParams As Any) As Long

Type CLSID: Data1 As Long: Data2 As Integer: Data3 As Integer: Data4(0 To 7) As Byte: End Type
Type PictDesc: cbSizeOfStruct As Long: picType As Long: hGDIObj As Long: hPalOrXYExt As Long: Reserved As Long: End Type
Type GdiPlusStartupInput: GdiPlusVersion As Long: DebugEventCallback As Long: SuppressBackgroundThread As Long: SuppressExternalCodecs As Long: End Type

'__AVI file functions__________________________________________________________

Declare Function mmioStringToFOURCC Lib "WinMM" Alias "mmioStringToFOURCCA" (ByVal szStr As String, ByVal uFlags As Long) As Long
Declare Sub AVIFileInit Lib "AVIFil32" ()
Declare Sub AVIFileExit Lib "AVIFil32" ()
Declare Function AVIFileOpen Lib "AVIFil32" (ppFile As Long, ByVal szFile As String, ByVal uMode As Long, ByVal pClsIDHandler As Long) As Long
'OF_READ=0 OF_WRITE=1 OF_CREATE=&H1000 OF_SHARE_EXCLUSIVE=&H10 OF_SHARE_DENY_WRITE=&H20 OF_SHARE_DENY_READ=&H30 OF_SHARE_DENY_NONE=&H40

Declare Function AVIFileClose Lib "AVIFil32" Alias "AVIFileRelease" (ByVal pFile As Long) As Long
Declare Function AVISaveOptionsFree Lib "AVIFil32" (ByVal nStreams As Long, ppOptions As Long) As Long
Declare Function AVISaveOptions Lib "AVIFil32" (ByVal hWnd As Long, ByVal uiFlags As Long, ByVal nStreams As Long, ppAvi As Long, ppOptions As Long) As Long
'TRUE if user pressed OK, FALSE if cancel, or error if error
'ICMF_CHOOSE_KEYFRAME=1 ICMF_CHOOSE_DATARATE=2 ICMF_CHOOSE_PREVIEW=4 ICMF_CHOOSE_ALLCOMPRESSORS=8
Declare Function AVIFileCreateStream Lib "AVIFil32" Alias "AVIFileCreateStreamA" (ByVal pFile As Long, ppAvi As Long, Psi As AVIStreamInfo) As Long
Declare Function AVIMakeCompressedStream Lib "AVIFil32" (ppsCompressed As Long, ByVal psSource As Long, lpOptions As AVICompressOptions, ByVal pClsIDHandler As Long) As Long
'AVICOMPRESSF_INTERLEAVE=1 AVICOMPRESSF_DATARATE=2 AVICOMPRESSF_KEYFRAMES=4 AVICOMPRESSF_VALID=8
Declare Function AVIStreamSetFormat Lib "AVIFil32" (ByVal pAvi As Long, ByVal lPos As Long, lpFormat As BitmapInfoHeader, ByVal cbFormat As Long) As Long
Declare Function AVIStreamWrite Lib "AVIFil32" (ByVal pAvi As Long, ByVal lStart As Long, ByVal lSamples As Long, ByVal lpBuffer As Long, ByVal cbBuffer As Long, ByVal dwFlags As Long, plSampWritten As Long, plBytesWritten As Long) As Long
Declare Function AVIStreamClose Lib "AVIFil32" Alias "AVIStreamRelease" (ByVal pAvi As Long) As Long
Declare Function ICCompressorChoose Lib "Msvfw32" (ByVal hWnd As Long, ByVal uiFlags As Long, pvIn As Any, lpData As Any, Pc As CompVars, Optional ByVal lpszTitle As String = vbNullString) As Long

Type AVIRect: Left As Long: Top As Long: Right As Long: Bottom As Long: End Type
Type AVIStreamInfo: fccType As Long: fccHandler As Long: dwFlags As Long: dwCaps As Long: wPriority As Integer: wLanguage As Integer: dwScale As Long: dwRate As Long: dwStart As Long: dwLength As Long: dwInitialFrames As Long: dwSuggestedBufferSize As Long: dwQuality As Long: dwSampleSize As Long: rcFrame As AVIRect: dwEditCount As Long: dwFormatChangeCount As Long: szName As String * 64: End Type
Type AVICompressOptions: fccType As Long: fccHandler As Long: dwKeyFrameEvery As Long: dwQuality As Long: dwBytesPerSecond As Long: dwFlags As Long: lpFormat As Long: cbFormat As Long: lpParms As Long: cbParms As Long: dwInterleaveEvery As Long: End Type
Type CompVars: lSize As Long: lFlags As Long: hIC As Long: lFccType As Long: lFccHandler As Long: pBitmapInfoIn As Long: pBitmapInfoOut As Long: pBitsOut As Long: pBitsPrev As Long: lFrame As Long: lKey As Long: lDataRate As Long: lQuality As Long: lKeyCount As Long: pState As Long: lState As Long: End Type

'__DirectShow functions________________________________________________________

Declare Function DSGetVideoDevices Lib "Source\DShow" (ByVal strDevName As String, Optional pFilter As Long, Optional ByVal Count As Long) As Long 'pFilter As IFilterInfo
Declare Function DSGetAudioDevices Lib "Source\DShow" (ByVal strDevName As String, Optional pFilter As Long, Optional ByVal Count As Long) As Long 'pFilter As IFilterInfo
Declare Function DSStartStream Lib "Source\DShow" (M As DSParams, ByVal strVidDevice As String, ByVal strAudDevice As String, ByVal dwPort As Long, ByVal VidCount As Long, ByVal AudCount As Long, Optional ByVal Quality As Long, Optional ByVal strUrl As String, Optional ByVal hWnd As Long, Optional Left As Long, Optional Top As Long, Optional Width As Long, Optional Height As Long) As Long
Declare Function DSStopStream Lib "Source\DShow" (M As DSParams) As Long
Declare Function DSResizeWindow Lib "Source\DShow" (M As DSParams, ByVal Left As Long, ByVal Top As Long, ByVal Width As Long, ByVal Height As Long) As Long
Declare Function DSGetSnapshot Lib "Source\DShow" (M As DSParams, pBufferSize As Long, pBuffer As Byte, pBmi As BitmapInfo) As Long
Declare Function DSShowSettings Lib "Source\DShow" (ByVal Filter As Long, ByVal hWnd As Long, ByVal DeviceName As String) As Long
'Declare Function DSGetVideoFormat Lib "Source\DShow" (ByVal Filter As Long, Index As Long, Optional Width As Long, Optional Height As Long) As Long
'Declare Function DSSetVideoFormat Lib "Source\DShow" (ByVal Filter As Long, ByVal Index As Long) As Long
Declare Function DSGetError Lib "Source\DShow" (M As DSParams, ByVal Buffer As String, ByVal Length As Long) As Long

Type DSParams: pGraph As Long: pVidDeviceFilter As Long: pAudDeviceFilter As Long: pSmartTee As Long: pGrabFilter As Long: pGrab As Long: pVideo As Long: pVideoControl As Long: pWindow As Long: pMediaControl As Long: pEvent As Long: pWMASFWritter As Long: pFileSinkFilter As Long: pWriter2 As Long: pNetSink As Long: pProvider As Long: strError(255) As Byte: End Type

'--- Audio functions ----------------------------------------------------------

'Declare Function waveOutOpen Lib "WinMM" (hWaveOut As Long, ByVal uDeviceID As Long, pFormat As WaveFormatEx, ByVal dwCallback As Long, ByVal dwCallbackInstance As Long, ByVal dwFlags As Long) As Long
'Declare Function waveOutPrepareHeader Lib "WinMM" (ByVal hWaveOut As Long, lpWaveInHdr As WaveHdr, ByVal uSize As Long) As Long
'Declare Function waveOutUnprepareHeader Lib "WinMM" (ByVal hWaveOut As Long, lpWaveInHdr As WaveHdr, ByVal uSize As Long) As Long
'Declare Function waveOutWrite Lib "WinMM" (ByVal hWaveOut As Long, lpWaveOutHdr As WaveHdr, ByVal uSize As Long) As Long
'Declare Function waveOutClose Lib "WinMM" (ByVal hWaveOut As Long) As Long
'Declare Function waveOutReset Lib "WinMM" (ByVal hWaveOut As Long) As Long
''Declare Function waveOutPause Lib "WinMM" (ByVal hWaveOut As Long) As Long
'Declare Function waveOutGetPosition Lib "WinMM" (ByVal hWaveOut As Long, lpInfo As MMTime, ByVal uSize As Long) As Long
'TIME_MS=1 TIME_SAMPLES=2 TIME_BYTES=4 TIME_SMPTE=8

Declare Function waveInOpen Lib "WinMM" (lphWaveIn As Long, ByVal uDeviceID As Long, lpFormat As WaveFormatEx, ByVal dwCallback As Long, ByVal dwInstance As Long, ByVal dwFlags As Long) As Long
'CALLBACK_NULL=0 CALLBACK_WINDOW=&H10000 CALLBACK_FUNCTION=&H30000
'Sub waveInProc(ByVal hWi As Long, ByVal uMsg As Long, ByVal dwInstance As Long, ByVal dwParam1 As WaveHdr, ByVal dwParam2 As Long): End Sub
Declare Function waveInPrepareHeader Lib "WinMM" (ByVal hWaveIn As Long, lpWaveInHdr As WaveHdr, ByVal uSize As Long) As Long
Declare Function waveInUnprepareHeader Lib "WinMM" (ByVal hWaveIn As Long, lpWaveInHdr As WaveHdr, ByVal uSize As Long) As Long
Declare Function waveInAddBuffer Lib "WinMM" (ByVal hWaveIn As Long, lpWaveInHdr As WaveHdr, ByVal uSize As Long) As Long
Declare Function waveInStart Lib "WinMM" (ByVal hWaveIn As Long) As Long
Declare Function waveInStop Lib "WinMM" (ByVal hWaveIn As Long) As Long
Declare Function waveInClose Lib "WinMM" (ByVal hWaveIn As Long) As Long
Declare Function waveInReset Lib "WinMM" (ByVal hWaveIn As Long) As Long
'Declare Function waveInGetPosition Lib "WinMM" (ByVal hWaveIn As Long, lpInfo As MMTime, ByVal uSize As Long) As Long
Declare Function waveInGetNumDevs Lib "WinMM" () As Long
'Declare Function waveInGetDevCaps Lib "WinMM" Alias "waveInGetDevCapsA" (ByVal uDeviceID As Long, lpCaps As WaveInCaps, ByVal uSize As Long) As Long

Type WaveHdr: lpData As Long: dwBufferLength As Long: dwBytesRecorded As Long: dwUser As Long: dwFlags As Long: dwLoops As Long: lpNext As Long: Reserved As Long: End Type
'Type WaveFormat: wFormatTag As Integer: nChannels As Integer: nSamplesPerSec As Long: nAvgBytesPerSec As Long: nBlockAlign As Integer: End Type
Type WaveFormatEx: wFormatTag As Integer: nChannels As Integer: nSamplesPerSec As Long: nAvgBytesPerSec As Long: nBlockAlign As Integer: wBitsPerSample As Integer: cbSize As Integer: End Type
'Type WaveInCaps: wMid As Integer: wPid As Integer: vDriverVersion As Long: szPname As String * 32: dwFormats As Long: wChannels As Integer: End Type 'MAXPNAMELEN=32
Type MMTime: wType As Long: U As Long: X As Long: End Type

'__Joystick functions__________________________________________________________

'Declare Function joyGetNumDevs Lib "WinMM" () As Long
'Declare Function joyGetDevCaps Lib "WinMM" Alias "joyGetDevCapsA" (ByVal ID As Long, lpCaps As JoyCaps, ByVal uSize As Long) As Long
Declare Function joyGetPosEx Lib "WinMM" (ByVal uJoyID As Long, pJi As JoyInfoEx) As Long
'JOY_RETURNX=1 JOY_RETURNY=2 JOY_RETURNZ=4 JOY_RETURNR=8 JOY_RETURNU=&H10 JOY_RETURNV=&H20 JOY_RETURNPOV=&H40 JOY_RETURNBUTTONS=&H80 JOY_RETURNRAWDATA=&H100 JOY_RETURNPOVCTS=&H200 JOY_RETURNCENTERED=&H400 JOY_RETURNALL=&HFF
'JOYSTICKID1=0 JOYSTICKID2=1 ... JOYSTICKID16=15
'JOY_BUTTON1=1 JOY_BUTTON2=2 JOY_BUTTON3=4 ... JOY_BUTTON32=&H80000000
'JOYERR_BASE=160 JOYERR_NOERROR=0 JOYERR_UNPLUGGED=JOYERR_BASE+7=167
'Declare Function joySetCapture Lib "WinMM" (ByVal hWnd As Long, ByVal uID As Long, ByVal uPeriod As Long, ByVal bChanged As Long) As Long
'Declare Function joyReleaseCapture Lib "WinMM" (ByVal ID As Long) As Long
'MM_JOY1BUTTONDOWN=&H3B5 MM_JOY1BUTTONUP=&H3B7 MM_JOY1MOVE=&H3A0 MM_JOY1ZMOVE=&H3A2
'MM_JOY2BUTTONDOWN=&H3B6 MM_JOY2BUTTONUP=&H3B8 MM_JOY2MOVE=&H3A1 MM_JOY2ZMOVE=&H3A3
'Type JoyCaps: wMid As Integer: wPid As Integer: szPname As String * 32: wXmin As Integer: wXmax As Integer: wYmin As Integer: wYmax As Integer: wZmin As Integer: wZmax As Integer: wNumButtons As Integer: wPeriodMin As Integer: wPeriodMax As Integer: wRmin As Integer: wRmax As Integer: wUmin As Integer: wUmax As Integer: wVmin As Integer: wVmax As Integer: wCaps As Integer: wMaxAxes As Integer: wMNumAxes As Integer: wMaxButtons As Integer: szRegKey As String * 32: szOEMVxd As String * 260: End Type 'MAXPNAMELEN=32 MAX_JOYSTICKOEMVXDNAME=260
Type JoyInfoEx: dwSize As Long: dwFlags As Long: dwXpos As Long: dwYpos As Long: dwZpos As Long: dwRpos As Long: dwUpos As Long: dwVpos As Long: dwButtons As Long: dwButtonNumber As Long: dwPOV As Long: dwReserved1 As Long: dwReserved2 As Long: End Type

'__WMM World Magnetic Model functions__________________________________________

Declare Function WMMInit Lib "Source\Moon" (Optional ByVal WMMFile As String = "Wmm.cof") As Long
Declare Sub WMMCleanup Lib "Source\Moon" ()
Declare Sub WMMGetElements Lib "Source\Moon" (Data As WMMData)

Type WMMData                                                'WMM input and output structure
    Lon                     As Double                       'Longitude and Latitude (degrees)
    Lat                     As Double
    Alt                     As Double                       'Altitude above the ellipsoid HaE (kilometers)
    AltGeoid                As Double                       'Altitude above the EGM96 geoid (not used)
    UseGeoid                As Long
    H1                      As Long
    Year2                   As Long                         'Date Year/Month/Day (not used)
    Min2                    As Long
    Day2                    As Long
    H2                      As Long
    Year                    As Double                       'Date in decimal years
    Decl                    As Double                       'Angle between the magnetic field vector and true north, positive east (degrees)
    Incl                    As Double                       'Angle between the magnetic field vector and the horizontal plane, positive down (degrees)
    F                       As Double                       'Magnetic field strength (nanotesla)
    H                       As Double                       'Horizontal magnetic field strength (nanotesla)
    X                       As Double                       'Northern component of the magnetic field vector (nanotesla)
    Y                       As Double                       'Eastern component of the magnetic field vector (nanotesla)
    Z                       As Double                       'Downward component of the magnetic field vector (nanotesla)
    GV                      As Double                       'Grid variation (degrees)
    Decldot                 As Double                       'Yearly rate of change in declination (degrees per year)
    Incldot                 As Double                       'Yearly rate of change in inclination (degrees per year)
    Fdot                    As Double                       'Yearly rate of change in magnetic field strength (nanotesla per year)
    Hdot                    As Double                       'Yearly rate of change in horizontal field strength (nanotesla per year)
    Xdot                    As Double                       'Yearly rate of change in the northern component (nanotesla per year)
    Ydot                    As Double                       'Yearly rate of change in the eastern component (nanotesla per year)
    Zdot                    As Double                       'Yearly rate of change in the downward component (nanotesla per year)
    GVdot                   As Double                       'Yearly rate of chnage in grid variation (degrees per year)
    End Type

'__GFS Athmospheric Model functions____________________________________________

Declare Function GRIBUnPack Lib "Source\Moon" (Msg As Byte, Optional Num As Long, Optional Data As Single) As Long
Declare Sub GRIBFill Lib "Source\Moon" (ByVal Num As Long, Data As Single, Optional ByVal Value As Single)

'__muParser math parser functions______________________________________________

Declare Function GetError Lib "Source\Moon" (Optional ByVal Buffer As String = vbNullString, Optional ByVal Length As Long) As Long
Declare Sub ClearError Lib "Source\Moon" ()
Declare Function IsError Lib "Source\Moon" () As Long
Declare Sub AddVariable Lib "Source\Moon" (ByVal Name As String, Ptr As Double)
Declare Sub Parse Lib "Source\Moon" (ByVal Formula As String)
Declare Function Calculate Lib "Source\Moon" () As Double

'__CProPEP simulator functions_________________________________________________

Declare Sub InitCP Lib "Source\Moon" (Optional ByVal PropellantFile As String = "Propellant.dat", Optional ByVal ThermoFile As String = "Thermo.dat")
Declare Sub CleanUpCP Lib "Source\Moon" ()
Declare Sub ExecuteCP Lib "Source\Moon" (C As CPropellant)
Declare Sub NameCP Lib "Source\Moon" (ByVal Code As Long, ByVal Name As String)
Declare Sub CompCP Lib "Source\Moon" (ByVal Code As Long, ByVal Formula As String)
Declare Function CodeCP Lib "Source\Moon" (ByVal Name As String) As Long

Type CProperties                                            'Propellant properties
    P                       As Double                       'Pressure (atm)
    T                       As Double                       'Temperature (K)
    H                       As Double                       'Enthalpy (kJ/kg)
    U                       As Double                       'Internal energy (kJ/kg)
    G                       As Double                       'Gibbs free energy (kJ/kg)
    S                       As Double                       'Entropy (kJ/(kg)(K))
    M                       As Double                       'Molar mass (g/mol)
    DV_P                    As Double                       '(d ln(V) / d ln(P))t
    DV_T                    As Double                       '(d ln(V) / d ln(T))p
    Cp                      As Double                       'Specific heat (kJ/(kg)(K))
    Cv                      As Double                       'Specific heat (kJ/(kg)(K))
    Isex                    As Double                       'Isentropic exponent (gamma)
    Vson                    As Double                       'Sound speed (m/s)
    End Type

Type CPerformances                                          'Prepellant performances
    AeAt                    As Double                       'Exit area / Throat aera
    ADotM                   As Double                       'Exit area / mass flow rate (m/s/atm)
    CStar                   As Double                       'Characteristic velocity
    Cf                      As Double                       'Coefficient of thrust
    IspVac                  As Double                       'Specific impulse (vacuum)
    Isp                     As Double                       'Specific impulse
    End Type

Type CComp                                                  'Chemical component structure
    Mass                    As Double                       'Component mass in grams
    Code                    As Long                         'Component code number
    H                       As Long                         'Structure align
    End Type

Type CPropellant                                            'Chemical component list and parameters
    Prop(3 - 1)             As CProperties                  'Propellant properties
    Perf(3 - 1)             As CPerformances                'Propellant performances
    Density                 As Double                       'Propellant density in grams per cubic centimeter
    ChamberTemperature      As Double                       'Combustion Chamber Temperature in Kelvin
    ChamberPressure         As Double                       'Combustion Chamber Pressure in atmospheres
    ExitPressure            As Double                       'Exit Pressure in atmospheres or Area Ratio
    Mode                    As Long                         'Calculation mode: 0=SIMPLE_EQUILIBRIUM 1=FIND_FLAME_TEMPERATURE 2=FROZEN_PERFORMANCE 3=EQUILIBRIUM_PERFORMANCE
    ExitMode                As Long                         'Exit Pressure data type: 0=SUBSONIC_AREA_RATIO 1=SUPERSONIC_AREA_RATIO 2=PRESSURE
    Components              As Long                         'Number of chemical components
    H                       As Long                         'Structure align
    Comp(1 To 6)            As CComp                        'Component list
    End Type

'__OpenGL functions____________________________________________________________

'4x4 OpenGL Matrix:
'Visual Basic notation -> M(X,Y), reverse for C++ .. or unidimensional -> M(N)
'X = X*M(0,0) + Y*M(0,1) + Z*M(0,2) + M(0,3)    X = X*M(00) + Y*M(04) + Z*M(08) + *M(12)
'Y = X*M(1,0) + Y*M(1,1) + Z*M(1,2) + M(1,3)    Y = X*M(01) + Y*M(05) + Z*M(09) + *M(13)
'Z = X*M(2,0) + Y*M(2,1) + Z*M(2,2) + M(2,3)    Z = X*M(02) + Y*M(06) + Z*M(10) + *M(14)
'W = ?*M(3,0) + ?*M(3,1) + ?*M(3,2) + M(3,3)    W = ?*M(03) + ?*M(07) + ?*M(11) + *M(15)

Declare Function InitGL Lib "Source\Moon" (ByVal hDC As Long, ByVal hInstance As Long, Optional ByVal Flags As Long = -1) As Long '-1=Default &H25=GL+Win+DblBuf  &H28=GL+Bitmap
'Declare Sub CleanupGL Lib "Source\Moon" ()
'Declare Sub InitFont Lib "Source\Moon" (M As Long, ByVal W As Long, ByVal H As Long, ByVal C As Long, ByVal B As Long, XX As Long, SS As Long, ByVal hDC As Long)
'Declare Function wglChoosePixelFormatARB Lib "Source\Moon" (ByVal hDC As Long, piAttribIList As Long, pfAttribFList As Single, ByVal nMaxFormats As Long, piFormats As Long, nNumFormats As Long) As Long
Declare Function wglSwapIntervalEXT Lib "Source\Moon" (ByVal Interval As Long) As Long
Declare Sub glCompressedTexImage2DARB Lib "Source\Moon" (ByVal Target As Long, ByVal Level As Long, ByVal InternalFormat As Long, ByVal Width As Long, ByVal Height As Long, ByVal Border As Long, ByVal ImageSize As Long, Pixels As Byte)

Declare Function ChoosePixelFormat Lib "Gdi32" (ByVal hDC As Long, pPfd As PixelFormatDescriptor) As Long
Declare Function GetPixel Lib "Gdi32" (ByVal hDC As Long, ByVal X As Long, ByVal Y As Long) As Long
Declare Function SetPixelFormat Lib "Gdi32" (ByVal hDC As Long, ByVal iPixelFormat As Long, pPfd As PixelFormatDescriptor) As Long
Declare Function SwapBuffers Lib "Gdi32" (ByVal hDC As Long) As Long

Declare Function wglCopyContext Lib "OpenGL32" (ByVal hGlrc As Long, ByVal hGlrc As Long, ByVal uInt As Long) As Long
Declare Function wglCreateContext Lib "OpenGL32" (ByVal hDC As Long) As Long
'Declare Function wglCreateLayerContext Lib "OpenGL32" (ByVal hDC As Long, ByVal oglInt As Long) As Long
Declare Function wglDeleteContext Lib "OpenGL32" (ByVal hGlrc As Long) As Long
'Declare Function wglDescribeLayerPlane Lib "OpenGL32" (ByVal hDC As Long, ByVal Var1 As Long, ByVal Var2 As Long, ByVal Var3 As Long, lpLayerPlaneDescriptor As LayerPlaneDescriptor) As Long
Declare Function wglGetCurrentContext Lib "OpenGL32" () As Long
'Declare Function wglGetCurrentDC Lib "OpenGL32" () As Long
'Declare Function wglGetLayerPaletteEntries Lib "OpenGL32" (ByVal hDC As Long, ByVal Var1 As Long, ByVal Var2 As Long, ByVal Var3 As Long, iColorRef As Long) As Long
Declare Function wglGetProcAddress Lib "OpenGL32" (lpStr As String) As Long
Declare Function wglMakeCurrent Lib "OpenGL32" (ByVal hDC As Long, ByVal hGlrc As Long) As Long
'Declare Function wglRealizeLayerPalette Lib "OpenGL32" (ByVal hDC As Long, ByVal Var1 As Long, ByVal Var2 As Long) As Long
'Declare Function wglSetLayerPaletteEntries Lib "OpenGL32" (ByVal hDC As Long, ByVal Var1 As Long, ByVal Var2 As Long, ByVal Var3 As Long, iColorRef As Long) As Long
Declare Function wglShareLists Lib "OpenGL32" (ByVal hDC As Long, ByVal hGlrc As Long) As Long
'Declare Function wglSwapBuffers Lib "OpenGL32" (ByVal hDC As Long) As Long
'Declare Function wglSwapLayerBuffers Lib "OpenGL32" (ByVal hDC As Long, ByVal Var1 As Long) As Long
'Declare Function wglSwapMultipleBuffers Lib "OpenGL32" (ByVal Var1 As Long, Var2 As wglSwap) As Long
'Declare Function wglUseFontBitmaps Lib "OpenGL32" Alias "wglUseFontBitmapsA" (ByVal hDC As Long, ByVal Var1 As Long, ByVal Var2 As Long, ByVal Var3 As Long) As Long
'Declare Function wglUseFontOutlines Lib "OpenGL32" Alias "wglUseFontOutlinesA" (ByVal hDC As Long, ByVal Var1 As Long, ByVal Var2 As Long, ByVal Var3 As Long, ByVal Var3 As Single, ByVal Var4 As Single, ByVal Var5 As Long, lpGlyphmetricsFloat As Long) As Long

Declare Sub glBegin Lib "OpenGL32" (ByVal Mode As Long)
Declare Sub glBindTexture Lib "OpenGL32" (ByVal nTarget As Long, ByVal Texture As Long)
Declare Sub glBitmap Lib "OpenGL32" (ByVal nWidth As Long, ByVal Height As Long, ByVal xOrig As Single, ByVal yOrig As Single, ByVal xMove As Single, ByVal yMove As Single, nBitmap As Byte)
Declare Sub glBitmapX Lib "OpenGL32" Alias "glBitmap" (ByVal nWidth As Long, ByVal Height As Long, ByVal xOrig As Single, ByVal yOrig As Single, ByVal xMove As Single, ByVal yMove As Single, nBitmap As Long)
Declare Sub glBlendFunc Lib "OpenGL32" (ByVal sFactor As Long, ByVal dFactor As Long)
Declare Sub glCallList Lib "OpenGL32" (ByVal List As Long)
Declare Sub glCallLists Lib "OpenGL32" (ByVal N As Long, ByVal nType As Long, ByVal Lists As String)
Declare Sub glCallListsX Lib "OpenGL32" Alias "glCallLists" (ByVal N As Long, ByVal nType As Long, Lists As Byte)
Declare Sub glClear Lib "OpenGL32" (ByVal Mask As Long)
Declare Sub glClearColor Lib "OpenGL32" (ByVal Red As Single, ByVal Green As Single, ByVal Blue As Single, ByVal Alpha As Single)
'Declare Sub glColor3f Lib "OpenGL32" (ByVal Red As Single, ByVal Green As Single, ByVal Blue As Single)
'Declare Sub glColor3ub Lib "OpenGL32" (Optional ByVal R As Byte, Optional ByVal G As Byte, Optional ByVal B As Byte)
'Declare Sub glColor3ubv Lib "OpenGL32" (Optional V As Long = -1)
Declare Sub glColor4f Lib "OpenGL32" (ByVal Red As Single, ByVal Green As Single, ByVal Blue As Single, Optional ByVal Alpha As Single = 1)
'Declare Sub glColor4fv Lib "OpenGL32" (V As Single)
'Declare Sub glColor4ub Lib "OpenGL32" (Optional ByVal R As Byte, Optional ByVal G As Byte, Optional ByVal B As Byte, Optional ByVal A As Byte = 255)
Declare Sub glColor4ubv Lib "OpenGL32" (Optional V As Long = -1)
Declare Sub glColorMaterial Lib "OpenGL32" (ByVal Face As Long, ByVal Mode As Long)
Declare Sub glCullFace Lib "OpenGL32" (ByVal Mode As Long)
Declare Sub glDeleteLists Lib "OpenGL32" (ByVal List As Long, Optional ByVal Range As Long = 1)
Declare Sub glDeleteTextures Lib "OpenGL32" (ByVal N As Long, Textures As Long)
Declare Sub glDepthFunc Lib "OpenGL32" (ByVal Func As Long)
Declare Sub glDepthMask Lib "OpenGL32" (ByVal Flag As Byte)
'Declare Sub glDepthRange Lib "OpenGL32" (ByVal ZNear As Double, ByVal ZFar As Double)
Declare Sub glDisable Lib "OpenGL32" (ByVal Cap As Long)
Declare Sub glDrawPixels Lib "OpenGL32" (ByVal nWidth As Long, ByVal Height As Long, ByVal nFormat As Long, ByVal nType As Long, nPixels As Any)
Declare Sub glEnable Lib "OpenGL32" (ByVal Cap As Long)
Declare Sub glEnd Lib "OpenGL32" ()
Declare Sub glEndList Lib "OpenGL32" ()
'Declare Sub glFinish Lib "OpenGL32" ()
'Declare Sub glFlush Lib "OpenGL32" ()
Declare Sub glFrontFace Lib "OpenGL32" (ByVal Mode As Long)
'Declare Sub glFrustum Lib "OpenGL32" (ByVal Left As Double, ByVal Right As Double, ByVal Bottom As Double, ByVal Top As Double, ByVal ZNear As Double, ByVal ZFar As Double)
Declare Function glGenLists Lib "OpenGL32" (Optional ByVal Range As Long = 1) As Long
Declare Sub glGenTextures Lib "OpenGL32" (ByVal N As Long, Textures As Long)
'Declare Sub glGetDoublev Lib "OpenGL32" (ByVal pName As Long, Params As Double)
'Declare Function glGetError Lib "OpenGL32" () As Long
Declare Sub glGetFloatv Lib "OpenGL32" (ByVal pName As Long, Params As Single)
Declare Sub glGetIntegerv Lib "OpenGL32" (ByVal pName As Long, Params As Long)
Declare Function glGetString Lib "OpenGL32" (ByVal Name As Long) As Long
Declare Sub glLightfv Lib "OpenGL32" (ByVal Light As Long, ByVal pName As Long, Params As Single)
Declare Sub glLineStipple Lib "OpenGL32" (Optional ByVal Factor As Long = 1, Optional ByVal Pattern As Integer = -1)
'Declare Sub glLineWidth Lib "OpenGL32" (ByVal nWidth As Single)
Declare Sub glListBase Lib "OpenGL32" (ByVal nBase As Long)
Declare Sub glLoadIdentity Lib "OpenGL32" ()
'Declare Sub glLoadMatrixd Lib "OpenGL32" (M As Double)
Declare Sub glLoadMatrixf Lib "OpenGL32" (M As Single)
'Declare Sub glMaterialfv Lib "OpenGL32" (ByVal Face As Long, ByVal pName As Long, Params As Single)
Declare Sub glMatrixMode Lib "OpenGL32" (ByVal Mode As Long)
Declare Sub glMultMatrixd Lib "OpenGL32" (M As Double)
Declare Sub glMultMatrixf Lib "OpenGL32" (M As Single)
Declare Sub glNewList Lib "OpenGL32" (ByVal List As Long, ByVal Mode As Long)
Declare Sub glNormal3f Lib "OpenGL32" (ByVal NX As Single, ByVal NY As Single, ByVal NZ As Single)
'Declare Sub glOrtho Lib "OpenGL32" (ByVal Left As Double, ByVal Right As Double, ByVal Bottom As Double, ByVal Top As Double, ByVal ZNear As Double, ByVal ZFar As Double)
Declare Sub glPixelStorei Lib "OpenGL32" (ByVal pName As Long, ByVal Param As Long)
Declare Sub glPointSize Lib "OpenGL32" (ByVal lSize As Single)
'Declare Sub glPolygonMode Lib "OpenGL32" (ByVal Face As Long, ByVal Mode As Long)
'Declare Sub glPolygonStipple Lib "OpenGL32" (Optional Mask As Long = -1)
Declare Sub glPopAttrib Lib "OpenGL32" ()
Declare Sub glPopMatrix Lib "OpenGL32" ()
Declare Sub glPushAttrib Lib "OpenGL32" (ByVal Mask As Long)
Declare Sub glPushMatrix Lib "OpenGL32" ()
Declare Sub glRasterPos2i Lib "OpenGL32" (ByVal X As Long, ByVal Y As Long)
Declare Sub glRasterPos2f Lib "OpenGL32" (ByVal X As Single, ByVal Y As Single)
Declare Sub glRasterPos3i Lib "OpenGL32" (ByVal X As Long, ByVal Y As Long, ByVal Z As Long)
Declare Sub glRasterPos3f Lib "OpenGL32" (ByVal X As Single, ByVal Y As Single, ByVal Z As Single)
Declare Sub glReadPixels Lib "OpenGL32" (ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal Height As Long, ByVal Format As Long, ByVal nType As Long, nPixels As Any)
Declare Sub glRotatef Lib "OpenGL32" (ByVal Angle As Single, ByVal X As Single, ByVal Y As Single, ByVal Z As Single)
Declare Sub glScalef Lib "OpenGL32" (ByVal X As Single, ByVal Y As Single, ByVal Z As Single)
Declare Sub glTexCoord2f Lib "OpenGL32" (ByVal S As Single, ByVal T As Single)
Declare Sub glTexEnvi Lib "OpenGL32" (ByVal nTarget As Long, ByVal pName As Long, ByVal Param As Long)
Declare Sub glTexImage2D Lib "OpenGL32" (ByVal nTarget As Long, ByVal Level As Long, ByVal InternalFormat As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal Border As Long, ByVal Format As Long, ByVal nType As Long, nPixels As Any)
Declare Sub glTexParameteri Lib "OpenGL32" (ByVal nTarget As Long, ByVal pName As Long, ByVal Param As Long)
Declare Sub glTranslatei Lib "OpenGL32" (ByVal X As Long, ByVal Y As Long, ByVal Z As Long)
Declare Sub glTranslatef Lib "OpenGL32" (ByVal X As Single, ByVal Y As Single, ByVal Z As Single)
Declare Sub glVertex2f Lib "OpenGL32" (ByVal X As Single, ByVal Y As Single)
Declare Sub glVertex3f Lib "OpenGL32" (ByVal X As Single, ByVal Y As Single, ByVal Z As Single)
'Declare Sub glVertex3fv Lib "OpenGL32" (V As Single)
Declare Sub glVertex2i Lib "OpenGL32" (ByVal X As Long, ByVal Y As Long)
Declare Sub glViewport Lib "OpenGL32" (ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal Height As Long)

'Sub glCompressedTexImage2DARB(ByVal Target As Long, ByVal Level As Long, ByVal InternalFormat As Long, ByVal Width As Long, ByVal Height As Long, ByVal Border As Long, ByVal ImageSize As Long, Pixels As Byte)
'    End Sub

''ReplaceEntryWithDLLFunction 8, wglGetProcAddress("glCompressedTexImage2DARB")

'Function ReplaceEntryWithDLLFunction(ByVal Entry As Long, ByVal lpFn As Long) As Long
'    Dim lFuncAddr As Long, lpVtableHead As Long, lpfnAddr As Long, lOldProtect As Long
'    If lpFn = 0 Then Exit Function
'    RtlMoveMemory lpVtableHead, ByVal ObjPtr(Me), 4
'    lpfnAddr = lpVtableHead + (Entry - 1) * 4
'    RtlMoveMemory lFuncAddr, ByVal lpfnAddr, 4
'    If lFuncAddr <> lpFn Then
'        VirtualProtect lFuncAddr, 10, &H40&, lOldProtect 'PAGE_EXECUTE_READWRITE=&H40
'        RtlMoveMemory ByVal lFuncAddr, &HB824048F, 4 '8F04:Pop (ESP)  24B8:Mov EAX,lFuncAddr
'        RtlMoveMemory ByVal lFuncAddr + 4, lpFn, 4 'lFuncAddr
'        RtlMoveMemory ByVal lFuncAddr + 8, &HE0FF, 2 'FFE0:Jmp EAX
'        VirtualProtect lFuncAddr, 10, lOldProtect, lOldProtect
'        End If
'    ReplaceEntryWithDLLFunction = lFuncAddr
'    End Function

Type FontX                                                  'Text font structure
    List                    As Long
    Tex                     As Long
    Color                   As Long
    BackColor               As Long
    XX(256 - 1)             As Long
    X                       As Long
    Y                       As Long
    End Type

Type TextureX                                               'Texture structure
    Poly                    As Long
    Flat                    As Long
    W                       As Long
    H                       As Long
    List()                  As Long
    End Type

Type PictureX                                               'Bitmap structure
    X                       As Long
    Y                       As Long
    W                       As Long
    H                       As Long
    List                    As Long
    Visible                 As Boolean
    End Type

Type MatrixX                                                'Rotation matrix of 4x4 elements
    XX                      As Single
    XY                      As Single
    XZ                      As Single
    XW                      As Single
    YX                      As Single
    YY                      As Single
    YZ                      As Single
    YW                      As Single
    ZX                      As Single
    ZY                      As Single
    ZZ                      As Single
    ZW                      As Single
    WX                      As Single
    WY                      As Single
    WZ                      As Single
    WW                      As Single
    End Type

Type IntXYZW                                                'Matrix of 1x4 integer elements
    X                       As Long
    Y                       As Long
    Z                       As Long
    W                       As Long
    End Type

Type FloatXYZW                                              'Matrix of 1x4 floating point elements
    X                       As Single
    Y                       As Single
    Z                       As Single
    W                       As Single
    End Type

Type FloatRGBA                                              'Matrix of 1x4 floating point elements
    R                       As Single
    G                       As Single
    B                       As Single
    A                       As Single
    End Type

Type PixelFormatDescriptor                                  'Window pixel descriptor
    nSize                   As Integer
    nVersion                As Integer
    dwFlags                 As Long
    iPixelType              As Byte
    cColorBits              As Byte
    cRedBits                As Byte
    cRedShift               As Byte
    cGreenBits              As Byte
    cGreenShift             As Byte
    cBlueBits               As Byte
    cBlueShift              As Byte
    cAlphaBits              As Byte
    cAlphaShift             As Byte
    cAccumBits              As Byte
    cAccumRedBits           As Byte
    cAccumGreenBits         As Byte
    cAccumBlueBits          As Byte
    cAccumAlphaBits         As Byte
    cDepthBits              As Byte
    cStencilBits            As Byte
    cAuxBuffers             As Byte
    iLayerType              As Byte
    bReserved               As Byte
    dwLayerMask             As Long
    dwVisibleMask           As Long
    dwDamageMask            As Long
    End Type

Type BmpHeader                                              'BMP file format header
    FileSize                As Long
    Reserved                As Long
    OffBits                 As Long
    Size                    As Long
    Width                   As Long
    Height                  As Long
    Planes                  As Integer
    BitCount                As Integer
    Compression             As Long
    SizeImage               As Long
    XPelsPerMeter           As Long
    YPelsPerMeter           As Long
    ClrUsed                 As Long
    ClrImportant            As Long
    End Type

Type DDColorKey                                             'DDS file format color space
    dwColorSpaceLowValue    As Long
    dwColorSpaceHighValue   As Long
    End Type

Type DDPixelFormat                                          'DDS file format pixel descriptor
    dwSize                  As Long
    dwFlags                 As Long
    dwFourCC                As String * 4
    dwRGBBitCount           As Long
    dwRBitMask              As Long
    dwGBitMask              As Long
    dwBBitMask              As Long
    dwRGBAlphaBitMask       As Long
    End Type

Type DDSCaps                                                'DDS file format capabilities
    lCaps                   As Long
    lCaps2                  As Long
    lCaps3                  As Long
    lCaps4                  As Long
    End Type

Type DDSurfaceDesc                                          'DDS file format surface descriptor
    dwSize                  As Long
    dwFlags                 As Long
    dwHeight                As Long
    dwWidth                 As Long
    dwLinearSize            As Long
    dwBackBufferCount       As Long
    dwMipMapCount           As Long
    dwAlphaBitDepth         As Long
    dwReserved              As Long
    lpSurface               As Long
    ddckCKDestOverlay       As DDColorKey
    ddckCKDestBlt           As DDColorKey
    ddckCKSrcOverlay        As DDColorKey
    ddckCKSrcBlt            As DDColorKey
    ddpfPixelFormat         As DDPixelFormat
    DDSCaps                 As DDSCaps
    dwReserved2             As Long
    End Type

Public Const pfd_DoubleBuffer = 1, pfd_Draw_To_Window = 4, pfd_Draw_To_Bitmap = 8, _
    pfd_Support_OpenGL = &H20&, pfd_Type_RGBA = 0, pfd_Main_Plane = 0, _
    wgl_Draw_To_Window_ARB = &H2001&, wgl_Draw_To_Bitmap_ARB = &O2002&, _
    wgl_Support_OpenGL_ARB = &H2010&, wgl_Acceleration_ARB = &H2003&, _
    wgl_Double_Buffer_ARB = &H2011&, wgl_Sample_Buffers_ARB = &O2041&, _
    wgl_Samples_ARB = &H2042&, wgl_Full_Acceleration_ARB = &H2027&, _
    wgl_Color_Bits_ARB = &H2014&, _
    wgl_Red_Bits_ARB = &H2015&, wgl_Green_Bits_ARB = &H2017&, _
    wgl_Blue_Bits_ARB = &H2019&, wgl_Alpha_Bits_ARB = &H201B&, _
    wgl_Depth_Bits_ARB = &H2022&, wgl_Stencil_Bits_ARB = &H2023&
Public Const _
    gl_All_Attrib_Bits = &HFFFFF, _
    gl_Compressed_Rgba_S3TC_DXT1_Ext = &H83F1&, _
    gl_Compressed_Rgba_S3TC_DXT3_Ext = &H83F2&, _
    gl_Compressed_Rgba_S3TC_DXT5_Ext = &H83F3&, _
    gl_Modelview = &H1700&, gl_Modelview_Matrix = &HBA6&, _
    gl_Projection = &H1701&, gl_Projection_Matrix = &HBA7&, _
    gl_Points = 0, gl_Lines = 1, gl_Line_Strip = 3, gl_Triangles = 4, _
    gl_Triangle_Strip = 5, gl_Triangle_Fan = 6, gl_Quads = 7, gl_Quad_Strip = 8, _
    gl_Color_Buffer_Bit = &H4000&, gl_Depth_Buffer_Bit = &H100&, _
    gl_Zero = 0, gl_One = 1, gl_Src_Alpha = &H302&, gl_Dst_Alpha = &H304&, _
    gl_One_Minus_Src_Alpha = &H303&, gl_Src_Alpha_Saturate = &H308&, gl_Blend = &HBE2&
Public Const gl_Point_Smooth = &HB10&, gl_Line_Smooth = &HB20&, gl_Polygon_Smooth = &HB41&, _
    gl_Line_Stipple = &HB24&, gl_Depth_Test = &HB71&, gl_MultiSample_Arb = &H809D&, _
    gl_Less = &H201&, gl_LEqual = &H203&, gl_Greater = &H204&, gl_GEqual = &H206&, gl_Always = &H207&, _
    gl_Current_Raster_Position = &HB07&, gl_Current_Raster_Position_Valid = &HB08&, _
    gl_Current_Raster_Distance = &HB09&, gl_Lighting = &HB50&, gl_Light0 = &H4000&, _
    gl_Ambient = &H1200&, gl_Diffuse = &H1201&, gl_Position = &H1203&, gl_Ambient_And_Diffuse = &H1602&, _
    gl_Color_Material = &HB57&, gl_Depth_Component = &H1902&, gl_Depth_Range = &HB70&, _
    gl_Normalize = &HBA1&, gl_Point = &H1B00&, gl_Line = &H1B01&, gl_Fill = &H1B02&, _
    gl_Front = &H404&, gl_Back = &H405&, gl_Front_And_Back = &H408&, gl_Texture_2d = &HDE1&, _
    gl_Unsigned_Byte = &H1401&, gl_Unsigned_Short = &H1403&, _
    gl_Unsigned_Int = &H1405&, gl_Int = &H1404&, gl_Float = &H1406, _
    gl_Rgb = &H1907&, gl_Rgba = &H1908&, gl_Bgr_Ext = &H80E0&, gl_Bgra_Ext = &H80E1&, _
    gl_Compressed_Rgb_Arb = &H84ED&, gl_Compressed_Rgba_Arb = &H84EE&
Public Const gl_Texture_Mag_Filter = &H2800&, gl_Texture_Min_Filter = &H2801&, _
    gl_Nearest_Mipmap_Nearest = &H2700&, gl_Linear_Mipmap_Nearest = &H2701&, _
    gl_Nearest_Mipmap_Linear = &H2702&, gl_Linear_Mipmap_Linear = &H2703&, _
    gl_Nearest = &H2600&, gl_Linear = &H2601&, gl_Cull_Face = &HB44&, _
    gl_CW = &H900&, gl_CCW = &H901&, gl_Texture_Env = &H2300&, gl_Texture_Env_Mode = &H2200&, _
    gl_Modulate = &H2100&, gl_Decal = &H2101&, gl_Unpack_Alignment = &HCF5&, _
    gl_Texture_Wrap_S = &H2802&, gl_Texture_Wrap_T = &H2803&, _
    gl_Clamp = &H2900&, gl_Repeat = &H2901&, gl_Compile = &H1300&, gl_Current_Bit = 1, _
    gl_Vendor = &H1F00&, gl_Renderer = &H1F01&, gl_Version = &H1F02&, gl_Extensions = &H1F03&

    'Public IsDLL                As Boolean                  'OpenGL DLL is present
    Public Antialias            As Long                     'Antialias mode
    Public Near                 As Double                   'Z near
    Public Far                  As Double                   'Z far
    Public FontTextures         As Boolean                  'Use textures in text
    Public TextFont             As FontX                    'Viewer labels font
    Public ReportFont           As FontX                    'Report tool font
    Public ModelFont            As FontX                    '3D Graph labels font
    Public Logo                 As PictureX                 'Logo object
    Public Flare                As PictureX                 'Sun flare object
    Public Crosshair            As PictureX                 'Sensor crosshair object
    Public InfoWeb              As PictureX                 'Info icon object
    Public InfoList             As PictureX                 'Search icon object
    Public Cursor               As PictureX                 'Selection cursor object
    Public SkyList              As Long                     'Sky object
    Public CompassList          As Long                     'Compass circle object
    Public CompassFlatList      As Long                     'Compass circle object in flat mode
    Public GridList             As Long                     'Compass grid object
    Public SphereList           As Long                     'Sphere object
    Public WhiteDot             As Long                     'Big white dot icon
    Public GreenDot             As Long                     'Big green dot icon
    Public RedDot               As Long                     'Big red dot icon
    Public SmallGreenDot        As Long                     'Small green dot icon
    Public SmallRedDot          As Long                     'Small red dot icon
    Public ShipList             As Long                     'Ship object pointer
    Public StageList            As Long                     'Stages objects
    Public StageEmptyList       As Long                     'Empty stages objects
    Public HoverList            As Long                     'Hover object
    Public SatelliteList        As Long                     'Satellite object
    Public RoverList            As Long                     'Rover object
    Public RoverModel           As String                   'Rover model
    Public StationList          As Long                     'Ground station object
    Public StationModel         As String                   'Station model
    Public TrackList            As Long                     'Tracking object
    Public TrackModel           As String                   'Tracking object model
    Public AirspacesList        As Long                     'Airspaces object
    Public AirspacesFlatList    As Long                     'Airspaces object in flat mode
    Public AreasList            As Long                     'Populated areas list
    Public AreasFlatList        As Long                     'Populated areas list in flat mode
    Public LandList             As Long                     'Land boundaries list
    Public LandFlatList         As Long                     'Land boundaries list in flat mode
    Public SeaList              As Long                     'Water boundaries list
    Public SeaFlatList          As Long                     'Water boundaries list in flat mode
    Public Textures             As Collection               'Model textures list
    Public RasterI              As IntXYZW                  'Point raster position
    Public RasterF              As FloatXYZW                'Point raster position
    Public TargetPos            As FloatXYZW                'Target raster position
    Public OpenGLName           As String                   'OpenGL implementation name
    Public DisplayName          As String                   'OpenGL implementation name

'__COB loader structures_______________________________________________________

Type Vertex                                                 '2 vertices index list structure
    W()                     As Long                         'World Vertex list
    T()                     As Long                         'Texture Vertex list
    End Type

Type Vertex2                                                '2 single vertex structure
    S                       As Single                       'Vertex X, H, S
    T                       As Single                       'Vertex Y, V, T
    End Type

Type Vertex3                                                '3 single vertex structure
    X                       As Single                       'Vertex X
    Y                       As Single                       'Vertex Y
    Z                       As Single                       'Vertex Z
    End Type

Type Vertex8                                                '8 single vertex structure
    Time                    As Single                       'Time
    Acc                     As Single                       'Acceleration
    T                       As Single                       'T
    C                       As Single                       'C
    B                       As Single                       'B
    Orientation             As Single                       'Orientation
    Position(2)             As Single                       'Position vector
    End Type

Type ModelHeader                                            'Material header structure
    ID                      As Long                         'Single Identifier
    Parent                  As Long                         'Parent's Id
    Size                    As Long                         'Group size
    End Type

Type ModelName                                              'Name, center and axis structure
    Name                    As String                       'Name
    C(2)                    As Single                       'Center vector
    A(8)                    As Single                       'Axis matrix 3 by 3
    T(15)                   As Single                       'Transform matrix 4 by 4
    End Type

Type ModelPhAn                                              'Physics and Anatomy structure
    H                       As ModelHeader                  'Id, Parent, Size
    G(2)                    As Single                       'CentreOfGravity
    M(2)                    As Single                       'InitMotion
    O(2)                    As Single                       'InitRorationOrig
    V(2)                    As Single                       'InitRorationVect
    A(2)                    As Single                       'AccMotion
    B(2)                    As Single                       'AccRotationOrig
    C(2)                    As Single                       'AccRotationVect
    Weight                  As Single                       'Weight
    Elasticity              As Single                       'Elasticity
    EnviResist              As Single                       'EnviResist
    Friction                As Single                       'Friction
    Active                  As Single                       'Active
    NumFxPoints             As Single                       'NumFxPoints
    End Type

Type ModelChan                                              'Changes structure
    H                       As ModelHeader                  'Id, Parent, Size
    Name                    As String                       'Name
    Type                    As String                       'Type: Rotate, Move
    Flags                   As Long                         'Flags
    Frames                  As Long                         'Frames
    T()                     As Vertex8                      'Time, acc, t, c, b, orientation/position vector
    End Type

Type ModelLight                                             'Light structure
    H                       As ModelHeader                  'Id, Parent, Size
    N                       As ModelName                    'Name, center axis and Transform matrix
    Local                   As String                       'Local NoShadows
    R                       As Single                       'RGBA color
    G                       As Single                       'RGBA color
    B                       As Single                       'RGBA color
    ConeAngle               As Single                       'Conde angle
    InnerAngle              As Single                       'Inner angle
    FallOff                 As Single                       'Falloff
    End Type

Type ModelUnits                                             'Units structure
    H                       As ModelHeader                  'Id, Parent, Size
    Units                   As Long                         'Units
    End Type

Type ModelMaterial                                          'Material structure
    H                       As ModelHeader                  'Id, Parent, Size
    Mat                     As Long                         'Mat#
    Shader                  As String                       'Shader: Flat, Phong, Metal
    Facet                   As String                       'Facet: Faceted, AutoFacet, Smooth
    R                       As Single                       'RGBA color
    G                       As Single                       'RGBA color
    B                       As Single                       'RGBA color
    A                       As Single                       'RGBA alpha (Transparent=0)
    Ka                      As Single                       'Ambient glow (White body=1)
    Ks                      As Single                       'Shininess (Reflector=1)
    Exp                     As Single                       'Roughness exponent (Pulished=1)
    Ior                     As Single                       'Index of refraction (Mirror=1)
    Texture                 As String                       'Texture path
    glTexture               As Long                         'OpenGL texture index
    OffsetH                 As Single                       'Offset in H
    OffsetV                 As Single                       'Offset in V
    RepeatsH                As Single                       'Repeats in H
    RepeatsV                As Single                       'Repeats in V
    Flags                   As Long                         'Flags TBD
    End Type

Type PolH                                                   'PolH object structure
    H                       As ModelHeader                  'Id, Parent, Size
    N                       As ModelName                    'Name, center axis and Transform matrix
    W()                     As Vertex3                      'World Vertex list
    WorldVertices           As Long                         'World Vertices in this PolH
    T()                     As Vertex2                      'Texture Vertex list
    TextureVertices         As Long                         'Texture Vertices in this PolH
    F()                     As Vertex                       'List of Vertex per face
    Faces                   As Long                         'Faces in this PolH
    G()                     As Long                         'Flags per face
    L()                     As Long                         'Mats per face
    FaceVerts()             As Long                         'Verts per face
    U                       As ModelUnits                   'Units
    M()                     As ModelMaterial                'Mat list
    Materials               As Long                         'Mats in this PolH
    A                       As ModelPhAn                    'PhAn
    End Type

Type Grou                                                   'Object group structure
    H                       As ModelHeader                  'Id, Parent, Size
    N                       As ModelName                    'Name, center axis and Transform matrix
    P()                     As PolH                         'PolH list
    PolHs                   As Long                         'Number of PolH in this group
    U                       As ModelUnits                   'Units
    A                       As ModelPhAn                    'PhAn
    C()                     As ModelChan                    'Chan list
    Chans                   As Long                         'Number of Chan in this group
    L()                     As ModelLight                   'Light list
    Lights                  As Long                         'Number of Lights in this group
    End Type

Type Model                                                  'Model or Object structure
    G()                     As Grou                         'Group list
    Groups                  As Long                         'Number of groups
    Name                    As String                       'Name
    glList                  As Long                         'glList handler
    End Type

'__Simulation types, structures and variables__________________________________

Enum PlayMode                                               'Simulation loop / record mode
    Active = -1: Paused: Toggle: Start: Pause: Restart: Reset: Settings: NextStep
:   Forward: Backward: Faster: Slower
    End Enum

Enum Phase                                                  'Mission phase
    Crashed = -2: Landed: Launch: TakeOff: Landing: Revert: Deployed: Transfer: StandBy: Aborted
    Controlled = Revert
    End Enum

Enum ZoneType                                               'Zone types
    VOID = 0: Proximity: Ionosphere: Atmosphere
    End Enum

Enum PlanetType                                             'Planet types
    ClassStar = 0: ClassGasGiant: ClassPlanet: ClassDwarfPlanet: ClassMoon: ClassPlanetoid
    End Enum

Enum TargetType                                             'Target object
    ToNext = -1: ToNone: ToSimul: ToTactic: ToOnBoard: ToShip: ToEarth: ToMoon: ToSun: ToAir
:   ToMercury: ToVenus: ToMars: ToJupiter: ToSaturn: ToUranus: ToNeptune: ToPluto
:   ToObject
    End Enum

Enum ObjectType                                             'Object types
    TypeAll = -1: TypeNone = 0: TypePlanet: TypeMoon: TypeComet: TypeAsteroid: TypeStar
:   TypeSatellite: TypeStation: TypeLocation: TypeAirspace: TypeAirport: TypeSeaport
:   TypeCity: TypeGhost: TypeShip
:   TypeLast
    End Enum

Enum PlaneType                                              'Main plane types
    Ecliptic = 0: Equatorial: Orbital
    End Enum

Enum LinkType                                               'Link mode
    Disabled = 0: TrackLink: RoverLink: ControlLink: TerminalLink: GraphLink
    End Enum

Enum LinkID                                                 'Link object type
    IDLink = 0: IDStation: IDRover: IDLast
    End Enum

Enum LinkDevice                                             'Link port type
    LinkNone = 0: LinkCOM: LinkAudio: LinkAPRS: LinkServer: LinkSelect: LinkFixed
    End Enum

Enum CameraID
    MainID = 0: ViewID: RoverID: StageID: GraphID
    End Enum

Enum FileType
    FileNone = 0: FilePreset: FilePath: FileKML: FileLog: FileTLE: FileCOB: FileNMEA: FileCSV
:   FileImage: FileVideo: FileAll
    End Enum

Enum BurnMode
    BurnModeCursor = -4: BurnModeLog = -3: BurnModeFast = -2: BurnModeSlow = -1
:   BurnModeGraph = 0: BurnModeCurve = 1: BurnModeEngine = 2
    End Enum

Enum ThemeStyle
    ThemeNone = -1: ThemeDefault: ThemeBlack: ThemeWhite: ThemeVista: ThemeWinXP: ThemeWin98
    End Enum

Enum PanelStyle
    StyleDefault = -2: StyleToggle: StyleWhite: StyleBlack: StyleBasic
    End Enum

Enum Tools                                                  'Render tools
    ToolPath = 1:             ToolSnap = 2:              ToolTime = 4:            ToolPathAll = 8
    ToolStage = &H10&:        ToolInfo = &H20&:          ToolLaunch = &H40&:      ToolRanges = &H80&
    ToolVectors = &H100&:     ToolSensor = &H200&:       ToolTrajectory = &H400&: ToolAltimeter = &H800&
    ToolGravity = &H1000&:    ToolAtmosphere = &H2000&:  ToolTerrain = &H4000&:   ToolGrid = &H8000&
    ToolLocations = &H10000:  ToolStations = &H20000:    ToolPlanets = &H40000:   ToolStars = &H80000
    ToolHidden = &H100000:    ToolPoints = &H200000:     ToolSmooth = &H400000:   ToolAxes = &H800000
    ToolTextures = &H1000000: ToolReport = &H2000000:    ToolAirways = &H4000000: ToolAsteroids = &H8000000
    ToolComets = &H10000000:  ToolMagnetic = &H20000000: ToolMoons = &H40000000:  ToolSatellites = &H80000000
    ToolMinorPlanets = ToolAsteroids Or ToolComets Or ToolMoons
    ToolOptions = ToolTime Or ToolPathAll Or ToolInfo Or ToolHidden Or ToolPoints Or ToolSmooth Or ToolTextures
    ToolDefault = ToolPath Or ToolSnap Or ToolTime Or ToolStage Or ToolInfo Or ToolLaunch Or ToolRanges Or ToolVectors Or ToolSensor Or ToolGrid Or ToolStars Or ToolSmooth Or ToolTextures
    ToolNone = ToolHidden Or ToolSmooth Or ToolTerrain Or ToolTextures
    End Enum

Enum ToolsX                                                 'Render ranges, vectors and sensor tools
    ToolOrbit = 1:             ToolIonosphere = 2:         ToolApsis = 4:           ToolBraking = 8
    ToolOrbits = &H10&:        ToolObjects = &H20&:        ToolAirDrag = &H40&:     ToolAltitude = &H80&
    ToolReference = &H100&:    ToolGroundSpeed = &H200&:   ToolNetGravity = &H400&: ToolSpeed = &H800&
    ToolVertSpeed = &H1000&:   ToolCrosshair = &H2000&:    ToolFOV = &H4000&:       ToolLabels = &H8000&
    ToolPlane = &H10000:       ToolMagneticLine = &H20000: ToolDeviation = &H40000: ToolTrace = &H80000
    ToolCompass = &H100000:    ToolCompassGrid = &H200000: ToolGlow = &H400000:     ToolGhosts = &H800000
    ToolDistances = &H1000000: ToolBoundaries = &H2000000: ToolFOVArea = &H4000000: ToolFOVTime = &H8000000
    ToolDefaultX = ToolOrbit Or ToolIonosphere Or ToolApsis Or ToolBraking Or ToolObjects Or ToolOrbits Or ToolAirDrag Or ToolAltitude Or ToolReference Or ToolGroundSpeed Or ToolSpeed Or ToolCrosshair Or ToolFOV Or ToolLabels Or ToolFOVArea Or ToolFOVTime Or ToolGlow
    ToolNoneX = ToolCompassGrid Or ToolGlow Or ToolGhosts Or ToolDistances
    ToolRangesAll = ToolOrbit Or ToolIonosphere Or ToolApsis Or ToolBraking Or ToolObjects Or ToolOrbits Or ToolPlane
    ToolVectorsAll = ToolAirDrag Or ToolReference Or ToolAltitude Or ToolGroundSpeed Or ToolMagneticLine Or ToolNetGravity Or ToolSpeed Or ToolVertSpeed
    ToolSensorsAll = ToolCrosshair Or ToolDeviation Or ToolFOV Or ToolLabels Or ToolTrace Or ToolFOVArea Or ToolFOVTime
    End Enum

Enum ToolsL                                                 'Tools labels
    AirwaysLabels = 1:        AsteroidsLabels = 2:      CometsLabels = 4:    MoonsLabels = 8
    PlanetsLabels = &H10&:    SatellitesLabels = &H20&: StarsLabels = &H40&: StationsLabels = &H80&
    LocationsLabels = &H100&
    MinorPlanetsLabels = AsteroidsLabels Or CometsLabels Or MoonsLabels
    LabelDefault = AirwaysLabels Or PlanetsLabels Or MinorPlanetsLabels Or SatellitesLabels Or StationsLabels Or LocationsLabels
    LabelNone = 0
    End Enum

Enum LayersType                                            'Layers types
    LayersLower = 1:           LayersRNAV = 2:           LayersAirspaces = 4:       LayersAirports = 8
    LayersSeaports = &H10&:    LayersCities = &H20&:     LayersPlaces = &H40&:      LayersAreas = &H80&
    LayersLand = &H100&:       LayersSea = &H200&
    LowerLabels = &H1000&:     RNAVLabels = &H2000&:     AirspacesLabels = &H4000&: AirportsLabels = &H8000&
    SeaportsLabels = &H10000:  CitiesLabels = &H20000:   PlacesLabels = &H40000
    LayersAltitude = &H100000: LayersDensity = &H200000: LayersDefinition = &H400000
    LayersZones = LayersAreas Or LayersLand Or LayersSea
    LayersDefault = LayersLower Or LayersRNAV Or LayersPlaces Or LayersLand Or LowerLabels Or RNAVLabels Or AirspacesLabels Or AirportsLabels Or SeaportsLabels Or CitiesLabels Or PlacesLabels
    End Enum

Enum LogOptions                                             'Simulation log to file parameters
    LogToFile = 1:             LogDate = 2:                LogTime = 4:                  LogCoordinates = 8
    LogAltitude = &H10&:       LogDistance = &H20&:        LogSpeed = &H40&:             LogAirSpeed = &H80&
    LogThrust = &H100&:        LogAcceleration = &H200&:   LogInertial = &H400&:         LogAirDrag = &H800&
    LogGravity = &H1000&:      LogMass = &H2000&:          LogTemperature = &H4000&:     LogStations = &H8000&
    LogAirspaces = &H10000:    LogCoordinates3D = &H20000: LogGround = &H40000:          LogSpeed3D = &H80000
    LogDistance3D = &H100000:  LogAirSpeed3D = &H200000:   LogAcceleration3D = &H400000: LogGravity3D = &H800000
    LogInertial3D = &H1000000: LogAirDrag3D = &H2000000:   LogSeparator = &H4000000
    LogDefault = LogTime Or LogCoordinates Or LogAltitude Or LogSpeed Or LogAirSpeed Or LogThrust Or LogAcceleration Or LogAirDrag Or LogGravity Or LogMass Or LogTemperature Or LogStations Or LogAirspaces
    End Enum

Enum ToolsD                                                 'Data panel rows
    DataStage = 1:           DataTime = 2:            DataDate = 4:            DataSpeedE = 8
    DataSpeedT = &H10&:      DataSpeedV = &H20&:      DataOrbit = &H40&:       DataEscape = &H80&
    DataApsis = &H100&:      DataPeriod = &H200&:     DataAngles = &H400&:     DataAltitudeE = &H800&
    DataAltitudeT = &H1000&: DataGravityE = &H2000&:  DataGravityT = &H4000&:  DataAccel = &H8000&
    DataInertial = &H10000:  DataDrag = &H20000:      DataLift = &H40000:      DataPitch = &H80000
    DataMagnetic = &H100000
    
    DataAir = 1:             DataAirT = 2:            DataAirS = 4:            DataBurn = 8
    DataThrust = &H10&:      DataMass = &H20&:        DataRangeSE = &H40&:     DataRangeHE = &H80&
    DataRangeHM = &H100&:    DataMaxG = &H200&:       DataImpulse = &H400&:    DataDeltaV = &H800&
    DataDeltaVG = &H1000&:   DataTimeAD = &H2000&:    DataProxy = &H4000&:     DataLatLon = &H8000&
    DataAlt = &H10000:       DataGround = &H20000:    DataCamera = &H40000
    
    DataToolBits = 21: DataAll = &H1FFFFF: DataNone = DataStage Or DataTime: DataElements = DataApsis Or DataPeriod Or DataAngles
    DataDefault = DataStage Or DataTime Or DataDate Or DataSpeedE Or DataSpeedT Or DataSpeedV Or DataOrbit Or DataEscape Or DataAltitudeE Or DataAltitudeT Or DataGravityE Or DataGravityT Or DataAccel Or DataDrag Or DataLift Or DataPitch
    DataToolBitsX = 19: DataAllX = &H7FFFF: DataNoneX = 0
    DataDefaultX = DataAir Or DataBurn Or DataThrust Or DataMass Or DataMaxG Or DataImpulse Or DataDeltaV Or DataTimeAD Or DataLatLon Or DataAlt Or DataGround
    End Enum

Enum GraphOptions                                           '3D graph options
    GraphAxes = 1:            GraphAxisZ = 2:            GraphRanges = 4:       GraphRangesZ = 8
    GraphLinesX = &H10&:      GraphLinesY = &H20&:       GraphAltX = &H40&:     GraphAltY = &H80&
    GraphSolid = &H100&:      GraphWireframe = &H200&:   GraphShadows = &H400&: GraphAutoScale = &H800&
    GraphAdjust = &H1000&:    GraphAdjustZ = &H2000&:    GraphMax = &H4000&:    GraphInfo = &H8000&
    GraphFaceMask = &HF0000:  GraphFaceShift = &H10000
    GraphLineMask = &HF00000: GraphLineShift = &H100000
    GraphDefaultColor = &H140000
    GraphNone = GraphDefaultColor Or GraphLinesX Or GraphLinesY Or GraphAutoScale Or GraphAdjust Or GraphAdjustZ Or GraphSolid Or GraphShadows
    GraphDefault = GraphDefaultColor Or GraphAxes Or GraphRanges Or GraphLinesX Or GraphLinesY Or GraphAltX Or GraphAltY Or GraphSolid Or GraphShadows Or GraphAutoScale Or GraphAdjust Or GraphAdjustZ Or GraphInfo
    End Enum

Type Camera                                                 'Camera structure
    X                       As Double                       'Position of the target
    Y                       As Double
    Z                       As Double
    AngleU                  As Double                       'Orientation of the target
    AngleV                  As Double
    AngleW                  As Double
    Altitude                As Double                       'Distance of the target
    Width                   As Long                         'Window width, height and square size
    Height                  As Long
    Square                  As Long
    CenterX                 As Double                       'Position relative to the simulation center
    CenterY                 As Double
    CenterZ                 As Double
    FOV                     As Double                       'Field of view and ratio
    FOVR                    As Double
    ShipSize                As Double                       'Iconic ship size (0=default, -1=point)
    Follow                  As TargetType                   'Follow mode
    LookAt                  As TargetType                   'Look at mode
    Relative                As TargetType                   'Relative to mode
    Target                  As TargetType                   'Target
    hRC                     As Long                         'OpenGL render context
    Free                    As Byte                         'Free mode
    Flat                    As Byte                         'Flat or spherical render mode
    Ortho                   As Byte                         'Orthogonal or conic render
    Auto                    As Byte                         'Auto-zoom mode
    Matrix3D                As MatrixX                      '3D projection matrix
    Matrix2D                As MatrixX                      'Left-top 2D projection matrix
    Matrix2DX               As MatrixX                      'Centered 2D projection matrix
    hDC                     As Long                         'Window device context
    F                       As Form                         'Form object
    ID                      As CameraID                     'ID and number
    N                       As Byte
    End Type

Type AirCurve                                               'Air curve structure
    D                       As Double                       'Density
    D2                      As Double
    P                       As Double                       'Pressure
    P2                      As Double
    T                       As Double                       'Temperature
    T2                      As Double
    End Type

Type Planet                                                 'Planet structure
    X                       As Double                       'Position
    Y                       As Double
    Z                       As Double
    MainSpeedX              As Double                       'Speed
    MainSpeedY              As Double
    MainSpeedZ              As Double
    'AccelX                  As Double                       'Acceleration
    'AccelY                  As Double
    'AccelZ                  As Double
    AngleU                  As Double                       'Orientation
    AngleV                  As Double
    AngleW                  As Double
    AngularSpeedW           As Double                       'Angular speed
    SinA                    As Double                       'Sinus and cosinus of AngleU or AngleV
    CosA                    As Double
    OrbitX                  As Double                       'Distance from orbit center
    OrbitY                  As Double
    OrbitZ                  As Double
    OrbitDistance           As Double
    ParentOrbitX            As Double                       'Distance from parent orbit center
    ParentOrbitY            As Double
    ParentOrbitZ            As Double
    ParentOrbitDistance     As Double
    OrbitI                  As Double                       'Orbital inclination to the ecliptic
    OrbitIV                 As Double
    OrbitO                  As Double                       'Longitude of ascending node
    OrbitOV                 As Double
    OrbitP                  As Double                       'Longitude of periapsis
    OrbitPV                 As Double
    OrbitS                  As Double                       'Semi-major axis
    OrbitSV                 As Double
    OrbitL                  As Double                       'Mean anomaly at epoch
    OrbitLV                 As Double
    OrbitE                  As Double                       'Orbital eccentricity
    OrbitEV                 As Double
    Distance                As Double                       'Ship relative altitude to the center
    LastAltitude            As Double
    Altitude                As Double                       'Ship relative altitude to the mean surface level
    SpeedX                  As Double
    SpeedY                  As Double
    SpeedZ                  As Double
    Speed                   As Double                       'Ship relative speed to the center
    AirSpeedX               As Double
    AirSpeedY               As Double
    AirSpeedZ               As Double
    AirSpeed                As Double                       'Ship relative speed to the surface
    VerticalSpeed           As Double                       'Ship relative vertical speed to the center and last vertical speed
    LastVerticalSpeed       As Double
    HorizontalSpeed2        As Double                       'Ship relative horizontal squared speed to the center
    CentripetalAccel        As Double                       'Ship centripetal acceleration
    Temperature             As Double                       'Surface temperature
    Infrared                As Double                       'Surface infrared
    Albedo                  As Double                       'Surface albedo
    Radius                  As Double                       'Equatorial and polar radius, squared eccentricity
    Radius2                 As Double
    E2                      As Double
    RA2                     As Double                       'Squared equatorial and polar radius, double squared eccentricity
    RB2                     As Double
    E4                      As Double
    EE2                     As Double                       'Temporary eccentricity variables
    E21                     As Double
    EAB2                    As Double
    Diameter                As Double                       'Diameter
    Mass                    As Double                       'Mass
    Gravity                 As Double                       'Gravity
    GravityMass             As Double                       'Gravitational constant multiplied by the mass
    GravityMassDistance     As Double                       'Temporary gravitational constant multiplied by the mass and divided by the distance
    GasConstant             As Double                       'Atmosphere gas constant and ratio of specific heats
    HeatRatio               As Double
    InnerRing               As Double                       'Inner and outer radius of the rings
    OuterRing               As Double
    Class                   As PlanetType                   'Type: 0=Star 1=Gas 2=Planet 3=Dwarf 4=Moon 5=Asteroid
    Texture                 As TextureX                     'Texture structure for the surface
    Ring                    As Long                         'Texture for the ring
    'Terrain                 As Long                         'Terrain list and last rendered position
    'TerrainX                As Long
    'TerrainY                As Long
    Grid                    As Long                         'Grid list for spherical model
    GridFlat                As Long                         'Grid list for flat model
    Field                   As Long                         'List of the magnetic field lines
    Atm                     As Long                         'List of the atmospheric model layers for spherical model
    AtmFlat                 As Long                         'List of the atmospheric model layers for flat model
    ID                      As TargetType                   'Number
    'Link                    As String                       'Web link
    'Name                    As String                       'Name
    AirCurve()              As AirCurve                     'Atmosphere data linear matrix
    Terrain()               As Integer                      'Terrain data matrix
    TerrainN                As Long                         'Terrain data matrix count
    TerrainP()              As Long                         'Terrain data matrix directory
    Geoid()                 As Byte                         'Geoid data matrix
    End Type

Type Graphic                                                'Graph point
    Speed                   As Double                       'Speed relative to Earth
    AirSpeed                As Double                       'Speed relative to Earth atmosphere
    Altitude                As Double                       'Altitude relative to Earth
    Mass                    As Double                       'Mass
    Gravity                 As Double                       'Earth gravity
    Thrust                  As Double                       'Engine thrust
    Accel                   As Double                       'Acceleration
    Drag                    As Double                       'Atmosphere drag
    Temperature             As Double                       'Ship temperature
    Links                   As Long                         'Radio station visibility count
    Airspaces               As Long                         'Airspace detection count
    End Type

Type Graph2D                                                '3D graph structure
    XN                      As Long                         'Size
    YN                      As Long
    XT                      As Long                         'Step
    YT                      As Long
    XC                      As Long                         'Center value
    YC                      As Long
    ZC                      As Double
    X1                      As Long                         'Minimum value
    Y1                      As Long
    Z1                      As Double
    X2                      As Long                         'Maximum value
    Y2                      As Long
    Z2                      As Double
    XS                      As Long                         'Range
    YS                      As Long
    ZS                      As Double
    TopX                    As Long                         'Max Z position
    TopY                    As Long
    SelX                    As Long                         'Selected point
    SelY                    As Long
    SelZ                    As Double
    ZS1                     As Double                       'Z scale lines position and step
    ZST                     As Double
    CamZ                    As Double                       'Render Z scale
    Tools                   As GraphOptions                 'Options
    ScaleZ                  As Double                       'Z scale
    List                    As Long                         'Render object
    Name                    As String                       'File name
    NameX                   As String                       'Axes names
    NameY                   As String
    NameZ                   As String
    Value()                 As Double                       'Matrix and graph names
    Hdr()                   As String
    End Type

Type Vector2                                                '2D position
    X                       As Double
    Y                       As Double
    End Type

Type Vector3                                                '3D position
    X                       As Double
    Y                       As Double
    Z                       As Double
    End Type

Type Matrix3                                                'Rotation matrix of 3x3 elements
    XX                      As Double
    XY                      As Double
    XZ                      As Double
    YX                      As Double
    YY                      As Double
    YZ                      As Double
    ZX                      As Double
    ZY                      As Double
    ZZ                      As Double
    End Type

Type Matrix4                                                'Rotation matrix of 4x4 elements
    XX                      As Double
    XY                      As Double
    XZ                      As Double
    XW                      As Double
    YX                      As Double
    YY                      As Double
    YZ                      As Double
    YW                      As Double
    ZX                      As Double
    ZY                      As Double
    ZZ                      As Double
    ZW                      As Double
    WX                      As Double
    WY                      As Double
    WZ                      As Double
    WW                      As Double
    End Type

Type PlanetPos                                              'Planet position structure
    X                       As Double                       'Position
    Y                       As Double
    Z                       As Double
    SpeedX                  As Double                       'Speed
    SpeedY                  As Double
    SpeedZ                  As Double
    AngleU                  As Double                       'Orientation
    AngleV                  As Double
    AngleW                  As Double
    LastAltitude            As Double                       'Last altitude and vertical speed
    LastVerticalSpeed       As Double
    AngularSpeedW           As Double                       'Angular speed
    End Type

Type StarPos                                                'Starfield star structure
    X                       As Single                       'Position
    Y                       As Single
    Z                       As Single
    Mag                     As Single                       'Visual magnitude
    End Type

Type StarPosEx                                              'Star tracker star structure
    Lat                     As Double                       'Coordinates
    Lon                     As Double
    Dist                    As Double
    X                       As Double                       'Position
    Y                       As Double
    Z                       As Double
    Mag                     As Single                       'Visual magnitude
    Mass                    As Single                       'Mass in kilograms
    Radius                  As Single                       'Radius in meters
    Lum                     As Single                       'Luminosity in watts
    Color                   As Long                         'Color
    Name                    As String                       'Name
    End Type

Type StagePos                                               'Stage position and speed structure
    X                       As Double                       'Position
    Y                       As Double
    Z                       As Double
    SpeedX                  As Double                       'Speed
    SpeedY                  As Double
    SpeedZ                  As Double
    End Type

Type PathPoint                                              'Path point structure
    Sun                     As Vector3                      'Earth, Moon and Sun position
    Earth                   As Vector3
    Target                  As Vector3
    AngleW                  As Double                       'Earth rotation
    T                       As Double                       'Simulation time pointer
    P                       As Long                         'Target planet
    N                       As Long                         'Total of Stations visible
    End Type

Type KMLPoint                                               'KML path point structure
    Lat                     As Double                       'Latitude in degrees
    Lon                     As Double                       'Longitude in degrees
    Alt                     As Double                       'Altitude in meters
    Time                    As Double                       'Time UTC in seconds
    SpeedH                  As Double                       'Horizontal and vertical speed in meters per second
    SpeedV                  As Double
    Density                 As Double                       'Air density in kilograms per cubic meter
    Pressure                As Double                       'Air pressure in Pascals
    Temperature             As Double                       'Air temperature in Kelvin
    Humidity                As Double                       'Air relative humidity in percent
    CloudWater              As Double                       'Cloud water mixing ratio
    WindX                   As Double                       'Air speed vector in meters per second
    WindY                   As Double
    WindZ                   As Double
    Color                   As Long                         'Color of the line
    End Type

Type Item                                                   'Selected item structure
    N                       As Long                         'Number
    Class                   As Long                         'Class
    End Type

Type ItemEx                                                 'Selected item string structure
    Name                    As String                       'Name
    Class                   As Long                         'Class
    End Type

Type Snap                                                   'Snapshot structure
    Sun                     As PlanetPos                    'Sun, Earth and Moon position
    Earth                   As PlanetPos
    Moon                    As PlanetPos
    Ship                    As PlanetPos                    'Ship position
    Mercury                 As PlanetPos                    'Planets position
    Venus                   As PlanetPos
    Mars                    As PlanetPos
    Jupiter                 As PlanetPos
    Saturn                  As PlanetPos
    Uranus                  As PlanetPos
    Neptune                 As PlanetPos
    Pluto                   As PlanetPos
    StageE()                As StagePos                     'Empty stages position
    StageC                  As Long
    StageP                  As Long                         'Global variables
    StageN                  As Long
    Status                  As Long
    StatusText              As String
    Mass                    As Double
    Thrust                  As Double
    Throttle                As Double
    ThrustP                 As Long
    T                       As Double
    RealTime                As Double
    DelayTime               As Double
    AccelTime               As Double
    BrakeTime               As Double
    Drag                    As Double
    Lift                    As Double
    Accel                   As Double
    Attitude                As Matrix3
    AngularSpeed            As Matrix3
    ControlThrustU          As Double
    ControlAccU             As Double
    ControlLastU            As Double
    ControlThrustV          As Double
    ControlAccV             As Double
    ControlLastV            As Double
    ControlThrustW          As Double
    ControlAccW             As Double
    ControlLastW            As Double
    TargetAngle             As Double
    MaxVe                   As Double                       'Statistics
    MinVe                   As Double
    MaxHe                   As Double
    MinHe                   As Double
    MaxHm                   As Double
    MinHm                   As Double
    MaxG                    As Double
    ControlThrustMax        As Double
    ThrustDV                As Double
    BurnOutDV               As Double
    GravityDV               As Double                       'Delta-V
    DragDV                  As Double
    LiftDV                  As Double
    Impulse                 As Double
    IsLiftOff               As Byte                         'Event flags
    IsEarthSoundSpeed       As Byte
    IsEarthOrbitalSpeed     As Byte
    IsEarthEscapeSpeed      As Byte
    IsMoonOrbitalSpeed      As Byte
    IsMoonEscapeSpeed       As Byte
    IsMarsOrbitalSpeed      As Byte
    IsMarsEscapeSpeed       As Byte
    IsApogee                As Byte
    IsPeriselene            As Byte
    IsAtmosphere            As Byte
    IsIonosphere            As Byte
    IsEquidistantPoint      As Byte
    IsL1LagrangePoint       As Byte
    IsMoonDistance          As Byte
    IsControl               As Byte
    End Type

Type Curve                                                  'Thrust curve point
    T                       As Double                       'Time
    F                       As Double                       'Thrust
    End Type

Type Engine                                                 'Engine structure
    Name                    As String                       'Name
    Diameter                As Double                       'Diameter
    Length                  As Double                       'Length
    ThrustV                 As Double                       'Mean thrust in Vacuum
    ThrustS                 As Double                       'Mean thrust at Sea Level
    IspV                    As Double                       'Specific impulse in Vacuum
    IspS                    As Double                       'Specific impulse at Sea Level
    FullMass                As Double                       'Full mass
    DryMass                 As Double                       'Dry mass without propellant
    BurnRate                As Double                       'Propellant burn rate
    ThrustCurve()           As Curve                        'Thrust curve matrix
    ThrustN                 As Long                         'Thrust curve size
    End Type

Type Stage                                                  'Stage structure
    ThrustV                 As Double                       'Thrust in Vacuum
    ThrustS                 As Double                       'Thrust at Sea Level
    IspV                    As Double                       'Specific Impulse in Vacuum
    IspS                    As Double                       'Specific Impulse at Sea Level
    Delay                   As Double                       'Delay
    FullMass                As Double                       'Full mass
    FuelMass                As Double                       'Propellant mass
    DryMass                 As Double                       'Dry mass without propellant
    FullArea                As Double                       'Total area
    Area                    As Double                       'Front area
    Diameter                As Double                       'Diameter or front area
    Length                  As Double                       'Length
    BurnRate                As Double                       'Propellant burn rate
    Volume                  As Double                       'Propellant volume
    Cost                    As Double                       'Propellant cost
    DryVolume               As Double                       'Propellant volume correction
    Fuel                    As String                       'Propellant or engine type
    Control                 As Double                       'Attitude control thrust limit
    ControlP                As Double                       'Attitude control P parameter
    ControlI                As Double                       'Attitude control I parameter
    ControlD                As Double                       'Attitude control D parameter
    InertiaX                As Double                       'Moment of inertia around X axis
    InertiaY                As Double                       'Moment of inertia around Y axis
    InertiaZ                As Double                       'Moment of inertia around Z axis
    CenterX                 As Double                       'Center of mass in X axis
    CenterY                 As Double                       'Center of mass in Y axis
    CenterZ                 As Double                       'Center of mass in Z axis
    Battery                 As Double                       'Battery charge
    Imax                    As Double                       'Maximum allowable current
    Vmax                    As Double                       'Maximum allowable voltage
    Power                   As Double                       'Electrical power
    Tmin                    As Double                       'Minimum allowable temperature
    Tmax                    As Double                       'Maximum allowable temperature
    Emissivity              As Double                       'Material emissivity
    ThrustCurve()           As Curve                        'Thrust curve matrix
    ThrustN                 As Long                         'Thrust curve size
    DragCurve()             As Double                       'Drag coefficient curve matrix
    DragStep                As Double                       'Drag coefficient curve step
    DragN                   As Long                         'Total of Drag coefficient points
    LiftToDrag              As Double                       'Lift to drag ratio
    End Type

Type StageB                                                 'Preset stage structure
    Active                  As Long                         'Stage is active
    Mass                    As String                       'Full mass
    Dry                     As String                       'Dry mass without propellant
    Thrust                  As String                       'Thrust at Sea Level
    Delay                   As String                       'Delay
    Fuel                    As String                       'Propellant type
    Area                    As String                       'Diameter or front area
    Length                  As String                       'Length
    Control                 As String                       'Attitude control thrust limit
    ControlP                As String                       'Attitude control P parameter
    ControlI                As String                       'Attitude control I parameter
    ControlD                As String                       'Attitude control D parameter
    LiftToDrag              As String                       'Lift to drag ratio
    DragCf                  As String                       'Drag coefficient
    InertiaX                As String                       'Moment of inertia over X axis
    InertiaY                As String                       'Moment of inertia over Y axis
    InertiaZ                As String                       'Moment of inertia over Z axis
    CenterX                 As String                       'Center of mass in X axis
    CenterY                 As String                       'Center of mass in Y axis
    CenterZ                 As String                       'Center of mass in Z axis
    Battery                 As String                       'Battery charge
    Imax                    As String                       'Maximum allowable current
    Vmax                    As String                       'Maximum allowable voltage
    Power                   As String                       'Electrical power
    Tmin                    As String                       'Minimum allowable temperature
    Tmax                    As String                       'Maximum allowable temperature
    Emissivity              As String                       'Material emissivity
    IspS                    As String                       'Specific impulse at sea level
    IspV                    As String                       'Specific impulse in vacuum
    Model                   As String                       '3D model name
    End Type

Type Preset                                                 'Preset structure
    Name                    As String                       'Name
    'TerrainMode             As String                       'Terrain mode
    TimeStep                As String                       'Simulation time step
    Date                    As String                       'Launch date
    Time                    As String                       'Launch time
    Location                As String                       'Launch site name
    Lat                     As String                       'Latitude
    Lon                     As String                       'Longitude
    Alt                     As String                       'Altitude
    Speed                   As String                       'Initial speed
    Angle                   As String                       'Initial pitch angle
    Heading                 As String                       'Initial heading
    StartAngle              As String                       'Start trajectory angle
    EndAngle                As String                       'End trajectory angle
    StartAlt                As String                       'Start trajectory altitude
    EndAlt                  As String                       'End trajectory altitude
    ThrustLimit             As String                       'Engine thrust limit
    LiftLimit               As String                       'Lift force limit
    Brake                   As String                       'Hover or parachute braking altitude
    EarthPark               As String                       'Earth orbit parking time
    MoonPark                As String                       'Moon orbit parking time
    Abort                   As String                       'Abort stage number
    Mass                    As String                       'Total mass
    Parachute               As String                       'Parachute diameter or front area
    Jettison                As Long                         'Jettison upper stages on abort
    Targets()               As ItemEx                       'Target list
    Stage()                 As StageB                       'Stages structure matrix
    End Type

Type Path                                                   'Trajectory structure
    Preset                  As Preset                       'Preset
    TimeStep                As Double                       'Simulation time step
    Time                    As Double                       'Launch date and time in J2000 seconds
    Lat                     As Double                       'Coordinates
    Lon                     As Double
    Alt                     As Double
    InitialAngle            As Double                       'Initial angle
    StartAngle              As Double                       'Start and end trajectory angles
    EndAngle                As Double
    StartAlt                As Double                       'Start and end trajectory altitudes
    EndAlt                  As Double
    EarthAngle              As Double                       'Launch Earth orientation
    ThrustLimit             As Double                       'Engine thrust limit
    LiftLimit               As Double                       'Lift force limit
    EarthPark               As Double                       'Earth orbit parking time
    MoonPark                As Double                       'Moon orbit parking time
    Brake                   As Double                       'Hover or parachute braking altitude
    Abort                   As Long                         'Stage number abort
    Jettison                As Long                         'Jettison upper stages on abort
    Parachute               As Double                       'Parachute diameter or front area
    Targets()               As Item                         'Target list
    TargetsN                As Long                         'Total of targets
    Stage()                 As Stage                        'Stages
    Snap()                  As Snap                         'Snapshots
    SnapN                   As Long                         'Total of snapshots
    SnapP                   As Long                         'Current snapshot
    SnapTime                As Long                         'Snapshot frequency
    Path()                  As PathPoint                    'Path points
    PathN                   As Long                         'Total of path points
    PathP                   As Long                         'Current path point
    PathTime                As Long                         'Path point frequency
    PointTime               As Long                         'Time pointer of path
    End Type

Type Fuel                                                   'Propellant structure
    IspV                    As Double                       'Specific Impulse in Vacuum
    IspS                    As Double                       'Specific Impulse at Sea Level
    Cost                    As Double                       'Cost
    Density                 As Double                       'Density
    Volume                  As Double                       'Volume correction
    Curve                   As String                       'Thrust curve name
    Name                    As String                       'Propellant name
    End Type

Type BurnPoint                                              'Burn curve point structure
    Thrust                  As Double                       'Thrust in Newtons
    Pressure                As Double                       'Combustion chamber pressure in Pascals
    'Volume                  As Double                       'Combustion chamber volume in cubic meters
    'Mass                    As Double                       'Propellant mass in kilograms
    Burn                    As Double                       'Propellant burn distance in meters
    BurnRate                As Double                       'Propellant burn rate in meters per second
    'BurnFlow                As Double                       'Burned mass flow in kilograms per second
    'NozzleFlow              As Double                       'Nozzle mass flow in kilograms per second
    ExpRatio                As Double                       'Nozzle expansion ratio
    End Type

Type BurnPointB                                             'Burn adjust curve point structure
    Diameter                As Double                       'Diameter in meters
    Thrust                  As Double                       'Thrust in Newtons
    Pressure                As Double                       'Combustion chamber pressure in Pascals
    Isp                     As Double                       'Specific Impulse in seconds
    Impulse                 As Double                       'Total Impulse in Newtons per second
    End Type

Type PressurePoint                                          'Pressure to burn rate curve .. BurnRate[mm]=A*Pressure[MPa]^N
    P                       As Double                       'Pressure in Pascals
    A                       As Double                       'Propellant burn rate base parameter
    N                       As Double                       'Propellant burn rate exponential parameter
    End Type

Type Location                                               'Location structure
    Lat                     As Double                       'Latitude
    Lon                     As Double                       'Longitude
    Alt                     As Double                       'Altitude
    Time                    As Double                       'Time
    Planet                  As Long                         'Planet
    Name                    As String                       'Name
    End Type

Type TLE                                                    'TLE orbit structure
    I                       As Double                       'Orbital inclination in radians
    O                       As Double                       'Right ascension of the ascending node in radians
    P                       As Double                       'Argument of perigee in radians
    S                       As Double                       'Semi-major axis in meters
    L                       As Double                       'Mean anomaly at epoch in radians
    E                       As Double                       'Orbital eccentricity
    M                       As Double                       'Mean motion in radians per second
    N                       As Double                       'Revolution number at epoch
    T                       As Double                       'Epoch in J2000 seconds
    F                       As Double                       'First time derivative of the mean motion
    C                       As Double                       'Second time derivative of the mean motion
    B                       As Double                       'BSTAR drag term
    R                       As Long                         'Ephemeris type (0=undefined, 1=SGP, 2=SGP4, 3=SDP4, 4=SGP8, 5=SDP8)
    U                       As Long                         'Element number
    Design                  As String                       'International designator
    Number                  As String                       'Number
    Class                   As Long                         'Class
    Name                    As String                       'Name
    End Type

Type Asteroid                                               'Asteroid, comet and moon structure
    I                       As Double                       'Orbital inclination to the ecliptic in radians
    O                       As Double                       'Longitude of ascending node in radians
    P                       As Double                       'Longitude of periapsis in radians
    S                       As Double                       'Semi-major axis in meters
    L                       As Double                       'Mean anomaly at epoch in radians
    E                       As Double                       'Orbital eccentricity
    M                       As Double                       'Mean motion in radians per second
    R                       As Double                       'Radius in meters
    'List                    As Long                         'Model list
    'Tex                     As Long                         'Model texture
    IsModel                 As Boolean                      '3D object flag
    Class                   As Integer                      'Type of asteroid
    Name                    As String                       'Name
    End Type

Type MinorPlanet                                            'Minor planet structure
    N                       As Long                         'Matrix number
    Class                   As ObjectType                   'Object type
    List                    As Long                         'Model list
    Tex                     As Long                         'Model texture
    End Type

Type Sensor                                                 'Sensor structure
    Name                    As String                       'Name
    Lens                    As Double                       'Lens size in inches
    FOV                     As Double                       'Horizontal field of vision in degrees
    Width                   As Long                         'Image width in pixels
    Height                  As Long                         'Image height in pixels
    Link                    As String                       'Link to a web page with information
    End Type

Type Lens                                                   'Lens structure
    Name                    As String                       'Name
    Lens                    As Double                       'Lens size in inches
    Length                  As Double                       'Focal length in millimeters
    FNO                     As Double                       'FNO (Brightness)
    FOV                     As Double                       'Horizontal field of view in degrees
    FOVV                    As Double                       'Vertical field of view in degrees
    Link                    As String                       'Link to a web page with information
    End Type

Type Rover                                                  'Rover structure
    X                       As Double                       '3D position
    Y                       As Double
    Z                       As Double
    U                       As Double                       '3D orientation
    V                       As Double
    W                       As Double
    T                       As Double                       'Elapsed time
    Port                    As Long                         'Port
    End Type

Type Track                                                  'Tracking structure
    Lat                     As Double                       'Coordinates
    Lon                     As Double
    Alt                     As Double
    Time                    As Double                       'Elapsed time and start time
    Start                   As Double
    Heading                 As Double                       'Heading and elevation
    Elevation               As Double
    TargetHdg               As Double                       'Target heading, elevation and distance
    TargetElv               As Double
    TargetDst               As Double
    Last                    As Double                       'Last server query time
    Port                    As Long                         'Port
    Mode                    As LinkDevice                   'Connection mode
    ID                      As LinkID                       'ID number
    Valid                   As Boolean                      'Position valid flag
    SelType                 As ObjectType                   'Selected object
    SelN                    As Long
    Path()                  As KMLPoint                     'KML path points
    N                       As Long                         'KML path points count
    Color                   As Long                         'OpenGL path color
    ColorKML                As String                       'KML path color
    File                    As String                       'KML path file name
    Label                   As String                       'Label short name
    Name                    As String                       'Label name
    End Type

Type NMEA                                                   'NMEA position structure
    Day                     As Double                       'Date in UTC of position
    Time                    As Double                       'Time in UTC of position
    Lat                     As Double                       'Latitude of position
    Lon                     As Double                       'Longitude of position
    Alt                     As Double                       'Altitude above mean sea level (-9999.9 to 99999.9)
    Geoid                   As Double                       'Height of geoid above ellipsoid (-999.9 to 9999.9)
    Speed                   As Double                       'Speed over ground
    Heading                 As Double                       'True course
    Mag                     As Double                       'Magnetic variation
    Sat                     As Long                         'Number of satellites (00 to 12)
    HDOP                    As Double                       'Horizontal dilution of precision (0.5 to 99.9)
    VDOP                    As Double                       'Vertictal dilution of precision (0.5 to 99.9)
    PDOP                    As Double                       'Positional dilution of precision (0.5 to 99.9)
    Valid                   As Boolean                      'Validity (A=OK V=Invalid)
    Mode                    As Long                         'GPS fix (0=fix not available, 1=Non-differential GPS fix available, 2=Differential GPS (WAAS) fix available, 6=Estimated)
    Chk                     As Long                         'Checksum (*HH=Two hex digits representing an 8 bit exclusive OR of all characters between, but not including, the '$' and '*')
    End Type

Type Point                                                  'Notification point structure
    Nam                     As String                       'Point name
    Lat                     As Double                       'Coordinates
    Lon                     As Double
    HUp                     As Double                       'Orientation of the previous and next points
    HDn                     As Double
    FLU                     As Double                       'Upper and lower flight level
    FLD                     As Double
    Dis                     As Double                       'Distance to the next point
    End Type

Type Airway                                                 'Airway or RNAV structure
    Nam                     As String                       'Name
    Num                     As Long                         'Points count
    Way(30 - 1)             As Point                        'List of points
    End Type

Type Coord                                                  '2D coordinates
    Lat                     As Single                                           'Latitude
    Lon                     As Single                                           'Longitude
    End Type

Type Airspace                                               'Airspace structure
    Name                    As String                       'Name
    'Desc                    As String                      'Description
    Lat                     As Single                       'Coordinates
    Lon                     As Single
    Alt                     As Single
    FLD                     As Single                       'Floor and ceiling in meters
    FLU                     As Single
    CLat                    As Single                       'Center point coordinates
    CLon                    As Single
    SLat                    As Single                       'Size of the bounding rectangle
    SLon                    As Single
    Color                   As Long                         'Color
    N                       As Long                         'Number of points
    Point()                 As Coord                        'List of points
    End Type

Type URL                                                    'Web link structure
    Name                    As String
    URL                     As String
    End Type

Type StringItem                                             'String and position
    N                       As Long
    Name                    As String
    End Type

Type StringValue                                            'String, position and value
    N                       As Long
    Value                   As Long
    Name                    As String
    End Type

Type GRIB                                                   'GRIB message structure
    N                       As Long                         'Count
    Pos                     As Long                         'Position in bytes
    Size                    As Long                         'Size in bytes
    Year                    As Long                         'Time
    Month                   As Long
    Day                     As Long
    Hour                    As Long
    Forecast                As Long                         'Forecast period
    Width                   As Long                         'Matrix size and step
    Height                  As Long
    Scale                   As Single
    NData                   As Long                         'Total of values
    Param                   As Long                         'Parameter type and ID
    ParamID                 As String
    Layer                   As Long                         'Layer type and ID
    LayerID                 As String
    Level                   As Long                         'Layer level
    Level1                  As Long
    Level2                  As Long
    LevelID                 As String                       'Layer level description
    S()                     As Byte                         'Packed bytes
    F()                     As Single                       'Matrix of values
    End Type

Type StgD '  32 .. 8*4                                      'Link-Stage-Double structure
    Mass                    As Double
    Dry                     As Double
    Thrust                  As Double
    Delay                   As Double
    End Type

Type StgS '  16 .. 4*4                                      'Link-Stage-Single structure
    Mass                    As Single
    Dry                     As Single
    Thrust                  As Single
    Delay                   As Single
    End Type

Type InitD '280 .. 4+8+10+8+8*11+1+1+5*8*4                  'Link-Init-Double structure
    Head(4 - 1)             As Byte
    TimeStep                As Double
    Date(10 - 1)            As Byte
    Time(8 - 1)             As Byte
    Lat                     As Double
    Lon                     As Double
    Alt                     As Double
    Speed                   As Double
    Heading                 As Double
    StartAngle              As Double
    EndAngle                As Double
    StartAlt                As Double
    EndAlt                  As Double
    FullMass                As Double
    PayloadMass             As Double
    Abort                   As Byte
    Stages                  As Byte
    Stage(1 To 5)           As StgD
    End Type

Type InitS '152 .. 4+4+10+8+4*11+1+1+5*4*4                  'Link-Init-Single structure
    Head(4 - 1)             As Byte
    TimeStep                As Single
    Date(10 - 1)            As Byte
    Time(8 - 1)             As Byte
    Lat                     As Single
    Lon                     As Single
    Alt                     As Single
    Speed                   As Single
    Heading                 As Single
    StartAngle              As Single
    EndAngle                As Single
    StartAlt                As Single
    EndAlt                  As Single
    FullMass                As Single
    PayloadMass             As Single
    Abort                   As Byte
    Stages                  As Byte
    Stage(1 To 5)           As StgS
    End Type

Type DataD '172 .. 4+8*21                                   'Link-Data-Double structure
    Head(4 - 1)             As Byte
    T                       As Double
    M                       As Double
    Mt                      As Double
    Mf                      As Double
    F                       As Double
    Te                      As Double
    eG                      As Double
    eH                      As Double
    eX                      As Double
    eY                      As Double
    eZ                      As Double
    eA                      As Double
    eR                      As Double
    eVR                     As Double
    eVRX                    As Double
    eVRY                    As Double
    eVRZ                    As Double
    eVA                     As Double
    eVAX                    As Double
    eVAY                    As Double
    eVAZ                    As Double
    End Type

Type DataS ' 88 .. 4+4*21                                   'Link-Data-Single structure
    Head(4 - 1)             As Byte
    T                       As Single
    M                       As Single
    Mt                      As Single
    Mf                      As Single
    F                       As Single
    Te                      As Single
    eG                      As Single
    eH                      As Single
    eX                      As Single
    eY                      As Single
    eZ                      As Single
    eA                      As Single
    eR                      As Single
    eVR                     As Single
    eVRX                    As Single
    eVRY                    As Single
    eVRZ                    As Single
    eVA                     As Single
    eVAX                    As Single
    eVAY                    As Single
    eVAZ                    As Single
    End Type

Type CtrlD ' 48 .. 4+8+1+1+1+1+8*4                          'Link-Ctrl-Double structure
    Head(4 - 1)             As Byte
    Tt                      As Double
    St                      As Byte
    Nt                      As Byte
    At                      As Byte
    Bt                      As Byte
    Rt                      As Double
    Ut                      As Double
    Vt                      As Double
    Wt                      As Double
    End Type

Type CtrlS ' 28 .. 4+4+1+1+1+1+4*4                          'Link-Ctrl-Single structure
    Head(4 - 1)             As Byte
    Tt                      As Single
    St                      As Byte
    Nt                      As Byte
    At                      As Byte
    Bt                      As Byte
    Rt                      As Single
    Ut                      As Single
    Vt                      As Single
    Wt                      As Single
    End Type

'Planets and moons
    Public Sun                  As Planet, _
           Mercury              As Planet, _
           Venus                As Planet, _
           Earth                As Planet, _
           Mars                 As Planet, _
           Jupiter              As Planet, _
           Saturn               As Planet, _
           Uranus               As Planet, _
           Neptune              As Planet, _
           Pluto                As Planet                   'Planets
    Public Moon                 As Planet, _
           Io                   As MinorPlanet, _
           Europa               As MinorPlanet, _
           Ganymede             As MinorPlanet, _
           Callisto             As MinorPlanet, _
           Titan                As MinorPlanet, _
           Triton               As MinorPlanet, _
           Ceres                As MinorPlanet              'Moons

'Ship
    'Public X                    As Double, _
            Y                    As Double, _
            Z                    As Double                   'Position relative to Sun in meters
    Public SpeedX               As Double, _
           SpeedY               As Double, _
           SpeedZ               As Double                   'Speed relative to Sun in meters per second
    Public AccelX               As Double, _
           AccelY               As Double, _
           AccelZ               As Double, _
           Accel                As Double                   'Acceleration in meters per squared seconds
    Public InertialAccelX       As Double, _
           InertialAccelY       As Double, _
           InertialAccelZ       As Double, _
           InertialAccel        As Double                   'Acceleration without gravity in meters per squared seconds
    Public AngleU               As Double, _
           AngleV               As Double, _
           AngleW               As Double                   'Orientation (normalized vector)
    Public AngleU2              As Double, _
           AngleV2              As Double, _
           AngleW2              As Double                   'Orientation when engines are off (normalized vector)
    Public ControlThrustU       As Double, _
           ControlThrustV       As Double, _
           ControlThrustW       As Double                   'Attitude control thrust in Newtons (U=X=Pitch V=Y=Yaw W=Z=Roll)
    Public Control              As Double                   'Attitude control limit thrust in Newtons or magnetic moment in Amperes meter squared
    Public ControlP             As Double, _
           ControlI             As Double, _
           ControlD             As Double                   'Attitude control PID parameters
    Public ControlAccU          As Double, _
           ControlLastU         As Double                   'Attitude control accumulated error and last error (Pitch)
    Public ControlAccV          As Double, _
           ControlLastV         As Double                   'Attitude control accumulated error and last error (Yaw)
    Public ControlAccW          As Double, _
           ControlLastW         As Double                   'Attitude control accumulated error and last error (Roll)
    Public Attitude             As Matrix4, _
           AngularSpeed         As Matrix4                  'Attitude and angular speed rotation matrices
    Public Battery              As Double                   'Battery charge in watts per hour
    Public InertiaX             As Double, _
           InertiaY             As Double, _
           InertiaZ             As Double                   'Moment of inertia around X, Y and Z axes
    Public CenterX              As Double, _
           CenterY              As Double, _
           CenterZ              As Double                   'Center of mass in X, Y and Z axes
    Public Imax                 As Double                   'Maximum allowable current in amperes
    Public Vmax                 As Double                   'Maximum allowable voltage in volts
    Public Power                As Double                   'Electrical power in watts
    Public Tmin                 As Double                   'Minimum allowable temperature in Kelvin
    Public Tmax                 As Double                   'Maximum allowable temperature in Kelvin
    Public Emissivity           As Double                   'Material emissivity
    Public Temperature          As Double                   'Static temperature in Kelvin
    Public GroundAltitude       As Double                   'Altitude to the surface in meters
    Public FloorAltitude        As Double                   'Altitude of the ground in meters
    Public GeoidAltitude        As Double                   'Altitude of the geoid in meters
    Public AltitudeX            As Double, _
           AltitudeY            As Double, _
           AltitudeZ            As Double, _
           Altitude             As Double                   'Altitude vector to the mean surface level in meters
    Public AirSpeedX            As Double, _
           AirSpeedY            As Double, _
           AirSpeedZ            As Double, _
           AirSpeed             As Double                   'Air speed vector in meters per second
    Public VerticalSpeed        As Double                   'Vertical speed in meters per second
    Public AirLiftU             As Double, _
           AirLiftV             As Double, _
           AirLiftW             As Double                   'Air lift vector (normalized vector)
    Public AirRollU             As Double, _
           AirRollV             As Double, _
           AirRollW             As Double                   'Air roll vector (normalized vector)
    Public AirDensity           As Double                   'Air density in kilograms per cubic meter
    Public AirPressure          As Double                   'Air pressure in Pascals
    Public AirTemperature       As Double                   'Air temperature in Kelvin
    Public AirHumidity          As Double                   'Air relative humidity in percent ratio
    Public AirCloudWater        As Double                   'Air cloud water mixing ratio
    Public AirWindU             As Double, _
           AirWindV             As Double, _
           AirWindW             As Double, _
           AirWindH             As Double                   'Wind speed vector in meters per second
    Public MachNumber           As Double                   'Mach number
    Public Drag                 As Double                   'Drag deceleration in meters per squared seconds
    Public Lift                 As Double                   'Air lift acceleration in meters per squared seconds
    Public TargetAngle          As Double                   'Trajectory target angle
    Public Const ErrorN         As Long = 100               'Deviation samples
    Public ErrorX(ErrorN)       As Single, _
           ErrorY(ErrorN)       As Single, _
           ErrorZ(ErrorN)       As Byte, _
           ErrorU(ErrorN)       As Single, _
           ErrorV(ErrorN)       As Single, _
           ErrorW(ErrorN)       As Single, _
           ErrorP               As Long                     'Deviation X, Y, U, V and W from target vector
    Public Angle                As Double                   'Pitch angle respect to the vertical in radians
    Public IsControl            As Long                     'Controlled attitude flag
    Public AirAngleV            As Double                   'Air angle of incidence respect to the vertical in radians
    Public AirAngle             As Double                   'Air angle of incidence respect to the ship in radians
    Public SolarWind            As Double                   'Solar wind pressure in Pascals
    Public Mass                 As Double                   'Mass in kilograms
    Public Thrust               As Double                   'Thrust in Newtons
    Public Throttle             As Double                   'Throttle 0=None 1=Full
    Public FullMass             As Double                   'Full mass in kilograms
    Public DryMass              As Double                   'Dry mass without propellant in kilograms
    Public BurnRate             As Double                   'Propellant burn rate in kilograms per second
    Public ThrustV              As Double                   'Engine thrust in Vacuum in Newtons
    Public ThrustS              As Double                   'Engine thrust at Sea Level in Newtons
    Public Delay                As Double                   'Stage delay in seconds
    Public Area                 As Double                   'Front area in squared meters
    Public FullArea             As Double                   'Total area in squared meters
    Public IspV                 As Double                   'Specific Impulse in Vacuum
    Public IspS                 As Double                   'Specific Impulse at Sea Level
    Public ThrustCurve()        As Curve                    'Thrust curve matrix
    Public ThrustN              As Long                     'Thrust curve size
    Public ThrustP              As Long                     'Current thrust curve point
    Public DragCurve()          As Double                   'Drag coefficient curve matrix
    Public DragStep             As Double                   'Drag coefficient curve step
    Public DragN                As Long                     'Total of Drag coefficient points
    Public DragCf               As Double                   'Drag coefficient
    Public LiftToDrag           As Double                   'Lift to drag ratio
    Public Diameter             As Double                   'Diameter in meters or front area in squared meters
    Public Length               As Double                   'Length in meters
    Public LengthToDiameter     As Double                   'Length to diameter ratio
    Public Cost                 As Double                   'Fuel cost in US dollars
    Public Volume               As Double                   'Fuel volume in cubic meters
    Public TotalFuelMass        As Double                   'Total propellant mass
    Public TotalDryMass         As Double                   'Total dry mass
    Public EarthPark            As Double                   'Earth orbit parking time in seconds
    Public MoonPark             As Double                   'Moon orbit parking time in seconds
    Public ThrustLimit          As Double                   'Engine thrust limit in meters per squared seconds
    Public LiftLimit            As Double                   'Lift force limit in meters per squared seconds
    Public Brake                As Double                   'Braking altitude in meters
    Public Parachute            As Double                   'Parachute diameter or front area
    Public Jettison             As Long                     'Jettison upper stages on abort
    Public Abort                As Long                     'Stage number abort
    Public Targets()            As Item                     'Target list
    Public TargetsN             As Long                     'Total of targets
    Public Stage()              As Stage                    'Stages
    Public StageT               As Long                     'Total of stages
    Public StageP               As Long                     'Stage number reversed
    Public StageN               As Long                     'Stage number
    Public StageE()             As StagePos                 'Empty stages struct
    Public StageC               As Long                     'Last empty stage pointer
    Public Status               As Phase                    'Phase number
    Public StatusText           As String                   'Phase number text

'Simulation
    Public T                    As Double                   'Simulation time in seconds
    Public dT                   As Double                   'Simulation time step in seconds
    Public Const dTMin          As Double = 0.000001        'Minimum time step
    Public Const dTMax          As Double = 10              'Maximum time step
    Public Const SpeedMin       As Double = 0.01            'Minimum simulation speed
    Public Const SpeedMax       As Double = 1000000000      'Maximum simulation speed
    Public Count                As Long                     'Simulation main loop counter
    Public TimeSpeed            As Double                   'Simulation speed
    Public RealSpeed            As Double, _
           AverageSpeed         As Double                   'Simulation real and average speeds
    Public RealTime             As Double, _
           LastTime             As Double                   'Simulation real time in seconds
    Public SyncTime             As Double, _
           SyncMode             As Boolean                  'Sync mode time and flag
    Public Fps                  As Double                   'Render FPS
    Public FrameCount           As Long, _
           FrameTime            As Double                   'Render frame count and time pointer
    Public StartTime            As Double, _
           StartT               As Double                   'Simulation time pointers
    Public StepTime             As Double, _
           StepT                As Double                   'Simulation time pointers
    Public IsCalc               As Boolean                  'Trajectory calculation in progress
    Public InitTxt              As String                   'Simulation error message
    Public DelayTime            As Double                   'Stage delay time in seconds
    Public AccelTime            As Double                   'Acceleration time in seconds
    Public BrakeTime            As Double                   'Braking time in seconds
    Public Play                 As PlayMode                 'Play / pause mode
    Public Sector               As TargetType               'Orbital planet zone
    Public LastSector           As TargetType               'Last orbital planet zone
    Public Target               As TargetType               'Nearest planet
    Public LastTarget           As TargetType               'Last nearest planet
    Public Zone                 As ZoneType                 'Zone of the nearest planet
    Public LastZone             As ZoneType                 'Last zone of the nearest planet
    Public LastPreset           As Preset                   'Current preset
    Public Snap()               As Snap                     'Snapshots
    Public SnapN                As Long                     'Total of snapshots
    Public SnapP                As Long                     'Current snapshot
    Public SnapTime             As Long                     'Snapshot frequency
    Public LastSnap             As Long                     'Last snapshot used when applying changes
    Public Paths()              As Path                     'Trajectories
    Public PathsN               As Long                     'Total of trajectories
    Public PathsP               As Long                     'Current trajectory
    Public IsPathModified       As Byte                     'Modified trajectory flag
    Public Path()               As PathPoint                'Path points
    Public PathN                As Long                     'Total of path points
    Public PathP                As Long                     'Current path point
    Public PathTime             As Long                     'Path point frequency
    Public PathNext             As Long                     'Next path point time
    Public PathName             As String                   'Last loaded path
    Public RefreshPoints        As Byte                     'Force update render every time point
    Public GravityMode          As Byte                     'Gravity mode
    Public TerrainMode          As Byte                     'Terrain model
    Public AtmMode              As Byte                     'Atmosphere model
    Public IsAdaptive           As Byte                     'Adaptive time step flag
    Public IsAirspaceDetection  As Byte                     'Airspace detection flag
    Public IsAreaDetection      As Byte                     'Area detection flag
    Public IsAsteroidProximity  As Byte                     'Asteroid proximity detection flag
    Public IsDrag               As Byte                     'Air drag flag
    Public IsBalloonTrajectory  As Byte                     'Balloon trajectory flag
    Public IsEmptyStages        As Byte                     'Empty stage kinetics flag
    Public IsStationVisibility  As Byte                     'Ground station or target visibility detection flag
    Public IsHiDefDetection     As Byte                     'High definition detection flag
    Public IsLift               As Byte                     'Lift force flag
    Public IsThrustSL           As Byte                     'Thrust at Sea Level flag
    Public IsCollision          As Byte                     'Planet collision flag
    Public IsSunShadow          As Byte                     'Sun shadow flag
    Public IsRealTime           As Byte                     'Real time simulation flag
    Public IsSatelliteProximity As Byte                     'Satellite proximity detection flag
    Public IsSolarWind          As Byte                     'Solar wind pressure
    Public IsWind               As Byte                     'Wind force flag
    Public IsStatistics         As Byte                     'Statistics flag
    Public IsLaunchTrajectory   As Byte                     'Trajectory control flag
    Public IsSatelliteControl   As Byte                     'Satellite control flag
    Public IsLandingManeuver    As Byte                     'Landing maneuver control flag
    'Public IsStandByControl     As Byte                     'Stand by control flag
    Public LinkMode             As LinkType                 'External link mode
    Public LinkText             As Byte                     'External binary or text mode
    Public LinkWait             As Byte                     'Wait mode flag
    Public LinkTest             As Byte                     'Test mode flag
    Public LinkLogIn            As Byte, _
           LinkLogIn2           As Byte                     'Log input flags
    Public LinkLogOut           As Byte                     'Log output flag
    Public LinkFullLog          As Byte                     'Full data log flag
    Public LinkDouble           As Byte                     'Double precision flag
    'Public LinkCtlIn            As Byte                     'Control input mode (0=3D vector 1=rotation angles)
    Public LinkCtlOut           As Byte                     'Control output mode (0=DATA command 1=NMEA command)
    Public LinkCtlAxes          As Byte                     'Control input axes mode (0=simulation 1=launch site)
    Public LinkCtlNormalize     As Byte                     'Control normalize input axes flag
    Public LinkAxisX             As Long, _
           LinkAxisY             As Long, _
           LinkAxisZ             As Long                    'Input axes mapping
    Public Const LinkInSize     As Long = 128               'Input buffer size
    Public Const LinkOutSize    As Long = 512               'Output buffer size
    Public LinkIn               As String * LinkInSize, _
           LinkInPos            As Long                     'Input buffer and current position
    Public LinkOut              As String * LinkOutSize     'Output buffer
    Public LinkPos              As Long                     'Buffer pointer
    Public LinkPort             As Long                     'COM port handle
    Public LinkSocket           As Long                     'TCP socket handle
    Public LinkAddr             As SockAddr                 'TCP address
    Public LinkSocketIn         As Long                     'UDP input socket handle
    Public LinkSocketOut        As Long                     'UDP output socket handle
    Public LinkAddrIn           As SockAddr                 'UDP input address
    Public LinkAddrOut          As SockAddr                 'UDP output address
    Public LinkAddrSize         As Long                     'UDP address size
    Public IsLinkError          As Byte                     'Link error flag
    Public RCMode               As Long                     'Remote control mode
    Public RCDevice             As Long                     'Remote control device
    Public RCRSteering          As Long                     'Remote control Rover steering axis
    Public RCRThrust            As Long                     'Remote control Rover thrust axis
    Public RCURoll              As Long                     'Remote control UAV roll axis
    Public RCUPitch             As Long                     'Remote control UAV pitch axis
    Public RCUYaw               As Long                     'Remote control UAV yaw axis
    Public RCUThrust            As Long                     'Remote control UAV thrust axis
    Public Graph2Mode           As Long                     'Input graph mode
    Public Graph2Min()          As Double                   'Input graph minimum value
    Public Graph2Max()          As Double                   'Input graph maximum value
    Public Graph2R()            As Long                     'Input graph histogram values
    Public Graph2H              As Long                     'Input graph histogram pointer
    Public Graph2E              As Long                     'Input graph histogram range
    Public Graph2EMin           As Double                   'Input graph histogram range minimum
    Public Graph2EStep          As Double                   'Input graph histogram range step
    Public Graph2F              As Long                     'Input graph histogram total steps
    Public Graph2W              As Long                     'Input graph view pointer
    Public Graph2I              As Long                     'Input graph total data values
    Public Graph2P              As Long                     'Input graph matrix pointer
    Public Graph2N              As Long                     'Input graph total matrix values
    Public Graph2V()            As Double                   'Input graph matrix
    Public Graph2T()            As Double                   'Input graph start time
    Public Graph2L()            As String                   'Input graph labels
    Public cGraph2()            As Long                     'Input graph colors
    Public Graph2Scroll         As Boolean                  'Input graph auto scroll flag
    Public Graph2Hist           As Boolean                  'Input graph histogram flag
    Public Graph2Step           As Long                     'Input graph histogram steps
    Public Graph2Cmd            As String                   'Input graph read buffer
    Public WaveI                As Long                     'Audio input handler and headers
    Public WaveH()              As WaveHdr                  'Audio input headers
    Public WaveD                As Long, _
           WaveN                As Long, _
           WaveP                As Long                     'Audio input buffer size, number and pointer
    Public WaveCh               As Long, _
           WaveB                As Long, _
           WaveM                As Long                     'Audio input decoder channel, current byte and mask
    Public WaveF                As Long, _
           WaveL0               As Long, _
           WaveL1               As Long, _
           WaveR0               As Long, _
           WaveR1               As Long                     'Audio input decoder flank flag and last levels
    Public WaveZ                As Long, _
           WaveZ0               As Long, _
           WaveZ1               As Long                     'Audio input decoder pointer and flank positions
    Public WaveL()              As Integer                  'Audio input buffers
    Public WaveIn               As String * LinkInSize, _
           WavePos              As Long                     'Audio input text buffer and position
    Public WSAData              As WSAData                  'Windows sockets structure
    Public Gp                   As GdiPlusStartupInput, _
           hGP                  As Long                     'GDI+ API structure and handler
    Public LaunchTrajectory     As Byte                     'Launch trajectory flag
    Public SatelliteControl     As Byte                     'Satellite control flag
    Public LandingManeuver      As Byte                     'Landing maneuver flag
    'Public StandByControl       As Byte                     'Stand by flag
    Public InitD                As InitD, _
           InitS                As InitS                    'Link binary structures
    Public DataD                As DataD, _
           DataS                As DataS
    Public CtrlD                As CtrlD, _
           CtrlS                As CtrlS
    Public Hdr                  As String * 4, _
           Tt                   As Double                   'Link control variables
    Public Stx                  As Byte, _
           Nt                   As Byte, _
           Rt                   As Double
    Public Ut                   As Double, _
           Vt                   As Double, _
           Wt                   As Double
    Public Track1               As Track, _
           Track2               As Track                    'Tracking link mode objects
    Public TrackLog             As Byte, _
           TrackSync            As Byte                     'Tracking log and sync flags
    Public AutoSaveLog          As Byte, _
           LogFile              As Long                     'Auto save log flag and log file handle
    Public LogOption            As LogOptions, _
           LogHandle            As Long                     'Log to file options and handle
    Public AddCmd               As Long                     'Option to add bytes to the command
    Public SavePass             As Byte                     'Save user and password flag
    Public TrackRefresh         As Double                   'Tracking server refresh time
    Public TrackAltitude        As Byte                     'Show path altitudes
    Public TrackFollow          As Long                     'Camera follow tracking mode
    Public RelativeAngles       As Byte                     'Show angles relative to station
    Public Rover()              As Rover                    'Rover link mode object
    Public RoverN               As Long                     'Total of rover objects

'Launch trajectory
    Public Lat                  As Double, _
           Lon                  As Double, _
           Alt                  As Double                   'Latitude, Longitude y Altitude
    Public InitialAngle         As Double                   'Initial angle
    Public StartAngle           As Double, _
           EndAngle             As Double                   'Start and end trajectory angles
    Public StartAlt             As Double, _
           EndAlt               As Double                   'Start and end trajectory altitudes
    Public InitialSpeed         As Double                   'Initial speed
    Public Heading              As Double                   'Trajectory heading, Initial Earth orientation
    Public EarthAngle           As Double                   'Launch Earth orientation
    Public Time                 As Double                   'Launch date and time in J2000 seconds

'Misc
    Public Propellants          As String                   'Propellant list
    Public DragCurves           As String                   'Drag curves list
    Public Curve()              As Engine                   'Thust curves list
    Public Engine()             As Engine                   'Engines list
    Public EngineN              As Long                     'Total of engines
    Public IsTarget             As Long, _
           IsLastTarget         As Long                     'Target object for the onboard sensor
    Public TargetItem           As Long                     'Nearest station or target
    Public TargetClass           As Long                     'Nearest target class
    Public TargetIndex          As Long                     'Nearest target list index
    Public IsTargetN            As Long                     'Total of visible stations or targets
    Public Location()           As Location                 'Locations list
    Public LocationN            As Long                     'Total of locations
    Public IsLocation()         As Byte                     'Visible locations matrix
    Public Station()            As Location                 'Stations
    Public StationN             As Long                     'Total of stations
    Public StationGroup()       As StringItem               'Station groups
    Public StationP             As Long                     'Current station group
    Public StationFirst         As Long                     'First station in the group
    Public StationLast          As Long                     'Last station in the group
    Public StationAngle         As Double                   'Station visibility angle
    Public IsStation()          As Byte                     'Visible stations matrix
    Public Satellite()          As TLE                      'Satellites list
    Public SatelliteN           As Long                     'Total of satellites
    Public SatelliteGroup()     As StringItem               'Satellites groups
    Public SatelliteP           As Long                     'Current satellite group
    Public SatelliteFirst       As Long                     'First satellite in the group
    Public SatelliteLast        As Long                     'Last satellite in the group
    Public SatelliteDist        As Double                   'Satellite proximity distance
    Public SatelliteInRange     As Double                   'Time counter for satellite proximity
    Public IsSatellite()        As Byte                     'Satellites in proximity range matrix
    Public IsSatelliteN         As Long                     'Total of satellites in proximity range
    Public Asteroid()           As Asteroid                 'Asteroids list
    Public AsteroidN            As Long                     'Total of asteroids
    Public AsteroidP            As Long                     'Current asteroid group
    Public AsteroidDist         As Double                   'Asteroid proximity distance
    Public AsteroidDensity      As Long                     'Asteroid density
    Public AsteroidInRange      As Double                   'Time counter for asteroid proximity
    Public IsAsteroid()         As Byte                     'Asteroids in proximity range matrix
    Public IsAsteroidN          As Long                     'Total of asteroids, comets and moons in proximity range
    Public Comet()              As Asteroid                 'Comets list
    Public CometN               As Long                     'Total of Comets
    Public IsComet()            As Byte                     'Comets in proximity range matrix
    Public Moons()              As Asteroid                 'Moons list
    Public MoonN                As Long                     'Total of Moons
    Public IsMoon()             As Byte                     'Moons in proximity range matrix
    Public ObjectVisibility     As Double                   'Object visibility distance
    Public Waypoint()           As Vector2                  '2D waypoint list
    Public WaypointN            As Long                     'Total waypoints
    Public WaypointP            As Long                     'Selected waypoint
    Public Sensor()             As Sensor                   'Sensors
    Public SensorN              As Long                     'Total of sensors
    Public SensorP              As Long                     'Current sensor
    Public Lens()               As Lens                     'Lenses
    Public LensN                As Long                     'Total of lenses
    Public Graphic()            As Graphic                  'Graph data matrix
    Public GraphicN             As Long                     'Total of Graph data points
    Public GraphicMax           As Long                     'Max Graph data points
    Public GraphicP             As Long                     'Current Graph data point
    Public GraphicWin           As Long                     'Graph data viewer pointer
    Public GraphicMode          As Long                     'Graph data flags
    Public GraphicScale         As Double                   'Graph data scale
    Public KMLPathP             As Long                     'Current KML path data point
    Public KMLPathWin           As Long                     'KML path data viewer pointer
    Public KMLPathMode          As Long                     'KML path data flags
    Public KMLPathScale         As Double                   'KML path data scale
    Public KMLPath()            As KMLPoint                 'KML path points
    Public KMLPathN             As Long                     'KML path points count
    Public KMLPathTop           As Long                     'KML path higher point
    Public KMLPathPos           As Long                     'KML path pointer
    Public KMLPathAlt           As Double                   'KML path altitude
    Public KMLPoint()           As Location                 'KML points
    Public KMLPointN            As Long                     'KML points count
    Public Sky()                As StarPos                  'Stars matrix
    Public SkyN                 As Long                     'Stars count
    Public Stars()              As StarPosEx                'Brightest stars for the star tracker
    Public StarsN               As Long                     'Brightest stars count
    Public Airway()             As Airway                   'Airways matrix
    Public AirwayN              As Long                     'Airways count
    Public RNAV()               As Airway                   'RNAV routes matrix
    Public RNAVN                As Long                     'RNAV routes count
    Public IsAirspace()         As Byte                     'Airspace detection matrix
    Public IsAirspaceN          As Long                     'Total of airspaces detected
    Public Airspace()           As Airspace                 'Airspace areas matrix
    Public AirspaceN            As Long                     'Airspace areas count
    Public AirspaceP            As Long                     'Selected airspace areas
    Public AirspaceZone()       As StringValue              'Airspace zones
    Public AirspaceZoneP        As Long                     'Selected airspace zones
    Public AirspaceGroup()      As StringValue              'Airspace groups
    Public AirspaceGroupP       As Long                     'Selected airspace groups
    Public Airport()            As Location                 'Airports list
    Public AirportN             As Long                     'Total of airports
    Public Seaport()            As Location                 'Seaports list
    Public SeaportN             As Long                     'Total of seaports
    Public SeaportZone()        As StringValue              'Seaport zones
    Public City()               As Location                 'Cities list
    Public CityN                As Long                     'Total of cities
    Public CityZone()           As StringValue              'City zones
    Public IsBoundary           As Long                     'Land or water boundary detected
    Public Land()               As Airspace                 'Land boundaries matrix
    Public LandN                As Long                     'Land boundaries count
    Public Sea()                As Airspace                 'Water boundaries matrix
    Public SeaN                 As Long                     'Water boundaries count
    Public IsPopulated          As Long                     'Populated area detected
    Public Areas()              As Airspace                 'Populated areas matrix
    Public AreasN               As Long                     'Populated areas count
    Public AtmFile              As String                   'Last atmospheric model file name
    Public AtmDate              As String, _
           AtmTime              As String                   'Last atmospheric model time
    Public AtmR()               As Byte, _
           AtmB                 As Long, _
           AtmL                 As Long, _
           AtmM                 As Long, _
           AtmL1                As Long, _
           AtmL2                As Long                     'Atmosphere altitude to pressure level conversion matrix
    Public AtmC()               As Single                   'Atmosphere layer standard altitude [m]
    Public AtmD()               As Single                   'Atmosphere layer pressure to altitude conversion
    Public AtmP()               As Single                   'Atmosphere pressure [Pa]
    Public AtmA()               As Single                   'Atmosphere altitude [m]
    Public AtmX()               As Single                   'Atmosphere wind U-vector [m/s]
    Public AtmY()               As Single                   'Atmosphere wind V-vector [m/s]
    Public AtmZ()               As Single                   'Atmosphere wind W-vector [m/s]
    Public AtmT()               As Single                   'Atmosphere temperature [K]
    Public AtmH()               As Single                   'Atmosphere relative humidity [%]
    Public AtmW()               As Single                   'Atmosphere cloud water mixing ratio [kg/kg]
    'Public AtmQ()               As Single                   'Atmosphere absolute vorticity [1/s]
    'Public AtmO()               As Single                   'Atmosphere ozone mixing ratio [kg/kg]
    Public AtmN                 As Long                     'Atmosphere layers count
    Public Report()             As String                   'Report text
    Public ReportPage           As Long                     'Current report page
    Public Links()              As URL                      'Web links matrix
    Public IsInertial           As Boolean                  'Calculate inertial acceleration flag
    Public Units                As Boolean                  'Units system: 0=metric 1=imperial
    Public RelativeTemp         As Boolean                  'Temperature mode: 0=absolute 1=relative
    Public Stimated             As Boolean                  'Time mode in trajectory prediction points
    Public SpeedInfo            As Boolean                  'Speed label mode: 0=auto 1=always orbital
    Public AltitudeInfo         As Boolean                  'Altitude mode: 0=geoid 1=ellipsoid
    Public MagneticMoment       As Boolean                  'Attitude control force: 0=Newtons 1=Amperes per meter squared
    Public AtmPressure          As Boolean                  'Pressures in editors: 0=Pascals/Psi 1=Atmospheres
    Public Degrees              As Long                     'Latitude and Longitude mode: 0=Degrees only 1=Degrees Minutes Seconds
    Public GhostTime            As Double                   'Planets ghosts time
    Public MapMode              As Long                     'Terrain mode for Map panel
    Public MapOverlay           As Long                     'Overlay for Map panel
    Public MapZoom              As Long                     'Zoom level for Map panel
    Public MapInfo              As Boolean                  'Show info in Map panel
    Public MapFollow            As Boolean                  'Follow simulation in Map panel
    Public MapDownload          As Boolean                  'Download tiles for Map panel
    Public GraphHidden          As Boolean                  'Show hidden lines in the propellant graph
    Public GraphData            As Long                     'Propellant graph data selection
    Public GraphDef             As Long                     'Propellant graph precision
    Public GraphStepH           As Double                   'Propellant graph horizontal step
    Public GraphStepV           As Double                   'Propellant graph vertical step
    Public GraphRangeH          As Double                   'Propellant graph horizontal range
    Public GraphRangeV          As Double                   'Propellant graph vertical range
    Public Graph2D()            As Graph2D                  '3D graph matrix
    Public BurnCalc             As Long                     'Type of burn simulation ouput generated
    Public BurnDef              As Double                   'Burn simulation definition
    Public BurnPreview          As Double                   'Burn preview percent
    Public BurnStyle            As Long                     'Burn graph style: 0=simulation 1=throat 2=exit
    Public BurnCurve()          As BurnPoint                'Burn graph matrix
    Public BurnCurveN           As Long                     'Total of Burn graph data points
    Public BurnCurveM           As Long                     'Max Burn graph data points
    Public BurnCurveP           As Long                     'Current Burn graph data point
    Public BurnCurveT           As Double                   'Burn graph timestep
    Public BurnCurveD           As Long                     'Selected burn graph parameter
    Public BurnThroat()         As BurnPointB               'Throat graph matrix
    Public BurnThroatN          As Long                     'Total of Throat graph data points
    Public BurnThroatP          As Long                     'Current Throat graph data point
    Public BurnExit()           As BurnPointB               'Exit graph matrix
    Public BurnExitN            As Long                     'Total of Exit graph data points
    Public BurnExitP            As Long                     'Current Exit graph data point
    Public PressureCurve()      As PressurePoint            'Pressure to burn rate curve matrix
    Public PressureCurveN       As Long                     'Total of pressure to burn rate curve points
    Public SearchClass          As ObjectType, _
           SearchItems          As Long, _
           SearchSort           As Long                     'Search options
    Public IsModel              As Boolean                  'Stage 3D model is changed
    Public IsRestart            As Boolean                  'Restart flag
    Public IsInit               As Boolean                  'Initialization flag
    Public IsDebug              As Boolean                  'Debug mode
    Public IsTest               As Boolean                  'Test mode
    Public IsExit               As Boolean                  'Application will exit
    Public NoSave               As Boolean                  'Do not save configuration on exit
    Public Ini                  As String                   'Config text
    Public List                 As New Collection           'Temporary list
    Public StrBuffer()          As Byte                     'String copy buffer
    Public StrLen               As Long, _
           StrSize              As Long                     'String copy length and buffer size
    Public C1 As Long, D1 As Long, E1 As Long, _
           X0 As Long, Y0 As Long, Z0 As Long, _
           X1 As Double, Y1 As Double, Z1 As Double, _
           X2 As Double, Y2 As Double, Z2 As Double, _
           X3 As Double, Y3 As Double, Z3 As Double, _
           X4 As Double, Y4 As Double, Z4 As Double, _
           X5 As Long, Y5 As Long, Z5 As Long, _
           X6 As Long, Y6 As Long, Z6 As Long, _
           H1 As Double, G1 As Double, A1 As Double, _
           U1 As Double, V1 As Double, W1 As Double, _
           R1 As Double, R2 As Double, _
           RX1 As Double, RY1 As Double, _
           R11 As Double, R12 As Double, _
           RX2 As Double, RY2 As Double, _
           R21 As Double, R22 As Double, _
           F1 As Single, I1 As Single                       'Temporary variables

'Event flags
    Public LiftOff              As Byte, _
           IsLiftOff            As Byte                     'Lift off event
    Public StageEnginesOn       As Byte, _
           IsEnginesEvent       As Byte                     'Stage engines on event
    Public StageEnginesOff      As Byte                     'Stage engines off event
    Public HoverEnginesOn       As Byte                     'Hover engines on event
    Public HoverEnginesOff      As Byte                     'Hover engines off event
    Public LastStageEnginesOff  As Byte                     'Last stage engines off event
    Public SatelliteDeployed    As Byte, _
           IsDeployed           As Byte                     'Satellite deployed event
    Public MoonTransfer         As Byte                     'Moon transfer event
    Public StageAbort           As Byte                     'Stage abort event
    Public EarthSoundSpeed      As Byte, _
           IsEarthSoundSpeed    As Byte                     'Earth sound speed event
    Public EarthOrbitalSpeed    As Byte, _
           IsEarthOrbitalSpeed  As Byte                     'Earth orbital speed event
    Public EarthEscapeSpeed     As Byte, _
           IsEarthEscapeSpeed   As Byte                     'Earth escape speed event
    Public MoonOrbitalSpeed     As Byte, _
           IsMoonOrbitalSpeed   As Byte                     'Moon orbital speed event
    Public MoonEscapeSpeed      As Byte, _
           IsMoonEscapeSpeed    As Byte                     'Moon escape speed event
    Public MarsOrbitalSpeed     As Byte, _
           IsMarsOrbitalSpeed   As Byte                     'Mars orbital speed event
    Public MarsEscapeSpeed      As Byte, _
           IsMarsEscapeSpeed    As Byte                     'Mars escape speed event
    Public Apogee               As Byte, _
           IsApogee             As Byte                     'Apogee event
    Public Perigee              As Byte                     'Perigee event
    Public Aposelene            As Byte                     'Aposelene event
    Public Periselene           As Byte, _
           IsPeriselene         As Byte                     'Periselene event
    Public Apoapsis             As Byte                     'Planet apoapsis event
    Public Periapsis            As Byte                     'Planet periapsis event
    Public ExitAtmosphere       As Byte, _
           IsAtmosphere         As Byte                     'Exit atmosphere event
    Public EnterAtmosphere      As Byte                     'Enter atmosphere event
    Public ExitIonosphere       As Byte, _
           IsIonosphere         As Byte                     'Exit ionosphere event
    Public EnterIonosphere      As Byte                     'Enter ionosphere event
    Public EquidistantPoint     As Byte, _
           IsEquidistantPoint   As Byte                     'Equidistant point event
    Public L1LagrangePoint      As Byte, _
           IsL1LagrangePoint    As Byte                     'L-1 Lagrange point event
    Public MoonDistance         As Byte, _
           IsMoonDistance       As Byte                     'Moon distance event
    Public PlanetDistance       As Byte                     'Planet distance event
    Public PlanetApproach       As Byte                     'Planet approach event
    Public PlanetProximity      As Byte                     'Planet proximity event
    Public PlanetFaraway        As Byte                     'Planet faraway event
    Public BrakingAltitude      As Byte                     'Braking altitude event
    Public StationAvailable     As Byte                     'Station available event
    Public StationUnAvailable   As Byte                     'Station unavailable event
    Public SatelliteProximity   As Byte                     'Satellite proximity event
    Public SatelliteFaraway     As Byte                     'Satellite faraway event
    Public AsteroidProximity    As Byte                     'Asteroid proximity event
    Public AsteroidFaraway      As Byte                     'Asteroid faraway event
    Public EnterAirspace        As Byte                     'Enter airspace event
    Public ExitAirspace         As Byte                     'Exit airspace event
    Public LinkError            As Byte                     'Link error event
    Public IsPlanetCollision    As Byte                     'Planet collision event
    Public TouchDown            As Byte                     'Landing or crashing event

'Statistics
    Public MaxVe                As Double, _
           MinVe                As Double                   'Minimum and maximum relative speed to the Earth
    Public MaxHe                As Double, _
           MinHe                As Double                   'Minimum and maximum relative altitude to the Earth
    Public MaxHm                As Double, _
           MinHm                As Double                   'Minimum and maximum relative speed to the Moon
    Public MaxG                 As Double                   'Maximum acceleration
    Public ControlThrustMax     As Double                   'Maximum thrust used by the attitude control
    Public ThrustDV             As Double                   'Thrust Delta-V
    Public BurnOutDV            As Double                   'Thrust Delta-V at burn out
    Public GravityDV            As Double                   'Gravity minus centripetal acceleration Delta-V
    Public DragDV               As Double                   'Drag Delta-V
    Public LiftDV               As Double                   'Lift Delta-V
    Public Impulse              As Double                   'Thrust impulse

'Render
    Public Cam1                 As Camera                   'Main window camera
    'Public Cam2                 As Camera                   'View panel camera
    Public Cam3                 As Camera                   'Rover 3D model camera
    Public Cam4                 As Camera                   'Stage 3D model camera
    'Public Cam5                 As Camera                   '3D graph camera
    Public Const MinCam         As Long = 6                 'Number of the first extra view camera
    Public Const MinGra         As Long = 6                 'Number of the first 3D graph camera
    Public Cam()                As Camera, _
           CamN                 As Long                     'Extra View cameras
    Public Gra()                As Camera, _
           GraN                 As Long                     'Extra 3D graph cameras
    Public CamX                 As Double, _
           CamY                 As Double, _
           CamZ                 As Double                   'Camera position
    Public CamU                 As Double, _
           CamW                 As Double                   'Camera main plane correction and square size
    Public CamS                 As Long, _
           CamWidth             As Long, _
           CamHeight            As Long                     'Camera square size and window size
    Public CamH                 As Double, _
           CamR                 As Double                   'Camera distance, FOV factor
    Public ShipFactor           As Double                   'Camera iconic ship size
    Public Matrix2D             As MatrixX                  'Camera 2D projection matrix
    Public Follow               As TargetType, _
           LookAt               As TargetType, _
           Relative             As TargetType, _
           ObjectPlanet         As TargetType               'Camera modes
    Public Free                 As Byte, _
           Flat                 As Byte, _
           Ortho                As Byte, _
           IsTactic             As Byte                     'Camera parameters
    Public EarthTexture         As String                   'Texture used for the Earth
    Public Tiles                As Long                     'Number of planet texture tiles
    Public TerrainQuality       As Long                     'Terrain quality
    Public TerrainScale         As Double                   'Terrain vertical scale
    Public GravityField         As Long                     'Gravity field style
    Public GravityScale         As Double                   'Gravity field scale
    Public AtmStyle             As Long, _
           AtmLayer             As Long                     'Atmosphere layers style
    Public Grid                 As Long                     'Latitude and longitude lines step in degrees
    Public Layers               As LayersType               'Layers flags
    Public Color                As ThemeStyle               'Color theme (0=Default 1=Black 2=White)
    Public Theme                As PanelStyle               'Button style (0=White 1=Black 2=Basic)
    Public ButtonColor          As Long                     'Buttons text color
    Public ButtonBack           As Long                     'Buttons color
    Public PanelColor           As Long                     'Panels color
    Public SymbolColor          As Long                     'Symbol buttons color
    Public ViewerColor          As Long                     '3D viewer text color
    Public ViewerBack           As Long                     '3D viewer background color
    Public ModelBack            As Long                     '3D models background color
    Public StarsDensity         As Double                   'Starfield visual magnitude
    Public SunShadow            As Double                   'Ambient light level
    Public RangeScale           As Double                   'Iconic ranges factor
    Public VectorScale          As Double                   'Iconic vectors factor
    Public ForceScale           As Double                   'Iconic forces factor
    Public PointScale           As Double                   'Iconic points altitude correction
    Public ShipScale            As Double                   'Iconic ship factor
    Public ShipSize             As Double                   'Iconic ship size
    'Public TrackSize            As Double                  'Iconic tracking object size
    'Public StationSize          As Double                  'Iconic ground station size
    Public ShowIcons            As Long                     'Spacecraft, tracking and station icon flags
    Public HideCursor           As Boolean, _
           IsCursor             As Boolean                  'Selection cursor flags
    Public AxesMove             As Long, _
           AxesRotate           As Long, _
           AxesZoom             As Boolean                  'Move, rotate and zoom mouse flags
    Public ShowErrors           As Boolean                  'Show simulation errors
    Public TipDelay             As Double                   'Tooltip delay
    Public Asymmetric           As Double                   'Asymmetric render deviation
    Public Tool                 As Tools                    'Render tools
    Public ToolX                As ToolsX                   'Render ranges and vectors tools
    Public ToolL                As ToolsL                   'Labels for the render tools
    Public Plane                As PlaneType                'Simulation main plane
    Public SyncTimer            As Boolean, _
           TacticTimer          As Boolean                  'Timer flags

'Screen
    Public DataMode             As Long                     'Data panel mode
    Public DataTool             As ToolsD, _
           DataToolX            As ToolsD                   'Data panel flags
    Public DataToolTip1()       As String, _
           DataToolTip2()       As String, _
           DataToolTip3()       As String                   'Data panel tooltip list
    Public GraphToolTip1()      As String, _
           GraphToolTip2()      As String                   'Graph panel tooltip list
    Public BurnDataToolTip1()   As String, _
           BurnDataToolTip2()   As String, _
           BurnLogToolTip()     As String                   'Burn editor tooltip data and list
    Public ExtendedLog          As Long                     'Extended log mode
    Public HiDefLog             As Long                     'High definition parameters in log
    Public FmtLog               As String                   'Parameter format in log
    Public ToolMode             As Long                     'Toolbar mode
    Public ColorPalette         As Long                     'Color selector palette style
    Public ColorMode            As Long                     'Color selector mode
    Public BrowserBar           As Boolean                  'Web browser address bar visible
    Public IsSelect             As Boolean                  'Mouse select flag
    Public SelectedType         As ObjectType, _
           SelectedN            As Long                     'Selected object type and number
    Public LastType             As ObjectType, _
           LastN                As Long                     'Last selected object
    Public SelType1             As ObjectType, _
           SelN1                As Long, _
           SelType2             As ObjectType, _
           SelN2                As Long, _
           SelType3             As ObjectType, _
           SelN3                As Long                     'Selected objects for the ruler tool
    Public LastSelType          As ObjectType, _
           LastSelN             As Long, _
           SelType              As ObjectType, _
           SelN                 As Long, _
           SelDist              As Single, _
           SelW                 As Single, _
           SelH                 As Single, _
           SelR                 As Single, _
           SelShift             As Integer                  'Temporary selection variables
    Public OverlayX             As Single, _
           OverlayY             As Single                   'Overlay current drawing position
    Public TacticSpeed As Double                            'Demo camera rotation speed
    Public MouseX               As Single, _
           MouseY               As Single                   'Mouse position
    Public ClickX               As Single, _
           ClickY               As Single                   'Mouse click position
    Public Click                As PointApi                 'Mouse click position
    Public IsClick              As Boolean                  'Mouse click flag
    Public Mouse                As PointApi                 'Mouse position
    Public Last                 As PointApi                 'Last mouse position
    Public LastButton           As Integer                  'Last mouse button pressed
    Public MouseButton          As Integer, _
           IsButton             As Boolean                  'Mouse buttons state
    Public NoClick              As Boolean                  'No mouse click flag
    Public NoMove               As Boolean                  'No mouse move flag
    Public NoResize             As Long                     'No main window resize counter
    Public NoResizeView         As Boolean                  'No View panel resize flag
    Public Keys(255)            As Byte                     'Key press matrix
    Public MouseAxisX           As Single, _
           MouseAxisY           As Single                   'Mouse speed
    Public WindowSizes()        As String                   'Window sizes matrix
    Public IsScreenPrimary      As Boolean                  'Current display is primary
    Public PrimaryWidth         As Long, _
           PrimaryHeight        As Long                     'Primary display size
    Public IsMultiScreen              As Boolean                  'Multiple displays flag
    Public ScreenLeft           As Long, _
           ScreenTop            As Long, _
           ScreenWidth          As Long, _
           ScreenHeight         As Long, _
           ScreenRight          As Long, _
           ScreenBottom         As Long                     'Current display position and size
    Public FullLeft             As Long, _
           FullTop              As Long, _
           FullWidth            As Long, _
           FullHeight           As Long, _
           FullRight            As Long, _
           FullBottom           As Long                     'Multiple display position and size
    Public TwipX                As Single, _
           TwipY                As Single                   'Twips per pixel
    Public Record               As PlayMode                 'Record video mode
    Public VideoMode            As Long                     'Video capture mode
    Public CaptureMouse         As Boolean                  'Capture mouse icon flag
    Public VideoHdr             As AVIStreamInfo            'Video stream settings
    Public VideoOpts            As AVICompressOptions       'Video codec settings
    Public VideoSize            As Long                     'Video player size
    Public VideoSync            As Boolean                  'Video player sync
    Public VideoControls        As Boolean                  'Video player controls
    Public VideoDevices()       As String, _
           AudioDevices()       As String                   'Video and audio input devices
    Public VideoBuffer()        As Byte                     'Video input buffer

'Viewer colors
    Public cShipModel           As Long                     'Spacecraft model default
    Public cShipEngineA         As Long                     'Spacecraft engine glow A
    Public cShipEngineB         As Long                     'Spacecraft engine glow B
    Public cShipEngineC         As Long                     'Spacecraft engine glow C
    Public cRoverModel          As Long                     'Rover model default

'Viewer-Icons
    Public cWhiteDot            As Long                     'Big white dot
    Public cGreenDot            As Long                     'Big green dot
    Public cSmallGreenDot       As Long                     'Small green dot
    Public cRedDot              As Long                     'Big red dot
    Public cSmallRedDot         As Long                     'Small red dot

'Viewer-Selection ruler
    Public cRulerLine           As Long                     'Selection ruler lines

'Viewer-Selection information
    Public cInfoBack            As Long                     'Information overlay background
    Public cInfoLine            As Long                     'Information overlay lines
    Public cInfoLineB           As Long                     'Information overlay lines B

'Viewer-Color scale
    Public cColorScale          As Long                     'Color scale box

'Tools-Airways
    Public cAirspaces           As Long                     'Airspaces
    Public cAirspacesLine       As Long                     'Airspaces lines
    Public cAirspacesLineB      As Long                     'Airspaces lines at ground
    Public cAirwaysLower        As Long                     'Lower airways lines
    Public cAirwaysUpper        As Long                     'RNAV routes lines

'Tools-Altimeter/Distances
    Public cAltimeterLine       As Long                     'Altimeter line
    Public cDistancesLine       As Long                     'Distances circles
    Public cDistancesCross      As Long                     'Distances cross

'Tools-Asteroids/Comets/Moons
    Public cAsteroidPoint       As Long                     'Asteroids, comets and moons point
    Public cAsteroidLine        As Long                     'Asteroids, comets and moons proximity line

'Tools-Atmosphere glow
    Public cAtmosphereTemp1     As Long                     'Atmosphere temperature at -50ºC
    Public cAtmosphereTemp2     As Long                     'Atmosphere temperature at -25ºC
    Public cAtmosphereTemp3     As Long                     'Atmosphere temperature at -10ºC
    Public cAtmosphereTemp4     As Long                     'Atmosphere temperature at 5ºC
    Public cAtmosphereTemp5     As Long                     'Atmosphere temperature at 20ºC
    Public cAtmosphereTemp6     As Long                     'Atmosphere temperature at 35ºC
    'Public cAtmosphereClouds    As Long                     'Atmosphere clouds (gradient)
    'Public cAtmosphereWater     As Long                     'Atmosphere water (gradient)
    Public cAtmosphereWind1     As Long                     'Atmosphere wind at 5 m/s
    Public cAtmosphereWind2     As Long                     'Atmosphere wind at 15 m/s
    Public cAtmosphereWind3     As Long                     'Atmosphere wind at 25 m/s
    Public cAtmosphereWind4     As Long                     'Atmosphere wind at 50 m/s
    Public cAtmosphereWind5     As Long                     'Atmosphere wind at 75 m/s
    Public cAtmosphereWind6     As Long                     'Atmosphere wind at 100 m/s
    Public cAtmosphereWind7     As Long                     'Atmosphere wind at 125 m/s
    'Public cAtmosphereWindV     As Long                     'Atmosphere wind vectors (gradient)
    Public cGlowEarthA          As Long                     'Earth atmosphere glow air
    Public cGlowEarthG          As Long                     'Earth atmosphere glow ground
    Public cGlowMarsA           As Long                     'Mars atmosphere glow air
    Public cGlowMarsG           As Long                     'Mars atmosphere glow ground
    Public cGlowSunA            As Long                     'Sun glow A
    Public cGlowSunB            As Long                     'Sun glow B
    Public cGlowSunC            As Long                     'Sun glow C

'Tools-Axes
    Public cAxesLine            As Long                     'Reference axes lines
    Public cAxesPlanet          As Long                     'Planet axes lines
    Public cAxesShip            As Long                     'Spacecraft axes lines
    Public cAxesModel           As Long                     '3D model axes lines

'Tools-Boundaries
    Public cAreasLine           As Long                     'Populated areas lines
    Public cLandLine            As Long                     'Land boundaries lines
    Public cLandLineB           As Long                     'Land boundaries lines for the White texture
    Public cSeaLine             As Long                     'Water boundaries lines

'Tools-Compass/Grid
    Public cCompassCircle       As Long                     'Compass circle
    Public cCompassCross        As Long                     'Compass cross
    Public cCompassDiskA        As Long                     'Compass inner disk
    Public cCompassDiskB        As Long                     'Compass outer disk
    Public cCompassGrid         As Long                     'Background grid

'Tools-Gravity field
    Public cGravityDisk         As Long                     'Gravity field disk
    Public cGravityDisk1A       As Long                     'Gravity field disk 1A
    Public cGravityDisk1B       As Long                     'Gravity field disk 1B
    Public cGravityDisk2A       As Long                     'Gravity field disk 2A
    Public cGravityDisk2B       As Long                     'Gravity field disk 2B
    Public cGravityDisk3A       As Long                     'Gravity field disk 3A
    Public cGravityDisk3B       As Long                     'Gravity field disk 3B
    'Public cGravityDisk4A       As Long                     'Gravity field disk 4A
    'Public cGravityDisk4B       As Long                     'Gravity field disk 4B
    Public cGravityZone1        As Long                     'Gravity field zone at 10 m/s²
    Public cGravityZone2        As Long                     'Gravity field zone at 1 m/s²
    Public cGravityZone3        As Long                     'Gravity field zone at 100 mm/s²
    Public cGravityZone4        As Long                     'Gravity field zone at 10 mm/s²
    Public cGravityGrid         As Long                     'Gravity field grid
    Public cGravityVectorA      As Long                     'Gravity field vector A
    Public cGravityVectorB      As Long                     'Gravity field vector B

'Tools-Grid
    Public cPlanetGrid          As Long                     'Planet grid line
    Public cPlanetGridD         As Long                     'Planet grid divisor
    Public cPlanetGridG         As Long                     'Planet grid Greenwich meridian
    Public cPlanetGridE         As Long                     'Planet grid Equator parallel

'Tools-Magnetic field
    'Public cMagneticLines       As Long                    'Magnetic field lines (gradient)
    'Public cMagneticPlanes      As Long                    'Magnetic field planes (gradient)
    Public cMagneticVectorsN    As Long                     'Magnetic field vectors Noth Pole
    Public cMagneticVectorsS    As Long                     'Magnetic field vectors South Pole

'Tools-Path
    Public cPathA               As Long                     'Spacecraft path after the current point
    Public cPathB               As Long                     'Spacecraft path before the current point
    Public cPathFlatA           As Long                     'Spacecraft path after the current point in flat view
    Public cPathFlatB           As Long                     'Spacecraft path before the current point in flat view
    Public cPathStationA        As Long                     'Spacecraft path when a station is available
    Public cPathStationU        As Long                     'Spacecraft path when a station is unavailable
    Public cPathKML             As Long                     'KML path default
    Public cPathKMLLine         As Long                     'KML path altitude lines
    Public cPathKMLPlane        As Long                     'KML path altitude planes
    Public cPathTrack           As Long                     'Tracking path
    Public cPathTrackKML        As String                   'Tracking path in KML
    Public cPathStation         As Long                     'Ground station path
    Public cPathStationKML      As String                   'Ground station path in KML
    Public cTrackSphere         As Long                     'Tracking object distance
    Public cTrackLine           As Long                     'Tracking object line

'Tools-Planets
    Public cPlanetSun           As Long                     'Sun 3D model
    Public cPlanetEarth         As Long                     'Earth 3D model
    Public cPlanetMoon          As Long                     'Moon 3D model
    Public cPlanetMercury       As Long                     'Mercury 3D model
    Public cPlanetVenus         As Long                     'Venus 3D model
    Public cPlanetMars          As Long                     'Mars 3D model
    Public cPlanetJupiter       As Long                     'Jupiter 3D model
    Public cPlanetSaturn        As Long                     'Saturn 3D model
    Public cPlanetUranus        As Long                     'Uranus 3D model
    Public cPlanetNeptune       As Long                     'Neptune 3D model
    Public cPlanetPluto         As Long                     'Pluto 3D model
    Public cPlanetIo            As Long                     'Io 3D model
    Public cPlanetEuropa        As Long                     'Europa 3D model
    Public cPlanetGanymede      As Long                     'Ganymede 3D model
    Public cPlanetCallisto      As Long                     'Callisto 3D model
    Public cPlanetTitan         As Long                     'Titan 3D model
    Public cPlanetTriton        As Long                     'Triton 3D model
    Public cPlanetCeres         As Long                     'Ceres 3D model

'Tools-Satellites
    Public cSatellitePoint      As Long                     'Satellite point
    Public cSatelliteLine       As Long                     'Satellite proximity line

'Tools-Stars
    Public cStars               As Long                     'Stars point

'Tools-Stations
    Public cStationLine         As Long                     'Ground station availability line
    Public cStationTarget       As Long                     'Ground station target line

'Tools-Trajectory
    Public cTrajectory          As Long                     'Trajectory prediction line

'Tools->Ranges-Altitude
    Public cAltitudeCircle      As Long                     'Spacecraft altitude circle

'Tools->Ranges-Atmosphere/Ionosphere
    Public cAtmosphereA         As Long                     'Earth atmosphere A
    Public cAtmosphereB         As Long                     'Earth atmosphere B
    Public cIonosphereA         As Long                     'Earth ionosphere A
    Public cIonosphereB         As Long                     'Earth ionosphere B

'Tools->Ranges-Apoapsis/Periapsis
    Public cApoapsis            As Long                     'Spacecraft apoapsis
    Public cPeriapsis           As Long                     'Spacecraft periapsis

'Tools->Ranges-Braking distance
    Public cBrakingA            As Long                     'Spacecraft braking distance A
    Public cBrakingB            As Long                     'Spacecraft braking distance B

'Tools->Ranges-Orbital plane
    Public cOrbitalPlane        As Long                     'Spacecraft orbital plane on Earth surface
    Public cOrbitalPlaneM       As Long                     'Spacecraft orbital plane at Moon distance

'Tools->Ranges-Objects orbits
    Public cAsteroidOrbit       As Long                     'Asteroids, comets and moons orbit
    Public cAsteroidOrbitSel    As Long                     'Asteroids, comets and moons selected orbit
    Public cSatelliteOrbit      As Long                     'Satellites orbit
    Public cSatelliteOrbitSel   As Long                     'Satellites selected orbit

'Tools->Ranges-Planets orbits
    Public cPlanetOrbit         As Long                     'Planet orbit
    Public cPlanetOrbitSel      As Long                     'Planet selected orbit

'Tools->Vectors-Air drag and lift
    Public cAirDrag             As Long                     'Spacecraft air drag deceleration
    Public cAirLift             As Long                     'Spacecraft air lift acceleration

'Tools->Vectors-Altitude to plane
    Public cAltitudePlane       As Long                     'Spacecraft altitude to reference plane

'Tools->Vectors-Ground projection
    Public cAltitudeProj        As Long                     'Spacecraft ground projection

'Tools->Vectors-Ground speed
    Public cSpeedGround         As Long                     'Spacecraft speed relative to ground

'Tools->Vectors-Magnetic field vector
    Public cMagneticCompassN    As Long                     'Magnetic field at spacecraft position N
    Public cMagneticCompassS    As Long                     'Magnetic field at spacecraft position S

'Tools->Vectors-Net gravity
    Public cNetGravity          As Long                     'Spacecraft net gravity force

'Tools->Vectors-Speed
    Public cSpeedCenter         As Long                     'Spacecraft speed relative to planet center

'Tools->Vectors-Vertical speed
    Public cSpeedVertical       As Long                     'Spacecraft vertical speed

'Tools->Vectors-Unused
    'Public cAcceleration        As Long                     'Spacecraft acceleration
    'Public cCentripetal         As Long                     'Spacecraft centripetal acceleration
    'Public cSpeedHorizontal     As Long                     'Spacecraft horizontal speed
    'Public cSpeedAir            As Long                     'Atmosphere speed
    'Public cSpeedAirVector      As Long                     'Atmosphere speed vector
    'Public cAirLiftVector       As Long                     'Air lift vector
    'Public cAirRollVector       As Long                     'Air roll vector

'Tools->Sensor-Deviation
    Public cDeviationLine       As Long                     'Deviation line
    Public cDeviationBackA      As Long                     'Deviation background
    Public cDeviationBackB      As Long                     'Deviation background B
    Public cDeviationCenter     As Long                     'Deviation center line

'Tools->Sensor-FOV projection
    Public cSensorFOVLineA      As Long                     'Sensor FOV projection lines A
    Public cSensorFOVLineB      As Long                     'Sensor FOV projection lines B
    Public cSensorFOVPlaneA     As Long                     'Sensor FOV projection plane A
    Public cSensorFOVPlaneB     As Long                     'Sensor FOV projection plane B

'Tools->Sensor-Trace
    Public cTraceLine           As Long                     'Trace line
    Public cTraceCursor         As Long                     'Trace cursor
    Public cTraceCenter         As Long                     'Trace center lines

'3DGraph-Graph colors
    Public cGraph3DFaceBlue     As Long                     '3D graph faces blue
    Public cGraph3DFaceRed      As Long                     '3D graph faces red
    Public cGraph3DFaceGreen    As Long                     '3D graph faces green
    Public cGraph3DFaceYellow   As Long                     '3D graph faces yellow
    Public cGraph3DFaceGray     As Long                     '3D graph faces gray
    Public cGraph3DFaceWhite    As Long                     '3D graph faces white

'3DGraph-Line colors
    Public cGraph3DLineBlue     As Long                     '3D graph lines blue
    Public cGraph3DLineRed      As Long                     '3D graph lines red
    Public cGraph3DLineGreen    As Long                     '3D graph lines green
    Public cGraph3DLineGray     As Long                     '3D graph lines gray
    Public cGraph3DLineBlack    As Long                     '3D graph lines black

'3DGraph-Projection lines
    Public cGraph3DAxes         As Long                     '3D graph axes lines
    Public cGraph3DPlane        As Long                     '3D graph lines projection
    Public cGraph3DMax          As Long                     '3D graph maximum value line
    Public cGraph3DSel          As Long                     '3D graph selected value line

'Graph-Grid
    Public cGraphLine           As Long                     'Graph reference lines

'Graph-Lines
    Public cGraphThrust         As Long                     'Spacecraft mass line
    Public cGraphAccel          As Long                     'Spacecraft acceleration line
    Public cGraphDrag           As Long                     'Spacecraft drag line
    Public cGraphTemp           As Long                     'Spacecraft temperature line
    Public cGraphLink           As Long                     'Available stations background
    Public cGraphAirspace       As Long                     'Detected airspaces background
    Public cGraphLinkAirspace   As Long                     'Available stations and detected airspaces background
    Public cGraphAltitude       As Long                     'Spacecraft altitude line
    Public cGraphSpeed          As Long                     'Spacecraft speed line
    Public cGraphHumidity       As Long                     'Air humidity line

'Input graph lines
    Public cGraph2A             As Long                     'Input graph data 1 line
    Public cGraph2B             As Long                     'Input graph data 2 line
    Public cGraph2C             As Long                     'Input graph data 3 line
    Public cGraph2D             As Long                     'Input graph data 4 line
    Public cGraph2E             As Long                     'Input graph data 5 line
    Public cGraph2F             As Long                     'Input graph data 6 line
    Public cGraph2G             As Long                     'Input graph data 7 line
    Public cGraph2H             As Long                     'Input graph data 8 line

'Burn editor
    Public cBurnThrust          As Long                     'Burn graph thrust line
    Public cBurnPressure        As Long                     'Burn graph pressure line
    Public cBurnRate            As Long                     'Burn graph burn rate line
    Public cBurnRatio           As Long                     'Burn graph nozzle expansion ratio line
    Public cBurnThroatClass     As Long                     'Nozzle throat total impulse line
    Public cBurnThroatIsp       As Long                     'Nozzle throat specific impulse line
    Public cBurnThroatThrust    As Long                     'Nozzle throat maximum thrust line
    Public cBurnThroatPressure  As Long                     'Nozzle throat maximum pressure line
    Public cBurnExitClass       As Long                     'Nozzle exit total impulse line
    Public cBurnExitIsp         As Long                     'Nozzle exit specific impulse line
    Public cBurnExitThrust      As Long                     'Nozzle exit maximum thrust line
    Public cBurnExitPressure    As Long                     'Nozzle exit throat maximum pressure line

'2DRender-Spacecraft
    Public cShipModel2D         As Long                     'Spacecraft line
    Public cShipEngineA2D       As Long                     'Spacecraft engine glow A
    Public cShipEngineB2D       As Long                     'Spacecraft engine glow B

'2DRender-Tools
    Public cAltitudeCircle2D    As Long                     'Spacecraft altitude circle
    Public cPlanetOrbit2D       As Long                     'Planet orbit
    Public cAtmosphere2D        As Long                     'Earth atmosphere
    Public cIonosphere2D        As Long                     'Earth ionosphere
    Public cApoapsis2D          As Long                     'Spacecraft apoapsis
    Public cPeriapsis2D         As Long                     'Spacecraft periapsis
    Public cBraking2D           As Long                     'Spacecraft braking distance
    Public cPath2D              As Long                     'Spacecraft path
    Public cSnapLabel2D         As Long                     'Snapshot label
    Public cSnapPoint2D         As Long                     'Snapshot point
    Public cSpeedCenter2D       As Long                     'Spacecraft speed relative to planet center
    Public cSpeedGround2D       As Long                     'Spacecraft speed relative to ground
    Public cAltimeterLine2D     As Long                     'Altimeter line

'2DRender-Planets
    Public cPlanetSun2D         As Long                     'Sun
    Public cPlanetEarth2D       As Long                     'Earth
    Public cPlanetMoon2D        As Long                     'Moon
    Public cPlanetMercury2D     As Long                     'Mercury
    Public cPlanetVenus2D       As Long                     'Venus
    Public cPlanetMars2D        As Long                     'Mars
    Public cPlanetJupiter2D     As Long                     'Jupiter
    Public cPlanetSaturn2D      As Long                     'Saturn
    Public cPlanetUranus2D      As Long                     'Uranus
    Public cPlanetNeptune2D     As Long                     'Neptune
    Public cPlanetPluto2D       As Long                     'Pluto

'Basic color constants
    Public Const cBlack         As Long = &HFF000000        'Black
    Public Const cGray          As Long = &HFF808080        'Gray
    Public Const cLightGray     As Long = &HFFC0C0C0        'LightGray
    Public Const cDarkWhite     As Long = &HFFF0F0F0        'DarkWhite
    Public Const cWhite         As Long = &HFFFFFFFF        'White
    Public Const cRed           As Long = &HFF0000FF        'Red
    Public Const cGreen         As Long = &HFF00FF00        'Green
    Public Const cBlue          As Long = &HFFFF0000        'Blue
    Public Const cYellow        As Long = &HFF00FFFF        'Yellow
    Public Const cCyan          As Long = &HFFFFFF00        'Cyan
    Public Const cMagenta       As Long = &HFFFF00FF        'Magenta

'Planets proximity
    Public Const hEarthProximity    As Double = 1000000000   '< Earth proximity zone
    Public Const hMoonProximity     As Double = 50000000     '< Moon proximity zone
    Public Const hMercuryProximity  As Double = 1000000000   '< Mercury proximity zone
    Public Const hVenusProximity    As Double = 1000000000   '< Venus proximity zone
    Public Const hMarsProximity     As Double = 1000000000   '< Mars proximity zone
    Public Const hJupiterProximity  As Double = 15000000000# '< Jupiter proximity zone
    Public Const hSaturnProximity   As Double = 12000000000# '< Saturn proximity zone
    Public Const hUranusProximity   As Double = 5000000000#  '< Uranus proximity zone
    Public Const hNeptuneProximity  As Double = 5000000000#  '< Neptune proximity zone
    Public Const hPlutoProximity    As Double = 1000000000   '< Pluto proximity zone

'Camera distances
    Public Const hPointSize         As Single = 3            'Object point size
    Public Const hAsteroidsOrbit    As Double = 3000000000#  '> Asteroids orbits
    Public Const hCometsOrbit       As Double = 3000000000#  '> Comets orbits
    Public Const hMoonsOrbit        As Double = 300000000    '> Moons orbits
    Public Const hMoonOrbit         As Double = 150000000000# '< Moon orbit and label
    Public Const hPlanetsOrbit      As Double = 3000000000#  '> Planet orbit
    Public Const hPlanetsGrid       As Double = 47           '< Planet grid (radius factor)
    Public Const hPlanetsModel      As Double = 1000         '< Planet 3D model (radius factor)
    Public Const hPlanetsTexture    As Double = 100000000000# '< Planet texture
    Public Const hPlanetsGlow       As Double = 50000000     '< Planet glow maximum
    Public Const hPlanetsGlowL      As Double = 5000         '> Planet glow minimum
    Public Const hEarthGlowL        As Double = 10000        'Earth glow low altitude
    Public Const hEarthGlowH        As Double = 100000       'Earth glow high altitude
    Public Const hMarsGlowL         As Double = 5000         'Mars glow low altitude
    Public Const hMarsGlowH         As Double = 50000        'Mars glow high altitude
    Public Const hSunGlow           As Double = 150000000000# '<-> Sun glow
    Public Const hMagneticField     As Double = 200000000    '< Magnetic field vectors
    Public Const hAirways           As Double = 30000000     '< Airways
    Public Const hAirwaysFlat       As Double = 100000000    '< Airways in flat mode
    Public Const hAirwaysLabels     As Double = 1000000      '< Airways labels
    Public Const hReferences        As Double = 300000000    '< Reference points
    Public Const hCities            As Double = 100000000    '< Cities
    Public Const hCitiesLabels      As Double = 15000000     '< Cities labels far
    Public Const hCitiesLabelsH     As Double = 1000000      '< Cities labels near
    Public Const hLocations         As Double = 300000000    '< Locations
    Public Const hStations          As Double = 300000000    '< Radio-stations
    Public Const hAtmosphere        As Double = 1000000000   '< Atmosphere
    Public Const hAtmosphereLabels  As Double = 6000000      '< Atmosphere labels

'Folders
    Public AppPathName          As String                   'Application directory
    Public AppPath              As String                   'Application directory
    Public EXEPathName          As String                   'Executable directory and name
    Public AirwaysDir           As String                   'Airways directory
    Public AtmosphereDir        As String                   'Atmosphere directory
    Public DataDir              As String                   'Data directory
    Public EnginesDir           As String                   'Engines directory
    Public GraphsDir            As String                   'Graphs directory
    Public ImagesDir            As String                   'Images directory
    Public LocationsDir         As String                   'Locations directory
    Public LogsDir              As String                   'Logs directory
    Public ModelsDir            As String                   'Models directory
    Public PathsDir             As String                   'Paths directory
    Public PlanetsDir           As String                   'Planets directory
    Public PresetsDir           As String                   'Presets directory
    Public PropellantsDir       As String                   'Propellants directory
    Public SatellitesDir        As String                   'Satellites directory
    Public SensorsDir           As String                   'Sensors directory
    Public SourceDir            As String                   'Source directory
    Public StationsDir          As String                   'Stations directory
    Public TerrainDir           As String                   'Terrain directory
    Public TexturesDir          As String                   'Textures directory
    Public ToolBarDir           As String                   'Toolbar directory
    Public VideosDir            As String                   'Videos directory

'Constants
    Public Planets(ToNext To ToPluto)         As String     'Planets names
    Public PlanetApoapsis(ToNext To ToPluto)  As String     'Planets apoapsis names
    Public PlanetPeriapsis(ToNext To ToPluto) As String     'Planets periapsis names
    'Public PlanetLink(ToNext To ToPluto)      As String     'Planets web links
    'Public MoonLink(ToNext To ToPluto)        As String     'Planet's moons web links
    Public AsteroidZones()      As String, _
           AsteroidList         As String                   'Asteroid zones
    Public CometGroups()        As String, _
           CometList            As String                   'Comet groups
    Public Months(12)           As String                   'Months names
    Public Days(7)              As String                   'Days names
    Public Types(TypeLast)      As String                   'Object type names
    Public ObjectLink(TypeLast) As String                   'Object web links
    Public InfoNames            As String                   'Object names database
    Public Const G          As Double = 6.67428E-11         'Universal gravitational constant
    Public Const S0         As Double = 274                 'Sun surface gravity in meters per square second
    Public Const G0         As Double = 9.80665             'Earth surface gravity in meters per square second
    Public Const M0         As Double = 1.622               'Moon surface gravity in meters per square second
    Public Const AU         As Double = 149597870700#       'Astronomic unit in meters
    Public Const AirD       As Double = 1.225               'Earth atmosphere density at sea level in kilograms per cubic meter
    Public Const AirP       As Double = 101325              'Earth atmosphere pressure at sea level in pascals
    Public Const AirM       As Double = 1.50267595631405E-02 'Mars atmosphere density at ground Level in kilograms per cubic meter
    Public Const Kelvin     As Double = 273.15              'Temperature in kelvin of water melting point
    Public Const Rankine    As Double = 459.69              'Temperature in rankine of water melting point
    Public Const Sigma      As Double = 0.000000056703      'Stefan-Boltzmann constant
    Public Const Runiv      As Double = 8314.462175         'Universal gas constant in joules per kmol per kelvin
    Public Const Field      As Double = 0.00005             'Average Earth magnetic field in teslas
    Public Const Mach       As Double = 343                 'Sound speed at sea level in meters per second
    Public Const Mile       As Double = 1609.344            'Mile to meter conversion
    Public Const NMile      As Double = 1852                'Nautical mile to meter conversion
    Public Const Foot       As Double = 0.3048              'Foot to meter conversion
    Public Const Foot2      As Double = Foot * Foot         'Square foot to square meter conversion
    Public Const Foot3      As Double = Foot * Foot * Foot  'Cubic foot to cubic meter conversion
    Public Const Foot10     As Double = Foot * 10           '10 feet in meters
    Public Const Foot100k   As Double = Foot * 100000       '100,000 feet in meters
    Public Const Inch       As Double = 0.0254              'Inch to meter conversion
    Public Const Inch2      As Double = Inch * Inch         'Square inch to square meter conversion
    Public Const Inch3      As Double = Inch * Inch * Inch  'Cubic inch to cubic meter conversion
    Public Const Knot       As Double = 0.514444444444444   'Knots to meter per second conversion
    Public Const Mph        As Double = 0.44704             'Miles per hour to meter per second conversion
    Public Const Kph        As Double = 0.277777777777778   'Kilometer per hour to meter per second conversion
    Public Const Pound      As Double = 0.45359237          'Pound to kilogram conversion
    Public Const PoundForce As Double = Pound * G0          'Pound force to newton conversion
    Public Const Ounce      As Double = 0.028349523         'Ounce to kilogram conversion
    Public Const Pascal     As Double = 101325              'Atmosphere to pascal conversion
    Public Const Bar        As Double = 100000              'Bar to pascal conversion
    Public Const Psi        As Double = PoundForce / Inch2  'Psi to pascal conversion
    Public Const Min        As Double = 60                  '1 minute in seconds
    Public Const Hour       As Double = 3600                '1 hour in seconds
    Public Const Day        As Double = 86400               '1 day in seconds
    Public Const Week       As Double = 604800              '1 week in seconds
    Public Const Year       As Double = 31536000            '365 days in seconds
    Public Const YearR      As Double = 31558150            '1 year in seconds
    Public Const YearDays   As Double = 365.256366          '1 year in days
    Public Const Hour3      As Double = Hour * 3            '3 hours in seconds
    Public Const Hour6      As Double = Hour * 6            '6 hours in seconds
    Public Const J2000      As Double = 2451545             'J2000 in Julian days
    Public Const MJD        As Double = 2400000.5           'MJD in Julian days
    Public Const MinDate    As Double = -59958187200#       'Minimum date: 0100-01-01 00:00:00 UTC
    Public Const MaxDate    As Double = 252455572799#       'Maximum date: 9999-01-01 23:59:59 UTC
    Public Const AxialTilt  As Double = 23.4376906116422    'Earth axial tilt at Epoch J2000.0
    Public Const StarsDistance As Double = 1E+30            'Starfield drawing distance
    Public Const SunLuminosity As Double = 3.846E+26        'Sun luminosity in watts
    Public Const LightYear  As Double = 9.4607304725808E+15 'Light year to meters conversion
    Public Const LightSpeed As Double = 299792458           'Light speed in meters per second
    Public Const Log2       As Double = 0.693147180559945   'Logarithm base e of 2
    Public Const Log10      As Double = 2.30258509299405    'Logarithm base e of 10
    Public Const Sq2        As Double = 1.4142135623731     'Square root of 2
    Public Const Pi         As Double = 3.14159265358979    'Pi
    Public Const Rad        As Double = 1.74532925199433E-02 'Degrees to radians conversion
    Public Const Deg        As Double = 57.2957795130823    'Radians to degrees conversion
    Public Const Hrs        As Double = 3.81971863420549    'Radians to hours conversion
    Public Const A30        As Double = 0.523598775598299   '30º in radians
    Public Const A45        As Double = 0.785398163397448   '45º in radians
    Public Const A90        As Double = 1.5707963267949     '90º in radians
    Public Const A180       As Double = 3.14159265358979    '180º in radians
    Public Const A270       As Double = 4.71238898038469    '270º in radians
    Public Const A360       As Double = 6.28318530717959    '360º in radians
    Public Const A720       As Double = 12.5663706143592    '720º in radians
    Public Const Atm        As Double = 100000              'Atmosphere altitude in meters
    Public Const Ion        As Double = 500000              'Ionosphere altitude in meters
    Public Const IonEx      As Double = 1000000             'Ionosphere altitude doubled in meters
    Public Const Ground     As Double = 0.001               'Ground altitude in meters
    Public Const Crash      As Double = 5                   'Crash speed in meters per second
    Public Const AirspaceMax As Double = 30001.464          'Airspace upper limit in meters
    Public Const LandspaceMax As Double = AirspaceMax       'Ground area upper limit in meters
    Public Const Infinite   As Double = 1E+308              'Infinite for 64 bit float
    Public Const InfiniteSng As Double = 1E+38!             'Infinite for 32 bit float
    Public Const InfiniteInt As Long = &H7FFFFFFF           'Infinite for 32 bit integer
    Public Const Zero       As Double = 1E-309              'Zero for 64 bit float
    Public Const ZeroSng    As Double = 1E-39!              'Zero for 32 bit float
    Public Const Undefined  As Double = 1.1E+308            'Undefined
    Public Const FOVMin     As Double = 1 / Hour            'Minimum camera FOV in degrees
    Public Const FOVMax     As Double = 160                 'Maximum camera FOV in degrees
    Public Const ZoomMin    As Double = 1000                'Minimum camera distance in meters
    Public Const ZoomMax    As Double = 1E+20               'Maximum camera distance in meters
    Public Const ZoomModelMin As Double = 0.001             'Minimum model camera distance in meters
    Public Const ZoomModelMax As Double = 1000000           'Maximum model camera distance in meters
    Public Const MaxTimeSpeed As Double = 1000000000        'Maximum simulation speed
    Public Const SelectRange As Single = 15                 'Selection range in pixels
    Public Const KB         As Double = 1024                'Kilobyte
    Public Const MB         As Double = 1048576             'Megabyte
    Public Const GB         As Double = 1073741824          'Gigabyte
    Public Const TB         As Double = 1099511627776#      'Terabyte
    Public Const FZero          As String = "0"             'Data formats
    Public Const FOne           As String = "0.0"
    Public Const FMini          As String = "0.00"
    Public Const FShort         As String = "0.000"
    Public Const FQuad          As String = "0.0000"
    Public Const FFive          As String = "0.00000"
    Public Const FLong          As String = "0.000000"
    Public Const FExt           As String = "000.000"
    Public Const FAngle         As String = "0.0º"
    Public Const FAngleEx       As String = "0.00º"
    Public Const FAngleLat      As String = "00.0º"
    Public Const FAngleLon      As String = "000.0º"
    Public Const Tab1           As String = vbTab           'String constants
    Public Const Tab2           As String = vbTab & vbTab
    Public Const Tab3           As String = vbTab & vbTab & vbTab
    Public Const vbCrLf2        As String = vbCrLf & vbCrLf
    Public Const vbCrBack       As String = vbCr & vbBack
    Public Const vbBackCr       As String = vbBack & vbCr
    Public Const vbBackCrBack   As String = vbBack & vbCr & vbBack
    Public Const vbBackCrCrBack As String = vbBack & vbCr & vbCr & vbBack
    Public Const vbSpc          As String = " "
    Public Const vbCrSpc        As String = vbCr & vbSpc
    Public Const vbSpcCr        As String = vbSpc & vbCr
    Public Const vbSpcCrCr      As String = vbSpc & vbCr & vbCr
    Public Const vbSpcCrSpc     As String = vbSpc & vbCr & vbSpc
    Public Const vbSpcCrCrSpc   As String = vbSpc & vbCr & vbCr & vbSpc
    Public Const vbCrTab        As String = vbCr & vbTab
    Public Const vbNullChar2    As String = vbNullChar & vbNullChar
    Public Const vbKeyContextMenu As Integer = 93           'Contextual menu key code
    Public Const Graph2C        As Long = 8                 'Input graph maximum graphs
    Public Const GraphHeight    As Single = 12              'Panels width and height divided by 20 pixels
    Public Const LinkWidth      As Single = 19, _
                 LinkHeight     As Single = 13
    Public Const SetupWidth     As Single = 9.5, _
                 SetupWidthEx   As Single = 26, _
                 SetupHeight    As Single = 7
    Public Const StagesWidth    As Single = 16.5, _
                 StagesWidthEx  As Single = 26, _
                 StagesHeight   As Single = 3
    Public Const EditorWidth    As Single = 22, _
                 EditorHeight   As Single = 13
    Public Const BurnWidth      As Single = 24.5, _
                 BurnWidthEx    As Single = 32.5, _
                 BurnHeight     As Single = 14
    Public Const PresetWidth    As Single = 45, _
                 PresetHeight   As Single = 21
    Public Const CalcWidth      As Single = 6, _
                 CalcHeight     As Single = 7
    Public Const OptionsWidth   As Single = 17, _
                 OptionsHeight  As Single = 25.75
    Public Const ColorsWidth    As Single = 9, _
                 ColorsHeight   As Single = 25.75
    Public Const AxesWidth      As Single = 7, _
                 AxesHeight     As Single = 13
    Public Const StageMax       As Long = 5                 'Number of stages
    Public Const HoverMax       As Long = StageMax + 1      'Number of the hover stage
    Public Const PayloadMax     As Long = StageMax + 2      'Number of the payload stage
    Public Const ComponentMax   As Long = 6                 'Number of propellant components

'__Hook_functions______________________________________________________________

    Dim MainProc As Long, SetupProc As Long, _
        SetupListProc As Long, EditorListProc As Long, SearchListProc As Long, _
        DataProc As Long, LogProc As Long, ViewProc As Long, ToolBarProc As Long, _
        GraphProc As Long, Graph2Proc As Long, MapProc As Long, InfoProc As Long, _
        HelpProc As Long, PadProc As Long, ModelProc As Long, ColorsProc As Long, _
        TipProc As Long

Sub SetMainProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then MainProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewMainProc Else SetWindowLong hWnd, -4, MainProc
    End Sub

Function NewMainProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage Main.hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
    Case 3, &H7E&: ResizeMain 'WM_MOVE=3 WM_DISPLAYCHANGE=&H7E
        End Select
    NewMainProc = CallWindowProc(MainProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetSetupProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then SetupProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewSetupProc Else SetWindowLong hWnd, -4, SetupProc
    End Sub

Function NewSetupProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Dim C As Long
    Select Case Msg
    Case &H20A&: PostMessage Main.hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
    Case 3: If hWnd = Setup.hWnd Then If Options.SnapPanels Then _
        Stages.Move Setup.Left, Setup.Top + Setup.Height 'WM_MOVE=3
        End Select
    NewSetupProc = CallWindowProc(SetupProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetSetupListProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then SetupListProc = GetWindowLong(hWnd, -4): SetWindowLong hWnd, -4, _
        AddressOf NewSetupListProc Else SetWindowLong hWnd, -4, SetupListProc
    End Sub

Function NewSetupListProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H7B&: SetFocusX hWnd: PostMessage hWnd, &H101&, 1 'WM_CONTEXTMENU=&H7B WM_KEYUP=&H101
        End Select
    NewSetupListProc = CallWindowProc(SetupListProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetEditorListProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then EditorListProc = GetWindowLong(hWnd, -4): SetWindowLong hWnd, -4, _
        AddressOf NewEditorListProc Else SetWindowLong hWnd, -4, EditorListProc
    End Sub

Function NewEditorListProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20&: PostMessage hWnd, &H101&, 2 'WM_SETCURSOR=&H20 WM_KEYUP=&H101
    Case &H7B&: SetFocusX hWnd: PostMessage hWnd, &H101&, 1 'WM_CONTEXTMENU=&H7B WM_KEYUP=&H101
        End Select
    NewEditorListProc = CallWindowProc(EditorListProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetSearchListProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then SearchListProc = GetWindowLong(hWnd, -4): SetWindowLong hWnd, -4, _
        AddressOf NewSearchListProc Else SetWindowLong hWnd, -4, SearchListProc
    End Sub

Function NewSearchListProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H7B&: SetFocusX hWnd: PostMessage hWnd, &H101&, 1 'WM_CONTEXTMENU=&H7B WM_KEYUP=&H101
        End Select
    NewSearchListProc = CallWindowProc(SearchListProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetDataProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then DataProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewDataProc Else SetWindowLong hWnd, -4, DataProc
    End Sub

Function NewDataProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage Main.hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
        End Select
    NewDataProc = CallWindowProc(DataProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetLogProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then LogProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewLogProc Else SetWindowLong hWnd, -4, LogProc
    End Sub

Function NewLogProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H205&: PostMessage Log.hWnd, &H100&, 1 'WM_RBUTTONUP=&H205& WM_KEYDOWN=&H100
    Case Else: NewLogProc = CallWindowProc(LogProc, hWnd, Msg, wParam, lParam)
        End Select
    End Function

Sub SetViewProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then ViewProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewViewProc Else SetWindowLong hWnd, -4, ViewProc
    End Sub

Function NewViewProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
    Case &H24&: PutLong lParam + 32, 32000: PutLong lParam + 36, 32000 'WM_GETMINMAXINFO=&H24 (LPMINMAXINFO)lParam.ptMaxTrackSize -> .X=MaxWidth .Y=MaxHeight
        End Select
    NewViewProc = CallWindowProc(ViewProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetToolBarProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then ToolBarProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewToolBarProc Else SetWindowLong hWnd, -4, ToolBarProc
    End Sub

Function NewToolBarProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage Main.hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
        End Select
    NewToolBarProc = CallWindowProc(ToolBarProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetGraphProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then GraphProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewGraphProc Else SetWindowLong hWnd, -4, GraphProc
    End Sub

Function NewGraphProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
        End Select
    NewGraphProc = CallWindowProc(GraphProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetGraph2Proc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then Graph2Proc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewGraph2Proc Else SetWindowLong hWnd, -4, Graph2Proc
    End Sub

Function NewGraph2Proc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
        End Select
    NewGraph2Proc = CallWindowProc(Graph2Proc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetMapProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then MapProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewMapProc Else SetWindowLong hWnd, -4, MapProc
    End Sub

Function NewMapProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage Map.Pic.hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
        End Select
    NewMapProc = CallWindowProc(MapProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetInfoProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then InfoProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewInfoProc Else SetWindowLong hWnd, -4, InfoProc
    End Sub

Function NewInfoProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage Map.Pic.hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
        End Select
    NewInfoProc = CallWindowProc(InfoProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetPadProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then PadProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewPadProc Else SetWindowLong hWnd, -4, PadProc
    End Sub

Function NewPadProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H205&: PostMessage Notepad.hWnd, &H100&, 1 'WM_RBUTTONUP=&H205& WM_KEYDOWN=&H100
    Case Else: NewPadProc = CallWindowProc(PadProc, hWnd, Msg, wParam, lParam)
        End Select
    End Function

Sub SetModelProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then ModelProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewModelProc Else SetWindowLong hWnd, -4, ModelProc
    End Sub

Function NewModelProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
        End Select
    NewModelProc = CallWindowProc(ModelProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetColorsProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then ColorsProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewColorsProc Else SetWindowLong hWnd, -4, ColorsProc
    End Sub

Function NewColorsProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
        ByVal lParam As Long) As Long
    Select Case Msg
    Case &H20A&: PostMessage Colors.hWnd, &H100&, IfInt(wParam > 0, 1, 2) 'WM_MOUSEWHEEL=&H20A WM_KEYDOWN=&H100
    'Case &H114&: Colors.BarX = (wParam And &HFFFF0000) \ &H10000
    '    SendMessage Colors.hWnd, &H100&, 260 + (wParam And &HF&) 'WM_HSCROLL=&H114 WM_KEYDOWN=&H100 SB_LINELEFT=0 SB_LINERIGHT=1 SB_PAGELEFT=2 SB_PAGERIGHT=3 SB_THUMBTRACK=5
    Case &H115&: Colors.BarY = (wParam And &HFFFF0000) \ &H10000
        SendMessage Colors.hWnd, &H100&, 270 + (wParam And &HF&) 'WM_VSCROLL=&H115 WM_KEYDOWN=&H100 SB_LINEUP=0 SB_LINEDOWN=1 SB_PAGEUP=2 SB_PAGEDOWN=3 SB_THUMBTRACK=5
        End Select
    NewColorsProc = CallWindowProc(ColorsProc, hWnd, Msg, wParam, lParam)
    End Function

Sub SetTipProc(ByVal hWnd As Long, Optional ByVal Enable As Boolean = True)
    If Enable Then TipProc = GetWindowLong(hWnd, -4): _
        SetWindowLong hWnd, -4, AddressOf NewTipProc Else SetWindowLong hWnd, -4, TipProc
    End Sub

Function NewTipProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Select Case Msg
    'Case 6: SetFocusX lParam: Exit Function 'WM_ACTIVATE=6
    Case &H21&: NewTipProc = 3: Exit Function 'WM_MOUSEACTIVATE=&H21 MA_NOACTIVATE=3
        End Select
    NewTipProc = CallWindowProc(TipProc, hWnd, Msg, wParam, lParam)
    End Function

'__Global_functions____________________________________________________________

Function LeftEx(S As String, T As String) As Boolean
    LeftEx = Left(S, Len(T)) = T
    End Function

Function RightEx(S As String, T As String) As Boolean
    RightEx = Right(S, Len(T)) = T
    End Function

Sub MidEx(S As String, T As String)
    S = Mid(S, Len(T) + 1)
    End Sub

Function LeftLCase(S As String, T As String) As Boolean
    LeftLCase = Left(LCase(S), Len(T)) = LCase(T)
    End Function

Function RightLCase(S As String, T As String) As Boolean
    RightLCase = Right(LCase(S), Len(T)) = LCase(T)
    End Function

Sub InsertText(S As String, T As String, Optional ByVal Pos As Long = 1)
    If Pos > 0 Then S = Left(S, Pos - 1) & T & Mid(S, Pos)
    End Sub

Sub RemoveLine(S As String, Optional ByVal Pos As Long = 1)
    Dim A As Long, B As Long
    A = InStrRev(S, vbCrLf, Pos) + 2: If A <= 2 Then A = 1
    B = InStr(Pos, S, vbCrLf) + 2: If B <= 2 Then B = Len(S) + 1
    S = Left(S, A - 1) & Mid(S, B)
    End Sub

Sub NextLine(S As String, Pos As Long)
    Pos = InStr(Pos, S, vbCrLf) + 2: If Pos <= 2 Then Pos = Len(S) + 1
    End Sub

Sub LastLine(S As String, Pos As Long)
    Pos = InStrRev(S, vbCrLf, Pos) + 2: If Pos <= 2 Then Pos = 1: Exit Sub
    Pos = InStrRev(S, vbCrLf, Pos - 2) + 2: If Pos <= 2 Then Pos = 1
    End Sub

'Sub LogAddAll(Optional ByVal Add As Boolean = True)
'    Dim S As String
'    If MsgBox(IIf(Add, "Add", "Remove") & " error handlers?", vbOKCancel) <> vbOK Then End
'    S = Dir("*.frm"): Do While S <> "": LogAddFile S, Add: S = Dir: Loop
'    LogAddFile "Sys.bas", Add: End
'    End Sub
'
'Sub LogAddFile(File As String, Optional ByVal Add As Boolean = True)
'    Dim A As Long, B As Long, C As Long, S As String, T As String, _
'        Module As String, Name As String, Style As String
'    Open File For Input As 1: T = Input(LOF(1), 1): Close
'    Module = Left(File, Len(File) - 4)
'    A = 1
'    Do
'        S = LTrim(GetLine(T, A)): If A = 0 Then Exit Do
'        Style = ParamX(S): If Style = "Private" Then Style = ParamX(S)
'        If Style <> "Sub" And Style <> "Function" Then GoTo Nxt
'        C = InStr(S, "("): If C > 0 Then Name = Left(S, C - 1) Else Stop
'        If Module = "Nav" Or Module = "Render" Then _
'            If Name = "LogIn" Or Name = "LogOut" Or Name = "LogErr" Or Name = "LogMsg" Or _
'                Name = "GetNum" Or Name = "GetStr" Or _
'                (Left(Name, 3) = "New" And Right(Name, 4) = "Proc") Then GoTo Nxt
'        Do While Right(S, 2) = " _": S = GetLine(T, A): Loop
'        B = InStr(A, T, "End " & Style): If B = 0 Then Stop
'        B = InStrRev(T, vbCrLf, B) + 2: If B <= 2 Then Stop
'        If Add Then
'            Name = """" & Module & "." & Name & """"
'            InsertText T, "Error:    Select Case LogErr(" & Name & "): Case vbRetry: Resume: Case vbIgnore: Resume Next: Case vbAbort: End: End Select" & vbCrLf, B
'            InsertText T, "          On Error GoTo 0: LogOut " & Name & ": Exit " & Style & vbCrLf, B
'            InsertText T, "          On Error GoTo Error: LogIn " & Name & vbCrLf, A
'        Else
'            LastLine T, B: LastLine T, B
'            RemoveLine T, B: RemoveLine T, B
'            RemoveLine T, A
'            End If
'Nxt:
'        Loop
'    Open File For Output As 1: Print #1, T;: Close
'    End Sub
'
'Sub LogIn(Name As String)
'    LogMsg "In:  " & Name
'    End Sub
'
'Sub LogOut(Name As String)
'    LogMsg "Out: " & Name
'    End Sub
'
'Function LogErr(Name As String) As VbMsgBoxResult
'    LogErr = MsgBox(Name & "." & Erl & ": " & Err.Description, vbAbortRetryIgnore Or vbExclamation)
'    LogMsg "Err: " & Name & "." & Erl & ": " & Err.Description & Switch(LogErr = vbRetry, " [Retry]", LogErr = vbIgnore, " [Ignore]", LogErr = vbAbort, " [Abort]", True, "")
'    End Function
'
'Sub LogMsg(Text As String)
'    Dim S As String, T As SystemTime: Static F As Long
'    If F = 0 Then If InStr(LCase(Command), "nolog") Then F = -1 _
'        Else F = CreateFile("Log.txt", &H40000000, , , 2) 'GENERIC_WRITE=&H40000000 CREATE_ALWAYS=2
'    If F = -1 Then Exit Sub
'    GetLocalTime T
'    S = Format(T.Hour, "00") & ":" & Format(T.Minute, "00") & ":" & Format(T.Second, "00") & "." & _
'        Format(T.Milliseconds, "000  ") & Text & vbCrLf
'    WriteFile F, S, Len(S)
'    End Sub

Sub InitOp(Op As OpenFileName, _
        Optional ByVal LenFile As Long = 256, Optional ByVal LenFileTitle As Long = 256)
    With Op
    .Size = Len(Op): .hWnd = Main.hWnd
    .Filter = _
        "Preset files" & vbNullChar & "*.ini" & vbNullChar & _
        "Path files" & vbNullChar & "*.bin" & vbNullChar & _
        "KML files" & vbNullChar & "*.kml" & vbNullChar & _
        "Log files" & vbNullChar & "*.txt" & vbNullChar & _
        "TLE files" & vbNullChar & "*.tle" & vbNullChar & _
        "COB files" & vbNullChar & "*.cob" & vbNullChar & _
        "NMEA files" & vbNullChar & "*.txt;*.nmea" & vbNullChar & _
        "CSV files" & vbNullChar & "*.csv" & vbNullChar & _
        "Image files" & vbNullChar & "*.bmp;*.jpg;*.gif" & vbNullChar & _
        "Video files" & vbNullChar & "*.avi;*.mpg;*.mp4;*.wmv" & vbNullChar & _
        "All files" & vbNullChar & "*.*" & vbNullChar & vbNullChar
    .File = String(LenFile, vbNullChar): .MaxFile = LenFile
    .FileTitle = String(LenFileTitle, vbNullChar): .MaxFileTitle = LenFileTitle
    End With
    End Sub

Function GetOpenFiles(Op As OpenFileName, _
        Names() As String, Optional ByVal Filter As FileType = FileAll, _
        Optional Folder As String, Optional ByVal SortFiles As Boolean) As Boolean
    Dim N As Long, Path As String
    With Op
    If .Size = 0 Then InitOp Op, 32768
    .Flags = &H80204 'OFN_HIDEREADONLY=4 OFN_NOCHANGEDIR=8 OFN_ALLOWMULTISELECT=&H200 OFN_EXPLORER=&H80000
    If Filter Then .FilterIndex = Filter ': .DefExt = "ini"
    If Folder <> "" Then .InitialDir = Folder
    Mid(.File, 1, 2) = vbNullChar2
    If GetOpenFileName(Op) Then GetOpenFiles = True Else Exit Function
    Names = Split(Left(.File, InStr(.File, vbNullChar2) - 1), vbNullChar)
    End With
    If UBound(Names) > 0 Then
        Path = Names(0): If Right(Path, 1) <> "\" Then Path = Path & "\"
        For N = 1 To UBound(Names): Names(N - 1) = Path & Names(N): Next
        ReDim Preserve Names(UBound(Names) - 1)
        If SortFiles Then SortList Names(), vbBinaryCompare
        End If
    End Function

Function GetOpenFile(Op As OpenFileName, _
        Optional Name As String, Optional ByVal Filter As FileType = FileAll, _
        Optional Folder As String, Optional ByVal ClearName As Boolean) As String
    With Op
    If .Size = 0 Then InitOp Op
    .Flags = &HC& 'OFN_HIDEREADONLY=4 OFN_NOCHANGEDIR=8
    If Filter Then .FilterIndex = Filter ': .DefExt = "ini"
    If Folder <> "" Then .InitialDir = Folder
    If ClearName Then Mid(.File, 1, 1) = vbNullChar _
    Else If Name <> "" Then Mid(.File, 1, Len(Name) + 1) = Name & vbNullChar
    If GetOpenFileName(Op) Then GetOpenFile = Left(.File, InStr(.File, vbNullChar) - 1)
    End With
    End Function

Function GetSaveFile(Op As OpenFileName, _
        Optional Name As String, Optional ByVal Filter As FileType = FileAll, _
        Optional Ext As String, Optional Folder As String) As String
    With Op
    If .Size = 0 Then InitOp Op
    .Flags = &HE& 'OFN_OVERWRITEPROMPT=2 OFN_HIDEREADONLY=4 OFN_NOCHANGEDIR=8
    If Filter Then .FilterIndex = Filter
    If Ext <> "" Then .DefExt = Ext
    If Folder <> "" Then .InitialDir = Folder
    If Name <> "" Then Mid(.File, 1, Len(Name) + 1) = Name & vbNullChar
    If GetSaveFileName(Op) Then GetSaveFile = Left(.File, InStr(.File, vbNullChar) - 1)
    End With
    End Function

Sub SortList(T() As String, Optional ByVal Mode As VbCompareMethod = vbTextCompare)
    Dim B As Long, C As Long, N As Long, S As String
    C = UBound(T)
    Do
        B = 0
        For N = 0 To C - 1
            If StrComp(T(N), T(N + 1), Mode) <= 0 Then B = B + 1 _
            Else S = T(N): T(N) = T(N + 1): T(N + 1) = S
            Next
        Loop While B < C
    End Sub

Sub ChangeDir(Path As String)
    If CurDir <> Path Then SetCurrentDirectory Path
    End Sub

Sub MakeDir(Path As String)
    If GetFileAttributes(Path) = -1 Then CreateDirectory Path
    End Sub

Sub InitDir(Path As String, Optional Default As String, Optional ByVal IsDebug As Boolean)
    If Default <> "" Then Path = AppPath & IIf(IsDebug, "Backup\", "") & Default & "\"
    If GetFileAttributes(Path) = -1 Then CreateDirectory Path
    End Sub

Function InStrNoCase(Text As String, Find As String, Optional ByVal Pos As Long = 1) As Long
    InStrNoCase = InStr(Pos, LCase(Text), LCase(Find))
    End Function

Sub SaveLog(Txt As TextBox, Optional ByVal AddTime As Boolean)
    Dim Name As String: Static Op As OpenFileName
    Name = Setup.Preset: If AddTime Then Name = Name & "." & ShortDateTime
    Name = GetSaveFile(Op, Name & ".txt", FileLog, "txt", LogsDir): If Name = "" Then Exit Sub
    On Error Resume Next: Open Name For Output As 1: Print #1, Txt;: Close: On Error GoTo 0
    End Sub

Sub UpdatePanels(Optional ByVal Update As Boolean = True)
    If Update Then Info: Plot , 1: Plot2: Editor.Update: Burn.Update: Map.Update
    End Sub

Sub PanelsStyle(Optional ByVal Style As PanelStyle = StyleDefault, _
        Optional ByVal Update As Boolean = True)
    If Style >= StyleWhite And Style <= StyleBasic Then Theme = Style _
    Else Theme = Switch(Theme <> StyleBasic, StyleBasic, _
        Color = ThemeBlack, StyleBlack, True, StyleWhite)
    If Update Then ColorTheme: UpdatePanels: Options.UpdateScreen
    End Sub

Sub ColorTheme(Optional ByVal N As ThemeStyle = ThemeNone, Optional ByVal Update As Boolean)
    Dim C As Control, D As Long, Q As Boolean
    
    On Error Resume Next
    Select Case N
    Case ThemeDefault:  ViewerBack = vbBlack: PanelColor = vb3DFace
                        ButtonBack = vbWindowBackground: ButtonColor = vbButtonText
    Case ThemeBlack:    ViewerBack = vbBlack: PanelColor = &H303030
                        ButtonBack = &H202020: ButtonColor = vbWhite
    Case ThemeWhite:    ViewerBack = vbWhite: PanelColor = vbWhite
                        ButtonBack = &HF0F0F0: ButtonColor = vbBlack
    Case ThemeVista:    ViewerBack = vbBlack: PanelColor = &HF0F0F0
                        ButtonBack = &HFFFFFF: ButtonColor = vbBlack
    Case ThemeWinXP:    ViewerBack = vbBlack: PanelColor = &HD8E9EC
                        ButtonBack = vbWhite: ButtonColor = vbBlack
    Case ThemeWin98:    ViewerBack = vbBlack: PanelColor = &HC8CCD0
                        ButtonBack = vbWhite: ButtonColor = vbBlack
    Case Else: GoTo NoColor
        End Select
    Color = N: Q = True
    ViewerColor = vbWhite: Main.Report.ForeColor = ViewerColor
    TextFont.BackColor = &H80202020: ReportFont.BackColor = TextFont.BackColor
    ModelBack = ButtonBack: Main.ModelFont.ForeColor = vbBlack
    ModelFont.BackColor = &H80FFFFFF
    SymbolColor = SystemColor(ButtonColor) Or &H404040
    If Theme <> StyleBasic Then Theme = IfInt(Color = ThemeBlack, StyleBlack, StyleWhite)
NoColor:
    If Not Q Then ViewerColor = Main.ForeColor: ButtonColor = Setup.ForeColor
    
    With Main: .BackColor = ViewerBack: .ForeColor = ViewerColor: End With
    With View: .BackColor = ViewerBack: .ForeColor = ViewerColor: End With
    With Model: .BackColor = ModelBack: .ForeColor = ViewerColor: End With
    With Model2: .BackColor = ModelBack: .ForeColor = ViewerColor: End With
    With Graph3D: .BackColor = ModelBack: .ForeColor = ViewerColor: End With
    For N = 1 To CamN - 1: With Cam(N).F
        .BackColor = ViewerBack: .ForeColor = ViewerColor: End With
        Next
    For N = 1 To GraN - 1: With Gra(N).F
        .BackColor = ButtonBack: .ForeColor = ViewerColor: End With
        Next
    With ToolBar: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls
        C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        Next
        .Mover.ForeColor = &H808080: D = IfInt(Color = 1, &HE0E0E0, &H808080)
        .Quit.ForeColor = D: .Mini.ForeColor = D: .Maxi.ForeColor = D
    .Resize
    End With
    
    With Setup: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    With Stages: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    
    With Help: .BackColor = ButtonBack: .ForeColor = ButtonColor
        .Txt.BackColor = ButtonBack: .Txt.ForeColor = ButtonColor: End With
    With Data: .BackColor = PanelColor: .ForeColor = ButtonColor
        .Pic.BackColor = PanelColor: .Pic.ForeColor = ButtonColor: End With
    With Log: .BackColor = PanelColor: .ForeColor = ButtonColor
        .Txt.BackColor = PanelColor: .Txt.ForeColor = ButtonColor: End With
    With Graph: .BackColor = PanelColor: .ForeColor = ButtonColor
        .Pic.BackColor = PanelColor: .Pic.ForeColor = ButtonColor: End With
    With Editor: .BackColor = PanelColor: .ForeColor = ButtonColor
        .List.BackColor = ButtonBack: .List.ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    With Link: .BackColor = PanelColor: .ForeColor = ButtonColor
        .Txt.BackColor = ButtonBack: .Txt.ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    With Options: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls
        C.ForeColor = ButtonColor: C.LabelColor = PanelColor
        If Left(C.Name, 3) = "Tab" Then C.BackColor = PanelColor Else C.BackColor = ButtonBack
        Next: .SelectTab: End With
    With Colors: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls
        C.ForeColor = ButtonColor: C.LabelColor = PanelColor: If C.Name <> "Sel" Then _
            If C.Container Is Colors Or C.Container Is .List Then _
                C.BackColor = PanelColor Else C.BackColor = ButtonBack
        Next: End With
    
    With Preset: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        Next: End With
    With Search: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    With Axes: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    With Calc: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    With Burn: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls
        C.ForeColor = ButtonColor: If C.Name = "Txt" Or Left(C.Name, 3) = "Pic" Then _
            C.BackColor = PanelColor Else C.BackColor = ButtonBack
        C.LabelColor = PanelColor
        Next: End With
    With InputBox: .BackColor = PanelColor: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = ButtonBack: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next: End With
    
    With Notepad: .BackColor = PanelColor: .ForeColor = ButtonColor
        .Txt.BackColor = PanelColor: .Txt.ForeColor = ButtonColor: End With
    With Map: .BackColor = ButtonBack: .ForeColor = ButtonColor
    For Each C In .Controls: C.BackColor = PanelColor: C.ForeColor = ButtonColor
        C.LabelColor = PanelColor: Next
        .Pic.BackColor = ButtonBack: .Pic.ForeColor = ButtonColor
        .Locations.ForeColor = SymbolColor: .ShowWeb.ForeColor = SymbolColor
        .Quit.ForeColor = SymbolColor
        End With
    With Graph2: .BackColor = PanelColor: .ForeColor = ButtonColor
        .Pic.BackColor = PanelColor: .Pic.ForeColor = ButtonColor: End With
    With Browser: .BackColor = PanelColor: .Address.BackColor = PanelColor
        .URL.BackColor = PanelColor: .URL.ForeColor = ButtonColor
        End With
    
    If Q Then Colors.Update
    If Update Then UpdateTextFonts False, False: _
        RenderModels: RenderGraphs: Render: UpdatePanels: Options.UpdateScreen
    
    On Error GoTo 0
    
    End Sub

Sub Press(C As Control)
    C.BackColor = vbHighlight: C.ForeColor = vbHighlightText: C.Refresh
    End Sub

Sub UnPress(C As Control)
    C.ForeColor = ButtonColor: C.BackColor = ButtonBack: C.Refresh
    End Sub

Function IsPressed(C As Control) As Boolean
    IsPressed = C.BackColor = vbHighlight
    End Function

Sub ColorToRGBA(ByVal C As Long, F As FloatRGBA)
    F.A = (((C And &HFF000000) \ &H1000000) And &HFF&) / 255
    F.B = ((C And &HFF0000) \ &H10000) / 255
    F.G = ((C And &HFF00&) \ &H100&) / 255
    F.R = (C And &HFF&) / 255
    End Sub

Sub ColorSub(D As FloatRGBA, F As FloatRGBA, S As FloatRGBA)
    D.R = F.R - S.R: D.G = F.G - S.G: D.B = F.B - S.B: D.A = F.A - S.A
    End Sub

Sub ColorRamp4( _
        ByVal Color1 As Long, ByVal Color2 As Long, ByVal Color3 As Long, ByVal Color4 As Long, _
        F1 As FloatRGBA, D1 As FloatRGBA, F2 As FloatRGBA, D2 As FloatRGBA, _
        F3 As FloatRGBA, D3 As FloatRGBA, F4 As FloatRGBA)
    ColorToRGBA Color1, F1
    ColorToRGBA Color2, F2: ColorSub D1, F2, F1
    ColorToRGBA Color3, F3: ColorSub D2, F3, F2
    ColorToRGBA Color4, F4: ColorSub D3, F4, F3
    End Sub

Sub ColorRamp6( _
        ByVal Color1 As Long, ByVal Color2 As Long, ByVal Color3 As Long, ByVal Color4 As Long, _
        ByVal Color5 As Long, ByVal Color6 As Long, _
        F1 As FloatRGBA, D1 As FloatRGBA, F2 As FloatRGBA, D2 As FloatRGBA, _
        F3 As FloatRGBA, D3 As FloatRGBA, F4 As FloatRGBA, D4 As FloatRGBA, _
        F5 As FloatRGBA, D5 As FloatRGBA, F6 As FloatRGBA)
    ColorToRGBA Color1, F1
    ColorToRGBA Color2, F2: ColorSub D1, F2, F1
    ColorToRGBA Color3, F3: ColorSub D2, F3, F2
    ColorToRGBA Color4, F4: ColorSub D3, F4, F3
    ColorToRGBA Color5, F5: ColorSub D4, F5, F4
    ColorToRGBA Color6, F6: ColorSub D5, F6, F5
    End Sub

Sub ColorRamp7( _
        ByVal Color1 As Long, ByVal Color2 As Long, ByVal Color3 As Long, ByVal Color4 As Long, _
        ByVal Color5 As Long, ByVal Color6 As Long, ByVal Color7 As Long, _
        F1 As FloatRGBA, D1 As FloatRGBA, F2 As FloatRGBA, D2 As FloatRGBA, _
        F3 As FloatRGBA, D3 As FloatRGBA, F4 As FloatRGBA, D4 As FloatRGBA, _
        F5 As FloatRGBA, D5 As FloatRGBA, F6 As FloatRGBA, D6 As FloatRGBA, _
        F7 As FloatRGBA)
    ColorToRGBA Color1, F1
    ColorToRGBA Color2, F2: ColorSub D1, F2, F1
    ColorToRGBA Color3, F3: ColorSub D2, F3, F2
    ColorToRGBA Color4, F4: ColorSub D3, F4, F3
    ColorToRGBA Color5, F5: ColorSub D4, F5, F4
    ColorToRGBA Color6, F6: ColorSub D5, F6, F5
    ColorToRGBA Color7, F7: ColorSub D6, F7, F6
    End Sub

Sub ColorRGB(Optional ByVal Color As Long, _
        Optional R As Single, Optional G As Single, Optional B As Single)
    If Color And &H80000000 Then Color = GetSysColor(Color And Not &H80000000)
    R = (Color And &HFF&) / 255
    G = ((Color And &HFF00&) \ &H100&) / 255
    B = ((Color And &HFF0000) \ &H10000) / 255
    End Sub

Function ColorRGBA(Optional R As Single, Optional G As Single, Optional B As Single, _
        Optional A As Single = 1) As Long
    If A >= 1 Then ColorRGBA = &HFF000000 _
    Else If A > 0 Then ColorRGBA = Int(A * 255 + 0.5): _
        If ColorRGBA < 128 Then ColorRGBA = ColorRGBA * &H1000000 _
        Else ColorRGBA = (ColorRGBA - 256) * &H1000000
    If B >= 1 Then ColorRGBA = ColorRGBA Or &HFF0000 _
    Else If B > 0 Then ColorRGBA = ColorRGBA Or (Int(B * 255 + 0.5) * &H10000)
    If G >= 1 Then ColorRGBA = ColorRGBA Or &HFF00& _
    Else If G > 0 Then ColorRGBA = ColorRGBA Or (Int(G * 255 + 0.5) * &H100&)
    If R >= 1 Then ColorRGBA = ColorRGBA Or &HFF& _
    Else If R > 0 Then ColorRGBA = ColorRGBA Or Int(R * 255 + 0.5)
    End Function

Function ColorAlpha(Optional A As Single = 1, Optional ByVal RGB As Long, _
        Optional ByVal SysColor As Boolean) As Long
    If A >= 1 Then ColorAlpha = &HFF000000 _
    Else If A > 0 Then ColorAlpha = Int(A * 255 + 0.5): _
        If ColorAlpha < 128 Then ColorAlpha = ColorAlpha * &H1000000 _
        Else ColorAlpha = (ColorAlpha - 256) * &H1000000
    If SysColor Then If RGB And &H80000000 Then RGB = GetSysColor(RGB And &H7FFFFFFF)
    ColorAlpha = (RGB And &HFFFFFF) Or ColorAlpha
    End Function

Sub Int2RGBA(Optional ByVal Color As Long, _
        Optional R As Long, Optional G As Long, Optional B As Long, Optional A As Long)
    R = Color And &HFF&: G = (Color And &HFF00&) \ &H100&: B = (Color And &HFF0000) \ &H10000
    A = (Color And &HFF000000) \ &H1000000: If A < 0 Then A = A + 256
    End Sub

Function RGBA2Int(Optional ByVal R As Long, Optional ByVal G As Long, Optional ByVal B As Long, _
        Optional ByVal A As Long = 255) As Long
    If A >= 256 Then RGBA2Int = &HFF000000 _
    Else If A > 0 Then _
        If A < 128 Then RGBA2Int = A * &H1000000 _
        Else RGBA2Int = (A - 256) * &H1000000
    If B >= 256 Then RGBA2Int = RGBA2Int Or &HFF0000 _
    Else If B > 0 Then RGBA2Int = RGBA2Int Or (B * &H10000)
    If G >= 256 Then RGBA2Int = RGBA2Int Or &HFF00& _
    Else If G > 0 Then RGBA2Int = RGBA2Int Or (G * &H100&)
    If R >= 256 Then RGBA2Int = RGBA2Int Or &HFF& _
    Else If R > 0 Then RGBA2Int = RGBA2Int Or R
    End Function

Function SystemColor(ByVal Color As Long) As Long
    If Color And &H80000000 Then SystemColor = GetSysColor(Color And &H7FFFFFFF) _
    Else SystemColor = Color
    End Function

Function GrayColor(ByVal Color As Long) As Long
    If Color And &H80000000 Then Color = GetSysColor(Color And Not &H80000000)
    GrayColor = ((Color And &HFF&) + ((Color \ &H100&) And &HFF&) + ((Color \ &H10000) And &HFF&)) \ 3
    End Function

Function SelectFont(Optional X As Object) As Boolean
    Dim S As String * 32, Ft As Choose_Font, LogFont As Log_Font
    If X Is Nothing Then Set X = Setup
    With X.Font
    Ft.rgbColors = SystemColor(vbButtonText)
    Ft.StructSize = Len(Ft): Ft.hwndOwner = Colors.hWnd
    Ft.LogFont = VarPtr(LogFont): Ft.Flags = &H141& 'CF_SCREENFONTS=1 CF_INITTOLOGFONTSTRUCT=&H40 CF_EFFECTS=&H100
    LogFont.Height = .Size * GetDeviceCaps(Main.hDC, 90) / 72 'LOGPIXELSY=90=&H5A
    LogFont.Weight = 400 - 300 * .Bold: LogFont.Italic = -.Italic 'Weight: 400=Normal 700=Bold
    S = .Name & vbNullChar: lstrcpy LogFont.FaceName(0), ByVal S
    If ChooseFont(Ft) = 0 Then Exit Function
    lstrcpy ByVal S, LogFont.FaceName(0)
    .Name = S: .Size = Ft.PointSize / 10: .Bold = LogFont.Weight > 500: .Italic = LogFont.Italic
    If Ft.rgbColors <> SystemColor(vbButtonText) Then X.ForeColor = Ft.rgbColors And &HFFFFFF
    End With
    SelectFont = True
    End Function

Sub SetFont(Optional X As Object, Optional Name As String, _
        Optional ByVal Size As Single = 8, Optional ByVal Bold As Boolean, _
        Optional ByVal Italic As Boolean, Optional ByVal Height As Single = -1)
    Dim LogFont As Log_Font
    If X Is Nothing Then Set X = Setup
    With X.Font
    If Name = "" Or LCase(Name) = "default" Then
        If SystemParametersInfo(&H1F&, Len(LogFont), LogFont, 0) = 0 Then GoTo NoSysFont 'SPI_GETICONTITLELOGFONT=&h1F
        Name = Space(32): lstrcpy ByVal Name, LogFont.FaceName(0)
        .Name = Name
        .Size = -LogFont.Height / GetDeviceCaps(X.hDC, 90) * 72
        .Bold = -(LogFont.Weight > 400) 'Weight: 400=Normal 700=Bold
        .Italic = -LogFont.Italic
    Else
NoSysFont:
        .Name = Name
        If Size Then .Size = Size
        .Bold = Bold: .Italic = Italic
        End If
    If Height >= 0 Then X.Height = Height
    End With
    End Sub

Sub UpdateFonts(Optional X As Object, Optional ByVal Update As Boolean = True)
    Dim N As Long, C As Control, _
        MainFont As StdFont, SymbolFont As StdFont, ModelFont As StdFont
    On Error Resume Next
    If X Is Nothing Then Set X = Setup
    Set MainFont = X.Font
    Main.Symbol.Font.Size = Round(Setup.Font.Size + 2)
    Set SymbolFont = Main.Symbol.Font
    Set ModelFont = Main.ModelFont.Font
    
    Set View.Font = MainFont
    Set Model.Font = ModelFont
    Set Model2.Font = ModelFont
    Set Graph3D.Font = ModelFont
    For N = 1 To CamN - 1: Set Cam(N).F.Font = MainFont: Next
    For N = 1 To GraN - 1: Set Gra(N).F.Font = MainFont: Next
    
    Set Setup.Font = MainFont
    For Each C In Setup.Controls
        If C.Font = "Marlett" Then Set C.Font = SymbolFont Else _
            Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set Stages.Font = MainFont
    For Each C In Stages.Controls
        If C.Font = "Marlett" Then Set C.Font = SymbolFont Else _
            Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    
    Set Help.Font = MainFont: Set Help.Txt.Font = MainFont
    Set Data.Font = MainFont: Set Data.Pic.Font = MainFont
    Set Log.Font = MainFont: Set Log.Txt.Font = MainFont
    Set Graph.Font = MainFont: Set Graph.Pic.Font = MainFont
    Set Editor.Font = MainFont
    For Each C In Editor.Controls
        If C.Font = "Marlett" Then Set C.Font = SymbolFont Else _
            Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set Link.Font = MainFont: Set Link.Txt.Font = MainFont
    For Each C In Link.Controls
        If C.Font = "Marlett" Then Set C.Font = SymbolFont Else _
            Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set Options.Font = MainFont
    For Each C In Options.Controls
        If C.Font = "Marlett" Then Set C.Font = SymbolFont Else _
            Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set Colors.Font = MainFont
    For Each C In Colors.Controls
        Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    
    Set Preset.Font = MainFont
    CopyFont Preset.pBasicDesign, Preset: Preset.pBasicDesign.Font.Bold = True
    For Each C In Preset.Controls
        If C.Font.Bold Then Set C.Font = Preset.pBasicDesign.Font _
        Else If C.Font = "Marlett" Then Set C.Font = SymbolFont _
        Else Set C.Font = MainFont
        Next
    Set Search.Font = MainFont
    For Each C In Search.Controls
        Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set Axes.Font = MainFont
    For Each C In Axes.Controls
        Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set Calc.Font = MainFont
    For Each C In Calc.Controls
        Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set Burn.Font = MainFont
    For Each C In Burn.Controls
        Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Set InputBox.Font = MainFont
    For Each C In InputBox.Controls
        Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    
    Set Notepad.Font = MainFont: CopyFont Notepad.Txt, X, _
        IfText(Notepad.mnFixedFont.Checked, "Lucida Console")
    Set Note.Font = MainFont: Set Note.Txt.Font = MainFont
    Set Map.Font = MainFont ': Set Map.Pic.Font = MainFont
    Set Graph2.Font = MainFont: Set Graph2.Pic.Font = MainFont
    'Set Info.Font = MainFont: Set Pos.Font = MainFont
    For Each C In Map.Controls
        If C.Name <> "Quit" Then _
            If C.Font = "Marlett" Then Set C.Font = SymbolFont _
            Else Set C.Font = MainFont: C.SymbolFont SymbolFont
        Next
    Map.InfoSize
    Set Browser.Font = MainFont: Set Browser.URL.Font = MainFont
    Set Tip.Font = MainFont
    
    If Update Then Help.Resize: _
        UpdateData: UpdateLog: UpdateGraph: UpdateSetup: UpdateStages: UpdateEditor: _
        UpdateLink: UpdateOptions: UpdateColors: UpdatePreset: UpdateNotepad: UpdateSearch: _
        UpdateAxes: UpdateCalc: UpdateBurn: UpdateMap: UpdateGraph2: UpdateInputBox: _
        UpdateNote: Colors.Update: Browser.ActivateBar 1: _
        ResizeMain: RenderModels: RenderGraphs

    On Error GoTo 0
    End Sub

Sub CopyFont(Dst As Object, Src As Object, Optional Name As String)
    On Error Resume Next
    Dst.Font.Name = IIf(Name <> "", Name, Src.Font.Name)
    Dst.Font.Size = Src.Font.Size: Dst.Font.Bold = Src.Font.Bold: Dst.Font.Italic = Src.Font.Italic
    Dst.Font.Strikethrough = Src.Font.Strikethrough: Dst.Font.Underline = Src.Font.Underline
    Dst.Font.Weight = Src.Font.Weight: Dst.Font.CharSet = Src.Font.CharSet
    On Error GoTo 0
    End Sub

Sub ResizePanels()
    Setup.Form_Resize
    Stages.Form_Resize
    Editor.Form_Resize
    Link.Form_Resize
    Options.Form_Resize
    Axes.Form_Resize
    Calc.Form_Resize
    Colors.Form_Resize
    Burn.Form_Resize
    Search.Form_Resize
    End Sub

Sub CopyStage(Dst As Long, Src As Long)
    With Stages
    IsCalc = True
    .Stage(Dst) = .Stage(Src): .Mass(Dst) = .Mass(Src)
    .Dry(Dst) = .Dry(Src): .Thrust(Dst) = .Thrust(Src): .Delay(Dst) = .Delay(Src)
    .Fuel(Dst) = .Fuel(Src): .Area(Dst) = .Area(Src): .Length(Dst) = .Length(Src)
    .Control(Dst) = .Control(Src): .ControlP(Dst) = .ControlP(Src)
    .ControlI(Dst) = .ControlI(Src): .ControlD(Dst) = .ControlD(Src)
    .LiftToDrag(Dst) = .LiftToDrag(Src): .DragCf(Dst) = .DragCf(Src)
    .InertiaX(Dst) = .InertiaX(Src): .InertiaY(Dst) = .InertiaY(Src): .InertiaZ(Dst) = .InertiaZ(Src)
    .CenterX(Dst) = .CenterX(Src): .CenterY(Dst) = .CenterY(Src): .CenterZ(Dst) = .CenterZ(Src)
    .Battery(Dst) = .Battery(Src): .Imax(Dst) = .Imax(Src): .Vmax(Dst) = .Vmax(Src)
    .Power(Dst) = .Power(Src): .Tmin(Dst) = .Tmin(Src): .Tmax(Dst) = .Tmax(Src)
    .Emissivity(Dst) = .Emissivity(Src): .IspS(Dst) = .IspS(Src): .IspV(Dst) = .IspV(Src)
    .Model(Dst) = .Model(Src)
    IsCalc = False
    End With
    End Sub

Sub UpdateData(Optional ByVal N As Long)
    Dim W As Long, H As Long
    If N > 0 Then DataMode = N Else If N < 0 Then _
        DataMode = DataMode - 1: If DataMode < 1 Then DataMode = 3
    Select Case DataMode
    Case 1: W = 15: H = 3
    Case 2: W = 25: H = BitCount(DataTool) + BitCount(DataToolX)
    Case 3: W = 14: H = 54
        End Select
    Resize Data, Data.TextWidth("O") * W + 4, Data.TextHeight("") * H + 4
    If Main.Visible Then If Play = Paused Then Render: Info
    End Sub

Sub UpdateLog()
    Resize Log, Log.TextWidth("C") * 39, Log.TextHeight("") * 25.5
    End Sub

Sub UpdateGraph()
    Resize Graph, Graph.TextWidth(Tab3) + 360, Graph.TextHeight("") * GraphHeight + 8
    If Main.Visible Then Plot , 1
    End Sub

Sub UpdateSetup()
    PanelResize Setup, IfSng(Setup.ShowOverview, SetupWidthEx, SetupWidth), SetupHeight
    End Sub

Sub UpdateStages(Optional ByVal N As Long, Optional ByVal Insert As Boolean)
    Dim C As Long
    With Stages
    If Insert Then
        If .Num + N < 0 Then N = .Num
        If .Num + N > StageMax Then N = StageMax - .Num
        If N > 0 Then
            For C = 1 To N: For N = StageMax To .Pos + 1 Step -1: CopyStage N, N - 1: Next: Next
        ElseIf N < 0 Then
            For C = 1 To -N: For N = .Pos To StageMax - 1: CopyStage N, N + 1: Next: Next
            End If
    Else
        .Num = .Num + N: If .Num < 0 Then .Num = 0 _
            Else If .Num > StageMax Then .Num = StageMax
        For N = .Num + 1 To StageMax: .Stage(N) = False: Next
        PanelResize Stages, IfSng(.ShowParameters, StagesWidthEx, StagesWidth), _
            StagesHeight + IfInt(.ShowParameters, StageMax, .Num)
        End If
    End With
    End Sub

Sub UpdateEditor()
    PanelResize Editor, EditorWidth, EditorHeight
    End Sub

Sub UpdateLink()
    PanelResize Link, LinkWidth, LinkHeight
    End Sub

Sub UpdateOptions()
    PanelResize Options, OptionsWidth, OptionsHeight
    End Sub

Sub UpdateColors()
    PanelResize Colors, ColorsWidth, ColorsHeight
    End Sub

Sub UpdatePreset()
    PanelResize Preset, PresetWidth, PresetHeight
    End Sub

Sub UpdateNotepad()
    Resize Notepad, Notepad.TextWidth("C") * 80, Notepad.TextHeight("") * 34
    End Sub

Sub UpdateSearch()
    ResizeWidth Search, Search.TextWidth("O") * 18
    End Sub

Sub UpdateAxes()
    PanelResize Axes, AxesWidth, AxesHeight
    End Sub

Sub UpdateCalc()
    PanelResize Calc, CalcWidth, CalcHeight
    End Sub

Sub UpdateBurn()
    PanelResize Burn, IfSng(Burn.ShowLog, BurnWidthEx, BurnWidth), BurnHeight
    End Sub

Sub UpdateMap()
    Resize Map, PanelWidth(Link, LinkWidth), PanelWidth(Link, LinkWidth) * 0.9
    End Sub

Sub UpdateGraph2()
    Resize Graph2, Graph2.TextWidth(Tab3) + 360, Graph2.TextHeight("") * GraphHeight + 8
    If Main.Visible Then Plot2
    End Sub

Sub UpdateInputBox()
    Resize InputBox, InputBox.TextWidth("O") * 45, InputBox.TextHeight("") * 9
    End Sub

Sub UpdateNote(Optional Text As String, Optional ByVal Delay As Long, _
        Optional ByVal Cancel As Long = 1, Optional ByVal Icon As Long = 1, _
        Optional ByVal Update As Boolean)
    Dim C As Long, X As Long, W As Long, H As Long
    With Note
    If Text <> "" Then .Txt = Text: .Caption = Text Else If Not .Visible Then Exit Sub
    W = .Txt.Width + 20: H = .Txt.Height + 10
    .iWarning.Top = (H - .iWarning.Height) / 2
    If Icon <> 1 Then .iWarning.Visible = Icon = -1: _
        C = IfInt(Icon = -1, &H80&, 0): If .BackColor <> C Then .BackColor = C
    If Cancel <> 1 Then .Active = Cancel: .Quit.Visible = Cancel: .Minimize.Visible = Cancel
    If .Quit.Visible Then W = W + .Quit.Width + 5 + .Minimize.Width + 2
    If Not .iWarning.Visible Then .Txt.Left = 10 _
    Else W = W + .iWarning.Width: .Txt.Left = .iWarning.Width + 10
    X = (H - .Quit.Height) / 2: If X > 4 Then X = 4
    .Quit.Move W - .Quit.Width - X, X: .Minimize.Move .Quit.Left - 2 - .Minimize.Width, X
    .Border.Move 0, 0, W, H
    End With
    SetWindowPos Note.hWnd, -1, _
        (Main.Left + Main.Width / 2 * (LimitDbl(Asymmetric, -0.7, 0.7) + 1)) / TwipX - W / 2, _
        (Main.Top + Main.Height / 2.2) / TwipY - H / 2, W, H, &H50&
    'If Note.WindowState <> vbNormal Then Note.WindowState = vbNormal: Note.Refresh
    If Note.Tm Then Note.Tm = False
    If Delay Then Note.Tm.Interval = Delay: Note.Tm = True
    If Update Then Note.Refresh
    End Sub

Sub HideNote()
    Note.Tm = False: Note.Hide: Note.Active = False
    End Sub

Sub PanelResize(F As Form, ByVal Width As Single, ByVal Height As Single)
    If F.FontSize <= 9 Then _
         Resize F, RoundInt((F.TextWidth("O") + 12) * Width + 3), _
                   RoundInt((F.TextHeight("") + 7) * Height + 3) _
    Else Resize F, RoundInt(F.TextWidth("O") * Width * 7 / 3 + 3), _
                   RoundInt(F.TextHeight("") * Height * 22 / 15 + 3)
    End Sub

Function PanelWidth(F As Form, ByVal Width As Single) As Long
    If F.FontSize <= 9 Then PanelWidth = RoundInt((F.TextWidth("O") + 12) * Width + 3) _
                       Else PanelWidth = RoundInt(F.TextWidth("O") * Width * 7 / 3 + 3)
    End Function

Function PanelHeight(F As Form, ByVal Height As Single) As Long
    If F.FontSize <= 9 Then PanelHeight = RoundInt((F.TextHeight("") + 7) * Height + 3) _
                       Else PanelHeight = RoundInt(F.TextHeight("") * Height * 22 / 15 + 3)
    End Function

Sub ResizeMain()
    Dim C As Long, L As Single, N As Single, X As Single, W As Single, _
        Left As Single, Top As Single, Right As Single, Bottom As Single, _
        Border As Single, Pt As PointApi
    On Error Resume Next
    With Main
    UpdateScreenSize
    If Options.Visible Then Options.MainBorder = GetWindowLong(.hWnd, -16) And &H400000
    If Not .Visible Then Exit Sub
    C = IIf(IsFullScreen(Main), 2, 1): If ToolBar.Maxi.Caption <> C Then ToolBar.Maxi.Caption = C
    If .WindowState = vbMinimized Then NoResize = IfInt(IsDebug, -1, -2): Note.Hide: Exit Sub
    If Note.Active Then SetWindowPos Note.hWnd, -1, , , , , &H53&
    If NoResize Then NoResize = NoResize + 1: Exit Sub
    Border = 10 * TwipX: ClientToScreen .hWnd, Pt
    Left = Pt.X * TwipX + Border: Right = (Pt.X + .ScaleWidth) * TwipX - Border
    Top = Pt.Y * TwipY + Border: Bottom = (Pt.Y + .ScaleHeight) * TwipY - Border
    If Left < FullLeft * TwipX + Border Then Left = FullLeft * TwipX + Border
    If Right > FullRight * TwipX - Border Then Right = FullRight * TwipX - Border
    If Top < FullTop * TwipY + Border Then Top = FullTop * TwipY + Border
    If Bottom > FullBottom * TwipY - Border Then Bottom = FullBottom * TwipY - Border
    InitCamera Cam1
    Logo.X = .ScaleWidth - Logo.W - 5: Logo.Y = 5
    If Options.SnapPanels Then
        Setup.Move Left, Bottom - Setup.Height - IfSng(Stages.Visible, Stages.Height, 0)
        If Setup.Visible Then MoveToRight Calc, Setup _
        Else Calc.Move Left, Bottom - Calc.Height
        Stages.Move Setup.Left, Setup.Top + Setup.Height
        If Stages.Visible Then MoveToRight Editor, Stages _
        Else If Setup.Visible Then MoveToRight Editor, Setup _
        Else Editor.Move Left, Bottom - Editor.Height
        If Editor.Visible Then MoveToRight Burn, Editor _
        Else If Stages.Visible Then MoveToRight Burn, Stages _
        Else If Setup.Visible Then MoveToRight Burn, Setup _
        Else Burn.Move Left, Bottom - Burn.Height
        If Stages.Visible Then MoveToRight Model2, Stages _
        Else If Setup.Visible Then MoveToRight Model2, Setup _
        Else Model2.Move Left, Bottom - Model2.Height
        Graph3D.Move Right - Graph3D.Width, Bottom - Graph3D.Height
        Log.Move Right - Log.Width, Bottom - Log.Height
        Data.Move Left, Top
        'X = IfSng(IsMultiScreen And Asymmetric <> 0 And .ScaleWidth >= FullWidth, _
            ScreenRight * TwipX - ToolBar.Width - Border, .Left + (.Width - ToolBar.Width) / 2)
        X = ScreenWidth * TwipX / 2: X = .Left + (IfSng( _
            IsMultiScreen And Asymmetric <> 0 And .ScaleWidth >= ScreenWidth, _
            IfSng(Asymmetric > 0, .Width - X, X), .Width / 2) - ToolBar.Width / 2)
        N = .Left + .Width - ToolBar.Width - Border: If X > N Then X = N
        N = Border + IfSng(Data.Visible, Data.Left + Data.Width, 0): If X < N Then X = N
        ToolBar.Move X, Top
        X = IfSng(ToolBar.Visible, ToolBar.Top + ToolBar.Height + Border, Top)
        W = ToolBar.Left + ToolBar.Width + Border
        N = Right - Graph.Width: Graph.Move N, IfSng(N < W, X, Top)
        N = Right - View.Width: View.Move N, IfSng(N < W, X, Top)
        N = Right - Link.Width: Link.Move N, IfSng(N < W, X, Top)
        N = Right - Map.Width: Map.Move N, IfSng(Link.Visible, Link.Top + Link.Height, _
            IfSng(N < W, X, Top))
        N = Right - Graph2.Width: Graph2.Move N, IfSng(Link.Visible, Link.Top + Link.Height, _
            IfSng(N < W, X, Top))
        L = IfSng(Link.Visible, Link.Left, Right)
        N = L - Axes.Width: Axes.Move N, IfSng(N < W, X, Top)
        L = L - IfSng(Axes.Visible, Axes.Width, 0)
        N = L - Onboard.Width: Onboard.Move N, IfSng(N < W, X, Top)
        N = L - Model.Width: Model.Move N, _
            IfSng(Onboard.Visible, Onboard.Top + Onboard.Height, IfSng(N < W, X, Top))
        N = Right - Preset.Width: Preset.Move N, IfSng(N < W, X, Top)
        N = Right - Options.Width: Options.Move N, IfSng(N < W, X, Top)
        N = IfSng(Options.Visible, Options.Left, Right) - Colors.Width: Colors.Move N, _
            IfSng(N < W, X, Top)
        Notepad.Move Right - Notepad.Width, Bottom - Notepad.Height
        Browser.Move Right - Browser.Width, Bottom - Browser.Height
        Search.Move IfSng(Browser.Visible, Browser.Left, Right) - Search.Width, _
            Bottom - Search.Height
        End If
    End With
    Render
    On Error GoTo 0
    End Sub

Sub MoveToRight(F As Form, X As Form)
    F.Move X.Left + X.Width, X.Top + X.Height - F.Height
    End Sub

Sub MoveToLeft(F As Form, X As Form)
    F.Move X.Left - F.Width, X.Top
    End Sub

Function Resize(F As Form, Optional ByVal Width As Long = -1, Optional ByVal Height As Long = -1, _
        Optional ByVal Relative As Boolean) As Boolean
    Dim W As Single, H As Single
    With F
    If .WindowState <> vbNormal Then Exit Function
    W = .Width: If Relative Then W = W + Width * TwipX _
    Else If Width >= 0 Then W = W + (Width - .ScaleWidth) * TwipX
    H = .Height: If Relative Then H = H + Height * TwipY _
    Else If Height >= 0 Then H = H + (Height - .ScaleHeight) * TwipY
    If .Width <> W Or .Height <> H Then Resize = True: _
        On Error Resume Next: .Move .Left, .Top, W, H: On Error GoTo 0
    End With
    End Function

Function ResizeWidth(F As Form, Optional ByVal Width As Long = -1, _
        Optional ByVal Relative As Boolean) As Boolean
    Dim W As Single
    With F
    If .WindowState <> vbNormal Then Exit Function
    W = .Width: If Relative Then W = W + Width * TwipX _
    Else If Width >= 0 Then W = W + (Width - .ScaleWidth) * TwipX
    If .Width <> W Then ResizeWidth = True: _
        On Error Resume Next: .Width = W: On Error GoTo 0
    End With
    End Function

Function ResizeHeight(F As Form, Optional ByVal Height As Long = -1, _
        Optional ByVal Relative As Boolean) As Boolean
    Dim H As Single
    With F
    If .WindowState <> vbNormal Then Exit Function
    H = .Height: If Relative Then H = H + Height * TwipY _
    Else If Height >= 0 Then H = H + (Height - .ScaleHeight) * TwipY
    If .Height <> H Then ResizeHeight = True: _
        On Error Resume Next: .Height = H: On Error GoTo 0
    End With
    End Function

Sub UpdateWidth(F As Form, ByVal Value As Boolean, Width As Single, WidthEx As Single)
    ResizeWidth F, Int((F.ScaleWidth - 3) * _
        IfSng(Value, WidthEx / Width, Width / WidthEx) + 0.5) + 3
    End Sub

Sub UpdateHeight(F As Form, ByVal Value As Boolean, Height As Single, HeightEx As Single)
    ResizeHeight F, Int((F.ScaleHeight - 3) * _
        IfSng(Value, HeightEx / Height, Height / HeightEx) + 0.5) + 3
    End Sub

Sub ResizeControl(C As Control, ByVal Left As Long, ByVal Top As Long, _
        ByVal Width As Long, ByVal Height As Long, _
        ByVal X As Long, ByVal Y As Long, ByVal W As Long, ByVal H As Long, _
        Optional ByVal Visible As Long = 1)
    On Error Resume Next
    Left = RoundInt(CDbl(Left) * X / W): Top = RoundInt(CDbl(Top) * Y / H)
    Width = RoundInt(CDbl(Width) * X / W): Height = RoundInt(CDbl(Height) * Y / H)
    Err = 0: C.Move Left, Top, Width, Height
    If Err Then SetWindowPos C.hWnd, , Left, Top, Width, Height, 4 '4=NoZOrd
    If Visible < 1 Then C.Visible = Visible: C.Enabled = Visible
    On Error GoTo 0
    End Sub

Sub ResizeSetup()
    Dim X As Long, Y As Long, W As Long, H As Long
    'GetControls Setup
    With Setup
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(IfSng(.ShowOverview, SetupWidthEx, SetupWidth) * 20 + 3)
    H = RoundInt(SetupHeight * 20 + 3)
    ResizeControl .AddTarget, 503, 5, 15, 15, X, Y, W, H
    ResizeControl .Alt, 100, 85, 90, 15, X, Y, W, H
    ResizeControl .Angle, 5, 85, 85, 15, X, Y, W, H
    ResizeControl .Brake, 200, 125, 95, 15, X, Y, W, H
    ResizeControl .Date, 5, 25, 94, 15, X, Y, W, H
    ResizeControl .DelTarget, 486, 5, 15, 15, X, Y, W, H
    ResizeControl .EarthPark, 200, 25, 95, 15, X, Y, W, H
    ResizeControl .EndAlt, 100, 125, 90, 15, X, Y, W, H
    ResizeControl .EndAngle, 5, 125, 85, 15, X, Y, W, H
    ResizeControl .Heading, 5, 65, 85, 15, X, Y, W, H
    ResizeControl .Lat, 100, 45, 90, 15, X, Y, W, H
    ResizeControl .LblTargets, 305, 5, 140, 15, X, Y, W, H
    ResizeControl .LiftLimit, 200, 85, 95, 15, X, Y, W, H
    ResizeControl .Location, 5, 45, 69, 15, X, Y, W, H
    ResizeControl .Lon, 100, 65, 90, 15, X, Y, W, H
    ResizeControl .MoonPark, 200, 45, 95, 15, X, Y, W, H
    ResizeControl .MoveDown, 469, 5, 15, 15, X, Y, W, H
    ResizeControl .MoveUp, 452, 5, 15, 15, X, Y, W, H
    ResizeControl .Overview, 300, 5, 220, 135, X, Y, W, H
    ResizeControl .Preset, 5, 5, 115, 15, X, Y, W, H
    ResizeControl .SelDate, 100, 25, 15, 15, X, Y, W, H
    ResizeControl .SelLocation, 75, 45, 15, 15, X, Y, W, H
    ResizeControl .SelPreset, 121, 5, 15, 15, X, Y, W, H
    ResizeControl .SelTime, 175, 25, 15, 15, X, Y, W, H
    ResizeControl .ShowCalc, 141, 5, 15, 15, X, Y, W, H
    ResizeControl .ShowOverview, 175, 5, 15, 15, X, Y, W, H
    ResizeControl .ShowStages, 158, 5, 15, 15, X, Y, W, H
    ResizeControl .ShowTargets, 280, 5, 15, 15, X, Y, W, H
    ResizeControl .Speed, 200, 105, 95, 15, X, Y, W, H
    ResizeControl .StartAlt, 100, 105, 90, 15, X, Y, W, H
    ResizeControl .StartAngle, 5, 105, 85, 15, X, Y, W, H
    ResizeControl .Targets, 300, 25, 220, 115, X, Y, W, H
    ResizeControl .ThrustLimit, 200, 65, 95, 15, X, Y, W, H
    ResizeControl .Time, 120, 25, 54, 15, X, Y, W, H
    ResizeControl .TimeStep, 200, 5, 78, 15, X, Y, W, H
    End With
    End Sub

Sub ResizeStages()
    Dim C As Long, D As Long, E As Boolean, N As Long, M As Long, _
        X As Long, Y As Long, W As Long, H As Long
    'GetControls Stages
    With Stages
    M = IfInt(.ShowParameters, StageMax, .Num)
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(IfSng(.ShowParameters, StagesWidthEx, StagesWidth) * 20 + 3)
    H = RoundInt((StagesHeight + M) * 20 + 3)
    N = PayloadMax
    For C = 0 To N
        D = (C - IfInt(C < N - 1, 0, N - 2 - M)) * 20 + 5
        E = C <= M Or C > N - 2
        If C < 1 Then
            ResizeControl .Mass(C), 5, D, 105, 15, X, Y, W, H, E
        ElseIf C < PayloadMax Then
            ResizeControl .Mass(C), 65, D, 45, 15, X, Y, W, H, E
        Else
            ResizeControl .Mass(C), 24, D, 86, 15, X, Y, W, H, E
            End If
        If C < PayloadMax Then
            ResizeControl .Delay(C), 210, D, 30, 15, X, Y, W, H, E
            ResizeControl .Dry(C), 115, D, 40, 15, X, Y, W, H, E
            ResizeControl .Fuel(C), 245, D, 69, 15, X, Y, W, H, E
            ResizeControl .SelFuel(C), 315, D, 15, 15, X, Y, W, H, E
            ResizeControl .Stage(C), 5, D, 60, 15, X, Y, W, H, E
            ResizeControl .Thrust(C), 160, D, 45, 15, X, Y, W, H, E
            End If
        Next
    D = (M + 2) * 20 + 5
    ResizeControl .Abort, 205, D, 45, 15, X, Y, W, H
    ResizeControl .AddStage, 298, 5, 15, 15, X, Y, W, H
    ResizeControl .CalcStages, 5, D, 15, 15, X, Y, W, H
    'ResizeControl .Cursor, 3, 23, 330, 20, X, Y, W, H
    ResizeControl .DelStage, 281, 5, 15, 15, X, Y, W, H
    ResizeControl .Jettison, 255, D, 60, 15, X, Y, W, H
    ResizeControl .Parachute, 115, D, 85, 15, X, Y, W, H
    ResizeControl .SelDragCf, 415, 105, 15, 15, X, Y, W, H
    ResizeControl .SelModel, 505, 145, 15, 15, X, Y, W, H
    ResizeControl .ShowEditor, 315, 5, 15, 15, X, Y, W, H
    ResizeControl .ShowParameters, 315, D, 15, 15, X, Y, W, H
    ResizeControl .LblDelay, 210, 5, 30, 15, X, Y, W, H
    ResizeControl .LblDry, 115, 5, 40, 15, X, Y, W, H
    ResizeControl .LblThrust, 160, 5, 45, 15, X, Y, W, H
    ResizeControl .LblType, 245, 5, 34, 15, X, Y, W, H
    ResizeControl .Sep, 338, 2, 3, 160, X, Y, W, H
    For C = 1 To PayloadMax: ResizeControl .Area(C), 345, 5, 85, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Battery(C), 440, 5, 80, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .CenterX(C), 345, 145, 41, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .CenterY(C), 388, 145, 20, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .CenterZ(C), 410, 145, 20, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Control(C), 345, 45, 85, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .ControlD(C), 410, 65, 20, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .ControlI(C), 388, 65, 20, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .ControlP(C), 345, 65, 41, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .DragCf(C), 345, 105, 69, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Emissivity(C), 440, 105, 80, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Imax(C), 440, 25, 80, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .InertiaX(C), 345, 125, 41, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .InertiaY(C), 388, 125, 20, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .InertiaZ(C), 410, 125, 20, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .IspS(C), 440, 125, 48, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .IspV(C), 490, 125, 30, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Length(C), 345, 25, 85, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .LiftToDrag(C), 345, 85, 85, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Model(C), 440, 145, 64, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Power(C), 440, 65, 80, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Tmax(C), 485, 85, 35, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Tmin(C), 440, 85, 43, 15, X, Y, W, H: Next
    For C = 1 To PayloadMax: ResizeControl .Vmax(C), 440, 45, 80, 15, X, Y, W, H: Next
    .Cursor.Move .Stage(1).Left - 2, .Mass(.Pos).Top - 2, _
        .SelFuel(1).Left + .SelFuel(1).Width - .Stage(1).Left + 5, .Stage(1).Height + 5
    .Cursor.Visible = .ShowParameters
    End With
    End Sub

Sub ResizeEditor()
    Dim C As Long, D As Long, _
        X As Long, Y As Long, W As Long, H As Long
    'GetControls Editor
    With Editor
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(EditorWidth * 20 + 3): H = RoundInt(EditorHeight * 20 + 3)
    For C = 1 To ComponentMax: D = (C - 1) * 20 + 5
        ResizeControl .Active(C), 5, D, 16, 15, X, Y, W, H
        ResizeControl .Mix(C), 22, D, 35, 15, X, Y, W, H
        ResizeControl .Component(C), 60, D, 150, 15, X, Y, W, H
        Next
    .Cursor.Move .Active(1).Left - 2, .Active(.Pos).Top - 2, _
        .Component(1).Left + .Component(1).Width - .Active(1).Left + 6, .Component(1).Height + 5
    ResizeControl .AddProp, 370, 4, 18, 17, X, Y, W, H
    ResizeControl .CalcGraph, 404, 24, 17, 16, X, Y, W, H
    ResizeControl .CalcMix, 423, 24, 17, 16, X, Y, W, H
    ResizeControl .CalcProp, 390, 4, 50, 17, X, Y, W, H
    ResizeControl .Chamber, 350, 45, 90, 15, X, Y, W, H
    ResizeControl .Class, 350, 165, 90, 15, X, Y, W, H
    ResizeControl .Cost, 350, 145, 90, 15, X, Y, W, H
    'ResizeControl .Cursor, 3, 3, 211, 20, X, Y, W, H
    ResizeControl .Curve, 215, 139, 110, 15, X, Y, W, H
    ResizeControl .Definition, 350, 25, 52, 15, X, Y, W, H
    ResizeControl .DelProp, 350, 4, 18, 17, X, Y, W, H
    ResizeControl .Density, 350, 125, 90, 15, X, Y, W, H
    ResizeControl .Fuel, 215, 2, 130, 133, X, Y, W, H
    ResizeControl .Hypergolic, 350, 185, 90, 15, X, Y, W, H
    ResizeControl .IspSL, 350, 225, 90, 15, X, Y, W, H
    ResizeControl .IspV, 350, 245, 90, 15, X, Y, W, H
    ResizeControl .List, 5, 125, 205, 135, X, Y, W, H
    ResizeControl .Nozzle, 350, 65, 90, 15, X, Y, W, H
    ResizeControl .Pic, 215, 159, 130, 100, X, Y, W, H
    ResizeControl .Ratio, 350, 85, 90, 15, X, Y, W, H
    ResizeControl .ShowBurn, 327, 138, 17, 16, X, Y, W, H
    ResizeControl .Temp, 350, 105, 90, 15, X, Y, W, H
    ResizeControl .Volume, 350, 205, 90, 15, X, Y, W, H
    End With
    End Sub

Sub ResizeLink()
    Dim C As Long, D As Long, _
        X As Long, Y As Long, W As Long, H As Long
    'GetControls Link
    With Link
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(LinkWidth * 20 + 3): H = RoundInt(LinkHeight * 20 + 3)
    For C = 0 To IDLast - 1: D = C * 20 + 25
        ResizeControl .LinkPort(C), 5, D, 120, 15, X, Y, W, H
        ResizeControl .IP(C), 5, 185, 120, 15, X, Y, W, H
        ResizeControl .Pass(C), 5, 165, 120, 15, X, Y, W, H
        ResizeControl .Ping(C), 5, 225, 120, 15, X, Y, W, H
        ResizeControl .Port(C), 5, 205, 120, 15, X, Y, W, H
        ResizeControl .Settings(C), 5, 105, 120, 15, X, Y, W, H
        ResizeControl .Speed(C), 5, 85, 120, 15, X, Y, W, H
        ResizeControl .TimeOut(C), 5, 125, 120, 15, X, Y, W, H
        ResizeControl .User(C), 5, 145, 120, 15, X, Y, W, H
        Next
    .Cursor.Move .LinkPort(0).Left - 3, .LinkPort(.Pos).Top - 2, _
        .LinkPort(0).Width + 6, .LinkPort(0).Height + 5
    ResizeControl .AddPoint, 246, 245, 15, 15, X, Y, W, H
    ResizeControl .ClearLog, 110, 5, 15, 15, X, Y, W, H
    ResizeControl .Cmd, 130, 205, 253, 35, X, Y, W, H
    'ResizeControl .Cursor, 3, 23, 125, 20, X, Y, W, H
    ResizeControl .DelPoint, 263, 245, 15, 15, X, Y, W, H
    ResizeControl .Mode, 5, 5, 69, 15, X, Y, W, H
    ResizeControl .NextPoint, 40, 245, 14, 15, X, Y, W, H
    ResizeControl .Num, 20, 245, 19, 15, X, Y, W, H
    ResizeControl .PosX, 65, 245, 82, 15, X, Y, W, H
    ResizeControl .PosY, 157, 245, 82, 15, X, Y, W, H
    ResizeControl .PrevPoint, 5, 245, 14, 15, X, Y, W, H
    ResizeControl .ShowCamera, 297, 245, 15, 15, X, Y, W, H
    ResizeControl .ShowCmd, 365, 245, 15, 15, X, Y, W, H
    ResizeControl .ShowControl, 280, 245, 15, 15, X, Y, W, H
    ResizeControl .ShowGraph, 348, 245, 15, 15, X, Y, W, H
    ResizeControl .ShowMap, 331, 245, 15, 15, X, Y, W, H
    ResizeControl .ShowModel, 314, 245, 15, 15, X, Y, W, H
    ResizeControl .StartLink, 76, 5, 15, 15, X, Y, W, H
    ResizeControl .StopLink, 93, 5, 15, 15, X, Y, W, H
    ResizeControl .Txt, 130, 1, 253, 200, X, Y, W, H
    End With
    End Sub

Sub ResizeAxes()
    Dim X As Long, Y As Long, W As Long, H As Long
    'GetControls Axes
    With Axes
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(AxesWidth * 20 + 3): H = RoundInt(AxesHeight * 20 + 3)
    ResizeControl .AlignAxes, 5, 240, 80, 15, X, Y, W, H
    ResizeControl .Control, 115, 235, 20, 20, X, Y, W, H
    'ResizeControl .ControlPic, 1, 1, 16, 16, X, Y, W, H
    ResizeControl .Device, 5, 25, 135, 15, X, Y, W, H
    ResizeControl .LblInputAxes, 5, 140, 70, 15, X, Y, W, H
    ResizeControl .LblInvert, 95, 140, 45, 15, X, Y, W, H
    ResizeControl .Mode, 5, 5, 135, 15, X, Y, W, H
    ResizeControl .NormalizeVector, 5, 220, 120, 15, X, Y, W, H
    ResizeControl .RSteering, 5, 45, 110, 15, X, Y, W, H
    ResizeControl .RSteeringR, 123, 45, 18, 15, X, Y, W, H
    ResizeControl .RThrust, 5, 65, 110, 15, X, Y, W, H
    ResizeControl .RThrustR, 123, 65, 18, 15, X, Y, W, H
    ResizeControl .Sep, 5, 130, 132, 3, X, Y, W, H
    ResizeControl .UPitch, 5, 65, 110, 15, X, Y, W, H
    ResizeControl .UPitchR, 123, 65, 18, 15, X, Y, W, H
    ResizeControl .URoll, 5, 45, 110, 15, X, Y, W, H
    ResizeControl .URollR, 123, 45, 18, 15, X, Y, W, H
    ResizeControl .UThrust, 5, 105, 110, 15, X, Y, W, H
    ResizeControl .UThrustR, 123, 105, 18, 15, X, Y, W, H
    ResizeControl .UYaw, 5, 85, 110, 15, X, Y, W, H
    ResizeControl .UYawR, 123, 85, 18, 15, X, Y, W, H
    ResizeControl .XAxis, 5, 160, 110, 15, X, Y, W, H
    ResizeControl .XAxisR, 123, 160, 18, 15, X, Y, W, H
    ResizeControl .YAxis, 5, 180, 110, 15, X, Y, W, H
    ResizeControl .YAxisR, 123, 180, 18, 15, X, Y, W, H
    ResizeControl .ZAxis, 5, 200, 110, 15, X, Y, W, H
    ResizeControl .ZAxisR, 123, 200, 18, 15, X, Y, W, H
    End With
    End Sub

Sub ResizeCalc()
    Dim X As Long, Y As Long, W As Long, H As Long
    'GetControls Calc
    With Calc
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(CalcWidth * 20 + 3): H = RoundInt(CalcHeight * 20 + 3)
    ResizeControl .Ascent, 5, 85, 115, 15, X, Y, W, H
    ResizeControl .Burst, 5, 65, 115, 15, X, Y, W, H
    ResizeControl .Calc, 30, 124, 60, 17, X, Y, W, H
    ResizeControl .Descent, 5, 105, 115, 15, X, Y, W, H
    ResizeControl .Mission, 5, 25, 115, 15, X, Y, W, H
    ResizeControl .Mode, 5, 5, 115, 15, X, Y, W, H
    ResizeControl .Orbit, 5, 45, 115, 15, X, Y, W, H
    End With
    End Sub

Sub ResizeBurn()
    Dim X As Long, Y As Long, W As Long, H As Long
    'GetControls Burn
    With Burn
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(IfSng(.ShowLog, BurnWidthEx, BurnWidth) * 20 + 3)
    H = RoundInt(BurnHeight * 20 + 3)
    ResizeControl .BurnExp, 105, 45, 40, 15, X, Y, W, H
    ResizeControl .BurnRate, 5, 45, 98, 15, X, Y, W, H
    ResizeControl .Burst, 5, 65, 140, 15, X, Y, W, H
    ResizeControl .CalcCurve, 128, 4, 17, 16, X, Y, W, H
    ResizeControl .CalcNozzle, 109, 4, 17, 16, X, Y, W, H
    ResizeControl .Chamber, 270, 5, 90, 15, X, Y, W, H
    ResizeControl .Class, 370, 5, 120, 15, X, Y, W, H
    ResizeControl .CoreDiameter, 370, 65, 120, 15, X, Y, W, H
    ResizeControl .CoreLength, 370, 45, 120, 15, X, Y, W, H
    ResizeControl .Density, 155, 45, 105, 15, X, Y, W, H
    ResizeControl .Diameter, 270, 45, 90, 15, X, Y, W, H
    ResizeControl .DryMass, 155, 105, 105, 15, X, Y, W, H
    ResizeControl .Efficiency, 155, 85, 105, 15, X, Y, W, H
    ResizeControl .EndPressure, 5, 105, 140, 15, X, Y, W, H
    ResizeControl .Exposed, 390, 25, 100, 15, X, Y, W, H
    ResizeControl .File, 5, 5, 102, 15, X, Y, W, H
    ResizeControl .FinDepth, 390, 85, 100, 15, X, Y, W, H
    ResizeControl .FinWidth, 370, 105, 100, 15, X, Y, W, H
    ResizeControl .iWarning, 5, 5, 16, 16, X, Y, W, H
    ResizeControl .K2ph, 220, 25, 40, 15, X, Y, W, H
    ResizeControl .Kmix, 155, 25, 63, 15, X, Y, W, H
    ResizeControl .Length, 270, 25, 90, 15, X, Y, W, H
    ResizeControl .Mol, 155, 5, 105, 15, X, Y, W, H
    ResizeControl .NFin, 370, 85, 15, 15, X, Y, W, H
    ResizeControl .NozzleExit, 270, 85, 90, 15, X, Y, W, H
    ResizeControl .NozzleThroat, 270, 65, 90, 15, X, Y, W, H
    ResizeControl .NSeg, 370, 25, 15, 15, X, Y, W, H
    ResizeControl .Pic, 5, 130, 320, 150, X, Y, W, H
    ResizeControl .PicD, 335, 222, 155, 58, X, Y, W, H
    ResizeControl .PicH, 335, 135, 85, 85, X, Y, W, H
    ResizeControl .PicV, 425, 135, 60, 85, X, Y, W, H
    ResizeControl .Pressure, 5, 85, 140, 15, X, Y, W, H
    ResizeControl .Propellant, 5, 25, 140, 15, X, Y, W, H
    ResizeControl .Sep, 5, 125, 485, 3, X, Y, W, H
    ResizeControl .ShowLog, 475, 105, 15, 15, X, Y, W, H
    ResizeControl .Temp, 155, 65, 105, 15, X, Y, W, H
    ResizeControl .Thickness, 270, 105, 90, 15, X, Y, W, H
    ResizeControl .Txt, 500, 1, 150, 280, X, Y, W, H
    End With
    End Sub

Sub ResizeOptions()
    Dim X As Long, Y As Long, W As Long, H As Long
    'GetControls Options
    With Options
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(OptionsWidth * 20 + 3): H = RoundInt(OptionsHeight * 20 + 3)
    ResizeControl .AbsoulteTemp, 180, 85, 160, 15, X, Y, W, H
    ResizeControl .AdaptiveStep, 15, 85, 145, 15, X, Y, W, H
    ResizeControl .AirDragLift, 170, 195, 135, 15, X, Y, W, H
    ResizeControl .Airports, 5, 305, 155, 15, X, Y, W, H
    ResizeControl .AirspaceDetection, 15, 105, 145, 15, X, Y, W, H
    ResizeControl .AirspaceGroup, 5, 265, 155, 15, X, Y, W, H
    ResizeControl .Airspaces, 5, 285, 155, 15, X, Y, W, H
    ResizeControl .AirspaceZone, 5, 245, 155, 15, X, Y, W, H
    ResizeControl .Airways, 15, 25, 125, 15, X, Y, W, H
    ResizeControl .AirwaysAltitude, 180, 465, 160, 15, X, Y, W, H
    ResizeControl .Altimeter, 15, 45, 125, 15, X, Y, W, H
    ResizeControl .Altitude, 170, 25, 85, 15, X, Y, W, H
    ResizeControl .AltitudeReference, 170, 215, 170, 15, X, Y, W, H
    ResizeControl .Antialiasing, 5, 125, 155, 15, X, Y, W, H
    ResizeControl .Apoapsis, 190, 100, 150, 15, X, Y, W, H
    ResizeControl .Apogee, 190, 20, 150, 15, X, Y, W, H
    ResizeControl .Aposelene, 190, 60, 150, 15, X, Y, W, H
    ResizeControl .Apsis, 170, 65, 170, 15, X, Y, W, H
    ResizeControl .AreaDetection, 15, 125, 130, 15, X, Y, W, H
    ResizeControl .Asteroid, 180, 125, 160, 15, X, Y, W, H
    ResizeControl .AsteroidAlert, 15, 145, 145, 15, X, Y, W, H
    ResizeControl .AsteroidDensity, 180, 165, 160, 15, X, Y, W, H
    ResizeControl .AsteroidDist, 180, 145, 160, 15, X, Y, W, H
    ResizeControl .AsteroidFaraway, 190, 470, 150, 15, X, Y, W, H
    ResizeControl .AsteroidProximity, 190, 450, 150, 15, X, Y, W, H
    ResizeControl .Asteroids, 15, 65, 125, 15, X, Y, W, H
    ResizeControl .Atmosphere, 15, 85, 125, 15, X, Y, W, H
    ResizeControl .AtmosphereDrag, 15, 165, 140, 15, X, Y, W, H
    ResizeControl .AtmosphereLift, 15, 185, 130, 15, X, Y, W, H
    ResizeControl .AtmosphericModel, 15, 65, 150, 15, X, Y, W, H
    ResizeControl .AtmPressure, 180, 185, 145, 15, X, Y, W, H
    ResizeControl .Axes, 15, 105, 70, 15, X, Y, W, H
    ResizeControl .BalloonTrajectory, 15, 205, 140, 15, X, Y, W, H
    ResizeControl .BarEvents, 180, 5, 3, 480, X, Y, W, H
    ResizeControl .BarOptions, 170, 5, 3, 475, X, Y, W, H
    ResizeControl .BarScreen, 170, 5, 3, 435, X, Y, W, H
    ResizeControl .BarSimulation, 175, 5, 3, 435, X, Y, W, H
    ResizeControl .BarTools, 150, 5, 3, 475, X, Y, W, H
    ResizeControl .Boundaries, 15, 125, 95, 15, X, Y, W, H
    ResizeControl .BrakingAltitude, 190, 380, 150, 15, X, Y, W, H
    ResizeControl .BrakingDistance, 170, 85, 150, 15, X, Y, W, H
    ResizeControl .BtnEvents, 145, 5, 60, 17, X, Y, W, H
    ResizeControl .BtnOptions, 275, 5, 60, 17, X, Y, W, H
    ResizeControl .BtnScreen, 210, 5, 60, 17, X, Y, W, H
    ResizeControl .BtnSimulation, 65, 5, 75, 17, X, Y, W, H
    ResizeControl .BtnTools, 5, 5, 55, 17, X, Y, W, H
    ResizeControl .CameraMove, 180, 225, 160, 15, X, Y, W, H
    ResizeControl .CameraRotate, 180, 245, 160, 15, X, Y, W, H
    ResizeControl .CameraZoom, 180, 265, 160, 15, X, Y, W, H
    ResizeControl .CaptureMouse, 5, 185, 125, 15, X, Y, W, H
    ResizeControl .Cities, 5, 445, 155, 15, X, Y, W, H
    ResizeControl .ColorTheme, 26, 5, 134, 15, X, Y, W, H
    ResizeControl .Comets, 15, 145, 125, 15, X, Y, W, H
    ResizeControl .Compass, 15, 165, 125, 15, X, Y, W, H
    ResizeControl .Coordinates, 180, 65, 160, 15, X, Y, W, H
    ResizeControl .Crosshair, 170, 385, 105, 15, X, Y, W, H
    ResizeControl .Deviation, 170, 405, 105, 15, X, Y, W, H
    ResizeControl .EarthEscapeSpeed, 5, 280, 165, 15, X, Y, W, H
    ResizeControl .EarthOrbitalSpeed, 5, 260, 165, 15, X, Y, W, H
    ResizeControl .EarthSoundSpeed, 5, 240, 165, 15, X, Y, W, H
    ResizeControl .EarthTexture, 5, 225, 155, 15, X, Y, W, H
    ResizeControl .EmptyStages, 15, 225, 125, 15, X, Y, W, H
    ResizeControl .EnterAirspace, 5, 430, 165, 15, X, Y, W, H
    ResizeControl .EnterAtmosphere, 190, 170, 150, 15, X, Y, W, H
    ResizeControl .EnterIonosphere, 190, 210, 150, 15, X, Y, W, H
    ResizeControl .EquidistantPoint, 190, 240, 150, 15, X, Y, W, H
    ResizeControl .EventsAll, 5, 20, 165, 15, X, Y, W, H
    ResizeControl .ExitAirspace, 5, 450, 165, 15, X, Y, W, H
    ResizeControl .ExitAtmosphere, 190, 150, 150, 15, X, Y, W, H
    ResizeControl .ExitIonosphere, 190, 190, 150, 15, X, Y, W, H
    ResizeControl .FieldDensity, 180, 385, 160, 15, X, Y, W, H
    ResizeControl .FieldDistance, 180, 365, 160, 15, X, Y, W, H
    ResizeControl .FieldScale, 180, 405, 160, 15, X, Y, W, H
    ResizeControl .FOV, 170, 425, 160, 15, X, Y, W, H
    ResizeControl .GeoidAltitude, 180, 145, 120, 15, X, Y, W, H
    ResizeControl .GraphTime, 5, 45, 155, 15, X, Y, W, H
    ResizeControl .Gravity, 15, 25, 150, 15, X, Y, W, H
    ResizeControl .GravityDisk, 180, 225, 140, 15, X, Y, W, H
    ResizeControl .GravityField, 15, 185, 100, 15, X, Y, W, H
    ResizeControl .GravityGrid, 180, 265, 140, 15, X, Y, W, H
    ResizeControl .GravityVector, 180, 285, 145, 15, X, Y, W, H
    ResizeControl .GravityZone, 180, 245, 145, 15, X, Y, W, H
    ResizeControl .Grid, 15, 205, 70, 15, X, Y, W, H
    ResizeControl .GridStep, 5, 105, 155, 15, X, Y, W, H
    ResizeControl .GroundProjection, 170, 235, 150, 15, X, Y, W, H
    ResizeControl .GroundSpeed, 170, 255, 130, 15, X, Y, W, H
    ResizeControl .HiddenLines, 5, 325, 115, 15, X, Y, W, H
    ResizeControl .HiddenPoints, 5, 305, 120, 15, X, Y, W, H
    ResizeControl .HideCursor, 5, 425, 155, 15, X, Y, W, H
    ResizeControl .HiDefDetection, 15, 265, 150, 15, X, Y, W, H
    ResizeControl .HoverEnginesOff, 5, 130, 165, 15, X, Y, W, H
    ResizeControl .HoverEnginesOn, 5, 110, 165, 15, X, Y, W, H
    ResizeControl .InfoLabel, 5, 385, 140, 15, X, Y, W, H
    ResizeControl .Ionosphere, 170, 45, 170, 15, X, Y, W, H
    ResizeControl .IspSeaLevel, 15, 285, 130, 15, X, Y, W, H
    ResizeControl .L1LagrangePoint, 190, 260, 150, 15, X, Y, W, H
    ResizeControl .LabelEvents1, 70, 2, 100, 15, X, Y, W, H
    ResizeControl .LabelEvents2, 240, 2, 100, 15, X, Y, W, H
    ResizeControl .Labels, 170, 445, 160, 15, X, Y, W, H
    ResizeControl .LabelsDensity, 180, 425, 160, 15, X, Y, W, H
    ResizeControl .LandingManeuver, 195, 35, 140, 15, X, Y, W, H
    ResizeControl .LandLimits, 5, 385, 135, 15, X, Y, W, H
    ResizeControl .LastStageEnginesOff, 5, 150, 165, 15, X, Y, W, H
    ResizeControl .Launch, 15, 225, 100, 15, X, Y, W, H
    ResizeControl .LaunchTrajectory, 195, 15, 140, 15, X, Y, W, H
    ResizeControl .Layer, 5, 125, 155, 15, X, Y, W, H
    ResizeControl .LayerClouds, 5, 185, 145, 15, X, Y, W, H
    ResizeControl .LayerHumidity, 5, 165, 130, 15, X, Y, W, H
    ResizeControl .LayerTemp, 5, 145, 155, 15, X, Y, W, H
    ResizeControl .LayerWind, 5, 205, 145, 15, X, Y, W, H
    ResizeControl .LayerWindVector, 5, 225, 155, 15, X, Y, W, H
    ResizeControl .Lens, 180, 65, 160, 15, X, Y, W, H
    ResizeControl .LiftOff, 5, 50, 165, 15, X, Y, W, H
    ResizeControl .LinesDefinition, 180, 445, 160, 15, X, Y, W, H
    ResizeControl .LinkError, 5, 470, 165, 15, X, Y, W, H
    ResizeControl .Locations, 15, 245, 125, 15, X, Y, W, H
    ResizeControl .LogAcceleration, 195, 265, 135, 15, X, Y, W, H
    ResizeControl .LogAirDrag, 195, 305, 135, 15, X, Y, W, H
    ResizeControl .LogAirspaces, 195, 405, 130, 15, X, Y, W, H
    ResizeControl .LogAirSpeed, 195, 225, 135, 15, X, Y, W, H
    ResizeControl .LogAltitude, 195, 165, 135, 15, X, Y, W, H
    ResizeControl .LogCoordinates, 195, 145, 135, 15, X, Y, W, H
    ResizeControl .LogDate, 195, 105, 120, 15, X, Y, W, H
    ResizeControl .LogDistance, 195, 185, 135, 15, X, Y, W, H
    ResizeControl .LogGravity, 195, 325, 135, 15, X, Y, W, H
    ResizeControl .LogInertial, 195, 285, 135, 15, X, Y, W, H
    ResizeControl .LogMass, 195, 345, 70, 15, X, Y, W, H
    ResizeControl .LogSeparator, 180, 205, 160, 15, X, Y, W, H
    ResizeControl .LogSpeed, 195, 205, 135, 15, X, Y, W, H
    ResizeControl .LogStations, 195, 385, 130, 15, X, Y, W, H
    ResizeControl .LogTemperature, 195, 365, 115, 15, X, Y, W, H
    ResizeControl .LogThrust, 195, 245, 80, 15, X, Y, W, H
    ResizeControl .LogTime, 195, 125, 110, 15, X, Y, W, H
    ResizeControl .LogToFile, 185, 85, 100, 15, X, Y, W, H
    ResizeControl .LowerAirways, 5, 325, 155, 15, X, Y, W, H
    ResizeControl .MagneticField, 15, 265, 100, 15, X, Y, W, H
    ResizeControl .MagneticLines, 180, 305, 150, 15, X, Y, W, H
    ResizeControl .MagneticMoment, 180, 165, 145, 15, X, Y, W, H
    ResizeControl .MagneticPlanes, 180, 325, 160, 15, X, Y, W, H
    ResizeControl .MagneticVector, 170, 275, 160, 15, X, Y, W, H
    ResizeControl .MagneticVectors, 180, 345, 160, 15, X, Y, W, H
    ResizeControl .MainBorder, 5, 65, 150, 15, X, Y, W, H
    ResizeControl .MainPlane, 180, 25, 160, 15, X, Y, W, H
    ResizeControl .MarsEscapeSpeed, 5, 360, 165, 15, X, Y, W, H
    ResizeControl .MarsOrbitalSpeed, 5, 340, 165, 15, X, Y, W, H
    ResizeControl .MoonDistance, 190, 280, 150, 15, X, Y, W, H
    ResizeControl .MoonEscapeSpeed, 5, 320, 165, 15, X, Y, W, H
    ResizeControl .MoonOrbitalSpeed, 5, 300, 165, 15, X, Y, W, H
    ResizeControl .Moons, 15, 285, 125, 15, X, Y, W, H
    ResizeControl .MoonTransfer, 5, 190, 165, 15, X, Y, W, H
    ResizeControl .NetGravity, 170, 295, 145, 15, X, Y, W, H
    ResizeControl .ObjectsOrbits, 170, 125, 125, 15, X, Y, W, H
    ResizeControl .ObjectVisibility, 180, 185, 160, 15, X, Y, W, H
    ResizeControl .OrbitalPlane, 170, 105, 120, 15, X, Y, W, H
    ResizeControl .PanelsStyle, 5, 25, 155, 15, X, Y, W, H
    ResizeControl .Path, 15, 305, 125, 15, X, Y, W, H
    ResizeControl .PathPoints, 5, 345, 145, 15, X, Y, W, H
    ResizeControl .PathTime, 5, 25, 155, 15, X, Y, W, H
    ResizeControl .Periapsis, 190, 120, 150, 15, X, Y, W, H
    ResizeControl .Perigee, 190, 40, 150, 15, X, Y, W, H
    ResizeControl .Periselene, 190, 80, 150, 15, X, Y, W, H
    ResizeControl .Places, 5, 465, 155, 15, X, Y, W, H
    ResizeControl .PlanetApproach, 190, 320, 150, 15, X, Y, W, H
    ResizeControl .PlanetCollision, 15, 305, 125, 15, X, Y, W, H
    ResizeControl .PlanetDistance, 190, 300, 150, 15, X, Y, W, H
    ResizeControl .PlanetFaraway, 190, 360, 150, 15, X, Y, W, H
    ResizeControl .PlanetProximity, 190, 340, 150, 15, X, Y, W, H
    ResizeControl .Planets, 15, 325, 125, 15, X, Y, W, H
    ResizeControl .PlanetShadow, 15, 325, 125, 15, X, Y, W, H
    ResizeControl .PlanetsOrbits, 170, 145, 125, 15, X, Y, W, H
    ResizeControl .PlanetTextures, 5, 245, 150, 15, X, Y, W, H
    ResizeControl .PopulatedAreas, 5, 425, 135, 15, X, Y, W, H
    ResizeControl .Ranges, 160, 5, 85, 15, X, Y, W, H
    ResizeControl .RealTime, 15, 345, 130, 15, X, Y, W, H
    ResizeControl .RelativeSpeed, 180, 105, 130, 15, X, Y, W, H
    ResizeControl .RelativeTime, 180, 125, 115, 15, X, Y, W, H
    ResizeControl .Report, 15, 345, 80, 15, X, Y, W, H
    ResizeControl .RNAVRoutes, 5, 345, 155, 15, X, Y, W, H
    ResizeControl .Satellite, 180, 85, 160, 15, X, Y, W, H
    ResizeControl .SatelliteAlert, 15, 365, 140, 15, X, Y, W, H
    ResizeControl .SatelliteControl, 195, 55, 130, 15, X, Y, W, H
    ResizeControl .SatelliteDeployed, 5, 170, 165, 15, X, Y, W, H
    ResizeControl .SatelliteDist, 180, 105, 160, 15, X, Y, W, H
    ResizeControl .SatelliteFaraway, 190, 430, 150, 15, X, Y, W, H
    ResizeControl .SatelliteProximity, 190, 410, 150, 15, X, Y, W, H
    ResizeControl .Satellites, 15, 365, 125, 15, X, Y, W, H
    ResizeControl .SeaLimits, 5, 405, 140, 15, X, Y, W, H
    ResizeControl .Seaports, 5, 365, 155, 15, X, Y, W, H
    ResizeControl .Sensor, 180, 45, 160, 15, X, Y, W, H
    ResizeControl .Sensors, 160, 365, 85, 15, X, Y, W, H
    ResizeControl .ShipScale, 180, 325, 160, 15, X, Y, W, H
    ResizeControl .ShowColor, 5, 4, 17, 16, X, Y, W, H
    ResizeControl .ShowErrors, 5, 405, 135, 15, X, Y, W, H
    ResizeControl .ShowIcons, 180, 345, 160, 15, X, Y, W, H
    ResizeControl .SimulationAll, 5, 5, 110, 15, X, Y, W, H
    ResizeControl .SmoothLines, 5, 285, 115, 15, X, Y, W, H
    ResizeControl .SnapPanels, 5, 45, 155, 15, X, Y, W, H
    ResizeControl .SnapTime, 5, 5, 155, 15, X, Y, W, H
    ResizeControl .SolarWind, 15, 385, 150, 15, X, Y, W, H
    ResizeControl .Speed, 170, 315, 90, 15, X, Y, W, H
    ResizeControl .StageAbort, 5, 210, 165, 15, X, Y, W, H
    ResizeControl .StageEnginesOff, 5, 90, 165, 15, X, Y, W, H
    ResizeControl .StageEnginesOn, 5, 70, 165, 15, X, Y, W, H
    ResizeControl .StageInfo, 15, 385, 95, 15, X, Y, W, H
    ResizeControl .StarDensity, 180, 205, 160, 15, X, Y, W, H
    ResizeControl .Stars, 15, 405, 125, 15, X, Y, W, H
    ResizeControl .Station, 180, 5, 160, 15, X, Y, W, H
    ResizeControl .StationAngle, 180, 25, 160, 15, X, Y, W, H
    ResizeControl .StationAvailable, 5, 390, 165, 15, X, Y, W, H
    ResizeControl .Stations, 15, 425, 125, 15, X, Y, W, H
    ResizeControl .StationUnAvailable, 5, 410, 165, 15, X, Y, W, H
    ResizeControl .StationVisibility, 15, 245, 150, 15, X, Y, W, H
    ResizeControl .Statistics, 15, 405, 100, 15, X, Y, W, H
    ResizeControl .SunShadow, 180, 305, 160, 15, X, Y, W, H
    ResizeControl .TabEvents, 0, 25, 343, 493, X, Y, W, H
    ResizeControl .TabOptions, 0, 25, 343, 493, X, Y, W, H
    ResizeControl .TabScreen, 0, 25, 343, 493, X, Y, W, H
    ResizeControl .TabSimulation, 0, 25, 343, 493, X, Y, W, H
    ResizeControl .TabTools, 0, 25, 343, 493, X, Y, W, H
    ResizeControl .Terrain, 15, 445, 85, 15, X, Y, W, H
    ResizeControl .TerrainModel, 15, 45, 150, 15, X, Y, W, H
    ResizeControl .TerrainQuality, 5, 65, 155, 15, X, Y, W, H
    ResizeControl .TerrainScale, 5, 85, 155, 15, X, Y, W, H
    ResizeControl .TextTextures, 5, 265, 140, 15, X, Y, W, H
    ResizeControl .TileTextures, 5, 205, 155, 15, X, Y, W, H
    ResizeControl .TimePoints, 5, 365, 110, 15, X, Y, W, H
    ResizeControl .ToolsAll, 5, 5, 135, 15, X, Y, W, H
    ResizeControl .ToolTipDelay, 180, 285, 160, 15, X, Y, W, H
    ResizeControl .Trace, 170, 465, 85, 15, X, Y, W, H
    ResizeControl .Trajectory, 15, 465, 125, 15, X, Y, W, H
    ResizeControl .Units, 180, 45, 160, 15, X, Y, W, H
    ResizeControl .UseOpenGL, 5, 85, 110, 15, X, Y, W, H
    ResizeControl .Vectors, 160, 175, 85, 15, X, Y, W, H
    ResizeControl .VerticalSpeed, 170, 335, 125, 15, X, Y, W, H
    ResizeControl .VerticalSync, 5, 105, 115, 15, X, Y, W, H
    ResizeControl .VideoFPS, 26, 145, 134, 15, X, Y, W, H
    ResizeControl .VideoMode, 5, 165, 155, 15, X, Y, W, H
    ResizeControl .VideoSettings, 5, 144, 17, 16, X, Y, W, H
    ResizeControl .ViewerMode, 180, 5, 160, 15, X, Y, W, H
    ResizeControl .Wind, 15, 425, 110, 15, X, Y, W, H
    End With
    End Sub

Sub ResizePreset()
    Dim C As Long, _
        X As Long, Y As Long, W As Long, H As Long
    'GetControls Preset
    With Preset
    X = RoundInt(.ScaleWidth): Y = RoundInt(.ScaleHeight)
    W = RoundInt(PresetWidth * 20 + 3): H = RoundInt(PresetHeight * 20 + 3)
    For C = 1 To 7: ResizeControl .cModel(C), 880, 40 + 16 * (C - 1), 15, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .pDragCoeff(C), 607, 40 + 16 * (C - 1), 15, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .pStg(C), 243, 40 + 16 * (C - 1), 45, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgAbort(C), 863, 215 + 16 * (C - 1), 15, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgActive(C), 225, 40 + 16 * (C - 1), 15, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgBattery(C), 612, 215 + 16 * (C - 1), 46, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgCenterX(C), 721, 40 + 16 * (C - 1), 28, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgCenterY(C), 751, 40 + 16 * (C - 1), 28, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgCenterZ(C), 781, 40 + 16 * (C - 1), 28, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgD(C), 444, 215 + 16 * (C - 1), 20, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgDelay(C), 786, 215 + 16 * (C - 1), 30, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgDiameter(C), 411, 40 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgDragCoeff(C), 537, 40 + 16 * (C - 1), 69, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgDryMass(C), 369, 40 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgEmmisivity(C), 490, 215 + 16 * (C - 1), 35, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgForce(C), 368, 215 + 16 * (C - 1), 30, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgFuel(C), 335, 215 + 16 * (C - 1), 15, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgI(C), 422, 215 + 16 * (C - 1), 20, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgImax(C), 660, 215 + 16 * (C - 1), 30, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgInertiaX(C), 627, 40 + 16 * (C - 1), 28, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgInertiaY(C), 657, 40 + 16 * (C - 1), 28, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgInertiaZ(C), 687, 40 + 16 * (C - 1), 28, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgJettison(C), 831, 215 + 16 * (C - 1), 15, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgLength(C), 453, 40 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgLiftToDrag(C), 495, 40 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgMass(C), 285, 40 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgMaxTemp(C), 559, 215 + 16 * (C - 1), 30, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgMinTemp(C), 527, 215 + 16 * (C - 1), 30, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgModel(C), 815, 40 + 16 * (C - 1), 64, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgP(C), 400, 215 + 16 * (C - 1), 20, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgPower(C), 724, 215 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgPropellant(C), 270, 215 + 16 * (C - 1), 64, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgThrust(C), 228, 215 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgVmax(C), 692, 215 + 16 * (C - 1), 30, 15, X, Y, W, H: Next
    For C = 1 To 7: ResizeControl .StgWetMass(C), 327, 40 + 16 * (C - 1), 40, 15, X, Y, W, H: Next
    ResizeControl .cApply, 145, 220, 65, 17, X, Y, W, H
    ResizeControl .Charges, 615, 335, 150, 80, X, Y, W, H
    ResizeControl .cLoad, 5, 220, 65, 17, X, Y, W, H
    ResizeControl .cSave, 75, 220, 65, 17, X, Y, W, H
    ResizeControl .GroundStations, 5, 275, 200, 140, X, Y, W, H
    ResizeControl .lStageD, 451, 200, 10, 15, X, Y, W, H
    ResizeControl .pAttitudeSubsystem, 360, 185, 113, 13, X, Y, W, H
    ResizeControl .pBasicDesign, 245, 10, 374, 13, X, Y, W, H
    ResizeControl .pCommunicationsSubsystem, 5, 255, 164, 15, X, Y, W, H
    ResizeControl .pJettisonSubsystem, 780, 185, 112, 13, X, Y, W, H
    ResizeControl .pPowerSubsystem, 610, 185, 156, 13, X, Y, W, H
    ResizeControl .pPresetName, 9, 11, 49, 15, X, Y, W, H
    ResizeControl .PresetName, 55, 11, 141, 15, X, Y, W, H
    ResizeControl .PresetOverview, 9, 35, 203, 175, X, Y, W, H
    ResizeControl .pStageAbort, 863, 200, 30, 15, X, Y, W, H
    ResizeControl .pStageBattery, 612, 200, 49, 15, X, Y, W, H
    ResizeControl .pStageCenterOfMass, 726, 10, 80, 15, X, Y, W, H
    ResizeControl .pStageCMX, 731, 25, 15, 15, X, Y, W, H
    ResizeControl .pStageCMY, 761, 25, 15, 15, X, Y, W, H
    ResizeControl .pStageCMZ, 791, 25, 15, 15, X, Y, W, H
    ResizeControl .pStageDelay, 786, 200, 33, 15, X, Y, W, H
    ResizeControl .pStageDiameter, 415, 25, 49, 15, X, Y, W, H
    ResizeControl .pStageDragCoeff, 538, 25, 80, 15, X, Y, W, H
    ResizeControl .pStageDryMass, 365, 25, 49, 15, X, Y, W, H
    ResizeControl .pStageEmissivity, 490, 200, 49, 15, X, Y, W, H
    ResizeControl .pStageForce, 370, 200, 28, 15, X, Y, W, H
    ResizeControl .pStageI, 430, 200, 10, 15, X, Y, W, H
    ResizeControl .pStageImax, 660, 200, 25, 15, X, Y, W, H
    ResizeControl .pStageJettison, 820, 200, 41, 15, X, Y, W, H
    ResizeControl .pStageLength, 465, 25, 36, 15, X, Y, W, H
    ResizeControl .pStageLiftToDrag, 505, 25, 20, 15, X, Y, W, H
    ResizeControl .pStageMass, 285, 25, 29, 15, X, Y, W, H
    ResizeControl .pStageModel, 820, 25, 55, 15, X, Y, W, H
    ResizeControl .pStageMoIX, 637, 25, 15, 15, X, Y, W, H
    ResizeControl .pStageMoIY, 667, 25, 15, 15, X, Y, W, H
    ResizeControl .pStageMoIZ, 697, 25, 15, 15, X, Y, W, H
    ResizeControl .pStageMomentOfInertia, 627, 10, 95, 15, X, Y, W, H
    ResizeControl .pStageP, 407, 200, 10, 15, X, Y, W, H
    ResizeControl .pStagePower, 725, 200, 39, 15, X, Y, W, H
    ResizeControl .pStagePropellant, 275, 200, 51, 15, X, Y, W, H
    ResizeControl .pStageTemperature, 546, 200, 33, 15, X, Y, W, H
    ResizeControl .pStageThrust, 230, 200, 37, 15, X, Y, W, H
    ResizeControl .pStageVmax, 692, 200, 25, 15, X, Y, W, H
    ResizeControl .pStageWetMass, 315, 25, 49, 15, X, Y, W, H
    ResizeControl .pThermalSubsystem, 485, 185, 113, 13, X, Y, W, H
    ResizeControl .pThrustSubsystem, 230, 185, 118, 13, X, Y, W, H
    ResizeControl .pTotalMass, 243, 156, 50, 15, X, Y, W, H
    ResizeControl .Sep1, 5, 245, 206, 3, X, Y, W, H
    ResizeControl .Sep2, 215, 5, 3, 410, X, Y, W, H
    ResizeControl .Sep3, 225, 175, 661, 3, X, Y, W, H
    ResizeControl .Sep4, 355, 180, 3, 235, X, Y, W, H
    ResizeControl .Sep5, 480, 180, 3, 235, X, Y, W, H
    ResizeControl .Sep6, 605, 180, 3, 235, X, Y, W, H
    ResizeControl .Sep7, 775, 180, 3, 235, X, Y, W, H
    ResizeControl .ThermalNodes, 490, 335, 105, 80, X, Y, W, H
    ResizeControl .TotalDiameter, 411, 156, 40, 15, X, Y, W, H
    ResizeControl .TotalLength, 453, 156, 40, 15, X, Y, W, H
    ResizeControl .TotalMass, 285, 156, 40, 15, X, Y, W, H
    ResizeControl .Waypoints, 370, 335, 99, 80, X, Y, W, H
    End With
    End Sub

Sub GetControls(F As Form, Optional X As Double = 1, Optional Y As Double = 1)
    Dim B As Long, C As Control, S As String
    Set List = Nothing
    On Error Resume Next
    For Each C In F.Controls
        B = -1: B = C.Index
        If Left(C.Name, 2) <> "mn" Then
            ListAdd "    ResizeControl ." & _
                C.Name & IfText(B >= 0, "(" & B & ")") & "," & _
                Str(CDbl(C.Left) * X) & "," & Str(CDbl(C.Top) * Y) & "," & _
                Str(CDbl(C.Width) * X) & "," & Str(CDbl(C.Height) * Y) & _
                ", X, Y, W, H" & vbCrLf
            End If
        Next
    On Error GoTo 0
    For B = 1 To List.Count: StrAdd List(B): Next
    Clipboard.Clear: Clipboard.SetText StrAdd
    End Sub

Sub ShowMenu(Optional ByVal F As Form, Optional M As Menu, Optional C As Control, _
        Optional ByVal Width As Long, Optional ByVal Height As Long = -1, _
        Optional ByVal Align As MenuControlConstants = vbPopupMenuLeftAlign)
    Dim V As Boolean
    If F Is Nothing Then Set F = Main
    If M Is Nothing Then Set M = Main.mnMenu
    V = M.Visible: If C Is Nothing Then F.PopupMenu M _
    Else F.PopupMenu M, Align, _
        C.Left + Switch(Width > 0, Width, Width, C.Width, True, 0), _
        C.Top + Switch(Height > 0, Height, Height, C.Height, True, 0)
    M.Visible = V
    'Dim hMenu As Long: hMenu = CreatePopupMenu
    'AppendMenu hMenu, , 1, "Normal"
    'AppendMenu hMenu, 3, 2, "Disabled" 'MF_GRAYED=1 MF_DISABLED=2
    'AppendMenu hMenu, &H800& 'MF_SEPARATOR=&H800
    'AppendMenu hMenu, 8, 3, "Checked" 'MF_CHECKED=8
    'Dim Mi As MenuInfo
    'With Mi: .cbSize = Len(Mi): .fMask = &H10&: .dwStyle = &H40000000: End With 'MIM_STYLE=&H10 MNS_MODELESS=&H40000000
    'SetMenuInfo hMenu, Mi
    'Dim Mii As MenuItemInfo: With Mii: .cbSize = Len(Mii): .fMask = 4: End With 'MIIM_SUBMENU=4
    'GetMenuItemInfo GetMenu(hWnd), 0, 1, Mii
    'Dim Pt As PointApi: GetCursorPos Pt
    'N = TrackPopupMenu(hMenu, &H180&, Pt.X, Pt.Y, , hWnd) 'TPM_RETURNCMD=&H100 TPM_NONOTIFY=&H80
    'DestroyMenu hMenu: hMenu = 0
    End Sub

Function Value(Check As Label) As Boolean
    Value = Check = "a" Or Check = "3" Or Check = "5"
    End Function

Sub SetValue(Check As Label, Optional ByVal Value As Boolean = True)
    Check = IIf(Value, "a", "")
    End Sub

Sub SetValueUp(Check As Label, Optional ByVal Value As Boolean = True)
    Check = IIf(Value, "5", "6")
    End Sub

Sub SetValueLeft(Check As Label, Optional ByVal Value As Boolean = True)
    Check = IIf(Value, "3", "4")
    End Sub

Sub ToggleValue(Check As Label)
    SetValue Check, Not Value(Check)
    End Sub

Sub CheckToggle(M As Menu)
    M.Checked = Not M.Checked
    End Sub
    
Sub Check(M As Menu, Optional ByVal Checked As Boolean = False)
    If M.Checked <> Checked Then M.Checked = Checked
    End Sub
    
Function StringZero(Text As String) As String
    Dim N As Long
    N = InStr(Text, vbNullChar): If N > 0 Then StringZero = Left(Text, N - 1)
    End Function

Function FontParams(Optional X As Object) As String
    If X Is Nothing Then Set X = Setup
    With X.Font: FontParams = .Name & "," & Round(.Size) & "," & -.Bold & "," & -.Italic & _
        ",&H" & Hex(X.ForeColor) & "&": End With
    End Function

Sub MoveWin(ByVal hWnd As Long, Optional ByVal X As Long, Optional ByVal Y As Long, _
        Optional ByVal Relative As Boolean)
    Dim Re As Rect
    If Relative Then GetWindowRect hWnd, Re: X = Re.Left + X: Y = Re.Top + Y
    SetWindowPos hWnd, , X, Y, , , &H15& '1=NoSize 2=NoMove 4=NoZOrd &H10=NoAct
    End Sub

Sub ResizeWin(ByVal hWnd As Long, Optional ByVal W As Long, Optional ByVal H As Long, _
        Optional ByVal Relative As Boolean)
    Dim Re As Rect
    If Relative Then GetWindowRect hWnd, Re: W = Re.Right - Re.Left + W: H = Re.Bottom - Re.Top + H
    SetWindowPos hWnd, , , , W, H, &H16& '1=NoSize 2=NoMove 4=NoZOrd &H10=NoAct
    End Sub

Sub SnapForm(F As Form, Optional ByVal Border As Long = 10, _
        Optional ByVal Left As Long = -1, Optional ByVal Top As Long = -1, _
        Optional ByVal Width As Long = -1, Optional ByVal Height As Long = -1)
    Dim BorderLeft As Long, BorderTop As Long, BorderRight As Long, BorderBottom As Long
    With F
    If .WindowState = vbMinimized Then Exit Sub
    
    BorderLeft = (FullLeft + Border) * TwipX
    BorderRight = BorderLeft + (FullWidth - Border * 2) * TwipX
    BorderTop = (FullTop + Border) * TwipY
    BorderBottom = BorderTop + (FullHeight - Border * 2) * TwipY
    
    If Left < 0 Then Left = .Left ' Else Left = Left * TwipX
    If Top < 0 Then Top = .Top ' Else Top = Top * TwipY
    If Width < 0 Then Width = .Width ' Else Width = Width * TwipX
    If Height < 0 Then Height = .Height ' Else Height = Height * TwipY
    
    If Width > BorderRight - BorderLeft Then Width = BorderRight - BorderLeft
    If Left > BorderRight - Width Then Left = BorderRight - Width
    If Left < BorderLeft Then Left = BorderLeft
    
    If Height > BorderBottom - BorderTop Then Height = BorderBottom - BorderTop
    If Top > BorderBottom - Height Then Top = BorderBottom - Height
    If Top < BorderTop Then Top = BorderTop
    
    If .Left <> Left Or .Top <> Top Or .Width <> Width Or .Height <> Height Then _
        .Move Left, Top, Width, Height
    End With
    End Sub

Function GetDisplayName(Optional ByVal N As Long = -1) As String
    Dim Di As DisplayDevice
    Di.Size = Len(Di)
    If N < 0 Then
        N = 0: Do While EnumDisplayDevices(vbNullString, N, Di)
            If (Di.StateFlags And 1) And (Di.StateFlags And 4) <> 0 And _
                (Di.StateFlags And 8) = 0 Then GoTo Nxt
            N = N + 1
            Loop
    ElseIf EnumDisplayDevices(vbNullString, N, Di) Then
        GoTo Nxt
        End If
    Exit Function
Nxt:
    GetDisplayName = Trim(StringZero(Di.DeviceString))
    End Function

Sub UpdateScreenSize()
    Dim C As Long, Sc As Rect, Mi As MonitorInfo, Dm As DevMode, Di As DisplayDevice
    
    TwipX = Screen.TwipsPerPixelX: TwipY = Screen.TwipsPerPixelY
    'SystemParametersInfo &H30&, 0, Sc, 0 'SPI_GETWORKAREA=&H30
    GetWindowRect GetDesktopWindow, Sc
    PrimaryWidth = Sc.Right: PrimaryHeight = Sc.Bottom
    
    Mi.Size = Len(Mi)
    If GetMonitorInfo(MonitorFromWindow(Main.hWnd, 2), Mi) Then 'MONITOR_DEFAULTTONEAREST=2
        IsScreenPrimary = Mi.Flags And 1 'MONITORINFOF_PRIMARY
        With Mi.Monitor: ScreenLeft = .Left: ScreenTop = .Top: ScreenRight = .Right: ScreenBottom = .Bottom: End With
        'With Mi.Work: ScreenLeft = .Left: ScreenTop = .Top: ScreenRight = .Right: ScreenBottom = .Bottom: End With
    Else
        ScreenLeft = 0: ScreenTop = 0: ScreenRight = PrimaryWidth: ScreenBottom = PrimaryHeight
        End If
    ScreenWidth = ScreenRight - ScreenLeft: ScreenHeight = ScreenBottom - ScreenTop
    
    'ScreenWidth = GetSystemMetrics(0): ScreenHeight = GetSystemMetrics(1) 'SM_CXSCREEN=0 SM_CYSCREEN=1
    FullLeft = GetSystemMetrics(76): FullWidth = GetSystemMetrics(78) 'SM_XVIRTUALSCREEN=76 SM_CXVIRTUALSCREEN=78
    FullTop = GetSystemMetrics(77): FullHeight = FullTop + GetSystemMetrics(79) 'SM_YVIRTUALSCREEN=77 SM_CYVIRTUALSCREEN=79
    FullRight = FullLeft + FullWidth: FullBottom = FullTop + FullHeight
    IsMultiScreen = ScreenWidth <> FullWidth Or ScreenHeight <> FullHeight
    'FullLeft = 0: FullTop = 0: FullRight = 0: FullBottom = 0
    'IsMultiScreen = False: Dm.Size = Len(Dm): Di.Size = Len(Di): C = 0
    'Do While EnumDisplayDevices(vbNullString, C, Di)
    '    If (Di.StateFlags And 8) = 0 Then 'And (Di.StateFlags And 1) <> 0 Then 'DISPLAY_DEVICE_MIRRORING_DRIVER=8 DISPLAY_DEVICE_ATTACHED_TO_DESKTOP=1
    '        If EnumDisplaySettings(Di.DeviceName, -1, Dm) Then 'ENUM_CURRENT_SETTINGS=-1
    '            IsMultiScreen = True
    '            If FullLeft > Dm.PositionX Then FullLeft = Dm.PositionX
    '            If FullRight < Dm.PositionX + Dm.PelsWidth Then FullRight = Dm.PositionX + Dm.PelsWidth
    '            If FullTop > Dm.PositionY Then FullTop = Dm.PositionY
    '            If FullBottom < Dm.PositionY + Dm.PelsHeight Then FullBottom = Dm.PositionY + Dm.PelsHeight
    '            End If
    '        End If
    '    C = C + 1
    '    Loop
    'FullWidth = FullRight - FullLeft: FullHeight = FullBottom - FullTop
    End Sub

Function FormSize(F As Form) As String
    Dim Wp As WindowPlacement
    With Wp: .Length = Len(Wp): GetWindowPlacement F.hWnd, Wp
    FormSize = .Left & "," & .Top & "," & .Right - .Left & "," & .Bottom - .Top: End With
    End Function

Function FormPos(F As Form) As String
    Dim Wp As WindowPlacement
    With Wp: .Length = Len(Wp): GetWindowPlacement F.hWnd, Wp: FormPos = .Left & "," & .Top: End With
    End Function

Sub RestoreForm(F As Form)
    ShowWindow F.hWnd, 9: F.Refresh 'SW_RESTORE=9
    End Sub

Sub ResizeForm(F As Form, S As String)
    If S <> "" Then F.Move NumN(S) * TwipX, NumN(S) * TwipY, NumN(S) * TwipX, NumN(S) * TwipY
    End Sub

Sub MoveForm(F As Form, S As String)
    If S <> "" Then F.Move NumN(S) * TwipX, NumN(S) * TwipY
    End Sub

Sub RemoveBorder(ByVal hWnd As Long)
    SetWindowLong hWnd, -20, GetWindowLong(hWnd, -20) And Not &H200& 'GWL_EXSTYLE=-20 WS_EX_CLIENTEDGE=&H200
    End Sub

Sub RemoveListBorder(L As ListBox)
    RemoveBorder L.hWnd
    L.Width = L.Width + 1: L.Width = L.Width - 1 'Force repaint border
    End Sub

Sub RemoveComboBorder(L As ComboBox)
    Dim hWnd As Long
    hWnd = GetWindow(L.hWnd, 5) 'GW_CHILD=5
    Do While hWnd: RemoveBorder hWnd: hWnd = GetWindow(hWnd, 2): Loop 'GW_HWNDNEXT=2
    L.Width = L.Width + 1: L.Width = L.Width - 1 'Force repaint border
    End Sub

Function BorderMask() As Long
    BorderMask = IfInt(InStr(DisplayName, "ATI") > 0 Or InStr(DisplayName, "AMD") > 0, &H440000, &HC40000)
    End Function

Sub Border(F As Form, Optional ByVal Redraw As Boolean = True, Optional ByVal Mode As Long = 1, _
        Optional ByVal Fixed As Boolean)
    Dim C As Long
    With F
    C = GetWindowLong(.hWnd, -16)
    If Mode = 1 Then Mode = C And &H400000 Else Mode = Mode = 0
    SetWindowLong .hWnd, -16, IfInt(Mode, _
        C And Not BorderMask, C Or IfInt(Fixed, &HC00000, &HC40000))
    If Redraw Then SetWindowPos .hWnd, , , , , , &H37&
    End With
    End Sub

Sub Title(F As Form, Optional ByVal IsTitle As Long = 1, Optional ByVal Redraw As Boolean = True)
    Dim C As Long
    With F
    C = GetWindowLong(.hWnd, -16)
    SetWindowLong .hWnd, -16, _
        Switch(IsTitle = -1, C Or &H800000, IsTitle = 0, C And Not &H800000, True, C Xor &H800000)
    If Redraw Then SetWindowPos .hWnd, , , , , , &H37&
    End With
    End Sub

Sub FullScreenPanel(F As Form)
    Border F, , F.WindowState = vbMaximized
    F.WindowState = IfInt(F.WindowState <> vbMaximized, vbMaximized, vbNormal)
    End Sub

Sub FullScreen(F As Form, Optional ByVal Redraw As Boolean = True)
    Dim C As Long: Static Last As Integer
    With F
    If .WindowState = vbMinimized Then Exit Sub
    C = GetWindowLong(.hWnd, -16)
    If C And &H400000 Then 'GWL_STYLE=-16 GWL_EXSTYLE=-20 WS_THICKFRAME=&H40000 WS_DLGFRAME=&H400000 WS_BORDER=&H800000
        SetWindowLong .hWnd, -16, C And Not BorderMask
        Last = .WindowState: If .WindowState <> vbMaximized Then .WindowState = vbMaximized
    Else
        SetWindowLong .hWnd, -16, C Or &HC40000
        If .WindowState <> Last Then .WindowState = Last
        End If
    If Redraw Then SetWindowPos .hWnd, , , , , , &H37&
    End With
    End Sub

Function IsFullScreen(F As Form) As Boolean
    If Not F Is Nothing Then If F.WindowState = vbMaximized And _
        (GetWindowLong(F.hWnd, -16) And &H400000) = 0 Then IsFullScreen = True
    End Function

Sub FullSize(F As Form, _
        Optional ByVal UseFullWidth As Boolean = True, Optional ByVal UseFullHeight As Boolean = True)
    Dim X As Long, Y As Long, W As Long, H As Long
    With F
    If .WindowState <> vbNormal Then .WindowState = vbNormal
    SetWindowLong .hWnd, -16, GetWindowLong(.hWnd, -16) And Not BorderMask
    SetWindowPos .hWnd, , _
        IfInt(UseFullWidth, FullLeft, 0), IfInt(UseFullHeight, FullTop, 0), _
        IfInt(UseFullWidth, FullWidth, PrimaryWidth), IfInt(UseFullHeight, FullHeight, PrimaryHeight), &H34&
    End With
    End Sub

Sub Maximize(F As Form)
    If F.WindowState <> vbMaximized Then F.WindowState = vbMaximized Else F.WindowState = vbNormal
    End Sub

Sub SetAlpha(ByVal hWnd As Long, Optional Alpha As Double = 1, Optional Activate As Long = 1)
    Dim N As Long
    If Activate = False Then SetWindowLong _
        hWnd, -20, GetWindowLong(hWnd, -20) And Not &H80020: Exit Sub 'GWL_EXSTYLE=-20 WS_EX_LAYERED=&H80000 WS_EX_TRANSPARENT=&H20
    If Alpha <= 0 Then N = 0 Else If Alpha >= 1 Then N = 255 Else N = Round(Alpha * 255)
    If Activate = True Then SetWindowLong _
        hWnd, -20, GetWindowLong(hWnd, -20) Or &H80020 'GWL_EXSTYLE=-20 WS_EX_LAYERED=&H80000 WS_EX_TRANSPARENT=&H20
    SetLayeredWindowAttributes hWnd, 0, N, 2 'LWA_COLORKEY=1 LWA_ALPHA=2
    RedrawWindow hWnd, , , &H485& 'RDW_INVALIDATE=1 RDW_ERASE=4 RDW_ALLCHILDREN=&H80 RDW_FRAME=&H400
    End Sub

Function PixelColor() As Long
    Dim D As Long, H As Long, P As PointApi
    H = GetDesktopWindow: D = GetWindowDC(H): GetCursorPos P
    PixelColor = GetPixel(D, P.X, P.Y)
    ReleaseDC H, D
    End Function

Sub PrintPic(Pic As PictureBox, Text As String, _
        Optional ByVal X As Single = -1, Optional ByVal Y As Single = -1, _
        Optional ByVal Position As Long = -1, _
        Optional ByVal Border As Long = -1, Optional ByVal TextColor As Long = -1, _
        Optional ByVal BorderColor As Long = -1, Optional ByVal BackColor As Long = -1, _
        Optional Icon As Image)
    'Pos: -1=Current 0=Center 1=LeftUp 2=Up 3=RightUp 4=Right 5=RightDown 6=Down 7=LeftDown 8=Left
    Dim W As Single, H As Single, WW As Single, HH As Single, _
        L As Long, F As Long, Q As Integer, I As Single
    W = Pic.TextWidth(Text) + 1: H = Pic.TextHeight("") + 1
    If Not Icon Is Nothing Then I = Icon.Width + 4: W = W + I
    WW = Pic.ScaleWidth: HH = Pic.ScaleHeight
    Select Case Position
    Case -1
        If X < 0 Then X = Pic.CurrentX
        If Y < 0 Then Y = Pic.CurrentY
        If X < Border Then X = Border _
        Else If X > Pic.ScaleWidth - W - Border Then X = Pic.ScaleWidth - W - Border
        If Y < Border Then Y = Border _
        Else If Y > Pic.ScaleHeight - H - Border Then Y = Pic.ScaleHeight - H - Border
        Pic.CurrentX = X: Pic.CurrentY = Y
    Case 0: X = (WW - W) / 2: Y = (HH - H) / 2
    Case 1: X = Border: Y = Border
    Case 2: X = (WW - W) / 2: Y = Border
    Case 3: X = WW - W - Border: Y = Border
    Case 4: X = WW - W - Border: Y = (HH - H) / 2
    Case 5: X = WW - W - Border: Y = HH - H - Border
    Case 6: X = (WW - W) / 2: Y = HH - H - Border
    Case 7: X = Border: Y = HH - H - Border
    Case 8: X = Border: Y = (HH - H) / 2
    End Select
    If Border >= 0 Then
        If BackColor <> -1 Then F = Pic.FillColor: Pic.FillColor = BackColor: _
            Q = Pic.FillStyle: Pic.FillStyle = vbFSSolid
        Pic.Line (X - Border, Y - Border)-Step(W + Border * 2 - 1, H + Border * 2 - 1), _
            IfInt(BorderColor <> -1, BorderColor, Pic.ForeColor), B
        If BackColor <> -1 Then Pic.FillColor = F: Pic.FillStyle = Q
        End If
    If TextColor <> -1 Then L = Pic.ForeColor: Pic.ForeColor = TextColor
    Pic.CurrentX = X + I: Pic.CurrentY = Y: Pic.Print Text;
    If TextColor <> -1 Then Pic.ForeColor = L
    If I Then Pic.PaintPicture Icon.Picture, X + 2, Y + (H - Icon.Height) / 2
    End Sub

'Sub ToolBox(ByVal hWnd As Long, Optional ByVal Mode As Long = 1, Optional ByVal TopMost As Boolean)
'    Dim C As Long
'    C = GetWindowLong(hWnd, -20): SetWindowLong hWnd, -20, Switch( _
'        Mode = -1, C Or &H80, Mode = 0, C And Not &H80, True, C Xor &H80) 'GWL_STYLE=-16 GWL_EXSTYLE=-20 WS_EX_TOOLWINDOW=&H80
'    SetWindowPos hWnd, IfInt(TopMost, -1, -2), , , , , &H33&: RedrawWindow hWnd, , , &H401&
'    End Sub

Sub LineEx(Pic As PictureBox, _
        Optional ByVal X As Double, Optional ByVal Y As Double, _
        Optional ByVal X1 As Double = Undefined, Optional ByVal Y1 As Double, _
        Optional ByVal X2 As Double = Undefined, Optional ByVal Y2 As Double, _
        Optional ByVal C As Long, Optional ByVal B As Long = -1, _
        Optional ByVal A As Double = Undefined, _
        Optional ByVal SinA As Double, Optional ByVal CosA As Double = 1)
    Dim J As Long, K As Long, L As Long, X0 As Double, Y0 As Double, Pt(4 - 1) As PointApi
    If A <> Undefined Then SinA = Sin(A): CosA = Cos(A)
    If B <> -1 Then
        L = Pic.ForeColor: J = Pic.FillColor: K = Pic.FillStyle
        Pic.ForeColor = C: Pic.FillColor = B: Pic.FillStyle = vbFSSolid
        X0 = X1: Y0 = Y1: RotateEx SinA, CosA, X0, Y0: Pt(0).X = RoundInt(X + X0): Pt(0).Y = RoundInt(Y - Y0)
        X0 = X1: Y0 = Y2: RotateEx SinA, CosA, X0, Y0: Pt(1).X = RoundInt(X + X0): Pt(1).Y = RoundInt(Y - Y0)
        X0 = X2: Y0 = Y2: RotateEx SinA, CosA, X0, Y0: Pt(2).X = RoundInt(X + X0): Pt(2).Y = RoundInt(Y - Y0)
        X0 = X2: Y0 = Y1: RotateEx SinA, CosA, X0, Y0: Pt(3).X = RoundInt(X + X0): Pt(3).Y = RoundInt(Y - Y0)
        Polygon Pic.hDC, Pt(0), 4
        Pic.ForeColor = L: Pic.FillColor = J: Pic.FillStyle = K
        'Pic.Line (X + X1, Y - Y1)-(X + X2, Y - Y2), C ', BF
    Else
        If X1 <> Undefined Then A = X1 * CosA - Y1 * SinA: Y1 = X1 * SinA + Y1 * CosA: X1 = A
        If X2 <> Undefined Then A = X2 * CosA - Y2 * SinA: Y2 = X2 * SinA + Y2 * CosA: X2 = A
        If X2 = Undefined Then Pic.PSet (X + X1, Y - Y1), C _
        Else If X1 = Undefined Then Pic.Line -(X + X2, Y - Y2), C _
        Else Pic.Line (X + X1, Y - Y1)-(X + X2, Y - Y2), C
        End If
    End Sub

Sub CircleEx(Pic As PictureBox, _
        Optional ByVal X As Single, Optional ByVal Y As Single, Optional R As Double, _
        Optional ByVal C As Long, Optional ByVal B As Long = -1, _
        Optional A1 As Double, Optional A2 As Double)
    Dim J As Long, K As Long
    A1 = A90 - A1: If A1 < 0 Then A1 = A1 + A360 Else If A1 >= A360 Then A1 = -A360
    A2 = A90 - A2: If A2 < 0 Then A2 = A2 + A360 Else If A2 >= A360 Then A2 = -A360
    J = Pic.FillColor: K = Pic.FillStyle: If B = -1 Then Pic.FillStyle = vbFSTransparent _
        Else Pic.FillStyle = vbFSSolid: Pic.FillColor = B
    Pic.Circle (X, Y), R, C, A2, A1
    Pic.FillColor = J: Pic.FillStyle = K
    End Sub

Function FreeMemory() As Double
    Dim Ms As MemoryStatus
    GlobalMemoryStatus Ms: FreeMemory = Ms.AvailPhys
    If FreeMemory < 0 Then FreeMemory = FreeMemory + 4294967296#
    End Function

Function AddData(List As Collection, Key As String, Optional Data As Long) As Boolean
    On Error Resume Next: List.Add Data, Key: AddData = Err: On Error GoTo 0
    End Function

Function DelData(List As Collection, Key As String) As Boolean
    On Error Resume Next: List.Remove Key: DelData = Err: On Error GoTo 0
    End Function

Function IsData(List As Collection, Key As String) As Long
    On Error Resume Next: IsData = List(Key): On Error GoTo 0
    End Function

Sub ListAdd(Name As String, Optional L As Collection, Optional Text As String)
    Dim C As Long, N As Long, S As String
    On Error Resume Next
    If L Is Nothing Then Set L = List
    If Text = "" Then Text = Name
    With L
    For N = 1 To .Count
        If StrComp(Text, .Item(N), vbTextCompare) <= 0 Then
            .Add Text, Name, N
            C = 2: Do While Err
                S = ":" & C: Err = 0: .Add Text & S, Name & S, , N: N = N + 1: C = C + 1
                Loop
            Exit Sub
            End If
        Next
    .Add Text, Name
    End With
    On Error GoTo 0
    End Sub

Function ListDelete(Name As String, Optional L As Collection) As Boolean
    If L Is Nothing Then Set L = List
    On Error Resume Next: Err = 0: L.Remove Name: ListDelete = Err = 0: On Error GoTo 0
    End Function

Function ListText(Optional L As Collection) As String
    Dim N As Long
    On Error Resume Next
    If L Is Nothing Then Set L = List
    For N = 1 To L.Count: StrAdd L(N): Next: ListText = StrAdd
    On Error GoTo 0
    End Function

Sub StrReplace(Optional OldChar As Byte = 44, Optional NewChar As Byte = 46) ',44 .46 :58 ;59
    Dim N As Long
    For N = 0 To StrLen - 1
        If StrBuffer(N) = OldChar Then StrBuffer(N) = NewChar
        Next
    End Sub

Function SplitEx(Optional ByVal N As Long, Optional List As String, _
        Optional Sep As String = "|", Optional Default As String) As String
    Dim T() As String
    T = Split(List, Sep): If N >= LBound(T) And N <= UBound(T) Then SplitEx = T(N) Else SplitEx = Default
    End Function

Function StrAdd(Optional Text As String) As String
    'Static StrBuffer() As Byte, StrLen As Long, StrSize As Long
    Dim L As Long, C As Long
    L = Len(Text)
    If L Then
        C = (StrLen + L + 4095) And &HFFFFF000
        If StrSize < C Then StrSize = C: ReDim Preserve StrBuffer(StrSize - 1)
        RtlMoveMemory StrBuffer(StrLen), ByVal Text, L: StrLen = StrLen + L
    Else
        If StrLen = 0 Then Exit Function
        StrAdd = Space(StrLen): RtlMoveMemory ByVal StrAdd, StrBuffer(0), StrLen
        'StrLen = 0: If StrSize <> 4096 Then StrSize = 4096: ReDim StrBuffer(StrSize - 1)
        StrClear
        End If
    End Function

Sub StrClear()
    StrLen = 0: If StrSize <> 4096 Then StrSize = 4096: ReDim StrBuffer(StrSize - 1)
    End Sub

Function StrAddEx(Optional Text As String) As String
    Static StrBuffer() As Byte, StrLen As Long, StrSize As Long
    Dim L As Long, C As Long
    L = Len(Text)
    If L Then
        C = (StrLen + L + 4095) And &HFFFFF000
        If StrSize < C Then StrSize = C: ReDim Preserve StrBuffer(StrSize - 1)
        RtlMoveMemory StrBuffer(StrLen), ByVal Text, L: StrLen = StrLen + L
    Else
        If StrLen = 0 Then Exit Function
        StrAddEx = Space(StrLen): RtlMoveMemory ByVal StrAddEx, StrBuffer(0), StrLen
        StrLen = 0: If StrSize <> 4096 Then StrSize = 4096: ReDim StrBuffer(StrSize - 1)
        End If
    End Function

Function StrLine(Optional ByVal Text As String) As String
    Static StrBuffer() As Byte, StrLen As Long, StrSize As Long
    Dim L As Long, C As Long
    Text = Text & vbCrLf
    L = Len(Text)
    If L Then
        C = (StrLen + L + 4095) And &HFFFFF000
        If StrSize < C Then StrSize = C: ReDim Preserve StrBuffer(StrSize - 1)
        RtlMoveMemory StrBuffer(StrLen), ByVal Text, L: StrLen = StrLen + L
    Else
        If StrLen = 0 Then Exit Function
        StrLine = Space(StrLen): RtlMoveMemory ByVal StrLine, StrBuffer(0), StrLen
        StrLen = 0: If StrSize <> 4096 Then StrSize = 4096: ReDim StrBuffer(StrSize - 1)
        End If
    End Function

Function GetStringFromPtr(ByVal Ptr As Long) As String
    If Ptr = 0 Then Exit Function
    GetStringFromPtr = String(4096, vbNullChar)
    lstrcpyn ByVal GetStringFromPtr, ByVal Ptr, Len(GetStringFromPtr) - 1
    GetStringFromPtr = Left(GetStringFromPtr, InStr(GetStringFromPtr, vbNullChar) - 1)
    End Function

Sub GetTextNumber(Text As String, Name As String, N As Long)
    N = InStrRev(Text, ":"): If N <= 0 Then Name = Text: N = 1 _
    Else Name = Left(Text, N - 1): N = Val(Mid(Text, N + 1))
    End Sub

Function GetLine(Text As String, Optional Pos As Long = 1) As String
    'Extracts one line terminated with 'vbCrLf' Chr(13)+Chr(10) and updates the position
    Dim A As Long
    If Pos > 0 Then A = InStr(Pos, Text, vbCrLf) Else Exit Function
    If A Then GetLine = Mid(Text, Pos, A - Pos): Pos = A + 2 Else GetLine = Mid(Text, Pos): Pos = 0
    End Function

Function GetLineLf(Text As String, Optional Pos As Long = 1) As String
    'Extracts one line terminated with 'vbLf' Chr(10) and updates the position
    Dim A As Long
    If Pos > 0 Then A = InStr(Pos, Text, vbLf) Else Exit Function
    If A Then GetLineLf = Mid(Text, Pos, A - Pos): Pos = A + 1 Else GetLineLf = Mid(Text, Pos): Pos = 0
    End Function

Function GetLineEx(Text As String) As String
    Dim A As Long
    A = InStr(Text, vbCrLf): If A Then GetLineEx = Left(Text, A - 1): Text = Mid(Text, A + 2) _
        Else GetLineEx = Text: Text = ""
    End Function

Function GetText() As String
    Dim A As Long
    If LinkPos > 0 Then A = InStr(LinkPos, LinkIn, ",") Else Exit Function
    If A = 0 Then A = InStr(LinkPos, LinkIn, ";"): If A = 0 Then A = InStr(LinkPos, LinkIn, vbCr)
    If A Then GetText = Mid(LinkIn, LinkPos, A - LinkPos): LinkPos = A + 1 _
    Else GetText = Mid(LinkIn, LinkPos): LinkPos = 0
    End Function

Function GetData() As Double
    Dim A As Long
    If LinkPos > 0 Then A = InStr(LinkPos, LinkIn, ",") Else Exit Function
    If A = 0 Then A = InStr(LinkPos, LinkIn, ";"): If A = 0 Then A = InStr(LinkPos, LinkIn, vbCr)
    If A Then GetData = Val(Mid(LinkIn, LinkPos, A - LinkPos)): LinkPos = A + 1 _
    Else GetData = Val(Mid(LinkIn, LinkPos)): LinkPos = 0
    End Function

Function GetCmd(Text As String, Optional Pos As Long = 1, Optional UseCr As Boolean) As String
    Dim A As Long, B As Long, L As Long
    If Pos Then L = Len(Text) Else UseCr = False: Exit Function
    Do: If Pos > L Then GoTo Ext
        Select Case Asc(Mid(Text, Pos, 1))
        Case 44: Pos = Pos + 1: GoTo Ext: Case Is > 32: A = Pos: Exit Do
        End Select: Pos = Pos + 1: Loop
    Do: If Pos > L Then GoTo Ext
        Select Case Asc(Mid(Text, Pos, 1))
        Case 44: B = Pos: Pos = Pos + 1: GoTo Ext: Case Is <= 32: B = Pos: Exit Do
        End Select: Pos = Pos + 1: Loop
    Do: If Pos > L Then GoTo Ext
        Select Case Asc(Mid(Text, Pos, 1))
        Case 44: Pos = Pos + 1: GoTo Ext: Case Is > 32: UseCr = False: Exit Do
        End Select: Pos = Pos + 1: Loop
Ext:
    If A Then If B Then GetCmd = Mid(Text, A, B - A) Else GetCmd = Mid(Text, A)
    If Pos > L Then Pos = 0: UseCr = False
    End Function

Function InStrCmd(Ini As String, Name As String) As Long
    InStrCmd = InStr(Ini, vbCrLf & Name & "="): If InStrCmd = 0 Then _
        If InStr(Ini, Name & "=") = 1 Then InStrCmd = -1
    End Function

Function GetBool(Name As String, Optional ByVal RaiseError As Boolean = True) As Boolean
    Dim A As Long, B As Long, V As String
    If RaiseError Then Err = 0
    A = InStrCmd(Ini, Name): If A Then A = A + 2 + Len(Name) + 1 _
        Else If RaiseError Then Err.Raise 512, , "Item not found": Exit Function _
        Else Exit Function
    B = InStr(A, Ini, vbCrLf): If B Then V = LCase(Mid(Ini, A, B - A)) Else V = LCase(Mid(Ini, A))
    If V = "on" Or V = "true" Or V = "yes" Then GetBool = True _
    Else If V = "off" Or V = "false" Or V = "no" Then GetBool = False _
    Else GetBool = Val(V)
    End Function

Function GetInt(Name As String, Optional ByVal RaiseError As Boolean = True) As Long
    Dim A As Long, B As Long
    If RaiseError Then Err = 0
    A = InStrCmd(Ini, Name): If A Then A = A + 2 + Len(Name) + 1 _
        Else If RaiseError Then Err.Raise 512, , "Item not found": Exit Function _
        Else Exit Function
    B = InStr(A, Ini, vbCrLf): If B Then GetInt = Val(Mid(Ini, A, B - A)) _
        Else GetInt = Val(Mid(Ini, A))
    End Function

Function GetNum(Name As String, Optional ByVal RaiseError As Boolean = True) As Double
    Dim A As Long, B As Long
    If RaiseError Then Err = 0
    A = InStrCmd(Ini, Name): If A Then A = A + 2 + Len(Name) + 1 _
        Else If RaiseError Then Err.Raise 512, , "Item not found": Exit Function _
        Else Exit Function
    B = InStr(A, Ini, vbCrLf): If B Then GetNum = Val(Mid(Ini, A, B - A)) _
        Else GetNum = Val(Mid(Ini, A))
    End Function

Function GetStr(Name As String, Optional ByVal RaiseError As Boolean = True, _
        Optional ByVal FullText As Boolean) As String
    Dim A As Long, B As Long
    If RaiseError Then Err = 0
    A = InStrCmd(Ini, Name): If A Then A = A + 2 + Len(Name) + 1 _
        Else If RaiseError Then Err.Raise 512, , "Item not found": Exit Function _
        Else Exit Function
    If FullText Then B = 0 Else B = InStr(A, Ini, vbCrLf)
    If B Then GetStr = Mid(Ini, A, B - A) Else GetStr = Mid(Ini, A)
    End Function

Function NumCmd(Ini As String, Name As String, Optional ByVal RaiseError As Boolean = True) As Double
    Dim A As Long, B As Long
    If RaiseError Then Err = 0
    A = InStrCmd(Ini, Name): If A Then A = A + 2 + Len(Name) + 1 _
        Else If RaiseError Then Err.Raise 512, , "Item not found": Exit Function _
        Else Exit Function
    B = InStr(A, Ini, vbCrLf): If B Then NumCmd = Val(Mid(Ini, A, B - A)) _
        Else NumCmd = Val(Mid(Ini, A))
    End Function

Function StrCmd(Ini As String, Name As String, Optional ByVal RaiseError As Boolean = True, _
        Optional ByVal FullText As Boolean) As String
    Dim A As Long, B As Long
    If RaiseError Then Err = 0
    A = InStrCmd(Ini, Name): If A Then A = A + 2 + Len(Name) + 1 _
        Else If RaiseError Then Err.Raise 512, , "Item not found": Exit Function _
        Else Exit Function
    If FullText Then B = 0 Else B = InStr(A, Ini, vbCrLf)
    If B Then StrCmd = Mid(Ini, A, B - A) Else StrCmd = Mid(Ini, A)
    End Function

Function FileNumCmd(File As String, Name As String) As Double
    Dim Ini As String
    Ini = LoadFile(File): If Ini <> "" Then FileNumCmd = NumCmd(Ini, Name, False)
    End Function

Function FileStrCmd(File As String, Name As String, Optional ByVal FullText As Boolean) As String
    Dim Ini As String
    Ini = LoadFile(File): If Ini <> "" Then FileStrCmd = StrCmd(Ini, Name, False, FullText)
    End Function

Function InfoName(Name As String) As String
    Dim A As Long, B As Long
    A = InStr(InfoNames, vbCrLf & Name & "="): If A = 0 Then Exit Function
    A = A + Len(Name) + 3: B = InStr(A, InfoNames, vbCrLf)
    If B Then InfoName = Mid(InfoNames, A, B - A) Else InfoName = Mid(InfoNames, A)
    End Function

Function InString(Text As String, Optional Left As String, Optional Right As String) As String
    Dim A As Long, B As Long
    If Left <> "" Then A = InStr(Text, Left): If A Then A = A + Len(Left)
    If A <= 0 Then A = 1
    If Right <> "" Then B = InStr(Text, Right)
    If B <= 0 Then B = Len(Text) + 1
    If B > A Then InString = Mid(Text, A, B - A)
    End Function

Function LeftToString(Text As String, Optional Cmd As String = ".") As String
    Dim A As Long
    A = InStr(Text, Cmd): If A Then LeftToString = Left(Text, A - 1) Else LeftToString = Text
    End Function

Function RightToString(Text As String, Optional Cmd As String = ".") As String
    Dim A As Long
    A = InStrRev(Text, Cmd): If A Then RightToString = Mid(Text, A + Len(Cmd)) Else RightToString = Text
    End Function

Function LeftText(Text As String) As String ' "Text1234.5" -> "Text"
    Dim N As Long
    For N = 1 To Len(Text)
        If Mid(Text, N, 1) >= "0" And Mid(Text, N, 1) <= "9" Then _
            LeftText = Left(Text, N - 1): Exit Function
        Next
    LeftText = Text
    End Function

Function RightText(Text As String) As String ' "1234.5Text" -> "Text"
    Dim N As Long
    For N = Len(Text) To 1 Step -1
        If Mid(Text, N, 1) >= "0" And Mid(Text, N, 1) <= "9" Then _
            RightText = Mid(Text, N + 1): Exit Function
        Next
    RightText = Text
    End Function

Function LeftNum(Text As String) As Double ' "1234.5Text" -> 1234.5
    Dim N As Long
    For N = 1 To Len(Text)
        If Mid(Text, N, 1) < "0" Or Mid(Text, N, 1) > "9" Then If Mid(Text, N, 1) <> "." Then _
            LeftNum = Val(Left(Text, N - 1)): Exit Function
        Next
    LeftNum = Text
    End Function

Function RightNum(Text As String) As String ' "Text1234.5" -> 1234.5
    Dim N As Long
    For N = Len(Text) To 1 Step -1
        If Mid(Text, N, 1) < "0" Or Mid(Text, N, 1) > "9" Then If Mid(Text, N, 1) <> "." Then _
            RightNum = Mid(Text, N + 1): Exit Function
        Next
    RightNum = Text
    End Function

Function ToSSV(Text As String) As String
    ToSSV = Replace(Replace(Text, ",", ";"), ".", ",")
    End Function

Function ToCSV(Text As String) As String
    ToCSV = Replace(Replace(Text, ",", "."), ";", ",")
    End Function

Function NumH(Cmd As String, Optional Param As String, Optional Pos As Long) As Double 'Double ","
    Dim A As Long, C As Long, U As String
    A = InStr(Cmd, ","): If A Then Param = Trim(Left(Cmd, A - 1)): Cmd = Mid(Cmd, A + 1) _
        Else Param = Trim(Cmd): Cmd = ""
    NumH = Val(Param): If NumH <> 0 Then Pos = 1: Exit Function
    Pos = 0: A = Len(Param): If A <= 0 Then Exit Function
    For C = 1 To A: U = Mid(Param, C, 1)
        If U >= "0" And U <= "9" Then
            If Pos <= 0 Then Pos = C
            NumH = Val(Mid(Param, Pos)): Exit Function
        ElseIf U = "-" Or U = "+" Then
            Pos = C
        ElseIf U = "." Then
            If C = 1 Then Pos = C
        Else
            Pos = 0
            End If
        Next
    Pos = 0
    End Function

Sub CmdInt(Text As String, Cmd As String, Var As Long) 'Long "Cmd="
    Dim A As Long, B As Long
    A = InStr(Text, vbCrLf & Cmd & "="): If A Then A = A + 2 + Len(Cmd) + 1 Else Exit Sub
    B = InStr(A, Text, vbCrLf): If B Then Var = Int(Val(Mid(Text, A, B - A))) _
        Else Var = Int(Val(Mid(Text, A)))
    End Sub

Sub CmdDbl(Text As String, Cmd As String, Var As Double) 'Double "Cmd="
    Dim A As Long, B As Long
    A = InStr(Text, vbCrLf & Cmd & "="): If A Then A = A + 2 + Len(Cmd) + 1 Else Exit Sub
    B = InStr(A, Text, vbCrLf): If B Then Var = Val(Mid(Text, A, B - A)) _
        Else Var = Val(Mid(Text, A))
    End Sub

Sub CmdStr(Text As String, Cmd As String, Var As String, Optional ByVal FullText As Boolean) 'String "Cmd="
    Dim A As Long, B As Long
    A = InStr(Text, vbCrLf & Cmd & "="): If A Then A = A + 2 + Len(Cmd) + 1 Else Exit Sub
    If FullText Then B = 0 Else B = InStr(A, Text, vbCrLf)
    If B Then Var = Mid(Text, A, B - A) Else Var = Mid(Text, A)
    End Sub

Function ParamA(Cmd As String, Optional Sep As String = ";") As String 'String "Sep"
    Dim A As Long, C As Long
    C = Len(Sep): A = InStr(Cmd, Sep): If A Then ParamA = Left(Cmd, A - C): Cmd = Mid(Cmd, A + C) _
        Else ParamA = Cmd: Cmd = ""
    End Function

Function ParamM(Cmd As String, Optional ByVal N As Long = 1) As String 'String ","
    Dim A As Long, B As Long
    For N = 1 To N - 1
        A = InStr(A + 1, Cmd, ","): If A = 0 Then Exit Function
        Next
    B = InStr(A + 1, Cmd, ","): If B = 0 Then B = Len(Cmd) + 1
    ParamM = Mid(Cmd, A + 1, B - A - 1)
    End Function

Function IsParamE(Text As String, Cmd As String) As Boolean
    Dim A As Long
    A = InStr(Text, "="): If A Then IsParamE = Left(Left(Text, A - 1), Len(Cmd)) = Cmd: _
        Text = Mid(Text, A + 1) Else IsParamE = True
    End Function

Function ParamE(Cmd As String) As String 'String "="
    Dim A As Long
    A = InStr(Cmd, "="): If A Then ParamE = Left(Cmd, A - 1): Cmd = Mid(Cmd, A + 1)
    End Function

Function ParamXRev(Cmd As String) As String 'String " "
    Dim A As Long
    Cmd = RTrim(Cmd): A = InStrRev(Cmd, " "): If A Then ParamXRev = Mid(Cmd, A + 1): _
        Cmd = Left(Cmd, A - 1) Else ParamXRev = Cmd: Cmd = ""
    End Function

Function ParamX(Cmd As String) As String 'String " "
    Dim A As Long
    Cmd = LTrim(Cmd): A = InStr(Cmd, " "): If A Then ParamX = Left(Cmd, A - 1): _
        Cmd = Mid(Cmd, A + 1) Else ParamX = Cmd: Cmd = ""
    End Function

Function ParamN(Cmd As String) As Double 'Double " "
    Dim A As Long
    Cmd = LTrim(Cmd): A = InStr(Cmd, " "): If A Then ParamN = Val(Left(Cmd, A - 1)): _
        Cmd = Mid(Cmd, A + 1) Else ParamN = Val(Cmd): Cmd = ""
    End Function

Function Param(Cmd As String) As String  'String ","
    Dim A As Long
    A = InStr(Cmd, ","): If A Then Param = Left(Cmd, A - 1): Cmd = Mid(Cmd, A + 1) _
        Else Param = Cmd: Cmd = ""
    End Function

Function NumL(Cmd As String) As Boolean  'Boolean ","
    Dim A As Long, V As String
    A = InStr(Cmd, ","): If A Then V = LCase(Left(Cmd, A - 1)): Cmd = Mid(Cmd, A + 1) _
        Else V = LCase(Cmd): Cmd = ""
    If V = "on" Or V = "true" Or V = "yes" Then NumL = True _
    Else If V = "off" Or V = "false" Or V = "no" Then NumL = False _
    Else NumL = Val(V)
    End Function

Function NumI(Cmd As String) As Long     'Long ","
    Dim A As Long
    A = InStr(Cmd, ","): If A Then NumI = Val(Left(Cmd, A - 1)): Cmd = Mid(Cmd, A + 1) _
        Else NumI = Val(Cmd): Cmd = ""
    End Function

Function NumS(Cmd As String) As Single   'Single ","
    Dim A As Long
    A = InStr(Cmd, ","): If A Then NumS = Val(Left(Cmd, A - 1)): Cmd = Mid(Cmd, A + 1) _
        Else NumS = Val(Cmd): Cmd = ""
    End Function

Function NumN(Cmd As String) As Double   'Double ","
    Dim A As Long
    A = InStr(Cmd, ","): If A Then NumN = Val(Left(Cmd, A - 1)): Cmd = Mid(Cmd, A + 1) _
        Else NumN = Val(Cmd): Cmd = ""
    End Function

Function ParamB(Cmd As String) As String 'String "|"
    Dim A As Long
    A = InStr(Cmd, "|"): If A Then ParamB = Left(Cmd, A - 1): Cmd = Mid(Cmd, A + 1) _
        Else ParamB = Cmd: Cmd = ""
    End Function

Function NumB(Cmd As String) As Double   'Double "|"
    Dim A As Long
    A = InStr(Cmd, "|"): If A Then NumB = Val(Left(Cmd, A - 1)): Cmd = Mid(Cmd, A + 1) _
        Else NumB = Val(Cmd): Cmd = ""
    End Function

Function ParamF(Text As String, Optional Pos As Long = 1) As String 'String ":"
    Dim A As Long
    If Pos > 0 Then A = InStr(Pos, Text, ":") Else Exit Function
    If A Then ParamF = Mid(Text, Pos, A - Pos): Pos = A + 1 Else ParamF = Mid(Text, Pos): Pos = 0
    End Function

Function NumF(Text As String, Optional Pos As Long = 1) As Double 'Double ":"
    Dim A As Long
    If Pos > 0 Then A = InStr(Pos, Text, ":") Else Exit Function
    If A Then NumF = Val(Mid(Text, Pos, A - Pos)): Pos = A + 1 Else NumF = Val(Mid(Text, Pos)): Pos = 0
    End Function

Function ParamT(Cmd As String) As String 'String Tab
    Dim A As Long
    A = InStr(Cmd, vbTab): If A Then ParamT = Left(Cmd, A - 1): Cmd = Mid(Cmd, A + 1) _
        Else ParamT = Cmd: Cmd = ""
    End Function

Function ParamK(Text As String, Cmd As String, Optional Pos As Long = 1, _
        Optional ByVal ReturnAll As Boolean) As String 'String "<Cmd>...</Cmd>"
    Dim A As Long, B As Long, C As Long
    C = InStr(Cmd, " ") - 1: If C < 0 Then C = Len(Cmd)
    A = InStr(Pos, Text, "<" & Cmd & ">"): If A Then A = A + 2 + Len(Cmd): _
    B = InStr(A + 1, Text, "</" & Left(Cmd, C) & ">"): If B Then ParamK = Mid(Text, A, B - A): Pos = B
    If A = 0 Then Pos = 0: If ReturnAll Then ParamK = Text
    End Function

Function ParamQ(Text As String, Cmd As String) As String 'String "<Cmd>...</Cmd>"
    Dim A As Long, B As Long
    A = InStr(Text, "<" & Cmd & ">"): If A Then A = A + 2 + Len(Cmd) Else Exit Function
    B = InStr(A + 1, Text, "</" & Cmd & ">"): If B = 0 Then Exit Function
    ParamQ = Mid(Text, A, B - A)
    End Function

Function ParamR(Text As String, Optional Cmd As String, Optional Default As String, _
        Optional ByVal First As Long, Optional ByVal Last As Long) As String 'String "<Cmd>...</Cmd>"
    Dim N As Long
    First = InStr(First + 1, Text, "<" & Cmd & ">")
    If First Then First = First + 2 + Len(Cmd) Else Exit Function
    N = Last: Last = InStr(First + 1, Text, "</" & Cmd & ">")
    If Last > 0 And (N <= 0 Or Last <= N) Then ParamR = Mid(Text, First, Last - First) _
    Else If Default <> "" Then ParamR = Default
    End Function

Function ParamP(Text As String, Cmd As String) As String 'String "Cmd: ...CRLF"
    Dim A As Long, B As Long
    A = InStr(Text, Cmd & ": "): If A Then A = A + 2 + Len(Cmd) Else Exit Function
    B = InStr(A + 1, Text, vbCrLf): If B = 0 Then Exit Function
    ParamP = Mid(Text, A, B - A)
    'If Right(ParamP, 1) < " " Then ParamP = Left(ParamP, Len(ParamP) - 1)
    End Function

Function ParamU(Text As String, First As String, Last As String) As String 'String "First...Last"
    Dim A As Long, B As Long
    A = InStr(Text, First): If A Then A = A + Len(First) Else Exit Function
    B = InStr(A + 1, Text, Last): If B = 0 Then Exit Function
    ParamU = Mid(Text, A, B - A)
    End Function

Function ParamC(Text As String) As String 'String "}"
    Dim A As Long
    A = InStr(Text, "}"): If A Then ParamC = Left(Text, A): Text = Mid(Text, A + 1) _
        Else ParamC = Text: Text = ""
    End Function

Function ParamD(Text As String, Cmd As String) As String 'String ""Cmd":"...""
    Dim A As Long, B As Long
    A = InStr(Text, """" & Cmd & """:"""): If A Then A = A + 4 + Len(Cmd) _
        Else A = InStr(Text, """" & Cmd & """:"): If A Then A = A + 3 + Len(Cmd) _
        Else Exit Function
    B = InStr(A + 1, Text, """"): If B = 0 Then _
        B = InStr(A + 1, Text, ","): If B = 0 Then _
            B = InStr(A + 1, Text, "}"): If B = 0 Then Exit Function
    ParamD = Mid(Text, A, B - A)
    End Function

Function GetParam(Text As String, Cmd As String, Optional Pos As Long = 1) As String
    'Extracts <Cmd>...</Cmd> and updates the position
    Dim A As Long, B As Long
    If Pos = 0 Then Exit Function
    Pos = InStr(Pos, Text, "<" & Cmd & ">"): If Pos = 0 Then Exit Function
    B = Len(Cmd): A = Pos + B + 2
    Pos = InStr(A, Text, "</" & Cmd & ">"): If Pos = 0 Then Exit Function
    GetParam = Mid(Text, A, Pos - A): Pos = Pos + B + 3
    End Function

Function GetParamT(Text As String, Optional Pos As Long = 1) As String 'String "," and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, ","): If Pos Then _
        GetParamT = Mid(Text, A, Pos - A): Pos = Pos + 1 Else GetParamT = Mid(Text, A)
    End Function

Function GetParamN(Text As String, Optional Pos As Long = 1) As Double 'Double "," and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, ","): If Pos Then _
        GetParamN = Val(Mid(Text, A, Pos - A)): Pos = Pos + 1 Else GetParamN = Val(Mid(Text, A))
    End Function

Function GetParamS(Text As String, Optional Pos As Long = 1) As Single 'Single "," and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, ","): If Pos Then _
        GetParamS = Val(Mid(Text, A, Pos - A)): Pos = Pos + 1 Else GetParamS = Val(Mid(Text, A))
    End Function

Function GetParamI(Text As String, Optional Pos As Long = 1) As Long   'Long "," and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, ","): If Pos Then _
        GetParamI = Val(Mid(Text, A, Pos - A)): Pos = Pos + 1 Else GetParamI = Val(Mid(Text, A))
    End Function

Function GetItemT(Text As String, Optional Pos As Long = 1) As String 'String " " and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, " "): If Pos Then _
        GetItemT = Mid(Text, A, Pos - A): Pos = Pos + 1 Else GetItemT = Mid(Text, A)
    End Function

Function GetItemN(Text As String, Optional Pos As Long = 1) As Double 'Double " " and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, " "): If Pos Then _
        GetItemN = Val(Mid(Text, A, Pos - A)): Pos = Pos + 1 Else GetItemN = Val(Mid(Text, A))
    End Function

Function GetItemS(Text As String, Optional Pos As Long = 1) As Single 'Single " " and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, " "): If Pos Then _
        GetItemS = Val(Mid(Text, A, Pos - A)): Pos = Pos + 1 Else GetItemS = Val(Mid(Text, A))
    End Function

Function GetItemI(Text As String, Optional Pos As Long = 1) As Long   'Long " " and updates the position
    Dim A As Long
    If Pos Then A = Pos: Pos = InStr(Pos, Text, " "): If Pos Then _
        GetItemI = Val(Mid(Text, A, Pos - A)): Pos = Pos + 1 Else GetItemI = Val(Mid(Text, A))
    End Function

Sub LogPrint(ByVal Text As String, Optional ByVal AddCrLf As Boolean = True)
    If Not Log.Visible Then MsgBox Text _
    Else Log.Txt.SelStart = &HFFFF&: If AddCrLf Then Log.Txt.SelText = Text & vbCrLf _
        Else Log.Txt.SelText = Text
    End Sub

Sub Message(Optional Text As String, Optional ByVal Delay As Long, _
        Optional ByVal Cancel As Boolean, Optional ByVal Icon As Long, _
        Optional ByVal Update As Boolean)
    If Text <> "" Then
        If Intro.Visible Then Intro.Txt = Text _
        Else If Main.Visible Then UpdateNote Text, Delay, Cancel, Icon, Update
    ElseIf Note.Visible Or Main.WindowState = vbMinimized Then
        HideNote
        End If
    End Sub

Sub MessageNote(Optional Text As String, Optional ByVal Delay As Long, _
        Optional ByVal Cancel As Boolean, Optional ByVal Icon As Long, _
        Optional ByVal Update As Boolean)
    If Text = "" Then HideNote _
    Else If Not Note.Active And Not Intro.Visible And Main.Visible Then _
        UpdateNote Text, Delay, Cancel, Icon, Update
    End Sub

Sub InitApp()
    Dim N As Long
    
    'Initialize variables
    IsInit = True                                           'Set initialization flag
    IsDebug = InStr(LCase(Command), "debug")                'Command line options to run in debug mode
    IsTest = IsDebug Or InStr(LCase(Command), "test")       'Test mode
    NoSave = False                                          'Enable saving settings on exit
    UpdateScreenSize                                        'Update display position and size
    DisplayName = GetDisplayName                            'Display driver name
    
    'Initialize folders
    ChangeDir App.Path                                      'Change to the application folder
    If IsDebug Then ChangeDir ".."                          'Change to the executable folder in debug mode
    AppPathName = CurDir: AppPath = AppPathName & _
        IIf(Right(AppPathName, 1) <> "\", "\", "")          'Get the application folder
    EXEPathName = AppPath & App.EXEName                     'Get the executable path and name
    InitDir AirwaysDir, "Airways"                           'Initialize airways folder
    InitDir AtmosphereDir, "Atmosphere", IsTest             'Initialize atmosphere folder
    InitDir DataDir, "Data"                                 'Initialize data folder
    InitDir EnginesDir, "Engines"                           'Initialize engines folder
    InitDir GraphsDir, "Graphs"                             'Initialize graphs folder
    InitDir ImagesDir, "Images"                             'Initialize images folder
    InitDir LocationsDir, "Locations"                       'Initialize locations folder
    InitDir LogsDir, "Logs"                                 'Initialize logs folder
    InitDir ModelsDir, "Models"                             'Initialize models folder
    InitDir PathsDir, "Paths"                               'Initialize paths folder
    InitDir PlanetsDir, "Planets"                           'Initialize planets folder
    InitDir PresetsDir, "Presets"                           'Initialize presets folder
    InitDir PropellantsDir, "Propellants"                   'Initialize propellants folder
    InitDir SatellitesDir, "Satellites"                     'Initialize satellites folder
    InitDir SensorsDir, "Sensors"                           'Initialize sensors folder
    InitDir SourceDir, "Source"                             'Initialize source folder
    InitDir StationsDir, "Stations"                         'Initialize stations folder
    InitDir TerrainDir, "Terrain", IsTest                   'Initialize terrain folder
    InitDir TexturesDir, "Textures", IsTest                 'Initialize textures folder
    InitDir ToolBarDir, "Toolbar"                           'Initialize toolbar folder
    InitDir VideosDir, "Videos"                             'Initialize viseos folder
    If Not IsTest And Not FileExists(EXEPathName & ".ini") Then CreateLinks 'Create application shortcuts
    ShellEx SourceDir & "Splash.exe " & ImagesDir & "Splash2.jpg" 'Show splash image
    
    'Initialize windows and panels
    'Load Main                                               'Load Main window
    Load Note                                               'Load Message window
    Load InputBox                                           'Load Input panel
    Load ToolBar                                            'Load ToolBar panel
    Load Help                                               'Load Help panel
    Load Data                                               'Load Data panel
    Load Log                                                'Load Log panel
    Load Graph                                              'Load Graph panel
    Load Setup                                              'Load Setup panel
    Load Stages                                             'Load Stages panel
    Load Editor                                             'Load Propellant editor
    Load View                                               'Load View panel
    Load Link                                               'Load Link panel
    Load Preset                                             'Load Preset editor
    Load Browser                                            'Load Web browser
    Load Search                                             'Load Search panel
    Load Onboard                                            'Load Payload camera
    Load Video                                              'Load Video player
    Load Video360                                           'Load 360º Video player
    Load Notepad                                            'Load Notepad panel
    Load Model                                              'Load Rover 3D model panel
    Load Model2                                             'Load Stage 3D model panel
    Load Graph3D                                            'Load 3D graph panel
    Load Map                                                'Load Map panel
    Load Graph2                                             'Load Input graph panel
    Load Axes                                               'Load Remote control
    Load Calc                                               'Load Trajectory editor
    Load Burn                                               'Load Burn editor
    Load Options                                            'Load Options panel
    Load Colors                                             'Load Color selector
    Load Intro: DoEvents                                    'Load Splash window
    'Intro.Load                                              'Change the splash image
    LoadConfig                                              'Load configuration
    LoadPlanets                                             'Load planets
    LoadMoons                                               'Load moons
    LoadAsteroids                                           'Load asteroids
    LoadComets                                              'Load comets
    LoadCurves                                              'Load thrust curves
    LoadEngines                                             'Load engines
    LoadPropellants                                         'Load propellants
    LoadLocations                                           'Load locations
    LoadCities                                              'Load cities
    LoadCDs                                                 'Load drag curves
    LoadStars                                               'Load stars
    LoadStarTracker                                         'Load star tracker list
    LoadSatellites                                          'Load satellites
    LoadAir                                                 'Load atmospheres
    LoadAirspaces                                           'Load airspaces
    LoadAirways                                             'Load airways
    LoadAirports                                            'Load airports
    LoadSeaports                                            'Load seaports
    LoadAreas                                               'Load populated areas
    LoadLand                                                'Load land and water boundaries
    LoadStations                                            'Load stations
    LoadSensors                                             'Load sensors
    LoadLenses                                              'Load lenses
    LoadWaypoints                                           'Load waypoints
    LoadCommands                                            'Load rover commands
    LoadLinks                                               'Load links
    LoadNotepad                                             'Load notepad text and variables
    LoadMagneticModel                                       'Load World Magnetic Model library
    InitSockets                                             'Initialize network sockets
    InitGdiPlus                                             'Initialize GDI+ API
    Message "Loading default preset …"
    LoadPreset "Default", False, False, False               'Load current preset
    LoadBurnPreset "Default", False                         'Load current burn preset
    If InStr(LCase(Command), "gdi") Then Options.UseOpenGL = False 'Command line option to force GDI render
    Options.UseOpenGL = InitOpenGL(Options.UseOpenGL)       'Initialize OpenGL
    Message "Starting simulation …"
    Init                                                    'Initialize simulation
    IsInit = False                                          'Clear initialization flag
    ResizePanels                                            'Update panels size
    Main.Show: DoEvents                                     'Show Main window
    If Main.mnPanelsToolBar.Checked Then ToolBar.Activate   'Show ToolBar panel
    If Main.mnPanelsData.Checked Then Data.Activate         'Show Data panel
    If Main.mnPanelsLog.Checked Then Log.Activate           'Show Log panel
    If Main.mnPanelsLink.Checked Then Link.Activate         'Show Link panel
    If Main.mnPanelsGraph.Checked Then Graph.Activate       'Show Graph panel
    If Main.mnPanelsSetup.Checked Then Setup.Activate       'Show Setup panel
    If Main.mnPanelsStages.Checked Then Stages.Activate     'Show Stages panel
    If Main.mnPanelsEditor.Checked Then Editor.Activate     'Show Propellant editor
    If Main.mnPanelsBurn.Checked Then Burn.Activate         'Show Burn editor
    If Main.mnPanelsPreset.Checked Then Preset.Activate     'Show Preset panel
    If Main.mnPanelsNotepad.Checked Then Notepad.Activate   'Show Notepad panel
    If Main.mnPanelsOptions.Checked Then Options.Activate   'Show Options panel
    If Main.mnPanelsColors.Checked Then Colors.Activate     'Show Color selector
    If Main.mnPanelsMap.Checked Then Map.Activate           'Show Map panel
    If Main.mnPanelsGraph2.Checked Then Graph2.Activate     'Show Input graph panel
    If Main.mnPanelsModel.Checked Then ActivateView Cam3    'Show Rover 3D model
    If Main.mnPanelsModel2.Checked Then ActivateView Cam4   'Show Stage 3D model
    If Main.mnPanelsView.Checked Then ActivateView Cam(0)   'Show View panel
    For N = 1 To CamN - 1: ActivateView Cam(N): Next        'Show extra View panels
    If Main.mnPanelsGraph3D.Checked Then ActivateView Gra(0) 'Show 3D graph panel
    For N = 1 To GraN - 1: ActivateView Gra(N): Next        'Show extra 3D graph panels
    If Main.mnPanelsSearch.Checked Then Search.Activate     'Show Search panel
    If Main.mnPanelsTrajectory.Checked Then Calc.Activate   'Show Trajectory editor
    If Main.mnPanelsRemote.Checked Then Axes.Activate         'Show Remote control
    If Main.mnPanelsSetup.Checked Then SetFocusX Setup.hWnd 'Focus on Setup panel if visible
    Intro.Tm = True                                         'Hide Splash window
    End Sub

Sub CloseApp()
    
    'Clean up simulation
    If Main.WindowState = vbMinimized Then RestoreForm Main 'Restore main window if minimized
    Play = Paused                                           'Stop simulation if running
    If Record = Active Then RecordVideo Pause               'Stop video recording if active
    StopTrack                                               'Close tracking connection
    StopLink                                                'Close link connection
    SetTactic False: SetSync False                          'Stop main timers
    If Not IsTest And InStr(LCase(Command), "nosave") = 0 And Shift = 0 Then 'Command line option to disable auto-save
        If Not NoSave Then SaveConfig                       'Save configuration
        SavePreset "Default"                                'Save current preset
        SaveBurnPreset "Default"                            'Save current burn preset
        SavePropellants                                     'Save propellants
        SaveLocations                                       'Save locations
        SaveWaypoints                                       'Save waypoints
        SaveLinks                                           'Save links
        SaveNotepad                                         'Save notepad text and variables
        End If
    On Error Resume Next
    Video.StopAll                                           'Stop video player
    Onboard.StopAll                                         'Stop video stream
    CleanUpOpenGL                                           'Clean up OpenGL
    GdiplusShutdown hGP: hGP = 0                            'Clean up GDI+ API
    WSACleanup                                              'Clean up network sockets
    WMMCleanup                                              'Clean up World Magnetic Model library
    CleanUpCP                                               'Clean up CProPEP simulator
    On Error GoTo 0
    
    'Unload windows and panels
    UnloadGraphs                                            'Unload extra 3D graph panels
    UnloadViews                                             'Unload extra View panels
    Unload Colors                                           'Unload Color selector
    Unload Options                                          'Unload Options panel
    Unload Burn                                             'Unload Burn editor
    Unload Calc                                             'Unload Trajectory editor
    Unload Axes                                             'Unload Remote control
    Unload Graph2                                           'Unload Input graph panel
    Unload Map                                              'Unload Map panel
    Unload Graph3D                                          'Unload 3D graph panel
    Unload Model2                                           'Unload Stage 3D model panel
    Unload Model                                            'Unload Rover 3D model panel
    Unload Notepad                                          'Unload Notepad panel
    Unload Video360                                         'Unload 360º Video player
    Unload Video                                            'Unload Video player
    Unload Onboard                                          'Unload Payload camera
    Unload Search                                           'Unload Search panel
    Unload Browser                                          'Unload Web browser
    Unload Preset                                           'Unload Preset editor
    Unload Link                                             'Unload Link panel
    Unload View                                             'Unload View panel
    Unload Editor                                           'Unload Propellant editor
    Unload Stages                                           'Unload Stages panel
    Unload Setup                                            'Unload Setup panel
    Unload Graph                                            'Unload Graph panel
    Unload Log                                              'Unload Log panel
    Unload Data                                             'Unload Data panel
    Unload Help                                             'Unload Help panel
    Unload ToolBar                                          'Unload ToolBar panel
    Unload InputBox                                         'Unload Input panel
    Unload Note                                             'Unload Message window
    Unload Intro                                            'Unload Splash window
    'Unload Main                                             'Unload Main window
    UnloadAll                                               'Unload all panels
    
    If IsDebug Then ChangeDir App.Path                      'Return to the source code folder in debug mode
    
    End Sub

Sub InitVars()
    Months(1) = "Jan": Months(2) = "Feb": Months(3) = "Mar": Months(4) = "Apr"
    Months(5) = "May": Months(6) = "Jun": Months(7) = "Jul": Months(8) = "Aug"
    Months(9) = "Sep": Months(10) = "Oct": Months(11) = "Nov": Months(12) = "Dec"
    Days(1) = "Mon": Days(2) = "Tue": Days(3) = "Wed": Days(4) = "Thu"
    Days(5) = "Fri": Days(6) = "Sat": Days(7) = "Sun"
    Planets(ToEarth) = "Earth": Planets(ToMoon) = "Moon": Planets(ToSun) = "Sun"
    Planets(ToMercury) = "Mercury": Planets(ToVenus) = "Venus": Planets(ToMars) = "Mars"
    Planets(ToJupiter) = "Jupiter": Planets(ToSaturn) = "Saturn": Planets(ToUranus) = "Uranus"
    Planets(ToNeptune) = "Neptune": Planets(ToPluto) = "Pluto"
    PlanetApoapsis(ToEarth) = "Apogee": PlanetApoapsis(ToMoon) = "Aposelene": PlanetApoapsis(ToSun) = "Aphelion"
    PlanetApoapsis(ToMercury) = "Apohermion": PlanetApoapsis(ToVenus) = "Apocytherion": PlanetApoapsis(ToMars) = "Apoareion"
    PlanetApoapsis(ToJupiter) = "Apozene": PlanetApoapsis(ToSaturn) = "Apokrone": PlanetApoapsis(ToUranus) = "Apouranion"
    PlanetApoapsis(ToNeptune) = "Apoposeidion": PlanetApoapsis(ToPluto) = "Apohadion"
    PlanetPeriapsis(ToEarth) = "Perigee": PlanetPeriapsis(ToMoon) = "Periselene": PlanetPeriapsis(ToSun) = "Perihelion"
    PlanetPeriapsis(ToMercury) = "Perihermion": PlanetPeriapsis(ToVenus) = "Pericytherion": PlanetPeriapsis(ToMars) = "Periareion"
    PlanetPeriapsis(ToJupiter) = "Perizene": PlanetPeriapsis(ToSaturn) = "Perikrone": PlanetPeriapsis(ToUranus) = "Periuranion"
    PlanetPeriapsis(ToNeptune) = "Periposeidion": PlanetPeriapsis(ToPluto) = "Perihadion"
    AsteroidList = "All asteroids|Inner-Earth asteroids|Near-Earth asteroids|Amor asteroids|" & _
        "Near-Mars asteroids|Inner asteroid belt|Middle asteroid belt|Outer asteroid belt|" & _
        "Inner-Jupiter asteroids|Jupiter trojans|Centaurs|Kuiper belt|Scattered disk"
    AsteroidZones = Split(AsteroidList, "|")
    CometList = "Unknown|Periodic|Non periodic|Non reliable orbit|Disappeared|Not a comet"
    CometGroups = Split(CometList, "|")
    Types(TypeNone) = "None": Types(TypePlanet) = "Planet": Types(TypeMoon) = "Moon"
    Types(TypeComet) = "Comet": Types(TypeAsteroid) = "Asteroid": Types(TypeStar) = "Star"
    Types(TypeSatellite) = "Satellite": Types(TypeStation) = "Radio station"
    Types(TypeLocation) = "Location": Types(TypeAirspace) = "Airspace"
    Types(TypeAirport) = "Airport": Types(TypeSeaport) = "Seaport": Types(TypeCity) = "City"
    Types(TypeGhost) = "Ghost"
    WindowSizes = Split( _
        "1920 x 1080|1680 x 1050|1680 x 945|1600 x 1200|1440 x 1080|1440 x 900|" & _
        "1280 x 1024|1280 x 800|1280 x 720|1024 x 768|1024 x 640|" & _
        "854 x 480|800 x 600|640 x 640|640 x 480|640 x 360|" & _
        "480 x 480|480 x 360|480 x 270|320 x 320|320 x 240", "|")
    InitToolTips
    With Cam1: .ID = MainID: Set .F = Main
        .Follow = ToSimul: .LookAt = ToNone: .Relative = ToNone: .Target = ToNone
        .X = 0: .Y = 0: .Z = 0: .AngleU = 0: .AngleV = 0: .AngleW = 0: .Altitude = 3000000
        .FOV = 45: .ShipSize = 0: .Free = False: .Ortho = False: .Flat = False: .Auto = False
        End With
    CamN = 1: ReDim Cam(0)
    With Cam(0): .ID = ViewID: Set .F = View: End With
    With Cam3: .ID = RoverID: Set .F = Model
        .Altitude = 10: .FOV = 45: .AngleU = -30: .AngleV = 15: End With
    With Cam4: .ID = StageID: Set .F = Model2
        .Altitude = 65: .FOV = 45: .AngleU = -135: .AngleV = 0: .Y = 20: End With
    GraN = 1: ReDim Gra(0)
    With Gra(0): .ID = GraphID: Set .F = Graph3D
        .Altitude = 250: .FOV = 45: .AngleU = 45: .AngleV = 45: .Ortho = True: End With
    ReDim Graph2D(0): With Graph2D(0): .Tools = GraphDefault: .ScaleZ = 1: End With
    Tool = ToolDefault: ToolX = ToolDefaultX: ToolL = LabelDefault: LogOption = LogDefault
    DataMode = 2: DataTool = DataDefault: DataToolX = DataDefaultX: ToolMode = 1 ': Antialias = 0
    ExtendedLog = 0: HiDefLog = 0: GraphData = 0: GraphDef = 0: GraphHidden = 0
    GraphStepH = 1: GraphStepV = 1: GraphRangeH = 20: GraphRangeV = 20
    ColorPalette = 0: ColorMode = 0: BrowserBar = True: Asymmetric = 0
    If IsTest Then FontTextures = True Else FontTextures = False
    Color = ThemeDefault: Theme = StyleWhite: PanelColor = vb3DFace: ViewerColor = vbWhite
    ViewerBack = vbBlack: ModelBack = vbWindowBackground
    ButtonBack = vbWindowBackground: ButtonColor = vbButtonText: SymbolColor = ButtonColor
    VideoHdr.dwRate = 30: With VideoOpts: .dwKeyFrameEvery = 15: .dwFlags = &HC& 'AVICOMPRESSF_KEYFRAMES=4 AVICOMPRESSF_VALID=8
        .dwQuality = 7500: .fccHandler = &H6376736D: End With 'DIB=&H20424944 msvc=&H6376736D
    VideoSize = 100: VideoSync = False: VideoControls = False
    SearchClass = TypeAll: SearchItems = 50: SearchSort = 0
    StarsDensity = 5.8: SunShadow = 0.3: Tiles = 4: EarthTexture = ""
    Grid = 10: TacticSpeed = 0.1: AxesMove = 0: AxesRotate = 0: AxesZoom = False
    ShowIcons = &H7&: ShipSize = 1 ': TrackSize = 1: StationSize = 1
    TerrainScale = 1: TerrainQuality = 0: GravityField = &H21&: GravityScale = 1
    AtmStyle = 2: AtmLayer = -1
    Layers = LayersDefault: AirspaceP = -1: AirspaceZoneP = -1: AirspaceGroupP = -1
    Fps = 60: TimeSpeed = 10: PathsN = 0: PathsP = -1: PathTime = 30: RefreshPoints = False
    StationP = 16: StationAngle = 5 * Rad: SatelliteP = 35: SatelliteDist = 1000
    AsteroidDist = 1000000: AsteroidDensity = 0: ObjectVisibility = 0: GhostTime = Day * 2.5
    SnapTime = 6 * Hour: GraphicScale = 1: GraphicMode = &H63F87: GraphicMax = 1 + 6 * Min
    GravityMode = 3: TerrainMode = 0: AtmMode = 0
    IsAdaptive = True: IsAirspaceDetection = False: IsAreaDetection = False
    IsAsteroidProximity = False: IsDrag = True: IsBalloonTrajectory = True: IsEmptyStages = False
    IsStationVisibility = False: IsHiDefDetection = False: IsLift = True: IsThrustSL = True
    IsCollision = True: IsSunShadow = False: IsRealTime = False
    IsSatelliteProximity = False: IsSolarWind = False: IsWind = True
    IsStatistics = True: IsLaunchTrajectory = True: IsSatelliteControl = True
    IsLandingManeuver = True ': IsStandByControl = True
    LinkMode = Disabled: LinkText = True: LinkWait = False: LinkTest = False: LinkLogIn = True
    LinkLogIn2 = True: LinkLogOut = True: LinkFullLog = False: LinkDouble = False
    LinkCtlOut = 0 ': LinkCtlIn = 0
    LinkAxisX = 1: LinkAxisY = 2: LinkAxisZ = 3: LinkCtlAxes = 0: LinkCtlNormalize = True
    RCMode = 1: RCDevice = 1: RCRSteering = 1: RCRThrust = 2
    RCURoll = 1: RCUPitch = 2: RCUYaw = 4: RCUThrust = 3
    LaunchTrajectory = False: LandingManeuver = False: SatelliteControl = False
    LinkPort = -1: LinkSocket = -1: LinkSocketIn = -1: LinkSocketOut = -1
    Tt = 0: Stx = 1: Nt = 1: Rt = 1: Ut = 0: Vt = 1: Wt = 0
    ReDim Rover(0): With Rover(0): .Port = -1: .T = -1: .X = 0: .Y = 0: .Z = 0
        .U = 0: .V = 1: .W = 0: End With
    Track1.Mode = LinkNone: Track1.Port = -1: Track1.Valid = False
    Track2.Mode = LinkNone: Track2.Port = -1: Track2.Valid = False
    TrackLog = True: TrackSync = False: TrackAltitude = True: RelativeAngles = False
    TrackFollow = 0: TrackRefresh = 60
    MapMode = 0: MapOverlay = 0: MapZoom = 2: MapInfo = True: MapFollow = True: MapDownload = True
    SavePass = False: AutoSaveLog = True: AddCmd = 0
    Graph2Mode = &HF&: Graph2N = 0: Graph2I = 0: Graph2Hist = False: Graph2Step = 20
    BurnCalc = 2: BurnDef = 0.0001: BurnPreview = 0.3: BurnStyle = 2: BurnCurveT = 0
    BurnCurveM = 0: BurnCurveN = 0: BurnCurveD = 0: BurnCurveP = -1: PressureCurveN = 0
    Units = False: RelativeTemp = False: SpeedInfo = False: Stimated = False
    AltitudeInfo = False: MagneticMoment = False: AtmPressure = True: Degrees = 0
    Plane = Orbital: HideCursor = False: ShowErrors = True: TipDelay = 1
    Propellants = "": DragCurves = "": SetPresetName: IsExit = False
    End Sub

Sub LoadList(Folder As String, Optional Files As String = "*", Optional ByVal ScanDirs As Boolean, _
        Optional ByVal AddPath As Boolean, Optional ByVal AddExt As Boolean)
    Dim N As Long, S As String, T As String, D() As String
    Set List = Nothing
    If ScanDirs Then
        S = Dir(Folder & "*.", vbDirectory): T = "."
        Do While S <> "": If S <> "." And S <> ".." Then T = T & "|" & S
            S = Dir: Loop
        D = Split(T, "|")
        For N = 0 To UBound(D)
            S = Dir(Folder & D(N) & "\" & Files)
            Do While S <> ""
                If AddPath Then _
                    If AddExt Then ListAdd Folder & D(N) & "\" & S _
                        Else ListAdd Folder & D(N) & "\" & FileName(S) _
                Else If AddExt Then ListAdd S _
                    Else ListAdd FileName(S)
                S = Dir: Loop
            Next
    Else
        S = Dir(Folder & Files)
        Do While S <> ""
            If AddPath Then If AddExt Then ListAdd Folder & S Else ListAdd Folder & FileName(S) _
            Else If AddExt Then ListAdd S Else ListAdd FileName(S)
            S = Dir: Loop
        End If
    End Sub

Function LoadListEx( _
        Folder As String, Optional Files As String = "*", Optional ByVal ScanDirs As Boolean, _
        Optional ByVal AddPath As Boolean, Optional ByVal AddExt As Boolean, _
        Optional Sep As String = "|", Optional FirstItem As String) As String
    Dim N As Long
    LoadList Folder, Files, ScanDirs, AddPath, AddExt
    For N = 1 To List.Count: LoadListEx = LoadListEx & List(N) & Sep: Next
    N = Len(LoadListEx) - Len(Sep): If N >= 0 Then LoadListEx = Left(LoadListEx, N)
    If FirstItem <> "" Then LoadListEx = FirstItem & Sep & LoadListEx
    End Function

Sub LoadInfo()
    Dim N As Long
    InfoNames = LoadFile(DataDir & "Info.ini")
    ObjectLink(TypePlanet) = InfoName("Planets")
    ObjectLink(TypeMoon) = InfoName("Moons")
    ObjectLink(TypeComet) = InfoName("Comets")
    ObjectLink(TypeAsteroid) = InfoName("Asteroids")
    ObjectLink(TypeStar) = InfoName("Stars")
    ObjectLink(TypeSatellite) = InfoName("Satellites")
    ObjectLink(TypeStation) = InfoName("Stations")
    ObjectLink(TypeLocation) = InfoName("Locations")
    ObjectLink(TypeAirspace) = InfoName("Airspaces")
    ObjectLink(TypeAirport) = InfoName("Airports")
    ObjectLink(TypeSeaport) = InfoName("Seaports")
    ObjectLink(TypeCity) = InfoName("Cities")
    End Sub

Sub ResetSettings()
    If Not IsTest Then If MsgBox( _
            "All the simulation settings will be reset to defaults" & vbCrLf & _
            "and the current configuration will be lost, continue?", _
            vbOKCancel Or vbQuestion, AppName) = vbOK Then _
        NoSave = True: DeleteFile EXEPathName & ".ini" Else Exit Sub
    PostMessage Main.hWnd, &H100&, Settings: DoEvents
    End Sub

Sub LoadConfig(Optional ByVal Update As Boolean)
    Dim B As Long, C As Long, S As String, U As String
    
    Message "Loading settings …"
    InitVars
    LoadInfo
    
    With Main
    On Error Resume Next
    Ini = LoadFile(EXEPathName & ".ini"): If Ini = "" Then GoTo NoIni
    
    'Window
    ResizeForm Main, GetStr("Main")
    Border Main, False, GetBool("MainBorder")
    .WindowState = IfInt(GetBool("MainMaximized"), vbMaximized, vbNormal)
    LoadView Cam1, GetStr("MainCam")
    ResizeForm Setup, GetStr("Setup")
    Check .mnPanelsSetup, GetBool("SetupVisible")
    Setup.ShowOverview = GetBool("SetupOverview")
    Setup.SetTargets GetBool("SetupTargets")
    ResizeForm Stages, GetStr("Stages")
    Check .mnPanelsStages, GetBool("StagesVisible")
    Stages.ShowParameters = GetBool("StagesParameters")
    ResizeForm Model2, GetStr("StagesModel")
    Check .mnPanelsModel2, GetBool("StagesModelVisible")
    LoadView Cam4, GetStr("StagesModelCam"), StageID
    
    ResizeForm Editor, GetStr("Propellant")
    Check .mnPanelsEditor, GetBool("PropellantVisible")
    Editor.SetDefinition GetInt("PropellantPrecision")
    GraphData = GetInt("PropellantData")
    GraphHidden = GetBool("PropellantHidden")
    GraphRangeH = GetNum("PropellantRangeH")
    GraphRangeV = GetNum("PropellantRangeV")
    GraphStepH = GetNum("PropellantStepH")
    GraphStepV = GetNum("PropellantStepV")
    ResizeForm Burn, GetStr("Burn")
    Check .mnPanelsBurn, GetBool("BurnVisible")
    BurnCalc = GetInt("BurnMode")
    BurnDef = GetNum("BurnQuality")
    BurnPreview = LimitDbl(GetNum("BurnPreview"), , 1)
    Burn.ShowLog = GetBool("BurnLog")
    BurnCurveD = GetInt("BurnGraph")
    
    ResizeForm Data, GetStr("Data")
    Check .mnPanelsData, GetBool("DataVisible")
    DataMode = GetInt("DataMode")
    DataTool = GetInt("DataTools")
    DataToolX = GetInt("DataToolsExt")
    ResizeForm Log, GetStr("Log")
    Check .mnPanelsLog, GetBool("LogVisible")
    ExtendedLog = GetInt("LogExtended")
    HiDefLog = GetInt("LogHighDef")
    ResizeForm Link, GetStr("Link")
    Check .mnPanelsLink, GetBool("LinkVisible")
    ResizeForm Axes, GetStr("RemoteControl")
    Check .mnPanelsRemote, GetBool("RemoteControlVisible")
    ResizeForm Model, GetStr("LinkModel")
    Check .mnPanelsModel, GetBool("LinkModelVisible")
    LoadView Cam3, GetStr("LinkModelCam"), RoverID
    ResizeForm Map, GetStr("Map")
    Check .mnPanelsMap, GetBool("MapVisible")
    Err = 0: C = GetInt("MapMode")
    If Err = 0 Then MapMode = (C And &HFF&): MapOverlay = (C And &HFF&) \ &H100&: _
        MapInfo = C And &H10000: MapFollow = C And &H20000: MapDownload = C And &H40000
    Map.SetMode , False
    ResizeForm Graph2, GetStr("InputGraph")
    Check .mnPanelsGraph2, GetBool("InputGraphVisible")
    Graph2Mode = GetInt("InputGraphLines")
    Graph2Hist = GetBool("InputGraphHistogram")
    Graph2Step = GetInt("InputGraphSteps")
    
    ResizeForm View, GetStr("View")
    Check .mnPanelsView, GetBool("ViewVisible")
    LoadView Cam(0), GetStr("ViewCam"), ViewID
    Do While CreateView(GetStr("View" & CamN + 1, False))
        LoadView Cam(CamN), GetStr("View" & CamN + 1 & "Cam", False), ViewID, CamN
        CamN = CamN + 1
        Loop
    
    ResizeForm Graph3D, GetStr("3DGraph")
    Check .mnPanelsGraph3D, GetBool("3DGraphVisible")
    Graph2D(0).Tools = GetInt("3DGraphTools")
    Graph2D(0).ScaleZ = GetNum("3DGraphScale")
    LoadView Gra(0), GetStr("3DGraphCam"), GraphID
    LoadGraph Gra(0), Graph2D(0), GetStr("3DGraphName"), False, False, False
    Do While CreateGraph(GetStr("3DGraph" & GraN + 1, False))
        Graph2D(GraN).Tools = GetInt("3DGraph" & GraN + 1 & "Tools")
        Graph2D(GraN).ScaleZ = GetNum("3DGraph" & GraN + 1 & "Scale")
        LoadView Gra(GraN), GetStr("3DGraph" & GraN + 1 & "Cam"), GraphID, GraN
        LoadGraph Gra(GraN), Graph2D(GraN), _
            GetStr("3DGraph" & GraN + 1 & "Name"), False, False, False
        GraN = GraN + 1
        Loop
    
    ResizeForm Graph, GetStr("Graph")
    Check .mnPanelsGraph, GetBool("GraphVisible")
    GraphicScale = GetNum("GraphScale")
    GraphicMode = GetInt("GraphLines")
    ResizeForm Notepad, GetStr("Notepad")
    Check .mnPanelsNotepad, GetBool("NotepadVisible")
    Check Notepad.mnUseCalc, GetBool("NotepadUseCalc")
    Check Notepad.mnFixedFont, GetBool("NotepadFixedFont")
    ResizeForm Preset, GetStr("Preset")
    Check .mnPanelsPreset, GetBool("PresetVisible")
    ResizeForm Options, GetStr("Options")
    Check .mnPanelsOptions, GetBool("OptionsVisible")
    Options.SelectTab GetStr("OptionsTab")
    ResizeForm Colors, GetStr("ColorSelector")
    Check .mnPanelsColors, GetBool("ColorSelectorVisible")
    ColorPalette = GetInt("ColorSelectorPalette")
    ColorMode = GetInt("ColorSelectorMode")
    BrowserBar = GetBool("BrowserBar")
    
    MoveForm Video, GetStr("Video")
    VideoSync = GetBool("VideoSync")
    VideoControls = GetBool("VideoControls")
    VideoSize = GetInt("VideoSize")
    ResizeForm Search, GetStr("Search")
    Check .mnPanelsSearch, GetBool("SearchVisible")
    SearchClass = GetInt("SearchType")
    SearchItems = GetInt("SearchItems")
    SearchSort = GetInt("SearchSort")
    ResizeForm Calc, GetStr("Trajectory")
    Check .mnPanelsTrajectory, GetBool("TrajectoryVisible")
    Calc.SetMode GetInt("TrajectoryMode")
    MoveForm ToolBar, GetStr("Toolbar")
    Check .mnPanelsToolBar, GetBool("ToolbarVisible")
    ToolMode = GetInt("ToolbarMode")
    ToolBar.SetButtons GetInt("ToolbarButtons")
    Logo.Visible = GetBool("LogoVisible")
    
    VideoMode = GetInt("VideoMode")
    VideoHdr.dwRate = GetInt("VideoFPS")
    VideoOpts.dwQuality = GetInt("VideoQuality") * 100
    VideoOpts.dwKeyFrameEvery = GetInt("VideoKeyframes")
    VideoOpts.fccHandler = GetInt("VideoCodec")
    CaptureMouse = GetBool("CaptureMouse")
    Check .mnPanelsGE, GetBool("GoogleEarth")
    Options.SnapPanels = GetBool("SnapPanels")
    GetFont Setup, "PanelsFont"
    GetFont Main, "ViewerFont"
    GetFont Main.Report, "ReportFont", False
    GetFont Main.ModelFont, "ModelFont", False
    UpdateFonts , False
    ToolBar.Resize , False
    Help.Resize True
    
    'Render
    Options.UseOpenGL = GetBool("OpenGL")
    Options.VerticalSync = GetBool("VerticalSync")
    If Not IsTest Then Antialias = GetInt("Antialiasing")
    Cam1.Auto = GetBool("AutoZoom")
    SyncTime = GetNum("SyncTime")
    Color = GetInt("ColorTheme")
    Theme = GetInt("PanelsStyle")
    Grid = GetInt("GridStep")
    TerrainScale = GetNum("TerrainScale")
    TerrainQuality = GetInt("TerrainQuality")
    GravityField = GetInt("GravityField")
    GravityScale = GetNum("GravityScale")
    AtmStyle = GetInt("AtmosphereStyle")
    AtmLayer = GetInt("AtmosphereLayer")
    
    Layers = GetInt("Layers")
    AirspaceZoneP = GetInt("AirspaceZone")
    AirspaceGroupP = GetInt("AirspaceGroup")
    StarsDensity = GetNum("StarsDensity")
    SunShadow = GetNum("SunShadow")
    ShipSize = GetNum("ShipSize")
    ShowIcons = GetInt("ShowIcons")
    EarthTexture = GetStr("EarthTexture")
    Tiles = GetInt("TileTextures")
    FontTextures = GetBool("FontTextures")
    HideCursor = GetBool("HideCursor")
    TipDelay = GetNum("ToolTipDelay")
    ShowErrors = GetBool("ShowErrors")
    AxesMove = GetInt("InvertMovement")
    AxesRotate = GetInt("InvertRotation")
    AxesZoom = GetBool("InvertZoom")
    Asymmetric = GetNum("AsymmetricRender")
    Tool = GetInt("RenderTools")
    ToolX = GetInt("RenderToolsExt")
    ToolL = GetInt("RenderToolsLabels")
    
    'Simulation
    SetSettings GetInt("Settings")
    GravityMode = GetInt("GravityMode")
    TerrainMode = GetInt("TerrainMode")
    AtmMode = GetInt("AtmosphereMode")
    Plane = GetInt("MainPlane")
    Units = GetBool("EnglishUnits")
    RelativeTemp = Not GetBool("AbsoluteTemp")
    SpeedInfo = Not GetBool("Speed")
    Stimated = GetBool("RelativeTime")
    AltitudeInfo = Not GetBool("GeoidAltitude")
    MagneticMoment = GetBool("MagneticMoment")
    AtmPressure = GetBool("AtmospherePressures")
    Degrees = GetInt("Degrees")
    SnapTime = GetInt("SnapPointsTime")
    PathTime = GetInt("PathPointsTime")
    GraphicMax = GetInt("GraphPointsTime") + 1
    
    StationP = GetInt("StationsGroup")
    StationAngle = GetNum("StationAngle") * Rad
    SatelliteP = GetInt("SatelliteGroup")
    SatelliteDist = GetNum("SatelliteDistance")
    AsteroidP = GetInt("AsteroidGroup")
    AsteroidDist = GetNum("AsteroidDistance")
    AsteroidDensity = GetInt("AsteroidDensity")
    ObjectVisibility = GetNum("ObjectVisibility")
    LogOption = GetInt("LogToFile")
    SetEventFlags GetNum("PauseEvents"), 1
    SetEventFlagsEx GetNum("PauseEventsEx"), 1
    SetEventFlags GetNum("SaveEvents"), 2
    SetEventFlagsEx GetNum("SaveEventsEx"), 2
    SetEventFlags GetNum("LogEvents"), 4
    SetEventFlagsEx GetNum("LogEventsEx"), 4
    SetEventFlags GetNum("RecEvents"), 8
    SetEventFlagsEx GetNum("RecEventsEx"), 8
    TimeSpeed = GetNum("Speed")
    
    'Link
    B = GetInt("LinkMode")
    Link.SetParams GetInt("LinkCursor")
    With Link
    For C = 0 To IDLast - 1
        U = Switch(C = IDStation, "Station", C = IDRover, "Rover", True, "Link")
        Err = 0: S = GetStr(U & "COMSetup")
        If Err = 0 Then .LinkPort(C) = Param(S): .Speed(C) = NumN(S) & " baud": _
            .Settings(C) = Param(S) & "," & Param(S) & "," & Param(S) & "," & Param(S): _
            .TimeOut(C) = Param(S)
        Err = 0: S = GetStr(U & "UDPSetup")
        If Err = 0 Then .IP(C) = Param(S): .Port(C) = Param(S): .Ping(C) = Param(S)
        .User(C) = GetStr(U & "User")
        .Pass(C) = GetStr(U & "Password")
        Next
    End With
    LaunchTrajectory = GetBool("LaunchTrajectory")
    LandingManeuver = GetBool("LandingManeuver")
    SatelliteControl = GetBool("SatelliteControl")
    
    RCMode = GetInt("RemoteControlMode")
    RCDevice = GetInt("RemoteControlDevice")
    RCRSteering = GetInt("MapRoverSteering")
    RCRThrust = GetInt("MapRoverThrust")
    RCURoll = GetInt("MapUAVRoll")
    RCUPitch = GetInt("MapUAVPitch")
    RCUYaw = GetInt("MapUAVYaw")
    RCUThrust = GetInt("MapUAVThrust")
    LinkAxisX = GetInt("MapInputAxisX")
    LinkAxisY = GetInt("MapInputAxisY")
    LinkAxisZ = GetInt("MapInputAxisZ")
    LinkCtlAxes = GetInt("AlignInputAxes")
    LinkCtlNormalize = GetBool("NormalizeInputVector")
    LinkCtlOut = GetInt("NMEAOutput")
    LinkText = GetBool("TextMode")
    LinkDouble = GetBool("DoublePrecision")
    
    LinkWait = GetBool("WaitDevice")
    LinkTest = GetBool("TestMode")
    LinkLogIn = GetBool("LogInput")
    LinkLogIn2 = GetBool("LogInput2")
    LinkLogOut = GetBool("LogOutput")
    LinkFullLog = GetBool("FullDataLog")
    TrackLog = GetBool("TrackLog")
    TrackSync = GetBool("TrackSync")
    TrackFollow = GetInt("TrackFollow")
    TrackRefresh = GetNum("TrackRefresh")
    TrackAltitude = GetBool("TrackAltitude")
    RelativeAngles = GetBool("RelativeAngles")
    SavePass = GetBool("SavePassword")
    AutoSaveLog = GetBool("AutoSaveLog")
    AddCmd = GetInt("AddCmd")
    
    RoverModel = GetStr("RoverModel")
    StationModel = GetStr("StationModel")
    TrackModel = GetStr("TrackingModel")
    Err = 0: C = GetInt("LinkFlags")
    If Err = 0 Then _
        LaunchTrajectory = CBool(C And 1): LandingManeuver = CBool(C And 2): _
        SatelliteControl = CBool(C And 4)
    SetLink B
    
    If TerrainMode = 2 Or (Tool And ToolTerrain) <> 0 Then _
        If Not LoadTerrain Then TerrainMode = IfInt(TerrainMode = 2, 1, TerrainMode): _
        Tool = Tool And Not ToolTerrain
    Colors.SetPalette
    Colors.Update ModeLoad
    ColorTheme
    
    On Error GoTo 0
    End With
    GoTo Ext
    
NoIni:
    Border Main, , False
    Main.WindowState = vbMaximized
    Calc.Mode.SelectedItem = 0
    Setup.ShowOverview = False
    Stages.ShowParameters = False
    Burn.ShowLog = False
    SetFont
    SetFont Main
    Colors.SetPalette
    ColorTheme 0
    UpdateFonts
    ToolBar.Resize , False
    Help.Resize True
    
Ext:
    Message
    If Update Then ResizeMain: Stats: Render: Info
    
    End Sub

Sub SaveConfig()
    Dim C As Long, N As Long, U As String
    With Main
    
    StrAdd vbCrLf & "[Window]" & vbCrLf & vbCrLf & _
        "Main=" & FormSize(Main) & vbCrLf & _
        "MainMaximized=" & -(.WindowState = vbMaximized) & vbCrLf & _
        "MainBorder=" & -((GetWindowLong(.hWnd, -16) And &H400000) <> 0) & vbCrLf & _
        "MainCam=" & ViewParams(Cam1) & vbCrLf & _
        "Setup=" & FormSize(Setup) & vbCrLf & _
        "SetupVisible=" & -Setup.Visible & vbCrLf & _
        "SetupOverview=" & -Setup.ShowOverview & vbCrLf & _
        "SetupTargets=" & -(Not Setup.Overview.Visible) & vbCrLf & _
        "Stages=" & FormSize(Stages) & vbCrLf & _
        "StagesVisible=" & -Stages.Visible & vbCrLf & _
        "StagesParameters=" & -Stages.ShowParameters & vbCrLf & _
        "StagesModel=" & FormSize(Model2) & vbCrLf & _
        "StagesModelVisible=" & -Model2.Visible & vbCrLf & _
        "StagesModelCam=" & ViewParams(Cam4) & vbCrLf
    StrAdd _
        "Propellant=" & FormSize(Editor) & vbCrLf & _
        "PropellantVisible=" & -Editor.Visible & vbCrLf & _
        "PropellantPrecision=" & GraphDef & vbCrLf & _
        "PropellantData=" & GraphData & vbCrLf & _
        "PropellantHidden=" & -GraphHidden & vbCrLf & _
        "PropellantRangeH=" & NumD(GraphRangeH) & vbCrLf & _
        "PropellantRangeV=" & NumD(GraphRangeV) & vbCrLf & _
        "PropellantStepH=" & NumD(GraphStepH) & vbCrLf & _
        "PropellantStepV=" & NumD(GraphStepV) & vbCrLf & _
        "Burn=" & FormSize(Burn) & vbCrLf & _
        "BurnVisible=" & -Burn.Visible & vbCrLf & _
        "BurnMode=" & BurnCalc & vbCrLf & _
        "BurnQuality=" & NumD(BurnDef) & vbCrLf & _
        "BurnPreview=" & NumD(BurnPreview) & vbCrLf & _
        "BurnLog=" & -Burn.ShowLog & vbCrLf & _
        "BurnGraph=" & BurnCurveD & vbCrLf
    StrAdd _
        "Data=" & FormSize(Data) & vbCrLf & _
        "DataVisible=" & -Data.Visible & vbCrLf & _
        "DataMode=" & DataMode & vbCrLf & _
        "DataTools=" & ToHex(DataTool) & vbCrLf & _
        "DataToolsExt=" & ToHex(DataToolX) & vbCrLf & _
        "Log=" & FormSize(Log) & vbCrLf & _
        "LogVisible=" & -Log.Visible & vbCrLf & _
        "LogExtended=" & ExtendedLog & vbCrLf & _
        "LogHighDef=" & HiDefLog & vbCrLf & _
        "Link=" & FormSize(Link) & vbCrLf & _
        "LinkVisible=" & -Link.Visible & vbCrLf & _
        "RemoteControl=" & FormSize(Axes) & vbCrLf & _
        "RemoteControlVisible=" & -Axes.Visible & vbCrLf & _
        "LinkModel=" & FormSize(Model) & vbCrLf & _
        "LinkModelVisible=" & -Model.Visible & vbCrLf & _
        "LinkModelCam=" & ViewParams(Cam3) & vbCrLf
    StrAdd _
        "Map=" & FormSize(Map) & vbCrLf & _
        "MapVisible=" & -Map.Visible & vbCrLf & _
        "MapMode=" & ToHex(CLng((MapMode And &HFF&) + (MapOverlay And &HFF&) * &H100& - _
            MapInfo * &H10000 - MapFollow * &H20000 - MapDownload * &H40000)) & vbCrLf & _
        "InputGraph=" & FormSize(Graph2) & vbCrLf & _
        "InputGraphVisible=" & -Graph2.Visible & vbCrLf & _
        "InputGraphLines=" & ToHex(Graph2Mode) & vbCrLf & _
        "InputGraphHistogram=" & -Graph2Hist & vbCrLf & _
        "InputGraphSteps=" & Graph2Step & vbCrLf
    StrAdd _
        "View=" & FormSize(View) & vbCrLf & _
        "ViewVisible=" & -View.Visible & vbCrLf & _
        "ViewCam=" & ViewParams(Cam(0)) & vbCrLf
    N = 2: For C = 1 To CamN - 1
        If Not Cam(C).F Is Nothing Then If Cam(C).F.Visible Then StrAdd _
        "View" & N & "=" & FormSize(Cam(C).F) & vbCrLf & _
        "View" & N & "Cam=" & ViewParams(Cam(C)) & vbCrLf: N = N + 1
        Next
    StrAdd _
        "3DGraph=" & FormSize(Graph3D) & vbCrLf & _
        "3DGraphVisible=" & -Graph3D.Visible & vbCrLf & _
        "3DGraphTools=" & ToHex(Graph2D(0).Tools) & vbCrLf & _
        "3DGraphScale=" & NumD(Graph2D(0).ScaleZ) & vbCrLf & _
        "3DGraphCam=" & ViewParams(Gra(0)) & vbCrLf & _
        "3DGraphName=" & IIf(Graph3D.Visible, Graph2D(0).Name, "") & vbCrLf
    N = 2: For C = 1 To GraN - 1
        If Not Gra(C).F Is Nothing Then If Gra(C).F.Visible Then StrAdd _
        "3DGraph" & N & "=" & FormSize(Gra(C).F) & vbCrLf & _
        "3DGraph" & N & "Tools=" & ToHex(Graph2D(C).Tools) & vbCrLf & _
        "3DGraph" & N & "Scale=" & NumD(Graph2D(C).ScaleZ) & vbCrLf & _
        "3DGraph" & N & "Cam=" & ViewParams(Gra(C)) & vbCrLf & _
        "3DGraph" & N & "Name=" & Graph2D(C).Name & vbCrLf
        N = N + 1
        Next
    StrAdd _
        "Graph=" & FormSize(Graph) & vbCrLf & _
        "GraphVisible=" & -Graph.Visible & vbCrLf & _
        "GraphScale=" & GraphicScale & vbCrLf & _
        "GraphLines=" & ToHex(GraphicMode) & vbCrLf & _
        "Notepad=" & FormSize(Notepad) & vbCrLf & _
        "NotepadVisible=" & -Notepad.Visible & vbCrLf & _
        "NotepadUseCalc=" & -Notepad.mnUseCalc.Checked & vbCrLf & _
        "NotepadFixedFont=" & -Notepad.mnFixedFont.Checked & vbCrLf & _
        "Preset=" & FormSize(Preset) & vbCrLf & _
        "PresetVisible=" & -Preset.Visible & vbCrLf & _
        "Options=" & FormSize(Options) & vbCrLf & _
        "OptionsVisible=" & -Options.Visible & vbCrLf & _
        "OptionsTab=" & Options.SelectedTab & vbCrLf & _
        "ColorSelector=" & FormSize(Colors) & vbCrLf & _
        "ColorSelectorVisible=" & -Colors.Visible & vbCrLf & _
        "ColorSelectorPalette=" & ColorPalette & vbCrLf & _
        "ColorSelectorMode=" & ColorMode & vbCrLf & _
        "BrowserBar=" & -BrowserBar & vbCrLf
    StrAdd _
        "Video=" & FormPos(Video) & vbCrLf & _
        "VideoSync=" & -VideoSync & vbCrLf & _
        "VideoControls=" & -VideoControls & vbCrLf & _
        "VideoSize=" & VideoSize & vbCrLf & _
        "Search=" & FormSize(Search) & vbCrLf & _
        "SearchVisible=" & -Search.Visible & vbCrLf & _
        "SearchType=" & SearchClass & vbCrLf & _
        "SearchItems=" & SearchItems & vbCrLf & _
        "SearchSort=" & SearchSort & vbCrLf & _
        "Trajectory=" & FormSize(Calc) & vbCrLf & _
        "TrajectoryVisible=" & -Calc.Visible & vbCrLf & _
        "TrajectoryMode=" & Calc.Mode.SelectedItem & vbCrLf & _
        "Toolbar=" & FormPos(ToolBar) & vbCrLf & _
        "ToolbarVisible=" & -ToolBar.Visible & vbCrLf & _
        "ToolbarMode=" & ToolMode & vbCrLf & _
        "ToolbarButtons=" & ToHex(ToolBar.GetButtons) & vbCrLf & _
        "LogoVisible=" & -Logo.Visible & vbCrLf
    StrAdd _
        "VideoMode=" & IfInt(VideoMode = 3, 0, VideoMode) & vbCrLf & _
        "VideoFPS=" & VideoHdr.dwRate & vbCrLf & _
        "VideoQuality=" & CLng(VideoOpts.dwQuality \ 100) & vbCrLf & _
        "VideoKeyframes=" & VideoOpts.dwKeyFrameEvery & vbCrLf & _
        "VideoCodec=" & ToHex(VideoOpts.fccHandler) & vbCrLf & _
        "CaptureMouse=" & -CaptureMouse & vbCrLf & _
        "GoogleEarth=" & -.mnPanelsGE.Checked & vbCrLf & _
        "SnapPanels=" & -Options.SnapPanels & vbCrLf & _
        "PanelsFont=" & FontParams(Setup) & vbCrLf & _
        "ViewerFont=" & FontParams(Main) & vbCrLf & _
        "ReportFont=" & FontParams(.Report) & vbCrLf & _
        "ModelFont=" & FontParams(.ModelFont) & vbCrLf & _
        vbCrLf
    
    StrAdd "[Render]" & vbCrLf & vbCrLf & _
        "OpenGL=" & -Options.UseOpenGL & vbCrLf & _
        "VerticalSync=" & -Options.VerticalSync & vbCrLf & _
        "Antialiasing=" & Antialias & vbCrLf & _
        "AutoZoom=" & -CBool(Cam1.Auto) & vbCrLf & _
        "SyncTime=" & NumD(SyncTime) & vbCrLf & _
        "ColorTheme=" & Color & vbCrLf & _
        "PanelsStyle=" & Theme & vbCrLf & _
        "GridStep=" & Grid & vbCrLf & _
        "TerrainScale=" & NumD(TerrainScale) & vbCrLf & _
        "TerrainQuality=" & TerrainQuality & vbCrLf & _
        "GravityField=" & ToHex(GravityField) & vbCrLf & _
        "GravityScale=" & NumD(GravityScale) & vbCrLf & _
        "AtmosphereStyle=" & ToHex(AtmStyle) & vbCrLf & _
        "AtmosphereLayer=" & AtmLayer & vbCrLf
    StrAdd _
        "Layers=" & ToHex(Layers) & vbCrLf & _
        "AirspaceZone=" & AirspaceZoneP & vbCrLf & _
        "AirspaceGroup=" & AirspaceGroupP & vbCrLf & _
        "StarsDensity=" & NumD(StarsDensity) & vbCrLf & _
        "SunShadow=" & NumD(SunShadow) & vbCrLf & _
        "ShipSize=" & NumD(ShipSize) & vbCrLf & _
        "ShowIcons=" & ToHex(ShowIcons) & vbCrLf & _
        "EarthTexture=" & EarthTexture & vbCrLf & _
        "TileTextures=" & Tiles & vbCrLf & _
        "FontTextures=" & -FontTextures & vbCrLf & _
        "HideCursor=" & -HideCursor & vbCrLf & _
        "ToolTipDelay=" & NumD(TipDelay) & vbCrLf & _
        "ShowErrors=" & -ShowErrors & vbCrLf & _
        "InvertMovement=" & ToHex(AxesMove) & vbCrLf & _
        "InvertRotation=" & ToHex(AxesRotate) & vbCrLf & _
        "InvertZoom=" & -AxesZoom & vbCrLf & _
        "AsymmetricRender=" & NumD(Asymmetric) & vbCrLf & _
        "RenderTools=" & ToHex(Tool) & vbCrLf & _
        "RenderToolsExt=" & ToHex(ToolX) & vbCrLf & _
        "RenderToolsLabels=" & ToHex(ToolL) & vbCrLf & _
        vbCrLf
    
    StrAdd "[Simulation]" & vbCrLf & vbCrLf & _
        "Settings=" & ToHex(GetSettings) & vbCrLf & _
        "GravityMode=" & GravityMode & vbCrLf & _
        "TerrainMode=" & TerrainMode & vbCrLf & _
        "AtmosphereMode=" & AtmMode & vbCrLf & _
        "MainPlane=" & Plane & vbCrLf & _
        "EnglishUnits=" & -Units & vbCrLf & _
        "AbsoluteTemp=" & -(Not RelativeTemp) & vbCrLf & _
        "RelativeSpeed=" & -(Not SpeedInfo) & vbCrLf & _
        "RelativeTime=" & -Stimated & vbCrLf & _
        "GeoidAltitude=" & -(Not AltitudeInfo) & vbCrLf & _
        "MagneticMoment=" & -MagneticMoment & vbCrLf & _
        "AtmospherePressures=" & -AtmPressure & vbCrLf & _
        "Degrees=" & Degrees & vbCrLf & _
        "SnapPointsTime=" & SnapTime & vbCrLf & _
        "PathPointsTime=" & PathTime & vbCrLf & _
        "GraphPointsTime=" & GraphicMax - 1 & vbCrLf
    StrAdd _
        "StationsGroup=" & StationP & vbCrLf & _
        "StationAngle=" & Round(StationAngle / Rad) & vbCrLf & _
        "SatelliteGroup=" & SatelliteP & vbCrLf & _
        "SatelliteDistance=" & NumD(SatelliteDist) & vbCrLf & _
        "AsteroidGroup=" & AsteroidP & vbCrLf & _
        "AsteroidDistance=" & NumD(AsteroidDist) & vbCrLf & _
        "AsteroidDensity=" & AsteroidDensity & vbCrLf & _
        "ObjectVisibility=" & ObjectVisibility & vbCrLf & _
        "LogToFile=" & ToHex(LogOption) & vbCrLf & _
        "PauseEvents=" & ToHex(GetEventFlags(1)) & vbCrLf & _
        "PauseEventsEx=" & ToHex(GetEventFlagsEx(1)) & vbCrLf & _
        "SaveEvents=" & ToHex(GetEventFlags(2)) & vbCrLf & _
        "SaveEventsEx=" & ToHex(GetEventFlagsEx(2)) & vbCrLf & _
        "LogEvents=" & ToHex(GetEventFlags(4)) & vbCrLf & _
        "LogEventsEx=" & ToHex(GetEventFlagsEx(4)) & vbCrLf & _
        "RecEvents=" & ToHex(GetEventFlags(8)) & vbCrLf & _
        "RecEventsEx=" & ToHex(GetEventFlagsEx(8)) & vbCrLf & _
        "Speed=" & NumD(TimeSpeed) & vbCrLf & _
        vbCrLf
    
    With Link
    StrAdd "[Link]" & vbCrLf & vbCrLf & _
        "LinkMode=" & LinkMode & vbCrLf & _
        "LinkCursor=" & Link.Pos & vbCrLf
    For C = 0 To IDLast - 1
        U = Switch(C = IDStation, "Station", C = IDRover, "Rover", True, "Link")
        StrAdd _
            U & "COMSetup=" & .LinkPort(C) & "," & _
                Round(Val(.Speed(C))) & "," & .Settings(C) & "," & .TimeOut(C) & vbCrLf & _
            U & "UDPSetup=" & .IP(C) & "," & .Port(C) & "," & .Ping(C) & vbCrLf & _
            U & "User=" & IfText(SavePass, .User(C)) & vbCrLf & _
            U & "Password=" & IfText(SavePass, .Pass(C)) & vbCrLf
        Next
    StrAdd _
        "LaunchTrajectory=" & -CBool(LaunchTrajectory) & vbCrLf & _
        "LandingManeuver=" & -CBool(LandingManeuver) & vbCrLf & _
        "SatelliteControl=" & -CBool(SatelliteControl) & vbCrLf
    End With
    StrAdd _
        "RemoteControlMode=" & RCMode & vbCrLf & _
        "RemoteControlDevice=" & RCDevice & vbCrLf & _
        "MapRoverSteering=" & RCRSteering & vbCrLf & _
        "MapRoverThrust=" & RCRThrust & vbCrLf & _
        "MapUAVRoll=" & RCURoll & vbCrLf & _
        "MapUAVPitch=" & RCUPitch & vbCrLf & _
        "MapUAVYaw=" & RCUYaw & vbCrLf & _
        "MapUAVThrust=" & RCUThrust & vbCrLf & _
        "MapInputAxisX=" & LinkAxisX & vbCrLf & _
        "MapInputAxisY=" & LinkAxisY & vbCrLf & _
        "MapInputAxisZ=" & LinkAxisZ & vbCrLf & _
        "AlignInputAxes=" & LinkCtlAxes & vbCrLf & _
        "NormalizeInputVector=" & -CBool(LinkCtlNormalize) & vbCrLf
    StrAdd _
        "NMEAOutput=" & LinkCtlOut & vbCrLf & _
        "TextMode=" & -CBool(LinkText) & vbCrLf & _
        "DoublePrecision=" & -CBool(LinkDouble) & vbCrLf & _
        "WaitDevice=" & -CBool(LinkWait) & vbCrLf & _
        "TestMode=" & -CBool(LinkTest) & vbCrLf & _
        "LogInput=" & -CBool(LinkLogIn) & vbCrLf & _
        "LogInput2=" & -CBool(LinkLogIn2) & vbCrLf & _
        "LogOutput=" & -CBool(LinkLogOut) & vbCrLf & _
        "FullDataLog=" & -CBool(LinkFullLog) & vbCrLf & _
        "TrackLog=" & -CBool(TrackLog) & vbCrLf & _
        "TrackSync=" & -CBool(TrackSync) & vbCrLf & _
        "TrackFollow=" & TrackFollow & vbCrLf & _
        "TrackRefresh=" & NumD(TrackRefresh) & vbCrLf & _
        "TrackAltitude=" & -CBool(TrackAltitude) & vbCrLf & _
        "RelativeAngles=" & -CBool(RelativeAngles) & vbCrLf & _
        "SavePassword=" & -CBool(SavePass) & vbCrLf & _
        "AutoSaveLog=" & -CBool(AutoSaveLog) & vbCrLf & _
        "AddCmd=" & AddCmd & vbCrLf
    StrAdd _
        "RoverModel=" & IfText(RoverModel <> "Rover", RoverModel) & vbCrLf & _
        "StationModel=" & IfText(StationModel <> "Station", StationModel) & vbCrLf & _
        "TrackingModel=" & IfText(TrackModel <> "Tracking", TrackModel) & vbCrLf & _
        "Flags=" & ToHex(CLng( _
            -CBool(LaunchTrajectory) - CBool(LandingManeuver) * 2 _
            - CBool(SatelliteControl) * 4 _
            )) & vbCrLf & _
        vbCrLf
    StrAdd "[Colors]" & vbCrLf & vbCrLf
    Colors.Update ModeSave
    
    'Clipboard.Clear: Clipboard.SetText StrAdd: Exit Sub
    UpdateFile EXEPathName & ".ini", StrAdd
    End With
    End Sub

'Sub CleanControlsFont(Optional Folder As String = "Source\", Optional File As String = "*.frm")
'    Dim A As Long, B As Long, S As String, T As String, U As String
'    T = Dir(Folder & File)
'    Do While T <> ""
'        Open Folder & T For Input As 1: S = Input(LOF(1), 1): U = S: Close
'        A = 0: Do
'            A = InStr(1, S, "BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851}")
'            If A = 0 Then Exit Do
'            A = InStrRev(S, vbCrLf, A): If A Then A = A + 2 Else Stop
'            B = InStr(A, S, "EndProperty"): If B = 0 Then Stop
'            B = InStr(B, S, vbCrLf): If B Then B = B + 2 Else Stop
'            S = Left(S, A - 1) & Mid(S, B)
'            Loop
'        If S <> U Then Open Folder & T For Output As 1: Print #1, S;: Close
'        T = Dir
'        Loop
'    End Sub

'Sub UpdatePresets()
'    Dim N As Long, S As String, T() As String
'    If Right(CurDir, 7) = "\Source" Then ChDir ".."
'    ReDim T(1000): S = Dir(PresetsDir & "*.ini")
'    Do While S <> "": T(N) = Left(S, Len(S) - 4): S = Dir: N = N + 1: Loop
'    For N = 0 To N - 1: LoadPreset T(N), False, False: SavePreset T(N): Next
'    ChDir "Source"
'    End
'    End Sub

Sub LoadPreset(Optional Name As String, Optional ByVal Update As Boolean = True, _
        Optional ByVal LoadModels As Boolean = True, Optional ByVal ActivateTerrain As Boolean = True)
    Dim C As Long, S As String, File As String: Static Op As OpenFileName
    
    If Name <> "" Then _
        If Name = FileName(Name) Then File = PresetsDir & Name & ".ini" _
        Else File = Name: Name = FileNameFromPath(Name) _
    Else File = GetOpenFile(Op, , FilePreset, PresetsDir): If File <> "" Then _
        Name = FileNameFromPath(File) _
        Else Exit Sub
    Ini = LoadFile(File): If InStr(Ini, "[Preset]") = 0 Then Exit Sub
    
    On Error Resume Next
    
    SetPresetName GetStr("Name")
    SetTerrainMode GetNum("TerrainMode", False), False
    SetAtmMode GetNum("AtmosphereMode", False), False
    
    Calc.Mission = GetStr("MissionTime")
    Calc.Orbit = GetStr("OrbitAltitude")
    Calc.Burst = GetStr("BurstAltitude")
    Calc.Ascent = GetStr("AscentRate")
    Calc.Descent = GetStr("TerminalVelocity")
    
    With Setup
    .TimeStep = GetStr("TimeStep")
    .Date = GetStr("Date")
    .Time = GetStr("Time")
    .Location = GetStr("Location")
    .Lat = GetStr("Latitude")
    .Lon = GetStr("Longitude")
    .Alt = GetStr("Altitude")
    .Speed = GetStr("Speed")
    .Heading = GetStr("Heading")
    .Angle = GetStr("Angle")
    .StartAngle = GetStr("StartAngle")
    .EndAngle = GetStr("EndAngle")
    .StartAlt = GetStr("StartAltitude")
    .EndAlt = GetStr("EndAltitude")
    .ThrustLimit = GetStr("ThrustLimit")
    .LiftLimit = GetStr("LiftLimit")
    .Brake = GetStr("BrakeDistance")
    .EarthPark = GetStr("EarthPark")
    .MoonPark = GetStr("MoonPark")
    End With
    
    With Stages
    IsModel = False: IsCalc = True
    .Parachute = GetStr("Parachute")
    .Abort = GetStr("AbortStage")
    .Jettison = GetNum("Jettison")
    S = "Payload": C = PayloadMax
    .Mass(C) = GetStr(S)
    .Area(C) = GetStr(S & "Diameter")
    .Length(C) = GetStr(S & "Length")
    .Control(C) = GetStr(S & "ControlThrust")
    .ControlP(C) = GetStr(S & "ControlP")
    .ControlI(C) = GetStr(S & "ControlI")
    .ControlD(C) = GetStr(S & "ControlD")
    .LiftToDrag(C) = GetStr(S & "LiftToDrag")
    .DragCf(C) = GetStr(S & "DragCf")
    .InertiaX(C) = GetStr(S & "MomentOfInertiaX")
    .InertiaY(C) = GetStr(S & "MomentOfInertiaY")
    .InertiaZ(C) = GetStr(S & "MomentOfInertiaZ")
    .CenterX(C) = GetStr(S & "CenterOfMassX")
    .CenterY(C) = GetStr(S & "CenterOfMassY")
    .CenterZ(C) = GetStr(S & "CenterOfMassZ")
    .Battery(C) = GetStr(S & "BatteryCharge")
    .Imax(C) = GetStr(S & "MaxIntensity")
    .Vmax(C) = GetStr(S & "MaxVoltage")
    .Power(C) = GetStr(S & "ElectricalPower")
    .Tmin(C) = GetStr(S & "MinTemperature")
    .Tmax(C) = GetStr(S & "MaxTemperature")
    .Emissivity(C) = GetStr(S & "Emissivity")
    IsModel = IsModel Or TextModified(.Model(C), GetStr(S & "Model"))
    .Num = 0
    For C = 1 To HoverMax
        S = IIf(C = HoverMax, "Hover", "Stage" & C)
        .Stage(C) = GetNum(S, False)
        .Mass(C) = GetStr(S & "Mass")
        .Dry(C) = GetStr(S & "Dry")
        .Thrust(C) = GetStr(S & "Thrust")
        .Delay(C) = GetStr(S & "Delay")
        .Fuel(C) = GetStr(S & "Type")
        .IspS(C) = GetStr(S & "IspSeaLevel")
        .IspV(C) = GetStr(S & "IspVacuum")
        .Area(C) = GetStr(S & "Diameter")
        .Length(C) = GetStr(S & "Length")
        .Control(C) = GetStr(S & "ControlThrust")
        .ControlP(C) = GetStr(S & "ControlP")
        .ControlI(C) = GetStr(S & "ControlI")
        .ControlD(C) = GetStr(S & "ControlD")
        .LiftToDrag(C) = GetStr(S & "LiftToDrag")
        .DragCf(C) = GetStr(S & "DragCf")
        .InertiaX(C) = GetStr(S & "MomentOfInertiaX")
        .InertiaY(C) = GetStr(S & "MomentOfInertiaY")
        .InertiaZ(C) = GetStr(S & "MomentOfInertiaZ")
        .CenterX(C) = GetStr(S & "CenterOfMassX")
        .CenterY(C) = GetStr(S & "CenterOfMassY")
        .CenterZ(C) = GetStr(S & "CenterOfMassZ")
        .Battery(C) = GetStr(S & "BatteryCharge")
        .Imax(C) = GetStr(S & "MaxIntensity")
        .Vmax(C) = GetStr(S & "MaxVoltage")
        .Power(C) = GetStr(S & "ElectricalPower")
        .Tmin(C) = GetStr(S & "MinTemperature")
        .Tmax(C) = GetStr(S & "MaxTemperature")
        .Emissivity(C) = GetStr(S & "Emissivity")
        IsModel = IsModel Or TextModified(.Model(C), GetStr(S & "Model"))
        If .Stage(C) Then If C < HoverMax Then .Num = C
        Next
    .Mass(0) = GetStr("Mass")
    IsCalc = False
    End With
    
    With Setup
    If .Targets.ListCount > 0 Then .Targets.Clear
    C = 1: Do
        S = GetStr("Target" & C, False): If S <> "" Then .AddItem S Else Exit Do
        C = C + 1: Loop
    If .Targets.ListCount > 0 Then IsStationVisibility = True: _
        Tool = Tool Or (ToolStations Or ToolLocations): Layers = Layers Or PlacesLabels
    .Overview = GetStr("Overview", , True)
    End With
    
    On Error GoTo 0
    
    Editor.Pic.Cls
    UpdateStages
    If LoadModels And IsModel Then UpdateModels
    If VideoMode = 3 Then SetVideoMode
    If Update Then If Options.ViewerMode = "Auto" Then ViewerAuto Name, False, False
    If TerrainMode < 2 Then Tool = Tool And Not ToolTerrain _
    Else If ActivateTerrain Then Tool = Tool Or ToolTerrain
    S = FileName(File)
    If FileExists(S & ".csv") Then If LoadPathEx(S & ".csv") Then GoTo DoPath
    If FileExists(S & ".kml") Then If LoadPathEx(S & ".kml") Then GoTo DoPath
    GoTo NoPath
DoPath:
    If IsBalloonTrajectory Then KMLPathPos = 1
    If Update Then With KMLPath(IfInt(IsBalloonTrajectory, 0, KMLPathTop)): _
        SetLaunch .Lat, .Lon, .Alt: End With
    GoTo Nxt
NoPath:
    If KMLPathN Then ClearKMLPaths False
Nxt:
    Options.Update
    If Update Then PostMessage Main.hWnd, &H100&, Reset
    
    End Sub

Sub SavePreset(Optional Name As String)
    Dim C As Long, N As Long, S As String, File As String: Static Op As OpenFileName
    
    If Name <> "" Then File = PresetsDir & Name & ".ini" _
    Else File = GetSaveFile(Op, Setup.Preset, FilePreset, "ini", PresetsDir)
    If File = "" Then Exit Sub
    
    With Setup
    StrAdd vbCrLf & _
        "[Preset]" & vbCrLf & _
        vbCrLf & _
        "Name=" & Replace(.Preset, ",", ".") & vbCrLf & _
        IfText(TerrainMode > 0, "TerrainMode=" & TerrainMode & vbCrLf) & _
        IfText(AtmMode > 0, "AtmosphereMode=" & AtmMode & vbCrLf) & _
        IfText(Calc.Mission <> "", "MissionTime=" & Calc.Mission & vbCrLf) & _
        IfText(Calc.Orbit <> "", "OrbitAltitude=" & Calc.Orbit & vbCrLf) & _
        IfText(Calc.Burst <> "", "BurstAltitude=" & Calc.Burst & vbCrLf) & _
        IfText(Calc.Ascent <> "", "AscentRate=" & Calc.Ascent & vbCrLf) & _
        IfText(Calc.Descent <> "", "TerminalVelocity=" & Calc.Descent & vbCrLf)
    StrAdd _
        "TimeStep=" & .TimeStep & vbCrLf & _
        "Date=" & .Date & vbCrLf & _
        "Time=" & .Time & vbCrLf & _
        "Location=" & .Location & vbCrLf & _
        "Latitude=" & .Lat & vbCrLf & _
        "Longitude=" & .Lon & vbCrLf & _
        "Altitude=" & .Alt & vbCrLf & _
        "Speed=" & .Speed & vbCrLf & _
        "Heading=" & .Heading & vbCrLf & _
        "Angle=" & .Angle & vbCrLf & _
        "StartAngle=" & .StartAngle & vbCrLf & _
        "EndAngle=" & .EndAngle & vbCrLf & _
        "StartAltitude=" & .StartAlt & vbCrLf & _
        "EndAltitude=" & .EndAlt & vbCrLf & _
        vbCrLf
    StrAdd _
        "ThrustLimit=" & .ThrustLimit & vbCrLf & _
        "LiftLimit=" & .LiftLimit & vbCrLf & _
        "BrakeDistance=" & .Brake & vbCrLf & _
        "EarthPark=" & .EarthPark & vbCrLf & _
        "MoonPark=" & .MoonPark & vbCrLf
    End With
    
    With Stages
    StrAdd _
        "Parachute=" & .Parachute & vbCrLf & _
        "AbortStage=" & .Abort & vbCrLf & _
        "Jettison=" & -.Jettison & vbCrLf & _
        "Mass=" & .Mass(0) & vbCrLf & _
        vbCrLf
    For N = 0 To HoverMax
        If N <= 0 Then
            C = PayloadMax: S = "Payload"
            StrAdd _
                S & "=" & .Mass(C) & vbCrLf
        Else
            C = N: If N <= .Num Then S = "Stage" & N _
            Else If N = HoverMax Then S = "Hover" _
            Else GoTo NoStage
            StrAdd _
                S & "=" & -.Stage(C) & vbCrLf & _
                S & "Mass=" & .Mass(C) & vbCrLf & _
                S & "Dry=" & .Dry(C) & vbCrLf & _
                S & "Thrust=" & .Thrust(C) & vbCrLf & _
                S & "Delay=" & .Delay(C) & vbCrLf & _
                S & "Type=" & .Fuel(C) & vbCrLf & _
                S & "IspSeaLevel=" & .IspS(C) & vbCrLf & _
                S & "IspVacuum=" & .IspV(C) & vbCrLf
            End If
        StrAdd _
            S & "Diameter=" & .Area(C) & vbCrLf & _
            S & "Length=" & .Length(C) & vbCrLf & _
            S & "ControlThrust=" & .Control(C) & vbCrLf & _
            S & "ControlP=" & .ControlP(C) & vbCrLf & _
            S & "ControlI=" & .ControlI(C) & vbCrLf & _
            S & "ControlD=" & .ControlD(C) & vbCrLf & _
            S & "LiftToDrag=" & .LiftToDrag(C) & vbCrLf & _
            S & "DragCf=" & .DragCf(C) & vbCrLf & _
            S & "MomentOfInertiaX=" & .InertiaX(C) & vbCrLf & _
            S & "MomentOfInertiaY=" & .InertiaY(C) & vbCrLf & _
            S & "MomentOfInertiaZ=" & .InertiaZ(C) & vbCrLf & _
            S & "CenterOfMassX=" & .CenterX(C) & vbCrLf & _
            S & "CenterOfMassY=" & .CenterY(C) & vbCrLf & _
            S & "CenterOfMassZ=" & .CenterZ(C) & vbCrLf
        StrAdd _
            S & "BatteryCharge=" & .Battery(C) & vbCrLf & _
            S & "MaxIntensity=" & .Imax(C) & vbCrLf & _
            S & "MaxVoltage=" & .Vmax(C) & vbCrLf & _
            S & "ElectricalPower=" & .Power(C) & vbCrLf & _
            S & "MinTemperature=" & .Tmin(C) & vbCrLf & _
            S & "MaxTemperature=" & .Tmax(C) & vbCrLf & _
            S & "Emissivity=" & .Emissivity(C) & vbCrLf & _
            S & "Model=" & .Model(C) & vbCrLf & _
            IfText(C <> HoverMax, vbCrLf)
NoStage:
        Next
    End With
    
    With Setup
    With .Targets
    If .ListCount > 0 Then StrAdd vbCrLf: _
        For C = 0 To .ListCount - 1: StrAdd "Target" & C + 1 & "=" & .List(C) & vbCrLf: Next
    End With
    If .Overview <> "" Then StrAdd vbCrLf & "Overview=" & .Overview
    End With
    
    UpdateFile File, StrAdd: S = FileName(File)
    If KMLPathN <= 0 Then
        DeleteFile S & ".kml": DeleteFile S & ".csv"
    Else
        If SavePathEx(False) Then UpdateFile S & ".kml", StrAdd
        If KMLPath(0).Time <> 0 Then If SavePathCSV Then UpdateFile S & ".csv", StrAdd
        End If
    
    End Sub

Sub DeletePreset(Optional Name As String, Optional ByVal Ask As Boolean)
    If Name = "" Then Name = Setup.Preset
    Select Case LCase(Name)
    Case "", "default", "leo", "geo", "moon": Exit Sub
        End Select
    If DeleteFile(PresetsDir & Name & ".ini", Ask) Then _
        DeleteFile PresetsDir & Name & ".csv": _
        DeleteFile PresetsDir & Name & ".kml"
    End Sub

Sub LoadBurnPreset(Optional Name As String, Optional ByVal Update As Boolean = True)
    Static Op As OpenFileName
    If Name <> "" Then Name = EnginesDir & Name & ".ini" _
    Else Name = GetOpenFile(Op, , FilePreset, EnginesDir)
    If Name = "" Then Exit Sub
    Ini = LoadFile(Name): If InStr(Ini, "[Burn preset]") = 0 Then Exit Sub
    On Error Resume Next
    With Burn
    .File = GetStr("Name")
    .Propellant = GetStr("Propellant")
    .BurnRate = GetStr("BurnRate")
    .BurnExp = GetStr("BurnRateExp")
    .Pressure = GetStr("Pressure")
    .Burst = GetStr("PressureMax")
    .EndPressure = GetStr("PressureEnd")
    .DryMass = GetStr("DryMass")
    .Mol = GetStr("MolarMass")
    .Kmix = GetStr("KMixture")
    .K2ph = GetStr("K2PhaseFlow")
    .Density = GetStr("Density")
    .Temp = GetStr("Temperature")
    .Efficiency = GetStr("Efficiency")
    .Chamber = GetStr("Chamber")
    .Length = GetStr("Length")
    .Diameter = GetStr("Diameter")
    .Thickness = GetStr("Thickness")
    .NozzleThroat = GetStr("Throat")
    .NozzleExit = GetStr("Exit")
    .SelectClass GetInt("Type"), False
    .NSeg = GetStr("NSegments")
    .Exposed.SelectItem GetInt("Exposed")
    .CoreLength = GetStr("CoreLength")
    .CoreDiameter = GetStr("CoreDiameter")
    .NFin = GetStr("NFins")
    .FinDepth = GetStr("FinDepth")
    .FinWidth = GetStr("FinWidth")
    End With
    On Error GoTo 0
    If Update Then Burn.Update
    End Sub

Sub SaveBurnPreset(Optional Name As String)
    Static Op As OpenFileName
    If Name <> "" Then Name = EnginesDir & Name & ".ini" _
    Else Name = GetSaveFile(Op, Burn.File, FilePreset, "ini", EnginesDir)
    If Name = "" Then Exit Sub
    With Burn
    StrAdd _
        vbCrLf & _
        "[Burn preset]" & vbCrLf & vbCrLf & _
        "Name=" & .File & vbCrLf & _
        "Propellant=" & .Propellant & vbCrLf & _
        "BurnRate=" & .BurnRate & vbCrLf & _
        "BurnRateExp=" & .BurnExp & vbCrLf & _
        "Pressure=" & .Pressure & vbCrLf & _
        "PressureMax=" & .Burst & vbCrLf & _
        "PressureEnd=" & .EndPressure & vbCrLf & _
        "DryMass=" & .DryMass & vbCrLf & _
        "MolarMass=" & .Mol & vbCrLf & _
        "KMixture=" & .Kmix & vbCrLf & _
        "K2PhaseFlow=" & .K2ph & vbCrLf & _
        "Density=" & .Density & vbCrLf & _
        "Temperature=" & .Temp & vbCrLf
    StrAdd _
        "Efficiency=" & .Efficiency & vbCrLf & _
        "Chamber=" & .Chamber & vbCrLf & _
        "Length=" & .Length & vbCrLf & _
        "Diameter=" & .Diameter & vbCrLf & _
        "Thickness=" & .Thickness & vbCrLf & _
        "Throat=" & .NozzleThroat & vbCrLf & _
        "Exit=" & .NozzleExit & vbCrLf & _
        "Type=" & .Class.SelectedItem & vbCrLf & _
        "NSegments=" & .NSeg & vbCrLf & _
        "Exposed=" & .Exposed.SelectedItem & vbCrLf & _
        "CoreLength=" & .CoreLength & vbCrLf & _
        "CoreDiameter=" & .CoreDiameter & vbCrLf & _
        "NFins=" & .NFin & vbCrLf & _
        "FinDepth=" & .FinDepth & vbCrLf & _
        "FinWidth=" & .FinWidth & vbCrLf
    End With
    UpdateFile Name, StrAdd
    End Sub

Sub SaveWindow()
    Dim hWnd As Long, Left As Long, Top As Long, Width As Long, Height As Long, _
        FrameWidth As Long, FrameHeight As Long, Win As Long, _
        Name As String, Bmp As Long, hSrc As Long, hDst As Long, Re As Rect, _
        Ici As IconInfo, Ci As CursorInfo, _
        Img As Long, ID As CLSID, Pd As PictDesc, Pic As StdPicture
    On Error Resume Next
    Name = VideosDir & Setup.Preset & "." & ShortDateTime
    With Re
    hWnd = GetDesktopWindow: hSrc = GetWindowDC(hWnd): GetWindowRect hWnd, Re
    If VideoMode > 0 Then _
        Win = IfInt((VideoMode = 2 Or VideoMode = 3) And Cam(0).hRC <> 0, View.hWnd, Main.hWnd): _
        GetClientRect Win, Re: ClientToScreenEx Win, Re
    Left = .Left: Top = .Top: Width = .Right: Height = .Bottom
    End With
    FrameWidth = Width: FrameHeight = Height: hDst = CreateCompatibleDC(hSrc)
    Bmp = CreateCompatibleBitmap(hSrc, FrameWidth, FrameHeight): SelectObject hDst, Bmp
    If FrameWidth = Width And FrameHeight = Height Then
        BitBlt hDst, 0, 0, FrameWidth, FrameHeight, hSrc, Left, Top, vbSrcCopy
        If CaptureMouse Then
            Ci.cbSize = Len(Ci): GetCursorInfo Ci 'IDC_ARROW=32512?
            GetIconInfo Ci.hCursor, Ici: If Ci.Flags And 1 Then DrawIconEx hDst, _
                Ci.ptScreenPos.X - Ici.xHotspot, Ci.ptScreenPos.Y - Ici.yHotspot, _
                Ci.hCursor, 32, 32, , , 3
            DeleteObject Ici.hbmColor: DeleteObject Ici.hbmMask
            End If
    Else
        SetStretchBltMode hDst, 4: SetBrushOrgEx hDst, 0, 0, 0 'Smooth On=4 Off=3
        StretchBlt hDst, 0, 0, FrameWidth, FrameHeight, hSrc, Left, Top, Width, Height, vbSrcCopy
        End If
    DeleteDC hDst: ReleaseDC hWnd, hSrc
    If hGP = 0 Then GoTo NoGP
    GdipCreateBitmapFromHBITMAP Bmp, 0, Img
    If Img = 0 Then GoTo NoGP
    CLSIDFromString StrConv("{557CF406-1A04-11D3-9A73-0000F81EF32E}", vbUnicode), ID 'PNG
    GdipSaveImageToFile Img, StrConv(Name & ".png", vbUnicode), ID, ByVal 0
    GdipDisposeImage Img
    GoTo Ext
NoGP:
    ID.Data4(0) = &HC0&: ID.Data4(7) = &H46&
    With Pd: .cbSizeOfStruct = Len(Pd): .picType = vbPicTypeBitmap: .hGDIObj = Bmp: End With
    OleCreatePictureIndirect Pd, ID, 0, Pic
    If Not Pic Is Nothing Then SavePicture Pic, Name & ".bmp": Set Pic = Nothing
Ext:
    DeleteObject Bmp
    On Error GoTo 0
    End Sub

Sub SetVideoMode(Optional ByVal Mode As Long, Optional ByVal Update As Boolean = True)
    Dim Q As Boolean
    If Mode >= 0 And Mode <= 3 Then VideoMode = Mode
    Q = VideoMode = 3
    ToolBar.ActivateButton ToolBar.Record, Q: SetTool ToolSensor, Not Q
    Options.LabelEvents1 = IIf(Q, "Rec", "Pause") & " Save Log"
    Options.LabelEvents2 = Options.LabelEvents1
    If Update Then Options.UpdateScreen
    End Sub

Sub RecordVideo(Optional ByVal Mode As PlayMode = NextStep, Optional Label As String)
    Dim N As Long, Q As Boolean
    Static hWnd As Long, Left As Long, Top As Long, Width As Long, Height As Long, _
        FrameWidth As Long, FrameHeight As Long, Win As Long, Frame As Long, _
        Bmp As Long, hSrc As Long, hDst As Long, Re As Rect, Pt As PointApi, _
        Ici As IconInfo, Ci As CursorInfo, Bits As Long, Bi As BitmapInfo, _
        pFile As Long, pStream As Long, pSCompressed As Long, Cv As CompVars
    
    Select Case Mode
    
    Case Toggle
        If Record = Active Then GoTo StopRecord Else GoTo StartRecord
    
    Case Pause
StopRecord:
        If Record <> Paused Then Record = Paused Else Exit Sub
        If VideoMode <> 3 Then
            For N = 0 To VideoHdr.dwRate - 1
                If AVIStreamWrite(pSCompressed, Frame, 1, Bits, Bi.Bh.biSizeImage, _
                    &H10&, ByVal 0, ByVal 0) Then Exit For 'AVIIF_KEYFRAME=&H10
                Frame = Frame + 1
                Next
            End If
        If Not IsExit Then ToolBar.SetIcon ToolBar.Record
        'If VideoOpts.lpFormat <> 0 Or VideoOpts.lpParms <> 0 Then AVISaveOptionsFree 1, VarPtr(VideoOpts)
        If pStream Then AVIStreamClose pStream: pStream = 0
        If pSCompressed Then AVIStreamClose pSCompressed: pSCompressed = 0
        If pFile Then AVIFileClose pFile: pFile = 0
        AVIFileExit
        If Ici.hbmColor Then DeleteObject Ici.hbmColor: Ici.hbmColor = 0
        If Ici.hbmMask Then DeleteObject Ici.hbmMask: Ici.hbmMask = 0
        If Bmp Then DeleteObject Bmp: Bmp = 0
        If hDst Then DeleteDC hDst: hDst = 0
        If hSrc Then If hWnd Then ReleaseDC hWnd, hSrc: hSrc = 0 Else DeleteDC hSrc: hSrc = 0
        If Mode = Start Then GoTo StartRecord
    
    Case Start
        If Record <> Paused Then GoTo StopRecord
StartRecord:
        If IsMultiScreen Then hWnd = 0: hSrc = CreateDC _
        Else hWnd = GetDesktopWindow: hSrc = GetWindowDC(hWnd)
        If VideoMode <= 0 Or VideoMode > 3 Then
            Left = FullLeft: Top = FullTop: Width = FullWidth: Height = FullHeight
        Else
            Q = VideoMode >= 2 And VideoMode <= 3 And Cam(0).hRC <> 0
            If Q Then Win = View.hWnd: GetClientRect Win, Re _
            Else Win = Main.hWnd: GetWindowRect Win, Re
            Width = Re.Right - Re.Left: Height = Re.Bottom - Re.Top
            If Q Then ClientToScreenEx Win, Re
            Left = Re.Left: Top = Re.Top
            End If
        Frame = 0: FrameWidth = Width: FrameHeight = Height: hDst = CreateCompatibleDC(hSrc)
        With Bi.Bh
        .biSize = Len(Bi.Bh): .biWidth = FrameWidth: .biHeight = FrameHeight
        .biPlanes = 1: .biBitCount = 24: .biSizeImage = .biWidth * .biHeight * .biBitCount \ 8
        .biCompression = 0: .biXPelsPerMeter = 3780: .biYPelsPerMeter = 3780
        End With
        Bmp = CreateDIBSection(hDst, Bi, 0, Bits, 0, 0) 'DIB_RGB_COLORS=0
        SelectObject hDst, Bmp
        If CaptureMouse Then _
            Ci.cbSize = Len(Ci): GetCursorInfo Ci: GetIconInfo Ci.hCursor, Ici _
        Else Ci.Flags = 0 'IDC_ARROW=32512?
        With VideoHdr: .fccType = mmioStringToFOURCC("vids", 0): .fccHandler = 0: .dwScale = 1
        .dwSuggestedBufferSize = Bi.Bh.biSizeImage: .rcFrame.Left = 0: .rcFrame.Top = 0
        .rcFrame.Right = Bi.Bh.biWidth: .rcFrame.Bottom = Bi.Bh.biHeight: End With
        VideoOpts.dwFlags = 8 + IfInt(VideoOpts.dwKeyFrameEvery, 4, 0) 'AVICOMPRESSF_KEYFRAMES=4 AVICOMPRESSF_VALID=8
        AVIFileInit
        AVIFileOpen pFile, VideosDir & Setup.Preset & "." & ShortDateTime & _
            IIf(Label = "", "", "." & Label) & ".avi", &H1001&, 0 'OF_WRITE=1 OF_CREATE=&H1000&
        AVIFileCreateStream pFile, pStream, VideoHdr
        'AVISaveOptions Main.hWnd, 9, 1, pStream, VarPtr(VideoOpts) 'ICMF_CHOOSE_KEYFRAME=1 ICMF_CHOOSE_DATARATE=2
        AVIMakeCompressedStream pSCompressed, pStream, VideoOpts, 0
        AVIStreamSetFormat pSCompressed, 0, Bi.Bh, Len(Bi.Bh)
        ToolBar.SetIcon ToolBar.Record, 2
        Record = Active
    
    Case Settings
        If Record = Paused Then
            Cv.lSize = Len(Cv): Cv.lFlags = 1: Cv.lKey = VideoOpts.dwKeyFrameEvery
            Cv.lFccHandler = VideoOpts.fccHandler: Cv.lQuality = VideoOpts.dwQuality
            If ICCompressorChoose(Options.hWnd, 9, ByVal 0, ByVal 0, Cv) Then
                VideoOpts.fccHandler = Cv.lFccHandler: VideoOpts.dwQuality = Cv.lQuality
                VideoOpts.dwKeyFrameEvery = Cv.lKey
                End If
            End If
    
    Case NextStep
        If FrameWidth = Width And FrameHeight = Height Then
            BitBlt hDst, 0, 0, FrameWidth, FrameHeight, hSrc, Left, Top, vbSrcCopy
        Else
            SetStretchBltMode hDst, 4: SetBrushOrgEx hDst, 0, 0, 0 'Smooth On=4 Off=3
            StretchBlt hDst, 0, 0, FrameWidth, FrameHeight, hSrc, Left, Top, Width, Height, vbSrcCopy
            End If
        If Ci.Flags And 1 Then GetCursorPos Pt: _
            DrawIconEx hDst, Pt.X - Left - Ici.xHotspot, Pt.Y - Top - Ici.yHotspot, Ci.hCursor, 32, 32, , , 3
        If AVIStreamWrite(pSCompressed, Frame, 1, Bits, Bi.Bh.biSizeImage, &H10&, ByVal 0, ByVal 0) Then _
            Message "Video recording failed", 5000: GoTo StopRecord 'AVIIF_KEYFRAME=&H10
        Frame = Frame + 1
    
        End Select
    
    End Sub

Sub ConvertVideo()
    Dim C As Long, D As Long, N As Long, _
        Files() As String, VideoFile As String, File As String, _
        Left As Long, Top As Long, Width As Long, Height As Long, _
        Frame As Long, FrameWidth As Long, FrameHeight As Long, _
        Bits As Long, Bmp As Long, hSrc As Long, hDst As Long, _
        Bi As BitmapInfo, Img As Long, Gr As Long, _
        pFile As Long, pStream As Long, pSCompressed As Long
    Static Op As OpenFileName
    
    On Error Resume Next
    If Note.Active Then Exit Sub
    If Not GetOpenFiles(Op, Files, FileImage, VideosDir, True) Then Exit Sub
    
    With Main.Pic
    Frame = 0: C = UBound(Files) + 1: D = -1
    Do While Frame < C
        N = Frame * 100 \ C: If D <> N Then D = N: _
            Message "Converting images to video, please wait … (" & N & "%)", , True
        DoEvents: If Not Note.Active Then Exit Do
        File = Files(Frame)
        If hGP = 0 Then GoTo NoGP
        GdipLoadImageFromFile StrConv(File, vbUnicode), Img
        If Img = 0 Then GoTo NoGP
        GdipGetImageWidth Img, Width: GdipGetImageHeight Img, Height
        If .Picture Then Set .Picture = Nothing
        If .Width <> Width Or .Height <> Height Then .Width = Width: .Height = Height
        Err = 0: GdipCreateFromHDC .hDC, Gr
        If Err Or Gr = 0 Then GdipDisposeImage Img: Img = 0: GoTo NoGP
        GdipDrawImageRectI Gr, Img, 0, 0, Width, Height
        GdipDeleteGraphics Gr
        GdipDisposeImage Img
NoGP:
        If Img = 0 Then Err = 0: .Picture = LoadPicture(File): If Err Then Exit Do
        hSrc = .hDC: Width = .Width: Height = .Height: If Width <= 0 Or Height <= 0 Then Exit Do
        
        If Frame = 0 Then
            FrameWidth = Width: FrameHeight = Height: VideoFile = FileReplaceExt(File, "avi")
            hDst = CreateCompatibleDC(hSrc)
            With Bi.Bh
            .biSize = Len(Bi.Bh): .biWidth = FrameWidth: .biHeight = FrameHeight
            .biPlanes = 1: .biBitCount = 24: .biSizeImage = .biWidth * .biHeight * .biBitCount \ 8
            .biCompression = 0: .biXPelsPerMeter = 3780: .biYPelsPerMeter = 3780
            End With
            Bmp = CreateDIBSection(hDst, Bi, 0, Bits, 0, 0) 'DIB_RGB_COLORS=0
            SelectObject hDst, Bmp
            With VideoHdr: .fccType = mmioStringToFOURCC("vids", 0): .fccHandler = 0: .dwScale = 1
            .dwSuggestedBufferSize = Bi.Bh.biSizeImage: .rcFrame.Left = 0: .rcFrame.Top = 0
            .rcFrame.Right = Bi.Bh.biWidth: .rcFrame.Bottom = Bi.Bh.biHeight: End With
            VideoOpts.dwFlags = 8 + IfInt(VideoOpts.dwKeyFrameEvery, 4, 0) 'AVICOMPRESSF_KEYFRAMES=4 AVICOMPRESSF_VALID=8
            AVIFileInit
            AVIFileOpen pFile, VideoFile, &H1001&, 0 'OF_WRITE=1 OF_CREATE=&H1000&
            AVIFileCreateStream pFile, pStream, VideoHdr
            AVIMakeCompressedStream pSCompressed, pStream, VideoOpts, 0
            AVIStreamSetFormat pSCompressed, 0, Bi.Bh, Len(Bi.Bh)
            End If
    
        If FrameWidth = Width And FrameHeight = Height Then
            BitBlt hDst, 0, 0, FrameWidth, FrameHeight, hSrc, Left, Top, vbSrcCopy
        Else
            SetStretchBltMode hDst, 4: SetBrushOrgEx hDst, 0, 0, 0 'Smooth On=4 Off=3
            StretchBlt hDst, 0, 0, FrameWidth, FrameHeight, hSrc, Left, Top, Width, Height, vbSrcCopy
            End If
        If AVIStreamWrite(pSCompressed, Frame, 1, Bits, Bi.Bh.biSizeImage, &H10&, ByVal 0, ByVal 0) Then _
            Message "Conversion failed", 5000: Exit Do 'AVIIF_KEYFRAME=&H10
        Frame = Frame + 1
        Loop
    
    If pStream Then AVIStreamClose pStream: pStream = 0
    If pSCompressed Then AVIStreamClose pSCompressed: pSCompressed = 0
    If pFile Then AVIFileClose pFile: pFile = 0
    AVIFileExit
    If Bmp Then DeleteObject Bmp: Bmp = 0
    If hDst Then DeleteDC hDst: hDst = 0
Ext:
    .Cls: If .Picture Then Set .Picture = Nothing
    End With
    Message
    On Error GoTo 0
    End Sub

Sub LoadLocations(Optional ByVal Update As Boolean)
    Dim A As Long, C As Long, S As String, T As String, U As String
    Message "Loading locations …"
    LocationN = 0: ReDim Location(10 - 1)
    T = LoadFiles(LocationsDir, "*.lst")
    With Setup
    For C = 1 To .mnLoc.UBound: Unload .mnLoc(C): Next
    A = 1: If T = "" Then T = "Earth,Tenerife,28.2N,16.6W,0"
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If LocationN > UBound(Location) Then ReDim Preserve Location(UBound(Location) + 10)
        With Location(LocationN)
        .Planet = IsPlanet(Param(S)): .Name = Param(S)
        .Lat = ValAngle(Param(S)): .Lon = ValAngle(Param(S)): .Alt = ValSpace(Param(S))
        End With
        LocationN = LocationN + 1
        Loop
    End With
    If LocationN <= 0 Then LocationN = 0: ReDim Location(0) _
    Else If UBound(Location) <> LocationN - 1 Then ReDim Preserve Location(LocationN - 1)
    ReDim IsLocation(UBound(Location))
    UpdateSelection
    Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub SaveLocations()
    Dim C As Long
    For C = 0 To LocationN - 1
        With Location(C)
        StrAdd Planets(.Planet) & "," & .Name & "," & _
            ToLat(.Lat, "") & "," & ToLon(.Lon, "") & "," & NumD(.Alt) & vbCrLf
        End With
        Next
    UpdateFile LocationsDir & "Locations.lst", StrAdd
    End Sub

Sub ClearLocations(Optional ByVal AllPlanets As Boolean)
    Dim N As Long
    If AllPlanets Then Erase Location: LocationN = 0: Exit Sub
    N = 0: Do While N < LocationN
        If Location(N).Planet = ToEarth Then DelLocation N Else N = N + 1
        Loop
    End Sub

Sub AddLocation(Optional ByVal N As Long = -1)
    If N < 0 Then
        For N = 0 To LocationN - 1
            If Location(N).Planet <> ToEarth Or StrComp(Setup.Location, Location(N).Name, vbTextCompare) < 0 Then Exit For
            Next
        End If
    LocationN = LocationN + 1: If LocationN <= 1 Then ReDim Location(0) _
        Else ReDim Preserve Location(LocationN - 1)
    For N = LocationN - 1 To N + 1 Step -1: Location(N) = Location(N - 1): Next
    With Location(N)
    .Planet = ToEarth: .Name = Setup.Location
    .Lat = ValAngle(Setup.Lat): .Lon = ValAngle(Setup.Lon): .Alt = ValSpace(Setup.Alt)
    End With
    End Sub

Sub DelLocation(Optional ByVal N As Long = -1)
    If N < 0 Then
        For N = 0 To LocationN - 1
            If Setup.Location = Location(N).Name Then Exit For
            Next
        End If
    If N < 0 Or N >= LocationN Then Exit Sub
    LocationN = LocationN - 1: For N = N To LocationN - 1: Location(N) = Location(N + 1): Next
    If LocationN <= 0 Then Erase Location Else ReDim Preserve Location(LocationN - 1)
    End Sub

Sub LoadCDs()
    Dim N As Long, S As String
    Message "Loading drag curves …"
    With Stages
    LoadList DataDir, "*.cd"
    DragCurves = "": For N = 0 To List.Count - 1
        S = LoadFile(DataDir & List(N + 1) & ".cd"): If S = "" Then GoTo Nxt
        DragCurves = DragCurves & List(N + 1) & "," & S & vbCrLf
        If N > .mnCd.UBound Then Load .mnCd(N)
        .mnCd(N).Caption = List(N + 1): .mnCd(N).Visible = True
Nxt:
        Next
    If DragCurves = "" Then
        N = 1: S = "Round Nose": .mnCd(0).Caption = S: .mnCd(0).Visible = True
        DragCurves = _
            S & ",0.1,0.175,0.175,0.178,0.178,0.181,0.184,0.193,0.209,0.233,0.279,0.347,0.521,0.586,0.610,0.620,0.623,0.620,0.610,0.601,0.589,0.580,0.571,0.567,0.561,0.558,0.555,0.552,0.549,0.546,0.543,0.543,0.540,0.540,0.537,0.537,0.537,0.537,0.534,0.534,0.534" & vbCrLf
        End If
    For N = .mnCd.UBound To N Step -1
        If N > 0 Then Unload .mnCd(N) Else .mnCd(0).Caption = "…": .mnCd(0) = False
        Next
    End With
    Message
    End Sub

Sub GetCD(Stage As Stage, N As String, Optional Name As String)
    Dim C As Long, E As Long, S As String, T As String
    With Stage
    ReDim .DragCurve(0): .DragN = 0: .DragStep = 0.1
    If N = "" Then GoTo Def
    DragCf = ValNum(N): If DragCf <> 0 Or N = "0" Then GoTo Ext
    C = InStr(DragCurves, N & ","): If C = 0 Then GoTo Def
    E = InStr(C, DragCurves, vbCrLf): If E = 0 Then E = Len(DragCurves) + 1
    T = Mid(DragCurves, C, E - C): Param T
    .DragStep = NumN(T): If .DragStep <= 0 Then GoTo Def
    Do
        S = Param(T): If S = "" Then Exit Do
        If .DragN > UBound(.DragCurve) Then ReDim Preserve .DragCurve(.DragN + 10)
        .DragCurve(.DragN) = Val(S): DragCf = DragCf + .DragCurve(.DragN): .DragN = .DragN + 1
        Loop
    If .DragN < 1 Then GoTo Def
    If .DragN - 1 < UBound(.DragCurve) Then ReDim Preserve .DragCurve(.DragN - 1)
    DragCf = DragCf / .DragN
    Exit Sub
Def:
    DragCf = 0.5
Ext:
    If DragCf < 0 Then DragCf = 0: If Name <> "" Then _
        MsgInit Name & " drag coefficient out of range"
    ReDim .DragCurve(0): .DragN = 1: .DragStep = 0.1: .DragCurve(0) = DragCf
    End With
    End Sub

'Sub GetCDs()
'    Dim C As Long, D As Long, X As Long, Y As Long, X1 As Single, T(3) As String
'    Main.Picture = LoadPicture("Pic.bmp"): Main.Move 60, 60, 400 * 15, 800 * 15: Main.Show: DoEvents
'    For X1 = 0 To 4 Step 0.1: D = 0: X = Int(X1 * 85 + 1.5) '85,326
'        For Y = 1 To 652: C = GetPixel(Main.hDC, X, Y)
'            If C = 0 Then
'                T(D) = T(D) & Replace(Format(Y / 326, "0.000"), ",", ".") & ","
'                D = D + 1: If D >= 4 Then Exit For
'                Do While Y < 652 And GetPixel(Main.hDC, X, Y) = 0: Y = Y + 1: Loop
'                End If
'            Next
'        Next
'    'Caption = "X=" & X & "  Y=" & Y & "  CX=" & Format(X / 85, FMini) & "  CY=" & Format(Y / 326, FMini)
'    End Sub

Sub LoadStars(Optional ByVal Update As Boolean)
    Dim A As Long, N As Double, S As String, T As String, _
        Lat As Double, Lon As Double
    Message "Loading star map …"
    SkyN = 0: ReDim Sky(100 - 1)
    T = LoadFile(PlanetsDir & "Stars.ini"): If T = "" Then GoTo Nxt
    A = 1
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If SkyN > UBound(Sky) Then ReDim Preserve Sky(UBound(Sky) + 100)
        With Sky(SkyN)
        .Mag = NumN(S): Lat = NumN(S) * Rad: Lon = NumN(S) * Rad
        N = Cos(Lat): .X = N * Cos(Lon): .Y = N * Sin(Lon): .Z = Sin(Lat)
        End With
        SkyN = SkyN + 1
        Loop
Nxt:
    If SkyN <= 0 Then Erase Sky _
    Else If UBound(Sky) <> SkyN - 1 Then ReDim Preserve Sky(SkyN - 1)
    Message
    If Update Then If Cam1.hRC Then DeleteList SkyList: If Play = Paused Then Render
    End Sub

'Sub GetStars()
'    Dim A As Long, N As Double, S As String, T As String, _
'        Lat As Double, Lon As Double, _
'        X1 As Double, Y1 As Double, Z1 As Double
'    'R=Sqr(X*X+Z*Z) RA=ATan2(X/Z) Dec=ATan2(Y/R)
'    T = LoadFile("..\Backup\Docs\Star_catalog.txt")
'    A = 1: If T = "" Then Exit Sub
'    Open "..\Data\Stars.lst" For Output As 1
'    Do
'        S = GetLine(T, A): If S = "" Then Exit Do
'        'X1 = ParamN(S): Y1 = ParamN(S): Z1 = ParamN(S)
'        'Print #1, LTrim(Str(Z1)) & "," & LTrim(Str(Y1)) & "," & LTrim(Str(X1))
'        X1 = ParamN(S): Y1 = ParamN(S): N = ParamN(S): X1 = ParamN(S): Y1 = ParamN(S): Z1 = ParamN(S)
'        Print #1, LTrim(Str(N)) & "," & LTrim(Str(X1)) & "," & LTrim(Str(Y1)) & "," & LTrim(Str(Z1))
'        Loop
'    Close
'    End
'    End Sub

Sub LoadStarTracker(Optional ByVal Update As Boolean)
    Dim A As Long, S As String, T As String, _
        N As Double, Lat As Double, Lon As Double
    Message "Loading star tracker …"
    StarsN = 0: ReDim Stars(10 - 1)
    T = LoadFile(PlanetsDir & "StarTracker.ini"): If T = "" Then GoTo Ext
    A = 1
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        'If Not IsParamE(S, "Star") Then GoTo Nxt
        If Left(S, 1) = "#" Then GoTo NoItem
        If StarsN > UBound(Stars) Then ReDim Preserve Stars(UBound(Stars) + 10)
        With Stars(StarsN)
        '#RightAscension[HHMMSS], Declination[DDMMSS], ApparentMagnitude, Distance[ly], Mass[Sun], Radius[Sun], Luminosity[Sun], Name
        '14h39m40.90s,-60º50'06.5",-0.01, 4.4, 1.1,   1.2,     2,Rigil Kent A (Alpha Centauri A)
        .Lon = ValTimeHMS(Param(S), True): .Lat = ValAngle(Param(S))
        .Mag = NumN(S): .Dist = NumN(S) * LightYear: .Mass = NumN(S) * Sun.Mass
        .Radius = NumN(S) * Sun.Radius: .Lum = NumN(S) * SunLuminosity: .Color = &HFFE0FFFF
        .Name = S
        End With
        StarsN = StarsN + 1
NoItem:
        Loop
Ext:
    If StarsN <= 0 Then ReDim Stars(0) _
    Else If StarsN - 1 <> UBound(Stars) Then ReDim Preserve Stars(StarsN - 1)
    InitStars
    UpdateSelection
    Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub InitStars()
    Dim C As Long, N As Double
    For C = 0 To StarsN - 1: With Stars(C)
        .Z = Sin(.Lat) * .Dist: N = Cos(.Lat)
        .X = N * Cos(.Lon) * .Dist: .Y = N * Sin(.Lon) * .Dist
        Rotate -AxialTilt * Rad, .Y, .Z
        End With: Next
    End Sub

'Sub GetStarTracker()
'    Dim A As Long, N As Double, Lat As Double, Lon As Double, _
'        S As String, T As String, U As String, V As String, W As String
'    T = LoadFile("Backup\Util\StarTracker.txt")
'    A = 1: If T = "" Then Exit Sub
'    Open "Data\StarTracker.lst" For Output As 1
'    Do
'        'Sirius,CMa,Alpha,9,48915,32349,06h 45m 09.25s,-16º 42' 47.3",-1.47,1.42,8.6,A0m...,Alhabor, Aschere, Canicula, the Dog Star; brightest star, 5th nearest star system; binary
'        S = GetLine(T, A): If S = "" Then Exit Do
'        V = Param(S): U = Param(S): W = Param(S)
'        U = Replace(U, "And", "Andromedae")
'        U = Replace(U, "Aql", "Aquilae")
'        U = Replace(U, "Ari", "Arietis")
'        U = Replace(U, "Aur", "Aurigae")
'        U = Replace(U, "Boo", "Bootis")
'        U = Replace(U, "CMa", "Canis Majoris")
'        U = Replace(U, "CMi", "Canis Minoris")
'        U = Replace(U, "Car", "Carinae")
'        U = Replace(U, "Cen", "Centauri")
'        U = Replace(U, "Cet", "Ceti")
'        U = Replace(U, "Cru", "Crucis")
'        U = Replace(U, "Cyg", "Cygni")
'        U = Replace(U, "Eri", "Eridani")
'        U = Replace(U, "Gem", "Geminorum")
'        U = Replace(U, "Gru", "Gruis")
'        U = Replace(U, "Hya", "Hydrae")
'        U = Replace(U, "Leo", "Leonis")
'        U = Replace(U, "Lyr", "Lyrae")
'        U = Replace(U, "Ori", "Orionis")
'        U = Replace(U, "Pav", "Pavonis")
'        U = Replace(U, "Per", "Persei")
'        U = Replace(U, "PsA", "Piscis Austrini")
'        U = Replace(U, "Sag", "Sagittarii")
'        U = Replace(U, "Sco", "Scorpii")
'        U = Replace(U, "Tau", "Tauri")
'        U = Replace(U, "TrA", "Trangulis Australis")
'        U = Replace(U, "UMa", "Ursae Majoris")
'        U = Replace(U, "UMi", "Ursae Minoris")
'        U = Replace(U, "Vel", "Velorum")
'        U = Replace(U, "Vir", "Virginis")
'        V = V & " (" & W & " " & U & ")"
'        Param S: W = Param(S): Param S
'        V = W & " " & V
'        'U = Param(S) '06h 45m 09.25s
'        'Lon = (Val(Left(U, 2)) + Val(Mid(U, 5, 2)) / 60 + Val(Mid(U, 9, 5)) / 3600) / 24 * 360 * Rad
'        'U = Param(S) '-16º 42' 47.3"
'        'Lat = (Val(Mid(U, 2, 2)) + Val(Mid(U, 6, 2)) / 60 + Val(Mid(U, 10, 4)) / 3600) * Rad
'        'If Left(U, 1) = "-" Then Lat = -Lat
'        'V = V & "," & W & "," & ToLat(Lat, , True) & "," & ToLon(Lon, , True)
'        U = Replace(Param(S), " ", "") & "," & Replace(Param(S), " ", "") & ","
'        W = Param(S) & ",": If Left(W, 1) <> "-" Then W = "+" & W
'        V = U & W & V
'        Print #1, V
'        Loop
'    Close
'    End
'    End Sub

Sub LoadLinks()
    Dim A As Long, N As Long, S As String, T As String
    Message "Loading links …"
    T = "": N = 0: ReDim Links(10 - 1)
    T = LoadFile(DataDir & "Links.lst")
    A = 1
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If N > UBound(Links) Then ReDim Preserve Links(UBound(Links) + 10)
        With Links(N): .Name = Param(S): .URL = S: End With
        N = N + 1
        Loop
    If N <= 0 Then ClearLinks Else If UBound(Links) <> N - 1 Then ReDim Preserve Links(N - 1)
    Message
    End Sub

Sub SaveLinks()
    Dim N As Long
    For N = 0 To UBound(Links): With Links(N)
        StrAdd Replace(.Name, ",", "") & IfText(.Name <> "-" And .URL <> "", "," & .URL) & vbCrLf
        End With: Next
    UpdateFile DataDir & "Links.lst", StrAdd
    End Sub

Sub ClearLinks()
    ReDim Links(0): With Links(0): .Name = "Home page": .URL = "http://code.google.com/p/moon-20/": End With
    End Sub

Sub AddLink()
    Dim N As Long, T As Long
    If Browser.Web Is Nothing Then Exit Sub
    With Browser.Web.object
    T = UBound(Links)
    For N = 1 To T
        If StrComp(.LocationName, Links(N).Name, vbTextCompare) < 0 Then Exit For
        Next
    If N < 1 Then Exit Sub
    T = T + 1: ReDim Preserve Links(T)
    For N = T To N + 1 Step -1: Links(N) = Links(N - 1): Next
    Links(N).URL = .LocationURL: Links(N).Name = .LocationName
    End With
    End Sub

Sub DelLink()
    Dim N As Long, T As Long
    If Browser.Web Is Nothing Then Exit Sub
    With Browser.Web.object
    T = UBound(Links): If T <= 0 Then Exit Sub
    For N = 1 To T
        If Links(N).URL = .LocationURL Then Exit For
        Next
    If N > T Then Exit Sub
    T = T - 1: For N = N To T: Links(N) = Links(N + 1): Next
    ReDim Preserve Links(T)
    End With
    End Sub

Sub LoadWaypoints(Optional ByVal Update As Boolean = True)
    Dim A As Long, S As String, T As String
    Message "Loading waypoints …"
    T = "": WaypointN = 0: ReDim Waypoint(1 To 10)
    T = LoadFile(DataDir & "Waypoints.csv")
    A = 1: If T = "" Then Exit Sub
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        WaypointN = WaypointN + 1
        If WaypointN > UBound(Waypoint) Then ReDim Preserve Waypoint(1 To UBound(Waypoint) + 10)
        With Waypoint(WaypointN): .X = NumN(S): .Y = NumN(S): End With
        Loop
    If WaypointN < 1 Then Erase Waypoint Else If UBound(Waypoint) <> WaypointN Then ReDim Preserve Waypoint(1 To WaypointN)
    If WaypointP < 1 Then WaypointP = 1
    If WaypointP > WaypointN Then WaypointP = WaypointN
    Message
    If Update Then Map.UpdateMap True
    End Sub

Sub LoadCommands()
    Dim C As Long, N As Long, S As String
    Message "Loading rover commands …"
    On Error Resume Next
    With Link
    Open ModelsDir & "Rover.ini" For Input As 1: If FreeFile = 1 Then GoTo Ext
    Do While Not EOF(1)
        Line Input #1, S
        If S <> "" Then
            If N > .mnCmd.UBound Then Load .mnCmd(N)
            With .mnCmd(N): C = InStr(S, ";")
            If C Then .Caption = Replace(S, ";", "; (") & ")": .Tag = Mid(S, C + 1) _
            Else .Caption = S: .Tag = ""
            End With: N = N + 1
            End If
        Loop
    Close
    If N = 0 Then .mnCmd(0).Caption = "…" Else _
        For N = .mnCmd.UBound To N Step -1: Unload .mnCmd(N): Next
    End With
    On Error GoTo 0
Ext:
    Message
    End Sub

Sub SaveWaypoints()
    Dim N As Long, Name As String
    Name = DataDir & "Waypoints.csv": If WaypointN <= 0 Then DeleteFile Name: Exit Sub
    For N = 1 To WaypointN: With Waypoint(N): StrAdd NumD(.X) & "," & NumD(.Y) & vbCrLf: End With: Next
    UpdateFile Name, StrAdd
    End Sub

Sub LoadNotepad()
    Message "Loading notepad …"
    Notepad.Txt = LoadFile(DataDir & "Notepad.txt"): LoadVariables
    Message
    End Sub

Sub SaveNotepad()
    UpdateFile DataDir & "Notepad.txt", Notepad.Txt: SaveVariables
    End Sub

Sub LoadVariables()
    Dim N As Long, S() As String
    S = Split(Replace(LoadFile(DataDir & "Variables.lst"), vbCrLf, vbLf), vbLf)
    For N = 0 To UBound(S): Notepad.DefineVar S(N): Next
    End Sub

Sub SaveVariables()
    UpdateFile DataDir & "Variables.lst", Notepad.VarList
    End Sub

Sub LoadAir()
    Message "Loading atmosphere …"
    GetAir Earth.AirCurve, Earth.GasConstant, Earth.HeatRatio, LoadFile(PlanetsDir & "Earth.Air.ini")
    GetAir Mars.AirCurve, Mars.GasConstant, Mars.HeatRatio, LoadFile(PlanetsDir & "Mars.Air.ini")
    If UBound(Earth.AirCurve) <= 0 Then DefaultAir
    Message
    If AtmMode > 0 Or (Tool And ToolAtmosphere) <> 0 Then GRIBLoad
    End Sub

Sub GetAir(Air() As AirCurve, Gas As Double, Heat As Double, Text As String)
    Dim A As Long, C As Long, S As String
    C = 0: ReDim Air(100 - 1)
    A = 1: S = GetLine(Text, A): If S = "" Then GoTo Ext
    Param S: Gas = NumN(S): Heat = NumN(S)
    Do
        S = GetLine(Text, A): If S = "" Then Exit Do
        If C > UBound(Air) Then ReDim Preserve Air(UBound(Air) + 100)
        With Air(C): Param S: .D = NumN(S): .T = NumN(S): .P = .D * Gas * .T: End With
        C = C + 1
        Loop
Ext:
    If C <= 0 Then ReDim Air(0) _
    Else If C - 1 <> UBound(Air) Then ReDim Preserve Air(C - 1)
    For C = 0 To UBound(Air) - 1: With Air(C)
        .D2 = Air(C + 1).D - .D: .P2 = Air(C + 1).P - .P: .T2 = Air(C + 1).T - .T
        End With: Next
    End Sub

Sub DefaultAir()
    StrAdd "001.0,286.9,1.4,Earth,38N,0E,2000-01-01,12:00:00|"
    StrAdd "000.0,1.229E+00,288.4|001.0,1.104E+00,284.8|002.0,9.980E-01,279.3|003.0,9.040E-01,272.5|004.0,8.192E-01,264.8|"
    StrAdd "005.0,7.411E-01,256.9|006.0,6.683E-01,249.0|007.0,5.998E-01,241.4|008.0,5.352E-01,234.5|009.0,4.745E-01,228.3|"
    StrAdd "010.0,4.177E-01,223.0|011.0,3.651E-01,218.7|012.0,3.171E-01,215.4|013.0,2.738E-01,212.8|014.0,2.353E-01,210.9|"
    StrAdd "015.0,2.014E-01,209.7|016.0,1.717E-01,209.0|017.0,1.460E-01,208.9|018.0,1.240E-01,209.2|019.0,1.052E-01,209.8|"
    StrAdd "020.0,8.915E-02,210.6|021.0,7.560E-02,211.5|022.0,6.412E-02,212.5|023.0,5.440E-02,213.6|024.0,4.618E-02,214.8|"
    StrAdd "025.0,3.922E-02,216.2|026.0,3.332E-02,217.7|027.0,2.833E-02,219.3|028.0,2.410E-02,221.0|029.0,2.052E-02,222.9|"
    StrAdd "030.0,1.748E-02,225.0|031.0,1.491E-02,227.2|032.0,1.273E-02,229.5|033.0,1.087E-02,232.1|034.0,9.294E-03,234.9|"
    StrAdd "035.0,7.955E-03,238.0|036.0,6.818E-03,241.2|037.0,5.853E-03,244.5|038.0,5.035E-03,247.8|039.0,4.341E-03,251.1|"
    StrAdd "040.0,3.752E-03,254.2|041.0,3.252E-03,257.1|042.0,2.826E-03,259.7|043.0,2.463E-03,261.9|044.0,2.154E-03,263.4|"
    StrAdd "045.0,1.890E-03,264.4|046.0,1.663E-03,264.6|047.0,1.466E-03,264.2|048.0,1.296E-03,263.1|049.0,1.147E-03,261.6|"
    StrAdd "050.0,1.015E-03,259.7|051.0,8.992E-04,257.6|052.0,7.961E-04,255.2|053.0,7.043E-04,252.8|054.0,6.224E-04,250.3|"
    StrAdd "055.0,5.492E-04,248.0|056.0,4.838E-04,245.7|057.0,4.256E-04,243.6|058.0,3.737E-04,241.6|059.0,3.277E-04,239.7|"
    StrAdd "060.0,2.869E-04,237.9|061.0,2.509E-04,236.3|062.0,2.190E-04,234.8|063.0,1.910E-04,233.3|064.0,1.663E-04,232.0|"
    StrAdd "065.0,1.447E-04,230.8|066.0,1.257E-04,229.7|067.0,1.091E-04,228.7|068.0,9.465E-05,227.7|069.0,8.201E-05,226.9|"
    StrAdd "070.0,7.099E-05,226.2|071.0,6.140E-05,225.5|072.0,5.307E-05,225.0|073.0,4.596E-05,224.5|074.0,3.975E-05,223.6|"
    StrAdd "075.0,3.442E-05,222.4|076.0,2.981E-05,220.9|077.0,2.583E-05,219.1|078.0,2.237E-05,217.1|079.0,1.936E-05,214.9|"
    StrAdd "080.0,1.675E-05,212.6|081.0,1.447E-05,210.2|082.0,1.248E-05,207.7|083.0,1.075E-05,205.2|084.0,9.237E-06,202.8|"
    StrAdd "085.0,7.924E-06,200.4|086.0,6.783E-06,198.0|087.0,5.793E-06,195.8|088.0,4.937E-06,193.7|089.0,4.197E-06,191.7|"
    StrAdd "090.0,3.560E-06,189.8|091.0,3.012E-06,188.2|092.0,2.543E-06,186.7|093.0,2.142E-06,185.4|094.0,1.800E-06,184.4|"
    StrAdd "095.0,1.510E-06,183.7|096.0,1.263E-06,183.3|097.0,1.055E-06,183.3|098.0,8.796E-07,183.6|099.0,7.322E-07,184.3|"
    StrAdd "100.0,6.086E-07,185.5|101.0,5.053E-07,187.2|102.0,4.193E-07,189.3|103.0,3.478E-07,192.1|104.0,2.886E-07,195.3|"
    StrAdd "105.0,2.397E-07,199.1|106.0,1.993E-07,203.5|107.0,1.659E-07,208.5|108.0,1.384E-07,214.2|109.0,1.157E-07,220.6|"
    StrAdd "110.0,9.692E-08,227.7|111.0,8.140E-08,235.8|112.0,6.857E-08,244.7|113.0,5.795E-08,254.5|114.0,4.915E-08,265.2|"
    StrAdd "115.0,4.186E-08,276.7|116.0,3.580E-08,289.2|117.0,3.076E-08,302.3|118.0,2.656E-08,316.1|119.0,2.306E-08,330.3|"
    StrAdd "120.0,2.014E-08,344.6|121.0,1.771E-08,358.6|122.0,1.569E-08,372.0|123.0,1.401E-08,383.6|124.0,1.258E-08,394.7|"
    StrAdd "125.0,1.134E-08,405.6|126.0,1.026E-08,416.1|127.0,9.318E-09,426.4|128.0,8.486E-09,436.5|129.0,7.751E-09,446.3|"
    StrAdd "130.0,7.099E-09,455.8|131.0,6.518E-09,465.2|132.0,5.999E-09,474.3|133.0,5.534E-09,483.2|134.0,5.116E-09,491.8|"
    StrAdd "135.0,4.738E-09,500.3|136.0,4.397E-09,508.5|137.0,4.087E-09,516.6|138.0,3.806E-09,524.4|139.0,3.549E-09,532.1|"
    StrAdd "140.0,3.315E-09,539.6|141.0,3.101E-09,546.9|142.0,2.904E-09,554.0|143.0,2.723E-09,560.9|144.0,2.557E-09,567.7|"
    StrAdd "145.0,2.404E-09,574.3|146.0,2.262E-09,580.7|147.0,2.131E-09,587.0|148.0,2.010E-09,593.2|149.0,1.897E-09,599.1|"
    StrAdd "150.0,1.792E-09,605.0|151.0,1.695E-09,610.7|152.0,1.604E-09,616.2|153.0,1.520E-09,621.7|154.0,1.441E-09,627.0|"
    StrAdd "155.0,1.367E-09,632.1|156.0,1.298E-09,637.2|157.0,1.233E-09,642.1|158.0,1.173E-09,646.9|159.0,1.116E-09,651.6|"
    StrAdd "160.0,1.062E-09,656.1|161.0,1.011E-09,660.6|162.0,9.640E-10,665.0|163.0,9.195E-10,669.2|164.0,8.775E-10,673.3|"
    StrAdd "165.0,8.378E-10,677.4|166.0,8.004E-10,681.3|167.0,7.650E-10,685.2|168.0,7.316E-10,688.9|169.0,6.999E-10,692.6|"
    StrAdd "170.0,6.700E-10,696.2|171.0,6.416E-10,699.7|172.0,6.147E-10,703.1|173.0,5.891E-10,706.4|174.0,5.649E-10,709.7|"
    StrAdd "175.0,5.418E-10,712.8|176.0,5.200E-10,715.9|177.0,4.991E-10,718.9|178.0,4.793E-10,721.9|179.0,4.605E-10,724.8|"
    StrAdd "180.0,4.425E-10,727.6|181.0,4.254E-10,730.3|182.0,4.091E-10,733.0|183.0,3.936E-10,735.6|184.0,3.787E-10,738.1|"
    StrAdd "185.0,3.646E-10,740.6|186.0,3.510E-10,743.0|187.0,3.381E-10,745.4|188.0,3.257E-10,747.7|189.0,3.139E-10,749.9|"
    StrAdd "190.0,3.026E-10,752.1|191.0,2.918E-10,754.3|192.0,2.814E-10,756.4|193.0,2.715E-10,758.4|194.0,2.620E-10,760.4|"
    StrAdd "195.0,2.529E-10,762.4|196.0,2.441E-10,764.3|197.0,2.358E-10,766.1|198.0,2.277E-10,767.9|199.0,2.200E-10,769.7|"
    StrAdd "200.0,2.126E-10,771.4|201.0,2.055E-10,773.1|202.0,1.987E-10,774.8|203.0,1.921E-10,776.4|204.0,1.858E-10,777.9|"
    StrAdd "205.0,1.797E-10,779.5|206.0,1.739E-10,780.9|207.0,1.683E-10,782.4|208.0,1.629E-10,783.8|209.0,1.577E-10,785.2|"
    StrAdd "210.0,1.527E-10,786.6|211.0,1.479E-10,787.9|212.0,1.432E-10,789.2|213.0,1.388E-10,790.4|214.0,1.345E-10,791.7|"
    StrAdd "215.0,1.303E-10,792.9|216.0,1.263E-10,794.0|217.0,1.224E-10,795.2|218.0,1.187E-10,796.3|219.0,1.151E-10,797.4|"
    StrAdd "220.0,1.117E-10,798.5|221.0,1.083E-10,799.5|222.0,1.051E-10,800.5|223.0,1.020E-10,801.5|224.0,9.896E-11,802.5|"
    StrAdd "225.0,9.605E-11,803.4|226.0,9.324E-11,804.3|227.0,9.052E-11,805.2|228.0,8.790E-11,806.1|229.0,8.536E-11,807.0|"
    StrAdd "230.0,8.291E-11,807.8|231.0,8.053E-11,808.6|232.0,7.824E-11,809.4|233.0,7.602E-11,810.2|234.0,7.387E-11,811.0|"
    StrAdd "235.0,7.179E-11,811.7|236.0,6.978E-11,812.4|237.0,6.784E-11,813.1|238.0,6.595E-11,813.8|239.0,6.412E-11,814.5|"
    StrAdd "240.0,6.236E-11,815.2|241.0,6.064E-11,815.8|242.0,5.899E-11,816.4|243.0,5.738E-11,817.0|244.0,5.582E-11,817.6|"
    StrAdd "245.0,5.431E-11,818.2|246.0,5.285E-11,818.8|247.0,5.143E-11,819.4|248.0,5.005E-11,819.9|249.0,4.872E-11,820.4|"
    StrAdd "250.0,4.743E-11,821.0|251.0,4.617E-11,821.5|252.0,4.496E-11,822.0|253.0,4.377E-11,822.4|254.0,4.263E-11,822.9|"
    StrAdd "255.0,4.152E-11,823.4|256.0,4.044E-11,823.8|257.0,3.939E-11,824.3|258.0,3.837E-11,824.7|259.0,3.738E-11,825.1|"
    StrAdd "260.0,3.642E-11,825.5|261.0,3.549E-11,825.9|262.0,3.459E-11,826.3|263.0,3.371E-11,826.7|264.0,3.285E-11,827.1|"
    StrAdd "265.0,3.203E-11,827.4|266.0,3.122E-11,827.8|267.0,3.044E-11,828.1|268.0,2.967E-11,828.5|269.0,2.893E-11,828.8|"
    StrAdd "270.0,2.821E-11,829.1|271.0,2.751E-11,829.4|272.0,2.683E-11,829.7|273.0,2.617E-11,830.0|274.0,2.553E-11,830.3|"
    StrAdd "275.0,2.490E-11,830.6|276.0,2.429E-11,830.9|277.0,2.370E-11,831.2|278.0,2.312E-11,831.4|279.0,2.256E-11,831.7|"
    StrAdd "280.0,2.202E-11,832.0|281.0,2.149E-11,832.2|282.0,2.097E-11,832.4|283.0,2.047E-11,832.7|284.0,1.998E-11,832.9|"
    StrAdd "285.0,1.950E-11,833.1|286.0,1.903E-11,833.4|287.0,1.858E-11,833.6|288.0,1.814E-11,833.8|289.0,1.771E-11,834.0|"
    StrAdd "290.0,1.730E-11,834.2|291.0,1.689E-11,834.4|292.0,1.649E-11,834.6|293.0,1.611E-11,834.8|294.0,1.573E-11,835.0|"
    StrAdd "295.0,1.536E-11,835.1|296.0,1.501E-11,835.3|297.0,1.466E-11,835.5|298.0,1.432E-11,835.6|299.0,1.399E-11,835.8|"
    StrAdd "300.0,1.365E-11,836.0|301.0,1.334E-11,836.1|302.0,1.303E-11,836.3|303.0,1.273E-11,836.4|304.0,1.244E-11,836.6|"
    StrAdd "305.0,1.216E-11,836.7|306.0,1.188E-11,836.8|307.0,1.161E-11,837.0|308.0,1.135E-11,837.1|309.0,1.109E-11,837.2|"
    StrAdd "310.0,1.084E-11,837.4|311.0,1.060E-11,837.5|312.0,1.036E-11,837.6|313.0,1.013E-11,837.7|314.0,9.903E-12,837.8|"
    StrAdd "315.0,9.682E-12,837.9|316.0,9.466E-12,838.1|317.0,9.256E-12,838.2|318.0,9.050E-12,838.3|319.0,8.850E-12,838.4|"
    StrAdd "320.0,8.654E-12,838.5|321.0,8.463E-12,838.6|322.0,8.277E-12,838.7|323.0,8.095E-12,838.8|324.0,7.917E-12,838.8|"
    StrAdd "325.0,7.743E-12,838.9|326.0,7.574E-12,839.0|327.0,7.409E-12,839.1|328.0,7.247E-12,839.2|329.0,7.089E-12,839.3|"
    StrAdd "330.0,6.935E-12,839.3|331.0,6.785E-12,839.4|332.0,6.638E-12,839.5|333.0,6.494E-12,839.6|334.0,6.354E-12,839.6|"
    StrAdd "335.0,6.217E-12,839.7|336.0,6.084E-12,839.8|337.0,5.953E-12,839.9|338.0,5.825E-12,839.9|339.0,5.701E-12,840.0|"
    StrAdd "340.0,5.579E-12,840.0|341.0,5.460E-12,840.1|342.0,5.343E-12,840.2|343.0,5.229E-12,840.2|344.0,5.118E-12,840.3|"
    StrAdd "345.0,5.010E-12,840.3|346.0,4.904E-12,840.4|347.0,4.800E-12,840.4|348.0,4.698E-12,840.5|349.0,4.599E-12,840.5|"
    StrAdd "350.0,4.502E-12,840.6|351.0,4.408E-12,840.6|352.0,4.315E-12,840.7|353.0,4.225E-12,840.7|354.0,4.136E-12,840.8|"
    StrAdd "355.0,4.050E-12,840.8|356.0,3.965E-12,840.9|357.0,3.882E-12,840.9|358.0,3.801E-12,841.0|359.0,3.722E-12,841.0|"
    StrAdd "360.0,3.645E-12,841.0|361.0,3.569E-12,841.1|362.0,3.495E-12,841.1|363.0,3.423E-12,841.1|364.0,3.352E-12,841.2|"
    StrAdd "365.0,3.283E-12,841.2|366.0,3.215E-12,841.2|367.0,3.149E-12,841.3|368.0,3.085E-12,841.3|369.0,3.021E-12,841.3|"
    StrAdd "370.0,2.959E-12,841.4|371.0,2.899E-12,841.4|372.0,2.839E-12,841.4|373.0,2.781E-12,841.5|374.0,2.724E-12,841.5|"
    StrAdd "375.0,2.669E-12,841.5|376.0,2.615E-12,841.5|377.0,2.561E-12,841.6|378.0,2.509E-12,841.6|379.0,2.459E-12,841.6|"
    StrAdd "380.0,2.409E-12,841.7|381.0,2.360E-12,841.7|382.0,2.312E-12,841.7|383.0,2.266E-12,841.7|384.0,2.220E-12,841.7|"
    StrAdd "385.0,2.175E-12,841.8|386.0,2.131E-12,841.8|387.0,2.089E-12,841.8|388.0,2.047E-12,841.8|389.0,2.006E-12,841.9|"
    StrAdd "390.0,1.965E-12,841.9|391.0,1.926E-12,841.9|392.0,1.888E-12,841.9|393.0,1.850E-12,841.9|394.0,1.813E-12,841.9|"
    StrAdd "395.0,1.777E-12,842.0|396.0,1.742E-12,842.0|397.0,1.707E-12,842.0|398.0,1.673E-12,842.0|399.0,1.640E-12,842.0|"
    StrAdd "400.0,1.607E-12,842.0|401.0,1.576E-12,842.1|402.0,1.545E-12,842.1|403.0,1.514E-12,842.1|404.0,1.484E-12,842.1|"
    StrAdd "405.0,1.455E-12,842.1|406.0,1.426E-12,842.1|407.0,1.398E-12,842.1|408.0,1.371E-12,842.2|409.0,1.344E-12,842.2|"
    StrAdd "410.0,1.318E-12,842.2|411.0,1.292E-12,842.2|412.0,1.267E-12,842.2|413.0,1.242E-12,842.2|414.0,1.218E-12,842.2|"
    StrAdd "415.0,1.194E-12,842.2|416.0,1.171E-12,842.3|417.0,1.148E-12,842.3|418.0,1.126E-12,842.3|419.0,1.104E-12,842.3|"
    StrAdd "420.0,1.082E-12,842.3|421.0,1.062E-12,842.3|422.0,1.041E-12,842.3|423.0,1.021E-12,842.3|424.0,1.001E-12,842.3|"
    StrAdd "425.0,9.819E-13,842.3|426.0,9.630E-13,842.4|427.0,9.445E-13,842.4|428.0,9.264E-13,842.4|429.0,9.086E-13,842.4|"
    StrAdd "430.0,8.912E-13,842.4|431.0,8.741E-13,842.4|432.0,8.574E-13,842.4|433.0,8.410E-13,842.4|434.0,8.250E-13,842.4|"
    StrAdd "435.0,8.092E-13,842.4|436.0,7.938E-13,842.4|437.0,7.787E-13,842.4|438.0,7.639E-13,842.4|439.0,7.495E-13,842.4|"
    StrAdd "440.0,7.353E-13,842.5|441.0,7.213E-13,842.5|442.0,7.077E-13,842.5|443.0,6.943E-13,842.5|444.0,6.812E-13,842.5|"
    StrAdd "445.0,6.684E-13,842.5|446.0,6.558E-13,842.5|447.0,6.435E-13,842.5|448.0,6.314E-13,842.5|449.0,6.196E-13,842.5|"
    StrAdd "450.0,6.080E-13,842.5|451.0,5.965E-13,842.5|452.0,5.854E-13,842.5|453.0,5.744E-13,842.5|454.0,5.637E-13,842.5|"
    StrAdd "455.0,5.532E-13,842.5|456.0,5.429E-13,842.5|457.0,5.329E-13,842.5|458.0,5.230E-13,842.5|459.0,5.133E-13,842.6|"
    StrAdd "460.0,5.038E-13,842.6|461.0,4.945E-13,842.6|462.0,4.853E-13,842.6|463.0,4.764E-13,842.6|464.0,4.676E-13,842.6|"
    StrAdd "465.0,4.590E-13,842.6|466.0,4.506E-13,842.6|467.0,4.423E-13,842.6|468.0,4.342E-13,842.6|469.0,4.263E-13,842.6|"
    StrAdd "470.0,4.185E-13,842.6|471.0,4.109E-13,842.6|472.0,4.034E-13,842.6|473.0,3.960E-13,842.6|474.0,3.888E-13,842.6|"
    StrAdd "475.0,3.818E-13,842.6|476.0,3.749E-13,842.6|477.0,3.681E-13,842.6|478.0,3.614E-13,842.6|479.0,3.549E-13,842.6|"
    StrAdd "480.0,3.485E-13,842.6|481.0,3.422E-13,842.6|482.0,3.361E-13,842.6|483.0,3.300E-13,842.6|484.0,3.241E-13,842.6|"
    StrAdd "485.0,3.183E-13,842.6|486.0,3.126E-13,842.6|487.0,3.071E-13,842.6|488.0,3.016E-13,842.6|489.0,2.962E-13,842.6|"
    StrAdd "490.0,2.910E-13,842.6|491.0,2.858E-13,842.6|492.0,2.807E-13,842.6|493.0,2.758E-13,842.6|494.0,2.709E-13,842.7|"
    StrAdd "495.0,2.661E-13,842.7|496.0,2.614E-13,842.7|497.0,2.569E-13,842.7|498.0,2.523E-13,842.7|499.0,2.479E-13,842.7|"
    StrAdd "500.0,2.436E-13,842.7|"
    GetAir Earth.AirCurve, Earth.GasConstant, Earth.HeatRatio, Replace(StrAdd, "|", vbCrLf)
    End Sub

'Sub SaveAir()
'    Dim H As Double, D As Double, P As Double, T As Double, S As String
'    'Open "..\Data\Earth.air" For Input As 1
'    Open "..\Data\Mars.air.new" For Output As 2
'    'Line Input #1, S: Print #2, S '
'    Print #2, "001.0,192.1,Mars,0N,0E,2000-01-01,12:00:00"
'    For H = 0 To 600000.1 Step 1000
'        'If EOF(1) Then Exit For
'        'Line Input #1, S: Print #2, Mid(S, 7)
'        'If Val(Left(S, 5)) <> H Then Stop
'        If H > 111000 Then T = 3.33 _
'        Else If H > 7000 Then T = 249.75 - 0.00222 * H _
'        Else T = 242.15 - 0.000998 * H
'        P = 699 * Exp(-0.00009 * H): D = P / 192.1 / T
'        'Format(12345.678, "0.00000e+00") = "1.23457e+04" .. '+'=use sign always '-'=use sign only when needed
'        Print #2, NumX(H / 1000, "000.0"); ","; Format(D, "0.000E+00"); ","; NumX(T, "000.0")
'        Next
'    Close
'    End Sub

Sub DownloadAtmosphere(Optional ByVal Update As Boolean = True)
    Dim C As Long, D As Long, N As Long, U As String, URL As String, File As String, _
        Year As String, Month As String, Day As String, Hour As String, _
        Fore As String, Name As String, Time As String, Historic As Boolean
    Static IsBusy As Boolean
    If Note.Active Then Exit Sub
    If Not IsBusy Then IsBusy = True Else Exit Sub
    U = "Downloading atmospheric model, please wait …"
    On Error Resume Next
    
    With Main.Download
    For D = 1 To 5
        Name = GRIBTimeFore(Year, Month, Day, Hour, Fore, D < 5, Historic)
        URL = InfoName("AtmosphereFile" & IfText(D >= 2, CStr(D)))
        If URL = "" Then Message: GoTo Ext
        If Historic And InStr(URL, "{DD}") = 0 Then GoTo Nxt
        URL = Replace(Replace(Replace(Replace(Replace( _
            URL, "{YYYY}", Year), "{MM}", Month), "{DD}", Day), "{HH}", Hour), "{FF}", Fore)
        File = AtmosphereDir & Name & "." & FileFromURL(URL)
        If FileExists(File) Then If MsgBox("The atmospheric model already exists. Overwrite it?", _
            vbQuestion Or vbOKCancel, AppName) <> vbOK Then Message: GoTo Ext
        Message U, , True
        .Begin URL, File, True, True
        Do: DoEvents: Sleep 1
            If Not Note.Active Then .Cancel File: Message "Download aborted", 2000: GoTo Ext
            C = Int(CDbl(.BytesRead) * 100 / .BytesMax): If N <> C Then N = C: _
                Message U & " (" & N & "%)", , True
            Loop While .IsBusy
        If Not .IsError Then Exit For
Nxt:
        Next
    If .IsError Then Message "Atmospheric model not found", 2000: GoTo Ext
    End With
    
    If Update And (AtmMode > 0 Or (Tool And ToolAtmosphere) <> 0) Then
        If Not GRIBLoad(True) Then Message "Error loading atmospheric model", 2000
    Else
        Message "Atmospheric model updated", 2000
        End If
    On Error GoTo 0
    If Update Then
        If AtmMode > 0 Then
            If T = 0 Then Init
        Else
            If Tool And ToolAtmosphere Then If Play = Paused Then Render
            End If
        End If
Ext:
    IsBusy = False
    End Sub

Function GRIBTimeFore(Optional Year As String, Optional Month As String, Optional Day As String, _
        Optional Hour As String, Optional Fore As String, Optional ByVal Delay As Long, _
        Optional Historic As Boolean) As String
    Dim R As Double, T As Double, U As String, _
        F As Long, Y As Long, M As Long, D As Long, H As Long
    T = Round((ValDateEx(Setup.Date) + ValTimeEx(Setup.Time)) / Hour3) * Hour3
    R = Int(J2ksNow / Hour6 + Delay) * Hour6
    Historic = T <= R: If Historic Then R = Int(T / Hour6) * Hour6
    F = Round((T - R) / Hour3) * 3
    J2ksToDate T, Y, M, D, H
    Year = Format(Y, "0000"): Month = Format(M, "00"): Day = Format(D, "00"): Hour = Format(H, "00")
    Fore = Format(F, "000"): GRIBTimeFore = Year & Month & Day & "." & Hour & "f" & Fore
    J2ksToDate R, Y, M, D, H
    Year = Format(Y, "0000"): Month = Format(M, "00"): Day = Format(D, "00"): Hour = Format(H, "00")
    Fore = Format(F, "00")
    End Function

Function GRIBTime() As String
    Dim Hour3 As Double, T As Double, U As String, Y As Long, M As Long, D As Long, H As Long
    Hour3 = 3 * Hour: T = Round((ValDateEx(Setup.Date) + ValTimeEx(Setup.Time)) / Hour3) * Hour3
    J2ksToDate T, Y, M, D, H
    GRIBTime = Format(Y, "0000") & Format(M, "00") & Format(D, "00") & "." & Format(H, "00"):
    End Function

Function GRIBLoad(Optional ByVal Update As Boolean, _
        Optional ByVal ShowMsg As Boolean = True) As Boolean
    Dim C As Long, D As Long, E As Double, F As Double, G As Double, J As Single, _
        I As Long, W As Long, H As Long, _
        X As Long, Y As Long, Z As Long, _
        S As String, T As String, Time As String, File As String
    Time = GRIBTime
    File = Dir(AtmosphereDir & Time & "*")
    If File <> "" Then
        D = InfiniteInt: S = File
        Do
            C = Val(Mid(S, 13, 3)): If C < D Then D = C: File = S
            S = Dir
            Loop While S <> ""
        GoTo Nxt
        End If
    File = Dir(AtmosphereDir & "*")
    If File <> "" Then
        F = Infinite: G = ValShortDateTimeEx(Time): S = File
        Do
            E = Abs(ValShortDateTimeEx(S) - G) + Val(Mid(S, 13, 3)) / 1000
            If E <= F Then F = E: File = S
            S = Dir
            Loop While S <> ""
        GoTo Nxt
        End If
    GoTo Ext
Nxt:
    If AtmN > 0 And File = AtmFile Then GoTo Ok
    If Update Then If Cam1.hRC Then DeleteList Earth.Atm: DeleteList Earth.AtmFlat
    S = GRIBInventory(AtmosphereDir & File): If S = "" Then GoTo Ext
    If ShowMsg Then Message "Loading atmospheric model …": DoEvents
    On Error GoTo Ext
    Open AtmosphereDir & File For Binary As 1
    Options.Layer.List = ""
    AtmN = 1: ReDim AtmP(AtmN): AtmP(0) = AirP / 100
    For C = 1500 To 0 Step -5
        If InStr(S, ":HGT:ISBL:" & C & "mb:") Then
            If AtmN > UBound(AtmP) Then ReDim Preserve AtmP(AtmN)
            AtmP(AtmN) = C: AtmN = AtmN + 1
            End If
        Next
    AtmN = AtmN - 1: If AtmN <= 0 Then GoTo Ext
    W = 360: H = 181: I = W * H
    AtmL1 = 30000: AtmL2 = 40000: AtmL = 200: AtmM = AtmL1 \ AtmL
    ReDim AtmC(AtmN), AtmD(AtmN), _
        AtmR(W - 1, H - 1, AtmM), AtmX(W - 1, H - 1, AtmN), _
        AtmY(W - 1, H - 1, AtmN), AtmZ(W - 1, H - 1, AtmN), _
        AtmA(W - 1, H - 1, AtmN), AtmT(W - 1, H - 1, AtmN), _
        AtmH(W - 1, H - 1, AtmN), AtmW(W - 1, H - 1, AtmN) ', _
        AtmQ(W - 1, H - 1, AtmN), AtmO(W - 1, H - 1, AtmN)
    'P = 101325 * (1 - 0.0000225577 * H) ^ 5.25588
    'H = (1 - (P / 101325) ^ (1 / 5.25588)) / 0.0000225577
    'H = ((((P / 10 / 101.29) ^ (1 / 5.256)) * 288.08) - 273.15 - 15.04) / (-0.00649)
    For C = AtmN To 0 Step -1
        AtmC(C) = (1 - (AtmP(C) / AtmP(0)) ^ (1 / 5.25588)) / 0.0000225577
        AtmD(C) = (1 - ((AtmP(C) + 0.005) / AtmP(0)) ^ (1 / 5.25588)) / 0.0000225577 - _
                  (1 - ((AtmP(C) - 0.005) / AtmP(0)) ^ (1 / 5.25588)) / 0.0000225577
        If C > 0 Then
            If GRIBData(S, ":HGT:ISBL:" & AtmP(C) & "mb:", I, AtmA(0, 0, C)) Then GoTo Ext   ' 10-1000mb
            If GRIBData(S, ":TMP:ISBL:" & AtmP(C) & "mb:", I, AtmT(0, 0, C)) Then GoTo Ext   ' 10-1000mb
            If GRIBData(S, ":RH:ISBL:" & AtmP(C) & "mb:", I, AtmH(0, 0, C)) Then GoTo Ext    ' 10-1000mb
            If GRIBData(S, ":VVEL:ISBL:" & AtmP(C) & "mb:", I, AtmZ(0, 0, C)) Then GoTo Ext  '100-1000mb
            If GRIBData(S, ":UGRD:ISBL:" & AtmP(C) & "mb:", I, AtmX(0, 0, C)) Then GoTo Ext  ' 10-1000mb
            If GRIBData(S, ":VGRD:ISBL:" & AtmP(C) & "mb:", I, AtmY(0, 0, C)) Then GoTo Ext  ' 10-1000mb
            If GRIBData(S, ":CLWMR:ISBL:" & AtmP(C) & "mb:", I, AtmW(0, 0, C)) Then GoTo Ext '100-1000mb
            'If GRIBData(S, ":ABSV:ISBL:" & AtmP(C) & "mb:", I, AtmQ(0, 0, C)) Then GoTo Ext  ' 10-1000mb
            'If GRIBData(S, ":O3MR:ISBL:" & AtmP(C) & "mb:", I, AtmO(0, 0, C)) Then GoTo Ext  ' 10- 100mb
        Else
            GRIBData S, ":HGT:SFC:0:", I, AtmA(0, 0, 0)
            GRIBData S, ":TMP:SFC:0:", I, AtmT(0, 0, 0)
            GRIBData S, ":RH:HTGL:2m:", I, AtmH(0, 0, 0)
            'GRIBData S, ":VVEL:HTGL:10m:", I, AtmZ(0, 0, 0)
            GRIBData S, ":UGRD:HTGL:10m:", I, AtmX(0, 0, 0)
            GRIBData S, ":VGRD:HTGL:10m:", I, AtmY(0, 0, 0)
            GRIBData S, ":CWAT:EATM:0:", I, AtmW(0, 0, 0)
            'GRIBData S, ":ABSV:HTGL:10m:", I, AtmQ(0, 0, 0)
            'GRIBData S, ":O3MR:HTGL:10m:", I, AtmO(0, 0, 0)
            End If
        Next
    For Y = 0 To H - 1
        For X = 0 To W - 1
            C = 1: D = 0: J = AtmA(X, Y, C)
            For Z = 0 To AtmM
                Do While J <= D And C < AtmN
                    C = C + 1: J = AtmA(X, Y, C)
                    Loop
                If C > 1 Then AtmR(X, Y, Z) = C - 1 Else AtmR(X, Y, Z) = C
                D = D + AtmL
                Next
            Next
        Next
    Close
    AtmFile = File: AtmDate = Setup.Date: AtmTime = Setup.Time
Ok:
    GRIBLoad = True
    If ShowMsg Then Message
    Exit Function
Ext:
    GRIBDelete
    Close
    If ShowMsg Then Message
    End Function

Sub GRIBDelete()
    AtmN = 0: AtmFile = "": AtmDate = "": AtmTime = ""
    Erase AtmP, AtmC, AtmD, AtmR, AtmX, AtmY, AtmZ, AtmA, AtmT, AtmH, AtmW ', AtmQ, AtmO
    End Sub

Function GRIBInventory(File As String) As String
    Dim G As GRIB
    On Error GoTo Ext
    If Not FileExists(File) Then GoTo Ext
    Open File For Binary As 1
    With G
    .N = 1: .Pos = 0: ReDim .S(80 - 1)
    Do While .Pos < LOF(1)
        Get 1, .Pos + 1, .S: If Not GRIBParse(G) Then StrAdd: GoTo Ext
        StrAdd ":" & .ParamID & ":" & .LayerID & ":" & .LevelID & ":" & .Pos & ":" & .Size & vbCrLf
        .Pos = .Pos + .Size: .N = .N + 1
        Loop
    End With
    GRIBInventory = StrAdd
Ext:
    Close
    End Function

Function GRIBParse(G As GRIB) As Boolean
    With G
    If .S(0) <> Asc("G") Or .S(1) <> Asc("R") Or .S(2) <> Asc("I") Or .S(3) <> Asc("B") _
        Or .S(7) <> 1 Then Exit Function
    .Size = CLng(.S(4)) * &H10000 + CLng(.S(5)) * &H100& + CLng(.S(6))
    
    .Param = .S(8 + 8): .Layer = .S(8 + 9)
    .Level1 = .S(8 + 10) ': If .Level1 And &H80& Then .Level1 = -(.Level1 And Not &H80&)
    .Level2 = .S(8 + 11) ': If .Level2 And &H80& Then .Level2 = -(.Level2 And Not &H80&)
    .Level = .Level1 * &H100& + .Level2
    If .Level And &H8000& Then .Level = -(.Level And Not &H8000&)
    
    .Year = (.S(8 + 24) - 1) * 100 + .S(8 + 12): .Month = .S(8 + 13)
    .Day = .S(8 + 14): .Hour = .S(8 + 15)
    .Forecast = 0: If .S(8 + 17) = 1 Then If .S(8 + 20) = 2 Then .Forecast = .S(8 + 18) _
        Else If .S(8 + 20) = 10 Then .Forecast = .S(8 + 18) * &H100& + .S(8 + 19)
    
    Select Case .S(8 + 6)
    Case 2:     .NData = 10512: .Width = 144:  .Height = 73: .Scale = 2.5
    Case 3:     .NData = 65160: .Width = 360: .Height = 181: .Scale = 1
    Case 4:    .NData = 259920: .Width = 720: .Height = 361: .Scale = 0.5
    Case Else: .NData = 0: .Width = 0: .Height = 0: .Scale = 0
        End Select
    
    Select Case .Param
    Case 1:    .ParamID = "PRES"  'Pressure                                   Pa
    Case 2:    .ParamID = "PRMSL" 'Pressure reduced to mean sea level         Pa
    Case 5:    .ParamID = "ICAHT" 'ICAO standard atmosphere reference height  m
    Case 7:    .ParamID = "HGT"   'Geopotential height                        gpm
    Case 10:   .ParamID = "TOZNE" 'Total ozone                                Dobson
    Case 11:   .ParamID = "TMP"   'Temperature                                K
    Case 13:   .ParamID = "POT"   'Potential temperature                      K
    Case 27:   .ParamID = "GPA"   'Geopotential height anomaly                gpm
    Case 33:   .ParamID = "UGRD"  'U-component of wind                        m/s
    Case 34:   .ParamID = "VGRD"  'V-component of wind                        m/s
    Case 39:   .ParamID = "VVEL"  'Vertical velocity (pressure)               Pa/s
    Case 41:   .ParamID = "ABSV"  'Absolute vorticity                         1/s
    Case 51:   .ParamID = "SPFH"  'Specific humidity                          kg/kg
    Case 52:   .ParamID = "RH"    'Relative humidity                          %
    Case 54:   .ParamID = "PWAT"  'Precipitable water                         kg/m2
    Case 65:   .ParamID = "WEASD" 'Water equivalent of accumulated snow depth kg/m2
    Case 71:   .ParamID = "TCDC"  'Total cloud cover                          %
    Case 76:   .ParamID = "CWAT"  'Cloud water                                kg/m2
    Case 81:   .ParamID = "LAND"  'Land cover (land=1, sea=0)                 proportion
    Case 91:   .ParamID = "ICEC"  'Ice cover (ice=1, no ice=0)                proportion
    Case 130:  .ParamID = "MSLET" 'Mean sea level pressure (ETA model)        Pa
    Case 131:  .ParamID = "LFTX"  'Surface lifted index                       K
    Case 132:  .ParamID = "4LFTX" 'Best (4 layer) lifted index                K
    Case 136:  .ParamID = "VWSH"  'Vertical speed shear                       1/s
    Case 141:  .ParamID = "PLPL"  'Pressure of level from which parcel was lifted  Pa
    Case 144:  .ParamID = "SOILW" 'Volumetric soil moisture                   fraction
    Case 153:  .ParamID = "CLWMR" 'Cloud water                                kg/kg
    Case 154:  .ParamID = "O3MR"  'Ozone mixing ratio                         kg/kg
    Case 156:  .ParamID = "CIN"   'Convective inhibition                      J/kg
    Case 157:  .ParamID = "CAPE"  'Convective available potential energy      J/kg
    Case 180:  .ParamID = "GUST"  'Surface wind gust                          m/s
    Case 190:  .ParamID = "HLCY"  'Storm relative helicity                    m2/s2
    Case 191:  .ParamID = "SUNSD" 'Sunshine Duration                          s
    Case 196:  .ParamID = "USTM"  'U-component of storm motion                m/s
    Case 197:  .ParamID = "VSTM"  'V-component of storm motion                m/s
    Case 219:  .ParamID = "WILT"  'Wilting point                              fraction
    Case 220:  .ParamID = "FLDCP" 'Field Capacity                             fraction
    Case 221:  .ParamID = "HPBL"  'Planetary boundary layer height            m
    Case 222:  .ParamID = "5WAVH" '5-wave geopotential height                 gpm
    Case 230:  .ParamID = "5WAVA" '5-wave geopotential height anomaly         gpm
    Case 240:  .ParamID = "RETOP" 'Radar Echo Top (18.3 DBZ)                  m
    Case 241:  .ParamID = "LRGHR" 'Large scale condensate heat rate           K/s
    Case 250:  .ParamID = "SWHR"  'Solar radiative heating rate               K/s
    Case Else: .ParamID = "#" & .Param 'Unknown
        End Select

    .LevelID = "0"
    Select Case .Layer
    Case 1:    .LayerID = "SFC"   'Ground or water surface
    Case 4:    .LayerID = "0DEG"  '.Level of 0 deg (C) isotherm
    Case 6:    .LayerID = "MWSL"  'Maximum wind level
    Case 7:    .LayerID = "TRO"   'Tropopause
    Case 100:  .LayerID = "ISBL"  'Isobaric level : pressure in hectoPascals [hPa] (2 octets)
               .LevelID = .Level & "mb"
    Case 102:  .LayerID = "MSL"   'Mean sea level
    Case 103:  .LayerID = "GPML"  'Specified altitude above mean sea level : altitude in meters [m] (2 octets)
               .LevelID = .Level & "m"
    Case 105:  .LayerID = "HTGL"  'Specified height level above ground : height in meters [m] (2 octets)
               .LevelID = .Level & "m"
    Case 106:  .LayerID = "HTGY"  '.Layer between two specified height levels above ground : height of top [hm] - height of bottom [hm]
               .LevelID = .Level1 * 100 & "-" & .Level2 * 100 & "m"
    Case 107:  .LayerID = "SIGL"  'Sigma level : sigma value in 1/10000 (2 octets)
               .LevelID = NumX(.Level / 10000, FQuad)
    Case 108:  .LayerID = "SIGY"  '.Layer between two sigma levels : sigma value at top in 1/100 - sigma value at bottom in 1/100
               .LevelID = NumX(.Level1 / 100, "0.00") & "-" & NumX(.Level2 / 100, "0.00")
    Case 112:  .LayerID = "DBLY"  '.Layer between two depths below land surface : depth of upper surface [cm] - depth of lower surface [cm]
               .LevelID = .Level1 & "-" & .Level2 & "cm"
    Case 116:  .LayerID = "SPDY"  '.Layer between two levels at specified pressure difference from ground to level : pressure difference from ground to top level in [hPa] - pressure difference from ground to bottom level in [hPa]
               .LevelID = .Level1 & "-" & .Level2 & "mb"
    Case 117:  .LayerID = "PVL"   'Potential vorticity (pv) : surface pv value in units of 10-9Km2/kgs (2 octets)
               .LevelID = .Level & "pv"
    Case 200:  .LayerID = "EATM"  'Entire atmosphere (considered as a single layer)
    Case 204:  .LayerID = "HTFL"  'Highest tropospheric freezing level
    Case 220:  .LayerID = "PBLRI" 'Planetary Boundary .Layer (derived from Richardson number)
    Case 244:  .LayerID = "CCY"   'Convective cloud layer
    Case Else: .LayerID = "#" & .Layer 'Unknown
               .LevelID = .Level1 & "|" & .Level2
        End Select
    
    End With
    GRIBParse = True
    End Function

Function GRIBData(S As String, L As String, I As Long, A As Single, _
        Optional ByVal N As Single) As Boolean
    Dim B As Long, E As Long, F As Long, M() As Byte
    B = InStr(S, L): If B Then B = B + Len(L) Else GRIBFill I, A, N: Exit Function
    E = NumF(S, B): F = NumF(S, B)
    ReDim M(F - 1): Get 1, E + 1, M
    GRIBData = GRIBUnPack(M(0), I, A)
    End Function

Sub AtmEarth(K As KMLPoint)
    Dim C1 As Long, A1 As Double, G1 As Double, H1 As Double, _
        X1 As Long, Y1 As Long, Z1 As Long, _
        X2 As Long, Y2 As Long, Z2 As Long, _
        Z4 As Long, R1 As Double, R2 As Double, _
        RX1 As Double, RY1 As Double, R11 As Double, R12 As Double, _
        RX2 As Double, RY2 As Double, R21 As Double, R22 As Double
    
    If AtmMode <= 0 Or AtmN <= 0 Then Exit Sub
    
    If AtmMode = 1 Then 'No interpolation
        X1 = Int(K.Lon / Rad + 0.5): If X1 < 0 Then X1 = X1 + 360
        Y1 = 90 - Int(K.Lat / Rad + 0.5)
        Z1 = Int(K.Alt / AtmL + 0.5): If Z1 < 0 Then Z1 = 0 Else If Z1 > AtmM Then Z1 = AtmM
        Z1 = AtmR(X1, Y1, Z1)
        K.Pressure = AtmP(Z1) * 100
        K.Temperature = AtmT(X1, Y1, Z1)
        K.Humidity = AtmH(X1, Y1, Z1)
        K.CloudWater = AtmW(X1, Y1, Z1)
        K.WindX = AtmX(X1, Y1, Z1)
        K.WindY = AtmY(X1, Y1, Z1)
        K.WindZ = AtmZ(X1, Y1, Z1) * AtmD(Z1)
        
    ElseIf AtmMode = 2 Then '1D-Z interpolation
        X1 = Int(K.Lon / Rad + 0.5): If X1 < 0 Then X1 = X1 + 360
        Y1 = 90 - Int(K.Lat / Rad + 0.5)
        Z1 = Int(K.Alt / AtmL): If Z1 < 0 Then Z1 = 0 Else If Z1 > AtmM Then Z1 = AtmM
        Z1 = AtmR(X1, Y1, Z1): Z2 = Z1 + 1: G1 = AtmA(X1, Y1, Z2)
        If Z2 >= AtmN And K.Alt > G1 Then
            R1 = G1 / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): H1 = (.P + .P2 * R1) / AtmP(Z2) / 100
            A1 = (.T + .T2 * R1) / AtmT(X1, Y1, Z2): End With
            R1 = K.Alt / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): K.Pressure = (.P + .P2 * R1) / H1
            K.Temperature = (.T + .T2 * R1) / A1: End With
            R1 = 0: If K.Alt < AtmL2 Then R2 = 1 - (K.Alt - G1) / (AtmL2 - G1) Else R2 = 0
        Else
            R1 = AtmA(X1, Y1, Z1): R2 = (K.Alt - R1) / (G1 - R1): R1 = 1 - R2
            K.Pressure = (AtmP(Z1) * R1 + AtmP(Z2) * R2) * 100
            K.Temperature = AtmT(X1, Y1, Z1) * R1 + AtmT(X1, Y1, Z2) * R2
            End If
        K.Humidity = AtmH(X1, Y1, Z1) * R1 + AtmH(X1, Y1, Z2) * R2
        K.CloudWater = AtmW(X1, Y1, Z1) * R1 + AtmW(X1, Y1, Z2) * R2
        K.WindX = AtmX(X1, Y1, Z1) * R1 + AtmX(X1, Y1, Z2) * R2
        K.WindY = AtmY(X1, Y1, Z1) * R1 + AtmY(X1, Y1, Z2) * R2
        K.WindZ = AtmZ(X1, Y1, Z1) * AtmD(Z1) * R1 + AtmZ(X1, Y1, Z2) * AtmD(Z2) * R2
        
    Else '3D-XYZ interpolation
        RX2 = K.Lon / Rad: X1 = Int(RX2): RX2 = RX2 - X1: RX1 = 1 - RX2: If X1 < 0 Then X1 = X1 + 360
        X2 = X1 + 1: If X2 >= 360 Then X2 = X2 - 360
        RY2 = K.Lat / Rad: Y1 = Int(RY2): RY2 = RY2 - Y1: RY1 = 1 - RY2: Y1 = 90 - Y1
        Y2 = Y1 - 1: If Y2 < -90 Then Y2 = -90
        R11 = RX1 * RY1: R12 = RX1 * RY2: R21 = RX2 * RY1: R22 = RX2 * RY2
        Z4 = Int(K.Alt / AtmL): If Z4 < 0 Then Z4 = 0 Else If Z4 > AtmM Then Z4 = AtmM
        
        Z1 = AtmR(X1, Y1, Z4): Z2 = Z1 + 1: G1 = AtmA(X1, Y1, Z2)
        If Z2 >= AtmN And K.Alt > G1 Then
            R1 = G1 / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): H1 = (.P + .P2 * R1) / AtmP(Z2) / 100
            A1 = (.T + .T2 * R1) / AtmT(X1, Y1, Z2): End With
            R1 = K.Alt / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): K.Pressure = (.P + .P2 * R1) / H1 * R11
            K.Temperature = (.T + .T2 * R1) / A1 * R11: End With
            R1 = 0: If K.Alt < AtmL2 Then R2 = 1 - (K.Alt - G1) / (AtmL2 - G1) Else R2 = 0
        Else
            R1 = AtmA(X1, Y1, Z1): R2 = (K.Alt - R1) / (G1 - R1): R1 = 1 - R2
            K.Pressure = ((AtmP(Z1) * R1 + AtmP(Z2) * R2) * 100) * R11
            K.Temperature = (AtmT(X1, Y1, Z1) * R1 + AtmT(X1, Y1, Z2) * R2) * R11
            End If
        K.Humidity = (AtmH(X1, Y1, Z1) * R1 + AtmH(X1, Y1, Z2) * R2) * R11
        K.CloudWater = (AtmW(X1, Y1, Z1) * R1 + AtmW(X1, Y1, Z2) * R2) * R11
        K.WindX = (AtmX(X1, Y1, Z1) * R1 + AtmX(X1, Y1, Z2) * R2) * R11
        K.WindY = (AtmY(X1, Y1, Z1) * R1 + AtmY(X1, Y1, Z2) * R2) * R11
        K.WindZ = (AtmZ(X1, Y1, Z1) * AtmD(Z1) * R1 + AtmZ(X1, Y1, Z2) * AtmD(Z2) * R2) * R11
        
        Z1 = AtmR(X2, Y1, Z4): Z2 = Z1 + 1: G1 = AtmA(X2, Y1, Z2)
        If Z2 >= AtmN And K.Alt > G1 Then
            R1 = G1 / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): H1 = (.P + .P2 * R1) / AtmP(Z2) / 100
            A1 = (.T + .T2 * R1) / AtmT(X2, Y1, Z2): End With
            R1 = K.Alt / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): K.Pressure = K.Pressure + (.P + .P2 * R1) / H1 * R21
            K.Temperature = K.Temperature + (.T + .T2 * R1) / A1 * R21: End With
            R1 = 0: If K.Alt < AtmL2 Then R2 = 1 - (K.Alt - G1) / (AtmL2 - G1) Else R2 = 0
        Else
            R1 = AtmA(X2, Y1, Z1): R2 = (K.Alt - R1) / (G1 - R1): R1 = 1 - R2
            K.Pressure = K.Pressure + ((AtmP(Z1) * R1 + AtmP(Z2) * R2) * 100) * R21
            K.Temperature = K.Temperature + (AtmT(X2, Y1, Z1) * R1 + AtmT(X2, Y1, Z2) * R2) * R21
            End If
        K.Humidity = K.Humidity + (AtmH(X2, Y1, Z1) * R1 + AtmH(X2, Y1, Z2) * R2) * R21
        K.CloudWater = K.CloudWater + (AtmW(X2, Y1, Z1) * R1 + AtmW(X2, Y1, Z2) * R2) * R21
        K.WindX = K.WindX + (AtmX(X2, Y1, Z1) * R1 + AtmX(X2, Y1, Z2) * R2) * R21
        K.WindY = K.WindY + (AtmY(X2, Y1, Z1) * R1 + AtmY(X2, Y1, Z2) * R2) * R21
        K.WindZ = K.WindZ + (AtmZ(X2, Y1, Z1) * AtmD(Z1) * R1 + AtmZ(X2, Y1, Z2) * AtmD(Z2) * R2) * R21
        
        Z1 = AtmR(X1, Y2, Z4): Z2 = Z1 + 1: G1 = AtmA(X1, Y2, Z2)
        If Z2 >= AtmN And K.Alt > G1 Then
            R1 = G1 / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): H1 = (.P + .P2 * R1) / AtmP(Z2) / 100
            A1 = (.T + .T2 * R1) / AtmT(X1, Y2, Z2): End With
            R1 = K.Alt / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): K.Pressure = K.Pressure + (.P + .P2 * R1) / H1 * R12
            K.Temperature = K.Temperature + (.T + .T2 * R1) / A1 * R12: End With
            R1 = 0: If K.Alt < AtmL2 Then R2 = 1 - (K.Alt - G1) / (AtmL2 - G1) Else R2 = 0
        Else
            R1 = AtmA(X1, Y2, Z1): R2 = (K.Alt - R1) / (G1 - R1): R1 = 1 - R2
            K.Pressure = K.Pressure + ((AtmP(Z1) * R1 + AtmP(Z2) * R2) * 100) * R12
            K.Temperature = K.Temperature + (AtmT(X1, Y2, Z1) * R1 + AtmT(X1, Y2, Z2) * R2) * R12
            End If
        K.Humidity = K.Humidity + (AtmH(X1, Y2, Z1) * R1 + AtmH(X1, Y2, Z2) * R2) * R12
        K.CloudWater = K.CloudWater + (AtmW(X1, Y2, Z1) * R1 + AtmW(X1, Y2, Z2) * R2) * R12
        K.WindX = K.WindX + (AtmX(X1, Y2, Z1) * R1 + AtmX(X1, Y2, Z2) * R2) * R12
        K.WindY = K.WindY + (AtmY(X1, Y2, Z1) * R1 + AtmY(X1, Y2, Z2) * R2) * R12
        K.WindZ = K.WindZ + (AtmZ(X1, Y2, Z1) * AtmD(Z1) * R1 + AtmZ(X1, Y2, Z2) * AtmD(Z2) * R2) * R12
        
        Z1 = AtmR(X2, Y2, Z4): Z2 = Z1 + 1: G1 = AtmA(X2, Y2, Z2)
        If Z2 >= AtmN And K.Alt > G1 Then
            R1 = G1 / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): H1 = (.P + .P2 * R1) / AtmP(Z2) / 100
            A1 = (.T + .T2 * R1) / AtmT(X2, Y2, Z2): End With
            R1 = K.Alt / 1000: C1 = Int(R1): R1 = R1 - C1
            With Earth.AirCurve(C1): K.Pressure = K.Pressure + (.P + .P2 * R1) / H1 * R22
            K.Temperature = K.Temperature + (.T + .T2 * R1) / A1 * R22: End With
            R1 = 0: If K.Alt < AtmL2 Then R2 = 1 - (K.Alt - G1) / (AtmL2 - G1) Else R2 = 0
        Else
            R1 = AtmA(X2, Y2, Z1): R2 = (K.Alt - R1) / (G1 - R1): R1 = 1 - R2
            K.Pressure = K.Pressure + ((AtmP(Z1) * R1 + AtmP(Z2) * R2) * 100) * R22
            K.Temperature = K.Temperature + (AtmT(X2, Y2, Z1) * R1 + AtmT(X2, Y2, Z2) * R2) * R22
            End If
        K.Humidity = K.Humidity + (AtmH(X2, Y2, Z1) * R1 + AtmH(X2, Y2, Z2) * R2) * R22
        K.CloudWater = K.CloudWater + (AtmW(X2, Y2, Z1) * R1 + AtmW(X2, Y2, Z2) * R2) * R22
        K.WindX = K.WindX + (AtmX(X2, Y2, Z1) * R1 + AtmX(X2, Y2, Z2) * R2) * R22
        K.WindY = K.WindY + (AtmY(X2, Y2, Z1) * R1 + AtmY(X2, Y2, Z2) * R2) * R22
        K.WindZ = K.WindZ + (AtmZ(X2, Y2, Z1) * AtmD(Z1) * R1 + AtmZ(X2, Y2, Z2) * AtmD(Z2) * R2) * R22
        
    'Else '4D-XYZT interpolation
        
        End If
    
    K.Density = K.Pressure / K.Temperature / Earth.GasConstant
    End Sub

Function AtmAlt(Optional Lat As Double, Optional Lon As Double) As Double
    If AtmN <= 0 Then Exit Function
    X0 = Round(Lon / Rad + 0.5): Y0 = 90 - Int(Lat / Rad + 0.5): If X0 < 0 Then X0 = X0 + 360
    AtmAlt = AtmA(X0, Y0, 0)
    End Function

Sub InitSockets()
    Message "Initializing network …"
    On Error Resume Next: WSAStartup &H101, WSAData: On Error GoTo 0
    End Sub

Sub InitGdiPlus()
    Message "Initializing graphics …"
    On Error Resume Next: Gp.GdiPlusVersion = 1: GdiplusStartup hGP, Gp: On Error GoTo 0
    End Sub

Sub LoadMagneticModel()
    Message "Loading magnetic model …"
    On Error Resume Next: WMMInit DataDir & "Wmm.cof": On Error GoTo 0
    Message
    End Sub

Function LoadTerrain() As Boolean
    Dim C As Long, M As Double, X As Long, Y As Long, NoError As Boolean
    On Error GoTo Ext
    OpenFile TerrainDir & "Terrain.bin": If FreeFile = 1 Then GoTo Ext
    Get 1, , Earth.TerrainN: C = Earth.TerrainN * 120 * 120 * 2: M = FreeMemory
    If M > 0 And M < C Then DoEvents: If MsgBox( _
        ToBytes(C) & " of free RAM memory is needed" & vbCrLf & _
        "to load the ""Terrain.bin"" file, continue?", _
        vbExclamation Or vbOKCancel, AppName _
        ) <> vbOK Then NoError = True: GoTo Ext
    ReDim Earth.TerrainP(360 - 1, 180 - 1): Get 1, , Earth.TerrainP
    ReDim Earth.Terrain(120 - 1, 120 - 1, 1 To Earth.TerrainN)
    If Earth.TerrainN > 0 Then _
        Message "Loading terrain …": DoEvents: Get 1, , Earth.Terrain
    Close
    ReDim Earth.Geoid(8640 - 1, 4320 + 1 - 1)
    OpenFile TerrainDir & "Geoid.bin": If FreeFile = 1 Then RtlFillMemory _
            Earth.Geoid(0, 0), (UBound(Earth.Geoid) + 1) * (UBound(Earth.Geoid, 2) + 1), 128 _
        Else Get 1, , Earth.Geoid: Close
    ReDim Moon.Terrain(5760 - 1, 2880 - 1): ReDim Mars.Terrain(5760 - 1, 2880 - 1)
    OpenFile TerrainDir & "Moon.bin": If FreeFile > 1 Then Get 1, , Moon.Terrain: Close
    OpenFile TerrainDir & "Mars.bin": If FreeFile > 1 Then Get 1, , Mars.Terrain: Close
    On Error GoTo 0
    LoadTerrain = True: Message: DoEvents
    Exit Function
Ext:
    If Not NoError And Main.Visible Then Message "Error loading terrain", 2000: DoEvents
    Close
    DeleteTerrain
    End Function
    
Sub DeleteTerrain()
    Earth.TerrainN = 0: Erase Earth.Terrain, Earth.TerrainP, Earth.Geoid, Moon.Terrain, Mars.Terrain
    FloorAltitude = 0: GeoidAltitude = 0
    End Sub

Function AltEarth(Optional Lat As Double, Optional Lon As Double) As Double
    Dim X1 As Long, Y1 As Long, N As Long
    If TerrainMode < 2 Then Exit Function
    X1 = Int((Lon / Rad + 180) * 120 + 0.5): If X1 < 0 Then X1 = X1 + 43200 Else If X1 >= 43200 Then X1 = X1 - 43200
    Y1 = Int((Lat / Rad + 90) * 120 + 0.5): If Y1 < 0 Then Y1 = 0 Else If Y1 >= 21600 Then Y1 = 21599
    AltEarth = CLng(Earth.Geoid(X1 \ 5, Y1 \ 5)) - 128
    N = Earth.TerrainP(X1 \ 120, Y1 \ 120)
    If N Then AltEarth = (AltEarth + Earth.Terrain(X1 Mod 120, Y1 Mod 120, N)) * TerrainScale _
    Else AltEarth = AltEarth * TerrainScale
    End Function
        
Function GeoidEarth(Optional Lat As Double, Optional Lon As Double) As Double
    Dim X1 As Long, Y1 As Long
    If TerrainMode < 2 Then Exit Function
    X1 = Int((Lon / Rad + 180) * 24 + 0.5): If X1 < 0 Then X1 = X1 + 8640 Else If X1 >= 8640 Then X1 = X1 - 8640
    Y1 = Int((Lat / Rad + 90) * 24 + 0.5): If Y1 < 0 Then Y1 = 0 Else If Y1 > 4320 Then Y1 = 4320
    GeoidEarth = (CDbl(Earth.Geoid(X1, Y1)) - 128) * TerrainScale
    End Function

Function AltMoon(Optional Lat As Double, Optional Lon As Double) As Double
    If TerrainMode < 2 Then Exit Function
    C1 = Int((Lon / Rad + 180) * 16 + 0.5): If C1 < 0 Then C1 = C1 + 5760 Else If C1 >= 5760 Then C1 = C1 - 5760
    D1 = Int((Lat / Rad + 90) * 16 + 0.5): If D1 < 0 Then D1 = 0 Else If D1 >= 2880 Then D1 = 2879
    AltMoon = Moon.Terrain(C1, D1) * TerrainScale
    End Function

Function AltMars(Optional Lat As Double, Optional Lon As Double) As Double
    If TerrainMode < 2 Then Exit Function
    C1 = Int((Lon / Rad + 180) * 16 + 0.5): If C1 < 0 Then C1 = C1 + 5760 Else If C1 >= 5760 Then C1 = C1 - 5760
    D1 = Int((Lat / Rad + 90) * 16 + 0.5): If D1 < 0 Then D1 = 0 Else If D1 >= 2880 Then D1 = 2879
    AltMars = Mars.Terrain(C1, D1) * TerrainScale
    End Function

Function TrimL(Text As String, Optional Sep As String, Optional AddSep As Boolean) As String ' begin..sep..end -> [begin..] + sep..end
    Dim N As Long
    N = InStr(Text, Sep): If N = 0 Then Exit Function
    If AddSep Then N = N + Len(Sep)
    TrimL = Left(Text, N - 1): Text = Mid(Text, N)
    End Function

Function TrimR(Text As String, Optional Sep As String, Optional AddSep As Boolean) As String ' begin..sep..end -> begin..sep + [..end]
    Dim N As Long
    N = InStrRev(Text, Sep): If N = 0 Then Exit Function
    If Not AddSep Then N = N + Len(Sep)
    TrimR = Mid(Text, N): Text = Left(Text, N - 1)
    End Function

'Sub CopyTerrain()
'    Dim X1 As Long, Y1 As Long, X2 As Long, Y2 As Long, _
'        X3 As Long, Y3 As Long, X4 As Long, Y4 As Long, _
'        X5 As Long, Y5 As Long, L As Integer, _
'        C As Long, N As Long, M As Long, _
'        S As String, T(1200 - 1, 1200 - 1) As Integer
'    'S = IIf(IsDebug, "..\", "") & "Terrain\Terrain.bin": If Dir(S) <> "" Then Kill S
'    'Open S For Binary As 2: Put 2, , N: Put 2, , Earth.TerrainP
'    N = 1: X2 = -1000: Y2 = -1000: Erase Earth.TerrainP
'    'ReDim Earth.Terrain(120 - 1, 120 - 1, 1 To 1)
'    ReDim Earth.Terrain(120 - 1, 120 - 1, 1 To 32000)
'    'For Y1 = 20 To 50 - 1: For X1 = -20 To 10 - 1
'    For Y1 = -90 To 90 - 1: For X1 = -180 To 180 - 1
'        'ReDim Preserve Earth.Terrain(120 - 1, 120 - 1, 1 To N)
'        If N > 32000 Then Stop
'        X4 = Int(X1 / 10) * 10: Y4 = Int(Y1 / 10) * 10
'        If X2 <> X4 Or Y2 <> Y4 Then
'            X2 = X4: Y2 = Y4
'            S = IIf(IsDebug, "..\", "") & "..\NavBackup\Terrain\G" & _
'                IIf(X2 <= 0, "W", "E") & Format(Abs(X2), "000") & _
'                IIf(Y2 + 10 < 0, "S", "N") & Format(Abs(Y2 + 10), "00") & ".bin"
'            If Dir(S) = "" Then Stop Else Open S For Binary As 1: Get 1, , T: Close 1
'            End If
'        C = 0: X5 = (X1 - X2) * 120: Y5 = (Y1 - Y2) * 120
'        For Y3 = 0 To 120 - 1: For X3 = 0 To 120 - 1
'            L = T(X5 + X3, 1199 - Y5 - Y3): If L <> 0 Then Earth.Terrain(X3, Y3, N) = L: C = C + 1
'            'L = T(X5 + X3, 1199 - Y5 - Y3): If L <> 0 Then Earth.Terrain(X3, Y3, 1) = L: C = C + 1
'            Next: Next
'        If C Then Earth.TerrainP(X1 + 180, Y1 + 90) = N: N = N + 1
'        'If C Then Earth.TerrainP(X1 + 180, Y1 + 90) = N: N = N + 1: Put 2, , Earth.Terrain
'        Next: Next
'    N = N - 1
'    If N > 0 Then ReDim Preserve Earth.Terrain(120 - 1, 120 - 1, 1 To N) Else Erase Earth.Terrain
'    S = IIf(IsDebug, "..\", "") & "Terrain\Terrain.bin": If Dir(S) <> "" Then Kill S
'    Open S For Binary As 2: Put 2, , N: Put 2, , Earth.TerrainP: If N > 0 Then Put 2, , Earth.Terrain
'    'Put 2, 1, N: Put 2, 1 + 4, Earth.TerrainP
'    Close 2
'    Erase Earth.Terrain, Earth.TerrainP
'    End Sub

'Sub PrintTerrain()
'    Dim X As Long, Y As Long, Z As Integer, P As Long, H As Long, _
'        T(5760 - 1, 2880 - 1) As Integer, T2(5760 - 1, 2880 - 1) As Integer
'    Open "Moon.bin" For Binary As 1: Get 1, , T: Close
'    For Y = 0 To 2880 - 1: For X = 0 To 5760 - 1
'        T2(X, 2880 - 1 - Y) = T(X, Y)
'        Next: Next
'    Open "Moon.bin" For Binary As 1: Put 1, , T2: Close
'    Stop
'    Open "Mars.bin" For Binary As 1: Get 1, , T: Close
'    Main.Move 60, 60, 800 * 15, 400 * 15: Main.AutoRedraw = True: Main.Show: DoEvents
'    P = 10: H = Main.ScaleHeight
'    For Y = 0 To 2880 - 1 Step P: For X = 0 To 5760 - 1 Step P
'        Z = T(X, Y) \ 256 + 128: Main.PSet (X / P, H - Y / P), RGB(Z, Z, Z)
'        Next: Next
'    End Sub

'Sub CopyAirspaces()
'    Open AirwaysDir & "Airspace.bin" For Binary As 1
'    Get 1, , N: If N > 0 Then ReDim AirspaceZone(N): Get 1, , AirspaceZone
'    Get 1, , N: If N > 0 Then ReDim AirspaceGroup(N): Get 1, , AirspaceGroup
'    Get 1, , AirspaceN: If AirspaceN > 0 Then ReDim Airspace(AirspaceN - 1): Get 1, , Airspace
'    Dim C As Long, N As Long, Lat As Single, Lon As Single, _
'        Lat1 As Single, Lon1 As Single, Lat2 As Single, Lon2 As Single
'    Dim Airspace2() As Airspace2
'    ReDim Airspace2(AirspaceN - 1)
'    For N = 0 To AirspaceN - 1: With Airspace(N)
'        Airspace2(N).Name = .Name
'        Airspace2(N).Lat = .Lat
'        Airspace2(N).Lon = .Lon
'        Airspace2(N).Alt = .Alt
'        Airspace2(N).FLD = .FLD
'        Airspace2(N).FLU = .FLU
'        Airspace2(N).N = .N
'        Airspace2(N).Color = .Color
'        ReDim Airspace2(N).Point(.N - 1)
'        For C = 0 To .N - 1
'            Lat = .Point(C).Lat: Lon = .Point(C).Lon
'            Airspace2(N).Point(C).Lat = Lat
'            Airspace2(N).Point(C).Lon = Lon
'            If C = 0 Then
'                Lat1 = .Point(C).Lat: Lat2 = Lat1
'                Lon1 = .Point(C).Lon: Lon2 = Lon1
'            Else
'                If Lat < Lat1 Then Lat1 = Lat Else If Lat > Lat2 Then Lat2 = Lat
'                If Lon < Lon1 Then Lon1 = Lon Else If Lon > Lon2 Then Lon2 = Lon
'                End If
'            Next
'        With Airspace2(N)
'        .CLat = (Lat1 + Lat2) / 2
'        .CLon = (Lon1 + Lon2) / 2
'        .SLat = (Lat2 - Lat1) / 2
'        .SLon = (Lon2 - Lon1) / 2
'        If Lon2 - Lon1 > A180 Then _
'            .SLon = A180 - .SLon: If .CLon > 0 Then .CLon = .CLon - A180 Else .CLon = .CLon + A180
'        End With
'        End With: Next
'    Stop
'    Open "D:\Temp\Airspace.bin" For Binary As 2
'    N = UBound(AirspaceZone): Put 2, , N: Put 2, , AirspaceZone
'    N = UBound(AirspaceGroup): Put 2, , N: Put 2, , AirspaceGroup
'    Put 2, , AirspaceN: Put 2, , Airspace2
'    Close
'    End Sub

'Function FindAirspace(S As String, U As String, _
'        A As Long, B As Long, C As Long, D As Long, E As Long, F As Long) As Boolean
'    A = InStr(A + 1, S, U): If A = 0 Then FindAirspace = True: Exit Function
'    B = InStrRev(S, vbLf, A): If B = 0 Then Stop
'    C = InStrRev(S, ">", A): If C = 0 Then Stop
'    If B < C Then B = C
'    B = B + 1
'    D = InStr(A, S, "<"): If D = 0 Then Stop
'    E = InStr(A, S, " "): If E = 0 Then Stop
'    F = InStr(A, S, vbLf): If F = 0 Then Stop
'    If D > E Then D = E
'    If D > F Then D = F
'    End Function

'Sub CleanAirspaces()
'    Dim A1 As Long, B1 As Long, C1 As Long, D1 As Long, E1 As Long, F1 As Long, _
'        A2 As Long, B2 As Long, C2 As Long, D2 As Long, E2 As Long, F2 As Long, _
'        S As String, T As String, U As String, V As String, W As String, _
'        P As String, Q As String, _
'        File1 As String, File2 As String
'    'File1 = AirwaysDir & "Denmark.kml"
'    'File2 = "D:\Temp\Airways\DK.DK-OpenAir-AllinOne-2012-v4.1_int.kml"
'    'File1 = AirwaysDir & "Germany.kml"
'    'File2 = "D:\Temp\Airways\DE.DE120312.kml"
'    'File1 = AirwaysDir & "Italy.kml"
'    'File2 = "D:\Temp\Airways\IT.italy.kml"
'    'File1 = AirwaysDir & "Spain.kml"
'    'File2 = "D:\Temp\Airways\SP.SUASpain201207.old.kml"
'    S = LoadFile(File1): If S = "" Then Stop
'    T = LoadFile(File2): If T = "" Then Stop
'    T = UCase(T)
'    A1 = 0
'    Do
'        If FindAirspace(S, "ï¿½", A1, B1, C1, D1, E1, F1) Then Exit Do
'        V = U: U = Mid(S, B1, D1 - B1)
'        If V <> U Then
'            V = Mid(S, B1, A1 - B1): W = Mid(S, A1 + 3, D1 - A1 - 3)
'            If V = "" And W = "" Then
'                Stop
'            ElseIf Len(V) > Len(W) Then
'                A2 = 0
'                Do
'                    If FindAirspace(T, V, A2, B2, C2, D2, E2, F2) Then A2 = 21: P = "*": GoTo Nxt
'                    Loop While Mid(T, A2 + Len(V) + 1, Len(W)) <> W
'                P = Mid(T, A2 + Len(V), 1)
'            Else
'                A2 = 0
'                Do
'                    If FindAirspace(T, W, A2, B2, C2, D2, E2, F2) Then A2 = 21: P = "*": GoTo Nxt
'                    Loop While Mid(T, A2 - Len(V) - 1, Len(V)) <> V
'                P = Mid(T, A2 - 1, 1)
'                End If
'Nxt:
'            StrAdd "S = Replace(S, """; U; """, """; Replace(U, "ï¿½", P); """) ' ["; _
'                V; "?"; W; "] .. ["; Replace(Replace(Mid(S, A1 - 20, 40), vbCr, "."), vbLf, "."); "] .. ["; Replace(Replace(Mid(T, A2 - 20, 40), vbCr, "."), vbLf, "."); "]"
'            A1 = A1
'            End If
'        Loop
'    Clipboard.Clear: Clipboard.SetText StrAdd
'    Stop
'
'    UpdateFile File1 & ".new", S
'    Stop
'    End Sub

'Function TrimCoord(Text As String, Optional Sep As String) As String
'    Dim N As Long
'    If Sep = "" Then Sep = "                <coordinates>" & vbLf
'    N = InStrRev(Text, Sep): If N Then N = N + Len(Sep): TrimCoord = ParamX(LTrim(Mid(Text, N)))
'    End Function

'Sub CleanAirways()
'    Dim A As Long, B As Long, C As Long, D As Long, E As Long, F As Long, _
'        R As String, S As String, T As String, U As String, V As String, W As String, _
'        Folder() As String, Place() As String, Poly() As String, _
'        FolderL As String, PlaceL As String, PolyL As String, _
'        FolderR As String, PlaceR As String, PolyR As String
'    S = LoadFile("D:\Temp\Airways2.txt"): If S = "" Then Stop
'    S = Replace(S, " " & vbLf, vbLf)
'    S = Replace(S, "    </Folder>" & vbLf & "    <Folder>" & vbLf, _
'                   "    </Folder>" & vbLf & "|    <Folder>" & vbLf)
'    Folder = Split(S, "|")
'    For A = 0 To UBound(Folder)
'        T = Folder(A)
'        FolderL = TrimL(T, "      <Placemark>" & vbLf)
'        FolderR = TrimR(T, "      </Placemark>" & vbLf)
'        T = Replace(T, "      </Placemark>" & vbLf & "      <Placemark>" & vbLf, _
'                       "      </Placemark>" & vbLf & "|      <Placemark>" & vbLf)
'        Place = Split(T, "|")
'        For B = 0 To UBound(Place)
'            U = Place(B)
'            PlaceL = TrimL(U, "          <Polygon>" & vbLf)
'            PlaceR = TrimR(U, "          </Polygon>" & vbLf)
'            U = Replace(U, "          </Polygon>" & vbLf & "          <Polygon>" & vbLf, _
'                           "          </Polygon>" & vbLf & "|          <Polygon>" & vbLf)
'            Poly = Split(U, "|")
'            D = UBound(Poly) + 1: If (D Mod 4) <> 0 Or D < 4 Then Stop
'            For C = 0 To (D \ 2) - 2 Step 2
'                V = Poly(C)
'                PolyL = TrimL(V, "                <coordinates>" & vbLf, True)
'                PolyR = TrimR(V, "                </coordinates>" & vbLf, True)
'                V = PolyL & "                  " & _
'                    TrimCoord(Poly(C)) & " " & TrimCoord(Poly(C + 1)) & " " & _
'                    TrimCoord(Poly(D - C - 2)) & " " & TrimCoord(Poly(D - C - 1)) & " " & _
'                    TrimCoord(Poly(C)) & vbLf & PolyR
'                V = FolderL & PlaceL & V & Poly(C) & Poly(C + 1) & Poly(D - C - 2) & Poly(D - C - 1) & PlaceR & FolderR
'                StrAdd V
'                Next
'            Next
'        Next
'    V = StrAdd: V = "  " & Replace(V, vbLf, vbLf & "  "): V = Left(V, Len(V) - 2)
'    Stop
'    UpdateFile "D:\Temp\Airways2b.txt", V: Stop
'    End Sub

'Sub UpdateAirports()
'    Dim N As Long, S As String, T As String, U As String, V As String, W As String, _
'        ID As String, Name As String, Country As String, Size As Double, _
'        Lat As Double, Lon As Double, Alt As Double
'    '"SP28453":{
'    '   "lat":"36.744622",
'    '   "min_zoom":8,
'    '   "name":"JEREZ",
'    '   "lng":"-6.060111",
'    '   "arpt_ident":"SP28453"
'    '   },
'    S = LoadFile("Backup\Airports.txt"): If S = "" Then Stop
'    V = LoadFile("Backup\CountryCodes.txt"): If V = "" Then Stop
'    Do
'        T = ParamC(S): If Len(T) <= 1 Then Exit Do
'        ID = ParamD(T, "arpt_ident"): Name = ParamD(T, "name")
'        Lat = Val(ParamD(T, "lat")): Lon = Val(ParamD(T, "lng")): Alt = 0
'        Size = Val(ParamD(T, "min_zoom"))
'        'If ID = "" Or Name = "" Or Lat = 0 Or Lon = 0 Or Size = 0 Or Size <> Int(Size) Then Stop
'        W = Left(ID, 2): N = InStr(V, W & "|"): If N Then Country = GetLine(Mid(V, N + 3)) Else Stop
'        'SP28453,Spain,Jerez,36.744622N,6.060111W,0,8
'        U = ID & "," & Country & "," & Replace(Name, ",", ".") & "," & ToLat(Lat * Rad, "") & "," & ToLon(Lon * Rad, "") & "," & _
'            NumD(Alt) & "," & NumD(Size) & vbCrLf
'        StrAdd U
'        Loop
'    T = StrAdd: Clipboard.Clear: Clipboard.SetText T
'    'UpdateFile AirwaysDir & "Airport.csv", StrAdd
'    Stop
'    End Sub

Sub LoadCities(Optional ByVal HideMsg As Boolean = True, Optional ByVal Update As Boolean)
    Dim A As Long, N As Long, S As String, T As String, U As String, V As String
    Message "Loading cities …": DoEvents
    On Error Resume Next
    If CityN > 0 Then DeleteCities
    CityN = 0: ReDim City(1000 - 1)
    T = LoadFile(AirwaysDir & "City.csv"): If T = "" Then GoTo Ext
    ReDim CityZone(0): V = "Unknown": CityZone(0).Name = V
    A = 1
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If CityN > UBound(City) Then ReDim Preserve City(UBound(City) + 1000)
        With City(CityN)
        'Spain,Madrid,40.401972N,3.685298W,0,5567000,3
        U = Param(S): If V <> U Then ReDim Preserve CityZone(N): _
            With CityZone(N): .Name = U: .N = N: End With: V = U: N = N + 1
        .Planet = 0: .Planet = N - 1: .Name = Param(S)
        .Lat = ValAngle(Param(S)): .Lon = ValAngle(Param(S)): .Alt = ValSpace(Param(S))
        .Time = Val(Param(S))
        End With
        CityN = CityN + 1
        Loop
    On Error GoTo 0
Ext:
    If CityN <= 0 Then CityN = 0: ReDim City(0) _
    Else If UBound(City) <> CityN - 1 Then ReDim Preserve City(CityN - 1)
    UpdateSelection
    If HideMsg Then Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub DeleteCities()
    CityN = 0: ReDim City(0), CityZone(0)
    UpdateSelection
    End Sub

Sub LoadSeaports(Optional ByVal HideMsg As Boolean = True, Optional ByVal Update As Boolean)
    Dim A As Long, N As Long, S As String, T As String, U As String, V As String
    Message "Loading seaports …": DoEvents
    On Error Resume Next
    If SeaportN > 0 Then DeleteSeaports
    SeaportN = 0: ReDim Seaport(1000 - 1)
    T = LoadFile(AirwaysDir & "Seaport.csv"): If T = "" Then GoTo Ext
    ReDim SeaportZone(0): V = "Unknown": SeaportZone(0).Name = V
    A = 1
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If SeaportN > UBound(Seaport) Then ReDim Preserve Seaport(UBound(Seaport) + 1000)
        With Seaport(SeaportN)
        'Earth,Barcelona,41.354722N,2.168787E,0,75,3
        U = Param(S): If V <> U Then ReDim Preserve SeaportZone(N): _
            With SeaportZone(N): .Name = U: .N = N: End With: V = U: N = N + 1
        .Planet = 0: .Planet = N - 1: .Name = Param(S)
        .Lat = ValAngle(Param(S)): .Lon = ValAngle(Param(S)): .Alt = ValSpace(Param(S))
        .Time = Val(Param(S))
        End With
        SeaportN = SeaportN + 1
        Loop
    On Error GoTo 0
Ext:
    If SeaportN <= 0 Then SeaportN = 0: ReDim Seaport(0) _
    Else If UBound(Seaport) <> SeaportN - 1 Then ReDim Preserve Seaport(SeaportN - 1)
    UpdateSelection
    If HideMsg Then Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub DeleteSeaports()
    SeaportN = 0: ReDim Seaport(0), SeaportZone(0)
    UpdateSelection
    End Sub

Sub LoadAirports(Optional ByVal HideMsg As Boolean = True, Optional ByVal Update As Boolean)
    Dim A As Long, N As Long, S As String, T As String, U As String, Zone As Collection
    Message "Loading airports …": DoEvents
    On Error Resume Next
    If AirportN > 0 Then DeleteAirports
    AirportN = 0: ReDim Airport(1000 - 1)
    T = LoadFile(AirwaysDir & "Airport.csv"): If T = "" Then GoTo Ext
    Set Zone = New Collection
    For N = 0 To UBound(AirspaceZone): Zone.Add N, AirspaceZone(N).Name: Next
    A = 1
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If AirportN > UBound(Airport) Then ReDim Preserve Airport(UBound(Airport) + 1000)
        With Airport(AirportN)
        'SP28453,Spain,Jerez,36.744622N,6.060111W,0,8
        Param S: .Planet = -1: .Planet = Zone(Param(S)): .Name = ULCase(Param(S))
        .Lat = ValAngle(Param(S)): .Lon = ValAngle(Param(S)): .Alt = ValSpace(Param(S))
        .Time = Val(Param(S))
        End With
        AirportN = AirportN + 1
        Loop
    On Error GoTo 0
Ext:
    If AirportN <= 0 Then AirportN = 0: ReDim Airport(0) _
    Else If UBound(Airport) <> AirportN - 1 Then ReDim Preserve Airport(AirportN - 1)
    Set Zone = Nothing
    UpdateSelection
    If HideMsg Then Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub DeleteAirports()
    AirportN = 0: ReDim Airport(0)
    UpdateSelection
    End Sub

Sub LoadAirways(Optional ByVal HideMsg As Boolean = True, Optional ByVal Update As Boolean)
    Message "Loading airways …": DoEvents
    On Error GoTo Ext
    If AirwayN > 0 Or RNAVN > 0 Then DeleteAirways
    Open AirwaysDir & "Airway.bin" For Binary As 1: Get 1, , AirwayN
    If AirwayN > 0 Then ReDim Airway(AirwayN - 1): Get 1, , Airway
    Close
    Open AirwaysDir & "Rnav.bin" For Binary As 1: Get 1, , RNAVN
    If RNAVN > 0 Then ReDim RNAV(RNAVN - 1): Get 1, , RNAV
Ext:
    Close
    On Error GoTo 0
    If HideMsg Then Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub DeleteAirways()
    AirwayN = 0: RNAVN = 0: ReDim Airway(0), RNAV(0)
    End Sub

Function LoadAirspaces(Optional ByVal Update As Boolean) As Boolean
    Dim N As Long
    Message "Loading airspaces …": DoEvents
    On Error GoTo Ext
    If AirspaceN > 0 Then DeleteAirspaces
    Open AirwaysDir & "Airspace.bin" For Binary As 1
    Get 1, , N: If N > 0 Then ReDim AirspaceZone(N): Get 1, , AirspaceZone
    'For N = 0 To UBound(AirspaceZone) - 1: SetULCase AirspaceZone(N).Name: Next
    If IsAirspaceDetection Or _
            ((Tool And ToolAirways) <> 0 And (Layers And LayersAirspaces) <> 0) Then
        Get 1, , N: If N > 0 Then ReDim AirspaceGroup(N): Get 1, , AirspaceGroup
        Get 1, , AirspaceN: If AirspaceN > 0 Then ReDim Airspace(AirspaceN - 1): Get 1, , Airspace
        For N = 0 To UBound(AirspaceGroup) - 1: SetULCase AirspaceGroup(N).Name: Next
        For N = 0 To AirspaceN - 1: With Airspace(N)
            SetULCase .Name
            If .FLD < 0 Then .FLD = 0 Else If .FLU >= AirspaceMax Then .FLU = AirspaceMax
            If .Alt >= AirspaceMax Then .Alt = AirspaceMax
            .FLD = Int(.FLD / Foot10 + 0.5) * Foot10: .FLU = Int(.FLU / Foot10 + 0.5) * Foot10
            End With: Next
        End If
    ReDim IsAirspace(AirspaceN)
    On Error GoTo 0
Ext:
    Close
    SetAirspaceZone , , , Update
    UpdateSelection
    Message
    LoadAirspaces = AirspaceN > 0
    End Function

Sub SaveAirspaces()
    Dim N As Long
    On Error Resume Next
    DeleteFile AirwaysDir & "Airspace.bin"
    Open AirwaysDir & "Airspace.bin" For Binary As 1
    N = UBound(AirspaceZone): Put 1, , N: Put 1, , AirspaceZone
    N = UBound(AirspaceGroup): Put 1, , N: Put 1, , AirspaceGroup
    Put 1, , AirspaceN: Put 1, , Airspace
    Close
    On Error GoTo 0
    End Sub

Sub DeleteAirspaces(Optional ByVal Update As Boolean)
    DeleteList AirspacesList: DeleteList AirspacesFlatList
    AirspaceN = 0: ReDim Airspace(0), AirspaceGroup(0)
    SetAirspaceZone , , , Update
    UpdateSelection
    End Sub

Sub UpdateAirspaces(Optional ByVal Update As Boolean = True, Optional ByVal Ask As Boolean = True)
    Dim N As Long
    Static IsBusy As Boolean
    If Not IsBusy Then IsBusy = True Else Exit Sub
    Message "Updating airspaces …": DoEvents
    If Update Then SetTool ToolAirways, False
    If AirspaceN > 0 Then DeleteAirspaces
    AirspaceN = 0: ReDim Airspace(100 - 1), AirspaceZone(0), AirspaceGroup(0)
    LoadList AirwaysDir, "*.kml"
    If List.Count <= 0 Then
        If Ask Then If MsgBox( _
                "No airspace KML files found in the ""Airways"" folder." & vbCrLf & _
                "Open the web browser to download them?", _
                vbQuestion Or vbOKCancel, AppName) = vbOK Then _
            Browser.Explore "http://www.lloydbailey.net/airspace.html"
        GoTo Ext
        End If
    For N = 1 To List.Count
        Message "Updating airspaces: " & List(N) & " …": DoEvents
        UpdateAirspace List(N)
        Next
    If AirspaceN <= 0 Then AirspaceN = 0: ReDim Airspace(0) _
    Else If UBound(Airspace) <> AirspaceN - 1 Then ReDim Preserve Airspace(AirspaceN - 1)
    With AirspaceGroup(UBound(AirspaceGroup)): .Name = "Last": .N = AirspaceN: End With
    With AirspaceZone(UBound(AirspaceZone)): .Name = "Last": .N = UBound(AirspaceGroup): End With
    SaveAirspaces
    DeleteAirspaces
    If Update Then If LoadAirspaces Then SetTool ToolAirways, True
Ext:
    IsBusy = False
    Message
    End Sub

Function UpdateAirspace(File As String) As Boolean
    Dim A As Long, B As Long, C As Long, D As Long, _
        E As Long, F As Long, G As Long, H As Long, _
        I As Long, L As Long, Color As Long, _
        S As String, T As String, U As String, _
        Class As String, N0 As Long, P0 As Long, _
        Group As String, N1 As Long, P1 As Long, _
        Item As String, N2 As Long, P2 As Long, _
        IsPoint As Boolean, N3 As Long, P3 As Long, _
        Lat As Single, Lon As Single, Alt As Single, _
        Lat2 As Single, Lon2 As Single, _
        Lat3 As Single, Lon3 As Single, _
        Lat4 As Single, Lon4 As Single, _
        Lat5 As Single, Lon5 As Single, _
        Lat6 As Single, Lon6 As Single, _
        Lat7 As Single, Lon7 As Single
    
    S = LoadFile(AirwaysDir & File & ".kml"): If S = "" Then Exit Function      'File not found
    With AirspaceZone(UBound(AirspaceZone))
    .Name = File: .N = UBound(AirspaceGroup): End With
    ReDim Preserve AirspaceZone(UBound(AirspaceZone) + 1)
    L = 0
DoInit:
    A = 0: B = 0: C = 0: D = 0: E = 0: T = ""
    Class = "": N0 = 0: P0 = 0
    Group = "": N1 = 0: P1 = 0
    Item = "": N2 = 0: P2 = 0
    N3 = 0: P3 = 0
    If L > 0 Then D = L: E = D - 1: Class = "AIRSPACE": P0 = 1
    
    Do
        B = A
DoNext:
        A = InStr(A + 1, S, "Folder>")
        If A < 2 Then If D = L Then Exit Do Else Exit Do                        'Folder not closed
        If D = 2 And L = 0 Then C = InStr(B, S, "<coordinates>"): _
            If C > 0 And C < A Then L = 1: GoTo DoInit
        E = D: If Mid(S, A - 1, 1) = "<" Then D = D + 1 _
        Else If Mid(S, A - 1, 1) = "/" Then D = D - 1 _
        Else GoTo DoNext
        
        If E = 0 And D = 1 Then                                                 'Begin class
            P0 = A
        ElseIf E = 1 And D = 2 Then                                             'Begin group
            If P1 = 0 Then
                N0 = N0 + 1: If L = 0 Then Class = ParamR(S, "name", "Class " & N0, B, A)
                End If
            P1 = A
        ElseIf E = 2 And D = 3 Then                                             'Begin item
            If P2 = 0 Then
                DoEvents
                N1 = N1 + 1: Group = ParamR(S, "name", "Group " & N1, B, A)
                With AirspaceGroup(UBound(AirspaceGroup))
                .Name = Class & " - " & Group: .N = AirspaceN: End With
                ReDim Preserve AirspaceGroup(UBound(AirspaceGroup) + 1)
                End If
            P2 = A
        'ElseIf E = 3 And D = 4 Then                                            'Begin item folder
        'ElseIf E = 4 And D = 5 Then                                            'Begin item sub-folder
        'ElseIf E = 4 And D = 3 Then                                            'End item folder
        ElseIf E = 3 And D = 2 Then                                             'End item
            F = 0: N2 = N2 + 1: Item = ParamR(S, "name", "Item " & N2, P2, A)
            
            Do
                If AirspaceN > UBound(Airspace) Then ReDim Preserve Airspace(UBound(Airspace) + 100)
                With Airspace(AirspaceN)
                P3 = InStr(P2 + 1, S, "</Point>"): If P3 = 0 Or P3 > A Then P3 = A: GoTo DoPoly
                P3 = InStr(P3 + 1, S, "</Point>"): If P3 = 0 Or P3 > A Then P3 = A: GoTo DoPoly
                
                T = ParamR(S, "Point", , P2, P3): If T = "" Then GoTo DoPoly
                T = ParamR(T, "coordinates"): If T = "" Then GoTo DoPoly
                .Lon = NumS(T): .Lat = NumS(T): .Alt = NumS(T)
DoPoly:
                T = ParamR(S, "outerBoundaryIs", , P2, P3): If T = "" Then GoTo DoPoint
                T = ParamR(T, "coordinates"): If T = "" Then GoTo DoPoint
                ClearCoords T
                .N = 0: IsPoint = .Lat <> 0 Or .Lon <> 0 Or .Alt <> 0
                ReDim .Point(50 - 1)
                Do
                    If .N > UBound(.Point) Then ReDim Preserve .Point(UBound(.Point) + 50)
                    If T <> "" Then .Point(.N).Lon = NumS(T) Else Exit Do
                    If T <> "" Then .Point(.N).Lat = NumS(T) Else Exit Do
                    If T <> "" Then .FLU = NumS(T) Else Exit Do
                    
                    If .N = 0 Then
                        Lat6 = .Point(.N).Lat: Lat7 = Lat6
                        Lon6 = .Point(.N).Lon: Lon7 = Lon6
                        GoTo DoNextPoint
                        End If
                    
                    Lat = .Point(.N).Lat: Lon = .Point(.N).Lon
                    If Lat < Lat6 Then Lat6 = Lat Else If Lat > Lat7 Then Lat7 = Lat
                    If Lon < Lon6 Then Lon6 = Lon Else If Lon > Lon7 Then Lon7 = Lon
                    
                    Lat3 = .Point(.N - 1).Lat: Lon3 = .Point(.N - 1).Lon
                    Lat4 = .Point(.N).Lat: Lon4 = .Point(.N).Lon
                    Lat2 = Lat4 - Lat3: Lon2 = Lon4 - Lon3
                    Lat5 = Abs(Lat2): Lon5 = Abs(Lon2)
                    If (Lat5 <= 1 And Lon5 <= 1) Or Lon5 >= 180 Then GoTo DoNextPoint
                    If Lat5 > Lon5 Then I = 1 Else I = 2
                    If I = 1 Then
                        If Lat2 > 0 Then H = 1: G = Int(Lat3) + 1 Else H = -1: G = Int(Lat3)
                    Else
                        If Lon2 > 0 Then H = 1: G = Int(Lon3) + 1 Else H = -1: G = Int(Lon3)
                        End If
                    
                    Do
                        If .N > UBound(.Point) Then ReDim Preserve .Point(UBound(.Point) + 50)
                        If I = 1 Then .Point(.N).Lat = G: .Point(.N).Lon = Lon3 + (G - Lat3) / Lat2 * Lon2 _
                        Else .Point(.N).Lon = G: .Point(.N).Lat = Lat3 + (G - Lon3) / Lon2 * Lat2
                        .N = .N + 1: G = G + H
                        
                        If I = 1 Then
                            If (H > 0 And G >= Lat4) Or (H < 0 And G <= Lat4) Then Exit Do
                        Else
                            If (H > 0 And G >= Lon4) Or (H < 0 And G <= Lon4) Then Exit Do
                            End If
                        Loop
                    
                    If .N > UBound(.Point) Then ReDim Preserve .Point(UBound(.Point) + 50)
                    .Point(.N).Lat = Lat4: .Point(.N).Lon = Lon4
DoNextPoint:
                    .N = .N + 1
                    Loop
                If .N < 2 Then .N = 0: Erase .Point: GoTo DoPoint
                'If .Point(.N - 1).Lat = .Point(0).Lat And .Point(.N - 1).Lon = .Point(0).Lon Then _
                    .N = .N - 1
                If .Point(.N - 1).Lat <> .Point(0).Lat Or .Point(.N - 1).Lon <> .Point(0).Lon Then
                    .N = .N + 1: If .N > UBound(.Point) Then ReDim Preserve .Point(UBound(.Point) + 50)
                    .Point(.N - 1).Lat = .Point(0).Lat: .Point(.N - 1).Lon = .Point(0).Lon
                    End If
                ReDim Preserve .Point(.N - 1)
                .CLat = (Lat6 + Lat7) / 2: .CLon = (Lon6 + Lon7) / 2
                .SLat = (Lat7 - Lat6) / 2: .SLon = (Lon7 - Lon6) / 2
                If .SLon > 90 Then .SLon = 180 - .SLon: _
                    If .CLon > 0 Then .CLon = .CLon - 180 Else .CLon = .CLon + 180
                If Not IsPoint Then .Lat = .CLat: .Lon = .CLon: .Alt = .FLU
                .Lat = .Lat * Rad: .Lon = .Lon * Rad
                .CLat = .CLat * Rad: .CLon = .CLon * Rad
                .SLat = .SLat * Rad: .SLon = .SLon * Rad
                For C = 0 To .N - 1: With .Point(C)
                    .Lat = .Lat * Rad: .Lon = .Lon * Rad
                    End With: Next
                C = InStr(P2, S, "</outerBoundaryIs>"): If C = 0 Or C > P3 Then GoTo DoColor
                T = ParamR(S, "outerBoundaryIs", , C, P3): If T = "" Then GoTo DoColor
                T = ParamR(T, "coordinates"): If T = "" Then GoTo DoColor
                ClearCoords T
                Do
                    NumS T: NumS T: If T <> "" Then Alt = NumS(T) Else Exit Do
                    If Alt <> .FLU Then
                        If Alt < .FLU Then .FLD = Alt Else .FLD = .FLU: .FLU = Alt
                        Exit Do
                        End If
                    Loop
DoColor:
                .Color = &HFFFFFF
                T = ParamR(S, "PolyStyle", , P2, P3): If T = "" Then GoTo DoStyle
                T = ParamR(T, "color"): If T = "" Then GoTo DoStyle
                .Color = Val("&H" & T & "&") And &HFFFFFF
                GoTo DoName
DoStyle:
                T = ParamR(S, "styleUrl", , P2, P3): If T = "" Then GoTo DoName
                If Left(T, 1) = "#" And Len(T) > 1 Then T = Mid(T, 2) Else GoTo DoName
                C = InStr(S, "<Style id=""" & T & """>"): If C = 0 Then GoTo DoName
                T = ParamR(S, "color", , C): If T = "" Then GoTo DoName
                .Color = Val("&H" & T & "&") And &HFFFFFF
DoName:
                .Name = Item
                AirspaceN = AirspaceN + 1
                F = F + 1
DoPoint:
                End With
                P2 = P3
                Loop While P3 < A
        
        ElseIf E = 2 And D = 1 Then                                             'End group
            P2 = 0
        ElseIf E = 1 And D = 0 Then                                             'End class
            P1 = 0: N1 = 0
            End If
        Loop
    
    UpdateAirspace = True
    
    End Function

Sub ClearCoords(T As String)
    T = Trim(Replace(T, vbCrLf, vbLf))
    Do While Right(T, 1) = vbLf: T = Left(T, Len(T) - 1): Loop
    Do While Left(T, 1) = vbLf: T = Mid(T, 2): Loop
    T = Trim(T)
    If InStr(T, "  ") = 0 Then If InStr(T, " ") Then T = Replace(T, " ", ",")
    T = Replace(T, vbLf, ",")
    End Sub

Function GetAirspaceZone(ByVal N As Long) As Long
    If N < 0 Or N >= AirspaceN Then Exit Function
    For GetAirspaceZone = 1 To UBound(AirspaceZone)
        If N < AirspaceGroup(AirspaceZone(GetAirspaceZone).N).N Then _
            GetAirspaceZone = GetAirspaceZone - 1: Exit Function
        Next
    GetAirspaceZone = 0
    End Function

Function GetAirspaceGroup(ByVal N As Long) As Long
    Dim C As Long
    If N < 0 Or N >= AirspaceN Then Exit Function
    For C = 1 To UBound(AirspaceZone)
        If N < AirspaceGroup(AirspaceZone(C).N).N Then C = C - 1: GoTo Nxt
        Next
    GoTo Ext
Nxt:
    For GetAirspaceGroup = AirspaceZone(C).N To AirspaceZone(C + 1).N
        If N < AirspaceGroup(GetAirspaceGroup).N Then _
            GetAirspaceGroup = GetAirspaceGroup - 1: Exit Function
        Next
Ext:
    GetAirspaceGroup = 0
    End Function

Sub SetAirspaceZone(Optional ByVal Zone As Long = -2, _
        Optional ByVal Group As Long = -2, Optional ByVal Area As Long = -2, _
        Optional ByVal Update As Boolean = True)
    Dim C As Long, Zones As String, Groups As String ', Areas As String
    
    Zones = "All zones": Groups = "All groups" ': Areas = "All areas"
    
    If AirspaceN <= 0 Then
        AirspaceZoneP = -1
    Else
        If Zone > -2 Then AirspaceZoneP = Zone: AirspaceGroupP = -1: AirspaceP = -1
        If AirspaceZoneP < -1 Or AirspaceZoneP >= UBound(AirspaceZone) Then AirspaceZoneP = -1
        If Group > -2 Then AirspaceGroupP = Group: AirspaceP = -1
        If AirspaceGroupP < -1 Or AirspaceGroupP >= UBound(AirspaceGroup) Then AirspaceGroupP = -1
        If Area > -2 Then AirspaceP = Area
        If AirspaceP < -1 Or AirspaceP >= UBound(Airspace) Then AirspaceP = -1
        For C = 0 To UBound(AirspaceZone) - 1
            Zones = Zones & "|" & AirspaceZone(C).Name
            Next
        End If
    
    If AirspaceZoneP < 0 Then
        AirspaceGroupP = -1: AirspaceP = -1
    ElseIf AirspaceN > 0 Then
        For C = AirspaceZone(AirspaceZoneP).N To AirspaceZone(AirspaceZoneP + 1).N - 1
            Groups = Groups & "|" & AirspaceGroup(C).Name
            Next
        'If AirspaceGroupP < 0 Then
            AirspaceP = -1
        'Else
        '    For C = AirspaceGroup(AirspaceZone(AirspaceZoneP).N + AirspaceGroupP).N To AirspaceGroup(AirspaceZone(AirspaceZoneP).N + AirspaceGroupP + 1).N - 1
        '        Areas = Areas & "|" & Airspace(C).Name
        '        Next
        '    End If
        End If
    
    With Options
    If .AirspaceZone.List <> Zones Then .AirspaceZone.List = Zones: _
        If .AirspaceZone.SelectedItem <> AirspaceZoneP + 1 Then _
            .AirspaceZone.SelectItem AirspaceZoneP + 1
    If .AirspaceGroup.List <> Groups Then .AirspaceGroup.List = Groups: _
        If .AirspaceGroup.SelectedItem <> AirspaceGroupP + 1 Then _
            .AirspaceGroup.SelectItem AirspaceGroupP + 1
    'If .AirspaceArea.List <> Areas Then .AirspaceArea.List = Areas: _
        If .AirspaceArea.SelectedItem <> AirspaceP + 1 Then _
            .AirspaceArea.SelectItem AirspaceP + 1
    End With
    
    If Update Then DeleteList AirspacesList: DeleteList AirspacesFlatList: _
        If Play = Paused Then Render
    
    End Sub

Sub LoadAreas(Optional ByVal Update As Boolean)
    Dim N As Long
    Message "Loading populated areas …": DoEvents
    On Error Resume Next
    If AreasN > 0 Then DeleteAreas
    Open AirwaysDir & "Populated.bin" For Binary As 1
    Get 1, , N: If N > 0 Then AreasN = N: ReDim Areas(AreasN - 1): Get 1, , Areas
    Close
    On Error GoTo 0
    If Update Then DeleteList AreasList: DeleteList AreasFlatList: If Play = Paused Then Render
    Message
    End Sub

Sub DeleteAreas(Optional ByVal Update As Boolean)
    DeleteList AreasList: DeleteList AreasFlatList: AreasN = 0: ReDim Areas(0)
    If Update Then If Play = Paused Then Render
    End Sub

Sub LoadLand(Optional ByVal Update As Boolean)
    Dim N As Long
    Message "Loading country boundaries …": DoEvents
    On Error Resume Next
    If LandN > 0 Or SeaN > 0 Then DeleteLand
    Open AirwaysDir & "Country.bin" For Binary As 1
    Get 1, , N: If N > 0 Then SeaN = N: ReDim Sea(SeaN - 1): Get 1, , Sea: _
        Get 1, , N: If N > 0 Then LandN = N: ReDim Land(LandN - 1): Get 1, , Land: _
            If Layers And LayersDefinition Then _
                Get 1, , N: If N > 0 Then LandN = N: ReDim Land(LandN - 1): Get 1, , Land
    Close
    On Error GoTo 0
    If Update Then DeleteList LandList: DeleteList LandFlatList: _
        DeleteList SeaList: DeleteList SeaFlatList: If Play = Paused Then Render
    Message
    End Sub

Sub DeleteLand(Optional ByVal Update As Boolean)
    DeleteList LandList: DeleteList LandFlatList: LandN = 0: ReDim Land(0)
    DeleteList SeaList: DeleteList SeaFlatList: SeaN = 0: ReDim Sea(0)
    If Update Then If Play = Paused Then Render
    End Sub

'Sub UpdateCities()
'    Dim A As Long, B As Long, C As Long, N As Long, _
'        S As String, T As String, U As String, V As String, W As String, S2 As String, S3 As String, _
'        ID As String, Name As String, Country As String, Size As Double, Population As Double, _
'        Lat As Double, Lon As Double, Alt As Double
'    S = LoadFile("D:\Temp\Layers\ne_10m_populated_places.kml"): If S = "" Then Stop
'    S2 = LoadFile("D:\Temp\Layers\ne_50m_populated_places.kml"): If S = "" Then Stop
'    S3 = LoadFile("D:\Temp\Layers\ne_110m_populated_places.kml"): If S = "" Then Stop
'    B = InStr(B + 1, S, "<Placemark>"): If B = 0 Then Stop
'    Do
'        A = B: B = InStr(B + 1, S, "<Placemark>"): If B Then T = Mid(S, A, B - A) Else T = Mid(S, A)
'        Name = ParamQ(T, "name")
'        Country = ParamU(T, "<SimpleData name=""ADM0NAME"">", "</SimpleData>")
'        'Region = ParamU(T, "<SimpleData name=""ADM1NAME"">", "</SimpleData>")
'        Population = Val(ParamU(T, "<SimpleData name=""POP_MAX"">", "</SimpleData>"))
'        V = ParamQ(T, "coordinates")
'        Lon = RoundEx(NumS(V), 0.0001): Lat = RoundEx(NumS(V), 0.0001): Alt = 0 'NumS(V)
'        W = "<name>" & Name & "</name>"
'        If InStr(S3, W) Then Size = 3 Else If InStr(S2, W) Then Size = 2 Else Size = 1
'        'Spain,Madrid,40.401972N,3.685298W,0,5567000,3
'        U = Country & "," & Replace(Name, ",", ".") & "," & ToLat(Lat * Rad, FLong) & "," & ToLon(Lon * Rad, FLong) & "," & _
'            NumD(Alt) & "," & NumD(Population) & "," & NumD(Size) & vbCrLf
'        StrAdd U
'        Loop While B
'    T = StrAdd: Clipboard.Clear: Clipboard.SetText T
'    'UpdateFile AirwaysDir & "City.csv", StrAdd
'    Stop
'    End Sub

'Sub UpdateSeaports()
'    Dim A As Long, B As Long, C As Long, N As Long, _
'        S As String, T As String, U As String, V As String, W As String, S2 As String, S3 As String, _
'        ID As String, Name As String, Country As String, Size As Double, Rank As Double, _
'        Lat As Double, Lon As Double, Alt As Double
'    S = LoadFile("D:\Temp\Layers\ne_10m_ports.kml"): If S = "" Then Stop
'    S2 = LoadFile("D:\Temp\Layers\ne_50m_ports.kml"): If S = "" Then Stop
'    B = InStr(B + 1, S, "<Placemark>"): If B = 0 Then Stop
'    Do
'        A = B: B = InStr(B + 1, S, "<Placemark>"): If B Then T = Mid(S, A, B - A) Else T = Mid(S, A)
'        Name = ParamQ(T, "name")
'        Rank = Val(ParamU(T, "<SimpleData name=""natlscale"">", "</SimpleData>"))
'        V = ParamQ(T, "coordinates")
'        Lon = NumS(V): Lat = NumS(V): Alt = 0 'NumS(V)
'        W = "<name>" & Name & "</name>"
'        If InStr(S2, W) Then Size = 3 Else If Rank >= 20 Then Size = 2 Else Size = 1
'        'Earth,Barcelona,41.354722N,2.168787E,0,75,3
'        U = "Earth," & Replace(Name, ",", ".") & "," & ToLat(Lat * Rad, FLong) & "," & ToLon(Lon * Rad, FLong) & "," & _
'            NumD(Alt) & "," & NumD(Rank) & "," & NumD(Size) & vbCrLf
'        StrAdd U
'        Loop While B
'    T = StrAdd: Clipboard.Clear: Clipboard.SetText T
'    'UpdateFile AirwaysDir & "City.csv", StrAdd
'    Stop
'    End Sub

'Sub UpdateAreas()
'    Dim A As Long, B As Long, C As Long, D As Long, N As Long, _
'        S As String, T As String, U As String, V As String, W As String, _
'        ID As String, Name As String, Country As String, Size As Double, Rank As Double, _
'        Lat As Single, Lon As Single, _
'        Lat0 As Single, Lon0 As Single, _
'        Lat1 As Single, Lon1 As Single, _
'        Lat2 As Single, Lon2 As Single
'    S = LoadFile("D:\Temp\Layers\ne_50m_urban_areas.kml"): If S = "" Then Stop
'    AreasN = 0: ReDim Areas(0)
'    A = 1: Do
'        T = GetParam(S, "Placemark", A): If T = "" Then Exit Do
'        B = 1: Do
'            U = GetParam(T, "outerBoundaryIs", B): If U = "" Then Exit Do
'            If AreasN > UBound(Areas) Then ReDim Preserve Areas(AreasN)
'            With Areas(AreasN)
'            V = ParamQ(T, "coordinates"): If V = "" Then GoTo Nxt
'            .N = 0: ReDim .Point(50 - 1)
'            Lat = 0: Lon = 0: Lat1 = 90: Lat2 = -90: Lon1 = 180: Lon2 = -180
'            Do
'                If .N > UBound(.Point) Then ReDim Preserve .Point(UBound(.Point) + 50)
'                Lat0 = Lat: Lon0 = Lon
'                Lon = NumS(V): If V = "" Then Exit Do
'                Lat = NumS(V): If V = "" Then Exit Do
'                ParamX V
'                If Lat < Lat1 Then Lat1 = Lat
'                If Lat > Lat2 Then Lat2 = Lat
'                If Lon < Lon1 Then Lon1 = Lon
'                If Lon > Lon2 Then Lon2 = Lon
'                If .N = 0 Or Lat <> Lat0 Or Lon <> Lon0 Then _
'                    .Point(.N).Lat = Lat * Rad: .Point(.N).Lon = Lon * Rad: .N = .N + 1
'                Loop
'            If .N = 0 Then Erase .Point _
'            Else If .N - 1 < UBound(.Point) Then ReDim Preserve .Point(.N - 1)
'            .CLat = (Lat1 + Lat2) / 2 * Rad: .CLon = (Lon1 + Lon2) / 2 * Rad
'            .SLat = (Lat2 - Lat1) / 2 * Rad: .SLon = (Lon2 - Lon1) / 2 * Rad
'            If Lat1 < -90 Or Lat2 > 90 Or Lon1 < -180 Or Lon1 > 180 Then Stop
'            If .SLat > A90 Or .SLon > A180 Then Stop
'            AreasN = AreasN + 1
'Nxt:
'            End With
'            Loop
'        Loop
'    Stop
'    DeleteFile AirwaysDir & "Populated.bin"
'    On Error Resume Next
'    Open AirwaysDir & "Populated.bin" For Binary As 1
'    Put 1, , AreasN: Put 1, , Areas
'    Close
'    On Error GoTo 0
'    Stop
'    End Sub

'Sub UpdateBoundaries()
'    DeleteFile AirwaysDir & "Country.bin"
'    On Error Resume Next: Open AirwaysDir & "Country.bin" For Binary As 1: On Error GoTo 0
'    UpdateLand Sea(), SeaN, "D:\Temp\Layers\World_EEZ_v6_1_simplifiedcoastlines_20110512.kml", " Exclusive Economic Zone"
'    Put 1, , SeaN: Put 1, , Sea
'    UpdateLand Land(), LandN, "D:\Temp\Layers\ne_50m_admin_0_countries.kml"
'    Put 1, , LandN: Put 1, , Land
'    UpdateLand Land(), LandN, "D:\Temp\Layers\ne_10m_admin_0_countries.kml"
'    Put 1, , LandN: Put 1, , Land
'    Close
'    Stop
'    End Sub

'Sub UpdateLand(Land() As Airspace, LandN As Long, File As String, Optional Clean As String)
'    Dim A As Long, B As Long, C As Long, D As Long, E As Long, _
'        G As Long, H As Long, I As Long, N As Long, _
'        S As String, T As String, U As String, V As String, W As String, _
'        Name As String, Size As Double, Rank As Double, _
'        Lat As Single, Lon As Single, _
'        Lat0 As Single, Lon0 As Single, _
'        Lat1 As Single, Lon1 As Single, _
'        Lat2 As Single, Lon2 As Single, _
'        Lat3 As Single, Lon3 As Single, _
'        Lat4 As Single, Lon4 As Single
'
'    'E = 0: ReDim LandZone(0): W = "Unknown": LandZone(0).Name = W
'    LandN = 0: ReDim Land(50 - 1)
'    S = LoadFileN(File): If S = "" Then Stop
'
'    A = 1: Do
'        T = GetParam(S, "Placemark", A): If T = "" Then Exit Do
'        C = 0: Name = ParamQ(T, "name"): If Name = "" Then Stop
'        If Clean <> "" Then Name = Replace(Name, Clean, "")
'        'If W <> Name Then ReDim Preserve LandZone(E): _
'            With LandZone(E): .Name = Name: .N = LandN: End With: W = Name: E = E + 1
'
'        B = 1: Do
'            U = GetParam(T, "outerBoundaryIs", B): If U = "" Then If C = 0 Then Stop Else Exit Do
'            If LandN > UBound(Land) Then ReDim Preserve Land(UBound(Land) + 50)
'            With Land(LandN)
'            C = C + 1: .Name = Name ' & IIf(C > 1, " " & C, "")
'            V = ParamQ(U, "coordinates"): If V = "" Then Stop
'            .N = 0: ReDim .Point(50 - 1)
'            Lat = 0: Lon = 0: Lat1 = 90: Lat2 = -90: Lon1 = 180: Lon2 = -180
'
'            D = 1: Do
'                Lat0 = Lat: Lon0 = Lon
'                Lon = GetParamS(V, D): If D = 0 Then Exit Do
'                Lat = GetParamS(V, D): If D = 0 Then Exit Do
'                GetItemS V, D
'                If Lat > 90 Or Lat < -90 Or Lon > 180 Or Lon < -180 Then Stop
'                If Lat >= 89.998 Then Lat = 90 Else If Lat < -89.998 Then Lat = -90
'                If Lon >= 179.999 Then Lon = 180 Else If Lon <= -179.999 Then Lon = -180
'                If Lat < Lat1 Then Lat1 = Lat
'                If Lat > Lat2 Then Lat2 = Lat
'                If Lon < Lon1 Then Lon1 = Lon
'                If Lon > Lon2 Then Lon2 = Lon
'                If .N <= 0 Then GoTo Ptr
'                If Lat = Lat0 And Lon = Lon0 Then GoTo Nxt
'
'                Lat3 = Lat - Lat0: Lat4 = Abs(Lat3)
'                Lon3 = Lon - Lon0: Lon4 = Abs(Lon3)
'                If Lon4 > 359 Then
'                    If Lon3 < -359 Then
'                        Lon = Lon + 360: If Lon > 179.999 Then Lon = 180
'                    ElseIf Lon3 > 359 Then
'                        'Lon = Lon - 360: If Lon < -179.999 Then Lon = -180
'                        For C = .N - 1 To 0 Step -1: .Point(C).Lon = 180 * Rad: Next
'                        Lon0 = 180
'                        End If
'                    Lon3 = Lon - Lon0: Lon4 = Abs(Lon3)
'                    End If
'                If Lat4 <= 1 And Lon4 <= 1 Then GoTo Ptr
'                If Lat4 > Lon4 Then I = 1 Else I = 2
'                If I = 1 Then
'                    If Lat3 > 0 Then H = 1: G = Int(Lat0) + 1 Else H = -1: G = Int(Lat0)
'                Else
'                    If Lon3 > 0 Then H = 1: G = Int(Lon0) + 1 Else H = -1: G = Int(Lon0)
'                    End If
'                Do
'                    If .N > UBound(.Point) Then ReDim Preserve .Point(UBound(.Point) + 50)
'                    If I = 1 Then .Point(.N).Lat = G * Rad: .Point(.N).Lon = (Lon0 + (G - Lat0) / Lat3 * Lon3) * Rad _
'                    Else .Point(.N).Lon = G * Rad: .Point(.N).Lat = (Lat0 + (G - Lon0) / Lon3 * Lat3) * Rad
'                    .N = .N + 1: G = G + H
'                    If I = 1 Then
'                        If (H > 0 And G >= Lat) Or (H < 0 And G <= Lat) Then Exit Do
'                    Else
'                        If (H > 0 And G >= Lon) Or (H < 0 And G <= Lon) Then Exit Do
'                        End If
'                    Loop
'Ptr:
'                If .N > UBound(.Point) Then ReDim Preserve .Point(UBound(.Point) + 50)
'                .Point(.N).Lat = Lat * Rad: .Point(.N).Lon = Lon * Rad: .N = .N + 1
'Nxt:
'                Loop
'
'            If .N = 0 Then Erase .Point _
'            Else If .N - 1 < UBound(.Point) Then ReDim Preserve .Point(.N - 1)
'            .CLat = (Lat1 + Lat2) / 2 * Rad: .CLon = (Lon1 + Lon2) / 2 * Rad
'            .SLat = (Lat2 - Lat1) / 2 * Rad: .SLon = (Lon2 - Lon1) / 2 * Rad
'            If Lat1 < -90 Or Lat2 > 90 Or Lon1 < -180 Or Lon1 > 180 Then Stop
'            If .SLat > A90 Or .SLon > A180 Then Stop
'            LandN = LandN + 1
'            End With
'            Loop
'
'        Loop
'
'    If LandN <= 0 Then Stop Else If LandN - 1 < UBound(Land) Then ReDim Preserve Land(LandN - 1)
'    Stop
'
'    End Sub

Sub LoadStations(Optional ByVal Update As Boolean)
    Dim A As Long, N As Long, S As String, T As String, U As String, V As String
    Message "Loading ground stations …"
    StationN = 0: ReDim Station(100 - 1): ReDim StationGroup(0)
    T = LoadFiles(StationsDir, "*.csv")
    If T = "" Then T = _
        "NASA DSN,Canberra 34STD,-35.2192,148.9814,554" & vbCrLf & _
        "NASA DSN,Madrid 34STD,40.2389,-4.2489,493" & vbCrLf & _
        "NASA DSN,Goldstone 34STD,35.1186,-116.8056,560" & vbCrLf
    A = 1: Options.Station.List = "All stations"
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If StationN > UBound(Station) Then ReDim Preserve Station(UBound(Station) + 100)
        With Station(StationN): V = Param(S): .Name = Param(S)
        .Lat = ValAngle(Param(S)): .Lon = ValAngle(Param(S)): .Alt = ValSpace(Param(S))
        End With
        If U <> V Then
            U = V: N = N + 1: Options.Station.Add V, StationN: ReDim Preserve StationGroup(N)
            With StationGroup(N): .Name = V: .N = StationN: End With
            End If
        Station(StationN).Planet = N
        StationN = StationN + 1
        Loop
    If StationN <= 0 Then StationN = 0: ReDim Station(0) _
    Else If UBound(Station) <> StationN - 1 Then ReDim Preserve Station(StationN - 1)
    ReDim IsStation(UBound(Station))
    SetStation
    UpdateSelection
    Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub SetStation(Optional ByVal N As Long = -1)
    If N >= 0 And N <= UBound(StationGroup) Then StationP = N
    If StationP <= 0 Or StationP > UBound(StationGroup) Then StationFirst = 0 _
        Else StationFirst = StationGroup(StationP).N
    If StationP <= 0 Or StationP >= UBound(StationGroup) Then StationLast = StationN - 1 _
        Else StationLast = StationGroup(StationP + 1).N - 1
    End Sub

Sub SetStationItem(ByVal N As Long)
    Dim C As Long
    If StationP = 0 Then Exit Sub
    For C = 0 To UBound(StationGroup) - 1
        If N >= StationGroup(C).N And N < StationGroup(C + 1).N Then _
            SetStation C: Exit Sub
        Next
    If N >= StationGroup(C).N And N < StationN Then SetStation C
    End Sub

'Sub ClearStations()
'    Dim A As Long, B As Long, C As Long, D As Long, _
'        N As Long, S As String, T As String
'    For A = 17 To 17 '1 To Main.mnSt.UBound
'        If A > 0 And A <= Main.mnSt.UBound Then B = Main.mnSt(A).Tag Else B = 0
'        If A > 0 And A < Main.mnSt.UBound Then C = Main.mnSt(A + 1).Tag Else C = StationN
'        S = Main.mnSt(A).Caption ': T = T & A & "..." & S & vbCrLf
'        For N = B To C - 1: D = 0
'            With Station(N)
'            If Len(.Name) = 4 And .Name = UCase(.Name) Then
'                For C = B To C - 1 '0 To StationN - 1
'                    If .Lat = Station(C).Lat And .Lon = Station(C).Lon And C <> N Then
'                        'T = T & N & "." & .Name & " -> " & C & "." & Station(C).Name & vbCrLf
'                        If Not (Len(Station(C).Name) = 4 And Station(C).Name = UCase(Station(C).Name)) Then
'                            T = T & IIf(D, "*", "") & S & "," & .Name & " " & Station(C).Name & "," & _
'                                NumD(.Lat / Rad) & "," & NumD(.Lon / Rad) & "," & _
'                                NumD(.Alt) & vbCrLf
'                            D = D + 1
'                            End If
'                        End If
'                    Next
'                End If
'            End With
'            Next
'        Next
'    Clipboard.Clear: Clipboard.SetText T
'    End Sub

Sub LoadSensors(Optional ByVal Update As Boolean)
    Dim A As Long, N As Long, S As String, T As String
    Message "Loading sensors …"
    T = "": N = 0: SensorN = 0: ReDim Sensor(0)
    T = LoadFile(SensorsDir & "Sensors.lst")
    If T = "" Then T = "TOSHIBA TCM8230MD,1/6,57.4,660,492,1,http://www.sparkfun.com" & vbCrLf
    A = 1: Options.Sensor.Clear
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If SensorN > UBound(Sensor) Then ReDim Preserve Sensor(UBound(Sensor) + 1)
        With Sensor(SensorN)
        .Name = Param(S): .Lens = ValRatio(Param(S)): .FOV = NumN(S)
        .Width = NumN(S): .Height = NumN(S): .Link = S
        Options.Sensor.Add .Name
        End With
        SensorN = SensorN + 1
        Loop
    If SensorN <= 0 Then SensorN = 0: ReDim Sensor(0)
    SensorP = SensorN - 1
    Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub LoadLenses(Optional ByVal Update As Boolean)
    Dim A As Long, N As Long, S As String, T As String
    Message "Loading lenses …"
    T = "": N = 0: LensN = 0: ReDim Lens(0)
    T = LoadFile(SensorsDir & "Lenses.lst")
    If T = "" Then T = "Kyocera Optec 1407 (1/4in 31º),1/4,6.5,2.4,31,24,http://www.kop.co.jp/english/products/unit/ccd.html" & vbCrLf
    A = 1: Options.Lens.List = "Default"
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If LensN > UBound(Lens) Then ReDim Preserve Lens(UBound(Lens) + 1)
        With Lens(LensN)
        .Name = Param(S): .Lens = ValRatio(Param(S)): .Length = NumN(S)
        .FNO = NumN(S): .FOV = NumN(S): .FOVV = NumN(S): .Link = S
        Options.Lens.Add .Name
        End With
        LensN = LensN + 1
        Loop
    If LensN <= 0 Then LensN = 0: ReDim Lens(0)
    Message
    If Update Then If Play = Paused Then Render
    End Sub

Sub LoadSatellites(Optional ByVal Update As Boolean)
    Dim A As Long, N As Long, S As String, T As String, U As String, V As String
    Message "Loading satellites …": DoEvents
    T = "": U = "": SatelliteN = 0: ReDim Satellite(100 - 1): ReDim SatelliteGroup(0)
    T = LoadFile(SatellitesDir & "Satellites.csv")
    If T = "" Then
        T = "Space stations,http://celestrak.com/NORAD/elements/stations.txt" & vbCrLf
        UpdateFile SatellitesDir & "Satellites.csv", T
        'UpdateFile SatellitesDir & "stations.txt", _
            "ISS (ZARYA)             " & vbCrLf & _
            "1 25544U 98067A   12001.88777778  .00014942  00000-0  19331-3 0  7384" & vbCrLf & _
            "2 25544  51.6429 241.3855 0024064 231.9065 306.2035 15.58825088751922" & vbCrLf
        End If
    A = 1: Options.Satellite.Clear: Options.Satellite.Add "All satellites"
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        V = Param(S): GetSatellite V, FileFromURL(S)
        Loop
    If SatelliteN <= 0 Then SatelliteN = 0: ReDim Satellite(0) _
    Else If UBound(Satellite) <> SatelliteN - 1 Then ReDim Preserve Satellite(SatelliteN - 1)
    ReDim IsSatellite(UBound(Satellite))
    SetSatellite
    UpdateSelection
    Message
    If Update Then If Play = Paused Then Render
    'Clipboard.SetText StrAdd: Stop '3273=objects 3058=non-repeat
    End Sub

Sub GetSatellite(Name As String, File As String)
    Dim A As Long, N As Long, S As String, T As String, U As String, _
        Y1 As Long, D1 As Double
    Options.Satellite.Add Name, SatelliteN
    N = UBound(SatelliteGroup) + 1: ReDim Preserve SatelliteGroup(N)
    With SatelliteGroup(N): .Name = Name: .N = SatelliteN: End With
    T = LoadFile(SatellitesDir & File): If T = "" Then GoTo Ext
    A = 1
    Do
        If SatelliteN > UBound(Satellite) Then ReDim Preserve Satellite(UBound(Satellite) + 100)
        With Satellite(SatelliteN)
Ini:
        'ISS (ZARYA)             |
        '1 25544U 98067A   12001.88777778  .00014942  00000-0  19331-3 0  7384
        '2 25544  51.6429 241.3855 0024064 231.9065 306.2035 15.58825088751922
        
        'Name
        S = GetLine(T, A): If A = 0 Then GoTo Ext _
        Else If S = "" Or Len(S) <> 24 Then GoTo Ini
        .Name = Trim(S): .Class = N
        
        'Line 1
        S = GetLine(T, A): If A = 0 Then GoTo Ext _
        Else If S = "" Or Len(S) <> 69 Or Left(S, 2) <> "1 " Then GoTo Ini
        .Number = Trim(Mid(S, 3, 6))                'Satellite number (25544U)
        .Design = Trim(Mid(S, 10, 8))               'International designator (98067A)
        Y1 = Val(Mid(S, 19, 2)) + 1900: If Y1 < 1957 Then Y1 = Y1 + 100 'Epoch year
        D1 = Val(Mid(S, 21, 12)) - 1                'Epoch day
        .T = DateToJ2ks(Y1) + D1 * Day
        .F = Val(Mid(S, 34, 1) & "0" & Trim(Mid(S, 35, 9))) 'First time derivative of the mean motion
        .C = Val(Mid(S, 45, 1) & "0." & Trim(Mid(S, 46, 5)) & "E" & Trim(Mid(S, 51, 2))) 'Second time derivative of the mean motion
        .B = Val(Mid(S, 54, 1) & "0." & Trim(Mid(S, 55, 5)) & "E" & Trim(Mid(S, 60, 2))) 'BSTAR drag term
        .R = Val(Mid(S, 63, 1))                     'Ephemeris type (0=undefined, 1=SGP, 2=SGP4, 3=SDP4, 4=SGP8, 5=SDP8)
        .U = Val(Trim(Mid(S, 65, 4)))               'Element number
        'N = Val(Mid(S, 69, 1))                     'Checksum (modulo 10) (letters, blanks, periods, plus signs = 0; minus signs = 1)
        
        'Line 2
        S = GetLine(T, A): If A = 0 Then GoTo Ext _
        Else If S = "" Or Len(S) <> 69 Or Left(S, 2) <> "2 " Then GoTo Ini
        .I = Val(Trim(Mid(S, 9, 8))) * Rad          'Orbital inclination in radians
        .O = Val(Trim(Mid(S, 18, 8))) * Rad         'Right ascension of the ascending node in radians
        .E = Val("0." & Trim(Mid(S, 27, 8)))        'Orbital eccentricity
        .P = Val(Trim(Mid(S, 35, 8))) * Rad         'Argument of perigee in radians
        .L = Val(Trim(Mid(S, 44, 8))) * Rad         'Mean anomaly at epoch in radians
        .M = Val(Trim(Mid(S, 53, 11))) * A360 / Day 'Mean motion in radians per second
        .N = Val(Trim(Mid(S, 64, 5)))               'Revolution number at epoch
        .S = (398600441800000# / .M / .M) ^ (1 / 3) 'Semi-major axis in meters (EarthGM=398600441800000, EarthR=6378137)
        'N = Val(Mid(S, 69, 1))                     'Checksum (modulo 10) (letters, blanks, periods, plus signs = 0; minus signs = 1)
        
        'For N = 0 To SatelliteN - 1
            'If .Design = Satellite(N).Design Then StrAdd File & vbTab & .Name & "(" & .Design & ")" & vbCrLf: GoTo Ini
            'Next
        End With
        SatelliteN = SatelliteN + 1
        Loop
Ext:
    End Sub

Sub SetSatellite(Optional ByVal N As Long = -1)
    If N >= 0 And N <= UBound(SatelliteGroup) Then SatelliteP = N
    If SatelliteP < 0 Or SatelliteP > UBound(SatelliteGroup) Then _
        SatelliteFirst = 0: SatelliteLast = -1 _
    Else If SatelliteP = 0 Then _
        SatelliteFirst = 0: SatelliteLast = SatelliteN - 1 _
    Else SatelliteFirst = SatelliteGroup(SatelliteP).N: _
        If SatelliteP < UBound(SatelliteGroup) Then _
            SatelliteLast = SatelliteGroup(SatelliteP + 1).N - 1 _
        Else SatelliteLast = SatelliteN - 1
    End Sub

Sub SetSatelliteItem(ByVal N As Long)
    Dim C As Long
    If SatelliteP = 0 Then Exit Sub
    For C = 0 To UBound(SatelliteGroup) - 1
        If N >= SatelliteGroup(C).N And N < SatelliteGroup(C + 1).N Then _
            SetSatellite C: Exit Sub
        Next
    If N >= SatelliteGroup(C).N And N < SatelliteN Then SetSatellite C
    End Sub

Sub DownloadSatellites(Optional ByVal ShowMsg As Boolean = True)
    Dim A As Long, S As String, T As String
    Static IsBusy As Boolean
    If Note.Active Then Exit Sub
    If Not IsBusy Then IsBusy = True Else Exit Sub
    If ShowMsg Then Message "Downloading satellites from server, please wait …", , True
    On Error Resume Next
    T = LoadFile(SatellitesDir & "Satellites.csv"): If T = "" Then Exit Sub
    A = 1
    Do: DoEvents: Sleep 1
        If ShowMsg Then If Not Note.Active Then Message "Download aborted", 2000: GoTo Ext
        S = GetLine(T, A): If S = "" Then Exit Do
        Param S: Main.Download.Begin S, SatellitesDir & FileFromURL(S)
        If Main.Download.IsError Then Message "Download error", 2000: GoTo Ext
        Loop
    LoadSatellites
    On Error GoTo 0
    If ShowMsg Then Message: If Play = Paused Then Render
Ext:
    IsBusy = False
    End Sub

Sub LoadAsteroids(Optional ByVal ShowMsg As Boolean = True, Optional ByVal Update As Boolean)
    Dim C As Long, N As Double, R As Double, A As Double, P As Double, S As String, T As String, _
        V As String, Size As Double, Mag As Double, _
        A1 As Long, A2 As Long, A3 As Long, A4 As Long, A5 As Long, A6 As Long, _
        A7 As Long, A8 As Long, A9 As Long, A10 As Long, A11 As Long, A12 As Long, _
        G1 As Double, G2 As Double, G3 As Double, G4 As Double, G5 As Double, G6 As Double, _
        G7 As Double, G8 As Double, G9 As Double, G10 As Double, G11 As Double, GM As Double
    Static IsBusy As Boolean
    If Not IsBusy Then IsBusy = True Else Exit Sub
    If ShowMsg Then Message "Loading asteroids …": DoEvents
    '[1   ]-[2               ]-[3            ]-[4  ]-[5  ]-[6 ]-[7  ]-[8 ]-[9a][9b][9c][9d][9e][9f]-|
    '     1 Ceres              E. Bowell        3.34  0.12 0.72 848.4 G?      0   0   0   0   0   0 |
    '[10 ][11 ]-[12    ]-[13      ]-[14      ]-[15      ]-[16     ]-[17      ]-[18        ]-[19    ]-|
    '76619 6161 20120314 242.444341  72.145552  80.350514 10.587853 0.07778989   2.76881753 20111219 |
    '[20   ]-[21    ]-[22    ]-[23a  ]-[23b   ]-[24a  ]-[24b   ]-[25a  ]-[25b   ]|
    '1.0E-02 -2.3E-05 20120213 2.3E-02 20121213 2.6E-02 20180124 2.6E-02 20180124|
    StoreObject TypeAsteroid, V, True
    AsteroidN = 0: ReDim Asteroid(10000 - 1): Options.Asteroid.List = AsteroidList
    S = InfoName("AsteroidsFile"): If S = "" Then GoTo Ext
    If OpenFile(PlanetsDir & FileFromURL(S)) Then GoTo Ext
    'Open PlanetsDir & FileFromURL(S) & ".new" For Output As 2
    Select Case AsteroidDensity '0=Low 1=Medium 2=High 3=Very high 4=All
    Case 1: Mag = 4: Size = 200000 'Medium
    Case 2: Mag = 6: Size = 100000 'High
    Case 3: Mag = 10: Size = 1 'Very high
    Case 4: Mag = Infinite: Size = 0 'All
    Case Else: Mag = 2: Size = 350000 'Low
        End Select
    G1 = 0.983 * AU: G2 = 1.017 * AU: G3 = 1.3 * AU              'NearEarth 0.983 1.017 1.300
    G4 = 2.06 * AU: G5 = 2.5 * AU: G6 = 2.82 * AU: G7 = 3.6 * AU 'MainBelt  2.060 2.500 2.820 3.280
    G8 = 4.2 * AU: G9 = 5.4 * AU: G10 = 30 * AU: G11 = 55 * AU   'Outer     5.050 5.400 30.0  55.0
    GM = PlanetGravityMass
    On Error GoTo Ext
    S = Space(268): Do While Not EOF(1)
        Get 1, , S
        R = Val(Mid(S, 43, 5))          ' 4) A5,1X       Absolute magnitude H, mag [see  E. Bowell et al., pp. 549-554, in "Asteroids II", R. P. Binzel et al. (eds.), The University of Arizona Press, Tucson, 1989 and more recent Minor Planet Circulars]. Note that H may be given to 2 decimal places (e.g., 13.41), 1 decimal place (13.4) or as an integer (13), depending on its estimated accuracy. H is given to two decimal places for all unnumbered asteroids, even though it may be very poorly known
        N = Val(Mid(S, 60, 5)) * 1000   ' 7) A5,1X       IRAS diameter, km (blank if unknown; see E. F. Tedesco et al., pp. 1151-1161, op.cit.)
        If R > Mag And N < Size Then GoTo NoSize
        If AsteroidN > UBound(Asteroid) Then _
            ReDim Preserve Asteroid(UBound(Asteroid) + 10000): DoEvents
        With Asteroid(AsteroidN)
        .R = N / 2
        .Name = Trim(Mid(S, 1, 25))     ' 1) A6,1X       Asteroid number (blank if unnumbered)
                                        ' 2) A18,1X      Name or preliminary designation
        N = ValEpoch(S, 107, 0)         '12) I4,2I2.2,1X (T) Epoch of osculation, yyyymmdd (TDT). The epoch is the Julian date ending in 00.5 nearest the date the file was created. Thus, as the file is updated, epochs will succeed each other at 100-day intervals on or after Julian dates ending in 50.5 (19980328, 19980706, 19981014, 19990122,...)
        .L = Val(Mid(S, 116, 10)) * Rad '13) F10.6,1X    (ma) Mean anomaly, deg
        .P = Val(Mid(S, 127, 10)) * Rad '14) F10.6,1X    (so) Argument of perihelion, deg (J2000.0)
        .O = Val(Mid(S, 138, 10)) * Rad '15) F10.6,1X    (lo) Longitude of ascending node, deg (J2000.0)
        .I = Val(Mid(S, 149, 9)) * Rad  '16) F9.6,1X     (i)  Inclination, deg (J2000.0)
        .E = Val(Mid(S, 159, 10))       '17) F10.8,1X    (e)  Eccentricity
        .S = Val(Mid(S, 170, 12)) * AU  '18) F12.8,1X    (a)  Semimajor axis, AU
        If .S <= 0 Then GoTo NoAxis
        .M = Sqr(GM / .S / .S / .S) 'w=sqr(mu/a^3) .. w=mean angular speed (rad/s) mu=GM a=semi-major axis
        .L = .L - .M * N: A = .S * (1 + .E): P = .S * (1 - .E) 'e=(a-p)/(a+p)=1-(2/(1+a/p)) s=(a+p)/2 p=(1-e)*s a=(1+e)*s .. e=eccentricity, s=semi-major axis, a=apoapsis, p=periapsis
        If A < G1 Then A1 = A1 + 1: .Class = 1 _
        Else If P <= G2 Then A2 = A2 + 1: .Class = 2 _
        Else If P <= G3 Then A3 = A3 + 1: .Class = 3 _
        Else If .S <= G4 Then A4 = A4 + 1: .Class = 4 _
        Else If .S <= G5 Then A5 = A5 + 1: .Class = 5 _
        Else If .S <= G6 Then A6 = A6 + 1: .Class = 6 _
        Else If .S <= G7 Then A7 = A7 + 1: .Class = 7 _
        Else If .S <= G8 Then A8 = A8 + 1: .Class = 8 _
        Else If .S <= G9 Then A9 = A9 + 1: .Class = 9 _
        Else If .S <= G10 Then A10 = A10 + 1: .Class = 10 _
        Else If .S <= G11 Then A11 = A11 + 1: .Class = 11 _
        Else A12 = A12 + 1: .Class = 12
        AsteroidN = AsteroidN + 1
        'Print #2, S;
NoAxis:
        End With
NoSize:
        Loop
Ext:
    On Error GoTo 0
    Close
    If AsteroidN <= 0 Then AsteroidN = 0: ReDim Asteroid(0) _
    Else If UBound(Asteroid) <> AsteroidN - 1 Then ReDim Preserve Asteroid(AsteroidN - 1)
    ReDim IsAsteroid(UBound(Asteroid))
    UpdateObjects TypeAsteroid: StoreObject TypeAsteroid, V
    UpdateSelection
    If ShowMsg Then Message
    If Update Then If Play = Paused Then Render
    IsBusy = False
    End Sub

Sub DownloadAsteroids()
    Dim C As Long, N As Long, S As String, T As String, U As String, _
        Q1 As Boolean, Q2 As Boolean
    Static IsBusy As Boolean
    If Note.Active Then Exit Sub
    If Not IsBusy Then IsBusy = True Else Exit Sub
    On Error Resume Next
    U = "Downloading asteroids from server, please wait …": Message U, , True
    S = InfoName("AsteroidsFile"): If S = "" Then GoTo NoFile
    T = PlanetsDir & FileFromURL(S)
    With Main.Download
    .Begin S, T, True, True
    Do: DoEvents: Sleep 1
        If Not Note.Active Then .Cancel T: Message "Download aborted", 2000: GoTo Ext
        C = Int(CDbl(.BytesRead) * 100 / .BytesMax): If N <> C Then N = C: _
            Message U & " (" & N & "%)", , True
        Loop While .IsBusy
    Q1 = Not .IsError
    End With
NoFile:
    U = "Downloading comets from server, please wait …": Message U, , True
    S = InfoName("CometsFile"): If S = "" Then GoTo NoFile2
    T = PlanetsDir & FileFromURL(S)
    With Main.Download
    .Begin S, T, True, True
    Do: DoEvents: Sleep 1
        If Not Note.Active Then .Cancel T: Message "Download aborted", 2000: GoTo Ext
        C = Int(CDbl(.BytesRead) * 100 / .BytesMax): If N <> C Then N = C: _
            Message U & " (" & N & "%)", , True
        Loop While .IsBusy
    Q2 = Not .IsError
    End With
NoFile2:
    If Q1 Then LoadAsteroids
    If Q2 Then LoadComets
    If Q1 Or Q2 Then Message Else Message "Download error", 2000
    If Play = Paused Then Render
Ext:
    On Error GoTo 0
    IsBusy = False
    End Sub

Sub LoadComets(Optional ByVal Update As Boolean)
    Dim A As Long, C As Long, N As Long, S As String, T As String, U As String, V As String, _
        GM As Double
    Static IsBusy As Boolean
    If Not IsBusy Then IsBusy = True Else Exit Sub
    Message "Loading comets …": DoEvents
    StoreObject TypeComet, V, True
    Select Case AsteroidDensity
    Case 1: N = 20: Case 2: N = 70: Case 3: N = 326: Case 4: N = 0: Case Else: N = 4
        End Select
    GM = PlanetGravityMass
    T = "": CometN = 0: ReDim Comet(1000 - 1)
    'T = LoadFile(PlanetsDir & "Comets.ini"): If T = "" Then GoTo NoFile
    S = InfoName("CometsFile"): If S = "" Then GoTo NoFile
    T = LoadFile(PlanetsDir & FileFromURL(S)): If T = "" Then GoTo NoFile
    A = 1: GetLineLf T, A: GetLineLf T, A
    Do
        S = GetLineLf(T, A): If S = "" Then Exit Do
        'If Not IsParamE(S, "Comet") Then GoTo NoItem
        'If Left(S, 1) = "#" Then GoTo NoItem
        If CometN > UBound(Comet) Then ReDim Preserve Comet(UBound(Comet) + 1000)
        With Comet(CometN)
        ''#Name,Epoch[YYYYMMDD.D],SemiMajorAxis[AU],Eccentricity,OrbitalInclination[deg],ArgumentOfPeriapsis[deg],LongitudeOfAscendingNode[deg]
        ''1P Halley,19860205.89532,0.58597811,0.96714291,162.26269,111.33249,58.42008
        '.Name = Param(S): U = Param(S): .S = NumN(S) * AU: .E = NumN(S) 'Name,Epoch,SemiMajorAxis,Eccentricity
        '.I = NumN(S) * Rad: .P = NumN(S) * Rad: .O = NumN(S) * Rad 'Inclination,LongitudeOfPeriapsis,LongitudeOfAscendingNode
        '[ N]/[Name                                 ][Epoch] [q        ] [e       ] [i      ] [w      ] [Node   ] [Tp          ] [Ref       ]
        '  1P/Halley                                   49400  0.58597811 0.96714291 162.26269 111.33249  58.42008 19860205.89532 JPL J863/77
        .Name = Replace(Trim(Mid(S, 1, 43)), "/", " ")          '  1, 4  Num         Number assigned to the asteroid or comet. Numbers are only assigned to secure short-period (<200 year) comets
        .S = Val(Trim(Mid(S, 53, 11))) * AU                     ' 53,11  q     (AU)  Perihelion distance (comets only)
        .E = Val(Trim(Mid(S, 65, 10)))                          ' 65,10  e           Eccentricity of the orbit
        .I = Val(Trim(Mid(S, 76, 9))) * Rad                     ' 76, 9  i     (deg) Inclination of the orbit with respect to the J2000 ecliptic plane
        .P = Val(Trim(Mid(S, 86, 9))) * Rad                     ' 86, 9  w     (deg) Argument of perihelion (J2000-Ecliptic)
        .O = Val(Trim(Mid(S, 96, 9))) * Rad                     ' 96, 9  Node  (deg) Longitude of the ascending node (J2000-Ecliptic)
        U = Trim(Mid(S, 106, 14))                               '106,14  Tp          Time of perihelion passage (comets only), formatted as a calendar date (YYYYMMDD.DDD) where "YYYY" is the year, "MM" is the numeric month, and "DD.DDD" is the day and day fraction
        If .E > 0.99 Then .E = 0.99
        .S = .S / (1 - .E): .M = Sqr(GM / .S / .S / .S): .L = -ValEpoch(U) * .M
        .Class = 0: C = InStr(.Name, " "): If C <= 1 Then GoTo NoClass
        Select Case UCase(Mid(.Name, C - 1, 1))
        Case "P": .Class = 1 'Periodic
        Case "C": .Class = 2 'Non periodic
        Case "X": .Class = 3 'Non reliable orbit
        Case "D": .Class = 4 'Disappeared
        Case "A": .Class = 5 'Not a comet
            End Select
NoClass:
        End With
        CometN = CometN + 1: If N > 0 And CometN >= N Then Exit Do
NoItem:
        Loop
NoFile:
    If CometN <= 0 Then CometN = 0: ReDim Comet(0) _
    Else If UBound(Comet) <> CometN - 1 Then ReDim Preserve Comet(CometN - 1)
    ReDim IsComet(UBound(Comet))
    UpdateObjects TypeComet: StoreObject TypeComet, V
    UpdateSelection
    Message
    If Update Then If Play = Paused Then Render
    IsBusy = False
    End Sub

'Sub CopyAsteroids()
'    Dim A As Long, B As Long, N As Long, D As Double, H As Double, S As String, T As String
'    ' 1) A6,1X       Asteroid number (blank if unnumbered)
'    ' 2) A18,1X      Name or preliminary designation
'    ' 4) A5,1X       Absolute magnitude H, mag [see  E. Bowell et al., pp. 549-554, in "Asteroids II", R. P. Binzel et al. (eds.), The University of Arizona Press, Tucson, 1989 and more recent Minor Planet Circulars]. Note that H may be given to 2 decimal places (e.g., 13.41), 1 decimal place (13.4) or as an integer (13), depending on its estimated accuracy. H is given to two decimal places for all unnumbered asteroids, even though it may be very poorly known
'    ' 7) A5,1X       IRAS diameter, km (blank if unknown; see E. F. Tedesco et al., pp. 1151-1161, op.cit.)
'    '12) I4,2I2.2,1X (T) Epoch of osculation, yyyymmdd (TDT). The epoch is the Julian date ending in 00.5 nearest the date the file was created. Thus, as the file is updated, epochs will succeed each other at 100-day intervals on or after Julian dates ending in 50.5 (19980328, 19980706, 19981014, 19990122,...)
'    '13) F10.6,1X    (ma) Mean anomaly, deg
'    '14) F10.6,1X    (so) Argument of perihelion, deg (J2000.0)
'    '15) F10.6,1X    (lo) Longitude of ascending node, deg (J2000.0)
'    '16) F9.6,1X     (i)  Inclination, deg (J2000.0)
'    '17) F10.8,1X    (e)  Eccentricity
'    '18) F12.8,1X    (a)  Semimajor axis, AU
'    T = LoadFile(PlanetsDir & "astorb.dat"): If T = "" Then Stop 'ftp://ftp.lowell.edu/pub/elgb/astorb.dat
'    A = 1: N = 1
'    Do
'        B = A: S = GetLineLf(T, A): If S = "" Then Exit Do
'        H = Val(Mid(S, 43, 5))          ' 4) A5,1X       Absolute magnitude H, mag [see  E. Bowell et al., pp. 549-554, in "Asteroids II", R. P. Binzel et al. (eds.), The University of Arizona Press, Tucson, 1989 and more recent Minor Planet Circulars]. Note that H may be given to 2 decimal places (e.g., 13.41), 1 decimal place (13.4) or as an integer (13), depending on its estimated accuracy. H is given to two decimal places for all unnumbered asteroids, even though it may be very poorly known
'        D = Val(Mid(S, 60, 5)) * 1000   ' 7) A5,1X       IRAS diameter, km (blank if unknown; see E. F. Tedesco et al., pp. 1151-1161, op.cit.)
'        If D > 0 Or H < 10 Then StrAdd Mid(T, B, A - B) 'Copy only asteroids with known diameter and an absolute magnitude low enough
'        N = N + 1
'        Loop
'    S = StrAdd: Clipboard.Clear: Clipboard.SetText S: Stop
'    End Sub

'Sub CopyComets()
'    Dim A As Long, N As Long, S As String, T As String
'    '  1, 4  Num         Number assigned to the asteroid or comet. Numbers are only assigned to secure short-period (<200 year) comets
'    '  6,38  Name        Name given to the numbered asteroid or comet
'    ' 45, 7  Epoch (MJD) Epoch of the elements represented as the Modified Julian Date (MJD), which is defined as the Julian date - 2400000.5
'    ' 53,11  q     (AU)  Perihelion distance (comets only)
'    ' 65,10  e           Eccentricity of the orbit
'    ' 76, 9  i     (deg) Inclination of the orbit with respect to the J2000 ecliptic plane
'    ' 86, 9  w     (deg) Argument of perihelion (J2000-Ecliptic)
'    ' 96, 9  Node  (deg) Longitude of the ascending node (J2000-Ecliptic)
'    '106,14  Tp          Time of perihelion passage (comets only), formatted as a calendar date (YYYYMMDD.DDD) where "YYYY" is the year, "MM" is the numeric month, and "DD.DDD" is the day and day fraction
'    '121...  Ref         Orbit solution reference
'    '[Num/Name                                 ] [Epoch] [    q    ] [   e    ] [   i   ] [   w   ] [ Node  ] [     Tp     ] [ Ref .....
'    '  1P/Halley                                   49400  0.58597811 0.96714291 162.26269 111.33249  58.42008 19860205.89532 JPL J863/77
'    T = LoadFile(PlanetsDir & "ELEMENTS.COMET"): If T = "" Then Stop 'http://ssd.jpl.nasa.gov/dat/ELEMENTS.COMET
'    A = 1: N = 1: GetLineLf T, A: GetLineLf T, A
'    Do
'        S = GetLineLf(T, A): If S = "" Then Exit Do
'        StrAdd "Comet" & N & "=" & Replace(Trim(Mid(S, 1, 43)), "/", " ") & "," & _
'            Trim(Mid(S, 106, 14)) & "," & Trim(Mid(S, 53, 11)) & "," & Trim(Mid(S, 65, 10)) & "," & _
'            Trim(Mid(S, 76, 9)) & "," & Trim(Mid(S, 86, 9)) & "," & Trim(Mid(S, 96, 9)) & _
'            vbCrLf
'        N = N + 1
'        Loop
'    Clipboard.Clear: Clipboard.SetText StrAdd: Stop
'    End Sub

Sub LoadMoons(Optional ByVal Update As Boolean)
    Dim A As Long, C As Long, M As Double, N As Double, GM As Double, _
        S As String, T As String, U As String, V As String
    Static IsBusy As Boolean
    If Not IsBusy Then IsBusy = True Else Exit Sub
    Message "Loading moons …": DoEvents
    StoreObject TypeMoon, V, True
    Select Case AsteroidDensity
    Case 1: M = 10000: Case 2 To 4: M = 0: Case Else: M = 500000
        End Select
    T = "": MoonN = 0: ReDim Moons(100 - 1)
    For C = ToMercury To ToPluto
        T = LoadFile(PlanetsDir & Planets(C) & ".Moons.ini"): If T = "" Then GoTo NoFile
        GM = PlanetGravityMass(C)
        A = 1
        Do
            S = GetLineLf(T, A): If S = "" Then Exit Do
            'If Not IsParamE(S, "Moon") Then GoTo NoItem
            'If Left(S, 1) = "#" Then GoTo NoItem
            If MoonN > UBound(Moons) Then ReDim Preserve Moons(UBound(Moons) + 100)
            With Moons(MoonN)
            '#Name,Radius[km],Mass[kg],Epoch[YYYYMMDD.D],SemiMajorAxis[km],Eccentricity,ArgumentOfPeriapsis[deg],MeanAnomaly[deg],OrbitalInclination[deg],LongitudeOfAscendingNode[deg],LongitudeRate[deg/year],SiderealPeriod[day],ArgumentOfPeriapsisPrecessionPeriod[year],LongitudeOfAscendingNodePrecessionPeriod[year],RightAscensionOfLaplacePlane[deg],DeclinationOfLaplacePlane[deg],TiltOfLaplacePlane[deg]
            'Io,1818.1,8.9319E+22,19970116,421800,0.0041,84.129,342.021,0.036,43.977,203.4889583,1.769,1.625,7.420,268.057,64.495,0.000
            .Name = Param(S): .R = NumN(S) * 1000: If .R < M Then GoTo NoSize
            NumN S: N = ValEpoch(Param(S)): .S = NumN(S) * 1000: .E = NumN(S)
            .P = NumN(S) * Rad: .L = NumN(S) * Rad: .I = NumN(S) * Rad: .O = NumN(S) * Rad
            If .S <= 0 Then GoTo NoSize
            .M = Sqr(GM / .S / .S / .S): .L = .L - .M * N: .Class = -C
            MoonN = MoonN + 1
NoSize:
            End With
NoItem:
            Loop
NoFile:
        Next
    If MoonN <= 0 Then MoonN = 0: ReDim Moons(0) _
    Else If UBound(Moons) <> MoonN - 1 Then ReDim Preserve Moons(MoonN - 1)
    ReDim IsMoon(UBound(Moons))
    UpdateObjects TypeMoon: StoreObject TypeMoon, V
    UpdateSelection
    Message
    If Update Then If Play = Paused Then Render
    IsBusy = False
    End Sub

Function LoadGraph(Cam As Camera, G As Graph2D, _
        Optional Name As String, Optional ByVal Update As Boolean = True, _
        Optional ByVal ResetAngles As Boolean = True, Optional ByVal ResetPosition As Boolean = True, _
        Optional ByVal AdjustScale As Boolean = True, Optional ByVal Ask As Boolean) As Boolean
    Dim A As Long, B As Long, C As Long, P As Long, N As Double, L As Double, _
        S As String, T As String, U As String, V As String, _
        X0 As Long, Y0 As Long, X1 As Long, Y1 As Long
    Static Op As OpenFileName
    
    If Name <> "" Then Name = GraphsDir & Name & ".csv" _
    Else If Ask Then Name = GetOpenFile(Op, , FileCSV, GraphsDir)
    If Name = "" Then Exit Function
    T = LoadFile(Name): If T = "" Then Exit Function
    
    G.X1 = 0: G.X2 = 0: G.Y1 = 0: G.Y2 = 0: G.Z1 = 0: G.Z2 = 0
    G.Name = FileNameFromPath(Name): G.NameZ = "Isp"
    G.XT = 10: G.YT = 10: G.XN = G.XT * 100: G.YN = G.YT * 100
    ReDim G.Hdr(G.YN)
    
    'Header
    A = 1: X0 = 0: Y0 = 0: X1 = 0: Y1 = 0: P = 0
    S = GetLine(T, A): If S = "" Then GoTo Ext
    If InStr(S, ";") > 0 And InStr(S, ",") = 0 Then T = ToCSV(T): S = ToCSV(S)
    Do
        If S <> "" Then U = Trim(Param(S)) Else Exit Do
        If P = 0 Then
            C = InStr(U, ":"): If C Then G.NameZ = Left(U, C - 1): U = Mid(U, C + 1)
            P = 1: Y0 = 1: G.NameX = U: G.Hdr(0) = U
        Else
            N = Val(RightNum(U)): V = LeftText(U)
            If Not IsLimitDbl(N, 0, 100) Or V = "" Then Exit Do
            If G.Y1 = 0 Then Y0 = RoundInt(N * G.YT): G.Y1 = Y0: G.Y2 = Y0: G.NameY = V _
            Else If Y1 = 0 Then Y1 = RoundInt(N * G.YT) - Y0: If Y1 < 1 Then Exit Do
            Y0 = Y0 + Y1: If Y0 <> RoundInt(N * G.YT) Or Y0 >= G.YN Then Exit Do
            'If Y0 > UBound(G.Hdr) Then ReDim Preserve G.Hdr(UBound(G.Hdr) + 1000)
            G.Hdr(Y0) = U: If Y0 > G.Y2 Then G.Y2 = Y0
            End If
        Loop
    If Y1 < 1 Then Y1 = 1
    If Not IsLimit(G.Y1, 0, G.YN) Or Not IsLimit(G.Y2, 0, G.YN) Or _
        G.Y1 > G.Y2 Or G.NameX = "" Or G.NameY = "" Then GoTo Ext
    
    ''Scan number of lines
    'B = A: S = GetLine(T, B): If S <> "" Then G.X1 = RoundInt(NumN(S) * 10) Else GoTo Ext
    'Do
    '    S = GetLine(T, B): If S <> "" Then G.X2 = RoundInt(NumN(S) * 10) Else Exit Do
    '    Loop
    'GraphXR = 0.1: GraphYR = 0.1: G.XT = 1: G.YT = 1
    'GraphXI = G.X1 * GraphXR: G.XN = G.X2 - G.X1: GraphXS = G.XN * GraphXR
    'GraphYI = G.Y1 * GraphXR: G.YN = G.Y2 - G.Y1: GraphYS = G.YN * GraphXR
    'G.X1 = 0: G.X2 = 0
    'If UBound(G.Hdr) <> G.YN - 1 Then ReDim Preserve G.Hdr(G.YN - 1)
    ReDim G.Value(G.XN, G.YN)
    
    'Lines
    Do
        S = GetLine(T, A): If S = "" Then Exit Do
        Y0 = 0: P = 0
        Do
            If S <> "" Then L = NumN(S) Else Exit Do
            If P = 0 Then
                P = 1: Y0 = G.Y1
                If X0 = 0 Then
                    If Not IsLimitDbl(L, 0, 100) Then GoTo NoLine
                    X0 = RoundInt(L * G.XT): G.X1 = X0: G.X2 = X0
                Else
                    If X1 = 0 Then X1 = RoundInt(L * G.XT) - X0: If X1 < 1 Then GoTo NoLine
                    X0 = X0 + X1
                    End If
                If X0 <> RoundInt(L * G.XT) Then GoTo NoLine
                If X0 > G.X2 Then G.X2 = X0
            Else
                If L < 0 Then L = 0 Else If L > 10000 Then L = 10000
                If X0 < 0 Or X0 > G.XN Or Y0 < 0 Or Y0 > G.YN Then GoTo NoLine
                G.Value(X0, Y0) = L
                Y0 = Y0 + Y1
                End If
            Loop
        Loop
NoLine:
    If X1 < 1 Then X1 = 1
    If Not IsLimit(G.X1, 0, G.XN) Or Not IsLimit(G.X2, 0, G.XN) Or _
        G.X1 > G.X2 Then GoTo Ext
    
    G.XT = X1: G.YT = Y1
    LoadGraph = True
Ext:
    InitGraph Cam, G, Update, ResetAngles, ResetPosition, AdjustScale
    End Function

Sub InitGraph(Cam As Camera, G As Graph2D, Optional ByVal Update As Boolean = True, _
        Optional ByVal ResetAngles As Boolean = True, Optional ByVal ResetPosition As Boolean = True, _
        Optional ByVal AdjustScale As Boolean = True)
    Dim L As Long, H1 As Double, X1 As Long, Y1 As Long, _
        X0 As Long, Y0 As Long, Z0 As Double
Ini:
    G.SelX = -1: G.SelY = -1: G.SelZ = 0
    G.X1 = InfiniteInt: G.X2 = -InfiniteInt
    G.Y1 = InfiniteInt: G.Y2 = -InfiniteInt
    G.Z1 = Infinite: G.Z2 = -Infinite
    X1 = Limit(G.XT, 1, 10): Y1 = Limit(G.YT, 1, 10)
    For X0 = 0 To G.XN Step X1
        For Y0 = 0 To G.YN Step Y1
            Z0 = G.Value(X0, Y0)
            If Z0 <> 0 Then
                Z0 = Z0 * G.ScaleZ
                If X0 < G.X1 Then G.X1 = X0
                If X0 > G.X2 Then G.X2 = X0
                If Y0 < G.Y1 Then G.Y1 = Y0
                If Y0 > G.Y2 Then G.Y2 = Y0
                If Z0 < G.Z1 Then G.Z1 = Z0
                If Z0 > G.Z2 Then G.Z2 = Z0: G.TopX = X0: G.TopY = Y0
                End If
            Next
        Next
    If G.XT > 10 Or G.YT > 10 Then G.Tools = G.Tools Or GraphAdjust
    If (G.Tools And GraphAdjust) = 0 Then _
        G.X1 = 0: G.X2 = G.XN: G.Y1 = 0: G.Y2 = G.YN: _
        G.XT = Limit(G.XT, 1, 10): G.YT = Limit(G.YT, 1, 10)
    If (G.Tools And GraphAdjust) = 0 Or (G.Tools And GraphAdjustZ) = 0 Then G.Z1 = 0
    If G.X1 > G.X2 Then G.X1 = 0: G.X2 = 0
    G.XC = Int((G.X1 + G.X2) / 2 / G.XT) * G.XT: G.XS = G.X2 - G.X1
    If G.Y1 > G.Y2 Then G.Y1 = 0: G.Y2 = 0
    G.YC = Int((G.Y1 + G.Y2) / 2 / G.YT) * G.YT: G.YS = G.Y2 - G.Y1
    If G.Z1 > G.Z2 Then G.Z1 = 0: G.Z2 = 0
    G.ZC = Int((G.Z1 + G.Z2) / 2): G.ZS = G.Z2 - G.Z1
    G.CamZ = 0
    If G.XS > G.CamZ Then G.CamZ = G.XS
    If G.YS > G.CamZ Then G.CamZ = G.YS
    Z0 = G.ScaleZ: If AdjustScale And (G.Tools And GraphAutoScale) <> 0 Then _
        If G.ZS > 0 Then H1 = G.CamZ / G.ZS: If H1 > 3 Then Z0 = 3 Else If H1 < 1 Then Z0 = 1
    If G.ScaleZ <> Z0 Then L = L + 1: If L < 2 Then G.ScaleZ = Z0: GoTo Ini
    If G.ZS > G.CamZ Then G.CamZ = Int(G.ZS)
    If G.CamZ = 0 Then G.CamZ = 1000
    G.CamZ = G.CamZ * 2.5
    Z0 = G.ZS / G.ScaleZ: Z0 = Switch(Z0 > 3500, 1000, Z0 > 1500, 500, Z0 > 600, 200, Z0 > 350, 100, _
        Z0 > 150, 50, Z0 > 60, 20, Z0 > 35, 10, Z0 > 15, 5, Z0 > 6, 2, True, 1)
    G.ZS1 = Int(G.Z1 / G.ScaleZ / Z0 + 1) * Z0 * G.ScaleZ: G.ZST = Z0 * G.ScaleZ
    With Cam
    If Not .F.Sync Then
        If ResetAngles Then .AngleU = 45: .AngleV = 45: .AngleW = 0
        If ResetPosition Then .X = G.XC: .Y = G.YC: .Z = G.ZC: .Altitude = G.CamZ
        End If
    End With
    'Caption = IIf(G.Name <> "", "3D Graph - " & G.Name, "3D Graph")
    If Update Then DeleteList G.List, 2: If Not Cam.F Is Nothing Then _
        If Cam.F.Visible Then InitCamera Cam: RenderModel Cam Else ActivateView Cam
    End Sub

Sub ClearGraph(Cam As Camera, G As Graph2D)
    G.X1 = 0: G.X2 = 0: G.Y1 = 0: G.Y2 = 0
    G.SelX = -1: G.SelY = -1: G.SelZ = 0
    G.Name = "": G.NameX = "": G.NameY = "": G.NameZ = ""
    Erase G.Value, G.Hdr
    DeleteList G.List, 2: RenderModel Cam
    End Sub

Sub LoadPropellants(Optional ByVal LoadData As Boolean = True)
    Message "Loading propellants …"
    If LoadData Then UpdateComponents
    Propellants = LoadFile(PropellantsDir & "Propellants.lst")
    If Propellants <> "" Then UpdatePropellants Else DefaultPropellants
    Message
    End Sub

Sub SavePropellants()
    UpdateFile PropellantsDir & "Propellants.lst", Propellants
    End Sub

Sub UpdateComponents()
    Dim C As Long, S As String
    On Error GoTo Ext
    With Editor
    .List.Clear
    InitCP PropellantsDir & "Propellant.dat", PropellantsDir & "Thermo.dat"
    C = 0: Do
        S = GetCP(C): If S <> "" Then .List.AddItem S: C = C + 1 Else Exit Do
        Loop
Ext:
    End With
    On Error GoTo 0
    End Sub

Sub UpdatePropellants()
    Dim A As Long, S As String
    With Editor
    .Fuel.Clear: A = 1: Do
        S = GetLine(Propellants, A): If S <> "" Then .Fuel.AddItem ParamB(Mid(S, 2)) Else Exit Do
        Loop
    If .Fuel.ListCount > 0 Then .Fuel.ListIndex = .Fuel.ListCount - 1
    End With
    End Sub

Sub DefaultPropellants()
    Propellants = _
        "|Radical|100%|Hydrogen Free Radical||100atm|0.01atm|125.3:1|5672K|1g/cm³|1$/kg|Liquid|Linear|1|3%|1488.2s|1477.2s" & vbCrLf & _
        "|LF2/LH2/LLi|50%|Fluorine (Liquid)|30%|Hydrogen (Cryogenic)|20%|Lithium (Pure Crystaline)||100atm|0.01atm|315.2:1|2260K|0.205g/cm³|17.32$/kg|Liquid|Linear|1|3%|510.9s|502.5s" & vbCrLf & _
        "|LF2/LLi|70%|Fluorine (Liquid)|30%|Lithium (Pure Crystaline)||100atm|0.01atm|239.9:1|5761K|0.973g/cm³|20.00$/kg|Liquid|Linear|1|3%|422.6s|417.1s" & vbCrLf & _
        "|LF2/LH2|83%|Fluorine (Liquid)|17%|Hydrogen (Cryogenic)||100atm|0.01atm|212.9:1|3238K|0.343g/cm³|18.18$/kg|Liquid|Linear|1|3%|471.3s|465.8s" & vbCrLf & _
        "|LF2/UDMH|72%|Fluorine (Liquid)|28%|Unsym-Dimethylhydrazine (UDMH)||100atm|0.01atm|265.3:1|4307K|1.195g/cm³|21.13$/kg|Liquid|Linear|1|3%|370.3s|365.1s" & vbCrLf & _
        "|LO2/LH2|78%|Oxygen (Liquid)|22%|Hydrogen (Cryogenic)||100atm|0.01atm|273.9:1|2748K|0.267g/cm³|0.58$/kg|Liquid|Linear|0|3%|468.0s|461.3s" & vbCrLf & _
        "|LO2/UDMH|58%|Oxygen (Liquid)|42%|Unsym-Dimethylhydrazine (UDMH)||100atm|0.01atm|309.6:1|3477K|0.957g/cm³|9.04$/kg|Liquid|Linear|0|3%|364.6s|358.7s" & vbCrLf & _
        "|LO2/RP-1|72%|Oxygen (Liquid)|28%|RP-1||100atm|0.01atm|356.6:1|3517K|1.019g/cm³|0.11$/kg|Liquid|Linear|0|3%|342.8s|336.6s" & vbCrLf & _
        "|LO2/C2H5OH|63%|Oxygen (Liquid)|37%|Ethanol (Liquid)  -Rnl-||100atm|0.01atm|376.4:1|3463K|0.979g/cm³|0.11$/kg|Liquid|Linear|0|3%|347.9s|341.3s" & vbCrLf & _
        "|N2O4/MMH|64%|Nitrogen Tetroxide (Liq.)|36%|Monomethyl Hydrazine (MMH)||33atm|0.01atm|133.7:1|3249K|1.164g/cm³|9.48$/kg|Liquid|Linear|1|3%|334.2s|327.1s" & vbCrLf & _
        "|N2O4/UDMH|68%|Nitrogen Tetroxide (Liq.)|32%|Unsym-Dimethylhydrazine (UDMH)||33atm|0.01atm|134.2:1|3282K|1.132g/cm³|10.99$/kg|Liquid|Linear|1|3%|331.1s|324.0s" & vbCrLf & _
        "|N2O4/RP-1|80%|Nitrogen Tetroxide (Liq.)|20%|RP-1||33atm|0.01atm|147.2:1|3255K|1.236g/cm³|4.85$/kg|Liquid|Linear|0|3%|309.0s|301.8s" & vbCrLf & _
        "|H2O2/UDMH|78%|Hydrogen Peroxide (100 Pc)|22%|Unsym-Dimethylhydrazine (UDMH)||33atm|0.01atm|155.6:1|2926K|1.197g/cm³|5.97$/kg|Liquid|Linear|1|3%|337.2s|329.0s" & vbCrLf & _
        "|H2O2/RP-1|87%|Hydrogen Peroxide (100 Pc)|13%|RP-1||33atm|0.01atm|166.7:1|2847K|1.280g/cm³|1.78$/kg|Liquid|Linear|0|3%|323.4s|315.1s" & vbCrLf & _
        "|APCP|70%|Ammonium Perchlorate (AP)|10%|Styrene|20%|Aluminum (Pure Crystaline)||33atm|1atm|5.4:1|3635K|1.900g/cm³|37.85$/kg|Solid|Finocyl 5-Star|0|5%|258.7s|233.4s" & vbCrLf & _
        "|C1|87%|Ammonium Perchlorate (AP)|13%|Styrene||33atm|1atm|4.9:1|2970K|1.762g/cm³|37.85$/kg|Solid|Finocyl 5-Star|0|5%|247.5s|224.7s" & vbCrLf & _
        "|C2|80%|Ammonium Perchlorate (AP)|17%|Styrene|3%|Aluminum (Pure Crystaline)||33atm|1atm|4.9:1|2781K|1.724g/cm³|37.85$/kg|Solid|Finocyl 5-Star|0|5%|244.6s|222.5s" & vbCrLf & _
        "|KNSU|65%|Potassium Nitrate|35%|Sucrose (Table Sugar)||33atm|1atm|5.7:1|1696K|1.900g/cm³|48$/kg|Solid|Finocyl 5-Star|0|5%|157.8s|141.6s" & vbCrLf & _
        "|KNDX|65%|Potassium Nitrate|35%|Dextrose (Glucose) -Rnk-||33atm|1atm|5.7:1|1687K|1.890g/cm³|48$/kg|Solid|Finocyl 5-Star|0|5%|156.9s|140.7s" & vbCrLf & _
        "|KNSB|65%|Potassium Nitrate|35%|Sorbitol -Rnk-||33atm|1atm|5.7:1|1589K|1.837g/cm³|48$/kg|Solid|Finocyl 5-Star|0|5%|156.1s|140.1s" & vbCrLf & _
        "|KNER|65%|Potassium Nitrate|35%|Erythritol Tetranitrate||33atm|1atm|5.8:1|1325K|3.266g/cm³|48$/kg|Solid|Finocyl 5-Star|0|5%|125.1s|112.1s" & vbCrLf & _
        "|Powder|75%|Potassium Nitrate|15%|Carbon (Graphite)|10%|Sulfur||33atm|1atm|5.9:1|1902K|2.137g/cm³|325$/kg|Solid|Finocyl 5-Star|0|5%|136.4s|122.0s" & vbCrLf
    UpdatePropellants
    End Sub

Sub LoadEngines(Optional ByVal ShowMsg As Boolean = True, Optional ByVal Update As Boolean)
    Dim N As Long
    If ShowMsg Then Message "Loading engines …"
    With Stages
    LoadList EnginesDir, "*.eng"
    EngineN = 0: For N = 0 To List.Count - 1
        If N > .mnEn.UBound Then Load .mnEn(N)
        If .mnEn(N).Caption <> List(N + 1) Then .mnEn(N).Caption = List(N + 1)
        .mnEn(N).Visible = True
        If EngineN < 1 Then ReDim Engine(0) Else ReDim Preserve Engine(EngineN)
        If LoadEngine(Engine(EngineN), .mnEn(N).Caption & ".eng") Then EngineN = EngineN + 1
        Next
    For N = .mnEn.UBound To N Step -1
        If N > 0 Then Unload .mnEn(N) Else .mnEn(0).Visible = False
        Next
    If EngineN < 1 Then Erase Engine
    End With
    If ShowMsg Then Message
    If Update Then Stages.UpdateEngines: If Play = Paused And T = 0 Then Init
    End Sub

Function LoadEngine(E As Engine, Name As String, Optional Text As String, _
        Optional ByVal Normalize As Boolean) As Boolean
    Dim A As Long, N As Long, S As String, F As Double, T As Double
    If Text = "" Then Text = LoadFile(EnginesDir & Name): If Text = "" Then Exit Function
    With E
    A = 1: Do
        S = GetLine(Text, A): If S = "" Then Exit Function
        Loop While Left(S, 1) = ";"
    ParamX S: .Diameter = ParamN(S) / 1000: .Length = ParamN(S) / 1000
    ParamX S: .DryMass = ParamN(S): .FullMass = ParamN(S)
    ParamX S: .Name = FileName(Name)
    N = 0: ReDim .ThrustCurve(N)
    Do
        S = GetLine(Text, A): If S = "" Or Left(S, 1) = ";" Then Exit Do
        N = N + 1: ReDim Preserve .ThrustCurve(N)
        .ThrustCurve(N).T = ParamN(S): .ThrustCurve(N).F = ParamN(S)
        F = F + (.ThrustCurve(N).F + .ThrustCurve(N - 1).F) / 2 * (.ThrustCurve(N).T - .ThrustCurve(N - 1).T)
        Loop
    .ThrustN = N + 1: If .ThrustN < 1 Then Exit Function
    T = .ThrustCurve(.ThrustN - 1).T
    If .ThrustCurve(.ThrustN - 1).F <> 0 Then .ThrustCurve(N).F = 0
    If Normalize Then For N = 0 To .ThrustN - 1: .ThrustCurve(N).F = .ThrustCurve(N).F / F: Next
    .ThrustS = F / T: .IspS = F / G0 / .DryMass 'Mean thrust and Specific impulse at Sea Level
    .ThrustV = .ThrustS * 1.18: .IspV = .IspS * 1.18 'Mean thrust and Specific impulse in Vacuum
    .BurnRate = .DryMass / T 'Mean propellant consumption
    .DryMass = .FullMass - .DryMass 'Dry mass
    End With
    LoadEngine = True
    End Function

Sub LoadCurves(Optional ByVal ShowMsg As Boolean = True, Optional ByVal Update As Boolean)
    Dim C As Long, N As Long, S As String, U As String
    If ShowMsg Then Message "Loading thrust curves …"
    With Editor.Curve
    .Clear: .Add "Linear"
    ReDim Curve(0): N = 0: LoadList EnginesDir, "*.cur"
    For C = 0 To List.Count - 1
        If N > 0 Then ReDim Preserve Curve(N)
        If LoadEngine(Curve(N), List(C + 1) & ".cur", , True) Then .Add List(C + 1): N = N + 1
        Next
    If N <= 0 Then
        S = "Finocyl 5-Star": U = Replace("Curve 1 1 P 1 1 Generic|" & _
            "   0.024 0.986|   0.039 1.000|   0.419 0.843|   0.761 0.780|" & _
            "   0.800 0.292|   0.886 0.069|   0.930 0.023|   1.000 0.000|", "|", vbCrLf)
        If LoadEngine(Curve(N), S & ".cur", U, True) Then .Add S Else Erase Curve
        End If
    End With
    If ShowMsg Then Message
    If Update Then Editor.Update: If Play = Paused And T = 0 Then Init
    End Sub

Function GetEngine(E As Engine, Name As String) As Boolean
    Dim N As Long
    For N = 0 To EngineN - 1
        If Engine(N).Name = Name Then E = Engine(N): GetEngine = True: Exit Function
        Next
    End Function

Function GetCurve(E As Engine, Name As String, _
        Optional T As Double = 1, Optional F As Double = 1) As Boolean
    Dim A As Long, N As Long
    With E
    On Error Resume Next: N = UBound(Curve) + 1: On Error GoTo 0
    For N = 0 To N - 1
        If Curve(N).Name = Name Then
            .ThrustCurve = Curve(N).ThrustCurve: .ThrustN = Curve(N).ThrustN: .Name = Curve(N).Name
            If T <> 1 Or F <> 1 Then
                For A = 0 To UBound(.ThrustCurve)
                    .ThrustCurve(A).T = .ThrustCurve(A).T * T
                    .ThrustCurve(A).F = .ThrustCurve(A).F * F
                    Next
                End If
            GetCurve = True: Exit Function
            End If
        Next
    End With
    End Function

Function GetFuel(Fuel As Fuel, Name As String) As Boolean
    Dim A As Long, S As String
    With Fuel
    If Propellants = "" Then DefaultPropellants
    A = InStr(Propellants, "|" & Name & "|"): If A = 0 Then Exit Function
    S = GetLine(Propellants, A + 1): .Name = ParamB(S)
    Do While ParamB(S) <> "": Loop
    ParamB S: ParamB S: ParamB S: ParamB S: .Density = ValDensity(ParamB(S))
    .Cost = ValCost(ParamB(S)): ParamB S: .Curve = ParamB(S): NumB S
    .Volume = ValRatio(ParamB(S)) + 1: .IspV = NumB(S): .IspS = NumB(S)
    End With
    GetFuel = True
    End Function

'Sub NormalizeCurve(ParamArray C())
'    Dim N As Long, S As String, T As Double, F As Double, Tx As Double, Fx As Double
'    S = "Curve 1 1 P 1 1 Generic" & vbCrLf
'    For N = 0 To UBound(C) Step 2
'        T = CDbl(C(N)): If Tx < T Then Tx = T
'        F = CDbl(C(N + 1)): If Fx < F Then Fx = F
'        Next
'    For N = 0 To UBound(C) Step 2
'        T = CDbl(C(N)) / Tx: F = CDbl(C(N + 1)) / Fx
'        S = S & "   " & Replace(Format(T, FShort), ",", ".") & " " & _
'            Replace(Format(F, FShort), ",", ".") & vbCrLf
'        Next
'    Clipboard.Clear: Clipboard.SetText S
'    End Sub

'Sub GenerateCurves()
'    'Circular bore
'    NormalizeCurve _
'        0.018, 172, _
'        0.036, 180, _
'        0.234, 230, _
'        0.455, 270, _
'        0.682, 285, _
'        0.8, 281, _
'        0.809, 260, _
'        0.823, 33, _
'        0.85, 0 _
'    '0.,, _
'
'    End Sub

Function GetCP(ByVal N As Long) As String
    GetCP = Space(120): NameCP N, GetCP: GetCP = LTrim(ULCaseEx(GetCP))
    End Function

Function GetCompCP(ByVal N As Long) As String
    GetCompCP = Space(40): CompCP N, GetCompCP
    GetCompCP = Replace(Trim(ULCaseEx(GetCompCP)), "  ", " ")
    End Function

Function GetPartCP(Name As String) As String
    Dim C As Long
    C = CodeCP(UCase(Name)): If C >= 0 Then GetPartCP = GetCP(C)
    End Function

Sub GetNameCP(T As TextBox, Optional ByVal N As Long, Optional ByVal Relative As Boolean)
    Dim C As Long, S As String
    If Relative Then C = CodeCP(UCase(T)): If C >= 0 Then N = C + N Else Sel T: Exit Sub
    S = Space(120): NameCP N, S: S = LTrim(ULCaseEx(S)): If S <> "" Then T = S: Sel T
    End Sub

Function IsForm(Name As String) As Boolean
    Dim F As Form
    For Each F In Forms
        If F.Name = Name Then IsForm = True: Exit Function
        Next
    End Function

Function IsFormVisible(Name As String) As Boolean
    Dim F As Form
    For Each F In Forms
        If F.Name = Name Then IsFormVisible = F.Visible: Exit Function
        Next
    End Function

Function FormName(Name As String, Optional N As Long) As String
    Select Case Name
    Case "Axes": FormName = "Remote control"
    Case "Browser": FormName = "Web browser"
    Case "Burn": FormName = "Burn editor"
    Case "Calc": FormName = "Trajectory editor"
    Case "Colors": FormName = "Color selector"
    Case "Editor": FormName = "Propellant editor"
    Case "Graph2": FormName = "Input graph"
    Case "Graph3D": FormName = "3D Graph"
    Case "Model": FormName = "Rover model"
    Case "Model2": FormName = "Stage model"
    Case "Note": FormName = "Popup message"
    Case "Onboard": FormName = "Payload camera"
    Case "Preset": FormName = "Preset editor"
    Case "Video": FormName = "Video player"
    Case "Video360": FormName = "360º player"
    Case Else: FormName = Name
        End Select
    End Function

Sub SwitchToForm(Name As String)
    Dim C As Long, F As Form, S As String
    GetTextNumber FormName(Name), S, C
    For Each F In Forms
        If FormName(F.Name) = S Then If F.Visible Then C = C - 1: If C <= 0 Then _
            On Error Resume Next: F.ZOrder: F.SetFocus: On Error GoTo 0: Exit For
        Next
    End Sub

Sub SetPanels(Optional ByVal Mode As Long)
    Dim F As Form
    For Each F In Forms
        Select Case Mode
        Case 1 'Default
            Select Case F.Name
            Case "Data":    UpdateData 2: Activate Data
            Case "Setup":   Setup.SetOverview: Activate Setup
            Case "Stages":  Stages.SetParameters: Activate Stages
            Case "Log":     Activate Log
            Case "ToolBar": Activate ToolBar
            Case Else:      GoTo Nxt
                End Select
        Case 2 'Basic
            Select Case F.Name
            Case "Data":    UpdateData 1: Activate Data
            Case "Setup":   Setup.SetOverview: Activate Setup
            Case "ToolBar": Activate ToolBar
            Case Else:      GoTo Nxt
                End Select
        Case Else 'None
Nxt:
            Select Case F.Name
            Case "InputBox", "Intro", "Main", "Note"
            Case Else
                If F.Visible Then PostMessage F.hWnd, &H10& 'WM_CLOSE=&H10
                End Select
            End Select
        Next
    End Sub

Sub Activate(F As Form, Optional ByVal Show As Boolean = True)
    On Error Resume Next
    If Show Xor F.Visible Then F.Activate
    On Error GoTo 0
    End Sub

Sub FullScreenActive()
    Dim F As Form
    For Each F In Forms
        If F.Visible Then
            Select Case F.Name
            Case "Browser", "Map", "Video360", "Graph3D":   FullScreenPanel F: Exit Sub
            Case "Onboard", "Video":                        F.FullScreen: Exit Sub
            Case "View", "Model", "Model2":                 Maximize F: Exit Sub
                End Select
            End If
        Next
    End Sub

Sub SetULCase(Text As String)
    Dim C As Long, N As Long, X As Boolean, L As String, U As String
    Text = LCase(Text)
    For N = 1 To Len(Text)
        L = Mid(Text, N, 1): If L = vbNullChar Then _
            Text = Left(Text, N - 1) & Mid(Text, N + 1) _
        Else U = UCase(L): If L = U Then X = False _
            Else If Not X Then Mid(Text, N, 1) = U: X = True
        Next
    End Sub

Function ULCase(Text As String) As String
    Dim C As Long, N As Long, X As Boolean, L As String, U As String
    ULCase = LCase(Text)
    For N = 1 To Len(ULCase)
        L = Mid(ULCase, N, 1): If L = vbNullChar Then _
            ULCase = Left(ULCase, N - 1) & Mid(ULCase, N + 1) _
        Else U = UCase(L): If L = U Then X = False _
            Else If Not X Then Mid(ULCase, N, 1) = U: X = True
        Next
    End Function

Function ULCaseEx(S As String) As String
    Dim N As Long, Word As Boolean
    ULCaseEx = LCase(S)
    For N = 1 To Len(ULCaseEx)
        Select Case Mid(ULCaseEx, N, 1)
        Case vbNullChar: ULCaseEx = Left(ULCaseEx, N - 1)
        Case Is < "a", Is > "z": Word = False
        Case Else: If Not Word Then Word = True: Mid(ULCaseEx, N, 1) = UCase(Mid(ULCaseEx, N, 1))
            End Select
        Next
    End Function

Function DeleteFile(Name As String, Optional ByVal Ask As Boolean) As Boolean
    If Ask Then If MsgBox("Delete the file """ & FileFromPath(Name) & """?", _
        vbOKCancel Or vbQuestion, AppName) <> vbOK Then Exit Function
    If GetFileAttributes(Name) <> -1 Then _
        On Error Resume Next: Err = 0: Kill Name: DeleteFile = Err = 0: On Error GoTo 0
    End Function

Sub UpdateFile(Name As String, Text As String)
    Dim T As String
    On Error Resume Next
    Open Name For Input As 1: T = Input(LOF(1), 1): Close
    If Text <> T Then Open Name For Output As 1: Print #1, Text;: Close
    On Error GoTo 0
    End Sub

Sub LoadPic(Image As Image, File As String, Optional Folder As String)
    On Error Resume Next
    Set Image.Picture = LoadPicture(IIf(Folder = "", ImagesDir, Folder) & File)
    On Error GoTo 0
    End Sub

Sub LoadPicEx(Pic As PictureBox, File As String, Optional Folder As String)
    On Error Resume Next
    Set Pic.Picture = LoadPicture(IIf(Folder = "", ImagesDir, Folder) & File)
    On Error GoTo 0
    End Sub

Sub LoadButton(Button As Button, File As String, Optional Folder As String)
    On Error Resume Next
    Set Button.Image = LoadPicture(IIf(Folder = "", ImagesDir, Folder) & File)
    On Error GoTo 0
    End Sub

Sub LoadButton2(Button As Button, File As String, File2 As String, Optional Folder As String)
    On Error Resume Next
    Set Button.Image = LoadPicture(IIf(Folder = "", ImagesDir, Folder) & File)
    Set Button.Image2 = LoadPicture(IIf(Folder = "", ImagesDir, Folder) & File2)
    On Error GoTo 0
    End Sub

Function ShellEx(File As String, Optional ByVal Mode As VbAppWinStyle = vbNormalNoFocus) As Long
    On Error Resume Next: ShellEx = Shell(File, Mode): On Error GoTo 0
    End Function

Sub MkPath(Path As String)
    Dim N As Long
    Do: N = InStrRev(Path, "\", N - 1): If N = 0 Then Exit Do
        If GetFileAttributes(Left(Path, N - 1)) <> -1 Then Exit Do
        Loop
    Do: N = InStr(N + 1, Path, "\"): If N = 0 Then Exit Do
        If CreateDirectory(Left(Path, N - 1)) = 0 Then Exit Sub
        Loop
    End Sub

Function FilePathFromFile(Name As String) As String ' C:\Dir\File.ext -> C:\Dir\
    Dim N As Long
    N = InStrRev(Name, "\"): If N Then FilePathFromFile = Left(Name, N)
    End Function

Function FileName(Name As String) As String         ' C:\Dir\File.ext -> C:\Dir\File
    Dim N As Long
    N = InStrRev(Name, "."): If N Then FileName = Left(Name, N - 1) Else FileName = Name
    End Function

Function FileExt(Name As String) As String          ' C:\Dir\File.ext -> .ext
    Dim N As Long
    N = InStrRev(Name, "."): If N Then FileExt = Mid(Name, N)
    End Function

Function FileFromPath(Name As String) As String     ' C:\Dir\File.ext -> File.ext
    Dim N As Long
    N = InStrRev(Name, "\"): If N Then FileFromPath = Mid(Name, N + 1) Else FileFromPath = Name
    End Function

Function FileNameFromPath(Name As String) As String ' C:\Dir\File.ext -> File
    Dim C As Long, N As Long
    C = InStrRev(Name, "\"): N = InStrRev(Name, ".")
    If N Then FileNameFromPath = Mid(Name, C + 1, N - C - 1) Else FileNameFromPath = Mid(Name, C + 1)
    End Function

Function FileReplaceExt(Name As String, Ext As String) As String ' C:\Dir\File.ext -> C:\Dir\File.newext
    Dim N As Long
    If Name <> "" Then N = InStrRev(Name, "."): _
        If N Then FileReplaceExt = Left(Name, N) & Ext Else FileReplaceExt = Name & "." & Ext
    End Function

Function FileReplaceName(Name As String, NewName As String) As String ' C:\Dir\File.ext -> C:\Dir\NewFile.ext
    Dim N As Long, P As Long
    If Name <> "" Then N = InStrRev(Name, "."): P = InStrRev(Name, "\"): _
        If N Then FileReplaceName = Left(Name, P) & NewName & Mid(Name, N) _
        Else FileReplaceName = Left(Name, P) & NewName
    End Function

Function FileAddName(Name As String, Add As String) As String ' C:\Dir\File.ext -> C:\Dir\FileAdd.ext
    Dim N As Long
    N = InStrRev(Name, "."): If N Then FileAddName = Left(Name, N - 1) & Add & Mid(Name, N) _
        Else FileAddName = Name & Add
    End Function

Function LoadFiles(Optional Folder As String, Optional Files As String = "*") As String
    Dim S As String
    S = Dir(Folder & Files)
    On Error Resume Next
    Do While S <> ""
        Open Folder & S For Input As 1: LoadFiles = LoadFiles & Input(LOF(1), 1): Close
        S = Dir: Loop
    On Error GoTo 0
    End Function

Function LoadFile(File As String) As String
    On Error Resume Next
    Open File For Input As 1: LoadFile = Input(LOF(1), 1): Close
    On Error GoTo 0
    End Function

Function LoadFileN(File As String, Optional ByVal N As Long = 2) As String
    On Error Resume Next
    Open File For Input As N: LoadFileN = Input(LOF(N), N): Close N
    On Error GoTo 0
    End Function

Function OpenFile(Name As String, _
        Optional ByVal ReadOnly As Boolean = True, Optional ByVal Check As Boolean) As Boolean
    If GetFileAttributes(Name) = -1 Then OpenFile = True: Exit Function
    If Check Then Exit Function
    On Error Resume Next
    If ReadOnly Then Open Name For Binary Access Read As 1 _
    Else Open Name For Binary As 1
    On Error GoTo 0
    OpenFile = FreeFile = 1
    End Function

Function FileExists(File As String) As Boolean
    FileExists = GetFileAttributes(File) <> -1
    End Function

Function FileSize(ByVal File As String) As Long
    Dim F As Win32_Find_Data
    FileSize = FindFirst(File, F): If FileSize <> -1 Then FindClose FileSize: FileSize = F.Size
    End Function

Sub Rename(File As String, NewFile As String, Optional ByVal Delete As Boolean) 'File -> NewFile
    On Error Resume Next
    If GetFileAttributes(File) = -1 Then Exit Sub
    If Delete Then If GetFileAttributes(NewFile) <> -1 Then Kill NewFile
    Name File As NewFile
    On Error GoTo 0
    End Sub

Sub RenameEx(File As String, NewFile As String) 'File.ext -> NewFile[.ext]
    Dim C As Long
    C = InStr(File, "."): If C = 0 Then C = Len(File) + 1
    On Error Resume Next: Name File As NewFile & Mid(File, C): On Error GoTo 0
    End Sub

Function Utf8ToAscii(Text As String) As String
    Utf8ToAscii = Space(MultiByteToWideChar(&HFDE9&, 0, Text, Len(Text), vbNullString, 0) * 2) 'CP_ACP=0 CP_UTF8=&HFDE9
    If MultiByteToWideChar(&HFDE9&, 0, Text, -1, Utf8ToAscii, Len(Utf8ToAscii) / 2 + 1) Then _
        Utf8ToAscii = StrConv(Utf8ToAscii, vbFromUnicode) Else Utf8ToAscii = "" 'CP_ACP=0 CP_UTF8=&HFDE9
    End Function

Function GetLong(ByVal Addr As Long) As Long
    RtlMoveMemory GetLong, ByVal Addr, 4
    End Function
    
Sub PutLong(ByVal Addr As Long, Optional ByVal N As Long)
    RtlMoveMemory ByVal Addr, N, 4
    End Sub
    
Function HostToAdd(H As String) As Long
    HostToAdd = inet_addr(H): If HostToAdd = -1 Then HostToAdd = gethostbyname(H): _
    If HostToAdd Then HostToAdd = GetLong(GetLong(GetLong(HostToAdd + 12))) _
    Else HostToAdd = -1
    End Function
    
Sub SetPauseEvent(E As Byte, Optional ByVal Value As Boolean, Optional ByVal Value2 As Boolean, _
        Optional ByVal Update As Boolean = True)
    SetEvent E, Value, 1, False: SetEvent E, Value2, 8, Update
    End Sub

Sub SetSaveEvent(E As Byte, Optional ByVal Value As Boolean, Optional ByVal Update As Boolean = True)
    SetEvent E, Value, 2, Update
    End Sub

Sub SetLogEvent(E As Byte, Optional ByVal Value As Boolean, Optional ByVal Update As Boolean = True)
    SetEvent E, Value, 4, Update
    End Sub

Sub SetEvent(E As Byte, Optional ByVal Value As Boolean, _
        Optional ByVal Mask As Byte = 1, Optional ByVal Update As Boolean)
    If Value Then E = E Or Mask Else E = E And Not Mask
    If Update Then Options.UpdateEvents
    End Sub

Sub SetPauseEvents(Optional ByVal Value As Boolean, Optional ByVal Value2 As Boolean, _
        Optional ByVal Update As Boolean = True)
    SetEvents Value, 1, False: SetEvents Value2, 8, Update
    End Sub

Sub SetSaveEvents(Optional ByVal Value As Boolean, Optional ByVal Update As Boolean = True)
    SetEvents Value, 2, Update
    End Sub

Sub SetLogEvents(Optional ByVal Value As Boolean, Optional ByVal Update As Boolean = True)
    SetEvents Value, 4, Update
    End Sub

Sub SetEvents(Optional ByVal Value As Boolean, _
        Optional ByVal Mask As Byte = 1, Optional ByVal Update As Boolean = True)
    SetEvent LiftOff, Value, Mask
    SetEvent StageEnginesOn, Value, Mask
    SetEvent StageEnginesOff, Value, Mask
    SetEvent HoverEnginesOn, Value, Mask
    SetEvent HoverEnginesOff, Value, Mask
    SetEvent LastStageEnginesOff, Value, Mask
    SetEvent SatelliteDeployed, Value, Mask
    SetEvent MoonTransfer, Value, Mask
    SetEvent StageAbort, Value, Mask
    SetEvent EarthSoundSpeed, Value, Mask
    SetEvent EarthOrbitalSpeed, Value, Mask
    SetEvent EarthEscapeSpeed, Value, Mask
    SetEvent MoonOrbitalSpeed, Value, Mask
    SetEvent MoonEscapeSpeed, Value, Mask
    SetEvent MarsOrbitalSpeed, Value, Mask
    SetEvent MarsEscapeSpeed, Value, Mask
    SetEvent Apogee, Value, Mask
    SetEvent Perigee, Value, Mask
    SetEvent Aposelene, Value, Mask
    SetEvent Periselene, Value, Mask
    SetEvent Apoapsis, Value, Mask
    SetEvent Periapsis, Value, Mask
    SetEvent ExitAtmosphere, Value, Mask
    SetEvent EnterAtmosphere, Value, Mask
    SetEvent ExitIonosphere, Value, Mask
    SetEvent EnterIonosphere, Value, Mask
    SetEvent EquidistantPoint, Value, Mask
    SetEvent L1LagrangePoint, Value, Mask
    SetEvent MoonDistance, Value, Mask
    SetEvent PlanetDistance, Value, Mask
    SetEvent PlanetApproach, Value, Mask
    SetEvent PlanetProximity, Value, Mask
    SetEvent PlanetFaraway, Value, Mask
    SetEvent BrakingAltitude, Value, Mask
    SetEvent StationAvailable, Value, Mask
    SetEvent StationUnAvailable, Value, Mask
    SetEvent SatelliteProximity, Value, Mask
    SetEvent SatelliteFaraway, Value, Mask
    SetEvent AsteroidProximity, Value, Mask
    SetEvent AsteroidFaraway, Value, Mask
    SetEvent EnterAirspace, Value, Mask
    SetEvent ExitAirspace, Value, Mask
    SetEvent LinkError, Value, Mask
    If Update Then Options.UpdateEvents
    End Sub

Function GetEventFlags(Optional ByVal N As Long = 1) As Long
    GetEventFlags = _
        GetMask(LiftOff And N, 1) + GetMask(StageEnginesOn And N, 2) + _
        GetMask(StageEnginesOff And N, 4) + GetMask(HoverEnginesOn And N, 8) + _
        GetMask(HoverEnginesOff And N, &H10&) + GetMask(LastStageEnginesOff And N, &H20&) + _
        GetMask(SatelliteDeployed And N, &H40&) + GetMask(MoonTransfer And N, &H80&) + _
        GetMask(StageAbort And N, &H100&) + GetMask(EarthSoundSpeed And N, &H200&) + _
        GetMask(EarthOrbitalSpeed And N, &H400&) + GetMask(EarthEscapeSpeed And N, &H800&) + _
        GetMask(MoonOrbitalSpeed And N, &H1000&) + GetMask(MoonEscapeSpeed And N, &H2000&) + _
        GetMask(MarsOrbitalSpeed And N, &H4000&) + GetMask(MarsEscapeSpeed And N, &H8000&) + _
        GetMask(Apogee And N, &H10000) + GetMask(Perigee And N, &H20000) + _
        GetMask(Aposelene And N, &H40000) + GetMask(Periselene And N, &H80000) + _
        GetMask(Apoapsis And N, &H100000) + GetMask(Periapsis And N, &H200000) + _
        GetMask(ExitAtmosphere And N, &H400000) + GetMask(EnterAtmosphere And N, &H800000) + _
        GetMask(ExitIonosphere And N, &H1000000) + GetMask(EnterIonosphere And N, &H2000000)
    End Function

Function GetEventFlagsEx(Optional ByVal N As Long = 1) As Long
    GetEventFlagsEx = _
        GetMask(EquidistantPoint And N, 1) + GetMask(L1LagrangePoint And N, 2) + _
        GetMask(MoonDistance And N, 4) + GetMask(PlanetDistance And N, 8) + _
        GetMask(PlanetApproach And N, &H10&) + GetMask(PlanetProximity And N, &H20&) + _
        GetMask(PlanetFaraway And N, &H40&) + GetMask(BrakingAltitude And N, &H80&) + _
        GetMask(StationAvailable And N, &H100&) + GetMask(StationUnAvailable And N, &H200&) + _
        GetMask(SatelliteProximity And N, &H400&) + GetMask(SatelliteFaraway And N, &H800&) + _
        GetMask(AsteroidProximity And N, &H1000&) + GetMask(AsteroidFaraway And N, &H2000&) + _
        GetMask(EnterAirspace And N, &H4000&) + GetMask(ExitAirspace And N, &H8000&) + _
        GetMask(LinkError And N, &H10000)
    End Function

Sub SetEventFlags(ByVal Events As Long, Optional ByVal N As Long = 1)
    SetByte LiftOff, Events And 1, N: SetByte StageEnginesOn, Events And 2, N
    SetByte StageEnginesOff, Events And 4, N: SetByte HoverEnginesOn, Events And 8, N
    SetByte HoverEnginesOff, Events And &H10&, N: SetByte LastStageEnginesOff, Events And &H20&, N
    SetByte SatelliteDeployed, Events And &H40&, N: SetByte MoonTransfer, Events And &H80&, N
    SetByte StageAbort, Events And &H100&, N: SetByte EarthSoundSpeed, Events And &H200&, N
    SetByte EarthOrbitalSpeed, Events And &H400&, N: SetByte EarthEscapeSpeed, Events And &H800&, N
    SetByte MoonOrbitalSpeed, Events And &H1000&, N: SetByte MoonEscapeSpeed, Events And &H2000&, N
    SetByte MarsOrbitalSpeed, Events And &H4000&, N: SetByte MarsEscapeSpeed, Events And &H8000&, N
    SetByte Apogee, Events And &H10000, N: SetByte Perigee, Events And &H20000, N
    SetByte Aposelene, Events And &H40000, N: SetByte Periselene, Events And &H80000, N
    SetByte Apoapsis, Events And &H100000, N: SetByte Periapsis, Events And &H200000, N
    SetByte ExitAtmosphere, Events And &H400000, N: SetByte EnterAtmosphere, Events And &H800000, N
    SetByte ExitIonosphere, Events And &H1000000, N: SetByte EnterIonosphere, Events And &H2000000, N
    End Sub

Sub SetEventFlagsEx(ByVal EventsEx As Long, Optional ByVal N As Long = 1)
    SetByte EquidistantPoint, EventsEx And 1, N: SetByte L1LagrangePoint, EventsEx And 2, N
    SetByte MoonDistance, EventsEx And 4, N: SetByte PlanetDistance, EventsEx And 8, N
    SetByte PlanetApproach, EventsEx And &H10&, N: SetByte PlanetProximity, EventsEx And &H20&, N
    SetByte PlanetFaraway, EventsEx And &H40&, N: SetByte BrakingAltitude, EventsEx And &H80&, N
    SetByte StationAvailable, EventsEx And &H100&, N: SetByte StationUnAvailable, EventsEx And &H200&, N
    SetByte SatelliteProximity, EventsEx And &H400&, N: SetByte SatelliteFaraway, EventsEx And &H800&, N
    SetByte AsteroidProximity, EventsEx And &H1000&, N: SetByte AsteroidFaraway, EventsEx And &H2000&, N
    SetByte EnterAirspace, EventsEx And &H4000&, N: SetByte ExitAirspace, EventsEx And &H8000&, N
    SetByte LinkError, EventsEx And &H10000, N
    End Sub

Sub SetLink(Optional ByVal Mode As LinkType = Disabled, Optional ByVal Init As Boolean = True)
    If LinkMode = Mode Then Exit Sub
    Select Case LinkMode
    Case TrackLink:     StopTrack
    Case RoverLink:     StopRover True
    Case ControlLink:   StopControl
    Case TerminalLink:  StopTerminal
    Case GraphLink:     StopTerminal True
        End Select
    LinkMode = Mode
    Select Case LinkMode
    Case TrackLink:     Link.Mode = Link.mnTrack.Caption: StartTrack
    Case RoverLink:     Link.Mode = Link.mnRover.Caption: StartRover Init, True
    Case ControlLink:   Link.Mode = Link.mnControl.Caption
    Case TerminalLink:  Link.Mode = Link.mnTerminal.Caption: StartTerminal
    Case GraphLink:     Link.Mode = Link.mnGraph.Caption: StartTerminal
    Case Else:          Link.Mode = Link.mnDisabled.Caption
        End Select
    End Sub

Sub StartLink()
    Select Case LinkMode
    Case RoverLink:     StartRover
    Case ControlLink:   StartControl
    Case TerminalLink:  StartTerminal
    Case GraphLink:     StartTerminal
        End Select
    End Sub

Sub StopLink()
    StopTerminal
    StopRover
    CloseCOM
    CloseUDP
    CloseTCP
    End Sub

Function StartControl() As Boolean
    If UCase(Left(Link.LinkPort(IDLink), 3)) = "COM" Then StartControl = OpenCOM(IDLink) _
    Else If Link.LinkPort(IDLink) = "Network" Then StartControl = OpenUDP(IDLink)
    End Function

Sub StopControl()
    CloseCOM
    CloseUDP
    End Sub

Function StartTerminal() As Boolean
    If LinkMode = GraphLink Then InitGraph2: _
        If Main.Visible And Not Graph2.Visible Then Graph2.Activate
    If UCase(Left(Link.LinkPort(IDLink), 3)) = "COM" Then StartTerminal = OpenCOM(IDLink, True) _
    Else If Link.LinkPort(IDLink) = "Network" Then StartTerminal = OpenTCP(IDLink)
    If StartTerminal Then Main.Rover.Interval = 10: Main.Rover = True
    End Function

Sub StopTerminal(Optional ByVal HideGraph As Boolean)
    If LinkMode = GraphLink Then Graph2Scroll = False: _
        If HideGraph Then If Graph2.Visible Then Graph2.Activate 'CleanUpGraph2
    If Main.Rover Then Main.Rover = False
    CloseCOM
    CloseTCP
    End Sub

Sub StartRover(Optional ByVal Init As Boolean = True, Optional ByVal ShowAxes As Boolean)
    StopRover
    If ShowAxes Then If Main.Visible And Not Axes.Visible Then Axes.Activate
    With Rover(0)
    .Port = OpenCOMPort(IDRover, True): If .Port = -1 Then Exit Sub
    .T = -1: .X = 0: .Y = 0: .Z = 0: .U = 0: .V = 1: .W = 0: Map.UpdateMap
    End With
    Main.Rover.Interval = ValTime(Link.TimeOut(IDRover)) * 1000
    Main.Rover = True
    If Not Init Then Exit Sub
    If Not InitRover Then MsgLink "COM write error"
    End Sub

Sub StopRover(Optional ByVal HideAxes As Boolean)
    Dim C As Long, S As String
    If HideAxes Then If Axes.Visible Then Axes.Activate
    If Main.Rover Then Main.Rover = False
    With Rover(0)
    If .Port <> -1 Then
        If IsPressed(Axes.Control) Then _
            S = "H": WriteFile .Port, S, 1: If LinkLogOut Then MsgLink S
        CloseHandle .Port: .Port = -1: MsgLink "COM port closed"
        End If
    End With
    End Sub

Function InitRover() As Boolean
    Dim C As Long, D As Long, S As String
    For C = 1 To IfInt(WaypointN > 10, 10, WaypointN)
        With Waypoint(C): S = S & "," & NumD(.X) & "," & NumD(.Y): End With
        Next
    LinkOut = "I" & S & ";"
    C = InStr(LinkOut, ";"): If C = 0 Then Exit Function
    If LinkLogOut Then MsgLink Left(LinkOut, C)
    For C = 1 To C
        If WriteFile(Rover(0).Port, Mid(LinkOut, C, 1), 1, D) = 0 Then Exit Function
        If D = 0 Then Exit Function
        Next
    InitRover = True
    End Function

Sub ToggleControl()
    If LinkMode <> RoverLink Then Exit Sub
    If IsPressed(Axes.Control) Then
        StopRover
        UnPress Axes.Control
    Else
        If LinkMode <> RoverLink Then SetLink RoverLink, False _
        Else If Rover(0).Port = -1 Then StartRover False
        If Rover(0).Port = -1 Then Exit Sub
        Press Axes.Control
        End If
    End Sub

Sub SendCmd(Optional Cmd As String)
    Dim C As Long, D As Long, P As Long
    On Error Resume Next
    If Cmd = "" Then Exit Sub
    Select Case LinkMode
    Case RoverLink
        If Rover(0).Port = -1 Then StartRover False
        P = Rover(0).Port: If P = -1 Then Exit Sub
COMWrite:
        If LinkLogOut Then MsgSplit Cmd
        For C = 1 To Len(Cmd)
            If WriteFile(P, Mid(Cmd, C, 1), 1, D) = 0 Then GoTo Err
            If D = 0 Then GoTo Err
            Next
        Exit Sub
Err:
        MsgLink "COM write error"
    Case Else
        If LinkMode <> TerminalLink And LinkMode <> GraphLink Then SetLink TerminalLink
        If LinkPort = -1 And LinkSocket = -1 Then StartTerminal
        If LinkPort <> -1 Then
            P = LinkPort: GoTo COMWrite
        ElseIf LinkSocket <> -1 Then
            If LinkLogOut Then MsgSplit Cmd
            If send(LinkSocket, Cmd, Len(Cmd), 0) <= 0 Then _
                MsgLink "TCP write error"
            End If
        End Select
    On Error GoTo 0
    End Sub

Sub LinkRover()
    Dim C As Long, D As Long, Q As Boolean, _
        L() As String, S As String * 1, T As String, _
        X As Double, Y As Double, Z As Double, R As Double, _
        Last As Double, TimeOut As Single, J As JoyInfoEx

    On Error Resume Next
    
    Select Case LinkMode
    
    Case RoverLink
        
        With Rover(0)
        If .Port = -1 Then Exit Sub
        If RCDevice <= 0 Or Not IsPressed(Axes.Control) Then GoTo Read
        
        Select Case RCMode
        Case 1 'Rover Mode
            Select Case RCDevice
            Case 1 'Keyboard
                If Keys(vbKeyDown) Then Y = -1 Else If Keys(vbKeyUp) Then Y = 1
                If Keys(vbKeyLeft) And Not Keys(vbKeyRight) Then X = -1
                If Keys(vbKeyRight) And Not Keys(vbKeyLeft) Then X = 1
            Case 2 'Mouse
                X = MouseAxisX: MouseAxisX = 0
                Y = MouseAxisY: MouseAxisY = 0
            Case 3 To 18 'Joystick
                J.dwSize = Len(J): J.dwFlags = &HFF&
                If joyGetPosEx(RCDevice - 3, J) Then GoTo Read
                X = GetJoyData(RCRSteering, J)
                Y = GetJoyData(RCRThrust, J)
            Case Else: GoTo Read
                End Select
            If Keys(vbKeySpace) Then Z = 1
            If RCRSteering < 0 Then X = -X
            If RCRThrust < 0 Then Y = -Y
            If Y < -0.5 Then T = "B" Else If Y > 0.5 Then T = "F"
            If X < -0.5 Then T = T & "L" Else If X > 0.5 Then T = T & "R"
            If Z > 0.5 Then T = "H"
            If T = "" Then T = "S"
        Case 2 'UAV Mode
            Select Case RCDevice
            Case 1 'Keyboard
                If Keys(vbKeyPageDown) Then R = -1 Else If Keys(vbKeyPageUp) Then R = 1
                If Keys(vbKeyDown) Then Y = -1 Else If Keys(vbKeyUp) Then Y = 1
                If Keys(vbKeyLeft) And Not Keys(vbKeyRight) Then X = -1
                If Keys(vbKeyRight) And Not Keys(vbKeyLeft) Then X = 1
                'If Keys(vbKeyLeft) And Not Keys(vbKeyRight) Then Z = -1
                'If Keys(vbKeyRight) And Not Keys(vbKeyLeft) Then Z = 1
                'If Keys(vbKeySpace) Then Z = 1
            Case 2 'Mouse
                X = MouseAxisX: MouseAxisX = 0
                Y = MouseAxisY: MouseAxisY = 0
            Case 3 To 18 'Joystick
                J.dwSize = Len(J): J.dwFlags = &HFF&
                If joyGetPosEx(RCDevice - 3, J) Then GoTo Read
                X = GetJoyData(RCURoll, J)
                Y = GetJoyData(RCUPitch, J)
                Z = GetJoyData(RCUYaw, J)
                R = GetJoyData(RCUThrust, J)
            Case Else: GoTo Read
                End Select
            If RCURoll < 0 Then X = -X
            If RCUPitch < 0 Then Y = -Y
            If RCUYaw < 0 Then Z = -Z
            If RCUThrust < 0 Then R = -R
            If Y < -0.5 Then T = "U" Else If Y > 0.5 Then T = "D"
            If X < -0.5 Then T = T & "L" Else If X > 0.5 Then T = T & "R"
            If Z < -0.5 Then T = T & "W" Else If Z > 0.5 Then T = T & "E"
            If R < -0.5 Then T = T & "B" Else If R > 0.5 Then T = T & "F"
            'If Z > 0.5 Then T = "H"
            If T = "" Then T = "S"
        Case Else: GoTo Read
            End Select
        
        If LinkLogOut Then MsgLink T
        For C = 1 To Len(T)
            If WriteFile(.Port, Mid(T, C, 1), 1, D) = 0 Then GoTo Err
            If D = 0 Then GoTo Err
            Next
        GoTo Read
Err:
        If T <> "S" Then MsgLink "COM write error"
Read:
        TimeOut = Main.Rover.Interval / 1000
        Do
        Last = TimerEx
        For C = 1 To LinkInSize
Nxt:
            If ReadFile(.Port, S, 1, D) = 0 Then MsgLink "COM read error": Exit Sub
            If D = 0 Then If C <= 1 Then Exit Sub _
                Else If TimerEx - Last < TimeOut Then GoTo Nxt _
                Else If LinkLogIn Then MsgLink Left(LinkIn, C - 1): Exit Sub _
                Else Exit Sub 'MsgLink "COM read timeout": Exit Sub
            Mid(LinkIn, C, 1) = S: If S = ";" Or S < " " Then Exit For
            Next
        'I,x1,y1,x2,y2,...,xn,yn; ... (limited to 10 x,y points)
        'D,t,x,y,z;
        'A,u,v,w;
        LinkPos = 1: T = GetText
        Select Case T
        Case "D", "DATA" 'Time and position X,Y,Z
            If LinkLogIn Then MsgLink Left(LinkIn, C)
            .T = GetData 'Elapsed time
            .X = GetData 'X position
            .Y = GetData 'Y position
            .Z = GetData 'Z position
            Map.UpdateMap
        Case "A", "ATTI", "ACCE" 'Attitude U,V,W (cartesian vector)
            If LinkLogIn Then MsgLink Left(LinkIn, C)
            X = GetData 'X axis
            Y = GetData 'Y axis
            Z = GetData 'Z axis
            Select Case LinkAxisX 'Map X axis
                Case -3: .U = -Z: Case -2: .U = -Y: Case -1: .U = -X
                Case 2: .U = Y: Case 3: .U = Z: Case Else: .U = X
                End Select
            Select Case LinkAxisY 'Map Y axis
                Case -3: .V = -Z: Case -2: .V = -Y: Case -1: .V = -X
                Case 1: .V = X: Case 3: .V = Z: Case Else: .V = Y
                End Select
            Select Case LinkAxisZ 'Map Z axis
                Case -3: .W = -Z: Case -2: .W = -Y: Case -1: .W = -X
                Case 1: .W = X: Case 2: .W = Y: Case Else: .W = Z
                End Select
            RenderModel Cam3
        Case "E", "EULR" 'Attitude U,V,W (Euler angles)
            If LinkLogIn Then MsgLink Left(LinkIn, C)
            .U = GetData * Rad 'X axis
            .V = GetData * Rad 'Y axis
            .W = GetData * Rad 'Z axis
            X = 0: Y = 1: Z = 0
            Rotate .U, Y, Z: Rotate .V, X, Z: Rotate .W, Y, Z
            .U = X: .V = Y: .W = Z
            RenderModel Cam3
        Case Else
            If LinkLogIn Then MsgLink Left(LinkIn, C)
            MsgLink "Unknown command"
            End Select
            Exit Do
        Loop
        End With
        
    Case TerminalLink, GraphLink
        
        If LinkPort <> -1 Then
            
            If LinkTest Then If LinkMode = GraphLink Then _
                Graph2Cmd = "": For C = 1 To Graph2C: Graph2Cmd = Graph2Cmd & _
                    Round(Rnd * 20) + C * 30 - 20 & IIf(C < Graph2C, ",", ";"): Next: GoTo Nxt3
                'Graph2Cmd = "": For C = 1 To 4: Graph2Cmd = Graph2Cmd & NumD(Sin((Graph2N) / Pi / (C + 1)) * 300 / (Graph2N + 10) + C * 30 - 20) & IIf(C < 4, ",", ";"): Next: GoTo Nxt3
            
            Do
Nxt2:
                T = String(LinkInSize, vbNullChar)
                If ReadFile(LinkPort, T, LinkInSize, D) = 0 Then Exit Do
                If D = 0 Then Exit Do
                Graph2Cmd = Graph2Cmd & Replace(Replace(Replace(Replace( _
                    Left(T, D), vbCrLf, ";"), vbCr, ";"), vbLf, ";"), vbNullChar, ";")
                Do
Nxt3:
                    C = InStr(Graph2Cmd, ";"): If C <= 0 Then GoTo Nxt2
                    T = Left(Graph2Cmd, C - 1): Graph2Cmd = Mid(Graph2Cmd, C + 1)
                    If T = "" Then GoTo Nxt3
                    If LinkLogIn Then MsgLink T
                    If LinkMode = GraphLink Then LinkGraph T: Q = True
                    Loop
                Loop
            If Q Then Plot2
            
        ElseIf LinkSocket <> -1 Then
            
            D = recv(LinkSocket, ByVal LinkIn, LinkInSize, 0)
            If D > 0 Then T = Left(LinkIn, D): MsgSplit T: _
                If LinkMode = GraphLink Then LinkGraph T: Plot2
            
            End If
        
        End Select
    
    On Error GoTo 0
    End Sub

Function GetJoyData(ByVal N As Long, J As JoyInfoEx, _
        Optional Add As Double = 32767, Optional Mult As Double = 32767, _
        Optional Min As Double = -1, Optional Max As Double = 1) As Double
    If N < 0 Then N = -N
    Select Case N
    Case 1: N = J.dwXpos 'X position - 1st axis position
    Case 2: N = J.dwYpos 'Y position - 2nd axis position
    Case 3: N = J.dwZpos 'Z position - 3rd axis position
    Case 4: N = J.dwRpos 'Rudder     - 4th axis position
    Case 5: N = J.dwUpos 'U position - 5th axis position
    Case 6: N = J.dwVpos 'V position - 6th axis position
    'Case 7: N = J.dwPOV  'Point of view state
    'Case 8 To 39: N = dwButtonsLong 'Button states
    Case Else: Exit Function
        End Select
    GetJoyData = (CDbl(N) - Add) / Mult
    If GetJoyData < Min Then GetJoyData = Min Else If GetJoyData > Max Then GetJoyData = Max
    End Function

Sub MsgSplit(Text As String)
    Dim N As Long, S() As String
    S = Split(Text, vbCrLf): For N = 0 To UBound(S): MsgLink S(N): Next
    End Sub

Sub MsgLink(Text As String)
    Dim T As SystemTime, S As String
    GetLocalTime T: S = _
        Format(T.Hour, "00") & ":" & Format(T.Minute, "00") & ":" & _
        Format(T.Second, "00") & "." & Format(T.Milliseconds, "000  ") & _
        Text & vbCrLf
    SendMessage Link.Txt.hWnd, &HB1&, &H7FFFFFFF, ByVal &H7FFFFFFF 'EM_SETSEL=&HB1
    SendMessage Link.Txt.hWnd, &HC2&, 0, ByVal S 'EM_REPLACESEL=&HC2 CAN_UNDO=0
    If AutoSaveLog And Not IsTest Then
        If LogFile = 0 Then LogFile = CreateFile( _
                CStr(LogsDir & ShortDateTime & ".txt"), &H40000000, 1, , 2): _
            If LogFile = -1 Then LogFile = 0 'GENERIC_WRITE=&H40000000 CREATE_ALWAYS=2 FILE_SHARE_READ=1
        WriteFile LogFile, S, Len(S)
        End If
    End Sub

Function OpenAudio() As Boolean
    Dim C As Long, Wf As WaveFormatEx
    
    MsgLink "Initializing audio input"
    
    With Wf
    .cbSize = Len(Wf): .wFormatTag = 1
    .nChannels = 2: .wBitsPerSample = 16: .nSamplesPerSec = 48000 '8000 11025 22050 44100 48000 96000 192000
    .nBlockAlign = (.nChannels * .wBitsPerSample) \ 8
    .nAvgBytesPerSec = .nSamplesPerSec * .nBlockAlign
    End With
    WaveCh = 0: WaveN = 2: WaveD = Wf.nSamplesPerSec
    ReDim WaveH(WaveN - 1), WaveL(Wf.nChannels - 1, WaveD - 1, WaveN - 1)
    C = waveInGetNumDevs: If C = 0 Then GoTo Ext
    If waveInOpen(WaveI, C - 1, Wf, 0, 0, 0) Then GoTo Ext 'WAVE_MAPPER=-1 CALLBACK_NULL=0 CALLBACK_WINDOW=&H10000 CALLBACK_FUNCTION=&H30000
    For WaveP = 0 To WaveN - 1
        With WaveH(WaveP)
        .dwBufferLength = WaveD * Wf.nBlockAlign: .lpData = VarPtr(WaveL(0, 0, WaveP))
        .dwFlags = 0: .dwLoops = 0
        End With
        If waveInPrepareHeader(WaveI, WaveH(WaveP), Len(WaveH(WaveP))) Then GoTo Ext
        If waveInAddBuffer(WaveI, WaveH(WaveP), Len(WaveH(WaveP))) Then GoTo Ext
        Next
    WaveP = 0: WavePos = 0: WaveB = 0: WaveM = 0: WaveF = 0: WaveZ = 0 'Mask .. MSB=&H00, LSB=&H100
    WaveZ0 = 0: WaveZ1 = 0: WaveL0 = 0: WaveL1 = 0: WaveR0 = 0: WaveR1 = 0
    If waveInStart(WaveI) Then GoTo Ext
    
    OpenAudio = True
    MsgLink "Audio input started"
    Exit Function

Ext:
    MsgLink "Audio input error"
    CloseAudio
    
    End Function

Function ReadAudio() As Boolean
    Dim C As Long, D As Long, N As Long, Out As Long, Min As Long, Min0 As Long, Min1 As Long
    
    LinkInPos = 0: If WaveI = 0 Then Exit Function
    Out = 15: Min = 20: Min1 = Int(Min * 32768 / 100): Min0 = -Min1
    
    For D = 1 To WaveN: DoEvents
        
        If (WaveH(WaveP).dwFlags And 1) = 0 Then Exit For 'WHDR_DONE=1 WHDR_PREPARED=2
        waveInUnprepareHeader WaveI, WaveH(WaveP), Len(WaveH(WaveP))
        
        C = 0
        Do While C < WaveD
            WaveL1 = WaveL0: WaveL0 = WaveL(0, C, WaveP)                        'Save last sample in left channel
            WaveR1 = WaveR0: WaveR0 = WaveL(1, C, WaveP)                        'Save last sample in right channel
            If WaveZ0 > 0 Then GoTo CheckZero                                   'Check if the bitstream processing is active
            N = WaveL0 - WaveL1
            If N < Min0 Or N > Min1 Then WaveCh = 0: GoTo CheckFlank            'Detect minimum level flank in left channel
            N = WaveR0 - WaveR1
            If N < Min0 Or N > Min1 Then WaveCh = 1: GoTo CheckFlank            'Detect minimum level flank in right channel
            GoTo NextSample
CheckZero:
            If WaveCh = 0 Then
                If Not (WaveL0 > 0 Xor WaveL1 > 0) Then GoTo CheckTimeOut      'Detect zero pass in left channel
            Else
                If Not (WaveR0 > 0 Xor WaveR1 > 0) Then GoTo CheckTimeOut      'Detect zero pass in right channel
                End If
CheckFlank:
            If WaveF = 0 And WaveZ0 > 0 Then                                   'Detect third flank / end of bit
                If WaveM = 0 Then WaveM = &H80&: WaveB = 0                     'Detect end of byte (MSB=&H00, LSB=&H100) and initialize byte mask (MSB=&H80, LSB=&H01)
                If WaveZ - WaveZ0 < WaveZ0 - WaveZ1 Then WaveB = WaveB Or WaveM 'Compare first and second periods to determine the bit
                WaveM = WaveM \ 2                                              'Rotate mask (MSB=right, LSB=left)
                If WaveM = 0 Then                                              'Detect end of byte
                    Mid(WaveIn, WavePos + 1, 1) = Chr(WaveB): WavePos = WavePos + 1 'Save byte in the buffer
                    If WaveB = 10 Then                                         'Detect end of line
                        LinkIn = WaveIn: LinkInPos = WavePos: WavePos = 0      'Save buffer
                        ReadAudio = True                                       'Activate line read flag
                        End If
                    End If
                End If
            WaveZ1 = WaveZ0: WaveZ0 = WaveZ                                    'Save last flank positions
            WaveF = 1 - WaveF                                                  'Flip flank flag
            GoTo NextSample
CheckTimeOut:
            If WaveZ - WaveZ0 <= Out Or WaveZ0 = 0 Then GoTo NextSample        'Detect timeout
TimeOut:
            WavePos = 0: WaveB = 0: WaveM = 0: WaveF = 0: WaveZ = 0            'Reset variables (Mask .. MSB=&H00, LSB=&H100)
            WaveZ0 = 0: WaveZ1 = 0: WaveL1 = WaveL0: WaveR1 = WaveR0
NextSample:
            WaveZ = WaveZ + 1: C = C + 1                                       'Increment sample pointer
            Loop
        
        waveInPrepareHeader WaveI, WaveH(WaveP), Len(WaveH(WaveP))
        waveInAddBuffer WaveI, WaveH(WaveP), Len(WaveH(WaveP))
        WaveP = WaveP + 1: If WaveP >= WaveN Then WaveP = 0
        Next
    
    End Function

Sub CloseAudio()
    If WaveI = 0 Then Exit Sub
    waveInStop WaveI: waveInReset WaveI
    For WaveP = 0 To WaveN - 1
        waveInUnprepareHeader WaveI, WaveH(WaveP), Len(WaveH(WaveP))
        Next
    waveInClose WaveI: WaveI = 0
    Erase WaveH, WaveL
    MsgLink "Audio input stopped"
    End Sub

Function OpenCOMPort(Optional ByVal ID As Long, Optional ByVal NonBlock As Boolean) As Long
    Dim C As Long, Tmo As COMMTimeouts, Dcb As Dcb, Cmd As String, Port As String
    If ID < 0 Or ID >= IDLast Then ID = IDLink
    Port = UCase(Link.LinkPort(ID)): If Port = CStr(Val(Port)) Then Port = "COM" & Port _
        Else If Port = "NONE" Then OpenCOMPort = -1: Exit Function
    MsgLink "Initializing " & Port
    OpenCOMPort = CreateFile(CStr("\\.\" & Port), &HC0000000, , , 3) 'GENERIC_READ=&H80000000 GENERIC_WRITE=&H40000000 OPEN_EXISTING=3
    If OpenCOMPort = -1 Then MsgLink "Open COM port error": Exit Function
    GetCommState OpenCOMPort, Dcb
    With Dcb: .DCBlength = Len(Dcb): .BaudRate = Val(Link.Speed(ID))
    Cmd = Link.Settings(ID)
    .ByteSize = NumN(Cmd) '4, 5, 6, 7 or 8
    Select Case NumN(Cmd) '1, 1.5 or 2
    Case 1.5: .StopBits = 1: Case 2: .StopBits = 2: Case Else: .StopBits = 0
        End Select
    Select Case LCase(Left(Param(Cmd), 1)) 'None, Odd, Even, Mark or Space
    Case "o": .Parity = 1: Case "e": .Parity = 2
    Case "m": .Parity = 3: Case "s": .Parity = 4
    Case Else: .Parity = 0
        End Select
    Select Case LCase(Left(Param(Cmd), 1)) 'None, Hardware or Xon/Xoff
    Case "h": .BitFields = &H6095&: Case "x": .BitFields = &H5391&
    Case Else: .BitFields = &H5091&
        End Select
    End With
    If SetCommState(OpenCOMPort, Dcb) = 0 Then MsgLink "Setup COM port error": CloseCOM: Exit Function
    GetCommTimeouts OpenCOMPort, Tmo
    With Tmo: C = ValTime(Link.TimeOut(ID)) * 1000
    If NonBlock Then .ReadIntervalTimeOut = -1: .ReadTotalTimeOutMultiplier = 0: .ReadTotalTimeOutConstant = 0 _
    Else .ReadIntervalTimeOut = C: .ReadTotalTimeOutMultiplier = C: .ReadTotalTimeOutConstant = C
    .WriteTotalTimeOutMultiplier = C: .WriteTotalTimeOutConstant = C
    End With
    If SetCommTimeouts(OpenCOMPort, Tmo) = 0 Then MsgLink "Setup COM timeout error": CloseCOM: Exit Function
    MsgLink "COM port opened"
    End Function

Function OpenCOM(Optional ByVal ID As Long, Optional ByVal NonBlock As Boolean) As Boolean
    CloseCOM
    LinkPort = OpenCOMPort(ID, NonBlock): If LinkPort = -1 Then Exit Function
    CleanCOM
    OpenCOM = True
    End Function

Sub CloseCOM(Optional ByVal Cancel As Boolean)
    If Cancel Then Exit Sub
    If LinkPort <> -1 Then CloseHandle LinkPort: LinkPort = -1: MsgLink "COM port closed"
    If LogFile Then CloseHandle LogFile: LogFile = 0
    End Sub

Sub CleanCOM()
    Dim C As Long, S As String * 1
    Do: Loop While ReadFile(LinkPort, S, 1, C) <> 0 And C > 0
    End Sub

Function DownloadBalloon() As Boolean
    Dim B As Long, C As Long, N As Long, S As String, T As String, T1 As Double, H1 As Double, _
        TimeOut As Long, Msg As String, Host As String, URL As String, _
        Addr As SockAddr
    Static Sock As Long
    If Note.Active Then Exit Function
    On Error Resume Next
    If Sock > 0 Or Sock < -1 Then Exit Function
    Host = "weather.uwyo.edu": Message "Connecting to server: " & Host & " …"
    Msg = "Connection error"
    Sock = -1
    'TimeOut = 60 * 1000
    H1 = J2ksNow: T1 = J2ksNowDay + ValTimeEx(Setup.Time): If T1 > H1 Then T1 = T1 - Day
    H1 = Hour * 6: T1 = Int(T1 / H1 - 1) * H1
    'T1 = Int((J2ksNowDay + ValTimeEx(Setup.Time)) / Hour / 6 + 0.5) * Hour * 6
    'If T1 >= J2ksNow - 6 * Hour Then T1 = T1 - Day
    H1 = ValSpace(Setup.Alt): If H1 <= 0 Then H1 = 32000
    URL = "/cgi-bin/balloon_traj/" & _
        "?TIME=" & ShortJ2ksDateTime(T1, 1) & _
        "&FCST=Analysis" & _
        "&LAT=" & NumD(ValAngle(Setup.Lat, True)) & _
        "&LON=" & NumD(ValAngle(Setup.Lon, True)) & _
        "&TOP=" & NumD(H1) & _
        "&OUTPUT=kml"
    'PF_INET=2 .. SOCK_STREAM=1 SOCK_DGRAM=2 .. IPPROTO_IP=0 IPPROTO_TCP=6 IPPROTO_UDP=17
    Sock = socket(2, 1, 6): If Sock = -1 Then GoTo Ext 'PF_INET=2 SOCK_STREAM=1 IPPROTO_TCP=6
    Addr.Family = 2 'AF_INET=2
    Addr.Port = htons(80) 'HTTP=80
    Addr.Addr = HostToAdd(Host)
    If connect(Sock, Addr, Len(Addr)) = -1 Then GoTo Ext
    Message "Downloading trajectory, please wait …", , True
    S = "GET " & URL & " HTTP/1.1" & vbCrLf & _
        "Host: " & Host & vbCrLf & _
        vbCrLf
    N = send(Sock, ByVal S, Len(S), 0): If N <= 0 Then GoTo Ext
    'setsockopt Sock, &HFFFF&, &H1006&, TimeOut 'SOL_SOCKET=&HFFFF SO_RCVTIMEO=&H1006
    ioctlsocket Sock, &H8004667E, 1 'FIONBIO=&H8004667E 0=block 1=nonblock
    Do
        DoEvents: Sleep 1: If Not Note.Active Then Msg = "Download aborted": GoTo Ext
        S = Space(65536 - 1)
        N = recv(Sock, ByVal S, Len(S), 0)
        If N > 0 Then T = T & Left(S, N): If Right(T, 4) = vbCrLf2 Then Exit Do
        Loop
    If InStr(T, "the server is too busy") Then Msg = "Server is too busy": GoTo Ext
    Msg = "Error downloading file"
    B = InStr(T, vbCrLf2): If B Then B = B + 4 Else GoTo Ext
    Do
        C = InStr(B + 1, T, vbCrLf): If C = 0 Then GoTo Ext
        N = Val("&H" & Mid(T, B, C - B) & "&")
        If N > 0 Then T = Left(T, B - 3) & Mid(T, C + 2) Else T = Left(T, B - 3): Exit Do
        B = B + N
        Loop
    B = InStr(T, "<?xml"): If B = 0 Then GoTo Ext
    C = InStr(B + 1, T, "</kml>"): If C = 0 Then GoTo Ext
    C = InStr(C + 1, T, vbLf): If C Then C = C + 1 Else GoTo Ext
    T = Replace(Replace(Mid(T, B, C - B), vbCrLf, vbLf), vbLf, vbCrLf)
    S = PresetsDir & Setup.Preset & ".kml"
    Open S For Output As 1: Print #1, T;: Close
    LoadPath S, False
    If KMLPathN Then
        If IsBalloonTrajectory Then KMLPathPos = 1
        With KMLPath(IfInt(IsBalloonTrajectory, 0, KMLPathTop))
        SetLaunch .Lat, .Lon, .Alt, , True: KMLPathAlt = .Alt
        End With
        End If
    Msg = "Download completed"
    DownloadBalloon = True
Ext:
    Message Msg, 2000: If Sock <> -1 Then closesocket Sock: Sock = -1
    On Error GoTo 0
    End Function

Function InitURL(Host As String, _
        Optional ByVal Port As Long = 80, Optional ByVal NonBlock As Boolean = True) As Long
    Dim Addr As SockAddr
    InitURL = socket(2, 1, 6): If InitURL = -1 Then GoTo Ext 'PF_INET=2 SOCK_STREAM=1 IPPROTO_TCP=6
    Addr.Family = 2 'AF_INET=2
    Addr.Port = htons(Port) 'FTP=21 HTTP=80
    Addr.Addr = HostToAdd(Host)
    If connect(InitURL, Addr, Len(Addr)) = -1 Then GoTo Ext
    ioctlsocket InitURL, &H8004667E, -NonBlock 'FIONBIO=&H8004667E 0=block 1=nonblock
    Exit Function
Ext:
    If InitURL <> -1 Then closesocket InitURL: InitURL = -1
    End Function

Sub ParseURL(URL As String, Optional Protocol As String, _
        Optional Host As String, Optional Folder As String, Optional File As String) 'mode://server/folder/file.ext .. mode - server - folder - file.ext
    Dim B As Long, C As Long, D As Long
    B = InStr(URL, "//"): If B Then B = B + 1
    C = InStr(B + 1, URL, "/"): D = InStrRev(URL, "/")
    If C <= 0 Or D <= 0 Then Host = Mid(URL, B + 1): Folder = "": File = "": Exit Sub
    Host = Mid(URL, B + 1, C - B - 1)
    If D > C Then Folder = Mid(URL, C + 1, D - C - 1)
    File = Mid(URL, D + 1)
    End Sub

Sub ParseURLEx(URL As String, Optional Protocol As String, _
        Optional Host As String, Optional Path As String) 'mode://server/folder/file.ext .. mode - server - /folder/file.ext
    Dim B As Long, C As Long
    B = InStr(URL, "//"): If B Then B = B + 1
    C = InStr(B + 1, URL, "/")
    If C Then Host = Mid(URL, B + 1, C - B - 1): Path = Mid(URL, C) _
    Else Host = Mid(URL, B + 1): Path = ""
    End Sub

Function ServerFromURL(URL As String) As String 'mode://server/folder/file.ext .. server
    Dim B As Long, C As Long
    B = InStr(URL, "//"): If B = 0 Then B = -1
    C = InStr(B + 2, URL, "/")
    If C Then ServerFromURL = Mid(URL, B + 2, C - B - 2) Else ServerFromURL = Mid(URL, B + 2)
    End Function

Function PathFromURL(URL As String) As String   'mode://server/folder/file.ext .. /folder/file.ext
    Dim B As Long, C As Long
    B = InStr(URL, "//"): If B = 0 Then B = -1
    C = InStr(B + 2, URL, "/"): If C Then PathFromURL = "/" & Mid(URL, C + 1) Else PathFromURL = "/"
    End Function

Function FileFromURL(URL As String) As String   'mode://server/folder/file.ext .. file.ext
    FileFromURL = Mid(URL, InStrRev(URL, "/") + 1)
    End Function

Function CmdURL(Sock As Long, Optional Cmd As String, _
        Optional ByVal WaitForResponse As Boolean = True, Optional ByVal TimeOut As Long = 2 _
        ) As String
    Dim N As Long, T As Double, S As String * 16384
    If Cmd <> "" Then If send(Sock, ByVal CStr(Cmd & vbCrLf), Len(Cmd) + 2, 0) <= 0 Then Exit Function
    If WaitForResponse Then T = TimerEx Else Exit Function
    Do
        DoEvents
        N = recv(Sock, ByVal S, Len(S), 0)
        If N > 0 Then CmdURL = CmdURL & Left(S, N): _
            If Right(CmdURL, 1) = vbLf Then Exit Do
        If TimerEx - T > TimeOut Then Exit Do
        Loop
    End Function

Function MsgURL(Sock As Long, Optional Cmd As String) As Long
    If Cmd <> "" Then MsgURL = send(Sock, ByVal CStr(Cmd & vbCrLf), Len(Cmd) + 2, 0)
    End Function

Function ReadURL(Sock As Long, Optional ByVal CloseOnExit As Boolean, _
        Optional ByVal TimeOut As Long = 5, Optional EndString As String) As String
    Dim N As Long, T As Double, S As String * 16384
    T = TimerEx
    Do
        DoEvents
        N = recv(Sock, ByVal S, Len(S), 0)
        If N > 0 Then ReadURL = ReadURL & Left(S, N) _
        Else If EndString <> "" Then If InStr(ReadURL, EndString) Then Exit Do
        If TimerEx - T > TimeOut Then Exit Do
        Loop
    If CloseOnExit Then closesocket Sock: Sock = -1
    End Function

Function GetEMail( _
        Optional Host As String, Optional User As String, Optional Pass As String, _
        Optional ByVal Num As Long) As String
    Dim B As Long, C As Long, D As Long, E As Long, N As Long, _
        S As String, T As String, U As String
    Static Sock As Long
    On Error Resume Next
    If Sock <> 0 And Sock <> -1 Then Exit Function
    'Host = "pop.server.com": User = "user": Pass = "password": Num = 1
    Sock = InitURL(Host, 110): If Sock = -1 Then GoTo Ext
    U = CmdURL(Sock)
    U = CmdURL(Sock, "USER " & User)
    U = CmdURL(Sock, "PASS " & Pass)
    U = CmdURL(Sock, "STAT") 'Total of messages and size
    U = CmdURL(Sock, "LIST") 'List of all messages and size
    U = CmdURL(Sock, "RETR " & Num) 'Download message
    U = CmdURL(Sock, "QUIT", False)
Ext:
    If Sock <> -1 Then closesocket Sock: Sock = -1
    On Error GoTo 0
    End Function

Function SendEMail( _
        Optional Host As String, Optional Dest As String, _
        Optional Server As String, Optional User As String, _
        Optional ByVal Text As String) As Boolean
    Dim S As String
    Static Sock As Long
    On Error Resume Next
    If Sock <> 0 And Sock <> -1 Then Exit Function
    'Host = "smtp.server.com": Dest = "user@server.com": _
    Server = "server.com": User = "user@server.com": Text = _
        "From: ""Name"" <user@server.com>" & vbCrLf & _
        "To: ""Name"" <user@server.com>" & vbCrLf & _
        "Cc: <user@server.com>" & vbCrLf & _
        "Date: Mon, 1 January 2000 12:00:00 -0300" & vbCrLf & _
        "Subject: Test message" & vbCrLf & _
        vbCrLf & _
        "This is the message body." & vbCrLf & _
        "." & vbCrLf
    Sock = InitURL(Host, 25): If Sock = -1 Then GoTo Ext 'OldPort=25 SSL=465 StartTLS=587
    S = CmdURL(Sock)                                '220 smtp.example.com ESMTP Postfix
    S = CmdURL(Sock, "HELO " & Server)              '250 Hello relay.example.org
    S = CmdURL(Sock, "MAIL FROM:<" & User & ">")    '250 Ok
    S = CmdURL(Sock, "RCPT TO:<" & Dest & ">")      '250 Ok
    'S = CmdURL(Sock, "RCPT TO:<" & Dest2 & ">")     '250 Ok
    S = CmdURL(Sock, "DATA")                        '354 End data with <CR><LF>.<CR><LF>
    S = CmdURL(Sock, Text)                          '250 Ok: queued as 12345
    SendEMail = Left(S, 4) = "250 "
    S = CmdURL(Sock, "QUIT", False)                 '221 Bye
Ext:
    If Sock <> -1 Then closesocket Sock: Sock = -1
    On Error GoTo 0
    End Function

Function GetFTP(URL As String) As String
    Dim B As Long, C As Long, D As Long, E As Long, N As Long, _
        S As String, T As String, U As String, _
        Host As String, Folder As String, File As String
    Static Sock As Long, Sock2 As Long
    On Error Resume Next
    'URL = "ftp://ftp.microsoft.com/developr/readme.txt"
    If Sock <> 0 And Sock <> -1 Then Exit Function
    ParseURL URL, , Host, Folder, File: If Host = "" Or File = "" Then Exit Function
    Sock = InitURL(Host, 21): If Sock = -1 Then GoTo Ext
    U = U & CmdURL(Sock)
    U = U & CmdURL(Sock, "USER anonymous")
    U = U & CmdURL(Sock, "PASS user@server.com")
    U = U & CmdURL(Sock, "TYPE I")
    If Folder <> "" Then U = U & CmdURL(Sock, "CWD " & Folder)
    T = CmdURL(Sock, "PASV"): U = U & T '227 Entering Passive Mode (64,4,30,61,52,225).
    N = 20: B = InStrRev(T, ","): If B > 0 Then C = InStrRev(T, ",", B - 1): _
        If C > 0 Then N = Val(Mid(T, C + 1)) * 256 + Val(Mid(T, B + 1))
    U = U & CmdURL(Sock, "RETR " & File) 'Download
    'U = U & CmdURL(Sock, "STOR " & File) 'Upload
    Sock2 = InitURL(Host, N): If Sock2 = -1 Then GoTo Ext
    GetFTP = ReadURL(Sock2, True) ': U = U & GetFTP
    U = U & CmdURL(Sock, "QUIT", False)
Ext:
    If Sock <> -1 Then closesocket Sock: Sock = -1
    If Sock2 <> -1 Then closesocket Sock2: Sock2 = -1
    On Error GoTo 0
    End Function

Function GetHTTP(URL As String, Optional EndString As String) As String
    Dim Host As String, Folder As String, File As String
    Static Sock As Long
    On Error Resume Next
    If Sock <> 0 And Sock <> -1 Then Exit Function
    ParseURL URL, , Host, Folder, File: If Host = "" Or File = "" Then Exit Function
    Sock = InitURL(Host, 80): If Sock = -1 Then GoTo Ext
    If MsgURL(Sock, "GET /" & Folder & "/" & File & " HTTP/1.1" & vbCrLf & "Host: " & Host & vbCrLf) <= 0 Then GoTo Ext
    GetHTTP = ReadURL(Sock, , 5, EndString)
Ext:
    If Sock <> -1 Then closesocket Sock: Sock = -1
    On Error GoTo 0
    End Function

Function GetAPRS(Track As Track) As Boolean
    Dim B As Long, C As Long, D As Long, E As Long, S As String, T As String, N As Long, _
        T1 As Double, H1 As Double, Host As String, URL As String, Addr As SockAddr
    Static Sock As Long
    On Error Resume Next
    If Sock <> 0 And Sock <> -1 Then Exit Function
    T1 = TimerEx
    Sock = -1
    Host = "api.aprs.fi"
    URL = "/api/get?name=" & Link.User(Track.ID) & _
        "&what=loc&apikey=" & Link.Pass(Track.ID) & "&format=xml"
    Sock = socket(2, 1, 6): If Sock = -1 Then GoTo Ext 'PF_INET=2 SOCK_STREAM=1 IPPROTO_TCP=6
    Addr.Family = 2 'AF_INET=2
    Addr.Port = htons(80) 'HTTP=80
    Addr.Addr = HostToAdd(Host)
    If connect(Sock, Addr, Len(Addr)) = -1 Then GoTo Ext
    If MsgURL(Sock, "GET " & URL & " HTTP/1.1" & vbCrLf & "Host: " & Host & vbCrLf) <= 0 Then GoTo Ext
    ioctlsocket Sock, &H8004667E, 1 'FIONBIO=&H8004667E 0=block 1=nonblock
    S = Space(65536 - 1)
    Do
        DoEvents
        N = recv(Sock, ByVal S, Len(S), 0): If N > 0 Then _
            T = T & Left(S, N): If InStr(T, "</xml>") Or TimerEx - T1 > 10 Then Exit Do
        Loop
    With Track
    S = ParamQ(T, "lat"): If S <> "" Then .Lat = Val(S) * Rad Else GoTo Ext
    S = ParamQ(T, "lng"): If S <> "" Then .Lon = Val(S) * Rad Else GoTo Ext
    S = ParamQ(T, "altitude"): If S <> "" Then .Alt = Val(S) Else GoTo Ext
    S = ParamQ(T, "lasttime"): If S <> "" Then .Time = Val(S) - 946728000 Else GoTo Ext 'Unix time: seconds since 1970-01-01
    End With
    GetAPRS = True
Ext:
    If Sock <> -1 Then closesocket Sock: Sock = -1
    On Error GoTo 0
    End Function

Function GetServer(Track As Track) As Boolean
    Dim B As Long, C As Long, N As Long, S As String, T As String, U As String, _
        Pos As Long, Size As Long, T1 As Double, _
        URL As String, Host As String, Path As String, Addr As SockAddr
    Static Sock As Long
    On Error Resume Next
    If Sock <> 0 And Sock <> -1 Then Exit Function
    T1 = TimerEx
    Sock = -1
    '29/08/2011 02:38:07 UTC,41.858458,-.751786,307.81,0.3240,844.66,000.2,11,.94,1.14<LF>
    'Date and time (UTC), Latitude (degrees), Longitude (degrees), Altitude sea level (m), _
     Horizontal speed (km/h), True North course (degrees), Vertical speed (m/s), _
     Number of satellites used for fix, Horizontal disolution, Vertical disolution
    URL = Link.IP(Track.ID): If URL = "" Or URL = "localhost" Then _
        URL = "http://www.idetic.ulpgc.es/trackingsat/fix_log.csv"
    ParseURLEx URL, , Host, Path: If Host = "" Or Path = "" Then GoTo Ext
    Sock = socket(2, 1, 6): If Sock = -1 Then GoTo Ext 'PF_INET=2 SOCK_STREAM=1 IPPROTO_TCP=6
    Addr.Family = 2 'AF_INET=2
    Addr.Port = htons(80) 'HTTP=80
    Addr.Addr = HostToAdd(Host)
    If connect(Sock, Addr, Len(Addr)) = -1 Then GoTo Ext
    If MsgURL(Sock, "GET " & Path & " HTTP/1.1" & vbCrLf & "Host: " & Host & vbCrLf) <= 0 Then GoTo Ext
    ioctlsocket Sock, &H8004667E, 1 'FIONBIO=&H8004667E 0=block 1=nonblock
    S = Space(1048576 - 1)
    Do
        DoEvents
        N = recv(Sock, ByVal S, Len(S), 0)
        If N > 0 Then T = T & Left(S, N): If Pos = 0 Then Pos = InStr(T, vbCrLf2): _
            If Pos Then U = ParamP(T, "Content-Length"): _
            If U = "" Then Pos = 0 Else Size = Val(U): If Size <= 0 Then GoTo Ext
        If (Pos > 0 And Len(T) >= Pos + Size) Or TimerEx - T1 > 10 Then Exit Do
        Loop
    B = InStrRev(T, vbLf)
    For N = 1 To 3
        If B >= Len(T) - 2 Then B = InStrRev(T, vbLf, B - 1) Else Exit For
        Next
    C = InStr(B + 1, T, vbLf): If C = 0 Then C = Len(T) + 1
    T = Mid(T, B + 1, C - B - 1): If Right(T, 1) < " " Then T = Left(T, Len(T) - 1)
    If InStr(T, ",") = 0 Then GoTo Ext
    With Track
    S = ParamX(T): .Time = ValDateEx(S, , 1)
    S = ParamX(T): .Time = .Time + ValTimeEx(S): Param T
    .Lat = NumN(T) * Rad: .Lon = NumN(T) * Rad: .Alt = NumN(T)
    GetServer = .Time > 0
    End With
Ext:
    If Sock <> -1 Then closesocket Sock: Sock = -1
    On Error GoTo 0
    End Function

Function OpenUDP(Optional ByVal ID As Long) As Boolean
    Dim Port As Long, TimeOut As Long
    CloseUDP
    MsgLink "Initializing UDP socket"
    LinkSocketIn = socket(2, 2, 17) 'AF_INET=2 SOCK_DGRAM=2 IPPROTO_UDP=17
    LinkSocketOut = socket(2, 2, 17) 'AF_INET=2 SOCK_DGRAM=2 IPPROTO_UDP=17
    If LinkSocketIn = -1 Or LinkSocketOut = -1 Then _
        MsgLink "Open UDP socket error": CloseUDP: Exit Function
    Port = Val(Link.Port(ID)): TimeOut = ValTime(Link.Ping(ID)) * 1000
    LinkAddrSize = Len(LinkAddrIn)
    LinkAddrOut.Family = 2 'AF_INET=2
    LinkAddrOut.Port = htons(Port)
    LinkAddrOut.Addr = HostToAdd(Link.IP(ID))
    'setsockopt LinkSocketOut, &HFFFF&, &H1001&, LinkOutSize 'SOL_SOCKET=&HFFFF SO_SNDBUF=&H1001
    'setsockopt LinkSocketOut, &HFFFF&, &H1005&, TimeOut 'SOL_SOCKET=&HFFFF SO_SNDTIMEO=&H1005
    LinkAddrIn.Family = 2 'AF_INET=2
    LinkAddrIn.Port = htons(Port)
    'LinkAddrIn.Addr = HostToAdd(Link.IP(ID))
    LinkAddrIn.Addr = htonl(0) 'INADDR_ANY=0
    'setsockopt LinkSocketIn, &HFFFF&, &H1002&, LinkInSize 'SOL_SOCKET=&HFFFF SO_RCVBUF=&H1002
    setsockopt LinkSocketIn, &HFFFF&, &H1006&, TimeOut 'SOL_SOCKET=&HFFFF SO_RCVTIMEO=&H1006
    setsockopt LinkSocketIn, &HFFFF&, 4, 1 'SOL_SOCKET=&HFFFF SO_REUSEADDR=4
    If bind(LinkSocketIn, LinkAddrIn, LinkAddrSize) Then _
        MsgLink "Bind UDP port error": CloseUDP: Exit Function
    'If connect(LinkSocketOut, LinkAddrOut, LinkAddrSize) Then _
        MsgLink "Connect UDP socket error": CloseUDP: Exit Function
    MsgLink "UDP socket opened": OpenUDP = True
    End Function

Sub CloseUDP()
    If LinkSocketIn = -1 And LinkSocketOut = -1 Then Exit Sub
    If LinkSocketIn <> -1 Then closesocket LinkSocketIn: LinkSocketIn = -1
    If LinkSocketOut <> -1 Then closesocket LinkSocketOut: LinkSocketOut = -1
    MsgLink "UDP socket closed"
    End Sub

Function OpenTCP(Optional ByVal ID As Long, Optional ByVal NonBlock As Boolean = True) As Boolean
    Dim Port As Long, TimeOut As Long
    CloseTCP
    MsgLink "Initializing TCP socket"
    LinkSocket = socket(2, 1, 6) 'PF_INET=2 SOCK_STREAM=1 IPPROTO_TCP=6
    If LinkSocket = -1 Then MsgLink "Open TCP socket error": CloseTCP: Exit Function
    Port = Val(Link.Port(ID)): TimeOut = ValTime(Link.Ping(ID)) * 1000
    LinkAddr.Family = 2 'AF_INET=2
    LinkAddr.Port = htons(Port)
    LinkAddr.Addr = HostToAdd(Link.IP(ID))
    'setsockopt LinkSocket, &HFFFF&, &H1001&, LinkSize 'SOL_SOCKET=&HFFFF SO_SNDBUF=&H1001
    'setsockopt LinkSocket, &HFFFF&, &H1005&, TimeOut 'SOL_SOCKET=&HFFFF SO_SNDTIMEO=&H1005
    If connect(LinkSocket, LinkAddr, Len(LinkAddr)) Then _
        MsgLink "Connect TCP port error": CloseTCP: Exit Function
    ioctlsocket LinkSocket, &H8004667E, -NonBlock 'FIONBIO=&H8004667E 0=block 1=nonblock
    MsgLink "TCP socket opened": OpenTCP = True
    End Function

Sub CloseTCP()
    If LinkSocket <> -1 Then closesocket LinkSocket: LinkSocket = -1: MsgLink "TCP socket closed"
    End Sub

Function COMInit() As Boolean
    Dim C As Long, D As Long
    If LinkText Then
        C = InStr(LinkOut, ";"): If C = 0 Then COMInit = True: Exit Function
        For C = 1 To C
            If WriteFile(LinkPort, Mid(LinkOut, C, 1), 1, D) = 0 Then COMInit = True: Exit Function
            If D = 0 Then COMInit = True: Exit Function
            Next
    ElseIf LinkDouble Then
        For C = VarPtr(InitD) To VarPtr(InitD) + IfInt(LinkTest, 4 + 8, Len(InitD)) - 1
            If WriteFileX(LinkPort, C, 1, D) = 0 Then COMInit = True: Exit Function
            If D = 0 Then COMInit = True: Exit Function
            Next
    Else
        For C = VarPtr(InitS) To VarPtr(InitS) + IfInt(LinkTest, 4 + 4, C = Len(InitS)) - 1
            If WriteFileX(LinkPort, C, 1, D) = 0 Then COMInit = True: Exit Function
            If D = 0 Then COMInit = True: Exit Function
            Next
        End If
    End Function

Function COMData() As Boolean
    Dim C As Long, D As Long
    If LinkText Then
        If LinkCtlOut Then C = InStr(LinkOut, vbLf) Else C = InStr(LinkOut, ";")
        If C = 0 Then COMData = True: Exit Function
        For C = 1 To C
            If WriteFile(LinkPort, Mid(LinkOut, C, 1), 1, D) = 0 Then COMData = True: Exit Function
            If D = 0 Then COMData = True: Exit Function
            Next
    ElseIf LinkDouble Then
        For C = VarPtr(DataD) To VarPtr(DataD) + IfInt(LinkTest, 4 + 8, Len(DataD)) - 1
            If WriteFileX(LinkPort, C, 1, D) = 0 Then COMData = True: Exit Function
            If D = 0 Then COMData = True: Exit Function
            Next
    Else
        For C = VarPtr(DataS) To VarPtr(DataS) + IfInt(LinkTest, 4 + 4, Len(DataS)) - 1
            If WriteFileX(LinkPort, C, 1, D) = 0 Then COMData = True: Exit Function
            If D = 0 Then COMData = True: Exit Function
            Next
        End If
    End Function

Function COMCtrl() As Long
    Dim C As Long, D As Long, S As String * 1
    If LinkText Then
        For C = 1 To LinkInSize
            If ReadFile(LinkPort, S, 1, D) = 0 Then COMCtrl = -1: Exit Function
            If D = 0 Then COMCtrl = 0: Exit Function
            Mid(LinkIn, C, 1) = S: If S = ";" Then COMCtrl = C: Exit Function
            Next
        COMCtrl = -1
    ElseIf LinkDouble Then
        For C = VarPtr(CtrlD) To VarPtr(CtrlD) + Len(CtrlD) - 1
            If ReadFileX(LinkPort, C, 1, D) = 0 Then COMCtrl = -1: Exit Function
            If D = 0 Then COMCtrl = 0: Exit Function
            Next
        COMCtrl = C - VarPtr(CtrlD)
    Else
        For C = VarPtr(CtrlS) To VarPtr(CtrlS) + Len(CtrlS) - 1
            If ReadFileX(LinkPort, C, 1, D) = 0 Then COMCtrl = -1: Exit Function
            If D = 0 Then COMCtrl = 0: Exit Function
            Next
        COMCtrl = C - VarPtr(CtrlS)
        End If
    End Function

Function UDPInit() As Boolean
    Dim C As Long
    If LinkText Then
        UDPInit = sendto(LinkSocketOut, ByVal LinkOut, LinkOutSize, 0, LinkAddrOut, LinkAddrSize) < LinkOutSize
    ElseIf LinkDouble Then
        C = IfInt(LinkTest, 4 + 8, Len(InitD))
        UDPInit = sendto(LinkSocketOut, InitD, C, 0, LinkAddrOut, LinkAddrSize) < C
    Else
        C = IfInt(LinkTest, 4 + 4, Len(InitS))
        UDPInit = sendto(LinkSocketOut, InitS, C, 0, LinkAddrOut, LinkAddrSize) < C
        End If
    End Function

Function UDPData() As Boolean
    Dim C As Long
    If LinkText Then
        UDPData = sendto(LinkSocketOut, ByVal LinkOut, LinkOutSize, 0, LinkAddrOut, LinkAddrSize) < LinkOutSize
    ElseIf LinkDouble Then
        C = IfInt(LinkTest, 4 + 8, Len(DataD))
        UDPData = sendto(LinkSocketOut, DataD, C, 0, LinkAddrOut, LinkAddrSize) < C
    Else
        C = IfInt(LinkTest, 4 + 4, Len(DataS))
        UDPData = sendto(LinkSocketOut, DataS, C, 0, LinkAddrOut, LinkAddrSize) < C
        End If
    End Function

Function UDPCtrl() As Boolean
    'ioctlsocket LinkSocketIn, &H4004667F, C 'FIONREAD=&H4004667F .. Get available data size
    If LinkText Then
        UDPCtrl = recvfrom(LinkSocketIn, ByVal LinkIn, LinkInSize, 0, LinkAddrIn, LinkAddrSize) < LinkInSize
    ElseIf LinkDouble Then
        UDPCtrl = recvfrom(LinkSocketIn, CtrlD, Len(CtrlD), 0, LinkAddrIn, LinkAddrSize) < Len(CtrlD)
    Else
        UDPCtrl = recvfrom(LinkSocketIn, CtrlS, Len(CtrlS), 0, LinkAddrIn, LinkAddrSize) < Len(CtrlS)
        End If
    End Function

Function LinkInitEx(Optional ByVal Short As Boolean) As String
    Dim C As Long
    If LinkDouble Then
        If Short Then
            LinkInitEx = "INIT," & Str(dT) & ";"
        Else
            LinkInitEx = "INIT," & Str(dT) & "," & J2ksDate(Time) & "," & J2ksTime(Time) & "," & _
                Str(Lat / Rad) & "," & Str(Lon / Rad) & "," & Str(Alt) & "," & Str(InitialSpeed) & "," & _
                Str(Heading / Rad) & "," & Str(StartAngle / Rad) & "," & Str(EndAngle / Rad) & "," & _
                Str(StartAlt) & "," & Str(EndAlt) & "," & Str(Abort) & "," & _
                Str(Stage(StageP).FullMass) & "," & Str(StageP - 1) & ","
            For C = StageP To 2 Step -1
                LinkInitEx = LinkInitEx & Str(Stage(C).FullMass - Stage(C - 1).FullMass) & "," & Str(Stage(C).DryMass - Stage(C - 1).FullMass) & "," & _
                    Str(Stage(C).ThrustV) & "," & Str(Stage(C).Delay) & ","
                Next
            LinkInitEx = LinkInitEx & Str(Stage(0).FullMass) & ";"
            End If
    Else
        If Short Then
            LinkInitEx = "INIT," & Str(CSng(dT)) & ";"
        Else
            LinkInitEx = "INIT," & Str(CSng(dT)) & "," & J2ksDate(Time) & "," & J2ksTime(Time) & "," & _
                Str(CSng(Lat / Rad)) & "," & Str(CSng(Lon / Rad)) & "," & Str(CSng(Alt)) & "," & Str(CSng(InitialSpeed)) & "," & _
                Str(CSng(Heading / Rad)) & "," & Str(CSng(StartAngle / Rad)) & "," & Str(CSng(EndAngle / Rad)) & "," & _
                Str(CSng(StartAlt)) & "," & Str(CSng(EndAlt)) & "," & Str(Abort) & "," & _
                Str(CSng(Stage(StageP).FullMass)) & "," & Str(CSng(StageP - 1)) & ","
            For C = StageP To 2 Step -1
                LinkInitEx = LinkInitEx & Str(CSng(Stage(C).FullMass - Stage(C - 1).FullMass)) & "," & Str(CSng(Stage(C).DryMass - Stage(C - 1).FullMass)) & "," & _
                    Str(CSng(Stage(C).ThrustV)) & "," & Str(CSng(Stage(C).Delay)) & ","
                Next
            LinkInitEx = LinkInitEx & Str(CSng(Stage(0).FullMass)) & ";"
            End If
        End If
    End Function

Sub LinkInit()
    Dim C As Long
    If LinkText Then
        LinkOut = LinkInitEx(LinkTest)
    ElseIf LinkDouble Then
        With InitD
        SetArray .Head, "INIT": .TimeStep = dT
        If LinkTest = 0 Then
            SetArray .Date, J2ksDate(Time): SetArray .Time, J2ksTime(Time)
            .Lat = Lat / Rad: .Lon = Lon / Rad: .Alt = Alt: .Speed = InitialSpeed: .Heading = Heading / Rad
            .StartAngle = 90 - (StartAngle / Rad): .EndAngle = 90 - (EndAngle / Rad): .StartAlt = StartAlt: .EndAlt = EndAlt
            .Abort = Abort: .PayloadMass = Stage(0).FullMass
            For C = StageP To 2 Step -1
                With .Stage(StageP - C + 1): .Mass = Stage(C).FullMass - Stage(C - 1).FullMass: .Dry = Stage(C).DryMass - Stage(C - 1).FullMass
                .Thrust = Stage(C).ThrustV: .Delay = Stage(C).Delay: End With
                Next
            End If
        End With
    Else
        With InitS
        SetArray .Head, "INIT": .TimeStep = dT
        If LinkTest = 0 Then
            SetArray .Date, J2ksDate(Time): SetArray .Time, J2ksTime(Time)
            .Lat = Lat / Rad: .Lon = Lon / Rad: .Alt = Alt: .Speed = InitialSpeed: .Heading = Heading / Rad
            .StartAngle = 90 - (StartAngle / Rad): .EndAngle = 90 - (EndAngle / Rad): .StartAlt = StartAlt: .EndAlt = EndAlt
            .Abort = Abort: .PayloadMass = Stage(0).FullMass
            For C = StageP To 2 Step -1
                With .Stage(StageP - C + 1): .Mass = Stage(C).FullMass - Stage(C - 1).FullMass: .Dry = Stage(C).DryMass - Stage(C - 1).FullMass
                .Thrust = Stage(C).ThrustV: .Delay = Stage(C).Delay: End With
                Next
            End If
        End With
        End If
    If LinkLogOut Then MsgLink LinkInitEx(LinkTest Or LinkFullLog = 0)
    End Sub

Function LinkDataEx(Pl As Planet, Optional ByVal Short As Boolean) As String
    If LinkCtlOut Then
        LinkDataEx = GetNMEA(Pl, Short)
    ElseIf LinkDouble Then
        If Short Then LinkDataEx = "DATA," & Str(T) & ";" _
        Else LinkDataEx = "DATA," & Str(T) & "," & Str(Mass) & "," & _
            Str(FullMass) & "," & Str(DryMass) & "," & Str(Thrust) & "," & Str(BurnRate) & "," & Str(Pl.Gravity) & "," & _
            Str(Pl.Distance) & "," & Str(Pl.X) & "," & Str(Pl.Y) & "," & Str(Pl.Z) & "," & _
            Str(Pl.Altitude) & "," & Str(GroundAltitude) & "," & _
            Str(Pl.Speed) & "," & Str(Pl.SpeedX) & "," & Str(Pl.SpeedY) & "," & Str(Pl.SpeedZ) & "," & _
            Str(Pl.AirSpeed) & "," & Str(Pl.AirSpeedX) & "," & Str(Pl.AirSpeedY) & "," & Str(Pl.AirSpeedZ) & ";"
    Else
        If Short Then LinkDataEx = "DATA," & Str(CSng(T)) & ";" _
        Else LinkDataEx = "DATA," & Str(CSng(T)) & "," & Str(CSng(Mass)) & "," & _
            Str(CSng(FullMass)) & "," & Str(CSng(DryMass)) & "," & Str(CSng(Thrust)) & "," & Str(CSng(BurnRate)) & "," & Str(CSng(Pl.Gravity)) & "," & _
            Str(CSng(Pl.Distance)) & "," & Str(CSng(Pl.X)) & "," & Str(CSng(Pl.Y)) & "," & Str(CSng(Pl.Z)) & "," & _
            Str(CSng(Pl.Altitude)) & "," & Str(CSng(GroundAltitude)) & "," & _
            Str(CSng(Pl.Speed)) & "," & Str(CSng(Pl.SpeedX)) & "," & Str(CSng(Pl.SpeedY)) & "," & Str(CSng(Pl.SpeedZ)) & "," & _
            Str(CSng(Pl.AirSpeed)) & "," & Str(CSng(Pl.AirSpeedX)) & "," & Str(CSng(Pl.AirSpeedY)) & "," & Str(CSng(Pl.AirSpeedZ)) & ";"
        End If
    End Function

Sub LinkData(Pl As Planet)
    If LinkText Then
        LinkOut = LinkDataEx(Pl, LinkTest)
    ElseIf LinkDouble Then
        With DataD
        SetArray .Head, "DATA": .T = T
        If LinkTest = 0 Then
            .M = Mass: .Mt = FullMass: .Mf = DryMass: .F = Thrust: .Te = BurnRate: .eG = Pl.Gravity
            .eH = Pl.Distance: .eX = Pl.X: .eY = Pl.Y: .eZ = Pl.Z: .eA = Pl.Altitude: .eR = GroundAltitude
            .eVR = Pl.Speed: .eVRX = Pl.SpeedX: .eVRY = Pl.SpeedY: .eVRZ = Pl.SpeedZ
            .eVA = Pl.AirSpeed: .eVAX = Pl.AirSpeedX: .eVAY = Pl.AirSpeedY: .eVAZ = Pl.AirSpeedZ
            End If
        End With
    Else
        With DataS
        SetArray .Head, "DATA": .T = T
        If LinkTest = 0 Then
            .M = Mass: .Mt = FullMass: .Mf = DryMass: .F = Thrust: .Te = BurnRate: .eG = Pl.Gravity
            .eH = Pl.Distance: .eX = Pl.X: .eY = Pl.Y: .eZ = Pl.Z: .eA = Pl.Altitude: .eR = GroundAltitude
            .eVR = Pl.Speed: .eVRX = Pl.SpeedX: .eVRY = Pl.SpeedY: .eVRZ = Pl.SpeedZ
            .eVA = Pl.AirSpeed: .eVAX = Pl.AirSpeedX: .eVAY = Pl.AirSpeedY: .eVAZ = Pl.AirSpeedZ
            End If
        End With
        End If
    If LinkLogOut Then MsgLink LinkDataEx(Pl, LinkTest Or LinkFullLog = 0)
    End Sub

Sub LinkCtrl()
    If Not LinkLogIn Then Exit Sub
    If LinkTest Or LinkFullLog = 0 Then
        If LinkDouble Then MsgLink Hdr & "," & Str(Tt) & ";" _
        Else MsgLink Hdr & "," & Str(CSng(Tt)) & ";"
    ElseIf LinkDouble Then
        MsgLink Hdr & "," & Str(Tt) & "," & Stx & "," & Nt & "," & _
            Str(Rt) & "," & Str(Ut) & "," & Str(Vt) & "," & Str(Wt) & ";"
    Else
        MsgLink Hdr & "," & Str(CSng(Tt)) & "," & Stx & "," & Nt & "," & _
            Str(CSng(Rt)) & "," & Str(CSng(Ut)) & "," & Str(CSng(Vt)) & "," & Str(CSng(Wt)) & ";"
        End If
    End Sub

Sub StartTrack()
    StopTrack
    ClearTrack
    OpenTrack Track1, IDLink, Setup.Preset, "Tracking", "Track", cPathTrackKML, cPathTrack
    If TrackSync = 0 Then OpenTrack Track2, IDStation, Setup.Preset, "Station", "Stat", cPathStationKML, cPathStation
    LinkLogIn = True: LinkLogIn2 = True: LinkFullLog = True
    Main.Track = True
    End Sub

Sub StopTrack()
    If Main.Track Then Main.Track = False
    CloseTrack Track1
    CloseTrack Track2
    End Sub

Sub ClearTrack()
    With Track1: Erase .Path: .N = 0: .Valid = False: .Start = 0: .SelType = TypeNone: .SelN = 0
        .Time = 0: .Lat = 0: .Lon = 0: .Alt = 0: End With
    With Track2: Erase .Path: .N = 0: .Valid = False: .Start = 0: .SelType = TypeNone: .SelN = 0
        .Time = 0: .Lat = 0: .Lon = 0: .Alt = 0: End With
    End Sub

Function OpenTrack(Track As Track, ByVal ID As Long, File As String, Name As String, _
        Label As String, ColorKML As String, ByVal Color As Long) As Boolean
    Dim S As String, T As String
    With Track
    .Mode = LinkNone: .Port = -1: .Last = 0
    Select Case LCase(Link.LinkPort(ID))
    Case "none":    Exit Function
    Case "network": Exit Function
    Case "audio":   If OpenAudio Then .Mode = LinkAudio Else Exit Function
    Case "aprs":    .Mode = LinkAPRS
    Case "server":  .Mode = LinkServer
    Case "select":  .Mode = LinkSelect
    Case "fixed":   .Mode = LinkFixed
    Case Else:      .Port = OpenCOMPort(ID, True)
                    If .Port <> -1 Then .Mode = LinkCOM Else Exit Function
        End Select
    OpenTrack = True
    ReDim .Path(100 - 1)
    .N = 0: .Name = Name: .ColorKML = ColorKML: .Color = Color: .ID = ID: .Label = Label
    .File = PresetsDir & File & "." & Name & ".kml"
    If TrackLog <> 0 And Not IsTest Then T = FileDate(.File): If T <> "" Then _
        Rename Track.File, Left(.File, Len(.File) - 4) & "." & T & ".kml"
    End With
    End Function

Sub CloseTrack(Track As Track)
    With Track
    Select Case .Mode
    Case 1: CloseHandle .Port: MsgLink "COM port closed"
    Case 2: CloseAudio
        End Select
    .Mode = LinkNone: .Port = -1: .Last = 0
    End With
    End Sub

Sub UpdateTrack(Track As Track, Text As String)
    Dim L As Long: Static N As Long, Hdr As String
    On Error Resume Next
    Open Track.File For Append As 1: If FreeFile = 1 Then Exit Sub
    If N = 0 Then
        Hdr = _
            "        </coordinates>" & vbCrLf & _
            "      </LineString>" & vbCrLf & _
            "    </Placemark>" & vbCrLf & _
            "  </Document>" & vbCrLf & _
            "</kml>" & vbCrLf
        N = Len(Hdr)
        End If
    L = LOF(1): If L > 0 Then Seek 1, 1 + L - N _
    Else Print #1, _
            "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf & _
            "<kml xmlns=""http://earth.google.com/kml/2.0"">" & vbCrLf & _
            "  <Document>" & vbCrLf & _
            "    <name>" & Track.Name & "</name>" & vbCrLf & _
            "    <Style id=""track"">" & vbCrLf & _
            "      <LineStyle>" & vbCrLf & _
            "        <color>" & Track.ColorKML & "</color>" & vbCrLf & _
            "        <width>1.5</width>" & vbCrLf & _
            "      </LineStyle>" & vbCrLf & _
            "      <PolyStyle>" & vbCrLf & _
            "        <color>3f00ff00</color>" & vbCrLf & _
            "      </PolyStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf & _
            "    <Placemark>" & vbCrLf & _
            "      <name>" & Track.Name & "</name>" & vbCrLf & _
            "      <styleUrl>#track</styleUrl>" & vbCrLf & _
            "      <LineString>" & vbCrLf & _
            "        <extrude>1</extrude>" & vbCrLf & _
            "        <tessellate>1</tessellate>" & vbCrLf & _
            "        <altitudeMode>absolute</altitudeMode>" & vbCrLf & _
            "        <coordinates>" & vbCrLf;
    Print #1, Text; Hdr;: Close
    On Error GoTo 0
    End Sub

Sub LinkTrack()
    If Track2.Mode Then LinkTrackEx Track2
    If Track1.Mode Then LinkTrackEx Track1
    End Sub

Sub LinkTrackEx(Track As Track)
    Dim D As Long, S As String * 1, T As String, V As String, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        NewDate As Double, NewTime As Double, TimeOut As Double, _
        LogInput As Boolean, Change As Boolean, Add As Boolean, Chk As Long, ID As String
    On Error Resume Next
    With Track
    
    LogInput = (LinkLogIn <> 0 And .ID = 0) Or (LinkLogIn2 <> 0 And .ID = 1)
    
    Select Case .Mode
    
    Case LinkCOM, LinkAudio
    
        NewDate = -1: NewTime = -1
        
        If LinkTest Then V = _
            "$GPGSA,A,3,11,13,,17,19,20,24,28,31,32,,,1.2,1.0,0.8*39" & vbCrLf & _
            "$GPGSV,3,1,11,11,72,127,37,13,11,197,35,14,03,034,23,17,38,304,35*79" & vbCrLf & _
            "$GPGSV,3,2,11,19,13,163,26,20,73,308,38,24,66,086,40,28,08,255,23*79" & vbCrLf & _
            "$GPGSV,3,3,11,31,14,081,38,32,62,038,41,23,41,185,00*4F" & vbCrLf & _
            "$GPRMC,144954,A,3935.0721,N,00244.4596,E,000.0,000.0,261210,000.6,W*69" & vbCrLf & _
            "$GPGGA,144954,3935.0721,N,00244.4596,E,1,09,1.0,42.0,M,51.0,M,,*75" & vbCrLf
        
        Do
        
        If LinkTest Then
        
            If V = "" Then Exit Do Else LinkIn = GetLineEx(V): LinkInPos = InStr(LinkIn, " ") + 1
            
        ElseIf .Mode = LinkAudio Then
            
            ReadAudio
            If .Mode = LinkNone Then Exit Sub
            
        Else
            
            TimeOut = TimerEx + 0.5
            For LinkInPos = 1 To LinkInSize
Nxt:
                If ReadFile(.Port, S, 1, D) = 0 Then _
                    MsgLink .Label & ":COM read error": Exit Sub
                If D = 0 Then If TimerEx < TimeOut Then GoTo Nxt _
                    Else MsgLink .Label & ":COM read timeout": Exit Sub
                Mid(LinkIn, LinkInPos, 1) = S: If S = vbLf Then Exit For
                Next
            
            End If
        
        LinkPos = 1: T = GetText
        Select Case T
        'Checksum: Two hex digits representing an 8 bit exclusive OR of all characters between, but not including, the '$' and '*'
        '$GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh<CR><LF>
        Case "$GPGGA"                                                          'Message header
            If LogInput Then MsgLink .Label & ":" & Left(LinkIn, LinkInPos - 2)
            'If Val("&H" & T & "&") <> NMEAChecksum(LinkIn) Then _
                If LogInput Then MsgLink .Label & ":Checksum error"
            T = GetText: If T <> "" Then NewTime = ValTimeEx(T, True)   'Time in UTC of position
            T = GetText: If T <> "" Then .Lat = ValDegLat(T)            'Latitude of position
            T = GetText: If T = "S" Then .Lat = -.Lat                   'Latitude N or S
            T = GetText: If T <> "" Then .Lon = ValDegLon(T)            'Longitude of position
            T = GetText: If T = "W" Then .Lon = -.Lon                   'Longitude E or W
            T = GetText                                                 'GPS quality                            (0=fix not available, 1=Non-differential GPS fix available, 2=Differential GPS (WAAS) fix available, 6=Estimated)
            T = GetText                                                 'Number of satellites                   (00 to 12)
            T = GetText                                                 'Horizontal dilution of precision       (0.5 to 99.9)
            T = GetText: If T <> "" Then .Alt = Val(T)                  'Antenna altitude above mean sea level  (-9999.9 to 99999.9)
            T = GetText                                                 'Units of antenna altitude, meters      M
            T = GetText: If T <> "" Then .Alt = .Alt + Val(T)           'Height of geoid above ellipsoid        (-999.9 to 9999.9)
            T = GetText                                                 'Units of geoid height, meters          M
            T = GetText                                                 'Age of differential GPS data, seconds  Null
            T = GetText                                                 'Differential reference station ID      Null
            '                                                           'Checksum                               *HH
            Exit Do
        '$GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh
        Case "$GPRMC"                                                   'Message header
            If LogInput Then MsgLink .Label & ":" & Left(LinkIn, LinkInPos - 2)
            'If Val("&H" & T & "&") <> NMEAChecksum(LinkIn) Then _
                If LogInput Then MsgLink .Label & ":Checksum error"
            T = GetText: If T <> "" Then NewTime = ValTimeEx(T, True)   'Time in UTC of position
            T = GetText                                                 'Validity A=OK V=Invalid
            T = GetText: If T <> "" Then .Lat = ValDegLat(T)            'Latitude of position
            T = GetText: If T = "S" Then .Lat = -.Lat                   'Latitude N or S
            T = GetText: If T <> "" Then .Lon = ValDegLon(T)            'Longitude of position
            T = GetText: If T = "W" Then .Lon = -.Lon                   'Longitude E or W
            T = GetText                                                 'Speed over ground in knots
            T = GetText: If T <> "" Then .Heading = Val(T) * Rad        'True course
            T = GetText: If T <> "" Then NewDate = ValDateEx(T, True)   'Date in UTC of position
            T = GetText                                                 'Magnetic variation
            T = GetText                                                 'Magnetic variation E or W
            '                                                           'Checksum                               *HH
        Case Else
            If LogInput Then If LinkFullLog Then MsgLink .Label & ":" & Left(LinkIn, LinkInPos - 2)
            End Select
        Loop
        If NewTime >= 0 Then .Time = NewTime Else Exit Sub
        If NewDate >= 0 Then .Time = .Time + NewDate Else .Time = .Time + J2ksNowDay
        
        If LinkTest Then
            If .ID = 0 Then .Lon = .Lon + Rad
            .Lat = .Lat + (Rnd + 1) / 10000 * .N
            .Lon = .Lon + (Rnd + 1) / 10000 * .N
            If .ID = 0 Then .Alt = .Alt + (Rnd + .N) * 1000
            .Time = J2ksNow
            End If
    
    Case LinkAPRS To LinkFixed
        
        If .Mode = LinkSelect Then
            If .ID = 0 Then
                If SelectedType <> TypeSatellite Then GoTo NoSelect
            ElseIf SelectedType = TypeLocation Then
                If Location(SelectedN).Planet <> ToEarth Then GoTo NoSelect
            ElseIf SelectedType <> TypeStation Then
                GoTo NoSelect
                End If
            .SelType = SelectedType: .SelN = SelectedN
NoSelect:
            If .SelType = TypeNone Then Exit Sub
            ID = "Select": .Time = J2ksNow
            ObjectPosition .SelType, .SelN, X1, Y1, Z1
            XYZ2LLA Earth, .Lat, .Lon, .Alt, X1, Y1, Z1
        
        ElseIf .Mode = LinkFixed Then
            If .ID = 0 Then Exit Sub
            If TimerEx - .Last >= TrackRefresh Then .Last = TimerEx Else Exit Sub
            ID = "Fixed": .Time = J2ksNow: .Lat = Lat: .Lon = Lon: .Alt = Alt
        
        ElseIf LinkTest Then
            If .Mode = LinkAPRS Then
                ID = "APRS"
            ElseIf .Mode = LinkServer Then
                ID = "Server"
                End If
            With Track
            If .Time = 0 Then .Lat = ValAngle(Setup.Lat): .Lon = ValAngle(Setup.Lon): _
                .Alt = ValSpace(Setup.Alt): If .ID = 0 Then _
                    .Lat = .Lat + 0.1 * Rad: .Lon = .Lon + 0.2 * Rad: .Alt = .Alt + 10000
            If .ID = 0 Then .Lat = .Lat - (Rnd + 0.5) * 0.005 * Rad: _
                .Lon = .Lon + (Rnd + 0.5) * 0.01 * Rad: .Alt = .Alt + Rnd * 200
            .Heading = 0: .Time = J2ksNow
            End With
        
        Else
            If TimerEx - .Last >= TrackRefresh Then .Last = TimerEx Else Exit Sub
            If .Mode = LinkAPRS Then
                ID = "APRS": If Not GetAPRS(Track) Then _
                    MsgLink .Label & ": " & ID & " read error": Exit Sub
            ElseIf .Mode = LinkServer Then
                ID = "Server": If Not GetServer(Track) Then _
                    MsgLink .Label & ": " & ID & " read error": Exit Sub
            Else
                Exit Sub
                End If
            End If
        
        If LogInput Then
            MsgLink .Label & ": " & ID & " " & J2ksDateTime(.Time)
            MsgLink .Label & ": " & ID & " " & ToLat(.Lat, FLong, Degrees) & " " & _
                ToLon(.Lon, FLong, Degrees) & " " & ToAlt(.Alt, FMini)
            End If
        
    Case Else
                
        Exit Sub
        
        End Select
    
    If .Lat <> 0 Or .Lon <> 0 Or .Alt <> 0 Then .Valid = True Else Exit Sub
    
    If .ID = 0 And Track2.Valid Then LLA2AED Earth, _
        Track2.TargetHdg, Track2.TargetElv, Track2.TargetDst, _
        Track2.Lat, Track2.Lon, Track2.Alt, Track1.Lat, Track1.Lon, Track1.Alt: _
        If LogInput Then If LinkFullLog Then MsgLink .Label & ": " & ID & " " & _
            ToAngle(Track2.TargetHdg - IfDbl(Stimated, Track2.Heading, 0), FAngleLon) & " " & _
            ToAngle(Track2.TargetElv, FAngleLat) & " " & ToDistance(Track2.TargetDst, FOne)
    
    With .Path(.N)
    If .Lat <> Track.Lat Or .Lon <> Track.Lon Or .Alt <> Track.Alt Then Change = True: _
        If Track.N > 0 Or .Color <> 0 Then Add = True
    End With
    If Add Then .N = .N + 1: If .N > UBound(.Path) Then ReDim Preserve .Path(UBound(.Path) + 100)
    With .Path(.N)
    .Time = Track.Time: If Track.N = 0 Then .Color = Track.Color
    If Change Then .Lat = Track.Lat: .Lon = Track.Lon: .Alt = Track.Alt: _
        If TrackLog <> 0 And Not IsTest Then _
            UpdateTrack Track, NumX(.Lon / Rad) & "," & NumX(.Lat / Rad) & "," & _
                NumX(.Alt, FOne) & vbCrLf
    End With
    Tool = Tool And Not (ToolStage Or ToolLaunch Or ToolRanges Or ToolVectors) 'Or ToolLocations
    ToolX = ToolX And Not ToolReference
    If Cam1.Flat = 0 Then If Cam1.Free Or Cam1.Follow <> ToTactic Then FollowMode Cam1, ToTactic
    If TrackSync <> 0 And .Alt >= Alt And Alt > 0 Then
        If .Start = 0 Then .Start = .Time
        Init , , , , False, False, False
        NewTime = .Time - .Start: If NewTime > 10 * Min Then NewTime = 10 * Min
        TimeSpeed = Int(NewTime / dT + 0.5): If TimeSpeed < 1 Then _
            Render: Info Else Play = Paused: Nav
        Play = Paused
    Else
        With Setup
        If TrackSync <> 0 And Track.ID = 0 Then
            .Lat = ToLat(Track.Lat, FQuad, Degrees): .Lat.Refresh
            .Lon = ToLon(Track.Lon, FQuad, Degrees): .Lon.Refresh
            .Date = J2ksDate(Track.Time): .Date.Refresh
            .Time = J2ksTime(Track.Time): .Time.Refresh
        ElseIf TrackSync = 0 And Track.ID = 1 Then
            .Lat = ToLat(Track.Lat, FQuad, Degrees): .Lat.Refresh
            .Lon = ToLon(Track.Lon, FQuad, Degrees): .Lon.Refresh
            .Alt = ToAlt(Track.Alt, FOne): .Alt.Refresh
            .Date = J2ksDate(Track.Time): .Date.Refresh
            .Time = J2ksTime(Track.Time): .Time.Refresh
            .Heading = ToAngle(A90, "0º")
            End If
        End With
        Play = Paused: Init
        End If
    
    End With
    On Error GoTo 0
    
    End Sub

Function GetNMEA(Pl As Planet, Optional ByVal Short As Boolean) As String
    Dim A As Long, B As Long, C As Long, D As Long, _
        H As Long, N As Long, S As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        Lon2 As Long, Lat2 As Long, _
        LonH As String, LatH As String
        
    J2ksToDate Time + T, , , , H, N, S
    If Short Then GetNMEA = _
        "$GPGGA," & Format(H, "00") & Format(N, "00") & Format(Int(S), "00"): Exit Function
    XYZ2LLA Pl, Lat, Lon, Alt
    Lat = Lat / Rad: If Lat < 0 Then Lat = -Lat: LatH = ",S," Else LatH = ",N,"
    Lat2 = Int(Lat): Lat = Int((Lat - Lat2) * 600000) / 10000
    Lon = Lon / Rad: If Lon < 0 Then Lon = -Lon: LonH = ",W," Else LonH = ",E,"
    Lon2 = Int(Lon): Lon = Int((Lon - Lon2) * 600000) / 10000
    
    '$GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh<CR><LF>
    '$GPGGA,144954,3935.0721,N,00244.4596,E,1,09,1.0,42.0,M,51.0,M,,*75
    GetNMEA = "$GPGGA," & _
        Format(H, "00") & Format(N, "00") & Format(Int(S), "00") & "," & _
        Format(Lat2, "00") & Replace(Format(Lat, "00.0000"), ",", ".") & LatH & _
        Format(Lon2, "000") & Replace(Format(Lon, "00.0000"), ",", ".") & LonH & _
        "1,08,1.0," & Replace(Format(Alt, "0.0"), ",", ".") & ",M,0.0,M,,"
    
    'Checksum: Two hex digits representing an 8 bit exclusive OR of all characters between, but not including, the '$' and '*'
    For C = 2 To Len(GetNMEA)
        D = D Xor Asc(Mid(GetNMEA, C, 1))
        Next
    D = D And &HFF&: If D >= &H10& Then GetNMEA = GetNMEA & "*" & Hex(D) & vbCrLf _
        Else GetNMEA = GetNMEA & "*0" & Hex(D) & vbCrLf
    
    End Function

'Sub UBXChecksum(Optional S As String, Optional ChkA As Long, Optional ChkB As Long)
'    Dim C As Long, N As Long, U As String, T() As String
'    'S = "0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x40"
'    'S = "0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x05, 0x47"
'    S = "0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xDC"
'    T = Split(Replace(Replace(S, " ", ""), "0x", ""), ","): ChkA = 0: ChkB = 0
'    For N = 0 + 2 To UBound(T) - 2
'        C = Val("&H" & T(N) & "&") And &HFF&
'        ChkA = ChkA + C
'        ChkB = ChkB + ChkA
'        Next
'    ChkA = ChkA And &HFF&: ChkB = ChkB And &HFF&
'    Clipboard.Clear: Clipboard.SetText S & " = 0x" & Hex(ChkA) & ", 0x" & Hex(ChkB): Stop
'    End Sub

'Function NMEAChecksum(Optional S As String) As Long
'    Dim A As Long, B As Long, C As Long
'    'S = "$GPGGA,144954,3935.0721,N,00244.4596,E,1,09,1.0,42.0,M,51.0,M,,*75"
'    A = InStr(S, "$"): B = InStr(S, "*"): If B = 0 Then B = Len(S) + 1
'    For C = A + 1 To B - 1
'        NMEAChecksum = NMEAChecksum Xor Asc(Mid(S, C, 1))
'        Next
'    'Clipboard.Clear: Clipboard.SetText Mid(LinkIn, A, B - A + 1) & IIf(NMEAChecksum < &H10&, "0", "") & Hex(NMEAChecksum)
'    End Function

Sub LinkGraph(T As String)
    Dim C As Long, N As Long, S As String, U As String, V As Double, T1 As Double
    If T = "" Then Exit Sub
    V = NumH(T, S, N)
    If N Then
DoValues:
        If Graph2N > UBound(Graph2V, 2) Then _
            ReDim Preserve Graph2V(Graph2C, UBound(Graph2V, 2) + 100)
        T1 = J2ksNow(True): If Graph2T(0) = 0 Then Graph2T(0) = T1
        Graph2V(0, Graph2N) = T1 - Graph2T(0)
        N = 1: Do While N <= Graph2C
            Graph2V(N, Graph2N) = V
            If V < Graph2Min(N) Then Graph2Min(N) = V
            If V > Graph2Max(N) Then Graph2Max(N) = V
            If N > Graph2I Then Graph2I = N
            If Graph2T(N) = 0 Then Graph2T(N) = T1
            N = N + 1
            If T <> "" Then V = NumH(T) Else Exit Do
            Loop
        Graph2N = Graph2N + 1: If Graph2Scroll Then Graph2P = Graph2N - 1
    ElseIf S = "I" Or S = "INIT" Then
        InitGraph2 False
        C = &H10&: For N = 1 To Graph2C
            If T <> "" Then S = Trim(Param(T)) Else Exit For
            If S = "" Or S = "-" Then S = "": Graph2Mode = Graph2Mode Or C _
            Else Graph2Mode = Graph2Mode And Not C
            Graph2L(N) = S: C = C * 2
            Next
    Else 'If S = "D" Or S = "DATA" Then
        If T <> "" Then V = NumH(T): GoTo DoValues
    'Else
        'MsgLink "Unknown command"
        End If
    End Sub

Sub InitGraph2(Optional ByVal Update As Boolean = True)
    Dim N As Long
    CleanUpGraph2 False: InitGraph2Colors False: Graph2Scroll = True
    ReDim Graph2V(Graph2C, 100 - 1), Graph2T(Graph2C), Graph2L(Graph2C), _
        Graph2Min(Graph2C), Graph2Max(Graph2C), Graph2R(0, 0)
    For N = 0 To Graph2C
        Graph2Min(N) = Infinite: Graph2Max(N) = -Infinite
        Graph2T(N) = 0: Graph2L(N) = IIf(N = 0, "Time", "Data " & N)
        Next
    If Update Then Plot2
    End Sub

Sub CleanUpGraph2(Optional ByVal Update As Boolean = True)
    Graph2N = 0: Graph2P = 0: Graph2I = 0: Graph2W = 0
    Graph2H = 0: Graph2E = 0: Graph2F = 0: Graph2EMin = 0: Graph2EStep = 0
    Graph2Cmd = "": Graph2Scroll = False
    Erase Graph2V, Graph2T, Graph2L, Graph2R, Graph2Min, Graph2Max, cGraph2
    If Update Then Plot2
    End Sub

Sub InitGraph2Colors(Optional ByVal Update As Boolean = True)
    ReDim cGraph2(Graph2C)
    Dim N As Long
    For N = 1 To Graph2C
        cGraph2(N) = Switch( _
            N = 1, cGraph2A, N = 2, cGraph2B, N = 3, cGraph2C, N = 4, cGraph2D, _
            N = 5, cGraph2E, N = 6, cGraph2F, N = 7, cGraph2G, True, cGraph2H)
        Next
    If Update Then Plot2
    End Sub

Sub SaveGraph2()
    Dim C As Long, N As Long, S As String, Name As String
    Static Op As OpenFileName
    If Graph2N <= 0 Or Graph2I <= 0 Then Exit Sub
    If Graph2Hist Then If Graph2F <= 0 Then Exit Sub
    Name = GetSaveFile(Op, Setup.Preset & ".csv", FileCSV, "csv", LogsDir)
    If Name = "" Then Exit Sub
    On Error Resume Next
    Open Name For Output As 1: If FreeFile = 1 Then Exit Sub
    If Graph2Hist Then S = "#, Min, Max" Else S = "#, Time[HH:MM:SS.S], ElapsedTime[s]"
    For N = 1 To Graph2I: S = S & ", " & Graph2L(N): Next
    If LogOption And LogSeparator Then Print #1, Replace(S, ",", ";") Else Print #1, S
    If Graph2Hist Then
        For C = 0 To Graph2F - 1
            S = C & "," & NumD(Graph2EMin + C * Graph2EStep) & "," & NumD(Graph2EMin + C * Graph2EStep + Graph2EStep)
            For N = 1 To Graph2I: S = S & "," & NumD(Graph2R(N, C)): Next
            If LogOption And LogSeparator Then Print #1, ToSSV(S) Else Print #1, S
            Next
    Else
        For C = 0 To Graph2N - 1
            S = C & "," & J2ksTime(Graph2T(0) + Graph2V(0, C), True) & "," & NumD(Graph2V(0, C))
            For N = 1 To Graph2I: S = S & "," & NumD(Graph2V(N, C)): Next
            If LogOption And LogSeparator Then Print #1, ToSSV(S) Else Print #1, S
            Next
        End If
    Close
    On Error GoTo 0
    End Sub

Sub CopyGraph2()
    Dim N As Long, S As String, R As Double, _
        MinR As Double, MinV As Double, MaxV As Double
    If Graph2N <= 0 Or Graph2P < 0 Or Graph2P >= Graph2N Or Graph2I <= 0 Then Exit Sub
    MinV = GetGraph2Min: MaxV = GetGraph2Max
    If Graph2Hist Then
        If Graph2F > 0 Then MinR = Graph2EMin: R = Graph2EStep Else Exit Sub
        S = "Range" & vbTab & NumDX(MinR + R * Graph2E, LinkDouble) & vbCrLf & _
            "to" & vbTab & NumDX(MinR + R * Graph2E + R, LinkDouble) & vbCrLf & _
            "Max" & vbTab & NumDX(MaxV, LinkDouble) & vbCrLf & _
            "Min" & vbTab & NumDX(MinV, LinkDouble) & vbCrLf
        For N = 1 To Graph2I
            S = S & Graph2L(N) & vbTab & Graph2R(N, Graph2E) & vbCrLf
            Next
    Else
        S = Graph2L(0) & vbTab & J2ksTime(Graph2T(0) + Graph2V(0, Graph2P), True) & vbCrLf & _
            vbTab & NumX(Graph2V(0, Graph2P), IIf(LinkDouble, FLong, FShort)) & "s" & vbCrLf & _
            "Max" & vbTab & NumDX(MaxV, LinkDouble) & vbCrLf & _
            "Min" & vbTab & NumDX(MinV, LinkDouble) & vbCrLf
        For N = 1 To Graph2I
            S = S & Graph2L(N) & vbTab & NumDX(Graph2V(N, Graph2P), LinkDouble) & vbCrLf
            Next
        End If
    Clipboard.Clear: Clipboard.SetText S
    End Sub

Function GetGraph2Min() As Double
    Dim E As Long, N As Long
    If Graph2N > 0 And Graph2I > 0 Then GetGraph2Min = Infinite: E = &H10& Else Exit Function
    For N = 1 To Graph2I
        If (Graph2Mode And E) = 0 Then _
            If Graph2Min(N) < GetGraph2Min Then GetGraph2Min = Graph2Min(N)
        E = E * 2
        Next
    If GetGraph2Min = Infinite Then GetGraph2Min = 0
    End Function

Function GetGraph2Max() As Double
    Dim E As Long, N As Long
    If Graph2N > 0 And Graph2I > 0 Then GetGraph2Max = -Infinite: E = &H10& Else Exit Function
    For N = 1 To Graph2I
        If (Graph2Mode And E) = 0 Then _
            If Graph2Max(N) > GetGraph2Max Then GetGraph2Max = Graph2Max(N)
        E = E * 2
        Next
    If GetGraph2Max = -Infinite Then GetGraph2Max = 0
    End Function

Sub Plot2(Optional ByVal Pos As Long, Optional ByVal Mode As Long, _
        Optional X As Single, Optional Y As Single)
    Dim B As Long, C As Long, D As Long, E As Long, I As Long, _
        L As Long, P As Long, N As Long, W As Long, H As Long, _
        K As Double, R As Double, V As Double, _
        R1 As Double, R2 As Double, K2 As Double, V2 As Double, _
        Min As Double, Max As Double, MinV As Double, MaxV As Double, _
        MinR As Double, MaxR As Double, Pic As PictureBox
    
    On Error Resume Next
    If Not Graph2.Visible Then Exit Sub
    
    Set Pic = Graph2.Pic
    Pic.Cls: I = Pic.ForeColor
    Pic.Print Tab(2);: D = Pic.CurrentX * IfInt(LinkDouble, 30, 22)
    Pic.Print: L = Pic.CurrentY
    B = 10: H = Pic.ScaleHeight - 2: W = Pic.ScaleWidth - D - 1
    Select Case Mode '0=Relative -1=Absolute -2=MouseMove -3=MouseDown -4=DblClick -5=MoveWindow
    Case 0: Graph2P = Graph2P + Pos: Graph2H = Graph2H + Pos
    Case -1: Graph2P = Pos: If Pos <= 0 Then Graph2H = 0 Else Graph2H = W - 1
    Case -2: Graph2P = Graph2W + X - D: Graph2H = X - D
                If Graph2P < Graph2W Then Graph2P = Graph2W
    Case -3, -4: If X < D Then Graph2Mode = Graph2Mode Xor 2 ^ Int(Y / L) _
                Else If Mode = -4 Then Graph2Scroll = Not Graph2Scroll
    Case -5: Graph2P = Graph2P + Pos: Graph2W = Graph2W + Pos
        End Select
    'If (Mode <> 0 Or Pos <> 0) And Mode <> -2 Then Graph2Scroll = False
    'If Graph2Scroll Then Graph2P = Graph2N - 1
    If Graph2W > Graph2N - W Then Graph2W = Graph2N - W
    If Graph2W < 0 Then Graph2W = 0
    If Graph2P > Graph2N - 1 Then Graph2P = Graph2N - 1
    If Graph2P < 0 Then Graph2P = 0
    If Graph2P > Graph2W + W - 1 Then Graph2W = Graph2P - W + 1
    If Graph2P < Graph2W Then Graph2W = Graph2P
    If W > Graph2N - Graph2W Then W = Graph2N - Graph2W
    MinV = GetGraph2Min: MaxV = GetGraph2Max
    
    Pic.Line (D, 2)-Step(0, H - 1), cGraphLine 'Left margin
    If Graph2N <= 0 Or Graph2I <= 0 Then GoTo NoGraph
    Pic.Line (D, H)-Step(W - 1, 0), cGraphLine 'Bottom margin
    
    If Graph2Hist Then 'Histogram
        
    If Graph2Step < 5 Then Graph2Step = 5 Else If Graph2Step > 1000 Then Graph2Step = 1000
    W = Pic.ScaleWidth - D - 1: If W < 10 Or H < 10 Then GoTo NoGraph
    If Graph2H < 0 Then Graph2H = 0 Else If Graph2H >= W Then Graph2H = W - 1
    R = NumRound((MaxV - MinV) / Graph2Step): If R <= 0 Then R = 1
    MinR = Int(MinV / R): MaxR = Int(MaxV / R) + 1: If MaxR <= MinR Then MaxR = MinR + 1
    Graph2F = MaxR - MinR: MinR = MinR * R: MaxR = MaxR * R
    Graph2EMin = MinR: Graph2EStep = R
    
    'Calculate histogram
    ReDim Graph2R(Graph2I, Graph2F)
    E = &H10&: For N = 1 To Graph2I
        If (Graph2Mode And E) = 0 Then
            For C = 0 To Graph2N - 1
                P = Int((Graph2V(N, C) - MinR) / R)
                Graph2R(N, P) = Graph2R(N, P) + 1
                Next
            End If
        E = E * 2
        Next
    E = &H10&: For N = 1 To Graph2I
        If (Graph2Mode And E) = 0 Then
            For C = 0 To Graph2F
                If Graph2R(N, C) > Graph2R(0, C) Then Graph2R(0, C) = Graph2R(N, C)
                Next
            End If
        E = E * 2
        Pic.Print
        Next
    For C = 0 To Graph2F
        If Graph2R(0, C) > Max Then Max = Graph2R(0, C)
        Next
    Min = 0: If Max <= Min Then Max = Min + 10
    
    'Data numbers
    Pic.CurrentX = 0: Pic.CurrentY = 2
    Graph2E = Int(Graph2H * Graph2F / W)
    Pic.Print "  Range"; Tab(B); NumDX(MinR + R * Graph2E, LinkDouble)
    Pic.Print "  to"; Tab(B); NumDX(MinR + R * Graph2E + R, LinkDouble)
    Pic.Print "  Max"; Tab(B); NumDX(MaxV, LinkDouble)
    Pic.Print "  Min"; Tab(B); NumDX(MinV, LinkDouble)
    E = &H10&: For N = 1 To Graph2I
        Pic.ForeColor = cGraph2(N): Pic.Print "•";: Pic.ForeColor = I
        Pic.Print Graph2L(N); Tab(B); IIf(Graph2Mode And E, "-", _
            CStr(Graph2R(N, Graph2E)))
        E = E * 2
        Next
    
    'Ranges scale lines
    If (Graph2Mode And 1) <> 0 And W > 100 Then 'Time scale
        R1 = W / (MaxR - MinR): If R1 < 0 Then R1 = 0
        If R1 > 0 Then
            R2 = NumRound(100 / R1)
            If R2 > 0 Then
                Pic.DrawStyle = vbDot
                V2 = Int(MinR / R2 + 1) * R2: K2 = Int(MaxR / R2 + 1) * R2
                V = V2: Do While V < K2
                    Pic.Line (D + (V - MinR) * R1, H)-Step(0, -H), cGraphLine
                    If Graph2Mode And 4 Then Pic.CurrentX = Pic.CurrentX + 2: Pic.CurrentY = 2: _
                        If Pic.CurrentX < D + W - 20 Then Pic.Print NumDX(V, LinkDouble);
                    V = V + R2
                    Loop
                Pic.DrawStyle = vbSolid
                End If
            End If
        End If
    If W > 0 Then Pic.Line (D + W - 1, 2)-Step(0, H - 1), cGraphLine 'Right margin
    
    'Population scale lines
    R1 = (H - L) / (Max - Min): If R1 < 0 Then R1 = 0
    If (Graph2Mode And 2) <> 0 And H > 25 Then 'Data scale
        If R1 > 0 Then
            R2 = NumRound(50 / R1)
            If R2 > 0 Then
                V2 = Int(Min / R2 + 1) * R2: K2 = Int(Max / R2 + 1) * R2
                V = V2: Do While V < K2
                    Pic.Line (D, H - (V - Min) * R1)-Step(W - 1, 0), cGraphLine
                    V = V + R2
                    Loop
                End If
            End If
        End If
    
    'Histogram lines
    Pic.DrawWidth = 2
    E = &H10&: For N = 1 To Graph2I
        If (Graph2Mode And E) = 0 Then
            I = cGraph2(N): Pic.PSet (D, H - 1), I
            For C = 0 To Graph2F - 1
                V = H - (Graph2R(N, C) - Min) * R1
                Pic.Line -(D + W * C / Graph2F, V), I
                Pic.Line -(D + W * (C + 1) / Graph2F, V), I
                Next
            Pic.Line -(D + W, H), I
            End If
        E = E * 2
        Next
    Pic.DrawWidth = 1
    Pic.Line (D, H)-Step(W - 1, 0), cGraphLine 'Bottom margin
    
    'Population labels
    If (Graph2Mode And &HA&) = &HA& And R2 > 0 Then 'Data scale
        V = V2: Do While V < K2
            Pic.CurrentY = H - (V - Min) * R1
            If Pic.CurrentY > 0 And Pic.CurrentY < H - L Then _
                Pic.CurrentX = D + 2: Pic.Print NumD(RoundEx(V, R2));
            V = V + R2
            Loop
        End If
    
    Pic.Line (D + Graph2H, 2)-Step(0, H - 1), &HC08040 'Cursor
    
    Else 'Graph
    
    Min = MinV: Max = MaxV
    'If Max <= 0 Then Max = 0
    'If Min >= 0 Then Min = 0
    If Min = Max Then Max = Max + 2: Min = Min - 2
    R1 = (H - L) / (Max - Min): If R1 < 0 Then R1 = 0
    
    'Data numbers
    Pic.CurrentX = 0: Pic.CurrentY = 2
    Pic.Print "  "; Graph2L(0); Tab(B); J2ksTime(Graph2T(0) + Graph2V(0, Graph2P), True)
    Pic.Print Tab(B); NumX(Graph2V(0, Graph2P), IIf(LinkDouble, FLong, FShort)) & "s"
    Pic.Print "  Max"; Tab(B); NumDX(MaxV, LinkDouble)
    Pic.Print "  Min"; Tab(B); NumDX(MinV, LinkDouble)
    E = &H10&: For N = 1 To Graph2I
        Pic.ForeColor = cGraph2(N): Pic.Print "•";: Pic.ForeColor = I
        Pic.Print Graph2L(N); Tab(B); IIf(Graph2Mode And E, "-", _
            NumDX(Graph2V(N, Graph2P), LinkDouble))
        E = E * 2
        Next
    'If C <= 0 Then GoTo NoLines
    
    'Data scale lines
    If (Graph2Mode And 2) <> 0 And H > 25 Then 'Data scale
        If R1 > 0 Then
            R2 = NumRound(50 / R1)
            If R2 > 0 Then
                V2 = Int(Min / R2 + 1) * R2: K2 = Int(Max / R2 + 1) * R2
                V = V2: Do While V < K2
                    Pic.Line (D, H - (V - Min) * R1)-Step(W - 1, 0), cGraphLine
                    V = V + R2
                    Loop
                End If
            End If
        End If
    
    'Time scale lines and labels
    If (Graph2Mode And 1) <> 0 And W > 35 Then 'Time scale
        Pic.DrawStyle = vbDot
        C = Graph2W + W - 1: If C >= Graph2N Then C = Graph2N - 1
        V = Graph2V(0, Graph2W): R = (Graph2V(0, C) - V) / W * 50
        R = NumRound(R): If R < 0.1 Then R = 0.1
        V = Int(V / R) * R: N = 1
        For C = 1 To W - 1
            K = Int(Graph2V(0, Graph2W + C) / R) * R
            If V <> K Then
                V = K
                If N = 1 Or C - N > 20 Then
                    N = C: Pic.Line (D + C, H)-Step(0, -H), cGraphLine
                    If Graph2Mode And 4 Then _
                        Pic.CurrentX = D + C + 2: Pic.CurrentY = 2: Pic.Print NumX(V, FOne);
                    End If
                End If
            Next
        Pic.DrawStyle = vbSolid
        End If
    If W > 0 Then Pic.Line (D + W - 1, 2)-Step(0, H - 1), cGraphLine 'Right margin

    'Data lines
    If H > L Then
        Pic.DrawWidth = 2: E = &H10&: R = 1
        For N = 1 To Graph2I
            If (Graph2Mode And E) = 0 Then
                I = cGraph2(N): Pic.PSet (D, H - (Graph2V(N, Graph2W) - Min) * R1 - 1), I
                For C = 1 To W - 1
                    Pic.Line -(D + C, H - (Graph2V(N, Graph2W + C) - Min) * R1), I
                    Next
                End If
            E = E * 2
            Next
        Pic.DrawWidth = 1
        End If
    
    'Data labels
    If (Graph2Mode And &HA&) = &HA& And R2 > 0 Then 'Data scale
        V = V2: Do While V < K2
            Pic.CurrentY = H - (V - Min) * R1
            If Pic.CurrentY > 0 And Pic.CurrentY < H - L Then _
                Pic.CurrentX = D + 2: Pic.Print NumD(RoundEx(V, R2));
            V = V + R2
            Loop
        End If
NoLines:
    If Graph2P >= Graph2W Then Pic.Line (D - Graph2W + Graph2P, 2)-Step(0, H - 1), &HC08040 'Cursor
    
    End If
NoGraph:
    On Error GoTo 0
    End Sub

Sub CalcBalloon()
    Dim C As Long, D As Long, E As Long, _
        T1 As Double, T2 As Double, T3 As Double, L1 As Double, K1 As Double, _
        A1 As Double, G1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double
    
    If Play <> Paused Then Exit Sub
    If T > 0 Then Nav Reset
    If AtmMode = 0 Then SetAtmMode 3, True: Options.UpdateSimulation
    If AtmN = 0 Then Exit Sub
    If AtmDate <> Setup.Date Or AtmTime <> Setup.Time Then GRIBLoad True
    If KMLPathN > 0 And (KMLPathP >= KMLPathTop Or IsBalloonTrajectory = 0 Or T > 0) Then
        With KMLPath(0): T1 = .Time: X1 = .Lon: Y1 = .Lat: Z1 = .Alt: End With
    Else
        T1 = ValDateEx(Setup.Date) + ValTimeEx(Setup.Time)
        X1 = ValAngle(Setup.Lon): Y1 = ValAngle(Setup.Lat)
        If Setup.Alt <> "" Then Z1 = ValSpace(Setup.Alt) _
        Else If TerrainMode = 2 Then Z1 = AltEarth(Y1, X1) _
        Else Z1 = RoundEx(AtmAlt(Y1, X1), 10): If Z1 < 0 Then Z1 = 0
        End If
    KMLPathN = 0: KMLPathTop = 0: KMLPathPos = 0: KMLPathAlt = 0: KMLPathP = 0
    ReDim KMLPath(100 - 1)
    With KMLPath(KMLPathN)
    .Time = T1: .Lon = X1: .Lat = Y1: .Alt = Z1
    L1 = ValSpace(Calc.Burst): If L1 > 100000 Then L1 = 100000 Else If L1 <= 0 Then _
        If Calc.Burst = "" Then L1 = 32000 Else L1 = .Alt: C = 1
    If IsMass(Calc.Ascent) Then 'Terminal velocity .. V=Sqr((2*M*G)/(Cd*D*A))
        A1 = ValMass(Calc.Ascent): If A1 < 0.001 Then A1 = 0.001
        A1 = Sqr((2 * A1 * G0) / (0.5 * AirD * Pi * 1.5 ^ 2 / 4)) 'Cd=0.5 Diameter=1.5m
    Else
        A1 = Abs(ValSpace(Calc.Ascent))
        End If
    If A1 <= 0 Then A1 = 5 Else If A1 < 0.5 Then A1 = 0.5 Else If A1 > 50 Then A1 = 50
    K1 = Abs(ValSpace(Calc.Descent))
    If K1 <= 0 Then K1 = 5 Else If K1 < 0.5 Then K1 = 0.5 Else If K1 > 70 Then K1 = 70
    K1 = K1 ^ 2 * AirD: G1 = Earth.Diameter * Pi / A360
    T2 = ValTime(Calc.Mission)
    If T2 <= 0 Then T2 = 0 Else If T2 > Year Then T2 = T1 + Year Else T2 = T1 + T2
    T1 = 60: D = 10: T1 = T1 / D '60 sec point step .. 10 sec sim step .. 6 steps per point
    End With
    AtmEarth KMLPath(KMLPathN)
    With KMLPath(KMLPathN): .SpeedV = A1 + .WindZ: .SpeedH = Sqr(.WindX * .WindX + .WindY * .WindY): End With
    
    Do
        KMLPathN = KMLPathN + 1
        If C >= 2 Then Exit Do
        If KMLPathN > UBound(KMLPath) Then ReDim Preserve KMLPath(UBound(KMLPath) + 100)
        KMLPath(KMLPathN) = KMLPath(KMLPathN - 1)
        For E = 1 To D
            With KMLPath(KMLPathN)
            H1 = Cos(.Lat): If H1 <= 1E-20 Then H1 = 1E-20
            .Lat = .Lat + .WindY * T1 / G1
            If .Lat < -A90 Then .Lat = -A90 Else If .Lat > A90 Then .Lat = A90
            .Lon = .Lon + .WindX * T1 / G1 / H1
            If .Lon < -A180 Then .Lon = .Lon + A360 Else If .Lon >= A180 Then .Lon = .Lon - A360
            If C = 1 Then A1 = -Sqr(K1 / .Density)
            .SpeedV = A1 + .WindZ: .SpeedH = Sqr(.WindX * .WindX + .WindY * .WindY)
            .Time = .Time + T1: .Alt = .Alt + (A1 + .WindZ) * T1
            End With
            AtmEarth KMLPath(KMLPathN)
            Next
        With KMLPath(KMLPathN)
        If C = 0 Then
            If .Alt >= L1 Then .Alt = L1: _
                If T2 = 0 Then KMLPathTop = KMLPathN: C = 1 _
                Else .SpeedV = 0: If .Time >= T2 Then KMLPathTop = KMLPathN: C = 1
        Else
            If Not IsCollision Then Z1 = 0 _
            Else If TerrainMode = 2 Then Z1 = AltEarth(.Lat, .Lon) _
            Else Z1 = AtmAlt(.Lat, .Lon)
            If .Alt <= Z1 Then C = 2: .Alt = Z1
            End If
        End With
        Loop
    
    If KMLPathN > 0 Then
        If UBound(KMLPath) <> KMLPathN - 1 Then ReDim Preserve KMLPath(KMLPathN - 1)
        KMLPointN = 3: ReDim KMLPoint(KMLPointN - 1)
        CopyPoint KMLPoint(0), KMLPath(0), "Launch"
        CopyPoint KMLPoint(1), KMLPath(KMLPathTop), "Burst"
        CopyPoint KMLPoint(2), KMLPath(KMLPathN - 1), "Impact"
        If Not IsTest Then
            If SavePathEx(False) Then UpdateFile PresetsDir & Setup.Preset & ".kml", StrAdd
            If SavePathCSV Then UpdateFile PresetsDir & Setup.Preset & ".csv", StrAdd
            End If
        If IsBalloonTrajectory Then KMLPathPos = 1
        With KMLPath(IfInt(IsBalloonTrajectory, 0, KMLPathTop))
        SetTime .Time
        SetLaunch .Lat, .Lon, .Alt, , True: KMLPathAlt = .Alt
        End With
    Else
        KMLPathN = 0: KMLPointN = 0: KMLPathTop = 0: KMLPathPos = 0: KMLPathAlt = 0: KMLPathP = 0
        Erase KMLPath, KMLPoint
        End If
    Init
    End Sub

Sub CalcLEO()
    Dim C As Long, N As Long, Ok As Long, Eq As Long, St As Long, _
        R As Double, S As String, _
        Delta As Double, Mass As Double, LastMass As Double, _
        Speed As Double, LastSpeed As Double, TargetSpeed As Double, _
        Angle As Double, LastAngle As Double, _
        Altitude As Double, LastAltitude As Double, TargetAltitude As Double, _
        OrbitTime As Double, Delay As Double, TargetVerticalSpeed As Double, _
        Payload As Double, LastTimeSpeed As Double, StartTime As Double, _
        Orbit As TLE
    
    On Error Resume Next
    IsCalc = CheckErrors: If Not IsCalc Then Exit Sub
    Delta = 0.00001: TargetVerticalSpeed = 0
    
    With Calc
    If InStr(.Orbit, "s") Or InStr(.Orbit, "h") Or InStr(.Orbit, "d") Or InStr(.Orbit, "y") Then
        OrbitTime = ValTimeHMS(.Orbit)
IsOrbitTime:
        TargetAltitude = (OrbitTime / Pi / 2 * Sqr(Earth.GravityMass)) ^ (2 / 3) - Earth.Radius
        GoTo IsOrbitAltitude
    Else
        TargetAltitude = ValSpace(.Orbit)
IsOrbitAltitude:
        If TargetAltitude <= 0 Then TargetAltitude = 150000 _
        Else If TargetAltitude <= 1440 And Right(.Orbit, 1) = "m" And Right(.Orbit, 2) <> "km" Then _
            OrbitTime = TargetAltitude * Min: GoTo IsOrbitTime _
        Else If TargetAltitude < Atm Then TargetAltitude = Atm
        End If
    TargetSpeed = Sqr(Earth.GravityMass / (Earth.Radius + TargetAltitude))
    OrbitTime = (Earth.Radius + TargetAltitude) * 2 * Pi / TargetSpeed
    End With
    
    With Setup
    .StartAlt = ToAlt(IfDbl(TargetAltitude < 250000, 250000, TargetAltitude) / 10, "0")
    .EndAlt = ToAlt(TargetAltitude, IIf(TargetAltitude >= 1000000, FShort, FZero))
    .Angle = "90º": .StartAngle = "60º": .EndAngle = "0º"
    TargetAltitude = ValSpace(.EndAlt)
    .EarthPark = "": .MoonPark = "": .ThrustLimit = "": .LiftLimit = ""
    End With
    
    With Stages
    .Abort = "": Payload = ValMass(.Mass(PayloadMax)) ': .Stage(HoverMax) = False
    If .Stage(HoverMax) Then Payload = Payload + ValMass(.Mass(HoverMax))
    .Mass(0) = ToMass(Payload * 20, FShort)
    For N = 1 To StageMax
        If .Stage(N) Then .Delay(N) = IfText(N > 1, "3s"): C = N
        Next
    SetSaveEvents True, False: SetLogEvents True, False: SetPauseEvents , , False
    SetPauseEvent LastStageEnginesOff, True
    Options.EventsAll = 6: TouchDown = True
    Log.Txt = "Calculating trajectory for a low Earth orbit of " & _
        ToAlt(TargetAltitude, FMini) & vbCrLf & "altitude and a payload of " & _
        ToMass(Payload, FShort) & ":" & vbCrLf & vbCrLf
    End With
    
    Play = Paused: LastTimeSpeed = TimeSpeed: TimeSpeed = MaxTimeSpeed: StartTime = TimerEx
    If C < 1 Then GoTo Quit
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    
SetMass:
    'MsgLog vbCrLf & "Adjusting total mass to reach orbital speed …"
    'If Abs(Speed / TargetSpeed) < 1.001 Then N = 1 Else N = 10
    Mass = 0: Speed = 0
IsMass:
    LastMass = Mass: LastSpeed = Speed
    Mass = ValMass(Stages.Mass(0)): Speed = Earth.Speed
    'TargetSpeed = Sqr(Earth.GravityMass / Earth.Distance)
    R = Abs(Speed / TargetSpeed - 1)
    MsgLog "Adjusting total mass … " & vbTab & _
        ToMass(Mass, FShort) & vbTab & "-> " & NumX(Speed / TargetSpeed, "0.000%")
    If R < Delta Then St = St Or 1: Ok = Ok + 1: GoTo OkMass
    If Mass <= 0 Or Mass > Payload * 1000000 Then GoTo Quit
    If LastMass = 0 Then R = TargetSpeed / Speed _
    Else R = (Mass + (Mass - LastMass) / (Speed - LastSpeed) * (TargetSpeed - Speed)) / Mass
    If St = 7 Then R = 1 - ((1 - R) / 3)
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = ToMass(Mass * R, FShort): If Stages.Mass(0) <> S Then _
        Stages.Mass(0) = S: Eq = 0 Else St = St Or 1: Eq = Eq + 1: GoTo OkMass
    Ok = 0
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    'N = N - 1: If N > 0 Then GoTo IsMass
    If St = 0 Then GoTo IsMass
OkMass:
    'MsgLog "  … adjusted using a total mass of " & Stages.Mass(0)
    If Ok >= 3 Then GoTo Done
    If Eq >= 6 Then GoTo Loss
    'If St = 0 Then GoTo SetMass
    
SetAngle:
    'MsgLog vbCrLf & "Adjusting trajectory angle to reach orbital altitude …"
    'If Abs(Altitude - TargetAltitude) < 5000 Then N = 1 Else N = 5
    Angle = 0: Altitude = 0
IsAngle:
    LastAngle = Angle: LastAltitude = Altitude
    Angle = Val(Setup.StartAngle): Altitude = Earth.Altitude
    R = Abs(Altitude / TargetAltitude - 1) / 5
    MsgLog "Adjusting trajectory angle …" & vbTab & _
        NumX(Angle, "0.000º") & vbTab & "-> " & ToAlt(Altitude, FMini)
    If R < Delta Then St = St Or 2: Ok = Ok + 1: GoTo OkAngle
    If Angle <= 0 Or MinHe < MaxHe / 2 Then GoTo Quit
    If LastAngle = 0 Then R = TargetAltitude / Altitude _
    Else R = (Angle + (Angle - LastAngle) / (Altitude - LastAltitude) * _
        (TargetAltitude - Altitude)) / Angle
    If St = 7 Then R = 1 - ((1 - R) / 3)
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    If Angle * R < 10 Then R = 10 / Angle Else If Angle * R > 88 Then R = 88 / Angle
    S = NumX(Angle * R, "0.000º"): If Setup.StartAngle <> S Then _
        Setup.StartAngle = S: Eq = 0 Else St = St Or 2: Eq = Eq + 1: GoTo OkAngle
    Ok = 0
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    'N = N - 1: If N > 0 Then GoTo IsAngle
    'If St = 3 Then GoTo IsAngle
OkAngle:
    'MsgLog "  … adjusted using a start angle of " & Setup.StartAngle
    If Ok >= 3 Then GoTo Done
    If Eq >= 6 Then GoTo Loss
    If St = 1 Then GoTo SetMass
    
SetDelay:
    'MsgLog vbCrLf & "Adjusting last stage delay for a null vertical speed …"
    'If Abs(Earth.VerticalSpeed - TargetVerticalSpeed) < 100 Then N = 1 Else N = 3
IsDelay:
    Delay = ValTime(Stages.Delay(C))
    R = Abs(Earth.VerticalSpeed) / 100000
    MsgLog "Adjusting last stage delay …" & vbTab & _
        NumX(Delay, FMini) & "s" & vbTab & "-> " & ToSpeed(Earth.VerticalSpeed, FMini)
    If R < Delta Then St = St Or 4: Ok = Ok + 1: GoTo OkDelay
    If Delay < 3 Or MinHe < MaxHe / 2 Then GoTo Quit
    R = (Delay + (Earth.VerticalSpeed - TargetVerticalSpeed) / 20) / Delay
    If St = 7 Then R = 1 - ((1 - R) / 3)
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = NumX(Delay * R, FMini) & "s": If Stages.Delay(C) <> S Then _
        Stages.Delay(C) = S: Eq = 0 Else St = St Or 4: Eq = Eq + 1: GoTo OkDelay
    Ok = 0
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    'N = N - 1: If N > 0 Then GoTo IsDelay
OkDelay:
    'MsgLog "  … adjusted using a last stage delay of " & Stages.Delay(C)
    If Ok >= 3 Then GoTo Done
    If Eq >= 6 Then GoTo Loss
    'If St = 3 Then GoTo SetMass
    GoTo SetMass
    
Abort:
    MsgLog vbCrLf & _
        "Trajectory calculation aborted:": GoTo Msg
Loss:
    MsgLog vbCrLf & _
        "Error adjusting trajectory.": GoTo Ext
Quit:
    MsgLog vbCrLf & _
        "Error adjusting trajectory:" & vbCrLf & _
        "  Try to use a propellant with higher Isp, reduce the" & vbCrLf & _
        "  dry mass, add more stages or adjust the stage mass" & vbCrLf & _
        "  distribution and the engine thrust for every stage." _
        : GoTo Ext
Done:
    MsgLog vbCrLf & _
        "Trajectory adjusted for a low Earth orbit:"
Msg:
    Vector2TLE Earth, Orbit
    MsgLog vbCrLf & _
        "  Payload mass:" & Tab1 & ToMass(Stage(0).FullMass, FShort) & vbCrLf & _
        "  Total mass:" & Tab1 & ToMass(Stage(StageT).FullMass, FShort) & vbCrLf & _
        "  Propellant mass:" & Tab1 & ToMass(TotalFuelMass, FShort) & vbCrLf & _
        "  Altitude:" & Tab2 & ToAlt(Earth.Altitude, FShort) & vbCrLf & _
        "  Orbital period:" & Tab1 & ToDhEx(A360 / Orbit.M) & vbCrLf & _
        "  Orbital speed:" & Tab1 & ToSpeed(Earth.Speed, FShort) & _
            " (" & NumX(Earth.Speed / TargetSpeed, "0.000%") & ")" & vbCrLf & _
        "  Vertical speed:" & Tab1 & ToAlt(Earth.VerticalSpeed, FShort) & "/s" & vbCrLf & _
        "  Burn time:" & Tab1 & ToMs(T) & vbCrLf & _
        "  Simulation time:" & Tab1 & ToMs(TimerEx - StartTime)
Ext:
    Options.EventsAll = 0: TouchDown = False
    SetSaveEvents , False: SetLogEvents , False: SetPauseEvents , , False
    SetPauseEvent LastStageEnginesOff, True, , True
    TimeSpeed = LastTimeSpeed: Message
    IsCalc = False: Calc.Calc.Caption = "Calculate"
    On Error GoTo 0
    
    End Sub

Sub CalcMEO()
    Dim C As Long, N As Long, Ok As Long, Eq As Long, St As Long, Rp As Long, _
        R As Double, S As String, A1 As Double, _
        Delta As Double, Mass As Double, LastMass As Double, _
        Speed As Double, LastSpeed As Double, TargetSpeed As Double, _
        Angle As Double, LastAngle As Double, _
        Altitude As Double, LastAltitude As Double, TargetAltitude As Double, _
        Delay As Double, TargetVerticalSpeed As Double, _
        AngleH As Double, AngleV As Double, Park As Double, _
        OrbitTime As Double, OrbitAltitude As Double, OrbitSpeed As Double, _
        Lat2 As Double, Lon2 As Double, Alt2 As Double, _
        Payload As Double, LastTimeSpeed As Double, StartTime As Double, _
        Orbit As TLE
    
    On Error Resume Next
    IsCalc = CheckErrors: If Not IsCalc Then Exit Sub
    Delta = 0.00001: TargetVerticalSpeed = 0
    
    With Calc
    If InStr(.Orbit, "s") Or InStr(.Orbit, "h") Or InStr(.Orbit, "d") Or InStr(.Orbit, "y") Then
        OrbitTime = ValTimeHMS(.Orbit)
IsOrbitTime:
        TargetAltitude = (OrbitTime / Pi / 2 * Sqr(Earth.GravityMass)) ^ (2 / 3) - Earth.Radius
        GoTo IsOrbitAltitude
    Else
        TargetAltitude = ValSpace(.Orbit)
IsOrbitAltitude:
        If TargetAltitude <= 0 Then TargetAltitude = 150000 _
        Else If TargetAltitude <= 1440 And Right(.Orbit, 1) = "m" And Right(.Orbit, 2) <> "km" Then _
            OrbitTime = TargetAltitude * Min: GoTo IsOrbitTime _
        Else If TargetAltitude < Atm Then TargetAltitude = Atm
        End If
    TargetSpeed = Sqr(Earth.GravityMass / (Earth.Radius + TargetAltitude))
    OrbitTime = (Earth.Radius + TargetAltitude) * 2 * Pi / TargetSpeed
    OrbitAltitude = TargetAltitude: OrbitSpeed = TargetSpeed
    TargetAltitude = 150000
    TargetSpeed = Sqr(Earth.GravityMass / (Earth.Radius + TargetAltitude))
    End With
    
    With Setup
    .StartAlt = ToAlt(25000, "0")
    .EndAlt = ToAlt(TargetAltitude, IIf(TargetAltitude >= 1000000, "0.00", "0"))
    .Angle = "90º": .StartAngle = "45º": .EndAngle = "0º"
    TargetAltitude = ValSpace(.EndAlt)
    .EarthPark = "": .MoonPark = "": .ThrustLimit = "": .LiftLimit = ""
    .Time = J2ksTime(Int((ValDateEx(.Date) + ValTimeEx(.Time)) / Min) * Min)
    End With
    
    With Stages
    .Abort = "": Payload = ValMass(.Mass(PayloadMax))
    If .Stage(HoverMax) Then Payload = Payload + ValMass(.Mass(HoverMax))
    .Mass(0) = ToMass(Payload * 10, FShort)
    For N = 1 To StageMax
        If .Stage(N) Then .Delay(N) = IfText(N > 1, "3s"): C = N
        Next
    SetSaveEvents True, False: SetLogEvents True, False: SetPauseEvents , , False
    SetPauseEvent StageAbort, True
    Options.EventsAll = 6: TouchDown = True
    Log.Txt = "Calculating trajectory for a medium Earth orbit of" & vbCrLf & _
        ToAlt(OrbitAltitude, FShort) & " altitude and a payload of " & _
        ToMass(Payload, FShort) & ":" & vbCrLf & vbCrLf
    End With
    
    Play = Paused: LastTimeSpeed = TimeSpeed: TimeSpeed = MaxTimeSpeed: StartTime = TimerEx
    C = C - 1: If C >= 1 Then Stages.Abort = C Else GoTo Quit
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    
SetMass:
    Mass = 0: Speed = 0
IsMass:
    LastMass = Mass: LastSpeed = Speed
    Mass = ValMass(Stages.Mass(0)): Speed = Earth.Speed
    R = Abs(Speed / TargetSpeed - 1)
    MsgLog "Adjusting total mass … " & vbTab & _
        ToMass(Mass, FShort) & vbTab & "-> " & NumX(Speed / TargetSpeed, "0.000%")
    If R < Delta Then St = St Or 1: Ok = Ok + 1: GoTo OkMass
    If Mass <= 0 Or Mass > Payload * 1000000 Then GoTo Quit
    If LastMass = 0 Then R = TargetSpeed / Speed _
    Else R = (Mass + (Mass - LastMass) / (Speed - LastSpeed) * (TargetSpeed - Speed)) / Mass
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = ToMass(Mass * R, FShort): If Stages.Mass(0) <> S Then _
        Stages.Mass(0) = S: Eq = 0 Else St = St Or 1: Eq = Eq + 1: GoTo OkMass
    Ok = 0
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    GoTo IsMass
OkMass:
    SetPauseEvent LastStageEnginesOff, False: SetPauseEvent EarthOrbitalSpeed, True
    
SetAngle:
    Angle = 0: Altitude = 0
IsAngle:
    LastAngle = Angle: LastAltitude = Altitude
    Angle = Val(Setup.StartAngle): Altitude = Earth.Altitude
    R = Abs(Altitude / TargetAltitude - 1) / 5
    MsgLog "Adjusting trajectory angle …" & vbTab & _
        NumX(Angle, "0.000º") & vbTab & "-> " & ToAlt(Altitude, FMini)
    If R < Delta * 5 Then St = St Or 2: Ok = Ok + 1: GoTo OkAngle
    If Angle <= 0 Or MinHe < MaxHe / 2 Then GoTo Quit
    If LastAngle = 0 Then R = TargetAltitude / Altitude _
    Else R = (Angle + (Angle - LastAngle) / (Altitude - LastAltitude) * _
        (TargetAltitude - Altitude)) / Angle
    If R > 0.9 And R < 1.1 Then R = ((R - 1) / 2) + 1
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    If Angle * R < 10 Then R = 10 / Angle Else If Angle * R > 88 Then R = 88 / Angle
    S = NumX(Angle * R, "0.000º"): If Setup.StartAngle <> S Then _
        Setup.StartAngle = S: Eq = 0 Else St = St Or 2: Eq = Eq + 1: GoTo OkAngle
    Ok = 1
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
OkAngle:
    If Ok >= 3 Then GoTo SetApogee
    If St = 1 Then GoTo IsAngle
    
SetDelay:
IsDelay:
    Delay = ValTime(Stages.Delay(C))
    R = Abs(Earth.VerticalSpeed) / 100000
    MsgLog "Adjusting inter stage delay …" & vbTab & _
        NumX(Delay, FMini) & "s" & vbTab & "-> " & ToSpeed(Earth.VerticalSpeed, FMini)
    If R < Delta Then St = St Or 4: Ok = Ok + 1: GoTo OkDelay
    If Delay < 3 Or MinHe < MaxHe / 2 Then GoTo Quit
    R = (Delay + (Earth.VerticalSpeed - TargetVerticalSpeed) / 20) / Delay
    If Abs(Earth.VerticalSpeed) < 30 Then R = ((R - 1) / 3) + 1
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = NumX(Delay * R, FMini) & "s": If Stages.Delay(C) <> S Then _
        Stages.Delay(C) = S: Eq = 0 Else St = St Or 4: Eq = Eq + 1: GoTo OkDelay
    Ok = 1
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
OkDelay:
    If Ok >= 3 Then GoTo SetApogee
    GoTo SetAngle
    
SetApogee:
    SetPauseEvent EarthOrbitalSpeed, False: SetPauseEvent StageAbort, False
    SetPauseEvent Apogee, True: RefreshPoints = True
    Init False, , , , False, False, False: Do: Nav: If Not Note.Active Then GoTo Abort
        Loop While Status <> Aborted
    Mass = 0: Altitude = 0: TargetAltitude = OrbitAltitude
IsApogee:
    LastMass = Mass: LastAltitude = Altitude
    Mass = ValMass(Stages.Mass(0)): Altitude = Earth.Altitude
    R = Abs(Altitude / TargetAltitude - 1) / 5
    MsgLog "Adjusting total mass … " & vbTab & _
        ToMass(Mass, FShort) & vbTab & "-> " & ToAlt(Altitude, FMini)
    If R < Delta * 5 Then St = St Or 8: Ok = Ok + 1: GoTo OkApogee
    If Mass <= 0 Or Mass > Payload * 1000000 Then GoTo Quit
    If LastMass = 0 Then R = TargetAltitude / Altitude _
    Else R = (Mass + (Mass - LastMass) / (Altitude - LastAltitude) * _
        (TargetAltitude - Altitude)) / Mass
    If St = 7 Then R = 1 - ((1 - R) / 3)
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = ToMass(Mass * R, FShort): If Stages.Mass(0) <> S Then _
        Stages.Mass(0) = S: Eq = 0 Else St = St Or 8: Eq = Eq + 1: Ok = Ok + 1: GoTo OkApogee
    Ok = 1
    Init False, , , , False, False, False: Do: Nav: If Not Note.Active Then GoTo Abort
        Loop While Status <> Aborted
    GoTo IsApogee
OkApogee:
    Rp = Rp + 1: If Rp >= 4 Or Ok >= 4 Then GoTo Nxt
    SetPauseEvent EarthOrbitalSpeed, True: SetPauseEvent StageAbort, True
    SetPauseEvent Apogee, False: RefreshPoints = False
    TargetAltitude = 150000
    GoTo SetAngle
    
Nxt:
    SetPauseEvent Apogee, False: SetPauseEvent LastStageEnginesOff, True
    Stages.Abort = "": Stages.Dry(C + 1) = "50%"
    A1 = T - AccelTime: Stages.Delay(C + 1) = NumX(A1 / Min, "0.00") & "m"
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    TargetSpeed = OrbitSpeed
    
SetLastDelay:
IsLastDelay:
    Delay = ValTime(Stages.Delay(C + 1))
    R = Abs(Earth.VerticalSpeed) / 100000
    MsgLog "Adjusting last stage delay …" & vbTab & _
        NumX(Delay / Min, FMini) & "m" & vbTab & "-> " & ToSpeed(Earth.VerticalSpeed, FMini)
    If R < Delta Then St = St Or 16: Ok = Ok + 1: GoTo OkLastDelay
    If Delay < 3 Or MinHe < MaxHe / 2 Then GoTo Quit
    R = (Delay + (Earth.VerticalSpeed - TargetVerticalSpeed) / 20 * Min) / Delay
    If Abs(Earth.VerticalSpeed) < 30 Then R = ((R - 1) / 3) + 1
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = NumX(Delay * R / Min, FMini) & "m": If Stages.Delay(C + 1) <> S Then _
        Stages.Delay(C + 1) = S: Eq = 0 Else St = St Or 16: Eq = Eq + 1: GoTo OkLastDelay
    Ok = 4
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
OkLastDelay:
    If Ok >= 6 Or Eq > 2 Then GoTo Done
    
SetDry:
IsDry:
    Mass = Val(Replace(Stages.Dry(C + 1), "%", "")) / 100: Speed = Earth.Speed
    R = Abs(Speed / TargetSpeed - 1)
    MsgLog "Adjusting dry mass … " & vbTab & _
        NumX(Mass, "0.00%") & vbTab & "-> " & NumX(Speed / TargetSpeed, "0.000%")
    If R < Delta Then St = St Or 32: Ok = Ok + 1: GoTo OkDry
    If Mass <= 0.01 Or Mass > 0.99 Then GoTo Quit
    R = TargetSpeed / Speed: If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = NumX(Mass / R, "0.00%"): If Stages.Dry(C + 1) <> S Then _
        Stages.Dry(C + 1) = S: Eq = 0 Else St = St Or 32: Eq = Eq + 1: GoTo OkDry
    Ok = 4
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
OkDry:
    If Ok >= 6 Or Eq > 2 Then GoTo Done
    GoTo SetLastDelay
    
Abort:
    MsgLog vbCrLf & _
        "Trajectory calculation aborted:": GoTo Msg
Loss:
    MsgLog vbCrLf & _
        "Error adjusting trajectory.": GoTo Ext
Quit:
    MsgLog vbCrLf & _
        "Error adjusting trajectory:" & vbCrLf & _
        "  Try to use a propellant with higher Isp, reduce the" & vbCrLf & _
        "  dry mass, add more stages or adjust the stage mass" & vbCrLf & _
        "  distribution and the engine thrust for every stage." _
        : GoTo Ext
Done:
    MsgLog vbCrLf & _
        "Trajectory adjusted for a medium Earth orbit:"
Msg:
    Vector2TLE Earth, Orbit
    MsgLog vbCrLf & _
        "  Payload mass:" & Tab1 & ToMass(Stage(0).FullMass, FShort) & vbCrLf & _
        "  Total mass:" & Tab1 & ToMass(Stage(StageT).FullMass, FShort) & vbCrLf & _
        "  Propellant mass:" & Tab1 & ToMass(TotalFuelMass, FShort) & vbCrLf & _
        "  Altitude:" & Tab2 & ToAlt(Earth.Altitude, FShort) & vbCrLf & _
        "  Orbital period:" & Tab1 & ToDhEx(A360 / Orbit.M) & vbCrLf & _
        "  Orbital speed:" & Tab1 & ToSpeed(Earth.Speed, FShort) & _
            " (" & NumX(Earth.Speed / TargetSpeed, "0.000%") & ")" & vbCrLf & _
        "  Vertical speed:" & Tab1 & ToAlt(Earth.VerticalSpeed, FShort) & "/s" & vbCrLf & _
        "  Burn time:" & Tab1 & ToMs(T) & vbCrLf & _
        "  Simulation time:" & Tab1 & ToMs(TimerEx - StartTime)
Ext:
    Stages.Abort = ""
    Options.EventsAll = 0: TouchDown = False
    SetSaveEvents , False: SetLogEvents , False: SetPauseEvents , , False
    SetPauseEvent LastStageEnginesOff, True, , True
    RefreshPoints = False
    TimeSpeed = LastTimeSpeed: Message
    IsCalc = False: Calc.Calc.Caption = "Calculate"
    On Error GoTo 0
    
    End Sub

Sub CalcMoon()
    Dim C As Long, N As Long, Ok As Long, Eq As Long, St As Long, Rp As Long, _
        R As Double, S As String, A1 As Double, H1 As Double, _
        IsMoon As Boolean, Delta As Double, Mass As Double, LastMass As Double, _
        Speed As Double, LastSpeed As Double, TargetSpeed As Double, _
        Angle As Double, LastAngle As Double, _
        Altitude As Double, LastAltitude As Double, TargetAltitude As Double, _
        Delay As Double, TargetVerticalSpeed As Double, _
        AngleH As Double, AngleV As Double, Park As Double, _
        OrbitTime As Double, IonSpeed As Double, _
        Lat2 As Double, Lon2 As Double, Alt2 As Double, _
        Payload As Double, LastTimeSpeed As Double, StartTime As Double
    
    On Error Resume Next
    IsCalc = CheckErrors: If Not IsCalc Then Exit Sub
    Delta = 0.00001: TargetVerticalSpeed = 0
    
    With Calc
    If IsSpeed(.Mission) Then
        IonSpeed = ValSpace(.Mission)
    Else
        A1 = ValTime(.Mission) / Day: If A1 <= 0 Then A1 = 2.5 _
            Else If A1 < 0.5 Then A1 = 0.5 Else If A1 > 4 Then A1 = 4
             If A1 < 1 Then IonSpeed = 14022 - (A1 - 0.5) / (1 - 0.5) * (14022 - 11465) _
        Else If A1 < 1.5 Then IonSpeed = 11465 - (A1 - 1) / (1.5 - 1) * (11465 - 10992) _
        Else If A1 < 2 Then IonSpeed = 10992 - (A1 - 1.5) / (2 - 1.5) * (10992 - 10808) _
        Else If A1 < 2.5 Then IonSpeed = 10808 - (A1 - 2) / (2.5 - 2) * (10808 - 10731) _
        Else If A1 < 3 Then IonSpeed = 10731 - (A1 - 2.5) / (3 - 2.5) * (10731 - 10694) _
        Else IonSpeed = 10694 - (A1 - 3) / (4 - 3) * (10694 - 10673)
        End If
    If InStr(.Orbit, "s") Or InStr(.Orbit, "h") Or InStr(.Orbit, "d") Or InStr(.Orbit, "y") Then
        OrbitTime = ValTimeHMS(.Orbit)
IsOrbitTime:
        TargetAltitude = (OrbitTime / Pi / 2 * Sqr(Earth.GravityMass)) ^ (2 / 3) - Earth.Radius
        GoTo IsOrbitAltitude
    Else
IsOrbitAltitude:
        TargetAltitude = ValSpace(.Orbit)
        If TargetAltitude <= 0 Then TargetAltitude = 150000 _
        Else If TargetAltitude <= 1440 And Right(.Orbit, 1) = "m" And Right(.Orbit, 2) <> "km" Then _
            OrbitTime = TargetAltitude * Min: GoTo IsOrbitTime _
        Else If TargetAltitude < Atm Then TargetAltitude = Atm
        End If
    H1 = Earth.Radius + TargetAltitude: A1 = Sqr(Earth.GravityMass / H1)
    TargetSpeed = A1 * 1.33: OrbitTime = H1 * Pi * 2 / A1 / Min
    End With
    
    With Setup
    .StartAlt = ToAlt(25000, "0")
    .EndAlt = ToAlt(TargetAltitude, IIf(TargetAltitude >= 1000000, "0.00", "0"))
    .Angle = "90º": .StartAngle = "45º": .EndAngle = "0º"
    TargetAltitude = ValSpace(.EndAlt)
    .EarthPark = "": .MoonPark = "": .ThrustLimit = "": .LiftLimit = "": .Brake = ""
    .Time = J2ksTime(Int((ValDateEx(.Date) + ValTimeEx(.Time)) / Min) * Min)
    End With
    
    With Stages
    .Abort = "": Payload = ValMass(.Mass(PayloadMax))
    If .Stage(HoverMax) Then Payload = Payload + ValMass(.Mass(HoverMax))
    .Mass(0) = ToMass(Payload * 10, FShort)
    For N = 1 To StageMax
        If .Stage(N) Then .Delay(N) = IfText(N > 1, "3s"): C = N
        Next
    SetSaveEvents True, False: SetLogEvents True, False: SetPauseEvents , , False
    SetPauseEvent LastStageEnginesOff, True
    Options.EventsAll = 6: TouchDown = True
    Log.Txt = "Calculating trajectory for a Moon mission with a" & vbCrLf & _
        "parking orbit and a payload of " & ToMass(ValMass(.Mass(PayloadMax)), FShort) & ":" & vbCrLf & vbCrLf
    End With
    
    Play = Paused: LastTimeSpeed = TimeSpeed: TimeSpeed = MaxTimeSpeed: StartTime = TimerEx
    If C < 1 Or Not Stages.Stage(HoverMax) Then GoTo Quit
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    
SetMass:
    Mass = 0: Speed = 0
IsMass:
    LastMass = Mass: LastSpeed = Speed
    Mass = ValMass(Stages.Mass(0)): Speed = Earth.Speed
    R = Abs(Speed / TargetSpeed - 1)
    MsgLog "Adjusting total mass … " & vbTab & _
        ToMass(Mass, FShort) & vbTab & "-> " & NumX(Speed / TargetSpeed, "0.000%")
    If R < Delta Then St = St Or 1: Ok = Ok + 1: GoTo OkMass
    If Mass <= 0 Or Mass > Payload * 1000000 Then GoTo Quit
    If LastMass = 0 Then R = TargetSpeed / Speed _
    Else R = (Mass + (Mass - LastMass) / (Speed - LastSpeed) * (TargetSpeed - Speed)) / Mass
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = ToMass(Mass * R, FShort): If Stages.Mass(0) <> S Then _
        Stages.Mass(0) = S: Eq = 0 Else St = St Or 1: Eq = Eq + 1: GoTo OkMass
    Ok = 0
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    GoTo IsMass
OkMass:
    SetPauseEvent LastStageEnginesOff, False: SetPauseEvent EarthOrbitalSpeed, True
    
SetAngle:
    Angle = 0: Altitude = 0
IsAngle:
    LastAngle = Angle: LastAltitude = Altitude
    Angle = Val(Setup.StartAngle): Altitude = Earth.Altitude
    R = Abs(Altitude / TargetAltitude - 1) / 5
    MsgLog "Adjusting trajectory angle …" & vbTab & _
        NumX(Angle, "0.000º") & vbTab & "-> " & ToAlt(Altitude, FMini)
    If R < Delta * 5 Then St = St Or 2: Ok = Ok + 1: GoTo OkAngle
    If Angle <= 0 Or MinHe < MaxHe / 2 Then GoTo Quit
    If LastAngle = 0 Then R = TargetAltitude / Altitude _
    Else R = (Angle + (Angle - LastAngle) / (Altitude - LastAltitude) * _
        (TargetAltitude - Altitude)) / Angle
    If R > 0.9 And R < 1.1 Then R = ((R - 1) / 2) + 1
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    If Angle * R < 10 Then R = 10 / Angle Else If Angle * R > 88 Then R = 88 / Angle
    S = NumX(Angle * R, "0.000º"): If Setup.StartAngle <> S Then _
        Setup.StartAngle = S: Eq = 0 Else St = St Or 2: Eq = Eq + 1: GoTo OkAngle
    Ok = 1
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
OkAngle:
    If Ok >= 3 Then GoTo SetIon
    If St = 1 Then GoTo IsAngle
    
SetDelay:
IsDelay:
    Delay = ValTime(Stages.Delay(C))
    R = Abs(Earth.VerticalSpeed) / 100000
    MsgLog "Adjusting last stage delay …" & vbTab & _
        NumX(Delay, FMini) & "s" & vbTab & "-> " & ToSpeed(Earth.VerticalSpeed, FMini)
    If R < Delta Then St = St Or 4: Ok = Ok + 1: GoTo OkDelay
    If Delay < 3 Or MinHe < MaxHe / 2 Then GoTo Quit
    R = (Delay + (Earth.VerticalSpeed - TargetVerticalSpeed) / 20) / Delay
    If Abs(Earth.VerticalSpeed) < 30 Then R = ((R - 1) / 3) + 1
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = NumX(Delay * R, FMini) & "s": If Stages.Delay(C) <> S Then _
        Stages.Delay(C) = S: Eq = 0 Else St = St Or 4: Eq = Eq + 1: GoTo OkDelay
    Ok = 1
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
OkDelay:
    If Ok >= 3 Then GoTo SetIon
    GoTo SetAngle
    
SetIon:
    SetPauseEvent EarthOrbitalSpeed, False: SetPauseEvent ExitIonosphere, True
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    Mass = 0: Speed = 0: TargetSpeed = IonSpeed
IsIon:
    LastMass = Mass: LastSpeed = Speed
    Mass = ValMass(Stages.Mass(0)): Speed = Earth.Speed
    R = Abs(Speed / TargetSpeed - 1)
    MsgLog "Adjusting total mass … " & vbTab & _
        ToMass(Mass, FShort) & vbTab & "-> " & NumX(Speed / TargetSpeed, "0.000%")
    If R < Delta Then St = St Or 8: Ok = Ok + 1: GoTo OkIon
    If Mass <= 0 Or Mass > Payload * 1000000 Then GoTo Quit
    If LastMass = 0 Then R = TargetSpeed / Speed _
    Else R = (Mass + (Mass - LastMass) / (Speed - LastSpeed) * (TargetSpeed - Speed)) / Mass
    If St = 7 Then R = 1 - ((1 - R) / 2)
    If R < 0.95 Then R = 0.95 Else If R > 1.05 Then R = 1.05
    S = ToMass(Mass * R, FShort): If Stages.Mass(0) <> S Then _
        Stages.Mass(0) = S: Eq = 0 Else St = St Or 8: Eq = Eq + 1: Ok = Ok + 1: GoTo OkIon
    Ok = 1
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    GoTo IsIon
OkIon:
    Rp = Rp + 1: If Rp >= 4 Or Ok >= 4 Then GoTo Nxt
    SetPauseEvent ExitIonosphere, False: SetPauseEvent EarthOrbitalSpeed, True
    GoTo SetAngle
    
Nxt:
    SetPauseEvent ExitIonosphere, False: SetPauseEvent MoonDistance, True: RefreshPoints = True
    Init False, , , , False, False, False: Nav: If Not Note.Active Then GoTo Abort
    
SetTime:
IsTime:
    AngleH = (ATan2(-Earth.Y, -Earth.X) - ATan2(Moon.Y - Earth.Y, Moon.X - Earth.X)) / Rad
    If AngleH < -180 Then AngleH = AngleH + 360 _
    Else If AngleH >= 180 Then AngleH = AngleH - 360 _
    Else If Abs(AngleH) < 0.01 Then St = St Or 16: Ok = Ok + 1: GoTo OkTime
    Eq = 0: SetTime -Int(AngleH * Min * 5), True
    MsgLog "Adjusting date and time …" & vbTab & Setup.Time & vbTab & "-> " & NumX(-AngleH, "0.000º")
    Ok = 4
    Init False, , , , False, False, False: Nav: DoEvents: If Not Note.Active Then GoTo Abort
OkTime:
    If Ok >= 6 Then GoTo Done
    
SetPark:
IsPark:
    Park = Val(Setup.EarthPark)
    AngleV = Atn(-Moon.Z / Moon.OrbitDistance) / Rad
    If Abs(AngleV) < 0.01 Then St = St Or 32: Ok = Ok + 1: GoTo OkPark
    Park = Park + AngleV / IIf(Abs(Lat) / Rad > 60, 4, 2)
    If Park < 0 Or Park >= OrbitTime Then Park = Park - Int(Park / OrbitTime) * OrbitTime
    Eq = 0: Setup.EarthPark = NumX(Park, "0.000m")
    MsgLog "Adjusting parking time …" & vbTab & Setup.EarthPark & vbTab & "-> " & NumX(AngleV, "0.000º")
    Ok = 4
    Init False, , , , False, False, False: Nav: DoEvents: If Not Note.Active Then GoTo Abort
OkPark:
    If Ok >= 6 Then GoTo Done
    
    GoTo SetTime
    
Abort:
    MsgLog vbCrLf & _
        "Trajectory calculation aborted:": GoTo Msg
Loss:
    MsgLog vbCrLf & _
        "Error adjusting trajectory.": GoTo Ext
Quit:
    MsgLog vbCrLf & _
        "Error adjusting trajectory:" & vbCrLf & _
        "  Try to use a propellant with higher Isp, reduce the" & vbCrLf & _
        "  dry mass, add more stages or adjust the stage mass" & vbCrLf & _
        "  distribution and the engine thrust for every stage." _
        : GoTo Ext
Done:
    MsgLog vbCrLf & _
        "Trajectory adjusted for a Moon mission:"
Msg:
    IsMoon = (Status = Landed Or Status = Crashed) And Target = ToMoon
    If IsMoon Then XYZ2LLA Moon, Lat2, Lon2, Alt2
    MsgLog vbCrLf & _
        "  Payload mass:" & Tab1 & ToMass(Stage(0).FullMass, FShort) & vbCrLf & _
        "  Total mass:" & Tab1 & ToMass(Stage(StageT).FullMass, FShort) & vbCrLf & _
        "  Propellant mass:" & Tab1 & ToMass(TotalFuelMass, FShort) & vbCrLf & _
        "  Burn time:" & Tab1 & ToMs(IfDbl(AccelTime >= 0, AccelTime, T)) & vbCrLf & _
        "  Parking time:" & Tab1 & ToMs(Val(Setup.EarthPark) * Min) & vbCrLf & _
        "  Parking orbit:" & Tab1 & ToAlt(TargetAltitude, FOne) & vbCrLf & _
        "  Mission time:" & Tab1 & ToDhms(T) & vbCrLf & _
        "  Launch site:" & Tab1 & ToLat(Lat, FQuad, Degrees) & "  " & _
            ToLon(Lon, FQuad, Degrees) & "  " & ToAlt(Alt, FMini) & vbCrLf & _
        "  Launch date:" & Tab1 & J2ksDateTime(Time) & vbCrLf & _
        "  Landing site:" & Tab1 & IIf(IsMoon, ToLat(Lat2, FQuad, Degrees) & "  " & _
            ToLon(Lon2, FQuad, Degrees) & "  " & ToAlt(Alt2, FMini), "-") & vbCrLf & _
        "  Landing date:" & Tab1 & IIf(IsMoon, J2ksDateTime(Time + T), "-") & vbCrLf & _
        "  Simulation time:" & Tab1 & ToMs(TimerEx - StartTime)
Ext:
    Options.EventsAll = 0: TouchDown = False
    SetSaveEvents , False: SetLogEvents , False: SetPauseEvents , , False
    SetPauseEvent MoonDistance, True, , True: RefreshPoints = False
    TimeSpeed = LastTimeSpeed: Message
    IsCalc = False: Calc.Calc.Caption = "Calculate"
    On Error GoTo 0
    
    End Sub

Function CheckErrors() As Boolean
    Dim C As Long, D As Long, E As Long, N As Long, F As Long, V As Long, _
        Q As Boolean, S As String, U As String, _
        G As Double, H As Double, M As Double, R As Double, Msg As String
    
    If Play <> Paused Or Note.Active Then Exit Function
    
    With Stages
    IsCalc = True
    
    'Missing total or payload mass
    R = ValMass(.Mass(PayloadMax)): If R <= 0 Then _
        Msg = "Missing payload mass": Q = True: GoTo Ext
    R = ValMass(.Mass(0)): If R <= 0 Then R = 1: .Mass(0) = ToMass(R, FOne)
    
    'Find active, empty, fixed and variable stages
    N = 0: F = 0: V = 0: E = 0
    For C = 1 To StageMax
        If .Stage(C) Then N = N + 1: E = E - (ValMass(.Mass(C), R) <= 0): _
            If InStr(.Mass(C), "%") Then V = V + 1 Else F = F + 1
        Next
    
    'No stages defined
    If N < 1 Then Msg = "No stages defined": Q = True: GoTo Ext
    
    'Fixed mass in all stages
    If F >= N And E <= 0 Then
        For C = 1 To StageMax
            If .Stage(C) Then .Mass(C) = ToPercent(ValMass(.Mass(C)) / R, FOne)
            Next
        If Msg = "" Then Msg = "Fixed mass in all stages corrected"
        End If
    
    'Missing or invalid mass in stage
    D = 0: U = "": For C = 1 To StageMax
        If .Stage(C) Then _
            If ValMass(.Mass(C), R) <= 0 Then _
                .Mass(C) = "10.0%": D = D + 1: If D <= 1 Then U = C Else U = U & "," & C
        Next
    If D > 0 Then _
        If Msg = "" Then Msg = "Missing mass in Stage" & IIf(D = 1, " ", "s ") & U & " corrected"
    
    'Total mass is not 100%
    D = 0: H = 0: M = 0: For C = 1 To StageMax
        If .Stage(C) And InStr(.Mass(C), "%") > 0 Then _
            G = ValRatio(.Mass(C)): M = M + G: If G > H Then H = G: D = C
        Next
    If D > 0 And M <> 1 Then
        S = NumD(LimitDbl(RoundEx(ValRatio(.Mass(D)) + 1 - M, 0.000000000000001) * 100))
        .Mass(D) = S & IIf(InStr(S, "."), "%", ".0%")
        If Msg = "" Then Msg = "Variable mass in stages adjusted to 100%"
        End If
    
    'Fixed thrust in variable mass stage
    D = 0: U = "": For C = 1 To StageMax
        If .Stage(C) And InStr(.Mass(C), "%") > 0 Then S = .Thrust(C): _
            If InStr(S, "%") <= 0 And InStr(S, "s") <= 0 Or ValForce(S) <= 0 Then _
                .Thrust(C) = "300%": D = D + 1: If D <= 1 Then U = C Else U = U & "," & C
        Next
    If D > 0 Then _
        If Msg = "" Then Msg = "Fixed thrust in Stage" & IIf(D = 1, " ", "s ") & U & " corrected"
Ext:
    IsCalc = False
    End With
    
    Nav Reset
    If (Q Or ShowErrors) And Msg <> "" Then
        'Log.Txt = "WARNING: errors in input parameters detected …" & vbCrLf & _
            Replace(Msg, vbCrLf, vbCrLf & "  •  ") & vbCrLf
        Message Msg, 3000, , True, True: DoEvents
        Exit Function
        End If
    If KMLPathPos > 0 And KMLPathPos <= KMLPathTop Then Nav NextStep
    Calc.Calc.Caption = "Stop"
    Message "Calculating trajectory, please wait …", , True
    CheckErrors = True
    
    End Function

'Sub SaveMagneticField( _
'        Optional Year As Double = 2012, Optional Alt As Double = 250000, _
'        Optional ByVal Lat As Long = 70, _
'        Optional ByVal StepLat As Long = 10, Optional ByVal StepLon As Long = 15)
'    Dim X2 As Long, Y2 As Long, A1 As Double, _
'        X1 As Double, Y1 As Double, Z1 As Double, D1 As WMMData
'    Dim S As String
'    Dim A As Long
'    StrAdd "// Earth magnetic field declination matrix" & vbCrLf & "// Epoch: " & Year & ", Altitude: " & Alt & "m, Latitude step: " & StepLat & ", Longitude step: " & StepLon & ", Latitude range: -" & Lat & ",+" & Lat & vbCrLf
'    StrAdd _
'        "char wmm[" & (Lat * 2 / StepLat) + 1 & "][" & (360 / StepLon) + 1 & "] =" & vbCrLf & _
'        "{" & vbCrLf
'    D1.Alt = Alt / 1000: D1.Year = Year
'    For Y2 = -Lat To Lat Step StepLat
'        StrAdd "  {"
'        For X2 = -180 To 180 Step StepLon
'            D1.Lat = Y2: D1.Lon = X2: WMMGetElements D1
'            A1 = Round(ATan2(D1.Y, D1.X) / Rad)
'            If A1 < -128 Then A1 = -128 Else If A1 > 127 Then A1 = 127
'            StrAdd Space(-(A1 >= 0) - (Abs(A1) < 10) - (Abs(A1) < 100)) & A1 & IIf(X2 < 180, ", ", "")
'            Next
'        StrAdd "}" & IIf(Y2 < Lat, ",", "") & vbCrLf
'        Next
'    StrAdd "};" & vbCrLf
'    'Clipboard.Clear: Clipboard.SetText StrAdd: Exit Sub
'    On Error Resume Next
'    Open LogsDir & "Declination " & Year & " Alt" & Alt & "m " & StepLat & " " & StepLon & ".c" For Output As 1
'    Print #1, StrAdd;
'    Close
'    On Error GoTo 0
'    End Sub

Sub SetGravityMode(Optional ByVal Mode As Long, Optional ByVal Update As Boolean = True)
    If GravityMode = Mode Then Exit Sub
    If Mode < 4 Then GravityMode = Mode Else PlanetPosAll: GravityMode = Mode: Stats
    If Update Then If Play = Paused Then If T = 0 Then Init Else Stats: Render: Info
    End Sub

Sub SetTerrainMode(Optional ByVal Mode As Long, Optional ByVal Update As Boolean = True)
    If TerrainMode = Mode Then Exit Sub
    If Mode = 2 Then
        If TerrainMode < 2 Then If Not LoadTerrain Then Options.UpdateSimulation: Exit Sub
    Else
        If TerrainMode = 2 Then DeleteTerrain: _
            If Tool And ToolTerrain Then Tool = Tool And Not ToolTerrain: Options.UpdateTools
        End If
    If TerrainMode > 0 Xor Mode > 0 Then If Cam1.hRC Then DeletePlanetLists
    TerrainMode = Mode
    If Update Then If Play = Paused Then If T = 0 Then Init Else Stats: Render: Info
    End Sub

Sub SetAtmMode(Optional ByVal Mode As Long, Optional ByVal Ask As Boolean, _
        Optional ByVal Update As Boolean = True)
    Static IsBusy As Boolean
    If IsBusy Or AtmMode = Mode Then Exit Sub
    AtmMode = Mode
    If Mode Then
        If AtmN = 0 Then
            If Not GRIBLoad Then _
                If Ask Then If MsgBox("No atmospheric model found. Download it from server?", _
                        vbQuestion Or vbOKCancel, AppName) = vbOK Then _
                    IsBusy = True: DownloadAtmosphere: IsBusy = False
            If AtmN = 0 Then AtmMode = 0: Options.UpdateSimulation
        End If
    Else
        If (Tool And ToolAtmosphere) = 0 Then GRIBDelete
        End If
    If Update Then If Play = Paused Then If T = 0 Then Init Else Stats: Render: Info
    End Sub

Sub SetAirspaceDetection(Optional ByVal Mode As Boolean, Optional ByVal Update As Boolean = True)
    If IsAirspaceDetection = Mode Then Exit Sub
    IsAirspaceDetection = Mode
    If Mode Then
        If AirspaceN = 0 Then If Not LoadAirspaces(Update) Then _
            IsAirspaceDetection = False: Options.UpdateSimulation: Exit Sub
    Else
        If AirspaceN > 0 And ((Tool And ToolAirways) = 0 Or (Layers And LayersAirspaces) = 0) Then _
            DeleteAirspaces Update
        End If
    If Update Then Plot , 1
    End Sub

Sub SaveLastPresetParams()
    Dim C As Long
    With LastPreset
    .EarthPark = Setup.EarthPark: .MoonPark = Setup.MoonPark
    .ThrustLimit = Setup.ThrustLimit: .LiftLimit = Setup.LiftLimit
    .Brake = Setup.Brake: .Parachute = Stages.Parachute
    For C = 1 To PayloadMax: With .Stage(C)
        .Control = Stages.Control(C): .ControlP = Stages.ControlP(C)
        .ControlI = Stages.ControlI(C): .ControlD = Stages.ControlD(C)
        If C < PayloadMax Then .Delay = Stages.Delay(C)
        .Power = Stages.Power(C): .Emissivity = Stages.Emissivity(C)
        End With: Next
    End With
    End Sub

Sub SaveLastPreset()
    Dim C As Long
    With LastPreset
    .Name = Setup.Preset: .TimeStep = Setup.TimeStep
    .Date = Setup.Date: .Time = Setup.Time: .Location = Setup.Location
    .Lat = Setup.Lat: .Lon = Setup.Lon: .Alt = Setup.Alt
    .Speed = Setup.Speed: .Angle = Setup.Angle: .Heading = Setup.Heading
    .StartAngle = Setup.StartAngle: .EndAngle = Setup.EndAngle
    .StartAlt = Setup.StartAlt: .EndAlt = Setup.EndAlt
    .ThrustLimit = Setup.ThrustLimit: .LiftLimit = Setup.LiftLimit
    .Brake = Setup.Brake: .EarthPark = Setup.EarthPark: .MoonPark = Setup.MoonPark
    .Parachute = Stages.Parachute: .Abort = Stages.Abort: .Jettison = Stages.Jettison
    .Mass = Stages.Mass(0)
    ReDim .Stage(1 To PayloadMax)
    For C = 1 To PayloadMax: With .Stage(C)
        If C < PayloadMax Then
            .Active = -Stages.Stage(C)
            .Dry = Stages.Dry(C): .Thrust = Stages.Thrust(C)
            .Delay = Stages.Delay(C): .Fuel = Stages.Fuel(C)
            End If
        .Mass = Stages.Mass(C): .Area = Stages.Area(C): .Length = Stages.Length(C)
        .Control = Stages.Control(C): .ControlP = Stages.ControlP(C)
        .ControlI = Stages.ControlI(C): .ControlD = Stages.ControlD(C)
        .DragCf = Stages.DragCf(C): .LiftToDrag = Stages.LiftToDrag(C)
        .InertiaX = Stages.InertiaX(C): .InertiaY = Stages.InertiaY(C): .InertiaZ = Stages.InertiaZ(C)
        .CenterX = Stages.CenterX(C): .CenterY = Stages.CenterY(C): .CenterZ = Stages.CenterZ(C)
        .Battery = Stages.Battery(C): .Imax = Stages.Imax(C): .Vmax = Stages.Vmax(C)
        .Power = Stages.Power(C): .Tmin = Stages.Tmin(C): .Tmax = Stages.Tmax(C)
        .Emissivity = Stages.Emissivity(C): .IspS = Stages.IspS(C): .IspV = Stages.IspV(C)
        .Model = Stages.Model(C)
        End With: Next
    ReDim .Targets(Setup.Targets.ListCount)
    For C = 1 To UBound(.Targets)
        .Targets(C).Name = Setup.Targets.List(C - 1)
        .Targets(C).Class = Setup.Targets.ItemData(C - 1)
        Next
    End With
    End Sub

Sub LoadLastPreset()
    Dim C As Long
    With LastPreset: SetPresetName .Name: Setup.TimeStep = .TimeStep
    Setup.Date = .Date: Setup.Time = .Time: Setup.Location = .Location
    Setup.Lat = .Lat: Setup.Lon = .Lon: Setup.Alt = .Alt
    Setup.Speed = .Speed: Setup.Angle = .Angle: Setup.Heading = .Heading
    Setup.StartAngle = .StartAngle: Setup.EndAngle = .EndAngle
    Setup.StartAlt = .StartAlt: Setup.EndAlt = .EndAlt
    Setup.ThrustLimit = .ThrustLimit: Setup.LiftLimit = .LiftLimit
    Setup.Brake = .Brake: Setup.EarthPark = .EarthPark: Setup.MoonPark = .MoonPark
    IsCalc = True
    Stages.Abort = .Abort: Stages.Parachute = .Parachute: Stages.Jettison = .Jettison
    Stages.Mass(0) = .Mass
    For C = 1 To PayloadMax: With .Stage(C)
        If C < PayloadMax Then
            Stages.Stage(C) = .Active
            Stages.Dry(C) = .Dry: Stages.Thrust(C) = .Thrust
            Stages.Delay(C) = .Delay: Stages.Fuel(C) = .Fuel
            End If
        Stages.Mass(C) = .Mass: Stages.Area(C) = .Area: Stages.Length(C) = .Length
        Stages.Control(C) = .Control: Stages.ControlP(C) = .ControlP
        Stages.ControlI(C) = .ControlI: Stages.ControlD(C) = .ControlD
        Stages.DragCf(C) = .DragCf: Stages.LiftToDrag(C) = .LiftToDrag
        Stages.InertiaX(C) = .InertiaX: Stages.InertiaY(C) = .InertiaY: Stages.InertiaZ(C) = .InertiaZ
        Stages.CenterX(C) = .CenterX: Stages.CenterY(C) = .CenterY: Stages.CenterZ(C) = .CenterZ
        Stages.Battery(C) = .Battery: Stages.Imax(C) = .Imax: Stages.Vmax(C) = .Vmax
        Stages.Power(C) = .Power: Stages.Tmin(C) = .Tmin: Stages.Tmax(C) = .Tmax
        Stages.Emissivity(C) = .Emissivity: Stages.IspS(C) = .IspS: Stages.IspV(C) = .IspV
        Stages.Model(C) = .Model
        End With: Next
    IsCalc = False
    If Setup.Targets.ListCount > 0 Then Setup.Targets.Clear
    For C = 1 To UBound(.Targets)
        Setup.Targets.AddItem .Targets(C).Name
        Setup.Targets.ItemData(Setup.Targets.NewIndex) = .Targets(C).Class
        Next
    End With
    End Sub

Sub SetPresetName(Optional Name As String)
    With Setup
    If Name <> "" Then .Preset = Name: .Preset.Refresh
    SetWindowText Main.hWnd, AppName & " - " & .Preset
    End With
    End Sub

Sub SelectPreset(Optional Name As String)
    SetPresetName Name
    With Setup: .TimeStep = "": .Date = "2007-06-26": .Time = "12:09:00": .Speed = ""
    .Angle = "90º": .ThrustLimit = "": .LiftLimit = "": .EarthPark = "": .MoonPark = ""
    End With
    With Stages: .Abort = "": .Parachute = "": .Jettison = False: End With
    With Setup
    If .Targets.ListCount > 0 Then .Targets.Clear
    IsModel = False
    Select Case Name
    Case "LEO"
        .Location = "Tenerife": .Lat = "28.2N": .Lon = "16.6W": .Alt = ""
        .StartAngle = "47.2º": Setup.EndAngle = "0º": .Heading = "90º"
        .StartAlt = "33km": .EndAlt = "250km": .Brake = "2km"
        SetStage 1, , "90.0%", "10%", "150%", , "APCP"
        SetStage 2, , "10.0%", "10%", "150%", "110s", "APCP"
        SetStage 3
        SetStage -2, False
        SetStage -1, , "20g", , , , , "7cm²", "15cm"
        SetStage , , "30.5kg"
        .Overview = _
            "This mission puts a femto-satellite in " & _
            "a low Earth orbit of 250 km altitude " & _
            "using a mini-launcher."
        If Options.ViewerMode = "Auto" Then Viewer "Low Earth orbit", False
    Case "GEO"
        .Location = "Amazon": .Lat = "0N": .Lon = "50W": .Alt = ""
        .StartAngle = "25º": .EndAngle = "0º": .Heading = "90º"
        .StartAlt = "33km": .EndAlt = "66km": .Brake = "2km"
        SetStage 1, , "90.0%", "8%", "150%", , "APCP"
        SetStage 2, , "10.0%", "8%", "150%", "3s", "N2O4/MMH"
        SetStage 3, , "200g", "59.88%", "150%", "314m", "N2O4/MMH"
        SetStage 4
        SetStage -2, False
        SetStage -1, , "20g", , , , , "7cm²", "15cm"
        SetStage , , "71.2kg"
        .Overview = _
            "This mission puts a femto-satellite in " & _
            "a geostationary Earth orbit of 35,786 km " & _
            "altitude using a mini-launcher."
        If Options.ViewerMode = "Auto" Then Viewer "Low Earth orbit", False
    Case Else
        .Location = "Tenerife": .Lat = "28.2N": .Lon = "16.6W": .Alt = ""
        .StartAngle = "29º": .EndAngle = "3º": .Heading = "87.2º"
        .StartAlt = "19km": .EndAlt = "56km": .Brake = "125km"
        SetStage 1, , "90.0%", "5%", "150%", , "APCP"
        SetStage 2, , "10.0%", "5%", "150%", "4s", "N2O4/MMH"
        SetStage 3
        SetStage -2, , "9.5kg", "20%", "300%", "10s", "N2O4/MMH"
        SetStage -1, , "500g", , , , , "15cm", "15cm"
        SetStage , , "1476kg"
        .Overview = _
            "This mission puts a ball-like pico-rover " & _
            "on the Moon using a direct trajectory " & _
            "whitout any parking orbits and using a " & _
            "direct landing trajectory. Landing " & _
            "point is not controlled."
        If Options.ViewerMode = "Auto" Then Viewer "Moon mission", False
        End Select
    End With
    ClearKMLPaths False: Editor.Pic.Cls: SetTerrainMode , False: UpdateStages
    If IsModel Then UpdateModels
    If VideoMode = 3 Then SetVideoMode
    PostMessage Main.hWnd, &H100&, Reset
    Options.Update
    End Sub

Sub ApplyChanges(Optional ByVal Start As Boolean)
    Play = Paused: If T = 0 Then Init: Exit Sub
    If LastSnap < 0 Then LastSnap = SnapP - 1 _
    Else If LastSnap <> SnapP - 1 Then LoadSnap LastSnap _
    Else LastSnap = -1
    InitParams
    If Start Then If Play = Paused Then PostMessage Main.hWnd, &H100&, Pause
    End Sub

Sub DemoExec(Optional ByVal N As Long)
    If N > 0 Then If Play = Paused Then _
            Message Main.mnDemo(N).Caption, 3000: DoEvents _
        Else Exit Sub
    If VideoMode = 3 Then SetVideoMode
    If SyncMode Then SyncTime = 1: SyncExec Reset
    Tool = Tool And Not ( _
        ToolAirways Or ToolAsteroids Or ToolComets Or ToolMoons Or ToolSatellites Or ToolStations)
    ShipSize = 1: ShowIcons = &H7&: SunShadow = 0.3: IsStationVisibility = False
    IsAsteroidProximity = False: IsSatelliteProximity = False
    IsAirspaceDetection = False: IsAreaDetection = False
    If AirspaceN Then DeleteAirspaces
    
    Select Case N
        
    Case 1 'Asteroids evolution
        'Select: None
        'Follow: Sun
        'Sync mode: 1 day
        '2012-03-01 12:00:00 -> 2012-12-31 12:00:00
        '...
        'Sync mode: 1 day -> 1 year
        '2012-12-31 12:00:00 -> 2091-12-31 12:00:00
        Viewer "Deep space"
        SetToolL AsteroidsLabels, False
        SetToolX ToolObjects, False
        SetTool ToolVectors, False
        SetTool ToolStage, False
        ShipSize = 0.001
        ZoomCam Cam1, 2500000000000#, , False
        RotateCam Cam1, 0, -55, 0, False
        SetTimeEx "2012-03-01", "12:00:00", True
        FollowMode Cam1, ToSun
        AsteroidDensity = 0: LoadAsteroids False
        SetTool ToolAsteroids, True
        SyncTime = Day: SyncExec Start, , True
        AsteroidDensity = 4: LoadAsteroids False
        Options.Update: If Not SyncTimer Then Render
        
    Case 2 'Jupiter moons fly by
        'Select: Europa
        'Follow: Object
        'Sync mode: 1 min
        '2012-03-12 22:00:00 -> 2012-03-13 11:30:00
        Viewer "Deep space"
        ZoomCam Cam1, 25000000, , False
        RotateCam Cam1, 43.5, -86, 0, False
        SetToolL MoonsLabels, True
        SunShadow = -0.1
        SetTimeEx "2012-03-12", "21:00:00", True
        SetTool ToolMoons, True
        SetObject TypeMoon, Europa.N
        FollowMode Cam1, ToObject: SetObject
        SyncTime = Min: SyncExec Start, , True
        Options.Update
        
    Case Else 'Restore default settings
        If Not Note.Active Then SetEvents False, 7
        Options.ViewerMode = "Auto": ViewerAuto
        FollowMode Cam1, ToSimul
        RotateCam Cam1, 0, 0, 0, False
        ZoomCam Cam1, 3000000, , False
        If AsteroidDensity > 0 Then AsteroidDensity = 0: _
            LoadAsteroids: LoadComets: LoadMoons
        Message "Default settings restored", 2000: DoEvents
        
        End Select
    
    End Sub

Sub SetStage(Optional ByVal N As Long, Optional ByVal Active As Boolean = True, _
        Optional Mass As String, Optional Dry As String = "10%", _
        Optional Thrust As String = "150%", Optional Delay As String, _
        Optional Fuel As String, Optional Area As String = "10%", Optional Length As String = "10%", _
        Optional Control As String, Optional ControlP As String = "2", _
        Optional ControlI As String = "5", Optional ControlD As String = "1", _
        Optional LiftToDrag As String, Optional DragCf As String = "Round Nose", _
        Optional InertiaX As String, Optional InertiaY As String, Optional InertiaZ As String, _
        Optional CenterX As String, Optional CenterY As String, Optional CenterZ As String, _
        Optional Battery As String = "1Ah", Optional Imax As String = "1A", Optional Vmax As String = "5V", _
        Optional Power As String = "1W", Optional Tmin As String = "250K", Optional Tmax As String = "350K", _
        Optional Emissivity As String = "0.25", Optional IspS As String, Optional IspV As String, _
        Optional Model As String)
    With Stages
    IsCalc = True
    If N < 0 Then N = PayloadMax + N + 1 ' Else If N > HoverMax Then GoTo Ext
    If Not Active Then .Stage(N) = False: GoTo Ext
    If Mass = "" Then .Num = N - 1: GoTo Ext
    .Mass(N) = Mass
    If N > 0 Then
        .Area(N) = Area: .Length(N) = Length
        .Control(N) = Control: .ControlP(N) = ControlP
        .ControlI(N) = ControlI: .ControlD(N) = ControlD
        .LiftToDrag(N) = LiftToDrag: .DragCf(N) = DragCf
        .InertiaX(N) = InertiaX: .InertiaY(N) = InertiaY: .InertiaZ(N) = InertiaZ
        .CenterX(N) = CenterX: .CenterY(N) = CenterY: .CenterZ(N) = CenterZ
        .Battery(N) = Battery: .Imax(N) = Imax: .Vmax(N) = Vmax: .Power(N) = Power
        .Tmin(N) = Tmin: .Tmax(N) = Tmax: .Emissivity(N) = Emissivity
        .IspS(N) = IspS: .IspV(N) = IspV
        IsModel = IsModel Or TextModified(.Model(N), Model)
        If N < PayloadMax Then .Stage(N) = True: _
            .Dry(N) = Dry: .Thrust(N) = Thrust: .Delay(N) = Delay: .Fuel(N) = Fuel
        End If
Ext:
    IsCalc = False
    End With
    End Sub

Sub ViewerAuto(Optional Name As String, Optional ByVal Update As Boolean = True, _
        Optional ByVal UseDefault As Boolean = True)
    If Name = "" Then Name = Setup.Preset
    If Left(Name, 4) = "SOF " Then Viewer "Sub-orbital flight", Update _
    Else If Left(Name, 4) = "LEO " Then Viewer "Low Earth orbit", Update _
    Else If Left(Name, 5) = "Moon " Then Viewer "Moon mission", Update _
    Else If Left(Name, 3) = "DS " Then Viewer "Deep space", Update _
    Else If UseDefault Then Viewer , Update
    End Sub

Sub Viewer(Optional Name As String, Optional ByVal Update As Boolean = True)
    Select Case Name
    Case "Auto", "None": Exit Sub
    Case "Sub-orbital flight"
        Tool = Tool Or ToolPath Or ToolSnap Or ToolTime Or ToolStage Or ToolInfo Or ToolLaunch Or _
            ToolRanges Or ToolVectors Or ToolLocations
        Tool = Tool And Not (ToolPlanets Or ToolMinorPlanets)
        ToolX = ToolX Or ToolObjects And Not (ToolReference Or ToolOrbits)
        ToolL = LabelDefault
        Plane = Equatorial: PathTime = 5: SnapTime = 5 * Min
        GravityMode = 3: IsSatelliteControl = True
    Case "Low Earth orbit"
        Tool = Tool Or ToolPath Or ToolSnap Or ToolStage Or ToolInfo Or ToolLaunch Or _
            ToolRanges Or ToolVectors
        Tool = Tool And Not (ToolTime Or ToolPlanets Or ToolMinorPlanets)
        ToolX = ToolX Or ToolObjects And Not (ToolReference Or ToolOrbits)
        ToolL = LabelDefault
        Plane = Equatorial: PathTime = 30: SnapTime = Hour
        GravityMode = 3: IsSatelliteControl = True
    Case "Moon mission"
        Tool = Tool Or ToolPath Or ToolSnap Or ToolTime Or ToolStage Or ToolInfo Or ToolLaunch Or _
            ToolRanges Or ToolVectors
        Tool = Tool And Not (ToolPlanets Or ToolMinorPlanets)
        ToolX = ToolX Or ToolReference Or ToolObjects Or ToolOrbits
        ToolL = LabelDefault
        Plane = Orbital: PathTime = 30: SnapTime = 6 * Hour
        GravityMode = 3: IsSatelliteControl = True
    Case "Deep space"
        Tool = Tool Or ToolPath Or ToolSnap Or ToolStage Or ToolInfo Or ToolLaunch Or _
            ToolRanges Or ToolVectors Or ToolPlanets
        Tool = Tool And Not ToolTime
        ToolX = ToolX Or ToolReference Or ToolObjects Or ToolOrbits
        If Tool And ToolMinorPlanets Then _
            ToolL = (LabelDefault And Not MinorPlanetsLabels) Or (ToolL And MinorPlanetsLabels) _
        Else ToolL = LabelDefault
        Plane = Ecliptic: PathTime = 10 * Min: SnapTime = Day
        GravityMode = 4: IsSatelliteControl = False
    Case Else
        Tool = ToolDefault: ToolX = ToolDefaultX: ToolL = LabelDefault
        Plane = Orbital: PathTime = 30: SnapTime = 6 * Hour: GravityMode = 3
        End Select
    If Update Then Options.Update: If Play = Paused Then Stats: Render: Info
    End Sub

Function IsInsideLandspace(R As Airspace, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double) As Boolean
    Dim C As Long
    If Abs(Lon - R.CLon) <= R.SLon And Abs(Lat - R.CLat) <= R.SLat Then
        For C = 0 To R.N - 2
            If R.Point(C).Lat < Lat Xor R.Point(C + 1).Lat < Lat Then _
                If Lon - R.Point(C).Lon > (Lat - R.Point(C).Lat) / _
                    (R.Point(C + 1).Lat - R.Point(C).Lat) * (R.Point(C + 1).Lon - R.Point(C).Lon) Then _
                        IsInsideLandspace = Not IsInsideLandspace
            Next
        End If
    End Function

Function IsInsideAirspace(R As Airspace, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double) As Boolean
    Dim C As Long
    If Alt <= R.FLU And Alt >= R.FLD And _
        Abs(Lon - R.CLon) <= R.SLon And Abs(Lat - R.CLat) <= R.SLat Then
        For C = 0 To R.N - 2
            If R.Point(C).Lat < Lat Xor R.Point(C + 1).Lat < Lat Then _
                If Lon - R.Point(C).Lon > (Lat - R.Point(C).Lat) / _
                    (R.Point(C + 1).Lat - R.Point(C).Lat) * (R.Point(C + 1).Lon - R.Point(C).Lon) Then _
                        IsInsideAirspace = Not IsInsideAirspace
            Next
        End If
    End Function

Function IsStationVisible(L As Location, _
        Optional Dist As Double, Optional Angle As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal Planet As Long) As Boolean
    If Planet Then If L.Planet <> Planet Then Exit Function
    If TerrainMode = 0 Then H1 = Earth.Radius + L.Alt: Z = H1 * Sin(L.Lat): _
        A1 = L.Lon + Earth.AngleW: H1 = H1 * Cos(L.Lat) _
    Else A1 = Sin(L.Lat): H1 = Earth.Radius / Sqr(1 - Earth.E2 * A1 * A1): _
        Z = (H1 * (1 - Earth.E2) + L.Alt) * A1: A1 = L.Lon + Earth.AngleW: _
        H1 = (H1 + L.Alt) * Cos(L.Lat)
    X2 = -H1 * Sin(A1): Y = H1 * Cos(A1)
    Y2 = Y * Earth.CosA - Z * Earth.SinA
    Z2 = Y * Earth.SinA + Z * Earth.CosA
    X = X2 + Earth.X: Y = Y2 + Earth.Y: Z = Z2 + Earth.Z
    Dist = Sqr(X * X + Y * Y + Z * Z): X1 = X / Dist: Y1 = Y / Dist: Z1 = Z / Dist
    G1 = Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2): X2 = X2 / G1: Y2 = Y2 / G1: Z2 = Z2 / G1
    X3 = X1 - X2: Y3 = Y1 - Y2: Z3 = Z1 - Z2 'Vector subtraction
    G1 = (X3 * X3 + Y3 * Y3 + Z3 * Z3) / 4 'Normalize to a squared vector
    If G1 <= 0 Then Angle = -A90 Else If G1 >= 1 Then Angle = A90 _
    Else Angle = Atn(Sqr(G1) / Sqr(1 - G1)) * 2 - A90 'ASin
    If Angle > StationAngle Then IsStationVisible = True
    End Function

Sub Station2XYZ(L As Location, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim A1 As Double, H1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double
    If TerrainMode = 0 Then H1 = Earth.Radius + L.Alt: Z = H1 * Sin(L.Lat): _
        A1 = L.Lon + Earth.AngleW: H1 = H1 * Cos(L.Lat) _
    Else A1 = Sin(L.Lat): H1 = Earth.Radius / Sqr(1 - Earth.E2 * A1 * A1): _
        Z = (H1 * (1 - Earth.E2) + L.Alt) * A1: A1 = L.Lon + Earth.AngleW: _
        H1 = (H1 + L.Alt) * Cos(L.Lat)
    X2 = -H1 * Sin(A1): Y = H1 * Cos(A1)
    Y2 = Y * Earth.CosA - Z * Earth.SinA
    Z2 = Y * Earth.SinA + Z * Earth.CosA
    X = X2 + Earth.X: Y = Y2 + Earth.Y: Z = Z2 + Earth.Z
    End Sub

Sub Location2XYZ(L As Location, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Select Case L.Planet
    Case ToMoon:    LLA2XYZ Moon, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToSun:     LLA2XYZ Sun, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToMercury: LLA2XYZ Mercury, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToVenus:   LLA2XYZ Venus, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToMars:    LLA2XYZ Mars, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToJupiter: LLA2XYZ Jupiter, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToSaturn:  LLA2XYZ Saturn, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToUranus:  LLA2XYZ Uranus, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToNeptune: LLA2XYZ Neptune, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case ToPluto:   LLA2XYZ Pluto, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    Case Else:      LLA2XYZ Earth, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
        End Select
    End Sub

Sub LocationEarth2XYZ(L As Location, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    LLA2XYZ Earth, L.Lat, L.Lon, L.Alt, X, Y, Z, , , True, True
    End Sub

Sub AsteroidOrbit2XYZ(S As Asteroid, Optional T As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, Optional R As Double)
    Dim A As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        SinA As Double, CosA As Double, _
        SinI As Double, CosI As Double, _
        SinO As Double, CosO As Double
    'Position in the orbit
    X = Cos(T) * S.S - S.E * S.S: Y = Sin(T) * Sqr((1 - S.E * S.E) * S.S * S.S)
    R = Sqr(X * X + Y * Y)
    If X = 0 Then
        If Y > 0 Then A = A90 Else If Y < 0 Then A = -A90 Else A = 0
    Else
        A = Atn(Y / X): If X < 0 Then If Y >= 0 Then A = A + A180 Else A = A - A180
        End If
    A = A + S.P
    SinA = Sin(A): CosA = Cos(A): SinI = Sin(S.I): CosI = Cos(S.I): SinO = Sin(S.O): CosO = Cos(S.O)
    If S.Class >= 0 Then
        'Rectangular coordinates relative to Sun
        X = Sun.X + R * (CosO * CosA - SinO * SinA * CosI)
        Y = Sun.Y + R * (SinO * CosA + CosO * SinA * CosI)
        Z = Sun.Z + R * SinA * SinI
    Else
        'Rectangular coordinates relative to the planet
        X = R * (CosO * CosA - SinO * SinA * CosI)
        Y = R * (SinO * CosA + CosO * SinA * CosI)
        Z = R * SinA * SinI
        Select Case -S.Class
        Case ToMercury: Planet2XYZ Mercury, X, Y, Z
        Case ToVenus:   Planet2XYZ Venus, X, Y, Z
        Case ToEarth:   Planet2XYZ Earth, X, Y, Z
        Case ToMars:    Planet2XYZ Mars, X, Y, Z
        Case ToJupiter: Planet2XYZ Jupiter, X, Y, Z
        Case ToSaturn:  Planet2XYZ Saturn, X, Y, Z
        Case ToUranus:  Planet2XYZ Uranus, X, Y, Z
        Case ToNeptune: Planet2XYZ Neptune, X, Y, Z
        Case ToPluto:   Planet2XYZ Pluto, X, Y, Z
        Case Else:      Planet2XYZ Sun, X, Y, Z
            End Select
        End If
    End Sub

'Function Kepler(Ecc As Double, Mean_Anom As Double, Optional Eps As Double = 0.00000001) As Double
'    Dim Err As Double, Thresh As Double, Trial As Double, AbsEcc As Double, _
'        Is_Negative As Long, N_Iter As Long
'
'    If Mean_Anom = 0 Then Exit Function
'
'    If Ecc < 0.3 Then 'low-eccentricity formula from Meeus,  p. 195
'        Kepler = ATan2(Sin(Mean_Anom), Cos(Mean_Anom) - Ecc)
'        ' one correction step,  and we're done
'        Err = Kepler - Ecc * Sin(Kepler) - Mean_Anom
'        Kepler = Kepler - Err / (1 - Ecc * Cos(Kepler))
'        Exit Function
'        End If
'
'    If Mean_Anom < 0 Then
'        Mean_Anom = -Mean_Anom
'        Is_Negative = 1
'        End If
'
'    Kepler = Mean_Anom
'    AbsEcc = Abs(1 - Ecc)
'    Thresh = Eps * AbsEcc
'
'    If Ecc > 0.8 And Mean_Anom < Pi / 3 Or Ecc > 1 Then 'up to 60 degrees
'        Trial = Mean_Anom / AbsEcc
'
'        If Trial * Trial > 6 * AbsEcc Then 'cubic term is dominant
'            If Mean_Anom < Pi Then
'                Trial = (6 * Mean_Anom) ^ (1 / 3)
'            Else 'hyperbolic w/ 5th & higher-order terms predominant
'                Trial = Mean_Anom / Ecc
'                Trial = VBA.Log(Trial + Sqr(Trial * Trial + 1))
'            End If
'        End If
'        Kepler = Trial
'        End If
'
'    If Ecc < 1 Then
'        Err = Kepler - Ecc * Sin(Kepler) - Mean_Anom
'        Do While Abs(Err) > Thresh
'            N_Iter = N_Iter + 1
'            Kepler = Kepler - Err / (1 - Ecc * Cos(Kepler))
'            Err = Kepler - Ecc * Sin(Kepler) - Mean_Anom
'            Loop
'    Else
'        Err = Ecc * (VBA.Exp(Kepler) - VBA.Exp(-Kepler)) / 2 - Kepler - Mean_Anom
'        Do While Abs(Err) > Thresh
'            N_Iter = N_Iter + 1
'            Kepler = Kepler - Err / (Ecc * ((VBA.Exp(Kepler) + VBA.Exp(-Kepler)) / 2) - 1)
'            Err = Ecc * SinH(Kepler) - Kepler - Mean_Anom
'            Loop
'        End If
'
'    If Is_Negative Then Kepler = -Kepler
'
'    End Function

Sub Asteroid2XYZ(S As Asteroid, Optional T As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, Optional R As Double) ', _
        Optional ByVal Mode As Boolean = True)
    Dim A As Double, _
        SinA As Double, CosA As Double, _
        SinI As Double, CosI As Double, _
        SinO As Double, CosO As Double, _
        M As Double, D As Double, D1 As Double, D2 As Double
    'Position in the orbit
    A = S.L + S.M * T
    
    'If Mode Then
        
        'MoonPosition.bas
        A = A + S.E * Sin(A) * (1 + S.E * Cos(A))
        X = S.S * (Cos(A) - S.E)
        Y = S.S * (Sqr(1 - S.E * S.E) * Sin(A))
        R = Sqr(X * X + Y * Y)
        If X = 0 Then
            If Y > 0 Then A = A90 Else If Y < 0 Then A = -A90 Else A = 0
        Else
            A = Atn(Y / X): If X < 0 Then If Y >= 0 Then A = A + A180 Else A = A - A180
            End If
    
        ''M=mean anomaly N=eccentric anomaly  E=orbital eccentricity
        'M = A: A = M + S.E * Sin(M)
        'Do
        '    D2 = M - (A - S.E * Sin(S.E))
        '    D1 = D2 / (1 - S.E * Cos(S.E)): If D1 < 0.00000000001 And D1 > -0.00000000001 Then Exit Do
        '    A = A + D1
        '    Loop
        'A = ACos((Cos(A) - S.E) / (1 - S.E * Cos(A)))
        'R = S.S * (1 - S.E * S.E) / (1 + S.E * Cos(A))
        
        'A = Kepler(Ecc, A)
        'A = ACos((Cos(A) - S.E) / (1 - S.E * Cos(A)))
        'R = S.S * (1 - S.E * S.E) / (1 + S.E * Cos(A))
    
    'Else
        
        ''PlanetsPosition.bas
        'M = A: D = 1: D1 = D
        'Do: D2 = D1: D = A - S.E * Sin(A) - M: A = A - D / (1 - S.E * Cos(A))
        '    If D < 0 Then D1 = -D Else D1 = D
        '    Loop While D1 >= 0.00000000001 And D1 < D2
        'A = Atn(Sqr((1 + S.E) / (1 - S.E)) * Tan(A / 2)) * 2
        ''A = ACos((Cos(A) - S.E) / (1 - S.E * Cos(A)))
        'R = S.S * (1 - S.E * S.E) / (1 + S.E * Cos(A))
        
        'M = A: D = 1: D1 = D
        'Do: D2 = D1: D = A - S.E * Sin(A) - M: A = A - D / (1 - S.E * Cos(A))
        '    If D < 0 Then D1 = -D Else D1 = D
        '    Loop While D1 >= 0.00000000001 And D1 < D2
        'X = S.S * (Cos(A) - S.E)
        'Y = S.S * (Sqr(1 - S.E * S.E) * Sin(A))
        'R = Sqr(X * X + Y * Y)
        'If X = 0 Then
        '    If Y > 0 Then A = A90 Else If Y < 0 Then A = -A90 Else A = 0
        'Else
        '    A = Atn(Y / X): If X < 0 Then If Y >= 0 Then A = A + A180 Else A = A - A180
        '    End If
        
        'End If
    
    A = A + S.P
    If S.Class >= 0 Then
        'Rectangular coordinates relative to Sun
        SinA = Sin(A): CosA = Cos(A): SinI = Sin(S.I): CosI = Cos(S.I): SinO = Sin(S.O): CosO = Cos(S.O)
        X = Sun.X + R * (CosO * CosA - SinO * SinA * CosI)
        Y = Sun.Y + R * (SinO * CosA + CosO * SinA * CosI)
        Z = Sun.Z + R * SinA * SinI
    Else
        'Rectangular coordinates relative to the planet
        SinA = Sin(A): CosA = Cos(A): SinI = Sin(S.I): CosI = Cos(S.I): SinO = Sin(S.O): CosO = Cos(S.O)
        X = R * (CosO * CosA - SinO * SinA * CosI)
        Y = R * (SinO * CosA + CosO * SinA * CosI)
        Z = R * SinA * SinI
        Select Case -S.Class
        Case ToMercury: Planet2XYZ Mercury, X, Y, Z
        Case ToVenus:   Planet2XYZ Venus, X, Y, Z
        Case ToEarth:   Planet2XYZ Earth, X, Y, Z
        Case ToMars:    Planet2XYZ Mars, X, Y, Z
        Case ToJupiter: Planet2XYZ Jupiter, X, Y, Z
        Case ToSaturn:  Planet2XYZ Saturn, X, Y, Z
        Case ToUranus:  Planet2XYZ Uranus, X, Y, Z
        Case ToNeptune: Planet2XYZ Neptune, X, Y, Z
        Case ToPluto:   Planet2XYZ Pluto, X, Y, Z
        Case Else:      Planet2XYZ Sun, X, Y, Z
            End Select
        End If
    End Sub

Sub Planet2XYZ(Pl As Planet, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional X2 As Double, Optional Y2 As Double, Optional Z2 As Double)
    If Pl.AngleU <> 0 Then X2 = X: Y2 = Y * Pl.CosA - Z * Pl.SinA: Z2 = Z * Pl.CosA + Y * Pl.SinA _
    Else If Pl.AngleV <> 0 Then X2 = X * Pl.CosA + Z * Pl.SinA: Y2 = Y: Z2 = Z * Pl.CosA - X * Pl.SinA
    X = Pl.X + X2: Y = Pl.Y + Y2: Z = Pl.Z + Z2
    End Sub

Sub TLE2XYZ(Pl As Planet, S As TLE, Optional T As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional R As Double)
    Dim A As Double, L As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        M As Double, N As Double, D As Double, D2 As Double, _
        SinA As Double, CosA As Double, _
        SinI As Double, CosI As Double, _
        SinO As Double, CosO As Double ', _
        SinW As Double, CosW As Double
    'Position in the orbit
    L = S.L + S.M * (T - S.T)
    A = L + S.E * Sin(L) * (1 + S.E * Cos(L))
    X = S.S * (Cos(A) - S.E)
    Y = S.S * (Sqr(1 - S.E * S.E) * Sin(A))
    R = Sqr(X * X + Y * Y)
    If X = 0 Then
        If Y > 0 Then A = A90 Else If Y < 0 Then A = -A90 Else A = 0
    Else
        A = Atn(Y / X): If X < 0 Then If Y >= 0 Then A = A + A180 Else A = A - A180
        End If
    A = A + S.P
    'Rectangular coordinates
    SinA = Sin(A): CosA = Cos(A): SinI = Sin(S.I): CosI = Cos(S.I): SinO = Sin(S.O): CosO = Cos(S.O)
    X = R * (CosO * CosA - SinO * SinA * CosI)
    Y = R * (SinO * CosA + CosO * SinA * CosI)
    Z = R * SinA * SinI
    'Relative to the planet
    If Pl.AngleU <> 0 Then X2 = X: Y2 = Y * Pl.CosA - Z * Pl.SinA: Z2 = Z * Pl.CosA + Y * Pl.SinA _
    Else If Pl.AngleV <> 0 Then X2 = X * Pl.CosA + Z * Pl.SinA: Y2 = Y: Z2 = Z * Pl.CosA - X * Pl.SinA
    X = Pl.X + X2: Y = Pl.Y + Y2: Z = Pl.Z + Z2
    End Sub

Function Vector2TLE(Pl As Planet, E As TLE, Optional Nu As Double, _
        Optional ByVal RelativeToPlanet As Boolean) As String
    Dim R As Vector3, V As Vector3, En As Double, Se As Double
    R.X = -Pl.X: R.Y = -Pl.Y: R.Z = -Pl.Z: V.X = Pl.SpeedX: V.Y = Pl.SpeedY: V.Z = Pl.SpeedZ
    If RelativeToPlanet Then
        If Pl.AngleU <> 0 Then Rotate -Pl.AngleU, R.Y, R.Z: Rotate -Pl.AngleU, V.Y, V.Z
        If Pl.AngleV <> 0 Then Rotate -Pl.AngleV, R.X, R.Z: Rotate -Pl.AngleV, V.X, V.Z
        End If
    Vector2Orbit R, V, Pl.GravityMass, E.E, E.S, E.I, E.O, E.P, Nu, E.L, E.M, En, Se
    E.T = Time + T: E.M = E.M / Day * A360
    End Function

Function Vector2TextTLE(Pl As Planet) As String
    Dim R As Vector3, V As Vector3, Mu As Double, _
        Ecc As Double, A As Double, I As Double, _
        Lan As Double, Ap As Double, Nu As Double, _
        M As Double, Mm As Double, E As Double, _
        Se As Double, Bs As Double, T1 As Double, _
        Y1 As Long, D1 As Double, C As Long, S As String
    'ISS (ZARYA)             '
    '1 25544U 98067A   12001.88777778  .00014942  00000-0  19331-3 0  7384
    '2 25544  51.6429 241.3855 0024064 231.9065 306.2035 15.58825088751922
    S = Setup.Preset: C = InStr(S, "SN-")
    If C > 0 And C = Len(S) - 7 Then C = Val(Mid(S, C + 3, 5)) Else C = 0
    S = Format(C, "00000")
    T1 = Time + T: J2ksToDate T1, Y1: D1 = (T1 - DateToJ2ks(Y1)) / Day + 1: Y1 = Y1 Mod 100
    Bs = 0.5 * DragCf * Area / Mass * AirD: Mu = Pl.GravityMass
    R.X = -Pl.X: R.Y = -Pl.Y: R.Z = -Pl.Z
    V.X = Pl.SpeedX: V.Y = Pl.SpeedY: V.Z = Pl.SpeedZ
    If Pl.AngleU <> 0 Then Rotate -Pl.AngleU, R.Y, R.Z: Rotate -Pl.AngleU, V.Y, V.Z
    If Pl.AngleV <> 0 Then Rotate -Pl.AngleV, R.X, R.Z: Rotate -Pl.AngleV, V.X, V.Z
    Vector2Orbit R, V, Mu, Ecc, A, I, Lan, Ap, Nu, M, Mm, E, Se
    Vector2TextTLE = _
        "SATELLITE               " & vbCrLf & _
        "1 " & S & "U 00000A   " & Format(Y1, "00") & NumX(D1, "000.00000000") & _
            " -.00000000  00000-0 " & TLEExp(Bs) & " 0 00000" & vbCrLf & _
        "2 " & S & TLEAngle(I) & TLEAngle(Lan) & TLEDec(Ecc) & TLEAngle(Ap) & TLEAngle(M) & _
            TLERev(Mm) & "000000" & vbCrLf
    C = 1 + 24 + 2
    TLECheck Vector2TextTLE, C, 69: C = C + 2
    TLECheck Vector2TextTLE, C, 69: C = C + 2
    'Vector2TextTLE = Vector2TextTLE & ".. " & ToAngle(I, FLong & "º") & " " & ToAngle(Lan, FLong & "º") & " " & _
        NumX(Ecc) & " " & ToAngle(Ap, FLong & "º") & " " & ToAngle(M, FLong & "º") & " " & NumX(Mm)
    End Function

Function TLEAngle(ByVal A As Double) As String
    A = A / Rad: If A < 0 Then A = 0 Else If A > 360 Then A = 360
    TLEAngle = Replace(Format(A, " 000.0000"), ",", ".")
    End Function

Function TLEDec(ByVal A As Double) As String
    A = Int(A * 10000000): If A < 0 Then A = 0 Else If A > 9999999 Then A = 9999999
    TLEDec = Replace(Format(A, " 0000000"), ",", ".")
    End Function

Function TLEExp(ByVal A As Double) As String
    '-12345-6 .. -0.12345E-6
    Dim E As Long, S As String
    If A < 0 Then A = -A: S = "-" Else S = " "
    If A < 0.0000000001 Then TLEExp = S & "00000-0": Exit Function
    If A > 999990000 Then TLEExp = S & "99999+9": Exit Function
    TLEExp = Format(A, ".00000E+0")
    TLEExp = S & Mid(TLEExp, 2, 5) & Mid(TLEExp, 5 + 3, 2)
    End Function

Function TLERev(ByVal A As Double) As String
    If A < 0 Then A = 0 Else If A > 99.99999999 Then A = 99.99999999
    TLERev = Replace(Format(A, " 00.00000000"), ",", ".")
    End Function

Sub TLECheck(T As String, Optional N As Long = 1, Optional ByVal L As Long)
    Dim D As Long, C As String
    For N = N To N + L - 2: C = Mid(T, N, 1)
        If C >= "1" And C <= "9" Then D = D + Val(C) Else If C = "-" Then D = D + 1
        Next
    Mid(T, N, 1) = CStr(D Mod 10): N = N + 1
    End Sub

Sub Vector2Orbit( _
        R As Vector3, V As Vector3, Optional Mu As Double = 398600000000000#, _
        Optional Ecc As Double, Optional A As Double, Optional I As Double, _
        Optional Lan As Double, Optional Ap As Double, Optional Nu As Double, _
        Optional M As Double, Optional Mm As Double, Optional E As Double, _
        Optional Se As Double)

    'This function calculates orbital elements from position R and velocity V cartesian vectors
    
    'R       Current position cartesian vector
    'V       Current velocity cartesian vector
    'Mu      Standard gravitational parameter µ=G*M [m³/s²]
    
    'Ecc     *Eccentricity
    'A       Semimajor axis [m]
    'I       *Inclination [rad]
    'Lan     *Longitude of ascending node [rad]
    'Ap      *Argument of perigee [rad]
    'Nu      True anomaly [rad]
    'M       *Mean anomaly [rad]
    'Mm      *Mean motion [rpd]
    'E       Eccentric, parabolic or hyperbolic anomaly [rad]
    'Se      Specific orbital energy

    Dim Hb As Vector3, Nb As Vector3, Eb As Vector3, _
        Mr As Double, Mv As Double, Mn As Double, Mh As Double, _
        N As Double, C As Double, Hk As Double, Rdv As Double, _
        Sne As Double, Cse As Double
    Ecc = 0: A = 0: I = 0: Lan = 0: Ap = 0: Nu = 0: M = 0: Mm = 0: E = 0: Se = 0
    Mr = Magnitude(R): Mv = Magnitude(V)
    CrossProduct R, V, Hb: Mh = Magnitude(Hb)
    If Mh <= 0 Then Exit Sub 'H, N and E vectors
    Nb.X = -Hb.Y: Nb.Y = Hb.X: Nb.Z = 0
    Mn = Magnitude(Nb): C = Mv * Mv - Mu / Mr: Rdv = DotProduct(R, V)
    Eb.X = (C * R.X - Rdv * V.X) / Mu
    Eb.Y = (C * R.Y - Rdv * V.Y) / Mu
    Eb.Z = (C * R.Z - Rdv * V.Z) / Mu
    Ecc = Magnitude(Eb) 'Eccentricity
    Se = (Mv * Mv / 2) - (Mu / Mr) 'Specific orbital energy
    If Se = 0 Then A = Infinite: Mm = 0 _
    Else A = -Mu / (Se * 2): If A <= 0 Then Mm = 0 _
    Else Mm = Sqr(Mu / A / A / A) / 2 / Pi * 86400 'Semimajor axis and Mean motion
    Hk = Hb.Z / Mh: I = ACos(Hk) 'Inclination
    If Mn > 0 Then N = Nb.X / Mn: Lan = ACos(N): _
        If Nb.Y < 0 Then Lan = A360 - Lan 'Longitude of ascending node
    If Ecc > 0 And I > 0 Then Ap = Angle2(Nb, Eb): _
        If Eb.Z < 0 Then Ap = A360 - Ap 'Argument of perigee
    If Ecc > 0 Then Nu = Angle2(Eb, R): _
        If Rdv < 0 Then Nu = A360 - Nu 'True anomaly at epoch
    If Ecc <= 0 Then 'Mean anomaly - circular
        E = Nu: M = Nu
    ElseIf Ecc < 1 Then 'Mean anomaly - elliptical
        Sne = (Sqr(1 - Ecc * Ecc) * Sin(Nu)) / (1 + Ecc * Cos(Nu))
        Cse = (Ecc + Cos(Nu)) / (1 + Ecc * Cos(Nu))
        E = ATan2(Sne, Cse): M = E - Ecc * Sin(E)
    ElseIf Ecc = 1 Then 'Mean anomaly - parabolic
        E = Tan(Nu / 2): M = E + (E * E * E) / 3
    ElseIf Abs(Nu) < Pi - ACos(1 / Ecc) Then 'Mean anomaly - hyperbolic
        Sne = (Sqr(Ecc * Ecc - 1) * Sin(Nu)) / (1 + Ecc * Cos(Nu))
        E = ASinH(Sne): M = Ecc * SinH(E) - E
        End If
    If Ecc < 1 Then
        If M < -A360 Or M >= A360 Then M = M - Int(M / A360) * A360
        If M < 0 Then M = M + A360
        If E < -A360 Or E >= A360 Then E = E - Int(E / A360) * A360
        If E < 0 Then E = E + A360
        End If
    End Sub

Function Magnitude(A As Vector3) As Double
    Magnitude = Sqr(A.X * A.X + A.Y * A.Y + A.Z * A.Z)
    End Function

Sub CrossProduct(A As Vector3, B As Vector3, V As Vector3)
    V.X = A.Y * B.Z - A.Z * B.Y: V.Y = A.Z * B.X - A.X * B.Z: V.Z = A.X * B.Y - A.Y * B.X
    End Sub

Function DotProduct(A As Vector3, B As Vector3) As Double
    DotProduct = (A.X * B.X + A.Y * B.Y + A.Z * B.Z)
    End Function

Private Function Angle2(A As Vector3, B As Vector3) As Double
    Dim M1 As Double, M2 As Double
    M1 = Magnitude(A): M2 = Magnitude(B): If M1 = 0 Or M2 = 0 Then Exit Function
    Angle2 = ACos(DotProduct(A, B) / M1 / M2)
    End Function

Private Function Sgn2(X As Double) As Double
    If X > 0 Then Sgn2 = 1 Else If X < 0 Then Sgn2 = -1
    End Function

Function ATan2(Y As Double, X As Double) As Double
    'X=1 Y=0 A=0º .. X=0 Y=1 A=90º .. X=-1 Y=0 A=-180º .. X=0 Y=-1 A=-90º .. A+=CCW
    If X = 0 Then
        If Y > 0 Then ATan2 = A90 Else If Y < 0 Then ATan2 = -A90
    Else
        ATan2 = Atn(Y / X)
        If X < 0 Then If Y > 0 Then ATan2 = ATan2 + A180 Else ATan2 = ATan2 - A180
        End If
    End Function

Sub LLA2AED(Pl As Planet, _
        Optional Azimuth As Double, Optional Elevation As Double, Optional Distance As Double, _
        Optional Lat1 As Double, Optional Lon1 As Double, Optional Alt1 As Double, _
        Optional Lat2 As Double, Optional Lon2 As Double, Optional Alt2 As Double)
    'Calculates heading and elevation angles and distance from Lat1-Lon1-Alt1 to Lat2-Lon2-Alt2
    Dim X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X3 As Double, Y3 As Double, Z3 As Double
    LLA2XYZ Pl, Lat1, Lon1, Alt1, X1, Y1, Z1, , , , True
    LLA2XYZ Pl, Lat2, Lon2, Alt2, X2, Y2, Z2, , , , True
    X3 = X2 - X1: Y3 = Y2 - Y1: Z3 = Z2 - Z1: Distance = Sqr(X3 * X3 + Y3 * Y3 + Z3 * Z3)
    Elevation = A90 - Angle3D(X1, Y1, Z1, X3, Y3, Z3, True, True)
    Azimuth = Sys.Azimuth(Lat1, Lon1, Lat2, Lon2)
    End Sub

Function Azimuth( _
        Lat1 As Double, Lon1 As Double, Lat2 As Double, Lon2 As Double) As Double
    'Calculates the heading angle to point from Lat1-Lon1 to Lat2-Lon2
    Dim L2 As Double, L1 As Double, DL As Double, T1 As Double, _
        B1 As Double, R As Double, T As Double
    On Error Resume Next
    L1 = A720 - Lon1: L2 = A720 - Lon2: DL = L2 - L1
    If Abs(DL) > A180 Then
        If Lon2 < 0 And Lon1 > 0 Then Lon2 = Lon2 + A360
        If Lon1 < 0 And Lon2 > 0 Then Lon2 = Lon2 - A360
        L1 = A720 - Lon1: L2 = A720 - Lon2: DL = L2 - L1
        End If
    R = ACos(Cos(DL) * Cos(Lat2) * Cos(Lat1) + Sin(Lat1) * Sin(Lat2))
    T1 = Sin(Lat2) - Cos(R) * Sin(Lat1)
    B1 = Sin(R) * Cos(Lat1): If B1 = 0 Then T = 0 Else T = T1 / B1
    If T > 1 Then T = 1 Else If T < -1 Then T = -1
    Azimuth = ACos(T): If L1 < L2 Then Azimuth = A360 - Azimuth
    On Error GoTo 0
    End Function

'Sub XYZ2XYZ(Pl As Planet, _
'        Optional X As Double, Optional Y As Double, Optional Z As Double)
'    Dim X2 As Double, Y2 As Double, Z2 As Double, SinW As Double, CosW As Double
'    X = X - Pl.X: Y = Y - Pl.Y: Z = Z - Pl.Z
'    If Pl.AngleU <> 0 Then X2 = X: Y2 = Y * Pl.CosA + Z * Pl.SinA: Z2 = Z * Pl.CosA - Y * Pl.SinA _
'    Else If Pl.AngleV <> 0 Then X2 = X * Pl.CosA - Z * Pl.SinA: Y2 = Y: Z2 = Z * Pl.CosA + X * Pl.SinA
'    SinW = Sin(Pl.AngleW): CosW = Cos(Pl.AngleW): X = X2 * CosW + Y2 * SinW: Y = Y2 * CosW - X2 * SinW: Z = Z2
'    End Sub

Sub XYZ2LLA(Pl As Planet, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional ByVal X As Double, Optional ByVal Y As Double, Optional ByVal Z As Double, _
        Optional ByVal Relative As Boolean = True)
    Dim X2 As Double, Y2 As Double, Z2 As Double, SinW As Double, CosW As Double, _
        R As Double, R2 As Double, F As Double, G As Double, _
        C As Double, S As Double, P As Double, Q As Double, RR As Double, _
        V As Double, SS As Double
    If Relative Then
        X = X - Pl.X: Y = Y - Pl.Y: Z = Z - Pl.Z
        If Pl.AngleU <> 0 Then X2 = X: Y2 = Y * Pl.CosA + Z * Pl.SinA: Z2 = Z * Pl.CosA - Y * Pl.SinA _
        Else If Pl.AngleV <> 0 Then X2 = X * Pl.CosA - Z * Pl.SinA: Y2 = Y: Z2 = Z * Pl.CosA + X * Pl.SinA
        SinW = Sin(Pl.AngleW): CosW = Cos(Pl.AngleW)
        X = X2 * CosW + Y2 * SinW: Y = Y2 * CosW - X2 * SinW: Z = Z2
        End If
    R = Sqr(X * X + Y * Y)
    If TerrainMode = 0 Then
        'Spherical model
        If R <> 0 Then Alt = Sqr(R * R + Z * Z) - Pl.Radius: Lat = Atn(Z / R) _
        Else Lon = 0: If Z < 0 Then Alt = -Z - Pl.Radius: Lat = -A90 Else Lat = A90: Alt = Z - Pl.Radius
    Else
        'Ellipsoidal model
        If R < 0.1 Then
            If R <> 0 Then Alt = Sqr(R * R + Z * Z) - Pl.Radius2: Lat = Atn(Z / R) _
            Else Lon = 0: If Z < 0 Then Alt = -Z - Pl.Radius2: Lat = -A90 Else Lat = A90: Alt = Z - Pl.Radius2
        Else
            R2 = R * R: Z2 = Z * Z
            F = Pl.RB2 * Z2 * 54
            G = R2 + Pl.E21 * Z2 - Pl.EAB2
            C = F * Pl.E4 * R2 / G / G / G
            S = (Sqr(C * C + C + C) + C + 1) ^ 0.333333333333333
            SS = ((1 / S) + S + 1) * G
            P = F / SS / SS / 3
            Q = Sqr(P * Pl.E4 * 2 + 1)
            RR = R - Pl.E2 * (Sqr((1 / Q + 1) * Pl.RA2 / 2 - P * Z2 * Pl.E21 / (Q + 1) / Q - P * R2 / 2) - _
                P * R * Pl.E2 / (Q + 1))
            RR = RR * RR
            V = Pl.RB2 / Pl.Radius / Sqr(RR + Z2 * Pl.E21)
            Alt = (1 - V) * Sqr(RR + Z2)
            Lat = Atn((Pl.EE2 * V * Z + Z) / R)
            End If
        End If
    If Y = 0 Then
        If X < 0 Then Lon = A90 Else Lon = -A90
    Else
        Lon = Atn(-X / Y)
        If Y < 0 Then If X < 0 Then Lon = Lon + A180 Else Lon = Lon - A180
        End If
    End Sub

Sub LLA2XYZ(Pl As Planet, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, Optional H As Double, _
        Optional ByVal Update As Boolean, Optional ByVal Relative As Boolean, _
        Optional ByVal Move As Boolean)
    Dim X2 As Double, Y2 As Double, Z2 As Double, _
        SLat As Double, SinW As Double, CosW As Double
    If TerrainMode = 0 Then
        'Spherical model
        H = Pl.Radius + Alt: Z = H * Sin(Lat): H = H * Cos(Lat)
    Else
        'Ellipsoidal model
        SLat = Sin(Lat): H = Pl.Radius / Sqr(1 - Pl.E2 * SLat * SLat)
        Z = (H * (1 - Pl.E2) + Alt) * SLat
        H = (H + Alt) * Cos(Lat)
        End If
    X = -H * Sin(Lon): Y = H * Cos(Lon)
    If Update Or Relative Then
        SinW = Sin(Pl.AngleW): CosW = Cos(Pl.AngleW): X2 = X * CosW - Y * SinW: Y2 = Y * CosW + X * SinW: Z2 = Z
        If Pl.AngleU <> 0 Then X = X2: Y = Y2 * Pl.CosA - Z2 * Pl.SinA: Z = Z2 * Pl.CosA + Y2 * Pl.SinA _
        Else If Pl.AngleV <> 0 Then X = X2 * Pl.CosA + Z2 * Pl.SinA: Y = Y2: Z = Z2 * Pl.CosA - X2 * Pl.SinA
        If Update Then PlanetsUpdate -X - Pl.X, -Y - Pl.Y, -Z - Pl.Z: Pl.Altitude = Alt
        If Move Then X = X + Pl.X: Y = Y + Pl.Y: Z = Z + Pl.Z
        End If
    End Sub

Sub LL2XYZ( _
        Optional Lat As Double, Optional Lon As Double, Optional E2 As Double = 1, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim H As Double, SLat As Double
    If TerrainMode = 0 Then Z = Sin(Lat): H = Cos(Lat) _
    Else SLat = Sin(Lat): E2 = 1 - E2 * E2: H = 1 / Sqr(1 - E2 * SLat * SLat): _
        Z = H * SLat * (1 - E2): H = H * Cos(Lat)
    X = H * Sin(Lon): Y = H * Cos(Lon)
    End Sub

Sub LLA2XYZP(Pl As Planet, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim H As Double, SLat As Double
    If TerrainMode = 0 Then
        H = Pl.Radius + Alt: Z = H * Sin(Lat)
        H = H * Cos(Lat): X = -H * Sin(Lon): Y = H * Cos(Lon)
    Else
        SLat = Sin(Lat): H = Pl.Radius / Sqr(1 - Pl.E2 * SLat * SLat)
        Z = (H * (1 - Pl.E2) + Alt) * SLat
        H = (H + Alt) * Cos(Lat): X = -H * Sin(Lon): Y = H * Cos(Lon)
        End If
    End Sub

Sub SetAlt(Pl As Planet, Optional Alt As Double)
    Dim Lat As Double, Lon As Double
    XYZ2LLA Pl, Lat, Lon: LLA2XYZ Pl, Lat, Lon, Alt, , , , , True
    End Sub

Sub SetLaunchEx(Cam As Camera, Optional ByVal X As Double, Optional ByVal Y As Double)
    Dim R As Double
    With Cam
    If .hRC Then If .Follow = ToTactic Then _
        Rotate .AngleU * Rad, X, Y: If Not .Free Then X = -X: Y = -Y
    If .Altitude > 0 And .FOVR > 0 And .Square > 0 Then R = .Square * 132000 / .Altitude / .FOVR
    End With
    If R < 1 Then R = 1 Else If R > 1000000 Then R = 1000000
    SetLaunch -Y / R * Rad, X / R * Rad, , True, True, Switch( _
        R >= 100000, FLong, R >= 10000, FFive, R >= 1000, FQuad, _
        R >= 100, FShort, R >= 10, FMini, True, FOne)
    End Sub

Sub SetLaunch( _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional ByVal Relative As Boolean, Optional ByVal Update As Boolean, _
        Optional Fmt As String = FLong)
    With Setup
    If Relative Then Lat = Lat + ValAngle(.Lat): Lon = Lon + ValAngle(.Lon)
    If Lat > A90 Then Lat = A90 Else If Lat < -A90 Then Lat = -A90
    If Lon > A180 Then Lon = Lon - A360 Else If Lon < -A180 Then Lon = Lon + A360
    .Lat = ToLat(Lat, Fmt, Degrees): .Lat.Refresh
    .Lon = ToLon(Lon, Fmt, Degrees): .Lon.Refresh
    If Not Relative Then
        If Alt = 0 Then .Alt = "" Else _
        If Units Then .Alt = Format(Alt / Foot, "0ft") Else .Alt = Format(Alt, "0m")
        .Alt.Refresh
        End If
    End With
    If Update And Play = Paused Then If T = 0 Then Init False, False, , , , , , True, True
    End Sub

Sub SetLaunchSelected()
    Dim X1 As Double, Y1 As Double, Z1 As Double
    If SelectedType <> TypeNone Then _
        If ObjectPositionFlat(SelectedType, SelectedN, X1, Y1, Z1) Then _
            SetLaunch Y1, X1, Z1, , True: Setup.Location = GetName(SelectedType, SelectedN)
    End Sub

Sub SetAngle(X As Edit, _
        Optional ByVal N As Double, Optional ByVal Update As Boolean = True)
    Dim S As String, U As String
    S = X: U = NumUpDown(S, N, 360, -360, , True)
    If S <> U Then X = U: X.Refresh: If Update Then If T = 0 Then Init False, False
    End Sub

Sub SetAngleDown(X As Edit, _
        Optional ByVal N As Double = 1, Optional ByVal Update As Boolean = True)
    SetAngle X, -IfDbl(Shift, N * 10, N), Update
    End Sub

Sub SetAngleUp(X As Edit, _
        Optional ByVal N As Double = 1, Optional ByVal Update As Boolean = True)
    SetAngle X, IfDbl(Shift, N * 10, N), Update
    End Sub

Sub SetPitch(X As Edit, _
        Optional ByVal N As Double, Optional ByVal Update As Boolean = True)
    Dim S As String, U As String
    S = X: U = NumUpDown(S, N, 90, -90)
    If S <> U Then X = U: X.Refresh: If Update Then If T = 0 Then Init False, False
    End Sub

Sub SetPitchDown(X As Edit, _
        Optional ByVal N As Double = 1, Optional ByVal Update As Boolean = True)
    SetPitch X, -IfDbl(Shift, N * 10, N), Update
    End Sub

Sub SetPitchUp(X As Edit, _
        Optional ByVal N As Double = 1, Optional ByVal Update As Boolean = True)
    SetPitch X, IfDbl(Shift, N * 10, N), Update
    End Sub

Sub SetParamDown(X As Edit, Optional ByVal N As Double = 1, _
        Optional ByVal LimitPercent As Long = True, Optional ByVal Update As Boolean = True)
    If EditDown(X, N, LimitPercent) Then If Update Then If T = 0 Then Init
    End Sub

Sub SetParamUp(X As Edit, Optional ByVal N As Double = 1, _
        Optional ByVal LimitPercent As Long = True, Optional ByVal Update As Boolean = True)
    If EditUp(X, N, LimitPercent) Then If Update Then If T = 0 Then Init
    End Sub

Function NumUpDown(Text As String, Optional ByVal N As Double, _
        Optional ByVal Max As Double = Infinite, Optional ByVal Min As Double, _
        Optional ByVal LimitPercent As Long = True, Optional ByVal Wrap As Boolean) As String
    Dim C As Long, P As Long, L As Long, D As Double, Fmt As String
    For C = 1 To Len(Text)
        Select Case Mid(Text, C, 1)
        Case ".": P = C: Case "0" To "9": If P Then L = C
        Case ",", "-", "+": Case Else: Exit For
            End Select
        Next
    If C = 1 Then If Text <> "" Then NumUpDown = Text: Exit Function
    If P <= 0 Or L <= P Then Fmt = "0" _
    Else L = L - P: N = N / 10 ^ L: Fmt = "0." & String(L, 48)
    N = N + Val(Left(Text, C - 1))
    NumUpDown = Mid(Text, C): If LimitPercent Then If InStr(NumUpDown, "%") Then _
        If LimitPercent = 2 Then Min = 100: Max = Infinite Else Min = 0: Max = 100
    If Wrap Then
        D = Max - Min: If N >= Max Or N < Min Then N = N - Fix(N / D) * D: _
            If N >= Max Then N = N - D Else If N < Min Then N = N + D
    Else
        If N < Min Then N = Min Else If N > Max Then N = Max
        End If
    NumUpDown = Replace(Format(N, Fmt), ",", ".") & NumUpDown
    End Function

Function EditUpDown(X As Edit, Optional ByVal N As Double, _
        Optional ByVal Max As Double = Infinite, Optional ByVal Min As Double, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    Dim S As String, T As String
    S = X: T = NumUpDown(S, N, Max, Min, LimitPercent)
    If S <> T Then X = T: X.Refresh: EditUpDown = True
    End Function

Function EditDown(X As Edit, Optional ByVal N As Double = 1, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    EditDown = EditUpDown(X, -IfDbl(Shift, N * 10, N), , , LimitPercent)
    End Function

Function EditUp(X As Edit, Optional ByVal N As Double = 1, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    EditUp = EditUpDown(X, IfDbl(Shift, N * 10, N), , , LimitPercent)
    End Function

Function EditLimitDown(X As Edit, Optional ByVal N As Double = 1, _
        Optional ByVal Max As Double = Infinite, Optional ByVal Min As Double, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    EditLimitDown = EditUpDown(X, -IfDbl(Shift, N * 10, N), Max, Min, LimitPercent)
    End Function

Function EditLimitUp(X As Edit, Optional ByVal N As Double = 1, _
        Optional ByVal Max As Double = Infinite, Optional ByVal Min As Double, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    EditLimitUp = EditUpDown(X, IfDbl(Shift, N * 10, N), Max, Min, LimitPercent)
    End Function

Function ListUpDown(X As DropList, Optional ByVal N As Double, _
        Optional ByVal Max As Double = Infinite, Optional ByVal Min As Double, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    Dim S As String, T As String
    S = X.Text: T = NumUpDown(S, N, Max, Min, LimitPercent)
    If S <> T Then X.Text = T: X.Refresh: ListUpDown = True
    End Function

Function ListDown(X As DropList, Optional ByVal N As Double = 1, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    ListDown = ListUpDown(X, -IfDbl(Shift, N * 10, N), , , LimitPercent)
    End Function

Function ListUp(X As DropList, Optional ByVal N As Double = 1, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    ListUp = ListUpDown(X, IfDbl(Shift, N * 10, N), , , LimitPercent)
    End Function

Function ListLimitDown(X As DropList, Optional ByVal N As Double = 1, _
        Optional ByVal Max As Double = Infinite, Optional ByVal Min As Double, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    ListLimitDown = ListUpDown(X, -IfDbl(Shift, N * 10, N), Max, Min, LimitPercent)
    End Function

Function ListLimitUp(X As DropList, Optional ByVal N As Double = 1, _
        Optional ByVal Max As Double = Infinite, Optional ByVal Min As Double, _
        Optional ByVal LimitPercent As Long = True) As Boolean
    ListLimitUp = ListUpDown(X, IfDbl(Shift, N * 10, N), Max, Min, LimitPercent)
    End Function

Function IsPlanet(Name As String) As Long
    Select Case LCase(Name)
    Case "moon":    IsPlanet = ToMoon
    Case "mercury": IsPlanet = ToMercury
    Case "venus":   IsPlanet = ToVenus
    Case "mars":    IsPlanet = ToMars
    Case "jupiter": IsPlanet = ToJupiter
    Case "saturn":  IsPlanet = ToSaturn
    Case "uranus":  IsPlanet = ToUranus
    Case "neptune": IsPlanet = ToNeptune
    Case "pluto":   IsPlanet = ToPluto
    Case "sun":     IsPlanet = ToSun
    Case Else:      IsPlanet = ToEarth
        End Select
    End Function

Sub PlanetPosAll(Optional ByVal AllPlanets As Boolean = True)
    Dim T1 As Double
    Sun.SpeedX = SpeedX: Sun.SpeedY = SpeedY: Sun.SpeedZ = SpeedZ
    Sun.Speed = Sqr(Sun.SpeedX * Sun.SpeedX + Sun.SpeedY * Sun.SpeedY + Sun.SpeedZ * Sun.SpeedZ)
    If Not AllPlanets Then Exit Sub
    If GravityMode < 3 Then T1 = Time Else T1 = Time + T
    PlanetPos Mercury, Sun, T1
    PlanetPos Venus, Sun, T1
    PlanetPos Mars, Sun, T1
    PlanetPos Jupiter, Sun, T1
    PlanetPos Saturn, Sun, T1
    PlanetPos Uranus, Sun, T1
    PlanetPos Neptune, Sun, T1
    PlanetPos Pluto, Sun, T1
    End Sub

Sub PlanetSet(Pl As Planet, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional SpeedX As Double, Optional SpeedY As Double, Optional SpeedZ As Double)
    With Pl: .X = X: .Y = Y: .Z = Z
    .MainSpeedX = SpeedX: .MainSpeedY = SpeedY: .MainSpeedZ = SpeedZ: End With
    End Sub

Sub PlanetGet(Pl As Planet, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional SpeedX As Double, Optional SpeedY As Double, Optional SpeedZ As Double, _
        Optional AngleU As Double, Optional AngleV As Double, Optional AngleW As Double, _
        Optional VW As Double, Optional Diameter As Double, Optional Mass As Double)
    With Pl: X = .X: Y = .Y: Z = .Z
    SpeedX = .MainSpeedX: SpeedY = .MainSpeedY: SpeedZ = .MainSpeedZ
    AngleU = .AngleU: AngleV = .AngleV: AngleW = .AngleW: VW = .AngularSpeedW
    Diameter = .Diameter: Mass = .Mass: End With
    End Sub

Sub GetXYZ(Pl As Planet, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    X = Pl.X: Y = Pl.Y: Z = Pl.Z
    End Sub

Sub GetVector(Pl As Planet, Optional N As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Z = N / Pl.Distance: X = Pl.X * Z: Y = Pl.Y * Z: Z = Pl.Z * Z
    End Sub

'Sub SavePlanets()
'    PlanetSave "Sun", _
'        1391000000, 1390987481, 1.98841582939283E+30, 5778, , _
'        7.25, , , 2164320 / Day
'    PlanetSave "Earth", _
'        12756274, 12713504.62849, 5.97218639014246E+24, Kelvin + 15, 0.35, _
'        -23.496932, , , 86400 / Day, _
'        0.00005, -0.000000356985, _
'        348.73936, -0.00013863, _
'        102.94719, 0.00000911309, _
'        1.00000011, -1.36893E-12, _
'        100.46435, 0.985609101, _
'        0.01671022, -0.00000000104148
'    PlanetSave "Earth.Moon", _
'        3476280, 3471940, 7.34577797155648E+22, 220, 0.12, _
'        , 1.5424, , 2380713.1099797 / Day, _
'        5.1454, , _
'        125.1228, -0.0529538083, _
'        318.0634, 0.1643573223, _
'        60.2666, , _
'        115.3654, 13.0649929509, _
'        0.0549
'    PlanetSave "Mercury", _
'        4879400, , 3.30103022348478E+23, 340, 0.14, _
'        , 3.51666666666667E-02, , 5067033.84 / Day, _
'        7.00487, -0.000000178797, _
'        48.33167, -0.0000033942, _
'        77.45645, 0.00000436208, _
'        0.38709893, 1.80698E-11, _
'        252.25084, 4.092338796, _
'        0.20563069, 0.000000000691855
'    PlanetSave "Venus", _
'        12103600, , 4.86732651312201E+24, 373, 0.84, _
'        -177.3, , , -20996815.68 / Day, _
'        3.39471, -0.0000000217507, _
'        76.68069, -0.0000075815, _
'        131.53298, -0.000000827439, _
'        0.72333199, 2.51882E-11, _
'        181.97973, 1.602130474, _
'        0.00677323, -0.00000000135195
'    PlanetSave "Mars", _
'        6792400, 6752400, 6.41687193225337E+23, 227, 0.15, _
'        , 25.19, , 88642.6632 / Day, _
'        1.85061, -0.000000193703, _
'        49.57854, -0.0000077587, _
'        336.04084, 0.00001187, _
'        1.52366231, -0.000000001977, _
'        355.45332, 0.524033035, _
'        0.09341233, -0.00000000325859
'    PlanetSave "Jupiter", _
'        142984000, 133708000, 1.89813034514584E+27, 165, 0.52, _
'        , -3.13, , 35729.856 / Day, _
'        1.3053, -0.0000000315613, _
'        100.55615, 0.00000925675, _
'        14.75385, 0.00000638779, _
'        5.20336301, 0.0000000166289, _
'        34.40438, 0.083086762, _
'        0.04839266, -0.00000000352635
'    PlanetSave "Saturn", _
'        120536000, 108728000, 5.6831878494759E+26, 134, 0.47, _
'        -26.73, , , 38362.464 / Day, _
'        2.48446, 0.0000000464674, _
'        113.71504, -0.0000121, _
'        92.43194, -0.0000148216, _
'        9.53707032, -0.0000000825544, _
'        49.94432, 0.033470629, _
'        0.0541506, -0.0000000100649
'    PlanetSave "Uranus", _
'        51118000, 49946000, 8.68099480393391E+25, 76, 0.51, _
'        , -97.77, , -62063.712 / Day, _
'        0.76986, -0.0000000158947, _
'        74.22988, 0.0000127873, _
'        170.96424, 0.0000099822, _
'        19.19126393, 0.0000000416222, _
'        313.23218, 0.011731294, _
'        0.04716771, -0.00000000524298
'    PlanetSave "Neptune", _
'        49528000, 48682000, 1.02430958844999E+26, 72, 0.41, _
'        , 28.32, , 57996 / Day, _
'        1.76917, -0.0000000276827, _
'        131.72169, -0.0000011503, _
'        44.97135, -0.00000642201, _
'        30.06896348, -0.0000000342768, _
'        304.88003, 0.0059810572, _
'        0.00858587, 0.000000000688296
'    PlanetSave "Pluto", _
'        2306000, , 1.30500967894664E+22 + 1.52E+21, 44, 0.44, _
'        , -119.591, , -551852.352 / Day, _
'        17.14175, 0.0000000841889, _
'        110.30347, -0.0000002839, _
'        224.06676, -0.00000100578, _
'        39.48168677, -0.0000000210574, _
'        238.92881, 0.003972926, _
'        0.24880766, 0.00000000177002
'    End Sub

'Sub PlanetSave(Name As String, _
'        Optional Diameter As Double, Optional Diameter2 As Double, Optional Mass As Double, _
'        Optional Infrared As Double, Optional Albedo As Double, _
'        Optional AngleU As Double, Optional AngleV As Double, Optional AngleW As Double, _
'        Optional RotationTime As Double, _
'        Optional OrbitI As Double, Optional OrbitIV As Double, _
'        Optional OrbitO As Double, Optional OrbitOV As Double, _
'        Optional OrbitP As Double, Optional OrbitPV As Double, _
'        Optional OrbitS As Double, Optional OrbitSV As Double, _
'        Optional OrbitL As Double, Optional OrbitLV As Double, _
'        Optional OrbitE As Double, Optional OrbitEV As Double)
'    StrAdd _
'        "EquatorialRadius=" & NumD(Diameter / 2) & vbCrLf & _
'        "PolarRadius=" & NumD(Diameter2 / 2) & vbCrLf & _
'        "Mass=" & NumD(Mass) & vbCrLf & _
'        "SurfaceTemperature=" & NumD(Infrared - Kelvin) & vbCrLf & _
'        "SurfaceAlbedo=" & NumD(Albedo) & vbCrLf & _
'        "AxialTiltX=" & NumD(AngleU) & vbCrLf & _
'        "AxialTiltY=" & NumD(AngleV) & vbCrLf & _
'        "RotationTime=" & NumD(RotationTime) & vbCrLf & _
'        "OrbitalInclination=" & NumD(OrbitI) & vbCrLf & _
'        "OrbitalInclinationV=" & NumD(OrbitIV) & vbCrLf & _
'        "LongitudeOfAscendingNode=" & NumD(OrbitO) & vbCrLf & _
'        "LongitudeOfAscendingNodeV=" & NumD(OrbitOV) & vbCrLf & _
'        "LongitudeOfPeriapsis=" & NumD(OrbitP) & vbCrLf & _
'        "LongitudeOfPeriapsisV=" & NumD(OrbitPV) & vbCrLf & _
'        "SemiMajorAxis=" & NumD(OrbitS) & vbCrLf & _
'        "SemiMajorAxisV=" & NumD(OrbitSV) & vbCrLf & _
'        "MeanAnomalyAtEpoch=" & NumD(OrbitL) & vbCrLf & _
'        "MeanAnomalyAtEpochV=" & NumD(OrbitLV) & vbCrLf & _
'        "OrbitalEccentricity=" & NumD(OrbitE) & vbCrLf & _
'        "OrbitalEccentricityV=" & NumD(OrbitEV) & vbCrLf
'    UpdateFile PlanetsDir & Name & ".ini", StrAdd
'    End Sub

Sub LoadPlanets(Optional ByVal Update As Boolean)
    Message "Loading planets …"
    
'Sun        Equatorial=1391000000 Polar=1390987481 Flattening=0.000009 (1/111111)
    LoadPlanet "Sun", Sun, Earth, ToSun, ClassStar, , _
        1391000000, 1390987481, , , 1.98841582939283E+30, 5778, , _
        7.25, , , 2164320 / Day
    
'Earth      Equatorial=12756274 Polar=12713504.6284904 Flattening=0.00335281066474426621 (1/298)
    'Angular speed slows down 2.3 milliseconds per century
    'Axial tilt .. varies from 22º38’ to 24º21’ every 41013 years
    'J2000=23.439281º .. -23,4376906116422
    LoadPlanet "Earth", Earth, Sun, ToEarth, ClassPlanet, , _
        12756274, 12713504.62849, , , 5.97218639014246E+24, Kelvin + 15, 0.35, _
        -23.496932, , , 86400 / Day, _
        0.00005, -0.000000356985, _
        348.73936, -0.00013863, _
        102.94719, 0.00000911309, _
        1.00000011, -1.36893E-12, _
        100.46435, 0.985609101, _
        0.01671022, -0.00000000104148
    
'Moon       Equatorial=3476280 Polar=3471940 Flattening=0.00124846099853866778 (1/801)
    'Sidreal period=2360584.7s(27d 07h 43m 04.7s)  Synodic period=2551442.9s(29d 12h 44m 2.9s)
    '    ?=2488197=28d 16h 9m 57s  Anomalistic-MoonXYZ=2380713.11=27d 13h 18m 33.11s
    'Axial tilt .. 1.5424º(axis to ecliptic) 6.687º(axis to orbit) 5.145º(orbit to ecliptic)
    LoadPlanet "Earth.Moon", Moon, Sun, ToMoon, ClassMoon, Earth.Radius, _
        3476280, 3471940, , , 7.34577797155648E+22, 220, 0.12, _
        , 1.5424, , 2380713.1099797 / Day, _
        5.1454, , _
        125.1228, -0.0529538083, _
        318.0634, 0.1643573223, _
        60.2666, , _
        115.3654, 13.0649929509, _
        0.0549
    
'Mercury    Equatorial=Polar=4879400 Flattening=0 .. 4879280?
    LoadPlanet "Mercury", Mercury, Earth, ToMercury, ClassPlanet, , _
        4879400, , , , 3.30103022348478E+23, 340, 0.14, _
        , 3.51666666666667E-02, , 5067033.84 / Day, _
        7.00487, -0.000000178797, _
        48.33167, -0.0000033942, _
        77.45645, 0.00000436208, _
        0.38709893, 1.80698E-11, _
        252.25084, 4.092338796, _
        0.20563069, 0.000000000691855
    
'Venus      Equatorial=Polar=12103600 Flattening=0 .. 12103180?
    LoadPlanet "Venus", Venus, Earth, ToVenus, ClassPlanet, , _
        12103600, , , , 4.86732651312201E+24, 373, 0.84, _
        -177.3, , , -20996815.68 / Day, _
        3.39471, -0.0000000217507, _
        76.68069, -0.0000075815, _
        131.53298, -0.000000827439, _
        0.72333199, 2.51882E-11, _
        181.97973, 1.602130474, _
        0.00677323, -0.00000000135195
    
'Mars       Equatorial=6792400 Polar=6752400 Flattening=0.00588893469171426889 (1/170) .. 6794000?
    LoadPlanet "Mars", Mars, Earth, ToMars, ClassPlanet, , _
        6792400, 6752400, , , 6.41687193225337E+23, 227, 0.15, _
        , 25.19, , 88642.6632 / Day, _
        1.85061, -0.000000193703, _
        49.57854, -0.0000077587, _
        336.04084, 0.00001187, _
        1.52366231, -0.000000001977, _
        355.45332, 0.524033035, _
        0.09341233, -0.00000000325859
    
'Jupiter    Equatorial=142984000 Polar=133708000 Flattening=0.0648743915403122028 (1/15.4) .. 142985360?
    LoadPlanet "Jupiter", Jupiter, Earth, ToJupiter, ClassGasGiant, , _
        142984000, 133708000, 122500000, 226000000, 1.89813034514584E+27, 165, 0.52, _
        , -3.13, , 35729.856 / Day, _
        1.3053, -0.0000000315613, _
        100.55615, 0.00000925675, _
        14.75385, 0.00000638779, _
        5.20336301, 0.0000000166289, _
        34.40438, 0.083086762, _
        0.04839266, -0.00000000352635
    
'Saturn     Equatorial=120536000 Polar=108728000 Flattening=0.0979624344594146147 (1/10.2) .. 120534280?
    LoadPlanet "Saturn", Saturn, Earth, ToSaturn, ClassGasGiant, , _
        120536000, 108728000, 74500000, 140220000, 5.6831878494759E+26, 134, 0.47, _
        -26.73, , , 38362.464 / Day, _
        2.48446, 0.0000000464674, _
        113.71504, -0.0000121, _
        92.43194, -0.0000148216, _
        9.53707032, -0.0000000825544, _
        49.94432, 0.033470629, _
        0.0541506, -0.0000000100649
    
'Uranus     Equatorial=51118000 Polar=49946000 Flattening=0.0229273445752963731 (1/43.6) .. 51114500?
    LoadPlanet "Uranus", Uranus, Earth, ToUranus, ClassGasGiant, , _
        51118000, 49946000, 41837000, 51179000, 8.68099480393391E+25, 76, 0.51, _
        , -97.77, , -62063.712 / Day, _
        0.76986, -0.0000000158947, _
        74.22988, 0.0000127873, _
        170.96424, 0.0000099822, _
        19.19126393, 0.0000000416222, _
        313.23218, 0.011731294, _
        0.04716771, -0.00000000524298
    
'Neptune    Equatorial=49528000 Polar=48682000 Flattening=0.0170812469714101114 (1/58.5) .. 49532720?
    LoadPlanet "Neptune", Neptune, Earth, ToNeptune, ClassGasGiant, , _
        49528000, 48682000, 53150000, 62947000, 1.02430958844999E+26, 72, 0.41, _
        , 28.32, , 57996 / Day, _
        1.76917, -0.0000000276827, _
        131.72169, -0.0000011503, _
        44.97135, -0.00000642201, _
        30.06896348, -0.0000000342768, _
        304.88003, 0.0059810572, _
        0.00858587, 0.000000000688296
    
'Pluto      Equatorial=Polar=2306000 Flattening=0 .. 2390000? .. Lv=0.013831834 or 0.00397292649241742147
    LoadPlanet "Pluto", Pluto, Earth, ToPluto, ClassDwarfPlanet, , _
        2306000, , , , 1.30500967894664E+22 + 1.52E+21, 44, 0.44, _
        , -119.591, , -551852.352 / Day, _
        17.14175, 0.0000000841889, _
        110.30347, -0.0000002839, _
        224.06676, -0.00000100578, _
        39.48168677, -0.0000000210574, _
        238.92881, 0.003972926, _
        0.24880766, 0.00000000177002
    
    Message
    If Update Then DeletePlanets: Init
    
    End Sub

Sub LoadPlanet(File As String, Pl As Planet, Rt As Planet, ByVal ID As TargetType, _
        Optional ByVal Class As PlanetType = ClassPlanet, Optional Distance As Double = AU, _
        Optional Diameter As Double, Optional Diameter2 As Double, _
        Optional InnerRing As Double, Optional OuterRing As Double, _
        Optional Mass As Double, Optional Temperature As Double, Optional Albedo As Double, _
        Optional AngleU As Double, Optional AngleV As Double, Optional AngleW As Double, _
        Optional RotationTime As Double, _
        Optional OrbitI As Double, Optional OrbitIV As Double, _
        Optional OrbitO As Double, Optional OrbitOV As Double, _
        Optional OrbitP As Double, Optional OrbitPV As Double, _
        Optional OrbitS As Double, Optional OrbitSV As Double, _
        Optional OrbitL As Double, Optional OrbitLV As Double, _
        Optional OrbitE As Double, Optional OrbitEV As Double)
    Dim A As Long, N As Double, S As String, T As String
    T = LoadFile(PlanetsDir & File & ".ini"): If T = "" Then GoTo Nxt
    On Error Resume Next
    Diameter = NumCmd(T, "EquatorialRadius") * 2
    Diameter2 = NumCmd(T, "PolarRadius") * 2
    InnerRing = NumCmd(T, "InnerRing")
    OuterRing = NumCmd(T, "OuterRing")
    Mass = NumCmd(T, "Mass")
    Temperature = NumCmd(T, "SurfaceTemperature") + Kelvin
    Albedo = NumCmd(T, "SurfaceAlbedo")
    AngleU = NumCmd(T, "AxialTiltX")
    AngleV = NumCmd(T, "AxialTiltY")
    RotationTime = NumCmd(T, "RotationTime")
    OrbitI = NumCmd(T, "OrbitalInclination")
    OrbitIV = NumCmd(T, "OrbitalInclinationV")
    OrbitO = NumCmd(T, "LongitudeOfAscendingNode")
    OrbitOV = NumCmd(T, "LongitudeOfAscendingNodeV")
    OrbitP = NumCmd(T, "LongitudeOfPeriapsis")
    OrbitPV = NumCmd(T, "LongitudeOfPeriapsisV")
    OrbitS = NumCmd(T, "SemiMajorAxis")
    OrbitSV = NumCmd(T, "SemiMajorAxisV")
    OrbitL = NumCmd(T, "MeanAnomalyAtEpoch")
    OrbitLV = NumCmd(T, "MeanAnomalyAtEpochV")
    OrbitE = NumCmd(T, "OrbitalEccentricity")
    OrbitEV = NumCmd(T, "OrbitalEccentricityV")
    On Error GoTo 0
Nxt:
    With Pl
    .ID = ID: .Class = Class
    .Diameter = Diameter: .Radius = .Diameter / 2
    If Diameter2 > 0 Then .Radius2 = Diameter2 / 2 Else .Radius2 = .Radius
    .RA2 = .Radius * .Radius: .RB2 = .Radius2 * .Radius2
    .E2 = 1 - .RB2 / .RA2: .E4 = .E2 * .E2: .EE2 = .E2 * .RA2 / .RB2
    .E21 = 1 - .E2: .EAB2 = .E2 * (.RA2 - .RB2)
    .InnerRing = InnerRing: .OuterRing = OuterRing
    .Mass = Mass: .GravityMass = G * .Mass
    .Albedo = SunLuminosity / 4 / Pi 'QSun
    .Temperature = Temperature: .Infrared = .Radius ^ 2 * Sigma * Temperature ^ 4 * 0.9 'QInfrared
    If Class <> ClassStar Then .Albedo = .Infrared + .Albedo / _
        IfDbl(Class = ClassMoon, Earth.OrbitS, OrbitS * Distance) ^ 2 * .Radius ^ 2 * Albedo 'QAlbedo
    .AngleU = AngleU * Rad: .AngleV = AngleV * Rad: .AngleW = AngleW * Rad
    .AngularSpeedW = 360 / RotationTime / Day * Rad
    If AngleU <> 0 Then .CosA = Cos(.AngleU): .SinA = Sin(.AngleU)
    If AngleV <> 0 Then .CosA = Cos(.AngleV): .SinA = Sin(.AngleV)
    .OrbitI = OrbitI * Rad: .OrbitIV = OrbitIV * Rad
    .OrbitO = OrbitO * Rad: .OrbitOV = OrbitOV * Rad
    .OrbitP = OrbitP * Rad: .OrbitPV = OrbitPV * Rad
    .OrbitS = OrbitS * Distance: .OrbitSV = OrbitSV * Distance
    .OrbitL = OrbitL * Rad: .OrbitLV = OrbitLV * Rad
    .OrbitE = OrbitE: .OrbitEV = OrbitEV
    End With
    End Sub

Sub PlanetMove(Pl As Planet, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional SpeedX As Double, Optional SpeedY As Double, Optional SpeedZ As Double)
    Pl.X = Pl.X - X: Pl.Y = Pl.Y - Y: Pl.Z = Pl.Z - Z
    Pl.MainSpeedX = Pl.MainSpeedX - SpeedX
    Pl.MainSpeedY = Pl.MainSpeedY - SpeedY
    Pl.MainSpeedZ = Pl.MainSpeedZ - SpeedZ
    End Sub
        
Sub PlanetUpdate(Pl As Planet, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Pl.X = Pl.X + X: Pl.Y = Pl.Y + Y: Pl.Z = Pl.Z + Z
    Pl.Distance = Sqr(Pl.X * Pl.X + Pl.Y * Pl.Y + Pl.Z * Pl.Z)
    Pl.Altitude = Pl.Distance - Pl.Radius
    End Sub

Sub PlanetsUpdate(Optional X As Double, Optional Y As Double, Optional Z As Double)
    PlanetUpdate Earth, X, Y, Z
    PlanetUpdate Moon, X, Y, Z
    PlanetUpdate Sun, X, Y, Z
    PlanetUpdate Mercury, X, Y, Z
    PlanetUpdate Venus, X, Y, Z
    PlanetUpdate Mars, X, Y, Z
    PlanetUpdate Jupiter, X, Y, Z
    PlanetUpdate Saturn, X, Y, Z
    PlanetUpdate Uranus, X, Y, Z
    PlanetUpdate Neptune, X, Y, Z
    PlanetUpdate Pluto, X, Y, Z
    End Sub

Sub PlanetPos(Pl As Planet, Rt As Planet, Time As Double, _
        Optional Delta As Double = Hour, Optional ByVal Relative As Long = 1)
    With Pl
    PlanetXYZ Pl, (Time - Delta / 2) / Day
    .MainSpeedX = .X: .MainSpeedY = .Y: .MainSpeedZ = .Z
    PlanetXYZ Pl, (Time + Delta / 2) / Day
    .MainSpeedX = (.X - .MainSpeedX) / Delta
    .MainSpeedY = (.Y - .MainSpeedY) / Delta
    .MainSpeedZ = (.Z - .MainSpeedZ) / Delta
    PlanetXYZ Pl, Time / Day
    Select Case Relative
    Case 1: .X = .X + Rt.X: .Y = .Y + Rt.Y: .Z = .Z + Rt.Z
    Case -1: .X = .X - Rt.X: .Y = .Y - Rt.Y: .Z = .Z - Rt.Z
        End Select
    .Distance = Sqr(.X * .X + .Y * .Y + .Z * .Z)
    .Altitude = .Distance - .Radius: .LastAltitude = .Distance
    .SpeedX = SpeedX - .MainSpeedX: .SpeedY = SpeedY - .MainSpeedY: .SpeedZ = SpeedZ - .MainSpeedZ
    .Speed = Sqr(.SpeedX * .SpeedX + .SpeedY * .SpeedY + .SpeedZ * .SpeedZ)
    .AngleW = .AngularSpeedW * Time
    End With
    End Sub

Sub PlanetXYZ(Pl As Planet, T As Double) ', ByVal CalcSpeed As Boolean)
    Dim I As Double, O As Double, P As Double, S As Double, L As Double, E As Double, _
        R As Double, A As Double, V As Double, _
        M As Double, N As Double, D As Double, D1 As Double, D2 As Double, _
        SinA As Double, CosA As Double, _
        SinI As Double, CosI As Double, _
        SinO As Double, CosO As Double
    With Pl
    Select Case .Class
    Case ClassStar
        .X = 0: .Y = 0: .Z = 0
        'If CalcSpeed Then .MainSpeedX = 0: .MainSpeedY = 0: .MainSpeedZ = 0
    Case ClassMoon
        'Get the osculating elements
        A = T + 1.5
        I = .OrbitI + .OrbitIV * A                                    'Inclination to the ecliptic
        O = .OrbitO + .OrbitOV * A                                    'Longitude of ascending node
        P = .OrbitP + .OrbitPV * A                                    'Longitude of periapsis
        S = .OrbitS + .OrbitSV * A                                    'Semi-major axis
        L = .OrbitL + .OrbitLV * A                                    'Mean anomaly at epoch
        E = .OrbitE + .OrbitEV * A                                    'Orbital eccentricity
        'Position of the Moon in its orbit
        A = L + E * Sin(L) * (1 + E * Cos(L))
        .X = S * (Cos(A) - E)
        .Y = S * (Sqr(1 - E * E) * Sin(A))
        A = ATan(.Y, .X) + P
        R = -Sqr(.X * .X + .Y * .Y)
        'Geocentric rectangular coordinates
        SinA = Sin(A): CosA = Cos(A): SinI = Sin(I): CosI = Cos(I): SinO = Sin(O): CosO = Cos(O)
        .X = R * (CosO * CosA - SinO * SinA * CosI)
        .Y = R * (SinO * CosA + CosO * SinA * CosI)
        .Z = R * SinA * SinI
        'If CalcSpeed Then
        '    V = Pl.GravityMass * (2 / R - 1 / S)
        '    .MainSpeedX = V * (-SinO * CosA - SinO * CosI * SinA)
        '    .MainSpeedY = V * (-SinO * CosA - SinO * CosI * SinA)
        '    .MainSpeedZ = V * (CosO * SinI)
        '    End If
    Case Else
        'Get the osculating elements
        I = .OrbitI + .OrbitIV * T                                    'Inclination to the ecliptic
        O = .OrbitO + .OrbitOV * T                                    'Longitude of ascending node
        P = .OrbitP + .OrbitPV * T                                    'Longitude of periapsis
        S = .OrbitS + .OrbitSV * T                                    'Semi-major axis
        L = .OrbitL + .OrbitLV * T                                    'Mean anomaly at epoch
        E = .OrbitE + .OrbitEV * T                                    'Orbital eccentricity
        'Position of the planet in its orbit
        M = L - P: N = M: D = 1: D1 = D
        Do
            D2 = D1: D = N - E * Sin(N) - M: N = N - D / (1 - E * Cos(N))
            If D < 0 Then D1 = -D Else D1 = D
            Loop While D1 >= 0.00000000001 And D1 < D2
        A = 2 * Atn(((1 + E) / (1 - E)) ^ 0.5 * Tan(0.5 * N))
        If A < 0 Then A = A + A360
        R = S * (1 - E * E) / (1 + E * Cos(A))
        A = A + P - O
        'Heliocentric rectangular coordinates
        SinA = Sin(A): CosA = Cos(A): SinI = Sin(I): CosI = Cos(I): SinO = Sin(O): CosO = Cos(O)
        .X = R * (CosO * CosA - SinO * SinA * CosI)
        .Y = R * (SinO * CosA + CosO * SinA * CosI)
        .Z = R * SinA * SinI
        'If CalcSpeed Then
        '    V = Pl.GravityMass * (2 / R - 1 / S)
        '    .MainSpeedX = V * (-SinO * CosA - SinO * CosI * SinA)
        '    .MainSpeedY = V * (-SinO * CosA - SinO * CosI * SinA)
        '    .MainSpeedZ = V * (CosO * SinI)
        '    End If
        End Select
    End With
    End Sub

Sub PlanetXYZEx(Pl As Planet, T As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim I As Double, O As Double, P As Double, S As Double, L As Double, E As Double, _
        R As Double, A As Double, V As Double, _
        M As Double, N As Double, D As Double, D1 As Double, D2 As Double, _
        SinA As Double, CosA As Double, _
        SinI As Double, CosI As Double, _
        SinO As Double, CosO As Double
    With Pl
    Select Case .Class
    Case ClassStar: X = 0: Y = 0: Z = 0
    Case ClassMoon
        'Get the osculating elements
        A = T + 1.5
        I = .OrbitI + .OrbitIV * A                                    'Inclination to the ecliptic
        O = .OrbitO + .OrbitOV * A                                    'Longitude of ascending node
        P = .OrbitP + .OrbitPV * A                                    'Longitude of periapsis
        S = .OrbitS + .OrbitSV * A                                    'Semi-major axis
        L = .OrbitL + .OrbitLV * A                                    'Mean anomaly at epoch
        E = .OrbitE + .OrbitEV * A                                    'Orbital eccentricity
        'Position of the Moon in its orbit
        A = L + E * Sin(L) * (1 + E * Cos(L))
        X = S * (Cos(A) - E)
        Y = S * (Sqr(1 - E * E) * Sin(A))
        A = ATan(Y, X) + P
        R = -Sqr(X * X + Y * Y)
        'Geocentric rectangular coordinates
        SinA = Sin(A): CosA = Cos(A): SinI = Sin(I): CosI = Cos(I): SinO = Sin(O): CosO = Cos(O)
        X = R * (CosO * CosA - SinO * SinA * CosI)
        Y = R * (SinO * CosA + CosO * SinA * CosI)
        Z = R * SinA * SinI
    Case Else
        'Get the osculating elements
        I = .OrbitI + .OrbitIV * T                                    'Inclination to the ecliptic
        O = .OrbitO + .OrbitOV * T                                    'Longitude of ascending node
        P = .OrbitP + .OrbitPV * T                                    'Longitude of periapsis
        S = .OrbitS + .OrbitSV * T                                    'Semi-major axis
        L = .OrbitL + .OrbitLV * T                                    'Mean anomaly at epoch
        E = .OrbitE + .OrbitEV * T                                    'Orbital eccentricity
        'Position of the planet in its orbit
        M = L - P: N = M: D = 1: D1 = D
        Do
            D2 = D1: D = N - E * Sin(N) - M: N = N - D / (1 - E * Cos(N))
            If D < 0 Then D1 = -D Else D1 = D
            Loop While D1 >= 0.00000000001 And D1 < D2
        A = 2 * Atn(((1 + E) / (1 - E)) ^ 0.5 * Tan(0.5 * N))
        If A < 0 Then A = A + A360
        R = S * (1 - E * E) / (1 + E * Cos(A))
        A = A + P - O
        'Heliocentric rectangular coordinates
        SinA = Sin(A): CosA = Cos(A): SinI = Sin(I): CosI = Cos(I): SinO = Sin(O): CosO = Cos(O)
        X = R * (CosO * CosA - SinO * SinA * CosI)
        Y = R * (SinO * CosA + CosO * SinA * CosI)
        Z = R * SinA * SinI
        End Select
    End With
    End Sub

Sub PlanetData(ByVal P As TargetType, Optional Radius As Double, _
        Optional Apoapsis As Double, Optional Periapsis As Double, Optional MeanMotion As Double, _
        Optional Temperature As Double, Optional Gravity As Double)
    Dim R As Double, S As Double, E As Double, GM As Double, G As Double, T As Double
    Select Case P
    Case ToEarth, ToAir: R = Earth.Radius: S = Earth.OrbitS: E = Earth.OrbitE: GM = Sun.GravityMass: G = Earth.GravityMass: T = Earth.Temperature
    Case ToMoon:    R = Moon.Radius: S = Moon.OrbitS: E = Moon.OrbitE: GM = Earth.GravityMass: G = Moon.GravityMass: T = Moon.Temperature
    Case ToSun:     R = Sun.Radius: G = Sun.GravityMass: T = Sun.Temperature: GoTo NoOrbit
    Case ToMercury: R = Mercury.Radius: S = Mercury.OrbitS: E = Mercury.OrbitE: GM = Sun.GravityMass: G = Mercury.GravityMass: T = Mercury.Temperature
    Case ToVenus:   R = Venus.Radius: S = Venus.OrbitS: E = Venus.OrbitE: GM = Sun.GravityMass: G = Venus.GravityMass: T = Venus.Temperature
    Case ToMars:    R = Mars.Radius: S = Mars.OrbitS: E = Mars.OrbitE: GM = Sun.GravityMass: G = Mars.GravityMass: T = Mars.Temperature
    Case ToJupiter: R = Jupiter.Radius: S = Jupiter.OrbitS: E = Jupiter.OrbitE: GM = Sun.GravityMass: G = Jupiter.GravityMass: T = Jupiter.Temperature
    Case ToSaturn:  R = Saturn.Radius: S = Saturn.OrbitS: E = Saturn.OrbitE: GM = Sun.GravityMass: G = Saturn.GravityMass: T = Saturn.Temperature
    Case ToUranus:  R = Uranus.Radius: S = Uranus.OrbitS: E = Uranus.OrbitE: GM = Sun.GravityMass: G = Uranus.GravityMass: T = Uranus.Temperature
    Case ToNeptune: R = Neptune.Radius: S = Neptune.OrbitS: E = Neptune.OrbitE: GM = Sun.GravityMass: G = Neptune.GravityMass: T = Neptune.Temperature
    Case ToPluto:   R = Pluto.Radius: S = Pluto.OrbitS: E = Pluto.OrbitE: GM = Sun.GravityMass: G = Pluto.GravityMass: T = Pluto.Temperature
    Case Else:      Radius = 0: Apoapsis = 0: Periapsis = 0: MeanMotion = 0: Temperature = 0: Gravity = 0: GoTo Ext
        End Select
    Apoapsis = S * (1 + E): Periapsis = S * (1 - E): MeanMotion = Sqr(GM / S / S / S)
NoOrbit:
    Radius = R: Temperature = T: Gravity = G / R / R
Ext:
    End Sub

Sub PlanetDataEx(ByVal P As TargetType, Optional Altitude As Double, Optional Distance As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Select Case Target
    Case ToEarth, ToAir: Altitude = Earth.Altitude: Distance = Earth.Distance: X = Earth.X:  Y = Earth.Y:   Z = Earth.Z
    Case ToMoon:    Altitude = Moon.Altitude:    Distance = Moon.Distance:    X = Moon.X:    Y = Moon.Y:    Z = Moon.Z
    Case ToMercury: Altitude = Mercury.Altitude: Distance = Mercury.Distance: X = Mercury.X: Y = Mercury.Y: Z = Mercury.Z
    Case ToVenus:   Altitude = Venus.Altitude:   Distance = Venus.Distance:   X = Venus.X:   Y = Venus.Y:   Z = Venus.Z
    Case ToMars:    Altitude = Mars.Altitude:    Distance = Mars.Distance:    X = Mars.X:    Y = Mars.Y:    Z = Mars.Z
    Case ToJupiter: Altitude = Jupiter.Altitude: Distance = Jupiter.Distance: X = Jupiter.X: Y = Jupiter.Y: Z = Jupiter.Z
    Case ToSaturn:  Altitude = Saturn.Altitude:  Distance = Saturn.Distance:  X = Saturn.X:  Y = Saturn.Y:  Z = Saturn.Z
    Case ToUranus:  Altitude = Uranus.Altitude:  Distance = Uranus.Distance:  X = Uranus.X:  Y = Uranus.Y:  Z = Uranus.Z
    Case ToNeptune: Altitude = Neptune.Altitude: Distance = Neptune.Distance: X = Neptune.X: Y = Neptune.Y: Z = Neptune.Z
    Case ToPluto:   Altitude = Pluto.Altitude:   Distance = Pluto.Distance:   X = Pluto.X:   Y = Pluto.Y:   Z = Pluto.Z
    Case Else:      Altitude = Sun.Altitude:     Distance = Sun.Distance:     X = Sun.X:     Y = Sun.Y:     Z = Sun.Z
        End Select
    End Sub

Function PlanetGravityMass(Optional ByVal P As TargetType = ToSun) As Double
    Select Case P
    Case ToEarth, ToAir: PlanetGravityMass = Earth.GravityMass
    Case ToMoon:    PlanetGravityMass = Moon.GravityMass
    Case ToMercury: PlanetGravityMass = Mercury.GravityMass
    Case ToVenus:   PlanetGravityMass = Venus.GravityMass
    Case ToMars:    PlanetGravityMass = Mars.GravityMass
    Case ToJupiter: PlanetGravityMass = Jupiter.GravityMass
    Case ToSaturn:  PlanetGravityMass = Saturn.GravityMass
    Case ToUranus:  PlanetGravityMass = Uranus.GravityMass
    Case ToNeptune: PlanetGravityMass = Neptune.GravityMass
    Case ToPluto:   PlanetGravityMass = Pluto.GravityMass
    Case Else:      PlanetGravityMass = Sun.GravityMass
        End Select
    End Function

Function PlanetRadius(Optional ByVal P As TargetType = ToSun) As Double
    Select Case P
    Case ToEarth, ToAir: PlanetRadius = Earth.Radius
    Case ToMoon:    PlanetRadius = Moon.Radius
    Case ToMercury: PlanetRadius = Mercury.Radius
    Case ToVenus:   PlanetRadius = Venus.Radius
    Case ToMars:    PlanetRadius = Mars.Radius
    Case ToJupiter: PlanetRadius = Jupiter.Radius
    Case ToSaturn:  PlanetRadius = Saturn.Radius
    Case ToUranus:  PlanetRadius = Uranus.Radius
    Case ToNeptune: PlanetRadius = Neptune.Radius
    Case ToPluto:   PlanetRadius = Pluto.Radius
    Case Else:      PlanetRadius = Sun.Radius
        End Select
    End Function

Function PlanetAltitude(Optional ByVal P As TargetType = ToNone) As Double
    If P = ToNone Then P = Target
    Select Case P
    Case ToEarth, ToAir: PlanetAltitude = Earth.Altitude
    Case ToMoon:    PlanetAltitude = Moon.Altitude
    Case ToMercury: PlanetAltitude = Mercury.Altitude
    Case ToVenus:   PlanetAltitude = Venus.Altitude
    Case ToMars:    PlanetAltitude = Mars.Altitude
    Case ToJupiter: PlanetAltitude = Jupiter.Altitude
    Case ToSaturn:  PlanetAltitude = Saturn.Altitude
    Case ToUranus:  PlanetAltitude = Uranus.Altitude
    Case ToNeptune: PlanetAltitude = Neptune.Altitude
    Case ToPluto:   PlanetAltitude = Pluto.Altitude
    Case Else:      PlanetAltitude = Sun.Altitude
        End Select
    End Function

Function PlanetSpeed(Optional ByVal P As TargetType = ToNone, _
        Optional ByVal Absolute As Boolean) As Double
    If P = ToNone Then P = Target
    Select Case P
    Case ToEarth, ToAir: If Absolute Then PlanetSpeed = Earth.Speed Else PlanetSpeed = Earth.AirSpeed
    Case ToMoon:    If Absolute Then PlanetSpeed = Moon.Speed Else PlanetSpeed = Moon.AirSpeed
    Case ToMercury: PlanetSpeed = Mercury.Speed
    Case ToVenus:   PlanetSpeed = Venus.Speed
    Case ToMars:    If Absolute Then PlanetSpeed = Mars.Speed Else PlanetSpeed = Mars.AirSpeed
    Case ToJupiter: PlanetSpeed = Jupiter.Speed
    Case ToSaturn:  PlanetSpeed = Saturn.Speed
    Case ToUranus:  PlanetSpeed = Uranus.Speed
    Case ToNeptune: PlanetSpeed = Neptune.Speed
    Case ToPluto:   PlanetSpeed = Pluto.Speed
    Case Else:      PlanetSpeed = Vector3DEx(SpeedX, SpeedY, SpeedZ)
        End Select
    End Function

Sub PlanetPosition(Optional ByVal P As TargetType = ToSun, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Select Case P
    Case ToEarth, ToAir: X = Earth.X: Y = Earth.Y: Z = Earth.Z
    Case ToMoon:    X = Moon.X: Y = Moon.Y: Z = Moon.Z
    Case ToMercury: X = Mercury.X: Y = Mercury.Y: Z = Mercury.Z
    Case ToVenus:   X = Venus.X: Y = Venus.Y: Z = Venus.Z
    Case ToMars:    X = Mars.X: Y = Mars.Y: Z = Mars.Z
    Case ToJupiter: X = Jupiter.X: Y = Jupiter.Y: Z = Jupiter.Z
    Case ToSaturn:  X = Saturn.X: Y = Saturn.Y: Z = Saturn.Z
    Case ToUranus:  X = Uranus.X: Y = Uranus.Y: Z = Uranus.Z
    Case ToNeptune: X = Neptune.X: Y = Neptune.Y: Z = Neptune.Z
    Case ToPluto:   X = Pluto.X: Y = Pluto.Y: Z = Pluto.Z
    Case Else:      X = Sun.X: Y = Sun.Y: Z = Sun.Z
        End Select
    End Sub

Sub GhostPosition(Optional ByVal P As TargetType = ToSun, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Select Case P
    Case ToEarth, ToAir: PlanetXYZEx Earth, (Time + GhostTime) / Day, X, Y, Z
    Case ToMoon:    PlanetXYZEx Moon, (Time + GhostTime) / Day, X, Y, Z
    Case ToMercury: PlanetXYZEx Mercury, (Time + GhostTime) / Day, X, Y, Z
    Case ToVenus:   PlanetXYZEx Venus, (Time + GhostTime) / Day, X, Y, Z
    Case ToMars:    PlanetXYZEx Mars, (Time + GhostTime) / Day, X, Y, Z
    Case ToJupiter: PlanetXYZEx Jupiter, (Time + GhostTime) / Day, X, Y, Z
    Case ToSaturn:  PlanetXYZEx Saturn, (Time + GhostTime) / Day, X, Y, Z
    Case ToUranus:  PlanetXYZEx Uranus, (Time + GhostTime) / Day, X, Y, Z
    Case ToNeptune: PlanetXYZEx Neptune, (Time + GhostTime) / Day, X, Y, Z
    Case ToPluto:   PlanetXYZEx Pluto, (Time + GhostTime) / Day, X, Y, Z
    Case Else:      X = 0: Y = 0: Z = 0
        End Select
    If P = ToMoon Then X = X + Earth.X: Y = Y + Earth.Y: Z = Z + Earth.Z _
    Else X = X + Sun.X: Y = Y + Sun.Y: Z = Z + Sun.Z
    End Sub

Sub GetFont(Optional X As Object, Optional Name As String, _
        Optional ByVal DefaultFont As Boolean = True)
    Dim C As Long, S As String
    If X Is Nothing Then Set X = Setup
    S = GetStr(Name, False): If S = "" Then _
        If DefaultFont Then SetFont X: Exit Sub Else Exit Sub
    With X.Font: .Name = Param(S): .Size = NumS(S): .Bold = NumS(S): .Italic = NumS(S): End With
    C = NumI(S): If C Then X.ForeColor = SystemColor(C)
    End Sub

Sub SetArray(N() As Byte, S As String)
    RtlMoveMemory N(0), ByVal S, Len(S)
    End Sub

Function GetArray(N() As Byte) As String
    GetArray = StrConv(N, vbUnicode)
    End Function

Function IsKey(Optional Key As Long = vbKeyControl) As Boolean
    IsKey = GetKeyState(Key) And &H8000&
    End Function

Function Shift() As Integer
    Shift = -CBool(GetKeyState(vbKeyShift) And &H8000&) _
       - 2 * CBool(GetKeyState(vbKeyControl) And &H8000&) _
       - 4 * CBool(GetKeyState(vbKeyMenu) And &H8000&)
    End Function

'Sub GetRGB(C As RGB, Optional ByVal Color As Long = -1)
'    C.R = Color And &HFF&: C.G = (Color \ &H100&) And &HFF&: C.B = (Color \ &H10000) And &HFF&
'    End Sub

'Sub GetRGBA(C As RGBA, Optional ByVal Color As Long = -1, Optional ByVal Alpha As Long = -1)
'    C.R = Color And &HFF&: C.G = (Color And &HFF00&) \ &H100&: C.B = (Color And &HFF0000) \ &H10000
'    If Alpha >= 0 Then C.A = Alpha Else C.A = ((Color And &HFF000000) \ &H1000000) And &HFF&
'    End Sub

Sub GetRGBA(R As Long, G As Long, B As Long, A As Long, _
        Optional ByVal Color As Long = -1, Optional ByVal Alpha As Long = -1)
    R = Color And &HFF&: G = (Color And &HFF00&) \ &H100&: B = (Color And &HFF0000) \ &H10000
    If Alpha >= 0 Then A = Alpha Else A = ((Color And &HFF000000) \ &H1000000) And &HFF&
    End Sub

Function LogBase2(ByVal N As Long) As Long
    Dim C As Long
    C = &H80000000
    For LogBase2 = 31 To 0 Step -1
        If N And C Then Exit Function
        C = C \ 2
        Next
    End Function

Function ToHex(Optional ByVal N As Long) As String
    ToHex = "&H" & Hex(N) & "&"
    End Function

Function ToHexL(Optional ByVal N As Long, Optional Fill As String = "00000000") As String
    ToHexL = LCase(Hex(N)): N = Len(Fill): If Len(ToHexL) < N Then ToHexL = Left(Fill, N - Len(ToHexL)) & ToHexL
    End Function

Function ToHexU(Optional ByVal N As Long, Optional Fill As String = "00000000") As String
    ToHexU = Hex(N): N = Len(Fill): If Len(ToHexU) < N Then ToHexU = Left(Fill, N - Len(ToHexU)) & ToHexU
    End Function

Function Hex64(N As Long64, Optional Fill As String = "00000000", _
        Optional FillLow As String = "00000000") As String
    Hex64 = Hex(N.L): If Len(Hex64) < 8 Then Hex64 = Left(FillLow, 8 - Len(Hex64)) & Hex64
    Hex64 = Hex(N.H) & Hex64: If Len(Hex64) < Len(Fill) Then Hex64 = Left(Fill, Len(Fill) - Len(Hex64)) & Hex64
    End Function

Function Bin64(N As Long64) As String
    Bin64 = _
        -((N.H And &H80000000) <> 0) & -((N.H And &H40000000) <> 0) & -((N.H And &H20000000) <> 0) & -((N.H And &H10000000) <> 0) & _
        -((N.H And &H8000000) <> 0) & -((N.H And &H4000000) <> 0) & -((N.H And &H2000000) <> 0) & -((N.H And &H1000000) <> 0) & _
        -((N.H And &H800000) <> 0) & -((N.H And &H400000) <> 0) & -((N.H And &H200000) <> 0) & -((N.H And &H100000) <> 0) & _
        -((N.H And &H80000) <> 0) & -((N.H And &H40000) <> 0) & -((N.H And &H20000) <> 0) & -((N.H And &H10000) <> 0) & _
        -((N.H And &H8000&) <> 0) & -((N.H And &H4000&) <> 0) & -((N.H And &H2000&) <> 0) & -((N.H And &H1000&) <> 0) & _
        -((N.H And &H800&) <> 0) & -((N.H And &H400&) <> 0) & -((N.H And &H200&) <> 0) & -((N.H And &H100&) <> 0) & _
        -((N.H And &H80&) <> 0) & -((N.H And &H40&) <> 0) & -((N.H And &H20&) <> 0) & -((N.H And &H10&) <> 0) & _
        -((N.H And &H8&) <> 0) & -((N.H And &H4&) <> 0) & -((N.H And &H2&) <> 0) & -((N.H And &H1&) <> 0) & _
        -((N.L And &H80000000) <> 0) & -((N.L And &H40000000) <> 0) & -((N.L And &H20000000) <> 0) & -((N.L And &H10000000) <> 0) & _
        -((N.L And &H8000000) <> 0) & -((N.L And &H4000000) <> 0) & -((N.L And &H2000000) <> 0) & -((N.L And &H1000000) <> 0) & _
        -((N.L And &H800000) <> 0) & -((N.L And &H400000) <> 0) & -((N.L And &H200000) <> 0) & -((N.L And &H100000) <> 0) & _
        -((N.L And &H80000) <> 0) & -((N.L And &H40000) <> 0) & -((N.L And &H20000) <> 0) & -((N.L And &H10000) <> 0) & _
        -((N.L And &H8000&) <> 0) & -((N.L And &H4000&) <> 0) & -((N.L And &H2000&) <> 0) & -((N.L And &H1000&) <> 0) & _
        -((N.L And &H800&) <> 0) & -((N.L And &H400&) <> 0) & -((N.L And &H200&) <> 0) & -((N.L And &H100&) <> 0) & _
        -((N.L And &H80&) <> 0) & -((N.L And &H40&) <> 0) & -((N.L And &H20&) <> 0) & -((N.L And &H10&) <> 0) & _
        -((N.L And &H8&) <> 0) & -((N.L And &H4&) <> 0) & -((N.L And &H2&) <> 0) & -((N.L And &H1&) <> 0)
    End Function

Function BitPosEx(ByVal N As Long, _
        Optional ByVal Mask1 As Long = -1, Optional ByVal Mask2 As Long = -1, _
        Optional ByVal Size1 As Long) As Long
    BitPosEx = BitCount(Mask1): If N < BitPosEx Then BitPosEx = BitPos(N, Mask1) _
        Else BitPosEx = BitPos(N - BitPosEx, Mask2): If BitPosEx >= 0 Then BitPosEx = BitPosEx + Size1
    End Function

Function BitPos(ByVal N As Long, Optional ByVal Mask As Long = -1) As Long
                         If Mask And &H1& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H2& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H4& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H8& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H10& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H20& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H40& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H80& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H100& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H200& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H400& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H800& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H1000& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H2000& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H4000& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H8000& Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H10000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H20000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H40000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H80000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H100000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H200000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H400000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H800000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H1000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H2000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H4000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H8000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H10000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H20000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H40000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = BitPos + 1: If Mask And &H80000000 Then N = N - 1: If N < 0 Then Exit Function
    BitPos = -1
    End Function

Function BitCount(Optional ByVal Mask As Long) As Long
    Do
        BitCount = BitCount + (Mask And 1)
        If Mask >= 0 Then Mask = Mask \ 2 Else Mask = ((Mask \ 2) - 1) And &H7FFFFFFF
        Loop While Mask
    End Function

Function Round(N As Double) As Double
    Round = Int(N + 0.5)
    End Function

Function RoundInt(N As Double) As Long
    RoundInt = Int(N + 0.5)
    End Function

Function RoundUp(N As Double) As Double
    RoundUp = Int(N + 0.999999999999999)
    End Function

Function RoundUpBase2(ByVal N As Long) As Long
    If N > 0 Then RoundUpBase2 = 2 ^ Int(VBA.Log(N) / Log2): _
        If N > RoundUpBase2 Then RoundUpBase2 = RoundUpBase2 * 2
    End Function

Function RoundEx(N As Double, Optional D As Double = Hour) As Double
    RoundEx = Int(N / D + 0.5) * D
    End Function

Function RoundIntEx(N As Double, Optional ByVal D As Long = Hour) As Long
    RoundIntEx = Int(N / D + 0.5) * D
    End Function

Function RoundInt2Ex(ByVal N As Long, Optional ByVal D As Long = Hour) As Long
    RoundInt2Ex = Int(N \ D + 0.5) * D
    End Function

Function RoundN(N As Double, Optional ByVal C As Long = 3) As Double
    If N <> 0 Then RoundN = 10 ^ Int(VBA.Log(Abs(N)) / Log10 - C + 1): _
        RoundN = Int(N / RoundN + 0.5) * RoundN
    End Function

Function IntEx(N As Double, Optional D As Double = Hour) As Double
    IntEx = Int(N / D) * D
    End Function

Function IntIntEx(N As Double, Optional D As Long = Hour) As Long
    IntIntEx = Int(N / D) * D
    End Function

Function IntInt2Ex(ByVal N As Long, Optional D As Long = Hour) As Long
    IntInt2Ex = (N \ D) * D
    End Function

Function IntN(N As Double, Optional ByVal C As Long = 3) As Double
    If N <> 0 Then IntN = 10 ^ Int(VBA.Log(Abs(N)) / Log10 - C + 1): _
        IntN = Int(N / IntN) * IntN
    End Function

Function Limit(ByVal N As Long, Optional ByVal Min As Long, Optional ByVal Max As Long = 100) As Long
    If N < Min Then Limit = Min Else If N > Max Then Limit = Max Else Limit = N
    End Function

Function LimitMax(N As Double, Optional Max As Double = 1) As Double
    If N > Max Then LimitMax = Max Else LimitMax = N
    End Function

Function LimitMin(N As Double, Optional Min As Double) As Double
    If N < Min Then LimitMin = Min Else LimitMin = N
    End Function

Function IsLimit(ByVal N As Long, Optional ByVal Min As Long, Optional ByVal Max As Long = 100) As Boolean
    If N >= Min And N <= Max Then IsLimit = True
    End Function

Function LimitDbl(N As Double, Optional Min As Double, Optional Max As Double = 100) As Double
    If N < Min Then LimitDbl = Min Else If N > Max Then LimitDbl = Max Else LimitDbl = N
    End Function

Function IsLimitDbl(ByVal N As Double, Optional ByVal Min As Double, Optional ByVal Max As Double = 1) As Boolean
    If N >= Min And N <= Max Then IsLimitDbl = True
    End Function

Function IsDblLimit(V As Double, ByVal N As Double, _
        Optional ByVal Min As Double, Optional ByVal Max As Double = Infinite) As Boolean
    If N < Min Then V = Min: IsDblLimit = True _
    Else If N > Max Then V = Max: IsDblLimit = True _
    Else V = N
    End Function

Function IsDblEqual(V As Double, ByVal N As Double, _
        Optional ByVal Min As Double, Optional ByVal Max As Double = Infinite) As Boolean
    If N <= Min Then V = Min: IsDblEqual = True _
    Else If N >= Max Then V = Max: IsDblEqual = True _
    Else V = N
    End Function

Function IsDblEqualEx(V As Double, ByVal N As Double, _
        Optional ByVal Min As Double, Optional ByVal Max As Double = Infinite, _
        Optional ByVal SetMin As Double, Optional ByVal SetMax As Double = Infinite) As Boolean
    If N <= Min Then V = SetMin: IsDblEqualEx = True _
    Else If N >= Max Then V = SetMax: IsDblEqualEx = True _
    Else V = N
    End Function

Sub SgnInt(N As Long, Optional ByVal Value As Long)
    If N >= 0 Then N = Value Else N = -Value
    End Sub

Sub SetSgnInt(N As Long, Optional ByVal Negative As Boolean)
    If Negative Xor N < 0 Then N = -N
    End Sub

Function RoundColor(N As Double) As Long
    If N < 0 Then RoundColor = 0 Else If N > 255 Then RoundColor = 255 Else RoundColor = Int(N + 0.5)
    End Function

Function Wrap(ByVal N As Long, Optional ByVal Min As Long, Optional ByVal Max As Long = 100) As Long
    If N < Min Then Wrap = Max Else If N > Max Then Wrap = Min Else Wrap = N
    End Function

Function Choose(ByVal Index As Long, ParamArray Items() As Variant) As Variant
    Choose = Items(Index)
    End Function

Function RampUp(Value As Variant, ParamArray Items() As Variant) As Variant
    Dim N As Long
    For N = 1 To UBound(Items) - 1
        If Value < Items(N) Then Exit For
        Next
    RampUp = Items(N)
    End Function

Function RampDown(Value As Variant, ParamArray Items() As Variant) As Variant
    Dim N As Long
    For N = UBound(Items) To 1 Step -1
        If Value > Items(N) Then Exit For
        Next
    RampDown = Items(N)
    End Function

Function Find(Value As Variant, ParamArray Items() As Variant) As Long
    For Find = 0 To UBound(Items)
        If Value = Items(Find) Then Exit Function
        Next
    Find = -1
    End Function

Sub GetNumItems(Text As String, Separator As String, ParamArray Items() As Variant)
    Dim N As Long, S() As String
    On Error Resume Next
    S = Split(Text, Separator)
    For N = 0 To UBound(S): Items(N) = Val(S(N)): Next
    On Error GoTo 0
    End Sub

Sub GetStringItems(Text As String, Separator As String, ParamArray Items() As Variant)
    Dim N As Long, S() As String
    On Error Resume Next
    S = Split(Text, Separator)
    For N = 0 To UBound(S): Items(N) = S(N): Next
    On Error GoTo 0
    End Sub

Function SqrDbl(N As Double) As Double
    If N < 0 Then SqrDbl = -N * N Else SqrDbl = N * N
    End Function

Function IIf(ByVal Expression As Boolean, TruePart As Variant, FalsePart As Variant) As Variant
    If Expression Then IIf = TruePart Else IIf = FalsePart
    End Function

Function IfText(ByVal Expression As Boolean, Text As String) As String
    If Expression Then IfText = Text
    End Function

Function IfDbl(ByVal Expression As Boolean, _
        Optional TruePart As Double, Optional FalsePart As Double) As Double
    If Expression Then IfDbl = TruePart Else IfDbl = FalsePart
    End Function

Function IfSng(ByVal Expression As Boolean, _
        Optional TruePart As Single, Optional FalsePart As Single) As Single
    If Expression Then IfSng = TruePart Else IfSng = FalsePart
    End Function

Function IfInt(ByVal Expression As Boolean, _
        Optional ByVal TruePart As Long, Optional ByVal FalsePart As Long) As Long
    If Expression Then IfInt = TruePart Else IfInt = FalsePart
    End Function

Function IfShort(ByVal Expression As Boolean, _
        Optional ByVal TruePart As Integer, Optional ByVal FalsePart As Integer) As Integer
    If Expression Then IfShort = TruePart Else IfShort = FalsePart
    End Function

Function IsMax(A As Double, B As Double) As Double
    If A > B Then IsMax = A Else IsMax = B
    End Function

Function IsMax3(A As Double, B As Double, C As Double) As Double
    If A > B And A > C Then IsMax3 = A Else If B > A And B > C Then IsMax3 = B Else IsMax3 = C
    End Function

Function IsMaxAbs(A As Double, B As Double) As Double
    If Abs(A) > Abs(B) Then IsMaxAbs = A Else IsMaxAbs = B
    End Function

Function IsMaxAbs3(A As Double, B As Double, C As Double) As Double
    If Abs(A) >= Abs(B) And Abs(A) >= Abs(C) Then IsMaxAbs3 = A _
    Else If Abs(B) >= Abs(A) And Abs(B) >= Abs(C) Then IsMaxAbs3 = B _
    Else IsMaxAbs3 = C
    End Function

Function IsMaxInt(ByVal A As Long, ByVal B As Long) As Long
    If A > B Then IsMaxInt = A Else IsMaxInt = B
    End Function

Function IsMaxX(ParamArray Items() As Variant) As Variant
    Dim N As Long
    IsMaxX = Items(0)
    For N = 1 To UBound(Items)
        If Items(N) > IsMaxX Then IsMaxX = Items(N)
        Next
    End Function

Function IsMin(A As Double, B As Double) As Double
    If A < B Then IsMin = A Else IsMin = B
    End Function

Function IsMin3(A As Double, B As Double, C As Double) As Double
    If A <= B And A <= C Then IsMin3 = A Else If B <= A And B <= C Then IsMin3 = B Else IsMin3 = C
    End Function

Function IsMinInt(ByVal A As Long, ByVal B As Long) As Long
    If A < B Then IsMinInt = A Else IsMinInt = B
    End Function

Function IsMinX(ParamArray Items() As Variant) As Variant
    Dim N As Long
    IsMinX = Items(0)
    For N = 1 To UBound(Items)
        If Items(N) < IsMinX Then IsMinX = Items(N)
        Next
    End Function

Function IsInside( _
        ByVal PointX As Long, ByVal PointY As Long, _
        ByVal ZoneX As Long, ByVal ZoneY As Long, _
        Optional ByVal ZoneW As Long, Optional ByVal ZoneH As Long, _
        Optional ByVal Border As Long) As Boolean
    IsInside = _
        PointX >= ZoneX - Border And PointX < ZoneX + ZoneW + Border And _
        PointY >= ZoneY - Border And PointY < ZoneY + ZoneH + Border
    End Function

Function IsInsidePic( _
        ByVal PointX As Long, ByVal PointY As Long, Pic As PictureX, _
        Optional ByVal Border As Long) As Boolean
    IsInsidePic = _
        PointX >= Pic.X - Border And PointX < Pic.X + Pic.W + Border And _
        PointY >= Pic.Y - Border And PointY < Pic.Y + Pic.H + Border
    End Function

Function TextModified(T As Edit, S As String) As Boolean
    If T <> S Then T = S: TextModified = True
    End Function
    
Function Mask(ByVal Num As Long, Optional ByVal Value As Boolean = True, _
        Optional ByVal BitMask As Long = 1) As Long
    If Value Then Mask = Num Or BitMask Else Mask = Num And Not BitMask
    End Function

Function GetMask(Optional ByVal Value As Boolean = True, Optional ByVal BitMask As Long = 1) As Long
    If Value Then GetMask = BitMask
    End Function

Sub SetMask(Num As Long, Optional ByVal Value As Boolean = True, _
        Optional ByVal BitMask As Long = 1)
    If Value Then Num = Num Or BitMask Else Num = Num And Not BitMask
    End Sub

Sub SetByte(Num As Byte, Optional ByVal Value As Boolean = True, _
        Optional ByVal BitMask As Byte = 1)
    If Value Then Num = Num Or BitMask Else Num = Num And Not BitMask
    End Sub

Sub RGB2HSL( _
        Optional ByVal R As Double, Optional ByVal G As Double, Optional ByVal B As Double, _
        Optional H As Double, Optional S As Double, Optional L As Double, _
        Optional ByVal AdjustRGB As Boolean = True, Optional ByVal AdjustHSL As Boolean = True)
    'RGB->HSL .. (Red, Green, Blue) -> (Hue, Saturation, Lightness)
    Dim P As Double, Q As Double, Min As Double, Max As Double
    If AdjustRGB Then R = R / 255: G = G / 255: B = B / 255
    Min = R
    If Min > G Then Min = G
    If Min > B Then Min = B
    Max = R
    If Max < G Then Max = G
    If Max < B Then Max = B
    If Max = Min Then
        H = 0: S = 0: L = Max
    Else
        P = Max + Min: Q = Max - Min
        L = P / 2: If L > 0.5 Then S = Q / (2 - P) Else S = Q / P
             If Max = B Then H = (R - G) / Q + 4 _
        Else If Max = G Then H = (B - R) / Q + 2 _
        Else If Max = R Then H = (G - B) / Q: If G < B Then H = H + 6
        H = H / 6
        End If
    If AdjustHSL Then H = H * 360: S = S * 100: L = L * 100
    End Sub

Sub HSL2RGB( _
        Optional ByVal H As Double, Optional ByVal S As Double, Optional ByVal L As Double, _
        Optional R As Double, Optional G As Double, Optional B As Double, _
        Optional ByVal AdjustHSL As Boolean = True, Optional ByVal AdjustRGB As Boolean = True)
    'HSL->RGB .. (Hue, Saturation, Lightness) -> (Red, Green, Blue)
    Dim P As Double, Q As Double, T As Double
    If AdjustHSL Then H = H / 360: L = L / 100: S = S / 100
    H = (H - Int(H)) * 6
    If L < 0.5 Then Q = L * (S + 1) Else Q = L + S - L * S
    P = L * 2 - Q: T = Q - P
    R = H + 2
         If R < 0 Then R = R + 6
         If R > 6 Then R = R - 6
         If R < 1 Then R = R * T + P _
    Else If R < 3 Then R = Q _
    Else If R < 4 Then R = (4 - R) * T + P _
                  Else R = P
    G = H
         If G < 0 Then G = G + 6
         If G > 6 Then G = G - 6
         If G < 1 Then G = G * T + P _
    Else If G < 3 Then G = Q _
    Else If G < 4 Then G = (4 - G) * T + P _
                  Else G = P
    B = H - 2
         If B < 0 Then B = B + 6
         If B > 6 Then B = B - 6
         If B < 1 Then B = B * T + P _
    Else If B < 3 Then B = Q _
    Else If B < 4 Then B = (4 - B) * T + P _
                  Else B = P
    If AdjustRGB Then R = R * 255: G = G * 255: B = B * 255
    End Sub

Sub Initialize(A As Matrix4)
    A.XX = 1: A.XY = 0: A.XZ = 0: A.XW = 0
    A.YX = 0: A.YY = 1: A.YZ = 0: A.YW = 0
    A.ZX = 0: A.ZY = 0: A.ZZ = 1: A.ZW = 0
    A.WX = 0: A.WY = 0: A.WZ = 0: A.WW = 1
    End Sub

Sub RotateX(A As Matrix4, Optional Angle As Double, Optional ByVal Reverse As Boolean)
    'Rotate matrix over X axis
    Dim M As Matrix4, S As Double, C As Double
    If Angle <> 0 Then S = Sin(Angle): C = Cos(Angle) Else Exit Sub
    If Reverse Then
        M.YX = C * A.YX - S * A.ZX: M.YY = C * A.YY - S * A.ZY
        M.YZ = C * A.YZ - S * A.ZZ: M.ZX = S * A.YX + C * A.ZX
        M.ZY = S * A.YY + C * A.ZY: M.ZZ = S * A.YZ + C * A.ZZ
        A.YX = M.YX: A.YY = M.YY: A.YZ = M.YZ: A.ZX = M.ZX: A.ZY = M.ZY: A.ZZ = M.ZZ
    Else
        M.XY = A.XY * C + A.XZ * S: M.XZ = -A.XY * S + A.XZ * C
        M.YY = A.YY * C + A.YZ * S: M.YZ = -A.YY * S + A.YZ * C
        M.ZY = A.ZY * C + A.ZZ * S: M.ZZ = -A.ZY * S + A.ZZ * C
        A.XY = M.XY: A.XZ = M.XZ: A.YY = M.YY: A.YZ = M.YZ: A.ZY = M.ZY: A.ZZ = M.ZZ
        End If
    End Sub

Sub RotateY(A As Matrix4, Optional Angle As Double, Optional ByVal Reverse As Boolean)
    'Rotate matrix over Y axis
    Dim M As Matrix4, S As Double, C As Double
    If Angle <> 0 Then S = Sin(Angle): C = Cos(Angle) Else Exit Sub
    If Reverse Then
        M.XX = C * A.XX - S * A.ZX: M.XY = C * A.XY - S * A.ZY
        M.XZ = C * A.XZ - S * A.ZZ: M.ZX = S * A.XX + C * A.ZX
        M.ZY = S * A.XY + C * A.ZY: M.ZZ = S * A.XZ + C * A.ZZ
        A.XX = M.XX: A.XY = M.XY: A.XZ = M.XZ: A.ZX = M.ZX: A.ZY = M.ZY: A.ZZ = M.ZZ
    Else
        M.XX = A.XX * C + A.XZ * S: M.XZ = -A.XX * S + A.XZ * C
        M.YX = A.YX * C + A.YZ * S: M.YZ = -A.YX * S + A.YZ * C
        M.ZX = A.ZX * C + A.ZZ * S: M.ZZ = -A.ZX * S + A.ZZ * C
        A.XX = M.XX: A.XZ = M.XZ: A.YX = M.YX: A.YZ = M.YZ: A.ZX = M.ZX: A.ZZ = M.ZZ
        End If
    End Sub

Sub RotateZ(A As Matrix4, Optional Angle As Double, Optional ByVal Reverse As Boolean)
    'Rotate matrix over Z axis
    Dim M As Matrix4, S As Double, C As Double
    If Angle <> 0 Then S = Sin(Angle): C = Cos(Angle) Else Exit Sub
    If Reverse Then
        M.XX = C * A.XX - S * A.YX: M.XY = C * A.XY - S * A.YY
        M.XZ = C * A.XZ - S * A.YZ: M.YX = S * A.XX + C * A.YX
        M.YY = S * A.XY + C * A.YY: M.YZ = S * A.XZ + C * A.YZ
        A.XX = M.XX: A.XY = M.XY: A.XZ = M.XZ: A.YX = M.YX: A.YY = M.YY: A.YZ = M.YZ
    Else
        M.XX = A.XX * C + A.XY * S: M.XY = -A.XX * S + A.XY * C
        M.YX = A.YX * C + A.YY * S: M.YY = -A.YX * S + A.YY * C
        M.ZX = A.ZX * C + A.ZY * S: M.ZY = -A.ZX * S + A.ZY * C
        A.XX = M.XX: A.XY = M.XY: A.YX = M.YX: A.YY = M.YY: A.ZX = M.ZX: A.ZY = M.ZY
        End If
    End Sub

Sub RotateMatrix(A As Matrix4, B As Matrix4, Optional ByVal Reverse As Boolean = True)
    'Multiply matrices: A=A*B (Reverse=False) or B=A*B (Reverse=True)
    Dim M As Matrix4
    M.XX = A.XX * B.XX + A.XY * B.YX + A.XZ * B.ZX
    M.XY = A.XX * B.XY + A.XY * B.YY + A.XZ * B.ZY
    M.XZ = A.XX * B.XZ + A.XY * B.YZ + A.XZ * B.ZZ
    M.YX = A.YX * B.XX + A.YY * B.YX + A.YZ * B.ZX
    M.YY = A.YX * B.XY + A.YY * B.YY + A.YZ * B.ZY
    M.YZ = A.YX * B.XZ + A.YY * B.YZ + A.YZ * B.ZZ
    M.ZX = A.ZX * B.XX + A.ZY * B.YX + A.ZZ * B.ZX
    M.ZY = A.ZX * B.XY + A.ZY * B.YY + A.ZZ * B.ZY
    M.ZZ = A.ZX * B.XZ + A.ZY * B.YZ + A.ZZ * B.ZZ
    If Reverse Then
        B.XX = M.XX: B.XY = M.XY: B.XZ = M.XZ
        B.YX = M.YX: B.YY = M.YY: B.YZ = M.YZ
        B.ZX = M.ZX: B.ZY = M.ZY: B.ZZ = M.ZZ
    Else
        A.XX = M.XX: A.XY = M.XY: A.XZ = M.XZ
        A.YX = M.YX: A.YY = M.YY: A.YZ = M.YZ
        A.ZX = M.ZX: A.ZY = M.ZY: A.ZZ = M.ZZ
        End If
    End Sub

Sub RotateVector(A As Matrix4, Optional Angle As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    'Rotate matrix over a cartesian vector: A=Matrix to rotate, A=angle in radians, XYZ=Vector
    Dim B As Matrix4, M As Matrix4, S As Double, C As Double, T As Double
    If Angle <> 0 Then S = Sin(Angle): C = Cos(Angle): T = 1 - C Else Exit Sub
    B.XX = T * X * X + C: B.XY = T * X * Y - S * Z: B.XZ = T * X * Z + S * Y
    B.YX = T * X * Y + S * Z: B.YY = T * Y * Y + C: B.YZ = T * Y * Z - S * X
    B.ZX = T * X * Z - S * Y: B.ZY = T * Y * Z + S * X: B.ZZ = T * Z * Z + C
    M.XX = A.XX * B.XX + A.XY * B.YX + A.XZ * B.ZX
    M.XY = A.XX * B.XY + A.XY * B.YY + A.XZ * B.ZY
    M.XZ = A.XX * B.XZ + A.XY * B.YZ + A.XZ * B.ZZ
    M.YX = A.YX * B.XX + A.YY * B.YX + A.YZ * B.ZX
    M.YY = A.YX * B.XY + A.YY * B.YY + A.YZ * B.ZY
    M.YZ = A.YX * B.XZ + A.YY * B.YZ + A.YZ * B.ZZ
    M.ZX = A.ZX * B.XX + A.ZY * B.YX + A.ZZ * B.ZX
    M.ZY = A.ZX * B.XY + A.ZY * B.YY + A.ZZ * B.ZY
    M.ZZ = A.ZX * B.XZ + A.ZY * B.YZ + A.ZZ * B.ZZ
    A.XX = M.XX: A.XY = M.XY: A.XZ = M.XZ
    A.YX = M.YX: A.YY = M.YY: A.YZ = M.YZ
    A.ZX = M.ZX: A.ZY = M.ZY: A.ZZ = M.ZZ
    End Sub

Sub RotatePoint(A As Matrix4, Optional X As Double, Optional Y As Double, Optional Z As Double = 1, _
        Optional ByVal Reverse As Boolean)
    'Rotate cartesian point using a matrix (Reverse=False) or reversed matrix (Reverse=True)
    Dim X1 As Double, Y1 As Double, Z1 As Double
    If Reverse Then
        X1 = X * A.XX + Y * A.YX + Z * A.ZX
        Y1 = X * A.XY + Y * A.YY + Z * A.ZY
        Z1 = X * A.XZ + Y * A.YZ + Z * A.ZZ
    Else
        X1 = X * A.XX + Y * A.XY + Z * A.XZ
        Y1 = X * A.YX + Y * A.YY + Z * A.YZ
        Z1 = X * A.ZX + Y * A.ZY + Z * A.ZZ
        End If
    X = X1: Y = Y1: Z = Z1
    End Sub

Sub ReverseMatrix(A As Matrix4)
    'Reverse rotation matrix swapping rows and columns
    Dim N As Double
    N = A.XY: A.XY = A.YX: A.YX = N: A.XW = -A.XW
    N = A.YZ: A.YZ = A.ZY: A.ZY = N: A.YW = -A.YW
    N = A.XZ: A.XZ = A.ZX: A.ZX = N: A.ZW = -A.ZW
    End Sub

Sub ReverseMultMatrix(A As Matrix4)
    'Reverse rotation matrix swapping rows and columns and multiply OpenGL matrix
    Dim B As Matrix4
    B.XX = A.XX: B.XY = A.YX: B.XZ = A.ZX: B.XW = A.XW
    B.YX = A.XY: B.YY = A.YY: B.YZ = A.ZY: B.YW = A.YW
    B.ZX = A.XZ: B.ZY = A.YZ: B.ZZ = A.ZZ: B.ZW = A.ZW
    B.WX = A.WX: B.WY = A.WY: B.WZ = A.WZ: B.WW = A.WW
    glMultMatrixd B.XX
    End Sub

Sub Vector2Matrix(A As Matrix4, Optional X As Double, Optional Y As Double, Optional Z As Double)
    Initialize A
    RotateX A, ATan3(X, Y, Z) + A180
    RotateZ A, ATan(X, Y) + A180
    End Sub

Sub Rotate(A As Double, Optional X As Double, Optional Y As Double)
    'A=angle in radians, XY=point to rotate
    Dim X2 As Double, SinA As Double, CosA As Double
    If A <> 0 Then SinA = Sin(A): CosA = Cos(A): _
        X2 = X * CosA - Y * SinA: Y = X * SinA + Y * CosA: X = X2 'X=SinA Y=CosA
    End Sub

Sub RotateEx(SinA As Double, CosA As Double, Optional X As Double, Optional Y As Double)
    'SinA/CosA=sin/cos of angle, XY=point to rotate
    Dim X2 As Double
    X2 = X * CosA - Y * SinA: Y = X * SinA + Y * CosA: X = X2 'X=SinA Y=CosA
    End Sub

Sub Rotate3D( _
        A As Double, SinA As Double, CosA As Double, _
        X As Double, Y As Double, Z As Double, _
        U As Double, V As Double, W As Double, _
        U2 As Double, V2 As Double, W2 As Double)
    'A=angle in radians, XYZ=vector used to rotate the point, UVW=point rotated, UVW2=point to rotate
    If A <> 0 Then SinA = Sin(A): CosA = 1 - Cos(A) Else SinA = 0: CosA = 0: Exit Sub
    U = U2 * ((X * X - 1) * CosA + 1) + V2 * (X * Y * CosA + Z * SinA) + W2 * (X * Z * CosA - Y * SinA)
    V = U2 * (X * Y * CosA - Z * SinA) + V2 * ((Y * Y - 1) * CosA + 1) + W2 * (Y * Z * CosA + X * SinA)
    W = U2 * (X * Z * CosA + Y * SinA) + V2 * (Y * Z * CosA - X * SinA) + W2 * ((Z * Z - 1) * CosA + 1)
    End Sub

Function Vector3DEx( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal Normalize As Boolean) As Double
    'Returns a vector defined by X, Y and Z components
    Vector3DEx = Sqr(X * X + Y * Y + Z * Z)
    If Normalize Then X = X / Vector3DEx: Y = Y / Vector3DEx: Z = Z / Vector3DEx
    End Function

Sub Vector3D( _
        Optional X1 As Double, Optional Y1 As Double, Optional Z1 As Double, _
        Optional X2 As Double, Optional Y2 As Double, Optional Z2 As Double)
    'Stores the normalized vector defined by X1, Y1 and Z1 in X2, Y2 and Z2
    Dim H As Double
    H = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): X2 = X1 / H: Y2 = Y1 / H: Z2 = Z1 / H
    End Sub

Function Angle3D( _
        Optional X1 As Double, Optional Y1 As Double, Optional Z1 As Double, _
        Optional X2 As Double, Optional Y2 As Double, Optional Z2 As Double, _
        Optional ByVal Normalize1 As Boolean, Optional ByVal Normalize2 As Boolean) As Double
    'Returns the angle in radians between 2 normalized vectors
    Dim X3 As Double, Y3 As Double, Z3 As Double, H1 As Double
    If Normalize1 Then H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H1 <> 0 Then _
        X1 = X1 / H1: Y1 = Y1 / H1: Z1 = Z1 / H1 Else Exit Function
    If Normalize2 Then H1 = Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2): If H1 <> 0 Then _
        X2 = X2 / H1: Y2 = Y2 / H1: Z2 = Z2 / H1 Else Exit Function
    'X3 = Y1 * Z2 - Z1 * Y2: Y3 = Z1 * X2 - X1 * Z2: Z3 = X1 * Y2 - Y1 * X2 'Cross product
    'H1 = X3 * X3 + Y3 * Y3 + Z3 * Z3 'Squared vector
    'If H1 >= 1 Then Angle3D = A90 Else If H1 <= -1 Then Angle3D = -A90 Else Angle3D = Atn(Sqr(H1 / (1 - H1))) 'ASin
    X3 = X1 - X2: Y3 = Y1 - Y2: Z3 = Z1 - Z2 'Vector subtraction
    H1 = (X3 * X3 + Y3 * Y3 + Z3 * Z3) / 4 'Normalize to a squared vector
    If H1 <= 0 Then Angle3D = 0 Else If H1 >= 1 Then Angle3D = A180 _
    Else Angle3D = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
    End Function

Sub Interpolate( _
        Optional T As Double, Optional T1 As Double, Optional T2 As Double, _
        Optional X As Double, Optional X1 As Double, Optional X2 As Double)
    'Interpolate two points X1, X2 to X, given a pointer T and two reference points T1, T2
    Dim R1 As Double, R2 As Double
    If T <= T1 Or T1 = T2 Then X = X1 _
    Else If T >= T2 Then X = X2 _
    Else R2 = (T - T1) / (T2 - T1): R1 = 1 - R2: X = X1 * R1 + X2 * R2
    End Sub

Sub Interpolate2D( _
        Optional T As Double, Optional T1 As Double, Optional T2 As Double, _
        Optional X As Double, Optional Y As Double, _
        Optional X1 As Double, Optional Y1 As Double, _
        Optional X2 As Double, Optional Y2 As Double)
    'Interpolate two 2D points XYZ1, XYZ2 to XYZ, given a pointer T and two reference points T1, T2
    Dim R1 As Double, R2 As Double
    If T <= T1 Or T1 = T2 Then X = X1: Y = Y1 _
    Else If T >= T2 Then X = X2: Y = Y2 _
    Else R2 = (T - T1) / (T2 - T1): R1 = 1 - R2: X = X1 * R1 + X2 * R2: Y = Y1 * R1 + Y2 * R2
    End Sub

Sub Interpolate3D( _
        Optional T As Double, Optional T1 As Double, Optional T2 As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional X1 As Double, Optional Y1 As Double, Optional Z1 As Double, _
        Optional X2 As Double, Optional Y2 As Double, Optional Z2 As Double)
    'Interpolate two 3D points XYZ1, XYZ2 to XYZ, given a pointer T and two reference points T1, T2
    Dim R1 As Double, R2 As Double
    If T <= T1 Or T1 = T2 Then X = X1: Y = Y1: Z = Z1 _
    Else If T >= T2 Then X = X2: Y = Y2: Z = Z2 _
    Else R2 = (T - T1) / (T2 - T1): R1 = 1 - R2: _
        X = X1 * R1 + X2 * R2: Y = Y1 * R1 + Y2 * R2: Z = Z1 * R1 + Z2 * R2
    End Sub

Sub Normal3D(N As Vertex3, A As Vertex3, B As Vertex3, C As Vertex3, _
        Optional ByVal GetArea As Boolean = True, Optional TotalArea As Double, _
        Optional TotalEmissivity As Double, Optional Emissivity As Double)
    'Stores in N the cross product vector from points A->B and A->C
    'Optionally calculates the area and the emissivity of the triangle ABC
    Dim X1 As Single, Y1 As Single, Z1 As Single, _
        X2 As Single, Y2 As Single, Z2 As Single, _
        H1 As Double
    X1 = B.X - A.X: Y1 = B.Y - A.Y: Z1 = B.Z - A.Z 'Vector A->B
    'H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H1 > 0 Then _
        X1 = X1 / H1: Y1 = Y1 / H1: Z1 = Z1 / H1 'Normalize
    X2 = C.X - A.X: Y2 = C.Y - A.Y: Z2 = C.Z - A.Z 'Vector A->C
    'H1 = Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2): If H1 > 0 Then _
        X2 = X2 / H1: Y2 = Y2 / H1: Z2 = Z2 / H1 'Normalize
    N.X = Y1 * Z2 - Z1 * Y2: N.Y = Z1 * X2 - X1 * Z2: N.Z = X1 * Y2 - Y1 * X2 'Cross product
    If GetArea Then H1 = Sqr(N.X * N.X + N.Y * N.Y + N.Z * N.Z) / 2: _
        TotalArea = TotalArea + H1: TotalEmissivity = TotalEmissivity + H1 * Emissivity 'Calculate total area and emissivity
    'H1 = Sqr(N.X * N.X + N.Y * N.Y + N.Z * N.Z): If H1 > 0 Then _
        N.X = N.X / H1: N.Y = N.Y / H1: N.Z = N.Z / H1 'Normalize
    End Sub

Function Sqrt(N As Double) As Double
    If N > 0 Then Sqrt = Sqr(N)
    End Function

Function ASin(N As Double) As Double 'ASin(X)=ATan(X/Sqr(1-X²))
    If N >= 1 Then ASin = A90 Else If N <= -1 Then ASin = -A90 Else ASin = Atn(N / Sqr(1 - N * N))
    End Function

Function ACos(N As Double) As Double 'ACos(X)=ATan(Sqr(1-X²)/X)
    If N > 0 Then If N >= 1 Then ACos = 0 Else ACos = Atn(Sqr(1 - N * N) / N) _
    Else If N < 0 Then If N <= -1 Then ACos = A180 Else ACos = Atn(Sqr(1 - N * N) / N) + A180 _
    Else ACos = A90
    End Function

Function ATan(X As Double, Y As Double) As Double
    'X=1 Y=0 A=-90º .. X=0 Y=1 A=-180º .. X=-1 Y=0 A=90º .. X=0 Y=-1 A=0º .. A+=CW
    If Y = 0 Then
        If X < 0 Then ATan = A90 Else If X > 0 Then ATan = -A90
    Else
        ATan = Atn(X / Y)
        If Y > 0 Then If X < 0 Then ATan = ATan + A180 Else ATan = ATan - A180
        End If
    End Function

Function ATan3(X As Double, Y As Double, Z As Double) As Double
    If Z = 0 Then ATan3 = -A90 _
    Else ATan3 = Atn(Sqr(X * X + Y * Y) / Z): If Z > 0 Then _
        If ATan3 < 0 Then ATan3 = ATan3 + A180 Else ATan3 = ATan3 - A180
    End Function

Function ATanEx(X As Double, Y As Double) As Double
    'X=1 Y=0 A=90º .. X=0 Y=1 A=0º .. X=-1 Y=0 A=-90º .. X=0 Y=-1 A=-180º .. A+=CW
    If Y = 0 Then
        If X > 0 Then ATanEx = A90 Else If X < 0 Then ATanEx = -A90
    Else
        ATanEx = Atn(X / Y)
        If Y < 0 Then If X < 0 Then ATanEx = ATanEx - A180 Else If X > 0 Then ATanEx = ATanEx + A180
        End If
    End Function

Function ATanD(X As Double, Y As Double) As Double
    'X=1 Y=0 A=0º .. X=0 Y=1 A=90º .. X=-1 Y=0 A=180º .. X=0 Y=-1 A=270º .. A+=CCW
    If X = 0 Then
        If Y > 0 Then ATanD = A90 Else If Y < 0 Then ATanD = A270
    Else
        ATanD = Atn(Y / X)
        If X < 0 Then ATanD = ATanD + A180 Else If Y < 0 Then ATanD = ATanD + A360
        End If
    End Function

Function Sec(X As Double) As Double     'Sec(X) = 1 / Cos(X)
    Sec = 1 / Cos(X)
    End Function

Function CoSec(X As Double) As Double   'CoSec(X) = 1 / Sin(X)
    CoSec = 1 / Sin(X)
    End Function

Function CoTan(X As Double) As Double   'CoTan(X) = 1 / Tan(X)
    CoTan = 1 / Tan(X)
    End Function

Function ASec(X As Double) As Double    'ASec(X) = Atn(X / Sqr(X * X - 1)) + Sgn((X) - 1) * (2 * Atn(1))
    ASec = Atn(X / Sqr(X * X - 1)) + Sgn((X) - 1) * (2 * Atn(1))
    End Function

Function ACoSec(X As Double) As Double  'ACoSec(X) = Atn(X / Sqr(X * X - 1)) + (Sgn(X) - 1) * (2 * Atn(1))
    ACoSec = Atn(X / Sqr(X * X - 1)) + (Sgn(X) - 1) * (2 * Atn(1))
    End Function

Function ACoTan(X As Double) As Double  'ACoTan(X) = Atn(X) + 2 * Atn(1)
    ACoTan = Atn(X) + 2 * Atn(1)
    End Function

Function SinH(X As Double) As Double    'SinH(X) = (Exp(X) - Exp(-X)) / 2
    SinH = (VBA.Exp(X) - VBA.Exp(-X)) / 2
    End Function

Function CosH(X As Double) As Double    'CosH(X) = (Exp(X) + Exp(-X)) / 2
    CosH = (VBA.Exp(X) + VBA.Exp(-X)) / 2
    End Function

Function TanH(X As Double) As Double    'TanH(X) = (Exp(X) - Exp(-X)) / (Exp(X) + Exp(-X))
    TanH = (VBA.Exp(X) - VBA.Exp(-X)) / (VBA.Exp(X) + VBA.Exp(-X))
    End Function

Function SecH(X As Double) As Double    'SecH(X) = 2 / (Exp(X) + Exp(-X))
    SecH = 2 / (VBA.Exp(X) + VBA.Exp(-X))
    End Function

Function CoSecH(X As Double) As Double  'CoSecH(X) = 2 / (Exp(X) - Exp(-X))
    CoSecH = 2 / (VBA.Exp(X) - VBA.Exp(-X))
    End Function

Function CoTanH(X As Double) As Double  'CoTanH(X) = (Exp(X) + Exp(-X)) / (Exp(X) - Exp(-X))
    CoTanH = (VBA.Exp(X) + VBA.Exp(-X)) / (VBA.Exp(X) - VBA.Exp(-X))
    End Function

Function ASinH(X As Double) As Double   'ASinH(X) = Log(X + Sqr(X * X + 1))
    ASinH = VBA.Log(X + Sqr(X * X + 1))
    End Function

Function ACosH(X As Double) As Double   'ACosH(X) = Log(X + Sqr(X * X - 1))
    ACosH = VBA.Log(X + Sqr(X * X - 1))
    End Function

Function ATanH(X As Double) As Double   'ATanH(X) = Log((1 + X) / (1 - X)) / 2
    ATanH = VBA.Log((1 + X) / (1 - X)) / 2
    End Function

Function ASecH(X As Double) As Double   'ASecH(X) = Log((Sqr(-X * X + 1) + 1) / X)
    ASecH = VBA.Log((Sqr(-X * X + 1) + 1) / X)
    End Function

Function ACoSecH(X As Double) As Double 'ACoSecH(X) = Log((Sgn(X) * Sqr(X * X + 1) + 1) / X)
    ACoSecH = VBA.Log((Sgn(X) * Sqr(X * X + 1) + 1) / X)
    End Function

Function ACoTanH(X As Double) As Double 'ACoTanH(X) = Log((X + 1) / (X - 1)) / 2
    ACoTanH = VBA.Log((X + 1) / (X - 1)) / 2
    End Function

Function LogN(X As Double, Optional N As Double = 10) As Double 'LogN(X) = Log(X) / Log(N)
    LogN = VBA.Log(X) / VBA.Log(N)
    End Function

Function ValAngle(ByVal N As String, Optional ByVal Degrees As Boolean) As Double
    Dim A As Long, B As Long, R As Double
    If Degrees Then R = 1 Else R = Rad
    A = InStr(N, "º"): If A = 0 Then A = InStr(N, "°")
    B = InStr(N, "'"): If B < A Then B = 0
    If A = 0 And B = 0 Then ValAngle = Abs(Val(N)) * R _
    Else If A > 0 And B = 0 Then ValAngle = (Abs(Val(Left(N, A - 1))) + Val(Mid(N, A + 1)) / Min) * R _
    Else If A = 0 And B > 0 Then ValAngle = (Abs(Val(Left(N, B - 1))) / Min + Val(Mid(N, B + 1)) / Hour) * R _
    Else ValAngle = (Abs(Val(Left(N, A - 1))) + Val(Mid(N, A + 1, B - A - 1)) / Min + Val(Mid(N, B + 1)) / Hour) * R
    If Left(N, 1) = "-" Or InStr(LCase(N), "w") Or InStr(LCase(N), "s") Then ValAngle = -ValAngle
    End Function

Function ValAngleEx(ByVal N As String, Optional ByVal Degrees As Boolean) As Double
    Dim R As Double
    If Degrees Then R = 1 Else R = Rad
    If Right(N, 1) = """" Then ValAngleEx = Val(N) / Hour * R _
    Else If Right(N, 1) = "'" Then ValAngleEx = Val(N) / Min * R _
    Else ValAngleEx = Val(N) * R
    End Function

Function ValDegLat(ByVal N As String) As Double 'DD.MM...'
    ValDegLat = (Val(Left(N, 2)) + Val(Mid(N, 3)) / Min) * Rad
    End Function

Function ValDegLon(ByVal N As String) As Double 'DDD.MM...'
    ValDegLon = (Val(Left(N, 3)) + Val(Mid(N, 4)) / Min) * Rad
    End Function

Function ValPitch(ByVal N As String, Optional ByVal Degrees As Boolean) As Double
    If Degrees Then ValPitch = (90 - Val(N)) Else ValPitch = (90 - Val(N)) * Rad
    End Function

Function ValMass(ByVal N As String, Optional ByVal Total As Double = 1, _
        Optional ByVal AddRatio As Boolean) As Double
    If InStr(N, "%") Then
        ValMass = Val(Replace(N, "%", "")) / 100
Nxt:
        If Not AddRatio Then ValMass = Total * ValMass _
        Else If ValMass < 0 Or ValMass >= 1 Then ValMass = 0 _
        Else ValMass = Total * ValMass / (1 - ValMass)
    ElseIf InStr(N, ":") Then
        ValMass = ValRatio(N): GoTo Nxt
    Else
        ValMass = Val(Replace(N, ",", "")): _
             If InStr(N, "Gg") Then ValMass = ValMass * 1000000 _
        Else If InStr(N, "Mg") Then ValMass = ValMass * 1000 _
        Else If InStr(N, "kg") Then ValMass = ValMass _
        Else If InStr(N, "mg") Then ValMass = ValMass / 1000000 _
        Else If InStr(N, "ug") Or InStr(N, "µg") Then ValMass = ValMass / 1000000000 _
        Else If InStr(N, "ng") Then ValMass = ValMass / 1000000000000# _
        Else If InStr(N, "g") Then ValMass = ValMass / 1000 _
        Else If InStr(N, "t") Then ValMass = ValMass * 1000 _
        Else If InStr(N, "lb") Then ValMass = ValMass * Pound
        End If
    End Function

Function ValForce(ByVal N As String, Optional ByVal Total As Double = 1) As Double
    If InStr(N, "%") Then ValForce = Total * G0 * Val(Replace(N, "%", "")) / 100 _
    Else If InStr(N, ":") Then ValForce = Total * G0 * ValRatio(N) _
    Else ValForce = Val(Replace(N, ",", "")): _
         If InStr(N, "Gg") Then ValForce = G0 * ValForce * 1000000 _
    Else If InStr(N, "Mg") Then ValForce = G0 * ValForce * 1000 _
    Else If InStr(N, "kg") Then ValForce = G0 * ValForce _
    Else If InStr(N, "mg") Then ValForce = G0 * ValForce / 1000000 _
    Else If InStr(N, "ug") Or InStr(N, "µg") Then ValForce = G0 * ValForce / 1000000000 _
    Else If InStr(N, "ng") Then ValForce = G0 * ValForce / 1000000000000# _
    Else If InStr(N, "g") Then ValForce = G0 * ValForce / 1000 _
    Else If InStr(N, "GN") Then ValForce = ValForce * 1000000000 _
    Else If InStr(N, "MN") Then ValForce = ValForce * 1000000 _
    Else If InStr(N, "kN") Then ValForce = ValForce * 1000 _
    Else If InStr(N, "mN") Then ValForce = ValForce / 1000 _
    Else If InStr(N, "uN") Or InStr(N, "µN") Then ValForce = ValForce / 1000000 _
    Else If InStr(N, "nN") Then ValForce = ValForce / 1000000000 _
    Else If InStr(N, "N") Then _
    Else If InStr(N, "MA/m") Then ValForce = Field * ValForce * 1000000 _
    Else If InStr(N, "kA/m") Then ValForce = Field * ValForce * 1000 _
    Else If InStr(N, "mA/m") Then ValForce = Field * ValForce / 1000 _
    Else If InStr(N, "uA/m") Or InStr(N, "µA/m") Then ValForce = Field * ValForce / 1000000 _
    Else If InStr(N, "A/m") Then ValForce = Field * ValForce _
    Else If InStr(N, "t") Then ValForce = G0 * ValForce * 1000 _
    Else If InStr(N, "lb") Then ValForce = G0 * ValForce * Pound _
    Else If InStr(N, "G") Then ValForce = Total * G0 * ValForce
    End Function
    
Function ValTorque(ByVal N As String) As Double
    ValTorque = Val(Replace(N, ",", ""))
         If InStr(N, "Gg") Then ValTorque = G0 * ValTorque * 1000000 _
    Else If InStr(N, "Mgm") Then ValTorque = G0 * ValTorque * 1000 _
    Else If InStr(N, "kgm") Then ValTorque = G0 * ValTorque _
    Else If InStr(N, "mgm") Then ValTorque = G0 * ValTorque / 1000000 _
    Else If InStr(N, "ugm") Or InStr(N, "µg") Then ValTorque = G0 * ValTorque / 1000000000 _
    Else If InStr(N, "ngm") Then ValTorque = G0 * ValTorque / 1000000000000# _
    Else If InStr(N, "gm") Then ValTorque = G0 * ValTorque / 1000 _
    Else If InStr(N, "GNm") Then ValTorque = ValTorque * 1000000000 _
    Else If InStr(N, "MNm") Then ValTorque = ValTorque * 1000000 _
    Else If InStr(N, "kNm") Then ValTorque = ValTorque * 1000 _
    Else If InStr(N, "mNm") Then ValTorque = ValTorque / 1000 _
    Else If InStr(N, "uNm") Or InStr(N, "µN") Then ValTorque = ValTorque / 1000000 _
    Else If InStr(N, "nNm") Then ValTorque = ValTorque / 1000000000 _
    Else If InStr(N, "Nm") Then _
    Else If InStr(N, "MAm²") Or InStr(N, "MAm2") Then ValTorque = Field * ValTorque * 1000000 _
    Else If InStr(N, "kAm²") Or InStr(N, "kAm2") Then ValTorque = Field * ValTorque * 1000 _
    Else If InStr(N, "mAm²") Or InStr(N, "mAm2") Then ValTorque = Field * ValTorque / 1000 _
    Else If InStr(N, "uAm²") Or InStr(N, "uAm2") Or _
            InStr(N, "µAm²") Or InStr(N, "µAm2") Then ValTorque = Field * ValTorque / 1000000 _
    Else If InStr(N, "Am²") Or InStr(N, "Am2") Then ValTorque = Field * ValTorque _
    Else If InStr(N, "lbft") Then ValTorque = G0 * ValTorque * Pound * Foot
    End Function

Function ValInertia(ByVal N As String) As Double
    ValInertia = Val(Replace(N, ",", ""))
         If InStr(N, "Ggm²") Or InStr(N, "Ggm2") Then ValInertia = ValInertia * 1000000 _
    Else If InStr(N, "Mgm²") Or InStr(N, "Mgm2") Then ValInertia = ValInertia * 1000 _
    Else If InStr(N, "kgm²") Or InStr(N, "kgm2") Then _
    Else If InStr(N, "mgm²") Or InStr(N, "mgm2") Then ValInertia = ValInertia / 1000000 _
    Else If InStr(N, "ugm²") Or InStr(N, "ugm2") Or _
            InStr(N, "µgm²") Or InStr(N, "µgm2") Then ValInertia = ValInertia / 1000000000 _
    Else If InStr(N, "ngm²") Or InStr(N, "ngm2") Then ValInertia = ValInertia / 1000000000000# _
    Else If InStr(N, "gm²") Or InStr(N, "gm2") Then ValInertia = ValInertia / 1000 _
    Else If InStr(N, "lbft²") Or InStr(N, "lbft2") Then ValInertia = ValInertia * Pound / Foot / Foot
    End Function

Function ValForceG(ByVal N As String) As Double
    ValForceG = Val(Replace(N, ",", "")) * G0
    End Function

Function ValDensity(ByVal N As String) As Double
    ValDensity = Val(Replace(N, ",", ""))
         If InStr(N, "Gg/") Then ValDensity = ValDensity * 1000000 _
    Else If InStr(N, "Mg/") Then ValDensity = ValDensity * 1000 _
    Else If InStr(N, "mg/") Then ValDensity = ValDensity / 1000000 _
    Else If InStr(N, "ug/") Or InStr(N, "µg/") Then ValDensity = ValDensity / 1000000000 _
    Else If InStr(N, "ng/") Then ValDensity = ValDensity / 1000000000000# _
    Else If InStr(N, "g/") Then ValDensity = ValDensity / 1000 _
    Else If InStr(N, "t/") Then ValDensity = ValDensity * 1000 _
    Else If InStr(N, "lb/") Then ValDensity = ValDensity * Pound
         If InStr(N, "ft") Then ValDensity = ValDensity / Foot3 _
    Else If InStr(N, "/in") Then ValDensity = ValDensity / Inch3 _
    Else If InStr(N, "/Mm") Then ValDensity = ValDensity / 1E+18 _
    Else If InStr(N, "/km") Then ValDensity = ValDensity / 1000000000 _
    Else If InStr(N, "/dm") Then ValDensity = ValDensity * 1000 _
    Else If InStr(N, "/cm") Then ValDensity = ValDensity * 1000000 _
    Else If InStr(N, "/mm") Then ValDensity = ValDensity * 1000000000 _
    Else If InStr(N, "/um") Or InStr(N, "/µm") Then ValDensity = ValDensity * 1E+18
    End Function

Function ValTemp(ByVal N As String) As Double
    ValTemp = Val(Replace(N, ",", ""))
         If InStr(N, "C") Then ValTemp = ValTemp + Kelvin _
    Else If InStr(N, "F") Then ValTemp = (ValTemp + 459.67) / 1.8 _
    Else If InStr(N, "R") Then ValTemp = (ValTemp - 0.02) / 1.8
    End Function

Function ValCost(ByVal N As String, Optional ByVal Total As Double = 1) As Double
    ValCost = Val(Replace(N, ",", ""))
         If InStr(N, "$/lb") Then ValCost = ValCost / Pound
    End Function

Function ValTime(ByVal N As String) As Double
    ValTime = Val(Replace(N, ",", ""))
         If InStr(N, "ds") Then ValTime = ValTime / 10 _
    Else If InStr(N, "cs") Then ValTime = ValTime / 100 _
    Else If InStr(N, "ms") Then ValTime = ValTime / 1000 _
    Else If InStr(N, "us") Or InStr(N, "µs") Then ValTime = ValTime / 1000000 _
    Else If InStr(N, "ns") Then ValTime = ValTime / 1000000000 _
    Else If InStr(N, "ps") Then ValTime = ValTime / 1000000000000# _
    Else If InStr(N, "m") Then ValTime = ValTime * Min _
    Else If InStr(N, "h") Then ValTime = ValTime * Hour _
    Else If InStr(N, "d") Then ValTime = ValTime * Day _
    Else If InStr(N, "y") Then ValTime = ValTime * Year
    End Function

Function ValTimeHMS(N As String, Optional ByVal UseAngle As Boolean) As Double
    Dim A As Long, B As Long
    A = 1
    B = InStr(N, "y"): If B Then ValTimeHMS = ValTimeHMS + Val(Mid(N, A, B - A)) * Year: A = B + 1
    B = InStr(N, "d"): If B Then ValTimeHMS = ValTimeHMS + Val(Mid(N, A, B - A)) * Day: A = B + 1
    B = InStr(N, "h"): If B Then ValTimeHMS = ValTimeHMS + Val(Mid(N, A, B - A)) * Hour: A = B + 1
    B = InStr(N, "m"): If B Then ValTimeHMS = ValTimeHMS + Val(Mid(N, A, B - A)) * Min: A = B + 1
    B = InStr(N, "s"): B = IfInt(B, B, Len(N) + 1): ValTimeHMS = ValTimeHMS + Val(Mid(N, A, B - A))
    If Not UseAngle Then Exit Function
    ValTimeHMS = ValTimeHMS / 240: If ValTimeHMS >= 180 Then ValTimeHMS = ValTimeHMS - 360
    ValTimeHMS = ValTimeHMS * Rad
    End Function

Function ValRatio(ByVal N As String) As Double
    Dim C As Long
    If InStr(N, "%") Then ValRatio = Val(Replace(N, "%", "")) / 100: Exit Function
    If InStr(N, ",") Then N = Replace(N, ",", "")
    C = InStr(N, ":"): If C = 0 Then C = InStr(N, "/")
    If C = 0 Then ValRatio = Val(N) _
    Else ValRatio = Val(Mid(N, C + 1)): If ValRatio <> 0 Then _
        ValRatio = Val(Left(N, C - 1)) / ValRatio
    End Function

Function IsSpeed(ByVal N As String) As Boolean
    If InStr(N, "m/s") Or InStr(N, "ft/s") Or InStr(N, "in/s") Or _
        InStr(N, "kn") Or InStr(N, "mph") Or InStr(N, "kph") Or _
        InStr(N, "km/h") Or InStr(N, "Mach") Then IsSpeed = True
    End Function

Function IsArea(ByVal N As String) As Boolean
    If InStr(N, "m²") Or InStr(N, "ft²") Or InStr(N, "in²") Or _
        InStr(N, "m2") Or InStr(N, "ft2") Or InStr(N, "in2") Or _
        InStr(N, "m^2") Or InStr(N, "ft^2") Or InStr(N, "in^2") Then IsArea = True
    End Function

Function IsVolume(ByVal N As String) As Boolean
    If InStr(N, "m³") Or InStr(N, "ft³") Or InStr(N, "in³") Or _
        InStr(N, "m3") Or InStr(N, "ft3") Or InStr(N, "in3") Or _
        InStr(N, "m^3") Or InStr(N, "ft^3") Or InStr(N, "in^3") Then IsVolume = True
    End Function

Function IsMass(ByVal N As String) As Boolean
    If InStr(N, "g") Or InStr(N, "lb") Or _
        (InStr(N, "t") > 0 And InStr(N, "ft") = 0) Then IsMass = True
    End Function

Function IsRatio(ByVal N As String) As Boolean
    If InStr(N, "%") Or InStr(N, ":") Then IsRatio = True
    End Function

Function IsTorque(ByVal N As String) As Boolean
    If InStr(N, "Am²") Or InStr(N, "Am2") Then IsTorque = True
    End Function

Function ValSpace(ByVal N As String) As Double
    If Left(N, 4) = "Mach" Then ValSpace = Val(Mid(N, 5)) * Mach _
    Else ValSpace = Val(Replace(N, ",", "")): _
         If InStr(N, "nmi") Then ValSpace = ValSpace * NMile _
    Else If InStr(N, "mi") Then ValSpace = ValSpace * Mile _
    Else If InStr(N, "ft") Then ValSpace = ValSpace * Foot _
    Else If InStr(N, "in") Then ValSpace = ValSpace * Inch _
    Else If InStr(N, "kn") Then ValSpace = ValSpace * Knot _
    Else If InStr(N, "mph") Then ValSpace = ValSpace * Mph _
    Else If InStr(N, "kph") Or InStr(N, "km/h") Then ValSpace = ValSpace * Kph _
    Else If InStr(N, "Mach") Then ValSpace = ValSpace * Mach _
    Else If InStr(N, "Em") Then ValSpace = ValSpace * 1E+18 _
    Else If InStr(N, "Pm") Then ValSpace = ValSpace * 1E+15 _
    Else If InStr(N, "Tm") Then ValSpace = ValSpace * 1000000000000# _
    Else If InStr(N, "Gm") Then ValSpace = ValSpace * 1000000000 _
    Else If InStr(N, "Mm") Then ValSpace = ValSpace * 1000000 _
    Else If InStr(N, "km") Then ValSpace = ValSpace * 1000 _
    Else If InStr(N, "dm") Then ValSpace = ValSpace / 10 _
    Else If InStr(N, "cm") Then ValSpace = ValSpace / 100 _
    Else If InStr(N, "mm") Then ValSpace = ValSpace / 1000 _
    Else If InStr(N, "um") Or InStr(N, "µm") Then ValSpace = ValSpace / 1000000 _
    Else If InStr(N, "nm") Then ValSpace = ValSpace / 1000000000 _
    Else If InStr(N, "pm") Then ValSpace = ValSpace / 1000000000000#
    End Function

Function ValArea(ByVal N As String) As Double
    ValArea = Val(Replace(N, ",", ""))
         If InStr(N, "ft") Then ValArea = ValArea * Foot * Foot _
    Else If InStr(N, "in") Then ValArea = ValArea * Inch * Inch _
    Else If InStr(N, "Mm") Then ValArea = ValArea * 1000000000000# _
    Else If InStr(N, "km") Then ValArea = ValArea * 1000000 _
    Else If InStr(N, "dm") Then ValArea = ValArea / 100 _
    Else If InStr(N, "cm") Then ValArea = ValArea / 10000 _
    Else If InStr(N, "mm") Then ValArea = ValArea / 1000000 _
    Else If InStr(N, "um") Or InStr(N, "µm") Then ValArea = ValArea / 1000000000000# _
    Else If InStr(N, "nm") Then ValArea = ValArea / 1E+18 _
    Else If InStr(N, "pm") Then ValArea = ValArea / 1E+24
    End Function

Function ValVolume(ByVal N As String) As Double
    ValVolume = Val(Replace(N, ",", ""))
         If InStr(N, "ft") Then ValVolume = ValVolume * Foot * Foot * Foot _
    Else If InStr(N, "in") Then ValVolume = ValVolume * Inch * Inch * Inch _
    Else If InStr(N, "Mm") Then ValVolume = ValVolume * 1E+18 _
    Else If InStr(N, "km") Then ValVolume = ValVolume * 1000000000 _
    Else If InStr(N, "dm") Then ValVolume = ValVolume / 1000 _
    Else If InStr(N, "cm") Then ValVolume = ValVolume / 1000000 _
    Else If InStr(N, "mm") Then ValVolume = ValVolume / 1000000000 _
    Else If InStr(N, "um") Or InStr(N, "µm") Then ValVolume = ValVolume / 1E+18 _
    Else If InStr(N, "nm") Then ValVolume = ValVolume / 1E+27 _
    Else If InStr(N, "pm") Then ValVolume = ValVolume / 1E+36
    End Function

Function ValPressure(ByVal N As String) As Double
    ValPressure = Val(Replace(N, ",", ""))
         If InStr(N, "mb") Then ValPressure = ValPressure / 1000 * Bar _
    Else If InStr(N, "kbar") Then ValPressure = ValPressure * 1000 * Bar _
    Else If InStr(N, "bar") Then ValPressure = ValPressure * Bar _
    Else If InStr(N, "psi") Then ValPressure = ValPressure * Psi _
    Else If InStr(N, "TPa") Then ValPressure = ValPressure * 1000000000000# _
    Else If InStr(N, "GPa") Then ValPressure = ValPressure * 1000000000 _
    Else If InStr(N, "MPa") Then ValPressure = ValPressure * 1000000 _
    Else If InStr(N, "kPa") Then ValPressure = ValPressure * 1000 _
    Else If InStr(N, "hPa") Then ValPressure = ValPressure * 100 _
    Else If InStr(N, "mPa") Then ValPressure = ValPressure / 1000 _
    Else If InStr(N, "uPa") Or InStr(N, "µPa") Then ValPressure = ValPressure / 1000000 _
    Else If InStr(N, "nPa") Then ValPressure = ValPressure / 1000000000 _
    Else If InStr(N, "pPa") Then ValPressure = ValPressure / 1000000000000# _
    Else If InStr(N, "Pa") Then _
    Else ValPressure = ValPressure * Pascal
    End Function

Function ValNum(ByVal N As String) As Double
    ValNum = Val(Replace(N, ",", ""))
    If InStr(N, "P") Then ValNum = ValNum * 1E+15 _
    Else If InStr(N, "T") Then ValNum = ValNum * 1000000000000# _
    Else If InStr(N, "G") Then ValNum = ValNum * 1000000000 _
    Else If InStr(N, "M") Then ValNum = ValNum * 1000000 _
    Else If InStr(N, "k") Then ValNum = ValNum * 1000 _
    Else If InStr(N, "d") Then ValNum = ValNum / 10 _
    Else If InStr(N, "c") Then ValNum = ValNum / 100 _
    Else If InStr(N, "m") Then ValNum = ValNum / 1000 _
    Else If InStr(N, "u") Or InStr(N, "µ") Then ValNum = ValNum / 1000000 _
    Else If InStr(N, "n") Then ValNum = ValNum / 1000000000 _
    Else If InStr(N, "p") Then ValNum = ValNum / 1000000000000#
    End Function

Function NumX(Optional N As Double, Optional Fmt As String = FLong) As String
    NumX = Replace(Format(N, Fmt), ",", ".")
    End Function

Function NumXS(Optional N As Single, Optional Fmt As String = FLong) As String
    NumXS = Replace(Format(N, Fmt), ",", ".")
    End Function

Function NumD(ByVal N As Double) As String
    NumD = Replace(CStr(N), ",", ".")
    End Function

Function NumDS(ByVal N As Single) As String
    NumDS = Replace(CStr(N), ",", ".")
    End Function

Function NumDX(ByVal N As Double, Optional ByVal IsDouble As Boolean = True) As String
    If IsDouble Then NumDX = Replace(CStr(N), ",", ".") Else NumDX = Replace(CStr(CSng(N)), ",", ".")
    End Function

Function Num(ByVal N As Double, Optional Fmt As String = FExt) As String
    Select Case Abs(N)
    Case Is < 0.000000000001: Num = Replace(Format(0, Fmt), ",", ".")
    Case Is < 0.000000001: Num = Replace(Format(N * 1000000000000#, Fmt), ",", ".") & "p"
    Case Is < 0.000001: Num = Replace(Format(N * 1000000000, Fmt), ",", ".") & "n"
    Case Is < 0.001: Num = Replace(Format(N * 1000000, Fmt), ",", ".") & "u"
    Case Is < 1: Num = Replace(Format(N * 1000, Fmt), ",", ".") & "m"
    Case Is < 1000: Num = Replace(Format(N, Fmt), ",", ".")
    Case Is < 1000000: Num = Replace(Format(N / 1000, Fmt), ",", ".") & "k"
    Case Is < 1000000000: Num = Replace(Format(N / 1000000, Fmt), ",", ".") & "M"
    Case Is < 1000000000000#: Num = Replace(Format(N / 1000000000, Fmt), ",", ".") & "G"
    Case Is < 1E+15: Num = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "T"
    Case Else: Num = Replace(Format(N / 1E+15, Fmt), ",", ".") & "P"
        End Select
    End Function

Function NumA(ByVal N As Double, Optional Fmt As String = FExt) As String
    Select Case Abs(N)
    Case Is < 0.000000000001: NumA = Replace(Format(0, Fmt), ",", ".")
    Case Is < 0.000000001: NumA = Replace(Format(N * 1000000000000#, Fmt), ",", ".") & "p"
    Case Is < 0.000001: NumA = Replace(Format(N * 1000000000, Fmt), ",", ".") & "n"
    Case Is < 0.001: NumA = Replace(Format(N * 1000000, Fmt), ",", ".") & "u"
    Case Is < 1: NumA = Replace(Format(N * 1000, Fmt), ",", ".") & "m"
    Case Else: NumA = Replace(Format(N, Fmt), ",", ".")
        End Select
    End Function

Function NumM(ByVal N As Double, Optional Fmt As String = FExt) As String
    Select Case N
    Case Is < 0: NumM = Replace(Format(0, Fmt), ",", ".")
    Case Is < 1000: NumM = Replace(Format(N, Fmt), ",", ".")
    Case Is < 1000000: NumM = Replace(Format(N / 1000, Fmt), ",", ".") & "k"
    Case Is < 1000000000: NumM = Replace(Format(N / 1000000, Fmt), ",", ".") & "M"
    Case Is < 1000000000000#: NumM = Replace(Format(N / 1000000000, Fmt), ",", ".") & "G"
    Case Is < 1E+15: NumM = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "T"
    Case Else: NumM = Replace(Format(N / 1E+15, Fmt), ",", ".") & "P"
        End Select
    End Function

Function NumE(ByVal N As Double) As String
    Select Case Abs(N)
    Case Is < 0.000000001: NumE = "0": Exit Function
    Case Is < 0.000001: NumE = Replace(CStr(N * 1000000000), ",", ".") & "n"
    Case Is < 0.001: NumE = Replace(CStr(N * 1000000), ",", ".") & "u"
    Case Is < 1: NumE = Replace(CStr(N * 1000), ",", ".") & "m"
    Case Is < 1000: NumE = Replace(CStr(N), ",", ".")
    Case Is < 1000000: NumE = Replace(CStr(N / 1000), ",", ".") & "k"
    Case Is < 1000000000: NumE = Replace(CStr(N / 1000000), ",", ".") & "M"
    Case Is < 1000000000000#: NumE = Replace(CStr(N / 1000000000), ",", ".") & "G"
    Case Is < 1E+15: NumE = Replace(CStr(N / 1000000000000#), ",", ".") & "T"
    Case Else: NumE = Replace(CStr(N / 1E+15), ",", ".") & "P"
        End Select
    End Function

Function NumG(ByVal N As Double) As String
    Select Case Abs(N)
    Case Is < 0.000000001: NumG = "0": Exit Function
    Case Is < 0.000001: NumG = Replace(CStr(N * 1000000000), ",", ".") & "n"
    Case Is < 0.001: NumG = Replace(CStr(N * 1000000), ",", ".") & "u"
    Case Is < 1: NumG = Replace(CStr(N * 1000), ",", ".") & "m"
    Case Else: NumG = Replace(CStr(N), ",", ".")
        End Select
    End Function

Sub NumAdd(N As Double, Optional ByVal Add As Long, _
        Optional LimitDown As Double = -Infinite, Optional LimitUp As Double = Infinite)
    Dim X As Double, E As Double, S As Double
    If N = 0 Then GoTo Nxt Else S = Sgn(N): If S < 0 Then N = -N
    X = Int(VBA.Log(N) / Log10 * 3 + Add + 0.5) / 3
    E = 10 ^ Int(X): N = Int(10 ^ X / E + 0.5) * E: If S < 0 Then N = -N
Nxt:
    If N > LimitUp Then N = LimitUp Else If N < LimitDown Then N = LimitDown
    End Sub

Sub NumAddInt(N As Long, Optional ByVal Add As Long, _
        Optional ByVal LimitDown As Long = -InfiniteInt, Optional ByVal LimitUp As Long = InfiniteInt)
    Dim X As Double, E As Double, S As Double
    If N = 0 Then GoTo Nxt Else S = Sgn(N): If S < 0 Then N = -N
    X = Int(VBA.Log(N) / Log10 * 3 + Add + 0.5) / 3
    E = 10 ^ Int(X): N = Int(10 ^ X / E + 0.5) * E: If S < 0 Then N = -N
Nxt:
    If N > LimitUp Then N = LimitUp Else If N < LimitDown Then N = LimitDown
    End Sub

Sub NumAdjust(N As Double, Optional Add As Double = 0.999999999999999, _
        Optional LimitDown As Double = -Infinite, Optional LimitUp As Double = Infinite)
    Dim X As Double, E As Double, S As Double
    If N = 0 Then GoTo Nxt Else S = Sgn(N): If S < 0 Then N = -N
    X = Int(VBA.Log(N) / Log10 * 3 + Add) / 3
    E = 10 ^ Int(X): N = Int(10 ^ X / E + 0.5) * E: If S < 0 Then N = -N
Nxt:
    If N > LimitUp Then N = LimitUp Else If N < LimitDown Then N = LimitDown
    End Sub

Function NumRound(N As Double) As Double
    Dim X As Double, E As Double, S As Double
    If N = 0 Then GoTo Nxt Else S = Sgn(N): If S < 0 Then N = -N
    X = Int(VBA.Log(N) / Log10 * 3 + 0.5) / 3
    E = 10 ^ Int(X): NumRound = Int(10 ^ X / E + 0.5) * E: If S < 0 Then NumRound = -NumRound
Nxt:
    End Function

Function ToBytes(ByVal N As Double, Optional Fmt As String = FMini) As String
    N = Int(N): If N < 0 Then N = 0
    Select Case N
    Case Is < KB: If N = 1 Then ToBytes = N & " Byte" Else ToBytes = N & " Bytes"
    Case Is < MB: ToBytes = Replace(Format(N / KB, Fmt), ",", ".") & " KB"
    Case Is < GB: ToBytes = Replace(Format(N / MB, Fmt), ",", ".") & " MB"
    Case Is < TB: ToBytes = Replace(Format(N / GB, Fmt), ",", ".") & " GB"
    Case Else:    ToBytes = Replace(Format(N / TB, Fmt), ",", ".") & " TB"
        End Select
    End Function

Function ToLat(ByVal N As Double, Optional Fmt As String = "00.000000", _
        Optional ByVal Degrees As Long) As String
    Dim X As Long, D As Long, M As Long, S As Long, R As Long
    Select Case Degrees
    Case 2
        X = Int(Abs(N) / Rad * 360000 + 0.5)
        D = X \ 360000: M = (X \ 6000) Mod 60: S = (X \ 100) Mod 60: R = X Mod 100
        ToLat = Format(D, "0º") & Format(M, "00'") & _
            Format(S, "00") & IfText(Fmt <> "", "." & Format(R, "00")) & IIf(N < 0, """S", """N")
    Case 1
        X = Int(Abs(N) / Rad * 600000 + 0.5)
        D = X \ 600000: M = (X \ 10000) Mod 60: R = X Mod 10000
        ToLat = Format(D, "0º") & Format(M, "00") & _
            IfText(Fmt <> "", "." & Format(R, "0000")) & IIf(N < 0, "'S", "'N")
    Case Else
        ToLat = Replace(Format(Abs(N / Rad), Fmt), ",", ".") & IIf(N < 0, "S", "N")
        End Select
    End Function

Function ToLon(ByVal N As Double, Optional Fmt As String = "000.000000", _
        Optional ByVal Degrees As Long) As String
    Dim X As Long, D As Long, M As Long, S As Long, R As Long
    If N >= A180 Or N < -A180 Then N = N - Fix(N / A360) * A360: _
        If N > A180 Then N = N - A360 Else If N < A180 Then N = N + A360
    Select Case Degrees
    Case 2
        X = Int(Abs(N) / Rad * 360000 + 0.5)
        D = X \ 360000: M = (X \ 6000) Mod 60: S = (X \ 100) Mod 60: R = X Mod 100
        ToLon = Format(D, "0º") & Format(M, "00'") & _
            Format(S, "00") & IfText(Fmt <> "", "." & Format(R, "00")) & IIf(N < 0, """W", """E")
    Case 1
        X = Int(Abs(N) / Rad * 600000 + 0.5)
        D = X \ 600000: M = (X \ 10000) Mod 60: R = X Mod 10000
        ToLon = Format(D, "0º") & Format(M, "00") & _
            IfText(Fmt <> "", "." & Format(R, "0000")) & IIf(N < 0, "'W", "'E")
    Case Else
        ToLon = Replace(Format(Abs(N / Rad), Fmt), ",", ".") & IIf(N < 0, "W", "E")
        End Select
    End Function

Function ToAngle(ByVal N As Double, Optional Fmt As String = FAngleLat, _
        Optional ByVal Limit As Boolean) As String
    If Limit Then If N >= A360 Or N < 0 Then N = N - Fix(N / A360) * A360: _
        If N >= A360 Then N = N - A360 Else If N < 0 Then N = N + A360
    ToAngle = Replace(Format(N / Rad, Fmt), ",", ".")
    End Function

Function ToDeclination(ByVal N As Double, Optional ByVal Degrees As Long) As String
    Dim X As Long, D As Long, M As Long, S As Long, R As Long
    Select Case Degrees
    Case 2
        X = Int(Abs(N) / Rad * 360000 + 0.5)
        D = X \ 360000: M = (X \ 6000) Mod 60: S = (X \ 100) Mod 60: R = X Mod 100
        ToDeclination = IfText(N < 0, "-") & Format(D, "0º") & Format(M, "00'") & _
            Format(S, "00") & "." & Format(R, "00") & """"
    Case 1
        X = Int(Abs(N) / Rad * 600000 + 0.5)
        D = X \ 600000: M = (X \ 10000) Mod 60: R = X Mod 10000
        ToDeclination = IfText(N < 0, "-") & Format(D, "0º") & Format(M, "00") & _
            "." & Format(R, "0000'")
    Case Else: ToDeclination = Replace(Format(N / Rad, "0.000000º"), ",", ".")
        End Select
    End Function

Function ToRightAscension(ByVal N As Double, Optional ByVal Degrees As Long) As String
    Dim X As Long, H As Long, M As Long, S As Long, R As Long
    Select Case Degrees
    Case 2
        X = Int(N * Hrs * 360000 + 0.5)
        If X < 0 Then X = X + 8640000 Else If X >= 8640000 Then X = X - 8640000
        H = X \ 360000: M = (X \ 6000) Mod 60: S = (X \ 100) Mod 60: R = X Mod 100
        ToRightAscension = Format(H, "0h ") & Format(M, "00m ") & _
            Format(S, "00") & "." & Format(R, "00s")
    Case 1
        X = Int(N * Hrs * 600000 + 0.5)
        If X < 0 Then X = X + 14400000 Else If X >= 14400000 Then X = X - 14400000
        H = X \ 600000: M = (X \ 10000) Mod 60: R = X Mod 10000
        ToRightAscension = Format(H, "0h ") & Format(M, "00") & _
            "." & Format(R, "0000m")
    Case Else
        N = N * Hrs: If N < 0 Then N = N + 24 Else If N >= 24 Then N = N - 24
        ToRightAscension = Replace(Format(N, "0.000000h"), ",", ".")
        End Select
    End Function

Function ToTemp(N As Double, Optional Fmt As String = FMini) As String
    'ºC=K-273.15 K=ºC+273.15 ºC=(ºF-32)*5/9 ºF=ºC/5*9+32 R=ºF+459.69=K/5*9+0.02
    If Units Then If RelativeTemp Then ToTemp = Replace(Format(N * 1.8 - 459.67, Fmt) & "ºF", ",", ".") _
        Else ToTemp = Replace(Format(N * 1.8 + 0.02, Fmt) & "R", ",", ".") _
    Else If RelativeTemp Then ToTemp = Replace(Format(N - Kelvin, Fmt) & "ºC", ",", ".") _
        Else ToTemp = Replace(Format(N, Fmt) & "K", ",", ".")
    End Function

Function ToRatio(ByVal N As Double, Optional Sep As String = " : ") As String
    If N >= 1 Or N <= -1 Or N = 0 Then ToRatio = Replace(CStr(N), ",", ".") & Sep & "1" _
    Else If N > 0 Then ToRatio = "1" & Sep & Replace(CStr(1 / N), ",", ".") _
    Else ToRatio = "-1" & Sep & Replace(CStr(-1 / N), ",", ".")
    End Function

Function ToRatioEx(ByVal A As Double, Optional B As Double, _
        Optional ByVal Simple As Boolean = True, Optional ByVal Limit As Boolean = True, _
        Optional Sep As String = " : ") As String
    Dim N As Double
    If Limit And A > B Then A = B
    If Simple Then
        If B = 0 Then N = 0 Else N = A / B
        ToRatioEx = Replace(Format(N, "0.00000E+00"), ",", ".")
    ElseIf A = 0 Or B = 0 Then
        ToRatioEx = "None"
    Else
        N = B / A: If N > 1000 Then ToRatioEx = "1" & Sep & Replace(Format(N, "0.00E+00"), ",", ".") _
        Else ToRatioEx = "1" & Sep & Replace(Format(N, FShort), ",", ".")
        End If
    End Function

Function ToPercent(ByVal N As Double, Optional Fmt As String = FZero) As String
    ToPercent = Replace(Format(N * 100, Fmt) & "%", ",", ".")
    End Function

Function ToAirSpeed(Optional N As Double, Optional Fmt As String = FShort) As String
    If N >= Mach Then ToAirSpeed = "Mach " & NumM(N / Mach, Fmt) _
    Else If Units Then ToAirSpeed = NumX(N / Knot, Fmt) & "kn" _
    Else ToAirSpeed = NumX(N / Kph, Fmt) & "kph"
    End Function

Function ToGroundSpeed(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then ToGroundSpeed = NumX(N * 1000 / NMile, Fmt) & "knots" _
    Else ToGroundSpeed = NumX(N, Fmt) & "kph"
    End Function

Function ToSpeed(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / Knot
        Select Case Abs(N)
        'Case Is < 0.000000001: ToSpeed = Replace(Format(0, Fmt), ",", ".") & "kn"
        'Case Is < 0.000001: ToSpeed = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9kn"
        'Case Is < 0.001: ToSpeed = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6kn"
        'Case Is < 1: ToSpeed = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3kn"
        Case Is < 1000: ToSpeed = Replace(Format(N, Fmt), ",", ".") & "kn"
        Case Is < 1000000: ToSpeed = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "kn"
        'Case Is < 1000000: ToSpeed = Replace(Format(N / 1000, Fmt), ",", ".") & "E+3kn"
        Case Is < 1000000000: ToSpeed = Replace(Format(N / 1000000, Fmt), ",", ".") & "E+6kn"
        Case Else: ToSpeed = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E+9kn"
            End Select
    Else
        Select Case Abs(N)
        Case Is < 0.000000001: ToSpeed = Replace(Format(0, Fmt), ",", ".") & "m/s"
        Case Is < 0.000001: ToSpeed = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nm/s"
        Case Is < 0.001: ToSpeed = Replace(Format(N * 1000000, Fmt), ",", ".") & "um/s"
        Case Is < 1: ToSpeed = Replace(Format(N * 1000, Fmt), ",", ".") & "mm/s"
        Case Is < 1000: ToSpeed = Replace(Format(N, Fmt), ",", ".") & "m/s"
        Case Is < 1000000: ToSpeed = Replace(Format(N / 1000, Fmt), ",", ".") & "km/s"
        Case Is < 1000000000: ToSpeed = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm/s"
        Case Is < 1000000000000#: ToSpeed = Replace(Format(N / 1000000000, Fmt), ",", ".") & "Gm/s"
        Case Else: ToSpeed = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "Tm/s"
            End Select
        End If
    End Function

Function ToAccel(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / Foot
        Select Case Abs(N)
        'Case Is < 0.000000001: ToAccel = Replace(Format(0, Fmt), ",", ".") & "ft/s²"
        'Case Is < 0.000001: ToAccel = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9ft/s²"
        'Case Is < 0.001: ToAccel = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6ft/s²"
        'Case Is < 1: ToAccel = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3ft/s²"
        Case Is < 1000: ToAccel = Replace(Format(N, Fmt), ",", ".") & "ft/s²"
        Case Is < 1000000: ToAccel = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "ft/s²"
        'Case Is < 1000000: ToAccel = Replace(Format(N / 1000, Fmt), ",", ".") & "E+3ft/s²"
        Case Is < 1000000000: ToAccel = Replace(Format(N / 1000000, Fmt), ",", ".") & "E+6ft/s²"
        Case Else: ToAccel = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E+9ft/s²"
            End Select
    Else
        Select Case Abs(N)
        Case Is < 0.000000001: ToAccel = Replace(Format(0, Fmt), ",", ".") & "m/s²"
        Case Is < 0.000001: ToAccel = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nm/s²"
        Case Is < 0.001: ToAccel = Replace(Format(N * 1000000, Fmt), ",", ".") & "um/s²"
        Case Is < 1: ToAccel = Replace(Format(N * 1000, Fmt), ",", ".") & "mm/s²"
        Case Is < 1000: ToAccel = Replace(Format(N, Fmt), ",", ".") & "m/s²"
        Case Is < 1000000: ToAccel = Replace(Format(N / 1000, Fmt), ",", ".") & "km/s²"
        Case Is < 1000000000: ToAccel = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm/s²"
        Case Is < 1000000000000#: ToAccel = Replace(Format(N / 1000000000, Fmt), ",", ".") & "Gm/s²"
        Case Else: ToAccel = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "Tm/s²"
            End Select
        End If
    End Function

Function ToForceG(Optional F As Double, Optional M As Double, Optional Fmt As String = FLong) As String
    If M = 0 Then ToForceG = Replace(Format(F / G0, Fmt), ",", ".") & "G" _
    Else ToForceG = Replace(Format(F / M / G0, Fmt), ",", ".") & "G"
    End Function

Function ToControl(ByVal N As Double, Optional Fmt As String = FExt, _
        Optional Radius As Double = 1) As String
    If Not MagneticMoment Then ToControl = ToForce(N, Fmt): Exit Function
    N = N * Radius / Field
    Select Case Abs(N)
    Case Is < 0.000000000001: ToControl = Replace(Format(0, Fmt), ",", ".") & "Am²"
    Case Is < 0.000000001: ToControl = Replace(Format(N * 1000000000000#, Fmt), ",", ".") & "pAm²"
    Case Is < 0.000001: ToControl = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nAm²"
    Case Is < 0.001: ToControl = Replace(Format(N * 1000000, Fmt), ",", ".") & "uAm²"
    Case Is < 1: ToControl = Replace(Format(N * 1000, Fmt), ",", ".") & "mAm²"
    Case Is < 1000: ToControl = Replace(Format(N, Fmt), ",", ".") & "Am²"
    Case Is < 1000000: ToControl = Replace(Format(N / 1000, Fmt), ",", ".") & "kAm²"
    Case Is < 1000000000: ToControl = Replace(Format(N / 1000000, Fmt), ",", ".") & "MAm²"
    Case Is < 1000000000000#: ToControl = Replace(Format(N / 1000000000, Fmt), ",", ".") & "GAm²"
    Case Else: ToControl = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "TAm²"
        End Select
    End Function

Function ToForce(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / PoundForce
        Select Case Abs(N)
        'Case Is < 0.000000001: ToForce = Replace(Format(0, Fmt), ",", ".") & "lbf"
        'Case Is < 0.000001: ToForce = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9lbf"
        'Case Is < 0.001: ToForce = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6lbf"
        'Case Is < 1: ToForce = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3lbf"
        Case Is < 1000: ToForce = Replace(Format(N, Fmt), ",", ".") & "lbf"
        Case Is < 1000000: ToForce = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "lbf"
        'Case Is < 1000000: ToForce = Replace(Format(N / 1000, Fmt), ",", ".") & "E3lbf"
        Case Is < 1000000000: ToForce = Replace(Format(N / 1000000, Fmt), ",", ".") & "E6lbf"
        Case Else: ToForce = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E9lbf"
            End Select
    Else
        Select Case Abs(N)
        Case Is < 0.000000000001: ToForce = Replace(Format(0, Fmt), ",", ".") & "N"
        Case Is < 0.000000001: ToForce = Replace(Format(N * 1000000000000#, Fmt), ",", ".") & "pN"
        Case Is < 0.000001: ToForce = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nN"
        Case Is < 0.001: ToForce = Replace(Format(N * 1000000, Fmt), ",", ".") & "uN"
        Case Is < 1: ToForce = Replace(Format(N * 1000, Fmt), ",", ".") & "mN"
        Case Is < 1000: ToForce = Replace(Format(N, Fmt), ",", ".") & "N"
        Case Is < 1000000: ToForce = Replace(Format(N / 1000, Fmt), ",", ".") & "kN"
        Case Is < 1000000000: ToForce = Replace(Format(N / 1000000, Fmt), ",", ".") & "MN"
        Case Is < 1000000000000#: ToForce = Replace(Format(N / 1000000000, Fmt), ",", ".") & "GN"
        Case Else: ToForce = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "TN"
            End Select
        End If
    End Function

Function ToMass(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / Pound
        Select Case N
        'Case Is < 0.000000001: ToMass = Replace(Format(0, Fmt), ",", ".") & "lb"
        'Case Is < 0.000001: ToMass = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9lb"
        'Case Is < 0.001: ToMass = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6lb"
        'Case Is < 1: ToMass = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3lb"
        Case Is < 1000: ToMass = Replace(Format(N, Fmt), ",", ".") & "lb"
        Case Is < 1000000: ToMass = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "lb"
        'Case Is < 1000000: ToMass = Replace(Format(N / 1000, Fmt), ",", ".") & "E3lb"
        Case Is < 1000000000: ToMass = Replace(Format(N / 1000000, Fmt), ",", ".") & "E6lb"
        Case Else: ToMass = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E9lb"
            End Select
    Else
        Select Case N
        Case Is < 0.000000000001: ToMass = Replace(Format(0, Fmt), ",", ".") & "g"
        Case Is < 0.000000001: ToMass = Replace(Format(N * 1000000000000#, Fmt), ",", ".") & "ng"
        Case Is < 0.000001: ToMass = Replace(Format(N * 1000000000, Fmt), ",", ".") & "ug"
        Case Is < 0.001: ToMass = Replace(Format(N * 1000000, Fmt), ",", ".") & "mg"
        Case Is < 1: ToMass = Replace(Format(N * 1000, Fmt), ",", ".") & "g"
        Case Is < 1000: ToMass = Replace(Format(N, Fmt), ",", ".") & "kg"
        Case Is < 1000000: ToMass = Replace(Format(N / 1000, Fmt), ",", ".") & "Mg"
        Case Is < 1000000000#: ToMass = Replace(Format(N / 1000000, Fmt), ",", ".") & "Gg"
        Case Else: ToMass = Replace(Format(N / 1000000000, Fmt), ",", ".") & "Tg"
            End Select
        End If
    End Function

Function ToEnergy(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / PoundForce / Foot
        Select Case Abs(N)
        'Case Is < 0.000000001: ToEnergy = Replace(Format(0, Fmt), ",", ".") & "ft·lb"
        'Case Is < 0.000001: ToEnergy = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9ft·lb"
        'Case Is < 0.001: ToEnergy = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6ft·lb"
        'Case Is < 1: ToEnergy = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3ft·lb"
        Case Is < 1000: ToEnergy = Replace(Format(N, Fmt), ",", ".") & "ft·lb"
        Case Is < 1000000: ToEnergy = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "ft·lb"
        'Case Is < 1000000: ToEnergy = Replace(Format(N / 1000, Fmt), ",", ".") & "E3ft·lb"
        Case Is < 1000000000: ToEnergy = Replace(Format(N / 1000000, Fmt), ",", ".") & "E6ft·lb"
        Case Else: ToEnergy = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E9ft·lb"
            End Select
    Else
        Select Case Abs(N)
        Case Is < 0.000000001: ToEnergy = Replace(Format(0, Fmt), ",", ".") & "J/kg"
        Case Is < 0.000001: ToEnergy = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nJ/kg"
        Case Is < 0.001: ToEnergy = Replace(Format(N * 1000000, Fmt), ",", ".") & "uJ/kg"
        Case Is < 1: ToEnergy = Replace(Format(N * 1000, Fmt), ",", ".") & "mJ/kg"
        Case Is < 1000: ToEnergy = Replace(Format(N, Fmt), ",", ".") & "J/kg"
        Case Is < 1000000: ToEnergy = Replace(Format(N / 1000, Fmt), ",", ".") & "kJ/kg"
        Case Is < 1000000000: ToEnergy = Replace(Format(N / 1000000, Fmt), ",", ".") & "MJ/kg"
        Case Is < 1000000000000#: ToEnergy = Replace(Format(N / 1000000000, Fmt), ",", ".") & "GJ/kg"
        Case Else: ToEnergy = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "TJ/kg"
            End Select
        End If
    End Function

Function ToDensity(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / Pound * Foot3
        Select Case N
        Case Is < 0.000000001: ToDensity = Replace(Format(0, Fmt), ",", ".") & "lb/ft³"
        Case Is < 0.000001: ToDensity = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9lb/ft³"
        Case Is < 0.001: ToDensity = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6lb/ft³"
        Case Is < 1: ToDensity = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3lb/ft³"
        Case Is < 1000: ToDensity = Replace(Format(N, Fmt), ",", ".") & "lb/ft³"
        Case Is < 1000000: ToDensity = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "lb/ft³"
        'Case Is < 1000000: ToDensity = Replace(Format(N / 1000, Fmt), ",", ".") & "E3lb/ft³"
        Case Is < 1000000000: ToDensity = Replace(Format(N / 1000000, Fmt), ",", ".") & "E6lb/ft³"
        Case Else: ToDensity = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E9lb/ft³"
            End Select
    Else
        Select Case N
        Case Is < 0.000000000001: ToDensity = Replace(Format(0, Fmt), ",", ".") & "g/m³"
        Case Is < 0.000000001: ToDensity = Replace(Format(N * 1000000000000#, Fmt), ",", ".") & "ng/m³"
        Case Is < 0.000001: ToDensity = Replace(Format(N * 1000000000, Fmt), ",", ".") & "ug/m³"
        Case Is < 0.001: ToDensity = Replace(Format(N * 1000000, Fmt), ",", ".") & "mg/m³"
        Case Is < 1: ToDensity = Replace(Format(N * 1000, Fmt), ",", ".") & "g/m³"
        Case Is < 1000: ToDensity = Replace(Format(N, Fmt), ",", ".") & "kg/m³"
        Case Is < 1000000: ToDensity = Replace(Format(N / 1000, Fmt), ",", ".") & "Mg/m³"
        Case Is < 1000000000#: ToDensity = Replace(Format(N / 1000000, Fmt), ",", ".") & "Gg/m³"
        Case Else: ToDensity = Replace(Format(N / 1000000000, Fmt), ",", ".") & "Tg/m³"
            End Select
        End If
    End Function

Function ToPressure(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / Psi
        Select Case N
        Case Is < 0.000000001: ToPressure = Replace(Format(0, Fmt), ",", ".") & "psi"
        Case Is < 0.000001: ToPressure = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9psi"
        Case Is < 0.001: ToPressure = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6psi"
        Case Is < 1: ToPressure = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3psi"
        Case Is < 1000: ToPressure = Replace(Format(N, Fmt), ",", ".") & "psi"
        Case Is < 1000000: ToPressure = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "psi"
        'Case Is < 1000000: ToPressure = Replace(Format(N / 1000, Fmt), ",", ".") & "E3psi"
        Case Is < 1000000000: ToPressure = Replace(Format(N / 1000000, Fmt), ",", ".") & "E6psi"
        Case Else: ToPressure = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E9psi"
            End Select
    Else
        Select Case N
        Case Is < 0.000000001: ToPressure = Replace(Format(0, Fmt), ",", ".") & "Pa"
        Case Is < 0.000001: ToPressure = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nPa"
        Case Is < 0.001: ToPressure = Replace(Format(N * 1000000, Fmt), ",", ".") & "uPa"
        Case Is < 1: ToPressure = Replace(Format(N * 1000, Fmt), ",", ".") & "mPa"
        Case Is < 1000: ToPressure = Replace(Format(N, Fmt), ",", ".") & "Pa"
        Case Is < 1000000: ToPressure = Replace(Format(N / 1000, Fmt), ",", ".") & "kPa"
        Case Is < 1000000000: ToPressure = Replace(Format(N / 1000000, Fmt), ",", ".") & "MPa"
        Case Is < 1000000000000#: ToPressure = Replace(Format(N / 1000000000, Fmt), ",", ".") & "GPa"
        Case Else: ToPressure = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "TPa"
            End Select
        End If
    End Function

Function ToPressureAtm(ByVal N As Double, Optional Fmt As String = FExt) As String
    If AtmPressure Then ToPressureAtm = NumX(N / Pascal, Fmt) & "atm" _
    Else ToPressureAtm = ToPressure(N, Fmt)
    End Function

Function ToAlt(ByVal N As Double, _
        Optional Fmt As String = FExt, Optional ByVal AddGeoid As Boolean) As String
    If AddGeoid Then N = N - GeoidAltitude
    If Units Then
        N = N / Foot
        Select Case Abs(N)
        Case Is < 0.001: ToAlt = Replace(Format(0, Fmt), ",", ".") & "ft"
        'Case Is < 0.000000001: ToAlt = Replace(Format(0, Fmt), ",", ".") & "ft"
        'Case Is < 0.000001: ToAlt = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9ft"
        'Case Is < 0.001: ToAlt = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6ft"
        'Case Is < 1: ToAlt = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3ft"
        Case Is < 1000: ToAlt = Replace(Format(N, Fmt), ",", ".") & "ft"
        Case Is < 1000000: ToAlt = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "ft"
        'Case Is < 1000000: ToAlt = Replace(Format(N / 1000, Fmt), ",", ".") & "E3ft"
        Case Is < 1000000000: ToAlt = Replace(Format(N / 1000000, Fmt), ",", ".") & "E6ft"
        Case Is < 1000000000000#: ToAlt = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E9ft"
        Case Is < 1E+15: ToAlt = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "E12ft"
        Case Else: ToAlt = Replace(Format(N / 1E+15, Fmt), ",", ".") & "E15ft"
            End Select
    Else
        Select Case Abs(N)
        Case Is < 0.001: ToAlt = Replace(Format(0, Fmt), ",", ".") & "m"
        'Case Is < 0.000000001: ToAlt = Replace(Format(0, Fmt), ",", ".") & "m"
        'Case Is < 0.000001: ToAlt = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nm"
        'Case Is < 0.001: ToAlt = Replace(Format(N * 1000000, Fmt), ",", ".") & "um"
        'Case Is < 1: ToAlt = Replace(Format(N * 1000, Fmt), ",", ".") & "mm"
        Case Is < 1000: ToAlt = Replace(Format(N, Fmt), ",", ".") & "m"
        Case Is < 1000000: ToAlt = Replace(Format(N / 1000, Fmt), ",", ".") & "km"
        Case Is < 1000000000: ToAlt = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm"
        Case Is < 1000000000000#: ToAlt = Replace(Format(N / 1000000000, Fmt), ",", ".") & "Gm"
        Case Is < 1E+15: ToAlt = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "Tm"
        Case Is < 1E+18: ToAlt = Replace(Format(N / 1E+15, Fmt), ",", ".") & "Pm"
        Case Else: ToAlt = Replace(Format(N / 1E+18, Fmt), ",", ".") & "Em"
            End Select
        End If
    End Function

Function ToAltEx(ByVal N As Double, Optional Fmt As String = FOne) As String
    If Units Then ToAltEx = Replace(Format(N / Foot, "#," & Replace(Fmt, ".", "")), ".", ",") & "ft" _
    Else ToAltEx = Replace(Format(N, Fmt), ",", ".") & "m"
    End Function

Function ToDistance(ByVal N As Double, Optional Fmt As String = FExt) As String
    If Units Then
        N = N / Mile
        Select Case N
        Case Is < 0.001: ToDistance = Replace(Format(0, Fmt), ",", ".") & "mi"
        'Case Is < 0.000000001: ToDistance = Replace(Format(0, Fmt), ",", ".") & "mi"
        'Case Is < 0.000001: ToDistance = Replace(Format(N * 1000000000, Fmt), ",", ".") & "E-9mi"
        'Case Is < 0.001: ToDistance = Replace(Format(N * 1000000, Fmt), ",", ".") & "E-6mi"
        'Case Is < 1: ToDistance = Replace(Format(N * 1000, Fmt), ",", ".") & "E-3mi"
        Case Is < 1000: ToDistance = Replace(Format(N, Fmt), ",", ".") & "mi"
        Case Is < 1000000: ToDistance = Replace(Format(N, "#," & Replace(Fmt, ".", "")), ".", ",") & "mi"
        'Case Is < 1000000: ToDistance = Replace(Format(N / 1000, Fmt), ",", ".") & "E3mi"
        Case Is < 1000000000: ToDistance = Replace(Format(N / 1000000, Fmt), ",", ".") & "E6mi"
        Case Is < 1000000000000#: ToDistance = Replace(Format(N / 1000000000, Fmt), ",", ".") & "E9mi"
        Case Is < 1E+15: ToDistance = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "E12ft"
        Case Else: ToDistance = Replace(Format(N / 1E+15, Fmt), ",", ".") & "E15ft"
            End Select
    Else
        Select Case N
        Case Is < 0.001: ToDistance = Replace(Format(0, Fmt), ",", ".") & "m"
        'Case Is < 0.000000001: ToDistance = Replace(Format(0, Fmt), ",", ".") & "m"
        'Case Is < 0.000001: ToDistance = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nm"
        'Case Is < 0.001: ToDistance = Replace(Format(N * 1000000, Fmt), ",", ".") & "um"
        'Case Is < 1: ToDistance = Replace(Format(N * 1000, Fmt), ",", ".") & "mm"
        Case Is < 1000: ToDistance = Replace(Format(N, Fmt), ",", ".") & "m"
        Case Is < 1000000: ToDistance = Replace(Format(N / 1000, Fmt), ",", ".") & "km"
        Case Is < 1000000000: ToDistance = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm"
        Case Is < 1000000000000#: ToDistance = Replace(Format(N / 1000000000, Fmt), ",", ".") & "Gm"
        Case Is < 1E+15: ToDistance = Replace(Format(N / 1000000000000#, Fmt), ",", ".") & "Tm"
        Case Is < 1E+18: ToDistance = Replace(Format(N / 1E+15, Fmt), ",", ".") & "Pm"
        Case Else: ToDistance = Replace(Format(N / 1E+18, Fmt), ",", ".") & "Em"
            End Select
        End If
    End Function

Function ToSize(ByVal N As Double, Optional Fmt As String = FMini, _
        Optional ByVal Dec As Boolean) As String
    If Units Then
        If N < 0 Then
            Select Case N
            Case Is > -Foot: ToSize = Replace(Format(N / Inch, Fmt), ",", ".") & "in"
            Case Else: ToSize = Replace(Format(N / Foot, Fmt), ",", ".") & "ft"
                End Select
        Else
            Select Case N
            Case Is < Foot: ToSize = Replace(Format(N / Inch, Fmt), ",", ".") & "in"
            Case Else: ToSize = Replace(Format(N / Foot, Fmt), ",", ".") & "ft"
                End Select
            End If
    ElseIf Dec Then
        If N < 0 Then
            Select Case N
            Case Is > -0.000000000001: ToSize = Replace(Format(0, Fmt), ",", ".") & "m"
            Case Is > -0.000000001: ToSize = Replace(Format(N * 1000000000, Fmt), ",", ".") & "pm"
            Case Is > -0.000001: ToSize = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nm"
            Case Is > -0.001: ToSize = Replace(Format(N * 1000000, Fmt), ",", ".") & "um"
            Case Is > -1: ToSize = Replace(Format(N * 1000, Fmt), ",", ".") & "mm"
            Case Is > -1000: ToSize = Replace(Format(N, Fmt), ",", ".") & "m"
            Case Is > -1000000: ToSize = Replace(Format(N / 1000, Fmt), ",", ".") & "km"
            Case Else: ToSize = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm"
                End Select
        Else
            Select Case N
            Case Is < 0.000000000001: ToSize = Replace(Format(0, Fmt), ",", ".") & "m"
            Case Is < 0.000000001: ToSize = Replace(Format(N * 1000000000, Fmt), ",", ".") & "pm"
            Case Is < 0.000001: ToSize = Replace(Format(N * 1000000000, Fmt), ",", ".") & "nm"
            Case Is < 0.001: ToSize = Replace(Format(N * 1000000, Fmt), ",", ".") & "um"
            Case Is < 1: ToSize = Replace(Format(N * 1000, Fmt), ",", ".") & "mm"
            Case Is < 1000: ToSize = Replace(Format(N, Fmt), ",", ".") & "m"
            Case Is < 1000000: ToSize = Replace(Format(N / 1000, Fmt), ",", ".") & "km"
            Case Else: ToSize = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm"
                End Select
            End If
    Else
        If N < 0 Then
            Select Case N
            Case Is > -0.001: ToSize = Replace(Format(0, Fmt), ",", ".") & "m"
            Case Is > -1: ToSize = Replace(Format(N * 1000, Fmt), ",", ".") & "mm"
            Case Is > -1000: ToSize = Replace(Format(N, Fmt), ",", ".") & "m"
            Case Is > -1000000: ToSize = Replace(Format(N / 1000, Fmt), ",", ".") & "km"
            Case Else: ToSize = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm"
                End Select
        Else
            Select Case N
            Case Is < 0.001: ToSize = Replace(Format(0, Fmt), ",", ".") & "m"
            Case Is < 1: ToSize = Replace(Format(N * 1000, Fmt), ",", ".") & "mm"
            Case Is < 1000: ToSize = Replace(Format(N, Fmt), ",", ".") & "m"
            Case Is < 1000000: ToSize = Replace(Format(N / 1000, Fmt), ",", ".") & "km"
            Case Else: ToSize = Replace(Format(N / 1000000, Fmt), ",", ".") & "Mm"
                End Select
            End If
        End If
    End Function

Function ToArea(ByVal N As Double, Optional Fmt As String = FMini) As String
    If Units Then
        Select Case N
        Case Is < Foot2: ToArea = Replace(Format(N / Inch2, "#," & Fmt), ",", ".") & "in²"
        Case Else: ToArea = Replace(Format(N / Foot2, "#," & Fmt), ",", ".") & "ft²"
            End Select
    Else
        Select Case N
        Case Is < 0.000001: ToArea = Replace(Format(0, Fmt), ",", ".") & "m²"
        Case Is < 0.0001: ToArea = Replace(Format(N * 1000000, Fmt), ",", ".") & "mm²"
        'Case Is < 0.01: ToArea = Replace(Format(N * 10000, Fmt), ",", ".") & "cm²"
        'Case Is < 1: ToArea = Replace(Format(N * 100, Fmt), ",", ".") & "dm²"
        Case Is < 1: ToArea = Replace(Format(N * 10000, Fmt), ",", ".") & "cm²"
        Case Is < 1000000: ToArea = Replace(Format(N, Fmt), ",", ".") & "m²"
        Case Else: ToArea = Replace(Format(N / 1000000, Fmt), ",", ".") & "km²"
            End Select
        End If
    End Function

Function ToVolume(ByVal N As Double, Optional Fmt As String = FMini) As String
    If Units Then
        Select Case N
        Case Is < Foot3: ToVolume = Replace(Format(N / Inch3, "#," & Fmt), ",", ".") & "in³"
        Case Else: ToVolume = Replace(Format(N / Foot3, "#," & Fmt), ",", ".") & "ft³"
            End Select
    Else
        Select Case N
        Case Is < 0.000000001: ToVolume = Replace(Format(0, Fmt), ",", ".") & "m³"
        Case Is < 0.000001: ToVolume = Replace(Format(N * 1000000000, Fmt), ",", ".") & "mm³"
        'Case Is < 0.001: ToVolume = Replace(Format(N * 1000000, Fmt), ",", ".") & "cm³"
        'Case Is < 1: ToVolume = Replace(Format(N * 1000, Fmt), ",", ".") & "dm³"
        Case Is < 1: ToVolume = Replace(Format(N * 1000000, Fmt), ",", ".") & "cm³"
        Case Is < 1000000000: ToVolume = Replace(Format(N, Fmt), ",", ".") & "m³"
        Case Else: ToVolume = Replace(Format(N / 1000000000, Fmt), ",", ".") & "km³"
            End Select
        End If
    End Function

Function ToClass(ByVal N As Double, _
        Optional C As Long, Optional Pre As String, Optional Sep As String) As String
    If N > 0 Then C = Int(VBA.Log(N / 10) / Log2) + 3: _
        If C >= 0 Then If C <= 25 Then ToClass = Pre & Chr(C + 65) & Sep _
        Else ToClass = Pre & C - 24 & "Z" & Sep
    End Function

Function RoundTime(Optional N As Double) As Double
    RoundTime = Int(N / dT + 0.5) * dT
    End Function

Function RoundDay(Optional N As Double) As Double
    RoundDay = Int(N * 86400 / dT + 0.5) * dT / 86400
    End Function

Function ToDhms(ByVal T As Double, Optional ByVal Short As Boolean, _
        Optional ByVal Dec As Boolean = True) As String
    Dim X As Long, Y As Long, D As Long, H As Long, M As Long, S As Long, R As Long
    X = Int(T): Y = X \ Year: D = (X \ Day) Mod 365: H = (X \ Hour) Mod 24
    M = (X \ Min) Mod 60: S = X Mod 60: R = Int((T - X) * 1000)
    If Short Then
        If Y > 0 Then
            If Dec Then ToDhms = Format(Y, "0:") & Format(D, "000:") & _
                Format(H, "00:") & Format(M, "00:") & Format(S, "00 ") & Format(X, "000000") & "." & Format(R, "000s") _
            Else ToDhms = Format(Y, "0:") & Format(D, "000:") & _
                Format(H, "00:") & Format(M, "00:") & Format(S, "00 ") & Format(X, "000000s")
        Else
            If Dec Then ToDhms = Format(D, "0:") & _
                Format(H, "00:") & Format(M, "00:") & Format(S, "00 ") & Format(X, "000000") & "." & Format(R, "000s") _
            Else ToDhms = Format(D, "0:") & _
                Format(H, "00:") & Format(M, "00:") & Format(S, "00 ") & Format(X, "000000s")
            End If
    Else
        If Y > 0 Then
            If Dec Then ToDhms = Format(Y, "0y ") & Format(D, "0d ") & _
                Format(H, "00h ") & Format(M, "00m ") & Format(S, "00") & "." & Format(R, "000s") _
            Else ToDhms = Format(Y, "0y ") & Format(D, "0d ") & _
                Format(H, "00h ") & Format(M, "00m ") & Format(S, "00s")
        Else
            If Dec Then ToDhms = Format(D, "0d ") & _
                Format(H, "00h ") & Format(M, "00m ") & Format(S, "00") & "." & Format(R, "000s") _
            Else ToDhms = Format(D, "0d ") & _
                Format(H, "00h ") & Format(M, "00m ") & Format(S, "00s")
            End If
        End If
    End Function

Function ToDhmsEx(ByVal T As Double, Optional ByVal Fixed As Boolean, _
        Optional ByVal Short As Boolean = True, Optional ByVal Dec As Boolean) As String
    Dim X As Long, Y As Long, D As Long, H As Long, M As Long, S As Long, R As Long
    X = Int(T): Y = X \ Year: D = (X \ Day) Mod 365: H = (X \ Hour) Mod 24
    M = (X \ Min) Mod 60: S = X Mod 60: R = Int((T - X) * 1000)
    If Fixed Then
        If Dec Then ToDhmsEx = Format(Y, "0") & "." & Format(D, "000") & "." & _
                               Format(H, "00") & "." & Format(M, "00") & "." & Format(S, "00") & "." & Format(R, "000") _
               Else ToDhmsEx = Format(Y, "0") & "." & Format(D, "000") & "." & _
                               Format(H, "00") & "." & Format(M, "00") & "." & Format(S, "00")
    ElseIf Y > 0 Then
        If Dec Then ToDhmsEx = Format(Y, "0:") & Format(D, "000:") & _
                               Format(H, "00:") & Format(M, "00:") & Format(S, "00") & "." & Format(R, "000") _
               Else ToDhmsEx = Format(Y, "0:") & Format(D, "000:") & _
                               Format(H, "00:") & Format(M, "00:") & Format(S, "00")
    ElseIf D > 0 Or Not Short Then
        If Dec Then ToDhmsEx = Format(D, "0:") & _
                               Format(H, "00:") & Format(M, "00:") & Format(S, "00") & "." & Format(R, "000") _
               Else ToDhmsEx = Format(D, "0:") & _
                               Format(H, "00:") & Format(M, "00:") & Format(S, "00")
    ElseIf H > 0 Then
        If Dec Then ToDhmsEx = Format(H, "0:") & Format(M, "00:") & Format(S, "00") & "." & Format(R, "000") _
               Else ToDhmsEx = Format(H, "0:") & Format(M, "00:") & Format(S, "00")
    Else
        If Dec Then ToDhmsEx = Format(M, "0:") & Format(S, "00") & "." & Format(R, "000") _
               Else ToDhmsEx = Format(M, "0:") & Format(S, "00")
        End If
    End Function

Function ToDh(ByVal T As Double, Optional ByVal Extended As Boolean) As String
    Dim X As Long, Y As Long, D As Long, H As Long, M As Long, S As Long, R As Long
    X = Int(T): Y = X \ Year: D = (X \ Day) Mod 365: H = (X \ Hour) Mod 24
    M = (X \ Min) Mod 60: S = X Mod 60
    If Extended Then R = Int((T - X) * 100): ToDh = IfText(Y, Format(Y, "0y ")) & _
        IfText(D, Format(D, "0d ")) & IfText(H, Format(H, "0h ")) & _
        IfText(M, Format(M, "0m ")) & Format(S, "0") & "." & Format(R, "00s") _
    Else ToDh = IfText(Y, Format(Y, "0y ")) & _
        IfText(D, Format(D, "0d ")) & IfText(H, Format(H, "0h ")) & _
        IfText(M, Format(M, "0m ")) & IfText(S, Format(S, "0s "))
    End Function

Function ToDhEx(ByVal T As Double, Optional ByVal Extended As Boolean) As String
    Dim Y As Long, D As Long, H As Long, M As Long, S As Long
    Y = Int(T / Year): T = T - CDbl(Y) * Year: D = Int(T / Day): T = T - CDbl(D) * Day
    H = Int(T / Hour): T = T - CDbl(H) * Hour: M = Int(T / Min): T = T - CDbl(M) * Min: S = Int(T)
    ToDhEx = IfText(Y, Format(Y, "0y ")) & _
        IfText(D, Format(D, "0d ")) & IfText(H, Format(H, "0h ")) & _
        IfText(M > 0 And Y = 0, Format(M, "0m ")) & IfText(S > 0 And D = 0, Format(S, "0s "))
    End Function

Function ToMs(ByVal T As Double) As String
    Dim X As Long, M As Long, S As Long, R As Long
    X = Int(T): M = X \ Min: S = X Mod 60: R = Int((T - X) * 10)
    ToMs = IfText(M, Format(M, "0m ")) & Format(S, "00") & "." & Format(R, "0s")
    End Function

Function ToHms(ByVal T As Double, Optional Fmt As String = "0", _
        Optional ByVal UseDec As Boolean) As String
    If T >= Year Then ToHms = Replace(Format(T / Year, Fmt), ",", ".") & " year" _
    Else If T >= Day Then ToHms = Replace(Format(T / Day, Fmt), ",", ".") & " day" _
    Else If T >= Hour Then ToHms = Replace(Format(T / Hour, Fmt), ",", ".") & " hour" _
    Else If T >= Min Then ToHms = Replace(Format(T / Min, Fmt), ",", ".") & " min" _
    Else If T >= 1 Or Not UseDec Then ToHms = Replace(Format(T, Fmt), ",", ".") & " sec" _
    Else If T >= 0.001 Then ToHms = Replace(Format(T * 1000, Fmt), ",", ".") & " msec" _
    Else If T >= 0.000001 Then ToHms = Replace(Format(T * 1000000, Fmt), ",", ".") & " µsec" _
    Else If T >= 0.000000001 Then ToHms = Replace(Format(T * 1000000000, Fmt), ",", ".") & " nsec" _
    Else ToHms = Replace(Format(T * 1000000000000#, Fmt), ",", ".") & " psec"
    End Function

Function ToHmsEx(ByVal T As Double, Optional ByVal Short As Boolean) As String
    Dim X As Long, D As Long, H As Long, M As Long, S As Long
    X = Int(T): D = X \ Day: H = (X \ Hour) Mod 24: M = (X \ Min) Mod 60: S = X Mod 60
    If D > 0 Then
        If Short Then ToHmsEx = Format(D, "0:") & Format(H, "00:") & Format(M, "00") _
        Else ToHmsEx = Format(D, "0:") & Format(H, "00:") & Format(M, "00:") & Format(S, "00")
    Else
        If Short Then ToHmsEx = Format(H, "0:") & Format(M, "00") _
        Else ToHmsEx = Format(H, "00:") & Format(M, "00:") & Format(S, "00")
        End If
    End Function

Function ValDateEx(T As String, Optional ByVal NoSep As Boolean, _
        Optional ByVal Mode As Long, Optional Invalid As Long) As Double
    Dim Y As Long, M As Long, D As Long, X() As String
    If NoSep Then
        If Len(T) >= 6 Then D = Val(Left(T, 2)): _
            M = Val(Mid(T, 3, 2)): Y = Val(Mid(T, 5, 2))
    Else
        M = 1: D = 1: X = Split(T, Switch(InStr(T, "-"), "-", InStr(T, "/"), "/", _
            InStr(T, ":"), ":", InStr(T, "."), ".", InStr(T, ","), ",", _
            True, " "), 3)
        Select Case Mode
        Case 0 'YYYY-MM-DD
            If UBound(X) >= 2 Then Y = Val(X(0)): M = Val(X(1)): D = Val(X(2)) _
            Else If UBound(X) = 1 Then Y = Val(X(0)): M = Val(X(1)) _
            Else If UBound(X) = 0 Then Y = Val(X(0)) Else Y = Val(T)
        Case 1 'DD-MM-YYYY
            If UBound(X) >= 2 Then D = Val(X(0)): M = Val(X(1)): Y = Val(X(2))
        Case 2 'MM-DD-YYYY
            If UBound(X) >= 2 Then M = Val(X(0)): D = Val(X(1)): Y = Val(X(2))
            End Select
        End If
    If Y < 0 Then Y = 0: Invalid = True Else If Y < 90 Then Y = Y + 2000 _
    Else If Y < 100 Then Y = Y + 1900 Else If Y > 9999 Then Y = 9999: Invalid = True
    If M < 1 Then M = 1: Invalid = True Else If M > 12 Then M = 12: Invalid = True
    If D < 1 Then D = 1: Invalid = True Else If D > 31 Then D = 31: Invalid = True
    ValDateEx = DateToJ2ks(Y, M, D)
    End Function

Function ValTimeEx(T As String, Optional ByVal NoSep As Boolean, _
        Optional Invalid As Long) As Double
    Dim H As Long, M As Long, S As Double, X() As String
    If NoSep Then
        If Len(T) >= 6 Then H = Val(Left(T, 2)): M = Val(Mid(T, 3, 2)): S = Val(Mid(T, 5))
    Else
        X = Split(T, Switch(InStr(T, "-"), "-", InStr(T, "/"), "/", _
            InStr(T, ":"), ":", InStr(T, "."), ".", InStr(T, ","), ",", _
            True, " "), 3)
        If UBound(X) >= 2 Then H = Val(X(0)): M = Val(X(1)): S = Val(X(2)) _
        Else If UBound(X) = 1 Then H = Val(X(0)): M = Val(X(1)) _
        Else If UBound(X) = 0 Then H = Val(X(0)) Else H = Val(T)
        End If
    If H < 0 Then H = 0: Invalid = True Else If H > 23 Then H = 23: Invalid = True
    If M < 0 Then M = 0: Invalid = True Else If M > 59 Then M = 59: Invalid = True
    If S < 0 Then S = 0: Invalid = True Else If S > 59.999999 Then S = 59.999999: Invalid = True
    ValTimeEx = S + M * Min + H * Hour
    End Function

Function ValShortDateTime(T As String, Optional ByVal Sep As Boolean) As Double
    Dim Y As Long, M As Long, D As Long, H As Long, N As Long, S As Double
    Y = Val(Left(T, 4)): M = Val(Mid(T, 5, 2)): D = Val(Mid(T, 7, 2))
    H = Val(Mid(T, 9 - Sep, 2)): N = Val(Mid(T, 11 - Sep, 2)): S = Val(Mid(T, 13 - Sep))
    ValShortDateTime = DateToJ2ks(Y, M, D, H, N, S)
    End Function

Function ValShortDateTimeEx(T As String, Optional ByVal Sep As Boolean = True) As Double
    Dim Y As Long, M As Long, D As Long, H As Long
    Y = Val(Left(T, 4)): M = Val(Mid(T, 5, 2)): D = Val(Mid(T, 7, 2))
    H = Val(Mid(T, 9 - Sep, 2))
    ValShortDateTimeEx = DateToJ2ks(Y, M, D, H)
    End Function

Function ValShortDate(T As String) As Double
    Dim Y As Long, M As Long, D As Long
    Y = Val(Left(T, 4)): M = Val(Mid(T, 5, 2)): D = Val(Mid(T, 7, 2))
    ValShortDate = DateToJ2ks(Y, M, D)
    End Function

Function ValShortTime(T As String) As Double
    Dim H As Long, M As Long, S As Double
    H = Val(Left(T, 2)): M = Val(Mid(T, 3, 2)): S = Val(Mid(T, 5))
    ValShortTime = S + M * Min + H * Hour
    End Function

Function ValEpoch(T As String, _
        Optional ByVal Pos As Long = 1, Optional ByVal Dec As Long = -1) As Double 'YYYYMMDD[.DD ..]
    ValEpoch = DateToJ2ks(Val(Mid(T, Pos, 4)), Val(Mid(T, Pos + 4, 2)), Val(Mid(T, Pos + 6, 2)))
    If Dec < 0 Then ValEpoch = ValEpoch + Val(Mid(T, Pos + 8)) * Day _
    Else If Dec > 0 Then ValEpoch = ValEpoch + Val(Mid(T, Pos + 8, Dec + 1)) * Day
    End Function

Function DateToJ2ks(Optional ByVal Y As Long, Optional ByVal M As Long = 1, _
        Optional ByVal D As Long = 1, Optional ByVal H As Long, _
        Optional ByVal N As Long, Optional ByVal S As Double, _
        Optional Q As Long) As Double
    '2000-01-01 12:00:00 UTC = J2000 = 2451545.0 JD
    Dim C As Long, X As Long, J As Long
    If M < 3 Then M = M + 12: Y = Y - 1
    C = Y \ 100: X = Y Mod 100
    J = C * 146097 \ 4 + X * 36525 \ 100 + (M * 153 - 457) \ 5 + D - 1
    DateToJ2ks = (CDbl(J) - 730425.5) * Day + H * Hour + N * Min + S 'J2000=-730425.5 JD=+1721119.5
    Q = (J + 730431) Mod 7 + 1
    End Function

Sub J2ksToDate(Optional J As Double, Optional Y As Long, Optional M As Long, _
        Optional D As Long, Optional H As Long, Optional N As Long, _
        Optional S As Double, Optional Q As Long)
    Dim C1 As Long, C2 As Long, X0 As Long, X1 As Long, X2 As Long
    X2 = Int(J / Day + 730425.5) 'J2000=+730425.5 JD=-1721119.5
    C2 = (X2 * 4 + 3) \ 146097: X1 = X2 - C2 * 146097 \ 4
    C1 = (X1 * 100 + 99) \ 36525: X0 = X1 - C1 * 36525 \ 100
    Y = C2 * 100 + C1: M = (X0 * 5 + 461) \ 153: D = X0 - (M * 153 - 457) \ 5 + 1
    If M > 12 Then M = M - 12: Y = Y + 1
    S = J + Day / 2: S = S - Int(S / Day) * Day: H = Int(S / Hour): N = Int((S - H * Hour) / Min)
    S = S - H * Hour - N * Min
    Q = (X2 + 730431) Mod 7 + 1
    End Sub

Function J2ksDateTime(Optional J As Double, Optional ByVal DayName As Boolean = True, _
        Optional ByVal MonthName As Boolean = True) As String
    Dim Y As Long, M As Long, D As Long, H As Long, N As Long, S As Double, Q As Long
    J2ksToDate J, Y, M, D, H, N, S, Q
    J2ksDateTime = Format(Y, "0000 ") & IIf(MonthName, Months(M), Format(M, "00")) & _
        Format(D, " 00 ") & IfText(DayName, Days(Q)) & _
        Format(H, " 00:") & Format(N, "00:") & Format(Int(S), "00")
    End Function

Function J2ksDateTimeEx(Optional J As Double) As String
    Dim Y As Long, M As Long, D As Long, H As Long
    J2ksToDate J, Y, M, D, H
    J2ksDateTimeEx = Format(Y, "0000") & Months(M) & Format(D, "00 ") & Format(H, "00Z")
    End Function

Function J2ksDate(Optional J As Double) As String
    Dim Y As Long, M As Long, D As Long, Q As Long
    J2ksToDate J, Y, M, D, , , , Q
    J2ksDate = Format(Y, "0000") & Format(M, "-00") & Format(D, "-00")
    End Function

Function J2ksDateEx(Optional J As Double, Optional ByVal DayName As Boolean = True, _
        Optional ByVal MonthName As Boolean = True) As String
    Dim Y As Long, M As Long, D As Long, Q As Long
    J2ksToDate J, Y, M, D, , , , Q
    J2ksDateEx = Format(Y, "0000 ") & IIf(MonthName, Months(M), Format(M, "00")) & _
        Format(D, " 00 ") & IfText(DayName, Days(Q))
    End Function

Function J2ksTime(Optional J As Double, Optional ByVal Dec As Boolean) As String
    Dim H As Long, N As Long, S As Double
    J2ksToDate J, , , , H, N, S
    J2ksTime = Format(H, "00:") & Format(N, "00:") & Format(Int(S), "00")
    If Dec Then J2ksTime = J2ksTime & "." & Format(Int((S - Int(S)) * 10), "0")
    End Function

Function J2ksTimeEx(Optional J As Double) As String
    Dim H As Long, N As Long
    J2ksToDate J, , , , H, N
    J2ksTimeEx = Format(H, "00:") & Format(N, "00")
    End Function

Function J2ksNow(Optional ByVal UseLocal As Boolean) As Double
    Dim T As Long64, U As Long64
    If UseLocal Then GetSystemTimeAsFileTime U: FileTimeToLocalFileTime U, T _
    Else GetSystemTimeAsFileTime T
    J2ksNow = T.H * 4294967296# + T.L
    If T.L < 0 Then J2ksNow = J2ksNow + 4294967296#
    J2ksNow = J2ksNow / 10000000 - 145731.5 * Day 'UTC 100 ns since -> 1 Jan 1601DC -> -145731.5 (J2000)
    End Function

Function J2ksNowDay() As Double
    J2ksNowDay = (Int(J2ksNow / Day - 0.5) + 0.5) * Day
    End Function

Function J2ksNowTime() As Double
    J2ksNowTime = J2ksNow: J2ksNowTime = J2ksNowTime - (Int(J2ksNowTime / Day - 0.5) + 0.5) * Day
    End Function

Function ShortJ2ksDateTime(Optional J As Double, Optional ByVal Mode As Long = 2) As String
    Dim Y As Long, M As Long, D As Long, H As Long, N As Long, S As Double
    J2ksToDate J, Y, M, D, H, N, S
    If Mode = 0 Then        '0=YYYYMMDD
        ShortJ2ksDateTime = Format(Y, "0000") & Format(M, "00") & Format(D, "00")
    ElseIf Mode = 1 Then    '1=YYYYMMDDHH
        ShortJ2ksDateTime = Format(Y, "0000") & Format(M, "00") & Format(D, "00") & Format(H, "00")
    ElseIf Mode = 3 Then    '3=HHMMSS
        ShortJ2ksDateTime = Format(H, "00") & Format(N, "00") & Format(Int(S), "00")
    Else                    '2=YYYYMMDDHHMMSS
        ShortJ2ksDateTime = Format(Y, "0000") & Format(M, "00") & Format(D, "00") & _
                            Format(H, "00") & Format(N, "00") & Format(Int(S), "00")
        End If
    End Function

Function KMLTime(Optional J As Double) As String '2011-12-02T14:44:51.000Z
    Dim Y As Long, M As Long, D As Long, H As Long, N As Long, S As Double, Q As Long, C As Long
    J2ksToDate J, Y, M, D, H, N, S, Q: C = Int(S)
    KMLTime = Format(Y, "0000") & "-" & Format(M, "00") & "-" & Format(D, "00") & "T" & _
        Format(H, "00") & ":" & Format(N, "00") & ":" & Format(C, "00") & "." & _
        Format(Int((S - C) * 100), "000") & "Z"
    End Function

Sub SetTime(Optional ByVal J As Double, Optional ByVal Relative As Boolean, _
        Optional ByVal Update As Boolean, Optional ByVal Dec As Boolean)
    With Setup
    If Relative Then J = J + ValDateEx(.Date) + ValTimeEx(.Time)
    If J < MinDate Then J = MinDate Else If J > MaxDate Then J = MaxDate
    .Date = J2ksDate(J): .Date.Refresh
    .Time = J2ksTime(J, Dec): .Time.Refresh
    End With
    If Update And Play = Paused Then If T = 0 Then Init False, False, , , , , , True, True
    End Sub

Sub SetTimeEx(Optional NewDate As String, Optional NewTime As String, _
        Optional ByVal InitNav As Boolean)
    If NewDate <> "" Then Setup.Date = NewDate
    If NewTime <> "" Then Setup.Time = NewTime
    If InitNav And Play = Paused And T = 0 Then Init False, False
    End Sub

Function ShortDate(Optional ByVal LocalTime As Boolean = True) As String
    Dim T As SystemTime
    If LocalTime Then GetLocalTime T Else GetSystemTime T
    ShortDate = Format(T.Year, "0000") & Format(T.Month, "00") & Format(T.Day, "00")
    End Function

Function ShortTime(Optional ByVal LocalTime As Boolean = True) As String
    Dim T As SystemTime
    If LocalTime Then GetLocalTime T Else GetSystemTime T
    ShortTime = Format(T.Hour, "00") & Format(T.Minute, "00") & Format(T.Second, "00")
    End Function

Function ShortDateTime(Optional ByVal LocalTime As Boolean = True) As String
    Dim T As SystemTime
    If LocalTime Then GetLocalTime T Else GetSystemTime T
    ShortDateTime = _
        Format(T.Year, "0000") & Format(T.Month, "00") & Format(T.Day, "00") & "." & _
        Format(T.Hour, "00") & Format(T.Minute, "00") & Format(T.Second, "00")
    End Function

Function FileDate(FileName As String) As String
    Dim T As Date
    On Error Resume Next
    Err = 0: T = FileDateTime(FileName): If Err Then Exit Function
    FileDate = _
        Format(VBA.Year(T), "0000") & Format(VBA.Month(T), "00") & Format(VBA.Day(T), "00") & _
        Format(VBA.Hour(T), "00") & Format(VBA.Minute(T), "00") & Format(VBA.Second(T), "00")
    On Error GoTo 0
    End Function

Function FileDateEx(FileName As String) As String
    Dim T As Date
    On Error Resume Next
    Err = 0: T = FileDateTime(FileName): If Err Then Exit Function
    FileDateEx = _
        Format(VBA.Year(T), "0000-") & Format(VBA.Month(T), "00-") & Format(VBA.Day(T), "00  ") & _
        Format(VBA.Hour(T), "00:") & Format(VBA.Minute(T), "00:") & Format(VBA.Second(T), "00")
    On Error GoTo 0
    End Function

Sub Sel(X As Object, Optional ByVal Full As Boolean, Optional ByVal Focus As Boolean)
    Dim C As Long, S As String
    If X Is Nothing Then Exit Sub
    If Focus Then X.SetFocus
    X.SelStart = 0: If Full Then X.SelLength = &HFFFF&: Exit Sub
    S = X: For C = 1 To Len(S)
        Select Case Mid(S, C, 1)
        Case ".", ",", "-", "+", "0" To "9"
        Case Else: If C > 1 Then C = C - 1 Else C = &HFFFF&
            Exit For
            End Select
        Next
    X.SelLength = C
    End Sub

Function TimerEx() As Double
    Static M As Long64, F As Double
    If F = 0 Then QueryPerformanceFrequency M: F = M.H * 4294967296# + M.L: _
        If M.L < 0 Then F = F + 4294967296#
    QueryPerformanceCounter M: TimerEx = M.H * 4294967296# + M.L
    If M.L < 0 Then TimerEx = (TimerEx + 4294967296#) / F Else TimerEx = TimerEx / F
    End Function

Sub Wait(Optional ByVal T As Double, Optional ByVal Async As Boolean = True)
    T = TimerEx + T
    Do
        If Async Then DoEvents
        Loop While TimerEx < T
    End Sub

Sub SetSync(Optional ByVal Value As Boolean = True)
    SyncTimer = Value
    If SyncTimer Then
        If Not Main.Sync Then Main.Sync = True
    Else
        If Not TacticTimer Then If Main.Sync Then Main.Sync = False
        End If
    End Sub

Sub SetTactic(Optional ByVal Value As Boolean = True)
    TacticTimer = Value
    If TacticTimer Then
        If Not Main.Sync Then Main.Sync = True
    Else
        If Not SyncTimer Then If Main.Sync Then Main.Sync = False
        End If
    End Sub

Sub SyncExec(Optional ByVal Mode As PlayMode = NextStep, Optional Shift As Integer, _
        Optional ByVal UsePlay As Boolean)
    Select Case Mode
    Case Toggle
DoToggle:
        If SyncMode Then GoTo DoStop Else GoTo DoStart
    Case Start
DoStart:
        If Play = Paused And Not SyncMode Then
            If T <> 0 Then Init
DoForceStart:
            SyncMode = True: Info: If SyncTime = 0 Or UsePlay Then _
                If Not SyncTimer Then SetSync: If SyncTime <> 0 Then ToolBar.SetIcon ToolBar.Play, 2
            End If
    Case Reset
DoStop:
        If SyncMode Then SyncMode = False: ToolBar.SetIcon ToolBar.Play: _
            Info: If SyncTimer Then SetSync False
    Case Pause
DoPause:
        If SyncTimer Then SetSync False: ToolBar.SetIcon ToolBar.Play _
        Else SyncMode = True: SetSync True: ToolBar.SetIcon ToolBar.Play, 2
    Case NextStep
DoNext:
        If SyncMode Then _
            If Play = Active Or T <> 0 Then GoTo DoStop _
            Else If SyncTime = 0 Then SetTime J2ksNow, , True, True _
            Else SetTime SyncTime, True, True: If Time >= MaxDate Then GoTo DoPause
DoRec:
        If Play = Paused And Record = Active Then RecordVideo
    Case Restart
DoUpdate:
        If SyncMode Then SetTime J2ksNow, , True
    Case Forward
DoForward:
        If SyncMode Then If Not SyncTimer Then _
            SetTime IfDbl(Shift, SyncTime * 10, SyncTime), True, True
    Case Backward
DoBackward:
        If SyncMode Then If Not SyncTimer Then _
            SetTime -IfDbl(Shift, SyncTime * 10, SyncTime), True, True
    Case Faster
DoFaster:
        If SyncTime = 0 Then ToolBar.SetIcon ToolBar.Play, 2
        SyncTime = RampUp(SyncTime, 0, 1, 10, Min, 10 * Min, Hour, 6 * Hour, _
            Day, 10 * Day, 30 * Day, 120 * Day, Year, 10 * Year)
        If Not SyncTimer Then Info
    Case Slower
DoSlower:
        If SyncTime = 1 Then If SyncTimer Then ToolBar.SetIcon ToolBar.Play _
            Else SyncTime = 0: GoTo DoForceStart
        SyncTime = RampDown(SyncTime, 0, 1, 10, Min, 10 * Min, Hour, 6 * Hour, _
            Day, 10 * Day, 30 * Day, 120 * Day, Year, 10 * Year)
        If Not SyncTimer Then Info
        End Select
    End Sub

Sub InvertAxes(Optional ByVal Mask As Long = 3, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    If Mask And 1 Then X = -X
    If Mask And 2 Then Y = -Y
    If Mask And 4 Then Z = -Z
    End Sub

Sub InvertAxis(Optional ByVal Mask As Boolean = True, Optional N As Double)
    If Mask Then N = -N
    End Sub

Sub StepUp(Optional ByVal Smooth As Boolean)
    NumAdd dT, IfInt(Smooth, 1, 3), dTMin, dTMax: If Play = Paused Then Info
    End Sub

Sub StepDown(Optional ByVal Smooth As Boolean)
    NumAdd dT, -IfInt(Smooth, 1, 3), dTMin, dTMax: If Play = Paused Then Info
    End Sub

Sub SpeedUp(Optional ByVal Smooth As Boolean)
    Cam1.Auto = False: Cam(0).Auto = False: IsRealTime = False: Options.UpdateSimulation
    NumAdd TimeSpeed, IfInt(Smooth, 1, 3), SpeedMin, SpeedMax: If Play = Paused Then Info
    End Sub

Sub SpeedDown(Optional ByVal Smooth As Boolean)
    Cam1.Auto = False: Cam(0).Auto = False: IsRealTime = False: Options.UpdateSimulation
    NumAdd TimeSpeed, -IfInt(Smooth, 1, 3), SpeedMin, SpeedMax: If Play = Paused Then Info
    End Sub

Sub SetAsymmetric(Optional N As Double, Optional ByVal Relative As Boolean)
    If Relative Then N = N + Asymmetric
    Asymmetric = Round(N * 100) / 100: ResizeMain
    End Sub

Sub ResetCam(Cam As Camera)
    With Cam
    .AngleW = 0
    If .Flat Then
        If .AngleU <> 0 Or .AngleV <> -90 Then .AngleU = 0: .AngleV = -90 _
        Else .AngleU = 45: .AngleV = -150
    ElseIf .Follow = ToTactic Then
        If .AngleU <> 0 Then .AngleU = 0 _
        Else If .AngleV <> -90 Then .AngleV = -90 _
        Else If .AngleV <> -180 Then .AngleV = -180 _
        Else .AngleV = -90
    Else
        If .AngleU <> 0 Then .AngleU = 0 _
        Else If .AngleV <> 0 Then .AngleV = 0 _
        Else If .AngleV <> -90 Then .AngleV = -90 _
        Else .AngleV = 0
        End If
    End With
    If Play = Paused Then Render: _
        If Cam.ID = MainID Then If DataToolX And DataCamera Then Info: Data.Pic.Refresh
    End Sub

Sub ResetModel(Cam As Camera)
    With Cam
    Select Case .ID
    Case RoverID
        .AngleW = 0: .Altitude = 10
             If .AngleU = -30 And .AngleV = 15 Then .AngleU = -90: .AngleV = 0 _
        Else If .AngleU = -90 And .AngleV = 0 Then .AngleU = 0: .AngleV = 0 _
        Else If .AngleU = 0 And .AngleV = 0 Then .AngleU = 0: .AngleV = 90 _
        Else .AngleU = -30: .AngleV = 15
    Case StageID
        .AngleW = 0: .Altitude = 65: .X = 0: .Y = 20: .Z = 0
             If .AngleU = -135 And .AngleV = 0 Then .AngleU = -90: .AngleV = 0 _
        Else If .AngleU = -90 And .AngleV = 0 Then .AngleU = 0: .AngleV = 0 _
        Else If .AngleU = 0 And .AngleV = 0 Then .AngleU = -90: .AngleV = 90: .Y = 0 _
        Else .AngleU = -135: .AngleV = 0
    Case GraphID
        .AngleW = 0: If Graph2D(.N).CamZ <= 0 Then Graph2D(.N).CamZ = 250
        If .X <> Graph2D(.N).XC Or .Y <> Graph2D(.N).YC Or .Z <> Graph2D(.N).ZC Or .Altitude <> Graph2D(.N).CamZ Then _
            .X = Graph2D(.N).XC: .Y = Graph2D(.N).YC: .Z = Graph2D(.N).ZC: .Altitude = Graph2D(.N).CamZ _
        Else If .AngleU = 45 And .AngleV = 45 Then .AngleU = 0: .AngleV = 0 _
        Else If .AngleU = 0 And .AngleV = 0 Then .AngleU = 90: .AngleV = 0 _
        Else If .AngleU = 90 And .AngleV = 0 Then .AngleU = 0: .AngleV = 90 _
        Else .AngleU = 45: .AngleV = 45
        End Select
    End With
    If Cam.Ortho Then InitCamera Cam
    RenderModel Cam
    End Sub

Sub FixCam(Cam As Camera)
    Dim Rt As TargetType, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double
    With Cam
    If Not .Flat Then
        If .Relative >= ToEarth And .Relative <= ToPluto Then Rt = .Relative Else Rt = Target
        If Rt < ToEarth Or Rt > ToPluto Or Rt = ToAir Then Rt = ToEarth
        Select Case .Follow
        Case ToObject
            ObjectPosition SelectedType, SelectedN, X2, Y2, Z2
            If IsObjectTactic(SelectedType, SelectedN, Rt) Then GoTo FixTactic
            .X = X2: .Y = Y2: .Z = Z2
        Case ToTactic
FixTactic:
            XYZ2LLAID Rt, Y1, X1, Z1, X2, Y2, Z2: H1 = PlanetRadius(Rt) / .FOVR
            .X = X1 * H1: .Y = Y1 * H1: .Z = Z1
        Case ToEarth To ToPluto
            PlanetPosition .Follow, .X, .Y, .Z
            PlanetPosition Rt, X1, Y1, Z1: .X = .X - X1: .Y = .Y - Y1: .Z = .Z - Z1
        Case Else
            PlanetPosition Rt, X1, Y1, Z1: .X = -X1: .Y = -Y1: .Z = -Z1
            End Select
        End If
    .Free = True
    End With
    End Sub

Sub MoveCam(Cam As Camera, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal Redraw As Boolean, Optional ByVal Relative As Boolean = True)
    Dim R As Double
    With Cam
    If .Follow = ToTactic Or .Follow = ToObject Or (.Follow >= ToShip And .Follow <= ToPluto) Or .Flat Then _
        If LinkMode = Disabled And Not .Free Then FixCam Cam
    If Relative Then
        R = .Altitude * .FOVR / .Square / 1.2: X = X * R: Y = -Y * R: Z = -Z * R
        If .hRC Then Rotate -.AngleU * Rad, X, Y
        InvertAxes AxesMove, X, Y, Z: .X = .X + X: .Y = .Y + Y: .Z = .Z + Z
    Else
        .X = X: .Y = Y: .Z = Z
        End If
    End With
    If Play = Paused Then Render: _
        If Cam.ID = MainID Then If DataToolX And DataCamera Then Info: Data.Pic.Refresh
    If Redraw Then If Cam1.hRC = 0 Then Main.Refresh
    End Sub

Sub MoveModel(Cam As Camera, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal Relative As Boolean = True, Optional ByVal Rotated As Boolean)
    With Cam
    X = X * .Altitude / .Square / 1.2: Y = -Y * .Altitude / .Square / 1.2: Z = -Z * .Altitude / .Square / 1.2
    If Not Relative Then
        .X = X: .Y = Y: .Z = Z
    Else
        If Rotated Then Rotate -.AngleU * Rad, X, Y
        InvertAxes AxesMove, X, Y, Z: .X = .X + X: .Y = .Y + Y: .Z = .Z + Z
        End If
    End With
    RenderModel Cam
    End Sub

Sub RotateCam(Cam As Camera, _
        Optional U As Double, Optional V As Double, Optional W As Double, _
        Optional ByVal Relative As Boolean = True)
    If Cam1.hRC = 0 Then Exit Sub
    With Cam
    If Relative Then InvertAxes AxesRotate, U, V, W: _
        .AngleU = .AngleU + U: .AngleV = .AngleV + V: .AngleW = .AngleW + W _
    Else .AngleU = U: .AngleV = V: .AngleW = W
    End With
    If Play = Paused Then Render: _
        If Cam.ID = MainID Then If DataToolX And DataCamera Then Info: Data.Pic.Refresh
    End Sub

Sub RotateModel(Cam As Camera, _
        Optional U As Double, Optional V As Double, Optional W As Double, _
        Optional ByVal Relative As Boolean = True)
    If Cam1.hRC = 0 Then Exit Sub
    With Cam
    If Relative Then InvertAxes AxesRotate, U, V, W: _
        .AngleU = .AngleU + U: .AngleV = .AngleV + V: .AngleW = .AngleW + W _
    Else .AngleU = U: .AngleV = V: .AngleW = W
    If .AngleV < -90 Then .AngleV = -90 Else If .AngleV > 90 Then .AngleV = 90
    End With
    RenderModel Cam
    End Sub

Sub ZoomIn(Cam As Camera, Optional Shift As Integer, Optional ByVal Redraw As Boolean)
    ZoomCam Cam, IfDbl(Shift, 0.2, 0.6), Redraw
    End Sub

Sub ZoomOut(Cam As Camera, Optional Shift As Integer, Optional ByVal Redraw As Boolean)
    ZoomCam Cam, -IfDbl(Shift, 0.2, 0.6), Redraw
    End Sub

Sub ZoomCam(Cam As Camera, Optional ByVal H As Double, _
        Optional ByVal Redraw As Boolean, Optional ByVal Relative As Boolean = True)
    With Cam
    Cam1.Auto = False: Sys.Cam(0).Auto = False
    If Not Relative Then .Altitude = H _
    Else InvertAxis AxesZoom, H: _
        If H > 0 Then .Altitude = .Altitude / (1 + H) _
        Else If H < 0 Then .Altitude = .Altitude * (1 - H)
    If .Altitude < ZoomMin Then .Altitude = ZoomMin _
    Else If .Altitude > ZoomMax Then .Altitude = ZoomMax
    End With
    InitRender
    If Play = Paused Then Render: Info
    If Redraw Then Data.Refresh: If Cam1.hRC = 0 Then Main.Refresh
    End Sub

Sub ZoomModel(Cam As Camera, Optional ByVal H As Double, _
        Optional ByVal Redraw As Boolean, Optional ByVal Relative As Boolean = True)
    With Cam
    If Not Relative Then .Altitude = H _
    Else InvertAxis AxesZoom, H: _
        If H > 0 Then .Altitude = .Altitude / (1 + H) _
        Else If H < 0 Then .Altitude = .Altitude * (1 - H)
    If .Altitude < ZoomModelMin Then .Altitude = ZoomModelMin _
    Else If .Altitude > ZoomModelMax Then .Altitude = ZoomModelMax
    InitCamera Cam: RenderModel Cam
    End With
    End Sub

Sub FOVCam(Cam As Camera, Optional F As Double = 10, _
        Optional ByVal Redraw As Boolean, Optional ByVal Relative As Boolean = True)
    With Cam
    If Relative Then InvertAxis AxesZoom, F: .FOV = .FOV + F Else .FOV = F
    If .FOV < FOVMin Then .FOV = FOVMin Else If .FOV > FOVMax Then .FOV = FOVMax
    .Free = False
    End With
    InitRender
    If Play = Paused Then Render
    If Redraw Then Data.Refresh: If Cam1.hRC = 0 Then Main.Refresh
    End Sub

Sub FOVModel(Cam As Camera, Optional F As Double = 10, Optional ByVal Relative As Boolean)
    With Cam
    If Relative Then InvertAxis AxesZoom, F: .FOV = .FOV + F Else .FOV = F
    If .FOV < FOVMin Then .FOV = FOVMin Else If .FOV > FOVMax Then .FOV = FOVMax
    .Free = False
    End With
    InitCamera Cam: RenderModel Cam
    End Sub

Function IsTargetPlanet(ByVal Target As TargetType) As Boolean
    IsTargetPlanet = (Target >= ToEarth And Target <= ToSun) Or (Target >= ToMercury And Target <= ToPluto)
    End Function

Sub FollowSwitch(Cam As Camera)
    Select Case Cam.Follow
    Case ToShip: FollowMode Cam, ToTactic
    Case ToTactic: FollowMode Cam, ToSimul
    Case Else: FollowMode Cam, ToShip
        End Select
    End Sub

Sub FollowMode(Cam As Camera, Optional ByVal NewMode As TargetType = ToNext)
    Dim H1 As Double
    With Cam
    If NewMode = ToObject Then IsObjectValid LastType, LastN: _
        If .Follow <> ToObject And IsTargetPlanet(.Follow) And _
                LastType = TypeNone Then _
            LastType = TypePlanet: LastN = .Follow _
        Else If LastType >= TypeSatellite And LastType < TypeGhost Then _
            .AngleU = 0: .AngleV = 0: .AngleW = 0
    If .Flat Then
        If NewMode = ToShip Or NewMode = ToOnBoard Then
            .Free = False
        Else
            If NewMode = ToNone And .Free Then .Free = False _
            Else .Free = True: .Ortho = True: .X = 0: .Y = 0: .Z = 0: _
                .AngleU = 0: .AngleV = -90: .AngleW = 0: .Altitude = 49000000 / .FOVR
            NewMode = ToShip
            End If
        If .Follow <> NewMode Then .Follow = NewMode
    Else
        If NewMode = ToNext Then _
            If .Free Then NewMode = ToNone _
            Else NewMode = .Follow + 1: If NewMode > ToMoon Then NewMode = ToSimul
        If NewMode = ToNone And Not .Free Then
            If .Follow = ToSimul Or .Follow = ToOnBoard Then .Follow = ToShip
            .Free = True: FixCam Cam
        Else
            .Free = False: If NewMode <> ToNone Then .Follow = NewMode
            End If
        Select Case .Follow
        Case ToSimul: .AngleU = 0: .AngleW = 0 ': If .ID = ViewID Then .F.Caption = "Simul"
        Case ToTactic: .AngleU = 0: .AngleV = -135: .AngleW = 0 ': If .ID = ViewID Then .F.Caption = "Tactic"
        Case ToOnBoard: .AngleU = 0: .AngleV = 0: .AngleW = 0: .FOV = 0
        Case ToShip ': If .ID = ViewID Then .F.Caption = "View"
        Case ToObject ': If .ID = ViewID Then .F.Caption = "Object"
        Case Else: H1 = PlanetRadius(.Follow) * 10: If .Altitude < H1 Then .Altitude = H1
            If .Follow >= ToMercury And .Follow <= ToPluto And (Tool And ToolPlanets) = 0 Then SetTool ToolPlanets
            'If .ID = ViewID Then .F.Caption = Planets(.Follow)
            End Select
        End If
    End With
    InitRender
    If Play = Paused Then Render: Info
    End Sub

Sub LookAtMode(Cam As Camera, Optional ByVal NewMode As TargetType = ToNext)
    Dim S As String
    With Cam
    If NewMode <> ToNext Then .LookAt = NewMode _
    Else If .LookAt >= ToSun Then .LookAt = ToNone _
    Else .LookAt = .LookAt + 1
    If .LookAt = ToSimul Or .LookAt = ToTactic Then .LookAt = ToShip
    If .LookAt >= ToMercury Then If (Tool And ToolPlanets) = 0 Then SetTool ToolPlanets
    End With
    If Play = Paused Then Render
    End Sub

Sub RelativeMode(Cam As Camera, Optional ByVal NewMode As TargetType = ToNext)
    With Cam
    If NewMode <> ToNext Then .Relative = NewMode _
    Else If .Relative < ToEarth Then .Relative = ToEarth _
    Else If .Relative >= ToSun Then .Relative = ToNone _
    Else .Relative = .Relative + 1
    If .Relative >= ToMercury And (Tool And ToolPlanets) = 0 Then SetTool ToolPlanets
    If .Free Then FixCam Cam
    End With
    If Play = Paused Then Render: Info
    End Sub

Sub TargetMode(Cam As Camera, Optional ByVal NewMode As TargetType = ToNone)
    Cam.Target = NewMode: If Play = Paused Then Render: Info
    End Sub

Sub OrthoMode(Cam As Camera)
    With Cam: .Ortho = Not .Ortho
    If .Follow = ToOnBoard Then Exit Sub
    End With
    InitRender
    If Play = Paused Then Render
    End Sub

Sub OrthoModel(Cam As Camera)
    Cam.Ortho = Not Cam.Ortho: InitCamera Cam: RenderModel Cam
    End Sub

Sub FlatMode(Cam As Camera)
    With Cam
    .Flat = Not .Flat
    If .Follow = ToOnBoard Then Exit Sub
    If Not .Flat Then .Ortho = False: .X = 0: .Y = 0: .Z = 0: FollowMode Cam, ToTactic _
    Else .Free = False: FollowMode Cam, ToNone
    End With
    InitRender
    If Play = Paused Then Render
    End Sub

Function LoadPathEx(Optional Name As String, Optional ByVal Convert As Boolean) As Boolean
    Dim A As Long, B As Long, C As Long, D As Long, N As Long, L As Boolean, _
        X As Double, S As String, T As String, U As String, _
        Num As Long, Cmd As String, Last As String, P As NMEA ', NewName As String
    Static Op As OpenFileName
    On Error GoTo Ext
    If Name = "" Then Name = GetOpenFile(Op, , FileAll, PathsDir): If Name = "" Then Exit Function
    T = LoadFile(Name): If T = "" Then Exit Function
    Num = 1: If Not Convert Then _
        KMLPathN = 0: KMLPathTop = 0: KMLPathPos = 0: KMLPathAlt = 0: ReDim KMLPath(100 - 1): _
        KMLPointN = 0: ReDim KMLPoint(10 - 1)
    
    If InStr(T, "#, ElapsedTime[s]") Then 'CSV path file
    
    If Convert Then Exit Function
    A = 1: Do
        S = GetLine(T, A): If S = "" Then Exit Do
        If Left(S, 1) = "#" Then GoTo Nxt
        If KMLPathN > UBound(KMLPath) Then ReDim Preserve KMLPath(UBound(KMLPath) + 100)
        With KMLPath(KMLPathN)
        '#, ElapsedTime[s], Julian2000Seconds[s], DateUTC[YYYY-MM-DD], TimeUTC[HH:MM:SS], Latitude[deg], Longitude[deg], Altitude[m], HorizontalSpeed[m/s], VerticalSpeed[m/s], Density[kg/m³], Pressure[Pa], Temperature[K], RelativeHumidity[%], CloudWater[kg/kg], WindSpeedX[m/s), WindSpeedY[m/s), WindSpeedZ[m/s), Name
        Param S: Param S: .Time = NumN(S): Param S: Param S: If .Time = 0 Then Exit Do
        .Lat = NumN(S) * Rad: .Lon = NumN(S) * Rad: .Alt = NumN(S)
        .SpeedH = NumN(S): .SpeedV = NumN(S): .Density = NumN(S): .Pressure = NumN(S)
        .Temperature = NumN(S): .Humidity = NumN(S): .CloudWater = NumN(S)
        .WindX = NumN(S): .WindY = NumN(S): .WindZ = NumN(S)
        If KMLPathTop = 0 Or .Alt > KMLPath(KMLPathTop).Alt Then KMLPathTop = KMLPathN
        End With
        U = Param(S)
        If U <> "" Then
            If KMLPointN > UBound(KMLPoint) Then ReDim Preserve KMLPoint(UBound(KMLPoint) + 10)
            CopyPoint KMLPoint(KMLPointN), KMLPath(KMLPathN), U, True
            End If
        KMLPathN = KMLPathN + 1
Nxt:
        Loop
    
    ElseIf InStr(T, "<LineString>") Then 'KML file
        
    If Convert Then Exit Function
    A = 1: Do
        S = ParamK(T, "Placemark", A): U = ParamK(S, "styleUrl")
        U = ParamK(T, "Style id=""" & Mid(U, 2) & """")
        U = ParamK(U, "LineStyle"): U = ParamK(U, "color")
        If U <> "" Then D = Val("&H" & U & "&") Else D = cPathKML
        S = ParamK(S, "LineString"): S = ParamK(S, "coordinates")
        C = KMLPathN
        If S <> "" Then
            B = 1: Do
                L = True: If KMLPathN > UBound(KMLPath) Then ReDim Preserve KMLPath(UBound(KMLPath) + 100)
                U = GetCmd(S, B, L): If B = 0 And U = "" Then Exit Do
                With KMLPath(KMLPathN)
                If KMLPathN = C Then .Color = D
                .Lon = Val(U) * Rad: If L Then _
                .Lat = Val(GetCmd(S, B, L)) * Rad: If L Then _
                .Alt = Val(GetCmd(S, B, L))
                If KMLPathTop = 0 Or .Alt > KMLPath(KMLPathTop).Alt Then KMLPathTop = KMLPathN
                End With
                KMLPathN = KMLPathN + 1
                Loop While B
            End If
        Loop While A
    If KMLPathN <= 0 Then KMLPathN = 0: Erase KMLPath _
    Else If UBound(KMLPath) <> KMLPathN - 1 Then ReDim Preserve KMLPath(KMLPathN - 1)
    
    A = 1: Do
        S = ParamK(T, "Placemark", A): U = Utf8ToAscii(ParamK(S, "name"))
        S = ParamK(S, "Point"): S = ParamK(S, "coordinates")
        If S <> "" Then
            L = True: If KMLPointN > UBound(KMLPoint) Then ReDim Preserve KMLPoint(UBound(KMLPoint) + 10)
            B = 1: With KMLPoint(KMLPointN)
            .Lon = Val(GetCmd(S, B, L)) * Rad: If L Then _
            .Lat = Val(GetCmd(S, B, L)) * Rad: If L Then _
            .Alt = Val(GetCmd(S, B, L))
            .Name = U: .Planet = ToEarth
            End With
            KMLPointN = KMLPointN + 1
            End If
        Loop While A
        
    ElseIf InStr(T, "$GPGGA") Then 'GPS NMEA file
    
    Do
        A = InStr(A + 1, T, "$"): If A = 0 Then Exit Do
        B = InStr(A + 1, T, "*"): If B = 0 Then Exit Do
        C = InStr(A + 1, T, "$"): If C = 0 Then C = B + 1
        If B < A Or C < B Then GoTo NoEnd Else If B - A > 128 Then GoTo Invalid
        With P
        Cmd = Mid(T, A, B - A + 3)
        C = Val("&H" & Right(Cmd, 2) & "&"): If C = CheckNMEA(Cmd) Then .Chk = C Else GoTo Invalid
        Mid(Cmd, Len(Cmd) - 2, 1) = ","
        Select Case Param(Cmd)
        '$GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh<CR><LF>
        Case "$GPGGA"                                                             'Message header
            S = Param(Cmd): If Last <> S Then PosNMEA P, Num, Convert: Last = S
                            .Time = ValTimeEx(S, True)                            'Time in UTC of position
            S = Param(Cmd): .Lat = (Val(Left(S, 2)) + Val(Mid(S, 3)) / Min)       'Latitude of position
            U = Param(Cmd): If U = "S" Then .Lat = -.Lat                          'Latitude N or S
            S = Param(Cmd): .Lon = (Val(Left(S, 3)) + Val(Mid(S, 4)) / Min)       'Longitude of position
            U = Param(Cmd): If U = "W" Then .Lon = -.Lon                          'Longitude E or W
            S = Param(Cmd): .Mode = Val(S)                                        'GPS quality                            (0=fix not available, 1=Non-differential GPS fix available, 2=Differential GPS (WAAS) fix available, 6=Estimated)
            S = Param(Cmd): .Sat = Val(S)                                         'Number of satellites                   (00 to 12)
            S = Param(Cmd): .HDOP = Val(S)                                        'Horizontal dilution of precision       (0.5 to 99.9)
            S = Param(Cmd): .Alt = Val(S)                                         'Antenna altitude above mean sea level  (-9999.9 to 99999.9)
            U = Param(Cmd)                                                        'Units of antenna altitude, meters      M
            S = Param(Cmd): .Geoid = Val(S)                                       'Height of geoid above ellipsoid        (-999.9 to 9999.9)
            U = Param(Cmd)                                                        'Units of geoid height, meters          M
                                                                                  'Age of differential GPS data, seconds  Null
                                                                                  'Differential reference station ID      Null
            '                                                                     'Checksum                               *HH
        '$GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh<CR><LF>
        Case "$GPRMC"                                                             'Message header
            S = Param(Cmd): If Last <> S Then PosNMEA P, Num, Convert: Last = S
                            .Time = ValTimeEx(S, True)                            'Time in UTC of position
            S = Param(Cmd): .Valid = S = "A"                                      'Validity A=OK V=Invalid
            S = Param(Cmd): .Lat = (Val(Left(S, 2)) + Val(Mid(S, 3)) / Min)       'Latitude of position
            U = Param(Cmd): If U = "S" Then .Lat = -.Lat                          'Latitude N or S
            S = Param(Cmd): .Lon = (Val(Left(S, 3)) + Val(Mid(S, 4)) / Min)       'Longitude of position
            U = Param(Cmd): If U = "W" Then .Lon = -.Lon                          'Longitude E or W
            S = Param(Cmd): .Speed = Val(S) * NMile / 1000                        'Speed over ground in knots
            S = Param(Cmd): .Heading = Val(S)                                     'True course
            S = Param(Cmd): .Day = ValDateEx(S, True)                             'Date in UTC of position
            S = Param(Cmd): .Mag = Val(S)                                         'Magnetic variation
            U = Param(Cmd): If U = "W" Then .Mag = -.Mag                          'Magnetic variation E or W
            '                                                                     'Checksum                               *HH
        '$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
        Case "$GPGSA"                                                             'Message header
            S = Param(Cmd)                                                        'Auto selection of 2D or 3D fix         (A = auto, M = manual)
            S = Param(Cmd)                                                        '3D fix                                 (1 = no fix, 2 = 2D fix, 3 = 3D fix)
            S = Param(Cmd)                                                        'PRNs of satellites used for fix (space for 12)
            S = Param(Cmd)                                                        '2
            S = Param(Cmd)                                                        '3
            S = Param(Cmd)                                                        '4
            S = Param(Cmd)                                                        '5
            S = Param(Cmd)                                                        '6
            S = Param(Cmd)                                                        '7
            S = Param(Cmd)                                                        '8
            S = Param(Cmd)                                                        '9
            S = Param(Cmd)                                                        '10
            S = Param(Cmd)                                                        '11
            S = Param(Cmd)                                                        '12
            S = Param(Cmd): .PDOP = Val(S)                                        'PDOP (Dilution of precision)
            S = Param(Cmd): .HDOP = Val(S)                                        'HDOP (Horizontal dilution of precision)
            S = Param(Cmd): .VDOP = Val(S)                                        'VDOP (Vertical dilution of precision)
                                                                                  'Checksum
            End Select
        End With
Invalid:
        A = B
NoEnd:
        Loop
    PosNMEA P, Num, Convert
    
    'ElseIf InStr(T, "<data>") Then 'Iridium XML messages

    '<data>
    '  <meta>
    '    <imei>300234011861070</imei>
    '  </meta>
    '  <nalGpsReport6>
    '    <time>2014-08-24T08:05:29.0Z</time>
    '    <lat>41,8583933</lat>
    '    <lng>-0,7517167</lng>
    '    <alt>307,88</alt>
    '    <gndVel>2,304</gndVel>
    '    <course>206,47</course>
    '    <verVel>0,1</verVel>
    '    <fix>Valid 3D</fix>
    '    <sats>7</sats>
    '    <hdop>1,06</hdop>
    '    <vdop>1,39</vdop>
    '    <motion>0</motion>
    '    <emer>0</emer>
    '    <emerAcked>0</emerAcked>
    '    <abCode>0</abCode>
    '    <cmCode>0</cmCode>
    '  </nalGpsReport6>
    '</data>
    
    'ElseIf InStr(T, "UTC Time=") Then 'Iridium TXT messages

    'Normal
    'UTC Time=08:52:47.0
    'UTC Date=08-24-2014
    'Latitude=41:45.1821 North
    'Longitude=000:23.3985 West
    'Altitude=10406.0 meters
    'Ground Velocity=80.1 km/h at 137.59 degrees from True North
    'Vertical Velocity=-3.9 m/s
    'Satellites Used=08
    'HDOP=1.04
    'VDOP=1.75
    
    Else 'Iridium CSV messages

    'Date and time (UTC), Latitude (degrees), Longitude (degrees), Altitude sea level (m), _
     Horizontal speed (km/h), True North course (degrees), Vertical speed (m/s), _
     Number of satélites used for fix, Horizontal disolution, Vertical disolution
    '02/09/2012 06:50:59 UTC,41.858390,-.751623,305.46,1.8000,347.81,001.8,05,1.62,2.52
    A = 1: Do
        S = Trim(GetLine(T, A))
        If S <> "" Then
            With P
            U = ParamX(S): .Time = ValDateEx(U, , 1)
            U = ParamX(S): .Time = .Time + ValTimeEx(U): Param S
            .Lat = NumN(S): .Lon = NumN(S): .Alt = NumN(S)
            .Speed = NumN(S) * Kph: .Heading = NumN(S): NumN S
            .Sat = NumI(S): .HDOP = NumN(S): .VDOP = NumN(S)
            If .Time > 0 Then .PDOP = 0: .Valid = True: .Mode = 1: PosNMEA P, Num, Convert
            End With
            End If
        Loop While A
        
    End If
    
    If Convert Then
        'NewName = FileReplaceExt(Name, "kml"): If FileExists(NewName) Then _
            NewName = FileReplaceName(NewName, NewName & ".new")
        Open FileReplaceExt(Name, "kml") For Output As 1: If FreeFile = 1 Then GoTo Ext
        Print #1, _
            "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf & _
            "<kml xmlns=""http://earth.google.com/kml/2.0"">" & vbCrLf & _
            "  <Document> <name>" & FileNameFromPath(Name) & "</name>" & vbCrLf & _
            "    <Style id=""point"">" & vbCrLf & _
            "       <IconStyle>" & vbCrLf & _
            "           <Icon>" & vbCrLf & _
            "               <href>http://earth.google.com/images/kml-icons/track-directional/track-none.png</href>" & vbCrLf & _
            "           </Icon>" & vbCrLf & _
            "       </IconStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf & _
            "    <Style id=""track"">" & vbCrLf & _
            "      <LineStyle>" & vbCrLf & _
            "        <color>ff80ffff</color>" & vbCrLf & _
            "        <width>2</width>" & vbCrLf & _
            "      </LineStyle>" & vbCrLf & _
            "      <PolyStyle>" & vbCrLf & _
            "        <color>3f00ff00</color>" & vbCrLf & _
            "      </PolyStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf & _
            "    <Folder> <name>Points</name>" & vbCrLf;
        Print #1, StrAdd;
        Print #1, _
            "    </Folder>" & vbCrLf & _
            "    <Placemark> <name>Path</name>" & vbCrLf & _
            "      <styleUrl>#track</styleUrl>" & vbCrLf & _
            "      <LineString>" & vbCrLf & _
            "        <extrude>1</extrude>" & vbCrLf & _
            "        <tessellate>1</tessellate>" & vbCrLf & _
            "        <altitudeMode>absolute</altitudeMode>" & vbCrLf & _
            "        <coordinates>" & vbCrLf;
        Print #1, StrAddEx;
        Print #1, _
            "        </coordinates>" & vbCrLf & _
            "      </LineString>" & vbCrLf & _
            "    </Placemark>" & vbCrLf & _
            "  </Document>" & vbCrLf & _
            "</kml>" & vbCrLf;
        Close
        Message "File converted" & IIf(Num > 1, " (" & Num - 1 & " points)", ""), 2000
    Else
        If KMLPathN <= 0 Then KMLPathN = 0: Erase KMLPath _
        Else If UBound(KMLPath) <> KMLPathN - 1 Then ReDim Preserve KMLPath(KMLPathN - 1)
        If KMLPointN = 0 And KMLPathN > 0 Then _
            CopyPoint KMLPoint(0), KMLPath(0), "Start", True: _
            CopyPoint KMLPoint(1), KMLPath(KMLPathN - 1), "End", True: _
            If KMLPathTop > 0 And KMLPathTop < KMLPathN - 1 Then _
                CopyPoint KMLPoint(2), KMLPath(KMLPathTop), "Apogee", True
        If KMLPointN <= 0 Then KMLPointN = 0: Erase KMLPoint _
        Else If UBound(KMLPoint) <> KMLPointN - 1 Then ReDim Preserve KMLPoint(KMLPointN - 1)
        End If
    If KMLPathN > 0 Then LoadPathEx = True
    Exit Function
Ext:
    Close
    On Error GoTo 0
    End Function

Sub PosNMEA(P As NMEA, Num As Long, ByVal Convert As Boolean)
    Dim S As String, T As Double, T2 As Double, H2 As Double
    With P
    If Not .Valid Or .Mode = 0 Then Exit Sub
    T = .Day + .Time
    If Convert Then
        '2011-12-02T14:44:51.000Z 1.986001,41.275239,14.900000
        S = NumX(.Lon, FLong) & "," & NumX(.Lat, FLong) & "," & NumX(.Alt, FOne)
        StrAdd _
            "      <Placemark> <name>" & Num & "</name>" & vbCrLf & _
            "        <styleUrl>#point</styleUrl>" & vbCrLf & _
            "        <description>" & vbCrLf & _
            "          <![CDATA[<table>" & vbCrLf & _
            "            <tr><th>Date</th><td>" & J2ksDateEx(T) & "</td></tr>" & vbCrLf & _
            "            <tr><th>Time</th><td>" & J2ksTime(T) & " UTC</td></tr>" & vbCrLf & _
            "            <tr><th>Latitude</th><td>" & ToLat(.Lat * Rad, FLong) & "</td></tr>" & vbCrLf & _
            "            <tr><th>Longitude</th><td>" & ToLon(.Lon * Rad, FLong) & "</td></tr>" & vbCrLf & _
            "            <tr><th>Altitude</th><td>" & ToAlt(.Alt, FMini) & "</td></tr>" & vbCrLf & _
            "            <tr><th>Speed</th><td>" & ToGroundSpeed(.Speed, FMini) & "</td></tr>" & vbCrLf & _
            "            <tr><th>Heading</th><td>" & NumX(.Heading, FMini) & "º</td></tr>" & vbCrLf & _
            "            <tr><th>HDOP</th><td>" & NumX(.HDOP, FShort) & "</td></tr>" & vbCrLf & _
            "            <tr><th>VDOP</th><td>" & NumX(.VDOP, FShort) & "</td></tr>" & vbCrLf & _
            "            <tr><th>PDOP</th><td>" & NumX(.PDOP, FShort) & "</td></tr>" & vbCrLf & _
            "            <tr><th>Satellites</th><td>" & .Sat & "  (" & IIf(.Valid, "Valid", "Not valid") & ", " & FixNMEA(.Mode) & ")</td></tr>" & vbCrLf & _
            "          </table>]]>" & vbCrLf & _
            "        </description>" & vbCrLf & _
            "        <TimeStamp> <when>" & KMLTime(T) & "</when> </TimeStamp>" & vbCrLf & _
            "        <Point>" & vbCrLf & _
            "          <altitudeMode>absolute</altitudeMode>" & vbCrLf & _
            "          <coordinates>" & S & "</coordinates>" & vbCrLf & _
            "        </Point>" & vbCrLf & _
            "      </Placemark>" & vbCrLf
        StrAddEx _
            "          " & S & vbCrLf
    Else
        If KMLPathN > UBound(KMLPath) Then ReDim Preserve KMLPath(UBound(KMLPath) + 100)
        With KMLPath(KMLPathN)
        .Color = 0: .Lon = P.Lon * Rad: .Lat = P.Lat * Rad: .Alt = P.Alt: .Time = T
        .SpeedH = P.Speed
        If KMLPathN > 0 Then
            With KMLPath(KMLPathN - 1): T2 = .Time: H2 = .Alt: End With
            If T > 0 And T2 > 0 And T > T2 Then .SpeedV = (.Alt - H2) / (T - T2)
            End If
        If KMLPathTop = 0 Or .Alt > KMLPath(KMLPathTop).Alt Then KMLPathTop = KMLPathN
        End With
        KMLPathN = KMLPathN + 1
        End If
    End With
    Num = Num + 1
    End Sub

Function CheckNMEA(Cmd As String) As Long 'Checksum: Two hex digits representing an 8 bit exclusive OR of all characters between, but not including, the '$' and '*'
    Dim N As Long
    For N = 2 To Len(Cmd) - 3: CheckNMEA = CheckNMEA Xor Asc(Mid(Cmd, N, 1)): Next
    End Function

Function FixNMEA(ByVal N As Long) As String
    FixNMEA = Choose(Limit(N, 0, 9), _
        "Fix not available", "Non-differential GPS fix", _
        "Differential GPS (WAAS) fix", "PPS fix", "Real Time Kinematic", _
        "Float RTK", "Estimated", "Manual input mode", "Simulation mode", _
        "Unknown")
    End Function

Function LoadPath(Optional Name As String, Optional ByVal Update As Boolean = True) As Boolean
    Static Op As OpenFileName
    If Name <> "" Then
        If Name = FileName(Name) Then Name = PathsDir & Name & ".bin"
    Else
        Name = GetOpenFile(Op, , FilePath, PathsDir): If Name = "" Then Exit Function
        End If
    If LCase(Right(Name, 4)) = ".bin" Then
        On Error Resume Next
        Open Name For Binary As 1: If FreeFile = 1 Then Exit Function
        PathsP = 0: Get 1, , PathsN: ReDim Paths(PathsN - 1): Get 1, , Paths: Close
        On Error GoTo 0
        PathName = FileNameFromPath(Name)
        If T = 0 And PathN = 0 Then GetPath 1
        If Update Then If Play = Paused Then Render
    Else
        If Not LoadPathEx(Name) Then Exit Function
        If Update Then If T = 0 Then PostMessage Main.hWnd, &H100&, Reset _
            Else If Play = Paused Then Render
        End If
    LoadPath = True
    End Function

Sub SavePath(Optional Name As String)
    Dim Add As Boolean: Static Op As OpenFileName
    If Name <> "" Then Name = PathsDir & Name & ".bin" _
    Else Name = GetSaveFile(Op, Setup.Preset, FilePath, "bin", PathsDir)
    If Name = "" Then Exit Sub
    Select Case LCase(Right(Name, 4))
    Case ".tle"
        UpdateFile Name, Vector2TextTLE(Earth)
    Case ".kml"
        If SavePathEx Then UpdateFile Name, StrAdd
    Case ".csv"
        If SavePathCSV Then UpdateFile Name, StrAdd
    Case Else
        On Error Resume Next
        If FileExists(Name) Then Open Name For Output As 1: Close
        If PathsN <= 0 Then AddPath False: Add = True
        Open Name For Binary As 1: Put 1, , PathsN: Put 1, , Paths: Close
        If Add Then DeletePath False
        On Error GoTo 0
        End Select
    End Sub

Function SavePathEx(Optional ByVal SavePath As Boolean = True, _
        Optional ByVal SaveKMLPath As Boolean = True) As Boolean
    Dim C As Long
    If PathsN <= 0 And PathP <= 0 Then SavePath = False
    If KMLPathN <= 0 Then SaveKMLPath = False
    If Not SavePath And Not SaveKMLPath Then Exit Function
        StrAdd _
            "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf & _
            "<kml xmlns=""http://earth.google.com/kml/2.0"">" & vbCrLf & _
            "  <Document>" & vbCrLf & _
            "    <name>" & AppName & " simulation</name>" & vbCrLf
    If SaveKMLPath Then
        StrAdd _
            "    <Style id=""launch"">" & vbCrLf & _
            "      <LineStyle>" & vbCrLf & _
            "        <color>7fff40ff</color>" & vbCrLf & _
            "        <width>1.5</width>" & vbCrLf & _
            "      </LineStyle>" & vbCrLf & _
            "      <PolyStyle>" & vbCrLf & _
            "        <color>5fff40ff</color>" & vbCrLf & _
            "      </PolyStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf
        StrAdd _
            "    <Style id=""balloon"">" & vbCrLf & _
            "       <IconStyle>" & vbCrLf & _
            "           <Icon>" & vbCrLf & _
            "               <href>http://weather.uwyo.edu/icons/purple.gif</href>" & vbCrLf & _
            "           </Icon>" & vbCrLf & _
            "       </IconStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf
        End If
    If SavePath Then
        StrAdd _
            "    <Style id=""track"">" & vbCrLf & _
            "      <LineStyle>" & vbCrLf & _
            "        <color>7f00ffff</color>" & vbCrLf & _
            "        <width>1.5</width>" & vbCrLf & _
            "      </LineStyle>" & vbCrLf & _
            "      <PolyStyle>" & vbCrLf & _
            "        <color>5f00ff00</color>" & vbCrLf & _
            "      </PolyStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf
        StrAdd _
            "    <Style id=""trace"">" & vbCrLf & _
            "       <IconStyle>" & vbCrLf & _
            "           <scale>0.3</scale>" & vbCrLf & _
            "           <Icon>" & vbCrLf & _
            "               <href>http://maps.google.com/mapfiles/kml/shapes/arrow.png</href>" & vbCrLf & _
            "           </Icon>" & vbCrLf & _
            "       </IconStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf
        StrAdd _
            "    <Style id=""plane"">" & vbCrLf & _
            "       <IconStyle>" & vbCrLf & _
            "           <Icon>" & vbCrLf & _
            "               <href>http://maps.google.com/mapfiles/kml/shapes/airports.png</href>" & vbCrLf & _
            "           </Icon>" & vbCrLf & _
            "       </IconStyle>" & vbCrLf & _
            "    </Style>" & vbCrLf
        End If
    If SaveKMLPath Then SavePathKML
    If SavePath Then
        For C = 0 To PathsN - 1: With Paths(C)
            SavePathLine .Path, .PathP, .Snap, .SnapN, .Preset.Name
            End With: Next
        If PathP > 0 Then SavePathLine Path, PathP, Snap, SnapN, Setup.Preset, True
        End If
        StrAdd _
            "  </Document>" & vbCrLf & _
            "</kml>" & vbCrLf
    SavePathEx = True
    End Function

Sub SavePathLine(Path() As PathPoint, ByVal PathP As Long, Snap() As Snap, ByVal SnapN As Long, _
        Name As String, Optional ByVal AddCurrent As Boolean)
    Dim A As Long, C As Long, S As String, T As String, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        Lat As Double, Lon As Double, Alt As Double
        StrAdd _
            "    <Folder>" & vbCrLf & _
            "      <name>" & Name & "</name>" & vbCrLf & _
            "      <Placemark>" & vbCrLf & _
            "        <name>Path</name>" & vbCrLf & _
            "        <description>" & Name & "</description>" & vbCrLf & _
            "        <styleUrl>#track</styleUrl>" & vbCrLf & _
            "        <LineString>" & vbCrLf & _
            "          <extrude>1</extrude>" & vbCrLf & _
            "          <tessellate>1</tessellate>" & vbCrLf & _
            "          <altitudeMode>absolute</altitudeMode>" & vbCrLf & _
            "          <coordinates>" & vbCrLf
        For C = 0 To PathP - 1
            With Path(C): X1 = -.Earth.X: Y1 = -.Earth.Y: Z1 = -.Earth.Z
            Rotate -Earth.AngleU, Y1, Z1: Rotate -.AngleW, X1, Y1: End With
            XYZ2LLA Earth, Lat, Lon, Alt, X1, Y1, Z1, False
        StrAdd _
            "            " & NumX(Lon / Rad) & "," & NumX(Lat / Rad) & "," & NumX(Alt, FOne) & vbCrLf
            Next
        If AddCurrent Then
            XYZ2LLA Earth, Lat, Lon, Alt
        StrAdd _
            "            " & NumX(Lon / Rad) & "," & NumX(Lat / Rad) & "," & NumX(Alt, FOne) & vbCrLf
            End If
        StrAdd _
            "          </coordinates>" & vbCrLf & _
            "        </LineString>" & vbCrLf & _
            "      </Placemark>" & vbCrLf
        StrAdd _
            "      <Folder>" & vbCrLf & _
            "        <name>Points</name>" & vbCrLf
        For C = 0 To SnapN - 1: SavePathPoint Snap(C): Next
        For C = 0 To SnapN - 1: SavePathPoint Snap(C), True: Next
        If AddCurrent Then XYZ2LLA Earth, Lat, Lon, Alt: SavePathPointPos _
                "Current position", _
                "Lat:" & ToLat(Lat, FLong, Degrees) & " Lon:" & ToLon(Lon, FLong, Degrees) & " Alt:" & ToAlt(Earth.Altitude, FMini) & " Speed: " & ToSpeed(Earth.Speed, FMini), _
                "plane", Lon, Lat, Earth.Altitude, True
        StrAdd _
            "      </Folder>" & vbCrLf & _
            "    </Folder>" & vbCrLf
    End Sub

Sub SavePathPointPos(Name As String, Description As String, Optional Style As String, _
        Optional Lon As Double, Optional Lat As Double, Optional Alt As Double, _
        Optional ByVal UseAlt As Boolean)
        StrAdd _
            "        <Placemark>" & vbCrLf & _
            IfText(Not UseAlt, _
            "          <name>" & Name & "</name>" & vbCrLf) & _
            "          <description>" & Description & "</description>" & vbCrLf & _
            IfText(Style <> "", _
            "          <styleUrl>#" & Style & "</styleUrl>" & vbCrLf) & _
            "          <Point>" & vbCrLf & _
            "            <altitudeMode>" & IIf(UseAlt, "absolute", "clampToGround") & "</altitudeMode>" & vbCrLf & _
            "            <coordinates>" & NumX(Lon / Rad) & "," & NumX(Lat / Rad) & "," & IIf(UseAlt, NumX(Alt), "0") & "</coordinates>" & vbCrLf & _
            "          </Point>" & vbCrLf & _
            "        </Placemark>" & vbCrLf
    '       "            <extrude>1</extrude>" & vbCrLf
    'altitudeMode: relativeToSeaFloor, clampToSeaFloor, relativeToGround, clampToGround, absolute
    End Sub

Sub SavePathPoint(Q As Snap, Optional ByVal UseAlt As Boolean)
    Dim A As Long, S As String, T As String, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        Lat As Double, Lon As Double, Alt As Double
    With Q: X1 = .Ship.X - .Earth.X: Y1 = .Ship.Y - .Earth.Y: Z1 = .Ship.Z - .Earth.Z
    Rotate -Earth.AngleU, Y1, Z1: Rotate Earth.AngleW - .Earth.AngleW, X1, Y1: Rotate Earth.AngleU, Y1, Z1
    X1 = X1 + Earth.X: Y1 = Y1 + Earth.Y: Z1 = Z1 + Earth.Z
    XYZ2LLA Earth, Lat, Lon, Alt, X1, Y1, Z1: S = .StatusText
    A = InStr(S, "  "): If A = 0 Then T = S _
    Else T = Mid(S, A + 2): A = InStr(T, "  "): If A Then T = Left(T, A - 1)
    If T = "" Then T = ToDh(.T)
    S = S & " Lat:" & ToLat(Lat, FLong, Degrees) & " Lon:" & ToLon(Lon, FLong, Degrees) & " Alt:" & ToAlt(Alt, FMini)
    End With
    SavePathPointPos T, S, IfText(UseAlt, "trace"), Lon, Lat, Alt, UseAlt
    End Sub

Sub SavePathKML(Optional Name As String)
    Dim C As Long
    If Name = "" Then Name = "Balloon Trajectory": _
        If KMLPathN > 0 Then If KMLPath(0).Time <> 0 Then _
            Name = Name & " (" & J2ksDateTimeEx(RoundEx(KMLPath(0).Time, 3)) & ")"
        StrAdd _
            "    <Folder>" & vbCrLf & _
            "      <name>" & Name & "</name>" & vbCrLf & _
            "      <Placemark>" & vbCrLf & _
            "        <name>Path</name>" & vbCrLf & _
            "        <description>" & Name & "</description>" & vbCrLf & _
            "        <styleUrl>#launch</styleUrl>" & vbCrLf & _
            "        <LineString>" & vbCrLf & _
            "          <extrude>1</extrude>" & vbCrLf & _
            "          <tessellate>1</tessellate>" & vbCrLf & _
            "          <altitudeMode>absolute</altitudeMode>" & vbCrLf & _
            "          <coordinates>" & vbCrLf
        For C = 0 To KMLPathN - 1: With KMLPath(C)
        StrAdd _
            "            " & NumX(.Lon / Rad) & "," & NumX(.Lat / Rad) & "," & NumX(.Alt, FOne) & vbCrLf
            End With: Next
        StrAdd _
            "          </coordinates>" & vbCrLf & _
            "        </LineString>" & vbCrLf & _
            "      </Placemark>" & vbCrLf
        StrAdd _
            "      <Folder>" & vbCrLf & _
            "        <name>Points</name>" & vbCrLf
        For C = 0 To KMLPointN - 1: With KMLPoint(C)
        StrAdd _
            "        <Placemark>" & vbCrLf & _
            "          <name>" & .Name & "</name>" & vbCrLf & _
            "          <description>" & J2ksDateTime(.Time) & " UTC Lat:" & ToLat(.Lat, FLong, Degrees) & " Lon:" & ToLon(.Lon, FLong, Degrees) & " Alt:" & ToAltEx(.Alt) & "</description>" & vbCrLf & _
            "          <styleUrl>#balloon</styleUrl>" & vbCrLf & _
            "          <Point>" & vbCrLf & _
            "            <altitudeMode>absolute</altitudeMode>" & vbCrLf & _
            "            <coordinates>" & NumX(.Lon / Rad) & "," & NumX(.Lat / Rad) & "," & NumX(.Alt, FOne) & "</coordinates>" & vbCrLf & _
            "          </Point>" & vbCrLf & _
            "        </Placemark>" & vbCrLf
            End With: Next
        StrAdd _
            "      </Folder>" & vbCrLf & _
            "    </Folder>" & vbCrLf
    End Sub

Function SavePathCSV() As Boolean
    Dim C As Long, N As Long, T1 As Double, S As String
    If KMLPathN > 0 Then SavePathCSV = True Else Exit Function
    S = "#, ElapsedTime[s], Julian2000Seconds[s], DateUTC[YYYY-MM-DD], TimeUTC[HH:MM:SS], Latitude[deg], Longitude[deg], Altitude[m], HorizontalSpeed[m/s], VerticalSpeed[m/s], Density[kg/m³], Pressure[Pa], Temperature[K], RelativeHumidity[%], CloudWater[kg/kg], WindSpeedX[m/s), WindSpeedY[m/s), WindSpeedZ[m/s), Name" & vbCrLf
    If LogOption And LogSeparator Then StrAdd Replace(S, ",", ";") Else StrAdd S
    C = 0: T1 = KMLPath(0).Time
    For N = 0 To KMLPathN - 1: With KMLPath(N)
        S = N & "," & _
            NumD(.Time - T1) & "," & NumD(.Time) & "," & J2ksDate(.Time) & "," & J2ksTime(.Time) & "," & _
            NumD(.Lat / Rad) & "," & NumD(.Lon / Rad) & "," & NumD(.Alt) & "," & _
            NumD(.SpeedH) & "," & NumD(.SpeedV) & "," & NumD(.Density) & "," & NumD(.Pressure) & "," & _
            NumD(.Temperature) & "," & NumD(.Humidity) & "," & NumD(.CloudWater) & "," & _
            NumD(.WindX) & "," & NumD(.WindY) & "," & NumD(.WindZ)
        If KMLPoint(C).Time = .Time And KMLPoint(C).Lat = .Lat And KMLPoint(C).Lon = .Lon And _
            KMLPoint(C).Alt = .Alt Then S = S & "," & KMLPoint(C).Name: C = C + 1
        If LogOption And LogSeparator Then StrAdd ToSSV(S) & vbCrLf Else StrAdd S & vbCrLf
        End With: Next
    End Function

Sub AddPath(Optional ByVal Update As Boolean = True)
    Dim IsLast As Boolean
    IsLast = SaveLast
    PathsN = PathsN + 1: PathsP = PathsN - 1
    If PathsP = 0 Then ReDim Paths(PathsP) Else ReDim Preserve Paths(PathsP)
    IsPathModified = True
    With Paths(PathsP)
    .Preset = LastPreset: .TimeStep = dT: .Time = Time
    .Lat = Lat: .Lon = Lon: .Alt = Alt: .InitialAngle = InitialAngle
    .StartAngle = StartAngle: .EndAngle = EndAngle: .StartAlt = StartAlt: .EndAlt = EndAlt
    .ThrustLimit = ThrustLimit: .LiftLimit = LiftLimit
    .EarthPark = EarthPark: .MoonPark = MoonPark
    .Brake = Brake: .Abort = Abort: .Jettison = Jettison: .Parachute = Parachute
    .Targets = Targets: .TargetsN = TargetsN: .Stage = Stage
    .Snap = Snap: .SnapN = SnapN: .SnapP = SnapP: .SnapTime = SnapTime
    .Path = Path: .PathN = PathN: .PathP = PathP: .PathTime = PathTime
    End With
    If IsLast Then RemoveLast
    If Update And Play = Paused Then Render
    End Sub

Sub CopyPoint(L As Location, K As KMLPoint, Name As String, Optional ByVal AddN As Boolean)
    L.Lat = K.Lat: L.Lon = K.Lon: L.Alt = K.Alt: L.Time = K.Time: L.Name = Name: L.Planet = ToEarth
    If AddN Then KMLPointN = KMLPointN + 1
    End Sub

Sub GetPath(Optional ByVal N As Long, Optional ByVal Relative As Boolean)
    If PathsN <= 0 Then Exit Sub
    If Relative Then PathsP = PathsP + N Else PathsP = N
    If PathsP < 0 Then PathsP = PathsN - 1 Else If PathsP > PathsN - 1 Then PathsP = 0
    IsPathModified = True
    With Paths(PathsP)
    LastPreset = .Preset: LoadLastPreset: dT = .TimeStep: Time = .Time
    Lat = .Lat: Lon = .Lon: Alt = .Alt: InitialAngle = .InitialAngle
    StartAngle = .StartAngle: EndAngle = .EndAngle: StartAlt = .StartAlt: EndAlt = .EndAlt
    ThrustLimit = .ThrustLimit: LiftLimit = .LiftLimit
    EarthPark = .EarthPark: MoonPark = .MoonPark
    Brake = .Brake: Abort = .Abort: Jettison = .Jettison: Parachute = .Parachute
    Targets = .Targets: TargetsN = .TargetsN: Stage = .Stage
    Snap = .Snap: SnapN = .SnapN: SnapP = .SnapP: SnapTime = .SnapTime
    Path = .Path: PathN = .PathN: PathP = .PathP: PathTime = .PathTime
    End With
    If SnapN > 0 Then
        For SnapP = SnapN - 1 To 1 Step -1: If Snap(SnapP).T <= T Then Exit For
            Next
        If SnapP + 1 < SnapN Then _
            If Abs(Snap(SnapP).T - T) > Abs(Snap(SnapP + 1).T - T) Then SnapP = SnapP + 1
        SnapP = SnapP + 1: LoadSnap SnapP - 1, , False
    Else
        Init False: Play = Paused
        End If
    End Sub

Sub DeletePath(Optional ByVal Update As Boolean = True)
    Dim C As Long
    If PathsN <= 0 Then Exit Sub
    With Paths(PathsP): Erase .Snap: .SnapN = 0: .SnapP = 0
    Erase .Path: .PathN = 0: .PathP = 0: Erase .Stage: End With
    For C = PathsP To PathsN - 2: Paths(C) = Paths(C + 1): Next
    PathsN = PathsN - 1: If PathsN = 0 Then _
        ReDim Paths(PathsN) Else ReDim Preserve Paths(PathsN)
    If PathsP > PathsN - 1 Then PathsP = PathsN - 1
    If Update And Play = Paused Then Render
    End Sub

Sub ClearSavedPaths(Optional ByVal Update As Boolean = True)
    If PathsN > 0 Then ReDim Paths(0): PathsP = 0: PathsN = 0: PathName = ""
    If Update And Play = Paused Then Render: Info
    End Sub

Sub ClearKMLPaths(Optional ByVal Update As Boolean = True)
    If KMLPathN > 0 Then Erase KMLPath: KMLPathN = 0: _
        KMLPathTop = 0: KMLPathPos = 0: KMLPathAlt = 0: KMLPathP = 0
    If KMLPointN > 0 Then Erase KMLPoint: KMLPointN = 0
    If Update And Play = Paused Then If T = 0 Then Init Else Render: Info
    End Sub

Sub ClearTrackingPaths(Optional ByVal Update As Boolean = True)
    If LinkMode <> TrackLink Then ClearTrack
    If Update And Play = Paused Then Render: Info
    End Sub

Sub ClearAllPaths(Optional ByVal Update As Boolean = True)
    ClearSavedPaths False
    ClearKMLPaths False
    ClearTrackingPaths False
    If Update And Play = Paused Then If T = 0 Then Init Else Render: Info
    End Sub

'Sub GetAPRSPath()
'    Dim A As Long, S As String, T As String, U As String, _
'        Lat As Double, Lon As Double, Alt As Double
'    S = Clipboard.GetText
'    'Open "Backup\APRS.txt" For Input As 1: S = Input(LOF(1), 1): Close
'    Do While S <> ""
'        '2011-07-09 15:02:42 UTC: EA8CRA-10>APT3A2,WIDE1-1,WIDE2-1,qAR,EA2CNS:/150239h4210.64N/00031.04E[038/029/A=052782/MT AIO
'        T = GetLineEx(S)
'        A = InStr(T, "/")
'        If A Then
'            Lat = Val(Mid(T, A + 8, 2)) + Val(Mid(T, A + 10, 5)) / 60: If UCase(Mid(T, A + 15, 1)) = "S" Then Lat = -Lat
'            Lon = Val(Mid(T, A + 17, 3)) + Val(Mid(T, A + 20, 5)) / 60: If UCase(Mid(T, A + 25, 1)) = "W" Then Lon = -Lon
'            Alt = Val(Mid(T, A + 37, 6)) * 0.3048
'            U = U & "            " & NumX(Lon) & "," & NumX(Lat) & "," & NumX(Alt, "0.0") & vbCrLf
'        Else
'            Stop
'            End If
'        Loop
'    Clipboard.Clear: Clipboard.SetText U: Stop
'    End Sub

'Sub GetIridiumPathNew()
'    Dim A As Long, S As String, T As String, U As String, _
'        Lat As Double, Lon As Double, Alt As Double
'    S = Clipboard.GetText
'    'Open "Backup\Iridium.txt" For Input As 1: S = Input(LOF(1), 1): Close
'    S = Replace(Replace(S, vbCrLf, vbLf), vbLf, vbCrLf)
'    Do While S <> ""
'        'Date Time UTC, Latitude, Longitude, Altitude, Gnd_Velocity?, Heading?, -Vert_Velocity?, Satellites_Used, HDOP?, VDOP?
'        '02/09/2012 06:52:06 UTC,41.858386,-.751638,307.86,1.7280,345.86,001.7,05,1.61,2.51
'        T = GetLineEx(S)
'        If T <> "" Then
'            Param T: Lat = NumN(T): Lon = NumN(T): Alt = NumN(T)
'            U = U & "            " & NumX(Lon) & "," & NumX(Lat) & "," & NumX(Alt, "0.0") & vbCrLf
'            End If
'        Loop
'    Clipboard.Clear: Clipboard.SetText U: Stop
'    End Sub

'Sub GetIridiumPath()
'    Dim A As Long, S As String, T As String, U As String, _
'        Lat As Double, Lon As Double, Alt As Double, _
'        A1 As Double, T1 As Double, T2 As Double
'    Dim V As String
'    T1 = 0: S = Clipboard.GetText
'    'Open "Backup\Iridium.txt" For Input As 1: S = Input(LOF(1), 1): Close
'    S = Replace(Replace(S, vbCrLf, vbLf), vbLf, vbCrLf)
'    Do While S <> ""
'        'State, Time, Date, Latitude, Longitude, Altitude, Gnd_Velocity, Vert_Velocity, Satellites_Used, HDOP, VDOP
'        'Normal,06:12:59.0,08-29-2011,42:00.8008 North,000:04.3348 East,12079.0 meters,74.52 km/h at 82.19 degrees from True North,-0.1 m/s,05,1.23,1.44
'        T = GetLineEx(S)
'        If T <> "" Then
'            Param T 'State
'            T2 = T1: T1 = ValTimeEx(Param(T)) 'Time
'            If T1 <> T2 Then
'                Param T 'Date
'                V = Param(T): Lat = Val(Left(V, 2)) + Val(Mid(V, 4)) / 60 '42:00.8008 North
'                If UCase(Mid(V, 12, 1)) = "S" Then Lat = -Lat
'                V = Param(T): Lon = Val(Left(V, 3)) + Val(Mid(V, 5)) / 60 '000:04.3348 East
'                If UCase(Mid(V, 13, 1)) = "W" Then Lon = -Lon
'                A1 = Alt: Alt = NumN(T)                                   '1234567890123456
'                Param T 'Gnd_VelociTy
'                Param T 'VerT_VelociTy
'                If Val(Param(T)) < 3 And Alt < 12080 Then Alt = 12080
'                U = U & "            " & NumX(Lon) & "," & NumX(Lat) & "," & NumX(Alt, "0.0") & vbCrLf
'                'U = U & T1 & Tab1 & (T1 - T2) & Tab1 & NumX(Lon) & Tab1 & NumX(LaT) & Tab1 & NumX(AlT, "0.0") & Tab1 & NumX((AlT - A1) / (T1 - T2)) & vbCrLf
'                End If
'            End If
'        Loop
'    Clipboard.Clear: Clipboard.SetText U: Stop
'    End Sub

Sub LinkGE(Optional ByVal Mode As PlayMode = Active)
    Dim Lat As Double, Lon As Double, Alt As Double, S As String
    Static GehWnd As Long, Ge As ApplicationGE 'Can be type Object, but the connexion is slower
    On Error Resume Next
    If Mode <> Active Then
        If Mode = Settings Then If Ge Is Nothing Then GoTo Start Else GoTo CopyTo
        If Mode = Toggle Then Mode = IfInt(Main.mnPanelsGE.Checked, Pause, Start)
        If Mode = Start Then GoTo Start Else GoTo Pause
        End If
    If IsWindow(GehWnd) = 0 Then
Pause:
        'ToolBox GehWnd, False
        'PostMessage GehWnd, &H10&, 0, 0 'WM_CLOSE=&H10
        Set Ge = Nothing: Main.mnPanelsGE.Checked = False
        Exit Sub
        End If
    If Ge Is Nothing Then
Start:
        Set Ge = New ApplicationGE: If Ge Is Nothing Then Exit Sub
        GehWnd = Ge.GetMainHwnd ': ToolBox GehWnd, True, True
        Main.mnPanelsGE.Checked = True
CopyTo:
        If Mode = Settings Then
            If SavePathEx Then Ge.LoadKmlData StrAdd
            Exit Sub
            End If
        End If
    With Cam1
    If .Relative = ToEarth Then XYZ2LLA Earth, Lat, Lon, Alt _
    Else If .Relative = ToMoon Then XYZ2LLA Moon, Lat, Lon, Alt _
    Else If .Relative = ToMars Then XYZ2LLA Mars, Lat, Lon, Alt _
    Else If Target = ToMoon Then XYZ2LLA Moon, Lat, Lon, Alt _
    Else If Target = ToMars Then XYZ2LLA Mars, Lat, Lon, Alt _
    Else XYZ2LLA Earth, Lat, Lon, Alt
    Err = 0
    If LinkMode = TrackLink Then _
        Ge.SetCameraParams Track1.Lat / Rad, Track1.Lon / Rad, Track1.Alt, AbsoluteAltitudeGE, _
            .Altitude, -90 - .AngleV, .AngleU, 5 _
    Else If .Follow = ToTactic Then _
        Ge.SetCameraParams Lat / Rad, Lon / Rad, Alt, AbsoluteAltitudeGE, _
            .Altitude, -90 - .AngleV, .AngleU, 5 _
    Else Ge.SetCameraParams Lat / Rad, Lon / Rad, Alt, AbsoluteAltitudeGE, _
            .Altitude, 0, 0, 5
    'If Err Then Set Ge = Nothing '5=Invalid call or argument -2147188734=Automation error
    End With
    On Error GoTo 0
    End Sub

Function Quit(Optional ByVal Restart As Boolean) As Boolean
    If Note.Active Then Note.Active = False: Exit Function
    If Not IsTest Then _
        If MsgBox(IIf(IsRestart, _
            "The application must be restarted" & vbCrLf & _
            "to apply the changes, restart ", "Exit ") & AppName & "?", _
            vbOKCancel Or vbQuestion, AppName) <> vbOK Then Exit Function
    PostMessage Main.hWnd, &H10& 'WM_CLOSE=&H10
    Quit = True
    End Function

Sub CreateLinks()
    If MsgBox("Do you want to create shortcuts for the " & AppName & " simulator" & vbCrLf & _
        "in the desktop and the start menu?", vbOKCancel Or vbQuestion, AppName) <> vbOK Then _
            Exit Sub
    CreateLink App.EXEName & ".exe", AppPathName, AppName & ".lnk", "Desktop", "Images\" & App.EXEName & ".ico"
    CreateLink App.EXEName & ".exe", AppPathName, AppName & ".lnk", "StartMenu", "Images\" & App.EXEName & ".ico"
    End Sub

Function CreateLink(File As String, Optional Folder As String, _
        Optional LinkFile As String, Optional LinkFolder As String = "Desktop", _
        Optional IconFile As String) As Boolean
    Dim Shell As Object, ShortCut As Object
    On Error GoTo Ext
    Set Shell = CreateObject("WScript.Shell")
    Select Case LinkFolder
    Case "Desktop":     LinkFolder = Shell.SpecialFolders("Desktop")     'User-Desktop
    Case "StartMenu":   LinkFolder = Shell.SpecialFolders("StartMenu")   'User-StartMenu
    Case "Programs":    LinkFolder = Shell.SpecialFolders("Programs")    'User-StartMenu-Programs
    Case "Startup":     LinkFolder = Shell.SpecialFolders("Startup")     'User-StartMenu-Programs-Startup
    Case "MyDocuments": LinkFolder = Shell.SpecialFolders("MyDocuments") 'User-MyDocuments
    'Case Else:          LinkFolder = Shell.ExpandEnvironmentStrings(LinkFolder) '"%SystemRoot%\Notepad.exe"
        End Select
    Set ShortCut = Shell.CreateShortCut(LinkFolder & "\" & LinkFile)
    ShortCut.TargetPath = Folder & "\" & File
    ShortCut.WorkingDirectory = Folder
    If IconFile <> "" Then ShortCut.IconLocation = Folder & "\" & IconFile & ",0"
    'ShortCut.Arguments = ""
    'ShortCut.Description = ""
    'ShortCut.WindowsStyle = 1
    ShortCut.Save
    CreateLink = True
    On Error GoTo 0
Ext:
    Set ShortCut = Nothing
    Set Shell = Nothing
    End Function

Sub SystemInfo()
    Dim S As String
    S = _
        "Operating system" & Tab1 & WinVersion & vbCrLf & _
        "Free RAM memory" & Tab1 & ToBytes(FreeMemory) & vbCrLf & _
        "Build date" & Tab2 & FileDateEx(EXEPathName & ".exe") & vbCrLf & _
        "Video device" & Tab1 & GetDisplayName & vbCrLf
    If Cam1.hRC Then S = S & _
        "3D Vendor" & Tab2 & GetStringFromPtr(glGetString(gl_Vendor)) & vbCrLf & _
        "3D Render" & Tab2 & GetStringFromPtr(glGetString(gl_Renderer)) & vbCrLf & _
        "3D Version" & Tab1 & GetStringFromPtr(glGetString(gl_Version)) & vbCrLf
    MsgBox S, vbInformation, AppName
    End Sub

Function WinVersion() As String
    Dim B As Long, C As Long, L As Long, H As Long, N As Long
    C = GetVersion
    H = C And &HFF&                  'Major version number
    L = (C And &HFF00&) \ &H100&     'Minor version number
    B = (C And &H7FFF0000) \ &H10000 'Build number
    N = C And &H80000000             '0=WinNT 1=Win32
    Select Case H
    Case 3: If N Then WinVersion = "Windows " & H & "." & L _
                 Else WinVersion = "Windows NT " & H & "." & L
    Case 4: If N Then WinVersion = "Windows 98" _
                 Else WinVersion = "Windows NT " & H & "." & L
    Case 5:           WinVersion = "Windows XP"
    Case 6: Select Case L
        Case 0:       WinVersion = "Windows Vista"
        Case 1:       WinVersion = "Windows 7"
        Case 2:       WinVersion = "Windows 8"
        Case Else:    WinVersion = "Windows " & H & "." & L
            End Select
    Case Else:        WinVersion = "Windows " & H & "." & L
        End Select
    End Function

Function AppName() As String
    AppName = App.EXEName & App.Major & "." & App.Minor
    End Function

Sub Msg(Text As String, Optional Text2 As String, _
        Optional ByVal Event1 As Byte, Optional ByVal Event2 As Byte = 6, Optional Add As Long = -1)
    'Event .. 0=NoPause+Log+Save 1=Pause 2=NoSave 4=NoLog
    If (Event1 And 2) = 0 Or (Event2 And 2) = 0 Then _
        If KMLPathPos > 0 Then StatusText = _
            J2ksTimeEx(KMLPath(KMLPathPos).Time) & " " & Text _
        Else StatusText = ToDhmsEx(T) & "  " & Text: SaveSnap: _
            If Add >= 0 Then Add = Add + 1: SnapP = SnapP - 1
    If (Event1 And 4) = 0 Or (Event2 And 4) = 0 Then
        With Log.Txt
        SendMessage .hWnd, &HB1&, &H7FFFFFFF, ByVal &H7FFFFFFF 'EM_SETSEL=&HB1
        If KMLPathPos <= 0 Then SendMessage .hWnd, &HC2&, 0, ByVal CStr( _
            ToDhms(T, True, False) & "  " & Text & vbCrLf) _
        Else SendMessage .hWnd, &HC2&, 0, ByVal CStr(J2ksTime(KMLPath(KMLPathPos).Time) & " " & _
            Format(Int(KMLPath(KMLPathPos).Time - KMLPath(0).Time), "000000s") & "  " & Text & vbCrLf) 'EM_REPLACESEL=&HC2 CAN_UNDO=0
        If Text2 <> "" Then SendMessage .hWnd, &HC2&, 0, ByVal (vbTab & Text2 & vbCrLf) _
        Else If KMLPathPos <= 0 Then SendMessage .hWnd, &HC2&, 0, ByVal MsgPos( _
            PlanetAltitude, PlanetSpeed(, True), PlanetSpeed, Target = ToEarth Or Target = ToMars) _
        Else SendMessage .hWnd, &HC2&, 0, ByVal MsgBalloon( _
            Alt, KMLPath(KMLPathPos).SpeedH, KMLPath(KMLPathPos).SpeedV) 'EM_REPLACESEL=&HC2 CAN_UNDO=0
        End With
        End If
    If VideoMode = 3 Then
        If (Event1 And 8) <> 0 Or (Event2 And 8) <> 0 Then RecordVideo Pause _
        Else If (Event1 And 1) <> 0 Or (Event2 And 1) <> 0 Then _
            RecordVideo Start, Format(Int(T * 1000), "00000000000") & "." & Text
    Else
        If (Event1 And 1) <> 0 Or (Event2 And 1) <> 0 Then Play = Paused: Count = 0
        End If
    'If (Event1 And 1) <> 0 Or (Event2 And 1) <> 0 Then _
        If VideoMode <> 3 Then Play = Paused: Count = 0 _
        Else If Rec Then RecordVideo Start, Format(Int(T * 1000), "00000000000") & "." & Text _
        Else RecordVideo Pause
    End Sub

Function MsgEx() As String
    If BurnOutDV > 0 Then _
        MsgEx = "DeltaV=" & ToSpeed(ThrustDV, FmtLog) & " Hover=" & ToSpeed(ThrustDV - BurnOutDV, FmtLog) _
    Else MsgEx = "DeltaV=" & ToSpeed(ThrustDV, FmtLog) & " Drag=" & ToSpeed(DragDV, FmtLog) & vbCrLf & _
        vbTab & "Gravity=" & ToSpeed(GravityDV, FmtLog) & " Lift=" & ToSpeed(LiftDV, FmtLog)
    End Function

Function MsgPos(Optional A As Double, Optional VR As Double, Optional VA As Double, _
        Optional ByVal IsAir As Boolean = True) As String
    MsgPos = vbTab & ToAlt(A, FmtLog) & " " & ToSpeed(VR, FmtLog)
    If VA > 0 Then _
        If A > Atm Or Not IsAir Then MsgPos = MsgPos & " " & ToSpeed(VA, FmtLog) _
        Else If VA >= Mach Then MsgPos = MsgPos & " Mach " & NumM(VA / Mach, FmtLog) _
        Else If Units Then MsgPos = MsgPos & " " & NumX(VA / Knot, FmtLog) & "kn" _
        Else MsgPos = MsgPos & " " & NumX(VA / Kph, FmtLog) & "kph"
    MsgPos = MsgPos & vbCrLf
    End Function

Function MsgBalloon(Optional Alt As Double, _
        Optional SpeedH As Double, Optional SpeedV As Double) As String
    If Units Then MsgBalloon = MsgBalloon & NumX(SpeedH / Knot, FmtLog) & "kn" _
    Else MsgBalloon = MsgBalloon & NumX(SpeedH / Kph, FmtLog) & "kph"
    MsgBalloon = vbTab & ToAlt(Alt, FmtLog) & " " & MsgBalloon & " " & _
        ToSpeed(SpeedV, FmtLog) & vbCrLf
    End Function

Sub MsgApoapsis(Pl As Planet, Apoapsis As Byte)
    Msg PlanetApoapsis(Pl.ID) & "  " & ToAlt(Pl.Altitude, FmtLog), , Apoapsis And Not IfInt( _
        Cam1.Target > ToShip And Cam1.Target <> Pl.ID And Pl.ID <> ToEarth And Pl.ID <> ToMoon, 1, 0)
    End Sub

Sub MsgPeriapsis(Pl As Planet, Periapsis As Byte)
    Msg PlanetPeriapsis(Pl.ID) & "  " & ToAlt(Pl.Altitude, FmtLog), , Periapsis And Not IfInt( _
        Cam1.Target > ToShip And Cam1.Target <> Pl.ID And Pl.ID <> ToEarth And Pl.ID <> ToMoon, 1, 0)
    End Sub

Sub MsgDistance(Pl As Planet, PlanetDistance As Byte)
    Msg Planets(Pl.ID) & " distance  " & ToSpeed(Pl.Speed, FmtLog), , PlanetDistance And Not IfInt( _
        Cam1.Target > ToShip And Cam1.Target <> Pl.ID, 1, 0)
    End Sub

Sub MsgLog(Optional Text As String)
    Dim N As Long
    N = Log.Txt.hWnd
    SendMessage N, &HB1&, &H7FFFFFFF, ByVal &H7FFFFFFF 'EM_SETSEL=&HB1
    SendMessage N, &HC2&, 0, ByVal CStr(Text & vbCrLf) 'EM_REPLACESEL=&HC2 CAN_UNDO=0
    End Sub

Sub SavePlanetPos(P As PlanetPos, Pl As Planet)
    With P: .X = Pl.X: .Y = Pl.Y: .Z = Pl.Z
    .SpeedX = Pl.MainSpeedX: .SpeedY = Pl.MainSpeedY: .SpeedZ = Pl.MainSpeedZ
    .AngleU = Pl.AngleU: .AngleV = Pl.AngleV: .AngleW = Pl.AngleW: .AngularSpeedW = Pl.AngularSpeedW
    .LastAltitude = Pl.LastAltitude: .LastVerticalSpeed = Pl.LastVerticalSpeed: End With
    End Sub

Sub LoadPlanetPos(Pl As Planet, P As PlanetPos)
    With Pl: .X = P.X: .Y = P.Y: .Z = P.Z
    .MainSpeedX = P.SpeedX: .MainSpeedY = P.SpeedY: .MainSpeedZ = P.SpeedZ
    .AngleU = P.AngleU: .AngleV = P.AngleV: .AngleW = P.AngleW: .AngularSpeedW = P.AngularSpeedW
    .LastAltitude = P.LastAltitude: .LastVerticalSpeed = P.LastVerticalSpeed: End With
    End Sub

Sub SaveMatrix(A As Matrix3, B As Matrix4)
    A.XX = B.XX: A.XY = B.XY: A.XZ = B.XZ
    A.YX = B.YX: A.YY = B.YY: A.YZ = B.YZ
    A.ZX = B.ZX: A.ZY = B.ZY: A.ZZ = B.ZZ
    End Sub

Sub LoadMatrix(A As Matrix4, B As Matrix3)
    A.XX = B.XX: A.XY = B.XY: A.XZ = B.XZ
    A.YX = B.YX: A.YY = B.YY: A.YZ = B.YZ
    A.ZX = B.ZX: A.ZY = B.ZY: A.ZZ = B.ZZ
    End Sub

Sub LoadSnap(Optional ByVal N As Long, Optional ByVal Relative As Boolean, _
        Optional ByVal LastSnap As Boolean = True)
    Dim C As Long, X1 As Double, Y1 As Double, Z1 As Double, _
        IsTimeSnap As Boolean, IsLastSnap As Boolean
    If KMLPathPos > 0 Then
        If Relative Then If N < 0 Then Nav Reset Else Nav NextStep ': If Play = Paused Then Init
        Exit Sub
        End If
    If SnapN <= 0 Then Exit Sub
    If Relative Then N = SnapP + N + (N > 0 Or T <= Snap(SnapP - 1).T) _
    Else If N < 0 Then N = SnapN - 1
    If N < 0 Then If SnapP > 1 Then N = 0 Else Exit Sub
    If N >= SnapN Then If SnapP < SnapN Then N = SnapN - 1 Else Exit Sub
    If LastSnap Then SaveLast
    SnapP = N
    
    With Snap(SnapP)
    With .Ship: SpeedX = .SpeedX: SpeedY = .SpeedY: SpeedZ = .SpeedZ
    AngleU = .AngleU: AngleV = .AngleV: AngleW = .AngleW: End With
    LoadMatrix Attitude, .Attitude
    LoadMatrix AngularSpeed, .AngularSpeed
    ControlThrustU = .ControlThrustU: ControlAccU = .ControlAccU: ControlLastU = .ControlLastU
    ControlThrustV = .ControlThrustV: ControlAccV = .ControlAccV: ControlLastV = .ControlLastV
    ControlThrustW = .ControlThrustW: ControlAccW = .ControlAccW: ControlLastW = .ControlLastW
    TargetAngle = .TargetAngle
    LoadPlanetPos Sun, .Sun
    LoadPlanetPos Earth, .Earth
    LoadPlanetPos Moon, .Moon
    If GravityMode >= 4 Then
        LoadPlanetPos Mercury, .Mercury
        LoadPlanetPos Venus, .Venus
        LoadPlanetPos Mars, .Mars
        LoadPlanetPos Jupiter, .Jupiter
        LoadPlanetPos Saturn, .Saturn
        LoadPlanetPos Uranus, .Uranus
        LoadPlanetPos Neptune, .Neptune
        LoadPlanetPos Pluto, .Pluto
        End If
    
    If IsEmptyStages Then _
        If KMLPathN > 0 Or .StageC > 0 Then StageE = .StageE: StageC = .StageC _
        Else If StageC > 0 Then StageC = 0: ReDim StageE(0)
    StageP = .StageP: StageN = .StageN: Status = .Status: StatusText = .StatusText
    Mass = .Mass: Thrust = .Thrust: Throttle = .Throttle: ThrustP = .ThrustP
    If VideoSync Then If Not Video.WMP Is Nothing Then If T <> .T Then T = T - .T: _
        With Video.WMP.object: .CurrentPosition = .CurrentPosition - T: End With
    T = .T: RealTime = .RealTime: DelayTime = .DelayTime
    AccelTime = .AccelTime: BrakeTime = .BrakeTime
    Accel = .Accel: Drag = .Drag: Lift = .Lift
    ThrustDV = .ThrustDV: BurnOutDV = .BurnOutDV: GravityDV = .GravityDV
    DragDV = .DragDV: LiftDV = .LiftDV: Impulse = .Impulse
    MaxVe = .MaxVe: MinVe = .MinVe
    MaxHe = .MaxHe: MinHe = .MinHe
    MaxHm = .MaxHm: MinHm = .MinHm
    MaxG = .MaxG: ControlThrustMax = .ControlThrustMax
    IsLiftOff = .IsLiftOff: IsEarthSoundSpeed = .IsEarthSoundSpeed
    IsEarthOrbitalSpeed = .IsEarthOrbitalSpeed: IsEarthEscapeSpeed = .IsEarthEscapeSpeed
    IsMoonOrbitalSpeed = .IsMoonOrbitalSpeed: IsMoonEscapeSpeed = .IsMoonEscapeSpeed
    IsMarsOrbitalSpeed = .IsMarsOrbitalSpeed: IsMarsEscapeSpeed = .IsMarsEscapeSpeed
    IsApogee = .IsApogee: IsPeriselene = .IsPeriselene
    IsAtmosphere = .IsAtmosphere: IsIonosphere = .IsIonosphere
    IsEquidistantPoint = .IsEquidistantPoint: IsL1LagrangePoint = .IsL1LagrangePoint
    IsMoonDistance = .IsMoonDistance
    IsControl = .IsControl
    End With
    
    StageUp False, False
    SatelliteInRange = 0: AsteroidInRange = 0: IsBoundary = 0: IsPopulated = 0
    IsTarget = ToNone: AccelX = 0: AccelY = 0: AccelZ = 0
    InertialAccel = 0: InertialAccelX = 0: InertialAccelY = 0: InertialAccelZ = 0
    ErrorP = 0: Erase ErrorX, ErrorY, ErrorZ, ErrorU, ErrorV, ErrorW
    IsEnginesEvent = False: IsDeployed = False: IsPlanetCollision = False
    If StatusText = "" Then IsTimeSnap = True _
    Else If InStr(StatusText, "  ") = 0 Then IsLastSnap = True
    If IsTimeSnap Or IsLastSnap Then
        For C = SnapP - 1 To 0 Step -1
            If Snap(C).StatusText <> "" Then StatusText = Snap(C).StatusText: Exit For
            Next
        End If
    PathP = 0: PathNext = 0
    For C = 0 To PathN - 1
        If Path(C).T > T Then Exit For
        Next
    PathP = C: If C < PathN Then PathNext = Path(C).T _
    Else If PathN > 0 Then PathNext = Path(PathN - 1).T + PathTime Else PathNext = PathTime
    GraphicP = Int(T)
Nxt:
    If Not IsLastSnap Then SnapP = SnapP + 1
    Play = Paused: Stats: Render: Info: Plot
    End Sub

Sub SaveSnap(Optional ByVal ShowMsg As Boolean, Optional ByVal Text As Boolean = True, _
        Optional ByVal Update As Boolean)
    If SnapP > UBound(Snap) Then ReDim Preserve Snap(UBound(Snap) + 100)
    If ShowMsg Then StatusText = "Snapshot": Msg StatusText, , 2: _
        StatusText = ToDhmsEx(T) & "  " & StatusText
    
    With Snap(SnapP)
    With .Ship: .SpeedX = SpeedX: .SpeedY = SpeedY: .SpeedZ = SpeedZ
    .AngleU = AngleU: .AngleV = AngleV: .AngleW = AngleW: End With
    SaveMatrix .Attitude, Attitude
    SaveMatrix .AngularSpeed, AngularSpeed
    .ControlThrustU = ControlThrustU: .ControlAccU = ControlAccU: .ControlLastU = ControlLastU
    .ControlThrustV = ControlThrustV: .ControlAccV = ControlAccV: .ControlLastV = ControlLastV
    .ControlThrustW = ControlThrustW: .ControlAccW = ControlAccW: .ControlLastW = ControlLastW
    .TargetAngle = TargetAngle
    SavePlanetPos .Sun, Sun
    SavePlanetPos .Earth, Earth
    SavePlanetPos .Moon, Moon
    If GravityMode >= 4 Then
        SavePlanetPos .Mercury, Mercury
        SavePlanetPos .Venus, Venus
        SavePlanetPos .Mars, Mars
        SavePlanetPos .Jupiter, Jupiter
        SavePlanetPos .Saturn, Saturn
        SavePlanetPos .Uranus, Uranus
        SavePlanetPos .Neptune, Neptune
        SavePlanetPos .Pluto, Pluto
        End If
    
    If IsEmptyStages Then If KMLPathN > 0 Or StageC > 0 Then .StageE = StageE: .StageC = StageC
    .StageP = StageP: .StageN = StageN: .Status = Status: .StatusText = IfText(Text, StatusText)
    .Mass = Mass: .Thrust = Thrust: .Throttle = Throttle: .ThrustP = ThrustP
    .T = T: .RealTime = RealTime + TimerEx - LastTime: .DelayTime = DelayTime
    .AccelTime = AccelTime: .BrakeTime = BrakeTime
    .Accel = Accel: .Drag = Drag: .Lift = Lift
    .ThrustDV = ThrustDV: .BurnOutDV = BurnOutDV: .GravityDV = GravityDV
    .DragDV = DragDV: .LiftDV = LiftDV: .Impulse = Impulse
    .MaxVe = MaxVe: .MinVe = MinVe
    .MaxHe = MaxHe: .MinHe = MinHe
    .MaxHm = MaxHm: .MinHm = MinHm
    .MaxG = MaxG: .ControlThrustMax = ControlThrustMax
    .IsLiftOff = IsLiftOff: .IsEarthSoundSpeed = IsEarthSoundSpeed
    .IsEarthOrbitalSpeed = IsEarthOrbitalSpeed: .IsEarthEscapeSpeed = IsEarthEscapeSpeed
    .IsMoonOrbitalSpeed = IsMoonOrbitalSpeed: .IsMoonEscapeSpeed = IsMoonEscapeSpeed
    .IsMarsOrbitalSpeed = IsMarsOrbitalSpeed: .IsMarsEscapeSpeed = IsMarsEscapeSpeed
    .IsApogee = IsApogee: .IsPeriselene = IsPeriselene
    .IsAtmosphere = IsAtmosphere: .IsIonosphere = IsIonosphere
    .IsEquidistantPoint = IsEquidistantPoint: .IsL1LagrangePoint = IsL1LagrangePoint
    .IsMoonDistance = IsMoonDistance
    .IsControl = IsControl
    End With
    
    SnapP = SnapP + 1: If SnapP > SnapN Then SnapN = SnapP
    If Play = Paused And Update Then Render: Info: DoEvents
    End Sub

Function SaveLast() As Boolean
    If SnapN > 0 Then If T > Snap(SnapN - 1).T Then _
        StatusText = ToDhmsEx(T): SaveSnap: SaveLast = True
    End Function

Sub RemoveLast()
    If SnapN > 0 Then If InStr(Snap(SnapN - 1).StatusText, "  ") = 0 Then _
        SnapN = SnapN - 1: If SnapP > SnapN Then SnapP = SnapN
    End Sub

Sub CalcStages()
    Dim B As Long, C As Long, D As Long, E As Long, F As Long, _
        M As Double, P As Double, X As Double, Y As Double, Z As Double
    With Stages
    IsCalc = True
    C = HoverMax
    M = ValMass(.Mass(0)): P = ValMass(.Mass(C + 1), M): _
        If .Stage(C) Then P = P + ValMass(.Mass(C), M)
    If M <= P Then GoTo Ext
    For C = C - 1 To 1 Step -1: B = B - .Stage(C): Next
    X = 0: Y = 1
    For E = 1 To 15
        For F = 1 To 10
            Z = 0: For D = 0 To B: Z = Z + P * X ^ D: Next
            If Z < M Then X = X + Y Else Exit For
            Next
        X = X - Y: Y = Y / 10
        Next
    M = M - P: Z = 1
    For C = StageMax To 1 Step -1
        If .Stage(C) Then
            B = B - 1: P = P * X: .Mass(C) = NumX(IfDbl(B > 0, P / M, Z), "0.0%")
            Z = Z - ValRatio(.Mass(C))
            End If
        Next
Ext:
    IsCalc = False
    End With
    End Sub

Sub AddStage( _
        Optional Name As String, Optional ByVal Active As Boolean = True, _
        Optional Mass As String, Optional Dry As String = "5%", _
        Optional Thrust As String = "150%", Optional Delay As String = "3s", _
        Optional Fuel As String = "APCP", _
        Optional Area As String = "10%", Optional Length As String = "10%", _
        Optional Control As String, Optional ControlP As String = "2", _
        Optional ControlI As String = "5", Optional ControlD As String = "1", _
        Optional LiftToDrag As String, Optional DragCf As String = "Round Nose", _
        Optional InertiaX As String, Optional InertiaY As String, _
        Optional InertiaZ As String, Optional CenterX As String, _
        Optional CenterY As String, Optional CenterZ As String, _
        Optional Battery As String = "500mAh", _
        Optional Imax As String = "1A", Optional Vmax As String = "5V", _
        Optional Power As String = "3W", _
        Optional Tmin As String = "250K", Optional Tmax As String = "350K", _
        Optional Emissivity As String = "0.25", _
        Optional IspS As String, Optional IspV As String)
    Dim N As Double, Vol As Double, F As Fuel, E As Engine
    If StageP > UBound(Stage) Then ReDim Preserve Stage(StageP)
    With Stage(StageP)
    .FullMass = 0: .DryMass = 0: .FuelMass = 0: .ThrustV = 0 'Initialize structure
    .ThrustS = 0: .BurnRate = 0: .Delay = 0: .IspV = 0: .IspS = 0
    .DryVolume = 0: .Fuel = "": .ThrustN = 0: Erase .ThrustCurve
    .DragN = 0: .DragStep = 0: Erase .DragCurve: .Cost = 0
    .FullArea = 0: .Area = 0: .Diameter = 0: .Length = 0: .Volume = 0
    .Control = 0: .ControlP = 1: .ControlI = 0: .ControlD = 0
    .InertiaX = 0: .InertiaY = 0: .InertiaZ = 0
    .CenterX = 0: .CenterY = 0: .CenterZ = 0
    .Battery = 0: .Imax = 0: .Vmax = 0
    .Power = 0: .Tmin = 0: .Tmax = 0: .Emissivity = 0
    If StageP = 1 And Not Active Then .FullMass = Stage(0).FullMass: GoTo NoStage
    GetCD Stage(StageP), DragCf, Name                       'Drag coefficient curve
    If LiftToDrag = "" Then .LiftToDrag = 1 _
    Else .LiftToDrag = ValRatio(LiftToDrag): If .LiftToDrag < 0 Then .LiftToDrag = 0: _
        MsgInit Name & " lift to drag ratio out of range"   'Lift to drag ratio
    If IsRatio(Control) Then                                'Attitude control thrust limit
        If StageP <= 0 Then GoTo NoControl
        .Control = -ValRatio(Control): If .Control > 0 Then GoTo NoControl
    ElseIf IsTorque(Control) Then
        If StageP > 0 Then GoTo NoControl
        .Control = -ValTorque(Control): If .Control > 0 Then GoTo NoControl
    Else
        .Control = ValForce(Control)
        If .Control < 0 Then
NoControl:
            .Control = 0: MsgInit Name & " control thrust limit out of range"
            End If
        End If
    .ControlP = ValNum(ControlP): If .ControlP < 0 Then .ControlP = 0: _
        MsgInit Name & " parameter P out of range"          'Attitude control P parameter
    .ControlI = ValNum(ControlI): If .ControlI < 0 Then .ControlI = 0: _
        MsgInit Name & " parameter I out of range"          'Attitude control I parameter
    If .ControlI > 0 Then .ControlI = 1 / .ControlI
    .ControlD = ValNum(ControlD): If .ControlD < 0 Then .ControlD = 0: _
        MsgInit Name & " parameter D out of range"          'Attitude control D parameter
    .InertiaX = ValInertia(InertiaX): If .InertiaX < 0 Then .InertiaX = 0: _
        MsgInit Name & " moment of inertia X out of range"  'Moment of inertia over X axis
    .InertiaY = ValInertia(InertiaY): If .InertiaY < 0 Then .InertiaY = 0: _
        MsgInit Name & " moment of inertia Y out of range"  'Moment of inertia over Y axis
    .InertiaZ = ValInertia(InertiaZ): If .InertiaZ < 0 Then .InertiaZ = 0: _
        MsgInit Name & " moment of inertia Z out of range"  'Moment of inertia over Z axis
    .CenterX = ValSpace(CenterX)                            'Center of mass in X axis
    .CenterY = ValSpace(CenterY)                            'Center of mass in Y axis
    .CenterZ = ValSpace(CenterZ)                            'Center of mass in Z axis
    .Battery = ValNum(Battery): If .Battery < 0 Then .Battery = 0: _
        MsgInit Name & " battery charge out of range"       'Battery charge
    .Imax = ValNum(Imax): If .Imax < 0 Then .Imax = 0: _
        MsgInit Name & " maximum current out of range"      'Maximum allowable current
    .Vmax = ValNum(Vmax): If .Vmax < 0 Then .Vmax = 0: _
        MsgInit Name & " maximum voltage out of range"      'Maximum allowable voltage
    .Power = ValNum(Power): If .Power < 0 Then .Power = 0: _
        MsgInit Name & " electrical power out of range"     'Electrical power
    .Tmin = ValNum(Tmin): If .Tmin < 0 Then .Tmin = 0: _
        MsgInit Name & " minimum temperature out of range"  'Minimum allowable temperature
    .Tmax = ValNum(Tmax): If .Tmax < 0 Then .Tmax = 0: _
        MsgInit Name & " maximum temperature out of range"  'Maximum allowable temperature
    If Emissivity = "" Then .Emissivity = 0.25 _
    Else .Emissivity = Val(Emissivity): If .Emissivity < 0 Then .Emissivity = 0: _
        MsgInit Name & " emissivity out of range"           'Emissivity .. Copper=0.15 Aluminum=0.25 Glass fiber=0.90
    .FullMass = ValMass(Mass, Sys.Mass)                     'Mass
    If .FullMass <= 0 Then .FullMass = 0.01: If Sys.Mass > 0 Then _
        MsgInit Name & " mass out of range"
    If IsRatio(Length) Then                                 'Length
        .Length = -ValRatio(Length): If .Length >= 0 Then GoTo NoLength
    Else
        .Length = ValSpace(Length)
        If .Length <= 0 Then
NoLength:
            .Length = 5: MsgInit Name & " length out of range"
            End If
        End If
    If IsRatio(Area) Then                                   'Diameter
        .Diameter = -ValRatio(Area): If .Diameter >= 0 Then GoTo NoArea
    ElseIf IsArea(Area) Then
        .Area = ValArea(Area): If .Area <= 0 Then GoTo NoArea
        .Diameter = Sqr(.Area / Pi * 4)
    Else
        .Diameter = ValSpace(Area): .Area = .Diameter ^ 2 * Pi / 4
        If .Diameter <= 0 Then
NoArea:
            .Diameter = 0.5: .Area = .Diameter ^ 2 * Pi / 4
            MsgInit Name & " area out of range"
            End If
        End If
    If StageP > 0 Then
        .DryMass = ValMass(Dry, .FullMass)                  'Dry mass
        If .DryMass <= 0 Then
            .DryMass = .FullMass * 0.1: GoTo NoDry
        ElseIf .DryMass >= .FullMass Then
            .DryMass = .FullMass * 0.9
NoDry:
            MsgInit Name & " dry mass out of range"
            End If
        N = Stage(StageP - 1).FullMass
        .FullMass = .FullMass + N: .DryMass = .DryMass + N  'Full mass with the previous stages and the payload
        .Delay = ValTime(Delay): If .Delay < 0 Then .Delay = 0: _
            MsgInit Name & " delay out of range"            'Delay time
        .IspS = Val(IspS): If .IspS < 0 Then .IspS = 0: _
            MsgInit Name & " Isp at sea level out of range" 'Specific impulse at sea level
        .IspV = Val(IspV): If .IspV < 0 Then .IspV = 0: _
            MsgInit Name & " Isp in vacuum out of range"    'Specific impulse in vacuum
        .Fuel = Fuel                                        'Propellant type
        .FuelMass = .FullMass - .DryMass                    'Propellant mass
        TotalFuelMass = TotalFuelMass + .FuelMass           'Total propellant mass
        TotalDryMass = TotalDryMass + .DryMass              'Total dry mass
        If GetFuel(F, .Fuel) Then
            Vol = .FuelMass / F.Density * F.Volume          'Volume = Mass / Density
            If .Length = 0 Then .Length = -0.1
            If .Diameter = 0 Then .Diameter = -0.1
            If .Length > 0 Then
                If .Diameter < 0 Then .Volume = Vol: .Diameter = Sqr(.Volume / Pi * 4 / .Length) _
                Else .Volume = .Diameter ^ 2 * Pi / 4 * .Length: If .Volume < Vol Then _
                    .Volume = Vol: .Length = .Volume / Pi * 4 / .Diameter ^ 2
            Else
                .Volume = Vol * (1 - .Length): If .Diameter > 0 Then _
                    .Length = .Volume / Pi * 4 / .Diameter ^ 2
                End If
            If .IspS <= 0 Then .IspS = F.IspS               'Specific impulse at sea level
            If .IspV <= 0 Then .IspV = F.IspV               'Specific impulse in vacuum
            If Right(Thrust, 2) = "/s" Or Right(Thrust, 3) = "mph" Or _
                    Right(Thrust, 3) = "kph" Or Left(Thrust, 4) = "Mach" Then
                .BurnRate = -ValSpace(Thrust): GoTo NoThrust
            ElseIf Right(Thrust, 1) = "s" Or Right(Thrust, 1) = "m" Or _
                    Right(Thrust, 1) = "h" Or Right(Thrust, 1) = "d" Then
                .BurnRate = .FuelMass / ValSpace(Thrust)    'Propellant burn rate at full thrust
                .ThrustS = .BurnRate * G0 * .IspS           'Engine thrust at Sea Level
            Else
                .ThrustS = ValForce(Thrust, .FullMass)      'Engine thrust at Sea Level
                .BurnRate = .ThrustS / G0 / .IspS           'Propellant burn rate at full thrust
                End If
            .ThrustV = .ThrustS * .IspV / .IspS: .ThrustS = .ThrustV - .ThrustS 'Engine thrust at Sea Level
            If GetCurve(E, F.Curve, .FuelMass / .BurnRate, .ThrustV - .ThrustS) Then _
                .ThrustCurve = E.ThrustCurve: .ThrustN = E.ThrustN 'Thrust curve
NoThrust:
            .Cost = .FuelMass * F.Cost                      'Propellant cost
        ElseIf GetEngine(E, .Fuel) Then
            .Diameter = E.Diameter: .Length = E.Length      'Diameter and length
            .Volume = .Diameter ^ 2 * Pi / 4 * .Length      'Volume = Front area * Length
            .ThrustV = E.ThrustV: .ThrustS = E.ThrustV - E.ThrustS 'Mean thrust
            If .IspS <= 0 Then .IspS = E.IspS               'Specific impulse at sea level
            If .IspV <= 0 Then .IspV = E.IspV               'Specific impulse in vacuum
            .BurnRate = E.BurnRate                          'Average burn rate
            .ThrustCurve = E.ThrustCurve: .ThrustN = E.ThrustN 'Thrust curve
            .Cost = .FuelMass * 37.85                       'Propellant cost (assumed APCP)
        ElseIf StageP > 0 Then
            .Diameter = -0.1: .Length = -0.1: .Volume = 0.007
            .ThrustV = .FullMass * G0 * 2: .ThrustS = 0
            .IspV = 250: .IspS = .IspV: .BurnRate = .ThrustV / G0 / .IspV
            MsgInit Name & " propellant not defined"
            End If
        If .Volume <= 0 Then .Volume = 0.1
        If Sys.Diameter < .Diameter Then Sys.Diameter = .Diameter
    Else
        '.DryMass = .FullMass
        If .Length <= 0 Then .Length = 0.15
        If .Diameter <= 0 Then .Diameter = 0.15
        .Volume = .Diameter ^ 2 * Pi / 4 * .Length
        End If
    If .Length > 0 Then Sys.Length = Sys.Length + .Length   'Total length
    Sys.Volume = Sys.Volume + .Volume                       'Total volume
    Sys.Cost = Sys.Cost + .Cost                             'Total fuel cost
NoStage:
    End With
    StageP = StageP + 1
    End Sub

Sub AddStageEx(Stage As Stage, H As Double)
    Dim F As Fuel, E As Engine
    With Stage
    If .Diameter <= 0 Then .Diameter = (-.Diameter) ^ (1 / 3) * H
    If .Diameter > 0 Then .Area = .Diameter ^ 2 * Pi / 4            'Front area = Diameter ^ 2 * PI / 4
    If .Length < 0 Then .Length = .Volume / .Area                   'Length = Volume / Front area
    .FullArea = .Area * 2 + .Length * .Diameter * Pi                'Surface area = Front area * 2 + Length * Diameter * PI
    '.Emissivity = Emissivity * FullArea / (FullArea + .FullArea) + _
        .Emissivity * .FullArea / (FullArea + .FullArea)            'Total emissivity
    FullArea = FullArea + .FullArea                                 'Total surface area
    .FullArea = FullArea: Emissivity = .Emissivity
    If Area < .Area Then Area = .Area Else .Area = Area             'Maximum front area
    If Diameter < .Diameter Then Diameter = .Diameter               'Maximum diameter
    Length = Length + .Length: .Length = Length                     'Total length
    Volume = Volume + .Volume                                       'Total volume
    Cost = Cost + .Cost                                             'Total fuel cost
    If .BurnRate < 0 Then
        .BurnRate = -.FuelMass / .Diameter * 2 * .BurnRate          'Propellant burn rate at full thrust
        .ThrustS = .BurnRate * G0 * .IspS                           'Engine thrust at Sea Level
        .ThrustV = .ThrustS * .IspV / .IspS: .ThrustS = .ThrustV - .ThrustS 'Engine thrust at Sea Level
        If GetFuel(F, .Fuel) Then _
            If GetCurve(E, F.Curve, .FuelMass / .BurnRate, .ThrustV - .ThrustS) Then _
                .ThrustCurve = E.ThrustCurve: .ThrustN = E.ThrustN  'Thrust curve
        End If
    End With
    End Sub

Sub StageUp(Optional ByVal Up As Boolean = True, Optional ByVal Init As Boolean = True)
    If Up Then StageP = StageP - 1: StageN = StageN + 1
    With Stage(StageP)
    If Init Then ThrustP = .ThrustN < 2: Throttle = 1
    ThrustN = .ThrustN: ThrustCurve = .ThrustCurve: BurnRate = .BurnRate
    IspS = .IspS: IspV = .IspV: ThrustV = .ThrustV: ThrustS = .ThrustS
    FullMass = .FullMass: DryMass = .DryMass: If Init Then Mass = FullMass
    FullArea = .FullArea: Area = .Area: Diameter = .Diameter: Length = .Length
    DragN = .DragN: DragCurve = .DragCurve
    DragStep = .DragStep: LiftToDrag = .LiftToDrag
    Control = .Control: ControlP = .ControlP: ControlI = .ControlI: ControlD = .ControlD
    InertiaX = .InertiaX: InertiaY = .InertiaY: InertiaZ = .InertiaZ
    Battery = .Battery: Imax = .Imax: Vmax = .Vmax: Power = .Power
    Tmin = .Tmin: Tmax = .Tmax: Emissivity = .Emissivity
    If StageP > 2 Then Delay = Stage(StageP - 1).Delay Else Delay = 0
    End With
    StageModel
    End Sub

Sub StageModel()
    Dim N As Long
    Select Case StageP
    Case 0: ShipList = SatelliteList
    Case 1: ShipList = HoverList
    Case Else: ShipList = StageList + IfInt(StageN > StageMax, StageMax, StageN)
        End Select
    End Sub

Sub AddEmptyStage(Optional ByVal Reset As Boolean)
    If Reset Then StageC = 0: ReDim StageE(0) _
    Else StageC = StageC + 1: ReDim Preserve StageE(StageC)
    With StageE(StageC)
    .X = -Earth.X: .Y = -Earth.Y: .Z = -Earth.Z
    .SpeedX = SpeedX - Earth.MainSpeedX
    .SpeedY = SpeedY - Earth.MainSpeedY
    .SpeedZ = SpeedZ - Earth.MainSpeedZ
    End With
    End Sub

Sub ClearStats()
    AsteroidInRange = 0: SatelliteInRange = 0
    ThrustDV = 0: BurnOutDV = 0: GravityDV = 0: DragDV = 0: LiftDV = 0: Impulse = 0
    MaxVe = 0: MinVe = 0: MaxHe = 0: MinHe = 0: MaxHm = 0: MinHm = 0
    MaxG = 0: ControlThrustMax = 0
    End Sub

Function FinArea(ByVal Nf As Long, Di0 As Double, D0 As Double, _
        Fd0 As Double, Fw0 As Double, Optional ByVal GetPerimeter As Boolean) As Double
    Dim D1 As Double, _
        Fa As Double, Fa0 As Double, Fa1 As Double, Fa2 As Double, Fa3 As Double, _
        Fb0 As Double, Fb1 As Double, Fb2 As Double, Fd1 As Double, Fd2 As Double, _
        Fe1 As Double, Fe2 As Double, Fw1 As Double, Fw2 As Double
    D1 = D0 / 2: Fw1 = Fw0 / 2: Fb0 = Di0 / 2: Fd1 = Fd0: Fa1 = Pi / Nf 'Propellant diameter and fin width, base, depth and section angle
    Fe1 = Fb0 + Fd1: If Fe1 > 0 Then _
        Fe2 = Fe1: Fw2 = Fw1: Fb1 = Fb0 Else Exit Function           'Check inner core outside chamber
    Fa3 = Atn(Fw1 / Fe1): If Fa3 >= Fa1 Then _
        Fa3 = Fa1: Fw2 = Fe1 * Tan(Fa3): If Fw2 <= Fw1 Then _
            Fw1 = Fw2 Else Fw2 = Fw1                                 'Check fin end walls intersection .. fin width adjusted
    If Sqr(Fw1 * Fw1 + Fe1 * Fe1) >= D1 Then                         'Check fin corners outside chamber
        If Fa3 >= Fa1 Then Fe2 = D1 * Cos(Fa1): Fw1 = D1 * Sin(Fa1) _
        Else If Fw1 >= D1 Then Fe2 = 0 Else Fe2 = Sqr(D1 * D1 - Fw1 * Fw1) 'Fin end position and fin width adjusted
        If Fe1 >= D1 Then Fw2 = 0 Else Fw2 = Sqr(D1 * D1 - Fe1 * Fe1) 'Fin end wall width adjusted
        End If
    If Fw2 < 0 Then Fw2 = 0                                          'Check fin end wall exists
    If Fw1 >= Fb0 Then Fb2 = 0 Else Fb2 = Sqr(Fb0 * Fb0 - Fw1 * Fw1) 'Fin side wall and core intersection
    If Fb0 <= 0 Then Fa = 0 Else Fa = Fa1 - ASin(Fw1 / Fb0)          'Check fin side walls intersection
    If Fa <= 0 Then Fa = 0: Fb1 = Fw1 / Sin(Fa1): _
        If Fw1 >= Fb1 Then Fb2 = 0 Else Fb2 = Sqr(Fb1 * Fb1 - Fw1 * Fw1) 'Fin base adjusted
    Fd2 = Fe2 - Fb2: If Fd2 <= 0 Then Fd2 = 0: Fe2 = Fb2             'Check fin side wall exist
    If Fe2 > 0 Then Fa2 = Atn(Fw1 / Fe2)                             'Fin side wall angle
    If Fe1 > 0 Then Fa3 = Atn(Fw2 / Fe1)                             'Fin end wall angle
    If GetPerimeter Then FinArea = (Fw2 + Fd2 + Fb0 * Fa) * 2 * Nf: Exit Function
    FinArea = Fw1 * Fb2 + Fw1 * 2 * Fd2
    If Fa > 0 Then FinArea = FinArea + Fb0 * Fb0 * Fa
    If Fe2 < Fe1 Then FinArea = FinArea + D1 * D1 * Fa2 - Fw1 * Fe2: _
        If Fe1 < D1 Then FinArea = FinArea - D1 * D1 * Fa3 + Fw2 * Fe1
    FinArea = FinArea * Nf
    End Function

Function DrawFins(Optional Pic As PictureBox, _
        Optional ByVal Nf As Long, Optional Di0 As Double, Optional D0 As Double, _
        Optional Fd0 As Double, Optional Fw0 As Double, Optional ByVal W As Double, _
        Optional ByVal I As Double, Optional ByVal X As Single, Optional ByVal Y As Single, _
        Optional ByVal C As Long, Optional ByVal B As Long = -1) As Double
    Dim N As Long, D1 As Double, R As Double, SinF As Double, CosF As Double, _
        Fa As Double, Fa0 As Double, Fa1 As Double, Fa2 As Double, Fa3 As Double, _
        Fb0 As Double, Fb1 As Double, Fb2 As Double, Fd1 As Double, Fd2 As Double, _
        Fe1 As Double, Fe2 As Double, Fw1 As Double, Fw2 As Double
    D1 = W: R = W * 2 / D0: Fw1 = (Fw0 / 2 + I) * R: Fb0 = (Di0 / 2 + I) * R
    Fd1 = Fd0 * R: Fa1 = A180 / Nf                                   'Propellant diameter and fin width, base, depth and section angle
    Fe1 = Fb0 + Fd1: If Fe1 > 0 Then _
        Fe2 = Fe1: Fw2 = Fw1: Fb1 = Fb0 Else Exit Function           'Check inner core outside chamber
    Fa3 = Atn(Fw1 / Fe1): If Fa3 >= Fa1 Then _
        Fa3 = Fa1: Fw2 = Fe1 * Tan(Fa3): If Fw2 <= Fw1 Then _
            Fw1 = Fw2 Else Fw2 = Fw1                                 'Check fin end walls intersection .. fin width adjusted
    If Sqr(Fw1 * Fw1 + Fe1 * Fe1) >= D1 Then                         'Check fin corners outside chamber
        If Fa3 >= Fa1 Then Fe2 = D1 * Cos(Fa1): Fw1 = D1 * Sin(Fa1) _
        Else If Fw1 >= D1 Then Fe2 = 0 Else Fe2 = Sqr(D1 * D1 - Fw1 * Fw1) 'Fin end position and fin width adjusted
        If Fe1 >= D1 Then Fw2 = 0 Else Fw2 = Sqr(D1 * D1 - Fe1 * Fe1) 'Fin end wall width adjusted
        End If
    If Fw2 < 0 Then Fw2 = 0                                          'Check fin end wall exists
    If Fw1 >= Fb0 Then Fb2 = 0 Else Fb2 = Sqr(Fb0 * Fb0 - Fw1 * Fw1) 'Fin side wall and core intersection
    If Fb0 <= 0 Then Fa = 0 Else Fa = Fa1 - ASin(Fw1 / Fb0)          'Check fin side walls intersection
    If Fa <= 0 Then Fa = 0: Fb1 = Fw1 / Sin(Fa1): _
        If Fw1 >= Fb1 Then Fb2 = 0 Else Fb2 = Sqr(Fb1 * Fb1 - Fw1 * Fw1) 'Fin base adjusted
    Fd2 = Fe2 - Fb2: If Fd2 <= 0 Then Fd2 = 0: Fe2 = Fb2             'Check fin side wall exist
    If Fe2 > 0 Then Fa2 = Atn(Fw1 / Fe2)                             'Fin side wall angle
    If Fe1 > 0 Then Fa3 = Atn(Fw2 / Fe1)                             'Fin end wall angle
    If Pic Is Nothing Then DrawFins = IfDbl(Fb1 < Fe1, Fb1, Fe1): Exit Function
    'If B = -1 And Fb0 < D1 And Fb0 < Fb1 Then Pic.Circle (X, Y), Fb0, C Or &HA0A0E0
    'If B = -1 And Fb1 < D1 Then Pic.Circle (X, Y), Fb1, C Or &HA0A0E0
    'If B = -1 Then For N = 1 To Nf * 2 - 1 Step 2: LineEx Pic, X, Y, 0, 0, 0, D1, C Or &HD0D0D0, , A180 / Nf * N: Next
    For N = 0 To Nf - 1
        Fa0 = A360 / Nf * N: SinF = Sin(Fa0): CosF = Cos(Fa0)
        If B <> -1 Then
            LineEx Pic, X, Y, -Fw1, 0, Fw1, Fe1, C, B, , SinF, CosF
        Else
            'If Fa > 0 And Fb0 < D1 Then CircleEx Pic, X, Y, Fb0, C, , Fa0 + Fa1 - Fa, Fa0 + Fa1 + Fa
            'If N = 0 And I > 0 Then LineEx Pic, X, Y, 0, 0, 0, D1, &HA0A0A0, , , SinF, CosF
            'If N = 0 And I > 0 Then LineEx Pic, X, Y, 0, 0, D1 * Sin(Fa1), D1 * Cos(Fa1), vbGreen, , , SinF, CosF
            'If N = 0 And I > 0 Then LineEx Pic, X, Y, 0, 0, D1 * Sin(Fa1 - Fa), D1 * Cos(Fa1 - Fa), vbBlue, , , SinF, CosF
            'If N = 0 And I > 0 Then LineEx Pic, X, Y, 0, 0, D1 * Sin(Fa2), D1 * Cos(Fa2), vbRed, , , SinF, CosF
            'If N = 0 And I > 0 Then LineEx Pic, X, Y, 0, 0, D1 * Sin(Fa3), D1 * Cos(Fa3), vbMagenta, , , SinF, CosF
            If Fe2 > Fb2 And Fw1 = Fw2 Then
                LineEx Pic, X, Y, Fw1, Fb2, Fw1, Fe2, C, , , SinF, CosF
                LineEx Pic, X, Y, , , -Fw1, Fe2, C, , , SinF, CosF
                LineEx Pic, X, Y, , , -Fw1, Fb2, C, , , SinF, CosF
            Else
                If Fe2 > Fb2 Then LineEx Pic, X, Y, Fw1, Fb2, Fw1, Fe2, C, , , SinF, CosF: _
                    LineEx Pic, X, Y, -Fw1, Fb2, -Fw1, Fe2, C, , , SinF, CosF
                If Fw2 > 0 Then LineEx Pic, X, Y, Fw2, Fe1, -Fw2, Fe1, C, , , SinF, CosF
                End If
            End If
        Next
    End Function

'_Init_and_Nav_functions_______________________________________________________

Sub MsgInit(Text As String)
    If ShowErrors And InitTxt = "" Then InitTxt = Text
    End Sub

Sub InitParams()
    Dim C As Long, P As Long
    'Apply changes in parameters that are not relevant to the current trajectory
    SaveLastPresetParams
    With LastPreset
    EarthPark = ValTime(.EarthPark)                         'Earth orbit parking time
    If EarthPark < 0 Then EarthPark = 0
    MoonPark = ValTime(.MoonPark)                           'Moon orbit parking time
    ThrustLimit = ValForceG(.ThrustLimit)                   'Engine thrust limit
    If ThrustLimit < 0 Then ThrustLimit = 0
    If InStr(.LiftLimit, "º") Then                          'Wing lift limit
        LiftLimit = -ValAngle(.LiftLimit): If LiftLimit > 0 Then LiftLimit = 0
    Else
        LiftLimit = ValForceG(.LiftLimit): If LiftLimit < 0 Then LiftLimit = 0
        End If
    Brake = ValSpace(.Brake): If Brake < 0 Then Brake = 0   'Braking distance
    If IsRatio(.Parachute) Then                             'Parachute
        Parachute = -SqrDbl(ValRatio(.Parachute)): If Parachute > 0 Then Parachute = 0
    Else
        If IsArea(.Parachute) Then Parachute = ValArea(.Parachute) _
        Else Parachute = SqrDbl(ValSpace(.Parachute)) * Pi / 4
        If Parachute < 0 Then Parachute = 0
        End If
    For P = 0 To UBound(Stage)
        If P >= 2 Then C = UBound(Stage) - P + 1 Else C = PayloadMax - P
        With LastPreset.Stage(C)
        If P Then
            If IsRatio(.Control) Then Stage(P).Control = -ValRatio(.Control) _
            Else Stage(P).Control = ValForce(.Control)      'Attitude control thrust limit
        Else
            If InStr(.Control, "Am²") Or InStr(.Control, "Am2") Then Stage(P).Control = -ValTorque(.Control) _
            Else Stage(P).Control = ValForce(.Control)      'Attitude control thrust limit
            End If
        Stage(P).ControlP = ValNum(.ControlP)               'Attitude control P parameter
        Stage(P).ControlI = ValNum(.ControlI): If Stage(P).ControlI > 0 Then _
            Stage(P).ControlI = 1 / Stage(P).ControlI Else Stage(P).ControlI = 0 'Attitude control I parameter
        Stage(P).ControlD = ValNum(.ControlD): If Stage(P).ControlD < 0 Then _
            Stage(P).ControlD = 0                           'Attitude control D parameter
        Stage(P).Delay = ValTime(.Delay)                    'Delay time
        Stage(P).Power = ValNum(.Power)                     'Electrical power
        If .Emissivity = "" Then Stage(P).Emissivity = 0.25 _
        Else Stage(P).Emissivity = Val(.Emissivity)         'Emissivity .. Copper=0.15 Aluminum=0.25 Glass fiber=0.90
        End With
        If P = StageP Then
            With Stage(StageP)
            Control = .Control: ControlP = .ControlP: ControlI = .ControlI: ControlD = .ControlD
            Delay = .Delay: Power = .Power: Emissivity = .Emissivity
            End With
            End If
        Next
    End With
    If Play = Paused Then Render
    End Sub

Sub Init( _
        Optional ByVal InitLog As Boolean = True, _
        Optional ByVal InitStages As Boolean = True, _
        Optional ByVal InitStep As Boolean = True, _
        Optional ByVal ExecStats As Boolean = True, _
        Optional ByVal ExecRender As Boolean = True, _
        Optional ByVal ExecInfo As Boolean = True, _
        Optional ByVal ExecPlot As Boolean = True, _
        Optional ByVal RefreshData As Boolean, _
        Optional ByVal RefreshGraph As Boolean)
    
    Dim B As Long, C As Long, D As Long, S As String, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        H1 As Double, G1 As Double, A1 As Double
    
    SaveLastPreset
    
'Statistics
    InitTxt = "": ClearStats
    DelayTime = 0: AccelTime = -1: BrakeTime = -1
    SatelliteInRange = 0: AsteroidInRange = 0
    IsLiftOff = True: IsEarthSoundSpeed = True
    IsEarthOrbitalSpeed = True: IsEarthEscapeSpeed = True
    IsMoonOrbitalSpeed = True: IsMoonEscapeSpeed = True
    IsMarsOrbitalSpeed = True: IsMarsEscapeSpeed = True
    IsApogee = True: IsPeriselene = True
    IsAtmosphere = True: IsIonosphere = True
    IsEquidistantPoint = True: IsL1LagrangePoint = True
    IsMoonDistance = True
    IsLinkError = True: IsEnginesEvent = False
    IsDeployed = False: IsPlanetCollision = False
    If HiDefLog Then FmtLog = FLong Else FmtLog = FShort
    
'Graph
    GraphicN = 0: GraphicP = 0: GraphicWin = 0
    ReDim Graphic(GraphicMax - 1)
    ReDim IsSatellite(UBound(Satellite)): ReDim IsAsteroid(UBound(Asteroid))
    ReDim IsComet(UBound(Comet)): ReDim IsMoon(UBound(Moons))
    If KMLPathPos = 0 Then _
        ReDim IsStation(UBound(Station)): ReDim IsLocation(UBound(Location)): _
        ReDim IsAirspace(AirspaceN): IsBoundary = 0: IsPopulated = 0
    
    With LastPreset
    
'Simulation setup
    If VideoSync Then If Not Video.WMP Is Nothing Then If T > 0 Then _
        With Video.WMP.object: .CurrentPosition = .CurrentPosition - T: End With
    T = 0: RealTime = 0                                     'Simulation and real time
    StartTime = TimerEx: StartT = T: StepTime = StartTime: StepT = T
    FrameCount = 0: FrameTime = StartTime
    If InitStep Then                                        'Time step
        If .TimeStep = "" Then dT = 0.05 _
        Else dT = ValTime(.TimeStep): If dT < dTMin Or dT > dTMax Then _
            MsgInit "Simulation time step out of range"
    Else
        If IsAdaptive Then _
            If Target = ToSun Then dT = dT / 100 _
            Else If Zone = VOID Then dT = dT / 10
        End If
    If dT < dTMin Then dT = dTMin Else If dT > dTMax Then dT = dTMax
    ReDim Path(1000 - 1): PathN = 0: PathP = 0: PathNext = 0 'Initialize path
    If KMLPathPos = 0 Then ReDim Snap(100 - 1): _
        SnapN = 0: SnapP = 0: LastSnap = -1                 'Initialize snapshots
    IsPathModified = False                                  'Trajectory modified flag
    
'Planets position
    If AtmN Then If AtmDate <> Setup.Date Or AtmTime <> Setup.Time Then GRIBLoad True, False
    B = False: Time = ValDateEx(.Date, , , B): If B Then _
        MsgInit "Launch date out of range"                  'Launch date
    B = False: Time = Time + ValTimeEx(.Time, , B): If B Then _
        MsgInit "Launch time out of range"                  'Launch time
    If KMLPathPos > 0 And KMLPathPos <= KMLPathTop Then If KMLPath(KMLPathPos).Time <> 0 Then _
        Time = KMLPath(IfInt(Play = Paused, 0, KMLPathPos)).Time
    If Time < MinDate Then Time = MinDate Else If Time > MaxDate Then Time = MaxDate
    A1 = Time / Day: H1 = A1 / YearDays
    PlanetSet Sun
    PlanetPos Earth, Sun, Time, , -1
    Earth.AngleU = (-23.496932 + 0.86 * Sin(((H1) + 2000 - 1550) / 41013 * A360)) * Rad 'Axial tilt
    Earth.SinA = Sin(Earth.AngleU): Earth.CosA = Cos(Earth.AngleU)
    Earth.AngleW = -ATan(Earth.X, Earth.Y) + (A1 - Int(A1)) * A360 'Rotation position
    Earth.AngularSpeedW = 360 / (86164.090530833 + 0.0023 * H1 / 100) * Rad 'Rotation speed
    PlanetPos Moon, Sun, Time, , -1
    Moon.AngleW = -ATan(Moon.X, Moon.Y)                     'Rotation position
    PlanetPos Sun, Earth, Time, , -1
    PlanetPos Mercury, Earth, Time, , -1
    PlanetPos Venus, Earth, Time, , -1
    PlanetPos Mars, Earth, Time, , -1
    PlanetPos Jupiter, Earth, Time, , -1
    PlanetPos Saturn, Earth, Time, , -1
    PlanetPos Uranus, Earth, Time, , -1
    PlanetPos Neptune, Earth, Time, , -1
    PlanetPos Pluto, Earth, Time, , -1
    
'Stage setup
    If Not InitStages Then GoTo NoStages
    If IsRatio(.Stage(PayloadMax).Mass) Then G1 = 1 _
        Else G1 = ValMass(.Stage(PayloadMax).Mass): If G1 <= 0 Then G1 = 1
    Mass = ValMass(.Mass, G1): If Mass <= 0 Then Mass = 0: _
        MsgInit "Total mass out of range"                   'Full mass with propellant, structure and payload
    Volume = 0: FullArea = 0: Area = 0: Diameter = 0        'Initialize parameters
    Length = 0: Emissivity = 0: Cost = 0
    TotalFuelMass = 0: TotalDryMass = 0
    StageP = 0: ReDim Stage(1)                              'Initialize stages
    C = PayloadMax: With .Stage(C)
    AddStage "Payload", , .Mass, , , , , .Area, .Length, _
        .Control, .ControlP, .ControlI, .ControlD, _
        .LiftToDrag, .DragCf, .InertiaX, .InertiaY, .InertiaZ, _
        .CenterX, .CenterY, .CenterZ, .Battery, .Imax, .Vmax, _
        .Power, .Tmin, .Tmax, .Emissivity
    End With
    C = C - 1: With .Stage(C)
    AddStage "Hover", .Active, .Mass, _
        .Dry, .Thrust, .Delay, .Fuel, .Area, .Length, _
        .Control, .ControlP, .ControlI, .ControlD, .LiftToDrag, .DragCf, _
        .InertiaX, .InertiaY, .InertiaZ, .CenterX, .CenterY, .CenterZ, _
        .Battery, .Imax, .Vmax, .Power, .Tmin, .Tmax, .Emissivity, .IspS, .IspV
    End With
    Mass = Mass - Stage(StageP - 1).FullMass                'Mass without hover and payload
    If Mass < 0 Then Mass = 0
    For C = C - 1 To 1 Step -1: With .Stage(C)
        D = 1: S = .Fuel: B = InStr(S, "x"): If B Then _
            D = Int(Val(Mid(S, B + 1))): S = Left(S, B - 1): _
            If D <= 0 Then D = 1: MsgInit "Stage " & C & " multistage out of range"
        If .Active Then For B = 1 To D: AddStage "Stage " & C, , .Mass, _
            .Dry, .Thrust, .Delay, S, .Area, .Length, _
            .Control, .ControlP, .ControlI, .ControlD, .LiftToDrag, .DragCf, _
            .InertiaX, .InertiaY, .InertiaZ, .CenterX, .CenterY, .CenterZ, _
            .Battery, .Imax, .Vmax, .Power, .Tmin, .Tmax, .Emissivity, .IspS, .IspV: Next
        End With: Next
    If StageP > 2 And Mass <= 0 Then Mass = 0: _
        MsgInit "Total mass too low"
    'Ar=Dr^2*PI/4 Diameter=Sqr(Area/PI*4) Diameter=(Volume/PI*4/Ratio)^(1/3)
    H1 = Volume / Pi * 4: If Diameter <= 0 Then Diameter = H1 ^ (1 / 3)
    H1 = H1 / Diameter ^ 2
    Volume = 0: FullArea = 0: Area = 0: Diameter = 0        'Initialize parameters
    Length = 0: Cost = 0
    For C = 0 To StageP - 1: AddStageEx Stage(C), H1: Next
    If Diameter > 0 Then LengthToDiameter = Length / Diameter _
        Else LengthToDiameter = 1
    Jettison = .Jettison                                    'Jettison upper stages on abort
    Abort = Val(.Abort): If Abort < 0 Then _
        Abort = -Abort: Jettison = True                     'Stage abort number (optional)
    If Abort = 0 Then Abort = StageP _
    Else If Abort > StageP - 2 Then Abort = StageP: _
        MsgInit "Stage abort number out of range"
    'If InStr(LCase(.Abort), "n") Then Jettison = 1          'Do not jettison stages on burn out
    StageP = StageP - 1: StageN = 1: StageT = StageP
    Thrust = 0: Throttle = 1: ThrustP = -1: StageUp False
    'If CStr(ValMass(Stages.Mass(0))) <> CStr(Mass) Then Stages.Mass(0) = ToMass(Mass, "") 'Total mass check
    TargetsN = UBound(.Targets): ReDim Targets(TargetsN) 'Target list
    For C = 1 To TargetsN
        Targets(C).N = GetN(.Targets(C).Class, .Targets(C).Name)
        Targets(C).Class = .Targets(C).Class
        Next
NoStages:
    If KMLPathPos > 0 And KMLPathPos <= KMLPathTop Then ShipList = TrackList
    
'Launch setup
    Status = Launch                                         'Phase number
    StatusText = "0s  Launch"                               'Phase number text
    EarthPark = ValTime(.EarthPark): If EarthPark < 0 Then EarthPark = 0: _
        MsgInit "Earth parking delay out of range"          'Earth orbit parking delay
    MoonPark = ValTime(.MoonPark)                           'Moon orbit parking delay
    Select Case LCase(Right(.MoonPark, 1))
    Case "r": MoonPark = -MoonPark
    Case "o": MoonPark = -MoonPark - Week
        End Select
    ThrustLimit = ValForceG(.ThrustLimit): If ThrustLimit < 0 Then ThrustLimit = 0: _
        MsgInit "Engine thrust limit out of range"          'Engine thrust limit
    If InStr(.LiftLimit, "º") Then                          'Wing lift limit
        LiftLimit = -ValAngle(.LiftLimit): If LiftLimit > 0 Then GoTo NoLiftLimit
    Else
        LiftLimit = ValForceG(.LiftLimit)
        If LiftLimit < 0 Then
NoLiftLimit:
            LiftLimit = 0: MsgInit "Lift force limit out of range"
            End If
        End If
    Brake = ValSpace(.Brake): If Brake < 0 Then Brake = 0: _
        MsgInit "Braking distance out of range"             'Braking distance
    If IsRatio(.Parachute) Then                             'Parachute
        Parachute = -SqrDbl(ValRatio(.Parachute)): If Parachute > 0 Then GoTo NoParachute
    Else
        If IsArea(.Parachute) Then Parachute = ValArea(.Parachute) _
        Else Parachute = SqrDbl(ValSpace(.Parachute)) * Pi / 4
        If Parachute < 0 Then
NoParachute:
            Parachute = 0: MsgInit "Parachute size out of range"
            End If
        End If
    EarthAngle = Earth.AngleW                               'Initial Earth orientation
    'Launch trajectory setup
    Select Case LCase(.Heading)                             'Trajectory heading
    Case "e", "": Heading = A90: Case "w": Heading = -A90
    Case "n": Heading = 0: Case "s": Heading = A180
    Case Else: Heading = ValAngle(.Heading)
        End Select
    StartAngle = ValPitch(.StartAngle): StartAlt = ValSpace(.StartAlt) 'Start and end angles
    EndAngle = ValPitch(.EndAngle): EndAlt = ValSpace(.EndAlt) 'Start and end altitudes
    If EndAlt < StartAlt Then EndAlt = StartAlt: _
        MsgInit "Launch start altitude higher than end altitude"
    'Launch position
    If .Angle = "" Then InitialAngle = 0 _
    Else InitialAngle = ValPitch(.Angle)                    'Launch pitch angle
    Lon = ValAngle(.Lon): Lat = ValAngle(.Lat)              'Launch coordinates
    A1 = 90 * Rad: If Lat < -A1 Or Lat > A1 Then _
        MsgInit "Launch latitude out of range" 'Do not use A90
    A1 = 180 * Rad: If Lon < -A1 Or Lon > A1 Then _
        MsgInit "Launch longitude out of range" 'Do not use A180
    If .Alt = "" Then Alt = AltEarth(Lat, Lon) Else Alt = ValSpace(.Alt): IsLiftOff = False
    Select Case LCase(Right(.Alt, 1))
    Case "d": Alt = Alt + GeoidEarth(Lat, Lon)              'Geoid altitude
    Case "g": Alt = Alt + AltEarth(Lat, Lon)                'Ellipsoidal altitude
        End Select
    If KMLPathPos > 0 And KMLPathPos <= KMLPathTop Then
        Interpolate3D _
            KMLPathAlt, KMLPath(KMLPathPos - 1).Alt, KMLPath(KMLPathPos).Alt, _
            Lon, Lat, Alt, _
            KMLPath(KMLPathPos - 1).Lon, KMLPath(KMLPathPos - 1).Lat, KMLPath(KMLPathPos - 1).Alt, _
            KMLPath(KMLPathPos).Lon, KMLPath(KMLPathPos).Lat, KMLPath(KMLPathPos).Alt
        InitialAngle = 0
        End If
    LLA2XYZ Earth, Lat, Lon + Earth.AngleW, Alt, X1, Y1, Z1, H1 'Cartesian coordinates
    'Initial speed
    G1 = 0
    Select Case LCase(Right(.Speed, 2))
    Case "vo", "ov": InitialSpeed = Sqr(Earth.GravityMass / (Earth.Radius + Alt)): G1 = ValSpace(.Speed)
    Case "ve", "ev": InitialSpeed = Sqr(Earth.GravityMass / (Earth.Radius + Alt)) * Sq2: G1 = ValSpace(.Speed)
    Case Else: Select Case LCase(Right(.Speed, 1))
        Case "o": InitialSpeed = ValSpace(.Speed) + Sqr(Earth.GravityMass / (Earth.Radius + Alt))
        Case "e": InitialSpeed = ValSpace(.Speed) + Sqr(Earth.GravityMass / (Earth.Radius + Alt) * 2)
        Case "a": InitialSpeed = ValSpace(.Speed)
        Case "v": InitialSpeed = H1 * Earth.AngularSpeedW: G1 = ValSpace(.Speed)
        Case Else: InitialSpeed = ValSpace(.Speed) + H1 * Earth.AngularSpeedW
            End Select
        End Select
    SpeedX = InitialSpeed: SpeedY = 0: SpeedZ = 0           'Initial speed
    Rotate Earth.AngleW + Lon + A180, SpeedX, SpeedY
    'Initial attitude vector
    Initialize Attitude: Initialize AngularSpeed            'Initialize attitude and angular speed matrices
    'Rotate Heading - A90, SpeedX, SpeedZ
    Rotate Earth.AngleU, Y1, Z1: Rotate Earth.AngleU, SpeedY, SpeedZ 'Rotation correction due to Earth axial tilt
    RotateX Attitude, -InitialAngle                         'Pitch
    RotateZ Attitude, Heading                               'Heading
    RotateX Attitude, A90 - Lat                             'Latitude
    RotateZ Attitude, -Lon - Earth.AngleW                   'Longitude and Earth orientation
    RotateX Attitude, -Earth.AngleU                         'Earth axial tilt
    H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): AngleU = X1 / H1: AngleV = Y1 / H1: AngleW = Z1 / H1
    If G1 <> 0 Then SpeedX = SpeedX + G1 * AngleU: _
        SpeedY = SpeedY + G1 * AngleV: SpeedZ = SpeedZ + G1 * AngleW 'Initial vertical speed
    AngleU = Attitude.ZX: AngleV = Attitude.ZY: AngleW = Attitude.ZZ 'Get Z vector from attitude rotation matrix
    AngleU2 = AngleU: AngleV2 = AngleV: AngleW2 = AngleW
    'Init vars
    AccelX = 0: AccelY = 0: AccelZ = 0: Accel = 0           'Acceleration
    InertialAccel = 0                                       'Acceleration without gravity
    InertialAccelX = 0: InertialAccelY = 0: InertialAccelZ = 0
    TargetAngle = InitialAngle: Angle = InitialAngle        'Attitude control thrust and auxiliary variables
    ErrorP = 0: Erase ErrorX, ErrorY, ErrorZ, ErrorU, ErrorV, ErrorW
    ControlThrustU = 0: ControlAccU = 0: ControlLastU = 0
    ControlThrustV = 0: ControlAccV = 0: ControlLastV = 0
    ControlThrustW = 0: ControlAccW = 0: ControlLastW = 0
    IsTarget = ToNone: IsControl = 2                        'Target object and control flag
    AirAngle = 0: AirAngleV = 0: Drag = 0: Lift = 0         'Drag and lift
    Earth.Altitude = Alt: Earth.LastAltitude = H1           'Altitude auxiliary variables
    
'Earth-Moon barycenter correction
    G1 = -Moon.Mass / Earth.Mass: H1 = G1 + 1
    'Position
    Earth.X = 0: Earth.Y = 0: Earth.Z = 0                   'Earth-Moon barycenter position
    X2 = Earth.X: Y2 = Earth.Y: Z2 = Earth.Z
    Earth.X = Moon.X * G1 + X2: Earth.Y = Moon.Y * G1 + Y2: Earth.Z = Moon.Z * G1 + Z2
    Moon.X = Moon.X * H1 + X2: Moon.Y = Moon.Y * H1 + Y2: Moon.Z = Moon.Z * H1 + Z2
    X1 = X1 + Earth.X: Y1 = Y1 + Earth.Y: Z1 = Z1 + Earth.Z
    'Speed
    If GravityMode < 3 Then _
        Earth.MainSpeedX = 0: Earth.MainSpeedY = 0: Earth.MainSpeedZ = 0 'Stop Earth if no Sun gravity is calculated
    X2 = Earth.MainSpeedX: Y2 = Earth.MainSpeedY: Z2 = Earth.MainSpeedZ
    Earth.MainSpeedX = Moon.MainSpeedX * G1 + X2: Earth.MainSpeedY = Moon.MainSpeedY * G1 + Y2: Earth.MainSpeedZ = Moon.MainSpeedZ * G1 + Z2
    Moon.MainSpeedX = Moon.MainSpeedX * H1 + X2: Moon.MainSpeedY = Moon.MainSpeedY * H1 + Y2: Moon.MainSpeedZ = Moon.MainSpeedZ * H1 + Z2
    SpeedX = SpeedX + Earth.MainSpeedX: SpeedY = SpeedY + Earth.MainSpeedY: SpeedZ = SpeedZ + Earth.MainSpeedZ

'All planets position relative to Ship
    PlanetMove Sun, X1, Y1, Z1
    PlanetMove Earth, X1, Y1, Z1
    PlanetMove Moon, X1, Y1, Z1
    PlanetMove Mercury, X1, Y1, Z1
    PlanetMove Venus, X1, Y1, Z1
    PlanetMove Mars, X1, Y1, Z1
    PlanetMove Jupiter, X1, Y1, Z1
    PlanetMove Saturn, X1, Y1, Z1
    PlanetMove Uranus, X1, Y1, Z1
    PlanetMove Neptune, X1, Y1, Z1
    PlanetMove Pluto, X1, Y1, Z1
    AddEmptyStage True 'Initialize empty stages

'Target
    Target = ToEarth: Sector = ToEarth: Zone = Atmosphere
    LastTarget = Target: LastSector = Sector: LastZone = Zone

    End With
    
'Log
    If InitLog Then
        If ExtendedLog Then
            StrAdd _
                "Preset" & vbTab & vbTab & Setup.Preset & vbCrLf & _
                "Time step" & vbTab & vbTab & Num(dT, "0") & "s" & IfText(IsAdaptive, " (adaptive)") & vbCrLf & _
                "Date" & vbTab & vbTab & J2ksDateTime(Time) & vbCrLf & _
                "Coordinates" & vbTab & ToLat(Lat, FQuad, Degrees) & "  " & ToLon(Lon, FQuad, Degrees) & "  " & ToAlt(Alt, FMini) & vbCrLf & _
                "Heading - Speed" & vbTab & ToAngle(Heading, "0.0000º") & "  " & ToSpeed(InitialSpeed, FShort) & vbCrLf & _
                "Curve" & vbTab & vbTab & ToAngle(A90 - InitialAngle) & ", " & ToAngle(A90 - StartAngle) & " to " & ToAngle(A90 - EndAngle) & vbCrLf & _
                "Altitude" & vbTab & vbTab & ToAlt(StartAlt, FShort) & " to " & ToAlt(EndAlt, FShort) & vbCrLf & _
                "Brake" & vbTab & vbTab & IIf(Brake > 0, ToAlt(Brake, FShort), "Auto") & vbCrLf & _
                "Park Earth - Moon" & vbTab & IIf(EarthPark > 0, ToDhEx(EarthPark), "  -  ") & "  " & IIf(MoonPark > 0, ToDhEx(MoonPark), "  -  ") & vbCrLf & _
                "Limit Thrust - Lift" & vbTab & IIf(ThrustLimit > 0, ToForceG(ThrustLimit, , FOne), "  -  ") & "  " & Switch(LiftLimit > 0, ToForceG(LiftLimit, , FOne), LiftLimit < 0, ToAngle(-LiftLimit), True, "  -  ") & vbCrLf & _
                "Gravity model" & vbTab & SplitEx(GravityMode, "None|Earth|Earth & Moon|Earth, Moon & Sun|All planets", , "None") & vbCrLf & _
                "Terrain model" & vbTab & SplitEx(TerrainMode, "Spherical|Ellipsoidal|Matrix", , "Spherical") & vbCrLf & _
                "Atmosphere model" & vbTab & SplitEx(AtmMode, "None|Low quality|Medium quality|High quality", , "None") & vbCrLf & _
                vbCrLf
            StrAdd _
                "  Spacecraft:" & vbCrLf & _
                "" & vbCrLf & _
                "Mass - Fuel - Cost" & vbTab & ToMass(Mass, FShort) & "  " & ToMass(TotalFuelMass, FShort) & "  " & Replace(Format(Cost, "$#,0"), ".", ",") & vbCrLf & _
                "Dimensions" & vbTab & ToSize(Length) & " x " & ToSize(Diameter) & "  " & ToArea(Area) & vbCrLf & _
                "Drag - Lift - Ratio" & vbTab & NumX(DragCf, "0.000  ") & NumX(LiftToDrag, "0.0:1  ") & NumX(LengthToDiameter, "0.0:1") & vbCrLf & _
                vbCrLf
            B = 1: For C = StageP To IfInt(Stage(1).FullMass > Stage(0).FullMass, 1, 2) Step -1
                With Stage(C)
                If .BurnRate > 0 Then A1 = .FuelMass / .BurnRate _
                Else If .ThrustN > 0 Then A1 = .ThrustCurve(.ThrustN - 1).T _
                Else A1 = 0
                G1 = (.ThrustV - .ThrustS) * A1
                StrAdd _
                    IIf(C > 1, "  Stage " & B, "  Hover") & ":" & vbCrLf & _
                    vbCrLf & _
                    "Mass - Fuel - Cost" & vbTab & ToMass(.FullMass - Stage(C - 1).FullMass, FShort) & "  " & ToMass(.FuelMass, FShort) & "  " & Replace(Format(.Cost, "$#,0"), ".", ",") & vbCrLf & _
                    "Dimensions" & vbTab & ToSize(.Length - Stage(C - 1).Length) & " x " & ToSize(.Diameter) & "  " & ToArea(.Area) & vbCrLf & _
                    "Thrust SL - Vac" & vbTab & ToForce(.ThrustV - .ThrustS, FShort) & "  " & ToForce(.ThrustV, FShort) & vbCrLf & _
                    "Isp SL - Vac" & vbTab & NumX(.IspS, FOne) & "s  " & NumX(.IspV, FOne) & "s" & vbCrLf & _
                    "Burn - Dry mass" & vbTab & NumX(A1, "0.0s") & "  " & ToMass(.DryMass - Stage(C - 1).FullMass, FShort) & "  " & NumX((.DryMass - Stage(C - 1).FullMass) / (.FullMass - Stage(C - 1).FullMass), "0.0%") & vbCrLf & _
                    "Total impulse" & vbTab & ToForce(G1, FShort) & "·s" & ToClass(G1, , "  Class ") & vbCrLf & _
                    "Emissivity - Temp" & vbTab & NumX(.Emissivity, FMini) & "  " & ToTemp(.Tmin, FOne) & "  " & ToTemp(.Tmax, FOne) & vbCrLf & _
                    "Control - PID" & vbTab & Switch(.Control > 0, ToForce(.Control, FShort), .Control < 0, NumX(-.Control, "0.0%"), True, "Unlimited") & "  " & NumX(.ControlP, FOne) & "  " & NumX(1 / .ControlI, FOne) & "  " & NumX(.ControlD, FOne) & vbCrLf & _
                    vbCrLf
                End With
                B = B + 1: Next
            With Stage(0)
            StrAdd _
                "  Payload:" & vbCrLf & _
                vbCrLf & _
                "Mass" & vbTab & vbTab & ToMass(Stage(0).FullMass, FShort) & vbCrLf & _
                "Dimensions" & vbTab & ToSize(.Length) & " x " & ToSize(.Diameter) & "  " & ToArea(.Area) & vbCrLf & _
                "Emissivity - Temp" & vbTab & NumX(.Emissivity, FMini) & "  " & ToTemp(.Tmin, FOne) & "  " & ToTemp(.Tmax, FOne) & vbCrLf & _
                "Control - PID" & vbTab & Switch(.Control > 0, ToForce(.Control, FShort), .Control < 0, ToControl(-.Control, FOne), True, "Unlimited") & "  " & NumX(.ControlP, FOne) & "  " & NumX(1 / .ControlI, FOne) & "  " & NumX(.ControlD, FOne) & vbCrLf & _
                vbCrLf
            End With
        Else
            StrAdd _
                "Time step" & vbTab & vbTab & Num(dT, "0") & "s" & IfText(IsAdaptive, " (adaptive)") & vbCrLf & _
                "Date" & vbTab & vbTab & J2ksDateTime(Time) & vbCrLf & _
                "Coordinates" & vbTab & ToLat(Lat, FQuad, Degrees) & "  " & ToLon(Lon, FQuad, Degrees) & "  " & ToAlt(Alt, FMini) & vbCrLf & _
                "Heading - Speed" & vbTab & ToAngle(Heading, "0.0000º") & "  " & ToSpeed(InitialSpeed, FShort) & vbCrLf & _
                "Curve" & vbTab & vbTab & ToAngle(A90 - InitialAngle) & ", " & ToAngle(A90 - StartAngle) & " to " & ToAngle(A90 - EndAngle) & vbCrLf & _
                "Altitude" & vbTab & vbTab & ToAlt(StartAlt, FShort) & " to " & ToAlt(EndAlt, FShort) & vbCrLf & _
                "Drag - Lift - Ratio" & vbTab & NumX(DragCf, "0.000  ") & NumX(LiftToDrag, "0.0:1  ") & NumX(LengthToDiameter, "0.0:1") & vbCrLf & _
                "Dimensions" & vbTab & ToSize(Length) & " x " & ToSize(Diameter) & "  " & ToArea(Area) & vbCrLf & _
                "Mass - Fuel - Cost" & vbTab & ToMass(Mass, FShort) & "  " & ToMass(TotalFuelMass, FShort) & "  " & Replace(Format(Cost, "$#,0"), ".", ",") & vbCrLf & _
                vbCrLf & _
                vbTab & "Mass" & vbTab & " T-SL" & vbTab & "T-Vac" & vbTab & "Burn   Cost" & vbCrLf
            B = 1: For C = StageP To IfInt(Stage(1).FullMass > Stage(0).FullMass, 1, 2) Step -1
                With Stage(C)
                If .BurnRate > 0 Then A1 = .FuelMass / .BurnRate _
                Else If .ThrustN > 0 Then A1 = .ThrustCurve(.ThrustN - 1).T _
                Else A1 = 0
                StrAdd _
                    IIf(C > 1, "Stage " & B, "Hover") & vbTab & ToMass(.FullMass - Stage(C - 1).FullMass, "000.0") & vbTab & _
                    ToForce(.ThrustV - .ThrustS, " 000.0") & vbTab & ToForce(.ThrustV, "000.0") & vbTab & _
                    Format(A1, "000s  ") & NumM(.Cost, "$0.0") & vbCrLf
                End With
                B = B + 1: Next
            StrAdd _
                "Payload" & vbTab & ToMass(Stage(0).FullMass, "000.0") & vbCrLf & vbCrLf
            End If
        Log.Txt = StrAdd: Log.Txt.SelStart = 0
        
        End If
    
'Update Render and Data windows
    If ExecStats Then Stats
    If ExecRender Then Render
    If ExecInfo Then Info
    If ExecPlot Then Plot
    If RefreshData Then Data.Refresh
    If RefreshGraph Then Graph.Refresh
    If ShowErrors Then If InitTxt <> "" Then MessageNote InitTxt, 2000, , True, True
    
    'If FreeFile = 1 Then Open "E:\Temp\Log.txt" For Output As 1 'LogData
    
    End Sub

Sub Nav(Optional ByVal Code As PlayMode = Paused)
    Dim B As Long, C As Long, D As Long, E As Long, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X3 As Double, Y3 As Double, Z3 As Double, _
        H1 As Double, G1 As Double, A1 As Double, _
        K1 As Double, S1 As Double
    
'Loop control
    If VideoMode = 3 Then
        If Cam(0).hRC = 0 Then ActivateView Cam(0), , True
        If View.Visible Then View.ZOrder
        End If
    If SyncMode Then SyncExec Reset
    If Code = Pause Then If Status = Landed Or Status = Crashed Then Exit Sub
    If Code = Paused Then GoTo DoNav
    Select Case Code
        Case Pause: If Play = Active Then Play = Paused _
            Else Play = Active: If KMLPathPos > 0 Then _
                    ToolBar.SetIcon ToolBar.Play, 2: GoTo DoLaunchLoop _
                Else If T = 0 Then GoTo DoLaunch _
                Else Stats: GoTo DoNav
        Case Restart: If Play = Active Then Play = Restart _
            Else Play = Active: GoTo DoLaunch
        Case Reset: If Play = Active Then Play = Reset _
            Else GoTo DoReset
        Case Settings: IsRestart = True: Unload Main: Load Main: IsRestart = False
        Case NextStep: If KMLPathPos > 0 Then _
                If Play = Active Then KMLPathPos = 0 Else GoTo DoLaunchEnd _
            Else If Play = Active Then Play = Paused _
            Else Play = NextStep: If T = 0 Then GoTo DoInit _
                Else Stats: GoTo DoNav
        End Select
    Exit Sub

DoReset:
    If KMLPathN >= 2 Then
        KMLPathP = IfInt(IsBalloonTrajectory, 0, KMLPathTop)
        With KMLPath(KMLPathP)
        If IsBalloonTrajectory Then KMLPathPos = 1: KMLPathAlt = .Alt: _
            ReDim IsStation(UBound(Station)): ReDim IsLocation(UBound(Location)): _
            ReDim IsAirspace(AirspaceN): IsBoundary = 0: IsPopulated = 0: _
            ReDim Snap(100 - 1): SnapN = 0: SnapP = 0: LastSnap = -1 _
        Else KMLPathPos = 0
        SetLaunch .Lat, .Lon, .Alt, , False: If .Time <> 0 Then SetTime .Time, , False
        End With
        End If
    Init

    Exit Sub

'Balloon trajectory simulation
DoLaunch:
    If IsBalloonTrajectory = 0 Or KMLPathN < 2 Or KMLPathPos > KMLPathTop Then GoTo DoInit
    ToolBar.SetIcon ToolBar.Play, 2
DoLaunchInit:
    ReDim IsStation(UBound(Station)): ReDim IsLocation(UBound(Location))
    ReDim IsAirspace(AirspaceN): IsBoundary = 0: IsPopulated = 0
    ReDim Snap(100 - 1): SnapN = 0: SnapP = 0: LastSnap = -1
    KMLPathPos = 1: KMLPathAlt = KMLPath(0).Alt: KMLPathP = 0: Init
DoLaunchLoop:
    E = 0: A1 = TimerEx: S1 = KMLPath(KMLPathPos).Time
    If S1 = 0 Then S1 = (KMLPath(KMLPathPos).Alt - KMLPath(0).Alt) / 5
    G1 = S1
    
    Do: DoEvents: If IsExit Then GoTo DoExit
        
        If KMLPathPos = 0 Then Exit Do
        If KMLPathN < 2 Then GoTo DoInit
        If Play <> Active Then
            If Play = Reset Or Play = Restart Then
                Play = Active: GoTo DoLaunchInit
            Else
                Render
                Play = Paused: GoTo DoExit
                End If
            End If
        If Record = Active Then RecordVideo
        KMLPathAlt = KMLPathAlt + TimeSpeed * 10: D = Int(TimeSpeed / 30)
        C = 0: H1 = 0: Z1 = 0
        Do
            If KMLPathPos > KMLPathTop Then Exit Do
            Z1 = KMLPath(KMLPathPos).Alt: If Z1 >= KMLPathAlt Then Exit Do
            If H1 <> Z1 Then H1 = Z1 Else C = C + 1: If C > D Then Exit Do
            KMLPathPos = KMLPathPos + 1
            Loop
        If KMLPathPos > KMLPathTop Then Exit Do
        If Record = Active Then Fps = VideoHdr.dwRate
        KMLPathP = KMLPathPos: Init False, False, , , , , , True, True
        
        IsTargetN = 0
        If IsStationVisibility And Earth.Distance > Earth.Radius Then
            TargetItem = -1: TargetClass = TypeNone: B = 0: K1 = Infinite
            If TargetsN > 0 Then
                For D = 1 To TargetsN
                    Select Case Targets(D).Class
                    Case TypeStation
                        C = Targets(D).N
                        If C >= 0 And C < StationN Then
                            If IsStationVisible(Station(C), H1) Then
                                If TargetItem < 0 Then _
                                    TargetItem = C: TargetClass = TypeStation: TargetIndex = D
                                If Not IsStation(C) Then IsStation(C) = True: _
                                    Msg Station(C).Name & " available", ToAlt(PlanetAltitude, FmtLog) _
                                        & " " & ToAlt(H1, FmtLog), StationAvailable, , B
                                IsTargetN = IsTargetN + 1
                            ElseIf IsStation(C) Then
                                IsStation(C) = False
                                Msg Station(C).Name & " unavailable", ToAlt(PlanetAltitude, FmtLog) _
                                    & " " & ToAlt(H1, FmtLog), StationUnAvailable, , B
                                End If
                            End If
                    Case TypeLocation
                        C = Targets(D).N
                        If C >= 0 And C < LocationN Then
                            If IsStationVisible(Location(C), H1, , , , , ToEarth) Then
                                If TargetItem < 0 Then _
                                    TargetItem = C: TargetClass = TypeLocation: TargetIndex = D
                                If Not IsLocation(C) Then IsLocation(C) = True: _
                                    Msg Location(C).Name & " available", ToAlt(PlanetAltitude, FmtLog) _
                                        & " " & ToAlt(H1, FmtLog), StationAvailable, , B
                                IsTargetN = IsTargetN + 1
                            ElseIf IsLocation(C) Then
                                IsLocation(C) = False
                                Msg Location(C).Name & " unavailable", ToAlt(PlanetAltitude, FmtLog) _
                                    & " " & ToAlt(H1, FmtLog), StationUnAvailable, , B
                                End If
                            End If
                        End Select
                    Next
            Else
                For C = StationLast To StationFirst Step -1
                    If IsStationVisible(Station(C), H1) Then
                        If H1 < K1 Then K1 = H1: TargetItem = C: TargetClass = TypeStation
                        If Not IsStation(C) Then IsStation(C) = True: _
                            Msg Station(C).Name & " available", ToAlt(PlanetAltitude, FmtLog) _
                                & " " & ToAlt(H1, FmtLog), StationAvailable, , B
                        IsTargetN = IsTargetN + 1
                    ElseIf IsStation(C) Then
                        IsStation(C) = False
                        Msg Station(C).Name & " unavailable", ToAlt(PlanetAltitude, FmtLog) _
                            & " " & ToAlt(H1, FmtLog), StationUnAvailable, , B
                        End If
                    Next
                End If
            If B > 0 Then SnapP = SnapP + 1
            End If
        
        IsAirspaceN = 0
        If IsAirspaceDetection Or IsAreaDetection Then
            Y1 = Lat: X1 = Lon: Z1 = Alt
            If IsAirspaceDetection Then
                If Z1 <= AirspaceMax And Z1 >= 0 And IsLiftOff = 0 Then
                    For C = 0 To AirspaceN - 1
                        If IsInsideAirspace(Airspace(C), Y1, X1, Z1) Then
                            If Not IsAirspace(C) Then _
                                IsAirspace(C) = True: Msg "Enter " & Airspace(C).Name, , EnterAirspace
                            IsAirspaceN = IsAirspaceN + 1
                        ElseIf IsAirspace(C) Then
                            IsAirspace(C) = False: Msg "Exit " & Airspace(C).Name, , ExitAirspace
                            End If
                        Next
                    End If
                End If
            If IsAreaDetection Then
                If Z1 <= LandspaceMax And Z1 >= 0 And IsLiftOff = 0 Then
                    For C = 0 To LandN - 1
                        If IsInsideLandspace(Land(C), Y1, X1, Z1) Then C = C + 1: GoTo IsBBoundary
                        Next
                    For C = 0 To SeaN - 1
                        If IsInsideLandspace(Sea(C), Y1, X1, Z1) Then C = -(C + 1): GoTo IsBBoundary
                        Next
                    C = 0
IsBBoundary:
                    For D = 0 To AreasN - 1
                        If IsInsideLandspace(Areas(D), Y1, X1, Z1) Then D = D + 1: GoTo IsBPopulated
                        Next
                    D = 0
IsBPopulated:
                    If IsBoundary <> C Then
                        If IsBoundary Then _
                            If IsBoundary > 0 Then Msg "Exit " & Land(IsBoundary - 1).Name & " territory", , ExitAirspace _
                            Else Msg "Exit " & Sea(-IsBoundary - 1).Name & " waters", , ExitAirspace
                        If C <> 0 Then _
                            If C > 0 Then Msg "Enter " & Land(C - 1).Name & " territory", , EnterAirspace _
                            Else Msg "Enter " & Sea(-C - 1).Name & " waters", , EnterAirspace
                        End If
                    If IsPopulated = 0 Xor D = 0 Then _
                        If IsPopulated Then Msg "Exit populated area", , ExitAirspace _
                        Else Msg "Enter populated area", , EnterAirspace
                Else
                    C = 0: D = 0
                    If IsBoundary <> 0 Or IsPopulated <> 0 Then GoTo IsBPopulated
                    End If
                IsBoundary = C: IsPopulated = D
                End If
            End If
        
        K1 = TimerEx: E = E + 1
        If Int(K1) > Int(A1) Then
            If Record = Active Then Fps = VideoHdr.dwRate Else Fps = CDbl(E) / (K1 - A1)
            If KMLPathPos > 0 And KMLPathPos < KMLPathTop Then
                G1 = S1: Interpolate _
                    KMLPathAlt, KMLPath(KMLPathPos - 1).Alt, KMLPath(KMLPathPos).Alt, S1, _
                    IfDbl(KMLPath(KMLPathPos - 1).Time, KMLPath(KMLPathPos - 1).Time, (KMLPath(KMLPathPos - 1).Alt - KMLPath(0).Alt) / 5), _
                    IfDbl(KMLPath(KMLPathPos).Time, KMLPath(KMLPathPos).Time, (KMLPath(KMLPathPos).Alt - KMLPath(0).Alt) / 5)
                If K1 > A1 And S1 > G1 Then _
                    RealSpeed = (S1 - G1) / (K1 - A1): If IsRealTime And RealSpeed > 0 Then _
                        TimeSpeed = TimeSpeed * 0.7 + TimeSpeed / RealSpeed * 0.3
                End If
            A1 = K1: E = 0
            End If
        
        Loop
    
DoLaunchEnd:
    KMLPathPos = 0: KMLPathAlt = 0: KMLPathP = KMLPathTop
    With KMLPath(KMLPathTop)
    SetLaunch .Lat, .Lon, .Alt, , False: If .Time <> 0 Then SetTime .Time, , False
    End With
    If Play = Paused Then Init: KMLPathPos = KMLPathTop + 1: Exit Sub

DoInit:
    Init
    
'Simulation main loop
DoNav:
    ToolBar.SetIcon ToolBar.Play, 2
    If VideoSync Then If Not Video.WMP Is Nothing Then Video.WMP.object.Play
    If LogOption And LogToFile Then LogStart
    IsInertial = (LogHandle <> 0 And (LogOption And (LogInertial Or LogInertial3D)) <> 0) Or _
                 (DataTool And DataInertial) <> 0
    
    StartTime = TimerEx: StartT = T: StepTime = StartTime: StepT = T
    FrameCount = 0: FrameTime = StartTime
    
    Do
StartLoop:
    If Play = NextStep Then
        Count = 1: Play = Paused
    ElseIf TimeSpeed < 1 Then
        If TimeSpeed <= 0.01 Then TimeSpeed = 0.01
        Count = Count - 1: If Count <= -Int(1 / TimeSpeed + 0.5) Then Count = 1
    Else
        If TimeSpeed > MaxTimeSpeed Then TimeSpeed = MaxTimeSpeed
        Count = TimeSpeed
        End If
    LastTime = TimerEx
    Do While Count > 0
        
    If T = Int(T) Or IsHiDefDetection Then
        
'Test stations or targets visibility
        IsTargetN = 0
        If IsStationVisibility And Earth.Distance > Earth.Radius Then
            TargetItem = -1: TargetClass = TypeNone: B = 0: K1 = Infinite
            If TargetsN > 0 Then
                For D = 1 To TargetsN
                    Select Case Targets(D).Class
                    Case TypeStation
                        C = Targets(D).N
                        If C >= 0 And C < StationN Then
                            If IsStationVisible(Station(C), H1) Then
                                If TargetItem < 0 Then _
                                    TargetItem = C: TargetClass = TypeStation: TargetIndex = D
                                If Not IsStation(C) Then IsStation(C) = True: _
                                    Msg Station(C).Name & " available", ToAlt(PlanetAltitude, FmtLog) _
                                        & " " & ToAlt(H1, FmtLog), StationAvailable, , B
                                IsTargetN = IsTargetN + 1
                            ElseIf IsStation(C) Then
                                IsStation(C) = False
                                Msg Station(C).Name & " unavailable", ToAlt(PlanetAltitude, FmtLog) _
                                    & " " & ToAlt(H1, FmtLog), StationUnAvailable, , B
                                End If
                            End If
                    Case TypeLocation
                        C = Targets(D).N
                        If C >= 0 And C < LocationN Then
                            If IsStationVisible(Location(C), H1, , , , , ToEarth) Then
                                If TargetItem < 0 Then _
                                    TargetItem = C: TargetClass = TypeLocation: TargetIndex = D
                                If Not IsLocation(C) Then IsLocation(C) = True: _
                                    Msg Location(C).Name & " available", ToAlt(PlanetAltitude, FmtLog) _
                                        & " " & ToAlt(H1, FmtLog), StationAvailable, , B
                                IsTargetN = IsTargetN + 1
                            ElseIf IsLocation(C) Then
                                IsLocation(C) = False
                                Msg Location(C).Name & " unavailable", ToAlt(PlanetAltitude, FmtLog) _
                                    & " " & ToAlt(H1, FmtLog), StationUnAvailable, , B
                                End If
                            End If
                        End Select
                    Next
            Else
                For C = StationLast To StationFirst Step -1
                    If IsStationVisible(Station(C), H1) Then
                        If H1 < K1 Then K1 = H1: TargetItem = C: TargetClass = TypeStation
                        If Not IsStation(C) Then IsStation(C) = True: _
                            Msg Station(C).Name & " available", ToAlt(PlanetAltitude, FmtLog) _
                                & " " & ToAlt(H1, FmtLog), StationAvailable, , B
                        IsTargetN = IsTargetN + 1
                    ElseIf IsStation(C) Then
                        IsStation(C) = False
                        Msg Station(C).Name & " unavailable", ToAlt(PlanetAltitude, FmtLog) _
                            & " " & ToAlt(H1, FmtLog), StationUnAvailable, , B
                        End If
                    Next
                End If
            If B > 0 Then SnapP = SnapP + 1
            End If
        
'Check airspaces, land and water boundaries and populated areas
        IsAirspaceN = 0
        If IsAirspaceDetection Or IsAreaDetection Then
            XYZ2LLA Earth, Y1, X1, Z1
            If IsAirspaceDetection Then
                If Z1 <= AirspaceMax And Z1 >= 0 And IsLiftOff = 0 Then
                    For C = 0 To AirspaceN - 1
                        If IsInsideAirspace(Airspace(C), Y1, X1, Z1) Then
                            If Not IsAirspace(C) Then _
                                IsAirspace(C) = True: Msg "Enter " & Airspace(C).Name, , EnterAirspace
                            IsAirspaceN = IsAirspaceN + 1
                        ElseIf IsAirspace(C) Then
                            IsAirspace(C) = False: Msg "Exit " & Airspace(C).Name, , ExitAirspace
                            End If
                        Next
                    End If
                End If
            If IsAreaDetection Then
                If Z1 <= LandspaceMax And Z1 >= 0 And IsLiftOff = 0 Then
                    For C = 0 To LandN - 1
                        If IsInsideLandspace(Land(C), Y1, X1, Z1) Then C = C + 1: GoTo IsBoundary
                        Next
                    For C = 0 To SeaN - 1
                        If IsInsideLandspace(Sea(C), Y1, X1, Z1) Then C = -(C + 1): GoTo IsBoundary
                        Next
                    C = 0
IsBoundary:
                    For D = 0 To AreasN - 1
                        If IsInsideLandspace(Areas(D), Y1, X1, Z1) Then D = D + 1: GoTo IsPopulated
                        Next
                    D = 0
IsPopulated:
                    If IsBoundary <> C Then
                        If IsBoundary Then _
                            If IsBoundary > 0 Then Msg "Exit " & Land(IsBoundary - 1).Name & " territory", , ExitAirspace _
                            Else Msg "Exit " & Sea(-IsBoundary - 1).Name & " waters", , ExitAirspace
                        If C <> 0 Then _
                            If C > 0 Then Msg "Enter " & Land(C - 1).Name & " territory", , EnterAirspace _
                            Else Msg "Enter " & Sea(-C - 1).Name & " waters", , EnterAirspace
                        End If
                    If IsPopulated = 0 Xor D = 0 Then _
                        If IsPopulated Then Msg "Exit populated area", , ExitAirspace _
                        Else Msg "Enter populated area", , EnterAirspace
                Else
                    C = 0: D = 0
                    If IsBoundary <> 0 Or IsPopulated <> 0 Then GoTo IsPopulated
                    End If
                IsBoundary = C: IsPopulated = D
                End If
            End If
        
'Check satellite proximity
        IsSatelliteN = 0
        If IsSatelliteProximity Then
            A1 = Time + T
            For C = 0 To SatelliteN - 1
                TLE2XYZ Earth, Satellite(C), A1, X1, Y1, Z1
                H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
                If H1 <= SatelliteDist Then
                    If Not IsSatellite(C) Then IsSatellite(C) = True: _
                        Msg Satellite(C).Name & " proximity", , SatelliteProximity
                    IsSatelliteN = IsSatelliteN + 1
                Else
                    If IsSatellite(C) Then IsSatellite(C) = False: _
                        Msg Satellite(C).Name & " faraway", , SatelliteFaraway
                    End If
                Next
            If IsSatelliteN Then _
                If IsHiDefDetection Then SatelliteInRange = SatelliteInRange + dT _
                Else SatelliteInRange = SatelliteInRange + 1
            End If
        
'Check asteroid, comet and moon proximity
        IsAsteroidN = 0
        If IsAsteroidProximity Then
            A1 = Time + T
            For C = 0 To AsteroidN - 1
                Asteroid2XYZ Asteroid(C), A1, X1, Y1, Z1
                H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
                If H1 <= AsteroidDist Then
                    If Not IsAsteroid(C) Then IsAsteroid(C) = True: _
                        Msg Asteroid(C).Name & " proximity", , AsteroidProximity
                    IsAsteroidN = IsAsteroidN + 1
                Else
                    If IsAsteroid(C) Then IsAsteroid(C) = False: _
                        Msg Asteroid(C).Name & " faraway", , AsteroidFaraway
                    End If
                Next
            For C = 0 To CometN - 1
                Asteroid2XYZ Comet(C), A1, X1, Y1, Z1
                H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
                If H1 <= AsteroidDist Then
                    If Not IsComet(C) Then IsComet(C) = True: _
                        Msg Comet(C).Name & " proximity", , AsteroidProximity
                    IsAsteroidN = IsAsteroidN + 1
                Else
                    If IsComet(C) Then IsComet(C) = False: _
                        Msg Comet(C).Name & " faraway", , AsteroidFaraway
                    End If
                Next
            For C = MoonN - 1 To 0 Step -1
                Asteroid2XYZ Moons(C), A1, X1, Y1, Z1
                H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
                If H1 <= AsteroidDist Then
                    If Not IsMoon(C) Then IsMoon(C) = True: _
                        Msg Moons(C).Name & " proximity", , AsteroidProximity
                    IsAsteroidN = IsAsteroidN + 1
                Else
                    If IsMoon(C) Then IsMoon(C) = False: _
                        Msg Moons(C).Name & " faraway", , AsteroidFaraway
                    End If
                Next
            If IsAsteroidN > 0 Then _
                If IsHiDefDetection Then AsteroidInRange = AsteroidInRange + dT _
                Else AsteroidInRange = AsteroidInRange + 1
            End If
        
        If IsHiDefDetection Then If T <> Int(T) Then GoTo NoPathGraph
        
'Path points
        If T >= PathNext Then
            If PathP > UBound(Path) Then ReDim Preserve Path(UBound(Path) + 1000)
            With Path(PathP)
            .Sun.X = Sun.X: .Sun.Y = Sun.Y: .Sun.Z = Sun.Z
            .Earth.X = Earth.X: .Earth.Y = Earth.Y: .Earth.Z = Earth.Z: .AngleW = Earth.AngleW
            If Cam1.Target <= ToShip Then C = Target Else C = Cam1.Target
            Select Case C
            Case Is < ToMercury: .P = ToMoon: .Target.X = Moon.X: .Target.Y = Moon.Y: .Target.Z = Moon.Z
            Case ToMercury: .P = ToMercury: .Target.X = Mercury.X: .Target.Y = Mercury.Y: .Target.Z = Mercury.Z
            Case ToVenus:   .P = ToVenus: .Target.X = Venus.X: .Target.Y = Venus.Y: .Target.Z = Venus.Z
            Case ToMars:    .P = ToMars: .Target.X = Mars.X: .Target.Y = Mars.Y: .Target.Z = Mars.Z
            Case ToJupiter: .P = ToJupiter: .Target.X = Jupiter.X: .Target.Y = Jupiter.Y: .Target.Z = Jupiter.Z
            Case ToSaturn:  .P = ToSaturn: .Target.X = Saturn.X: .Target.Y = Saturn.Y: .Target.Z = Saturn.Z
            Case ToUranus:  .P = ToUranus: .Target.X = Uranus.X: .Target.Y = Uranus.Y: .Target.Z = Uranus.Z
            Case ToNeptune: .P = ToNeptune: .Target.X = Neptune.X: .Target.Y = Neptune.Y: .Target.Z = Neptune.Z
            Case ToPluto:   .P = ToPluto: .Target.X = Pluto.X: .Target.Y = Pluto.Y: .Target.Z = Pluto.Z
                End Select
            .T = T: .N = IsTargetN
            End With
            'Auto time point
            If SnapTime >= PathTime Then C = SnapTime \ PathTime Else C = 1
            C = PathP Mod C = 0 And PathP > 0
            PathNext = T + PathTime: PathP = PathP + 1
            If PathP > PathN Then PathN = PathP
            If C Then SaveSnap , False, RefreshPoints
            End If
        
'Graph points
        If T < GraphicMax Then
            GraphicP = T: If GraphicP >= GraphicN Then GraphicN = GraphicP + 1
            With Graphic(GraphicP)
            '.T = T
            .Mass = Mass: .Thrust = Thrust * Throttle: .Drag = Drag: .Accel = Accel
            .Temperature = Temperature: .Links = IsTargetN: .Airspaces = IsAirspaceN
            If Target = ToMoon Then .Speed = Moon.Speed: .AirSpeed = Moon.AirSpeed: _
                .Altitude = Moon.Altitude: .Gravity = Moon.Gravity - Moon.CentripetalAccel _
            Else .Speed = Earth.Speed: .AirSpeed = Earth.AirSpeed: _
                .Altitude = Earth.Altitude: .Gravity = Earth.Gravity - Earth.CentripetalAccel
            End With
            End If
    
        End If
    
NoPathGraph:
    
'External link control
    If LinkMode = ControlLink And Play = Active Then
        If LinkPort <> -1 Then
            If T = 0 Then
COMLinkInit:
                LinkInit
                If COMInit Then MsgLink "COM write error": CloseCOM LinkWait: GoTo ErrorLink
                End If
            If Target = ToMoon Then LinkData Moon Else LinkData Earth
            If COMData Then MsgLink "COM write error": CloseCOM LinkWait: GoTo ErrorLink
            C = COMCtrl: If C = -1 Then MsgLink "COM read error": GoTo ErrorLink
            If C = 0 Then MsgLink "COM connection timeout": GoTo ErrorLink
        ElseIf LinkSocketIn <> -1 And LinkSocketOut <> -1 Then
            If T = 0 Then
UDPLinkInit:
                LinkInit
                If UDPInit Then MsgLink "UDP write error": GoTo ErrorLink
                End If
            If Target = ToMoon Then LinkData Moon Else LinkData Earth
            If UDPData Then MsgLink "UDP write error": GoTo ErrorLink
            If UDPCtrl Then MsgLink "UDP read error": GoTo ErrorLink
        ElseIf StartControl Then
            If LinkPort <> -1 Then GoTo COMLinkInit _
            Else If LinkSocketIn <> -1 And LinkSocketOut <> -1 Then GoTo UDPLinkInit _
            Else GoTo ExitLink
        Else
            GoTo ErrorLink
            End If
        On Error Resume Next
        If LinkText Then
            LinkPos = 1: Hdr = GetText
            Tt = Int(GetData / dT + 0.5) * dT: Stx = GetData: Nt = GetData
            Rt = GetData: X1 = GetData: Y1 = GetData: Z1 = GetData
        ElseIf LinkDouble Then
            With CtrlD: Hdr = GetArray(.Head): Tt = Int(.Tt / dT + 0.5) * dT: Stx = .St: Nt = .Nt
            Rt = .Rt: X1 = .Ut: Y1 = .Vt: Z1 = .Wt: End With
        Else
            With CtrlS: Hdr = GetArray(.Head): Tt = Int(.Tt / dT + 0.5) * dT: Stx = .St: Nt = .Nt
            Rt = .Rt: X1 = .Ut: Y1 = .Vt: Z1 = .Wt: End With
            End If
        Select Case LinkAxisX 'Map X axis
            Case -3: Ut = -Z1: Case -2: Ut = -Y1: Case -1: Ut = -X1
            Case 2: Ut = Y1: Case 3: Ut = Z1: Case Else: Ut = X1
            End Select
        Select Case LinkAxisY 'Map Y axis
            Case -3: Vt = -Z1: Case -2: Vt = -Y1: Case -1: Vt = -X1
            Case 1: Vt = X1: Case 3: Vt = Z1: Case Else: Vt = Y1
            End Select
        Select Case LinkAxisZ 'Map Z axis
            Case -3: Wt = -Z1: Case -2: Wt = -Y1: Case -1: Wt = -X1
            Case 1: Wt = X1: Case 2: Wt = Y1: Case Else: Wt = Z1
            End Select
        If LinkCtlNormalize Then
            H1 = Sqr(Ut * Ut + Vt * Vt + Wt * Wt) 'Normalize attitude vector
            If H1 > 0 Then Ut = Ut / H1: Vt = Vt / H1: Wt = Wt / H1 Else Ut = 0: Vt = 0: Wt = 1
            If Rt < 0 Then Rt = 0 Else If Rt > 1 Then Rt = 1 'Limit thrust
            End If
        LinkCtrl
        On Error GoTo 0
        If Hdr <> "CTRL" Then If Not LinkTest Then MsgLink "Unknown command": GoTo ErrorLink
        If Tt <> T Then If Not LinkTest Then MsgLink "Incorrect time": GoTo ErrorLink
        GoTo ExitLink
ErrorLink:
        If T > 0 Then If IsLinkError Then IsLinkError = False: Msg "Link error", , LinkError
        If LinkWait Then Play = Paused: GoTo ExitLoop
ExitLink:
        End If
    
'Phase selection
    IsControl = 0 'Controlled attitude flag
    Select Case Status
    
'Start launch
    Case Launch
Launch:
        MaxHe = Earth.Altitude: MinHe = Earth.Altitude
        MaxHm = Moon.Altitude: MinHm = Moon.Altitude
        If StageN > Abort Then GoTo Abort _
        Else Status = TakeOff: If StageP <= 1 Or Stage(StageP).Delay <= 0 Then GoTo StartEngine _
            Else Thrust = 0: DelayTime = T + Stage(StageP).Delay: GoTo TakeOff
    
'Take off
    Case TakeOff
TakeOff:
        If Thrust = 0 Then
            If T >= DelayTime Then 'If delay time is over
                If Mass > DryMass Then
                    'Restart engines after parking delay
                    IsEnginesEvent = 1: Thrust = ThrustV
                ElseIf StageN >= Abort Then
Abort:
                    'Abort sequence
                    IsDeployed = True: AccelTime = T
                    If Jettison And StageP > 0 Then _
                        Mass = Mass - Stage(StageP - 1).FullMass: DryMass = Mass
                    Status = Aborted: GoTo Aborted
                Else
                    If IsEmptyStages And StageP > 2 Then AddEmptyStage 'Simulate empty stage
                    StageUp
                    'Start next stage engines if any
StartEngine:
                    If StageP > 1 Then
                        IsEnginesEvent = 1: Thrust = ThrustV
                    Else
                        IsDeployed = True: AccelTime = T
                        'If Hover exists go to Planet Transfer
                        If Stage(1).FullMass > Stage(0).FullMass Then _
                            Status = Transfer: GoTo Transfer
                        'Else go to Deploy Satellite
                        StageUp
                        Status = Deployed: GoTo Satellite
                        End If
                    End If
                End If
            End If
        
        If LaunchTrajectory Then
            Throttle = Rt
            'If IsLaunchTrajectory Then
            '    G1 = Diameter / 2: H1 = G1 * Sqr(1 - (Mass - DryMass) / (FullMass - DryMass))
            '    G1 = Mass * ((G1 * G1 + H1 * H1) * 3 + Length * Length) / 12
            '    ControlThrustU = Ut: ControlThrustV = Vt: ControlThrustW = Wt
            '    X2 = 0
            '    Y2 = ControlThrustV * Length / G1 / 2
            '    Z2 = 0
            '    GoTo Angular
            '    End If
            'If LinkCtlIn Then
            '    AngularSpeed = LaunchMatrix 'Initialize rotation matrix
            '    If Ut <> 0 Then RotateX AngularSpeed, -Ut 'Apply yaw rotation
            '    If Vt <> 0 Then RotateX AngularSpeed, -Vt 'Apply pitch rotation
            '    If Wt <> 0 Then RotateX AngularSpeed, -Wt 'Apply roll rotation
            '    AngleU = Attitude.ZX: AngleV = Attitude.ZY: AngleW = Attitude.ZZ 'Get Z vector from attitude rotation matrix
            '    IsControl = 2
            'Else
            If LinkCtlAxes Then 'Axes aligned with launch site: Z+=Vertical Y+=North X+=East
                AngleU = -Ut: AngleV = -Vt: AngleW = Wt
                Rotate -A90 + Lat, AngleV, AngleW       'Latitude
                Rotate Lon + EarthAngle, AngleU, AngleV 'Longitude and Earth orientation
                Rotate Earth.AngleU, AngleV, AngleW     'Earth axial tilt
            Else 'Axes aligned with ecliptic plane
                AngleU = Ut: AngleV = Vt: AngleW = Wt
                End If
            IsControl = 1
            '    End If
        Else
            'Launch trajectory angle calculation
            If Earth.Altitude >= EndAlt Then TargetAngle = EndAngle _
            Else If Earth.Altitude >= StartAlt Then TargetAngle = StartAngle + (EndAngle - StartAngle) * (Earth.Altitude - StartAlt) / (EndAlt - StartAlt) _
            Else If Earth.Altitude >= Alt Then TargetAngle = InitialAngle + (StartAngle - InitialAngle) * (Earth.Altitude - Alt) / (StartAlt - Alt) _
            Else TargetAngle = InitialAngle
            'TargetAngle = TargetAngle + 15 * Rad * Sin(T * A360)
            'Current pitch angle calculation
            X1 = Earth.X: Y1 = Earth.Y: Z1 = Earth.Z
            RotatePoint Attitude, X1, Y1, Z1: Angle = ATan(Y1, Z1)
            If IsLaunchTrajectory Then
        
                'Attitude control .. PID=Proportional Integral Derivative
                H1 = TargetAngle - Angle            'Proportional
                ControlAccU = ControlAccU + H1 * dT 'Integral
                G1 = (H1 - ControlLastU) / dT       'Derivative
                Y2 = ControlP * (H1 + ControlAccU * ControlI + G1 * ControlD)
                X2 = 0: Z2 = 0: ControlLastU = H1
                ControlAccV = 0: ControlLastV = 0
                ControlAccW = 0: ControlLastW = 0
                'Angular acceleration
                'Moment of inertia of cylindrical tube .. Iz=1/2*M*(Ro²+Ri²) Ix=Iy=1/12*M*(3*(Ro²+Ri²)+H²)
                'F*D=I*A .. F=Force (N)  D=Distance (m)  I=Angular mass (Kg*m²) A=Angular acceleration (Rad/s²)
                If InertiaX <> 0 Then G1 = InertiaX Else _
                    G1 = Diameter / 2: H1 = G1 * Sqr(1 - (Mass - DryMass) / (FullMass - DryMass))
                    G1 = Mass * ((G1 * G1 + H1 * H1) * 3 + Length * Length) / 12 'Moment of inertia or Angular mass
                'Force needed .. F=A*I/D
                G1 = G1 / Length * 2
                ControlThrustU = Y2 * G1: ControlThrustV = 0: ControlThrustW = 0
                'Thrust max and min limits
                If Control <> 0 Then
                    If Control > 0 Then H1 = Control Else H1 = -ThrustS * Control
                    If Abs(ControlThrustU) > H1 Then ControlThrustU = H1 * Sgn(ControlThrustU): _
                        Y2 = ControlThrustU / G1: ControlAccU = 0 ' _
                    Else If Abs(ControlThrustU) < Y2 Then ControlThrustU = 0: _
                        Y2 = 0: ControlAccU = 0
                    End If
                'Max thrust needed
                If IsStatistics Then _
                    If Abs(ControlThrustU) > ControlThrustMax Then ControlThrustMax = Abs(ControlThrustU)
Angular:
                'Angular kinetics (this is not correct, angular speed is stored as 'Angle/Delta-Time' instead of 'Angle/Time' and this is because rotation matrices in contrast of angles and quaternions, can not be interpolated to apply the time step)
                'AxisZ->Roll: +Left/CCW -Right/CW .. AxisX-Pitch: +Up -Down .. AxisY-Yaw: +Left -Right
                If X2 <> 0 Then RotateX AngularSpeed, -X2 * dT * dT 'Apply yaw angular acceleration
                If Y2 <> 0 Then RotateX AngularSpeed, -Y2 * dT * dT 'Apply pitch angular acceleration
                If Z2 <> 0 Then RotateX AngularSpeed, -Z2 * dT * dT 'Apply roll angular acceleration
                RotateMatrix AngularSpeed, Attitude 'Apply angular speed
            Else
                ControlThrustU = 0: ControlThrustV = 0: ControlThrustW = 0
                Initialize AngularSpeed
                RotateX Attitude, Angle - TargetAngle, True
                End If
            'H1 = Sqr(Attitude.ZX * Attitude.ZX + Attitude.ZY * Attitude.ZY + Attitude.ZZ * Attitude.ZZ) 'Normalize Z vector
            'AngleU = Attitude.ZX / H1: AngleV = Attitude.ZY / H1: AngleW = Attitude.ZZ / H1 'Get Z vector from attitude rotation matrix
            AngleU = Attitude.ZX: AngleV = Attitude.ZY: AngleW = Attitude.ZZ 'Get Z vector from attitude rotation matrix
            IsControl = 2
            End If
        
'Planet transfer
    Case Transfer
Transfer:
        'When braking distance is reached, start Hover engines
        If AirSpeed > 0 And Target <> ToEarth Then
            If Brake <= 0 Then
                Brake = -AirSpeed * AirSpeed / ThrustV * Mass / 2 'Braking distance D=V²/A/2 A=F/M (Propellant mass loss and gravity are not calculated)
                If GroundAltitude <= -Brake Then If IsCollision Then GoTo Brake
            ElseIf GroundAltitude <= Brake Then
Brake:
                Status = Landing: IsDeployed = True
                If IsLandingManeuver Then
                    'If Not IsStandByControl Then
                    Initialize AngularSpeed: Vector2Matrix Attitude, AirSpeedX, AirSpeedY, AirSpeedZ
                    DelayTime = T + Stage(StageP).Delay: GoTo Landing
                    End If
                GoTo StartBrake
                End If
            End If
    
'Planet landing
    Case Landing
Landing:
        'Adjust throttle to land on the planet surface
        If LandingManeuver Then
            Throttle = Rt: AngleU = Ut: AngleV = Vt: AngleW = Wt: IsControl = 1
        ElseIf AirSpeed > 0 Then
            If Thrust = 0 Then
                If T >= DelayTime Then 'If delay time is over
                    If Mass >= FullMass Then
StartBrake:
                        'Start engines after stage delay
                        IsEnginesEvent = 1: BurnOutDV = ThrustDV: BrakeTime = T: Thrust = ThrustV
                    ElseIf Mass > DryMass Then
Restart:
                        'Deploy satellite after parking delay
                        If MoonPark <= -Week Then _
                            IsDeployed = True: StageUp: Status = Deployed: GoTo Satellite
                        'Restart engines after parking delay
                        IsEnginesEvent = 1: Thrust = ThrustV
                        If MoonPark < 0 Then Status = Revert: GoTo Revert
                        End If
                    End If
            ElseIf GroundAltitude < 1 Then
                Throttle = 0 'If altitude is less than 1 meter, stop engines
            Else
                'Adjust throttle respect to the altitude
                Throttle = AirSpeed * AirSpeed / ThrustV * Mass / GroundAltitude 'Braking distance D=V²/A/2 A=F/M
                If Throttle > 1 Then Throttle = 1 'Thrust limited to 100%
                End If
            'Point nozzle's ship relative to the planet ground speed
            If IsLandingManeuver Then X1 = -AirSpeedX: Y1 = -AirSpeedY: Z1 = -AirSpeedZ: GoTo SatLookVector
            AngleU = -AirSpeedX: AngleV = -AirSpeedY: AngleW = -AirSpeedZ: IsControl = 1
            End If
    
'Return to Earth
    Case Revert
Revert:
        'Point nose's ship relative to the planet ground speed
        If Thrust = 0 Then Status = StandBy: GoTo StandBy
        If AirSpeed > 0 Then AngleU = AirSpeedX: AngleV = AirSpeedY: AngleW = AirSpeedZ: IsControl = 1
    
'Satellite deployed
    Case Deployed
Satellite:
        If SatelliteControl Then
            AngleU = Ut: AngleV = Vt: AngleW = Wt: IsControl = 1: IsTarget = ToNone
        ElseIf IsSatelliteControl Then
            'Point nose's ship to the ground target if available
            If IsStationVisibility And IsTargetN > 0 Then
                If TargetsN <= 0 Then _
                    IsTarget = -TargetItem - 1: Station2XYZ Station(TargetItem), X1, Y1, Z1 _
                Else If ObjectPosition(TargetClass, TargetItem, X1, Y1, Z1) Then _
                    IsTarget = -TargetIndex Else IsTarget = ToNone: GoTo SatLookAt
                GoTo SatLookTarget
                End If
            If TargetsN > 0 Then IsTarget = ToNone _
            Else If Cam1.Follow = ToOnBoard Then IsTarget = Cam1.LookAt _
            Else If Cam(0).Follow = ToOnBoard And Cam(0).hRC <> 0 Then IsTarget = Cam(0).LookAt _
            Else If IsStationVisibility Then IsTarget = ToEarth _
            Else IsTarget = ToNone: GoTo NoSatLook
SatLookAt:
            If IsTarget < ToEarth Then IsTarget = Target
            Select Case IsTarget
            Case Is <= ToEarth: GoTo SatLookEarth
            Case ToMoon:    X1 = Moon.X: Y1 = Moon.Y: Z1 = Moon.Z
            Case ToSun:     X1 = Sun.X: Y1 = Sun.Y: Z1 = Sun.Z
            Case ToMercury: X1 = Mercury.X: Y1 = Mercury.Y: Z1 = Mercury.Z
            Case ToVenus:   X1 = Venus.X: Y1 = Venus.Y: Z1 = Venus.Z
            Case ToMars:    X1 = Mars.X: Y1 = Mars.Y: Z1 = Mars.Z
            Case ToJupiter: X1 = Jupiter.X: Y1 = Jupiter.Y: Z1 = Jupiter.Z
            Case ToSaturn:  X1 = Saturn.X: Y1 = Saturn.Y: Z1 = Saturn.Z
            Case ToUranus:  X1 = Uranus.X: Y1 = Uranus.Y: Z1 = Uranus.Z
            Case ToNeptune: X1 = Neptune.X: Y1 = Neptune.Y: Z1 = Neptune.Z
            Case ToPluto:   X1 = Pluto.X: Y1 = Pluto.Y: Z1 = Pluto.Z
            Case ToObject:  If SelectedType = TypeShip Then IsTarget = ToNone: GoTo SatLookAt
                            If Not ObjectPosition(SelectedType, SelectedN, X1, Y1, Z1) Then _
                                IsTarget = ToNone: GoTo SatLookAt
            Case Else
SatLookEarth:
                            X1 = Earth.X: Y1 = Earth.Y: Z1 = Earth.Z: IsTarget = ToEarth
                End Select
SatLookTarget:
            H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
            X1 = X1 / H1: Y1 = Y1 / H1: Z1 = Z1 / H1
SatLookVector:
            S1 = dT: If S1 > 0.1 Then S1 = 0.1 'Limit time step
            'Deviation angle calculation
            X2 = X1 - AngleU: Y2 = Y1 - AngleV: Z2 = Z1 - AngleW 'Vector subtraction
            H1 = (X2 * X2 + Y2 * Y2 + Z2 * Z2) / 4 'Normalize to a squared vector
            If H1 <= 0 Then TargetAngle = 0 Else If H1 >= 1 Then TargetAngle = A180 _
            Else TargetAngle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
            'Deviation vector calculation
            RotatePoint Attitude, X1, Y1, Z1
            'Attitude control .. PID=Proportional Integral Derivative
            'Pitch control (X-Axis)
            H1 = -ASin(Y1)                      'Proportional
            ControlAccU = ControlAccU + H1 * S1 'Integral
            G1 = (H1 - ControlLastU) / S1       'Derivative
            X2 = ControlP * (H1 + ControlAccU * ControlI + G1 * ControlD)
            ControlLastU = H1
            'Yaw control (Y-Axis)
            H1 = -ASin(X1)                      'Proportional
            ControlAccV = ControlAccV + H1 * S1 'Integral
            G1 = (H1 - ControlLastV) / S1       'Derivative
            Y2 = ControlP * (H1 + ControlAccV * ControlI + G1 * ControlD)
            ControlLastV = H1
            'Roll control (Z-Axis)
            H1 = -ASin(AngularSpeed.XY) * 3     'Proportional
            ControlAccW = ControlAccW + H1 * S1 'Integral
            G1 = (H1 - ControlLastW) / S1       'Derivative
            Z2 = 2 * (H1 + ControlAccW * 5 + G1) 'PID fixed to default 2,5,1 to avoid control loss
            ControlLastW = H1
            'Moment of inertia of a solid cylinder .. Iz=1/2*M*R² Ix=Iy=1/12*M*(3*R²+H²)
            'F*D=I*A .. F=Force (N)  D=Distance (m)  I=Angular mass (Kg*m²) A=Angular acceleration (Rad/s²)
            A1 = Diameter / 2: H1 = Length / 2
            G1 = Mass * (A1 * A1 * 3 + Length * Length) / 12 'Moment of inertia or Angular mass
            'Force needed .. F=A*I/D
            If InertiaX <> 0 Then X1 = InertiaX / H1 Else X1 = G1 / H1
            If InertiaY <> 0 Then Y1 = InertiaY / H1 Else Y1 = G1 / H1
            If InertiaZ <> 0 Then Z1 = InertiaZ / A1 Else Z1 = Mass * A1 / 2 'Iz/R=(1/2*M*R*R)/R -> (1/2*M*R)
            ControlThrustU = X2 * X1
            ControlThrustV = Y2 * Y1
            ControlThrustW = Z2 * Z1
            'Thrust max and min limits .. A=F/I*D
            If Control <> 0 Then
                If Control > 0 Then H1 = Control: K1 = Control _
                Else H1 = -Control / H1: K1 = -Control / A1
                If Abs(ControlThrustU) > H1 Then ControlThrustU = H1 * Sgn(ControlThrustU): _
                    X2 = ControlThrustU / X1: ControlAccU = 0
                If Abs(ControlThrustV) > H1 Then ControlThrustV = H1 * Sgn(ControlThrustV): _
                    Y2 = ControlThrustV / Y1: ControlAccV = 0
                If Abs(ControlThrustW) > K1 Then ControlThrustW = K1 * Sgn(ControlThrustW): _
                    Z2 = ControlThrustW / Z1: ControlAccW = 0
                End If
            'Max thrust needed
            If IsStatistics Then
                If Abs(ControlThrustU) > ControlThrustMax Then ControlThrustMax = Abs(ControlThrustU)
                If Abs(ControlThrustV) > ControlThrustMax Then ControlThrustMax = Abs(ControlThrustV)
                If Abs(ControlThrustW) > ControlThrustMax Then ControlThrustMax = Abs(ControlThrustW)
                End If
            'Angular kinetics (this is not correct, angular speed is stored as 'Angle/Delta-Time' instead of 'Angle/Time' and this is because rotation matrices in contrast of angles and quaternions, can not be interpolated to apply the time step)
            'AxisZ->Roll: +Left -Right .. AxisX-Pitch: +Up -Down .. AxisY-Yaw: +Left -Right
            S1 = S1 * S1
            If X2 <> 0 Then RotateX AngularSpeed, -X2 * S1 'Apply yaw angular acceleration
            If Y2 <> 0 Then RotateY AngularSpeed, -Y2 * S1 'Apply pitch angular acceleration
            If Z2 <> 0 Then RotateZ AngularSpeed, -Z2 * S1 'Apply roll angular acceleration
            RotateMatrix AngularSpeed, Attitude 'Apply angular speed to attitude
            'H1 = Sqr(Attitude.ZX * Attitude.ZX + Attitude.ZY * Attitude.ZY + Attitude.ZZ * Attitude.ZZ) 'Normalize vector
            'AngleU = Attitude.ZX / H1: AngleV = Attitude.ZY / H1: AngleW = Attitude.ZZ / H1 'Get Z vector from attitude rotation matrix
            AngleU = Attitude.ZX: AngleV = Attitude.ZY: AngleW = Attitude.ZZ 'Get Z vector from attitude rotation matrix
            IsControl = 2
NoSatLook:
            End If
    
'Stand by
    Case StandBy
StandBy:
    
'Launch sequence aborted
    Case Aborted
Aborted:
    
'End of phase selection
    Case Else
        Play = Paused: GoTo ExitLoop
    
        End Select
    
'Engine thrust
    If Thrust = 0 Then
        AccelX = 0: AccelY = 0: AccelZ = 0
    Else
        'Thrust curve
        If ThrustP >= 0 Then
            A1 = T - DelayTime + dT / 2
            Do
                If ThrustP >= ThrustN - 1 Then Mass = DryMass: G1 = 0: H1 = Mass: GoTo StopEngine
                If A1 >= ThrustCurve(ThrustP + 1).T Then ThrustP = ThrustP + 1 Else Exit Do
                Loop
            H1 = ThrustCurve(ThrustP).T: H1 = (A1 - H1) / (ThrustCurve(ThrustP + 1).T - H1)
            Thrust = ThrustCurve(ThrustP + 1).F * H1 + ThrustCurve(ThrustP).F * (1 - H1) 'Thrust interpolation
            If Thrust <= 0 Then GoTo StopEngine
            BurnRate = Thrust / G0 / IspS 'Burn rate proportional to thrust (this is not correct, it should use a mass curve)
            'Engine thrust relative to air density
            If AirDensity > 0 Then If IsThrustSL Then _
                Thrust = Thrust * ((IspV / IspS - 1) * (1 - AirDensity / AirD) + 1) _
            Else Thrust = Thrust * IspV / IspS 'Air density
        Else
            'Engine thrust relative to air density
            If AirDensity > 0 Then If IsThrustSL Then Thrust = ThrustV - ThrustS * AirDensity / AirD _
            Else Thrust = ThrustV 'Air density
            End If
        'Engine thrust limit
        If ThrustLimit <> 0 Then _
            If Thrust * Throttle / Mass > ThrustLimit Then Throttle = ThrustLimit / Thrust * Mass
        'Mass loss due to the propellant consumption
        G1 = Mass: A1 = BurnRate * Throttle * dT: Mass = Mass - A1
        If Mass > DryMass Then
            H1 = Thrust * Throttle: G1 = H1 / (G1 + Mass) * 2 'Average mass
        Else 'When there is no more propellant, stop engines
            Mass = DryMass
            H1 = Thrust * Throttle * (G1 - Mass) / A1: G1 = H1 / (G1 + Mass) * 2 'Average mass and thrust correction
StopEngine:
            Mass = DryMass: Thrust = 0: DelayTime = T + Delay: IsEnginesEvent = 2
            End If
        'Ground detection during take off
        If IsLiftOff Then If IsCollision Then _
            If G1 <= Earth.Gravity Then LLA2XYZ Earth, Lat, Lon, FloorAltitude, , , , , True: _
                AccelX = 0: AccelY = 0: AccelZ = 0: GoTo NoGravity _
            Else If Earth.Altitude - FloorAltitude > Ground Then _
                IsLiftOff = False: IsPlanetCollision = 2
        'Acceleration
        AccelX = G1 * AngleU: AccelY = G1 * AngleV: AccelZ = G1 * AngleW
        'Thrust Delta-AngleV
        ThrustDV = ThrustDV + G1 * dT
        'Thrust impulse
        Impulse = Impulse + H1 * dT
        'Earth gravity minus centripetal acceleration Delta-AngleV
        GravityDV = GravityDV + (Earth.Gravity - Earth.CentripetalAccel) * Cos(Angle) * dT
        End If
    
'Air drag and lift
    If AirDensity > 0 Then
        'Front area correction .. Ar=Ar*(Sin(A)*(LD-1)+1)  A=Angle of attack  LD=Lenght/diameter ratio
        'Drag force (parallel to air direction) .. DragForce = 0.5 * DragCf * Area * AirDensity * AirSpeed²
        Drag = 0.5 * DragCf * Area * AirDensity * AirSpeed * AirSpeed / Mass / (1 - 0.02 * dT) 'dT error correction
        'If AirAngle < 0 Then A1 = Area * (-Sin(Abs(AirAngle)) * (LengthToDiameter - 1) + 1) 'Front area increase due to the angle of attack (maybe is not correct inside hypersonic airflow)
        If Not IsApogee And Parachute <> 0 And Earth.Altitude < Brake And Earth.Altitude < Atm Then 'Parachute deployed
            If Parachute < 0 Then A1 = -Area * Parachute Else A1 = Parachute
            If A1 > Area Then Drag = Drag / Area * A1
            End If
        If IsDrag Then AccelX = AccelX - Drag * AirSpeedX: AccelY = AccelY - Drag * AirSpeedY: AccelZ = AccelZ - Drag * AirSpeedZ
        'Drag Delta-AngleV
        DragDV = DragDV + Drag * dT
        'Angle of attack
        If Status > Controlled Then
            If LiftLimit <> 0 And LiftToDrag > 0 And Drag > 0 Then 'Reentry
                If LiftLimit < 0 Then
                    AirAngle = -LiftLimit: If AirAngle > A90 Then AirAngle = A90
                Else
                    AirAngle = ASin(LiftLimit / LiftToDrag / Drag) / 2: If AirAngle > A45 Then AirAngle = A45
                    End If
                Angle = AirAngleV - AirAngle: If IsControl = 0 Then IsControl = 1: _
                    Rotate3D AirAngle, 0, 0, AirRollU, AirRollV, AirRollW, AngleU, AngleV, AngleW, AirSpeedX, AirSpeedY, AirSpeedZ
            Else
                Angle = AirAngleV: AirAngle = 0: If IsControl = 0 Then IsControl = 1: _
                    AngleU = AirSpeedX: AngleV = AirSpeedY: AngleW = AirSpeedZ
                End If
            End If
        'Lift force (perpendicular to air direction) .. La=Da*Ld*Sin(A*2)  A=Angle of attack  Ld=Lift to drag ratio
        Lift = Drag * LiftToDrag * Sin(AirAngle * 2)
        'If LiftLimit > 0 And Lift > LiftLimit Then Lift = LiftLimit 'Air lift limit
        If IsLift Then AccelX = AccelX + Lift * AirLiftU: AccelY = AccelY + Lift * AirLiftV: AccelZ = AccelZ + Lift * AirLiftW
        'Lift Delta-AngleV
        LiftDV = LiftDV + Lift * dT
    Else
        'Solar wind pressure
        If IsSolarWind Then H1 = SolarWind * FullArea / Sun.Distance: _
            AccelX = AccelX - H1 * Sun.X: AccelY = AccelY - H1 * Sun.Y: AccelZ = AccelZ - H1 * Sun.Z
        Drag = 0: Lift = 0
        End If
    
    If IsInertial Then 'Save inertial acceleration before add gravity
        InertialAccel = Sqr(AccelX * AccelX + AccelY * AccelY + AccelZ * AccelZ)
        InertialAccelX = AccelX: InertialAccelY = AccelY: InertialAccelZ = AccelZ
        End If
    
'Gravity and Kinetics
    Select Case GravityMode
    Case 1 'Gravity Earth->Ship
        G1 = Earth.Gravity / Earth.Distance
        AccelX = AccelX + G1 * Earth.X: AccelY = AccelY + G1 * Earth.Y: AccelZ = AccelZ + G1 * Earth.Z
    Case 2 'Gravity Earth/Moon->Ship
        G1 = Earth.Gravity / Earth.Distance: H1 = Moon.Gravity / Moon.Distance
        AccelX = AccelX + G1 * Earth.X + H1 * Moon.X: AccelY = AccelY + G1 * Earth.Y + H1 * Moon.Y: AccelZ = AccelZ + G1 * Earth.Z + H1 * Moon.Z
    Case 3 'Gravity Earth/Moon/Sun->Ship
        G1 = Earth.Gravity / Earth.Distance: H1 = Moon.Gravity / Moon.Distance: A1 = Sun.Gravity / Sun.Distance
        AccelX = AccelX + G1 * Earth.X + H1 * Moon.X + A1 * Sun.X
        AccelY = AccelY + G1 * Earth.Y + H1 * Moon.Y + A1 * Sun.Y
        AccelZ = AccelZ + G1 * Earth.Z + H1 * Moon.Z + A1 * Sun.Z
    Case 4 'Gravity All planets->Ship
        Earth.GravityMassDistance = Earth.Gravity / Earth.Distance: Moon.GravityMassDistance = Moon.Gravity / Moon.Distance: Sun.GravityMassDistance = Sun.Gravity / Sun.Distance
        Mercury.GravityMassDistance = Mercury.Gravity / Mercury.Distance: Venus.GravityMassDistance = Venus.Gravity / Venus.Distance: Mars.GravityMassDistance = Mars.Gravity / Mars.Distance
        Jupiter.GravityMassDistance = Jupiter.Gravity / Jupiter.Distance: Saturn.GravityMassDistance = Saturn.Gravity / Saturn.Distance: Uranus.GravityMassDistance = Uranus.Gravity / Uranus.Distance
        Neptune.GravityMassDistance = Neptune.Gravity / Neptune.Distance: Pluto.GravityMassDistance = Pluto.Gravity / Pluto.Distance
        AccelX = AccelX + Earth.GravityMassDistance * Earth.X + Moon.GravityMassDistance * Moon.X + Sun.GravityMassDistance * Sun.X + _
            Mercury.GravityMassDistance * Mercury.X + Venus.GravityMassDistance * Venus.X + Mars.GravityMassDistance * Mars.X + _
            Jupiter.GravityMassDistance * Jupiter.X + Saturn.GravityMassDistance * Saturn.X + Uranus.GravityMassDistance * Uranus.X + _
            Neptune.GravityMassDistance * Neptune.X + Pluto.GravityMassDistance * Pluto.X
        AccelY = AccelY + Earth.GravityMassDistance * Earth.Y + Moon.GravityMassDistance * Moon.Y + Sun.GravityMassDistance * Sun.Y + _
            Mercury.GravityMassDistance * Mercury.Y + Venus.GravityMassDistance * Venus.Y + Mars.GravityMassDistance * Mars.Y + _
            Jupiter.GravityMassDistance * Jupiter.Y + Saturn.GravityMassDistance * Saturn.Y + Uranus.GravityMassDistance * Uranus.Y + _
            Neptune.GravityMassDistance * Neptune.Y + Pluto.GravityMassDistance * Pluto.Y
        AccelZ = AccelZ + Earth.GravityMassDistance * Earth.Z + Moon.GravityMassDistance * Moon.Z + Sun.GravityMassDistance * Sun.Z + _
            Mercury.GravityMassDistance * Mercury.Z + Venus.GravityMassDistance * Venus.Z + Mars.GravityMassDistance * Mars.Z + _
            Jupiter.GravityMassDistance * Jupiter.Z + Saturn.GravityMassDistance * Saturn.Z + Uranus.GravityMassDistance * Uranus.Z + _
            Neptune.GravityMassDistance * Neptune.Z + Pluto.GravityMassDistance * Pluto.Z
        End Select

NoGravity:
    
    'Acceleration/Speed-Ship
    Accel = Sqr(AccelX * AccelX + AccelY * AccelY + AccelZ * AccelZ)
    If IsInertial Then
        If IsLiftOff Then InertialAccel = Earth.Gravity: G1 = -Earth.Gravity / Earth.Distance: _
            InertialAccelX = G1 * Earth.X: InertialAccelY = G1 * Earth.Y: InertialAccelZ = G1 * Earth.Z
        If LogHandle Then LogSimulation 'Log simulation to file
        End If
    SpeedX = SpeedX + AccelX * dT: SpeedY = SpeedY + AccelY * dT: SpeedZ = SpeedZ + AccelZ * dT
    
    'Gravity Moon->Earth
    A1 = Moon.OrbitDistance * Moon.OrbitDistance * Moon.OrbitDistance
    G1 = -Moon.GravityMass / A1 * dT
    Earth.MainSpeedX = Earth.MainSpeedX - G1 * Moon.OrbitX
    Earth.MainSpeedY = Earth.MainSpeedY - G1 * Moon.OrbitY
    Earth.MainSpeedZ = Earth.MainSpeedZ - G1 * Moon.OrbitZ
    'Gravity Earth->Moon
    G1 = Earth.GravityMass / A1 * dT
    Moon.MainSpeedX = Moon.MainSpeedX - G1 * Moon.OrbitX
    Moon.MainSpeedY = Moon.MainSpeedY - G1 * Moon.OrbitY
    Moon.MainSpeedZ = Moon.MainSpeedZ - G1 * Moon.OrbitZ
    If GravityMode >= 3 Then
        'Gravity Sun->Earth
        Earth.GravityMassDistance = Sun.GravityMass / Earth.OrbitDistance / Earth.OrbitDistance / Earth.OrbitDistance * dT
        Earth.MainSpeedX = Earth.MainSpeedX - Earth.GravityMassDistance * Earth.OrbitX
        Earth.MainSpeedY = Earth.MainSpeedY - Earth.GravityMassDistance * Earth.OrbitY
        Earth.MainSpeedZ = Earth.MainSpeedZ - Earth.GravityMassDistance * Earth.OrbitZ
        'Gravity Sun->Moon
        Moon.GravityMassDistance = Sun.GravityMass / Moon.ParentOrbitDistance / Moon.ParentOrbitDistance / Moon.ParentOrbitDistance * dT
        Moon.MainSpeedX = Moon.MainSpeedX - Moon.GravityMassDistance * Moon.ParentOrbitX
        Moon.MainSpeedY = Moon.MainSpeedY - Moon.GravityMassDistance * Moon.ParentOrbitY
        Moon.MainSpeedZ = Moon.MainSpeedZ - Moon.GravityMassDistance * Moon.ParentOrbitZ
        'Gravity Sun->Planets
        If GravityMode >= 4 Then
            PlanetKinetics Mercury
            PlanetKinetics Venus
            PlanetKinetics Mars
            PlanetKinetics Jupiter
            PlanetKinetics Saturn
            PlanetKinetics Uranus
            PlanetKinetics Neptune
            PlanetKinetics Pluto
            End If
        End If
    
    'Position-Sun
    Sun.X = Sun.X - SpeedX * dT: Sun.Y = Sun.Y - SpeedY * dT: Sun.Z = Sun.Z - SpeedZ * dT
    Sun.AngleW = Sun.AngleW + Sun.AngularSpeedW * dT
    'Position-Earth
    Earth.X = Earth.X + (Earth.MainSpeedX - SpeedX) * dT: Earth.Y = Earth.Y + (Earth.MainSpeedY - SpeedY) * dT: Earth.Z = Earth.Z + (Earth.MainSpeedZ - SpeedZ) * dT
    Earth.AngleW = Earth.AngleW + Earth.AngularSpeedW * dT
    'Position-Moon
    Moon.X = Moon.X + (Moon.MainSpeedX - SpeedX) * dT: Moon.Y = Moon.Y + (Moon.MainSpeedY - SpeedY) * dT: Moon.Z = Moon.Z + (Moon.MainSpeedZ - SpeedZ) * dT
    'Moon.X = Moon.X - Moon.SpeedX * dT: Moon.Y = Moon.Y - Moon.SpeedY * dT: Moon.Z = Moon.Z - Moon.SpeedZ * dT
    Moon.AngleW = Moon.AngleW + Moon.AngularSpeedW * dT
    ''Position-Ship
    'X = X + SpeedX * dT: Y = Y + SpeedY * dT: Z = Z + SpeedZ * dT
    'Kinetics of empty stages
    If IsEmptyStages Then _
        For C = -(KMLPathN = 0) To StageC: StageKinetics StageE(C): Next
    
'Time step
    T = Int(T / dT + 1.5) * dT 'Round T to avoid precision loss
    
'Auxiliary variables
    Earth.LastAltitude = Earth.Distance: Earth.LastVerticalSpeed = Earth.VerticalSpeed
    Moon.LastAltitude = Moon.Distance: Moon.LastVerticalSpeed = Moon.VerticalSpeed
    Sun.LastAltitude = Sun.Distance: Sun.LastVerticalSpeed = Sun.VerticalSpeed
    If GravityMode >= 4 Then
        Mercury.LastAltitude = Mercury.Distance: Mercury.LastVerticalSpeed = Mercury.VerticalSpeed
        Venus.LastAltitude = Venus.Distance: Venus.LastVerticalSpeed = Venus.VerticalSpeed
        Mars.LastAltitude = Mars.Distance: Mars.LastVerticalSpeed = Mars.VerticalSpeed
        Jupiter.LastAltitude = Jupiter.Distance: Jupiter.LastVerticalSpeed = Jupiter.VerticalSpeed
        Saturn.LastAltitude = Saturn.Distance: Saturn.LastVerticalSpeed = Saturn.VerticalSpeed
        Uranus.LastAltitude = Uranus.Distance: Uranus.LastVerticalSpeed = Uranus.VerticalSpeed
        Neptune.LastAltitude = Neptune.Distance: Neptune.LastVerticalSpeed = Neptune.VerticalSpeed
        Pluto.LastAltitude = Pluto.Distance: Pluto.LastVerticalSpeed = Pluto.VerticalSpeed
        End If
    Stats
    
    If IsStatistics Then
    
'Statistics
        If Earth.Altitude > MaxHe Then MaxHe = Earth.Altitude: MinHe = Earth.Altitude
        If Earth.Altitude < MinHe Then MinHe = Earth.Altitude
        If Moon.Altitude > MaxHm Then MaxHm = Moon.Altitude
        If Moon.Altitude < MinHm Then MinHm = Moon.Altitude: MaxHm = Moon.Altitude
        If Earth.Speed > MaxVe Then MaxVe = Earth.Speed: MinVe = Earth.Speed
        If Earth.Speed < MinVe Then MinVe = Earth.Speed
        If Accel > MaxG Then MaxG = Accel
        
'Events
        'MsgApoapsis PlanetApoapsis(Pl.ID) & "  " & ToAlt(Pl.Altitude, FmtLog), , Apoapsis
        'MsgPeriapsis PlanetPeriapsis(Pl.ID) & "  " & ToAlt(Pl.Altitude, FmtLog), , Periapsis
        If Target = ToMoon Then
            'Aposelene and Periselene
            If Moon.VerticalSpeed < 0 Then
                If Moon.LastVerticalSpeed >= 0 And T > dT And Moon.Altitude - FloorAltitude > 1 Then _
                MsgApoapsis Moon, Aposelene
            ElseIf Moon.LastVerticalSpeed < 0 And T > dT And Moon.Altitude - FloorAltitude > 1 Then
                If IsPeriselene Then IsPeriselene = False: MaxHm = Moon.Distance
                MsgPeriapsis Moon, Periselene
                End If
            'L-1 Lagrange point ..  Earth.G=Moon.G
            If IsL1LagrangePoint Then If Moon.Gravity > Earth.Gravity Xor IsMoonEscapeSpeed = False Then IsL1LagrangePoint = False: _
                Msg "L-1 Lagrange  " & ToSpeed(Earth.Speed, FmtLog), , L1LagrangePoint
        ElseIf Target = ToEarth Then
            'Apogee and Perigee
            If Earth.VerticalSpeed < 0 Then
                If Earth.LastVerticalSpeed >= 0 And T > dT And Earth.Altitude - FloorAltitude > 1 Then
                If IsApogee Then IsApogee = False: MinHe = Earth.Distance
                MsgApoapsis Earth, Apogee
                End If
            ElseIf Earth.LastVerticalSpeed < 0 And T > dT And Earth.Altitude - FloorAltitude > 1 Then
                MsgPeriapsis Earth, Perigee
                End If
            'Exit atmosphere ..     Earth.A>100k
            If IsAtmosphere Then If Earth.Altitude > Atm Then IsAtmosphere = False: _
                Msg "Exit atmosphere  " & ToSpeed(Earth.Speed, FmtLog), , ExitAtmosphere
            'Enter atmosphere ..    Earth.A<100k
            If Not IsAtmosphere Then If Earth.Altitude < Atm Then IsAtmosphere = True: _
                Msg "Enter atmosphere  " & ToSpeed(Earth.Speed, FmtLog), , EnterAtmosphere
            'Exit ionosphere ..     Earth.A>500k
            If IsIonosphere Then If Earth.Altitude > Ion Then IsIonosphere = False: _
                Msg "Exit ionosphere  " & ToSpeed(Earth.Speed, FmtLog), , ExitIonosphere
            'Enter ionosphere ..    Earth.A<500k
            If Not IsIonosphere Then If Earth.Altitude < Ion Then IsIonosphere = True: _
                Msg "Enter ionosphere  " & ToSpeed(Earth.Speed, FmtLog), , EnterIonosphere
            'Sound speed ..         Earth.AirSpeed/Am>1
            If IsEarthSoundSpeed Then If Earth.AirSpeed / MachNumber >= 1 Then IsEarthSoundSpeed = False: _
                Msg "Sound speed  " & ToSpeed(Earth.AirSpeed, FmtLog), , EarthSoundSpeed
            'Equidistant point ..   Earth.H=Moon.H
            If IsEquidistantPoint Then If Earth.Distance > Moon.Distance Xor IsMoonEscapeSpeed = False Then IsEquidistantPoint = False: _
                Msg "Equidistant  " & ToSpeed(Earth.Speed, FmtLog), , EquidistantPoint
        Else
            GoTo NoEarth
            End If
        'Earth orbital speed .. V=Sqr(G*Earth.Mass/Earth.Distance) H=G*Earth.Mass/AngleV^2-Earth.Radius
        If IsEarthOrbitalSpeed Then If Earth.Speed >= Sqr(Earth.GravityMass / Earth.Distance) Then IsEarthOrbitalSpeed = False: _
            Msg "Orbital speed  " & ToSpeed(Sqr(Earth.GravityMass / Earth.Distance), FmtLog), , EarthOrbitalSpeed: _
            If EarthPark > 0 Then Thrust = 0: DelayTime = T + EarthPark: IsEnginesEvent = 2
        'Earth escape speed ..  V=Sqr(2*G*Earth.Mass/Earth.Distance)
        If IsEarthEscapeSpeed Then If Earth.Speed >= Sqr(Earth.GravityMass / Earth.Distance * 2) Then IsEarthEscapeSpeed = False: _
            Msg "Escape speed  " & ToSpeed(Sqr(Earth.GravityMass / Earth.Distance * 2), FmtLog), , EarthEscapeSpeed
        'Moon orbital speed ..  V=Sqr(G*Moon.Mass/Moon.Distance) H=G*Moon.Mass/AngleV^2-Moon.Radius
        If IsMoonOrbitalSpeed Then If Moon.Speed <= Sqr(Moon.GravityMass / Moon.Distance) And _
                Moon.Distance < Earth.Distance Then IsMoonOrbitalSpeed = False: _
            Msg "Orbital speed  " & ToSpeed(Sqr(Moon.GravityMass / Moon.Distance), FmtLog), , MoonOrbitalSpeed: _
            If MoonPark <> 0 Then Thrust = 0: IsEnginesEvent = 2: DelayTime = Abs(MoonPark): _
                If DelayTime >= Week Then DelayTime = DelayTime - Week + T Else DelayTime = DelayTime + T
        'Moon escape speed ..   V=Sqr(2*G*Moon.Mass/Moon.Distance)
        If IsMoonEscapeSpeed And Not IsMoonOrbitalSpeed Then If Moon.Speed >= Sqr(Moon.GravityMass / Moon.Distance * 2) Then _
            IsMoonEscapeSpeed = False: IsEquidistantPoint = True: IsL1LagrangePoint = True: _
            Msg "Escape speed  " & ToSpeed(Sqr(Moon.GravityMass / Moon.Distance * 2), FmtLog), , MoonEscapeSpeed
        'Moon distance ..       Earth.Distance=Moon.OrbitDistance
        If IsMoonDistance Then If Earth.Distance > Moon.OrbitDistance Then IsMoonDistance = False: _
            Msg "Moon distance  " & ToSpeed(Earth.Speed, FmtLog), , MoonDistance
NoEarth:
        If GravityMode >= 4 Then
            'Planet distance ..     Sun.Distance=Planet.OrbitDistance
            If LastSector <> Sector Then
                Select Case Sector
                Case ToMercury: MsgDistance Mercury, PlanetDistance
                Case ToVenus:   If LastSector = ToMercury Then MsgDistance Mercury, PlanetDistance _
                                Else MsgDistance Venus, PlanetDistance
                Case ToEarth:   If LastSector <= ToVenus Then MsgDistance Venus, PlanetDistance _
                                Else MsgDistance Earth, PlanetDistance
                Case ToMars:    If LastSector <= ToVenus Then MsgDistance Earth, PlanetDistance _
                                Else MsgDistance Mars, PlanetDistance
                Case ToJupiter: If LastSector <= ToMars Then MsgDistance Mars, PlanetDistance _
                                Else MsgDistance Jupiter, PlanetDistance
                Case ToSaturn:  If LastSector <= ToJupiter Then MsgDistance Jupiter, PlanetDistance _
                                Else MsgDistance Saturn, PlanetDistance
                Case ToUranus:  If LastSector <= ToSaturn Then MsgDistance Saturn, PlanetDistance _
                                Else MsgDistance Uranus, PlanetDistance
                Case ToNeptune: If LastSector <= ToUranus Then MsgDistance Uranus, PlanetDistance _
                                Else MsgDistance Neptune, PlanetDistance
                Case ToPluto:   If LastSector <= ToNeptune Then MsgDistance Neptune, PlanetDistance _
                                Else MsgDistance Pluto, PlanetDistance
                Case ToObject:  MsgDistance Pluto, PlanetDistance
                    End Select
                End If
            Select Case Target
            Case ToMercury
                'Apohermion and Perihermion
                If Mercury.VerticalSpeed < 0 Then
                    If Mercury.LastVerticalSpeed >= 0 And T > dT And Mercury.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Mercury, Apoapsis
                ElseIf Mercury.LastVerticalSpeed < 0 And T > dT And Mercury.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Mercury, Periapsis
                    End If
            Case ToVenus
                'Apocytherion and Pericytherion
                If Venus.VerticalSpeed < 0 Then
                    If Venus.LastVerticalSpeed >= 0 And T > dT And Venus.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Venus, Apoapsis
                ElseIf Venus.LastVerticalSpeed < 0 And T > dT And Venus.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Venus, Periapsis
                    End If
            Case ToMars
                'Apoareion and Periareion
                If Mars.VerticalSpeed < 0 Then
                    If Mars.LastVerticalSpeed >= 0 And T > dT And Mars.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Mars, Apoapsis
                ElseIf Mars.LastVerticalSpeed < 0 And T > dT And Mars.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Mars, Periapsis
                    End If
                'Mars orbital speed ..  V=Sqr(G*Mars.M/Mars.Distance) H=G*Mars.M/AngleV^2-Mars.Radius
                If IsMarsOrbitalSpeed Then If Mars.Speed <= Sqr(Mars.GravityMass / Mars.Distance) And _
                        Mars.Distance < Earth.Distance Then IsMarsOrbitalSpeed = False: _
                    Msg "Orbital speed  " & ToSpeed(Sqr(Mars.GravityMass / Mars.Distance), FmtLog), , MarsOrbitalSpeed: _
                    If MoonPark <> 0 Then Thrust = 0: IsEnginesEvent = 2: DelayTime = Abs(MoonPark): _
                        If DelayTime >= Week Then DelayTime = DelayTime - Week + T Else DelayTime = DelayTime + T
                    'If MoonPark <> 0 Then Thrust = 0: DelayTime = T + Abs(MoonPark): IsEnginesEvent = 2
                'Mars escape speed ..   V=Sqr(2*G*Mars.M/Mars.Distance)
                If IsMarsEscapeSpeed And Not IsMarsOrbitalSpeed Then If Mars.Speed >= Sqr(Mars.GravityMass / Mars.Distance * 2) Then _
                    IsMarsEscapeSpeed = False: IsEquidistantPoint = True: IsL1LagrangePoint = True: _
                    Msg "Escape speed  " & ToSpeed(Sqr(Mars.GravityMass / Mars.Distance * 2), FmtLog), , MarsEscapeSpeed
            Case ToJupiter
                'Apozene and Perizene
                If Jupiter.VerticalSpeed < 0 Then
                    If Jupiter.LastVerticalSpeed >= 0 And T > dT And Jupiter.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Jupiter, Apoapsis
                ElseIf Jupiter.LastVerticalSpeed < 0 And T > dT And Jupiter.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Jupiter, Periapsis
                    End If
            Case ToSaturn
                'Apokrone and Perikrone
                If Saturn.VerticalSpeed < 0 Then
                    If Saturn.LastVerticalSpeed >= 0 And T > dT And Saturn.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Saturn, Apoapsis
                ElseIf Saturn.LastVerticalSpeed < 0 And T > dT And Saturn.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Saturn, Periapsis
                    End If
            Case ToUranus
                'Apouranion and Periuranion
                If Uranus.VerticalSpeed < 0 Then
                    If Uranus.LastVerticalSpeed >= 0 And T > dT And Uranus.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Uranus, Apoapsis
                ElseIf Uranus.LastVerticalSpeed < 0 And T > dT And Uranus.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Uranus, Periapsis
                    End If
            Case ToNeptune
                'Apoposeidion and Periposeidion
                If Neptune.VerticalSpeed < 0 Then
                    If Neptune.LastVerticalSpeed >= 0 And T > dT And Neptune.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Neptune, Apoapsis
                ElseIf Neptune.LastVerticalSpeed < 0 And T > dT And Neptune.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Neptune, Periapsis
                    End If
            Case ToPluto
                'Apohadion and Perihadion
                If Pluto.VerticalSpeed < 0 Then
                    If Pluto.LastVerticalSpeed >= 0 And T > dT And Pluto.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Pluto, Apoapsis
                ElseIf Pluto.LastVerticalSpeed < 0 And T > dT And Pluto.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Pluto, Periapsis
                    End If
            Case Else
                'Aphelion and Perihelion
                If Sun.VerticalSpeed < 0 Then
                    If Sun.LastVerticalSpeed >= 0 And T > dT And Sun.Altitude - FloorAltitude > 1 Then _
                    MsgApoapsis Sun, Apoapsis
                ElseIf Sun.LastVerticalSpeed < 0 And T > dT And Sun.Altitude - FloorAltitude > 1 Then
                    MsgPeriapsis Sun, Periapsis
                    End If
                End Select
            End If
        
        'Planet proximity
        If LastTarget <> Target Then _
            If LastTarget = ToSun Or Target = ToMoon Then _
                Msg Planets(Target) & " approach  " & ToSpeed(PlanetSpeed(, True), FmtLog), , PlanetApproach And Not IfInt( _
                    Cam1.Target > ToShip And Cam1.Target <> Target And Target <> ToEarth And Target <> ToMoon, 1, 0) _
            Else If Target = ToSun Or LastTarget = ToMoon Then _
                Msg Planets(LastTarget) & " faraway  " & ToSpeed(PlanetSpeed(LastTarget, True), FmtLog), , PlanetFaraway And Not IfInt( _
                    Cam1.Target > ToShip And Cam1.Target <> LastTarget And LastTarget <> ToEarth And LastTarget <> ToMoon, 1, 0)
        If LastZone <> Zone Then _
            If LastZone = VOID And Zone <> VOID Then _
                Msg Planets(Target) & " proximity  " & ToSpeed(PlanetSpeed(, True), FmtLog), , PlanetProximity And Not IfInt( _
                    Cam1.Target > ToShip And Cam1.Target <> Target And Target <> ToEarth And Target <> ToMoon, 1, 0)
        
        End If
    
'Stage events
    If IsEnginesEvent Then
        Select Case IsEnginesEvent
        Case 1
            Select Case StageP
            'Hover engines on
            Case 1: Msg "Hover engines on", , HoverEnginesOn
            'Stage engines on
            Case Is > 1: Msg "Stage " & StageN & " engines on", , StageEnginesOn
                End Select
        Case 2
            Select Case StageP
            'Hover engines off
            Case 1: Msg "Hover engines off", , HoverEnginesOff
            'Last stage engines off
            Case 2: Msg "Stage " & StageN & " engines off", , StageEnginesOff, LastStageEnginesOff
            'Stage engines off
            Case Is > 2: Msg "Stage " & StageN & " engines off", , StageEnginesOff
                End Select
            End Select
        IsEnginesEvent = False
        End If
    If IsDeployed Then
        Select Case StageP
        'Satellite deployed
        Case 0: Msg "Satellite deployed", MsgEx, SatelliteDeployed
        'Moon transfer or braking altitude
        Case 1: If Status = Transfer Then Msg "Moon transfer", MsgEx, MoonTransfer _
            Else Msg "Braking altitude", , BrakingAltitude
        'Launch sequence aborted
        Case Is > 1: Msg "Aborted on " & IIf(Abort, "stage " & StageN, "launch"), MsgEx, StageAbort
            End Select
        IsDeployed = False: DragDV = 0
        End If

'Planet collision and lift off
    If IsPlanetCollision Then
        Select Case IsPlanetCollision
        'Ground collision
        Case 1
        If Status > Landed Then
            Status = IfInt(PlanetSpeed < Crash, Landed, Crashed)
            Thrust = 0: Throttle = 0: RealTime = RealTime + TimerEx - LastTime
            If Not TouchDown Then
                Msg _
                    IIf(Status = Landed, "Landed", "Crashed") & " on " & Planets(Target), MsgEx & vbCrLf & _
                    vbTab & "Simulation=" & ToDh(RealTime, True) & "  x" & Num(T / RealTime, FMini) & IfText(IsSatelliteProximity, vbCrLf & _
                    vbTab & "Satellite collision probability=" & ToRatioEx(SatelliteInRange, T)) & IfText(IsAsteroidProximity, vbCrLf & _
                    vbTab & "Asteroid collision probability=" & ToRatioEx(AsteroidInRange, T))
                If SnapN > SnapP Then SnapN = SnapP: ReDim Preserve Snap(SnapN)
                End If
            End If
        Play = Paused: GoTo ExitLoop
        'Lift off
        Case 2: Msg "Lift off", "DeltaV=" & ToSpeed(ThrustDV, FmtLog), LiftOff
            End Select
        IsPlanetCollision = False
        End If
    
'End of main loop
EndLoop:
    Count = Count - 1: Loop
    
'Update Sensor target pointer, Simulation time, Render, Data and Graph panels
ExitLoop:
    ErrorP = ErrorP + 1: If ErrorP > ErrorN Then ErrorP = 0
    A1 = TimerEx: RealTime = RealTime + A1 - LastTime: Render: Info: Plot: DoEvents
    
'FPS and real speed
    A1 = TimerEx
    If IsRealTime Then
        RealSpeed = RealSpeed * 0.9 + (T - StepT) / (A1 - StepTime) * 0.1
        If RealSpeed < 0 Then RealSpeed = 0
        AverageSpeed = (T - StartT) / (A1 - StartTime)
        If RealSpeed > 0 And AverageSpeed > 0 Then _
            TimeSpeed = TimeSpeed * 0.7 + TimeSpeed / RealSpeed * 0.1 + TimeSpeed / AverageSpeed * 0.2: _
            If TimeSpeed < 0 Then TimeSpeed = 0
        StepTime = A1: StepT = T
        End If
    FrameCount = FrameCount + 1
    If Int(A1) > Int(FrameTime) Then
        If Record = Active Then Fps = VideoHdr.dwRate _
        Else Fps = CDbl(FrameCount) / (A1 - FrameTime)
        FrameTime = A1: FrameCount = 0
        If Not IsRealTime Then RealSpeed = Fps * TimeSpeed * dT
        End If

'Video frame recording
    If Record = Active Then RecordVideo
    
    Loop While Play = Active
    
'Reset simulation if needed
    If Play = Reset Or Play = Restart Then
DoRestart:
        Play = Active: If IsBalloonTrajectory Then GoTo DoLaunch _
        Else Init , , Play = Restart: GoTo StartLoop
        End If
DoExit:
    If Not IsExit Then
        If Status = Landed Or Status = Crashed Then If Record = Active Then RecordVideo Pause
        ToolBar.SetIcon ToolBar.Play
        If VideoSync Then If Not Video.WMP Is Nothing Then Video.WMP.object.Pause
        If LogHandle Then LogStop
        End If
    
    End Sub

Sub Stats()
    
    LastTarget = Target: LastSector = Sector: LastZone = Zone
    
'Earth
    'Distance to Ship
    Earth.Distance = Sqr(Earth.X * Earth.X + Earth.Y * Earth.Y + Earth.Z * Earth.Z)
    Earth.Altitude = Earth.Distance - Earth.Radius
    'Speed relative to the center
    Earth.SpeedX = SpeedX - Earth.MainSpeedX: Earth.SpeedY = SpeedY - Earth.MainSpeedY: Earth.SpeedZ = SpeedZ - Earth.MainSpeedZ
    Earth.Speed = Sqr(Earth.SpeedX * Earth.SpeedX + Earth.SpeedY * Earth.SpeedY + Earth.SpeedZ * Earth.SpeedZ)
    Earth.VerticalSpeed = (Earth.Distance - Earth.LastAltitude) / dT: Earth.HorizontalSpeed2 = Earth.Speed * Earth.Speed - Earth.VerticalSpeed * Earth.VerticalSpeed
    'Centripetal acceleration
    Earth.CentripetalAccel = Earth.HorizontalSpeed2 / Earth.Distance 'A=V²/R
    'Gravity
    Earth.Gravity = Earth.GravityMass / Earth.Distance / Earth.Distance
    If Earth.Distance < Earth.Diameter Then
        'Ground altitude if available
        If TerrainMode > 0 Then
            XYZ2LLA Earth, Y2, X2, Earth.Altitude
            If TerrainMode = 2 Then
                C1 = Int((X2 / Rad + 180) * 120 + 0.5): If C1 < 0 Then C1 = C1 + 43200 Else If C1 >= 43200 Then C1 = C1 - 43200
                D1 = Int((Y2 / Rad + 90) * 120 + 0.5): If D1 < 0 Then D1 = 0 Else If D1 >= 21600 Then D1 = 21599
                GeoidAltitude = TerrainScale * (CLng(Earth.Geoid(C1 \ 5, D1 \ 5)) - 128)
                E1 = Earth.TerrainP(C1 \ 120, D1 \ 120)
                If E1 Then FloorAltitude = GeoidAltitude + Earth.Terrain(C1 Mod 120, D1 Mod 120, E1) * TerrainScale _
                Else FloorAltitude = GeoidAltitude
            Else
                FloorAltitude = 0: GeoidAltitude = 0
                End If
        Else
            FloorAltitude = 0: GeoidAltitude = 0
            End If
        'Speed relative to the atmosphere with correction due to the axial tilt
        'X1 = Earth.X: Y1 = Earth.Y: Z1 = Earth.Z: Rotate Earth.AngleU, Y1, Z1
        'X2 = Earth.AngularSpeedW * Y1: Y2 = -Earth.AngularSpeedW * X1: Z2 = 0: Rotate -Earth.AngleU, Y2, Z2
        'Earth.AirSpeedX = Earth.SpeedX - X2: Earth.AirSpeedY = Earth.SpeedY - Y2: Earth.AirSpeedZ = Earth.SpeedZ - Z2
        Earth.AirSpeedX = Earth.SpeedX - Earth.AngularSpeedW * (Earth.Z * Earth.SinA + Earth.Y * Earth.CosA)
        Earth.AirSpeedY = Earth.SpeedY + Earth.AngularSpeedW * Earth.X * Earth.CosA
        Earth.AirSpeedZ = Earth.SpeedZ + Earth.AngularSpeedW * Earth.X * Earth.SinA
        Earth.AirSpeed = Sqr(Earth.AirSpeedX * Earth.AirSpeedX + Earth.AirSpeedY * Earth.AirSpeedY + Earth.AirSpeedZ * Earth.AirSpeedZ)
        AirSpeedX = Earth.AirSpeedX / Earth.AirSpeed: AirSpeedY = Earth.AirSpeedY / Earth.AirSpeed: AirSpeedZ = Earth.AirSpeedZ / Earth.AirSpeed
        AirSpeed = Earth.AirSpeed 'Air speed vector
        AltitudeX = Earth.X / Earth.Distance: AltitudeY = Earth.Y / Earth.Distance: AltitudeZ = Earth.Z / Earth.Distance: Altitude = Earth.Altitude 'Vertical vector
        GroundAltitude = Altitude - FloorAltitude 'Distance to the ground
        VerticalSpeed = Earth.VerticalSpeed 'Vertical speed
        'Pitch angle
        X1 = AngleU + AltitudeX: Y1 = AngleV + AltitudeY: Z1 = AngleW + AltitudeZ 'Vector addition
        H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
        If H1 <= 0 Then Angle = 0 Else If H1 >= 1 Then Angle = A180 _
        Else Angle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
        If Earth.Altitude < Ion Then
            'Atmosphere density, pressure and temperature due to the altitude
            If AtmMode > 0 And AtmN > 0 Then
                'Use atmospheric model if available
                XYZ2LLA Earth, Y2, X2, Z2
                
                If AtmMode = 1 Then 'No interpolation
                    X0 = Int(X2 / Rad + 0.5): If X0 < 0 Then X0 = X0 + 360
                    Y0 = 90 - Int(Y2 / Rad + 0.5)
                    Z0 = Int(Z2 / AtmL + 0.5): If Z0 < 0 Then Z0 = 0 Else If Z0 > AtmM Then _
                        AirWindU = 0: AirWindV = 0: AirWindW = 0: AirWindH = 0: GoTo NoAtm
                    Z0 = AtmR(X0, Y0, Z0)
                    AirPressure = AtmP(Z0) * 100
                    AirTemperature = AtmT(X0, Y0, Z0)
                    AirHumidity = AtmH(X0, Y0, Z0)
                    AirCloudWater = AtmW(X0, Y0, Z0)
                    AirWindU = AtmX(X0, Y0, Z0)
                    AirWindV = AtmY(X0, Y0, Z0)
                    AirWindW = AtmZ(X0, Y0, Z0) * AtmD(Z0)
                
                ElseIf AtmMode = 2 Then '1D-Z interpolation
                    X0 = Int(X2 / Rad + 0.5): If X0 < 0 Then X0 = X0 + 360
                    Y0 = 90 - Int(Y2 / Rad + 0.5)
                    Z0 = Int(Z2 / AtmL): If Z0 < 0 Then Z0 = 0 Else If Z0 > AtmM Then Z0 = AtmM
                    Z0 = AtmR(X0, Y0, Z0): Z5 = Z0 + 1: G1 = AtmA(X0, Y0, Z5)
                    If Z5 >= AtmN And Z2 > G1 Then
                        R1 = G1 / 1000: C1 = Int(R1): R1 = R1 - C1
                        With Earth.AirCurve(C1): H1 = (.P + .P2 * R1) / AtmP(Z5) / 100
                        A1 = (.T + .T2 * R1) / AtmT(X0, Y0, Z5): End With
                        R1 = Z2 / 1000: C1 = Int(R1): R1 = R1 - C1
                        With Earth.AirCurve(C1): AirPressure = (.P + .P2 * R1) / H1
                        AirTemperature = (.T + .T2 * R1) / A1: End With
                        R1 = 0: If Z2 < AtmL2 Then R2 = 1 - (Z2 - G1) / (AtmL2 - G1) Else R2 = 0
                    Else
                        R1 = AtmA(X0, Y0, Z0): R2 = (Z2 - R1) / (G1 - R1): R1 = 1 - R2
                        AirPressure = (AtmP(Z0) * R1 + AtmP(Z5) * R2) * 100
                        AirTemperature = AtmT(X0, Y0, Z0) * R1 + AtmT(X0, Y0, Z5) * R2
                        End If
                    AirHumidity = AtmH(X0, Y0, Z0) * R1 + AtmH(X0, Y0, Z5) * R2
                    AirCloudWater = AtmW(X0, Y0, Z0) * R1 + AtmW(X0, Y0, Z5) * R2
                    AirWindU = AtmX(X0, Y0, Z0) * R1 + AtmX(X0, Y0, Z5) * R2
                    AirWindV = AtmY(X0, Y0, Z0) * R1 + AtmY(X0, Y0, Z5) * R2
                    AirWindW = AtmZ(X0, Y0, Z0) * AtmD(Z0) * R1 + AtmZ(X0, Y0, Z5) * AtmD(Z5) * R2
                
                Else '3D-XYZ interpolation
                    RX2 = X2 / Rad: X0 = Int(RX2): RX2 = RX2 - X0: RX1 = 1 - RX2: If X0 < 0 Then X0 = X0 + 360
                    X5 = X0 + 1: If X5 >= 360 Then X5 = X5 - 360
                    RY2 = Y2 / Rad: Y0 = Int(RY2): RY2 = RY2 - Y0: RY1 = 1 - RY2: Y0 = 90 - Y0
                    Y5 = Y0 - 1: If Y5 < -90 Then Y5 = -90
                    R11 = RX1 * RY1: R12 = RX1 * RY2: R21 = RX2 * RY1: R22 = RX2 * RY2
                    Z6 = Int(Z2 / AtmL): If Z6 < 0 Then Z6 = 0 Else If Z6 > AtmM Then Z6 = AtmM
                    AtmData X0, Y0, R11, True
                    AtmData X5, Y0, R21
                    AtmData X0, Y5, R12
                    AtmData X5, Y5, R22
                    
                'Else '4D-XYZT interpolation
                    
                    End If
                AirDensity = AirPressure / AirTemperature / Earth.GasConstant
                AirWindH = Sqr(AirWindU * AirWindU + AirWindV * AirWindV)
                If IsWind Then
                    AirSpeedX = AirWindU: AirSpeedY = -AirWindW: AirSpeedZ = -AirWindV
                    Rotate Y2, AirSpeedY, AirSpeedZ 'Latitude
                    Rotate X2 + Earth.AngleW, AirSpeedX, AirSpeedY 'Longitude and Earth rotation
                    Rotate Earth.AngleU, AirSpeedY, AirSpeedZ 'Earth axis tilt
                    AirSpeedX = AirSpeedX + Earth.AirSpeedX: AirSpeedY = AirSpeedY + Earth.AirSpeedY: AirSpeedZ = AirSpeedZ + Earth.AirSpeedZ
                    AirSpeed = Sqr(AirSpeedX * AirSpeedX + AirSpeedY * AirSpeedY + AirSpeedZ * AirSpeedZ)
                    AirSpeedX = AirSpeedX / AirSpeed: AirSpeedY = AirSpeedY / AirSpeed: AirSpeedZ = AirSpeedZ / AirSpeed
                    End If
            Else
NoAtm:
                G1 = Earth.Altitude / 1000: C1 = Int(G1): If C1 < 0 Then C1 = 0 Else G1 = G1 - C1
                With Earth.AirCurve(C1): AirDensity = .D + .D2 * G1
                AirPressure = .P + .P2 * G1: AirTemperature = .T + .T2 * G1: End With
                End If
            'Sound speed .. V=Sqr(G*R*T) G=1.4=Ratio of specific heats  R=286.9=Gas constant(m²/s²/ºK)  T=Temperature(ºK)
            MachNumber = Sqr(AirTemperature * Earth.GasConstant * Earth.HeatRatio) '401.66
            'Drag coefficient curve
            If DragN > 0 Then If DragN = 1 Then DragCf = DragCurve(0) _
                Else A1 = AirSpeed / MachNumber / DragStep: C1 = Int(A1): If C1 >= DragN - 1 Then DragCf = DragCurve(DragN - 1) _
                Else H1 = A1 - C1: G1 = 1 - H1: DragCf = DragCurve(C1) * G1 + DragCurve(C1 + 1) * H1 'Drag value interpolation
            'Air speed angle
            AirRollU = AirSpeedY * AltitudeZ - AirSpeedZ * AltitudeY: AirRollV = AirSpeedZ * AltitudeX - AirSpeedX * AltitudeZ: AirRollW = AirSpeedX * AltitudeY - AirSpeedY * AltitudeX 'Cross product
            AirAngleV = Sqr(AirRollU * AirRollU + AirRollV * AirRollV + AirRollW * AirRollW)
            If AirAngleV > 0 Then AirRollU = AirRollU / AirAngleV: AirRollV = AirRollV / AirAngleV: AirRollW = AirRollW / AirAngleV
            If AirAngleV >= 1 Then AirAngleV = A90 Else If AirAngleV <= -1 Then AirAngleV = -A90 Else AirAngleV = Atn(AirAngleV / Sqr(1 - AirAngleV * AirAngleV)) 'ASin
            If VerticalSpeed < 0 Then AirAngleV = A180 - AirAngleV
            'Lift vector
            AirLiftU = AirSpeedY * AirRollW - AirSpeedZ * AirRollV: AirLiftV = AirSpeedZ * AirRollU - AirSpeedX * AirRollW: AirLiftW = AirSpeedX * AirRollV - AirSpeedY * AirRollU 'Cross product
            'Angle of attack
            AirAngle = AirAngleV - Angle
            'Heating due to atmosphere drag
            Temperature = DragCf * AirDensity * AirSpeed * AirSpeed * AirSpeed 'QDrag=0.5*Cd*D*V³
            If Earth.Altitude < Atm Then Zone = Atmosphere Else Zone = Ionosphere
        Else
            AirDensity = 0: Zone = Proximity
            End If
    Else
        'Pitch angle
        If Status <= Controlled And Earth.Altitude < hEarthProximity Then
            X1 = AngleU + Earth.X / Earth.Distance: Y1 = AngleV + Earth.Y / Earth.Distance: Z1 = AngleW + Earth.Z / Earth.Distance 'Vector addition
            H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
            If H1 <= 0 Then Angle = 0 Else If H1 >= 1 Then Angle = A180 _
            Else Angle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
            End If
        Earth.AirSpeed = 0: AirSpeed = 0: AirDensity = 0
        Altitude = 0: FloorAltitude = 0: GeoidAltitude = 0
        Temperature = 0: Zone = VOID
        End If
    
'Moon
    'Distance to Earth
    Moon.OrbitX = Moon.X - Earth.X: Moon.OrbitY = Moon.Y - Earth.Y: Moon.OrbitZ = Moon.Z - Earth.Z
    Moon.OrbitDistance = Sqr(Moon.OrbitX * Moon.OrbitX + Moon.OrbitY * Moon.OrbitY + Moon.OrbitZ * Moon.OrbitZ)
    'Distance to Ship
    Moon.Distance = Sqr(Moon.X * Moon.X + Moon.Y * Moon.Y + Moon.Z * Moon.Z)
    Moon.Altitude = Moon.Distance - Moon.Radius
    'Speed relative to the center
    Moon.SpeedX = SpeedX - Moon.MainSpeedX: Moon.SpeedY = SpeedY - Moon.MainSpeedY: Moon.SpeedZ = SpeedZ - Moon.MainSpeedZ
    Moon.Speed = Sqr(Moon.SpeedX * Moon.SpeedX + Moon.SpeedY * Moon.SpeedY + Moon.SpeedZ * Moon.SpeedZ)
    Moon.VerticalSpeed = (Moon.Distance - Moon.LastAltitude) / dT: Moon.HorizontalSpeed2 = Moon.Speed * Moon.Speed - Moon.VerticalSpeed * Moon.VerticalSpeed
    'Centripetal acceleration
    Moon.CentripetalAccel = Moon.HorizontalSpeed2 / Moon.Distance 'A=V²/R
    'Gravity
    Moon.Gravity = Moon.GravityMass / Moon.Distance / Moon.Distance
    If Moon.Distance < Moon.Diameter Then
        'Ground altitude if available
        If TerrainMode > 0 Then
            XYZ2LLA Moon, Y2, X2, Moon.Altitude
            If TerrainMode = 2 Then
                C1 = Int((X2 / Rad + 180) * 16 + 0.5): If C1 < 0 Then C1 = C1 + 5760 Else If C1 >= 5760 Then C1 = C1 - 5760
                D1 = Int((Y2 / Rad + 90) * 16 + 0.5): If D1 < 0 Then D1 = 0 Else If D1 >= 2880 Then D1 = 2879
                FloorAltitude = Moon.Terrain(C1, D1) * TerrainScale
                End If
            End If
        'Speed relative to the surface with correction due to the axial tilt
        'X1 = Moon.X: Y1 = Moon.Y: Z1 = Moon.Z: Rotate Moon.AngleV, X1, Z1
        'X2 = Moon.AngularSpeedW * Y1: Y2 = -Moon.AngularSpeedW * X1: Z2 = 0: Rotate -Moon.AngleV, X2, Z2
        'Moon.AirSpeedX = Moon.SpeedX - X2: Moon.AirSpeedY = Moon.SpeedY - Y2: Moon.AirSpeedZ = Moon.SpeedZ - Z2
        Moon.AirSpeedX = Moon.SpeedX - Moon.AngularSpeedW * Moon.Y * Moon.CosA
        Moon.AirSpeedY = Moon.SpeedY - Moon.AngularSpeedW * (Moon.Z * Moon.SinA - Moon.X * Moon.CosA)
        Moon.AirSpeedZ = Moon.SpeedZ + Moon.AngularSpeedW * Moon.Y * Moon.SinA
        Moon.AirSpeed = Sqr(Moon.AirSpeedX * Moon.AirSpeedX + Moon.AirSpeedY * Moon.AirSpeedY + Moon.AirSpeedZ * Moon.AirSpeedZ)
        AltitudeX = Moon.X / Moon.Distance: AltitudeY = Moon.Y / Moon.Distance: AltitudeZ = Moon.Z / Moon.Distance: Altitude = Moon.Altitude
        GroundAltitude = Altitude - FloorAltitude 'Vertical vector
        AirSpeedX = Moon.AirSpeedX / Moon.AirSpeed: AirSpeedY = Moon.AirSpeedY / Moon.AirSpeed: AirSpeedZ = Moon.AirSpeedZ / Moon.AirSpeed
        AirSpeed = Moon.AirSpeed 'Ground speed vector
        VerticalSpeed = Moon.VerticalSpeed 'Vertical speed
        Zone = Proximity
        'Pitch angle
        X1 = AngleU + AltitudeX: Y1 = AngleV + AltitudeY: Z1 = AngleW + AltitudeZ 'Vector addition
        H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
        If H1 <= 0 Then Angle = 0 Else If H1 >= 1 Then Angle = A180 _
        Else Angle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
    Else
        'Pitch angle
        If Status <= Controlled And Moon.Altitude < hMoonProximity Then
            X1 = AngleU + Moon.X / Moon.Distance: Y1 = AngleV + Moon.Y / Moon.Distance: Z1 = AngleW + Moon.Z / Moon.Distance 'Vector addition
            H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
            If H1 <= 0 Then Angle = 0 Else If H1 >= 1 Then Angle = A180 _
            Else Angle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
            End If
        Moon.AirSpeed = 0
        End If
    
    If GravityMode >= 3 Then

'Sun
        'Distance to Earth
        Earth.OrbitX = Earth.X - Sun.X: Earth.OrbitY = Earth.Y - Sun.Y: Earth.OrbitZ = Earth.Z - Sun.Z
        Earth.OrbitDistance = Sqr(Earth.OrbitX * Earth.OrbitX + Earth.OrbitY * Earth.OrbitY + Earth.OrbitZ * Earth.OrbitZ)
        'Distance to Moon
        Moon.ParentOrbitX = Moon.X - Sun.X: Moon.ParentOrbitY = Moon.Y - Sun.Y: Moon.ParentOrbitZ = Moon.Z - Sun.Z
        Moon.ParentOrbitDistance = Sqr(Moon.ParentOrbitX * Moon.ParentOrbitX + Moon.ParentOrbitY * Moon.ParentOrbitY + Moon.ParentOrbitZ * Moon.ParentOrbitZ)
        'Distance to Ship
        Sun.Distance = Sqr(Sun.X * Sun.X + Sun.Y * Sun.Y + Sun.Z * Sun.Z): Sun.Altitude = Sun.Distance - Sun.Radius
        'Gravity
        Sun.Gravity = Sun.GravityMass / Sun.Distance / Sun.Distance

        If GravityMode >= 4 Then
            
            'Distance to Sun
            If Sun.Distance < Sun.Diameter Then Zone = Proximity: If TerrainMode > 0 Then XYZ2LLA Sun, , , Sun.Altitude
            'Speed relative to the Sun center
            Sun.SpeedX = SpeedX: Sun.SpeedY = SpeedY: Sun.SpeedZ = SpeedZ
            Sun.Speed = Sqr(Sun.SpeedX * Sun.SpeedX + Sun.SpeedY * Sun.SpeedY + Sun.SpeedZ * Sun.SpeedZ)
            Sun.VerticalSpeed = (Sun.Distance - Sun.LastAltitude) / dT: Sun.HorizontalSpeed2 = Sun.Speed * Sun.Speed - Sun.VerticalSpeed * Sun.VerticalSpeed
            'Centripetal acceleration
            Sun.CentripetalAccel = Sun.HorizontalSpeed2 / Sun.Distance 'A=V²/R
            
'Mars
            'Distance to Sun
            Mars.OrbitX = Mars.X - Sun.X: Mars.OrbitY = Mars.Y - Sun.Y: Mars.OrbitZ = Mars.Z - Sun.Z
            Mars.OrbitDistance = Sqr(Mars.OrbitX * Mars.OrbitX + Mars.OrbitY * Mars.OrbitY + Mars.OrbitZ * Mars.OrbitZ)
            'Distance to Ship
            Mars.Distance = Sqr(Mars.X * Mars.X + Mars.Y * Mars.Y + Mars.Z * Mars.Z)
            Mars.Altitude = Mars.Distance - Mars.Radius
            'Speed relative to the center
            Mars.SpeedX = SpeedX - Mars.MainSpeedX: Mars.SpeedY = SpeedY - Mars.MainSpeedY: Mars.SpeedZ = SpeedZ - Mars.MainSpeedZ
            Mars.Speed = Sqr(Mars.SpeedX * Mars.SpeedX + Mars.SpeedY * Mars.SpeedY + Mars.SpeedZ * Mars.SpeedZ)
            Mars.VerticalSpeed = (Mars.Distance - Mars.LastAltitude) / dT: Mars.HorizontalSpeed2 = Mars.Speed * Mars.Speed - Mars.VerticalSpeed * Mars.VerticalSpeed
            'Centripetal acceleration
            Mars.CentripetalAccel = Mars.HorizontalSpeed2 / Mars.Distance 'A=V²/R
            'Gravity
            Mars.Gravity = Mars.GravityMass / Mars.Distance / Mars.Distance
            If Mars.Distance < Mars.Diameter Then
                'Ground altitude if available
                If TerrainMode > 0 Then
                    XYZ2LLA Mars, Y2, X2, Mars.Altitude
                    If TerrainMode = 2 Then
                        C1 = Int((X2 / Rad + 180) * 16 + 0.5): If C1 < 0 Then C1 = C1 + 5760 Else If C1 >= 5760 Then C1 = C1 - 5760
                        D1 = Int((Y2 / Rad + 90) * 16 + 0.5): If D1 < 0 Then D1 = 0 Else If D1 >= 2880 Then D1 = 2879
                        FloorAltitude = Mars.Terrain(C1, D1) * TerrainScale
                        End If
                    End If
                'Speed relative to the atmosphere with correction due to the axial tilt
                Mars.AirSpeedX = Mars.SpeedX - Mars.AngularSpeedW * Mars.Y * Mars.CosA
                Mars.AirSpeedY = Mars.SpeedY - Mars.AngularSpeedW * (Mars.Z * Mars.SinA - Mars.X * Mars.CosA)
                Mars.AirSpeedZ = Mars.SpeedZ + Mars.AngularSpeedW * Mars.Y * Mars.SinA
                Mars.AirSpeed = Sqr(Mars.AirSpeedX * Mars.AirSpeedX + Mars.AirSpeedY * Mars.AirSpeedY + Mars.AirSpeedZ * Mars.AirSpeedZ)
                AirSpeedX = Mars.AirSpeedX / Mars.AirSpeed: AirSpeedY = Mars.AirSpeedY / Mars.AirSpeed: AirSpeedZ = Mars.AirSpeedZ / Mars.AirSpeed
                AirSpeed = Mars.AirSpeed 'Air speed vector
                AltitudeX = Mars.X / Mars.Distance: AltitudeY = Mars.Y / Mars.Distance: AltitudeZ = Mars.Z / Mars.Distance: Altitude = Mars.Altitude
                GroundAltitude = Altitude - FloorAltitude 'Vertical vector
                VerticalSpeed = Mars.VerticalSpeed 'Vertical speed
                'Pitch angle
                X1 = AngleU + AltitudeX: Y1 = AngleV + AltitudeY: Z1 = AngleW + AltitudeZ 'Vector addition
                H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
                If H1 <= 0 Then Angle = 0 Else If H1 >= 1 Then Angle = A180 _
                Else Angle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
                If Mars.Altitude < Ion Then
                    'Atmosphere density, pressure and temperature due to the altitude
                    G1 = Mars.Altitude / 1000: C1 = Int(G1): If C1 < 0 Then C1 = 0 Else G1 = G1 - C1
                    With Mars.AirCurve(C1): AirDensity = .D + .D2 * G1: AirPressure = .P + .P2 * G1: AirTemperature = .T + .T2 * G1: End With
                    'Sound speed .. V=Sqr(G*R*T) G=1.29=Ratio of specific heats  R=191.277=Gas constant(m²/s²/ºK)  T=Temperature(ºK)
                    MachNumber = Sqr(AirTemperature * Mars.GasConstant * Mars.HeatRatio) '246.74733
                    'Drag coefficient curve
                    If DragN < 1 Then DragCf = 0.5 Else If DragN = 1 Then DragCf = DragCurve(0) _
                    Else A1 = AirSpeed / MachNumber / DragStep: C1 = Int(A1): If C1 >= DragN - 1 Then DragCf = DragCurve(DragN - 1) _
                    Else H1 = A1 - C1: G1 = 1 - H1: DragCf = DragCurve(C1) * G1 + DragCurve(C1 + 1) * H1 'Drag value interpolation
                    'Air speed angle
                    AirRollU = AirSpeedY * AltitudeZ - AirSpeedZ * AltitudeY: AirRollV = AirSpeedZ * AltitudeX - AirSpeedX * AltitudeZ: AirRollW = AirSpeedX * AltitudeY - AirSpeedY * AltitudeX 'Cross product
                    AirAngleV = Sqr(AirRollU * AirRollU + AirRollV * AirRollV + AirRollW * AirRollW)
                    If AirAngleV > 0 Then AirRollU = AirRollU / AirAngleV: AirRollV = AirRollV / AirAngleV: AirRollW = AirRollW / AirAngleV
                    If AirAngleV >= 1 Then AirAngleV = A90 Else If AirAngleV <= -1 Then AirAngleV = -A90 Else AirAngleV = Atn(AirAngleV / Sqr(1 - AirAngleV * AirAngleV)) 'ASin
                    If VerticalSpeed < 0 Then AirAngleV = A180 - AirAngleV
                    'Lift vector
                    AirLiftU = AirSpeedY * AirRollW - AirSpeedZ * AirRollV: AirLiftV = AirSpeedZ * AirRollU - AirSpeedX * AirRollW: AirLiftW = AirSpeedX * AirRollV - AirSpeedY * AirRollU 'Cross product
                    'Angle of attack
                    If Status <> Landing Then
                        AirAngle = AirAngleV - Angle
                    ElseIf Not IsLandingManeuver Then
                        AirAngle = A180
                    Else
                        X1 = AngleU - AirSpeedX: Y1 = AngleV - AirSpeedY: Z1 = AngleW - AirSpeedZ 'Vector addition
                        H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
                        If H1 <= 0 Then AirAngle = 0 Else If H1 >= 1 Then AirAngle = A180 _
                        Else AirAngle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
                        End If
                    'Heating due to atmosphere drag
                    Temperature = DragCf * AirDensity * AirSpeed * AirSpeed * AirSpeed 'QDrag=0.5*Cd*D*V³
                    If Mars.Altitude < Atm Then Zone = Atmosphere Else Zone = Ionosphere
                Else
                    AirDensity = 0: Zone = Proximity
                    End If
            Else
                'Pitch angle
                If Status <= Controlled And Mars.Altitude < hMarsProximity Then
                    X1 = AngleU + Mars.X / Mars.Distance: Y1 = AngleV + Mars.Y / Mars.Distance: Z1 = AngleW + Mars.Z / Mars.Distance 'Vector addition
                    H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
                    If H1 <= 0 Then Angle = 0 Else If H1 >= 1 Then Angle = A180 _
                    Else Angle = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
                    End If
                Mars.AirSpeed = 0
                End If
            
'Planets
            PlanetStats Mercury
            PlanetStats Venus
            'PlanetStats Mars
            PlanetStats Jupiter
            PlanetStats Saturn
            PlanetStats Uranus
            PlanetStats Neptune
            PlanetStats Pluto
            
            'Planet orbital zone
            If Sun.Distance <= Mars.OrbitDistance Then
                     If Sun.Distance > Earth.OrbitDistance Then Sector = ToMars _
                Else If Sun.Distance > Venus.OrbitDistance Then Sector = ToEarth _
                Else If Sun.Distance > Mercury.OrbitDistance Then Sector = ToVenus _
                                                            Else Sector = ToMercury
            Else
                     If Sun.Distance <= Jupiter.OrbitDistance Then Sector = ToJupiter _
                Else If Sun.Distance <= Saturn.OrbitDistance Then Sector = ToSaturn _
                Else If Sun.Distance <= Uranus.OrbitDistance Then Sector = ToUranus _
                Else If Sun.Distance <= Neptune.OrbitDistance Then Sector = ToNeptune _
                Else If Sun.Distance <= Pluto.OrbitDistance Then Sector = ToPluto _
                                                            Else Sector = ToObject
                End If
            
            End If
            
        End If
    
'Ship static temperature
    'QAlbedo+QInfrared
    If GravityMode >= 3 Then
        C1 = -1
        If IsSunShadow Then X1 = -Sun.X / Sun.Distance: Y1 = -Sun.Y / Sun.Distance: Z1 = -Sun.Z / Sun.Distance
        If Sun.Distance < Earth.OrbitDistance Then Temperature = Temperature + Earth.Albedo / Earth.Distance / Earth.Distance _
        Else Temperature = Temperature + Earth.Infrared / Earth.Distance / Earth.Distance: If IsSunShadow Then _
        If Angle3D(Earth.OrbitX / Earth.OrbitDistance, Earth.OrbitY / Earth.OrbitDistance, Earth.OrbitZ / Earth.OrbitDistance, _
            X1, Y1, Z1) < Atn(Earth.Radius / Earth.OrbitDistance) Then C1 = 0
        If Sun.Distance < Moon.ParentOrbitDistance Then Temperature = Temperature + Moon.Albedo / Moon.Distance / Moon.Distance _
        Else Temperature = Temperature + Moon.Infrared / Moon.Distance / Moon.Distance: If IsSunShadow Then _
        If Angle3D(Moon.ParentOrbitX / Moon.ParentOrbitDistance, Moon.ParentOrbitY / Moon.ParentOrbitDistance, Moon.ParentOrbitZ / Moon.ParentOrbitDistance, _
            X1, Y1, Z1) < Atn(Moon.Radius / Moon.ParentOrbitDistance) Then C1 = 0
        If GravityMode >= 4 Then
            If Sun.Distance < Mercury.OrbitDistance Then Temperature = Temperature + Mercury.Albedo / Mercury.Distance / Mercury.Distance _
            Else Temperature = Temperature + Mercury.Infrared / Mercury.Distance / Mercury.Distance: If IsSunShadow Then _
            If Angle3D(Mercury.OrbitX / Mercury.OrbitDistance, Mercury.OrbitY / Mercury.OrbitDistance, Mercury.OrbitZ / Mercury.OrbitDistance, _
                X1, Y1, Z1) < Atn(Mercury.Radius / Mercury.OrbitDistance) Then C1 = 0
            If Sun.Distance < Venus.OrbitDistance Then Temperature = Temperature + Venus.Albedo / Venus.Distance / Venus.Distance _
            Else Temperature = Temperature + Venus.Infrared / Venus.Distance / Venus.Distance: If IsSunShadow Then _
            If Angle3D(Venus.OrbitX / Venus.OrbitDistance, Venus.OrbitY / Venus.OrbitDistance, Venus.OrbitZ / Venus.OrbitDistance, _
                X1, Y1, Z1) < Atn(Venus.Radius / Venus.OrbitDistance) Then C1 = 0
            If Sun.Distance < Mars.OrbitDistance Then Temperature = Temperature + Mars.Albedo / Mars.Distance / Mars.Distance _
            Else Temperature = Temperature + Mars.Infrared / Mars.Distance / Mars.Distance: If IsSunShadow Then _
            If Angle3D(Mars.OrbitX / Mars.OrbitDistance, Mars.OrbitY / Mars.OrbitDistance, Mars.OrbitZ / Mars.OrbitDistance, _
                X1, Y1, Z1) < Atn(Mars.Radius / Mars.OrbitDistance) Then C1 = 0
            If Sun.Distance < Jupiter.OrbitDistance Then Temperature = Temperature + Jupiter.Albedo / Jupiter.Distance / Jupiter.Distance _
            Else Temperature = Temperature + Jupiter.Infrared / Jupiter.Distance / Jupiter.Distance: If IsSunShadow Then _
            If Angle3D(Jupiter.OrbitX / Jupiter.OrbitDistance, Jupiter.OrbitY / Jupiter.OrbitDistance, Jupiter.OrbitZ / Jupiter.OrbitDistance, _
                X1, Y1, Z1) < Atn(Jupiter.Radius / Jupiter.OrbitDistance) Then C1 = 0
            If Sun.Distance < Saturn.OrbitDistance Then Temperature = Temperature + Saturn.Albedo / Saturn.Distance / Saturn.Distance _
            Else Temperature = Temperature + Saturn.Infrared / Saturn.Distance / Saturn.Distance: If IsSunShadow Then _
            If Angle3D(Saturn.OrbitX / Saturn.OrbitDistance, Saturn.OrbitY / Saturn.OrbitDistance, Saturn.OrbitZ / Saturn.OrbitDistance, _
                X1, Y1, Z1) < Atn(Saturn.Radius / Saturn.OrbitDistance) Then C1 = 0
            If Sun.Distance < Uranus.OrbitDistance Then Temperature = Temperature + Uranus.Albedo / Uranus.Distance / Uranus.Distance _
            Else Temperature = Temperature + Uranus.Infrared / Uranus.Distance / Uranus.Distance: If IsSunShadow Then _
            If Angle3D(Uranus.OrbitX / Uranus.OrbitDistance, Uranus.OrbitY / Uranus.OrbitDistance, Uranus.OrbitZ / Uranus.OrbitDistance, _
                X1, Y1, Z1) < Atn(Uranus.Radius / Uranus.OrbitDistance) Then C1 = 0
            If Sun.Distance < Neptune.OrbitDistance Then Temperature = Temperature + Neptune.Albedo / Neptune.Distance / Neptune.Distance _
            Else Temperature = Temperature + Neptune.Infrared / Neptune.Distance / Neptune.Distance: If IsSunShadow Then _
            If Angle3D(Neptune.OrbitX / Neptune.OrbitDistance, Neptune.OrbitY / Neptune.OrbitDistance, Neptune.OrbitZ / Neptune.OrbitDistance, _
                X1, Y1, Z1) < Atn(Neptune.Radius / Neptune.OrbitDistance) Then C1 = 0
            If Sun.Distance < Pluto.OrbitDistance Then Temperature = Temperature + Pluto.Albedo / Pluto.Distance / Pluto.Distance _
            Else Temperature = Temperature + Pluto.Infrared / Pluto.Distance / Pluto.Distance: If IsSunShadow Then _
            If Angle3D(Pluto.OrbitX / Pluto.OrbitDistance, Pluto.OrbitY / Pluto.OrbitDistance, Pluto.OrbitZ / Pluto.OrbitDistance, _
                X1, Y1, Z1) < Atn(Pluto.Radius / Pluto.OrbitDistance) Then C1 = 0
            End If
        If C1 Then Temperature = Temperature + Sun.Albedo / Sun.Distance / Sun.Distance: GoTo SolarWind 'QSun
    Else
        Temperature = Temperature + Sun.Albedo / AU / AU + Earth.Albedo / Earth.Distance / Earth.Distance + _
            Moon.Albedo / Moon.Distance / Moon.Distance
SolarWind:
        If AirDensity = 0 Then SolarWind = 56000000000000# / Sun.Distance / Sun.Distance Else SolarWind = 0 'Solar wind pressure
        End If
    Temperature = ((Temperature / 4 + 3.12659812624336E-06 + Power / FullArea) * Emissivity / Sigma) ^ 0.25 'QElectric=Power  QSpace=Sigma*T^4 .. T=2.725K
    If AirDensity > 0 Then If AirDensity > 1 Then Temperature = AirTemperature _
    Else Temperature = AirTemperature * AirDensity + Temperature * (1 - AirDensity) 'Atmosphere thermal conductivity emulation
    
'Planet proximity and collision detection
    If Earth.Altitude < hEarthProximity Then
        Target = ToEarth: If Earth.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Earth, FloorAltitude
        If Moon.Altitude < hMoonProximity Then
            Target = ToMoon: If Moon.Altitude < FloorAltitude - Ground Then If IsCollision Then _
                IsPlanetCollision = 1: SetAlt Moon, FloorAltitude
            End If
    ElseIf GravityMode <= 3 Then
        GoTo TargetSun
    ElseIf Mercury.Altitude < hMercuryProximity Then
        Target = ToMercury: If Mercury.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Mercury, FloorAltitude
    ElseIf Venus.Altitude < hVenusProximity Then
        Target = ToVenus: If Venus.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Venus, FloorAltitude
    ElseIf Mars.Altitude < hMarsProximity Then
        Target = ToMars: If Mars.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Mars, FloorAltitude
    ElseIf Jupiter.Altitude < hJupiterProximity Then
        Target = ToJupiter: If Jupiter.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Jupiter, FloorAltitude
    ElseIf Saturn.Altitude < hSaturnProximity Then
        Target = ToSaturn: If Saturn.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Saturn, FloorAltitude
    ElseIf Uranus.Altitude < hUranusProximity Then
        Target = ToUranus: If Uranus.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Uranus, FloorAltitude
    ElseIf Neptune.Altitude < hNeptuneProximity Then
        Target = ToNeptune: If Neptune.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Neptune, FloorAltitude
    ElseIf Pluto.Altitude < hPlutoProximity Then
        Target = ToPluto: If Pluto.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Pluto, FloorAltitude
    Else
TargetSun:
        Target = ToSun: If Sun.Altitude < FloorAltitude - Ground Then If IsCollision Then _
            IsPlanetCollision = 1: SetAlt Sun, FloorAltitude
        End If
    
    'Adaptive time step
    If IsAdaptive Then
        If LastTarget <> Target Then _
            If Target = ToSun Then dT = dT * 10 Else If LastTarget = ToSun Then dT = dT / 10
        If LastZone <> Zone Then _
            If Zone = VOID Then dT = dT * 10 Else If LastZone = VOID Then dT = dT / 10
        End If
    
    End Sub

Sub PlanetKinetics(Pl As Planet)
    Pl.GravityMassDistance = Sun.GravityMass / Pl.OrbitDistance / Pl.OrbitDistance / Pl.OrbitDistance * dT
    Pl.MainSpeedX = Pl.MainSpeedX - Pl.GravityMassDistance * Pl.OrbitX
    Pl.MainSpeedY = Pl.MainSpeedY - Pl.GravityMassDistance * Pl.OrbitY
    Pl.MainSpeedZ = Pl.MainSpeedZ - Pl.GravityMassDistance * Pl.OrbitZ
    Pl.X = Pl.X + (Pl.MainSpeedX - SpeedX) * dT
    Pl.Y = Pl.Y + (Pl.MainSpeedY - SpeedY) * dT
    Pl.Z = Pl.Z + (Pl.MainSpeedZ - SpeedZ) * dT
    Pl.AngleW = Pl.AngleW + Pl.AngularSpeedW * dT
    End Sub

Sub PlanetStats(Pl As Planet)
    'Distance to Sun
    Pl.OrbitX = Pl.X - Sun.X: Pl.OrbitY = Pl.Y - Sun.Y: Pl.OrbitZ = Pl.Z - Sun.Z
    Pl.OrbitDistance = Sqr(Pl.OrbitX * Pl.OrbitX + Pl.OrbitY * Pl.OrbitY + Pl.OrbitZ * Pl.OrbitZ)
    'Distance to Ship
    Pl.Distance = Sqr(Pl.X * Pl.X + Pl.Y * Pl.Y + Pl.Z * Pl.Z): Pl.Altitude = Pl.Distance - Pl.Radius
    If Pl.Distance < Pl.Diameter Then Zone = Proximity: If TerrainMode > 0 Then XYZ2LLA Pl, , , Pl.Altitude
    'Relative speed to the center
    Pl.SpeedX = SpeedX - Pl.MainSpeedX: Pl.SpeedY = SpeedY - Pl.MainSpeedY: Pl.SpeedZ = SpeedZ - Pl.MainSpeedZ
    Pl.Speed = Sqr(Pl.SpeedX * Pl.SpeedX + Pl.SpeedY * Pl.SpeedY + Pl.SpeedZ * Pl.SpeedZ)
    Pl.VerticalSpeed = (Pl.Distance - Pl.LastAltitude) / dT
    Pl.HorizontalSpeed2 = Pl.Speed * Pl.Speed - Pl.VerticalSpeed * Pl.VerticalSpeed
    'Centripetal acceleration
    Pl.CentripetalAccel = Pl.HorizontalSpeed2 / Pl.Distance 'A=V²/R
    'Gravity
    Pl.Gravity = Pl.GravityMass / Pl.Distance / Pl.Distance
    End Sub

Sub StageKinetics(S As StagePos)
    Dim A1 As Double, H1 As Double
    H1 = Sqr(S.X * S.X + S.Y * S.Y + S.Z * S.Z): A1 = H1 - Earth.Radius
    If A1 > 0 Then
        G1 = Earth.GravityMass / H1 / H1 / H1
        S.SpeedX = S.SpeedX - G1 * S.X * dT
        S.SpeedY = S.SpeedY - G1 * S.Y * dT
        S.SpeedZ = S.SpeedZ - G1 * S.Z * dT
        S.X = S.X + S.SpeedX * dT
        S.Y = S.Y + S.SpeedY * dT
        S.Z = S.Z + S.SpeedZ * dT
    Else
        A1 = Earth.AngularSpeedW * dT
        Rotate -Earth.AngleU, S.Y, S.Z: Rotate -Earth.AngleU, S.SpeedY, S.SpeedZ
        Rotate A1, S.X, S.Y: Rotate A1, S.SpeedX, S.SpeedY
        Rotate Earth.AngleU, S.Y, S.Z: Rotate Earth.AngleU, S.SpeedY, S.SpeedZ
        End If
    End Sub

Sub AtmData(ByVal X0 As Long, ByVal Y0 As Long, R As Double, Optional ByVal Init As Boolean)
    Z0 = AtmR(X0, Y0, Z6): Z5 = Z0 + 1: G1 = AtmA(X0, Y0, Z5)
    If Z5 >= AtmN And Z2 > G1 Then
        R1 = G1 / 1000: C1 = Int(R1): R1 = R1 - C1
        With Earth.AirCurve(C1): H1 = (.P + .P2 * R1) / AtmP(Z5) / 100
        A1 = (.T + .T2 * R1) / AtmT(X0, Y0, Z5): End With
        R1 = Z2 / 1000: C1 = Int(R1): R1 = R1 - C1
        With Earth.AirCurve(C1)
        If Init Then
            AirPressure = (.P + .P2 * R1) / H1 * R
            AirTemperature = (.T + .T2 * R1) / A1 * R
        Else
            AirPressure = AirPressure + (.P + .P2 * R1) / H1 * R
            AirTemperature = AirTemperature + (.T + .T2 * R1) / A1 * R
            End If
        End With
        R1 = 0: If Z2 < AtmL2 Then R2 = 1 - (Z2 - G1) / (AtmL2 - G1) Else R2 = 0
    Else
        R1 = AtmA(X0, Y0, Z0): R2 = (Z2 - R1) / (G1 - R1): R1 = 1 - R2
        If Init Then
            AirPressure = ((AtmP(Z0) * R1 + AtmP(Z5) * R2) * 100) * R
            AirTemperature = (AtmT(X0, Y0, Z0) * R1 + AtmT(X0, Y0, Z5) * R2) * R
        Else
            AirPressure = AirPressure + ((AtmP(Z0) * R1 + AtmP(Z5) * R2) * 100) * R
            AirTemperature = AirTemperature + (AtmT(X0, Y0, Z0) * R1 + AtmT(X0, Y0, Z5) * R2) * R
            End If
        End If
    If Init Then
        AirHumidity = (AtmH(X0, Y0, Z0) * R1 + AtmH(X0, Y0, Z5) * R2) * R
        AirCloudWater = (AtmW(X0, Y0, Z0) * R1 + AtmW(X0, Y0, Z5) * R2) * R
        AirWindU = (AtmX(X0, Y0, Z0) * R1 + AtmX(X0, Y0, Z5) * R2) * R
        AirWindV = (AtmY(X0, Y0, Z0) * R1 + AtmY(X0, Y0, Z5) * R2) * R
        AirWindW = (AtmZ(X0, Y0, Z0) * AtmD(Z0) * R1 + AtmZ(X0, Y0, Z5) * AtmD(Z5) * R2) * R
    Else
        AirHumidity = AirHumidity + (AtmH(X0, Y0, Z0) * R1 + AtmH(X0, Y0, Z5) * R2) * R
        AirCloudWater = AirCloudWater + (AtmW(X0, Y0, Z0) * R1 + AtmW(X0, Y0, Z5) * R2) * R
        AirWindU = AirWindU + (AtmX(X0, Y0, Z0) * R1 + AtmX(X0, Y0, Z5) * R2) * R
        AirWindV = AirWindV + (AtmY(X0, Y0, Z0) * R1 + AtmY(X0, Y0, Z5) * R2) * R
        AirWindW = AirWindW + (AtmZ(X0, Y0, Z0) * AtmD(Z0) * R1 + AtmZ(X0, Y0, Z5) * AtmD(Z5) * R2) * R
        End If
    End Sub

Sub LogStart()
    If (LogOption And Not LogSeparator) = LogToFile Then Exit Sub
    LogStop
    LogHandle = CreateFile(CStr(LogsDir & ShortDateTime & ".csv"), &H40000000, 1, , 2): _
        If LogHandle = -1 Then LogHandle = 0: Exit Sub 'GENERIC_WRITE=&H40000000 CREATE_ALWAYS=2 FILE_SHARE_READ=1
    Message "Log file recording started", 2000
    If LogOption And LogDate Then StrAdd "Date[UTC];Time[UTC];"
    If LogOption And LogTime Then StrAdd "ElapsedTime[s];"
    If LogOption And LogCoordinates Then StrAdd "Latitude[degrees];Longitude[degrees];"
    If LogOption And LogCoordinates3D Then StrAdd "CoordinateX[m];CoordinateY[m];CoordinateZ[m];"
    If LogOption And LogAltitude Then StrAdd "Altitude[m];"
    If LogOption And LogGround Then StrAdd "GroundAltitude[m];"
    If LogOption And LogDistance Then StrAdd "Distance[m];"
    If LogOption And LogDistance3D Then StrAdd "DistanceX[m];DistanceY[m];DistanceZ[m];"
    If LogOption And LogSpeed Then StrAdd "Speed[m/s];"
    If LogOption And LogSpeed3D Then StrAdd "SpeedX[m/s];SpeedY[m/s];SpeedZ[m/s];"
    If LogOption And LogAirSpeed Then StrAdd "AirSpeed[m/s];"
    If LogOption And LogAirSpeed3D Then StrAdd "AirSpeedX[m/s];AirSpeedY[m/s];AirSpeedZ[m/s];"
    If LogOption And LogThrust Then StrAdd "Thrust[N];"
    If LogOption And LogAcceleration Then StrAdd "Acceleration[m/s2];"
    If LogOption And LogAcceleration3D Then StrAdd "AccelerationX[m/s2];AccelerationY[m/s2];AccelerationZ[m/s2];"
    If LogOption And LogInertial Then StrAdd "Inertial[m/s2];"
    If LogOption And LogInertial3D Then StrAdd "InertialX[m/s2];InertialY[m/s2];InertialZ[m/s2];"
    If LogOption And LogAirDrag Then StrAdd "AirDrag[m/s2];"
    If LogOption And LogAirDrag3D Then StrAdd "AirDragX[m/s2];AirDragY[m/s2];AirDragZ[m/s2];"
    If LogOption And LogGravity Then StrAdd "Gravity[m/s2];"
    If LogOption And LogGravity3D Then StrAdd "GravityX[m/s2];GravityY[m/s2];GravityZ[m/s2];"
    If LogOption And LogMass Then StrAdd "Mass[kg];"
    If LogOption And LogTemperature Then StrAdd "Temperature[K];"
    If LogOption And LogStations Then StrAdd "Links;"
    If LogOption And LogAirspaces Then StrAdd "Airspaces;"
    StrLen = StrLen - 1: StrAdd vbCrLf
    If (LogOption And LogSeparator) = 0 Then StrReplace 59, 44 ',44 .46 :58 ;59
    WriteFileX LogHandle, VarPtr(StrBuffer(0)), StrLen: StrClear
    End Sub

Sub LogStop()
    If LogHandle Then CloseHandle LogHandle: LogHandle = 0
    End Sub

Sub LogSimulation()
    If Target = ToEarth Then LogSimulationX Earth _
    Else If Target = ToMoon Then LogSimulationX Moon _
    Else If GravityMode <= 3 Then LogSimulationX Earth _
    Else If Target = ToMercury Then LogSimulationX Mercury _
    Else If Target = ToVenus Then LogSimulationX Venus _
    Else If Target = ToMars Then LogSimulationX Mars _
    Else If Target = ToJupiter Then LogSimulationX Jupiter _
    Else If Target = ToSaturn Then LogSimulationX Saturn _
    Else If Target = ToUranus Then LogSimulationX Uranus _
    Else If Target = ToNeptune Then LogSimulationX Neptune _
    Else If Target = ToPluto Then LogSimulationX Pluto _
    Else LogSimulationX Sun
    End Sub

Sub LogSimulationX(Pl As Planet)
    If LogOption And LogDate Then _
        StrAdd J2ksDate(Time + T) & ";" & J2ksTime(Time + T) & ";"  'Date[UTC],Time[UTC]
    If LogOption And LogTime Then StrAdd T & ";"                    'ElapsedTime[s]
    If LogOption And LogCoordinates Then XYZ2LLA Pl, Y2, X2, Z2: _
        StrAdd Y2 / Rad & ";" & X2 / Rad & ";"                      'Latitude[degrees],Longitude[degrees]
    If LogOption And LogCoordinates3D Then XYZ2LLA Pl, Y2, X2, Z2: _
        LLA2XYZ Pl, Y2, X2, Z2, X1, Y1, Z1: _
        StrAdd X1 & ";" & Y1 & ";" & Z1 & ";"                       'CoordinateXYZ[m]
    If LogOption And LogAltitude Then StrAdd Pl.Altitude & ";"      'Altitude[m]
    If LogOption And LogGround Then StrAdd GroundAltitude & ";"     'GroundAltitude[m]
    If LogOption And LogDistance Then StrAdd Pl.Distance & ";"      'Distance[m]
    If LogOption And LogDistance3D Then StrAdd _
        -Pl.X & ";" & -Pl.Y & ";" & -Pl.Z & ";"                     'DistanceXYZ[m]
    If LogOption And LogSpeed Then StrAdd Pl.Speed & ";"            'Speed[m/s]
    If LogOption And LogSpeed3D Then StrAdd _
        Pl.SpeedX & ";" & Pl.SpeedY & ";" & Pl.SpeedZ & ";"         'SpeedXYZ[m/s]
    If LogOption And LogAirSpeed Then StrAdd Pl.AirSpeed & ";"      'AirSpeed[m/s]
    If LogOption And LogAirSpeed3D Then StrAdd _
        Pl.AirSpeedX & ";" & Pl.AirSpeedY & ";" & Pl.AirSpeedZ & ";" 'AirSpeedXYZ[m/s]
    If LogOption And LogThrust Then StrAdd Thrust * Throttle & ";"  'Thrust[N]
    If LogOption And LogAcceleration Then StrAdd Accel & ";"        'Acceleration[m/s2]
    If LogOption And LogAcceleration3D Then StrAdd _
        AccelX & ";" & AccelY & ";" & AccelZ & ";"                  'AccelerationXYZ[m/s2]
    If LogOption And LogInertial Then StrAdd InertialAccel & ";"    'Inertial[m/s2]
    If LogOption And LogInertial3D Then StrAdd _
        InertialAccelX & ";" & InertialAccelY & ";" & InertialAccelZ & ";" 'InertialXYZ[m/s2]
    If LogOption And LogAirDrag Then StrAdd Drag & ";"              'AirDrag[m/s2]
    If LogOption And LogAirDrag3D Then StrAdd _
        Drag * AirSpeedX & ";" & Drag * AirSpeedY & ";" & Drag * AirSpeedZ & ";" 'AirDragXYZ[m/s2]
    If LogOption And LogGravity Then StrAdd Pl.Gravity & ";"        'Gravity[m/s2]
    If LogOption And LogGravity3D Then G1 = Pl.Gravity / Pl.Distance: _
        StrAdd G1 * Pl.X & ";" & G1 * Pl.Y & ";" & G1 * Pl.Z & ";"  'GravityXYZ[m/s2]
    If LogOption And LogMass Then StrAdd Mass & ";"                 'Mass[kg]
    If LogOption And LogTemperature Then StrAdd Temperature & ";"   'Temperature[K]
    If LogOption And LogStations Then StrAdd IsTargetN & ";"        'Links
    If LogOption And LogAirspaces Then StrAdd IsAirspaceN & ";"     'Airspaces
    StrLen = StrLen - 1: StrAdd vbCrLf
    If (LogOption And LogSeparator) = 0 Then StrReplace: StrReplace 59, 44 '[,]44 [.]46 [:]58 [;]59
    WriteFileX LogHandle, VarPtr(StrBuffer(0)), StrLen: StrClear
    End Sub

'__Data_and_graph_functions____________________________________________________

Sub RedimGraph(ByVal N As Long)
    GraphicMax = N: If GraphicN >= GraphicMax Then GraphicN = GraphicMax - 1
    ReDim Preserve Graphic(GraphicMax - 1): Plot , 1
    End Sub

Sub SaveGraph()
    Dim C As Long, N As Long, S As String, T1 As Double, Q As Boolean, Name As String
    Static Op As OpenFileName
    Name = GetSaveFile(Op, Setup.Preset & ".csv", FileCSV, "csv", LogsDir)
    If Name = "" Then Exit Sub
    On Error Resume Next
    Open Name For Output As 1: If FreeFile = 1 Then Exit Sub
    If KMLPathN > 0 And T = 0 Then T1 = KMLPath(0).Time: If T1 <> 0 Then Q = True
    If Not Q Then
        S = "#, Time[s], Altitude[m], Airspeed[m/s], Speed[m/s], Mass[kg], Thrust[N], Acceleration[m/s²], Gravity[m/s²], Drag[m/s²], Temperature[K], Stations, Airspaces"
        If LogOption And LogSeparator Then Print #1, Replace(S, ",", ";") Else Print #1, S
        For N = 0 To GraphicN - 1: With Graphic(N)
            S = N & "," & N & "," & NumD(.Altitude) & "," & NumD(.AirSpeed) & "," & NumD(.Speed) & "," & _
                NumD(.Mass) & "," & NumD(.Thrust) & "," & NumD(.Accel) & "," & NumD(.Gravity) & "," & _
                NumD(.Drag) & "," & NumD(.Temperature) & "," & .Links & "," & .Airspaces
            If LogOption And LogSeparator Then Print #1, ToSSV(S) Else Print #1, S
            End With: Next
    Else
        S = "#, ElapsedTime[s], Julian2000Seconds[s], DateUTC[YYYY-MM-DD], TimeUTC[HH:MM:SS], Latitude[deg], Longitude[deg], Altitude[m], HorizontalSpeed[m/s], VerticalSpeed[m/s], Density[kg/m³], Pressure[Pa], Temperature[K], RelativeHumidity[%], CloudWater[kg/kg], WindSpeedX[m/s), WindSpeedY[m/s), WindSpeedZ[m/s), Name"
        If LogOption And LogSeparator Then Print #1, Replace(S, ",", ";") Else Print #1, S
        C = 0: For N = 0 To KMLPathN - 1: With KMLPath(N)
            S = N & "," & _
                NumD(.Time - T1) & "," & NumD(.Time) & "," & J2ksDate(.Time) & "," & J2ksTime(.Time) & "," & _
                NumD(.Lat / Rad) & "," & NumD(.Lon / Rad) & "," & NumD(.Alt) & "," & _
                NumD(.SpeedH) & "," & NumD(.SpeedV) & "," & NumD(.Density) & "," & NumD(.Pressure) & "," & _
                NumD(.Temperature) & "," & NumD(.Humidity) & "," & NumD(.CloudWater) & "," & _
                NumD(.WindX) & "," & NumD(.WindY) & "," & NumD(.WindZ)
            If KMLPoint(C).Time = .Time And KMLPoint(C).Lat = .Lat And KMLPoint(C).Lon = .Lon And _
                KMLPoint(C).Alt = .Alt Then S = S & "," & KMLPoint(C).Name: C = C + 1
            If LogOption And LogSeparator Then Print #1, ToSSV(S) Else Print #1, S
            End With: Next
        End If
    Close
    On Error GoTo 0
    End Sub

Sub CopyGraph()
    Dim Q As Boolean
    Clipboard.Clear
    If KMLPathN > 0 And T = 0 Then If KMLPath(0).Time <> 0 Then Q = True
    If Not Q Then
        With Graphic(GraphicP)
        Clipboard.SetText _
            "Time" & vbTab & ToDhmsEx(GraphicP) & vbCrLf & _
            "Speed" & vbTab & ToSpeed(.Speed) & vbCrLf & _
            vbTab & ToAirSpeed(.AirSpeed) & vbCrLf & _
            "Altitude" & vbTab & ToAlt(.Altitude) & vbCrLf & _
            "Mass" & vbTab & ToMass(.Mass) & vbCrLf & _
            "Gravity" & vbTab & ToForceG(.Gravity) & vbCrLf & _
            "Thrust" & vbTab & ToForce(.Thrust) & vbCrLf & _
            vbTab & ToForceG(.Thrust, .Mass) & vbCrLf & _
            "Accel" & vbTab & ToForceG(.Accel) & vbCrLf & _
            "Drag" & vbTab & ToForceG(.Drag) & vbCrLf & _
            "Temp" & vbTab & ToTemp(.Temperature) & vbCrLf & _
            "Links" & vbTab & CStr(.Links) & vbCrLf & _
            "Areas" & vbTab & CStr(.Airspaces) & vbCrLf
        End With
    Else
        With KMLPath(KMLPathP)
        Clipboard.SetText _
            "Time" & vbTab & J2ksTime(.Time) & vbCrLf & _
            vbTab & ToHmsEx(.Time - KMLPath(0).Time) & vbCrLf & _
            "Lat" & vbTab & ToLat(.Lat, FLong, Degrees) & vbCrLf & _
            "Lon" & vbTab & ToLon(.Lon, FLong, Degrees) & vbCrLf & _
            "Alt" & vbTab & ToAlt(.Alt, FShort) & vbCrLf & _
            "Speed" & vbTab & ToAirSpeed(.SpeedH) & vbCrLf & _
            vbTab & ToAlt(.SpeedV, FShort) & "/s" & vbCrLf & _
            "Density" & vbTab & ToDensity(.Density, FShort) & vbCrLf & _
            "Press" & vbTab & ToPressure(.Pressure, FShort) & vbCrLf & _
            "Temp" & vbTab & ToTemp(.Temperature) & vbCrLf & _
            "RH" & vbTab & NumX(.Humidity, FMini) & "%" & vbCrLf & _
            "Water" & vbTab & ToMass(.CloudWater, FShort) & IIf(Units, "/lb", "/kg") & vbCrLf
        End With
        End If
    End Sub

Sub ToolTipGraph(X As Single, Y As Single)
    Dim N As Long, S As String, Q As Boolean
    With Graph.Pic
    If X < .TextWidth(Tab3) * 0.9 Then
        N = CLng(Int(Y / .TextHeight("")))
        If KMLPathN > 0 And T = 0 Then If KMLPath(0).Time <> 0 Then Q = True
        If Not Q Then
            If N >= 0 And N <= UBound(GraphToolTip1) Then S = GraphToolTip1(N)
        Else
            If N >= 0 And N <= UBound(GraphToolTip2) Then S = GraphToolTip2(N)
            End If
        End If
    Tip.Update S
    End With
    End Sub

Sub ToolTipData(X As Single, Y As Single)
    Dim N As Long, S As String
    With Data.Pic
    N = CLng(Int(Y / .TextHeight("")))
    Select Case DataMode
    Case 1: If N >= 0 And N <= UBound(DataToolTip1) Then S = DataToolTip1(N)
    Case 2: N = BitPosEx(N, DataTool, DataToolX, DataToolBits)
            If N >= 0 And N <= UBound(DataToolTip2) Then S = DataToolTip2(N)
    Case 3: If N >= 0 And N <= UBound(DataToolTip3) Then S = DataToolTip3(N)
        End Select
    Tip.Update S
    End With
    End Sub

Sub InitToolTips()
    
    'Data panel compact
    StrAdd _
        "Current phase of mission|" & _
        "Simulation elapsed time|" & _
        "Simulation time step, scale of time and render frames per second"
    DataToolTip1 = Split(StrAdd, "|")
    
    'Data panel wide
    StrAdd _
        "Current stage on command and label for this stage|" & _
        "Simulation time step, scale of time and elapsed time|" & _
        "Date and time in UTC and render frames per second|" & _
        "Speed relative to the Earth center and relative to the atmosphere|" & _
        "Speed relative to the nearest planet center and relative to its surface|" & _
        "Vertical speed relative to the nearest planet and gravity minus the centripetal acceleration|" & _
        "Orbital speed for the current altitude and percent reached|" & _
        "Escape velocity for the current altitude and percent reached|" & _
        "Orbital periapsis, semi-major axis and apoapsis|" & _
        "Orbital inclination, eccentricity and period|" & _
        "Orbital argument of periapsis, longitude of ascending node, mean anomaly and true anomaly|" & _
        "Altitude relative to the Earth center and above the main level|" & _
        "Altitude relative to the the nearest planet center and above the main level|" & _
        "Gravity G-Force and acceleration felt by the spaceship due to the Earth|" & _
        "Gravity G-Force and acceleration felt by the spaceship due to the nearest planet|" & _
        "Total G-Force and acceleration felt by the spaceship|" & _
        "G-Force and acceleration felt by the spaceship without the gravity|" & _
        "Drag G-Force and deceleration caused by the friction with the atmosphere|" & _
        "Lift G-Force and acceleration generated by the spacecraft wings and main body|" & _
        "Spacecraft pitch angle relative to the horizontal, deviation angle from the target, angle of attack and attitude thrust|" & _
        "Earth magnetic field declination, inclination and strength|"
    StrAdd _
        "Atmosphere density and pressure|" & _
        "Atmosphere temperature, relative humidity and wind heading|" & _
        "Atmosphere vertical and horizontal wind speed|" & _
        "Propellant burn rate and spacecraft static temperature|" & _
        "Thrust due to the engine, G-Force and percent of throttle|" & _
        "Total spaceship mass, burned mass and remaining percent of propellant|" & _
        "Maximum and minimum speed relative to the Earth center|" & _
        "Maximum and minimum altitude relative to the Earth main level|" & _
        "Maximum and minimum altitude relative to the Moon main level|" & _
        "Maximum G-Force and acceleration felt by the spaceship|" & _
        "Total impulse, specific orbital energy and maximum attitude control thrust|" & _
        "Delta-V due to the engine thrust and to the atmosphere drag|" & _
        "Delta-V due to the gravity drag and to the atmosphere lift|" & _
        "Accelerating and decelerating time during takeoff and landing phases|" & _
        "Collision probability between the spacecraft and the satellites or the asteroids|" & _
        "Latitude and longitude coordinates relative to the nearest planet|" & _
        "Altitude relative to the main level and camera distance|" & _
        "Altitude relative to the ground level and ground level altitude|" & _
        "Camera yaw, pitch and roll angles and altitude relative to the nearest planet"
    DataToolTip2 = Split(StrAdd, "|")
    
    'Data panel large
    StrAdd _
        "Current phase of mission|" & _
        "Simulation elapsed time|" & _
        "Date and time in UTC|" & _
        "Simulation time step, scale of time and render frames per second|" & _
        "Current stage on command|" & _
        "Speed relative to the Earth center|" & _
        "Speed relative to the atmosphere|" & _
        "Speed relative to the nearest planet center|" & _
        "Speed relative to the nearest planet surface|" & _
        "Vertical speed relative to the nearest planet|" & _
        "Gravity minus the centripetal acceleration|" & _
        "Orbital speed for the current altitude|" & _
        "Percent reached of the orbital speed|" & _
        "Escape velocity for the current altitude|" & _
        "Percent reached of the escape velocity|"
    StrAdd _
        "Altitude above the Earth main level|" & _
        "Altitude relative to the Earth center|" & _
        "Altitude above the nearest planet main level|" & _
        "Altitude relative to the the nearest planet center|" & _
        "Gravity G-Force felt by the spaceship due to the Earth|" & _
        "Gravity acceleration felt by the spaceship due to the Earth|" & _
        "Gravity G-Force felt by the spaceship due to the nearest planet|" & _
        "Gravity acceleration felt by the spaceship due to the nearest planet|" & _
        "Total G-Force felt by the spaceship|" & _
        "Total acceleration felt by the spaceship|" & _
        "Drag G-Force caused by the friction with the atmosphere|" & _
        "Drag deceleration caused by the friction with the atmosphere|" & _
        "Lift G-Force generated by the spacecraft wings and main body|" & _
        "Lift acceleration generated by the spacecraft wings and main body|" & _
        "Spacecraft pitch angle relative to the horizontal|" & _
        "Spacecraft angle of attack|"
    StrAdd _
        "Atmosphere density|" & _
        "Atmosphere pressure|" & _
        "Spacecraft static temperature|" & _
        "Thrust due to the engine|" & _
        "G-Force due to the engine|" & _
        "Percent of throttle|" & _
        "Total spaceship mass|" & _
        "Propellant burned mass|" & _
        "Propellant burn rate|" & _
        "Remaining percent of propellant|"
    StrAdd _
        "Maximum G-Force felt by the spaceship|" & _
        "Maximum acceleration felt by the spaceship|" & _
        "Total impulse|" & _
        "Specific orbital energy|" & _
        "Delta-V due to the engine thrust|" & _
        "Delta-V due to the atmosphere drag|" & _
        "Accelerating time during the takeoff phase|" & _
        "Decelerating time during the landing phase|" & _
        "Latitude relative to the nearest planet|" & _
        "Longitude relative to the nearest planet|" & _
        "Altitude relative to the main level|" & _
        "Altitude relative to the ground level|" & _
        "Camera distance"
    DataToolTip3 = Split(StrAdd, "|")
    
    'Graph panel simulation
    StrAdd _
        "Elapsed time since the start of the mission|" & _
        "Speed relative to the planet center|" & _
        "Speed relative to the atmosphere|" & _
        "Altitude relative to the main level|" & _
        "Total mass|" & _
        "Gravity minus the centripetal acceleration|" & _
        "Engine thrust|" & _
        "Engine thrust acceleration|" & _
        "Total acceleration|" & _
        "Atmosphere drag deceleration|" & _
        "Static temperature|" & _
        "Number of ground stations available and number of airspaces detected"
    GraphToolTip1 = Split(StrAdd, "|")
    
    'Graph panel balloon
    StrAdd _
        "Current time UTC|" & _
        "Elapsed time since the launch|" & _
        "Latitude coordinate of the current position|" & _
        "Longitude coordinate of the current position|" & _
        "Altitude relative to the main level|" & _
        "Horizontal speed relative to the ground|" & _
        "Vertical speed relative to the ground|" & _
        "Air density|" & _
        "Air pressure|" & _
        "Air temperature|" & _
        "Air relative humidity|" & _
        "Cloud water mixing ratio"
    GraphToolTip2 = Split(StrAdd, "|")
    
    'Burn editor specifications
    StrAdd "Engine specifications|" & _
        "Engine classification and total impulse|" & _
        "Engine specific impulse|" & _
        "Average engine thrust|" & _
        "Maximum engine thrust|" & _
        "Maximum combustion chamber pressure|" & _
        "Burst pressure of the combustion chamber|" & _
        "Time of the maximum combustion chamber pressure|" & _
        "Time of the propellant burnout|" & _
        "Time of the end of thrust|" & _
        "Average propellant burn rate|"
    StrAdd _
        "Geometry of the propellant core|" & _
        "Length of the motor chamber|" & _
        "Length of the propellant|" & _
        "Diameter of the propellant|" & _
        "Thickness of the motor chamber walls|" & _
        "Length of the propellant core|" & _
        "Diameter of the propellant core|" & _
        "Depth of the propellant fins|" & _
        "Width of the propellant fins|" & _
        "Diameter of the nozzle throat and burning area to throat area ratio|" & _
        "Diameter of the nozzle exit and nozzle expansion ratio|" & _
        "Propellant volume|" & _
        "Propellant mass and loading volume ratio|" & _
        "Engine dry mass (without propellant) and dry mass to full mass ratio|" & _
        "Engine total mass (with propellant)"
    BurnLogToolTip = Split(StrAdd, "|")
    
    'Burn editor data
    StrAdd _
        "Engine classification and total impulse|" & _
        "Engine specific impulse|" & _
        "Maximum engine thrust|" & _
        "Maximum combustion chamber pressure"
    BurnDataToolTip1 = Split(StrAdd, "|")
    
    'Burn editor simulation cursor
    StrAdd _
        "Engine thrust|" & _
        "Combustion chamber pressure|" & _
        "Propellant burn rate|" & _
        "Nozzle expansion ratio"
    BurnDataToolTip2 = Split(StrAdd, "|")
    
    End Sub

Sub CopyData()
    Clipboard.Clear: Clipboard.SetText DataString
    End Sub

Sub CopyImage(Pic As PictureBox)
    Clipboard.Clear: Clipboard.SetData Pic.Image
    End Sub

Function DataString() As String
    Select Case IfInt(Cam1.Relative <= ToShip, Target, Cam1.Relative)
    Case ToEarth, ToAir: DataStringEx Earth, Moon
    Case ToMoon:    DataStringEx Moon, Moon
    Case ToMercury: DataStringEx Mercury, Mercury
    Case ToVenus:   DataStringEx Venus, Venus
    Case ToMars:    DataStringEx Mars, Mars
    Case ToJupiter: DataStringEx Jupiter, Jupiter
    Case ToSaturn:  DataStringEx Saturn, Saturn
    Case ToUranus:  DataStringEx Uranus, Uranus
    Case ToNeptune: DataStringEx Neptune, Neptune
    Case ToPluto:   DataStringEx Pluto, Pluto
    Case Else:      DataStringEx Sun, Sun
        End Select
    DataString = StrAdd
    End Function

Sub DataStringEx(Pl As Planet, Rt As Planet)
    Dim Q As Boolean, T1 As Double, _
        H1 As Double, G1 As Double, A1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        CamLon As Double, CamLat As Double, CamAlt As Double, _
        EarthLon As Double, EarthLat As Double, _
        OrbitSpeed As Double, EscapeSpeed As Double, _
        Speed As String, Nu As Double, Orbit As TLE, D1 As WMMData
    
    If Not SyncMode Then Speed = Num(RealSpeed, FOne) _
    Else If SyncTime <> 0 Then Speed = ToHms(SyncTime) Else Speed = "Real"
    If KMLPathPos <= 0 Then T1 = T _
    Else T1 = KMLPath(IfInt(KMLPathPos > 1, KMLPathPos, 0)).Time - KMLPath(0).Time
    X1 = Cam1.CenterX: Y1 = Cam1.CenterY: Z1 = Cam1.CenterZ
    If Rt.ID = ToMoon And Pl.ID = ToEarth Then XYZ2LLA Earth, CamLat, CamLon, CamAlt, X1, Y1, Z1 _
    Else XYZ2LLA Rt, CamLat, CamLon, CamAlt, X1, Y1, Z1
    If Rt.ID = ToMoon And Pl.ID = ToEarth Then Vector2TLE Earth, Orbit, Nu, Plane = Equatorial _
    Else Vector2TLE Rt, Orbit, Nu, Plane = Equatorial
    XYZ2LLA Pl, Lat, Lon, Alt
    Q = (IsLaunchTrajectory And (Status = TakeOff Or Status = Launch)) Or _
        (IsLandingManeuver And (Status = Landing Or Status = Landed Or Status = Crashed)) Or _
        (IsSatelliteControl And Status = Deployed)
    If Status > Controlled Or Status = Crashed Then _
        X1 = Pl.X / Pl.Distance: Y1 = Pl.Y / Pl.Distance: Z1 = Pl.Z / Pl.Distance: _
        Angle = Angle3D(X1, Y1, Z1, AngleU2, AngleV2, AngleW2)
    OrbitSpeed = Sqr(Pl.GravityMass / Pl.Distance): EscapeSpeed = Sqr(Pl.GravityMass / Pl.Distance * 2)
    If Pl.Gravity = 0 Then A1 = 0 Else A1 = 1 - Pl.CentripetalAccel / Pl.Gravity
    If Pl.ID = ToEarth Then EarthLat = Lat: EarthLon = Lon _
    Else XYZ2LLA Earth, EarthLat, EarthLon
    D1.Alt = (Earth.Distance - Earth.Radius) / 1000
    D1.Year = (Time + T) / YearDays / Day + 2000
    D1.Lat = EarthLat / Rad: D1.Lon = EarthLon / Rad: WMMGetElements D1
    
    StrAdd _
        Mid(StatusText, InStr(StatusText, "  ") + 2) & vbCrLf & _
        ToDhms(T1) & vbCrLf & _
        J2ksDateTime(Time + T, False) & vbCrLf & _
        Num(dT, "0") & "s x" & Speed & " " & _
            NumX(IfDbl(Play = Active, Fps, 0), "00.0 fps") & vbCrLf & _
        "Stage" & vbTab & IfInt(Status > 0, Status, 0) & " · " & StageN & vbCrLf & _
        "Speed E" & vbTab & ToSpeed(Earth.Speed) & vbCrLf & _
            vbTab & IIf(Earth.AirSpeed > 0, ToAirSpeed(Earth.AirSpeed), "-") & vbCrLf & _
        "Speed T" & vbTab & ToSpeed(Rt.Speed) & vbCrLf & _
            vbTab & IIf(Rt.AirSpeed > 0, ToSpeed(Rt.AirSpeed), "-") & vbCrLf & _
        "Speed V" & vbTab & ToSpeed(Pl.VerticalSpeed) & vbCrLf & _
            vbTab & IIf(Abs(A1) < 10, NumX(A1, "0.000%"), "-") & vbCrLf
    StrAdd _
        "Orbit" & vbTab & ToSpeed(OrbitSpeed) & vbCrLf & _
            vbTab & IIf(Abs(Pl.Speed / OrbitSpeed) < 10, NumX(Pl.Speed / OrbitSpeed, "0.000%"), "-") & vbCrLf & _
        "Escape" & vbTab & ToSpeed(EscapeSpeed) & vbCrLf & _
            vbTab & IIf(Abs(Pl.Speed / EscapeSpeed) < 10, NumX(Pl.Speed / EscapeSpeed, "0.000%"), "-") & vbCrLf & _
        "Apsis" & vbTab & ToAlt(Orbit.S * (1 - Orbit.E)) & vbCrLf & _
            vbTab & IIf(Orbit.S >= 0, ToAlt(Orbit.S), "-") & vbCrLf & _
            vbTab & IIf(Orbit.S >= 0, ToAlt(Orbit.S * (1 + Orbit.E)), "-") & vbCrLf & _
        "Period" & vbTab & ToAngle(Orbit.I) & vbCrLf & _
            vbTab & IIf(Orbit.E < 1000, NumX(Orbit.E), "-") & vbCrLf & _
            vbTab & IIf(Orbit.E < 1000 And Orbit.M > 0, ToDhEx(A360 / IfDbl(Orbit.M > 0, Orbit.M, 1)), "-") & vbCrLf & _
        "Angles" & vbTab & ToAngle(Orbit.P) & vbCrLf & _
            vbTab & ToAngle(Orbit.O) & vbCrLf & _
            vbTab & ToAngle(Orbit.L) & vbCrLf & _
            vbTab & ToAngle(Nu) & vbCrLf
    StrAdd _
        "Alt E" & vbTab & ToAlt(Earth.Altitude) & vbCrLf & _
            vbTab & ToAlt(Earth.Distance) & vbCrLf & _
        "Alt T" & vbTab & ToAlt(Rt.Altitude) & vbCrLf & _
            vbTab & ToAlt(Rt.Distance) & vbCrLf & _
        "Grav E" & vbTab & ToForceG(Earth.Gravity) & vbCrLf & _
            vbTab & ToAccel(Earth.Gravity) & vbCrLf & _
        "Grav T" & vbTab & ToForceG(Rt.Gravity) & vbCrLf & _
            vbTab & ToAccel(Rt.Gravity) & vbCrLf & _
        "Accel" & vbTab & ToForceG(Accel) & vbCrLf & _
            vbTab & ToAccel(Accel) & vbCrLf & _
        "Inert" & vbTab & ToForceG(InertialAccel) & vbCrLf & _
            vbTab & ToAccel(InertialAccel) & vbCrLf & _
        "Drag" & vbTab & ToForceG(Drag) & vbCrLf & _
            vbTab & ToAccel(Drag) & vbCrLf & _
        "Lift" & vbTab & ToForceG(Lift) & vbCrLf & _
            vbTab & ToAccel(Lift) & vbCrLf & _
        "Pitch" & vbTab & ToAngle(A90 - Angle) & vbCrLf & _
            vbTab & IIf(Q, ToAngle(IfDbl(Status = TakeOff Or Status = Launch, TargetAngle - Angle, TargetAngle)), "-") & vbCrLf & _
            vbTab & IIf(AirDensity > 0, ToAngle(AirAngle), "-") & vbCrLf & _
            vbTab & IIf(Q, ToControl(IsMaxAbs3(ControlThrustU, ControlThrustV, ControlThrustW), FMini, Length / 2), "-") & vbCrLf
    StrAdd _
        "Field" & vbTab & IIf(Target = ToEarth Or Target = ToMoon, Num(D1.F / 1000000000, FShort) & "T", "-") & vbCrLf & _
        "Dec" & vbTab & IIf(Target = ToEarth Or Target = ToMoon, NumX(D1.Decl, FAngleLon), "-") & vbCrLf & _
        "Inc" & vbTab & IIf(Target = ToEarth Or Target = ToMoon, NumX(-D1.Incl, FAngleLat), "-") & vbCrLf & _
        "Air" & vbTab & IIf(AirDensity > 0, ToDensity(AirDensity), "-") & vbCrLf & _
            vbTab & IIf(AirDensity > 0, ToPressure(AirPressure), "-") & vbCrLf & _
            vbTab & IIf(AtmMode > 0 And AirDensity > 0, ToTemp(AirTemperature), "-") & vbCrLf & _
            vbTab & IIf(AtmMode > 0 And AirDensity > 0, NumX(AirHumidity, FMini) & "%", "-") & vbCrLf & _
            vbTab & IIf(AtmMode > 0 And AirDensity > 0, ToAngle(IfDbl(AirWindH <> 0, ATan(AirWindU, AirWindV) + A180, 0)), "-") & vbCrLf & _
        "Speed" & vbTab & IIf(AtmMode > 0 And AirDensity > 0, ToAlt(AirWindW) & "/s", "-") & vbCrLf & _
            vbTab & IIf(AtmMode > 0 And AirDensity > 0, ToAirSpeed(AirWindH), "-") & vbCrLf & _
        "Temp" & vbTab & ToTemp(Temperature) & vbCrLf & _
        "Thrust" & vbTab & ToForce(Thrust * Throttle) & vbCrLf & _
            vbTab & ToForceG(Thrust * Throttle, Mass) & vbCrLf & _
            vbTab & NumX(IfDbl(Thrust <> 0, Throttle, 0), "0.00%") & vbCrLf
    StrAdd _
        "Mass" & vbTab & ToMass(Mass) & vbCrLf & _
        "Fuel" & vbTab & ToMass(FullMass - Mass) & vbCrLf & _
            vbTab & ToMass(IfDbl(Thrust <> 0, BurnRate * Throttle, 0)) & "/s" & vbCrLf & _
            vbTab & NumX((Mass - DryMass) / (FullMass - DryMass), "0.0%") & vbCrLf & _
        "Max SE" & vbTab & ToSpeed(MaxVe) & vbCrLf & _
        "Min SE" & vbTab & ToSpeed(MinVe) & vbCrLf & _
        "Max HE" & vbTab & ToAlt(MaxHe) & vbCrLf & _
        "Min HE" & vbTab & ToAlt(MinHe) & vbCrLf & _
        "Max HM" & vbTab & ToAlt(MaxHm) & vbCrLf & _
        "Min HM" & vbTab & ToAlt(MinHm) & vbCrLf & _
        "Max G" & vbTab & ToForceG(MaxG) & vbCrLf & _
            vbTab & ToAccel(MaxG) & vbCrLf & _
        "Max Ctl" & vbTab & ToControl(ControlThrustMax, FOne, Length / 2) & vbCrLf & _
        "Impulse" & vbTab & ToForce(Impulse) & "·s" & vbCrLf & _
        "Energy" & vbTab & ToEnergy(Pl.Speed * Pl.Speed / 2 - (Pl.Mass + Mass) * G / Pl.Distance) & vbCrLf
    StrAdd _
        "DeltaV" & vbTab & ToSpeed(ThrustDV) & vbCrLf & _
            vbTab & ToSpeed(IfDbl(BurnOutDV > 0, ThrustDV - BurnOutDV, DragDV)) & vbCrLf & _
        "DeltaVG" & vbTab & ToSpeed(GravityDV) & vbCrLf & _
            vbTab & ToSpeed(LiftDV) & vbCrLf & _
        "Accel T" & vbTab & ToMs(IfDbl(AccelTime >= 0, AccelTime, T)) & vbCrLf & _
        "Decel T" & vbTab & ToMs(IfDbl(BrakeTime >= 0, T - BrakeTime, 0)) & vbCrLf & _
        "Proxy" & vbTab & IIf(IsSatelliteProximity, ToRatioEx(SatelliteInRange, T), "-") & vbCrLf & _
            vbTab & IIf(IsAsteroidProximity, ToRatioEx(AsteroidInRange, T), "-") & vbCrLf & _
        "Lat" & vbTab & ToLat(Lat, , Degrees) & vbCrLf & _
        "Lon" & vbTab & ToLon(Lon, , Degrees) & vbCrLf & _
        "Alt" & vbTab & ToAlt(Alt, , Not AltitudeInfo) & vbCrLf & _
        "Ground" & vbTab & IIf(TerrainMode = 2 And Earth.Distance < Earth.Diameter, ToAlt(Alt - FloorAltitude), "-") & vbCrLf & _
            vbTab & IIf(TerrainMode = 2 And Earth.Distance < Earth.Diameter, ToAlt(FloorAltitude, , Not AltitudeInfo), "-") & vbCrLf
    StrAdd _
        "Camera" & vbTab & ToAlt(Cam1.Altitude) & vbCrLf & _
            vbTab & ToAlt(CamAlt, FShort) & vbCrLf & _
            vbTab & ToAngle(Cam1.AngleU * Rad, , True) & vbCrLf & _
            vbTab & ToAngle(IfDbl(Cam1.Follow = ToTactic, Cam1.AngleV + 180, Cam1.AngleV + 90) * Rad) & vbCrLf & _
            vbTab & ToAngle(IfDbl(Cam1.Follow = ToTactic, 0, Cam1.AngleW * Rad)) & vbCrLf
    End Sub

Sub Info()
    Select Case IfInt(Cam1.Relative <= ToShip, Target, Cam1.Relative)
    Case ToEarth, ToAir: InfoEx Earth, Moon
    Case ToMoon: InfoEx Moon, Moon
    Case ToMercury: InfoEx Mercury, Mercury
    Case ToVenus: InfoEx Venus, Venus
    Case ToMars: InfoEx Mars, Mars
    Case ToJupiter: InfoEx Jupiter, Jupiter
    Case ToSaturn: InfoEx Saturn, Saturn
    Case ToUranus: InfoEx Uranus, Uranus
    Case ToNeptune: InfoEx Neptune, Neptune
    Case ToPluto: InfoEx Pluto, Pluto
    Case Else: InfoEx Sun, Sun
            End Select
    End Sub

Sub InfoEx(Pl As Planet, Rt As Planet)
    Dim B As Long, C As Long, D As Long, E As Long, H As Long, _
        I As Long, J As Long, L As Long, N As Single, Q As Boolean, _
        H1 As Double, G1 As Double, A1 As Double, T1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        CamLon As Double, CamLat As Double, CamAlt As Double, _
        EarthLon As Double, EarthLat As Double, _
        OrbitSpeed As Double, EscapeSpeed As Double, _
        Speed As String, Nu As Double, Orbit As TLE, D1 As WMMData, _
        Pic As PictureBox
    
    On Error Resume Next
    If Not Data.Visible Then Exit Sub
    Set Pic = Data.Pic
    If Not SyncMode Then Speed = Num(RealSpeed, FOne) _
        Else If SyncTime <> 0 Then Speed = ToHms(SyncTime) Else Speed = "Real"
    If KMLPathPos <= 0 Then T1 = T _
    Else T1 = KMLPath(IfInt(KMLPathPos > 1, KMLPathPos, 0)).Time - KMLPath(0).Time
    If DataMode > 1 Then
        If DataToolX And DataCamera Then X1 = Cam1.CenterX: Y1 = Cam1.CenterY: Z1 = Cam1.CenterZ: _
            If Rt.ID = ToMoon And Pl.ID = ToEarth Then XYZ2LLA Earth, CamLat, CamLon, CamAlt, X1, Y1, Z1 _
            Else XYZ2LLA Rt, CamLat, CamLon, CamAlt, X1, Y1, Z1
        If DataTool And DataElements Then _
            If Rt.ID = ToMoon And Pl.ID = ToEarth Then Vector2TLE Earth, Orbit, Nu, Plane = Equatorial _
            Else Vector2TLE Rt, Orbit, Nu, Plane = Equatorial
        XYZ2LLA Pl, Lat, Lon, Alt
        Q = (IsLaunchTrajectory And (Status = TakeOff Or Status = Launch)) Or _
            (IsLandingManeuver And (Status = Landing Or Status = Landed Or Status = Crashed)) Or _
            (IsSatelliteControl And Status = Deployed)
        If Status > Controlled Or Status = Crashed Then _
            X1 = -Pl.X / Pl.Distance: Y1 = -Pl.Y / Pl.Distance: Z1 = -Pl.Z / Pl.Distance: _
            Angle = Angle3D(X1, Y1, Z1, AngleU2, AngleV2, AngleW2)
        OrbitSpeed = Sqr(Pl.GravityMass / Pl.Distance): EscapeSpeed = Sqr(Pl.GravityMass / Pl.Distance * 2)
        If Pl.Gravity = 0 Then A1 = 0 Else A1 = 1 - Pl.CentripetalAccel / Pl.Gravity
        If DataTool And DataMagnetic Then
            If Pl.ID = ToEarth Then EarthLat = Lat: EarthLon = Lon _
            Else XYZ2LLA Earth, EarthLat, EarthLon
            D1.Alt = (Earth.Distance - Earth.Radius) / 1000
            D1.Year = (Time + T) / YearDays / Day + 2000
            D1.Lat = EarthLat / Rad: D1.Lon = EarthLon / Rad: WMMGetElements D1
            End If
        End If
    
    Pic.Cls: Pic.Print Tab(2);: N = Pic.ScaleWidth / Pic.CurrentX: Pic.CurrentY = 2
    L = Pic.TextHeight(""): H = Pic.ScaleHeight
    Select Case DataMode
    
    Case 1 'Compact
        D = 4
        C = InStr(StatusText, "  ") + 2: E = InStr(C + 1, StatusText, "  "): E = IfInt(E > 0, E, Len(StatusText) + 2)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Mid(StatusText, C, E - C)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: If SyncMode Then Pic.Print J2ksDateTime(Time + T) Else Pic.Print ToDhms(T1)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Num(dT, "0") & "s x" & Speed & " " & NumX(IfDbl(Play = Active, Fps, 0), "0.0 fps")
        'Pic.Print Vector2TextTLE(Earth)
    
    Case 2 'Wide
        B = N * 0.33: C = N * 0.67: D = 4: I = N * 0.59: J = N * 0.83
        If DataTool And DataStage Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "ST" & IfInt(Status > 0, Status, 0) & "·" & _
            StageN & "  " & Mid(StatusText, InStr(StatusText, " ") + 1)
        If DataTool And DataTime Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Num(dT, "0") & "s  x" & Speed; Tab(N * 0.42); ToDhms(T1)
        If DataTool And DataDate Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print J2ksDateTime(Time + T); Tab(N * 0.77); NumX(IfDbl(Play = Active, Fps, 0), "00.0 fps")
        If DataTool And DataSpeedE Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Speed E"; Tab(B); ToSpeed(Earth.Speed); Tab(C);: _
            If Earth.AirSpeed > 0 Then Pic.Print ToAirSpeed(Earth.AirSpeed) Else Pic.Print "-"
        If DataTool And DataSpeedT Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Speed T"; Tab(B); ToSpeed(Rt.Speed); Tab(C);: _
            If Rt.AirSpeed > 0 Then Pic.Print ToSpeed(Rt.AirSpeed) Else Pic.Print "-"
        If DataTool And DataSpeedV Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Speed V"; Tab(B); ToSpeed(Pl.VerticalSpeed); Tab(C);: _
            If Abs(A1) < 10 Then Pic.Print NumX(A1, "0.000%") Else Pic.Print "-"
        If DataTool And DataOrbit Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Orbit"; Tab(B); ToSpeed(OrbitSpeed); Tab(C);: _
            If Abs(Pl.Speed / OrbitSpeed) < 10 Then Pic.Print NumX(Pl.Speed / OrbitSpeed, "0.000%") Else Pic.Print "-"
        If DataTool And DataEscape Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Escape"; Tab(B); ToSpeed(EscapeSpeed); Tab(C);: _
            If Abs(Pl.Speed / EscapeSpeed) < 10 Then Pic.Print NumX(Pl.Speed / EscapeSpeed, "0.000%") Else Pic.Print "-"
        If DataTool And DataApsis Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Apsis"; Tab(B); ToAlt(Orbit.S * (1 - Orbit.E), FOne) & "  ";: _
            If Orbit.S >= 0 Then Pic.Print ToAlt(Orbit.S, FOne) & "  " & ToAlt(Orbit.S * (1 + Orbit.E), FOne) Else Pic.Print "      -          -"
        If DataTool And DataPeriod Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Period"; Tab(B); ToAngle(Orbit.I) & "  ";: If Orbit.E >= 1000 Then Pic.Print "      -          -" _
            Else Pic.Print NumX(Orbit.E, FFive);: If Orbit.M > 0 Then Pic.Print Tab(C); ToDhEx(A360 / Orbit.M) Else Pic.Print "     -"
        If DataTool And DataAngles Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Angles"; Tab(B); ToAngle(Orbit.P) & "  " & _
            ToAngle(Orbit.O) & "  " & ToAngle(Orbit.L) & "  " & ToAngle(Nu)
        If DataTool And DataAltitudeE Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Altitude E"; Tab(B); ToAlt(Earth.Altitude); Tab(C); ToAlt(Earth.Distance)
        If DataTool And DataAltitudeT Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Altitude T"; Tab(B); ToAlt(Rt.Altitude); Tab(C); ToAlt(Rt.Distance)
        If DataTool And DataGravityE Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Gravity E"; Tab(B); ToForceG(Earth.Gravity); Tab(C); ToAccel(Earth.Gravity)
        If DataTool And DataGravityT Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Gravity T"; Tab(B); ToForceG(Rt.Gravity); Tab(C); ToAccel(Rt.Gravity)
        If DataTool And DataAccel Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Accel"; Tab(B); ToForceG(Accel); Tab(C); ToAccel(Accel)
        If DataTool And DataInertial Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Inertial"; Tab(B); ToForceG(InertialAccel); Tab(C); ToAccel(InertialAccel)
        If DataTool And DataDrag Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Drag"; Tab(B); ToForceG(Drag); Tab(C); ToAccel(Drag)
        If DataTool And DataLift Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Lift"; Tab(B); ToForceG(Lift); Tab(C); ToAccel(Lift)
        If DataTool And DataPitch Then
            H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Pitch"; Tab(B); ToAngle(A90 - Angle);
            If Q Then Pic.Print "  " & ToAngle(IfDbl(Status = TakeOff Or Status = Launch, TargetAngle - Angle, TargetAngle)); Else Pic.Print "    -    ";
            If AirDensity > 0 Then Pic.Print "  " & ToAngle(AirAngle); Else Pic.Print "  -  ";
            If Q Then Pic.Print "  " & ToControl(IsMaxAbs3(ControlThrustU, ControlThrustV, ControlThrustW), FMini, Length / 2) Else Pic.Print "   -"
            End If
        If DataTool And DataMagnetic Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Magnetic"; Tab(B);: If Target <> ToEarth And Target <> ToMoon Then Pic.Print "-            -            -" _
            Else Pic.Print NumX(D1.Decl, FAngleLon) & "   " & NumX(-D1.Incl, FAngleLat) & "  " & Num(D1.F / 1000000000, FShort) & "T"
        If DataToolX And DataAir Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Air"; Tab(B);: _
            If AirDensity > 0 Then Pic.Print ToDensity(AirDensity, "000.00"); Tab(C); ToPressure(AirPressure, "000.00") Else Pic.Print "-"; Tab(C); "-"
        If DataToolX And DataAirT Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Temp"; Tab(B);: _
            If AtmMode > 0 And AirDensity > 0 Then Pic.Print ToTemp(AirTemperature); Tab(I); NumX(AirHumidity, FMini) & "%"; Tab(J); _
                ToAngle(IfDbl(AirWindH <> 0, ATan(AirWindU, AirWindV) + A180, 0)) Else Pic.Print "-"; Tab(I); "-"; Tab(J); "-"
        If DataToolX And DataAirS Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Speed"; Tab(B);: _
            If AtmMode > 0 And AirDensity > 0 Then Pic.Print ToAlt(AirWindW) & "/s"; Tab(C); ToAirSpeed(AirWindH) Else Pic.Print "-"; Tab(C); "-"
        If DataToolX And DataBurn Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Burn"; Tab(B); _
            ToMass(IfDbl(Thrust <> 0, BurnRate * Throttle, 0), "000.00") & "/s"; Tab(C); ToTemp(Temperature)
        If DataToolX And DataThrust Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Thrust"; Tab(B); ToForce(Thrust * Throttle, "000.00"); Tab(I); _
            ToForceG(Thrust * Throttle, Mass, FQuad); Tab(J); NumX(IfDbl(Thrust <> 0, Throttle, 0), "0.0%")
        If DataToolX And DataMass Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Mass"; Tab(B); ToMass(Mass, "000.00"); Tab(I); _
            ToMass(FullMass - Mass, "000.00"); Tab(J); NumX((Mass - DryMass) / (FullMass - DryMass), "0.0%")
        If DataToolX And DataRangeSE Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Range SE"; Tab(B); ToSpeed(MinVe); Tab(C); ToSpeed(MaxVe)
        If DataToolX And DataRangeHE Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Range HE"; Tab(B); ToAlt(MinHe); Tab(C); ToAlt(MaxHe)
        If DataToolX And DataRangeHM Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Range HM"; Tab(B); ToAlt(MinHm); Tab(C); ToAlt(MaxHm)
        If DataToolX And DataMaxG Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Max G"; Tab(B); ToForceG(MaxG); Tab(C); ToAccel(MaxG)
        If DataToolX And DataImpulse Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Impulse"; Tab(B); ToForce(Impulse, FOne) & "·s  " & _
            ToEnergy(Pl.Speed * Pl.Speed / 2 - (Pl.Mass + Mass) * G / Pl.Distance, FOne) & "  " & ToControl(ControlThrustMax, FOne, Length / 2)
        If DataToolX And DataDeltaV Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Delta V"; Tab(B); ToSpeed(ThrustDV); Tab(C); _
            ToSpeed(IfDbl(BurnOutDV > 0, ThrustDV - BurnOutDV, DragDV))
        If DataToolX And DataDeltaVG Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Delta V G"; Tab(B); ToSpeed(GravityDV); Tab(C); _
            ToSpeed(LiftDV)
        If DataToolX And DataTimeAD Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Time A/D"; Tab(B); ToMs(IfDbl(AccelTime >= 0, AccelTime, T)); Tab(C); _
            ToMs(IfDbl(BrakeTime >= 0, T - BrakeTime, 0))
        If DataToolX And DataProxy Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Proximity"; Tab(B); IIf(IsSatelliteProximity, ToRatioEx(SatelliteInRange, T), "-"); Tab(C); _
            IIf(IsAsteroidProximity, ToRatioEx(AsteroidInRange, T), "-")
        If DataToolX And DataLatLon Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Lat Lon"; Tab(B); ToLat(Lat, , Degrees); Tab(C); ToLon(Lon, , Degrees)
        If DataToolX And DataAlt Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Alt"; Tab(B); ToAlt(Alt, , Not AltitudeInfo); Tab(C); ToAlt(Cam1.Altitude, FShort)
        If DataToolX And DataGround Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Ground"; Tab(B);: If TerrainMode = 2 And Pl.Distance < Pl.Diameter Then _
            Pic.Print ToAlt(Alt - FloorAltitude); Tab(C); ToAlt(FloorAltitude, , Not AltitudeInfo) Else Pic.Print "-"; Tab(C); "-"
        If DataToolX And DataCamera Then H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Camera"; Tab(B); ToAngle(Cam1.AngleU * Rad, , True) & "  " & _
            ToAngle(IfDbl(Cam1.Follow = ToTactic, Cam1.AngleV + 180, Cam1.AngleV + 90) * Rad) & "  " & ToAngle(IfDbl(Cam1.Follow = ToTactic, 0, Cam1.AngleW * Rad)) & "  " & ToAlt(CamAlt, FShort)
        'Pic.CurrentX = D: Pic.Print "Mars G"; Tab(B); ToForceG(Mars.Gravity); Tab(C); ToAccel(Mars.Gravity)
        
    Case 3 'Large
        B = N * 0.45: D = 4 '12/39
        C = InStr(StatusText, "  ") + 2: E = InStr(C + 1, StatusText, "  "): E = IfInt(E > 0, E, Len(StatusText) + 2)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Mid(StatusText, C, E - C)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print ToDhms(T1)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print J2ksDateTime(Time + T, False)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Num(dT, "0") & "s x" & Speed & " " & NumX(IfDbl(Play = Active, Fps, 0), "0.0 fps")
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Stage"; Tab(B); IfInt(Status > 0, Status, 0) & " · " & StageN
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Speed E"; Tab(B); ToSpeed(Earth.Speed)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B);: If Earth.AirSpeed > 0 Then Pic.Print ToAirSpeed(Earth.AirSpeed) Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Speed T"; Tab(B); ToSpeed(Rt.Speed)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B);: If Rt.AirSpeed > 0 Then Pic.Print ToSpeed(Rt.AirSpeed) Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Speed V"; Tab(B); ToSpeed(Pl.VerticalSpeed)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B);: If Abs(A1) < 10 Then Pic.Print NumX(A1, "0.000%") Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Orbit"; Tab(B); ToSpeed(OrbitSpeed)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B);: If Abs(Pl.Speed / OrbitSpeed) < 10 Then Pic.Print NumX(Pl.Speed / OrbitSpeed, "0.000%") Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Escape"; Tab(B); ToSpeed(EscapeSpeed)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B);: If Abs(Pl.Speed / EscapeSpeed) < 10 Then Pic.Print NumX(Pl.Speed / EscapeSpeed, "0.000%") Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Alt E"; Tab(B); ToAlt(Earth.Altitude)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAlt(Earth.Distance)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Alt T"; Tab(B); ToAlt(Rt.Altitude)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAlt(Rt.Distance)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Grav E"; Tab(B); ToForceG(Earth.Gravity)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAccel(Earth.Gravity)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Grav T"; Tab(B); ToForceG(Rt.Gravity)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAccel(Rt.Gravity)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Accel"; Tab(B); ToForceG(Accel)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAccel(Accel)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Drag"; Tab(B); ToForceG(Drag)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAccel(Drag)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Lift"; Tab(B); ToForceG(Lift)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAccel(Lift)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Pitch"; Tab(B); ToAngle(A90 - Angle)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B);: If AirDensity > 0 Then Pic.Print ToAngle(AirAngle) Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Air"; Tab(B);: If AirDensity > 0 Then Pic.Print ToDensity(AirDensity) Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B);: If AirDensity > 0 Then Pic.Print ToPressure(AirPressure) Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Temp"; Tab(B); ToTemp(Temperature)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Thrust"; Tab(B); ToForce(Thrust * Throttle)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToForceG(Thrust * Throttle, Mass)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); NumX(IfDbl(Thrust <> 0, Throttle, 0), "0.00%")
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Mass"; Tab(B); ToMass(Mass)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Fuel"; Tab(B); ToMass(FullMass - Mass)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToMass(IfDbl(Thrust <> 0, BurnRate * Throttle, 0)) & "/s"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); NumX((Mass - DryMass) / (FullMass - DryMass), "0.00%")
        'H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Max SE"; Tab(B); ToSpeed(MaxVe)
        'H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Min SE"; Tab(B); ToSpeed(MinVe)
        'H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Max HE"; Tab(B); ToAlt(MaxHe)
        'H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Min HE"; Tab(B); ToAlt(MinHe)
        'H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Min HM"; Tab(B); ToAlt(MinHm)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Max G"; Tab(B); ToForceG(MaxG)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToAccel(MaxG)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Impulse"; Tab(B); ToForce(Impulse) & "·s"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Energy"; Tab(B); ToEnergy(Pl.Speed * Pl.Speed / 2 - (Pl.Mass + Mass) * G / Pl.Distance)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "DeltaV"; Tab(B); ToSpeed(ThrustDV)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print Tab(B); ToSpeed(IfDbl(BurnOutDV > 0, ThrustDV - BurnOutDV, DragDV))
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Accel T"; Tab(B); ToMs(IfDbl(AccelTime >= 0, AccelTime, T))
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Decel T"; Tab(B); ToMs(IfDbl(BrakeTime >= 0, T - BrakeTime, 0))
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Lat"; Tab(B); ToLat(Lat, , Degrees)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Lon"; Tab(B); ToLon(Lon, , Degrees)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Alt"; Tab(B); ToAlt(Alt, , Not AltitudeInfo)
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Ground"; Tab(B);: If TerrainMode = 2 And Pl.Distance < Pl.Diameter Then Pic.Print ToAlt(Alt - FloorAltitude) Else Pic.Print "-"
        H = H - L: If H < 0 Then Exit Sub Else Pic.CurrentX = D: Pic.Print "Camera"; Tab(B); ToAlt(Cam1.Altitude)
        
        End Select
    On Error GoTo 0
    
    End Sub

Sub Plot(Optional ByVal Pos As Long, Optional ByVal Mode As Long, _
        Optional X As Single, Optional Y As Single)
    Dim B As Long, C As Long, D As Long, E As Long, H As Long, I As Long, W As Long, _
        L As Long, Q As Boolean, T1 As Double, Pic As PictureBox
    Static LastT As Double
    
    On Error Resume Next
    If Not Graph.Visible Then Exit Sub
    Set Pic = Graph.Pic
    If KMLPathN > 0 And (KMLPathPos > 0 Or IsBalloonTrajectory <> 0) And T = 0 Then _
        If KMLPath(0).Time <> 0 Then Q = True
    
    If Not Q Then

    'Spacecraft graph
    If Pos = 0 And Mode = 0 And T >= GraphicMax And LastT >= GraphicMax Then Exit Sub
    LastT = T
    Pic.Cls: I = Pic.ForeColor
    Pic.Print Tab(2);: D = Pic.CurrentX * 22
    Pic.Print: L = Pic.CurrentY
    B = 10: H = Pic.ScaleHeight - 2: W = Pic.ScaleWidth - D
    Select Case Mode '0=Relative -1=Absolute -2=MouseMove -3=MouseDown -4=DblClick -5=MoveWindow
    Case 0: GraphicP = GraphicP + Pos
    Case -1: GraphicP = Pos
    Case -2: GraphicP = GraphicWin + X - D: If GraphicP < GraphicWin Then GraphicP = GraphicWin
    Case -3, -4: If X < D Then GraphicMode = GraphicMode Xor 2 ^ Int(Y / L) _
                    Else If Mode = -4 Then GraphicScale = 1
    Case -5: GraphicP = GraphicP + Pos: GraphicWin = GraphicWin + Pos
        End Select
    If GraphicWin > GraphicN - W Then GraphicWin = GraphicN - W
    If GraphicWin < 0 Then GraphicWin = 0
    If GraphicP > GraphicN - 1 Then GraphicP = GraphicN - 1
    If GraphicP < 0 Then GraphicP = 0
    If GraphicP > GraphicWin + W - 1 Then GraphicWin = GraphicP - W + 1
    If GraphicP < GraphicWin Then GraphicWin = GraphicP
    If GraphicScale <= 0.1 Then GraphicScale = 0.1 _
    Else If GraphicScale > 1000 Then GraphicScale = 1000
    If W > GraphicN - GraphicWin Then W = GraphicN - GraphicWin

    'Data numbers
    Pic.CurrentX = 0: Pic.CurrentY = 2
    Pic.Print "  Time"; Tab(B); ToDhmsEx(GraphicP)
    Pic.Print "  Speed"; Tab(B); ToSpeed(Graphic(GraphicP).Speed)
        Pic.Print Tab(B); ToAirSpeed(Graphic(GraphicP).AirSpeed)
    Pic.Print "  Altitude"; Tab(B); ToAlt(Graphic(GraphicP).Altitude)
    Pic.Print "  Mass"; Tab(B); ToMass(Graphic(GraphicP).Mass)
    Pic.Print "  Gravity"; Tab(B); ToForceG(Graphic(GraphicP).Gravity)
    Pic.Print "  Thrust "; Tab(B); ToForce(Graphic(GraphicP).Thrust)
    Pic.ForeColor = cGraphThrust: Pic.Print "•";: Pic.ForeColor = I
        Pic.Print Tab(B); ToForceG(Graphic(GraphicP).Thrust, Graphic(GraphicP).Mass)
    Pic.ForeColor = cGraphAccel: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " Accel "; Tab(B); ToForceG(Graphic(GraphicP).Accel)
    Pic.ForeColor = cGraphDrag: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " Drag "; Tab(B); ToForceG(Graphic(GraphicP).Drag)
    Pic.ForeColor = cGraphTemp: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " Temp "; Tab(B); ToTemp(Graphic(GraphicP).Temperature)
    Pic.ForeColor = cGraphLink: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " Links"; Tab(B); CStr(Graphic(GraphicP).Links);
    If IsAirspaceDetection Then Pic.Print "  -  " & CStr(Graphic(GraphicP).Airspaces);
    
    If (GraphicMode And &H800&) <> 0 And (IsStationVisibility Or IsAirspaceDetection) Then 'Stations visibility
        For C = 1 To W - 2
            E = 0
            If Graphic(GraphicWin + C).Links Then E = cGraphLink
            If Graphic(GraphicWin + C).Airspaces Then _
                If E Then E = cGraphLinkAirspace Else E = cGraphAirspace
            If E Then Pic.Line (D + C, 2)-Step(0, H - 1), E
            Next
        End If
    Pic.Line (D, 2)-Step(0, H - 1), cGraphLine 'Left margin
    If (GraphicMode And 2) <> 0 And GraphicN > 0 Then 'Data scale
        C = 0: Do
            Pic.Line (D, H - G0 * C)-Step(W - 1, 0), cGraphLine
            If GraphicMode And 8 Then Pic.CurrentX = D: Pic.Print C \ 5;
            C = C + 5: Loop While H - G0 * C > 0
        End If
    If (GraphicMode And 1) <> 0 And GraphicN > 0 Then 'Time scale
        Pic.DrawStyle = vbDot
        C = 60 - (GraphicWin Mod 60)
        If GraphicMode And 4 Then If C = 60 Then _
            Pic.CurrentX = D + 2: Pic.CurrentY = 2: Pic.Print ToDhmsEx(GraphicWin);
        Do While C < W
            Pic.Line (D + C, H)-Step(0, -H), cGraphLine
            If GraphicMode And 4 Then _
                Pic.CurrentX = D + C + 2: Pic.CurrentY = 2: Pic.Print ToDhmsEx(GraphicWin + C);
            C = C + 60: Loop
        Pic.DrawStyle = vbSolid
        End If
    If W > 0 Then Pic.Line (D + W - 1, 2)-Step(0, H - 1), cGraphLine 'Right margin

    'Data lines
    Pic.DrawWidth = 2
    If GraphicMode And &H80& Then Pic.PSet (D, H - Graphic(GraphicWin).Thrust / Graphic(GraphicWin).Mass / 2 * GraphicScale), cGraphThrust: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - Graphic(GraphicWin + C).Thrust / Graphic(GraphicWin + C).Mass / 2 * GraphicScale), cGraphThrust: Next
    If GraphicMode And &H100& Then Pic.PSet (D, H - Graphic(GraphicWin).Accel / 2 * GraphicScale), cGraphAccel: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - Graphic(GraphicWin + C).Accel / 2 * GraphicScale), cGraphAccel: Next
    If GraphicMode And &H200& Then Pic.PSet (D, H - Graphic(GraphicWin).Drag * 5 * GraphicScale), cGraphDrag: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - Graphic(GraphicWin + C).Drag * 5 * GraphicScale), cGraphDrag: Next
    If GraphicMode And &H400& Then Pic.PSet (D, H - Graphic(GraphicWin).Temperature / 200 * G0 * GraphicScale), cGraphTemp: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - Graphic(GraphicWin + C).Temperature / 200 * G0 * GraphicScale), cGraphTemp: Next
    Pic.DrawWidth = 1
    If GraphicP >= GraphicWin Then Pic.Line (D - GraphicWin + GraphicP, 2)-Step(0, H - 1), &HC08040 'Cursor
    
    Else
    
    'KML path graph
    Pic.Cls: I = Pic.ForeColor
    Pic.Print Tab(2);: D = Pic.CurrentX * 22
    Pic.Print: L = Pic.CurrentY
    B = 10: H = Pic.ScaleHeight - 2: W = Pic.ScaleWidth - D
    Select Case Mode '0=Relative -1=Absolute -2=MouseMove -3=MouseDown -4=DblClick -5=MoveWindow
    Case 0: KMLPathP = KMLPathP + Pos
    Case -1: KMLPathP = Pos
    Case -2: KMLPathP = KMLPathWin + X - D: If KMLPathP < KMLPathWin Then KMLPathP = KMLPathWin
    Case -3, -4: If X < D Then GraphicMode = GraphicMode Xor 2 ^ Int(Y / L) * &H100& _
                    Else If Mode = -4 Then GraphicScale = 1
    Case -5: KMLPathP = KMLPathP + Pos: KMLPathWin = KMLPathWin + Pos
        End Select
    If KMLPathWin > KMLPathN - W Then KMLPathWin = KMLPathN - W
    If KMLPathWin < 0 Then KMLPathWin = 0
    If KMLPathP > KMLPathN - 1 Then KMLPathP = KMLPathN - 1
    If KMLPathP < 0 Then KMLPathP = 0
    If KMLPathP > KMLPathWin + W - 1 Then KMLPathWin = KMLPathP - W + 1
    If KMLPathP < KMLPathWin Then KMLPathWin = KMLPathP
    If GraphicScale <= 0.1 Then GraphicScale = 0.1 _
    Else If GraphicScale > 1000 Then GraphicScale = 1000
    If W > KMLPathN - KMLPathWin Then W = KMLPathN - KMLPathWin
    
    'Data numbers
    Pic.CurrentX = 0: Pic.CurrentY = 2
    Pic.Print "  Time"; Tab(B); J2ksTime(KMLPath(KMLPathP).Time)
        Pic.Print Tab(B); ToHmsEx(KMLPath(KMLPathP).Time - KMLPath(0).Time)
    Pic.Print "  Lat"; Tab(B); ToLat(KMLPath(KMLPathP).Lat, FLong, Degrees)
    Pic.Print "  Lon"; Tab(B); ToLon(KMLPath(KMLPathP).Lon, FLong, Degrees)
    Pic.ForeColor = cGraphAltitude: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " Alt"; Tab(B); ToAlt(KMLPath(KMLPathP).Alt, FShort)
    Pic.ForeColor = cGraphSpeed: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " Speed"; Tab(B); ToAirSpeed(KMLPath(KMLPathP).SpeedH)
        Pic.Print Tab(B); ToAlt(KMLPath(KMLPathP).SpeedV, FShort) & "/s"
    Pic.Print "  Density"; Tab(B); ToDensity(KMLPath(KMLPathP).Density, FShort)
    Pic.Print "  Press"; Tab(B); ToPressure(KMLPath(KMLPathP).Pressure, FShort)
    Pic.ForeColor = cGraphTemp: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " Temp"; Tab(B); ToTemp(KMLPath(KMLPathP).Temperature)
    Pic.ForeColor = cGraphHumidity: Pic.Print "•";: Pic.ForeColor = I
    Pic.Print " RH"; Tab(B); NumX(KMLPath(KMLPathP).Humidity, FMini) & "%"
    Pic.Print "  Water"; Tab(B); ToMass(KMLPath(KMLPathP).CloudWater, FShort) & IIf(Units, "/lb", "/kg");
    
    Pic.Line (D, 2)-Step(0, H - 1), cGraphLine 'Left margin
    If (GraphicMode And 2) <> 0 And KMLPathN > 0 Then 'Data scale
        C = 0: Do
            Pic.Line (D, H - G0 * C)-Step(W - 1, 0), cGraphLine
            If GraphicMode And 8 Then Pic.CurrentX = D: Pic.Print C \ 5;
            C = C + 5: Loop While H - G0 * C > 0
        End If
    If (GraphicMode And 1) <> 0 And KMLPathN > 0 Then 'Time scale
        Pic.DrawStyle = vbDot
        C = 60 - (KMLPathWin Mod 60): T1 = KMLPath(0).Time
        If GraphicMode And 4 Then If C = 60 Then Pic.CurrentX = D + 2: Pic.CurrentY = 2: _
            Pic.Print ToHmsEx(KMLPath(KMLPathWin).Time - T1, True);
        Do While C < W
            Pic.Line (D + C, H)-Step(0, -H), cGraphLine
            If GraphicMode And 4 Then Pic.CurrentX = D + C + 2: Pic.CurrentY = 2: _
                Pic.Print ToHmsEx(KMLPath(KMLPathWin + C).Time - T1, True);
            C = C + 60: Loop
        Pic.DrawStyle = vbSolid
        End If
    If W > 0 Then Pic.Line (D + W - 1, 2)-Step(0, H - 1), cGraphLine 'Right margin
    
    'Data lines
    Pic.DrawWidth = 2
    If GraphicMode And &H1000& Then Pic.PSet (D, H - KMLPath(KMLPathWin).Alt / 200 * GraphicScale), cGraphAltitude: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - KMLPath(KMLPathWin + C).Alt / 200 * GraphicScale), cGraphAltitude: Next
    If GraphicMode And &H2000& Then Pic.PSet (D, H - KMLPath(KMLPathWin).SpeedH / Kph / 2 * GraphicScale), cGraphSpeed: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - KMLPath(KMLPathWin + C).SpeedH / Kph / 2 * GraphicScale), cGraphSpeed: Next
    If GraphicMode And &H20000 Then Pic.PSet (D, H - KMLPath(KMLPathWin).Temperature / 20 * G0 * GraphicScale), cGraphTemp: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - KMLPath(KMLPathWin + C).Temperature / 20 * G0 * GraphicScale), cGraphTemp: Next
    If GraphicMode And &H40000 Then Pic.PSet (D, H - KMLPath(KMLPathWin).Humidity / 2 * GraphicScale), cGraphHumidity: _
        For C = 1 To W - 1: Pic.Line -(D + C, H - KMLPath(KMLPathWin + C).Humidity / 2 * GraphicScale), cGraphHumidity: Next
    Pic.DrawWidth = 1
    If KMLPathP >= KMLPathWin Then Pic.Line (D - KMLPathWin + KMLPathP, 2)-Step(0, H - 1), &HC08040 'Cursor
    
    End If
    
    On Error GoTo 0
    End Sub

'__Render_functions____________________________________________________________

Sub Render(Optional ByVal SelectCam As Long = -2)
    Dim N As Long
    
'Sun and planets position
    If GravityMode < 4 Then PlanetPosAll Tool And ToolPlanets
    
'Render
    If SelectedType Then IsObjectValid SelectedType, SelectedN
    If LastType Then IsObjectValid LastType, LastN
    
    If SelectCam > -2 Then
        IsSelect = True
        For N = -1 To CamN - 1
            If SelectCam = N Then _
                If N < 0 Then RenderPl Cam1 _
                Else If Cam(N).hRC Then RenderPl Cam(N)
            Next
        IsSelect = False
        End If
    For N = -1 To CamN - 1
        If SelectCam <> N Then _
            If N < 0 Then RenderPl Cam1 _
            Else If Cam(N).hRC Then RenderPl Cam(N)
        Next
    
    If Cam1.hRC Then
        For N = -1 To CamN - 1
            If N < 0 Then SwapBuffers Cam1.hDC _
            Else If Cam(N).hRC Then SwapBuffers Cam(N).hDC
            Next
        End If
    
'Update sensor title and target list selection
    If IsLastTarget <> IsTarget Then
        IsLastTarget = IsTarget
        If Cam(0).Follow = ToOnBoard Then UpdateViewTarget
        If TargetsN > 0 Then If IsTarget >= 0 Then Setup.SetSelection _
            Else Setup.SelectItem TargetIndex - 1, False
        End If

'Update Google Earth window and Map panel
    If Main.mnPanelsGE.Checked Then LinkGE
    If MapMode > 0 And MapFollow Then Map.UpdatePos , , True
    
    End Sub

Sub RenderPl(Cam As Camera)
    If Cam.Relative >= ToEarth Then Relative = Cam.Relative _
    Else If Target <> ToEarth Then Relative = Target _
    Else If Earth.Altitude >= Atm Then Relative = ToEarth _
    Else Relative = ToAir
    Select Case Relative
    Case ToEarth, ToAir: RenderEx Cam, Earth
    Case ToMoon:    RenderEx Cam, Moon
    Case ToMercury: RenderEx Cam, Mercury
    Case ToVenus:   RenderEx Cam, Venus
    Case ToMars:    RenderEx Cam, Mars
    Case ToJupiter: RenderEx Cam, Jupiter
    Case ToSaturn:  RenderEx Cam, Saturn
    Case ToUranus:  RenderEx Cam, Uranus
    Case ToNeptune: RenderEx Cam, Neptune
    Case ToPluto:   RenderEx Cam, Pluto
    Case Else:      RenderEx Cam, Sun
        End Select
    End Sub

Sub RenderEx(Cam As Camera, Pl As Planet)
    Dim B As Long, C As Long, D As Long, F1 As Single, P As TargetType, _
        H1 As Double, G1 As Double, A1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double
    
    If Cam.ID <> MainID And Cam.hRC = 0 Then Exit Sub
    On Error Resume Next
    
'Auto zoom
    If Cam.Auto Then
        A1 = (PlanetAltitude - FloorAltitude) * 10: If A1 < 20000 Then A1 = 20000 _
            Else If A1 > 400000000 Then A1 = 400000000
        TimeSpeed = 10 ^ Int(VBA.Log(A1) / Log10 + 0.5) / 1000000 / dT
        If TimeSpeed < 1 Then TimeSpeed = 1
        Select Case Cam.Follow: Case ToSimul To ToShip: Cam.Altitude = A1 / Cam.FOVR: End Select
        End If
    
'Camera parameters
    Follow = Cam.Follow: Free = Cam.Free: LookAt = Cam.LookAt: CamR = Cam.FOVR
    IsTactic = False: CamS = Cam.Square: CamWidth = Cam.Width: CamHeight = Cam.Height
    If Follow = ToOnBoard And Cam.hRC <> 0 Then Free = 0: Flat = 0: Ortho = 0: CamH = ZoomMin _
    Else Flat = Cam.Flat: Ortho = Cam.Ortho: CamH = Cam.Altitude
    If Cam.ShipSize = 0 Then ShipFactor = ShipSize Else If Cam.ShipSize < 0 Then ShipFactor = 0 _
        Else ShipFactor = Cam.ShipSize
    TargetPos.Z = 0: TargetPos.W = 0
    ObjectPlanet = ToNone: If LastType = TypeLocation Then _
        If LastN >= 0 And LastN < LocationN Then ObjectPlanet = Location(LastN).Planet
    
CameraLookAt:
    
    'LookAt
    If Cam.AngleV > 0 Then Cam.AngleV = 0 Else If Cam.AngleV < -180 Then Cam.AngleV = -180
    If IsControl Then
        AngleU2 = AngleU: AngleV2 = AngleV: AngleW2 = AngleW
    ElseIf (Relative = ToAir And Pl.AirSpeed > 0) Or (Relative = ToMoon And Pl.Altitude < Atm) Then
        Vector3D Pl.AirSpeedX, Pl.AirSpeedY, Pl.AirSpeedZ, AngleU2, AngleV2, AngleW2
    Else
        Vector3D Pl.SpeedX, Pl.SpeedY, Pl.SpeedZ, AngleU2, AngleV2, AngleW2
        End If
    
CameraFollow:
    
    'Follow
    If Flat Then
        If Not Free Then XYZ2LLA Earth, Y1, X1, Z1: _
            Cam.X = X1 * Earth.Radius: Cam.Y = Y1 * Earth.Radius: Cam.Z = Z1
        CamX = Cam.X: CamY = Cam.Y: CamZ = Cam.Z
    Else
        Select Case Follow
        Case ToSimul:   CamX = 0: CamY = 0: CamZ = 0
        Case ToTactic:  If Free Then CamX = Cam.X: CamY = Cam.Y: CamZ = Cam.Z _
                        Else CamX = 0: CamY = 0: CamZ = 0
        Case ToOnBoard: CamX = 0: CamY = 0: CamZ = 0
        Case ToShip:    If Free Then CamX = Cam.X + Pl.X: CamY = Cam.Y + Pl.Y: CamZ = Cam.Z + Pl.Z _
                        Else CamX = 0: CamY = 0: CamZ = 0
        Case ToObject:  If LastType = TypePlanet Then Follow = LastN: GoTo CameraFollow _
                        Else If Free Then CamX = Cam.X: CamY = Cam.Y: CamZ = Cam.Z _
                        Else ObjectPosition LastType, LastN, CamX, CamY, CamZ
        Case Else:      If Free Then CamX = Cam.X + Pl.X: CamY = Cam.Y + Pl.Y: CamZ = Cam.Z + Pl.Z _
                        Else PlanetPosition Follow, CamX, CamY, CamZ
            End Select
        End If
    
Render2D:
    
'GDI Render
    
    If Cam.hRC = 0 Then Render2D Cam, Pl: GoTo RenderEnd
    
RenderOpenGL:
    
'OpenGL Render
    
    Matrix2D = Cam.Matrix2D
    If wglGetCurrentContext <> Cam.hRC Then wglMakeCurrent Cam.hDC, Cam.hRC
    
'Camera setup
    glMatrixMode gl_Modelview: glLoadIdentity
    glDepthMask 1
    ClearColor IfInt(Follow = ToOnBoard, 0, ViewerBack)
    glDepthMask 0
    If Antialias Then glEnable gl_MultiSample_Arb 'Activate antialiasing
    glListBase TextFont.List
    
    If Not Flat Then GoTo SetupSpheric
    
SetupFlat:
    
'Camera setup in flat mode
    If Cam.AngleV > -90 Then Cam.AngleV = -90
    glTranslatef 0, 0, -CamH
    glRotatef Cam.AngleV - 90, 1, 0, 0: glRotatef -Cam.AngleU - 180, 0, 0, 1
    glTranslatef CamX, -CamY, CamZ: glScalef -Earth.Radius, Earth.Radius, -1
    
    GoTo RenderInit
    
SetupSpheric:

'Camera setup in spheric mode
    If Follow <> ToOnBoard Then glTranslatef 0, 0, -CamH
    
    Select Case Plane
    Case Ecliptic: CamU = 0: CamW = 0
    Case Equatorial: CamU = Earth.AngleU / Rad: CamW = 0
    Case Orbital: A1 = Time / Day
        CamU = (Moon.OrbitI + Moon.OrbitIV * A1) / Rad
        CamW = (Moon.OrbitO + Moon.OrbitOV * A1) / Rad
        End Select
    
    If Follow = ToSimul And LookAt <= ToShip Then
        glRotatef Cam.AngleV - 90, 1, 0, 0
        glRotatef Cam.AngleW, 0, 0, 1
        glRotatef -Cam.AngleU, 0, 1, 0
        glRotatef ATan3(AngleU2, AngleV2, AngleW2) / Rad + 180, 1, 0, 0
        glRotatef ATan(AngleU2, AngleV2) / Rad + 180, 0, 0, 1
    
    ElseIf Follow = ToOnBoard Then
        If IsControl = 2 Then
            glRotatef 180, 0, 1, 0: ReverseMultMatrix Attitude
        Else
            glRotatef 90, 0, 0, 1
            glRotatef ATan3(AngleU2, AngleV2, AngleW2) / Rad, 1, 0, 0
            glRotatef ATan(AngleU2, AngleV2) / Rad + 180, 0, 0, 1
            End If
    
    ElseIf Follow = ToTactic Then
        If LinkMode = TrackLink Then
            If TrackFollow = 1 Then
                If Track2.Valid Then GoTo TacticStat Else GoTo TacticShip
            ElseIf TrackFollow = 2 Then
                If Track1.Valid Then GoTo TacticTrack Else GoTo TacticShip
            ElseIf Track1.Valid Then
                If Track2.Valid Then If TrackSync = 0 Then GoTo TacticStat
TacticTrack:
                Y1 = Track1.Lat: X1 = Track1.Lon: Z1 = Track1.Alt
            ElseIf Track2.Valid Then
TacticStat:
                Y1 = Track2.Lat: X1 = Track2.Lon: Z1 = Track2.Alt
            Else
                GoTo TacticShip
                End If
            LLA2XYZ Earth, Y1, X1, Z1, CamX, CamY, CamZ, , , True, True
        ElseIf Free Then
            H1 = Pl.Radius / Cam.FOVR: X1 = CamX / H1: Y1 = CamY / H1: Z1 = CamZ
            If Y1 > A90 Then Y1 = A90: Cam.Y = Y1 * H1 _
            Else If Y1 < -A90 Then Y1 = -A90: Cam.Y = Y1 * H1
            LLA2XYZ Pl, Y1, X1, Z1, CamX, CamY, CamZ, , , True, True
        Else
TacticShip:
            XYZ2LLA Pl, Y1, X1, Z1: CamX = 0: CamY = 0: CamZ = 0
            End If
        If Cam.AngleV > -90 Then Cam.AngleV = -90
        glRotatef Cam.AngleV, 1, 0, 0: glRotatef 180 - Cam.AngleU, 0, 1, 0
        glRotatef 180 - Y1 / Rad, 1, 0, 0: glRotatef -X1 / Rad, 0, 0, 1
        PlanetGLEx Pl
        IsTactic = True
    
    ElseIf Follow = ToObject Then
        If Not IsObjectTactic(LastType, LastN, P) Then GoTo NoObject
        
        If Free Then
            H1 = PlanetRadius(P) / Cam.FOVR: X1 = CamX / H1: Y1 = CamY / H1: Z1 = CamZ
            If Y1 > A90 Then Y1 = A90: Cam.Y = Y1 * H1 _
            Else If Y1 < -A90 Then Y1 = -A90: Cam.Y = Y1 * H1
            LLA2XYZID P, Y1, X1, Z1, CamX, CamY, CamZ, , , True, True
        Else
            XYZ2LLAID P, Y1, X1, Z1, CamX, CamY, CamZ
            End If
        
        If Cam.AngleV > -90 Then Cam.AngleV = -90
        glRotatef Cam.AngleV, 1, 0, 0: glRotatef 180 - Cam.AngleU, 0, 1, 0
        glRotatef 180 - Y1 / Rad, 1, 0, 0: glRotatef -X1 / Rad, 0, 0, 1
        PlanetGLID P
        IsTactic = True
    
    Else
NoObject:
        A1 = Cam.AngleU
        Select Case LookAt
        Case ToNone: GoTo NoLookAt
        Case ToSimul, ToTactic, ToShip
            If Follow = ToSimul Or Follow = ToTactic Or Follow = ToShip Then GoTo NoLookAt
            X1 = 0: Y1 = 0
        Case ToObject: If Follow = ToObject Then GoTo NoLookAt
            If Not ObjectPosition(LastType, LastN, X1, Y1) Then GoTo NoLookAt
        Case Else: If Follow = LookAt Or _
                (LookAt = ToPluto And (Tool And ToolAsteroids) <> 0) Then GoTo NoLookAt
            PlanetPosition LookAt, X1, Y1
            End Select
        A1 = ATan(CamX - X1, CamY - Y1) / Rad
NoLookAt:
        glRotatef Cam.AngleV, 1, 0, 0: glRotatef Cam.AngleW, 0, 1, 0 'Camera
        glRotatef A1 + CamW, 0, 0, 1 'LookAt
        glRotatef -CamU, 1, 0, 0: glRotatef -CamW, 0, 0, 1 'Main plane correction
        End If
    
    If Follow = ToTactic Or (Follow >= ToShip And Follow <= ToObject) Then _
        glTranslatef -CamX, -CamY, -CamZ
    
RenderInit:
    
    CameraXYZ Cam.CenterX, Cam.CenterY, Cam.CenterZ
    
'Render options
    glDepthFunc gl_LEqual 'gl_Less gl_LEqual
    glBlendFunc gl_Src_Alpha, gl_One_Minus_Src_Alpha
    'glBlendFunc gl_Src_Alpha_Saturate, gl_One
    'glBlendFunc gl_Src_Alpha, gl_Dst_Alpha
    'glPolygonMode gl_Front_And_Back, gl_Line
    'glPolygonMode gl_Front, gl_Fill: glPolygonMode gl_Back, gl_Line
    glCullFace gl_Back
    glColorMaterial gl_Front_And_Back, gl_Ambient_And_Diffuse
    glLineStipple 1, &HF0F0
    
    glEnable gl_Cull_Face
    'glEnable gl_Normalize
    glEnable gl_Color_Material
    glEnable gl_Blend
    If Tool And ToolSmooth Then glEnable gl_Point_Smooth: glEnable gl_Line_Smooth _
    Else glDisable gl_Point_Smooth: glDisable gl_Line_Smooth
    'glEnable gl_Polygon_Smooth
    'glEnable gl_Texture_2d
    'glEnable gl_Lighting
    'glDisable gl_Depth_Test
    
'Iconic elements size
    If Ortho Or Flat Then H1 = CamH _
    Else glRasterPos3f 0, 0, 0: glGetFloatv gl_Current_Raster_Distance, F1: _
        If F1 < -ZoomMin Then H1 = -F1 Else H1 = CamH
    H1 = H1 * CamR: ShipScale = H1 / 60: VectorScale = H1 / 30000: ForceScale = H1 / 300
    RangeScale = CamH / 60 * CamR: PointScale = CamH / 1000
    
RenderFlat:
    
'Flat mode
    
    If Flat = 0 Then GoTo RenderSpheric
    
'Planet
    Light3D 100, -100, 1000000000, SunShadow
    PlanetFlat3D Earth, , cPlanetEarth
    If Tool And ToolGrid And CamH < Earth.Radius * hPlanetsGrid Then GridFlat3D Earth
    
    glDisable gl_Cull_Face
    
'Atmospheric model layers
    If Tool And ToolAtmosphere Then If CamH < hAtmosphere Then AtmosphereFlat3D
    
'Boundaries
    If ToolX And ToolBoundaries Then BoundariesFlat3D
    
'Airways
    If (Tool And ToolAirways) <> 0 Or SelectedType = TypeAirspace Or _
        SelectedType = TypeAirport Or SelectedType = TypeSeaport Then AirwaysFlat3D
    
'Path
    If Tool And ToolPath Then PathFlat3D KMLPath, KMLPathN: PathAirFlat3D
    
'Reference points
    If (Tool And ToolLaunch) <> 0 And CamH < hReferences Then
        'Launch site
        Text3D "Launch", Lon, Lat, Alt, , , GreenDot
        'LatLon0, North and South Poles
        If (Tool And ToolLocations) = 0 Or (ToolL And LocationsLabels) = 0 Or TargetsN > 0 Then _
            Text3D "S", , -A90, , , , GreenDot: Text3D "N", , A90, , , , GreenDot
        Text3D "0", , , , , , GreenDot
        End If
    
'Cities
    If (Tool And ToolLocations) <> 0 Or SelectedType = TypeCity Then CitiesFlat3D
    
'Locations
    If (Tool And ToolLocations) <> 0 And CamH < hLocations Then _
        If Layers And LayersPlaces Then SelType = TypeLocation: _
            LocationsFlat3D (ToolL And LocationsLabels) <> 0 And (Layers And PlacesLabels) <> 0
    If IsTarget < 0 And TargetClass = TypeLocation Then _
        StationLineFlat3D Location(TargetItem), _
            (Tool And ToolLocations) <> 0 And (ToolL And LocationsLabels) <> 0 And _
            (Layers And LayersPlaces) <> 0 And (Layers And PlacesLabels) <> 0
    If SelectedType = TypeLocation Then SelN = SelectedN: _
        LocationFlat3D Location(SelN)
    
'Radio-stations
    If (Tool And ToolStations) <> 0 And CamH < hStations Then _
        SelType = TypeStation: StationsFlat3D ToolL And StationsLabels
    If IsTarget < 0 And TargetClass = TypeStation Then StationLineFlat3D Station(TargetItem)
    If SelectedType = TypeStation Then SelN = SelectedN: _
        StationFlat3D Station(SelN), True
    
'Satellites
    If Tool And ToolSatellites Then _
        SelType = TypeSatellite: SatellitesFlat3D ToolL And SatellitesLabels, _
            (ToolX And ToolObjects) <> 0 And (Tool And ToolRanges) <> 0
    If SelectedType = TypeSatellite Then SelN = SelectedN: _
        SatelliteFlat3D Satellite(SelN), True, True, True
    
    SelType = TypeNone
    
'Snapshots
    If Tool And ToolSnap Then
        If KMLPathN > 0 And (Tool And ToolTime) <> 0 Then
            For C = 0 To KMLPointN - 1: With KMLPoint(C)
                If (Tool And ToolInfo) <> 0 And .Time <> 0 Then _
                    Text3D J2ksTimeEx(.Time) & " " & .Name, .Lon, .Lat, .Alt, , , GreenDot _
                Else Text3D .Name, .Lon, .Lat, .Alt, , , GreenDot
                End With: Next
            End If
        For C = 0 To SnapN - 1: PointAirFlat Snap(C): Next
        End If
    

'Reference compass
    If ToolX And ToolCompass Then CompassFlat3D
    

'Tracking mode position
    If Track1.Valid Then TrackFlat3D Track1
    If Track2.Valid Then TrackFlat3D Track2
    
'Ship
    ShipFlat3D Thrust, Throttle
    
'Selection cursor
    If SelectedType Then CursorFlat3D
    
    GoTo RenderOverlay
    
'Spherical mode
    
RenderSpheric:
    
'Stars
    If Tool And ToolStars Then Stars3D
    If ToolL And StarsLabels Then If Tool And ToolPlanets Then StarTrackerModels3D
    
RenderSun:
    
'Sun
    Light3D Sun.X, Sun.Y, Sun.Z, SunShadow, _
        Switch(SunShadow >= 0.3, 2, SunShadow >= 0, 2.5, True, 3)
    If ToolX And ToolGlow Then
        H1 = PointZ(Sun.X, Sun.Y, Sun.Z): glCallList Flare.List
        If -H1 < hSunGlow Then
            Disk3D Sun.X, Sun.Y, Sun.Z, _
                Sun.Radius * 0.9, Sun.Radius * 1.2, 5, cGlowSunC, cGlowSunB, True
            Disk3D Sun.X, Sun.Y, Sun.Z, _
                Sun.Radius * 1.2, Sun.Radius * 2, 5, cGlowSunB, cGlowSunA, True
        Else
            Disk3D Sun.X, Sun.Y, Sun.Z, Sun.Radius, _
                Sun.Radius * 1.8, 5, cGlowSunC, cGlowSunA, True
            End If
        End If
    
    glEnable gl_Depth_Test
    
RenderPlanets:
    
'Planets
    glDepthMask 1
    
    'Sun, Earth and Moon
    Planet3D Sun, 5, cPlanetSun, False
    C = Follow >= ToSimul And Follow <= ToShip And _
        TerrainMode >= 2 And (Tool And ToolTerrain) <> 0 And Not Free
    If C And Earth.Altitude + CamH < Earth.Radius / 4 Then TerrainEarth3D cPlanetEarth _
    Else Planet3D Earth, 1, cPlanetEarth
    If C And Moon.Altitude + CamH < Moon.Radius / 4 Then TerrainMoon3D cPlanetMoon _
    Else Planet3D Moon, 2, cPlanetMoon
    
    'Planets
    D = (Tool And ToolPlanets) <> 0: B = SelectedType = TypePlanet
    If D Or (B And SelectedN = ToMercury) Then Planet3D Mercury, 5, cPlanetMercury
    If D Or (B And SelectedN = ToVenus) Then Planet3D Venus, 5, cPlanetVenus
    If D Or (B And SelectedN = ToMars) Then _
        If C And Mars.Altitude + CamH < Mars.Radius / 4 Then TerrainMars3D cPlanetMars _
        Else Planet3D Mars, 2, cPlanetMars
    If D Or (B And SelectedN = ToJupiter) Then Planet3D Jupiter, 5, cPlanetJupiter
    If D Or (B And SelectedN = ToSaturn) Then Planet3D Saturn, 5, cPlanetSaturn
    If D Or (B And SelectedN = ToUranus) Then Planet3D Uranus, 5, cPlanetUranus
    If D Or (B And SelectedN = ToNeptune) Then Planet3D Neptune, 5, cPlanetNeptune
    If D Or (B And SelectedN = ToPluto) Then _
        If (Tool And ToolAsteroids) = 0 Then Planet3D Pluto, 5, cPlanetPluto
    
    'Moons
    D = (Tool And ToolMoons) <> 0: B = SelectedType = TypeMoon
    If D Or (B And SelectedN = Io.N) Then MinorPlanet3D Io, Moons(Io.N), cPlanetIo
    If D Or (B And SelectedN = Europa.N) Then MinorPlanet3D Europa, Moons(Europa.N), cPlanetEuropa
    If D Or (B And SelectedN = Ganymede.N) Then MinorPlanet3D Ganymede, Moons(Ganymede.N), cPlanetGanymede
    If D Or (B And SelectedN = Callisto.N) Then MinorPlanet3D Callisto, Moons(Callisto.N), cPlanetCallisto
    If D Or (B And SelectedN = Titan.N) Then MinorPlanet3D Titan, Moons(Titan.N), cPlanetTitan
    If D Or (B And SelectedN = Triton.N) Then MinorPlanet3D Triton, Moons(Triton.N), cPlanetTriton
    
    'Asteroids
    D = (Tool And ToolAsteroids) <> 0: B = SelectedType = TypeAsteroid
    If D Or (B And SelectedN = Ceres.N) Then _
        If AsteroidP = 0 Or Asteroid(Ceres.N).Class = AsteroidP Then _
            MinorPlanet3D Ceres, Asteroid(Ceres.N), cPlanetCeres
    
    glDepthMask 0
    
RenderGrid:
    
'Meridians and parallels
    If Tool And ToolGrid And Follow <> ToOnBoard Then
        If Target = ToEarth Or Follow = ToEarth Or ObjectPlanet = ToEarth Or Target = ToMoon Then Grid3D Earth
        If Target = ToMoon Or Follow = ToMoon Or ObjectPlanet = ToMoon Then Grid3D Moon
        If Tool And ToolPlanets Then
            If Target = ToMercury Or Follow = ToMercury Or ObjectPlanet = ToMercury Then Grid3D Mercury
            If Target = ToVenus Or Follow = ToVenus Or ObjectPlanet = ToVenus Then Grid3D Venus
            If Target = ToMars Or Follow = ToMars Or ObjectPlanet = ToMars Then Grid3D Mars
            'If Target = ToJupiter Or Follow = ToJupiter Or ObjectPlanet = ToJupiter Then Grid3D Jupiter
            'If Target = ToSaturn Or Follow = ToSaturn Or ObjectPlanet = ToSaturn Then Grid3D Saturn
            'If Target = ToUranus Or Follow = ToUranus Or ObjectPlanet = ToUranus Then Grid3D Uranus
            'If Target = ToNeptune Or Follow = ToNeptune Or ObjectPlanet = ToNeptune Then Grid3D Neptune
            'If Target = ToPluto Or Follow = ToPluto Or ObjectPlanet = ToPluto Then Grid3D Pluto
            End If
        End If
    
    glDisable gl_Cull_Face
    glDisable gl_Depth_Test
    
RenderGlow:
    
'Earth atmosphere
    If ToolX And ToolGlow And _
            CamH < hPlanetsGlow And (Follow <> ToOnBoard Or Earth.Altitude > hPlanetsGlowL) Then
        Glow3D Earth, Cam, hEarthGlowH, hEarthGlowL, cGlowEarthG, cGlowEarthA
        If Tool And ToolPlanets Then Glow3D Mars, Cam, hMarsGlowH, hMarsGlowL, cGlowMarsG, cGlowMarsA
        End If
    
'Onboard sensor target
    If Follow = ToOnBoard Then
        If Status = Deployed Then
            Select Case IsTarget
            Case Is < 0: If TargetsN <= 0 Then Station2XYZ Station(TargetItem), X1, Y1, Z1 _
                Else If Not ObjectPosition(TargetClass, TargetItem, X1, Y1, Z1) Then GoTo TargetEarth
            Case ToEarth To ToPluto: PlanetPosition IsTarget, X1, Y1, Z1
            Case ToObject: If SelectedType = TypeShip Then GoTo TargetEarth
                If Not ObjectPosition(SelectedType, SelectedN, X1, Y1, Z1) Then GoTo TargetEarth
            Case Else
TargetEarth:
                X1 = Earth.X: Y1 = Earth.Y: Z1 = Earth.Z
                End Select
            glRasterPos3f X1, Y1, Z1: RasterPos TargetPos
        Else
            TargetPos.Z = 0
            End If
        If Tool And ToolSensor Then
            If ToolX And ToolLabels Then GoTo RenderPoints
            If ToolX And ToolCrosshair Then GoTo RenderCrosshair
            End If
        GoTo RenderOverlay
        End If
    
    If (Tool And ToolHidden) = 0 Then glEnable gl_Depth_Test
    
RenderPath:
    
'Path
    If Tool And ToolPath Then
        Select Case Relative
        Case ToEarth:   PathEarth3D
        Case ToAir:     PathAir3D
        Case ToSun:     PathSun3D
        Case Else:      PathTarget3D Pl
            End Select
        End If
    
RenderRanges:
    
'Ranges
    If Tool And ToolRanges Then
        'Planets orbits
        If ToolX And ToolOrbits Then
            If CamH > hPlanetsOrbit Then
                If Tool And ToolPlanets Then
                    Orbit3D Mercury, Sun
                    Orbit3D Venus, Sun
                    Orbit3D Mars, Sun
                    Orbit3D Jupiter, Sun
                    Orbit3D Saturn, Sun
                    Orbit3D Uranus, Sun
                    Orbit3D Neptune, Sun
                    If (Tool And ToolAsteroids) = 0 Then Orbit3D Pluto, Sun
                    End If
                'Earth orbit
                Orbit3D Earth, Sun
                End If
            'Moon orbit
            If CamH < hMoonOrbit Then Orbit3D Moon, Earth
            End If
        'Orbital plane
        If ToolX And ToolPlane Then Plane3D Lat, Lon, Heading, _
            Earth.Radius + RangeScale / 50, Moon.OrbitDistance, , cOrbitalPlane, cOrbitalPlaneM
        If Follow = ToTactic Then GoTo NoFollowRanges 'No main plane projections in follow mode
        Select Case Relative
        'Perigee, Apogee and Altitude relative to the surface and to the Z position
        Case ToEarth, ToAir
            If ToolX And ToolApsis Then
                glEnable gl_Line_Stipple
                If MinHe > 0 And MinHe <> Earth.Altitude And Not IsApogee Then _
                    Circle3D Earth.X, Earth.Y, Earth.Z, Earth.Radius + MinHe, , cPeriapsis
                If MaxHe > 0 And MaxHe <> Earth.Altitude Then _
                    Circle3D Earth.X, Earth.Y, Earth.Z, Earth.Radius + MaxHe, , cApoapsis
                glDisable gl_Line_Stipple
                End If
            If ToolX And ToolOrbit Then Circle3D Earth.X, Earth.Y, Earth.Z, Earth.Distance, , cAltitudeCircle
            If ToolX And ToolReference Then If Earth.Altitude > Atm Then _
                Altitude3D , , , Earth.X, Earth.Y, Earth.Z, cAltitudePlane
        'Periselene, Aposelene and Altitude relative to the surface and to the Z position
        Case ToMoon
            If ToolX And ToolApsis Then
                glEnable gl_Line_Stipple
                If MinHm > 0 And MinHm <> Moon.Altitude Then _
                    Circle3D Moon.X, Moon.Y, Moon.Z, Moon.Radius + MinHm, , cPeriapsis
                If MaxHm > 0 And MaxHm <> Moon.Altitude And Not IsPeriselene Then _
                    Circle3D Moon.X, Moon.Y, Moon.Z, Moon.Radius + MaxHm, , cApoapsis
                glDisable gl_Line_Stipple
                End If
            If ToolX And ToolOrbit Then _
                Circle3D Moon.X, Moon.Y, Moon.Z, Moon.Distance, , cAltitudeCircle
            If ToolX And ToolReference Then If Moon.Altitude > Atm Then _
                Altitude3D , , , Moon.X, Moon.Y, Moon.Z, cAltitudePlane
        'Planets Altitudes
        Case Else
            If ToolX And ToolOrbit Then Circle3D Pl.X, Pl.Y, Pl.Z, Pl.Distance, , cAltitudeCircle
            If ToolX And ToolReference Then If Pl.Altitude > Atm Then _
                Altitude3D , , , Pl.X, Pl.Y, Pl.Z, cAltitudePlane
            End Select
        'Earth atmosphere and ionosphere
        If ToolX And ToolIonosphere Then
            'Sphere3D Earth.X, Earth.Y, Earth.Z, Earth.Radius + Atm, cAtmosphereA
            'Sphere3D Earth.X, Earth.Y, Earth.Z, Earth.Radius + Ion, cIonosphereA
            Disk3D Earth.X, Earth.Y, Earth.Z, Earth.Radius, Earth.Radius + Atm, , cAtmosphereB, cAtmosphereA
            Disk3D Earth.X, Earth.Y, Earth.Z, Earth.Radius + Atm, Earth.Radius + Ion, , cIonosphereB, cIonosphereA
            End If
NoFollowRanges:
        'Braking distance
        If Status = Transfer And ToolX And ToolBraking Then
            If Moon.AirSpeed > 0 Then _
                Disk3D Moon.X, Moon.Y, Moon.Z, Moon.Radius, Moon.Radius + Abs(Brake), , cBrakingB, cBrakingA _
            Else If Tool And ToolPlanets Then If Mars.AirSpeed > 0 Then _
                Disk3D Mars.X, Mars.Y, Mars.Z, Mars.Radius, Mars.Radius + Abs(Brake), , cBrakingB, cBrakingA
            End If
        End If
    
RenderAtmosphere:
    
'Atmospheric model layers
    If Tool And ToolAtmosphere Then If CamH < hAtmosphere Then Atmosphere3D
    
RenderMagnetic:
    
'Magnetic field
    If Tool And ToolMagnetic Then Magnetic3D
    
RenderGravity:
    
'Gravity field
    If Tool And ToolGravity Then Gravity3D CamH * CamR * 0.6, CamX, CamY, CamZ
    
RenderBoundaries:

'Boundaries
    If ToolX And ToolBoundaries Then Boundaries3D
    
RenderAirways:
    
'Airways
    If Tool And ToolAirways Then Airways3D
    
RenderTrackingPath:
    
'Tracking paths
    If Tool And ToolPath Then
        If KMLPathN Then Path3D KMLPath, KMLPathN, ToolX And ToolAltitude
        If Track1.Valid Then Path3D Track1.Path, Track1.N + 1, TrackAltitude
        If Track2.Valid Then Path3D Track2.Path, Track2.N + 1, TrackAltitude
        End If
    
RenderTrajectory:
    
    'Trajectory prediction
    If Tool And ToolTrajectory Then _
        Trajectory3D Pl, IfDbl(Pl.Class = ClassGasGiant, 1, 0.5), Relative = ToAir
    
RenderPoints:
    
    glDisable gl_Depth_Test
    
'Points
    
RenderAsteroids:
    
'Asteroids
    If Tool And ToolAsteroids Then SelType = TypeAsteroid: Asteroids3D ToolL And AsteroidsLabels, _
        (ToolX And ToolObjects) <> 0 And (Tool And ToolRanges) <> 0 And Follow <> ToOnBoard And _
        CamH > hAsteroidsOrbit
    If SelectedType = TypeAsteroid Then SelN = SelectedN: _
        Asteroid3D Asteroid(SelN), Time + T, True, Follow <> ToOnBoard, True
    
RenderComets:
    
'Comets
    If Tool And ToolComets Then SelType = TypeComet: Comets3D ToolL And CometsLabels, _
        (ToolX And ToolObjects) <> 0 And (Tool And ToolRanges) <> 0 And Follow <> ToOnBoard And _
        CamH > hCometsOrbit
    If SelectedType = TypeComet Then SelN = SelectedN: _
        Asteroid3D Comet(SelN), Time + T, True, Follow <> ToOnBoard, True
    
RenderMoons:
    
'Moons
    If Tool And ToolMoons Then SelType = TypeMoon: Moons3D ToolL And MoonsLabels, _
        (ToolX And ToolObjects) <> 0 And (Tool And ToolRanges) <> 0 And Follow <> ToOnBoard And _
        CamH > hMoonsOrbit
    If SelectedType = TypeMoon Then SelN = SelectedN: _
        Asteroid3D Moons(SelN), Time + T, True, Follow <> ToOnBoard, True
    
RenderReferences:
    
'Reference points
    SelType = TypeNone
    If Tool And ToolLaunch And CamH < hReferences Then
        'Launch site
        PointPlanet3D Earth, IIf(KMLPathPos, "Balloon", "Launch"), Lat, Lon, Alt
        'LatLon0 point, North and South Poles
        If Target = ToEarth Or Follow = ToEarth Or ObjectPlanet = ToEarth Or Target = ToMoon Then _
            PointPlanet3D Earth, "0": If (Tool And ToolLocations) = 0 Or (ToolL And LocationsLabels) = 0 Or _
                TargetsN > 0 Then PointPlanet3D Earth, "S", -A90: PointPlanet3D Earth, "N", A90
        If Target = ToMoon Or Follow = ToMoon Or ObjectPlanet = ToMoon Then PointsPlanet3D Moon
        If Tool And ToolPlanets Then
            If Target = ToMercury Or Follow = ToMercury Or ObjectPlanet = ToMercury Then PointsPlanet3D Mercury
            If Target = ToVenus Or Follow = ToVenus Or ObjectPlanet = ToVenus Then PointsPlanet3D Venus
            If Target = ToMars Or Follow = ToMars Or ObjectPlanet = ToMars Then PointsPlanet3D Mars
            'If Target = ToJupiter Or Follow = ToJupiter Or ObjectPlanet = ToJupiter Then PointsPlanet3D Jupiter
            'If Target = ToSaturn Or Follow = ToSaturn Or ObjectPlanet = ToSaturn Then PointsPlanet3D Saturn
            'If Target = ToUranus Or Follow = ToUranus Or ObjectPlanet = ToUranus Then PointsPlanet3D Uranus
            'If Target = ToNeptune Or Follow = ToNeptune Or ObjectPlanet = ToNeptune Then PointsPlanet3D Neptune
            'If Target = ToPluto Or Follow = ToPluto Or ObjectPlanet = ToPluto Then PointsPlanet3D Pluto
            End If
        If Follow = ToOnBoard Then GoTo RenderLabels
        If Target <> ToMoon And Follow <> ToMoon Then GoTo RenderMoonLabel
    Else
RenderLabels:
        'Planets ghosts
        If (ToolX And ToolGhosts) <> 0 Or SelectedType = TypeGhost Then
            SelType = TypeGhost
            'TextPlanetGhost3D Sun, Sun', False
            D = (Tool And ToolPlanets) <> 0 And (ToolX And ToolGhosts) <> 0
            B = SelectedType = TypeGhost: C = Cam1.Target <= ToShip
            If (D And (C Or Cam1.Target = ToMercury)) Or _
                (B And SelectedN = ToMercury) Then TextPlanetGhost3D Mercury, Sun
            If (D And (C Or Cam1.Target = ToVenus)) Or _
                (B And SelectedN = ToVenus) Then TextPlanetGhost3D Venus, Sun
            If (D And (C Or Cam1.Target = ToMars)) Or _
                (B And SelectedN = ToMars) Then TextPlanetGhost3D Mars, Sun
            If (D And (C Or Cam1.Target = ToJupiter)) Or _
                (B And SelectedN = ToJupiter) Then TextPlanetGhost3D Jupiter, Sun
            If (D And (C Or Cam1.Target = ToSaturn)) Or _
                (B And SelectedN = ToSaturn) Then TextPlanetGhost3D Saturn, Sun
            If (D And (C Or Cam1.Target = ToUranus)) Or _
                (B And SelectedN = ToUranus) Then TextPlanetGhost3D Uranus, Sun
            If (D And (C Or Cam1.Target = ToNeptune)) Or _
                (B And SelectedN = ToNeptune) Then TextPlanetGhost3D Neptune, Sun
            If (D And (C Or Cam1.Target = ToPluto)) Or _
                (B And SelectedN = ToPluto) Then _
                If (Tool And ToolAsteroids) = 0 Then TextPlanetGhost3D Pluto, Sun
            D = (ToolX And ToolGhosts) <> 0
            If (D And (C Or Cam1.Target = ToEarth)) Or _
                (B And SelectedN = ToEarth) Then TextPlanetGhost3D Earth, Sun, A1
            If (D And (C Or Cam1.Target = ToMoon)) Or _
                (B And SelectedN = ToMoon) Then If CamH < hMoonOrbit Then TextPlanetGhost3D Moon, Earth ', False
            End If
        'Planets names
        SelType = TypePlanet
        TextPlanet3D Sun, Sun, False
        D = (Tool And ToolPlanets) <> 0: B = SelectedType = TypePlanet
        If D Or (B And SelectedN = ToMercury) Then TextPlanet3D Mercury, Sun
        If D Or (B And SelectedN = ToVenus) Then TextPlanet3D Venus, Sun
        If D Or (B And SelectedN = ToMars) Then TextPlanet3D Mars, Sun
        If D Or (B And SelectedN = ToJupiter) Then TextPlanet3D Jupiter, Sun
        If D Or (B And SelectedN = ToSaturn) Then TextPlanet3D Saturn, Sun
        If D Or (B And SelectedN = ToUranus) Then TextPlanet3D Uranus, Sun
        If D Or (B And SelectedN = ToNeptune) Then TextPlanet3D Neptune, Sun
        If D Or (B And SelectedN = ToPluto) Then _
            If (Tool And ToolAsteroids) = 0 Then TextPlanet3D Pluto, Sun
        TextPlanet3D Earth, Sun
RenderMoonLabel:
        SelType = TypePlanet
        If CamH < hMoonOrbit Then TextPlanet3D Moon, Earth ', False
        End If
    
RenderStarLabels:
    
'Brightest stars names
    If ToolL And StarsLabels Then SelType = TypeStar: StarTracker3D
    If SelectedType = TypeStar Then StarTrackerN3D SelectedN
    
RenderAirwaysLabels:
    
'Airways, airports and seaports
    If (Tool And ToolAirways) <> 0 Or SelectedType = TypeAirspace Or _
        SelectedType = TypeAirport Or SelectedType = TypeSeaport Then AirwaysLabels3D
    
RenderCities:
    
'Cities
    If (Tool And ToolLocations) <> 0 Or SelectedType = TypeCity Then Cities3D
    
RenderLocations:
    
'Locations
    If Tool And ToolLocations And CamH < hLocations Then _
        If Layers And LayersPlaces Then SelType = TypeLocation: _
            Locations3D (ToolL And LocationsLabels) <> 0 And (Layers And PlacesLabels) <> 0
    If IsTarget < 0 And TargetClass = TypeLocation Then StationLine3D Location(TargetItem), _
        (Tool And ToolLocations) <> 0 And (ToolL And LocationsLabels) <> 0 And _
        (Layers And LayersPlaces) <> 0 And (Layers And PlacesLabels) <> 0
    If SelectedType = TypeLocation Then SelN = SelectedN: _
        Location3D Location(SelN)
    
RenderStations:
    
'Radio-stations
    If Tool And ToolStations And CamH < hStations Then _
        SelType = TypeStation: Stations3D ToolL And StationsLabels
    If IsTarget < 0 And TargetClass = TypeStation Then StationLine3D Station(TargetItem)
    If SelectedType = TypeStation Then SelN = SelectedN: _
        Station3D Station(SelN), SelN, True
    
    SelType = TypeNone
    
RenderSatellites:
    
'Satellites
    If Tool And ToolSatellites Then _
        SelType = TypeSatellite: Satellites3D ToolL And SatellitesLabels, _
            (ToolX And ToolObjects) <> 0 And (Tool And ToolRanges) <> 0 And Follow <> ToOnBoard
    If SelectedType = TypeSatellite Then SelN = SelectedN: _
        Satellite3D Satellite(SelN), True, Follow <> ToOnBoard, True
    
RenderCrosshair:
    
'Crosshair, Deviation and Trace
    If Follow = ToOnBoard Then
        If ToolX And ToolCrosshair Then Crosshair3D
        If (ToolX And ToolDeviation) <> 0 Or (ToolX And ToolTrace) <> 0 Then Trace2D Cam
        If ToolX And ToolLabels Then
            If (ToolX And ToolFOV) <> 0 And (ToolX And ToolFOVArea) <> 0 Then FOVArea2D Cam
            If ToolX And ToolFOVTime Then FOVTime2D Cam
            End If
        GoTo RenderOverlay
        End If
    
RenderSnapshots:
    
'Snapshots
    If Tool And ToolSnap Then
        If KMLPathN > 0 And (Tool And ToolSnap) <> 0 Then
            For C = 0 To KMLPointN - 1: With KMLPoint(C)
                If (Tool And ToolInfo) <> 0 And .Time <> 0 Then _
                    PointPlanet3D Earth, J2ksTimeEx(.Time) & " " & .Name, .Lat, .Lon, .Alt _
                Else PointPlanet3D Earth, .Name, .Lat, .Lon, .Alt
                End With: Next
            End If
        Select Case Relative
        Case ToEarth:   PointsEarth
        Case ToAir:     PointsAir
        Case ToMoon:    PointsMoon
        Case ToMercury: PointsMercury
        Case ToVenus:   PointsVenus
        Case ToMars:    PointsMars
        Case ToJupiter: PointsJupiter
        Case ToSaturn:  PointsSaturn
        Case ToUranus:  PointsUranus
        Case ToNeptune: PointsNeptune
        Case ToPluto:   PointsPluto
        Case Else:      PointsSun
            End Select
        End If
    
RenderGhosts:
    
'Ghosts
    If Tool And ToolPath Then
        If Tool And ToolInfo Then
            For B = PathsN - 1 To 0 Step -1: With Paths(B)
                If .PathN > 0 And (B <> PathsP Or IsPathModified = 0) Then
                    C = .PointTime: If C < 0 Then C = 0 Else If C >= .PathN Then C = .PathN - 1
                    D = C - 1: If D < 0 Then D = 0
                    Select Case Relative
                    Case ToEarth:   PointEarthEx .Preset.Name, .Path(D), .Path(C)
                    Case ToAir:     PointAirEx .Preset.Name, .Path(D), .Path(C)
                    Case ToSun:     PointSunEx .Preset.Name, .Path(D), .Path(C)
                    Case Else:      PointTargetEx Pl, .Preset.Name, .Path(D), .Path(C)
                        End Select
                    End If
                End With: Next
            End If
        End If
    
RenderStages:
    
'Empty stages
    If KMLPathPos = 0 And IsEmptyStages <> 0 Then _
        For C = -(KMLPathN = 0) To StageC: Stage3D StageE(C), C: Next
    
RenderTrack:
    
'Tracking mode position
    If Track1.Valid Then Track3D Track1
    If Track2.Valid Then Track3D Track2
    
RenderVectors:
    
    If (Tool And ToolHidden) = 0 Then glEnable gl_Depth_Test
    
'Vectors
    If Tool And ToolVectors Then
        If Tool And ToolAltimeter Then
            'Altimeter
            Altimeter3D Pl
        ElseIf Zone >= Proximity And ToolX And ToolAltitude Then
            'Altitude to the surface
            PlanetDataEx Target, A1, H1, X1, Y1, Z1
            H1 = A1 / H1: Line3D , , , X1 * H1, Y1 * H1, Z1 * H1, cAltitudeProj
            End If
    'Ship
        'Line3D , , , AngleU * ShipScale * 2, AngleV * ShipScale * 2, AngleW * ShipScale * 2, cWhite
    'Relative speed
        If ToolX And ToolSpeed Then _
            If Relative = ToSun Then Line3D , , , _
                SpeedX * VectorScale, SpeedY * VectorScale, SpeedZ * VectorScale, cSpeedCenter _
            Else Line3D , , , Pl.SpeedX * VectorScale, Pl.SpeedY * VectorScale, Pl.SpeedZ * VectorScale, cSpeedCenter
        If ToolX And ToolGroundSpeed Then _
            If Pl.AirSpeed > 0 Then Line3D , , , _
                Pl.AirSpeedX * VectorScale, Pl.AirSpeedY * VectorScale, Pl.AirSpeedZ * VectorScale, cSpeedGround
    'Acceleration
        'Line3D , , , AX * ForceScale, AY * ForceScale, AZ * ForceScale, cAcceleration
    'Gravity minus centripetal acceleration
        If ToolX And ToolNetGravity Then If Target = ToEarth Or Target = ToMoon Then _
            G1 = (Earth.Gravity - Earth.CentripetalAccel) / Earth.Distance * ForceScale: _
            H1 = Moon.Gravity / Moon.Distance * ForceScale: _
            Line3D , , , G1 * Earth.X + H1 * Moon.X, G1 * Earth.Y + H1 * Moon.Y, G1 * Earth.Z + H1 * Moon.Z, cNetGravity
    'Centripetal acceleration
        'G1 = -Pl.CentripetalAccel / Pl.Distance * ForceScale: Line3D , , , G1 * Pl.X, G1 * Pl.Y, G1 * Pl.Z, cCentripetal
    'Vertical speed
        If ToolX And ToolVertSpeed Then If AirSpeed > 0 Then G1 = -VerticalSpeed * VectorScale * 10: _
            Line3D , , , G1 * AltitudeX, G1 * AltitudeY, G1 * AltitudeZ, cSpeedVertical
    'Horizontal speed
        'Line3D , , , Sqr(Pl.HorizontalSpeed2) * VectorScale, , , cSpeedHorizontal
    'Air vector
        'If Pl.AirSpeed > 0 Then Line3D , , , (Pl.SpeedX - Pl.AirSpeedX) * VectorScale * 5, _
            (Pl.SpeedY - Pl.AirSpeedY) * VectorScale * 5, (Pl.SpeedZ - Pl.AirSpeedZ) * VectorScale * 5, cNetGravity
    'Wind vector
        'If AtmMode > 0 Then G1 = -VectorScale * 100: Line3D , , , _
            (AirSpeedX * AirSpeed - Pl.AirSpeedX) * G1, (AirSpeedY * AirSpeed - Pl.AirSpeedY) * G1, _
            (AirSpeedZ * AirSpeed - Pl.AirSpeedZ) * G1, cAirLift
    'Horizontal vector
        'If AirDensity > 0 Then H1 = ForceScale * 10: _
            Line3D , , , AirLiftU * H1, AirLiftV * H1, AirLiftW * H1, cNetGravity
    'Vertical vector
        'If AirDensity > 0 Then H1 = ForceScale * 10: _
            Line3D , , , AirRollU * H1, AirRollV * H1, AirRollW * H1, cNetGravity
    'Magnetic field vector
        If ToolX And ToolMagneticLine Then MagneticVector3D
    'Air drag and lift
        If ToolX And ToolAirDrag Then If AirDensity > 0 Then _
            G1 = -Drag * ForceScale: Line3D , , , AirSpeedX * G1, AirSpeedY * G1, AirSpeedZ * G1, cAirDrag: _
            G1 = Lift * ForceScale: Line3D , , , AirLiftU * G1, AirLiftV * G1, AirLiftW * G1, cAirLift
        End If
    
RenderDistances:
    
    'Reference distances
    If ToolX And ToolDistances Then Distances3D Pl
    
    glDisable gl_Depth_Test
    
RenderCompass:
    
'Reference compass
    If ToolX And (ToolCompass Or ToolCompassGrid) Then Compass3D Pl
    
RenderAxes:
    
'Sun, Earth, Moon, Ship and Camera axes
    If Tool And ToolAxes Then
        G1 = Moon.Mass / Earth.Mass: H1 = 1 - G1
        Axe3D Moon.X * G1 + Earth.X * H1, Moon.Y * G1 + Earth.Y * H1, Moon.Z * G1 + Earth.Z * H1, , cAxesLine
        AxePlanet3D Earth
        AxePlanet3D Moon
        AxePlanet3D Sun, True
        If Tool And ToolPlanets Then
            AxePlanet3D Mercury
            AxePlanet3D Venus
            AxePlanet3D Mars
            AxePlanet3D Jupiter, True
            AxePlanet3D Saturn, True
            AxePlanet3D Uranus, True
            AxePlanet3D Neptune, True
            AxePlanet3D Pluto, True
            End If
        If Free Then
            If IsTactic Then
                Point3D "Camera", CamX, CamY, CamZ, GreenDot, True
            Else
                Axe3D CamX, CamY, CamZ, RangeScale * 2, cAxesLine
                Altitude3D CamX, CamY, CamZ, Earth.X, Earth.Y, Earth.Z, cAltitudePlane
                Text3D "Camera", CamX, CamY, CamZ, 5, -1, GreenDot
                End If
            End If
        'If Cam.ID <> MainID Then If Cam1.hRC Then _
        '    With Cam1: Axe3D .CenterX, .CenterY, .CenterZ, , cAxesLine: _
        '    Text3D "Camera", .CenterX, .CenterY, .CenterZ: End With
        'For C = 0 To CamN - 1
        '    If Cam.N <> C Then If Cam(C).hRC Then _
        '        With Cam(C): Axe3D .CenterX, .CenterY, .CenterZ, , cAxesLine: _
        '            Text3D "Camera " & C + 2, .CenterX, .CenterY, .CenterZ: End With
        '    Next
        Axe3D , , , , cAxesLine
        End If
    
RenderShip:
    
'Ship
    'If SelectedType = TypeNone Then SelType = TypeShip: SelN = 0
    'glRasterPos3f 0, 0, 0: If IsVisible Then GoTo NoShip
    If Tool And ToolStage Then ShipInfo Pl, _
        Relative = ToEarth Or Relative = ToAir Or (Relative = ToMars And GravityMode >= 4)
    'SelType = TypeNone
    Ship3D AngleU2, AngleV2, AngleW2, Thrust, Throttle
    
RenderCursor:
    
'Selection cursor
    If SelectedType Then Cursor3D Pl
    
RenderOverlay:
    
'Overlay
    If Cam.ID = MainID And Cam1.Follow <> ToOnBoard Then
        Mode2D
        OverlayY = 0: glDisable gl_Line_Smooth ': glDisable gl_Polygon_Smooth
        'Logo
        If Logo.Visible Then PaintImage Logo
        'Selected item info
        If SelectedType Then Info3D
        'Color table
        If Tool And ToolInfo Then _
            If (Tool And ToolAtmosphere) <> 0 Or (Tool And ToolGravity) <> 0 Then ColorScale2D
        'Report tool
        If Tool And ToolReport Then Report3D
        Mode3D
        End If
    
RenderEnd:
    
    On Error GoTo 0
    
    End Sub

Sub Render2D(Cam As Camera, Pl As Planet)
    Dim C As Long, CX2 As Single, CY2 As Single, _
        H1 As Double, A1 As Double
    
    On Error Resume Next
    
'Initialize render
    
    If Main.Visible Then CX2 = Int(Main.ScaleWidth / 2): CY2 = Int(Main.ScaleHeight / 2) _
    Else CX2 = Int(ScreenWidth / 2): CY2 = Int(ScreenHeight / 2)
    If CX2 >= CY2 Then CamZ = CX2 Else CamZ = CY2
    'CamZ = CamZ * 2 * 0.289 '1215=980x800
    CamZ = CamZ / Tan(Cam.FOV * Rad / 2)
    '30=1.87(0.268)  45=1.200(0.414)  90=0.500(0.500)  120=0.289(1.732)
    CamH = CamZ / CamH: CamX = CX2 - CamX * CamH: CamY = CY2 + CamY * CamH
    Main.Cls
    Main.FillStyle = vbFSTransparent
    Main.ForeColor = ViewerColor
    
'Ranges
    If Tool And ToolRanges Then
        'Earth atmosphere
        If ToolX And ToolIonosphere Then
            Circle2D Earth.X, Earth.Y, Earth.Radius + Ion, cIonosphere2D
            Circle2D Earth.X, Earth.Y, Earth.Radius + Atm, cAtmosphere2D
            End If
        'Planets orbits
        If ToolX And ToolOrbits Then
            Circle2D Earth.X, Earth.Y, Moon.OrbitDistance, cPlanetOrbit2D
            If CamH > hPlanetsOrbit Then
                Circle2D Sun.X, Sun.Y, Earth.OrbitDistance, cPlanetOrbit2D
                If Tool And ToolPlanets Then
                    Circle2D Sun.X, Sun.Y, Mercury.OrbitDistance, cPlanetOrbit2D
                    Circle2D Sun.X, Sun.Y, Venus.OrbitDistance, cPlanetOrbit2D
                    Circle2D Sun.X, Sun.Y, Mars.OrbitDistance, cPlanetOrbit2D
                    Circle2D Sun.X, Sun.Y, Jupiter.OrbitDistance, cPlanetOrbit2D
                    Circle2D Sun.X, Sun.Y, Saturn.OrbitDistance, cPlanetOrbit2D
                    Circle2D Sun.X, Sun.Y, Uranus.OrbitDistance, cPlanetOrbit2D
                    Circle2D Sun.X, Sun.Y, Neptune.OrbitDistance, cPlanetOrbit2D
                    Circle2D Sun.X, Sun.Y, Pluto.OrbitDistance, cPlanetOrbit2D
                    End If
                End If
            End If
        Main.DrawStyle = vbDot
        If Relative = ToMoon Then
            If ToolX And ToolApsis Then
                'Periselene
                If MinHm > 0 Then _
                    Circle2D Moon.X, Moon.Y, Moon.Radius + MinHm, cPeriapsis2D
                'Aposelene
                If MaxHm > 0 And Not IsPeriselene Then _
                    Circle2D Moon.X, Moon.Y, Moon.Radius + MaxHm, cApoapsis2D
                End If
            'Altitude
            If ToolX And ToolOrbit Then _
                Circle2D Moon.X, Moon.Y, Moon.Distance, cAltitudeCircle2D
            'Braking distance
            If ToolX And ToolBraking Then If Status = Transfer And Moon.AirSpeed > 0 Then _
                Circle2D Moon.X, Moon.Y, Moon.Radius + Abs(Brake), cBraking2D
        Else
            If ToolX And ToolApsis Then
                'Perigee
                If MinHe > 0 And Not IsApogee Then _
                    Circle2D Earth.X, Earth.Y, Earth.Radius + MinHe, cPeriapsis2D
                'Apogee
                If MaxHe > 0 Then _
                    Circle2D Earth.X, Earth.Y, Earth.Radius + MaxHe, cApoapsis2D
                End If
            'Altitude
            If ToolX And ToolOrbit Then _
                Circle2D Earth.X, Earth.Y, Earth.Distance, cAltitudeCircle2D
            End If
        Main.DrawStyle = vbSolid
        End If

    Main.FillStyle = IfInt(Tool And ToolTextures, vbFSSolid, vbFSTransparent)
    
'Planets
    Planet2D Sun, cPlanetSun2D, cPlanetSun2D
    Planet2D Earth, cPlanetEarth2D
    Planet2D Moon, cPlanetMoon2D
    If Tool And ToolPlanets Then
        Planet2D Mercury, cPlanetMercury2D
        Planet2D Venus, cPlanetVenus2D
        Planet2D Mars, cPlanetMars2D
        Planet2D Jupiter, cPlanetJupiter2D
        Planet2D Saturn, cPlanetSaturn2D
        Planet2D Uranus, cPlanetUranus2D
        Planet2D Neptune, cPlanetNeptune2D
        Planet2D Pluto, cPlanetPluto2D
        End If
    Main.FillStyle = vbFSTransparent
    
'Engine
    If Thrust <> 0 And (ShowIcons And 1) <> 0 And ShipFactor > 0 Then
        Main.FillStyle = vbFSSolid
        Main.FillColor = cShipEngineA2D
        Main.Circle (CamX - 1 - AngleU2 * ShipFactor * 2, CamY + AngleV2 * ShipFactor * 2), _
            Throttle * ShipFactor * 4 + ShipFactor * 2, Main.FillColor
        Main.FillColor = cShipEngineB2D
        Main.Circle (CamX - 1 - AngleU2 * ShipFactor * 2, CamY + AngleV2 * ShipFactor * 2), _
            Throttle * ShipFactor * 2 + ShipFactor, Main.FillColor
        Main.FillStyle = vbFSTransparent
        End If
    
'Path
    If Tool And ToolPath And PathP > 0 Then
        Main.ForeColor = cPath2D
        Select Case Relative
        Case ToEarth, ToAir
            For C = 0 To PathP - 1: With Path(C)
                If C Then Main.Line -(CamX + (Earth.X - .Earth.X) * CamH, CamY - (Earth.Y - .Earth.Y) * CamH) _
                Else Main.PSet (CamX + (Earth.X - .Earth.X) * CamH, CamY - (Earth.Y - .Earth.Y) * CamH)
                End With: Next
        Case ToMoon
            For C = 0 To PathP - 1: With Path(C)
                If C Then Main.Line -(CamX + (Moon.X - .Target.X) * CamH, CamY - (Moon.Y - .Target.Y) * CamH) _
                Else Main.PSet (CamX + (Moon.X - .Target.X) * CamH, CamY - (Moon.Y - .Target.Y) * CamH)
                End With: Next
        Case Else
            For C = 0 To PathP - 1: With Path(C)
                If C Then Main.Line -(CamX + (Sun.X - .Sun.X) * CamH, CamY - (Sun.Y - .Sun.Y) * CamH) _
                Else Main.PSet (CamX + (Sun.X - .Sun.X) * CamH, CamY - (Sun.Y - .Sun.Y) * CamH)
                End With: Next
            End Select
        If PathP < UBound(Path) Then Main.Line -(CamX, CamY)
        Main.ForeColor = ViewerColor
        End If
    
'Reference points
    If Tool And ToolLaunch And CamH < hReferences Then
        'Earth Greenwich meridian
        PointEarth2D Earth, "0º"
        'Moon Greenwich meridian
        PointEarth2D Moon, "0º"
        'Launch site
        PointEarth2D Earth, "Launch", , Lat, Lon, Alt
        End If
    
'Snapshots
    If Tool And ToolSnap Then
        Main.ForeColor = cSnapLabel2D
        For C = SnapN - 1 To 0 Step -1
            With Snap(C)
            Select Case Relative
            Case ToEarth, ToAir: X1 = .Ship.X + Earth.X - .Earth.X: Y1 = .Ship.Y + Earth.Y - .Earth.Y
            Case ToMoon: X1 = .Ship.X + Moon.X - .Moon.X: Y1 = .Ship.Y + Moon.Y - .Moon.Y
            Case Else: X1 = .Ship.X + Sun.X - .Sun.X: Y1 = .Ship.Y + Sun.Y - .Sun.Y
                End Select
            If .StatusText <> "" Then
                Main.Circle (Int(CamX + X1 * CamH) + 0.5, Int(CamY - Y1 * CamH) + 0.5), 1.5, cSnapPoint2D
                Main.CurrentX = Main.CurrentX + 3: Main.Print .StatusText;
            ElseIf (Tool And (ToolTime Or ToolPath)) = (ToolTime Or ToolPath) Then
                Main.Circle (CamX + X1 * CamH, CamY - Y1 * CamH), 1, cSnapPoint2D
                Main.CurrentX = Main.CurrentX + 3: Main.Print ToDh(.T);
                End If
            End With
            Next
        Main.ForeColor = ViewerColor
        End If
    
'Vectors
    If Tool And ToolVectors Then
        'Relative speed
        If ToolX And ToolSpeed Then _
            Main.Line (CamX, CamY)-Step(Pl.SpeedX / 10, -Pl.SpeedY / 10), cSpeedCenter2D
        If ToolX And ToolGroundSpeed Then _
            If Relative = ToEarth Or Relative = ToAir Or Relative = ToMoon Then If Pl.AirSpeed > 0 Then _
                Main.Line (CamX, CamY)-Step(Pl.AirSpeedX / 10, -Pl.AirSpeedY / 10), cSpeedGround2D
        'Altimeter
        If Tool And ToolAltimeter And Main.Visible Then
            H1 = CamH: If Units Then H1 = H1 / Foot
            A1 = 10 ^ (1 - Int(VBA.Log(H1) / Log10))
            If Units Then A1 = A1 * Foot
            Main.CurrentX = CX2 * 2 - 38 - IfInt(Units, 12, 0): Main.CurrentY = 5
            Main.Print ToAlt(A1, "0")
            Main.ForeColor = cAltimeterLine2D
            Main.PSet (CX2 * 2 - 8, 20): Main.Line -Step(0, Round(H1 * A1)): Main.Line -Step(-3, 0)
            Main.ForeColor = ViewerColor
            End If
        End If
    
'Ship
    If (ShowIcons And 1) <> 0 And ShipFactor > 0 Then
        Main.DrawWidth = ShipFactor * 3
        H1 = 40: A1 = 5
        Main.PSet (CamX - 1, CamY), cShipModel2D
        Main.Line -Step(AngleU2 * ShipFactor * H1, -AngleV2 * ShipFactor * H1), cShipModel2D
        Main.Line (CamX - 1 - AngleV2 * ShipFactor * A1, CamY - AngleU2 * ShipFactor * A1)-Step( _
            AngleV2 * ShipFactor * A1 * 2, AngleU2 * ShipFactor * A1 * 2), cShipModel2D
        Main.Line -(CamX - 1 + AngleU2 * ShipFactor * A1, CamY - AngleV2 * ShipFactor * A1), cShipModel2D
        Main.Line -(CamX - 1 - AngleV2 * ShipFactor * A1, CamY - AngleU2 * ShipFactor * A1), cShipModel2D
        Main.DrawWidth = 1
    Else
        Main.FillStyle = vbFSSolid
        Main.FillColor = cWhiteDot
        Main.Circle (CamX, CamY), 2, cWhiteDot
        Main.FillStyle = vbFSTransparent
        End If
    If Tool And ToolStage Then
        Main.CurrentX = CamX: Main.CurrentY = CamY
        Main.Print " " & Switch(StageP = 0, "S", StageP = 1, "H", True, StageN);
        If Tool And ToolInfo Then
            If Pl.AirSpeed = 0 Or Pl.Altitude >= Atm Or SpeedInfo Then _
                Main.Print "  " & ToSpeed(Pl.Speed, FMini) _
            Else If Relative <> ToEarth And Relative <> ToAir And (Relative <> ToMars Or GravityMode < 4) Then _
                Main.Print "  " & ToSpeed(Pl.AirSpeed, FMini) _
            Else If KMLPathPos <= 0 Then _
                Main.Print "  " & ToAirSpeed(Pl.AirSpeed, FMini) _
            Else _
                Main.Print "  " & ToAirSpeed(KMLPath(KMLPathPos).SpeedH, FMini)
            Main.CurrentX = CamX: Main.Print "     " & ToAlt(Pl.Altitude, FMini, Not AltitudeInfo);
            End If
        End If
    If TextFont.Color Then Main.ForeColor = TextFont.Color And &HFFFFFF
    
    On Error GoTo 0
    
    End Sub

Sub RenderModels()
    If Cam3.hRC Then RenderModel Cam3
    If Cam4.hRC Then RenderModel Cam4
    End Sub

Sub RenderGraphs()
    Dim N As Long
    For N = 0 To GraN - 1
        If Gra(N).hRC Then RenderModel Gra(N)
        Next
    End Sub

Sub RenderModel(Cam As Camera)
    Dim C As Long, Q As Boolean, G1 As Double, H1 As Double, _
        U1 As Double, V1 As Double, W1 As Double
    
    With Cam
    If .hRC = 0 Or .F Is Nothing Then Exit Sub
    CamHeight = .Height
    Matrix2D = .Matrix2D
    If wglGetCurrentContext <> .hRC Then wglMakeCurrent .hDC, .hRC
    
    glMatrixMode gl_Modelview: glLoadIdentity
    ClearColor .F.BackColor
    If Antialias Then glEnable gl_MultiSample_Arb 'Activate antialiasing
    If ModelFont.List = 0 Then CreateFont ModelFont, Main.ModelFont
    glListBase ModelFont.List
    
    glDepthFunc gl_LEqual
    glBlendFunc gl_Src_Alpha, gl_One_Minus_Src_Alpha
    glColorMaterial gl_Front_And_Back, gl_Ambient_And_Diffuse
    glEnable gl_Cull_Face
    glEnable gl_Color_Material
    glEnable gl_Blend
    If Tool And ToolSmooth Then glEnable gl_Point_Smooth: glEnable gl_Line_Smooth _
    Else glDisable gl_Point_Smooth: glDisable gl_Line_Smooth
    
    H1 = 1000000
    If .ID = RoverID Then
        Light3D 0, H1, 0, SunShadow, 1
        glTranslatef 0, 0, -.Altitude
        glRotatef .AngleV, 1, 0, 0
        glRotatef .AngleU, 0, 1, 0
    ElseIf .ID = StageID Then
        Light3D -H1, H1, H1, SunShadow, 1
        glTranslatef -.X, 0, -.Altitude
        glRotatef .AngleV - 90, 1, 0, 0
        glRotatef .AngleU, 0, 0, 1
        glTranslatef 0, 0, -.Y
    ElseIf .ID = GraphID Then
        glTranslatef 0, 0, -.Altitude
        glRotatef .AngleV - 90, 1, 0, 0
        glRotatef .AngleU, 0, 0, 1
        glTranslatef -.X, -.Y, -.Z
        Light3D H1, H1, H1, SunShadow / 3 + 0.4, SunShadow / 3 + 0.3
        End If
    glPushMatrix
    If .ID = RoverID Then
        With Rover(0)
        'H1 = 0: .U = 0: .V = Cos(H1 * Rad) * 5: .W = Sin(H1 * Rad) * 5
        U1 = ATanEx(.W, .V): W1 = ATanEx(.U, .V)
        glRotatef -U1 / Rad, 1, 0, 0
        glRotatef -W1 / Rad, 0, 0, 1
        End With
        End If
    glPushMatrix
    'H1 = ShipFactor: glScalef H1, H1, H1
    'If Tool And ToolSmooth Then glEnable gl_Polygon_Smooth
    glColor4ubv cWhite
    If .ID = RoverID Then
        glCallList RoverList
    ElseIf .ID = StageID Then
        glCallList Switch( _
            Stages.Pos = PayloadMax, SatelliteList, _
            Stages.Pos = HoverMax, HoverList, _
            True, StageList + Stages.Pos)
    ElseIf .ID = GraphID Then
        With Graph2D(.N)
        Q = (.X1 <> 0 Or .X2 <> 0 Or .Y1 <> 0 Or .Y2 <> 0) And _
            (.X1 <> .X2 Or .Y1 <> .Y2)
        If Q Then
            If IsSelect Then PlotSelect3D Graph2D(Cam.N)
            If .List = 0 Then .List = glGenLists(2): If .List Then _
                glNewList .List, gl_Compile: PlotGraph3D Graph2D(Cam.N): glEndList: _
                glNewList .List + 1, gl_Compile: PlotLines3D Graph2D(Cam.N): glEndList
            If .Tools And GraphSolid Then glPushMatrix: _
                glTranslatef 0, 0, -Cam.Altitude / 500 * 360 / Cam.Square
            glCallList .List
            If .Tools And GraphSolid Then glPopMatrix
            glCallList .List + 1
        ElseIf .List Then
            DeleteList .List, 2
            End If
        End With
        End If
    'If Tool And ToolSmooth Then glDisable gl_Polygon_Smooth
    glDepthMask 1
    If .ID = GraphID And Q Then PlotAxes3D Graph2D(.N)
    glPopMatrix
    If .ID = RoverID Or .ID = StageID Then
        C = cAxesModel: H1 = IIf(.ID = RoverID, .Altitude / 3.5, .Altitude / 4)
        Line3D , , , H1, , , C: TextFont3D ModelFont, "X", H1, , , -3, 0
        Line3D , , , , H1, , C: TextFont3D ModelFont, "Y", , H1, , -3, 0
        Line3D , , , , , H1, C: TextFont3D ModelFont, "Z", , , H1, -3, 0
        End If
    glPopMatrix
    If .ID = RoverID Then
        Mode2D
        TextFont2D ModelFont, , _
            "Pitch: " & ToAngle(U1) & "  Roll: " & ToAngle(W1), 4, .Height - TextFont.Y - 4
        Mode3D
    ElseIf .ID = GraphID And Q Then
        Mode2D
        PlotInfo3D Graph2D(.N), Cam
        Mode3D
        End If
    
    SwapBuffers .hDC
    End With
    
    End Sub

Sub PlotGraph3D(G As Graph2D)
    Dim C As Long, Q As Boolean, _
        X1 As Long, Y1 As Long, Z1 As Double, Z2 As Double, _
        YL As Long, ZL As Double, ZL2 As Double, _
        X3 As Double, Y3 As Double, Z3 As Double, _
        XL As Long, XR As Long, YD As Long, YU As Long
    
    'Graph planes
    If G.Tools And GraphWireframe Then Exit Sub
    glDisable gl_Cull_Face
    Q = G.Tools And GraphSolid: If Q Then glEnable gl_Depth_Test: glDepthMask 1 _
    Else glDisable gl_Depth_Test
    If Q And G.Tools And GraphShadows Then glEnable gl_Lighting: glEnable gl_Normalize _
    Else glDisable gl_Lighting
    
    Select Case (G.Tools And GraphFaceMask) \ GraphFaceShift
    Case 0: C = 0: Case 1: C = cGraph3DFaceBlue: Case 2: C = cGraph3DFaceRed
    Case 3: C = cGraph3DFaceGreen: Case 4: C = cGraph3DFaceYellow: Case 5: C = cGraph3DFaceGray
    Case Else: C = cGraph3DFaceWhite
        End Select
    If C Then glColor4ubv C
    For Y1 = G.Y1 To G.Y2 - G.YT Step G.YT 'AP
        Z1 = 0: ZL = 0: YL = Y1 + G.YT
        For X1 = G.X1 To G.X2 Step G.XT 'ST
            Z2 = Z1: Z1 = G.Value(X1, Y1) * G.ScaleZ
            ZL2 = ZL: ZL = G.Value(X1, YL) * G.ScaleZ
            If Z1 <> 0 And ZL <> 0 Then
                If Z2 = 0 Or ZL2 = 0 Then glBegin gl_Quad_Strip
                If C = 0 Then PlotColor G, ZL
                If G.Tools And GraphShadows Then PlotNormal G, X1, YL
                glVertex3f X1, YL, ZL
                If C = 0 Then PlotColor G, Z1
                If G.Tools And GraphShadows Then PlotNormal G, X1, Y1
                glVertex3f X1, Y1, Z1
                If X1 >= G.X2 Then GoTo EndZ
            ElseIf Z2 <> 0 And ZL2 <> 0 Then
EndZ:
                glEnd
                End If
            Next
        Next
    
    If G.Tools And GraphSolid Then glDepthMask 0
    
    End Sub

Sub PlotLines3D(G As Graph2D)
    Dim C As Long, D As Long, E As Long, N As Long, _
        G1 As Double, H1 As Double, _
        Y0 As Long, L1 As Double, L2 As Double, _
        X1 As Long, Y1 As Long, Z1 As Double, _
        X2 As Long, Y2 As Long, Z2 As Double
    
    'Reference plane
    If G.Tools And (GraphAltX Or GraphAltY Or GraphRangesZ) Then
        glColor4ubv cGraph3DPlane
        H1 = G.Z1: G1 = G.Z2
        Do
            glBegin gl_Line_Strip
            glVertex3f G.X1, G.Y1, H1: glVertex3f G.X2, G.Y1, H1
            glVertex3f G.X2, G.Y2, H1: glVertex3f G.X1, G.Y2, H1
            glVertex3f G.X1, G.Y1, H1
            glEnd
            If (G.Tools And GraphRangesZ) = 0 Or H1 >= G.Z2 Then Exit Do
            If H1 = G.Z1 Then H1 = G.ZS1 Else H1 = H1 + G.ZST
            Loop
        If H1 > G.Z1 Then
            glBegin gl_Lines
            glVertex3f G.X1, G.Y1, G.Z1: glVertex3f G.X1, G.Y1, H1
            glVertex3f G.X2, G.Y1, G.Z1: glVertex3f G.X2, G.Y1, H1
            glVertex3f G.X2, G.Y2, G.Z1: glVertex3f G.X2, G.Y2, H1
            glVertex3f G.X1, G.Y2, G.Z1: glVertex3f G.X1, G.Y2, H1
            glEnd
            End If
        End If
    
    'Graph lines
    Select Case (G.Tools And GraphLineMask) \ GraphLineShift
    Case 0: C = 0: Case 1: C = cGraph3DLineBlue: Case 2: C = cGraph3DLineRed
    Case 3: C = cGraph3DLineGreen: Case 4: C = cGraph3DLineGray
    Case Else: C = cGraph3DLineBlack
        End Select
    D = IfInt(G.Tools And GraphAltX, 0, 1) ': E = IfInt(G.Tools And GraphLinesX, 1, 0)
    For N = D To 1 'E
        E = IfInt(N = 0 Or (G.Tools And GraphLinesX) <> 0, G.YT, G.Y2 - G.Y1)
        If E <= 0 Then E = 1
        For Y1 = G.Y1 To G.Y2 Step E 'AP
            Z1 = 0
            For X1 = G.X1 To G.X2 Step G.XT 'ST
                Z2 = Z1: Z1 = G.Value(X1, Y1) * G.ScaleZ
                If Z1 <> 0 Then
                    X2 = X1: Y2 = Y1
                    If Z2 = 0 Then
                        If N = 0 Then
                            glColor4ubv cGraph3DPlane: glBegin gl_Line_Strip
                            If G.Tools And GraphShadows Then glNormal3f -1, 0, 0
                            glVertex3f X2, Y2, G.Z1: glVertex3f X1, Y1, Z1: glEnd
                        Else
                            If C Then glColor4ubv C
                            glBegin gl_Line_Strip
                            End If
                        End If
                    If N Then
                        If C = 0 Then PlotColor G, Z1, 0.7
                        If G.Tools And GraphShadows Then PlotNormal G, X1, Y1
                        glVertex3f X1, Y1, Z1
                        End If
                    If X1 >= G.X2 Then Z2 = Z1: GoTo EndX
                ElseIf Z2 <> 0 Then
EndX:
                    If N Then
                        glEnd
                    Else
                        glColor4ubv cGraph3DPlane: glBegin gl_Line_Strip
                        If G.Tools And GraphShadows Then glNormal3f 1, 0, 0
                        glVertex3f X2, Y2, Z2: glVertex3f X2, Y2, G.Z1
                        glEnd
                        End If
                    End If
                Next
            Next
        Next
    D = IfInt(G.Tools And GraphAltY, 0, 1) ': E = IfInt(G.Tools And GraphLinesY, 1, 0)
    For N = D To 1 'E
        E = IfInt(N = 0 Or (G.Tools And GraphLinesY) <> 0, G.XT, G.X2 - G.X1)
        If E <= 0 Then E = 1
        For X1 = G.X1 To G.X2 Step E 'ST
            Z1 = 0
            For Y1 = G.Y1 To G.Y2 Step G.YT 'AP
                Z2 = Z1: Z1 = G.Value(X1, Y1) * G.ScaleZ
                If Z1 <> 0 Then
                    X2 = X1: Y2 = Y1
                    If Z2 = 0 Then
                        If N = 0 Then
                            glColor4ubv cGraph3DPlane: glBegin gl_Line_Strip
                            If G.Tools And GraphShadows Then glNormal3f 0, -1, 0
                            glVertex3f X2, Y2, G.Z1: glVertex3f X1, Y1, Z1: glEnd
                        Else
                            If C Then glColor4ubv C
                            glBegin gl_Line_Strip
                            End If
                        End If
                    If N Then
                        If C = 0 Then PlotColor G, Z1, 0.7
                        If G.Tools And GraphShadows Then PlotNormal G, X1, Y1
                        glVertex3f X1, Y1, Z1
                        End If
                    If Y1 >= G.Y2 Then Z2 = Z1: GoTo EndY
                ElseIf Z2 <> 0 Then
EndY:
                    If N Then
                        glEnd
                    Else
                        glColor4ubv cGraph3DPlane: glBegin gl_Line_Strip
                        If G.Tools And GraphShadows Then glNormal3f 0, 1, 0
                        glVertex3f X2, Y2, Z2: glVertex3f X2, Y2, G.Z1: glEnd
                        End If
                    End If
                Next
            Next
        Next
    
    If G.Tools And GraphSolid Then glDisable gl_Depth_Test
    If G.Tools And GraphShadows Then glDisable gl_Lighting
    
    End Sub

Sub PlotAxes3D(G As Graph2D)
    Dim C As Long, G1 As Double, H1 As Double, X0 As Long, Y0 As Long
    
    'If G.Tools And GraphSolid Then glEnable gl_Depth_Test
    
    'Axes
    If G.Tools And GraphAxes Then
    
        C = cGraph3DAxes: H1 = 1.15: G1 = 1.21
        If G.XS <> 0 Then _
            Line3D CDbl(G.X1), CDbl(G.Y1), G.Z1, G.XS * H1, , , C: _
            TextFont3D ModelFont, G.NameX, G.X1 + G.XS * G1, CDbl(G.Y1), G.Z1, -5
        If G.YS <> 0 Then _
            Line3D CDbl(G.X1), CDbl(G.Y1), G.Z1, , G.YS * H1, , C: _
            TextFont3D ModelFont, G.NameY, CDbl(G.X1), G.Y1 + G.YS * G1, G.Z1, -5
        If G.Tools And GraphAxisZ Then _
            Line3D CDbl(G.X1), CDbl(G.Y1), G.Z1, , , G.ZS * H1, C: _
            TextFont3D ModelFont, G.NameZ, CDbl(G.X1), CDbl(G.Y1), G.Z1 + G.ZS * G1, -5
        End If
    
    'X and Y ranges
    If G.Tools And GraphRanges Then
        'C = cGraph3DPlane
        'H1 = 0.03: G1 = G.YS * H1: H1 = G.XS * H1
        'If G.XS <> 0 Then _
            Line3D CDbl(G.X1), CDbl(G.Y1), G.Z1, , -G1, , C: _
            Line3D CDbl(G.X2), CDbl(G.Y1), G.Z1, , -G1, , C
        'If G.YS <> 0 Then _
            Line3D CDbl(G.X1), CDbl(G.Y1), G.Z1, -H1, , , C: _
            Line3D CDbl(G.X1), CDbl(G.Y2), G.Z1, -H1, , , C
        H1 = 0.1: G1 = G.YS * H1: H1 = G.XS * H1: X0 = -8: Y0 = 0
        If G.XS <> 0 Then _
            TextFont3D ModelFont, NumD(G.X1 / 10), CDbl(G.X1), G.Y1 - G1, G.Z1, X0, Y0: _
            TextFont3D ModelFont, NumD(G.X2 / 10), CDbl(G.X2), G.Y1 - G1, G.Z1, X0, Y0
        If G.YS <> 0 Then _
            TextFont3D ModelFont, NumD(G.Y1 / 10), G.X1 - H1, CDbl(G.Y1), G.Z1, X0, Y0: _
            TextFont3D ModelFont, NumD(G.Y2 / 10), G.X1 - H1, CDbl(G.Y2), G.Z1, X0, Y0
        If G.Tools And GraphRangesZ Then
            H1 = G.ZS1: Do
                TextFont3D ModelFont, Round(H1 / G.ScaleZ), CDbl(G.X1), CDbl(G.Y1), H1, 3, 0
                If H1 <= G.Z2 Then H1 = H1 + G.ZST Else Exit Do
                Loop
            End If

        End If
    
    'If G.Tools And GraphSolid Then glDisable gl_Depth_Test
    
    'Z maximum value
    If G.Tools And GraphMax Then _
        Line3D CDbl(G.TopX), CDbl(G.TopY), G.Z1, , , G.ZS, cGraph3DMax: _
        TextFont3D ModelFont, NumX(G.Z2 / G.ScaleZ, FMini), CDbl(G.TopX), CDbl(G.TopY), G.Z2, , , SmallRedDot
    
    'Selected value
    If G.SelZ <> 0 Then H1 = G.SelZ * G.ScaleZ: _
        Line3D CDbl(G.SelX), CDbl(G.SelY), G.Z1, , , H1 - G.Z1, cGraph3DSel: _
        TextFont3D ModelFont, NumX(G.SelZ, FMini), _
            CDbl(G.SelX), CDbl(G.SelY), H1, , , SmallGreenDot
    
    End Sub

Sub PlotInfo3D(G As Graph2D, Cam As Camera)
    Dim B As Long, X As Long, Y As Long, W As Long, H As Long, N As Double, Q As Boolean
    
    If (G.Tools And GraphInfo) = 0 Then Exit Sub
    
    'Color table
    If (G.Tools And GraphFaceMask) = 0 Or (G.Tools And GraphLineMask) = 0 Then
        glDisable gl_Cull_Face
        B = Main.TextHeight(""): W = Main.TextWidth("   "): H = B * 3 \ 2: B = (B + H) \ 2
        X = Cam.F.ScaleWidth - H - 10: Y = 10: B = B \ 6
        N = G.ZS1 + G.ZST * (Int((G.Z2 - G.ZS1) / G.ZST) + 1)
        PlotBox G, B, X, Y, W, H, cWhite, N:     N = N - G.ZST
        PlotBox G, B, X, Y, W, H, cBlue, N:      N = N - G.ZST
        PlotBox G, B, X, Y, W, H, cMagenta, N:   N = N - G.ZST
        PlotBox G, B, X, Y, W, H, cRed, N:       N = N - G.ZST
        PlotBox G, B, X, Y, W, H, cYellow, N:    N = N - G.ZST
        PlotBox G, B, X, Y, W, H, cGreen, N:     N = N - G.ZST
        PlotBox G, B, X, Y, W, H, cLightGray, N: N = N - G.ZST
        End If
    
    'Information
    If G.SelZ <> 0 Then X = G.SelX: Y = G.SelY Else X = G.TopX: Y = G.TopY
    Q = G.Y1 <> 0 Or G.Y2 <> 0: TextFont2D ModelFont, , _
        "Propellant:  " & LeftToString(G.Name) & vbBackCrBack & _
        G.NameZ & ":  " & NumX(G.Z1 / G.ScaleZ, FMini) & " to " & NumX(G.Z2 / G.ScaleZ, FMini) & vbBackCrBack & _
        "at:  " & IIf(Q, G.NameY & "=" & NumD(Y / 10) & ", ", "") & G.NameX & "=" & NumD(X / 10) & vbBackCrBack & _
        G.NameX & ":  " & NumD(G.X1 / 10) & " to " & NumD(G.X2 / 10) & _
        IIf(Q, vbBackCrBack & G.NameY & ":  " & NumD(G.Y1 / 10) & " to " & NumD(G.Y2 / 10), "") _
        , 10, 10
    'X1 = Cam.F.TextWidth(Left(S, InStr(S, vbBack) - 1)): Y1 = Cam.F.TextHeight("") * 5
    'BoxEx2D 5, 5, X1 + 15, Y1 + 12, cInfoBack, cInfoLine
    'TextFont2D ModelFont, , S, 10, 10
    
    End Sub

Sub PlotBox(G As Graph2D, _
        B As Long, X As Long, Y As Long, W As Long, H As Long, C As Long, Optional N As Double)
    If Round(N / G.ScaleZ) < 0 Then Exit Sub
    BoxEx2D X, Y, H, H, C And Not &H40000000, cInfoLine
    TextFont2D ModelFont, , CStr(Round(N / G.ScaleZ)), X - W, Y + B, True
    Y = Y + H
    End Sub

Sub PlotColor(G As Graph2D, Z As Double, Optional L As Single = 1)
    Dim N As Long, F As Single, R1 As Single, G1 As Single, B1 As Single, A As Single
    F = (Z - G.ZS1) / G.ZST: N = Int(F): F = F - N
    N = N + 5 - Int((G.Z2 - G.ZS1) / G.ZST)
    If N < 0 Then N = 0: F = 0 Else If N > 6 Then N = 6: F = 1
    Select Case 0
    Case 0
        Select Case N
        Case 0: R1 = 0.88 - 0.38 * F: G1 = 0.88:            B1 = 0.88 - 0.38 * F 'Gray
        Case 1: R1 = 0.5 + 0.38 * F:  G1 = 0.88:            B1 = 0.5             'Green
        Case 2: R1 = 0.88 + 0.12 * F: G1 = 0.88 - 0.63 * F: B1 = 0.5 - 0.25 * F  'Yellow
        Case 3: R1 = 1:               G1 = 0.25:            B1 = 0.25 + 0.75 * F 'Red
        Case 4: R1 = 1 - 0.75 * F:    G1 = 0.25:            B1 = 1               'Magenta
        Case 5: R1 = 0.25 + 0.75 * F: G1 = 0.25 + 0.75 * F: B1 = 1               'Blue
        Case 6: R1 = 1:               G1 = 1:               B1 = 1               'White
            End Select
    Case 1
        F = 1 - F
        Select Case N
        Case 0: R1 = 1:               G1 = 1:               B1 = 1               'White
        Case 1: R1 = 0.25 + 0.75 * F: G1 = 0.25 + 0.75 * F: B1 = 1               'Blue
        Case 2: R1 = 1 - 0.75 * F:    G1 = 0.25:            B1 = 1               'Magenta
        Case 3: R1 = 1:               G1 = 0.25:            B1 = 0.25 + 0.75 * F 'Red
        Case 4: R1 = 0.88 + 0.12 * F: G1 = 0.88 - 0.63 * F: B1 = 0.5 - 0.25 * F  'Yellow
        Case 5: R1 = 0.5 + 0.38 * F:  G1 = 0.88:            B1 = 0.5             'Green
        Case 6: R1 = 0.88 - 0.38 * F: G1 = 0.88:            B1 = 0.88 - 0.38 * F 'Gray
            End Select
        End Select
    glColor4f R1 * L, G1 * L, B1 * L, 1
    End Sub
    
Sub PlotNormal(G As Graph2D, ByVal X1 As Long, ByVal Y1 As Long)
    Dim H1 As Single, _
        XL As Long, XR As Long, YD As Long, YU As Long, _
        L As Single, R As Single, D As Single, U As Single, _
        X2 As Single, Y2 As Single ', X3 As Single, Y3 As Single, _
        LD As Single, LU As Single, RD As Single, RU As Single
    XL = X1 - G.XT: If XL < G.X1 Then XL = G.X1
    XR = X1 + G.XT: If XR > G.X2 Then XR = G.X2
    YD = Y1 - G.YT: If YD < G.Y1 Then YD = G.Y1
    YU = Y1 + G.YT: If YU > G.Y2 Then YU = G.Y2
    H1 = G.Value(X1, Y1)
    ''(x) matrix style
    'RU = G.Value(XR, YU): If RU = 0 Then RU = H1
    'LD = G.Value(XL, YD): If LD = 0 Then LD = H1
    'LU = G.Value(XL, YU): If LU = 0 Then LU = H1
    'RD = G.Value(XR, YD): If RD = 0 Then RD = H1
    'X3 = XR - XL: Y3 = YU - YD: H1 = Sqr(X3 * X3 + Y3 * Y3)
    'If H1 = 0 Then X2 = 0: Y2 = 0 _
    'Else X2 = (RU - LD) * G.ScaleZ / G.XT / H1: _
    '     Y2 = (LU - RD) * G.ScaleZ / G.YT / H1
    'glNormal3f (X2 - Y2) / Sq2, (X2 + Y2) / Sq2, 1 'Rotate A45, X2, Y2
    '(+) matrix style
    L = G.Value(XL, Y1): If L = 0 Then L = H1
    R = G.Value(XR, Y1): If R = 0 Then R = H1
    D = G.Value(X1, YD): If D = 0 Then D = H1
    U = G.Value(X1, YU): If U = 0 Then U = H1
    If XR = XL Then X2 = 0 Else X2 = (R - L) * G.ScaleZ / G.XT / (XR - XL) * 10
    If YU = YD Then Y2 = 0 Else Y2 = (U - D) * G.ScaleZ / G.YT / (YU - YD) * 10
    glNormal3f X2, Y2, 1
    End Sub

Sub SetGraphPoint(G As Graph2D, Cam As Camera, _
        Optional ByVal X As Long, Optional ByVal Y As Long, _
        Optional ByVal Relative As Boolean = True)
    Dim Z1 As Double
    If Relative Then X = G.SelX + X * G.XT: Y = G.SelY + Y * G.YT
    If X < G.X1 Then X = G.X1 Else If X > G.X2 Then X = G.X2
    If Y < G.Y1 Then Y = G.Y1 Else If Y > G.Y2 Then Y = G.Y2
    If G.SelX <> X Or G.SelY <> Y Then Z1 = G.Value(X, Y): If Z1 <> 0 Then _
        G.SelX = X: G.SelY = Y: G.SelZ = Z1: RenderModel Cam
    End Sub

Sub PlotCheckSelected(X As Single, Y As Single, Cam As Camera, Optional Shift As Integer)
    If Cam.hRC Then _
        ClickX = X: ClickY = Cam.F.ScaleHeight - Y - 1: SelDist = InfiniteSng: _
        IsSelect = True: RenderModel Cam: IsSelect = False
    End Sub

Sub PlotSelect3D(G As Graph2D)
    Dim X1 As Long, Y1 As Long, Z1 As Double
    G.SelX = -1: G.SelY = -1: G.SelZ = 0
    For Y1 = G.Y1 To G.Y2 Step G.YT 'AP
        For X1 = G.X1 To G.X2 Step G.XT 'ST
            Z1 = G.Value(X1, Y1): If Z1 = 0 Then GoTo Nxt
            glRasterPos3f X1, Y1, Z1 * G.ScaleZ
            glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 = 0 Then GoTo Nxt
            glGetFloatv gl_Current_Raster_Position, RasterF.X
            SelW = RasterF.X - ClickX: If SelW > -SelectRange And SelW < SelectRange Then _
                SelH = RasterF.Y - ClickY: If SelH > -SelectRange And SelH < SelectRange Then _
                    SelR = Sqr(SelW * SelW + SelH * SelH): If SelR <= SelDist Then _
                        SelDist = SelR: G.SelX = X1: G.SelY = Y1: G.SelZ = Z1
Nxt:
            Next
        Next
    End Sub

Sub ClearColor(Optional ByVal Color As Long)
    If Color And &H80000000 Then Color = GetSysColor(Color And Not &H80000000)
    glClearColor (Color And &HFF&) / 255, ((Color And &HFF00&) \ &H100&) / 255, ((Color And &HFF0000) \ &H10000) / 255, 0
    glClear gl_Color_Buffer_Bit Or gl_Depth_Buffer_Bit
    End Sub

Sub ShipInfo(Pl As Planet, _
        Optional ByVal IsAir As Boolean, Optional Fmt As String = FMini, _
        Optional ByVal SetPos As Boolean = True)
    Dim T As String
    Select Case StageP: Case 0: T = "S": Case 1: T = "H": Case Else: T = StageN: End Select
    If (Tool And ToolInfo) = 0 Then Text3D T, , , , 4, TextFont.Y - 1, , SetPos: Exit Sub
    If Pl.AirSpeed = 0 Or Pl.Altitude >= Atm Or SpeedInfo Then T = T & "  " & ToSpeed(Pl.Speed, Fmt) _
    Else If Not IsAir Then T = T & "  " & ToSpeed(Pl.AirSpeed, Fmt) _
    Else If KMLPathPos <= 0 Then T = T & "  " & ToAirSpeed(Pl.AirSpeed, Fmt) _
    Else T = T & "  " & ToAirSpeed(KMLPath(KMLPathPos).SpeedH, Fmt)
    'If Pl.AirSpeed >= Mach Then T = T & "  Mach " & NumM(Pl.AirSpeed / Mach, Fmt) _
    Else If Units Then T = T & "  " & NumX(Pl.AirSpeed / Knot, Fmt) & "kn" _
    Else T = T & "  " & NumX(Pl.AirSpeed / Kph, Fmt) & "kph"
    Text3D T & vbBackCr & "    " & _
        ToAlt(Pl.Altitude, Fmt, Not AltitudeInfo), , , , 4, TextFont.Y - 1, , SetPos
    End Sub

Sub SetReportPage(Optional ByVal N As Long, Optional ByVal Relative As Boolean = True, _
        Optional ByVal Update As Boolean, Optional ByVal ReturnToLast As Boolean)
    Dim C As Long: Static LastPage As Long
    If (Tool And ToolReport) = 0 Then Exit Sub
    On Error Resume Next
    C = ReportPage
    If Relative Then ReportPage = ReportPage + N Else ReportPage = N
    If ReportPage < 0 Then ReportPage = 0 Else If ReportPage > UBound(Report) Then ReportPage = UBound(Report)
    If ReportPage <> C Then LastPage = C Else If ReturnToLast Then ReportPage = LastPage
    If (ReportPage <> C Or Update) And Play = Paused Then Render
    'If LastPage <> C Then LastPage = C
    On Error GoTo 0
    End Sub

Sub Report3D()
    Static Tr As Double, S As String
    Dim C As Long, N As Long, T As Double
    T = TimerEx
    If T > Tr Then
        Tr = T + 1
        On Error Resume Next
        S = LoadFile(LogsDir & "Report.txt"): If S = "" Then GoTo Nxt
        S = Replace(Replace(S, vbCrLf, vbLf), vbLf, vbCrLf)
        If Left(S, 1) = "|" Then S = Mid(S, 2)
        Report = Split(S, "|")
        For N = 0 To UBound(Report)
            C = InStr(Report(N), vbCrLf): If C Then Report(N) = Mid(Report(N), C + 2)
            Next
Nxt:
        On Error GoTo 0
        End If
    If ReportFont.List = 0 Then CreateFont ReportFont, Main.Report
    glListBase ReportFont.List
    If ReportPage < 0 Then ReportPage = 0 _
    Else If ReportPage > UBound(Report) Then ReportPage = UBound(Report)
    TextFormat2D Report(ReportPage), ReportFont, 10, 10, Main.ScaleWidth - 20
    If ReportPage > 0 Then TextFormat2D "Page " & CStr(ReportPage + 1), ReportFont, _
        -15 - IfInt(Logo.Visible, Logo.W, 0), 10, Main.ScaleWidth
    glListBase TextFont.List
    End Sub

Sub CursorFlat3D()
    Dim X0 As Double, Y0 As Double, Z0 As Double
    If IsSelect Then IsCursor = True
    If SelectedType = TypeNone Then IsCursor = False: Exit Sub
    If HideCursor And Not IsCursor Then Exit Sub
    If Not ObjectPositionFlat(SelectedType, SelectedN, X0, Y0, Z0) Then Exit Sub
    glRasterPos3f X0, Y0, Z0: glCallList Cursor.List
    If SelShift = 2 Then SelShift = 0
    End Sub

Sub Cursor3D(Pl As Planet)
    Dim A1 As Double, H2 As Double, H3 As Double, _
        X0 As Double, Y0 As Double, Z0 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X3 As Double, Y3 As Double, Z3 As Double
    
    If IsSelect Then IsCursor = True
    If SelectedType = TypeNone Then IsCursor = False: Exit Sub
    If HideCursor And Not IsCursor Then Exit Sub
    If Not ObjectPosition(SelectedType, SelectedN, X0, Y0, Z0) Then Exit Sub
    
    If SelShift = 2 Then
        SelShift = 0
        If SelType1 = TypeNone Then
            If LastSelType Then SelType1 = LastSelType: SelN1 = LastSelN _
            Else If SelectedType <> TypeShip Then SelType1 = TypeShip: SelN1 = 0 _
            Else GoTo Nxt
            SelType2 = SelectedType: SelN2 = SelectedN
        ElseIf SelType2 = TypeNone Then
            SelType2 = SelectedType: SelN2 = SelectedN
        ElseIf SelType3 = TypeNone Then
            If SelType1 = SelectedType And SelN1 = SelectedN Then
                GoTo Nxt
            ElseIf SelType2 = SelectedType And SelN2 = SelectedN Then
                GoTo Nxt
            Else
                SelType3 = SelectedType: SelN3 = SelectedN
            End If
        Else
            If SelType1 = SelectedType And SelN1 = SelectedN Then
                GoTo Nxt
            ElseIf SelType2 = SelectedType And SelN2 = SelectedN Then
                SelType3 = TypeNone: SelN3 = 0
            ElseIf SelType3 = SelectedType And SelN3 = SelectedN Then
                SelType2 = SelType3: SelN2 = SelN3: SelType3 = TypeNone: SelN3 = 0
            Else
                SelType3 = SelectedType: SelN3 = SelectedN
                End If
            End If
        End If
Nxt:
    If SelType1 = TypeNone Or SelType2 = TypeNone Then
        glRasterPos3f X0, Y0, Z0: If IsVisible Then glCallList Cursor.List
        Exit Sub
        End If
    
    If SelType1 Then If SelType1 = SelectedType And SelN1 = SelectedN Then _
        X1 = X0: Y1 = Y0: Z1 = Z0 Else ObjectPosition SelType1, SelN1, X1, Y1, Z1
    If SelType2 Then If SelType2 = SelectedType And SelN2 = SelectedN Then _
        X2 = X0: Y2 = Y0: Z2 = Z0 Else ObjectPosition SelType2, SelN2, X2, Y2, Z2
    If SelType3 Then If SelType3 = SelectedType And SelN3 = SelectedN Then _
        X3 = X0: Y3 = Y0: Z3 = Z0 Else ObjectPosition SelType3, SelN3, X3, Y3, Z3
    
    glColor4ubv cRulerLine: glBegin gl_Line_Strip
    glVertex3f X2, Y2, Z2: glVertex3f X1, Y1, Z1: If SelType3 Then glVertex3f X3, Y3, Z3
    glEnd
    Azimuth3D Pl, X1, Y1, Z1, X2, Y2, Z2
    If SelType3 Then
        Azimuth3D Pl, X1, Y1, Z1, X3, Y3, Z3
        Text3D ToAngle( _
            Angle3D(X2 - X1, Y2 - Y1, Z2 - Z1, X3 - X1, Y3 - Y1, Z3 - Z1, True, True), FAngle), _
            (X1 / 2 + (X2 + X3) / 4) * 0.8 + X1 * 0.2, _
            (Y1 / 2 + (Y2 + Y3) / 4) * 0.8 + Y1 * 0.2, _
            (Z1 / 2 + (Z2 + Z3) / 4) * 0.8 + Z1 * 0.2
        End If
    
    If SelType1 Then glRasterPos3f X1, Y1, Z1: If IsVisible Then glCallList Cursor.List
    If SelType2 Then glRasterPos3f X2, Y2, Z2: If IsVisible Then glCallList Cursor.List
    If SelType3 Then glRasterPos3f X3, Y3, Z3: If IsVisible Then glCallList Cursor.List
    
    End Sub

Sub Azimuth3D(Pl As Planet, _
        Optional ByVal X1 As Double, Optional ByVal Y1 As Double, Optional ByVal Z1 As Double, _
        Optional ByVal X2 As Double, Optional ByVal Y2 As Double, Optional ByVal Z2 As Double)
    Dim X3 As Double, Y3 As Double, Z3 As Double, _
        X4 As Double, Y4 As Double, Z4 As Double, _
        Lat1 As Double, Lon1 As Double, Alt1 As Double, _
        Lat2 As Double, Lon2 As Double, Alt2 As Double, _
        A1 As Double, G1 As Double, H1 As Double
    X3 = (X2 + X1) / 2: Y3 = (Y2 + Y1) / 2: Z3 = (Z2 + Z1) / 2
    X4 = X2 - X1: Y4 = Y2 - Y1: Z4 = Z2 - Z1: H1 = Sqr(X4 * X4 + Y4 * Y4 + Z4 * Z4)
    If Not IsTactic Then Text3D ToDistance(H1, FMini), X3, Y3, Z3: Exit Sub
    XYZ2LLA Pl, Lat1, Lon1, Alt1, X1, Y1, Z1
    XYZ2LLA Pl, Lat2, Lon2, Alt2, X2, Y2, Z2
    X1 = X1 - Pl.X: Y1 = Y1 - Pl.Y: Z1 = Z1 - Pl.Z
    G1 = A90 - Angle3D(X1, Y1, Z1, X4, Y4, Z4, True, True)
    A1 = Azimuth(Lat1, Lon1, Lat2, Lon2)
    Text3D ToDistance(H1, FMini) & vbCrBack & ToAngle(A1, FAngleLon) & _
        vbCrBack & ToAngle(G1), X3, Y3, Z3 ' & vbCrBack & ToAlt(Alt2, FMini), X3, Y3, Z3
    End Sub

Sub Info3D()
    Dim B As Long, C As Long, F As Long, N As Long, X As Long, Y As Long, W As Long, H As Long, _
        A As Double, P As Double, R As Double, M As Double, T As Double, G As Double, _
        S As String
    On Error GoTo Ext
    N = SelectedN
    Select Case SelectedType
    Case TypePlanet, TypeGhost
        Select Case N
        Case ToSun
        PlanetData N, R, A, P, M, T, G
        H = 7: S = vbSpc & Planets(N) & vbSpcCrCr & _
            " Type:  Star" & vbSpcCr & _
            " Group:  Milky Way" & vbSpc & vbCr & _
            " Temperature:  " & ToTemp(T) & vbSpcCr & _
            " Gravity:  " & ToForceG(G, , FMini) & vbSpcCr & _
            " Radius:  " & ToAlt(R, FShort) & vbSpc '" (" & ToAccel(G, FOne) & ")"
        Case ToMoon
        PlanetData N, R, A, P, M, T, G
        H = 10: S = vbSpc & Planets(N) & IfText(SelectedType = TypeGhost, "'") & vbSpcCrCr & _
            " Type:  Moon" & vbSpcCr & _
            " Group:  Earth's Moon" & vbSpcCr & _
            " Apogee:  " & ToAlt(A, FShort) & vbSpcCr & _
            " Perigee:  " & ToAlt(P, FShort) & vbSpcCr & _
            " Orbit time:  " & ToDhEx(A360 / M) & vbCr & _
            " Temperature:  " & ToTemp(T) & vbSpcCr & _
            " Gravity:  " & ToForceG(G, , FMini) & vbSpcCr & _
            " Radius:  " & ToAlt(R, FShort) & vbSpc '" (" & ToAccel(G, FOne) & ")"
        Case Else
        PlanetData N, R, A, P, M, T, G
        H = 10: S = vbSpc & Planets(N) & IfText(SelectedType = TypeGhost, "'") & vbSpcCrCr & _
            " Type:  Planet" & vbSpcCr & _
            " Group:  Solar System" & vbSpcCr & _
            " Aphelion:  " & NumX(A / AU, FShort) & "AU" & vbSpcCr & _
            " Perihelion:  " & NumX(P / AU, FShort) & "AU" & vbSpcCr & _
            " Orbit time:  " & ToDhEx(A360 / M) & vbCr & _
            " Temperature:  " & ToTemp(T) & vbSpcCr & _
            " Gravity:  " & ToForceG(G, , FMini) & vbSpcCr & _
            " Radius:  " & ToAlt(R, FShort) & vbSpc '" (" & ToAlt(A, FOne) & ")" ' " (" & ToAccel(G, FOne) & ")" &
            End Select
    Case TypeMoon
        With Moons(N)
        H = 8: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Moon" & vbSpcCr & _
            " Group:  " & Planets(-.Class) & "'s Moons" & vbSpcCr & _
            " Apoapsis:  " & ToAlt(.S * (1 + .E), FShort) & vbSpcCr & _
            " Periapsis:  " & ToAlt(.S * (1 - .E), FShort) & vbSpcCr & _
            " Orbit time:  " & ToDhEx(A360 / .M) & vbCr & _
            " Radius:  " & IIf(.R > 0, ToAlt(.R, FShort), "Unknown") & vbSpc
        End With
    Case TypeComet
        With Comet(N)
        A = .S * (1 + .E): P = .S * (1 - .E)
        H = 8: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Comet" & vbSpcCr & _
            " Group:  " & CometGroups(.Class) & vbSpcCr & _
            " Apoapsis:  " & NumX(.S * (1 + .E) / AU, FShort) & "AU" & vbSpcCr & _
            " Periapsis:  " & NumX(.S * (1 - .E) / AU, FShort) & "AU" & vbSpcCr & _
            " Perihelion date:  " & J2ksDate(-.L / .M) & vbSpcCr & _
            " Orbit time:  " & ToDhEx(A360 / .M)
        End With
    Case TypeAsteroid
        With Asteroid(N)
        A = .S * (1 + .E): P = .S * (1 - .E)
        H = 8: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Asteroid" & vbSpcCr & _
            " Group:  " & AsteroidZones(.Class) & vbSpcCr & _
            " Apoapsis:  " & NumX(.S * (1 + .E) / AU, FShort) & "AU" & vbSpcCr & _
            " Periapsis:  " & NumX(.S * (1 - .E) / AU, FShort) & "AU" & vbSpcCr & _
            " Orbit time:  " & ToDhEx(A360 / .M) & vbCr & _
            " Radius:  " & IIf(.R > 0, ToAlt(.R, FShort), "Unknown") & vbSpc
        End With
    Case TypeStar
        With Stars(N)
        H = 9: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Star" & vbSpcCr & _
            " Group:  Milky Way" & vbSpcCr & _
            " Right ascension:  " & ToRightAscension(.Lon, Degrees) & vbSpcCr & _
            " Declination:  " & ToDeclination(.Lat, Degrees) & vbSpcCr & _
            " Apparent magnitude:  " & NumXS(.Mag, FMini) & vbSpcCr & _
            " Radius:  " & ToAlt(.Radius, FMini) & vbSpcCr & _
            " Distance:  " & NumX(.Dist / LightYear, FOne) & " light years" & vbSpc
        End With
    Case TypeSatellite
        With Satellite(N)
        A = .S * (1 + .E): P = .S * (1 - .E)
        H = 7: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Satellite" & vbSpcCr & _
            " Group:  " & SatelliteGroup(.Class).Name & vbSpcCr & _
            " Apogee:  " & ToAlt(A - Earth.Radius, FShort) & vbSpcCr & _
            " Perigee:  " & ToAlt(P - Earth.Radius, FShort) & vbSpcCr & _
            " Orbit time:  " & ToDhEx(A360 / .M)
        End With
    Case TypeStation
        With Station(N)
        H = 7: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Radio station" & vbSpcCr & _
            " Group:  " & StationGroup(.Planet).Name & vbSpcCr & _
            " Latitude:  " & ToLat(.Lat, FLong, Degrees) & vbSpcCr & _
            " Longitude:  " & ToLon(.Lon, FLong, Degrees) & vbSpcCr & _
            " Altitude:  " & ToAlt(.Alt, FOne) & vbSpc
        End With
    Case TypeLocation
        With Location(N)
        H = 6: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Location" & vbSpcCr & _
            " Group:  " & Planets(.Planet) & vbSpcCr & _
            " Latitude:  " & ToLat(.Lat, FLong, Degrees) & vbSpcCr & _
            " Longitude:  " & ToLon(.Lon, FLong, Degrees) & vbSpc
        End With
    Case TypeAirspace
        With Airspace(N)
        H = 7: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Airspace" & vbSpcCr & _
            " Group:  " & AirspaceZone(GetAirspaceZone(N)).Name & vbSpcCr & _
            " Class:  " & AirspaceGroup(GetAirspaceGroup(N)).Name & vbSpcCr & _
            " Floor:  " & ToAlt(.FLD, FOne) & " (" & IIf(.FLD > 0, NumX(.FLD / Foot / 100, "FL000"), "SFC") & ")" & vbSpcCr & _
            " Ceiling:  " & ToAlt(.FLU, FOne) & " (" & NumX(.FLU / Foot / 100, "FL000") & ")" & vbSpc
        End With
    Case TypeAirport
        With Airport(N)
        H = 6: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Airport" & vbSpcCr & _
            " Group:  " & IIf(.Planet >= 0, AirspaceZone(IfInt(.Planet >= 0, .Planet, 0)).Name, "None") & vbSpcCr & _
            " Latitude:  " & ToLat(.Lat, FLong, Degrees) & vbSpcCr & _
            " Longitude:  " & ToLon(.Lon, FLong, Degrees) & vbSpc
        End With
    Case TypeSeaport
        With Seaport(N)
        H = 6: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  Seaport" & vbSpcCr & _
            " Group:  " & SeaportZone(.Planet).Name & vbSpcCr & _
            " Latitude:  " & ToLat(.Lat, FLong, Degrees) & vbSpcCr & _
            " Longitude:  " & ToLon(.Lon, FLong, Degrees) & vbSpc
        End With
    Case TypeCity
        With City(N)
        H = 7: S = vbSpc & .Name & vbSpcCrCr & _
            " Type:  City" & vbSpcCr & _
            " Group:  " & CityZone(.Planet).Name & vbSpcCr & _
            " Population:  " & Replace(Format(.Time, "#,0"), ".", ",") & vbSpcCr & _
            " Latitude:  " & ToLat(.Lat, FLong, Degrees) & vbSpcCr & _
            " Longitude:  " & ToLon(.Lon, FLong, Degrees) & vbSpc
        End With
    Case TypeShip
        H = 3: S = vbSpc & "Spacecraft" & vbSpcCrCr & _
            " Mission:  " & LastPreset.Name & vbSpc
    Case Else: Exit Sub
        End Select
    B = 5: F = TextFont.Y
    H = (H + 1) * F: W = Main.TextWidth(S) + B * 3: If W < 150 Then W = 150
    C = Main.TextWidth(Left(S, InStr(S, vbSpcCr))) + F * 1.2 + InfoWeb.W + InfoList.W + B: If W < C Then W = C
    X = Main.ScaleWidth - W - B * 3: Y = 15
    If Logo.Visible Then Y = Y + Logo.Y + Logo.H _
        Else If Options.SnapPanels And ToolBar.Visible Then _
        If X < (ToolBar.Left + ToolBar.Width - Main.Left) / TwipX Then _
            Y = (ToolBar.Top + ToolBar.Height - Main.Top) / TwipY
    OverlayY = Y + H
    BoxEx2D X, Y, W, H, cInfoBack, cInfoLine
    Line2D X, Y + F * 2, W, 0, cInfoLineB, True
    If InfoWeb.H > InfoList.H Then C = F - InfoWeb.H \ 2 Else C = F - InfoList.H \ 2
    InfoWeb.X = X + W - InfoWeb.W - C: InfoWeb.Y = Y + C: PaintImage InfoWeb
    InfoList.X = InfoWeb.X - 5 - InfoList.W: InfoList.Y = Y + C: PaintImage InfoList
    Text2D S, X + B + 2, Y + B \ 2 + F \ 2 - 1, , False
Ext:
    End Sub

Sub ColorScale2D()
    Dim B As Long, X As Long, Y As Long, W As Long, H As Long
    
    If (Tool And ToolAtmosphere) <> 0 And (AtmStyle And &H19&) = 0 And (Tool And ToolGravity) <> 0 And (GravityField And 2) = 0 Then Exit Sub
    
    B = Main.TextHeight(""): W = Main.TextWidth("   "): H = B * 3 \ 2: B = (B + H) \ 2
    X = Main.ScaleWidth - H - 15: Y = OverlayY
    If Y = 0 Then _
        If Logo.Visible Then Y = Y + Logo.Y + Logo.H + IfInt(SelectedType, B * 12, 0) _
        Else If Options.SnapPanels And ToolBar.Visible Then _
        If X - H < (ToolBar.Left + ToolBar.Width) / TwipX Then _
            Y = (ToolBar.Top + ToolBar.Height - Main.Top) / TwipY - 15
    B = B \ 6: Y = Y + H
    
    'Wind UVW planes and vectors [m/s]
    If (Tool And ToolAtmosphere) <> 0 And (AtmStyle And &H18&) <> 0 Then
        'White      5m/s   18km/h   10kn
        'Green     15m/s   54km/h   29kn
        'Yellow    25m/s   90km/h   49kn
        'Red       50m/s  180km/h   97kn
        'Magenta   75m/s  270km/h  146kn
        'Blue     100m/s  360km/h  194kn
        'White    125m/s  450km/h  243kn
        'Box2D X - 1, Y - 1, H + 2, H * 7 + 2, &HFF808080, True
        ColorBox2D B, X, Y, W, H, cAtmosphereWind1 Or &HFF000000, 0, SpeedInfo, 5
        ColorBox2D B, X, Y, W, H, cAtmosphereWind2 Or &HFF000000, 0, SpeedInfo, 15
        ColorBox2D B, X, Y, W, H, cAtmosphereWind3 Or &HFF000000, 0, SpeedInfo, 25
        ColorBox2D B, X, Y, W, H, cAtmosphereWind4 Or &HFF000000, 0, SpeedInfo, 50
        ColorBox2D B, X, Y, W, H, cAtmosphereWind5 Or &HFF000000, 0, SpeedInfo, 75
        ColorBox2D B, X, Y, W, H, cAtmosphereWind6 Or &HFF000000, 0, SpeedInfo, 100
        ColorBox2D B, X, Y, W, H, cAtmosphereWind7 Or &HFF000000, 0, SpeedInfo, 125
        Y = Y + H
        End If
    
    'Temperature [K]
    If (Tool And ToolAtmosphere) <> 0 And (AtmStyle And 1) <> 0 Then
        'Magenta  -50ºC  -58ºF  223K  402R
        'Blue     -30ºC  -22ºF  243K  438R
        'Cyan     -15ºC  + 5ºF  258K  465R
        'Black    + 0ºC  +32ºF  273K  492R
        'Yellow   +15ºC  +59ºF  288K  519R
        'Red      +30ºC  +86ºF  303K  546R
        'Box2D X - 1, Y - 1, H + 2, H * 6 + 2, &HFF808080, True
        ColorBox2D B, X, Y, W, H, cAtmosphereTemp1 Or &HFF000000, 1, False, -50
        ColorBox2D B, X, Y, W, H, cAtmosphereTemp2 Or &HFF000000, 1, False, -30
        ColorBox2D B, X, Y, W, H, cAtmosphereTemp3 Or &HFF000000, 1, RelativeTemp And Units, -15
        ColorBox2D B, X, Y, W, H, cAtmosphereTemp4 Or &HFF000000, 1, RelativeTemp And Units, 0
        ColorBox2D B, X, Y, W, H, cAtmosphereTemp5 Or &HFF000000, 1, RelativeTemp, 15
        ColorBox2D B, X, Y, W, H, cAtmosphereTemp6 Or &HFF000000, 1, RelativeTemp, 30
        Y = Y + H
        End If
    
    'Gravity [m/s²]
    If (Tool And ToolGravity) <> 0 And (GravityField And 2) <> 0 Then
        'White    10.0m/s²  32.8ft/s²
        'Magenta   1.0m/s²   3.3ft/s²
        'Blue      0.1m/s²   0.3ft/s²
        'Black    0.01m/s²   0.0ft/s²
        'Box2D X - 1, Y - 1, H + 2, H * 4 + 2, &HFF808080, True
        ColorBox2D B, X, Y, W, H, cGravityZone1 Or &HFF000000, 2, , 10
        ColorBox2D B, X, Y, W, H, cGravityZone2 Or &HFF000000, 2, , 1
        ColorBox2D B, X, Y, W, H, cGravityZone3 Or &HFF000000, 2, , 0.1
        ColorBox2D B, X, Y, W, H, cGravityZone4 Or &HFF000000, 2, , 0.01
        Y = Y + H
        End If
    
    End Sub

Sub ColorBox2D(B As Long, X As Long, Y As Long, W As Long, H As Long, C As Long, _
        M As Long, Optional Q As Boolean, Optional N As Double)
    Dim S As String
    BoxEx2D X, Y, H, H, C, cColorScale
    Select Case M
    Case 0: If Q Then S = ToSpeed(N, FZero) Else S = ToAirSpeed(N, FZero)
    Case 1: S = ToTemp(Kelvin + N, FZero): If Q Then S = "+" & S
    Case 2: S = ToAccel(N / GravityScale, FOne)
        End Select
    Text2D S, X - W, Y + B, True
    Y = Y + H
    End Sub

Sub Box2D(ByVal X As Single, ByVal Y As Single, ByVal W As Single, ByVal H As Single, _
        Optional ByVal C As Long = -1, Optional ByVal UseLines As Boolean)
    glColor4ubv C: X = X - 0.5: Y = Y - 0.5
    If UseLines Then
        glBegin gl_Line_Strip
            glVertex2f X, Y: glVertex2f X + W, Y: glVertex2f X + W, Y + H: glVertex2f X, Y + H
            glVertex2f X, Y: glEnd
    Else
        glBegin gl_Quads
            glVertex2f X, Y: glVertex2f X + W, Y: glVertex2f X + W, Y + H: glVertex2f X, Y + H
            glEnd
        End If
    End Sub

Sub BoxEx2D(ByVal X As Single, ByVal Y As Single, ByVal W As Single, ByVal H As Single, _
        Optional ByVal C1 As Long, Optional ByVal C2 As Long = -1)
    glColor4ubv C2: X = X - 0.5: Y = Y - 0.5
    glBegin gl_Line_Strip
        glVertex2f X, Y: glVertex2f X + W, Y: glVertex2f X + W, Y + H: glVertex2f X, Y + H
        glVertex2f X, Y: glEnd
    glColor4ubv C1: X = X + 0.5: Y = Y + 0.5: W = W - 1: H = H - 1
    glBegin gl_Quads
        glVertex2f X, Y: glVertex2f X + W, Y: glVertex2f X + W, Y + H: glVertex2f X, Y + H
        glEnd
    End Sub

Sub Line2D(ByVal X As Long, ByVal Y As Long, Optional ByVal W As Long, Optional ByVal H As Long, _
        Optional ByVal C As Long = -1, Optional ByVal Relative As Boolean)
    glColor4ubv C: glBegin gl_Lines
        glVertex2i X, Y: If Relative Then glVertex2i X + W, Y + H Else glVertex2i X, Y
        glEnd
    End Sub

Sub Text2D(Text As String, Optional ByVal X As Long, Optional ByVal Y As Long, _
        Optional ByVal ToLeftTop As Long, Optional ByVal Border As Boolean = True, _
        Optional ByVal UseMatrix As Boolean)
    If ToLeftTop = -1 Or ToLeftTop = -3 Then X = X - Main.TextWidth(Text)
    If ToLeftTop = -2 Or ToLeftTop = -3 Then Y = Y - Main.TextHeight(Text)
    If FontTextures Or UseMatrix Then
        glMatrixMode gl_Projection: glPushMatrix: glLoadMatrixf Matrix2D.XX
        glMatrixMode gl_Modelview: glPushMatrix: glLoadIdentity
        End If
    If FontTextures Then
        glTranslatef X, Y, 0: glPushMatrix
        glColor4ubv cWhite: glBindTexture gl_Texture_2d, TextFont.Tex: glEnable gl_Texture_2d
    Else
        glRasterPos2i X, Y + TextFont.Y
        glPushAttrib gl_Current_Bit
        End If
    If Border Then glCallList TextFont.List + 9 'vbTab
    glCallLists Len(Text), gl_Unsigned_Byte, Text
    If Border Then glCallList TextFont.List + 8 'vbBack
    If FontTextures Then
        glDisable gl_Texture_2d
    Else
        glPopAttrib
        End If
    If FontTextures Or UseMatrix Then
        glPopMatrix
        glMatrixMode gl_Projection: glPopMatrix
        glMatrixMode gl_Modelview: glPopMatrix
        End If
    End Sub

Sub TextFont2D(TextFont As FontX, Optional F As Form, Optional Text As String, _
        Optional ByVal X As Long, Optional ByVal Y As Long, _
        Optional ByVal ToLeftTop As Long, Optional ByVal Border As Boolean = True)
    If ToLeftTop = -1 Or ToLeftTop = -3 Then _
        If F Is Nothing Then X = X - Graph3D.TextWidth(Text) Else X = X - F.TextWidth(Text)
    If ToLeftTop = -2 Or ToLeftTop = -3 Then _
        If F Is Nothing Then Y = Y - Graph3D.TextHeight(Text) Else Y = Y - F.TextHeight(Text)
    If FontTextures Then
        glMatrixMode gl_Projection: glPushMatrix: glLoadMatrixf Matrix2D.XX
        glMatrixMode gl_Modelview: glPushMatrix: glLoadIdentity
        glTranslatef X, Y, 0: glPushMatrix
        glColor4ubv cWhite: glBindTexture gl_Texture_2d, TextFont.Tex: glEnable gl_Texture_2d
    Else
        glRasterPos2i X, Y + TextFont.Y
        glPushAttrib gl_Current_Bit
        End If
    If Border Then glCallList TextFont.List + 9 'vbTab
    glCallLists Len(Text), gl_Unsigned_Byte, Text
    If Border Then glCallList TextFont.List + 8 'vbBack
    If FontTextures Then
        glDisable gl_Texture_2d
        glPopMatrix
        glMatrixMode gl_Projection: glPopMatrix
        glMatrixMode gl_Modelview: glPopMatrix
    Else
        glPopAttrib
        End If
    End Sub

Sub TextFormat2D(Text As String, F As FontX, _
        Optional ByVal X As Long, Optional ByVal Y As Long, Optional ByVal Width As Long)
    Dim C As Byte, L As Long, N As Long, P As Long, S As Long, W As Long, T() As Byte
    On Error Resume Next
    If X < 0 Then X = Width + X - TextWidth(Text, F)
    If FontTextures Then
        glMatrixMode gl_Projection: glPushMatrix: glLoadMatrixf Matrix2D.XX
        glMatrixMode gl_Modelview: glPushMatrix: glLoadIdentity
        glTranslatef X, Y, 0: glPushMatrix
        glColor4ubv cWhite: glBindTexture gl_Texture_2d, F.Tex: glEnable gl_Texture_2d
    Else
        glRasterPos2i X, Y + F.Y
        glPushAttrib gl_Current_Bit
        End If
    If Width <= 0 Then
        glCallLists Len(Text), gl_Unsigned_Byte, Text
    Else
        S = -1: T = StrConv(Text, vbFromUnicode)
        For N = 0 To UBound(T)
            C = T(N): P = P + F.XX(C)
            If C = 32 Then S = N: W = P
            If C = 13 Then
                S = N + 1
                If S > L Then glCallListsX S - L, gl_Unsigned_Byte, T(L)
                L = S + 1: P = 0: W = 0
            ElseIf P > Width Then
                If S > L Then _
                    glCallListsX S - L, gl_Unsigned_Byte, T(L): glCallListsX 1, gl_Unsigned_Byte, 13
                L = S + 1: P = P - W
                End If
            Next
        glCallListsX N - L, gl_Unsigned_Byte, T(L)
        End If
    If FontTextures Then
        glDisable gl_Texture_2d
        glPopMatrix
        glMatrixMode gl_Projection: glPopMatrix
        glMatrixMode gl_Modelview: glPopMatrix
    Else
        glPopAttrib
        End If
    On Error GoTo 0
    End Sub

Sub Light3D(X As Double, Y As Double, Z As Double, _
        Optional A As Double = 0.3, Optional D As Double = 1.5)
    Dim L As FloatRGBA
    L.A = 1
    L.R = A: L.G = A: L.B = A: glLightfv gl_Light0, gl_Ambient, L.R
    L.R = D: L.G = D: L.B = D: glLightfv gl_Light0, gl_Diffuse, L.R
    L.R = X: L.G = Y: L.B = Z: glLightfv gl_Light0, gl_Position, L.R
    glEnable gl_Light0
    End Sub

Sub Ambient3D(N As Double)
    Dim L As FloatRGBA
    L.R = N: L.G = N: L.B = N: L.A = 1: glLightfv gl_Light0, gl_Ambient, L.R
    End Sub

Sub Line3D( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional X2 As Double, Optional Y2 As Double, Optional Z2 As Double, _
        Optional ByVal C As Long = -1, Optional ByVal Relative As Boolean = True)
    glColor4ubv C
    glBegin gl_Line_Strip
    glVertex3f X, Y, Z: If Relative Then glVertex3f X + X2, Y + Y2, Z + Z2 _
    Else glVertex3f X2, Y2, Z2
    glEnd
    End Sub

Sub LineText3D(Optional Txt As String, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional X2 As Double, Optional Y2 As Double, Optional Z2 As Double, _
        Optional ByVal C As Long = -1, Optional ByVal Relative As Boolean = True)
    glColor4ubv C
    glBegin gl_Line_Strip
    glVertex3f X, Y, Z: If Relative Then glVertex3f X + X2, Y + Y2, Z + Z2 _
    Else glVertex3f X2, Y2, Z2
    glEnd
    If Txt <> "" Then If Relative Then Text3D Txt, X + X2, Y + Y2, Z + Z2, 0 _
    Else Text3D Txt, X2, Y2, Z2, 0
    End Sub

Sub Axe3D( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal R As Double, Optional ByVal C As Long = cRed)
    If R < ShipScale * 3 Then R = ShipScale * 3
    LineText3D "X", X, Y, Z, R, , , C
    LineText3D "Y", X, Y, Z, , R, , C
    LineText3D "Z", X, Y, Z, , , R, C
    End Sub

Sub AxePlanet3D(Pl As Planet, Optional ByVal ShowPoles As Boolean)
    Dim R As Double
    R = Pl.Diameter: If R < ShipScale * 3 Then R = ShipScale * 3
    glPushMatrix
    glTranslatef Pl.X, Pl.Y, Pl.Z
    LineText3D "X", , , , R, , , cAxesLine
    LineText3D "Y", , , , , R, , cAxesLine
    LineText3D "Z", , , , , , R, cAxesLine
    glPopMatrix
    If CamH > Pl.Radius * 100 Then Exit Sub
    glPushMatrix
    PlanetGL Pl
    Line3D , , , , , Pl.Diameter, cAxesPlanet
    Line3D , , , , , -Pl.Diameter, cAxesPlanet
    Line3D , , , , Pl.Diameter, , cAxesPlanet
    glPopMatrix
    If ShowPoles Then PointsPlanet3D Pl
    End Sub

Sub Altitude3D( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional CX As Double, Optional CY As Double, Optional CZ As Double, _
        Optional ByVal C As Long = -1)
    Dim A1 As Double, X1 As Double, Y1 As Double, Z1 As Double, _
        SinU As Double, CosU As Double, SinW As Double, CosW As Double
    A1 = CamU * Rad: SinU = Sin(A1): CosU = Cos(A1)
    A1 = CamW * Rad: SinW = Sin(A1): CosW = Cos(A1)
    X1 = X - CX: Y1 = Y - CY
    A1 = X1 * CosW + Y1 * SinW: Y1 = (Y1 * CosW - X1 * SinW) * CosU + (Z - CZ) * SinU
    Z1 = Y1 * SinU + CZ: Y1 = Y1 * CosU
    X1 = A1 * CosW - Y1 * SinW + CX: Y1 = A1 * SinW + Y1 * CosW + CY
    glColor4ubv C
    glBegin gl_Lines: glVertex3f X, Y, Z: glVertex3f X1, Y1, Z1: glEnd
    End Sub

Sub Planet2D(Pl As Planet, Optional ByVal C As Long = vbBlue, Optional ByVal B As Long = -1)
    Main.FillColor = C: If B >= 0 Then _
        Main.Circle (CamX + Pl.X * CamH, CamY - Pl.Y * CamH), Int(Pl.Radius * CamH + 1), B _
    Else Main.Circle (CamX + Pl.X * CamH, CamY - Pl.Y * CamH), Int(Pl.Radius * CamH + 1)
    Main.Print Planets(Pl.ID);
    End Sub

Sub PointEarth2D(Pl As Planet, Optional Text As String, Optional R As Double = 2, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double)
    Main.Circle (CamX + (Pl.X + Sin(-Pl.AngleW - Lon) * (Pl.Radius + Alt)) * CamH, _
        CamY - (Pl.Y + Cos(-Pl.AngleW - Lon) * (Pl.Radius + Alt)) * CamH), R
    If Text <> "" Then Main.Print Text;
    End Sub

Sub Circle2D(Optional X As Double, Optional Y As Double, Optional R As Double, _
        Optional ByVal C As Long = vbWhite)
    Main.Circle (CamX + X * CamH, CamY - Y * CamH), Int(R * CamH), C
    End Sub

Sub Circle3D( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional R As Double, Optional ByVal P As Long = 1, Optional ByVal C As Long = -1)
    Dim A As Long, N As Double
    glPushMatrix
    glTranslatef X, Y, Z: glRotatef CamW, 0, 0, 1: glRotatef CamU, 1, 0, 0
    
    glColor4ubv C
    glBegin gl_Line_Strip
    For A = 0 To 360 Step P
        N = CDbl(A) * Rad: glVertex3f R * Sin(N), R * Cos(N), 0
        Next
    glEnd
    glPopMatrix
    End Sub

Sub CircleEx3D( _
        Optional R As Double, Optional ByVal P As Long = 1, Optional ByVal C As Long = -1)
    Dim A As Long, N As Double
    glColor4ubv C
    glBegin gl_Line_Strip
    For A = 0 To 360 Step P
        N = CDbl(A) * Rad: glVertex3f R * Sin(N), R * Cos(N), 0
        Next
    glEnd
    End Sub

Sub Circles3D( _
        Optional R As Double = 1, Optional ByVal P As Long = 1, _
        Optional R2 As Double, Optional ByVal P2 As Long, Optional ByVal C As Long = -1)
    Dim A As Long, N As Double, X As Double, Y As Double
    glColor4ubv C
    glBegin gl_Line_Strip
    For A = 0 To 360 Step P
        N = CDbl(A) * Rad: glVertex3f R * Cos(N), R * Sin(N), 0
        Next
    glEnd
    If P2 Then
        glBegin gl_Lines: If R2 = 0 Then R2 = R * 0.95
        For A = 0 To 360 - 1 Step P2
            N = CDbl(A) * Rad: X = Cos(N): Y = Sin(N)
            glVertex3f X * R, Y * R, 0: glVertex3f X * R2, Y * R2, 0
            Next
        glEnd
        End If
    End Sub

Sub CircleText3D(Optional R As Double = 1, Optional ByVal P As Long = 1, _
        Optional ByVal C As Long = -1)
    Dim A As Long, N As Double
    For A = 0 To 360 - 1 Step P
        N = CDbl(A) * Rad: Text3D A & "º", R * Cos(N), R * Sin(N) ', , , , WhiteDot
        Next
    End Sub

Sub SphereEx3D( _
        Optional R As Double, Optional ByVal P As Long = 1, Optional ByVal P2 As Long = 10, _
        Optional ByVal C As Long = -1)
    Dim N As Double, X As Double, Y As Double, X2 As Long, Y2 As Long
    glColor4ubv C
    For Y2 = -90 + P2 To 90 - P2 Step P2: Y = Y2 * Rad
        glBegin gl_Line_Strip: For X2 = 0 To 360 Step P: X = X2 * Rad
            N = Cos(Y) * R: glVertex3f Sin(X) * N, Cos(X) * N, Sin(Y) * R
            Next: glEnd
        Next
    For X2 = 0 To 360 - P2 Step P2: X = X2 * Rad
        glBegin gl_Line_Strip: For Y2 = -90 To 90 Step P: Y = Y2 * Rad
            N = Cos(Y) * R: glVertex3f Sin(X) * N, Cos(X) * N, Sin(Y) * R
            Next: glEnd
        Next
    End Sub

Sub Disk3D( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional R1 As Double, Optional R2 As Double, Optional ByVal P As Long = 1, _
        Optional ByVal C1 As Long = -1, Optional ByVal C2 As Long = -1, _
        Optional ByVal Reset As Boolean)
    Dim A As Long, N As Double, SinN As Double, CosN As Double
    If R1 >= R2 Then Exit Sub
    glPushMatrix
    glTranslatef X, Y, Z
    If Reset Then ResetMatrix Else glRotatef CamW, 0, 0, 1: glRotatef CamU, 1, 0, 0
    glBegin gl_Quad_Strip
    For A = 0 To 360 Step P
        N = CDbl(A) * Rad: SinN = Sin(N): CosN = Cos(N)
        glColor4ubv C2: glVertex3f R2 * SinN, R2 * CosN, 0
        glColor4ubv C1: glVertex3f R1 * SinN, R1 * CosN, 0
        Next
    glEnd
    glPopMatrix
    End Sub

Sub DiskEx3D( _
        Optional R1 As Double, Optional R2 As Double, Optional ByVal P As Long = 1, _
        Optional ByVal C1 As Long = -1, Optional ByVal C2 As Long = -1)
    Dim A As Long, N As Double, SinN As Double, CosN As Double
    If R1 >= R2 Then Exit Sub
    glBegin gl_Quad_Strip
    For A = 0 To 360 Step P
        N = CDbl(A) * Rad: SinN = Sin(N): CosN = Cos(N)
        glColor4ubv C2: glVertex3f R2 * SinN, R2 * CosN, 0
        glColor4ubv C1: glVertex3f R1 * SinN, R1 * CosN, 0
        Next
    glEnd
    End Sub

Sub Cone3D( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional U1 As Double, Optional V1 As Double, Optional RR2 As Double, _
        Optional U As Double, Optional V As Double, Optional W As Double, _
        Optional A As Double, Optional R1 As Double, Optional R2 As Double, _
        Optional ByVal P As Long = 1, Optional ByVal C1 As Long = -1, _
        Optional ByVal C2 As Long = -1)
    Dim C As Long, N As Double, _
        SinN As Double, CosN As Double, SinA As Double, CosA As Double
    If R1 >= R2 Then Exit Sub
    glPushMatrix
    glTranslatef X, Y, Z
    If TerrainMode Then glRotatef U1 / Rad, 1, 0, 0: glRotatef V1 / Rad, 0, 1, 0: _
        glScalef 1, 1, RR2: glRotatef -U1 / Rad, 1, 0, 0: glRotatef -V1 / Rad, 0, 1, 0
    glRotatef -ATan(U, V) / Rad + 270, 0, 0, 1
    glRotatef ATan3(U, V, W) / Rad + 180, 0, 1, 0
    glBegin gl_Quad_Strip
    SinA = Sin(A): CosA = Cos(A)
    For C = 0 To 360 Step P
        N = CDbl(C) * Rad: SinN = Sin(N) * SinA: CosN = Cos(N) * SinA
        glColor4ubv C2: glVertex3f R2 * SinN, R2 * CosN, R2 * CosA
        glColor4ubv C1: glVertex3f R1 * SinN, R1 * CosN, R1 * CosA
        Next
    glEnd
    glPopMatrix
    End Sub

Sub GetGravity( _
        X As Double, Y As Double, Z As Double, H As Double, _
        Optional X2 As Double, Optional Y2 As Double, Optional Z2 As Double, _
        Optional H2 As Double)
    X2 = 0: Y2 = 0: Z2 = 0
    
    If GravityMode < 1 Then GoTo Ext
    X1 = X - Earth.X: Y1 = Y - Earth.Y: Z1 = Z - Earth.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Earth.Radius Then H2 = Earth.Radius
    H2 = Earth.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    If GravityMode < 2 Then GoTo Ext
    X1 = X - Moon.X: Y1 = Y - Moon.Y: Z1 = Z - Moon.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Moon.Radius Then H2 = Moon.Radius
    H2 = Moon.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    If GravityMode < 3 Then GoTo Ext
    X1 = X - Sun.X: Y1 = Y - Sun.Y: Z1 = Z - Sun.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Sun.Radius Then H2 = Sun.Radius
    H2 = Sun.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    If GravityMode < 4 Then GoTo Ext
    X1 = X - Mercury.X: Y1 = Y - Mercury.Y: Z1 = Z - Mercury.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Mercury.Radius Then H2 = Mercury.Radius
    H2 = Mercury.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    X1 = X - Venus.X: Y1 = Y - Venus.Y: Z1 = Z - Venus.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Venus.Radius Then H2 = Venus.Radius
    H2 = Venus.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    X1 = X - Mars.X: Y1 = Y - Mars.Y: Z1 = Z - Mars.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Mars.Radius Then H2 = Mars.Radius
    H2 = Mars.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    X1 = X - Jupiter.X: Y1 = Y - Jupiter.Y: Z1 = Z - Jupiter.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Jupiter.Radius Then H2 = Jupiter.Radius
    H2 = Jupiter.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    X1 = X - Saturn.X: Y1 = Y - Saturn.Y: Z1 = Z - Saturn.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Saturn.Radius Then H2 = Saturn.Radius
    H2 = Saturn.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    X1 = X - Uranus.X: Y1 = Y - Uranus.Y: Z1 = Z - Uranus.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Uranus.Radius Then H2 = Uranus.Radius
    H2 = Uranus.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    X1 = X - Neptune.X: Y1 = Y - Neptune.Y: Z1 = Z - Neptune.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Neptune.Radius Then H2 = Neptune.Radius
    H2 = Neptune.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
    
    X1 = X - Pluto.X: Y1 = Y - Pluto.Y: Z1 = Z - Pluto.Z
    H2 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): If H2 < Pluto.Radius Then H2 = Pluto.Radius
    H2 = Pluto.GravityMass / H2 / H2 / H2
    X2 = X2 + X1 * H2: Y2 = Y2 + Y1 * H2: Z2 = Z2 + Z1 * H2
Ext:
    H2 = Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2)
    End Sub

Sub Gravity3D(H As Double, CX As Double, CY As Double, CZ As Double)
    Dim S1 As Long, S2 As Long, S3 As Long, _
        X0 As Long, Y0 As Long, Z0 As Long, _
        F1 As FloatRGBA, D1 As FloatRGBA, F2 As FloatRGBA, D2 As FloatRGBA, _
        F3 As FloatRGBA, D3 As FloatRGBA, F4 As FloatRGBA, _
        I1 As Single, _
        H1 As Double, A1 As Double, G1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X4 As Double, Y4 As Double, Z4 As Double, _
        AX As Double, AY As Double, AZ As Double, _
        XX As Double, YY As Double, _
        XZ As Double, YZ As Double
    
    'glRotatef -CamU, 1, 0, 0: glRotatef -CamW, 0, 0, 1 'Main plane correction
    AX = 0: AY = 0: AZ = 1: Rotate -CamU * Rad, AY, AZ: Rotate CamW * Rad, AX, AY
    H1 = Sqr(AX * AX + AZ * AZ): XX = AZ / H1: XZ = AX / H1
    H1 = Sqr(AY * AY + AZ * AZ): YY = AZ / H1: YZ = AY / H1
    'G1 = 10 ^ (((GravityField And &HF0&) \ &H10&) - 3)
    
'Zone
    If GravityField And 2 Then
        A1 = H: S1 = 50
        If GravityField And &H100& Then A1 = A1 * 2: S1 = S1 * 2
        If GravityField And &H200& Then S1 = S1 * 2
        ColorRamp4 cGravityZone4, cGravityZone3, cGravityZone2, cGravityZone1, _
            F1, D1, F2, D2, F3, D3, F4
        For Y0 = -S1 To S1: glBegin gl_Quad_Strip
            Y1 = CY + A1 * Y0 / S1 * YY: X4 = A1 * Y0 / S1 * YZ
            Y4 = A1 / S1 * YY: Z4 = A1 / S1 * YZ
            S3 = Int(Sqr((S1 + 0.5) ^ 2 - Y0 ^ 2))
            For X0 = -S3 To S3
                Z1 = A1 * X0 / S1: X1 = CX + Z1 * XX: Z1 = CZ + Z1 * XZ + X4
                GetGravity X1, Y1, Z1, A1, X2, Y2, Z2, H1
                I1 = VBA.Log(H1 * GravityScale) / Log10
                If I1 <= -2 Then glColor4f F1.R, F1.G, F1.B, F1.A _
                Else If I1 < -1 Then I1 = I1 + 2: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, D1.A * I1 + F1.A _
                Else If I1 < 0 Then I1 = I1 + 1: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, D2.A * I1 + F2.A _
                Else If I1 < 1 Then glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, D3.A * I1 + F3.A _
                Else glColor4f F4.R, F4.G, F4.B, F4.A
                glVertex3f X1, Y1, Z1
                GetGravity X1, Y1 + Y4, Z1 + Z4, A1, X2, Y2, Z2, H1
                I1 = VBA.Log(H1 * GravityScale) / Log10
                If I1 <= -2 Then glColor4f F1.R, F1.G, F1.B, F1.A _
                Else If I1 < -1 Then I1 = I1 + 2: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, D1.A * I1 + F1.A _
                Else If I1 < 0 Then I1 = I1 + 1: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, D2.A * I1 + F2.A _
                Else If I1 < 1 Then glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, D3.A * I1 + F3.A _
                Else glColor4f F4.R, F4.G, F4.B, F4.A
                glVertex3f X1, Y1 + Y4, Z1 + Z4
                Next: glEnd
            Next
        End If
    
'Grid
    If GravityField And 4 Then
        glColor4ubv cGravityGrid
        A1 = H: S1 = 25: S2 = S1 * 2
        If GravityField And &H100& Then A1 = A1 * 2: S1 = S1 * 2: S2 = S2 * 2
        If GravityField And &H200& Then S1 = S1 * 2: S2 = S2 * 2
        For Y0 = -S1 To S1: glBegin gl_Line_Strip
            Y1 = CY + A1 * Y0 / S1 * YY: Y4 = A1 * Y0 / S1 * YZ
            S3 = Int(Sqr((S1 + 0.5) ^ 2 - Y0 ^ 2) * S2 / S1)
            For X0 = -S3 To S3
                Z1 = A1 * X0 / S2: X1 = CX + Z1 * XX: Z1 = CZ + Z1 * XZ + Y4
                GetGravity X1, Y1, Z1, A1, X2, Y2, Z2, H1
                H1 = H1 * GravityScale * A1: glVertex3f X1 + H1 * AX, Y1 + H1 * AY, Z1 - H1 * AZ
                Next: glEnd
                Next
            For X0 = -S1 To S1: glBegin gl_Line_Strip
            X1 = CX + A1 * X0 / S1 * XX: X4 = A1 * X0 / S1 * XZ
            S3 = Int(Sqr((S1 + 0.5) ^ 2 - X0 ^ 2) * S2 / S1)
            For Y0 = -S3 To S3
                Z1 = A1 * Y0 / S2: Y1 = CY + Z1 * YY: Z1 = CZ + Z1 * YZ + X4
                GetGravity X1, Y1, Z1, A1, X2, Y2, Z2, H1
                H1 = H1 * GravityScale * A1: glVertex3f X1 + H1 * AX, Y1 + H1 * AY, Z1 - H1 * AZ
                Next: glEnd
            Next
        End If
    
'Vector
    If GravityField And 8 Then
        A1 = H: S1 = 25: S2 = 5
        If GravityField And &H100& Then A1 = A1 * 2: S1 = S1 * 2
        If GravityField And &H200& Then S1 = S1 * 2: S2 = S2 / 2
        For Y0 = -S1 To S1: glBegin gl_Lines
            Y1 = CY + A1 * Y0 / S1 * YY: Y4 = A1 * Y0 / S1 * YZ
            S3 = Int(Sqr((S1 + 0.5) ^ 2 - Y0 ^ 2))
            For X0 = -S3 To S3
                Z1 = A1 * X0 / S1: X1 = CX + Z1 * XX: Z1 = CZ + Z1 * XZ + Y4
                GetGravity X1, Y1, Z1, A1, X2, Y2, Z2, H1
                glColor4ubv cGravityVectorB
                glVertex3f X1, Y1, Z1
                glColor4ubv cGravityVectorA
                If H1 * GravityScale > 0.02 Then H1 = A1 / H1 * 0.02 * S2 _
                Else H1 = A1 * GravityScale * S2
                'H1 = A1 * S2 * GravityScale
                glVertex3f X1 + X2 * H1, Y1 + Y2 * H1, Z1 + Z2 * H1
                Next: glEnd
            Next
        End If
    
'Disk
    If GravityField And 1 Then
        H1 = Earth.Radius: If Tool And ToolRanges Then _
            If ToolX And ToolIonosphere Then H1 = H1 + Ion
        Field3D Earth, H1, Earth.GravityMass / G0
        H1 = Moon.Radius: If Tool And ToolRanges Then _
            If Status = Transfer And ToolX And ToolBraking And Moon.AirSpeed > 0 Then H1 = H1 + Abs(Brake)
        Field3D Moon, H1, Moon.GravityMass / M0
        Field3D Sun, Sun.Radius, Sun.GravityMass / S0
        If Tool And ToolPlanets Then
            Field3D Mercury, Mercury.Radius, Mercury.GravityMass / 3.7
            Field3D Venus, Venus.Radius, Venus.GravityMass / 8.87
            Field3D Mars, IfDbl(Tool And ToolRanges And Status = Transfer And Mars.AirSpeed > 0, _
                Mars.Radius + Abs(Brake), Mars.Radius), Mars.GravityMass / 3.711
            Field3D Jupiter, Jupiter.Radius, Jupiter.GravityMass / 24.79
            Field3D Saturn, Saturn.Radius, Saturn.GravityMass / 10.44
            Field3D Uranus, Uranus.Radius, Uranus.GravityMass / 8.69
            Field3D Neptune, Neptune.Radius, Neptune.GravityMass / 11.15
            Field3D Pluto, Pluto.Radius, Pluto.GravityMass / 0.658
            End If
        End If
    
    End Sub

Sub Magnetic3D()
    Dim S As Long, _
        S1 As Long, S2 As Long, S3 As Long, _
        X0 As Long, Y0 As Long, Z0 As Long, _
        F1 As Single, I1 As Single, _
        H1 As Double, A1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        D1 As WMMData, D2 As WMMData
    
'Magnetic field
    If (GravityField And &H70&) = 0 Then Exit Sub
    glPushMatrix
    PlanetGL Earth
    
    If GravityField And &H60& Then
        If Earth.Field = 0 Then
            Earth.Field = glGenLists: glNewList Earth.Field, gl_Compile
            
            'Planes
            If GravityField And &H40& Then
                S1 = IfInt(GravityField And &H200&, 5, 20): S3 = 3
                A1 = Earth.Radius * A360 / 360 / 1000
                D1.Year = (Time + T) / YearDays / Day + 2000: D2.Year = D1.Year
                For X0 = -180 To 180 - S1 Step S1
                    Z0 = 0: Do While Z0 <= 15000: Z0 = (Z0 + 500) * 4 / 3
                        For S = -S3 To S3 Step S3 + S3
                            glBegin gl_Quad_Strip
                            D1.Lat = 0: D1.Lon = X0: D1.Alt = Z0: WMMGetElements D1
                            D2.Lat = 0: D2.Lon = X0 + S1: D2.Alt = Z0: WMMGetElements D2
                            For Y0 = 0 To 100 'X+=North Y+=East Z+=Down Dec=H Inc=V
                                F1 = D1.Lat / 360: glColor4f 0.5 - F1, 0.5 + F1, 0.25, D1.F / 60000
                                VertexLLAEx Earth, D1.Lat * Rad, D1.Lon * Rad, D1.Alt * 1000
                                F1 = D1.Lat / 360: glColor4f 0.5 - F1, 0.5 + F1, 0.25, D2.F / 60000
                                VertexLLAEx Earth, D2.Lat * Rad, D2.Lon * Rad, D2.Alt * 1000
                                If (D1.Alt < 0 Or D1.Lat > 90 Or D1.Lat < -90) And _
                                   (D2.Alt < 0 Or D2.Lat > 90 Or D2.Lat < -90) Then Exit For
                                WMMGetElements D1: H1 = D1.F / S
                                D1.Lat = D1.Lat + D1.X / H1: D1.Lon = D1.Lon + D1.Y / H1
                                D1.Alt = D1.Alt - D1.Z / H1 * A1
                                WMMGetElements D2: H1 = D2.F / S
                                D2.Lat = D2.Lat + D2.X / H1: D2.Lon = D2.Lon + D2.Y / H1
                                D2.Alt = D2.Alt - D2.Z / H1 * A1
                                Next
                            glEnd
                            Next
                        Loop
                    Next
                End If
            
            'Lines
            If GravityField And &H20& Then
                S1 = IfInt(GravityField And &H200&, 30, 60): S3 = 3
                A1 = Earth.Radius * A360 / 360 / 1000
                D1.Year = (Time + T) / YearDays / Day + 2000
                For X0 = -180 To 180 - S1 Step S1
                    Z0 = 0: Do While Z0 <= 15000: Z0 = (Z0 + 500) * 4 / 3
                        For S = -S3 To S3 Step S3 + S3
                            glBegin gl_Line_Strip
                            D1.Lat = 0: D1.Lon = X0: D1.Alt = Z0: WMMGetElements D1
                            For Y0 = 0 To 100 'X+=North Y+=East Z+=Down Dec=H Inc=V
                                F1 = D1.Lat / 180: If GravityField And &H100& Then _
                                    I1 = 1 Else I1 = D1.F / 5000
                                glColor4f 0.5 - F1, 0.5 + F1, 0, I1
                                VertexLLAEx Earth, D1.Lat * Rad, D1.Lon * Rad, D1.Alt * 1000
                                If D1.Alt < 0 Or D1.Lat > 90 Or D1.Lat < -90 Then Exit For
                                WMMGetElements D1: H1 = D1.F / S
                                D1.Lat = D1.Lat + D1.X / H1: D1.Lon = D1.Lon + D1.Y / H1
                                D1.Alt = D1.Alt - D1.Z / H1 * A1
                                Next
                            glEnd
                            Next
                        Loop
                    Next
                End If
            
            
            glEndList
            End If
        glCallList Earth.Field
        End If
    
    'Vectors
    If (GravityField And &H10&) <> 0 And Earth.Distance < hMagneticField Then
        If GravityField And &H200& Then S1 = 2: S2 = 5: S3 = 10 Else S1 = 5: S2 = 10: S3 = 20
        H1 = GravityScale * 5: If GravityField And &H100& Then H1 = H1 * 3
        D1.Alt = (Earth.Distance - Earth.Radius) / 1000
        D1.Year = (Time + T) / YearDays / Day + 2000
        For Y0 = -90 + S1 To 90 - S1 Step S1
            If Y0 > -50 And Y0 < 50 Then S = S1 _
                Else If Y0 > -70 And Y0 < 70 Then S = S2 Else S = S3
            For X0 = -180 To 180 - S1 Step S
                D1.Lat = Y0: D1.Lon = X0: WMMGetElements D1
                X1 = -D1.Y * H1: Y1 = -D1.Z * H1: Z1 = D1.X * H1
                glPushMatrix
                glRotatef X0, 0, 0, 1: glRotatef Y0, 1, 0, 0: glTranslatef 0, Earth.Distance, 0
                glBegin gl_Lines
                glColor4ubv cMagneticVectorsN: glVertex3f X1, Y1, Z1
                glColor4ubv cMagneticVectorsS: glVertex3f -X1, -Y1, -Z1
                glEnd
                glPopMatrix
                Next
            Next
        End If
    
    glPopMatrix

    End Sub

Sub Atmosphere3D()
    Dim C As Long, D As Long, E As Long, F As Long, _
        H1 As Double, K1 As Double, T As String, _
        X0 As Long, Y0 As Long, Z0 As Long, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X3 As Long, X4 As Long, Y4 As Long, _
        X5 As Long, Y5 As Long, X6 As Long, Y6 As Long, _
        E1 As Single, I1 As Single, A2 As Single, _
        R1 As Single, G1 As Single, B1 As Single, A1 As Single, _
        F1 As FloatRGBA, D1 As FloatRGBA, F2 As FloatRGBA, D2 As FloatRGBA, _
        F3 As FloatRGBA, D3 As FloatRGBA, F4 As FloatRGBA, D4 As FloatRGBA, _
        F5 As FloatRGBA, D5 As FloatRGBA, F6 As FloatRGBA, D6 As FloatRGBA, _
        F7 As FloatRGBA, D7 As FloatRGBA, F8 As FloatRGBA

    If AtmStyle = 0 Or AtmN <= 0 Then Exit Sub
    glPushMatrix
    PlanetGL Earth
    If GravityField And &H200& Then F = 1 Else F = 5
    If AtmLayer = -3 Then
        D = 1: E = F
    ElseIf AtmLayer = -2 Then
        If Earth.AirSpeed = 0 Then DeleteList Earth.Atm: GoTo NoAtm
        XYZ2LLA Earth, Y2, X2, Z2
        X0 = Int(X2 / Rad + 0.5): If X0 < 0 Then X0 = X0 + 360
        Y0 = 90 - Int(Y2 / Rad + 0.5)
        Z0 = Int(Z2 / AtmL + 0.5): If Z0 < 0 Then Z0 = 0 Else If Z0 > AtmM Then Z0 = AtmM
        D = AtmR(X0, Y0, Z0): If AtmB <> D Then AtmB = D: DeleteList Earth.Atm
        E = AtmN + 1
    ElseIf AtmLayer < 0 Or AtmLayer > AtmN Then
        AtmLayer = -1: D = 1: E = F
    Else
        D = AtmLayer: E = AtmN + 1
        End If
    
    If Earth.Atm = 0 Then
        Earth.Atm = glGenLists: glNewList Earth.Atm, gl_Compile
        
        'Temperature [K]
        If AtmStyle And 1 Then
            If AtmLayer = -1 Then D = 1: E = AtmN + 1
            ColorRamp6 cAtmosphereTemp1, cAtmosphereTemp2, cAtmosphereTemp3, cAtmosphereTemp4, _
                cAtmosphereTemp5, cAtmosphereTemp6, F1, D1, F2, D2, F3, D3, F4, D4, F5, D5, F6
            For Z0 = D To AtmN Step E
                If E > AtmN Then E1 = 1 Else E1 = ((AtmN + 1 - Z0) / AtmN) ^ 2: _
                    If E < 5 Then E1 = E1 / 5
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        I1 = AtmT(X3, Y0, Z0) - Kelvin - 5
                        If I1 < 0 Then
                            If I1 <= -50 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < -30 Then I1 = (I1 + 50) / 20: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < -15 Then I1 = (I1 + 30) / 15: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 + 15) / 15: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 15 Then I1 = I1 / 15: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 30 Then I1 = (I1 - 15) / 15: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else glColor4f F6.R, F6.G, F6.B, F6.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0) * Rad, X0 * Rad, H1
                        I1 = AtmT(X3, Y0 + 1, Z0) - Kelvin - 5
                        If I1 < 0 Then
                            If I1 <= -50 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < -30 Then I1 = (I1 + 50) / 20: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < -15 Then I1 = (I1 + 30) / 15: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 + 15) / 15: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 15 Then I1 = I1 / 15: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 30 Then I1 = (I1 - 15) / 15: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else glColor4f F6.R, F6.G, F6.B, F6.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0 - 1) * Rad, X0 * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Humidity [%]
        If AtmStyle And 2 Then
            If AtmLayer = -1 Then D = 1: E = F
            'A2 = 0.9: I1 = 0: If E > AtmN Then E1 = 150 Else If E >= 5 Then E1 = 500 Else E1 = 1500
            A2 = 0.9: I1 = 50: If E > AtmN Then E1 = 50 Else If E >= 5 Then E1 = 150 Else E1 = 500
            'A2 = 0.9: I1 = 85: If E > AtmN Then E1 = 10 Else If E >= 5 Then E1 = 30 Else E1 = 50
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        A1 = (AtmH(X3, Y0, Z0) - I1) / E1: If A1 > A2 Then A1 = A2
                        R1 = 1: G1 = 1: B1 = 1: If AtmStyle And 4 Then G1 = AtmW(X3, Y0, Z0): _
                            If Z0 > 0 Then G1 = 1 - G1 * 2000 Else G1 = 1 - G1
                        glColor4f R1, G1, B1, A1
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0) * Rad, X0 * Rad, H1
                        A1 = (AtmH(X3, Y0 + 1, Z0) - I1) / E1: If A1 > A2 Then A1 = A2
                        R1 = 1: G1 = 1: B1 = 1: If AtmStyle And 4 Then G1 = AtmW(X3, Y0 + 1, Z0): _
                            If Z0 > 0 Then G1 = 1 - G1 * 2000 Else G1 = 1 - G1
                        glColor4f R1, G1, B1, A1
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0 - 1) * Rad, X0 * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Cloud water mixing ratio [kg/kg]
        If (AtmStyle And 6) = 4 Then
            If AtmLayer = -1 Then D = 1: E = F
            A2 = 0.9: If E > AtmN Then E1 = 5000 Else If E >= 5 Then E1 = 2000 Else E1 = 500
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        A1 = (AtmW(X3, Y0, Z0)): If Z0 > 0 Then A1 = A1 * E1
                        If A1 > A2 Then A1 = A2
                        glColor4f 1, 1, 1, A1
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0) * Rad, X0 * Rad, H1
                        A1 = (AtmW(X3, Y0 + 1, Z0)): If Z0 > 0 Then A1 = A1 * E1
                        If A1 > A2 Then A1 = A2
                        glColor4f 1, 1, 1, A1
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0 - 1) * Rad, X0 * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Wind UVW planes [m/s]
        If AtmStyle And 8 Then
            If AtmLayer = -1 Then D = 1: E = F
            ColorRamp7 cAtmosphereWind1, cAtmosphereWind2, cAtmosphereWind3, cAtmosphereWind4, _
                cAtmosphereWind5, cAtmosphereWind6, cAtmosphereWind7, _
                F1, D1, F2, D2, F3, D3, F4, D4, F5, D5, F6, D6, F7
            If E > AtmN Then E1 = 1 Else If E >= 5 Then E1 = 0.4 Else E1 = 0.1
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        X2 = AtmX(X3, Y0, Z0): Y2 = AtmY(X3, Y0, Z0) ': Z2 = AtmZ(X3, Y0, Z0)
                        I1 = Sqr(X2 * X2 + Y2 * Y2)
                        If I1 < 50 Then
                            If I1 <= 5 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < 15 Then I1 = (I1 - 5) / 10: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < 25 Then I1 = (I1 - 15) / 10: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 - 25) / 25: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 75 Then I1 = (I1 - 50) / 25: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 100 Then I1 = (I1 - 75) / 25: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else If I1 < 125 Then I1 = (I1 - 100) / 25: glColor4f D6.R * I1 + F6.R, D6.G * I1 + F6.G, D6.B * I1 + F6.B, (D6.A * I1 + F6.A) * E1 _
                            Else glColor4f F7.R, F7.G, F7.B, F7.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0) * Rad, X0 * Rad, H1
                        X2 = AtmX(X3, Y0 + 1, Z0): Y2 = AtmY(X3, Y0 + 1, Z0) ': Z2 = AtmZ(X3, Y0 + 1, Z0)
                        I1 = Sqr(X2 * X2 + Y2 * Y2)
                        If I1 < 50 Then
                            If I1 <= 5 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < 15 Then I1 = (I1 - 5) / 10: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < 25 Then I1 = (I1 - 15) / 10: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 - 25) / 25: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 75 Then I1 = (I1 - 50) / 25: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 100 Then I1 = (I1 - 75) / 25: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else If I1 < 125 Then I1 = (I1 - 100) / 25: glColor4f D6.R * I1 + F6.R, D6.G * I1 + F6.G, D6.B * I1 + F6.B, (D6.A * I1 + F6.A) * E1 _
                            Else glColor4f F7.R, F7.G, F7.B, F7.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        VertexLLA Earth, (90 - Y0 - 1) * Rad, X0 * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Wind UVW vectors [m/s]
        If AtmStyle And &H10& Then
            If AtmLayer = -1 Then D = 1: E = F
            E1 = Rad / 20 * GravityScale: I1 = 5566 * GravityScale
            glBegin gl_Lines
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180
                    If Y0 > 0 And Y0 < 180 Then H1 = Sin(Y0 * Rad) Else H1 = 1
                    For X0 = -180 To 180 - 1
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        X1 = X0 * Rad: Y1 = (90 - Y0) * Rad
                        Z1 = AtmA(X3, Y0, Z0): If Z1 < 0 Then Z1 = 0
                        X2 = AtmX(X3, Y0, Z0): Y2 = AtmY(X3, Y0, Z0): Z2 = AtmZ(X3, Y0, Z0) * AtmD(Z0)
                        C = Int(Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2))
                        If C < 10 Then C = cAtmosphereWind1 _
                        Else If C < 20 Then C = cAtmosphereWind2 _
                        Else If C < 40 Then C = cAtmosphereWind3 _
                        Else If C < 65 Then C = cAtmosphereWind4 _
                        Else If C < 90 Then C = cAtmosphereWind5 _
                        Else If C < 115 Then C = cAtmosphereWind6 _
                        Else C = cAtmosphereWind7
                        glColor4ubv C Or &HFF000000: VertexLLA Earth, Y1, X1, Z1
                        glColor4ubv C And &HFFFFFF: VertexLLA Earth, Y1 + Y2 * E1, X1 + X2 / H1 * E1, Z1 - Z2 * I1
                        Next
                    Next
                Next
            glEnd
            End If
        
        glEndList
        End If
    glCallList Earth.Atm
    
    'Wind labels
    If (AtmStyle And &H1000&) <> 0 And (AtmStyle And &H10&) <> 0 And _
            E > AtmN And CamH < hAtmosphereLabels Then
        glDisable gl_Depth_Test
        Z0 = D
        If CamH < hAtmosphereLabels / 4 Then C = 1 _
        Else If CamH < hAtmosphereLabels / 2 Then C = 2 _
        Else C = 5
        XYZ2LLA Earth, Y2, X2
        X4 = Int(X2 / Rad + 0.5): If X4 < 0 Then X4 = X4 + 360
        Y4 = 90 - Int(Y2 / Rad + 0.5)
        Y6 = 5 * C
        For Y0 = 0 To 180 Step C
            If Y0 <= 0 Or Y0 >= 180 Then X6 = 90 Else X6 = Int(Y6 / Sin(Y0 * Rad) + 0.5): If X6 > 90 Then X6 = 90
            For X0 = -180 To 180 - 1 Step C
                If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                X5 = X3 - X4: If X5 < 0 Then X5 = -X5
                If X5 > 180 Then X5 = 360 - X5
                Y5 = Y0 - Y4: If Y5 < 0 Then Y5 = -Y5
                If X5 < X6 And Y5 < Y6 Then
                    X1 = X0 * Rad: Y1 = (90 - Y0) * Rad
                    Z1 = AtmA(X3, Y0, Z0): If Z1 < 0 Then Z1 = 0
                    RasterLLA Earth, Y1, X1, Z1
                    If IsVisible Then
                        X2 = AtmX(X3, Y0, Z0): Y2 = AtmY(X3, Y0, Z0): Z2 = AtmZ(X3, Y0, Z0) * AtmD(Z0)
                        H1 = Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2): K1 = ATan(X2, Y2)
                        If SpeedInfo Then T = ToSpeed(H1, FZero) Else T = ToAirSpeed(H1, FZero)
                        Text3D T & vbBackCrBack & ToAngle(K1 + A180), , , , , , , False
                        End If
                    End If
                Next
            Next
        glEnable gl_Depth_Test
        End If
NoAtm:
    glPopMatrix
    End Sub

Sub AtmosphereFlat3D()
    Dim C As Long, D As Long, E As Long, F As Long, _
        H1 As Double, K1 As Double, T As String, _
        X0 As Long, Y0 As Long, Z0 As Long, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X3 As Long, X4 As Long, Y4 As Long, _
        X5 As Long, Y5 As Long, X6 As Long, Y6 As Long, _
        E1 As Single, I1 As Single, A2 As Single, _
        R1 As Single, G1 As Single, B1 As Single, A1 As Single, _
        F1 As FloatRGBA, D1 As FloatRGBA, F2 As FloatRGBA, D2 As FloatRGBA, _
        F3 As FloatRGBA, D3 As FloatRGBA, F4 As FloatRGBA, D4 As FloatRGBA, _
        F5 As FloatRGBA, D5 As FloatRGBA, F6 As FloatRGBA, D6 As FloatRGBA, _
        F7 As FloatRGBA, D7 As FloatRGBA, F8 As FloatRGBA

    If AtmStyle = 0 Or AtmN <= 0 Then Exit Sub
    glPushMatrix
    If GravityField And &H200& Then F = 1 Else F = 5
    If AtmLayer = -3 Then
        D = 1: E = F
    ElseIf AtmLayer = -2 Then
        If Earth.AirSpeed = 0 Then DeleteList Earth.AtmFlat: GoTo NoAtm
        XYZ2LLA Earth, Y2, X2, Z2
        X0 = Int(X2 / Rad + 0.5): If X0 < 0 Then X0 = X0 + 360
        Y0 = 90 - Int(Y2 / Rad + 0.5)
        Z0 = Int(Z2 / AtmL + 0.5): If Z0 < 0 Then Z0 = 0 Else If Z0 > AtmM Then Z0 = AtmM
        D = AtmR(X0, Y0, Z0): If AtmB <> D Then AtmB = D: DeleteList Earth.AtmFlat
        E = AtmN + 1
    ElseIf AtmLayer < 0 Or AtmLayer > AtmN Then
        AtmLayer = -1: D = 1: E = F
    Else
        D = AtmLayer: E = AtmN + 1
        End If
    
    If Earth.AtmFlat = 0 Then
        Earth.AtmFlat = glGenLists: glNewList Earth.AtmFlat, gl_Compile
        
        'Temperature [K]
        If AtmStyle And 1 Then
            If AtmLayer = -1 Then D = 1: E = AtmN + 1
            ColorRamp6 cAtmosphereTemp1, cAtmosphereTemp2, cAtmosphereTemp3, cAtmosphereTemp4, _
                cAtmosphereTemp5, cAtmosphereTemp6, F1, D1, F2, D2, F3, D3, F4, D4, F5, D5, F6
            For Z0 = D To AtmN Step E
                If E > AtmN Then E1 = 1 Else E1 = ((AtmN + 1 - Z0) / AtmN) ^ 2: _
                    If E < 5 Then E1 = E1 / 5
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        I1 = AtmT(X3, Y0, Z0) - Kelvin - 5
                        If I1 < 0 Then
                            If I1 <= -50 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < -30 Then I1 = (I1 + 50) / 20: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < -15 Then I1 = (I1 + 30) / 15: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 + 15) / 15: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 15 Then I1 = I1 / 15: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 30 Then I1 = (I1 - 15) / 15: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else glColor4f F6.R, F6.G, F6.B, F6.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0) * Rad, H1
                        I1 = AtmT(X3, Y0 + 1, Z0) - Kelvin - 5
                        If I1 < 0 Then
                            If I1 <= -50 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < -30 Then I1 = (I1 + 50) / 20: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < -15 Then I1 = (I1 + 30) / 15: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 + 15) / 15: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 15 Then I1 = I1 / 15: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 30 Then I1 = (I1 - 15) / 15: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else glColor4f F6.R, F6.G, F6.B, F6.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0 - 1) * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Humidity [%]
        If AtmStyle And 2 Then
            If AtmLayer = -1 Then D = 1: E = F
            'A2 = 0.9: I1 = 0: If E > AtmN Then E1 = 150 Else If E >= 5 Then E1 = 500 Else E1 = 1500
            A2 = 0.9: I1 = 50: If E > AtmN Then E1 = 50 Else If E >= 5 Then E1 = 150 Else E1 = 500
            'A2 = 0.9: I1 = 85: If E > AtmN Then E1 = 10 Else If E >= 5 Then E1 = 30 Else E1 = 50
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        A1 = (AtmH(X3, Y0, Z0) - I1) / E1: If A1 > A2 Then A1 = A2
                        R1 = 1: G1 = 1: B1 = 1: If AtmStyle And 4 Then G1 = AtmW(X3, Y0, Z0): _
                            If Z0 > 0 Then G1 = 1 - G1 * 2000 Else G1 = 1 - G1
                        glColor4f R1, G1, B1, A1
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0) * Rad, H1
                        A1 = (AtmH(X3, Y0 + 1, Z0) - I1) / E1: If A1 > A2 Then A1 = A2
                        R1 = 1: G1 = 1: B1 = 1: If AtmStyle And 4 Then G1 = AtmW(X3, Y0 + 1, Z0): _
                            If Z0 > 0 Then G1 = 1 - G1 * 2000 Else G1 = 1 - G1
                        glColor4f R1, G1, B1, A1
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0 - 1) * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Cloud water mixing ratio [kg/kg]
        If (AtmStyle And 6) = 4 Then
            If AtmLayer = -1 Then D = 1: E = F
            A2 = 0.9: If E > AtmN Then E1 = 5000 Else If E >= 5 Then E1 = 2000 Else E1 = 500
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        A1 = (AtmW(X3, Y0, Z0)): If Z0 > 0 Then A1 = A1 * E1
                        If A1 > A2 Then A1 = A2
                        glColor4f 1, 1, 1, A1
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0) * Rad, H1
                        A1 = (AtmW(X3, Y0 + 1, Z0)): If Z0 > 0 Then A1 = A1 * E1
                        If A1 > A2 Then A1 = A2
                        glColor4f 1, 1, 1, A1
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0 - 1) * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Wind UVW planes [m/s]
        If AtmStyle And 8 Then
            If AtmLayer = -1 Then D = 1: E = F
            ColorRamp7 cAtmosphereWind1, cAtmosphereWind2, cAtmosphereWind3, cAtmosphereWind4, _
                cAtmosphereWind5, cAtmosphereWind6, cAtmosphereWind7, _
                F1, D1, F2, D2, F3, D3, F4, D4, F5, D5, F6, D6, F7
            If E > AtmN Then E1 = 1 Else If E >= 5 Then E1 = 0.4 Else E1 = 0.1
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180 - 1
                    glBegin gl_Quad_Strip
                    For X0 = -180 To 180
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        X2 = AtmX(X3, Y0, Z0): Y2 = AtmY(X3, Y0, Z0) ': Z2 = AtmZ(X3, Y0, Z0)
                        I1 = Sqr(X2 * X2 + Y2 * Y2)
                        If I1 < 50 Then
                            If I1 <= 5 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < 15 Then I1 = (I1 - 5) / 10: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < 25 Then I1 = (I1 - 15) / 10: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 - 25) / 25: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 75 Then I1 = (I1 - 50) / 25: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 100 Then I1 = (I1 - 75) / 25: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else If I1 < 125 Then I1 = (I1 - 100) / 25: glColor4f D6.R * I1 + F6.R, D6.G * I1 + F6.G, D6.B * I1 + F6.B, (D6.A * I1 + F6.A) * E1 _
                            Else glColor4f F7.R, F7.G, F7.B, F7.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0) * Rad, H1
                        X2 = AtmX(X3, Y0 + 1, Z0): Y2 = AtmY(X3, Y0 + 1, Z0) ': Z2 = AtmZ(X3, Y0 + 1, Z0)
                        I1 = Sqr(X2 * X2 + Y2 * Y2)
                        If I1 < 50 Then
                            If I1 <= 5 Then glColor4f F1.R, F1.G, F1.B, F1.A * E1 _
                            Else If I1 < 15 Then I1 = (I1 - 5) / 10: glColor4f D1.R * I1 + F1.R, D1.G * I1 + F1.G, D1.B * I1 + F1.B, (D1.A * I1 + F1.A) * E1 _
                            Else If I1 < 25 Then I1 = (I1 - 15) / 10: glColor4f D2.R * I1 + F2.R, D2.G * I1 + F2.G, D2.B * I1 + F2.B, (D2.A * I1 + F2.A) * E1 _
                            Else I1 = (I1 - 25) / 25: glColor4f D3.R * I1 + F3.R, D3.G * I1 + F3.G, D3.B * I1 + F3.B, (D3.A * I1 + F3.A) * E1
                        Else
                            If I1 < 75 Then I1 = (I1 - 50) / 25: glColor4f D4.R * I1 + F4.R, D4.G * I1 + F4.G, D4.B * I1 + F4.B, (D4.A * I1 + F4.A) * E1 _
                            Else If I1 < 100 Then I1 = (I1 - 75) / 25: glColor4f D5.R * I1 + F5.R, D5.G * I1 + F5.G, D5.B * I1 + F5.B, (D5.A * I1 + F5.A) * E1 _
                            Else If I1 < 125 Then I1 = (I1 - 100) / 25: glColor4f D6.R * I1 + F6.R, D6.G * I1 + F6.G, D6.B * I1 + F6.B, (D6.A * I1 + F6.A) * E1 _
                            Else glColor4f F7.R, F7.G, F7.B, F7.A * E1
                            End If
                        H1 = CDbl(AtmA(X3, Y0 + 1, Z0)): If H1 < 0 Then H1 = 0
                        glVertex3f X0 * Rad, (90 - Y0 - 1) * Rad, H1
                        Next
                    glEnd
                    Next
                Next
            End If
        
        'Wind UVW vectors [m/s]
        If AtmStyle And &H10& Then
            If AtmLayer = -1 Then D = 1: E = F
            E1 = Rad / 20 * GravityScale: I1 = 5566 * GravityScale
            glBegin gl_Lines
            For Z0 = D To AtmN Step E
                For Y0 = 0 To 180
                    For X0 = -180 To 180 - 1
                        If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                        X1 = X0 * Rad: Y1 = (90 - Y0) * Rad
                        Z1 = AtmA(X3, Y0, Z0): If Z1 < 0 Then Z1 = 0
                        X2 = AtmX(X3, Y0, Z0): Y2 = AtmY(X3, Y0, Z0): Z2 = AtmZ(X3, Y0, Z0) * AtmD(Z0)
                        C = Int(Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2))
                        If C < 10 Then C = cAtmosphereWind1 _
                        Else If C < 20 Then C = cAtmosphereWind2 _
                        Else If C < 40 Then C = cAtmosphereWind3 _
                        Else If C < 65 Then C = cAtmosphereWind4 _
                        Else If C < 90 Then C = cAtmosphereWind5 _
                        Else If C < 115 Then C = cAtmosphereWind6 _
                        Else C = cAtmosphereWind7
                        glColor4ubv C Or &HFF000000: glVertex3f X1, Y1, Z1
                        glColor4ubv C And &HFFFFFF: glVertex3f X1 + X2 * E1, Y1 + Y2 * E1, Z1 - Z2 * I1
                        Next
                    Next
                Next
            glEnd
            End If
        
        glEndList
        End If
    glCallList Earth.AtmFlat
    
    'Wind labels
    If (AtmStyle And &H1000&) <> 0 And (AtmStyle And &H10&) <> 0 And _
            E > AtmN And CamH < hAtmosphereLabels Then
        Z0 = D
        If CamH < hAtmosphereLabels / 4 Then C = 1 _
        Else If CamH < hAtmosphereLabels / 2 Then C = 2 _
        Else C = 5
        XYZ2LLA Earth, Y2, X2
        X4 = Int(X2 / Rad + 0.5): If X4 < 0 Then X4 = X4 + 360
        Y4 = 90 - Int(Y2 / Rad + 0.5)
        Y6 = 5 * C: X6 = Y6
        For Y0 = 0 To 180 Step C
            For X0 = -180 To 180 - 1 Step C
                If X0 < 0 Then X3 = X0 + 360 Else If X0 < 360 Then X3 = X0 Else X3 = X0 - 360
                X5 = X3 - X4: If X5 < 0 Then X5 = -X5
                If X5 > 180 Then X5 = 360 - X5
                Y5 = Y0 - Y4: If Y5 < 0 Then Y5 = -Y5
                If X5 < X6 And Y5 < Y6 Then
                    X1 = X0 * Rad: Y1 = (90 - Y0) * Rad
                    Z1 = AtmA(X3, Y0, Z0): If Z1 < 0 Then Z1 = 0
                    glRasterPos3f X1, Y1, Z1
                    X2 = AtmX(X3, Y0, Z0): Y2 = AtmY(X3, Y0, Z0): Z2 = AtmZ(X3, Y0, Z0) * AtmD(Z0)
                    H1 = Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2): K1 = ATan(X2, Y2)
                    If SpeedInfo Then T = ToSpeed(H1, FZero) Else T = ToAirSpeed(H1, FZero)
                    Text3D T & vbBackCrBack & ToAngle(K1 + A180), , , , , , , False
                    End If
                Next
            Next
        End If
NoAtm:
    glPopMatrix
    End Sub

Sub Crosshair3D()
    glPushMatrix
    glLoadIdentity
    glRasterPos3f 0, 0, -ZoomMin * 2: glCallList Crosshair.List
    glPopMatrix
    End Sub

Function PlanetProjection(Pl As Planet) As Double
    A1 = Pl.Distance: G1 = A1 - Pl.Altitude 'G1 = Pl.Radius
    If G1 <= 0 Or A1 <= 0 Then GoTo NoAngle
    X1 = -AngleU + Pl.X / A1: Y1 = -AngleV + Pl.Y / A1: Z1 = -AngleW + Pl.Z / A1 'Vector addition
    H1 = (X1 * X1 + Y1 * Y1 + Z1 * Z1) / 4 'Normalize to a squared vector
    If H1 <= 0 Then GoTo ZeroAngle Else If H1 >= 1 Then GoTo NoAngle
    V1 = Atn(Sqr(H1) / Sqr(1 - H1)) * 2 'ASin
    If V1 <= 0 Then
ZeroAngle:
        'U1 = 0: V1 = 0: W1 = A180
        PlanetProjection = A1 - G1
    ElseIf V1 < A180 Then
        H1 = A1 / G1 * Sin(V1): If H1 >= 1 Or H1 <= -1 Then GoTo NoAngle
        W1 = A180 - Atn(H1 / Sqr(1 - H1 * H1)) 'ASin
        U1 = A180 - V1 - W1
        PlanetProjection = G1 * Sin(U1) / Sin(V1)
    Else
NoAngle:
        'U1 = 0: V1 = A180: W1 = 0
        End If
    End Function

Sub FOVArea2D(Cam As Camera)
    Dim X1 As Double, Y1 As Double, Z1 As Double, _
        A1 As Double, W As Long, H As Long
    Z1 = PlanetProjection(Earth): If Z1 < Ground Then _
    Z1 = PlanetProjection(Moon): If Z1 < Ground Then _
    Z1 = PlanetProjection(Sun): If Z1 < Ground Then _
    If GravityMode < 4 Then Exit Sub Else _
    Z1 = PlanetProjection(Mercury): If Z1 < Ground Then _
    Z1 = PlanetProjection(Venus): If Z1 < Ground Then _
    Z1 = PlanetProjection(Mars): If Z1 < Ground Then _
    Z1 = PlanetProjection(Jupiter): If Z1 < Ground Then _
    Z1 = PlanetProjection(Saturn): If Z1 < Ground Then _
    Z1 = PlanetProjection(Uranus): If Z1 < Ground Then _
    Z1 = PlanetProjection(Neptune): If Z1 < Ground Then _
    Z1 = PlanetProjection(Pluto): If Z1 < Ground Then Exit Sub
    W = Cam.Width And Not 1: H = Cam.Height And Not 1
    A1 = Cam.FOV * Rad
    X1 = Z1 * Tan(A1 / 2) * 2: Y1 = Z1 * Tan(A1 * H / W / 2) * 2
    A1 = X1 / W
    Text2D ToAlt(X1, FMini) & " x " & ToAlt(Y1, FMini) & ", pixel " & ToAlt(A1, FMini), _
        Cam.Width - 10, Cam.Height - 5, -3, , True
    End Sub

Sub FOVTime2D(Cam As Camera)
    If Stimated Then Text2D ToDhmsEx(T, , False, True), 6, Cam.Height - 5, -2, , True _
    Else Text2D J2ksDate(Time + T) & "  " & J2ksTime(Time + T), 6, Cam.Height - 5, -2, , True
    End Sub

Sub Trace2D(Cam As Camera)
    Dim B As Long, C As Long, D As Long, E As Long, _
        X As Long, Y As Long, Z As Long, _
        CR As Single, CG As Single, CB As Single
    
    glMatrixMode gl_Projection: glPushMatrix: glLoadMatrixf Cam.Matrix2DX.XX
    glMatrixMode gl_Modelview: glPushMatrix: glLoadIdentity
    
    If ToolX And ToolDeviation Then
        glScalef -1, -1, 1 'ToolX And ToolTrace
        ErrorV(ErrorP) = ControlLastV: ErrorU(ErrorP) = ControlLastU
        X = Crosshair.W \ 2: Y = X * 8: Z = X * 4 \ 3
        D = (Y - Z) \ ErrorN
        'Border
        B = cDeviationBackB: C = cDeviationBackA
        glBegin gl_Quads
        glColor4ubv B: glVertex2i -X, Z: glVertex2i X, Z
        glColor4ubv C: glVertex2i X, Y: glVertex2i -X, Y
        glColor4ubv B: glVertex2i Z, -X: glVertex2i Z, X
        glColor4ubv C: glVertex2i Y, X: glVertex2i Y, -X
        glEnd
        'Graph
        glColor4ubv cDeviationCenter
        ColorRGB cDeviationLine, CR, CG, CB
        glBegin gl_Lines
        glVertex2i 0, Z: glVertex2i 0, Y
        glVertex2i Z, 0: glVertex2i Y, 0
        glEnd
        X = -X 'ToolX And ToolTrace
        E = ErrorP
        glBegin gl_Line_Strip: For B = 0 To ErrorN
            glColor4f CR, CG, CB, 1 - (B / ErrorN)
            glVertex2f ErrorV(E) * X, B * D + Z + 1
            E = E - 1: If E < 0 Then E = ErrorN
            Next: glEnd
        E = ErrorP
        glBegin gl_Line_Strip: For B = 0 To ErrorN
            glColor4f CR, CG, CB, 1 - (B / ErrorN)
            glVertex2f B * D + Z + 1, ErrorU(E) * X
            E = E - 1: If E < 0 Then E = ErrorN
            Next: glEnd
        End If
    
    If ToolX And ToolTrace Then
        If ToolX And ToolDeviation Then glScalef -1, -1, 1
        If TargetPos.Z > 0 Then _
            ErrorX(ErrorP) = TargetPos.X: ErrorY(ErrorP) = TargetPos.Y: ErrorZ(ErrorP) = True _
        Else ErrorX(ErrorP) = 0: ErrorY(ErrorP) = 0: ErrorZ(ErrorP) = False
        X = (Cam.Width \ 2) + 1: Y = (Cam.Height \ 2) + 1
        'Axes
        glColor4ubv cTraceCenter
        glBegin gl_Lines
        glVertex2i -X, 0: glVertex2i X, 0
        glVertex2i 0, -Y: glVertex2i 0, Y
        glEnd
        'Cursor
        If TargetPos.Z > 0 Then
            glColor4ubv cTraceCursor
            glBegin gl_Lines
            glVertex2f -X, TargetPos.Y: glVertex2f X, TargetPos.Y
            glVertex2f TargetPos.X, -Y: glVertex2f TargetPos.X, Y
            glEnd
            End If
        'Graph
        ColorRGB cTraceLine, CR, CG, CB
        E = ErrorP
        glBegin gl_Line_Strip: For B = 0 To ErrorN
            glColor4f CR, CG, CB, 1 - (B / ErrorN)
            If ErrorZ(E) Then glVertex2f ErrorX(E), ((B * Y) \ ErrorN) _
            Else glEnd: glBegin gl_Line_Strip
            E = E - 1: If E < 0 Then E = ErrorN
            Next: glEnd
        E = ErrorP
        glBegin gl_Line_Strip: For B = 0 To ErrorN
            glColor4f CR, CG, CB, 1 - (B / ErrorN)
            If ErrorZ(E) Then glVertex2f ((B * X) \ ErrorN), ErrorY(E) _
            Else glEnd: glBegin gl_Line_Strip
            E = E - 1: If E < 0 Then E = ErrorN
            Next: glEnd
        End If
    
    glPopMatrix
    glMatrixMode gl_Projection: glPopMatrix
    glMatrixMode gl_Modelview
    
    End Sub

Sub Glow3D(Pl As Planet, Cam As Camera, Optional Radius1 As Double, Optional Radius2 As Double, _
        Optional ByVal C1 As Long, Optional ByVal C2 As Long)
    Dim C As Long, A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double
    X1 = -(Pl.X - Cam.CenterX): Y1 = -(Pl.Y - Cam.CenterY): Z1 = -(Pl.Z - Cam.CenterZ)
    H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
    If H1 < Pl.Radius * 10 And H1 > Pl.Radius * 0.99 Then
        If H1 <= 0 Then A1 = A90 Else A1 = ACos(Pl.Radius / H1): If A1 < Rad / 100 Then A1 = Rad / 100
        If Radius2 > H1 - Pl.Radius Then Radius2 = Pl.Radius * Sin(A1) / 35.59
        C = (1 - H1 / Pl.Radius / 10) * 255: If C >= 128 Then C = C - 256
        C = C * &H1000000
        If Ortho And Follow <> ToOnBoard Then
            Disk3D Pl.X, Pl.Y, Pl.Z, Pl.Radius - Radius2, Pl.Radius, 1, C1, C Or C1, True
            Disk3D Pl.X, Pl.Y, Pl.Z, Pl.Radius, Pl.Radius + Radius1, 1, C Or C1, C2, True
        Else
            Cone3D Pl.X, Pl.Y, Pl.Z, Pl.AngleU, Pl.AngleV, Pl.Radius2 / Pl.Radius, X1, Y1, Z1, A1, _
                Pl.Radius - Radius2, Pl.Radius, 1, C1, C Or C1
            Cone3D Pl.X, Pl.Y, Pl.Z, Pl.AngleU, Pl.AngleV, Pl.Radius2 / Pl.Radius, X1, Y1, Z1, A1, _
                Pl.Radius, Pl.Radius + Radius1, 1, C Or C1, C2
            End If
        End If
    End Sub

Sub Field3D(Pl As Planet, _
        Optional R As Double, Optional G As Double, Optional ByVal Reset As Boolean)
    Dim R1 As Double, R2 As Double, R3 As Double ', R4 As Double
    R1 = Sqr(G * 2): If R1 < R Then R1 = R '50%
    R2 = Sqr(G * 10): If R2 < R Then R2 = R '10%
    R3 = Sqr(G * 100): If R3 < R Then R3 = R '1%
    'R4 = Sqr(G * 1000): If R4 < R Then R4 = R '0.1%
    If R < R1 Then Disk3D Pl.X, Pl.Y, Pl.Z, R, R1, , cGravityDisk1B, cGravityDisk1A, Reset
    If R < R2 Then Disk3D Pl.X, Pl.Y, Pl.Z, R1, R2, , cGravityDisk2B, cGravityDisk2A, Reset
    If R < R3 Then Disk3D Pl.X, Pl.Y, Pl.Z, R2, R3, , cGravityDisk3B, cGravityDisk3A, Reset
    'If R < R4 Then Disk3D Pl.X, Pl.Y, Pl.Z, R3, R4, , cGravityDisk4B, cGravityDisk4A, Reset
    End Sub

Sub Orbit3D(Pl As Planet, Rt As Planet, _
        Optional ByVal Selected As Boolean, Optional ByVal DrawAlt As Boolean)
    Dim A As Double, R As Double, X As Double, Y As Double, Z As Double, _
        I As Double, O As Double, P As Double, S As Double, L As Double, E As Double, _
        M As Double, N As Double, D As Double, D2 As Double, _
        T1 As Double, T3 As Double, T2 As Double, _
        SinA As Double, CosA As Double, _
        SinI As Double, CosI As Double, _
        SinO As Double, CosO As Double, _
        Ps As Double, V As Boolean
    With Pl
    If .ID = ToSun Then Exit Sub
    V = SelectedType = TypePlanet And SelectedN = .ID: If Selected Xor V Then Exit Sub
    If DrawAlt Then Altitude3D .X, .Y, .Z, Rt.X, Rt.Y, Rt.Z, cAltitudePlane
    If .ID = ToMoon Then Circle3D Rt.X, Rt.Y, Rt.Z, _
        Pl.OrbitDistance, , IfInt(V, cPlanetOrbitSel, cPlanetOrbit): Exit Sub
    glPushMatrix
    glTranslatef Rt.X, Rt.Y, Rt.Z
    If V Then glColor4ubv cPlanetOrbitSel Else glColor4ubv cPlanetOrbit
    glBegin gl_Line_Strip
    Select Case .Class
    Case ClassMoon
        T1 = (Time + T) / Day + 1.5: Ps = Rad
        'Circle3D , , , , , (.OrbitS + .OrbitSV * T1) * (1 - .OrbitE * .OrbitE) / (1 + .OrbitE), Ps / Rad, cPlanetOrbit
        T1 = T1
        T3 = Ps / .OrbitLV: T2 = T1 + (A180 + Ps / 2) / .OrbitLV: T1 = T1 - A180 / .OrbitLV
        Do While T1 < T2
            'Get the osculating elements
            I = .OrbitI + .OrbitIV * T1                             'Inclination to the ecliptic
            O = .OrbitO + .OrbitOV * T1                             'Longitude of ascending node
            P = .OrbitP + .OrbitPV * T1                             'Argument of Perigee?
            S = .OrbitS + .OrbitSV * T1                             'Semi-major axis
            L = .OrbitL + .OrbitLV * T1                             'Longitude
            E = .OrbitE + .OrbitEV * T1                             'Eccentricity
            'Position of the Moon in its orbit
            A = L + E * Sin(L) * (1 + E * Cos(L))
            X = S * (Cos(A) - E)
            Y = S * (Sqr(1 - E * E) * Sin(A))
            A = ATan(Y, X) + P
            R = -Sqr(X * X + Y * Y) ' * (1 - Moon.M / Earth.M)
            'Geocentric rectangular coordinates
            SinA = Sin(A): CosA = Cos(A): SinI = Sin(I): CosI = Cos(I): SinO = Sin(O): CosO = Cos(O)
            glVertex3f _
                R * (CosO * CosA - SinO * SinA * CosI), _
                R * (SinO * CosA + CosO * SinA * CosI), _
                R * SinA * SinI
            T1 = T1 + T3
            Loop
    Case Else
        T1 = (Time + T) / Day: Ps = Rad
        'Circle3D , , , , , (.OrbitS + .OrbitSV * T1) * (1 - .OrbitE * .OrbitE) / (1 + .OrbitE), Ps / Rad, cPlanetOrbit
        T3 = Ps / .OrbitLV: T2 = T1 + (A180 + Ps / 2) / .OrbitLV: T1 = T1 - A180 / .OrbitLV
        Do While T1 < T2
            'Get the osculating elements
            I = .OrbitI + .OrbitIV * T1                             'Inclination to the ecliptic
            O = .OrbitO + .OrbitOV * T1                             'Longitude of ascending node
            P = .OrbitP + .OrbitPV * T1                             'Argument of Perigee?
            S = .OrbitS + .OrbitSV * T1                             'Semi-major axis
            L = .OrbitL + .OrbitLV * T1                             'Longitude
            E = .OrbitE + .OrbitEV * T1                             'Eccentricity
            'Position of the planet in its orbit
            M = L - P: N = M: D = 0.05
            Do
                D2 = D: D = N - E * Sin(N) - M: N = N - D / (1 - E * Cos(N))
                Loop While Abs(D) >= 0.0000000001 And D <> D2
            A = 2 * Atn(((1 + E) / (1 - E)) ^ 0.5 * Tan(0.5 * N))
            If A < 0 Then A = A + A360
            R = S * (1 - E * E) / (1 + E * Cos(A))
            'Heliocentric rectangular coordinates
            A = A + P - O
            SinA = Sin(A): CosA = Cos(A): SinI = Sin(I): CosI = Cos(I): SinO = Sin(O): CosO = Cos(O)
            glVertex3f _
                R * (CosO * CosA - SinO * SinA * CosI), _
                R * (SinO * CosA + CosO * SinA * CosI), _
                R * SinA * SinI
            T1 = T1 + T3
            Loop
        End Select
    glEnd
    glPopMatrix
    End With
    End Sub

Sub Trajectory3D(Pl As Planet, Optional dT As Double = 0.5, Optional ByVal Relative As Boolean)
    Dim N As Long, P As Long, T1 As Double, dT1 As Double, _
        W As Double, H1 As Double, G1 As Double, _
        A As Double, A1 As Double, A2 As Double, _
        AX As Double, AY As Double, AZ As Double, _
        VX As Double, VY As Double, VZ As Double, _
        X As Double, Y As Double, Z As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        V1 As Double, V2 As Double
    glPushMatrix
    glTranslatef Pl.X, Pl.Y, Pl.Z
    glColor4ubv cTrajectory
    glBegin gl_Line_Strip
    If IsStatistics Then V1 = Pl.VerticalSpeed: V2 = Pl.LastVerticalSpeed: H1 = Pl.Distance
    VX = Pl.SpeedX: VY = Pl.SpeedY: VZ = Pl.SpeedZ
    X = -Pl.X: Y = -Pl.Y: Z = -Pl.Z
    A = X / Y: A1 = A
    T1 = 0
    For N = 0 To 100000
        If Relative Then
            X1 = X: Y1 = Y: Z1 = Z
            If Pl.AngleU <> 0 Then Rotate -Pl.AngleU, Y1, Z1: Rotate -W, X1, Y1: Rotate Pl.AngleU, Y1, Z1
            If Pl.AngleV <> 0 Then Rotate -Pl.AngleV, X1, Z1: Rotate -W, X1, Y1: Rotate Pl.AngleV, X1, Z1
            glVertex3f X1, Y1, Z1
        Else
            glVertex3f X, Y, Z
            End If
        If IsStatistics Then V2 = V1: V1 = H1
        H1 = Sqr(X * X + Y * Y + Z * Z): If H1 > StarsDistance Then Exit For
        If H1 > Pl.Radius Then dT1 = H1 / Pl.Radius * dT Else dT1 = dT: If IsCollision Then If V1 <> 0 And V2 <> 0 Then _
            TrajectoryPoint3D "Crashed on " & Planets(Pl.ID) & _
                IIf(Stimated, " in " & ToDhmsEx(T1), " at " & ToDhmsEx(T + T1)), _
                X, Y, Z, X1, Y1, Z1, RedDot, Relative: Exit For Else Exit For
        If IsStatistics Then V1 = H1 - V1: If V1 >= 0 Xor V2 >= 0 Then If V1 <> 0 And V2 <> 0 Then _
            TrajectoryPoint3D IIf(V1 >= 0, PlanetPeriapsis(Pl.ID), PlanetApoapsis(Pl.ID)) & " " & ToAlt(H1 - Pl.Radius, FShort) & _
                IIf(Stimated, " in " & ToDhmsEx(T1), " at " & ToDhmsEx(T + T1)), _
                X, Y, Z, X1, Y1, Z1, GreenDot, Relative
        G1 = Pl.GravityMass / H1 / H1 / H1 * dT1
        VX = VX - X * G1: VY = VY - Y * G1: VZ = VZ - Z * G1
        X = X + VX * dT1: Y = Y + VY * dT1: Z = Z + VZ * dT1
        W = W + Pl.AngularSpeedW * dT1
        T1 = T1 + dT1
        A2 = A1: A1 = X / Y: If A1 >= A Xor A2 >= A Then If N Then P = P + 1: If P >= 4 Then Exit For
        Next
    glEnd
    glPopMatrix
    End Sub

Sub TrajectoryPoint3D(Text As String, X As Double, Y As Double, Z As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, ByVal List As Long, ByVal Relative As Boolean)
    If Not Relative Then X1 = X: Y1 = Y: Z1 = Z
    glEnd
    glDisable gl_Depth_Test: Text3D Text, X1, Y1, Z1, , , List, 1
    If (Tool And ToolHidden) = 0 Then glEnable gl_Depth_Test
    glBegin gl_Line_Strip: glColor4ubv cTrajectory: glVertex3f X1, Y1, Z1
    End Sub

Sub Plane3D( _
        Optional Lat As Double, Optional Lon As Double, Optional Head As Double, _
        Optional R1 As Double, Optional R2 As Double, Optional ByVal P As Long = 1, _
        Optional ByVal C1 As Long = -1, Optional ByVal C2 As Long = -1)
    Dim A As Long, N As Double
    glPushMatrix
    glTranslatef Earth.X, Earth.Y, Earth.Z
    glRotatef Earth.AngleU / Rad, 1, 0, 0: glRotatef Earth.AngleV / Rad, 0, 1, 0: glRotatef EarthAngle / Rad, 0, 0, 1
    glRotatef Lon / Rad, 0, 0, 1
    glRotatef Lat / Rad, 1, 0, 0
    glRotatef 90 - Head / Rad, 0, 1, 0
    If R1 > 0 Then glColor4ubv C1: glBegin gl_Line_Strip: For A = 0 To 360 Step P: _
        N = CDbl(A) * Rad: glVertex3f R1 * Sin(N), R1 * Cos(N), 0: Next: glEnd
    If R2 > 0 Then glColor4ubv C2: glBegin gl_Line_Strip: For A = 0 To 360 Step P: _
        N = CDbl(A) * Rad: glVertex3f R2 * Sin(N), R2 * Cos(N), 0: Next: glEnd
    glPopMatrix
    End Sub

Sub PointFlat3D(Optional Txt As String, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal L As Long = -1, Optional ByVal DrawAlt As Boolean = True)
    Dim Lat As Double, Lon As Double, Alt As Double
    XYZ2LLA Earth, Lat, Lon, Alt, X, Y, Z
    If DrawAlt Then If ToolX And ToolAltitude Then Line3D Lon, Lat, , , , Alt, cAltitudeProj
    If L < 0 Then L = GreenDot
    If Txt <> "" Then Text3D Txt, Lon, Lat, Alt, , , L _
    Else glRasterPos3f Lon, Lat, Alt: If L Then glCallList L
    End Sub

Sub PointPlanet3D(Pl As Planet, Optional Txt As String, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double)
    Dim X As Double, Y As Double, Z As Double
    LLA2XYZP Pl, Lat, Lon, Alt + PointScale * CamR, X, Y, Z
    glPushMatrix
    PlanetGL Pl
    glRasterPos3f X, Y, Z: If IsVisible Then _
        If Txt <> "" Then Text3D Txt, , , , , , GreenDot, False _
        Else glCallList GreenDot
    glPopMatrix
    End Sub

Sub PointsPlanet3D(Pl As Planet)
    PointPlanet3D Pl, "S", -A90: PointPlanet3D Pl, "N", A90: PointPlanet3D Pl, "0"
    End Sub

Sub Locations3D(Optional ByVal Label As Boolean = True)
    Dim C As Long, N As Long
    If TargetsN > 0 Then
        For N = TargetsN To 1 Step -1
            If Targets(N).Class = TypeLocation Then SelN = Targets(N).N: _
                If SelN >= 0 And SelN < LocationN Then _
                    If SelectedType <> TypeLocation Or SelN <> SelectedN Then _
                        Location3D Location(SelN), Label
            Next
    Else
        For SelN = 0 To LocationN - 1
            C = Location(SelN).Planet: If (SelectedType = TypeLocation And SelN = SelectedN) Or _
                ((C = Target Or C = Follow Or C = ObjectPlanet) Or (C = ToEarth And Target = ToMoon)) Then _
                    Location3D Location(SelN), Label
            Next
        End If
    End Sub

Sub LocationsFlat3D(Optional ByVal Label As Boolean = True)
    Dim N As Long
    If TargetsN > 0 Then
        For N = TargetsN To 1 Step -1
            If Targets(N).Class = TypeLocation Then SelN = Targets(N).N: _
                If SelN >= 0 And SelN < LocationN Then _
                    If SelectedType <> TypeLocation Or SelN <> SelectedN Then _
                        LocationFlat3D Location(SelN), Label
            Next
    Else
        For SelN = 0 To LocationN - 1
            If SelectedType <> TypeLocation Or SelN <> SelectedN Then _
                LocationFlat3D Location(SelN), Label
            Next
        End If
    End Sub

Sub Location3D(L As Location, Optional ByVal Label As Boolean = True)
    Select Case L.Planet
    Case ToMoon:    If Label Then PointPlanet3D Moon, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Moon, , L.Lat, L.Lon, L.Alt
    Case ToMercury: If Label Then PointPlanet3D Mercury, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Mercury, , L.Lat, L.Lon, L.Alt
    Case ToVenus:   If Label Then PointPlanet3D Venus, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Venus, , L.Lat, L.Lon, L.Alt
    Case ToMars:    If Label Then PointPlanet3D Mars, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Mars, , L.Lat, L.Lon, L.Alt
    Case ToJupiter: If Label Then PointPlanet3D Jupiter, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Jupiter, , L.Lat, L.Lon, L.Alt
    Case ToSaturn:  If Label Then PointPlanet3D Saturn, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Saturn, , L.Lat, L.Lon, L.Alt
    Case ToUranus:  If Label Then PointPlanet3D Uranus, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Uranus, , L.Lat, L.Lon, L.Alt
    Case ToNeptune: If Label Then PointPlanet3D Neptune, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Neptune, , L.Lat, L.Lon, L.Alt
    Case ToPluto:   If Label Then PointPlanet3D Pluto, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Pluto, , L.Lat, L.Lon, L.Alt
    Case ToSun:     If Label Then PointPlanet3D Sun, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Sun, , L.Lat, L.Lon, L.Alt
    Case Else:      If Label Then PointPlanet3D Earth, L.Name, L.Lat, L.Lon, L.Alt _
                    Else PointPlanet3D Earth, , L.Lat, L.Lon, L.Alt
        End Select
    End Sub

Sub LocationFlat3D(L As Location, Optional ByVal Label As Boolean = True)
    If L.Planet <> ToEarth Then Exit Sub
    If Label Then Text3D L.Name, L.Lon, L.Lat, L.Alt, , , GreenDot _
    Else glRasterPos3f L.Lon, L.Lat, L.Alt: glCallList GreenDot
    If IsSelect Then GetSelectedValid
    End Sub

Sub LocationEarth3D(R As Location)
    RasterLLA Earth, R.Lat, R.Lon, R.Alt: If IsVisible Then Text3D R.Name, , , , , , GreenDot, False
    End Sub

Sub LocationEarthFlat3D(R As Location)
    Text3D R.Name, R.Lon, R.Lat, R.Alt, , , GreenDot: If IsSelect Then GetSelectedValid
    End Sub

Sub Stations3D(Optional ByVal Label As Boolean)
    Dim N As Long
    If TargetsN > 0 Then
        For N = TargetsN To 1 Step -1
            If Targets(N).Class = TypeStation Then SelN = Targets(N).N: _
                If SelN >= 0 And SelN < StationN Then _
                    If SelectedType <> TypeStation Or SelN <> SelectedN Then _
                        Station3D Station(SelN), SelN, Label
            Next
    Else
        For SelN = StationLast To StationFirst Step -1
            If SelectedType <> TypeStation Or SelN <> SelectedN Then _
                Station3D Station(SelN), SelN, Label
            Next
        End If
    End Sub

Sub StationsFlat3D(Optional ByVal Label As Boolean)
    Dim N As Long
    If TargetsN > 0 Then
        For N = TargetsN To 1 Step -1
            If Targets(N).Class = TypeStation Then SelN = Targets(N).N: _
                If SelN >= 0 And SelN < StationN Then _
                    If SelectedType <> TypeStation Or SelN <> SelectedN Then _
                        StationFlat3D Station(SelN), Label
            Next
    Else
        For SelN = StationLast To StationFirst Step -1
            If SelectedType <> TypeStation Or SelN <> SelectedN Then _
                StationFlat3D Station(SelN), Label
            Next
        End If
    End Sub

Sub Station3D(L As Location, Optional ByVal N As Long, Optional ByVal Label As Boolean)
    Dim A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double
    If IsStationVisible(L, H1, A1, X1, Y1, Z1) Then
        glEnable gl_Depth_Test: glColor4ubv cStationLine
        glBegin gl_Lines: glVertex3f X1, Y1, Z1: glVertex3f 0, 0, 0: glEnd
        glDisable gl_Depth_Test
        glRasterPos3f X1, Y1, Z1: If IsVisible Then _
            If Not Label Then glCallList GreenDot _
            Else If Tool And ToolInfo Then _
                Text3D L.Name & vbBackCrBack & ToAlt(H1, FMini) & " " & _
                    ToAngle(A1, FAngle), , , , , , GreenDot, False _
            Else Text3D L.Name, , , , , , GreenDot, False
    Else
        glRasterPos3f X1, Y1, Z1: If IsVisible Then glCallList RedDot
        End If
    End Sub

Sub StationFlat3D(L As Location, Optional ByVal Label As Boolean)
    Dim A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double
    If IsStationVisible(L, H1, A1) Then
        XYZ2LLA Earth, Y1, X1, Z1
        glColor4ubv cStationLine
        glBegin gl_Lines: glVertex3f L.Lon, L.Lat, L.Alt: glVertex3f X1, Y1, Z1: glEnd
        If Not Label Then _
            glRasterPos3f L.Lon, L.Lat, L.Alt: glCallList GreenDot _
        Else If Tool And ToolInfo Then _
            Text3D L.Name & vbBackCrBack & ToAlt(H1, FMini) & " " & _
                ToAngle(A1, FAngle), L.Lon, L.Lat, L.Alt, , , GreenDot _
        Else Text3D L.Name, L.Lon, L.Lat, L.Alt, , , GreenDot
    Else
        glRasterPos3f L.Lon, L.Lat, L.Alt: glCallList RedDot
        End If
    If IsSelect Then GetSelectedValid
    End Sub

Sub StationLine3D(L As Location, Optional ByVal ShowLabel As Boolean)
    Dim A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double
    If Not IsStationVisible(L, H1, A1, X1, Y1, Z1) Then Exit Sub
    glEnable gl_Depth_Test: glColor4ubv cStationTarget
    glBegin gl_Lines: glVertex3f X1, Y1, Z1: glVertex3f 0, 0, 0: glEnd
    glDisable gl_Depth_Test
    If ShowLabel Then If Tool And ToolInfo Then _
        glRasterPos3f X1, Y1, Z1: If IsVisible Then _
            Text3D ToAlt(H1, FMini) & " " & _
                ToAngle(A1, FAngle), , , , 4, TextFont.Y - 4, , False
    End Sub

Sub StationLineFlat3D(L As Location, Optional ByVal ShowLabel As Boolean)
    Dim A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double
    If Not IsStationVisible(L, H1, A1) Then Exit Sub
    XYZ2LLA Earth, Y1, X1, Z1
    glColor4ubv cStationTarget
    glBegin gl_Lines: glVertex3f L.Lon, L.Lat, L.Alt: glVertex3f X1, Y1, Z1: glEnd
    If ShowLabel Then If Tool And ToolInfo Then _
        Text3D ToAlt(H1, FMini) & " " & _
            ToAngle(A1, FAngle), L.Lon, L.Lat, L.Alt, 4, TextFont.Y - 1
    End Sub

Sub Satellites3D( _
        Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True)
    Dim C As Long, X1 As Double, Y1 As Double, Z1 As Double, T1 As Double
    If Label Or Orbit Then
        For SelN = SatelliteLast To SatelliteFirst Step -1
            If SelectedType <> TypeSatellite Or SelN <> SelectedN Then _
                Satellite3D Satellite(SelN), Label, Orbit
            Next
    Else
        T1 = Time + T
        If (Tool And ToolPoints) = 0 Then glEnable gl_Depth_Test
        glColor4ubv cSatellitePoint: glPointSize hPointSize
        glBegin gl_Points: For C = SatelliteLast To SatelliteFirst Step -1
            TLE2XYZ Earth, Satellite(C), T1, X1, Y1, Z1: glVertex3f X1, Y1, Z1
            Next: glEnd
        glPointSize 1: If (Tool And ToolPoints) = 0 Then glDisable gl_Depth_Test
        End If
    End Sub

Sub SatellitesFlat3D( _
        Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True)
    For SelN = SatelliteLast To SatelliteFirst Step -1
        If SelectedType <> TypeSatellite Or SelN <> SelectedN Then _
            SatelliteFlat3D Satellite(SelN), Label, Orbit
        Next
    End Sub

Sub Satellite3D(L As TLE, _
        Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True, _
        Optional ByVal Selected As Boolean)
    Dim A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        T1 As Double, T2 As Double, T3 As Double, T4 As Double, _
        Ps As Double, V As Boolean
    T1 = Time + T
    TLE2XYZ Earth, L, T1, X1, Y1, Z1, A1
    If ObjectVisibility > 0 And Not Selected Then
        X2 = X1 - CamX: Y2 = Y1 - CamY: Z2 = Z1 - CamZ
        If Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2) > ObjectVisibility Then
            If (Tool And ToolPoints) = 0 Then glEnable gl_Depth_Test
            glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points: glVertex3f X1, Y1, Z1: glEnd: glPointSize 1
            If (Tool And ToolPoints) = 0 Then glDisable gl_Depth_Test
            Exit Sub
            End If
        End If
    If Orbit Then
        If (Tool And ToolHidden) = 0 Then glEnable gl_Depth_Test
        If Selected Then glColor4ubv cSatelliteOrbitSel Else glColor4ubv cSatelliteOrbit
        glBegin gl_Line_Strip
        T1 = Time + T: T4 = T1: Ps = Rad
        T3 = Ps / L.M: T2 = T1 + (A180 + Ps / 2) / L.M: T1 = T1 - A180 / L.M
        Do While T1 < T2
            TLE2XYZ Earth, L, T1, X2, Y2, Z2
            If Relative = ToAir Then X2 = X2 - Earth.X: Y2 = Y2 - Earth.Y: Z2 = Z2 - Earth.Z: _
                Rotate -Earth.AngleU, Y2, Z2: Rotate -(T1 - T4) * Earth.AngularSpeedW, X2, Y2: _
                Rotate Earth.AngleU, Y2, Z2: _
                X2 = X2 + Earth.X: Y2 = Y2 + Earth.Y: Z2 = Z2 + Earth.Z:
            glVertex3f X2, Y2, Z2
            T1 = T1 + T3
            Loop
        glEnd
        If (Tool And ToolHidden) = 0 Then glDisable gl_Depth_Test
        End If
    H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): V = H1 <= SatelliteDist
    If V Then
        glEnable gl_Depth_Test: glColor4ubv cSatelliteLine
        glBegin gl_Line_Strip: glVertex3f X1, Y1, Z1: glVertex3f 0, 0, 0: glEnd
        glDisable gl_Depth_Test
        End If
    glRasterPos3f X1, Y1, Z1: If IsVisible Then _
        If Not Label Then glCallList GreenDot _
        Else If (Tool And ToolInfo) = 0 Then Text3D L.Name, , , , , , GreenDot, False _
        Else If Not V Then Text3D L.Name & vbBackCrBack & _
            ToAlt(A1 - Earth.Radius, FMini), , , , , , GreenDot, False _
        Else Text3D L.Name & vbBackCrBack & ToAlt(A1 - Earth.Radius, FMini) & vbBackCrBack & _
            ToAlt(H1, FMini), , , , , , GreenDot, False
    End Sub

Sub SatelliteFlat3D(L As TLE, _
        Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True, _
        Optional ByVal Selected As Boolean)
    Dim A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X3 As Double, Y3 As Double, Z3 As Double, _
        X4 As Double, _
        T1 As Double, T3 As Double, T2 As Double, T4 As Double, T5 As Double, _
        Ps As Double, V As Boolean
    TLE2XYZ Earth, L, Time + T, X1, Y1, Z1, A1: H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
    If ObjectVisibility > 0 And Not Selected Then
        'X2 = X1 - CamX: Y2 = Y1 - CamY: Z2 = Z1 - CamZ
        'If Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2) > ObjectVisibility Then
        If H1 > ObjectVisibility Then
            XYZ2LLA Earth, Y2, X2, Z2, X1, Y1, Z1
            glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points: glVertex3f X2, Y2, Z2: glEnd: glPointSize 1
            Exit Sub
            End If
        End If
    If Orbit Then
        glEnable gl_Depth_Test
        If Selected Then glColor4ubv cSatelliteOrbitSel Else glColor4ubv cSatelliteOrbit
        glBegin gl_Line_Strip
        T1 = Time + T: T4 = T1: Ps = 1 * Rad
        T3 = Ps / L.M: T2 = T1 + (A180 + Ps / 2) / L.M: T1 = T1 - A180 / L.M: T5 = T1
        Do While T1 < T2
            TLE2XYZ Earth, L, T1, X2, Y2, Z2
            XYZ2LLA Earth, Y3, X3, Z3, X2, Y2, Z2
            X3 = X3 - (T1 - T4) * Earth.AngularSpeedW
            If X3 >= A180 Then X3 = X3 - A360 Else If X3 < -A180 Then X3 = X3 + A360
            X4 = X3 - X4: If (X4 > -A90 And X4 < A90) Or T1 = T5 Then glVertex3f X3, Y3, Z3 _
            Else glEnd: glBegin gl_Line_Strip
            X4 = X3
            T1 = T1 + T3
            Loop
        glEnd
        glDisable gl_Depth_Test
        End If
    XYZ2LLA Earth, Y2, X2, Z2, X1, Y1, Z1: V = H1 <= SatelliteDist
    If V Then
        XYZ2LLA Earth, Y1, X1, Z1
        glEnable gl_Depth_Test: glColor4ubv cSatelliteLine
        glBegin gl_Line_Strip: glVertex3f X2, Y2, Z2: glVertex3f X1, Y1, Z1: glEnd
        glDisable gl_Depth_Test
        End If
    glRasterPos3f X2, Y2, Z2: If IsVisible Then _
        If Not Label Then glCallList GreenDot _
        Else If (Tool And ToolInfo) = 0 Then Text3D L.Name, , , , , , GreenDot, False _
        Else If Not V Then Text3D L.Name & vbBackCrBack & _
            ToAlt(A1 - Earth.Radius, FMini), , , , , , GreenDot, False _
        Else Text3D L.Name & vbBackCrBack & ToAlt(A1 - Earth.Radius, FMini) & vbBackCrBack & _
            ToAlt(H1, FMini), , , , , , GreenDot, False
    If IsSelect Then GetSelectedValid
    End Sub

Sub Altimeter3D(Pl As Planet)
    Dim B As Long, A As Double, E As Double, F As Double, G As Double, H As Double, _
        L As Double, M As Double, U As Double ', S As String
    With Pl
    If Units Then A = .Altitude / Foot: U = Foot: G = 2 Else A = .Altitude: U = 1: G = 10
    If A < CamH / G Then A = CamH / G
    B = (Int(VBA.Log(Abs(A)) / Log10) + 1): F = 10 ^ B: B = B Mod 3
    Select Case A / F
    Case Is < 0.2: F = Int(F / 5 + 0.5): E = 0.25
    Case Is < 0.5: F = Int(F / 2 + 0.5): E = 0.2
    Case Else: F = Int(F + 0.5): E = 0.25
        End Select
    If F <= 200 Then F = 200: B = 1: E = 0.25
    L = .Altitude / .Distance: M = F / .Distance * U
    Line3D .X * L, .Y * L, .Z * L, -.X * M, -.Y * M, -.Z * M, cAltimeterLine
    'S = IIf(B <> 1 Or E = 0.2, "0", FOne)
    H = E: Do While H <= 1
        G = L - M * H: Point3D ToAlt(F * H * U, ""), .X * G, .Y * G, .Z * G, GreenDot
        H = H + E
        Loop
    End With
    End Sub

Sub Distances3D(Pl As Planet)
    Dim P As Long, B As Long, _
        A As Double, E As Double, F As Double, G As Double, H As Double, _
        L As Double, M As Double, U As Double
    glPushMatrix
    If IsTactic Then
        If Pl.Distance = 0 Then GoTo Ext Else If CamH / Pl.Distance < 0.0001 Then GoTo Ext
        XYZ2LLA Pl, Y2, X2, Z2, CamX, CamY, CamZ
        LLA2XYZP Pl, Y2, X2, Z2, X1, Y1, Z1
        PlanetGL Pl
        glTranslatef X1, Y1, Z1
        glRotatef 90 + X2 / Rad, 0, 0, 1
        glRotatef 90 - Y2 / Rad, 0, 1, 0
        glRotatef 180, 0, 1, 0
    Else
        glTranslatef CamX, CamY, CamZ: glRotatef CamW, 0, 0, 1: glRotatef CamU, 1, 0, 0
        End If
    P = 2: A = CamH * CamR / 4
    If Units Then A = A / Foot: U = Foot Else U = 1
    B = (Int(VBA.Log(Abs(A)) / Log10) + 1): F = 10 ^ B
    Select Case A / F
    Case Is < 0.2: F = Int(F / 5 + 0.5): E = 0.25
    Case Is < 0.5: F = Int(F / 2 + 0.5): E = 0.2
    Case Else: F = Int(F + 0.5): E = 0.25
        End Select
    If F <= 200 Then F = 200: E = 0.25
    Line3D , -F, , , F, , cDistancesCross, False: Line3D -F, , , F, , , cDistancesCross, False
    H = E: Do While H <= 1
        A = H * F * U
        CircleEx3D A, P, ((&H7F000000 * (1 - H / 2)) And &HFF000000) Or cDistancesLine
        'Text3D ToAlt(A, ""), A: Text3D ToAlt(A, ""), -A
        Text3D ToAlt(A, ""), , A ': Text3D ToAlt(A, ""), , -A
        H = H + E
        Loop
Ext:
    glPopMatrix
    End Sub

Sub Compass3D(Pl As Planet)
    Dim P As Long, H1 As Double
    glPushMatrix
    glTranslatef CamX, CamY, CamZ: glRotatef CamW, 0, 0, 1: glRotatef CamU, 1, 0, 0
    If ToolX And ToolCompassGrid Then
        If GridList = 0 Then
            GridList = glGenLists: If GridList Then glNewList GridList, gl_Compile
            P = 2: glEnable gl_Depth_Test
            SphereEx3D StarsDistance, P, 10, cCompassGrid
            Circles3D StarsDistance, P, , , cCompassCircle
            glDisable gl_Depth_Test
            glEndList
            End If
        glCallList GridList
        CircleText3D StarsDistance, 10, cCompassCircle
        Text3D "N", , , StarsDistance: Text3D "S", , , -StarsDistance
        End If
    If (ToolX And ToolCompass) <> 0 Then
        If CompassList = 0 Then
            CompassList = glGenLists: If CompassList Then glNewList CompassList, gl_Compile
            P = 2
            DiskEx3D , 1, P, cCompassDiskA, cCompassDiskB
            Line3D -1, , , 1, , , cCompassCross, False: Line3D , -1, , , 1, , cCompassCross, False
            Circles3D , P, , 10, cCompassCircle
            glEndList
            End If
        If IsTactic Then
            If Pl.Distance = 0 Then GoTo Ext Else If CamH / Pl.Distance < 0.0001 Then GoTo Ext
            glRotatef -CamU, 1, 0, 0: glRotatef -CamW, 0, 0, 1: glTranslatef -CamX, -CamY, -CamZ
            XYZ2LLA Pl, Y2, X2, Z2, CamX, CamY, CamZ
            LLA2XYZP Pl, Y2, X2, Z2, X1, Y1, Z1
            PlanetGL Pl
            glTranslatef X1, Y1, Z1
            glRotatef 90 + X2 / Rad, 0, 0, 1
            glRotatef 90 - Y2 / Rad, 0, 1, 0
            glRotatef 180, 0, 1, 0
            End If
        H1 = CamH * CamR / 8: glScalef H1, H1, H1
        glCallList CompassList
        CircleText3D , 45, cCompassCircle
        End If
Ext:
    glPopMatrix
    End Sub

Sub CompassFlat3D()
    Dim P As Long, H1 As Double
    glPushMatrix
    If CompassFlatList = 0 Then
        CompassFlatList = glGenLists: If CompassFlatList Then glNewList CompassFlatList, gl_Compile
        P = 2
        DiskEx3D , 1, P, cCompassDiskA, cCompassDiskB
        Line3D -1, , , 1, , , cCompassCross, False: Line3D , -1, , , 1, , cCompassCross, False
        Circles3D , P, , 10, cCompassCircle
        glEndList
        End If
    glScalef -1 / Earth.Radius, 1 / Earth.Radius, -1: glTranslatef -CamX, CamY, -CamZ
    glRotatef 90, 0, 0, 1
    H1 = CamH * CamR / 8: glScalef H1, H1, H1
    glCallList CompassFlatList
    CircleText3D , 45, cCompassCircle
    glPopMatrix
    End Sub

Sub MagneticVector3D()
    Dim Lat As Double, Lon As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        D1 As WMMData
    glPushMatrix
    'glTranslatef Earth.X, Earth.Y, Earth.Z
    glRotatef Earth.AngleU / Rad, 1, 0, 0 ': glRotatef Earth.AngleV / Rad, 0, 1, 0
    glRotatef (Earth.AngleW - Fix(Earth.AngleW / A360) * A360) / Rad, 0, 0, 1
    'glScalef 1, 1, Earth.E21
    XYZ2LLA Earth, Lat, Lon
    H1 = GravityScale * ForceScale / 2000
    D1.Alt = (Earth.Distance - Earth.Radius) / 1000
    D1.Year = (Time + T) / YearDays / Day + 2000
    D1.Lat = Lat / Rad: D1.Lon = Lon / Rad: WMMGetElements D1
    X1 = -D1.Y * H1: Y1 = -D1.Z * H1: Z1 = D1.X * H1
    glRotatef D1.Lon, 0, 0, 1: glRotatef D1.Lat, 1, 0, 0
    'glTranslatef 0, Earth.Distance, 0
    glBegin gl_Lines
    glColor4ubv cMagneticCompassN: glVertex3f X1, Y1, Z1
    glColor4ubv cMagneticCompassS: glVertex3f -X1, -Y1, -Z1
    glEnd
    glPopMatrix
    End Sub

Sub Point3D(Optional Txt As String, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal L As Long, Optional ByVal DrawAlt As Boolean)
    Dim A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double
    glRasterPos3f X, Y, Z: If Not IsVisible Then Exit Sub
    If DrawAlt And (Tool And ToolVectors) <> 0 And (ToolX And ToolAltitude) <> 0 Then
        X1 = X - Earth.X: Y1 = Y - Earth.Y: Z1 = Z - Earth.Z
        H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
        If H1 < Earth.Diameter Then
            If (Tool And ToolHidden) = 0 Then glEnable gl_Depth_Test
            If TerrainMode = 0 Then A1 = H1 - Earth.Radius _
            Else Y2 = Y1 * Earth.CosA + Z1 * Earth.SinA: Z2 = Z1 * Earth.CosA - Y1 * Earth.SinA: _
                XYZ2LLA Earth, , , A1, X1, Y2, Z2, False
            H1 = -A1 / H1: Line3D X, Y, Z, X1 * H1, Y1 * H1, Z1 * H1, cAltitudeProj
            If (Tool And ToolHidden) = 0 Then glDisable gl_Depth_Test
            End If
        X1 = X - Moon.X: Y1 = Y - Moon.Y: Z1 = Z - Moon.Z
        H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
        If H1 < Moon.Diameter Then
            If (Tool And ToolHidden) = 0 Then glEnable gl_Depth_Test
            If TerrainMode = 0 Then A1 = H1 - Moon.Radius _
            Else X2 = X1 * Moon.CosA + Z1 * Moon.SinA: Z2 = Z1 * Moon.CosA - X1 * Moon.SinA: _
                XYZ2LLA Moon, , , A1, X2, Y1, Z2, False
            H1 = -A1 / H1: Line3D X, Y, Z, X1 * H1, Y1 * H1, Z1 * H1, cAltitudeProj
            If (Tool And ToolHidden) = 0 Then glDisable gl_Depth_Test
            End If
'        If GravityMode >= 4 Then
'            X1 = X - Mars.X: Y1 = Y - Mars.Y: Z1 = Z - Mars.Z
'            H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
'            If H1 < Mars.Diameter Then
'                If TerrainMode = 0 Then A1 = H1 - Mars.Radius _
'                Else X2 = X1 * Mars.CosA + Z1 * Mars.SinA: Z2 = Z1 * Mars.CosA - X1 * Mars.SinA: _
'                    XYZ2LLA Mars, , , A1, X2, Y1, Z2, False
'                H1 = -A1 / H1: Line3D X, Y, Z, X1 * H1, Y1 * H1, Z1 * H1, cAltitudeProj
'                End If
'            End If
        End If
    If L = 0 Then L = GreenDot
    Text3D Txt, , , , , , L, False
    End Sub

Sub PosGL( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional U As Double, Optional V As Double, Optional W As Double, _
        Optional R As Double = 1)
    glTranslatef X, Y, Z: glRotatef U / Rad, 1, 0, 0: glRotatef V / Rad, 0, 1, 0
    glRotatef (W - Fix(W / A360) * A360) / Rad, 0, 0, 1
    If R <> 1 Then glScalef R, R, R
    End Sub

Sub PlanetGL(Pl As Planet)
    glTranslatef Pl.X, Pl.Y, Pl.Z
    glRotatef Pl.AngleU / Rad, 1, 0, 0: glRotatef Pl.AngleV / Rad, 0, 1, 0
    glRotatef (Pl.AngleW - Fix(Pl.AngleW / A360) * A360) / Rad, 0, 0, 1
    End Sub

Sub PlanetGLEx(Pl As Planet, _
        Optional ByVal Move As Boolean, Optional ByVal Reverse As Boolean = True)
    If Reverse Then
        glRotatef -(Pl.AngleW - Fix(Pl.AngleW / A360) * A360) / Rad, 0, 0, 1
        glRotatef -Pl.AngleU / Rad, 1, 0, 0: glRotatef -Pl.AngleV / Rad, 0, 1, 0
        If Move Then glTranslatef -Pl.X, -Pl.Y, -Pl.Z
    Else
        If Move Then glTranslatef Pl.X, Pl.Y, Pl.Z
        glRotatef Pl.AngleU / Rad, 1, 0, 0: glRotatef Pl.AngleV / Rad, 0, 1, 0
        glRotatef (Pl.AngleW - Fix(Pl.AngleW / A360) * A360) / Rad, 0, 0, 1
        End If
    End Sub

Sub PlanetGLID(Optional P As TargetType, _
        Optional ByVal Move As Boolean, Optional ByVal Reverse As Boolean = True)
    Select Case P
    Case ToEarth, ToAir: PlanetGLEx Earth, Move, Reverse
    Case ToMoon:    PlanetGLEx Moon, Move, Reverse
    Case ToMercury: PlanetGLEx Mercury, Move, Reverse
    Case ToVenus:   PlanetGLEx Venus, Move, Reverse
    Case ToMars:    PlanetGLEx Mars, Move, Reverse
    Case ToJupiter: PlanetGLEx Jupiter, Move, Reverse
    Case ToSaturn:  PlanetGLEx Saturn, Move, Reverse
    Case ToUranus:  PlanetGLEx Uranus, Move, Reverse
    Case ToNeptune: PlanetGLEx Neptune, Move, Reverse
    Case ToPluto:   PlanetGLEx Pluto, Move, Reverse
    Case Else:      PlanetGLEx Sun, Move, Reverse
        End Select
    End Sub

Sub XYZ2LLAID(Optional P As TargetType, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional ByVal X As Double, Optional ByVal Y As Double, Optional ByVal Z As Double, _
        Optional ByVal Relative As Boolean = True)
    Select Case P
    Case ToEarth, ToAir: XYZ2LLA Earth, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToMoon:    XYZ2LLA Moon, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToMercury: XYZ2LLA Mercury, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToVenus:   XYZ2LLA Venus, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToMars:    XYZ2LLA Mars, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToJupiter: XYZ2LLA Jupiter, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToSaturn:  XYZ2LLA Saturn, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToUranus:  XYZ2LLA Uranus, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToNeptune: XYZ2LLA Neptune, Lat, Lon, Alt, X, Y, Z, Relative
    Case ToPluto:   XYZ2LLA Pluto, Lat, Lon, Alt, X, Y, Z, Relative
    Case Else:      XYZ2LLA Sun, Lat, Lon, Alt, X, Y, Z, Relative
        End Select
    End Sub

Sub LLA2XYZID(Optional P As TargetType, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, Optional H As Double, _
        Optional ByVal Update As Boolean, Optional ByVal Relative As Boolean, _
        Optional ByVal Move As Boolean)
    Select Case P
    Case ToEarth, ToAir: LLA2XYZ Earth, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToMoon:    LLA2XYZ Moon, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToMercury: LLA2XYZ Mercury, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToVenus:   LLA2XYZ Venus, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToMars:    LLA2XYZ Mars, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToJupiter: LLA2XYZ Jupiter, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToSaturn:  LLA2XYZ Saturn, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToUranus:  LLA2XYZ Uranus, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToNeptune: LLA2XYZ Neptune, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case ToPluto:   LLA2XYZ Pluto, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
    Case Else:      LLA2XYZ Sun, Lat, Lon, Alt, X, Y, Z, H, Update, Relative, Move
        End Select
    End Sub

Sub VertexLLA(Pl As Planet, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim H As Double, SLat As Double
    If TerrainMode = 0 Then
        'Spherical model
        H = Pl.Radius + Alt: Z = H * Sin(Lat): H = H * Cos(Lat)
    Else
        'Ellipsoidal model
        SLat = Sin(Lat): H = Pl.Radius / Sqr(1 - Pl.E2 * SLat * SLat)
        Z = (H * (1 - Pl.E2) + Alt) * SLat
        H = (H + Alt) * Cos(Lat)
        End If
    X = -H * Sin(Lon): Y = H * Cos(Lon)
    glVertex3f X, Y, Z
    End Sub

Sub VertexLLAEx(Pl As Planet, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim H As Double
    H = Pl.Radius + Alt: Z = H * Sin(Lat): H = H * Cos(Lat)
    X = -H * Sin(Lon): Y = H * Cos(Lon)
    glVertex3f X, Y, Z
    End Sub

Sub RasterLLA(Pl As Planet, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim H As Double, SLat As Double
    If TerrainMode = 0 Then
        'Spherical model
        H = Pl.Radius + Alt: Z = H * Sin(Lat): H = H * Cos(Lat)
    Else
        'Ellipsoidal model
        SLat = Sin(Lat): H = Pl.Radius / Sqr(1 - Pl.E2 * SLat * SLat)
        Z = (H * (1 - Pl.E2) + Alt) * SLat
        H = (H + Alt) * Cos(Lat)
        End If
    X = -H * Sin(Lon): Y = H * Cos(Lon)
    glRasterPos3f X, Y, Z
    End Sub

Sub PathAirFlat3D()
    Dim C As Long
    If Tool And ToolStations Then
        glBegin gl_Line_Strip: For C = 0 To PathP - 1
            If Path(C).N Then glColor4ubv cPathStationA Else glColor4ubv cPathStationU
            VertexAirFlat Path(C), C: Next
    Else
        glColor4ubv cPathFlatB
        glBegin gl_Line_Strip: For C = 0 To PathP - 1
            VertexAirFlat Path(C), C: Next
        End If
    VertexAirFlat Path(0): glEnd
    glColor4ubv cPathFlatA
    glBegin gl_Line_Strip: VertexAirFlat Path(0)
    For C = PathP To PathN - 1: VertexAirFlat Path(C), C: Next: glEnd
    End Sub

Sub PathAir3D()
    Dim B As Long, C As Long
    For B = 0 To PathsN - 1: With Paths(B): .PointTime = -1
        If B <> PathsP Or IsPathModified = 0 Then
            glColor4ubv cPathA: glBegin gl_Line_Strip
            For C = 0 To .PathN - 1: VertexAir .Path(C)
                If .PointTime < 0 Then If .Path(C).T >= T Then .PointTime = C
                Next: glEnd
            If .PointTime < 0 Then .PointTime = .PathN - 1
            End If
        End With: Next
    If Tool And ToolStations Then
        glBegin gl_Line_Strip: For C = 0 To PathP - 1
        If Path(C).N Then glColor4ubv cPathStationA Else glColor4ubv cPathStationU
        VertexAir Path(C): Next
    Else
        glColor4ubv cPathB: glBegin gl_Line_Strip
        For C = 0 To PathP - 1: VertexAir Path(C): Next
        End If
    glVertex3f 0, 0, 0: glEnd: glColor4ubv cPathA: glBegin gl_Line_Strip: glVertex3f 0, 0, 0
    For C = PathP To PathN - 1: VertexAir Path(C): Next
    glEnd
    End Sub

Sub PathEarth3D()
    Dim B As Long, C As Long
    For B = 0 To PathsN - 1: With Paths(B): .PointTime = -1
        If B <> PathsP Or IsPathModified = 0 Then
            glColor4ubv cPathA: glBegin gl_Line_Strip
            For C = 0 To .PathN - 1: VertexEarth .Path(C)
                If .PointTime < 0 Then If .Path(C).T >= T Then .PointTime = C
                Next: glEnd
            If .PointTime < 0 Then .PointTime = .PathN - 1
            End If
        End With: Next
    If Tool And ToolStations Then
        glBegin gl_Line_Strip: For C = 0 To PathP - 1
        If Path(C).N Then glColor4ubv cPathStationA Else glColor4ubv cPathStationU
        VertexEarth Path(C): Next
    Else
        glColor4ubv cPathB: glBegin gl_Line_Strip
        For C = 0 To PathP - 1: VertexEarth Path(C): Next
        End If
    glVertex3f 0, 0, 0: glEnd: glColor4ubv cPathA: glBegin gl_Line_Strip: glVertex3f 0, 0, 0
    For C = PathP To PathN - 1: VertexEarth Path(C): Next
    glEnd
    End Sub

Sub PathSun3D()
    Dim B As Long, C As Long
    For B = 0 To PathsN - 1: With Paths(B): .PointTime = -1
        If B <> PathsP Or IsPathModified = 0 Then
            glColor4ubv cPathA: glBegin gl_Line_Strip
            For C = 0 To .PathN - 1: VertexSun .Path(C)
                If .PointTime < 0 Then If .Path(C).T >= T Then .PointTime = C
                Next: glEnd
            If .PointTime < 0 Then .PointTime = .PathN - 1
            End If
        End With: Next
    If Tool And ToolStations Then
        glBegin gl_Line_Strip: For C = 0 To PathP - 1
        If Path(C).N Then glColor4ubv cPathStationA Else glColor4ubv cPathStationU
        VertexSun Path(C): Next
    Else
        glColor4ubv cPathB: glBegin gl_Line_Strip
        For C = 0 To PathP - 1: VertexSun Path(C): Next
        End If
    glVertex3f 0, 0, 0: glEnd: glColor4ubv cPathA: glBegin gl_Line_Strip: glVertex3f 0, 0, 0
    For C = PathP To PathN - 1: VertexSun Path(C): Next
    glEnd
    End Sub

Sub PathTarget3D(Pl As Planet)
    Dim B As Long, C As Long, D As Long
    For B = 0 To PathsN - 1: With Paths(B): .PointTime = -1
        If B <> PathsP Or IsPathModified = 0 Then
            D = ToNone: glColor4ubv cPathA
            For C = 0 To .PathN - 1: VertexTarget Pl, .Path(C), D
                If .PointTime < 0 Then If .Path(C).T >= T Then .PointTime = C
                Next: glEnd
            If .PointTime < 0 Then .PointTime = .PathN - 1
            End If
        End With: Next
    D = ToNone
    If Tool And ToolStations Then
        glBegin gl_Line_Strip: For C = 0 To PathP - 1
        If Path(C).N Then glColor4ubv cPathStationA Else glColor4ubv cPathStationU
        VertexTarget Pl, Path(C), D: Next
    Else
        glColor4ubv cPathB: For C = 0 To PathP - 1: VertexTarget Pl, Path(C), D: Next
        End If
    If D = Pl.ID Then glVertex3f 0, 0, 0: glEnd
    If PathP < PathN Then D = Path(PathP).P Else Exit Sub
    glColor4ubv cPathA
    If D = Pl.ID Then glBegin gl_Line_Strip: glVertex3f 0, 0, 0
    For C = PathP To PathN - 1: VertexTarget Pl, Path(C), D: Next
    If D = Pl.ID Then glEnd
    End Sub

Sub VertexEarth(P As PathPoint)
    glVertex3f Earth.X - P.Earth.X, Earth.Y - P.Earth.Y, Earth.Z - P.Earth.Z
    End Sub

Sub VertexSun(P As PathPoint)
    glVertex3f Sun.X - P.Sun.X, Sun.Y - P.Sun.Y, Sun.Z - P.Sun.Z
    End Sub

Sub VertexTarget(Pl As Planet, P As PathPoint, Last As Long)
    If P.P = Pl.ID Then
        If Last <> Pl.ID Then glBegin gl_Line_Strip
        glVertex3f Pl.X - P.Target.X, Pl.Y - P.Target.Y, Pl.Z - P.Target.Z
    Else
        If Last = Pl.ID Then glEnd
        End If
    Last = P.P
    End Sub

Sub VertexAir(P As PathPoint)
    Dim X1 As Double, Y1 As Double, Z1 As Double, _
        SinW As Double, CosW As Double
    'Rotate -Earth.AngleU, Y, Z: Rotate Earth.AngleW - .Earth.AngleW, X, Y: Rotate Earth.AngleU, Y, Z
    X1 = Earth.AngleW - P.AngleW: SinW = Sin(X1): CosW = Cos(X1)
    Y1 = -P.Earth.Y * Earth.CosA - P.Earth.Z * Earth.SinA: Z1 = P.Earth.Y * Earth.SinA - P.Earth.Z * Earth.CosA
    X1 = -P.Earth.X * CosW - Y1 * SinW: Y1 = -P.Earth.X * SinW + Y1 * CosW
    glVertex3f X1 + Earth.X, Y1 * Earth.CosA - Z1 * Earth.SinA + Earth.Y, Y1 * Earth.SinA + Z1 * Earth.CosA + Earth.Z
    End Sub

Sub PointEarthEx(Name As String, P1 As PathPoint, P2 As PathPoint, _
        Optional R1 As Double, Optional R2 As Double)
    If T <= P1.T Then R1 = 1: R2 = 0 Else If T >= P2.T Then R1 = 0: R2 = 1 _
    Else R2 = (T - P1.T) / (P2.T - P1.T): R1 = 1 - R2
    Point3D Name, _
        Earth.X - P1.Earth.X * R1 - P2.Earth.X * R2, _
        Earth.Y - P1.Earth.Y * R1 - P2.Earth.Y * R2, _
        Earth.Z - P1.Earth.Z * R1 - P2.Earth.Z * R2, _
        WhiteDot, True
    End Sub

Sub PointSunEx(Name As String, P1 As PathPoint, P2 As PathPoint, _
        Optional R1 As Double, Optional R2 As Double)
    If T <= P1.T Then R1 = 1: R2 = 0 Else If T >= P2.T Then R1 = 0: R2 = 1 _
    Else R2 = (T - P1.T) / (P2.T - P1.T): R1 = 1 - R2
    Point3D Name, _
        Sun.X - P1.Sun.X * R1 - P2.Sun.X * R2, _
        Sun.Y - P1.Sun.Y * R1 - P2.Sun.Y * R2, _
        Sun.Z - P1.Sun.Z * R1 - P2.Sun.Z * R2, _
        WhiteDot, True
    End Sub

Sub PointTargetEx(Pl As Planet, Name As String, P1 As PathPoint, P2 As PathPoint, _
        Optional R1 As Double, Optional R2 As Double)
    If P1.P <> Pl.ID Or P2.P <> Pl.ID Then Exit Sub
    If T <= P1.T Then R1 = 1: R2 = 0 Else If T >= P2.T Then R1 = 0: R2 = 1 _
    Else R2 = (T - P1.T) / (P2.T - P1.T): R1 = 1 - R2
    Point3D Name, _
        Pl.X - P1.Target.X * R1 - P2.Target.X * R2, _
        Pl.Y - P1.Target.Y * R1 - P2.Target.Y * R2, _
        Pl.Z - P1.Target.Z * R1 - P2.Target.Z * R2, _
        WhiteDot, True
    End Sub

Sub PointAirEx(Name As String, P1 As PathPoint, P2 As PathPoint)
    Dim X As Double, Y As Double, Z As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        R1 As Double, R2 As Double, _
        SinW As Double, CosW As Double
    'Rotate -Earth.AngleU, Y, Z: Rotate Earth.AngleW - .Earth.AngleW, X, Y: Rotate Earth.AngleU, Y, Z
    X = Earth.AngleW - P1.AngleW: SinW = Sin(X): CosW = Cos(X)
    Y = -P1.Earth.Y * Earth.CosA - P1.Earth.Z * Earth.SinA: Z = P1.Earth.Y * Earth.SinA - P1.Earth.Z * Earth.CosA
    X = -P1.Earth.X * CosW - Y * SinW: Y = -P1.Earth.X * SinW + Y * CosW
    X1 = X + Earth.X: Y1 = Y * Earth.CosA - Z * Earth.SinA + Earth.Y: Z1 = Y * Earth.SinA + Z * Earth.CosA + Earth.Z
    
    X = Earth.AngleW - P2.AngleW: SinW = Sin(X): CosW = Cos(X)
    Y = -P2.Earth.Y * Earth.CosA - P2.Earth.Z * Earth.SinA: Z = P2.Earth.Y * Earth.SinA - P2.Earth.Z * Earth.CosA
    X = -P2.Earth.X * CosW - Y * SinW: Y = -P2.Earth.X * SinW + Y * CosW
    X2 = X + Earth.X: Y2 = Y * Earth.CosA - Z * Earth.SinA + Earth.Y: Z2 = Y * Earth.SinA + Z * Earth.CosA + Earth.Z
    
    If T <= P1.T Then R1 = 1: R2 = 0 Else If T >= P2.T Then R1 = 0: R2 = 1 _
    Else R2 = (T - P1.T) / (P2.T - P1.T): R1 = 1 - R2
    Point3D Name, X1 * R1 + X2 * R2, Y1 * R1 + Y2 * R2, Z1 * R1 + Z2 * R2, WhiteDot, True
    End Sub

Sub VertexAirFlat(P As PathPoint, Optional ByVal N As Long = -1)
    Dim Lat As Double, Lon As Double, Alt As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        SinW As Double, CosW As Double
    Static Lon2 As Double
    If N >= 0 Then
        'Rotate -Earth.AngleU, Y, Z: Rotate Earth.AngleW - .Earth.AngleW, X, Y: Rotate Earth.AngleU, Y, Z
        X1 = Earth.AngleW - P.AngleW: SinW = Sin(X1): CosW = Cos(X1)
        Y1 = -P.Earth.Y * Earth.CosA - P.Earth.Z * Earth.SinA
        Z1 = P.Earth.Y * Earth.SinA - P.Earth.Z * Earth.CosA
        X1 = -P.Earth.X * CosW - Y1 * SinW: Y1 = -P.Earth.X * SinW + Y1 * CosW
        XYZ2LLA Earth, Lat, Lon, Alt, X1 + Earth.X, _
            Y1 * Earth.CosA - Z1 * Earth.SinA + Earth.Y, Y1 * Earth.SinA + Z1 * Earth.CosA + Earth.Z
    Else
        XYZ2LLA Earth, Lat, Lon, Alt
        End If
    If N Then X1 = Lon - Lon2: If X1 > A90 Or X1 < -A90 Then glEnd: glBegin gl_Line_Strip
    glVertex3f Lon, Lat, Alt
    Lon2 = Lon
    End Sub

Sub Pos2LLA(P As PathPoint, _
        Optional Lat As Double, Optional Lon As Double, Optional Alt As Double, _
        Optional ByVal GetShip As Boolean)
    Dim SinW As Double, CosW As Double
    If GetShip Then
        XYZ2LLA Earth, Lat, Lon, Alt
    Else
        'Rotate -Earth.AngleU, Y, Z: Rotate Earth.AngleW - .Earth.AngleW, X, Y: Rotate Earth.AngleU, Y, Z
        X1 = Earth.AngleW - P.AngleW: SinW = Sin(X1): CosW = Cos(X1)
        Y1 = -P.Earth.Y * Earth.CosA - P.Earth.Z * Earth.SinA
        Z1 = P.Earth.Y * Earth.SinA - P.Earth.Z * Earth.CosA
        X1 = -P.Earth.X * CosW - Y1 * SinW: Y1 = -P.Earth.X * SinW + Y1 * CosW
        XYZ2LLA Earth, Lat, Lon, Alt, X1 + Earth.X, _
            Y1 * Earth.CosA - Z1 * Earth.SinA + Earth.Y, Y1 * Earth.SinA + Z1 * Earth.CosA + Earth.Z
        End If
    End Sub

Sub PointsEarth()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointEarth .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointEarth Snap(C): Next
    End Sub

Sub PointsSun()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointSun .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointSun Snap(C): Next
    End Sub

Sub PointsMoon()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointMoon .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointMoon Snap(C): Next
    End Sub

Sub PointsMercury()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointMercury .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointMercury Snap(C): Next
    End Sub

Sub PointsVenus()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointVenus .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointVenus Snap(C): Next
    End Sub

Sub PointsMars()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointMars .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointMars Snap(C): Next
    End Sub

Sub PointsJupiter()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointJupiter .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointJupiter Snap(C): Next
    End Sub

Sub PointsSaturn()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointSaturn .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointSaturn Snap(C): Next
    End Sub

Sub PointsUranus()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointUranus .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointUranus Snap(C): Next
    End Sub

Sub PointsNeptune()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointNeptune .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointNeptune Snap(C): Next
    End Sub

Sub PointsPluto()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointPluto .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointPluto Snap(C): Next
    End Sub

Sub PointsAir()
    Dim B As Long, C As Long
    If Tool And ToolPathAll Then
        For B = 0 To PathsN - 1: With Paths(B)
            If B <> PathsP Or IsPathModified = 0 Then For C = 0 To .SnapN - 1: PointAir .Snap(C): Next
            End With: Next
        End If
    For C = 0 To SnapN - 1: PointAir Snap(C): Next
    End Sub

Sub PointEarth(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Earth.X - P.Earth.X, P.Ship.Y + Earth.Y - P.Earth.Y, P.Ship.Z + Earth.Z - P.Earth.Z
    End Sub

Sub PointSun(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Sun.X - P.Sun.X, P.Ship.Y + Sun.Y - P.Sun.Y, P.Ship.Z + Sun.Z - P.Sun.Z
    End Sub

Sub PointMoon(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Moon.X - P.Moon.X, P.Ship.Y + Moon.Y - P.Moon.Y, P.Ship.Z + Moon.Z - P.Moon.Z
    End Sub

Sub PointMercury(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Mercury.X - P.Mercury.X, P.Ship.Y + Mercury.Y - P.Mercury.Y, P.Ship.Z + Mercury.Z - P.Mercury.Z
    End Sub

Sub PointVenus(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Venus.X - P.Venus.X, P.Ship.Y + Venus.Y - P.Venus.Y, P.Ship.Z + Venus.Z - P.Venus.Z
    End Sub

Sub PointMars(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Mars.X - P.Mars.X, P.Ship.Y + Mars.Y - P.Mars.Y, P.Ship.Z + Mars.Z - P.Mars.Z
    End Sub

Sub PointJupiter(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Jupiter.X - P.Jupiter.X, P.Ship.Y + Jupiter.Y - P.Jupiter.Y, P.Ship.Z + Jupiter.Z - P.Jupiter.Z
    End Sub

Sub PointSaturn(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Saturn.X - P.Saturn.X, P.Ship.Y + Saturn.Y - P.Saturn.Y, P.Ship.Z + Saturn.Z - P.Saturn.Z
    End Sub

Sub PointUranus(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Uranus.X - P.Uranus.X, P.Ship.Y + Uranus.Y - P.Uranus.Y, P.Ship.Z + Uranus.Z - P.Uranus.Z
    End Sub

Sub PointNeptune(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Neptune.X - P.Neptune.X, P.Ship.Y + Neptune.Y - P.Neptune.Y, P.Ship.Z + Neptune.Z - P.Neptune.Z
    End Sub

Sub PointPluto(P As Snap)
    PointSnap P.StatusText, P.T, P.Ship.X + Pluto.X - P.Pluto.X, P.Ship.Y + Pluto.Y - P.Pluto.Y, P.Ship.Z + Pluto.Z - P.Pluto.Z
    End Sub

Sub PointAir(P As Snap)
    Dim X1 As Double, Y1 As Double, Z1 As Double
    X1 = P.Ship.X - P.Earth.X: Y1 = P.Ship.Y - P.Earth.Y: Z1 = P.Ship.Z - P.Earth.Z
    Rotate -Earth.AngleU, Y1, Z1: Rotate Earth.AngleW - P.Earth.AngleW, X1, Y1: Rotate Earth.AngleU, Y1, Z1
    PointSnap P.StatusText, P.T, X1 + Earth.X, Y1 + Earth.Y, Z1 + Earth.Z
    End Sub

Sub PointSnap(StatusText As String, T As Double, X1 As Double, Y1 As Double, Z1 As Double)
    If StatusText <> "" Then
        Point3D StatusText, X1, Y1, Z1, GreenDot, True
    ElseIf (Tool And (ToolTime Or ToolPath)) = (ToolTime Or ToolPath) Then
        Point3D ToDh(T), X1, Y1, Z1, SmallGreenDot, True
        End If
    End Sub

Sub PointAirFlat(P As Snap)
    Dim X1 As Double, Y1 As Double, Z1 As Double
    X1 = P.Ship.X - P.Earth.X: Y1 = P.Ship.Y - P.Earth.Y: Z1 = P.Ship.Z - P.Earth.Z
    Rotate -Earth.AngleU, Y1, Z1: Rotate Earth.AngleW - P.Earth.AngleW, X1, Y1: Rotate Earth.AngleU, Y1, Z1
    X1 = X1 + Earth.X: Y1 = Y1 + Earth.Y: Z1 = Z1 + Earth.Z
    If P.StatusText <> "" Then
        If Tool And ToolInfo Then PointFlat3D P.StatusText, X1, Y1, Z1, GreenDot _
        Else PointFlat3D , X1, Y1, Z1, GreenDot
    ElseIf (Tool And (ToolTime Or ToolPath)) = (ToolTime Or ToolPath) Then
        If Tool And ToolInfo Then PointFlat3D ToDh(P.T), X1, Y1, Z1, SmallGreenDot _
        Else PointFlat3D , X1, Y1, Z1, SmallGreenDot
        End If
    End Sub

Sub Sphere3D( _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional R As Double = 1, Optional ByVal C As Long = -1)
    Dim X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Long, Y2 As Long, N As Double, P As Long
    If SphereList = 0 Then
        P = 5: SphereList = glGenLists
        If SphereList Then glNewList SphereList, gl_Compile Else Exit Sub
        For Y2 = -90 To 90 - P Step P
            glBegin gl_Quad_Strip
            For X2 = -180 To 180 Step P
                N = Cos(Y2 * Rad): Z1 = Sin(Y2 * Rad)
                X1 = N * Sin(X2 * Rad): Y1 = N * Cos(X2 * Rad)
                'glNormal3f X1, Y1, Z1
                'glTexCoord2f (X2 + 180) / 360, (Y2 + 90) / 180
                glVertex3f X1, Y1, Z1
                N = Cos((Y2 + P) * Rad): Z1 = Sin((Y2 + P) * Rad)
                X1 = N * Sin(X2 * Rad): Y1 = N * Cos(X2 * Rad)
                'glNormal3f X1, Y1, Z1
                'glTexCoord2f (X2 + 180) / 360, (Y2 + P + 90) / 180
                glVertex3f X1, Y1, Z1
                Next
            glEnd
            Next
        glEndList
        End If
    glColor4ubv C
    glPushMatrix
    glTranslatef X, Y, Z: glScalef R, R, R
    glCallList SphereList
    glPopMatrix
    End Sub

Sub GridFlat3D(Pl As Planet)
    Dim X2 As Long, Y2 As Long, P As Long, S As Long
    If Pl.GridFlat = 0 Then
        S = 1: P = Grid: If P < 1 Then P = 1 Else If P > 90 Then P = 90
        Pl.GridFlat = glGenLists: glNewList Pl.GridFlat, gl_Compile
        For Y2 = -90 To 90 Step P
            If P >= 10 Then glColor4ubv cPlanetGrid Else If Y2 = 0 Then glColor4ubv cPlanetGridE _
            Else If Y2 / 10 = Int(Y2 / 10) Then glColor4ubv cPlanetGridD Else glColor4ubv cPlanetGrid
            glBegin gl_Line_Strip
            For X2 = -180 To 180 Step S
                glVertex3f -X2 * Rad, Y2 * Rad, 0
                Next
            glEnd
            Next
        For X2 = -180 To 180 Step P
            If P >= 10 Then glColor4ubv cPlanetGrid Else If X2 = 0 Then glColor4ubv cPlanetGridG _
            Else If X2 / 10 = Int(X2 / 10) Then glColor4ubv cPlanetGridD Else glColor4ubv cPlanetGrid
            glBegin gl_Line_Strip
            For Y2 = -90 To 90 Step S
                glVertex3f -X2 * Rad, Y2 * Rad, 0
                Next
            glEnd
            Next
        glEndList
        End If
    glCallList Pl.GridFlat
    End Sub

Sub Grid3D(Pl As Planet)
    Dim X2 As Long, Y2 As Long, N As Double, P As Long, S As Long, _
        X1 As Double, Y1 As Double, Z1 As Double
    If CamH > Pl.Radius * hPlanetsGrid Then Exit Sub
    If Pl.Grid = 0 Then
        S = 1: P = Grid: If P < 1 Then P = 1 Else If P > 90 Then P = 90
        Pl.Grid = glGenLists: glNewList Pl.Grid, gl_Compile
        For Y2 = -90 + P To 90 - P Step P
            If P >= 10 Then glColor4ubv cPlanetGrid Else If Y2 = 0 Then glColor4ubv cPlanetGridE _
            Else If Y2 / 10 = Int(Y2 / 10) Then glColor4ubv cPlanetGridD Else glColor4ubv cPlanetGrid
            glBegin gl_Line_Strip
            For X2 = 0 To 360 Step S
                LL2XYZ Y2 * Rad, X2 * Rad, Pl.Radius2 / Pl.Radius, X1, Y1, Z1
                glVertex3f X1 * Pl.Radius, Y1 * Pl.Radius, Z1 * Pl.Radius
                Next
            glEnd
            Next
        For X2 = 0 To 360 - P Step P
            If P >= 10 Then glColor4ubv cPlanetGrid Else If X2 = 0 Then glColor4ubv cPlanetGridG _
            Else If X2 / 10 = Int(X2 / 10) Then glColor4ubv cPlanetGridD Else glColor4ubv cPlanetGrid
            glBegin gl_Line_Strip
            For Y2 = -90 To 90 Step S
                LL2XYZ Y2 * Rad, X2 * Rad, Pl.Radius2 / Pl.Radius, X1, Y1, Z1
                glVertex3f X1 * Pl.Radius, Y1 * Pl.Radius, Z1 * Pl.Radius
                Next
            glEnd
            Next
        glEndList
        End If
    glPushMatrix
    PlanetGL Pl: N = (Pl.Radius + RangeScale / 50) / Pl.Radius
    If Tool And ToolTerrain Then If N < 1.0001 Then N = 1.0001
    glScalef N, N, N
    glCallList Pl.Grid
    glPopMatrix
    End Sub

Sub MinorPlanet3D(Pl As MinorPlanet, S As Asteroid, Optional ByVal C As Long = -1)
    Dim X1 As Double, Y1 As Double, Z1 As Double, T1 As Double, _
        X2 As Long, Y2 As Long, N As Double, P As Long
    If Not S.IsModel Then Exit Sub
    If CamH > S.R * 1000 Then Exit Sub
    If Pl.List = 0 Then
        P = 5: Pl.List = glGenLists
        If Pl.List Then glNewList Pl.List, gl_Compile Else Exit Sub
        For Y2 = -90 To 90 - P Step P
            glBegin gl_Quad_Strip
            For X2 = -180 To 180 Step P
                N = Cos(Y2 * Rad): Z1 = Sin(Y2 * Rad)
                X1 = N * Sin(X2 * Rad): Y1 = N * Cos(X2 * Rad)
                glNormal3f X1, Y1, Z1
                glTexCoord2f (X2 + 180) / 360, (Y2 + 90) / 180
                glVertex3f X1 * S.R, Y1 * S.R, Z1 * S.R
                N = Cos((Y2 + P) * Rad): Z1 = Sin((Y2 + P) * Rad)
                X1 = N * Sin(X2 * Rad): Y1 = N * Cos(X2 * Rad)
                glNormal3f X1, Y1, Z1
                glTexCoord2f (X2 + 180) / 360, (Y2 + P + 90) / 180
                glVertex3f X1 * S.R, Y1 * S.R, Z1 * S.R
                Next
            glEnd
            Next
        glEndList
        End If
    glDisable gl_Blend
    glEnable gl_Lighting
    If (Tool And ToolTextures) <> 0 And Pl.Tex <> 0 Then _
        glColor4ubv cWhite: glEnable gl_Texture_2d: glBindTexture gl_Texture_2d, Pl.Tex _
    Else glColor4ubv C
    glPushMatrix
    T1 = Time + T: Asteroid2XYZ S, T1, X1, Y1, Z1: glTranslatef X1, Y1, Z1
    T1 = T1 * S.M: glRotatef (T1 - Fix(T1 / A360) * A360) / Rad, 0, 0, 1
    glCallList Pl.List
    glPopMatrix
    If Tool And ToolTextures Then glDisable gl_Texture_2d
    glDisable gl_Lighting
    glEnable gl_Blend
    End Sub

Sub PlanetFlat3D(Pl As Planet, Optional ByVal P As Long = 90, Optional ByVal C As Long = -1)
    Dim T As Boolean, _
        X1 As Long, Y1 As Long, X2 As Long, Y2 As Long, _
        X3 As Long, Y3 As Long, W2 As Long, H2 As Long
    With Pl.Texture
    If .Flat = 0 Then
        .Flat = glGenLists: glNewList .Flat, gl_Compile
        T = .W > 0 And .H > 0: If T Then glColor4ubv cWhite _
            Else glBindTexture gl_Texture_2d, 0: glColor4ubv C
        W2 = 360 \ (.W - Not T): H2 = 180 \ (.H - Not T)
        For Y2 = 0 To .H + T: For X2 = 0 To .W + T
            If T Then glBindTexture gl_Texture_2d, .List(X2, Y2)
            Y3 = 0
            For Y1 = -90 + Y2 * H2 To -90 + Y2 * H2 + H2 - P Step P
                glBegin gl_Quad_Strip
                X3 = 0
                For X1 = -180 + X2 * W2 To -180 + X2 * W2 + W2 Step P
                    glNormal3f 0, 0, 1: If T Then glTexCoord2f X3 / W2, Y3 / H2
                    glVertex3f -X1 * Rad, Y1 * Rad, 0
                    glNormal3f 0, 0, 1: If T Then glTexCoord2f X3 / W2, (Y3 + P) / H2
                    glVertex3f -X1 * Rad, (Y1 + P) * Rad, 0
                    X3 = X3 + P
                    Next
                glEnd
                Y3 = Y3 + P
                Next
            Next: Next
        glEndList
        End If
    glDisable gl_Blend
    glEnable gl_Texture_2d
    glCallList .Flat
    glDisable gl_Texture_2d
    glEnable gl_Blend
    End With
    End Sub

Sub Planet3D(Pl As Planet, Optional ByVal P As Long = 2, _
        Optional ByVal C As Long = -1, Optional UseLighting As Boolean = True)
    Dim D As Long, T As Boolean, _
        X As Double, Y As Double, Z As Double, _
        X1 As Long, Y1 As Long, X2 As Long, Y2 As Long, _
        X3 As Long, Y3 As Long, W2 As Long, H2 As Long, _
        N As Double, SinN As Double, CosN As Double
    If CamH > Pl.Radius * hPlanetsModel Then Exit Sub
    With Pl.Texture
    If .Poly = 0 Then
        .Poly = glGenLists: If .Poly Then glNewList .Poly, gl_Compile Else Exit Sub
        T = .W > 0 And .H > 0: If Not T Then glBindTexture gl_Texture_2d, 0: glColor4ubv C
        W2 = 360 \ (.W - Not T): H2 = 180 \ (.H - Not T)
        For Y2 = 0 To .H + T: For X2 = 0 To .W + T
            If T Then glBindTexture gl_Texture_2d, .List(X2, Y2)
            Y3 = 0
            For Y1 = -90 + Y2 * H2 To -90 + Y2 * H2 + H2 - P Step P
                glBegin gl_Quad_Strip
                X3 = 0
                For X1 = -180 + X2 * W2 To -180 + X2 * W2 + W2 Step P
                    LL2XYZ Y1 * Rad, X1 * Rad, Pl.Radius2 / Pl.Radius, X, Y, Z
                    glNormal3f X, Y, Z
                    If T Then glTexCoord2f X3 / W2 - 0.5 / 8192, Y3 / H2 - 0.5 / 4096
                    glVertex3f X * Pl.Radius, Y * Pl.Radius, Z * Pl.Radius
                    LL2XYZ (Y1 + P) * Rad, X1 * Rad, Pl.Radius2 / Pl.Radius, X, Y, Z
                    glNormal3f X, Y, Z
                    If T Then glTexCoord2f X3 / W2 - 0.5 / 8192, (Y3 + P) / H2 - 0.5 / 4096
                    glVertex3f X * Pl.Radius, Y * Pl.Radius, Z * Pl.Radius
                    X3 = X3 + P
                    Next
                glEnd
                Y3 = Y3 + P
                Next
            Next: Next
        If Pl.Ring Then
            glEnable gl_Blend
            glDisable gl_Cull_Face
            glDepthMask 0
            'glDisable gl_Lighting
            glBindTexture gl_Texture_2d, Pl.Ring
            glBegin gl_Quad_Strip
            Y1 = 0
            X = Pl.X - Sun.X: Y = Pl.Y - Sun.Y: Z = Pl.Z - Sun.Z
            N = Sqr(X * X + Y * Y + Z * Z)
            'glNormal3f Y / N, Z / N, X / N
            glNormal3f 0, 0, 1
            For X1 = 0 To 360 Step P
                N = CDbl(X1) * Rad: SinN = Sin(N): CosN = Cos(N)
                glTexCoord2f 1, 0: glVertex3f Pl.InnerRing * SinN, Pl.InnerRing * CosN, 0
                glTexCoord2f 0, 0: glVertex3f Pl.OuterRing * SinN, Pl.OuterRing * CosN, 0
                Next
            glEnd
            glDepthMask 1
            glEnable gl_Cull_Face
            End If
        glEndList
        End If
    glPushMatrix
    PlanetGL Pl
    'glEnable gl_Polygon_Smooth
    glDisable gl_Blend
    'glBlendFunc gl_Src_Alpha_Saturate, gl_One
    'glBlendFunc gl_Src_Alpha, gl_Dst_Alpha
    'glDepthFunc gl_Always 'Disable depth test but continue to store Z values
    'glDisable gl_Depth_Test
    If UseLighting Then glEnable gl_Lighting: glEnable gl_Texture_2d: glColor4ubv cWhite _
    Else If -PointZ(Pl.X, Pl.X, Pl.X) < hPlanetsTexture Then glEnable gl_Texture_2d: glColor4ubv cWhite _
    Else glColor4ubv C
    glCallList .Poly
    glDisable gl_Lighting
    glDisable gl_Texture_2d
    'glEnable gl_Depth_Test
    'glDepthFunc gl_LEqual
    'glBlendFunc gl_Src_Alpha, gl_One_Minus_Src_Alpha
    glEnable gl_Blend
    'glDisable gl_Polygon_Smooth
    glPopMatrix
    End With
    End Sub

Sub TerrainEarth3D(Optional ByVal C As Long = -1)
    Dim A As Double, SinA As Double, SinX As Double, CosX As Double, _
        D As Long, E As Long, F As Long, P As Long, T As Boolean, _
        N As Double, E2 As Double, HA As Double, R1 As Double, R2 As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        X As Double, Y As Double, Z As Double, X0 As Double, Y0 As Double, Z0 As Double, _
        X1 As Long, Y1 As Long, X2 As Long, Y2 As Long, X3 As Long, Y3 As Long, _
        X4 As Long, Y4 As Long, X5 As Long, Y5 As Long, X6 As Long, Y6 As Long, _
        X7 As Long, Y7 As Long, X8 As Long, Y8 As Long, _
        W2 As Long, H2 As Long, W3 As Long, H3 As Long
    
    glPushMatrix
    glDisable gl_Blend
    glEnable gl_Texture_2d
    glEnable gl_Lighting
    'glCullFace gl_Front
    PlanetGL Earth
    XYZ2LLA Earth, Lat, Lon, Alt
    With Earth
    
    X5 = -Int(Lon / Rad) - 1: Y5 = Int(Lat / Rad)
    If X5 >= 180 Then X5 = X5 - 360 Else If X5 < -180 Then X5 = X5 + 360
    If Y5 > 90 Then Y5 = 90 Else If Y5 < -90 Then Y5 = -90
    
    'If .Terrain = 0 Or .TerrainX <> X5 Or .TerrainY <> Y5 Then
        'DeleteList .Terrain
        '.TerrainX = X5: .TerrainY = Y5: .Terrain = glGenLists: glNewList .Terrain, gl_Compile 'gl_Compile_And_Execute = &H1301
        
    HA = .Altitude + CamH: R1 = .Radius: R2 = .Radius2: E2 = 1 - R2 * R2 / R1 / R1
    
    With .Texture
    X4 = -1: Y4 = -1: W3 = .W: H3 = .H
    If W3 > 0 And H3 > 0 Then T = True: glColor4ubv cWhite Else glColor4ubv C
    W2 = 360 \ (W3 - Not T): H2 = 180 \ (H3 - Not T)
    
    'D = 20
    D = Int(HA / (HA / 230000 + 1) / 10000 + 0.5): If D < 1 Then D = 1
    
    Y6 = 0: Y2 = Y5 - D: Y3 = Y5 + D
    If Y2 <= -90 Then Y6 = -180 - Y2: Y2 = -90: If Y3 < Y6 Then Y2 = Y6
    If Y3 >= 90 Then Y6 = 180 - Y3: Y3 = 89: If Y2 > Y6 Then Y2 = Y6
    For Y1 = Y2 To Y3
        
        If (Y6 > 0 And Y1 >= Y6) Or (Y6 < 0 And Y1 <= Y6) Then F = 180 _
        Else F = D / Abs(Cos(Y1 * Rad)) ^ 1.5: If F < 0 Then F = 0 Else If F > 180 Then F = 180
        For X6 = X5 - F To X5 + F
            
            If X6 >= 180 Then X1 = X6 - 360 Else If X6 < -180 Then X1 = X6 + 360 Else X1 = X6
            
            X2 = X1 - X5: If X2 < 0 Then X2 = -X2
            Y2 = Y1 - Y5: If Y2 < 0 Then Y2 = -Y2
            If X2 > Y2 Then P = X2 Else P = Y2
            'P = 120 / Int(120 / 4 ^ P + 0.5)
            If TerrainQuality = 0 Then
                If P >= 3 Then P = 60 Else If P = 2 Then P = 15 _
                Else If P = 1 Then P = 4 Else If HA > 500000 Then P = 2 Else P = 1
            ElseIf TerrainQuality = 1 Then
                If P >= 4 Then P = 60 Else If P = 3 Then P = 15 _
                Else If P = 2 Then P = 4 Else If P = 1 Then P = 2 Else P = 1
            Else
                If P >= 5 Then P = 60 Else If P = 4 Then P = 15 _
                Else If P = 3 Then P = 4 Else If P = 2 Then P = 2 Else P = 1
                End If
            
            X2 = X1 + 180: Y2 = Y1 + 90
            If T Then X3 = (X2 \ W2) * W2 - 180: Y3 = (Y2 \ H2) * H2 - 90: _
                If X4 <> X2 * W3 \ 360 Or Y4 <> Y2 * H3 \ 180 Then _
                    X4 = X2 * W3 \ 360: Y4 = Y2 * H3 \ 180: _
                    glBindTexture gl_Texture_2d, .List(X4, Y4)

            E = Earth.TerrainP(359 - X2, Y2)
            Y2 = 0: Do
            
                glBegin gl_Quad_Strip
                X2 = 0: Do
                    X8 = (179 - X1) * 24 + ((120 - X2) \ 5)
                    'If X8 < 0 Then X8 = X8 + 8640 Else
                    If X8 >= 8640 Then X8 = X8 - 8640
                    Y8 = (Y1 + 90) * 24 + (Y2 \ 5)
                    'If Y8 < 0 Then Y8 = 0 Else If Y8 > 4320 Then Y8 = 4320
                    Z0 = CLng(Earth.Geoid(X8, Y8)) - 128
                    If E = 0 Then
                        Z0 = Z0 * TerrainScale
                    ElseIf X2 = 0 Then
                        If X1 <= -180 Then X7 = 0 Else X7 = 180 - X1
                        F = Earth.TerrainP(X7, Y1 + 90): If F = 0 Then Z0 = Z0 * TerrainScale _
                        Else Z0 = (Z0 + Earth.Terrain(0, Y2, F)) * TerrainScale
                    Else
                        Z0 = (Z0 + Earth.Terrain(120 - X2, Y2, E)) * TerrainScale
                        End If
                    Y0 = Y2 / 120 + Y1: A = Y0 * Rad: X0 = X2 / 120 + X1
                    If TerrainMode > 0 Then SinA = Sin(A): N = R1 / Sqr(1 - E2 * SinA * SinA): _
                        Z = (N * (1 - E2) + Z0) * SinA: N = (N + Z0) * Cos(A) _
                    Else N = R1 + Z0: Z = N * Sin(A): N = N * Cos(A)
                    A = X0 * Rad: SinX = Sin(A): CosX = Cos(A)
                    X = N * SinX: Y = N * CosX
                    glNormal3f X / R1, Y / R1, Z / R1
                    If T Then glTexCoord2f (X0 - X3) / W2, (Y0 - Y3) / H2
                    glVertex3f X, Y, Z
    
                    Y8 = (Y1 + 90) * 24 + ((Y2 + P) \ 5)
                    'If Y8 < 0 Then Y8 = 0 Else If Y8 > 4320 Then Y8 = 4320
                    Z0 = CLng(Earth.Geoid(X8, Y8)) - 128
                    If E = 0 Then
                        Z0 = Z0 * TerrainScale
                    ElseIf Y2 + P >= 120 Then
                        If X2 = 0 Then
                            If X1 <= -180 Then X7 = 0 Else X7 = 180 - X1
                            If Y1 >= 89 Then Y7 = 179 Else Y7 = Y1 + 91
                            F = Earth.TerrainP(X7, Y7): If F = 0 Then Z0 = Z0 * TerrainScale _
                            Else Z0 = (Z0 + Earth.Terrain(0, 0, F)) * TerrainScale
                        Else
                            If Y1 >= 89 Then Y7 = 179 Else Y7 = Y1 + 91
                            F = Earth.TerrainP(179 - X1, Y7): If F = 0 Then Z0 = Z0 * TerrainScale _
                            Else Z0 = (Z0 + Earth.Terrain(120 - X2, 0, F)) * TerrainScale
                            End If
                    ElseIf X2 = 0 Then
                        If X1 <= -180 Then X7 = 0 Else X7 = 180 - X1
                        F = Earth.TerrainP(X7, Y1 + 90): If F = 0 Then Z0 = Z0 * TerrainScale _
                        Else Z0 = (Z0 + Earth.Terrain(0, Y2 + P, F)) * TerrainScale
                    Else
                        Z0 = (Z0 + Earth.Terrain(120 - X2, Y2 + P, E)) * TerrainScale
                        End If
                    Y0 = Y0 + P / 120: A = Y0 * Rad
                    If TerrainMode > 0 Then SinA = Sin(A): N = R1 / Sqr(1 - E2 * SinA * SinA): _
                        Z = (N * (1 - E2) + Z0) * SinA: N = (N + Z0) * Cos(A) _
                    Else N = R1 + Z0: Z = N * Sin(A): N = N * Cos(A)
                    X = N * SinX: Y = N * CosX
                    glNormal3f X / R1, Y / R1, Z / R1
                    If T Then glTexCoord2f (X0 - X3) / W2, (Y0 - Y3) / H2
                    glVertex3f X, Y, Z
    
                    X2 = X2 + P: Loop While X2 <= 120
                glEnd
                    
                Y2 = Y2 + P: Loop While Y2 <= 119
            
                'glDisable gl_Depth_Test: glColor4ubv cWhite: Text3D P, X, Y, Z: glEnable gl_Depth_Test: If T Then glColor4ubv cWhite Else glColor4ubv C
                
            Next
        Next
    
    End With
        
        'glEndList
        'End If
    'glCallList .Terrain
    
    End With
    'glCullFace gl_Back
    glDisable gl_Lighting
    glDisable gl_Texture_2d
    glEnable gl_Blend
    glPopMatrix
    
    End Sub

Sub TerrainMoon3D(Optional ByVal C As Long = -1)
    Dim A As Double, SinA As Double, SinX As Double, CosX As Double, _
        D As Long, F As Long, P As Long, T As Boolean, _
        N As Double, E2 As Double, HA As Double, R1 As Double, R2 As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        X As Double, Y As Double, Z As Double, X0 As Double, Y0 As Double, Z0 As Double, _
        X1 As Long, Y1 As Long, X2 As Long, Y2 As Long, X3 As Long, Y3 As Long, _
        X4 As Long, Y4 As Long, X5 As Long, Y5 As Long, X6 As Long, Y6 As Long, _
        W2 As Long, H2 As Long, W3 As Long, H3 As Long

    glPushMatrix
    glDisable gl_Blend
    glEnable gl_Texture_2d
    glEnable gl_Lighting
    'glCullFace gl_Front
    PlanetGL Moon
    XYZ2LLA Moon, Lat, Lon, Alt
    With Moon
    
    X5 = -Int(Lon / Rad) - 1: Y5 = Int(Lat / Rad)
    If X5 >= 180 Then X5 = X5 - 360 Else If X5 < -180 Then X5 = X5 + 360
    If Y5 > 90 Then Y5 = 90 Else If Y5 < -90 Then Y5 = -90

    'If .Terrain = 0 Or .TerrainX <> X5 Or .TerrainY <> Y5 Then
        'DeleteList .Terrain
        '.TerrainX = X5: .TerrainY = Y5: .Terrain = glGenLists: glNewList .Terrain, gl_Compile 'gl_Compile_And_Execute = &H1301
        
    HA = .Altitude + CamH + 30000: R1 = .Radius: R2 = .Radius2: E2 = 1 - R2 * R2 / R1 / R1

    With .Texture
    X4 = -1: Y4 = -1: W3 = .W: H3 = .H
    If W3 > 0 And H3 > 0 Then T = True: glColor4ubv cWhite Else glColor4ubv C
    W2 = 360 \ (W3 - Not T): H2 = 180 \ (H3 - Not T)

    D = Int(HA / (HA / 230000 + 1) / 5000 + 0.5): If D < 1 Then D = 1

    Y6 = 0: Y2 = Y5 - D: Y3 = Y5 + D
    If Y2 <= -90 Then Y6 = -180 - Y2: Y2 = -90: If Y3 < Y6 Then Y2 = Y6
    If Y3 >= 90 Then Y6 = 180 - Y3: Y3 = 89: If Y2 > Y6 Then Y2 = Y6
    For Y1 = Y2 To Y3

        If (Y6 > 0 And Y1 >= Y6) Or (Y6 < 0 And Y1 <= Y6) Then F = 180 _
        Else F = D / Abs(Cos(Y1 * Rad)) ^ 1.5: If F < 0 Then F = 0 Else If F > 180 Then F = 180
        For X6 = X5 - F To X5 + F

            If X6 >= 180 Then X1 = X6 - 360 Else If X6 < -180 Then X1 = X6 + 360 Else X1 = X6

            X2 = X1 - X5: If X2 < 0 Then X2 = -X2
            Y2 = Y1 - Y5: If Y2 < 0 Then Y2 = -Y2
            If X2 > Y2 Then P = X2 Else P = Y2
            If TerrainQuality = 0 Then
                If P >= 5 Then P = 8 Else If P = 4 Then P = 4 Else If P = 3 Then P = 2 Else If HA > 150000 Then P = 2 Else P = 1
            ElseIf TerrainQuality = 1 Then
                If P >= 10 Then P = 8 Else If P >= 6 Then P = 4 Else If P >= 3 Then P = 2 Else P = 1
            Else
                If P >= 15 Then P = 8 Else If P >= 10 Then P = 4 Else If P >= 5 Then P = 2 Else P = 1
                End If

            X2 = X1 + 180: Y2 = Y1 + 90
            If T Then X3 = (X2 \ W2) * W2 - 180: Y3 = (Y2 \ H2) * H2 - 90: _
                If X4 <> X2 * W3 \ 360 Or Y4 <> Y2 * H3 \ 180 Then _
                    X4 = X2 * W3 \ 360: Y4 = Y2 * H3 \ 180: _
                    glBindTexture gl_Texture_2d, .List(X4, Y4)

            Y2 = 0: Do

                glBegin gl_Quad_Strip
                X2 = 0: Do
                    If X1 = -180 And X2 = 0 Then Z0 = Moon.Terrain(0, (Y1 + 90) * 16 + Y2) * TerrainScale _
                    Else Z0 = Moon.Terrain((180 - X1) * 16 - X2, (Y1 + 90) * 16 + Y2) * TerrainScale
                    Y0 = Y2 / 16 + Y1: A = Y0 * Rad: X0 = X2 / 16 + X1
                    If TerrainMode > 0 Then SinA = Sin(A): N = R1 / Sqr(1 - E2 * SinA * SinA): _
                        Z = (N * (1 - E2) + Z0) * SinA: N = (N + Z0) * Cos(A) _
                    Else N = R1 + Z0: Z = N * Sin(A): N = N * Cos(A)
                    A = X0 * Rad: SinX = Sin(A): CosX = Cos(A)
                    X = N * SinX: Y = N * CosX
                    glNormal3f X / R1, Y / R1, Z / R1
                    If T Then glTexCoord2f (X0 - X3) / W2, (Y0 - Y3) / H2
                    glVertex3f X, Y, Z

                    If Y1 = 89 And Y2 + P >= 16 Then
                        If X1 = -180 And X2 = 0 Then Z0 = Moon.Terrain(0, 180 * 16 - 1) * TerrainScale _
                        Else Z0 = Moon.Terrain((180 - X1) * 16 - X2, 180 * 16 - 1) * TerrainScale
                    Else
                        If X1 = -180 And X2 = 0 Then Z0 = Moon.Terrain(0, (Y1 + 90) * 16 + Y2 + P) * TerrainScale _
                        Else Z0 = Moon.Terrain((180 - X1) * 16 - X2, (Y1 + 90) * 16 + Y2 + P) * TerrainScale
                        End If
                    Y0 = Y0 + P / 16: A = Y0 * Rad
                    If TerrainMode > 0 Then SinA = Sin(A): N = R1 / Sqr(1 - E2 * SinA * SinA): _
                        Z = (N * (1 - E2) + Z0) * SinA: N = (N + Z0) * Cos(A) _
                    Else N = R1 + Z0: Z = N * Sin(A): N = N * Cos(A)
                    X = N * SinX: Y = N * CosX
                    glNormal3f X / R1, Y / R1, Z / R1
                    If T Then glTexCoord2f (X0 - X3) / W2, (Y0 - Y3) / H2
                    glVertex3f X, Y, Z

                    X2 = X2 + P: Loop While X2 <= 16
                glEnd

                Y2 = Y2 + P: Loop While Y2 <= 15

                'glDisable gl_Depth_Test: glColor4ubv cWhite: Text3D P, X, Y, Z: glEnable gl_Depth_Test: If T Then glColor4ubv cWhite Else glColor4ubv C

            Next
        Next

    End With

        'glEndList
        'End If
    'glCallList .Terrain

    End With

    'glCullFace gl_Back
    glDisable gl_Lighting
    glDisable gl_Texture_2d
    glEnable gl_Blend
    glPopMatrix

    End Sub

Sub TerrainMars3D(Optional ByVal C As Long = -1)
    Dim A As Double, SinA As Double, SinX As Double, CosX As Double, _
        D As Long, F As Long, P As Long, T As Boolean, _
        N As Double, E2 As Double, HA As Double, R1 As Double, R2 As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        X As Double, Y As Double, Z As Double, X0 As Double, Y0 As Double, Z0 As Double, _
        X1 As Long, Y1 As Long, X2 As Long, Y2 As Long, X3 As Long, Y3 As Long, _
        X4 As Long, Y4 As Long, X5 As Long, Y5 As Long, X6 As Long, Y6 As Long, _
        W2 As Long, H2 As Long, W3 As Long, H3 As Long

    glPushMatrix
    glDisable gl_Blend
    glEnable gl_Texture_2d
    glEnable gl_Lighting
    'glCullFace gl_Front
    PlanetGL Mars
    XYZ2LLA Mars, Lat, Lon, Alt
    With Mars
    
    X5 = -Int(Lon / Rad) - 1: Y5 = Int(Lat / Rad)
    If X5 >= 180 Then X5 = X5 - 360 Else If X5 < -180 Then X5 = X5 + 360
    If Y5 > 90 Then Y5 = 90 Else If Y5 < -90 Then Y5 = -90

    'If .Terrain = 0 Or .TerrainX <> X5 Or .TerrainY <> Y5 Then
        'DeleteList .Terrain
        '.TerrainX = X5: .TerrainY = Y5: .Terrain = glGenLists: glNewList .Terrain, gl_Compile 'gl_Compile_And_Execute = &H1301
        
    HA = .Altitude + CamH + 20000: R1 = .Radius: R2 = .Radius2: E2 = 1 - R2 * R2 / R1 / R1

    With .Texture
    X4 = -1: Y4 = -1: W3 = .W: H3 = .H
    If W3 > 0 And H3 > 0 Then T = True: glColor4ubv cWhite Else glColor4ubv C
    W2 = 360 \ (W3 - Not T): H2 = 180 \ (H3 - Not T)

    D = Int(HA / (HA / 230000 + 1) / 7000 + 0.5): If D < 1 Then D = 1

    Y6 = 0: Y2 = Y5 - D: Y3 = Y5 + D
    If Y2 <= -90 Then Y6 = -180 - Y2: Y2 = -90: If Y3 < Y6 Then Y2 = Y6
    If Y3 >= 90 Then Y6 = 180 - Y3: Y3 = 89: If Y2 > Y6 Then Y2 = Y6
    For Y1 = Y2 To Y3

        If (Y6 > 0 And Y1 >= Y6) Or (Y6 < 0 And Y1 <= Y6) Then F = 180 _
        Else F = D / Abs(Cos(Y1 * Rad)) ^ 1.5: If F < 0 Then F = 0 Else If F > 180 Then F = 180
        For X6 = X5 - F To X5 + F

            If X6 >= 180 Then X1 = X6 - 360 Else If X6 < -180 Then X1 = X6 + 360 Else X1 = X6

            X2 = X1 - X5: If X2 < 0 Then X2 = -X2
            Y2 = Y1 - Y5: If Y2 < 0 Then Y2 = -Y2
            If X2 > Y2 Then P = X2 Else P = Y2
            If TerrainQuality = 0 Then
                If P >= 5 Then P = 8 Else If P = 4 Then P = 4 Else If P = 3 Then P = 2 Else If HA > 150000 Then P = 2 Else P = 1
            ElseIf TerrainQuality = 1 Then
                If P >= 10 Then P = 8 Else If P >= 6 Then P = 4 Else If P >= 3 Then P = 2 Else P = 1
            Else
                If P >= 15 Then P = 8 Else If P >= 10 Then P = 4 Else If P >= 5 Then P = 2 Else P = 1
                End If

            X2 = X1 + 180: Y2 = Y1 + 90
            If T Then X3 = (X2 \ W2) * W2 - 180: Y3 = (Y2 \ H2) * H2 - 90: _
                If X4 <> X2 * W3 \ 360 Or Y4 <> Y2 * H3 \ 180 Then _
                    X4 = X2 * W3 \ 360: Y4 = Y2 * H3 \ 180: _
                    glBindTexture gl_Texture_2d, .List(X4, Y4)

            Y2 = 0: Do

                glBegin gl_Quad_Strip
                X2 = 0: Do
                    If X1 = -180 And X2 = 0 Then Z0 = Mars.Terrain(0, (Y1 + 90) * 16 + Y2) * TerrainScale _
                    Else Z0 = Mars.Terrain((180 - X1) * 16 - X2, (Y1 + 90) * 16 + Y2) * TerrainScale
                    Y0 = Y2 / 16 + Y1: A = Y0 * Rad: X0 = X2 / 16 + X1
                    If TerrainMode > 0 Then SinA = Sin(A): N = R1 / Sqr(1 - E2 * SinA * SinA): _
                        Z = (N * (1 - E2) + Z0) * SinA: N = (N + Z0) * Cos(A) _
                    Else N = R1 + Z0: Z = N * Sin(A): N = N * Cos(A)
                    A = X0 * Rad: SinX = Sin(A): CosX = Cos(A)
                    X = N * SinX: Y = N * CosX
                    glNormal3f X / R1, Y / R1, Z / R1
                    If T Then glTexCoord2f (X0 - X3) / W2, (Y0 - Y3) / H2
                    glVertex3f X, Y, Z

                    If Y1 = 89 And Y2 + P >= 16 Then
                        If X1 = -180 And X2 = 0 Then Z0 = Mars.Terrain(0, 180 * 16 - 1) * TerrainScale _
                        Else Z0 = Mars.Terrain((180 - X1) * 16 - X2, 180 * 16 - 1) * TerrainScale
                    Else
                        If X1 = -180 And X2 = 0 Then Z0 = Mars.Terrain(0, (Y1 + 90) * 16 + Y2 + P) * TerrainScale _
                        Else Z0 = Mars.Terrain((180 - X1) * 16 - X2, (Y1 + 90) * 16 + Y2 + P) * TerrainScale
                        End If
                    Y0 = Y0 + P / 16: A = Y0 * Rad
                    If TerrainMode > 0 Then SinA = Sin(A): N = R1 / Sqr(1 - E2 * SinA * SinA): _
                        Z = (N * (1 - E2) + Z0) * SinA: N = (N + Z0) * Cos(A) _
                    Else N = R1 + Z0: Z = N * Sin(A): N = N * Cos(A)
                    X = N * SinX: Y = N * CosX
                    glNormal3f X / R1, Y / R1, Z / R1
                    If T Then glTexCoord2f (X0 - X3) / W2, (Y0 - Y3) / H2
                    glVertex3f X, Y, Z

                    X2 = X2 + P: Loop While X2 <= 16
                glEnd

                Y2 = Y2 + P: Loop While Y2 <= 15

                'glDisable gl_Depth_Test: glColor4ubv cWhite: Text3D P, X, Y, Z: glEnable gl_Depth_Test: If T Then glColor4ubv cWhite Else glColor4ubv C

            Next
        Next

    End With

        'glEndList
        'End If
    'glCallList .Terrain

    End With

    'glCullFace gl_Back
    glDisable gl_Lighting
    glDisable gl_Texture_2d
    glEnable gl_Blend
    glPopMatrix
    
    End Sub

Sub Asteroid3D(L As Asteroid, Optional T As Double, _
        Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True, _
        Optional ByVal Selected As Boolean) ', Optional ByVal Mode As Boolean = True)
    Dim D As Long, A1 As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        T1 As Double, T2 As Double, T3 As Double, _
        U As Boolean, V As Boolean
    If L.Class = -ToPluto Then If (Tool And ToolAsteroids) <> 0 Then Exit Sub
    Asteroid2XYZ L, T, X1, Y1, Z1, A1 ', Mode
    If ObjectVisibility > 0 And Not Selected Then
        X2 = X1 - CamX: Y2 = Y1 - CamY: Z2 = Z1 - CamZ
        If Sqr(X2 * X2 + Y2 * Y2 + Z2 * Z2) > ObjectVisibility Then
            If (Tool And ToolPoints) = 0 Then glEnable gl_Depth_Test
            glColor4ubv cAsteroidPoint: glBegin gl_Points: glVertex3f X1, Y1, Z1: glEnd
            If (Tool And ToolPoints) = 0 Then glDisable gl_Depth_Test
            Exit Sub
            End If
        End If
    If Orbit Then
        If (Tool And ToolHidden) = 0 Then glEnable gl_Depth_Test
        If Selected Then glColor4ubv cAsteroidOrbitSel Else glColor4ubv cAsteroidOrbit
        glBegin gl_Line_Strip
        T3 = Rad: T1 = 0: T2 = A360 + T3 / 2
        Do
            AsteroidOrbit2XYZ L, T1, X2, Y2, Z2: glVertex3f X2, Y2, Z2
            T1 = T1 + T3
            Loop While T1 < T2
        glEnd
        If (Tool And ToolHidden) = 0 Then glDisable gl_Depth_Test
        End If
    H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1)
    V = H1 <= AsteroidDist
    If V Then
        glEnable gl_Depth_Test: glColor4ubv cAsteroidLine
        glBegin gl_Line_Strip: glVertex3f X1, Y1, Z1: glVertex3f 0, 0, 0: glEnd
        glDisable gl_Depth_Test
        End If
    D = SmallGreenDot
    If L.IsModel Then
        If CamH <= L.R * 1000 Then D = 0
        If IsPosVisible(X1 + L.R, Y1 + L.R, Z1 + L.R) Or _
        IsPosVisible(X1 - L.R, Y1 - L.R, Z1 - L.R) Then U = True
        End If
    glRasterPos3f X1, Y1, Z1: If IsVisible Then U = True
    If U Then If Not Label Then glCallList D _
    Else If Not V Then Text3D L.Name, , , , , , D, False _
    Else Text3D L.Name & vbBackCrBack & ToAlt(H1, FMini), , , , , , D, False
    End Sub

Sub Asteroids3D(Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True)
    Dim C As Long, X1 As Double, Y1 As Double, Z1 As Double, T1 As Double
    T1 = Time + T
    If Label Or Orbit Or AsteroidN <= 100 Then
        For SelN = AsteroidN - 1 To 0 Step -1
            If AsteroidP = 0 Or Asteroid(SelN).Class = AsteroidP Then _
                If SelectedType <> TypeAsteroid Or SelN <> SelectedN Then _
                    Asteroid3D Asteroid(SelN), T1, Label, Orbit
            Next
    Else
        If (Tool And ToolPoints) = 0 Then glEnable gl_Depth_Test
        glColor4ubv cAsteroidPoint: glBegin gl_Points
        For C = AsteroidN - 1 To 0 Step -1
            If AsteroidP = 0 Or Asteroid(C).Class = AsteroidP Then _
                Asteroid2XYZ Asteroid(C), T1, X1, Y1, Z1: glVertex3f X1, Y1, Z1
            Next
        glEnd
        If (Tool And ToolPoints) = 0 Then glDisable gl_Depth_Test
        End If
    End Sub

Sub Comets3D(Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True)
    Dim C As Long, X1 As Double, Y1 As Double, Z1 As Double, T1 As Double
    T1 = Time + T
    If Label Or Orbit Or CometN <= 50 Then
        For SelN = CometN - 1 To 0 Step -1
            If SelectedType <> TypeComet Or SelN <> SelectedN Then _
                Asteroid3D Comet(SelN), T1, Label, Orbit
            Next
    Else
        If (Tool And ToolPoints) = 0 Then glEnable gl_Depth_Test
        glColor4ubv cAsteroidPoint: glBegin gl_Points
        For C = CometN - 1 To 0 Step -1
            Asteroid2XYZ Comet(C), T1, X1, Y1, Z1: glVertex3f X1, Y1, Z1
            Next
        glEnd
        If (Tool And ToolPoints) = 0 Then glDisable gl_Depth_Test
        End If
    End Sub

Sub Moons3D(Optional ByVal Label As Boolean = True, Optional ByVal Orbit As Boolean = True)
    Dim C As Long, X1 As Double, Y1 As Double, Z1 As Double, T1 As Double
    T1 = Time + T
    If Label Or Orbit Or MoonN <= 100 Then
        For SelN = MoonN - 1 To 0 Step -1
            If SelectedType <> TypeMoon Or SelN <> SelectedN Then _
                Asteroid3D Moons(SelN), T1, Label, Orbit
            Next
    Else
        If (Tool And ToolPoints) = 0 Then glEnable gl_Depth_Test
        glColor4ubv cAsteroidPoint: glBegin gl_Points
        For C = MoonN - 1 To 0 Step -1
            Asteroid2XYZ Moons(C), T1, X1, Y1, Z1: glVertex3f X1, Y1, Z1
            Next
        glEnd
        If (Tool And ToolPoints) = 0 Then glDisable gl_Depth_Test
        End If
    End Sub

Sub Stars3D()
    Dim N As Long, R As Single
    If SkyN <= 0 Then Exit Sub
    If SkyList = 0 Then
        SkyList = glGenLists: glNewList SkyList, gl_Compile
        For N = 0 To SkyN - 1: With Sky(N)
            If .Mag <= StarsDensity Then _
                glPointSize Sqr(1 / (.Mag + 3)) * 3: glBegin gl_Points: glVertex3f .X, .Y, .Z: glEnd
            End With: Next
        glPointSize 1
        glEndList
        End If
    glColor4ubv cStars
    glPushMatrix
    glRotatef -AxialTilt, 1, 0, 0: glScalef StarsDistance, StarsDistance, StarsDistance
    glCallList SkyList
    glPopMatrix
    End Sub

Sub StarTrackerModels3D()
    Dim N As Long
    For N = 0 To StarsN - 1: With Stars(N)
        Sphere3D Sun.X + .X, Sun.Y + .Y, Sun.Z + .Z, CDbl(.Radius), .Color
        End With: Next
    End Sub

Sub StarTracker3D()
    glPushMatrix
    glTranslatef Sun.X, Sun.Y, Sun.Z
    For SelN = StarsN - 1 To 0 Step -1: With Stars(SelN)
        If SelectedType <> TypeStar Or SelN <> SelectedN Then _
            Text3D .Name, .X, .Y, .Z, , , , 1
        End With: Next
    glPopMatrix
    End Sub

Sub StarTrackerN3D(ByVal N As Long)
    glPushMatrix
    glTranslatef Sun.X, Sun.Y, Sun.Z
    SelN = N: With Stars(SelN)
        Text3D .Name, .X, .Y, .Z, , , , 1
        End With
    glPopMatrix
    End Sub

Sub SensorFOV3D()
    Dim X1 As Single, Y1 As Single, Z1 As Single
    If (Cam1.Follow = ToOnBoard Or (Cam(0).Follow = ToOnBoard And Cam(0).hRC <> 0)) And _
            Status = Deployed And SensorP >= 0 Then
        If Cam(0).FOV > 0 And Cam(0).Width > 0 And Altitude > 0 Then
            Z1 = Altitude * 8
            X1 = Z1 * Tan(Cam(0).FOV * Rad / 2): Y1 = X1 * Cam(0).Height / Cam(0).Width
            glEnable gl_Depth_Test
            glBegin gl_Triangle_Fan
            glColor4ubv cSensorFOVPlaneB: glVertex3f 0, 0, 0
            glColor4ubv cSensorFOVPlaneA: glVertex3f -X1, -Y1, Z1: glVertex3f X1, -Y1, Z1
            glVertex3f X1, Y1, Z1: glVertex3f -X1, Y1, Z1: glVertex3f -X1, -Y1, Z1
            glEnd
            glBegin gl_Lines
            glColor4ubv cSensorFOVLineB: glVertex3f 0, 0, 0: glColor4ubv cSensorFOVLineA: glVertex3f -X1, -Y1, Z1
            glColor4ubv cSensorFOVLineB: glVertex3f 0, 0, 0: glColor4ubv cSensorFOVLineA: glVertex3f X1, -Y1, Z1
            glColor4ubv cSensorFOVLineB: glVertex3f 0, 0, 0: glColor4ubv cSensorFOVLineA: glVertex3f X1, Y1, Z1
            glColor4ubv cSensorFOVLineB: glVertex3f 0, 0, 0: glColor4ubv cSensorFOVLineA: glVertex3f -X1, Y1, Z1
            glEnd
            'If ToolX And ToolCrosshair Then glColor4ubv cStationTarget: _
                glBegin gl_Lines: glVertex3f 0, 0, 0: glVertex3f 0, 0, Z1: glEnd
            glDisable gl_Depth_Test
            End If
        End If
    End Sub

Sub Boundaries3D()
    Dim C As Long, N As Long, Q As Boolean, R As Double

    glPushMatrix
    PlanetGL Earth
    
    Q = CamH < hCities
    R = 1 + RangeScale / 100 / Earth.Radius: glScalef R, R, R
    
    'Populated areas
    If (Layers And LayersAreas) <> 0 And Q Then
        If AreasList = 0 Then
            AreasList = glGenLists: If AreasList Then glNewList AreasList, gl_Compile
            glColor4ubv cAreasLine
            For N = 0 To AreasN - 1: With Areas(N)
                glBegin gl_Line_Strip
                For C = 0 To .N - 1
                    VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon)
                    Next
                glEnd
                End With: Next
            glEndList
            End If
        glCallList AreasList
        End If
    
    'Water boundaries
    If (Layers And LayersSea) <> 0 And Q Then
        If SeaList = 0 Then
            SeaList = glGenLists: If SeaList Then glNewList SeaList, gl_Compile
            glColor4ubv cSeaLine
            For N = 0 To SeaN - 1: With Sea(N)
                glBegin gl_Line_Strip
                For C = 0 To .N - 1
                    VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon)
                    Next
                glEnd
                End With: Next
            glEndList
            End If
        glCallList SeaList
        End If
    
    'Land boundaries
    If (Layers And LayersLand) <> 0 And Q Then
        If LandList = 0 Then
            LandList = glGenLists: If LandList Then glNewList LandList, gl_Compile
            If EarthTexture = "White" Then glColor4ubv cLandLineB Else glColor4ubv cLandLine
            For N = 0 To LandN - 1: With Land(N)
                glBegin gl_Line_Strip
                For C = 0 To .N - 1
                    VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon)
                    Next
                glEnd
                End With: Next
            glEndList
            End If
        glCallList LandList
        End If
    
    glPopMatrix
    
    End Sub

Sub Airways3D()
    Dim B As Long, C As Long, D As Long, E As Long, N As Long, _
        L As Boolean, M As Boolean, P As Boolean, Q As Boolean, _
        H As Double, R As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        N1 As Long, N2 As Long

    glPushMatrix
    PlanetGL Earth
    
    H = IfDbl(Layers And LayersDensity, 8, 5)
    P = CamH < hAirways: Q = CamH < hCities
    L = (Layers And LayersAltitude) <> 0 Or (Tool And ToolTerrain) <> 0
    M = (ToolL And AirwaysLabels) <> 0 And CamH < hAirwaysLabels
    
    R = 1 + RangeScale / IfDbl(L, 100, 20) / Earth.Radius: glScalef R, R, R
    
    'Airspaces
    If (Layers And LayersAirspaces) <> 0 And P Then
        If AirspaceZoneP < 0 Then _
            D = AirspaceZone(0).N: E = AirspaceZone(UBound(AirspaceZone)).N - 1 _
        Else If AirspaceGroupP < 0 Then _
            D = AirspaceZone(AirspaceZoneP).N: E = AirspaceZone(AirspaceZoneP + 1).N - 1 _
        Else D = AirspaceZone(AirspaceZoneP).N + AirspaceGroupP: E = D
        
        If AirspacesList = 0 Then
            AirspacesList = glGenLists: If AirspacesList Then glNewList AirspacesList, gl_Compile
            For B = D To E
                If AirspaceP < 0 Then N1 = AirspaceGroup(B).N: N2 = AirspaceGroup(B + 1).N - 1 _
                Else N1 = AirspaceGroup(B).N + AirspaceP: N2 = N1
                For N = N1 To N2: With Airspace(N)
                    glColor4ubv (.Color And &HFFFFFF) Or cAirspaces
                    If L Then
                        glBegin gl_Quad_Strip
                        For C = 0 To .N - 1
                            VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon), CDbl(.FLU)
                            VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon), CDbl(.FLD)
                            Next
                        glEnd
                        End If
                    glBegin gl_Triangle_Fan
                    If L Then VertexLLA Earth, CDbl(.CLat), CDbl(.CLon), CDbl(.FLU) _
                    Else VertexLLA Earth, CDbl(.CLat), CDbl(.CLon)
                    For C = 0 To .N - 1
                        If L Then VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon), CDbl(.FLU) _
                        Else VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon)
                        Next
                    glEnd
                    End With: Next
                Next
            For B = D To E
                If AirspaceP < 0 Then N1 = AirspaceGroup(B).N: N2 = AirspaceGroup(B + 1).N - 1 _
                Else N1 = AirspaceGroup(B).N + AirspaceP: N2 = N1
                For N = N1 To N2: With Airspace(N)
                    If L Then
                        glColor4ubv (.Color And &HFFFFFF) Or cAirspacesLine
                        For C = 0 To .N - 2
                            glBegin gl_Line_Strip
                            VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon), CDbl(.FLU)
                            VertexLLA Earth, CDbl(.Point(C + 1).Lat), CDbl(.Point(C + 1).Lon), CDbl(.FLU)
                            VertexLLA Earth, CDbl(.Point(C + 1).Lat), CDbl(.Point(C + 1).Lon), CDbl(.FLD)
                            VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon), CDbl(.FLD)
                            glEnd
                            Next
                    Else
                        glColor4ubv (.Color And &HFFFFFF) Or cAirspacesLineB
                        glBegin gl_Line_Strip
                        For C = 0 To .N - 1
                            VertexLLA Earth, CDbl(.Point(C).Lat), CDbl(.Point(C).Lon)
                            Next
                        glEnd
                        End If
                    End With: Next
                Next
            glEndList
            End If
        
        glCallList AirspacesList
        
        End If
    
    If AirspaceN > 0 Then If AirspaceZoneP >= 0 Then _
        If AirspaceZone(AirspaceZoneP).Name <> "Spain" Then GoTo NoAirways
    
    'Lower airways
    If (Layers And LayersLower) <> 0 And P Then
        For N = 0 To AirwayN - 1: With Airway(N)
            For C = 0 To .Num - 2
                If .Way(C).Dis > 0 Then
                    glColor4ubv cAirwaysLower
                    glBegin gl_Lines
                    Lon = .Way(C).Lon * Rad: Lat = .Way(C).Lat * Rad
                    If L Then Alt = .Way(C).FLD * 100 * Foot Else Alt = 0
                    VertexLLA Earth, Lat, Lon, Alt, X1, Y1, Z1
                    Lon = .Way(C + 1).Lon * Rad: Lat = .Way(C + 1).Lat * Rad
                    If L Then Alt = .Way(C + 1).FLD * 100 * Foot Else Alt = 0
                    VertexLLA Earth, Lat, Lon, Alt, X2, Y2, Z2
                    glEnd
                    If (Layers And LowerLabels) <> 0 And M Then _
                        glRasterPos3f (X1 + X2) / 2, (Y1 + Y2) / 2, (Z1 + Z2) / 2: _
                        If IsVisible Then glDisable gl_Depth_Test: Text3D .Nam & vbCr & _
                            Str(.Way(C).Dis), , , , , , , False: glEnable gl_Depth_Test '"FL " & Int(.Way(C).FLD) & "-" & Int(.Way(C).FLU)
                    End If
                Next
            End With: Next
        End If
    
    'RNAV routes
    If (Layers And LayersRNAV) <> 0 And P Then
        For N = 0 To RNAVN - 1: With RNAV(N)
            For C = 0 To .Num - 2
                If .Way(C).Dis > 0 Then
                    glColor4ubv cAirwaysUpper
                    glBegin gl_Lines
                    Lon = .Way(C).Lon * Rad: Lat = .Way(C).Lat * Rad
                    If L Then Alt = .Way(C).FLD * 100 * Foot Else Alt = 0
                    VertexLLA Earth, Lat, Lon, Alt, X1, Y1, Z1
                    Lon = .Way(C + 1).Lon * Rad: Lat = .Way(C + 1).Lat * Rad
                    If L Then Alt = .Way(C + 1).FLD * 100 * Foot Else Alt = 0
                    VertexLLA Earth, Lat, Lon, Alt, X2, Y2, Z2
                    glEnd
                    If (Layers And RNAVLabels) <> 0 And M Then _
                        glRasterPos3f (X1 + X2) / 2, (Y1 + Y2) / 2, (Z1 + Z2) / 2: _
                        If IsVisible Then glDisable gl_Depth_Test: Text3D .Nam & vbCrBack & _
                            Str(.Way(C).Dis), , , , , , , False: glEnable gl_Depth_Test '"FL " & Int(.Way(C).FLD) & "-" & Int(.Way(C).FLU)
                    End If
                Next
            End With: Next
        End If

NoAirways:
    
    glPopMatrix
    
    End Sub

Sub AirwaysLabels3D()
    Dim B As Long, D As Long, E As Long, L As Boolean, M As Boolean, P As Boolean, _
        H As Double, R As Double, N1 As Long, N2 As Long
    
    glPushMatrix
    PlanetGL Earth
    
    P = (Tool And ToolAirways) <> 0 And CamH < hAirways
    L = (Layers And LayersAltitude) <> 0 Or (Tool And ToolTerrain) <> 0
    M = (ToolL And AirwaysLabels) <> 0 And CamH < hAirwaysLabels
    
    'Airspaces labels
    SelType = TypeAirspace
    If (Layers And LayersAirspaces) <> 0 And (Layers And AirspacesLabels) <> 0 And P And M Then
        If AirspaceZoneP < 0 Then _
            D = AirspaceZone(0).N: E = AirspaceZone(UBound(AirspaceZone)).N - 1 _
        Else If AirspaceGroupP < 0 Then _
            D = AirspaceZone(AirspaceZoneP).N: E = AirspaceZone(AirspaceZoneP + 1).N - 1 _
        Else D = AirspaceZone(AirspaceZoneP).N + AirspaceGroupP: E = D
        For B = D To E
            If AirspaceP < 0 Then N1 = AirspaceGroup(B).N: N2 = AirspaceGroup(B + 1).N - 1 _
            Else N1 = AirspaceGroup(B).N + AirspaceP: N2 = N1
            For SelN = N1 To N2
                If SelectedType <> TypeAirspace Or SelN <> SelectedN Then _
                    Airspace3D Airspace(SelN), L
                Next
            Next
        End If
    If SelectedType = TypeAirspace Then SelN = SelectedN: Airspace3D Airspace(SelN), L
    
    'Airports
    SelType = TypeAirport
    H = IfDbl(Layers And LayersDensity, 8, 5)
    If (Layers And LayersAirports) <> 0 And P Then
        If (ToolL And AirwaysLabels) <> 0 And (Layers And AirportsLabels) <> 0 And _
                CamH <= hAirwaysLabels * IfDbl(Layers And LayersDensity, 1, 3) Then
            For SelN = 0 To AirportN - 1
                If AirspaceZoneP < 0 Or Airport(SelN).Planet = AirspaceZoneP Then _
                    If Airport(SelN).Time <= H Then _
                        If SelectedType <> TypeAirport Or SelN <> SelectedN Then _
                            LocationEarth3D Airport(SelN)
                Next
        Else
            glPushMatrix
            R = (Earth.Radius + RangeScale / 10) / Earth.Radius: glScalef R, R, R
            glEnable gl_Depth_Test: glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points
            For SelN = 0 To AirportN - 1
                If AirspaceZoneP < 0 Or Airport(SelN).Planet = AirspaceZoneP Then _
                    If Airport(SelN).Time <= H Then _
                        If SelectedType <> TypeAirport Or SelN <> SelectedN Then _
                            With Airport(SelN): VertexLLA Earth, .Lat, .Lon, .Alt: End With
                Next
            glEnd
            glPointSize 1: glDisable gl_Depth_Test
            glPopMatrix
            End If
        End If
    If SelectedType = TypeAirport Then SelN = SelectedN: LocationEarth3D Airport(SelN)
    
    'Seaports
    SelType = TypeSeaport
    H = IfDbl(Layers And LayersDensity, 1, 50)
    If (Layers And LayersSeaports) <> 0 And P Then
        If (ToolL And AirwaysLabels) <> 0 And (Layers And SeaportsLabels) <> 0 And _
                CamH <= hAirwaysLabels * IfDbl(Layers And LayersDensity, 1, 3) Then
            For SelN = 0 To SeaportN - 1
                If Seaport(SelN).Time >= H Then _
                    If SelectedType <> TypeSeaport Or SelN <> SelectedN Then _
                        LocationEarth3D Seaport(SelN)
                Next
        Else
            glPushMatrix
            R = (Earth.Radius + RangeScale / 10) / Earth.Radius: glScalef R, R, R
            glEnable gl_Depth_Test: glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points
            For SelN = 0 To SeaportN - 1
                If Seaport(SelN).Time >= H Then _
                    If SelectedType <> TypeSeaport Or SelN <> SelectedN Then _
                        With Seaport(SelN): VertexLLA Earth, .Lat, .Lon, .Alt: End With
                Next
            glEnd
            glPointSize 1: glDisable gl_Depth_Test
            glPopMatrix
            End If
        End If
    If SelectedType = TypeSeaport Then SelN = SelectedN: LocationEarth3D Seaport(SelN)
    
    SelType = TypeNone
    
    glPopMatrix
    
    End Sub

Sub BoundariesFlat3D()
    Dim C As Long, N As Long, Q As Boolean, R As Double
    
    glPushMatrix
    
    Q = CamH < hCities
    R = 1 + RangeScale / 100 / Earth.Radius: glScalef 1, 1, R
    
    'Populated areas
    If (Layers And LayersAreas) <> 0 And Q Then
        If AreasFlatList = 0 Then
            AreasFlatList = glGenLists: If AreasFlatList Then glNewList AreasFlatList, gl_Compile
            glColor4ubv cAreasLine
            For N = 0 To AreasN - 1: With Areas(N)
                glBegin gl_Line_Strip
                For C = 0 To .N - 1
                    glVertex3f .Point(C).Lon, .Point(C).Lat, 0
                    Next
                glEnd
                End With: Next
            glEndList
            End If
        glCallList AreasFlatList
        End If
    
    'Water boundaries
    If (Layers And LayersSea) <> 0 And Q Then
        If SeaFlatList = 0 Then
            SeaFlatList = glGenLists: If SeaFlatList Then glNewList SeaFlatList, gl_Compile
            glColor4ubv cSeaLine
            For N = 0 To SeaN - 1: With Sea(N)
                glBegin gl_Line_Strip
                For C = 0 To .N - 1
                    glVertex3f .Point(C).Lon, .Point(C).Lat, 0
                    Next
                glEnd
                End With: Next
            glEndList
            End If
        glCallList SeaFlatList
        End If
    
    'Land boundaries
    If (Layers And LayersLand) <> 0 And Q Then
        If LandFlatList = 0 Then
            LandFlatList = glGenLists: If LandFlatList Then glNewList LandFlatList, gl_Compile
            If EarthTexture = "White" Then glColor4ubv cLandLineB Else glColor4ubv cLandLine
            For N = 0 To LandN - 1: With Land(N)
                glBegin gl_Line_Strip
                For C = 0 To .N - 1
                    glVertex3f .Point(C).Lon, .Point(C).Lat, 0
                    Next
                glEnd
                End With: Next
            glEndList
            End If
        glCallList LandFlatList
        End If
    
    glPopMatrix
    
    End Sub

Sub AirwaysFlat3D()
    Dim B As Long, C As Long, D As Long, E As Long, N As Long, _
        L As Boolean, M As Boolean, P As Boolean, Q As Boolean, _
        H As Double, R As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        N1 As Long, N2 As Long
    
    glPushMatrix
    
    P = (Tool And ToolAirways) <> 0 And CamH < hAirwaysFlat: Q = CamH < hCities
    L = (Layers And LayersAltitude) <> 0 Or (Tool And ToolTerrain) <> 0
    M = (ToolL And AirwaysLabels) <> 0 And CamH < hAirwaysLabels
    R = 1 + RangeScale / IfDbl(L, 100, 20) / Earth.Radius: glScalef 1, 1, R
    
    'Airspaces
    If (Layers And LayersAirspaces) <> 0 And P Then
        If AirspaceZoneP < 0 Then _
            D = AirspaceZone(0).N: E = AirspaceZone(UBound(AirspaceZone)).N - 1 _
        Else If AirspaceGroupP < 0 Then _
            D = AirspaceZone(AirspaceZoneP).N: E = AirspaceZone(AirspaceZoneP + 1).N - 1 _
        Else D = AirspaceZone(AirspaceZoneP).N + AirspaceGroupP: E = D
        
        If AirspacesFlatList = 0 Then
            AirspacesFlatList = glGenLists: If AirspacesFlatList Then glNewList AirspacesFlatList, gl_Compile
            For B = D To E
                If AirspaceP < 0 Then N1 = AirspaceGroup(B).N: N2 = AirspaceGroup(B + 1).N - 1 _
                Else N1 = AirspaceGroup(B).N + AirspaceP: N2 = N1
                For N = N1 To N2: With Airspace(N)
                    glColor4ubv (.Color And &HFFFFFF) Or cAirspaces
                    If L Then
                        glBegin gl_Quad_Strip
                        For C = 0 To .N - 1
                            If C > 0 Then If Abs(.Point(C).Lon - .Point(C - 1).Lon) > A180 Then _
                                glEnd: glBegin gl_Quad_Strip
                            glVertex3f .Point(C).Lon, .Point(C).Lat, .FLU
                            glVertex3f .Point(C).Lon, .Point(C).Lat, .FLD
                            Next
                        glEnd
                        End If
                    glBegin gl_Triangle_Fan
                    If L Then glVertex3f .CLon, .CLat, .FLU _
                    Else glVertex3f .CLon, .CLat, 0
                    For C = 0 To .N - 1
                        If C > 0 Then If Abs(.Point(C).Lon - .Point(C - 1).Lon) > A180 Then _
                            glEnd: glBegin gl_Triangle_Fan
                        If L Then glVertex3f .Point(C).Lon, .Point(C).Lat, .FLU _
                        Else glVertex3f .Point(C).Lon, .Point(C).Lat, 0
                        Next
                    glEnd
                    End With: Next
                Next
            For B = D To E
                If AirspaceP < 0 Then N1 = AirspaceGroup(B).N: N2 = AirspaceGroup(B + 1).N - 1 _
                Else N1 = AirspaceGroup(B).N + AirspaceP: N2 = N1
                For N = N1 To N2: With Airspace(N)
                    If L Then
                        glColor4ubv (.Color And &HFFFFFF) Or cAirspacesLine
                        For C = 0 To .N - 2
                            glBegin gl_Line_Strip
                            Q = Abs(.Point(C).Lon - .Point(C + 1).Lon) > A180
                            glVertex3f .Point(C).Lon, .Point(C).Lat, .FLU
                            If Q Then glEnd: glBegin gl_Line_Strip
                            glVertex3f .Point(C + 1).Lon, .Point(C + 1).Lat, .FLU
                            glVertex3f .Point(C + 1).Lon, .Point(C + 1).Lat, .FLD
                            If Q Then glEnd: glBegin gl_Line_Strip
                            glVertex3f .Point(C).Lon, .Point(C).Lat, .FLD
                            glEnd
                            Next
                    Else
                        glColor4ubv (.Color And &HFFFFFF) Or cAirspacesLineB
                        glBegin gl_Line_Strip
                        For C = 0 To .N - 1
                            If C Then If Abs(.Point(C).Lon - .Point(C - 1).Lon) > A180 Then _
                                glEnd: glBegin gl_Line_Strip
                            glVertex3f .Point(C).Lon, .Point(C).Lat, 0
                            Next
                        glEnd
                        End If
                    End With: Next
                Next
            glEndList
            End If
        
        glCallList AirspacesFlatList
            
        End If
    
    If AirspaceN > 0 Then If AirspaceZoneP >= 0 Then _
        If AirspaceZone(AirspaceZoneP).Name <> "Spain" Then GoTo NoAirways
    
    'Lower airways
    If (Layers And LayersLower) <> 0 And P Then
        For N = 0 To AirwayN - 1: With Airway(N)
            For C = 0 To .Num - 2
                If .Way(C).Dis > 0 Then
                    glColor4ubv cAirwaysLower
                    glBegin gl_Lines
                    X1 = .Way(C).Lon * Rad: Y1 = .Way(C).Lat * Rad
                    If L Then Z1 = .Way(C).FLD * 100 * Foot Else Z1 = 0
                    glVertex3f X1, Y1, Z1
                    X2 = .Way(C + 1).Lon * Rad: Y2 = .Way(C + 1).Lat * Rad
                    If L Then Z2 = .Way(C + 1).FLD * 100 * Foot Else Z2 = 0
                    glVertex3f X2, Y2, Z2
                    glEnd
                    If (Layers And LowerLabels) <> 0 And M Then _
                        Text3D .Nam & vbCr & Str(.Way(C).Dis), (X1 + X2) / 2, (Y1 + Y2) / 2, (Z1 + Z2) / 2
                    End If
                Next
            End With: Next
        End If
    
    'RNAV routes
    If (Layers And LayersRNAV) <> 0 And P Then
        For N = 0 To RNAVN - 1: With RNAV(N)
            For C = 0 To .Num - 2
                If .Way(C).Dis > 0 Then
                    glColor4ubv cAirwaysUpper
                    glBegin gl_Lines
                    X1 = .Way(C).Lon * Rad: Y1 = .Way(C).Lat * Rad
                    If L Then Z1 = .Way(C).FLD * 100 * Foot Else Z1 = 0
                    glVertex3f X1, Y1, Z1
                    X2 = .Way(C + 1).Lon * Rad: Y2 = .Way(C + 1).Lat * Rad
                    If L Then Z2 = .Way(C + 1).FLD * 100 * Foot Else Z2 = 0
                    glVertex3f X2, Y2, Z2
                    glEnd
                    If (Layers And RNAVLabels) <> 0 And M Then _
                        Text3D .Nam & vbCr & Str(.Way(C).Dis), (X1 + X2) / 2, (Y1 + Y2) / 2, (Z1 + Z2) / 2
                    End If
                Next
            End With: Next
        End If
    
NoAirways:
    
    'Airspaces labels
    SelType = TypeAirspace
    If (Layers And LayersAirspaces) <> 0 And (Layers And AirspacesLabels) <> 0 And P And M Then
        For B = D To E
            If AirspaceP < 0 Then N1 = AirspaceGroup(B).N: N2 = AirspaceGroup(B + 1).N - 1 _
            Else N1 = AirspaceGroup(B).N + AirspaceP: N2 = N1
            For SelN = N1 To N2
                If SelectedType <> TypeAirspace Or SelN <> SelectedN Then _
                    AirspaceFlat3D Airspace(SelN), L
                Next
            Next
        End If
    If SelectedType = TypeAirspace Then SelN = SelectedN: AirspaceFlat3D Airspace(SelN), L

    'Airports
    SelType = TypeAirport
    If (Layers And LayersAirports) <> 0 And P Then
        H = IfDbl(Layers And LayersDensity, 8, 5)
        If (ToolL And AirwaysLabels) <> 0 And (Layers And AirportsLabels) <> 0 And _
                CamH <= hAirwaysLabels * IfDbl(Layers And LayersDensity, 1, 3) Then
            For SelN = 0 To AirportN - 1
                If AirspaceZoneP < 0 Or Airport(SelN).Planet = AirspaceZoneP Then _
                    If Airport(SelN).Time <= H Then _
                        If SelectedType <> TypeAirport Or SelN <> SelectedN Then _
                            LocationEarthFlat3D Airport(SelN)
                Next
        Else
            glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points
            For SelN = 0 To AirportN - 1
                If AirspaceZoneP < 0 Or Airport(SelN).Planet = AirspaceZoneP Then _
                    If Airport(SelN).Time <= H Then _
                        If SelectedType <> TypeAirport Or SelN <> SelectedN Then _
                            With Airport(SelN): glVertex3f .Lon, .Lat, .Alt: End With
                Next
            glEnd
            glPointSize 1
            End If
        End If
    If SelectedType = TypeAirport Then SelN = SelectedN: LocationEarthFlat3D Airport(SelN)
    
    'Seaports
    SelType = TypeSeaport
    If (Layers And LayersSeaports) <> 0 And P Then
        H = IfDbl(Layers And LayersDensity, 1, 50)
        If (ToolL And AirwaysLabels) <> 0 And (Layers And SeaportsLabels) <> 0 And _
                CamH <= hAirwaysLabels * IfDbl(Layers And LayersDensity, 1, 3) Then
            For SelN = 0 To SeaportN - 1
                If Seaport(SelN).Time >= H Then _
                    If SelectedType <> TypeSeaport Or SelN <> SelectedN Then _
                        LocationEarthFlat3D Seaport(SelN)
                Next
        Else
            glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points
            For SelN = 0 To SeaportN - 1
                If Seaport(SelN).Time >= H Then _
                    If SelectedType <> TypeSeaport Or SelN <> SelectedN Then _
                        With Seaport(SelN): glVertex3f .Lon, .Lat, .Alt: End With
                Next
            glEnd
            glPointSize 1
            End If
        End If
    If SelectedType = TypeSeaport Then SelN = SelectedN: LocationEarthFlat3D Seaport(SelN)
    
    SelType = TypeNone
    
    glPopMatrix
    
    End Sub

Sub Airspace3D(R As Airspace, Optional ByVal UseAlt As Boolean = True)
    If UseAlt Then RasterLLA Earth, CDbl(R.Lat), CDbl(R.Lon), CDbl(R.Alt) _
    Else RasterLLA Earth, CDbl(R.Lat), CDbl(R.Lon)
    If IsVisible Then Text3D R.Name, , , , , , GreenDot, False
    End Sub

Sub AirspaceFlat3D(R As Airspace, Optional ByVal UseAlt As Boolean = True)
    If UseAlt Then glRasterPos3f R.Lon, R.Lat, R.Alt Else glRasterPos3f R.Lon, R.Lat, 0
    Text3D R.Name, , , , , , GreenDot, False: If IsSelect Then GetSelectedValid
    End Sub

Sub Cities3D()
    Dim H As Double, R As Double
    glPushMatrix
    PlanetGL Earth
    SelType = TypeCity
    If (Tool And ToolLocations) <> 0 And (Layers And LayersCities) <> 0 And CamH < hCities Then
        H = IfDbl(Layers And LayersDensity, 1, 2000000)
        If (ToolL And LocationsLabels) <> 0 And (Layers And CitiesLabels) <> 0 And _
                CamH <= IfDbl(Layers And LayersDensity, hCitiesLabelsH, hCitiesLabels) Then
            For SelN = 0 To CityN - 1
                If City(SelN).Time >= H Then _
                    If SelectedType <> TypeCity Or SelN <> SelectedN Then _
                        LocationEarth3D City(SelN)
                Next
        Else
            glPushMatrix
            R = (Earth.Radius + RangeScale / 10) / Earth.Radius: glScalef R, R, R
            glEnable gl_Depth_Test: glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points
            For SelN = 0 To CityN - 1
                If City(SelN).Time >= H Then _
                    If SelectedType <> TypeCity Or SelN <> SelectedN Then _
                        With City(SelN): VertexLLA Earth, .Lat, .Lon, .Alt: End With
                Next
            glEnd
            glPointSize 1: glDisable gl_Depth_Test
            glPopMatrix
            End If
        End If
    If SelectedType = TypeCity Then SelN = SelectedN: LocationEarth3D City(SelN)
    SelType = TypeNone
    glPopMatrix
    End Sub

Sub CitiesFlat3D()
    Dim H As Double, R As Double
    glPushMatrix
    glScalef 1, 1, (Earth.Radius + RangeScale / 10) / Earth.Radius
    SelType = TypeCity
    If (Tool And ToolLocations) <> 0 And (Layers And LayersCities) <> 0 And CamH < hCities Then
        H = IfDbl(Layers And LayersDensity, 1, 2000000)
        If (ToolL And LocationsLabels) <> 0 And (Layers And CitiesLabels) <> 0 And _
                CamH <= IfDbl(Layers And LayersDensity, hCitiesLabelsH, hCitiesLabels) Then
            For SelN = 0 To CityN - 1
                If City(SelN).Time >= H Then _
                    If SelectedType <> TypeCity Or SelN <> SelectedN Then _
                        LocationEarthFlat3D City(SelN)
                Next
        Else
            glColor4ubv cSatellitePoint: glPointSize hPointSize
            glBegin gl_Points
            For SelN = 0 To CityN - 1
                If City(SelN).Time >= H Then _
                    If SelectedType <> TypeCity Or SelN <> SelectedN Then _
                        With City(SelN): glVertex3f .Lon, .Lat, .Alt: End With
                Next
            glEnd
            glPointSize 1
            End If
        End If
    If SelectedType = TypeCity Then SelN = SelectedN: LocationEarthFlat3D City(SelN)
    SelType = TypeNone
    glPopMatrix
    End Sub

Sub Stage3D(S As StagePos, ByVal N As Long)
    Dim H1 As Double, F1 As Single, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double
    'H1 = Sqr(S.X * S.X + S.Y * S.Y + S.Z * S.Z)
    X1 = Earth.X + S.X: Y1 = Earth.Y + S.Y: Z1 = Earth.Z + S.Z
    If N Then X2 = S.SpeedX: Y2 = S.SpeedY: Z2 = S.SpeedZ _
    Else X2 = S.X: Y2 = S.Y: Z2 = S.Z
    glRasterPos3f X1, Y1, Z1: If Not IsVisible Then Exit Sub
    Text3D IIf(N, "Stage " & N, "Balloon"), , , , , , , False 'IfInt(H1 <= Earth.Radius, WhiteDot, 0), False
    'If H1 <= Earth.Radius Then Exit Sub
    If Ortho Then H1 = CamH _
    Else glGetFloatv gl_Current_Raster_Distance, F1: _
        If F1 < -ZoomMin Then H1 = -F1 Else H1 = CamH
    If (ShowIcons And 1) <> 0 And ShipFactor > 0 Then
        H1 = H1 * CamR / 60 * ShipFactor * 1500 / 11 / CamS
        glPushMatrix
        glTranslatef X1, Y1, Z1
        glRotatef -ATan(X2, Y2) / Rad + 270, 0, 0, 1
        glRotatef ATan3(X2, Y2, Z2) / Rad + 180, 0, 1, 0
        glColor4ubv
        glScalef H1, H1, H1
        If N <= 0 Then glCallList TrackList _
        Else glCallList StageEmptyList + IfInt(N > StageMax, StageMax, N)
        glPopMatrix
    Else
        glCallList WhiteDot
        End If
    End Sub

Sub Track3D(Track As Track)
    Dim H1 As Double, F1 As Single, _
        X1 As Double, Y1 As Double, Z1 As Double, _
        X2 As Double, Y2 As Double, Z2 As Double, _
        X3 As Double, Y3 As Double, Z3 As Double
    LLA2XYZ Earth, Track.Lat, Track.Lon, Track.Alt, X1, Y1, Z1, , , True, True
    X3 = X1: Y3 = Y1: Z3 = Z1
    If Track.ID = 0 Then
        If Track.Start > 0 Then H1 = Sqr(X1 * X1 + Y1 * Y1 + Z1 * Z1): _
            If H1 > 1 Then Sphere3D X1, Y1, Z1, H1, cTrackSphere
        If Not Track2.Valid Then GoTo Nxt
    ElseIf Track.ID = 1 And Track1.Valid Then
        glColor4ubv cTrackLine
        glBegin gl_Line_Strip
        glVertex3f X1, Y1, Z1
        LLA2XYZ Earth, Track1.Lat, Track1.Lon, Track1.Alt, X1, Y1, Z1, , , True, True
        glVertex3f X1, Y1, Z1
        glEnd
Nxt:
        glColor4ubv
        If Ortho Then H1 = CamH _
        Else glRasterPos3f X1, Y1, Z1: glGetFloatv gl_Current_Raster_Distance, F1: _
            If F1 < -ZoomMin Then H1 = -F1 Else H1 = CamH
        If (ShowIcons And 4) <> 0 And ShipFactor > 0 Then
            H1 = H1 * CamR / 60 * ShipFactor * 1500 / 11 / CamS
            X2 = X1 - Earth.X: Y2 = Y1 - Earth.Y: Z2 = Z1 - Earth.Z
            glPushMatrix
            glTranslatef X1, Y1, Z1
            glRotatef -ATan(X2, Y2) / Rad + 270, 0, 0, 1
            glRotatef ATan3(X2, Y2, Z2) / Rad + 180, 0, 1, 0
            glScalef H1, H1, H1
            'If Tool And ToolSmooth Then glEnable gl_Polygon_Smooth
            glCallList TrackList
            'If Tool And ToolSmooth Then glDisable gl_Polygon_Smooth
            glPopMatrix
            End If
        End If
    Text3D _
        "Lat " & ToLat(Track.Lat, FLong, Degrees) & vbBackCrBack & _
        "Lon " & ToLon(Track.Lon, FLong, Degrees) & vbBackCrBack & _
        "Alt " & ToAlt(Track.Alt, FMini) & IIf(Track.ID = 1 And Track1.Valid, _
            " " & ToDistance(Track.TargetDst, FOne) & vbBackCrBack & _
        "Hdg " & ToAngle(Track.TargetHdg - IfDbl(RelativeAngles, Track.Heading, 0), FAngleLon) & _
            " " & ToAngle(Track.TargetElv, FAngleLat), ""), _
        X3, Y3, Z3, 8, 12, IfInt(Track.Mode, WhiteDot, RedDot)
    End Sub

Sub TrackFlat3D(Track As Track)
    Dim H As Double, L As Long
    With Track
    If ((.ID = 0 And (ShowIcons And 4) <> 0) Or (.ID = 1 And (ShowIcons And 2) <> 0)) And ShipFactor > 0 Then
        glPushMatrix
        glTranslatef .Lon, .Lat, .Alt
        H = ShipScale * ShipFactor * 1500 / 11 / CamS
        glScalef H / Earth.Radius, H / Earth.Radius, H
        glCallList IfInt(.ID = 0, TrackList, StationList)
        glPopMatrix
        End If
    Text3D .Name & vbCr & ToAlt(.Alt, FMini), .Lon, .Lat, .Alt, , , IfInt(.Mode, WhiteDot, RedDot)
    Line3D .Lon, .Lat, .Alt, , , -.Alt, cAltitudeProj
    End With
    End Sub

Sub PathFlat3D(KMLPath() As KMLPoint, ByVal KMLPathN As Long, _
        Optional ByVal DrawAlt As Boolean = True)
    Dim C As Long, X1 As Double
    If KMLPathN <= 0 Then Exit Sub
    If DrawAlt Then
        glColor4ubv cPathKMLPlane: glBegin gl_Quad_Strip
        For C = 0 To KMLPathN - 1: With KMLPath(C)
            If C Then If Abs(.Lon - X1) > A180 Then glEnd: glBegin gl_Quad_Strip
            glVertex3f .Lon, .Lat, .Alt: glVertex3f .Lon, .Lat, 0: X1 = .Lon
            End With: Next: glEnd
        glColor4ubv cPathKMLLine: glBegin gl_Lines
        For C = 0 To KMLPathN - 1: With KMLPath(C)
            glVertex3f .Lon, .Lat, .Alt: glVertex3f .Lon, .Lat, 0
            End With: Next: glEnd
        End If
    glColor4ubv cPathKML: glBegin gl_Line_Strip
    For C = 0 To KMLPathN - 1: With KMLPath(C)
        If .Color Then glColor4ubv .Color: If C Then glEnd: glBegin gl_Line_Strip
        If C Then If Abs(.Lon - X1) > A180 Then glEnd: glBegin gl_Line_Strip
        glVertex3f .Lon, .Lat, .Alt: X1 = .Lon
        End With: Next: glEnd
    End Sub

Sub Path3D(KMLPath() As KMLPoint, ByVal KMLPathN As Long, _
        Optional ByVal DrawAlt As Boolean = True)
    Dim C As Long
    If KMLPathN <= 0 Then Exit Sub
    glPushMatrix
    PlanetGL Earth
    If DrawAlt Then
        glColor4ubv cPathKMLPlane: glBegin gl_Quad_Strip
        For C = 0 To KMLPathN - 1: With KMLPath(C)
            VertexLLA Earth, .Lat, .Lon, .Alt: VertexLLA Earth, .Lat, .Lon, 0
            End With: Next: glEnd
        glColor4ubv cPathKMLLine: glBegin gl_Lines
        For C = 0 To KMLPathN - 1: With KMLPath(C)
            VertexLLA Earth, .Lat, .Lon, .Alt: VertexLLA Earth, .Lat, .Lon, 0
            End With: Next: glEnd
        End If
    glColor4ubv cPathKML: glBegin gl_Line_Strip
    For C = 0 To KMLPathN - 1: With KMLPath(C)
        If .Color Then glColor4ubv .Color: If C Then glEnd: glBegin gl_Line_Strip
        VertexLLA Earth, .Lat, .Lon, .Alt
        End With: Next: glEnd
    glPopMatrix
    End Sub

Sub Ship3D( _
        Optional U As Double, Optional V As Double, Optional W As Double, _
        Optional F As Double, Optional R As Double)
    Dim N As Double, H As Double, L As Double
    If SelectedType = TypeNone Then SelType = TypeShip: SelN = 0
    glRasterPos3f 0, 0, 0: IsVisible 'If Not IsVisible Then glCallList WhiteDot
    SelType = TypeNone
    'If Tool And ToolAxes Then Axe3D , , , , cAxesLine
    H = ShipScale * ShipFactor * 1500 / 11 / CamS
    If F <> 0 And R <> 0 And (ShowIcons And 1) <> 0 And H > 0 Then
        glEnable gl_Depth_Test: N = H * (R / 2 + 0.5)
        Disk3D , , , , N * 2, 5, cShipEngineC, cShipEngineB, True
        Disk3D , , , N * 2, N * 5, 5, cShipEngineB, cShipEngineA, True
        glDisable gl_Depth_Test
        End If
    glPushMatrix
    If IsControl = 2 Then
        glMultMatrixd Attitude.XX
    Else
        glRotatef -ATan(U, V) / Rad + 270, 0, 0, 1
        glRotatef ATan3(U, V, W) / Rad + 180, 0, 1, 0
        End If
    If ToolX And ToolFOV Then SensorFOV3D
    L = SunShadow: If L > 0.5 Then L = 0.5
    If AirSpeed > 0 Then Ambient3D L * (0.5 + 0.5 / (Altitude / 200000 + 1)) _
    Else Ambient3D L * 0.5
    glColor4ubv cShipModel
    glPushMatrix
    'glEnable gl_MultiSample_Arb
    'If Tool And ToolSmooth Then glEnable gl_Polygon_Smooth
    If Track2.Valid And TrackSync = 0 And KMLPathP = 0 And KMLPathPos <= 1 And T = 0 Then
        If (ShowIcons And 2) <> 0 And H > 0 Then _
            glRotatef 270 - IfDbl(RelativeAngles, Track2.Heading, Track2.TargetHdg) / Rad, 0, 0, 1: _
            glScalef H, H, H: glCallList StationList _
        Else glCallList IfInt(Track2.Mode, WhiteDot, RedDot)
    Else
        If (ShowIcons And 1) <> 0 And H > 0 Then glScalef H, H, H: glCallList ShipList _
        Else glCallList WhiteDot
        End If
    'If Tool And ToolSmooth Then glDisable gl_Polygon_Smooth
    'glDisable gl_MultiSample_Arb
    Ambient3D SunShadow
    glPopMatrix
    If Tool And ToolAxes Then Axe3D , , , , cAxesShip
    glPopMatrix
    End Sub

Sub ShipFlat3D(Optional F As Double, Optional R As Double)
    Dim N As Double, H As Double, _
        Lat As Double, Lon As Double, Alt As Double, _
        X1 As Double, Y1 As Double, X2 As Double, Y2 As Double
    glPushMatrix
    XYZ2LLA Earth, Lat, Lon, Alt: glTranslatef Lon, Lat, Alt
    If Tool And ToolStage Then ShipInfo Earth, True
    If Tool And ToolRanges Then If ToolX And ToolAltitude Then Line3D , , , , , -Alt, cAltitudeProj
    If SelectedType = TypeNone Then SelType = TypeShip: SelN = 0
    glRasterPos3f 0, 0, 0: If IsSelect Then GetSelectedValid
    SelType = TypeNone
    If (ShowIcons And 1) <> 0 And ShipFactor > 0 Then
        H = ShipScale * ShipFactor * 1500 / 11 / CamS
        glScalef 1 / Earth.Radius, 1 / Earth.Radius, 1
        If F <> 0 And R <> 0 Then N = H * (R / 2 + 0.5): _
            Disk3D , , , , N * 2, 5, cShipEngineC, cShipEngineB, True: _
            Disk3D , , , N * 2, N * 5, 5, cShipEngineB, cShipEngineA, True
        If SunShadow > 0.5 Then Ambient3D 0.5
        glColor4ubv cShipModel
        If PathP Then
            Pos2LLA Path(0), Y1, X1, , True: Pos2LLA Path(PathP - 1), Y2, X2
            X1 = X1 - X2: If X1 > A90 Then X1 = X1 - A360 Else If X1 < -A90 Then X1 = A360 + X1
            Y1 = Y1 - Y2: N = -A90 - ATan(X1, Y1)
        Else
            N = A90 - Heading
            End If
        glRotatef N / Rad, 0, 0, 1
        'If StageP = 0 Then glRotatef 90, 0, 0, 1
        glRotatef Angle3D(-AngleU2, -AngleV2, -AngleW2, _
            Earth.X / Earth.Distance, Earth.Y / Earth.Distance, Earth.Z / Earth.Distance) / Rad, 0, 1, 0
        glScalef H, H, H
        'If Tool And ToolSmooth Then glEnable gl_Polygon_Smooth
        glCallList ShipList
        'If Tool And ToolSmooth Then glDisable gl_Polygon_Smooth
        Ambient3D SunShadow
    Else
        glCallList WhiteDot
        End If
    glPopMatrix
    End Sub

'Sub CutTexture(File As String, Optional ByVal W2 As Long = 2, Optional ByVal H2 As Long = 1)
'    Dim C As Long, D As Long, X As Long, Y As Long, W As Long, H As Long, N As Long, _
'        Code As String * 2, Bh As BmpHeader, Pix() As Byte
'    'CutTexture "Backup\Textures\MoonE1KX", 4, 2
'    'BMP: (biFileSize-54)/4+54 biHeight/2 biWidth/2 biCompression=0
'    On Error Resume Next
'    Open File & ".bmp" For Binary Access Read As 1
'    For Y = 0 To H2 - 1: For X = 0 To W2 - 1
'        Open File & "." & X & "." & Y & ".bmp" For Binary As 2
'        Get 1, 1 + 0, Code: Get 1, 1 + 2, Bh
'        W = Bh.Width \ W2: H = Bh.Height \ H2
'        Bh.Width = W: Bh.Height = H
'        Bh.FileSize = (Bh.FileSize - 54) \ W2 \ H2 + 54
'        Put 2, , Code: Put 2, , Bh
'        C = W * 3: D = W * H * 3 '24bit=3
'        ReDim Pix(C - 1)
'        For N = 0 To H - 1
'            Get 1, 1 + 54 + Y * D * W2 + C * W2 * N + C * X, Pix
'            Put 2, , Pix
'            Next
'        Close 2
'        Next: Next
'    Close 1
'    On Error GoTo 0
'    End Sub

'Sub CopyTexture(File As String, Optional ByVal W2 As Long = 2, Optional ByVal H2 As Long = 1)
'    Dim C As Long, D As Long, X As Long, Y As Long, W As Long, H As Long, N As Long, _
'        Code As String * 4, DDSd As DDSurfaceDesc, Pix() As Byte
'    'CopyTexture "Backup\Textures\MoonE1KX", 4, 2
'    'Convert Image 8192x4096 -> 4096x2048
'    'DDS: dwHeight/2 dwWidth/2 dwLinearSize/4 dwMipMapCount=1
'    ' DXT1  4x4  64bit  8bytes Size=dwLinearSize*2
'    ' DXT2+ 4x4 128bit 16bytes Size=dwLinearSize*4
'    'BMP: (biFileSize-54)/4+54 biHeight/2 biWidth/2 biCompression=0
'    On Error Resume Next
'    Open File & ".dds" For Binary As 2
'    For Y = 0 To H2 - 1: For X = 0 To W2 - 1
'        Open File & "." & X & "." & H2 - 1 - Y & ".dds" For Binary Access Read As 1
'        Get 1, , Code: Get 1, , DDSd
'        W = DDSd.dwWidth: H = DDSd.dwHeight
'        If X = 0 And Y = 0 Then
'            DDSd.dwWidth = W * W2: DDSd.dwHeight = H * H2
'            DDSd.dwLinearSize = DDSd.dwLinearSize * W2 * H2
'            Put 2, , Code: Put 2, , DDSd
'            End If
'        C = W * 2: D = W * H * 2 \ 4 'DXT1=2 DXTn=4
'        ReDim Pix(C - 1)
'        For N = 0 To H \ 4
'            Get 1, , Pix
'            Put 2, 1 + 128 + Y * D * W2 + C * W2 * N + C * X, Pix
'            Next
'        Close 1
'        Next: Next
'    Close 2
'    On Error GoTo 0
'    End Sub

Sub LoadImage(Name As String, Pic As PictureX, _
        Optional ByVal Visible As Boolean = True, Optional ByVal Center As Boolean, _
        Optional ByVal ToAlpha As Long, Optional ByVal C As Long = 255)
    Dim X As Long, Y As Long, W As Long, H As Long, _
        Code As String * 2, Bh As BmpHeader, Pix() As Byte, PixB() As Byte
    
    On Error Resume Next
    OpenFile ImagesDir & Name & ".bmp": If FreeFile = 1 Then Exit Sub
    Get 1, , Code: If Code <> "BM" Then Close: Exit Sub
    Get 1, , Bh: If Bh.Compression <> 0 Then Close: Exit Sub
    W = Bh.Width: H = Bh.Height: If W < 1 Or H < 1 Then Close: Exit Sub
    If Bh.BitCount = 24 Then ReDim Pix(3 - 1, W - 1, H - 1) _
    Else If Bh.BitCount = 32 Then ReDim Pix(4 - 1, W - 1, H - 1) _
    Else Close: Exit Sub
    Get 1, , Pix
    Close
    On Error GoTo 0
    
    ReDim PixB(4 - 1, W - 1, H - 1)
    If Bh.BitCount = 24 Then
        If ToAlpha Then
            If ToAlpha < 0 Then ToAlpha = 255
            For Y = 0 To H - 1: For X = 0 To W - 1
                PixB(0, X, Y) = C
                PixB(1, X, Y) = C
                PixB(2, X, Y) = C
                PixB(3, X, Y) = _
                    ((CLng(Pix(0, X, Y)) + CLng(Pix(1, X, Y)) + CLng(Pix(2, X, Y))) * ToAlpha) \ 765
                Next: Next
        Else
            For Y = 0 To H - 1: For X = 0 To W - 1
                PixB(0, X, Y) = Pix(2, X, Y)
                PixB(1, X, Y) = Pix(1, X, Y)
                PixB(2, X, Y) = Pix(0, X, Y)
                PixB(3, X, Y) = &HFF&
                Next: Next
            End If
    Else
        For Y = 0 To H - 1: For X = 0 To W - 1
            PixB(0, X, Y) = Pix(2, X, Y)
            PixB(1, X, Y) = Pix(1, X, Y)
            PixB(2, X, Y) = Pix(0, X, Y)
            PixB(3, X, Y) = Pix(3, X, Y)
            Next: Next
        End If
    Pic.W = W: Pic.H = H: If Visible Then Pic.Visible = Visible
    Pic.List = glGenLists: glNewList Pic.List, gl_Compile
    glPixelStorei gl_Unpack_Alignment, 1
    If Center Then glBitmap 1, 1, 0, 0, -W \ 2, -H \ 2, 0 _
    Else glBitmap 1, 1, 0, 0, 0, -H, 0
    glDrawPixels W, H, gl_Rgba, gl_Unsigned_Byte, PixB(0, 0, 0)
    glEndList
    Erase Pix, PixB
    End Sub

Sub DeleteImage(Pic As PictureX)
    DeleteList Pic.List
    End Sub

Sub PaintImage(Pic As PictureX, Optional ByVal X As Long, Optional ByVal Y As Long)
    If Pic.List Then glRasterPos2f Pic.X + X, Pic.Y + Y: glCallList Pic.List
    End Sub

Function OpenTexture(Name As String, Optional Ext As String = "dds", _
        Optional ByVal Check As Boolean, Optional File As String) As Boolean
    If Name <> "Earth" Or EarthTexture = "" Then File = Name & "." & Ext _
    Else File = Name & "." & EarthTexture & "." & Ext
    If Not OpenFile(TexturesDir & File, , Check) Then File = TexturesDir & File _
    Else If OpenFile(File, , Check) Then File = "": Exit Function
    OpenTexture = True
    End Function

Sub LoadTexture(Name As String, Pl As Planet, Optional ByVal LoadRing As Boolean)
    Dim B As Long, C As Long, D As Long, N As Long, S As String, _
        X1 As Long, Y1 As Long, X2 As Long, Y2 As Long, X3 As Long, Y3 As Long, _
        W As Long, H As Long, W2 As Long, H2 As Long, Format As Long, _
        CodeD As String * 4, CodeB As String * 2, DDSd As DDSurfaceDesc, _
        Bh As BmpHeader, Bmi As BitmapInfo, Pix() As Byte, PixB() As Byte
    On Error Resume Next
    
    'If Not IsDLL Then GoTo NoDDS
    
    If Not OpenTexture(Name, "dds") Then GoTo NoDDS
    Get 1, , CodeD: If CodeD <> "DDS " Then Close: GoTo NoDDS
    Get 1, , DDSd: N = DDSd.dwLinearSize: If N < 1 Then Close: GoTo NoDDS
    W = DDSd.dwWidth: H = DDSd.dwHeight: If W < 1 Or H < 1 Then Close: GoTo NoDDS
    Select Case DDSd.ddpfPixelFormat.dwFourCC 'Ratio-Block4x4 DXT1=8:1-8bytes DXT3/5=4:1-16bytes
    Case "DXT1": Format = gl_Compressed_Rgba_S3TC_DXT1_Ext: B = 2
    Case "DXT3": Format = gl_Compressed_Rgba_S3TC_DXT3_Ext: B = 1
    Case "DXT5": Format = gl_Compressed_Rgba_S3TC_DXT5_Ext: B = 1
    Case Else: Close: GoTo NoDDS
        End Select
    ReDim PixB(N - 1): Get 1, , PixB
    Close
    With Pl.Texture
    .W = IfInt(Tiles < 1 Or Tiles > 4, 4, Tiles): .H = IfInt(.W <= 2, 1, .W \ 2)
    W2 = W \ .W: H2 = H \ .H
    C = W2 * H2 \ B: ReDim Pix(C - 1)
    ReDim .List(.W - 1, .H - 1)
    glPixelStorei gl_Unpack_Alignment, 1
    glGenTextures .W * .H, .List(0, 0)
    If .List(.W - 1, .H - 1) Then
        For Y2 = 0 To H - H2 Step H2: For X2 = 0 To W - W2 Step W2
            For Y1 = 0 To H2 - 4 Step 4
                RtlMoveMemory Pix(Y1 * W2 \ B), PixB(((Y2 + Y1) * W + X2 * 4) \ B), W2 * 4 \ B
                Next
            glBindTexture gl_Texture_2d, .List(X2 \ W2, Y2 \ H2)
            glTexParameteri gl_Texture_2d, gl_Texture_Min_Filter, gl_Linear 'gl_Linear gl_Nearest gl_Linear_Mipmap_Linear
            glTexParameteri gl_Texture_2d, gl_Texture_Mag_Filter, gl_Linear 'gl_Linear gl_Nearest
            glTexParameteri gl_Texture_2d, gl_Texture_Wrap_S, gl_Repeat 'gl_Repeat gl_Clamp
            glTexParameteri gl_Texture_2d, gl_Texture_Wrap_T, gl_Repeat 'gl_Repeat gl_Clamp
            glTexEnvi gl_Texture_Env, gl_Texture_Env_Mode, gl_Modulate 'gl_Modulate gl_Decal
            glCompressedTexImage2DARB gl_Texture_2d, 0, Format, W2, H2, 0, C, Pix(0)
            'glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Rgb, gl_Unsigned_Byte, Pix(0, 0, 0)
            'glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Compressed_Rgb_Arb, gl_Unsigned_Byte, Pix(0, 0, 0)
            Next: Next
        End If
    Erase Pix, PixB
    End With
    GoTo Ext
NoDDS:
    
    If Not OpenTexture(Name, "bmp") Then GoTo NoBMP
    Get 1, , CodeB: If CodeB <> "BM" Then Close: GoTo NoBMP
    Get 1, , Bh: If Bh.BitCount <> 24 Or Bh.Compression <> 0 Then Close: GoTo NoBMP
    W = Bh.Width: H = Bh.Height: If W < 1 Or H < 1 Then Close: GoTo NoBMP
    ReDim PixB(3 - 1, W - 1, H - 1): Get 1, , PixB
    Close
    With Pl.Texture
    .W = IfInt(Tiles >= 1, Tiles, 4): .H = .W \ 2: .H = IfInt(.H < 1, 1, .H)
    W2 = W \ .W: H2 = H \ .H
    ReDim Pix(3 - 1, W2 - 1, H2 - 1)
    ReDim .List(.W - 1, .H - 1)
    glPixelStorei gl_Unpack_Alignment, 1
    glGenTextures .W * .H, .List(0, 0)
    If .List(.W - 1, .H - 1) Then
        For Y2 = 0 To H - H2 Step H2: For X2 = 0 To W - W2 Step W2
            For Y1 = 0 To H2 - 1: For X1 = 0 To W2 - 1
                X3 = W - X2 - X1 - 1: Y3 = Y2 + Y1
                Pix(0, X1, Y1) = PixB(2, X3, Y3)
                Pix(1, X1, Y1) = PixB(1, X3, Y3)
                Pix(2, X1, Y1) = PixB(0, X3, Y3)
                Next: Next
            glBindTexture gl_Texture_2d, .List(X2 \ W2, Y2 \ H2)
            glTexParameteri gl_Texture_2d, gl_Texture_Min_Filter, gl_Linear 'gl_Linear gl_Nearest gl_Linear_Mipmap_Linear
            glTexParameteri gl_Texture_2d, gl_Texture_Mag_Filter, gl_Linear 'gl_Linear gl_Nearest
            glTexParameteri gl_Texture_2d, gl_Texture_Wrap_S, gl_Repeat 'gl_Repeat gl_Clamp
            glTexParameteri gl_Texture_2d, gl_Texture_Wrap_T, gl_Repeat 'gl_Repeat gl_Clamp
            glTexEnvi gl_Texture_Env, gl_Texture_Env_Mode, gl_Modulate 'gl_Modulate gl_Decal
            glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Rgb, gl_Unsigned_Byte, Pix(0, 0, 0)
            'glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Compressed_Rgb_Arb, gl_Unsigned_Byte, Pix(0, 0, 0)
            Next: Next
        End If
    Erase Pix, PixB
    End With
    GoTo Ext
NoBMP:
    
    If Not OpenTexture(Name, "jpg", True, S) Then GoTo NoJPG
    With Main.Pic
    Err = 0: .Picture = LoadPicture(S): If Err Then GoTo NoPic
    W = .Width: H = .Height: If W < 1 Or H < 1 Then GoTo NoPic
    
    D = GetCurrentObject(.hDC, 7): If D = 0 Then GoTo NoPic 'OBJ_BITMAP=7
    With Bmi.Bh
    .biSize = Len(Bmi.Bh): .biPlanes = 1: .biBitCount = 24: .biCompression = 0 'BI_RGB=0
    .biWidth = W: .biHeight = -H: .biSizeImage = .biWidth * .biHeight * .biBitCount \ 8
    End With
    ReDim PixB(3 - 1, W - 1, H - 1) ': Get 1, , PixB
    If GetDIBits(.hDC, D, 0, H, PixB(0, 0, 0), Bmi, 0) = 0 Then GoTo NoPic 'DIB_RGB_COLORS=0
    
    With Pl.Texture
    .W = IfInt(Tiles >= 1, Tiles, 4): .H = .W \ 2: .H = IfInt(.H < 1, 1, .H)
    W2 = W \ .W: H2 = H \ .H
    ReDim Pix(3 - 1, W2 - 1, H2 - 1)
    ReDim .List(.W - 1, .H - 1)
    glPixelStorei gl_Unpack_Alignment, 1
    glGenTextures .W * .H, .List(0, 0)
    If .List(.W - 1, .H - 1) Then
        For Y2 = 0 To H - H2 Step H2: For X2 = 0 To W - W2 Step W2
            For Y1 = 0 To H2 - 1: For X1 = 0 To W2 - 1
                X3 = W - X2 - X1 - 1: Y3 = H - Y2 - Y1 - 1
                Pix(0, X1, Y1) = PixB(2, X3, Y3)
                Pix(1, X1, Y1) = PixB(1, X3, Y3)
                Pix(2, X1, Y1) = PixB(0, X3, Y3)
                Next: Next
            glBindTexture gl_Texture_2d, .List(X2 \ W2, Y2 \ H2)
            glTexParameteri gl_Texture_2d, gl_Texture_Min_Filter, gl_Linear 'gl_Linear gl_Nearest gl_Linear_Mipmap_Linear
            glTexParameteri gl_Texture_2d, gl_Texture_Mag_Filter, gl_Linear 'gl_Linear gl_Nearest
            glTexParameteri gl_Texture_2d, gl_Texture_Wrap_S, gl_Repeat 'gl_Repeat gl_Clamp
            glTexParameteri gl_Texture_2d, gl_Texture_Wrap_T, gl_Repeat 'gl_Repeat gl_Clamp
            glTexEnvi gl_Texture_Env, gl_Texture_Env_Mode, gl_Modulate 'gl_Modulate gl_Decal
            glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Rgb, gl_Unsigned_Byte, Pix(0, 0, 0)
            'glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Compressed_Rgb_Arb, gl_Unsigned_Byte, Pix(0, 0, 0)
            Next: Next
        End If
    End With
NoPic:
    Erase Pix, PixB
    .Move 0, 0, 0, 0: Set .Picture = Nothing
    End With
    GoTo Ext
NoJPG:
    
Ext:
    If LoadRing Then LoadObjectTexture Name & ".Rings", Pl.Ring
    On Error GoTo 0
    End Sub

Function LoadObjectTexture(Name As String, List As Long) As Boolean
    Dim B As Long, C As Long, N As Long, _
        X1 As Long, Y1 As Long, X3 As Long, Y3 As Long, _
        W As Long, H As Long, _
        CodeB As String * 2, Bh As BmpHeader, _
        Pix() As Byte, PixB() As Byte
    On Error Resume Next
    OpenFile TexturesDir & Name & ".bmp": If FreeFile = 1 Then _
    OpenFile Name & ".bmp": If FreeFile = 1 Then GoTo NoBMP
    Get 1, , CodeB: If CodeB <> "BM" Then Close: GoTo NoBMP
    Get 1, , Bh: B = Bh.BitCount \ 8: W = Bh.Width: H = Bh.Height
    If (B <> 3 And B <> 4) Or W < 1 Or H < 1 Or Bh.Compression <> 0 Then Close: GoTo NoBMP
    ReDim PixB(B - 1, W - 1, H - 1): Get 1, , PixB
    Close
    ReDim Pix(B - 1, W - 1, H - 1)
    glPixelStorei gl_Unpack_Alignment, 1
    glGenTextures 1, List: If List = 0 Then GoTo NoList
    For Y1 = 0 To H - 1: For X1 = 0 To W - 1
        X3 = W - X1 - 1: Y3 = Y1
        Pix(0, X1, Y1) = PixB(2, X3, Y3)
        Pix(1, X1, Y1) = PixB(1, X3, Y3)
        Pix(2, X1, Y1) = PixB(0, X3, Y3)
        If B = 4 Then Pix(3, X1, Y1) = PixB(3, X3, Y3)
        Next: Next
    glBindTexture gl_Texture_2d, List
    glTexParameteri gl_Texture_2d, gl_Texture_Min_Filter, gl_Linear 'gl_Linear gl_Nearest gl_Linear_Mipmap_Linear
    glTexParameteri gl_Texture_2d, gl_Texture_Mag_Filter, gl_Linear 'gl_Linear gl_Nearest
    glTexParameteri gl_Texture_2d, gl_Texture_Wrap_S, gl_Repeat 'gl_Repeat gl_Clamp
    glTexParameteri gl_Texture_2d, gl_Texture_Wrap_T, gl_Repeat 'gl_Repeat gl_Clamp
    glTexEnvi gl_Texture_Env, gl_Texture_Env_Mode, gl_Modulate 'gl_Modulate gl_Decal
    If B = 3 Then glTexImage2D gl_Texture_2d, 0, B, W, H, 0, gl_Rgb, gl_Unsigned_Byte, Pix(0, 0, 0) _
    Else If B = 4 Then glTexImage2D gl_Texture_2d, 0, B, W, H, 0, gl_Rgba, gl_Unsigned_Byte, Pix(0, 0, 0)
    'glTexImage2D gl_Texture_2d, 0, 3, W, H, 0, gl_Compressed_Rgb_Arb, gl_Unsigned_Byte, Pix(0, 0, 0)
NoList:
    Erase Pix, PixB
    LoadObjectTexture = True
NoBMP:
    On Error GoTo 0
    End Function

Sub Text3D(ByVal Text As String, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal MoveX As Long = 7, Optional ByVal MoveY As Long = -3, _
        Optional ByVal List As Long, Optional ByVal SetPos As Long = -1)
    On Error Resume Next
    If SetPos Then glRasterPos3f X, Y, Z: If SetPos = 1 Then If Not IsVisible Then Exit Sub
    If List Then glCallList List
    If FontTextures Then
        If SetPos <> 1 Or (Tool And ToolPoints) <> 0 Then
            glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 = 0 Then Exit Sub
            glGetFloatv gl_Current_Raster_Position, RasterF.X
            End If
        glMatrixMode gl_Projection: glPushMatrix: glLoadMatrixf Matrix2D.XX
        glMatrixMode gl_Modelview: glPushMatrix: glLoadIdentity
        glTranslatef Int(RasterF.X + 0.5) + MoveX, _
            CamHeight - Int(RasterF.Y + 0.5) + MoveY - (TextFont.Y \ 2) - 1, 0: glPushMatrix
        glColor4ubv cWhite: glBindTexture gl_Texture_2d, TextFont.Tex: glEnable gl_Texture_2d
    Else
        glBitmap 1, 1, 0, 0, MoveX, -MoveY - (TextFont.Y \ 2), 0
        glPushAttrib gl_Current_Bit
        End If
    glCallList TextFont.List + 9 'vbTab
    glCallLists Len(Text), gl_Unsigned_Byte, Text
    glCallList TextFont.List + 8 'vbBack
    If FontTextures Then
        glDisable gl_Texture_2d
        glPopMatrix
        glMatrixMode gl_Projection: glPopMatrix
        glMatrixMode gl_Modelview: glPopMatrix
    Else
        glPopAttrib
        End If
    On Error GoTo 0
    End Sub

Sub TextFont3D(TextFont As FontX, ByVal Text As String, _
        Optional X As Double, Optional Y As Double, Optional Z As Double, _
        Optional ByVal MoveX As Long = 7, Optional ByVal MoveY As Long = -3, _
        Optional ByVal List As Long, Optional ByVal SetPos As Long = -1)
    On Error Resume Next
    If SetPos Then glRasterPos3f X, Y, Z: If SetPos = 1 Then If Not IsVisible Then Exit Sub
    If List Then glCallList List
    If FontTextures Then
        If SetPos <> 1 Or (Tool And ToolPoints) <> 0 Then
            glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 = 0 Then Exit Sub
            glGetFloatv gl_Current_Raster_Position, RasterF.X
            End If
        glMatrixMode gl_Projection: glPushMatrix: glLoadMatrixf Matrix2D.XX
        glMatrixMode gl_Modelview: glPushMatrix: glLoadIdentity
        glTranslatef Int(RasterF.X + 0.5) + MoveX, _
            CamHeight - Int(RasterF.Y + 0.5) + MoveY - (TextFont.Y \ 2) - 1, 0: glPushMatrix
        glColor4ubv cWhite: glBindTexture gl_Texture_2d, TextFont.Tex: glEnable gl_Texture_2d
    Else
        glBitmap 1, 1, 0, 0, MoveX, -MoveY - (TextFont.Y \ 2), 0
        glPushAttrib gl_Current_Bit
        End If
    glCallList TextFont.List + 9 'vbTab
    glCallLists Len(Text), gl_Unsigned_Byte, Text
    glCallList TextFont.List + 8 'vbBack
    If FontTextures Then
        glDisable gl_Texture_2d
        glPopMatrix
        glMatrixMode gl_Projection: glPopMatrix
        glMatrixMode gl_Modelview: glPopMatrix
    Else
        glPopAttrib
        End If
    On Error GoTo 0
    End Sub

Sub TextPlanet3D(Pl As Planet, Rt As Planet, Optional ByVal UseIcon As Boolean = True)
    Dim P As Boolean, T As Boolean, V As Boolean
    SelN = Pl.ID
    V = UseIcon And CamH > Pl.Radius * hPlanetsModel
    If ToolL And PlanetsLabels Then T = IsPlanetVisible(Pl) _
    Else If V Then glRasterPos3f Pl.X, Pl.Y, Pl.Z: P = IsVisible
    If SelectedType = TypePlanet And SelectedN = Pl.ID And (ToolX And ToolOrbits) <> 0 And _
            (Pl.ID = ToMoon Or CamH > hPlanetsOrbit) And Follow <> ToOnBoard Then _
        glEnable gl_Depth_Test: Orbit3D Pl, Rt, True: glDisable gl_Depth_Test
    If T Then Text3D Planets(Pl.ID), , , , , , IfInt(V, SmallGreenDot, 0), False _
    Else If P Then glCallList SmallGreenDot
    End Sub

Sub TextPlanetGhost3D(Pl As Planet, Rt As Planet, Optional ByVal UseIcon As Boolean = True)
    Dim X1 As Double, Y1 As Double, Z1 As Double
    SelN = Pl.ID
    PlanetXYZEx Pl, (Time + GhostTime) / Day, X1, Y1, Z1
    glRasterPos3f X1 + Rt.X, Y1 + Rt.Y, Z1 + Rt.Z
    If IsVisible Then Text3D Planets(Pl.ID) & "'", , , , , , IfInt(UseIcon, SmallGreenDot, 0), False
    End Sub

Function TextWidth(Text As String, F As FontX) As Long
    Dim N As Long
    For N = 1 To Len(Text): TextWidth = TextWidth + F.XX(Asc(Mid(Text, N, 1))): Next
    End Function

Sub ShowLogo(Optional ByVal Value As Long = 1)
    If Value = 1 Then Logo.Visible = Not Logo.Visible Else Logo.Visible = Value
    If Play = Paused Then Render
    End Sub

Sub MoveXY(Optional ByVal MoveX As Long, Optional ByVal MoveY As Long)
    glBitmap 1, 1, 0, 0, MoveX, -MoveY, 0
    End Sub

Function PointZ(Optional X As Double, Optional Y As Double, Optional Z As Double) As Single
    glRasterPos3f X, Y, Z: glGetFloatv gl_Current_Raster_Distance, PointZ
    End Function

Sub RasterPos(Pos As FloatXYZW, Optional ByVal Relative As Boolean = True)
    Dim C As Long, P As FloatXYZW
    glGetIntegerv gl_Current_Raster_Position_Valid, C
    If C = 0 Then Pos.X = 0: Pos.Y = 0: Pos.Z = 0: Pos.W = 0: Exit Sub
    If Not Relative Then Exit Sub
    glGetFloatv gl_Current_Raster_Position, Pos.X: _
    glPushMatrix: glPushAttrib gl_Current_Bit: glLoadIdentity: glRasterPos3f 0, 0, -1E+38
    glGetFloatv gl_Current_Raster_Position, P.X: _
    glPopAttrib: glPopMatrix
    Pos.X = Pos.X - P.X: Pos.Y = P.Y - Pos.Y
    End Sub

Function IsVisible() As Boolean
    If Tool And ToolPoints Then
        IsVisible = True: If IsSelect Then GetSelectedValid
    Else
        glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 = 0 Then Exit Function
        glGetFloatv gl_Current_Raster_Position, RasterF.X
        glReadPixels Int(RasterF.X), Int(RasterF.Y), 1, 1, gl_Depth_Component, gl_Float, F1
        If F1 - RasterF.Z > -0.0001 Then IsVisible = True: If IsSelect Then GetSelected
        End If
    End Function

Function IsPosVisible(Optional X As Double, Optional Y As Double, Optional Z As Double) As Boolean
    If Tool And ToolPoints Then IsPosVisible = True: Exit Function
    glRasterPos3f X, Y, Z
    glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 = 0 Then Exit Function
    glGetFloatv gl_Current_Raster_Position, RasterF.X
    glReadPixels Int(RasterF.X), Int(RasterF.Y), 1, 1, gl_Depth_Component, gl_Float, F1
    If F1 - RasterF.Z > -0.0001 Then IsPosVisible = True
    End Function

Function IsPlanetVisible(Pl As Planet) As Boolean
    If IsPosVisible(Pl.X + Pl.Radius, Pl.Y + Pl.Radius, Pl.Z + Pl.Radius) Or _
        IsPosVisible(Pl.X - Pl.Radius, Pl.Y - Pl.Radius, Pl.Z - Pl.Radius) Then _
            glRasterPos3f Pl.X, Pl.Y, Pl.Z: _
            glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 Then _
                glGetFloatv gl_Current_Raster_Position, RasterF.X: _
                IsPlanetVisible = True: If IsSelect Then GetSelected
    End Function

Function IsZoneVisible(Optional Radius As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double) As Boolean
    If IsPosVisible(X + Radius, Y + Radius, Z + Radius) Or _
        IsPosVisible(X - Radius, Y - Radius, Z - Radius) Then _
            glRasterPos3f X, Y, Z: _
            glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 Then _
                glGetFloatv gl_Current_Raster_Position, RasterF.X: _
                IsZoneVisible = True: If IsSelect Then GetSelected
    End Function

Sub GetSelectedValid()
    glGetIntegerv gl_Current_Raster_Position_Valid, C1: If C1 = 0 Then Exit Sub
    glGetFloatv gl_Current_Raster_Position, RasterF.X
    GetSelected
    End Sub

Sub GetSelected()
    If SelType <> TypeNone Then _
        SelW = RasterF.X - ClickX: If SelW > -SelectRange And SelW < SelectRange Then _
            SelH = RasterF.Y - ClickY: If SelH > -SelectRange And SelH < SelectRange Then _
                SelR = Sqr(SelW * SelW + SelH * SelH): If SelR <= SelDist Then _
                    SelDist = SelR: SelectedType = SelType: SelectedN = SelN
    End Sub

Sub ClearMultiSel()
    SelType1 = TypeNone: SelN1 = 0: SelType2 = TypeNone: SelN2 = 0: SelType3 = TypeNone: SelN3 = 0
    End Sub

Sub CheckSelected(X As Single, Y As Single, Cam As Camera, Optional Shift As Integer)
    
    If Cam.hRC = 0 Or Cam.Follow = ToOnBoard Then Exit Sub
    
    If SelectedType Then
        If IsInsidePic(X, Y, InfoWeb, 5) Then InfoBrowse: Exit Sub
        If IsInsidePic(X, Y, InfoList, 5) Then Search.Activate: Exit Sub
        End If
    If Shift <> 2 Then ClearMultiSel
    SelShift = Shift: LastSelType = SelectedType: LastSelN = SelectedN
    ClickX = X: ClickY = Cam.F.ScaleHeight - Y - 1
    SelectedType = TypeNone: SelectedN = 0: IsCursor = False
    SelType = TypeNone: SelN = 0: SelDist = InfiniteSng
    If IsLastTarget = ToObject Then IsLastTarget = ToNone
    
    Render Cam.N + (Cam.ID = MainID)
    
    If SelShift = 2 Then ClearMultiSel _
    Else If SelShift = 1 Then _
        If SelectedType = TypePlanet Then RelativeMode Cam, SelectedN _
        Else If SelectedType = TypeNone Then RelativeMode Cam, ToShip
    SelShift = 0
    'If SelectedType = TypeShip Then GoTo NoSelect
    If SelectedType Then
        If HideCursor Then Main.Cursor = False: Main.Cursor = True
        If LastType <> SelectedType Or LastN <> SelectedN Then _
            LastType = SelectedType: LastN = SelectedN: GoTo IsRender
    ElseIf Not IsObjectVisible(LastType, LastN) Then
NoSelect:
        SelectedType = TypeNone: SelectedN = 0
        LastType = TypeNone: LastN = 0
IsRender:
        If Play = Paused And (IsCamFollow Or IsCamLookAt) Then Render
        End If
    
    If Browser.Visible Then InfoBrowse
    
    End Sub

Sub InfoBrowse(Optional ByVal Toggle As Boolean)
    Dim B As Long, C As Long, N As Long, S As String, T As String, U As String
    Static X As Long
    If Toggle Then If Browser.Visible Then Browser.Activate: Exit Sub
    T = ObjectLink(SelectedType)
    Select Case SelectedType
    Case TypePlanet, TypeGhost
        U = T & InfoName(Planets(SelectedN))
    Case TypeMoon
        'U = T & InfoName(Moons(SelectedN).Name)
        U = Moons(SelectedN).Name: If Left(U, 2) <> "S/" Then U = U & "_(moon)"
        U = T & Replace(U, " ", "_")
    Case TypeComet
        '1P Halley                     .. 1P/Halley
        'P 2010 B2 (WISE)              .. P/2010_B2
        '73P Schwassmann-Wachmann 3-AD .. 73P/Schwassmann-Wachmann
        U = Comet(SelectedN).Name: B = InStr(U, " "): If B Then Mid(U, B, 1) = "/"
        If Right(U, 1) = ")" Then C = InStrRev(U, "("): If C >= 2 Then U = Left(U, C - 2)
        C = InStrRev(U, " "): If C Then S = Mid(U, C + 1, 1): _
            If S >= "0" And S <= "9" Then U = Left(U, C - 1)
        U = T & Replace(U, " ", "_")
    Case TypeAsteroid
        '1 Ceres                       .. 1_Ceres
        '148209 2000 CR105             .. (148209)_2000_CR105
        U = Asteroid(SelectedN).Name: B = InStr(U, " "): If B Then _
            C = InStr(B + 1, U, " "): If C - B = 5 Then N = Val(Mid(U, B + 1, 4)): _
                If N < 1000 Or N > 3000 Then N = 0
        If B > 0 And N > 0 Then U = "(" & Left(U, B - 1) & ")" & Mid(U, B)
        U = T & Replace(U, " ", "_")
    Case TypeStar
        U = Stars(SelectedN).Name: C = InStr(U, " ("): If C > 1 Then U = Left(U, C - 1)
        U = T & InfoName(U)
        'If Right(U, 1) = ")" Then C = InStrRev(U, "("): If C Then U = Mid(U, C + 1, Len(U) - C - 1)
        'S = Right(U, 2): If S = " A" Or S = " B" Or S = " C" Then U = Left(U, Len(U) - 2)
        'U = T & Replace(U, " ", "_")
    Case TypeSatellite
        If T <> "" Then _
            S = Satellite(SelectedN).Design: N = Val(Left(S, 2)) + 1900: _
            U = T & IfInt(N < 1957, N + 100, N) & "-" & RTrim(Mid(S, 3))
    Case TypeStation
        If T <> "" Then _
            With Station(SelectedN): U = T & NumX(.Lat / Rad) & "," & NumX(.Lon / Rad): End With
    Case TypeLocation
        If T <> "" Then _
            With Location(SelectedN): U = T & NumX(.Lat / Rad) & "," & NumX(.Lon / Rad): End With
    Case TypeAirspace
        If T <> "" Then _
            With Airspace(SelectedN): U = T & NumX(.Lat / Rad) & "," & NumX(.Lon / Rad): End With
    Case TypeAirport
        If T <> "" Then _
            With Airport(SelectedN): U = T & NumX(.Lat / Rad) & "," & NumX(.Lon / Rad): End With
    Case TypeSeaport
        If T <> "" Then _
            With Seaport(SelectedN): U = T & NumX(.Lat / Rad) & "," & NumX(.Lon / Rad): End With
    Case TypeCity
        If T <> "" Then _
            With City(SelectedN): U = T & NumX(.Lat / Rad) & "," & NumX(.Lon / Rad): End With
        End Select
    If U <> "" Then Browser.Explore U, True
    End Sub

Function IsObjectValid(Class As ObjectType, N As Long) As Boolean
    Select Case Class
    Case TypePlanet:    IsObjectValid = True
    Case TypeMoon:      If N >= 0 And N < MoonN Then IsObjectValid = True
    Case TypeComet:     If N >= 0 And N < CometN Then IsObjectValid = True
    Case TypeAsteroid:  If N >= 0 And N < AsteroidN Then IsObjectValid = True
    Case TypeStar:      If N >= 0 And N < StarsN Then IsObjectValid = True
    Case TypeSatellite: If N >= 0 And N <= SatelliteN Then IsObjectValid = True
    Case TypeStation:   If N >= 0 And N <= StationN Then IsObjectValid = True
    Case TypeLocation:  If N >= 0 And N < LocationN Then IsObjectValid = True
    Case TypeAirspace:  If N >= 0 And N < AirspaceN Then IsObjectValid = True
    Case TypeAirport:   If N >= 0 And N < AirportN Then IsObjectValid = True
    Case TypeSeaport:   If N >= 0 And N < SeaportN Then IsObjectValid = True
    Case TypeCity:      If N >= 0 And N < CityN Then IsObjectValid = True
    Case TypeGhost:     IsObjectValid = True
    Case TypeShip:      IsObjectValid = True
        End Select
    If Not IsObjectValid Then Class = TypeNone: N = 0
    End Function

Function IsObjectVisible(Class As ObjectType, N As Long) As Boolean
    If SelectedType Then If SelectedType = Class And SelectedN = N Then _
        IsObjectVisible = True: Exit Function
    Select Case Class
    Case TypePlanet:    If N < ToMercury Or (Tool And ToolPlanets) <> 0 Then IsObjectVisible = True
    Case TypeMoon:      If N >= 0 And N < MoonN And (Tool And ToolMoons) <> 0 Then _
                            If -Moons(N).Class <> ToPluto Or (Tool And ToolAsteroids) = 0 Then IsObjectVisible = True
    Case TypeComet:     If N >= 0 And N < CometN And (Tool And ToolComets) <> 0 Then IsObjectVisible = True
    Case TypeAsteroid:  If N >= 0 And N < AsteroidN And (Tool And ToolAsteroids) <> 0 Then _
                            If AsteroidP <= 0 Or Asteroid(N).Class = AsteroidP Then IsObjectVisible = True
    Case TypeStar:      If N >= 0 And N < StarsN And (ToolL And StarsLabels) <> 0 Then IsObjectVisible = True
    Case TypeSatellite: If N >= SatelliteFirst And N <= SatelliteLast And (Tool And ToolSatellites) <> 0 Then _
                            IsObjectVisible = True
    Case TypeStation:   If N >= StationFirst And N <= StationLast And (Tool And ToolStations) <> 0 Then _
                            IsObjectVisible = True
    Case TypeLocation:  If N >= 0 And N < LocationN And (Tool And ToolLocations) <> 0 And _
                            (Layers And LayersPlaces) <> 0 Then IsObjectVisible = True
    Case TypeAirspace:  If N >= 0 And N < AirspaceN And (Tool And ToolAirways) <> 0 And _
                            (Layers And LayersAirspaces) <> 0 Then IsObjectVisible = True
    Case TypeAirport:   If N >= 0 And N < AirportN And (Tool And ToolAirways) <> 0 And _
                            (Layers And LayersAirports) <> 0 Then IsObjectVisible = True
    Case TypeSeaport:   If N >= 0 And N < SeaportN And (Tool And ToolAirways) <> 0 And _
                            (Layers And LayersSeaports) <> 0 Then IsObjectVisible = True
    Case TypeCity:      If N >= 0 And N < CityN And (Tool And ToolLocations) <> 0 And _
                            (Layers And LayersCities) <> 0 Then IsObjectVisible = True
    Case TypeGhost:     If ToolX And ToolGhosts Then If N < ToMercury Or (Tool And ToolPlanets) <> 0 Then _
                            IsObjectVisible = True
    Case TypeShip:      IsObjectVisible = True
        End Select
    If Not IsObjectVisible Then Class = TypeNone: N = 0
    End Function

Function IsObjectTactic( _
        Optional Class As ObjectType = TypePlanet, Optional ByVal N As Long, _
        Optional P As TargetType) As Boolean
    Select Case Class
    Case TypeSatellite: P = ToEarth: IsObjectTactic = Satellite(N).S <= Earth.Diameter
    Case TypeStation, TypeAirspace, TypeAirport, TypeSeaport, TypeCity: P = ToEarth: IsObjectTactic = True
    Case TypeLocation: P = Location(N).Planet: IsObjectTactic = True
        End Select
    End Function

Function ObjectPositionFlat( _
        Optional Class As ObjectType = TypeLocation, Optional ByVal N As Long, _
        Optional X As Double, Optional Y As Double, Optional Z As Double) As Boolean
    Dim X2 As Double, Y2 As Double, Z2 As Double
    Select Case Class
    Case TypeSatellite: TLE2XYZ Earth, Satellite(N), Time + T, X2, Y2, Z2
                        XYZ2LLA Earth, Y, X, Z, X2, Y2, Z2
    Case TypeStation:   With Station(N):  X = .Lon: Y = .Lat: Z = .Alt: End With
    Case TypeLocation:  With Location(N): X = .Lon: Y = .Lat: Z = .Alt: End With
    Case TypeAirspace:  With Airspace(N): X = .Lon: Y = .Lat: Z = .Alt: End With
    Case TypeAirport:   With Airport(N):  X = .Lon: Y = .Lat: Z = .Alt: End With
    Case TypeSeaport:   With Seaport(N):  X = .Lon: Y = .Lat: Z = .Alt: End With
    Case TypeCity:      With City(N):     X = .Lon: Y = .Lat: Z = .Alt: End With
    Case Else:          X = 0: Y = 0: Z = 0: Exit Function
        End Select
    ObjectPositionFlat = True
    End Function

Function ObjectPosition( _
        Optional Class As ObjectType = TypePlanet, Optional ByVal N As Long, _
        Optional X As Double, Optional Y As Double, Optional Z As Double) As Boolean
    Select Case Class
    Case TypePlanet:    PlanetPosition N, X, Y, Z
    Case TypeMoon:      Asteroid2XYZ Moons(N), Time + T, X, Y, Z
    Case TypeComet:     Asteroid2XYZ Comet(N), Time + T, X, Y, Z
    Case TypeAsteroid:  Asteroid2XYZ Asteroid(N), Time + T, X, Y, Z
    Case TypeStar:      With Stars(N): X = Sun.X + .X: Y = Sun.Y + .Y: Z = Sun.Z + .Z: End With
    Case TypeSatellite: TLE2XYZ Earth, Satellite(N), Time + T, X, Y, Z
    Case TypeStation:   Station2XYZ Station(N), X, Y, Z
    Case TypeLocation:  Location2XYZ Location(N), X, Y, Z
    Case TypeAirspace:  With Airspace(N)
                        If (Layers And LayersAltitude) <> 0 Or (Tool And ToolTerrain) <> 0 Then _
                            LLA2XYZ Earth, CDbl(.Lat), CDbl(.Lon), CDbl(.Alt), X, Y, Z, , , True, True _
                        Else LLA2XYZ Earth, CDbl(.Lat), CDbl(.Lon), 0, X, Y, Z, , , True, True
                        End With
    Case TypeAirport:   LocationEarth2XYZ Airport(N), X, Y, Z
    Case TypeSeaport:   LocationEarth2XYZ Seaport(N), X, Y, Z
    Case TypeCity:      LocationEarth2XYZ City(N), X, Y, Z
    Case TypeGhost:     GhostPosition N, X, Y, Z
    Case Else:          X = 0: Y = 0: Z = 0: If Class <> TypeShip Then Exit Function
        End Select
    ObjectPosition = True
    End Function

Sub UpdateSelection()
    ClearMultiSel
    If SelectedType Then IsObjectValid SelectedType, SelectedN
    If LastType Then IsObjectValid LastType, LastN
    If Search.List.ListCount > 0 Then Search.Update
    End Sub

Sub SetObject(Optional ByVal Class As ObjectType = TypeNone, Optional ByVal N As Long, _
        Optional ByVal Update As Boolean = True, Optional ByVal Force As Boolean = True, _
        Optional ByVal UseMultiSel As Boolean)
    'If Show Then
    '    Select Case Class
    '    Case TypePlanet:    If (Tool And ToolPlanets) = 0 And N >= ToMercury Then SetTool ToolPlanets, True
    '    Case TypeMoon:      If (Tool And ToolMoons) = 0 Then SetTool ToolMoons, True
    '                        If (Tool And ToolPlanets) = 0 Then SetTool ToolPlanets, True
    '    Case TypeComet:     If (Tool And ToolComets) = 0 Then SetTool ToolComets, True
    '    Case TypeAsteroid:  If (Tool And ToolAsteroids) = 0 Then SetTool ToolAsteroids, True
    '                        If AsteroidP > 0 Then AsteroidP = Asteroid(N).Class: Options.UpdateOptions
    '    Case TypeStar:      If (ToolL And StarsLabels) = 0 Then SetToolL StarsLabels, True
    '    Case TypeSatellite: If (Tool And ToolSatellites) = 0 Then SetTool ToolSatellites, True
    '                        If SatelliteP > 0 Then SetSatelliteItem N: Options.UpdateOptions
    '    Case TypeStation:   If (Tool And ToolStations) = 0 Then SetTool ToolStations, True
    '                        If StationP > 0 Then SetStationItem N: Options.UpdateOptions
    '    Case TypeLocation:  If (Tool And ToolLocations) = 0 Then SetTool ToolLocations, True
    '                        If Location(N).Planet >= ToMercury Then SetTool ToolPlanets, True
    '    Case TypeAirspace:  If (Tool And ToolAirways) = 0 Then Layers = Layers Or LayersAirspaces: SetTool ToolAirways, True
    '    Case TypeAirport:   If (Tool And ToolAirways) = 0 Then Layers = Layers Or LayersAirports: SetTool ToolAirways, True
    '    Case TypeSeaport:   If (Tool And ToolAirways) = 0 Then Layers = Layers Or LayersSeaports: SetTool ToolAirways, True
    '    Case TypeCity:      If (Tool And ToolLocations) = 0 Then Layers = Layers Or LayersCities: SetTool ToolLocations, True
    '    Case TypeGhost:     If ToolX And ToolGhosts Then If (Tool And ToolPlanets) = 0 And N >= ToMercury Then SetTool ToolPlanets, True
    '        End Select
    '    If Not IsObjectVisible(Class, N) Then Exit Sub
    '    End If
    'If Follow Then If Cam1.Follow <> ToObject Then Cam1.Follow = ToObject
    If Force Or SelectedType <> Class Or SelectedN <> N Then _
        SelectedType = Class: SelectedN = N Else Exit Sub
    If SelectedType Then LastType = SelectedType: LastN = SelectedN
    If UseMultiSel Then
        If SelType3 Then
            If (SelType1 <> SelectedType Or SelN1 <> SelectedN) And (SelType2 <> SelectedType Or SelN2 <> SelectedN) Then _
                SelType3 = SelectedType: SelN3 = SelectedN
        ElseIf SelType2 Then
            If SelType1 <> SelectedType Or SelN1 <> SelectedN Then _
                SelType2 = SelectedType: SelN2 = SelectedN
            End If
    ElseIf SelType1 Then
        ClearMultiSel
        End If
    If Update Then
        If Play = Paused Then Render
        If Browser.Visible Then InfoBrowse
        End If
    End Sub

Function GetN(Optional ByVal Class As ObjectType = TypeMoon, Optional Name As String) As Long
    If Name = "" Then GetN = -1: Exit Function
    Select Case Class
    Case TypePlanet
        For GetN = ToEarth To ToPluto
            If Name = Planets(GetN) Then Exit Function
            Next
    Case TypeMoon
        For GetN = 0 To MoonN - 1
            If Name = Moons(GetN).Name Then Exit Function
            Next
    Case TypeComet
        For GetN = 0 To CometN - 1
            If Name = Comet(GetN).Name Then Exit Function
            Next
    Case TypeAsteroid
        For GetN = 0 To AsteroidN - 1
            If Name = Asteroid(GetN).Name Then Exit Function
            Next
    Case TypeStar
        For GetN = 0 To StarsN - 1
            If Name = Stars(GetN).Name Then Exit Function
            Next
    Case TypeSatellite
        For GetN = 0 To SatelliteN - 1
            If Name = Satellite(GetN).Name Then Exit Function
            Next
    Case TypeStation
        For GetN = 0 To StationN - 1
            If Name = Station(GetN).Name Then Exit Function
            Next
    Case TypeLocation
        For GetN = 0 To LocationN - 1
            If Name = Location(GetN).Name Then Exit Function
            Next
    Case TypeAirspace
        For GetN = 0 To AirspaceN - 1
            If Name = Airspace(GetN).Name Then Exit Function
            Next
    Case TypeAirport
        For GetN = 0 To AirportN - 1
            If Name = Airport(GetN).Name Then Exit Function
            Next
    Case TypeSeaport
        For GetN = 0 To SeaportN - 1
            If Name = Seaport(GetN).Name Then Exit Function
            Next
    Case TypeCity
        For GetN = 0 To CityN - 1
            If Name = City(GetN).Name Then Exit Function
            Next
    Case TypeGhost
        For GetN = ToEarth To ToPluto
            If Name = Planets(GetN) & "'" Then Exit Function
            Next
    Case TypeShip
        GetN = 0
        End Select
    GetN = -1
    End Function

Function GetClass(Optional Name As String, Optional N As Long = -1) As Long
    If Name = "" Then N = -1: GetClass = TypeNone _
    Else N = GetN(TypePlanet, Name): If N >= 0 Then GetClass = TypePlanet _
    Else N = GetN(TypeMoon, Name): If N >= 0 Then GetClass = TypeMoon _
    Else N = GetN(TypeComet, Name): If N >= 0 Then GetClass = TypeComet _
    Else N = GetN(TypeAsteroid, Name): If N >= 0 Then GetClass = TypeAsteroid _
    Else N = GetN(TypeStar, Name): If N >= 0 Then GetClass = TypeStar _
    Else N = GetN(TypeSatellite, Name): If N >= 0 Then GetClass = TypeSatellite _
    Else N = GetN(TypeStation, Name): If N >= 0 Then GetClass = TypeStation _
    Else N = GetN(TypeLocation, Name): If N >= 0 Then GetClass = TypeLocation _
    Else N = GetN(TypeAirspace, Name): If N >= 0 Then GetClass = TypeAirspace _
    Else N = GetN(TypeAirport, Name): If N >= 0 Then GetClass = TypeAirport _
    Else N = GetN(TypeSeaport, Name): If N >= 0 Then GetClass = TypeSeaport _
    Else N = GetN(TypeCity, Name): If N >= 0 Then GetClass = TypeCity _
    Else N = GetN(TypeGhost, Name): If N >= 0 Then GetClass = TypeGhost _
    Else N = -1: GetClass = TypeNone
    End Function

Function GetTargetClass(Optional Name As String, Optional N As Long = -1) As Long
    If Name = "" Then N = -1: GetTargetClass = TypeNone _
    Else N = GetN(TypeStation, Name): If N >= 0 Then GetTargetClass = TypeStation _
    Else N = GetN(TypeLocation, Name): If N >= 0 Then GetTargetClass = TypeLocation _
    Else N = -1: GetTargetClass = TypeNone
    End Function

Function GetName(Optional ByVal Class As ObjectType = TypeMoon, Optional ByVal N As Long) As String
    Select Case Class
    Case TypePlanet:    GetName = Planets(N)
    Case TypeAsteroid:  GetName = Asteroid(N).Name
    Case TypeComet:     GetName = Comet(N).Name
    Case TypeMoon:      GetName = Moons(N).Name
    Case TypeStar:      GetName = Stars(N).Name
    Case TypeSatellite: GetName = Satellite(N).Name
    Case TypeStation:   GetName = Station(N).Name
    Case TypeLocation:  GetName = Location(N).Name
    Case TypeAirspace:  GetName = Airspace(N).Name
    Case TypeAirport:   GetName = Airport(N).Name
    Case TypeSeaport:   GetName = Seaport(N).Name
    Case TypeCity:      GetName = City(N).Name
    Case TypeGhost:     GetName = Planets(N) & "'"
    Case TypeShip:      GetName = "Spacecraft"
        End Select
    End Function

Sub ClearGravity(Optional ByVal Add As Boolean = True)
    If Not Add Then GravityField = GravityField And Not &HF&
    End Sub

Sub SetGravity(Optional ByVal X As Long = -1)
    If X <> -1 Then GravityField = X
    DeleteList Earth.Atm: DeleteList Earth.AtmFlat
    If GravityField And &H60& Then DeleteList Earth.Field
    If Play = Paused Then Render
    End Sub

Sub SetSettings(Optional ByVal N As Long)
    IsAdaptive = CBool(N And 1): IsDrag = CBool(N And 2)
    IsLift = CBool(N And 4): IsThrustSL = CBool(N And 8)
    IsSolarWind = CBool(N And &H10&): IsSunShadow = CBool(N And &H20&)
    IsCollision = CBool(N And &H40&): IsLaunchTrajectory = CBool(N And &H80&)
    IsSatelliteControl = CBool(N And &H100&): IsStatistics = CBool(N And &H200&)
    IsBalloonTrajectory = CBool(N And &H400&): IsEmptyStages = CBool(N And &H800&)
    IsRealTime = CBool(N And &H1000&): IsSatelliteProximity = CBool(N And &H2000&)
    IsStationVisibility = CBool(N And &H4000&): IsAsteroidProximity = CBool(N And &H8000&)
    IsWind = CBool(N And &H10000): IsAirspaceDetection = CBool(N And &H20000)
    IsHiDefDetection = CBool(N And &H40000): IsLandingManeuver = CBool(N And &H80000)
    IsAreaDetection = CBool(N And &H100000)
    End Sub

Function GetSettings() As Long
    GetSettings = CLng(- _
        CBool(IsAdaptive) - CBool(IsDrag) * 2 - _
        CBool(IsLift) * 4 - CBool(IsThrustSL) * 8 - _
        CBool(IsSolarWind) * &H10& - CBool(IsSunShadow) * &H20& - _
        CBool(IsCollision) * &H40& - CBool(IsLaunchTrajectory) * &H80& - _
        CBool(IsSatelliteControl) * &H100& - CBool(IsStatistics) * &H200& - _
        CBool(IsBalloonTrajectory) * &H400& - CBool(IsEmptyStages) * &H800& - _
        CBool(IsRealTime) * &H1000& - CBool(IsSatelliteProximity) * &H2000& - _
        CBool(IsStationVisibility) * &H4000& - CBool(IsAsteroidProximity) * &H8000& - _
        CBool(IsWind) * &H10000 - CBool(IsAirspaceDetection) * &H20000 - _
        CBool(IsHiDefDetection) * &H40000 - CBool(IsLandingManeuver) * &H80000 - _
        CBool(IsAreaDetection) * &H100000 _
        )
    End Function

Sub SetSimulation(ByVal X As Boolean, Optional ByVal Update As Boolean = True)
    IsAdaptive = X: IsDrag = X: IsLift = X: IsThrustSL = X: IsSolarWind = X
    IsSunShadow = X: IsCollision = X: IsWind = X: IsLaunchTrajectory = X: IsSatelliteControl = X
    IsBalloonTrajectory = X: IsEmptyStages = X: IsRealTime = X: IsStationVisibility = X
    IsSatelliteProximity = X: IsAsteroidProximity = X: IsAirspaceDetection = X: IsAreaDetection = X
    IsHiDefDetection = X: IsLandingManeuver = X: IsStatistics = X
    If X Then
        If GravityMode < 4 Then If GravityMode <> 4 Then PlanetPosAll: GravityMode = 4: Stats
        If TerrainMode < 2 Then SetTerrainMode 2, Update
        If AtmMode = 0 Then SetAtmMode 3, , Update
        If AirspaceN = 0 Then SetAirspaceDetection True
    Else
        If GravityMode > 0 Then GravityMode = 0
        If TerrainMode > 0 Then SetTerrainMode , Update
        If AtmMode > 0 Then SetAtmMode , , Update
        If AirspaceN > 0 Then SetAirspaceDetection
        End If
    If Update Then Options.UpdateSimulation
    End Sub

Function GetSimulation() As Boolean
    GetSimulation = GravityMode <> 0 Or TerrainMode <> 0 Or AtmMode <> 0 Or _
        IsAdaptive Or IsDrag Or IsLift Or IsThrustSL Or IsSolarWind Or _
        IsSunShadow Or IsCollision Or IsWind Or IsLaunchTrajectory Or IsSatelliteControl Or _
        IsBalloonTrajectory Or IsEmptyStages Or IsRealTime Or IsStationVisibility Or _
        IsSatelliteProximity Or IsAsteroidProximity Or IsAirspaceDetection Or IsAreaDetection Or _
        IsHiDefDetection Or IsLandingManeuver Or IsStatistics
    End Function

Sub SetTool(Optional ByVal X As Tools, Optional ByVal Activate As Long = 1)
    Static IsBusy As Boolean
    
    If IsBusy Then If (X And ToolSatellites) <> 0 Or (X And ToolAsteroids) <> 0 Then Exit Sub
    If X = 0 Then _
        If Activate = -1 Then Activate = 1: _
            Tool = ToolDefault: ToolX = ToolDefaultX: ToolL = LabelDefault _
        Else Activate = 1: _
            Tool = Tool And ToolNone: ToolX = ToolX And Not ToolNoneX: ToolL = LabelNone _
    Else If Activate = 1 Then Tool = Tool Xor X _
    Else If Activate = 0 Then Tool = Tool And Not X _
    Else If Activate = -1 Then Tool = Tool Or X
    
    If X And ToolTextures Then DeleteTextures: LoadTextures
    
    If X And ToolTerrain Then
        If Tool And ToolTerrain Then
            If TerrainMode < 2 Then If Not LoadTerrain Then _
                Tool = Tool And Not ToolTerrain: Options.UpdateTools Else TerrainMode = 2: GoTo Nxt
        ElseIf TerrainMode = 2 Then
            TerrainMode = 0: DeleteTerrain
Nxt:
            Options.UpdateSimulation: If Cam1.hRC Then DeletePlanetLists
            If T = 0 Then Init
            End If
        End If
    
    If X And ToolSatellites Then
        If (Tool And ToolSatellites) <> 0 And SatelliteN = 0 Then
            LoadSatellites
            If (Tool And ToolSatellites) <> 0 And SatelliteN = 0 Then
                If MsgBox("No satellite database found. Download it from server?", _
                    vbQuestion Or vbOKCancel, AppName) = vbOK Then _
                    IsBusy = True: DownloadSatellites: IsBusy = False
                If SatelliteN = 0 Then Tool = Tool And Not ToolSatellites: Options.UpdateTools
                End If
            End If
        IsSatelliteProximity = (Tool And ToolSatellites) <> 0: Options.UpdateSimulation
        End If
    
    If X And ToolAsteroids Then
        If (Tool And ToolAsteroids) <> 0 And AsteroidN = 0 Then
            LoadAsteroids
            If (Tool And ToolAsteroids) <> 0 And AsteroidN = 0 Then
                If MsgBox("No asteroid database found. Download it from server?", _
                    vbQuestion Or vbOKCancel, AppName) = vbOK Then _
                    IsBusy = True: DownloadAsteroids: IsBusy = False
                If AsteroidN = 0 Then Tool = Tool And Not ToolAsteroids: Options.UpdateTools
                End If
            End If
        End If
    
    If X And ToolAtmosphere Then
        If (Tool And ToolAtmosphere) <> 0 And AtmN = 0 Then
            GRIBLoad
            If AtmN = 0 Then
                If MsgBox("No atmospheric model found. Download it from server?", _
                    vbQuestion Or vbOKCancel, AppName) = vbOK Then _
                    IsBusy = True: DownloadAtmosphere: IsBusy = False
                If AtmN = 0 Then Tool = Tool And Not ToolAtmosphere: Options.UpdateTools
                End If
            End If
        If (Tool And ToolAtmosphere) = 0 And AtmN > 0 And AtmMode = 0 Then GRIBDelete
        End If
    
    If X And ToolMinorPlanets Then _
        IsAsteroidProximity = (Tool And ToolMinorPlanets) <> 0: Options.UpdateSimulation
    
    If X And ToolStations Then _
        IsStationVisibility = (Tool And ToolStations) <> 0: Options.UpdateSimulation
    
    If X And ToolAirways Then
        If Layers And LayersAirspaces Then
            If Tool And ToolAirways Then
                IsAirspaceDetection = True: If AirspaceN = 0 Then LoadAirspaces: _
                    If AirspaceN > 0 Then Plot , 1 _
                    Else Tool = Tool And Not ToolAirways: Options.UpdateTools
            Else
                IsAirspaceDetection = False: If AirspaceN > 0 Then DeleteAirspaces: Plot , 1
                End If
            Options.UpdateSimulation
            End If
        End If
    
    RenderModels
    RenderGraphs
    If Play = Paused Then Stats: Render: Info
    If Activate = 1 Then
        If Tool And ToolOptions Then Options.UpdateScreen
        If (Tool And Not ToolOptions) <> 0 Or X = 0 Then Options.UpdateTools
        End If
    
    End Sub

Sub SetToolX(Optional ByVal X As ToolsX, Optional ByVal Activate As Long = 1)
    If Activate = 1 Then ToolX = ToolX Xor X _
    Else If Activate = 0 Then ToolX = ToolX And Not X _
    Else ToolX = ToolX Or X
    If X And ToolBoundaries Then If Layers And LayersZones Then _
        IsAreaDetection = (ToolX And ToolBoundaries) <> 0: Options.UpdateSimulation
    If Play = Paused Then Render
    If Activate = 1 Then Options.UpdateTools
    End Sub

Sub SetToolL(Optional ByVal X As ToolsL, Optional ByVal Activate As Long = 1)
    If X = 0 Then _
        If Activate = -1 Then Activate = 1: ToolL = LabelDefault _
        Else Activate = 1: ToolL = LabelNone _
    Else If Activate = 1 Then ToolL = ToolL Xor X _
    Else If Activate = 0 Then ToolL = ToolL And Not X Else ToolL = ToolL Or X
    If Play = Paused Then Render
    If Activate = 1 Then Options.UpdateTools
    End Sub

Sub SetDataTool(Optional ByVal X As ToolsD, Optional ByVal X2 As ToolsD, _
        Optional ByVal Activate As Long = 1)
    If X = 0 And X2 = 0 Then _
        If Activate = -1 Then Activate = 1: DataTool = DataAll: DataToolX = DataAllX _
        Else If Activate = 0 Then DataTool = DataNone: DataToolX = DataNoneX _
        Else Activate = 1: DataTool = DataDefault: DataToolX = DataDefaultX _
    Else If Activate = 1 Then DataTool = DataTool Xor X: DataToolX = DataToolX Xor X2 _
    Else If Activate = 0 Then DataTool = DataTool And Not X: DataToolX = DataToolX And Not X2 _
    Else DataTool = DataTool Or X: DataToolX = DataToolX Or X2
    If DataMode = 2 Then UpdateData
    End Sub

Sub SetGraphTool(Cam As Camera, _
        Optional ByVal X As GraphOptions, Optional ByVal Activate As Long = 1, _
        Optional ByVal RefreshList As Boolean, Optional ByVal Initialize As Boolean)
    With Graph2D(Cam.N)
    If X = 0 Then
        If Activate = True Then Cam.Ortho = True: .Tools = GraphDefault _
        Else If Activate = False Then Cam.Ortho = True: .Tools = GraphNone
    Else
        If Activate = 1 Then .Tools = .Tools Xor X _
        Else If Activate = 0 Then .Tools = .Tools And Not X _
        Else .Tools = .Tools Or X
        End If
    End With
    If Initialize Then InitGraph Cam, Graph2D(Cam.N), False
    If RefreshList Then DeleteList Graph2D(Cam.N).List, 2
    InitCamera Cam: RenderModel Cam
    End Sub

Sub SetGraphColor(Cam As Camera, _
        Optional ByVal FaceColor As Long = -1, Optional ByVal LineColor As Long = -1)
    With Graph2D(Cam.N)
    If FaceColor >= 0 Then _
        .Tools = (.Tools And Not GraphFaceMask) Or ((FaceColor And &HF&) * GraphFaceShift)
    If LineColor >= 0 Then _
        .Tools = (.Tools And Not GraphLineMask) Or ((LineColor And &HF&) * GraphLineShift)
    DeleteList .List, 2
    End With
    RenderModel Cam
    End Sub

Sub SetLayersMode(Optional ByVal Mask As LayersType, Optional ByVal Activate As Boolean, _
        Optional ByVal Update As Boolean = True)
    If Mask Then SetMask Layers, Activate, Mask
    If Not Update Then Exit Sub
    If Mask And LayersAltitude Then DeleteList AirspacesList: DeleteList AirspacesFlatList
    If Mask And LayersDefinition Then DeleteLand: LoadLand
    If (Tool And ToolAirways) <> 0 And (Layers And LayersAirspaces) <> 0 Then
        IsAirspaceDetection = True: Options.UpdateSimulation
        If AirspaceN = 0 Then LoadAirspaces: If AirspaceN > 0 Then Plot , 1
    ElseIf (Layers And LayersAirspaces) = 0 Then
        IsAirspaceDetection = False: Options.UpdateSimulation
        DeleteList AirspacesList: DeleteList AirspacesFlatList
        End If
    If (ToolX And ToolBoundaries) <> 0 And (Layers And LayersZones) <> 0 Then
        IsAreaDetection = True: Options.UpdateSimulation
    ElseIf (Layers And LayersZones) = 0 Then
        IsAreaDetection = False: Options.UpdateSimulation
        End If
    If Play = Paused Then Render
    End Sub

Sub SetAtmStyle(Optional ByVal Mask As LayersType, Optional ByVal Activate As Long = 1, _
        Optional ByVal Update As Boolean = True)
    If Mask Then If Activate = 1 Then AtmStyle = AtmStyle Xor Mask _
        Else If Activate Then AtmStyle = AtmStyle Or Mask _
        Else AtmStyle = AtmStyle And Not Mask
    If Update Then DeleteList Earth.Atm: DeleteList Earth.AtmFlat: If Play = Paused Then Render
    End Sub

Sub CreateFont(F As FontX, Optional Pic As Object)
    Dim N As Long, X As Long, Y As Long, W As Long, H As Long, _
        D As Long, M As Long, O As Long, W2 As Long, H2 As Long, Y2 As Long, _
        CR As Long, CG As Long, CB As Long, CA As Long, _
        BR As Long, BG As Long, BB As Long, BA As Long, _
        Pix() As Byte, Pix2() As Byte, Bmi As BitmapInfo
    
    With Main.Fnt
    If Pic Is Nothing Then Set Pic = Main
    Set .Font = Pic.Font
    .ForeColor = vbWhite: .BackColor = vbBlack
    .Move 0, 0, 0, 0: .CurrentY = 0: Main.Fnt.Print: H = .CurrentY: H2 = H * 256
    F.XX(8) = 1: F.XX(9) = 2: For N = 32 To 256 - 1
        .CurrentX = 0: Main.Fnt.Print Chr(N);
        X = .CurrentX: F.XX(N) = X: If W < X Then W = X
        Next
    W = Int((W + 3) / 4) * 4: .Move 0, 0, W, H2: .Cls
    For N = 0 To 32 - 1: Main.Fnt.Print: Next
    For N = 32 To 256 - 1: Main.Fnt.Print Chr(N): Next
    
    D = GetCurrentObject(.hDC, 7): If D = 0 Then GoTo Ext 'OBJ_BITMAP=7
    'GetObject D, Len(Bm), Bm: W = Bm.bmWidth: H = Bm.bmHeight
    With Bmi.Bh
    .biSize = Len(Bmi.Bh): .biPlanes = 1: .biBitCount = 24: .biCompression = 0 'BI_RGB=0
    .biWidth = W: .biHeight = -H2: .biSizeImage = .biWidth * .biHeight * .biBitCount \ 8
    End With
    ReDim Pix(3 - 1, W - 1, H - 1, 256 - 1): ReDim Pix2(4 - 1, W - 1, H - 1, 256 - 1)
    If GetDIBits(.hDC, D, 0, H2, Pix(0, 0, 0, 0), Bmi, 0) = 0 Then GoTo Ext 'DIB_RGB_COLORS=0
    End With
    
    With F
    F.Color = SystemColor(Pic.ForeColor) Or &HFF000000
    GetRGBA CR, CG, CB, CA, F.Color: GetRGBA BR, BG, BB, BA, F.BackColor
    For N = 0 To 256 - 1: W2 = .XX(N)
        For Y = 0 To H - 1: Y2 = H - Y - 1
            For X = 0 To W2 - 1
                M = (CLng(Pix(0, X, Y, N)) + CLng(Pix(1, X, Y, N)) + CLng(Pix(2, X, Y, N))) \ 3
                O = 255 - M
                Pix2(0, X, Y2, N) = (CR * M + BR * O) \ 255
                Pix2(1, X, Y2, N) = (CG * M + BG * O) \ 255
                Pix2(2, X, Y2, N) = (CB * M + BB * O) \ 255
                Pix2(3, X, Y2, N) = (CA * M + BA * O) \ 255
                Next
            Next
        Next
    
    .X = W: .Y = H: .List = glGenLists(256): If .List = 0 Then GoTo Ext
    If FontTextures Then
        glPixelStorei gl_Unpack_Alignment, 1
        glGenTextures 1, .Tex: If .Tex = 0 Then GoTo Ext
        glBindTexture gl_Texture_2d, .Tex
        glTexParameteri gl_Texture_2d, gl_Texture_Min_Filter, gl_Linear 'gl_Linear gl_Nearest gl_Linear_Mipmap_Linear
        glTexParameteri gl_Texture_2d, gl_Texture_Mag_Filter, gl_Linear 'gl_Linear gl_Nearest
        glTexParameteri gl_Texture_2d, gl_Texture_Wrap_S, gl_Repeat 'gl_Repeat gl_Clamp
        glTexParameteri gl_Texture_2d, gl_Texture_Wrap_T, gl_Repeat 'gl_Repeat gl_Clamp
        glTexEnvi gl_Texture_Env, gl_Texture_Env_Mode, gl_Modulate 'gl_Modulate gl_Decal
        glTexImage2D gl_Texture_2d, 0, 4, W, H * 256, 0, gl_Rgba, gl_Unsigned_Byte, Pix2(0, 0, 0, 0)
        'glTexImage2D gl_Texture_2d, 0, 4, W, H * 256, 0, gl_Compressed_Rgba_Arb, gl_Unsigned_Byte, Pix2(0, 0, 0, 0)
        End If
    
    For N = 0 To 256 - 1
        glNewList .List + N, gl_Compile
        X = .XX(N)
        If FontTextures Then
            If N = 13 Then
                glPopMatrix
                glTranslatef 0, H, 0
                glPushMatrix
            ElseIf X Then
                'glBindTexture gl_Texture_2d, .Tex
                glBegin gl_Quads
                'glNormal3f X, Y, Z
                glTexCoord2f 0, N / 256:           glVertex2f 0, H
                glTexCoord2f X / W, N / 256:       glVertex2f X, H
                glTexCoord2f X / W, (N + 1) / 256: glVertex2f X, 0
                glTexCoord2f 0, (N + 1) / 256:     glVertex2f 0, 0
                glEnd
                glTranslatef X, 0, 0
                End If
        Else
            glDrawPixels W, H, gl_Rgba, gl_Unsigned_Byte, Pix2(0, 0, 0, N)
            glBitmap 1, 1, 0, 0, .XX(N), 0, 0
            If N = 13 Then glPopAttrib: glBitmap 1, 1, 0, 0, 0, -H, 0: glPushAttrib gl_Current_Bit
            End If
        glEndList
        Next
    
    End With
Ext:
    Erase Pix, Pix2
    End Sub

Sub DeleteFont(F As FontX)
    DeleteList F.List, 256
    DeleteTexture F.Tex
    End Sub

Sub CreateFonts()
    Message "Loading fonts …": DoEvents
    CreateFont TextFont
    Message
    End Sub

Sub DeleteFonts()
    DeleteFont TextFont
    DeleteFont ReportFont
    DeleteFont ModelFont
    End Sub

Sub UpdateFont(F As FontX, Optional Pic As Object)
    DeleteFont F: CreateFont F, Pic
    End Sub

Sub UpdateTextFonts( _
        Optional ByVal Update As Boolean = True, Optional ByVal ShowMsg As Boolean = True)
    If Cam1.hRC = 0 Then Exit Sub
    If ShowMsg Then Message "Loading fonts …": DoEvents
    DeleteFont ModelFont: DeleteFont ReportFont: UpdateFont TextFont
    If Update Then RenderModels: RenderGraphs: If Play = Paused Then Render
    If ShowMsg Then Message
    End Sub

Function CreateIcon(P As Long, _
        Optional ByVal W As Long, Optional ByVal H As Long, _
        Optional ByVal X As Long, Optional ByVal Y As Long _
        ) As Long
    CreateIcon = glGenLists
    If CreateIcon Then glNewList CreateIcon, gl_Compile Else Exit Function
    glBitmap 1, 1, 0, 0, X, Y, 0: glDrawPixels W, H, gl_Rgba, gl_Unsigned_Byte, P
    glEndList
    End Function

Function CreateBigDot(Optional ByVal C As Long, _
        Optional ByVal X As Long = -3, Optional ByVal Y As Long = -2) As Long
    Dim A As Long, B As Long, P() As Long: ReDim P(5 - 1, 5 - 1)
    A = C: B = &HA0000000 Or C: C = &HFF000000 Or C
    P(0, 0) = A: P(1, 0) = B: P(2, 0) = C: P(3, 0) = B: P(4, 0) = A
    P(0, 1) = B: P(1, 1) = C: P(2, 1) = C: P(3, 1) = C: P(4, 1) = B
    P(0, 2) = C: P(1, 2) = C: P(2, 2) = C: P(3, 2) = C: P(4, 2) = C
    P(0, 3) = B: P(1, 3) = C: P(2, 3) = C: P(3, 3) = C: P(4, 3) = B
    P(0, 4) = A: P(1, 4) = B: P(2, 4) = C: P(3, 4) = B: P(4, 4) = A
    CreateBigDot = CreateIcon(P(0, 0), 5, 5, X, Y)
    End Function

Function CreateSmallDot(Optional ByVal C As Long = -1, _
        Optional ByVal X As Long = -2, Optional ByVal Y As Long = -1) As Long
    Dim B As Long, P() As Long: ReDim P(3 - 1, 3 - 1)
    B = &HC0000000 Or C: C = &HFF000000 Or C
    P(0, 0) = B: P(1, 0) = C: P(2, 0) = B
    P(0, 1) = C: P(1, 1) = C: P(2, 1) = C
    P(0, 2) = B: P(1, 2) = C: P(2, 2) = B
    CreateSmallDot = CreateIcon(P(0, 0), 3, 3, X, Y)
    End Function

Sub CreateIcons()
    If Cam1.hRC = 0 Then Exit Sub
    WhiteDot = CreateBigDot(cWhiteDot)             'Big White Dot
    GreenDot = CreateBigDot(cGreenDot)             'Big Green Dot
    RedDot = CreateBigDot(cRedDot)                 'Big Red Dot
    SmallGreenDot = CreateSmallDot(cSmallGreenDot) 'Small Green Dot
    SmallRedDot = CreateSmallDot(cSmallRedDot)     'Small Red Dot
    End Sub

Sub DeleteIcons()
    If Cam1.hRC = 0 Then Exit Sub
    DeleteList WhiteDot      'Big White Dot
    DeleteList GreenDot      'Big Green Dot
    DeleteList RedDot        'Big Red Dot
    DeleteList SmallGreenDot 'Small Green Dot
    DeleteList SmallRedDot   'Small Red Dot
    End Sub

Sub UpdateIcons(Optional ByVal Update As Boolean = True)
    DeleteIcons
    CreateIcons
    If Update Then If Play = Paused Then Render
    End Sub

Sub LoadImages()
    Message "Loading images …": DoEvents
    LoadImage "Logo", Logo
    LoadImage "Flare", Flare, , True, True
    LoadImage "Crosshair", Crosshair, , True, &H80&
    LoadImage "Info", InfoWeb
    LoadImage "List", InfoList
    LoadImage "Cursor", Cursor, , True ', &HA0&
    Message
    End Sub

Sub DeleteImages()
    DeleteImage Logo
    DeleteImage Flare
    DeleteImage Crosshair
    DeleteImage InfoWeb
    DeleteImage InfoList
    DeleteImage Cursor
    End Sub

Function LoadModelTexture(ByVal Name As String, Optional Folder As String) As Long
    Dim B As Long, C As Byte, N As Long, X As Long, Y As Long, _
        W As Long, H As Long, W2 As Long, H2 As Long, _
        S As String, File As String, Code As String * 2, Bh As BmpHeader, _
        Pix() As Byte, Pix2() As Byte, _
        Img As Long, Gr As Long, Bi As BitmapInfo, Bm As Bitmap
    
    On Error Resume Next
    Name = FileFromPath(Name)
    LoadModelTexture = IsData(Textures, Folder & "\" & Name): If LoadModelTexture Then Exit Function
    glPixelStorei gl_Unpack_Alignment, 1
    glGenTextures 1, LoadModelTexture: If LoadModelTexture = 0 Then Exit Function
    
    File = ModelsDir & Name: If Not FileExists(File) Then _
    File = ModelsDir & Folder & "\" & Name: If Not FileExists(File) Then _
    File = TexturesDir & Name: If Not FileExists(File) Then _
    File = ModelsDir & FileName(Name) & ".bmp": If Not FileExists(File) Then _
    File = ModelsDir & Folder & "\" & FileName(Name) & ".bmp": If Not FileExists(File) Then _
    File = TexturesDir & FileName(Name) & ".bmp": If Not FileExists(File) Then _
        Exit Function
    
    With Main.Pic
    If hGP = 0 Then GoTo NoGP
    GdipLoadImageFromFile StrConv(File, vbUnicode), Img
    If Img = 0 Then GoTo NoGP
    GdipGetImageWidth Img, W: GdipGetImageHeight Img, H
    If .Picture Then Set .Picture = Nothing
    .Width = W: .Height = H: .Cls
    Err = 0: GdipCreateFromHDC .hDC, Gr
    If Err Or Gr = 0 Then GdipDisposeImage Img: Img = 0: GoTo NoGP
    GdipDrawImageRectI Gr, Img, 0, 0, W, H
    GdipDeleteGraphics Gr
    GdipDisposeImage Img
NoGP:
    If Img = 0 Then Err = 0: .Cls: .Picture = LoadPicture(File): If Err Then GoTo Ext
    W = .Width: H = .Height: If W <= 0 Or H <= 0 Then GoTo Ext
    B = GetCurrentObject(.hDC, 7): If B = 0 Then GoTo Ext 'OBJ_BITMAP=7
    GetObject B, Len(Bm), Bm: W = Bm.bmWidth: H = Bm.bmHeight
    With Bi.Bh
    .biSize = Len(Bi.Bh): .biPlanes = 1: .biBitCount = 24: .biCompression = 0 'BI_RGB=0
    .biWidth = W: .biHeight = H: .biSizeImage = .biWidth * .biHeight * .biBitCount \ 8
    End With
    
    ReDim Pix(3 - 1, W - 1, H - 1)
    If GetDIBits(.hDC, B, 0, H, Pix(0, 0, 0), Bi, 0) = 0 Then GoTo Ext 'DIB_RGB_COLORS=0
    W2 = RoundUpBase2(W): H2 = RoundUpBase2(H)
    ReDim Pix2(3 - 1, W2 - 1, H2 - 1)
    For Y = 0 To H - 1: For X = 0 To W - 1
        Pix2(0, X, Y) = Pix(2, X, Y): Pix2(1, X, Y) = Pix(1, X, Y): Pix2(2, X, Y) = Pix(0, X, Y)
        Next: Next
    
    glBindTexture gl_Texture_2d, LoadModelTexture
    glTexParameteri gl_Texture_2d, gl_Texture_Min_Filter, gl_Linear 'gl_Linear gl_Nearest gl_Linear_Mipmap_Linear
    glTexParameteri gl_Texture_2d, gl_Texture_Mag_Filter, gl_Linear 'gl_Linear gl_Nearest
    glTexParameteri gl_Texture_2d, gl_Texture_Wrap_S, gl_Repeat 'gl_Repeat gl_Clamp
    glTexParameteri gl_Texture_2d, gl_Texture_Wrap_T, gl_Repeat 'gl_Repeat gl_Clamp
    glTexEnvi gl_Texture_Env, gl_Texture_Env_Mode, gl_Modulate 'gl_Modulate gl_Decal
    glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Rgb, gl_Unsigned_Byte, Pix2(0, 0, 0)
    'glTexImage2D gl_Texture_2d, 0, 3, W2, H2, 0, gl_Compressed_Rgb_Arb, gl_Unsigned_Byte, Pix2(0, 0, 0)
    AddData Textures, Folder & "\" & Name, LoadModelTexture
Ext:
    Erase Pix, Pix2
    Set .Picture = Nothing
    End With
    On Error GoTo 0
    End Function

Sub ModelMultMatrix(A() As Single, C() As Single)
    Dim M As MatrixX
    M.XX = A(0): M.XY = A(3): M.XZ = A(6): M.XW = 0
    M.YX = A(1): M.YY = A(4): M.YZ = A(7): M.YW = 0
    M.ZX = A(2): M.ZY = A(5): M.ZZ = A(8): M.ZW = 0
    M.WX = 0: M.WY = 0: M.WZ = 0: M.WW = 1
    glMultMatrixf M.XX
    glTranslatef -C(0), -C(1), -C(2)
    End Sub

Sub GetModelHeader(S As String, ID As Long, Parent As Long, Size As Long)
    'Parse Id, Parent and Size
    ParamX S: ID = ParamN(S): ParamX S: Parent = ParamN(S): ParamX S: Size = ParamN(S)
    End Sub

Sub GetModelFaces(Compile As Boolean, D As String, A As Long, S As String, P As PolH)
    'Parse TextureVertices and Texture vertex list
    Dim N As Long, O As Long, U As String, Q As Long, W As Long, T As Long, H As Boolean
    ParamX S: P.Faces = ParamN(S) 'Faces <W,T>
    ReDim P.F(P.Faces - 1): ReDim P.G(P.Faces - 1): ReDim P.L(P.Faces - 1)
    ReDim P.FaceVerts(P.Faces - 1): N = 0
    Do
        S = GetLineLf(D, A)
        H = ParamX(S) = "Face": ParamX S: P.FaceVerts(N) = ParamN(S)
        ReDim P.F(N).T(P.FaceVerts(N) - 1): ReDim P.F(N).W(P.FaceVerts(N) - 1): O = 0
        ParamX S: P.G(N) = ParamN(S): ParamX S: P.L(N) = ParamN(S) 'Face verts
        S = GetLineLf(D, A): If Compile Then glBegin gl_Triangle_Fan
        Do
            S = LTrim(S): Q = InStr(S, " ")
            If Q Then U = Left(S, Q - 1): S = Mid(S, Q + 1) Else U = S: S = ""
            U = Mid(U, 2): W = NumS(U): P.F(N).W(O) = W: T = Val(U): P.F(N).T(O) = T
            If Compile Then glTexCoord2f P.T(T).S, P.T(T).T: glVertex3f P.W(W).X, P.W(W).Y, P.W(W).Z
            O = O + 1
            Loop While O < P.FaceVerts(N)
        N = N + 1: If Compile Then glEnd
        Loop While N < P.Faces
    End Sub

Sub GetModelTextureV(D As String, A As Long, S As String, _
        TextureVertices As Long, X() As Vertex2)
    'Parse TextureVertices and Texture vertex list
    Dim F As Long
    ParamX S: ParamX S: TextureVertices = ParamN(S)
    ReDim X(TextureVertices - 1): F = 0
    Do
        S = GetLineLf(D, A): X(F).S = ParamN(S): X(F).T = ParamN(S)
        F = F + 1
        Loop While F < TextureVertices
    End Sub

Sub GetModelWorldV(D As String, A As Long, S As String, _
        WorldVertices As Long, W() As Vertex3)
    'Parse WorldVertices and World vertex list
    Dim F As Long
    ParamX S: ParamX S: WorldVertices = ParamN(S)
    ReDim W(WorldVertices - 1): F = 0
    Do
        S = GetLineLf(D, A): W(F).X = ParamN(S): W(F).Y = ParamN(S): W(F).Z = ParamN(S)
        F = F + 1
        Loop While F < WorldVertices
    F = F
    End Sub

Sub GetModelLight(Compile As Boolean, D As String, A As Long, S As String, _
        L As ModelLight)
    'Parse Name, center axis, Transform matrix, Local, color, cone angle, inner angle and falloff
    S = GetLineLf(D, A): ParamX S: L.N.Name = ParamX(S)
    S = GetLineLf(D, A): ParamX S: L.N.C(0) = ParamN(S): L.N.C(1) = ParamN(S): L.N.C(2) = ParamN(S)
    S = GetLineLf(D, A): ParamX S: ParamX S: L.N.A(0) = ParamN(S): L.N.A(3) = ParamN(S): L.N.A(6) = ParamN(S)
    S = GetLineLf(D, A): ParamX S: ParamX S: L.N.A(1) = ParamN(S): L.N.A(4) = ParamN(S): L.N.A(7) = ParamN(S)
    S = GetLineLf(D, A): ParamX S: ParamX S: L.N.A(2) = ParamN(S): L.N.A(5) = ParamN(S): L.N.A(8) = ParamN(S)
    S = GetLineLf(D, A) 'Transform matrix
    S = GetLineLf(D, A): L.N.T(0) = ParamN(S): L.N.T(4) = ParamN(S): L.N.T(8) = ParamN(S): L.N.T(12) = ParamN(S)
    S = GetLineLf(D, A): L.N.T(1) = ParamN(S): L.N.T(5) = ParamN(S): L.N.T(9) = ParamN(S): L.N.T(13) = ParamN(S)
    S = GetLineLf(D, A): L.N.T(2) = ParamN(S): L.N.T(6) = ParamN(S): L.N.T(10) = ParamN(S): L.N.T(14) = ParamN(S)
    S = GetLineLf(D, A): L.N.T(3) = ParamN(S): L.N.T(7) = ParamN(S): L.N.T(11) = ParamN(S): L.N.T(15) = ParamN(S)
    S = GetLineLf(D, A): L.Local = ParamX(S) 'Local
    S = GetLineLf(D, A): ParamX (S): L.R = NumS(S): L.G = NumS(S): L.B = ParamN(S): ParamX S: ParamX S
        L.ConeAngle = ParamN(S): ParamX S: ParamX S: L.InnerAngle = ParamN(S): ParamX S
        L.FallOff = ParamN(S) 'color 1.3,0.841176,0.489412 cone angle 90 inner angle 0 falloff 1
    If Compile Then
        'Transform matrix
        A = A: glPushMatrix: glMultMatrixf L.N.T(0)
        'The light should be painted here
        A = A: glPushMatrix: glTranslatef L.N.C(0), L.N.C(1), L.N.C(2)
        'The center icon should be painted here
        A = A: glPopMatrix: glPopMatrix
        End If
    End Sub

Sub GetModelChan(D As String, A As Long, S As String, C As ModelChan)
    'Parse Name, center axis, Transform matrix, Type, flags, frames and time
    Dim R As Boolean, F As Long
    S = GetLineLf(D, A): ParamX S: C.Name = ParamX(S) 'Name Global
    S = GetLineLf(D, A): ParamX S: C.Type = ParamX(S): If C.Type = "Rotate" Then R = True 'Type Rotate, Move
    S = GetLineLf(D, A): ParamX S: C.Flags = ParamN(S): ParamX S: C.Frames = ParamN(S): F = C.Frames 'flags 1 frames 541
    ReDim C.T(C.Frames - 1): F = 0
    Do
        S = GetLineLf(D, A): ParamX S: C.T(F).Time = ParamN(S): ParamX S
            C.T(F).Acc = ParamN(S): ParamX S: C.T(F).T = ParamN(S): ParamX S
            C.T(F).C = ParamN(S): ParamX S: C.T(F).B = ParamN(S) 'time 0 acc 0 t 0 c 0 b 0
        If R Then ParamX S: C.T(F).Orientation = ParamN(S): C.T(F).Position(0) = ParamN(S): _
            C.T(F).Position(1) = ParamN(S): C.T(F).Position(2) = ParamN(S) _
        Else ParamX S: C.T(F).Orientation = 0: C.T(F).Position(0) = ParamN(S): _
            C.T(F).Position(1) = ParamN(S): C.T(F).Position(2) = ParamN(S)
        F = F + 1
        Loop While F < C.Frames
    End Sub

Sub GetModelPhAn(D As String, A As Long, S As String, P As ModelPhAn)
    'Parse CentreOfGravity, InitMotion, InitRorationOrig, InitRorationVect, AccMotion, AccRotationOrig, AccRotationVect, Weight, Elasticity, EnviResist, Friction, Active and NumFxPoints
    S = GetLineLf(D, A): ParamX S: P.G(0) = ParamN(S): P.G(1) = ParamN(S): P.G(2) = ParamN(S) 'CentreOfGravity
    S = GetLineLf(D, A): ParamX S: P.M(0) = ParamN(S): P.M(1) = ParamN(S): P.M(2) = ParamN(S) 'InitMotion
    S = GetLineLf(D, A): ParamX S: P.O(0) = ParamN(S): P.O(1) = ParamN(S): P.O(2) = ParamN(S) 'InitRorationOrig
    S = GetLineLf(D, A): ParamX S: P.V(0) = ParamN(S): P.V(1) = ParamN(S): P.V(2) = ParamN(S) 'InitRorationVect
    S = GetLineLf(D, A): ParamX S: P.A(0) = ParamN(S): P.A(1) = ParamN(S): P.A(2) = ParamN(S) 'AccMotion
    S = GetLineLf(D, A): ParamX S: P.B(0) = ParamN(S): P.B(1) = ParamN(S): P.B(2) = ParamN(S) 'AccRotationOrig
    S = GetLineLf(D, A): ParamX S: P.C(0) = ParamN(S): P.C(1) = ParamN(S): P.C(2) = ParamN(S) 'AccRotationVect
    S = GetLineLf(D, A): ParamX S: P.Weight = ParamN(S) 'Weight
    S = GetLineLf(D, A): ParamX S: P.Elasticity = ParamN(S) 'Elasticity
    S = GetLineLf(D, A): ParamX S: P.EnviResist = ParamN(S) 'EnviResist
    S = GetLineLf(D, A): ParamX S: P.Friction = ParamN(S) 'Friction
    S = GetLineLf(D, A): ParamX S: P.Active = ParamN(S) 'Active
    S = GetLineLf(D, A): ParamX S: P.NumFxPoints = ParamN(S) 'NumFxPoints
    End Sub

Sub GetModelName(Compile As Boolean, D As String, A As Long, S As String, _
        Name As String, C() As Single, X() As Single, R() As Single)
    'Parse Name, center axis and Transform matrix
    ParamX S: Name = ParamX(S)
    S = GetLineLf(D, A): ParamX S: C(0) = ParamN(S): C(1) = ParamN(S): C(2) = ParamN(S)
    S = GetLineLf(D, A): ParamX S: ParamX S: X(0) = ParamN(S): X(3) = ParamN(S): X(6) = ParamN(S)
    S = GetLineLf(D, A): ParamX S: ParamX S: X(1) = ParamN(S): X(4) = ParamN(S): X(7) = ParamN(S)
    S = GetLineLf(D, A): ParamX S: ParamX S: X(2) = ParamN(S): X(5) = ParamN(S): X(8) = ParamN(S)
    S = GetLineLf(D, A) 'Transform matrix
    S = GetLineLf(D, A): R(0) = ParamN(S): R(4) = ParamN(S): R(8) = ParamN(S): R(12) = ParamN(S)
    S = GetLineLf(D, A): R(1) = ParamN(S): R(5) = ParamN(S): R(9) = ParamN(S): R(13) = ParamN(S)
    S = GetLineLf(D, A): R(2) = ParamN(S): R(6) = ParamN(S): R(10) = ParamN(S): R(14) = ParamN(S)
    S = GetLineLf(D, A): R(3) = ParamN(S): R(7) = ParamN(S): R(11) = ParamN(S): R(15) = ParamN(S)
    If Compile Then
        'Transform matrix
        A = A: glMultMatrixf R(0): glPushMatrix: glTranslatef C(0), C(1), C(2)
        'The center icon should be painted here
        A = A: glPopMatrix
        End If
    End Sub

Sub GetModelNameIdentity(Name As String, C() As Single, X() As Single, R() As Single)
    'Default matrix identity in Name, center axis and Transform matrix
    C(0) = 0: C(1) = 0: C(2) = 0
    X(0) = 1: X(1) = 0: X(2) = 0
    X(3) = 0: X(4) = 1: X(5) = 0
    X(6) = 0: X(7) = 0: X(8) = 1
    R(0) = 1: R(4) = 0: R(8) = 0: R(12) = 0
    R(1) = 0: R(5) = 1: R(9) = 0: R(13) = 0
    R(2) = 0: R(6) = 0: R(10) = 1: R(14) = 0
    R(3) = 0: R(7) = 0: R(11) = 0: R(15) = 1
    End Sub

Sub GetModelMat1(D As String, A As Long, S As String, M As ModelMaterial, _
        Optional ByVal T As Boolean = True, Optional Folder As String)
    'Parse mat#, shader,facet,rgba,texture,offsetH,offsetV,repeatsH and repeatsV
    Dim U As String, B As Long
    On Error Resume Next
    S = GetLineLf(D, A): ParamX S: M.Mat = ParamN(S)
    S = GetLineLf(D, A): ParamX S: M.Shader = ParamX(S): ParamX S: M.Facet = ParamX(S)
    S = GetLineLf(D, A): ParamX S: M.R = NumS(S): M.G = NumS(S): M.B = NumS(S)
    S = GetLineLf(D, A): ParamX S: M.A = ParamN(S): ParamX S: M.Ka = ParamN(S): ParamX S
        M.Ks = ParamN(S): ParamX S: M.Exp = ParamN(S): ParamX S: M.Ior = ParamN(S)
    B = A: S = GetLineLf(D, B): U = ParamX(S): If Left(U, 4) = "text" Then A = B: _
        M.Texture = Mid(S, 3): If T Then M.glTexture = LoadModelTexture(M.Texture, Folder)
    B = A: S = GetLineLf(D, B): U = ParamX(S): If Left(U, 4) = "offs" Then A = B: _
        U = ParamX(S): M.OffsetH = NumS(U): M.OffsetV = NumS(U): ParamX S: U = ParamX(S): _
        M.RepeatsH = NumS(U): M.RepeatsV = NumS(U): ParamX S: M.Flags = ParamN(S)
    End Sub

Function LoadModel(List As Long, _
        Optional ByVal Name As String, Optional DefaultName As String, _
        Optional Area As Edit, Optional Emissivity As Edit, _
        Optional CenterX As Edit, Optional CenterY As Edit, Optional CenterZ As Edit, _
        Optional ByVal GetArea As Boolean = True, Optional ByVal GetCenter As Boolean = True, _
        Optional ByVal UseLighting As Boolean = True, Optional ByVal UseTexture As Boolean = True, _
        Optional ByVal Solid As Boolean = True) As Boolean
    
    'Loads a .COB file and compiles the OpenGL list
    Dim A As Long, C As Long, D As String, F As Long, G As Long, L As Long, _
        M As Long, N As Long, O As Long, P As Long, S As String, T As Long, W As Long, _
        N1 As Vertex3, V1 As Vertex3, V2 As Vertex3, V3 As Vertex3, E1 As Double, _
        TotalArea As Double, TotalEmissivity As Double, _
        OffsetH As Single, OffsetV As Single, RepeatsH As Single, RepeatsV As Single, _
        LastTex As Long, IsTexture As Boolean, Compile As Boolean, Model As Model
    
    If Name = "" Then Name = DefaultName
    On Error Resume Next
    Open ModelsDir & Name & ".cob" For Input As 1
    If FreeFile = 1 Then Open ModelsDir & Name & "\" & Name & ".cob" For Input As 1
    If FreeFile = 1 Then Exit Function
    D = Replace(Input(LOF(1), 1), vbCrLf, vbLf): Close
    On Error GoTo 0
    
    'Generate a struct reading the file
    A = 1: S = GetLineLf(D, A): If Len(S) < 18 Then Exit Function
    If Left(S, 18) <> "Caligari V00.01ALH" Then Exit Function
    With Model
    .Name = Name: Compile = False: G = -1: .Groups = G: A = 1: S = GetLineLf(D, A)
    Do
        S = GetLineLf(D, A)
        If Len(S) <= 4 Then Exit Do 'Ignore the command
        Select Case Left(S, 4)
        Case "Name" 'Name, center axis and Transforms
            If G < 0 Then G = G _
            Else If P < 0 Then With .G(G).N: GetModelName Compile, D, A, S, .Name, .C, .A, .T: End With _
            Else With .G(G).P(P).N: GetModelName Compile, D, A, S, .Name, .C, .A, .T: End With
            'If there is no Grou ignore the command
        Case "Unit" 'Unit V0.01 Id 159602285 Parent 159602284 Size 00000009
            If G < 0 Then G = G Else _
            If P < 0 Then GetModelHeader S, .G(G).U.H.ID, .G(G).U.H.Parent, .G(G).U.H.Size: _
                S = GetLineLf(D, A): ParamX S: .G(G).U.Units = ParamN(S) _
            Else GetModelHeader S, .G(G).P(P).U.H.ID, .G(G).P(P).U.H.Parent, .G(G).P(P).U.H.Size: _
                S = GetLineLf(D, A): ParamX S: .G(G).P(P).U.Units = ParamN(S)
            'If there is no Grou ignore the command
        Case "Worl" 'World Vertices
            If G < 0 Then G = G Else _
            If P < 0 Then P = P Else GetModelWorldV D, A, S, .G(G).P(P).WorldVertices, .G(G).P(P).W
            'If there is no Grou ignore the command and also if there is no PolH
        Case "Text" 'Texture Vertices
            If G < 0 Then G = G Else _
            If P < 0 Then P = P Else GetModelTextureV D, A, S, .G(G).P(P).TextureVertices, .G(G).P(P).T
            'If there is no Grou ignore the command and also if there is no PolH
        Case "Face" 'Faces
            If G < 0 Then G = G Else _
            If P < 0 Then P = P Else GetModelFaces Compile, D, A, S, .G(G).P(P)
            'If there is no Grou ignore the command
        Case "Mat1" 'Mat1 V0.06 Id 159596668 Parent 159595620 Size 00000106
            If G < 0 Then G = G Else _
            If P < 0 Then P = P Else If M < 0 Then M = 0: With .G(G).P(P): .Materials = M: _
                ReDim .M(M): GetModelHeader S, .M(M).H.ID, .M(M).H.Parent, .M(M).H.Size: _
                GetModelMat1 D, A, S, .M(M), UseTexture, Name: End With _
            Else M = M + 1: With .G(G).P(P): .Materials = M: ReDim Preserve .M(M): _
                GetModelHeader S, .M(M).H.ID, .M(M).H.Parent, .M(M).H.Size: _
                GetModelMat1 D, A, S, .M(M), UseTexture, Name: End With
            'Go up one level of Mat1. If there is no Grou ignore the command and also if there is no PolH
        Case "PolH" 'PolH V0.04 Id 159595620 Parent 159602284 Size 00000908
            If G < 0 Then G = 0: .Groups = G: ReDim .G(G): P = -1: With .G(G): .PolHs = P: C = -1: _
                .Chans = C: L = -1: .Lights = L: GetModelNameIdentity .N.Name, .N.C, .N.A, .N.T: End With
            'Go up one level of Grou if there is no Grou
            If P < 0 Then P = 0: With .G(G): .PolHs = P: ReDim .P(P): M = -1: .P(P).Materials = M: _
                GetModelHeader S, .P(P).H.ID, .P(P).H.Parent, .P(P).H.Size: End With Else P = P + 1: _
                With .G(G): .PolHs = P: ReDim Preserve .P(P): M = -1: .P(P).Materials = M: _
                GetModelHeader S, .P(P).H.ID, .P(P).H.Parent, .P(P).H.Size: End With
            'Go up one level of PolH
        Case "Grou" 'Grou V0.01 Id 159602284 Parent 0 Size 00000107
            If G < 0 Then G = 0: .Groups = G: ReDim .G(G): P = -1: With .G(G): .PolHs = P: C = -1: _
                .Chans = C: L = -1: .Lights = L: GetModelHeader S, .H.ID, .H.Parent, .H.Size: End With _
            Else G = G + 1: .Groups = G: ReDim Preserve .G(G): With .G(G): C = -1: .Chans = C: _
                L = -1: .Lights = L: GetModelHeader S, .H.ID, .H.Parent, .H.Size: P = -1: .PolHs = P: End With
            'Go up one level of Grou and close PolH if there is one
        Case "Lght" 'Lght V0.04 Id 174673012 Parent 159680204 Size 00000217
            If G < 0 Then G = G _
            Else If L < 0 Then L = 0: With .G(G): .Lights = L: ReDim .L(L): _
                GetModelHeader S, .L(L).H.ID, .L(L).H.Parent, .L(L).H.Size: _
                GetModelLight Compile, D, A, S, .L(L): End With _
            Else L = L + 1: .G(G).Lights = L: With .G(G): ReDim Preserve .L(L): _
                GetModelHeader S, .L(L).H.ID, .L(L).H.Parent, .L(L).H.Size: _
                GetModelLight Compile, D, A, S, .L(L): End With
            'Go up one level of Lght. If there is no Grou ignore the command
        Case "Chan" 'Chan V0.08 Id 159591864 Parent 159591676 Size 00042980
            If G < 0 Then G = G _
            Else If C < 0 Then C = 0: With .G(G): .Chans = C: ReDim .C(C): _
                GetModelHeader S, .C(C).H.ID, .C(C).H.Parent, .C(C).H.Size: _
                GetModelChan D, A, S, .C(C): End With _
            Else C = C + 1: With .G(G): ReDim Preserve .C(C): _
                GetModelHeader S, .C(C).H.ID, .C(C).H.Parent, .C(C).H.Size: _
                GetModelChan D, A, S, .C(C): End With
            'Go up one level of Chan. If there is no Grou ignore the command
        Case "PhAn" 'PhAn V0.04 Id 159693668 Parent 159591676 Size 00000244
            If G < 0 Then G = G _
            Else If P < 0 Then With .G(G): GetModelHeader S, .A.H.ID, .A.H.Parent, .A.H.Size: _
                GetModelPhAn D, A, S, .A: End With _
            Else With .G(G).P(P): GetModelHeader S, .A.H.ID, .A.H.Parent, .A.H.Size: _
                GetModelPhAn D, A, S, .A: End With
            'If there is no Grou ignore the command
        Case "END " 'END  V1.00 Id 0 Parent 0 Size        0
            Exit Do
            End Select
        Loop
    End With
    
    'Generate an OpenGL list reading the structure
    With Model
    If .Groups < 0 Then Exit Function
    'If List = 0 Then Exit Function 'List = glGenLists(1)
    LoadModel = True
    .glList = List: glNewList .glList, gl_Compile
    glEnable gl_Depth_Test: glDepthMask 1 'Use Z-buffer
    glEnable gl_Cull_Face: glFrontFace gl_CW 'Reverse vertex order
    If UseLighting Then glEnable gl_Lighting: glEnable gl_Normalize _
    Else glDisable gl_Lighting 'Use lighting and normalize normal vectors
    If GetArea Then TotalArea = 0: TotalEmissivity = 0: UseLighting = True
    glPushMatrix
    glScalef 50, 50, 50 'Scale up 3D model
    For G = 0 To .Groups 'Grou
        With .G(G): If G = 0 Then ModelMultMatrix .N.A, .N.C ' glMultMatrixf .N.T(0)
        If GetCenter And G = 0 Then 'Get the center of mass from the PhAn command of the first group
            If Not CenterX Is Nothing Then If .A.G(0) = 0 Then CenterX = "" _
                Else CenterX = ToSize(.A.G(0), FShort)
            If Not CenterY Is Nothing Then If .A.G(1) = 0 Then CenterY = "" _
                Else CenterY = ToSize(.A.G(1), FShort)
            If Not CenterZ Is Nothing Then If .A.G(2) = 0 Then CenterZ = "" _
                Else CenterZ = ToSize(.A.G(2), FShort) Else
            'InertiaX = .A.C(0): InertiaY = .A.C(1): InertiaZ = .A.C(2)  'Get the moment of inertia from the acceleration rotation vector of the first group
            End If
        For P = 0 To .PolHs 'PolH
            With .P(P): glPushMatrix: glMultMatrixf .N.T(0)
            For F = 0 To .Faces - 1 'Faces
                N = .L(F): If .M(N).Mat <> N Then _
                    N = .Materials: Do While N > 0 And .M(N).Mat <> .L(F): N = N - 1: Loop
                With .M(N)
                If GetArea Then E1 = .Ks 'Shininess
                If .glTexture Then 'Texture
                    If Not IsTexture Then IsTexture = True: glEnable gl_Texture_2d
                    If LastTex <> .glTexture Then _
                        LastTex = .glTexture: glBindTexture gl_Texture_2d, .glTexture
                    RepeatsH = .RepeatsH: RepeatsV = .RepeatsV
                    OffsetH = .OffsetH: OffsetV = .OffsetV
                    glColor4f 1, 1, 1, 1
                Else
                    If IsTexture Then IsTexture = False: glDisable gl_Texture_2d
                    LastTex = 0
                    glColor4f .R, .G, .B, .A 'Colors
                    End If
                End With
                If Solid Then glBegin gl_Triangle_Fan Else glBegin gl_Line_Strip 'Solid or wireframe
                O = .FaceVerts(F)
                If UseLighting And O >= 3 Then 'Use lighting only if the polygon has 3 or more vertices
                    V3 = .W(.F(F).W(0)): V1 = .W(.F(F).W(1)): V2 = .W(.F(F).W(2))
                    Normal3D N1, V3, V2, V1, GetArea, TotalArea, TotalEmissivity, E1
                    glNormal3f N1.X, N1.Y, N1.Z
                    End If
                For O = 0 To O - 1 'Vertices
                    With .F(F): W = .W(O): T = .T(O): End With
                    If IsTexture Then With .T(T): _
                        glTexCoord2f .S * RepeatsH - OffsetH, .T * RepeatsV - OffsetV: End With
                    If Not UseLighting Or O < 3 Then
                        With .W(W): glVertex3f .X, .Y, .Z: End With
                    Else
                        V1 = .W(W): Normal3D N1, V3, V1, V2, GetArea, TotalArea, TotalEmissivity, E1
                        glNormal3f N1.X, N1.Y, N1.Z: glVertex3f V1.X, V1.Y, V1.Z: V2 = V1
                        End If
                    Next
                glEnd
                Next
            glDisable gl_Texture_2d
            glPopMatrix
            End With: Next
        End With: Next
    glPopMatrix
    glDisable gl_Lighting: glDisable gl_Normalize
    glDisable gl_Cull_Face: glFrontFace gl_CCW
    glDisable gl_Depth_Test: glDepthMask 0
    glEndList
    If GetArea And TotalArea > 0 Then
        If Not Area Is Nothing Then Area = ToArea(TotalArea, FShort)
        TotalEmissivity = TotalEmissivity / TotalArea
        If Not Emissivity Is Nothing Then Emissivity = NumX(TotalEmissivity, FMini)
        End If
    End With

    End Function

Sub CreateRover(ByVal List As Long, Optional H As Double = 2, Optional ByVal P As Long = 10)
    Dim X As Long, Y As Long, N As Double, H1 As Double, _
        X1 As Double, Y1 As Double, Z1 As Double
    glNewList List, gl_Compile
    glEnable gl_Depth_Test: glDepthMask 1 'Use Z-buffer
    glEnable gl_Cull_Face: glFrontFace gl_CW 'Reverse vertex order
    glEnable gl_Lighting: glEnable gl_Normalize 'Use lighting and normalize normal vectors
    glColor4ubv cRoverModel
    glBegin gl_Quad_Strip
    For Y = -90 To 90 Step P
        For X = 0 To 360 - P Step P
            N = CDbl(Y + P) * Rad: Z1 = Sin(N) * H: H1 = Cos(N) * H
            N = CDbl(X) * Rad: X1 = Sin(N) * H1: Y1 = Cos(N) * H1
            glNormal3f X1, Y1, Z1
            glVertex3f X1, Y1, Z1
            N = CDbl(Y) * Rad: Z1 = Sin(N) * H: H1 = Cos(N) * H
            N = CDbl(X) * Rad: X1 = Sin(N) * H1: Y1 = Cos(N) * H1
            glNormal3f X1, Y1, Z1
            glVertex3f X1, Y1, Z1
            Next
        Next
    glEnd
    glDisable gl_Lighting: glDisable gl_Normalize
    glDisable gl_Cull_Face: glFrontFace gl_CCW
    glDisable gl_Depth_Test: glDepthMask 0
    glEndList
    End Sub

Sub CreateShip(ByVal List As Long, _
        Optional H As Double, Optional ByVal F As Boolean, Optional ByVal P As Long = 6)
    Dim A As Long, N As Double, X1 As Double, Y1 As Double
    P = 360 \ 6: glNewList List, gl_Compile
    glColor4ubv cShipModel
    If F Then
        'Flaps
        glBegin gl_Quads
        glVertex3f 0, 0, 0:  glVertex3f 0, 0, 4:  glVertex3f 3, 0, 1: glVertex3f 3, 0, 0
        glVertex3f 0, 0, 0:  glVertex3f 0, 0, 4:  glVertex3f -3, 0, 1: glVertex3f -3, 0, 0
        glVertex3f 0, 0, 0:  glVertex3f 0, 0, 4:  glVertex3f 0, 3, 1: glVertex3f 0, 3, 0
        glVertex3f 0, 0, 0:  glVertex3f 0, 0, 4:  glVertex3f 0, -3, 1: glVertex3f 0, -3, 0
        'glVertex3f 0, 0, 0:  glVertex3f 0, 0, 4:  glVertex3f 0, -0.3, 1: glVertex3f 0, -0.3, 0
        glEnd
        End If
    If H > 0 Then
        'Body
        glBegin gl_Quad_Strip
        For A = 360 To 0 Step -P
            N = CDbl(A) * Rad: X1 = Sin(N): Y1 = Cos(N)
            glVertex3f X1, Y1, H: glVertex3f X1, Y1, 0
            Next
        glEnd
        End If
    'Nose
    glBegin gl_Triangle_Fan
    glVertex3f 0, 0, H + 2: For A = 360 To 0 Step -P
        N = CDbl(A) * Rad: X1 = Sin(N): Y1 = Cos(N)
        glVertex3f X1, Y1, H
        Next
    glEnd
    If H = 0 Then
        'Antennas
        glBegin gl_Quads
        glVertex3f 1.5, -1, 0: glVertex3f 1.5, 1, 0: glVertex3f 5, 1, 0: glVertex3f 5, -1, 0
        glVertex3f -1.5, -1, 0: glVertex3f -1.5, 1, 0: glVertex3f -5, 1, 0: glVertex3f -5, -1, 0
        glVertex3f -1, 1.5, 0:  glVertex3f 1, 1.5, 0:  glVertex3f 1, 5, 0: glVertex3f -1, 5, 0
        glVertex3f -1, -1.5, 0:  glVertex3f 1, -1.5, 0:  glVertex3f 1, -5, 0: glVertex3f -1, -5, 0
        'glVertex3f -1, -1.5, 0:  glVertex3f 1, -1.5, 0:  glVertex3f 1, -0.5, 0: glVertex3f -1, -0.5, 0
        glEnd
        End If
    glEndList
    End Sub

Sub CreateModels()
    Set Textures = New Collection
    StageList = glGenLists(StageMax + 1)      'Stages objects
    StageEmptyList = glGenLists(StageMax + 1) 'Empty stages objects
    HoverList = glGenLists                    'Hover object
    SatelliteList = glGenLists                'Satellite object
    RoverList = glGenLists                    'Rover object
    StationList = glGenLists                  'Station object
    TrackList = glGenLists                    'Tracking object
    UpdateModels False, False
    StageModel
    End Sub

Sub DeleteModels()
    DeleteList StageList, StageMax + 1      'Stages objects
    DeleteList StageEmptyList, StageMax + 1 'Empty stages objects
    DeleteList HoverList                    'Hover object
    DeleteList SatelliteList                'Satellite object
    DeleteList RoverList                    'Rover object
    DeleteList StationList                  'Station object
    DeleteList TrackList                    'Tracking object
    DeleteModelTextures False               'Model textures
    End Sub
    
Sub DeleteModelTextures(Optional ByVal Refresh As Boolean = True)
    Dim X As Variant
    For Each X In Textures: glDeleteTextures 1, CLng(X): Next
    If Refresh Then Set Textures = New Collection Else Set Textures = Nothing
    End Sub
    
Sub UpdateModels( _
        Optional ByVal UpdateRender As Boolean = True, _
        Optional ByVal UpdateTextures As Boolean = True)
    Dim N As Long
    If Cam1.hRC = 0 Then Exit Sub
    Message "Loading 3D models …": DoEvents
    If UpdateTextures Then DeleteModelTextures
    For N = -3 To PayloadMax
        UpdateModel N: If N >= 0 And N <= StageMax Then UpdateModel N, , True
        Next
    Message
    If UpdateRender Then RenderModels: If Play = Paused Then Render
    End Sub
    
Sub UpdateModel(Optional ByVal N As Long, _
        Optional ByVal ShowMsg As Boolean, Optional ByVal EmptyStage As Boolean, _
        Optional ByVal UpdateRender As Boolean)
    Dim C As Long, D As Long, E As Long, S As String
    If Cam1.hRC = 0 Then Exit Sub
    If ShowMsg Then Message "Loading 3D model …": DoEvents
    With Stages
    Select Case N
    Case -3
        If RoverList Then If Not LoadModel(RoverList, RoverModel, "Rover") Then _
            CreateRover RoverList
    Case -2
        If StationList Then If Not LoadModel(StationList, StationModel, "Station") Then _
            CreateRover StationList
    Case -1
        If TrackList Then If Not LoadModel(TrackList, TrackModel, "Tracking") Then _
            CreateRover TrackList
    Case Is <= StageMax
        E = IfInt(EmptyStage, StageEmptyList, StageList)
        If E <> 0 Then
            E = E + N: S = IfText(EmptyStage, "Empty")
            If N > 0 Then
                If Not LoadModel(E, IfText(.Model(N) <> "", .Model(N) & S), "Stage" & N & S, _
                        , .Emissivity(N), .CenterX(N), .CenterY(N), .CenterZ(N)) Then _
                    CreateShip E, 20 - Int(20 / (2 + 1)) * (IfInt(N > 3, 3, N) - 1), N = 1
            Else
                If Not LoadModel(E, , "Balloon" & S) Then CreateRover E
                End If
            End If
    Case HoverMax
        If HoverList Then If Not LoadModel(HoverList, .Model(N), "Hover", _
                , .Emissivity(N), .CenterX(N), .CenterY(N), .CenterZ(N)) Then _
            CreateShip HoverList, 20 - Int(20 / (2 + 1)) * 2
    Case PayloadMax
        If SatelliteList Then If Not LoadModel(SatelliteList, .Model(N), "Satellite", _
                , .Emissivity(N), .CenterX(N), .CenterY(N), .CenterZ(N)) Then _
            CreateShip SatelliteList
        End Select
    End With
    If ShowMsg Then Message
    If UpdateRender Then RenderModels: If Play = Paused Then Render
    End Sub
    
Sub DeleteObjects()
    DeleteList SphereList
    DeleteList SkyList
    DeleteList CompassList
    DeleteList CompassFlatList
    DeleteList GridList
    DeleteList AirspacesList
    DeleteList AirspacesFlatList
    DeleteList AreasList
    DeleteList AreasFlatList
    DeleteList LandList
    DeleteList LandFlatList
    DeleteList SeaList
    DeleteList SeaFlatList
    End Sub

Sub SetModel(Optional ByVal Class As ObjectType = TypeMoon, Optional ByVal N As Long, _
        Optional ByVal Value As Boolean = True)
    Select Case Class
    Case TypeAsteroid: Asteroid(N).IsModel = Value
    Case TypeComet: Comet(N).IsModel = Value
    Case TypeMoon: Moons(N).IsModel = Value
        End Select
    End Sub

Sub StoreObject(ByVal Class As ObjectType, Name As String, Optional ByVal Save As Boolean)
    If Save Then
        If SelectedType = Class Then Name = GetName(Class, SelectedN) Else Name = ""
    ElseIf Name <> "" Then
        SelectedN = GetN(Class, Name): If LastType = Class Then LastN = SelectedN
        End If
    End Sub

Sub UpdateObject(P As MinorPlanet, Name As String)
    P.N = GetN(P.Class, Name): If P.N >= 0 Then SetModel P.Class, P.N Else P.N = 0
    End Sub

Sub UpdateObjects(Optional ByVal Class As ObjectType = TypeAll)
    If Class = TypeAll Or Class = TypeAsteroid Then
        UpdateObject Ceres, "1 Ceres"
        End If
    If Class = TypeAll Or Class = TypeComet Then
        'UpdateObject Halley, "1P Halley"
        End If
    If Class = TypeAll Or Class = TypeMoon Then
        UpdateObject Io, "Io"
        UpdateObject Europa, "Europa"
        UpdateObject Ganymede, "Ganymede"
        UpdateObject Callisto, "Callisto"
        UpdateObject Titan, "Titan"
        UpdateObject Triton, "Triton"
        End If
    End Sub

Sub LoadTextureEx(P As MinorPlanet, Name As String, Optional Planet As String, _
        Optional Texture As String, Optional ByVal Class As ObjectType = TypeMoon)
    If Texture = "" Then Texture = Name
    If Planet <> "" Then Texture = Planet & "." & Texture
    P.Class = Class: P.N = GetN(P.Class, Name)
    If P.N >= 0 Then SetModel P.Class, P.N: LoadObjectTexture Texture, P.Tex Else P.N = 0
    End Sub

Sub DeleteTextureEx(P As MinorPlanet)
    DeleteList P.List: DeleteTexture P.Tex
    End Sub

Sub UpdateTexture(Name As String, Pl As Planet, Optional ByVal LoadRing As Boolean)
    If Cam1.hRC = 0 Or (Tool And ToolTextures) = 0 Then Exit Sub
    Message "Loading texture …": DoEvents
    DeletePlanet Pl, True
    LoadTexture Name, Pl, LoadRing
    Message
    DoEvents
    If Play = Paused Then Render
    End Sub

Sub LoadTextures()
    If Cam1.hRC = 0 Or (Tool And ToolTextures) = 0 Then Exit Sub
    Message "Loading textures …": DoEvents
    LoadTexture "Earth", Earth
    LoadTexture "Moon", Moon
    LoadTexture "Sun", Sun
    LoadTexture "Mercury", Mercury
    LoadTexture "Venus", Venus
    LoadTexture "Mars", Mars
    LoadTexture "Jupiter", Jupiter, True
    LoadTexture "Saturn", Saturn, True
    LoadTexture "Uranus", Uranus, True
    LoadTexture "Neptune", Neptune, True
    LoadTexture "Pluto", Pluto
    LoadTextureEx Io, "Io", "Jupiter"
    LoadTextureEx Europa, "Europa", "Jupiter"
    LoadTextureEx Ganymede, "Ganymede", "Jupiter"
    LoadTextureEx Callisto, "Callisto", "Jupiter"
    LoadTextureEx Titan, "Titan", "Saturn"
    LoadTextureEx Triton, "Triton", "Neptune"
    LoadTextureEx Ceres, "1 Ceres", , "Asteroid", TypeAsteroid
    Message
    DoEvents
    End Sub

Sub DeleteTextures()
    If Cam1.hRC = 0 Then Exit Sub
    DeletePlanet Earth, True
    DeletePlanet Moon, True
    DeletePlanet Sun, True
    DeletePlanet Mercury, True
    DeletePlanet Venus, True
    DeletePlanet Mars, True
    DeletePlanet Jupiter, True
    DeletePlanet Saturn, True
    DeletePlanet Uranus, True
    DeletePlanet Neptune, True
    DeletePlanet Pluto, True
    DeleteTextureEx Io
    DeleteTextureEx Europa
    DeleteTextureEx Ganymede
    DeleteTextureEx Callisto
    DeleteTextureEx Titan
    DeleteTextureEx Triton
    DeleteTextureEx Ceres
    End Sub

Sub DeletePlanetLists()
    DeletePlanets
    DeleteList AirspacesList: DeleteList AirspacesFlatList
    DeleteList AreasList: DeleteList AreasFlatList
    DeleteList SeaList: DeleteList SeaFlatList
    DeleteList LandList: DeleteList LandFlatList
    End Sub

Sub DeletePlanets()
    If Cam1.hRC = 0 Then Exit Sub
    DeletePlanet Earth
    DeletePlanet Moon
    DeletePlanet Sun
    DeletePlanet Mercury
    DeletePlanet Venus
    DeletePlanet Mars
    DeletePlanet Jupiter
    DeletePlanet Saturn
    DeletePlanet Uranus
    DeletePlanet Neptune
    DeletePlanet Pluto
    End Sub

Sub DeletePlanet(Pl As Planet, Optional ByVal DeleteTextures As Boolean)
    With Pl
    If DeleteTextures Then DeleteTexture .Ring: _
        If .Texture.W > 0 And .Texture.H > 0 Then _
            glDeleteTextures .Texture.W * .Texture.H, .Texture.List(0, 0): _
            Erase .Texture.List: .Texture.W = 0: .Texture.H = 0
    DeleteList .Texture.Poly
    DeleteList .Texture.Flat
    'DeleteList .Terrain
    DeleteList .Grid
    DeleteList .GridFlat
    DeleteList .Field
    DeleteList .Atm
    DeleteList .AtmFlat
    End With
    End Sub

Sub DeleteList(List As Long, Optional ByVal Range As Long = 1)
    If List Then glDeleteLists List, Range: List = 0
    End Sub

Sub DeleteTexture(Texture As Long, Optional ByVal Range As Long = 1)
    If Texture Then glDeleteTextures Range, Texture: Texture = 0
    End Sub

Sub ResetMatrix()
    Dim M As MatrixX
    glGetFloatv gl_Modelview_Matrix, M.XX
    M.XX = 1: M.XY = 0: M.XZ = 0
    M.YX = 0: M.YY = 1: M.YZ = 0
    M.ZX = 0: M.ZY = 0: M.ZZ = 1
    glLoadMatrixf M.XX
    End Sub

Sub SetMatrixPos(Optional X As Double, Optional Y As Double, Optional Z As Double)
    Dim M As MatrixX
    glGetFloatv gl_Modelview_Matrix, M.XX
    M.WX = X: M.WY = Y: M.WZ = Z
    glLoadMatrixf M.XX
    End Sub

Sub CameraXYZ( _
        Optional CX As Double, Optional CY As Double, Optional CZ As Double, _
        Optional X As Double, Optional Y As Double, Optional Z As Double)
    Static M As MatrixX, X1 As Double, Y1 As Double, Z1 As Double
    glGetFloatv gl_Modelview_Matrix, M.XX
    X1 = -M.WX: Y1 = -M.WY: Z1 = -M.WZ
    CX = X1 * M.XX + Y1 * M.XY + Z1 * M.XZ - X
    CY = X1 * M.YX + Y1 * M.YY + Z1 * M.YZ - Y
    CZ = X1 * M.ZX + Y1 * M.ZY + Z1 * M.ZZ - Z
    End Sub

Sub Mode2D()
    glMatrixMode gl_Projection: glPushMatrix: glLoadMatrixf Matrix2D.XX
    glMatrixMode gl_Modelview: glPushMatrix: glLoadIdentity
    End Sub

Sub Mode3D()
    glMatrixMode gl_Projection: glPopMatrix
    glMatrixMode gl_Modelview: glPopMatrix
    End Sub

Sub InitRender()
    Dim N As Long
    InitCamera Cam1: For N = 0 To CamN - 1: InitCamera Cam(N): Next
    End Sub

Sub InitCamera(Cam As Camera)
    Dim N As Long, W As Long, H As Long, A1 As Double, H1 As Double, G1 As Double, _
        B As Boolean, Width As Single, Height As Single
    With Cam
    If .hRC = 0 Then
        If .ID = MainID Then .Width = .F.ScaleWidth: .Height = .F.ScaleHeight: _
            .Square = IfInt(.Width < .Height, .Width, .Height)
        Exit Sub
        End If
    B = .Follow = ToOnBoard And SensorP >= 0
    If .ID = ViewID Then
        If B Then
            With Sensor(SensorP)
            UpdateViewTarget
            If (Cam.F.ScaleWidth <> .Width Or Cam.F.ScaleHeight <> .Height) Then _
                NoResizeView = True: Resize Cam.F, .Width, .Height: _
                If Options.SnapPanels Then ResizeMain
            End With
        Else
            .F.Caption = "View"
            End If
        End If
    Width = .F.ScaleWidth: Height = .F.ScaleHeight
    If wglGetCurrentContext <> .hRC Then wglMakeCurrent .hDC, .hRC
    If .FOV <= 0 Then If SensorN Then .FOV = Sensor(SensorP).FOV Else .FOV = 45
    .Width = Int(Width) Or 1: .Height = Int(Height) Or 1
    glViewport 0, 0, .Width, .Height
    glMatrixMode gl_Projection
    
    '2D Matrix
    glLoadIdentity
    glGetFloatv gl_Projection_Matrix, .Matrix2D.XX
    .Matrix2D.XX = 2 / .Width: .Matrix2D.YY = -2 / .Height: .Matrix2D.ZZ = -1
    .Matrix2DX = .Matrix2D
    .Matrix2D.WX = (-CDbl(.Width \ 2) - 0.5) * .Matrix2D.XX
    .Matrix2D.WY = (-CDbl((.Height \ 2) + (.Height And 1)) + 0.5) * .Matrix2D.YY
    
    '3D Matrix
    glLoadIdentity
    glGetFloatv gl_Projection_Matrix, .Matrix3D.XX
    If B Then H1 = 100000 Else H1 = .Altitude
    G1 = Tan(.FOV / 2 * Rad)
    A1 = 1 / G1: If .Ortho And Not B Then A1 = A1 / H1
    .FOVR = G1 / Tan(45 / 2 * Rad)
    If .Width < .Height And Not B Then _
        N = .Height: .Matrix3D.XX = A1 * .Height / .Width: .Matrix3D.YY = A1 _
    Else N = .Width: .Matrix3D.XX = A1: .Matrix3D.YY = A1 * .Width / .Height
    .Matrix3D.ZZ = 0.5
    If .ID = MainID And Asymmetric <> 0 And Not B Then _
        If .Ortho Then .Matrix3D.WX = Asymmetric Else .Matrix3D.ZX = -Asymmetric
    .Square = N
    If .Ortho And Not B Then
        Near = H1 / 5000000: Far = Near * 1000000000
        .Matrix3D.ZZ = -(2 / (Far - Near))
        .Matrix3D.WZ = 0: .Matrix3D.WZ = 0: .Matrix3D.WW = 1
    Else
        Near = H1 / 50 ': If Near < ZoomMin Then Near = ZoomMin
        Far = Near * 50000000: If Far < H1 Then Far = H1
        .Matrix3D.ZZ = -((Far + Near) / (Far - Near))
        .Matrix3D.WZ = -((Far * Near * 2) / (Far - Near))
        .Matrix3D.ZW = -1: .Matrix3D.WW = 0
        End If
    glLoadMatrixf .Matrix3D.XX
    
    End With
    End Sub

Sub ActivateView(Cam As Camera, _
        Optional ByVal Update As Boolean = True, Optional ByVal IsSensor As Boolean)
    If Main.WindowState = vbMinimized Or Cam1.hRC = 0 Then Exit Sub
    With Cam
    If .ID = GraphID Then DeleteList Graph2D(.N).List, 2
    If .hRC Then
        If IsSensor And .Follow <> ToOnBoard Then FollowMode Cam, ToOnBoard: GoTo Ext
        If wglGetCurrentContext <> Cam1.hRC Then wglMakeCurrent Cam1.hDC, Cam1.hRC
        wglDeleteContext .hRC: .hRC = 0
        If Not .F Is Nothing Then .F.Hide
    ElseIf Not .F Is Nothing Then
        .hDC = .F.hDC
        On Error Resume Next: .hRC = InitGL(.hDC, App.hInstance, -Antialias): On Error GoTo 0
        If .hRC = 0 Then GoTo Ext
        wglCopyContext Cam1.hRC, .hRC, gl_All_Attrib_Bits
        wglShareLists Cam1.hRC, .hRC
        'If .ID = GraphID Then DeleteList Graph2D(.N).List, 2
        If .ID = ViewID And .Altitude = 0 Then CloneView Cam1, Cam
        If IsSensor Then If .Follow <> ToOnBoard Then FollowMode Cam, ToOnBoard
        .F.Show vbModeless, Main
        .F.Form_Resize
        If wglGetCurrentContext <> Cam1.hRC Then wglMakeCurrent Cam1.hDC, Cam1.hRC
        End If
    End With
Ext:
    If Update Then If Options.SnapPanels Then ResizeMain
    End Sub

Sub AddView(Optional ByVal Update As Boolean = True, Optional ByVal Num As Long = -1)
    Dim C As Long, N As Long
    If Not View.Visible Then GoTo Ext
    For N = 1 To CamN - 1
        If Cam(N).hRC = 0 Then GoTo Nxt
        Next
    N = CamN: ReDim Preserve Cam(N): CamN = CamN + 1
    With Cam(N)
    .ID = ViewID: .N = N: Set .F = New View: .F.N = N: Load .F
    .F.BackColor = View.BackColor: .F.ForeColor = View.ForeColor: Set .F.Font = View.Font
    .F.Move View.Left - ((N - 1) * 25) * TwipX, _
        View.Top + IfSng(View.Visible, View.Height, 0) + (10 + (N - 1) * 25) * TwipY
    End With
Nxt:
    If Cam(N).Altitude = 0 Then C = IfInt(Num >= 0 And Num < CamN, Num, 0): _
        CopyView Cam(C), Cam(N), True
Ext:
    ActivateView Cam(N), Update
    End Sub

Sub AddGraph(Optional ByVal Update As Boolean = True, Optional ByVal Num As Long = -1)
    Dim C As Long, N As Long
    If Not Graph3D.Visible Then GoTo Ext
    For N = 1 To GraN - 1
        If Gra(N).hRC = 0 Then GoTo Nxt
        Next
    N = GraN: ReDim Preserve Gra(N): ReDim Preserve Graph2D(N): GraN = GraN + 1
    With Gra(N)
    .ID = GraphID: .N = N: Set .F = New Graph3D: .F.N = N: Load .F
    .F.BackColor = Graph3D.BackColor: .F.ForeColor = Graph3D.ForeColor: Set .F.Font = Graph3D.Font
    .F.Move Graph3D.Left - (N * 25) * TwipX, _
        Graph3D.Top - (N * 25) * TwipY
    End With
Nxt:
    If Gra(N).Altitude = 0 Then C = IfInt(Num >= 0 And Num < GraN, Num, 0): _
        CopyView Gra(C), Gra(N), True: CopyGraph2D Graph2D(C), Graph2D(N)
Ext:
    DeleteList Graph2D(N).List, 2: ActivateView Gra(N), Update
    End Sub

Function CreateView(S As String) As Boolean
    If S = "" Then Exit Function
    ReDim Preserve Cam(CamN)
    With Cam(CamN)
    .ID = ViewID: .N = CamN: Set .F = New View: .F.N = CamN: Load .F
    ResizeForm .F, S
    .Width = .F.ScaleWidth: .Height = .F.ScaleHeight
    .Square = IfInt(.Width < .Height, .Width, .Height)
    End With
    CreateView = True
    End Function

Function CreateGraph(S As String) As Boolean
    If S = "" Then Exit Function
    ReDim Preserve Gra(GraN): ReDim Preserve Graph2D(GraN)
    With Gra(GraN)
    .ID = GraphID: .N = GraN: Set .F = New Graph3D: .F.N = GraN: Load .F
    ResizeForm .F, S
    .Width = .F.ScaleWidth: .Height = .F.ScaleHeight
    .Square = IfInt(.Width < .Height, .Width, .Height)
    End With
    CreateGraph = True
    End Function

Sub LoadView(Cam As Camera, S As String, _
        Optional ByVal ID As CameraID = MainID, Optional ByVal Num As Long, _
        Optional ByVal UseAngles As Boolean = True)
    With Cam
    If S = "" Then GoTo Ext
    If UseAngles Then _
        .AngleU = NumN(S): .AngleV = NumN(S): .AngleW = NumN(S): _
        .X = NumN(S): .Y = NumN(S): .Z = NumN(S): .Altitude = NumN(S)
    .FOV = NumN(S): .FOVR = NumN(S): .ShipSize = NumN(S)
    .Follow = NumN(S): .LookAt = NumN(S): .Relative = NumN(S): .Target = NumN(S)
    .Free = NumL(S): .Flat = NumL(S): .Ortho = NumL(S): .Auto = NumL(S)
    .ID = ID: .N = Num
Ext:
    If .Altitude <= 0 Then .Altitude = 3000000
    If .FOV <= 0 Then .FOV = 45: .FOVR = 1
    If .FOVR <= 0 Then .FOVR = 1
    If .Follow <= 0 Then .Follow = 1
    End With
    End Sub

Function ViewParams(Cam As Camera, Optional ByVal UseAngles As Boolean = True) As String
    With Cam
    If UseAngles Then ViewParams = _
        NumD(.AngleU) & "," & NumD(.AngleV) & "," & NumD(.AngleW) & "," & _
        NumD(.X) & "," & NumD(.Y) & "," & NumD(.Z) & "," & NumD(.Altitude) & ","
    ViewParams = ViewParams & _
        NumD(.FOV) & "," & NumD(.FOVR) & "," & NumD(.ShipSize) & "," & _
        .Follow & "," & .LookAt & "," & .Relative & "," & .Target & "," & _
        -CBool(.Free) & "," & -CBool(.Flat) & "," & -CBool(.Ortho) & "," & -CBool(.Auto)
    End With
    End Function

Sub DeleteView(Cam As Camera, Optional ByVal UnloadForm As Boolean)
    If Cam.ID = GraphID Then DeleteList Graph2D(Cam.N).List, 2
    If Cam.hRC Then ActivateView Cam, False
    If UnloadForm And Not Cam.F Is Nothing Then Unload Cam.F: Set Cam.F = Nothing
    End Sub

Sub DeleteViews()
    Dim N As Long
    For N = 0 To CamN - 1: DeleteView Cam(N): Next
    End Sub

Sub UnloadViews()
    Dim N As Long
    For N = 1 To CamN - 1: DeleteView Cam(N), True: Next
    CamN = 1: ReDim Preserve Cam(0)
    End Sub

Sub DeleteGraphs()
    Dim N As Long
    For N = 0 To GraN - 1: DeleteView Gra(N): Next
    End Sub

Sub UnloadGraphs()
    Dim N As Long
    For N = 1 To GraN - 1: DeleteView Gra(N), True: Next
    GraN = 1: ReDim Preserve Gra(0): ReDim Preserve Graph2D(0)
    End Sub

Sub DeleteGraphsList()
    Dim N As Long
    For N = 0 To GraN - 1: DeleteList Graph2D(N).List, 2: Next
    End Sub

Sub UnloadAll()
    Dim F As Form
    For Each F In Forms: Unload F: Next
    End Sub

Sub UpdateViewTarget()
    Dim S As String, T As String
    If SensorP >= 0 Then S = Sensor(SensorP).Name Else Exit Sub
    Select Case IsTarget
    Case 0
    Case Is < 0:             If TargetsN > 0 Then T = GetName(TargetClass, TargetItem) _
                             Else T = Station(TargetItem).Name
    Case Is < ToEarth:       T = Planets(ToEarth)
    Case ToEarth To ToPluto: T = Planets(IsTarget)
    Case ToObject:           T = GetName(SelectedType, SelectedN)
        End Select
    If T <> "" Then S = S & " - " & T
    If View.Caption <> S Then View.Caption = S
    End Sub

Function IsCamFollow(Optional ByVal Follow As TargetType = ToObject) As Boolean
    Dim N As Long
    If Cam1.Follow = Follow Or Cam(0).Follow = Follow Then IsCamFollow = True: Exit Function
    For N = 0 To CamN - 1
        If Cam(N).Follow = Follow Then IsCamFollow = True: Exit Function
        Next
    End Function

Function IsCamLookAt(Optional ByVal LookAt As TargetType = ToObject) As Boolean
    Dim N As Long
    If Cam1.LookAt = LookAt Or Cam(0).LookAt = LookAt Then IsCamLookAt = True: Exit Function
    For N = 0 To CamN - 1
        If Cam(N).LookAt = LookAt Then IsCamLookAt = True: Exit Function
        Next
    End Function

Sub CopyGraph2D(G1 As Graph2D, G2 As Graph2D)
    DeleteList G2.List: G2 = G1: G2.List = 0
    End Sub

Sub CopyView(Cam1 As Camera, Cam2 As Camera, Optional ByVal CopySize As Boolean)
    Dim F As Form, hRC As Long, hDC As Long, N As Long, S As Long, ID As CameraID
    With Cam2: ID = .ID: N = .N: S = .Square: hRC = .hRC: hDC = .hDC: Set F = .F: End With
    Cam2 = Cam1
    With Cam2: .ID = ID: .N = N: .Square = S: .hRC = hRC: .hDC = hDC: Set .F = F: End With
    If CopySize Then If Not Cam1.F Is Nothing And Not Cam2.F Is Nothing Then _
        Resize Cam2.F, Cam1.F.ScaleWidth, Cam1.F.ScaleHeight
    End Sub

Sub CloneView(Cam1 As Camera, Cam2 As Camera, Optional ByVal Redraw As Boolean)
    CopyView Cam1, Cam2: If Redraw Then InitRender: If Play = Paused Then Render
    End Sub

Sub SwapView(Cam1 As Camera, Cam2 As Camera, Optional ByVal Redraw As Boolean)
    Dim Cam As Camera
    CopyView Cam1, Cam: CopyView Cam2, Cam1: CopyView Cam, Cam2
    If Redraw Then InitRender: If Play = Paused Then Render
    End Sub

Sub DeleteCamera(Cam As Camera)
    If Cam.hRC Then wglDeleteContext Cam.hRC: Cam.hRC = 0
    End Sub

Function ToggleOpenGL() As Long
    If Cam1.hRC Then CleanUpOpenGL Else ToggleOpenGL = InitOpenGL
    If Play = Paused Then Render
    Options.UpdateScreen
    End Function

Function RestartOpenGL() As Long
    If Cam1.hRC Then CleanUpOpenGL: RestartOpenGL = InitOpenGL
    End Function

'Function InitVBGL(ByVal hDC As Long, ByVal hInstance As Long, Optional ByVal Flags As Long) As Long
'    'It works in debug mode but it fails in compiled exe
'    Dim P As Long, Pf As PixelFormatDescriptor
'    With Pf
'    .nSize = Len(Pf): .nVersion = 1: If Flags Then .dwFlags = Flags _
'        Else .dwFlags = pfd_Support_OpenGL Or pfd_Draw_To_Window Or pfd_DoubleBuffer
'    .iPixelType = pfd_Type_RGBA: .cColorBits = 32: .cAlphaBits = 8: .cDepthBits = 32
'    .iLayerType = pfd_Main_Plane
'    End With
'    P = ChoosePixelFormat(hDC, Pf): If P Then _
'        If SetPixelFormat(hDC, P, Pf) Then _
'            InitVBGL = wglCreateContext(hDC): If InitVBGL Then _
'                wglMakeCurrent hDC, InitVBGL
'    End Function

Function InitOpenGL(Optional ByVal UseOpenGL As Boolean = True) As Long
    If Not UseOpenGL Then GoTo Ext
    Message "Starting OpenGL …"
    Main.Cls: Main.AutoRedraw = False: DoEvents
    With Cam1: Set .F = Main: .hDC = .F.hDC
    On Error Resume Next: .hRC = InitGL(.hDC, App.hInstance, -Antialias): On Error GoTo 0
    'IsDLL = .hRC: If Not IsDLL Then .hRC = InitVBGL(.hDC, App.hInstance)
    InitOpenGL = .hRC: If .hRC = 0 Then GoTo Ext
    End With
    OpenGLName = GetStringFromPtr(glGetString(gl_Vendor)) & " " & _
        GetStringFromPtr(glGetString(gl_Renderer))
    CreateFonts
    LoadTextures
    CreateModels
    LoadImages
    CreateIcons
    InitRender
Ext:
    Main.AutoRedraw = Cam1.hRC = 0
    Message
    End Function

Sub CleanUpOpenGL()
    If Cam1.hRC = 0 Then Exit Sub
    glBindTexture gl_Texture_2d, 0
    DeleteObjects
    DeleteIcons
    DeleteImages
    DeleteModels
    DeleteTextures
    DeleteFonts
    wglMakeCurrent 0, 0
    DeleteGraphs
    DeleteViews
    DeleteView Cam4
    DeleteView Cam3
    DeleteCamera Cam1
    Main.AutoRedraw = True
    End Sub
