< Summary

Class:AgentBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Agents/AgentBase.cs
Covered lines:96
Uncovered lines:80
Coverable lines:176
Total lines:337
Line coverage:54.5% (96 of 176)
Covered branches:0
Total branches:0
Covered methods:39
Total methods:63
Method coverage:61.9% (39 of 63)

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%
CheckGroundCollision(...)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 System;
 2using UnityEngine;
 3
 4// Base implementation of an agent.
 5//
 6// See the agent interface for property and method documentation.
 7public class AgentBase : MonoBehaviour, IAgent, ICommsEndpoint {
 8  public event Action<IAgent> OnTerminated;
 9
 10  private const float _epsilon = 1e-12f;
 11
 12  // Rigid body component.
 13  protected Rigidbody _rigidbody;
 14
 15  // The position field is cached and is updated before every fixed update.
 16  [SerializeField]
 17  private Vector3 _position;
 18
 19  // The acceleration field is not part of the rigid body component, so it is tracked separately.
 20  // The acceleration is applied as a force during each frame update.
 21  [SerializeField]
 22  private Vector3 _acceleration;
 23
 24  // The acceleration input is calculated by the controller and provided to the movement behavior.
 25  [SerializeField]
 26  private Vector3 _accelerationInput;
 27
 28  // The agent's position within the hierarchical strategy is given by the hierarchical agent.
 29  [SerializeReference]
 30  private HierarchicalAgent _hierarchicalAgent;
 31
 32  // Static configuration of the agent, including agent type, unit cost, acceleration configuration,
 33  // aerodynamics parameters, power table, and visualization configuration.
 34  private Configs.StaticConfig _staticConfig;
 35
 36  // Agent configuration, including initial state, attack behavior configuration (for threats),
 37  // dynamic configuration, and sub-agent configuration (for interceptors).
 38  private Configs.AgentConfig _agentConfig;
 39
 40  // Last sensing time.
 41  [SerializeField]
 17342  private float _lastSensingTime = Mathf.NegativeInfinity;
 43
 44  public HierarchicalAgent HierarchicalAgent {
 045    get => _hierarchicalAgent;
 046    set => _hierarchicalAgent = value;
 47  }
 48  public Configs.StaticConfig StaticConfig {
 40549    get => _staticConfig;
 6450    set {
 6451      _staticConfig = value;
 6452      _rigidbody.mass = StaticConfig.BodyConfig?.Mass ?? 1;
 6453    }
 54  }
 55  public Configs.AgentConfig AgentConfig {
 1356    get => _agentConfig;
 757    set {
 758      _agentConfig = value;
 759      UpdateAgentConfig();
 760    }
 61  }
 62
 063  public IMovement Movement { get; set; }
 064  public IController Controller { get; set; }
 1665  public ISensor Sensor { get; set; }
 7066  public IAgent TargetModel { get; set; }
 67
 68  public Vector3 Position {
 27469    get => _position;
 9070    set {
 9071      Transform.position = value;
 9072      _position = value;
 9073    }
 74  }
 75  public Vector3 Velocity {
 34076    get => _rigidbody.linearVelocity;
 12277    set => _rigidbody.linearVelocity = value;
 78  }
 11779  public float Speed => Velocity.magnitude;
 80  public Vector3 Acceleration {
 2581    get => _acceleration;
 1082    set => _acceleration = value;
 83  }
 84  public Vector3 AccelerationInput {
 085    get => _accelerationInput;
 086    set => _accelerationInput = value;
 87  }
 88
 89  // If true, the agent is able to pursue targets.
 090  public virtual bool IsPursuer => true;
 91
 92  // Elapsed time since the creation of the agent.
 18193  public float ElapsedTime { get; private set; } = 0f;
 94
 95  // If true, the agent is terminated.
 17396  public bool IsTerminated { get; private set; } = false;
 97
 98  // The agent transform is cached.
 118399  public Transform Transform { get; private set; }
 100
 101  // The up direction is cached and updated before every fixed update.
 173102  public Vector3 Up { get; private set; }
 103
 104  // The forward direction is cached and updated before every fixed update.
 244105  public Vector3 Forward { get; private set; }
 106
 107  // The right direction is cached and updated before every fixed update.
 174108  public Vector3 Right { get; private set; }
 109
 110  // The inverse rotation is cached and updated before every fixed update.
 291111  public Quaternion InverseRotation { get; private set; }
 112
 113  // Communication node managed by the communication manager.
 0114  public CommsNode CommsNode { get; set; }
 115
 26116  public float MaxForwardAcceleration() {
 26117    return StaticConfig.AccelerationConfig?.MaxForwardAcceleration ?? 0;
 26118  }
 119
 65120  public float MaxNormalAcceleration() {
 65121    float maxReferenceNormalAcceleration =
 122        (StaticConfig.AccelerationConfig?.MaxReferenceNormalAcceleration ??
 123         float.PositiveInfinity) *
 124        Constants.kGravity;
 65125    float referenceSpeed = StaticConfig.AccelerationConfig?.ReferenceSpeed ?? 1;
 65126    return Mathf.Pow(Speed / referenceSpeed, 2) * maxReferenceNormalAcceleration;
 65127  }
 128
 0129  public void CreateTargetModel(IHierarchical target) {
 0130    TargetModel = SimManager.Instance.CreateDummyAgent(target.Position, target.Velocity);
 0131  }
 132
 0133  public void DestroyTargetModel() {
 0134    if (TargetModel != null) {
 0135      SimManager.Instance.DestroyDummyAgent(TargetModel);
 0136      TargetModel = null;
 0137    }
 0138  }
 139
 0140  public void UpdateTargetModel() {
 0141    if (HierarchicalAgent == null || HierarchicalAgent.Target == null || Sensor == null) {
 0142      return;
 143    }
 0144    if (HierarchicalAgent.Target.IsTerminated) {
 0145      HierarchicalAgent.Target = null;
 0146      return;
 147    }
 148
 149    // Check whether the sensing period has elapsed.
 0150    float sensingFrequency = AgentConfig?.DynamicConfig?.SensorConfig?.Frequency ?? Mathf.Infinity;
 0151    float sensingPeriod = 1f / sensingFrequency;
 0152    if (ElapsedTime - _lastSensingTime >= sensingPeriod) {
 153      // Sense the target.
 0154      SensorOutput sensorOutput = Sensor.Sense(HierarchicalAgent.Target);
 0155      TargetModel.Position = Position + sensorOutput.Position.Cartesian;
 0156      TargetModel.Velocity = Velocity + sensorOutput.Velocity.Cartesian;
 0157      TargetModel.Acceleration = Acceleration + sensorOutput.Acceleration.Cartesian;
 0158      _lastSensingTime = ElapsedTime;
 0159    }
 0160  }
 161
 25162  public Transformation GetRelativeTransformation(IAgent target) {
 25163    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 25164  }
 165
 32166  public Transformation GetRelativeTransformation(IHierarchical target) {
 32167    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 32168  }
 169
 2170  public Transformation GetRelativeTransformation(in Vector3 waypoint) {
 2171    return GetRelativeTransformation(waypoint, velocity: Vector3.zero, acceleration: Vector3.zero);
 2172  }
 173
 0174  public void Terminate() {
 0175    if (HierarchicalAgent != null) {
 0176      HierarchicalAgent.Target = null;
 0177    }
 0178    if (Movement is MissileMovement movement) {
 0179      movement.FlightPhase = Simulation.FlightPhase.Terminated;
 0180    }
 0181    IsTerminated = true;
 0182    OnTerminated?.Invoke(this);
 0183    Destroy(gameObject);
 0184  }
 185
 186  // Awake is called before Start and right after a prefab is instantiated.
 173187  protected virtual void Awake() {
 173188    Transform = transform;
 173189    _rigidbody = GetComponent<Rigidbody>();
 190
 173191    UpdateTransformData();
 173192    if (EarlyFixedUpdateManager.Instance != null) {
 0193      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate += UpdateTransformData;
 0194    }
 173195  }
 196
 197  // Start is called before the first frame update.
 0198  protected virtual void Start() {}
 199
 200  // FixedUpdate is called multiple times per frame. All physics calculations and updates occur
 201  // immediately after FixedUpdate, and all movement values are multiplied by Time.deltaTime.
 0202  protected virtual void FixedUpdate() {
 0203    ElapsedTime += Time.fixedDeltaTime;
 204
 0205    UpdateTargetModel();
 0206    AlignWithVelocity();
 0207  }
 208
 209  // Update is called every frame.
 0210  protected virtual void Update() {}
 211
 212  // LateUpdate is called every frame after all Update functions have been called.
 0213  protected virtual void LateUpdate() {}
 214
 215  // OnDestroy is called when the object is being destroyed.
 0216  protected virtual void OnDestroy() {
 0217    if (EarlyFixedUpdateManager.Instance != null) {
 0218      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate -= UpdateTransformData;
 0219    }
 0220  }
 221
 222  // UpdateAgentConfig is called whenever the agent configuration is changed.
 7223  protected virtual void UpdateAgentConfig() {
 224    // Set the sensor.
 7225    switch (AgentConfig.DynamicConfig?.SensorConfig?.Type) {
 7226      case Simulation.SensorType.Ideal: {
 7227        Sensor = new IdealSensor(this);
 7228        break;
 229      }
 0230      default: {
 0231        Debug.LogWarning($"Sensor type {AgentConfig.DynamicConfig?.SensorConfig?.Type} not found.");
 0232        break;
 233      }
 234    }
 7235  }
 236
 0237  protected virtual void OnDrawGizmos() {
 0238    if (Application.isPlaying) {
 0239      Gizmos.color = Color.green;
 0240      Gizmos.DrawRay(Position, _accelerationInput);
 0241    }
 0242  }
 243
 0244  protected bool CheckGroundCollision(Collider other) {
 245    // Check if the agent hit the ground with a negative vertical speed.
 0246    return other.gameObject.name == "Floor" && Vector3.Dot(Velocity, Vector3.up) < 0;
 0247  }
 248
 0249  protected bool ShouldIgnoreCollision(IAgent otherAgent) {
 250    // Dummy agents are virtual targets and should not trigger collisions.
 0251    return otherAgent == null || otherAgent is DummyAgent || otherAgent.IsTerminated;
 0252  }
 253
 173254  private void UpdateTransformData() {
 173255    _position = Transform.position;
 173256    Up = Transform.up;
 173257    Forward = Transform.forward;
 173258    Right = Transform.right;
 173259    InverseRotation = Quaternion.Inverse(Transform.rotation);
 173260  }
 261
 0262  private void AlignWithVelocity() {
 263    const float speedThreshold = 0.1f;
 264    const float rotationSpeedDegreesPerSecond = 10000f;
 265
 266    // Only align if the velocity is significant.
 0267    if (Speed > speedThreshold) {
 268      // Create a rotation with the forward direction along the velocity vector and the up direction
 269      // along world up.
 0270      Quaternion targetRotation = Quaternion.LookRotation(Velocity, Vector3.up);
 271
 272      // Smoothly rotate towards the target rotation.
 0273      Transform.rotation = Quaternion.RotateTowards(
 274          Transform.rotation, targetRotation,
 275          maxDegreesDelta: rotationSpeedDegreesPerSecond * Time.fixedDeltaTime);
 0276    }
 0277  }
 278
 279  private Transformation GetRelativeTransformation(in Vector3 position, in Vector3 velocity,
 59280                                                   in Vector3 acceleration) {
 59281    Vector3 relativePosition = position - Position;
 59282    Vector3 relativeLocalPosition = InverseRotation * relativePosition;
 59283    Vector3 relativeVelocity = velocity - Velocity;
 59284    Vector3 relativeLocalVelocity = InverseRotation * relativeVelocity;
 285
 59286    float x = relativeLocalPosition.x;
 59287    float y = relativeLocalPosition.y;
 59288    float z = relativeLocalPosition.z;
 289
 59290    float horizontalSqr = x * x + z * z;
 59291    float horizontal = Mathf.Sqrt(horizontalSqr);
 59292    float rangeSqr = horizontalSqr + y * y;
 59293    float range = Mathf.Sqrt(rangeSqr);
 294
 59295    float azimuth = Mathf.Atan2(x, z);
 59296    float elevation = Mathf.Atan2(y, horizontal);
 59297    var positionTransformation = new PositionTransformation {
 298      Cartesian = relativePosition,
 299      Range = range,
 300      Azimuth = azimuth,
 301      Elevation = elevation,
 302    };
 303
 59304    float rangeRate =
 305        range > _epsilon ? Vector3.Dot(relativeLocalVelocity, relativeLocalPosition) / range : 0f;
 59306    float azimuthRate = 0f;
 59307    float elevationRate = 0f;
 105308    if (horizontal > _epsilon) {
 46309      azimuthRate = -(x * relativeLocalVelocity.z - z * relativeLocalVelocity.x) / horizontalSqr;
 46310      elevationRate =
 311          (relativeLocalVelocity.y * horizontal -
 312           y * (x * relativeLocalVelocity.x + z * relativeLocalVelocity.z) / horizontal) /
 313          rangeSqr;
 59314    } else {
 315      // The other agent is exactly above or below.
 13316      azimuthRate = 0f;
 13317      float horizontalSpeed = Mathf.Sqrt(relativeLocalVelocity.x * relativeLocalVelocity.x +
 318                                         relativeLocalVelocity.z * relativeLocalVelocity.z);
 13319      elevationRate = -horizontalSpeed / (Mathf.Abs(y) > _epsilon ? y : Mathf.Sign(y) * _epsilon);
 13320    }
 59321    var velocityTransformation = new VelocityTransformation {
 322      Cartesian = relativeVelocity,
 323      Range = rangeRate,
 324      Azimuth = azimuthRate,
 325      Elevation = elevationRate,
 326    };
 327
 59328    var accelerationTransformation = new AccelerationTransformation {
 329      Cartesian = acceleration,
 330    };
 59331    return new Transformation {
 332      Position = positionTransformation,
 333      Velocity = velocityTransformation,
 334      Acceleration = accelerationTransformation,
 335    };
 59336  }
 337}