Esempio n. 1
0
        private static Vector3d CalcReference()
        {
            if (!Trajectories.IsVesselAttached || TargetProfile.Body == null)
            {
                return(Vector3d.zero);
            }

            double plannedAngleOfAttack = (double)DescentProfile.GetAngleOfAttack(TargetProfile.Body, position, velocity);

            return(velocity.normalized * Math.Cos(plannedAngleOfAttack) + Vector3d.Cross(vel_right, velocity).normalized *Math.Sin(plannedAngleOfAttack));
        }
        private IEnumerable <bool> AddPatch(VesselState startingState)
        {
            if (null == AttachedVessel.patchedConicSolver)
            {
                AttachedVessel.AttachPatchedConicsSolver();
            }

            CelestialBody body = startingState.ReferenceBody;

            Patch patch = new Patch
            {
                StartingState = startingState,
                IsAtmospheric = false,
                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.
            List <Orbit> flightPlan = new List <Orbit>();

            for (Orbit orbit = AttachedVessel.orbit; orbit != null && orbit.activePatch; orbit = orbit.nextPatch)
            {
                if (AttachedVessel.patchedConicSolver.flightPlan.Contains(orbit))
                {
                    break;
                }
                flightPlan.Add(orbit);
            }

            foreach (Orbit orbit in AttachedVessel.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      = patch.SpaceOrbit.getRelativePositionAtUT(entryTime).SwapYZ(),
                            ReferenceBody = body,
                            Time          = entryTime,
                            Velocity      = patch.SpaceOrbit.getOrbitalVelocityAtUT(entryTime).SwapYZ()
                        };
                    }
                    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, pos.SwapYZ(), t))
                                                      + body.Radius;
                            if (pos.magnitude < groundAltitude)
                            {
                                t           -= iterationSize;
                                groundImpact = true;
                                break;
                            }
                        }

                        if (groundImpact)
                        {
                            patch.EndTime           = t;
                            patch.RawImpactPosition = patch.SpaceOrbit.getRelativePositionAtUT(t).SwapYZ();
                            patch.ImpactPosition    = CalculateRotatedPosition(body, patch.RawImpactPosition.Value, t);
                            patch.ImpactVelocity    = patch.SpaceOrbit.getOrbitalVelocityAtUT(t).SwapYZ();
                            patchesBackBuffer_.Add(patch);
                            AddPatch_outState = null;
                        }
                        else
                        {
                            // no impact, just add the space orbit
                            patchesBackBuffer_.Add(patch);
                            if (nextPatch != null)
                            {
                                AddPatch_outState = new VesselState
                                {
                                    Position      = patch.SpaceOrbit.getRelativePositionAtUT(patch.EndTime).SwapYZ(),
                                    Velocity      = patch.SpaceOrbit.getOrbitalVelocityAtUT(patch.EndTime).SwapYZ(),
                                    ReferenceBody = nextPatch.referenceBody,
                                    Time          = patch.EndTime,
                                    StockPatch    = nextPatch
                                };
                            }
                            else
                            {
                                AddPatch_outState = null;
                            }
                        }
                    }
                }
                else
                {
                    if (patch.StartingState.ReferenceBody != AttachedVessel.mainBody)
                    {
                        // currently, we can't handle predictions for another body, so we stop
                        AddPatch_outState = null;
                        yield break;
                    }

                    // simulate atmospheric flight (drag and lift), until impact or atmosphere exit
                    // (typically for an aerobraking maneuver) assuming a constant angle of attack
                    patch.IsAtmospheric            = true;
                    patch.StartingState.StockPatch = null;

                    // lower dt would be more accurate, but a trade-off has to be found between performances and accuracy
                    double dt = Settings.IntegrationStepSize;

                    // some shallow entries can result in very long flight. For performances reasons,
                    // we limit the prediction duration
                    int maxIterations = (int)(60.0 * 60.0 / dt);

                    int chunkSize = 128;

                    // time between two consecutive stored positions (more intermediate positions are computed for better accuracy),
                    // also used for ground collision checks
                    double trajectoryInterval = 10.0;

                    List <Point[]> buffer = new List <Point[]>
                    {
                        new Point[chunkSize]
                    };
                    int nextPosIdx = 0;

                    SimulationState state;
                    state.position = patch.SpaceOrbit.getRelativePositionAtUT(entryTime).SwapYZ();
                    state.velocity = patch.SpaceOrbit.getOrbitalVelocityAtUT(entryTime).SwapYZ();

                    // Initialize a patch with zero acceleration
                    Vector3d currentAccel = new Vector3d(0.0, 0.0, 0.0);


                    double   currentTime               = entryTime;
                    double   lastPositionStoredUT      = 0;
                    Vector3d lastPositionStored        = new Vector3d();
                    bool     hitGround                 = false;
                    int      iteration                 = 0;
                    int      incrementIterations       = 0;
                    int      minIterationsPerIncrement = maxIterations / Settings.MaxFramesPerPatch;

                    #region Acceleration Functor

                    // function that calculates the acceleration under current parameters
                    Vector3d AccelerationFunc(Vector3d position, Vector3d velocity)
                    {
                        Profiler.Start("accelerationFunc inside");

                        // gravity acceleration
                        double   R_      = position.magnitude;
                        Vector3d accel_g = position * (-body.gravParameter / (R_ * R_ * R_));

                        // aero force
                        Vector3d vel_air = velocity - body.getRFrmVel(body.position + position);

                        double aoa = DescentProfile.GetAngleOfAttack(body, position, vel_air) ?? 0d;

                        Profiler.Start("GetForces");
                        Vector3d force_aero = aerodynamicModel_.GetForces(body, position, vel_air, aoa);

                        Profiler.Stop("GetForces");

                        Vector3d accel = accel_g + force_aero / aerodynamicModel_.Mass;

                        accel += API.HandleAccel(AttachedVessel, body, position, velocity, aoa);

                        Profiler.Stop("accelerationFunc inside");
                        return(accel);
                    }

                    #endregion


                    #region Integration Loop

                    while (true)
                    {
                        ++iteration;
                        ++incrementIterations;

                        if (incrementIterations > minIterationsPerIncrement && Util.ElapsedMilliseconds(increment_time) > MAX_INCREMENT_TIME)
                        {
                            yield return(false);

                            incrementIterations = 0;
                        }


                        double R               = state.position.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 = state.position;
                                patch.ImpactPosition    = CalculateRotatedPosition(body, patch.RawImpactPosition.Value, currentTime);
                                patch.ImpactVelocity    = state.velocity;
                            }

                            patch.EndTime = Math.Min(currentTime, patch.EndTime);

                            int totalCount = (buffer.Count - 1) * chunkSize + nextPosIdx;
                            patch.AtmosphericTrajectory = new Point[totalCount];
                            int outIdx = 0;
                            foreach (Point[] chunk in buffer)
                            {
                                foreach (Point 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;
                            }

                            if (atmosphereCoeff <= 0.0 || hitGround)
                            {
                                patchesBackBuffer_.Add(patch);
                                AddPatch_outState = null;
                                yield break;
                            }

                            patchesBackBuffer_.Add(patch);
                            AddPatch_outState = new VesselState
                            {
                                Position      = state.position,
                                Velocity      = state.velocity,
                                ReferenceBody = body,
                                Time          = patch.EndTime
                            };
                            yield break;
                        }

                        Vector3d        lastAccel = currentAccel;
                        SimulationState lastState = state;

                        Profiler.Start("IntegrationStep");

                        // Verlet integration (more precise than using the velocity)
                        // state = VerletStep(state, accelerationFunc, dt);
                        state = RK4Step(state, AccelerationFunc, dt, out currentAccel);

                        currentTime += dt;

                        // KSP presumably uses Euler integration for position updates. Since RK4 is actually more precise than that,
                        // we try to reintroduce an approximation of the error.

                        // The local truncation error for euler integration is:
                        // LTE = 1/2 * h^2 * y''(t)
                        // https://en.wikipedia.org/wiki/Euler_method#Local_truncation_error
                        //
                        // For us,
                        // h is the time step of the outer simulation (KSP), which is the physics time step
                        // y''(t) is the difference of the velocity/acceleration divided by the physics time step
                        state.position += 0.5 * TimeWarp.fixedDeltaTime * currentAccel * dt;
                        state.velocity += 0.5 * TimeWarp.fixedDeltaTime * (currentAccel - lastAccel);

                        Profiler.Stop("IntegrationStep");

                        // calculate gravity and aerodynamic force
                        Vector3d gravityAccel     = lastState.position * (-body.gravParameter / (R * R * R));
                        Vector3d aerodynamicForce = (currentAccel - gravityAccel) / aerodynamicModel_.Mass;

                        // acceleration in the vessel reference frame is acceleration - gravityAccel
                        maxAccelBackBuffer_ = Math.Max(
                            (float)(aerodynamicForce.magnitude / aerodynamicModel_.Mass),
                            maxAccelBackBuffer_);

                        #region Impact Calculation

                        Profiler.Start("AddPatch#impact");

                        double interval = altitude < 10000.0 ? trajectoryInterval * 0.1 : trajectoryInterval;
                        if (currentTime >= lastPositionStoredUT + interval)
                        {
                            double groundAltitude = GetGroundAltitude(body, CalculateRotatedPosition(body, state.position, currentTime));
                            if (lastPositionStoredUT > 0)
                            {
                                // check terrain collision, to detect impact on mountains etc.
                                Vector3 rayOrigin         = lastPositionStored;
                                Vector3 rayEnd            = state.position;
                                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)));
                                    state.position = 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 = state.position;
                            if (Settings.BodyFixedMode)
                            {
                                nextPos = CalculateRotatedPosition(body, nextPos, currentTime);
                            }

                            buffer.Last()[nextPosIdx].aerodynamicForce = aerodynamicForce;
                            buffer.Last()[nextPosIdx].orbitalVelocity  = state.velocity;
                            buffer.Last()[nextPosIdx].groundAltitude   = (float)groundAltitude;
                            buffer.Last()[nextPosIdx].time             = currentTime;
                            buffer.Last()[nextPosIdx++].pos            = nextPos;
                            lastPositionStored = state.position;
                        }

                        Profiler.Stop("AddPatch#impact");

                        #endregion
                    }

                    #endregion
                }
            }
            else
            {
                // no atmospheric entry, just add the space orbit
                patchesBackBuffer_.Add(patch);
                if (nextPatch != null)
                {
                    AddPatch_outState = new VesselState
                    {
                        Position      = patch.SpaceOrbit.getRelativePositionAtUT(patch.EndTime).SwapYZ(),
                        Velocity      = patch.SpaceOrbit.getOrbitalVelocityAtUT(patch.EndTime).SwapYZ(),
                        ReferenceBody = nextPatch.referenceBody,
                        Time          = patch.EndTime,
                        StockPatch    = nextPatch
                    };
                }
                else
                {
                    AddPatch_outState = null;
                }
            }
        }
Esempio n. 3
0
        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;
                }
            }
        }
Esempio n. 4
0
        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;
                }
            }
        }
Esempio n. 5
0
        private static Vector2d GetCorrection()
        {
            if (!Trajectories.IsVesselAttached)
            {
                return(Vector2d.zero);
            }

            Vector3d?     targetPosition = TargetProfile.WorldPosition;
            CelestialBody body           = TargetProfile.Body;

            if (!targetPosition.HasValue || patch == null || !patch.ImpactPosition.HasValue || patch.StartingState.ReferenceBody != body || !patch.IsAtmospheric)
            {
                return(Vector2d.zero);
            }

            // Get impact position, or, if some point over the trajectory has not enough clearance, smoothly interpolate to that point depending on how much clearance is missing
            Vector3d impactPosition = patch.ImpactPosition.Value;

            foreach (Trajectory.Point p in patch.AtmosphericTrajectory)
            {
                double neededClearance  = 600.0d;
                double missingClearance = neededClearance - (p.pos.magnitude - body.Radius - p.groundAltitude);
                if (missingClearance > 0.0d)
                {
                    if (Vector3d.Distance(p.pos, patch.RawImpactPosition.Value) > 3000.0d)
                    {
                        double   coeff      = missingClearance / neededClearance;
                        Vector3d rotatedPos = p.pos;
                        if (!Settings.BodyFixedMode)
                        {
                            rotatedPos = Trajectory.CalculateRotatedPosition(body, p.pos, p.time);
                        }
                        impactPosition = impactPosition * (1.0d - coeff) + rotatedPos * coeff;
                    }
                    break;
                }
            }

            Vector3d right  = Vector3d.Cross(patch.ImpactVelocity.Value, impactPosition).normalized;
            Vector3d behind = Vector3d.Cross(right, impactPosition).normalized;

            Vector3d offset    = targetPosition.Value - impactPosition;
            Vector2d offsetDir = new Vector2d(Vector3d.Dot(right, offset), Vector3d.Dot(behind, offset));

            offsetDir *= 0.00005d; // 20km <-> 1 <-> 45° (this is purely indicative, no physical meaning, it would be very complicated to compute an actual correction angle as it depends on the spacecraft behavior in the atmosphere ; a small angle will suffice for a plane, but even a big angle might do almost nothing for a rocket)

            Vector3d pos = Trajectories.AttachedVessel.GetWorldPos3D() - body.position;
            Vector3d vel = Trajectories.AttachedVessel.obt_velocity - body.getRFrmVel(body.position + pos); // air velocity

            double plannedAngleOfAttack = (double)DescentProfile.GetAngleOfAttack(body, pos, vel);

            if (plannedAngleOfAttack < Util.HALF_PI)
            {
                offsetDir.y = -offsetDir.y; // behavior is different for prograde or retrograde entry
            }
            double maxCorrection = 1.0d;

            offsetDir.x = Util.Clamp(offsetDir.x, -maxCorrection, maxCorrection);
            offsetDir.y = Util.Clamp(offsetDir.y, -maxCorrection, maxCorrection);

            return(offsetDir);
        }