1// BEGIN LICENSE BLOCK 2// Version: CMPL 1.1 3// 4// The contents of this file are subject to the Cisco-style Mozilla Public 5// License Version 1.1 (the "License"); you may not use this file except 6// in compliance with the License. You may obtain a copy of the License 7// at www.eclipse-clp.org/license. 8// 9// Software distributed under the License is distributed on an "AS IS" 10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 11// the License for the specific language governing rights and limitations 12// under the License. 13// 14// The Original Code is The ECLiPSe Constraint Logic Programming System. 15// The Initial Developer of the Original Code is Cisco Systems, Inc. 16// Portions created by the Initial Developer are 17// Copyright (C) 2006 Cisco Systems, Inc. All Rights Reserved. 18// 19// Contributor(s): 20// 21// END LICENSE BLOCK 22 23package com.parctechnologies.eclipse.visualisation; 24 25import java.util.*; 26import java.awt.*; 27 28/** 29 * Spatial, recursive data structure for storing a set of components S whose 30 * bounds fall within a given rectangle R. Allows efficient addition of 31 * components to S and removal of components from S. Also allows efficient 32 * lookup of which components in S intersect an arbitrary given rectangle 33 * contained by R. Assumes that the bounds of the component do not change while 34 * it is in the RectQuadTree. If the bounds are to change, the component should 35 * be removed and added again once the new bounds are set. 36 */ 37 38class RectQuadTree 39{ 40 // for type INTERIOR, sub nodes covering four quadrants. 41 private RectQuadTree [] child; 42 43 // kind of node: 44 // NONEMPTY_LEAF: some components intersect bounds. Either this node has max 45 // depth == 0 or all intersecting components contain this node's bounds. 46 // EMPTY_LEAF: nuff said 47 // INTERIOR: some components intersect bounds, max depth > 0 and node has 48 // children. 49 private int type; 50 // values for type 51 private static final int NONEMPTY_LEAF = 0; 52 private static final int EMPTY_LEAF = 1; 53 private static final int INTERIOR = 2; 54 55 private static final int DEFAULT_MIN_SIDE = 20; 56 57 // for type NONEMPTY_LEAF: collection of components which intersect this node's 58 // bounds. 59 private Collection intersectingComponents; 60 private Rectangle bounds; 61 62 // maximum nesting level. 63 private int maxDepth; 64 65 /** 66 * create empty tree, with max splitting depth maxDepth. 67 */ 68 private RectQuadTree(Rectangle bounds, int maxDepth) 69 { 70 this.bounds = bounds; 71 this.maxDepth = maxDepth; 72 type = EMPTY_LEAF; 73 } 74 75 private static int depthGivenBounds(Rectangle bounds) 76 { 77 int smallestDim = Math.min(bounds.width, bounds.height); 78 int maxDepth = 79 (int) (Math.log((double) smallestDim / (double) DEFAULT_MIN_SIDE) / 80 Math.log(2.0)) - 1; 81 return(maxDepth); 82 } 83 84 /** 85 * Creates empty tree with maxDepth such that the side of any node is at most 86 * DEFAULT_MIN_SIDE 87 */ 88 public RectQuadTree(Rectangle bounds) 89 { 90 this(bounds, depthGivenBounds(bounds)); 91 } 92 93 94 95 // public level methods 96 97 /** 98 * Add a component to this RectQuadTree. 99 */ 100 public void addComponent(Component c) 101 { 102 addComponent(c, c.getBounds()); 103 } 104 105 /** 106 * Remove a component from this RectQuadTree. 107 */ 108 public void removeComponent(Component c) 109 { 110 removeComponent(c, c.getBounds()); 111 } 112 113 /** 114 * Return the set of components within this RectQuadTree which intersect 115 * rectangle. 116 */ 117 public Collection getComponentsWithin(Rectangle rectangle) 118 { 119 HashSet result = new HashSet(); 120 addIntersectingComponentsWithin(rectangle, result); 121 return(result); 122 } 123 124 /** 125 * Assumes added component intersects bounds. 126 * 127 * type = empty leaf 128 * 129 * if(maxDepth == 0 OR c.bounds contains this.bounds) 130 * this.type -> NONEMPTY_LEAF 131 * initialise intersectingComponents. 132 * add c to self 133 * 134 * else 135 * this.type -> INTERIOR 136 * initialise child array 137 * add c to self 138 * 139 * --- 140 * 141 * type = nonempty leaf 142 * 143 * if(maxDepth == 0 OR c.bounds contains this.bounds) 144 * intersectingComponents.add(c) 145 * else 146 * this.type -> INTERIOR 147 * initialise child array 148 * add all intersectingComponents to each child, 149 * intersectingComponents = null; 150 * add c to self. 151 * 152 * --- 153 * 154 * type = interior 155 * 156 * for each child: 157 * if(c.bounds intersects child.bounds) 158 * child.addComponent(c) 159 * 160 */ 161 private void addComponent(Component c, Rectangle cBounds) 162 { 163 if(type == EMPTY_LEAF) 164 { 165 if(maxDepth == 0 || cBounds.contains(bounds)) 166 { 167 type = NONEMPTY_LEAF; 168 initialiseIntersectingComponents(); 169 intersectingComponents.add(c); 170 } 171 else 172 { 173 type = INTERIOR; 174 initialiseChild(); 175 addComponent(c, cBounds); 176 } 177 return; 178 } 179 if(type == NONEMPTY_LEAF) 180 { 181 if(maxDepth == 0 || cBounds.contains(bounds)) 182 { 183 intersectingComponents.add(c); 184 } 185 else 186 { 187 type = INTERIOR; 188 initialiseChild(); 189 for(int i = 0; i < child.length; i++) 190 { 191 child[i].type = NONEMPTY_LEAF; 192 child[i].initialiseIntersectingComponents(); 193 child[i].intersectingComponents.addAll(intersectingComponents); 194 } 195 intersectingComponents = null; 196 addComponent(c, cBounds); 197 } 198 return; 199 } 200 if(type == INTERIOR) 201 { 202 for(int i = 0; i < child.length; i++) 203 { 204 if(cBounds.intersects(child[i].bounds)) 205 { 206 child[i].addComponent(c, cBounds); 207 } 208 } 209 } 210 } 211 212 /** 213 * type = empty leaf 214 * do nothing. 215 * 216 * type = nonempty leaf 217 * result.addAll(intersectingComponents) 218 * 219 * type = interior 220 * for i = 0 to 3 221 * child[i].addIntersectingComponents(result); 222 */ 223 private void addIntersectingComponents(Collection result) 224 { 225 if(type == EMPTY_LEAF) 226 { 227 return; 228 } 229 if(type == NONEMPTY_LEAF) 230 { 231 result.addAll(intersectingComponents); 232 return; 233 } 234 if(type == INTERIOR) 235 { 236 for(int i = 0; i < child.length; i++) 237 { 238 child[i].addIntersectingComponents(result); 239 } 240 } 241 } 242 243 /** 244 * Add all components which are within this tree and which intersect rectangle 245 * to result. Assumes rectangle intersects this.bounds. 246 * 247 * type = empty leaf 248 * do nothing 249 * 250 * type = nonempty leaf 251 * for each member of intersectingComponents x 252 * if x intersects rectangle 253 * result.add(x) 254 * 255 * type = interior 256 * for i = 0 to 3 257 * if rectangle contains child[i].bounds 258 * child[i].addIntersectingComponents(result)) 259 * return 260 * else if rectangle intersects child[i].bounds 261 * child[i].addIntersectingComponents(rectangle, result) 262 */ 263 264 private void addIntersectingComponentsWithin(Rectangle rectangle, 265 Collection result) 266 { 267 if(type == EMPTY_LEAF) 268 { 269 return; 270 } 271 if(type == NONEMPTY_LEAF) 272 { 273 Iterator i = intersectingComponents.iterator(); 274 Component x; 275 while(i.hasNext()) 276 { 277 x = (Component) i.next(); 278 if(x.getBounds().intersects(rectangle)) 279 { 280 result.add(x); 281 } 282 } 283 } 284 if(type == INTERIOR) 285 { 286 for(int i = 0; i < child.length; i++) 287 { 288 if(rectangle.contains(child[i].bounds)) 289 { 290 child[i].addIntersectingComponents(result); 291 } 292 else 293 { 294 if(rectangle.intersects(child[i].bounds)) 295 { 296 child[i].addIntersectingComponentsWithin(rectangle, result); 297 } 298 } 299 } 300 } 301 } 302 303 304 /** 305 * Assumes that component is member of the set of intersecting components. 306 * 307 * type = emtpy leaf 308 * dp nothing. 309 * 310 * type = nonempty leaf 311 * intersectingComponents.remove(component). 312 * if intersectingComponents is empty 313 * intersectingComponents -> null 314 * type -> empty leaf 315 * 316 * type = interior 317 * int empty leaf children -> 0; 318 * int nonempty leaf children -> 0; 319 * for i = 0 to 3 320 * if component.bounds intersects child[i].bounds 321 * child[i].removeComponent(component) 322 * if child[i].type == empty leaf 323 * empty leaf children ++ 324 * if child[i].type == nonempty leaf 325 * nonempty leaf children ++ 326 * end for 327 * if empty leaf children == 4 328 * type -> empty 329 * child -> null 330 * if nonemtpy leaf children == 4 && 331 * child[0].intersectingComponents.equals child[1].intersectingComponents && 332 * child[1].intersectingComponents.equals child[2].intersectingComponents && 333 * child[2].intersectingComponents.equals child[3].intersectingComponents && 334 * foreach comp in child[0].intersectingComponents 335 * comp.bounds contains this.bounds. 336 * then 337 * type -> nonempty leaf 338 * intersectingComponents = child[0].intersectingComponents 339 * child -> null 340 */ 341 private void removeComponent(Component c, Rectangle cBounds) 342 { 343 if(type == EMPTY_LEAF) 344 { 345 return; 346 } 347 if(type == NONEMPTY_LEAF) 348 { 349 intersectingComponents.remove(c); 350 if(intersectingComponents.isEmpty()) 351 { 352 intersectingComponents = null; 353 type = EMPTY_LEAF; 354 } 355 return; 356 } 357 if(type == INTERIOR) 358 { 359 int emptyLeafChildren = 0; 360 int nonEmptyLeafChildren = 0; 361 for(int i = 0; i < child.length ; i++) 362 { 363 if(cBounds.intersects(child[i].bounds)) 364 { 365 child[i].removeComponent(c, cBounds); 366 } 367 if(child[i].type == EMPTY_LEAF) 368 { 369 emptyLeafChildren++; 370 } 371 else 372 { 373 if(child[i].type == NONEMPTY_LEAF) 374 { 375 nonEmptyLeafChildren++; 376 } 377 } 378 } 379 if(emptyLeafChildren == child.length) 380 { 381 type = EMPTY_LEAF; 382 child = null; 383 return; 384 } 385 if(nonEmptyLeafChildren == child.length && 386 child[0].intersectingComponents.equals(child[1].intersectingComponents) && 387 child[1].intersectingComponents.equals(child[2].intersectingComponents) && 388 child[2].intersectingComponents.equals(child[3].intersectingComponents)) 389 { 390 boolean allIntersectingComponentsContainBounds = true; 391 Iterator i = child[0].intersectingComponents.iterator(); 392 Component c2; 393 while(i.hasNext() && allIntersectingComponentsContainBounds) 394 { 395 c2 = (Component) i.next(); 396 if(!c2.getBounds().contains(bounds)) 397 { 398 allIntersectingComponentsContainBounds = false; 399 } 400 } 401 if(allIntersectingComponentsContainBounds) 402 { 403 type = NONEMPTY_LEAF; 404 intersectingComponents = child[0].intersectingComponents; 405 child = null; 406 } 407 } 408 } 409 } 410 411 private void initialiseChild() 412 { 413 child = new RectQuadTree[4]; 414 int widthLeft = bounds.width/2; 415 int widthRight = bounds.width - widthLeft; 416 int heightTop = bounds.height/2; 417 int heightBottom = bounds.height - heightTop; 418 int newDepth = maxDepth - 1; 419 child[0] = 420 new RectQuadTree(new Rectangle(bounds.x, bounds.y, 421 widthLeft, heightTop), newDepth); 422 child[1] = 423 new RectQuadTree(new Rectangle(bounds.x + widthLeft, bounds.y, 424 widthRight, heightTop), newDepth); 425 child[2] = 426 new RectQuadTree(new Rectangle(bounds.x, bounds.y + heightTop, 427 widthLeft, heightBottom), newDepth); 428 child[3] = 429 new RectQuadTree(new Rectangle(bounds.x + widthLeft, bounds.y + heightTop, 430 widthRight, heightBottom), newDepth); 431 } 432 433 private void initialiseIntersectingComponents() 434 { 435 intersectingComponents = new HashSet(); 436 } 437 438 439 // testing code 440 private static void test() 441 { 442 int MAX_DEPTH = 7; 443 int COMPONENTS = 50; 444 int COMPONENTS_TO_REPLACE = 25; 445 int TESTS = 2000; 446 int width = 2000; 447 int height = 2000; 448 int cHeight, cWidth; 449 int replacement_index; 450 Random random = new Random(1); 451 Component[] component = new Component[COMPONENTS]; 452 RectQuadTree rqt = 453 new RectQuadTree(new Rectangle(0,0,width, height), MAX_DEPTH); 454 455 for(int i = 0; i < COMPONENTS; i++) 456 { 457 component[i] = new Label(""); 458 cWidth = random.nextInt(width/2); 459 cHeight = random.nextInt(height/2); 460 component[i]. 461 setBounds(random.nextInt(width - cWidth), 462 random.nextInt(height - cHeight), 463 cWidth, cHeight); 464 rqt.addComponent(component[i]); 465 System.out.println("added component to rqt with bounds "+component[i].getBounds()); 466 } 467 468 System.out.println("Tests for 20 x 20: "+tests(rqt, TESTS, component, 20, 20)); 469 System.out.println("Tests for 20 x 500: "+tests(rqt, TESTS, component, 20, 500)); 470 System.out.println("Tests for 500 x 20: "+tests(rqt, TESTS, component, 500, 20)); 471 System.out.println("Tests for 500 x 500: "+tests(rqt, TESTS, component, 500, 500)); 472 473 for(int i = 0; i < COMPONENTS_TO_REPLACE; i++) 474 { 475 replacement_index = random.nextInt(COMPONENTS); 476 rqt.removeComponent(component[replacement_index]); 477 cWidth = random.nextInt(width/2); 478 cHeight = random.nextInt(height/2); 479 component[replacement_index] = new Label(""); 480 component[replacement_index]. 481 setBounds(random.nextInt(width - cWidth), 482 random.nextInt(height - cHeight), 483 cWidth, cHeight); 484 rqt.addComponent(component[replacement_index]); 485 System.out.println("replaced component : new one has bounds "+ 486 component[replacement_index].getBounds()); 487 } 488 System.out.println("Post-replacement tests for 20 x 20: "+tests(rqt, TESTS, component, 20, 20)); 489 System.out.println("Post-replacement tests for 20 x 500: "+tests(rqt, TESTS, component, 20, 500)); 490 System.out.println("Post-replacement tests for 500 x 20: "+tests(rqt, TESTS, component, 500, 20)); 491 System.out.println("Post-replacement tests for 500 x 500: "+tests(rqt, TESTS, component, 500, 500)); 492 } 493 494 private static boolean tests(RectQuadTree rqt, int tests, 495 Component[] component, int testWidth, int testHeight) 496 { 497 Rectangle testRect; 498 Random random = new Random(2); 499 boolean allTestsSucceeded = true; 500 501 for(int i = 0; i < tests; i++) 502 { 503 testRect = new Rectangle(random.nextInt(rqt.bounds.width - testWidth), 504 random.nextInt(rqt.bounds.height - testHeight), 505 testWidth, testHeight); 506 if(!test(rqt, testRect, component)) 507 { 508 System.out.println("test failed for testRect = "+testRect); 509 allTestsSucceeded = false; 510 } 511 } 512 return(allTestsSucceeded); 513 } 514 515 private static boolean test(RectQuadTree rqt, Rectangle testRect, 516 Component[] component) 517 { 518 Collection rqtResults = rqt.getComponentsWithin(testRect); 519 Collection naiveResults = new HashSet(); 520 for(int i = 0 ; i < component.length; i++) 521 { 522 if(component[i].getBounds().intersects(testRect)) 523 { 524 naiveResults.add(component[i]); 525 } 526 } 527 //System.out.println("Rqt results: "+rqtResults); 528 //System.out.println("Naive results: "+naiveResults); 529 return(rqtResults.equals(naiveResults)); 530 } 531} 532