using System.Runtime.InteropServices; using Brutal.ImGuiApi; using Brutal.Numerics; using KSA; using static Brutal.Logging.DefaultCategory; namespace OemLoader; public class MainWindow(string title) { public bool Typing { get; private set; } public bool Open; public bool DrawRealTrajectory; private readonly ImInputString _filename = new(short.MaxValue); public EphemerisTrajectory? Ephemeris { get; private set; } private string? _errorMessage; private long _sliderTicks; // Assume the ephemeris is for the home body of the system (i.e. Earth), fall back to vehicle's current parent // if null public IParentBody? ParentBody => Universe.CurrentSystem?.HomeBody ?? Program.ControlledVehicle?.Parent; private void GoToTime(DateTime time) { if (Program.ControlledVehicle is null || Ephemeris is not EphemerisTrajectory oem) return; var body = ParentBody; if (body is null) return; var orb = oem.OrbitForTime(time, body, Program.ControlledVehicle.Orbit.OrbitLineColor); if (orb is null) return; var simTime = new SimTime((time - OemLoader.KsaEpoch).TotalSeconds); // If the position at the provided time is within the SOI of a child body, transform the orbit to // an orbit around the child body. // (Code mostly stolen from KSA.KinematicStates.CheckSoiTransitions) var posCci = orb.GetStateVectorsAt(simTime).PositionCci; foreach (var child in body.Children) { if (child is not Celestial childCelestial) continue; var soi = childCelestial.SphereOfInfluence; var childPos = child.GetPositionOrb(simTime).Transform(childCelestial.Orbit.GetOrb2ParentCci()); var distance = (posCci - childPos).Length(); if (distance > soi) continue; var rotation = doubleQuat.Concatenate(childCelestial.GetCci2Orb(), childCelestial.Orbit.Orb2ParentCci) .Inverse(); var newPosition = (posCci - childPos).Transform(rotation); var newVelocity = (orb.GetVelocityCciAt(simTime) - childCelestial.GetVelocityCci(simTime)).Transform(rotation); orb = Orbit.CreateFromStateCci(childCelestial, simTime, newPosition, newVelocity, orb.OrbitLineColor); } // Go to the specified time (thanks to UniversePatch.ApplyVehicleSolversTranspiler this works // even for negative values of dt) var dt = simTime.Seconds() - Universe.GetElapsedSeconds(); // This is similar to the implementation of the stock `simulate` console command, but we // reset the simulation speed to 1x to avoid the bug in that command where the time step // gets multiplied by the simulation speed var step = Universe.GetJobSimStep(dt); Universe.ApplyVehicleSolvers(); Universe.ApplyOrbitSolvers(); Universe.ResetSimulationSpeed(); Universe.ExecuteNextOrbitSolvers(dt, step); Universe.ExecuteNextVehicleSolvers(dt, step); Universe.ApplyVehicleSolvers(); Universe.ApplyOrbitSolvers(); // Finally, teleport the vehicle onto the orbit calculated from the ephemeris Program.ControlledVehicle.Teleport(orb, null, null); Log.Debug($"OemLoader: Jumped to time {time} from ephemeris"); } public void Render() { Typing = false; if (!Open || !Program.DrawUI) return; ImGui.SetNextWindowSize(new float2(500, 300), ImGuiCond.Once); ImGui.SetNextWindowSizeConstraints(new float2(200), new float2(2000)); if (ImGui.Begin(title, ref Open)) { var windows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); ImGui.InputTextWithHint("OEM file path", windows ? @"C:\Users\User\OEM.asc" : "/home/user/OEM.asc", _filename); if (ImGui.Button("Load OEM file")) { try { Log.Debug($"OemLoader: Attempting to load ephemeris from {_filename.ToString()}..."); Ephemeris = EphemerisTrajectory.ReadFile(_filename.ToString()); if (Ephemeris is EphemerisTrajectory oem) { _sliderTicks = oem.StartTime.Ticks; _errorMessage = null; Log.Info($"OemLoader: Loaded ephemeris from {_filename.ToString()}" + $" ({oem.Count} entries from {oem.StartTime} to {oem.EndTime})"); } else { _errorMessage = "Could not find any ephemeris data in file"; Log.Warning($"OemLoader: Could not find any ephemeris data in {_filename.ToString()}"); } } catch (Exception e) { _errorMessage = $"Failed to open file:\n{e}"; Log.Warning($"OemLoader: Failed to open ephemeris file {_filename.ToString()}:\n{e}"); } } if (_errorMessage is not null) { ImGui.TextColored(new float4(0.863f, 0.196f, 0.184f, 1f), _errorMessage); } if (Ephemeris is EphemerisTrajectory traj && Program.ControlledVehicle is not null) { ImGui.Text($"Loaded ephemeris valid from {traj.StartTime} to {traj.EndTime}"); ImGui.Checkbox("Draw trajectory", ref DrawRealTrajectory); var now = DateTime.UtcNow; if (traj.StartTime <= now && now <= traj.EndTime) { if (ImGui.Button("Go to now")) GoToTime(now); } var minTicks = traj.StartTime.Ticks; var maxTicks = traj.EndTime.Ticks; unsafe { fixed (long* pTicks = &_sliderTicks) ImGui.SliderScalar( "Time", ImGuiDataType.U64, pTicks, &minTicks, &maxTicks, "", ImGuiSliderFlags.AlwaysClamp | ImGuiSliderFlags.NoRoundToFormat ); } var sliderTime = new DateTime(_sliderTicks); if (ImGui.Button($"Go to {sliderTime}")) GoToTime(sliderTime); } Typing = ImGui.IsWindowFocused() && ImGui.GetIO().WantCaptureKeyboard; } ImGui.End(); } }