#pragma once #include #include // Orbit movement helper (REQ-SHP-ORBIT). Behaviors that keep a ship circling a // target (attack, repair, salvage, rally) supply an orbit center and radius via // the movement intent; MovementIntentSystem resolves the orbit direction and // destination using these helpers. namespace OrbitMath { // Lead angle (radians) by which the radial direction is rotated to produce // tangential motion. The orbit direction (sign of the rotation) is chosen // per ship by resolveOrbitSign from the ship's current velocity, so ships // approaching a target from different sides circle it in different senses // instead of all bunching on one side. constexpr float kOrbitLeadAngle_rad = 0.6f; // Returns the orbit sense (+1 counter-clockwise, -1 clockwise) that matches // the ship's movement around `center`, so steering reinforces the motion the // ship already has. The sense is taken from the ship's velocity *relative to // the center* (`centerVelocity`): for a moving target this both removes the // target's own motion from the decision and dissolves the degenerate case // where two ships orbiting each other translate in a straight line — there // their shared velocity cancels, leaving ~zero relative velocity. When the // relative velocity is nearly radial or near zero (a head-on approach, a // freshly spawned ship, or that mutual-translation case) the sense is // ill-defined; this is an unstable point the ship leaves within a tick or // two, so a deterministic fallback of +1 is returned. inline float resolveOrbitSign(const QVector2D& shipPos, const QVector2D& center, const QVector2D& velocity, const QVector2D& centerVelocity = QVector2D()) { const QVector2D radial = shipPos - center; const QVector2D relativeVelocity = velocity - centerVelocity; const float radialLength = radial.length(); const float velocityLength = relativeVelocity.length(); if (radialLength < 1.0e-4f || velocityLength < 1.0e-4f) { return 1.0f; } // z-component of radial x relativeVelocity, normalised to sin(angle). const float cross = radial.x() * relativeVelocity.y() - radial.y() * relativeVelocity.x(); const float sinAngle = cross / (radialLength * velocityLength); constexpr float kRadialEpsilon = 1.0e-3f; if (std::abs(sinAngle) < kRadialEpsilon) { return 1.0f; } return (sinAngle > 0.0f) ? 1.0f : -1.0f; } // Returns a destination on the orbit circle of `radius` around `center`. The // result always lies exactly `radius` from `center`, so steering toward it // both corrects the standoff distance and advances the ship tangentially. // `sign` selects the orbit sense (+1 counter-clockwise, -1 clockwise). A // radius of zero or less falls back to the center (legacy "approach the // target" behavior), e.g. when the ship has no tool range to orbit at. inline QVector2D computeOrbitDestination(const QVector2D& shipPos, const QVector2D& center, float radius, float sign = 1.0f) { if (radius <= 0.0f) { return center; } QVector2D radial = shipPos - center; float length = radial.length(); if (length < 1.0e-4f) { // Ship sits on the center; pick an arbitrary radial direction. radial = QVector2D(1.0f, 0.0f); length = 1.0f; } const QVector2D radialDirection = radial / length; const float leadAngle = sign * kOrbitLeadAngle_rad; const float cosLead = std::cos(leadAngle); const float sinLead = std::sin(leadAngle); const QVector2D leadDirection( radialDirection.x() * cosLead - radialDirection.y() * sinLead, radialDirection.x() * sinLead + radialDirection.y() * cosLead); return center + radius * leadDirection; } }