View Javadoc

1   package nl.tudelft.goal.unreal.floydwarshall;
2   
3   import java.util.ArrayList;
4   import java.util.HashMap;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.logging.Level;
8   import java.util.logging.Logger;
9   
10  import javax.vecmath.Point3d;
11  
12  import cz.cuni.amis.pogamut.base.agent.navigation.IPathFuture;
13  import cz.cuni.amis.pogamut.base.agent.navigation.IPathPlanner;
14  import cz.cuni.amis.pogamut.base.agent.navigation.impl.PrecomputedPathFuture;
15  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
16  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.NavigationGraphBuilder;
17  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
18  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
19  import cz.cuni.amis.pogamut.ut2004.communication.translator.shared.events.MapPointListObtained;
20  import cz.cuni.amis.pogamut.ut2004.utils.LinkFlag;
21  import cz.cuni.amis.utils.collections.MyCollections;
22  
23  /**
24   * Private map using Floyd-Warshall for path-finding.
25   * <p><p>
26   * It should be initialized upon receiving {@link MapPointListObtained} event.
27   * It precomputes all the paths inside the environment using Floyd-Warshall
28   * algorithm (O(n^3)). Use getReachable(), getDistance(), getPath() to obtain
29   * the info about the path.
30   * <p><p>
31   * If needed you may call {@link FloydWarshallMap#refreshPathMatrix()} to
32   * recompute Floyd-Warshall. Especially useful if you're using
33   * {@link NavigationGraphBuilder} to modify the underlying navpoints/edges.
34   * <p><p>
35   * Based upon the implementation from Martin Krulis with his kind consent -
36   * Thank you!
37   * <p><p>
38   * NOTE: requires O(navpoints.size^3) of memory ~ which is 10000^3 at max for
39   * UT2004 (usually the biggest maps have 3000 navpoints max, but small maps,
40   * e.g., DM-1on1-Albatross has 200 navpoints).
41   * <p><p>
42   *
43   * @author Martin Krulis
44   * @author Jimmy
45   * @author mpkorstanje
46   */
47  public class FloydWarshallMap implements IPathPlanner<NavPoint> {
48  
49      public static class PathMatrixNode {
50  
51          private float distance = Float.POSITIVE_INFINITY;
52          private Integer viaNode = null;
53          private List<NavPoint> path = null;
54  
55          /**
56           * Doesn't leading anywhere
57           */
58          public PathMatrixNode() {
59          }
60  
61          public PathMatrixNode(float distance) {
62              this.distance = distance;
63          }
64  
65          public float getDistance() {
66              return distance;
67          }
68  
69          public void setDistance(float distance) {
70              this.distance = distance;
71          }
72  
73          /**
74           * Returns indice.
75           *
76           * @return
77           */
78          public Integer getViaNode() {
79              return viaNode;
80          }
81  
82          public void setViaNode(Integer indice) {
83              this.viaNode = indice;
84          }
85  
86          public List<NavPoint> getPath() {
87              return path;
88          }
89  
90          public void setPath(List<NavPoint> path) {
91              this.path = path;
92          }
93      }
94      /**
95       * Flag mask representing unusable edge.
96       */
97      public static final int BAD_EDGE_FLAG = LinkFlag.FLY.get()
98              | LinkFlag.LADDER.get() | LinkFlag.PROSCRIBED.get()
99              | LinkFlag.SWIM.get() | LinkFlag.PLAYERONLY.get();
100 
101     public static boolean isWalkable(int flag) {
102         return ((flag & BAD_EDGE_FLAG) == 0) && ((flag & LinkFlag.SPECIAL.get()) == 0);
103     }
104     /**
105      * Prohibited edges.
106      */
107     protected int badEdgeFlag = 0;
108     /**
109      * Hash table converting navPoint IDs to our own indices.
110      */
111     protected Map<UnrealId, Integer> navPointIndices;
112     /**
113      * Mapping indices to nav points.
114      * <p>
115      * <p>
116      * WILL BE NULL AFTER CONSTRUCTION! SERVES AS A TEMPORARY "GLOBAL VARIABLE"
117      * DURING FLOYD-WARSHALL COMPUTATION AND PATH RECONSTRUCTION.
118      */
119     protected Map<Integer, NavPoint> indicesNavPoints;
120     // Warshall's matrix of distances.
121     protected PathMatrixNode[][] pathMatrix;
122     /**
123      * Synchronizing access to object with respect to
124      * {@link FloydWarshallMap#enabled}.
125      */
126     protected Object mutex = new Object();
127     /**
128      * Log for logging
129      */
130     private Logger log;
131 
132     public FloydWarshallMap(Logger log) {
133         this(BAD_EDGE_FLAG, log);
134     }
135 
136     public FloydWarshallMap(int badEdgeFlag, Logger log) {
137         this.badEdgeFlag = badEdgeFlag;
138         this.log = log;
139     }
140 
141     /**
142      * Force FloydWarshall to run again, useful if you modify navpoints using
143      * {@link NavigationGraphBuilder}.
144      */
145     public void refreshPathMatrix(List<NavPoint> navPoints) {
146         synchronized (mutex) {
147             if (log.isLoggable(Level.FINE)) {
148                 log.fine(this + ": Refreshing path matrix...");
149             }
150             performFloydWarshall(navPoints);
151             if (log.isLoggable(Level.WARNING)) {
152                 log.warning(this + ": Path matrix refreshed!");
153             }
154         }
155     }
156 
157     protected void performFloydWarshall(MapPointListObtained map) {
158         List<NavPoint> navPoints = MyCollections.asList(map.getNavPoints().values());
159         performFloydWarshall(navPoints);
160     }
161 
162     protected void performFloydWarshall(List<NavPoint> navPoints) {
163         if (log.isLoggable(Level.FINE)) {
164             log.fine(this + ": Beginning Floyd-Warshall algorithm...");
165         }
166         long start = System.currentTimeMillis();
167 
168         // prepares data structures
169         int size = navPoints.size();
170         navPointIndices = new HashMap<UnrealId, Integer>(size);
171         indicesNavPoints = new HashMap<Integer, NavPoint>(size);
172         pathMatrix = new PathMatrixNode[size][size];
173 
174         // Initialize navPoint indices mapping.
175         for (int i = 0; i < navPoints.size(); ++i) {
176             navPointIndices.put(navPoints.get(i).getId(), i);
177             indicesNavPoints.put(i, navPoints.get(i));
178         }
179 
180         // Initialize distance matrix.
181         for (int i = 0; i < size; i++) {
182             for (int j = 0; j < size; j++) {
183                 pathMatrix[i][j] = new PathMatrixNode((i == j) ? 0.0f
184                         : Float.POSITIVE_INFINITY);
185             }
186         }
187 
188         // Set edge lengths into distance matrix.
189         for (int i = 0; i < size; i++) {
190             Point3d location = navPoints.get(i).getLocation().getPoint3d();
191             for (NavPointNeighbourLink link : navPoints.get(i)
192                     .getOutgoingEdges().values()) {
193                 if (checkLink(link)) {
194                     pathMatrix[i][navPointIndices.get(link.getToNavPoint()
195                             .getId())].setDistance((float) location
196                             .distance(link.getToNavPoint().getLocation()
197                             .getPoint3d()));
198                 }
199             }
200         }
201 
202         // Perform Floyd-Warshall.
203         for (int k = 0; k < size; k++) {
204             for (int i = 0; i < size; i++) {
205                 for (int j = 0; j < size; j++) {
206                     float newLen = pathMatrix[i][k].getDistance()
207                             + pathMatrix[k][j].getDistance();
208                     if (pathMatrix[i][j].getDistance() > newLen) {
209                         pathMatrix[i][j].setDistance(newLen);
210                         pathMatrix[i][j].setViaNode(k);
211                     }
212                 }
213             }
214         }
215 
216         // Construct paths + log unreachable paths.
217         int count = 0;
218         for (int i = 0; i < size; i++) {
219             for (int j = 0; j < size; j++) {
220                 if (pathMatrix[i][j].getDistance() == Float.POSITIVE_INFINITY) {
221                     if (log.isLoggable(Level.FINER)) {
222                         log.finer("Unreachable path from " + navPoints.get(i).getId().getStringId()
223                                 + " -> " + navPoints.get(j).getId().getStringId());
224                     }
225                     count++;
226                 } else {
227                     // path exists ... retrieve it
228                     pathMatrix[i][j].setPath(retrievePath(i, j));
229                 }
230             }
231         }
232 
233         if (count > 0) {
234             if (log.isLoggable(Level.WARNING)) {
235                 log.warning(this + ": There are " + count + " unreachable nav point pairs (if you wish to see more, set logging to Level.FINER).");
236             }
237         }
238 
239         if (log.isLoggable(Level.INFO)) {
240             log.info(this + ": computation for " + size + " navpoints took " + (System.currentTimeMillis() - start) + " millis");
241         }
242 
243         // null unneeded field to free some memory
244         indicesNavPoints = null;
245     }
246 
247     /**
248      * Checks whether the edge is usable.
249      *
250      * @param from Starting nav point.
251      * @param edge NeighNav object representing the edge.
252      * @return boolean
253      */
254     public boolean checkLink(NavPointNeighbourLink edge) {
255         // Bad flags (prohibited edges, swimming, flying...).        
256         if ((edge.getFlags() & badEdgeFlag) != 0) {
257             return false;
258         }
259         
260         // Bot can't move over translocator paths.
261         if (edge.isOnlyTranslocator()) {
262             return false;
263         }
264 
265         // Lift flags.
266         if ((edge.getFlags() & LinkFlag.SPECIAL.get()) != 0) {
267             return true;
268         }
269 
270         // Check whether the climbing angle is not so steep.
271 //		if ((edge.getFromNavPoint().getLocation().getPoint3d().distance(
272 //				edge.getToNavPoint().getLocation().getPoint3d()) < (edge
273 //				.getToNavPoint().getLocation().z - edge.getFromNavPoint()
274 //				.getLocation().z))
275 //				&& (edge.getFromNavPoint().getLocation().getPoint3d().distance(
276 //						edge.getToNavPoint().getLocation().getPoint3d()) > 100)) {
277 //			return false;
278 //		}
279 
280         // Check whether the jump is not so high.
281 //		if (((edge.getFlags() & LinkFlag.JUMP.get()) != 0)
282 //				&& (edge.getToNavPoint().getLocation().z
283 //						- edge.getFromNavPoint().getLocation().z > 80)) {
284 //			return false;
285 //		}
286         //Check whether there is NeededJump attribute set - this means the bot has to 
287         //provide the jump himself - if the Z of the jump is too high it means he
288         //needs to rocket jump or ShieldGun jump - we will erase those links
289         //as our bots are not capable of this
290         if (edge.getNeededJump() != null && edge.getNeededJump().z > 680) {
291             return false;
292         }
293 
294         //This is a navpoint that requires lift jump - our bots can't do this - banning the link!
295         if (edge.getToNavPoint().isLiftJumpExit()) {
296             return false;
297         }
298 
299         return true;
300     }
301 
302     /**
303      * Sub-routine of retrievePath - do not use! ... Well you may, it returns
304      * path without 'from', 'to' or null if such path dosn't exist.
305      * <p>
306      * <p>
307      * DO NOT USE OUTSIDE CONSTRUCTOR (relies on indicesNavPoints).
308      *
309      * @param from
310      * @param to
311      * @return
312      */
313     private List<NavPoint> retrievePathInner(Integer from, Integer to) {
314         PathMatrixNode node = pathMatrix[from][to];
315         if (node.getDistance() == Float.POSITIVE_INFINITY) {
316             return null;
317         }
318         if (node.getViaNode() == null) {
319             return new ArrayList<NavPoint>(0);
320         }
321         if (node.getViaNode() == null) {
322             return new ArrayList<NavPoint>(0);
323         }
324 
325         List<NavPoint> path = new ArrayList<NavPoint>();
326         path.addAll(retrievePathInner(from, node.getViaNode()));
327         path.add(indicesNavPoints.get(node.getViaNode()));
328         path.addAll(retrievePathInner(node.getViaNode(), to));
329 
330         return path;
331     }
332 
333     /**
334      * Returns path between from-to or null if path doesn't exist. Path begins
335      * with 'from' and ends with 'to'.
336      * <p>
337      * <p>
338      * DO NOT USE OUTSIDE CONSTRUCTOR (relies on indicesNavPoints).
339      *
340      * @param from
341      * @param to
342      * @return
343      */
344     private List<NavPoint> retrievePath(Integer from, Integer to) {
345         List<NavPoint> path = new ArrayList<NavPoint>();
346         path.add(indicesNavPoints.get(from));
347         path.addAll(retrievePathInner(from, to));
348         path.add(indicesNavPoints.get(to));
349         return path;
350     }
351 
352     protected PathMatrixNode getPathMatrixNode(NavPoint np1, NavPoint np2) {
353         return pathMatrix[navPointIndices.get(np1.getId())][navPointIndices
354                 .get(np2.getId())];
355     }
356 
357     /**
358      * Whether navpoint 'to' is reachable from the navpoint 'from'.
359      * <p><p>
360      * Throws exception if object is disabled, see
361      * {@link FloydWarshallMap#setEnabled(boolean)}. Note that the object is
362      * enabled by default.
363      *
364      * @param from
365      * @param to
366      * @return
367      */
368     public boolean reachable(NavPoint from, NavPoint to) {
369         if ((from == null) || (to == null)) {
370             return false;
371         }
372         return getPathMatrixNode(from, to).getDistance() != Float.POSITIVE_INFINITY;
373     }
374 
375     /**
376      * Calculate's distance between two nav points (using pathfinding).
377      * <p><p>
378      * Throws exception if object is disabled, see
379      * {@link FloydWarshallMap#setEnabled(boolean)}. Note that the object is
380      * enabled by default.
381      *
382      * @return Distance or POSITIVE_INFINITY if there's no path.
383      */
384     public float getDistance(NavPoint from, NavPoint to) {
385         if ((from == null) || (to == null)) {
386             return Float.POSITIVE_INFINITY;
387         }
388         return getPathMatrixNode(from, to).getDistance();
389     }
390 
391     /**
392      * Returns path between navpoints 'from' -> 'to'. The path begins with
393      * 'from' and ends with 'to'. If such path doesn't exist, returns null.
394      * <p><p>
395      * Throws exception if object is disabled, see
396      * {@link FloydWarshallMap#setEnabled(boolean)}. Note that the object is
397      * enabled by default.
398      *
399      * @param from
400      * @param to
401      * @return
402      */
403     public List<NavPoint> getPath(NavPoint from, NavPoint to) {
404         synchronized (mutex) {
405             if ((from == null) || (to == null)) {
406                 return null;
407             }
408             if (log.isLoggable(Level.FINE)) {
409                 log.fine("Retrieving path: " + from.getId().getStringId() + "[" + from.getLocation() + "] -> " + to.getId().getStringId() + "[" + to.getLocation() + "]");
410             }
411             List<NavPoint> path = getPathMatrixNode(from, to).getPath();
412             if (path == null) {
413                 if (log.isLoggable(Level.WARNING)) {
414                     log.warning("PATH NOT EXIST: " + from.getId().getStringId() + "[" + from.getLocation() + "] -> " + to.getId().getStringId() + "[" + to.getLocation() + "]");
415                 }
416             } else {
417                 if (log.isLoggable(Level.FINE)) {
418                     log.fine("Path exists - " + path.size() + " navpoints.");
419                 }
420             }
421             return path;
422         }
423     }
424 
425     protected void cleanUp() {
426         pathMatrix = null;
427         navPointIndices = null;
428     }
429 
430     @Override
431     public String toString() {
432         return "FloydWarshallMap";
433     }
434 
435     /**
436      * Returns path between navpoints 'from' -> 'to'. The path begins with
437      * 'from' and ends with 'to'. If such path does not exist, it returns
438      * zero-sized path.
439      * <p><p>
440      * Throws exception if object is disabled, see
441      * {@link FloydWarshallMap#setEnabled(boolean)}. Note that the object is
442      * enabled by default.
443      *
444      * @param from
445      * @param to
446      * @return
447      */
448     @Override
449     public IPathFuture<NavPoint> computePath(NavPoint from, NavPoint to) {
450         return new PrecomputedPathFuture<NavPoint>(from, to, getPath(from, to));
451     }
452 }