protected virtual float get_required_input(FlightCtrlState cntrl, float target_value)
 {
     return(ControlUtils.getControlFromState(cntrl, axis));
 }
        public override void ApplyControl(FlightCtrlState cntrl)
        {
            if (vessel.LandedOrSplashed())
            {
                return;
            }

            if (thrust_c.spd_control_enabled)
            {
                thrust_c.ApplyControl(cntrl, thrust_c.setpoint.mps());
            }

            desired_velocity = Vector3d.zero;
            planet2ves       = vessel.ReferenceTransform.position - vessel.mainBody.position;
            planet2vesNorm   = planet2ves.normalized;
            desired_vert_acc = Vector3d.zero;

            // centrifugal acceleration to stay on desired altitude
            level_acc = -planet2vesNorm * (imodel.surface_v - Vector3d.Project(imodel.surface_v, planet2vesNorm)).sqrMagnitude / planet2ves.magnitude;

            switch (current_mode)
            {
            default:
            case CruiseMode.LevelFlight:
                // simply select velocity from axis
                desired_velocity = Vector3d.Cross(planet2vesNorm, circle_axis);
                handle_wide_turn();
                if (vertical_control)
                {
                    if (height_mode == HeightMode.Altitude)
                    {
                        desired_velocity = account_for_height(desired_velocity);
                    }
                    else
                    {
                        desired_velocity = account_for_vertical_vel(desired_velocity);
                    }
                }
                break;

            case CruiseMode.CourseHold:
                if (Math.Abs(vessel.latitude) > 80.0)
                {
                    // we're too close to poles, let's switch to level flight
                    LevelFlightMode = true;
                    goto case CruiseMode.LevelFlight;
                }
                // get direction vector form course
                Vector3d    north           = vessel.mainBody.RotationAxis;
                Vector3d    north_projected = Vector3.ProjectOnPlane(north, planet2vesNorm);
                QuaternionD rotation        = QuaternionD.AngleAxis(desired_course, planet2vesNorm);
                desired_velocity = rotation * north_projected;
                handle_wide_turn();
                if (vertical_control)
                {
                    if (height_mode == HeightMode.Altitude)
                    {
                        desired_velocity = account_for_height(desired_velocity);
                    }
                    else
                    {
                        desired_velocity = account_for_vertical_vel(desired_velocity);
                    }
                }
                break;

            case CruiseMode.Waypoint:
                // set new axis
                Vector3d world_target_pos = vessel.mainBody.GetWorldSurfacePosition(desired_latitude, desired_longitude, vessel.altitude);
                dist_to_dest = Vector3d.Distance(world_target_pos, vessel.ReferenceTransform.position);
                if (dist_to_dest > 10000.0)
                {
                    double radius = vessel.mainBody.Radius;
                    dist_to_dest = Math.Acos(1 - (dist_to_dest * dist_to_dest) / (2 * radius * radius)) * radius;
                }
                if (dist_to_dest < 200.0)
                {
                    // we're too close to target, let's switch to level flight
                    LevelFlightMode  = true;
                    picking_waypoint = false;
                    MessageManager.post_quick_message("Waypoint reached");
                    goto case CruiseMode.LevelFlight;
                }
                // set new axis according to waypoint
                circle_axis = Vector3d.Cross(world_target_pos - vessel.mainBody.position, vessel.GetWorldPos3D() - vessel.mainBody.position).normalized;
                goto case CruiseMode.LevelFlight;
            }

            if (use_keys)
            {
                ControlUtils.neutralize_user_input(cntrl, PITCH);
                ControlUtils.neutralize_user_input(cntrl, YAW);
            }

            double old_str = dir_c.strength;

            dir_c.strength *= strength_mult;
            dir_c.ApplyControl(cntrl, desired_velocity, level_acc + desired_vert_acc);
            dir_c.strength = old_str;
        }
        /// <summary>
        /// Main control function
        /// </summary>
        /// <param name="cntrl">Control state to change</param>
        /// <param name="target_value">Desired AoA in radians</param>
        /// <param name="target_derivative">Desired AoA derivative</param>
        public float ApplyControl(FlightCtrlState cntrl, float target_value, float target_derivative)
        {
            if (imodel.dyn_pressure <= (v_controller.moder_cutoff_ias * v_controller.moder_cutoff_ias) ||
                !v_controller.moderate_aoa)
            {
                v_controller.user_controlled = true;
                v_controller.ApplyControl(cntrl, 0.0f);
                return(0.0f);
            }

            cur_aoa = imodel.AoA(axis);

            float user_input = Common.Clampf(ControlUtils.get_neutralized_user_input(cntrl, axis), 1.0f);

            if (user_controlled || user_input != 0.0f)
            {
                if (user_input >= 0.0f)
                {
                    desired_aoa = user_input * v_controller.res_max_aoa;
                }
                else
                {
                    desired_aoa = -user_input * v_controller.res_min_aoa;
                }
                user_controlled = true;
            }
            else
            {
                desired_aoa = (float)Common.Clamp(target_value, v_controller.res_min_aoa, v_controller.res_max_aoa);
            }

            // Let's find equilibrium angular v on desired_aoa
            LinearSystemModel model = lin_model_gen;

            eq_A[0, 0]  = model.A[0, 1];
            eq_A[0, 1]  = model.A[0, 2] + model.A[0, 3] + model.B[0, 0];
            eq_A[1, 0]  = model.A[1, 1];
            eq_A[1, 1]  = model.A[1, 2] + model.B[1, 0] + model.A[1, 3];
            eq_b[0, 0]  = target_derivative - (model.A[0, 0] * desired_aoa + model.C[0, 0]);
            eq_b[1, 0]  = -(model.A[1, 0] * desired_aoa + model.C[1, 0]);
            eq_A.old_lu = true;
            try
            {
                eq_x = eq_A.SolveWith(eq_b);
                double new_eq_v = eq_x[0, 0];
                if (!double.IsInfinity(new_eq_v) && !double.IsNaN(new_eq_v))
                {
                    desired_aoa_equilibr_v = (float)Common.simple_filter(new_eq_v, desired_aoa_equilibr_v, v_filter_k);
                }
            }
            catch (MSingularException) { }
            //cur_aoa_equilibr_v += 0.5f * (float)get_roll_aoa_deriv();

            // parabolic descend to desired angle of attack
            double error = Common.Clampf(desired_aoa - cur_aoa, Mathf.Abs(v_controller.res_max_aoa - v_controller.res_min_aoa));

            // special workaround for twitches on out of controllable regions for overdamped planes
            bool stable_out_of_bounds = false;

            if (v_controller.staticaly_stable &&
                ((desired_aoa == v_controller.max_input_aoa && error < 0.0) ||
                 (desired_aoa == v_controller.min_input_aoa && error > 0.0)))
            {
                stable_out_of_bounds = true;
            }

            double k = v_controller.transit_max_v * v_controller.transit_max_v / 2.0 / (v_controller.res_max_aoa - v_controller.res_min_aoa);
            double t = -Math.Sqrt(Math.Abs(error / k));
            double descend_v;

            if (t < -cubic_barrier)
            {
                // we're still far away from desired aoa, we'll descend using parabolic function
                cubic = false;
                double t_step     = Math.Min(0.0, t + TimeWarp.fixedDeltaTime);
                double relaxation = 1.0;
                if (t >= -relaxation_frame * TimeWarp.fixedDeltaTime)
                {
                    relaxation = relaxation_factor;
                }
                descend_v  = relaxation * k * (t * t - t_step * t_step) * Math.Sign(error) / TimeWarp.fixedDeltaTime;
                output_acc = 0.0f;
            }
            else
            {
                // we're close to desired aoa, we'll descend using cubic function
                cubic = true;
                double kacc_quadr = Math.Abs(v_controller.kacc_quadr);
                double k_cubic    = kacc_quadr / 6.0 * cubic_kp;
                double t_cubic    = -Math.Pow(Math.Abs(error / k_cubic), 0.33);
                double t_step     = Math.Min(0.0, t_cubic + TimeWarp.fixedDeltaTime);
                if (t >= -relaxation_frame * TimeWarp.fixedDeltaTime)
                {
                    descend_v = relaxation_factor * error / Math.Max((relaxation_frame * TimeWarp.fixedDeltaTime), TimeWarp.fixedDeltaTime);
                }
                else
                {
                    descend_v = k_cubic * (t_step * t_step * t_step - t_cubic * t_cubic * t_cubic) * Math.Sign(error) / TimeWarp.fixedDeltaTime;
                }
            }

            if (stable_out_of_bounds)
            {
                descend_v = -0.2 * descend_v;
            }

            output_v = (float)(descend_v + desired_aoa_equilibr_v);

            ControlUtils.neutralize_user_input(cntrl, axis);
            v_controller.user_controlled = false;
            v_controller.ApplyControl(cntrl, output_v);

            return(output_v);
        }