/// <summary> /// Find all Systems within range of the specified system. /// </summary> /// <param name="origin">The reference system.</param> /// <param name="range">The range to include.</param> /// <returns>A list of all Systems that are within range of the origin.</returns> public IEnumerable <KeyValuePair <string, EDSystem> > FindInRange(EDSystem origin, double range) { // We only need to consider systems within the same box as the origin, and neighbouring boxes up to the jump range away. // One possible optimisation here is to calculate the boxes within the spherical range, but I suspect that for all but the // longest routes, the constant circle-maths would cost more than it gains. Something to try out sometime. // Find out how many boxes away from the origin we need to inspect. var boxRange = ((int)range / BoxSize) + 1; // Build up a list of boxes that might contain reachable systems. These are the boxes we'll be searching later, so the smaller // we can make this list the better, and the fewer the number of systems in the boxes the better too. var boxes = new List <BoxKey>(); for (int x = origin.box.X - boxRange; x <= origin.box.X + boxRange; x++) { for (int z = origin.box.Z - boxRange; z <= origin.box.Z + boxRange; z++) { var key = new BoxKey(x, z); if (_BoxedList.ContainsKey(key)) { boxes.Add(key); } } } // Spin through every box in our search-space, and add any systems that are actually within range to the result list. // This can be done concurrently, because each system is independent. var systems = new ConcurrentDictionary <string, EDSystem>(); Parallel.ForEach(boxes, (box) => { // We're doing a faster box-based range check first, so we don't have to do an expensive √((x2-x1)² + (y2-y1)² + (z2-z1)²) calculation. foreach (var system in _BoxedList[box].Where(x => x.Value != origin && Math.Abs(x.Value.x - origin.x) <= range && Math.Abs(x.Value.y - origin.y) <= range && Math.Abs(x.Value.z - origin.z) <= range).ToList()) { // Now we have a list of systems that have their X/Y/Z coordinates all within ±jump of the origin, find out which really are in range. // (Because the corners of the cube will be further from the centre than 1 jump-range) if (Astrogation.Distance(origin, system.Value) <= range) { systems[system.Key] = system.Value; } } }); // We now should have a simple list of Systems within the specified range of the provided origin system. return(systems); }
public List <EDSystem> Route(EDSystem start, EDSystem end) { if (JumpRange == 0) { throw new InvalidOperationException("Jump range has not been set."); } var key = new CacheKey() { JumpRange = JumpRange, Start = start.key, End = end.key }; if (_routeCache.ContainsKey(key)) { Console.WriteLine($"Found a cached route from {start.name} to {end.name} in {JumpRange:n2} Ly jumps."); LastResultWasFromCache = true; return(_routeCache[key]); } else { LastResultWasFromCache = false; } var swTotal = new Stopwatch(); var swSort = new Stopwatch(); var swPriority = new Stopwatch(); var swNeighbours = new Stopwatch(); swTotal.Start(); var edsm = EDSystemManager.Instance; _frontier = new SimplePriorityQueue <EDSystem>(); _cameFrom = new Dictionary <string, RouteNode>(); _costSoFar = new Dictionary <string, RouteNode>(); _frontier.Enqueue(start, 0); _costSoFar.Add(start.key, new RouteNode() { System = start, Distance = 0, Priority = 0 }); var routeFound = false; while (_frontier.Count > 0) { var current = _frontier.Dequeue(); if (current == end) { routeFound = true; break; } swNeighbours.Start(); var neighbours = edsm.FindInRange(current, JumpRange).ToList(); swNeighbours.Stop(); // sort neighbours to put those closest to the target first swSort.Start(); neighbours.OrderBy(x => Astrogation.Distance(x.Value, end)); swSort.Stop(); foreach (var kvNext in neighbours) { var next = kvNext.Value; var new_cost = _costSoFar[current.key].Priority + 1; if (_costSoFar.ContainsKey(next.key) == false || new_cost < _costSoFar[next.key].Priority) { if (_costSoFar.ContainsKey(next.key)) { var cost = _costSoFar[next.key]; cost.Priority = new_cost; } else { _costSoFar.Add(next.key, new RouteNode() { System = next, Priority = new_cost }); } var node = new RouteNode() { System = current, Distance = Astrogation.Distance(current, next) }; swPriority.Start(); var priority = new_cost + Astrogation.Distance(next, end); swPriority.Stop(); _frontier.Enqueue(next, priority); _cameFrom[next.key] = node; } } } if (DebugDumpRouteGraphs) { DumpSearchSpaceGraph(start, end, _cameFrom, _costSoFar); } var path = new List <EDSystem>(); if (routeFound || AcceptPartialRoutes) { var c = end; while (c != start) { path.Add(c); if (_cameFrom.ContainsKey(c.key) == false) { // This should only happen if there is no route to the requested destination. break; } c = _cameFrom[c.key].System; } path.Reverse(); } _routeCache.Add(key, path); swTotal.Stop(); // Console.WriteLine($"total:{swTotal.ElapsedMilliseconds}, sort:{swSort.ElapsedMilliseconds}, priority:{swPriority.ElapsedMilliseconds}, neigh:{swNeighbours.ElapsedMilliseconds}"); return(path); }