protected override float process_desired_v(float des_v, bool user_input) { float rad_max_aoa = max_aoa * dgr2rad; res_max_aoa = 100.0f; res_min_aoa = -100.0f; res_equilibr_v_upper = 0.0f; res_equilibr_v_lower = 0.0f; float cur_aoa = imodel.AoA(axis); float abs_cur_aoa = Math.Abs(cur_aoa); bool moderated = false; // AoA moderation section if (moderate_aoa && imodel.dyn_pressure > moder_cutoff_ias * moder_cutoff_ias) { moderated = true; if (abs_cur_aoa < rad_max_aoa * 1.5f) { // We're in linear regime so we can update our limitations // get equilibrium aoa and angular_v for 1.0 input try { eq_A[0, 0] = lin_model.A[0, 0]; eq_A[0, 1] = lin_model.A[0, 1]; eq_A[1, 0] = lin_model.A[1, 0]; eq_A[1, 1] = 0.0; eq_b[0, 0] = -(lin_model.A[0, 2] + lin_model.A[0, 3] + lin_model.B[0, 0] + lin_model.C[0, 0]); eq_b[1, 0] = -(lin_model.A[1, 2] + lin_model.A[1, 3] + lin_model.B[1, 0] + lin_model.C[1, 0]); eq_A.old_lu = true; eq_x = eq_A.SolveWith(eq_b); if (!double.IsInfinity(eq_x[0, 0]) && !double.IsNaN(eq_x[0, 0])) { if (eq_x[0, 0] < 0.0) { // plane is statically unstable, eq_x solution is equilibrium on it's minimal stable aoa min_input_aoa = (float)Common.simple_filter(0.6 * eq_x[0, 0], min_input_aoa, moder_filter / 2.0); min_input_v = (float)Common.simple_filter(0.6 * eq_x[1, 0], min_input_v, moder_filter / 2.0); staticaly_stable = false; } else { // plane is statically stable, eq_x solution is equilibrium on it's maximal stable aoa max_input_aoa = (float)Common.simple_filter(eq_x[0, 0], max_input_aoa, moder_filter); max_input_v = (float)Common.simple_filter(eq_x[1, 0], max_input_v, moder_filter); staticaly_stable = true; } // get equilibrium aoa and angular_v for -1.0 input eq_b[0, 0] = -lin_model.C[0, 0] + lin_model.A[0, 2] + lin_model.A[0, 3] + lin_model.B[0, 0]; eq_b[1, 0] = lin_model.A[1, 2] + lin_model.A[1, 3] + lin_model.B[1, 0] - lin_model.C[1, 0]; eq_x = eq_A.SolveWith(eq_b); if (!double.IsInfinity(eq_x[0, 0]) && !double.IsNaN(eq_x[0, 0])) { if (eq_x[0, 0] >= 0.0) { // plane is statically unstable, eq_x solution is equilibrium on it's maximal stable aoa max_input_aoa = (float)Common.simple_filter(0.6 * eq_x[0, 0], max_input_aoa, moder_filter / 2.0); max_input_v = (float)Common.simple_filter(0.6 * eq_x[1, 0], max_input_v, moder_filter / 2.0); } else { // plane is statically stable, eq_x solution is equilibrium on it's minimal stable aoa min_input_aoa = (float)Common.simple_filter(eq_x[0, 0], min_input_aoa, moder_filter); min_input_v = (float)Common.simple_filter(eq_x[1, 0], min_input_v, moder_filter); } } } } catch (MSingularException) { } // get equilibrium v for max_aoa eq_A[0, 0] = lin_model.A[0, 1]; eq_A[0, 1] = lin_model.A[0, 2] + lin_model.A[0, 3] + lin_model.B[0, 0]; eq_A[1, 0] = lin_model.A[1, 1]; eq_A[1, 1] = lin_model.A[1, 2] + lin_model.A[1, 3] + lin_model.B[1, 0]; eq_b[0, 0] = -(lin_model.A[0, 0] * rad_max_aoa + lin_model.C[0, 0]); eq_b[1, 0] = -(lin_model.A[1, 0] * rad_max_aoa + lin_model.C[1, 0]); eq_A.old_lu = true; try { eq_x = eq_A.SolveWith(eq_b); double new_max_aoa_v = eq_x[0, 0]; eq_b[0, 0] = -(lin_model.A[0, 0] * -rad_max_aoa + lin_model.C[0, 0]); eq_b[1, 0] = -(lin_model.A[1, 0] * -rad_max_aoa + lin_model.C[1, 0]); eq_x = eq_A.SolveWith(eq_b); double new_min_aoa_v = eq_x[0, 0]; if (!double.IsInfinity(new_max_aoa_v) && !double.IsNaN(new_max_aoa_v) && !double.IsInfinity(new_min_aoa_v) && !double.IsNaN(new_min_aoa_v)) { max_aoa_v = (float)Common.simple_filter(new_max_aoa_v, max_aoa_v, moder_filter); min_aoa_v = (float)Common.simple_filter(new_min_aoa_v, min_aoa_v, moder_filter); } } catch (MSingularException) { } } // let's apply moderation with controllability region if (max_input_aoa < res_max_aoa) { res_max_aoa = max_input_aoa; res_equilibr_v_upper = max_input_v; } if (min_input_aoa > res_min_aoa) { res_min_aoa = min_input_aoa; res_equilibr_v_lower = min_input_v; } // apply simple AoA moderation if (rad_max_aoa < res_max_aoa) { res_max_aoa = rad_max_aoa; res_equilibr_v_upper = max_aoa_v; } if (-rad_max_aoa > res_min_aoa) { res_min_aoa = -rad_max_aoa; res_equilibr_v_lower = min_aoa_v; } } // Lift acceleration moderation section if (moderate_g && imodel.dyn_pressure > moder_cutoff_ias * moder_cutoff_ias) { moderated = true; if (Math.Abs(lin_model.A[0, 0]) > 1e-5 && abs_cur_aoa < rad_max_aoa * 1.5f) { // model may be sane, let's update limitations double gravity_acc = 0.0; switch (axis) { case PITCH: gravity_acc = imodel.pitch_gravity_acc + imodel.pitch_noninert_acc; break; case YAW: gravity_acc = imodel.yaw_gravity_acc + imodel.yaw_noninert_acc; break; default: gravity_acc = 0.0; break; } // get equilibrium aoa and angular v for max_g g-force max_g_v = (float)Common.simple_filter( (max_g_force * 9.81 + gravity_acc) / imodel.surface_v_magnitude, max_g_v, moder_filter); min_g_v = (float)Common.simple_filter( (-max_g_force * 9.81 + gravity_acc) / imodel.surface_v_magnitude, min_g_v, moder_filter); // get equilibrium aoa for max_g eq_A[0, 0] = lin_model.A[0, 0]; eq_A[0, 1] = lin_model.A[0, 2] + lin_model.A[0, 3] + lin_model.B[0, 0]; eq_A[1, 0] = lin_model.A[1, 0]; eq_A[1, 1] = lin_model.A[1, 2] + lin_model.A[1, 3] + lin_model.B[1, 0]; eq_b[0, 0] = -(max_g_v + lin_model.C[0, 0]); eq_b[1, 0] = -lin_model.C[1, 0]; eq_A.old_lu = true; try { eq_x = eq_A.SolveWith(eq_b); double new_max_g_aoa = eq_x[0, 0]; eq_b[0, 0] = -(min_g_v + lin_model.C[0, 0]); eq_x = eq_A.SolveWith(eq_b); double new_min_g_aoa = eq_x[0, 0]; if (!double.IsInfinity(new_max_g_aoa) && !double.IsNaN(new_max_g_aoa) && !double.IsInfinity(new_min_g_aoa) && !double.IsNaN(new_min_g_aoa)) { max_g_aoa = (float)Common.simple_filter(new_max_g_aoa, max_g_aoa, moder_filter); min_g_aoa = (float)Common.simple_filter(new_min_g_aoa, min_g_aoa, moder_filter); } } catch (MSingularException) { } } // apply moderation if (max_g_aoa < 2.0 && max_g_aoa > 0.0 && min_g_aoa > -2.0 && max_g_aoa > min_g_aoa) // sanity check { if (max_g_aoa < res_max_aoa) { res_max_aoa = max_g_aoa; res_equilibr_v_upper = max_g_v; } if (min_g_aoa > res_min_aoa) { res_min_aoa = min_g_aoa; res_equilibr_v_lower = min_g_v; } } } // let's get non-overshooting max v value, let's call it transit_max_v // we start on 0.0 aoa with transit_max_v and we must not overshoot res_max_aoa // while applying -1.0 input all the time if (abs_cur_aoa < rad_max_aoa * 1.5f && moderated) { double transit_max_aoa = Math.Min(rad_max_aoa, res_max_aoa); state_mat[0, 0] = staticaly_stable ? transit_max_aoa / 3.0 : transit_max_aoa; state_mat[2, 0] = -1.0; state_mat[3, 0] = -1.0; input_mat[0, 0] = -1.0; double acc = lin_model.eval_row(1, state_mat, input_mat); float new_dyn_max_v = transit_v_mult * (float)Math.Sqrt(2.0 * transit_max_aoa * (-acc)); if (float.IsNaN(new_dyn_max_v)) { if (old_dyn_max_v != 0.0f) { transit_max_v = old_dyn_max_v; } else { old_dyn_max_v = max_v_construction; } } else { // for cases when static authority is too small to comply to long-term dynamics, // we need to artificially increase it if (new_dyn_max_v < res_equilibr_v_upper * 1.2 || new_dyn_max_v < -res_equilibr_v_lower * 1.2) { new_dyn_max_v = 1.2f * Math.Max(Math.Abs(res_equilibr_v_upper), Math.Abs(res_equilibr_v_lower)); } new_dyn_max_v = Common.Clampf(new_dyn_max_v, max_v_construction); transit_max_v = (float)Common.simple_filter(new_dyn_max_v, transit_max_v, moder_filter); old_dyn_max_v = transit_max_v; } } else { transit_max_v = max_v_construction; } // if the user is in charge, let's hold surface-relative angular elocity float v_offset = 0.0f; if (user_input && vessel.obt_speed > 1.0) { if (FlightGlobals.speedDisplayMode == FlightGlobals.SpeedDisplayModes.Surface) { Vector3 planet2vessel = vessel.GetWorldPos3D() - vessel.mainBody.position; Vector3 still_ang_v = Vector3.Cross(vessel.obt_velocity, planet2vessel) / planet2vessel.sqrMagnitude; Vector3 principal_still_ang_v = imodel.world_to_cntrl_part * still_ang_v; v_offset = principal_still_ang_v[axis]; } } if (user_input) { v_offset += neutral_offset; } // desired_v moderation section if (user_controlled) { float normalized_des_v = des_v / max_v_construction; if (float.IsInfinity(normalized_des_v) || float.IsNaN(normalized_des_v)) { normalized_des_v = 0.0f; } normalized_des_v = Common.Clampf(normalized_des_v, 1.0f); if (moderated) { float max_v = Mathf.Min(max_v_construction, transit_max_v); float min_v = -max_v; // upper aoa limit moderation scaled_aoa_up = Common.Clampf((res_max_aoa - cur_aoa) * 2.0f / (res_max_aoa - res_min_aoa), 1.0f); if (scaled_aoa_up < 0.0f) { max_v = Mathf.Min(max_v, scaled_aoa_up * max_v + (1.0f + scaled_aoa_up) * Math.Min(res_equilibr_v_upper, max_v)); } else { max_v = Mathf.Min(max_v, scaled_aoa_up * max_v + (1.0f - scaled_aoa_up) * Math.Min(res_equilibr_v_upper, max_v)); } // lower aoa limit moderation scaled_aoa_down = Common.Clampf((res_min_aoa - cur_aoa) * 2.0f / (res_min_aoa - res_max_aoa), 1.0f); if (scaled_aoa_down < 0.0f) { min_v = Mathf.Max(min_v, scaled_aoa_down * min_v + (1.0f + scaled_aoa_down) * Math.Max(res_equilibr_v_lower, min_v)); } else { min_v = Mathf.Max(min_v, scaled_aoa_down * min_v + (1.0f - scaled_aoa_down) * Math.Max(res_equilibr_v_lower, min_v)); } // now let's restrain v scaled_restrained_v = Common.Clampf(v_offset, min_v, max_v); scaled_restrained_v = Mathf.Lerp(scaled_restrained_v, des_v >= 0.0 ? max_v : min_v, Mathf.Abs(normalized_des_v)); } else { scaled_restrained_v = transit_max_v * normalized_des_v + v_offset; } des_v = scaled_restrained_v; } return(des_v); }
protected override float get_required_input(FlightCtrlState cntrl, float target_value) { float new_input = 0.0f; if (AtmosphereAutopilot.AeroModel == AtmosphereAutopilot.AerodinamycsModel.FAR) { authority = lin_model.B[1, 0]; // check if we have inadequate model authority if (authority < 1e-4) { float user_input = axis == PITCH ? cntrl.pitch : cntrl.yaw; if (user_input != 0.0f) { return(user_input); } else { return(Common.Clampf(target_value, 1.0f)); } } // get model prediction for next frame cur_state[0, 0] = imodel.AoA(axis); cur_state[1, 0] = imodel.AngularVel(axis); cur_state[2, 0] = imodel.ControlSurfPos(axis); cur_state[3, 0] = imodel.GimbalPos(axis); input_mat[0, 0] = cur_state[2, 0]; double cur_acc_prediction = lin_model.eval_row(1, cur_state, input_mat); double acc_error = target_value - cur_acc_prediction; new_input = (float)(imodel.ControlSurfPos(axis) + acc_error / authority); new_input = Common.Clampf(new_input, 1.0f); // Exponential blend can mess with rotation model, let's check it if (FlightModel.far_blend_collapse(imodel.ControlSurfPos(axis), new_input)) { // we need to recalculate new_input according to undelayed model authority = lin_model_undelayed.B[1, 0]; new_input = (float)(imodel.ControlSurfPos(axis) + acc_error / authority); new_input = Common.Clampf(new_input, 1.0f); } model_predicted_acc = cur_acc_prediction + authority * (new_input - cur_state[2, 0]); } else { authority = lin_model_undelayed.B[1, 0]; // check if we have inadequate model authority if (authority < 1e-4) { float user_input = axis == PITCH ? cntrl.pitch : cntrl.yaw; if (user_input != 0.0f) { return(user_input); } else { return(Common.Clampf(target_value, 1.0f)); } } // get model prediction for next frame cur_state[0, 0] = imodel.AoA(axis); cur_state[1, 0] = imodel.AngularVel(axis); cur_state[2, 0] = imodel.GimbalPos(axis); input_mat[0, 0] = imodel.ControlSurfPos(axis); double cur_acc_prediction = lin_model_undelayed.eval_row(1, cur_state, input_mat); double acc_error = target_value - cur_acc_prediction; new_input = (float)(input_mat[0, 0] + acc_error / authority); new_input = Common.Clampf(new_input, 1.0f); if (Math.Abs(new_input - input_mat[0, 0]) / TimeWarp.fixedDeltaTime > SyncModuleControlSurface.CSURF_SPD) { // we're exceeding control surface speed cur_state[3, 0] = cur_state[2, 0]; cur_state[2, 0] = input_mat[0, 0]; if (new_input > input_mat[0, 0]) { cur_state[2, 0] += TimeWarp.fixedDeltaTime * SyncModuleControlSurface.CSURF_SPD; } else { cur_state[2, 0] -= TimeWarp.fixedDeltaTime * SyncModuleControlSurface.CSURF_SPD; } cur_state[2, 0] = Common.Clamp(cur_state[2, 0], 1.0); input_mat[0, 0] = cur_state[2, 0]; cur_acc_prediction = lin_model.eval_row(1, cur_state, input_mat); acc_error = target_value - cur_acc_prediction; authority = lin_model.B[1, 0]; new_input = (float)(cur_state[2, 0] + acc_error / authority); new_input = Common.Clampf(new_input, 1.0f); } model_predicted_acc = cur_acc_prediction + authority * (new_input - input_mat[0, 0]); } if (write_telemetry) { prediction_writer.Write(model_predicted_acc.ToString("G8") + ','); } return(new_input); }