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  import java.util.ArrayList;
21  import java.util.Random;
22  import java.util.logging.Level;
23  import java.util.logging.Logger;
24  import math.geom2d.Point2D;
25  import math.geom2d.line.Line2D;
26  import math.geom2d.line.StraightLine2D;
27  
28  /**
29   *
30   * @author Jakub
31   * Node of BSP tree structure
32   * built for NavMesh
33   */
34  public class BSPNode implements java.io.Serializable {
35      
36      public transient NavMesh navmesh;
37      // list of polygon ids (actual polygons are stored in navmesh)
38      public ArrayList polys;
39      public BSPNode parent;
40      // vertices separating polygons in this node (actual vertices are in navmesh)
41      public StraightLine2D sepLine;
42      // node children
43      public BSPNode left;
44      public BSPNode right;
45      
46      private Random random;
47      
48      /**
49       * 
50       * @param m
51       * @param par 
52       * 
53       * Cretaes a new node pointing to parent par with navmesh m.
54       * Remember to fill polys propery.
55       * Then build method should be called.
56       */
57      
58      public BSPNode(NavMesh m, BSPNode par) {
59          navmesh = m;
60          parent = par;
61          polys = new ArrayList();
62          random = new Random();
63          sepLine = null;
64      }
65      
66      public boolean shouldSplit() {
67          return (polys.size() > NavMeshConstants.stopSplittingNumberOfPolygons);
68      }
69      
70      public StraightLine2D findSeparatingLine () throws Exception {
71          System.out.println("findSeparatingLine(): Trying to split " + polys.size() + " polygons...");
72          // alg 1:
73          // pick 3 polygons at random and try all treir edges. remember the best one
74          // criterium is that the greater half of polgons should be as small as it can be - ideally 0.5
75          double bestFoundSplitFactor = NavMeshConstants.maxAllowedSplitFactor;
76          StraightLine2D bestFoundSeparatingLine = null;
77          
78          ArrayList candidatePolygons = (ArrayList) polys.clone();
79          
80          // several tries for polygons...
81          for(int i = 0; i < NavMeshConstants.maxNumberOfPolygonsToTry; i++) {
82              if(candidatePolygons.isEmpty()) break;
83              
84              int randomId = 0; //random.nextInt(candidatePolygons.size());
85              Integer polygonId = (Integer) candidatePolygons.get(randomId);        
86              int[] polygon = navmesh.getPolygon(polygonId);
87              
88              // pick all vertices... and try them
89              for(int j = 0; j<polygon.length; j++) {
90                  int v1 = polygon[j];
91                  int v2 = polygon[(j+1) % polygon.length];
92                  double[] vertex1 = navmesh.getVertex(v1);
93                  double[] vertex2 = navmesh.getVertex(v2);
94                  
95                  // we create 2Dpoints ignoring the third dimension
96                  Point2D point2D1 = new Point2D(vertex1[0], vertex1[1]);
97                  Point2D point2D2 = new Point2D(vertex2[0], vertex2[1]);
98                  
99                  // horray, separating line
100                 StraightLine2D separatingLine = new StraightLine2D(point2D1, point2D2);
101                 
102                 // now lets divide all polygons to left and right part
103                 ArrayList[] splittedPolys = splitPolygonsByLine(polys, separatingLine);
104                 ArrayList left = splittedPolys[0];
105                 ArrayList right = splittedPolys[1];
106                 
107                 int intersectedPolygons = left.size() + right.size() - polys.size();
108                 
109                 double splitFactor = max(left.size() / polys.size(), right.size() / polys.size());
110                 
111                 // if this line is best found, we remember it
112                 if(splitFactor < bestFoundSplitFactor) {
113                     bestFoundSeparatingLine = separatingLine;
114                     bestFoundSplitFactor = splitFactor;
115                 }   
116             }
117             // dont search this polygon again
118             candidatePolygons.remove(polygonId);
119         }
120         if(bestFoundSeparatingLine==null) throw new Exception("No good separating line have been found. Splitting " +polys.size()+ " polygons unsuccessfull." );
121         System.out.println("findSeparatingLine(): Returning a split line with splitFactor " + bestFoundSplitFactor);     
122         return bestFoundSeparatingLine;
123     }
124    
125      /*
126      * Splits the input polygons by line into left and right part.
127      * Polygons that this line intersects will occur in both sets
128      */
129     private ArrayList[] splitPolygonsByLine(ArrayList polysToSplit, StraightLine2D separatingLine) throws Exception {
130         
131         ArrayList left = new ArrayList();
132         ArrayList right = new ArrayList();
133         
134         // walk through each polygon
135         for(int i = 0; i < polysToSplit.size(); i++) {
136             
137             // switches where the polygon lie
138             boolean isOnLeft = false;
139             boolean isOnRight = false;
140             
141             Integer pId = (Integer) polysToSplit.get(i);
142             int[] polygon = navmesh.getPolygon(pId);
143             
144             // walk through each vertex
145             for(int j = 0; j < polygon.length; j++) {
146                 int vId = polygon[j];
147                 double[] vertex = navmesh.getVertex(vId);
148                 Point2D point2D = new Point2D(vertex[0], vertex[1]);     
149                 double dist = separatingLine.getSignedDistance(point2D);   
150                 if(dist<0) isOnLeft = true;
151                 if(dist>0) isOnRight = true;                
152                 if(isOnLeft && isOnRight) break;
153             }
154             if(isOnLeft) left.add(pId);
155             if(isOnRight) right.add(pId);
156             if(!isOnLeft && !isOnRight) throw new Exception("Something is wrong. The polygon number " +pId+ " seems to be on neither side of the separating line.");
157             
158         }  
159         ArrayList[] ret = new ArrayList[2];
160         ret[0] = left;
161         ret[1] = right;
162         return ret;
163     }  
164     
165     /**
166      * Recursive method building an antrige tree from this node as root
167      */
168     public void build() {
169     
170         // test end of recursion
171         if (!shouldSplit()) {
172             // set biggest leaf?
173             if(polys.size() > navmesh.getNumberOfPolygonsInBiggestLeaf()) {
174                 System.out.println("Setting biggest leaf sofar. Number of polygons in this node is " + polys.size());
175                 navmesh.setBiggestLeafInTree(this);
176             }
177             return;
178         }
179         
180         // find separating line
181         try {
182             sepLine = findSeparatingLine();
183         }
184         catch(Exception e) {
185             System.out.println("No separating line found for this node. Ok, stop splitting. Number of polygons in this node is " + polys.size());
186             // set biggest leaf?
187             if(polys.size() > navmesh.getNumberOfPolygonsInBiggestLeaf()) {
188                 System.out.println("Setting biggest leaf sofar. Number of polygons in this node is " + polys.size() + ". The polygons here are: " + polys.toString());
189                 navmesh.setBiggestLeafInTree(this);
190             }
191         }
192         
193         if(sepLine != null) {
194             try {
195                 ArrayList[] splittedPolys = splitPolygonsByLine(polys, sepLine);
196                 System.out.println("build(): Left has " + splittedPolys[0].size() + " polygons.");
197                 System.out.println("build(): Right has " + splittedPolys[1].size() + " polygons.");
198                 System.out.println("build(): Line is crossing " + (splittedPolys[0].size() + splittedPolys[1].size() - polys.size()) + " polygons.");
199                 left = new BSPNode(navmesh, this);
200                 left.polys = splittedPolys[0];
201                 left.build();
202                 right = new BSPNode(navmesh, this);
203                 right.polys = splittedPolys[1];
204                 right.build();
205             } catch (Exception e) {
206                 System.out.println("Could not split polys by the given line. This should not happen.");
207                 e.printStackTrace();
208             }
209         }    
210     }
211 
212     private double max(double d1, double d2) {
213         return d1 > d2 ? d1 : d2;
214     }
215     
216     public boolean isLeaf() {
217         return (left == null && right == null);
218     }
219     
220     
221 }