private static void AdjustManeuver(BurnModel burn, Vector3d direction, double fraction = 1.0) { const double DELTA_V_INCREMENT_LARGE = 0.5, DELTA_V_INCREMENT_SMALL = 0.01; if (burn != null && FlightGlobals.ActiveVessel != null) { ManeuverNode n = burn?.node; if (n != null) { if (GameSettings.MODIFIER_KEY.GetKey()) { n.DeltaV += DELTA_V_INCREMENT_SMALL * fraction * direction; } else { n.DeltaV += DELTA_V_INCREMENT_LARGE * fraction * direction; } if (n.attachedGizmo != null) { n.attachedGizmo.DeltaV = n.DeltaV; try { n.OnGizmoUpdated(n.DeltaV, burn.atTime ?? 0); } catch (Exception ex) { DbgExc("Problem updating gizmo", ex); } } n.solver.UpdateFlightPlan(); } } }
/// <summary> /// Calculate the time and delta V of the burn needed to transfer. /// </summary> public void CalculateEjectionBurn() { if (origin != null) { ejectionBurn = GenerateEjectionBurn(origin.GetOrbit()); } else { ejectionBurn = null; } if (planeChangeBurn != null && ejectionBurn != null) { if (planeChangeBurn.atTime < ejectionBurn.atTime) { DbgFmt("Resetting plane change burn because it's too early now"); planeChangeBurn = null; } } }
/// <summary> /// Calculate the time and delta V of the burn needed to change planes. /// </summary> public void CalculatePlaneChangeBurn() { if (FlightGlobals.ActiveVessel?.patchedConicSolver?.maneuverNodes != null && transferDestination != null && transferParent != null && destination.GetOrbit().eccentricity < 1) { bool ejectionAlreadyActive = false; if (FlightGlobals.ActiveVessel.patchedConicSolver.maneuverNodes.Count > 0) { if (Settings.Instance.DeleteExistingManeuvers) { ClearManeuverNodes(); } else if (FlightGlobals.ActiveVessel.patchedConicSolver.maneuverNodes.Count == 1 && ejectionBurn.node != null) { ejectionAlreadyActive = true; } else { // At least one unrelated maneuver is active, and we're not allowed to delete them. // Can't activate ejection burn for calculation. return; } } if (ejectionBurn != null) { ManeuverNode eNode; if (ejectionAlreadyActive) { eNode = ejectionBurn.node; } else { DbgFmt("Temporarily activating ejection burn to {0}", destination.GetName()); eNode = ejectionBurn.ToActiveManeuver(); DbgFmt("Activated ejection burn to {0}", destination.GetName()); } if (eNode != null) { if (eNode.nextPatch == null) { DbgFmt("This node goes nowhere."); } // Find the orbit patch that intersects the target orbit for (Orbit o = eNode.nextPatch; o != null; o = NextPatch(o)) { // Skip the patches that are in the wrong SoI if (o.referenceBody == transferParent) { DbgFmt("Identified matching reference body for {0}", transferParent.GetName()); // Find the AN or DN bool ascendingNode; double planeTime = TimeOfPlaneChange(o, transferDestination.GetOrbit(), ejectionBurn.atTime ?? 0, out ascendingNode); DbgFmt("Pinpointed plane change for {0}", transferParent.GetName()); if (planeTime > 0 && planeTime > ejectionBurn.atTime) { double magnitude = PlaneChangeDeltaV(o, transferDestination.GetOrbit(), planeTime, ascendingNode); // Don't bother to create tiny maneuver nodes if (Math.Abs(magnitude) > 0.05) { // Add a maneuver node to change planes planeChangeBurn = new BurnModel(planeTime, 0, magnitude); DbgFmt("Transmitted correction burn for {0}: {1}", transferDestination.GetName(), magnitude); } else { planeChangeBurn = null; DbgFmt("No plane change needed for {0}", transferDestination.GetName()); } // Stop looping through orbit patches since we found what we want break; } else { DbgFmt("Plane change burn would be before the ejection burn, skipping"); } } else { DbgFmt("Skipping a patch with the wrong parent body"); } } } else { DbgFmt("Ejection burn existed but generated a null node"); } if (!ejectionAlreadyActive) { // Clean up the node since we're just doing calculations, not intending to set things up for the user ejectionBurn.RemoveNode(); } DbgFmt("Released completed transfer to {0}", destination.GetName()); } else { DbgFmt("Ejection burn is missing somehow"); } } else { DbgFmt("Can't do a plane change without a vessel"); } }
private BurnModel GenerateEjectionBurnFromOrbit(Orbit currentOrbit, bool fakeOrbit = false) { DbgFmt("Looking for a route from {0} to {1}, via {2}", TheName(origin), TheName(destination), TheName(currentOrbit.referenceBody)); if (currentOrbit == null) { DbgFmt("Skipping transfer from null starting orbit."); // Sanity check just in case something unexpected happens. return(null); } else if (destination == null) { DbgFmt("Skipping transfer to null destination."); // Sanity check just in case something unexpected happens. return(null); } else if (destination.GetOrbit().eccentricity > 1) { DbgFmt("{0} is on an escape trajectory; bailing", TheName(destination)); return(null); } else if (currentOrbit.eccentricity > 1.0) { return(PlotCaptureBurn(currentOrbit)); } else { BurnModel b = FindIntermediateDestination(currentOrbit.referenceBody, currentOrbit); if (b != null) { // If that function generated a burn, that means this destination // is a "return to parent" scenario. // Otherwise we have to continue calculating. DbgFmt("Got return to parent burn"); return(b); } // The above function sets this if we're in the transfer patch. if (transferDestination != null) { // Base case - calculate a simple Hohmann transfer retrogradeTransfer = (currentOrbit.GetRelativeInclination(transferDestination.GetOrbit()) > 90f); if (!retrogradeTransfer) { return(PlotTransferBurn(currentOrbit)); } else { return(PlotRetrogradeTransferBurn(currentOrbit)); } } else { // Recursive case - get an orbit from the parent body and adjust it for ejection from here DbgFmt("Direct route to {0} not found, recursing through parent {1}", TheName(destination), TheName(currentOrbit.referenceBody)); return(PlotEjectionBurn(currentOrbit, fakeOrbit)); } } }
private BurnModel PlotLaunchToEjection() { DbgFmt("Launching to ejection"); double now = Planetarium.GetUniversalTime(); bool haveVessel = (origin.GetVessel() != null); CelestialBody body = origin as CelestialBody ?? origin.GetOrbit().referenceBody; double targetRadius = GoodLowOrbitRadius(body); bool atHome = (body == FlightGlobals.GetHomeBody()); bool haveLongitude = haveVessel || atHome; if (!body.rotates) { // A non-rotating body means we never get any closer to the // point where we want to escape. No calculation possible. // (This also lets us sidestep a divide by zero risk.) return(null); } // This will give us a burn with the right delta V from low orbit. // The time will be now plus the time it takes to get from the absolute // reference direction to the burn at the orbital speed of fakeOrbit. Orbit fakeOrbit = new Orbit(0, 0, targetRadius, 0, 0, 0, 0, body); // This variable prevents infinite recursion between a body and fake orbits around it. BurnModel ejection = GenerateEjectionBurnFromOrbit(fakeOrbit, true); DbgFmt("Ejection time null: {0}", (ejection.atTime == null)); if (haveLongitude && ejection.atTime != null) { DbgFmt("Using real longitude and ejection time"); double startingLongitude = haveVessel ? origin.GetVessel().longitude : atHome ? SpaceCenter.Instance.Longitude : 0; // Now we figure out where the vessel (or KSC) will be at the time of that burn. double currentPhaseAngle = AbsolutePhaseAngle( body, ejection.atTime ?? now, startingLongitude ); // This will tell us approximately where our ship should be to launch double targetAbsolutePhaseAngle = AbsolutePhaseAngle(fakeOrbit, ejection.atTime ?? now); // This tells us how fast the body rotates. // Note that we already have our divide-by-zero guard above. double phaseAnglePerSecond = Tau / body.rotationPeriod; // Now we adjust the original burn time to account for the planet rotating // into position for us. double burnTime = (ejection.atTime ?? now) + clamp(targetAbsolutePhaseAngle - currentPhaseAngle) / phaseAnglePerSecond; // Finally, generate the real burn if it seems OK. if (burnTime < now) { return(null); } else { return(new BurnModel( burnTime, ejection.prograde + DeltaVToOrbit(body) )); } } else { DbgFmt("Longitude or ejection time missing, using outer burn time"); // If we're at a body with no launch pad, we can't time the maneuver, // so just use what we got from the outer burn. return(new BurnModel( ejection.atTime, ejection.prograde + DeltaVToOrbit(body) )); } }
private BurnModel PlotEjectionBurn(Orbit currentOrbit, bool fakeOrbit = false) { double now = Planetarium.GetUniversalTime(); BurnModel outerBurn = GenerateEjectionBurnFromOrbit(ParentOrbit(currentOrbit)); if (outerBurn != null) { DbgFmt("Got route from {0}, calculating ejection", TheName(currentOrbit.referenceBody)); double angleOffset = outerBurn.prograde < 0 ? 0 : -Math.PI; // The angle, position, and time are interdependent. // So we seed them with parameters from the outer burn, then // cross-seed them with each other this many times. // Cross your fingers and hope this converges to the right answer. const int iterations = 6; if (fakeOrbit && outerBurn.atTime == null) { // We have absolutely no basis for a time, so don't fake it. return(new BurnModel( null, BurnToEscape( currentOrbit.referenceBody, currentOrbit.ApR, outerBurn.totalDeltaV ) )); } else { // Either the current orbit is real, or the outer burn constrains us, // so we can use those times. double burnTime = outerBurn.atTime ?? now; try { for (int i = 0; i < iterations; ++i) { double ejectionAngle = EjectionAngle( currentOrbit.referenceBody, RadiusAtTime(currentOrbit, burnTime), outerBurn.totalDeltaV); burnTime = TimeAtAngleFromMidnight( currentOrbit.referenceBody.orbit, currentOrbit, burnTime, ejectionAngle + angleOffset); } } catch (Exception ex) { DbgExc("Problem with ejection calc", ex); } return(new BurnModel( burnTime, BurnToEscape( currentOrbit.referenceBody, currentOrbit, outerBurn.totalDeltaV, burnTime ) )); } } else { DbgFmt("No outer burn found"); return(null); } }