99 const ballistic_config& config,
double speed,
double yaw,
double pitch,
double time)
101 if (!valid(config) || !std::isfinite(speed) || speed <= 0.0 ||
102 !std::isfinite(yaw) || !std::isfinite(pitch) || !std::isfinite(time) || time < 0.0)
104 const auto f = terms(config, time);
105 const double horizontal = speed * std::cos(pitch) * f.displacement;
106 ballistic_vector result {horizontal * std::cos(yaw), horizontal * std::sin(yaw),
107 speed * std::sin(pitch) * f.displacement - f.drop};
108 return finite(result) ? std::optional{result} : std::nullopt;
115 if (!valid(config)) {
116 result.status = ballistic_status::invalid_config;
119 const double range = std::hypot(target.position.x, target.position.y);
120 if (!finite(target.position) || !finite(target.velocity) ||
121 !std::isfinite(bullet_speed) || bullet_speed <= 0.0 ||
122 !std::isfinite(target.armor_yaw) || !std::isfinite(target.yaw_speed) ||
123 !std::isfinite(target.radius) || target.radius < 0.0 ||
124 !std::isfinite(target.alternate_radius) || target.alternate_radius < 0.0 ||
125 !std::isfinite(target.alternate_height) || target.armor_count < 1 || target.armor_count > 16 ||
126 range <= std::max(target.radius, target.alternate_radius))
return result;
128 result.tracking_rotation = std::abs(target.yaw_speed) < config.max_tracking_yaw_speed;
129 const double bearing = std::atan2(target.position.y, target.position.x);
130 double rough_time = std::hypot(range, target.position.z) / bullet_speed;
131 const double drag_fraction = config.
drag * rough_time;
132 if (config.
drag > 0.0 && drag_fraction < 1.0)
133 rough_time = -std::log1p(-drag_fraction) / config.
drag;
134 const double visible_angle = std::acos(std::clamp(target.radius / range, 0.0, 1.0));
135 const double switch_angle = result.tracking_rotation
136 ? visible_angle - pi / 12.0 + (-visible_angle + pi / 6.0) *
137 std::abs(target.yaw_speed) / config.max_tracking_yaw_speed
139 const double angle_error = std::remainder(
140 target.armor_yaw + target.yaw_speed * rough_time - bearing, 2.0 * pi);
141 if (target.armor_count > 1 &&
142 ((angle_error > switch_angle && target.yaw_speed > 0.0) ||
143 (angle_error < -switch_angle && target.yaw_speed < 0.0)))
144 result.selected_armor = target.yaw_speed > 0.0 ? -1 : 1;
145 auto error = [&](
double time) {
146 const auto p = predict(target, time, result.selected_armor, result.tracking_rotation);
147 const auto f = terms(config, time);
148 return std::hypot(std::hypot(p.x, p.y), p.z + f.drop) - bullet_speed * f.displacement;
152 bool bracketed =
false;
153 for (std::size_t i = 1; i <= config.bracket_steps; ++i) {
154 right = config.max_flight_time * (
static_cast<double>(i) / config.bracket_steps);
155 const double e = error(right);
156 if (!std::isfinite(e)) {
157 result.status = ballistic_status::invalid_input;
160 if (e <= 0.0) { bracketed =
true;
break; }
164 result.status = ballistic_status::no_intercept;
167 for (std::size_t i = 0; i < config.max_iterations; ++i) {
168 const double time = 0.5 * (left + right);
169 const double e = error(time);
170 result.iterations = i + 1;
171 if (std::abs(e) <= config.position_tolerance) {
172 const auto p = predict(target, time, result.selected_armor, result.tracking_rotation);
173 const auto f = terms(config, time);
174 result.yaw = std::atan2(p.y, p.x);
175 result.
pitch = std::atan2(p.z + f.drop, std::hypot(p.x, p.y));
176 result.flight_time = time;
177 result.target_position = p;
178 result.residual = std::abs(e);
179 result.status = ballistic_status::success;
182 if (e > 0.0) left = time;
else right = time;
184 result.status = ballistic_status::not_converged;