GKD.RoboCtrl
RoboMaster Linux 电控:异步 IO、设备驱动与机器人控制
载入中...
搜索中...
未找到
ballistics.cpp
1/*
2 * Target prediction and linear-drag model adapted from GKD_Control BulletSolver.
3 * Copyright (c) 2021, Qiayuan Liao. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 * 1. Redistributions of source code must retain the above copyright notice,
7 * this list of conditions and the following disclaimer.
8 * 2. Redistributions in binary form must reproduce the above copyright notice,
9 * this list of conditions and the following disclaimer in the documentation
10 * and/or other materials provided with the distribution.
11 * 3. Neither the name of the copyright holder nor the names of its contributors
12 * may be used to endorse or promote products derived from this software
13 * without specific prior written permission.
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "utils/ballistics.hpp"
28
29#include <algorithm>
30#include <cmath>
31#include <numbers>
32
33namespace roboctrl::utils {
34namespace {
35
36constexpr double pi = std::numbers::pi_v<double>;
37
38bool finite(ballistic_vector v) {
39 return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z);
40}
41
42bool valid(const ballistic_config& c) {
43 return std::isfinite(c.gravity) && c.gravity >= 0.0 &&
44 std::isfinite(c.drag) && c.drag >= 0.0 &&
45 std::isfinite(c.max_tracking_yaw_speed) && c.max_tracking_yaw_speed > 0.0 &&
46 std::isfinite(c.max_flight_time) && c.max_flight_time > 0.0 &&
47 std::isfinite(c.position_tolerance) && c.position_tolerance > 0.0 &&
48 c.bracket_steps >= 2 && c.bracket_steps <= 65536 &&
49 c.max_iterations > 0 && c.max_iterations <= 256;
50}
51
52struct flight_terms {
53 double displacement;
54 double drop;
55};
56
57flight_terms terms(const ballistic_config& c, double time) {
58 const double kt = c.drag * time;
59 // The series avoids subtracting two nearly equal values in (t - A) / k.
60 if (std::abs(kt) < 1e-4) {
61 const double a = time * (1.0 + kt * (-0.5 + kt * (1.0 / 6.0 - kt / 24.0)));
62 const double drop = c.gravity * time * time *
63 (0.5 + kt * (-1.0 / 6.0 + kt * (1.0 / 24.0 - kt / 120.0)));
64 return {a, drop};
65 }
66 const double a = -std::expm1(-kt) / c.drag;
67 return {a, c.gravity * (time - a) / c.drag};
68}
69
70ballistic_vector predict(const ballistic_target& target, double time,
71 int selected, bool tracking) {
72 ballistic_vector result {
73 target.position.x + target.velocity.x * time,
74 target.position.y + target.velocity.y * time,
75 target.position.z + target.velocity.z * time};
76 const bool alternate = target.armor_count == 4 && selected != 0;
77 const double radius = alternate ? target.alternate_radius : target.radius;
78 const double angle = tracking
79 ? target.armor_yaw + target.yaw_speed * time + selected * 2.0 * pi / target.armor_count
80 : std::atan2(result.y, result.x);
81 result.x -= radius * std::cos(angle);
82 result.y -= radius * std::sin(angle);
83 result.z += alternate ? target.alternate_height : 0.0;
84 return result;
85}
86
87} // namespace
88
89std::optional<double> legacy_ballistic_drag(double bullet_speed) {
90 if (!std::isfinite(bullet_speed) || bullet_speed <= 0.0) return std::nullopt;
91 if (bullet_speed < 12.5) return 0.45;
92 if (bullet_speed < 15.5) return 1.0;
93 if (bullet_speed < 17.0) return 0.7;
94 if (bullet_speed < 24.0) return 0.55;
95 return 5.0;
96}
97
98std::optional<ballistic_vector> ballistic_position(
99 const ballistic_config& config, double speed, double yaw, double pitch, double time)
100{
101 if (!valid(config) || !std::isfinite(speed) || speed <= 0.0 ||
102 !std::isfinite(yaw) || !std::isfinite(pitch) || !std::isfinite(time) || time < 0.0)
103 return std::nullopt;
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;
109}
110
112 const ballistic_config& config, const ballistic_target& target, double bullet_speed)
113{
114 ballistic_solution result;
115 if (!valid(config)) {
116 result.status = ballistic_status::invalid_config;
117 return result;
118 }
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;
127
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
138 : pi / 12.0;
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;
149 };
150 double left = 0.0;
151 double right = 0.0;
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;
158 return result;
159 }
160 if (e <= 0.0) { bracketed = true; break; }
161 left = right;
162 }
163 if (!bracketed) {
164 result.status = ballistic_status::no_intercept;
165 return result;
166 }
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;
180 return result;
181 }
182 if (e > 0.0) left = time; else right = time;
183 }
184 result.status = ballistic_status::not_converged;
185 return result;
186}
187
188} // namespace roboctrl::utils
用于存放工具函数的命名空间。
Definition ballistics.hpp:6
std::optional< double > legacy_ballistic_drag(double bullet_speed)
std::optional< ballistic_vector > ballistic_position(const ballistic_config &config, double speed, double yaw, double pitch, double time)
ballistic_solution solve_ballistics(const ballistic_config &config, const ballistic_target &target, double bullet_speed)