View Javadoc

1   /*
2    * Copyright (C) 2013 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;
18  
19  /**
20   *
21   * @author Jakub Tomek
22   */
23  import cz.cuni.amis.pogamut.base.agent.navigation.IPathFuture;
24  import cz.cuni.amis.pogamut.base.agent.navigation.IPathPlanner;
25  import cz.cuni.amis.pogamut.base.agent.navigation.impl.PrecomputedPathFuture;
26  import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
27  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
28  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
29  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.DrawStayingDebugLines;
30  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
31  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
32  import cz.cuni.amis.pogamut.ut2004.factory.guice.remoteagent.UT2004ServerFactory;
33  import cz.cuni.amis.pogamut.ut2004.factory.guice.remoteagent.UT2004ServerModule;
34  import cz.cuni.amis.pogamut.ut2004.server.impl.UT2004Server;
35  import cz.cuni.amis.pogamut.ut2004.utils.LinkFlag;
36  import cz.cuni.amis.pogamut.ut2004.utils.UT2004ServerRunner;
37  import cz.cuni.amis.utils.exception.PogamutException;
38  import java.io.*;
39  import java.util.*;
40  import java.util.logging.Level;
41  import javax.vecmath.Vector2d;
42  import math.geom2d.Point2D;
43  import math.geom2d.Shape2D;
44  import math.geom2d.line.Line2D;
45  import math.geom2d.line.StraightLine2D;
46  import math.geom3d.Point3D;
47  import math.geom3d.Vector3D;
48  import math.geom3d.line.StraightLine3D;
49  import math.geom3d.plane.Plane3D;
50  
51  /**
52   * Main class of Navigation Mesh module
53   * @author Jakub Tomek
54   */
55  public class NavMesh implements IPathPlanner<ILocated>  {
56      private ArrayList<double[]> verts = new ArrayList<double[]>();
57      private ArrayList<int[]>  polys = new ArrayList<int[]>();
58      private ArrayList<ArrayList<Integer>> vertsToPolys;
59      private ArrayList<Boolean> safeVertex;
60      private BSPNode bspTree;
61      private BSPNode biggestLeafInTree;
62      private ArrayList<OffMeshPoint> offMeshPoints;
63      private ArrayList<ArrayList<OffMeshPoint>> polysToOffMeshPoints;
64      private LevelGeometry levelGeometry;
65      
66      private UT2004Server server;
67      
68      public Random random;
69      
70      /**
71       * Reads Navmesh from file - creates data structure
72       */
73      public NavMesh(boolean loadLevelGeometry) throws FileNotFoundException, IOException {
74          
75          random = new Random();      
76          
77          // 1. create internal server and read navmesh file
78          UT2004ServerModule serverModule = new UT2004ServerModule();
79          UT2004ServerFactory serverFactory = new UT2004ServerFactory(serverModule);
80          UT2004ServerRunner serverRunner = new UT2004ServerRunner(serverFactory);
81          this.server = (UT2004Server) serverRunner.startAgent(); 
82          String mapName = server.getMapName();
83          
84          // 2. try to read all the data from already proccesed navmesh
85          boolean dataRestored = false;
86          System.out.println("Looking for previously saved data core...");
87          try {
88              ObjectInputStream in = new ObjectInputStream(new FileInputStream(NavMeshConstants.processedMeshDir + "\\" + mapName + ".processed"));
89              NavMeshCore core = (NavMeshCore) in.readObject();  
90              this.biggestLeafInTree = core.biggestLeafInTree;
91              this.bspTree = core.bspTree;
92              this.polys = core.polys;
93              this.verts = core.verts;
94              this.vertsToPolys = core.vertsToPolys;  
95              this.offMeshPoints = core.offMeshPoints;
96              this.polysToOffMeshPoints = core.polysToOffMeshPoints;   
97              this.safeVertex = core.safeVertex;
98              System.out.println("Data core has been restored. Great!");
99              dataRestored = true;
100         } catch (Exception e) {
101           System.out.println("Previously saved data core could not be restored. New NavMesh will be processed. Exception message: " + e.getMessage());
102         }
103         
104         
105         if(!dataRestored) {
106             // 3. read all vertices and polygons from file
107             String fileName = NavMeshConstants.pureMeshReadDir + "\\" + mapName + ".navmesh";
108             BufferedReader br = new BufferedReader(new FileReader(fileName));     
109             String line;
110             
111             while((line = br.readLine()) != null) {
112                 String[] toks = line.split("[ \\t]");
113 
114                 if(toks[0].equals("v")) {
115                     double[] v = new double[3];
116                     v[0] = Double.parseDouble(toks[1]);
117                     v[1] = Double.parseDouble(toks[2]);
118                     v[2] = Double.parseDouble(toks[3]);
119                     verts.add(v);
120                 }
121                 if(toks[0].equals("p")) {
122                     int[] p = new int[toks.length-1];
123                     for(int i = 0; i<toks.length-1; i++) {
124                         p[i] = Integer.parseInt(toks[i+1]);
125                     }              
126                     polys.add(p);
127                 }              
128             }
129 
130             // 4. when vertices and polygons are done creating, we create an array mapping vertices to polygons
131             resetVertsToPolys();
132 
133             // 5. mark safe and unsafe vertices
134             resetSafeVerts();
135             
136             // 6. create a BSP tree structure
137             resetBSPTree();
138 
139             // 7. get rid of unreachable polygons
140             eliminateUnreachablePolygons();  
141          
142             // 8. create off-mesh connections
143             resetOffMeshConnections();           
144            
145             // 9. save data core for next time
146             System.out.println("Writing navmesh core to a file...");
147             try {
148                 NavMeshCore core = new NavMeshCore();
149                 core.biggestLeafInTree = this.biggestLeafInTree;
150                 core.bspTree = this.bspTree;
151                 core.polys = this.polys;
152                 core.verts = this.verts;
153                 core.vertsToPolys = this.vertsToPolys;
154                 core.offMeshPoints = this.offMeshPoints;
155                 core.polysToOffMeshPoints = this.polysToOffMeshPoints;      
156                 core.safeVertex = this.safeVertex;
157                 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(NavMeshConstants.processedMeshDir + "\\" + mapName + ".processed"));
158                 out.writeObject(core);
159                 System.out.println("Writing navmesh to a file complete.");        
160             } catch (Exception e) {
161                 System.out.println("Exception during writing navmesh to a file: " + e.getMessage());
162             }        
163         }
164         
165         // 10. get level geometry
166         if(loadLevelGeometry) {
167             // try to read it from processed file
168             dataRestored = false;
169             System.out.println("Looking for previously saved data level geom...");
170             try {
171                 ObjectInputStream in = new ObjectInputStream(new FileInputStream(NavMeshConstants.processedLevelGeometryDir + "\\" + mapName + ".processed"));
172                 levelGeometry = (LevelGeometry) in.readObject();  
173                 System.out.println("Level geom has been restored. Great!");
174                 dataRestored = true;
175             } catch (Exception e) {
176             System.out.println("Previously saved level geom could not be restored. Exception message: " + e.getMessage());
177             e.printStackTrace();
178             }        
179 
180             if(!dataRestored) {
181                 System.out.println("Reading and building level geometry...");
182                 try { 
183                     levelGeometry = new LevelGeometry(mapName);
184                     System.out.println("Level geometry read and built ok.");
185                     // save levelgeom for next time
186                     try {
187                         System.out.println("Writing level geometry to a file...");
188                         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(NavMeshConstants.processedLevelGeometryDir + "\\" + mapName + ".processed"));
189                         out.writeObject(levelGeometry); 
190                         System.out.println("Level geometry written ok.");
191                     } catch (Exception e) {
192                             System.out.println("Exception during writing level geom to a file: " + e.getMessage());
193                     }  
194                 } catch(Exception e) {
195                     System.out.println("Unable to get level geometry from file. Exception message: "+e.getMessage());
196                     levelGeometry = null;
197                 }        
198             }        
199         }
200 
201     }
202     
203     /**
204      * Overload for constructor
205      */
206     public NavMesh() throws FileNotFoundException, IOException {
207         this(true);
208     }
209     
210     /**
211      * Returns all lines in one long string.
212      */
213     public String toDebugString() {
214         StringBuilder result = new StringBuilder("");      
215         // projdeme vsechny polygony a vykreslime caru vzdy z vrcholu n do n+1
216         for(int i = 0; i < polys.size(); i++) {
217             int[] p = (int[]) polys.get(i);
218             for(int j = 0; j<p.length; j++) {
219                 if(result.length()>0) result.append(";");
220                 // ziskame vrcholy v1 a v2 mezi kterymi vykreslime caru
221                 double[] v1,v2;
222                 v1 = (double[]) verts.get(p[j]);
223                 if(j==p.length-1) v2 = (double[]) verts.get(p[0]);
224                 else v2 = (double[]) verts.get(p[j+1]);
225                 // pridani cary
226                 result.append(v1[0]+","+v1[1]+","+v1[2]+";"+v2[0]+","+v2[1]+","+v2[2]);
227             }
228         }     
229         return result.toString();
230     }
231     
232     /**
233      * Restricted alternative to toDebugString() - returns only one polygon as string
234      */
235     public String polygonToDebugString(int polygonNumber) {
236         StringBuilder result = new StringBuilder("");      
237         // projdeme vsechny polygony a vykreslime caru vzdy z vrcholu n do n+1
238         int[] p = (int[]) polys.get(polygonNumber);
239         for(int j = 0; j<p.length; j++) {
240             if(result.length()>0) result.append(";");
241             // ziskame vrcholy v1 a v2 mezi kterymi vykreslime caru
242             double[] v1,v2;
243             v1 = (double[]) verts.get(p[j]);
244             if(j==p.length-1) v2 = (double[]) verts.get(p[0]);
245             else v2 = (double[]) verts.get(p[j+1]);
246             // pridani cary
247             result.append(v1[0]+","+v1[1]+","+v1[2]+";"+v2[0]+","+v2[1]+","+v2[2]);
248         }     
249         return result.toString();    
250     }
251     
252     /**
253      * computes debug string of one off-mesh edge to be drawn.
254      * @param oe
255      * @return 
256      */
257     private String offMeshEdgeToDebugString(OffMeshEdge oe) {
258         StringBuilder result = new StringBuilder("");      
259         Location l1 = oe.getFrom().getLocation();
260         Location l2 = oe.getTo().getLocation();
261         result.append(l1.x+","+l1.y+","+l1.z+";"+l2.x+","+l2.y+","+l2.z);
262         
263         // add arrow at the end
264        double[] vector = new double[3];
265        vector[0] = l1.x - l2.x;
266        vector[1] = l1.y - l2.y;
267        vector[2] = l1.z - l2.z;
268        // normalize the vector to small lenght
269        double length = Math.sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]);
270        vector[0] *= 1/length * 20;
271        vector[1] *= 1/length * 20;
272        vector[2] *= 1/length * 20;
273        Point3D cross = new Point3D(l2.x+vector[0], l2.y+vector[1], l2.z+vector[2]);
274        // to find edges of the arrow, we take 2D normal vector. And half size
275        double[] vector2 = new double[2];
276        vector2[0] = vector[1]/2;
277        vector2[1] = -vector[0]/2;
278        Point3D arrowPoint1 = new Point3D(cross.getX()+vector2[0], cross.getY()+vector2[1], cross.getZ());
279        Point3D arrowPoint2 = new Point3D(cross.getX()-vector2[0], cross.getY()-vector2[1], cross.getZ()); 
280        // we have all the points, now just connect them 
281        result.append(";");
282        result.append(arrowPoint1.getX()+","+arrowPoint1.getY()+","+arrowPoint1.getZ()+";"+l2.x+","+l2.y+","+l2.z);
283        result.append(";");
284        result.append(arrowPoint2.getX()+","+arrowPoint2.getY()+","+arrowPoint2.getZ()+";"+l2.x+","+l2.y+","+l2.z);
285        result.append(";");
286        result.append(arrowPoint1.getX()+","+arrowPoint1.getY()+","+arrowPoint1.getZ()+";"+arrowPoint2.getX()+","+arrowPoint2.getY()+","+arrowPoint2.getZ());
287                  
288        
289        return result.toString();   
290     }
291 
292     /**
293      * Cretaes a string of vector to be drawn from given path
294      * @param path
295      * @return 
296      */
297     private String pathToDebugString(IPathFuture<ILocated> path) {
298         StringBuilder result = new StringBuilder("");      
299         // projdeme vsechny body a vykreslime caru vzdy z vrcholu n do n+1
300         ILocated p0 = null;
301         List<ILocated> pathList = path.get();
302         for(ILocated p1 : pathList) {
303             if(result.length()>0) result.append(";");
304             if(p0 != null) {
305                 result.append(Math.round(p0.getLocation().x)+","+Math.round(p0.getLocation().y)+","+Math.round(p0.getLocation().z)+";"+Math.round(p1.getLocation().x)+","+Math.round(p1.getLocation().y)+","+Math.round(p1.getLocation().z));
306             }
307             p0 = p1;
308         }     
309         return result.toString(); 
310     }
311     
312      /**
313      * Draws navmesh in game
314      */   
315     public void draw() {
316         // draw polygons
317         for(int i = 0; i < this.polyCount(); i++) {
318             DrawStayingDebugLines d = new DrawStayingDebugLines();
319             String lines = this.polygonToDebugString(i);
320             d.setVectors(lines);
321             d.setColor(new Location(255, 255, 255));
322             d.setClearAll(i==0);                
323             server.getAct().act(d);                
324         }
325         
326         // draw off-mesh connections
327         for(OffMeshPoint op : offMeshPoints) {
328             // draw all outgoing edges
329             for(OffMeshEdge oe : op.getOutgoingEdges()) {
330                 DrawStayingDebugLines d = new DrawStayingDebugLines();
331                 String lines = offMeshEdgeToDebugString(oe);
332                 d.setVectors(lines);
333                 d.setColor(NavMeshConstants.getColorForOffMeshConnection(oe, server));
334                 d.setClearAll(false);                
335                 server.getAct().act(d);
336             }
337         }
338     }
339     
340     /**
341      * Draws given lines
342      * @param lines 
343      */
344     public void draw(String lines, Location color) {
345         DrawStayingDebugLines d = new DrawStayingDebugLines();
346         d.setVectors(lines);
347         d.setColor(color);
348         d.setClearAll(false);                
349         server.getAct().act(d);  
350     }
351     
352     /**
353      * Undraws all currently drawn lines
354      */
355     public void unDraw() {
356         DrawStayingDebugLines d = new DrawStayingDebugLines();
357         d.setClearAll(true);                
358         server.getAct().act(d);        
359     }
360     
361     public void drawLevelGeometry(Location color) {
362         levelGeometry.draw(server, color);
363     }
364     
365      /**
366      * Draws one polygon
367      * with the color set in color
368      */   
369     public void drawOnePolygon(int i, Location color) {
370         DrawStayingDebugLines d = new DrawStayingDebugLines();
371         String lines = this.polygonToDebugString(i);
372         d.setVectors(lines);
373         d.setColor(color);
374         d.setClearAll(false);                
375         server.getAct().act(d);                           
376     } 
377     
378      /**
379      * Draws one polygon
380      * with default color (yellow)
381      */   
382     public void drawOnePolygon(int i) {
383         drawOnePolygon(i, new Location(255,255,0));                         
384     }    
385     
386     /**
387      * Draws one atom (polygon or point)
388      * @param atom
389      * @param location 
390      */
391     private void drawAtom(INavMeshAtom atom, Location location) {
392         if(atom.getClass()==NavMeshPolygon.class) {
393             NavMeshPolygon p = (NavMeshPolygon) atom;
394             drawOnePolygon(p.getPolygonId(), location);
395         }
396     }
397     
398     /**
399      * Draws entire list of polygons
400      * @param polygonPath 
401      */
402     public void drawPolygonPath(List<INavMeshAtom> polygonPath, Location location) {
403         for(INavMeshAtom atom : polygonPath) {
404             drawAtom(atom, location);
405         }
406     }
407     
408     /**
409      * Draws the given path 
410      * @param path 
411      */
412     public void drawPath(IPathFuture<ILocated> path, Location color) {
413         
414         // the commented code sometimes doesnt work for soem reason. maybe there is a corrupted point along the path?
415         //String lines = pathToDebugString(path);
416         //System.out.println("path to be drawn: " + lines);
417         //draw(lines,color);
418         List<ILocated> pathList = path.get();
419         for(int i = 0; i<pathList.size()-1; i++) {
420             StringBuilder lines = new StringBuilder();
421             lines.append(pathList.get(i).getLocation().x + ",");
422             lines.append(pathList.get(i).getLocation().y + ",");
423             lines.append(pathList.get(i).getLocation().z + ";");
424             lines.append(pathList.get(i+1).getLocation().x + ",");
425             lines.append(pathList.get(i+1).getLocation().y + ",");
426             lines.append(pathList.get(i+1).getLocation().z + "");
427             draw(lines.toString(), color);
428         }       
429     }
430     
431      /**
432      * Debug method:  
433      * Draws only the polygons in the biggeswt leaf
434      * so that we can see whe they could not have been split any further
435      */   
436     public void drawOnlyBiggestLeaf() {
437         for(int i = 0; i < this.polyCount(); i++) {          
438             if(!biggestLeafInTree.polys.contains(i)) continue;
439             
440             DrawStayingDebugLines d = new DrawStayingDebugLines();
441             String lines = this.polygonToDebugString(i);
442             //System.out.println("polygon["+i+"] lines: " + lines);
443             d.setVectors(lines);
444             d.setColor(new Location(255, 255, 0));
445             d.setClearAll(false);                
446             server.getAct().act(d);
447         }    
448     }
449     
450     
451     /**
452      * Returns the number of polygons in navmesh
453      */
454     public int polyCount() {
455         return polys.size();
456     }
457     
458     /**
459      * Returns the number of vertices in navmesh
460      */    
461     public int vertCount() {
462         return verts.size();
463     }
464 
465      /**
466      * gets a polygon by its order
467      */ 
468     public int[] getPolygon(int i) {
469         int[] p = ((int[])polys.get(i)).clone();
470         return p;
471     }
472     
473      /**
474      * gets a vertex by its order
475      */ 
476     public double[] getVertex(int i) {
477         double[] v = ((double[])verts.get(i)).clone();
478         return v;
479     }
480 
481     /**
482      * Builds the resetVertsToPolys mapping array
483      */
484     private void resetVertsToPolys() {
485         System.out.println("Setting vertsToPolys mapping array...");
486         vertsToPolys = new ArrayList();
487         for(int i = 0; i < verts.size(); i++) vertsToPolys.add(new ArrayList());
488         for(int i = 0; i < polys.size(); i++) {
489             int[] p = (int[]) polys.get(i);
490             for(int j = 0; j < p.length; j++) {
491                 ArrayList p2 = (ArrayList) vertsToPolys.get(p[j]);
492                 if(!p2.contains(i)) p2.add(i);
493             }
494         }
495     }
496     
497     /**
498      * Resets the array of boolean values saying whether a vertex is at the edge of navmesh
499      */
500     private void resetSafeVerts() {
501         System.out.println("Setting safe vertices...");
502         safeVertex = new ArrayList<Boolean>();
503         int safeCount = 0;
504         
505         // check vertices one by one
506         for(int v1 = 0; v1<verts.size(); v1++) {
507             System.out.println("Looking at vertex "+v1+"...");
508             
509             // angles of polygons around must give 2 pi in sum
510             double sum = 0;
511             
512             ArrayList<Integer> polys = vertsToPolys.get(v1);
513             
514             for(Integer pId : polys) {
515                 //System.out.println("Looking at polygon "+pId+"...");
516                 //this.drawOnePolygon(pId);
517                 int[] polygon = getPolygon(pId);
518                 // we must fint the three vertices creating angle around vertex i
519                 int v0 = -1, v2 = -1;
520                 for(int i = 0; i<polygon.length; i++) {
521                     if(polygon[i]==v1) {
522                         v0 = (i==0 ? polygon[polygon.length-1] : polygon[i-1]);
523                         v2 = (i==polygon.length-1 ? polygon[0] : polygon[i+1]);
524                         break;
525                     }
526                 }
527                 // three vertices found, now get the angle
528                 double[] vv0 = getVertex(v0);
529                 double[] vv1 = getVertex(v1);
530                 double[] vv2 = getVertex(v2);
531                 
532                 double a = Math.sqrt( (vv1[0]-vv0[0])*(vv1[0]-vv0[0]) + (vv1[1]-vv0[1])*(vv1[1]-vv0[1]) + (vv1[2]-vv0[2])*(vv1[2]-vv0[2]) );
533                 double b = Math.sqrt( (vv2[0]-vv1[0])*(vv2[0]-vv1[0]) + (vv2[1]-vv1[1])*(vv2[1]-vv1[1]) + (vv2[2]-vv1[2])*(vv2[2]-vv1[2]) );
534                 double c = Math.sqrt( (vv2[0]-vv0[0])*(vv2[0]-vv0[0]) + (vv2[1]-vv0[1])*(vv2[1]-vv0[1]) + (vv2[2]-vv0[2])*(vv2[2]-vv0[2]) );
535                 
536                 
537                 double gama = Math.acos( (a*a + b*b - c*c) / (2*a*b) );
538                 System.out.println("angle gama is "+gama);
539                 //this.drawOnePolygon(pId, new Location(255,255,255));
540                 sum += gama;
541             }
542             System.out.println("sum angle is "+sum);
543             // we give it some tolerance for roundng errors
544             if(sum >= 2 * 3.14) {
545                 safeVertex.add(v1, true);
546                 safeCount++;
547             } else {
548                 safeVertex.add(v1, false);
549             }  
550         }
551         System.out.println("There are " + safeCount + " safe and " + (verts.size() + safeCount) + " unsafe vertices.");
552     }    
553    
554      /**
555      * gets a list of polygons containing this vertex
556      */ 
557     public ArrayList getPolygonsByVertex(int i) {
558         return (ArrayList) ((ArrayList) this.vertsToPolys.get(i)).clone();
559     }
560     
561     /**
562     * gets an array of polygon ids by an polygon id
563     */    
564     public ArrayList getNeighbourIdsToPolygon(int i) {
565         
566         ArrayList neighbours = new ArrayList();
567         int[] p = this.getPolygon(i);
568         
569         for(int j = 0; j<p.length; j++) {
570             ArrayList<Integer> p2 = getPolygonsByVertex(p[j]);
571             for(int k = 0; k < p2.size(); k++) {  
572                 int candidateId = p2.get(k);
573                 //this polygon shares one vertex with the input polygon, but that is not enough. neighbour must share two.
574                 // p[j] is one vertex
575                 int secondVertex = p[((j == p.length-1) ? 0 : j+1)];
576                 int[] candidatePolygon = this.getPolygon(candidateId);
577                 for(int l = 0; l < candidatePolygon.length; l++) {
578                     if(candidatePolygon[l]==secondVertex) {
579                         // its him! ok! shares 2 tertices
580                         if(!neighbours.contains(candidateId) && candidateId!=i) neighbours.add(candidateId); 
581                         break;
582                     }
583                 }
584                 
585             }
586         }
587         return neighbours;
588     }
589     
590     /**
591      * Returns a List of offmeshpoints that are located on target polygon
592      * @param pId
593      * @return 
594      */
595     public List<OffMeshPoint> getOffMeshPoinsOnPolygon(int pId) {
596         return polysToOffMeshPoints.get(pId);
597     }
598     
599     /**
600      * Gets a new BSPTree for this mesh
601      */
602     private void resetBSPTree() {
603         System.out.println("Creating BSP tree...");
604         bspTree = new BSPNode(this, null);
605         for(int i = 0; i<polys.size(); i++) bspTree.polys.add(i);
606         biggestLeafInTree = null;
607         bspTree.build();
608         System.out.println("BSP tree is done building.");
609         System.out.println("Biggest leaf has " + biggestLeafInTree.polys.size() + " polygons.");
610     }
611     
612     /**
613     * Debug method: helps to describe BSP tree by telling the number of polygons in the biggest leaf
614     * (this should not bee too big. 5 is a good number.)
615     */     
616     public int getNumberOfPolygonsInBiggestLeaf() {
617         if(biggestLeafInTree != null) {
618             return biggestLeafInTree.polys.size();
619         }
620         else return -1;
621     }
622     
623     /**
624     * Debug method: sets the biggest leaf so it can be easily found.
625     */     
626     public void setBiggestLeafInTree(BSPNode node) {
627         biggestLeafInTree = node;
628     }
629     
630     /**
631      * 
632      * @param point3D
633      * @return id of polygon on which is this point
634      */
635     public int getPolygonIdByPoint3D(Point3D point3D) {   
636         //System.out.println("trying to fing polygon for location [" + point3D.getX() + ", " + point3D.getY() + ", " + point3D.getZ() + "]");
637         // 2D projection of point
638         Point2D point2D = new Point2D(point3D.getX(), point3D.getY());  
639         // walk through BSP tree
640         BSPNode node = bspTree;
641         while(!node.isLeaf()) {
642             //System.out.println("Searching an inner node. There are " +node.polys.size()+ " polygons and their numbers are: " + node.polys.toString());            
643             StraightLine2D sepLine = node.sepLine;
644             double dist = sepLine.getSignedDistance(point2D);
645             if(dist<0) {
646                 //System.out.println("go to left child");
647                 node = node.left;
648             }
649             if(dist>0) {
650                 //System.out.println("go to right child");
651                 node = node.right;
652             }
653             if(dist==0) {
654                 //System.out.println("Wow, the location is exactly on the border. Let's move a little");
655                 point2D = new Point2D(point3D.getX() + random.nextDouble() -0.5 , point3D.getY() + random.nextDouble() -0.5);
656             }
657         }
658         // now we are in leaf so, we should see the list of possible polygons
659         //System.out.println("The leaf is found. There are " +node.polys.size()+ " polygons and their numbers are: " + node.polys.toString());
660         
661         // now we must choose which polygon is really the one we are staying at, if any
662         ArrayList candidatePolygons = new ArrayList();
663         
664         // are we staying inside in 2D projection? if not, reject this polygon
665         for(int i = 0; i<node.polys.size(); i++) {
666             Integer pId = (Integer) node.polys.get(i);
667             
668             if( polygonContainsPoint(pId, point2D) ) {
669                 //System.out.println("Polygon " +pId+ " contains this location.");
670                 candidatePolygons.add(pId);
671             }
672             else {
673                 //System.out.println("Polygon " +pId+ " does not contain this location.");
674             } 
675         }
676         
677         // now we have the candidtate polygons. the agent is inside each of them in a 2D projection
678         // now we select the one that is closest on z coordinate and return it   
679         //System.out.println("candidatePolygons: " + candidatePolygons.toString());
680         if(candidatePolygons.isEmpty()) return -1;
681         
682         // spocitame vzdalenost od vrcholu 0 na z souradnici. nejmensi vzdalenost = vitez
683         double minDist = NavMeshConstants.maxDistanceBotPolygon;
684         int retPId = -2;
685         for(int i = 0; i < candidatePolygons.size(); i++) {
686             Integer pId = (Integer) candidatePolygons.get(i);
687             int[] polygon = getPolygon(pId);
688             
689             // we take first three points, create plane, calculate distance
690             double[] v1 = getVertex(polygon[0]);
691             double[] v2 = getVertex(polygon[1]);
692             double[] v3 = getVertex(polygon[2]); 
693             Point3D p1 = new Point3D(v1[0], v1[1], v1[2]);
694             Vector3D vector1 = new Vector3D(v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2]);
695             Vector3D vector2 = new Vector3D(v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2]);
696             Plane3D plane = new Plane3D(p1, vector1, vector2);
697             double dist = plane.getDistance(point3D);
698             if(dist < minDist) {
699                 retPId = pId;
700                 minDist = dist;
701             }
702             
703             // watch out for rounding errors!
704             // if those three points are almost in line rounding error could become huge
705             // better try more points
706             if(polygon.length > 3) {
707                 v1 = getVertex(polygon[0]);
708                 v2 = getVertex(polygon[1]);
709                 v3 = getVertex(polygon[3]); 
710                 p1 = new Point3D(v1[0], v1[1], v1[2]);
711                 vector1 = new Vector3D(v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2]);
712                 vector2 = new Vector3D(v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2]);
713                 plane = new Plane3D(p1, vector1, vector2);
714                 dist = plane.getDistance(point3D);
715                 if(dist < minDist) {
716                     retPId = pId;
717                     minDist = dist;
718                 }        
719             }
720             // or even more
721             if(polygon.length > 4) {
722                 v1 = getVertex(polygon[0]);
723                 v2 = getVertex(polygon[2]);
724                 v3 = getVertex(polygon[4]); 
725                 p1 = new Point3D(v1[0], v1[1], v1[2]);
726                 vector1 = new Vector3D(v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2]);
727                 vector2 = new Vector3D(v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2]);
728                 plane = new Plane3D(p1, vector1, vector2);
729                 dist = plane.getDistance(point3D);
730                 if(dist < minDist) {
731                     retPId = pId;
732                     minDist = dist;
733                 }        
734             }              
735         }
736         System.out.println("distance of a point from polygon " +retPId+ " is " + minDist);
737         return retPId;
738     }
739     
740     /**
741      * Gets the id of a polygon that contains this location
742      * @param location
743      * @return id of polygon or value < 0 if the is no such polygon
744      */
745     public int getPolygonIdByLocation(Location location) {
746         return getPolygonIdByPoint3D(new Point3D(location.x, location.y, location.z));
747     }
748     
749     /**
750      * Decides whether the input point is inside of the polygon of navmesh identified by its id.
751      * @param pId
752      * @param point2D
753      * @return 
754      */
755     private boolean polygonContainsPoint(Integer pId, Point2D point2D) {
756        boolean result = true;
757        double rightSide = 0.0;
758        
759        int[] polygon = getPolygon(pId);
760        double[] v1, v2;
761        for(int i = 0; i < polygon.length; i++) {
762            v1 = getVertex(polygon[i]);
763            if(i < polygon.length -1) v2 = getVertex(polygon[i+1]);
764            else  v2 = getVertex(polygon[0]);
765            Point2D p1 = new Point2D(v1[0], v1[1]);
766            Point2D p2 = new Point2D(v2[0], v2[1]);
767            StraightLine2D line = new StraightLine2D(p1, p2);
768            double dist = line.getSignedDistance(point2D);
769            
770            if(rightSide == 0.0) {
771                rightSide = Math.signum(dist);
772            }
773            else {
774                if(rightSide * dist < 0) {
775                    result = false;
776                    break;
777                }
778            }
779        }
780        return result;
781     }
782 
783     /**
784      * Some polygons cannot be reached
785      * we find them with the hhelp of navigation graph
786      * Definition: 1. Any polygon with navigation point is reachable
787      * 2. Any polygon sharing edge with a reachbale polygon is also reachable.  
788      */
789     private void eliminateUnreachablePolygons() {
790         System.out.println("eliminateUnreachablePolygons() starts...");
791         
792         // 1. get list of all navpoints
793         Map<UnrealId, NavPoint> navPoints = (Map<UnrealId, NavPoint>)(Map)server.getWorldView().getAll(NavPoint.class);        
794         
795         // which polygons are reachbale and which are not?
796         boolean[] reachable = new  boolean[polys.size()];
797         // 2. walk through all navpoints and mark all reachable polygons
798         System.out.println("marking reachable polygons...");
799         for(NavPoint navPoint : navPoints.values()) {   
800             Point3D point3D = navPoint.getLocation().asPoint3D();
801             int pId = getPolygonIdByPoint3D(point3D);
802             if(pId < 0) continue; 
803             markAsReachableRecursive(pId, reachable);
804         }
805         // debugging control: how many polygons are unreachable?
806         int reachableCount = 0; int polyDelCount = 0; int vertDelCount = 0;
807         for(int i = 0; i < polys.size(); i++) {
808             if(reachable[i]) reachableCount++;
809         }
810         System.out.println("Marking complete. There are " +reachableCount+ " reachable polygons and " + (polys.size()-reachableCount) + " unreachable polygons.");
811         System.out.println("Deleting unreachable polygons...");
812         for(int i = polys.size()-1; i>=0; i--) {
813             if(!reachable[i]) {
814                 polys.remove(i);
815                 polyDelCount++;
816             }
817         }
818         System.out.println("Reseting vertsToPolys...");
819         resetVertsToPolys();
820         System.out.println("Deleting unused vertices...");
821         for(int i = vertsToPolys.size()-1; i>=0; i--) {
822             ArrayList polygons = (ArrayList) vertsToPolys.get(i);
823             if(polygons.isEmpty()) {
824                 verts.remove(i);
825                 vertDelCount++;
826                 // after removing a vertex (and moving all following vertices by -1) we must recalculate the vertices in polygons
827                 for(int j = 0; j < polys.size(); j++) {
828                     int[] polygon = (int[]) polys.get(j);
829                     for(int k = 0; k < polygon.length; k++) {
830                         if(polygon[k] > i) polygon[k]--;
831                     }
832                 }
833             }         
834         }
835         System.out.println("Deleting done. " + polyDelCount + " Polygons and " + vertDelCount + " vertices were deleted.");
836 
837         // we have changed the polygon a and vertex numbers, therefore we must reset BSPTree and vertsToPolys mapping array
838         resetVertsToPolys();
839         resetSafeVerts();
840         resetBSPTree();
841     }
842 
843     /**
844      * Helping function used only in method eliminateUnreachablePolygons
845      * @param pId
846      * @param reachable 
847      */
848     private void markAsReachableRecursive(int pId, boolean[] reachable) {
849         if(reachable[pId]) return;
850         reachable[pId] = true;
851         ArrayList neighbours = getNeighbourIdsToPolygon(pId);
852         for(int i = 0; i < neighbours.size(); i++) {
853             markAsReachableRecursive((Integer)neighbours.get(i), reachable);
854         }
855     }
856 
857     /**
858      * Creates off-mesh connections between polygons that does not share an edge, but there
859      * is a connection from one to the other in navigation graph.
860      * The endpoints are not necessarily polygons. Thay also may be off-mesh navpoints.
861      * This method also creates list of off-mesh points and also creates mappings between polygons and these points
862      */
863     private void resetOffMeshConnections() {
864         System.out.println("Creating off-mesh connections...");
865        
866         Map<UnrealId, OffMeshPoint> offPoints = new HashMap<UnrealId, OffMeshPoint>();
867         
868         // 1. get list of all navpoints
869         Map<UnrealId, NavPoint> navPoints = (Map<UnrealId, NavPoint>)(Map)server.getWorldView().getAll(NavPoint.class);        
870         
871         // offPoint definition: it has more than 0 offMeshEdges (outgoing or incoming)
872         // 1. we act like if every navpoint was an offpoint and calculate all outgoing & incoming offedges
873         // 2. any point with any (outgoing or incoming) out edges will be added to list of navmesh
874         
875         // for each navpoint create offpoint
876         for(Map.Entry<UnrealId, NavPoint> entry : navPoints.entrySet()) {
877             NavPoint np = entry.getValue();
878             UnrealId uId = entry.getKey();
879             int pId = this.getPolygonIdByPoint3D(np.getLocation().asPoint3D());
880             OffMeshPoint op = new OffMeshPoint(np, pId);
881             offPoints.put(uId, op);
882         }
883         
884         // now again - for each navpoint check his outgoing edges
885         // if we find an offedge, we add it to both start and target
886         for(Map.Entry<UnrealId, NavPoint> entry : navPoints.entrySet()) {
887             NavPoint np = entry.getValue();
888             UnrealId uId = entry.getKey();
889             
890             // check all outgoing edges
891             for(Map.Entry<UnrealId, NavPointNeighbourLink> entry2 : np.getOutgoingEdges().entrySet()) {
892                 NavPointNeighbourLink link = entry2.getValue();
893                 UnrealId uId2 = entry2.getKey();
894                 
895                 
896                 NavPoint target = link.getToNavPoint();
897                 
898                 System.out.println("Checking edge from navpoint "+uId+" to navpoint "+target.getId()+"...");
899                 
900                 // Flags. Important thing. Why?
901                 // Some edges will be considered as off-mesh immidietely, without checking mesh (lift)
902                 // Some edges will be ignored immidietely (ladder, swimming, etc.)
903                 // maybe put this code block into separate method/class/NavMeshConsts?
904                 boolean forceIgnore = false;
905                 boolean forceAdd = false;
906                 boolean addThisEdge = false;
907                 // point flags
908                 if(np.isLiftCenter()) { forceAdd = true; }
909                 if(target.isLiftCenter()) { forceAdd = true; }
910                 // edge flags
911                 int linkFlags = link.getFlags();
912                 if ((linkFlags & LinkFlag.DOOR.get()) > 0) {}
913                 if ((linkFlags & LinkFlag.FLY.get()) > 0) { forceIgnore = true; }
914                 if ((linkFlags & LinkFlag.FORCED.get()) > 0) {}
915                 if ((linkFlags & LinkFlag.JUMP.get()) > 0) {}
916                 if ((linkFlags & LinkFlag.LADDER.get()) > 0) { forceIgnore = true; }
917                 if ((linkFlags & LinkFlag.PLAYERONLY.get()) > 0) {}
918                 if ((linkFlags & LinkFlag.PROSCRIBED.get()) > 0) { forceIgnore = true; }
919                 if ((linkFlags & LinkFlag.SPECIAL.get()) > 0) { }
920                 if ((linkFlags & LinkFlag.SWIM.get()) > 0) { forceIgnore = true; }
921                 if ((linkFlags & LinkFlag.WALK.get()) > 0) {}
922                 
923                 if(!forceAdd && !forceIgnore) {
924                     
925                     // 2D projection of link
926                     Line2D linkAsLine2D = new Line2D(link.getFromNavPoint().getLocation().x, link.getFromNavPoint().getLocation().y, link.getToNavPoint().getLocation().x, link.getToNavPoint().getLocation().y);               
927 
928                     // how to decide if edge is offmesh?
929                     // 1. start on the polygon of starting navpoinpoint (no polygon = offmesh)
930                     // 2. while the current polygon (no polygon = offmesh) is not polygon of target repeat:
931                     // 3. go to the neighbour polygon that is behind the edge that is crossed by our line                 
932                     int currentPolygonId = getPolygonIdByPoint3D(link.getFromNavPoint().getLocation().asPoint3D());
933                     int targetPolygonId = this.getPolygonIdByPoint3D(link.getToNavPoint().getLocation().asPoint3D());
934                     int tabooPolygon = -1; // we are not searching backwards
935                     while(currentPolygonId >= 0 && currentPolygonId != targetPolygonId) {
936                         int newPolygon = -1;
937 
938                         // try all neighbours (except last one)
939                         List<Integer> neighbours = this.getNeighbourIdsToPolygon(currentPolygonId);
940                         for (Integer neighbour : neighbours) {
941                             if(neighbour.intValue() == tabooPolygon) continue;
942 
943                             // find the shared edge
944                             Line2D sharedEdge = null;
945                             int[] polygon1 = getPolygon(currentPolygonId);
946                             int[] polygon2 = getPolygon(neighbour);
947                             for(int i = 0; i < polygon1.length; i++) {
948                                 int v1 = polygon1[i];
949                                 int v2 = polygon1[((i==polygon1.length-1) ? 0 : i+1)];
950                                 // polygon2 must contain both vertices
951                                 boolean containsV1 = false, containsV2 = false;
952                                 for(int j = 0; j < polygon2.length; j++) {
953                                     if(polygon2[j]==v1) containsV1 = true;
954                                     if(polygon2[j]==v2) containsV2 = true;
955                                 }
956                                 if(containsV1 && containsV2) {
957                                     double[] vertex1 = this.getVertex(v1);
958                                     double[] vertex2 = this.getVertex(v2);
959                                     sharedEdge = new Line2D(vertex1[0], vertex1[1], vertex2[0], vertex2[1]);
960                                 }
961                             }
962                             // now we should have the shared edge or there is an error
963                             if(sharedEdge==null) {
964                                 System.out.println("Error! shared edge between polygon "+currentPolygonId+" and "+neighbour+" was not found!");
965                             }
966 
967                             // does our examined edge cross the shared edge?
968                             if(linkAsLine2D.getIntersection(sharedEdge) != null) {
969                                 System.out.println("Crossed a line into polygon "+neighbour);
970                                 tabooPolygon = currentPolygonId;
971                                 newPolygon = neighbour;
972                                 break;
973                             }
974                         }
975                         currentPolygonId = newPolygon;
976                     }
977                     // so now we either reached the target over the polygons, or we are off the mesh
978                     // which one is it?
979                     if(currentPolygonId > 0) {
980                         // path is inside navmesh
981                         addThisEdge = false;  
982                     } else {
983                         // path is off mesh
984                         addThisEdge = true;
985                     }
986                 } // end of checking path
987                 // else: we were forced to add/reject this edge
988                 else {
989                     // ignoring has higher priority
990                     if(forceAdd) addThisEdge = true;
991                     if(forceIgnore) addThisEdge = false;
992                 }
993                 
994                 // will we add this edge?
995                 if(addThisEdge) {
996                     System.out.println("This edge is off-mesh!");
997                     OffMeshPoint op1 = offPoints.get(uId);
998                     OffMeshPoint op2 = offPoints.get(target.getId());
999                     OffMeshEdge oe = new OffMeshEdge(op1, op2, link);
1000                     op1.getOutgoingEdges().add(oe);
1001                     op2.getIncomingEdges().add(oe);                      
1002                 } else {
1003                     System.out.println("This edge is not off-mesh.");
1004                 }                
1005             }
1006         }
1007         
1008         // all edges from all navpoints are checked. now lets see how many off-mesh points do we have
1009         offMeshPoints = new ArrayList<OffMeshPoint>();
1010         int offCount = 0;
1011         for(OffMeshPoint op : offPoints.values()) {
1012             if(op.getOutgoingEdges().isEmpty() && op.getIncomingEdges().isEmpty()) {
1013                 // nothing
1014             }
1015             else {
1016                 offMeshPoints.add(op);
1017                 offCount++;
1018             }
1019         }
1020         System.out.println("We found "+offCount+" offMeshPoints in total of "+offPoints.size()+" NavPoints.");
1021         
1022         // create mapping from polygons to offmesh points
1023         polysToOffMeshPoints = new ArrayList<ArrayList<OffMeshPoint>>();
1024         for(int i=0; i<polys.size(); i++) {
1025             polysToOffMeshPoints.add(new ArrayList<OffMeshPoint>());
1026         }
1027         for(OffMeshPoint op : offMeshPoints) {
1028             int pId = op.getPId();
1029             if(pId >=0) polysToOffMeshPoints.get(pId).add(op);
1030         }
1031         
1032         System.out.println("Off-mesh connections done.");
1033     }
1034 
1035     /**
1036      * Gets a List of polygons on which the path should go.
1037      * @param from
1038      * @param to
1039      * @return 
1040      */
1041     public List<INavMeshAtom> getPolygonPath(INavMeshAtom fromAtom, INavMeshAtom toAtom) {
1042        // List of atoms from which we will always pick the one with shortest distance and expand ir
1043         List<AStarNode> pickable = new ArrayList<AStarNode>();
1044         // List of atoms, that are no longer pickable, because they have no more neighbours
1045         List<AStarNode> expanded = new ArrayList<AStarNode>();
1046         pickable.add(new AStarNode(null, fromAtom, this, fromAtom, toAtom));
1047         
1048         // Let's search for toAtom!
1049         AStarNode targetNode = null;
1050         while(targetNode==null) {
1051             
1052             // 1. if pickable is empty, there is no way
1053             if(pickable.isEmpty()) { 
1054                 return null;
1055             }
1056             
1057             // 2. find the most perspective node in pickable
1058             // that means that it has the shortest estimated total path length;
1059             AStarNode best = pickable.get(0);
1060             for(AStarNode node : pickable) {
1061                 if(node.getEstimatedTotalDistance() < best.getEstimatedTotalDistance()) {
1062                     best = node;
1063                 }
1064             }
1065             
1066             // 3. we expand the best node
1067             List<INavMeshAtom> neighbours = best.getAtom().getNeighbours(this);
1068             for(INavMeshAtom atom : neighbours) {
1069                 boolean add = true;
1070                 // if this atom is already in our expanded tree, we reject it?
1071                 // TODO some optimalization for teleports
1072                 for(AStarNode expNode : expanded) {
1073                     if(expNode.getAtom().equals(atom)) add = false;
1074                 }
1075                 // we add new neighbour
1076                 if(add) {
1077                     AStarNode newNode = new AStarNode(best, atom, this, fromAtom, toAtom);
1078                     best.getFollowers().add(newNode);
1079                     pickable.add(newNode);
1080                     // target reach test
1081                     if(atom.equals(toAtom)) {
1082                         targetNode = newNode;
1083                     }
1084                 }                
1085             }
1086             // put expadned node into expanded
1087             pickable.remove(best);
1088             expanded.add(best);             
1089         }
1090         
1091         // now we just return the path of atoms from start to end. We must build it from the end
1092         List<INavMeshAtom> path = new ArrayList<INavMeshAtom>();
1093         AStarNode node = targetNode;
1094         while(node != null) {
1095             path.add(node.getAtom());
1096             node = node.getFrom();
1097         }
1098         Collections.reverse(path);
1099         return path;
1100     }
1101     
1102     /**
1103      * Calls the method with the same name but polygons as arguments and returns result
1104      * @param from
1105      * @param to
1106      * @return 
1107      */
1108     public List<INavMeshAtom> getPolygonPath(Location from, Location to) {
1109         INavMeshAtom fromAtom = getNearestAtom(from);
1110         INavMeshAtom toAtom = getNearestAtom(to);
1111         return getPolygonPath(fromAtom, toAtom);
1112     }
1113     
1114     /**
1115      * Counts a simple path from polygonPath. Only for testing use.
1116      * rules for adding points:
1117      * for each new atom in path:
1118         
1119      * 1. point -> point : just add the new point! :-)
1120      * 2. point -> polygon : is that point inside that polygon?
1121      *       a) yes : add nothing
1122      *       b) no  : this should not happen. offmesh points are connected to points inside. Add the center...
1123      * 3. polygon -> polygon : add point that is in middle of shared line
1124      * 4. polygon -> point : is that point inside that polygon?
1125      *       a) yes : add the new point
1126      *       b) no  : this should not happen. offmesh points are connected to points inside. Add the new point anyway
1127      * @param from
1128      * @param to
1129      * @param polygonPath
1130      * @return 
1131      */
1132     private List<ILocated> getPolygonCentrePath(ILocated from, ILocated to, List<INavMeshAtom> polygonPath) {
1133        List path = new ArrayList<ILocated>();
1134         path.add(from);
1135         INavMeshAtom lastAtom = null;
1136         for(INavMeshAtom atom : polygonPath) {
1137             // * -> point
1138             if(atom.getClass()==OffMeshPoint.class) {
1139                 NavPoint np = (NavPoint)server.getWorldView().get(((OffMeshPoint)atom).getNavPointId());
1140                 path.add(np);
1141             }
1142             else {
1143                 // point -> polygon
1144                 NavMeshPolygon polygon = (NavMeshPolygon)atom;
1145                 
1146                 if(lastAtom==null || lastAtom.getClass()==OffMeshPoint.class) {
1147                     OffMeshPoint op = (OffMeshPoint)lastAtom;
1148                     
1149                     // was lastAtom inside this polygon?
1150                     boolean inside;
1151                     if(lastAtom==null) {
1152                         inside = polygonContainsPoint(polygon.getPolygonId(), new Point2D(from.getLocation().x, from.getLocation().y));
1153                     } else {
1154                         inside = false;
1155                         List<OffMeshPoint> offPs = polysToOffMeshPoints.get(polygon.getPolygonId());
1156                         for(OffMeshPoint op2 : offPs) {
1157                             if(op2.equals(op)) inside = true;
1158                         }
1159                     }
1160                     
1161                     if(inside) {
1162                         // nothing
1163                     } else {
1164                         // add center
1165                         path.add(getLocation(atom));
1166                     }    
1167                 } 
1168                 // polygon -> polygon
1169                 else {
1170                     // we must find the two shared points
1171                     NavMeshPolygon polygon2 = (NavMeshPolygon)lastAtom;
1172                     int[] p1 = this.getPolygon(polygon.getPolygonId());
1173                     int[] p2 = this.getPolygon(polygon2.getPolygonId());
1174                     int v1 = -1;
1175                     int v2 = -1;
1176                     outer: for(int i = 0; i < p1.length; i++) {
1177                         for(int j = 0; j < p2.length; j++) {
1178                            if(p1[i]==p2[j]) {
1179                                if(v1==-1) {
1180                                    v1 = p1[i];
1181                                }
1182                                else {
1183                                    if(p1[i]!=v1) v2 = p1[i];
1184                                    break outer;
1185                                }
1186                            } 
1187                         }
1188                     }
1189                     double[] vv1 = getVertex(v1);
1190                     double[] vv2 = getVertex(v2);
1191                     path.add(new Location( (vv1[0]+vv2[0])/2,(vv1[1]+vv2[1])/2 ,(vv1[2]+vv2[2])/2 + NavMeshConstants.liftPolygonLocation));
1192                 }
1193             }
1194             lastAtom = atom;
1195         }
1196         path.add(to);
1197         return path;
1198     }
1199     
1200     /**
1201      * Computes shortest funneled path between given points from and to
1202      * @param from
1203      * @param to
1204      * @param polygonPath
1205      * @return 
1206      */
1207     private List<ILocated> getFunneledPath(ILocated from, ILocated to, List<INavMeshAtom> polygonPath) {
1208         List<ILocated> path = new ArrayList<ILocated>();
1209         path.add(from);
1210         INavMeshAtom lastAtom = null;
1211         INavMeshAtom atom = null;
1212         int index = -1;
1213         while(index < polygonPath.size()-1) {
1214             index++;
1215             if(index>0) lastAtom = polygonPath.get(index-1);
1216             else lastAtom = null;
1217             atom = polygonPath.get(index);
1218             
1219             // * -> point
1220             if(atom.getClass()==OffMeshPoint.class) {
1221                 NavPoint np = (NavPoint)server.getWorldView().get(((OffMeshPoint)atom).getNavPointId());
1222                 path.add(np);
1223             }
1224             
1225             // * -> polygon
1226             else {
1227                 
1228                 NavMeshPolygon polygon = (NavMeshPolygon)atom;
1229                 // point -> polygon
1230                 if(lastAtom==null || lastAtom.getClass()==OffMeshPoint.class) {
1231                     OffMeshPoint op = (OffMeshPoint)lastAtom;
1232                     
1233                     // was lastAtom inside this polygon?
1234                     boolean inside;
1235                     if(lastAtom==null) {
1236                         inside = polygonContainsPoint(polygon.getPolygonId(), new Point2D(from.getLocation().x, from.getLocation().y));
1237                     } else {
1238                         inside = false;
1239                         List<OffMeshPoint> offPs = polysToOffMeshPoints.get(polygon.getPolygonId());
1240                         for(OffMeshPoint op2 : offPs) {
1241                             if(op2.equals(op)) inside = true;
1242                         }
1243                     }
1244                     
1245                     if(inside) {
1246                         // nothing
1247                     } else {
1248                         // add center
1249                         path.add(getLocation(atom));
1250                     }    
1251                 } 
1252                 // polygon -> polygon
1253                 else {
1254                     // here comes the funneling
1255                     
1256                     // point from which we are starting = last point in path
1257                     ILocated start = path.get(path.size()-1);
1258                     
1259                     // we must find the 'gateway'
1260                     NavMeshPolygon polygon2 = (NavMeshPolygon)lastAtom;
1261                     int[] p1 = this.getPolygon(polygon.getPolygonId());
1262                     int[] p2 = this.getPolygon(polygon2.getPolygonId());
1263                     int v1 = -1;
1264                     int v2 = -1;
1265                     outer: for(int i = 0; i < p1.length; i++) {
1266                         for(int j = 0; j < p2.length; j++) {
1267                            if(p1[i]==p2[j]) {
1268                                if(v1==-1) {
1269                                    v1 = p1[i];
1270                                }
1271                                else {
1272                                    if(p1[i]!=v1) v2 = p1[i];
1273                                    break outer;
1274                                }
1275                            } 
1276                         }
1277                     }
1278                     double[] vv1 = getVertex(v1);
1279                     double[] vv2 = getVertex(v2);
1280                     Line2D gateway = new Line2D(vv1[0], vv1[1], vv2[0], vv2[1]);
1281                     // gateway found
1282                     
1283                     if( (start.getLocation().x==vv2[0] && start.getLocation().y==vv2[1]) ||
1284                         (start.getLocation().x==vv1[0] && start.getLocation().y==vv1[1])) {
1285                         System.out.println("We are already in the next polygon. No comparation, let's just continue.");
1286                         continue;
1287                     }                    
1288                     
1289                     // !!! recgonize left and right correctly !!!
1290                     double dist = gateway.getSignedDistance(start.getLocation().x, start.getLocation().y);
1291                     // create left and right mantinel
1292                     
1293                     Line2D leftMantinel = new Line2D(start.getLocation().x, start.getLocation().y, vv2[0], vv2[1]);
1294                     Line2D rightMantinel = new Line2D(start.getLocation().x, start.getLocation().y, vv1[0], vv1[1]);
1295                     if(dist < 0) {
1296                         Line2D swap = leftMantinel;
1297                         leftMantinel = rightMantinel;
1298                         rightMantinel = swap;
1299                         vv1 = getVertex(v2);
1300                         vv2 = getVertex(v1);
1301                         gateway = new Line2D(vv1[0], vv1[1], vv2[0], vv2[1]);                       
1302                     }
1303                     // now left and right mantinel are set correctly                                        
1304                     
1305                     int leftMantinelIndex = index;
1306                     double leftMantinelZ = vv2[2]; 
1307                     Location leftMantinelTarget;
1308                     if(safeVertex.get(v2)) {
1309                         leftMantinelTarget = new Location(vv2[0],vv2[1],vv2[2]+NavMeshConstants.liftPolygonLocation);
1310                     } else {
1311                         if(gateway.getLength() <= 2*NavMeshConstants.agentRadius) {
1312                             leftMantinelTarget = new Location((vv2[0]+vv1[0])/2,(vv2[1]+vv1[1])/2,(vv2[2]+vv1[2])/2+NavMeshConstants.liftPolygonLocation);
1313                         } else {
1314                             leftMantinelTarget = new Location( vv2[0] + (vv1[0]-vv2[0])/gateway.getLength()*NavMeshConstants.agentRadius,
1315                                                                vv2[1] + (vv1[1]-vv2[1])/gateway.getLength()*NavMeshConstants.agentRadius,
1316                                                                vv2[2] + (vv1[2]-vv2[2])/gateway.getLength()*NavMeshConstants.agentRadius+NavMeshConstants.liftPolygonLocation);
1317                         }
1318                     }
1319  
1320                     int rightMantinelIndex = index;
1321                     double rightMantinelZ = vv1[2];
1322                     Location rightMantinelTarget;
1323                     if(safeVertex.get(v1)) {
1324                         rightMantinelTarget = new Location(vv1[0],vv1[1],vv1[2]+NavMeshConstants.liftPolygonLocation);
1325                     } else {
1326                         if(gateway.getLength() <= 2*NavMeshConstants.agentRadius) {
1327                             rightMantinelTarget = new Location((vv2[0]+vv1[0])/2,(vv2[1]+vv1[1])/2,(vv2[2]+vv1[2])/2+NavMeshConstants.liftPolygonLocation);
1328                         } else {
1329                             rightMantinelTarget = new Location( vv1[0] + (vv2[0]-vv1[0])/gateway.getLength()*NavMeshConstants.agentRadius,
1330                                                                vv1[1] + (vv2[1]-vv1[1])/gateway.getLength()*NavMeshConstants.agentRadius,
1331                                                                vv1[2] + (vv2[2]-vv1[2])/gateway.getLength()*NavMeshConstants.agentRadius+NavMeshConstants.liftPolygonLocation);
1332                         }
1333                     }                    
1334                     
1335                     
1336                     // now we will go further over the polygons until the mantinels cross or until we find target point inside funnel
1337                     boolean targetAdded = false;
1338                     boolean outOfMantinels = false;
1339                     boolean endOfPolygonPathReached = false;
1340                     while(!targetAdded && !outOfMantinels) {
1341                         index++;
1342                         lastAtom = polygonPath.get(index-1);
1343                         if(index<polygonPath.size()) atom = polygonPath.get(index);
1344                         // if we are at the end, we dont need an atom
1345                         else endOfPolygonPathReached = true;
1346                         // last atom surely was polygon because we are in polygon->polygon branch
1347                         polygon2 = (NavMeshPolygon)lastAtom;                       
1348                         
1349                         // new atom is point - potential end of algorithm
1350                         // also go this way if we reached the last polygon
1351                         // every command has an alternative for this special option
1352                         if(endOfPolygonPathReached || atom.getClass()==OffMeshPoint.class) {
1353                             NavPoint np = null;
1354                             if(!endOfPolygonPathReached) np = (NavPoint)server.getWorldView().get(((OffMeshPoint)atom).getNavPointId());
1355                             
1356                             ILocated target;
1357                             if(endOfPolygonPathReached) target = to;
1358                             else target = np;
1359                             
1360                             // is np inside funnel?
1361                             // compare with left mantinel:
1362                             Line2D virtualGateway1 = new Line2D(target.getLocation().x, target.getLocation().y, leftMantinel.p2.x, leftMantinel.p2.y);
1363                             dist = virtualGateway1.getSignedDistance(start.getLocation().x, start.getLocation().y);
1364                             if(dist<0) {
1365                                 // point is out from left mantinel. we must go to corner of left mantinel and continue from there
1366                                 //path.add(new Location(leftMantinel.p2.x, leftMantinel.p2.y, leftMantinelZ+NavMeshConstants.liftPolygonLocation));
1367                                 path.add(leftMantinelTarget);
1368                                 // we will now 'restart' funneling algorithm - continue from this point and its polygon 
1369                                 outOfMantinels = true;
1370                                 index = leftMantinelIndex;
1371                             }
1372                             else {
1373                                 // point is inside left mantinel - Ok
1374                                 // check the right mantinel
1375                                 Line2D virtualGateway2 = new Line2D(rightMantinel.p2.x, rightMantinel.p2.y, target.getLocation().x, target.getLocation().y);
1376                                 dist = virtualGateway2.getSignedDistance(start.getLocation().x, start.getLocation().y);
1377                                 if(dist<0) {
1378                                     // point is out from right mantinel. we must go to corner of right mantinel and continue from there
1379                                     //path.add(new Location(rightMantinel.p2.x, rightMantinel.p2.y, rightMantinelZ+NavMeshConstants.liftPolygonLocation));
1380                                     path.add(rightMantinelTarget);
1381                                     // we will now 'restart' funneling algorithm - continue from this point and its polygon 
1382                                     outOfMantinels = true;
1383                                     index = leftMantinelIndex;                                    
1384                                 }
1385                                 else {
1386                                     // point is inside the maninels. that is great - we successfully finnished this funnelling
1387                                     if(!endOfPolygonPathReached) path.add(np);
1388                                     targetAdded = true;
1389                                 }
1390                             }  
1391                         }
1392                         // new atom is polygon again
1393                         else {
1394                             polygon = (NavMeshPolygon)atom;
1395                             Point2D middleOfOldGateway = new Point2D((gateway.p1.x+gateway.p2.x)/2, (gateway.p1.y+gateway.p2.y)/2);
1396                             // find new gateway
1397                             p1 = this.getPolygon(polygon.getPolygonId());
1398                             p2 = this.getPolygon(polygon2.getPolygonId());
1399                             v1 = -1;
1400                             v2 = -1;
1401                             outer: for(int i = 0; i < p1.length; i++) {
1402                                 for(int j = 0; j < p2.length; j++) {
1403                                 if(p1[i]==p2[j]) {
1404                                     if(v1==-1) {
1405                                         v1 = p1[i];
1406                                     }
1407                                     else {
1408                                         if(p1[i]!=v1) v2 = p1[i];
1409                                         break outer;
1410                                     }
1411                                 } 
1412                                 }
1413                             }
1414                             vv1 = getVertex(v1);
1415                             vv2 = getVertex(v2);
1416                             gateway = new Line2D(vv1[0], vv1[1], vv2[0], vv2[1]);
1417                             // decide which endpoint of gateway is on the left side and which is on the right side
1418                             // if the gateway is directed correctly, than the middle of old gateway should be on the left side of it
1419                             dist = gateway.getSignedDistance(middleOfOldGateway);
1420                             if(dist<0) {
1421                                 vv1 = getVertex(v2);
1422                                 vv2 = getVertex(v1);
1423                                 gateway = new Line2D(vv1[0], vv1[1], vv2[0], vv2[1]);
1424                             }                           
1425                             // gateway found
1426                             
1427                             // try to update mantinels
1428                             // left mantinel
1429                             dist = leftMantinel.getSignedDistance(gateway.p2);
1430                             // if the point is inside, it should be right from left mantinel
1431                             if(dist < 0) {
1432                                 // ok, it is right from left mantinel
1433                                 // now check if the new mantinel would cross right mantinel
1434                                 dist = rightMantinel.getSignedDistance(gateway.p2);
1435                                 // if the point is inside, it should be on the left
1436                                 if(dist > 0) {
1437                                     // ok, left point is inside funnel. we can narrow the funnel
1438                                     leftMantinel = new Line2D(leftMantinel.p1, gateway.p2);
1439                                     leftMantinelIndex = index;
1440                                     leftMantinelZ = vv2[2];
1441                                     if(safeVertex.get(v2)) {
1442                                         leftMantinelTarget = new Location(vv2[0],vv2[1],vv2[2]+NavMeshConstants.liftPolygonLocation);
1443                                     } else {
1444                                         if(gateway.getLength() <= 2*NavMeshConstants.agentRadius) {
1445                                             leftMantinelTarget = new Location((vv2[0]+vv1[0])/2,(vv2[1]+vv1[1])/2,(vv2[2]+vv1[2])/2+NavMeshConstants.liftPolygonLocation);
1446                                         } else {
1447                                             leftMantinelTarget = new Location( vv2[0] + (vv1[0]-vv2[0])/gateway.getLength()*NavMeshConstants.agentRadius,
1448                                                                             vv2[1] + (vv1[1]-vv2[1])/gateway.getLength()*NavMeshConstants.agentRadius,
1449                                                                             vv2[2] + (vv1[2]-vv2[2])/gateway.getLength()*NavMeshConstants.agentRadius+NavMeshConstants.liftPolygonLocation);
1450                                         }
1451                                     }                                    
1452                                 }
1453                                 else {
1454                                     // there is a cross! left mantinel would cross the right one!
1455                                     // we will restart funneling and continue from corner of right mantinel
1456                                     //path.add(new Location(rightMantinel.p2.x, rightMantinel.p2.y, rightMantinelZ+NavMeshConstants.liftPolygonLocation));
1457                                     path.add(rightMantinelTarget);
1458                                     outOfMantinels = true;
1459                                     index = rightMantinelIndex;
1460                                 }
1461                             } 
1462                             else {
1463                                 // point is left from left mantinel.
1464                                 // we cannot do anything.
1465                                 // the mantinel stays where it is
1466                             }
1467                             
1468                             // now the right mantinel
1469                             dist = rightMantinel.getSignedDistance(gateway.p1);
1470                             // if the point is inside, it should be left from right mantinel
1471                             if(dist > 0) {
1472                                 // ok, it is left from right mantinel
1473                                 
1474                                 // btw this is impossible if the left mantinel is already crossing the right.
1475                                 
1476                                 // now check if the new mantinel would cross left mantinel
1477                                 dist = leftMantinel.getSignedDistance(gateway.p1);
1478                                 // if the point is inside, it should be on the right
1479                                 if(dist < 0) {
1480                                     // ok, right point is inside funnel. we can narrow the funnel
1481                                     rightMantinel = new Line2D(rightMantinel.p1, gateway.p1);
1482                                     rightMantinelIndex = index;
1483                                     rightMantinelZ = vv1[2]; 
1484                                     if(safeVertex.get(v1)) {
1485                                         rightMantinelTarget = new Location(vv1[0],vv1[1],vv1[2]+NavMeshConstants.liftPolygonLocation);
1486                                     } else {
1487                                         if(gateway.getLength() <= 2*NavMeshConstants.agentRadius) {
1488                                             rightMantinelTarget = new Location((vv2[0]+vv1[0])/2,(vv2[1]+vv1[1])/2,(vv2[2]+vv1[2])/2+NavMeshConstants.liftPolygonLocation);
1489                                         } else {
1490                                             rightMantinelTarget = new Location( vv1[0] + (vv2[0]-vv1[0])/gateway.getLength()*NavMeshConstants.agentRadius,
1491                                                                             vv1[1] + (vv2[1]-vv1[1])/gateway.getLength()*NavMeshConstants.agentRadius,
1492                                                                             vv1[2] + (vv2[2]-vv1[2])/gateway.getLength()*NavMeshConstants.agentRadius+NavMeshConstants.liftPolygonLocation);
1493                                         }
1494                                     }                                    
1495                                 }
1496                                 else {
1497                                     // there is a cross! right mantinel would cross the left one!
1498                                     // we will restart funneling and continue from corner of left mantinel
1499                                     //path.add(new Location(leftMantinel.p2.x, leftMantinel.p2.y, leftMantinelZ+NavMeshConstants.liftPolygonLocation));
1500                                     path.add(leftMantinelTarget);
1501                                     outOfMantinels = true;
1502                                     index = leftMantinelIndex;
1503                                 }
1504                             } 
1505                             else {
1506                                 // point is right from right mantinel.
1507                                 // we cannot do anything.
1508                                 // the mantinel stays where it is
1509                             }                               
1510                         }
1511                     }
1512                 }
1513             }
1514         }
1515         path.add(to);
1516         return path;    
1517     }
1518     
1519     /**
1520      * Computes and returns a path between two points anywhere on the map.
1521      * If no such path is found, returns null;
1522      * @param from
1523      * @param to
1524      * @return 
1525      */
1526     public List<ILocated> getPath(ILocated from, ILocated to) {
1527         
1528         // first we found a list of polygons and off-mesh connections on the path
1529         // using A* algorithm
1530         List<INavMeshAtom> polygonPath = getPolygonPath(from.getLocation(), to.getLocation());
1531         if(polygonPath == null) return null;
1532         
1533         //this.drawPolygonPath(polygonPath, new Location(255,255,0));
1534         
1535         List<ILocated> path;
1536         
1537         // now we transform path made of polygons to path made of Locations        
1538         // path = getPolygonCentrePath(from, to, polygonPath);      
1539         path = getFunneledPath(from, to, polygonPath);
1540         
1541         return path;
1542     }
1543     
1544     /**
1545      * Computes and returns a path between two points anywhere on the map.
1546      * If no such path is found, returns path of zero length;
1547      * @param from
1548      * @param to
1549      * @return 
1550      */
1551     public IPathFuture<ILocated> computePath(ILocated from, ILocated to) {
1552             return new PrecomputedPathFuture<ILocated>(from, to, getPath(from, to));
1553     }
1554 
1555     /**
1556      * Returns the nearest NavMeshAtom to given location
1557      * @param location
1558      * @return 
1559      */
1560     private INavMeshAtom getNearestAtom(Location location) {
1561         
1562         // if this point is on a polygon we return this polygon
1563         int pId = getPolygonIdByLocation(location);
1564         if(pId > 0) {
1565             return new NavMeshPolygon(pId);
1566         }
1567         else {
1568             // we return nearest offmeshPoint
1569             // TODO: be smarter! count in polygons too!
1570             double minDist = Double.MAX_VALUE;
1571             INavMeshAtom nearest = null;
1572             for(OffMeshPoint op : offMeshPoints) {
1573                 double dist = location.getDistance(op.getLocation());
1574                 if(dist < minDist) {
1575                     nearest = op;
1576                     minDist = dist;
1577                 }
1578             }
1579             
1580             // if there are no offmeshpoints, return nearest polygon
1581             // this is slow and it should not happen often
1582             if(nearest==null) {
1583                 for(int i = 0; i < polys.size(); i++) {
1584                     NavMeshPolygon p = new NavMeshPolygon(i);
1585                     Location pl = getLocation(p);
1586                     double dist = location.getDistance(pl);
1587                     if(dist < minDist) {
1588                         nearest = p;
1589                         minDist = dist;
1590                     }                
1591                 }
1592             }
1593             
1594             return nearest;
1595         }    
1596     }
1597 
1598     /**
1599      * Returns distance between two atoms (euclidean distance)
1600      * If the atom is a polygon, this method takes its middle 
1601      * @param atom1
1602      * @param atom2
1603      * @return 
1604      */
1605     double getDistance(INavMeshAtom atom1, INavMeshAtom atom2) { 
1606         if(atom1.equals(atom2)) return 0.0;      
1607         Location l1, l2;      
1608         l1 = getLocation(atom1);
1609         l2 = getLocation(atom2);   
1610         return l1.getDistance(l2);   
1611     }
1612 
1613 
1614     /**
1615      * Empty method it is nver really called for interface atom. always for polygon or point
1616      * @param atom1
1617      * @return 
1618      */
1619     private Location getLocation(INavMeshAtom atom1) {
1620         if(atom1.getClass()==OffMeshPoint.class) return getLocation((OffMeshPoint)atom1);
1621         if(atom1.getClass()==NavMeshPolygon.class) return getLocation((NavMeshPolygon)atom1);
1622         throw new UnsupportedOperationException("Not implemented. Not now. Not ever.");
1623     }
1624     /**
1625      * Returns location of the contained navpoint
1626      * @param op
1627      * @return 
1628      */
1629     private Location getLocation(OffMeshPoint op) {
1630         NavPoint np = (NavPoint) server.getWorldView().get(op.getNavPointId());
1631         return np.getLocation();
1632     }
1633     /**
1634      * Returns the middle point of the polygon
1635      * @param p
1636      * @return 
1637      */
1638     private Location getLocation(NavMeshPolygon p) {
1639         int[] polygon = this.getPolygon(p.getPolygonId());     
1640         double sumX = 0.0;
1641         double sumY = 0.0;
1642         double sumZ = 0.0;       
1643         for(int i = 0; i < polygon.length; i++) {
1644             double[] v = getVertex(polygon[i]);
1645             sumX += v[0];
1646             sumY += v[1];
1647             sumZ += v[2];
1648         }        
1649         return new Location(sumX / polygon.length, sumY / polygon.length, (sumZ / polygon.length) + NavMeshConstants.liftPolygonLocation); 
1650     }
1651     
1652     /**
1653      * Returns levelGeometry object
1654      * @return 
1655      */
1656     public LevelGeometry getLevelGeometry() {
1657         return this.levelGeometry;
1658     }
1659     
1660     /**
1661      * A simple implementation of NavMesh's 2D raycasting. Returns distance from the edge of navmesh in a direction from a location
1662      * if the entrire ray is inside navmesh of there is no navmesh it returns 0;
1663      * @param location
1664      * @param vector
1665      * @return 
1666      */
1667     public double getDistanceFromEdge(Location location, Vector2d vector, double rayLength) {
1668         if(rayLength <= 0) return 0;
1669         
1670         // get the end location (in 2D)
1671         vector.normalize();
1672         vector.x *= rayLength;
1673         vector.y *= rayLength;
1674         Location end = new Location(location.x + vector.x, location.y + vector.y);
1675         
1676         
1677         // get a 2D projection of ray
1678         Line2D ray = new Line2D(location.x, location.y, end.x, end.y);
1679         // get the current polygon
1680         int pId = this.getPolygonIdByLocation(location);
1681         if(pId < 0) return 0;
1682         
1683         // how to find end of navmesh?
1684         // 1. start on the polygon of starting location
1685         // 2. find where the line crosses its border
1686         // 3. while there is another polyogn behind, repeat
1687         // 4. return the last cross (its distance from location)
1688         int currentPolygonId = pId;
1689         int lastPolygonId = -1;
1690         int nextPolygonId = -1;
1691         
1692         // find the first cross
1693         Point2D cross = null;
1694         int v1=-1,v2=-1;
1695         int[] polygon = getPolygon(currentPolygonId);
1696         for(int i = 0; i<polygon.length; i++) {
1697             v1 = polygon[i];
1698             v2 = polygon[((i==polygon.length-1) ? 0 : i+1)];   
1699             double[] vertex1 = getVertex(v1);
1700             double[] vertex2 = getVertex(v2);
1701             Line2D edge = new Line2D(vertex1[0],vertex1[1],vertex2[0],vertex2[1]);
1702             cross = ray.getIntersection(edge);
1703             if(cross!=null) {
1704                 if(cross.x <= Math.max(edge.p1.x, edge.p2.x) && cross.x >= Math.min(edge.p1.x, edge.p2.x)
1705                 && cross.x <= Math.max(ray.p1.x, ray.p2.x) && cross.x >= Math.min(ray.p1.x, ray.p2.x)
1706                         ) {
1707                     // int's a cross!
1708                     break;
1709                 } else {
1710                     // its not a cross
1711                     cross = null;
1712                 }
1713             }
1714         }
1715         // now we have the cross.
1716         // if it too far from location, we return 0;
1717         if(cross==null || ray.p1.getDistance(cross) >= rayLength ) return 0;
1718         
1719         // is there another polygon behind?
1720         nextPolygonId = getNeighbourPolygon(currentPolygonId,v1,v2);
1721         if(nextPolygonId == -1) {
1722             // there is no polygon. we return distance of cross
1723             return ray.p1.getDistance(cross);
1724         } else {
1725              // if there is another polygon, we return recursively distance from egde in that direction
1726             // move a little so it is in the neighbour polygon
1727             vector = ((Vector2d) vector.clone());
1728             vector.normalize();   
1729             Location crossLocation = new Location(cross.x+vector.x, cross.y+vector.y, location.z);
1730             return getDistanceFromEdge(crossLocation , vector, rayLength - ray.p1.getDistance(cross));
1731             
1732         }
1733     }
1734     
1735     /**
1736      * Returns distance of the location from the navmesh's edge in the given direction.
1737      * If location is not not on navmesh, 0 is returned
1738      * @param location
1739      * @param vector
1740      * @return 
1741      */
1742     public double getDistanceFromEdge(Location location, Vector2d vector) {
1743         return getDistanceFromEdge(location, vector, Double.MAX_VALUE);
1744     }
1745     
1746     
1747     /**
1748      * Finds neighbour behind given vertexes. Returns polygon id or -1 there is none
1749      * @param currentPolygonId
1750      * @param v1
1751      * @param v2
1752      * @return 
1753      */
1754     private int getNeighbourPolygon(int currentPolygonId, int v1, int v2) {
1755         // try all neighbours (except last one)
1756         List<Integer> neighbours = this.getNeighbourIdsToPolygon(currentPolygonId);
1757         for (Integer neighbour : neighbours) {
1758             // find the shared edge
1759             int[] polygon2 = getPolygon(neighbour);
1760             // polygon2 must contain both vertices
1761             boolean containsV1 = false, containsV2 = false;
1762             for(int j = 0; j < polygon2.length; j++) {
1763                 if(polygon2[j]==v1) containsV1 = true;
1764                 if(polygon2[j]==v2) containsV2 = true;
1765             }
1766             
1767             if(containsV1 && containsV2) {
1768                 return neighbour;
1769             }
1770         }
1771         // no good neighbour was found
1772         return -1;
1773     }
1774     
1775 }