Trajectories.Trajectory.AddPatch C# (CSharp) Method

AddPatch() private method

private AddPatch ( VesselState startingState, Trajectories.DescentProfile profile ) : IEnumerable
startingState VesselState
profile Trajectories.DescentProfile
return IEnumerable
        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 nextPatch = null;
            if (startingState.stockPatch == null)
			{
				nextPatch = patch.spaceOrbit.nextPatch;
			}
			else
            {
                int planIdx = flightPlan.IndexOf(startingState.stockPatch);
                if (planIdx >= 0 && planIdx < flightPlan.Count - 1)
                {
                    nextPatch = flightPlan[planIdx + 1];
                }
            }

            if (nextPatch != null)
            {
                patch.endTime = nextPatch.StartUT;
            }

            double maxAtmosphereAltitude = RealMaxAtmosphereAltitude(body);
			if(!body.atmosphere)
			{
				maxAtmosphereAltitude = body.pqsController.mapMaxHeight;
			}

            double minAltitude = patch.spaceOrbit.PeA;
            if (patch.spaceOrbit.timeToPe < 0 || patch.endTime < startingState.time + patch.spaceOrbit.timeToPe)
            {
                minAltitude = Math.Min(patch.spaceOrbit.getRelativePositionAtUT(patch.endTime).magnitude, patch.spaceOrbit.getRelativePositionAtUT(patch.startingState.time + 1.0).magnitude) - body.Radius;
            }
            if (minAltitude < maxAtmosphereAltitude)
            {
                double entryTime;
                if (startingState.position.magnitude <= body.Radius + maxAtmosphereAltitude)
                {
                    // whole orbit is inside the atmosphere
                    entryTime = startingState.time;
                }
                else
                {
					entryTime = FindOrbitBodyIntersection(patch.spaceOrbit, startingState.time, startingState.time + patch.spaceOrbit.timeToPe, body.Radius + maxAtmosphereAltitude);
				}

                if (entryTime > startingState.time + 0.1 || !body.atmosphere)
                {
                    if (body.atmosphere)
                    {
						// add the space patch before atmospheric entry

						patch.endTime = entryTime;
						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 entry inside the "ground sphere" (defined by the maximal ground altitude)
						// now we iterate until the inner ground sphere (minimal altitude), and check if we hit the ground along the way

						double groundRangeExit = FindOrbitBodyIntersection(patch.spaceOrbit, startingState.time, startingState.time + patch.spaceOrbit.timeToPe, body.Radius - maxAtmosphereAltitude);
						if (groundRangeExit <= entryTime)
							groundRangeExit = startingState.time + patch.spaceOrbit.timeToPe;

						double iterationSize = (groundRangeExit - entryTime) / 100.0;
						double t;
						bool groundImpact = false;
						for(t = entryTime; t < groundRangeExit; t += iterationSize)
						{
							Vector3d pos = patch.spaceOrbit.getRelativePositionAtUT(t);
							double groundAltitude = GetGroundAltitude(body, calculateRotatedPosition(body, Util.SwapYZ(pos), t)) + body.Radius;
							if (pos.magnitude < groundAltitude)
							{
								t -= iterationSize;
								groundImpact = true;
								break;
							}
						}

						if (groundImpact)
						{
							patch.endTime = t;
							patch.rawImpactPosition = Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(t));
							patch.impactPosition = calculateRotatedPosition(body, patch.rawImpactPosition.Value, t);
							patch.impactVelocity = Util.SwapYZ(patch.spaceOrbit.getOrbitalVelocityAtUT(t));
							patchesBackBuffer_.Add(patch);
							AddPatch_outState = null;
							yield break;
						}
						else
						{
							// no impact, just add the space orbit
							patchesBackBuffer_.Add(patch);
							if (nextPatch != null)
							{
								AddPatch_outState = new VesselState
								{
									position = Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(patch.endTime)),
									velocity = Util.SwapYZ(patch.spaceOrbit.getOrbitalVelocityAtUT(patch.endTime)),
									referenceBody = nextPatch == null ? body : nextPatch.referenceBody,
									time = patch.endTime,
									stockPatch = nextPatch
								};
								yield break;
							}
							else
							{
								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));

					//Util.PostSingleScreenMessage("atmo start cond", "Atmospheric start: vel=" + vel.ToString("0.00") + " (mag=" + vel.magnitude.ToString("0.00") + ")");

                    Vector3d prevPos = pos - vel * dt;
                    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;
					double accumulatedForces = 0;
                    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)
                        {
							//Util.PostSingleScreenMessage("atmo force", "Atmospheric accumulated force: " + accumulatedForces.ToString("0.00"));

                            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);
						accumulatedForces += aerodynamicForce.magnitude * dt;
						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 (nextPatch != null)
                {
                    AddPatch_outState = new VesselState
                    {
                        position = Util.SwapYZ(patch.spaceOrbit.getRelativePositionAtUT(patch.endTime)),
                        velocity = Util.SwapYZ(patch.spaceOrbit.getOrbitalVelocityAtUT(patch.endTime)),
                        referenceBody = nextPatch == null ? body : nextPatch.referenceBody,
                        time = patch.endTime,
                        stockPatch = nextPatch
                    };
                    yield break;
                }
                else
                {
                    AddPatch_outState = null;
                    yield break;
                }
            }
        }