private static double ComputeVisibilityAndDistance(VesselRelayPersistence relay, Vessel targetVessel) { var result = relay.Vessel.HasLineOfSightWith(targetVessel, 0) ? Vector3d.Distance(relay.Vessel.GetVesselPos(), targetVessel.GetVesselPos()) : -1; return(result); }
/// <summary> /// Returns transmitters which this vessel can connect /// </summary> /// <param name="maxHops">Maximum number of relays which can be used for connection to transmitter</param> public static IDictionary <VesselMicrowavePersistence, KeyValuePair <MicrowaveRoute, IList <VesselRelayPersistence> > > GetConnectedTransmitters(IBeamedPowerReceiver receiver, int maxHops = 25) { //these two dictionaries store transmitters and relays and best currently known route to them which is replaced if better one is found. var transmitterRouteDictionary = new Dictionary <VesselMicrowavePersistence, MicrowaveRoute>(); // stores all transmitter we can have a connection with var relayRouteDictionary = new Dictionary <VesselRelayPersistence, MicrowaveRoute>(); var transmittersToCheck = new List <VesselMicrowavePersistence>();//stores all transmitters to which we want to connect var receiverAtmosphericPressure = FlightGlobals.getStaticPressure(receiver.Vessel.GetVesselPos()) / GameConstants.EarthAtmospherePressureAtSeaLevel; foreach (VesselMicrowavePersistence transmitter in BeamedPowerSources.Instance.GlobalTransmitters.Values) { //ignore if no power or transmitter is on the same vessel if (transmitter.Vessel == receiver.Vessel || !transmitter.HasPower) { //Debug.Log("[KSPI] : Transmitter vessel is equal to receiver vessel / Transmitter vessel has no power available"); continue; } if (receiver.Vessel.HasLineOfSightWith(transmitter.Vessel)) { double facingFactor = ComputeFacingFactor(transmitter.Vessel, receiver); if (facingFactor <= 0) { continue; } var possibleWavelengths = new List <MicrowaveRoute>(); var distanceInMeter = ComputeDistance(receiver.Vessel, transmitter.Vessel); var transmitterAtmosphericPressure = FlightGlobals.getStaticPressure(transmitter.Vessel.GetVesselPos()) / GameConstants.EarthAtmospherePressureAtSeaLevel; foreach (var wavelengthData in transmitter.SupportedTransmitWavelengths.Where(wavelengthData => wavelengthData.Wavelength.Within(receiver.MaximumWavelength, receiver.MinimumWavelength))) { var spotSize = ComputeSpotSize(wavelengthData, distanceInMeter, transmitter.Aperture, receiver.ApertureMultiplier); var distanceFacingEfficiency = ComputeDistanceFacingEfficiency(spotSize, facingFactor, receiver.Diameter, receiver.FacingEfficiencyExponent, receiver.SpotSizeNormalizationExponent); var atmosphereEfficiency = GetAtmosphericEfficiency(transmitterAtmosphericPressure, receiverAtmosphericPressure, wavelengthData.AtmosphericAbsorption, distanceInMeter, receiver.Vessel, transmitter.Vessel); var routeEfficiency = distanceFacingEfficiency * atmosphereEfficiency; possibleWavelengths.Add(new MicrowaveRoute(routeEfficiency, distanceInMeter, facingFactor, spotSize, wavelengthData)); } var mostEfficientWavelength = possibleWavelengths.Count == 0 ? null : possibleWavelengths.FirstOrDefault(m => m.Efficiency == possibleWavelengths.Max(n => n.Efficiency)); if (mostEfficientWavelength != null) { //store in dictionary that optimal route to this transmitter is direct connection, can be replaced if better route is found transmitterRouteDictionary[transmitter] = mostEfficientWavelength; } } // add all transmitters that are not located on the receiving vessel transmittersToCheck.Add(transmitter); } //this algorithm processes relays in groups in which elements of the first group must be visible from receiver, //elements from the second group must be visible by at least one element from previous group and so on... var relaysToCheck = new List <VesselRelayPersistence>(); //relays which we have to check - all active relays will be here var currentRelayGroup = new List <KeyValuePair <VesselRelayPersistence, int> >(); //relays which are in line of sight, and we have not yet checked what they can see. Their index in relaysToCheck is also stored int relayIndex = 0; foreach (VesselRelayPersistence relay in BeamedPowerSources.Instance.GlobalRelays.Values) { if (!relay.IsActive) { continue; } if (receiver.Vessel.HasLineOfSightWith(relay.Vessel)) { double facingFactor = ComputeFacingFactor(relay.Vessel, receiver); if (facingFactor <= 0) { continue; } double distanceInMeter = ComputeDistance(receiver.Vessel, relay.Vessel); double transmitterAtmosphericPressure = FlightGlobals.getStaticPressure(relay.Vessel.GetVesselPos()) / GameConstants.EarthAtmospherePressureAtSeaLevel; var possibleWavelengths = new List <MicrowaveRoute>(); foreach (var waveLengthData in relay.SupportedTransmitWavelengths) { if (waveLengthData.MaxWavelength < receiver.MinimumWavelength || waveLengthData.MinWavelength > receiver.MaximumWavelength) { continue; } double spotSize = ComputeSpotSize(waveLengthData, distanceInMeter, relay.Aperture); double distanceFacingEfficiency = ComputeDistanceFacingEfficiency(spotSize, facingFactor, receiver.Diameter, receiver.FacingEfficiencyExponent, receiver.SpotSizeNormalizationExponent); double atmosphereEfficiency = GetAtmosphericEfficiency(transmitterAtmosphericPressure, receiverAtmosphericPressure, waveLengthData.AtmosphericAbsorption, distanceInMeter, receiver.Vessel, relay.Vessel); possibleWavelengths.Add(new MicrowaveRoute(distanceFacingEfficiency * atmosphereEfficiency, distanceInMeter, facingFactor, spotSize, waveLengthData)); } var mostEfficientWavelength = possibleWavelengths.Count == 0 ? null : possibleWavelengths.FirstOrDefault(m => m.Efficiency == possibleWavelengths.Max(n => n.Efficiency)); if (mostEfficientWavelength != null) { //store in dictionary that optimal route to this relay is direct connection, can be replaced if better route is found relayRouteDictionary[relay] = mostEfficientWavelength; currentRelayGroup.Add(new KeyValuePair <VesselRelayPersistence, int>(relay, relayIndex)); } } relaysToCheck.Add(relay); relayIndex++; } int hops = 0; //number of hops between relays //pre-compute distances and visibility thus limiting number of checks to (Nr^2)/2 + NrNt +Nr + Nt if (hops < maxHops && transmittersToCheck.Any()) { double[,] relayToRelayDistances = new double[relaysToCheck.Count, relaysToCheck.Count]; double[,] relayToTransmitterDistances = new double[relaysToCheck.Count, transmittersToCheck.Count]; for (int i = 0; i < relaysToCheck.Count; i++) { var relayToCheck = relaysToCheck[i]; for (int j = i + 1; j < relaysToCheck.Count; j++) { double visibilityAndDistance = ComputeVisibilityAndDistance(relayToCheck, relaysToCheck[j].Vessel); relayToRelayDistances[i, j] = visibilityAndDistance; relayToRelayDistances[j, i] = visibilityAndDistance; } for (int t = 0; t < transmittersToCheck.Count; t++) { relayToTransmitterDistances[i, t] = ComputeVisibilityAndDistance(relayToCheck, transmittersToCheck[t].Vessel); } } HashSet <int> coveredRelays = new HashSet <int>(); //runs as long as there is any relay to which we can connect and maximum number of hops have not been breached while (hops < maxHops && currentRelayGroup.Any()) { var nextRelayGroup = new List <KeyValuePair <VesselRelayPersistence, int> >(); //will put every relay which is in line of sight of any relay from currentRelayGroup here foreach (var relayEntry in currentRelayGroup) //relays visible from receiver in first iteration, then relays visible from them etc.... { VesselRelayPersistence relayPersistence = relayEntry.Key; MicrowaveRoute relayRoute = relayRouteDictionary[relayPersistence]; // current best route for this relay double relayRouteFacingFactor = relayRoute.FacingFactor; // it's always facing factor from the beginning of the route double relayAtmosphericPressure = FlightGlobals.getStaticPressure(relayPersistence.Vessel.GetVesselPos()) / GameConstants.EarthAtmospherePressureAtSeaLevel; for (int t = 0; t < transmittersToCheck.Count; t++)//check if this relay can connect to transmitters { double distanceInMeter = relayToTransmitterDistances[relayEntry.Value, t]; //it's >0 if it can see if (distanceInMeter <= 0) { continue; } VesselMicrowavePersistence transmitterToCheck = transmittersToCheck[t]; double newDistance = relayRoute.Distance + distanceInMeter;// total distance from receiver by this relay to transmitter double transmitterAtmosphericPressure = FlightGlobals.getStaticPressure(transmitterToCheck.Vessel.GetVesselPos()) / GameConstants.EarthAtmospherePressureAtSeaLevel; var possibleWavelengths = new List <MicrowaveRoute>(); foreach (var transmitterWavelengthData in transmitterToCheck.SupportedTransmitWavelengths) { if (transmitterWavelengthData.Wavelength.NotWithin(relayPersistence.MaximumRelayWavelength, relayPersistence.MinimumRelayWavelength)) { continue; } double spotSize = ComputeSpotSize(transmitterWavelengthData, distanceInMeter, transmitterToCheck.Aperture); double distanceFacingEfficiency = ComputeDistanceFacingEfficiency(spotSize, 1, relayPersistence.Diameter); double atmosphereEfficiency = GetAtmosphericEfficiency(transmitterAtmosphericPressure, relayAtmosphericPressure, transmitterWavelengthData.AtmosphericAbsorption, distanceInMeter, transmitterToCheck.Vessel, relayPersistence.Vessel); double efficiencyTransmitterToRelay = distanceFacingEfficiency * atmosphereEfficiency; double efficiencyForRoute = efficiencyTransmitterToRelay * relayRoute.Efficiency; possibleWavelengths.Add(new MicrowaveRoute(efficiencyForRoute, newDistance, relayRouteFacingFactor, spotSize, transmitterWavelengthData, relayPersistence)); } var mostEfficientWavelength = possibleWavelengths.Count == 0 ? null : possibleWavelengths.FirstOrDefault(m => m.Efficiency == possibleWavelengths.Max(n => n.Efficiency)); if (mostEfficientWavelength == null) { continue; } //this will return true if there is already a route to this transmitter if (transmitterRouteDictionary.TryGetValue(transmitterToCheck, out MicrowaveRoute currentOptimalRoute)) { if (currentOptimalRoute.Efficiency < mostEfficientWavelength.Efficiency) { //if route using this relay is better then replace the old route transmitterRouteDictionary[transmitterToCheck] = mostEfficientWavelength; } } else { //there is no other route to this transmitter yet known so algorithm puts this one as optimal transmitterRouteDictionary[transmitterToCheck] = mostEfficientWavelength; } } for (var r = 0; r < relaysToCheck.Count; r++) { VesselRelayPersistence nextRelay = relaysToCheck[r]; if (nextRelay == relayPersistence) { continue; } double distanceToNextRelay = relayToRelayDistances[relayEntry.Value, r]; if (distanceToNextRelay <= 0) { continue; } var possibleWavelengths = new List <MicrowaveRoute>(); double relayToNextRelayDistance = relayRoute.Distance + distanceToNextRelay; foreach (var transmitterWavelengthData in relayPersistence.SupportedTransmitWavelengths) { if (transmitterWavelengthData.MaxWavelength < relayPersistence.MaximumRelayWavelength || transmitterWavelengthData.MinWavelength > relayPersistence.MinimumRelayWavelength) { continue; } double spotSize = ComputeSpotSize(transmitterWavelengthData, distanceToNextRelay, nextRelay.Aperture); double efficiencyByThisRelay = ComputeDistanceFacingEfficiency(spotSize, 1, relayPersistence.Diameter); double efficiencyForRoute = efficiencyByThisRelay * relayRoute.Efficiency; possibleWavelengths.Add(new MicrowaveRoute(efficiencyForRoute, relayToNextRelayDistance, relayRouteFacingFactor, spotSize, transmitterWavelengthData, relayPersistence)); } MicrowaveRoute mostEfficientWavelength = possibleWavelengths.Count == 0 ? null : possibleWavelengths.FirstOrDefault(m => m.Efficiency == possibleWavelengths.Max(n => n.Efficiency)); if (mostEfficientWavelength != null) { if (relayRouteDictionary.TryGetValue(nextRelay, out MicrowaveRoute currentOptimalPredecessor)) //this will return true if there is already a route to next relay { //if route using this relay is better if (currentOptimalPredecessor.Efficiency < mostEfficientWavelength.Efficiency) { //we put it in dictionary as optimal relayRouteDictionary[nextRelay] = mostEfficientWavelength; } } else //there is no other route to this relay yet known so we put this one as optimal { relayRouteDictionary[nextRelay] = mostEfficientWavelength; } if (!coveredRelays.Contains(r)) { nextRelayGroup.Add(new KeyValuePair <VesselRelayPersistence, int>(nextRelay, r)); //in next iteration we will check what next relay can see coveredRelays.Add(r); } } } } currentRelayGroup = nextRelayGroup; //we don't have to check old relays so we just replace whole List hops++; } } //building final result var resultDictionary = new Dictionary <VesselMicrowavePersistence, KeyValuePair <MicrowaveRoute, IList <VesselRelayPersistence> > >(); foreach (var transmitterEntry in transmitterRouteDictionary) { VesselMicrowavePersistence vesselPersistence = transmitterEntry.Key; MicrowaveRoute microwaveRoute = transmitterEntry.Value; var relays = new Stack <VesselRelayPersistence>();//Last in, first out so relay visible from receiver will always be first VesselRelayPersistence relay = microwaveRoute.PreviousRelay; while (relay != null) { relays.Push(relay); relay = relayRouteDictionary[relay].PreviousRelay; } resultDictionary.Add(vesselPersistence, new KeyValuePair <MicrowaveRoute, IList <VesselRelayPersistence> >(microwaveRoute, relays.ToList())); } return(resultDictionary); }