Skip to content

Commit

Permalink
Merge pull request #34 from PisterLab/titan/reclustering
Browse files Browse the repository at this point in the history
[IADS] Recluster missed and escaping threats
  • Loading branch information
tryuan99 authored Jan 31, 2025
2 parents 5906e37 + 49887ec commit f809120
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 67 deletions.
171 changes: 141 additions & 30 deletions Assets/Scripts/IADS/IADS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
public class IADS : MonoBehaviour {
public static IADS Instance { get; private set; }

private const float LaunchInterceptorsPeriod = 0.4f;
private const float CheckForEscapingThreatsPeriod = 5.0f;
private const float ClusterThreatsPeriod = 2.0f;

// TODO(titan): Choose the CSV file based on the interceptor type.
private ILaunchAnglePlanner _launchAnglePlanner =
new LaunchAngleCsvInterpolator(Path.Combine("Planning", "hydra70_launch_angle.csv"));
private IAssignment _assignmentScheme = new MaxSpeedAssignment();
private Coroutine _launchInterceptorsCoroutine;

[SerializeField]
private List<TrackFileData> _trackFiles = new List<TrackFileData>();
Expand All @@ -26,6 +31,10 @@ public class IADS : MonoBehaviour {
new Dictionary<Interceptor, Cluster>();
private HashSet<Interceptor> _assignableInterceptors = new HashSet<Interceptor>();

private HashSet<Threat> _threatsToCluster = new HashSet<Threat>();
private Coroutine _checkForEscapingThreatsCoroutine;
private Coroutine _clusterThreatsCoroutine;

private int _trackFileIdTicker = 0;

private void Awake() {
Expand All @@ -36,6 +45,18 @@ private void Awake() {
}
}

private void OnDestroy() {
if (_launchInterceptorsCoroutine != null) {
StopCoroutine(_launchInterceptorsCoroutine);
}
if (_checkForEscapingThreatsCoroutine != null) {
StopCoroutine(_checkForEscapingThreatsCoroutine);
}
if (_clusterThreatsCoroutine != null) {
StopCoroutine(_clusterThreatsCoroutine);
}
}

public void Start() {
SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted;
SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
Expand All @@ -46,6 +67,7 @@ public void Start() {
public void LateUpdate() {
// Update the cluster centroids.
foreach (var cluster in _threatClusters) {
cluster.Recenter();
_threatClusterMap[cluster].UpdateCentroid();
}

Expand All @@ -55,39 +77,40 @@ public void LateUpdate() {
}

private void RegisterSimulationStarted() {
// Cluster the threats.
ClusterThreats(SimManager.Instance.GetActiveThreats());
_launchInterceptorsCoroutine =
StartCoroutine(LaunchInterceptorsManager(LaunchInterceptorsPeriod));
_checkForEscapingThreatsCoroutine =
StartCoroutine(CheckForEscapingThreatsManager(CheckForEscapingThreatsPeriod));
_clusterThreatsCoroutine = StartCoroutine(ClusterThreatsManager(ClusterThreatsPeriod));
}

// Cluster the threats.
public void ClusterThreats(List<Threat> threats) {
// Maximum number of threats per cluster.
const int MaxSize = 7;
// Maximum cluster radius in meters.
const float MaxRadius = 500;

// Cluster to threats.
IClusterer clusterer = new AgglomerativeClusterer(new List<Agent>(threats), MaxSize, MaxRadius);
clusterer.Cluster();
var clusters = clusterer.Clusters;
Debug.Log($"[IADS] Clustered {threats.Count} threats into {clusters.Count} clusters.");
UIManager.Instance.LogActionMessage(
$"[IADS] Clustered {threats.Count} threats into {clusters.Count} clusters.");

_threatClusters = clusters.ToList();
foreach (var cluster in clusters) {
_threatClusterMap.Add(cluster, new ThreatClusterData(cluster));
private IEnumerator LaunchInterceptorsManager(float period) {
while (true) {
// Check whether an interceptor should be launched at a cluster and launch it.
CheckAndLaunchInterceptors();
yield return new WaitForSeconds(period);
}
}

// Check whether an interceptor should be launched at a cluster and launch it.
public void CheckAndLaunchInterceptors() {
private void CheckAndLaunchInterceptors() {
foreach (var cluster in _threatClusters) {
// Check whether an interceptor has already been assigned to the cluster.
if (_threatClusterMap[cluster].Status != ThreatClusterStatus.UNASSIGNED) {
continue;
}

// Check whether all threats in the cluster have terminated.
bool allTerminated = true;
foreach (var threat in cluster.Threats) {
if (!threat.IsTerminated()) {
allTerminated = false;
break;
}
}
if (allTerminated) {
continue;
}

// Create a predictor to track the cluster's centroid.
IPredictor predictor = new LinearExtrapolator(_threatClusterMap[cluster].Centroid);

Expand Down Expand Up @@ -195,16 +218,15 @@ public void RequestAssignInterceptorToThreat(Interceptor interceptor) {
_assignableInterceptors.Add(interceptor);
}

public void AssignInterceptorToThreat(in IReadOnlyList<Interceptor> interceptors) {
private void AssignInterceptorToThreat(in IReadOnlyList<Interceptor> interceptors) {
if (interceptors.Count == 0) {
return;
}

// The threat originally assigned to the interceptor has been terminated, so assign another
// threat to the interceptor.
// TODO: We do not use clusters after the first assignment, we should consider re-clustering.

// This pulls from ALL available track files, not from our previously assigned cluster.
// This pulls from all available track files, not from our previously assigned cluster.
List<Threat> threats = _trackFiles.Where(trackFile => trackFile.Agent is Threat)
.Select(trackFile => trackFile.Agent as Threat)
.ToList();
Expand All @@ -222,18 +244,100 @@ public void AssignInterceptorToThreat(in IReadOnlyList<Interceptor> interceptors
}
}

public void RequestClusterThreat(Threat threat) {
_threatsToCluster.Add(threat);
}

private IEnumerator CheckForEscapingThreatsManager(float period) {
while (true) {
yield return new WaitForSeconds(period);
CheckForEscapingThreats();
}
}

private void CheckForEscapingThreats() {
List<Threat> threats = _trackFiles
.Where(trackFile => trackFile.Status == TrackStatus.ASSIGNED &&
trackFile.Agent is Threat)
.Select(trackFile => trackFile.Agent as Threat)
.ToList();
if (threats.Count == 0) {
return;
}

// Check whether the threats are escaping the pursuing interceptors.
foreach (var threat in threats) {
bool isEscaping = true;
foreach (var interceptor in threat.AssignedInterceptors) {
Vector3 interceptorPosition = interceptor.GetPosition();
Vector3 threatPosition = threat.GetPosition();

float threatTimeToHit = (float)(threatPosition.magnitude / threat.GetSpeed());
float interceptorTimeToHit =
(float)((threatPosition - interceptorPosition).magnitude / interceptor.GetSpeed());
if (interceptorPosition.magnitude < threatPosition.magnitude &&
threatTimeToHit > interceptorTimeToHit) {
isEscaping = false;
break;
}
}
if (isEscaping) {
RequestClusterThreat(threat);
}
}
}

private IEnumerator ClusterThreatsManager(float period) {
while (true) {
ClusterThreats();
yield return new WaitForSeconds(period);
}
}

private void ClusterThreats() {
// Maximum number of threats per cluster.
const int MaxSize = 7;
// Maximum cluster radius in meters.
const float MaxRadius = 500;

// Filter the threats.
List<Threat> threats =
_threatsToCluster
.Where(threat => !threat.IsTerminated() && threat.AssignedInterceptors.Count == 0)
.ToList();
if (threats.Count == 0) {
return;
}

// Cluster threats.
IClusterer clusterer = new AgglomerativeClusterer(new List<Agent>(threats), MaxSize, MaxRadius);
clusterer.Cluster();
var clusters = clusterer.Clusters;
Debug.Log($"Clustered {threats.Count} threats into {clusters.Count} clusters.");
UIManager.Instance.LogActionMessage(
$"[IADS] Clustered {threats.Count} threats into {clusters.Count} clusters.");

_threatClusters = clusters.ToList();
foreach (var cluster in clusters) {
_threatClusterMap.Add(cluster, new ThreatClusterData(cluster));
}

_threatsToCluster.Clear();
}

public void RegisterNewThreat(Threat threat) {
string trackID = $"T{1000 + _trackFileIdTicker++}";
string trackID = $"T{1000 + ++_trackFileIdTicker}";
ThreatData trackFile = new ThreatData(threat, trackID);
_trackFiles.Add(trackFile);
_trackFileMap.Add(threat, trackFile);
RequestClusterThreat(threat);

threat.OnThreatHit += RegisterThreatHit;
threat.OnThreatMiss += RegisterThreatMiss;
}

public void RegisterNewInterceptor(Interceptor interceptor) {
string trackID = $"I{2000 + _trackFileIdTicker++}";
string trackID = $"I{2000 + ++_trackFileIdTicker}";
InterceptorData trackFile = new InterceptorData(interceptor, trackID);
_trackFiles.Add(trackFile);
_trackFileMap.Add(interceptor, trackFile);
Expand Down Expand Up @@ -271,6 +375,11 @@ private void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {

if (threatTrack != null) {
threatTrack.RemoveInterceptor(interceptor);

// Check if the threat is being targeted by at least one interceptor.
if (threatTrack.AssignedInterceptorCount == 0) {
RequestClusterThreat(threat);
}
}

if (interceptorTrack != null) {
Expand Down Expand Up @@ -310,12 +419,14 @@ public List<InterceptorData> GetInterceptorTracks() =>
_trackFiles.OfType<InterceptorData>().ToList();

private void RegisterSimulationEnded() {
_assignableInterceptors.Clear();
_trackFiles.Clear();
_threatClusters.Clear();
_threatClusterMap.Clear();
_trackFileMap.Clear();
_assignmentQueue.Clear();
_threatClusters.Clear();
_threatClusterMap.Clear();
_interceptorClusterMap.Clear();
_assignableInterceptors.Clear();
_threatsToCluster.Clear();
_trackFileIdTicker = 0;
}
}
19 changes: 9 additions & 10 deletions Assets/Scripts/IADS/TrackFileData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,51 +35,50 @@ public virtual void MarkDestroyed() {

[System.Serializable]
public class ThreatData : TrackFileData {
public int assignedInterceptorCount = 0;
[SerializeField]
private List<Interceptor> _assignedInterceptors = new List<Interceptor>();

public ThreatData(Threat threat, string trackID) : base(threat, trackID) {}

public int AssignedInterceptorCount {
get { return _assignedInterceptors.Count; }
}

public void AssignInterceptor(Interceptor interceptor) {
if (Status == TrackStatus.DESTROYED) {
Debug.LogError($"AssignInterceptor: Track {TrackID} is destroyed, cannot assign interceptor");
Debug.LogError(
$"AssignInterceptor: Track {TrackID} is destroyed, cannot assign interceptor.");
return;
}
_status = TrackStatus.ASSIGNED;
_assignedInterceptors.Add(interceptor);
assignedInterceptorCount++;
}
public void RemoveInterceptor(Interceptor interceptor) {
if (_assignedInterceptors.Contains(interceptor)) {
_assignedInterceptors.Remove(interceptor);
if (_assignedInterceptors.Count == 0) {
_status = TrackStatus.UNASSIGNED;
}
assignedInterceptorCount--;
}
}

public override void MarkDestroyed() {
base.MarkDestroyed();
_assignedInterceptors.Clear();
assignedInterceptorCount = 0;
}
}

[System.Serializable]
public class InterceptorData : TrackFileData {
[SerializeField]
private List<Threat> _assignedThreats;
private List<Threat> _assignedThreats = new List<Threat>();

public InterceptorData(Interceptor interceptor, string interceptorID)
: base(interceptor, interceptorID) {
_assignedThreats = new List<Threat>();
}
: base(interceptor, interceptorID) {}

public void AssignThreat(Threat threat) {
if (_status == TrackStatus.DESTROYED) {
Debug.LogError($"AssignThreat: Interceptor {TrackID} is destroyed, cannot assign threat");
Debug.LogError($"AssignThreat: Interceptor {TrackID} is destroyed, cannot assign threat.");
return;
}
_status = TrackStatus.ASSIGNED;
Expand Down
3 changes: 0 additions & 3 deletions Assets/Scripts/SimManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -589,9 +589,6 @@ void FixedUpdate() {
RestartSimulation();
Debug.Log("Simulation completed.");
}

// Check whether to launch the interceptors. If so, create and launch the interceptor.
IADS.Instance.CheckAndLaunchInterceptors();
}

public void QuitSimulation() {
Expand Down
Loading

0 comments on commit f809120

Please sign in to comment.