< Summary

Class:AgentBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Agents/AgentBase.cs
Covered lines:140
Uncovered lines:36
Coverable lines:176
Total lines:337
Line coverage:79.5% (140 of 176)
Covered branches:0
Total branches:0
Covered methods:58
Total methods:63
Method coverage:92% (58 of 63)

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%
CheckGroundCollision(...)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 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]
 194142  private float _lastSensingTime = Mathf.NegativeInfinity;
 43
 44  public HierarchicalAgent HierarchicalAgent {
 4459745    get => _hierarchicalAgent;
 98146    set => _hierarchicalAgent = value;
 47  }
 48  public Configs.StaticConfig StaticConfig {
 2406949    get => _staticConfig;
 98150    set {
 98151      _staticConfig = value;
 98152      _rigidbody.mass = StaticConfig.BodyConfig?.Mass ?? 1;
 98153    }
 54  }
 55  public Configs.AgentConfig AgentConfig {
 856056    get => _agentConfig;
 98157    set {
 98158      _agentConfig = value;
 98159      UpdateAgentConfig();
 98160    }
 61  }
 62
 767863  public IMovement Movement { get; set; }
 767864  public IController Controller { get; set; }
 913465  public ISensor Sensor { get; set; }
 1255166  public IAgent TargetModel { get; set; }
 67
 68  public Vector3 Position {
 2870569    get => _position;
 163370    set {
 163371      Transform.position = value;
 163372      _position = value;
 163373    }
 74  }
 75  public Vector3 Velocity {
 6441676    get => _rigidbody.linearVelocity;
 356977    set => _rigidbody.linearVelocity = value;
 78  }
 2677979  public float Speed => Velocity.magnitude;
 80  public Vector3 Acceleration {
 1648381    get => _acceleration;
 833082    set => _acceleration = value;
 83  }
 84  public Vector3 AccelerationInput {
 1973785    get => _accelerationInput;
 1321786    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.
 3652893  public float ElapsedTime { get; private set; } = 0f;
 94
 95  // If true, the agent is terminated.
 848196  public bool IsTerminated { get; private set; } = false;
 97
 98  // The agent transform is cached.
 10444299  public Transform Transform { get; private set; }
 100
 101  // The up direction is cached and updated before every fixed update.
 15153102  public Vector3 Up { get; private set; }
 103
 104  // The forward direction is cached and updated before every fixed update.
 41920105  public Vector3 Forward { get; private set; }
 106
 107  // The right direction is cached and updated before every fixed update.
 15153108  public Vector3 Right { get; private set; }
 109
 110  // The inverse rotation is cached and updated before every fixed update.
 31459111  public Quaternion InverseRotation { get; private set; }
 112
 113  // Communication node managed by the communication manager.
 106114  public CommsNode CommsNode { get; set; }
 115
 7042116  public float MaxForwardAcceleration() {
 7042117    return StaticConfig.AccelerationConfig?.MaxForwardAcceleration ?? 0;
 7042118  }
 119
 7042120  public float MaxNormalAcceleration() {
 7042121    float maxReferenceNormalAcceleration =
 122        (StaticConfig.AccelerationConfig?.MaxReferenceNormalAcceleration ??
 123         float.PositiveInfinity) *
 124        Constants.kGravity;
 7042125    float referenceSpeed = StaticConfig.AccelerationConfig?.ReferenceSpeed ?? 1;
 7042126    return Mathf.Pow(Speed / referenceSpeed, 2) * maxReferenceNormalAcceleration;
 7042127  }
 128
 955129  public void CreateTargetModel(IHierarchical target) {
 955130    TargetModel = SimManager.Instance.CreateDummyAgent(target.Position, target.Velocity);
 955131  }
 132
 0133  public void DestroyTargetModel() {
 0134    if (TargetModel != null) {
 0135      SimManager.Instance.DestroyDummyAgent(TargetModel);
 0136      TargetModel = null;
 0137    }
 0138  }
 139
 13217140  public void UpdateTargetModel() {
 19914141    if (HierarchicalAgent == null || HierarchicalAgent.Target == null || Sensor == null) {
 6697142      return;
 143    }
 6520144    if (HierarchicalAgent.Target.IsTerminated) {
 0145      HierarchicalAgent.Target = null;
 0146      return;
 147    }
 148
 149    // Check whether the sensing period has elapsed.
 6520150    float sensingFrequency = AgentConfig?.DynamicConfig?.SensorConfig?.Frequency ?? Mathf.Infinity;
 6520151    float sensingPeriod = 1f / sensingFrequency;
 8153152    if (ElapsedTime - _lastSensingTime >= sensingPeriod) {
 153      // Sense the target.
 1633154      SensorOutput sensorOutput = Sensor.Sense(HierarchicalAgent.Target);
 1633155      TargetModel.Position = Position + sensorOutput.Position.Cartesian;
 1633156      TargetModel.Velocity = Velocity + sensorOutput.Velocity.Cartesian;
 1633157      TargetModel.Acceleration = Acceleration + sensorOutput.Acceleration.Cartesian;
 1633158      _lastSensingTime = ElapsedTime;
 1633159    }
 13217160  }
 161
 0162  public Transformation GetRelativeTransformation(IAgent target) {
 0163    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 0164  }
 165
 1633166  public Transformation GetRelativeTransformation(IHierarchical target) {
 1633167    return GetRelativeTransformation(target.Position, target.Velocity, target.Acceleration);
 1633168  }
 169
 6520170  public Transformation GetRelativeTransformation(in Vector3 waypoint) {
 6520171    return GetRelativeTransformation(waypoint, velocity: Vector3.zero, acceleration: Vector3.zero);
 6520172  }
 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.
 1936187  protected virtual void Awake() {
 1936188    Transform = transform;
 1936189    _rigidbody = GetComponent<Rigidbody>();
 190
 1936191    UpdateTransformData();
 3872192    if (EarlyFixedUpdateManager.Instance != null) {
 1936193      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate += UpdateTransformData;
 1936194    }
 1936195  }
 196
 197  // Start is called before the first frame update.
 3872198  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.
 13217202  protected virtual void FixedUpdate() {
 13217203    ElapsedTime += Time.fixedDeltaTime;
 204
 13217205    UpdateTargetModel();
 13217206    AlignWithVelocity();
 13217207  }
 208
 209  // Update is called every frame.
 10358210  protected virtual void Update() {}
 211
 212  // LateUpdate is called every frame after all Update functions have been called.
 8448213  protected virtual void LateUpdate() {}
 214
 215  // OnDestroy is called when the object is being destroyed.
 1920216  protected virtual void OnDestroy() {
 3840217    if (EarlyFixedUpdateManager.Instance != null) {
 1920218      EarlyFixedUpdateManager.Instance.OnEarlyFixedUpdate -= UpdateTransformData;
 1920219    }
 1920220  }
 221
 222  // UpdateAgentConfig is called whenever the agent configuration is changed.
 981223  protected virtual void UpdateAgentConfig() {
 224    // Set the sensor.
 981225    switch (AgentConfig.DynamicConfig?.SensorConfig?.Type) {
 981226      case Simulation.SensorType.Ideal: {
 981227        Sensor = new IdealSensor(this);
 981228        break;
 229      }
 0230      default: {
 0231        Debug.LogWarning($"Sensor type {AgentConfig.DynamicConfig?.SensorConfig?.Type} not found.");
 0232        break;
 233      }
 234    }
 981235  }
 236
 0237  protected virtual void OnDrawGizmos() {
 0238    if (Application.isPlaying) {
 0239      Gizmos.color = Color.green;
 0240      Gizmos.DrawRay(Position, _accelerationInput);
 0241    }
 0242  }
 243
 1803244  protected bool CheckGroundCollision(Collider other) {
 245    // Check if the agent hit the ground with a negative vertical speed.
 1803246    return other.gameObject.name == "Floor" && Vector3.Dot(Velocity, Vector3.up) < 0;
 1803247  }
 248
 1803249  protected bool ShouldIgnoreCollision(IAgent otherAgent) {
 250    // Dummy agents are virtual targets and should not trigger collisions.
 1803251    return otherAgent == null || otherAgent is DummyAgent || otherAgent.IsTerminated;
 1803252  }
 253
 15153254  private void UpdateTransformData() {
 15153255    _position = Transform.position;
 15153256    Up = Transform.up;
 15153257    Forward = Transform.forward;
 15153258    Right = Transform.right;
 15153259    InverseRotation = Quaternion.Inverse(Transform.rotation);
 15153260  }
 261
 13217262  private void AlignWithVelocity() {
 263    const float speedThreshold = 0.1f;
 264    const float rotationSpeedDegreesPerSecond = 10000f;
 265
 266    // Only align if the velocity is significant.
 25771267    if (Speed > speedThreshold) {
 268      // Create a rotation with the forward direction along the velocity vector and the up direction
 269      // along world up.
 12554270      Quaternion targetRotation = Quaternion.LookRotation(Velocity, Vector3.up);
 271
 272      // Smoothly rotate towards the target rotation.
 12554273      Transform.rotation = Quaternion.RotateTowards(
 274          Transform.rotation, targetRotation,
 275          maxDegreesDelta: rotationSpeedDegreesPerSecond * Time.fixedDeltaTime);
 12554276    }
 13217277  }
 278
 279  private Transformation GetRelativeTransformation(in Vector3 position, in Vector3 velocity,
 8153280                                                   in Vector3 acceleration) {
 8153281    Vector3 relativePosition = position - Position;
 8153282    Vector3 relativeLocalPosition = InverseRotation * relativePosition;
 8153283    Vector3 relativeVelocity = velocity - Velocity;
 8153284    Vector3 relativeLocalVelocity = InverseRotation * relativeVelocity;
 285
 8153286    float x = relativeLocalPosition.x;
 8153287    float y = relativeLocalPosition.y;
 8153288    float z = relativeLocalPosition.z;
 289
 8153290    float horizontalSqr = x * x + z * z;
 8153291    float horizontal = Mathf.Sqrt(horizontalSqr);
 8153292    float rangeSqr = horizontalSqr + y * y;
 8153293    float range = Mathf.Sqrt(rangeSqr);
 294
 8153295    float azimuth = Mathf.Atan2(x, z);
 8153296    float elevation = Mathf.Atan2(y, horizontal);
 8153297    var positionTransformation = new PositionTransformation {
 298      Cartesian = relativePosition,
 299      Range = range,
 300      Azimuth = azimuth,
 301      Elevation = elevation,
 302    };
 303
 8153304    float rangeRate =
 305        range > _epsilon ? Vector3.Dot(relativeLocalVelocity, relativeLocalPosition) / range : 0f;
 8153306    float azimuthRate = 0f;
 8153307    float elevationRate = 0f;
 16306308    if (horizontal > _epsilon) {
 8153309      azimuthRate = -(x * relativeLocalVelocity.z - z * relativeLocalVelocity.x) / horizontalSqr;
 8153310      elevationRate =
 311          (relativeLocalVelocity.y * horizontal -
 312           y * (x * relativeLocalVelocity.x + z * relativeLocalVelocity.z) / horizontal) /
 313          rangeSqr;
 8153314    } else {
 315      // The other agent is exactly above or below.
 0316      azimuthRate = 0f;
 0317      float horizontalSpeed = Mathf.Sqrt(relativeLocalVelocity.x * relativeLocalVelocity.x +
 318                                         relativeLocalVelocity.z * relativeLocalVelocity.z);
 0319      elevationRate = -horizontalSpeed / (Mathf.Abs(y) > _epsilon ? y : Mathf.Sign(y) * _epsilon);
 0320    }
 8153321    var velocityTransformation = new VelocityTransformation {
 322      Cartesian = relativeVelocity,
 323      Range = rangeRate,
 324      Azimuth = azimuthRate,
 325      Elevation = elevationRate,
 326    };
 327
 8153328    var accelerationTransformation = new AccelerationTransformation {
 329      Cartesian = acceleration,
 330    };
 8153331    return new Transformation {
 332      Position = positionTransformation,
 333      Velocity = velocityTransformation,
 334      Acceleration = accelerationTransformation,
 335    };
 8153336  }
 337}