allow custom orbit rotations directions
This commit is contained in:
@@ -9,5 +9,6 @@
|
|||||||
struct MovementIntentComponent
|
struct MovementIntentComponent
|
||||||
{
|
{
|
||||||
bool active = false;
|
bool active = false;
|
||||||
QVector2D target;
|
QVector2D target; // straight-line destination, or orbit center when orbitRadius_tiles > 0
|
||||||
|
float orbitRadius_tiles = 0.0f; // 0 ⇒ go straight to target; >0 ⇒ orbit target at this radius
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "FacingComponent.h"
|
#include "FacingComponent.h"
|
||||||
#include "MovementIntentComponent.h"
|
#include "MovementIntentComponent.h"
|
||||||
|
#include "OrbitMath.h"
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
#include "tracing.h"
|
#include "tracing.h"
|
||||||
|
|
||||||
@@ -45,7 +46,19 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVector2D delta = intent.target - pos.value;
|
// Resolve the steering destination. For orbit intents, pick the orbit
|
||||||
|
// sense from the ship's current velocity (so ships circling the same
|
||||||
|
// target spread to both sides) and aim at a point on the orbit circle.
|
||||||
|
QVector2D destination = intent.target;
|
||||||
|
if (intent.orbitRadius_tiles > 0.0f)
|
||||||
|
{
|
||||||
|
const float sign = OrbitMath::resolveOrbitSign(pos.value, intent.target,
|
||||||
|
body.velocity_tpt);
|
||||||
|
destination = OrbitMath::computeOrbitDestination(
|
||||||
|
pos.value, intent.target, intent.orbitRadius_tiles, sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVector2D delta = destination - pos.value;
|
||||||
const float dist = delta.length();
|
const float dist = delta.length();
|
||||||
|
|
||||||
if (dist < 0.001f)
|
if (dist < 0.001f)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "ModuleOwnerComponent.h"
|
#include "ModuleOwnerComponent.h"
|
||||||
#include "MovementIntentComponent.h"
|
#include "MovementIntentComponent.h"
|
||||||
#include "OrbitMath.h"
|
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
#include "SelectedBehaviorComponent.h"
|
#include "SelectedBehaviorComponent.h"
|
||||||
#include "tracing.h"
|
#include "tracing.h"
|
||||||
@@ -26,14 +25,14 @@ void AttackExecutor::execute(EntityAdmin& admin)
|
|||||||
if (!attack.currentTarget) { return; }
|
if (!attack.currentTarget) { return; }
|
||||||
|
|
||||||
const entt::entity t = *attack.currentTarget;
|
const entt::entity t = *attack.currentTarget;
|
||||||
QVector2D dest = pos.value;
|
QVector2D center = pos.value;
|
||||||
|
float radius = 0.0f;
|
||||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||||
{
|
{
|
||||||
const QVector2D targetPos = admin.get<PositionComponent>(t).value;
|
center = admin.get<PositionComponent>(t).value;
|
||||||
dest = OrbitMath::computeOrbitDestination(pos.value, targetPos,
|
radius = attack.orbitRadius_tiles;
|
||||||
attack.orbitRadius_tiles);
|
|
||||||
}
|
}
|
||||||
intent = MovementIntentComponent{true, dest};
|
intent = MovementIntentComponent{true, center, radius};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Weapons: assign the behavior target only if it is within this weapon's range.
|
// Weapons: assign the behavior target only if it is within this weapon's range.
|
||||||
|
|||||||
@@ -5,41 +5,76 @@
|
|||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
|
|
||||||
// Orbit movement helper (REQ-SHP-ORBIT). Behaviors that keep a ship circling a
|
// Orbit movement helper (REQ-SHP-ORBIT). Behaviors that keep a ship circling a
|
||||||
// target (attack, repair, salvage, rally) feed the result of this function as the
|
// target (attack, repair, salvage, rally) supply an orbit center and radius via
|
||||||
// movement intent destination instead of the target's center.
|
// the movement intent; MovementIntentSystem resolves the orbit direction and
|
||||||
|
// destination using these helpers.
|
||||||
namespace OrbitMath
|
namespace OrbitMath
|
||||||
{
|
{
|
||||||
// Lead angle (radians) by which the radial direction is rotated to produce
|
// Lead angle (radians) by which the radial direction is rotated to produce
|
||||||
// tangential motion. The fixed positive (counter-clockwise) sense makes the
|
// tangential motion. The orbit direction (sign of the rotation) is chosen
|
||||||
// orbit direction stable for the duration of orbiting a given target.
|
// 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;
|
constexpr float kOrbitLeadAngle_rad = 0.6f;
|
||||||
|
|
||||||
// Returns a destination on the orbit circle of `radius` around `target`. The
|
// Returns the orbit sense (+1 counter-clockwise, -1 clockwise) that matches
|
||||||
// result always lies exactly `radius` from `target`, so steering toward it
|
// the ship's current movement around `center`, so steering reinforces the
|
||||||
// both corrects the standoff distance and advances the ship tangentially.
|
// motion the ship already has. When the velocity is nearly radial or near
|
||||||
// A radius of zero or less falls back to the target center (legacy "approach
|
// zero (e.g. a head-on approach or a freshly spawned ship) the sense is
|
||||||
// the target" behavior), e.g. when the ship has no tool range to orbit at.
|
// ill-defined; this is an unstable point the ship leaves within a tick or
|
||||||
inline QVector2D computeOrbitDestination(const QVector2D& shipPos,
|
// two, so a deterministic fallback of +1 is returned.
|
||||||
const QVector2D& target, float radius)
|
inline float resolveOrbitSign(const QVector2D& shipPos, const QVector2D& center,
|
||||||
|
const QVector2D& velocity)
|
||||||
{
|
{
|
||||||
if (radius <= 0.0f) { return target; }
|
const QVector2D radial = shipPos - center;
|
||||||
|
const float radialLength = radial.length();
|
||||||
|
const float velocityLength = velocity.length();
|
||||||
|
if (radialLength < 1.0e-4f || velocityLength < 1.0e-4f)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
QVector2D radial = shipPos - target;
|
// z-component of radial x velocity, normalised to sin(angle) between them.
|
||||||
|
const float cross = radial.x() * velocity.y() - radial.y() * velocity.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();
|
float length = radial.length();
|
||||||
if (length < 1.0e-4f)
|
if (length < 1.0e-4f)
|
||||||
{
|
{
|
||||||
// Ship sits on the target; pick an arbitrary radial direction.
|
// Ship sits on the center; pick an arbitrary radial direction.
|
||||||
radial = QVector2D(1.0f, 0.0f);
|
radial = QVector2D(1.0f, 0.0f);
|
||||||
length = 1.0f;
|
length = 1.0f;
|
||||||
}
|
}
|
||||||
const QVector2D radialDirection = radial / length;
|
const QVector2D radialDirection = radial / length;
|
||||||
|
|
||||||
const float cosLead = std::cos(kOrbitLeadAngle_rad);
|
const float leadAngle = sign * kOrbitLeadAngle_rad;
|
||||||
const float sinLead = std::sin(kOrbitLeadAngle_rad);
|
const float cosLead = std::cos(leadAngle);
|
||||||
|
const float sinLead = std::sin(leadAngle);
|
||||||
const QVector2D leadDirection(
|
const QVector2D leadDirection(
|
||||||
radialDirection.x() * cosLead - radialDirection.y() * sinLead,
|
radialDirection.x() * cosLead - radialDirection.y() * sinLead,
|
||||||
radialDirection.x() * sinLead + radialDirection.y() * cosLead);
|
radialDirection.x() * sinLead + radialDirection.y() * cosLead);
|
||||||
|
|
||||||
return target + radius * leadDirection;
|
return center + radius * leadDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
#include "BehaviorKind.h"
|
#include "BehaviorKind.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "MovementIntentComponent.h"
|
#include "MovementIntentComponent.h"
|
||||||
#include "OrbitMath.h"
|
|
||||||
#include "PositionComponent.h"
|
|
||||||
#include "RallyBehavior.h"
|
#include "RallyBehavior.h"
|
||||||
#include "SelectedBehaviorComponent.h"
|
#include "SelectedBehaviorComponent.h"
|
||||||
#include "tracing.h"
|
#include "tracing.h"
|
||||||
@@ -12,15 +10,14 @@
|
|||||||
void RallyExecutor::execute(EntityAdmin& admin)
|
void RallyExecutor::execute(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
TRACE();
|
TRACE();
|
||||||
admin.forEach<RallyBehavior, SelectedBehaviorComponent, PositionComponent,
|
admin.forEach<RallyBehavior, SelectedBehaviorComponent,
|
||||||
MovementIntentComponent>(
|
MovementIntentComponent>(
|
||||||
[](entt::entity /*e*/, const RallyBehavior& rally,
|
[](entt::entity /*e*/, const RallyBehavior& rally,
|
||||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
const SelectedBehaviorComponent& selected,
|
||||||
MovementIntentComponent& intent)
|
MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
if (selected.winner != BehaviorKind::Rally) { return; }
|
if (selected.winner != BehaviorKind::Rally) { return; }
|
||||||
const QVector2D dest = OrbitMath::computeOrbitDestination(
|
intent = MovementIntentComponent{true, rally.rallyPoint,
|
||||||
pos.value, rally.rallyPoint, rally.orbitRadius_tiles);
|
rally.orbitRadius_tiles};
|
||||||
intent = MovementIntentComponent{true, dest};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "ModuleOwnerComponent.h"
|
#include "ModuleOwnerComponent.h"
|
||||||
#include "MovementIntentComponent.h"
|
#include "MovementIntentComponent.h"
|
||||||
#include "OrbitMath.h"
|
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
#include "RepairBehavior.h"
|
#include "RepairBehavior.h"
|
||||||
#include "RepairToolComponent.h"
|
#include "RepairToolComponent.h"
|
||||||
@@ -26,14 +25,14 @@ void RepairExecutor::execute(EntityAdmin& admin)
|
|||||||
if (!repair.currentTarget) { return; }
|
if (!repair.currentTarget) { return; }
|
||||||
|
|
||||||
const entt::entity t = *repair.currentTarget;
|
const entt::entity t = *repair.currentTarget;
|
||||||
QVector2D dest = pos.value;
|
QVector2D center = pos.value;
|
||||||
|
float radius = 0.0f;
|
||||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||||
{
|
{
|
||||||
const QVector2D targetPos = admin.get<PositionComponent>(t).value;
|
center = admin.get<PositionComponent>(t).value;
|
||||||
dest = OrbitMath::computeOrbitDestination(pos.value, targetPos,
|
radius = repair.orbitRadius_tiles;
|
||||||
repair.orbitRadius_tiles);
|
|
||||||
}
|
}
|
||||||
intent = MovementIntentComponent{true, dest};
|
intent = MovementIntentComponent{true, center, radius};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Repair tools: prefer the behavior target if it is within tool range.
|
// Repair tools: prefer the behavior target if it is within tool range.
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
#include "BehaviorKind.h"
|
#include "BehaviorKind.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "MovementIntentComponent.h"
|
#include "MovementIntentComponent.h"
|
||||||
#include "OrbitMath.h"
|
|
||||||
#include "PositionComponent.h"
|
|
||||||
#include "SalvageScrapBehavior.h"
|
#include "SalvageScrapBehavior.h"
|
||||||
#include "SelectedBehaviorComponent.h"
|
#include "SelectedBehaviorComponent.h"
|
||||||
#include "tracing.h"
|
#include "tracing.h"
|
||||||
@@ -12,16 +10,15 @@
|
|||||||
void SalvageScrapExecutor::execute(EntityAdmin& admin)
|
void SalvageScrapExecutor::execute(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
TRACE();
|
TRACE();
|
||||||
admin.forEach<SalvageScrapBehavior, SelectedBehaviorComponent, PositionComponent,
|
admin.forEach<SalvageScrapBehavior, SelectedBehaviorComponent,
|
||||||
MovementIntentComponent>(
|
MovementIntentComponent>(
|
||||||
[](entt::entity /*e*/, const SalvageScrapBehavior& salvage,
|
[](entt::entity /*e*/, const SalvageScrapBehavior& salvage,
|
||||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
const SelectedBehaviorComponent& selected,
|
||||||
MovementIntentComponent& intent)
|
MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
if (selected.winner != BehaviorKind::SalvageScrap) { return; }
|
if (selected.winner != BehaviorKind::SalvageScrap) { return; }
|
||||||
if (!salvage.scrapTarget) { return; }
|
if (!salvage.scrapTarget) { return; }
|
||||||
const QVector2D dest = OrbitMath::computeOrbitDestination(
|
intent = MovementIntentComponent{true, *salvage.scrapTarget,
|
||||||
pos.value, *salvage.scrapTarget, salvage.orbitRadius_tiles);
|
salvage.orbitRadius_tiles};
|
||||||
intent = MovementIntentComponent{true, dest};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "ModuleOwnerComponent.h"
|
#include "ModuleOwnerComponent.h"
|
||||||
#include "MovementIntentComponent.h"
|
#include "MovementIntentComponent.h"
|
||||||
#include "MovementIntentSystem.h"
|
#include "MovementIntentSystem.h"
|
||||||
|
#include "OrbitMath.h"
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
#include "RallyBehavior.h"
|
#include "RallyBehavior.h"
|
||||||
#include "RepairBehavior.h"
|
#include "RepairBehavior.h"
|
||||||
@@ -553,12 +554,13 @@ TEST_CASE("BehaviorSystem: repair ship orbits damaged friendly ship",
|
|||||||
REQUIRE(winnerOf(f.admin, repairShip) == BehaviorKind::Repair);
|
REQUIRE(winnerOf(f.admin, repairShip) == BehaviorKind::Repair);
|
||||||
REQUIRE(intent(f.admin, repairShip).active);
|
REQUIRE(intent(f.admin, repairShip).active);
|
||||||
|
|
||||||
// Orbit at orbit_factor * max repair range (REQ-SHP-ORBIT): the movement
|
// Orbit at orbit_factor * max repair range (REQ-SHP-ORBIT): the intent carries
|
||||||
// destination lies exactly the orbit radius from the target's center.
|
// the target's center and the orbit radius; MovementIntentSystem turns these
|
||||||
|
// into a point on the orbit circle when it steers.
|
||||||
const float orbitRadius = f.admin.get<RepairBehavior>(repairShip).orbitRadius_tiles;
|
const float orbitRadius = f.admin.get<RepairBehavior>(repairShip).orbitRadius_tiles;
|
||||||
REQUIRE(orbitRadius > 0.0f);
|
REQUIRE(orbitRadius > 0.0f);
|
||||||
REQUIRE((intent(f.admin, repairShip).target - pos(f.admin, friendly).value).length()
|
REQUIRE(intent(f.admin, repairShip).target == pos(f.admin, friendly).value);
|
||||||
== Approx(orbitRadius));
|
REQUIRE(intent(f.admin, repairShip).orbitRadius_tiles == Approx(orbitRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: repair ship heals damaged ally within repair range",
|
TEST_CASE("BehaviorSystem: repair ship heals damaged ally within repair range",
|
||||||
@@ -821,10 +823,12 @@ TEST_CASE("BehaviorSystem: salvage ship orbits nearest scrap", "[behavior]")
|
|||||||
REQUIRE(winnerOf(f.admin, ship) == BehaviorKind::SalvageScrap);
|
REQUIRE(winnerOf(f.admin, ship) == BehaviorKind::SalvageScrap);
|
||||||
REQUIRE(intent(f.admin, ship).active);
|
REQUIRE(intent(f.admin, ship).active);
|
||||||
|
|
||||||
// Orbit at orbit_factor * max collection range (REQ-SHP-ORBIT).
|
// Orbit at orbit_factor * max collection range (REQ-SHP-ORBIT): the intent
|
||||||
|
// carries the scrap center and the orbit radius.
|
||||||
const float orbitRadius = f.admin.get<SalvageScrapBehavior>(ship).orbitRadius_tiles;
|
const float orbitRadius = f.admin.get<SalvageScrapBehavior>(ship).orbitRadius_tiles;
|
||||||
REQUIRE(orbitRadius > 0.0f);
|
REQUIRE(orbitRadius > 0.0f);
|
||||||
REQUIRE((intent(f.admin, ship).target - scrapPos).length() == Approx(orbitRadius));
|
REQUIRE(intent(f.admin, ship).target == scrapPos);
|
||||||
|
REQUIRE(intent(f.admin, ship).orbitRadius_tiles == Approx(orbitRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: salvage ship collects scrap on arrival", "[behavior]")
|
TEST_CASE("BehaviorSystem: salvage ship collects scrap on arrival", "[behavior]")
|
||||||
@@ -1150,7 +1154,7 @@ TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[senso
|
|||||||
// Orbit movement (REQ-SHP-ORBIT)
|
// Orbit movement (REQ-SHP-ORBIT)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("Orbit: combat ship aims at a point on the orbit circle around its target",
|
TEST_CASE("Orbit: combat ship's intent carries the target center and orbit radius",
|
||||||
"[orbit]")
|
"[orbit]")
|
||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
@@ -1163,9 +1167,10 @@ TEST_CASE("Orbit: combat ship aims at a point on the orbit circle around its tar
|
|||||||
REQUIRE(winnerOf(f.admin, player) == BehaviorKind::Attack);
|
REQUIRE(winnerOf(f.admin, player) == BehaviorKind::Attack);
|
||||||
const float orbitRadius = f.admin.get<AttackBehavior>(player).orbitRadius_tiles;
|
const float orbitRadius = f.admin.get<AttackBehavior>(player).orbitRadius_tiles;
|
||||||
REQUIRE(orbitRadius > 0.0f);
|
REQUIRE(orbitRadius > 0.0f);
|
||||||
// The movement destination lies exactly the orbit radius from the enemy center.
|
// The intent carries the enemy center and the orbit radius; the orbit point is
|
||||||
REQUIRE((intent(f.admin, player).target - pos(f.admin, enemy).value).length()
|
// resolved later by MovementIntentSystem.
|
||||||
== Approx(orbitRadius));
|
REQUIRE(intent(f.admin, player).target == pos(f.admin, enemy).value);
|
||||||
|
REQUIRE(intent(f.admin, player).orbitRadius_tiles == Approx(orbitRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Orbit: rally ship orbits the rally point at the configured rally radius",
|
TEST_CASE("Orbit: rally ship orbits the rally point at the configured rally radius",
|
||||||
@@ -1181,7 +1186,8 @@ TEST_CASE("Orbit: rally ship orbits the rally point at the configured rally radi
|
|||||||
REQUIRE(winnerOf(f.admin, player) == BehaviorKind::Rally);
|
REQUIRE(winnerOf(f.admin, player) == BehaviorKind::Rally);
|
||||||
const float orbitRadius = f.admin.get<RallyBehavior>(player).orbitRadius_tiles;
|
const float orbitRadius = f.admin.get<RallyBehavior>(player).orbitRadius_tiles;
|
||||||
REQUIRE(orbitRadius == Approx(static_cast<float>(f.cfg.world.rallyOrbitRadius_tiles)));
|
REQUIRE(orbitRadius == Approx(static_cast<float>(f.cfg.world.rallyOrbitRadius_tiles)));
|
||||||
REQUIRE((intent(f.admin, player).target - rallyPoint).length() == Approx(orbitRadius));
|
REQUIRE(intent(f.admin, player).target == rallyPoint);
|
||||||
|
REQUIRE(intent(f.admin, player).orbitRadius_tiles == Approx(orbitRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Orbit: combat ship settles near the orbit radius and circles a stationary target",
|
TEST_CASE("Orbit: combat ship settles near the orbit radius and circles a stationary target",
|
||||||
@@ -1218,3 +1224,39 @@ TEST_CASE("Orbit: combat ship settles near the orbit radius and circles a statio
|
|||||||
// The ship is circling: its angular position around the target has moved.
|
// The ship is circling: its angular position around the target has moved.
|
||||||
REQUIRE(std::abs(angleAfter - angleBefore) > 0.05f);
|
REQUIRE(std::abs(angleAfter - angleBefore) > 0.05f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("OrbitMath: sign mirrors the orbit destination across the radial",
|
||||||
|
"[orbit]")
|
||||||
|
{
|
||||||
|
const QVector2D center(0.0f, 0.0f);
|
||||||
|
const QVector2D shipPos(5.0f, 0.0f);
|
||||||
|
const float radius = 5.0f;
|
||||||
|
|
||||||
|
const QVector2D ccw = OrbitMath::computeOrbitDestination(shipPos, center, radius, +1.0f);
|
||||||
|
const QVector2D cw = OrbitMath::computeOrbitDestination(shipPos, center, radius, -1.0f);
|
||||||
|
|
||||||
|
// Both lie exactly on the orbit circle.
|
||||||
|
REQUIRE((ccw - center).length() == Approx(radius));
|
||||||
|
REQUIRE((cw - center).length() == Approx(radius));
|
||||||
|
// Opposite tangential lead: CCW leads to +y, CW to -y; x components match.
|
||||||
|
REQUIRE(ccw.y() > 0.0f);
|
||||||
|
REQUIRE(cw.y() < 0.0f);
|
||||||
|
REQUIRE(ccw.y() == Approx(-cw.y()));
|
||||||
|
REQUIRE(ccw.x() == Approx(cw.x()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("OrbitMath: orbit sign follows the ship's tangential velocity",
|
||||||
|
"[orbit]")
|
||||||
|
{
|
||||||
|
const QVector2D center(0.0f, 0.0f);
|
||||||
|
const QVector2D shipPos(5.0f, 0.0f); // radial points +x
|
||||||
|
|
||||||
|
// Tangential +y velocity is counter-clockwise around the center → +1.
|
||||||
|
REQUIRE(OrbitMath::resolveOrbitSign(shipPos, center, QVector2D(0.0f, 1.0f)) == Approx(1.0f));
|
||||||
|
// Tangential -y velocity is clockwise → -1.
|
||||||
|
REQUIRE(OrbitMath::resolveOrbitSign(shipPos, center, QVector2D(0.0f, -1.0f)) == Approx(-1.0f));
|
||||||
|
// Radial velocity (toward/away from center) is ambiguous → fallback +1.
|
||||||
|
REQUIRE(OrbitMath::resolveOrbitSign(shipPos, center, QVector2D(-1.0f, 0.0f)) == Approx(1.0f));
|
||||||
|
// Zero velocity (e.g. freshly spawned) → fallback +1.
|
||||||
|
REQUIRE(OrbitMath::resolveOrbitSign(shipPos, center, QVector2D(0.0f, 0.0f)) == Approx(1.0f));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user