< 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 {
 4719144    get => _hierarchicalAgent;
 96945    set => _hierarchicalAgent = value;
 46  }
 47  public Configs.StaticConfig StaticConfig {
 2558248    get => _staticConfig;
 96949    set {
 96950      _staticConfig = value;
 96951      _rigidbody.mass = StaticConfig.BodyConfig?.Mass ?? 1;
 96952    }
 53  }
 54  public Configs.AgentConfig AgentConfig {
 898955    get => _agentConfig;
 96956    set {
 96957      _agentConfig = value;
 96958      UpdateAgentConfig();
 96959    }
 60  }
 61
 809662  public IMovement Movement { get; set; }
 809663  public IController Controller { get; set; }
 962564  public ISensor Sensor { get; set; }
 1293365  public IAgent TargetModel { get; set; }
 66
 67  public Vector3 Position {
 3020268    get => _position;
 166169    set {
 166170      Transform.position = value;
 166171      _position = value;
 166172    }
 73  }
 74  public Vector3 Velocity {
 6866575    get => _rigidbody.linearVelocity;
 358576    set => _rigidbody.linearVelocity = value;
 77  }
 2867378  public float Speed => Velocity.magnitude;
 79  public Vector3 Acceleration {
 1743780    get => _acceleration;
 878881    set => _acceleration = value;
 82  }
 83  public Vector3 AccelerationInput {
 2111784    get => _accelerationInput;
 1412285    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.
 3881592  public float ElapsedTime { get; private set; } = 0f;
 93
 94  // If true, the agent is terminated.
 892495  public bool IsTerminated { get; private set; } = false;
 96
 97  // The agent transform is cached.
 11078698  public Transform Transform { get; private set; }
 99
 100  // The up direction is cached and updated before every fixed update.
 16039101  public Vector3 Up { get; private set; }
 102
 103  // The forward direction is cached and updated before every fixed update.
 44707104  public Vector3 Forward { get; private set; }
 105
 106  // The right direction is cached and updated before every fixed update.
 16039107  public Vector3 Right { get; private set; }
 108
 109  // The inverse rotation is cached and updated before every fixed update.
 33351110  public Quaternion InverseRotation { get; private set; }
 111
 7563112  public float MaxForwardAcceleration() {
 7563113    return StaticConfig.AccelerationConfig?.MaxForwardAcceleration ?? 0;
 7563114  }
 115
 7563116  public float MaxNormalAcceleration() {
 7563117    float maxReferenceNormalAcceleration =
 118        (StaticConfig.AccelerationConfig?.MaxReferenceNormalAcceleration ?? 0) * Constants.kGravity;
 7563119    float referenceSpeed = StaticConfig.AccelerationConfig?.ReferenceSpeed ?? 1;
 7563120    return Mathf.Pow(Speed / referenceSpeed, 2) * maxReferenceNormalAcceleration;
 7563121  }
 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
 14115134  public void UpdateTargetModel() {
 21235135    if (HierarchicalAgent == null || HierarchicalAgent.Target == null || Sensor == null) {
 7120136      return;
 137    }
 6995138    if (HierarchicalAgent.Target.IsTerminated) {
 0139      HierarchicalAgent.Target = null;
 0140      return;
 141    }
 142
 143    // Check whether the sensing period has elapsed.
 6995144    float sensingFrequency = AgentConfig?.DynamicConfig?.SensorConfig?.Frequency ?? Mathf.Infinity;
 6995145    float sensingPeriod = 1f / sensingFrequency;
 8656146    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    }
 14115154  }
 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
 6995164  public Transformation GetRelativeTransformation(in Vector3 waypoint) {
 6995165    return GetRelativeTransformation(waypoint, velocity: Vector3.zero, acceleration: Vector3.zero);
 6995166  }
 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.
 14115196  protected virtual void FixedUpdate() {
 14115197    ElapsedTime += Time.fixedDeltaTime;
 198
 14115199    UpdateTargetModel();
 14115200    AlignWithVelocity();
 14115201  }
 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
 16039248  private void UpdateTransformData() {
 16039249    _position = Transform.position;
 16039250    Up = Transform.up;
 16039251    Forward = Transform.forward;
 16039252    Right = Transform.right;
 16039253    InverseRotation = Quaternion.Inverse(Transform.rotation);
 16039254  }
 255
 14115256  private void AlignWithVelocity() {
 257    const float speedThreshold = 0.1f;
 258    const float rotationSpeedDegreesPerSecond = 10000f;
 259
 260    // Only align if the velocity is significant.
 27618261    if (Speed > speedThreshold) {
 262      // Create a rotation with the forward direction along the velocity vector and the up direction
 263      // along world up.
 13503264      Quaternion targetRotation = Quaternion.LookRotation(Velocity, Vector3.up);
 265
 266      // Smoothly rotate towards the target rotation.
 13503267      Transform.rotation = Quaternion.RotateTowards(
 268          Transform.rotation, targetRotation,
 269          maxDegreesDelta: rotationSpeedDegreesPerSecond * Time.fixedDeltaTime);
 13503270    }
 14115271  }
 272
 273  private Transformation GetRelativeTransformation(in Vector3 position, in Vector3 velocity,
 8656274                                                   in Vector3 acceleration) {
 8656275    Vector3 relativePosition = position - Position;
 8656276    Vector3 relativeLocalPosition = InverseRotation * relativePosition;
 8656277    Vector3 relativeVelocity = velocity - Velocity;
 8656278    Vector3 relativeLocalVelocity = InverseRotation * relativeVelocity;
 279
 8656280    float x = relativeLocalPosition.x;
 8656281    float y = relativeLocalPosition.y;
 8656282    float z = relativeLocalPosition.z;
 283
 8656284    float horizontalSqr = x * x + z * z;
 8656285    float horizontal = Mathf.Sqrt(horizontalSqr);
 8656286    float rangeSqr = horizontalSqr + y * y;
 8656287    float range = Mathf.Sqrt(rangeSqr);
 288
 8656289    float azimuth = Mathf.Atan2(x, z);
 8656290    float elevation = Mathf.Atan2(y, horizontal);
 8656291    var positionTransformation = new PositionTransformation {
 292      Cartesian = relativePosition,
 293      Range = range,
 294      Azimuth = azimuth,
 295      Elevation = elevation,
 296    };
 297
 8656298    float rangeRate =
 299        range > _epsilon ? Vector3.Dot(relativeLocalVelocity, relativeLocalPosition) / range : 0f;
 8656300    float azimuthRate = 0f;
 8656301    float elevationRate = 0f;
 17312302    if (horizontal > _epsilon) {
 8656303      azimuthRate = -(x * relativeLocalVelocity.z - z * relativeLocalVelocity.x) / horizontalSqr;
 8656304      elevationRate =
 305          (relativeLocalVelocity.y * horizontal -
 306           y * (x * relativeLocalVelocity.x + z * relativeLocalVelocity.z) / horizontal) /
 307          rangeSqr;
 8656308    } 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    }
 8656315    var velocityTransformation = new VelocityTransformation {
 316      Cartesian = relativeVelocity,
 317      Range = rangeRate,
 318      Azimuth = azimuthRate,
 319      Elevation = elevationRate,
 320    };
 321
 8656322    var accelerationTransformation = new AccelerationTransformation {
 323      Cartesian = acceleration,
 324    };
 8656325    return new Transformation {
 326      Position = positionTransformation,
 327      Velocity = velocityTransformation,
 328      Acceleration = accelerationTransformation,
 329    };
 8656330  }
 331}