< Summary

Class:AgentBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Agents/AgentBase.cs
Covered lines:96
Uncovered lines:79
Coverable lines:175
Total lines:331
Line coverage:54.8% (96 of 175)
Covered branches:0
Total branches:0
Covered methods:39
Total methods:61
Method coverage:63.9% (39 of 61)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AgentBase()0%110100%
MaxForwardAcceleration()0%330100%
MaxNormalAcceleration()0%550100%
CreateTargetModel(...)0%2100%
DestroyTargetModel()0%6200%
UpdateTargetModel()0%2101400%
GetRelativeTransformation(...)0%110100%
GetRelativeTransformation(...)0%110100%
GetRelativeTransformation(...)0%110100%
Terminate()0%20400%
Awake()0%2.152066.67%
Start()0%2100%
FixedUpdate()0%2100%
Update()0%2100%
LateUpdate()0%2100%
OnDestroy()0%6200%
UpdateAgentConfig()0%15.4811066.67%
OnDrawGizmos()0%6200%
CheckFloorCollision(...)0%6200%
ShouldIgnoreCollision(...)0%12300%
UpdateTransformData()0%110100%
AlignWithVelocity()0%6200%
GetRelativeTransformation(...)0%550100%

File(s)

/github/workspace/Assets/Scripts/Agents/AgentBase.cs

#LineLine coverage
 1using UnityEngine;
 2
 3// Base implementation of an agent.
 4//
 5// See the agent interface for property and method documentation.
 6public class AgentBase : MonoBehaviour, IAgent {
 7  public event AgentTerminatedEventHandler OnTerminated;
 8
 9  private const float _epsilon = 1e-12f;
 10
 11  // Rigid body component.
 12  protected Rigidbody _rigidbody;
 13
 14  // The position field is cached and is updated before every fixed update.
 15  [SerializeField]
 16  private Vector3 _position;
 17
 18  // The acceleration field is not part of the rigid body component, so it is tracked separately.
 19  // The acceleration is applied as a force during each frame update.
 20  [SerializeField]
 21  private Vector3 _acceleration;
 22
 23  // The acceleration input is calculated by the controller and provided to the movement behavior.
 24  [SerializeField]
 25  private Vector3 _accelerationInput;
 26
 27  // The agent's position within the hierarchical strategy is given by the hierarchical agent.
 28  [SerializeReference]
 29  private HierarchicalAgent _hierarchicalAgent;
 30
 31  // Static configuration of the agent, including agent type, unit cost, acceleration configuration,
 32  // aerodynamics parameters, power table, and visualization configuration.
 33  private Configs.StaticConfig _staticConfig;
 34
 35  // Agent configuration, including initial state, attack behavior configuration (for threats),
 36  // dynamic configuration, and sub-agent configuration (for interceptors).
 37  private Configs.AgentConfig _agentConfig;
 38
 39  // Last sensing time.
 40  [SerializeField]
 16641  private float _lastSensingTime = Mathf.NegativeInfinity;
 42
 43  public HierarchicalAgent HierarchicalAgent {
 044    get => _hierarchicalAgent;
 045    set => _hierarchicalAgent = value;
 46  }
 47  public Configs.StaticConfig StaticConfig {
 36748    get => _staticConfig;
 5949    set {
 5950      _staticConfig = value;
 5951      _rigidbody.mass = StaticConfig.BodyConfig?.Mass ?? 1;
 5952    }
 53  }
 54  public Configs.AgentConfig AgentConfig {
 1355    get => _agentConfig;
 756    set {
 757      _agentConfig = value;
 758      UpdateAgentConfig();
 759    }
 60  }
 61
 062  public IMovement Movement { get; set; }
 063  public IController Controller { get; set; }
 1664  public ISensor Sensor { get; set; }
 6765  public IAgent TargetModel { get; set; }
 66
 67  public Vector3 Position {
 26268    get => _position;
 8969    set {
 8970      Transform.position = value;
 8971      _position = value;
 8972    }
 73  }
 74  public Vector3 Velocity {
 32375    get => _rigidbody.linearVelocity;
 11576    set => _rigidbody.linearVelocity = value;
 77  }
 11278  public float Speed => Velocity.magnitude;
 79  public Vector3 Acceleration {
 2480    get => _acceleration;
 981    set => _acceleration = value;
 82  }
 83  public Vector3 AccelerationInput {
 084    get => _accelerationInput;
 085    set => _accelerationInput = value;
 86  }
 87
 88  // If true, the agent is able to pursue targets.
 089  public virtual bool IsPursuer => true;
 90
 91  // Elapsed time since the creation of the agent.
 17492  public float ElapsedTime { get; private set; } = 0f;
 93
 94  // If true, the agent is terminated.
 16695  public bool IsTerminated { get; private set; } = false;
 96
 97  // The agent transform is cached.
 113498  public Transform Transform { get; private set; }
 99
 100  // The up direction is cached and updated before every fixed update.
 166101  public Vector3 Up { get; private set; }
 102
 103  // The forward direction is cached and updated before every fixed update.
 237104  public Vector3 Forward { get; private set; }
 105
 106  // The right direction is cached and updated before every fixed update.
 167107  public Vector3 Right { get; private set; }
 108
 109  // The inverse rotation is cached and updated before every fixed update.
 282110  public Quaternion InverseRotation { get; private set; }
 111
 26112  public float MaxForwardAcceleration() {
 26113    return StaticConfig.AccelerationConfig?.MaxForwardAcceleration ?? 0;
 26114  }
 115
 60116  public float MaxNormalAcceleration() {
 60117    float maxReferenceNormalAcceleration =
 118        (StaticConfig.AccelerationConfig?.MaxReferenceNormalAcceleration ?? 0) * Constants.kGravity;
 60119    float referenceSpeed = StaticConfig.AccelerationConfig?.ReferenceSpeed ?? 1;
 60120    return Mathf.Pow(Speed / referenceSpeed, 2) * maxReferenceNormalAcceleration;
 60121  }
 122
 0123  public void CreateTargetModel(IHierarchical target) {
 0124    TargetModel = SimManager.Instance.CreateDummyAgent(target.Position, target.Velocity);
 0125  }
 126
 0127  public void DestroyTargetModel() {
 0128    if (TargetModel != null) {
 0129      SimManager.Instance.DestroyDummyAgent(TargetModel);
 0130      TargetModel = null;
 0131    }
 0132  }
 133
 0134  public void UpdateTargetModel() {
 0135    if (HierarchicalAgent == null || HierarchicalAgent.Target == null || Sensor == null) {
 0136      return;
 137    }
 0138    if (HierarchicalAgent.Target.IsTerminated) {
 0139      HierarchicalAgent.Target = null;
 0140      return;
 141    }
 142
 143    // Check whether the sensing period has elapsed.
 0144    float sensingFrequency = AgentConfig?.DynamicConfig?.SensorConfig?.Frequency ?? Mathf.Infinity;
 0145    float sensingPeriod = 1f / sensingFrequency;
 0146    if (ElapsedTime - _lastSensingTime >= sensingPeriod) {
 147      // Sense the target.
 0148      SensorOutput sensorOutput = Sensor.Sense(HierarchicalAgent.Target);
 0149      TargetModel.Position = Position + sensorOutput.Position.Cartesian;
 0150      TargetModel.Velocity = Velocity + sensorOutput.Velocity.Cartesian;
 0151      TargetModel.Acceleration = Acceleration + sensorOutput.Acceleration.Cartesian;
 0152      _lastSensingTime = ElapsedTime;
 0153    }
 0154  }
 155
 24156  public Transformation GetRelativeTransformation(IAgent target) {
 24157    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 24158  }
 159
 32160  public Transformation GetRelativeTransformation(IHierarchical target) {
 32161    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 32162  }
 163
 2164  public Transformation GetRelativeTransformation(in Vector3 waypoint) {
 2165    return GetRelativeTransformation(waypoint, velocity: Vector3.zero, acceleration: Vector3.zero);
 2166  }
 167
 0168  public void Terminate() {
 0169    if (HierarchicalAgent != null) {
 0170      HierarchicalAgent.Target = null;
 0171    }
 0172    if (Movement is MissileMovement movement) {
 0173      movement.FlightPhase = Simulation.FlightPhase.Terminated;
 0174    }
 0175    IsTerminated = true;
 0176    OnTerminated?.Invoke(this);
 0177    Destroy(gameObject);
 0178  }
 179
 180  // Awake is called before Start and right after a prefab is instantiated.
 166181  protected virtual void Awake() {
 166182    Transform = transform;
 166183    _rigidbody = GetComponent<Rigidbody>();
 184
 166185    UpdateTransformData();
 166186    if (EarlyFixedUpdateManager.Instance != null) {
 0187      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate += UpdateTransformData;
 0188    }
 166189  }
 190
 191  // Start is called before the first frame update.
 0192  protected virtual void Start() {}
 193
 194  // FixedUpdate is called multiple times per frame. All physics calculations and updates occur
 195  // immediately after FixedUpdate, and all movement values are multiplied by Time.deltaTime.
 0196  protected virtual void FixedUpdate() {
 0197    ElapsedTime += Time.fixedDeltaTime;
 198
 0199    UpdateTargetModel();
 0200    AlignWithVelocity();
 0201  }
 202
 203  // Update is called every frame.
 0204  protected virtual void Update() {}
 205
 206  // LateUpdate is called every frame after all Update functions have been called.
 0207  protected virtual void LateUpdate() {}
 208
 209  // OnDestroy is called when the object is being destroyed.
 0210  protected virtual void OnDestroy() {
 0211    if (EarlyFixedUpdateManager.Instance != null) {
 0212      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate -= UpdateTransformData;
 0213    }
 0214  }
 215
 216  // UpdateAgentConfig is called whenever the agent configuration is changed.
 7217  protected virtual void UpdateAgentConfig() {
 218    // Set the sensor.
 7219    switch (AgentConfig.DynamicConfig?.SensorConfig?.Type) {
 7220      case Simulation.SensorType.Ideal: {
 7221        Sensor = new IdealSensor(this);
 7222        break;
 223      }
 0224      default: {
 0225        Debug.LogError($"Sensor type {AgentConfig.DynamicConfig?.SensorConfig?.Type} not found.");
 0226        break;
 227      }
 228    }
 7229  }
 230
 0231  protected virtual void OnDrawGizmos() {
 0232    if (Application.isPlaying) {
 0233      Gizmos.color = Color.green;
 0234      Gizmos.DrawRay(Position, _accelerationInput);
 0235    }
 0236  }
 237
 0238  protected bool CheckFloorCollision(Collider other) {
 239    // Check if the agent hit the floor with a negative vertical speed.
 0240    return other.gameObject.name == "Floor" && Vector3.Dot(Velocity, Vector3.up) < 0;
 0241  }
 242
 0243  protected bool ShouldIgnoreCollision(IAgent otherAgent) {
 244    // Dummy agents are virtual targets and should not trigger collisions.
 0245    return otherAgent == null || otherAgent is DummyAgent || otherAgent.IsTerminated;
 0246  }
 247
 166248  private void UpdateTransformData() {
 166249    _position = Transform.position;
 166250    Up = Transform.up;
 166251    Forward = Transform.forward;
 166252    Right = Transform.right;
 166253    InverseRotation = Quaternion.Inverse(Transform.rotation);
 166254  }
 255
 0256  private void AlignWithVelocity() {
 257    const float speedThreshold = 0.1f;
 258    const float rotationSpeedDegreesPerSecond = 10000f;
 259
 260    // Only align if the velocity is significant.
 0261    if (Speed > speedThreshold) {
 262      // Create a rotation with the forward direction along the velocity vector and the up direction
 263      // along world up.
 0264      Quaternion targetRotation = Quaternion.LookRotation(Velocity, Vector3.up);
 265
 266      // Smoothly rotate towards the target rotation.
 0267      Transform.rotation = Quaternion.RotateTowards(
 268          Transform.rotation, targetRotation,
 269          maxDegreesDelta: rotationSpeedDegreesPerSecond * Time.fixedDeltaTime);
 0270    }
 0271  }
 272
 273  private Transformation GetRelativeTransformation(in Vector3 position, in Vector3 velocity,
 58274                                                   in Vector3 acceleration) {
 58275    Vector3 relativePosition = position - Position;
 58276    Vector3 relativeLocalPosition = InverseRotation * relativePosition;
 58277    Vector3 relativeVelocity = velocity - Velocity;
 58278    Vector3 relativeLocalVelocity = InverseRotation * relativeVelocity;
 279
 58280    float x = relativeLocalPosition.x;
 58281    float y = relativeLocalPosition.y;
 58282    float z = relativeLocalPosition.z;
 283
 58284    float horizontalSqr = x * x + z * z;
 58285    float horizontal = Mathf.Sqrt(horizontalSqr);
 58286    float rangeSqr = horizontalSqr + y * y;
 58287    float range = Mathf.Sqrt(rangeSqr);
 288
 58289    float azimuth = Mathf.Atan2(x, z);
 58290    float elevation = Mathf.Atan2(y, horizontal);
 58291    var positionTransformation = new PositionTransformation {
 292      Cartesian = relativePosition,
 293      Range = range,
 294      Azimuth = azimuth,
 295      Elevation = elevation,
 296    };
 297
 58298    float rangeRate =
 299        range > _epsilon ? Vector3.Dot(relativeLocalVelocity, relativeLocalPosition) / range : 0f;
 58300    float azimuthRate = 0f;
 58301    float elevationRate = 0f;
 103302    if (horizontal > _epsilon) {
 45303      azimuthRate = -(x * relativeLocalVelocity.z - z * relativeLocalVelocity.x) / horizontalSqr;
 45304      elevationRate =
 305          (relativeLocalVelocity.y * horizontal -
 306           y * (x * relativeLocalVelocity.x + z * relativeLocalVelocity.z) / horizontal) /
 307          rangeSqr;
 58308    } else {
 309      // The other agent is exactly above or below.
 13310      azimuthRate = 0f;
 13311      float horizontalSpeed = Mathf.Sqrt(relativeLocalVelocity.x * relativeLocalVelocity.x +
 312                                         relativeLocalVelocity.z * relativeLocalVelocity.z);
 13313      elevationRate = -horizontalSpeed / (Mathf.Abs(y) > _epsilon ? y : Mathf.Sign(y) * _epsilon);
 13314    }
 58315    var velocityTransformation = new VelocityTransformation {
 316      Cartesian = relativeVelocity,
 317      Range = rangeRate,
 318      Azimuth = azimuthRate,
 319      Elevation = elevationRate,
 320    };
 321
 58322    var accelerationTransformation = new AccelerationTransformation {
 323      Cartesian = acceleration,
 324    };
 58325    return new Transformation {
 326      Position = positionTransformation,
 327      Velocity = velocityTransformation,
 328      Acceleration = accelerationTransformation,
 329    };
 58330  }
 331}