< 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:564
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  // Singleton instance of the camera controller.
 010  public static CameraController Instance { get; private set; }
 11
 12#endregion
 13
 14#region Camera Settings
 15
 16  // Determines if mouse input is active for camera control.
 017  public bool mouseActive = true;
 18
 19  // Locks user input for camera control.
 020  public bool lockUserInput = false;
 21
 22  // Normal speed of camera movement.
 23  [SerializeField]
 024  private float _cameraSpeedNormal = 100.0f;
 25
 26  // Maximum speed of camera movement.
 27  [SerializeField]
 028  private float _cameraSpeedMax = 1000.0f;
 29
 30  // Current speed of camera movement.
 31  private float _cameraSpeed;
 32
 33  // Horizontal rotation speed.
 034  public float _speedH = 2.0f;
 35
 36  // Vertical rotation speed.
 037  public float _speedV = 2.0f;
 38
 39  // Current yaw angle of the camera.
 040  private float _yaw = 0.0f;
 41
 42  // Current pitch angle of the camera.
 043  private float _pitch = 0.0f;
 44
 45#endregion
 46
 47#region Orbit Settings
 48
 49  // Determines if the camera should auto-rotate.
 050  public bool _autoRotate = false;
 51
 52  // Threat transform for orbit rotation.
 53  public Transform target;
 54
 55  // Distance from the camera to the orbit target.
 56  [SerializeField]
 057  private float _orbitDistance = 5.0f;
 58
 59  // Horizontal orbit rotation speed.
 60  [SerializeField]
 061  private float _orbitXSpeed = 120.0f;
 62
 63  // Vertical orbit rotation speed.
 64  [SerializeField]
 065  private float _orbitYSpeed = 120.0f;
 66
 67  // Speed of camera zoom.
 68  [SerializeField]
 069  private float _zoomSpeed = 500.0f;
 70
 71  // Minimum vertical angle limit for orbit.
 072  public float orbitYMinLimit = -20f;
 73
 74  // Maximum vertical angle limit for orbit.
 075  public float orbitYMaxLimit = 80f;
 76
 77  // Minimum distance for orbit.
 078  private float _orbitDistanceMin = 10f;
 79
 80  // Maximum distance for orbit.
 81  [SerializeField]
 082  private float _orbitDistanceMax = 20000f;
 83
 84  // Current horizontal orbit angle.
 085  private float _orbitX = 0.0f;
 86
 87  // Current vertical orbit angle.
 088  private float _orbitY = 0.0f;
 89
 90#endregion
 91
 92#region Rendering
 93
 94  // Renderer for the orbit target.
 95  public Renderer targetRenderer;
 96
 97  // Renderer for the floor.
 98  public Renderer floorRenderer;
 99
 100  // Alpha value for material transparency.
 101  public float matAlpha;
 102
 103#endregion
 104
 105#region Autoplay Settings
 106
 107  // Speed of camera movement during autoplay.
 0108  public float autoplayCamSpeed = 2f;
 109
 110  // Duration of horizontal auto-rotation.
 0111  public float xAutoRotateTime = 5f;
 112
 113  // Duration of vertical auto-rotation.
 0114  public float yAutoRotateTime = 5f;
 115
 116  // Coroutine for autoplay functionality.
 117  private Coroutine autoplayRoutine;
 118
 119#endregion
 120
 121#region Camera Presets
 122
 123  // Represents a preset camera position and rotation.
 124  [System.Serializable]
 125  public struct CameraPreset {
 126    public Vector3 position;
 127    public Quaternion rotation;
 128  }
 129
 130  // Preset camera position for key 4.
 0131  CameraPreset fourPos = new CameraPreset();
 132
 133  // Preset camera position for key 5.
 0134  CameraPreset fivePos = new CameraPreset();
 135
 136  // Preset camera position for key 6.
 0137  CameraPreset sixPos = new CameraPreset();
 138
 139#endregion
 140
 141#region Movement
 142
 143  // Mapping of translation inputs to movement vectors.
 144  private Dictionary<TranslationInput, Vector3> _translationInputToVectorMap;
 145
 146  // Forward movement vector.
 0147  Vector3 wVector = Vector3.forward;
 148
 149  // Left movement vector.
 0150  Vector3 aVector = Vector3.left;
 151
 152  // Backward movement vector.
 0153  Vector3 sVector = Vector3.back;
 154
 155  // Right movement vector.
 0156  Vector3 dVector = Vector3.right;
 157
 158  // Angle between forward vector and camera direction.
 159  public float forwardToCameraAngle;
 160
 0161  public CameraMode cameraMode = CameraMode.FREE;
 162
 0163  public int _selectedInterceptorSwarmIndex = -1;
 0164  public int _selectedThreatSwarmIndex = -1;
 165
 166  private Vector3 _lastCentroid;
 167  private Vector3 _currentCentroid;
 168  private Vector3 _targetCentroid;
 169
 0170  public float _centroidUpdateFrequency = 0.1f;
 0171  public float _defaultInterpolationSpeed = 5f;
 172  [SerializeField]
 173  private float _currentInterpolationSpeed;
 174
 175  [SerializeField]
 0176  private float _iirFilterCoefficient = 0.9f;
 177
 178  private Coroutine _centroidUpdateCoroutine;
 179
 180#endregion
 181
 0182  void SetCameraRotation(Quaternion rotation) {
 0183    transform.rotation = rotation;
 0184    _pitch = rotation.eulerAngles.x;
 0185    _yaw = rotation.eulerAngles.y;
 0186  }
 187
 0188  public void SetCameraSpeed(float speed) {
 0189    _cameraSpeed = speed;
 0190  }
 191
 0192  public float GetCameraSpeedMax() {
 0193    return _cameraSpeedMax;
 0194  }
 195
 0196  public float GetCameraSpeedNormal() {
 0197    return _cameraSpeedNormal;
 0198  }
 199
 0200  public bool IsAutoRotate() {
 0201    return _autoRotate;
 0202  }
 203
 0204  public void SetAutoRotate(bool autoRotate) {
 0205    if (autoRotate && !_autoRotate) {
 0206      _autoRotate = true;
 0207      autoplayRoutine = StartCoroutine(AutoPlayRoutine());
 0208    } else if (!autoRotate && _autoRotate) {
 0209      _autoRotate = false;
 0210      StopCoroutine(autoplayRoutine);
 0211    }
 0212  }
 213
 0214  public static float ClampAngle(float angle, float min, float max) {
 0215    if (angle < -360F)
 0216      angle += 360F;
 0217    if (angle > 360F)
 0218      angle -= 360F;
 0219    return Mathf.Clamp(angle, min, max);
 0220  }
 221
 0222  private void Awake() {
 0223    if (Instance == null) {
 0224      Instance = this;
 0225      DontDestroyOnLoad(gameObject);
 0226    } else {
 0227      Destroy(gameObject);
 0228    }
 229
 0230    _translationInputToVectorMap = new Dictionary<TranslationInput, Vector3> {
 231      { TranslationInput.Forward, wVector }, { TranslationInput.Left, aVector },
 232      { TranslationInput.Back, sVector },    { TranslationInput.Right, dVector },
 233      { TranslationInput.Up, Vector3.up },   { TranslationInput.Down, Vector3.down }
 234    };
 0235    _currentInterpolationSpeed = _defaultInterpolationSpeed;
 0236  }
 237
 0238  void Start() {
 0239    fourPos.position = new Vector3(0, 0, 0);
 0240    fourPos.rotation = Quaternion.Euler(0, 0, 0);
 0241    fivePos.position = new Vector3(0, 0, 0);
 0242    fivePos.rotation = Quaternion.Euler(0, 0, 0);
 0243    sixPos.position = new Vector3(0, 0, 0);
 0244    sixPos.rotation = Quaternion.Euler(0, 0, 0);
 245
 0246    Vector3 angles = transform.eulerAngles;
 0247    _orbitX = angles.y;
 0248    _orbitY = angles.x;
 249
 0250    UpdateTargetAlpha();
 0251    ResetCameraTarget();
 252
 0253    SetCameraMode(CameraMode.FREE);
 0254  }
 255
 0256  public void SnapToSwarm(List<Agent> swarm, bool forceFreeMode = true) {
 0257    Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(swarm);
 0258    SetCameraTargetPosition(swarmCenter);
 0259    if (forceFreeMode) {
 0260      SetCameraMode(CameraMode.FREE);
 0261    }
 0262  }
 263
 0264  public void SnapToNextInterceptorSwarm(bool forceFreeMode = true) {
 0265    if (SimManager.Instance.GetInterceptorSwarms().Count == 0) {
 0266      UIManager.Instance.LogActionWarning("[CAM] No interceptor swarms to follow");
 0267      return;
 268    }
 269
 270    // Set pre-set view 1
 0271    _selectedInterceptorSwarmIndex += 1;
 0272    _selectedThreatSwarmIndex = -1;
 0273    if (_selectedInterceptorSwarmIndex >= SimManager.Instance.GetInterceptorSwarms().Count) {
 0274      _selectedInterceptorSwarmIndex = 0;
 0275    }
 0276    List<(Agent, bool)> swarm =
 277        SimManager.Instance.GetInterceptorSwarms()[_selectedInterceptorSwarmIndex];
 0278    string swarmTitle = SimManager.Instance.GenerateInterceptorSwarmTitle(swarm);
 279
 280    // Filter out inactive agents
 0281    List<(Agent, bool)> activeAgents = swarm.FindAll(tuple => tuple.Item2);
 0282    List<Agent> activeAgentsList = activeAgents.ConvertAll(tuple => tuple.Item1);
 0283    Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(activeAgentsList);
 0284    SetCameraTargetPosition(swarmCenter);
 0285    if (forceFreeMode) {
 0286      SetCameraMode(CameraMode.FREE);
 0287    }
 0288    UIManager.Instance.LogActionMessage($"[CAM] Snap to interceptor swarm: {swarmTitle}");
 0289  }
 290
 0291  public void SnapToNextThreatSwarm(bool forceFreeMode = true) {
 0292    if (SimManager.Instance.GetThreatSwarms().Count == 0) {
 0293      return;
 294    }
 0295    _selectedInterceptorSwarmIndex = -1;
 0296    _selectedThreatSwarmIndex += 1;
 0297    if (_selectedThreatSwarmIndex >= SimManager.Instance.GetThreatSwarms().Count) {
 0298      _selectedThreatSwarmIndex = 0;
 0299    }
 0300    List<(Agent, bool)> swarm = SimManager.Instance.GetThreatSwarms()[_selectedThreatSwarmIndex];
 0301    string swarmTitle = SimManager.Instance.GenerateThreatSwarmTitle(swarm);
 302    // Filter out inactive agents
 0303    List<(Agent, bool)> activeAgents = swarm.FindAll(tuple => tuple.Item2);
 0304    List<Agent> activeAgentsList = activeAgents.ConvertAll(tuple => tuple.Item1);
 0305    Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(activeAgentsList);
 0306    SetCameraTargetPosition(swarmCenter);
 0307    if (forceFreeMode) {
 0308      SetCameraMode(CameraMode.FREE);
 0309    }
 0310    UIManager.Instance.LogActionMessage($"[CAM] Snap to threat swarm: {swarmTitle}");
 0311  }
 312
 0313  public void SnapToCenterAllAgents(bool forceFreeMode = true) {
 0314    Vector3 swarmCenter = SimManager.Instance.GetAllAgentsCenter();
 0315    SetCameraTargetPosition(swarmCenter);
 0316    if (forceFreeMode) {
 0317      SetCameraMode(CameraMode.FREE);
 0318    }
 0319    UIManager.Instance.LogActionMessage("[CAM] Snap to center all agents");
 0320  }
 321
 0322  public void SetCameraMode(CameraMode mode) {
 0323    if (cameraMode == CameraMode.FREE) {
 0324      if (_centroidUpdateCoroutine != null) {
 0325        StopCoroutine(_centroidUpdateCoroutine);
 0326        _centroidUpdateCoroutine = null;
 0327      }
 0328    } else {
 0329      _currentCentroid = _targetCentroid = target.position;
 0330    }
 0331    cameraMode = mode;
 0332  }
 333
 0334  private void StartCentroidUpdateCoroutine() {
 0335    if (_centroidUpdateCoroutine == null) {
 0336      _centroidUpdateCoroutine = StartCoroutine(UpdateCentroidCoroutine());
 0337    }
 0338  }
 339
 0340  public void FollowNextInterceptorSwarm() {
 0341    SnapToNextInterceptorSwarm(false);
 0342    StartCentroidUpdateCoroutine();
 0343    SetCameraMode(CameraMode.FOLLOW_INTERCEPTOR_SWARM);
 0344    UIManager.Instance.LogActionMessage("[CAM] Follow next interceptor swarm");
 0345  }
 346
 0347  public void FollowNextThreatSwarm() {
 0348    SnapToNextThreatSwarm(false);
 0349    SetCameraMode(CameraMode.FOLLOW_THREAT_SWARM);
 0350    StartCentroidUpdateCoroutine();
 0351    UIManager.Instance.LogActionMessage("[CAM] Follow next threat swarm");
 0352  }
 353
 0354  public void FollowCenterAllAgents() {
 0355    SnapToCenterAllAgents(false);
 0356    SetCameraMode(CameraMode.FOLLOW_ALL_AGENTS);
 0357    StartCentroidUpdateCoroutine();
 0358    UIManager.Instance.LogActionMessage("[CAM] Follow center all agents");
 0359  }
 360
 0361  private IEnumerator UpdateCentroidCoroutine() {
 0362    while (true) {
 0363      UpdateTargetCentroid();
 0364      yield return new WaitForSeconds(_centroidUpdateFrequency);
 0365    }
 366  }
 367
 0368  private void UpdateTargetCentroid() {
 0369    _lastCentroid = _currentCentroid;
 370
 0371    if (cameraMode == CameraMode.FOLLOW_INTERCEPTOR_SWARM) {
 0372      if (_selectedInterceptorSwarmIndex == -1) {
 0373        _selectedInterceptorSwarmIndex = 0;
 0374      }
 0375      if (SimManager.Instance.GetInterceptorSwarms().Count == 0) {
 0376        return;
 377      }
 0378      _targetCentroid = SimManager.Instance.GetSwarmCenter(
 379          SimManager.Instance.GetInterceptorSwarms() [_selectedInterceptorSwarmIndex].ConvertAll(
 0380              tuple => tuple.Item1));
 0381    } else if (cameraMode == CameraMode.FOLLOW_THREAT_SWARM) {
 0382      if (_selectedThreatSwarmIndex == -1) {
 0383        _selectedThreatSwarmIndex = 0;
 0384      }
 0385      if (SimManager.Instance.GetThreatSwarms().Count == 0) {
 0386        return;
 387      }
 0388      _targetCentroid = SimManager.Instance.GetSwarmCenter(
 389          SimManager.Instance.GetThreatSwarms() [_selectedThreatSwarmIndex].ConvertAll(
 0390              tuple => tuple.Item1));
 0391    } else if (cameraMode == CameraMode.FOLLOW_ALL_AGENTS) {
 0392      _targetCentroid = SimManager.Instance.GetAllAgentsCenter();
 0393    }
 394    // Apply IIR filter to adjust interpolation speed
 0395    float distance = Mathf.Abs(Vector3.Distance(_lastCentroid, _targetCentroid));
 0396    float targetSpeed = Mathf.Clamp(distance, 1f, 100000f);
 0397    _currentInterpolationSpeed = _iirFilterCoefficient * _currentInterpolationSpeed +
 398                                 (1 - _iirFilterCoefficient) * targetSpeed;
 0399  }
 400
 0401  IEnumerator AutoPlayRoutine() {
 0402    while (true) {
 0403      float elapsedTime = 0f;
 0404      while (elapsedTime <= xAutoRotateTime) {
 0405        _orbitX += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0406        UpdateCamPosition(_orbitX, _orbitY);
 0407        elapsedTime += Time.unscaledDeltaTime;
 0408        yield return null;
 0409      }
 0410      elapsedTime = 0f;
 0411      while (elapsedTime <= yAutoRotateTime) {
 0412        _orbitY -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0413        UpdateCamPosition(_orbitX, _orbitY);
 0414        elapsedTime += Time.unscaledDeltaTime;
 0415        yield return null;
 0416      }
 0417      elapsedTime = 0f;
 0418      while (elapsedTime <= xAutoRotateTime) {
 0419        _orbitX -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0420        UpdateCamPosition(_orbitX, _orbitY);
 0421        elapsedTime += Time.unscaledDeltaTime;
 0422        yield return null;
 0423      }
 0424      elapsedTime = 0f;
 0425      while (elapsedTime <= yAutoRotateTime) {
 0426        _orbitY += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
 0427        UpdateCamPosition(_orbitX, _orbitY);
 0428        elapsedTime += Time.unscaledDeltaTime;
 0429        yield return null;
 0430      }
 0431      yield return null;
 0432    }
 433  }
 434
 0435  public void SetCameraTargetPosition(Vector3 position) {
 0436    target.transform.position = position;
 0437    UpdateCamPosition(_orbitX, _orbitY);
 0438  }
 439
 0440  void ResetCameraTarget() {
 441    RaycastHit hit;
 0442    if (Physics.Raycast(transform.position, transform.forward, out hit, float.MaxValue,
 0443                        LayerMask.GetMask("Floor"), QueryTriggerInteraction.Ignore)) {
 0444      target.transform.position = hit.point;
 0445      _orbitDistance = hit.distance;
 0446      Vector3 angles = transform.eulerAngles;
 0447      _orbitX = angles.y;
 0448      _orbitY = angles.x;
 0449      UpdateCamPosition(_orbitX, _orbitY);
 0450    } else {
 0451      target.transform.position = transform.position + (transform.forward * 100);
 0452      _orbitDistance = 100;
 0453      Vector3 angles = transform.eulerAngles;
 0454      _orbitX = angles.y;
 0455      _orbitY = angles.x;
 456      // UpdateCamPosition();
 0457    }
 0458  }
 459
 0460  public void EnableTargetRenderer(bool enable) {
 0461    targetRenderer.enabled = enable;
 0462  }
 463
 0464  public void EnableFloorGridRenderer(bool enable) {
 0465    floorRenderer.enabled = enable;
 0466  }
 467
 0468  public void OrbitCamera(float xOrbit, float yOrbit) {
 0469    if (target) {
 0470      _orbitX += xOrbit * _orbitXSpeed * _orbitDistance * 0.02f;
 0471      _orbitY -= yOrbit * _orbitYSpeed * _orbitDistance * 0.02f;
 472
 0473      _orbitY = ClampAngle(_orbitY, orbitYMinLimit, orbitYMaxLimit);
 0474      UpdateCamPosition(_orbitX, _orbitY);
 0475    }
 0476  }
 477
 0478  public void RotateCamera(float xRotate, float yRotate) {
 0479    _yaw += xRotate * _speedH;
 0480    _pitch -= yRotate * _speedV;
 0481    transform.eulerAngles = new Vector3(_pitch, _yaw, 0.0f);
 0482  }
 483
 0484  private void UpdateCamPosition(float x, float y) {
 0485    Quaternion rotation = Quaternion.Euler(y, x, 0);
 486    RaycastHit hit;
 487    // Debug.DrawLine(target.position, transform.position, Color.red);
 0488    if (Physics.Linecast(target.position, transform.position, out hit, ~LayerMask.GetMask("Floor"),
 0489                         QueryTriggerInteraction.Ignore)) {
 0490      _orbitDistance -= hit.distance;
 0491    }
 0492    Vector3 negDistance = new Vector3(0.0f, 0.0f, -_orbitDistance);
 0493    Vector3 position = rotation * negDistance + target.position;
 0494    _orbitDistance = Mathf.Clamp(_orbitDistance, _orbitDistanceMin, _orbitDistanceMax);
 0495    UpdateTargetAlpha();
 496
 0497    SetCameraRotation(rotation);
 0498    transform.position = position;
 0499  }
 500
 0501  public void ZoomCamera(float zoom) {
 0502    _orbitDistance =
 503        Mathf.Clamp(_orbitDistance - zoom * _zoomSpeed, _orbitDistanceMin, _orbitDistanceMax);
 0504    UpdateCamPosition(_orbitX, _orbitY);
 0505  }
 506
 0507  void UpdateTargetAlpha() {
 0508    matAlpha = (_orbitDistance - _orbitDistanceMin) / (_orbitDistanceMax - _orbitDistanceMin);
 0509    matAlpha = Mathf.Max(Mathf.Sqrt(matAlpha) - 0.5f, 0);
 0510    Color matColor = targetRenderer.material.color;
 0511    matColor.a = matAlpha;
 0512    targetRenderer.material.color = matColor;
 0513  }
 514
 0515  void UpdateDirectionVectors() {
 0516    Vector3 cameraToTarget = target.position - transform.position;
 0517    cameraToTarget.y = 0;
 0518    forwardToCameraAngle = Vector3.SignedAngle(Vector3.forward, cameraToTarget, Vector3.down);
 519
 0520    if (forwardToCameraAngle > -45f && forwardToCameraAngle <= 45f) {
 0521      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.forward;
 0522      _translationInputToVectorMap[TranslationInput.Left] = Vector3.left;
 0523      _translationInputToVectorMap[TranslationInput.Back] = Vector3.back;
 0524      _translationInputToVectorMap[TranslationInput.Right] = Vector3.right;
 0525    } else if (forwardToCameraAngle > 45f && forwardToCameraAngle <= 135f) {
 0526      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.left;
 0527      _translationInputToVectorMap[TranslationInput.Left] = Vector3.back;
 0528      _translationInputToVectorMap[TranslationInput.Back] = Vector3.right;
 0529      _translationInputToVectorMap[TranslationInput.Right] = Vector3.forward;
 0530    } else if (forwardToCameraAngle > 135f || forwardToCameraAngle <= -135f) {
 0531      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.back;
 0532      _translationInputToVectorMap[TranslationInput.Left] = Vector3.right;
 0533      _translationInputToVectorMap[TranslationInput.Back] = Vector3.forward;
 0534      _translationInputToVectorMap[TranslationInput.Right] = Vector3.left;
 0535    } else if (forwardToCameraAngle > -135f && forwardToCameraAngle <= -45f) {
 0536      _translationInputToVectorMap[TranslationInput.Forward] = Vector3.right;
 0537      _translationInputToVectorMap[TranslationInput.Left] = Vector3.forward;
 0538      _translationInputToVectorMap[TranslationInput.Back] = Vector3.left;
 0539      _translationInputToVectorMap[TranslationInput.Right] = Vector3.back;
 0540    }
 0541  }
 542
 543  public enum TranslationInput { Forward, Left, Back, Right, Up, Down }
 544
 0545  public void TranslateCamera(TranslationInput input) {
 0546    if (cameraMode != CameraMode.FREE) {
 0547      SetCameraMode(CameraMode.FREE);
 0548    }
 0549    UpdateDirectionVectors();
 0550    target.Translate(_translationInputToVectorMap[input] * Time.unscaledDeltaTime * _cameraSpeed);
 0551    UpdateCamPosition(_orbitX, _orbitY);
 0552  }
 553
 0554  protected void Update() {
 0555    if (cameraMode != CameraMode.FREE) {
 556      // Use MoveTowards for smoother and more predictable movement
 0557      _currentCentroid = Vector3.MoveTowards(_currentCentroid, _targetCentroid,
 558                                             _currentInterpolationSpeed * Time.unscaledDeltaTime);
 0559      SetCameraTargetPosition(_currentCentroid);
 0560    }
 0561  }
 562}
 563
 564public enum CameraMode { FREE, FOLLOW_INTERCEPTOR_SWARM, FOLLOW_THREAT_SWARM, FOLLOW_ALL_AGENTS }