//returns a new Orbit object that represents the result of applying a given dV to o at UT public static Orbit PerturbedOrbit(this Orbit o, double UT, Vector3d dV) { //should these in fact be swapped? return(MuUtils.OrbitFromStateVectors(o.SwappedAbsolutePositionAtUT(UT), o.SwappedOrbitalVelocityAtUT(UT) + dV, o.referenceBody, UT)); }
//Computes the time and delta-V of an ejection burn to a Hohmann transfer from one planet to another. //It's assumed that the initial orbit around the first planet is circular, and that this orbit //is in the same plane as the orbit of the first planet around the sun. It's also assumed that //the target planet has a fairly low relative inclination with respect to the first planet. If the //inclination change is nonzero you should also do a mid-course correction burn, as computed by //DeltaVForCourseCorrection. public static Vector3d DeltaVAndTimeForInterplanetaryTransferEjection(Orbit o, double UT, Orbit target, bool syncPhaseAngle, out double burnUT) { Orbit planetOrbit = o.referenceBody.orbit; //Compute the time and dV for a Hohmann transfer where we pretend that we are the planet we are orbiting. //This gives us the "ideal" deltaV and UT of the ejection burn, if we didn't have to worry about waiting for the right //ejection angle and if we didn't have to worry about the planet's gravity dragging us back and increasing the required dV. double idealBurnUT; Vector3d idealDeltaV; if (syncPhaseAngle) { //time the ejection burn to intercept the target idealDeltaV = DeltaVAndTimeForHohmannTransfer(planetOrbit, target, UT, out idealBurnUT); } else { //don't time the ejection burn to intercept the target; we just care about the final peri/apoapsis idealBurnUT = UT; if (target.semiMajorAxis < planetOrbit.semiMajorAxis) { idealDeltaV = DeltaVToChangePeriapsis(planetOrbit, idealBurnUT, target.semiMajorAxis); } else { idealDeltaV = DeltaVToChangeApoapsis(planetOrbit, idealBurnUT, target.semiMajorAxis); } } //Compute the actual transfer orbit this ideal burn would lead to. Orbit transferOrbit = planetOrbit.PerturbedOrbit(idealBurnUT, idealDeltaV); //Now figure out how to approximately eject from our current orbit into the Hohmann orbit we just computed. //Assume we want to exit the SOI with the same velocity as the ideal transfer orbit at idealUT -- i.e., immediately //after the "ideal" burn we used to compute the transfer orbit. This isn't quite right. //We intend to eject from our planet at idealUT and only several hours later will we exit the SOI. Meanwhile //the transfer orbit will have acquired a slightly different velocity, which we should correct for. Maybe //just add in (1/2)(sun gravity)*(time to exit soi)^2 ? But how to compute time to exit soi? Or maybe once we //have the ejection orbit we should just move the ejection burn back by the time to exit the soi? Vector3d soiExitVelocity = idealDeltaV; //project the desired exit direction into the current orbit plane to get the feasible exit direction Vector3d inPlaneSoiExitDirection = Vector3d.Exclude(o.SwappedOrbitNormal(), soiExitVelocity).normalized; //compute the angle by which the trajectory turns between periapsis (where we do the ejection burn) //and SOI exit (approximated as radius = infinity) double soiExitEnergy = 0.5 * soiExitVelocity.sqrMagnitude - o.referenceBody.gravParameter / o.referenceBody.sphereOfInfluence; double ejectionRadius = o.semiMajorAxis; //a guess, good for nearly circular orbits double ejectionKineticEnergy = soiExitEnergy + o.referenceBody.gravParameter / ejectionRadius; double ejectionSpeed = Math.Sqrt(2 * ejectionKineticEnergy); //construct a sample ejection orbit Vector3d ejectionOrbitInitialVelocity = ejectionSpeed * (Vector3d)o.referenceBody.transform.right; Vector3d ejectionOrbitInitialPosition = o.referenceBody.position + ejectionRadius * (Vector3d)o.referenceBody.transform.up; Orbit sampleEjectionOrbit = MuUtils.OrbitFromStateVectors(ejectionOrbitInitialPosition, ejectionOrbitInitialVelocity, o.referenceBody, 0); double ejectionOrbitDuration = sampleEjectionOrbit.NextTimeOfRadius(0, o.referenceBody.sphereOfInfluence); Vector3d ejectionOrbitFinalVelocity = sampleEjectionOrbit.SwappedOrbitalVelocityAtUT(ejectionOrbitDuration); double turningAngle = Math.Abs(Vector3d.Angle(ejectionOrbitInitialVelocity, ejectionOrbitFinalVelocity)); //rotate the exit direction by 90 + the turning angle to get a vector pointing to the spot in our orbit //where we should do the ejection burn. Then convert this to a true anomaly and compute the time closest //to planetUT at which we will pass through that true anomaly. Vector3d ejectionPointDirection = Quaternion.AngleAxis(-(float)(90 + turningAngle), o.SwappedOrbitNormal()) * inPlaneSoiExitDirection; double ejectionTrueAnomaly = o.TrueAnomalyFromVector(ejectionPointDirection); burnUT = o.TimeOfTrueAnomaly(ejectionTrueAnomaly, idealBurnUT - o.period); if ((idealBurnUT - burnUT > o.period / 2) || (burnUT < UT)) { burnUT += o.period; } //rotate the exit direction by the turning angle to get a vector pointing to the spot in our orbit //where we should do the ejection burn Vector3d ejectionBurnDirection = Quaternion.AngleAxis(-(float)(turningAngle), o.SwappedOrbitNormal()) * inPlaneSoiExitDirection; Vector3d ejectionVelocity = ejectionSpeed * ejectionBurnDirection; Vector3d preEjectionVelocity = o.SwappedOrbitalVelocityAtUT(burnUT); return(ejectionVelocity - preEjectionVelocity); }
//Computes the time and delta-V of an ejection burn to a Hohmann transfer from one planet to another. //It's assumed that the initial orbit around the first planet is circular, and that this orbit //is in the same plane as the orbit of the first planet around the sun. It's also assumed that //the target planet has a fairly low relative inclination with respect to the first planet. If the //inclination change is nonzero you should also do a mid-course correction burn, as computed by //DeltaVForCourseCorrection. public static Vector3d DeltaVAndTimeForInterplanetaryLambertTransferEjection(Orbit o, double UT, Orbit target, out double burnUT) { Orbit planetOrbit = o.referenceBody.orbit; //Compute the time and dV for a Hohmann transfer where we pretend that we are the planet we are orbiting. //This gives us the "ideal" deltaV and UT of the ejection burn, if we didn't have to worry about waiting for the right //ejection angle and if we didn't have to worry about the planet's gravity dragging us back and increasing the required dV. double idealBurnUT; Vector3d idealDeltaV; //time the ejection burn to intercept the target //idealDeltaV = DeltaVAndTimeForHohmannTransfer(planetOrbit, target, UT, out idealBurnUT); double vesselOrbitVelocity = OrbitalManeuverCalculator.CircularOrbitSpeed(o.referenceBody, o.semiMajorAxis); idealDeltaV = DeltaVAndTimeForHohmannLambertTransfer(planetOrbit, target, UT, out idealBurnUT, vesselOrbitVelocity); Debug.Log("idealBurnUT = " + idealBurnUT + ", idealDeltaV = " + idealDeltaV); //Compute the actual transfer orbit this ideal burn would lead to. Orbit transferOrbit = planetOrbit.PerturbedOrbit(idealBurnUT, idealDeltaV); //Now figure out how to approximately eject from our current orbit into the Hohmann orbit we just computed. //Assume we want to exit the SOI with the same velocity as the ideal transfer orbit at idealUT -- i.e., immediately //after the "ideal" burn we used to compute the transfer orbit. This isn't quite right. //We intend to eject from our planet at idealUT and only several hours later will we exit the SOI. Meanwhile //the transfer orbit will have acquired a slightly different velocity, which we should correct for. Maybe //just add in (1/2)(sun gravity)*(time to exit soi)^2 ? But how to compute time to exit soi? Or maybe once we //have the ejection orbit we should just move the ejection burn back by the time to exit the soi? Vector3d soiExitVelocity = idealDeltaV; Debug.Log("soiExitVelocity = " + (Vector3)soiExitVelocity); //compute the angle by which the trajectory turns between periapsis (where we do the ejection burn) //and SOI exit (approximated as radius = infinity) double soiExitEnergy = 0.5 * soiExitVelocity.sqrMagnitude - o.referenceBody.gravParameter / o.referenceBody.sphereOfInfluence; double ejectionRadius = o.semiMajorAxis; //a guess, good for nearly circular orbits Debug.Log("soiExitEnergy = " + soiExitEnergy); Debug.Log("ejectionRadius = " + ejectionRadius); double ejectionKineticEnergy = soiExitEnergy + o.referenceBody.gravParameter / ejectionRadius; double ejectionSpeed = Math.Sqrt(2 * ejectionKineticEnergy); Debug.Log("ejectionSpeed = " + ejectionSpeed); //construct a sample ejection orbit Vector3d ejectionOrbitInitialVelocity = ejectionSpeed * (Vector3d)o.referenceBody.transform.right; Vector3d ejectionOrbitInitialPosition = o.referenceBody.position + ejectionRadius * (Vector3d)o.referenceBody.transform.up; Orbit sampleEjectionOrbit = MuUtils.OrbitFromStateVectors(ejectionOrbitInitialPosition, ejectionOrbitInitialVelocity, o.referenceBody, 0); double ejectionOrbitDuration = sampleEjectionOrbit.NextTimeOfRadius(0, o.referenceBody.sphereOfInfluence); Vector3d ejectionOrbitFinalVelocity = sampleEjectionOrbit.SwappedOrbitalVelocityAtUT(ejectionOrbitDuration); double turningAngle = Vector3d.Angle(ejectionOrbitInitialVelocity, ejectionOrbitFinalVelocity); Debug.Log("turningAngle = " + turningAngle); //sine of the angle between the vessel orbit and the desired SOI exit velocity double outOfPlaneAngle = (Math.PI / 180) * (90 - Vector3d.Angle(soiExitVelocity, o.SwappedOrbitNormal())); Debug.Log("outOfPlaneAngle (rad) = " + outOfPlaneAngle); double coneAngle = Math.PI / 2 - (Math.PI / 180) * turningAngle; Debug.Log("coneAngle (rad) = " + coneAngle); Vector3d exitNormal = Vector3d.Cross(-soiExitVelocity, o.SwappedOrbitNormal()).normalized; Vector3d normal2 = Vector3d.Cross(exitNormal, -soiExitVelocity).normalized; //unit vector pointing to the spot on our orbit where we will burn. //fails if outOfPlaneAngle > coneAngle. Vector3d ejectionPointDirection = Math.Cos(coneAngle) * (-soiExitVelocity.normalized) + Math.Cos(coneAngle) * Math.Tan(outOfPlaneAngle) * normal2 - Math.Sqrt(Math.Pow(Math.Sin(coneAngle), 2) - Math.Pow(Math.Cos(coneAngle) * Math.Tan(outOfPlaneAngle), 2)) * exitNormal; Debug.Log("soiExitVelocity = " + (Vector3)soiExitVelocity); Debug.Log("vessel orbit normal = " + (Vector3)(1000 * o.SwappedOrbitNormal())); Debug.Log("exitNormal = " + (Vector3)(1000 * exitNormal)); Debug.Log("normal2 = " + (Vector3)(1000 * normal2)); Debug.Log("ejectionPointDirection = " + ejectionPointDirection); double ejectionTrueAnomaly = o.TrueAnomalyFromVector(ejectionPointDirection); burnUT = o.TimeOfTrueAnomaly(ejectionTrueAnomaly, idealBurnUT - o.period); if ((idealBurnUT - burnUT > o.period / 2) || (burnUT < UT)) { burnUT += o.period; } Vector3d ejectionOrbitNormal = Vector3d.Cross(ejectionPointDirection, soiExitVelocity).normalized; Debug.Log("ejectionOrbitNormal = " + ejectionOrbitNormal); Vector3d ejectionBurnDirection = Quaternion.AngleAxis(-(float)(turningAngle), ejectionOrbitNormal) * soiExitVelocity.normalized; Debug.Log("ejectionBurnDirection = " + ejectionBurnDirection); Vector3d ejectionVelocity = ejectionSpeed * ejectionBurnDirection; Vector3d preEjectionVelocity = o.SwappedOrbitalVelocityAtUT(burnUT); return(ejectionVelocity - preEjectionVelocity); }
public Orbit EndOrbit() { return(MuUtils.OrbitFromStateVectors(WorldEndPosition(), WorldEndVelocity(), body, endUT)); }
protected void MaintainAerobrakeNode() { if (makeAerobrakeNodes) { //Remove node after finishing aerobraking: if (aerobrakeNode != null && vessel.patchedConicSolver.maneuverNodes.Contains(aerobrakeNode)) { if (aerobrakeNode.UT < vesselState.time && vesselState.altitudeASL > mainBody.RealMaxAtmosphereAltitude()) { aerobrakeNode.RemoveSelf(); aerobrakeNode = null; } } //Update or create node if necessary: ReentrySimulation.Result r = Result; if (r != null && r.outcome == ReentrySimulation.Outcome.AEROBRAKED) { //Compute the node dV: Orbit preAerobrakeOrbit = GetReenteringPatch(); //Put the node at periapsis, unless we're past periapsis. In that case put the node at the current time. double UT; if (preAerobrakeOrbit == orbit && vesselState.altitudeASL < mainBody.RealMaxAtmosphereAltitude() && vesselState.speedVertical > 0) { UT = vesselState.time; } else { UT = preAerobrakeOrbit.NextPeriapsisTime(preAerobrakeOrbit.StartUT); } Orbit postAerobrakeOrbit = MuUtils.OrbitFromStateVectors(r.WorldAeroBrakePosition(), r.WorldAeroBrakeVelocity(), r.body, r.aeroBrakeUT); Vector3d dV = OrbitalManeuverCalculator.DeltaVToChangeApoapsis(preAerobrakeOrbit, UT, postAerobrakeOrbit.ApR); if (aerobrakeNode != null && vessel.patchedConicSolver.maneuverNodes.Contains(aerobrakeNode)) { //update the existing node Vector3d nodeDV = preAerobrakeOrbit.DeltaVToManeuverNodeCoordinates(UT, dV); aerobrakeNode.UpdateNode(nodeDV, UT); } else { //place a new node aerobrakeNode = vessel.PlaceManeuverNode(preAerobrakeOrbit, dV, UT); } } else { //no aerobraking, remove the node: if (aerobrakeNode != null && vessel.patchedConicSolver.maneuverNodes.Contains(aerobrakeNode)) { aerobrakeNode.RemoveSelf(); } } } else { //Remove aerobrake node when it is turned off: if (aerobrakeNode != null && vessel.patchedConicSolver.maneuverNodes.Contains(aerobrakeNode)) { aerobrakeNode.RemoveSelf(); } } }