Kitten Space Agency mod to load real Artemis II orbit data
at main 155 lines 6.8 kB view raw
1using System.Runtime.InteropServices; 2using Brutal.ImGuiApi; 3using Brutal.Numerics; 4using KSA; 5 6using static Brutal.Logging.DefaultCategory; 7 8namespace OemLoader; 9 10public class MainWindow(string title) 11{ 12 public bool Typing { get; private set; } 13 public bool Open; 14 public bool DrawRealTrajectory; 15 16 private readonly ImInputString _filename = new(short.MaxValue); 17 public EphemerisTrajectory? Ephemeris { get; private set; } 18 private string? _errorMessage; 19 private long _sliderTicks; 20 21 // Assume the ephemeris is for the home body of the system (i.e. Earth), fall back to vehicle's current parent 22 // if null 23 public IParentBody? ParentBody => Universe.CurrentSystem?.HomeBody ?? Program.ControlledVehicle?.Parent; 24 25 26 private void GoToTime(DateTime time) 27 { 28 if (Program.ControlledVehicle is null || Ephemeris is not EphemerisTrajectory oem) return; 29 var body = ParentBody; 30 if (body is null) return; 31 32 var orb = oem.OrbitForTime(time, body, Program.ControlledVehicle.Orbit.OrbitLineColor); 33 if (orb is null) return; 34 var simTime = new SimTime((time - OemLoader.KsaEpoch).TotalSeconds); 35 36 // If the position at the provided time is within the SOI of a child body, transform the orbit to 37 // an orbit around the child body. 38 // (Code mostly stolen from KSA.KinematicStates.CheckSoiTransitions) 39 var posCci = orb.GetStateVectorsAt(simTime).PositionCci; 40 foreach (var child in body.Children) 41 { 42 if (child is not Celestial childCelestial) continue; 43 var soi = childCelestial.SphereOfInfluence; 44 var childPos = child.GetPositionOrb(simTime).Transform(childCelestial.Orbit.GetOrb2ParentCci()); 45 var distance = (posCci - childPos).Length(); 46 47 if (distance > soi) continue; 48 var rotation = doubleQuat.Concatenate(childCelestial.GetCci2Orb(), childCelestial.Orbit.Orb2ParentCci) 49 .Inverse(); 50 var newPosition = (posCci - childPos).Transform(rotation); 51 var newVelocity = (orb.GetVelocityCciAt(simTime) - childCelestial.GetVelocityCci(simTime)).Transform(rotation); 52 orb = Orbit.CreateFromStateCci(childCelestial, simTime, newPosition, newVelocity, orb.OrbitLineColor); 53 } 54 55 // Go to the specified time (thanks to UniversePatch.ApplyVehicleSolversTranspiler this works 56 // even for negative values of dt) 57 var dt = simTime.Seconds() - Universe.GetElapsedSeconds(); 58 // This is similar to the implementation of the stock `simulate` console command, but we 59 // reset the simulation speed to 1x to avoid the bug in that command where the time step 60 // gets multiplied by the simulation speed 61 var step = Universe.GetJobSimStep(dt); 62 Universe.ApplyVehicleSolvers(); 63 Universe.ApplyOrbitSolvers(); 64 Universe.ResetSimulationSpeed(); 65 Universe.ExecuteNextOrbitSolvers(dt, step); 66 Universe.ExecuteNextVehicleSolvers(dt, step); 67 Universe.ApplyVehicleSolvers(); 68 Universe.ApplyOrbitSolvers(); 69 // Finally, teleport the vehicle onto the orbit calculated from the ephemeris 70 Program.ControlledVehicle.Teleport(orb, null, null); 71 Log.Debug($"OemLoader: Jumped to time {time} from ephemeris"); 72 } 73 74 public void Render() 75 { 76 Typing = false; 77 if (!Open || !Program.DrawUI) return; 78 79 ImGui.SetNextWindowSize(new float2(500, 300), ImGuiCond.Once); 80 ImGui.SetNextWindowSizeConstraints(new float2(200), new float2(2000)); 81 if (ImGui.Begin(title, ref Open)) 82 { 83 var windows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 84 ImGui.InputTextWithHint("OEM file path", windows ? @"C:\Users\User\OEM.asc" : "/home/user/OEM.asc", _filename); 85 86 if (ImGui.Button("Load OEM file")) 87 { 88 try 89 { 90 Log.Debug($"OemLoader: Attempting to load ephemeris from {_filename.ToString()}..."); 91 Ephemeris = EphemerisTrajectory.ReadFile(_filename.ToString()); 92 if (Ephemeris is EphemerisTrajectory oem) 93 { 94 _sliderTicks = oem.StartTime.Ticks; 95 _errorMessage = null; 96 Log.Info($"OemLoader: Loaded ephemeris from {_filename.ToString()}" 97 + $" ({oem.Count} entries from {oem.StartTime} to {oem.EndTime})"); 98 } 99 else 100 { 101 _errorMessage = "Could not find any ephemeris data in file"; 102 Log.Warning($"OemLoader: Could not find any ephemeris data in {_filename.ToString()}"); 103 } 104 } 105 catch (Exception e) 106 { 107 _errorMessage = $"Failed to open file:\n{e}"; 108 Log.Warning($"OemLoader: Failed to open ephemeris file {_filename.ToString()}:\n{e}"); 109 } 110 } 111 112 if (_errorMessage is not null) 113 { 114 ImGui.TextColored(new float4(0.863f, 0.196f, 0.184f, 1f), _errorMessage); 115 } 116 117 if (Ephemeris is EphemerisTrajectory traj && Program.ControlledVehicle is not null) 118 { 119 ImGui.Text($"Loaded ephemeris valid from {traj.StartTime} to {traj.EndTime}"); 120 121 ImGui.Checkbox("Draw trajectory", ref DrawRealTrajectory); 122 123 var now = DateTime.UtcNow; 124 if (traj.StartTime <= now && now <= traj.EndTime) 125 { 126 if (ImGui.Button("Go to now")) 127 GoToTime(now); 128 } 129 130 var minTicks = traj.StartTime.Ticks; 131 var maxTicks = traj.EndTime.Ticks; 132 unsafe 133 { 134 fixed (long* pTicks = &_sliderTicks) 135 ImGui.SliderScalar( 136 "Time", 137 ImGuiDataType.U64, 138 pTicks, 139 &minTicks, 140 &maxTicks, 141 "", 142 ImGuiSliderFlags.AlwaysClamp | ImGuiSliderFlags.NoRoundToFormat 143 ); 144 } 145 146 var sliderTime = new DateTime(_sliderTicks); 147 if (ImGui.Button($"Go to {sliderTime}")) 148 GoToTime(sliderTime); 149 } 150 151 Typing = ImGui.IsWindowFocused() && ImGui.GetIO().WantCaptureKeyboard; 152 } 153 ImGui.End(); 154 } 155}