< 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:333
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%
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 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]
 17141  private float _lastSensingTime = Mathf.NegativeInfinity;
 42
 43  public HierarchicalAgent HierarchicalAgent {
 044    get => _hierarchicalAgent;
 045    set => _hierarchicalAgent = value;
 46  }
 47  public Configs.StaticConfig StaticConfig {
 40548    get => _staticConfig;
 6449    set {
 6450      _staticConfig = value;
 6451      _rigidbody.mass = StaticConfig.BodyConfig?.Mass ?? 1;
 6452    }
 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 {
 27268    get => _position;
 8969    set {
 8970      Transform.position = value;
 8971      _position = value;
 8972    }
 73  }
 74  public Vector3 Velocity {
 33875    get => _rigidbody.linearVelocity;
 12076    set => _rigidbody.linearVelocity = value;
 77  }
 11778  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.
 17992  public float ElapsedTime { get; private set; } = 0f;
 93
 94  // If true, the agent is terminated.
 17195  public bool IsTerminated { get; private set; } = false;
 96
 97  // The agent transform is cached.
 116998  public Transform Transform { get; private set; }
 99
 100  // The up direction is cached and updated before every fixed update.
 171101  public Vector3 Up { get; private set; }
 102
 103  // The forward direction is cached and updated before every fixed update.
 242104  public Vector3 Forward { get; private set; }
 105
 106  // The right direction is cached and updated before every fixed update.
 172107  public Vector3 Right { get; private set; }
 108
 109  // The inverse rotation is cached and updated before every fixed update.
 287110  public Quaternion InverseRotation { get; private set; }
 111
 26112  public float MaxForwardAcceleration() {
 26113    return StaticConfig.AccelerationConfig?.MaxForwardAcceleration ?? 0;
 26114  }
 115
 65116  public float MaxNormalAcceleration() {
 65117    float maxReferenceNormalAcceleration =
 118        (StaticConfig.AccelerationConfig?.MaxReferenceNormalAcceleration ??
 119         float.PositiveInfinity) *
 120        Constants.kGravity;
 65121    float referenceSpeed = StaticConfig.AccelerationConfig?.ReferenceSpeed ?? 1;
 65122    return Mathf.Pow(Speed / referenceSpeed, 2) * maxReferenceNormalAcceleration;
 65123  }
 124
 0125  public void CreateTargetModel(IHierarchical target) {
 0126    TargetModel = SimManager.Instance.CreateDummyAgent(target.Position, target.Velocity);
 0127  }
 128
 0129  public void DestroyTargetModel() {
 0130    if (TargetModel != null) {
 0131      SimManager.Instance.DestroyDummyAgent(TargetModel);
 0132      TargetModel = null;
 0133    }
 0134  }
 135
 0136  public void UpdateTargetModel() {
 0137    if (HierarchicalAgent == null || HierarchicalAgent.Target == null || Sensor == null) {
 0138      return;
 139    }
 0140    if (HierarchicalAgent.Target.IsTerminated) {
 0141      HierarchicalAgent.Target = null;
 0142      return;
 143    }
 144
 145    // Check whether the sensing period has elapsed.
 0146    float sensingFrequency = AgentConfig?.DynamicConfig?.SensorConfig?.Frequency ?? Mathf.Infinity;
 0147    float sensingPeriod = 1f / sensingFrequency;
 0148    if (ElapsedTime - _lastSensingTime >= sensingPeriod) {
 149      // Sense the target.
 0150      SensorOutput sensorOutput = Sensor.Sense(HierarchicalAgent.Target);
 0151      TargetModel.Position = Position + sensorOutput.Position.Cartesian;
 0152      TargetModel.Velocity = Velocity + sensorOutput.Velocity.Cartesian;
 0153      TargetModel.Acceleration = Acceleration + sensorOutput.Acceleration.Cartesian;
 0154      _lastSensingTime = ElapsedTime;
 0155    }
 0156  }
 157
 24158  public Transformation GetRelativeTransformation(IAgent target) {
 24159    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 24160  }
 161
 32162  public Transformation GetRelativeTransformation(IHierarchical target) {
 32163    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 32164  }
 165
 2166  public Transformation GetRelativeTransformation(in Vector3 waypoint) {
 2167    return GetRelativeTransformation(waypoint, velocity: Vector3.zero, acceleration: Vector3.zero);
 2168  }
 169
 0170  public void Terminate() {
 0171    if (HierarchicalAgent != null) {
 0172      HierarchicalAgent.Target = null;
 0173    }
 0174    if (Movement is MissileMovement movement) {
 0175      movement.FlightPhase = Simulation.FlightPhase.Terminated;
 0176    }
 0177    IsTerminated = true;
 0178    OnTerminated?.Invoke(this);
 0179    Destroy(gameObject);
 0180  }
 181
 182  // Awake is called before Start and right after a prefab is instantiated.
 171183  protected virtual void Awake() {
 171184    Transform = transform;
 171185    _rigidbody = GetComponent<Rigidbody>();
 186
 171187    UpdateTransformData();
 171188    if (EarlyFixedUpdateManager.Instance != null) {
 0189      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate += UpdateTransformData;
 0190    }
 171191  }
 192
 193  // Start is called before the first frame update.
 0194  protected virtual void Start() {}
 195
 196  // FixedUpdate is called multiple times per frame. All physics calculations and updates occur
 197  // immediately after FixedUpdate, and all movement values are multiplied by Time.deltaTime.
 0198  protected virtual void FixedUpdate() {
 0199    ElapsedTime += Time.fixedDeltaTime;
 200
 0201    UpdateTargetModel();
 0202    AlignWithVelocity();
 0203  }
 204
 205  // Update is called every frame.
 0206  protected virtual void Update() {}
 207
 208  // LateUpdate is called every frame after all Update functions have been called.
 0209  protected virtual void LateUpdate() {}
 210
 211  // OnDestroy is called when the object is being destroyed.
 0212  protected virtual void OnDestroy() {
 0213    if (EarlyFixedUpdateManager.Instance != null) {
 0214      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate -= UpdateTransformData;
 0215    }
 0216  }
 217
 218  // UpdateAgentConfig is called whenever the agent configuration is changed.
 7219  protected virtual void UpdateAgentConfig() {
 220    // Set the sensor.
 7221    switch (AgentConfig.DynamicConfig?.SensorConfig?.Type) {
 7222      case Simulation.SensorType.Ideal: {
 7223        Sensor = new IdealSensor(this);
 7224        break;
 225      }
 0226      default: {
 0227        Debug.LogWarning($"Sensor type {AgentConfig.DynamicConfig?.SensorConfig?.Type} not found.");
 0228        break;
 229      }
 230    }
 7231  }
 232
 0233  protected virtual void OnDrawGizmos() {
 0234    if (Application.isPlaying) {
 0235      Gizmos.color = Color.green;
 0236      Gizmos.DrawRay(Position, _accelerationInput);
 0237    }
 0238  }
 239
 0240  protected bool CheckGroundCollision(Collider other) {
 241    // Check if the agent hit the ground with a negative vertical speed.
 0242    return other.gameObject.name == "Floor" && Vector3.Dot(Velocity, Vector3.up) < 0;
 0243  }
 244
 0245  protected bool ShouldIgnoreCollision(IAgent otherAgent) {
 246    // Dummy agents are virtual targets and should not trigger collisions.
 0247    return otherAgent == null || otherAgent is DummyAgent || otherAgent.IsTerminated;
 0248  }
 249
 171250  private void UpdateTransformData() {
 171251    _position = Transform.position;
 171252    Up = Transform.up;
 171253    Forward = Transform.forward;
 171254    Right = Transform.right;
 171255    InverseRotation = Quaternion.Inverse(Transform.rotation);
 171256  }
 257
 0258  private void AlignWithVelocity() {
 259    const float speedThreshold = 0.1f;
 260    const float rotationSpeedDegreesPerSecond = 10000f;
 261
 262    // Only align if the velocity is significant.
 0263    if (Speed > speedThreshold) {
 264      // Create a rotation with the forward direction along the velocity vector and the up direction
 265      // along world up.
 0266      Quaternion targetRotation = Quaternion.LookRotation(Velocity, Vector3.up);
 267
 268      // Smoothly rotate towards the target rotation.
 0269      Transform.rotation = Quaternion.RotateTowards(
 270          Transform.rotation, targetRotation,
 271          maxDegreesDelta: rotationSpeedDegreesPerSecond * Time.fixedDeltaTime);
 0272    }
 0273  }
 274
 275  private Transformation GetRelativeTransformation(in Vector3 position, in Vector3 velocity,
 58276                                                   in Vector3 acceleration) {
 58277    Vector3 relativePosition = position - Position;
 58278    Vector3 relativeLocalPosition = InverseRotation * relativePosition;
 58279    Vector3 relativeVelocity = velocity - Velocity;
 58280    Vector3 relativeLocalVelocity = InverseRotation * relativeVelocity;
 281
 58282    float x = relativeLocalPosition.x;
 58283    float y = relativeLocalPosition.y;
 58284    float z = relativeLocalPosition.z;
 285
 58286    float horizontalSqr = x * x + z * z;
 58287    float horizontal = Mathf.Sqrt(horizontalSqr);
 58288    float rangeSqr = horizontalSqr + y * y;
 58289    float range = Mathf.Sqrt(rangeSqr);
 290
 58291    float azimuth = Mathf.Atan2(x, z);
 58292    float elevation = Mathf.Atan2(y, horizontal);
 58293    var positionTransformation = new PositionTransformation {
 294      Cartesian = relativePosition,
 295      Range = range,
 296      Azimuth = azimuth,
 297      Elevation = elevation,
 298    };
 299
 58300    float rangeRate =
 301        range > _epsilon ? Vector3.Dot(relativeLocalVelocity, relativeLocalPosition) / range : 0f;
 58302    float azimuthRate = 0f;
 58303    float elevationRate = 0f;
 103304    if (horizontal > _epsilon) {
 45305      azimuthRate = -(x * relativeLocalVelocity.z - z * relativeLocalVelocity.x) / horizontalSqr;
 45306      elevationRate =
 307          (relativeLocalVelocity.y * horizontal -
 308           y * (x * relativeLocalVelocity.x + z * relativeLocalVelocity.z) / horizontal) /
 309          rangeSqr;
 58310    } else {
 311      // The other agent is exactly above or below.
 13312      azimuthRate = 0f;
 13313      float horizontalSpeed = Mathf.Sqrt(relativeLocalVelocity.x * relativeLocalVelocity.x +
 314                                         relativeLocalVelocity.z * relativeLocalVelocity.z);
 13315      elevationRate = -horizontalSpeed / (Mathf.Abs(y) > _epsilon ? y : Mathf.Sign(y) * _epsilon);
 13316    }
 58317    var velocityTransformation = new VelocityTransformation {
 318      Cartesian = relativeVelocity,
 319      Range = rangeRate,
 320      Azimuth = azimuthRate,
 321      Elevation = elevationRate,
 322    };
 323
 58324    var accelerationTransformation = new AccelerationTransformation {
 325      Cartesian = acceleration,
 326    };
 58327    return new Transformation {
 328      Position = positionTransformation,
 329      Velocity = velocityTransformation,
 330      Acceleration = accelerationTransformation,
 331    };
 58332  }
 333}