ARMapRenderer: Clean up line renderers when the map is closed.
[AntennaRange.git] / ARMapRenderer.cs
blob:a/ARMapRenderer.cs -> blob:b/ARMapRenderer.cs
// AntennaRange // AntennaRange
// //
// ARMapRenderer.cs // ARMapRenderer.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class ARMapRenderer : MonoBehaviour public class ARMapRenderer : MonoBehaviour
{ {
#region Fields #region Fields
private Dictionary<Guid, LineRenderer> vesselLineRenderers; private Dictionary<Guid, LineRenderer> vesselLineRenderers;
private Dictionary<Guid, bool> vesselFrameCache; private Dictionary<Guid, bool> vesselFrameCache;
#endregion #endregion
   
#region Properties #region Properties
public LineRenderer this[Guid idx] public LineRenderer this[Guid idx]
{ {
get get
{ {
if (this.vesselLineRenderers == null) if (this.vesselLineRenderers == null)
{ {
this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>(); this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
} }
   
if (!this.vesselLineRenderers.ContainsKey(idx)) if (!this.vesselLineRenderers.ContainsKey(idx))
{ {
GameObject obj = new GameObject(); GameObject obj = new GameObject();
obj.layer = 31; obj.layer = 31;
   
LineRenderer lr = obj.AddComponent<LineRenderer>(); LineRenderer lr = obj.AddComponent<LineRenderer>();
   
lr.SetColors(Color.green, Color.green); lr.SetColors(Color.green, Color.green);
lr.material = new Material(Shader.Find("Particles/Additive")); lr.material = new Material(Shader.Find("Particles/Additive"));
lr.SetVertexCount(2); lr.SetVertexCount(2);
   
this.vesselLineRenderers[idx] = lr; this.vesselLineRenderers[idx] = lr;
} }
   
return this.vesselLineRenderers[idx]; return this.vesselLineRenderers[idx];
} }
} }
#endregion #endregion
   
#region MonoBehaviour Lifecycle #region MonoBehaviour Lifecycle
private void Awake() private void Awake()
{ {
this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>(); this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
this.vesselFrameCache = new Dictionary<Guid, bool>(); this.vesselFrameCache = new Dictionary<Guid, bool>();
} }
   
private void OnPreCull() private void OnPreCull()
{ {
if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled) if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled)
{ {
  this.Cleanup();
   
return; return;
} }
   
Tools.DebugLogger log = Tools.DebugLogger.New(this); Tools.DebugLogger log = Tools.DebugLogger.New(this);
   
try try
{ {
log.AppendFormat("OnPreCull.\n"); log.AppendFormat("OnPreCull.\n");
   
log.AppendFormat("\tMapView: Draw3DLines: {0}\n" + log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
"\tMapView.MapCamera.camera.fieldOfView: {1}\n" + "\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
"\tMapView.MapCamera.Distance: {2}\n", "\tMapView.MapCamera.Distance: {2}\n",
MapView.Draw3DLines, MapView.Draw3DLines,
MapView.MapCamera.camera.fieldOfView, MapView.MapCamera.camera.fieldOfView,
MapView.MapCamera.Distance MapView.MapCamera.Distance
); );
   
this.vesselFrameCache.Clear(); this.vesselFrameCache.Clear();
   
log.AppendLine("vesselFrameCache cleared."); log.AppendLine("vesselFrameCache cleared.");
   
if (FlightGlobals.ready && FlightGlobals.Vessels != null) if (FlightGlobals.ready && FlightGlobals.Vessels != null)
{ {
log.AppendLine("FlightGlobals ready and Vessels list not null."); log.AppendLine("FlightGlobals ready and Vessels list not null.");
   
foreach (Vessel vessel in FlightGlobals.Vessels) foreach (Vessel vessel in FlightGlobals.Vessels)
{ {
if (vessel == null) if (vessel == null)
{ {
log.AppendFormat("Skipping vessel {0} altogether because it is null.\n"); log.AppendFormat("Skipping vessel {0} altogether because it is null.\n");
continue; continue;
} }
   
log.AppendFormat("Checking vessel {0}.\n", vessel.vesselName); log.AppendFormat("Checking vessel {0}.\n", vessel.vesselName);
   
switch (vessel.vesselType) switch (vessel.vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
case VesselType.EVA: case VesselType.EVA:
case VesselType.Unknown: case VesselType.Unknown:
case VesselType.SpaceObject: case VesselType.SpaceObject:
log.AppendFormat("\tDiscarded because vessel is of invalid type {0}\n", log.AppendFormat("\tDiscarded because vessel is of invalid type {0}\n",
vessel.vesselType); vessel.vesselType);
continue; continue;
} }
   
log.Append("\tChecking connection status...\n"); log.Append("\tChecking connection status...\n");
   
if (vessel.HasConnectedRelay()) if (vessel.HasConnectedRelay())
{ {
log.AppendLine("\tHas a connection, checking for the best relay to use for the line."); log.AppendLine("\tHas a connection, checking for the best relay to use for the line.");
   
IAntennaRelay vesselRelay = null; IAntennaRelay vesselRelay = null;
float bestScore = float.PositiveInfinity; float bestScore = float.PositiveInfinity;
float relayScore = float.NaN; float relayScore = float.NaN;
   
foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values) foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values)
{ {
relayScore = (float)relay.transmitDistance / relay.maxTransmitDistance; relayScore = (float)relay.transmitDistance / relay.maxTransmitDistance;
   
if (relayScore < bestScore) if (relayScore < bestScore)
{ {
bestScore = relayScore; bestScore = relayScore;
vesselRelay = relay as IAntennaRelay; vesselRelay = relay as IAntennaRelay;
} }
} }
   
if (vesselRelay != null) if (vesselRelay != null)
{ {
log.AppendFormat("\t...picked relay {0} with a score of {1}", log.AppendFormat("\t...picked relay {0} with a score of {1}",
vesselRelay, relayScore vesselRelay, relayScore
); );
   
this.SetRelayVertices(vesselRelay); this.SetRelayVertices(vesselRelay);
} }
} }
else if (this.vesselLineRenderers.ContainsKey(vessel.id)) else if (this.vesselLineRenderers.ContainsKey(vessel.id))
{ {
log.AppendLine("\tDisabling line because vessel has no connection."); log.AppendLine("\tDisabling line because vessel has no connection.");
this[vessel.id].enabled = false; this[vessel.id].enabled = false;
} }
} }
} }
} }
finally finally
{ {
log.Print(); log.Print();
} }
} }
   
private void OnDestroy() private void OnDestroy()
{ {
this.vesselLineRenderers.Clear(); this.Cleanup();
this.vesselLineRenderers = null;  
print("ARMapRenderer: Destroyed."); print("ARMapRenderer: Destroyed.");
} }
#endregion #endregion
   
private void SetRelayVertices(IAntennaRelay relay) private void SetRelayVertices(IAntennaRelay relay)
{ {
do do
{ {
if (this.vesselFrameCache.ContainsKey(relay.vessel.id)) if (this.vesselFrameCache.ContainsKey(relay.vessel.id))
{ {
break; break;
} }
   
LineRenderer renderer = this[relay.vessel.id]; LineRenderer renderer = this[relay.vessel.id];
   
if (relay.CanTransmit()) if (relay.CanTransmit())
{ {
Vector3d start; Vector3d start;
Vector3d end; Vector3d end;
   
renderer.enabled = true; renderer.enabled = true;
   
if (relay.transmitDistance < relay.nominalTransmitDistance) if (relay.transmitDistance < relay.nominalTransmitDistance)
{ {
renderer.SetColors(Color.green, Color.green); renderer.SetColors(Color.green, Color.green);
} }
else else
{ {
renderer.SetColors(Color.yellow, Color.yellow); renderer.SetColors(Color.yellow, Color.yellow);
} }
   
start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D()); start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
   
if (relay.nearestRelay == null) if (relay.nearestRelay == null)
{ {
end = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position); end = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
} }
else else
{ {
end = ScaledSpace.LocalToScaledSpace(relay.nearestRelay.vessel.GetWorldPos3D()); end = ScaledSpace.LocalToScaledSpace(relay.nearestRelay.vessel.GetWorldPos3D());
} }
   
float lineWidth; float lineWidth;
   
if (MapView.Draw3DLines) if (MapView.Draw3DLines)
{ {
lineWidth = 0.004f * MapView.MapCamera.Distance; lineWidth = 0.004f * MapView.MapCamera.Distance;
} }
else else
{ {
lineWidth = 1f; lineWidth = 1f;
   
start = MapView.MapCamera.camera.WorldToScreenPoint(start); start = MapView.MapCamera.camera.WorldToScreenPoint(start);
end = MapView.MapCamera.camera.WorldToScreenPoint(end); end = MapView.MapCamera.camera.WorldToScreenPoint(end);
   
float d = Screen.height / 2f + 0.01f; float d = Screen.height / 2f + 0.01f;
start.z = start.z >= 0f ? d : -d; start.z = start.z >= 0f ? d : -d;
end.z = end.z >= 0f ? d : -d; end.z = end.z >= 0f ? d : -d;
} }
   
renderer.SetWidth(lineWidth, lineWidth); renderer.SetWidth(lineWidth, lineWidth);
   
renderer.SetPosition(0, start); renderer.SetPosition(0, start);
renderer.SetPosition(1, end); renderer.SetPosition(1, end);
   
this.vesselFrameCache[relay.vessel.id] = true; this.vesselFrameCache[relay.vessel.id] = true;
   
relay = relay.nearestRelay; relay = relay.nearestRelay;
} }
} }
while (relay != null); while (relay != null);
} }
   
  public void Cleanup()
  {
  foreach (LineRenderer lineRenderer in this.vesselLineRenderers.Values)
  {
  lineRenderer.enabled = false;
  GameObject.Destroy(lineRenderer.gameObject);
  }
  this.vesselLineRenderers.Clear();
  this.vesselFrameCache.Clear();
  }
} }
} }