private IEnumerable <bool> AddPatch(VesselState startingState, DescentProfile profile)
{
if (null == vessel_.patchedConicSolver)
{
UnityEngine.Debug.LogWarning("Trajectories: AddPatch() attempted when patchedConicsSolver is null; Skipping.");
yield break;
}
CelestialBody body = startingState.referenceBody;
var patch = new Patch();
patch.startingState = startingState;
patch.isAtmospheric = false;
patch.spaceOrbit = startingState.stockPatch ?? createOrbitFromState(startingState);
patch.endTime = patch.startingState.time + patch.spaceOrbit.period;
// the flight plan does not always contain the first patches (before the first maneuver node), so we populate it with the current orbit and associated encounters etc.
var flightPlan = new List <Orbit>();
for (var orbit = vessel_.orbit; orbit != null && orbit.activePatch; orbit = orbit.nextPatch)
{
if (vessel_.patchedConicSolver.flightPlan.Contains(orbit))
{
break;
}
flightPlan.Add(orbit);
}
foreach (var orbit in vessel_.patchedConicSolver.flightPlan)
{
flightPlan.Add(orbit);
}
Orbit nextStockPatch = null;
if (startingState.stockPatch != null)
{
int planIdx = flightPlan.IndexOf(startingState.stockPatch);
if (planIdx >= 0 && planIdx < flightPlan.Count - 1)
{
nextStockPatch = flightPlan[planIdx + 1];
}
}
if (nextStockPatch != null)
{
patch.endTime = nextStockPatch.StartUT;
}
double maxAtmosphereAltitude = RealMaxAtmosphereAltitude(body);
double minAltitude = patch.spaceOrbit.PeA;
if (patch.endTime < startingState.time + patch.spaceOrbit.timeToPe)
{
minAltitude = patch.spaceOrbit.getRelativePositionAtUT(patch.endTime).magnitude;
}
if (minAltitude < maxAtmosphereAltitude)
{
double entryTime;
if (startingState.position.magnitude <= body.Radius + maxAtmosphereAltitude)
{
// whole orbit is inside the atmosphere
entryTime = startingState.time;
}
else
{
// binary search of entry time in atmosphere
// I guess an analytic solution could be found, but I'm too lazy to search it
double from = startingState.time;
double to = from + patch.spaceOrbit.timeToPe;
int loopCount = 0;
while (to - from > 0.1)
{
++loopCount;
if (loopCount > 1000)
{
UnityEngine.Debug.Log("WARNING: infinite loop? (Trajectories.Trajectory.AddPatch, atmosphere limit search)");
++errorCount_;
break;
}
double middle = (from + to) * 0.5;
if (patch.spaceOrbit.getRelativePositionAtUT(middle).magnitude < body.Radius + maxAtmosphereAltitude)
{
to = middle;
}
else
{
from = middle;
}
}
entryTime = to;
}
if (entryTime > startingState.time + 0.1)
{
// add the space patch before atmospheric entry
patch.endTime = entryTime;
if (body.atmosphere)
{
patchesBackBuffer_.Add(patch);
AddPatch_outState = new VesselState
{
position = Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(entryTime)),
referenceBody = body,
time = entryTime,
velocity = Util.SwapYZ(patch.spaceOrbit.getOrbitalVelocityAtUT(entryTime))
};
yield break;
}
else
{
// the body has no atmosphere, so what we actually computed is the impact on the body surface
// now, go back in time until the impact point is above the ground to take ground height in account
// we assume the ground is horizontal around the impact position
double groundAltitude = GetGroundAltitude(body, calculateRotatedPosition(body, Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(entryTime)), entryTime)) + body.Radius;
double iterationSize = 1.0;
while (entryTime > startingState.time + iterationSize && patch.spaceOrbit.getRelativePositionAtUT(entryTime).magnitude < groundAltitude)
{
entryTime -= iterationSize;
}
patch.endTime = entryTime;
patch.rawImpactPosition = Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(entryTime));
patch.impactPosition = calculateRotatedPosition(body, patch.rawImpactPosition.Value, entryTime);
patch.impactVelocity = Util.SwapYZ(patch.spaceOrbit.getOrbitalVelocityAtUT(entryTime));
patchesBackBuffer_.Add(patch);
AddPatch_outState = null;
yield break;
}
}
else
{
if (patch.startingState.referenceBody != vessel_.mainBody)
{
// in current aerodynamic prediction code, we can't handle predictions for another body, so we stop here
AddPatch_outState = null;
yield break;
}
// simulate atmospheric flight (drag and lift), until landing (more likely to be a crash as we don't predict user piloting) or atmosphere exit (typically for an aerobraking maneuver)
// the simulation assumes a constant angle of attack
patch.isAtmospheric = true;
patch.startingState.stockPatch = null;
double dt = 0.1; // lower dt would be more accurate, but a tradeoff has to be found between performances and accuracy
int maxIterations = (int)(30.0 * 60.0 / dt); // some shallow entries can result in very long flight, for performances reasons, we limit the prediction duration
int chunkSize = 128;
double trajectoryInterval = 10.0; // time between two consecutive stored positions (more intermediate positions are computed for better accuracy), also used for ground collision checks
var buffer = new List <Point[]>();
buffer.Add(new Point[chunkSize]);
int nextPosIdx = 0;
Vector3d pos = Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(entryTime));
Vector3d vel = Util.SwapYZ(patch.spaceOrbit.getOrbitalVelocityAtUT(entryTime));
Vector3d prevPos = pos - vel * dt;
//Util.PostSingleScreenMessage("initial vel", "initial vel = " + vel);
double currentTime = entryTime;
double lastPositionStoredUT = 0;
Vector3d lastPositionStored = new Vector3d();
bool hitGround = false;
int iteration = 0;
int incrementIterations = 0;
int minIterationsPerIncrement = maxIterations / Settings.fetch.MaxFramesPerPatch;
while (true)
{
++iteration;
++incrementIterations;
if (incrementIterations > minIterationsPerIncrement && incrementTime_.ElapsedMilliseconds > MaxIncrementTime)
{
yield return(false);
incrementIterations = 0;
}
double R = pos.magnitude;
double altitude = R - body.Radius;
double atmosphereCoeff = altitude / maxAtmosphereAltitude;
if (hitGround || atmosphereCoeff <= 0.0 || atmosphereCoeff >= 1.0 || iteration == maxIterations || currentTime > patch.endTime)
{
if (hitGround || atmosphereCoeff <= 0.0)
{
patch.rawImpactPosition = pos;
patch.impactPosition = calculateRotatedPosition(body, patch.rawImpactPosition.Value, currentTime);
patch.impactVelocity = vel;
}
patch.endTime = Math.Min(currentTime, patch.endTime);
int totalCount = (buffer.Count - 1) * chunkSize + nextPosIdx;
patch.atmosphericTrajectory = new Point[totalCount];
int outIdx = 0;
foreach (var chunk in buffer)
{
foreach (var p in chunk)
{
if (outIdx == totalCount)
{
break;
}
patch.atmosphericTrajectory[outIdx++] = p;
}
}
if (iteration == maxIterations)
{
ScreenMessages.PostScreenMessage("WARNING: trajectory prediction stopped, too many iterations");
patchesBackBuffer_.Add(patch);
AddPatch_outState = null;
yield break;
}
else if (atmosphereCoeff <= 0.0 || hitGround)
{
patchesBackBuffer_.Add(patch);
AddPatch_outState = null;
yield break;
}
else
{
patchesBackBuffer_.Add(patch);
AddPatch_outState = new VesselState
{
position = pos,
velocity = vel,
referenceBody = body,
time = patch.endTime
};
yield break;
}
}
Vector3d gravityAccel = pos * (-body.gravParameter / (R * R * R));
//Util.PostSingleScreenMessage("prediction vel", "prediction vel = " + vel);
Vector3d airVelocity = vel - body.getRFrmVel(body.position + pos);
double angleOfAttack = profile.GetAngleOfAttack(body, pos, airVelocity);
Vector3d aerodynamicForce = aerodynamicModel_.GetForces(body, pos, airVelocity, angleOfAttack);
Vector3d acceleration = gravityAccel + aerodynamicForce / aerodynamicModel_.mass;
// acceleration in the vessel reference frame is acceleration - gravityAccel
maxAccelBackBuffer_ = Math.Max((float)(aerodynamicForce.magnitude / aerodynamicModel_.mass), maxAccelBackBuffer_);
//vel += acceleration * dt;
//pos += vel * dt;
// Verlet integration (more precise than using the velocity)
Vector3d ppos = prevPos;
prevPos = pos;
pos = pos + pos - ppos + acceleration * (dt * dt);
vel = (pos - prevPos) / dt;
currentTime += dt;
double interval = altitude < 10000.0 ? trajectoryInterval * 0.1 : trajectoryInterval;
if (currentTime >= lastPositionStoredUT + interval)
{
double groundAltitude = GetGroundAltitude(body, calculateRotatedPosition(body, pos, currentTime));
if (lastPositionStoredUT > 0)
{
// check terrain collision, to detect impact on mountains etc.
Vector3 rayOrigin = lastPositionStored;
Vector3 rayEnd = pos;
double absGroundAltitude = groundAltitude + body.Radius;
if (absGroundAltitude > rayEnd.magnitude)
{
hitGround = true;
float coeff = Math.Max(0.01f, (float)((absGroundAltitude - rayOrigin.magnitude) / (rayEnd.magnitude - rayOrigin.magnitude)));
pos = rayEnd * coeff + rayOrigin * (1.0f - coeff);
currentTime = currentTime * coeff + lastPositionStoredUT * (1.0f - coeff);
}
}
lastPositionStoredUT = currentTime;
if (nextPosIdx == chunkSize)
{
buffer.Add(new Point[chunkSize]);
nextPosIdx = 0;
}
Vector3d nextPos = pos;
if (Settings.fetch.BodyFixedMode)
{
nextPos = calculateRotatedPosition(body, nextPos, currentTime);
}
buffer.Last()[nextPosIdx].aerodynamicForce = aerodynamicForce;
buffer.Last()[nextPosIdx].orbitalVelocity = vel;
buffer.Last()[nextPosIdx].groundAltitude = (float)groundAltitude;
buffer.Last()[nextPosIdx].time = currentTime;
buffer.Last()[nextPosIdx++].pos = nextPos;
lastPositionStored = pos;
}
}
}
}
else
{
// no atmospheric entry, just add the space orbit
patchesBackBuffer_.Add(patch);
if (nextStockPatch != null)
{
AddPatch_outState = new VesselState
{
position = Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(patch.endTime)),
velocity = Util.SwapYZ(patch.spaceOrbit.getOrbitalVelocityAtUT(patch.endTime)),
referenceBody = nextStockPatch == null ? body : nextStockPatch.referenceBody,
time = patch.endTime,
stockPatch = nextStockPatch
};
yield break;
}
else
{
AddPatch_outState = null;
yield break;
}
}
}