< Summary

Class:AgentBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Agents/AgentBase.cs
Covered lines:139
Uncovered lines:36
Coverable lines:175
Total lines:331
Line coverage:79.4% (139 of 175)
Covered branches:0
Total branches:0
Covered methods:56
Total methods:61
Method coverage:91.8% (56 of 61)

Metrics

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

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]
 192941  private float _lastSensingTime = Mathf.NegativeInfinity;
 42
 43  public HierarchicalAgent HierarchicalAgent {
 3677444    get => _hierarchicalAgent;
 96945    set => _hierarchicalAgent = value;
 46  }
 47  public Configs.StaticConfig StaticConfig {
 2033548    get => _staticConfig;
 96949    set {
 96950      _staticConfig = value;
 96951      _rigidbody.mass = StaticConfig.BodyConfig?.Mass ?? 1;
 96952    }
 53  }
 54  public Configs.AgentConfig AgentConfig {
 728255    get => _agentConfig;
 96956    set {
 96957      _agentConfig = value;
 96958      UpdateAgentConfig();
 96959    }
 60  }
 61
 635462  public IMovement Movement { get; set; }
 635463  public IController Controller { get; set; }
 791864  public ISensor Sensor { get; set; }
 1122665  public IAgent TargetModel { get; set; }
 66
 67  public Vector3 Position {
 2508168    get => _position;
 166169    set {
 166170      Transform.position = value;
 166171      _position = value;
 166172    }
 73  }
 74  public Vector3 Velocity {
 5319775    get => _rigidbody.linearVelocity;
 358576    set => _rigidbody.linearVelocity = value;
 77  }
 2176878  public float Speed => Velocity.magnitude;
 79  public Vector3 Acceleration {
 1398880    get => _acceleration;
 704681    set => _acceleration = value;
 82  }
 83  public Vector3 AccelerationInput {
 1596184    get => _accelerationInput;
 1067385    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.
 3021092  public float ElapsedTime { get; private set; } = 0f;
 93
 94  // If true, the agent is terminated.
 721795  public bool IsTerminated { get; private set; } = false;
 96
 97  // The agent transform is cached.
 8664398  public Transform Transform { get; private set; }
 99
 100  // The up direction is cached and updated before every fixed update.
 12590101  public Vector3 Up { get; private set; }
 102
 103  // The forward direction is cached and updated before every fixed update.
 34353104  public Vector3 Forward { get; private set; }
 105
 106  // The right direction is cached and updated before every fixed update.
 12590107  public Vector3 Right { get; private set; }
 108
 109  // The inverse rotation is cached and updated before every fixed update.
 26488110  public Quaternion InverseRotation { get; private set; }
 111
 5814112  public float MaxForwardAcceleration() {
 5814113    return StaticConfig.AccelerationConfig?.MaxForwardAcceleration ?? 0;
 5814114  }
 115
 5814116  public float MaxNormalAcceleration() {
 5814117    float maxReferenceNormalAcceleration =
 118        (StaticConfig.AccelerationConfig?.MaxReferenceNormalAcceleration ?? 0) * Constants.kGravity;
 5814119    float referenceSpeed = StaticConfig.AccelerationConfig?.ReferenceSpeed ?? 1;
 5814120    return Mathf.Pow(Speed / referenceSpeed, 2) * maxReferenceNormalAcceleration;
 5814121  }
 122
 955123  public void CreateTargetModel(IHierarchical target) {
 955124    TargetModel = SimManager.Instance.CreateDummyAgent(target.Position, target.Velocity);
 955125  }
 126
 0127  public void DestroyTargetModel() {
 0128    if (TargetModel != null) {
 0129      SimManager.Instance.DestroyDummyAgent(TargetModel);
 0130      TargetModel = null;
 0131    }
 0132  }
 133
 10666134  public void UpdateTargetModel() {
 16044135    if (HierarchicalAgent == null || HierarchicalAgent.Target == null || Sensor == null) {
 5378136      return;
 137    }
 5288138    if (HierarchicalAgent.Target.IsTerminated) {
 0139      HierarchicalAgent.Target = null;
 0140      return;
 141    }
 142
 143    // Check whether the sensing period has elapsed.
 5288144    float sensingFrequency = AgentConfig?.DynamicConfig?.SensorConfig?.Frequency ?? Mathf.Infinity;
 5288145    float sensingPeriod = 1f / sensingFrequency;
 6949146    if (ElapsedTime - _lastSensingTime >= sensingPeriod) {
 147      // Sense the target.
 1661148      SensorOutput sensorOutput = Sensor.Sense(HierarchicalAgent.Target);
 1661149      TargetModel.Position = Position + sensorOutput.Position.Cartesian;
 1661150      TargetModel.Velocity = Velocity + sensorOutput.Velocity.Cartesian;
 1661151      TargetModel.Acceleration = Acceleration + sensorOutput.Acceleration.Cartesian;
 1661152      _lastSensingTime = ElapsedTime;
 1661153    }
 10666154  }
 155
 0156  public Transformation GetRelativeTransformation(IAgent target) {
 0157    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 0158  }
 159
 1661160  public Transformation GetRelativeTransformation(IHierarchical target) {
 1661161    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 1661162  }
 163
 5288164  public Transformation GetRelativeTransformation(in Vector3 waypoint) {
 5288165    return GetRelativeTransformation(waypoint, velocity: Vector3.zero, acceleration: Vector3.zero);
 5288166  }
 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.
 1924181  protected virtual void Awake() {
 1924182    Transform = transform;
 1924183    _rigidbody = GetComponent<Rigidbody>();
 184
 1924185    UpdateTransformData();
 3848186    if (EarlyFixedUpdateManager.Instance != null) {
 1924187      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate += UpdateTransformData;
 1924188    }
 1924189  }
 190
 191  // Start is called before the first frame update.
 3848192  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.
 10666196  protected virtual void FixedUpdate() {
 10666197    ElapsedTime += Time.fixedDeltaTime;
 198
 10666199    UpdateTargetModel();
 10666200    AlignWithVelocity();
 10666201  }
 202
 203  // Update is called every frame.
 10484204  protected virtual void Update() {}
 205
 206  // LateUpdate is called every frame after all Update functions have been called.
 8558207  protected virtual void LateUpdate() {}
 208
 209  // OnDestroy is called when the object is being destroyed.
 1909210  protected virtual void OnDestroy() {
 3818211    if (EarlyFixedUpdateManager.Instance != null) {
 1909212      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate -= UpdateTransformData;
 1909213    }
 1909214  }
 215
 216  // UpdateAgentConfig is called whenever the agent configuration is changed.
 969217  protected virtual void UpdateAgentConfig() {
 218    // Set the sensor.
 969219    switch (AgentConfig.DynamicConfig?.SensorConfig?.Type) {
 969220      case Simulation.SensorType.Ideal: {
 969221        Sensor = new IdealSensor(this);
 969222        break;
 223      }
 0224      default: {
 0225        Debug.LogError($"Sensor type {AgentConfig.DynamicConfig?.SensorConfig?.Type} not found.");
 0226        break;
 227      }
 228    }
 969229  }
 230
 0231  protected virtual void OnDrawGizmos() {
 0232    if (Application.isPlaying) {
 0233      Gizmos.color = Color.green;
 0234      Gizmos.DrawRay(Position, _accelerationInput);
 0235    }
 0236  }
 237
 994238  protected bool CheckFloorCollision(Collider other) {
 239    // Check if the agent hit the floor with a negative vertical speed.
 994240    return other.gameObject.name == "Floor" && Vector3.Dot(Velocity, Vector3.up) < 0;
 994241  }
 242
 994243  protected bool ShouldIgnoreCollision(IAgent otherAgent) {
 244    // Dummy agents are virtual targets and should not trigger collisions.
 994245    return otherAgent == null || otherAgent is DummyAgent || otherAgent.IsTerminated;
 994246  }
 247
 12590248  private void UpdateTransformData() {
 12590249    _position = Transform.position;
 12590250    Up = Transform.up;
 12590251    Forward = Transform.forward;
 12590252    Right = Transform.right;
 12590253    InverseRotation = Quaternion.Inverse(Transform.rotation);
 12590254  }
 255
 10666256  private void AlignWithVelocity() {
 257    const float speedThreshold = 0.1f;
 258    const float rotationSpeedDegreesPerSecond = 10000f;
 259
 260    // Only align if the velocity is significant.
 20720261    if (Speed > speedThreshold) {
 262      // Create a rotation with the forward direction along the velocity vector and the up direction
 263      // along world up.
 10054264      Quaternion targetRotation = Quaternion.LookRotation(Velocity, Vector3.up);
 265
 266      // Smoothly rotate towards the target rotation.
 10054267      Transform.rotation = Quaternion.RotateTowards(
 268          Transform.rotation, targetRotation,
 269          maxDegreesDelta: rotationSpeedDegreesPerSecond * Time.fixedDeltaTime);
 10054270    }
 10666271  }
 272
 273  private Transformation GetRelativeTransformation(in Vector3 position, in Vector3 velocity,
 6949274                                                   in Vector3 acceleration) {
 6949275    Vector3 relativePosition = position - Position;
 6949276    Vector3 relativeLocalPosition = InverseRotation * relativePosition;
 6949277    Vector3 relativeVelocity = velocity - Velocity;
 6949278    Vector3 relativeLocalVelocity = InverseRotation * relativeVelocity;
 279
 6949280    float x = relativeLocalPosition.x;
 6949281    float y = relativeLocalPosition.y;
 6949282    float z = relativeLocalPosition.z;
 283
 6949284    float horizontalSqr = x * x + z * z;
 6949285    float horizontal = Mathf.Sqrt(horizontalSqr);
 6949286    float rangeSqr = horizontalSqr + y * y;
 6949287    float range = Mathf.Sqrt(rangeSqr);
 288
 6949289    float azimuth = Mathf.Atan2(x, z);
 6949290    float elevation = Mathf.Atan2(y, horizontal);
 6949291    var positionTransformation = new PositionTransformation {
 292      Cartesian = relativePosition,
 293      Range = range,
 294      Azimuth = azimuth,
 295      Elevation = elevation,
 296    };
 297
 6949298    float rangeRate =
 299        range > _epsilon ? Vector3.Dot(relativeLocalVelocity, relativeLocalPosition) / range : 0f;
 6949300    float azimuthRate = 0f;
 6949301    float elevationRate = 0f;
 13898302    if (horizontal > _epsilon) {
 6949303      azimuthRate = -(x * relativeLocalVelocity.z - z * relativeLocalVelocity.x) / horizontalSqr;
 6949304      elevationRate =
 305          (relativeLocalVelocity.y * horizontal -
 306           y * (x * relativeLocalVelocity.x + z * relativeLocalVelocity.z) / horizontal) /
 307          rangeSqr;
 6949308    } else {
 309      // The other agent is exactly above or below.
 0310      azimuthRate = 0f;
 0311      float horizontalSpeed = Mathf.Sqrt(relativeLocalVelocity.x * relativeLocalVelocity.x +
 312                                         relativeLocalVelocity.z * relativeLocalVelocity.z);
 0313      elevationRate = -horizontalSpeed / (Mathf.Abs(y) > _epsilon ? y : Mathf.Sign(y) * _epsilon);
 0314    }
 6949315    var velocityTransformation = new VelocityTransformation {
 316      Cartesian = relativeVelocity,
 317      Range = rangeRate,
 318      Azimuth = azimuthRate,
 319      Elevation = elevationRate,
 320    };
 321
 6949322    var accelerationTransformation = new AccelerationTransformation {
 323      Cartesian = acceleration,
 324    };
 6949325    return new Transformation {
 326      Position = positionTransformation,
 327      Velocity = velocityTransformation,
 328      Acceleration = accelerationTransformation,
 329    };
 6949330  }
 331}