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