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.base3d.worldview.object.Location;
20  import cz.cuni.amis.pogamut.ut2004.agent.navigation.navmesh.NavMesh;
21  import cz.cuni.amis.pogamut.ut2004.agent.navigation.navmesh.NavMeshConstants;
22  import cz.cuni.amis.pogamut.ut2004.agent.navigation.navmesh.NavMeshPolygon;
23  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
24  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
25  import cz.cuni.amis.pogamut.ut2004.utils.LinkFlag;
26  import cz.cuni.amis.pogamut.ut2004.utils.UnrealUtils;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  import javax.vecmath.Vector2d;
30  import math.geom2d.Point2D;
31  import math.geom2d.Vector2D;
32  import math.geom2d.line.Line2D;
33  
34  /**
35   * Module for computation of jumps. Decides if the jump is doable, determines
36   * correct type and power of the jump.
37   *
38   * @author Bogo
39   */
40  public class JumpModule {
41  
42      //
43      // JUMP EQUATION - SINGLE JUMP
44      // z = -475 * t^2 + power * t
45      //
46      // t = Unreal time
47      //
48      //
49      // JUMP EQUATION - DOUBLE JUMP
50      // z = -475 * t^2 + power * t + koef * (t - delay)
51      //
52      // koef = (power - 340) * 1.066 + 2 
53      //
54      // t = Unreal time
55      //
56      public static final double MAX_DOUBLE_JUMP_POWER = 755;
57      public static final double MAX_SINGLE_JUMP_POWER = 340;
58  
59      private NavMesh navMesh;
60  
61      private Logger log;
62  
63      public static final double MAX_JUMP_HEIGHT = 130;
64      private static final double MAX_SINGLE_JUMP_HEIGHT = 60;
65      private static final double NAVMESH_Z_COORD_CORRECTION = 20;
66      private static final double SPEED_BOOST_DELAY = 0.100;
67      private static final double BOT_RADIUS = 70;
68      private static final double JUMP_PEEK_TIME = 0.39;
69  
70      public JumpModule(NavMesh mesh, Logger log) {
71          this.navMesh = mesh;
72          this.log = log;
73      }
74  
75      /**
76       * Computes jump boundaries for given link, with using maximal available
77       * information about environment. Stores the boundaries in the "Jump" object
78       * for later use.
79       *
80       * @param jumpLink Link on which the jump should occur.
81       * @return True if jump boundaries were successfully computed, false
82       * otherwise.
83       */
84      public JumpBoundaries computeJumpBoundaries(NavPointNeighbourLink jumpLink) {
85          if (jumpLink == null) {
86              return new JumpBoundaries(null);
87          }
88  
89          NavPoint startNavPoint = jumpLink.getFromNavPoint();
90          NavPoint endNavPoint = jumpLink.getToNavPoint();
91          Location startLocation = startNavPoint.getLocation();
92          Location endLocation = endNavPoint.getLocation();
93  
94          //Get edge of the mesh + offset
95          Location linkDirection3d = endLocation.sub(startLocation);
96          Vector2d linkDirection = new Vector2d(linkDirection3d.x, linkDirection3d.y);
97  
98          //double startDistanceFromEdge = navMesh.getDistanceFromEdge(startLocation, linkDirection);
99          BorderPoint startBorder = getBorderPoint(startLocation, endLocation);
100         Location startBorderPoint = startBorder.getPoint();
101 
102         //Get landing edge of the mesh + offset
103         Vector2d negatedLinkDirection = new Vector2d(linkDirection);
104         negatedLinkDirection.negate();
105 
106         //double endDistanceFromEdge = navMesh.getDistanceFromEdge(endLocation, negatedLinkDirection);
107         BorderPoint endBorderPoint = getBorderPoint(endLocation, startLocation);
108         if (!endBorderPoint.getPoint().equals(endNavPoint.getLocation(), 1.0)) {
109             //NavMesh Z coordinate correction
110             endBorderPoint.setPoint(endBorderPoint.getPoint().addZ(NAVMESH_Z_COORD_CORRECTION));
111         }
112 
113         //Check capability to jump between borders
114         boolean borderToBorder = isJumpable(startBorderPoint, endBorderPoint.getPoint(), UnrealUtils.MAX_VELOCITY);
115         if (!borderToBorder) {
116             //We can't jump between the nearest possible points. Uh-oh.
117             return new JumpBoundaries(jumpLink);
118         }
119 
120         //Find take-off booundary
121         Location testBoundary = startLocation;
122         Location currentBoundary = startBorderPoint;
123         boolean boundaryFound = false;
124         double distanceToSearch = 0;
125         do {
126             boolean isJumpable = isJumpable(testBoundary, endBorderPoint.getPoint(), UnrealUtils.MAX_VELOCITY);
127             if (isJumpable && distanceToSearch < BOUNDARY_THRESHOLD) {
128                 currentBoundary = testBoundary;
129                 boundaryFound = true;
130             } else if (isJumpable) {
131                 //Move the test location far from the border
132                 currentBoundary = testBoundary;
133                 distanceToSearch /= 2;
134                 testBoundary = getNavMeshPoint(testBoundary, startLocation, distanceToSearch);
135 
136             } else if (distanceToSearch < BOUNDARY_THRESHOLD && distanceToSearch > 0) {
137                 //Take the last successfull point
138                 boundaryFound = true;
139             } else {
140                 //Move the test location nearer to the border
141                 if (distanceToSearch == 0) {
142                     distanceToSearch = testBoundary.getDistance2D(startBorderPoint);
143                 }
144                 distanceToSearch /= 2;
145                 testBoundary = getNavMeshPoint(testBoundary, startBorderPoint, distanceToSearch);
146             }
147 
148         } while (!boundaryFound);
149 
150         return new JumpBoundaries(jumpLink, currentBoundary, startBorderPoint, startBorder.getDirection(), endBorderPoint.getPoint(), endBorderPoint.getDirection(), endLocation);
151     }
152     private static final int BOUNDARY_THRESHOLD = 50;
153 
154     /**
155      * Gets border point of the mesh for the path between specified points.
156      *
157      * @param start Start of the path.
158      * @param end End of the path.
159      * @return Border point in the direction of the path.
160      */
161     public BorderPoint getBorderPoint(Location start, Location end) {
162         int endPolygonId = navMesh.getPolygonId(end);
163         return getBorderPoint(start, end, -1, endPolygonId, start, 0);
164     }
165 
166     /**
167      * Border point lays on the navigation mesh edge.
168      *
169      * @param start
170      * @param end
171      * @param pId Neighboring polygon id.
172      * @return
173      */
174     private BorderPoint getBorderPoint(Location start, Location end, int pId, int endPolygonId, Location lastCross, int depth) {
175 
176         // get a 2D projection of ray
177         Line2D ray = new Line2D(start.x, start.y, end.x, end.y);
178         // get the current polygon
179         if (pId < 0) {
180             pId = navMesh.getPolygonId(start);
181         }
182         if (pId < 0 || depth > 250) {
183             return new BorderPoint(start, null);
184         }
185         if (pId == endPolygonId) {
186             return new BorderPoint(end, null);
187         }
188 
189         // how to find end of navmesh?
190         // 1. start on the polygon of starting location
191         // 2. find where the line crosses its border
192         // 3. while there is another polygon behind, repeat
193         // 4. return the last cross
194         int currentPolygonId = pId;
195         int nextPolygonId = -1;
196 
197         // find the first cross
198         Point2D cross = null, cross2 = null;
199         int v1 = -1, v2 = -1;
200         int c2v1 = -1, c2v2 = -1;
201         double[] vertex1 = null;
202         double[] vertex2 = null;
203         int[] polygon = navMesh.getPolygon(currentPolygonId);
204         boolean foundFirstCross = false;
205         for (int i = 0; i < polygon.length; i++) {
206             v1 = polygon[i];
207             v2 = polygon[((i == polygon.length - 1) ? 0 : i + 1)];
208             vertex1 = navMesh.getVertex(v1);
209             vertex2 = navMesh.getVertex(v2);
210             Line2D edge = new Line2D(vertex1[0], vertex1[1], vertex2[0], vertex2[1]);
211             cross = ray.getIntersection(edge);
212 
213             if (cross != null) {
214                 if (((cross.x <= Math.max(edge.p1.x, edge.p2.x) && cross.x >= Math.min(edge.p1.x, edge.p2.x)) || Math.abs(cross.x - edge.p1.x) < 0.0001)
215                         && ((cross.x <= Math.max(ray.p1.x, ray.p2.x) && cross.x >= Math.min(ray.p1.x, ray.p2.x)) || Math.abs(cross.x - ray.p1.x) < 0.0001)) {
216                     // is a cross!
217                     if (foundFirstCross) {
218                         break;
219                     } else {
220                         cross2 = cross;
221                         c2v1 = v1;
222                         c2v2 = v2;
223                         foundFirstCross = true;
224                     }
225                 } else {
226                     // it's not a cross
227                     cross = null;
228                 }
229             }
230         }
231         // now we have the cross.
232 
233         if (cross2 == null) {
234             return new BorderPoint(start, null);
235         } else if (cross == null) {
236             cross = cross2;
237             v1 = c2v1;
238             v2 = c2v2;
239             vertex1 = navMesh.getVertex(v1);
240             vertex2 = navMesh.getVertex(v2);
241         } else {
242             double distToCross = end.getDistance2D(new Location(cross.x, cross.y));
243             double distToCross2 = end.getDistance2D(new Location(cross2.x, cross2.y));
244             if (distToCross > distToCross2) {
245                 cross = cross2;
246                 v1 = c2v1;
247                 v2 = c2v2;
248                 vertex1 = navMesh.getVertex(v1);
249                 vertex2 = navMesh.getVertex(v2);
250             }
251         }
252 
253         // is there another polygon behind?
254         nextPolygonId = navMesh.getNeighbourPolygon(currentPolygonId, v1, v2);
255 
256         Location vertex1Loc = new Location(vertex1);
257         Location vertex2Loc = new Location(vertex2);
258         Location crossLocation = new Location(cross.x, cross.y);
259 
260         double koef = vertex1Loc.getDistance2D(crossLocation) / vertex1Loc.getDistance2D(vertex2Loc);
261 
262         Location border = vertex1Loc.interpolate(vertex2Loc, koef);
263 
264         if (nextPolygonId == -1) {
265             // there is no polygon. we return the cross
266 
267             return new BorderPoint(border.addZ(NavMeshConstants.liftPolygonLocation), vertex2Loc.sub(vertex1Loc));
268 
269         } else {
270             // if there is another polygon, we return recursively border in that direction
271 
272             if (border.getDistance2D(lastCross) < 2.0) {
273                 // move a little so it is in the neighbour polygon
274                 Vector2D directionVector = new Vector2D(end.x - start.x, end.y - start.y);
275                 directionVector.normalize();
276                 border = border.addX(directionVector.getX() * 3);
277                 border = border.addY(directionVector.getY() * 3);
278             }
279 
280             log.log(Level.INFO, "getBorderPoint(): Border location: {0} Next polygon: {1}", new Object[]{border, nextPolygonId});
281             return getBorderPoint(border.addZ(NavMeshConstants.liftPolygonLocation), end, nextPolygonId, endPolygonId, border, ++depth);
282         }
283     }
284 
285     /**
286      * Decides whether jump is needed for following given link.
287      *
288      * @param link Link to analyze.
289      * @return True if jump is needed for successful following of given link.
290      */
291     public boolean needsJump(NavPointNeighbourLink link) {
292         if (link == null) {
293             //It is NavMesh link, no jump is needed
294             return false;
295         } else {
296             //Off mesh link
297             if ((link.getFlags() & LinkFlag.JUMP.get()) != 0) {
298                 //Jump flag present
299                 return true;
300             }
301             if (link.isForceDoubleJump()) {
302                 //Flag for double jump is present
303                 return true;
304             }
305             if (link.getNeededJump() != null) {
306                 //Jump information is present
307                 return true;
308             }
309         }
310 
311         return false;
312     }
313     
314     // =======
315     // JUMPING
316     // =======
317 
318     /**
319      * Compute power for given jump.
320      *
321      * @param start Start location of the jump.
322      * @param boundaries Jump boundaries for given link.
323      * @param velocity Current velocity of the bot.
324      * @param jumpAngleCos Cos of angle of direction of the movement to the
325      * direction of the link.
326      * @return Power of the jump.
327      */
328     public Double computeJump(Location start, JumpBoundaries boundaries, double velocity, double jumpAngleCos) {
329     	Location end = boundaries.getLandingTarget();
330     	double distance2d = getDistance2D(start, boundaries.getLandingTarget(), jumpAngleCos);
331     	double targetZ = end.z - start.z;
332         debug("Jump {0} --(D3D:{1}|D2D:{2}|D2DC:{3}|DZ:{4})--> {5} [Velocity {6}]", new Object[]{start, end.getDistance(start), end.getDistance2D(start), distance2d, targetZ, end, velocity});
333         
334         //Try to jump to landing target
335         double timeToPassDistance = getTimeToPassDistance(distance2d, velocity) - 0.055;
336         Double force = computeJump(targetZ, timeToPassDistance, jumpAngleCos);
337 
338         double originalTime = timeToPassDistance;
339         int jumpKoef = originalTime > JUMP_PEEK_TIME ? 2 : 1;
340 
341         if (force.equals(Double.NaN) && timeToPassDistance < jumpKoef * JUMP_PEEK_TIME && targetZ > 0) {
342             force = getPowerForJumpByZDiff(targetZ);
343         }
344 
345         if (force.equals(Double.NaN) || force < 0) {
346             return force;
347         }
348 
349         if (log.isLoggable(Level.FINER)) {
350             debug("Computed force before collision adjustment: {0}", force);
351         }
352 
353         Location collisionLocation = getCollisionLocation(boundaries);
354         if (collisionLocation == null) {
355             return force;
356         } else {
357             double collisionDistance = getDistance2D(start, collisionLocation, jumpAngleCos);
358             double timeToCollision = getTimeToPassDistance(collisionDistance, velocity);
359 
360             double collidingTime = JUMP_PEEK_TIME * (force <= MAX_SINGLE_JUMP_POWER ? 1 : 2) - timeToCollision;
361             if (collidingTime > 0) {
362 
363                 Location edgeDirection = boundaries.getTargetEdgeDirection().setZ(0).getNormalized();
364                 Location computedDirection = boundaries.getLandingTarget().sub(boundaries.getTakeOffMax()).setZ(0).getNormalized();
365                 Location verticalDirection = new Location(edgeDirection.y, -edgeDirection.x);
366 
367                 double angleCos = verticalDirection.dot(computedDirection);
368 
369                 //Collision will occur...
370                 double collisionCoef = 1 + (collidingTime / JUMP_PEEK_TIME) * (1 - angleCos);
371                 force = Math.min(MAX_DOUBLE_JUMP_POWER, force * collisionCoef);
372                 debug("Possible jump collision detected, adjusting power. NEW POWER: {0}, Angle cos: {1}, Colliding time: {2}", new Object[]{force, angleCos, collidingTime});
373             }
374 
375             return force;
376         }
377 
378     }
379 
380     /**
381      * Compute jump power.
382      *
383      * @param targetZ Difference between start and end Z coordinate.
384      * @param timeToPassDistance Time it will take to pass the distance from
385      * start to end.
386      * @param jumpAngleCos Cos of angle of direction of the movement to the
387      * direction of the link.
388      * @return
389      */
390     public Double computeJump(double targetZ, double timeToPassDistance, double jumpAngleCos) {
391 
392         if (!isJumpable(timeToPassDistance, targetZ)) {
393             //We are not able to jump there
394             debug("We are not able to jump there! Time: {0} Z: {1}", new Object[]{timeToPassDistance, targetZ});
395             return Double.NaN;
396         }
397 
398         //Jump delay time
399         if (log.isLoggable(Level.FINER)) {
400             debug("Computing jump. Time to pass the distance: {0}", timeToPassDistance);
401         }
402 
403         Double power = Double.NaN;
404 
405         if (isSingleJumpable(timeToPassDistance, targetZ)) {
406             debug("Computing jump. Single jump should suffice.");
407             //TODO: Check - COrrection -> Jump Delay
408             power = getSingleJumpPower(targetZ, timeToPassDistance);
409         }
410         //Not single jumpable, or single jump power computation failed.
411         if (power.equals(Double.NaN)) {
412             debug("Computing jump. Double jump will be needed.");
413             power = getDoubleJumpPower(targetZ, timeToPassDistance, UnrealUtils.FULL_DOUBLEJUMP_DELAY);
414         }
415 
416         return power;
417     }
418     
419     // ========
420     // FALLIING
421     // ========
422     
423     /**
424      * Compute power for given fall.
425      *
426      * @param start Start location of the jump.
427      * @param boundaries Jump boundaries for given link.
428      * @param velocity Current velocity of the bot.
429      * @param jumpAngleCos Cos of angle of direction of the movement to the
430      * direction of the link.
431      * @return Power of the jump.
432      */
433     public Double computeFall(Location start, JumpBoundaries boundaries, double velocity, double jumpAngleCos) {
434     	Location end = boundaries.getLandingTarget();
435     	
436     	double distance2D = getDistance2D(start, boundaries.getLandingTarget(), jumpAngleCos);
437     	
438     	double targetZ = end.z - start.z;
439         
440         debug("Fall {0} --(D3D:{1}|D2D:{2}|D2DC:{3}|DZ:{4})--> {5} [Velocity {6}]", new Object[]{start, end.getDistance(start), end.getDistance2D(start), distance2D, targetZ, end, velocity});
441         
442         double fallSpeed = 430;        
443         double fallTime = Math.abs(targetZ) / fallSpeed;
444         double fallDistance2D = velocity * fallTime;
445         
446         double remainingDistance2D = distance2D - fallDistance2D;
447         
448         if (remainingDistance2D < 0) {
449         	debug("Remaining distance after fall " + ((int)remainingDistance2D) + " < 0 => perform only small jump");
450         	return 110.0d;
451         }
452         
453         //Try to jump to landing target
454         double timeToPassDistance = getTimeToPassDistance(distance2D, velocity) - 0.055;
455         Double force = computeFall(targetZ, timeToPassDistance, jumpAngleCos);
456 
457         double originalTime = timeToPassDistance;
458         int jumpKoef = originalTime > JUMP_PEEK_TIME ? 2 : 1;
459 
460         if (force.equals(Double.NaN) && timeToPassDistance < jumpKoef * JUMP_PEEK_TIME && targetZ > 0) {
461             force = getPowerForJumpByZDiff(targetZ);
462         }
463 
464         if (force.equals(Double.NaN) || force < 0) {
465             return force;
466         }
467 
468         if (log.isLoggable(Level.FINER)) {
469             debug("Computed force before collision adjustment: {0}", force);
470         }
471 
472         Location collisionLocation = getCollisionLocation(boundaries);
473         if (collisionLocation == null) {
474             return force;
475         } else {
476             double collisionDistance = getDistance2D(start, collisionLocation, jumpAngleCos);
477             double timeToCollision = getTimeToPassDistance(collisionDistance, velocity);
478 
479             double collidingTime = JUMP_PEEK_TIME * (force <= MAX_SINGLE_JUMP_POWER ? 1 : 2) - timeToCollision;
480             if (collidingTime > 0) {
481 
482                 Location edgeDirection = boundaries.getTargetEdgeDirection().setZ(0).getNormalized();
483                 Location computedDirection = boundaries.getLandingTarget().sub(boundaries.getTakeOffMax()).setZ(0).getNormalized();
484                 Location verticalDirection = new Location(edgeDirection.y, -edgeDirection.x);
485 
486                 double angleCos = verticalDirection.dot(computedDirection);
487 
488                 //Collision will occur...
489                 double collisionCoef = 1 + (collidingTime / JUMP_PEEK_TIME) * (1 - angleCos);
490                 force = Math.min(MAX_DOUBLE_JUMP_POWER, force * collisionCoef);
491                 debug("Possible jump collision detected, adjusting power. NEW POWER: {0}, Angle cos: {1}, Colliding time: {2}", new Object[]{force, angleCos, collidingTime});
492             }
493 
494             return force;
495         }
496 
497     }
498 
499     /**
500      * Compute jump power.
501      *
502      * @param targetZ Difference between start and end Z coordinate.
503      * @param timeToPassDistance Time it will take to pass the distance from
504      * start to end.
505      * @param jumpAngleCos Cos of angle of direction of the movement to the
506      * direction of the link.
507      * @return
508      */
509     public Double computeFall(double targetZ, double timeToPassDistance, double jumpAngleCos) {
510 
511         if (!isJumpable(timeToPassDistance, targetZ)) {
512             //We are not able to jump there
513             debug("We are not able to jump there! Time: {0} Z: {1}", new Object[]{timeToPassDistance, targetZ});
514             return Double.NaN;
515         }
516 
517         //Jump delay time
518         if (log.isLoggable(Level.FINER)) {
519             debug("Computing jump. Time to pass the distance: {0}", timeToPassDistance);
520         }
521 
522         Double power = Double.NaN;
523 
524         if (isSingleJumpable(timeToPassDistance, targetZ)) {
525             debug("Computing jump. Single jump should suffice.");
526             //TODO: Check - COrrection -> Jump Delay
527             power = getSingleJumpPower(targetZ, timeToPassDistance);
528         }
529         //Not single jumpable, or single jump power computation failed.
530         if (power.equals(Double.NaN)) {
531             debug("Computing jump. Double jump will be needed.");
532             power = getDoubleJumpPower(targetZ, timeToPassDistance, UnrealUtils.FULL_DOUBLEJUMP_DELAY);
533         }
534 
535         return power;
536     }
537     
538     // =====
539     // UTILS
540     // =====
541 
542     private double getDistance2D(Location start, Location end, double jumpAngleCos) {
543 
544         double distance2d = start.getDistance2D(end);
545        // debug("Computing jump. Distance2D: {0} AngleCos: {1}", new Object[]{distance2d, jumpAngleCos});
546         if (jumpAngleCos > 0) {
547             distance2d = start.getDistance2D(end) / jumpAngleCos;
548             //debug("Computing jump. Distance2D after angle correction: {0}", distance2d);
549         } else {
550             //debug("Computing jump. Jump angle is > 90, no angle correction: {0}", distance2d);
551         }
552         return distance2d;
553     }
554 
555     /**
556      * Whether the given jump is doable.
557      *
558      * @param start Start of the jump.
559      * @param end End of the jump.
560      * @param velocity Current velocity.
561      * @return Whether the given jump is doable.
562      */
563     public boolean isJumpable(Location start, Location end, double velocity) {
564         if (start == null || end == null) {
565             return false;
566         }
567 
568         if (end.z - start.z > MAX_JUMP_HEIGHT) {
569             //We cannot jump higher than MAX_JUMP_HEIGHT.
570             return false;
571         }
572 
573         double distance2d = start.getDistance2D(end);
574 
575         return isJumpable(distance2d, velocity, end.z - start.z);
576     }
577 
578     private boolean isJumpable(double distance2d, double velocity, double zDiff) {
579         double timeToPassDistance = getTimeToPassDistance(distance2d, velocity);
580         return isJumpable(timeToPassDistance, zDiff);
581     }
582 
583     private boolean isJumpable(double timeToPassDistance, double zDiff) {
584         double computedZDiff;
585         double maxTime = 2 * JUMP_PEEK_TIME;
586         if (timeToPassDistance < maxTime) {
587             computedZDiff = MAX_JUMP_HEIGHT;
588         } else {
589             computedZDiff = getZDiffForJump(MAX_DOUBLE_JUMP_POWER, timeToPassDistance, true, 0.39);
590         }
591 
592         return computedZDiff >= zDiff;
593     }
594 
595     private double getTimeToPassDistance(double distance2d, double velocity) {
596         //return distance2d / velocity;
597         //TODO: Inspect - strange behaviour
598         if (distance2d < velocity * SPEED_BOOST_DELAY) {
599             return distance2d / velocity;
600         } else {
601             return SPEED_BOOST_DELAY + (distance2d - velocity * SPEED_BOOST_DELAY) / (velocity * JUMP_SPEED_BOOST);
602         }
603     }
604 
605     private static final double JUMP_SPEED_BOOST = 1.08959;
606 
607     //
608     // JUMP EQUATION - SINGLE JUMP
609     // z = -475 * t^2 + power * t
610     //
611     // t = Unreal time
612     //
613     //
614     // JUMP EQUATION - DOUBLE JUMP
615     // z = -475 * t^2 + power * t + koef * (t - delay)
616     //
617     // koef = (power - 340) * 1.066 + 2 
618     //
619     // t = Unreal time
620     //
621     private double getZDiffForJump(double power, double deltaTime, boolean isDoubleJump, double delay) {
622         double z = 0;
623 
624         //Equation for single jump
625         z += -475 * (deltaTime * deltaTime) + Math.min(power, MAX_SINGLE_JUMP_POWER) * deltaTime;
626         if (isDoubleJump) {
627             //Double jump specific
628             z += ((power - MAX_SINGLE_JUMP_POWER) * 1.066 + 2) * (deltaTime - delay);
629         }
630         return z;
631     }
632 
633     private double getSingleJumpPower(double targetZ, double time) {
634 
635         double power = (targetZ + 475 * time * time) / time;
636 
637         if (power > MAX_SINGLE_JUMP_POWER) {
638             return Double.NaN;
639         }
640 
641         return power;
642     }
643 
644     private double getDoubleJumpPower(double targetZ, double time, double delay) {
645         double power = 0;
646 
647         if (time < delay) {
648             debug("Computing double jump power. Time is lower than delay, postponing result.");
649             power = getPowerForJumpByZDiff(targetZ);
650             if (power < 0) {
651                 return power;
652             }
653             return Double.NaN;
654         } else {
655 
656             //power = (targetZ + 475 * time * time + 360.5 * (time - delay)) / (2.066 * time - 1.066 * delay);
657             power = (targetZ + 475 * time * time + 20 * time - 140) / (1.066 * (time - delay));
658             debug("Computing double jump power. targetZ: {0}, Time: {1}, Delay: {2}, POWER: {3}", new Object[]{targetZ, time, delay, power});
659 
660         }
661 
662         if (power > MAX_DOUBLE_JUMP_POWER) {
663             return Double.NaN;
664         }
665 
666         return power;
667     }
668 
669     private Location getNavMeshPoint(Location from, Location direction, double distance) {
670 
671         return from.interpolate(direction, distance / from.getDistance2D(direction));
672     }
673 
674     private boolean isSingleJumpable(double timeToPassDistance, double zDiff) {
675         if (zDiff > MAX_SINGLE_JUMP_HEIGHT) {
676             //We cannot jump higher than MAX_JUMP_HEIGHT.
677             return false;
678         }
679 
680         if (timeToPassDistance < JUMP_PEEK_TIME) {
681             return true;
682         }
683 
684         double computedZDiff = getZDiffForJump(MAX_SINGLE_JUMP_POWER, timeToPassDistance, false, 0);
685 
686         return computedZDiff >= zDiff;
687     }
688 
689     public Location getCollisionLocation(JumpBoundaries boundaries) {
690 
691         if (!boundaries.isJumpUp()) {
692             return null;
693         }
694         if (boundaries.getTargetEdgeDirection() == null) {
695             return null;
696         }
697 
698         Location edgeDirection = boundaries.getTargetEdgeDirection().setZ(0).getNormalized();
699         Location computedDirection = boundaries.getLandingTarget().sub(boundaries.getTakeOffMax()).setZ(0).getNormalized();
700         Location verticalDirection = new Location(edgeDirection.y, -edgeDirection.x);
701 
702         double angleCos = verticalDirection.dot(computedDirection);
703 
704         double xAngle = edgeDirection.dot(computedDirection);
705         if (Math.abs(xAngle) < Math.cos(Math.PI / 3)) {
706             debug("Computing collision. Ignoring collision. Edge to mesh angle cos: {0}", xAngle);
707             return null;
708         }
709         debug("Computing collision. Angle cos: {0}", angleCos);
710 
711         double correctionDistance = (2 * BOT_RADIUS) / angleCos;
712 
713         double koef = correctionDistance / boundaries.getLandingTarget().getDistance2D(boundaries.getTakeOffMax());
714 
715         Location collisionLocation;
716         if (koef > 1.0) {
717             collisionLocation = boundaries.getTakeOffMax();
718         } else {
719             collisionLocation = boundaries.getLandingTarget().interpolate(boundaries.getTakeOffMax(), koef);
720         }
721 
722         return collisionLocation;
723     }
724 
725     public Location getNearestMeshDirection(Location location, Location direction) {
726 
727         //TODO: Not good enough - centre of polygon not suitable for big polygons
728         NavMeshPolygon poly = navMesh.getNearestPolygon(location);
729 
730         Line2D ray = new Line2D(location.x, location.y, location.x + direction.x * 10000, location.y + direction.y * 10000);
731 
732         int[] polygon = navMesh.getPolygon(poly.getPolygonId());
733         for (int i = 0; i < polygon.length; i++) {
734             int v1 = polygon[i];
735             int v2 = polygon[((i == polygon.length - 1) ? 0 : i + 1)];
736             double[] vertex1 = navMesh.getVertex(v1);
737             double[] vertex2 = navMesh.getVertex(v2);
738             Line2D edge = new Line2D(vertex1[0], vertex1[1], vertex2[0], vertex2[1]);
739             Point2D cross = ray.getIntersection(edge);
740             if (cross != null) {
741                 if (((cross.x <= Math.max(edge.p1.x, edge.p2.x) && cross.x >= Math.min(edge.p1.x, edge.p2.x)) || Math.abs(cross.x - edge.p1.x) < 0.0001)
742                         && ((cross.x <= Math.max(ray.p1.x, ray.p2.x) && cross.x >= Math.min(ray.p1.x, ray.p2.x)) || Math.abs(cross.x - ray.p1.x) < 0.0001)) {
743                     // is a cross!
744                     Location vertex1Loc = new Location(vertex1);
745                     Location vertex2Loc = new Location(vertex2);
746 
747                     return vertex2Loc.sub(vertex1Loc);
748                 }
749             }
750         }
751         return null;
752     }
753 
754     public double getCorrectedVelocity(double velocity, boolean isAccelerating) {
755         if (!isAccelerating) {
756 
757             return velocity;
758         } else {
759             return Math.min(UnrealUtils.MAX_VELOCITY, 0.9788 * velocity + 111);
760         }
761     }
762 
763     public double getCorrectedAngle(double angleCos, boolean isFirstStep) {
764         if (isFirstStep) {
765             return angleCos;
766         }
767         return Math.cos(Math.acos(angleCos) / 2);
768     }
769 
770     private double getPowerForJumpByZDiff(double zDiff) {
771         //Reserve
772         zDiff += 5;
773 
774         if (zDiff < MAX_SINGLE_JUMP_HEIGHT) {
775             return 3.87 * zDiff + 111;
776         } else {
777             return 3.136 * zDiff + 287;
778         }
779     }
780     
781     private void debug(String msg) {
782     	log.finer("      +-- " + msg);
783     }
784     
785     private void debug(String msg, Object... objects) {
786     	log.log(Level.FINER, "      +-- " + msg, objects);
787     }
788 
789 }