89 lines
4.1 KiB
C++
89 lines
4.1 KiB
C++
#pragma once
|
|
|
|
#include <cmath>
|
|
|
|
#include <QVector2D>
|
|
|
|
// 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;
|
|
}
|
|
}
|