fix mutually canceling orbits

This commit is contained in:
2026-06-17 20:50:31 +02:00
parent 0cf3d64983
commit e0e11b7933
6 changed files with 54 additions and 11 deletions

View File

@@ -2,6 +2,7 @@
#include "AttackBehavior.h"
#include "BehaviorKind.h"
#include "DynamicBodyComponent.h"
#include "EntityAdmin.h"
#include "ModuleOwnerComponent.h"
#include "MovementIntentComponent.h"
@@ -27,12 +28,17 @@ void AttackExecutor::execute(EntityAdmin& admin)
const entt::entity t = *attack.currentTarget;
QVector2D center = pos.value;
float radius = 0.0f;
QVector2D centerVelocity;
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
{
center = admin.get<PositionComponent>(t).value;
radius = attack.orbitRadius_tiles;
if (admin.hasAll<DynamicBodyComponent>(t))
{
centerVelocity = admin.get<DynamicBodyComponent>(t).velocity_tpt;
}
}
intent = MovementIntentComponent{true, center, radius};
intent = MovementIntentComponent{true, center, radius, centerVelocity};
});
// Weapons: assign the behavior target only if it is within this weapon's range.

View File

@@ -18,24 +18,32 @@ namespace OrbitMath
constexpr float kOrbitLeadAngle_rad = 0.6f;
// Returns the orbit sense (+1 counter-clockwise, -1 clockwise) that matches
// the ship's current movement around `center`, so steering reinforces the
// motion the ship already has. When the velocity is nearly radial or near
// zero (e.g. a head-on approach or a freshly spawned ship) the sense is
// 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& velocity,
const QVector2D& centerVelocity = QVector2D())
{
const QVector2D radial = shipPos - center;
const QVector2D relativeVelocity = velocity - centerVelocity;
const float radialLength = radial.length();
const float velocityLength = velocity.length();
const float velocityLength = relativeVelocity.length();
if (radialLength < 1.0e-4f || velocityLength < 1.0e-4f)
{
return 1.0f;
}
// z-component of radial x velocity, normalised to sin(angle) between them.
const float cross = radial.x() * velocity.y() - radial.y() * velocity.x();
// 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;

View File

@@ -1,6 +1,7 @@
#include "RepairExecutor.h"
#include "BehaviorKind.h"
#include "DynamicBodyComponent.h"
#include "EntityAdmin.h"
#include "ModuleOwnerComponent.h"
#include "MovementIntentComponent.h"
@@ -27,12 +28,17 @@ void RepairExecutor::execute(EntityAdmin& admin)
const entt::entity t = *repair.currentTarget;
QVector2D center = pos.value;
float radius = 0.0f;
QVector2D centerVelocity;
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
{
center = admin.get<PositionComponent>(t).value;
radius = repair.orbitRadius_tiles;
if (admin.hasAll<DynamicBodyComponent>(t))
{
centerVelocity = admin.get<DynamicBodyComponent>(t).velocity_tpt;
}
}
intent = MovementIntentComponent{true, center, radius};
intent = MovementIntentComponent{true, center, radius, centerVelocity};
});
// Repair tools: prefer the behavior target if it is within tool range.