Skip to content

omath::projectile_prediction::ProjPredEngineAvx2 — AVX2-accelerated ballistic aim solver

Header: your project’s projectile_prediction/proj_pred_engine_avx2.hpp Namespace: omath::projectile_prediction Inherits: ProjPredEngineInterface Depends on: Vector3<float>, Projectile, Target CPU: Uses AVX2 when available; falls back to scalar elsewhere (fields are marked [[maybe_unused]] for non-x86/AVX2 builds).

This engine computes a world-space aim point (and implicitly the firing yaw/pitch) to intersect a moving target under a constant gravity model and constant muzzle speed. It typically scans candidate times of flight and solves for the elevation (pitch) that makes the vertical and horizontal kinematics meet at the same time.


API

class ProjPredEngineAvx2 final : public ProjPredEngineInterface {
public:
  [[nodiscard]]
  std::optional<Vector3<float>>
  maybe_calculate_aim_point(const Projectile& projectile,
                            const Target& target) const override;

  ProjPredEngineAvx2(float gravity_constant,
                     float simulation_time_step,
                     float maximum_simulation_time);
  ~ProjPredEngineAvx2() override = default;

private:
  // Solve for pitch at a fixed time-of-flight t.
  [[nodiscard]]
  static std::optional<float>
  calculate_pitch(const Vector3<float>& proj_origin,
                  const Vector3<float>& target_pos,
                  float bullet_gravity, float v0, float time);

  // Tunables (may be unused on non-AVX2 builds)
  [[maybe_unused]] const float m_gravity_constant;      // |g| (e.g., 9.81)
  [[maybe_unused]] const float m_simulation_time_step;  // Δt (e.g., 1/240 s)
  [[maybe_unused]] const float m_maximum_simulation_time; // Tmax (e.g., 3 s)
};

Parameters (constructor)

  • gravity_constant — magnitude of gravity (units consistent with your world, e.g., m/s²).
  • simulation_time_step — Δt used to scan candidate intercept times.
  • maximum_simulation_time — cap on time of flight; larger allows longer-range solutions but increases cost.

Return (solver)

  • maybe_calculate_aim_point(...)

    • Vector3<float>: a world-space aim point yielding an intercept under the model.
    • std::nullopt: no feasible solution (e.g., target receding too fast, out of range, or kinematics inconsistent).

How it solves (expected flow)

  1. Predict target at time t (constant-velocity model unless your Target carries more):

T(t) = target.position + target.velocity * t 2. Horizontal/vertical kinematics at fixed t with muzzle speed v0 and gravity g:

* Let `Δ = T(t) - proj_origin`, `d = length(Δ.xz)`, `h = Δ.y`.
* Required initial components:

  ```
  cosθ = d / (v0 * t)
  sinθ = (h + 0.5 * g * t^2) / (v0 * t)
  ```
* If `cosθ` ∈ [−1,1] and `sinθ` ∈ [−1,1] and `sin²θ + cos²θ ≈ 1`, then

  ```
  θ = atan2(sinθ, cosθ)
  ```

  That is what `calculate_pitch(...)` returns on success.
  1. Yaw is the azimuth toward Δ.xz.
  2. Pick the earliest feasible t in [Δt, Tmax] (scanned in steps of Δt; AVX2 batches several t at once).
  3. Return the aim point. Common choices:

    • The impact point T(t*) (useful as a HUD marker), or
    • A point along the initial firing ray at some convenient range using (yaw, pitch); both are consistent—pick the convention your caller expects.

The private calculate_pitch(...) matches step 2 and returns nullopt if the trigonometric constraints are violated for that t.


AVX2 notes

  • On x86/x64 with AVX2, candidate times t can be evaluated 8 at a time using FMA (great for dense scans).
  • On ARM/ARM64 (no AVX2), code falls back to scalar math; the [[maybe_unused]] members acknowledge compilation without SIMD.

Usage example

using namespace omath::projectile_prediction;

ProjPredEngineAvx2 solver(
  /*gravity*/ 9.81f,
  /*dt*/       1.0f/240.0f,
  /*Tmax*/     3.0f
);

Projectile proj; // fill: origin, muzzle_speed, etc.
Target     tgt;  // fill: position, velocity

if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) {
  // Aim your weapon at *aim and fire with muzzle speed proj.v0
  // If you need yaw/pitch explicitly, replicate the pitch solve and azimuth.
} else {
  // No solution (out of envelope) — pick a fallback
}

Edge cases & failure modes

  • Zero or tiny v0 → no solution.
  • Target collinear & receding faster than v0 → no solution.
  • t constraints: if viable solutions exist only beyond Tmax, you’ll get nullopt.
  • Geometric infeasibility at a given t (e.g., d > v0*t) causes calculate_pitch to fail that sample.
  • Numerical tolerance: check sin²θ + cos²θ against 1 with a small epsilon (e.g., 1e-3).

Performance & tuning

  • Work is roughly O(Nt) where Nt ≈ Tmax / Δt.
  • Smaller Δt → better accuracy, higher cost. With AVX2 you can afford smaller steps.
  • If you frequently miss solutions between steps, consider:

    • Coarse-to-fine: coarse scan, then local refine around the best t.
    • Newton on time: root-find ‖horizontal‖ − v0 t cosθ(t) = 0 shaped from the kinematics.

Testing checklist

  • Stationary target at same height → θ ≈ 0, aim point ≈ target.
  • Higher target → positive pitch; lower target → negative pitch.
  • Perpendicular moving target → feasible at moderate speeds.
  • Very fast receding targetnullopt.
  • Boundary: d ≈ v0*Tmax and h large → verify pass/fail around thresholds.

See also

  • ProjPredEngineInterface — base interface and general contract
  • Projectile, Target — data carriers for solver inputs (speed, origin, position, velocity, etc.)

Last updated: 1 Nov 2025