View Javadoc

1   package cz.cuni.amis.pogamut.udk.agent.navigation.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.module.SensorModule;
13  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
14  import cz.cuni.amis.pogamut.udk.bot.impl.UDKBot;
15  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.NavPoint;
16  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.NavPointNeighbourLink;
17  import cz.cuni.amis.pogamut.udk.communication.translator.shared.events.MapPointListObtained;
18  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
19  import cz.cuni.amis.utils.collections.MyCollections;
20  
21  /**
22   * Private map using Floyd-Warshall for path-finding.
23   * <p>
24   * <p>
25   * It should be initialized upon receiving {@link MapPointListObtained} event.
26   * It precomputes all the paths inside the environment using Floyd-Warshall
27   * algorithm (O(n^3)). Use getReachable(), getDistance(), getPath() to obtain
28   * the info about the path.
29   * <p>
30   * <p>
31   * Based upon the implementation from Martin Krulis with his kind consent -
32   * Thank you!
33   * <p>
34   * <p>
35   * NOTE: requires O(navpoints.size^3) of memory ~ which is 10000^3 at max for
36   * UT2004 (usually maps have 3000 navpoints max). If you are going to use more
37   * than one bot in your simulation, we advise you to create a singleton that
38   * will provide an instance for all your bots.
39   * 
40   * @author Martin Krulis
41   * @author Jakub Gemrot
42   */
43  public class FloydWarshallMap extends SensorModule<UDKBot> {
44  
45  	public static class PathMatrixNode {
46  
47  		private float distance = Float.POSITIVE_INFINITY;
48  		private Integer viaNode = null;
49  		private List<NavPoint> path = null;
50  
51  		/**
52  		 * Doesn't leading anywhere
53  		 */
54  		public PathMatrixNode() {
55  		}
56  
57  		public PathMatrixNode(float distance) {
58  			this.distance = distance;
59  		}
60  
61  		public float getDistance() {
62  			return distance;
63  		}
64  
65  		public void setDistance(float distance) {
66  			this.distance = distance;
67  		}
68  
69  		/**
70  		 * Returns indice.
71  		 * 
72  		 * @return
73  		 */
74  		public Integer getViaNode() {
75  			return viaNode;
76  		}
77  
78  		public void setViaNode(Integer indice) {
79  			this.viaNode = indice;
80  		}
81  
82  		public List<NavPoint> getPath() {
83  			return path;
84  		}
85  
86  		public void setPath(List<NavPoint> path) {
87  			this.path = path;
88  		}
89  
90  	}
91  
92  	private IWorldEventListener<MapPointListObtained> mapListener = new IWorldEventListener<MapPointListObtained>() {
93  
94  		@Override
95  		public void notify(MapPointListObtained event) {
96  			if (log.isLoggable(Level.INFO)) log.info("Map point list obtained.");
97  			performFloydWarshall(event);
98  		}
99  	};
100 
101 	/**
102 	 * Flag mask representing unusable edge.
103 	 */
104 	public static final int BAD_EDGE_FLAG = LinkFlag.FLY.get()
105 			| LinkFlag.LADDER.get() | LinkFlag.PROSCRIBED.get()
106 			| LinkFlag.SWIM.get() | LinkFlag.PLAYERONLY.get()
107 			| LinkFlag.FORCED.get();
108 
109 	/**
110 	 * Prohibited edges.
111 	 */
112 	protected int badEdgeFlag = 0;
113 
114 	/**
115 	 * Hash table converting navPoint IDs to our own indices.
116 	 */
117 	protected Map<UnrealId, Integer> navPointIndices;
118 
119 	/**
120 	 * Mapping indices to nav points.
121 	 * <p>
122 	 * <p>
123 	 * WILL BE NULL AFTER CONSTRUCTION! SERVES AS A TEMPORARY "GLOBAL VARIABLE"
124 	 * DURING FLOYD-WARSHALL COMPUTATION AND PATH RECONSTRUCTION.
125 	 */
126 	protected Map<Integer, NavPoint> indicesNavPoints;
127 
128 	// Warshall's matrix of distances.
129 	protected PathMatrixNode[][] pathMatrix;
130 
131 	public FloydWarshallMap(UDKBot bot) {
132 		this(bot, BAD_EDGE_FLAG, null);
133 	}
134 
135 	public FloydWarshallMap(UDKBot bot, Logger log) {
136 		this(bot, BAD_EDGE_FLAG, log);
137 	}
138 
139 	public FloydWarshallMap(UDKBot bot, int badEdgeFlag, Logger log) {
140 		super(bot, log);
141 		this.badEdgeFlag = badEdgeFlag;
142 		worldView.addEventListener(MapPointListObtained.class, mapListener);
143 	}
144 
145 	protected void performFloydWarshall(MapPointListObtained map) {
146 		List<NavPoint> navPoints = MyCollections.asList(map.getNavPoints().values());
147 		performFloydWarshall(navPoints);
148 	}
149 
150 	protected void performFloydWarshall(List<NavPoint> navPoints) {
151 		if (log.isLoggable(Level.FINE)) log.fine(this + ": Beginning Floyd-Warshall algorithm...");
152 		long start = System.currentTimeMillis();
153 
154 		// prepares data structures
155 		int size = navPoints.size();
156 		navPointIndices = new HashMap<UnrealId, Integer>(size);
157 		indicesNavPoints = new HashMap<Integer, NavPoint>(size);
158 		pathMatrix = new PathMatrixNode[size][size];
159 
160 		// Initialize navPoint indices mapping.
161 		for (int i = 0; i < navPoints.size(); ++i) {
162 			navPointIndices.put(navPoints.get(i).getId(), i);
163 			indicesNavPoints.put(i, navPoints.get(i));
164 		}
165 
166 		// Initialize distance matrix.
167 		for (int i = 0; i < size; i++) {
168 			for (int j = 0; j < size; j++) {
169 				pathMatrix[i][j] = new PathMatrixNode((i == j) ? 0.0f
170 						: Float.POSITIVE_INFINITY);
171 			}
172 		}
173 
174 		// Set edge lengths into distance matrix.
175 		for (int i = 0; i < size; i++) {
176 			Point3d location = navPoints.get(i).getLocation().getPoint3d();
177 			for (NavPointNeighbourLink link : navPoints.get(i)
178 					.getOutgoingEdges().values()) {
179 				if (checkLink(link)) {
180 					pathMatrix[i][navPointIndices.get(link.getToNavPoint()
181 							.getId())].setDistance((float) location
182 							.distance(link.getToNavPoint().getLocation()
183 									.getPoint3d()));
184 				}
185 			}
186 		}
187 
188 		// Perform Floyd-Warshall.
189 		for (int k = 0; k < size; k++) {
190 			for (int i = 0; i < size; i++) {
191 				for (int j = 0; j < size; j++) {
192 					float newLen = pathMatrix[i][k].getDistance()
193 							+ pathMatrix[k][j].getDistance();
194 					if (pathMatrix[i][j].getDistance() > newLen) {
195 						pathMatrix[i][j].setDistance(newLen);
196 						pathMatrix[i][j].setViaNode(k);
197 					}
198 				}
199 			}
200 		}
201 
202 		// Construct paths + log unreachable paths.
203 		int count = 0;
204 		for (int i = 0; i < size; i++) {
205 			for (int j = 0; j < size; j++) {
206 				if (pathMatrix[i][j].getDistance() == Float.POSITIVE_INFINITY) {
207 					if (log.isLoggable(Level.WARNING)) log.warning("Unreachable path from " + navPoints.get(i).getId().getStringId()
208 							+ " -> " + navPoints.get(j).getId().getStringId());
209 					count++;
210 				} else {
211 					// path exists ... retrieve it
212 					pathMatrix[i][j].setPath(retrievePath(i, j));
213 				}
214 			}
215 		}
216 
217 		if (count > 0) {
218 			if (log.isLoggable(Level.WARNING)) log.warning(this + ": There are " + count + " unreachable nav point pairs.");
219 		}
220 
221 		if (log.isLoggable(Level.INFO)) log.info(this + ": computation for " + size + " navpoints took " + (System.currentTimeMillis() - start) + " millis");
222 
223 		// null unneeded field to free some memory
224 		indicesNavPoints = null;
225 	}
226 
227 	/**
228 	 * Checks whether the edge is usable.
229 	 * 
230 	 * @param from
231 	 *            Starting nav point.
232 	 * @param edge
233 	 *            NeighNav object representing the edge.
234 	 * @return boolean
235 	 */
236 	public boolean checkLink(NavPointNeighbourLink edge) {
237 		// Bad flags (prohibited edges, swimming, flying...).
238 		if ((edge.getFlags() & badEdgeFlag) != 0)
239 			return false;
240 
241 		// Lift flags.
242 		if ((edge.getFlags() & LinkFlag.SPECIAL.get()) != 0)
243 			return true;
244 
245 		// Check whether the climbing angle is not so steep.
246 		if ((edge.getFromNavPoint().getLocation().getPoint3d().distance(
247 				edge.getToNavPoint().getLocation().getPoint3d()) < (edge
248 				.getToNavPoint().getLocation().z - edge.getFromNavPoint()
249 				.getLocation().z))
250 				&& (edge.getFromNavPoint().getLocation().getPoint3d().distance(
251 						edge.getToNavPoint().getLocation().getPoint3d()) > 100)) {
252 			return false;
253 		}
254 
255 		// Check whether the jump is not so high.
256 		if (((edge.getFlags() & LinkFlag.JUMP.get()) != 0)
257 				&& (edge.getToNavPoint().getLocation().z
258 						- edge.getFromNavPoint().getLocation().z > 80)) {
259 			return false;
260 		}
261 
262 		return true;
263 	}
264 
265 	/**
266 	 * Sub-routine of retrievePath - do not use! ... Well you may, it returns
267 	 * path without 'from', 'to' or null if such path dosn't exist.
268 	 * <p>
269 	 * <p>
270 	 * DO NOT USE OUTSIDE CONSTRUCTOR (relies on indicesNavPoints).
271 	 * 
272 	 * @param from
273 	 * @param to
274 	 * @return
275 	 */
276 	private List<NavPoint> retrievePathInner(Integer from, Integer to) {
277 		PathMatrixNode node = pathMatrix[from][to];
278 		if (node.getDistance() == Float.POSITIVE_INFINITY)
279 			return null;
280 		if (node.getViaNode() == null) {
281 			return new ArrayList<NavPoint>(0);
282 		}
283 		if (node.getViaNode() == null)
284 			return new ArrayList<NavPoint>(0);
285 
286 		List<NavPoint> path = new ArrayList<NavPoint>();
287 		path.addAll(retrievePathInner(from, node.getViaNode()));
288 		path.add(indicesNavPoints.get(node.getViaNode()));
289 		path.addAll(retrievePathInner(node.getViaNode(), to));
290 
291 		return path;
292 	}
293 
294 	/**
295 	 * Returns path between from-to or null if path doesn't exist. Path begins
296 	 * with 'from' and ends with 'to'.
297 	 * <p>
298 	 * <p>
299 	 * DO NOT USE OUTSIDE CONSTRUCTOR (relies on indicesNavPoints).
300 	 * 
301 	 * @param from
302 	 * @param to
303 	 * @return
304 	 */
305 	private List<NavPoint> retrievePath(Integer from, Integer to) {
306 		List<NavPoint> path = new ArrayList<NavPoint>();
307 		path.add(indicesNavPoints.get(from));
308 		path.addAll(retrievePathInner(from, to));
309 		path.add(indicesNavPoints.get(to));
310 		return path;
311 	}
312 
313 	protected PathMatrixNode getPathMatrixNode(NavPoint np1, NavPoint np2) {
314 		return pathMatrix[navPointIndices.get(np1.getId())][navPointIndices
315 				.get(np2.getId())];
316 	}
317 
318 	/**
319 	 * Whether navpoint 'to' is reachable from the navpoint 'from'.
320 	 * 
321 	 * @param from
322 	 * @param to
323 	 * @return
324 	 */
325 	public boolean reachable(NavPoint from, NavPoint to) {
326 		if ((from == null) || (to == null))
327 			return false;
328 		return getPathMatrixNode(from, to).getDistance() != Float.POSITIVE_INFINITY;
329 	}
330 
331 	/**
332 	 * Calculate's distance between two nav points (using pathfinding).
333 	 * 
334 	 * @return Distance or POSITIVE_INFINITY if there's no path.
335 	 */
336 	public float getDistance(NavPoint from, NavPoint to) {
337 		if ((from == null) || (to == null))
338 			return Float.POSITIVE_INFINITY;
339 		return getPathMatrixNode(from, to).getDistance();
340 	}
341 
342 	/**
343 	 * Returns path between navpoints 'from' -> 'to'. The path begins with
344 	 * 'from' and ends with 'to'. If such path doesn't exist, returns null.
345 	 * 
346 	 * @param from
347 	 * @param to
348 	 * @return
349 	 */
350 	public List<NavPoint> getPath(NavPoint from, NavPoint to) {
351 		if ((from == null) || (to == null))
352 			return null;
353 		return getPathMatrixNode(from, to).getPath();
354 	}
355 
356 	@Override
357 	public String toString() {
358 		return "FloydWarshallMap";
359 	}
360 
361 }