JSim 2026.06.01-p(1)
Loading...
Searching...
No Matches
shot_calculator.hpp
Go to the documentation of this file.
1// Copyright (c) JSim contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the LGPLv3 license file in the root directory of this project.
4
5#pragma once
6
7#include <algorithm>
8#include <cmath>
9#include <cstddef>
10#include <deque>
11#include <limits>
12#include <utility>
13#include <vector>
14
17
18namespace frcsim {
19
25 public:
27 struct TablePoint {
28 double distance_m{0.0};
29 double hood_pitch_rad{0.0};
30 double muzzle_speed_mps{0.0};
31 double time_of_flight_s{0.0};
32 };
33
36 struct Config {
37 double min_distance_m{1.3};
38 double max_distance_m{5.8};
39 double phase_delay_s{0.03};
40 double tof_scale{1.0};
42
43 double hood_offset_rad{0.0};
45 double speed_offset_mps{0.0};
47
50 double recent_pose_band_m{0.75};
52
53 // Ballistic correction pass layered on top of table interpolation.
60
61 Vector3 gravity_mps2{0.0, 0.0, -9.81};
63 double air_density_kgpm3{1.225};
64 double drag_scale{1.0};
65 double magnus_coefficient{1e-4};
66 double magnus_scale{1.0};
67
68 double projectile_mass_kg{0.27};
72
73 double min_pitch_rad{-0.2};
74 double max_pitch_rad{1.35};
75 double min_speed_mps{0.0};
76 double max_speed_mps{45.0};
77 };
78
81 bool is_valid{false};
82 double turret_yaw_rad{0.0};
83 double hood_pitch_rad{0.0};
84 double flywheel_speed_mps{0.0};
85 double distance_m{0.0};
86 double time_of_flight_s{0.0};
87 };
88
89 ShotCalculator3D() = default;
90
96 : config_(sanitizeConfig(config)) {}
97
100 const Config& config() const { return config_; }
103 void setConfig(const Config& config) { config_ = sanitizeConfig(config); }
104
107 recent_shot_samples_.clear();
108 last_recent_sample_s_ = -std::numeric_limits<double>::infinity();
109 }
110
115 void setLookupTable(std::vector<TablePoint> points) {
116 table_.clear();
117 table_.reserve(points.size());
118 for (const auto& point : points) {
119 if (!std::isfinite(point.distance_m) ||
120 !std::isfinite(point.hood_pitch_rad) ||
121 !std::isfinite(point.muzzle_speed_mps) ||
122 !std::isfinite(point.time_of_flight_s)) {
123 continue;
124 }
125 table_.push_back({
126 std::max(0.0, point.distance_m),
127 point.hood_pitch_rad,
128 std::max(0.0, point.muzzle_speed_mps),
129 std::max(0.0, point.time_of_flight_s),
130 });
131 }
132 std::sort(table_.begin(), table_.end(),
133 [](const TablePoint& a, const TablePoint& b) {
134 return a.distance_m < b.distance_m;
135 });
136 }
137
140 const std::vector<TablePoint>& lookupTable() const { return table_; }
141
151 ShotParameters calculateShot(const Vector3& flywheel_origin_m,
152 const Vector3& robot_velocity_mps,
153 const Vector3& target_position_m, double now_s) {
154 ShotParameters result{};
155 if (table_.empty() || !finiteVector(flywheel_origin_m) ||
156 !finiteVector(robot_velocity_mps) || !finiteVector(target_position_m) ||
157 !std::isfinite(now_s)) {
158 return result;
159 }
160
161 trimRecentShotHistory(now_s);
162
163 const Vector3 delayed_origin =
164 flywheel_origin_m + robot_velocity_mps * config_.phase_delay_s;
165 Vector3 lookahead_origin = delayed_origin;
166 double lookahead_distance_m =
167 planarDistance(lookahead_origin, target_position_m);
168
169 for (int i = 0; i < 20; ++i) {
170 const auto sampled = sampleTable(clampedDistance(lookahead_distance_m));
171 const double tof_s =
172 sampled.time_of_flight_s * std::max(0.0, config_.tof_scale);
173 lookahead_origin = delayed_origin + robot_velocity_mps * tof_s;
174 lookahead_distance_m =
175 planarDistance(lookahead_origin, target_position_m);
176 }
177
178 const double distance_clamped = clampedDistance(lookahead_distance_m);
179 const auto base = sampleTable(distance_clamped);
180 const double effective_tof_s =
181 base.time_of_flight_s * std::max(0.0, config_.tof_scale);
182
183 const Vector3 target_delta = target_position_m - lookahead_origin;
184 const double yaw_rad = std::atan2(target_delta.y, target_delta.x);
185
186 double hood_pitch_rad = base.hood_pitch_rad;
187 double flywheel_speed_mps = base.muzzle_speed_mps;
188
189 const RecentShotSample* recent =
190 findRecentShotSample(lookahead_origin, target_position_m);
191 if (recent != nullptr) {
192 const double pose_band =
193 std::max(config_.valid_distance_epsilon, config_.recent_pose_band_m);
194 const double pose_delta =
195 (recent->robot_translation_m - lookahead_origin).norm();
196 const double history_weight = 1.0 - std::min(1.0, pose_delta / pose_band);
197 hood_pitch_rad =
198 lerp(hood_pitch_rad, recent->hood_pitch_rad, history_weight);
199 flywheel_speed_mps =
200 lerp(flywheel_speed_mps, recent->flywheel_speed_mps, history_weight);
201 }
202
203 hood_pitch_rad += config_.hood_offset_rad;
204 hood_pitch_rad += config_.hood_distance_slope_radpm * distance_clamped;
205
206 flywheel_speed_mps += config_.speed_offset_mps;
207 flywheel_speed_mps += config_.speed_distance_slope_mpspm * distance_clamped;
208
209 hood_pitch_rad = std::clamp(hood_pitch_rad, config_.min_pitch_rad,
210 config_.max_pitch_rad);
211 flywheel_speed_mps = std::clamp(flywheel_speed_mps, config_.min_speed_mps,
212 config_.max_speed_mps);
213
214 if (config_.ballistic_refinement_enabled) {
215 refineBallisticShot(lookahead_origin, target_position_m, yaw_rad,
216 hood_pitch_rad, flywheel_speed_mps);
217 }
218
219 const bool valid =
220 lookahead_distance_m >=
221 (config_.min_distance_m - config_.valid_distance_epsilon) &&
222 lookahead_distance_m <=
223 (config_.max_distance_m + config_.valid_distance_epsilon);
224
225 result.is_valid = valid;
226 result.turret_yaw_rad = yaw_rad;
227 result.hood_pitch_rad = hood_pitch_rad;
228 result.flywheel_speed_mps = flywheel_speed_mps;
229 result.distance_m = distance_clamped;
230 result.time_of_flight_s = effective_tof_s;
231
232 if (valid) {
233 saveRecentShotSample(now_s, lookahead_origin, target_position_m,
234 distance_clamped, hood_pitch_rad,
235 flywheel_speed_mps);
236 }
237
238 return result;
239 }
240
241 private:
242 struct RecentShotSample {
243 double timestamp_s{0.0};
244 Vector3 robot_translation_m{};
245 Vector3 target_position_m{};
246 double distance_m{0.0};
247 double hood_pitch_rad{0.0};
248 double flywheel_speed_mps{0.0};
249 };
250
251 struct SampledValues {
252 double hood_pitch_rad{0.0};
253 double muzzle_speed_mps{0.0};
254 double time_of_flight_s{0.0};
255 };
256
257 static double lerp(double a, double b, double t) { return a + (b - a) * t; }
258
259 static bool finiteVector(const Vector3& v) {
260 return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z);
261 }
262
263 static double sanitizeNonNegative(double value, double fallback) {
264 if (!std::isfinite(value)) {
265 return fallback;
266 }
267 return std::max(0.0, value);
268 }
269
270 static Config sanitizeConfig(const Config& config) {
271 Config sanitized = config;
272
273 sanitized.valid_distance_epsilon =
274 std::clamp(sanitizeNonNegative(sanitized.valid_distance_epsilon, 1e-6),
275 1e-9, 1e-2);
276 sanitized.min_distance_m =
277 sanitizeNonNegative(sanitized.min_distance_m, 1.3);
278 sanitized.max_distance_m =
279 sanitizeNonNegative(sanitized.max_distance_m, 5.8);
280 if (sanitized.max_distance_m <
281 sanitized.min_distance_m + sanitized.valid_distance_epsilon) {
282 sanitized.max_distance_m = sanitized.min_distance_m + 1.0;
283 }
284
285 sanitized.phase_delay_s =
286 std::isfinite(sanitized.phase_delay_s) ? sanitized.phase_delay_s : 0.03;
287 sanitized.tof_scale = sanitizeNonNegative(sanitized.tof_scale, 1.0);
288
289 sanitized.recent_shot_history_window_s =
290 sanitizeNonNegative(sanitized.recent_shot_history_window_s, 10.0);
291 sanitized.recent_shot_sample_period_s =
292 sanitizeNonNegative(sanitized.recent_shot_sample_period_s, 0.1);
293 sanitized.recent_pose_band_m =
294 std::max(sanitized.valid_distance_epsilon,
295 sanitizeNonNegative(sanitized.recent_pose_band_m, 0.75));
296 sanitized.recent_target_band_m =
297 std::max(sanitized.valid_distance_epsilon,
298 sanitizeNonNegative(sanitized.recent_target_band_m, 0.75));
299
300 sanitized.ballistic_refinement_iterations =
301 std::clamp(sanitized.ballistic_refinement_iterations, 0, 10);
302 sanitized.ballistic_time_step_s = std::clamp(
303 sanitizeNonNegative(sanitized.ballistic_time_step_s, 0.01), 1e-4, 0.05);
304 sanitized.ballistic_max_time_s = std::clamp(
305 sanitizeNonNegative(sanitized.ballistic_max_time_s, 2.5), 0.1, 10.0);
306 sanitized.ballistic_pitch_gain_radpm =
307 std::clamp(std::isfinite(sanitized.ballistic_pitch_gain_radpm)
308 ? sanitized.ballistic_pitch_gain_radpm
309 : 0.035,
310 0.0, 0.4);
311 sanitized.ballistic_speed_gain_mpspm =
312 std::clamp(std::isfinite(sanitized.ballistic_speed_gain_mpspm)
313 ? sanitized.ballistic_speed_gain_mpspm
314 : 0.75,
315 0.0, 10.0);
316
317 if (!finiteVector(sanitized.gravity_mps2)) {
318 sanitized.gravity_mps2 = Vector3(0.0, 0.0, -9.81);
319 }
320 sanitized.effective_gravity_scale = std::clamp(
321 sanitizeNonNegative(sanitized.effective_gravity_scale, 1.0), 0.0, 5.0);
322 sanitized.air_density_kgpm3 = std::clamp(
323 sanitizeNonNegative(sanitized.air_density_kgpm3, 1.225), 0.0, 5.0);
324 sanitized.drag_scale =
325 std::clamp(sanitizeNonNegative(sanitized.drag_scale, 1.0), 0.0, 10.0);
326 sanitized.magnus_coefficient = std::isfinite(sanitized.magnus_coefficient)
327 ? sanitized.magnus_coefficient
328 : 1e-4;
329 sanitized.magnus_scale =
330 std::clamp(sanitizeNonNegative(sanitized.magnus_scale, 1.0), 0.0, 10.0);
331
332 sanitized.projectile_mass_kg =
333 std::max(1e-6, sanitizeNonNegative(sanitized.projectile_mass_kg, 0.27));
334 sanitized.projectile_drag_coefficient = std::clamp(
335 sanitizeNonNegative(sanitized.projectile_drag_coefficient, 0.47), 0.0,
336 5.0);
337 sanitized.projectile_reference_area_m2 =
338 sanitizeNonNegative(sanitized.projectile_reference_area_m2, 0.025);
339 if (sanitized.projectile_reference_area_m2 <= 0.0) {
340 sanitized.projectile_reference_area_m2 = 0.025;
341 }
342 if (!finiteVector(sanitized.assumed_spin_radps)) {
343 sanitized.assumed_spin_radps = Vector3::zero();
344 }
345
346 sanitized.min_pitch_rad =
347 std::isfinite(sanitized.min_pitch_rad) ? sanitized.min_pitch_rad : -0.2;
348 sanitized.max_pitch_rad =
349 std::isfinite(sanitized.max_pitch_rad) ? sanitized.max_pitch_rad : 1.35;
350 if (sanitized.max_pitch_rad < sanitized.min_pitch_rad) {
351 std::swap(sanitized.min_pitch_rad, sanitized.max_pitch_rad);
352 }
353 sanitized.min_speed_mps = sanitizeNonNegative(sanitized.min_speed_mps, 0.0);
354 sanitized.max_speed_mps =
355 sanitizeNonNegative(sanitized.max_speed_mps, 45.0);
356 if (sanitized.max_speed_mps < sanitized.min_speed_mps) {
357 sanitized.max_speed_mps = sanitized.min_speed_mps + 1.0;
358 }
359
360 return sanitized;
361 }
362
363 double clampedDistance(double distance_m) const {
364 return std::clamp(distance_m, config_.min_distance_m,
365 config_.max_distance_m);
366 }
367
368 static double planarDistance(const Vector3& a, const Vector3& b) {
369 const Vector3 delta = b - a;
370 return std::sqrt(delta.x * delta.x + delta.y * delta.y);
371 }
372
373 struct BallisticSample {
374 bool reached_target_range{false};
375 Vector3 position_m{};
376 double travelled_range_m{0.0};
377 double time_s{0.0};
378 };
379
380 Vector3 ballisticAcceleration(const Vector3& velocity_mps) const {
381 const Vector3 gravity_mps2 =
382 config_.gravity_mps2 * config_.effective_gravity_scale;
383 const Vector3 drag_force_n =
384 Vector3::dragForce(velocity_mps, config_.projectile_drag_coefficient,
385 config_.projectile_reference_area_m2,
386 config_.air_density_kgpm3) *
387 config_.drag_scale;
388 const Vector3 magnus_force_n =
389 Vector3::magnusForce(velocity_mps, config_.assumed_spin_radps,
390 config_.magnus_coefficient) *
391 config_.magnus_scale;
392 const Vector3 accel_mps2 =
393 gravity_mps2 + (drag_force_n + magnus_force_n) *
394 (1.0 / std::max(1e-9, config_.projectile_mass_kg));
395 return finiteVector(accel_mps2) ? accel_mps2 : gravity_mps2;
396 }
397
398 BallisticSample simulateAtRange(const Vector3& origin_m, double yaw_rad,
399 double pitch_rad, double speed_mps,
400 double target_planar_range_m) const {
401 BallisticSample sample{};
402 sample.position_m = origin_m;
403
404 const double cos_pitch = std::cos(pitch_rad);
405 Vector3 velocity_mps(speed_mps * cos_pitch * std::cos(yaw_rad),
406 speed_mps * cos_pitch * std::sin(yaw_rad),
407 speed_mps * std::sin(pitch_rad));
408
409 const Vector3 forward(std::cos(yaw_rad), std::sin(yaw_rad), 0.0);
410 const double dt_s = config_.ballistic_time_step_s;
411
412 Vector3 previous_position_m = sample.position_m;
413 double previous_range_m = 0.0;
414
415 while (sample.time_s < config_.ballistic_max_time_s) {
416 previous_position_m = sample.position_m;
417 previous_range_m = sample.travelled_range_m;
418
419 const Vector3 accel0_mps2 = ballisticAcceleration(velocity_mps);
420 const Vector3 mid_velocity_mps =
421 velocity_mps + accel0_mps2 * (0.5 * dt_s);
422 const Vector3 accel_mid_mps2 = ballisticAcceleration(mid_velocity_mps);
423
424 velocity_mps += accel_mid_mps2 * dt_s;
425 sample.position_m += mid_velocity_mps * dt_s;
426 sample.time_s += dt_s;
427
428 const Vector3 offset_m = sample.position_m - origin_m;
429 sample.travelled_range_m =
430 offset_m.x * forward.x + offset_m.y * forward.y;
431
432 if (!finiteVector(sample.position_m) ||
433 !std::isfinite(sample.travelled_range_m)) {
434 sample.position_m = previous_position_m;
435 sample.travelled_range_m = previous_range_m;
436 break;
437 }
438
439 if (sample.travelled_range_m >= target_planar_range_m) {
440 const double denom =
441 std::max(config_.valid_distance_epsilon,
442 sample.travelled_range_m - previous_range_m);
443 const double alpha = std::clamp(
444 (target_planar_range_m - previous_range_m) / denom, 0.0, 1.0);
445 sample.position_m = previous_position_m +
446 (sample.position_m - previous_position_m) * alpha;
447 sample.travelled_range_m = target_planar_range_m;
448 sample.reached_target_range = true;
449 break;
450 }
451 }
452
453 return sample;
454 }
455
456 void refineBallisticShot(const Vector3& origin_m,
457 const Vector3& target_position_m, double yaw_rad,
458 double& hood_pitch_rad,
459 double& flywheel_speed_mps) const {
460 const double target_planar_range_m =
461 planarDistance(origin_m, target_position_m);
462 if (!std::isfinite(target_planar_range_m) ||
463 target_planar_range_m < config_.valid_distance_epsilon) {
464 return;
465 }
466
467 for (int i = 0; i < config_.ballistic_refinement_iterations; ++i) {
468 const auto sample =
469 simulateAtRange(origin_m, yaw_rad, hood_pitch_rad, flywheel_speed_mps,
470 target_planar_range_m);
471
472 if (!sample.reached_target_range) {
473 const double shortfall_m =
474 std::max(0.0, target_planar_range_m - sample.travelled_range_m);
475 flywheel_speed_mps += std::clamp(
476 shortfall_m * config_.ballistic_speed_gain_mpspm, 0.0, 2.0);
477 flywheel_speed_mps = std::clamp(
478 flywheel_speed_mps, config_.min_speed_mps, config_.max_speed_mps);
479 continue;
480 }
481
482 const double vertical_error_m = target_position_m.z - sample.position_m.z;
483 hood_pitch_rad += std::clamp(
484 vertical_error_m * config_.ballistic_pitch_gain_radpm, -0.08, 0.08);
485 flywheel_speed_mps += std::clamp(
486 vertical_error_m * config_.ballistic_speed_gain_mpspm * 0.5, -0.6,
487 0.6);
488
489 hood_pitch_rad = std::clamp(hood_pitch_rad, config_.min_pitch_rad,
490 config_.max_pitch_rad);
491 flywheel_speed_mps = std::clamp(flywheel_speed_mps, config_.min_speed_mps,
492 config_.max_speed_mps);
493 }
494 }
495
496 SampledValues sampleTable(double distance_m) const {
497 if (table_.empty()) {
498 return {};
499 }
500
501 if (distance_m <= table_.front().distance_m) {
502 return {table_.front().hood_pitch_rad, table_.front().muzzle_speed_mps,
503 table_.front().time_of_flight_s};
504 }
505 if (distance_m >= table_.back().distance_m) {
506 return {table_.back().hood_pitch_rad, table_.back().muzzle_speed_mps,
507 table_.back().time_of_flight_s};
508 }
509
510 for (std::size_t i = 1; i < table_.size(); ++i) {
511 const TablePoint& prev = table_[i - 1];
512 const TablePoint& next = table_[i];
513 if (distance_m > next.distance_m) {
514 continue;
515 }
516
517 const double span = std::max(config_.valid_distance_epsilon,
518 next.distance_m - prev.distance_m);
519 const double t =
520 std::clamp((distance_m - prev.distance_m) / span, 0.0, 1.0);
521 return {
522 lerp(prev.hood_pitch_rad, next.hood_pitch_rad, t),
523 lerp(prev.muzzle_speed_mps, next.muzzle_speed_mps, t),
524 lerp(prev.time_of_flight_s, next.time_of_flight_s, t),
525 };
526 }
527
528 return {table_.back().hood_pitch_rad, table_.back().muzzle_speed_mps,
529 table_.back().time_of_flight_s};
530 }
531
532 void trimRecentShotHistory(double now_s) {
533 while (!recent_shot_samples_.empty() &&
534 now_s - recent_shot_samples_.front().timestamp_s >
535 config_.recent_shot_history_window_s) {
536 recent_shot_samples_.pop_front();
537 }
538 }
539
540 const RecentShotSample* findRecentShotSample(
541 const Vector3& robot_translation_m,
542 const Vector3& target_position_m) const {
543 const double pose_band =
544 std::max(config_.valid_distance_epsilon, config_.recent_pose_band_m);
545 const double target_band =
546 std::max(config_.valid_distance_epsilon, config_.recent_target_band_m);
547
548 const RecentShotSample* best = nullptr;
549 double best_pose_delta = pose_band;
550
551 for (const auto& sample : recent_shot_samples_) {
552 const double target_delta =
553 (sample.target_position_m - target_position_m).norm();
554 if (target_delta > target_band) {
555 continue;
556 }
557
558 const double pose_delta =
559 (sample.robot_translation_m - robot_translation_m).norm();
560 if (pose_delta <= best_pose_delta) {
561 best_pose_delta = pose_delta;
562 best = &sample;
563 }
564 }
565
566 return best;
567 }
568
569 void saveRecentShotSample(double now_s, const Vector3& robot_translation_m,
570 const Vector3& target_position_m, double distance_m,
571 double hood_pitch_rad, double flywheel_speed_mps) {
572 if (now_s - last_recent_sample_s_ < config_.recent_shot_sample_period_s) {
573 return;
574 }
575
576 recent_shot_samples_.push_back({now_s, robot_translation_m,
577 target_position_m, distance_m,
578 hood_pitch_rad, flywheel_speed_mps});
579 last_recent_sample_s_ = now_s;
580 trimRecentShotHistory(now_s);
581 }
582
583 Config config_{};
584 std::vector<TablePoint> table_{};
585
586 std::deque<RecentShotSample> recent_shot_samples_{};
587 double last_recent_sample_s_{-std::numeric_limits<double>::infinity()};
588};
589
590} // namespace frcsim
void setLookupTable(std::vector< TablePoint > points)
Replaces lookup table with validated and distance-sorted points.
Definition shot_calculator.hpp:115
ShotParameters calculateShot(const Vector3 &flywheel_origin_m, const Vector3 &robot_velocity_mps, const Vector3 &target_position_m, double now_s)
Computes a shot solution for current robot/target kinematics.
Definition shot_calculator.hpp:151
ShotCalculator3D(const Config &config)
Constructs calculator with explicit configuration.
Definition shot_calculator.hpp:95
void setConfig(const Config &config)
Replaces configuration after sanitization.
Definition shot_calculator.hpp:103
const std::vector< TablePoint > & lookupTable() const
Returns current lookup table.
Definition shot_calculator.hpp:140
void clearHistory()
Clears learned recent-shot history used for contextual blending.
Definition shot_calculator.hpp:106
const Config & config() const
Returns active sanitized configuration.
Definition shot_calculator.hpp:100
Definition vector.hpp:13
Solver configuration for interpolation, refinement, and historical smoothing behavior.
Definition shot_calculator.hpp:36
double hood_distance_slope_radpm
Definition shot_calculator.hpp:44
double min_distance_m
Definition shot_calculator.hpp:37
double recent_target_band_m
Definition shot_calculator.hpp:51
double magnus_scale
Definition shot_calculator.hpp:66
double projectile_reference_area_m2
Definition shot_calculator.hpp:70
double min_pitch_rad
Definition shot_calculator.hpp:73
double min_speed_mps
Definition shot_calculator.hpp:75
double tof_scale
Definition shot_calculator.hpp:40
double recent_shot_history_window_s
Definition shot_calculator.hpp:48
double projectile_drag_coefficient
Definition shot_calculator.hpp:69
double valid_distance_epsilon
Definition shot_calculator.hpp:41
double magnus_coefficient
Definition shot_calculator.hpp:65
double max_distance_m
Definition shot_calculator.hpp:38
double max_speed_mps
Definition shot_calculator.hpp:76
Vector3 gravity_mps2
Definition shot_calculator.hpp:61
double max_pitch_rad
Definition shot_calculator.hpp:74
double air_density_kgpm3
Definition shot_calculator.hpp:63
double projectile_mass_kg
Definition shot_calculator.hpp:68
double effective_gravity_scale
Definition shot_calculator.hpp:62
double recent_pose_band_m
Definition shot_calculator.hpp:50
double speed_distance_slope_mpspm
Definition shot_calculator.hpp:46
double ballistic_time_step_s
Definition shot_calculator.hpp:56
double hood_offset_rad
Definition shot_calculator.hpp:43
bool ballistic_refinement_enabled
Definition shot_calculator.hpp:54
double recent_shot_sample_period_s
Definition shot_calculator.hpp:49
double drag_scale
Definition shot_calculator.hpp:64
double phase_delay_s
Definition shot_calculator.hpp:39
double ballistic_pitch_gain_radpm
Definition shot_calculator.hpp:58
double ballistic_max_time_s
Definition shot_calculator.hpp:57
double ballistic_speed_gain_mpspm
Definition shot_calculator.hpp:59
int ballistic_refinement_iterations
Definition shot_calculator.hpp:55
double speed_offset_mps
Definition shot_calculator.hpp:45
Vector3 assumed_spin_radps
Definition shot_calculator.hpp:71
Output shot command/result packet.
Definition shot_calculator.hpp:80
double time_of_flight_s
Definition shot_calculator.hpp:86
double flywheel_speed_mps
Definition shot_calculator.hpp:84
double distance_m
Definition shot_calculator.hpp:85
bool is_valid
Definition shot_calculator.hpp:81
double turret_yaw_rad
Definition shot_calculator.hpp:82
double hood_pitch_rad
Definition shot_calculator.hpp:83
Lookup-table row for distance-indexed shot tuning.
Definition shot_calculator.hpp:27
double distance_m
Definition shot_calculator.hpp:28
double muzzle_speed_mps
Definition shot_calculator.hpp:30
double hood_pitch_rad
Definition shot_calculator.hpp:29
double time_of_flight_s
Definition shot_calculator.hpp:31
3D vector utility used throughout JSim physics.
Definition vector.hpp:22
static Vector3 dragForce(const Vector3 &v, double Cd, double A, double rho=1.225) noexcept
Computes the total drag force vector for a velocity and body.
Definition vector.hpp:337
static constexpr Vector3 zero() noexcept
Returns the zero vector.
Definition vector.hpp:360
double x
X component.
Definition vector.hpp:24
static Vector3 magnusForce(const Vector3 &velocity, const Vector3 &omega, double k=1e-4) noexcept
Computes the Magnus lift force omega x v scaled by k.
Definition vector.hpp:226
double y
Y component.
Definition vector.hpp:26