View Javadoc

1   /*
2    * Copyright (C) 2014 AMIS research group, Faculty of Mathematics and Physics, Charles University in Prague, Czech Republic
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16   */
17  package cz.cuni.amis.pogamut.ut2004.agent.navigation.navmesh.pathfollowing;
18  
19  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
20  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObject;
21  import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
22  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
23  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
24  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentInfo;
25  import cz.cuni.amis.pogamut.ut2004.agent.navigation.IUT2004PathRunner;
26  import cz.cuni.amis.pogamut.ut2004.agent.navigation.navmesh.NavMesh;
27  import cz.cuni.amis.pogamut.ut2004.bot.command.AdvancedLocomotion;
28  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
29  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Move;
30  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
31  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Player;
32  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.WallCollision;
33  import cz.cuni.amis.pogamut.ut2004.utils.LinkFlag;
34  import cz.cuni.amis.pogamut.ut2004.utils.UnrealUtils;
35  import cz.cuni.amis.utils.NullCheck;
36  import java.util.logging.Level;
37  import java.util.logging.Logger;
38  
39  /**
40   * Runner for navigation with navigation mesh. Evolved from {@link KefikRunner}.
41   *
42   * We assume that we work with path generated by NavMesh path planner. It should
43   * contain point on the navigation mesh, which should be navigable by simple
44   * running, and off-mesh connections, which we will handle as original runner.
45   *
46   * Jump computing is working with exact locations, no reserves. Needed edge
47   * should be achieved by delay in executing the jump.
48   *
49   * @author Bogo
50   */
51  public class NavMeshRunner implements IUT2004PathRunner {
52  
53      private UT2004Bot bot;
54      private AgentInfo memory;
55      private AdvancedLocomotion body;
56      private Logger log;
57  
58      private JumpBoundaries jumpBoundaries;
59  
60      /**
61       * Our custom listener for WallCollision messages.
62       */
63      IWorldEventListener<WallCollision> myCollisionsListener = new IWorldEventListener<WallCollision>() {
64          @Override
65          public void notify(WallCollision event) {
66              lastCollidingEvent = event;
67          }
68  
69      };
70  
71      private JumpModule jumpModule;
72  
73      private CollisionDetector collisionDetector;
74      private boolean inCollision = false;
75      private String collisionReason = null;
76  
77      // MAINTAINED CONTEXT
78      /**
79       * Number of steps we have taken.
80       */
81      private int runnerStep = 0;
82  
83      /**
84       * Jumping sequence of a single-jumps.
85       */
86      private int jumpStep = 0;
87  
88      /**
89       * Collision counter.
90       */
91      private int collisionNum = 0;
92  
93      /**
94       * Collision location.
95       */
96      private Location collisionSpot = null;
97  
98      // COMPUTED CONTEXT OF THE runToLocation
99      /**
100      * Current distance to the target, recalculated every
101      * {@link NavMeshRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)}
102      * invocation.
103      */
104     private double distance;
105 
106     /**
107      * Current 2D distance (only in x,y) to the target, recalculated every
108      * {@link NavMeshRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)}
109      * invocation.
110      */
111     private double distance2D;
112 
113     /**
114      * Current Z distance to the target (positive => target is higher than us,
115      * negative => target is lower than us), recalculated every
116      * {@link NavMeshRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)}
117      * invocation.
118      */
119     private double distanceZ;
120 
121     /**
122      * Current velocity of the bot, recalculated every
123      * {@link NavMeshRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)}
124      * invocation.
125      */
126     private double velocity;
127 
128     /**
129      * Current velocity in Z-coord (positive, we're going up / negative, we're
130      * going down), recalculated every
131      * {@link NavMeshRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)}
132      * invocation.
133      */
134     private double velocityZ;
135 
136     /**
137      * Whether the jump is required somewhere along the link, recalculated every
138      * {@link NavMeshRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)}
139      * invocation.
140      */
141     private boolean jumpRequired;
142     
143     /**
144      * Why are we jumping? 
145      */
146     private String jumpReason;
147 
148     // CONTEXT PASSED INTO runToLocation
149     /**
150      * Current context of the
151      * {@link NavMeshRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
152      */
153     private Location runningFrom;
154 
155     /**
156      * Current context of the
157      * {@link NavMeshRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
158      */
159     private Location firstLocation;
160 
161     /**
162      * Current context of the
163      * {@link NavMeshRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
164      */
165     private Location secondLocation;
166 
167     /**
168      * Current context of the
169      * {@link NavMeshRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
170      */
171     private ILocated focus;
172 
173     /**
174      * Current context of the
175      * {@link NavMeshRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
176      */
177     private NavPointNeighbourLink link;
178     
179     /**
180      * Whether we are ordered NOT TO JUMP AT ALL... NEVER EVER.
181      */
182     private boolean forceNoJump;
183 
184     /**
185      * Current angle of the bot movement to the ideal direction
186      */
187     private double runDeviation2DDot;
188     
189     /**
190      * Ideal travalling angle (pitch), negative -> we're going down, positive -> we're going up. In radians.
191      */
192     private double runAngle;
193 
194     /**
195      * If the bot is accelerating
196      */
197     private boolean accelerating = false;
198 
199     /**
200      * Last received wall colliding event
201      */
202     protected WallCollision lastCollidingEvent = null;
203     /**
204      * If we have collided in last 0.5s we will signal it
205      */
206     private static final int WALL_COLLISION_THRESHOLD_MILLIS = 500;
207     
208     /**
209      * Constructor.
210      *
211      * @param bot Agent's bot.
212      * @param agentInfo
213      * @param locomotion
214      * @param log
215      * @param navMesh
216      */
217     public NavMeshRunner(UT2004Bot bot, AgentInfo agentInfo, AdvancedLocomotion locomotion, Logger log, NavMesh navMesh) {
218         // setup reference to agent
219         NullCheck.check(bot, "bot");
220         this.bot = bot;
221         NullCheck.check(agentInfo, "agentInfo");
222         this.memory = agentInfo;
223         NullCheck.check(locomotion, "locomotion");
224         this.body = locomotion;
225 
226         //registering listener for wall collisions
227         bot.getWorldView().addEventListener(WallCollision.class, myCollisionsListener);
228 
229         this.log = log;
230         if (this.log == null) {
231             this.log = bot.getLogger().getCategory(this.getClass().getSimpleName());
232         }
233 
234         this.jumpModule = new JumpModule(navMesh, this.log);
235         this.collisionDetector = new CollisionDetector();
236         
237         reset();
238     }
239 
240     @Override
241     public void reset() {
242     	log.finer("RUNNER RESET");
243 
244         runnerStep = 0;
245         
246         jumpRequired = false;
247         jumpBoundaries = null;
248         jumpReason = null;
249         jumpStep = 0;
250         forceNoJump = false;
251         
252         collisionNum = 0;
253         collisionSpot = null;
254         lastCollidingEvent = null;
255         inCollision = false;
256         collisionReason = null;
257 
258         distance = 0;
259         distance2D = 0;
260         distanceZ = 0;
261         velocity = 0;
262         velocityZ = 0;
263         runDeviation2DDot = 0;
264         accelerating = false;
265         lastVelocityZ = 0;
266     }
267 
268     @Override
269     public boolean runToLocation(Location runningFrom, Location firstLocation, Location secondLocation, ILocated focus, NavPointNeighbourLink navPointsLink, boolean reachable, boolean forceNoJump) {
270     	// take another step
271         ++runnerStep;
272 
273         // save context
274         this.runningFrom = runningFrom;
275         this.firstLocation = firstLocation;
276         this.secondLocation = secondLocation;
277         this.focus = focus;
278         this.link = navPointsLink;
279         this.forceNoJump = forceNoJump;
280 
281         // compute additional context
282         distance = memory.getLocation().getDistance(firstLocation);
283         distance2D = memory.getLocation().getDistance2D(firstLocation);
284         distanceZ = firstLocation.getDistanceZ(memory.getLocation());
285 
286         double newVelocity = memory.getVelocity().size();
287         accelerating = isAccelerating(newVelocity, velocity);
288 
289         velocity = newVelocity;
290         velocityZ = memory.getVelocity().z;
291         
292         Location direction = Location.sub(firstLocation, memory.getLocation()).setZ(0);
293         direction = direction.getNormalized();
294         Location velocityDir = new Location(memory.getVelocity().asVector3d()).setZ(0);
295         velocityDir = velocityDir.getNormalized();
296         runDeviation2DDot = direction.dot(velocityDir);
297         
298         runAngle = Math.atan2(distanceZ, distance2D);
299         
300         if (jumpBoundaries == null || jumpBoundaries.getLink() != link) {
301             jumpBoundaries = jumpModule.computeJumpBoundaries(link);            
302         }
303         
304         checkJump();
305         
306         checkCollision();
307         
308         logIteration();
309         
310         // DELIBERATION
311         
312         // IS THIS FIRST EXECUTION?
313         if (runnerStep <= 1) {
314             debug("FIRST STEP - Start running towards new location");
315             move(firstLocation, secondLocation, focus);
316             return true;
317         }
318         
319         // ARE WE JUMPING ALREADY?
320         if (jumpStep > 0) {
321             debug("We're already jumping" + (collisionSpot != null ? " [COLLISION]" : ""));
322             return iterateJumpSequence();
323         }
324 
325         // ARE WE COLLIDING?
326         if (inCollision) {
327         	// WE SHOULD RESOLVE THE COLLISION WITH A JUMP
328         	if (!forceNoJump) {
329         		// AND WE ARE NOT FORBIDDEN TO DO SO 
330         		return resolveCollision();
331         	}
332         } else {
333 	        if (collisionSpot != null || collisionNum != 0) {
334 	            debug("Collision waned...");
335 	            collisionNum = 0;
336 	            collisionSpot = null;
337 	        }
338         }
339 
340         // ARE WE STUCK FROM THE BEGINNING?
341         if (velocity < 5 && runnerStep > 5) {
342         	debug("Velocity is (almost) zero and we're in the middle of running...");
343         	if (forceNoJump) {
344         		debug("But we will not jump as forceNoJump == true ...");
345         	} else {
346         		debug("So we're going to jump...");
347                 return initJump(true, true);
348         	}
349         }
350         
351         // DO WE HAVE TO JUMP?
352         if (jumpRequired) {
353         	// YES, HAVE TO JUMP
354         	if (!forceNoJump) {
355         		// AND IT IS NOT FORBIDDEN TO DO SO
356         		return decideJump();
357         	}
358         }
359        
360         debug("Keeping running to the target");
361         move(firstLocation, secondLocation, focus);
362         
363         return true;
364     }
365     
366     /**
367      * Checks whether we need to jump in order to reach the next location.
368      * Sets up {@link #jumpRequired} and {@link #jumpReason}.
369      * @return
370      */
371     private boolean checkJump() {
372     	if (jumpStep > 0) return true;
373     	// WE HAVE NOT JUMPED YET ... should we?
374     	if (jumpModule.needsJump(link)) {
375     		// NAVIGATION LINK IS INDICATING JUMP
376     		jumpRequired = true;
377     		jumpReason = "LINK[";
378     		boolean first = true;
379     		if ((link.getFlags() & LinkFlag.JUMP.get()) != 0) {
380     			first = false;
381     			jumpReason += "JUMP-FLAG";
382     		}
383     		if (link.isForceDoubleJump()) {
384     			if (!first) jumpReason += "|";
385     			jumpReason += "DOUBLE-JUMP";
386     		}
387     		if (link.getNeededJump() != null) {
388     			if (!first) jumpReason += "|";
389     			int jumpDistance = (int) memory.getLocation().getDistance(new Location(link.getNeededJump()));
390     			jumpReason += "DIST:" + jumpDistance;
391     		}
392     		jumpReason += "]";
393     	} else {
394     		jumpRequired = false;
395     		jumpReason = null;
396     	}
397     	return jumpRequired;
398     }
399 
400     /**
401      * Checks whether we are colliding.
402      * Sets up {@link #inCollision} and {@link #collisionReason}.
403      * @return
404      */
405     private boolean checkCollision() {
406     	// INTERNAL COLLISION DETECTOR
407     	if (collisionDetector.isColliding(memory.getLocation(), velocity, distance)) {
408     		inCollision = true;
409     		collisionReason = "COLLISION DETECTED";
410     		return true;    		
411     	}
412     	
413     	// COLLISION EVENT
414         if (lastCollidingEvent == null) {
415         	inCollision = false;
416         	collisionReason = null;
417             return false;
418         }        
419         
420         int collisionTimeMillis = (int)(memory.getTime() * 1000 - lastCollidingEvent.getSimTime());
421         if (collisionTimeMillis <= WALL_COLLISION_THRESHOLD_MILLIS) {
422         	inCollision = true;
423         	collisionReason = "WALL-EVENT[before " + collisionTimeMillis + "ms]";
424             return true;
425         }
426         
427         inCollision = false;
428     	collisionReason = null;
429         return false;
430     }
431 
432     private void logIteration() {
433 		Location curr = memory.getLocation();
434 		double d1 = firstLocation == null ? -1 : curr.getDistance(firstLocation);
435 		double d2 = firstLocation != null && secondLocation == null ? 0 : firstLocation.getDistance(secondLocation);
436 		
437 		// DEBUG LOG
438         if (log != null && log.isLoggable(Level.FINER)) {
439             debug("RUNNER STEP " + runnerStep);
440             debug("running     " + memory.getLocation() + " --(D3D:" + ((int)d1) + "|D2D:" + ((int)distance2D) + "|DZ:" + ((int)distanceZ) + ")--> " + firstLocation + (secondLocation == null ? "" : " --(D3D:" + ((int)d2) + ")--> " + secondLocation) + (focus == null ? "" : ", focusing to " + focus));
441             debug("velocity    " + ((int)velocity) + " (z:" + ((int)velocityZ) + ")");
442             debug("deviation2D " + ((int)(Math.acos(runDeviation2DDot) * 180 / Math.PI)));    
443             debug("runAngle    " + ((int)(runAngle / Math.PI * 180)) + " degrees");
444             if (jumpRequired) {
445                 debug("jump        " + jumpReason);
446             }
447             if (inCollision) {
448                 debug("collision   " + collisionReason);                
449             }
450             if (jumpBoundaries.getLink() != null) {
451             	debug("jump-bounds " + (jumpBoundaries.isJumpable() ? "POSSIBLE" : "IMPOSSIBLE") + " X-" + jumpBoundaries.getTakeOffMin() + "<--(" + (jumpBoundaries.getTakeOffMin() != null && jumpBoundaries.getTakeOffMax() != null ? (int)(jumpBoundaries.getTakeOffMin().getDistance(jumpBoundaries.getTakeOffMax())) : "???") + ")-->" + jumpBoundaries.getTakeOffMax() + "-X--->" + jumpBoundaries.getLandingTarget());
452             }
453             if (jumpRequired || inCollision) {
454             	if (forceNoJump) {
455             		debug("jump forbidden [force-no-jump]");
456             	}
457             }
458         }        
459 	}
460 
461     private void move(ILocated firstLocation, ILocated secondLocation, ILocated focus) {
462         Move move = new Move();
463         if (firstLocation != null) {
464             move.setFirstLocation(firstLocation.getLocation());
465             if (secondLocation == null || secondLocation.equals(firstLocation)) {
466                 //We want to reach end of the path, we won't extend the second location.
467                 move.setSecondLocation(firstLocation.getLocation());
468             } else {
469             	double dist = firstLocation.getLocation().getDistance(secondLocation.getLocation());
470             	if (dist < 50) {
471                     //Extend the second location so the bot doesn't slow down, when it's near the original target.
472             		double quantifier = 1 + (200 / dist);
473                     Location extendedSecondLocation = firstLocation.getLocation().interpolate(secondLocation.getLocation(), quantifier);
474                     move.setSecondLocation(extendedSecondLocation);
475             	} else {
476             		move.setSecondLocation(secondLocation.getLocation());
477             	}
478             }
479         } else if (secondLocation != null) {
480             //First location was not set
481             move.setSecondLocation(secondLocation.getLocation());
482         }
483 
484         if (focus != null) {
485             if (focus instanceof Player) {
486                 move.setFocusTarget((UnrealId) ((IWorldObject) focus).getId());
487             } else {
488                 move.setFocusLocation(focus.getLocation());
489             }
490         }
491 
492         debug(move.toString());
493         bot.getAct().act(move);
494     }
495 
496     private boolean resolveCollision() {
497     	// Are we colliding at a new spot?
498         if ( // no collision yet
499              (collisionSpot == null)
500              // or the last collision is far away
501              || (memory.getLocation().getDistance2D(collisionSpot) > 120)
502         ) {
503         	// setup new collision spot info
504             collisionSpot = memory.getLocation();
505             collisionNum = 1;
506             debug("COLLISION[" + collisionNum + "] => just moving...");
507             // Meanwhile: keep running to the location..
508             move(firstLocation, secondLocation, focus);
509             return true;
510         } 
511         // So, we were already colliding here before...
512         // Try to solve the problem according to how long we're here...
513         
514         // Wait a while if the collision hasn't resolved by itself.
515         if (collisionNum > 8) {
516             debug("COLLISION[" + collisionNum + "] => JUMPING");
517             return initJump(true, true);
518         } 
519 
520         // Increase collision counter
521         ++collisionNum;
522         debug("COLLISION[" + collisionNum + "] => just moving...");
523         
524         // Meanwhile: keep running to the location..
525         move(firstLocation, secondLocation, focus);
526         return true;
527     }
528     
529     private boolean decideJump() {
530         debug("decideJump(): called");
531 
532         int jumpDistance2D = (int) distance2D;
533         debug("  ", "jumpDistance2D = " + jumpDistance2D);
534 
535         // follow the deliberation about the situation we're currently in
536         boolean jumpIndicated      = false;       // whether we should jump
537         boolean jumpForced         = inCollision; // whether we should jump RIGHT NOW
538         boolean doubleJumpRequired = false;       // whether we have to use double jump
539 
540         // deliberation, whether we should jump
541         if (jumpModule.needsJump(link)) {
542             debug("  ", "JumpModule is indicating jump");
543             jumpIndicated = true;
544         } else {
545         	debug("  ", "JumpModule is silent");
546         }
547 
548         //We need near perfect conditions for the jump, so we evaluate       
549         if (!jumpBoundaries.isJumpable()) {        	
550         	if ((runnerStep > 1 && velocity > UnrealUtils.MAX_VELOCITY - 50 && runDeviation2DDot < 20 && jumpDistance2D < 500) || jumpDistance2D < 250) {
551         		debug("  ", "jump-bounds impossible and we're heuristically in good position for jump => forcing double jump");
552         		jumpForced = true;
553         		doubleJumpRequired = true;
554         	} else {
555         		debug("  ", "jump-bounds indicating impossible jump => requiring double jump");
556         		doubleJumpRequired = true;
557         	}        	
558         }
559         
560         if (!doubleJumpRequired && runnerStep > 1 && runAngle < -Math.PI/6) {
561     		debug("  ", "we have to JUMP DOWN - indicating jump");
562     		jumpIndicated = true;
563     	}
564 
565         if (jumpIndicated || jumpForced) {
566             return prepareJump(jumpForced, doubleJumpRequired);
567         } else {
568             debug("  ", "we do not need to jump right now, waiting to reach the right spot to jump from");
569             move(firstLocation, secondLocation, focus);
570             return true;
571         }
572     }
573 
574     /*========================================================================*/
575     /**
576      * This method is called from {@link NavMeshRunner#decideJump()} that has
577      * decided that the jump is necessary to reach the the target.
578      *
579      * @param jumpForced ... true == we will jump RIGHT NOW; false == we will check whether the time is right for jumping
580      * @param doubleJumpRequired ... true == if we decide to jump, we will double jump
581      * @return whether we should reach the target
582      */
583     private boolean prepareJump(boolean jumpForced, boolean doubleJumpRequired) {
584         debug("prepareJump(): called");
585 
586         double jumpVelocity = jumpModule.getCorrectedVelocity(velocity, accelerating);
587         
588         Double jumpAngleDeviation = Math.acos(jumpModule.getCorrectedAngle(runDeviation2DDot, runnerStep <= 1));
589         boolean angleSuitable = !jumpAngleDeviation.isNaN() && jumpAngleDeviation < (Math.PI / 9);
590 
591         debug("  ", "jumpVelocity       " + ((int)jumpVelocity));
592         debug("  ", "jumpAngleDeviation " + ((int)(jumpAngleDeviation * 180 / Math.PI)) + " degress");
593         debug("  ", "angleSuitable      " + angleSuitable);
594         
595         if (!jumpForced && runnerStep > 1 && jumpBoundaries.isPastBoundaries(memory.getLocation())) {
596         	debug("  ", "past jump-bounds => forcing the jump");
597         	jumpForced = true;
598         }
599 
600         if (!jumpForced && jumpAngleDeviation > Math.PI / 3) {
601             debug("  ", "impossible jump angle, postponing the jump...");
602             move(firstLocation, secondLocation, focus);
603             return true;
604         }
605 
606         if (jumpForced) {
607             debug("  ", "jump is forced, bypassing jump checks");
608         } else {
609             debug("  ", "jump is not forced and we are within jump-bounds, checking jump conditions");
610 
611             if (!jumpModule.isJumpable(memory.getLocation(), jumpBoundaries.getLandingTarget(), jumpVelocity)) {
612                 debug("  ", "not jumpable! Start: " + memory.getLocation() + " Target: " + jumpBoundaries.getLandingTarget() + " Velocity: " + velocity + " Jump Velocity: " + jumpVelocity);
613                 debug("  ", "proceeding with the straight movement to gain speed");
614                 move(firstLocation, secondLocation, focus);
615                 return true;
616             }
617 
618             if (!angleSuitable) {
619                 debug("  ", "angle is not suitable for jumping (angle > 20 degrees)");
620                 debug("  ", "proceeding with the straight movement to gain speed");
621                 move(firstLocation, secondLocation, focus);
622                 return true;
623             }
624 
625             //Waiting for ideal JUMP conditions
626             if (jumpBoundaries.isJumpable()) {
627             	if (jumpBoundaries.getTakeOffMax().getDistance2D(memory.getLocation()) > IDEAL_JUMP_RESERVE) {
628             		boolean angleIdeal = !jumpAngleDeviation.isNaN() && jumpAngleDeviation < (Math.PI / 90);
629                     if (!angleIdeal) {
630                         debug("  ", "proceeding with the straight movement - waiting for IDEAL JUMP ANGLE");
631                         move(firstLocation, secondLocation, focus);
632                         return true;
633                     }
634                     if (velocity < UnrealUtils.MAX_VELOCITY - 50) {
635                         debug("  ", "proceeding with the straight movement - waiting for IDEAL SPEED");
636                         move(firstLocation, secondLocation, focus);
637                         return true;
638                     }
639             	}
640             }
641                 
642             debug("  ", "passed ideal reserve spot, forcing the jump");
643             jumpForced = true;
644         }
645 
646         return initJump(jumpForced, doubleJumpRequired);
647 
648     }
649     
650     private static final int IDEAL_JUMP_RESERVE = 80;
651 
652     /**
653      * We have to jump RIGHT NOW.
654      * <p>
655      * <p>
656      * Decides whether we need single / double jump and computes the best args
657      * for jump command according to current velocity.
658      *
659      * @param jumpForced
660      * @param maxJumpRequired
661      */
662     private boolean initJump(boolean jumpForced, boolean maxJumpRequired) {
663         debug("initJump(): called");
664         
665         Boolean doubleJump = null;
666         Double jumpForce = Double.NaN;
667         Double jumpVelocity = jumpModule.getCorrectedVelocity(velocity, accelerating);
668         Double jumpAngleCos = jumpModule.getCorrectedAngle(runDeviation2DDot, runnerStep <= 1);
669         
670         if (jumpForced) {
671         	// WE'RE GOING TO JUMP
672         	debug("  ", "Jump is forced!");
673         	
674         	// COLLISION AVOIDANCE?
675         	if (inCollision) {
676             	debug("  ", "In collision, forcing MAX DOUBLE jump!");
677             	inCollision = false;
678                 jumpStep = 1; // we have performed the JUMP            
679                 return jump(true, UnrealUtils.FULL_DOUBLEJUMP_DELAY, UnrealUtils.FULL_DOUBLEJUMP_FORCE);
680             }
681 
682         	// MAX JUMP?
683         	if (maxJumpRequired) {
684 	        	debug("  ", "Forced MAX DOUBLE jump!");
685 	        	jumpStep = 1; // we have performed the JUMP
686 	            return jump(true, UnrealUtils.FULL_DOUBLEJUMP_DELAY, UnrealUtils.FULL_DOUBLEJUMP_FORCE);
687         	}
688         	
689         	// COMPUTE IDEAL JUMP
690         	if (runAngle < 0 && distanceZ < -50) {
691         		debug("  ", "We are going to fall down, runAngle = " + ((int)(runAngle/Math.PI*180)) + " < 0, distaceZ = " + ((int)distanceZ) + " < -50");
692         		
693         		try {
694         			jumpForce = Math.abs(jumpModule.computeFall(memory.getLocation(), jumpBoundaries, jumpVelocity, jumpAngleCos));
695         			if (jumpForce < 110) jumpForce = 110.0d;
696         		} catch (Exception e) {
697         			debug("  ", "Failed to compute fall-jump force, setting 110.");
698         			jumpForce = 110.0d;
699         		}
700         	} else {
701         		debug("  ", "We have to jump normally, computing ideal jump force.");
702         		try {
703         			jumpForce = Math.abs(jumpModule.computeJump(memory.getLocation(), jumpBoundaries, jumpVelocity, jumpAngleCos));
704         		} catch (Exception e) {
705         			jumpForce = Double.NaN;
706         		}
707         	}
708         	
709         	if (jumpForce == Double.NaN) {
710         		debug("  ", "Could not compute jump force (NaN) => forcing MAX DOUBLE jump!");
711         		jumpForce = (double)UnrealUtils.FULL_DOUBLEJUMP_FORCE;
712         	}
713         	doubleJump = jumpForce > UnrealUtils.FULL_JUMP_FORCE;
714         	
715         	jumpStep = 1; // we have performed the JUMP
716             return jump(doubleJump, UnrealUtils.FULL_DOUBLEJUMP_DELAY, jumpForce);
717         }
718         
719         // JUMP IS NOT FORCED
720         
721         if (!jumpBoundaries.isJumpable()) {
722             debug("  ", "Jump boundaries not present! We shouldn't be trying to JUMP!");
723             jumpForce = Double.NaN;
724         } else if (!jumpBoundaries.isInBoundaries(memory.getLocation())) {
725 
726             if (runnerStep > 1 && jumpBoundaries.isPastBoundaries(memory.getLocation())) {
727                 debug("  ", "Already passed max take-off point, forcing jump!");
728                 jumpForced = true;
729                 jumpForce = jumpModule.computeJump(memory.getLocation(), jumpBoundaries, jumpVelocity, jumpAngleCos);
730             } else {
731                 debug("  ", "Not within jump boundaries! We shouldn't JUMP");
732                 jumpForce = Double.NaN;
733             }
734         } else {
735             jumpForce = jumpModule.computeJump(memory.getLocation(), jumpBoundaries, jumpVelocity, jumpAngleCos);
736             if (jumpForce < UnrealUtils.FULL_JUMP_FORCE) {
737                 doubleJump = false;
738             }
739         }
740 
741         if (jumpForce.isNaN()) {
742             if (jumpForced) {
743                 if (!accelerating) {
744                     debug("  ", "Forcing jump but NOT accelerating, postpone!");
745                     move(firstLocation, secondLocation, focus);
746                     return true;
747                 }
748 
749                 jumpStep = 1; // we have performed the JUMP
750                 debug("  ", "Forcing jump - MAX!");
751                 return jump(true, UnrealUtils.FULL_DOUBLEJUMP_DELAY, UnrealUtils.FULL_DOUBLEJUMP_FORCE);
752             }
753 
754             //We cannot jump
755             debug("  ", "Jump failed to compute, continuing with move! Computed force: " + jumpForce);
756             move(firstLocation, secondLocation, focus);
757             return true;
758         } else if (jumpForce < 0) {
759             //We don't need to jump, so we will set
760             debug("  ", "We don't need to jump, continuing with move! Computed force: " + jumpForce);
761             if (jumpBoundaries.isJumpable() && (this.jumpBoundaries.getTakeoffEdgeDirection() != null)) {
762                 //TODO: Jump down
763 
764                 Location movementDirection = jumpBoundaries.getLandingTarget().sub(jumpBoundaries.getTakeOffMax()).setZ(0).getNormalized();
765                 Location meshDirection = jumpBoundaries.getTakeoffEdgeDirection();
766 
767                 double fallAngleCos = meshDirection.setZ(0).getNormalized().dot(movementDirection);
768                 double takeOffDistance = jumpBoundaries.getLandingTarget().getDistance2D(jumpBoundaries.getTakeOffMax());
769                 if (Math.abs(fallAngleCos) > Math.cos(Math.PI / 2.5)) {
770                     //Not direct approach, we should propably jump a little.
771                     debug("  ", "Not direct approach to fall, we should jump a little. Angle: " + Math.acos(fallAngleCos) * (180 / Math.PI));
772                     //TODO: Fixed here
773                     jumpBoundaries.setLandingTarget(jumpBoundaries.getTakeOffMax().interpolate(jumpBoundaries.getLandingTarget(), (IDEAL_JUMP_RESERVE / takeOffDistance)).setZ(jumpBoundaries.getTakeOffMax().z));
774                     return true;
775                 } else {
776                     debug("  ", "Fall solved by not jumping, as angle is suitable. AngleCos: " + fallAngleCos);
777                     jumpStep = 1;
778                     return true;
779                 }
780             } else {
781                 debug("  ", "Fall solved by not jumping, as angle is suitable. Boundaries not jumpable.");
782                 jumpStep = 1;
783                 return true;
784             }
785         } else {
786             jumpStep = 1; // we have performed the JUMP
787             return jump(doubleJump, UnrealUtils.FULL_DOUBLEJUMP_DELAY, jumpForce);
788         }
789 
790     }
791 
792     /*========================================================================*/
793     /**
794      * Perform jump right here and now with provided args.
795      */
796     private boolean jump(boolean doubleJump, double delay, double force) {
797         if (doubleJump) {
798             debug("DOUBLE-JUMPING[delay=" + delay + ", force=" + force + "]");
799         } else {
800             debug("JUMPING[force=" + force + "]");
801         }
802         body.jump(doubleJump, delay, force);
803 
804         return true;
805     }
806 
807     /*========================================================================*/
808     /**
809      * Follows single-jump sequence steps.
810      *
811      * @return True, if no problem occured.
812      */
813     private boolean iterateJumpSequence() {
814         debug("iterateJumpSequence(): called");
815         // what phase of the jump sequence?
816         switch (jumpStep) {
817             // the first phase: wait for the jump
818             case 1:
819                 // did the agent started the jump already?
820                 if (velocityZ > 100) {
821                     debug("iterateJumpSequence(): jumping in progress (velocityZ > 100), increasing jumpStep");
822                     jumpStep++;
823                 }
824                 // meanwhile: just wait for the jump to start
825                 debug("iterateJumpSequence(): issuing move command to the target (just to be sure)");
826                 move(firstLocation, secondLocation, focus);
827                 return true;
828 
829             //  the last phase: finish the jump
830             default:
831                 // did the agent started to fall already
832                 jumpStep++;
833                 if (velocityZ <= 0.01) {
834                     if (velocityZ > lastVelocityZ) {
835                         debug("iterateJumpSequence(): jump has ended");
836                         lastVelocityZ = 0.02;
837                         jumpStep = 0;
838                     } else {
839                         lastVelocityZ = velocityZ;
840                     }
841 
842                 }
843                 debug("iterateJumpSequence(): continuing movement to the target");
844                 move(firstLocation, secondLocation, focus);
845                 return true;
846         }
847 
848     }
849 
850     // =====
851     // UTILS
852     // =====
853     
854     private double lastVelocityZ = 0.02;
855 
856     private boolean isMaxVelocity(double newVelocity) {
857         return Math.abs(newVelocity - UnrealUtils.MAX_VELOCITY) < 5;
858     }
859     
860     private boolean isAccelerating(double newVelocity, double oldVelocity) {
861         return velocity > 0 && (isMaxVelocity(newVelocity) || newVelocity > oldVelocity);
862     }
863     
864     private void debug(String message) {
865         debug("", message);
866     }
867     
868     private void debug(String prefix, String message) {
869         if (log.isLoggable(Level.FINER)) {
870             log.log(Level.FINER, prefix + "  +-- {0}", message);
871         }
872     }
873 
874 }
875