< Summary

Class:CameraController
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/UI/CameraController.cs
Covered lines:0
Uncovered lines:347
Coverable lines:347
Total lines:643
Line coverage:0% (0 of 347)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:36
Method coverage:0% (0 of 36)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
CameraController()0%2100%
SetCameraRotation(...)0%2100%
SetCameraSpeed(...)0%2100%
GetCameraSpeedMax()0%2100%
GetCameraSpeedNormal()0%2100%
IsAutoRotate()0%2100%
SetAutoRotate(...)0%42600%
ClampAngle(...)0%12300%
Awake()0%6200%
Start()0%2100%
SnapToSwarm(...)0%6200%
SnapToNextInterceptorSwarm(...)0%42600%
SnapToNextThreatSwarm(...)0%42600%
SnapToCenterAllAgents(...)0%6200%
SetCameraMode(...)0%12300%
StartCentroidUpdateCoroutine()0%6200%
FollowNextInterceptorSwarm()0%2100%
FollowNextThreatSwarm()0%2100%
FollowCenterAllAgents()0%2100%
UpdateCentroidCoroutine()0%12300%
UpdateTargetCentroid()0%1101000%
AutoPlayRoutine()0%1561200%
SetCameraTargetPosition(...)0%2100%
ResetCameraTarget()0%6200%
EnableTargetRenderer(...)0%2100%
EnableFloorGridRenderer(...)0%2100%
OrbitCamera(...)0%6200%
RotateCamera(...)0%2100%
UpdateCamPosition(...)0%6200%
ZoomCamera(...)0%2100%
UpdateTargetAlpha()0%2100%
UpdateDirectionVectors()0%90900%
TranslateCamera(...)0%6200%
Update()0%6200%

File(s)

/github/workspace/Assets/Scripts/UI/CameraController.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using UnityEngine;
 4using System.Linq;
 5
 6public class CameraController : MonoBehaviour {
 7#region Singleton
 8
 9  /// <summary>
 10  /// Singleton instance of the CameraController.
 11  /// </summary>
 012  public static CameraController Instance { get; private set; }
 13
 14#endregion
 15
 16#region Camera Settings
 17
 18  /// <summary>
 19  /// Determines if mouse input is active for camera control.
 20  /// </summary>
 021  public bool mouseActive = true;
 22
 23  /// <summary>
 24  /// Locks user input for camera control.
 25  /// </summary>
 026  public bool lockUserInput = false;
 27
 28  /// <summary>
 29  /// Normal speed of camera movement.
 30  /// </summary>
 31  [SerializeField]
 032  private float _cameraSpeedNormal = 100.0f;
 33
 34  /// <summary>
 35  /// Maximum speed of camera movement.
 36  /// </summary>
 37  [SerializeField]
 038  private float _cameraSpeedMax = 1000.0f;
 39
 40  /// <summary>
 41  /// Current speed of camera movement.
 42  /// </summary>
 43  private float _cameraSpeed;
 44
 45  /// <summary>
 46  /// Horizontal rotation speed.
 47  /// </summary>
 048  public float _speedH = 2.0f;
 49
 50  /// <summary>
 51  /// Vertical rotation speed.
 52  /// </summary>
 053  public float _speedV = 2.0f;
 54
 55  /// <summary>
 56  /// Current yaw angle of the camera.
 57  /// </summary>
 058  private float _yaw = 0.0f;
 59
 60  /// <summary>
 61  /// Current pitch angle of the camera.
 62  /// </summary>
 063  private float _pitch = 0.0f;
 64
 65#endregion
 66
 67#region Orbit Settings
 68
 69  /// <summary>
 70  /// Determines if the camera should auto-rotate.
 71  /// </summary>
 072  public bool _autoRotate = false;
 73
 74  /// <summary>
 75  /// Threat transform for orbit rotation.
 76  /// </summary>
 77  public Transform target;
 78
 79  /// <summary>
 80  /// Distance from the camera to the orbit target.
 81  /// </summary>
 82  [SerializeField]
 083  private float _orbitDistance = 5.0f;
 84
 85  /// <summary>
 86  /// Horizontal orbit rotation speed.
 87  /// </summary>
 88  [SerializeField]
 089  private float _orbitXSpeed = 120.0f;
 90
 91  /// <summary>
 92  /// Vertical orbit rotation speed.
 93  /// </summary>
 94  [SerializeField]
 095  private float _orbitYSpeed = 120.0f;
 96
 97  /// <summary>
 98  /// Speed of camera zoom.
 99  /// </summary>
 100  [SerializeField]
 0101  private float _zoomSpeed = 500.0f;
 102
 103  /// <summary>
 104  /// Minimum vertical angle limit for orbit.
 105  /// </summary>
 0106  public float orbitYMinLimit = -20f;
 107
 108  /// <summary>
 109  /// Maximum vertical angle limit for orbit.
 110  /// </summary>
 0111  public float orbitYMaxLimit = 80f;
 112
 113  /// <summary>
 114  /// Minimum distance for orbit.
 115  /// </summary>
 0116  private float _orbitDistanceMin = 10f;
 117
 118  /// <summary>
 119  /// Maximum distance for orbit.
 120  /// </summary>
 121  [SerializeField]
 0122  private float _orbitDistanceMax = 20000f;
 123
 124  /// <summary>
 125  /// Current horizontal orbit angle.
 126  /// </summary>
 0127  private float _orbitX = 0.0f;
 128
 129  /// <summary>
 130  /// Current vertical orbit angle.
 131  /// </summary>
 0132  private float _orbitY = 0.0f;
 133
 134#endregion
 135
 136#region Rendering
 137
 138  /// <summary>
 139  /// Renderer for the orbit target.
 140  /// </summary>
 141  public Renderer targetRenderer;
 142
 143  /// <summary>
 144  /// Renderer for the floor.
 145  /// </summary>
 146  public Renderer floorRenderer;
 147
 148  /// <summary>
 149  /// Alpha value for material transparency.
 150  /// </summary>
 151  public float matAlpha;
 152
 153#endregion
 154
 155#region Autoplay Settings
 156
 157  /// <summary>
 158  /// Speed of camera movement during autoplay.
 159  /// </summary>
 0160  public float autoplayCamSpeed = 2f;
 161
 162  /// <summary>
 163  /// Duration of horizontal auto-rotation.
 164  /// </summary>
 0165  public float xAutoRotateTime = 5f;
 166
 167  /// <summary>
 168  /// Duration of vertical auto-rotation.
 169  /// </summary>
 0170  public float yAutoRotateTime = 5f;
 171
 172  /// <summary>
 173  /// Coroutine for autoplay functionality.
 174  /// </summary>
 175  private Coroutine autoplayRoutine;
 176
 177#endregion
 178
 179#region Camera Presets
 180
 181  /// <summary>
 182  /// Represents a preset camera position and rotation.
 183  /// </summary>
 184  [System.Serializable]
 185  public struct CameraPreset {
 186    public Vector3 position;
 187    public Quaternion rotation;
 188  }
 189
 190  /// <summary>
 191  /// Preset camera position for key 4.
 192  /// </summary>
 0193  CameraPreset fourPos = new CameraPreset();
 194
 195  /// <summary>
 196  /// Preset camera position for key 5.
 197  /// </summary>
 0198  CameraPreset fivePos = new CameraPreset();
 199
 200  /// <summary>
 201  /// Preset camera position for key 6.
 202  /// </summary>
 0203  CameraPreset sixPos = new CameraPreset();
 204
 205#endregion
 206
 207#region Movement
 208
 209  /// <summary>
 210  /// Mapping of translation inputs to movement vectors.
 211  /// </summary>
 212  private Dictionary<TranslationInput, Vector3> _translationInputToVectorMap;
 213
 214  /// <summary>
 215  /// Forward movement vector.
 216  /// </summary>
 0217  Vector3 wVector = Vector3.forward;
 218
 219  /// <summary>
 220  /// Left movement vector.
 221  /// </summary>
 0222  Vector3 aVector = Vector3.left;
 223
 224  /// <summary>
 225  /// Backward movement vector.
 226  /// </summary>
 0227  Vector3 sVector = Vector3.back;
 228
 229  /// <summary>
 230  /// Right movement vector.
 231  /// </summary>
 0232  Vector3 dVector = Vector3.right;
 233
 234  /// <summary>
 235  /// Angle between forward vector and camera direction.
 236  /// </summary>
 237  public float forwardToCameraAngle;
 238
 0239  public CameraMode cameraMode = CameraMode.FREE;
 240
 0241  public int _selectedInterceptorSwarmIndex = -1;
 0242  public int _selectedThreatSwarmIndex = -1;
 243
 244  private Vector3 _lastCentroid;
 245  private Vector3 _currentCentroid;
 246  private Vector3 _targetCentroid;
 247
 0248  public float _centroidUpdateFrequency = 0.1f;
 0249  public float _defaultInterpolationSpeed = 5f;
 250  [SerializeField]
 251  private float _currentInterpolationSpeed;
 252
 253  [SerializeField]
 0254  private float _iirFilterCoefficient = 0.9f;
 255
 256  private Coroutine _centroidUpdateCoroutine;
 257
 258#endregion
 259
 0260  void SetCameraRotation(Quaternion rotation) {
 0261    transform.rotation = rotation;
 0262    _pitch = rotation.eulerAngles.x;
 0263    _yaw = rotation.eulerAngles.y;
 0264  }
 265
 0266  public void SetCameraSpeed(float speed) {
 0267    _cameraSpeed = speed;
 0268  }
 269
 0270  public float GetCameraSpeedMax() {
 0271    return _cameraSpeedMax;
 0272  }
 273
 0274  public float GetCameraSpeedNormal() {
 0275    return _cameraSpeedNormal;
 0276  }
 277
 0278  public bool IsAutoRotate() {
 0279    return _autoRotate;
 0280  }
 281
 0282  public void SetAutoRotate(bool autoRotate) {
 0283    if (autoRotate && !_autoRotate) {
 0284      _autoRotate = true;
 0285      autoplayRoutine = StartCoroutine(AutoPlayRoutine());
 0286    } else if (!autoRotate && _autoRotate) {
 0287      _autoRotate = false;
 0288      StopCoroutine(autoplayRoutine);
 0289    }
 0290  }
 291
 0292  public static float ClampAngle(float angle, float min, float max) {
 0293    if (angle < -360F)
 0294      angle += 360F;
 0295    if (angle > 360F)
 0296      angle -= 360F;
 0297    return Mathf.Clamp(angle, min, max);
 0298  }
 299
 0300  private void Awake() {
 0301    if (Instance == null) {
 0302      Instance = this;
 0303      DontDestroyOnLoad(gameObject);
 0304    } else {
 0305      Destroy(gameObject);
 0306    }
 307
 0308    _translationInputToVectorMap = new Dictionary<TranslationInput, Vector3> {
 309      { TranslationInput.Forward, wVector }, { TranslationInput.Left, aVector },
 310      { TranslationInput.Back, sVector },    { TranslationInput.Right, dVector },
 311      { TranslationInput.Up, Vector3.up },   { TranslationInput.Down, Vector3.down }
 312    };
 0313    _currentInterpolationSpeed = _defaultInterpolationSpeed;
 0314  }
 315
 316  // Start is called before the first frame update
 0317  void Start() {
 0318    fourPos.position = new Vector3(0, 0, 0);
 0319    fourPos.rotation = Quaternion.Euler(0, 0, 0);
 0320    fivePos.position = new Vector3(0, 0, 0);
 0321    fivePos.rotation = Quaternion.Euler(0, 0, 0);
 0322    sixPos.position = new Vector3(0, 0, 0);
 0323    sixPos.rotation = Quaternion.Euler(0, 0, 0);
 324
 0325    Vector3 angles = transform.eulerAngles;
 0326    _orbitX = angles.y;
 0327    _orbitY = angles.x;
 328
 0329    UpdateTargetAlpha();
 0330    ResetCameraTarget();
 331
 0332    SetCameraMode(CameraMode.FREE);
 0333  }
 334
 0335  public void SnapToSwarm(List<Agent> swarm, bool forceFreeMode = true) {
 0336    Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(swarm);
 0337    SetCameraTargetPosition(swarmCenter);
 0338    if (forceFreeMode) {
 0339      SetCameraMode(CameraMode.FREE);
 0340    }
 0341  }
 342
 0343  public void SnapToNextInterceptorSwarm(bool forceFreeMode = true) {
 0344    if (SimManager.Instance.GetInterceptorSwarms().Count == 0) {
 0345      UIManager.Instance.LogActionWarning("[CAM] No interceptor swarms to follow");
 0346      return;
 347    }
 348
 349    // Set pre-set view 1
 0350    _selectedInterceptorSwarmIndex += 1;
 0351    _selectedThreatSwarmIndex = -1;
 0352    if (_selectedInterceptorSwarmIndex >= SimManager.Instance.GetInterceptorSwarms().Count) {
 0353      _selectedInterceptorSwarmIndex = 0;
 0354    }
 0355    List<(Agent, bool)> swarm =
 356        SimManager.Instance.GetInterceptorSwarms()[_selectedInterceptorSwarmIndex];
 0357    string swarmTitle = SimManager.Instance.GenerateInterceptorSwarmTitle(swarm);
 358
 359    // Filter out inactive agents
 0360    List<(Agent, bool)> activeAgents = swarm.FindAll(tuple => tuple.Item2);
 0361    List<Agent> activeAgentsList = activeAgents.ConvertAll(tuple => tuple.Item1);
 0362    Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(activeAgentsList);
 0363    SetCameraTargetPosition(swarmCenter);
 0364    if (forceFreeMode) {
 0365      SetCameraMode(CameraMode.FREE);
 0366    }
 0367    UIManager.Instance.LogActionMessage($"[CAM] Snap to interceptor swarm: {swarmTitle}");
 0368  }
 369
 0370  public void SnapToNextThreatSwarm(bool forceFreeMode = true) {
 0371    if (SimManager.Instance.GetThreatSwarms().Count == 0) {
 0372      return;
 373    }
 0374    _selectedInterceptorSwarmIndex = -1;
 0375    _selectedThreatSwarmIndex += 1;
 0376    if (_selectedThreatSwarmIndex >= SimManager.Instance.GetThreatSwarms().Count) {
 0377      _selectedThreatSwarmIndex = 0;
 0378    }
 0379    List<(Agent, bool)> swarm = SimManager.Instance.GetThreatSwarms()[_selectedThreatSwarmIndex];
 0380    string swarmTitle = SimManager.Instance.GenerateThreatSwarmTitle(swarm);
 381    // Filter out inactive agents
 0382    List<(Agent, bool)> activeAgents = swarm.FindAll(tuple => tuple.Item2);
 0383    List<Agent> activeAgentsList = activeAgents.ConvertAll(tuple => tuple.Item1);
 0384    Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(activeAgentsList);
 0385    SetCameraTargetPosition(swarmCenter);
 0386    if (forceFreeMode) {
 0387      SetCameraMode(CameraMode.FREE);
 0388    }
 0389    UIManager.Instance.LogActionMessage($"[CAM] Snap to threat swarm: {swarmTitle}");
 0390  }
 391
 0392  public void SnapToCenterAllAgents(bool forceFreeMode = true) {
 0393    Vector3 swarmCenter = SimManager.Instance.GetAllAgentsCenter();
 0394    SetCameraTargetPosition(swarmCenter);
 0395    if (forceFreeMode) {
 0396      SetCameraMode(CameraMode.FREE);
 0397    }
 0398    UIManager.Instance.LogActionMessage("[CAM] Snap to center all agents");
 0399  }
 400
 0401  public void SetCameraMode(CameraMode mode) {
 0402    if (cameraMode == CameraMode.FREE) {
 0403      if (_centroidUpdateCoroutine != null) {
 0404        StopCoroutine(_centroidUpdateCoroutine);
 0405        _centroidUpdateCoroutine = null;
 0406      }
 0407    } else {
 0408      _currentCentroid = _targetCentroid = target.position;
 0409    }
 0410    cameraMode = mode;
 0411  }
 412
 0413  private void StartCentroidUpdateCoroutine() {
 0414    if (_centroidUpdateCoroutine == null) {
 0415      _centroidUpdateCoroutine = StartCoroutine(UpdateCentroidCoroutine());
 0416    }
 0417  }
 418
 0419  public void FollowNextInterceptorSwarm() {
 0420    SnapToNextInterceptorSwarm(false);
 0421    StartCentroidUpdateCoroutine();
 0422    SetCameraMode(CameraMode.FOLLOW_INTERCEPTOR_SWARM);
 0423    UIManager.Instance.LogActionMessage("[CAM] Follow next interceptor swarm");
 0424  }
 425
 0426  public void FollowNextThreatSwarm() {
 0427    SnapToNextThreatSwarm(false);
 0428    SetCameraMode(CameraMode.FOLLOW_THREAT_SWARM);
 0429    StartCentroidUpdateCoroutine();
 0430    UIManager.Instance.LogActionMessage("[CAM] Follow next threat swarm");
 0431  }
 432
 0433  public void FollowCenterAllAgents() {
 0434    SnapToCenterAllAgents(false);
 0435    SetCameraMode(CameraMode.FOLLOW_ALL_AGENTS);
 0436    StartCentroidUpdateCoroutine();
 0437    UIManager.Instance.LogActionMessage("[CAM] Follow center all agents");
 0438  }
 439
 0440  private IEnumerator UpdateCentroidCoroutine() {
 0441    while (true) {
 0442      UpdateTargetCentroid();
 0443      yield return new WaitForSeconds(_centroidUpdateFrequency);
 0444    }
 445  }
 446
 0447  private void UpdateTargetCentroid() {
 0448    _lastCentroid = _currentCentroid;
 449
 0450    if (cameraMode == CameraMode.FOLLOW_INTERCEPTOR_SWARM) {
 0451      if (_selectedInterceptorSwarmIndex == -1) {
 0452        _selectedInterceptorSwarmIndex = 0;
 0453      }
 0454      if (SimManager.Instance.GetInterceptorSwarms().Count == 0) {
 0455        return;
 456      }
 0457      _targetCentroid = SimManager.Instance.GetSwarmCenter(
 458          SimManager.Instance.GetInterceptorSwarms() [_selectedInterceptorSwarmIndex].ConvertAll(
 0459              tuple => tuple.Item1));
 0460    } else if (cameraMode == CameraMode.FOLLOW_THREAT_SWARM) {
 0461      if (_selectedThreatSwarmIndex == -1) {
 0462        _selectedThreatSwarmIndex = 0;
 0463      }
 0464      if (SimManager.Instance.GetThreatSwarms().Count == 0) {
 0465        return;
 466      }
 0467      _targetCentroid = SimManager.Instance.GetSwarmCenter(
 468          SimManager.Instance.GetThreatSwarms() [_selectedThreatSwarmIndex].ConvertAll(
 0469              tuple => tuple.Item1));
 0470    } else if (cameraMode == CameraMode.FOLLOW_ALL_AGENTS) {
 0471      _targetCentroid = SimManager.Instance.GetAllAgentsCenter();
 0472    }
 473    // Apply IIR filter to adjust interpolation speed
 0474    float distance = Mathf.Abs(Vector3.Distance(_lastCentroid, _targetCentroid));
 0475    float targetSpeed = Mathf.Clamp(distance, 1f, 100000f);
 0476    _currentInterpolationSpeed = _iirFilterCoefficient * _currentInterpolationSpeed +
 477                                 (1 - _iirFilterCoefficient) * targetSpeed;
 0478  }
 479
 0480  IEnumerator AutoPlayRoutine() {
 0481    while (true) {
 0482      float elapsedTime = 0f;
 0483      while (elapsedTime <= xAutoRotateTime) {
 0484        _orbitX += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0485        UpdateCamPosition(_orbitX, _orbitY);
 0486        elapsedTime += Time.unscaledDeltaTime;
 0487        yield return null;
 0488      }
 0489      elapsedTime = 0f;
 0490      while (elapsedTime <= yAutoRotateTime) {
 0491        _orbitY -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0492        UpdateCamPosition(_orbitX, _orbitY);
 0493        elapsedTime += Time.unscaledDeltaTime;
 0494        yield return null;
 0495      }
 0496      elapsedTime = 0f;
 0497      while (elapsedTime <= xAutoRotateTime) {
 0498        _orbitX -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0499        UpdateCamPosition(_orbitX, _orbitY);
 0500        elapsedTime += Time.unscaledDeltaTime;
 0501        yield return null;
 0502      }
 0503      elapsedTime = 0f;
 0504      while (elapsedTime <= yAutoRotateTime) {
 0505        _orbitY += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0506        UpdateCamPosition(_orbitX, _orbitY);
 0507        elapsedTime += Time.unscaledDeltaTime;
 0508        yield return null;
 0509      }
 0510      yield return null;
 0511    }
 512  }
 513
 0514  public void SetCameraTargetPosition(Vector3 position) {
 0515    target.transform.position = position;
 0516    UpdateCamPosition(_orbitX, _orbitY);
 0517  }
 518
 0519  void ResetCameraTarget() {
 520    RaycastHit hit;
 0521    if (Physics.Raycast(transform.position, transform.forward, out hit, float.MaxValue,
 0522                        LayerMask.GetMask("Floor"), QueryTriggerInteraction.Ignore)) {
 0523      target.transform.position = hit.point;
 0524      _orbitDistance = hit.distance;
 0525      Vector3 angles = transform.eulerAngles;
 0526      _orbitX = angles.y;
 0527      _orbitY = angles.x;
 0528      UpdateCamPosition(_orbitX, _orbitY);
 0529    } else {
 0530      target.transform.position = transform.position + (transform.forward * 100);
 0531      _orbitDistance = 100;
 0532      Vector3 angles = transform.eulerAngles;
 0533      _orbitX = angles.y;
 0534      _orbitY = angles.x;
 535      // UpdateCamPosition();
 0536    }
 0537  }
 538
 0539  public void EnableTargetRenderer(bool enable) {
 0540    targetRenderer.enabled = enable;
 0541  }
 542
 0543  public void EnableFloorGridRenderer(bool enable) {
 0544    floorRenderer.enabled = enable;
 0545  }
 546
 0547  public void OrbitCamera(float xOrbit, float yOrbit) {
 0548    if (target) {
 0549      _orbitX += xOrbit * _orbitXSpeed * _orbitDistance * 0.02f;
 0550      _orbitY -= yOrbit * _orbitYSpeed * _orbitDistance * 0.02f;
 551
 0552      _orbitY = ClampAngle(_orbitY, orbitYMinLimit, orbitYMaxLimit);
 0553      UpdateCamPosition(_orbitX, _orbitY);
 0554    }
 0555  }
 556
 0557  public void RotateCamera(float xRotate, float yRotate) {
 0558    _yaw += xRotate * _speedH;
 0559    _pitch -= yRotate * _speedV;
 0560    transform.eulerAngles = new Vector3(_pitch, _yaw, 0.0f);
 0561  }
 562
 0563  private void UpdateCamPosition(float x, float y) {
 0564    Quaternion rotation = Quaternion.Euler(y, x, 0);
 565    RaycastHit hit;
 566    // Debug.DrawLine(target.position, transform.position, Color.red);
 0567    if (Physics.Linecast(target.position, transform.position, out hit, ~LayerMask.GetMask("Floor"),
 0568                         QueryTriggerInteraction.Ignore)) {
 0569      _orbitDistance -= hit.distance;
 0570    }
 0571    Vector3 negDistance = new Vector3(0.0f, 0.0f, -_orbitDistance);
 0572    Vector3 position = rotation * negDistance + target.position;
 0573    _orbitDistance = Mathf.Clamp(_orbitDistance, _orbitDistanceMin, _orbitDistanceMax);
 0574    UpdateTargetAlpha();
 575
 0576    SetCameraRotation(rotation);
 0577    transform.position = position;
 0578  }
 579
 0580  public void ZoomCamera(float zoom) {
 0581    _orbitDistance =
 582        Mathf.Clamp(_orbitDistance - zoom * _zoomSpeed, _orbitDistanceMin, _orbitDistanceMax);
 0583    UpdateCamPosition(_orbitX, _orbitY);
 0584  }
 585
 0586  void UpdateTargetAlpha() {
 0587    matAlpha = (_orbitDistance - _orbitDistanceMin) / (_orbitDistanceMax - _orbitDistanceMin);
 0588    matAlpha = Mathf.Max(Mathf.Sqrt(matAlpha) - 0.5f, 0);
 0589    Color matColor = targetRenderer.material.color;
 0590    matColor.a = matAlpha;
 0591    targetRenderer.material.color = matColor;
 0592  }
 593
 0594  void UpdateDirectionVectors() {
 0595    Vector3 cameraToTarget = target.position - transform.position;
 0596    cameraToTarget.y = 0;
 0597    forwardToCameraAngle = Vector3.SignedAngle(Vector3.forward, cameraToTarget, Vector3.down);
 598
 0599    if (forwardToCameraAngle > -45f && forwardToCameraAngle <= 45f) {
 0600      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.forward;
 0601      _translationInputToVectorMap[TranslationInput.Left] = Vector3.left;
 0602      _translationInputToVectorMap[TranslationInput.Back] = Vector3.back;
 0603      _translationInputToVectorMap[TranslationInput.Right] = Vector3.right;
 0604    } else if (forwardToCameraAngle > 45f && forwardToCameraAngle <= 135f) {
 0605      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.left;
 0606      _translationInputToVectorMap[TranslationInput.Left] = Vector3.back;
 0607      _translationInputToVectorMap[TranslationInput.Back] = Vector3.right;
 0608      _translationInputToVectorMap[TranslationInput.Right] = Vector3.forward;
 0609    } else if (forwardToCameraAngle > 135f || forwardToCameraAngle <= -135f) {
 0610      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.back;
 0611      _translationInputToVectorMap[TranslationInput.Left] = Vector3.right;
 0612      _translationInputToVectorMap[TranslationInput.Back] = Vector3.forward;
 0613      _translationInputToVectorMap[TranslationInput.Right] = Vector3.left;
 0614    } else if (forwardToCameraAngle > -135f && forwardToCameraAngle <= -45f) {
 0615      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.right;
 0616      _translationInputToVectorMap[TranslationInput.Left] = Vector3.forward;
 0617      _translationInputToVectorMap[TranslationInput.Back] = Vector3.left;
 0618      _translationInputToVectorMap[TranslationInput.Right] = Vector3.back;
 0619    }
 0620  }
 621
 622  public enum TranslationInput { Forward, Left, Back, Right, Up, Down }
 623
 0624  public void TranslateCamera(TranslationInput input) {
 0625    if (cameraMode != CameraMode.FREE) {
 0626      SetCameraMode(CameraMode.FREE);
 0627    }
 0628    UpdateDirectionVectors();
 0629    target.Translate(_translationInputToVectorMap[input] * Time.unscaledDeltaTime * _cameraSpeed);
 0630    UpdateCamPosition(_orbitX, _orbitY);
 0631  }
 632
 0633  protected void Update() {
 0634    if (cameraMode != CameraMode.FREE) {
 635      // Use MoveTowards for smoother and more predictable movement
 0636      _currentCentroid = Vector3.MoveTowards(_currentCentroid, _targetCentroid,
 637                                             _currentInterpolationSpeed * Time.deltaTime);
 0638      SetCameraTargetPosition(_currentCentroid);
 0639    }
 0640  }
 641}
 642
 643public enum CameraMode { FREE, FOLLOW_INTERCEPTOR_SWARM, FOLLOW_THREAT_SWARM, FOLLOW_ALL_AGENTS }