View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.storyworld.place;
2   
3   import java.util.Arrays;
4   import java.util.Collection;
5   import java.util.Comparator;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Random;
12  import java.util.Set;
13  
14  import javax.vecmath.Point3d;
15  
16  import com.thoughtworks.xstream.annotations.XStreamAlias;
17  import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
18  import com.thoughtworks.xstream.annotations.XStreamOmitField;
19  
20  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
21  import cz.cuni.amis.pogamut.ut2004.storyworld.perception.SPLocation;
22  import cz.cuni.amis.utils.collections.MyCollections;
23  import cz.cuni.amis.utils.token.Token;
24  import cz.cuni.amis.utils.token.Tokens;
25  
26  /**
27   * Basic interface for all story places.
28   * <p><p>
29   * Contains two methods that provides a means to create hierarchical description of places.
30   * <p>(Kitchen is in House that is in the City that is in the Czech Republic...)
31   * <p><p>
32   * We don't support changing the story-place-graph at runtime! 
33   *  
34   * @author Jimmy
35   */
36  @XStreamAlias("place")
37  public class SPStoryPlace {
38  	
39  	@XStreamOmitField
40  	private static Random random = new Random(System.currentTimeMillis());
41  	
42  	@XStreamOmitField
43  	private SPStoryPlace insidePlace = null;
44  	
45  	@XStreamAlias("inside")
46  	@XStreamAsAttribute
47  	protected String insidePlaceName;
48  	
49  	@XStreamOmitField
50  	private Set<SPStoryPlace> higherPlaces = null;
51  	
52  	@XStreamOmitField
53  	private Set<SPStoryPlace> containsPlaces = new HashSet<SPStoryPlace>();
54  	
55  	@XStreamOmitField
56  	private Set<SPStoryPlace> containsAllPlaces = null;
57  	
58  	@XStreamOmitField
59  	private Set<NavPoint> virtualPlaces = null;
60  	
61  	@XStreamOmitField
62  	private SPLocation center = null;
63  	
64  	@XStreamOmitField
65  	private NavPoint centerNavPoint = null;
66  	
67  	@XStreamAlias("name")
68  	@XStreamAsAttribute
69  	private String nameString;
70  	
71  	@XStreamOmitField
72  	private Token name = null;
73  
74  	@XStreamOmitField
75  	private List<NavPoint> navPointsList = null;
76  	
77  	public SPStoryPlace(String name, SPStoryPlace inside) {
78  		this.nameString = name;
79  		this.name = Tokens.get(name);
80  		setInsidePlace(inside);
81  		
82  	}
83  	
84  	public SPStoryPlace(String name) {
85  		this.nameString = name;
86  		this.name = Tokens.get(name);		
87  	}
88  
89  	/**
90  	 * Called by XStream after deserialization.
91  	 */
92  	private SPStoryPlace readResolve() {
93  		insidePlace = null;
94  		higherPlaces = null;
95  		containsPlaces = new HashSet<SPStoryPlace>();
96  		containsAllPlaces = null;
97  		virtualPlaces = null;
98  		name = null;
99  		getName();
100 		return this;
101 	}
102 	
103 	/**
104 	 * Used to inject the insidePlace after construction with "name" only (needed because the xml definition may
105 	 * be written the way it needs to be processed twiced, 1) create SPStoryPlaces, 2) inject inside)
106 	 * <p><p>
107 	 * Can't be called if insidePlace is already bound (RuntimeException).
108 	 * 
109 	 * @param place
110 	 */
111 	protected void setInsidePlace(SPStoryPlace place) {
112 		if (insidePlace != null) throw new RuntimeException("insidePlace already set for the " + this);
113 		this.insidePlace = place;
114 		if (this.insidePlace != null) {
115 			this.insidePlaceName = place.getName().getToken();
116 			this.insidePlace.getContainsPlaces().add(this);
117 		}
118 	}
119 	
120 	@Override
121 	public int hashCode() {
122 		return name.hashCode();
123 	}
124 	
125 	@Override
126 	public boolean equals(Object obj) {
127 		if (obj == null) return false;
128 		if (!(obj instanceof SPStoryPlace)) return false;
129 		SPStoryPlace place = (SPStoryPlace)obj;
130 		return name.equals(place.getName());
131 	}
132 			
133 	public Token getName() {
134 		if (name == null) name = Tokens.get(nameString);		
135 		return name;
136 	}
137 
138 	/**
139 	 * Returns the place that this one is a part.<p> 
140 	 * If null - that means it's the highest
141 	 * @return
142 	 */
143 	public SPStoryPlace getInsidePlace() {
144 		return insidePlace;
145 	}
146 	
147 	/**
148 	 * Returns name of the place this one is inside.
149 	 * @return
150 	 */
151 	public String getInsidePlaceName() {
152 		return insidePlaceName;
153 	}
154 	
155 	/**
156 	 * Returns set of all places this one is a part of.<p>
157 	 * (You may ask whether this Kitchen in Czech Republic
158 	 * <p><p>
159 	 * Can't be called before the definition of all places 
160 	 * is completed otherwise it won't contain all higher places!
161 	 * (Lazy initialization.)
162 	 * 
163 	 * @return
164 	 */
165 	public Set<SPStoryPlace> getHigherPlaces() {
166 		if (higherPlaces == null) {
167 			higherPlaces = new HashSet<SPStoryPlace>();
168 			SPStoryPlace current = insidePlace;
169 			while (current != null) {
170 				higherPlaces.add(current);
171 				current = current.getInsidePlace();
172 			}
173 		}
174 		return higherPlaces;
175 	}
176 
177 	/**
178 	 * Returns set with places this one contains (not recursive!).
179 	 * @return
180 	 */
181 	public Set<SPStoryPlace> getContainsPlaces() {
182 		return containsPlaces;
183 	}
184 	
185 	/**
186 	 * Returns all places that are inside this one.
187 	 * <p><p>
188 	 * Can't be called before the definition of all story places are defined,
189 	 * otherwise it won't contains all places. (Lazy initialization.)
190 	 * 
191 	 * @return
192 	 */
193 	public Set<SPStoryPlace> getContainsAllPlaces() {
194 		if (containsAllPlaces == null) {
195 			containsAllPlaces = new HashSet<SPStoryPlace>();
196 			for (SPStoryPlace place : containsPlaces) {
197 				containsAllPlaces.addAll(place.getContainsAllPlaces());
198 			}
199 		}
200 		return containsAllPlaces;
201 	}
202 	
203 	/**
204 	 * Returns places inside the virtual world that belongs to this place. Basically
205 	 * this is binding to the chosen 3D world simulator. It should contains objects upon
206 	 * whose the real path-finding can run.
207 	 * <p><p>
208 	 * Can't be called before the definition of all story places are defined,
209 	 * otherwise it won't contains all places. (Lazy initialization.)
210 	 * 
211 	 * @return
212 	 */
213 	public Set<NavPoint> getNavPoints() {
214 		if (virtualPlaces == null) {
215 			virtualPlaces = new HashSet<NavPoint>();
216 			for (SPStoryPlace place : getContainsPlaces()) {
217 				virtualPlaces.addAll(place.getNavPoints());
218 			}
219 		}
220 		return virtualPlaces; 
221 	}
222 	
223 	/**
224 	 * Returns places inside the virtual world that belongs to this place. Basically
225 	 * this is binding to the chosen 3D world simulator. It should contains objects upon
226 	 * whose the real path-finding can run.
227 	 * <p><p>
228 	 * Can't be called before the definition of all story places are defined,
229 	 * otherwise it won't contains all places. (Lazy initialization.)
230 	 * @return
231 	 */
232 	public List<NavPoint> getNavPointsList() {
233 		if (navPointsList == null) {
234 			navPointsList = MyCollections.asList(getNavPoints());
235 		}
236 		return navPointsList;
237 	}
238 	
239 	public SPLocation getCenter() {
240 		if (center == null) {
241 			double x = 0, y = 0, z = 0;
242 			for (NavPoint navPoint : getNavPoints()) {
243 				x += navPoint.getLocation().x;
244 				y += navPoint.getLocation().y;
245 				z += navPoint.getLocation().z;
246 			}
247 			center = new SPLocation(x / getNavPoints().size(), y / getNavPoints().size(), z / getNavPoints().size());
248 		}
249 		return center;
250 	}
251 	
252 	public NavPoint getCenterNavPoint() {
253 		if (centerNavPoint == null) {
254 			centerNavPoint = getNearestNavPoint(getCenter());
255 		}
256 		return centerNavPoint;
257 	}
258 	
259 	public NavPoint getRandomNavPoint() {
260 		if (getNavPointsList().size() == 0) return null;
261 		return getNavPointsList().get(random.nextInt(getNavPointsList().size()));
262 	}
263 	
264 	public  NavPoint getNearestNavPoint(SPLocation location) {
265 		// TODO: implement using oct-trees
266 		Point3d loc = location.asPoint3d();
267 		double nearestDistance = Double.MAX_VALUE;
268 		NavPoint nearest = null;
269 		for (NavPoint navPoint : getNavPoints()) {
270 			double distance = loc.distance(navPoint.getLocation().getPoint3d());
271 			if (distance < nearestDistance) {
272 				nearestDistance = distance;
273 				nearest = navPoint;
274 			}
275 		}		
276 		return nearest;
277 	}
278 	
279 	public  NavPoint getFurthestNavPoint(SPLocation location) {
280 		// TODO: implement using oct-trees
281 		Point3d loc = location.asPoint3d();
282 		double furthestDistance = Double.MAX_VALUE;
283 		NavPoint furthest = null;
284 		for (NavPoint navPoint : getNavPoints()) {
285 			double distance = loc.distance(navPoint.getLocation().getPoint3d());
286 			if (distance > furthestDistance) {
287 				furthestDistance = distance;
288 				furthest = navPoint;
289 			}
290 		}		
291 		return furthest;
292 	}
293 	
294 	public Map<NavPoint, Double> getNavPointDistances(SPLocation location) {
295 		Map<NavPoint, Double> distances = new HashMap<NavPoint, Double>();
296 		for (NavPoint navPoint : getNavPoints()) { 
297 			distances.put(navPoint, navPoint.getLocation().getPoint3d().distance(location.asPoint3d()));
298 		}
299 		return distances;
300 	}
301 	
302 	public Map<Double, NavPoint> getNavPointDistancesSwapped(SPLocation location) {
303 		Map<Double, NavPoint> distances = new HashMap<Double, NavPoint>();
304 		for (NavPoint navPoint : getNavPoints()) { 
305 			distances.put(navPoint.getLocation().getPoint3d().distance(location.asPoint3d()), navPoint);
306 		}
307 		return distances;
308 	}
309 	
310 	/**
311 	 * @param distance must be &lt;0,1> ... 0 ~ pick from all possible navpoint, 1 ~ pick the furthest navpoint
312 	 * @return
313 	 */
314 	public NavPoint getRandomNavPoint(SPLocation location, double distance) {
315 		if (distance >= 1) {
316 			return getFurthestNavPoint(location);
317 		}
318 		Map<Double, NavPoint> distances = getNavPointDistancesSwapped(location);
319 		
320 		if (distance <= 0) {
321 			Collection<NavPoint> navPoints = distances.values();
322 			int num = random.nextInt(navPoints.size());
323 			Iterator<NavPoint> np = navPoints.iterator();
324 			for (int i = 0; i < num-1; ++i) {
325 				np.next();
326 			}
327 			return np.next();				
328 		}
329 		
330 		Double[] keys = distances.keySet().toArray(new Double[distances.keySet().size()]);
331 		Arrays.sort(keys, new Comparator<Double>() {
332 
333 			@Override
334 			public int compare(Double o1, Double o2) {
335 				return Double.compare(o1, o2);
336 			}
337 			
338 		});
339 		int randomInt = Double.valueOf(Math.round((1-distance)*keys.length)).intValue();
340 		int index = 0;
341 		if (randomInt > 1) {
342 			index = keys.length - 1 - random.nextInt(randomInt);
343 		} else { 
344 			index = keys.length - 1;
345 		}
346 		if (index < 0) {
347 			System.out.println("huh");
348 		}
349 		NavPoint navPoint = distances.get(keys[index]); 
350 		return navPoint;						
351 	}
352 	
353 	/**
354 	 * DO NOT ALTER!
355 	 * <p><p>
356 	 * Used during translation into prolog!
357 	 */
358 	@Override
359 	public String toString() {
360 		return name.getToken(); 
361 	}
362 
363 	public boolean contains(SPStoryPlace place) {
364 		if (place == this) return true;
365 		return getContainsAllPlaces().contains(place);		 
366 	}
367 
368 }