#include "MovementIntentSystem.h" #include #include #include #include "DynamicBodyComponent.h" #include "EntityAdmin.h" #include "FacingComponent.h" #include "MovementIntentComponent.h" #include "OrbitMath.h" #include "PositionComponent.h" #include "tracing.h" static float wrapAngle(float a) { constexpr float kPi = 3.14159265f; a = std::fmod(a, 2.0f * kPi); if (a > kPi) { a -= 2.0f * kPi; } if (a < -kPi) { a += 2.0f * kPi; } return a; } void MovementIntentSystem::tick(EntityAdmin& admin) { TRACE(); admin.forEach( [](entt::entity /*e*/, const PositionComponent& pos, const FacingComponent& facing, DynamicBodyComponent& body, const MovementIntentComponent& intent) { if (!intent.active) { // No movement intent: brake using available thrust. const float linearBraking = std::min(body.velocity_tpt.length(), body.maneuveringAcceleration_tptt); body.linearAcceleration_tptt = (body.velocity_tpt.length() > 0.0001f) ? -body.velocity_tpt.normalized() * linearBraking : QVector2D(0.0f, 0.0f); const float angBraking = std::min(std::abs(body.angularVelocity_rpt), body.maxAngularAcceleration_rptt); body.angularAcceleration_rptt = (body.angularVelocity_rpt >= 0.0f) ? -angBraking : angBraking; return; } // 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, intent.orbitCenterVelocity_tpt); destination = OrbitMath::computeOrbitDestination( pos.value, intent.target, intent.orbitRadius_tiles, sign); } const QVector2D delta = destination - pos.value; const float dist = delta.length(); if (dist < 0.001f) { // Already at target: no new thrust. The ship drifts; it will // re-approach next tick once it has moved away. body.linearAcceleration_tptt = QVector2D(0.0f, 0.0f); body.angularAcceleration_rptt = 0.0f; return; } // --- Angular acceleration --- const float desiredAngle = std::atan2(delta.y(), delta.x()); const float angleDiff = wrapAngle(desiredAngle - facing.radians); const float rotDelta = std::max(-body.maxAngularAcceleration_rptt, std::min(angleDiff, body.maxAngularAcceleration_rptt)); float newAngVel = body.angularVelocity_rpt + rotDelta; // Overshoot prevention: if the accumulated angular velocity already // exceeds the remaining angle, snap it to exactly that angle so the // ship doesn't rotate past its heading. const bool sameSign = (newAngVel >= 0.0f) == (angleDiff >= 0.0f); if (sameSign && std::abs(newAngVel) > std::abs(angleDiff)) { newAngVel = angleDiff; } body.angularAcceleration_rptt = newAngVel - body.angularVelocity_rpt; // DynamicBodySystem applies the clamp to maxRotationSpeed_rpt after // integrating, so we do not clamp here. // --- Linear acceleration --- // Use the projected facing (after this tick's angular integration) so // that the main thruster aligns with where the ship will actually be // pointing when DynamicBodySystem applies the forces. const float projectedRadians = wrapAngle(facing.radians + newAngVel); const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians)); const float manAccel = body.maneuveringAcceleration_tptt; const float stoppingDist = (body.maxSpeed_tpt * body.maxSpeed_tpt) / (2.0f * manAccel); // Cap to dist so the ship never overshoots the target in a single tick. const float baseDesiredSpeed = (dist <= stoppingDist) ? std::sqrt(2.0f * manAccel * dist) : body.maxSpeed_tpt; const float desiredSpeed = std::min(dist, baseDesiredSpeed); const QVector2D desiredVel = delta.normalized() * desiredSpeed; const QVector2D velError = desiredVel - body.velocity_tpt; const float mainAligned = std::max(0.0f, QVector2D::dotProduct(velError, facingVec)); const float mainApplied = std::min(mainAligned, body.mainAcceleration_tptt); const QVector2D mainDelta = facingVec * mainApplied; const QVector2D remaining = velError - mainDelta; const float remainLen = remaining.length(); const QVector2D maneuverDelta = (remainLen > manAccel) ? remaining.normalized() * manAccel : remaining; body.linearAcceleration_tptt = mainDelta + maneuverDelta; }); }