mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-13 07:03:25 +00:00
Adds documentation for the `omath::ViewAngles` struct, clarifying its purpose, common usage patterns, and the definition of the types of pitch, yaw and roll. Also, adds short explanations of how to use ViewAngles and what tradeoffs exist between using raw float types and strongly typed Angle<> types.
153 lines
5.1 KiB
Markdown
153 lines
5.1 KiB
Markdown
# `omath::projectile_prediction::ProjPredEngineInterface` — Aim-point solver interface
|
||
|
||
> Header: your project’s `projectile_prediction/proj_pred_engine_interface.hpp`
|
||
> Namespace: `omath::projectile_prediction`
|
||
> Depends on: `Vector3<float>`, `Projectile`, `Target`
|
||
> Purpose: **contract** for engines that compute a lead/aim point to hit a moving target.
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
`ProjPredEngineInterface` defines a single pure-virtual method that attempts to compute the **world-space aim point** where a projectile should be launched to intersect a target under the engine’s physical model (e.g., constant projectile speed, gravity, drag, max flight time, etc.).
|
||
|
||
If a valid solution exists, the engine returns the 3D aim point. Otherwise, it returns `std::nullopt` (no feasible intercept).
|
||
|
||
---
|
||
|
||
## API
|
||
|
||
```cpp
|
||
namespace omath::projectile_prediction {
|
||
|
||
class ProjPredEngineInterface {
|
||
public:
|
||
[[nodiscard]]
|
||
virtual std::optional<Vector3<float>>
|
||
maybe_calculate_aim_point(const Projectile& projectile,
|
||
const Target& target) const = 0;
|
||
|
||
virtual ~ProjPredEngineInterface() = default;
|
||
};
|
||
|
||
} // namespace omath::projectile_prediction
|
||
```
|
||
|
||
### Semantics
|
||
|
||
* **Input**
|
||
|
||
* `Projectile` — engine-specific projectile properties (typical: muzzle speed, gravity vector, drag flag/coeff, max range / flight time).
|
||
* `Target` — target state (typical: position, velocity, possibly acceleration).
|
||
|
||
* **Output**
|
||
|
||
* `std::optional<Vector3<float>>`
|
||
|
||
* `value()` — world-space point to aim at **now** so that the projectile intersects the target under the model.
|
||
* `std::nullopt` — no solution (e.g., target outruns projectile, blocked by constraints, numerical failure).
|
||
|
||
* **No side effects**: method is `const` and should not modify inputs.
|
||
|
||
---
|
||
|
||
## Typical usage
|
||
|
||
```cpp
|
||
using namespace omath::projectile_prediction;
|
||
|
||
std::unique_ptr<ProjPredEngineInterface> engine = /* your implementation */;
|
||
Projectile proj = /* fill from weapon config */;
|
||
Target tgt = /* read from tracking system */;
|
||
|
||
if (auto aim = engine->maybe_calculate_aim_point(proj, tgt)) {
|
||
// Rotate/steer to (*aim)
|
||
} else {
|
||
// Fall back: no-lead, predictive UI, or do not fire
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation guidance (for engine authors)
|
||
|
||
**Common models:**
|
||
|
||
1. **No gravity, constant speed**
|
||
Closed form intersect time `t` solves `‖p_t + v_t t − p_0‖ = v_p t`.
|
||
Choose the smallest non-negative real root; aim point = `p_t + v_t t`.
|
||
|
||
2. **Gravity (constant g), constant speed**
|
||
Solve ballistics with vertical drop: either numerical (Newton–Raphson on time) or 2D elevation + azimuth decomposition. Ensure convergence caps and time bounds.
|
||
|
||
3. **Drag**
|
||
Typically requires numeric integration (e.g., RK4) wrapped in a root find on time-of-flight.
|
||
|
||
**Robustness tips:**
|
||
|
||
* **Feasibility checks:** return `nullopt` when:
|
||
|
||
* projectile speed ≤ 0; target too fast in receding direction; solution time outside `[0, t_max]`.
|
||
* **Bounds:** clamp search time to reasonable `[t_min, t_max]` (e.g., `[0, max_flight_time]` or by range).
|
||
* **Tolerances:** use epsilons for convergence (e.g., `|f(t)| < 1e-4`, `|Δt| < 1e-4 s`).
|
||
* **Determinism:** fix iteration counts or seeds if needed for replayability.
|
||
|
||
---
|
||
|
||
## Example: constant-speed, no-gravity intercept (closed form)
|
||
|
||
```cpp
|
||
// Solve ||p + v t|| = s t where p = target_pos - shooter_pos, v = target_vel, s = projectile_speed
|
||
// Quadratic: (v·v - s^2) t^2 + 2 (p·v) t + (p·p) = 0
|
||
inline std::optional<float> intercept_time_no_gravity(const Vector3<float>& p,
|
||
const Vector3<float>& v,
|
||
float s) {
|
||
const float a = v.dot(v) - s*s;
|
||
const float b = 2.f * p.dot(v);
|
||
const float c = p.dot(p);
|
||
if (std::abs(a) < 1e-6f) { // near linear
|
||
if (std::abs(b) < 1e-6f) return std::nullopt;
|
||
float t = -c / b;
|
||
return t >= 0.f ? std::optional{t} : std::nullopt;
|
||
}
|
||
const float disc = b*b - 4.f*a*c;
|
||
if (disc < 0.f) return std::nullopt;
|
||
const float sqrtD = std::sqrt(disc);
|
||
float t1 = (-b - sqrtD) / (2.f*a);
|
||
float t2 = (-b + sqrtD) / (2.f*a);
|
||
float t = (t1 >= 0.f ? t1 : t2);
|
||
return t >= 0.f ? std::optional{t} : std::nullopt;
|
||
}
|
||
```
|
||
|
||
Aim point (given shooter origin `S`, target pos `T`, vel `V`):
|
||
|
||
```
|
||
p = T - S
|
||
t* = intercept_time_no_gravity(p, V, speed)
|
||
aim = T + V * t*
|
||
```
|
||
|
||
Return `nullopt` if `t*` is absent.
|
||
|
||
---
|
||
|
||
## Testing checklist
|
||
|
||
* **Stationary target**: aim point equals target position when `s > 0`.
|
||
* **Target perpendicular motion**: lead equals lateral displacement `V⊥ * t`.
|
||
* **Receding too fast**: expect `nullopt`.
|
||
* **Gravity model**: verify arc solutions exist for short & long trajectories (if implemented).
|
||
* **Numerics**: convergence within max iterations; monotonic improvement of residuals.
|
||
|
||
---
|
||
|
||
## Notes
|
||
|
||
* This is an **interface** only; concrete engines (e.g., `SimpleNoGravityEngine`, `BallisticGravityEngine`) should document their assumptions (gravity, drag, wind, bounds) and units (meters, seconds).
|
||
* The coordinate system and handedness should be consistent with `Vector3<float>` and the rest of your math stack.
|
||
|
||
---
|
||
|
||
*Last updated: 1 Nov 2025*
|